From 3ebed972c357e83b9d313ca3a64ef15038f6a7a6 Mon Sep 17 00:00:00 2001 From: reggie Date: Tue, 1 Oct 2024 15:10:55 -0500 Subject: [PATCH] Upload from latest rev on softwareheritage --- .editorconfig | 272 + .gitattributes | 4 + .github/FUNDING.yml | 1 + .github/ISSUE_TEMPLATE/bug_report.yml | 86 + .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.yml | 31 + .../missing_cpu_instruction.yml | 26 + .../ISSUE_TEMPLATE/missing_service_call.yml | 25 + .../missing_shader_instruction.yml | 19 + .github/csc.json | 18 + .github/dependabot.yml | 40 + .github/labeler.yml | 35 + .github/reviewers.yml | 25 + .github/workflows/build.yml | 163 + .github/workflows/checks.yml | 74 + .github/workflows/flatpak.yml | 212 + .github/workflows/mako.yml | 41 + .github/workflows/nightly_pr_comment.yml | 71 + .github/workflows/pr_triage.yml | 28 + .github/workflows/release.yml | 218 + .gitignore | 175 + CONTRIBUTING.md | 147 + Directory.Packages.props | 52 + LICENSE.txt | 9 + README.md | 170 + Ryujinx.sln | 265 + Ryujinx.sln.DotSettings | 23 + crowdin.yml | 3 + distribution/legal/THIRDPARTY.md | 713 ++ distribution/linux/Ryujinx.desktop | 14 + distribution/linux/Ryujinx.sh | 23 + distribution/linux/mime/Ryujinx.xml | 33 + distribution/linux/shortcut-template.desktop | 13 + distribution/macos/Info.plist | 169 + distribution/macos/Ryujinx.icns | Bin 0 -> 108982 bytes distribution/macos/bundle_fix_up.py | 609 ++ .../macos/construct_universal_dylib.py | 95 + distribution/macos/create_app_bundle.sh | 50 + distribution/macos/create_macos_build_ava.sh | 120 + .../macos/create_macos_build_headless.sh | 111 + distribution/macos/entitlements.xml | 23 + distribution/macos/shortcut-launch-script.sh | 8 + distribution/macos/shortcut-template.plist | 35 + distribution/macos/updater.sh | 57 + distribution/misc/Logo.svg | 1 + distribution/misc/add_tar_exec.py | 24 + distribution/windows/alsoft.ini | 2 + docs/README.md | 35 + docs/coding-guidelines/coding-style.md | 116 + docs/workflow/pr-guide.md | 56 + global.json | 6 + nuget.config | 7 + src/ARMeilleure/ARMeilleure.csproj | 26 + src/ARMeilleure/Allocators.cs | 39 + .../CodeGen/Arm64/Arm64Optimizer.cs | 270 + src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs | 49 + .../CodeGen/Arm64/ArmExtensionType.cs | 14 + src/ARMeilleure/CodeGen/Arm64/ArmShiftType.cs | 11 + src/ARMeilleure/CodeGen/Arm64/Assembler.cs | 1162 +++ .../CodeGen/Arm64/CallingConvention.cs | 96 + .../CodeGen/Arm64/CodeGenCommon.cs | 91 + .../CodeGen/Arm64/CodeGenContext.cs | 287 + .../CodeGen/Arm64/CodeGenerator.cs | 1581 +++ .../CodeGen/Arm64/CodeGeneratorIntrinsic.cs | 691 ++ .../CodeGen/Arm64/HardwareCapabilities.cs | 182 + .../CodeGen/Arm64/IntrinsicInfo.cs | 14 + .../CodeGen/Arm64/IntrinsicTable.cs | 465 + .../CodeGen/Arm64/IntrinsicType.cs | 60 + src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs | 887 ++ src/ARMeilleure/CodeGen/CompiledFunction.cs | 68 + src/ARMeilleure/CodeGen/Linking/RelocEntry.cs | 38 + src/ARMeilleure/CodeGen/Linking/RelocInfo.cs | 32 + src/ARMeilleure/CodeGen/Linking/Symbol.cs | 99 + src/ARMeilleure/CodeGen/Linking/SymbolType.cs | 28 + .../CodeGen/Optimizations/BlockPlacement.cs | 72 + .../CodeGen/Optimizations/ConstantFolding.cs | 346 + .../CodeGen/Optimizations/Optimizer.cs | 252 + .../CodeGen/Optimizations/Simplification.cs | 182 + .../CodeGen/Optimizations/TailMerge.cs | 83 + src/ARMeilleure/CodeGen/PreAllocatorCommon.cs | 57 + .../RegisterAllocators/AllocationResult.cs | 19 + .../RegisterAllocators/CopyResolver.cs | 249 + .../RegisterAllocators/HybridAllocator.cs | 454 + .../RegisterAllocators/IRegisterAllocator.cs | 12 + .../RegisterAllocators/LinearScanAllocator.cs | 1127 +++ .../RegisterAllocators/LiveInterval.cs | 419 + .../RegisterAllocators/LiveIntervalList.cs | 40 + .../CodeGen/RegisterAllocators/LiveRange.cs | 74 + .../RegisterAllocators/RegisterMasks.cs | 50 + .../RegisterAllocators/StackAllocator.cs | 25 + .../CodeGen/RegisterAllocators/UseList.cs | 86 + .../CodeGen/Unwinding/UnwindInfo.cs | 16 + .../CodeGen/Unwinding/UnwindPseudoOp.cs | 11 + .../CodeGen/Unwinding/UnwindPushEntry.cs | 20 + src/ARMeilleure/CodeGen/X86/Assembler.cs | 1580 +++ src/ARMeilleure/CodeGen/X86/AssemblerTable.cs | 299 + src/ARMeilleure/CodeGen/X86/CallConvName.cs | 8 + .../CodeGen/X86/CallingConvention.cs | 170 + src/ARMeilleure/CodeGen/X86/CodeGenCommon.cs | 19 + src/ARMeilleure/CodeGen/X86/CodeGenContext.cs | 105 + src/ARMeilleure/CodeGen/X86/CodeGenerator.cs | 1891 ++++ .../CodeGen/X86/HardwareCapabilities.cs | 144 + src/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs | 14 + src/ARMeilleure/CodeGen/X86/IntrinsicTable.cs | 202 + src/ARMeilleure/CodeGen/X86/IntrinsicType.cs | 18 + src/ARMeilleure/CodeGen/X86/Mxcsr.cs | 15 + src/ARMeilleure/CodeGen/X86/PreAllocator.cs | 788 ++ .../CodeGen/X86/PreAllocatorSystemV.cs | 333 + .../CodeGen/X86/PreAllocatorWindows.cs | 327 + src/ARMeilleure/CodeGen/X86/X86Condition.cs | 49 + src/ARMeilleure/CodeGen/X86/X86Instruction.cs | 231 + src/ARMeilleure/CodeGen/X86/X86Optimizer.cs | 259 + src/ARMeilleure/CodeGen/X86/X86Register.cs | 44 + src/ARMeilleure/Common/AddressTable.cs | 252 + src/ARMeilleure/Common/Allocator.cs | 24 + src/ARMeilleure/Common/ArenaAllocator.cs | 190 + src/ARMeilleure/Common/BitMap.cs | 222 + src/ARMeilleure/Common/BitUtils.cs | 57 + src/ARMeilleure/Common/Counter.cs | 98 + src/ARMeilleure/Common/EntryTable.cs | 188 + src/ARMeilleure/Common/EnumUtils.cs | 12 + src/ARMeilleure/Common/NativeAllocator.cs | 27 + src/ARMeilleure/Decoders/Block.cs | 101 + src/ARMeilleure/Decoders/Condition.cs | 32 + src/ARMeilleure/Decoders/DataOp.cs | 10 + src/ARMeilleure/Decoders/Decoder.cs | 393 + src/ARMeilleure/Decoders/DecoderHelper.cs | 167 + src/ARMeilleure/Decoders/DecoderMode.cs | 9 + src/ARMeilleure/Decoders/IOpCode.cs | 17 + src/ARMeilleure/Decoders/IOpCode32.cs | 9 + src/ARMeilleure/Decoders/IOpCode32Adr.cs | 9 + src/ARMeilleure/Decoders/IOpCode32Alu.cs | 8 + src/ARMeilleure/Decoders/IOpCode32AluBf.cs | 14 + src/ARMeilleure/Decoders/IOpCode32AluImm.cs | 9 + src/ARMeilleure/Decoders/IOpCode32AluImm16.cs | 7 + src/ARMeilleure/Decoders/IOpCode32AluMla.cs | 11 + src/ARMeilleure/Decoders/IOpCode32AluReg.cs | 7 + src/ARMeilleure/Decoders/IOpCode32AluRsImm.cs | 10 + src/ARMeilleure/Decoders/IOpCode32AluRsReg.cs | 10 + src/ARMeilleure/Decoders/IOpCode32AluUmull.cs | 13 + src/ARMeilleure/Decoders/IOpCode32AluUx.cs | 8 + src/ARMeilleure/Decoders/IOpCode32BImm.cs | 4 + src/ARMeilleure/Decoders/IOpCode32BReg.cs | 7 + .../Decoders/IOpCode32Exception.cs | 7 + .../Decoders/IOpCode32HasSetFlags.cs | 7 + src/ARMeilleure/Decoders/IOpCode32Mem.cs | 16 + src/ARMeilleure/Decoders/IOpCode32MemEx.cs | 7 + src/ARMeilleure/Decoders/IOpCode32MemMult.cs | 15 + src/ARMeilleure/Decoders/IOpCode32MemReg.cs | 7 + src/ARMeilleure/Decoders/IOpCode32MemRsImm.cs | 8 + src/ARMeilleure/Decoders/IOpCode32Simd.cs | 4 + src/ARMeilleure/Decoders/IOpCode32SimdImm.cs | 9 + src/ARMeilleure/Decoders/IOpCodeAlu.cs | 10 + src/ARMeilleure/Decoders/IOpCodeAluImm.cs | 7 + src/ARMeilleure/Decoders/IOpCodeAluRs.cs | 10 + src/ARMeilleure/Decoders/IOpCodeAluRx.cs | 10 + src/ARMeilleure/Decoders/IOpCodeBImm.cs | 7 + src/ARMeilleure/Decoders/IOpCodeCond.cs | 7 + src/ARMeilleure/Decoders/IOpCodeLit.cs | 11 + src/ARMeilleure/Decoders/IOpCodeSimd.cs | 7 + src/ARMeilleure/Decoders/InstDescriptor.cs | 18 + src/ARMeilleure/Decoders/InstEmitter.cs | 6 + src/ARMeilleure/Decoders/IntType.cs | 14 + src/ARMeilleure/Decoders/OpCode.cs | 48 + src/ARMeilleure/Decoders/OpCode32.cs | 34 + src/ARMeilleure/Decoders/OpCode32Alu.cs | 20 + src/ARMeilleure/Decoders/OpCode32AluBf.cs | 22 + src/ARMeilleure/Decoders/OpCode32AluImm.cs | 23 + src/ARMeilleure/Decoders/OpCode32AluImm16.cs | 17 + src/ARMeilleure/Decoders/OpCode32AluMla.cs | 30 + src/ARMeilleure/Decoders/OpCode32AluReg.cs | 14 + src/ARMeilleure/Decoders/OpCode32AluRsImm.cs | 20 + src/ARMeilleure/Decoders/OpCode32AluRsReg.cs | 20 + src/ARMeilleure/Decoders/OpCode32AluUmull.cs | 30 + src/ARMeilleure/Decoders/OpCode32AluUx.cs | 18 + src/ARMeilleure/Decoders/OpCode32BImm.cs | 29 + src/ARMeilleure/Decoders/OpCode32BReg.cs | 14 + src/ARMeilleure/Decoders/OpCode32Exception.cs | 14 + src/ARMeilleure/Decoders/OpCode32Mem.cs | 39 + src/ARMeilleure/Decoders/OpCode32MemImm.cs | 12 + src/ARMeilleure/Decoders/OpCode32MemImm8.cs | 15 + src/ARMeilleure/Decoders/OpCode32MemLdEx.cs | 14 + src/ARMeilleure/Decoders/OpCode32MemMult.cs | 52 + src/ARMeilleure/Decoders/OpCode32MemReg.cs | 14 + src/ARMeilleure/Decoders/OpCode32MemRsImm.cs | 18 + src/ARMeilleure/Decoders/OpCode32MemStEx.cs | 15 + src/ARMeilleure/Decoders/OpCode32Mrs.cs | 16 + src/ARMeilleure/Decoders/OpCode32MsrReg.cs | 29 + src/ARMeilleure/Decoders/OpCode32Sat.cs | 24 + src/ARMeilleure/Decoders/OpCode32Sat16.cs | 18 + src/ARMeilleure/Decoders/OpCode32Simd.cs | 33 + src/ARMeilleure/Decoders/OpCode32SimdBase.cs | 49 + .../Decoders/OpCode32SimdBinary.cs | 21 + src/ARMeilleure/Decoders/OpCode32SimdCmpZ.cs | 18 + .../Decoders/OpCode32SimdCvtFFixed.cs | 23 + src/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs | 24 + src/ARMeilleure/Decoders/OpCode32SimdCvtTB.cs | 44 + .../Decoders/OpCode32SimdDupElem.cs | 43 + src/ARMeilleure/Decoders/OpCode32SimdDupGP.cs | 36 + src/ARMeilleure/Decoders/OpCode32SimdExt.cs | 20 + src/ARMeilleure/Decoders/OpCode32SimdImm.cs | 38 + src/ARMeilleure/Decoders/OpCode32SimdImm44.cs | 41 + src/ARMeilleure/Decoders/OpCode32SimdLong.cs | 36 + .../Decoders/OpCode32SimdMemImm.cs | 40 + .../Decoders/OpCode32SimdMemMult.cs | 76 + .../Decoders/OpCode32SimdMemPair.cs | 50 + .../Decoders/OpCode32SimdMemSingle.cs | 51 + src/ARMeilleure/Decoders/OpCode32SimdMovGp.cs | 31 + .../Decoders/OpCode32SimdMovGpDouble.cs | 36 + .../Decoders/OpCode32SimdMovGpElem.cs | 51 + src/ARMeilleure/Decoders/OpCode32SimdMovn.cs | 13 + src/ARMeilleure/Decoders/OpCode32SimdReg.cs | 25 + .../Decoders/OpCode32SimdRegElem.cs | 31 + .../Decoders/OpCode32SimdRegElemLong.cs | 22 + .../Decoders/OpCode32SimdRegLong.cs | 24 + src/ARMeilleure/Decoders/OpCode32SimdRegS.cs | 23 + .../Decoders/OpCode32SimdRegWide.cs | 20 + src/ARMeilleure/Decoders/OpCode32SimdRev.cs | 23 + src/ARMeilleure/Decoders/OpCode32SimdS.cs | 39 + src/ARMeilleure/Decoders/OpCode32SimdSel.cs | 23 + src/ARMeilleure/Decoders/OpCode32SimdShImm.cs | 46 + .../Decoders/OpCode32SimdShImmLong.cs | 43 + .../Decoders/OpCode32SimdShImmNarrow.cs | 10 + .../Decoders/OpCode32SimdSpecial.cs | 19 + src/ARMeilleure/Decoders/OpCode32SimdSqrte.cs | 19 + src/ARMeilleure/Decoders/OpCode32SimdTbl.cs | 24 + src/ARMeilleure/Decoders/OpCode32System.cs | 28 + src/ARMeilleure/Decoders/OpCodeAdr.cs | 19 + src/ARMeilleure/Decoders/OpCodeAlu.cs | 23 + src/ARMeilleure/Decoders/OpCodeAluBinary.cs | 14 + src/ARMeilleure/Decoders/OpCodeAluImm.cs | 40 + src/ARMeilleure/Decoders/OpCodeAluRs.cs | 29 + src/ARMeilleure/Decoders/OpCodeAluRx.cs | 19 + src/ARMeilleure/Decoders/OpCodeBImm.cs | 11 + src/ARMeilleure/Decoders/OpCodeBImmAl.cs | 12 + src/ARMeilleure/Decoders/OpCodeBImmCmp.cs | 20 + src/ARMeilleure/Decoders/OpCodeBImmCond.cs | 25 + src/ARMeilleure/Decoders/OpCodeBImmTest.cs | 20 + src/ARMeilleure/Decoders/OpCodeBReg.cs | 24 + src/ARMeilleure/Decoders/OpCodeBfm.cs | 29 + src/ARMeilleure/Decoders/OpCodeCcmp.cs | 32 + src/ARMeilleure/Decoders/OpCodeCcmpImm.cs | 11 + src/ARMeilleure/Decoders/OpCodeCcmpReg.cs | 15 + src/ARMeilleure/Decoders/OpCodeCsel.cs | 17 + src/ARMeilleure/Decoders/OpCodeException.cs | 14 + src/ARMeilleure/Decoders/OpCodeMem.cs | 19 + src/ARMeilleure/Decoders/OpCodeMemEx.cs | 16 + src/ARMeilleure/Decoders/OpCodeMemImm.cs | 53 + src/ARMeilleure/Decoders/OpCodeMemLit.cs | 44 + src/ARMeilleure/Decoders/OpCodeMemPair.cs | 25 + src/ARMeilleure/Decoders/OpCodeMemReg.cs | 20 + src/ARMeilleure/Decoders/OpCodeMov.cs | 38 + src/ARMeilleure/Decoders/OpCodeMul.cs | 16 + src/ARMeilleure/Decoders/OpCodeSimd.cs | 24 + src/ARMeilleure/Decoders/OpCodeSimdCvt.cs | 21 + src/ARMeilleure/Decoders/OpCodeSimdExt.cs | 14 + src/ARMeilleure/Decoders/OpCodeSimdFcond.cs | 17 + src/ARMeilleure/Decoders/OpCodeSimdFmov.cs | 32 + src/ARMeilleure/Decoders/OpCodeSimdHelper.cs | 91 + src/ARMeilleure/Decoders/OpCodeSimdImm.cs | 110 + src/ARMeilleure/Decoders/OpCodeSimdIns.cs | 44 + src/ARMeilleure/Decoders/OpCodeSimdMemImm.cs | 28 + src/ARMeilleure/Decoders/OpCodeSimdMemLit.cs | 31 + src/ARMeilleure/Decoders/OpCodeSimdMemMs.cs | 71 + src/ARMeilleure/Decoders/OpCodeSimdMemPair.cs | 16 + src/ARMeilleure/Decoders/OpCodeSimdMemReg.cs | 21 + src/ARMeilleure/Decoders/OpCodeSimdMemSs.cs | 97 + src/ARMeilleure/Decoders/OpCodeSimdReg.cs | 18 + src/ARMeilleure/Decoders/OpCodeSimdRegElem.cs | 33 + .../Decoders/OpCodeSimdRegElemF.cs | 35 + src/ARMeilleure/Decoders/OpCodeSimdShImm.cs | 18 + src/ARMeilleure/Decoders/OpCodeSimdTbl.cs | 12 + src/ARMeilleure/Decoders/OpCodeSystem.cs | 24 + src/ARMeilleure/Decoders/OpCodeT16.cs | 15 + .../Decoders/OpCodeT16AddSubImm3.cs | 24 + .../Decoders/OpCodeT16AddSubReg.cs | 20 + src/ARMeilleure/Decoders/OpCodeT16AddSubSp.cs | 23 + src/ARMeilleure/Decoders/OpCodeT16Adr.cs | 19 + src/ARMeilleure/Decoders/OpCodeT16AluImm8.cs | 24 + .../Decoders/OpCodeT16AluImmZero.cs | 24 + .../Decoders/OpCodeT16AluRegHigh.cs | 20 + .../Decoders/OpCodeT16AluRegLow.cs | 20 + src/ARMeilleure/Decoders/OpCodeT16AluUx.cs | 22 + src/ARMeilleure/Decoders/OpCodeT16BImm11.cs | 15 + src/ARMeilleure/Decoders/OpCodeT16BImm8.cs | 17 + src/ARMeilleure/Decoders/OpCodeT16BImmCmp.cs | 19 + src/ARMeilleure/Decoders/OpCodeT16BReg.cs | 14 + .../Decoders/OpCodeT16Exception.cs | 14 + src/ARMeilleure/Decoders/OpCodeT16IfThen.cs | 33 + src/ARMeilleure/Decoders/OpCodeT16MemImm5.cs | 48 + src/ARMeilleure/Decoders/OpCodeT16MemLit.cs | 26 + src/ARMeilleure/Decoders/OpCodeT16MemMult.cs | 34 + src/ARMeilleure/Decoders/OpCodeT16MemReg.cs | 27 + src/ARMeilleure/Decoders/OpCodeT16MemSp.cs | 28 + src/ARMeilleure/Decoders/OpCodeT16MemStack.cs | 42 + src/ARMeilleure/Decoders/OpCodeT16ShiftImm.cs | 24 + src/ARMeilleure/Decoders/OpCodeT16ShiftReg.cs | 27 + src/ARMeilleure/Decoders/OpCodeT16SpRel.cs | 24 + src/ARMeilleure/Decoders/OpCodeT32.cs | 15 + src/ARMeilleure/Decoders/OpCodeT32Alu.cs | 20 + src/ARMeilleure/Decoders/OpCodeT32AluBf.cs | 22 + src/ARMeilleure/Decoders/OpCodeT32AluImm.cs | 38 + src/ARMeilleure/Decoders/OpCodeT32AluImm12.cs | 16 + src/ARMeilleure/Decoders/OpCodeT32AluMla.cs | 29 + src/ARMeilleure/Decoders/OpCodeT32AluReg.cs | 14 + src/ARMeilleure/Decoders/OpCodeT32AluRsImm.cs | 20 + src/ARMeilleure/Decoders/OpCodeT32AluUmull.cs | 28 + src/ARMeilleure/Decoders/OpCodeT32AluUx.cs | 18 + src/ARMeilleure/Decoders/OpCodeT32BImm20.cs | 27 + src/ARMeilleure/Decoders/OpCodeT32BImm24.cs | 35 + src/ARMeilleure/Decoders/OpCodeT32MemImm12.cs | 25 + src/ARMeilleure/Decoders/OpCodeT32MemImm8.cs | 29 + src/ARMeilleure/Decoders/OpCodeT32MemImm8D.cs | 31 + src/ARMeilleure/Decoders/OpCodeT32MemLdEx.cs | 26 + src/ARMeilleure/Decoders/OpCodeT32MemMult.cs | 52 + src/ARMeilleure/Decoders/OpCodeT32MemRsImm.cs | 30 + src/ARMeilleure/Decoders/OpCodeT32MemStEx.cs | 27 + src/ARMeilleure/Decoders/OpCodeT32MovImm16.cs | 14 + src/ARMeilleure/Decoders/OpCodeT32ShiftReg.cs | 19 + src/ARMeilleure/Decoders/OpCodeT32Tb.cs | 16 + src/ARMeilleure/Decoders/OpCodeTable.cs | 1528 +++ .../Decoders/Optimizations/TailCallRemover.cs | 88 + src/ARMeilleure/Decoders/RegisterSize.cs | 10 + src/ARMeilleure/Decoders/ShiftType.cs | 10 + src/ARMeilleure/Diagnostics/IRDumper.cs | 327 + src/ARMeilleure/Diagnostics/Logger.cs | 56 + src/ARMeilleure/Diagnostics/PassName.cs | 19 + src/ARMeilleure/Diagnostics/Symbols.cs | 85 + .../Diagnostics/TranslatorEventSource.cs | 67 + src/ARMeilleure/Instructions/CryptoHelper.cs | 282 + src/ARMeilleure/Instructions/InstEmitAlu.cs | 399 + src/ARMeilleure/Instructions/InstEmitAlu32.cs | 1241 +++ .../Instructions/InstEmitAluHelper.cs | 652 ++ src/ARMeilleure/Instructions/InstEmitBfm.cs | 196 + src/ARMeilleure/Instructions/InstEmitCcmp.cs | 60 + src/ARMeilleure/Instructions/InstEmitCsel.cs | 52 + src/ARMeilleure/Instructions/InstEmitDiv.cs | 66 + .../Instructions/InstEmitException.cs | 55 + .../Instructions/InstEmitException32.cs | 39 + src/ARMeilleure/Instructions/InstEmitFlow.cs | 107 + .../Instructions/InstEmitFlow32.cs | 136 + .../Instructions/InstEmitFlowHelper.cs | 240 + src/ARMeilleure/Instructions/InstEmitHash.cs | 69 + .../Instructions/InstEmitHash32.cs | 53 + .../Instructions/InstEmitHashHelper.cs | 124 + .../Instructions/InstEmitHelper.cs | 249 + .../Instructions/InstEmitMemory.cs | 184 + .../Instructions/InstEmitMemory32.cs | 264 + .../Instructions/InstEmitMemoryEx.cs | 177 + .../Instructions/InstEmitMemoryEx32.cs | 237 + .../Instructions/InstEmitMemoryExHelper.cs | 173 + .../Instructions/InstEmitMemoryHelper.cs | 778 ++ src/ARMeilleure/Instructions/InstEmitMove.cs | 41 + src/ARMeilleure/Instructions/InstEmitMul.cs | 101 + src/ARMeilleure/Instructions/InstEmitMul32.cs | 378 + .../Instructions/InstEmitSimdArithmetic.cs | 5284 ++++++++++ .../Instructions/InstEmitSimdArithmetic32.cs | 1736 ++++ .../Instructions/InstEmitSimdCmp.cs | 798 ++ .../Instructions/InstEmitSimdCmp32.cs | 437 + .../Instructions/InstEmitSimdCrypto.cs | 115 + .../Instructions/InstEmitSimdCrypto32.cs | 115 + .../Instructions/InstEmitSimdCvt.cs | 1890 ++++ .../Instructions/InstEmitSimdCvt32.cs | 874 ++ .../Instructions/InstEmitSimdHash.cs | 147 + .../Instructions/InstEmitSimdHash32.cs | 64 + .../Instructions/InstEmitSimdHashHelper.cs | 56 + .../Instructions/InstEmitSimdHelper.cs | 2108 ++++ .../Instructions/InstEmitSimdHelper32.cs | 1320 +++ .../Instructions/InstEmitSimdHelper32Arm64.cs | 372 + .../Instructions/InstEmitSimdHelperArm64.cs | 720 ++ .../Instructions/InstEmitSimdLogical.cs | 613 ++ .../Instructions/InstEmitSimdLogical32.cs | 278 + .../Instructions/InstEmitSimdMemory.cs | 162 + .../Instructions/InstEmitSimdMemory32.cs | 352 + .../Instructions/InstEmitSimdMove.cs | 877 ++ .../Instructions/InstEmitSimdMove32.cs | 675 ++ .../Instructions/InstEmitSimdShift.cs | 1935 ++++ .../Instructions/InstEmitSimdShift32.cs | 450 + .../Instructions/InstEmitSystem.cs | 278 + .../Instructions/InstEmitSystem32.cs | 338 + src/ARMeilleure/Instructions/InstName.cs | 699 ++ .../Instructions/NativeInterface.cs | 195 + src/ARMeilleure/Instructions/SoftFallback.cs | 648 ++ src/ARMeilleure/Instructions/SoftFloat.cs | 3526 +++++++ .../IntermediateRepresentation/BasicBlock.cs | 156 + .../BasicBlockFrequency.cs | 8 + .../IntermediateRepresentation/Comparison.cs | 24 + .../IIntrusiveListNode.cs | 8 + .../IntermediateRepresentation/Instruction.cs | 72 + .../IntermediateRepresentation/Intrinsic.cs | 641 ++ .../IntrusiveList.cs | 208 + .../MemoryOperand.cs | 54 + .../IntermediateRepresentation/Multiplier.cs | 11 + .../IntermediateRepresentation/Operand.cs | 598 ++ .../IntermediateRepresentation/OperandKind.cs | 13 + .../IntermediateRepresentation/OperandType.cs | 62 + .../IntermediateRepresentation/Operation.cs | 378 + .../PhiOperation.cs | 37 + .../IntermediateRepresentation/Register.cs | 43 + .../RegisterType.cs | 10 + src/ARMeilleure/Memory/IJitMemoryAllocator.cs | 8 + src/ARMeilleure/Memory/IJitMemoryBlock.cs | 15 + src/ARMeilleure/Memory/IMemoryManager.cs | 99 + .../Memory/InvalidAccessException.cs | 23 + src/ARMeilleure/Memory/MemoryManagerType.cs | 63 + src/ARMeilleure/Memory/ReservedRegion.cs | 58 + src/ARMeilleure/Native/JitSupportDarwin.cs | 13 + .../libs/libarmeilleure-jitsupport.dylib | Bin 0 -> 33564 bytes .../Native/macos_jit_support/Makefile | 8 + .../Native/macos_jit_support/support.c | 14 + src/ARMeilleure/Optimizations.cs | 70 + .../Signal/NativeSignalHandlerGenerator.cs | 341 + src/ARMeilleure/Signal/TestMethods.cs | 84 + .../Signal/WindowsPartialUnmapHandler.cs | 197 + src/ARMeilleure/State/Aarch32Mode.cs | 15 + src/ARMeilleure/State/ExceptionCallback.cs | 5 + src/ARMeilleure/State/ExecutionContext.cs | 175 + src/ARMeilleure/State/ExecutionMode.cs | 9 + src/ARMeilleure/State/FPCR.cs | 22 + src/ARMeilleure/State/FPException.cs | 12 + src/ARMeilleure/State/FPRoundingMode.cs | 11 + src/ARMeilleure/State/FPSCR.cs | 15 + src/ARMeilleure/State/FPSR.cs | 18 + src/ARMeilleure/State/FPState.cs | 31 + src/ARMeilleure/State/FPType.cs | 11 + src/ARMeilleure/State/ICounter.cs | 18 + src/ARMeilleure/State/NativeContext.cs | 269 + src/ARMeilleure/State/PState.cs | 17 + src/ARMeilleure/State/RegisterAlias.cs | 42 + src/ARMeilleure/State/RegisterConsts.cs | 15 + src/ARMeilleure/State/V128.cs | 316 + src/ARMeilleure/Statistics.cs | 96 + .../Translation/ArmEmitterContext.cs | 286 + .../Translation/Cache/CacheEntry.cs | 26 + .../Translation/Cache/CacheMemoryAllocator.cs | 96 + src/ARMeilleure/Translation/Cache/JitCache.cs | 207 + .../Translation/Cache/JitCacheInvalidation.cs | 79 + .../Translation/Cache/JitUnwindWindows.cs | 190 + src/ARMeilleure/Translation/Compiler.cs | 68 + .../Translation/CompilerContext.cs | 26 + .../Translation/CompilerOptions.cs | 17 + .../Translation/ControlFlowGraph.cs | 164 + src/ARMeilleure/Translation/DelegateInfo.cs | 19 + src/ARMeilleure/Translation/Delegates.cs | 610 ++ .../Translation/DispatcherFunction.cs | 7 + src/ARMeilleure/Translation/Dominance.cs | 95 + src/ARMeilleure/Translation/EmitterContext.cs | 680 ++ src/ARMeilleure/Translation/GuestFunction.cs | 6 + src/ARMeilleure/Translation/IntervalTree.cs | 745 ++ .../Translation/PTC/EncodingCache.cs | 9 + .../Translation/PTC/IPtcLoadState.cs | 10 + src/ARMeilleure/Translation/PTC/Ptc.cs | 1142 +++ .../Translation/PTC/PtcFormatter.cs | 199 + .../Translation/PTC/PtcLoadingState.cs | 9 + .../Translation/PTC/PtcProfiler.cs | 441 + src/ARMeilleure/Translation/PTC/PtcState.cs | 10 + .../Translation/RegisterToLocal.cs | 52 + src/ARMeilleure/Translation/RegisterUsage.cs | 406 + src/ARMeilleure/Translation/RejitRequest.cs | 16 + .../Translation/SsaConstruction.cs | 289 + .../Translation/SsaDeconstruction.cs | 48 + .../Translation/TranslatedFunction.cs | 34 + src/ARMeilleure/Translation/Translator.cs | 582 ++ .../Translation/TranslatorCache.cs | 95 + .../Translation/TranslatorQueue.cs | 124 + .../Translation/TranslatorStubs.cs | 313 + .../Translation/TranslatorTestMethods.cs | 147 + .../OpenALAudioBuffer.cs | 9 + .../OpenALHardwareDeviceDriver.cs | 189 + .../OpenALHardwareDeviceSession.cs | 212 + .../Ryujinx.Audio.Backends.OpenAL.csproj | 15 + .../Ryujinx.Audio.Backends.SDL2.csproj | 13 + .../SDL2AudioBuffer.cs | 16 + .../SDL2HardwareDeviceDriver.cs | 208 + .../SDL2HardwareDeviceSession.cs | 234 + .../Native/SoundIo.cs | 178 + .../Native/SoundIoBackend.cs | 13 + .../Native/SoundIoChannelId.cs | 75 + .../Native/SoundIoContext.cs | 106 + .../Native/SoundIoDeviceAim.cs | 8 + .../Native/SoundIoDeviceContext.cs | 49 + .../Native/SoundIoError.cs | 22 + .../Native/SoundIoException.cs | 11 + .../Native/SoundIoFormat.cs | 25 + .../Native/SoundIoOutStreamContext.cs | 164 + .../Native/libsoundio/libs/libsoundio.dll | Bin 0 -> 85504 bytes .../Native/libsoundio/libs/libsoundio.dylib | Bin 0 -> 54976 bytes .../Native/libsoundio/libs/libsoundio.so | Bin 0 -> 88584 bytes .../Ryujinx.Audio.Backends.SoundIo.csproj | 28 + .../SoundIoAudioBuffer.cs | 16 + .../SoundIoHardwareDeviceDriver.cs | 266 + .../SoundIoHardwareDeviceSession.cs | 451 + src/Ryujinx.Audio/AudioManager.cs | 133 + .../Backends/Common/BackendHelper.cs | 26 + .../Backends/Common/DynamicRingBuffer.cs | 173 + .../Common/HardwareDeviceSessionOutputBase.cs | 76 + .../CompatLayerHardwareDeviceDriver.cs | 190 + .../CompatLayerHardwareDeviceSession.cs | 162 + .../Backends/CompatLayer/Downmixing.cs | 129 + .../Dummy/DummyHardwareDeviceDriver.cs | 92 + .../Dummy/DummyHardwareDeviceSessionInput.cs | 67 + .../Dummy/DummyHardwareDeviceSessionOutput.cs | 62 + src/Ryujinx.Audio/Common/AudioBuffer.cs | 37 + .../Common/AudioDeviceSession.cs | 516 + src/Ryujinx.Audio/Common/AudioDeviceState.cs | 18 + .../Common/AudioInputConfiguration.cs | 29 + .../Common/AudioOutputConfiguration.cs | 37 + src/Ryujinx.Audio/Common/AudioUserBuffer.cs | 36 + src/Ryujinx.Audio/Common/SampleFormat.cs | 43 + src/Ryujinx.Audio/Constants.cs | 175 + src/Ryujinx.Audio/Input/AudioInputManager.cs | 264 + src/Ryujinx.Audio/Input/AudioInputSystem.cs | 396 + .../Integration/HardwareDeviceImpl.cs | 76 + .../Integration/IHardwareDevice.cs | 55 + .../Integration/IHardwareDeviceDriver.cs | 38 + .../Integration/IHardwareDeviceSession.cs | 28 + .../Integration/IWritableEvent.cs | 18 + .../Output/AudioOutputManager.cs | 258 + src/Ryujinx.Audio/Output/AudioOutputSystem.cs | 369 + .../Common/AuxiliaryBufferAddresses.cs | 13 + .../Renderer/Common/BehaviourParameter.cs | 50 + .../Renderer/Common/EdgeMatrix.cs | 150 + .../Renderer/Common/EffectType.cs | 58 + .../Renderer/Common/MemoryPoolUserState.cs | 43 + .../Renderer/Common/NodeIdHelper.cs | 28 + .../Renderer/Common/NodeIdType.cs | 33 + .../Renderer/Common/NodeStates.cs | 230 + .../Renderer/Common/PerformanceDetailType.cs | 20 + .../Renderer/Common/PerformanceEntryType.cs | 11 + .../Renderer/Common/PlayState.cs | 23 + .../Renderer/Common/ReverbEarlyMode.cs | 33 + .../Renderer/Common/ReverbLateMode.cs | 38 + src/Ryujinx.Audio/Renderer/Common/SinkType.cs | 23 + .../Renderer/Common/UpdateDataHeader.cs | 36 + .../Renderer/Common/VoiceUpdateState.cs | 103 + .../Renderer/Common/WaveBuffer.cs | 81 + .../Renderer/Common/WorkBufferAllocator.cs | 61 + .../Renderer/Device/VirtualDevice.cs | 89 + .../Renderer/Device/VirtualDeviceSession.cs | 27 + .../Device/VirtualDeviceSessionRegistry.cs | 81 + src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs | 222 + .../Renderer/Dsp/AudioProcessor.cs | 247 + .../Renderer/Dsp/BiquadFilterHelper.cs | 307 + .../Command/AdpcmDataSourceCommandVersion1.cs | 77 + .../Dsp/Command/AuxiliaryBufferCommand.cs | 182 + .../Dsp/Command/BiquadFilterAndMixCommand.cs | 123 + .../Dsp/Command/BiquadFilterCommand.cs | 58 + .../Dsp/Command/CaptureBufferCommand.cs | 136 + .../Dsp/Command/CircularBufferSinkCommand.cs | 76 + .../Dsp/Command/ClearMixBufferCommand.cs | 24 + .../Renderer/Dsp/Command/CommandList.cs | 156 + .../Renderer/Dsp/Command/CommandType.cs | 39 + .../Renderer/Dsp/Command/CompressorCommand.cs | 174 + .../Dsp/Command/CopyMixBufferCommand.cs | 30 + .../Dsp/Command/DataSourceVersion2Command.cs | 106 + .../Renderer/Dsp/Command/DelayCommand.cs | 280 + .../Dsp/Command/DepopForMixBuffersCommand.cs | 90 + .../Dsp/Command/DepopPrepareCommand.cs | 57 + .../Renderer/Dsp/Command/DeviceSinkCommand.cs | 103 + .../Command/DownMixSurroundToStereoCommand.cs | 68 + .../Renderer/Dsp/Command/ICommand.cs | 20 + .../Dsp/Command/LimiterCommandVersion1.cs | 145 + .../Dsp/Command/LimiterCommandVersion2.cs | 171 + .../Renderer/Dsp/Command/MixCommand.cs | 137 + .../Renderer/Dsp/Command/MixRampCommand.cs | 68 + .../Dsp/Command/MixRampGroupedCommand.cs | 103 + .../MultiTapBiquadFilterAndMixCommand.cs | 145 + .../Command/MultiTapBiquadFilterCommand.cs | 62 + .../PcmFloatDataSourceCommandVersion1.cs | 76 + .../PcmInt16DataSourceCommandVersion1.cs | 76 + .../Dsp/Command/PerformanceCommand.cs | 47 + .../Renderer/Dsp/Command/Reverb3dCommand.cs | 254 + .../Renderer/Dsp/Command/ReverbCommand.cs | 284 + .../Renderer/Dsp/Command/UpsampleCommand.cs | 70 + .../Renderer/Dsp/Command/VolumeCommand.cs | 137 + .../Renderer/Dsp/Command/VolumeRampCommand.cs | 56 + .../Renderer/Dsp/DataSourceHelper.cs | 466 + .../Renderer/Dsp/Effect/DecayDelay.cs | 52 + .../Renderer/Dsp/Effect/DelayLine.cs | 78 + .../Renderer/Dsp/Effect/DelayLineReverb3d.cs | 76 + .../Dsp/Effect/ExponentialMovingAverage.cs | 24 + .../Renderer/Dsp/Effect/IDelayLine.cs | 37 + .../Renderer/Dsp/FixedPointHelper.cs | 39 + .../Renderer/Dsp/FloatingPointHelper.cs | 103 + src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs | 134 + .../Renderer/Dsp/ResamplerHelper.cs | 599 ++ .../Renderer/Dsp/State/AdpcmLoopContext.cs | 12 + .../Dsp/State/AuxiliaryBufferHeader.cs | 74 + .../Renderer/Dsp/State/BiquadFilterState.cs | 17 + .../Renderer/Dsp/State/CompressorState.cs | 51 + .../Renderer/Dsp/State/DelayState.cs | 67 + .../Renderer/Dsp/State/LimiterState.cs | 31 + .../Renderer/Dsp/State/Reverb3dState.cs | 119 + .../Renderer/Dsp/State/ReverbState.cs | 204 + .../Renderer/Dsp/UpsamplerHelper.cs | 192 + .../Parameter/AudioRendererConfiguration.cs | 99 + .../Parameter/BehaviourErrorInfoOutStatus.cs | 30 + .../Parameter/BiquadFilterParameter.cs | 34 + .../Effect/AuxiliaryBufferParameter.cs | 84 + .../Effect/BiquadFilterEffectParameter.cs | 44 + .../Parameter/Effect/BufferMixerParameter.cs | 32 + .../Parameter/Effect/CompressorParameter.cs | 115 + .../Parameter/Effect/DelayParameter.cs | 101 + .../Parameter/Effect/LimiterParameter.cs | 138 + .../Parameter/Effect/LimiterStatistics.cs | 31 + .../Parameter/Effect/Reverb3dParameter.cs | 127 + .../Parameter/Effect/ReverbParameter.cs | 119 + .../Parameter/EffectInParameterVersion1.cs | 97 + .../Parameter/EffectInParameterVersion2.cs | 97 + .../Parameter/EffectOutStatusVersion1.cs | 23 + .../Parameter/EffectOutStatusVersion2.cs | 28 + .../Renderer/Parameter/EffectResultState.cs | 26 + .../Renderer/Parameter/EffectState.cs | 18 + .../Renderer/Parameter/IEffectInParameter.cs | 53 + .../Renderer/Parameter/IEffectOutStatus.cs | 13 + .../ISplitterDestinationInParameter.cs | 43 + .../Parameter/MemoryPoolInParameter.cs | 33 + .../Renderer/Parameter/MemoryPoolOutStatus.cs | 22 + .../MixInParameterDirtyOnlyUpdate.cs | 27 + .../Renderer/Parameter/MixParameter.cs | 95 + .../Performance/PerformanceInParameter.cs | 21 + .../Performance/PerformanceOutStatus.cs | 21 + .../Parameter/RendererInfoOutStatus.cs | 21 + .../Parameter/Sink/CircularBufferParameter.cs | 61 + .../Parameter/Sink/DeviceParameter.cs | 58 + .../Renderer/Parameter/SinkInParameter.cs | 53 + .../Renderer/Parameter/SinkOutStatus.cs | 26 + .../SplitterDestinationInParameterVersion1.cs | 76 + .../SplitterDestinationInParameterVersion2.cs | 81 + .../Renderer/Parameter/SplitterInParameter.cs | 46 + .../Parameter/SplitterInParameterHeader.cs | 45 + .../VoiceChannelResourceInParameter.cs | 28 + .../Renderer/Parameter/VoiceInParameter.cs | 332 + .../Renderer/Parameter/VoiceOutStatus.cs | 35 + .../Renderer/Server/AudioRenderSystem.cs | 897 ++ .../Renderer/Server/AudioRendererManager.cs | 391 + .../Renderer/Server/BehaviourContext.cs | 469 + .../Renderer/Server/CommandBuffer.cs | 682 ++ .../Renderer/Server/CommandGenerator.cs | 1262 +++ .../CommandProcessingTimeEstimatorVersion1.cs | 198 + .../CommandProcessingTimeEstimatorVersion2.cs | 490 + .../CommandProcessingTimeEstimatorVersion3.cs | 660 ++ .../CommandProcessingTimeEstimatorVersion4.cs | 47 + .../CommandProcessingTimeEstimatorVersion5.cs | 262 + .../Server/Effect/AuxiliaryBufferEffect.cs | 85 + .../Renderer/Server/Effect/BaseEffect.cs | 262 + .../Server/Effect/BiquadFilterEffect.cs | 67 + .../Renderer/Server/Effect/BufferMixEffect.cs | 49 + .../Server/Effect/CaptureBufferEffect.cs | 82 + .../Server/Effect/CompressorEffect.cs | 67 + .../Renderer/Server/Effect/DelayEffect.cs | 93 + .../Renderer/Server/Effect/EffectContext.cs | 123 + .../Renderer/Server/Effect/LimiterEffect.cs | 95 + .../Renderer/Server/Effect/Reverb3dEffect.cs | 92 + .../Renderer/Server/Effect/ReverbEffect.cs | 95 + .../Renderer/Server/Effect/UsageState.cs | 28 + .../Server/ICommandProcessingTimeEstimator.cs | 42 + .../Renderer/Server/MemoryPool/AddressInfo.cs | 133 + .../Server/MemoryPool/MemoryPoolState.cs | 130 + .../Renderer/Server/MemoryPool/PoolMapper.cs | 365 + .../Renderer/Server/Mix/MixContext.cs | 257 + .../Renderer/Server/Mix/MixState.cs | 312 + .../Performance/IPerformanceDetailEntry.cs | 52 + .../Server/Performance/IPerformanceEntry.cs | 46 + .../Server/Performance/IPerformanceHeader.cs | 80 + .../Performance/PerformanceDetailVersion1.cs | 72 + .../Performance/PerformanceDetailVersion2.cs | 72 + .../Performance/PerformanceEntryAddresses.cs | 56 + .../Performance/PerformanceEntryVersion1.cs | 62 + .../Performance/PerformanceEntryVersion2.cs | 62 + .../PerformanceFrameHeaderVersion1.cs | 101 + .../PerformanceFrameHeaderVersion2.cs | 117 + .../Server/Performance/PerformanceManager.cs | 98 + .../Performance/PerformanceManagerGeneric.cs | 308 + .../Renderer/Server/RendererSystemContext.cs | 48 + .../Renderer/Server/Sink/BaseSink.cs | 102 + .../Server/Sink/CircularBufferSink.cs | 109 + .../Renderer/Server/Sink/DeviceSink.cs | 75 + .../Renderer/Server/Sink/SinkContext.cs | 56 + .../Server/Splitter/SplitterContext.cs | 426 + .../Server/Splitter/SplitterDestination.cs | 368 + .../Splitter/SplitterDestinationVersion1.cs | 206 + .../Splitter/SplitterDestinationVersion2.cs | 250 + .../Renderer/Server/Splitter/SplitterState.cs | 243 + .../Renderer/Server/StateUpdater.cs | 598 ++ .../Types/AudioRendererExecutionMode.cs | 19 + .../Types/AudioRendererRenderingDevice.cs | 24 + .../Renderer/Server/Types/PlayState.cs | 39 + .../Server/Upsampler/UpsamplerBufferState.cs | 14 + .../Server/Upsampler/UpsamplerManager.cs | 84 + .../Server/Upsampler/UpsamplerState.cs | 68 + .../Server/Voice/VoiceChannelResource.cs | 40 + .../Renderer/Server/Voice/VoiceContext.cs | 149 + .../Renderer/Server/Voice/VoiceState.cs | 713 ++ .../Renderer/Server/Voice/WaveBuffer.cs | 105 + .../Utils/AudioProcessorMemoryManager.cs | 57 + src/Ryujinx.Audio/Renderer/Utils/BitArray.cs | 103 + .../Renderer/Utils/FileHardwareDevice.cs | 99 + src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs | 56 + .../Renderer/Utils/Math/Matrix2x2.cs | 71 + .../Renderer/Utils/Math/Matrix6x6.cs | 97 + .../Renderer/Utils/Math/MatrixHelper.cs | 45 + .../Renderer/Utils/Math/Vector6.cs | 56 + .../Renderer/Utils/SpanIOHelper.cs | 171 + .../Renderer/Utils/SpanMemoryManager.cs | 43 + .../Renderer/Utils/SplitterHardwareDevice.cs | 59 + src/Ryujinx.Audio/ResultCode.cs | 22 + src/Ryujinx.Audio/Ryujinx.Audio.csproj | 14 + src/Ryujinx.Common/AsyncWorkQueue.cs | 102 + .../Collections/IntervalTree.cs | 499 + .../Collections/IntrusiveRedBlackTree.cs | 280 + .../Collections/IntrusiveRedBlackTreeImpl.cs | 354 + .../Collections/IntrusiveRedBlackTreeNode.cs | 16 + .../Collections/TreeDictionary.cs | 620 ++ .../Configuration/AntiAliasing.cs | 16 + .../Configuration/AppDataManager.cs | 326 + .../Configuration/AspectRatioExtensions.cs | 69 + .../Configuration/BackendThreading.cs | 13 + .../DownloadableContentContainer.cs | 13 + ...ownloadableContentJsonSerializerContext.cs | 11 + .../Configuration/DownloadableContentNca.cs | 14 + .../Configuration/GraphicsBackend.cs | 12 + .../Configuration/GraphicsDebugLevel.cs | 14 + .../Hid/Controller/GamepadInputId.cs | 58 + .../GenericControllerInputConfig.cs | 82 + .../Controller/JoyconConfigControllerStick.cs | 11 + .../Motion/CemuHookMotionConfigController.cs | 30 + .../JsonMotionConfigControllerConverter.cs | 79 + .../Motion/MotionConfigController.cs | 25 + .../MotionConfigJsonSerializerContext.cs | 12 + .../Motion/MotionInputBackendType.cs | 13 + .../Motion/StandardMotionConfigController.cs | 4 + .../Hid/Controller/RumbleConfigController.cs | 20 + .../StandardControllerInputConfig.cs | 4 + .../Hid/Controller/StickInputId.cs | 15 + .../Configuration/Hid/ControllerType.cs | 23 + .../Hid/GenericInputConfigurationCommon.cs | 15 + .../Configuration/Hid/InputBackendType.cs | 13 + .../Configuration/Hid/InputConfig.cs | 41 + .../Hid/InputConfigJsonSerializerContext.cs | 14 + .../Hid/JsonInputConfigConverter.cs | 81 + src/Ryujinx.Common/Configuration/Hid/Key.cs | 143 + .../Keyboard/GenericKeyboardInputConfig.cs | 15 + .../Hid/Keyboard/JoyconConfigKeyboardStick.cs | 11 + .../Keyboard/StandardKeyboardInputConfig.cs | 4 + .../Configuration/Hid/KeyboardHotkeys.cs | 15 + .../Hid/LeftJoyconCommonConfig.cs | 15 + .../Configuration/Hid/PlayerIndex.cs | 22 + .../Hid/RightJoyconCommonConfig.cs | 15 + .../Configuration/HideCursorMode.cs | 9 + .../Configuration/MemoryManagerMode.cs | 13 + src/Ryujinx.Common/Configuration/Mod.cs | 9 + .../Configuration/ModMetadata.cs | 14 + .../ModMetadataJsonSerializerContext.cs | 10 + .../Multiplayer/MultiplayerMode.cs | 8 + .../Configuration/ScalingFilter.cs | 13 + .../Configuration/TitleUpdateMetadata.cs | 10 + ...itleUpdateMetadataJsonSerializerContext.cs | 10 + .../Extensions/BinaryReaderExtensions.cs | 14 + .../Extensions/BinaryWriterExtensions.cs | 27 + .../Extensions/SequenceReaderExtensions.cs | 181 + .../Extensions/StreamExtensions.cs | 138 + .../GraphicsDriver/DriverUtilities.cs | 42 + .../GraphicsDriver/NVAPI/Nvapi.cs | 11 + .../NVAPI/NvapiUnicodeString.cs | 42 + .../NVAPI/NvdrsApplicationV4.cs | 17 + .../GraphicsDriver/NVAPI/NvdrsProfile.cs | 15 + .../GraphicsDriver/NVAPI/NvdrsSetting.cs | 49 + .../GraphicsDriver/NVThreadedOptimization.cs | 160 + src/Ryujinx.Common/Hash128.cs | 48 + .../Logging/Formatters/DefaultLogFormatter.cs | 51 + .../Formatters/DynamicObjectFormatter.cs | 84 + .../Logging/Formatters/ILogFormatter.cs | 7 + src/Ryujinx.Common/Logging/LogClass.cs | 76 + src/Ryujinx.Common/Logging/LogEventArgs.cs | 23 + .../Logging/LogEventArgsJson.cs | 31 + .../Logging/LogEventJsonSerializerContext.cs | 9 + src/Ryujinx.Common/Logging/LogLevel.cs | 19 + src/Ryujinx.Common/Logging/Logger.cs | 246 + .../Logging/Targets/AsyncLogTargetWrapper.cs | 82 + .../Logging/Targets/ConsoleLogTarget.cs | 44 + .../Logging/Targets/FileLogTarget.cs | 108 + .../Logging/Targets/ILogTarget.cs | 11 + .../Logging/Targets/JsonLogTarget.cs | 42 + src/Ryujinx.Common/Memory/ArrayPtr.cs | 123 + src/Ryujinx.Common/Memory/Box.cs | 12 + src/Ryujinx.Common/Memory/IArray.cs | 21 + src/Ryujinx.Common/Memory/MemoryOwner.cs | 140 + .../Memory/MemoryStreamManager.cs | 99 + .../PartialUnmaps/NativeReaderWriterLock.cs | 85 + .../PartialUnmaps/PartialUnmapHelpers.cs | 20 + .../Memory/PartialUnmaps/PartialUnmapState.cs | 161 + .../Memory/PartialUnmaps/ThreadLocalMap.cs | 91 + src/Ryujinx.Common/Memory/Ptr.cs | 68 + src/Ryujinx.Common/Memory/SpanOwner.cs | 114 + src/Ryujinx.Common/Memory/SpanReader.cs | 74 + src/Ryujinx.Common/Memory/SpanWriter.cs | 45 + .../Memory/StructArrayHelpers.cs | 847 ++ .../Memory/StructByteArrayHelpers.cs | 89 + src/Ryujinx.Common/PerformanceCounter.cs | 82 + src/Ryujinx.Common/Pools/ObjectPool.cs | 75 + src/Ryujinx.Common/Pools/SharedPools.cs | 17 + src/Ryujinx.Common/Pools/ThreadStaticArray.cs | 17 + .../PreciseSleep/IPreciseSleepEvent.cs | 38 + src/Ryujinx.Common/PreciseSleep/Nanosleep.cs | 160 + .../PreciseSleep/NanosleepEvent.cs | 84 + .../PreciseSleep/NanosleepPool.cs | 228 + .../PreciseSleep/PreciseSleepHelper.cs | 104 + src/Ryujinx.Common/PreciseSleep/SleepEvent.cs | 51 + .../PreciseSleep/WindowsGranularTimer.cs | 220 + .../PreciseSleep/WindowsSleepEvent.cs | 92 + src/Ryujinx.Common/ReactiveObject.cs | 61 + .../ReferenceEqualityComparer.cs | 19 + src/Ryujinx.Common/ReleaseInformation.cs | 31 + src/Ryujinx.Common/Ryujinx.Common.csproj | 15 + .../SystemInterop/DisplaySleep.cs | 35 + .../SystemInterop/ForceDpiAware.cs | 96 + .../SystemInterop/GdiPlusHelper.cs | 76 + .../SystemInterop/StdErrAdapter.cs | 104 + .../WindowsMultimediaTimerResolution.cs | 114 + src/Ryujinx.Common/Utilities/BitUtils.cs | 59 + .../Utilities/BitfieldExtensions.cs | 57 + src/Ryujinx.Common/Utilities/Buffers.cs | 59 + .../Utilities/CommonJsonContext.cs | 11 + .../Utilities/EmbeddedResources.cs | 152 + .../Utilities/FileSystemUtils.cs | 56 + src/Ryujinx.Common/Utilities/HexUtils.cs | 90 + src/Ryujinx.Common/Utilities/JsonHelper.cs | 98 + .../Utilities/MessagePackObjectFormatter.cs | 298 + .../Utilities/NetworkHelpers.cs | 83 + src/Ryujinx.Common/Utilities/OsUtils.cs | 24 + src/Ryujinx.Common/Utilities/SpanHelpers.cs | 61 + src/Ryujinx.Common/Utilities/StreamUtils.cs | 89 + .../Utilities/TypedStringEnumConverter.cs | 37 + src/Ryujinx.Common/Utilities/UInt128Utils.cs | 18 + src/Ryujinx.Common/XXHash128.cs | 548 ++ src/Ryujinx.Cpu/AddressSpace.cs | 76 + src/Ryujinx.Cpu/AppleHv/Arm/ApFlags.cs | 27 + src/Ryujinx.Cpu/AppleHv/Arm/ExceptionClass.cs | 47 + .../AppleHv/DummyDiskCacheLoadState.cs | 17 + src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs | 129 + .../AppleHv/HvAddressSpaceRange.cs | 370 + src/Ryujinx.Cpu/AppleHv/HvApi.cs | 329 + src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs | 48 + src/Ryujinx.Cpu/AppleHv/HvEngine.cs | 22 + src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 305 + .../AppleHv/HvExecutionContextShadow.cs | 50 + .../AppleHv/HvExecutionContextVcpu.cs | 188 + src/Ryujinx.Cpu/AppleHv/HvIpaAllocator.cs | 34 + .../AppleHv/HvMemoryBlockAllocation.cs | 36 + .../AppleHv/HvMemoryBlockAllocator.cs | 58 + src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs | 392 + src/Ryujinx.Cpu/AppleHv/HvVcpu.cs | 56 + src/Ryujinx.Cpu/AppleHv/HvVcpuPool.cs | 107 + src/Ryujinx.Cpu/AppleHv/HvVm.cs | 70 + .../AppleHv/IHvExecutionContext.cs | 43 + src/Ryujinx.Cpu/AppleHv/TimeApi.cs | 21 + src/Ryujinx.Cpu/DummyDiskCacheLoadState.cs | 17 + src/Ryujinx.Cpu/ExceptionCallbacks.cs | 64 + src/Ryujinx.Cpu/ICpuContext.cs | 63 + src/Ryujinx.Cpu/ICpuEngine.cs | 18 + src/Ryujinx.Cpu/IDiskCacheState.cs | 20 + src/Ryujinx.Cpu/IExecutionContext.cs | 112 + src/Ryujinx.Cpu/ITickSource.cs | 31 + .../IVirtualMemoryManagerTracked.cs | 57 + .../AddressIntrusiveRedBlackTree.cs | 35 + .../Jit/HostTracked/AddressSpacePartition.cs | 708 ++ .../AddressSpacePartitionAllocator.cs | 202 + .../AddressSpacePartitionMultiAllocation.cs | 101 + .../HostTracked/AddressSpacePartitioned.cs | 407 + .../Jit/HostTracked/NativePageTable.cs | 223 + src/Ryujinx.Cpu/Jit/JitCpuContext.cs | 65 + src/Ryujinx.Cpu/Jit/JitDiskCacheLoadState.cs | 38 + src/Ryujinx.Cpu/Jit/JitEngine.cs | 20 + src/Ryujinx.Cpu/Jit/JitExecutionContext.cs | 123 + src/Ryujinx.Cpu/Jit/JitMemoryAllocator.cs | 18 + src/Ryujinx.Cpu/Jit/JitMemoryBlock.cs | 29 + src/Ryujinx.Cpu/Jit/MemoryManager.cs | 512 + .../Jit/MemoryManagerHostMapped.cs | 462 + .../Jit/MemoryManagerHostTracked.cs | 634 ++ src/Ryujinx.Cpu/LightningJit/AarchCompiler.cs | 32 + src/Ryujinx.Cpu/LightningJit/AddressForm.cs | 18 + .../LightningJit/Arm32/A32Compiler.cs | 30 + src/Ryujinx.Cpu/LightningJit/Arm32/Block.cs | 106 + .../LightningJit/Arm32/BranchType.cs | 15 + .../LightningJit/Arm32/CodeGenContext.cs | 198 + src/Ryujinx.Cpu/LightningJit/Arm32/Decoder.cs | 556 ++ .../LightningJit/Arm32/IInstEmit.cs | 1231 +++ .../LightningJit/Arm32/ImmUtils.cs | 137 + .../LightningJit/Arm32/InstDecoders.cs | 2927 ++++++ .../LightningJit/Arm32/InstFlags.cs | 63 + .../LightningJit/Arm32/InstInfo.cs | 20 + .../LightningJit/Arm32/InstInfoForTable.cs | 79 + .../LightningJit/Arm32/InstMeta.cs | 22 + .../LightningJit/Arm32/InstName.cs | 562 ++ .../LightningJit/Arm32/InstTableA32.cs | 1194 +++ .../LightningJit/Arm32/InstTableT16.cs | 146 + .../LightningJit/Arm32/InstTableT32.cs | 1212 +++ .../LightningJit/Arm32/MultiBlock.cs | 34 + .../LightningJit/Arm32/PendingBranch.cs | 20 + .../LightningJit/Arm32/RegisterAllocator.cs | 170 + .../LightningJit/Arm32/RegisterUtils.cs | 109 + .../LightningJit/Arm32/ScopedRegister.cs | 39 + .../Arm32/Target/Arm64/Compiler.cs | 808 ++ .../Arm32/Target/Arm64/InstEmit.cs | 8502 +++++++++++++++++ .../Arm32/Target/Arm64/InstEmitAbsDiff.cs | 87 + .../Arm32/Target/Arm64/InstEmitAlu.cs | 1105 +++ .../Arm32/Target/Arm64/InstEmitBit.cs | 103 + .../Arm32/Target/Arm64/InstEmitCommon.cs | 263 + .../Arm32/Target/Arm64/InstEmitCrc32.cs | 26 + .../Arm32/Target/Arm64/InstEmitDivide.cs | 25 + .../Arm32/Target/Arm64/InstEmitExtension.cs | 191 + .../Arm32/Target/Arm64/InstEmitFlow.cs | 256 + .../Arm32/Target/Arm64/InstEmitGE.cs | 265 + .../Arm32/Target/Arm64/InstEmitHalve.cs | 178 + .../Arm32/Target/Arm64/InstEmitMemory.cs | 1184 +++ .../Arm32/Target/Arm64/InstEmitMove.cs | 349 + .../Arm32/Target/Arm64/InstEmitMultiply.cs | 603 ++ .../Target/Arm64/InstEmitNeonArithmetic.cs | 344 + .../Arm32/Target/Arm64/InstEmitNeonBit.cs | 35 + .../Arm32/Target/Arm64/InstEmitNeonCommon.cs | 1513 +++ .../Arm32/Target/Arm64/InstEmitNeonCompare.cs | 126 + .../Arm32/Target/Arm64/InstEmitNeonConvert.cs | 137 + .../Arm32/Target/Arm64/InstEmitNeonCrypto.cs | 43 + .../Arm32/Target/Arm64/InstEmitNeonHash.cs | 97 + .../Arm32/Target/Arm64/InstEmitNeonLogical.cs | 79 + .../Arm32/Target/Arm64/InstEmitNeonMemory.cs | 797 ++ .../Arm32/Target/Arm64/InstEmitNeonMove.cs | 665 ++ .../Arm32/Target/Arm64/InstEmitNeonRound.cs | 105 + .../Target/Arm64/InstEmitNeonSaturate.cs | 205 + .../Arm32/Target/Arm64/InstEmitNeonShift.cs | 123 + .../Arm32/Target/Arm64/InstEmitNeonSystem.cs | 77 + .../Arm32/Target/Arm64/InstEmitSaturate.cs | 462 + .../Arm32/Target/Arm64/InstEmitSystem.cs | 660 ++ .../Target/Arm64/InstEmitVfpArithmetic.cs | 95 + .../Arm32/Target/Arm64/InstEmitVfpCompare.cs | 133 + .../Arm32/Target/Arm64/InstEmitVfpConvert.cs | 305 + .../Arm32/Target/Arm64/InstEmitVfpMove.cs | 22 + .../Arm32/Target/Arm64/InstEmitVfpRound.cs | 40 + .../LightningJit/Arm64/A64Compiler.cs | 29 + src/Ryujinx.Cpu/LightningJit/Arm64/Block.cs | 138 + .../LightningJit/Arm64/ImmUtils.cs | 20 + .../LightningJit/Arm64/InstFlags.cs | 108 + .../LightningJit/Arm64/InstInfo.cs | 22 + .../LightningJit/Arm64/InstName.cs | 1167 +++ .../LightningJit/Arm64/MultiBlock.cs | 64 + .../LightningJit/Arm64/RegisterAllocator.cs | 161 + .../LightningJit/Arm64/RegisterUtils.cs | 495 + .../LightningJit/Arm64/SysUtils.cs | 48 + .../Arm64/Target/Arm64/Compiler.cs | 743 ++ .../Arm64/Target/Arm64/Decoder.cs | 392 + .../Arm64/Target/Arm64/InstEmitMemory.cs | 641 ++ .../Arm64/Target/Arm64/InstEmitSystem.cs | 617 ++ .../Arm64/Target/Arm64/InstTable.cs | 1605 ++++ .../LightningJit/Cache/CacheEntry.cs | 22 + .../Cache/CacheMemoryAllocator.cs | 136 + .../LightningJit/Cache/JitCache.cs | 197 + .../Cache/JitCacheInvalidation.cs | 79 + .../LightningJit/Cache/JitSupportDarwin.cs | 16 + .../LightningJit/Cache/NoWxCache.cs | 340 + .../Cache/PageAlignedRangeList.cs | 218 + .../CodeGen/Arm64/AbiConstants.cs | 15 + .../CodeGen/Arm64/ArmCondition.cs | 30 + .../CodeGen/Arm64/ArmExtensionType.cs | 14 + .../CodeGen/Arm64/ArmShiftType.cs | 11 + .../LightningJit/CodeGen/Arm64/Assembler.cs | 4777 +++++++++ .../CodeGen/Arm64/CodeGenCommon.cs | 67 + .../CodeGen/Arm64/RegisterSaveRestore.cs | 252 + .../LightningJit/CodeGen/Arm64/StackWalker.cs | 30 + .../LightningJit/CodeGen/Arm64/TailMerger.cs | 120 + .../LightningJit/CodeGen/Operand.cs | 38 + .../LightningJit/CodeGen/OperandKind.cs | 10 + .../LightningJit/CodeGen/OperandType.cs | 35 + .../LightningJit/CodeGen/Register.cs | 42 + .../LightningJit/CodeGen/RegisterType.cs | 8 + src/Ryujinx.Cpu/LightningJit/CodeWriter.cs | 61 + .../LightningJit/CompiledFunction.cs | 16 + src/Ryujinx.Cpu/LightningJit/CpuPreset.cs | 14 + src/Ryujinx.Cpu/LightningJit/CpuPresets.cs | 13 + .../LightningJit/Graph/DataFlow.cs | 171 + src/Ryujinx.Cpu/LightningJit/Graph/IBlock.cs | 17 + .../LightningJit/Graph/IBlockList.cs | 9 + .../LightningJit/Graph/RegisterMask.cs | 60 + .../LightningJit/Graph/RegisterUse.cs | 24 + src/Ryujinx.Cpu/LightningJit/IStackWalker.cs | 10 + src/Ryujinx.Cpu/LightningJit/IsaFeature.cs | 65 + src/Ryujinx.Cpu/LightningJit/IsaVersion.cs | 17 + .../LightningJit/LightningJitCpuContext.cs | 58 + .../LightningJit/LightningJitEngine.cs | 20 + .../LightningJit/NativeContextOffsets.cs | 17 + .../LightningJit/NativeInterface.cs | 93 + .../LightningJit/State/ExecutionContext.cs | 153 + .../LightningJit/State/ExecutionMode.cs | 9 + .../LightningJit/State/NativeContext.cs | 173 + .../LightningJit/Table/IInstInfo.cs | 12 + .../LightningJit/Table/InstEncoding.cs | 14 + .../LightningJit/Table/InstTableLevel.cs | 96 + .../LightningJit/TranslatedFunction.cs | 16 + src/Ryujinx.Cpu/LightningJit/Translator.cs | 227 + .../LightningJit/TranslatorCache.cs | 96 + .../LightningJit/TranslatorStubs.cs | 380 + src/Ryujinx.Cpu/LoadState.cs | 12 + src/Ryujinx.Cpu/ManagedPageFlags.cs | 389 + src/Ryujinx.Cpu/MemoryEhMeilleure.cs | 85 + src/Ryujinx.Cpu/MemoryHelper.cs | 61 + src/Ryujinx.Cpu/PrivateMemoryAllocation.cs | 41 + src/Ryujinx.Cpu/PrivateMemoryAllocator.cs | 268 + src/Ryujinx.Cpu/Ryujinx.Cpu.csproj | 13 + src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs | 184 + .../Signal/UnixSignalHandlerRegistration.cs | 83 + .../WindowsSignalHandlerRegistration.cs | 24 + src/Ryujinx.Cpu/TickSource.cs | 45 + .../VirtualMemoryManagerRefCountedBase.cs | 32 + .../DeviceMemoryManager.cs | 396 + src/Ryujinx.Graphics.Device/DeviceState.cs | 165 + src/Ryujinx.Graphics.Device/IDeviceState.cs | 8 + .../IDeviceStateWithContext.cs | 9 + .../ISynchronizationManager.cs | 39 + src/Ryujinx.Graphics.Device/RwCallback.cs | 16 + .../Ryujinx.Graphics.Device.csproj | 11 + src/Ryujinx.Graphics.GAL/AddressMode.cs | 14 + .../AdvancedBlendDescriptor.cs | 16 + src/Ryujinx.Graphics.GAL/AdvancedBlendOp.cs | 52 + .../AdvancedBlendOverlap.cs | 9 + src/Ryujinx.Graphics.GAL/AntiAliasing.cs | 12 + src/Ryujinx.Graphics.GAL/BlendDescriptor.cs | 35 + src/Ryujinx.Graphics.GAL/BlendFactor.cs | 62 + src/Ryujinx.Graphics.GAL/BlendOp.cs | 17 + src/Ryujinx.Graphics.GAL/BufferAccess.cs | 18 + src/Ryujinx.Graphics.GAL/BufferAssignment.cs | 14 + src/Ryujinx.Graphics.GAL/BufferHandle.cs | 14 + src/Ryujinx.Graphics.GAL/BufferRange.cs | 23 + src/Ryujinx.Graphics.GAL/Capabilities.cs | 198 + src/Ryujinx.Graphics.GAL/ColorF.cs | 4 + src/Ryujinx.Graphics.GAL/CompareMode.cs | 8 + src/Ryujinx.Graphics.GAL/CompareOp.cs | 23 + src/Ryujinx.Graphics.GAL/CounterType.cs | 9 + src/Ryujinx.Graphics.GAL/DepthMode.cs | 8 + src/Ryujinx.Graphics.GAL/DepthStencilMode.cs | 8 + .../DepthTestDescriptor.cs | 20 + src/Ryujinx.Graphics.GAL/DeviceInfo.cs | 18 + src/Ryujinx.Graphics.GAL/Extents2D.cs | 31 + src/Ryujinx.Graphics.GAL/Extents2DF.cs | 18 + src/Ryujinx.Graphics.GAL/Face.cs | 9 + src/Ryujinx.Graphics.GAL/Format.cs | 746 ++ src/Ryujinx.Graphics.GAL/FrontFace.cs | 8 + src/Ryujinx.Graphics.GAL/HardwareInfo.cs | 16 + src/Ryujinx.Graphics.GAL/ICounterEvent.cs | 13 + src/Ryujinx.Graphics.GAL/IImageArray.cs | 10 + src/Ryujinx.Graphics.GAL/IPipeline.cs | 114 + src/Ryujinx.Graphics.GAL/IProgram.cs | 11 + src/Ryujinx.Graphics.GAL/IRenderer.cs | 68 + src/Ryujinx.Graphics.GAL/ISampler.cs | 6 + src/Ryujinx.Graphics.GAL/ITexture.cs | 50 + src/Ryujinx.Graphics.GAL/ITextureArray.cs | 10 + src/Ryujinx.Graphics.GAL/IWindow.cs | 18 + src/Ryujinx.Graphics.GAL/ImageCrop.cs | 37 + src/Ryujinx.Graphics.GAL/IndexType.cs | 9 + src/Ryujinx.Graphics.GAL/LogicalOp.cs | 22 + src/Ryujinx.Graphics.GAL/MagFilter.cs | 8 + src/Ryujinx.Graphics.GAL/MinFilter.cs | 12 + .../MultisampleDescriptor.cs | 19 + .../Multithreading/BufferMap.cs | 188 + .../Multithreading/CommandHelper.cs | 166 + .../Multithreading/CommandType.cs | 117 + .../Multithreading/Commands/BarrierCommand.cs | 12 + .../Commands/BeginTransformFeedbackCommand.cs | 18 + .../Commands/Buffer/BufferDisposeCommand.cs | 19 + .../Commands/Buffer/BufferGetDataCommand.cs | 28 + .../Commands/Buffer/BufferSetDataCommand.cs | 27 + .../Commands/ClearBufferCommand.cs | 24 + .../Commands/ClearRenderTargetColorCommand.cs | 26 + .../ClearRenderTargetDepthStencilCommand.cs | 28 + .../Commands/CommandBufferBarrierCommand.cs | 12 + .../Commands/CopyBufferCommand.cs | 26 + .../CounterEventDisposeCommand.cs | 21 + .../CounterEvent/CounterEventFlushCommand.cs | 21 + .../Commands/DispatchComputeCommand.cs | 22 + .../Multithreading/Commands/DrawCommand.cs | 26 + .../Commands/DrawIndexedCommand.cs | 24 + .../Commands/DrawIndexedIndirectCommand.cs | 18 + .../DrawIndexedIndirectCountCommand.cs | 29 + .../Commands/DrawIndirectCommand.cs | 18 + .../Commands/DrawIndirectCountCommand.cs | 29 + .../Commands/DrawTextureCommand.cs | 31 + .../EndHostConditionalRenderingCommand.cs | 12 + .../Commands/EndTransformFeedbackCommand.cs | 12 + .../Multithreading/Commands/IGALCommand.cs | 12 + .../ImageArray/ImageArrayDisposeCommand.cs | 21 + .../ImageArray/ImageArraySetFormatsCommand.cs | 26 + .../ImageArray/ImageArraySetImagesCommand.cs | 27 + .../Program/ProgramCheckLinkCommand.cs | 27 + .../Commands/Program/ProgramDisposeCommand.cs | 21 + .../Program/ProgramGetBinaryCommand.cs | 25 + .../Commands/Renderer/ActionCommand.cs | 21 + .../Renderer/CreateBufferAccessCommand.cs | 22 + .../Renderer/CreateBufferSparseCommand.cs | 25 + .../Renderer/CreateHostBufferCommand.cs | 22 + .../Renderer/CreateImageArrayCommand.cs | 25 + .../Commands/Renderer/CreateProgramCommand.cs | 28 + .../Commands/Renderer/CreateSamplerCommand.cs | 23 + .../Commands/Renderer/CreateSyncCommand.cs | 22 + .../Renderer/CreateTextureArrayCommand.cs | 25 + .../Commands/Renderer/CreateTextureCommand.cs | 23 + .../Renderer/GetCapabilitiesCommand.cs | 20 + .../Commands/Renderer/PreFrameCommand.cs | 12 + .../Commands/Renderer/ReportCounterCommand.cs | 32 + .../Commands/Renderer/ResetCounterCommand.cs | 18 + .../Renderer/UpdateCountersCommand.cs | 12 + .../Commands/Sampler/SamplerDisposeCommand.cs | 21 + .../Commands/SetAlphaTestCommand.cs | 22 + .../Commands/SetBlendStateAdvancedCommand.cs | 18 + .../Commands/SetBlendStateCommand.cs | 20 + .../Commands/SetDepthBiasCommand.cs | 24 + .../Commands/SetDepthClampCommand.cs | 18 + .../Commands/SetDepthModeCommand.cs | 18 + .../Commands/SetDepthTestCommand.cs | 18 + .../Commands/SetFaceCullingCommand.cs | 20 + .../Commands/SetFrontFaceCommand.cs | 18 + .../Commands/SetImageArrayCommand.cs | 26 + .../Commands/SetImageArraySeparateCommand.cs | 26 + .../Commands/SetImageCommand.cs | 28 + .../Commands/SetIndexBufferCommand.cs | 21 + .../Commands/SetLineParametersCommand.cs | 20 + .../Commands/SetLogicOpStateCommand.cs | 20 + .../Commands/SetMultisampleStateCommand.cs | 18 + .../Commands/SetPatchParametersCommand.cs | 25 + .../Commands/SetPointParametersCommand.cs | 24 + .../Commands/SetPolygonModeCommand.cs | 20 + .../Commands/SetPrimitiveRestartCommand.cs | 20 + .../Commands/SetPrimitiveTopologyCommand.cs | 18 + .../Commands/SetProgramCommand.cs | 25 + .../Commands/SetRasterizerDiscardCommand.cs | 18 + .../SetRenderTargetColorMasksCommand.cs | 23 + .../Commands/SetRenderTargetsCommand.cs | 24 + .../Commands/SetScissorsCommand.cs | 22 + .../Commands/SetStencilTestCommand.cs | 18 + .../Commands/SetStorageBuffersCommand.cs | 23 + .../Commands/SetTextureAndSamplerCommand.cs | 28 + .../Commands/SetTextureArrayCommand.cs | 26 + .../SetTextureArraySeparateCommand.cs | 26 + .../SetTransformFeedbackBuffersCommand.cs | 23 + .../Commands/SetUniformBuffersCommand.cs | 23 + .../Commands/SetUserClipDistanceCommand.cs | 20 + .../Commands/SetVertexAttribsCommand.cs | 23 + .../Commands/SetVertexBuffersCommand.cs | 23 + .../Commands/SetViewportsCommand.cs | 23 + .../Texture/TextureCopyToBufferCommand.cs | 29 + .../Commands/Texture/TextureCopyToCommand.cs | 28 + .../Texture/TextureCopyToScaledCommand.cs | 30 + .../Texture/TextureCopyToSliceCommand.cs | 32 + .../Texture/TextureCreateViewCommand.cs | 30 + .../Commands/Texture/TextureGetDataCommand.cs | 25 + .../Texture/TextureGetDataSliceCommand.cs | 29 + .../Commands/Texture/TextureReleaseCommand.cs | 21 + .../Commands/Texture/TextureSetDataCommand.cs | 25 + .../Texture/TextureSetDataSliceCommand.cs | 29 + .../TextureSetDataSliceRegionCommand.cs | 31 + .../Texture/TextureSetStorageCommand.cs | 23 + .../TextureArrayDisposeCommand.cs | 21 + .../TextureArraySetSamplersCommand.cs | 27 + .../TextureArraySetTexturesCommand.cs | 27 + .../Commands/TextureBarrierCommand.cs | 12 + .../Commands/TextureBarrierTiledCommand.cs | 12 + .../TryHostConditionalRenderingCommand.cs | 25 + ...TryHostConditionalRenderingFlushCommand.cs | 25 + .../Commands/Window/WindowPresentCommand.cs | 27 + .../Multithreading/Model/CircularSpanPool.cs | 89 + .../Multithreading/Model/ResultBox.cs | 7 + .../Multithreading/Model/SpanRef.cs | 39 + .../Multithreading/Model/TableRef.cs | 22 + .../Multithreading/Resources/ProgramQueue.cs | 107 + .../Programs/BinaryProgramRequest.cs | 25 + .../Resources/Programs/IProgramRequest.cs | 8 + .../Programs/SourceProgramRequest.cs | 23 + .../Resources/ThreadedCounterEvent.cs | 80 + .../Resources/ThreadedImageArray.cs | 42 + .../Resources/ThreadedProgram.cs | 48 + .../Resources/ThreadedSampler.cs | 22 + .../Resources/ThreadedTexture.cs | 146 + .../Resources/ThreadedTextureArray.cs | 43 + .../Multithreading/SyncMap.cs | 62 + .../Multithreading/ThreadedHelpers.cs | 28 + .../Multithreading/ThreadedPipeline.cs | 390 + .../Multithreading/ThreadedRenderer.cs | 546 ++ .../Multithreading/ThreadedWindow.cs | 44 + src/Ryujinx.Graphics.GAL/Origin.cs | 8 + src/Ryujinx.Graphics.GAL/PinnedSpan.cs | 53 + src/Ryujinx.Graphics.GAL/PolygonMode.cs | 9 + src/Ryujinx.Graphics.GAL/PolygonModeMask.cs | 12 + src/Ryujinx.Graphics.GAL/PrimitiveTopology.cs | 21 + src/Ryujinx.Graphics.GAL/ProgramLinkStatus.cs | 9 + .../ProgramPipelineState.cs | 80 + src/Ryujinx.Graphics.GAL/Rectangle.cs | 18 + src/Ryujinx.Graphics.GAL/ResourceLayout.cs | 203 + .../Ryujinx.Graphics.GAL.csproj | 20 + src/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs | 72 + .../ScreenCaptureImageInfo.cs | 22 + src/Ryujinx.Graphics.GAL/ShaderInfo.cs | 26 + src/Ryujinx.Graphics.GAL/ShaderSource.cs | 29 + src/Ryujinx.Graphics.GAL/StencilOp.cs | 23 + .../StencilTestDescriptor.cs | 56 + src/Ryujinx.Graphics.GAL/SwizzleComponent.cs | 12 + src/Ryujinx.Graphics.GAL/SystemMemoryType.cs | 29 + src/Ryujinx.Graphics.GAL/Target.cs | 34 + src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs | 159 + .../TextureReleaseCallback.cs | 4 + src/Ryujinx.Graphics.GAL/UpscaleType.cs | 9 + .../VertexAttribDescriptor.cs | 4 + .../VertexBufferDescriptor.cs | 17 + src/Ryujinx.Graphics.GAL/Viewport.cs | 33 + src/Ryujinx.Graphics.GAL/ViewportSwizzle.cs | 19 + src/Ryujinx.Graphics.Gpu/ClassId.cs | 15 + src/Ryujinx.Graphics.Gpu/Constants.cs | 98 + .../Engine/Compute/ComputeClass.cs | 208 + .../Engine/Compute/ComputeClassState.cs | 435 + .../Engine/Compute/ComputeQmd.cs | 275 + .../Engine/ConditionalRenderEnabled.cs | 12 + .../Engine/DeviceStateWithShadow.cs | 101 + .../Engine/Dma/DmaClass.cs | 707 ++ .../Engine/Dma/DmaClassState.cs | 271 + .../Engine/Dma/DmaTexture.cs | 20 + .../Engine/GPFifo/CompressedMethod.cs | 41 + .../Engine/GPFifo/GPEntry.cs | 55 + .../Engine/GPFifo/GPFifoClass.cs | 251 + .../Engine/GPFifo/GPFifoClassState.cs | 233 + .../Engine/GPFifo/GPFifoDevice.cs | 261 + .../Engine/GPFifo/GPFifoProcessor.cs | 351 + .../InlineToMemory/InlineToMemoryClass.cs | 275 + .../InlineToMemoryClassState.cs | 183 + .../Engine/MME/AluOperation.cs | 15 + .../Engine/MME/AluRegOperation.cs | 18 + .../Engine/MME/AssignmentOperation.cs | 17 + .../Engine/MME/IMacroEE.cs | 52 + src/Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs | 101 + .../Engine/MME/MacroHLE.cs | 550 ++ .../Engine/MME/MacroHLEFunctionName.cs | 24 + .../Engine/MME/MacroHLETable.cs | 116 + .../Engine/MME/MacroInterpreter.cs | 405 + .../Engine/MME/MacroJit.cs | 39 + .../Engine/MME/MacroJitCompiler.cs | 517 + .../Engine/MME/MacroJitContext.cs | 55 + .../Engine/MmeShadowScratch.cs | 18 + .../Engine/SetMmeShadowRamControlMode.cs | 31 + .../Engine/ShaderTexture.cs | 113 + .../Threed/Blender/AdvancedBlendFunctions.cs | 4226 ++++++++ .../Threed/Blender/AdvancedBlendManager.cs | 115 + .../Blender/AdvancedBlendPreGenTable.cs | 273 + .../Threed/Blender/AdvancedBlendUcode.cs | 126 + .../Engine/Threed/Blender/UcodeAssembler.cs | 305 + .../ComputeDraw/VertexInfoBufferUpdater.cs | 141 + .../Engine/Threed/ComputeDraw/VtgAsCompute.cs | 96 + .../Threed/ComputeDraw/VtgAsComputeContext.cs | 651 ++ .../Threed/ComputeDraw/VtgAsComputeState.cs | 536 ++ .../Engine/Threed/ConditionalRendering.cs | 130 + .../Engine/Threed/ConstantBufferUpdater.cs | 183 + .../Engine/Threed/DrawManager.cs | 986 ++ .../Engine/Threed/DrawState.cs | 81 + .../Engine/Threed/IbStreamer.cs | 192 + .../Engine/Threed/IndirectDrawType.cs | 41 + .../Engine/Threed/RenderTargetUpdateFlags.cs | 46 + .../Engine/Threed/SemaphoreUpdater.cs | 195 + .../Threed/SpecializationStateUpdater.cs | 391 + .../Engine/Threed/StateUpdateTracker.cs | 180 + .../Engine/Threed/StateUpdater.cs | 1617 ++++ .../Engine/Threed/ThreedClass.cs | 850 ++ .../Engine/Threed/ThreedClassState.cs | 1057 ++ .../Engine/Twod/TwodClass.cs | 386 + .../Engine/Twod/TwodClassState.cs | 816 ++ .../Engine/Twod/TwodTexture.cs | 22 + .../Engine/Types/Boolean32.cs | 20 + .../Engine/Types/ColorFormat.cs | 169 + .../Engine/Types/GpuVa.cs | 22 + .../Engine/Types/MemoryLayout.cs | 37 + .../Engine/Types/PrimitiveType.cs | 103 + .../Engine/Types/SamplerIndex.cs | 11 + .../Engine/Types/SbDescriptor.cs | 20 + .../Engine/Types/ZetaFormat.cs | 44 + src/Ryujinx.Graphics.Gpu/GpuChannel.cs | 150 + src/Ryujinx.Graphics.Gpu/GpuContext.cs | 456 + src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs | 77 + .../Image/AutoDeleteCache.cs | 297 + src/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs | 72 + src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs | 715 ++ .../Image/ITextureDescriptor.cs | 10 + src/Ryujinx.Graphics.Gpu/Image/Pool.cs | 254 + src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs | 130 + .../Image/ReductionFilter.cs | 15 + src/Ryujinx.Graphics.Gpu/Image/Sampler.cs | 115 + .../Image/SamplerDescriptor.cs | 266 + .../Image/SamplerMinFilter.cs | 11 + .../Image/SamplerMipFilter.cs | 12 + src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs | 162 + .../Image/SamplerPoolCache.cs | 30 + src/Ryujinx.Graphics.Gpu/Image/Texture.cs | 1771 ++++ .../Image/TextureBindingInfo.cs | 104 + .../Image/TextureBindingsArrayCache.cs | 1183 +++ .../Image/TextureBindingsManager.cs | 873 ++ .../Image/TextureCache.cs | 1454 +++ .../Image/TextureCompatibility.cs | 876 ++ .../Image/TextureComponent.cs | 40 + .../Image/TextureDependency.cs | 37 + .../Image/TextureDescriptor.cs | 278 + .../Image/TextureDescriptorType.cs | 16 + .../Image/TextureGroup.cs | 1703 ++++ .../Image/TextureGroupHandle.cs | 697 ++ src/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs | 411 + .../Image/TextureManager.cs | 591 ++ .../Image/TextureMatchQuality.cs | 9 + .../Image/TextureMsaaMode.cs | 68 + src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs | 642 ++ .../Image/TexturePoolCache.cs | 30 + .../Image/TextureScaleMode.cs | 16 + .../Image/TextureSearchFlags.cs | 19 + .../Image/TextureTarget.cs | 92 + .../Image/TextureViewCompatibility.cs | 15 + src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs | 1037 ++ .../Memory/BufferBackingState.cs | 294 + .../Memory/BufferBounds.cs | 58 + .../Memory/BufferCache.cs | 1096 +++ .../Memory/BufferCacheEntry.cs | 43 + .../Memory/BufferManager.cs | 940 ++ .../Memory/BufferMigration.cs | 251 + .../Memory/BufferModifiedRangeList.cs | 557 ++ .../Memory/BufferPreFlush.cs | 295 + .../Memory/BufferStage.cs | 99 + .../Memory/BufferTextureArrayBinding.cs | 66 + .../Memory/BufferTextureBinding.cs | 68 + .../Memory/BufferUpdater.cs | 123 + .../Memory/CounterCache.cs | 192 + .../Memory/GpuRegionHandle.cs | 101 + .../Memory/IndexBuffer.cs | 14 + .../Memory/MemoryManager.cs | 732 ++ .../Memory/MultiRangeBuffer.cs | 245 + .../Memory/MultiRangeWritableBlock.cs | 58 + .../Memory/PhysicalMemory.cs | 470 + src/Ryujinx.Graphics.Gpu/Memory/PteKind.cs | 268 + .../Memory/ResourceKind.cs | 13 + .../Memory/SupportBufferUpdater.cs | 224 + .../Memory/UnmapEventArgs.cs | 24 + .../Memory/VertexBuffer.cs | 14 + .../Memory/VirtualRangeCache.cs | 236 + .../Ryujinx.Graphics.Gpu.csproj | 17 + .../Shader/CachedShaderBindings.cs | 117 + .../Shader/CachedShaderProgram.cs | 79 + .../Shader/CachedShaderStage.cs | 38 + .../Shader/ComputeShaderCacheHashTable.cs | 71 + .../DiskCache/BackgroundDiskCacheWriter.cs | 138 + .../Shader/DiskCache/BinarySerializer.cs | 247 + .../Shader/DiskCache/CompressionAlgorithm.cs | 23 + .../Shader/DiskCache/DiskCacheCommon.cs | 57 + .../Shader/DiskCache/DiskCacheGpuAccessor.cs | 231 + .../Shader/DiskCache/DiskCacheGuestStorage.cs | 459 + .../Shader/DiskCache/DiskCacheHostStorage.cs | 870 ++ .../DiskCache/DiskCacheLoadException.cs | 48 + .../Shader/DiskCache/DiskCacheLoadResult.cs | 78 + .../DiskCache/DiskCacheOutputStreams.cs | 57 + .../Shader/DiskCache/GuestCodeAndCbData.cs | 29 + .../DiskCache/ParallelDiskCacheLoader.cs | 737 ++ .../DiskCache/ShaderBinarySerializer.cs | 50 + .../Shader/GpuAccessor.cs | 244 + .../Shader/GpuAccessorBase.cs | 309 + .../Shader/GpuAccessorState.cs | 69 + .../Shader/GpuChannelComputeState.cs | 65 + .../Shader/GpuChannelGraphicsState.cs | 190 + .../Shader/GpuChannelPoolState.cs | 60 + .../Shader/HashTable/HashState.cs | 113 + .../Shader/HashTable/IDataAccessor.cs | 27 + .../Shader/HashTable/PartitionHashTable.cs | 451 + .../Shader/HashTable/PartitionedHashTable.cs | 244 + .../Shader/HashTable/SmartDataAccessor.cs | 96 + .../Shader/ResourceCounts.cs | 33 + .../Shader/ShaderAddresses.cs | 64 + .../Shader/ShaderAsCompute.cs | 20 + .../Shader/ShaderCache.cs | 858 ++ .../Shader/ShaderCacheHashTable.cs | 282 + .../Shader/ShaderCacheState.cs | 15 + .../Shader/ShaderCodeAccessor.cs | 32 + .../Shader/ShaderDumpPaths.cs | 49 + .../Shader/ShaderDumper.cs | 129 + .../Shader/ShaderInfoBuilder.cs | 434 + .../Shader/ShaderSpecializationList.cs | 91 + .../Shader/ShaderSpecializationState.cs | 1052 ++ .../Shader/TransformFeedbackDescriptor.cs | 58 + .../Synchronization/HostSyncFlags.cs | 30 + .../Synchronization/ISyncActionHandler.cs | 22 + .../Synchronization/SynchronizationManager.cs | 109 + .../Synchronization/Syncpoint.cs | 125 + .../Synchronization/SyncpointWaiterHandle.cs | 10 + src/Ryujinx.Graphics.Gpu/Window.cs | 270 + src/Ryujinx.Graphics.Host1x/ClassId.cs | 20 + src/Ryujinx.Graphics.Host1x/Devices.cs | 32 + src/Ryujinx.Graphics.Host1x/Host1xClass.cs | 32 + .../Host1xClassRegisters.cs | 43 + src/Ryujinx.Graphics.Host1x/Host1xDevice.cs | 173 + src/Ryujinx.Graphics.Host1x/OpCode.cs | 21 + .../Ryujinx.Graphics.Host1x.csproj | 11 + .../SyncptIncrManager.cs | 99 + src/Ryujinx.Graphics.Host1x/ThiDevice.cs | 137 + src/Ryujinx.Graphics.Host1x/ThiRegisters.cs | 24 + .../FFmpegContext.cs | 175 + .../H264/Decoder.cs | 56 + .../H264/H264BitStreamWriter.cs | 121 + .../H264/SpsAndPpsReconstruction.cs | 159 + .../Native/AVCodec.cs | 26 + .../Native/AVCodec501.cs | 25 + .../Native/AVCodecContext.cs | 171 + .../Native/AVCodecID.cs | 8 + .../Native/AVFrame.cs | 37 + .../Native/AVLog.cs | 15 + .../Native/AVPacket.cs | 24 + .../Native/AVRational.cs | 8 + .../Native/FFCodec.cs | 21 + .../Native/FFCodecLegacy.cs | 23 + .../Native/FFmpegApi.cs | 128 + .../Ryujinx.Graphics.Nvdec.FFmpeg.csproj | 13 + src/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs | 41 + .../Vp8/Decoder.cs | 53 + src/Ryujinx.Graphics.Nvdec.Vp9/BitDepth.cs | 9 + src/Ryujinx.Graphics.Nvdec.Vp9/CodecErr.cs | 56 + .../Common/BitUtils.cs | 59 + .../Common/MemoryAllocator.cs | 94 + .../Common/MemoryUtil.cs | 23 + src/Ryujinx.Graphics.Nvdec.Vp9/Constants.cs | 69 + src/Ryujinx.Graphics.Nvdec.Vp9/DecodeFrame.cs | 1387 +++ src/Ryujinx.Graphics.Nvdec.Vp9/DecodeMv.cs | 1176 +++ src/Ryujinx.Graphics.Nvdec.Vp9/Decoder.cs | 181 + src/Ryujinx.Graphics.Nvdec.Vp9/Detokenize.cs | 327 + .../Dsp/Convolve.cs | 945 ++ src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Filter.cs | 12 + .../Dsp/IntraPred.cs | 1379 +++ src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/InvTxfm.cs | 2935 ++++++ src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Prob.cs | 75 + src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Reader.cs | 236 + .../Dsp/TxfmCommon.cs | 54 + src/Ryujinx.Graphics.Nvdec.Vp9/Idct.cs | 530 + .../InternalErrorException.cs | 15 + .../InternalErrorInfo.cs | 14 + src/Ryujinx.Graphics.Nvdec.Vp9/LoopFilter.cs | 408 + src/Ryujinx.Graphics.Nvdec.Vp9/Luts.cs | 1581 +++ src/Ryujinx.Graphics.Nvdec.Vp9/PredCommon.cs | 393 + src/Ryujinx.Graphics.Nvdec.Vp9/QuantCommon.cs | 204 + src/Ryujinx.Graphics.Nvdec.Vp9/ReconInter.cs | 243 + src/Ryujinx.Graphics.Nvdec.Vp9/ReconIntra.cs | 758 ++ .../Ryujinx.Graphics.Nvdec.Vp9.csproj | 13 + src/Ryujinx.Graphics.Nvdec.Vp9/TileBuffer.cs | 11 + .../TileWorkerData.cs | 20 + .../Types/BModeInfo.cs | 10 + .../Types/BlockSize.cs | 21 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/Buf2D.cs | 10 + .../Types/FrameType.cs | 8 + .../Types/LoopFilter.cs | 27 + .../Types/LoopFilterInfoN.cs | 10 + .../Types/LoopFilterMask.cs | 24 + .../Types/LoopFilterThresh.cs | 15 + .../Types/MacroBlockD.cs | 182 + .../Types/MacroBlockDPlane.cs | 21 + .../Types/ModeInfo.cs | 66 + .../Types/MotionVectorContext.cs | 14 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/Mv.cs | 187 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/Mv32.cs | 8 + .../Types/MvClassType.cs | 17 + .../Types/MvJointType.cs | 10 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvRef.cs | 10 + .../Types/PartitionType.cs | 12 + .../Types/PlaneType.cs | 9 + .../Types/Position.cs | 14 + .../Types/PredictionMode.cs | 21 + .../Types/RefBuffer.cs | 8 + .../Types/ReferenceMode.cs | 10 + .../Types/ScaleFactors.cs | 445 + .../Types/SegLvlFeatures.cs | 11 + .../Types/Segmentation.cs | 71 + .../Types/Surface.cs | 84 + .../Types/TileInfo.cs | 86 + .../Types/TxMode.cs | 12 + .../Types/TxSize.cs | 11 + .../Types/TxType.cs | 11 + .../Types/Vp9Common.cs | 331 + src/Ryujinx.Graphics.Nvdec/ApplicationId.cs | 14 + src/Ryujinx.Graphics.Nvdec/H264Decoder.cs | 57 + .../Image/SurfaceCache.cs | 174 + .../Image/SurfaceCommon.cs | 26 + .../Image/SurfaceReader.cs | 133 + .../Image/SurfaceWriter.cs | 175 + .../MemoryExtensions.cs | 28 + .../NvdecDecoderContext.cs | 29 + src/Ryujinx.Graphics.Nvdec/NvdecDevice.cs | 82 + src/Ryujinx.Graphics.Nvdec/NvdecRegisters.cs | 63 + src/Ryujinx.Graphics.Nvdec/NvdecStatus.cs | 16 + src/Ryujinx.Graphics.Nvdec/ResourceManager.cs | 17 + .../Ryujinx.Graphics.Nvdec.csproj | 17 + .../Types/H264/PictureInfo.cs | 123 + .../Types/H264/ReferenceFrame.cs | 15 + .../Types/Vp8/PictureInfo.cs | 75 + .../Types/Vp9/BackwardUpdates.cs | 72 + .../Types/Vp9/EntropyProbs.cs | 141 + .../Types/Vp9/FrameFlags.cs | 12 + .../Types/Vp9/FrameSize.cs | 12 + .../Types/Vp9/FrameStats.cs | 21 + .../Types/Vp9/LoopFilter.cs | 13 + .../Types/Vp9/PictureInfo.cs | 87 + .../Types/Vp9/Segmentation.cs | 16 + src/Ryujinx.Graphics.Nvdec/Vp8Decoder.cs | 33 + src/Ryujinx.Graphics.Nvdec/Vp9Decoder.cs | 90 + .../BackgroundContextWorker.cs | 93 + src/Ryujinx.Graphics.OpenGL/Buffer.cs | 121 + src/Ryujinx.Graphics.OpenGL/Constants.cs | 12 + src/Ryujinx.Graphics.OpenGL/Debugger.cs | 109 + .../DrawTextureEmulation.cs | 134 + .../Effects/FsrScalingFilter.cs | 177 + .../Effects/FxaaPostProcessingEffect.cs | 81 + .../Effects/IPostProcessingEffect.cs | 11 + .../Effects/IScalingFilter.cs | 18 + .../Effects/ShaderHelper.cs | 39 + .../Effects/Shaders/ffx_a.h | 2656 +++++ .../Effects/Shaders/ffx_fsr1.h | 1199 +++ .../Effects/Shaders/fsr_scaling.glsl | 88 + .../Effects/Shaders/fsr_sharpening.glsl | 37 + .../Effects/Shaders/fxaa.glsl | 1174 +++ .../Effects/Shaders/smaa.hlsl | 1361 +++ .../Effects/Shaders/smaa_blend.glsl | 26 + .../Effects/Shaders/smaa_edge.glsl | 24 + .../Effects/Shaders/smaa_neighbour.glsl | 26 + .../Effects/SmaaPostProcessingEffect.cs | 262 + .../Effects/Textures/SmaaAreaTexture.bin | Bin 0 -> 179200 bytes .../Effects/Textures/SmaaSearchTexture.bin | Bin 0 -> 1024 bytes src/Ryujinx.Graphics.OpenGL/EnumConversion.cs | 675 ++ src/Ryujinx.Graphics.OpenGL/FormatInfo.cs | 45 + src/Ryujinx.Graphics.OpenGL/FormatTable.cs | 241 + src/Ryujinx.Graphics.OpenGL/Framebuffer.cs | 235 + src/Ryujinx.Graphics.OpenGL/Handle.cs | 23 + .../Helper/GLXHelper.cs | 36 + .../Helper/WGLHelper.cs | 15 + src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs | 143 + src/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs | 12 + .../Image/FormatConverter.cs | 152 + .../Image/ITextureInfo.cs | 14 + .../Image/ImageArray.cs | 71 + .../Image/IntermmediatePool.cs | 103 + src/Ryujinx.Graphics.OpenGL/Image/Sampler.cs | 64 + .../Image/TextureArray.cs | 56 + .../Image/TextureBase.cs | 42 + .../Image/TextureBuffer.cs | 118 + .../Image/TextureCopy.cs | 517 + .../Image/TextureCopyIncompatible.cs | 248 + .../Image/TextureCopyMS.cs | 276 + .../Image/TextureStorage.cs | 210 + .../Image/TextureView.cs | 919 ++ src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs | 335 + .../PersistentBuffers.cs | 163 + src/Ryujinx.Graphics.OpenGL/Pipeline.cs | 1706 ++++ src/Ryujinx.Graphics.OpenGL/Program.cs | 176 + .../Queries/BufferedQuery.cs | 120 + .../Queries/CounterQueue.cs | 224 + .../Queries/CounterQueueEvent.cs | 163 + .../Queries/Counters.cs | 57 + src/Ryujinx.Graphics.OpenGL/ResourcePool.cs | 114 + .../Ryujinx.Graphics.OpenGL.csproj | 32 + src/Ryujinx.Graphics.OpenGL/Sync.cs | 172 + src/Ryujinx.Graphics.OpenGL/VertexArray.cs | 280 + src/Ryujinx.Graphics.OpenGL/Window.cs | 413 + src/Ryujinx.Graphics.Shader/AlphaTestOp.cs | 14 + src/Ryujinx.Graphics.Shader/AttributeType.cs | 46 + .../BufferDescriptor.cs | 34 + .../BufferUsageFlags.cs | 18 + .../CodeGen/CodeGenParameters.cs | 31 + .../CodeGen/Glsl/CodeGenContext.cs | 105 + .../CodeGen/Glsl/Declarations.cs | 663 ++ .../CodeGen/Glsl/DefaultNames.cs | 15 + .../CodeGen/Glsl/GlslGenerator.cs | 177 + .../HelperFunctions/HelperFunctionNames.cs | 10 + .../Glsl/HelperFunctions/MultiplyHighS32.glsl | 7 + .../Glsl/HelperFunctions/MultiplyHighU32.glsl | 7 + .../Glsl/HelperFunctions/SwizzleAdd.glsl | 7 + .../CodeGen/Glsl/Instructions/InstGen.cs | 208 + .../Glsl/Instructions/InstGenBallot.cs | 28 + .../CodeGen/Glsl/Instructions/InstGenCall.cs | 29 + .../CodeGen/Glsl/Instructions/InstGenFSI.cs | 29 + .../Glsl/Instructions/InstGenHelper.cs | 222 + .../Glsl/Instructions/InstGenMemory.cs | 700 ++ .../Glsl/Instructions/InstGenPacking.cs | 56 + .../Glsl/Instructions/InstGenShuffle.cs | 25 + .../Glsl/Instructions/InstGenVector.cs | 32 + .../CodeGen/Glsl/Instructions/InstInfo.cs | 18 + .../CodeGen/Glsl/Instructions/InstType.cs | 35 + .../CodeGen/Glsl/Instructions/IoMap.cs | 144 + .../CodeGen/Glsl/NumberFormatter.cs | 104 + .../CodeGen/Glsl/OperandManager.cs | 178 + .../CodeGen/Glsl/TypeConversion.cs | 93 + .../CodeGen/Spirv/CodeGenContext.cs | 373 + .../CodeGen/Spirv/Declarations.cs | 659 ++ .../CodeGen/Spirv/EnumConversion.cs | 22 + .../CodeGen/Spirv/ImageDeclaration.cs | 20 + .../CodeGen/Spirv/Instructions.cs | 2121 ++++ .../CodeGen/Spirv/IoMap.cs | 124 + .../CodeGen/Spirv/OperationResult.cs | 19 + .../CodeGen/Spirv/SamplerDeclaration.cs | 27 + .../CodeGen/Spirv/SpirvDelegates.cs | 228 + .../CodeGen/Spirv/SpirvGenerator.cs | 425 + src/Ryujinx.Graphics.Shader/Constants.cs | 14 + src/Ryujinx.Graphics.Shader/Decoders/Block.cs | 168 + .../Decoders/DecodedFunction.cs | 48 + .../Decoders/DecodedProgram.cs | 78 + .../Decoders/Decoder.cs | 905 ++ .../Decoders/FunctionType.cs | 10 + .../Decoders/InstDecoders.cs | 5394 +++++++++++ .../Decoders/InstName.cs | 188 + .../Decoders/InstOp.cs | 27 + .../Decoders/InstProps.cs | 31 + .../Decoders/InstTable.cs | 392 + .../Decoders/Register.cs | 36 + .../Decoders/RegisterConsts.cs | 13 + .../Decoders/RegisterType.cs | 9 + .../GpuGraphicsState.cs | 177 + src/Ryujinx.Graphics.Shader/IGpuAccessor.cs | 498 + src/Ryujinx.Graphics.Shader/ILogger.cs | 17 + src/Ryujinx.Graphics.Shader/InputTopology.cs | 53 + .../Instructions/AttributeMap.cs | 350 + .../Instructions/InstEmit.cs | 358 + .../Instructions/InstEmitAluHelper.cs | 159 + .../Instructions/InstEmitAttribute.cs | 409 + .../Instructions/InstEmitBarrier.cs | 46 + .../Instructions/InstEmitBitfield.cs | 194 + .../Instructions/InstEmitConditionCode.cs | 86 + .../Instructions/InstEmitConversion.cs | 424 + .../Instructions/InstEmitFloatArithmetic.cs | 531 + .../Instructions/InstEmitFloatComparison.cs | 570 ++ .../Instructions/InstEmitFloatMinMax.cs | 106 + .../Instructions/InstEmitFlowControl.cs | 328 + .../Instructions/InstEmitHelper.cs | 260 + .../Instructions/InstEmitIntegerArithmetic.cs | 701 ++ .../Instructions/InstEmitIntegerComparison.cs | 319 + .../Instructions/InstEmitIntegerLogical.cs | 166 + .../Instructions/InstEmitIntegerMinMax.cs | 71 + .../Instructions/InstEmitMemory.cs | 584 ++ .../Instructions/InstEmitMove.cs | 277 + .../Instructions/InstEmitMultifunction.cs | 97 + .../Instructions/InstEmitNop.cs | 15 + .../Instructions/InstEmitPredicate.cs | 116 + .../Instructions/InstEmitShift.cs | 249 + .../Instructions/InstEmitSurface.cs | 799 ++ .../Instructions/InstEmitTexture.cs | 1331 +++ .../Instructions/InstEmitVideoArithmetic.cs | 117 + .../Instructions/InstEmitVideoMinMax.cs | 183 + .../Instructions/InstEmitWarp.cs | 144 + .../Instructions/InstEmitter.cs | 6 + .../Instructions/Lop3Expression.cs | 142 + .../IntermediateRepresentation/BasicBlock.cs | 91 + .../IntermediateRepresentation/CommentNode.cs | 12 + .../IntermediateRepresentation/Function.cs | 23 + .../IntermediateRepresentation/INode.cs | 15 + .../IntermediateRepresentation/Instruction.cs | 197 + .../IntermediateRepresentation/IoVariable.cs | 49 + .../IntermediateRepresentation/IrConsts.cs | 8 + .../IntermediateRepresentation/Operand.cs | 79 + .../OperandHelper.cs | 62 + .../IntermediateRepresentation/OperandType.cs | 13 + .../IntermediateRepresentation/Operation.cs | 294 + .../IntermediateRepresentation/PhiNode.cs | 104 + .../IntermediateRepresentation/StorageKind.cs | 45 + .../TextureFlags.cs | 34 + .../TextureOperation.cs | 69 + src/Ryujinx.Graphics.Shader/OutputTopology.cs | 23 + .../ResourceReservationCounts.cs | 22 + .../Ryujinx.Graphics.Shader.csproj | 18 + src/Ryujinx.Graphics.Shader/SamplerType.cs | 159 + src/Ryujinx.Graphics.Shader/SetBindingPair.cs | 4 + src/Ryujinx.Graphics.Shader/ShaderProgram.cs | 35 + .../ShaderProgramInfo.cs | 57 + src/Ryujinx.Graphics.Shader/ShaderStage.cs | 40 + .../StructuredIr/AstAssignment.cs | 35 + .../StructuredIr/AstBlock.cs | 117 + .../StructuredIr/AstBlockType.cs | 12 + .../StructuredIr/AstBlockVisitor.cs | 68 + .../StructuredIr/AstComment.cs | 12 + .../StructuredIr/AstHelper.cs | 75 + .../StructuredIr/AstNode.cs | 11 + .../StructuredIr/AstOperand.cs | 38 + .../StructuredIr/AstOperation.cs | 94 + .../StructuredIr/AstOptimizer.cs | 155 + .../StructuredIr/AstTextureOperation.cs | 49 + .../StructuredIr/BufferDefinition.cs | 20 + .../StructuredIr/BufferLayout.cs | 8 + .../StructuredIr/GotoElimination.cs | 458 + .../StructuredIr/GotoStatement.cs | 23 + .../StructuredIr/HelperFunctionsMask.cs | 13 + .../StructuredIr/IAstNode.cs | 11 + .../StructuredIr/InstructionInfo.cs | 208 + .../StructuredIr/IoDefinition.cs | 44 + .../StructuredIr/MemoryDefinition.cs | 18 + .../StructuredIr/OperandInfo.cs | 32 + .../StructuredIr/PhiFunctions.cs | 49 + .../StructuredIr/ShaderProperties.cs | 110 + .../StructuredIr/StructureType.cs | 28 + .../StructuredIr/StructuredFunction.cs | 42 + .../StructuredIr/StructuredProgram.cs | 466 + .../StructuredIr/StructuredProgramContext.cs | 354 + .../StructuredIr/StructuredProgramInfo.cs | 20 + .../StructuredIr/TextureDefinition.cs | 43 + src/Ryujinx.Graphics.Shader/SupportBuffer.cs | 100 + src/Ryujinx.Graphics.Shader/TessPatchType.cs | 22 + src/Ryujinx.Graphics.Shader/TessSpacing.cs | 22 + .../TextureDescriptor.cs | 43 + src/Ryujinx.Graphics.Shader/TextureFormat.cs | 130 + src/Ryujinx.Graphics.Shader/TextureHandle.cs | 125 + .../TextureUsageFlags.cs | 19 + .../Translation/AggregateType.cs | 61 + .../Translation/AttributeConsts.cs | 38 + .../Translation/AttributeUsage.cs | 168 + .../Translation/ControlFlowGraph.cs | 176 + .../Translation/Dominance.cs | 94 + .../Translation/EmitterContext.cs | 627 ++ .../Translation/EmitterContextInsts.cs | 1045 ++ .../Translation/FeatureFlags.cs | 30 + .../Translation/FunctionMatch.cs | 865 ++ .../Translation/HelperFunctionManager.cs | 475 + .../Translation/HelperFunctionName.cs | 20 + .../Translation/HostCapabilities.cs | 37 + .../Translation/IoUsage.cs | 28 + .../Optimizations/BindlessElimination.cs | 494 + .../Optimizations/BindlessToArray.cs | 238 + .../Optimizations/BranchElimination.cs | 64 + .../Optimizations/ConstantFolding.cs | 351 + .../Optimizations/DoubleToFloat.cs | 70 + .../Optimizations/GlobalToStorage.cs | 1175 +++ .../Translation/Optimizations/Optimizer.cs | 453 + .../Optimizations/Simplification.cs | 235 + .../Translation/Optimizations/Utils.cs | 180 + .../Translation/RegisterUsage.cs | 491 + .../Translation/ResourceManager.cs | 649 ++ .../Translation/ResourceReservations.cs | 203 + .../Translation/ShaderDefinitions.cs | 355 + .../Translation/ShaderHeader.cs | 168 + .../Translation/Ssa.cs | 375 + .../Translation/TargetApi.cs | 8 + .../Translation/TargetLanguage.cs | 9 + .../Translation/TransformContext.cs | 39 + .../Translation/TransformFeedbackOutput.cs | 18 + .../Transforms/DrawParametersReplace.cs | 93 + .../Transforms/ForcePreciseEnable.cs | 36 + .../Transforms/GeometryToCompute.cs | 378 + .../Translation/Transforms/ITransformPass.cs | 11 + .../Transforms/SharedAtomicSignedCas.cs | 58 + .../Transforms/SharedStoreSmallIntCas.cs | 57 + .../Translation/Transforms/ShufflePass.cs | 52 + .../Translation/Transforms/TexturePass.cs | 748 ++ .../Translation/Transforms/TransformPasses.cs | 44 + .../Transforms/VectorComponentSelect.cs | 96 + .../Translation/Transforms/VertexToCompute.cs | 368 + .../Translation/TranslationFlags.cs | 14 + .../Translation/TranslationOptions.cs | 16 + .../Translation/Translator.cs | 373 + .../Translation/TranslatorContext.cs | 673 ++ .../VertexInfoBuffer.cs | 59 + .../Astc/AstcDecoder.cs | 1708 ++++ .../Astc/AstcDecoderException.cs | 9 + .../Astc/AstcPixel.cs | 68 + .../Astc/BitStream128.cs | 77 + src/Ryujinx.Graphics.Texture/Astc/Bits.cs | 76 + .../Astc/EndPointSet.cs | 23 + .../Astc/IntegerEncoded.cs | 345 + .../Astc/IntegerSequence.cs | 31 + src/Ryujinx.Graphics.Texture/BC6Decoder.cs | 819 ++ src/Ryujinx.Graphics.Texture/BC7Decoder.cs | 229 + src/Ryujinx.Graphics.Texture/BCnDecoder.cs | 897 ++ src/Ryujinx.Graphics.Texture/BCnEncoder.cs | 60 + .../BlockLinearConstants.cs | 10 + .../BlockLinearLayout.cs | 195 + src/Ryujinx.Graphics.Texture/Bpp12Pixel.cs | 11 + src/Ryujinx.Graphics.Texture/ETC2Decoder.cs | 683 ++ .../Encoders/BC7Encoder.cs | 1005 ++ .../Encoders/EncodeMode.cs | 10 + .../LayoutConverter.cs | 593 ++ .../OffsetCalculator.cs | 141 + .../PixelConverter.cs | 218 + src/Ryujinx.Graphics.Texture/Region.cs | 14 + .../Ryujinx.Graphics.Texture.csproj | 11 + src/Ryujinx.Graphics.Texture/Size.cs | 16 + .../SizeCalculator.cs | 309 + src/Ryujinx.Graphics.Texture/SizeInfo.cs | 119 + .../Utils/BC67Tables.cs | 297 + .../Utils/BC67Utils.cs | 1329 +++ .../Utils/BC7ModeInfo.cs | 37 + src/Ryujinx.Graphics.Texture/Utils/Block.cs | 55 + .../Utils/RgbaColor32.cs | 230 + .../Utils/RgbaColor8.cs | 84 + src/Ryujinx.Graphics.Vic/Blender.cs | 278 + src/Ryujinx.Graphics.Vic/Image/BufferPool.cs | 103 + .../Image/InputSurface.cs | 86 + src/Ryujinx.Graphics.Vic/Image/Pixel.cs | 10 + src/Ryujinx.Graphics.Vic/Image/Surface.cs | 46 + .../Image/SurfaceCommon.cs | 33 + .../Image/SurfaceReader.cs | 496 + .../Image/SurfaceWriter.cs | 667 ++ src/Ryujinx.Graphics.Vic/Rectangle.cs | 18 + src/Ryujinx.Graphics.Vic/ResourceManager.cs | 19 + .../Ryujinx.Graphics.Vic.csproj | 14 + src/Ryujinx.Graphics.Vic/Scaler.cs | 124 + .../Types/BlendingSlotStruct.cs | 29 + .../Types/ClearRectStruct.cs | 21 + .../Types/ConfigStruct.cs | 16 + .../Types/DeinterlaceMode.cs | 12 + src/Ryujinx.Graphics.Vic/Types/FrameFormat.cs | 79 + .../Types/LumaKeyStruct.cs | 19 + .../Types/MatrixStruct.cs | 27 + .../Types/OutputConfig.cs | 27 + .../Types/OutputSurfaceConfig.cs | 24 + src/Ryujinx.Graphics.Vic/Types/PipeConfig.cs | 15 + src/Ryujinx.Graphics.Vic/Types/PixelFormat.cs | 81 + src/Ryujinx.Graphics.Vic/Types/SlotConfig.cs | 67 + src/Ryujinx.Graphics.Vic/Types/SlotStruct.cs | 12 + .../Types/SlotSurfaceConfig.cs | 23 + src/Ryujinx.Graphics.Vic/VicDevice.cs | 73 + src/Ryujinx.Graphics.Vic/VicRegisters.cs | 51 + src/Ryujinx.Graphics.Video/FrameField.cs | 8 + src/Ryujinx.Graphics.Video/H264PictureInfo.cs | 47 + src/Ryujinx.Graphics.Video/IDecoder.cs | 11 + src/Ryujinx.Graphics.Video/IH264Decoder.cs | 9 + src/Ryujinx.Graphics.Video/ISurface.cs | 20 + src/Ryujinx.Graphics.Video/IVp9Decoder.cs | 14 + src/Ryujinx.Graphics.Video/Plane.cs | 6 + .../Ryujinx.Graphics.Video.csproj | 11 + src/Ryujinx.Graphics.Video/Vp8PictureInfo.cs | 11 + .../Vp9BackwardUpdates.cs | 32 + src/Ryujinx.Graphics.Video/Vp9EntropyProbs.cs | 36 + src/Ryujinx.Graphics.Video/Vp9Mv.cs | 8 + src/Ryujinx.Graphics.Video/Vp9MvRef.cs | 11 + src/Ryujinx.Graphics.Video/Vp9PictureInfo.cs | 39 + src/Ryujinx.Graphics.Vulkan/Auto.cs | 191 + .../AutoFlushCounter.cs | 179 + .../BackgroundResources.cs | 120 + src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs | 458 + src/Ryujinx.Graphics.Vulkan/BitMap.cs | 157 + src/Ryujinx.Graphics.Vulkan/BitMapStruct.cs | 263 + .../BufferAllocationType.cs | 13 + src/Ryujinx.Graphics.Vulkan/BufferHolder.cs | 922 ++ src/Ryujinx.Graphics.Vulkan/BufferManager.cs | 679 ++ .../BufferMirrorRangeList.cs | 305 + src/Ryujinx.Graphics.Vulkan/BufferState.cs | 56 + .../BufferUsageBitmap.cs | 82 + src/Ryujinx.Graphics.Vulkan/CacheByRange.cs | 394 + .../CommandBufferPool.cs | 370 + .../CommandBufferScoped.cs | 39 + src/Ryujinx.Graphics.Vulkan/Constants.cs | 23 + .../DescriptorSetCollection.cs | 222 + .../DescriptorSetManager.cs | 231 + .../DescriptorSetTemplate.cs | 210 + .../DescriptorSetTemplateUpdater.cs | 77 + .../DescriptorSetUpdater.cs | 1199 +++ .../DisposableBuffer.cs | 26 + .../DisposableBufferView.cs | 25 + .../DisposableFramebuffer.cs | 25 + .../DisposableImage.cs | 25 + .../DisposableImageView.cs | 25 + .../DisposableMemory.cs | 24 + .../DisposablePipeline.cs | 25 + .../DisposableRenderPass.cs | 25 + .../DisposableSampler.cs | 25 + .../Effects/FsrScalingFilter.cs | 172 + .../Effects/FxaaPostProcessingEffect.cs | 88 + .../Effects/IPostProcessingEffect.cs | 10 + .../Effects/IScalingFilter.cs | 20 + .../Effects/Shaders/FsrScaling.glsl | 3945 ++++++++ .../Effects/Shaders/FsrScaling.spv | Bin 0 -> 44672 bytes .../Effects/Shaders/FsrSharpening.glsl | 3904 ++++++++ .../Effects/Shaders/FsrSharpening.spv | Bin 0 -> 20472 bytes .../Effects/Shaders/Fxaa.glsl | 1177 +++ .../Effects/Shaders/Fxaa.spv | Bin 0 -> 25012 bytes .../Effects/Shaders/SmaaBlend.glsl | 1404 +++ .../Effects/Shaders/SmaaBlend.spv | Bin 0 -> 33728 bytes .../Effects/Shaders/SmaaEdge.glsl | 1402 +++ .../Effects/Shaders/SmaaEdge.spv | Bin 0 -> 8464 bytes .../Effects/Shaders/SmaaNeighbour.glsl | 1403 +++ .../Effects/Shaders/SmaaNeighbour.spv | Bin 0 -> 8328 bytes .../Effects/SmaaConstants.cs | 15 + .../Effects/SmaaPostProcessingEffect.cs | 266 + .../Effects/Textures/SmaaAreaTexture.bin | Bin 0 -> 179200 bytes .../Effects/Textures/SmaaSearchTexture.bin | Bin 0 -> 1024 bytes src/Ryujinx.Graphics.Vulkan/EnumConversion.cs | 452 + .../FeedbackLoopAspects.cs | 12 + src/Ryujinx.Graphics.Vulkan/FenceHelper.cs | 30 + src/Ryujinx.Graphics.Vulkan/FenceHolder.cs | 159 + .../FormatCapabilities.cs | 233 + .../FormatConverter.cs | 49 + src/Ryujinx.Graphics.Vulkan/FormatTable.cs | 358 + .../FramebufferParams.cs | 344 + .../HardwareCapabilities.cs | 138 + src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs | 143 + src/Ryujinx.Graphics.Vulkan/HelperShader.cs | 1740 ++++ .../HostMemoryAllocator.cs | 188 + src/Ryujinx.Graphics.Vulkan/IdList.cs | 121 + src/Ryujinx.Graphics.Vulkan/ImageArray.cs | 218 + .../IndexBufferPattern.cs | 139 + .../IndexBufferState.cs | 171 + .../MemoryAllocation.cs | 59 + .../MemoryAllocator.cs | 120 + .../MemoryAllocatorBlockList.cs | 310 + .../MoltenVK/MVKConfiguration.cs | 104 + .../MoltenVK/MVKInitialization.cs | 51 + .../MultiFenceHolder.cs | 267 + src/Ryujinx.Graphics.Vulkan/NativeArray.cs | 48 + .../PersistentFlushBuffer.cs | 97 + src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 1810 ++++ .../PipelineConverter.cs | 324 + .../PipelineDynamicState.cs | 203 + src/Ryujinx.Graphics.Vulkan/PipelineFull.cs | 351 + .../PipelineHelperShader.cs | 54 + .../PipelineLayoutCache.cs | 107 + .../PipelineLayoutCacheEntry.cs | 383 + .../PipelineLayoutFactory.cs | 115 + src/Ryujinx.Graphics.Vulkan/PipelineState.cs | 732 ++ src/Ryujinx.Graphics.Vulkan/PipelineUid.cs | 125 + .../Queries/BufferedQuery.cs | 216 + .../Queries/CounterQueue.cs | 252 + .../Queries/CounterQueueEvent.cs | 170 + .../Queries/Counters.cs | 71 + .../RenderPassCacheKey.cs | 43 + .../RenderPassHolder.cs | 221 + src/Ryujinx.Graphics.Vulkan/ResourceArray.cs | 81 + .../ResourceBindingSegment.cs | 22 + .../ResourceLayoutBuilder.cs | 57 + .../Ryujinx.Graphics.Vulkan.csproj | 65 + src/Ryujinx.Graphics.Vulkan/SamplerHolder.cs | 120 + src/Ryujinx.Graphics.Vulkan/Shader.cs | 160 + .../ShaderCollection.cs | 757 ++ .../ChangeBufferStrideShaderSource.comp | 64 + ...lorBlitClearAlphaFragmentShaderSource.frag | 11 + .../ColorBlitFragmentShaderSource.frag | 11 + .../ColorBlitMsFragmentShaderSource.frag | 11 + .../Shaders/ColorBlitVertexShaderSource.vert | 20 + .../ColorClearFFragmentShaderSource.frag | 9 + .../ColorClearSIFragmentShaderSource.frag | 9 + .../ColorClearUIFragmentShaderSource.frag | 9 + .../Shaders/ColorClearVertexShaderSource.vert | 19 + ...olorCopyShorteningComputeShaderSource.comp | 36 + .../ColorCopyToNonMsComputeShaderSource.comp | 37 + .../ColorCopyWideningComputeShaderSource.comp | 31 + .../ColorDrawToMsFragmentShaderSource.frag | 27 + .../ColorDrawToMsVertexShaderSource.vert | 11 + .../ConvertD32S8ToD24S8ShaderSource.comp | 58 + .../ConvertIndexBufferShaderSource.comp | 58 + .../ConvertIndirectDataShaderSource.comp | 103 + .../DepthBlitFragmentShaderSource.frag | 10 + .../DepthBlitMsFragmentShaderSource.frag | 10 + .../DepthDrawToMsFragmentShaderSource.frag | 25 + .../DepthDrawToNonMsFragmentShaderSource.frag | 28 + ...DepthStencilClearFragmentShaderSource.frag | 8 + .../SpirvBinaries/ChangeBufferStride.spv | Bin 0 -> 3932 bytes .../ColorBlitClearAlphaFragment.spv | Bin 0 -> 656 bytes .../SpirvBinaries/ColorBlitFragment.spv | Bin 0 -> 552 bytes .../SpirvBinaries/ColorBlitMsFragment.spv | Bin 0 -> 808 bytes .../Shaders/SpirvBinaries/ColorBlitVertex.spv | Bin 0 -> 1564 bytes .../SpirvBinaries/ColorClearFFragment.spv | Bin 0 -> 380 bytes .../SpirvBinaries/ColorClearSIFragment.spv | Bin 0 -> 428 bytes .../SpirvBinaries/ColorClearUIFragment.spv | Bin 0 -> 428 bytes .../SpirvBinaries/ColorClearVertex.spv | Bin 0 -> 1424 bytes .../ColorCopyShorteningCompute.spv | Bin 0 -> 2040 bytes .../SpirvBinaries/ColorCopyToNonMsCompute.spv | Bin 0 -> 2044 bytes .../ColorCopyWideningCompute.spv | Bin 0 -> 1968 bytes .../SpirvBinaries/ColorDrawToMsFragment.spv | Bin 0 -> 1572 bytes .../SpirvBinaries/ColorDrawToMsVertex.spv | Bin 0 -> 1104 bytes .../SpirvBinaries/ConvertD32S8ToD24S8.spv | Bin 0 -> 3356 bytes .../SpirvBinaries/ConvertIndexBuffer.spv | Bin 0 -> 3044 bytes .../SpirvBinaries/ConvertIndirectData.spv | Bin 0 -> 5424 bytes .../SpirvBinaries/DepthBlitFragment.spv | Bin 0 -> 600 bytes .../SpirvBinaries/DepthBlitMsFragment.spv | Bin 0 -> 856 bytes .../SpirvBinaries/DepthDrawToMsFragment.spv | Bin 0 -> 1596 bytes .../DepthDrawToNonMsFragment.spv | Bin 0 -> 1576 bytes .../DepthStencilClearFragment.spv | Bin 0 -> 468 bytes .../SpirvBinaries/StencilBlitFragment.spv | Bin 0 -> 704 bytes .../SpirvBinaries/StencilBlitMsFragment.spv | Bin 0 -> 944 bytes .../SpirvBinaries/StencilDrawToMsFragment.spv | Bin 0 -> 1684 bytes .../StencilDrawToNonMsFragment.spv | Bin 0 -> 1664 bytes .../StencilBlitFragmentShaderSource.frag | 12 + .../StencilBlitMsFragmentShaderSource.frag | 12 + .../StencilDrawToMsFragmentShaderSource.frag | 27 + ...tencilDrawToNonMsFragmentShaderSource.frag | 30 + src/Ryujinx.Graphics.Vulkan/SpecInfo.cs | 100 + src/Ryujinx.Graphics.Vulkan/StagingBuffer.cs | 297 + src/Ryujinx.Graphics.Vulkan/SyncManager.cs | 215 + src/Ryujinx.Graphics.Vulkan/TextureArray.cs | 234 + src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs | 164 + src/Ryujinx.Graphics.Vulkan/TextureCopy.cs | 473 + src/Ryujinx.Graphics.Vulkan/TextureStorage.cs | 618 ++ src/Ryujinx.Graphics.Vulkan/TextureView.cs | 1152 +++ src/Ryujinx.Graphics.Vulkan/Vendor.cs | 100 + .../VertexBufferState.cs | 137 + .../VertexBufferUpdater.cs | 82 + .../VulkanConfiguration.cs | 12 + .../VulkanDebugMessenger.cs | 133 + .../VulkanException.cs | 43 + .../VulkanInitialization.cs | 618 ++ src/Ryujinx.Graphics.Vulkan/VulkanInstance.cs | 127 + .../VulkanPhysicalDevice.cs | 97 + src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 1031 ++ src/Ryujinx.Graphics.Vulkan/Window.cs | 670 ++ src/Ryujinx.Graphics.Vulkan/WindowBase.cs | 19 + src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs | 205 + .../Input/GTK3/GTK3KeyboardDriver.cs | 99 + .../Input/GTK3/GTK3MappingHelper.cs | 178 + src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs | 90 + .../Input/GTK3/GTK3MouseDriver.cs | 108 + .../Modules/Updater/UpdateDialog.cs | 95 + .../Modules/Updater/UpdateDialog.glade | 127 + src/Ryujinx.Gtk3/Modules/Updater/Updater.cs | 622 ++ src/Ryujinx.Gtk3/Program.cs | 403 + src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj | 103 + src/Ryujinx.Gtk3/Ryujinx.ico | Bin 0 -> 108122 bytes .../UI/Applet/ErrorAppletDialog.cs | 31 + .../UI/Applet/GtkDynamicTextInputHandler.cs | 108 + .../UI/Applet/GtkHostUIHandler.cs | 203 + src/Ryujinx.Gtk3/UI/Applet/GtkHostUITheme.cs | 90 + .../UI/Applet/SwkbdAppletDialog.cs | 127 + src/Ryujinx.Gtk3/UI/Helper/ButtonHelper.cs | 158 + src/Ryujinx.Gtk3/UI/Helper/MetalHelper.cs | 135 + src/Ryujinx.Gtk3/UI/Helper/SortHelper.cs | 33 + src/Ryujinx.Gtk3/UI/Helper/ThemeHelper.cs | 36 + src/Ryujinx.Gtk3/UI/MainWindow.cs | 1993 ++++ src/Ryujinx.Gtk3/UI/MainWindow.glade | 1006 ++ src/Ryujinx.Gtk3/UI/OpenGLRenderer.cs | 142 + .../UI/OpenToolkitBindingsContext.cs | 20 + src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs | 813 ++ src/Ryujinx.Gtk3/UI/SPBOpenGLContext.cs | 49 + src/Ryujinx.Gtk3/UI/StatusUpdatedEventArgs.cs | 28 + src/Ryujinx.Gtk3/UI/VulkanRenderer.cs | 93 + .../Widgets/GameTableContextMenu.Designer.cs | 233 + .../UI/Widgets/GameTableContextMenu.cs | 634 ++ src/Ryujinx.Gtk3/UI/Widgets/GtkDialog.cs | 114 + src/Ryujinx.Gtk3/UI/Widgets/GtkInputDialog.cs | 37 + src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.cs | 57 + .../UI/Widgets/ProfileDialog.glade | 124 + .../UI/Widgets/RawInputToTextEntry.cs | 27 + .../UI/Widgets/UserErrorDialog.cs | 123 + .../UI/Windows/AboutWindow.Designer.cs | 511 + src/Ryujinx.Gtk3/UI/Windows/AboutWindow.cs | 85 + .../UI/Windows/AmiiboWindow.Designer.cs | 190 + src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.cs | 438 + src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs | 298 + src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs | 163 + src/Ryujinx.Gtk3/UI/Windows/CheatWindow.glade | 150 + .../UI/Windows/ControllerWindow.cs | 1232 +++ .../UI/Windows/ControllerWindow.glade | 2241 +++++ src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs | 288 + src/Ryujinx.Gtk3/UI/Windows/DlcWindow.glade | 202 + src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs | 847 ++ .../UI/Windows/SettingsWindow.glade | 3221 +++++++ .../UI/Windows/TitleUpdateWindow.cs | 234 + .../UI/Windows/TitleUpdateWindow.glade | 214 + .../UserProfilesManagerWindow.Designer.cs | 255 + .../UI/Windows/UserProfilesManagerWindow.cs | 326 + src/Ryujinx.HLE.Generators/CodeGenerator.cs | 63 + .../IpcServiceGenerator.cs | 76 + .../Ryujinx.HLE.Generators.csproj | 19 + .../ServiceSyntaxReceiver.cs | 24 + src/Ryujinx.HLE/AssemblyInfo.cs | 3 + .../GuestBrokeExecutionException.cs | 11 + .../Exceptions/InternalServiceException.cs | 9 + .../InvalidFirmwarePackageException.cs | 9 + .../Exceptions/InvalidNpdmException.cs | 9 + .../InvalidStructLayoutException.cs | 15 + .../InvalidSystemResourceException.cs | 9 + .../ServiceNotImplementedException.cs | 162 + .../Exceptions/TamperCompilationException.cs | 9 + .../Exceptions/TamperExecutionException.cs | 9 + .../UndefinedInstructionException.cs | 13 + src/Ryujinx.HLE/FileSystem/ContentManager.cs | 967 ++ src/Ryujinx.HLE/FileSystem/ContentMetaData.cs | 61 + src/Ryujinx.HLE/FileSystem/ContentPath.cs | 85 + .../FileSystem/EncryptedFileSystemCreator.cs | 26 + src/Ryujinx.HLE/FileSystem/LocationEntry.cs | 25 + src/Ryujinx.HLE/FileSystem/SystemVersion.cs | 38 + .../FileSystem/VirtualFileSystem.cs | 670 ++ src/Ryujinx.HLE/HLEConfiguration.cs | 227 + src/Ryujinx.HLE/HOS/Applets/AppletManager.cs | 34 + .../HOS/Applets/Browser/BootDisplayKind.cs | 11 + .../HOS/Applets/Browser/BrowserApplet.cs | 99 + .../HOS/Applets/Browser/BrowserArgument.cs | 133 + .../HOS/Applets/Browser/BrowserOutput.cs | 47 + .../HOS/Applets/Browser/BrowserOutputType.cs | 14 + .../HOS/Applets/Browser/DocumentKind.cs | 9 + .../HOS/Applets/Browser/LeftStickMode.cs | 8 + .../HOS/Applets/Browser/ShimKind.cs | 13 + .../HOS/Applets/Browser/WebArgHeader.cs | 9 + .../HOS/Applets/Browser/WebArgTLV.cs | 9 + .../HOS/Applets/Browser/WebArgTLVType.cs | 62 + .../Applets/Browser/WebCommonReturnValue.cs | 12 + .../HOS/Applets/Browser/WebExitReason.cs | 11 + .../HOS/Applets/CommonArguments.cs | 16 + .../Applets/Controller/ControllerApplet.cs | 145 + .../Controller/ControllerAppletUIArgs.cs | 14 + .../Controller/ControllerSupportArgHeader.cs | 18 + .../Controller/ControllerSupportArgPrivate.cs | 16 + .../Controller/ControllerSupportArgV7.cs | 26 + .../Controller/ControllerSupportArgVPre7.cs | 26 + .../Controller/ControllerSupportMode.cs | 9 + .../Controller/ControllerSupportResultInfo.cs | 16 + .../HOS/Applets/Error/ApplicationErrorArg.cs | 14 + .../HOS/Applets/Error/ErrorApplet.cs | 217 + .../HOS/Applets/Error/ErrorCommonArg.cs | 12 + .../HOS/Applets/Error/ErrorCommonHeader.cs | 17 + .../HOS/Applets/Error/ErrorType.cs | 13 + src/Ryujinx.HLE/HOS/Applets/IApplet.cs | 28 + .../PlayerSelect/PlayerSelectApplet.cs | 59 + .../PlayerSelect/PlayerSelectResult.cs | 8 + .../CJKCharacterValidation.cs | 17 + .../SoftwareKeyboard/InitialCursorPosition.cs | 18 + .../SoftwareKeyboard/InlineKeyboardRequest.cs | 48 + .../InlineKeyboardResponse.cs | 93 + .../SoftwareKeyboard/InlineKeyboardState.cs | 33 + .../SoftwareKeyboard/InlineResponses.cs | 281 + .../Applets/SoftwareKeyboard/InputFormMode.cs | 18 + .../SoftwareKeyboard/InvalidButtonFlags.cs | 17 + .../SoftwareKeyboard/InvalidCharFlags.cs | 56 + .../SoftwareKeyboard/KeyboardCalcFlags.cs | 26 + .../SoftwareKeyboard/KeyboardInputMode.cs | 14 + .../KeyboardMiniaturizationMode.cs | 12 + .../Applets/SoftwareKeyboard/KeyboardMode.cs | 39 + .../SoftwareKeyboard/KeyboardResult.cs | 12 + .../NumericCharacterValidation.cs | 17 + .../Applets/SoftwareKeyboard/PasswordMode.cs | 18 + .../SoftwareKeyboard/Resources/Icon_BtnA.png | Bin 0 -> 1074 bytes .../SoftwareKeyboard/Resources/Icon_BtnA.svg | 80 + .../SoftwareKeyboard/Resources/Icon_BtnB.png | Bin 0 -> 992 bytes .../SoftwareKeyboard/Resources/Icon_BtnB.svg | 93 + .../SoftwareKeyboard/Resources/Icon_KeyF6.png | Bin 0 -> 842 bytes .../SoftwareKeyboard/Resources/Icon_KeyF6.svg | 108 + .../Resources/Logo_Ryujinx.png | Bin 0 -> 52972 bytes .../SoftwareKeyboardAppear.cs | 120 + .../SoftwareKeyboardAppearEx.cs | 100 + .../SoftwareKeyboardApplet.cs | 816 ++ .../SoftwareKeyboard/SoftwareKeyboardCalc.cs | 221 + .../SoftwareKeyboardCalcEx.cs | 182 + .../SoftwareKeyboardConfig.cs | 138 + .../SoftwareKeyboardCustomizeDic.cs | 13 + .../SoftwareKeyboardDictSet.cs | 34 + .../SoftwareKeyboardInitialize.cs | 26 + .../SoftwareKeyboardRenderer.cs | 169 + .../SoftwareKeyboardRendererBase.cs | 632 ++ .../SoftwareKeyboard/SoftwareKeyboardState.cs | 28 + .../SoftwareKeyboardUIArgs.cs | 16 + .../SoftwareKeyboardUIState.cs | 22 + .../SoftwareKeyboardUserWord.cs | 13 + .../HOS/Applets/SoftwareKeyboard/TRef.cs | 19 + .../Applets/SoftwareKeyboard/TimedAction.cs | 186 + src/Ryujinx.HLE/HOS/ArmProcessContext.cs | 94 + .../HOS/ArmProcessContextFactory.cs | 122 + .../Ast/ArraySubscriptingExpression.cs | 25 + .../Diagnostics/Demangler/Ast/ArrayType.cs | 59 + .../HOS/Diagnostics/Demangler/Ast/BaseNode.cs | 113 + .../Demangler/Ast/BinaryExpression.cs | 41 + .../Demangler/Ast/BracedExpression.cs | 40 + .../Demangler/Ast/BracedRangeExpression.cs | 34 + .../Demangler/Ast/CallExpression.cs | 24 + .../Demangler/Ast/CastExpression.cs | 28 + .../Demangler/Ast/ConditionalExpression.cs | 29 + .../Demangler/Ast/ConversionExpression.cs | 24 + .../Demangler/Ast/ConversionOperatorType.cs | 15 + .../Demangler/Ast/CtorDtorNameType.cs | 24 + .../Demangler/Ast/CtorVtableSpecialName.cs | 24 + .../Demangler/Ast/DeleteExpression.cs | 33 + .../HOS/Diagnostics/Demangler/Ast/DtorName.cs | 15 + .../Demangler/Ast/DynamicExceptionSpec.cs | 16 + .../Demangler/Ast/ElaboratedType.cs | 21 + .../Demangler/Ast/EnclosedExpression.cs | 25 + .../Demangler/Ast/EncodedFunction.cs | 56 + .../Demangler/Ast/FoldExpression.cs | 48 + .../Demangler/Ast/ForwardTemplateReference.cs | 38 + .../Demangler/Ast/FunctionParameter.cs | 24 + .../Diagnostics/Demangler/Ast/FunctionType.cs | 61 + .../Demangler/Ast/GlobalQualifiedName.cs | 15 + .../Demangler/Ast/InitListExpression.cs | 26 + .../Demangler/Ast/IntegerCastExpression.cs | 22 + .../Demangler/Ast/IntegerLiteral.cs | 42 + .../Demangler/Ast/LiteralOperator.cs | 16 + .../Diagnostics/Demangler/Ast/LocalName.cs | 23 + .../Demangler/Ast/MemberExpression.cs | 25 + .../HOS/Diagnostics/Demangler/Ast/NameType.cs | 29 + .../Ast/NameTypeWithTemplateArguments.cs | 27 + .../Diagnostics/Demangler/Ast/NestedName.cs | 26 + .../Demangler/Ast/NewExpression.cs | 55 + .../Diagnostics/Demangler/Ast/NodeArray.cs | 30 + .../Diagnostics/Demangler/Ast/NoexceptSpec.cs | 16 + .../Demangler/Ast/PackedTemplateParameter.cs | 39 + .../Ast/PackedTemplateParameterExpansion.cs | 24 + .../Diagnostics/Demangler/Ast/ParentNode.cs | 17 + .../Diagnostics/Demangler/Ast/PointerType.cs | 45 + .../Demangler/Ast/PostfixExpression.cs | 22 + .../Demangler/Ast/PostfixQualifiedType.cs | 20 + .../Demangler/Ast/PrefixExpression.cs | 22 + .../Demangler/Ast/QualifiedName.cs | 23 + .../Diagnostics/Demangler/Ast/Qualifier.cs | 111 + .../Demangler/Ast/ReferenceType.cs | 47 + .../Diagnostics/Demangler/Ast/SpecialName.cs | 20 + .../Demangler/Ast/SpecialSubstitution.cs | 82 + .../Demangler/Ast/StdQualifiedName.cs | 15 + .../Demangler/Ast/TemplateArguments.cs | 26 + .../Demangler/Ast/ThrowExpression.cs | 20 + .../HOS/Diagnostics/Demangler/Demangler.cs | 3360 +++++++ src/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs | 77 + src/Ryujinx.HLE/HOS/Horizon.cs | 477 + src/Ryujinx.HLE/HOS/HorizonFsClient.cs | 119 + src/Ryujinx.HLE/HOS/IdDictionary.cs | 75 + src/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs | 27 + src/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs | 93 + src/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs | 8 + src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs | 281 + src/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs | 13 + src/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs | 58 + .../HOS/Ipc/IpcRecvListBuffDesc.cs | 21 + .../HOS/Ipc/ServiceProcessRequest.cs | 4 + .../Kernel/Common/IKFutureSchedulerObject.cs | 7 + .../HOS/Kernel/Common/KAutoObject.cs | 73 + .../HOS/Kernel/Common/KResourceLimit.cs | 188 + .../Kernel/Common/KSynchronizationObject.cs | 35 + .../HOS/Kernel/Common/KSystemControl.cs | 78 + .../HOS/Kernel/Common/KTimeManager.cs | 199 + .../HOS/Kernel/Common/KernelInit.cs | 89 + .../HOS/Kernel/Common/KernelTransfer.cs | 73 + .../HOS/Kernel/Common/LimitableResource.cs | 13 + .../HOS/Kernel/Common/MemoryArrange.cs | 12 + .../HOS/Kernel/Common/MemorySize.cs | 9 + .../HOS/Kernel/Common/MersenneTwister.cs | 128 + .../HOS/Kernel/Ipc/ChannelState.cs | 10 + .../HOS/Kernel/Ipc/KBufferDescriptor.cs | 20 + .../HOS/Kernel/Ipc/KBufferDescriptorTable.cs | 223 + src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs | 144 + .../HOS/Kernel/Ipc/KClientSession.cs | 84 + .../HOS/Kernel/Ipc/KLightClientSession.cs | 16 + .../HOS/Kernel/Ipc/KLightServerSession.cs | 16 + .../HOS/Kernel/Ipc/KLightSession.cs | 16 + src/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs | 74 + src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs | 87 + .../HOS/Kernel/Ipc/KServerSession.cs | 1247 +++ src/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs | 54 + .../HOS/Kernel/Ipc/KSessionRequest.cs | 33 + src/Ryujinx.HLE/HOS/Kernel/KernelConstants.cs | 20 + src/Ryujinx.HLE/HOS/Kernel/KernelContext.cs | 160 + src/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs | 73 + .../HOS/Kernel/Memory/DramMemoryMap.cs | 18 + .../HOS/Kernel/Memory/KCodeMemory.cs | 169 + .../HOS/Kernel/Memory/KMemoryBlock.cs | 156 + .../HOS/Kernel/Memory/KMemoryBlockManager.cs | 288 + .../Kernel/Memory/KMemoryBlockSlabManager.cs | 19 + .../HOS/Kernel/Memory/KMemoryInfo.cs | 36 + .../HOS/Kernel/Memory/KMemoryManager.cs | 65 + .../HOS/Kernel/Memory/KMemoryPermission.cs | 46 + .../HOS/Kernel/Memory/KMemoryRegionManager.cs | 242 + .../HOS/Kernel/Memory/KPageBitmap.cs | 298 + .../HOS/Kernel/Memory/KPageHeap.cs | 285 + .../HOS/Kernel/Memory/KPageList.cs | 97 + .../HOS/Kernel/Memory/KPageNode.cs | 14 + .../HOS/Kernel/Memory/KPageTable.cs | 269 + .../HOS/Kernel/Memory/KPageTableBase.cs | 3107 ++++++ .../HOS/Kernel/Memory/KScopedPageList.cs | 27 + .../HOS/Kernel/Memory/KSharedMemory.cs | 64 + .../HOS/Kernel/Memory/KSlabHeap.cs | 50 + .../HOS/Kernel/Memory/KTransferMemory.cs | 130 + .../HOS/Kernel/Memory/MemoryAttribute.cs | 21 + .../HOS/Kernel/Memory/MemoryFillValue.cs | 10 + .../HOS/Kernel/Memory/MemoryRegion.cs | 10 + .../HOS/Kernel/Memory/MemoryState.cs | 172 + .../HOS/Kernel/Memory/SharedMemoryStorage.cs | 49 + .../Kernel/Process/CapabilityExtensions.cs | 22 + .../HOS/Kernel/Process/CapabilityType.cs | 19 + .../HOS/Kernel/Process/HleProcessDebugger.cs | 465 + .../HOS/Kernel/Process/IProcessContext.cs | 17 + .../Kernel/Process/IProcessContextFactory.cs | 9 + .../HOS/Kernel/Process/KContextIdManager.cs | 83 + .../HOS/Kernel/Process/KHandleEntry.cs | 19 + .../HOS/Kernel/Process/KHandleTable.cs | 278 + .../HOS/Kernel/Process/KProcess.cs | 1179 +++ .../Kernel/Process/KProcessCapabilities.cs | 338 + .../HOS/Kernel/Process/KTlsPageInfo.cs | 77 + .../HOS/Kernel/Process/KTlsPageManager.cs | 61 + .../HOS/Kernel/Process/ProcessContext.cs | 37 + .../Kernel/Process/ProcessContextFactory.cs | 12 + .../Kernel/Process/ProcessCreationFlags.cs | 47 + .../HOS/Kernel/Process/ProcessCreationInfo.cs | 37 + .../Kernel/Process/ProcessExecutionContext.cs | 43 + .../HOS/Kernel/Process/ProcessState.cs | 14 + .../HOS/Kernel/Process/ProcessTamperInfo.cs | 24 + .../SupervisorCall/CodeMemoryOperation.cs | 10 + .../Kernel/SupervisorCall/ExternalEvent.cs | 25 + .../HOS/Kernel/SupervisorCall/InfoType.cs | 37 + .../HOS/Kernel/SupervisorCall/MemoryInfo.cs | 37 + .../SupervisorCall/PointerSizedAttribute.cs | 9 + .../HOS/Kernel/SupervisorCall/SvcAttribute.cs | 15 + .../Kernel/SupervisorCall/SvcImplAttribute.cs | 9 + .../HOS/Kernel/SupervisorCall/Syscall.cs | 3113 ++++++ .../Kernel/SupervisorCall/SyscallHandler.cs | 44 + .../Kernel/SupervisorCall/ThreadContext.cs | 22 + .../HOS/Kernel/Threading/ArbitrationType.cs | 9 + .../HOS/Kernel/Threading/KAddressArbiter.cs | 575 ++ .../Kernel/Threading/KConditionVariable.cs | 70 + .../HOS/Kernel/Threading/KCriticalSection.cs | 64 + .../HOS/Kernel/Threading/KEvent.cs | 14 + .../HOS/Kernel/Threading/KPriorityQueue.cs | 286 + .../HOS/Kernel/Threading/KReadableEvent.cs | 62 + .../HOS/Kernel/Threading/KScheduler.cs | 680 ++ .../HOS/Kernel/Threading/KSynchronization.cs | 142 + .../HOS/Kernel/Threading/KThread.cs | 1435 +++ .../HOS/Kernel/Threading/KThreadContext.cs | 33 + .../HOS/Kernel/Threading/KWritableEvent.cs | 25 + .../HOS/Kernel/Threading/SignalType.cs | 9 + .../HOS/Kernel/Threading/ThreadSchedState.cs | 23 + .../HOS/Kernel/Threading/ThreadType.cs | 9 + src/Ryujinx.HLE/HOS/LibHacHorizonManager.cs | 119 + src/Ryujinx.HLE/HOS/ModLoader.cs | 831 ++ src/Ryujinx.HLE/HOS/ResultCode.cs | 12 + src/Ryujinx.HLE/HOS/ServiceCtx.cs | 40 + .../Services/Account/Acc/AccountManager.cs | 254 + .../Account/Acc/AccountSaveDataManager.cs | 76 + .../AccountService/IManagerForApplication.cs | 75 + .../IManagerForSystemService.cs | 47 + .../Account/Acc/AccountService/IProfile.cs | 40 + .../Acc/AccountService/IProfileEditor.cs | 54 + .../Acc/AccountService/ManagerServer.cs | 199 + .../Acc/AccountService/ProfileServer.cs | 114 + .../Account/Acc/ApplicationServiceServer.cs | 254 + .../Acc/AsyncContext/AsyncExecution.cs | 56 + .../Services/Account/Acc/DefaultUserImage.jpg | Bin 0 -> 49000 bytes .../Acc/IAccountServiceForAdministrator.cs | 129 + .../Acc/IAccountServiceForApplication.cs | 200 + .../Acc/IAccountServiceForSystemService.cs | 107 + .../HOS/Services/Account/Acc/IAsyncContext.cs | 79 + .../IAsyncNetworkServiceLicenseKindContext.cs | 38 + .../Account/Acc/IBaasAccessTokenAccessor.cs | 8 + .../Acc/ProfilesJsonSerializerContext.cs | 11 + .../Account/Acc/Types/AccountServiceFlag.cs | 10 + .../Account/Acc/Types/AccountState.cs | 12 + .../Acc/Types/NetworkServiceLicenseKind.cs | 8 + .../Account/Acc/Types/ProfilesJson.cs | 10 + .../HOS/Services/Account/Acc/Types/UserId.cs | 64 + .../Services/Account/Acc/Types/UserProfile.cs | 87 + .../Account/Acc/Types/UserProfileJson.cs | 12 + .../HOS/Services/Account/Dauth/IService.cs | 8 + .../HOS/Services/Account/ResultCode.cs | 24 + .../ILibraryAppletProxy.cs | 105 + .../ISystemAppletProxy.cs | 113 + .../ILibraryAppletAccessor.cs | 261 + .../LibraryAppletProxy/AppletStandalone.cs | 16 + .../ILibraryAppletSelfAccessor.cs | 78 + .../IProcessWindingController.cs | 24 + .../IAppletCommonFunctions.cs | 7 + .../SystemAppletProxy/IApplicationCreator.cs | 7 + .../SystemAppletProxy/IAudioController.cs | 72 + .../SystemAppletProxy/ICommonStateGetter.cs | 335 + .../SystemAppletProxy/IDebugFunctions.cs | 7 + .../SystemAppletProxy/IDisplayController.cs | 106 + .../IGlobalStateController.cs | 7 + .../SystemAppletProxy/IHomeMenuFunctions.cs | 48 + .../ILibraryAppletCreator.cs | 93 + .../SystemAppletProxy/ISelfController.cs | 436 + .../SystemAppletProxy/IWindowController.cs | 36 + .../Types/AlbumReportOption.cs | 10 + .../SystemAppletProxy/Types/AppletMessage.cs | 36 + .../SystemAppletProxy/Types/FocusState.cs | 8 + .../SystemAppletProxy/Types/OperationMode.cs | 8 + .../Types/WirelessPriorityMode.cs | 9 + .../HOS/Services/Am/AppletAE/AppletFifo.cs | 120 + .../HOS/Services/Am/AppletAE/AppletSession.cs | 77 + .../IAllSystemAppletProxiesService.cs | 29 + .../HOS/Services/Am/AppletAE/IAppletFifo.cs | 10 + .../HOS/Services/Am/AppletAE/IStorage.cs | 23 + .../Services/Am/AppletAE/IStorageAccessor.cs | 86 + .../Am/AppletAE/Storage/StorageHelper.cs | 26 + .../Services/Am/AppletAE/Types/AppletId.cs | 27 + .../Am/AppletAE/Types/AppletIdentityInfo.cs | 12 + .../Types/AppletProcessLaunchReason.cs | 12 + .../Am/AppletAE/Types/LibraryAppletInfo.cs | 11 + .../Am/AppletAE/Types/LibraryAppletMode.cs | 14 + .../ApplicationProxy/IApplicationFunctions.cs | 677 ++ .../Types/LaunchParameterKind.cs | 9 + .../Types/ProgramSpecifyKind.cs | 9 + .../IApplicationProxy.cs | 87 + .../Am/AppletOE/IApplicationProxyService.cs | 19 + .../Services/Am/Idle/IPolicyManagerSystem.cs | 8 + .../Services/Am/Omm/IOperationModeManager.cs | 8 + src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs | 30 + .../Services/Am/Spsm/IPowerStateInterface.cs | 8 + .../HOS/Services/Am/Tcap/IManager.cs | 8 + src/Ryujinx.HLE/HOS/Services/Apm/IManager.cs | 43 + .../HOS/Services/Apm/IManagerPrivileged.cs | 19 + src/Ryujinx.HLE/HOS/Services/Apm/ISession.cs | 45 + .../HOS/Services/Apm/ISystemManager.cs | 42 + .../HOS/Services/Apm/ManagerServer.cs | 31 + .../HOS/Services/Apm/PerformanceState.cs | 25 + .../HOS/Services/Apm/ResultCode.cs | 12 + .../HOS/Services/Apm/SessionServer.cs | 58 + .../HOS/Services/Apm/SystemManagerServer.cs | 28 + .../HOS/Services/Apm/Types/CpuBoostMode.cs | 9 + .../Apm/Types/PerformanceConfiguration.cs | 22 + .../HOS/Services/Apm/Types/PerformanceMode.cs | 8 + .../Services/Arp/ApplicationLaunchProperty.cs | 43 + .../HOS/Services/Arp/LibHacIReader.cs | 75 + .../HOS/Services/Bgtc/IStateControlService.cs | 8 + .../HOS/Services/Bgtc/ITaskService.cs | 8 + .../BluetoothDriver/BluetoothEventManager.cs | 25 + .../Services/Bluetooth/IBluetoothDriver.cs | 103 + .../HOS/Services/Bluetooth/IBluetoothUser.cs | 30 + .../BluetoothManager/BtmUser/IBtmUserCore.cs | 128 + .../HOS/Services/BluetoothManager/IBtm.cs | 8 + .../Services/BluetoothManager/IBtmDebug.cs | 8 + .../Services/BluetoothManager/IBtmSystem.cs | 8 + .../HOS/Services/BluetoothManager/IBtmUser.cs | 19 + .../Services/BluetoothManager/ResultCode.cs | 10 + .../HOS/Services/Caps/CaptureManager.cs | 140 + .../Services/Caps/IAlbumAccessorService.cs | 8 + .../Services/Caps/IAlbumApplicationService.cs | 69 + .../HOS/Services/Caps/IAlbumControlService.cs | 15 + .../Caps/IScreenShotApplicationService.cs | 105 + .../Caps/IScreenShotControlService.cs | 8 + .../HOS/Services/Caps/IScreenshotService.cs | 8 + .../HOS/Services/Caps/ResultCode.cs | 18 + .../Services/Caps/Types/AlbumFileDateTime.cs | 16 + .../Caps/Types/AlbumImageOrientation.cs | 10 + .../HOS/Services/Caps/Types/AlbumStorage.cs | 8 + .../Caps/Types/ApplicationAlbumEntry.cs | 17 + .../HOS/Services/Caps/Types/ContentType.cs | 10 + .../Caps/Types/ScreenShotAttribute.cs | 15 + .../HOS/Services/Cec/ICecManager.cs | 8 + .../HOS/Services/CommandCmifAttribute.cs | 12 + .../HOS/Services/CommandTIpcAttribute.cs | 12 + .../HOS/Services/DisposableIpcService.cs | 22 + src/Ryujinx.HLE/HOS/Services/DummyService.cs | 12 + .../HOS/Services/Ectx/IReaderForSystem.cs | 8 + .../Services/Ectx/IWriterForApplication.cs | 8 + .../HOS/Services/Ectx/IWriterForSystem.cs | 8 + src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs | 8 + src/Ryujinx.HLE/HOS/Services/Erpt/ISession.cs | 8 + .../HOS/Services/Es/IETicketService.cs | 8 + .../HOS/Services/Eupld/IControl.cs | 8 + .../HOS/Services/Eupld/IRequest.cs | 8 + .../HOS/Services/Fatal/IPrivateService.cs | 8 + .../HOS/Services/Fatal/IService.cs | 147 + .../HOS/Services/Fatal/Types/CpuContext32.cs | 25 + .../HOS/Services/Fatal/Types/CpuContext64.cs | 24 + .../HOS/Services/Fatal/Types/FatalPolicy.cs | 9 + .../FileSystemProxy/FileSystemProxyHelper.cs | 164 + .../Services/Fs/FileSystemProxy/IDirectory.cs | 50 + .../HOS/Services/Fs/FileSystemProxy/IFile.cs | 93 + .../Fs/FileSystemProxy/IFileSystem.cs | 231 + .../Services/Fs/FileSystemProxy/IStorage.cs | 62 + .../Services/Fs/FileSystemProxy/LazyFile.cs | 65 + .../HOS/Services/Fs/IDeviceOperator.cs | 58 + .../HOS/Services/Fs/IFileSystemProxy.cs | 1441 +++ .../Services/Fs/IFileSystemProxyForLoader.cs | 8 + .../HOS/Services/Fs/IMultiCommitManager.cs | 44 + .../HOS/Services/Fs/IProgramRegistry.cs | 8 + .../HOS/Services/Fs/ISaveDataInfoReader.cs | 39 + src/Ryujinx.HLE/HOS/Services/Fs/ResultCode.cs | 16 + .../HOS/Services/Fs/Types/FileSystemType.cs | 12 + .../HOS/Services/Grc/IGrcService.cs | 8 + .../HOS/Services/Grc/IRemoteVideoTransfer.cs | 8 + src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs | 109 + .../HOS/Services/Hid/HidDevices/BaseDevice.cs | 14 + .../Services/Hid/HidDevices/DebugPadDevice.cs | 28 + .../Services/Hid/HidDevices/KeyboardDevice.cs | 35 + .../Services/Hid/HidDevices/MouseDevice.cs | 36 + .../Services/Hid/HidDevices/NpadDevices.cs | 643 ++ .../Services/Hid/HidDevices/TouchDevice.cs | 48 + .../Hid/HidDevices/Types/ControllerConfig.cs | 8 + .../Hid/HidDevices/Types/GamepadInput.cs | 10 + .../Hid/HidDevices/Types/JoystickPosition.cs | 8 + .../Hid/HidDevices/Types/KeyboardInput.cs | 8 + .../Hid/HidDevices/Types/SixAxisInput.cs | 13 + .../Hid/HidDevices/Types/TouchPoint.cs | 14 + .../HOS/Services/Hid/HidServer/HidUtils.cs | 50 + .../HidServer/IActiveVibrationDeviceList.cs | 18 + .../Services/Hid/HidServer/IAppletResource.cs | 35 + .../Types/Npad/NpadHandheldActivationMode.cs | 9 + .../HidServer/Types/Npad/NpadJoyDeviceType.cs | 8 + .../Types/SixAxis/AccelerometerParameters.cs | 8 + .../Types/SixAxis/GyroscopeZeroDriftMode.cs | 9 + .../Types/SixAxis/SensorFusionParameters.cs | 8 + .../Types/Vibration/VibrationDeviceHandle.cs | 10 + .../Vibration/VibrationDevicePosition.cs | 9 + .../Types/Vibration/VibrationDeviceType.cs | 9 + .../Types/Vibration/VibrationDeviceValue.cs | 8 + .../Types/Vibration/VibrationValue.cs | 34 + .../HOS/Services/Hid/IHidDebugServer.cs | 8 + .../HOS/Services/Hid/IHidServer.cs | 1852 ++++ .../HOS/Services/Hid/IHidSystemServer.cs | 76 + .../HOS/Services/Hid/IHidbusServer.cs | 31 + .../HOS/Services/Hid/ISystemServer.cs | 8 + .../HOS/Services/Hid/Irs/IIrSensorServer.cs | 240 + .../Services/Hid/Irs/IIrSensorSystemServer.cs | 8 + .../HOS/Services/Hid/Irs/ResultCode.cs | 15 + .../Irs/Types/ImageTransferProcessorState.cs | 12 + .../Services/Hid/Irs/Types/IrCameraHandle.cs | 12 + .../Types/PackedClusteringProcessorConfig.cs | 25 + .../PackedImageTransferProcessorConfig.cs | 19 + .../Irs/Types/PackedMomentProcessorConfig.cs | 23 + .../Types/PackedTeraPluginProcessorConfig.cs | 14 + .../HOS/Services/Hid/ResultCode.cs | 15 + .../Services/Hid/Types/AppletFooterUiType.cs | 30 + .../HOS/Services/Hid/Types/HidVector.cs | 9 + .../HOS/Services/Hid/Types/Npad/BusHandle.cs | 14 + .../HOS/Services/Hid/Types/Npad/BusType.cs | 9 + .../Services/Hid/Types/Npad/ControllerKeys.cs | 45 + .../Services/Hid/Types/Npad/ControllerType.cs | 19 + .../HOS/Services/Hid/Types/Npad/NpadColor.cs | 37 + .../HOS/Services/Hid/Types/Npad/NpadIdType.cs | 16 + .../Services/Hid/Types/Npad/NpadStyleIndex.cs | 13 + .../Services/Hid/Types/Npad/PlayerIndex.cs | 17 + .../HOS/Services/Hid/Types/NpadJoyHoldType.cs | 8 + .../SharedMemory/Common/AnalogStickState.cs | 8 + .../SharedMemory/Common/AtomicStorage.cs | 26 + .../SharedMemory/Common/ISampledDataStruct.cs | 65 + .../Hid/Types/SharedMemory/Common/RingLifo.cs | 149 + .../DebugPad/DebugPadAttribute.cs | 11 + .../SharedMemory/DebugPad/DebugPadButton.cs | 24 + .../SharedMemory/DebugPad/DebugPadState.cs | 15 + .../SharedMemory/Keyboard/KeyboardKey.cs | 29 + .../SharedMemory/Keyboard/KeyboardKeyShift.cs | 138 + .../SharedMemory/Keyboard/KeyboardModifier.cs | 20 + .../SharedMemory/Keyboard/KeyboardState.cs | 13 + .../SharedMemory/Mouse/MouseAttribute.cs | 12 + .../Types/SharedMemory/Mouse/MouseButton.cs | 15 + .../Types/SharedMemory/Mouse/MouseState.cs | 19 + .../Hid/Types/SharedMemory/Npad/DeviceType.cs | 29 + .../Types/SharedMemory/Npad/NpadAttribute.cs | 16 + .../SharedMemory/Npad/NpadBatteryLevel.cs | 11 + .../Hid/Types/SharedMemory/Npad/NpadButton.cs | 44 + .../SharedMemory/Npad/NpadColorAttribute.cs | 9 + .../SharedMemory/Npad/NpadCommonState.cs | 16 + .../Npad/NpadFullKeyColorState.cs | 9 + .../SharedMemory/Npad/NpadGcTriggerState.cs | 15 + .../SharedMemory/Npad/NpadInternalState.cs | 69 + .../Npad/NpadJoyAssignmentMode.cs | 8 + .../SharedMemory/Npad/NpadJoyColorState.cs | 11 + .../Types/SharedMemory/Npad/NpadLarkType.cs | 11 + .../Types/SharedMemory/Npad/NpadLuciaType.cs | 10 + .../Hid/Types/SharedMemory/Npad/NpadState.cs | 18 + .../Types/SharedMemory/Npad/NpadStyleTag.cs | 76 + .../Npad/NpadSystemButtonProperties.cs | 11 + .../SharedMemory/Npad/NpadSystemProperties.cs | 24 + .../Npad/SixAxisSensorAttribute.cs | 12 + .../SharedMemory/Npad/SixAxisSensorState.cs | 19 + .../Hid/Types/SharedMemory/SharedMemory.cs | 66 + .../TouchScreen/TouchAttribute.cs | 12 + .../TouchScreen/TouchScreenState.cs | 15 + .../SharedMemory/TouchScreen/TouchState.cs | 19 + src/Ryujinx.HLE/HOS/Services/IpcService.cs | 284 + .../Services/Ldn/IMonitorServiceCreator.cs | 8 + .../HOS/Services/Ldn/ISystemServiceCreator.cs | 8 + .../HOS/Services/Ldn/IUserServiceCreator.cs | 19 + src/Ryujinx.HLE/HOS/Services/Ldn/LdnConst.cs | 12 + .../HOS/Services/Ldn/Lp2p/IServiceCreator.cs | 9 + .../HOS/Services/Ldn/NetworkInterface.cs | 59 + .../HOS/Services/Ldn/ResultCode.cs | 24 + .../HOS/Services/Ldn/Types/AcceptPolicy.cs | 10 + .../HOS/Services/Ldn/Types/AddressEntry.cs | 13 + .../HOS/Services/Ldn/Types/AddressList.cs | 11 + .../Services/Ldn/Types/CommonNetworkInfo.cs | 16 + .../Services/Ldn/Types/DisconnectReason.cs | 13 + .../HOS/Services/Ldn/Types/IntentId.cs | 13 + .../HOS/Services/Ldn/Types/LdnNetworkInfo.cs | 23 + .../HOS/Services/Ldn/Types/NetworkConfig.cs | 16 + .../HOS/Services/Ldn/Types/NetworkId.cs | 12 + .../HOS/Services/Ldn/Types/NetworkInfo.cs | 12 + .../HOS/Services/Ldn/Types/NetworkState.cs | 13 + .../HOS/Services/Ldn/Types/NetworkType.cs | 10 + .../HOS/Services/Ldn/Types/NodeInfo.cs | 18 + .../Services/Ldn/Types/NodeLatestUpdate.cs | 62 + .../Ldn/Types/NodeLatestUpdateFlags.cs | 12 + .../HOS/Services/Ldn/Types/ScanFilter.cs | 16 + .../HOS/Services/Ldn/Types/ScanFilterFlag.cs | 18 + .../HOS/Services/Ldn/Types/SecurityConfig.cs | 13 + .../HOS/Services/Ldn/Types/SecurityMode.cs | 9 + .../Services/Ldn/Types/SecurityParameter.cs | 12 + .../HOS/Services/Ldn/Types/Ssid.cs | 12 + .../HOS/Services/Ldn/Types/UserConfig.cs | 12 + .../Ldn/UserServiceCreator/AccessPoint.cs | 103 + .../Ldn/UserServiceCreator/INetworkClient.cs | 25 + .../IUserLocalCommunicationService.cs | 1110 +++ .../UserServiceCreator/LdnDisabledClient.cs | 63 + .../LdnMitm/LanDiscovery.cs | 611 ++ .../UserServiceCreator/LdnMitm/LanProtocol.cs | 314 + .../LdnMitm/LdnMitmClient.cs | 104 + .../LdnMitm/Proxy/ILdnSocket.cs | 12 + .../LdnMitm/Proxy/ILdnTcpSocket.cs | 8 + .../LdnMitm/Proxy/LdnProxyTcpClient.cs | 99 + .../LdnMitm/Proxy/LdnProxyTcpServer.cs | 54 + .../LdnMitm/Proxy/LdnProxyTcpSession.cs | 83 + .../LdnMitm/Proxy/LdnProxyUdpServer.cs | 157 + .../LdnMitm/Types/LanPacketHeader.cs | 16 + .../LdnMitm/Types/LanPacketType.cs | 10 + .../NetworkChangeEventArgs.cs | 24 + .../Ldn/UserServiceCreator/Station.cs | 114 + .../Types/ConnectPrivateRequest.cs | 16 + .../Types/ConnectRequest.cs | 15 + .../Types/CreateAccessPointPrivateRequest.cs | 18 + .../Types/CreateAccessPointRequest.cs | 16 + .../UserServiceCreator/Types/NetworkError.cs | 22 + .../Types/NetworkErrorMessage.cs | 10 + .../Services/Loader/IDebugMonitorInterface.cs | 8 + .../Loader/IProcessManagerInterface.cs | 8 + .../HOS/Services/Loader/IShellInterface.cs | 8 + .../HOS/Services/Loader/ResultCode.cs | 46 + src/Ryujinx.HLE/HOS/Services/Mig/IService.cs | 8 + .../HOS/Services/Mii/DatabaseImpl.cs | 325 + .../Services/Mii/DatabaseSessionMetadata.cs | 24 + src/Ryujinx.HLE/HOS/Services/Mii/Helper.cs | 50 + .../HOS/Services/Mii/IImageDatabaseService.cs | 41 + .../HOS/Services/Mii/IStaticService.cs | 32 + .../HOS/Services/Mii/MiiDatabaseManager.cs | 514 + .../HOS/Services/Mii/ResultCode.cs | 30 + .../Mii/StaticService/DatabaseServiceImpl.cs | 266 + .../Mii/StaticService/IDatabaseService.cs | 425 + src/Ryujinx.HLE/HOS/Services/Mii/Types/Age.cs | 10 + .../HOS/Services/Mii/Types/BeardType.cs | 15 + .../HOS/Services/Mii/Types/CharInfo.cs | 482 + .../HOS/Services/Mii/Types/CharInfoElement.cs | 21 + .../HOS/Services/Mii/Types/CommonColor.cs | 9 + .../HOS/Services/Mii/Types/CoreData.cs | 912 ++ .../HOS/Services/Mii/Types/CreateId.cs | 45 + .../HOS/Services/Mii/Types/DefaultMii.cs | 197 + .../HOS/Services/Mii/Types/EyeType.cs | 69 + .../HOS/Services/Mii/Types/EyebrowType.cs | 33 + .../HOS/Services/Mii/Types/FacelineColor.cs | 19 + .../HOS/Services/Mii/Types/FacelineMake.cs | 21 + .../HOS/Services/Mii/Types/FacelineType.cs | 21 + .../HOS/Services/Mii/Types/FacelineWrinkle.cs | 21 + .../HOS/Services/Mii/Types/FontRegion.cs | 10 + .../HOS/Services/Mii/Types/Gender.cs | 12 + .../HOS/Services/Mii/Types/GlassType.cs | 29 + .../HOS/Services/Mii/Types/HairFlip.cs | 11 + .../HOS/Services/Mii/Types/HairType.cs | 141 + .../HOS/Services/Mii/Types/IElement.cs | 9 + .../HOS/Services/Mii/Types/IStoredData.cs | 15 + .../HOS/Services/Mii/Types/MoleType.cs | 11 + .../HOS/Services/Mii/Types/MouthType.cs | 45 + .../HOS/Services/Mii/Types/MustacheType.cs | 15 + .../HOS/Services/Mii/Types/Nickname.cs | 121 + .../Mii/Types/NintendoFigurineDatabase.cs | 254 + .../HOS/Services/Mii/Types/NoseType.cs | 27 + .../HOS/Services/Mii/Types/Race.cs | 10 + .../Services/Mii/Types/RandomMiiConstants.cs | 2243 +++++ .../HOS/Services/Mii/Types/Source.cs | 8 + .../HOS/Services/Mii/Types/SourceFlag.cs | 12 + .../Services/Mii/Types/SpecialMiiKeyCode.cs | 17 + .../HOS/Services/Mii/Types/StoreData.cs | 237 + .../Services/Mii/Types/StoreDataElement.cs | 21 + .../HOS/Services/Mii/Types/Ver3StoreData.cs | 19 + .../HOS/Services/Mii/UtilityImpl.cs | 75 + .../Services/Mnpp/IServiceForApplication.cs | 63 + .../HOS/Services/Mnpp/ResultCode.cs | 13 + .../HOS/Services/Ncm/IContentManager.cs | 8 + .../Ncm/Lr/ILocationResolverManager.cs | 22 + .../ILocationResolver.cs | 257 + .../HOS/Services/Ncm/Lr/ResultCode.cs | 20 + .../HOS/Services/News/IServiceCreator.cs | 12 + .../HOS/Services/Nfc/IAmManager.cs | 8 + .../HOS/Services/Nfc/ISystemManager.cs | 19 + .../HOS/Services/Nfc/IUserManager.cs | 19 + .../HOS/Services/Nfc/Mifare/IUserManager.cs | 8 + .../HOS/Services/Nfc/NfcManager/INfc.cs | 63 + .../NfcManager/Types/NfcPermissionLevel.cs | 8 + .../Services/Nfc/NfcManager/Types/State.cs | 8 + .../Nfc/Nfp/AmiiboJsonSerializerContext.cs | 10 + .../HOS/Services/Nfc/Nfp/IDebugManager.cs | 19 + .../HOS/Services/Nfc/Nfp/ISystemManager.cs | 19 + .../HOS/Services/Nfc/Nfp/IUserManager.cs | 19 + .../HOS/Services/Nfc/Nfp/NfpManager/INfp.cs | 997 ++ .../Nfp/NfpManager/Types/AmiiboConstants.cs | 8 + .../Nfc/Nfp/NfpManager/Types/CommonInfo.cs | 17 + .../Nfc/Nfp/NfpManager/Types/DeviceType.cs | 7 + .../Nfc/Nfp/NfpManager/Types/ModelInfo.cs | 16 + .../Nfc/Nfp/NfpManager/Types/MountTarget.cs | 9 + .../Nfc/Nfp/NfpManager/Types/NfpDevice.cs | 23 + .../Nfp/NfpManager/Types/NfpDeviceState.cs | 13 + .../NfpManager/Types/NfpPermissionLevel.cs | 9 + .../Nfc/Nfp/NfpManager/Types/RegisterInfo.cs | 19 + .../Nfc/Nfp/NfpManager/Types/State.cs | 8 + .../Nfc/Nfp/NfpManager/Types/TagInfo.cs | 16 + .../Nfp/NfpManager/Types/VirtualAmiiboFile.cs | 22 + .../HOS/Services/Nfc/Nfp/ResultCode.cs | 18 + .../HOS/Services/Nfc/Nfp/VirtualAmiibo.cs | 203 + src/Ryujinx.HLE/HOS/Services/Ngct/IService.cs | 22 + .../Ngct/IServiceWithManagementApi.cs | 22 + .../HOS/Services/Ngct/NgctServer.cs | 92 + .../HOS/Services/Nifm/IStaticService.cs | 30 + .../HOS/Services/Nifm/ResultCode.cs | 15 + .../GeneralService/GeneralServiceManager.cs | 30 + .../Types/GeneralServiceDetail.cs | 8 + .../Nifm/StaticService/IGeneralService.cs | 205 + .../Services/Nifm/StaticService/IRequest.cs | 146 + .../Nifm/StaticService/Types/DnsSetting.cs | 31 + .../Types/InternetConnectionState.cs | 11 + .../Types/InternetConnectionStatus.cs | 12 + .../Types/InternetConnectionType.cs | 9 + .../StaticService/Types/IpAddressSetting.cs | 24 + .../Nifm/StaticService/Types/IpSettingData.cs | 13 + .../Nifm/StaticService/Types/IpV4Address.cs | 24 + .../StaticService/Types/NetworkProfileData.cs | 17 + .../Nifm/StaticService/Types/ProxySetting.cs | 27 + .../Types/WirelessSettingData.cs | 15 + .../Services/Nim/INetworkInstallManager.cs | 8 + .../Services/Nim/IShopServiceAccessServer.cs | 21 + .../Nim/IShopServiceAccessServerInterface.cs | 51 + .../Nim/IShopServiceAccessSystemInterface.cs | 8 + .../HOS/Services/Nim/IShopServiceAccessor.cs | 42 + .../HOS/Services/Nim/IShopServiceAsync.cs | 7 + .../HOS/Services/Nim/IShopServiceManager.cs | 8 + .../HOS/Services/Nim/Ntc/IStaticService.cs | 24 + .../IEnsureNetworkClockAvailabilityService.cs | 77 + .../HOS/Services/Nim/ResultCode.cs | 12 + .../INotificationServicesForApplication.cs | 8 + .../INotificationServicesForSystem.cs | 8 + .../HOS/Services/Npns/INpnsSystem.cs | 8 + .../HOS/Services/Npns/INpnsUser.cs | 8 + .../Services/Ns/Aoc/IAddOnContentManager.cs | 362 + .../Ns/Aoc/IContentsServiceManager.cs | 7 + .../Services/Ns/Aoc/IPurchaseEventManager.cs | 68 + .../HOS/Services/Ns/Aoc/ResultCode.cs | 13 + .../Ns/IApplicationManagerInterface.cs | 29 + .../HOS/Services/Ns/IDevelopInterface.cs | 8 + ...ReadOnlyApplicationControlDataInterface.cs | 28 + .../Services/Ns/IServiceGetterInterface.cs | 30 + .../HOS/Services/Ns/ISystemUpdateInterface.cs | 8 + .../Ns/IVulnerabilityManagerInterface.cs | 8 + .../HOS/Services/Nv/Host1xContext.cs | 32 + .../HOS/Services/Nv/INvDrvDebugFSServices.cs | 8 + .../HOS/Services/Nv/INvDrvServices.cs | 580 ++ .../HOS/Services/Nv/INvGemControl.cs | 8 + .../HOS/Services/Nv/INvGemCoreDump.cs | 8 + .../Services/Nv/NvDrvServices/NvDeviceFile.cs | 94 + .../NvHostAsGpu/NvHostAsGpuDeviceFile.cs | 400 + .../NvHostAsGpu/Types/AddressSpaceContext.cs | 190 + .../NvHostAsGpu/Types/AddressSpaceFlags.cs | 11 + .../NvHostAsGpu/Types/AllocSpaceArguments.cs | 14 + .../NvHostAsGpu/Types/BindChannelArguments.cs | 10 + .../NvHostAsGpu/Types/FreeSpaceArguments.cs | 12 + .../Types/GetVaRegionsArguments.cs | 23 + .../Types/InitializeExArguments.cs | 16 + .../NvHostAsGpu/Types/MapBufferExArguments.cs | 16 + .../NvHostAsGpu/Types/RemapArguments.cs | 15 + .../NvHostAsGpu/Types/UnmapBufferArguments.cs | 9 + .../NvHostChannel/ChannelInitialization.cs | 1361 +++ .../NvHostChannel/NvHostChannelDeviceFile.cs | 578 ++ .../NvHostChannel/NvHostGpuDeviceFile.cs | 98 + .../Types/AllocGpfifoExArguments.cs | 17 + .../Types/AllocObjCtxArguments.cs | 12 + .../Types/GetParameterArguments.cs | 11 + .../Types/MapCommandBufferArguments.cs | 21 + .../NvHostChannel/Types/NvChannel.cs | 11 + .../NvHostChannel/Types/NvChannelPriority.cs | 9 + .../Types/SetErrorNotifierArguments.cs | 13 + .../NvHostChannel/Types/SubmitArguments.cs | 40 + .../Types/SubmitGpfifoArguments.cs | 14 + .../NvHostChannel/Types/SubmitGpfifoFlags.cs | 15 + .../NvHostChannel/Types/ZcullBindArguments.cs | 12 + .../NvHostCtrl/NvHostCtrlDeviceFile.cs | 540 ++ .../NvHostCtrl/Types/EventWaitArguments.cs | 13 + .../Types/GetConfigurationArguments.cs | 34 + .../NvHostCtrl/Types/NvHostEvent.cs | 187 + .../NvHostCtrl/Types/NvHostEventState.cs | 12 + .../NvHostCtrl/Types/NvHostSyncPt.cs | 196 + .../NvHostCtrl/Types/SyncptWaitArguments.cs | 12 + .../NvHostCtrl/Types/SyncptWaitExArguments.cs | 11 + .../NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs | 276 + .../Types/GetActiveSlotMaskArguments.cs | 11 + .../Types/GetCharacteristicsArguments.cs | 59 + .../Types/GetGpuTimeArguments.cs | 11 + .../Types/GetTpcMasksArguments.cs | 15 + .../NvHostCtrlGpu/Types/NumVsmsArguments.cs | 11 + .../Types/VsmsMappingArguments.cs | 14 + .../Types/ZbcSetTableArguments.cs | 15 + .../Types/ZcullGetCtxSizeArguments.cs | 10 + .../Types/ZcullGetInfoArguments.cs | 19 + .../NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs | 11 + .../NvHostProfGpu/NvHostProfGpuDeviceFile.cs | 11 + .../Nv/NvDrvServices/NvInternalResult.cs | 32 + .../Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs | 283 + .../NvDrvServices/NvMap/Types/NvMapAlloc.cs | 15 + .../NvDrvServices/NvMap/Types/NvMapCreate.cs | 11 + .../Nv/NvDrvServices/NvMap/Types/NvMapFree.cs | 14 + .../NvDrvServices/NvMap/Types/NvMapFromId.cs | 11 + .../NvDrvServices/NvMap/Types/NvMapGetId.cs | 11 + .../NvDrvServices/NvMap/Types/NvMapHandle.cs | 40 + .../NvMap/Types/NvMapHandleParam.cs | 12 + .../NvMap/Types/NvMapIdDictionary.cs | 61 + .../NvDrvServices/NvMap/Types/NvMapParam.cs | 12 + src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs | 45 + .../HOS/Services/Nv/NvMemoryAllocator.cs | 310 + .../HOS/Services/Nv/Types/NvFence.cs | 41 + .../Types/NvIoctlNotImplementedException.cs | 55 + .../NvQueryEventNotImplementedException.cs | 51 + .../HOS/Services/Nv/Types/NvResult.cs | 30 + .../HOS/Services/Nv/Types/NvStatus.cs | 15 + .../Olsc/IOlscServiceForApplication.cs | 90 + .../Olsc/IOlscServiceForSystemService.cs | 8 + .../HOS/Services/Olsc/ResultCode.cs | 13 + .../HOS/Services/Pcie/ILogManager.cs | 8 + src/Ryujinx.HLE/HOS/Services/Pcie/IManager.cs | 8 + .../Pctl/IParentalControlServiceFactory.cs | 40 + .../IParentalControlService.cs | 260 + .../HOS/Services/Pctl/ResultCode.cs | 16 + .../Pcv/Bpc/IBoardPowerControlManager.cs | 8 + .../HOS/Services/Pcv/Bpc/IRtcManager.cs | 32 + .../Clkrst/ClkrstManager/IClkrstSession.cs | 63 + .../Pcv/Clkrst/IArbitrationManager.cs | 8 + .../HOS/Services/Pcv/Clkrst/IClkrstManager.cs | 57 + .../HOS/Services/Pcv/IPcvService.cs | 8 + .../HOS/Services/Pcv/ResultCode.cs | 12 + .../Services/Pcv/Rgltr/IRegulatorManager.cs | 8 + .../HOS/Services/Pcv/Rtc/IRtcManager.cs | 8 + .../HOS/Services/Pcv/Types/DeviceCode.cs | 94 + .../HOS/Services/Pm/IBootModeInterface.cs | 8 + .../HOS/Services/Pm/IDebugMonitorInterface.cs | 49 + .../HOS/Services/Pm/IInformationInterface.cs | 27 + .../HOS/Services/Pm/IShellInterface.cs | 21 + src/Ryujinx.HLE/HOS/Services/Pm/ResultCode.cs | 17 + .../HOS/Services/Ptm/Fan/IManager.cs | 8 + .../HOS/Services/Ptm/Fgm/IDebugger.cs | 8 + .../HOS/Services/Ptm/Fgm/ISession.cs | 10 + .../HOS/Services/Ptm/Pcm/IManager.cs | 8 + .../HOS/Services/Ptm/Psm/IPsmServer.cs | 45 + .../HOS/Services/Ptm/Psm/IPsmSession.cs | 88 + .../HOS/Services/Ptm/Psm/Types/ChargerType.cs | 9 + .../HOS/Services/Ptm/Tc/IManager.cs | 8 + .../HOS/Services/Ro/IRoInterface.cs | 600 ++ src/Ryujinx.HLE/HOS/Services/Ro/ResultCode.cs | 27 + .../HOS/Services/Ro/Types/NRRCertification.cs | 15 + .../HOS/Services/Ro/Types/NroInfo.cs | 35 + .../HOS/Services/Ro/Types/NrrHeader.cs | 22 + .../HOS/Services/Ro/Types/NrrInfo.cs | 18 + .../HOS/Services/Sdb/Avm/IAvmService.cs | 8 + .../HOS/Services/Sdb/Pdm/INotifyService.cs | 8 + .../HOS/Services/Sdb/Pdm/IQueryService.cs | 24 + .../QueryPlayStatisticsManager.cs | 84 + .../Types/ApplicationPlayStatistics.cs | 12 + .../Types/PlayLogQueryCapability.cs | 9 + .../HOS/Services/Sdb/Pdm/ResultCode.cs | 15 + .../HOS/Services/Sdb/Pl/ISharedFontManager.cs | 146 + .../HOS/Services/Sdb/Pl/SharedFontManager.cs | 182 + .../Services/Sdb/Pl/Types/SharedFontType.cs | 13 + src/Ryujinx.HLE/HOS/Services/ServerBase.cs | 555 ++ .../HOS/Services/ServiceAttributes.cs | 17 + .../Settings/IFactorySettingsServer.cs | 8 + .../Settings/IFirmwareDebugSettingsServer.cs | 8 + .../HOS/Services/Settings/ISettingsServer.cs | 247 + .../Settings/ISystemSettingsServer.cs | 345 + .../HOS/Services/Settings/KeyCodeMaps.cs | 4849 ++++++++++ .../HOS/Services/Settings/NxSettings.cs | 1712 ++++ .../HOS/Services/Settings/ResultCode.cs | 126 + .../Services/Settings/Types/PlatformRegion.cs | 8 + .../HOS/Services/Sm/IManagerInterface.cs | 8 + .../HOS/Services/Sm/IUserInterface.cs | 262 + src/Ryujinx.HLE/HOS/Services/Sm/ResultCode.cs | 15 + src/Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs | 49 + .../HOS/Services/Sockets/Bsd/BsdContext.cs | 184 + .../HOS/Services/Sockets/Bsd/IClient.cs | 1147 +++ .../Services/Sockets/Bsd/IFileDescriptor.cs | 15 + .../HOS/Services/Sockets/Bsd/ISocket.cs | 53 + .../Sockets/Bsd/Impl/EventFileDescriptor.cs | 153 + .../Impl/EventFileDescriptorPollManager.cs | 119 + .../Sockets/Bsd/Impl/ManagedSocket.cs | 549 ++ .../Bsd/Impl/ManagedSocketPollManager.cs | 174 + .../HOS/Services/Sockets/Bsd/Impl/WSAError.cs | 134 + .../Sockets/Bsd/Impl/WinSockHelper.cs | 347 + .../Services/Sockets/Bsd/ServerInterface.cs | 8 + .../Sockets/Bsd/Types/BsdAddressFamily.cs | 11 + .../Services/Sockets/Bsd/Types/BsdIoctl.cs | 7 + .../Services/Sockets/Bsd/Types/BsdMMsgHdr.cs | 56 + .../Services/Sockets/Bsd/Types/BsdMsgHdr.cs | 212 + .../Services/Sockets/Bsd/Types/BsdSockAddr.cs | 39 + .../Bsd/Types/BsdSocketCreationFlags.cs | 14 + .../Sockets/Bsd/Types/BsdSocketFlags.cs | 22 + .../Sockets/Bsd/Types/BsdSocketOption.cs | 122 + .../Bsd/Types/BsdSocketShutdownFlags.cs | 9 + .../Sockets/Bsd/Types/BsdSocketType.cs | 13 + .../Sockets/Bsd/Types/EventFdFlags.cs | 12 + .../Sockets/Bsd/Types/IPollManager.cs | 13 + .../Services/Sockets/Bsd/Types/LinuxError.cs | 155 + .../Services/Sockets/Bsd/Types/PollEvent.cs | 14 + .../Sockets/Bsd/Types/PollEventData.cs | 11 + .../Sockets/Bsd/Types/PollEventTypeMask.cs | 15 + .../HOS/Services/Sockets/Bsd/Types/TimeVal.cs | 8 + .../Services/Sockets/Ethc/IEthInterface.cs | 8 + .../Sockets/Ethc/IEthInterfaceGroup.cs | 8 + .../HOS/Services/Sockets/Nsd/IManager.cs | 404 + .../Sockets/Nsd/Manager/FqdnResolver.cs | 99 + .../HOS/Services/Sockets/Nsd/ResultCode.cs | 19 + .../Types/ApplicationServerEnvironmentType.cs | 11 + .../Services/Sockets/Nsd/Types/NsdSettings.cs | 9 + .../Services/Sockets/Sfdnsres/IResolver.cs | 712 ++ .../Sockets/Sfdnsres/Proxy/DnsBlacklist.cs | 44 + .../Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs | 106 + .../Sockets/Sfdnsres/Types/AddrInfo4.cs | 51 + .../Sfdnsres/Types/AddrInfoSerialized.cs | 143 + .../Types/AddrInfoSerializedHeader.cs | 57 + .../Sockets/Sfdnsres/Types/GaiError.cs | 22 + .../Sockets/Sfdnsres/Types/NetDBError.cs | 13 + .../Sfdnsres/Types/SfdnsresContants.cs | 7 + .../HOS/Services/Spl/IGeneralInterface.cs | 128 + .../HOS/Services/Spl/IRandomInterface.cs | 36 + .../HOS/Services/Spl/ResultCode.cs | 12 + .../HOS/Services/Spl/Types/ConfigItem.cs | 24 + .../HOS/Services/Spl/Types/DramId.cs | 35 + .../HOS/Services/Spl/Types/HardwareState.cs | 8 + .../HOS/Services/Spl/Types/HardwareType.cs | 12 + .../HOS/Services/Spl/Types/SmcResult.cs | 19 + .../Services/Ssl/BuiltInCertificateManager.cs | 243 + .../HOS/Services/Ssl/ISslService.cs | 127 + .../HOS/Services/Ssl/ResultCode.cs | 20 + .../Services/Ssl/SslService/ISslConnection.cs | 516 + .../Ssl/SslService/ISslConnectionBase.cs | 24 + .../Services/Ssl/SslService/ISslContext.cs | 87 + .../SslService/SslManagedSocketConnection.cs | 276 + .../Ssl/Types/BuiltInCertificateInfo.cs | 10 + .../HOS/Services/Ssl/Types/CaCertificateId.cs | 68 + .../Services/Ssl/Types/CertificateFormat.cs | 8 + .../HOS/Services/Ssl/Types/IoMode.cs | 8 + .../HOS/Services/Ssl/Types/OptionType.cs | 10 + .../Services/Ssl/Types/SessionCacheMode.cs | 9 + .../HOS/Services/Ssl/Types/SslVersion.cs | 16 + .../Services/Ssl/Types/TrustedCertStatus.cs | 12 + .../HOS/Services/Ssl/Types/VerifyOption.cs | 15 + .../SurfaceFlinger/BufferItemConsumer.cs | 95 + .../Services/SurfaceFlinger/BufferQueue.cs | 15 + .../SurfaceFlinger/BufferQueueConsumer.cs | 420 + .../SurfaceFlinger/BufferQueueCore.cs | 341 + .../SurfaceFlinger/BufferQueueProducer.cs | 879 ++ .../HOS/Services/SurfaceFlinger/BufferSlot.cs | 29 + .../SurfaceFlinger/BufferSlotArray.cs | 28 + .../Services/SurfaceFlinger/ConsumerBase.cs | 175 + .../SurfaceFlinger/HOSBinderDriverServer.cs | 109 + .../HOS/Services/SurfaceFlinger/IBinder.cs | 41 + .../SurfaceFlinger/IConsumerListener.cs | 9 + .../Services/SurfaceFlinger/IFlattenable.cs | 13 + .../SurfaceFlinger/IGraphicBufferProducer.cs | 304 + .../SurfaceFlinger/IHOSBinderDriver.cs | 108 + .../SurfaceFlinger/IProducerListener.cs | 7 + .../HOS/Services/SurfaceFlinger/LayerState.cs | 10 + .../SurfaceFlinger/NativeWindowApi.cs | 11 + .../SurfaceFlinger/NativeWindowAttribute.cs | 13 + .../SurfaceFlinger/NativeWindowScalingMode.cs | 11 + .../SurfaceFlinger/NativeWindowTransform.cs | 18 + .../HOS/Services/SurfaceFlinger/Parcel.cs | 238 + .../Services/SurfaceFlinger/ParcelHeader.cs | 10 + .../Services/SurfaceFlinger/PixelFormat.cs | 14 + .../HOS/Services/SurfaceFlinger/Status.cs | 25 + .../Services/SurfaceFlinger/SurfaceFlinger.cs | 539 ++ .../SurfaceFlinger/Types/AndroidFence.cs | 104 + .../Types/AndroidStrongPointer.cs | 38 + .../SurfaceFlinger/Types/BufferInfo.cs | 14 + .../SurfaceFlinger/Types/BufferItem.cs | 63 + .../SurfaceFlinger/Types/BufferState.cs | 10 + .../Types/Color/ColorBytePerPixel.cs | 17 + .../Types/Color/ColorComponent.cs | 44 + .../Types/Color/ColorDataType.cs | 9 + .../SurfaceFlinger/Types/Color/ColorFormat.cs | 240 + .../SurfaceFlinger/Types/Color/ColorShift.cs | 10 + .../SurfaceFlinger/Types/Color/ColorSpace.cs | 33 + .../Types/Color/ColorSwizzle.cs | 33 + .../SurfaceFlinger/Types/GraphicBuffer.cs | 74 + .../Types/GraphicBufferHeader.cs | 21 + .../SurfaceFlinger/Types/NvGraphicBuffer.cs | 41 + .../Types/NvGraphicBufferSurface.cs | 44 + .../Types/NvGraphicBufferSurfaceArray.cs | 34 + .../HOS/Services/SurfaceFlinger/Types/Rect.cs | 71 + ...phemeralNetworkSystemClockContextWriter.cs | 10 + .../Clock/EphemeralNetworkSystemClockCore.cs | 7 + .../Clock/LocalSystemClockContextWriter.cs | 19 + .../Clock/NetworkSystemClockContextWriter.cs | 19 + .../Clock/StandardLocalSystemClockCore.cs | 7 + .../Clock/StandardNetworkSystemClockCore.cs | 36 + .../Time/Clock/StandardSteadyClockCore.cs | 72 + .../Time/Clock/StandardUserSystemClockCore.cs | 108 + .../Services/Time/Clock/SteadyClockCore.cs | 98 + .../Clock/SystemClockContextUpdateCallback.cs | 71 + .../Services/Time/Clock/SystemClockCore.cs | 141 + .../Time/Clock/TickBasedSteadyClockCore.cs | 24 + .../Time/Clock/Types/ClockSnapshot.cs | 50 + .../Types/ContinuousAdjustmentTimePoint.cs | 13 + .../Time/Clock/Types/SteadyClockTimePoint.cs | 43 + .../Time/Clock/Types/SystemClockContext.cs | 11 + .../Services/Time/Clock/Types/TimeSpanType.cs | 50 + .../HOS/Services/Time/IAlarmService.cs | 8 + .../Time/IPowerStateRequestHandler.cs | 8 + .../Services/Time/IStaticServiceForGlue.cs | 186 + .../HOS/Services/Time/IStaticServiceForPsc.cs | 432 + .../HOS/Services/Time/ITimeServiceManager.cs | 230 + .../HOS/Services/Time/ResultCode.cs | 27 + .../Time/StaticService/ISteadyClock.cs | 155 + .../Time/StaticService/ISystemClock.cs | 131 + .../StaticService/ITimeZoneServiceForGlue.cs | 141 + .../StaticService/ITimeZoneServiceForPsc.cs | 302 + .../HOS/Services/Time/TimeManager.cs | 179 + .../HOS/Services/Time/TimeSharedMemory.cs | 137 + .../HOS/Services/Time/TimeZone/TimeZone.cs | 1704 ++++ .../Time/TimeZone/TimeZoneContentManager.cs | 302 + .../Services/Time/TimeZone/TimeZoneManager.cs | 260 + .../TimeZone/Types/CalendarAdditionalInfo.cs | 21 + .../Time/TimeZone/Types/CalendarInfo.cs | 11 + .../Time/TimeZone/Types/CalendarTime.cs | 15 + .../Time/TimeZone/Types/TimeTypeInfo.cs | 28 + .../Time/TimeZone/Types/TimeZoneRule.cs | 56 + .../Time/TimeZone/Types/TzifHeader.cs | 19 + .../Services/Time/Types/SteadyClockContext.cs | 12 + .../Services/Time/Types/TimePermissions.cs | 22 + .../Services/Vi/IApplicationRootService.cs | 27 + .../HOS/Services/Vi/IManagerRootService.cs | 28 + .../HOS/Services/Vi/ISystemRootService.cs | 28 + src/Ryujinx.HLE/HOS/Services/Vi/ResultCode.cs | 17 + .../AndroidSurfaceComposerClient.cs | 19 + .../IManagerDisplayService.cs | 84 + .../ISystemDisplayService.cs | 61 + .../Types/DestinationScalingMode.cs | 11 + .../Types/DisplayInfo.cs | 16 + .../Types/SourceScalingMode.cs | 11 + .../RootService/IApplicationDisplayService.cs | 492 + .../HOS/Services/Vi/Types/ViServiceType.cs | 9 + .../HOS/SystemState/AppletStateMgr.cs | 42 + src/Ryujinx.HLE/HOS/SystemState/ColorSet.cs | 8 + .../HOS/SystemState/KeyboardLayout.cs | 25 + src/Ryujinx.HLE/HOS/SystemState/RegionCode.cs | 17 + .../HOS/SystemState/SystemLanguage.cs | 24 + .../HOS/SystemState/SystemStateMgr.cs | 89 + .../HOS/SystemState/TitleLanguage.cs | 22 + .../HOS/Tamper/AtmosphereCompiler.cs | 151 + .../HOS/Tamper/AtmosphereProgram.cs | 33 + .../HOS/Tamper/CodeEmitters/Arithmetic.cs | 127 + .../CodeEmitters/BeginConditionalBlock.cs | 14 + .../HOS/Tamper/CodeEmitters/DebugLog.cs | 87 + .../CodeEmitters/EndConditionalBlock.cs | 91 + .../CodeEmitters/KeyPressConditional.cs | 26 + .../Tamper/CodeEmitters/LegacyArithmetic.cs | 67 + .../CodeEmitters/LoadRegisterWithConstant.cs | 28 + .../CodeEmitters/LoadRegisterWithMemory.cs | 58 + .../Tamper/CodeEmitters/MemoryConditional.cs | 45 + .../HOS/Tamper/CodeEmitters/PauseProcess.cs | 17 + .../CodeEmitters/ReadOrWriteStaticRegister.cs | 47 + .../CodeEmitters/RegisterConditional.cs | 106 + .../HOS/Tamper/CodeEmitters/ResumeProcess.cs | 16 + .../CodeEmitters/SaveOrRestoreRegister.cs | 65 + .../SaveOrRestoreRegisterWithMask.cs | 33 + .../HOS/Tamper/CodeEmitters/StartEndLoop.cs | 72 + .../CodeEmitters/StoreConstantToAddress.cs | 41 + .../CodeEmitters/StoreConstantToMemory.cs | 71 + .../CodeEmitters/StoreRegisterToMemory.cs | 99 + src/Ryujinx.HLE/HOS/Tamper/CodeType.cs | 110 + src/Ryujinx.HLE/HOS/Tamper/Comparison.cs | 15 + .../HOS/Tamper/CompilationContext.cs | 75 + .../HOS/Tamper/Conditions/CondEQ.cs | 21 + .../HOS/Tamper/Conditions/CondGE.cs | 21 + .../HOS/Tamper/Conditions/CondGT.cs | 21 + .../HOS/Tamper/Conditions/CondLE.cs | 21 + .../HOS/Tamper/Conditions/CondLT.cs | 21 + .../HOS/Tamper/Conditions/CondNE.cs | 21 + .../HOS/Tamper/Conditions/ICondition.cs | 7 + .../HOS/Tamper/Conditions/InputMask.cs | 19 + src/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs | 13 + .../HOS/Tamper/ITamperedProcess.cs | 16 + .../HOS/Tamper/InstructionHelper.cs | 128 + src/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs | 90 + src/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs | 29 + src/Ryujinx.HLE/HOS/Tamper/OperationBlock.cs | 17 + .../HOS/Tamper/Operations/Block.cs | 27 + .../HOS/Tamper/Operations/ForBlock.cs | 41 + .../HOS/Tamper/Operations/IOperand.cs | 8 + .../HOS/Tamper/Operations/IOperation.cs | 7 + .../HOS/Tamper/Operations/IfBlock.cs | 34 + .../HOS/Tamper/Operations/OpAdd.cs | 21 + .../HOS/Tamper/Operations/OpAnd.cs | 21 + .../HOS/Tamper/Operations/OpLog.cs | 21 + .../HOS/Tamper/Operations/OpLsh.cs | 21 + .../HOS/Tamper/Operations/OpMov.cs | 19 + .../HOS/Tamper/Operations/OpMul.cs | 21 + .../HOS/Tamper/Operations/OpNot.cs | 19 + src/Ryujinx.HLE/HOS/Tamper/Operations/OpOr.cs | 21 + .../HOS/Tamper/Operations/OpProcCtrl.cs | 26 + .../HOS/Tamper/Operations/OpRsh.cs | 21 + .../HOS/Tamper/Operations/OpSub.cs | 21 + .../HOS/Tamper/Operations/OpXor.cs | 21 + src/Ryujinx.HLE/HOS/Tamper/Parameter.cs | 12 + src/Ryujinx.HLE/HOS/Tamper/Pointer.cs | 32 + src/Ryujinx.HLE/HOS/Tamper/Register.cs | 28 + .../HOS/Tamper/TamperedKProcess.cs | 68 + src/Ryujinx.HLE/HOS/Tamper/Value.cs | 24 + src/Ryujinx.HLE/HOS/TamperMachine.cs | 188 + src/Ryujinx.HLE/HOS/UserChannelPersistence.cs | 60 + src/Ryujinx.HLE/Homebrew.npdm | Bin 0 -> 972 bytes src/Ryujinx.HLE/Loaders/Elf/ElfDynamic.cs | 15 + src/Ryujinx.HLE/Loaders/Elf/ElfDynamicTag.cs | 76 + src/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs | 35 + src/Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs | 14 + src/Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs | 14 + .../Loaders/Elf/ElfSymbolBinding.cs | 9 + src/Ryujinx.HLE/Loaders/Elf/ElfSymbolType.cs | 13 + .../Loaders/Elf/ElfSymbolVisibility.cs | 10 + .../Loaders/Executables/IExecutable.cs | 18 + .../Loaders/Executables/KipExecutable.cs | 86 + .../Loaders/Executables/NroExecutable.cs | 38 + .../Loaders/Executables/NsoExecutable.cs | 123 + src/Ryujinx.HLE/Loaders/Mods/IPSPatcher.cs | 116 + .../Loaders/Mods/IPSwitchPatcher.cs | 281 + src/Ryujinx.HLE/Loaders/Mods/MemPatch.cs | 96 + src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs | 59 + src/Ryujinx.HLE/Loaders/Npdm/ACID.cs | 66 + .../Loaders/Npdm/FsAccessControl.cs | 32 + .../Loaders/Npdm/FsAccessHeader.cs | 44 + .../Loaders/Npdm/KernelAccessControl.cs | 27 + src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs | 81 + .../Loaders/Npdm/ServiceAccessControl.cs | 47 + .../Extensions/FileSystemExtensions.cs | 132 + .../Extensions/LocalFileSystemExtensions.cs | 36 + .../Extensions/MetaLoaderExtensions.cs | 61 + .../Processes/Extensions/NcaExtensions.cs | 258 + .../PartitionFileSystemExtensions.cs | 162 + .../Loaders/Processes/ProcessConst.cs | 33 + .../Loaders/Processes/ProcessLoader.cs | 246 + .../Loaders/Processes/ProcessLoaderHelper.cs | 503 + .../Loaders/Processes/ProcessResult.cs | 95 + src/Ryujinx.HLE/MemoryConfiguration.cs | 64 + src/Ryujinx.HLE/PerformanceStatistics.cs | 167 + src/Ryujinx.HLE/Ryujinx.HLE.csproj | 52 + src/Ryujinx.HLE/Switch.cs | 160 + .../UI/DynamicTextChangedHandler.cs | 4 + .../UI/IDynamicTextInputHandler.cs | 16 + src/Ryujinx.HLE/UI/IHostUIHandler.cs | 51 + src/Ryujinx.HLE/UI/IHostUITheme.cs | 13 + src/Ryujinx.HLE/UI/Input/NpadButtonHandler.cs | 6 + src/Ryujinx.HLE/UI/Input/NpadReader.cs | 140 + src/Ryujinx.HLE/UI/KeyPressedHandler.cs | 6 + src/Ryujinx.HLE/UI/KeyReleasedHandler.cs | 6 + src/Ryujinx.HLE/UI/RenderingSurfaceInfo.cs | 45 + src/Ryujinx.HLE/UI/ThemeColor.cs | 18 + .../Utilities/PartitionFileSystemUtils.cs | 45 + src/Ryujinx.HLE/Utilities/StringUtils.cs | 156 + .../HeadlessDynamicTextInputHandler.cs | 51 + .../HeadlessHostUiTheme.cs | 15 + .../OpenGL/OpenGLWindow.cs | 202 + src/Ryujinx.Headless.SDL2/Options.cs | 233 + src/Ryujinx.Headless.SDL2/Program.cs | 741 ++ .../Ryujinx.Headless.SDL2.csproj | 72 + src/Ryujinx.Headless.SDL2/Ryujinx.bmp | Bin 0 -> 9354 bytes src/Ryujinx.Headless.SDL2/SDL2Mouse.cs | 90 + src/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs | 178 + .../StatusUpdatedEventArgs.cs | 24 + .../Vulkan/VulkanWindow.cs | 112 + src/Ryujinx.Headless.SDL2/WindowBase.cs | 554 ++ src/Ryujinx.Horizon.Common/IExternalEvent.cs | 10 + src/Ryujinx.Horizon.Common/ISyscallApi.cs | 38 + src/Ryujinx.Horizon.Common/IThreadContext.cs | 11 + .../InvalidResultException.cs | 23 + src/Ryujinx.Horizon.Common/KernelResult.cs | 39 + src/Ryujinx.Horizon.Common/OnScopeExit.cs | 19 + src/Ryujinx.Horizon.Common/Result.cs | 123 + src/Ryujinx.Horizon.Common/ResultNames.cs | 1701 ++++ .../Ryujinx.Horizon.Common.csproj | 11 + .../ThreadTerminatedException.cs | 19 + .../CodeGenerator.cs | 63 + .../Hipc/CommandArgType.cs | 18 + .../Hipc/CommandInterface.cs | 17 + .../Hipc/HipcGenerator.cs | 798 ++ .../Hipc/HipcSyntaxReceiver.cs | 58 + .../Ryujinx.Horizon.Generators.csproj | 16 + .../CodeGenerator.cs | 58 + .../Ryujinx.Horizon.Kernel.Generators.csproj | 16 + .../SyscallGenerator.cs | 520 + .../SyscallSyntaxReceiver.cs | 53 + src/Ryujinx.Horizon/Arp/ArpIpcServer.cs | 62 + src/Ryujinx.Horizon/Arp/ArpMain.cs | 20 + src/Ryujinx.Horizon/Arp/Ipc/Reader.cs | 135 + src/Ryujinx.Horizon/Arp/Ipc/Registrar.cs | 52 + .../Arp/Ipc/UnregistrationNotifier.cs | 25 + src/Ryujinx.Horizon/Arp/Ipc/Updater.cs | 74 + src/Ryujinx.Horizon/Arp/Ipc/Writer.cs | 75 + src/Ryujinx.Horizon/Audio/AudioMain.cs | 17 + src/Ryujinx.Horizon/Audio/AudioManagers.cs | 78 + .../Audio/AudioUserIpcServer.cs | 55 + src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs | 46 + src/Ryujinx.Horizon/Audio/HwopusMain.cs | 17 + src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs | 50 + src/Ryujinx.Horizon/Bcat/BcatMain.cs | 17 + src/Ryujinx.Horizon/Bcat/BcatResult.cs | 29 + src/Ryujinx.Horizon/Bcat/BcatServerManager.cs | 28 + .../Bcat/Ipc/ServiceCreator.cs | 85 + .../Bcat/Ipc/ServiceCreator/BcatService.cs | 20 + .../DeliveryCacheDirectoryService.cs | 48 + .../DeliveryCacheFileService.cs | 54 + .../DeliveryCacheProgressService.cs | 58 + .../DeliveryCacheStorageService.cs | 74 + .../Types/DeliveryCacheProgressImpl.cs | 18 + .../Bcat/Types/BcatPortIndex.cs | 10 + .../Bcat/Types/BcatServicePermissionLevel.cs | 10 + .../Friends/FriendsIpcServer.cs | 50 + src/Ryujinx.Horizon/Friends/FriendsMain.cs | 17 + .../Friends/FriendsPortIndex.cs | 11 + .../Friends/FriendsServerManager.cs | 36 + src/Ryujinx.Horizon/HeapAllocator.cs | 143 + src/Ryujinx.Horizon/HorizonOptions.cs | 37 + src/Ryujinx.Horizon/HorizonStatic.cs | 44 + src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs | 48 + src/Ryujinx.Horizon/Hshl/HshlMain.cs | 17 + src/Ryujinx.Horizon/Hshl/Ipc/Manager.cs | 8 + src/Ryujinx.Horizon/Hshl/Ipc/SetterManager.cs | 8 + src/Ryujinx.Horizon/IService.cs | 7 + src/Ryujinx.Horizon/Ins/InsIpcServer.cs | 48 + src/Ryujinx.Horizon/Ins/InsMain.cs | 17 + .../Ins/Ipc/ReceiverManager.cs | 8 + src/Ryujinx.Horizon/Ins/Ipc/SenderManager.cs | 8 + src/Ryujinx.Horizon/Lbl/Ipc/LblController.cs | 130 + src/Ryujinx.Horizon/Lbl/LblIpcServer.cs | 44 + src/Ryujinx.Horizon/Lbl/LblMain.cs | 17 + src/Ryujinx.Horizon/LibHacResultExtensions.cs | 12 + .../LogManager/Ipc/LmLogger.cs | 171 + .../LogManager/Ipc/LogService.cs | 20 + src/Ryujinx.Horizon/LogManager/LmIpcServer.cs | 44 + src/Ryujinx.Horizon/LogManager/LmMain.cs | 17 + .../LogManager/Types/LogPacket.cs | 72 + src/Ryujinx.Horizon/MmNv/Ipc/Request.cs | 160 + src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs | 44 + src/Ryujinx.Horizon/MmNv/MmNvMain.cs | 17 + src/Ryujinx.Horizon/Ngc/Ipc/Service.cs | 68 + src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs | 51 + src/Ryujinx.Horizon/Ngc/NgcMain.cs | 21 + .../Ovln/Ipc/ReceiverService.cs | 8 + src/Ryujinx.Horizon/Ovln/Ipc/SenderService.cs | 8 + src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs | 49 + src/Ryujinx.Horizon/Ovln/OvlnMain.cs | 17 + src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs | 239 + src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs | 57 + src/Ryujinx.Horizon/Prepo/PrepoMain.cs | 17 + src/Ryujinx.Horizon/Prepo/PrepoResult.cs | 17 + .../Prepo/PrepoServerManager.cs | 36 + .../Prepo/Types/PrepoPortIndex.cs | 12 + .../Types/PrepoServicePermissionLevel.cs | 11 + src/Ryujinx.Horizon/Psc/Ipc/PmControl.cs | 8 + src/Ryujinx.Horizon/Psc/Ipc/PmService.cs | 8 + src/Ryujinx.Horizon/Psc/Ipc/PmStateLock.cs | 8 + src/Ryujinx.Horizon/Psc/PscIpcServer.cs | 51 + src/Ryujinx.Horizon/Psc/PscMain.cs | 17 + .../Ptm/Ipc/MeasurementServer.cs | 63 + src/Ryujinx.Horizon/Ptm/Ipc/Session.cs | 47 + src/Ryujinx.Horizon/Ptm/TsIpcServer.cs | 44 + src/Ryujinx.Horizon/Ptm/TsMain.cs | 17 + src/Ryujinx.Horizon/Ryujinx.Horizon.csproj | 19 + .../Sdk/Account/IEmulatorAccountManager.cs | 8 + .../Sdk/Account/NetworkServiceAccountId.cs | 20 + src/Ryujinx.Horizon/Sdk/Account/Nickname.cs | 29 + src/Ryujinx.Horizon/Sdk/Account/Uid.cs | 62 + src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs | 71 + .../Sdk/Applet/AppletResourceUserId.cs | 15 + .../Sdk/Arp/ApplicationCertificate.cs | 9 + .../Sdk/Arp/ApplicationKind.cs | 8 + .../Sdk/Arp/ApplicationLaunchProperty.cs | 14 + .../Sdk/Arp/ApplicationProcessProperty.cs | 10 + src/Ryujinx.Horizon/Sdk/Arp/ArpApi.cs | 130 + src/Ryujinx.Horizon/Sdk/Arp/ArpResult.cs | 17 + .../Sdk/Arp/Detail/ApplicationInstance.cs | 13 + .../Arp/Detail/ApplicationInstanceManager.cs | 31 + src/Ryujinx.Horizon/Sdk/Arp/IReader.cs | 18 + src/Ryujinx.Horizon/Sdk/Arp/IRegistrar.cs | 12 + .../Sdk/Arp/IUnregistrationNotifier.cs | 9 + src/Ryujinx.Horizon/Sdk/Arp/IUpdater.cs | 12 + src/Ryujinx.Horizon/Sdk/Arp/IWriter.cs | 12 + src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs | 50 + src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs | 12 + .../Sdk/Audio/Detail/AudioDevice.cs | 252 + .../Sdk/Audio/Detail/AudioIn.cs | 171 + .../Sdk/Audio/Detail/AudioInManager.cs | 130 + .../Sdk/Audio/Detail/AudioInProtocol.cs | 23 + .../Sdk/Audio/Detail/AudioInProtocolName.cs | 8 + .../Sdk/Audio/Detail/AudioOut.cs | 154 + .../Sdk/Audio/Detail/AudioOutManager.cs | 93 + .../Sdk/Audio/Detail/AudioRenderer.cs | 178 + .../Sdk/Audio/Detail/AudioRendererManager.cs | 132 + .../Detail/AudioRendererParameterInternal.cs | 14 + .../Sdk/Audio/Detail/AudioSnoopManager.cs | 30 + .../Sdk/Audio/Detail/DeviceName.cs | 30 + .../Sdk/Audio/Detail/FinalOutputRecorder.cs | 147 + .../Detail/FinalOutputRecorderManager.cs | 23 + .../Detail/FinalOutputRecorderParameter.cs | 17 + .../FinalOutputRecorderParameterInternal.cs | 21 + .../Sdk/Audio/Detail/IAudioDevice.cs | 24 + .../Sdk/Audio/Detail/IAudioIn.cs | 26 + .../Sdk/Audio/Detail/IAudioInManager.cs | 43 + .../Sdk/Audio/Detail/IAudioOut.cs | 25 + .../Sdk/Audio/Detail/IAudioOutManager.cs | 32 + .../Sdk/Audio/Detail/IAudioRenderer.cs | 25 + .../Sdk/Audio/Detail/IAudioRendererManager.cs | 29 + .../Sdk/Audio/Detail/IAudioSnoopManager.cs | 12 + .../Sdk/Audio/Detail/IFinalOutputRecorder.cs | 22 + .../Detail/IFinalOutputRecorderManager.cs | 16 + src/Ryujinx.Horizon/Sdk/Bcat/IBcatService.cs | 10 + .../Bcat/IDeliveryCacheDirectoryService.cs | 14 + .../Sdk/Bcat/IDeliveryCacheFileService.cs | 15 + .../Sdk/Bcat/IDeliveryCacheProgressService.cs | 12 + .../Sdk/Bcat/IDeliveryCacheStorageService.cs | 14 + .../Sdk/Bcat/IServiceCreator.cs | 13 + src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs | 16 + .../Sdk/Codec/Detail/HardwareOpusDecoder.cs | 375 + .../Detail/HardwareOpusDecoderManager.cs | 386 + .../HardwareOpusDecoderParameterInternal.cs | 11 + .../HardwareOpusDecoderParameterInternalEx.cs | 13 + ...OpusMultiStreamDecoderParameterInternal.cs | 15 + ...usMultiStreamDecoderParameterInternalEx.cs | 17 + .../Sdk/Codec/Detail/IHardwareOpusDecoder.cs | 20 + .../Detail/IHardwareOpusDecoderManager.cs | 19 + .../Sdk/Codec/Detail/OpusDecoderFlags.cs | 11 + src/Ryujinx.Horizon/Sdk/DebugUtil.cs | 12 + src/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs | 11 + .../Sdk/Friends/ApplicationInfo.cs | 12 + .../Sdk/Friends/Detail/BlockedUserImpl.cs | 8 + .../Sdk/Friends/Detail/FriendCandidateImpl.cs | 8 + .../Friends/Detail/FriendDetailedInfoImpl.cs | 9 + .../Sdk/Friends/Detail/FriendImpl.cs | 19 + .../Detail/FriendInvitationForViewerImpl.cs | 8 + .../Detail/FriendInvitationGroupImpl.cs | 9 + .../Sdk/Friends/Detail/FriendRequestImpl.cs | 8 + .../Sdk/Friends/Detail/FriendSettingImpl.cs | 9 + .../Detail/Ipc/DaemonSuspendSessionService.cs | 7 + .../Sdk/Friends/Detail/Ipc/FriendService.cs | 1015 ++ .../Ipc/FriendsServicePermissionLevel.cs | 16 + .../Ipc/IDaemonSuspendSessionService.cs | 9 + .../Sdk/Friends/Detail/Ipc/IFriendService.cs | 97 + .../Detail/Ipc/INotificationService.cs | 12 + .../Sdk/Friends/Detail/Ipc/IServiceCreator.cs | 13 + .../Detail/Ipc/NotificationEventHandler.cs | 58 + .../Detail/Ipc/NotificationEventType.cs | 9 + .../Friends/Detail/Ipc/NotificationService.cs | 172 + .../Detail/Ipc/PresenceStatusFilter.cs | 10 + .../Sdk/Friends/Detail/Ipc/ServiceCreator.cs | 51 + .../Friends/Detail/Ipc/SizedFriendFilter.cs | 25 + .../Detail/Ipc/SizedNotificationInfo.cs | 13 + .../Detail/NintendoNetworkIdFriendImpl.cs | 8 + .../Sdk/Friends/Detail/PlayHistoryImpl.cs | 8 + .../Sdk/Friends/Detail/PresenceStatus.cs | 9 + .../Sdk/Friends/Detail/ProfileExtraImpl.cs | 9 + .../Sdk/Friends/Detail/ProfileImpl.cs | 8 + .../Friends/Detail/SnsAccountFriendImpl.cs | 8 + .../Sdk/Friends/Detail/UserPresenceImpl.cs | 29 + .../Friends/Detail/UserPresenceViewImpl.cs | 9 + .../Sdk/Friends/Detail/UserSettingImpl.cs | 9 + .../Sdk/Friends/ExternalApplicationCatalog.cs | 9 + .../Friends/ExternalApplicationCatalogId.cs | 9 + .../FacedFriendRequestRegistrationKey.cs | 9 + src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs | 9 + .../FriendInvitationGameModeDescription.cs | 9 + .../Sdk/Friends/FriendInvitationGroupId.cs | 9 + .../Sdk/Friends/FriendInvitationId.cs | 8 + .../Sdk/Friends/FriendResult.cs | 13 + .../Sdk/Friends/InAppScreenName.cs | 26 + .../Sdk/Friends/MiiImageUrlParam.cs | 9 + src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs | 9 + .../Sdk/Friends/NintendoNetworkIdUserInfo.cs | 9 + .../Sdk/Friends/PlayHistoryRegistrationKey.cs | 18 + .../Sdk/Friends/PlayHistoryStatistics.cs | 9 + .../Sdk/Friends/Relationship.cs | 9 + src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs | 9 + .../Sdk/Friends/SnsAccountLinkage.cs | 9 + .../Sdk/Friends/SnsAccountProfile.cs | 9 + src/Ryujinx.Horizon/Sdk/Friends/Url.cs | 30 + src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs | 9 + src/Ryujinx.Horizon/Sdk/Fs/FileHandle.cs | 13 + src/Ryujinx.Horizon/Sdk/Fs/FsResult.cs | 13 + src/Ryujinx.Horizon/Sdk/Fs/IFsClient.cs | 16 + src/Ryujinx.Horizon/Sdk/Fs/OpenMode.cs | 14 + src/Ryujinx.Horizon/Sdk/Hshl/IManager.cs | 8 + .../Sdk/Hshl/ISetterManager.cs | 8 + .../Sdk/Ins/IReceiverManager.cs | 8 + src/Ryujinx.Horizon/Sdk/Ins/ISenderManager.cs | 8 + src/Ryujinx.Horizon/Sdk/Lbl/ILblController.cs | 20 + src/Ryujinx.Horizon/Sdk/Lbl/LblApi.cs | 43 + src/Ryujinx.Horizon/Sdk/Lm/ILmLogger.cs | 12 + src/Ryujinx.Horizon/Sdk/Lm/ILogService.cs | 11 + src/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs | 19 + src/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs | 14 + src/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs | 12 + src/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs | 15 + src/Ryujinx.Horizon/Sdk/MmNv/IRequest.cs | 17 + src/Ryujinx.Horizon/Sdk/MmNv/Module.cs | 15 + src/Ryujinx.Horizon/Sdk/MmNv/Session.cs | 26 + src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs | 52 + src/Ryujinx.Horizon/Sdk/Ncm/StorageId.cs | 13 + .../Sdk/Ngc/Detail/AhoCorasick.cs | 251 + .../Sdk/Ngc/Detail/BinaryReader.cs | 63 + .../Sdk/Ngc/Detail/BitVector32.cs | 78 + src/Ryujinx.Horizon/Sdk/Ngc/Detail/Bp.cs | 54 + src/Ryujinx.Horizon/Sdk/Ngc/Detail/BpNode.cs | 241 + .../Sdk/Ngc/Detail/CompressedArray.cs | 100 + .../Sdk/Ngc/Detail/ContentsReader.cs | 404 + .../Sdk/Ngc/Detail/EmbeddedTries.cs | 266 + .../Sdk/Ngc/Detail/MatchCheckState.cs | 16 + .../Sdk/Ngc/Detail/MatchDelimitedState.cs | 24 + .../Sdk/Ngc/Detail/MatchRangeList.cs | 113 + .../Sdk/Ngc/Detail/MatchRangeListState.cs | 21 + .../Sdk/Ngc/Detail/MatchSimilarFormState.cs | 18 + .../Sdk/Ngc/Detail/MatchState.cs | 49 + .../Sdk/Ngc/Detail/ProfanityFilter.cs | 886 ++ .../Sdk/Ngc/Detail/ProfanityFilterBase.cs | 789 ++ src/Ryujinx.Horizon/Sdk/Ngc/Detail/Sbv.cs | 34 + src/Ryujinx.Horizon/Sdk/Ngc/Detail/SbvRank.cs | 162 + .../Sdk/Ngc/Detail/SbvSelect.cs | 156 + src/Ryujinx.Horizon/Sdk/Ngc/Detail/Set.cs | 73 + .../Sdk/Ngc/Detail/SimilarFormTable.cs | 132 + .../Sdk/Ngc/Detail/SparseSet.cs | 125 + .../Sdk/Ngc/Detail/Utf8ParseResult.cs | 27 + .../Sdk/Ngc/Detail/Utf8Text.cs | 104 + .../Sdk/Ngc/Detail/Utf8Util.cs | 41 + src/Ryujinx.Horizon/Sdk/Ngc/INgcService.cs | 14 + src/Ryujinx.Horizon/Sdk/Ngc/MaskMode.cs | 8 + src/Ryujinx.Horizon/Sdk/Ngc/NgcResult.cs | 16 + .../Sdk/Ngc/ProfanityFilterFlags.cs | 12 + .../Sdk/Ngc/ProfanityFilterOption.cs | 23 + src/Ryujinx.Horizon/Sdk/Ngc/SkipMode.cs | 8 + .../Sdk/Ns/ApplicationControlProperty.cs | 309 + src/Ryujinx.Horizon/Sdk/OsTypes/Event.cs | 61 + .../Sdk/OsTypes/EventClearMode.cs | 8 + src/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs | 15 + .../Sdk/OsTypes/Impl/InterProcessEvent.cs | 89 + .../Sdk/OsTypes/Impl/InterProcessEventImpl.cs | 136 + .../Sdk/OsTypes/Impl/MultiWaitImpl.cs | 245 + .../Sdk/OsTypes/InitializationState.cs | 8 + .../Sdk/OsTypes/InterProcessEventType.cs | 27 + src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs | 46 + .../Sdk/OsTypes/MultiWaitHolder.cs | 16 + .../Sdk/OsTypes/MultiWaitHolderBase.cs | 39 + .../Sdk/OsTypes/MultiWaitHolderOfEvent.cs | 45 + .../Sdk/OsTypes/MultiWaitHolderOfHandle.cs | 14 + src/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs | 130 + .../Sdk/OsTypes/OsMultiWait.cs | 10 + .../Sdk/OsTypes/OsProcessHandle.cs | 33 + src/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs | 11 + .../Sdk/OsTypes/OsSystemEvent.cs | 85 + .../Sdk/OsTypes/OsThreadManager.cs | 10 + .../Sdk/OsTypes/SystemEventType.cs | 17 + src/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs | 9 + .../Sdk/Ovln/IReceiverService.cs | 8 + .../Sdk/Ovln/ISenderService.cs | 8 + .../Sdk/Prepo/IPrepoService.cs | 21 + src/Ryujinx.Horizon/Sdk/Psc/IPmControl.cs | 8 + src/Ryujinx.Horizon/Sdk/Psc/IPmService.cs | 8 + src/Ryujinx.Horizon/Sdk/Psc/IPmStateLock.cs | 8 + src/Ryujinx.Horizon/Sdk/ServiceUtil.cs | 288 + .../Sdk/Settings/BatteryLot.cs | 9 + .../Settings/Factory/AccelerometerOffset.cs | 12 + .../Settings/Factory/AccelerometerScale.cs | 12 + .../Factory/AmiiboEcdsaCertificate.cs | 9 + .../Factory/AmiiboEcqvBlsCertificate.cs | 9 + .../Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs | 9 + .../Factory/AmiiboEcqvBlsRootCertificate.cs | 9 + .../Settings/Factory/AmiiboEcqvCertificate.cs | 9 + .../Sdk/Settings/Factory/AmiiboKey.cs | 9 + .../Factory/AnalogStickFactoryCalibration.cs | 9 + .../Factory/AnalogStickModelParameter.cs | 9 + .../Sdk/Settings/Factory/BdAddress.cs | 9 + .../Sdk/Settings/Factory/ConfigurationId1.cs | 9 + .../ConsoleSixAxisSensorHorizontalOffset.cs | 12 + .../Sdk/Settings/Factory/CountryCode.cs | 8 + .../Factory/EccB233DeviceCertificate.cs | 9 + .../Sdk/Settings/Factory/EccB233DeviceKey.cs | 9 + .../Settings/Factory/GameCardCertificate.cs | 9 + .../Sdk/Settings/Factory/GameCardKey.cs | 9 + .../Sdk/Settings/Factory/GyroscopeOffset.cs | 12 + .../Sdk/Settings/Factory/GyroscopeScale.cs | 12 + .../Sdk/Settings/Factory/MacAddress.cs | 9 + .../Factory/Rsa2048DeviceCertificate.cs | 9 + .../Sdk/Settings/Factory/Rsa2048DeviceKey.cs | 9 + .../Sdk/Settings/Factory/SerialNumber.cs | 9 + .../Sdk/Settings/Factory/SpeakerParameter.cs | 32 + .../Sdk/Settings/Factory/SslCertificate.cs | 9 + .../Sdk/Settings/Factory/SslKey.cs | 9 + src/Ryujinx.Horizon/Sdk/Settings/Language.cs | 24 + .../Sdk/Settings/LanguageCode.cs | 63 + .../Sdk/Settings/SettingsItemKey.cs | 9 + .../Sdk/Settings/SettingsName.cs | 9 + .../System/AccountNotificationSettings.cs | 15 + .../System/AccountOnlineStorageSettings.cs | 6 + .../Sdk/Settings/System/AccountSettings.cs | 9 + .../Sdk/Settings/System/AllowedSslHost.cs | 9 + .../System/AnalogStickUserCalibration.cs | 9 + .../Sdk/Settings/System/AppletLaunchFlag.cs | 9 + .../Sdk/Settings/System/AudioVolume.cs | 9 + .../Sdk/Settings/System/BacklightSettings.cs | 22 + .../Settings/System/BacklightSettingsEx.cs | 9 + .../Sdk/Settings/System/BlePairingSettings.cs | 6 + .../System/BluetoothDevicesSettings.cs | 29 + .../System/ButtonConfigRegisteredSettings.cs | 9 + .../Settings/System/ButtonConfigSettings.cs | 9 + .../ConsoleSixAxisSensorAccelerationBias.cs | 9 + .../ConsoleSixAxisSensorAccelerationGain.cs | 9 + ...ConsoleSixAxisSensorAngularAcceleration.cs | 9 + ...ConsoleSixAxisSensorAngularVelocityBias.cs | 9 + ...ConsoleSixAxisSensorAngularVelocityGain.cs | 9 + ...oleSixAxisSensorAngularVelocityTimeBias.cs | 9 + .../Settings/System/DataDeletionSettings.cs | 18 + .../Sdk/Settings/System/DeviceNickName.cs | 25 + .../Sdk/Settings/System/Edid.cs | 9 + .../Sdk/Settings/System/EulaVersion.cs | 6 + .../Sdk/Settings/System/FatalDirtyFlag.cs | 9 + .../Sdk/Settings/System/FirmwareVersion.cs | 9 + .../Settings/System/FirmwareVersionDigest.cs | 9 + .../Sdk/Settings/System/HomeMenuScheme.cs | 14 + .../Sdk/Settings/System/HostFsMountPoint.cs | 9 + .../Settings/System/InitialLaunchSettings.cs | 14 + .../Sdk/Settings/System/NetworkSettings.cs | 6 + .../Settings/System/NotificationSettings.cs | 38 + .../System/NxControllerLegacySettings.cs | 9 + .../Settings/System/NxControllerSettings.cs | 9 + .../Settings/System/PtmFuelGaugeParameter.cs | 20 + .../System/RebootlessSystemUpdateVersion.cs | 9 + .../Sdk/Settings/System/SerialNumber.cs | 9 + .../System/ServiceDiscoveryControlSettings.cs | 10 + .../Sdk/Settings/System/SleepSettings.cs | 40 + .../Sdk/Settings/System/TelemetryDirtyFlag.cs | 9 + .../Sdk/Settings/System/ThemeId.cs | 9 + .../Sdk/Settings/System/ThemeSettings.cs | 9 + .../Sdk/Settings/System/TvSettings.cs | 59 + .../Sdk/Sf/Cmif/CmifDomainInHeader.cs | 12 + .../Sdk/Sf/Cmif/CmifDomainOutHeader.cs | 12 + .../Sdk/Sf/Cmif/CmifDomainRequestType.cs | 9 + .../Sdk/Sf/Cmif/CmifInHeader.cs | 10 + .../Sdk/Sf/Cmif/CmifMessage.cs | 135 + .../Sdk/Sf/Cmif/CmifOutHeader.cs | 14 + .../Sdk/Sf/Cmif/CmifRequest.cs | 21 + .../Sdk/Sf/Cmif/CmifRequestFormat.cs | 24 + .../Sdk/Sf/Cmif/CmifResponse.cs | 12 + .../Sdk/Sf/Cmif/CommandType.cs | 14 + .../Sdk/Sf/Cmif/DomainServiceObject.cs | 7 + .../Cmif/DomainServiceObjectDispatchTable.cs | 75 + .../Sf/Cmif/DomainServiceObjectProcessor.cs | 139 + .../Sdk/Sf/Cmif/HandlesToClose.cs | 69 + .../Sdk/Sf/Cmif/InlineContext.cs | 11 + .../Sdk/Sf/Cmif/PointerAndSize.cs | 17 + .../Sdk/Sf/Cmif/ScopedInlineContextChange.cs | 19 + .../Sdk/Sf/Cmif/ServerDomainBase.cs | 15 + .../Sdk/Sf/Cmif/ServerDomainManager.cs | 252 + .../Sdk/Sf/Cmif/ServerMessageProcessor.cs | 18 + .../Sf/Cmif/ServerMessageRuntimeMetadata.cs | 30 + .../Sdk/Sf/Cmif/ServiceDispatchContext.cs | 18 + .../Sdk/Sf/Cmif/ServiceDispatchMeta.cs | 12 + .../Sdk/Sf/Cmif/ServiceDispatchTable.cs | 33 + .../Sdk/Sf/Cmif/ServiceDispatchTableBase.cs | 94 + .../Sdk/Sf/Cmif/ServiceObjectHolder.cs | 34 + .../Sdk/Sf/CmifCommandAttribute.cs | 15 + src/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs | 56 + .../Sdk/Sf/CommandArgAttributes.cs | 38 + src/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs | 52 + .../Sdk/Sf/CommandSerialization.cs | 75 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs | 81 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs | 65 + .../Sdk/Sf/Hipc/HipcBufferDescriptor.cs | 22 + .../Sdk/Sf/Hipc/HipcBufferFlags.cs | 17 + .../Sdk/Sf/Hipc/HipcBufferMode.cs | 10 + .../Sdk/Sf/Hipc/HipcManager.cs | 115 + .../Sdk/Sf/Hipc/HipcMessage.cs | 221 + .../Sdk/Sf/Hipc/HipcMessageData.cs | 17 + .../Sdk/Sf/Hipc/HipcMetadata.cs | 16 + .../Sdk/Sf/Hipc/HipcReceiveListEntry.cs | 16 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs | 21 + .../Sdk/Sf/Hipc/HipcStaticDescriptor.cs | 22 + .../Sdk/Sf/Hipc/ManagerOptions.cs | 20 + .../Sdk/Sf/Hipc/ReceiveResult.cs | 9 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs | 36 + .../Sdk/Sf/Hipc/ServerDomainSessionManager.cs | 23 + .../Sdk/Sf/Hipc/ServerManager.cs | 197 + .../Sdk/Sf/Hipc/ServerManagerBase.cs | 329 + .../Sdk/Sf/Hipc/ServerSession.cs | 23 + .../Sdk/Sf/Hipc/ServerSessionManager.cs | 331 + .../Sdk/Sf/Hipc/SpecialHeader.cs | 27 + .../Sdk/Sf/HipcCommandProcessor.cs | 430 + src/Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs | 9 + .../Sdk/Sf/RawDataOffsetCalculator.cs | 49 + src/Ryujinx.Horizon/Sdk/Sf/SfResult.cs | 29 + src/Ryujinx.Horizon/Sdk/Sm/IManagerService.cs | 8 + src/Ryujinx.Horizon/Sdk/Sm/IUserService.cs | 13 + src/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs | 99 + src/Ryujinx.Horizon/Sdk/Sm/SmApi.cs | 125 + .../Sdk/Srepo/ISrepoService.cs | 8 + src/Ryujinx.Horizon/Sdk/Ts/DeviceCode.cs | 8 + .../Sdk/Ts/IMeasurementServer.cs | 14 + src/Ryujinx.Horizon/Sdk/Ts/ISession.cs | 12 + src/Ryujinx.Horizon/Sdk/Ts/Location.cs | 8 + .../Sdk/Usb/IClientRootSession.cs | 8 + src/Ryujinx.Horizon/Sdk/Usb/IDsRootSession.cs | 8 + .../Sdk/Usb/IPdCradleManager.cs | 8 + src/Ryujinx.Horizon/Sdk/Usb/IPdManager.cs | 8 + .../Sdk/Usb/IPdManufactureManager.cs | 8 + .../Sdk/Usb/IPmObserverService.cs | 8 + src/Ryujinx.Horizon/Sdk/Usb/IPmService.cs | 8 + src/Ryujinx.Horizon/Sdk/Usb/IQdbManager.cs | 8 + .../Sdk/Wlan/IDetectManager.cs | 8 + .../Sdk/Wlan/IGeneralServiceCreator.cs | 8 + src/Ryujinx.Horizon/Sdk/Wlan/IInfraManager.cs | 8 + .../Sdk/Wlan/ILocalGetActionFrame.cs | 8 + .../Sdk/Wlan/ILocalGetFrame.cs | 8 + src/Ryujinx.Horizon/Sdk/Wlan/ILocalManager.cs | 8 + .../Sdk/Wlan/IPrivateServiceCreator.cs | 8 + .../Sdk/Wlan/ISfDriverServiceCreator.cs | 8 + .../Sdk/Wlan/ISocketGetFrame.cs | 8 + .../Sdk/Wlan/ISocketManager.cs | 8 + src/Ryujinx.Horizon/ServiceEntry.cs | 27 + src/Ryujinx.Horizon/ServiceTable.cs | 93 + src/Ryujinx.Horizon/Sm/Impl/ServiceInfo.cs | 20 + src/Ryujinx.Horizon/Sm/Impl/ServiceManager.cs | 185 + src/Ryujinx.Horizon/Sm/Ipc/ManagerService.cs | 8 + src/Ryujinx.Horizon/Sm/Ipc/UserService.cs | 66 + src/Ryujinx.Horizon/Sm/SmMain.cs | 32 + src/Ryujinx.Horizon/Sm/SmResult.cs | 21 + src/Ryujinx.Horizon/Sm/SmServerManager.cs | 30 + src/Ryujinx.Horizon/Sm/Types/SmPortIndex.cs | 8 + src/Ryujinx.Horizon/Srepo/Ipc/SrepoService.cs | 8 + src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs | 47 + src/Ryujinx.Horizon/Srepo/SrepoMain.cs | 17 + .../Usb/Ipc/ClientRootSession.cs | 8 + src/Ryujinx.Horizon/Usb/Ipc/DsRootSession.cs | 8 + .../Usb/Ipc/PdCradleManager.cs | 8 + src/Ryujinx.Horizon/Usb/Ipc/PdManager.cs | 9 + .../Usb/Ipc/PdManufactureManager.cs | 8 + .../Usb/Ipc/PmObserverService.cs | 8 + src/Ryujinx.Horizon/Usb/Ipc/PmService.cs | 8 + src/Ryujinx.Horizon/Usb/Ipc/QdbManager.cs | 8 + src/Ryujinx.Horizon/Usb/UsbIpcServer.cs | 72 + src/Ryujinx.Horizon/Usb/UsbMain.cs | 17 + src/Ryujinx.Horizon/Wlan/Ipc/DetectManager.cs | 8 + .../Wlan/Ipc/GeneralServiceCreator.cs | 8 + src/Ryujinx.Horizon/Wlan/Ipc/InfraManager.cs | 8 + .../Wlan/Ipc/LocalGetActionFrame.cs | 8 + src/Ryujinx.Horizon/Wlan/Ipc/LocalGetFrame.cs | 8 + src/Ryujinx.Horizon/Wlan/Ipc/LocalManager.cs | 8 + .../Wlan/Ipc/PrivateServiceCreator.cs | 8 + .../Wlan/Ipc/SfDriverServiceCreator.cs | 8 + .../Wlan/Ipc/SocketGetFrame.cs | 8 + src/Ryujinx.Horizon/Wlan/Ipc/SocketManager.cs | 8 + src/Ryujinx.Horizon/Wlan/WlanIpcServer.cs | 60 + src/Ryujinx.Horizon/Wlan/WlanMain.cs | 17 + .../Ryujinx.Input.SDL2.csproj | 13 + src/Ryujinx.Input.SDL2/SDL2Gamepad.cs | 390 + src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs | 175 + src/Ryujinx.Input.SDL2/SDL2Keyboard.cs | 411 + src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs | 55 + .../Assigner/GamepadButtonAssigner.cs | 192 + src/Ryujinx.Input/Assigner/IButtonAssigner.cs | 36 + .../Assigner/KeyboardKeyAssigner.cs | 50 + src/Ryujinx.Input/Button.cs | 33 + src/Ryujinx.Input/ButtonType.cs | 9 + src/Ryujinx.Input/GamepadButtonInputId.cs | 57 + src/Ryujinx.Input/GamepadFeaturesFlag.cs | 28 + src/Ryujinx.Input/GamepadStateSnapshot.cs | 70 + src/Ryujinx.Input/HLE/InputManager.cs | 55 + src/Ryujinx.Input/HLE/NpadController.cs | 558 ++ src/Ryujinx.Input/HLE/NpadManager.cs | 357 + src/Ryujinx.Input/HLE/TouchScreenManager.cs | 102 + src/Ryujinx.Input/IGamepad.cs | 122 + src/Ryujinx.Input/IGamepadDriver.cs | 43 + src/Ryujinx.Input/IKeyboard.cs | 41 + src/Ryujinx.Input/IMouse.cs | 106 + src/Ryujinx.Input/Key.cs | 142 + src/Ryujinx.Input/KeyboardStateSnapshot.cs | 29 + src/Ryujinx.Input/Motion/CemuHook/Client.cs | 472 + .../CemuHook/Protocol/ControllerData.cs | 47 + .../CemuHook/Protocol/ControllerInfo.cs | 20 + .../Motion/CemuHook/Protocol/Header.cs | 15 + .../Motion/CemuHook/Protocol/MessageType.cs | 9 + .../CemuHook/Protocol/SharedResponse.cs | 51 + src/Ryujinx.Input/Motion/MotionInput.cs | 65 + .../Motion/MotionSensorFilter.cs | 162 + src/Ryujinx.Input/MotionInputId.cs | 25 + src/Ryujinx.Input/MouseButton.cs | 16 + src/Ryujinx.Input/MouseStateSnapshot.cs | 45 + src/Ryujinx.Input/Ryujinx.Input.csproj | 17 + src/Ryujinx.Input/StickInputId.cs | 14 + src/Ryujinx.Memory/AddressSpaceManager.cs | 260 + .../BytesReadOnlySequenceSegment.cs | 60 + src/Ryujinx.Memory/IRefCounted.cs | 8 + src/Ryujinx.Memory/IVirtualMemoryManager.cs | 230 + src/Ryujinx.Memory/IWritableBlock.cs | 27 + src/Ryujinx.Memory/InvalidAccessHandler.cs | 9 + .../InvalidMemoryRegionException.cs | 19 + src/Ryujinx.Memory/MemoryAllocationFlags.cs | 52 + src/Ryujinx.Memory/MemoryBlock.cs | 442 + src/Ryujinx.Memory/MemoryConstants.cs | 9 + src/Ryujinx.Memory/MemoryManagement.cs | 206 + src/Ryujinx.Memory/MemoryManagementUnix.cs | 211 + src/Ryujinx.Memory/MemoryManagementWindows.cs | 151 + src/Ryujinx.Memory/MemoryManagerUnixHelper.cs | 172 + src/Ryujinx.Memory/MemoryMapFlags.cs | 23 + .../MemoryNotContiguousException.cs | 19 + src/Ryujinx.Memory/MemoryPermission.cs | 51 + .../MemoryProtectionException.cs | 24 + src/Ryujinx.Memory/NativeMemoryManager.cs | 51 + src/Ryujinx.Memory/PageTable.cs | 141 + src/Ryujinx.Memory/Range/HostMemoryRange.cs | 81 + src/Ryujinx.Memory/Range/IMultiRangeItem.cs | 25 + .../Range/INonOverlappingRange.cs | 16 + src/Ryujinx.Memory/Range/IRange.cs | 31 + src/Ryujinx.Memory/Range/MemoryRange.cs | 91 + src/Ryujinx.Memory/Range/MultiRange.cs | 351 + src/Ryujinx.Memory/Range/MultiRangeList.cs | 199 + .../Range/NonOverlappingRangeList.cs | 106 + src/Ryujinx.Memory/Range/RangeList.cs | 483 + src/Ryujinx.Memory/Ryujinx.Memory.csproj | 12 + src/Ryujinx.Memory/Tracking/AbstractRegion.cs | 73 + src/Ryujinx.Memory/Tracking/BitMap.cs | 199 + .../Tracking/ConcurrentBitmap.cs | 152 + .../Tracking/IMultiRegionHandle.cs | 55 + src/Ryujinx.Memory/Tracking/IRegionHandle.cs | 18 + src/Ryujinx.Memory/Tracking/MemoryTracking.cs | 379 + .../Tracking/MultiRegionHandle.cs | 419 + .../Tracking/PreciseRegionSignal.cs | 4 + src/Ryujinx.Memory/Tracking/RegionFlags.cs | 21 + src/Ryujinx.Memory/Tracking/RegionHandle.cs | 523 + src/Ryujinx.Memory/Tracking/RegionSignal.cs | 4 + .../Tracking/SmartMultiRegionHandle.cs | 282 + src/Ryujinx.Memory/Tracking/VirtualRegion.cs | 154 + .../VirtualMemoryManagerBase.cs | 405 + .../WindowsShared/MappingTree.cs | 87 + .../WindowsShared/PlaceholderManager.cs | 736 ++ .../WindowsShared/WindowsApi.cs | 103 + .../WindowsShared/WindowsApiException.cs | 26 + .../WindowsShared/WindowsFlags.cs | 52 + src/Ryujinx.Memory/WritableRegion.cs | 48 + .../Ryujinx.SDL2.Common.csproj | 15 + src/Ryujinx.SDL2.Common/SDL2Driver.cs | 208 + src/Ryujinx.ShaderTools/Program.cs | 159 + .../Ryujinx.ShaderTools.csproj | 17 + .../MockVirtualMemoryManager.cs | 121 + .../MultiRegionTrackingTests.cs | 439 + .../Ryujinx.Tests.Memory.csproj | 18 + src/Ryujinx.Tests.Memory/Tests.cs | 118 + src/Ryujinx.Tests.Memory/TrackingTests.cs | 512 + src/Ryujinx.Tests.Unicorn/IndexedProperty.cs | 28 + src/Ryujinx.Tests.Unicorn/MemoryPermission.cs | 14 + .../Ryujinx.Tests.Unicorn.csproj | 17 + src/Ryujinx.Tests.Unicorn/SimdValue.cs | 110 + src/Ryujinx.Tests.Unicorn/UnicornAArch32.cs | 285 + src/Ryujinx.Tests.Unicorn/UnicornAArch64.cs | 298 + src/Ryujinx.Tests/.runsettings | 8 + .../AudioRendererConfigurationTests.cs | 15 + .../Audio/Renderer/BehaviourParameterTests.cs | 16 + .../Renderer/BiquadFilterParameterTests.cs | 15 + .../Renderer/Common/UpdateDataHeaderTests.cs | 15 + .../Renderer/Common/VoiceUpdateStateTests.cs | 15 + .../Audio/Renderer/Common/WaveBufferTests.cs | 15 + .../Audio/Renderer/Dsp/ResamplerTests.cs | 86 + .../Audio/Renderer/Dsp/UpsamplerTests.cs | 57 + .../Renderer/EffectInfoParameterTests.cs | 16 + .../Audio/Renderer/EffectOutStatusTests.cs | 16 + .../Renderer/MemoryPoolParameterTests.cs | 16 + .../BehaviourErrorInfoOutStatusTests.cs | 15 + .../Parameter/Effect/AuxParameterTests.cs | 15 + .../BiquadFilterEffectParameterTests.cs | 15 + .../Effect/BufferMixerParameterTests.cs | 15 + .../Effect/CompressorParameterTests.cs | 15 + .../Parameter/Effect/DelayParameterTests.cs | 15 + .../Parameter/Effect/LimiterParameterTests.cs | 15 + .../Effect/LimiterStatisticsTests.cs | 15 + .../Effect/Reverb3dParameterTests.cs | 15 + .../Parameter/Effect/ReverbParameterTests.cs | 15 + .../MixInParameterDirtyOnlyUpdateTests.cs | 15 + .../Renderer/Parameter/MixParameterTests.cs | 15 + .../Parameter/PerformanceInParameterTests.cs | 15 + .../Parameter/PerformanceOutStatusTests.cs | 15 + .../Parameter/RendererInfoOutStatusTests.cs | 15 + .../Sink/CircularBufferParameterTests.cs | 15 + .../Parameter/Sink/DeviceParameterTests.cs | 15 + .../Parameter/SinkInParameterTests.cs | 15 + .../Renderer/Parameter/SinkOutStatusTests.cs | 15 + .../Parameter/SplitterInParamHeaderTests.cs | 15 + .../Audio/Renderer/Server/AddressInfoTests.cs | 35 + .../Renderer/Server/BehaviourContextTests.cs | 372 + .../Renderer/Server/MemoryPoolStateTests.cs | 62 + .../Audio/Renderer/Server/MixStateTests.cs | 15 + .../Audio/Renderer/Server/PoolMapperTests.cs | 134 + .../Server/SplitterDestinationTests.cs | 16 + .../Renderer/Server/SplitterStateTests.cs | 15 + .../Server/VoiceChannelResourceTests.cs | 15 + .../Audio/Renderer/Server/VoiceStateTests.cs | 15 + .../Audio/Renderer/Server/WaveBufferTests.cs | 15 + .../VoiceChannelResourceInParameterTests.cs | 15 + .../Audio/Renderer/VoiceInParameterTests.cs | 15 + .../Audio/Renderer/VoiceOutStatusTests.cs | 15 + .../SequenceReaderExtensionsTests.cs | 359 + .../Cpu/Arm64CodeGenCommonTests.cs | 46 + src/Ryujinx.Tests/Cpu/CpuContext.cs | 39 + src/Ryujinx.Tests/Cpu/CpuTest.cs | 604 ++ src/Ryujinx.Tests/Cpu/CpuTest32.cs | 644 ++ src/Ryujinx.Tests/Cpu/CpuTestAlu.cs | 280 + src/Ryujinx.Tests/Cpu/CpuTestAlu32.cs | 264 + src/Ryujinx.Tests/Cpu/CpuTestAluBinary.cs | 307 + src/Ryujinx.Tests/Cpu/CpuTestAluBinary32.cs | 95 + src/Ryujinx.Tests/Cpu/CpuTestAluImm.cs | 433 + src/Ryujinx.Tests/Cpu/CpuTestAluImm32.cs | 55 + src/Ryujinx.Tests/Cpu/CpuTestAluRs.cs | 895 ++ src/Ryujinx.Tests/Cpu/CpuTestAluRs32.cs | 82 + src/Ryujinx.Tests/Cpu/CpuTestAluRx.cs | 723 ++ src/Ryujinx.Tests/Cpu/CpuTestBf32.cs | 106 + src/Ryujinx.Tests/Cpu/CpuTestBfm.cs | 130 + src/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs | 102 + src/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs | 110 + src/Ryujinx.Tests/Cpu/CpuTestCsel.cs | 205 + src/Ryujinx.Tests/Cpu/CpuTestMisc.cs | 485 + src/Ryujinx.Tests/Cpu/CpuTestMisc32.cs | 111 + src/Ryujinx.Tests/Cpu/CpuTestMov.cs | 112 + src/Ryujinx.Tests/Cpu/CpuTestMul.cs | 226 + src/Ryujinx.Tests/Cpu/CpuTestMul32.cs | 137 + src/Ryujinx.Tests/Cpu/CpuTestSimd.cs | 3665 +++++++ src/Ryujinx.Tests/Cpu/CpuTestSimd32.cs | 381 + src/Ryujinx.Tests/Cpu/CpuTestSimdCrypto.cs | 144 + src/Ryujinx.Tests/Cpu/CpuTestSimdCrypto32.cs | 154 + src/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs | 694 ++ src/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs | 555 ++ src/Ryujinx.Tests/Cpu/CpuTestSimdExt.cs | 72 + src/Ryujinx.Tests/Cpu/CpuTestSimdFcond.cs | 239 + src/Ryujinx.Tests/Cpu/CpuTestSimdFmov.cs | 62 + src/Ryujinx.Tests/Cpu/CpuTestSimdImm.cs | 402 + src/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs | 705 ++ src/Ryujinx.Tests/Cpu/CpuTestSimdLogical32.cs | 162 + src/Ryujinx.Tests/Cpu/CpuTestSimdMemory32.cs | 340 + src/Ryujinx.Tests/Cpu/CpuTestSimdMov32.cs | 610 ++ src/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs | 4057 ++++++++ src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs | 984 ++ src/Ryujinx.Tests/Cpu/CpuTestSimdRegElem.cs | 195 + src/Ryujinx.Tests/Cpu/CpuTestSimdRegElem32.cs | 80 + src/Ryujinx.Tests/Cpu/CpuTestSimdRegElemF.cs | 457 + src/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs | 1280 +++ src/Ryujinx.Tests/Cpu/CpuTestSimdShImm32.cs | 362 + src/Ryujinx.Tests/Cpu/CpuTestSimdTbl.cs | 319 + src/Ryujinx.Tests/Cpu/CpuTestSystem.cs | 69 + src/Ryujinx.Tests/Cpu/CpuTestT32Alu.cs | 1014 ++ src/Ryujinx.Tests/Cpu/CpuTestT32Flow.cs | 167 + src/Ryujinx.Tests/Cpu/CpuTestT32Mem.cs | 521 + src/Ryujinx.Tests/Cpu/CpuTestThumb.cs | 884 ++ src/Ryujinx.Tests/Cpu/EnvironmentTests.cs | 94 + .../Cpu/PrecomputedMemoryThumbTestCase.cs | 10 + .../Cpu/PrecomputedThumbTestCase.cs | 9 + .../HLE/SoftwareKeyboardTests.cs | 70 + src/Ryujinx.Tests/Memory/MockMemoryManager.cs | 53 + src/Ryujinx.Tests/Memory/PartialUnmaps.cs | 468 + src/Ryujinx.Tests/Ryujinx.Tests.csproj | 50 + src/Ryujinx.Tests/Time/TimeZoneRuleTests.cs | 18 + src/Ryujinx.Tests/TreeDictionaryTests.cs | 244 + .../App/ApplicationAddedEventArgs.cs | 9 + .../App/ApplicationCountUpdatedEventArgs.cs | 10 + src/Ryujinx.UI.Common/App/ApplicationData.cs | 166 + .../App/ApplicationJsonSerializerContext.cs | 10 + .../App/ApplicationLibrary.cs | 940 ++ .../App/ApplicationMetadata.cs | 51 + .../Configuration/AudioBackend.cs | 14 + .../Configuration/ConfigurationFileFormat.cs | 419 + .../ConfigurationFileFormatSettings.cs | 9 + .../ConfigurationJsonSerializerContext.cs | 10 + .../Configuration/ConfigurationState.cs | 1613 ++++ .../Configuration/FileTypes.cs | 12 + .../Configuration/LoggerModule.cs | 113 + .../Configuration/System/Language.cs | 28 + .../Configuration/System/Region.cs | 17 + .../Configuration/UI/ColumnSort.cs | 8 + .../Configuration/UI/GuiColumns.cs | 16 + .../Configuration/UI/ShownFileTypes.cs | 12 + .../Configuration/UI/WindowStartup.cs | 11 + .../DiscordIntegrationModule.cs | 129 + .../Extensions/FileTypeExtensions.cs | 25 + .../Helper/CommandLineState.cs | 108 + src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs | 50 + .../Helper/FileAssociationHelper.cs | 202 + src/Ryujinx.UI.Common/Helper/LinuxHelper.cs | 62 + src/Ryujinx.UI.Common/Helper/ObjectiveC.cs | 160 + src/Ryujinx.UI.Common/Helper/OpenHelper.cs | 112 + .../Helper/SetupValidator.cs | 114 + .../Helper/ShortcutHelper.cs | 172 + src/Ryujinx.UI.Common/Helper/TitleHelper.cs | 30 + .../Helper/ValueFormatUtils.cs | 219 + .../Models/Amiibo/AmiiboApi.cs | 67 + .../Models/Amiibo/AmiiboApiGamesSwitch.cs | 15 + .../Models/Amiibo/AmiiboApiUsage.cs | 12 + .../Models/Amiibo/AmiiboJson.cs | 14 + .../Amiibo/AmiiboJsonSerializerContext.cs | 9 + .../Github/GithubReleaseAssetJsonResponse.cs | 9 + .../Github/GithubReleasesJsonResponse.cs | 10 + .../GithubReleasesJsonSerializerContext.cs | 9 + .../Resources/Controller_JoyConLeft.svg | 1 + .../Resources/Controller_JoyConPair.svg | 1 + .../Resources/Controller_JoyConRight.svg | 1 + .../Resources/Controller_ProCon.svg | 132 + .../Resources/Icon_Blank.png | Bin 0 -> 16179 bytes src/Ryujinx.UI.Common/Resources/Icon_NCA.png | Bin 0 -> 18432 bytes src/Ryujinx.UI.Common/Resources/Icon_NRO.png | Bin 0 -> 18404 bytes src/Ryujinx.UI.Common/Resources/Icon_NSO.png | Bin 0 -> 18705 bytes src/Ryujinx.UI.Common/Resources/Icon_NSP.png | Bin 0 -> 18260 bytes src/Ryujinx.UI.Common/Resources/Icon_XCI.png | Bin 0 -> 18220 bytes .../Resources/Logo_Amiibo.png | Bin 0 -> 10573 bytes .../Resources/Logo_Discord_Dark.png | Bin 0 -> 9835 bytes .../Resources/Logo_Discord_Light.png | Bin 0 -> 10765 bytes .../Resources/Logo_GitHub_Dark.png | Bin 0 -> 4837 bytes .../Resources/Logo_GitHub_Light.png | Bin 0 -> 5166 bytes .../Resources/Logo_Patreon_Dark.png | Bin 0 -> 52210 bytes .../Resources/Logo_Patreon_Light.png | Bin 0 -> 29395 bytes .../Resources/Logo_Ryujinx.png | Bin 0 -> 52972 bytes .../Resources/Logo_Twitter_Dark.png | Bin 0 -> 18385 bytes .../Resources/Logo_Twitter_Light.png | Bin 0 -> 19901 bytes .../Ryujinx.UI.Common.csproj | 68 + .../SystemInfo/LinuxSystemInfo.cs | 85 + .../SystemInfo/MacOSSystemInfo.cs | 164 + .../SystemInfo/SystemInfo.cs | 79 + .../SystemInfo/WindowsSystemInfo.cs | 87 + src/Ryujinx.UI.Common/UserError.cs | 39 + .../LocaleGenerator.cs | 33 + .../Ryujinx.UI.LocaleGenerator.csproj | 18 + src/Ryujinx/App.axaml | 17 + src/Ryujinx/App.axaml.cs | 145 + src/Ryujinx/AppHost.cs | 1280 +++ src/Ryujinx/Assets/Fonts/SegoeFluentIcons.ttf | Bin 0 -> 408752 bytes .../Assets/Icons/Controller_JoyConLeft.svg | 155 + .../Assets/Icons/Controller_JoyConPair.svg | 341 + .../Assets/Icons/Controller_JoyConRight.svg | 185 + .../Assets/Icons/Controller_ProCon.svg | 84 + src/Ryujinx/Assets/Locales/ar_SA.json | 780 ++ src/Ryujinx/Assets/Locales/de_DE.json | 780 ++ src/Ryujinx/Assets/Locales/el_GR.json | 780 ++ src/Ryujinx/Assets/Locales/en_US.json | 784 ++ src/Ryujinx/Assets/Locales/es_ES.json | 780 ++ src/Ryujinx/Assets/Locales/fr_FR.json | 780 ++ src/Ryujinx/Assets/Locales/he_IL.json | 780 ++ src/Ryujinx/Assets/Locales/it_IT.json | 780 ++ src/Ryujinx/Assets/Locales/ja_JP.json | 780 ++ src/Ryujinx/Assets/Locales/ko_KR.json | 780 ++ src/Ryujinx/Assets/Locales/pl_PL.json | 780 ++ src/Ryujinx/Assets/Locales/pt_BR.json | 780 ++ src/Ryujinx/Assets/Locales/ru_RU.json | 780 ++ src/Ryujinx/Assets/Locales/th_TH.json | 780 ++ src/Ryujinx/Assets/Locales/tr_TR.json | 780 ++ src/Ryujinx/Assets/Locales/uk_UA.json | 780 ++ src/Ryujinx/Assets/Locales/zh_CN.json | 780 ++ src/Ryujinx/Assets/Locales/zh_TW.json | 780 ++ src/Ryujinx/Assets/Styles/Styles.xaml | 395 + src/Ryujinx/Assets/Styles/Themes.xaml | 85 + src/Ryujinx/Common/ApplicationHelper.cs | 424 + src/Ryujinx/Common/ApplicationSort.cs | 15 + src/Ryujinx/Common/KeyboardHotkeyState.cs | 16 + src/Ryujinx/Common/Locale/LocaleExtension.cs | 43 + src/Ryujinx/Common/Locale/LocaleManager.cs | 174 + src/Ryujinx/Common/ThemeManager.cs | 14 + src/Ryujinx/Input/AvaloniaKeyboard.cs | 203 + src/Ryujinx/Input/AvaloniaKeyboardDriver.cs | 107 + .../Input/AvaloniaKeyboardMappingHelper.cs | 185 + src/Ryujinx/Input/AvaloniaMouse.cs | 87 + src/Ryujinx/Input/AvaloniaMouseDriver.cs | 159 + src/Ryujinx/Modules/Updater/Updater.cs | 808 ++ src/Ryujinx/Program.cs | 259 + src/Ryujinx/Ryujinx.csproj | 167 + src/Ryujinx/Ryujinx.ico | Bin 0 -> 108122 bytes src/Ryujinx/UI/Applet/AvaHostUIHandler.cs | 206 + .../Applet/AvaloniaDynamicTextInputHandler.cs | 157 + src/Ryujinx/UI/Applet/AvaloniaHostUITheme.cs | 41 + .../UI/Applet/ControllerAppletDialog.axaml | 145 + .../UI/Applet/ControllerAppletDialog.axaml.cs | 137 + src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml | 54 + .../UI/Applet/ErrorAppletWindow.axaml.cs | 74 + src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml | 67 + .../UI/Applet/SwkbdAppletDialog.axaml.cs | 183 + .../UI/Controls/ApplicationContextMenu.axaml | 95 + .../Controls/ApplicationContextMenu.axaml.cs | 359 + .../UI/Controls/ApplicationGridView.axaml | 102 + .../UI/Controls/ApplicationGridView.axaml.cs | 51 + .../UI/Controls/ApplicationListView.axaml | 160 + .../UI/Controls/ApplicationListView.axaml.cs | 51 + .../UI/Controls/NavigationDialogHost.axaml | 17 + .../UI/Controls/NavigationDialogHost.axaml.cs | 217 + src/Ryujinx/UI/Controls/SliderScroll.axaml.cs | 31 + .../UI/Controls/UpdateWaitWindow.axaml | 42 + .../UI/Controls/UpdateWaitWindow.axaml.cs | 31 + .../UI/Helpers/ApplicationOpenedEventArgs.cs | 16 + .../UI/Helpers/BitmapArrayValueConverter.cs | 36 + src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs | 102 + src/Ryujinx/UI/Helpers/ContentDialogHelper.cs | 425 + src/Ryujinx/UI/Helpers/Glyph.cs | 9 + src/Ryujinx/UI/Helpers/GlyphValueConverter.cs | 42 + src/Ryujinx/UI/Helpers/KeyValueConverter.cs | 184 + .../UI/Helpers/LocalizedNeverConverter.cs | 43 + src/Ryujinx/UI/Helpers/LoggerAdapter.cs | 102 + src/Ryujinx/UI/Helpers/MiniCommand.cs | 71 + src/Ryujinx/UI/Helpers/NotificationHelper.cs | 70 + src/Ryujinx/UI/Helpers/OffscreenTextBox.cs | 42 + src/Ryujinx/UI/Helpers/TimeZoneConverter.cs | 28 + src/Ryujinx/UI/Helpers/UserErrorDialog.cs | 90 + src/Ryujinx/UI/Helpers/UserResult.cs | 12 + src/Ryujinx/UI/Helpers/Win32NativeInterop.cs | 115 + src/Ryujinx/UI/Models/CheatNode.cs | 57 + src/Ryujinx/UI/Models/ControllerModel.cs | 6 + src/Ryujinx/UI/Models/DeviceType.cs | 9 + .../UI/Models/DownloadableContentModel.cs | 39 + .../Models/Generic/LastPlayedSortComparer.cs | 31 + .../Models/Generic/TimePlayedSortComparer.cs | 31 + .../UI/Models/Input/GamepadInputConfig.cs | 580 ++ src/Ryujinx/UI/Models/Input/HotkeyConfig.cs | 141 + .../UI/Models/Input/KeyboardInputConfig.cs | 422 + src/Ryujinx/UI/Models/ModModel.cs | 32 + src/Ryujinx/UI/Models/PlayerModel.cs | 6 + src/Ryujinx/UI/Models/ProfileImageModel.cs | 32 + src/Ryujinx/UI/Models/SaveModel.cs | 96 + src/Ryujinx/UI/Models/StatusInitEventArgs.cs | 16 + .../UI/Models/StatusUpdatedEventArgs.cs | 24 + src/Ryujinx/UI/Models/TempProfile.cs | 61 + src/Ryujinx/UI/Models/TimeZone.cs | 16 + src/Ryujinx/UI/Models/TitleUpdateModel.cs | 21 + src/Ryujinx/UI/Models/UserProfile.cs | 104 + src/Ryujinx/UI/Renderer/EmbeddedWindow.cs | 226 + .../UI/Renderer/EmbeddedWindowOpenGL.cs | 94 + .../UI/Renderer/EmbeddedWindowVulkan.cs | 42 + .../UI/Renderer/OpenTKBindingsContext.cs | 20 + src/Ryujinx/UI/Renderer/RendererHost.axaml | 12 + src/Ryujinx/UI/Renderer/RendererHost.axaml.cs | 68 + src/Ryujinx/UI/Renderer/SPBOpenGLContext.cs | 49 + .../UI/ViewModels/AboutWindowViewModel.cs | 149 + .../UI/ViewModels/AmiiboWindowViewModel.cs | 518 + .../ViewModels/AppListFavoriteComparable.cs | 43 + src/Ryujinx/UI/ViewModels/BaseModel.cs | 15 + .../DownloadableContentManagerViewModel.cs | 344 + .../Input/ControllerInputViewModel.cs | 84 + .../UI/ViewModels/Input/InputViewModel.cs | 889 ++ .../Input/KeyboardInputViewModel.cs | 73 + .../ViewModels/Input/MotionInputViewModel.cs | 93 + .../ViewModels/Input/RumbleInputViewModel.cs | 27 + .../UI/ViewModels/MainWindowViewModel.cs | 1752 ++++ .../UI/ViewModels/ModManagerViewModel.cs | 336 + .../UI/ViewModels/SettingsViewModel.cs | 615 ++ .../UI/ViewModels/TitleUpdateViewModel.cs | 265 + .../UserFirmwareAvatarSelectorViewModel.cs | 225 + .../UserProfileImageSelectorViewModel.cs | 18 + .../UI/ViewModels/UserProfileViewModel.cs | 28 + .../UI/ViewModels/UserSaveManagerViewModel.cs | 117 + .../UI/Views/Input/ControllerInputView.axaml | 765 ++ .../Views/Input/ControllerInputView.axaml.cs | 198 + src/Ryujinx/UI/Views/Input/InputView.axaml | 225 + src/Ryujinx/UI/Views/Input/InputView.axaml.cs | 61 + .../UI/Views/Input/KeyboardInputView.axaml | 675 ++ .../UI/Views/Input/KeyboardInputView.axaml.cs | 208 + .../UI/Views/Input/MotionInputView.axaml | 171 + .../UI/Views/Input/MotionInputView.axaml.cs | 66 + .../UI/Views/Input/RumbleInputView.axaml | 62 + .../UI/Views/Input/RumbleInputView.axaml.cs | 56 + .../UI/Views/Main/MainMenuBarView.axaml | 210 + .../UI/Views/Main/MainMenuBarView.axaml.cs | 275 + .../UI/Views/Main/MainStatusBarView.axaml | 289 + .../UI/Views/Main/MainStatusBarView.axaml.cs | 72 + .../UI/Views/Main/MainViewControls.axaml | 177 + .../UI/Views/Main/MainViewControls.axaml.cs | 54 + .../UI/Views/Settings/SettingsAudioView.axaml | 81 + .../Views/Settings/SettingsAudioView.axaml.cs | 12 + .../UI/Views/Settings/SettingsCPUView.axaml | 77 + .../Views/Settings/SettingsCPUView.axaml.cs | 12 + .../Views/Settings/SettingsGraphicsView.axaml | 301 + .../Settings/SettingsGraphicsView.axaml.cs | 12 + .../Views/Settings/SettingsHotkeysView.axaml | 109 + .../Settings/SettingsHotkeysView.axaml.cs | 144 + .../UI/Views/Settings/SettingsInputView.axaml | 67 + .../Views/Settings/SettingsInputView.axaml.cs | 17 + .../Views/Settings/SettingsLoggingView.axaml | 120 + .../Settings/SettingsLoggingView.axaml.cs | 12 + .../Views/Settings/SettingsNetworkView.axaml | 64 + .../Settings/SettingsNetworkView.axaml.cs | 12 + .../Views/Settings/SettingsSystemView.axaml | 224 + .../Settings/SettingsSystemView.axaml.cs | 37 + .../UI/Views/Settings/SettingsUIView.axaml | 134 + .../UI/Views/Settings/SettingsUIView.axaml.cs | 65 + .../UI/Views/User/UserEditorView.axaml | 122 + .../UI/Views/User/UserEditorView.axaml.cs | 165 + .../User/UserFirmwareAvatarSelectorView.axaml | 113 + .../UserFirmwareAvatarSelectorView.axaml.cs | 95 + .../User/UserProfileImageSelectorView.axaml | 62 + .../UserProfileImageSelectorView.axaml.cs | 120 + .../UI/Views/User/UserRecovererView.axaml | 82 + .../UI/Views/User/UserRecovererView.axaml.cs | 51 + .../UI/Views/User/UserSaveManagerView.axaml | 213 + .../Views/User/UserSaveManagerView.axaml.cs | 148 + .../UI/Views/User/UserSelectorView.axaml | 162 + .../UI/Views/User/UserSelectorView.axaml.cs | 129 + src/Ryujinx/UI/Windows/AboutWindow.axaml | 270 + src/Ryujinx/UI/Windows/AboutWindow.axaml.cs | 63 + src/Ryujinx/UI/Windows/AmiiboWindow.axaml | 75 + src/Ryujinx/UI/Windows/AmiiboWindow.axaml.cs | 60 + src/Ryujinx/UI/Windows/CheatWindow.axaml | 126 + src/Ryujinx/UI/Windows/CheatWindow.axaml.cs | 128 + .../Windows/ContentDialogOverlayWindow.axaml | 25 + .../ContentDialogOverlayWindow.axaml.cs | 20 + .../DownloadableContentManagerWindow.axaml | 192 + .../DownloadableContentManagerWindow.axaml.cs | 114 + src/Ryujinx/UI/Windows/IconColorPicker.cs | 203 + src/Ryujinx/UI/Windows/MainWindow.axaml | 203 + src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 652 ++ src/Ryujinx/UI/Windows/ModManagerWindow.axaml | 179 + .../UI/Windows/ModManagerWindow.axaml.cs | 139 + src/Ryujinx/UI/Windows/SettingsWindow.axaml | 130 + .../UI/Windows/SettingsWindow.axaml.cs | 103 + src/Ryujinx/UI/Windows/StyleableWindow.cs | 44 + .../UI/Windows/TitleUpdateWindow.axaml | 133 + .../UI/Windows/TitleUpdateWindow.axaml.cs | 103 + src/Ryujinx/app.manifest | 10 + .../Autogenerated/CoreGrammar.cs | 5315 +++++++++++ .../Autogenerated/GlslStd450Grammar.cs | 439 + .../Autogenerated/OpenClGrammar.cs | 841 ++ src/Spv.Generator/ConstantKey.cs | 30 + src/Spv.Generator/DeterministicHashCode.cs | 109 + src/Spv.Generator/DeterministicStringKey.cs | 29 + src/Spv.Generator/GeneratorPool.cs | 56 + src/Spv.Generator/IOperand.cs | 14 + src/Spv.Generator/Instruction.cs | 247 + src/Spv.Generator/InstructionOperands.cs | 72 + src/Spv.Generator/LICENSE | 23 + src/Spv.Generator/LiteralInteger.cs | 105 + src/Spv.Generator/LiteralString.cs | 54 + src/Spv.Generator/Module.cs | 367 + src/Spv.Generator/OperandType.cs | 10 + src/Spv.Generator/Spv.Generator.csproj | 7 + src/Spv.Generator/TypeDeclarationKey.cs | 30 + src/Spv.Generator/spirv.cs | 1624 ++++ 4113 files changed, 595093 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/missing_cpu_instruction.yml create mode 100644 .github/ISSUE_TEMPLATE/missing_service_call.yml create mode 100644 .github/ISSUE_TEMPLATE/missing_shader_instruction.yml create mode 100644 .github/csc.json create mode 100644 .github/dependabot.yml create mode 100644 .github/labeler.yml create mode 100644 .github/reviewers.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/checks.yml create mode 100644 .github/workflows/flatpak.yml create mode 100644 .github/workflows/mako.yml create mode 100644 .github/workflows/nightly_pr_comment.yml create mode 100644 .github/workflows/pr_triage.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 Directory.Packages.props create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 Ryujinx.sln create mode 100644 Ryujinx.sln.DotSettings create mode 100644 crowdin.yml create mode 100644 distribution/legal/THIRDPARTY.md create mode 100644 distribution/linux/Ryujinx.desktop create mode 100755 distribution/linux/Ryujinx.sh create mode 100644 distribution/linux/mime/Ryujinx.xml create mode 100644 distribution/linux/shortcut-template.desktop create mode 100644 distribution/macos/Info.plist create mode 100644 distribution/macos/Ryujinx.icns create mode 100644 distribution/macos/bundle_fix_up.py create mode 100644 distribution/macos/construct_universal_dylib.py create mode 100755 distribution/macos/create_app_bundle.sh create mode 100755 distribution/macos/create_macos_build_ava.sh create mode 100755 distribution/macos/create_macos_build_headless.sh create mode 100644 distribution/macos/entitlements.xml create mode 100644 distribution/macos/shortcut-launch-script.sh create mode 100644 distribution/macos/shortcut-template.plist create mode 100755 distribution/macos/updater.sh create mode 100644 distribution/misc/Logo.svg create mode 100644 distribution/misc/add_tar_exec.py create mode 100644 distribution/windows/alsoft.ini create mode 100644 docs/README.md create mode 100644 docs/coding-guidelines/coding-style.md create mode 100644 docs/workflow/pr-guide.md create mode 100644 global.json create mode 100644 nuget.config create mode 100644 src/ARMeilleure/ARMeilleure.csproj create mode 100644 src/ARMeilleure/Allocators.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/Arm64Optimizer.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/ArmExtensionType.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/ArmShiftType.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/Assembler.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/CallingConvention.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/CodeGenCommon.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/IntrinsicInfo.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/IntrinsicTable.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/IntrinsicType.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs create mode 100644 src/ARMeilleure/CodeGen/CompiledFunction.cs create mode 100644 src/ARMeilleure/CodeGen/Linking/RelocEntry.cs create mode 100644 src/ARMeilleure/CodeGen/Linking/RelocInfo.cs create mode 100644 src/ARMeilleure/CodeGen/Linking/Symbol.cs create mode 100644 src/ARMeilleure/CodeGen/Linking/SymbolType.cs create mode 100644 src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs create mode 100644 src/ARMeilleure/CodeGen/Optimizations/ConstantFolding.cs create mode 100644 src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs create mode 100644 src/ARMeilleure/CodeGen/Optimizations/Simplification.cs create mode 100644 src/ARMeilleure/CodeGen/Optimizations/TailMerge.cs create mode 100644 src/ARMeilleure/CodeGen/PreAllocatorCommon.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/AllocationResult.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/IRegisterAllocator.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/LiveIntervalList.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/LiveRange.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/RegisterMasks.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/UseList.cs create mode 100644 src/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs create mode 100644 src/ARMeilleure/CodeGen/Unwinding/UnwindPseudoOp.cs create mode 100644 src/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs create mode 100644 src/ARMeilleure/CodeGen/X86/Assembler.cs create mode 100644 src/ARMeilleure/CodeGen/X86/AssemblerTable.cs create mode 100644 src/ARMeilleure/CodeGen/X86/CallConvName.cs create mode 100644 src/ARMeilleure/CodeGen/X86/CallingConvention.cs create mode 100644 src/ARMeilleure/CodeGen/X86/CodeGenCommon.cs create mode 100644 src/ARMeilleure/CodeGen/X86/CodeGenContext.cs create mode 100644 src/ARMeilleure/CodeGen/X86/CodeGenerator.cs create mode 100644 src/ARMeilleure/CodeGen/X86/HardwareCapabilities.cs create mode 100644 src/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs create mode 100644 src/ARMeilleure/CodeGen/X86/IntrinsicTable.cs create mode 100644 src/ARMeilleure/CodeGen/X86/IntrinsicType.cs create mode 100644 src/ARMeilleure/CodeGen/X86/Mxcsr.cs create mode 100644 src/ARMeilleure/CodeGen/X86/PreAllocator.cs create mode 100644 src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs create mode 100644 src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs create mode 100644 src/ARMeilleure/CodeGen/X86/X86Condition.cs create mode 100644 src/ARMeilleure/CodeGen/X86/X86Instruction.cs create mode 100644 src/ARMeilleure/CodeGen/X86/X86Optimizer.cs create mode 100644 src/ARMeilleure/CodeGen/X86/X86Register.cs create mode 100644 src/ARMeilleure/Common/AddressTable.cs create mode 100644 src/ARMeilleure/Common/Allocator.cs create mode 100644 src/ARMeilleure/Common/ArenaAllocator.cs create mode 100644 src/ARMeilleure/Common/BitMap.cs create mode 100644 src/ARMeilleure/Common/BitUtils.cs create mode 100644 src/ARMeilleure/Common/Counter.cs create mode 100644 src/ARMeilleure/Common/EntryTable.cs create mode 100644 src/ARMeilleure/Common/EnumUtils.cs create mode 100644 src/ARMeilleure/Common/NativeAllocator.cs create mode 100644 src/ARMeilleure/Decoders/Block.cs create mode 100644 src/ARMeilleure/Decoders/Condition.cs create mode 100644 src/ARMeilleure/Decoders/DataOp.cs create mode 100644 src/ARMeilleure/Decoders/Decoder.cs create mode 100644 src/ARMeilleure/Decoders/DecoderHelper.cs create mode 100644 src/ARMeilleure/Decoders/DecoderMode.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32Adr.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32Alu.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluBf.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluImm.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluImm16.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluMla.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluReg.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluRsImm.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluRsReg.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluUmull.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluUx.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32BImm.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32BReg.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32Exception.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32HasSetFlags.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32Mem.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32MemEx.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32MemMult.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32MemReg.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32MemRsImm.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32Simd.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32SimdImm.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeAlu.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeAluImm.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeAluRs.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeAluRx.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeBImm.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeCond.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeLit.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeSimd.cs create mode 100644 src/ARMeilleure/Decoders/InstDescriptor.cs create mode 100644 src/ARMeilleure/Decoders/InstEmitter.cs create mode 100644 src/ARMeilleure/Decoders/IntType.cs create mode 100644 src/ARMeilleure/Decoders/OpCode.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32Alu.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluBf.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluImm16.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluMla.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluRsImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluRsReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluUmull.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluUx.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32BImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32BReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32Exception.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32Mem.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MemImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MemImm8.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MemLdEx.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MemMult.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MemReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MemRsImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MemStEx.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32Mrs.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MsrReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32Sat.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32Sat16.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32Simd.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdBase.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdBinary.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdCmpZ.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdCvtFFixed.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdCvtTB.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdDupElem.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdDupGP.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdExt.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdImm44.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdLong.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMemImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMemMult.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMemPair.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMemSingle.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMovGp.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMovGpDouble.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMovGpElem.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMovn.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdRegElem.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdRegElemLong.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdRegLong.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdRegS.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdRegWide.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdRev.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdS.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdSel.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdShImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdShImmLong.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdShImmNarrow.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdSpecial.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdSqrte.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdTbl.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32System.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeAdr.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeAlu.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeAluBinary.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeAluImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeAluRs.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeAluRx.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeBImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeBImmAl.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeBImmCmp.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeBImmCond.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeBImmTest.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeBReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeBfm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeCcmp.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeCcmpImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeCcmpReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeCsel.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeException.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMem.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMemEx.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMemImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMemLit.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMemPair.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMemReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMov.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMul.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimd.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdCvt.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdExt.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdFcond.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdFmov.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdHelper.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdIns.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdMemImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdMemLit.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdMemMs.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdMemPair.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdMemReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdMemSs.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdRegElem.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdShImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdTbl.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSystem.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AddSubImm3.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AddSubReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AddSubSp.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16Adr.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AluImm8.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AluImmZero.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AluRegHigh.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AluRegLow.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AluUx.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16BImm11.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16BImm8.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16BImmCmp.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16BReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16Exception.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16IfThen.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16MemImm5.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16MemLit.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16MemMult.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16MemReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16MemSp.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16MemStack.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16ShiftImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16ShiftReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16SpRel.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32Alu.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluBf.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluImm12.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluMla.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluRsImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluUmull.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluUx.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32BImm20.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32BImm24.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MemImm12.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MemImm8.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MemImm8D.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MemLdEx.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MemMult.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MemRsImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MemStEx.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MovImm16.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32ShiftReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32Tb.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeTable.cs create mode 100644 src/ARMeilleure/Decoders/Optimizations/TailCallRemover.cs create mode 100644 src/ARMeilleure/Decoders/RegisterSize.cs create mode 100644 src/ARMeilleure/Decoders/ShiftType.cs create mode 100644 src/ARMeilleure/Diagnostics/IRDumper.cs create mode 100644 src/ARMeilleure/Diagnostics/Logger.cs create mode 100644 src/ARMeilleure/Diagnostics/PassName.cs create mode 100644 src/ARMeilleure/Diagnostics/Symbols.cs create mode 100644 src/ARMeilleure/Diagnostics/TranslatorEventSource.cs create mode 100644 src/ARMeilleure/Instructions/CryptoHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitAlu.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitAlu32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitAluHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitBfm.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitCcmp.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitCsel.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitDiv.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitException.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitException32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitFlow.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitFlow32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitFlowHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitHash.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitHash32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitHashHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMemory.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMemory32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMemoryEx.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMemoryEx32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMove.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMul.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMul32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdCmp.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdCmp32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdCrypto.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdCrypto32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdCvt.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdHash.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdHash32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdHashHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdHelper32Arm64.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdHelperArm64.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdLogical.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdLogical32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdMemory.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdMemory32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdMove.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdMove32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdShift.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdShift32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSystem.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSystem32.cs create mode 100644 src/ARMeilleure/Instructions/InstName.cs create mode 100644 src/ARMeilleure/Instructions/NativeInterface.cs create mode 100644 src/ARMeilleure/Instructions/SoftFallback.cs create mode 100644 src/ARMeilleure/Instructions/SoftFloat.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/BasicBlock.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/BasicBlockFrequency.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/Comparison.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/IIntrusiveListNode.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/Instruction.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/Intrinsic.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/IntrusiveList.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/MemoryOperand.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/Multiplier.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/Operand.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/OperandKind.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/OperandType.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/Operation.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/PhiOperation.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/Register.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/RegisterType.cs create mode 100644 src/ARMeilleure/Memory/IJitMemoryAllocator.cs create mode 100644 src/ARMeilleure/Memory/IJitMemoryBlock.cs create mode 100644 src/ARMeilleure/Memory/IMemoryManager.cs create mode 100644 src/ARMeilleure/Memory/InvalidAccessException.cs create mode 100644 src/ARMeilleure/Memory/MemoryManagerType.cs create mode 100644 src/ARMeilleure/Memory/ReservedRegion.cs create mode 100644 src/ARMeilleure/Native/JitSupportDarwin.cs create mode 100644 src/ARMeilleure/Native/libs/libarmeilleure-jitsupport.dylib create mode 100644 src/ARMeilleure/Native/macos_jit_support/Makefile create mode 100644 src/ARMeilleure/Native/macos_jit_support/support.c create mode 100644 src/ARMeilleure/Optimizations.cs create mode 100644 src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs create mode 100644 src/ARMeilleure/Signal/TestMethods.cs create mode 100644 src/ARMeilleure/Signal/WindowsPartialUnmapHandler.cs create mode 100644 src/ARMeilleure/State/Aarch32Mode.cs create mode 100644 src/ARMeilleure/State/ExceptionCallback.cs create mode 100644 src/ARMeilleure/State/ExecutionContext.cs create mode 100644 src/ARMeilleure/State/ExecutionMode.cs create mode 100644 src/ARMeilleure/State/FPCR.cs create mode 100644 src/ARMeilleure/State/FPException.cs create mode 100644 src/ARMeilleure/State/FPRoundingMode.cs create mode 100644 src/ARMeilleure/State/FPSCR.cs create mode 100644 src/ARMeilleure/State/FPSR.cs create mode 100644 src/ARMeilleure/State/FPState.cs create mode 100644 src/ARMeilleure/State/FPType.cs create mode 100644 src/ARMeilleure/State/ICounter.cs create mode 100644 src/ARMeilleure/State/NativeContext.cs create mode 100644 src/ARMeilleure/State/PState.cs create mode 100644 src/ARMeilleure/State/RegisterAlias.cs create mode 100644 src/ARMeilleure/State/RegisterConsts.cs create mode 100644 src/ARMeilleure/State/V128.cs create mode 100644 src/ARMeilleure/Statistics.cs create mode 100644 src/ARMeilleure/Translation/ArmEmitterContext.cs create mode 100644 src/ARMeilleure/Translation/Cache/CacheEntry.cs create mode 100644 src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs create mode 100644 src/ARMeilleure/Translation/Cache/JitCache.cs create mode 100644 src/ARMeilleure/Translation/Cache/JitCacheInvalidation.cs create mode 100644 src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs create mode 100644 src/ARMeilleure/Translation/Compiler.cs create mode 100644 src/ARMeilleure/Translation/CompilerContext.cs create mode 100644 src/ARMeilleure/Translation/CompilerOptions.cs create mode 100644 src/ARMeilleure/Translation/ControlFlowGraph.cs create mode 100644 src/ARMeilleure/Translation/DelegateInfo.cs create mode 100644 src/ARMeilleure/Translation/Delegates.cs create mode 100644 src/ARMeilleure/Translation/DispatcherFunction.cs create mode 100644 src/ARMeilleure/Translation/Dominance.cs create mode 100644 src/ARMeilleure/Translation/EmitterContext.cs create mode 100644 src/ARMeilleure/Translation/GuestFunction.cs create mode 100644 src/ARMeilleure/Translation/IntervalTree.cs create mode 100644 src/ARMeilleure/Translation/PTC/EncodingCache.cs create mode 100644 src/ARMeilleure/Translation/PTC/IPtcLoadState.cs create mode 100644 src/ARMeilleure/Translation/PTC/Ptc.cs create mode 100644 src/ARMeilleure/Translation/PTC/PtcFormatter.cs create mode 100644 src/ARMeilleure/Translation/PTC/PtcLoadingState.cs create mode 100644 src/ARMeilleure/Translation/PTC/PtcProfiler.cs create mode 100644 src/ARMeilleure/Translation/PTC/PtcState.cs create mode 100644 src/ARMeilleure/Translation/RegisterToLocal.cs create mode 100644 src/ARMeilleure/Translation/RegisterUsage.cs create mode 100644 src/ARMeilleure/Translation/RejitRequest.cs create mode 100644 src/ARMeilleure/Translation/SsaConstruction.cs create mode 100644 src/ARMeilleure/Translation/SsaDeconstruction.cs create mode 100644 src/ARMeilleure/Translation/TranslatedFunction.cs create mode 100644 src/ARMeilleure/Translation/Translator.cs create mode 100644 src/ARMeilleure/Translation/TranslatorCache.cs create mode 100644 src/ARMeilleure/Translation/TranslatorQueue.cs create mode 100644 src/ARMeilleure/Translation/TranslatorStubs.cs create mode 100644 src/ARMeilleure/Translation/TranslatorTestMethods.cs create mode 100644 src/Ryujinx.Audio.Backends.OpenAL/OpenALAudioBuffer.cs create mode 100644 src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs create mode 100644 src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs create mode 100644 src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj create mode 100644 src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj create mode 100644 src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs create mode 100644 src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs create mode 100644 src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dylib create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs create mode 100644 src/Ryujinx.Audio/AudioManager.cs create mode 100644 src/Ryujinx.Audio/Backends/Common/BackendHelper.cs create mode 100644 src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs create mode 100644 src/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs create mode 100644 src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs create mode 100644 src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs create mode 100644 src/Ryujinx.Audio/Backends/CompatLayer/Downmixing.cs create mode 100644 src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs create mode 100644 src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionInput.cs create mode 100644 src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs create mode 100644 src/Ryujinx.Audio/Common/AudioBuffer.cs create mode 100644 src/Ryujinx.Audio/Common/AudioDeviceSession.cs create mode 100644 src/Ryujinx.Audio/Common/AudioDeviceState.cs create mode 100644 src/Ryujinx.Audio/Common/AudioInputConfiguration.cs create mode 100644 src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs create mode 100644 src/Ryujinx.Audio/Common/AudioUserBuffer.cs create mode 100644 src/Ryujinx.Audio/Common/SampleFormat.cs create mode 100644 src/Ryujinx.Audio/Constants.cs create mode 100644 src/Ryujinx.Audio/Input/AudioInputManager.cs create mode 100644 src/Ryujinx.Audio/Input/AudioInputSystem.cs create mode 100644 src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs create mode 100644 src/Ryujinx.Audio/Integration/IHardwareDevice.cs create mode 100644 src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs create mode 100644 src/Ryujinx.Audio/Integration/IHardwareDeviceSession.cs create mode 100644 src/Ryujinx.Audio/Integration/IWritableEvent.cs create mode 100644 src/Ryujinx.Audio/Output/AudioOutputManager.cs create mode 100644 src/Ryujinx.Audio/Output/AudioOutputSystem.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/EffectType.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/NodeStates.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/PlayState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/SinkType.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs create mode 100644 src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs create mode 100644 src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs create mode 100644 src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/BitArray.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs create mode 100644 src/Ryujinx.Audio/ResultCode.cs create mode 100644 src/Ryujinx.Audio/Ryujinx.Audio.csproj create mode 100644 src/Ryujinx.Common/AsyncWorkQueue.cs create mode 100644 src/Ryujinx.Common/Collections/IntervalTree.cs create mode 100644 src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs create mode 100644 src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs create mode 100644 src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs create mode 100644 src/Ryujinx.Common/Collections/TreeDictionary.cs create mode 100644 src/Ryujinx.Common/Configuration/AntiAliasing.cs create mode 100644 src/Ryujinx.Common/Configuration/AppDataManager.cs create mode 100644 src/Ryujinx.Common/Configuration/AspectRatioExtensions.cs create mode 100644 src/Ryujinx.Common/Configuration/BackendThreading.cs create mode 100644 src/Ryujinx.Common/Configuration/DownloadableContentContainer.cs create mode 100644 src/Ryujinx.Common/Configuration/DownloadableContentJsonSerializerContext.cs create mode 100644 src/Ryujinx.Common/Configuration/DownloadableContentNca.cs create mode 100644 src/Ryujinx.Common/Configuration/GraphicsBackend.cs create mode 100644 src/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/GamepadInputId.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/JoyconConfigControllerStick.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/Motion/CemuHookMotionConfigController.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/Motion/JsonMotionConfigControllerConverter.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigController.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigJsonSerializerContext.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/Motion/StandardMotionConfigController.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/RumbleConfigController.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/StandardControllerInputConfig.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/StickInputId.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/ControllerType.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/GenericInputConfigurationCommon.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/InputConfig.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/InputConfigJsonSerializerContext.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Key.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Keyboard/GenericKeyboardInputConfig.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Keyboard/JoyconConfigKeyboardStick.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Keyboard/StandardKeyboardInputConfig.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/LeftJoyconCommonConfig.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/RightJoyconCommonConfig.cs create mode 100644 src/Ryujinx.Common/Configuration/HideCursorMode.cs create mode 100644 src/Ryujinx.Common/Configuration/MemoryManagerMode.cs create mode 100644 src/Ryujinx.Common/Configuration/Mod.cs create mode 100644 src/Ryujinx.Common/Configuration/ModMetadata.cs create mode 100644 src/Ryujinx.Common/Configuration/ModMetadataJsonSerializerContext.cs create mode 100644 src/Ryujinx.Common/Configuration/Multiplayer/MultiplayerMode.cs create mode 100644 src/Ryujinx.Common/Configuration/ScalingFilter.cs create mode 100644 src/Ryujinx.Common/Configuration/TitleUpdateMetadata.cs create mode 100644 src/Ryujinx.Common/Configuration/TitleUpdateMetadataJsonSerializerContext.cs create mode 100644 src/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs create mode 100644 src/Ryujinx.Common/Extensions/BinaryWriterExtensions.cs create mode 100644 src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs create mode 100644 src/Ryujinx.Common/Extensions/StreamExtensions.cs create mode 100644 src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs create mode 100644 src/Ryujinx.Common/GraphicsDriver/NVAPI/Nvapi.cs create mode 100644 src/Ryujinx.Common/GraphicsDriver/NVAPI/NvapiUnicodeString.cs create mode 100644 src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsApplicationV4.cs create mode 100644 src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsProfile.cs create mode 100644 src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsSetting.cs create mode 100644 src/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs create mode 100644 src/Ryujinx.Common/Hash128.cs create mode 100644 src/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs create mode 100644 src/Ryujinx.Common/Logging/Formatters/DynamicObjectFormatter.cs create mode 100644 src/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs create mode 100644 src/Ryujinx.Common/Logging/LogClass.cs create mode 100644 src/Ryujinx.Common/Logging/LogEventArgs.cs create mode 100644 src/Ryujinx.Common/Logging/LogEventArgsJson.cs create mode 100644 src/Ryujinx.Common/Logging/LogEventJsonSerializerContext.cs create mode 100644 src/Ryujinx.Common/Logging/LogLevel.cs create mode 100644 src/Ryujinx.Common/Logging/Logger.cs create mode 100644 src/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs create mode 100644 src/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs create mode 100644 src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs create mode 100644 src/Ryujinx.Common/Logging/Targets/ILogTarget.cs create mode 100644 src/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs create mode 100644 src/Ryujinx.Common/Memory/ArrayPtr.cs create mode 100644 src/Ryujinx.Common/Memory/Box.cs create mode 100644 src/Ryujinx.Common/Memory/IArray.cs create mode 100644 src/Ryujinx.Common/Memory/MemoryOwner.cs create mode 100644 src/Ryujinx.Common/Memory/MemoryStreamManager.cs create mode 100644 src/Ryujinx.Common/Memory/PartialUnmaps/NativeReaderWriterLock.cs create mode 100644 src/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapHelpers.cs create mode 100644 src/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapState.cs create mode 100644 src/Ryujinx.Common/Memory/PartialUnmaps/ThreadLocalMap.cs create mode 100644 src/Ryujinx.Common/Memory/Ptr.cs create mode 100644 src/Ryujinx.Common/Memory/SpanOwner.cs create mode 100644 src/Ryujinx.Common/Memory/SpanReader.cs create mode 100644 src/Ryujinx.Common/Memory/SpanWriter.cs create mode 100644 src/Ryujinx.Common/Memory/StructArrayHelpers.cs create mode 100644 src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs create mode 100644 src/Ryujinx.Common/PerformanceCounter.cs create mode 100644 src/Ryujinx.Common/Pools/ObjectPool.cs create mode 100644 src/Ryujinx.Common/Pools/SharedPools.cs create mode 100644 src/Ryujinx.Common/Pools/ThreadStaticArray.cs create mode 100644 src/Ryujinx.Common/PreciseSleep/IPreciseSleepEvent.cs create mode 100644 src/Ryujinx.Common/PreciseSleep/Nanosleep.cs create mode 100644 src/Ryujinx.Common/PreciseSleep/NanosleepEvent.cs create mode 100644 src/Ryujinx.Common/PreciseSleep/NanosleepPool.cs create mode 100644 src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs create mode 100644 src/Ryujinx.Common/PreciseSleep/SleepEvent.cs create mode 100644 src/Ryujinx.Common/PreciseSleep/WindowsGranularTimer.cs create mode 100644 src/Ryujinx.Common/PreciseSleep/WindowsSleepEvent.cs create mode 100644 src/Ryujinx.Common/ReactiveObject.cs create mode 100644 src/Ryujinx.Common/ReferenceEqualityComparer.cs create mode 100644 src/Ryujinx.Common/ReleaseInformation.cs create mode 100644 src/Ryujinx.Common/Ryujinx.Common.csproj create mode 100644 src/Ryujinx.Common/SystemInterop/DisplaySleep.cs create mode 100644 src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs create mode 100644 src/Ryujinx.Common/SystemInterop/GdiPlusHelper.cs create mode 100644 src/Ryujinx.Common/SystemInterop/StdErrAdapter.cs create mode 100644 src/Ryujinx.Common/SystemInterop/WindowsMultimediaTimerResolution.cs create mode 100644 src/Ryujinx.Common/Utilities/BitUtils.cs create mode 100644 src/Ryujinx.Common/Utilities/BitfieldExtensions.cs create mode 100644 src/Ryujinx.Common/Utilities/Buffers.cs create mode 100644 src/Ryujinx.Common/Utilities/CommonJsonContext.cs create mode 100644 src/Ryujinx.Common/Utilities/EmbeddedResources.cs create mode 100644 src/Ryujinx.Common/Utilities/FileSystemUtils.cs create mode 100644 src/Ryujinx.Common/Utilities/HexUtils.cs create mode 100644 src/Ryujinx.Common/Utilities/JsonHelper.cs create mode 100644 src/Ryujinx.Common/Utilities/MessagePackObjectFormatter.cs create mode 100644 src/Ryujinx.Common/Utilities/NetworkHelpers.cs create mode 100644 src/Ryujinx.Common/Utilities/OsUtils.cs create mode 100644 src/Ryujinx.Common/Utilities/SpanHelpers.cs create mode 100644 src/Ryujinx.Common/Utilities/StreamUtils.cs create mode 100644 src/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs create mode 100644 src/Ryujinx.Common/Utilities/UInt128Utils.cs create mode 100644 src/Ryujinx.Common/XXHash128.cs create mode 100644 src/Ryujinx.Cpu/AddressSpace.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/Arm/ApFlags.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/Arm/ExceptionClass.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/DummyDiskCacheLoadState.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvAddressSpaceRange.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvApi.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvEngine.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvIpaAllocator.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocation.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvVcpu.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvVcpuPool.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvVm.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/TimeApi.cs create mode 100644 src/Ryujinx.Cpu/DummyDiskCacheLoadState.cs create mode 100644 src/Ryujinx.Cpu/ExceptionCallbacks.cs create mode 100644 src/Ryujinx.Cpu/ICpuContext.cs create mode 100644 src/Ryujinx.Cpu/ICpuEngine.cs create mode 100644 src/Ryujinx.Cpu/IDiskCacheState.cs create mode 100644 src/Ryujinx.Cpu/IExecutionContext.cs create mode 100644 src/Ryujinx.Cpu/ITickSource.cs create mode 100644 src/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressIntrusiveRedBlackTree.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartition.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionAllocator.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionMultiAllocation.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitioned.cs create mode 100644 src/Ryujinx.Cpu/Jit/HostTracked/NativePageTable.cs create mode 100644 src/Ryujinx.Cpu/Jit/JitCpuContext.cs create mode 100644 src/Ryujinx.Cpu/Jit/JitDiskCacheLoadState.cs create mode 100644 src/Ryujinx.Cpu/Jit/JitEngine.cs create mode 100644 src/Ryujinx.Cpu/Jit/JitExecutionContext.cs create mode 100644 src/Ryujinx.Cpu/Jit/JitMemoryAllocator.cs create mode 100644 src/Ryujinx.Cpu/Jit/JitMemoryBlock.cs create mode 100644 src/Ryujinx.Cpu/Jit/MemoryManager.cs create mode 100644 src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs create mode 100644 src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/AarchCompiler.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/AddressForm.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/A32Compiler.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Block.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/BranchType.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/CodeGenContext.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Decoder.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/IInstEmit.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/ImmUtils.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/InstDecoders.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/InstFlags.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/InstInfo.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/InstInfoForTable.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/InstMeta.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/InstName.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/InstTableA32.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/InstTableT16.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/InstTableT32.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/MultiBlock.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/PendingBranch.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/RegisterAllocator.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/RegisterUtils.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/ScopedRegister.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmit.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitAbsDiff.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitAlu.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitBit.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitCommon.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitCrc32.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitDivide.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitExtension.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitGE.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitHalve.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMove.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMultiply.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonArithmetic.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonBit.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonCommon.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonCompare.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonConvert.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonCrypto.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonHash.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonLogical.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonMemory.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonMove.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonRound.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonSaturate.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonShift.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonSystem.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSaturate.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSystem.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpArithmetic.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpCompare.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpConvert.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpMove.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpRound.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/A64Compiler.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/Block.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/ImmUtils.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/InstFlags.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/InstInfo.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/MultiBlock.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/SysUtils.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstTable.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Cache/CacheEntry.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Cache/CacheMemoryAllocator.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Cache/JitCacheInvalidation.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Cache/JitSupportDarwin.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Cache/PageAlignedRangeList.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/AbiConstants.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmCondition.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmExtensionType.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmShiftType.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/Assembler.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/CodeGenCommon.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/RegisterSaveRestore.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/StackWalker.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/TailMerger.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeGen/Operand.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeGen/OperandKind.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeGen/OperandType.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeGen/Register.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeGen/RegisterType.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CodeWriter.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CompiledFunction.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CpuPreset.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/CpuPresets.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Graph/DataFlow.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Graph/IBlock.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Graph/IBlockList.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Graph/RegisterMask.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Graph/RegisterUse.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/IStackWalker.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/IsaFeature.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/IsaVersion.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/LightningJitEngine.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/NativeContextOffsets.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/NativeInterface.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/State/ExecutionMode.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/State/NativeContext.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Table/IInstInfo.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Table/InstEncoding.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Table/InstTableLevel.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/TranslatedFunction.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/Translator.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/TranslatorCache.cs create mode 100644 src/Ryujinx.Cpu/LightningJit/TranslatorStubs.cs create mode 100644 src/Ryujinx.Cpu/LoadState.cs create mode 100644 src/Ryujinx.Cpu/ManagedPageFlags.cs create mode 100644 src/Ryujinx.Cpu/MemoryEhMeilleure.cs create mode 100644 src/Ryujinx.Cpu/MemoryHelper.cs create mode 100644 src/Ryujinx.Cpu/PrivateMemoryAllocation.cs create mode 100644 src/Ryujinx.Cpu/PrivateMemoryAllocator.cs create mode 100644 src/Ryujinx.Cpu/Ryujinx.Cpu.csproj create mode 100644 src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs create mode 100644 src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs create mode 100644 src/Ryujinx.Cpu/Signal/WindowsSignalHandlerRegistration.cs create mode 100644 src/Ryujinx.Cpu/TickSource.cs create mode 100644 src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs create mode 100644 src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs create mode 100644 src/Ryujinx.Graphics.Device/DeviceState.cs create mode 100644 src/Ryujinx.Graphics.Device/IDeviceState.cs create mode 100644 src/Ryujinx.Graphics.Device/IDeviceStateWithContext.cs create mode 100644 src/Ryujinx.Graphics.Device/ISynchronizationManager.cs create mode 100644 src/Ryujinx.Graphics.Device/RwCallback.cs create mode 100644 src/Ryujinx.Graphics.Device/Ryujinx.Graphics.Device.csproj create mode 100644 src/Ryujinx.Graphics.GAL/AddressMode.cs create mode 100644 src/Ryujinx.Graphics.GAL/AdvancedBlendDescriptor.cs create mode 100644 src/Ryujinx.Graphics.GAL/AdvancedBlendOp.cs create mode 100644 src/Ryujinx.Graphics.GAL/AdvancedBlendOverlap.cs create mode 100644 src/Ryujinx.Graphics.GAL/AntiAliasing.cs create mode 100644 src/Ryujinx.Graphics.GAL/BlendDescriptor.cs create mode 100644 src/Ryujinx.Graphics.GAL/BlendFactor.cs create mode 100644 src/Ryujinx.Graphics.GAL/BlendOp.cs create mode 100644 src/Ryujinx.Graphics.GAL/BufferAccess.cs create mode 100644 src/Ryujinx.Graphics.GAL/BufferAssignment.cs create mode 100644 src/Ryujinx.Graphics.GAL/BufferHandle.cs create mode 100644 src/Ryujinx.Graphics.GAL/BufferRange.cs create mode 100644 src/Ryujinx.Graphics.GAL/Capabilities.cs create mode 100644 src/Ryujinx.Graphics.GAL/ColorF.cs create mode 100644 src/Ryujinx.Graphics.GAL/CompareMode.cs create mode 100644 src/Ryujinx.Graphics.GAL/CompareOp.cs create mode 100644 src/Ryujinx.Graphics.GAL/CounterType.cs create mode 100644 src/Ryujinx.Graphics.GAL/DepthMode.cs create mode 100644 src/Ryujinx.Graphics.GAL/DepthStencilMode.cs create mode 100644 src/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs create mode 100644 src/Ryujinx.Graphics.GAL/DeviceInfo.cs create mode 100644 src/Ryujinx.Graphics.GAL/Extents2D.cs create mode 100644 src/Ryujinx.Graphics.GAL/Extents2DF.cs create mode 100644 src/Ryujinx.Graphics.GAL/Face.cs create mode 100644 src/Ryujinx.Graphics.GAL/Format.cs create mode 100644 src/Ryujinx.Graphics.GAL/FrontFace.cs create mode 100644 src/Ryujinx.Graphics.GAL/HardwareInfo.cs create mode 100644 src/Ryujinx.Graphics.GAL/ICounterEvent.cs create mode 100644 src/Ryujinx.Graphics.GAL/IImageArray.cs create mode 100644 src/Ryujinx.Graphics.GAL/IPipeline.cs create mode 100644 src/Ryujinx.Graphics.GAL/IProgram.cs create mode 100644 src/Ryujinx.Graphics.GAL/IRenderer.cs create mode 100644 src/Ryujinx.Graphics.GAL/ISampler.cs create mode 100644 src/Ryujinx.Graphics.GAL/ITexture.cs create mode 100644 src/Ryujinx.Graphics.GAL/ITextureArray.cs create mode 100644 src/Ryujinx.Graphics.GAL/IWindow.cs create mode 100644 src/Ryujinx.Graphics.GAL/ImageCrop.cs create mode 100644 src/Ryujinx.Graphics.GAL/IndexType.cs create mode 100644 src/Ryujinx.Graphics.GAL/LogicalOp.cs create mode 100644 src/Ryujinx.Graphics.GAL/MagFilter.cs create mode 100644 src/Ryujinx.Graphics.GAL/MinFilter.cs create mode 100644 src/Ryujinx.Graphics.GAL/MultisampleDescriptor.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/BufferMap.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/BarrierCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/BeginTransformFeedbackCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferDisposeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferGetDataCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferSetDataCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearBufferCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetColorCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetDepthStencilCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/CommandBufferBarrierCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/CopyBufferCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventDisposeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventFlushCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DispatchComputeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCountCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCountCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawTextureCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndHostConditionalRenderingCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndTransformFeedbackCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/IGALCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArrayDisposeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetImagesCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramCheckLinkCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramDisposeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramGetBinaryCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ActionCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferAccessCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferSparseCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateHostBufferCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateImageArrayCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateProgramCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSamplerCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSyncCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureArrayCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/GetCapabilitiesCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ReportCounterCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ResetCounterCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/UpdateCountersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Sampler/SamplerDisposeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetAlphaTestCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateAdvancedCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthBiasCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthClampCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthModeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthTestCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFrontFaceCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArrayCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArraySeparateCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetIndexBufferCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLineParametersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLogicOpStateCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetMultisampleStateCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPatchParametersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPointParametersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPolygonModeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveRestartCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveTopologyCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetProgramCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRasterizerDiscardCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetColorMasksCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorsCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStencilTestCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStorageBuffersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureAndSamplerCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArrayCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArraySeparateCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTransformFeedbackBuffersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUniformBuffersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUserClipDistanceCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexAttribsCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexBuffersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToBufferCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToScaledCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToSliceCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCreateViewCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataSliceCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureReleaseCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetStorageCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArrayDisposeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetSamplersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetTexturesCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierTiledCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingFlushCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Model/CircularSpanPool.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Model/ResultBox.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Model/SpanRef.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Model/TableRef.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/ProgramQueue.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/BinaryProgramRequest.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/IProgramRequest.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedCounterEvent.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedProgram.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedSampler.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/SyncMap.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/ThreadedHelpers.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs create mode 100644 src/Ryujinx.Graphics.GAL/Origin.cs create mode 100644 src/Ryujinx.Graphics.GAL/PinnedSpan.cs create mode 100644 src/Ryujinx.Graphics.GAL/PolygonMode.cs create mode 100644 src/Ryujinx.Graphics.GAL/PolygonModeMask.cs create mode 100644 src/Ryujinx.Graphics.GAL/PrimitiveTopology.cs create mode 100644 src/Ryujinx.Graphics.GAL/ProgramLinkStatus.cs create mode 100644 src/Ryujinx.Graphics.GAL/ProgramPipelineState.cs create mode 100644 src/Ryujinx.Graphics.GAL/Rectangle.cs create mode 100644 src/Ryujinx.Graphics.GAL/ResourceLayout.cs create mode 100644 src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj create mode 100644 src/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs create mode 100644 src/Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs create mode 100644 src/Ryujinx.Graphics.GAL/ShaderInfo.cs create mode 100644 src/Ryujinx.Graphics.GAL/ShaderSource.cs create mode 100644 src/Ryujinx.Graphics.GAL/StencilOp.cs create mode 100644 src/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs create mode 100644 src/Ryujinx.Graphics.GAL/SwizzleComponent.cs create mode 100644 src/Ryujinx.Graphics.GAL/SystemMemoryType.cs create mode 100644 src/Ryujinx.Graphics.GAL/Target.cs create mode 100644 src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs create mode 100644 src/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs create mode 100644 src/Ryujinx.Graphics.GAL/UpscaleType.cs create mode 100644 src/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs create mode 100644 src/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs create mode 100644 src/Ryujinx.Graphics.GAL/Viewport.cs create mode 100644 src/Ryujinx.Graphics.GAL/ViewportSwizzle.cs create mode 100644 src/Ryujinx.Graphics.Gpu/ClassId.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Constants.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClassState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeQmd.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/ConditionalRenderEnabled.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/DeviceStateWithShadow.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClassState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaTexture.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/GPFifo/CompressedMethod.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPEntry.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClassState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClassState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/AluOperation.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/AluRegOperation.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/AssignmentOperation.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/IMacroEE.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLEFunctionName.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLETable.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/MacroInterpreter.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJit.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitCompiler.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitContext.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MmeShadowScratch.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/SetMmeShadowRamControlMode.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendFunctions.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendManager.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendPreGenTable.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendUcode.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/UcodeAssembler.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VertexInfoBufferUpdater.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsCompute.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ConditionalRendering.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/IbStreamer.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/IndirectDrawType.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/RenderTargetUpdateFlags.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClassState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodTexture.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/GpuVa.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/MemoryLayout.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/PrimitiveType.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/SamplerIndex.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/SbDescriptor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/ZetaFormat.cs create mode 100644 src/Ryujinx.Graphics.Gpu/GpuChannel.cs create mode 100644 src/Ryujinx.Graphics.Gpu/GpuContext.cs create mode 100644 src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/ITextureDescriptor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/Pool.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/Sampler.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/SamplerMinFilter.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/SamplerMipFilter.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/SamplerPoolCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/Texture.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureMatchQuality.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferCacheEntry.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferPreFlush.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferStage.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferUpdater.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/MultiRangeBuffer.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/MultiRangeWritableBlock.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/PteKind.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/ResourceKind.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/UnmapEventArgs.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/CachedShaderStage.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ComputeShaderCacheHashTable.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadException.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheOutputStreams.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/GpuChannelComputeState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/HashTable/HashState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/HashTable/IDataAccessor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionHashTable.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionedHashTable.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/HashTable/SmartDataAccessor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderCodeAccessor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderDumpPaths.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderDumper.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/TransformFeedbackDescriptor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Synchronization/HostSyncFlags.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Synchronization/ISyncActionHandler.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Synchronization/Syncpoint.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Synchronization/SyncpointWaiterHandle.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Window.cs create mode 100644 src/Ryujinx.Graphics.Host1x/ClassId.cs create mode 100644 src/Ryujinx.Graphics.Host1x/Devices.cs create mode 100644 src/Ryujinx.Graphics.Host1x/Host1xClass.cs create mode 100644 src/Ryujinx.Graphics.Host1x/Host1xClassRegisters.cs create mode 100644 src/Ryujinx.Graphics.Host1x/Host1xDevice.cs create mode 100644 src/Ryujinx.Graphics.Host1x/OpCode.cs create mode 100644 src/Ryujinx.Graphics.Host1x/Ryujinx.Graphics.Host1x.csproj create mode 100644 src/Ryujinx.Graphics.Host1x/SyncptIncrManager.cs create mode 100644 src/Ryujinx.Graphics.Host1x/ThiDevice.cs create mode 100644 src/Ryujinx.Graphics.Host1x/ThiRegisters.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/Decoder.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/H264BitStreamWriter.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/SpsAndPpsReconstruction.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodec.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodec501.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecContext.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecID.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVFrame.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVLog.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVPacket.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVRational.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodec.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodecLegacy.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/BitDepth.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/CodecErr.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Common/BitUtils.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Common/MemoryAllocator.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Common/MemoryUtil.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Constants.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/DecodeFrame.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/DecodeMv.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Decoder.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Detokenize.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Convolve.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Filter.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/IntraPred.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/InvTxfm.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Prob.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Reader.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/TxfmCommon.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Idct.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/InternalErrorException.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/InternalErrorInfo.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/LoopFilter.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Luts.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/PredCommon.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/QuantCommon.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/ReconInter.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/ReconIntra.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/TileBuffer.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/TileWorkerData.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/BModeInfo.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/BlockSize.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/Buf2D.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/FrameType.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilter.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterInfoN.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterMask.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterThresh.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/MacroBlockD.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/MacroBlockDPlane.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/ModeInfo.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/MotionVectorContext.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/Mv.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/Mv32.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvClassType.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvJointType.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvRef.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/PartitionType.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/PlaneType.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/Position.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/PredictionMode.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/RefBuffer.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/ReferenceMode.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/ScaleFactors.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/SegLvlFeatures.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/Segmentation.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/Surface.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/TileInfo.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxMode.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxSize.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxType.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/Vp9Common.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/ApplicationId.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/H264Decoder.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Image/SurfaceCache.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Image/SurfaceCommon.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Image/SurfaceReader.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Image/SurfaceWriter.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/MemoryExtensions.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/NvdecDecoderContext.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/NvdecDevice.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/NvdecRegisters.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/NvdecStatus.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/ResourceManager.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/H264/PictureInfo.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/H264/ReferenceFrame.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp8/PictureInfo.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/BackwardUpdates.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/EntropyProbs.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameFlags.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameSize.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameStats.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/LoopFilter.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/PictureInfo.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/Segmentation.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Vp8Decoder.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Vp9Decoder.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Buffer.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Constants.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Debugger.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/DrawTextureEmulation.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/FxaaPostProcessingEffect.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/IPostProcessingEffect.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/IScalingFilter.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_a.h create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_fsr1.h create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_sharpening.glsl create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fxaa.glsl create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa.hlsl create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_blend.glsl create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_edge.glsl create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_neighbour.glsl create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin create mode 100644 src/Ryujinx.Graphics.OpenGL/EnumConversion.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/FormatInfo.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/FormatTable.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Framebuffer.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Handle.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Helper/GLXHelper.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Helper/WGLHelper.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/ITextureInfo.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/Sampler.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureBase.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureCopyIncompatible.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Pipeline.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Program.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Queries/BufferedQuery.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Queries/CounterQueue.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Queries/CounterQueueEvent.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Queries/Counters.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/ResourcePool.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj create mode 100644 src/Ryujinx.Graphics.OpenGL/Sync.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/VertexArray.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Window.cs create mode 100644 src/Ryujinx.Graphics.Shader/AlphaTestOp.cs create mode 100644 src/Ryujinx.Graphics.Shader/AttributeType.cs create mode 100644 src/Ryujinx.Graphics.Shader/BufferDescriptor.cs create mode 100644 src/Ryujinx.Graphics.Shader/BufferUsageFlags.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/CodeGenParameters.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenCall.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenFSI.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenVector.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstInfo.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstType.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/IoMap.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/NumberFormatter.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/TypeConversion.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/EnumConversion.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ImageDeclaration.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/IoMap.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/OperationResult.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SamplerDeclaration.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvDelegates.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs create mode 100644 src/Ryujinx.Graphics.Shader/Constants.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/Block.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/DecodedFunction.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/DecodedProgram.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/FunctionType.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/InstDecoders.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/InstName.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/InstOp.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/InstProps.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/InstTable.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/Register.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/RegisterConsts.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/RegisterType.cs create mode 100644 src/Ryujinx.Graphics.Shader/GpuGraphicsState.cs create mode 100644 src/Ryujinx.Graphics.Shader/IGpuAccessor.cs create mode 100644 src/Ryujinx.Graphics.Shader/ILogger.cs create mode 100644 src/Ryujinx.Graphics.Shader/InputTopology.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/AttributeMap.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmit.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitBarrier.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitBitfield.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitConditionCode.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatArithmetic.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatComparison.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatMinMax.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitFlowControl.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerArithmetic.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerComparison.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerLogical.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerMinMax.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitMultifunction.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitNop.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitShift.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoArithmetic.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoMinMax.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitWarp.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Function.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IoVariable.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/StorageKind.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs create mode 100644 src/Ryujinx.Graphics.Shader/OutputTopology.cs create mode 100644 src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs create mode 100644 src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj create mode 100644 src/Ryujinx.Graphics.Shader/SamplerType.cs create mode 100644 src/Ryujinx.Graphics.Shader/SetBindingPair.cs create mode 100644 src/Ryujinx.Graphics.Shader/ShaderProgram.cs create mode 100644 src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs create mode 100644 src/Ryujinx.Graphics.Shader/ShaderStage.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/BufferDefinition.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/BufferLayout.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/IoDefinition.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/MemoryDefinition.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/StructureType.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/StructuredFunction.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs create mode 100644 src/Ryujinx.Graphics.Shader/SupportBuffer.cs create mode 100644 src/Ryujinx.Graphics.Shader/TessPatchType.cs create mode 100644 src/Ryujinx.Graphics.Shader/TessSpacing.cs create mode 100644 src/Ryujinx.Graphics.Shader/TextureDescriptor.cs create mode 100644 src/Ryujinx.Graphics.Shader/TextureFormat.cs create mode 100644 src/Ryujinx.Graphics.Shader/TextureHandle.cs create mode 100644 src/Ryujinx.Graphics.Shader/TextureUsageFlags.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/AggregateType.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/AttributeUsage.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/ControlFlowGraph.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Dominance.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/FunctionMatch.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/IoUsage.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/ConstantFolding.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/DoubleToFloat.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/RegisterUsage.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/ShaderHeader.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Ssa.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/TargetApi.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/TargetLanguage.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/TransformFeedbackOutput.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Transforms/DrawParametersReplace.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Transforms/ForcePreciseEnable.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Transforms/GeometryToCompute.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Transforms/ITransformPass.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Transforms/SharedAtomicSignedCas.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Transforms/SharedStoreSmallIntCas.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Transforms/ShufflePass.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Transforms/TransformPasses.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Transforms/VectorComponentSelect.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/TranslationOptions.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Translator.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs create mode 100644 src/Ryujinx.Graphics.Shader/VertexInfoBuffer.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/AstcDecoderException.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/BitStream128.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/Bits.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/EndPointSet.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/IntegerSequence.cs create mode 100644 src/Ryujinx.Graphics.Texture/BC6Decoder.cs create mode 100644 src/Ryujinx.Graphics.Texture/BC7Decoder.cs create mode 100644 src/Ryujinx.Graphics.Texture/BCnDecoder.cs create mode 100644 src/Ryujinx.Graphics.Texture/BCnEncoder.cs create mode 100644 src/Ryujinx.Graphics.Texture/BlockLinearConstants.cs create mode 100644 src/Ryujinx.Graphics.Texture/BlockLinearLayout.cs create mode 100644 src/Ryujinx.Graphics.Texture/Bpp12Pixel.cs create mode 100644 src/Ryujinx.Graphics.Texture/ETC2Decoder.cs create mode 100644 src/Ryujinx.Graphics.Texture/Encoders/BC7Encoder.cs create mode 100644 src/Ryujinx.Graphics.Texture/Encoders/EncodeMode.cs create mode 100644 src/Ryujinx.Graphics.Texture/LayoutConverter.cs create mode 100644 src/Ryujinx.Graphics.Texture/OffsetCalculator.cs create mode 100644 src/Ryujinx.Graphics.Texture/PixelConverter.cs create mode 100644 src/Ryujinx.Graphics.Texture/Region.cs create mode 100644 src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj create mode 100644 src/Ryujinx.Graphics.Texture/Size.cs create mode 100644 src/Ryujinx.Graphics.Texture/SizeCalculator.cs create mode 100644 src/Ryujinx.Graphics.Texture/SizeInfo.cs create mode 100644 src/Ryujinx.Graphics.Texture/Utils/BC67Tables.cs create mode 100644 src/Ryujinx.Graphics.Texture/Utils/BC67Utils.cs create mode 100644 src/Ryujinx.Graphics.Texture/Utils/BC7ModeInfo.cs create mode 100644 src/Ryujinx.Graphics.Texture/Utils/Block.cs create mode 100644 src/Ryujinx.Graphics.Texture/Utils/RgbaColor32.cs create mode 100644 src/Ryujinx.Graphics.Texture/Utils/RgbaColor8.cs create mode 100644 src/Ryujinx.Graphics.Vic/Blender.cs create mode 100644 src/Ryujinx.Graphics.Vic/Image/BufferPool.cs create mode 100644 src/Ryujinx.Graphics.Vic/Image/InputSurface.cs create mode 100644 src/Ryujinx.Graphics.Vic/Image/Pixel.cs create mode 100644 src/Ryujinx.Graphics.Vic/Image/Surface.cs create mode 100644 src/Ryujinx.Graphics.Vic/Image/SurfaceCommon.cs create mode 100644 src/Ryujinx.Graphics.Vic/Image/SurfaceReader.cs create mode 100644 src/Ryujinx.Graphics.Vic/Image/SurfaceWriter.cs create mode 100644 src/Ryujinx.Graphics.Vic/Rectangle.cs create mode 100644 src/Ryujinx.Graphics.Vic/ResourceManager.cs create mode 100644 src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj create mode 100644 src/Ryujinx.Graphics.Vic/Scaler.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/BlendingSlotStruct.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/ClearRectStruct.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/ConfigStruct.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/DeinterlaceMode.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/FrameFormat.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/LumaKeyStruct.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/MatrixStruct.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/OutputConfig.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/OutputSurfaceConfig.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/PipeConfig.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/PixelFormat.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/SlotConfig.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/SlotStruct.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/SlotSurfaceConfig.cs create mode 100644 src/Ryujinx.Graphics.Vic/VicDevice.cs create mode 100644 src/Ryujinx.Graphics.Vic/VicRegisters.cs create mode 100644 src/Ryujinx.Graphics.Video/FrameField.cs create mode 100644 src/Ryujinx.Graphics.Video/H264PictureInfo.cs create mode 100644 src/Ryujinx.Graphics.Video/IDecoder.cs create mode 100644 src/Ryujinx.Graphics.Video/IH264Decoder.cs create mode 100644 src/Ryujinx.Graphics.Video/ISurface.cs create mode 100644 src/Ryujinx.Graphics.Video/IVp9Decoder.cs create mode 100644 src/Ryujinx.Graphics.Video/Plane.cs create mode 100644 src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj create mode 100644 src/Ryujinx.Graphics.Video/Vp8PictureInfo.cs create mode 100644 src/Ryujinx.Graphics.Video/Vp9BackwardUpdates.cs create mode 100644 src/Ryujinx.Graphics.Video/Vp9EntropyProbs.cs create mode 100644 src/Ryujinx.Graphics.Video/Vp9Mv.cs create mode 100644 src/Ryujinx.Graphics.Video/Vp9MvRef.cs create mode 100644 src/Ryujinx.Graphics.Video/Vp9PictureInfo.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Auto.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/AutoFlushCounter.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BackgroundResources.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BitMap.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BitMapStruct.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BufferAllocationType.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BufferHolder.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BufferManager.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BufferMirrorRangeList.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BufferState.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BufferUsageBitmap.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/CacheByRange.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Constants.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplateUpdater.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableBuffer.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableBufferView.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableFramebuffer.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableImage.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableImageView.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableMemory.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposablePipeline.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableRenderPass.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableSampler.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/IPostProcessingEffect.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/IScalingFilter.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.glsl create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.glsl create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.glsl create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.glsl create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.glsl create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.glsl create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/SmaaConstants.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin create mode 100644 src/Ryujinx.Graphics.Vulkan/EnumConversion.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/FeedbackLoopAspects.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/FenceHelper.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/FenceHolder.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/FormatConverter.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/FormatTable.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/HelperShader.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/HostMemoryAllocator.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/IdList.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/ImageArray.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/IndexBufferState.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/MemoryAllocation.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/MemoryAllocator.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/NativeArray.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PersistentFlushBuffer.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineBase.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineFull.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineState.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineUid.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Queries/CounterQueueEvent.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Queries/Counters.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/RenderPassCacheKey.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/ResourceArray.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj create mode 100644 src/Ryujinx.Graphics.Vulkan/SamplerHolder.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Shader.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ChangeBufferStrideShaderSource.comp create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitClearAlphaFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitVertexShaderSource.vert create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearFFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearSIFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearUIFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearVertexShaderSource.vert create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyShorteningComputeShaderSource.comp create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyToNonMsComputeShaderSource.comp create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyWideningComputeShaderSource.comp create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorDrawToMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorDrawToMsVertexShaderSource.vert create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ConvertD32S8ToD24S8ShaderSource.comp create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ConvertIndexBufferShaderSource.comp create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ConvertIndirectDataShaderSource.comp create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/DepthBlitFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/DepthBlitMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/DepthDrawToMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/DepthDrawToNonMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/DepthStencilClearFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ChangeBufferStride.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorBlitClearAlphaFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorBlitFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorBlitMsFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorBlitVertex.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorClearFFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorClearSIFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorClearUIFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorClearVertex.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorCopyShorteningCompute.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorCopyToNonMsCompute.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorCopyWideningCompute.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorDrawToMsFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorDrawToMsVertex.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ConvertD32S8ToD24S8.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ConvertIndexBuffer.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ConvertIndirectData.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthBlitFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthBlitMsFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthDrawToMsFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthDrawToNonMsFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthStencilClearFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/StencilBlitFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/StencilBlitMsFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/StencilDrawToMsFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/StencilDrawToNonMsFragment.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/StencilBlitFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/StencilBlitMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/StencilDrawToMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/StencilDrawToNonMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/SpecInfo.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/StagingBuffer.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/SyncManager.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/TextureArray.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/TextureCopy.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/TextureStorage.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/TextureView.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Vendor.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VertexBufferState.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VulkanDebugMessenger.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VulkanException.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VulkanInstance.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VulkanPhysicalDevice.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Window.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/WindowBase.cs create mode 100644 src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs create mode 100644 src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs create mode 100644 src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs create mode 100644 src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs create mode 100644 src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs create mode 100644 src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.cs create mode 100644 src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.glade create mode 100644 src/Ryujinx.Gtk3/Modules/Updater/Updater.cs create mode 100644 src/Ryujinx.Gtk3/Program.cs create mode 100644 src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj create mode 100644 src/Ryujinx.Gtk3/Ryujinx.ico create mode 100644 src/Ryujinx.Gtk3/UI/Applet/ErrorAppletDialog.cs create mode 100644 src/Ryujinx.Gtk3/UI/Applet/GtkDynamicTextInputHandler.cs create mode 100644 src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs create mode 100644 src/Ryujinx.Gtk3/UI/Applet/GtkHostUITheme.cs create mode 100644 src/Ryujinx.Gtk3/UI/Applet/SwkbdAppletDialog.cs create mode 100644 src/Ryujinx.Gtk3/UI/Helper/ButtonHelper.cs create mode 100644 src/Ryujinx.Gtk3/UI/Helper/MetalHelper.cs create mode 100644 src/Ryujinx.Gtk3/UI/Helper/SortHelper.cs create mode 100644 src/Ryujinx.Gtk3/UI/Helper/ThemeHelper.cs create mode 100644 src/Ryujinx.Gtk3/UI/MainWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/MainWindow.glade create mode 100644 src/Ryujinx.Gtk3/UI/OpenGLRenderer.cs create mode 100644 src/Ryujinx.Gtk3/UI/OpenToolkitBindingsContext.cs create mode 100644 src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs create mode 100644 src/Ryujinx.Gtk3/UI/SPBOpenGLContext.cs create mode 100644 src/Ryujinx.Gtk3/UI/StatusUpdatedEventArgs.cs create mode 100644 src/Ryujinx.Gtk3/UI/VulkanRenderer.cs create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.Designer.cs create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/GtkDialog.cs create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/GtkInputDialog.cs create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.cs create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.glade create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/RawInputToTextEntry.cs create mode 100644 src/Ryujinx.Gtk3/UI/Widgets/UserErrorDialog.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/AboutWindow.Designer.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/AboutWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.Designer.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/CheatWindow.glade create mode 100644 src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.glade create mode 100644 src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/DlcWindow.glade create mode 100644 src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade create mode 100644 src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.glade create mode 100644 src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.Designer.cs create mode 100644 src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs create mode 100644 src/Ryujinx.HLE.Generators/CodeGenerator.cs create mode 100644 src/Ryujinx.HLE.Generators/IpcServiceGenerator.cs create mode 100644 src/Ryujinx.HLE.Generators/Ryujinx.HLE.Generators.csproj create mode 100644 src/Ryujinx.HLE.Generators/ServiceSyntaxReceiver.cs create mode 100644 src/Ryujinx.HLE/AssemblyInfo.cs create mode 100644 src/Ryujinx.HLE/Exceptions/GuestBrokeExecutionException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/InternalServiceException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/InvalidFirmwarePackageException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/InvalidNpdmException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/InvalidStructLayoutException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/InvalidSystemResourceException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/ServiceNotImplementedException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/TamperCompilationException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/TamperExecutionException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/UndefinedInstructionException.cs create mode 100644 src/Ryujinx.HLE/FileSystem/ContentManager.cs create mode 100644 src/Ryujinx.HLE/FileSystem/ContentMetaData.cs create mode 100644 src/Ryujinx.HLE/FileSystem/ContentPath.cs create mode 100644 src/Ryujinx.HLE/FileSystem/EncryptedFileSystemCreator.cs create mode 100644 src/Ryujinx.HLE/FileSystem/LocationEntry.cs create mode 100644 src/Ryujinx.HLE/FileSystem/SystemVersion.cs create mode 100644 src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs create mode 100644 src/Ryujinx.HLE/HLEConfiguration.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/AppletManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/BootDisplayKind.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/BrowserArgument.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutput.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutputType.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/DocumentKind.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/LeftStickMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/WebArgHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLVType.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/WebCommonReturnValue.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/WebExitReason.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/CommonArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUIArgs.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgPrivate.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgV7.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgVPre7.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportResultInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Error/ApplicationErrorArg.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/IApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectResult.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/CJKCharacterValidation.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardResponse.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardState.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineResponses.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidButtonFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardCalcFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardInputMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMiniaturizationMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardResult.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/NumericCharacterValidation.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnA.png create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnA.svg create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.png create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.svg create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_KeyF6.png create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_KeyF6.svg create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Logo_Ryujinx.png create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppear.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppearEx.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalc.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalcEx.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCustomizeDic.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardDictSet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardInitialize.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIArgs.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIState.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUserWord.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TRef.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TimedAction.cs create mode 100644 src/Ryujinx.HLE/HOS/ArmProcessContext.cs create mode 100644 src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs create mode 100644 src/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs create mode 100644 src/Ryujinx.HLE/HOS/Horizon.cs create mode 100644 src/Ryujinx.HLE/HOS/HorizonFsClient.cs create mode 100644 src/Ryujinx.HLE/HOS/IdDictionary.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/KSystemControl.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/MemoryArrange.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/MemorySize.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/KernelConstants.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/KernelContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockSlabManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KPageBitmap.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KPageHeap.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KScopedPageList.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryFillValue.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/SharedMemoryStorage.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/MemoryInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/PointerSizedAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcImplAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ThreadContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs create mode 100644 src/Ryujinx.HLE/HOS/LibHacHorizonManager.cs create mode 100644 src/Ryujinx.HLE/HOS/ModLoader.cs create mode 100644 src/Ryujinx.HLE/HOS/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/ServiceCtx.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForApplication.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForSystemService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfileEditor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AsyncContext/AsyncExecution.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForAdministrator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForSystemService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncNetworkServiceLicenseKindContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/IBaasAccessTokenAccessor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/ProfilesJsonSerializerContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountServiceFlag.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/NetworkServiceLicenseKind.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfileJson.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Dauth/IService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/AppletStandalone.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/IProcessWindingController.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAppletCommonFunctions.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AlbumReportOption.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AppletMessage.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/WirelessPriorityMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletIdentityInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletProcessLaunchReason.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/IManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/IManagerPrivileged.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/ISession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/ISystemManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/ManagerServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/PerformanceState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/SessionServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/SystemManagerServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/Types/CpuBoostMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceConfiguration.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bgtc/IStateControlService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bgtc/ITaskService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothDriver/BluetoothEventManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmUser/IBtmUserCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtm.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmDebug.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmUser.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/BluetoothManager/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Cec/ICecManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/CommandCmifAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/CommandTIpcAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/DisposableIpcService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/DummyService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ectx/IReaderForSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForApplication.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Erpt/ISession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Eupld/IControl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Eupld/IRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fatal/IPrivateService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext32.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext64.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fatal/Types/FatalPolicy.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/LazyFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxyForLoader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/IProgramRegistry.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/Types/FileSystemType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Grc/IGrcService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Grc/IRemoteVideoTransfer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/SixAxisInput.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/ImageTransferProcessorState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/IrCameraHandle.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedClusteringProcessorConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedImageTransferProcessorConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedMomentProcessorConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedTeraPluginProcessorConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/AppletFooterUiType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/HidVector.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/BusHandle.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/BusType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerKeys.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadColor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadIdType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/PlayerIndex.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/NpadJoyHoldType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AnalogStickState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadButton.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKey.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKeyShift.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseButton.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/DeviceType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadBatteryLevel.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadButton.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadColorAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadFullKeyColorState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadInternalState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyAssignmentMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyColorState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLarkType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLuciaType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadStyleTag.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemButtonProperties.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemProperties.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/IpcService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/ISystemServiceCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/LdnConst.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/AcceptPolicy.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/AddressEntry.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/AddressList.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/CommonNetworkInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/DisconnectReason.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/IntentId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/LdnNetworkInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeLatestUpdate.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeLatestUpdateFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/ScanFilter.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/ScanFilterFlag.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityParameter.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/Ssid.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/UserConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/AccessPoint.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/INetworkClient.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnDisabledClient.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/LanDiscovery.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/LanProtocol.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/LdnMitmClient.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnSocket.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnTcpSocket.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpClient.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyUdpServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Types/LanPacketHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Types/LanPacketType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/NetworkChangeEventArgs.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Station.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/ConnectPrivateRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/ConnectRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/CreateAccessPointPrivateRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/CreateAccessPointRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/NetworkError.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/NetworkErrorMessage.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Loader/IDebugMonitorInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Loader/IProcessManagerInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Loader/IShellInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Loader/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mig/IService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/DatabaseSessionMetadata.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Helper.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/IImageDatabaseService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/IStaticService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/Age.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/BeardType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfoElement.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/CommonColor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/CreateId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/DefaultMii.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/EyeType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/EyebrowType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineColor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineMake.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineWrinkle.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/FontRegion.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/Gender.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/GlassType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/HairFlip.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/HairType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/IElement.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/IStoredData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/MoleType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/MouthType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/MustacheType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/Nickname.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/NintendoFigurineDatabase.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/NoseType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/Race.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/RandomMiiConstants.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/Source.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/SourceFlag.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/SpecialMiiKeyCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreDataElement.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/Ver3StoreData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/UtilityImpl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mnpp/IServiceForApplication.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mnpp/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/INfc.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/NfcPermissionLevel.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/AmiiboConstants.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/CommonInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/DeviceType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/ModelInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/MountTarget.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDevice.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDeviceState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpPermissionLevel.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/RegisterInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/State.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/TagInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ngct/IService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ngct/IServiceWithManagementApi.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ngct/NgctServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/GeneralServiceManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/Types/GeneralServiceDetail.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/DnsSetting.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionStatus.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpAddressSetting.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpSettingData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpV4Address.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/NetworkProfileData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/ProxySetting.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/WirelessSettingData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/INetworkInstallManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessSystemInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAsync.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Npns/INpnsUser.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IContentsServiceManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IPurchaseEventManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/Aoc/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/IDevelopInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/ChannelInitialization.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/NumVsmsArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostProfGpu/NvHostProfGpuDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapIdDictionary.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForApplication.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Olsc/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcie/ILogManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcie/IManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IBoardPowerControlManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IRtcManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/ClkrstManager/IClkrstSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IArbitrationManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IClkrstManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/IPcvService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Rgltr/IRegulatorManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Rtc/IRtcManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Types/DeviceCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pm/IBootModeInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pm/IInformationInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pm/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Fan/IManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/IDebugger.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/ISession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Pcm/IManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Psm/Types/ChargerType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Tc/IManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ro/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ro/Types/NRRCertification.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ro/Types/NroInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Avm/IAvmService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/INotifyService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pl/Types/SharedFontType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/ServerBase.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/ServiceAttributes.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/IFactorySettingsServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/IFirmwareDebugSettingsServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/ISettingsServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/KeyCodeMaps.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/Types/PlatformRegion.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sm/IManagerInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sm/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WSAError.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMMsgHdr.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/LinuxError.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/TimeVal.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/ApplicationServerEnvironmentType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/Types/ConfigItem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/Types/DramId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/Types/SmcResult.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnection.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnectionBase.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/BuiltInCertificateInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/CaCertificateId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/CertificateFormat.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/IoMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/OptionType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/SessionCacheMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/SslVersion.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/TrustedCertStatus.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/VerifyOption.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferItem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ContinuousAdjustmentTimePoint.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/IAlarmService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarAdditionalInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarTime.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeTypeInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeZoneRule.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TzifHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/AndroidSurfaceComposerClient.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DestinationScalingMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DisplayInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/SourceScalingMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/Types/ViServiceType.cs create mode 100644 src/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs create mode 100644 src/Ryujinx.HLE/HOS/SystemState/ColorSet.cs create mode 100644 src/Ryujinx.HLE/HOS/SystemState/KeyboardLayout.cs create mode 100644 src/Ryujinx.HLE/HOS/SystemState/RegionCode.cs create mode 100644 src/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs create mode 100644 src/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs create mode 100644 src/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/Arithmetic.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/BeginConditionalBlock.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/DebugLog.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/EndConditionalBlock.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/KeyPressConditional.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LegacyArithmetic.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithConstant.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/MemoryConditional.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/PauseProcess.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ReadOrWriteStaticRegister.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/RegisterConditional.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ResumeProcess.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegister.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegisterWithMask.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StartEndLoop.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToAddress.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreRegisterToMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeType.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Comparison.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CompilationContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/CondEQ.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGE.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGT.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLE.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLT.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/CondNE.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/ICondition.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/InputMask.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/InstructionHelper.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/OperationBlock.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/Block.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/ForBlock.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/IOperand.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/IOperation.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpAdd.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpAnd.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpLog.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpLsh.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpMov.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpMul.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpNot.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpOr.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpProcCtrl.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpRsh.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpSub.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpXor.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Parameter.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Pointer.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Register.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Value.cs create mode 100644 src/Ryujinx.HLE/HOS/TamperMachine.cs create mode 100644 src/Ryujinx.HLE/HOS/UserChannelPersistence.cs create mode 100644 src/Ryujinx.HLE/Homebrew.npdm create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfDynamic.cs create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfDynamicTag.cs create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfSymbolBinding.cs create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfSymbolType.cs create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfSymbolVisibility.cs create mode 100644 src/Ryujinx.HLE/Loaders/Executables/IExecutable.cs create mode 100644 src/Ryujinx.HLE/Loaders/Executables/KipExecutable.cs create mode 100644 src/Ryujinx.HLE/Loaders/Executables/NroExecutable.cs create mode 100644 src/Ryujinx.HLE/Loaders/Executables/NsoExecutable.cs create mode 100644 src/Ryujinx.HLE/Loaders/Mods/IPSPatcher.cs create mode 100644 src/Ryujinx.HLE/Loaders/Mods/IPSwitchPatcher.cs create mode 100644 src/Ryujinx.HLE/Loaders/Mods/MemPatch.cs create mode 100644 src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs create mode 100644 src/Ryujinx.HLE/Loaders/Npdm/ACID.cs create mode 100644 src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs create mode 100644 src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs create mode 100644 src/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs create mode 100644 src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs create mode 100644 src/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs create mode 100644 src/Ryujinx.HLE/MemoryConfiguration.cs create mode 100644 src/Ryujinx.HLE/PerformanceStatistics.cs create mode 100644 src/Ryujinx.HLE/Ryujinx.HLE.csproj create mode 100644 src/Ryujinx.HLE/Switch.cs create mode 100644 src/Ryujinx.HLE/UI/DynamicTextChangedHandler.cs create mode 100644 src/Ryujinx.HLE/UI/IDynamicTextInputHandler.cs create mode 100644 src/Ryujinx.HLE/UI/IHostUIHandler.cs create mode 100644 src/Ryujinx.HLE/UI/IHostUITheme.cs create mode 100644 src/Ryujinx.HLE/UI/Input/NpadButtonHandler.cs create mode 100644 src/Ryujinx.HLE/UI/Input/NpadReader.cs create mode 100644 src/Ryujinx.HLE/UI/KeyPressedHandler.cs create mode 100644 src/Ryujinx.HLE/UI/KeyReleasedHandler.cs create mode 100644 src/Ryujinx.HLE/UI/RenderingSurfaceInfo.cs create mode 100644 src/Ryujinx.HLE/UI/ThemeColor.cs create mode 100644 src/Ryujinx.HLE/Utilities/PartitionFileSystemUtils.cs create mode 100644 src/Ryujinx.HLE/Utilities/StringUtils.cs create mode 100644 src/Ryujinx.Headless.SDL2/HeadlessDynamicTextInputHandler.cs create mode 100644 src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs create mode 100644 src/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs create mode 100644 src/Ryujinx.Headless.SDL2/Options.cs create mode 100644 src/Ryujinx.Headless.SDL2/Program.cs create mode 100644 src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj create mode 100644 src/Ryujinx.Headless.SDL2/Ryujinx.bmp create mode 100644 src/Ryujinx.Headless.SDL2/SDL2Mouse.cs create mode 100644 src/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs create mode 100644 src/Ryujinx.Headless.SDL2/StatusUpdatedEventArgs.cs create mode 100644 src/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs create mode 100644 src/Ryujinx.Headless.SDL2/WindowBase.cs create mode 100644 src/Ryujinx.Horizon.Common/IExternalEvent.cs create mode 100644 src/Ryujinx.Horizon.Common/ISyscallApi.cs create mode 100644 src/Ryujinx.Horizon.Common/IThreadContext.cs create mode 100644 src/Ryujinx.Horizon.Common/InvalidResultException.cs create mode 100644 src/Ryujinx.Horizon.Common/KernelResult.cs create mode 100644 src/Ryujinx.Horizon.Common/OnScopeExit.cs create mode 100644 src/Ryujinx.Horizon.Common/Result.cs create mode 100644 src/Ryujinx.Horizon.Common/ResultNames.cs create mode 100644 src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj create mode 100644 src/Ryujinx.Horizon.Common/ThreadTerminatedException.cs create mode 100644 src/Ryujinx.Horizon.Generators/CodeGenerator.cs create mode 100644 src/Ryujinx.Horizon.Generators/Hipc/CommandArgType.cs create mode 100644 src/Ryujinx.Horizon.Generators/Hipc/CommandInterface.cs create mode 100644 src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs create mode 100644 src/Ryujinx.Horizon.Generators/Hipc/HipcSyntaxReceiver.cs create mode 100644 src/Ryujinx.Horizon.Generators/Ryujinx.Horizon.Generators.csproj create mode 100644 src/Ryujinx.Horizon.Kernel.Generators/CodeGenerator.cs create mode 100644 src/Ryujinx.Horizon.Kernel.Generators/Ryujinx.Horizon.Kernel.Generators.csproj create mode 100644 src/Ryujinx.Horizon.Kernel.Generators/SyscallGenerator.cs create mode 100644 src/Ryujinx.Horizon.Kernel.Generators/SyscallSyntaxReceiver.cs create mode 100644 src/Ryujinx.Horizon/Arp/ArpIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Arp/ArpMain.cs create mode 100644 src/Ryujinx.Horizon/Arp/Ipc/Reader.cs create mode 100644 src/Ryujinx.Horizon/Arp/Ipc/Registrar.cs create mode 100644 src/Ryujinx.Horizon/Arp/Ipc/UnregistrationNotifier.cs create mode 100644 src/Ryujinx.Horizon/Arp/Ipc/Updater.cs create mode 100644 src/Ryujinx.Horizon/Arp/Ipc/Writer.cs create mode 100644 src/Ryujinx.Horizon/Audio/AudioMain.cs create mode 100644 src/Ryujinx.Horizon/Audio/AudioManagers.cs create mode 100644 src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Audio/HwopusMain.cs create mode 100644 src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Bcat/BcatMain.cs create mode 100644 src/Ryujinx.Horizon/Bcat/BcatResult.cs create mode 100644 src/Ryujinx.Horizon/Bcat/BcatServerManager.cs create mode 100644 src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator.cs create mode 100644 src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/BcatService.cs create mode 100644 src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheDirectoryService.cs create mode 100644 src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheFileService.cs create mode 100644 src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheProgressService.cs create mode 100644 src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheStorageService.cs create mode 100644 src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/Types/DeliveryCacheProgressImpl.cs create mode 100644 src/Ryujinx.Horizon/Bcat/Types/BcatPortIndex.cs create mode 100644 src/Ryujinx.Horizon/Bcat/Types/BcatServicePermissionLevel.cs create mode 100644 src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Friends/FriendsMain.cs create mode 100644 src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs create mode 100644 src/Ryujinx.Horizon/Friends/FriendsServerManager.cs create mode 100644 src/Ryujinx.Horizon/HeapAllocator.cs create mode 100644 src/Ryujinx.Horizon/HorizonOptions.cs create mode 100644 src/Ryujinx.Horizon/HorizonStatic.cs create mode 100644 src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Hshl/HshlMain.cs create mode 100644 src/Ryujinx.Horizon/Hshl/Ipc/Manager.cs create mode 100644 src/Ryujinx.Horizon/Hshl/Ipc/SetterManager.cs create mode 100644 src/Ryujinx.Horizon/IService.cs create mode 100644 src/Ryujinx.Horizon/Ins/InsIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Ins/InsMain.cs create mode 100644 src/Ryujinx.Horizon/Ins/Ipc/ReceiverManager.cs create mode 100644 src/Ryujinx.Horizon/Ins/Ipc/SenderManager.cs create mode 100644 src/Ryujinx.Horizon/Lbl/Ipc/LblController.cs create mode 100644 src/Ryujinx.Horizon/Lbl/LblIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Lbl/LblMain.cs create mode 100644 src/Ryujinx.Horizon/LibHacResultExtensions.cs create mode 100644 src/Ryujinx.Horizon/LogManager/Ipc/LmLogger.cs create mode 100644 src/Ryujinx.Horizon/LogManager/Ipc/LogService.cs create mode 100644 src/Ryujinx.Horizon/LogManager/LmIpcServer.cs create mode 100644 src/Ryujinx.Horizon/LogManager/LmMain.cs create mode 100644 src/Ryujinx.Horizon/LogManager/Types/LogPacket.cs create mode 100644 src/Ryujinx.Horizon/MmNv/Ipc/Request.cs create mode 100644 src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs create mode 100644 src/Ryujinx.Horizon/MmNv/MmNvMain.cs create mode 100644 src/Ryujinx.Horizon/Ngc/Ipc/Service.cs create mode 100644 src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Ngc/NgcMain.cs create mode 100644 src/Ryujinx.Horizon/Ovln/Ipc/ReceiverService.cs create mode 100644 src/Ryujinx.Horizon/Ovln/Ipc/SenderService.cs create mode 100644 src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Ovln/OvlnMain.cs create mode 100644 src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs create mode 100644 src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Prepo/PrepoMain.cs create mode 100644 src/Ryujinx.Horizon/Prepo/PrepoResult.cs create mode 100644 src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs create mode 100644 src/Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs create mode 100644 src/Ryujinx.Horizon/Prepo/Types/PrepoServicePermissionLevel.cs create mode 100644 src/Ryujinx.Horizon/Psc/Ipc/PmControl.cs create mode 100644 src/Ryujinx.Horizon/Psc/Ipc/PmService.cs create mode 100644 src/Ryujinx.Horizon/Psc/Ipc/PmStateLock.cs create mode 100644 src/Ryujinx.Horizon/Psc/PscIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Psc/PscMain.cs create mode 100644 src/Ryujinx.Horizon/Ptm/Ipc/MeasurementServer.cs create mode 100644 src/Ryujinx.Horizon/Ptm/Ipc/Session.cs create mode 100644 src/Ryujinx.Horizon/Ptm/TsIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Ptm/TsMain.cs create mode 100644 src/Ryujinx.Horizon/Ryujinx.Horizon.csproj create mode 100644 src/Ryujinx.Horizon/Sdk/Account/IEmulatorAccountManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Account/Nickname.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Account/Uid.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/ApplicationCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/ApplicationKind.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/ApplicationLaunchProperty.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/ApplicationProcessProperty.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/ArpApi.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/ArpResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstance.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstanceManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/IReader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/IRegistrar.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/IUnregistrationNotifier.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/IUpdater.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Arp/IWriter.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocolName.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererParameterInternal.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameter.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameterInternal.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRendererManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorderManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Bcat/IBcatService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheDirectoryService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheFileService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheProgressService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheStorageService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Bcat/IServiceCreator.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs create mode 100644 src/Ryujinx.Horizon/Sdk/DebugUtil.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/BlockedUserImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendCandidateImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendDetailedInfoImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationForViewerImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationGroupImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendRequestImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendSettingImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/DaemonSuspendSessionService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendsServicePermissionLevel.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IDaemonSuspendSessionService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IFriendService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/INotificationService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IServiceCreator.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventHandler.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventType.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/PresenceStatusFilter.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/ServiceCreator.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedFriendFilter.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedNotificationInfo.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/NintendoNetworkIdFriendImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/PlayHistoryImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/PresenceStatus.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileExtraImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/SnsAccountFriendImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceViewImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/UserSettingImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalog.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalogId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FacedFriendRequestRegistrationKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGameModeDescription.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGroupId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/InAppScreenName.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/MiiImageUrlParam.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/NintendoNetworkIdUserInfo.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryRegistrationKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryStatistics.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Relationship.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/SnsAccountLinkage.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/SnsAccountProfile.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Url.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Fs/FileHandle.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Fs/FsResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Fs/IFsClient.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Fs/OpenMode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Hshl/IManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Hshl/ISetterManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ins/IReceiverManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ins/ISenderManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lbl/ILblController.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lbl/LblApi.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lm/ILmLogger.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lm/ILogService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/MmNv/IRequest.cs create mode 100644 src/Ryujinx.Horizon/Sdk/MmNv/Module.cs create mode 100644 src/Ryujinx.Horizon/Sdk/MmNv/Session.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ncm/StorageId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/AhoCorasick.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/BinaryReader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/BitVector32.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/Bp.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/BpNode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/CompressedArray.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/ContentsReader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/EmbeddedTries.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchCheckState.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchDelimitedState.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchRangeList.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchRangeListState.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchSimilarFormState.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchState.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/ProfanityFilter.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/ProfanityFilterBase.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/Sbv.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/SbvRank.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/SbvSelect.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/Set.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/SimilarFormTable.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/SparseSet.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/Utf8ParseResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/Utf8Text.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/Detail/Utf8Util.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/INgcService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/MaskMode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/NgcResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/ProfanityFilterFlags.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/ProfanityFilterOption.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ngc/SkipMode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ns/ApplicationControlProperty.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/Event.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ovln/IReceiverService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ovln/ISenderService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Prepo/IPrepoService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Psc/IPmControl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Psc/IPmService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Psc/IPmStateLock.cs create mode 100644 src/Ryujinx.Horizon/Sdk/ServiceUtil.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/BatteryLot.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerOffset.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerScale.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcdsaCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsRootCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickFactoryCalibration.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickModelParameter.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/BdAddress.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/ConfigurationId1.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/ConsoleSixAxisSensorHorizontalOffset.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/CountryCode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeOffset.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeScale.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/MacAddress.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/SerialNumber.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/SpeakerParameter.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/SslCertificate.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/SslKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Language.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/LanguageCode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/SettingsItemKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/SettingsName.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AccountNotificationSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AccountOnlineStorageSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AccountSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AllowedSslHost.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AnalogStickUserCalibration.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AppletLaunchFlag.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AudioVolume.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettingsEx.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/BlePairingSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/BluetoothDevicesSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigRegisteredSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationBias.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationGain.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularAcceleration.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityBias.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityGain.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityTimeBias.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/DataDeletionSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/DeviceNickName.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/Edid.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/EulaVersion.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/FatalDirtyFlag.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersion.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersionDigest.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/HomeMenuScheme.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/HostFsMountPoint.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/InitialLaunchSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/NetworkSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/NotificationSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerLegacySettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/PtmFuelGaugeParameter.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/RebootlessSystemUpdateVersion.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/SerialNumber.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ServiceDiscoveryControlSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/SleepSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/TelemetryDirtyFlag.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ThemeId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ThemeSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/TvSettings.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/SfResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sm/IManagerService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sm/IUserService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sm/SmApi.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Srepo/ISrepoService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ts/DeviceCode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ts/IMeasurementServer.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ts/ISession.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ts/Location.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IClientRootSession.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IDsRootSession.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IPdCradleManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IPdManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IPdManufactureManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IPmObserverService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IPmService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Usb/IQdbManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Wlan/IDetectManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Wlan/IGeneralServiceCreator.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Wlan/IInfraManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Wlan/ILocalGetActionFrame.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Wlan/ILocalGetFrame.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Wlan/ILocalManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Wlan/IPrivateServiceCreator.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Wlan/ISfDriverServiceCreator.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Wlan/ISocketGetFrame.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Wlan/ISocketManager.cs create mode 100644 src/Ryujinx.Horizon/ServiceEntry.cs create mode 100644 src/Ryujinx.Horizon/ServiceTable.cs create mode 100644 src/Ryujinx.Horizon/Sm/Impl/ServiceInfo.cs create mode 100644 src/Ryujinx.Horizon/Sm/Impl/ServiceManager.cs create mode 100644 src/Ryujinx.Horizon/Sm/Ipc/ManagerService.cs create mode 100644 src/Ryujinx.Horizon/Sm/Ipc/UserService.cs create mode 100644 src/Ryujinx.Horizon/Sm/SmMain.cs create mode 100644 src/Ryujinx.Horizon/Sm/SmResult.cs create mode 100644 src/Ryujinx.Horizon/Sm/SmServerManager.cs create mode 100644 src/Ryujinx.Horizon/Sm/Types/SmPortIndex.cs create mode 100644 src/Ryujinx.Horizon/Srepo/Ipc/SrepoService.cs create mode 100644 src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Srepo/SrepoMain.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/ClientRootSession.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/DsRootSession.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/PdCradleManager.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/PdManager.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/PdManufactureManager.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/PmObserverService.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/PmService.cs create mode 100644 src/Ryujinx.Horizon/Usb/Ipc/QdbManager.cs create mode 100644 src/Ryujinx.Horizon/Usb/UsbIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Usb/UsbMain.cs create mode 100644 src/Ryujinx.Horizon/Wlan/Ipc/DetectManager.cs create mode 100644 src/Ryujinx.Horizon/Wlan/Ipc/GeneralServiceCreator.cs create mode 100644 src/Ryujinx.Horizon/Wlan/Ipc/InfraManager.cs create mode 100644 src/Ryujinx.Horizon/Wlan/Ipc/LocalGetActionFrame.cs create mode 100644 src/Ryujinx.Horizon/Wlan/Ipc/LocalGetFrame.cs create mode 100644 src/Ryujinx.Horizon/Wlan/Ipc/LocalManager.cs create mode 100644 src/Ryujinx.Horizon/Wlan/Ipc/PrivateServiceCreator.cs create mode 100644 src/Ryujinx.Horizon/Wlan/Ipc/SfDriverServiceCreator.cs create mode 100644 src/Ryujinx.Horizon/Wlan/Ipc/SocketGetFrame.cs create mode 100644 src/Ryujinx.Horizon/Wlan/Ipc/SocketManager.cs create mode 100644 src/Ryujinx.Horizon/Wlan/WlanIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Wlan/WlanMain.cs create mode 100644 src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj create mode 100644 src/Ryujinx.Input.SDL2/SDL2Gamepad.cs create mode 100644 src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs create mode 100644 src/Ryujinx.Input.SDL2/SDL2Keyboard.cs create mode 100644 src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs create mode 100644 src/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs create mode 100644 src/Ryujinx.Input/Assigner/IButtonAssigner.cs create mode 100644 src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs create mode 100644 src/Ryujinx.Input/Button.cs create mode 100644 src/Ryujinx.Input/ButtonType.cs create mode 100644 src/Ryujinx.Input/GamepadButtonInputId.cs create mode 100644 src/Ryujinx.Input/GamepadFeaturesFlag.cs create mode 100644 src/Ryujinx.Input/GamepadStateSnapshot.cs create mode 100644 src/Ryujinx.Input/HLE/InputManager.cs create mode 100644 src/Ryujinx.Input/HLE/NpadController.cs create mode 100644 src/Ryujinx.Input/HLE/NpadManager.cs create mode 100644 src/Ryujinx.Input/HLE/TouchScreenManager.cs create mode 100644 src/Ryujinx.Input/IGamepad.cs create mode 100644 src/Ryujinx.Input/IGamepadDriver.cs create mode 100644 src/Ryujinx.Input/IKeyboard.cs create mode 100644 src/Ryujinx.Input/IMouse.cs create mode 100644 src/Ryujinx.Input/Key.cs create mode 100644 src/Ryujinx.Input/KeyboardStateSnapshot.cs create mode 100644 src/Ryujinx.Input/Motion/CemuHook/Client.cs create mode 100644 src/Ryujinx.Input/Motion/CemuHook/Protocol/ControllerData.cs create mode 100644 src/Ryujinx.Input/Motion/CemuHook/Protocol/ControllerInfo.cs create mode 100644 src/Ryujinx.Input/Motion/CemuHook/Protocol/Header.cs create mode 100644 src/Ryujinx.Input/Motion/CemuHook/Protocol/MessageType.cs create mode 100644 src/Ryujinx.Input/Motion/CemuHook/Protocol/SharedResponse.cs create mode 100644 src/Ryujinx.Input/Motion/MotionInput.cs create mode 100644 src/Ryujinx.Input/Motion/MotionSensorFilter.cs create mode 100644 src/Ryujinx.Input/MotionInputId.cs create mode 100644 src/Ryujinx.Input/MouseButton.cs create mode 100644 src/Ryujinx.Input/MouseStateSnapshot.cs create mode 100644 src/Ryujinx.Input/Ryujinx.Input.csproj create mode 100644 src/Ryujinx.Input/StickInputId.cs create mode 100644 src/Ryujinx.Memory/AddressSpaceManager.cs create mode 100644 src/Ryujinx.Memory/BytesReadOnlySequenceSegment.cs create mode 100644 src/Ryujinx.Memory/IRefCounted.cs create mode 100644 src/Ryujinx.Memory/IVirtualMemoryManager.cs create mode 100644 src/Ryujinx.Memory/IWritableBlock.cs create mode 100644 src/Ryujinx.Memory/InvalidAccessHandler.cs create mode 100644 src/Ryujinx.Memory/InvalidMemoryRegionException.cs create mode 100644 src/Ryujinx.Memory/MemoryAllocationFlags.cs create mode 100644 src/Ryujinx.Memory/MemoryBlock.cs create mode 100644 src/Ryujinx.Memory/MemoryConstants.cs create mode 100644 src/Ryujinx.Memory/MemoryManagement.cs create mode 100644 src/Ryujinx.Memory/MemoryManagementUnix.cs create mode 100644 src/Ryujinx.Memory/MemoryManagementWindows.cs create mode 100644 src/Ryujinx.Memory/MemoryManagerUnixHelper.cs create mode 100644 src/Ryujinx.Memory/MemoryMapFlags.cs create mode 100644 src/Ryujinx.Memory/MemoryNotContiguousException.cs create mode 100644 src/Ryujinx.Memory/MemoryPermission.cs create mode 100644 src/Ryujinx.Memory/MemoryProtectionException.cs create mode 100644 src/Ryujinx.Memory/NativeMemoryManager.cs create mode 100644 src/Ryujinx.Memory/PageTable.cs create mode 100644 src/Ryujinx.Memory/Range/HostMemoryRange.cs create mode 100644 src/Ryujinx.Memory/Range/IMultiRangeItem.cs create mode 100644 src/Ryujinx.Memory/Range/INonOverlappingRange.cs create mode 100644 src/Ryujinx.Memory/Range/IRange.cs create mode 100644 src/Ryujinx.Memory/Range/MemoryRange.cs create mode 100644 src/Ryujinx.Memory/Range/MultiRange.cs create mode 100644 src/Ryujinx.Memory/Range/MultiRangeList.cs create mode 100644 src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs create mode 100644 src/Ryujinx.Memory/Range/RangeList.cs create mode 100644 src/Ryujinx.Memory/Ryujinx.Memory.csproj create mode 100644 src/Ryujinx.Memory/Tracking/AbstractRegion.cs create mode 100644 src/Ryujinx.Memory/Tracking/BitMap.cs create mode 100644 src/Ryujinx.Memory/Tracking/ConcurrentBitmap.cs create mode 100644 src/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs create mode 100644 src/Ryujinx.Memory/Tracking/IRegionHandle.cs create mode 100644 src/Ryujinx.Memory/Tracking/MemoryTracking.cs create mode 100644 src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs create mode 100644 src/Ryujinx.Memory/Tracking/PreciseRegionSignal.cs create mode 100644 src/Ryujinx.Memory/Tracking/RegionFlags.cs create mode 100644 src/Ryujinx.Memory/Tracking/RegionHandle.cs create mode 100644 src/Ryujinx.Memory/Tracking/RegionSignal.cs create mode 100644 src/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs create mode 100644 src/Ryujinx.Memory/Tracking/VirtualRegion.cs create mode 100644 src/Ryujinx.Memory/VirtualMemoryManagerBase.cs create mode 100644 src/Ryujinx.Memory/WindowsShared/MappingTree.cs create mode 100644 src/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs create mode 100644 src/Ryujinx.Memory/WindowsShared/WindowsApi.cs create mode 100644 src/Ryujinx.Memory/WindowsShared/WindowsApiException.cs create mode 100644 src/Ryujinx.Memory/WindowsShared/WindowsFlags.cs create mode 100644 src/Ryujinx.Memory/WritableRegion.cs create mode 100644 src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj create mode 100644 src/Ryujinx.SDL2.Common/SDL2Driver.cs create mode 100644 src/Ryujinx.ShaderTools/Program.cs create mode 100644 src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj create mode 100644 src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs create mode 100644 src/Ryujinx.Tests.Memory/MultiRegionTrackingTests.cs create mode 100644 src/Ryujinx.Tests.Memory/Ryujinx.Tests.Memory.csproj create mode 100644 src/Ryujinx.Tests.Memory/Tests.cs create mode 100644 src/Ryujinx.Tests.Memory/TrackingTests.cs create mode 100644 src/Ryujinx.Tests.Unicorn/IndexedProperty.cs create mode 100644 src/Ryujinx.Tests.Unicorn/MemoryPermission.cs create mode 100644 src/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj create mode 100644 src/Ryujinx.Tests.Unicorn/SimdValue.cs create mode 100644 src/Ryujinx.Tests.Unicorn/UnicornAArch32.cs create mode 100644 src/Ryujinx.Tests.Unicorn/UnicornAArch64.cs create mode 100644 src/Ryujinx.Tests/.runsettings create mode 100644 src/Ryujinx.Tests/Audio/Renderer/AudioRendererConfigurationTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/BehaviourParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/BiquadFilterParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Common/UpdateDataHeaderTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Common/VoiceUpdateStateTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Common/WaveBufferTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Dsp/ResamplerTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Dsp/UpsamplerTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/MemoryPoolParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/BehaviourErrorInfoOutStatusTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/AuxParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BufferMixerParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/CompressorParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/DelayParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterStatisticsTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/Reverb3dParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/ReverbParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdateTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/MixParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceInParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceOutStatusTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/RendererInfoOutStatusTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/CircularBufferParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/DeviceParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/SinkInParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/SinkOutStatusTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/SplitterInParamHeaderTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/AddressInfoTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/MemoryPoolStateTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/MixStateTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/PoolMapperTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/SplitterStateTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/VoiceChannelResourceTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/VoiceStateTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/WaveBufferTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/VoiceChannelResourceInParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/VoiceInParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/VoiceOutStatusTests.cs create mode 100644 src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs create mode 100644 src/Ryujinx.Tests/Cpu/Arm64CodeGenCommonTests.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuContext.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTest.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTest32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAlu.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAlu32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAluBinary.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAluBinary32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAluImm.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAluImm32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAluRs.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAluRs32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAluRx.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestBf32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestBfm.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestCsel.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestMisc.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestMisc32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestMov.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestMul.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestMul32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimd.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimd32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdCrypto.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdCrypto32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdExt.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdFcond.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdFmov.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdImm.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdLogical32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdMemory32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdMov32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdRegElem.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdRegElem32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdRegElemF.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdShImm32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdTbl.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSystem.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestT32Alu.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestT32Flow.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestT32Mem.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestThumb.cs create mode 100644 src/Ryujinx.Tests/Cpu/EnvironmentTests.cs create mode 100644 src/Ryujinx.Tests/Cpu/PrecomputedMemoryThumbTestCase.cs create mode 100644 src/Ryujinx.Tests/Cpu/PrecomputedThumbTestCase.cs create mode 100644 src/Ryujinx.Tests/HLE/SoftwareKeyboardTests.cs create mode 100644 src/Ryujinx.Tests/Memory/MockMemoryManager.cs create mode 100644 src/Ryujinx.Tests/Memory/PartialUnmaps.cs create mode 100644 src/Ryujinx.Tests/Ryujinx.Tests.csproj create mode 100644 src/Ryujinx.Tests/Time/TimeZoneRuleTests.cs create mode 100644 src/Ryujinx.Tests/TreeDictionaryTests.cs create mode 100644 src/Ryujinx.UI.Common/App/ApplicationAddedEventArgs.cs create mode 100644 src/Ryujinx.UI.Common/App/ApplicationCountUpdatedEventArgs.cs create mode 100644 src/Ryujinx.UI.Common/App/ApplicationData.cs create mode 100644 src/Ryujinx.UI.Common/App/ApplicationJsonSerializerContext.cs create mode 100644 src/Ryujinx.UI.Common/App/ApplicationLibrary.cs create mode 100644 src/Ryujinx.UI.Common/App/ApplicationMetadata.cs create mode 100644 src/Ryujinx.UI.Common/Configuration/AudioBackend.cs create mode 100644 src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs create mode 100644 src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormatSettings.cs create mode 100644 src/Ryujinx.UI.Common/Configuration/ConfigurationJsonSerializerContext.cs create mode 100644 src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs create mode 100644 src/Ryujinx.UI.Common/Configuration/FileTypes.cs create mode 100644 src/Ryujinx.UI.Common/Configuration/LoggerModule.cs create mode 100644 src/Ryujinx.UI.Common/Configuration/System/Language.cs create mode 100644 src/Ryujinx.UI.Common/Configuration/System/Region.cs create mode 100644 src/Ryujinx.UI.Common/Configuration/UI/ColumnSort.cs create mode 100644 src/Ryujinx.UI.Common/Configuration/UI/GuiColumns.cs create mode 100644 src/Ryujinx.UI.Common/Configuration/UI/ShownFileTypes.cs create mode 100644 src/Ryujinx.UI.Common/Configuration/UI/WindowStartup.cs create mode 100644 src/Ryujinx.UI.Common/DiscordIntegrationModule.cs create mode 100644 src/Ryujinx.UI.Common/Extensions/FileTypeExtensions.cs create mode 100644 src/Ryujinx.UI.Common/Helper/CommandLineState.cs create mode 100644 src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs create mode 100644 src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs create mode 100644 src/Ryujinx.UI.Common/Helper/LinuxHelper.cs create mode 100644 src/Ryujinx.UI.Common/Helper/ObjectiveC.cs create mode 100644 src/Ryujinx.UI.Common/Helper/OpenHelper.cs create mode 100644 src/Ryujinx.UI.Common/Helper/SetupValidator.cs create mode 100644 src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs create mode 100644 src/Ryujinx.UI.Common/Helper/TitleHelper.cs create mode 100644 src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs create mode 100644 src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApi.cs create mode 100644 src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs create mode 100644 src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiUsage.cs create mode 100644 src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJson.cs create mode 100644 src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs create mode 100644 src/Ryujinx.UI.Common/Models/Github/GithubReleaseAssetJsonResponse.cs create mode 100644 src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonResponse.cs create mode 100644 src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonSerializerContext.cs create mode 100644 src/Ryujinx.UI.Common/Resources/Controller_JoyConLeft.svg create mode 100644 src/Ryujinx.UI.Common/Resources/Controller_JoyConPair.svg create mode 100644 src/Ryujinx.UI.Common/Resources/Controller_JoyConRight.svg create mode 100644 src/Ryujinx.UI.Common/Resources/Controller_ProCon.svg create mode 100644 src/Ryujinx.UI.Common/Resources/Icon_Blank.png create mode 100644 src/Ryujinx.UI.Common/Resources/Icon_NCA.png create mode 100644 src/Ryujinx.UI.Common/Resources/Icon_NRO.png create mode 100644 src/Ryujinx.UI.Common/Resources/Icon_NSO.png create mode 100644 src/Ryujinx.UI.Common/Resources/Icon_NSP.png create mode 100644 src/Ryujinx.UI.Common/Resources/Icon_XCI.png create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Amiibo.png create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Discord_Dark.png create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Discord_Light.png create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_GitHub_Dark.png create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_GitHub_Light.png create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Patreon_Dark.png create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Patreon_Light.png create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Ryujinx.png create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Twitter_Dark.png create mode 100644 src/Ryujinx.UI.Common/Resources/Logo_Twitter_Light.png create mode 100644 src/Ryujinx.UI.Common/Ryujinx.UI.Common.csproj create mode 100644 src/Ryujinx.UI.Common/SystemInfo/LinuxSystemInfo.cs create mode 100644 src/Ryujinx.UI.Common/SystemInfo/MacOSSystemInfo.cs create mode 100644 src/Ryujinx.UI.Common/SystemInfo/SystemInfo.cs create mode 100644 src/Ryujinx.UI.Common/SystemInfo/WindowsSystemInfo.cs create mode 100644 src/Ryujinx.UI.Common/UserError.cs create mode 100644 src/Ryujinx.UI.LocaleGenerator/LocaleGenerator.cs create mode 100644 src/Ryujinx.UI.LocaleGenerator/Ryujinx.UI.LocaleGenerator.csproj create mode 100644 src/Ryujinx/App.axaml create mode 100644 src/Ryujinx/App.axaml.cs create mode 100644 src/Ryujinx/AppHost.cs create mode 100644 src/Ryujinx/Assets/Fonts/SegoeFluentIcons.ttf create mode 100644 src/Ryujinx/Assets/Icons/Controller_JoyConLeft.svg create mode 100644 src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg create mode 100644 src/Ryujinx/Assets/Icons/Controller_JoyConRight.svg create mode 100644 src/Ryujinx/Assets/Icons/Controller_ProCon.svg create mode 100644 src/Ryujinx/Assets/Locales/ar_SA.json create mode 100644 src/Ryujinx/Assets/Locales/de_DE.json create mode 100644 src/Ryujinx/Assets/Locales/el_GR.json create mode 100644 src/Ryujinx/Assets/Locales/en_US.json create mode 100644 src/Ryujinx/Assets/Locales/es_ES.json create mode 100644 src/Ryujinx/Assets/Locales/fr_FR.json create mode 100644 src/Ryujinx/Assets/Locales/he_IL.json create mode 100644 src/Ryujinx/Assets/Locales/it_IT.json create mode 100644 src/Ryujinx/Assets/Locales/ja_JP.json create mode 100644 src/Ryujinx/Assets/Locales/ko_KR.json create mode 100644 src/Ryujinx/Assets/Locales/pl_PL.json create mode 100644 src/Ryujinx/Assets/Locales/pt_BR.json create mode 100644 src/Ryujinx/Assets/Locales/ru_RU.json create mode 100644 src/Ryujinx/Assets/Locales/th_TH.json create mode 100644 src/Ryujinx/Assets/Locales/tr_TR.json create mode 100644 src/Ryujinx/Assets/Locales/uk_UA.json create mode 100644 src/Ryujinx/Assets/Locales/zh_CN.json create mode 100644 src/Ryujinx/Assets/Locales/zh_TW.json create mode 100644 src/Ryujinx/Assets/Styles/Styles.xaml create mode 100644 src/Ryujinx/Assets/Styles/Themes.xaml create mode 100644 src/Ryujinx/Common/ApplicationHelper.cs create mode 100644 src/Ryujinx/Common/ApplicationSort.cs create mode 100644 src/Ryujinx/Common/KeyboardHotkeyState.cs create mode 100644 src/Ryujinx/Common/Locale/LocaleExtension.cs create mode 100644 src/Ryujinx/Common/Locale/LocaleManager.cs create mode 100644 src/Ryujinx/Common/ThemeManager.cs create mode 100644 src/Ryujinx/Input/AvaloniaKeyboard.cs create mode 100644 src/Ryujinx/Input/AvaloniaKeyboardDriver.cs create mode 100644 src/Ryujinx/Input/AvaloniaKeyboardMappingHelper.cs create mode 100644 src/Ryujinx/Input/AvaloniaMouse.cs create mode 100644 src/Ryujinx/Input/AvaloniaMouseDriver.cs create mode 100644 src/Ryujinx/Modules/Updater/Updater.cs create mode 100644 src/Ryujinx/Program.cs create mode 100644 src/Ryujinx/Ryujinx.csproj create mode 100644 src/Ryujinx/Ryujinx.ico create mode 100644 src/Ryujinx/UI/Applet/AvaHostUIHandler.cs create mode 100644 src/Ryujinx/UI/Applet/AvaloniaDynamicTextInputHandler.cs create mode 100644 src/Ryujinx/UI/Applet/AvaloniaHostUITheme.cs create mode 100644 src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml create mode 100644 src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs create mode 100644 src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml create mode 100644 src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml.cs create mode 100644 src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml create mode 100644 src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs create mode 100644 src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml create mode 100644 src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs create mode 100644 src/Ryujinx/UI/Controls/ApplicationGridView.axaml create mode 100644 src/Ryujinx/UI/Controls/ApplicationGridView.axaml.cs create mode 100644 src/Ryujinx/UI/Controls/ApplicationListView.axaml create mode 100644 src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs create mode 100644 src/Ryujinx/UI/Controls/NavigationDialogHost.axaml create mode 100644 src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs create mode 100644 src/Ryujinx/UI/Controls/SliderScroll.axaml.cs create mode 100644 src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml create mode 100644 src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml.cs create mode 100644 src/Ryujinx/UI/Helpers/ApplicationOpenedEventArgs.cs create mode 100644 src/Ryujinx/UI/Helpers/BitmapArrayValueConverter.cs create mode 100644 src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs create mode 100644 src/Ryujinx/UI/Helpers/ContentDialogHelper.cs create mode 100644 src/Ryujinx/UI/Helpers/Glyph.cs create mode 100644 src/Ryujinx/UI/Helpers/GlyphValueConverter.cs create mode 100644 src/Ryujinx/UI/Helpers/KeyValueConverter.cs create mode 100644 src/Ryujinx/UI/Helpers/LocalizedNeverConverter.cs create mode 100644 src/Ryujinx/UI/Helpers/LoggerAdapter.cs create mode 100644 src/Ryujinx/UI/Helpers/MiniCommand.cs create mode 100644 src/Ryujinx/UI/Helpers/NotificationHelper.cs create mode 100644 src/Ryujinx/UI/Helpers/OffscreenTextBox.cs create mode 100644 src/Ryujinx/UI/Helpers/TimeZoneConverter.cs create mode 100644 src/Ryujinx/UI/Helpers/UserErrorDialog.cs create mode 100644 src/Ryujinx/UI/Helpers/UserResult.cs create mode 100644 src/Ryujinx/UI/Helpers/Win32NativeInterop.cs create mode 100644 src/Ryujinx/UI/Models/CheatNode.cs create mode 100644 src/Ryujinx/UI/Models/ControllerModel.cs create mode 100644 src/Ryujinx/UI/Models/DeviceType.cs create mode 100644 src/Ryujinx/UI/Models/DownloadableContentModel.cs create mode 100644 src/Ryujinx/UI/Models/Generic/LastPlayedSortComparer.cs create mode 100644 src/Ryujinx/UI/Models/Generic/TimePlayedSortComparer.cs create mode 100644 src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs create mode 100644 src/Ryujinx/UI/Models/Input/HotkeyConfig.cs create mode 100644 src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs create mode 100644 src/Ryujinx/UI/Models/ModModel.cs create mode 100644 src/Ryujinx/UI/Models/PlayerModel.cs create mode 100644 src/Ryujinx/UI/Models/ProfileImageModel.cs create mode 100644 src/Ryujinx/UI/Models/SaveModel.cs create mode 100644 src/Ryujinx/UI/Models/StatusInitEventArgs.cs create mode 100644 src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs create mode 100644 src/Ryujinx/UI/Models/TempProfile.cs create mode 100644 src/Ryujinx/UI/Models/TimeZone.cs create mode 100644 src/Ryujinx/UI/Models/TitleUpdateModel.cs create mode 100644 src/Ryujinx/UI/Models/UserProfile.cs create mode 100644 src/Ryujinx/UI/Renderer/EmbeddedWindow.cs create mode 100644 src/Ryujinx/UI/Renderer/EmbeddedWindowOpenGL.cs create mode 100644 src/Ryujinx/UI/Renderer/EmbeddedWindowVulkan.cs create mode 100644 src/Ryujinx/UI/Renderer/OpenTKBindingsContext.cs create mode 100644 src/Ryujinx/UI/Renderer/RendererHost.axaml create mode 100644 src/Ryujinx/UI/Renderer/RendererHost.axaml.cs create mode 100644 src/Ryujinx/UI/Renderer/SPBOpenGLContext.cs create mode 100644 src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/AppListFavoriteComparable.cs create mode 100644 src/Ryujinx/UI/ViewModels/BaseModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/Input/MotionInputViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/Input/RumbleInputViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/SettingsViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs create mode 100644 src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs create mode 100644 src/Ryujinx/UI/Views/Input/ControllerInputView.axaml create mode 100644 src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Input/InputView.axaml create mode 100644 src/Ryujinx/UI/Views/Input/InputView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml create mode 100644 src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Input/MotionInputView.axaml create mode 100644 src/Ryujinx/UI/Views/Input/MotionInputView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Input/RumbleInputView.axaml create mode 100644 src/Ryujinx/UI/Views/Input/RumbleInputView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml create mode 100644 src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml create mode 100644 src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Main/MainViewControls.axaml create mode 100644 src/Ryujinx/UI/Views/Main/MainViewControls.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/User/UserEditorView.axaml create mode 100644 src/Ryujinx/UI/Views/User/UserEditorView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml create mode 100644 src/Ryujinx/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml create mode 100644 src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/User/UserRecovererView.axaml create mode 100644 src/Ryujinx/UI/Views/User/UserRecovererView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml create mode 100644 src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml.cs create mode 100644 src/Ryujinx/UI/Views/User/UserSelectorView.axaml create mode 100644 src/Ryujinx/UI/Views/User/UserSelectorView.axaml.cs create mode 100644 src/Ryujinx/UI/Windows/AboutWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/AboutWindow.axaml.cs create mode 100644 src/Ryujinx/UI/Windows/AmiiboWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/AmiiboWindow.axaml.cs create mode 100644 src/Ryujinx/UI/Windows/CheatWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/CheatWindow.axaml.cs create mode 100644 src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs create mode 100644 src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs create mode 100644 src/Ryujinx/UI/Windows/IconColorPicker.cs create mode 100644 src/Ryujinx/UI/Windows/MainWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/MainWindow.axaml.cs create mode 100644 src/Ryujinx/UI/Windows/ModManagerWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs create mode 100644 src/Ryujinx/UI/Windows/SettingsWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/SettingsWindow.axaml.cs create mode 100644 src/Ryujinx/UI/Windows/StyleableWindow.cs create mode 100644 src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml create mode 100644 src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs create mode 100644 src/Ryujinx/app.manifest create mode 100644 src/Spv.Generator/Autogenerated/CoreGrammar.cs create mode 100644 src/Spv.Generator/Autogenerated/GlslStd450Grammar.cs create mode 100644 src/Spv.Generator/Autogenerated/OpenClGrammar.cs create mode 100644 src/Spv.Generator/ConstantKey.cs create mode 100644 src/Spv.Generator/DeterministicHashCode.cs create mode 100644 src/Spv.Generator/DeterministicStringKey.cs create mode 100644 src/Spv.Generator/GeneratorPool.cs create mode 100644 src/Spv.Generator/IOperand.cs create mode 100644 src/Spv.Generator/Instruction.cs create mode 100644 src/Spv.Generator/InstructionOperands.cs create mode 100644 src/Spv.Generator/LICENSE create mode 100644 src/Spv.Generator/LiteralInteger.cs create mode 100644 src/Spv.Generator/LiteralString.cs create mode 100644 src/Spv.Generator/Module.cs create mode 100644 src/Spv.Generator/OperandType.cs create mode 100644 src/Spv.Generator/Spv.Generator.csproj create mode 100644 src/Spv.Generator/TypeDeclarationKey.cs create mode 100644 src/Spv.Generator/spirv.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..76edc491 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,272 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +[*] + +#### Core EditorConfig Options #### + +# Set default charset +charset = utf-8 + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = lf +insert_final_newline = true + +# Markdown, JSON, YAML, props and csproj files +[*.{md,json,yml,props,csproj}] + +# Indentation and spacing +indent_size = 2 +tab_width = 2 + +# C# files +[*.cs] + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_property = false:silent + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent + +# Expression-level preferences +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion + +# Field preferences +dotnet_style_readonly_field = true:suggestion + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:silent + +#### C# Coding Conventions #### + +# Namespace preferences +csharp_style_namespace_declarations = block_scoped:warning +resharper_csharp_namespace_body = block_scoped + +# var preferences +csharp_style_var_elsewhere = false:silent +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = false:silent + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_switch_expression = false:silent + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_prefer_static_local_function = true:suggestion +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_method_group_conversion = true + +# Code-block preferences +csharp_prefer_braces = true:silent +csharp_prefer_simple_using_statement = true:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_style_implicit_object_creation_when_type_is_apparent = true + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interfaces_should_be_prefixed_with_I.severity = suggestion +dotnet_naming_rule.interfaces_should_be_prefixed_with_I.symbols = interface +dotnet_naming_rule.interfaces_should_be_prefixed_with_I.style = IPascalCase + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = PascalCase + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = PascalCase + +dotnet_naming_rule.private_static_readonly_fields_should_be_camel_case_and_prefixed_with__.symbols = private_static_readonly_fields +dotnet_naming_rule.private_static_readonly_fields_should_be_camel_case_and_prefixed_with__.severity = suggestion +dotnet_naming_rule.private_static_readonly_fields_should_be_camel_case_and_prefixed_with__.style = _camelCase + +dotnet_naming_rule.local_constants_should_be_pascal_case.symbols = local_constants +dotnet_naming_rule.local_constants_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.local_constants_should_be_pascal_case.style = PascalCase + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = static, readonly + +dotnet_naming_symbols.local_constants.applicable_kinds = local +dotnet_naming_symbols.local_constants.applicable_accessibilities = local +dotnet_naming_symbols.local_constants.required_modifiers = const + +# Naming styles + +dotnet_naming_style._camelCase.required_prefix = _ +dotnet_naming_style._camelCase.required_suffix = +dotnet_naming_style._camelCase.word_separator = +dotnet_naming_style._camelCase.capitalization = camel_case + +dotnet_naming_style.PascalCase.required_prefix = +dotnet_naming_style.PascalCase.required_suffix = +dotnet_naming_style.PascalCase.word_separator = +dotnet_naming_style.PascalCase.capitalization = pascal_case + +dotnet_naming_style.IPascalCase.required_prefix = I +dotnet_naming_style.IPascalCase.required_suffix = +dotnet_naming_style.IPascalCase.word_separator = +dotnet_naming_style.IPascalCase.capitalization = pascal_case + +# TODO: +# .NET 8 migration (new warnings are caused by the NET 8 C# compiler and analyzer) +# The following info messages might need to be fixed in the source code instead of hiding the actual message +# Without the following lines, dotnet format would fail +# Disable "Collection initialization can be simplified" +dotnet_diagnostic.IDE0028.severity = none +dotnet_diagnostic.IDE0300.severity = none +dotnet_diagnostic.IDE0301.severity = none +dotnet_diagnostic.IDE0302.severity = none +dotnet_diagnostic.IDE0305.severity = none +# Disable "'new' expression can be simplified" +dotnet_diagnostic.IDE0090.severity = none +# Disable "Use primary constructor" +dotnet_diagnostic.IDE0290.severity = none +# Disable "Member '' does not access instance data and can be marked as static" +dotnet_diagnostic.CA1822.severity = none +# Disable "Change type of field '' from '' to '' for improved performance" +dotnet_diagnostic.CA1859.severity = none +# Disable "Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array" +dotnet_diagnostic.CA1861.severity = none +# Disable "Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'" +dotnet_diagnostic.CA1862.severity = none + +[src/Ryujinx/UI/ViewModels/**.cs] +# Disable "mark members as static" rule for ViewModels +dotnet_diagnostic.CA1822.severity = none + +[src/Ryujinx.HLE/HOS/Services/**.cs] +# Disable "mark members as static" rule for services +dotnet_diagnostic.CA1822.severity = none + +[src/Ryujinx.Tests/Cpu/*.cs] +# Disable naming rules for CPU tests +dotnet_diagnostic.IDE1006.severity = none diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..e39a7f13 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto eol=lf diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..9c8cbebd --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +patreon: Ryujinx diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..68be1f5e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,86 @@ +name: Bug Report +description: File a bug report +title: "[Bug]" +labels: bug +body: + - type: textarea + id: issue + attributes: + label: Description of the issue + description: What's the issue you encountered? + validations: + required: true + - type: textarea + id: repro + attributes: + label: Reproduction steps + description: How can the issue be reproduced? + placeholder: Describe each step as precisely as possible + validations: + required: true + - type: textarea + id: log + attributes: + label: Log file + description: A log file will help our developers to better diagnose and fix the issue. + placeholder: Logs files can be found under "Logs" folder in Ryujinx program folder. You can drag and drop the log on to the text area + validations: + required: true + - type: input + id: os + attributes: + label: OS + placeholder: "e.g. Windows 10" + validations: + required: true + - type: input + id: ryujinx-version + attributes: + label: Ryujinx version + placeholder: "e.g. 1.0.470" + validations: + required: true + - type: input + id: game-version + attributes: + label: Game version + placeholder: "e.g. 1.1.1" + validations: + required: false + - type: input + id: cpu + attributes: + label: CPU + placeholder: "e.g. i7-6700" + validations: + required: false + - type: input + id: gpu + attributes: + label: GPU + placeholder: "e.g. NVIDIA RTX 2070" + validations: + required: false + - type: input + id: ram + attributes: + label: RAM + placeholder: "e.g. 16GB" + validations: + required: false + - type: textarea + id: mods + attributes: + label: List of applied mods + placeholder: You can list applied mods here. + validations: + required: false + - type: textarea + id: additional-context + attributes: + label: Additional context? + description: | + - Additional info about your environment: + - Any other information relevant to your issue. + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..abad80a3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Ryujinx Discord + url: https://discord.gg/N2FmfVc + about: This is for development related issues. For support and technical issues, please come to our Discord server. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..399aa039 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,31 @@ +name: Feature Request +description: Suggest a new feature for Ryujinx. +title: "[Feature Request]" +labels: enhancement +body: + - type: textarea + id: overview + attributes: + label: Overview + description: Include the basic, high-level concepts for this feature here. + validations: + required: true + - type: textarea + id: details + attributes: + label: Smaller details + description: These may include specific methods of implementation etc. + validations: + required: true + - type: textarea + id: request + attributes: + label: Nature of request + validations: + required: true + - type: textarea + id: feature + attributes: + label: Why would this feature be useful? + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/missing_cpu_instruction.yml b/.github/ISSUE_TEMPLATE/missing_cpu_instruction.yml new file mode 100644 index 00000000..d815ddfd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/missing_cpu_instruction.yml @@ -0,0 +1,26 @@ +name: Missing CPU Instruction +description: CPU Instruction is missing in Ryujinx. +title: "[CPU]" +labels: [cpu, not-implemented] +body: + - type: textarea + id: instruction + attributes: + label: CPU instruction + description: What CPU instruction is missing? + validations: + required: true + - type: textarea + id: name + attributes: + label: Instruction name + description: Include the name from [armconverter.com](https://armconverter.com/?disasm) or [shell-storm.org](http://shell-storm.org/online/Online-Assembler-and-Disassembler/?arch=arm64&endianness=big&dis_with_raw=True&dis_with_ins=True) in the above code block + validations: + required: true + - type: textarea + id: required + attributes: + label: Required by + description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this instruction. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/missing_service_call.yml b/.github/ISSUE_TEMPLATE/missing_service_call.yml new file mode 100644 index 00000000..80aae533 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/missing_service_call.yml @@ -0,0 +1,25 @@ +name: Missing Service Call +description: Service call is missing in Ryujinx. +labels: not-implemented +body: + - type: textarea + id: instruction + attributes: + label: Service call + description: What service call is missing? + validations: + required: true + - type: textarea + id: name + attributes: + label: Service description + description: Include the description/explanation from [Switchbrew](https://switchbrew.org/w/index.php?title=Services_API) and/or [SwIPC](https://reswitched.github.io/SwIPC/) in the above code block + validations: + required: true + - type: textarea + id: required + attributes: + label: Required by + description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this service. + validations: + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/missing_shader_instruction.yml b/.github/ISSUE_TEMPLATE/missing_shader_instruction.yml new file mode 100644 index 00000000..df37859a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/missing_shader_instruction.yml @@ -0,0 +1,19 @@ +name: Missing Shader Instruction +description: Shader Instruction is missing in Ryujinx. +title: "[GPU]" +labels: [gpu, not-implemented] +body: + - type: textarea + id: instruction + attributes: + label: Shader instruction + description: What shader instruction is missing? + validations: + required: true + - type: textarea + id: required + attributes: + label: Required by + description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this instruction. + validations: + required: true diff --git a/.github/csc.json b/.github/csc.json new file mode 100644 index 00000000..2b960edd --- /dev/null +++ b/.github/csc.json @@ -0,0 +1,18 @@ +{ + "problemMatcher": [ + { + "owner": "csc", + "pattern": [ + { + "regexp": "^((?:\\\\|/)(?:[^\\\\/:]+(?:\\\\|/))+[^\\\\/]+)\\((\\d+),(\\d+)\\):\\s+([a-zA-Z]+)\\s+([^:]+):\\s+([^[]+)\\s+\\[", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "code": 5, + "message": 6 + } + ] + } + ] +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..20bdc19d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,40 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: weekly + labels: + - "infra" + reviewers: + - TSRBerry + commit-message: + prefix: "ci" + + - package-ecosystem: nuget + directory: / + open-pull-requests-limit: 10 + schedule: + interval: daily + labels: + - "infra" + reviewers: + - TSRBerry + commit-message: + prefix: nuget + groups: + Avalonia: + patterns: + - "*Avalonia*" + Silk.NET: + patterns: + - "Silk.NET*" + OpenTK: + patterns: + - "OpenTK*" + SixLabors: + patterns: + - "SixLabors*" + NUnit: + patterns: + - "NUnit*" diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..cd7650a9 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,35 @@ +audio: +- changed-files: + - any-glob-to-any-file: 'src/Ryujinx.Audio*/**' + +cpu: +- changed-files: + - any-glob-to-any-file: ['src/ARMeilleure/**', 'src/Ryujinx.Cpu/**', 'src/Ryujinx.Memory/**'] + +gpu: +- changed-files: + - any-glob-to-any-file: ['src/Ryujinx.Graphics.*/**', 'src/Spv.Generator/**', 'src/Ryujinx.ShaderTools/**'] + +'graphics-backend:opengl': +- changed-files: + - any-glob-to-any-file: 'src/Ryujinx.Graphics.OpenGL/**' + +'graphics-backend:vulkan': +- changed-files: + - any-glob-to-any-file: ['src/Ryujinx.Graphics.Vulkan/**', 'src/Spv.Generator/**'] + +gui: +- changed-files: + - any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**', 'src/Ryujinx.Gtk3/**'] + +horizon: +- changed-files: + - any-glob-to-any-file: ['src/Ryujinx.HLE/**', 'src/Ryujinx.Horizon/**'] + +kernel: +- changed-files: + - any-glob-to-any-file: 'src/Ryujinx.HLE/HOS/Kernel/**' + +infra: +- changed-files: + - any-glob-to-any-file: ['.github/**', 'distribution/**', 'Directory.Packages.props'] diff --git a/.github/reviewers.yml b/.github/reviewers.yml new file mode 100644 index 00000000..46c0d5c1 --- /dev/null +++ b/.github/reviewers.yml @@ -0,0 +1,25 @@ + +cpu: + - gdkchan + - riperiperi + - LDj3SNuD + +gpu: + - gdkchan + - riperiperi + +gui: + - Ack77 + - emmauss + - TSRBerry + +horizon: + - gdkchan + - Ack77 + - TSRBerry + +infra: + - TSRBerry + +default: + - '@developers' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..221c7732 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,163 @@ +name: Build job + +on: + workflow_call: + +env: + POWERSHELL_TELEMETRY_OPTOUT: 1 + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + RYUJINX_BASE_VERSION: "1.1.0" + +jobs: + build: + name: ${{ matrix.platform.name }} (${{ matrix.configuration }}) + runs-on: ${{ matrix.platform.os }} + timeout-minutes: 45 + strategy: + matrix: + configuration: [Debug, Release] + platform: + - { name: win-x64, os: windows-latest, zip_os_name: win_x64 } + - { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 } + - { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 } + - { name: osx-x64, os: macos-13, zip_os_name: osx_x64 } + + fail-fast: false + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Overwrite csc problem matcher + run: echo "::add-matcher::.github/csc.json" + + - name: Get git short hash + id: git_short_hash + run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT + shell: bash + + - name: Change config filename + run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs + shell: bash + if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' + + - name: Change config filename for macOS + run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs + shell: bash + if: github.event_name == 'pull_request' && matrix.platform.os == 'macos-13' + + - name: Build + run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER + + - name: Test + uses: TSRBerry/unstable-commands@v1 + with: + commands: dotnet test --no-build -c "${{ matrix.configuration }}" + timeout-minutes: 10 + retry-codes: 139 + if: matrix.platform.name != 'linux-arm64' + + - name: Publish Ryujinx + run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true + if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' + + - name: Publish Ryujinx.Headless.SDL2 + run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true + if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' + + - name: Publish Ryujinx.Gtk3 + run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_gtk -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Gtk3 --self-contained true + if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' + + - name: Set executable bit + run: | + chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh + chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh + chmod +x ./publish_gtk/Ryujinx.Gtk3 ./publish_gtk/Ryujinx.sh + if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest' + + - name: Upload Ryujinx artifact + uses: actions/upload-artifact@v4 + with: + name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} + path: publish + if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' + + - name: Upload Ryujinx.Headless.SDL2 artifact + uses: actions/upload-artifact@v4 + with: + name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} + path: publish_sdl2_headless + if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' + + - name: Upload Ryujinx.Gtk3 artifact + uses: actions/upload-artifact@v4 + with: + name: gtk-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} + path: publish_gtk + if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' + + build_macos: + name: macOS Universal (${{ matrix.configuration }}) + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + matrix: + configuration: [ Debug, Release ] + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Setup LLVM 14 + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 14 + + - name: Install rcodesign + run: | + mkdir -p $HOME/.bin + gh release download -R indygreg/apple-platform-rs -O apple-codesign.tar.gz -p 'apple-codesign-*-x86_64-unknown-linux-musl.tar.gz' + tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1 + rm apple-codesign.tar.gz + mv rcodesign $HOME/.bin/ + echo "$HOME/.bin" >> $GITHUB_PATH + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get git short hash + id: git_short_hash + run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT + + - name: Change config filename + run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs + shell: bash + if: github.event_name == 'pull_request' + + - name: Publish macOS Ryujinx + run: | + ./distribution/macos/create_macos_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" + + - name: Publish macOS Ryujinx.Headless.SDL2 + run: | + ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" + + - name: Upload Ryujinx artifact + uses: actions/upload-artifact@v4 + with: + name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal + path: "publish/*.tar.gz" + if: github.event_name == 'pull_request' + + - name: Upload Ryujinx.Headless.SDL2 artifact + uses: actions/upload-artifact@v4 + with: + name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal + path: "publish_headless/*.tar.gz" + if: github.event_name == 'pull_request' diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 00000000..2bef2d8e --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,74 @@ +name: Perform checks + +on: + pull_request: + branches: [ master ] + paths: + - '**' + - '!.github/**' + - '!*.yml' + - '!*.config' + - '!*.md' + - '.github/workflows/*.yml' + +permissions: + pull-requests: write + checks: write + +concurrency: + group: pr-checks-${{ github.event.number }} + cancel-in-progress: true + +jobs: + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Overwrite csc problem matcher + run: echo "::add-matcher::.github/csc.json" + + - run: dotnet restore + + - name: Print dotnet format version + run: dotnet format --version + + - name: Run dotnet format whitespace + run: | + dotnet format whitespace --verify-no-changes --report ./whitespace-report.json -v d + + # For some unknown reason this step sometimes fails with exit code 139 (segfault?), + # so in that case we'll try again (3 tries max). + - name: Run dotnet format style + uses: TSRBerry/unstable-commands@v1 + with: + commands: dotnet format style --severity info --verify-no-changes --report ./style-report.json -v d + timeout-minutes: 5 + retry-codes: 139 + + # For some unknown reason this step sometimes fails with exit code 139 (segfault?), + # so in that case we'll try again (3 tries max). + - name: Run dotnet format analyzers + uses: TSRBerry/unstable-commands@v1 + with: + commands: dotnet format analyzers --severity info --verify-no-changes --report ./analyzers-report.json -v d + timeout-minutes: 5 + retry-codes: 139 + + - name: Upload report + if: failure() + uses: actions/upload-artifact@v4 + with: + name: dotnet-format + path: ./*-report.json + + pr_build: + uses: ./.github/workflows/build.yml + needs: format + secrets: inherit diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml new file mode 100644 index 00000000..bfed328c --- /dev/null +++ b/.github/workflows/flatpak.yml @@ -0,0 +1,212 @@ +name: Flatpak release job + +on: + workflow_call: + inputs: + ryujinx_version: + required: true + type: string + + +concurrency: flatpak-release + +jobs: + release: + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }} + runs-on: ubuntu-latest + + env: + NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages + GIT_COMMITTER_NAME: "RyujinxBot" + GIT_COMMITTER_EMAIL: "61127645+RyujinxBot@users.noreply.github.com" + RYUJINX_PROJECT_FILE: "src/Ryujinx/Ryujinx.csproj" + NUGET_SOURCES_DESTDIR: "nuget-sources" + RYUJINX_VERSION: "${{ inputs.ryujinx_version }}" + + steps: + - uses: actions/checkout@v4 + with: + path: Ryujinx + + - uses: actions/setup-dotnet@v4 + with: + global-json-file: Ryujinx/global.json + + - name: Get version info + id: version_info + working-directory: Ryujinx + run: | + echo "git_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + - uses: actions/checkout@v4 + with: + repository: flathub/org.ryujinx.Ryujinx + token: ${{ secrets.RYUJINX_BOT_PAT }} + submodules: recursive + path: flathub + + - name: Install dependencies + run: python -m pip install PyYAML lxml + + - name: Restore Nuget packages + # With .NET 8.0.100, Microsoft.NET.ILLink.Tasks isn't restored by default and only seems to appears when publishing. + # So we just publish to grab the dependencies + run: | + dotnet publish -c Release -r linux-x64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained + dotnet publish -c Release -r linux-arm64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained + + - name: Generate nuget_sources.json + shell: python + run: | + import hashlib + from pathlib import Path + import base64 + import binascii + import json + import os + import urllib.request + + sources = [] + + + def create_source_from_external(name, version): + full_dir_path = Path(os.environ["NUGET_PACKAGES"]).joinpath(name).joinpath(version) + os.makedirs(full_dir_path, exist_ok=True) + + filename = "{}.{}.nupkg".format(name, version) + url = "https://api.nuget.org/v3-flatcontainer/{}/{}/{}".format( + name, version, filename + ) + + print(f"Processing {url}...") + response = urllib.request.urlopen(url) + sha512 = hashlib.sha512(response.read()).hexdigest() + + return { + "type": "file", + "url": url, + "sha512": sha512, + "dest": os.environ["NUGET_SOURCES_DESTDIR"], + "dest-filename": filename, + } + + + has_added_x64_apphost = False + + for path in Path(os.environ["NUGET_PACKAGES"]).glob("**/*.nupkg.sha512"): + name = path.parent.parent.name + version = path.parent.name + filename = "{}.{}.nupkg".format(name, version) + url = "https://api.nuget.org/v3-flatcontainer/{}/{}/{}".format( + name, version, filename + ) + + with path.open() as fp: + sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode("ascii") + + sources.append( + { + "type": "file", + "url": url, + "sha512": sha512, + "dest": os.environ["NUGET_SOURCES_DESTDIR"], + "dest-filename": filename, + } + ) + + # .NET will not add current installed application host to the list, force inject it here. + if not has_added_x64_apphost and name.startswith('microsoft.netcore.app.host'): + sources.append(create_source_from_external("microsoft.netcore.app.host.linux-x64", version)) + has_added_x64_apphost = True + + with open("flathub/nuget_sources.json", "w") as fp: + json.dump(sources, fp, indent=4) + + - name: Update flatpak metadata + id: metadata + env: + RYUJINX_GIT_HASH: ${{ steps.version_info.outputs.git_hash }} + shell: python + run: | + import hashlib + import hmac + import json + import os + import yaml + from datetime import datetime + from lxml import etree + + + # Ensure we don't destroy multiline strings + def str_presenter(dumper, data): + if len(data.splitlines()) > 1: + return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|") + return dumper.represent_scalar("tag:yaml.org,2002:str", data) + + + yaml.representer.SafeRepresenter.add_representer(str, str_presenter) + + yaml_file = "flathub/org.ryujinx.Ryujinx.yml" + xml_file = "flathub/org.ryujinx.Ryujinx.appdata.xml" + + with open(yaml_file, "r") as f: + data = yaml.safe_load(f) + + for source in data["modules"][0]["sources"]: + if type(source) is str: + continue + if ( + source["type"] == "git" + and source["url"] == "https://github.com/Ryujinx/Ryujinx.git" + ): + source["commit"] = os.environ['RYUJINX_GIT_HASH'] + + is_same_version = data["modules"][0]["build-options"]["env"]["RYUJINX_VERSION"] == os.environ['RYUJINX_VERSION'] + + with open(os.environ['GITHUB_OUTPUT'], "a") as gh_out: + if is_same_version: + gh_out.write(f"commit_message=Retry update to {os.environ['RYUJINX_VERSION']}") + else: + gh_out.write(f"commit_message=Update to {os.environ['RYUJINX_VERSION']}") + + if not is_same_version: + data["modules"][0]["build-options"]["env"]["RYUJINX_VERSION"] = os.environ['RYUJINX_VERSION'] + + with open(yaml_file, "w") as f: + yaml.safe_dump(data, f, sort_keys=False) + + parser = etree.XMLParser(remove_blank_text=True) + tree = etree.parse(xml_file, parser) + + root = tree.getroot() + + releases = root.find("releases") + + element = etree.Element("release") + element.set("version", os.environ['RYUJINX_VERSION']) + element.set("date", datetime.now().date().isoformat()) + releases.insert(0, element) + + # Ensure 4 spaces + etree.indent(root, space=" ") + + with open(xml_file, "wb") as f: + f.write( + etree.tostring( + tree, + pretty_print=True, + encoding="UTF-8", + doctype='', + ) + ) + + - name: Push flatpak update + working-directory: flathub + env: + COMMIT_MESSAGE: ${{ steps.metadata.outputs.commit_message }} + run: | + git config user.name "${{ env.GIT_COMMITTER_NAME }}" + git config user.email "${{ env.GIT_COMMITTER_EMAIL }}" + git add . + git commit -m "$COMMIT_MESSAGE" + git push origin master \ No newline at end of file diff --git a/.github/workflows/mako.yml b/.github/workflows/mako.yml new file mode 100644 index 00000000..19165fb0 --- /dev/null +++ b/.github/workflows/mako.yml @@ -0,0 +1,41 @@ +name: Mako +on: + discussion: + types: [created, edited, answered, unanswered, category_changed] + discussion_comment: + types: [created, edited] + gollum: + issue_comment: + types: [created, edited] + issues: + types: [opened, edited, reopened, pinned, milestoned, demilestoned, assigned, unassigned, labeled, unlabeled] + pull_request_target: + types: [opened, edited, reopened, synchronize, ready_for_review, assigned, unassigned] + +jobs: + tasks: + name: Run Ryujinx tasks + permissions: + actions: read + contents: read + discussions: write + issues: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + if: github.event_name == 'pull_request_target' + with: + # Ensure we pin the source origin as pull_request_target run under forks. + fetch-depth: 0 + repository: Ryujinx/Ryujinx + ref: master + + - name: Run Mako command + uses: Ryujinx/Ryujinx-Mako@master + with: + command: exec-ryujinx-tasks + args: --event-name "${{ github.event_name }}" --event-path "${{ github.event_path }}" -w "${{ github.workspace }}" "${{ github.repository }}" "${{ github.run_id }}" + app_id: ${{ secrets.MAKO_APP_ID }} + private_key: ${{ secrets.MAKO_PRIVATE_KEY }} + installation_id: ${{ secrets.MAKO_INSTALLATION_ID }} diff --git a/.github/workflows/nightly_pr_comment.yml b/.github/workflows/nightly_pr_comment.yml new file mode 100644 index 00000000..38850df0 --- /dev/null +++ b/.github/workflows/nightly_pr_comment.yml @@ -0,0 +1,71 @@ +name: Comment PR artifacts links + +on: + workflow_run: + workflows: ['Perform checks'] + types: [completed] + +jobs: + pr_comment: + if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }} + steps: + - uses: actions/github-script@v6 + with: + script: | + const {owner, repo} = context.repo; + const run_id = ${{github.event.workflow_run.id}}; + const pull_head_sha = '${{github.event.workflow_run.head_sha}}'; + + const issue_number = await (async () => { + const pulls = await github.rest.pulls.list({owner, repo}); + for await (const {data} of github.paginate.iterator(pulls)) { + for (const pull of data) { + if (pull.head.sha === pull_head_sha) { + return pull.number; + } + } + } + })(); + if (issue_number) { + core.info(`Using pull request ${issue_number}`); + } else { + return core.error(`No matching pull request found`); + } + + const {data: {artifacts}} = await github.rest.actions.listWorkflowRunArtifacts({owner, repo, run_id}); + if (!artifacts.length) { + return core.error(`No artifacts found`); + } + let body = `Download the artifacts for this pull request:\n`; + let hidden_gtk_artifacts = `\n\n
Old GUI (GTK3)\n`; + let hidden_headless_artifacts = `\n\n
GUI-less (SDL2)\n`; + let hidden_debug_artifacts = `\n\n
Only for Developers\n`; + for (const art of artifacts) { + if(art.name.includes('Debug')) { + hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; + } else if(art.name.includes('gtk-ryujinx')) { + hidden_gtk_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; + } else if(art.name.includes('sdl2-ryujinx-headless')) { + hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; + } else { + body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; + } + } + hidden_gtk_artifacts += `\n
`; + hidden_headless_artifacts += `\n
`; + hidden_debug_artifacts += `\n
`; + body += hidden_gtk_artifacts; + body += hidden_headless_artifacts; + body += hidden_debug_artifacts; + + const {data: comments} = await github.rest.issues.listComments({repo, owner, issue_number}); + const existing_comment = comments.find((c) => c.user.login === 'github-actions[bot]'); + if (existing_comment) { + core.info(`Updating comment ${existing_comment.id}`); + await github.rest.issues.updateComment({repo, owner, comment_id: existing_comment.id, body}); + } else { + core.info(`Creating a comment`); + await github.rest.issues.createComment({repo, owner, issue_number, body}); + } \ No newline at end of file diff --git a/.github/workflows/pr_triage.yml b/.github/workflows/pr_triage.yml new file mode 100644 index 00000000..d8d66b70 --- /dev/null +++ b/.github/workflows/pr_triage.yml @@ -0,0 +1,28 @@ +name: "Pull Request Triage" +on: + pull_request_target: + types: [opened, ready_for_review] + +jobs: + triage: + permissions: + contents: read + pull-requests: write + + runs-on: ubuntu-latest + + steps: + # Grab sources to get latest labeler.yml + - name: Fetch sources + uses: actions/checkout@v4 + with: + # Ensure we pin the source origin as pull_request_target run under forks. + fetch-depth: 0 + repository: Ryujinx/Ryujinx + ref: master + + - name: Update labels based on changes + uses: actions/labeler@v5 + with: + sync-labels: true + dot: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..f2bebc77 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,218 @@ +name: Release job + +on: + workflow_dispatch: + inputs: {} + push: + branches: [ master ] + paths-ignore: + - '.github/**' + - '*.yml' + - '*.json' + - '*.config' + - '*.md' + +concurrency: release + +env: + POWERSHELL_TELEMETRY_OPTOUT: 1 + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + RYUJINX_BASE_VERSION: "1.1" + RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master" + RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryujinx" + RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master" + +jobs: + tag: + name: Create tag + runs-on: ubuntu-20.04 + steps: + - name: Get version info + id: version_info + run: | + echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT + shell: bash + + - name: Create tag + uses: actions/github-script@v6 + with: + script: | + github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: 'refs/tags/${{ steps.version_info.outputs.build_version }}', + sha: context.sha + }) + + - name: Create release + uses: ncipollo/release-action@v1 + with: + name: ${{ steps.version_info.outputs.build_version }} + tag: ${{ steps.version_info.outputs.build_version }} + body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)." + omitBodyDuringUpdate: true + owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }} + repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }} + token: ${{ secrets.RELEASE_TOKEN }} + + release: + name: Release for ${{ matrix.platform.name }} + runs-on: ${{ matrix.platform.os }} + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }} + strategy: + matrix: + platform: + - { name: win-x64, os: windows-latest, zip_os_name: win_x64 } + - { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 } + - { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 } + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Overwrite csc problem matcher + run: echo "::add-matcher::.github/csc.json" + + - name: Get version info + id: version_info + run: | + echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT + echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT + shell: bash + + - name: Configure for release + run: | + sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs + sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs + sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs + sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs + sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs + sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs + shell: bash + + - name: Create output dir + run: "mkdir release_output" + + - name: Publish + run: | + dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true + dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true + + - name: Packing Windows builds + if: matrix.platform.os == 'windows-latest' + run: | + pushd publish_ava + cp publish/Ryujinx.exe publish/Ryujinx.Ava.exe + 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish + 7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish + popd + + pushd publish_sdl2_headless + 7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish + popd + shell: bash + + - name: Packing Linux builds + if: matrix.platform.os == 'ubuntu-latest' + run: | + pushd publish_ava + cp publish/Ryujinx publish/Ryujinx.Ava + chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava publish/Ryujinx + tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish + tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish + popd + + pushd publish_sdl2_headless + chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2 + tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish + popd + shell: bash + + - name: Pushing new release + uses: ncipollo/release-action@v1 + with: + name: ${{ steps.version_info.outputs.build_version }} + artifacts: "release_output/*.tar.gz,release_output/*.zip" + tag: ${{ steps.version_info.outputs.build_version }} + body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)." + omitBodyDuringUpdate: true + allowUpdates: true + replacesArtifacts: true + owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }} + repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }} + token: ${{ secrets.RELEASE_TOKEN }} + + macos_release: + name: Release MacOS universal + runs-on: ubuntu-latest + timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Setup LLVM 15 + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 15 + + - name: Install rcodesign + run: | + mkdir -p $HOME/.bin + gh release download -R indygreg/apple-platform-rs -O apple-codesign.tar.gz -p 'apple-codesign-*-x86_64-unknown-linux-musl.tar.gz' + tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1 + rm apple-codesign.tar.gz + mv rcodesign $HOME/.bin/ + echo "$HOME/.bin" >> $GITHUB_PATH + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get version info + id: version_info + run: | + echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT + echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT + + - name: Configure for release + run: | + sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs + sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs + sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs + sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs + sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs + sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs + shell: bash + + - name: Publish macOS Ryujinx + run: | + ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release + + - name: Publish macOS Ryujinx.Headless.SDL2 + run: | + ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release + + - name: Pushing new release + uses: ncipollo/release-action@v1 + with: + name: ${{ steps.version_info.outputs.build_version }} + artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz" + tag: ${{ steps.version_info.outputs.build_version }} + body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)." + omitBodyDuringUpdate: true + allowUpdates: true + replacesArtifacts: true + owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }} + repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }} + token: ${{ secrets.RELEASE_TOKEN }} + + flatpak_release: + uses: ./.github/workflows/flatpak.yml + needs: release + with: + ryujinx_version: "1.1.${{ github.run_number }}" + secrets: inherit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..37b419d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates +.vs +.vscode + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Rider is a Visual Studio alternative +.idea/* + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings +packages/* +*.config + +# Include nuget.config +!nuget.config + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + + +#LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac desktop service store files +.DS_Store + +# VS Launch Settings +launchSettings.json + +# NetCore Publishing Profiles +PublishProfiles/ + +# Glade backup files +*.glade~ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..366eb843 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,147 @@ +# Contribution to Ryujinx + +You can contribute to Ryujinx with PRs, testing of PRs and issues. Contributing code and other implementations is greatly appreciated alongside simply filing issues for problems you encounter. +Please read the entire document before continuing as it can potentially save everyone involved a significant amount of time. + +# Quick Links + +* [Code Style Documentation](docs/coding-guidelines/coding-style.md) +* [Pull Request Guidelines](docs/workflow/pr-guide.md) + +## Reporting Issues + +We always welcome bug reports, feature proposals and overall feedback. Here are a few tips on how you can make reporting your issue as effective as possible. + +### Identify Where to Report + +The Ryujinx codebase is distributed across multiple repositories in the [Ryujinx organization](https://github.com/Ryujinx). Depending on the feedback you might want to file the issue on a different repo. Here are a few common repos: + +* [Ryujinx/Ryujinx](https://github.com/Ryujinx/Ryujinx) Ryujinx core project files. +* [Ryujinx/Ryujinx-Games-List](https://github.com/Ryujinx/Ryujinx-Games-List) Ryujinx game compatibility list. +* [Ryujinx/Ryujinx-Website](https://github.com/Ryujinx/Ryujinx-Website) Ryujinx website source code. +* [Ryujinx/Ryujinx-Ldn-Website](https://github.com/Ryujinx/Ryujinx-Ldn-Website) Ryujinx LDN website source code. + +### Finding Existing Issues + +Before filing a new issue, please search our [open issues](https://github.com/Ryujinx/Ryujinx/issues) to check if it already exists. + +If you do find an existing issue, please include your own feedback in the discussion. Do consider upvoting (👍 reaction) the original post, as this helps us prioritize popular issues in our backlog. + +### Writing a Good Feature Request + +Please review any feature requests already opened to both check it has not already been suggested, and to familiarize yourself with the format. When ready to submit a proposal, please use the [Feature Request issue template](https://github.com/Ryujinx/Ryujinx/issues/new?assignees=&labels=&projects=&template=feature_request.yml&title=%5BFeature+Request%5D). + +### Writing a Good Bug Report + +Good bug reports make it easier for maintainers to verify and root cause the underlying problem. The better a bug report, the faster the problem will be resolved. +Ideally, a bug report should contain the following information: + +* A high-level description of the problem. +* A _minimal reproduction_, i.e. the smallest time commitment/configuration required to reproduce the wrong behavior. This can be in the form of a small homebrew application, or by providing a save file and reproduction steps for a specific game. +* A description of the _expected behavior_, contrasted with the _actual behavior_ observed. +* Information on the environment: OS/distro, CPU, GPU (including driver), RAM etc. +* A Ryujinx log file of the run instance where the issue occurred. Log files can be found in `[Executable Folder]/Logs` and are named chronologically. +* Additional information, e.g. is it a regression from previous versions? Are there any known workarounds? + +When ready to submit a bug report, please use the [Bug Report issue template](https://github.com/Ryujinx/Ryujinx/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml&title=%5BBug%5D). + +## Contributing Changes + +Project maintainers will merge changes that both improve the project and meet our standards for code quality. + +The [Pull Request Guide](docs/workflow/pr-guide.md) and [License](https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt) docs define additional guidance. + +### DOs and DON'Ts + +Please do: + +* **DO** follow our [coding style](docs/coding-guidelines/coding-style.md) (C# code-specific). +* **DO** give priority to the current style of the project or file you're changing even if it diverges from the general guidelines. +* **DO** keep the discussions focused. When a new or related topic comes up + it's often better to create new issue than to side track the discussion. +* **DO** clearly state on an issue that you are going to take on implementing it. +* **DO** blog and tweet (or whatever) about your contributions, frequently! + +Please do not: + +* **DON'T** make PRs for style changes. +* **DON'T** surprise us with big pull requests. Instead, file an issue and talk with us on Discord to start + a discussion so we can agree on a direction before you invest a large amount + of time. +* **DON'T** commit code that you didn't write. If you find code that you think is a good fit to add to Ryujinx, file an issue or talk to us on Discord to start a discussion before proceeding. +* **DON'T** submit PRs that alter licensing related files or headers. If you believe there's a problem with them, file an issue and we'll be happy to discuss it. + +### Suggested Workflow + +We use and recommend the following workflow: + +1. Create or find an issue for your work. + - You can skip this step for trivial changes. + - Get agreement from the team and the community that your proposed change is a good one if it is of significant size or changes core functionality. + - Clearly state that you are going to take on implementing it, if that's the case. You can request that the issue be assigned to you. Note: The issue filer and the implementer don't have to be the same person. +2. Create a personal fork of the repository on GitHub (if you don't already have one). +3. In your fork, create a branch off of main (`git checkout -b mybranch`). + - Branches are useful since they isolate your changes from incoming changes from upstream. They also enable you to create multiple PRs from the same fork. +4. Make and commit your changes to your branch. + - [Build Instructions](https://github.com/Ryujinx/Ryujinx#building) explains how to build and test. + - Commit messages should be clear statements of action and intent. +6. Build the repository with your changes. + - Make sure that the builds are clean. + - Make sure that `dotnet format` has been run and any corrections tested and committed. +7. Create a pull request (PR) against the Ryujinx/Ryujinx repository's **main** branch. + - State in the description what issue or improvement your change is addressing. + - Check if all the Continuous Integration checks are passing. Refer to [Actions](https://github.com/Ryujinx/Ryujinx/actions) to check for outstanding errors. +8. Wait for feedback or approval of your changes from the [core development team](https://github.com/orgs/Ryujinx/teams/developers) + - Details about the pull request [review procedure](docs/workflow/ci/pr-guide.md). +9. When the team members have signed off, and all checks are green, your PR will be merged. + - The next official build will automatically include your change. + - You can delete the branch you used for making the change. + +### Good First Issues + +The team marks the most straightforward issues as [good first issues](https://github.com/Ryujinx/Ryujinx/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). This set of issues is the place to start if you are interested in contributing but new to the codebase. + +### Commit Messages + +Please format commit messages as follows (based on [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)): + +``` +Summarize change in 50 characters or less + +Provide more detail after the first line. Leave one blank line below the +summary and wrap all lines at 72 characters or less. + +If the change fixes an issue, leave another blank line after the final +paragraph and indicate which issue is fixed in the specific format +below. + +Fix #42 +``` + +Also do your best to factor commits appropriately, not too large with unrelated things in the same commit, and not too small with the same small change applied N times in N different commits. + +### PR - CI Process + +The [Ryujinx continuous integration](https://github.com/Ryujinx/Ryujinx/actions) (CI) system will automatically perform the required builds and run tests (including the ones you are expected to run) for PRs. Builds and test runs must be clean or have bugs properly filed against flaky/unexpected failures that are unrelated to your change. + +If the CI build fails for any reason, the PR actions tab should be consulted for further information on the failure. There are a few usual suspects for such a failure: +* `dotnet format` has not been run on the PR and has outstanding stylistic issues. +* There is an error within the PR that fails a test or errors the compiler. +* Random failure of the workflow can occasionally result in a CI failure. In this scenario a maintainer will manually restart the job. + +### PR Feedback + +Ryujinx team and community members will provide feedback on your change. Community feedback is highly valued. You may see the absence of team feedback if the community has already provided good review feedback. + +Two Ryujinx team members must review and approve every PR prior to merge. They will often reply with "LGTM, see nit". That means that the PR will be merged once the feedback is resolved. "LGTM" == "looks good to me". + +There are lots of thoughts and [approaches](https://github.com/antlr/antlr4-cpp/blob/master/CONTRIBUTING.md#emoji) for how to efficiently discuss changes. It is best to be clear and explicit with your feedback. Please be patient with people who might not understand the finer details about your approach to feedback. + +#### Copying Changes from Other Projects + +Ryujinx uses some implementations and frameworks from other projects. The following rules must be followed for PRs that include changes from another project: + +- The license of the file is [permissive](https://en.wikipedia.org/wiki/Permissive_free_software_licence). +- The license of the file is left in-tact. +- The contribution is correctly attributed in the [3rd party notices](https://github.com/Ryujinx/Ryujinx/blob/master/distribution/legal/THIRDPARTY.md) file in the repository, as needed. + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 00000000..301024cf --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,52 @@ + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..818ddd76 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) Ryujinx Team and Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..7f2294d3 --- /dev/null +++ b/README.md @@ -0,0 +1,170 @@ +

+
+ Ryujinx +
+ Ryujinx +
+ (REE-YOU-JINX) +
+

+ +

+ Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#. + This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds. + It was written from scratch and development on the project began in September 2017. + Ryujinx is available on Github under the MIT license. +
+

+ +

+ + + + + + + + Discord + +
+
+ +

+ +## Compatibility + +As of May 2024, Ryujinx has been tested on approximately 4,300 titles; +over 4,100 boot past menus and into gameplay, with roughly 3,550 of those being considered playable. + +You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). + +Anyone is free to submit a new game test or update an existing game test entry; +simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. +Use the search function to see if a game has been tested already! + +## Usage + +To run this emulator, your PC must be equipped with at least 8GiB of RAM; +failing to meet this requirement may result in a poor gameplay experience or unexpected crashes. + +See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide) on how to set up the emulator. + +For our Local Wireless (LDN) builds, see our [Multiplayer: Local Play/Local Wireless Guide +](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide). + +Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information. + +## Latest build + +These builds are compiled automatically for each commit on the master branch. +While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken**. + +If you want to see details on updates to the emulator, you can visit our [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog). + +The latest automatic build for Windows, macOS, and Linux can be found on the [Official Website](https://ryujinx.org/download). + +## Documentation + +If you are planning to contribute or just want to learn more about this project please read through our [documentation](docs/README.md). + +## Building + +If you wish to build the emulator yourself, follow these steps: + +### Step 1 + +Install the [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0). +Make sure your SDK version is higher or equal to the required version specified in [global.json](global.json). + +### Step 2 + +Either use `git clone https://github.com/Ryujinx/Ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files. + +### Step 3 + +To build Ryujinx, open a command prompt inside the project directory. +You can quickly access it on Windows by holding shift in File Explorer, then right clicking and selecting `Open command window here`. +Then type the following command: `dotnet build -c Release -o build` +the built files will be found in the newly created build directory. + +Ryujinx system files are stored in the `Ryujinx` folder. +This folder is located in the user folder, which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI. + +## Features + +- **Audio** + + Audio output is entirely supported, audio input (microphone) isn't supported. + We use C# wrappers for [OpenAL](https://openal-soft.org/), and [SDL2](https://www.libsdl.org/) & [libsoundio](http://libsound.io/) as fallbacks. + +- **CPU** + + The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions, including partial 32-bit support. + It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code. + There are three memory manager options available depending on the user's preference, leveraging both software-based (slower) and host-mapped modes (much faster). + The fastest option (host, unchecked) is set by default. + Ryujinx also features an optional Profiled Persistent Translation Cache, which essentially caches translated functions so that they do not need to be translated every time the game loads. + The net result is a significant reduction in load times (the amount of time between launching a game and arriving at the title screen) for nearly every game. + NOTE: This feature is enabled by default in the Options menu > System tab. + You must launch the game at least twice to the title screen or beyond before performance improvements are unlocked on the third launch! + These improvements are permanent and do not require any extra launches going forward. + +- **GPU** + + The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. + There are currently six graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Anti-Aliasing, Scaling Filters (including FSR), Anisotropic Filtering and Aspect Ratio Adjustment. + These enhancements can be adjusted or toggled as desired in the GUI. + +- **Input** + + We currently have support for keyboard, mouse, touch input, JoyCon input support, and nearly all controllers. + Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required. + In all scenarios, you can set up everything inside the input configuration menu. + +- **DLC & Modifications** + + Ryujinx is able to manage add-on content/downloadable content through the GUI. + Mods (romfs, exefs, and runtime mods such as cheats) are also supported; + the GUI contains a shortcut to open the respective mods folder for a particular game. + +- **Configuration** + + The emulator has settings for enabling or disabling some logging, remapping controllers, and more. + You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI. + +## Contact + +If you have contributions, suggestions, need emulator support or just want to get in touch with the team, join our [Discord server](https://discord.com/invite/Ryujinx). +You may also review our [FAQ](https://github.com/Ryujinx/Ryujinx/wiki/Frequently-Asked-Questions). + +## Donations + +If you'd like to support the project financially, Ryujinx has an active Patreon campaign. + + + + + +All developers working on the project do so in their free time, but the project has several expenses: +* Hackable Nintendo Switch consoles to reverse-engineer the hardware +* Additional computer hardware for testing purposes (e.g. GPUs to diagnose graphical bugs, etc.) +* Licenses for various software development tools (e.g. Jetbrains, IDA) +* Web hosting and infrastructure maintenance (e.g. LDN servers) + +All funds received through Patreon are considered a donation to support the project. Patrons receive early access to progress reports and exclusive access to developer interviews. + +## License + +This software is licensed under the terms of the [MIT license](LICENSE.txt). +This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3. +See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY.md) for more details. + +## Credits + +- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system. +- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation. +- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes. +- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation. diff --git a/Ryujinx.sln b/Ryujinx.sln new file mode 100644 index 00000000..76ebd573 --- /dev/null +++ b/Ryujinx.sln @@ -0,0 +1,265 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32228.430 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Gtk3", "src\Ryujinx.Gtk3\Ryujinx.Gtk3.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Unicorn", "src\Ryujinx.Tests.Unicorn\Ryujinx.Tests.Unicorn.csproj", "{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE", "src\Ryujinx.HLE\Ryujinx.HLE.csproj", "{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.ShaderTools", "src\Ryujinx.ShaderTools\Ryujinx.ShaderTools.csproj", "{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Common", "src\Ryujinx.Common\Ryujinx.Common.csproj", "{5FD4E4F6-8928-4B3C-BE07-28A675C17226}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ARMeilleure", "src\ARMeilleure\ARMeilleure.csproj", "{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Gpu", "src\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj", "{ADA7EA87-0D63-4D97-9433-922A2124401F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.GAL", "src\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj", "{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.OpenGL", "src\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj", "{9558FB96-075D-4219-8FFF-401979DC0B69}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Texture", "src\Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj", "{E1B1AD28-289D-47B7-A106-326972240207}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Shader", "src\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj", "{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec", "src\Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj", "{85A0FA56-DC01-4A42-8808-70DAC76BD66D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "src\Ryujinx.Audio\Ryujinx.Audio.csproj", "{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + Directory.Packages.props = Directory.Packages.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Memory", "src\Ryujinx.Memory\Ryujinx.Memory.csproj", "{A5E6C691-9E22-4263-8F40-42F002CE66BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Memory", "src\Ryujinx.Tests.Memory\Ryujinx.Tests.Memory.csproj", "{D1CC5322-7325-4F6B-9625-194B30BE1296}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Cpu", "src\Ryujinx.Cpu\Ryujinx.Cpu.csproj", "{3DF35E3D-D844-4399-A9A1-A9E923264C17}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Device", "src\Ryujinx.Graphics.Device\Ryujinx.Graphics.Device.csproj", "{C3002C3C-7B09-4FE7-894A-372EDA22FC6E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Host1x", "src\Ryujinx.Graphics.Host1x\Ryujinx.Graphics.Host1x.csproj", "{C35F1536-7DE5-4F9D-9604-B5B4E1561947}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.Vp9", "src\Ryujinx.Graphics.Nvdec.Vp9\Ryujinx.Graphics.Nvdec.Vp9.csproj", "{B9AECA11-E248-4886-A10B-81B631CAAF29}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vic", "src\Ryujinx.Graphics.Vic\Ryujinx.Graphics.Vic.csproj", "{81BB2C11-9408-4EA3-822E-42987AF54429}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Video", "src\Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj", "{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.OpenAL", "src\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj", "{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SoundIo", "src\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj", "{716364DE-B988-41A6-BAB4-327964266ECC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input", "src\Ryujinx.Input\Ryujinx.Input.csproj", "{C16F112F-38C3-40BC-9F5F-4791112063D6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input.SDL2", "src\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj", "{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.SDL2.Common", "src\Ryujinx.SDL2.Common\Ryujinx.SDL2.Common.csproj", "{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SDL2", "src\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj", "{D99A395A-8569-4DB0-B336-900647890052}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "src\Ryujinx.Headless.SDL2\Ryujinx.Headless.SDL2.csproj", "{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.Common", "src\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Generators", "src\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vulkan", "src\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj", "{D4D09B08-D580-4D69-B886-C35D2853F6C8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spv.Generator", "src\Spv.Generator\Spv.Generator.csproj", "{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.LocaleGenerator", "src\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj", "{77D01AD9-2C98-478E-AE1D-8F7100738FB4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Common", "src\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj", "{77F96ECE-4952-42DB-A528-DED25572A573}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryujinx.Horizon\Ryujinx.Horizon.csproj", "{AF34127A-3A92-43E5-8496-14960A50B1F1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Release|Any CPU.Build.0 = Release|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|Any CPU.Build.0 = Release|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Release|Any CPU.Build.0 = Release|Any CPU + {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|Any CPU.Build.0 = Release|Any CPU + {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|Any CPU.Build.0 = Release|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.Build.0 = Release|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.Build.0 = Release|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|Any CPU.Build.0 = Release|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|Any CPU.Build.0 = Release|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9558FB96-075D-4219-8FFF-401979DC0B69}.Release|Any CPU.Build.0 = Release|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1B1AD28-289D-47B7-A106-326972240207}.Release|Any CPU.Build.0 = Release|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|Any CPU.Build.0 = Release|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.Build.0 = Release|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Debug|Any CPU.Build.0 = Debug|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Release|Any CPU.ActiveCfg = Release|Any CPU + {806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Release|Any CPU.Build.0 = Release|Any CPU + {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|Any CPU.Build.0 = Release|Any CPU + {D1CC5322-7325-4F6B-9625-194B30BE1296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1CC5322-7325-4F6B-9625-194B30BE1296}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1CC5322-7325-4F6B-9625-194B30BE1296}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1CC5322-7325-4F6B-9625-194B30BE1296}.Release|Any CPU.Build.0 = Release|Any CPU + {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DF35E3D-D844-4399-A9A1-A9E923264C17}.Release|Any CPU.Build.0 = Release|Any CPU + {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Release|Any CPU.Build.0 = Release|Any CPU + {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Release|Any CPU.Build.0 = Release|Any CPU + {B9AECA11-E248-4886-A10B-81B631CAAF29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9AECA11-E248-4886-A10B-81B631CAAF29}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9AECA11-E248-4886-A10B-81B631CAAF29}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9AECA11-E248-4886-A10B-81B631CAAF29}.Release|Any CPU.Build.0 = Release|Any CPU + {81BB2C11-9408-4EA3-822E-42987AF54429}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81BB2C11-9408-4EA3-822E-42987AF54429}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81BB2C11-9408-4EA3-822E-42987AF54429}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81BB2C11-9408-4EA3-822E-42987AF54429}.Release|Any CPU.Build.0 = Release|Any CPU + {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|Any CPU.Build.0 = Release|Any CPU + {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Release|Any CPU.Build.0 = Release|Any CPU + {716364DE-B988-41A6-BAB4-327964266ECC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {716364DE-B988-41A6-BAB4-327964266ECC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {716364DE-B988-41A6-BAB4-327964266ECC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {716364DE-B988-41A6-BAB4-327964266ECC}.Release|Any CPU.Build.0 = Release|Any CPU + {C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|Any CPU.Build.0 = Release|Any CPU + {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.Build.0 = Release|Any CPU + {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Release|Any CPU.Build.0 = Release|Any CPU + {D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.Build.0 = Release|Any CPU + {390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Release|Any CPU.ActiveCfg = Release|Any CPU + {390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Release|Any CPU.Build.0 = Release|Any CPU + {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|Any CPU.Build.0 = Release|Any CPU + {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|Any CPU.Build.0 = Release|Any CPU + {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.Build.0 = Release|Any CPU + {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|Any CPU.Build.0 = Release|Any CPU + {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4D09B08-D580-4D69-B886-C35D2853F6C8}.Release|Any CPU.Build.0 = Release|Any CPU + {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Release|Any CPU.Build.0 = Release|Any CPU + {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Release|Any CPU.Build.0 = Release|Any CPU + {77F96ECE-4952-42DB-A528-DED25572A573}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77F96ECE-4952-42DB-A528-DED25572A573}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77F96ECE-4952-42DB-A528-DED25572A573}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77F96ECE-4952-42DB-A528-DED25572A573}.Release|Any CPU.Build.0 = Release|Any CPU + {AF34127A-3A92-43E5-8496-14960A50B1F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF34127A-3A92-43E5-8496-14960A50B1F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF34127A-3A92-43E5-8496-14960A50B1F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF34127A-3A92-43E5-8496-14960A50B1F1}.Release|Any CPU.Build.0 = Release|Any CPU + {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU + {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {110169B3-3328-4730-8AB0-BA05BEF75C1A} + EndGlobalSection +EndGlobal diff --git a/Ryujinx.sln.DotSettings b/Ryujinx.sln.DotSettings new file mode 100644 index 00000000..ed7f3e91 --- /dev/null +++ b/Ryujinx.sln.DotSettings @@ -0,0 +1,23 @@ + + WARNING + WARNING + UseExplicitType + UseExplicitType + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></Policy> + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + \ No newline at end of file diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 00000000..279cd216 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,3 @@ +files: + - source: /**/Assets/Locales/en_US.json + translation: /**/Assets/Locales/%locale_with_underscore%.json diff --git a/distribution/legal/THIRDPARTY.md b/distribution/legal/THIRDPARTY.md new file mode 100644 index 00000000..5caa0377 --- /dev/null +++ b/distribution/legal/THIRDPARTY.md @@ -0,0 +1,713 @@ +# ffmpeg (LGPLv3) +
+ See License + + ``` + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates + the terms and conditions of version 3 of the GNU General Public + License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser + General Public License, and the "GNU GPL" refers to version 3 of the GNU + General Public License. + + "The Library" refers to a covered work governed by this License, + other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided + by the Library, but which is not otherwise based on the Library. + Defining a subclass of a class defined by the Library is deemed a mode + of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an + Application with the Library. The particular version of the Library + with which the Combined Work was made is also called the "Linked + Version". + + The "Minimal Corresponding Source" for a Combined Work means the + Corresponding Source for the Combined Work, excluding any source code + for portions of the Combined Work that, considered in isolation, are + based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the + object code and/or source code for the Application, including any data + and utility programs needed for reproducing the Combined Work from the + Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License + without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a + facility refers to a function or data to be supplied by an Application + that uses the facility (other than as an argument passed when the + facility is invoked), then you may convey a copy of the modified + version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from + a header file that is part of the Library. You may convey such object + code under terms of your choice, provided that, if the incorporated + material is not limited to numerical parameters, data structure + layouts and accessors, or small macros, inline functions and templates + (ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, + taken together, effectively do not restrict modification of the + portions of the Library contained in the Combined Work and reverse + engineering for debugging such modifications, if you also do each of + the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the + Library side by side in a single library together with other library + facilities that are not Applications and are not covered by this + License, and convey such a combined library under terms of your + choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions + of the GNU Lesser General Public License from time to time. Such new + versions will be similar in spirit to the present version, but may + differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the + Library as you received it specifies that a certain numbered version + of the GNU Lesser General Public License "or any later version" + applies to it, you have the option of following the terms and + conditions either of that published version or of any later version + published by the Free Software Foundation. If the Library as you + received it does not specify a version number of the GNU Lesser + General Public License, you may choose any version of the GNU Lesser + General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide + whether future versions of the GNU Lesser General Public License shall + apply, that proxy's public statement of acceptance of any version is + permanent authorization for you to choose that version for the + Library. + ``` +
+ +# libvpx (BSD) +
+ See License + + ``` + Copyright (c) 2010, The WebM Project authors. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google, nor the WebM Project, nor the names + of its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ``` +
+ +# Atmosphère (MIT) +
+ See License + + ``` + MIT License + + Copyright (c) 2018-2020 Atmosphère-NX + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + ``` +
+ +# OpenAL Soft (LGPLv2) +
+ See License + + ``` + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + [This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your + freedom to share and change it. By contrast, the GNU General Public + Licenses are intended to guarantee your freedom to share and change + free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some + specially designated Free Software Foundation software, and to any + other libraries whose authors decide to use it. You can use it for + your libraries, too. + + When we speak of free software, we are referring to freedom, not + price. Our General Public Licenses are designed to make sure that you + have the freedom to distribute copies of free software (and charge for + this service if you wish), that you receive source code or can get it + if you want it, that you can change the software or use pieces of it + in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid + anyone to deny you these rights or to ask you to surrender the rights. + These restrictions translate to certain responsibilities for you if + you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis + or for a fee, you must give the recipients all the rights that we gave + you. You must make sure that they, too, receive or can get the source + code. If you link a program with the library, you must provide + complete object files to the recipients so that they can relink them + with the library, after making changes to the library and recompiling + it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright + the library, and (2) offer you this license which gives you legal + permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain + that everyone understands that there is no warranty for this free + library. If the library is modified by someone else and passed on, we + want its recipients to know that what they have is not the original + version, so that any problems introduced by others will not reflect on + the original authors' reputations. + + Finally, any free program is threatened constantly by software + patents. We wish to avoid the danger that companies distributing free + software will individually obtain patent licenses, thus in effect + transforming the program into proprietary software. To prevent this, + we have made it clear that any patent must be licensed for everyone's + free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary + GNU General Public License, which was designed for utility programs. This + license, the GNU Library General Public License, applies to certain + designated libraries. This license is quite different from the ordinary + one; be sure to read it in full, and don't assume that anything in it is + the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that + they blur the distinction we usually make between modifying or adding to a + program and simply using it. Linking a program with a library, without + changing the library, is in some sense simply using the library, and is + analogous to running a utility program or application program. However, in + a textual and legal sense, the linked executable is a combined work, a + derivative of the original library, and the ordinary General Public License + treats it as such. + + Because of this blurred distinction, using the ordinary General + Public License for libraries did not effectively promote software + sharing, because most developers did not use the libraries. We + concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the + users of those programs of all benefit from the free status of the + libraries themselves. This Library General Public License is intended to + permit developers of non-free programs to use free libraries, while + preserving your freedom as a user of such programs to change the free + libraries that are incorporated in them. (We have not seen how to achieve + this as regards changes in header files, but we have achieved it as regards + changes in the actual functions of the Library.) The hope is that this + will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and + modification follow. Pay close attention to the difference between a + "work based on the library" and a "work that uses the library". The + former contains code derived from the library, while the latter only + works together with the library. + + Note that it is possible for a library to be covered by the ordinary + General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which + contains a notice placed by the copyright holder or other authorized + party saying it may be distributed under the terms of this Library + General Public License (also called "this License"). Each licensee is + addressed as "you". + + A "library" means a collection of software functions and/or data + prepared so as to be conveniently linked with application programs + (which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work + which has been distributed under these terms. A "work based on the + Library" means either the Library or any derivative work under + copyright law: that is to say, a work containing the Library or a + portion of it, either verbatim or with modifications and/or translated + straightforwardly into another language. (Hereinafter, translation is + included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for + making modifications to it. For a library, complete source code means + all the source code for all modules it contains, plus any associated + interface definition files, plus the scripts used to control compilation + and installation of the library. + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of + running a program using the Library is not restricted, and output from + such a program is covered only if its contents constitute a work based + on the Library (independent of the use of the Library in a tool for + writing it). Whether that is true depends on what the Library does + and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's + complete source code as you receive it, in any medium, provided that + you conspicuously and appropriately publish on each copy an + appropriate copyright notice and disclaimer of warranty; keep intact + all the notices that refer to this License and to the absence of any + warranty; and distribute a copy of this License along with the + Library. + + You may charge a fee for the physical act of transferring a copy, + and you may at your option offer warranty protection in exchange for a + fee. + + 2. You may modify your copy or copies of the Library or any portion + of it, thus forming a work based on the Library, and copy and + distribute such modifications or work under the terms of Section 1 + above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Library, + and can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based + on the Library, the distribution of the whole must be on the terms of + this License, whose permissions for other licensees extend to the + entire whole, and thus to each and every part regardless of who wrote + it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Library. + + In addition, mere aggregation of another work not based on the Library + with the Library (or with a work based on the Library) on a volume of + a storage or distribution medium does not bring the other work under + the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public + License instead of this License to a given copy of the Library. To do + this, you must alter all the notices that refer to this License, so + that they refer to the ordinary GNU General Public License, version 2, + instead of to this License. (If a newer version than version 2 of the + ordinary GNU General Public License has appeared, then you can specify + that version instead if you wish.) Do not make any other change in + these notices. + + Once this change is made in a given copy, it is irreversible for + that copy, so the ordinary GNU General Public License applies to all + subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of + the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or + derivative of it, under Section 2) in object code or executable form + under the terms of Sections 1 and 2 above provided that you accompany + it with the complete corresponding machine-readable source code, which + must be distributed under the terms of Sections 1 and 2 above on a + medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy + from a designated place, then offering equivalent access to copy the + source code from the same place satisfies the requirement to + distribute the source code, even though third parties are not + compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the + Library, but is designed to work with the Library by being compiled or + linked with it, is called a "work that uses the Library". Such a + work, in isolation, is not a derivative work of the Library, and + therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library + creates an executable that is a derivative of the Library (because it + contains portions of the Library), rather than a "work that uses the + library". The executable is therefore covered by this License. + Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file + that is part of the Library, the object code for the work may be a + derivative work of the Library even though the source code is not. + Whether this is true is especially significant if the work can be + linked without the Library, or if the work is itself a library. The + threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data + structure layouts and accessors, and small macros and small inline + functions (ten lines or less in length), then the use of the object + file is unrestricted, regardless of whether it is legally a derivative + work. (Executables containing this object code plus portions of the + Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may + distribute the object code for the work under the terms of Section 6. + Any executables containing that work also fall under Section 6, + whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or + link a "work that uses the Library" with the Library to produce a + work containing portions of the Library, and distribute that work + under terms of your choice, provided that the terms permit + modification of the work for the customer's own use and reverse + engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the + Library is used in it and that the Library and its use are covered by + this License. You must supply a copy of this License. If the work + during execution displays copyright notices, you must include the + copyright notice for the Library among them, as well as a reference + directing the user to the copy of this License. Also, you must do one + of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the + Library" must include any data and utility programs needed for + reproducing the executable from it. However, as a special exception, + the source code distributed need not include anything that is normally + distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies + the executable. + + It may happen that this requirement contradicts the license + restrictions of other proprietary libraries that do not normally + accompany the operating system. Such a contradiction means you cannot + use both them and the Library together in an executable that you + distribute. + + 7. You may place library facilities that are a work based on the + Library side-by-side in a single library together with other library + facilities not covered by this License, and distribute such a combined + library, provided that the separate distribution of the work based on + the Library and of the other library facilities is otherwise + permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute + the Library except as expressly provided under this License. Any + attempt otherwise to copy, modify, sublicense, link with, or + distribute the Library is void, and will automatically terminate your + rights under this License. However, parties who have received copies, + or rights, from you under this License will not have their licenses + terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Library or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Library (or any work based on the + Library), you indicate your acceptance of this License to do so, and + all its terms and conditions for copying, distributing or modifying + the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the + Library), the recipient automatically receives a license from the + original licensor to copy, distribute, link with or modify the Library + subject to these terms and conditions. You may not impose any further + restrictions on the recipients' exercise of the rights granted herein. + You are not responsible for enforcing compliance by third parties to + this License. + + 11. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot + distribute so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you + may not distribute the Library at all. For example, if a patent + license would not permit royalty-free redistribution of the Library by + all those who receive copies directly or indirectly through you, then + the only way you could satisfy both it and this License would be to + refrain entirely from distribution of the Library. + + If any portion of this section is held invalid or unenforceable under any + particular circumstance, the balance of the section is intended to apply, + and the section as a whole is intended to apply in other circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system which is + implemented by public license practices. Many people have made + generous contributions to the wide range of software distributed + through that system in reliance on consistent application of that + system; it is up to the author/donor to decide if he or she is willing + to distribute software through any other system and a licensee cannot + impose that choice. + + This section is intended to make thoroughly clear what is believed to + be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Library under this License may add + an explicit geographical distribution limitation excluding those countries, + so that distribution is permitted only in or among countries not thus + excluded. In such case, this License incorporates the limitation as if + written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new + versions of the Library General Public License from time to time. + Such new versions will be similar in spirit to the present version, + but may differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Library + specifies a version number of this License which applies to it and + "any later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Library does not specify a + license version number, you may choose any version ever published by + the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free + programs whose distribution conditions are incompatible with these, + write to the author to ask for permission. For software which is + copyrighted by the Free Software Foundation, write to the Free + Software Foundation; we sometimes make exceptions for this. Our + decision will be guided by the two goals of preserving the free status + of all derivatives of our free software and of promoting the sharing + and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE + LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME + THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU + FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR + CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE + LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING + RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A + FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF + SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + DAMAGES. + + END OF TERMS AND CONDITIONS + ``` +
+ +# ShellLink (MIT) +
+ See License + + ``` + MIT License + + Copyright (c) 2017 Yorick Koster, Securify B.V. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + ``` +
diff --git a/distribution/linux/Ryujinx.desktop b/distribution/linux/Ryujinx.desktop new file mode 100644 index 00000000..44f05bf3 --- /dev/null +++ b/distribution/linux/Ryujinx.desktop @@ -0,0 +1,14 @@ +[Desktop Entry] +Version=1.0 +Name=Ryujinx +Type=Application +Icon=Ryujinx +Exec=Ryujinx.sh %f +Comment=A Nintendo Switch Emulator +GenericName=Nintendo Switch Emulator +Terminal=false +Categories=Game;Emulator; +MimeType=application/x-nx-nca;application/x-nx-nro;application/x-nx-nso;application/x-nx-nsp;application/x-nx-xci; +Keywords=Switch;Nintendo;Emulator; +StartupWMClass=Ryujinx +PrefersNonDefaultGPU=true diff --git a/distribution/linux/Ryujinx.sh b/distribution/linux/Ryujinx.sh new file mode 100755 index 00000000..30eb1439 --- /dev/null +++ b/distribution/linux/Ryujinx.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +SCRIPT_DIR=$(dirname "$(realpath "$0")") + +if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then + RYUJINX_BIN="Ryujinx.Headless.SDL2" +fi + +if [ -f "$SCRIPT_DIR/Ryujinx" ]; then + RYUJINX_BIN="Ryujinx" +fi + +if [ -z "$RYUJINX_BIN" ]; then + exit 1 +fi + +COMMAND="env DOTNET_EnableAlternateStackCheck=1" + +if command -v gamemoderun > /dev/null 2>&1; then + COMMAND="$COMMAND gamemoderun" +fi + +exec $COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@" diff --git a/distribution/linux/mime/Ryujinx.xml b/distribution/linux/mime/Ryujinx.xml new file mode 100644 index 00000000..bd9df0ed --- /dev/null +++ b/distribution/linux/mime/Ryujinx.xml @@ -0,0 +1,33 @@ + + + + Nintendo Content Archive + NCA + + + + + Nintendo Relocatable Object + NRO + + + + + Nintendo Shared Object + NSO + + + + + Nintendo Submission Package + NSP + + + + + Nintendo Switch Cartridge + XCI + + + + diff --git a/distribution/linux/shortcut-template.desktop b/distribution/linux/shortcut-template.desktop new file mode 100644 index 00000000..6bee0f8d --- /dev/null +++ b/distribution/linux/shortcut-template.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Version=1.0 +Name={0} +Type=Application +Icon={1} +Exec={2} %f +Comment=Nintendo Switch application +GenericName=Nintendo Switch Emulator +Terminal=false +Categories=Game;Emulator; +Keywords=Switch;Nintendo;Emulator; +StartupWMClass=Ryujinx +PrefersNonDefaultGPU=true diff --git a/distribution/macos/Info.plist b/distribution/macos/Info.plist new file mode 100644 index 00000000..53929f95 --- /dev/null +++ b/distribution/macos/Info.plist @@ -0,0 +1,169 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + Ryujinx + CFBundleGetInfoString + Ryujinx + CFBundleIconFile + Ryujinx.icns + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + nca + nro + nso + nsp + xci + + CFBundleTypeName + Nintendo Switch File + CFBundleTypeRole + Viewer + LSHandlerRank + Default + + + CFBundleIdentifier + org.ryujinx.Ryujinx + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + %%RYUJINX_BUILD_VERSION%%-%%RYUJINX_BUILD_GIT_HASH%%" + CFBundleName + Ryujinx + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.1 + CFBundleSignature + ???? + CFBundleVersion + 1.1.0 + NSHighResolutionCapable + + CSResourcesFileMapped + + NSHumanReadableCopyright + Copyright © 2018 - 2023 Ryujinx Team and Contributors. + LSApplicationCategoryType + public.app-category.games + LSMinimumSystemVersion + 12.0 + UTExportedTypeDeclarations + + + UTTypeDescription + Extensible Application Markup Language + UTTypeConformsTo + + public.xml + + UTTypeIdentifier + com.ryujinx.xaml + UTTypeTagSpecification + + public.filename-extension + + xaml + + + + + UTTypeDescription + Nintendo Submission Package + UTTypeConformsTo + + public.data + + UTTypeIdentifier + com.ryujinx.nsp + UTTypeTagSpecification + + public.filename-extension + + nsp + + + + + UTTypeDescription + Nintendo Switch Cartridge + UTTypeConformsTo + + public.data + + UTTypeIdentifier + com.ryujinx.xci + UTTypeTagSpecification + + public.filename-extension + + xci + + + + + UTTypeDescription + Nintendo Content Archive + UTTypeConformsTo + + public.data + + UTTypeIdentifier + com.ryujinx.nca + UTTypeTagSpecification + + public.filename-extension + + nca + + + + + UTTypeDescription + Nintendo Relocatable Object + UTTypeConformsTo + + public.data + + UTTypeIdentifier + com.ryujinx.nro + UTTypeTagSpecification + + public.filename-extension + + nro + + + + + UTTypeDescription + Nintendo Shared Object + UTTypeConformsTo + + public.data + + UTTypeIdentifier + com.ryujinx.nso + UTTypeTagSpecification + + public.filename-extension + + nso + + + + + LSEnvironment + + DOTNET_DefaultStackSize + 200000 + + + diff --git a/distribution/macos/Ryujinx.icns b/distribution/macos/Ryujinx.icns new file mode 100644 index 0000000000000000000000000000000000000000..f54a9aeb7e4780b09c1bddb1ad8b7fcf4f9a0d3c GIT binary patch literal 108982 zcmeFaby!qE`#*ekSzzg? zchTqje1F&VzJLDyd%Z4}GoN$MnYr(oIaBv%XYDMU+yPMOXFH1rya0eX9;L1-kB3c( z4FCY1qJoSj>I%R8KrvAN%j{fdP#2K9ro0sJVUT(q1!S!B6s=WM0CrRu3V_%@=PhEyd=13^U@No{Y?vVnsmsVs@OjpuH@ zy3%h*3Fj{4Ui=<5E9j*uF0|?mD}f3ciOQK72QyLN9i-uds}X3_9fin5ou~(f>XBFr z1j$6Kef=_(tOSHhD!HP5zb}70%KF*wwEMa7MZ(UE+b-kj;NX_U%H_$oH21-8_e53g zZTH$6+t|Q0KjCf<>BvG}-QC57fGkFoEcDlF@2Z`AqipPVdd8x1|Lez?oy}cvkK)`Z znvB7DCGnn-c*$y(vRX@%LpzVR1+iUgs!EG1TF$4!)q6f|St(B zv_A+Fxh!3{B<9e3!{|dFz2n5llp7lakC1Y&l4P)96fJ_>LMmXkFbyr=mcH zFjx;A4b|^A9vpvxvTVe(!kuofp4lfeBB?B+?Epp{4v%W%Dm%?#Z z1VE9W+0vded542tj!K4nsn~V%UzL@kk)&I_T`yhzdMRW9vAyKhjZ9ft)IUl)$5-zs z*OADRO{F>Or{C9)r3DWfjQ$&?Tz~qnb=8%;2*( zQCcq)R4*Ywc5t~wKO9KaAk5=Jbmnh}H|X0fJ8j@%GV1n@5F>T3;rFX8pS*+u!py>N zno$c&4bS|rPp)bbhe)e$;slIpgGf(oF(l z%F;e?*HkWTHdPc)*HfbH+2wSPO&Mm?OuAyG7E` za4-q+n*E}=l2?vsF>dIgG_#uIou6RB;QH|dO#%(Sagja&O`c+nj7kjMI#pN9_e`t8W@zl z$+?I5&dk}P@QrUmwNzns@+~7pO(f5LLeXEy5-c9Q8`H~{O%L`xr4SPu5%Nv2wBU|c zfsk(M(mQ606Y@XH!_r=)n3J?52-^^-15NoB`9WN8pl+m74XxoZ-(Q62KD1@Reh~T$ z@er*d>npyOGtMxY=cYxh>hJw%5_)6<`rADB*|Zw&oYRcfD=icM~C zKS`$NZaqpm_A74caLZDzaU9XN-sGRyQfJOB?iFG3-ZQ5VfiGgM^H7PN7!=l~##mw6 z-g(^WoOLhg#~T}nEm4cGOlv|~ip3uk8lynQKVE`wRzm9b!N&vC!>`fk=Mv1CAPSX7ch z0JK&6)!Ip1vRU+NVJv{g^@F+GX27P2{w)G>B#yEF+Mn>Q zwlbE~?HLe~{WAV&HF8|;mAp2rE--*E!g!f(f_=Fw?Zz8_c#aVPqT*9(j_|%h)56wP zzt^u-3|&6@s3!EXnlg+`7xc|hHIpa@=wC;hjMno2?S2pjwTc2abbXA{gx|;2MA1ct z#?sr{#|qgpSZQm>J&{Hgc&x^(iaO9dP50Kem_5CCgR=S!WT&dQ2-m2kE}1#gzzH4PYKAk%v_hZt<6!NM5r`p zn}i3A!DFiu!Z<=BPkBnf4qjK^Y5Y%!as+hfc3BhMa5{+jsyzLECHQ%+1BdzC^+OJ^ zo8C-LRW$xGg8C49ZD5tRb@U%nTFU|@CX6`EbaeeP)DGmA^r^N z?c!$rDDZz}P)Gb9gZga_fI=L^3jhc%{|@TgMh*$uouuN^Ue=zT;gY(oGDimvvZ40R95Y~(cp{06U(AVq}Ae|XY>iDd66c9 z8!>>{v6?WcdB4a*dtR}o$alhGvima)Db5$(6feqY80pK|Xm(X}l0Pf6CDcOZ-o6dO zOKg6%kZG-BQuK|ZK;K6ZIJ)}HGs5?cJFLwrtJnr`ygF0Dev$I_*gwtvW6OpQn(1hk zSw?k4oJ*vuQEQ%=`E4T=8AE=u%$!B$}BAO5-2CIN{ z0UkvY`rr5-?JA4Z*p_NwLBXu3Myh01tmF?^0eJ{R(TZ^M+>H>Eq<3UI72SH$?RM3l ztx_3^zE|zk6f$?QPZHXXo*eR^EMi=LK;{n};D+nRDCAEzX8BtB*^}Cob*t z$uEw!n;n73C79l&l&_Zj?94MaV!RehUm@`qqf*OOTkdCuajNIUAMLWQ2JsveE1N%d zXJ44ib9=g34}heo^_!ct>na{%YX&Mxap#W*_DD(C8)T-(m(u^7NIs|{3DL&tedacT zciQVbDQ!+p^AS)#D_0)OQ-^~TgC#YCKEC_iV{X7B<6l1f5f8vFG%P(VM8F+!1y)5d z>?);Z^k*w-$9aJ@&dxs0Mw)v({CX3oC7EKAzv)|noT)qdwgF?6ru+So)vL>#hnoih zu^@QUs{`qJM&@X;=E$T8zJsqg5vy9LlJPHDD(1(A&^ur=ZNGLK$+J~^A_%NSNd?AC zCbiV3FI9c@D{BJ5)kqE;8iX@D&$kbXF1URkwgN5D!hvyGeKuXv>av`v$H$Mh2{C{` zYd5v$x|AIW4`17_z5S9z2`uF)t7qXbVXw&_e0C_CO1A{&X}!ER$TB)|<362k zb$cgAY|)f2`S8C;OA}!mXV@S#)o6zSOtTSn%=Wp+cnEar4{gkdq9Jxjuoj##f$Pd# zMjYU$NJxkRFv|Rp(DH|+K#mAo3}j6;(G(ee`1zYDkf0d<^3n=WcjXvztOUWC#n^XJ zLGaEh(dyRMU$DJZ!r45bZ<)lIC_Rb_qNL+3?l%~7*d%qIGgSoBT)%UXLB7;c^sSs1 z^4wfi-aUzP3(!dIz5)jM`#xWvQ@m##tNy8__Wj^{w+)x)ksd1z@qm7xH z-ZQOvP))iy8PT#+KD^ki`Q*u6hM!V`JU>*XLmSTPZy0~R7H+hPeW7_x{Mj*W;(h(< z-sET>75J>~!4F2)I8QIk+_(=7t;trN;}Ay5T(#Abz@Y*!-=WmkCYGIwzj7j>U4aD6!47)t61n|EbJ}q|KT#j%B@VG! zE#@bA1f`j~$I83aJulG!3&(o0)m)gOs`CiB+=_>5T%!kJB(l^c7N>>voO@O@)8_bb zaAqI4CHz{t5Egfc)Ab<3;H_AcG1Z6moFIHg^dt}v^Vz%T)XdTfgkWN6-CTza49gUg zXf|#1@+H1`nJl2uxS;lnNYnJKjW4a74hbN%Iy%2cDMm5yd7BJ7+9*qOkzealY50a{ z3H0)Xm)~ttvLJxrTzUIICl)CqVtZj*0-i=YQHRUIQ*G_$l>x8H@p6M{t;e{1obz2Rtjq)(8|+#KRP9?LK4%-$s)v(A8^yEG?JM`2nU;fnv;_ z`5r?j_ZolQS$B;0)N~bB)~BcRt9j8?d)JdhdfI2xu}E}ZXL-B75O^q2x$RY&IpZ3| zc`1K`(NaB7r~m51#5djaMnlQ>i=$%M=0qjB;9MRR2fG3VBEjsZ>jo>Nda7|4cZA&< zpANCiY3}+=RIb!Ka{p%JJ!^4=hsZr)WqwCy$Q{j<(N&3C&tn^8#p$~%bFIe-O71=7 z>68fJaNNl4f@H9TMLh>8G%Ca%6W1FvbbLKSvWoxYg+B^j$}O({ez8Rxgf?eF(Cj?> zoP1&<$5-`6dbYOErwRAP{U8gBzgO9$BD^i_0L9 zUr{Yj<(V7l2ndz*Jn{Y6-YWh1b-*+h6(5Az<|X%9|08j1DyeTFvO|QEjN&0ZI%1T; zW>}!?x7b)L;)Cw*0Ft~{Yveq@U7?dE)yx#G*TwV7_Ljbg7(L zM|CpMK{ngXneT1Y`F>?wc@aU`nbH`*B7O!2J;IqRcm-#n{#!e9ce!(3GU0_IdkgT*bO21F2K;h1Xb! zW1?fHCK|ElZ)T?7CSL&ZMIhTsUSRz0wamm_k#9#ka~{hy-es*xzO)vhT1e}F`+idQ zZixJrvYV`$?2{!x>eKv*GPW zfwwi!1A_)camQpcv4vct34&nqx%+~_fR#||_j4n(vkHvUXpaJ-Nl$a~n+?*IS1DbL z6K8!u!H+ELP#_`7hzK5;87Y+X^4potppN4{Mg35C%&Pd+{A1t;pPV?Rwb-VtvxD>~ z`03qeE>7a9uB|`7>5nuw0VjF6V?}~GyTu3|V7kKjaWY3ZR=fYn4=ol)jCHoq<7SD} zU>f-^T3fAg**|?_bfv=z2yA=y8%yh`XWeUzz~1A#Thb#q*Do;!4`XbDU=#B?-%0-dSJ z@F6mKO_xWZZF&pqjfh?}619fKG?r^f!Yf)W8~(lIW)d+l8>r=!9}V_94v%i9|B*U~ z1@ZcxSvnaq4z%{{v;DXY8yNjs#2ff=u?|p!nk)MOnVRwBsOxu$2+4F1t2Yax@fWQq z(D8dHLei#Sh8-UbW+O%oA{J1^eY%GR$xQeT>6^<0KCl8V{pph9pA5@=lA}HsK%GzT z0e2+Gv2%#2;GG%^Kls!o>Ai^%W{*;px6lYq%_M8HCd6D?lCxvv<_!_5@gCfx`FNOO zqnI4LF6F>TSE}awy2poXglcQ=7mbx2Y#x^cMd)8yoFkutDGNL<5xRr$4s?9L<2Z7$ zaUPdHkVZtw_mDZL2yX^9rzZaD-2?`}#1P;^;s~hI__IFc#08Y(E)H0K06C&(>L2wj zK|t~JZ@!bR*YE6$=FHXUaDd^MyJ3{1HVc`Bt`FGF`dIIxe+0p-VuvI79~l%q)_tJm z=E|CY2Xy-!q{MKJc9ur76+%dQFaUPYdh^6LG^LhUA`;X{@wNK--NEn8HU)4jA9=9z;9zqMHbTBLPoJ1 zT&{QgV3{Nh`~`$~`J-C?8coEWHn+sB+Cd6-J6#_)rBC*1BVIMoei1F80;NXxg#ZKZ z9Y_Vf5`J|&eR~d{JIgN4Zh1WPi%D3d88i(q7k42P71nl)_DuoUz8ea7hE@{e@UVy$ z>`p6+3C=bZ!EXTdfef(=s}Tw$`{2;_17c~D!^ZN1Ml?j9=lUC6*@GXcmiKN#FeJIi zIgMoB1ofqX8!-{~0~zioy>auC(nkK6fdSagMuV*{-JD8G-`+>ZMnm|HlX9?G$6bZO zqA12FkT|bo<@sS_ZW=zrnn{w=3nQuVCp+}m75NUy_b<#+`oA`1!yZi7TJ`KXA_PI@~#*kOZ@^=?*iXvMIB(32)yll75pRO_voarldk(4_wpDXvIzNt4aHsAr)C_n#f0F*%gXeZmW{zH;jvEPnH0r>eS>p66Qj z=t{yOc6POV8xMFn&@+=#>?;~)Gk=JCR9{}o{xSBww|S7WN%H+`Bw0SNfBF=6OdVttGLFL)b3!2fPc+~xoPHXD?R0C4Z^e>NsE zabA&(r*$_QJeeK<@=6d5kSGEYf6)9onke%FoBXHAxKzYZ^~~vO>61G@t8Hjs?q_T{ zO{jb<|LLea@#GN}J0p54BzxNHjf=<$BWpPym{t0-Spv+a$vQda*;{9=&&fyBsB!IGQI^1-3o zW{DlYiI7S>Enx^;0j#5d-1iN-J`VZh&_kj)h~AfT_(HQdF<-9Cztx4|^D}e8xgd69 z5tuOaNDI79f_NgrOx0#rJiU^w)Sjk%E+DNC*eYx|$3%XnS(R;#nS>4=U`n6?srOZ8 zP2Fh7zcC?Vlu$hnerB2aW!ODUlzG4aMEqe7>rt<@%51kXtmJQqs1x{C}!jEg?@-}6- zCRdvF@78P@Y@|kX6otS?(OZ1Y4`sji%qo4kVr1X{P2+r}eFZa>aUinI0^ZF`Stt19 z^M7k*Z12g9olTbzDiAh;q%#1`v#ud*YxqxC4Hb;8CMMf`>fiu`hy~URTm_E$$OZ3c ze!0RLi<)ofaHqyVzzM*wDV~ct$()ZEep;{YLbPd$1*Fys=i^eE1p`nSIGq59N9Wo3 z%RbT_W?AW}`9^4rEQ}5Sj}S%50x_=*ng$!MY7ZN(MSoq6Sl^LC3ueWVnZY!DH+4b& zsy`r=I>P4YfZx~<^+YKX%nq-`@RJpY{AM>%W?t=CTsseJO_HvjvZe1mtwzeRv~iZh z{J?A|k9Y|DC6){W;P5%ak@e7t$Q-u*@Z#1EUP(HCV$7IOzMl!q?O7F9l69OPKWL@_dPIXw!IkP1 z_-r#H-IhT@F${-a;vuL+O#=Y(?F_D-dH~bC=4uxMxWEdAKLpx6Kdf|$ll{86W;{)d zNnx2Y0dyQ`yBKcBE!^W%kOKf*;@+GApxk}~UB+hc&J2=J5>Eny->bCL0(-7g*>l?s z25-qU3BcDeh-RYeu`K$Gou7K!lS6K_y>>oe)Lblu`QUdD$L`VSK??vtE{H)UU{h8b z_GKKsqtR?P8aoh(dCr*zLC+jY1}%sJ)TD_$tS(r48}U|ue>JyWSr!`6IXjQ% z>iIqA^d$0kik1D#cibRYlB)KPc|n!tpda671|Mo(hz}`=sW?d9)2-c7aH0|!6#pgY z{kGoirr&Wexq<11eWT#myf;bPIYq++pb47Nd>Pj+mmPK00kfNhC@t+x9QHP6D9;eM zuVEsY_h&sd9e>arQ{uEaKP{ojsr7tM)}LDJ)c)-v0Ql1T?47OrzEg964SH4x{66tq zZNYsR->jIKJE^(@Nj%I+c2g}TDWhle}VG-f^THu1bW(GbKWRnDhAd4G1BdzB<*_v zP`G9hy)kU98-kD3`qPlX`;7ydgifl_#E~@!l0%E^N~pnwmBg6eu}m{52&tAb`WdLf z_dXopPZn4J2e%UrGhj+(d_!I`31T^EyRS~r=1Capfp(+;a7&05Eb=wnN?bb20ZnkU zZvM2apG#a|;!MZ|3ZDui6o*3Sb5@DhRHgc__?I!cv{Gxc?{xU7*TN7v9dk60bV;}% zmhyvGdF;S;m5@?bSu9XwG)EhHa3$pEom42Z%WRA0@r#qEW})TFv|#mU?44k^K6Fh3 zOfTp5Y>xiZ+$dk301g=TP$LWztU)}S87dVs7Z{8=MlK#8_o`JI$Sa7dgwt_>om5)x zv?qt}XqjA%y(!5H24rN3<`M8Ih(|WUPPO3U7X?l|ST8lIsHW1w_ay0*z&o(qhp91x z2Ki7jFbrKb3O-Cxi5OO801gQ4|;e?&AzZ&ZSl>)xMN~}bO zP}1kT1*JTCgS%QT*(eQ!5QX}|6c|88IIsoY=gT(Tm=6fRurRWz5V$_pnm)qru3}%4 zXk-)&L027(s5lg*2cGt5|A2x6vFgC^;$b?65L#{+An|Gr z1M*R(h5uZ}dDT&ZSsJ)f521qf5P|5uRS}}2@xA|z10TXt?_vv74hPP1A%b)XOyC`C z$%p!EolkQ#9h$B@1aLhXY2wKAn8UyqnohF{7umB+2^K1Y> z-GajJuZn-4Ie^dsoJe8-p~H-lEli$$fkF3e`jrHA^zzJm1LhjUhhIYcJI{2d;aW zoQq-=AC+P6pLm55Z5WEDt=PH==C9#rASpQjsYbSxsS35qj@P`Xr(anq7_P8Nf4kTD zaBoA<<4cF1eB_hd#V7pjcwhB_f|(4d7X%VD!O$GCV4CNx`thz6CNRBnZ=<^OzJ6&x z7WwAP$A$Ra{1eB4u&uWjQ>)=0Un-wOM&oz6DqSIMLSH~92kxN?lx&@Gk?k2pcf~w` ztJ&7G7rl6Bn$E+A2A_`tVS5HMk6yebWoR4^agZ16-8>n7m!8EQ5UV!fAO5zK4ge|03Ut^)obd)a{e^qW zPMT@3SYP+Wd1T=bf2}`In(@}mRH?77xNxJ6(|=dm6uY}lk9tPUxWl6NAeN!7Bzt2U zSfUJe6Bq?;#;OxW$6;*+H5B{gqRWk_YA)-d3U8>22my4w6hsducT4W!kKaEUHV~y% zh;n;{P<^2gAM>^54UE_ch?7C#E$WDQ82MyQH=L(62@Z6+G)x|@NPdQY} zDFUt9G`xeOupw=wiU3%h__4&7^RZ3oGtMBnfs(z>Hq+dF8_#YOZB+~ZTt|`(Y@Oxk@Ng?KAJPs z2>DIg<8rjQgf?hUvti)WaA)n%ct`9w?*e<5@W;r{AN`$9$uJoAueu#LuyBux&d{Q# z<%OlNu11wkDK;#@lilOcF?X$2VuY!| z*IH)4_%ern)BL^e!uVG1DkZ{>W|?dD+LVYVo=m+-yP(?BneCA8g8=o|i(d~myC1gS zw0wcdAFy0=pB3I`!jOZ7ggQ)NLULR^N6x54ZHaXI)tI@$0?6LknjdE1cHB(*-w!r`#7h{Q@=)_#=|zel&e7M?MxO61wIF$ z<>~B--HCQ}qPAD&LHB{^l;dZ54k;)3LkQgn$0VuC@q;0^Y3AP^gM)ZjrE2kEI_YHf z*YV|1sG&wBY&*(+`K0P4u6Py8)aR=#mw=h!!WkDwo36oFImT-O@x^1I(ea?)KZou{ zl-&5ub@-tL0Q(2qd>*6fV0kVkZF?qTi$gjwa_jWQIUVb z-Nu1Nr0Bbf*|q>9-_6~PYV@zuo9qq7n8Q7Ve#oy%4xQ7dPL3TSSNy6ur`&n_E_2oN z;JoO*Oo`kN=8z{Yy}yxBihUnfP~FAzR_@e^M56^U!0(kNm-ph#v=zbDl+71u7gRaX za>MyE8?%=mp?o;4ul>_jG2bnbM&gwKm~=hejA2c<9o2HE5s)&MERojcq$Jx_{lbJX zwaWhY`zz*CY@h-lS^n;o@yJ5V_5I45>lD4`omZWrnl5BRJR8p{0z-eFY%lCENo-vM zv)@&L0KjgDzWHPy0uPMO(sBo>SLu0hoHxwo*niQ&A;Vp`Kpv&%lj3I!msNrkIGya8 zF+mD`_~)OT&nGmL%Ctmr5J$h$#^fkn{p---WghngX}f$c0QPa@Hb27n*nxejFap&Z ziun)@CS?d`7Vpz6v*q}@(Pj@nW^oCUe$%h1)yv!9j?TV#y4iX6@eB^J=oM<>15Us_ z?Xg&PZAdHdyC~J9|C4`K!W`a#W_BxM?r3!FWC>U_GTe*o#!Ae z)JVw{SW}-=9`EBF*ZMjv_hl2`vV=H%qY!UskpcA*#6LTf3S4-fE7dzH)*i%cQ{mzH zqv%kH)k2sh#tg~gBMcn>o=x=Yd94OP`W=jB9wD{L+rym>pF86;cjRrI5GNV zvF+I~{ThcjV3{Bfuxnu(=Pnj+Me;m^mi=vtcXhSHm#&cq=+4G&n3>o5eu47$~CV#sQL^4RYV_m76)@k&1*-anEMd+Z#(x?Pc|tHTqH z-Q4NUwZ9|{5DkSSV4yY?y67{u?U0*-pN)Jci~Q?|eaQ`lV_42JR@Q;Be4Mz^-X}5Y z#4%DuG&9nuZZr(?rp}9vW$&?XM(y>`Xb`E|VA}Ilw6+>4qyGG=4O8t~X( z+U^ue{2T?X(Ev(81!i}H97SDD&YMJ|kP=aLvY)3o@rg#Vajv#7kWDUOMAoXv+$hzR%1qo6PN#ygBX@fC zq17YI>iZ0g_$W?DiS6g}rmx9r$0;Gc{>G)`Yo%h+w#cs$!h7NPg)ho!H3YAB(J(5g zE#;qc$*xX%Xrpjw?)sED% zfAMuyMmOgs@x8&ow-xm-eoQ`R_D~J@Zi5S5tiUba^o?6I1-*haUSvHJaK`WJoRI(Wp43y2n?C#tZ2)9+ zyKvH^QN1^%LZb=i8rs;xe~JUi`O*-jw$sqFt$j#6MNvh+v(p$+1gzafbnnh;WmE6HV3LtfoC<;5?)uCRh=(zj1JyUzDTc_?3M{q(#PJ4JX<|nA=!3B)Vd-bm8=8k?~ zUVX6|;OiG{@mknni<0OGBdE=lI;+@{18)vZ)dDrsj0A1Wht!1QXSHQ|cKd~Im zKFwCL#jsbRS5uOINp^$kW&(hLg`1?l(V1tdb;XAyN8lz{>W;+PCM2gU*RRugYGikd zN9{LC!38pSBR+gOC`S!c_I$n8s}Pv<-Pp~FGhx$4UXQ#SCxy`IG>Cy`54#dA@%>*4 zpGZ{bt{-_5?d5-8_R8aZUz$}E#&xE8j!uSY44%r*Yt_QdrM!#r8->2sfYo}b;8 zn6a_WI`+b(Pgmik4H{)Y&lmAW3Z(%{BszENqtp}$5deSml@Ckf!0vc?e&nEDYi1jihw^6w9OyU_XLs~o?bLMUzE5YPT2SClRt)pU^{(R`C}R0AMaok2>o zGfZK@X^}$CoN`*C<3Z2zvcA~d5dyI)cv`h^Lg>)aJcc_+ovHg7B9leigX+7umUW1} zxZpU1<56_Gs_fw+pLA_|C2RSg=Ww14qgKzr$5*i{Ia)sK`#eK?)Z9CZBgf=x8D^L5 zO!8R+^R+$@H~|vNH*LpfZQEtO-RsxwYBYr*;D-h4!%#2a0DsjTGgF-~WBC3V#Ps@e zMbi`|kPUQbd6&`7`+Y|BO}wvy3@=dqqBfV?q39G=qy4(lLv22l#gqW$466c`v__4 z5~Jti54Yvr9xfcE-$lf698MU;awCsZrRtO-QLTl3FF0?TS|sJ`yxLJ5@+%HvCZ}yr zCAh8Jb4s>&kC~RCaR5h*u|y#8*P-<%a{pc0+a}W^uk~s$&p$B1ChCJRG2PnJbmq3t zL&db7paj&SBATXi!ieH5li&OvB!|#m<)q!fcfg~Ts5~yUc4dVSv9}#0J;YstpV7QY z56>OGsR%A=o(cg9#8_S%12TN5)?_5~SoSv|`xsW^MmQB}MPR>uSV0aX;(lsj-S#$^ z7Kg%r1%b-F@E1S&q!*)AezXkYBXyq_So_hu4UyxS8NHbV=i8FM?$ZNaoy;mgFX;gK zxrd$QmFJMTsVXPKow$Y!s|)8Wlc|^FzyKAEQz(9^8t|Dtf~5XQAlKPNg?N3mH)wdF z&@VknRVE1va>P!DdLk1kn(p1;Ifgsr+3fQp}dRS#rY z_BJ(EKvi)9U~w9_Q`blxaYNqEM8-XH_i+o9pBOTXnGod|l1K+5KrOjM4GGy%8?&gY zBmN0tkfftQJ?LF`PaD<)K}nfEV#$TTMNyTs?;=}5mw<(mDq9lAK!Uwd`Yg=9RF|Y9 zMoH696n8A}J`AAbfLMQFdPhX!dnHE(rg$@ALi96HH{S?0_4s|?zSoE;#svd{17%y} zOUaZd7F5Iqn@pIfqd*|oTw{*EWP-hzAe#T97YwLKTd+MQARMN4NT@AJ1Q7P9PPA#l zO4M3Xfgf&jzHVFFv|A(w654eD=+;IPFnpD!@U*}{R)>o?3RNlq_y&4L4B$t91qaPP z5^Xb~c0mWzC1`@1&|BEv#%os9q2+_v1{B|*gYjT{oB+S`m=g`3cY@msjJ#lSRQ~)# zd)$CyBgwKz>Dbe@cP;$ZOpf5-IS3dF7@$N_)2bgrQI3^f^mu%6l&9sp4BZ=`a=9(n zpqDFE14ltipp6V>8v{25RSr=#zAU(gk*ed^;y&^ZhWkPxWWclfS6#Iw+P`*bv@)}5 zT8K~?A|RARfUwQdV?&%eyZ{i$uKf|TAO>y$Ziz~;{w;BscFe88mHm;il^Ogwc??+D!IV9=!l|N;W?p$$J$)Z?e?u6R$T8u!M>0-97Npw)Po8N&vScY&L6TE zpYh8v?+!`QaYFJ40oB-mogXHLxUJ50?r0ECe5J=2?f9^@Xt*Ufhaqt_=3I?E)hSoB z;!YU!sPpa+oQ@Q7j}*wf_sHf*MSJT?uvQCpFX5$fI3b;j5I7MBXg+_9;tjmz081dC z)c7Jhp>ScS3^yQbB&G1hYJ9Tf$8n>QJ6)FT_h6U~0`7^`q075EN(05gD0}B&ziJ-Q?D|#B?8+p2!qDc=pI|iSv7|T|;Y+?u$jy zZ729l*C%k}2Y3Zf7@fyxn+a@BIwx9NURNw)z07^+ky9#TeJoATRRsUsSgBmq=Dor( zQDXz4#J5=82Z3ls_aSyq9ZMY83wZ@(-<{rXKU^Ui=6+o0IT}H;{f^wV9+LC>m(QW& zOH8nzF&q!Wsr~9oGg=G6dOv~3w?(U(`^ehz8b4 z&8}yygwb=-83YN>Qf}l`kNNyDL9mj0b2^D8W%W-F9fU6c`}FX)sM5qEOfaM;HGfar z{69PFQt(=2p?`eXegkbXYEPfF6%T6p0+D52vG(+0*RS^M0R?z2f@lf>*MY84B4EgN zO`J5D=4T6NMf=MnS#iOGK( zv)lNwye9{iJ&ipe8PzHVEr}&x7VutU)29{O#PmSGNGl&q zfeJLe!@_+%tsPh9PKH1)qPKJ!eZZ z%6?hB=WvK}Iav~2A>h=IqeNpV1@ZAyvJG`zl!-I6^88}$GQ>G7c>Wl{1<mxt zYTrS2NpcfQ%9L)|D$~X)3{etwPZU-n57#|C24%$^x(9B~GLlW7nu~F}ibVh&vSjli za5bx!U?Kr-azq-n8D$OLM|N7sAs6o?BUVbOj0)gb@SKU9><82o| zSec)@UAoMq`_O`im48K+BZ*yb#K^s8N>crfPY*eojv2>zlUCXnvO2j7@KB@uvqxaR zkIFe6=z+v+GiCh+vj@$`N6Ac7Uufnw3uYLJZjcy1XEIDZh9Yk1D*sEI;Su^rocTwb z`A3}jN1XXbocTwb`A3}D`$wGlN1XXbocTwb`A3}jN1XXbocTwb`A3}jN1XXbocTwb z`A3}jN1XXbocTwb`A3}jN1XXbocTwb`A3}jN1XXbocTwb`A3}jpD)<{BhLII&iwy~ zIKz)RjGjXhb#y&C0LV`x#Ula06@diABLEQI(rq9Zz@&0=6a)aW&Hq42B=X!6Al>*2 z0dWKpZ65a)qKg9*q<2M7c$4feCbHEw5Juq*(pyX_u8M49a1;>y=Mw@1BN3=@2oQoq9ei^e2u0pr5diQc>`w@F zI6t`Zzd^M1zd=+o9LUSRalrr}_~K7o)Lm=je^U6Lly0woGrbK(pfZX?Ws1rSRm^QY zeVr&)dUsz>cV`5E-qG3H+1(io;24{k838~i3jBqZmS)C4+kc>giK&U1$&*IVNj*0vT<#;t6w zuPvaA+g#pQURyvV@I*&f3jo$u*8V~r{inLx!0LaXgN_dBB%mjMA+Y&b4TW`{X#B;C zo1v=OxA;Hul}&SX?I-_(S64Sim2c61m4Lr^W92`}{Dr{M z=H9^^O1Hnc0l?D6+9Jw06x4l!G7g~w(tCokO-ENp2LMpN1%Y~3RM!9Br!%2i0s#Jd zI@6K&*aZsx-#DEK{C}LzbejVJ;GBJWf%?%2;6JA`O=D%;9S@khM3&aqe*JWh=dESb z0P`I*jIfw6$d5Z+9fuj-ueW}5^{7hCobhD|rFma5_bT(d^7gWD_p(%FC&#CCkin#5 z9Bo=3J4VdCku}G(ApA^6rU(sV6i}Q#ohduKSrvHp82o9Y#{N<5hL=Q&!L-rylwH&p zlZ02t9?6}N+^_7DJNVokpH%l;n`1@K;al&$6Pf#Nc~zcSk!hvqZnkL43#a3J^>c8$G3bs76fdvTnteDCIW9siHph5he-dZa&)mRGWm z|12%d;F~;o*-UDo#hH6E9bkO(^Gxtc0t}@?0N_p>7(o4Q|I=oKZBQT^_i z-<2H^LHwJZ=`G?!ndZ1yRVJ%JNdN?a!E;DJV1RbTAj?0%(qNZ2>74?jKI?m(ez`=2 zif+8w?nDbhI4UALIUOLRPP{f6=-=}#nla_|MFEnGf>OAxp6l8;_3cgY)|uF?+VDy+ z1h{lae;!XmTFkKfUGggasY9xQgI@REm-XHF=xhd*Wl-D{0Myd7{d|=8Ft6lTBlA;G z+B1T?v)Q>rqc=@T{_&_NbUbsYl!)=XfAh)9PwyZ(ne|Jjuez1;7B4cZqGk_BZ5IaB%$@kqp zdt`TGwF}vIKP>yUprK=$IccDlW^qqz|IJaPmdU(dE!RjtSnN|nO)1H(RV|j_0c^m@ z|7y6WMuPMGnZ!>W2i2gM?D2Evui|HS{}`@ZOgPo|C>=!zS0g!$SIgJOl|TK_ zffr{?0;t>nBAsu$TE=g0RK#~Bua_ry_^CDcPrW@PEAavtu~nSDPaRW8goH10LK;3$9!4wbLL%W;OjrI@G6U6V_khG#DyS{)xZ^4!sLZH7xh& zedqp_IPdjXS8%85GuO71*dM!LtlFk7Xo%a^Mcw=4v)IS5{TrzYLZUyKm*lpXVgR*{ z!hF0Vx{l=o-a&ZWN@ZcUim5lW%&CB{CS#qW^VVoRFq@Nonw!sX!l?hGy1~YyCk6;g z?4Pi{M9Sq&5MIanZ~SB)!~WYQ+R!nIG3Sa{kQOALDiT@6^P1l|&+}W})-w4E=m=o{3-oq>@afR-ERSr&m*MYowB(yN8BFv;Qcit92!OnOqu=G z;5|VX5#5#|JGbj0Re1RKbtHtc1Cerq0s7&;Xy<#IlN^GT{|G11dJ_ zwynTpVWEW8L6drpKt=QG_<%p(Dn<+~i23Y$9_48*q?Ay4jyj~{0Wd$##6SCOBha|S zeRks#xf0<4efY-y{yOb=-kh!LH?|}fZ4*1mRlcCZ+o}FcKYVC0?yYb5cD#0-DP`*j znmnL0)aND<0i(N0+dp$;qzDa1+<1|aWCmZ;cm9Vy@PxAUYYGk2pI;VTtB%}N1#=9|EL zr>TFoX!=uf8?m??L|NfOtI1D9UzQjegWHq}cw87YJtG+C{jj3;*(KfivzA24%-_)q z=oo=_K3gH@*GmH*5x?X~Q=cYl1spvCFchgZu}xgHkJ~P&ride2JK-I`Y9(qU_c-MI z)^-az|A_KHkRN<ly@imMy}C< z_Re2tN4%Z4BsZ&HuT$oaVTc>lcFoh+InSF08xZ{*-02^Z2G(dz=%&J}h30E`T1#jU zCDsdH&y#Bq7=}2(Wjjq)90>F#F^_I5^f zTL#BNpC%O=*^72Y)YrbeQMbSR=}NZVFUNU0(2d09+i$UZ0X1`*L;dMI?b3=KXx>iL|TM=)f#LbD+R?Ea0zKw+K?c0w+gLvK_Je6S(+_of| zR%o(@0(O!IZMB@m9D#06>=0Z=sScS9VZ5)ekKI^~o>05(`_3(xc~HL8)mQr>Bg^Y) zp3iX~BZYl&n$BU{e}o^^1=pHpH^nbhox#Qf+ykaPT`}KS%G|~1Y@puUqPFiWU7Kuv z6STRQ;f*ikbc$L=kHwE!Zml02w{cW&kV`>)DYjkFHt&>tA(VBcPpvfe=t~XI_|)Se z-=m+5_}z^O(tD#V$rENP-0zoIOvT6hOxlDBxy3u%w%00tmX^xAG?~_?#%RvP57D5( z6A8*v#t-x$Ze1?4M@)@3{3et93iU;<{ZXsziQfcAHn-#X)@Jwb%KTYCWuReSYFAQp zWAghoubxtza5kbd&E;MR@)OZ$m60T<0O?NusTaY9S?p#&sr01>^u2OIWrgZGzi8Gi(#Cj2iW9H}i+reT@&BIj5_cynguD zuXQcvd)7)Fz^{kvevf?sLTz}aLmz8OPt>dCL8 zmZ@Ix;

!&$XHG&z{BeSM^cYIPJ38YXhA9F_nk3-@Pu~h;r=IP;|TY<1hpNh8Ggp zJ#=BmGf_rzjqa0Y80W!8E;|>ueyhXhN-Y#W4rIV)M&8jI`q)6$+BGONEXgI!Lt6IS z=oYCET-g#$^g(EcMoC%)N5#cni{j?ArZ13b#F4L#k~=KEZ?&osUmK#o8K2D6XK7GE z47CKCepzB8ti{4yy78~T$0I?=kM!;1^Y26S6^KvepHQF5Qf&Xg4& z!HQ~0a;%@}xHV+E@rpRvD5Dz-5_Z2;<08N4@!r*O)EB0j#zo(E+*h{O%}*Uua6#e+ zf~pXtAtgYJKtHns@d7u!^==K*HgFEvGJ{=rCNJD24`!ZveF6Ep{Yg$=<9+{LV;{tP z)iZ$QR=r5J*R4ZwL_H;GBiFCRhW_|qCi91mHtb4^8@nE>1KpxQ-=BP2ckg;GWVML)rY_R}4)b`4r{7!#?em}sQ^Svp)OQSEJ zr*zL3FEud?B4T(>`ir*x!8_~S|9ta+%Ouj;uvg!z4J$=Y3 z=e*}tNEEk@!^g0)SImYlCYxsG&ZB_dM3^L*oTsSQWQ-AK-u8{%6g`U+1lPaDFpNj2)`EOq5Wuq+Uz;2abr@t%ZnS@f!TbnfML%DLH zHK8;J+`hw4|86&}33JJQiZ+VNU#OL>hMNU|;F%Y4d}8P0T%UPzsFj69xDNzdq-`^Y7~3x9n#?-j+^Rbf4Z{#eC!RywW{Amp#q>%Bh>ltlXGR%K>ukNTfDqN_AxY=59Q;c5c4q$dpzw4 zRadm!k^k+%KDCzaWoj%CEX+~krm$iWDzoThcGAD=)9<}igRGfc=sXw&5*o{SZc|=+ zhh;3AmQS_v(A#edYEW-7DL0c%M_ht6LXuf$2&3-lG++7n-wzHDI+|Os*oSb9+a171 zph(Scuow4i$wUprt`#iwB4j3!Ch${`n$8iK^F`{oJRSt>>h(PtnU4Tu2W&f6 z`Zg=vH#E*n%Up3BQS}Il&+p7~$gj!`Gf6KBeItGK;qe=8)L`(w&pD4XO!_81^!PFW zXM2~9#RXl|ZoUM0qSZOiv}sqWp90pLJt>4UZAvRtdo3nP^Is1VXcACr;Q zDZLaZy@xPD=e8Z$CWo~REW^>A9jR+BJ#^z(-lW28V2aGPuWR`!dfD2NrMEJ34YA`} zEUz>C^YZuUpI}i*NP!F|e)DqrPaNT@!;2KB-_ODwZ*U-&cx7m3$zxhrz*WrhJ>luwx0oI!`6_9BkVDGnM?* zLUPeJBlCn_DPrug_tV{`t6MQ*2f?PAaB0dq;QuV6%7eLw@AI`+VtsKM?kY0qF5A_! zNG2*SY%HuVc2AGxBAQ-iKR0=y*b<^_Fn@}ncwwsat95(f70^3+2Z>~a9$4#DYTS|? z&n^F(S8_(j+}xJw3wjq!a)llxPV~hS;hHsmeAD?h!}_XM=vl|jCS!%vTgQ;-KgX6x2rmpDjV@7_wNm9xD>qEv2=tUC9AsnDAW|IJlYIoshTd9VuC z`Fw!DOL7QcF>$!qi592U=2YuoPV0#K_BS53O12UFdHN;5{Mx6Ph5E}Z%HuNeEsv$z zvJ8{6d4An5`SK*DIE|Aw2cg3pbUHeS8FGN;rAtkQ^xftzRM^ko9TC`KPs5JhNz^;g zXfesu9<#<-{6NI$%yMYlvWcz&A?JBJYlLkeRO%Md(U03MVtelR_0|W^6%v*u!MXOD z=bZTl4xwX{PxMnhB0{FKvWqrZ0RgcO3)uV16UWfsRZQa-dO=$9An{Iq=H=gWy=F>n z&(4EDi}qLXTZ0cy1a|WpY#T2jG?yB`5`H>Dlp{!2#_VUo9Oy?WSIMs^g{*6Bshk zuWg$lo+^SP+Ed7g=eb6e5N%wqo9YGEq4~NhW`Y;Wp--;;vUG)yDa5Rl}DbD~Cr=&t2fuQKX^linUr1Bo=a@*rY7OBoF= z_pIRsMV_i|*14@@jMBVRIF z!da-SQ(0U;>(GhM6t`>BRU|ZJ^dAVsxgq0-OZN>B=kDQnsrBF9;RMI$`aqiOzN?qE zkhs#1UeRh3kST26g;l-`vUfTfgsX@8Oxi#605Ta}w8b_$_Ws>W!ss0KK*8|cx|EqG z4GOyFI0R}eGqQ}aBQ7SzY*`zM>aH4@*#Iu(1b;YXQr4$ga0y<7=Xt3UbuCU**dF1s zq?O7asRpG8oup#6@<6D!pLYBT%Dt4{_vPItzkn+04%8}@FT+vHf+kq4AO|4E;@N}C zTQTa}j~-l-=5>7jYau>zXFc_wPD!b%3;q5nkec$QG?>C*PbqgDTlb9QP0~n#uzqoQiB6 zZFWmDZ;i(9vpsFhLid2m!D(uPF}~?sgwF6=RfH|Hd2|xXla4YnOAT2|x;qfs={=5n zk4fB#kKO9ziVzBNZfO1zaHhxutRn)w^;2exO6TV<0hBE~1=9(8khH&!Il$9+Wt;fU zASh8yGo1l&Y!TBHC89fNaQZdG$Ucv(iGeUhqzja+G!2c5!R6?&4dN4LTKRlFhmSI! zQ%7@7+G#r5ecba{wt8Zn+VIbtn}D^$#V{-|LO5$TM0+wOt=7$) z(2n=ho;L()lAyU_~(> z1B@X-6dQ5@d$2imR4RI0+`r`Kot4JV*PqRGOG;V1D%!0PX}z{330M{!=eoyHe;mmLOmF3tsB^!Mg_BS!&7a#SGw)6o(;|lo9`oNPi z0+gNI_TQP&_YRreOj_8kpW9D#mF&uL0IeG7ePAp%J|=5$wGa_9DyQIDBz|qmjJ-Sp zcLYUa0XPbvUg&8tAbae!5A`1tOLTM z!s(}rWWORtvQ)23Yu%*1oFE<=o8B|}@X?Z6glPp)`Ss`F(UIx&qKQXcKydzQsaa8@ zwwh;oVnk#-6036x(j{74@aLOs+|fcrW56s8F6(noFUNy(9?Pu~KyDQB z4mT{i{^79roA>yi_q{H?|MhvJsB2uApWbZKR1!RP-c+0%P~LO1BiIyqxzy~noR@Nf z3d!!U)l9!=LCE7&bv{P4SC`udMAze8qaCJ|&$t>0x3n~1#z?Y8dA=W;(d8p>GZv~O_M&4wiptE-^HNrKBF)^YDRCb{XV<@#cQXo^#v z7hC#~MB6P|tKbe|mgWqNZS5_h_H)sR=U+w@maJ373pZ{V+Y8ok9IUXuB6o0mMNO(r zl6GWj&bz>u%Xe@f<<6cVR&`&29$}}df$g!a;U%ItVA9n0r z9|}0%R^#*3>UN5=Tid+$kvaAAcL-+Y@y}niG4V@3Ha&FW9dhqd24f~0b#`JIQ+~a8 z+xpvg2B&CyuIY4EUA{B(+_&$z!yHy&eqQK_WK?P4h>a8`EfFrFTvsDx*66|29KI4c75>F z-87AJ1f`pH?s2Ga=Xz2}O6n})v%&g0{tom=?ic5q>0@j{%3W#m{4@G{w_HZ7ThOCYT_E)K>McX5c9f428=9CWaX zJK|f$N;?bKKrP&|2$cMpI#y21+cH}54V4o)z9Hh@9v#jOS9TQWcq^6bfXuK@5^CMd zY@aW5IdyBP=?d{locDZ(w1un-4~iFT&G_22RYjJRyn0%v^%4^Wq1U29`t24_!rhA= zyd%!uopvTYE15Y8b8!o`7A?tL*Nj8x*m&D*1jX`_pYS*L3n2;6a7FdvShJ-FQWB%p zjg7}<8Eqd6U8Fu@hYnwq(TMlStXVCc4|XNa>V{7WS?|g{o1hDs{bE7YF$g1OHZwhF z(Oc);=;XAW>Z(%5aaD5SZtc~R<@(Vc`k9fg7RAG=H=>Q3yOJ5e*87nJ?Hh~;uA~7WM7AtjZ&*WP6>Wo{VO)+d=eI&TSSXz+1&kR zK0#&uu;PbKd2yJFRrX|wuumx=9xz_M_-WTj|UJVLYb z-I~tJd0H7in0NY>r^orwo}5Q*dv(*Ns!Z-wIlm0N^O{Lj=ZPzt7 zy(oWXc1_5EU+g8%8CwwAKD=uAzG&5Cm3=+)^D2i^zR|5s>*cV=GNQ(>hgS}ijztb% z=-`ky(S4)FhCnStM*7v|KZ{8-!fG|&7nNgiuS;k*ATI4uS#eL_!|rXc_dvYJC;bYd zY=k7B#w8qr-DI?}PM5|a+xFWf{^;!)ed!>1uBEzyIGsN1p!x;Q!)$bU@r?EKG*$CC z);$D6G~}q)9B*XgvJJ<{m_xwDl>xRo_xe zS^$2ISj`!N6jO&9M*Xdp@i#U#S;t1s-%Tdkjc=dFna@N#Iq&vro<60@+<4RVLAy$! z12W;)aZw?U1hOr*AFc(B3FRs2zZuTj+(~&#oDB6jChcZ&WOqU3U7s`D4E~1L>&iKc1`*bsRk0j}JJ-HFabANnlV)%Jp<^qG^d-n*i~? zi8fL3O-g~D-9>M(5H|mZ4K*PG6)#aD<$*sB>7f7Zv%@BX#I51wDJONSsTq*&9`*b; zE6FP`#@A%P-i)c{28Wb!3371i<}<742vyW6by6nre!(k+gr~v8K9Q;iZ^HykzB;D9 zXc3QWcVjdST)8{dsE%KW&^et8J?|v1&2m|PHd^;6W^%sZwq2nCfE&9z??**G;Upz9 zS1p_o2~x`&lGo+HCv6i~kNcmAdQ&RPXkEVk6>dA_`+QeX(O690>-)`Zd$+t4xS%oI zc|roPIJJdKfZV%pujCzFdLu#FTYrI)d7$Xf5$vr$$hPvrZK7xoy`-n%%(%4H-NSF* zdLa`wHyavS{U&K}aVv9tO^o=!!Ks6n?FG^ns5>di{fVcq5+XP6Is5_=opr?O0L z5LFI7b6<$B^tq^{dQf4DPirmDL>3f+^3KTbPu~F@rMlC(Xia_?Tt@P`CmbW_o{!X* z>#{?>JP*X)JSE0kmscMh@mUyOs&85%Zk3CaR}i_M+q6i`NE-V21z=Ut_=>h< z3kb}L?_2%_DtqL5x}5k!W}Nc9a-fMe<7U_XqDEx)M&!W1`)ZbsgvR1YjiN%Lu}M#k zgKb^sn;S9h?j#Rb(AoO%?@pV2b!;cTuKJoVD#@VT;C{``@3s`OfMK&g=ggwJ&#QJ6 z&fDW7er42shM`x5k!`kQ0fgqd%6ykyl zp%~;PR<*hg6RW})D=V=edqGa{;(qTe@iTqu#Wkdg$qd|6)a z{wee1dl1s)(w-HmrP!_S?$R}W`uMj4*#KJ6UEeh~ZVwNq0<_p=DDYo`w0~x>HC|og zfP5yFy?3#j26w?Z5_$rd#aVR?@)0^UPE-k%T4o`ihwtsV%c^@cR2dL#{ zsLUj|o5Gb2$yzv2msM9ZxYWPyty)@A?rT`Zl)tqHLktyj=pi8-2+0NJ!cDXKwQ$N$WP{G$lmYtbC+E|Fxvs zTfjb^pk#S4lufn}t@9)PZo^yC6YX}Z=9^2UY{?PbS8tD39VC=P0TSD00STgnICV!Dv*uNbxSg!X;GjO`->8q03|0jBnf_=ePt&FG4^EE>GMnx6*?$JUxtw zyykAh7s~4q7o4g5Gk>_SfoY`^I&wIHOZiOL)B4w?*Zdv6#Qb*ZzHLryuMj;j=B~Eh zyjI9gZ~#MJq!kdi!lowV#e4nxUs&}%ob9%;EZ=3PpM9`S)~5N483uA9&8rB(!7p^( z+lzE5EuYWr7C&{?pi%MD_dC*8Ys^d%9h)|8g4XG=^e=bcs5!si#W+5=IKhlIoZG*R-zokDmO*qf1N-66CCP`k>I{w? zS3I4t4B0Br(fla8YvE{@&8f(9*@-LlI63=?wr?v!ft-rLuX(jtxnB&Q?K=4UQodvF z5p}BvyRJ{3s0?)=vb&yIabI^82%-h=^G5pvYu8xa``v=Svpuf$`@oF80u>|aP`Us; z=I7!#TWZ%gJrZh?{OA+5Y)iVaqNm&WV}HTbgFSB~ji%QsZ`Bi;YX?`dn7&dg>iNcM zXRpLYt$OOQ-QQ$RJBJM)eU(0;UE;z9<0ii?K6NT-{XH_sx(+=Yq z+Yxf6XEH$F;n`2$4YxD4n&QlE_n}WvNNrW=C2Bx-pGLQWOh9VA=(VL3-`e`oy9Wz4 zew-=p`zi81R&QkQi^&h%*m!qly*B?M=BU@OBI|FdnK^>XGFb=~}rs%@oWha?W=9dY|c>JW>mu!N7nG$+uY~zr~eN$CXa|)O{)rn~GZd zZhD)m2{2AbdM&fC&u*8VIWM*Ltk|bs5-kQ@o(b0&d($R_r51JH&Z>vHWqp+FSJQV~ zo3=c*#uRhVtI9LuHzM0}WPo!c*j_dE$`dNPqfioB9wZyetQ=Qwi2VKRS&9qs>hyqu z*GksVr^t_`4<^mH5$)Wee4m|_{3ch{82fk{rQk|jF79q|)Lzq4*rBtow6E~C#y*9O zp@mbf)2)XvDqy*ApyD%e_SwrjL=(kPPSk!uXlInEIG&9lEwKSV;O|N-=B)j7 z>f_DmLlVDBXT7J5aI94>FaoPVFKSsUV3wKP3$aaL-;_wA$>zaSv^<~C|NM~VJ*dwJ z&k5izm?5S~wiR0s`ilf~$>JM&k?Nu;BHP}+v)uDs$U0U?z#E_2OWHhmi`3Z4Y>bHc zo*lPuSk=ZB-h3}y(Di9T+s7F0O^BBtFl{xF{l3wM%4EORS&!cMIiEs%WBeEK{$LOB zRX2+1E|*{6Qa~3}vJXagb9aeXOc#B#f*~uwtkg!hwCK^`X;0iA15`P*Xg{$`2w)lv zkEOU2eJ?~g2xfoz3{N;lct54CKCkdid3#8DvLonJl4M)GD;4%FPUZ);yvHqLTdIOj zJvlOW+4tv7rR^_hx6K+B%@;7}F^pkxY7hT}w^67AjscwIl{qpOEGGCO%-zKA^X-&z ziI&ODl&4o`{Eae=_R+ zF{w3I1?D==kJdYn+lj0ksZL!oTM=joE?%T^WV6V_^%jGMN{5At8+CacR8evoTJZS# z#0wnob_&}Y+;<2@_x$41xNhxcIFjmXaC6W2nT%qAU5E|+>Eb^dE9OO$#PbDr>#W&b z5?ImLcd76crqMI5s*f42+4u;zXMG=wImPNdqf z{?G$q1eMR3d8I90Nc@Niy=%2l za8A(%00Uk7CxgeoN^@2V&j`(X9`bm(JrzarY7(|reG4@j=!W)|8?-*=hG$ODg1g0m z)k(s|j-h0nqS{wWg|{;N!Aj+8caJ@9S6krG7o#`dxHQ3>)=I>3Zn7bo*b2#T}W*16K74k2gz_u(HGzm?h_yelEI_daP z-;U(xLA4mD4E7vfzPhFD822)V?{RHsx{#yD=gha-L@SR{;;cEBonv<4CMm2k4B7*l zGv6GUqYBFyC#@!`9GCYK+lPIZ7M!zXy|-FV)sNRUaXBri_*^Vi<*^GTx$3(!p14}c z-yehlV1FHQd34CPXSAW;w!lOl$ZR{l6^?V-I@Dlr-m*3(Z`XTx-ZXbw@pxhr71^ta zFH3?l1a~(Ys1#ugr{{xROia*MzqIAPC)u^;=-^s!Piu{I)eEahq{_P5>>LMl%I?1M zUc~@01c`G9_V`}6O!&ddh{i>r($E#wzXWK%c+$EB?eBDAn`7>YeajE)1$=+aj9pNV|6)}U+hGE3T+dw>akZEc+abc`Mz z?=^5=gQg4mfDV)q{MiF|d@0V}Wv%;T7pSxlgkIR(&~DD}G&mL5ub$Ax(ZOD#L=XO>JFaI5@&nIZzxrQ&?$dhm}&)0QrEWBgZBuUt48ss9U@__cbUe`bxVzTy72%ebsrC8Z9m1&lOguW?lba8ql!6Z79`)5vAs1%ypWO2s9%a`oF6Wn=jH^^4@Ap*q71&Ds@siK{sdtOGW7dj zlUu%QAZdB+``rv~MaR6|dQF!gkAGNgZrO{suSMbr#!p#k2~?0~2h;-kctiBeDq;r@ zi&soOb-U7lnRaMDD(4 zFEIs;2M=-$3Kh2%Q&xPO?4WZ<`l7(0Le;hRMVDM%Fg?J!ZhsH_#3@Xg3Wa7KHgDf` zMgyEA5EZ@8h;Mezf6A^4KgE7HvZx3VQt{`$UC{O|Rn~I)2ft0Y!gmp{uq8kHee~kD zkgf`z;R?#7O?F5#IL8~a^R0Wd^-cP$7a7<2vY&v0!&{<0-7TegvUv-5M!aa~ZFb}# zNSS-B`@^r_qL%5A9KZOyvW}75|c-ixvY9hz<7T z1T+eE5p&sJClSz`0=p#vU`wL;bkg3hsig#!9?nqq2$F^ z)iv5MeZ}k?Z1R%%ARo%-C`NsD7sq~hrdwHQEc2{>v|uCk#mJhZU9Vg+BR;d6->z-! zDuD4>W*9gJq!3-F%Se@W;tJ`gKj(t66gC7FgHa;zb4}|icVgO-)+pYC{d84w+ z=A!o+Q$QU8nmCG(q+3;goKU&wz+F(h?q z>~~n5nCSfBv3aVQ>jqWAv37I?7Lx|qI)U*_4$avze(lt@BG1i{_}eUIqWi{M7B1cS zLfzq5sC=7mxq_vp;oQJUwnMp{KbeaK4NQx*vI(W0FmhNvKz@OWAJxLjilUUAhh?kZ^OLi#5GX%*PJ~W5B1lf_0mxy<%HdX8=l=%N z@j?dByaa9`$VoqV9LcHte}14LFpQSw68B#U0wf0t z<_2U=2ULY{KBROLkHa8g7QnNq-LEBB$!UKus8y71hy}1eq64D0oiLOzy=!QAE8t|q z{R>>@{)th%g1SQooL7k)<1Wfngk$b^c2UZrb&rL>)DMl&QNHnM9+37)yy_rjmI9Fl zC}94YYkid%>r)qX{2_&|cw7W>tve?;dgDVt&(RACReE%%dnpqjri6``U4C?f;11TdZR(6)oV!Ef6%6d^; zd6*nyOoPMr0i2NiTcZj;9982yD>I2O2~EfXTJUCZTU{oxi_e@rp~+xh1UX9*ZHQd~ zxO&;;3l_utHghs&jmNcrliS#TJj56DOI=z^=Va)oL~H^Wufoa#_|B}$nB^~^i@`Ms znSYYDLI7@GLtjb@B0EB#^BRToF=(BHDA=3m&Q)6uPS0n>2&BEsV@j5q8{sP?&&t?_ zi%prMx@X#4C7=)#6OFr#z_Z8fU!Ybmw3uST7{0$miMvl>U}Xn_(*p%|TG_7h#XzgH zHjSLkjI&1mpaJE4A|1GBknD{9#r$~Nz1%q(C%O72}C&K-f*m%iyr z)K#fCc>PhoS!X;&ki~QpS^@5P?4J+M7T*_Y7flV7Umlry(npEzv$qH=Qr_dCFkEH&=V%8fWc%k!^cSzM0|)zuKya) zGol%KM0#nX7~upGr25Qky6{hL7Ybvd&as})iN33(LwWHHG|DAa>v^5&arIg4q`Hq^ z>OO6z9VSgKP7kR>4R#;m63v+UH70r8%}|e@lHq{IO;h_Zfa9rne*hR2Nmu}uO-05Z zTh^|RPnA4T_Otcfx}Zev_hb0oGjQNLbp>S4ro-zV{T91ub)Bz=94EK%mip^1aA0lC zX;wR|x5DLo*>0O0E8N@c(?`ic@j8XLPY8UXlnz*^;xVekzO4hUzF>^`6R&LlFrRAKR=ebH zJbG9ejs|4ikU8*2xkbZAz1cbPL+6X5HA;V#+X>+l?+|z)pF6#q8O?lK3BR15>|$(5 z`TI2Ff>{C;-iNEfbK1qGdCh%%>7dyxKJab*HFALW2(0~Z#YoU@&K6}-Xx}>OG z7yt}osE=P+c@rco`Z8yB$sasGd1W&`rR0_8;BLZU#2lBOe`cw&Xfh5%!!iTs)6Igi zEz`;A75#^GW|bI5&UZSpJU!M_f5dDO=;7KUq;v2q`}S z3N{ogf6X+E-#Bg2+$Kr=cWG(VA&j6v7C}Vj_qcP1ov*l7m}@j&L9wDF_J-Z>2PPZj zL}0iSyhq|6r6`Q%5n~t2g`YPy^*!Tr|EPGZY(Qy`3?+HP8jm|g1=`<@a((8NU&$+t zS#C~l=K6g4A+<~<&o8`C+)*C5n)wTxU5ZiN8_fwc$BM6i`8F=?WRTL+`-6YnBvlBV zvXxIg#vFwi5F!hJ4zkJm;EzKfN=MG|3sdO(V2?<3&sou)-lf7&v#;xjJyMjW(JEz8 z#_r+wftj|lg?+B`wq#h4Vv*FjX4hZ!zxr&j?=Gn<{`~SOZ}izF7m0rX!4_qoD;peG zkWm~x@OZDf_Rz+9#%xz!44X>NjEGuvk*O({+<7V_^GX+=d)}!v4pMg@pauQqjyP4C zMY!9Vi*yLZe`k6gEX9}N|{RvomTXY#)tu_rpwaji9S&ht?y`PZx(;c>1^DN^)yaA-!MDdzIlK| zlm6=nxYZnYYL+P2g@F6XsR);%+u0V~sfaJ$>r0%9>RH_E_OZfmXwc1H*91;%Y$msy zY#zsesc&ENA#uutP>b6l&+MPQ+=VFm%Adv^NJ~>tl)<4}EJ(JfaLULm+A=xIw#=&Q z`(!M#_lom&koWlM4Nt zx$%SUvhnr$U%vIHsQzc_Q1_h$n?fbSkr}zL8-(b8Clz(Cmj22c$u50fLyzC{BaznZ zv|l!vJJ9z8&0L%~czq*Xo1kxD-(h*uugDDt}p-Hs$;V*Kz|w+*sR#1rc;GbbMcf~OKCFzcRzjjv^gKd_yN*D^3$nMYOz+I|D-W9z!1> zDQ`^hOqu-FlgyJD**>2tr_kVW>*2pvg%=9$#I@qvi5q?mHkYKI1GA&Hu@nmr$Bdl$ zS$ei&+aS9jJ78!!VTaO;FeEICCHMkt^c^Eb3qU4EAKiN+JX4%uzbX1hUHn6TfY zl&NMt-e11mG9*$vk==9IS$`u%8@$U#_fgCrod@{OYXak?>aJr<)s-;7+i+tWKTDYg zT!IwqNhc>LJR+Q`4W7nbU4AJz?N89YZ}LYSaAJ zZA$L=oqc1jTLLTA?ZouhOUvh;g0d%1DEb~Fg(hrg`hLC?xFs?&k>Psa>^WNRqfTIV zbO?nLF_nV-NIc&fmy_Pw`|XwEhvdP=<3E3q29(hs))}^qgj#lQ7%^9Eq^N^_q4GJ( za98O+{oMAb^58hHcpaYROnQ1SSL^|-ug*nNo@=7dc238q$bPRDiK!`_YyBRav3(Bc zxG;oJjAw7^?x@nJvwbVX@Pa#aogqih0n9}^oqeQk5j^M?BiHkMEwffTBVOqR=7ZCA z%oNjFhY2A$o*iR!{Hp1?=^_1qgLur<`=Vt1u2i7$R{w$ z?6lAY74lGSQJ0%8dFs<{D<(-2Ilmqs>pg72(%*Z~>u(n0U4-ib+oZ!bcbn^0c7u=k zl4>)RjyI)kYNVFqGL$P|icy^VBmH;w7$OIbR< zFr3{x9FLQ-g1)hqec@#116CK2pJD2IzjSDJv1!eCM8iOFzwL$h&tdA|KQtA)1WDQx zZ?f)g6YTSSb18b-1#iY;#%1_ShOB#7c^ggp1$qw4uQ`sS+m-5Q^8ZYNJP;&XK-Tm3 zg!{`k!WK`ePYcxQ*_G_^xb0n|`d=1X+IOpGYo(fG)RLRo|Q2|~nIAoT_Y;!*vSg4)2(X+`#BM&@HStt{%V})q%nN_g4 zlekT^a>*oaY|V16`oa)5d*^i09!BiES@w+2hJdKp$4(=ko~&}|sSDSTc=;2n6nm!* z<#T6HTXHhP+qBhzSW`EVee=zg#1E%j*CoA;3k^1wm(+bdeecAJ8$22}anvVnIWe}s_^|$8y+cSqbu;=71YcfB%9ix@ z3>NMsu7s-ei5vJVy-KL&ymbwxQaT)XOfpWzOv-wudaMTQLT5tMB*wIeCMU;N_=08! zQWVb*Z9vSu67vJ(ovzMlcx-F`oK5e^V~ z8~*Sj3?TN6+#_EAqx_D10lHGC^vu_frwm4<01U_j;cQq(R&pCuN(J> zs0#GIe%~ijssGQPE`NU#01c7~P$7XTEaUdyo6n9SV-Nn1n|MIGyL1;<8T`!le~L1w z0tSroJEkkCOlXz=-c>=ouKqn-HS)V!0r%g#s$dUm#wFH*vwNxjRaO-MRKXrh2|f7t zdvUnZ@3X~KGbQ2RUuBiq#P27FMTLp;{i`f3d_$N%NIeS)@ZX-nyNKt9XyW$$kGnLa z)*$37B;Mbmu79ce_owE6f1)5Y=&DJx@sL~^xZwff#3*T={<#6?QlxB?Km=-k@3{K` z1l38rJVJr_?;X=HSot0iG0IN5fD^TSH5^{}5fQUDBH#sqh=>;;06zez2uOf}x2XK# zUs#6f9{KNm_>#Q*co=O>ic(x3p63W2Qn`xBs`qZC0k@~@u=Qf=6UzfDjB6oLvog!K8(9TWga zBq$?6l z{r+Bu!QYgB4~XI5J~0B^gC!yJf2}O9{83dN0cd|z)>Zs~m*D_eUj4t=`x1Ak_W0p5 zV`(Cp%9aQz6ha6kCQ3yjd%_H^C`2J-8B?~T(qhRvm24G~vNPFQWGPD7_kG{DnR&lw zbh|VCe(xXfKF|By=RS=&-}616egB^MM5sc1s(uLGm0k7e#`;G@Rqajq6VOR>EDmd9 za}z>T*ZuwqVPO-1Lt0y#15hjfd}(PNfMvqj;;ez>(0_Xa%W8|oK@k0SH?b@>R{!HG z+y7P(pbgXv$T$G9v<7P#FI1I7SgZeWApk;sto*KLb%F5;$ERwjpuV!&6N|lo!_sfY z@u>=``dn4zVS%;A;ux<~L!Z7>Rb9YZSsVX#Pc;OURW)O#(PizHmJH1U;{|TfGB*0TJ=d1Yx(z;4e&i>5QG7=|8q+f9TR?& ztGec2X2GjiVG34PeW7tTWc^NrEiM3J0)kK-u+C}Pe&Aa``-)E=KYjrC{8@^<0kLM( ze5$Ohs`&Gr6|mrz);F+7Kt9?>0v3t0#sUKmivrTd8XF0*T3TbV);Jqj`~n7-yxeM5aCH`YHQ1}1O9pJ3@TF)}eW1k(5CD}=Eb!31Gy z2nf*o?=pmmX@D`4$vG1sF$~|{FlIex3~;AkdDEE1)aZYFbDPMnQ;>i?kbn)ra`O|G?%F5fBsj7Xki* zh`|2RK7)UVO#gj`7dEz!2m-=E@GpyaA;#J_=Gz(jNi4lxC@?;5EVm(O9~alrE%8@y z245Y*L-`~3aoI`S`8Iq;Awek_JJ>HW>C-;FCm{=~cDViCgv7>`#8X@DeBFI5d-hbK zVrba5F!^0gP}sH`O|FyYN-lR-`RrG0>yGzWm^$2PT40~_X3?BVoy5<4T23lyzPtB% z*HNM~a(X~$=k}C^M1?J@;wcU4YC)HrPm;H3gge@>IcjWrF>``Ch5nXT9t5STo(g6N{@VRY~}Jiyozl{ZXj(=hN=h<&)-_EMNeYP}%ZMg=2 z-C>KGqv5;KxvjhZ-CY<%%m({gyEgVG%_&W)?!|tf#{SeBol#sH41T0HvqAG;^3m=P z`!@4{l@pLYT2AJe0)t~<`q8p1D2jf;T9MsTwi5-g0TPCdCBW1vV4uz zt7>MWDcD-d&;*8SeQb*@f z%g)w}iVBy3ezO0?pS?T4FS*hI9V|*j$O?R@yR%hkT=hl9%H&5Zr8DB;qRL2{IHsJA zD+;jCKwp!;q zon=rg7PQoN_rOW;`P3ScEkg=ebQrmlSOy+f@s zKD2_bc&MKBEjW^izWu|i*e))wEG^8jLr~+;n`&#e zJM=VU))EE`L^c(2Y|p!V__g*lr$%`6cj<%E^b`reYzKQCsIz^%r-Mr@gZnVmE;6ID z9`TZ22Q9XTfoIAW=2Z(2HA7s-9_ot4HJNAWLAJ;O>v0d3d)Yy{T2+1_?`iwTzsY+IaXafbGHd*9`x zEz^wq@Q8M83aCosRu+TV*?*YgOzELc&bWJj#WNUfv~ac;V0=~Uow1Mk{fQ?&^czwY z^`>e><0?sXWCf05FIW~EdHC`#|1*(2Au}!%@g^ySZU#1CZnC^Y=s)Y}A0^AAYpjW5 zDUL>5Pw1Br_W;>el=!EVq`vTO%*#Hkn@J@;QW13u#rUuTh-fIhfH+<4WAfp<;bcwO zO4)@kPh!9?B+^mBTnfl_bx-xH)-z**XUWdm-7n>WtuN87K%s{M0MTQ@1oGO*@%xN~ zbVUTzQ_!b%=xiYZDY!KoFea?TX>_inP^j}7MT08D>r2qz3w28N12GE5Zu5){PJyTQr~b4 zudEUaJx-`iY-Y@^4Yot)r=!kC85ik!>7k}`o^W2qFrXzYs|VQOts#9O4>oqanTq)B zHeR(hcRe>T?1tupEnW8R!SFYeHPn0eI{0k8=Hv66=urcfxCgd4!uQVSy$T;5;v96O z;_a9#c7Pv{rGKcD8R>>QyV&6+#B@}`3!Ac)P#Nk5&zb!Uiypzg@Tq6PUIf zal&AxACHO;rdMN#umzRRjJuTv6RXO|Ief2WcI6WRdIt)i(-I>hvYA`FG>43fnk|xe z*NPlARw#pjlO8*oXTD##tb>1frfnMEUx6+AC?cT6ASldXz->xBC~PV2<(z%uV%gWd zS(K5z$5|90p7-soIlUw3iZYAJC!7Dm$aYHuWG1{?86~)5Mr6{W%leu);|{iffMyWB zEqD9|S2^B_Byx?x`XZ2T(G5_H{q~-LqWYH^BQ7%&HG|%v?TiG*I0Jh*a5gM|IrFXY z7*(@le#m?5GA}*8Xkq=sK!h|O?Y>;P7`ktPqBeQ$o5;2KgA7zbr@aF5b5whq3#!Cy zobsZ$Mt*ZnT;5+#8)p%_B-G8v^zz4qm+8sre%np&n;7&NgOfnkLCR2XCfhuqq2 zrs8osMrax&05s%Bk{!k47xyNQ&Yr)b?&5Qckw*$=Fif~JpL$Xb-%C%qF7z2oHm&!r z5o3hd;U$3h<+uK%)!7@=66#is>*X(_kNic%JG|Mbu9sU)&R<(Qsy5pB6#wS5D#H@O zHIy<_*PuuI&*Jg&wK?qDXD@RYBoBeS36$oS=;iinHeuhfr_}}fULR+amKKqQWmZz@ zD8m$e3gsd5Cf&(k8;mJ$O4yT z=RIA99n~Q8(}#%QNhvIcnxW97wTW1+Z+HWL`bPtQu!k_A2xZveW7&P3qQj(2|GJ#c zicy=V>dFN-OB`l^8a)(?vFC@T~1Jz#c=yfenGzANygbTU@qKnm0+R> z^$ZB=eV%V=n29~b7~!L&%!rm80ceUad+y4QVD`*xCPwryJPx`5`u2r3zKX2$eT=XP zPn{$zC_o02mMSGnAGu027+A)P%6*E+cAC8#T^(VS9M5PL0!S1V^RG}$Mh~@^I#;ay z*PW-?6#ze#T+|PzxD&22`YZ%;1CN70lduVOfi&z5stXJl$M86S9|phNLYea&rv_=C z`fFnW3*uKm?J%lXs(nB8X7kS?M!1Lt_E4p?=Mj#31 z`RY#k_5N~$TqVZ)gd5Ue>2YBfkv9C_?!5?&onMnluHlJFEMZhuh;WfmtpJt(5FG5= z0mVOc`WxnQ7XC))Bcar~dsqeZ9T)%uY&r&r#U-Y}Hu?zsR5rs!vv`asKU8ApS*DQc zxz=Og4u1HZVe^JiR!o@T8S3IW2Gfl|>Y&s7z_vMV##U>S%~280G#OzAx_+q>8XDgy zQ)7JS>1-sD(FHR{3G1)2Vzh_L_w)^dUkhW<1!4B4LIirYfT~g*{o-$kvK0vCN-!p{g+U;8|FUn#Z3d#T1Lo*@V`Q``ST&0VTrLl!-w9=6(I#xLWkbWT}(8tv8FKgT1tPF()(zA4S zn^|Jn=VIB(mGa^!5+3!UoV+YWh?`kBp*hyhm$^z3TrSu^c+J-I0ufFW*akDZfEni%skoKyP7iM7Jy^=x zf^Ryh)lI%DPUYj`U^OXe@B%5pgTu^X9G!M4QcGLBY2VKG^x)!hk*WRNS)XbHCi^as zQr0G&yJu=rU37jTNj9`FUR))Y==a;r6 zdt$(~YLCq0X1Qx4k1)=_y6ldI+(({?pQAIgTDzy>(TyKwpCg1gUHr*&mdvWCv2AG} znv$`3g6Ptuk4y;N8;f_(ttQoaa{T1W^d)Z#R(g3qcAtI{D`wODt>@|Axfr}QE6dDv z=%OWxoHZbi%;az>nOl0F=j?Hc6g{!u_W+m z+3g5%k1fR8Dvpr*>yfMUwLOw1lDA(Td6|93es6oP)gVb??s-t2YoCu4q*Dv#&3m#B zCA)j%Pfpbjyg?-Tq$QMeT7YbD;K!LhIxNt9DI~+0U(or*cImE}8dFX;ob7i_u-{G( zG(!z#L|B$AIPLtTW&XKqe<3zg6Jx`d2$I4nsXNE${4wDjDt@cRnpf%Wu9c~3rJapY z$%Tm_te9qX1cS>0F{%w%lauW<$y>o4UHlKV$m{JXko@UpM48bC7W!6QuSHUPYe*jW zo->I4=@6A?tRz{*JM?KG;Tl_|?>ook=*U$wb^{ z7qy$7#=%nPiXb2WuXs=bIiskMMu<-&^9eolAV)aS{{}(n%iTnYj;qM%y zPYMZ#*yl8~O+zMq`zyNdxTvPo5w0Bp+M0<+_R(i*gl<&vfzxAT-=9+}gPZJ?pKSts z$NhX-OhxQ#!m<11X^Qfe+xex|6zT6~Is$@|>X>t8-TocRd z?<4vHa&U3BV_0^Dtyp@D^0hOBBTMCa{Lz?IUcjHS>?y{WGFWozTSS!Kp<+Ao!{Ea& zzw&_2MmC?~2YPD@QaP40?1t7lUb8E_^^4Hz9W4O5LZ%kIjf4nTl)#RaZlMb9QT!~g zjJJt7ki*>kWqSM8pi8)gt|+RAj9&YIwjC3D@Ac*f#Ftg}i-GELgt@GW{!vP~Ao6px zC1?;7Y-hOcOyJvJG=FdhJ#$g2yQdz=zU@hx=?S=pGynh- z!{3~vhXvDhA6ftDHq&K}=vV6V_g%d0egXnj@F?zN*hlJE*TyHj6cr2a>BsCUA4}PJ zK`|=-u_(h&Jg}_$J z62BOO@e$yHvRAY3Sd&+*5^iMpNby4+SrJQ&JjElkP%nCuCMG$u#z7w!N8NA5DDZ{s z$?nF$vFDBMykObW2&L)lsXR(&O%X_T-jmb!+_rVSWmnJkIRW~MSpx^?y#XeUIU}NH zb#@#9ex%&s*~jBdIIfIphU}W~04WKstH;Md;*s&W7u_yG3a@N>7$G5VR6V4Gp!^s| zES{KcaO8(Jyqn%kPdk)?PS?yCh3iqPkCag2pTt^MduuFT@TanYm{!Zy%v z$#&47hZG{*+?cGs+0CN%4Rf`Hxp_zvYLoCvc+$NH;qr87qsB=PrH!6XZR9-yB znC;b~m!9hf-RU0Z&2-Spe|FD+2{%`Lfid-%MyVcd%Uj|6An!rG@EuCS>K>zaQ3#~w zw$L)}OtZz2CmhXP1Ojzzdc5J> zMM$9K<4$@PB;2r>uqLQ%$=flPBHDVtt~>|WjF@P~G6C@uQZ8OVY+>!}LqjvCF@ETF zq?-UE&y2fXbT-Dv83g1NuY`)RmNmpELOlB} z&__Hdw8K5p+;hk<#IijxaC7`ERuaLvm~jpu0ex~31^e`OsTqid1-2T-D%<8gvRiEC zQ$?!1(gfqyN?9=vUx;60&}Y6V((+ga{in)9#uyoMMmVUGz1gDi=j7Kr9K{wvwTx_MUf3AF zWg>r^TlXzc?@RrajH2JqVz81h^Ki4h7Dqt0GJg<*BPkSdJ(|BZuXoWV14OJZV`J!} z3k=Q|X-v(S)-7Fn3`E{#@FW9TLn$QgZe5o{ITzC%{})4igCh`QYxbpA5zufds_?!Rwr*SN)6b;$=7jrkgR$4D<#= zPh6c1j~W!AScAP+=)>*+j3A5E&P=ayneTGh1#%amFWj#giOoD9lPwk4Q8nA?=j(F` z3a{_@!nijpA!Ba2qBqolwzJ%!)8)$(hcd|uQ1u=cMj>=~NV;8NoMmo27^nb^SO~smkYS7= zvq`X}q?OqqPVs-a>c)J@e|rEc#!XFo7o)qq&R+bqt{X6-;E)X?Bg8hUsyXE^%@wkO zu7C6AP{vwbtaw9~l}R%ZJk5Oj6-HQQnQc_FGLJe!O>YnMFzO;J+is2kRukQi3{=J7 zo**47vdSjTGWz>i$%t|lk4;ug#Jk2@ zwlUnMU@9zT4=Lnb-}N`CdAzRYug!d)?I>!vTL(!nJM%Lzb10m2Y-rs3QhN??+vks0 zGMY+h^hx_B>5Uu#g~rDir+^xK30z<>277!vgXM+@nj1mGQD39uFF0y>U0E@+juK1^ zPZxGQN#Hxf=HflSC8RB@Yx56CBGh>w!;+zJ%qDVVYV+Rn907-nHKiEn*|3w~U-AP1 zbhG}1ju^uweR0B1yi-Cs@IQu%m5hdZd`sx}*h71sA@ox(g)-Wi7SV`rE&rEGyNQT~ zluMr(Z$JrPFic&Ltkqx2>%3K2#KKM2xvn@%SJ*it+k#O;Fg!7+)>2AZ;Yl!s^n7a% zV?4F6aqO3yujPYp|4V#2zU`#7-T&><&~c7{1!H3^22x;fcQKvYA-7|H&r2L+tE?D# z5UB%=7;L=4z(>p~^FDhhH!JM`gIFVwZY)wrWz)*P+UpL_0(77jGh-D7{eTEZ36D%R zmCBY%JN_>g<{BZ!#?qkae?gI1O9GVDH7qn4LPTIP;gPMsiG@Ila)I%Rzsdk$Hp&vD zQ2zrlFlxaDCK1ui4U8lr?gxpaM|z+#Bb!JlkQHi$Iyx}Oq_93O$WRK6Gz1to!3Ba% zL@O2P>#Sf!+HGKwEcc0QX52(p18lOThd-9l+cX^qdGG#6G^1TW^F0Y*C84fx{AI>R zc7b7?XzkFyx_9CF7$#g`e(+yH=>f(z{%6MX|H=LT$xScz{|`tv&L+@y$8W{qyMbxD zT5p&kyL?`g?~;{fr4hNe&)ZQ=AuFLMyD_f0OnI(i&vC79+Y(Qt~5o1*no|9q(sZ@c&UlV7lutbNK!>9o5uU*{Go~jInM4R-u{8cR8M8r|mE+ z$*((=EK|Svy&KK^$VvgL^Cz$m*AR{@p_Bv8bFZu`~-Xvn0*jut9& zYz<9Xrfl@)AQ@^0#cTYG{lQYERv#e5lNgt@Kk1j$jFLH&9s1@JAZ^200N7m~s}|U% z>f;F=9Y>Dqso%?_3a#lsDx=^Jvr$3hh>?moxA9w2Qp7UnLclx3`~sG5-JX?PDUfAio8%^S0b)BuCaasydK*wcf8i;V`e-~AJ7_N&N$@N)X9 z{7_yt?0OtrrWFLvXbAW9IW4oB=Z*RK9dCuYcrN|qY(Hz9PVM(l?;fM!Mr!K0cL&S0 zB4Fqm%m6OcQD?ene_SB=bF;}nL(PNl>kXyaq5LCjRn_A+<$kU^;tBv5UuNo8SQZ0F z`pgFVR$1IH?Q+_#-Q~P}ca$@U*&dhM|4Mis$p&(lT;VsfWqF6ZXVxJW74Dp4W`C z+q3L-*A*-ce7n=xURtO?c+Qrr?~W++@1L6`Jm6la=ZKN+9(l3KB=>VYA`%5-f_#V! z5n8%OdcbV+6$rF-t1V!)VRnrSHX%vP@4}leokMff0}U5I#Rm*UC_sm(TOgo;2&Bi> z(DQi-F}*4UMyL0t)LyiV*{6sdAAJ0CzF43)=jjzOdmPvN!R(iV<9LROz!_4nH;Wwfu5 zq7mBd*4=V%&FPN-DgcT;yP~+>YBYO^c`rkhqpq~A&emGOZEKJ7lHcz88r&^6IyUov0$VBUTBo1lmKbHH$+@ihZO_X;Mei{4^FO!%Ls(v#{vzAs8Y+<%ViC^5 zL6Jf8Pgn#oI0TaHFR{YXgbZ-I-6-R=Tvb|7wmq}eYq3qj>GygtK^m_8BY%B80l7Em zDHfBOvfy+M7b@L(glE1!rjF#WEUtYW20jj8(!OukP8gG+WZGcFrp860%v@;anR<0^ z`zN19)N8p%^}ZZUG*ZxSrt9BDxG|d}JOGp8A=fS8pwFR_YPoOEvW!McVaA zCFlCQM^u=Is9e~9U~q7YL#rISZXvV?8Q2BNY$i3yaLj0Jjw%$mNdDeDaLjsr*eD#F zb-*PTv##eUvpf>l$;(YfbEC7ki(xB+>3M;VO#PB?s$RTV_%hDz1M%#3iDieDk6Y2Ko3c~i|-9aCaQ#t4Tq)5dwP`W8InH| z^`39Ns6+S)ChWsIer}?%po@rHHkf+aoV2()p{}kUQBLums1F{^Y*j?xM36 zCBG}6i3S5Z#L+Hn+vJi5AH~V}iYEB1Ewa^_PNp9$?!KZz`tXS3+dS2-WE~BLrjRz9 zRN3ld@4^c9GsDe;zkXiK>HEdYSu4<(@=N4*-$)zi(W9n=yfg;J?9s0>nqzke2QpZd zlvMqc;$7}jk%XT*|Hqv70|Ni4>u*9O1C-CqgUTJ7{xx#YaJu?-gUrJ9M1T*R;UUFz z>8@9xY<|fQ7lUVi_}^TqXMro$vv1^UuIE$AUgrIx;hPCoTWo2i2?sF%(>7OPI3wNN z%oTH~lWW<2uy#2Kh%PC5{IM0eObM6|moZ??f9p{f1{kXi52n{=Z}^^Ivho(!wrr+` zKC!*JLWQ*V`ID(Py9ty*R-5eg@-rKdMJ`sQ^>%m+kAxo?-#IetrskFIz$B(ed7OQv zH&Ny2&-Eq=JX{`jmF6tLz$l7~%yQjx>C;Vrzr-hHBN=wf!*2EmYu4_Zp~^5h^T4vJ z914&*>RP@KeHWSczA+<+w-7IJjtiH!t1!VrDvJTd803%wI_yr&55)n4aS^rqPo*A7bdhQ!Z8Z4Dw`&BKpxf-`#E_wvyUBczE7W|N*e2i2P z(l2&odEUG3#O0Mu;aBhRDMiM8IlZo{B$&MaHhcIAG4PqNy@B?eeqBr)d=;jRZhC9) z2^|w{qo$>ljVgnRcC*)5wf@D10I=D-yVqkK8#aQaczk4tbRLv2N*y%1k})?-^?n;t zs>nBB^qw|R9)1ECBGritunrUMvKbjmU8gR`0KAvZN|r)h?cGQ4eyg}m>pDpC0=rlD zUN)hjk%qClUp6GuVEB4+W%hl&`B&D{iLq2-87&0kTR#S*OLOY&+d}uoTgs05d@Hy| zzV?23R` zI?`y5<)XMGC5}=zyjZ7gwS@i>p7}#edir`05W{la(D-X({p>H+Qs2!@#Y^*4b#~6V zGG+9QbyFv+1)Q^7PaG{jDBKfa^J|p4Dpx@9T{!VF8=tr`0E^=vm95?j;*m3cZm?s+ zojG92)|MZV=pODij2_O28 zCg%1L22?O%(*y3++krGsn-_q?DjAP!cj1A75B#+o=DsZ4CS5-NYuyP+nG^G*65=@T z-rk8LY$&AQH=9ZMlkw_OoaYDsI4Wbnz?W-cu%jYmq8^n})WuShIQ`#!n-hez)k+02 z+m{|b*cQ7khXUGw#DB_fn&^*ZAEVchElC+-E;}`Tj`QuR9+sC$sV7M+hJHnc_vfyc z=x0ID8YBK_{rCmRhNb62>DygN2z^&X;Zme~ovnZVPp|WyZT->lQEu1We-%Gqfog8h zqJ8E85kNl9-9_tmc+Wy>eIH%_%gXo=e&NFX!IHg-5=iH*uwIl;&;T?)F z!z@thzGgs#6t+%;Fe~gNNIbC3Z8`?LoLCT==67=qg|pOnXJuyWdB0pfacDL0FwM^I zFPY2tovjj&qBr!jAg+$ddPJtVB9aZW+S}PV-=EKT+=iasogSSPZp|raOr`2|vJl%u z7c8jQGp#gb&V~RxS#ZC8$ALyfCF8jTyh!p;%^>DJTM|X6oZNkQ(f6V-^A4Ks6UBh; zEA9=Hg`Y!AK<-U>%HA-)IswTJJ~V1luP3KZ-O@Wf*7$C362ont3#$n8hrHJ zv1C*zyfp94x+Wf?F0O7V2b=y*K`2alJ#^&R14cLb+#6{LW#>R$I9;~Y!B1s;9kY4rf}6@| z%t5E!1L*i&5}^r!!($BPcxfLnFApVmg1GC8y~G7Fx-JnUg{Dj?C|XX4!A%2t-Oh7c z9|BS)V%iFw?X-sEQqBm?ZEuzgFPdRhO@CWOiz3$Hm^-LpwD% z^Z<}KIzyUvmPKNhe$}TOEaq7wbp!i(Uj%8j{ATB?cZUDI3*fsct8MnWXB`m-*q6nI zhQfhv?EZ+)R@6?tGwMQ1ACp}vq=g}+cY&ZZprw9i`?{8V1K39m46fHk!RZM;9e+fL zjFbBGj}xq4znX$h=wo@j)~_)|+`sW8k0cN;GnEhP;zdSP*85TeeRc9bOb))fU;Id8 z$;g}+xTa@ChmOdoTdKjrnbC&$6W^x;VbI}rSN$DbHN>^euAPe$#q&P|-Xfx=lj3qf zHAYSTPsnQs1R$12%M2!7!)b!i(h7B-xu1WX`13JjxQMSBmWrpw^Q~9K3FaVlYs!(j zu?x1dDQuPc?kp>xJ{_r1Z#R1J`&6<S)f?4SWUPM1y1K(nY}CCPdKyV zGmiCQ5a~@R-c*g;c@v~NYFuj5w?5sR=+=d)opuLkh_ki`8vGG%N(>u*I<19UIJ8lq z)N_(4WU~F0h*9d(Cb=m8HGRQ~t+ZC#*ni_hm_cTPw0KHqL57fWH@0N-UDlwdG>c#k{K3I1rT`sI|-&GMmvKb7dU(3Ac3$$Jc1W0&bo4xJ^v+!O^9QMlDYWP zmxXZPp1m8!ep)34U_Xu?(p6h}A(Cu2{!xpk0_{ava1m4qzY_k{hVXfqDxI0x1e`)T zz(j{#5{}8Fj**6`>|ZZYOG}!P4*55e^_=i22P|%o>dR*~xLA}=Z>9x-a)8U>i1tS? z9Ug+yZDnk7{qm41n7(tZJJLzMuxZ$JC-dfAQ=D)GV@!C`hZb2}f=y$^QxsfrJI?aX z32&8HKi(1cwAm-eBM(nWynaHWaGG72j9*hkBLMz9`+=&v%AWvyAsQnizf;c}*IVzN z%{g3F=*!p3_v|gQsG={Y%*FZIR?v>c+y-nkx3EYD!j1Ds4nJKUx6jH%#m_VfsbxwQ ze0DpF6ylu3mlYk83adK`Am_^h5rG%E7Wg|b9$y)kr!KZ(D)%+3^^EyOAb&o2^WU0c z%~|%uT;Eu^q%_ZVk~VKDiv{Fe*8T-gv`BNMiB-&HQN0B*oJh`%#S?dBe4|+iH&Lhi zk87%5k60|BSw%(g>V~u{R{}v+OE}A(%!id5Q3>yqBA9kw88v#di^tiT5LXTo)3T}f z6u7TU5CI->)5dSI07!>en!ln9Q>%1upD%vK?bT9pIG&xh|I;`%xaVie(fx(9?_BzP*Yb|Wg&q20VX$Q6RlnidCbyHeG_!FIK^4XR zXodYFC@B?YYrRK3XGLbM*75F4JIbe#_5uTpo28B@Je>H&6a=271`9OyF!$89rnrzj zuiiFz@EOfqSG2ld8!Q_x(z35X=Q6eVN6Y8i5TOo8mU~LmRv>018Rm>n9dqMUAXie^ zFFQMZ#xggY@mXTg14qMs)rtqRT!ef-(ZT`+U7#ozxwpx{20|EZdv&!;METvtP~M5P zg=RN3o**Z~vmvp!!J(qh7iD3EJq6%8J(U7KC7*d_oO)MRMAgM&HZCs_Ia)ejAk-v( z;Out|V%TM>>1_=Zd{|2d9Kbez=`k0rJq5Vh)K#+-Rg8Hkr{(*YBr#pz)AUb4K!Rc~wFN$fKte?i`~DFVM!w z&OY!+2`Smv<7=wV&X-8vKeB&%sZfy8(pr{Va#`+H>OrJu;3#Ee_U$b#VHi4kLs7dm z&Bkt?dLi=V$y{E)#UHz7W0DC-IbVrBon!ob1rc!Cgi{B;bM@#nTmd(iMBik^INmN= zs;pc4+J5ic+Sj{IZ!tM5_!py+Dxef9F6$?qMk_nGV}v`<*GoPmzc?H*a?)^Yv7#(g zj;HvJjqizYa>6t13?)Twu@rpQ9ggMuN>xGB@1!VeJ*Z z@nhLqwDtwQ*)i?G0qf6{JRNmseyyQupf}+09%zYG^`{;m%{IZ5#Nh5DOUx4TK5FuM zn1x%_^njH# zW>6*Tx1$Cm`D%g(FSi+9){uvl4I+GntExYmY43Mw5TnIM?SlR@U4t!^dC3-;g_q7* zZR}Rj;kSLi!I2~t^92FJqwfgJfqC=^SK1%E1o&X|3~xiDPSrNeCHL#g=|wuVrmNmN z*WSOm8vf#Q^*I%$XHxm=Ri8@0WKq_Gv;Zkrlh{0}n?%8m;gt+?m)c0;sZ%TL-V23USdYcBh?#OGUQh8aF;F>z>5vblvJaM8mz8!Kg z#pCb#_XfUU6T5Q$(4ZVhkr8E1DX!oOi&0}{Z*!*!MozE{?qurG>?jFnMxU0KGQTHPa47L&L< z;YzNa+!Tmg?~>=jrBIgRk7#Aqa1-I@Pn)SB=}q8Bs^vF6xT@S$fTgkv$I*^?eg?Vz zBPAu;L1u-M!od;v2+l#CP0NYy6SifRuBF%Y=E0$hhfKZ(UxlCP{M!Yhy<&mu*}*L^ z6%-6`jSG!e+wSe>_F?;!RsZI7wB8x&3U;B)G^Gi5ZKEnJ_*y_cA`3MwA?6L$2#ifslsUu!zcL&JF&fvX0@>RIvrYWkmzXo*T~ zcZ=iV=E{FDhSTCbL62x@arjB$pa<*?NBHg*PO^)BPWG-SC6{xn4B1C>STLphE+7)& zrsD6(O3X9n9^J$DY7pwLjT-h=$2D%ZAKMGJQ3~+$=TxdI!m1zd^TbK8T|NNACIJ*( z-=Nik2Brj|V@AP`y6n3x;J`g!av2L`Zy2pcRuA+yd#t8-n7N)-(g7pYV3OTPw*Y7( zXlk)&W2xBV>`>j_E_bGsHqoH-6>oRo4gRQA4v_MH3$z?t|E+Mem9%En%O~CAv$cwt zY=f&VFF@Y9t3eDdC=;&04GW!)Vi+oYN5ax;mscl6R9~1y?DbW#oXQvCjmw(YR^r`0 z*TQ~p-;NYe-}qeu*M|AeqeMFat)SWU@q7IzLe%+y{VVg{T{q>7=O)-Wb=&h;D=-}E ziQXtc#oW|jBWwXkvdkIzE(ntKCdDkdnSNG~OGSjyB+}bw>GmDZ&3`2FG~kaNOC)Xn zd(#Qx95TT0h&{B)B-4pWtaGgU;mz4rI4=P~A@F1lSY12dI)Gc_`nX8Gz0AAp7Nz_} zV{_;II<@Y*K>`gUelnoO5#M?7xEl~4nGt6iWB9-r3d!uJE^XBREzeR@U89?CBUYnE z^{He12BPWdgrqz-p_IO7lUu_dv<<+|`~BLk_GB8_>gn2u7gN z#I+Mu^uzaJx=7x@QXV#c*Wk>{r^}$EEs^&L><7x^{!|>t|i~k^mQ$#%gK( zVa08rJ^AYN-Xm{tW>{@|F#URC_Pb98$7o_alj=LC2*A8iXWBt_zEp6V@_a3=+^V=O zCdMk@viEDE1y%iIJLe#0sXor}>Qx=Wxzp{3TyMUP3sSo!U*=1Nj3D+$SIP5_0BN&e3#o%#B}VUMInFO6SD4_GnlQw;&6 z7(mx*3#gIC1zqNMmwmMFCbuEo_kxz>wc*96k~N#ort~a?Y!NH1Cy5z_8G*Ig2nW01 zy%ZV#-J|u=kaDUNxjdB`0;(9_O$V5NH}i7gPWSizNLr`+%X#5ofbFUusFMCjab*$< zl(z1zPXE(5y9P8mx2QCShVC+3C0IMjepqj9##hsk>_6I^^+iSPnNuRa?7ud~19-7C zP3}sX0@`wem;IRUF!1tC7dAkhZ!GL;9^5|l{o?iMs(v`X*K(BQx^i$MO-Mm4mT1zC zroGE3T$5;Od|Z!OpSL4>sclRn)s-?w!~V48lzG9b5p@-HnC(D&765k8o{d?6TZ zyDVy!0qB5BxPPsYNwKF`lv1b^0-9!OAi`!i-X?xDpiP_HWUu7Yp&*Z_6xc7Pa_&4E z(or%HJ51$KDRQc=BT4+0BZgTtzT32d#qy?r)aY2%;He<@_s?KG@?U+W_et*_D8_8fG^%lg`R zcg7a5{lLXoGSQq9(GaM3!oIwJXi@Y#3^zL#Ke0mnwYKbCHZ@G`KGEGLUv^L&6$cwV z#_64|Ey(n-c`8|)nhPmw(i1~yi;sxrC zP~M$5*6^Kof0?hx8S+KDMo%*Sck>6+4mdDkvW51_JIoeBzDMgV@23yLPDhEK61jY8 zIewAWR?c5tZz}^j-(DX|VZH>H_KF^$Xr(=E)SSWm+Sb`GU$z*666L1q(z2>7YUhrq z$x^#Ng=$-XwxDFbJB)Uh2!V?o7{UVT8p%Fu4z8UYv{=2L#PM%@;ST#RTb5|rOSIN3 zgoAFdH&m^Sx~*(lO=5qE?X0O!JpZHOzqYCe=r{a179t93)RA3!Q%(2J9B`cCs~G%7 zn{Kep$x$y^>O2c;RrTjRe8-8pw1E^4I3xe}fTie`m{6ZoUSb@DyYQpv9#GfSFx@j* zDtp9(&Cn-r)4%OGcldpJK8n%sQJOQyMI?*$Yokp%kKm6 z=sK&}w=_?rR5A=2{)TKDhPQ^7cdZvVR^UQu-oafobrNB()N3c_tM=IjtBxT;bmW{q zsUKOL_tr{l@j2t)62~y&g&}SMQguh-p)gIE_>d+3hwyy8Up&KOKve|Cz4{!8q2Raf zq#dFEstEi7(~#RTaJ$o8SD3mB3;OA0IV)>j_Ha;2DCC^>xBEx5UQjvMKVG}6u{CE_(5;jI$3m9VYRjVf0w;C(Yz4nd za&K~*|E+Eo(S+XnH-&gJi~s2r#V&lVXL%Q_PJKa5pee08T-7`LHVT~Yl;#0%5XC0Q zC}Nv?xZQnPha*Q0QFWYuans2*H5f3c0T3V3wB@ zzCH#dWEwXuId!^$viW$|v^x2HZ^(g&qlFSvo#pdFD^|SQqTwK^5T0o2AE`-z2qvgG zlhdUN;yuw_nnrJ&7khlZ=}i>8i2gPmaK_7EPi4p{TFA5rtWu$=+xBh34Z8?y&kqhz zAK|^>I*}@i)qK0m;$XpxcGMT{e3jxwCeK>Gue9Ou->=z?5(lqb{85{F^14SUbL5~n(qVnd@I)mg{I3WqbDx&)XR^q3)id{$sYRX zZTI@hN?2w~)&^B86I#`(29Cu|^oK#dBu(($2t+AQyHuw2_C)dcj^$faj!E2jbo^B| z>?ZgXBvv!ZtTZjqVq3Zwq7|jv0OaT+FBLyiX%98m&BelfIUNOs;k9c|!6@gAQ}U{^ZB z-?AqnI>2)UU!;8~%PDGp)#qO8Jn7wO&(HCAc=w)Hr%vvgTwATE?QQ$HlL^BS@$pg*-=8x0}ZcF~eF0UhPZl6@rEVH%}A%wa-vp zE(}Kr%#|dhUzm*hwjSU@qZ99JDseEl=D}1YE%9m>${ug zSZ;&6%oGxsfN|2d67IOVdUmhTU{unt@DQHRkPf?BKC1H@4ma<=f(r3GE385;yKP6) zvjyN=v(>q?K*m5!L6#f|vESANW1b*XKd5-gECgXU?3NIdf)SQ#2dv{n?pD=KF-m zzEi^!lboFq+DTW``mj@|;`d#yHx5CXpb#82$s(umn;-?aPWFnQ9)jk_%6iF{iRE8u zMztnAAuNhG2C1H@KC`gfc(XfK9diISo}Ly8q;EPhG!y4&Yft~UjmFJ)^`A$SPEdPo zCtzvKwT3<#e6UI4KQO6AII^z^F!V*r&$VaOzo+96ANQ40-y9+MacO-tNIw~&+@h~x z-+vMi!spiMM+0sn+!ZCk)E$3)%2z6gT79N9NBF#eGLeJXk7T1YFPz3#V6xIWvAf^pc$B7*esC zvLSH6=yhX_Ye|ej1V!P+<`b>mI(a=a{}h_%-}V&sy7~2l68LCjHd{quloU^ES_&Itrd7dH-KS}Nknw@ZNPU*q^z}a( zH2q)p^K+OPzAl@vER%LSpLwv&>GeaVUN)%&zX_|=4m>MUx*A|aVBMKzzW_5Q8o||1 zuicY*sSvDsG*CIX%0@o1rBh#~Hd0iCON)R2+n@oMGLTiR1N4b!>O9(5jM;~j-F$Ay zC6)Gls^4B8<@OJKGq{)R8*#utz^3h(BLP{`DjIn?ZG@x2mzo`>FsyjS!^-71$8<9j zJul#J@II@@K<0%6TOS42BOj2oyhGrmks>dN&PAFzRBuuvB>_7Z+vgI(KRSbZ)pZAA z^37w!6L<}P#-uo|magG&uXqAiaZzU6xu^6~k1wLAv2*XrH8cza@n1oMuh0Zr;oXi5 z6*Vfqe&BKne&C|w-QO>*^g%m3MLCj~%s6IrxmP_IeIzd6eNN4WfewUFd@tl9(UosPRodB^A zSnsEJ9v=iVhfJJ+znR2Mg0S!`tyjPz(+eu|!rk>cvavB|I0XS0llT{Jgu~xY8-e?; zS7FQbT1?((1WK6D@2l{BX%!aaybLxu_^9?o5iolDv?LGc?XTg}A7bd0c}r6J=Caf{ z5+<65W&5($pPSEC@-2c$iB1<9usywk?}-I?PDD5rXnSO$3w+(lEZ*fC`AG4yp=ZP! zeGoxO{R z#ZWBSU9&vbK@pKXk$h+R@4>&fQVzy3t!2Jay10njKhFxK4v<|t0yO;Fn=c@T!5Mz1 zU$yo1Cw#BHzgo2B7hOm_IEkY#B|+s=Zp7-1fbqWGh$OS&ZxAkl8M)$c4MGedy*NL= zcCJ#H$c@-vfW;10+vBt`y!AQHB)CeE`y#E&PtKJw>7hc*linp2ef<%2xljUpHHb@M zhmF5WzY2*6Vfc|=e>nyRnV}b%7%r#yEL@tvppUUDX1%)Tt|N*qi;#-|P_6L$#ZloP|-&gAn|==7bzDg!JdTV-1_ z=sFLhGQwiyFX}ma+FO1;SPYhbma};Fd;j6hM0Q~aclg0GA3T#w!P9^{QnnYCtv3$F zj9Yg4xreJqn2N9!_s38J^H0~`0v`sG8iovgIk1ik2`Qq-+WFi-465|ZJTq48PkMFr zGm518RUbhPa6rbdzK#o+W#kVK2j_s0>3xE$&}zu)ZaU3k z))lwKwhsXT?BBhGQXmrt;W_HYoZc%k1~xAWexc@<-?WFUN9dWzuzI{NwA*GuVZbN) z1q5Ik)OWZ}aKL_83ye+F10;Sd*|@i@IvlJkC&W(iWNU&se7cdGo-7(J-?)lXC@UMG z_V0fiWe;1T2sHIEfEW&mdJ4Lde$Rysk{>FUcpPX>NPK4cenH(tSx`&Mz76@wYLYuW(Zwa0sJ!ubo1W$u!>!mvQ$jfmHG z;FJdS)QPZ18Ks<~?!_e_#sM_Y(Oc82xJkNtPwXuPp$+j(rVCg*`zRr>4AVje!pd;z zHr0#uVj{Oy-eke@C1ag0ev~8+gC-UTqE@gr-))4IiOZtFaYgrjDK4M!x#Vj(UsK|4 zpRW__y>n&i3kw_h`vClk`~-RRN-Bnc{fB0y`Yh~lnqPhRtg@<5fV(xengn7%giOtF zP>vS}E>?9Y7bF#-hUnu89R`tJkh%b_b1$HhgT+KnM@4tx5MxGbSe%im*IH0N=O+85 z15;l=BlZto-sB_Sf-z6d%=WXVXwNK!oEuB8sHk|f&8$SNi;<1wus;({ic_Pr@kbMG zcc}Qf|BeTz6k6FZ^Lj5pIejviV;v-Tt>_`gPnsi3<3xYW6q)O+) zMoPr#7Y3&3PJLILz;R!|hwFez*9Poy<;@u0{?%fM9tZV41jLt^3%}N*NHB-cFBlKc zE1O^w;MAs*9E<9Xu*P-%GQEG>OjikW4e8u?3*Md8E-7YPb46d;k=2pJ!SJy%;fv=Qm~K*NWp-kG0>6Q z3sI+r46*N8r(X3*zLP6s_Ij^3_c!E{!lEHbb}kt~lB1nL$3hhv*`SVzUqKsZjSUV< zkSEB|>*P+Y<9NGR>V7Re-Bw?=kePqtB?&}v)NgpeBw}*Wn-pVwQe>45c9)z@%Xq#V zI>?9KrVEJr{w6!VU;KRGLjorzWjnGO|=Bck=5LBl8JXi{Kb4>4ry`bL4rmqgfvoyXN(A-2m28Q#Pq{SN?tC5qoZr{ezo3agF=-Q|0zTS~6)Z{l!M1v+3s+EzZJndI6MisGVR zf}A&CN~%S}^#;TWU`BsadIrrC&gbmmG(=yS5GYtHg;*LpbRqNj66ph-K6}JRf5arW_#mEYoAhm61ZHyj`I@S$bMl0=2bL|kcft&g`bUI zj*$8Oq*}ZwRl$As^x;-9kNGO|$L6^dl^TSTKp1)&IBr7Qnyv7&!w>ipaAW!4aOE4} ze+lNjTv7>c>rQ(K${ZX}LXEYW@v(~HQt0ACevRsht0&lDUMyLuevihRaV6AHl`}xi zPzZ&ti-1B0HbI|{J~Q;6sB`))VRBIUH#mOL$@07wU%mJ=?y@V*#j8#PtLiS8`-8^{ zg&_NU0)xQkPCFf}>C0&qruz~!rZpMgVi z8OcF*LsO<;K*r2<8JFz_#JtoqY345Ry9(N$;|pWw?_9G}b0df}%i+R^W>WAEjBdPo6K{hygi0ifGv+SYLwqVY?M z1Uy!UU(=~iF)C%!OLJxIW$GS-hy*z?SyD|XWpA%hK-FC_lNWcsK=TEBZP&?y*Z2Iq z-Xx7r+RIhtnV0|e1E(-II%}Mw0mhz1HR(}bvTf={L#8g71|2?QBz^z=msX@a^IqjC z^Zw?IxU3KEI{HG=uSRxW zGulilYq?H?=%wiSW4CqpWA1_R_o$>SJJ*1)8>L+wrx9C z0<4h-jqG?iu6q<@Pf#GsUNi!$M6;g)2^L~DApr!4TBzX+do0A17g@fh*VE}H3g^L$ zsPo=D30vt%UH<4>)~xR58UJVYdtEaNn?(s>$B_wCwd*>qI0Q`9a-pxu5t|H2-yw!Qw7%#E#D-K!qKDmmw_#L zet%th&=yyzK&gGN2&sMOE994ZaV9$DEHLGeCz>FIWv`D%{?hNNH$v_k;7cmHp!tW= zi})yvM)L1KQdcZEjYtE%mLgS#ol%&n!ioLLxjK?1EKYrnMgfvbWD7v@jy;~jSF1_p zXFxdrLy;6t3Oz-ZcJu|ib~zU*Lw4e7*eSVTcdK7Az~237Ac`0jEXu(-Io2-lG}1%eHyA} zS8<%HA^qfxWxW5^t;c zV~hb?d7<%1UFZt@j-PWn{LYnbTp9D(1!ZHOY5jpy=@iPg8=_qhxGyy*ui=k2+w+jv zpwyAY`eEUol4Zkd!TKODJ*M*Xa8K^Gayt&*ZxMR|uNf`KX7pxQ*2xM@$bl?rdj-!9HI_!*2+;3M{UCTW1G8C6F?E@++xLv+>ht>K}uJ!QVh zyxG>Boyq%jRq)M_s55W}lITZ8sRWsY9}pxaQ^BU{ibFYm>1_U|KgJczbn+ijBD`Y| zumk++8j%K2XeC60jppJ9K@UdrL-0Pd?K{ChO>G(n+s##=B(pOUukcnSezbT7e+6dc zLIFACX4v{q5_mJ%!~I{E2?Y5+D*qp2{QtQ8G*T##N6)zI5-`XhvcJtRMQ6RBE>&V2 zSW0FV2rN7>`}Ii8Zh3H!5xO<^3LNyR$!I|-3R^sZ{s5o1hULQ(?FR9t!}bj|Du9;W z$Lg;Z=f+RS-P)8!0pvE#FnyK&{tA~31-B*DTrWbH>|jUp{7D@iUuEl4iNz4QdrqtU zV2aqGt*_uOMgpN*(wSu^pDY%YJ3QN#h0qrh-$9V+qb-+u*Lsa_ue^dxyB+;^fI!{F z`yTOjH__eBfy~?;FSj5Eg#l9K0UEG@AKpmcx|_%c2W1OB1BA6RH-A6p-|-BHsbzs+ z`Fly7w0@0->_m?StqOS|z<=8QLWk^_BScRz2ns{D5OZbP|Ff5%=z^5@aGL^kXVE#w z-D8YJW&l|Ddq=D125&a+Ct$8@6IB7KjX>f--DnT%KDAgwTyBjTs&x@=0Bd88Ez{UV zIwtr>TiBfE^k22FF@$iTE#e6Zf(jZKGnu*XGC1FJqy!sqi*_J3%%%;dD8RLh)WymF z8&nP7x`n@BW==IC?VB#}D@;W1{^&gXqaAD@3tpTwKf}Kh6daHXboK*DLxOBO3yfSH zPW;itb?Ct);JPrNoL*U&jR6POJI5o)r}2XVW3Hv1a7=**?^)WS5Q9}2(w+n6anL{P zQ$jB-!JV@CH1Yscy=0}NQuI4n(RcCt1k9C9W+v+p(deW=o|*o-N0XA3)NXOv^7hWJ z@@Oa~exma)Uo#KoK03g&t7@LrM`Q*EOvi7CGy_0(5_K1P@F$(W>&z9CAA8$aJ}?9l zlQ35ox_zlt%ZUSNtBznI(&1=L_(w;W_KE2(O}5|L!^G?SPt+}?B;jf=Rt>Amk~7G~ zi4m-MG~^Q(=={GhGeym*TU_zO%DIeQ~akz>f= zKChvaHSM1bH~t1W(4VL!Uy+Do>bY@cDH#`be^(q|-si26Gq3qrtmOUaU6a58RrgU$ zsp)=7<8+QA1f@nqWF)Ey{?Hmm%k$QFDMslo+b(?2+$)J!04bXam0kA9wN=YU6Js+n zL?(uOvFt`P&SaFL%OzuiGCBgdE6Qf&e&zl&B=Z3R3f^ zR#;Xh$y1~LOaNWkYLfU6{?Hai8>~^a)OHtPf9(E$UO*}Nfh2rrI8&L|8^y>&3{?%H zFPK54CzQs0=5&OO`MH{1t6N1ru>hn@nP;l-8p}{IoA0vlK2b@2A`ut-tU~G6_32LZ zhTCkj|Izo5uQ^~v(ltKEs-0R7qe2N_3S0BqakyawteF5sM|-VvH4yI74zhzUPt*Bh zYyXMSN_cxPp88L}_gQ(9^2D*PNL2OODxa$%#P)A-BjIt9%YKzz0TR{1{3O>mOy*6( zHBg{+LAftVq2#`!{!+1zhc=(o5W=jq2Pic3_8u0jA@HnebbuTsVgoC>g0X%7& z0M$nuN+~c(E^VL$Ppke@+Fha^JYNW&=V~1@wH$Z~bFg){|IZuv8@>}{hnZNsgwzqz zU7Qt2lM9L;r7HRPPQ*#v^RsgI`Og~;HFQuQv7(r_y)=&BFPE}99ZLy5w>5vz1%SlP zum|iHp?1y98tdZwgy!(hN*thx_+GTGG>ih-dT!79MXbx@sq9cfkn+}hUL0*$0UgC2 z$g5)WD@r4eASw`okAwv!??{u1dHQ~N@cM^^k=Edyz1uUE_y8Vf1sjmY3F(Z|6(GYT z-zyT8@Bpl3i!Zw8AE{=Hd%ETW^UKv0|FTPj@Kd`2Z7rxapSy}Zg)2yY5DoPD?Oc+;rs!Ci(3j`>B1LQbzY{9FfZRqdh$l3uI(HqFl@PZbY#1ErM1-1&I zWz7$c?oyzx#!yYgbA##21$imVavjS#u$^51V#heKna)2_j7*WYR8DWX=cir=9tdyi zJBiKkI0qQ*(FCcwZZ>nz+CI~idNRmawf!EnE{$s`V4#WLYb*4F2*1X^mt*iA@GW|| zg1GFy=^0=Tt_-nN493ZEsO+kJ3lhSo+^w6Pzl=`=EzNq&#{bmq{Jg~&cm~S>yGZ5B z2~suP3<~VWs7Hv)0#N3HC~H*KO;HDZzjMZWr?M72JBSS?2CH0u3XI%$y@Q_r90%rM zav#H@WC$7WTOVQ<0>@KfA7l%;Fg0V=%Wq;9r$3}@-Nj!Z|A5`|Goh8qzZ6k>=N}k(ij}e3(K@jYzr)iN+=sJS?P%;UHQJeWjI_y`l%M)T`i76Ev`>G z>f`n-H@N6}7DxHeDr z4QOCYd+8xv{6p2Bn^vexYAu+M(6u!@aaqucsvXfm7X1K`L?upQs6eE1F>gVyb*pao z-E9K8K{uy0Y)E$U%v8_9CWmb-z9+y>6`kmKW;$9-TW3Z3^gVtIS==u4*ga5~Tz2D& z1x10ZvXyCmOt&rj*YaKSMF)`*!!h6RB?g9wU0fW8y?%vHpx$m*4Ysi2x^;tlyxviX zpG_z`Hf8%`a*$=GHV0||s?7c^f5N2&GCI>WCoPt%vc_EYs1pqjhNc3nt3r9Zz_c0o z?b4tB;{KY2`*U;Eomg;e9yRcVMybjCU9f4(jH+UgJ5e#Qd$kZw`}+S6NGu2Qbn=*S z$5f4&&WX{bVjj#F+vxa6;znqwk;t_(rt96-ZH3&mqa`1cg+N5@mgE2z?2x1OkJ%fo z3NydT!rn)`SQ|ALy@y&)@y8Ea1Z#l;MTf}Qlk)OSC&u3H_Z37GmynEJeoXYT4cLG& z;Wv!+)V*jI*y2D!u_j0B4$eTE`oE^YpuK~4B=*peT%_$g-m~$8R_NpBjY3DTyQuB zLPs>JWP-Y9UH0p&_k_%R_n&n97O6sPnO1|SPw|U2*7Q$}J?G+D18-&Bmne}Mo@vOP z-ABB!_zUVfX(U_=6?sy%Ja3S$UvetYX}O&O;4AFxq9m02)HrA$w+xPr^kzF?9J6_3 z#d4E_HvWTY#XGl?Xf<$Xk(*!RA?t7B>^pH#Cu{M}+LF0Q{96ciVOz0dNWe(;!de2O znw2j3@nz9Xz4oy8Z=Ugjpd@9#)F&9C@HfyjpcZ zD0;Tpha24CQZloTils24I%9E+noIIImV9`8%dO9|L1f;`eWxoQ0CZS`ftb$`X0uI_+1YQ~=t)<^%SD$7;rYw@#K}2Xz!VfT{NG z+-1$Jo`MTx4*4qzWDZUH%vf{ae3S?$owWVK;6#o&#r*J!eBb*3_;61Q2Z`8y zou#zsiZ@4o2k$hYq4@qTcth}++t!Q)`@&h(jTg(S9{y9y)bzIQ!D__&VgC>tP0v^q zW%FRiLjDAF&_L>ZKp5`(A-I)BA=EF1$v1mJNtwu_(#)V3^3kL0I-S3DEKKCV^P#Y1 zGq7o((>derV=T}&?{STl)=tS@bqAxPC8#)?iab>KGCbHIt;fz|@4a$2WY%SJ#8H1S zqFj&i#^c)MMUvw?7v)GkX2I!a#riD?kl~|z-5|w^(Do{n5Hsi zJ_cPoF5A3w9xx+tp_ZX$+_|Ql*Om_I@Ygg++IBZif+K7341r>$q|$2c-HzX65XuPA(kXGAr6Rf9>aRZPMQ*?^~Ez z`QZ9GJ!%;Ngs+w1X{C zhtYBU_s8`f=34g08IowQ8J;43P6Iy(6;9+c+p7CwxR;@|8KLuwHw(@CQ^dqT+u)>0xrvr}Y3SqN(h zQ*{Aef8&$>*RuskZ}nOxZ%J8Di%47P5fSw@^w4`MDn~1CsXAC2=O|0dY((a6bxfBh zyoub@wDS=Qr^H7iBNFkGo@iozn1-LI&0q8Ddq>i+lv7Leb|mUDyq6M0I0_^)dsUg9 zgz2gb;jsy~2F4Q(QXM*-st@RB9RoN$nnHw#yG=$$#*y6)%=OZWV?3iF3eZ3qXU=Ti zBB+3N1D=3@?D>kb3%-SQo&SrdnQ%X?w$IOgD3hX2AE@88iSz-Pe7D*^IQ=R$emm>( zza9^SkBZqww~%NxZN+kLb2TQ9Y+~VS{hoBsPb-dtyP zavBW(^$`ACe{1+7uP_VYD-R9U9MxX}J*~Jzd`3gIARLb6w=0;MSf9}Rv;QQ_#*mZP z20_}B-45y*d;am;4VDKW?hsx}ypL=+iOxR~>oTFL8vhYrf{fVdB*?1~z>Ld1pQuy~ zwn0gm?}@sfxC|F67O*=#DXf|Rimx{d3qqkN+iN#E2{Xaq<@o%%zot1TQyIZZtUcKU z!+Z9D=q(>oCO`S_>@annX)@y3bJs>)DqX~db%5fy4bLvWAVBzTQYZ8(PpOFA)qIPZ zyK45QKxSzp%|HMX1 znu&|cpg7?sH2qU~56u(LJd`jYrfSdZ+hf37|o8>=~ zNCl4ynp|nKl;&;xIFL$`%_{qa*dT8vzdaa$^F0khi(BU&a0=pnk&gF4nWUSe1Q!k0 z(2-d2*qren8F9T44+`KYs=y%P79g$?D>V1W-B#}g5Cu;!#|RKj_AwSV!x@~gX>;e!+gM?xFS%3PD?fQs>+AUbkHe<91H z>c8JRc{ETMR(Q>0cBfDT*tfR>wd1&?+$>vhB|rXA`#>+>@~Ci`K;3-PD(ugQhozbU?a`No7}Xa?*bT zPr%7vK)A2OT6aP3F}9pfKzi61usrj+2%w7!XIEA%$WI&wwnI< zWMK2Gt)V&an8TQRDTD-g9;zC#d5UnK4VRAV}hvI*2{09>4fke#Bcr~=s+ksx7@fPA_^yM}5 zo;F)eIa7K7swmfl54qhkT!6jk7}!(uqw*@vqPm=R(IK!GO^?4p^d2c18MYgqV)VnY zURp5%r_&0qk~wL9s3!8TgGkx2y*e@{{knTw|8V9in_I(N(>jRvd^f1Id6V1jWzg(D zd1Br3Ixv*N&V0qgIc<^e8Ixka^laODTwd3Ycy3h;$fY^`i&%;!^Gy|*{;23oVJbAb zauU{|qn@*K4~mQ-!ydOYUb-u#YW2dU5f+_X+D^oU*`Y{JH}|^P-u#-7c;f(npeNBo z_@tLDQ5T&G@L1{GN<{7CUca!4d_*@v?f;0S3y-?6sg40$5wj?5kj(i0imLr$KhM-t z@TZV8U|uP0TkU`!GnmC}gN*z%b|PQ5x*{ z;PG-*z{a~PbY+X7U2n7(kE`;&$<5M}1^8lmFqP_7;=ni1D7xoG?^;=749=%tW#Kev zvw^?1#fMYTNNS1o8|}H{_Q+)5VG%#*c7`EF;R4pJa9#{&#}{5*!x; z8BF$lip8bfR!#TFX@prB*IwVL#L>x=-m;(n4w^QTx1BuFFEwnv4;vctbPoVc7MS0I0R>%@8q~G$JfR`kn=w!`TZ6jgNp_a z^wO{?KxLhBoAk?`Wj;xo4cvv)hYs$P1CrV(%lrL^0M%hf2P|)KG%0T^8iZi=L}x1 zQeL~OQ$uCdIvr152>;grd`&%8ldyu*ZNG`_yd;b9Z3c-@(Y|!oHwO+a5cCjbl9Phk zLhf1`?rE*kwPaF~JlHAvog+u0B++a=+@3`y1GP9S!&{v{SpKu~(?FoB!hcT=Nn`yO zH80OAt6zK#F)H(s)ty=aU`Loxy^q?zau%1-vnIWI807TdUcXLM4gMQd(p2V@(h|IF<~fZB!&ZS6#nA>zT9^<^%cbdX4A&*|@V3)b<#sX1%IZ@zQJLJj&9UP{l} z_Q$$7vM*w)(>DON@u~ltLMDKtYYvU50`yASzdz_oiCEq?MP`L zT3kf&P4Oj&WJi)*Cprqz;6sZA883*U&khC66%Q%E#y=*#)r2GvG;5fZZC-Q zw+RnpCRBzX4#Wx|*HrB9?(h4BW3TgdEFJDu*8_(vyX=uf8erbFug+&L7uVl5{V

|JuC-t#7>@@#!s%43&s*9_n=wb9x8Z3ovkIHS+b9>FgETLpDV zSQ8f9)(qKJ=YZX&W`o?xX73dHuxo)h(w9-aOWbP*x#=w9K6+ia)cG89AB>n!{ud#V zdgIOJ%=vPDO@3FnCSUh|qxU@)yWAgV&e^F!wg4TnsB{qkZ2gQoFEgb_>p>>XPl`(A3Y@iY4X86IS_ z?>I0O4d?RV8I%;-maEne!@w_JZ%>(^VQh>i* zJU!>YFA^8cOY*SHI?hq>kMkD#N;gzhVMoC=C5$A<8b$^+0YB{E2L>ZeB!Q8F-z3og zCBnDv@+6XOUqcPw1)9x*<|)tttv*$io{^RIC1z}0P4{&U^LHKRpJbAqvzn$id3#P+ zko@trhCQ$QlvzUNlv3~CQJ16C6ui%KU&Q`(u6)jlhSk#+*4N>JS6}K(zqwC;=+{%h zE1^nKi*=m!UALatv0pd4-Ao$gWw}tZTzcB?gmScCTh4qTb;A292|=4X63B4>AMUZhG{Z`5%k|0NNBVgh7 zIIXj7`q1l3DZd-tyR}(Q#WG&$E)mFfTHnKH6u=%^e%C9Fkvy!tlPP~W_tA4bGi*gr zd*P^$_%oi<(*Mv`UmfoVR=&|IIr6nuUf~v8Y!4#hgQxFcR~!3e9abMM+_!!*qT6FC z{8W|t8kM4ja*|T(4RtLxU%?U!lJ|5r_PyNV#fpVS_ce@2=(!^Io;ox?RE0s14S2q| zn`?65>6-S}IffSO(6HDiMf+YB2d_8X4&^*<=t1NnvmgAzU0Y4{X|shByjqq+CvQt( zJNdfFspjZ!HHVGLT~48UC+Vg>&ChV)3BAL0X^o+8I!4`{-_DuMPd}RMXlxl16-w7I zndR^+UDpmxPh#;3cH2vq#JDf*Yy?$Mv~|pfAzuaGK4$oO0BT;*OLmXKx3I)@!@+1WMI?WJd6V%1aA z61BDPaJdG4Z@ftNu(_S7i_5EhjQ((c(pStZ{$&2-{uJ5{ruG&l46NXmg{!%#tEru% zrN~W-8x}V$9Lz1Wt<4=Bpm%a`vL+__Q#5h&@E*EGcoX!XKH<{B#ZXP_ytTP27>Maj zk5{~W`f6J0_yJJvV?Qi(Tv|!xik5+?m6O+ str: + res = subprocess.check_output([OTOOL, "-D", str(dylib_path.absolute())]).decode( + "utf-8" + ) + + return res.split("\n")[1] + + +def get_dylib_dependencies(dylib_path: Path) -> List[str]: + output = ( + subprocess.check_output([OTOOL, "-L", str(dylib_path.absolute())]) + .decode("utf-8") + .split("\n")[1:] + ) + + res = [] + + for line in output: + line = line.strip() + index = line.find(" (compatibility version ") + if index == -1: + continue + + line = line[:index] + + res.append(line) + + return res + + +def replace_dylib_id(dylib_path: Path, new_id: str): + subprocess.check_call( + [INSTALL_NAME_TOOL, "-id", new_id, str(dylib_path.absolute())] + ) + + +def change_dylib_link(dylib_path: Path, old: str, new: str): + subprocess.check_call( + [INSTALL_NAME_TOOL, "-change", old, new, str(dylib_path.absolute())] + ) + + +def add_dylib_rpath(dylib_path: Path, rpath: str): + subprocess.check_call( + [INSTALL_NAME_TOOL, "-add_rpath", rpath, str(dylib_path.absolute())] + ) + + +def fixup_dylib( + dylib_path: Path, + replacement_path: str, + search_path: List[str], + content_directory: Path, +): + dylib_id = get_dylib_id(dylib_path) + new_dylib_id = replacement_path + "/" + os.path.basename(dylib_id) + replace_dylib_id(dylib_path, new_dylib_id) + + dylib_dependencies = get_dylib_dependencies(dylib_path) + dylib_new_mapping = {} + + for dylib_dependency in dylib_dependencies: + if ( + not dylib_dependency.startswith("@executable_path") + and not dylib_dependency.startswith("/usr/lib") + and not dylib_dependency.startswith("/System/Library") + ): + dylib_dependency_name = os.path.basename(dylib_dependency) + library_found = False + for library_base_path in search_path: + lib_path = Path(os.path.join(library_base_path, dylib_dependency_name)) + + if lib_path.exists(): + target_replacement_path = get_path_related_to_target_exec( + content_directory, lib_path + ) + + dylib_new_mapping[dylib_dependency] = ( + target_replacement_path + + "/" + + os.path.basename(dylib_dependency) + ) + library_found = True + + if not library_found: + raise Exception( + f"{dylib_id}: Cannot find dependency {dylib_dependency_name} for fixup" + ) + + for key in dylib_new_mapping: + change_dylib_link(dylib_path, key, dylib_new_mapping[key]) + + +FILE_TYPE_ASSEMBLY = 1 + +ALIGN_REQUIREMENTS = 4096 + + +def parse_embedded_string(data: bytes) -> Tuple[bytes, str]: + first_byte = data[0] + + if (first_byte & 0x80) == 0: + size = first_byte + data = data[1:] + else: + second_byte = data[1] + + assert (second_byte & 0x80) == 0 + + size = (second_byte << 7) | (first_byte & 0x7F) + + data = data[2:] + + res = data[:size].decode("utf-8") + data = data[size:] + + return (data, res) + + +def write_embedded_string(file, string: str): + raw_str = string.encode("utf-8") + raw_str_len = len(raw_str) + + assert raw_str_len < 0x7FFF + + if raw_str_len > 0x7F: + file.write(struct.pack("b", raw_str_len & 0x7F | 0x80)) + file.write(struct.pack("b", raw_str_len >> 7)) + else: + file.write(struct.pack("b", raw_str_len)) + + file.write(raw_str) + + +class BundleFileEntry(object): + offset: int + size: int + compressed_size: int + file_type: int + relative_path: str + data: bytes + + def __init__( + self, + offset: int, + size: int, + compressed_size: int, + file_type: int, + relative_path: str, + data: bytes, + ) -> None: + self.offset = offset + self.size = size + self.compressed_size = compressed_size + self.file_type = file_type + self.relative_path = relative_path + self.data = data + + def write(self, file): + self.offset = file.tell() + + if ( + self.file_type == FILE_TYPE_ASSEMBLY + and (self.offset % ALIGN_REQUIREMENTS) != 0 + ): + padding_size = ALIGN_REQUIREMENTS - (self.offset % ALIGN_REQUIREMENTS) + file.write(b"\0" * padding_size) + self.offset += padding_size + + file.write(self.data) + + def write_header(self, file): + file.write( + struct.pack( + "QQQb", self.offset, self.size, self.compressed_size, self.file_type + ) + ) + write_embedded_string(file, self.relative_path) + + +class BundleManifest(object): + major: int + minor: int + bundle_id: str + deps_json: BundleFileEntry + runtimeconfig_json: BundleFileEntry + flags: int + files: List[BundleFileEntry] + + def __init__( + self, + major: int, + minor: int, + bundle_id: str, + deps_json: BundleFileEntry, + runtimeconfig_json: BundleFileEntry, + flags: int, + files: List[BundleFileEntry], + ) -> None: + self.major = major + self.minor = minor + self.bundle_id = bundle_id + self.deps_json = deps_json + self.runtimeconfig_json = runtimeconfig_json + self.flags = flags + self.files = files + + def write(self, file) -> int: + for bundle_file in self.files: + bundle_file.write(file) + + bundle_header_offset = file.tell() + file.write(struct.pack("iiI", self.major, self.minor, len(self.files))) + write_embedded_string(file, self.bundle_id) + + if self.deps_json is not None: + deps_json_location_offset = self.deps_json.offset + deps_json_location_size = self.deps_json.size + else: + deps_json_location_offset = 0 + deps_json_location_size = 0 + + if self.runtimeconfig_json is not None: + runtimeconfig_json_location_offset = self.runtimeconfig_json.offset + runtimeconfig_json_location_size = self.runtimeconfig_json.size + else: + runtimeconfig_json_location_offset = 0 + runtimeconfig_json_location_size = 0 + + file.write( + struct.pack("qq", deps_json_location_offset, deps_json_location_size) + ) + file.write( + struct.pack( + "qq", + runtimeconfig_json_location_offset, + runtimeconfig_json_location_size, + ) + ) + file.write(struct.pack("q", self.flags)) + + for bundle_file in self.files: + bundle_file.write_header(file) + + return bundle_header_offset + + +def read_file_entry( + raw_data: bytes, header_bytes: bytes +) -> Tuple[bytes, BundleFileEntry]: + ( + offset, + size, + compressed_size, + file_type, + ) = struct.unpack("QQQb", header_bytes[:0x19]) + (header_bytes, relative_path) = parse_embedded_string(header_bytes[0x19:]) + + target_size = compressed_size + + if target_size == 0: + target_size = size + + return ( + header_bytes, + BundleFileEntry( + offset, + size, + compressed_size, + file_type, + relative_path, + raw_data[offset : offset + target_size], + ), + ) + + +def get_dotnet_bundle_data(data: bytes) -> Optional[Tuple[int, int, BundleManifest]]: + offset = data.find(hashlib.sha256(b".net core bundle\n").digest()) + + if offset == -1: + return None + + raw_header_offset = data[offset - 8 : offset] + (header_offset,) = struct.unpack("q", raw_header_offset) + header_bytes = data[header_offset:] + + ( + major, + minor, + files_count, + ) = struct.unpack("iiI", header_bytes[:0xC]) + header_bytes = header_bytes[0xC:] + + (header_bytes, bundle_id) = parse_embedded_string(header_bytes) + + # v2 header + ( + deps_json_location_offset, + deps_json_location_size, + ) = struct.unpack("qq", header_bytes[:0x10]) + ( + runtimeconfig_json_location_offset, + runtimeconfig_json_location_size, + ) = struct.unpack("qq", header_bytes[0x10:0x20]) + (flags,) = struct.unpack("q", header_bytes[0x20:0x28]) + header_bytes = header_bytes[0x28:] + + files = [] + + deps_json = None + runtimeconfig_json = None + + for _ in range(files_count): + (header_bytes, file_entry) = read_file_entry(data, header_bytes) + + files.append(file_entry) + + if file_entry.offset == deps_json_location_offset: + deps_json = file_entry + elif file_entry.offset == runtimeconfig_json_location_offset: + runtimeconfig_json = file_entry + + file_entry = files[0] + + return ( + file_entry.offset, + header_offset, + BundleManifest( + major, minor, bundle_id, deps_json, runtimeconfig_json, flags, files + ), + ) + + +LC_SYMTAB = 0x2 +LC_SEGMENT_64 = 0x19 +LC_CODE_SIGNATURE = 0x1D + + +def fixup_linkedit(file, data: bytes, new_size: int): + offset = 0 + + ( + macho_magic, + macho_cputype, + macho_cpusubtype, + macho_filetype, + macho_ncmds, + macho_sizeofcmds, + macho_flags, + macho_reserved, + ) = struct.unpack("IiiIIIII", data[offset : offset + 0x20]) + + offset += 0x20 + + linkedit_offset = None + symtab_offset = None + codesign_offset = None + + for _ in range(macho_ncmds): + (cmd, cmdsize) = struct.unpack("II", data[offset : offset + 8]) + + if cmd == LC_SEGMENT_64: + ( + cmd, + cmdsize, + segname_raw, + vmaddr, + vmsize, + fileoff, + filesize, + maxprot, + initprot, + nsects, + flags, + ) = struct.unpack("II16sQQQQiiII", data[offset : offset + 72]) + segname = segname_raw.decode("utf-8").split("\0")[0] + + if segname == "__LINKEDIT": + linkedit_offset = offset + elif cmd == LC_SYMTAB: + symtab_offset = offset + elif cmd == LC_CODE_SIGNATURE: + codesign_offset = offset + + offset += cmdsize + pass + + assert linkedit_offset is not None and symtab_offset is not None + + # If there is a codesign section, clean it up. + if codesign_offset is not None: + ( + codesign_cmd, + codesign_cmdsize, + codesign_dataoff, + codesign_datasize, + ) = struct.unpack("IIII", data[codesign_offset : codesign_offset + 16]) + file.seek(codesign_offset) + file.write(b"\0" * codesign_cmdsize) + + macho_ncmds -= 1 + macho_sizeofcmds -= codesign_cmdsize + file.seek(0) + file.write( + struct.pack( + "IiiIIIII", + macho_magic, + macho_cputype, + macho_cpusubtype, + macho_filetype, + macho_ncmds, + macho_sizeofcmds, + macho_flags, + macho_reserved, + ) + ) + + file.seek(codesign_dataoff) + file.write(b"\0" * codesign_datasize) + + ( + symtab_cmd, + symtab_cmdsize, + symtab_symoff, + symtab_nsyms, + symtab_stroff, + symtab_strsize, + ) = struct.unpack("IIIIII", data[symtab_offset : symtab_offset + 24]) + + symtab_strsize = new_size - symtab_stroff + + new_symtab = struct.pack( + "IIIIII", + symtab_cmd, + symtab_cmdsize, + symtab_symoff, + symtab_nsyms, + symtab_stroff, + symtab_strsize, + ) + + file.seek(symtab_offset) + file.write(new_symtab) + + ( + linkedit_cmd, + linkedit_cmdsize, + linkedit_segname_raw, + linkedit_vmaddr, + linkedit_vmsize, + linkedit_fileoff, + linkedit_filesize, + linkedit_maxprot, + linkedit_initprot, + linkedit_nsects, + linkedit_flags, + ) = struct.unpack("II16sQQQQiiII", data[linkedit_offset : linkedit_offset + 72]) + + linkedit_filesize = new_size - linkedit_fileoff + linkedit_vmsize = linkedit_filesize + + new_linkedit = struct.pack( + "II16sQQQQiiII", + linkedit_cmd, + linkedit_cmdsize, + linkedit_segname_raw, + linkedit_vmaddr, + linkedit_vmsize, + linkedit_fileoff, + linkedit_filesize, + linkedit_maxprot, + linkedit_initprot, + linkedit_nsects, + linkedit_flags, + ) + file.seek(linkedit_offset) + file.write(new_linkedit) + + +def write_bundle_data( + output, + old_bundle_base_offset: int, + new_bundle_base_offset: int, + bundle: BundleManifest, +) -> int: + # Write bundle data + bundle_header_offset = bundle.write(output) + total_size = output.tell() + + # Patch the header position + offset = file_data.find(hashlib.sha256(b".net core bundle\n").digest()) + output.seek(offset - 8) + output.write(struct.pack("q", bundle_header_offset)) + + return total_size - new_bundle_base_offset + + +input_directory: Path = Path(args.input_directory) +content_directory: Path = Path(os.path.join(args.input_directory, "Contents")) +executable_path: Path = Path(os.path.join(content_directory, args.executable_sub_path)) + + +def get_path_related_to_other_path(a: Path, b: Path) -> str: + temp = b + + parts = [] + + while temp != a: + temp = temp.parent + parts.append(temp.name) + + parts.remove(parts[-1]) + parts.reverse() + + return "/".join(parts) + + +def get_path_related_to_target_exec(input_directory: Path, path: Path): + return "@executable_path/../" + get_path_related_to_other_path( + input_directory, path + ) + + +search_path = [ + Path(os.path.join(content_directory, "Frameworks")), + Path(os.path.join(content_directory, "Resources/lib")), +] + + +for path in content_directory.rglob("**/*.dylib"): + current_search_path = [path.parent] + current_search_path.extend(search_path) + + fixup_dylib( + path, + get_path_related_to_target_exec(content_directory, path), + current_search_path, + content_directory, + ) + +for path in content_directory.rglob("**/*.so"): + current_search_path = [path.parent] + current_search_path.extend(search_path) + + fixup_dylib( + path, + get_path_related_to_target_exec(content_directory, path), + current_search_path, + content_directory, + ) + + +with open(executable_path, "rb") as input: + file_data = input.read() + + +(bundle_base_offset, bundle_header_offset, bundle) = get_dotnet_bundle_data(file_data) + +add_dylib_rpath(executable_path, "@executable_path/../Frameworks/") + +# Recent "vanilla" version of LLVM (LLVM 13 and upper) seems to really dislike how .NET package its assemblies. +# As a result, after execution of install_name_tool it will have "fixed" the symtab resulting in a missing .NET bundle... +# To mitigate that, we check if the bundle offset inside the binary is valid after install_name_tool and readd .NET bundle if not. +output_file_size = os.stat(executable_path).st_size +if output_file_size < bundle_header_offset: + print("LLVM broke the .NET bundle, readding bundle data...") + with open(executable_path, "r+b") as output: + file_data = output.read() + bundle_data_size = write_bundle_data( + output, bundle_base_offset, output_file_size, bundle + ) + + # Now patch the __LINKEDIT section + new_size = output_file_size + bundle_data_size + fixup_linkedit(output, file_data, new_size) diff --git a/distribution/macos/construct_universal_dylib.py b/distribution/macos/construct_universal_dylib.py new file mode 100644 index 00000000..b6c3770c --- /dev/null +++ b/distribution/macos/construct_universal_dylib.py @@ -0,0 +1,95 @@ +import argparse +import os +from pathlib import Path +import platform +import shutil +import subprocess + +parser = argparse.ArgumentParser( + description="Construct Universal dylibs for nuget package" +) +parser.add_argument( + "arm64_input_directory", help="ARM64 Input directory containing dylibs" +) +parser.add_argument( + "x86_64_input_directory", help="x86_64 Input directory containing dylibs" +) +parser.add_argument("output_directory", help="Output directory") +parser.add_argument("rglob", help="rglob") + +args = parser.parse_args() + +# Use Apple LLVM on Darwin, otherwise standard LLVM. +if platform.system() == "Darwin": + LIPO = "lipo" +else: + LIPO = shutil.which("llvm-lipo") + + if LIPO is None: + for llvm_ver in [15, 14, 13]: + lipo_path = shutil.which(f"llvm-lipo-{llvm_ver}") + if lipo_path is not None: + LIPO = lipo_path + break + +if LIPO is None: + raise Exception("Cannot find a valid location for LLVM lipo!") + +arm64_input_directory: Path = Path(args.arm64_input_directory) +x86_64_input_directory: Path = Path(args.x86_64_input_directory) +output_directory: Path = Path(args.output_directory) +rglob = args.rglob + + +def get_new_name( + input_directory: Path, output_directory: str, input_dylib_path: Path +) -> Path: + input_component = str(input_dylib_path).replace(str(input_directory), "")[1:] + return Path(os.path.join(output_directory, input_component)) + + +def is_fat_file(dylib_path: Path) -> str: + res = subprocess.check_output([LIPO, "-info", str(dylib_path.absolute())]).decode( + "utf-8" + ) + + return not res.split("\n")[0].startswith("Non-fat file") + + +def construct_universal_dylib( + arm64_input_dylib_path: Path, x86_64_input_dylib_path: Path, output_dylib_path: Path +): + if output_dylib_path.exists() or output_dylib_path.is_symlink(): + os.remove(output_dylib_path) + + os.makedirs(output_dylib_path.parent, exist_ok=True) + + if arm64_input_dylib_path.is_symlink(): + os.symlink( + os.path.basename(arm64_input_dylib_path.resolve()), output_dylib_path + ) + else: + if is_fat_file(arm64_input_dylib_path) or not x86_64_input_dylib_path.exists(): + with open(output_dylib_path, "wb") as dst: + with open(arm64_input_dylib_path, "rb") as src: + dst.write(src.read()) + else: + subprocess.check_call( + [ + LIPO, + str(arm64_input_dylib_path.absolute()), + str(x86_64_input_dylib_path.absolute()), + "-output", + str(output_dylib_path.absolute()), + "-create", + ] + ) + + +print(rglob) +for path in arm64_input_directory.rglob("**/*.dylib"): + construct_universal_dylib( + path, + get_new_name(arm64_input_directory, x86_64_input_directory, path), + get_new_name(arm64_input_directory, output_directory, path), + ) diff --git a/distribution/macos/create_app_bundle.sh b/distribution/macos/create_app_bundle.sh new file mode 100755 index 00000000..0fa54ead --- /dev/null +++ b/distribution/macos/create_app_bundle.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +set -e + +PUBLISH_DIRECTORY=$1 +OUTPUT_DIRECTORY=$2 +ENTITLEMENTS_FILE_PATH=$3 + +APP_BUNDLE_DIRECTORY="$OUTPUT_DIRECTORY/Ryujinx.app" + +rm -rf "$APP_BUNDLE_DIRECTORY" +mkdir -p "$APP_BUNDLE_DIRECTORY/Contents" +mkdir "$APP_BUNDLE_DIRECTORY/Contents/Frameworks" +mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS" +mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources" + +# Copy executable and nsure executable can be executed +cp "$PUBLISH_DIRECTORY/Ryujinx" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx" +chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx" + +# Then all libraries +cp "$PUBLISH_DIRECTORY"/*.dylib "$APP_BUNDLE_DIRECTORY/Contents/Frameworks" + +# Then resources +cp Info.plist "$APP_BUNDLE_DIRECTORY/Contents" +cp Ryujinx.icns "$APP_BUNDLE_DIRECTORY/Contents/Resources/Ryujinx.icns" +cp updater.sh "$APP_BUNDLE_DIRECTORY/Contents/Resources/updater.sh" +cp -r "$PUBLISH_DIRECTORY/THIRDPARTY.md" "$APP_BUNDLE_DIRECTORY/Contents/Resources" + +echo -n "APPL????" > "$APP_BUNDLE_DIRECTORY/Contents/PkgInfo" + +# Fixup libraries and executable +python3 bundle_fix_up.py "$APP_BUNDLE_DIRECTORY" MacOS/Ryujinx + +# Now sign it +if ! [ -x "$(command -v codesign)" ]; +then + if ! [ -x "$(command -v rcodesign)" ]; + then + echo "Cannot find rcodesign on your system, please install rcodesign." + exit 1 + fi + + # cargo install apple-codesign + echo "Usign rcodesign for ad-hoc signing" + rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$APP_BUNDLE_DIRECTORY" +else + echo "Usign codesign for ad-hoc signing" + codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$APP_BUNDLE_DIRECTORY" +fi \ No newline at end of file diff --git a/distribution/macos/create_macos_build_ava.sh b/distribution/macos/create_macos_build_ava.sh new file mode 100755 index 00000000..23eafc12 --- /dev/null +++ b/distribution/macos/create_macos_build_ava.sh @@ -0,0 +1,120 @@ +#!/bin/bash + +set -e + +if [ "$#" -lt 7 ]; then + echo "usage " + exit 1 +fi + +mkdir -p "$1" +mkdir -p "$2" +mkdir -p "$3" + +BASE_DIR=$(readlink -f "$1") +TEMP_DIRECTORY=$(readlink -f "$2") +OUTPUT_DIRECTORY=$(readlink -f "$3") +ENTITLEMENTS_FILE_PATH=$(readlink -f "$4") +VERSION=$5 +SOURCE_REVISION_ID=$6 +CONFIGURATION=$7 +EXTRA_ARGS=$8 + +if [ "$VERSION" == "1.1.0" ]; +then + RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar +else + RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar +fi + +ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app" +X64_APP_BUNDLE="$TEMP_DIRECTORY/output_x64/Ryujinx.app" +UNIVERSAL_APP_BUNDLE="$OUTPUT_DIRECTORY/Ryujinx.app" +EXECUTABLE_SUB_PATH=Contents/MacOS/Ryujinx + +rm -rf "$TEMP_DIRECTORY" +mkdir -p "$TEMP_DIRECTORY" + +DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS) + +dotnet restore +dotnet build -c "$CONFIGURATION" src/Ryujinx +dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx +dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx + +# Get rid of the support library for ARMeilleure for x64 (that's only for arm64) +rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib" + +# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant +# TODO: remove this once done +rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib" + +pushd "$BASE_DIR/distribution/macos" +./create_app_bundle.sh "$TEMP_DIRECTORY/publish_x64" "$TEMP_DIRECTORY/output_x64" "$ENTITLEMENTS_FILE_PATH" +./create_app_bundle.sh "$TEMP_DIRECTORY/publish_arm64" "$TEMP_DIRECTORY/output_arm64" "$ENTITLEMENTS_FILE_PATH" +popd + +rm -rf "$UNIVERSAL_APP_BUNDLE" +mkdir -p "$OUTPUT_DIRECTORY" + +# Let's copy one of the two different app bundle and remove the executable +cp -R "$ARM64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" +rm "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH" + +# Make it libraries universal +python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_BUNDLE" "$X64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" "**/*.dylib" + +if ! [ -x "$(command -v lipo)" ]; +then + if ! [ -x "$(command -v llvm-lipo-14)" ]; + then + LIPO=llvm-lipo + else + LIPO=llvm-lipo-14 + fi +else + LIPO=lipo +fi + +# Make the executable universal +$LIPO "$ARM64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" "$X64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -create + +# Patch up the Info.plist to have appropriate version +sed -r -i.bck "s/\%\%RYUJINX_BUILD_VERSION\%\%/$VERSION/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist" +sed -r -i.bck "s/\%\%RYUJINX_BUILD_GIT_HASH\%\%/$SOURCE_REVISION_ID/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist" +rm "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist.bck" + +# Now sign it +if ! [ -x "$(command -v codesign)" ]; +then + if ! [ -x "$(command -v rcodesign)" ]; + then + echo "Cannot find rcodesign on your system, please install rcodesign." + exit 1 + fi + + # NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes. + # cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign" + echo "Using rcodesign for ad-hoc signing" + rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$UNIVERSAL_APP_BUNDLE" +else + echo "Using codesign for ad-hoc signing" + codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$UNIVERSAL_APP_BUNDLE" +fi + +echo "Creating archive" +pushd "$OUTPUT_DIRECTORY" +tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf "$RELEASE_TAR_FILE_NAME" Ryujinx.app 1> /dev/null +python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx" +gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz" +rm "$RELEASE_TAR_FILE_NAME" + +# Create legacy update package for Avalonia to not left behind old testers. +if [ "$VERSION" != "1.1.0" ]; +then + cp $RELEASE_TAR_FILE_NAME.gz test-ava-ryujinx-$VERSION-macos_universal.app.tar.gz +fi + +popd + +echo "Done" \ No newline at end of file diff --git a/distribution/macos/create_macos_build_headless.sh b/distribution/macos/create_macos_build_headless.sh new file mode 100755 index 00000000..a439aef4 --- /dev/null +++ b/distribution/macos/create_macos_build_headless.sh @@ -0,0 +1,111 @@ +#!/bin/bash + +set -e + +if [ "$#" -lt 7 ]; then + echo "usage " + exit 1 +fi + +mkdir -p "$1" +mkdir -p "$2" +mkdir -p "$3" + +BASE_DIR=$(readlink -f "$1") +TEMP_DIRECTORY=$(readlink -f "$2") +OUTPUT_DIRECTORY=$(readlink -f "$3") +ENTITLEMENTS_FILE_PATH=$(readlink -f "$4") +VERSION=$5 +SOURCE_REVISION_ID=$6 +CONFIGURATION=$7 +EXTRA_ARGS=$8 + +if [ "$VERSION" == "1.1.0" ]; +then + RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar +else + RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$VERSION-macos_universal.tar +fi + +ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64" +X64_OUTPUT="$TEMP_DIRECTORY/publish_x64" +UNIVERSAL_OUTPUT="$OUTPUT_DIRECTORY/publish" +EXECUTABLE_SUB_PATH=Ryujinx.Headless.SDL2 + +rm -rf "$TEMP_DIRECTORY" +mkdir -p "$TEMP_DIRECTORY" + +DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS) + +dotnet restore +dotnet build -c "$CONFIGURATION" src/Ryujinx.Headless.SDL2 +dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2 +dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2 + +# Get rid of the support library for ARMeilleure for x64 (that's only for arm64) +rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib" + +# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant +# TODO: remove this once done +rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib" + +rm -rf "$OUTPUT_DIRECTORY" +mkdir -p "$OUTPUT_DIRECTORY" + +# Let's copy one of the two different outputs and remove the executable +cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT" +rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" + +# Make it libraries universal +python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.dylib" + +if ! [ -x "$(command -v lipo)" ]; +then + if ! [ -x "$(command -v llvm-lipo-14)" ]; + then + LIPO=llvm-lipo + else + LIPO=llvm-lipo-14 + fi +else + LIPO=lipo +fi + +# Make the executable universal +$LIPO "$ARM64_OUTPUT/$EXECUTABLE_SUB_PATH" "$X64_OUTPUT/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" -create + +# Now sign it +if ! [ -x "$(command -v codesign)" ]; +then + if ! [ -x "$(command -v rcodesign)" ]; + then + echo "Cannot find rcodesign on your system, please install rcodesign." + exit 1 + fi + + # NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes. + # cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign" + echo "Using rcodesign for ad-hoc signing" + for FILE in "$UNIVERSAL_OUTPUT"/*; do + if [[ $(file "$FILE") == *"Mach-O"* ]]; then + rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$FILE" + fi + done +else + echo "Using codesign for ad-hoc signing" + for FILE in "$UNIVERSAL_OUTPUT"/*; do + if [[ $(file "$FILE") == *"Mach-O"* ]]; then + codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$FILE" + fi + done +fi + +echo "Creating archive" +pushd "$OUTPUT_DIRECTORY" +tar --exclude "publish/Ryujinx.Headless.SDL2" -cvf "$RELEASE_TAR_FILE_NAME" publish 1> /dev/null +python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "publish/Ryujinx.Headless.SDL2" "publish/Ryujinx.Headless.SDL2" +gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz" +rm "$RELEASE_TAR_FILE_NAME" +popd + +echo "Done" \ No newline at end of file diff --git a/distribution/macos/entitlements.xml b/distribution/macos/entitlements.xml new file mode 100644 index 00000000..bf318507 --- /dev/null +++ b/distribution/macos/entitlements.xml @@ -0,0 +1,23 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + com.apple.security.cs.disable-executable-page-protection + + com.apple.security.cs.debugger + + com.apple.security.get-task-allow + + com.apple.security.hypervisor + + + diff --git a/distribution/macos/shortcut-launch-script.sh b/distribution/macos/shortcut-launch-script.sh new file mode 100644 index 00000000..784d780a --- /dev/null +++ b/distribution/macos/shortcut-launch-script.sh @@ -0,0 +1,8 @@ +#!/bin/sh +launch_arch="$(uname -m)" +if [ "$(sysctl -in sysctl.proc_translated)" = "1" ] +then + launch_arch="arm64" +fi + +arch -$launch_arch {0} {1} diff --git a/distribution/macos/shortcut-template.plist b/distribution/macos/shortcut-template.plist new file mode 100644 index 00000000..27a9e46a --- /dev/null +++ b/distribution/macos/shortcut-template.plist @@ -0,0 +1,35 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + {0} + CFBundleGetInfoString + {1} + CFBundleIconFile + {2} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleVersion + 1.0 + NSHighResolutionCapable + + CSResourcesFileMapped + + NSHumanReadableCopyright + Copyright © 2018 - 2023 Ryujinx Team and Contributors. + LSApplicationCategoryType + public.app-category.games + LSMinimumSystemVersion + 11.0 + UIPrerenderedIcon + + LSEnvironment + + DOTNET_DefaultStackSize + 200000 + + + diff --git a/distribution/macos/updater.sh b/distribution/macos/updater.sh new file mode 100755 index 00000000..12e4c3aa --- /dev/null +++ b/distribution/macos/updater.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +set -e + +INSTALL_DIRECTORY=$1 +NEW_APP_DIRECTORY=$2 +APP_PID=$3 +APP_ARGUMENTS=("${@:4}") + +error_handler() { + local lineno="$1" + + script=""" + set alertTitle to \"Ryujinx - Updater error\" + set alertMessage to \"An error occurred during Ryujinx update (updater.sh:$lineno)\n\nPlease download the update manually from our website if the problem persists.\" + display dialog alertMessage with icon caution with title alertTitle buttons {\"Open Download Page\", \"Exit\"} + set the button_pressed to the button returned of the result + + if the button_pressed is \"Open Download Page\" then + open location \"https://ryujinx.org/download\" + end if + """ + + osascript -e "$script" + exit 1 +} + +trap 'error_handler ${LINENO}' ERR + +# Wait for Ryujinx to exit. +# If the main process is still acitve, we wait for 1 second and check it again. +# After the fifth time checking, this script exits with status 1. + +attempt=0 +while true; do + if lsof -p "$APP_PID" +r 1 &>/dev/null || ps -p "$APP_PID" &>/dev/null; then + if [ "$attempt" -eq 4 ]; then + exit 1 + fi + sleep 1 + else + break + fi + (( attempt++ )) +done + +sleep 1 + +# Now replace and reopen. +rm -rf "$INSTALL_DIRECTORY" +mv "$NEW_APP_DIRECTORY" "$INSTALL_DIRECTORY" + +if [ "$#" -le 3 ]; then + open -a "$INSTALL_DIRECTORY" +else + open -a "$INSTALL_DIRECTORY" --args "${APP_ARGUMENTS[@]}" +fi \ No newline at end of file diff --git a/distribution/misc/Logo.svg b/distribution/misc/Logo.svg new file mode 100644 index 00000000..d6a76312 --- /dev/null +++ b/distribution/misc/Logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/distribution/misc/add_tar_exec.py b/distribution/misc/add_tar_exec.py new file mode 100644 index 00000000..fe659c16 --- /dev/null +++ b/distribution/misc/add_tar_exec.py @@ -0,0 +1,24 @@ +import argparse +from io import BytesIO +import tarfile + +parser = argparse.ArgumentParser( + description="Add the main binary to a tar and force it to be executable" +) +parser.add_argument("input_tar_file", help="input tar file") +parser.add_argument("main_binary_path", help="Main executable path") +parser.add_argument("main_binary_tar_path", help="Main executable tar path") + +args = parser.parse_args() +input_tar_file = args.input_tar_file +main_binary_path = args.main_binary_path +main_binary_tar_path = args.main_binary_tar_path + +with open(main_binary_path, "rb") as f: + with tarfile.open(input_tar_file, "a") as tar: + data = f.read() + tar_info = tarfile.TarInfo(main_binary_tar_path) + tar_info.mode = 0o755 + tar_info.size = len(data) + + tar.addfile(tar_info, BytesIO(data)) diff --git a/distribution/windows/alsoft.ini b/distribution/windows/alsoft.ini new file mode 100644 index 00000000..a142619a --- /dev/null +++ b/distribution/windows/alsoft.ini @@ -0,0 +1,2 @@ +[General] +stereo-mode=speakers diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..a22da3c7 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,35 @@ +# Documents Index + +This repo includes several documents that explain both high-level and low-level concepts about Ryujinx and its functions. These are very useful for contributors, to get context that can be very difficult to acquire from just reading code. + +Intro to Ryujinx +================== + +Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#. +* The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions. +* The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. +* Audio output is entirely supported via C# wrappers for SDL2, with OpenAL & libsoundio as fallbacks. + +Getting Started +=============== + +- [Installing the .NET SDK](https://dotnet.microsoft.com/download) +- [Official .NET Docs](https://docs.microsoft.com/dotnet/core/) + +Contributing (Building, testing, benchmarking, profiling, etc.) +=============== + +If you want to contribute a code change to this repo, start here. + +- [Contributor Guide](../CONTRIBUTING.md) + +Coding Guidelines +================= + +- [C# coding style](coding-guidelines/coding-style.md) +- [Service Implementation Guidelines - WIP](https://gist.github.com/gdkchan/84ba88cd50efbe58d1babfaa7cd7c455) + +Project Docs +================= + +To be added. Many project files will contain basic XML docs for key functions and classes in the meantime. diff --git a/docs/coding-guidelines/coding-style.md b/docs/coding-guidelines/coding-style.md new file mode 100644 index 00000000..9c84055d --- /dev/null +++ b/docs/coding-guidelines/coding-style.md @@ -0,0 +1,116 @@ +# C# Coding Style + +The general rule we follow is "use Visual Studio defaults". +Using an IDE that supports the `.editorconfig` standard will make this much simpler. + +1. We use [Allman style](http://en.wikipedia.org/wiki/Indent_style#Allman_style) braces, where each brace begins on a new line. A single line statement block can go without braces but the block must be properly indented on its own line and must not be nested in other statement blocks that use braces (See rule 18 for more details). One exception is that a `using` statement is permitted to be nested within another `using` statement by starting on the following line at the same indentation level, even if the nested `using` contains a controlled block. +2. We use four spaces of indentation (no tabs). +3. We use `_camelCase` for internal and private fields and use `readonly` where possible. Prefix internal and private instance fields with `_`, static fields with `s_` and thread static fields with `t_`. When used on static fields, `readonly` should come after `static` (e.g. `static readonly` not `readonly static`). Public fields should be used sparingly and should use PascalCasing with no prefix when used. +4. We avoid `this.` unless absolutely necessary. +5. We always specify the visibility, even if it's the default (e.g. + `private string _foo` not `string _foo`). Visibility should be the first modifier (e.g. + `public abstract` not `abstract public`). +6. Namespace imports should be specified at the top of the file, *outside* of `namespace` declarations. +7. Avoid more than one empty line at any time. For example, do not have two + blank lines between members of a type. +8. Avoid spurious free spaces. + For example avoid `if (someVar == 0)...`, where the dots mark the spurious free spaces. + Consider enabling "View White Space (Ctrl+R, Ctrl+W)" or "Edit -> Advanced -> View White Space" if using Visual Studio to aid detection. +9. If a file happens to differ in style from these guidelines (e.g. private members are named `m_member` + rather than `_member`), the existing style in that file takes precedence. +10. We only use `var` when the type is explicitly named on the right-hand side, typically due to either `new` or an explicit cast, e.g. `var stream = new FileStream(...)` not `var stream = OpenStandardInput()`. + - Similarly, target-typed `new()` can only be used when the type is explicitly named on the left-hand side, in a variable definition statement or a field definition statement. e.g. `FileStream stream = new(...);`, but not `stream = new(...);` (where the type was specified on a previous line). +11. We use language keywords instead of BCL types (e.g. `int, string, float` instead of `Int32, String, Single`, etc) for both type references as well as method calls (e.g. `int.Parse` instead of `Int32.Parse`). See issue [#13976](https://github.com/dotnet/runtime/issues/13976) for examples. +12. We use PascalCasing to name all our constant local variables and fields. The only exception is for interop code where the constant value should exactly match the name and value of the code you are calling via interop. +13. We use PascalCasing for all method names, including local functions. +14. We use ```nameof(...)``` instead of ```"..."``` whenever possible and relevant. +15. Fields should be specified at the top within type declarations. +16. When including non-ASCII characters in the source code use Unicode escape sequences (\uXXXX) instead of literal characters. Literal non-ASCII characters occasionally get garbled by a tool or editor. +17. When using labels (for goto), indent the label one less than the current indentation. +18. When using a single-statement if, we follow these conventions: + - Never use single-line form (for example: `if (source == null) throw new ArgumentNullException("source");`) + - Using braces is always accepted, and required if any block of an `if`/`else if`/.../`else` compound statement uses braces or if a single statement body spans multiple lines. + - Braces may be omitted only if the body of *every* block associated with an `if`/`else if`/.../`else` compound statement is placed on a single line. +19. Make all internal and private types static or sealed unless derivation from them is required. As with any implementation detail, they can be changed if/when derivation is required in the future. +20. XML docs should be used when writing interfaces or when a class/method is deemed sufficient in scope or complexity. +21. So-called [Magic Numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)) should be defined as named constants before use (for example `for (int i = 56; i < 68; i++)` could read `for (int i = _currentAge; i < _retireAge; i++)`). + This may be ignored for trivial or syntactically common statements. + +An [EditorConfig](https://editorconfig.org "EditorConfig homepage") file (`.editorconfig`) has been provided at the root of the runtime repository, enabling C# auto-formatting conforming to the above guidelines. + +### Example File: + +``ShaderCache.cs:`` + +```C# +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.Shader.DiskCache; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Memory cache of shader code. + /// + class ShaderCache : IDisposable + { + /// + /// Default flags used on the shader translation process. + /// + public const TranslationFlags DefaultFlags = TranslationFlags.DebugMode; + + private readonly struct TranslatedShader + { + public readonly CachedShaderStage Shader; + public readonly ShaderProgram Program; + + public TranslatedShader(CachedShaderStage shader, ShaderProgram program) + { + Shader = shader; + Program = program; + } + } + ... + + /// + /// Processes the queue of shaders that must save their binaries to the disk cache. + /// + public void ProcessShaderCacheQueue() + { + // Check to see if the binaries for previously compiled shaders are ready, and save them out. + + while (_programsToSaveQueue.TryPeek(out ProgramToSave programToSave)) + { + ProgramLinkStatus result = programToSave.HostProgram.CheckProgramLink(false); + + if (result != ProgramLinkStatus.Incomplete) + { + if (result == ProgramLinkStatus.Success) + { + _cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary()); + } + + _programsToSaveQueue.Dequeue(); + } + else + { + break; + } + } + } + } +} +``` + +For other languages, our current best guidance is consistency. When editing files, keep new code and changes consistent with the style in the files. For new files, it should conform to the style for that component. If there is a completely new component, anything that is reasonably broadly accepted is fine. diff --git a/docs/workflow/pr-guide.md b/docs/workflow/pr-guide.md new file mode 100644 index 00000000..cc2c5900 --- /dev/null +++ b/docs/workflow/pr-guide.md @@ -0,0 +1,56 @@ +# Pull Request Guide + +## Contributing Rules + +All contributions to Ryujinx/Ryujinx repository are made via pull requests (PRs) rather than through direct commits. The pull requests are reviewed and merged by the maintainers after a review and at least two approvals from the core development team. + +To merge pull requests, you must have write permissions in the repository. + +## Quick Code Review Rules + +* Do not mix unrelated changes in one pull request. For example, a code style change should never be mixed with a bug fix. +* All changes should follow the existing code style. You can read more about our code style at [docs/coding-guidelines](../coding-guidelines/coding-style.md). +* Adding external dependencies is to be avoided unless not doing so would introduce _significant_ complexity. Any dependency addition should be justified and discussed before merge. +* Use Draft pull requests for changes you are still working on but want early CI loop feedback. When you think your changes are ready for review, [change the status](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-stage-of-a-pull-request) of your pull request. +* Rebase your changes when required or directly requested. Changes should always be commited on top of the upstream branch, not the other way around. +* If you are asked to make changes during the review process do them as a new commit. +* Only resolve GitHub conversations with reviewers once they have been addressed with a commit, or via a mutual agreement. + +## Pull Request Ownership + +Every pull request will have automatically have labels and reviewers assigned. The label not only indicates the code segment which the change touches but also the area reviewers to be assigned. + +If during the code review process a merge conflict occurs, the PR author is responsible for its resolution. Help will be provided if necessary although GitHub makes this easier by allowing simple conflict resolution using the [conflict-editor](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/resolving-a-merge-conflict-on-github). + +## Pull Request Builds + +When submitting a PR to the `Ryujinx/Ryujinx` repository, various builds will run validating many areas to ensure we keep developer productivity and product quality high. These various workflows can be tracked in the [Actions](https://github.com/Ryujinx/Ryujinx/actions) tab of the repository. If the job continues to completion, the build artifacts will be uploaded and posted as a comment in the PR discussion. + +## Review Turnaround Times + +Ryujinx is a project that is maintained by volunteers on a completely free-time basis. As such we cannot guarantee any particular timeframe for pull request review and approval. Weeks to months are common for larger (>500 line) PRs but there are some additional best practises to avoid review purgatory. + +* Make the reviewers life easier wherever possible. Make use of descriptive commit names, code comments and XML docs where applicable. +* If there is disagreement on feedback then always lean on the side of the development team and community over any personal opinion. +* We're human. We miss things. We forget things. If there has been radio silence on your changes for a substantial period of time then do not hesitate to reach out directly either with something simple like "bump" on GitHub or a directly on Discord. + +To re-iterate, make the review as easy for us as possible, respond promptly and be comfortable to interact directly with us for anything else. + +## Merging Pull Requests + +Anyone with write access can merge a pull request manually when the following conditions have been met: + +* The PR has been approved by two reviewers and any other objections are addressed. + * You can request follow up reviews from the original reviewers if they requested changes. +* The PR successfully builds and passes all tests in the Continuous Integration (CI) system. In case of failures, refer to the [Actions](https://github.com/Ryujinx/Ryujinx/actions) tab of your PR. + +Typically, PRs are merged as one commit (squash merges). It creates a simpler history than a Merge Commit. "Special circumstances" are rare, and typically mean that there are a series of cleanly separated changes that will be too hard to understand if squashed together, or for some reason we want to preserve the ability to dissect them. + +## Blocking Pull Request Merging + +If for whatever reason you would like to move your pull request back to an in-progress status to avoid merging it in the current form, you can turn the PR into a draft PR by selecting the option under the reviewers section. Alternatively, you can do that by adding [WIP] prefix to the pull request title. + +## Old Pull Request Policy + +From time to time we will review older PRs and check them for relevance. If we find the PR is inactive or no longer applies, we will close it. As the PR owner, you can simply reopen it if you feel your closed PR needs our attention. + diff --git a/global.json b/global.json new file mode 100644 index 00000000..391ba3c2 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.100", + "rollForward": "latestFeature" + } +} diff --git a/nuget.config b/nuget.config new file mode 100644 index 00000000..80f5bd7f --- /dev/null +++ b/nuget.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/ARMeilleure/ARMeilleure.csproj b/src/ARMeilleure/ARMeilleure.csproj new file mode 100644 index 00000000..550e50c2 --- /dev/null +++ b/src/ARMeilleure/ARMeilleure.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + true + + + + + + + + + + PreserveNewest + libarmeilleure-jitsupport.dylib + + + + + + <_Parameter1>Ryujinx.Tests + + + + diff --git a/src/ARMeilleure/Allocators.cs b/src/ARMeilleure/Allocators.cs new file mode 100644 index 00000000..fba30265 --- /dev/null +++ b/src/ARMeilleure/Allocators.cs @@ -0,0 +1,39 @@ +using ARMeilleure.Common; +using System; +using System.Runtime.CompilerServices; + +namespace ARMeilleure +{ + static class Allocators + { + [ThreadStatic] private static ArenaAllocator _default; + [ThreadStatic] private static ArenaAllocator _operands; + [ThreadStatic] private static ArenaAllocator _operations; + [ThreadStatic] private static ArenaAllocator _references; + [ThreadStatic] private static ArenaAllocator _liveRanges; + [ThreadStatic] private static ArenaAllocator _liveIntervals; + + public static ArenaAllocator Default => GetAllocator(ref _default, 256 * 1024, 4); + public static ArenaAllocator Operands => GetAllocator(ref _operands, 64 * 1024, 8); + public static ArenaAllocator Operations => GetAllocator(ref _operations, 64 * 1024, 8); + public static ArenaAllocator References => GetAllocator(ref _references, 64 * 1024, 8); + public static ArenaAllocator LiveRanges => GetAllocator(ref _liveRanges, 64 * 1024, 8); + public static ArenaAllocator LiveIntervals => GetAllocator(ref _liveIntervals, 64 * 1024, 8); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ArenaAllocator GetAllocator(ref ArenaAllocator alloc, uint pageSize, uint pageCount) + { + alloc ??= new ArenaAllocator(pageSize, pageCount); + + return alloc; + } + + public static void ResetAll() + { + Default.Reset(); + Operands.Reset(); + Operations.Reset(); + References.Reset(); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/Arm64Optimizer.cs b/src/ARMeilleure/CodeGen/Arm64/Arm64Optimizer.cs new file mode 100644 index 00000000..00ffd195 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/Arm64Optimizer.cs @@ -0,0 +1,270 @@ +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Collections.Generic; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class Arm64Optimizer + { + private const int MaxConstantUses = 10000; + + public static void RunPass(ControlFlowGraph cfg) + { + var constants = new Dictionary(); + + Operand GetConstantCopy(BasicBlock block, Operation operation, Operand source) + { + // If the constant has many uses, we also force a new constant mov to be added, in order + // to avoid overflow of the counts field (that is limited to 16 bits). + if (!constants.TryGetValue(source.Value, out var constant) || constant.UsesCount > MaxConstantUses) + { + constant = Local(source.Type); + + Operation copyOp = Operation(Instruction.Copy, constant, source); + + block.Operations.AddBefore(operation, copyOp); + + constants[source.Value] = constant; + } + + return constant; + } + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + constants.Clear(); + + Operation nextNode; + + for (Operation node = block.Operations.First; node != default; node = nextNode) + { + nextNode = node.ListNext; + + // Insert copies for constants that can't fit on a 32-bit immediate. + // Doing this early unblocks a few optimizations. + if (node.Instruction == Instruction.Add) + { + Operand src1 = node.GetSource(0); + Operand src2 = node.GetSource(1); + + if (src1.Kind == OperandKind.Constant && (src1.Relocatable || ConstTooLong(src1, OperandType.I32))) + { + node.SetSource(0, GetConstantCopy(block, node, src1)); + } + + if (src2.Kind == OperandKind.Constant && (src2.Relocatable || ConstTooLong(src2, OperandType.I32))) + { + node.SetSource(1, GetConstantCopy(block, node, src2)); + } + } + + // Try to fold something like: + // lsl x1, x1, #2 + // add x0, x0, x1 + // ldr x0, [x0] + // add x2, x2, #16 + // ldr x2, [x2] + // Into: + // ldr x0, [x0, x1, lsl #2] + // ldr x2, [x2, #16] + if (IsMemoryLoadOrStore(node.Instruction)) + { + OperandType type; + + if (node.Destination != default) + { + type = node.Destination.Type; + } + else + { + type = node.GetSource(1).Type; + } + + Operand memOp = GetMemoryOperandOrNull(node.GetSource(0), type); + + if (memOp != default) + { + node.SetSource(0, memOp); + } + } + } + } + + Optimizer.RemoveUnusedNodes(cfg); + } + + private static Operand GetMemoryOperandOrNull(Operand addr, OperandType type) + { + Operand baseOp = addr; + + // First we check if the address is the result of a local X with immediate + // addition. If that is the case, then the baseOp is X, and the memory operand immediate + // becomes the addition immediate. Otherwise baseOp keeps being the address. + int imm = GetConstOp(ref baseOp, type); + if (imm != 0) + { + return MemoryOp(type, baseOp, default, Multiplier.x1, imm); + } + + // Now we check if the baseOp is the result of a local Y with a local Z addition. + // If that is the case, we now set baseOp to Y and indexOp to Z. We further check + // if Z is the result of a left shift of local W by a value == 0 or == Log2(AccessSize), + // if that is the case, we set indexOp to W and adjust the scale value of the memory operand + // to match that of the left shift. + // There is one missed case, which is the address being a shift result, but this is + // probably not worth optimizing as it should never happen. + (Operand indexOp, Multiplier scale) = GetIndexOp(ref baseOp, type); + + // If baseOp is still equal to address, then there's nothing that can be optimized. + if (baseOp == addr) + { + return default; + } + + return MemoryOp(type, baseOp, indexOp, scale, 0); + } + + private static int GetConstOp(ref Operand baseOp, OperandType accessType) + { + Operation operation = GetAsgOpWithInst(baseOp, Instruction.Add); + + if (operation == default) + { + return 0; + } + + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + Operand constOp; + Operand otherOp; + + if (src1.Kind == OperandKind.Constant && src2.Kind == OperandKind.LocalVariable) + { + constOp = src1; + otherOp = src2; + } + else if (src1.Kind == OperandKind.LocalVariable && src2.Kind == OperandKind.Constant) + { + constOp = src2; + otherOp = src1; + } + else + { + return 0; + } + + // If we have addition by a constant that we can't encode on the instruction, + // then we can't optimize it further. + if (ConstTooLong(constOp, accessType)) + { + return 0; + } + + baseOp = otherOp; + + return constOp.AsInt32(); + } + + private static (Operand, Multiplier) GetIndexOp(ref Operand baseOp, OperandType accessType) + { + Operand indexOp = default; + + Multiplier scale = Multiplier.x1; + + Operation addOp = GetAsgOpWithInst(baseOp, Instruction.Add); + + if (addOp == default) + { + return (indexOp, scale); + } + + Operand src1 = addOp.GetSource(0); + Operand src2 = addOp.GetSource(1); + + if (src1.Kind != OperandKind.LocalVariable || src2.Kind != OperandKind.LocalVariable) + { + return (indexOp, scale); + } + + baseOp = src1; + indexOp = src2; + + Operation shlOp = GetAsgOpWithInst(src1, Instruction.ShiftLeft); + + bool indexOnSrc2 = false; + + if (shlOp == default) + { + shlOp = GetAsgOpWithInst(src2, Instruction.ShiftLeft); + + indexOnSrc2 = true; + } + + if (shlOp != default) + { + Operand shSrc = shlOp.GetSource(0); + Operand shift = shlOp.GetSource(1); + + int maxShift = Assembler.GetScaleForType(accessType); + + if (shSrc.Kind == OperandKind.LocalVariable && + shift.Kind == OperandKind.Constant && + (shift.Value == 0 || shift.Value == (ulong)maxShift)) + { + scale = shift.Value switch + { + 1 => Multiplier.x2, + 2 => Multiplier.x4, + 3 => Multiplier.x8, + 4 => Multiplier.x16, + _ => Multiplier.x1, + }; + + baseOp = indexOnSrc2 ? src1 : src2; + indexOp = shSrc; + } + } + + return (indexOp, scale); + } + + private static Operation GetAsgOpWithInst(Operand op, Instruction inst) + { + // If we have multiple assignments, folding is not safe + // as the value may be different depending on the + // control flow path. + if (op.AssignmentsCount != 1) + { + return default; + } + + Operation asgOp = op.Assignments[0]; + + if (asgOp.Instruction != inst) + { + return default; + } + + return asgOp; + } + + private static bool IsMemoryLoadOrStore(Instruction inst) + { + return inst == Instruction.Load || inst == Instruction.Store; + } + + private static bool ConstTooLong(Operand constOp, OperandType accessType) + { + if ((uint)constOp.Value != constOp.Value) + { + return true; + } + + return !CodeGenCommon.ConstFitsOnUImm12(constOp.AsInt32(), accessType); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs b/src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs new file mode 100644 index 00000000..5db89859 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs @@ -0,0 +1,49 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +namespace ARMeilleure.CodeGen.Arm64 +{ + enum ArmCondition + { + Eq = 0, + Ne = 1, + GeUn = 2, + LtUn = 3, + Mi = 4, + Pl = 5, + Vs = 6, + Vc = 7, + GtUn = 8, + LeUn = 9, + Ge = 10, + Lt = 11, + Gt = 12, + Le = 13, + Al = 14, + Nv = 15, + } + + static class ComparisonArm64Extensions + { + public static ArmCondition ToArmCondition(this Comparison comp) + { + return comp switch + { +#pragma warning disable IDE0055 // Disable formatting + Comparison.Equal => ArmCondition.Eq, + Comparison.NotEqual => ArmCondition.Ne, + Comparison.Greater => ArmCondition.Gt, + Comparison.LessOrEqual => ArmCondition.Le, + Comparison.GreaterUI => ArmCondition.GtUn, + Comparison.LessOrEqualUI => ArmCondition.LeUn, + Comparison.GreaterOrEqual => ArmCondition.Ge, + Comparison.Less => ArmCondition.Lt, + Comparison.GreaterOrEqualUI => ArmCondition.GeUn, + Comparison.LessUI => ArmCondition.LtUn, +#pragma warning restore IDE0055 + + _ => throw new ArgumentException(null, nameof(comp)), + }; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/ArmExtensionType.cs b/src/ARMeilleure/CodeGen/Arm64/ArmExtensionType.cs new file mode 100644 index 00000000..20ccfd4b --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/ArmExtensionType.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.CodeGen.Arm64 +{ + enum ArmExtensionType + { + Uxtb = 0, + Uxth = 1, + Uxtw = 2, + Uxtx = 3, + Sxtb = 4, + Sxth = 5, + Sxtw = 6, + Sxtx = 7, + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/ArmShiftType.cs b/src/ARMeilleure/CodeGen/Arm64/ArmShiftType.cs new file mode 100644 index 00000000..f32407c4 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/ArmShiftType.cs @@ -0,0 +1,11 @@ + +namespace ARMeilleure.CodeGen.Arm64 +{ + enum ArmShiftType + { + Lsl = 0, + Lsr = 1, + Asr = 2, + Ror = 3, + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/Assembler.cs b/src/ARMeilleure/CodeGen/Arm64/Assembler.cs new file mode 100644 index 00000000..41684faf --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/Assembler.cs @@ -0,0 +1,1162 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Diagnostics; +using System.IO; +using static ARMeilleure.IntermediateRepresentation.Operand; + +namespace ARMeilleure.CodeGen.Arm64 +{ + class Assembler + { + public const uint SfFlag = 1u << 31; + + private const int SpRegister = 31; + private const int ZrRegister = 31; + + private readonly Stream _stream; + + public Assembler(Stream stream) + { + _stream = stream; + } + + public void Add(Operand rd, Operand rn, Operand rm, ArmExtensionType extensionType, int shiftAmount = 0) + { + WriteInstructionAuto(0x0b200000u, rd, rn, rm, extensionType, shiftAmount); + } + + public void Add(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0, bool immForm = false) + { + WriteInstructionAuto(0x11000000u, 0x0b000000u, rd, rn, rm, shiftType, shiftAmount, immForm); + } + + public void And(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x12000000u, 0x0a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Ands(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x72000000u, 0x6a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Asr(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Sbfm(rd, rn, shift, mask); + } + else + { + Asrv(rd, rn, rm); + } + } + + public void Asrv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02800u, rd, rn, rm); + } + + public void B(int imm) + { + WriteUInt32(0x14000000u | EncodeSImm26_2(imm)); + } + + public void B(ArmCondition condition, int imm) + { + WriteUInt32(0x54000000u | (uint)condition | (EncodeSImm19_2(imm) << 5)); + } + + public void Blr(Operand rn) + { + WriteUInt32(0xd63f0000u | (EncodeReg(rn) << 5)); + } + + public void Br(Operand rn) + { + WriteUInt32(0xd61f0000u | (EncodeReg(rn) << 5)); + } + + public void Brk() + { + WriteUInt32(0xd4200000u); + } + + public void Cbz(Operand rt, int imm) + { + WriteInstructionAuto(0x34000000u | (EncodeSImm19_2(imm) << 5), rt); + } + + public void Cbnz(Operand rt, int imm) + { + WriteInstructionAuto(0x35000000u | (EncodeSImm19_2(imm) << 5), rt); + } + + public void Clrex(int crm = 15) + { + WriteUInt32(0xd503305fu | (EncodeUImm4(crm) << 8)); + } + + public void Clz(Operand rd, Operand rn) + { + WriteInstructionAuto(0x5ac01000u, rd, rn); + } + + public void CmeqVector(Operand rd, Operand rn, Operand rm, int size, bool q = true) + { + Debug.Assert((uint)size < 4); + WriteSimdInstruction(0x2e208c00u | ((uint)size << 22), rd, rn, rm, q); + } + + public void Cmp(Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Subs(Factory.Register(ZrRegister, RegisterType.Integer, rn.Type), rn, rm, shiftType, shiftAmount); + } + + public void Csel(Operand rd, Operand rn, Operand rm, ArmCondition condition) + { + WriteInstructionBitwiseAuto(0x1a800000u | ((uint)condition << 12), rd, rn, rm); + } + + public void Cset(Operand rd, ArmCondition condition) + { + var zr = Factory.Register(ZrRegister, RegisterType.Integer, rd.Type); + Csinc(rd, zr, zr, (ArmCondition)((int)condition ^ 1)); + } + + public void Csinc(Operand rd, Operand rn, Operand rm, ArmCondition condition) + { + WriteInstructionBitwiseAuto(0x1a800400u | ((uint)condition << 12), rd, rn, rm); + } + + public void Dmb(uint option) + { + WriteUInt32(0xd50330bfu | (option << 8)); + } + + public void DupScalar(Operand rd, Operand rn, int index, int size) + { + WriteInstruction(0x5e000400u | (EncodeIndexSizeImm5(index, size) << 16), rd, rn); + } + + public void Eor(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x52000000u, 0x4a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void EorVector(Operand rd, Operand rn, Operand rm, bool q = true) + { + WriteSimdInstruction(0x2e201c00u, rd, rn, rm, q); + } + + public void Extr(Operand rd, Operand rn, Operand rm, int imms) + { + uint n = rd.Type == OperandType.I64 ? 1u << 22 : 0u; + WriteInstructionBitwiseAuto(0x13800000u | n | (EncodeUImm6(imms) << 10), rd, rn, rm); + } + + public void FaddScalar(Operand rd, Operand rn, Operand rm) + { + WriteFPInstructionAuto(0x1e202800u, rd, rn, rm); + } + + public void FcvtScalar(Operand rd, Operand rn) + { + uint instruction = 0x1e224000u | (rd.Type == OperandType.FP64 ? 1u << 15 : 1u << 22); + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5)); + } + + public void FdivScalar(Operand rd, Operand rn, Operand rm) + { + WriteFPInstructionAuto(0x1e201800u, rd, rn, rm); + } + + public void Fmov(Operand rd, Operand rn) + { + WriteFPInstructionAuto(0x1e204000u, rd, rn); + } + + public void Fmov(Operand rd, Operand rn, bool topHalf) + { + Debug.Assert(rd.Type.IsInteger() != rn.Type.IsInteger()); + Debug.Assert(rd.Type == OperandType.I64 || rn.Type == OperandType.I64 || !topHalf); + + uint opcode = rd.Type.IsInteger() ? 0b110u : 0b111u; + + uint rmode = topHalf ? 1u << 19 : 0u; + uint ftype = rd.Type == OperandType.FP64 || rn.Type == OperandType.FP64 ? 1u << 22 : 0u; + uint sf = rd.Type == OperandType.I64 || rn.Type == OperandType.I64 ? SfFlag : 0u; + + WriteUInt32(0x1e260000u | (opcode << 16) | rmode | ftype | sf | EncodeReg(rd) | (EncodeReg(rn) << 5)); + } + + public void FmulScalar(Operand rd, Operand rn, Operand rm) + { + WriteFPInstructionAuto(0x1e200800u, rd, rn, rm); + } + + public void FnegScalar(Operand rd, Operand rn) + { + WriteFPInstructionAuto(0x1e214000u, rd, rn); + } + + public void FsubScalar(Operand rd, Operand rn, Operand rm) + { + WriteFPInstructionAuto(0x1e203800u, rd, rn, rm); + } + + public void Ins(Operand rd, Operand rn, int index, int size) + { + WriteInstruction(0x4e001c00u | (EncodeIndexSizeImm5(index, size) << 16), rd, rn); + } + + public void Ins(Operand rd, Operand rn, int srcIndex, int dstIndex, int size) + { + uint imm4 = (uint)srcIndex << size; + Debug.Assert((uint)srcIndex < (16u >> size)); + WriteInstruction(0x6e000400u | (imm4 << 11) | (EncodeIndexSizeImm5(dstIndex, size) << 16), rd, rn); + } + + public void Ldaxp(Operand rt, Operand rt2, Operand rn) + { + WriteInstruction(0x887f8000u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn, rt2); + } + + public void Ldaxr(Operand rt, Operand rn) + { + WriteInstruction(0x085ffc00u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn); + } + + public void Ldaxrb(Operand rt, Operand rn) + { + WriteInstruction(0x085ffc00u, rt, rn); + } + + public void Ldaxrh(Operand rt, Operand rn) + { + WriteInstruction(0x085ffc00u | (1u << 30), rt, rn); + } + + public void LdpRiPost(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x28c00000u, 0x2cc00000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void LdpRiPre(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29c00000u, 0x2dc00000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void LdpRiUn(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29400000u, 0x2d400000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void Ldr(Operand rt, Operand rn) + { + if (rn.Kind == OperandKind.Memory) + { + MemoryOperand memOp = rn.GetMemory(); + + if (memOp.Index != default) + { + Debug.Assert(memOp.Displacement == 0); + Debug.Assert(memOp.Scale == Multiplier.x1 || (int)memOp.Scale == GetScaleForType(rt.Type)); + LdrRr(rt, memOp.BaseAddress, memOp.Index, ArmExtensionType.Uxtx, memOp.Scale != Multiplier.x1); + } + else + { + LdrRiUn(rt, memOp.BaseAddress, memOp.Displacement); + } + } + else + { + LdrRiUn(rt, rn, 0); + } + } + + public void LdrLit(Operand rt, int offset) + { + uint instruction = 0x18000000u | (EncodeSImm19_2(offset) << 5); + + if (rt.Type == OperandType.I64) + { + instruction |= 1u << 30; + } + + WriteInstruction(instruction, rt); + } + + public void LdrRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8400400u, 0x3c400400u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8400c00u, 0x3c400c00u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb9400000u, 0x3d400000u, rt.Type) | (EncodeUImm12(imm, rt.Type) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void LdrRr(Operand rt, Operand rn, Operand rm, ArmExtensionType extensionType, bool shift) + { + uint instruction = GetLdrStrInstruction(0xb8600800u, 0x3ce00800u, rt.Type); + WriteInstructionLdrStrAuto(instruction, rt, rn, rm, extensionType, shift); + } + + public void LdrbRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38400400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrbRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38400c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrbRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x39400000u | (EncodeUImm12(imm, 0) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void LdrhRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78400400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrhRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78400c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrhRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x79400000u | (EncodeUImm12(imm, 1) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void Ldur(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8400000u, 0x3c400000u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void Lsl(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Ubfm(rd, rn, -shift & mask, mask - shift); + } + else + { + Lslv(rd, rn, rm); + } + } + + public void Lslv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02000u, rd, rn, rm); + } + + public void Lsr(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Ubfm(rd, rn, shift, mask); + } + else + { + Lsrv(rd, rn, rm); + } + } + + public void Lsrv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02400u, rd, rn, rm); + } + + public void Madd(Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteInstructionAuto(0x1b000000u, rd, rn, rm, ra); + } + + public void Mul(Operand rd, Operand rn, Operand rm) + { + Madd(rd, rn, rm, Factory.Register(ZrRegister, RegisterType.Integer, rd.Type)); + } + + public void Mov(Operand rd, Operand rn) + { + if (rd.Type.IsInteger()) + { + Orr(rd, Factory.Register(ZrRegister, RegisterType.Integer, rd.Type), rn); + } + else + { + OrrVector(rd, rn, rn); + } + } + + public void MovSp(Operand rd, Operand rn) + { + if (rd.GetRegister().Index == SpRegister || + rn.GetRegister().Index == SpRegister) + { + Add(rd, rn, Factory.Const(rd.Type, 0), immForm: true); + } + else + { + Mov(rd, rn); + } + } + + public void Mov(Operand rd, int imm) + { + Movz(rd, imm, 0); + } + + public void Movz(Operand rd, int imm, int hw) + { + Debug.Assert((hw & (rd.Type == OperandType.I64 ? 3 : 1)) == hw); + WriteInstructionAuto(0x52800000u | (EncodeUImm16(imm) << 5) | ((uint)hw << 21), rd); + } + + public void Movk(Operand rd, int imm, int hw) + { + Debug.Assert((hw & (rd.Type == OperandType.I64 ? 3 : 1)) == hw); + WriteInstructionAuto(0x72800000u | (EncodeUImm16(imm) << 5) | ((uint)hw << 21), rd); + } + + public void Mrs(Operand rt, uint o0, uint op1, uint crn, uint crm, uint op2) + { + uint instruction = 0xd5300000u; + + instruction |= (op2 & 7) << 5; + instruction |= (crm & 15) << 8; + instruction |= (crn & 15) << 12; + instruction |= (op1 & 7) << 16; + instruction |= (o0 & 1) << 19; + + WriteInstruction(instruction, rt); + } + + public void Mvn(Operand rd, Operand rn, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Orn(rd, Factory.Register(ZrRegister, RegisterType.Integer, rd.Type), rn, shiftType, shiftAmount); + } + + public void Neg(Operand rd, Operand rn, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Sub(rd, Factory.Register(ZrRegister, RegisterType.Integer, rd.Type), rn, shiftType, shiftAmount); + } + + public void Orn(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x2a200000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Orr(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x32000000u, 0x2a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void OrrVector(Operand rd, Operand rn, Operand rm, bool q = true) + { + WriteSimdInstruction(0x0ea01c00u, rd, rn, rm, q); + } + + public void Ret(Operand rn) + { + WriteUInt32(0xd65f0000u | (EncodeReg(rn) << 5)); + } + + public void Rev(Operand rd, Operand rn) + { + uint opc0 = rd.Type == OperandType.I64 ? 1u << 10 : 0u; + WriteInstructionAuto(0x5ac00800u | opc0, rd, rn); + } + + public void Ror(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Extr(rd, rn, rn, shift); + } + else + { + Rorv(rd, rn, rm); + } + } + + public void Rorv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02c00u, rd, rn, rm); + } + + public void Sbfm(Operand rd, Operand rn, int immr, int imms) + { + uint n = rd.Type == OperandType.I64 ? 1u << 22 : 0u; + WriteInstructionAuto(0x13000000u | n | (EncodeUImm6(imms) << 10) | (EncodeUImm6(immr) << 16), rd, rn); + } + + public void ScvtfScalar(Operand rd, Operand rn) + { + uint instruction = 0x1e220000u; + + if (rn.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteFPInstructionAuto(instruction, rd, rn); + } + + public void Sdiv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16Auto(0x1ac00c00u, rd, rn, rm); + } + + public void Smulh(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x9b407c00u, rd, rn, rm); + } + + public void Stlxp(Operand rt, Operand rt2, Operand rn, Operand rs) + { + WriteInstruction(0x88208000u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn, rs, rt2); + } + + public void Stlxr(Operand rt, Operand rn, Operand rs) + { + WriteInstructionRm16(0x0800fc00u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn, rs); + } + + public void Stlxrb(Operand rt, Operand rn, Operand rs) + { + WriteInstructionRm16(0x0800fc00u, rt, rn, rs); + } + + public void Stlxrh(Operand rt, Operand rn, Operand rs) + { + WriteInstructionRm16(0x0800fc00u | (1u << 30), rt, rn, rs); + } + + public void StpRiPost(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x28800000u, 0x2c800000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void StpRiPre(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29800000u, 0x2d800000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void StpRiUn(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29000000u, 0x2d000000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void Str(Operand rt, Operand rn) + { + if (rn.Kind == OperandKind.Memory) + { + MemoryOperand memOp = rn.GetMemory(); + + if (memOp.Index != default) + { + Debug.Assert(memOp.Displacement == 0); + Debug.Assert(memOp.Scale == Multiplier.x1 || (int)memOp.Scale == GetScaleForType(rt.Type)); + StrRr(rt, memOp.BaseAddress, memOp.Index, ArmExtensionType.Uxtx, memOp.Scale != Multiplier.x1); + } + else + { + StrRiUn(rt, memOp.BaseAddress, memOp.Displacement); + } + } + else + { + StrRiUn(rt, rn, 0); + } + } + + public void StrRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8000400u, 0x3c000400u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8000c00u, 0x3c000c00u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb9000000u, 0x3d000000u, rt.Type) | (EncodeUImm12(imm, rt.Type) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void StrRr(Operand rt, Operand rn, Operand rm, ArmExtensionType extensionType, bool shift) + { + uint instruction = GetLdrStrInstruction(0xb8200800u, 0x3ca00800u, rt.Type); + WriteInstructionLdrStrAuto(instruction, rt, rn, rm, extensionType, shift); + } + + public void StrbRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38000400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrbRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38000c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrbRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x39000000u | (EncodeUImm12(imm, 0) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void StrhRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78000400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrhRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78000c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrhRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x79000000u | (EncodeUImm12(imm, 1) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void Stur(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8000000u, 0x3c000000u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void Sub(Operand rd, Operand rn, Operand rm, ArmExtensionType extensionType, int shiftAmount = 0) + { + WriteInstructionAuto(0x4b200000u, rd, rn, rm, extensionType, shiftAmount); + } + + public void Sub(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionAuto(0x51000000u, 0x4b000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Subs(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionAuto(0x71000000u, 0x6b000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Sxtb(Operand rd, Operand rn) + { + Sbfm(rd, rn, 0, 7); + } + + public void Sxth(Operand rd, Operand rn) + { + Sbfm(rd, rn, 0, 15); + } + + public void Sxtw(Operand rd, Operand rn) + { + Sbfm(rd, rn, 0, 31); + } + + public void Tst(Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Ands(Factory.Register(ZrRegister, RegisterType.Integer, rn.Type), rn, rm, shiftType, shiftAmount); + } + + public void Ubfm(Operand rd, Operand rn, int immr, int imms) + { + uint n = rd.Type == OperandType.I64 ? 1u << 22 : 0u; + WriteInstructionAuto(0x53000000u | n | (EncodeUImm6(imms) << 10) | (EncodeUImm6(immr) << 16), rd, rn); + } + + public void UcvtfScalar(Operand rd, Operand rn) + { + uint instruction = 0x1e230000u; + + if (rn.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteFPInstructionAuto(instruction, rd, rn); + } + + public void Udiv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16Auto(0x1ac00800u, rd, rn, rm); + } + + public void Umov(Operand rd, Operand rn, int index, int size) + { + uint q = size == 3 ? 1u << 30 : 0u; + WriteInstruction(0x0e003c00u | (EncodeIndexSizeImm5(index, size) << 16) | q, rd, rn); + } + + public void Umulh(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x9bc07c00u, rd, rn, rm); + } + + public void Uxtb(Operand rd, Operand rn) + { + Ubfm(rd, rn, 0, 7); + } + + public void Uxth(Operand rd, Operand rn) + { + Ubfm(rd, rn, 0, 15); + } + + private void WriteInstructionAuto( + uint instI, + uint instR, + Operand rd, + Operand rn, + Operand rm, + ArmShiftType shiftType = ArmShiftType.Lsl, + int shiftAmount = 0, + bool immForm = false) + { + if (rm.Kind == OperandKind.Constant && (rm.Value != 0 || immForm)) + { + Debug.Assert(shiftAmount == 0); + int imm = rm.AsInt32(); + Debug.Assert((uint)imm == rm.Value); + if (imm != 0 && (imm & 0xfff) == 0) + { + instI |= 1 << 22; // sh flag + imm >>= 12; + } + WriteInstructionAuto(instI | (EncodeUImm12(imm, 0) << 10), rd, rn); + } + else + { + instR |= EncodeUImm6(shiftAmount) << 10; + instR |= (uint)shiftType << 22; + + WriteInstructionRm16Auto(instR, rd, rn, rm); + } + } + + private void WriteInstructionAuto( + uint instruction, + Operand rd, + Operand rn, + Operand rm, + ArmExtensionType extensionType, + int shiftAmount = 0) + { + Debug.Assert((uint)shiftAmount <= 4); + + instruction |= (uint)shiftAmount << 10; + instruction |= (uint)extensionType << 13; + + WriteInstructionRm16Auto(instruction, rd, rn, rm); + } + + private void WriteInstructionBitwiseAuto( + uint instI, + uint instR, + Operand rd, + Operand rn, + Operand rm, + ArmShiftType shiftType = ArmShiftType.Lsl, + int shiftAmount = 0) + { + if (rm.Kind == OperandKind.Constant && rm.Value != 0) + { + Debug.Assert(shiftAmount == 0); + bool canEncode = CodeGenCommon.TryEncodeBitMask(rm, out int immN, out int immS, out int immR); + Debug.Assert(canEncode); + uint instruction = instI | ((uint)immS << 10) | ((uint)immR << 16) | ((uint)immN << 22); + + WriteInstructionAuto(instruction, rd, rn); + } + else + { + WriteInstructionBitwiseAuto(instR, rd, rn, rm, shiftType, shiftAmount); + } + } + + private void WriteInstructionBitwiseAuto( + uint instruction, + Operand rd, + Operand rn, + Operand rm, + ArmShiftType shiftType = ArmShiftType.Lsl, + int shiftAmount = 0) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + instruction |= EncodeUImm6(shiftAmount) << 10; + instruction |= (uint)shiftType << 22; + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + private void WriteInstructionLdrStrAuto( + uint instruction, + Operand rd, + Operand rn, + Operand rm, + ArmExtensionType extensionType, + bool shift) + { + if (shift) + { + instruction |= 1u << 12; + } + + instruction |= (uint)extensionType << 13; + + if (rd.Type == OperandType.I64) + { + instruction |= 1u << 30; + } + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + private void WriteInstructionAuto(uint instruction, Operand rd) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstruction(instruction, rd); + } + + public void WriteInstructionAuto(uint instruction, Operand rd, Operand rn) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstruction(instruction, rd, rn); + } + + private void WriteInstructionAuto(uint instruction, Operand rd, Operand rn, Operand rm, Operand ra) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstruction(instruction, rd, rn, rm, ra); + } + + public void WriteInstruction(uint instruction, Operand rd) + { + WriteUInt32(instruction | EncodeReg(rd)); + } + + public void WriteInstruction(uint instruction, Operand rd, Operand rn) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5)); + } + + public void WriteInstruction(uint instruction, Operand rd, Operand rn, Operand rm) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5) | (EncodeReg(rm) << 10)); + } + + public void WriteInstruction(uint instruction, Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5) | (EncodeReg(ra) << 10) | (EncodeReg(rm) << 16)); + } + + private void WriteFPInstructionAuto(uint instruction, Operand rd, Operand rn) + { + if (rd.Type == OperandType.FP64) + { + instruction |= 1u << 22; + } + + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5)); + } + + private void WriteFPInstructionAuto(uint instruction, Operand rd, Operand rn, Operand rm) + { + if (rd.Type == OperandType.FP64) + { + instruction |= 1u << 22; + } + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + private void WriteSimdInstruction(uint instruction, Operand rd, Operand rn, Operand rm, bool q = true) + { + if (q) + { + instruction |= 1u << 30; + } + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + private void WriteInstructionRm16Auto(uint instruction, Operand rd, Operand rn, Operand rm) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + public void WriteInstructionRm16(uint instruction, Operand rd, Operand rn, Operand rm) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5) | (EncodeReg(rm) << 16)); + } + + public void WriteInstructionRm16NoRet(uint instruction, Operand rn, Operand rm) + { + WriteUInt32(instruction | (EncodeReg(rn) << 5) | (EncodeReg(rm) << 16)); + } + + private static uint GetLdpStpInstruction(uint intInst, uint vecInst, int imm, OperandType type) + { + uint instruction; + int scale; + + if (type.IsInteger()) + { + instruction = intInst; + + if (type == OperandType.I64) + { + instruction |= SfFlag; + scale = 3; + } + else + { + scale = 2; + } + } + else + { + int opc = type switch + { + OperandType.FP32 => 0, + OperandType.FP64 => 1, + _ => 2, + }; + + instruction = vecInst | ((uint)opc << 30); + scale = 2 + opc; + } + + instruction |= (EncodeSImm7(imm, scale) << 15); + + return instruction; + } + + private static uint GetLdrStrInstruction(uint intInst, uint vecInst, OperandType type) + { + uint instruction; + + if (type.IsInteger()) + { + instruction = intInst; + + if (type == OperandType.I64) + { + instruction |= 1 << 30; + } + } + else + { + instruction = vecInst; + + if (type == OperandType.V128) + { + instruction |= 1u << 23; + } + else + { + instruction |= type == OperandType.FP32 ? 2u << 30 : 3u << 30; + } + } + + return instruction; + } + + private static uint EncodeIndexSizeImm5(int index, int size) + { + Debug.Assert((uint)size < 4); + Debug.Assert((uint)index < (16u >> size), $"Invalid index {index} and size {size} combination."); + return ((uint)index << (size + 1)) | (1u << size); + } + + private static uint EncodeSImm7(int value, int scale) + { + uint imm = (uint)(value >> scale) & 0x7f; + Debug.Assert(((int)imm << 25) >> (25 - scale) == value, $"Failed to encode constant 0x{value:X} with scale {scale}."); + return imm; + } + + private static uint EncodeSImm9(int value) + { + uint imm = (uint)value & 0x1ff; + Debug.Assert(((int)imm << 23) >> 23 == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeSImm19_2(int value) + { + uint imm = (uint)(value >> 2) & 0x7ffff; + Debug.Assert(((int)imm << 13) >> 11 == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeSImm26_2(int value) + { + uint imm = (uint)(value >> 2) & 0x3ffffff; + Debug.Assert(((int)imm << 6) >> 4 == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeUImm4(int value) + { + uint imm = (uint)value & 0xf; + Debug.Assert((int)imm == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeUImm6(int value) + { + uint imm = (uint)value & 0x3f; + Debug.Assert((int)imm == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeUImm12(int value, OperandType type) + { + return EncodeUImm12(value, GetScaleForType(type)); + } + + private static uint EncodeUImm12(int value, int scale) + { + uint imm = (uint)(value >> scale) & 0xfff; + Debug.Assert((int)imm << scale == value, $"Failed to encode constant 0x{value:X} with scale {scale}."); + return imm; + } + + private static uint EncodeUImm16(int value) + { + uint imm = (uint)value & 0xffff; + Debug.Assert((int)imm == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeReg(Operand reg) + { + if (reg.Kind == OperandKind.Constant && reg.Value == 0) + { + return ZrRegister; + } + + uint regIndex = (uint)reg.GetRegister().Index; + Debug.Assert(reg.Kind == OperandKind.Register); + Debug.Assert(regIndex < 32); + return regIndex; + } + + public static int GetScaleForType(OperandType type) + { + return type switch + { + OperandType.I32 => 2, + OperandType.I64 => 3, + OperandType.FP32 => 2, + OperandType.FP64 => 3, + OperandType.V128 => 4, + _ => throw new ArgumentException($"Invalid type {type}."), + }; + } + +#pragma warning disable IDE0051 // Remove unused private member + private void WriteInt16(short value) + { + WriteUInt16((ushort)value); + } + + private void WriteInt32(int value) + { + WriteUInt32((uint)value); + } + + private void WriteByte(byte value) + { + _stream.WriteByte(value); + } +#pragma warning restore IDE0051 + + private void WriteUInt16(ushort value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + } + + private void WriteUInt32(uint value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)(value >> 16)); + _stream.WriteByte((byte)(value >> 24)); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/CallingConvention.cs b/src/ARMeilleure/CodeGen/Arm64/CallingConvention.cs new file mode 100644 index 00000000..a487c2ed --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CallingConvention.cs @@ -0,0 +1,96 @@ +using System; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class CallingConvention + { + private const int RegistersMask = unchecked((int)0xffffffff); + + // Some of those register have specific roles and can't be used as general purpose registers. + // X18 - Reserved for platform specific usage. + // X29 - Frame pointer. + // X30 - Return address. + // X31 - Not an actual register, in some cases maps to SP, and in others to ZR. + private const int ReservedRegsMask = (1 << CodeGenCommon.ReservedRegister) | (1 << 18) | (1 << 29) | (1 << 30) | (1 << 31); + + public static int GetIntAvailableRegisters() + { + return RegistersMask & ~ReservedRegsMask; + } + + public static int GetVecAvailableRegisters() + { + return RegistersMask; + } + + public static int GetIntCallerSavedRegisters() + { + return (GetIntCalleeSavedRegisters() ^ RegistersMask) & ~ReservedRegsMask; + } + + public static int GetFpCallerSavedRegisters() + { + return GetFpCalleeSavedRegisters() ^ RegistersMask; + } + + public static int GetVecCallerSavedRegisters() + { + return GetVecCalleeSavedRegisters() ^ RegistersMask; + } + + public static int GetIntCalleeSavedRegisters() + { + return 0x1ff80000; // X19 to X28 + } + + public static int GetFpCalleeSavedRegisters() + { + return 0xff00; // D8 to D15 + } + + public static int GetVecCalleeSavedRegisters() + { + return 0; + } + + public static int GetArgumentsOnRegsCount() + { + return 8; + } + + public static int GetIntArgumentRegister(int index) + { + if ((uint)index < (uint)GetArgumentsOnRegsCount()) + { + return index; + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public static int GetVecArgumentRegister(int index) + { + if ((uint)index < (uint)GetArgumentsOnRegsCount()) + { + return index; + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public static int GetIntReturnRegister() + { + return 0; + } + + public static int GetIntReturnRegisterHigh() + { + return 1; + } + + public static int GetVecReturnRegister() + { + return 0; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGenCommon.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGenCommon.cs new file mode 100644 index 00000000..1f0148d5 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CodeGenCommon.cs @@ -0,0 +1,91 @@ +using ARMeilleure.IntermediateRepresentation; +using System.Numerics; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class CodeGenCommon + { + public const int TcAddressRegister = 8; + public const int ReservedRegister = 17; + + public static bool ConstFitsOnSImm7(int value, int scale) + { + return (((value >> scale) << 25) >> (25 - scale)) == value; + } + + public static bool ConstFitsOnSImm9(int value) + { + return ((value << 23) >> 23) == value; + } + + public static bool ConstFitsOnUImm12(int value) + { + return (value & 0xfff) == value; + } + + public static bool ConstFitsOnUImm12(int value, OperandType type) + { + int scale = Assembler.GetScaleForType(type); + return (((value >> scale) & 0xfff) << scale) == value; + } + + public static bool TryEncodeBitMask(Operand operand, out int immN, out int immS, out int immR) + { + return TryEncodeBitMask(operand.Type, operand.Value, out immN, out immS, out immR); + } + + public static bool TryEncodeBitMask(OperandType type, ulong value, out int immN, out int immS, out int immR) + { + if (type == OperandType.I32) + { + value |= value << 32; + } + + return TryEncodeBitMask(value, out immN, out immS, out immR); + } + + public static bool TryEncodeBitMask(ulong value, out int immN, out int immS, out int immR) + { + // Some special values also can't be encoded: + // 0 can't be encoded because we need to subtract 1 from onesCount (which would became negative if 0). + // A value with all bits set can't be encoded because it is reserved according to the spec, because: + // Any value AND all ones will be equal itself, so it's effectively a no-op. + // Any value OR all ones will be equal all ones, so one can just use MOV. + // Any value XOR all ones will be equal its inverse, so one can just use MVN. + if (value == 0 || value == ulong.MaxValue) + { + immN = 0; + immS = 0; + immR = 0; + + return false; + } + + // Normalize value, rotating it such that the LSB is 1: Ensures we get a complete element that has not + // been cut-in-half across the word boundary. + int rotation = BitOperations.TrailingZeroCount(value & (value + 1)); + ulong rotatedValue = ulong.RotateRight(value, rotation); + + // Now that we have a complete element in the LSB with the LSB = 1, determine size and number of ones + // in element. + int elementSize = BitOperations.TrailingZeroCount(rotatedValue & (rotatedValue + 1)); + int onesInElement = BitOperations.TrailingZeroCount(~rotatedValue); + + // Check the value is repeating; also ensures element size is a power of two. + if (ulong.RotateRight(value, elementSize) != value) + { + immN = 0; + immS = 0; + immR = 0; + + return false; + } + + immN = (elementSize >> 6) & 1; + immS = (((~elementSize + 1) << 1) | (onesInElement - 1)) & 0x3f; + immR = (elementSize - rotation) & (elementSize - 1); + + return true; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs new file mode 100644 index 00000000..89b1e9e6 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs @@ -0,0 +1,287 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using Ryujinx.Common.Memory; +using System; +using System.Collections.Generic; +using System.IO; + +namespace ARMeilleure.CodeGen.Arm64 +{ + class CodeGenContext + { + private const int BccInstLength = 4; + private const int CbnzInstLength = 4; + private const int LdrLitInstLength = 4; + + private readonly Stream _stream; + + public int StreamOffset => (int)_stream.Length; + + public AllocationResult AllocResult { get; } + + public Assembler Assembler { get; } + + public BasicBlock CurrBlock { get; private set; } + + public bool HasCall { get; } + + public int CallArgsRegionSize { get; } + public int FpLrSaveRegionSize { get; } + + private readonly Dictionary _visitedBlocks; + private readonly Dictionary> _pendingBranches; + + private readonly struct ConstantPoolEntry + { + public readonly int Offset; + public readonly Symbol Symbol; + public readonly List<(Operand, int)> LdrOffsets; + + public ConstantPoolEntry(int offset, Symbol symbol) + { + Offset = offset; + Symbol = symbol; + LdrOffsets = new List<(Operand, int)>(); + } + } + + private readonly Dictionary _constantPool; + + private bool _constantPoolWritten; + private long _constantPoolOffset; + + private ArmCondition _jNearCondition; + private Operand _jNearValue; + + private long _jNearPosition; + + private readonly bool _relocatable; + + public CodeGenContext(AllocationResult allocResult, int maxCallArgs, bool relocatable) + { + _stream = MemoryStreamManager.Shared.GetStream(); + + AllocResult = allocResult; + + Assembler = new Assembler(_stream); + + bool hasCall = maxCallArgs >= 0; + + HasCall = hasCall; + + if (maxCallArgs < 0) + { + maxCallArgs = 0; + } + + CallArgsRegionSize = maxCallArgs * 16; + FpLrSaveRegionSize = hasCall ? 16 : 0; + + _visitedBlocks = new Dictionary(); + _pendingBranches = new Dictionary>(); + _constantPool = new Dictionary(); + + _relocatable = relocatable; + } + + public void EnterBlock(BasicBlock block) + { + CurrBlock = block; + + long target = _stream.Position; + + if (_pendingBranches.TryGetValue(block, out var list)) + { + foreach ((ArmCondition condition, long branchPos) in list) + { + _stream.Seek(branchPos, SeekOrigin.Begin); + WriteBranch(condition, target); + } + + _stream.Seek(target, SeekOrigin.Begin); + _pendingBranches.Remove(block); + } + + _visitedBlocks.Add(block, target); + } + + public void JumpTo(BasicBlock target) + { + JumpTo(ArmCondition.Al, target); + } + + public void JumpTo(ArmCondition condition, BasicBlock target) + { + if (_visitedBlocks.TryGetValue(target, out long offset)) + { + WriteBranch(condition, offset); + } + else + { + if (!_pendingBranches.TryGetValue(target, out var list)) + { + list = new List<(ArmCondition, long)>(); + _pendingBranches.Add(target, list); + } + + list.Add((condition, _stream.Position)); + + _stream.Seek(BccInstLength, SeekOrigin.Current); + } + } + + private void WriteBranch(ArmCondition condition, long to) + { + int imm = checked((int)(to - _stream.Position)); + + if (condition != ArmCondition.Al) + { + Assembler.B(condition, imm); + } + else + { + Assembler.B(imm); + } + } + + public void JumpToNear(ArmCondition condition) + { + _jNearCondition = condition; + _jNearPosition = _stream.Position; + + _stream.Seek(BccInstLength, SeekOrigin.Current); + } + + public void JumpToNearIfNotZero(Operand value) + { + _jNearValue = value; + _jNearPosition = _stream.Position; + + _stream.Seek(CbnzInstLength, SeekOrigin.Current); + } + + public void JumpHere() + { + long currentPosition = _stream.Position; + long offset = currentPosition - _jNearPosition; + + _stream.Seek(_jNearPosition, SeekOrigin.Begin); + + if (_jNearValue != default) + { + Assembler.Cbnz(_jNearValue, checked((int)offset)); + _jNearValue = default; + } + else + { + Assembler.B(_jNearCondition, checked((int)offset)); + } + + _stream.Seek(currentPosition, SeekOrigin.Begin); + } + + public void ReserveRelocatableConstant(Operand rt, Symbol symbol, ulong value) + { + if (!_constantPool.TryGetValue(value, out ConstantPoolEntry cpe)) + { + cpe = new ConstantPoolEntry(_constantPool.Count * sizeof(ulong), symbol); + _constantPool.Add(value, cpe); + } + + cpe.LdrOffsets.Add((rt, (int)_stream.Position)); + _stream.Seek(LdrLitInstLength, SeekOrigin.Current); + } + + private long WriteConstantPool() + { + if (_constantPoolWritten) + { + return _constantPoolOffset; + } + + long constantPoolBaseOffset = _stream.Position; + + foreach (ulong value in _constantPool.Keys) + { + WriteUInt64(value); + } + + foreach (ConstantPoolEntry cpe in _constantPool.Values) + { + foreach ((Operand rt, int ldrOffset) in cpe.LdrOffsets) + { + _stream.Seek(ldrOffset, SeekOrigin.Begin); + + int absoluteOffset = checked((int)(constantPoolBaseOffset + cpe.Offset)); + int pcRelativeOffset = absoluteOffset - ldrOffset; + + Assembler.LdrLit(rt, pcRelativeOffset); + } + } + + _stream.Seek(constantPoolBaseOffset + _constantPool.Count * sizeof(ulong), SeekOrigin.Begin); + + _constantPoolOffset = constantPoolBaseOffset; + _constantPoolWritten = true; + + return constantPoolBaseOffset; + } + + public (byte[], RelocInfo) GetCode() + { + long constantPoolBaseOffset = WriteConstantPool(); + + byte[] code = new byte[_stream.Length]; + + long originalPosition = _stream.Position; + + _stream.Seek(0, SeekOrigin.Begin); + _stream.ReadExactly(code, 0, code.Length); + _stream.Seek(originalPosition, SeekOrigin.Begin); + + RelocInfo relocInfo; + + if (_relocatable) + { + RelocEntry[] relocs = new RelocEntry[_constantPool.Count]; + + int index = 0; + + foreach (ConstantPoolEntry cpe in _constantPool.Values) + { + if (cpe.Symbol.Type != SymbolType.None) + { + int absoluteOffset = checked((int)(constantPoolBaseOffset + cpe.Offset)); + relocs[index++] = new RelocEntry(absoluteOffset, cpe.Symbol); + } + } + + if (index != relocs.Length) + { + Array.Resize(ref relocs, index); + } + + relocInfo = new RelocInfo(relocs); + } + else + { + relocInfo = new RelocInfo(Array.Empty()); + } + + return (code, relocInfo); + } + + private void WriteUInt64(ulong value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)(value >> 16)); + _stream.WriteByte((byte)(value >> 24)); + _stream.WriteByte((byte)(value >> 32)); + _stream.WriteByte((byte)(value >> 40)); + _stream.WriteByte((byte)(value >> 48)); + _stream.WriteByte((byte)(value >> 56)); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs new file mode 100644 index 00000000..2df86671 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs @@ -0,0 +1,1581 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Common; +using ARMeilleure.Diagnostics; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; +using static ARMeilleure.IntermediateRepresentation.Operand; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class CodeGenerator + { + private const int DWordScale = 3; + + private const int RegistersCount = 32; + + private const int FpRegister = 29; + private const int LrRegister = 30; + private const int SpRegister = 31; + private const int ZrRegister = 31; + + private enum AccessSize + { + Byte, + Hword, + Auto, + } + + private static readonly Action[] _instTable; + + static CodeGenerator() + { + _instTable = new Action[EnumUtils.GetCount(typeof(Instruction))]; + +#pragma warning disable IDE0055 // Disable formatting + Add(Instruction.Add, GenerateAdd); + Add(Instruction.BitwiseAnd, GenerateBitwiseAnd); + Add(Instruction.BitwiseExclusiveOr, GenerateBitwiseExclusiveOr); + Add(Instruction.BitwiseNot, GenerateBitwiseNot); + Add(Instruction.BitwiseOr, GenerateBitwiseOr); + Add(Instruction.BranchIf, GenerateBranchIf); + Add(Instruction.ByteSwap, GenerateByteSwap); + Add(Instruction.Call, GenerateCall); + // Add(Instruction.Clobber, GenerateClobber); + Add(Instruction.Compare, GenerateCompare); + Add(Instruction.CompareAndSwap, GenerateCompareAndSwap); + Add(Instruction.CompareAndSwap16, GenerateCompareAndSwap16); + Add(Instruction.CompareAndSwap8, GenerateCompareAndSwap8); + Add(Instruction.ConditionalSelect, GenerateConditionalSelect); + Add(Instruction.ConvertI64ToI32, GenerateConvertI64ToI32); + Add(Instruction.ConvertToFP, GenerateConvertToFP); + Add(Instruction.ConvertToFPUI, GenerateConvertToFPUI); + Add(Instruction.Copy, GenerateCopy); + Add(Instruction.CountLeadingZeros, GenerateCountLeadingZeros); + Add(Instruction.Divide, GenerateDivide); + Add(Instruction.DivideUI, GenerateDivideUI); + Add(Instruction.Fill, GenerateFill); + Add(Instruction.Load, GenerateLoad); + Add(Instruction.Load16, GenerateLoad16); + Add(Instruction.Load8, GenerateLoad8); + Add(Instruction.MemoryBarrier, GenerateMemoryBarrier); + Add(Instruction.Multiply, GenerateMultiply); + Add(Instruction.Multiply64HighSI, GenerateMultiply64HighSI); + Add(Instruction.Multiply64HighUI, GenerateMultiply64HighUI); + Add(Instruction.Negate, GenerateNegate); + Add(Instruction.Return, GenerateReturn); + Add(Instruction.RotateRight, GenerateRotateRight); + Add(Instruction.ShiftLeft, GenerateShiftLeft); + Add(Instruction.ShiftRightSI, GenerateShiftRightSI); + Add(Instruction.ShiftRightUI, GenerateShiftRightUI); + Add(Instruction.SignExtend16, GenerateSignExtend16); + Add(Instruction.SignExtend32, GenerateSignExtend32); + Add(Instruction.SignExtend8, GenerateSignExtend8); + Add(Instruction.Spill, GenerateSpill); + Add(Instruction.SpillArg, GenerateSpillArg); + Add(Instruction.StackAlloc, GenerateStackAlloc); + Add(Instruction.Store, GenerateStore); + Add(Instruction.Store16, GenerateStore16); + Add(Instruction.Store8, GenerateStore8); + Add(Instruction.Subtract, GenerateSubtract); + Add(Instruction.Tailcall, GenerateTailcall); + Add(Instruction.VectorCreateScalar, GenerateVectorCreateScalar); + Add(Instruction.VectorExtract, GenerateVectorExtract); + Add(Instruction.VectorExtract16, GenerateVectorExtract16); + Add(Instruction.VectorExtract8, GenerateVectorExtract8); + Add(Instruction.VectorInsert, GenerateVectorInsert); + Add(Instruction.VectorInsert16, GenerateVectorInsert16); + Add(Instruction.VectorInsert8, GenerateVectorInsert8); + Add(Instruction.VectorOne, GenerateVectorOne); + Add(Instruction.VectorZero, GenerateVectorZero); + Add(Instruction.VectorZeroUpper64, GenerateVectorZeroUpper64); + Add(Instruction.VectorZeroUpper96, GenerateVectorZeroUpper96); + Add(Instruction.ZeroExtend16, GenerateZeroExtend16); + Add(Instruction.ZeroExtend32, GenerateZeroExtend32); + Add(Instruction.ZeroExtend8, GenerateZeroExtend8); +#pragma warning restore IDE0055 + + static void Add(Instruction inst, Action func) + { + _instTable[(int)inst] = func; + } + } + + public static CompiledFunction Generate(CompilerContext cctx) + { + ControlFlowGraph cfg = cctx.Cfg; + + Logger.StartPass(PassName.Optimization); + + if (cctx.Options.HasFlag(CompilerOptions.Optimize)) + { + if (cctx.Options.HasFlag(CompilerOptions.SsaForm)) + { + Optimizer.RunPass(cfg); + } + + BlockPlacement.RunPass(cfg); + } + + Arm64Optimizer.RunPass(cfg); + + Logger.EndPass(PassName.Optimization, cfg); + + Logger.StartPass(PassName.PreAllocation); + + StackAllocator stackAlloc = new(); + + PreAllocator.RunPass(cctx, out int maxCallArgs); + + Logger.EndPass(PassName.PreAllocation, cfg); + + Logger.StartPass(PassName.RegisterAllocation); + + if (cctx.Options.HasFlag(CompilerOptions.SsaForm)) + { + Ssa.Deconstruct(cfg); + } + + IRegisterAllocator regAlloc; + + if (cctx.Options.HasFlag(CompilerOptions.Lsra)) + { + regAlloc = new LinearScanAllocator(); + } + else + { + regAlloc = new HybridAllocator(); + } + + RegisterMasks regMasks = new( + CallingConvention.GetIntAvailableRegisters(), + CallingConvention.GetVecAvailableRegisters(), + CallingConvention.GetIntCallerSavedRegisters(), + CallingConvention.GetVecCallerSavedRegisters(), + CallingConvention.GetIntCalleeSavedRegisters(), + CallingConvention.GetVecCalleeSavedRegisters(), + RegistersCount); + + AllocationResult allocResult = regAlloc.RunPass(cfg, stackAlloc, regMasks); + + Logger.EndPass(PassName.RegisterAllocation, cfg); + + Logger.StartPass(PassName.CodeGeneration); + + bool relocatable = (cctx.Options & CompilerOptions.Relocatable) != 0; + + CodeGenContext context = new(allocResult, maxCallArgs, relocatable); + + UnwindInfo unwindInfo = WritePrologue(context); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + context.EnterBlock(block); + + for (Operation node = block.Operations.First; node != default;) + { + node = GenerateOperation(context, node); + } + + if (block.SuccessorsCount == 0) + { + // The only blocks which can have 0 successors are exit blocks. + Operation last = block.Operations.Last; + + Debug.Assert(last.Instruction == Instruction.Tailcall || + last.Instruction == Instruction.Return); + } + else + { + BasicBlock succ = block.GetSuccessor(0); + + if (succ != block.ListNext) + { + context.JumpTo(succ); + } + } + } + + (byte[] code, RelocInfo relocInfo) = context.GetCode(); + + Logger.EndPass(PassName.CodeGeneration); + + return new CompiledFunction(code, unwindInfo, relocInfo); + } + + private static Operation GenerateOperation(CodeGenContext context, Operation operation) + { + if (operation.Instruction == Instruction.Extended) + { + CodeGeneratorIntrinsic.GenerateOperation(context, operation); + } + else + { + if (IsLoadOrStore(operation) && + operation.ListNext != default && + operation.ListNext.Instruction == operation.Instruction && + TryPairMemoryOp(context, operation, operation.ListNext)) + { + // Skip next operation if we managed to pair them. + return operation.ListNext.ListNext; + } + + Action func = _instTable[(int)operation.Instruction]; + + if (func != null) + { + func(context, operation); + } + else + { + throw new ArgumentException($"Invalid instruction \"{operation.Instruction}\"."); + } + } + + return operation.ListNext; + } + + private static void GenerateAdd(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + // ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Add(dest, src1, src2); + } + else + { + context.Assembler.FaddScalar(dest, src1, src2); + } + } + + private static void GenerateBitwiseAnd(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.And(dest, src1, src2); + } + + private static void GenerateBitwiseExclusiveOr(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Eor(dest, src1, src2); + } + else + { + context.Assembler.EorVector(dest, src1, src2); + } + } + + private static void GenerateBitwiseNot(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Mvn(dest, source); + } + + private static void GenerateBitwiseOr(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Orr(dest, src1, src2); + } + + private static void GenerateBranchIf(CodeGenContext context, Operation operation) + { + Operand comp = operation.GetSource(2); + + Debug.Assert(comp.Kind == OperandKind.Constant); + + var cond = ((Comparison)comp.AsInt32()).ToArmCondition(); + + GenerateCompareCommon(context, operation); + + context.JumpTo(cond, context.CurrBlock.GetSuccessor(1)); + } + + private static void GenerateByteSwap(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Rev(dest, source); + } + + private static void GenerateCall(CodeGenContext context, Operation operation) + { + context.Assembler.Blr(operation.GetSource(0)); + } + + private static void GenerateCompare(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand comp = operation.GetSource(2); + + Debug.Assert(dest.Type == OperandType.I32); + Debug.Assert(comp.Kind == OperandKind.Constant); + + var cond = ((Comparison)comp.AsInt32()).ToArmCondition(); + + GenerateCompareCommon(context, operation); + + context.Assembler.Cset(dest, cond); + } + + private static void GenerateCompareAndSwap(CodeGenContext context, Operation operation) + { + if (operation.SourcesCount == 5) // CompareAndSwap128 has 5 sources, compared to CompareAndSwap64/32's 3. + { + Operand actualLow = operation.GetDestination(0); + Operand actualHigh = operation.GetDestination(1); + Operand temp0 = operation.GetDestination(2); + Operand temp1 = operation.GetDestination(3); + Operand address = operation.GetSource(0); + Operand expectedLow = operation.GetSource(1); + Operand expectedHigh = operation.GetSource(2); + Operand desiredLow = operation.GetSource(3); + Operand desiredHigh = operation.GetSource(4); + + GenerateAtomicDcas( + context, + address, + expectedLow, + expectedHigh, + desiredLow, + desiredHigh, + actualLow, + actualHigh, + temp0, + temp1); + } + else + { + Operand actual = operation.GetDestination(0); + Operand result = operation.GetDestination(1); + Operand address = operation.GetSource(0); + Operand expected = operation.GetSource(1); + Operand desired = operation.GetSource(2); + + GenerateAtomicCas(context, address, expected, desired, actual, result, AccessSize.Auto); + } + } + + private static void GenerateCompareAndSwap16(CodeGenContext context, Operation operation) + { + Operand actual = operation.GetDestination(0); + Operand result = operation.GetDestination(1); + Operand address = operation.GetSource(0); + Operand expected = operation.GetSource(1); + Operand desired = operation.GetSource(2); + + GenerateAtomicCas(context, address, expected, desired, actual, result, AccessSize.Hword); + } + + private static void GenerateCompareAndSwap8(CodeGenContext context, Operation operation) + { + Operand actual = operation.GetDestination(0); + Operand result = operation.GetDestination(1); + Operand address = operation.GetSource(0); + Operand expected = operation.GetSource(1); + Operand desired = operation.GetSource(2); + + GenerateAtomicCas(context, address, expected, desired, actual, result, AccessSize.Byte); + } + + private static void GenerateCompareCommon(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(src1, src2); + + Debug.Assert(src1.Type.IsInteger()); + + context.Assembler.Cmp(src1, src2); + } + + private static void GenerateConditionalSelect(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(dest, src2, src3); + + Debug.Assert(dest.Type.IsInteger()); + Debug.Assert(src1.Type == OperandType.I32); + + context.Assembler.Cmp(src1, Const(src1.Type, 0)); + context.Assembler.Csel(dest, src2, src3, ArmCondition.Ne); + } + + private static void GenerateConvertI64ToI32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.I32 && source.Type == OperandType.I64); + + context.Assembler.Mov(dest, Register(source, OperandType.I32)); + } + + private static void GenerateConvertToFP(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.FP32 || dest.Type == OperandType.FP64); + Debug.Assert(dest.Type != source.Type); + Debug.Assert(source.Type != OperandType.V128); + + if (source.Type.IsInteger()) + { + context.Assembler.ScvtfScalar(dest, source); + } + else + { + context.Assembler.FcvtScalar(dest, source); + } + } + + private static void GenerateConvertToFPUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.FP32 || dest.Type == OperandType.FP64); + Debug.Assert(dest.Type != source.Type); + Debug.Assert(source.Type.IsInteger()); + + context.Assembler.UcvtfScalar(dest, source); + } + + private static void GenerateCopy(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger() || source.Kind != OperandKind.Constant); + + // Moves to the same register are useless. + if (dest.Kind == source.Kind && dest.Value == source.Value) + { + return; + } + + if (dest.Kind == OperandKind.Register && source.Kind == OperandKind.Constant) + { + if (source.Relocatable) + { + context.ReserveRelocatableConstant(dest, source.Symbol, source.Value); + } + else + { + GenerateConstantCopy(context, dest, source.Value); + } + } + else + { + context.Assembler.Mov(dest, source); + } + } + + private static void GenerateCountLeadingZeros(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Clz(dest, source); + } + + private static void GenerateDivide(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand dividend = operation.GetSource(0); + Operand divisor = operation.GetSource(1); + + ValidateBinOp(dest, dividend, divisor); + + if (dest.Type.IsInteger()) + { + context.Assembler.Sdiv(dest, dividend, divisor); + } + else + { + context.Assembler.FdivScalar(dest, dividend, divisor); + } + } + + private static void GenerateDivideUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand dividend = operation.GetSource(0); + Operand divisor = operation.GetSource(1); + + ValidateBinOp(dest, dividend, divisor); + + context.Assembler.Udiv(dest, dividend, divisor); + } + + private static void GenerateLoad(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = operation.GetSource(0); + + context.Assembler.Ldr(value, address); + } + + private static void GenerateLoad16(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = operation.GetSource(0); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.LdrhRiUn(value, address, 0); + } + + private static void GenerateLoad8(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = operation.GetSource(0); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.LdrbRiUn(value, address, 0); + } + + private static void GenerateMemoryBarrier(CodeGenContext context, Operation operation) + { + context.Assembler.Dmb(0xf); + } + + private static void GenerateMultiply(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Mul(dest, src1, src2); + } + else + { + context.Assembler.FmulScalar(dest, src1, src2); + } + } + + private static void GenerateMultiply64HighSI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1, src2); + + Debug.Assert(dest.Type == OperandType.I64); + + context.Assembler.Smulh(dest, src1, src2); + } + + private static void GenerateMultiply64HighUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1, src2); + + Debug.Assert(dest.Type == OperandType.I64); + + context.Assembler.Umulh(dest, src1, src2); + } + + private static void GenerateNegate(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + if (dest.Type.IsInteger()) + { + context.Assembler.Neg(dest, source); + } + else + { + context.Assembler.FnegScalar(dest, source); + } + } + + private static void GenerateLoad(CodeGenContext context, Operand value, Operand address, int offset) + { + if (CodeGenCommon.ConstFitsOnUImm12(offset, value.Type)) + { + context.Assembler.LdrRiUn(value, address, offset); + } + else if (CodeGenCommon.ConstFitsOnSImm9(offset)) + { + context.Assembler.Ldur(value, address, offset); + } + else + { + Operand tempAddress = Register(CodeGenCommon.ReservedRegister); + GenerateConstantCopy(context, tempAddress, (ulong)offset); + context.Assembler.Add(tempAddress, address, tempAddress, ArmExtensionType.Uxtx); // Address might be SP and must be the first input. + context.Assembler.LdrRiUn(value, tempAddress, 0); + } + } + + private static void GenerateReturn(CodeGenContext context, Operation operation) + { + WriteEpilogue(context); + + context.Assembler.Ret(Register(LrRegister)); + } + + private static void GenerateRotateRight(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Ror(dest, src1, src2); + } + + private static void GenerateShiftLeft(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Lsl(dest, src1, src2); + } + + private static void GenerateShiftRightSI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Asr(dest, src1, src2); + } + + private static void GenerateShiftRightUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Lsr(dest, src1, src2); + } + + private static void GenerateSignExtend16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Sxth(dest, source); + } + + private static void GenerateSignExtend32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Sxtw(dest, source); + } + + private static void GenerateSignExtend8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Sxtb(dest, source); + } + + private static void GenerateFill(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + context.CallArgsRegionSize + context.FpLrSaveRegionSize; + + GenerateLoad(context, dest, Register(SpRegister), offs); + } + + private static void GenerateStore(CodeGenContext context, Operand value, Operand address, int offset) + { + if (CodeGenCommon.ConstFitsOnUImm12(offset, value.Type)) + { + context.Assembler.StrRiUn(value, address, offset); + } + else if (CodeGenCommon.ConstFitsOnSImm9(offset)) + { + context.Assembler.Stur(value, address, offset); + } + else + { + Operand tempAddress = Register(CodeGenCommon.ReservedRegister); + GenerateConstantCopy(context, tempAddress, (ulong)offset); + context.Assembler.Add(tempAddress, address, tempAddress, ArmExtensionType.Uxtx); // Address might be SP and must be the first input. + context.Assembler.StrRiUn(value, tempAddress, 0); + } + } + + private static void GenerateSpill(CodeGenContext context, Operation operation) + { + GenerateSpill(context, operation, context.CallArgsRegionSize + context.FpLrSaveRegionSize); + } + + private static void GenerateSpillArg(CodeGenContext context, Operation operation) + { + GenerateSpill(context, operation, 0); + } + + private static void GenerateStackAlloc(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + context.CallArgsRegionSize + context.FpLrSaveRegionSize; + + context.Assembler.Add(dest, Register(SpRegister), Const(dest.Type, offs)); + } + + private static void GenerateStore(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = operation.GetSource(0); + + context.Assembler.Str(value, address); + } + + private static void GenerateStore16(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = operation.GetSource(0); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.StrhRiUn(value, address, 0); + } + + private static void GenerateStore8(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = operation.GetSource(0); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.StrbRiUn(value, address, 0); + } + + private static void GenerateSpill(CodeGenContext context, Operation operation, int baseOffset) + { + Operand offset = operation.GetSource(0); + Operand source = operation.GetSource(1); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + baseOffset; + + GenerateStore(context, source, Register(SpRegister), offs); + } + + private static void GenerateSubtract(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + // ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Sub(dest, src1, src2); + } + else + { + context.Assembler.FsubScalar(dest, src1, src2); + } + } + + private static void GenerateTailcall(CodeGenContext context, Operation operation) + { + WriteEpilogue(context); + + context.Assembler.Br(operation.GetSource(0)); + } + + private static void GenerateVectorCreateScalar(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + if (dest != default) + { + Debug.Assert(!dest.Type.IsInteger() && source.Type.IsInteger()); + + OperandType destType = source.Type == OperandType.I64 ? OperandType.FP64 : OperandType.FP32; + + context.Assembler.Fmov(Register(dest, destType), source, topHalf: false); + } + } + + private static void GenerateVectorExtract(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; // Value + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < OperandType.V128.GetSizeInBytes() / dest.Type.GetSizeInBytes()); + + if (dest.Type.IsInteger()) + { + context.Assembler.Umov(dest, src1, index, dest.Type == OperandType.I64 ? 3 : 2); + } + else + { + context.Assembler.DupScalar(dest, src1, index, dest.Type == OperandType.FP64 ? 3 : 2); + } + } + + private static void GenerateVectorExtract16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; // Value + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < 8); + + context.Assembler.Umov(dest, src1, index, 1); + } + + private static void GenerateVectorExtract8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; // Value + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < 16); + + context.Assembler.Umov(dest, src1, index, 0); + } + + private static void GenerateVectorInsert(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Value + Operand src3 = operation.GetSource(2); // Index + + EnsureSameReg(dest, src1); + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + if (src2.Type.IsInteger()) + { + context.Assembler.Ins(dest, src2, index, src2.Type == OperandType.I64 ? 3 : 2); + } + else + { + context.Assembler.Ins(dest, src2, 0, index, src2.Type == OperandType.FP64 ? 3 : 2); + } + } + + private static void GenerateVectorInsert16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Value + Operand src3 = operation.GetSource(2); // Index + + EnsureSameReg(dest, src1); + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + context.Assembler.Ins(dest, src2, index, 1); + } + + private static void GenerateVectorInsert8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Value + Operand src3 = operation.GetSource(2); // Index + + EnsureSameReg(dest, src1); + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + context.Assembler.Ins(dest, src2, index, 0); + } + + private static void GenerateVectorOne(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.CmeqVector(dest, dest, dest, 2); + } + + private static void GenerateVectorZero(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.EorVector(dest, dest, dest); + } + + private static void GenerateVectorZeroUpper64(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.V128 && source.Type == OperandType.V128); + + context.Assembler.Fmov(Register(dest, OperandType.FP64), Register(source, OperandType.FP64)); + } + + private static void GenerateVectorZeroUpper96(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.V128 && source.Type == OperandType.V128); + + context.Assembler.Fmov(Register(dest, OperandType.FP32), Register(source, OperandType.FP32)); + } + + private static void GenerateZeroExtend16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Uxth(dest, source); + } + + private static void GenerateZeroExtend32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + // We can eliminate the move if source is already 32-bit and the registers are the same. + if (dest.Value == source.Value && source.Type == OperandType.I32) + { + return; + } + + context.Assembler.Mov(Register(dest.GetRegister().Index, OperandType.I32), source); + } + + private static void GenerateZeroExtend8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Uxtb(dest, source); + } + + private static UnwindInfo WritePrologue(CodeGenContext context) + { + List pushEntries = new(); + + Operand rsp = Register(SpRegister); + + int intMask = CallingConvention.GetIntCalleeSavedRegisters() & context.AllocResult.IntUsedRegisters; + int vecMask = CallingConvention.GetFpCalleeSavedRegisters() & context.AllocResult.VecUsedRegisters; + + int intCalleeSavedRegsCount = BitOperations.PopCount((uint)intMask); + int vecCalleeSavedRegsCount = BitOperations.PopCount((uint)vecMask); + + int calleeSaveRegionSize = Align16(intCalleeSavedRegsCount * 8 + vecCalleeSavedRegsCount * 8); + + int offset = 0; + + WritePrologueCalleeSavesPreIndexed(context, pushEntries, ref intMask, ref offset, calleeSaveRegionSize, OperandType.I64); + WritePrologueCalleeSavesPreIndexed(context, pushEntries, ref vecMask, ref offset, calleeSaveRegionSize, OperandType.FP64); + + int localSize = Align16(context.AllocResult.SpillRegionSize + context.FpLrSaveRegionSize); + int outArgsSize = context.CallArgsRegionSize; + + if (CodeGenCommon.ConstFitsOnSImm7(localSize, DWordScale)) + { + if (context.HasCall) + { + context.Assembler.StpRiPre(Register(FpRegister), Register(LrRegister), rsp, -localSize); + context.Assembler.MovSp(Register(FpRegister), rsp); + } + + if (outArgsSize != 0) + { + context.Assembler.Sub(rsp, rsp, Const(OperandType.I64, outArgsSize)); + } + } + else + { + int frameSize = localSize + outArgsSize; + if (frameSize != 0) + { + if (CodeGenCommon.ConstFitsOnUImm12(frameSize)) + { + context.Assembler.Sub(rsp, rsp, Const(OperandType.I64, frameSize)); + } + else + { + Operand tempSize = Register(CodeGenCommon.ReservedRegister); + GenerateConstantCopy(context, tempSize, (ulong)frameSize); + context.Assembler.Sub(rsp, rsp, tempSize, ArmExtensionType.Uxtx); + } + } + + context.Assembler.StpRiUn(Register(FpRegister), Register(LrRegister), rsp, outArgsSize); + + if (outArgsSize != 0) + { + context.Assembler.Add(Register(FpRegister), Register(SpRegister), Const(OperandType.I64, outArgsSize)); + } + else + { + context.Assembler.MovSp(Register(FpRegister), Register(SpRegister)); + } + } + + return new UnwindInfo(pushEntries.ToArray(), context.StreamOffset); + } + + private static void WritePrologueCalleeSavesPreIndexed( + CodeGenContext context, + List pushEntries, + ref int mask, + ref int offset, + int calleeSaveRegionSize, + OperandType type) + { + if ((BitOperations.PopCount((uint)mask) & 1) != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.PushReg, context.StreamOffset, regIndex: reg)); + + mask &= ~(1 << reg); + + if (offset != 0) + { + context.Assembler.StrRiUn(Register(reg, type), Register(SpRegister), offset); + } + else + { + context.Assembler.StrRiPre(Register(reg, type), Register(SpRegister), -calleeSaveRegionSize); + } + + offset += type.GetSizeInBytes(); + } + + while (mask != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.PushReg, context.StreamOffset, regIndex: reg)); + + mask &= ~(1 << reg); + + int reg2 = BitOperations.TrailingZeroCount(mask); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.PushReg, context.StreamOffset, regIndex: reg2)); + + mask &= ~(1 << reg2); + + if (offset != 0) + { + context.Assembler.StpRiUn(Register(reg, type), Register(reg2, type), Register(SpRegister), offset); + } + else + { + context.Assembler.StpRiPre(Register(reg, type), Register(reg2, type), Register(SpRegister), -calleeSaveRegionSize); + } + + offset += type.GetSizeInBytes() * 2; + } + } + + private static void WriteEpilogue(CodeGenContext context) + { + Operand rsp = Register(SpRegister); + + int localSize = Align16(context.AllocResult.SpillRegionSize + context.FpLrSaveRegionSize); + int outArgsSize = context.CallArgsRegionSize; + + if (CodeGenCommon.ConstFitsOnSImm7(localSize, DWordScale)) + { + if (outArgsSize != 0) + { + context.Assembler.Add(rsp, rsp, Const(OperandType.I64, outArgsSize)); + } + + if (context.HasCall) + { + context.Assembler.LdpRiPost(Register(FpRegister), Register(LrRegister), rsp, localSize); + } + } + else + { + if (context.HasCall) + { + context.Assembler.LdpRiUn(Register(FpRegister), Register(LrRegister), rsp, outArgsSize); + } + + int frameSize = localSize + outArgsSize; + if (frameSize != 0) + { + if (CodeGenCommon.ConstFitsOnUImm12(frameSize)) + { + context.Assembler.Add(rsp, rsp, Const(OperandType.I64, frameSize)); + } + else + { + Operand tempSize = Register(CodeGenCommon.ReservedRegister); + GenerateConstantCopy(context, tempSize, (ulong)frameSize); + context.Assembler.Add(rsp, rsp, tempSize, ArmExtensionType.Uxtx); + } + } + } + + int intMask = CallingConvention.GetIntCalleeSavedRegisters() & context.AllocResult.IntUsedRegisters; + int vecMask = CallingConvention.GetFpCalleeSavedRegisters() & context.AllocResult.VecUsedRegisters; + + int intCalleeSavedRegsCount = BitOperations.PopCount((uint)intMask); + int vecCalleeSavedRegsCount = BitOperations.PopCount((uint)vecMask); + + int offset = intCalleeSavedRegsCount * 8 + vecCalleeSavedRegsCount * 8; + int calleeSaveRegionSize = Align16(offset); + + WriteEpilogueCalleeSavesPostIndexed(context, ref vecMask, ref offset, calleeSaveRegionSize, OperandType.FP64); + WriteEpilogueCalleeSavesPostIndexed(context, ref intMask, ref offset, calleeSaveRegionSize, OperandType.I64); + } + + private static void WriteEpilogueCalleeSavesPostIndexed( + CodeGenContext context, + ref int mask, + ref int offset, + int calleeSaveRegionSize, + OperandType type) + { + while (mask != 0) + { + int reg = BitUtils.HighestBitSet(mask); + + mask &= ~(1 << reg); + + if (mask != 0) + { + int reg2 = BitUtils.HighestBitSet(mask); + + mask &= ~(1 << reg2); + + offset -= type.GetSizeInBytes() * 2; + + if (offset != 0) + { + context.Assembler.LdpRiUn(Register(reg2, type), Register(reg, type), Register(SpRegister), offset); + } + else + { + context.Assembler.LdpRiPost(Register(reg2, type), Register(reg, type), Register(SpRegister), calleeSaveRegionSize); + } + } + else + { + offset -= type.GetSizeInBytes(); + + if (offset != 0) + { + context.Assembler.LdrRiUn(Register(reg, type), Register(SpRegister), offset); + } + else + { + context.Assembler.LdrRiPost(Register(reg, type), Register(SpRegister), calleeSaveRegionSize); + } + } + } + } + + private static void GenerateConstantCopy(CodeGenContext context, Operand dest, ulong value) + { + if (value == 0) + { + context.Assembler.Mov(dest, Register(ZrRegister, dest.Type)); + } + else if (CodeGenCommon.TryEncodeBitMask(dest.Type, value, out _, out _, out _)) + { + context.Assembler.Orr(dest, Register(ZrRegister, dest.Type), Const(dest.Type, (long)value)); + } + else + { + int hw = 0; + bool first = true; + + while (value != 0) + { + int valueLow = (ushort)value; + if (valueLow != 0) + { + if (first) + { + context.Assembler.Movz(dest, valueLow, hw); + first = false; + } + else + { + context.Assembler.Movk(dest, valueLow, hw); + } + } + + hw++; + value >>= 16; + } + } + } + + private static void GenerateAtomicCas( + CodeGenContext context, + Operand address, + Operand expected, + Operand desired, + Operand actual, + Operand result, + AccessSize accessSize) + { + int startOffset = context.StreamOffset; + + switch (accessSize) + { + case AccessSize.Byte: + context.Assembler.Ldaxrb(actual, address); + break; + case AccessSize.Hword: + context.Assembler.Ldaxrh(actual, address); + break; + default: + context.Assembler.Ldaxr(actual, address); + break; + } + + context.Assembler.Cmp(actual, expected); + + context.JumpToNear(ArmCondition.Ne); + + switch (accessSize) + { + case AccessSize.Byte: + context.Assembler.Stlxrb(desired, address, result); + break; + case AccessSize.Hword: + context.Assembler.Stlxrh(desired, address, result); + break; + default: + context.Assembler.Stlxr(desired, address, result); + break; + } + + context.Assembler.Cbnz(result, startOffset - context.StreamOffset); // Retry if store failed. + + context.JumpHere(); + + context.Assembler.Clrex(); + } + + private static void GenerateAtomicDcas( + CodeGenContext context, + Operand address, + Operand expectedLow, + Operand expectedHigh, + Operand desiredLow, + Operand desiredHigh, + Operand actualLow, + Operand actualHigh, + Operand temp0, + Operand temp1) + { + int startOffset = context.StreamOffset; + + context.Assembler.Ldaxp(actualLow, actualHigh, address); + context.Assembler.Eor(temp0, actualHigh, expectedHigh); + context.Assembler.Eor(temp1, actualLow, expectedLow); + context.Assembler.Orr(temp0, temp1, temp0); + + context.JumpToNearIfNotZero(temp0); + + Operand result = Register(temp0, OperandType.I32); + + context.Assembler.Stlxp(desiredLow, desiredHigh, address, result); + context.Assembler.Cbnz(result, startOffset - context.StreamOffset); // Retry if store failed. + + context.JumpHere(); + + context.Assembler.Clrex(); + } + + private static bool TryPairMemoryOp(CodeGenContext context, Operation currentOp, Operation nextOp) + { + if (!TryGetMemOpBaseAndOffset(currentOp, out Operand op1Base, out int op1Offset)) + { + return false; + } + + if (!TryGetMemOpBaseAndOffset(nextOp, out Operand op2Base, out int op2Offset)) + { + return false; + } + + if (op1Base != op2Base) + { + return false; + } + + OperandType valueType = GetMemOpValueType(currentOp); + + if (valueType != GetMemOpValueType(nextOp) || op1Offset + valueType.GetSizeInBytes() != op2Offset) + { + return false; + } + + if (!CodeGenCommon.ConstFitsOnSImm7(op1Offset, valueType.GetSizeInBytesLog2())) + { + return false; + } + + if (currentOp.Instruction == Instruction.Load) + { + context.Assembler.LdpRiUn(currentOp.Destination, nextOp.Destination, op1Base, op1Offset); + } + else if (currentOp.Instruction == Instruction.Store) + { + context.Assembler.StpRiUn(currentOp.GetSource(1), nextOp.GetSource(1), op1Base, op1Offset); + } + else + { + return false; + } + + return true; + } + + private static bool IsLoadOrStore(Operation operation) + { + return operation.Instruction == Instruction.Load || operation.Instruction == Instruction.Store; + } + + private static OperandType GetMemOpValueType(Operation operation) + { + if (operation.Destination != default) + { + return operation.Destination.Type; + } + + return operation.GetSource(1).Type; + } + + private static bool TryGetMemOpBaseAndOffset(Operation operation, out Operand baseAddress, out int offset) + { + baseAddress = default; + offset = 0; + Operand address = operation.GetSource(0); + + if (address.Kind != OperandKind.Memory) + { + return false; + } + + MemoryOperand memOp = address.GetMemory(); + Operand baseOp = memOp.BaseAddress; + + if (baseOp == default) + { + baseOp = memOp.Index; + + if (baseOp == default || memOp.Scale != Multiplier.x1) + { + return false; + } + } + if (memOp.Index != default) + { + return false; + } + + baseAddress = memOp.BaseAddress; + offset = memOp.Displacement; + + return true; + } + + private static Operand Register(Operand operand, OperandType type = OperandType.I64) + { + return Register(operand.GetRegister().Index, type); + } + + private static Operand Register(int register, OperandType type = OperandType.I64) + { + return Factory.Register(register, RegisterType.Integer, type); + } + + private static int Align16(int value) + { + return (value + 0xf) & ~0xf; + } + + [Conditional("DEBUG")] + private static void ValidateUnOp(Operand dest, Operand source) + { + // Destination and source aren't forced to be equals + // EnsureSameReg (dest, source); + EnsureSameType(dest, source); + } + + [Conditional("DEBUG")] + private static void ValidateBinOp(Operand dest, Operand src1, Operand src2) + { + // Destination and source aren't forced to be equals + // EnsureSameReg (dest, src1); + EnsureSameType(dest, src1, src2); + } + + [Conditional("DEBUG")] + private static void ValidateShift(Operand dest, Operand src1, Operand src2) + { + // Destination and source aren't forced to be equals + // EnsureSameReg (dest, src1); + EnsureSameType(dest, src1); + + Debug.Assert(dest.Type.IsInteger() && src2.Type == OperandType.I32); + } + + private static void EnsureSameReg(Operand op1, Operand op2) + { + Debug.Assert(op1.Kind == OperandKind.Register || op1.Kind == OperandKind.Memory); + Debug.Assert(op1.Kind == op2.Kind); + Debug.Assert(op1.Value == op2.Value); + } + + private static void EnsureSameType(Operand op1, Operand op2) + { + Debug.Assert(op1.Type == op2.Type); + } + + private static void EnsureSameType(Operand op1, Operand op2, Operand op3) + { + Debug.Assert(op1.Type == op2.Type); + Debug.Assert(op1.Type == op3.Type); + } + +#pragma warning disable IDE0051 // Remove unused private member + private static void EnsureSameType(Operand op1, Operand op2, Operand op3, Operand op4) + { + Debug.Assert(op1.Type == op2.Type); + Debug.Assert(op1.Type == op3.Type); + Debug.Assert(op1.Type == op4.Type); + } +#pragma warning restore IDE0051 + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs new file mode 100644 index 00000000..b8737055 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs @@ -0,0 +1,691 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Diagnostics; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class CodeGeneratorIntrinsic + { + public static void GenerateOperation(CodeGenContext context, Operation operation) + { + Intrinsic intrin = operation.Intrinsic; + + IntrinsicInfo info = IntrinsicTable.GetInfo(intrin & ~(Intrinsic.Arm64VTypeMask | Intrinsic.Arm64VSizeMask)); + + switch (info.Type) + { + case IntrinsicType.ScalarUnary: + GenerateVectorUnary( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.ScalarUnaryByElem: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorUnaryByElem( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(1).AsInt32(), + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.ScalarBinary: + GenerateVectorBinary( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.ScalarBinaryFPByElem: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryFPByElem( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(2).AsInt32(), + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.ScalarBinaryRd: + GenerateVectorUnary( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1)); + break; + case IntrinsicType.ScalarBinaryShl: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShlImm( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.ScalarBinaryShr: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.ScalarFPCompare: + GenerateScalarFPCompare( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.ScalarFPConvFixed: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + 0, + ((uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift) + 2u, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.ScalarFPConvFixedGpr: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateScalarFPConvGpr( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.ScalarFPConvGpr: + GenerateScalarFPConvGpr( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.ScalarTernary: + GenerateScalarTernary( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + operation.GetSource(2), + operation.GetSource(0)); + break; + case IntrinsicType.ScalarTernaryFPRdByElem: + Debug.Assert(operation.GetSource(3).Kind == OperandKind.Constant); + + GenerateVectorBinaryFPByElem( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(3).AsInt32(), + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.ScalarTernaryShlRd: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryShlImm( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + (uint)operation.GetSource(2).AsInt32()); + break; + case IntrinsicType.ScalarTernaryShrRd: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + (uint)operation.GetSource(2).AsInt32()); + break; + + case IntrinsicType.Vector128Unary: + GenerateVectorUnary( + context, + 1, + 0, + info.Inst, + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.Vector128Binary: + GenerateVectorBinary( + context, + 1, + 0, + info.Inst, + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.Vector128BinaryRd: + GenerateVectorUnary( + context, + 1, + 0, + info.Inst, + operation.Destination, + operation.GetSource(1)); + break; + + case IntrinsicType.VectorUnary: + GenerateVectorUnary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.VectorUnaryByElem: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorUnaryByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(1).AsInt32(), + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.VectorBinary: + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryBitwise: + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryByElem: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(2).AsInt32(), + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryFPByElem: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryFPByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(2).AsInt32(), + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryRd: + GenerateVectorUnary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryShl: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShlImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.VectorBinaryShr: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.VectorFPConvFixed: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + ((uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift) + 2u, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.VectorInsertByElem: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + Debug.Assert(operation.GetSource(3).Kind == OperandKind.Constant); + + GenerateVectorInsertByElem( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(3).AsInt32(), + (uint)operation.GetSource(1).AsInt32(), + operation.Destination, + operation.GetSource(2)); + break; + case IntrinsicType.VectorLookupTable: + Debug.Assert((uint)(operation.SourcesCount - 2) <= 3); + + for (int i = 1; i < operation.SourcesCount - 1; i++) + { + Register currReg = operation.GetSource(i).GetRegister(); + Register prevReg = operation.GetSource(i - 1).GetRegister(); + + Debug.Assert(prevReg.Index + 1 == currReg.Index && currReg.Type == RegisterType.Vector); + } + + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + info.Inst | ((uint)(operation.SourcesCount - 2) << 13), + operation.Destination, + operation.GetSource(0), + operation.GetSource(operation.SourcesCount - 1)); + break; + case IntrinsicType.VectorTernaryFPRdByElem: + Debug.Assert(operation.GetSource(3).Kind == OperandKind.Constant); + + GenerateVectorBinaryFPByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(3).AsInt32(), + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.VectorTernaryRd: + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.VectorTernaryRdBitwise: + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.VectorTernaryRdByElem: + Debug.Assert(operation.GetSource(3).Kind == OperandKind.Constant); + + GenerateVectorBinaryByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(3).AsInt32(), + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.VectorTernaryShlRd: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryShlImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + (uint)operation.GetSource(2).AsInt32()); + break; + case IntrinsicType.VectorTernaryShrRd: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + (uint)operation.GetSource(2).AsInt32()); + break; + + case IntrinsicType.GetRegister: + context.Assembler.WriteInstruction(info.Inst, operation.Destination); + break; + case IntrinsicType.SetRegister: + context.Assembler.WriteInstruction(info.Inst, operation.GetSource(0)); + break; + + default: + throw new NotImplementedException(info.Type.ToString()); + } + } + + private static void GenerateScalarFPCompare( + CodeGenContext context, + uint sz, + uint instruction, + Operand dest, + Operand rn, + Operand rm) + { + instruction |= (sz << 22); + + if (rm.Kind == OperandKind.Constant && rm.Value == 0) + { + instruction |= 0b1000; + rm = rn; + } + + context.Assembler.WriteInstructionRm16NoRet(instruction, rn, rm); + context.Assembler.Mrs(dest, 1, 3, 4, 2, 0); + } + + private static void GenerateScalarFPConvGpr( + CodeGenContext context, + uint sz, + uint instruction, + Operand rd, + Operand rn) + { + instruction |= (sz << 22); + + if (rd.Type.IsInteger()) + { + context.Assembler.WriteInstructionAuto(instruction, rd, rn); + } + else + { + if (rn.Type == OperandType.I64) + { + instruction |= Assembler.SfFlag; + } + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + } + + private static void GenerateScalarFPConvGpr( + CodeGenContext context, + uint sz, + uint instruction, + Operand rd, + Operand rn, + uint fBits) + { + Debug.Assert(fBits <= 64); + + instruction |= (sz << 22); + instruction |= (64 - fBits) << 10; + + if (rd.Type.IsInteger()) + { + Debug.Assert(rd.Type != OperandType.I32 || fBits <= 32); + + context.Assembler.WriteInstructionAuto(instruction, rd, rn); + } + else + { + if (rn.Type == OperandType.I64) + { + instruction |= Assembler.SfFlag; + } + else + { + Debug.Assert(fBits <= 32); + } + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + } + + private static void GenerateScalarTernary( + CodeGenContext context, + uint sz, + uint instruction, + Operand rd, + Operand rn, + Operand rm, + Operand ra) + { + instruction |= (sz << 22); + + context.Assembler.WriteInstruction(instruction, rd, rn, rm, ra); + } + + private static void GenerateVectorUnary( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + Operand rd, + Operand rn) + { + instruction |= (q << 30) | (sz << 22); + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + private static void GenerateVectorUnaryByElem( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + uint srcIndex, + Operand rd, + Operand rn) + { + uint imm5 = (srcIndex << ((int)sz + 1)) | (1u << (int)sz); + + instruction |= (q << 30) | (imm5 << 16); + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + private static void GenerateVectorBinary( + CodeGenContext context, + uint q, + uint instruction, + Operand rd, + Operand rn, + Operand rm) + { + instruction |= (q << 30); + + context.Assembler.WriteInstructionRm16(instruction, rd, rn, rm); + } + + private static void GenerateVectorBinary( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + Operand rd, + Operand rn, + Operand rm) + { + instruction |= (q << 30) | (sz << 22); + + context.Assembler.WriteInstructionRm16(instruction, rd, rn, rm); + } + + private static void GenerateVectorBinaryByElem( + CodeGenContext context, + uint q, + uint size, + uint instruction, + uint srcIndex, + Operand rd, + Operand rn, + Operand rm) + { + instruction |= (q << 30) | (size << 22); + + if (size == 2) + { + instruction |= ((srcIndex & 1) << 21) | ((srcIndex & 2) << 10); + } + else + { + instruction |= ((srcIndex & 3) << 20) | ((srcIndex & 4) << 9); + } + + context.Assembler.WriteInstructionRm16(instruction, rd, rn, rm); + } + + private static void GenerateVectorBinaryFPByElem( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + uint srcIndex, + Operand rd, + Operand rn, + Operand rm) + { + instruction |= (q << 30) | (sz << 22); + + if (sz != 0) + { + instruction |= (srcIndex & 1) << 11; + } + else + { + instruction |= ((srcIndex & 1) << 21) | ((srcIndex & 2) << 10); + } + + context.Assembler.WriteInstructionRm16(instruction, rd, rn, rm); + } + + private static void GenerateVectorBinaryShlImm( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + Operand rd, + Operand rn, + uint shift) + { + instruction |= (q << 30); + + Debug.Assert(shift >= 0 && shift < (8u << (int)sz)); + + uint imm = (8u << (int)sz) | (shift & (0x3fu >> (int)(3 - sz))); + + instruction |= (imm << 16); + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + private static void GenerateVectorBinaryShrImm( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + Operand rd, + Operand rn, + uint shift) + { + instruction |= (q << 30); + + Debug.Assert(shift > 0 && shift <= (8u << (int)sz)); + + uint imm = (8u << (int)sz) | ((8u << (int)sz) - shift); + + instruction |= (imm << 16); + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + private static void GenerateVectorInsertByElem( + CodeGenContext context, + uint sz, + uint instruction, + uint srcIndex, + uint dstIndex, + Operand rd, + Operand rn) + { + uint imm4 = srcIndex << (int)sz; + uint imm5 = (dstIndex << ((int)sz + 1)) | (1u << (int)sz); + + instruction |= imm4 << 11; + instruction |= imm5 << 16; + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs b/src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs new file mode 100644 index 00000000..86afc2b4 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs @@ -0,0 +1,182 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Versioning; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static partial class HardwareCapabilities + { + static HardwareCapabilities() + { + if (!ArmBase.Arm64.IsSupported) + { + return; + } + + if (OperatingSystem.IsLinux()) + { + LinuxFeatureInfoHwCap = (LinuxFeatureFlagsHwCap)getauxval(AT_HWCAP); + LinuxFeatureInfoHwCap2 = (LinuxFeatureFlagsHwCap2)getauxval(AT_HWCAP2); + } + + if (OperatingSystem.IsMacOS()) + { + for (int i = 0; i < _sysctlNames.Length; i++) + { + if (CheckSysctlName(_sysctlNames[i])) + { + MacOsFeatureInfo |= (MacOsFeatureFlags)(1 << i); + } + } + } + } + + #region Linux + + private const ulong AT_HWCAP = 16; + private const ulong AT_HWCAP2 = 26; + + [LibraryImport("libc", SetLastError = true)] + private static partial ulong getauxval(ulong type); + + [Flags] + public enum LinuxFeatureFlagsHwCap : ulong + { + Fp = 1 << 0, + Asimd = 1 << 1, + Evtstrm = 1 << 2, + Aes = 1 << 3, + Pmull = 1 << 4, + Sha1 = 1 << 5, + Sha2 = 1 << 6, + Crc32 = 1 << 7, + Atomics = 1 << 8, + FpHp = 1 << 9, + AsimdHp = 1 << 10, + CpuId = 1 << 11, + AsimdRdm = 1 << 12, + Jscvt = 1 << 13, + Fcma = 1 << 14, + Lrcpc = 1 << 15, + DcpOp = 1 << 16, + Sha3 = 1 << 17, + Sm3 = 1 << 18, + Sm4 = 1 << 19, + AsimdDp = 1 << 20, + Sha512 = 1 << 21, + Sve = 1 << 22, + AsimdFhm = 1 << 23, + Dit = 1 << 24, + Uscat = 1 << 25, + Ilrcpc = 1 << 26, + FlagM = 1 << 27, + Ssbs = 1 << 28, + Sb = 1 << 29, + Paca = 1 << 30, + Pacg = 1UL << 31, + } + + [Flags] + public enum LinuxFeatureFlagsHwCap2 : ulong + { + Dcpodp = 1 << 0, + Sve2 = 1 << 1, + SveAes = 1 << 2, + SvePmull = 1 << 3, + SveBitperm = 1 << 4, + SveSha3 = 1 << 5, + SveSm4 = 1 << 6, + FlagM2 = 1 << 7, + Frint = 1 << 8, + SveI8mm = 1 << 9, + SveF32mm = 1 << 10, + SveF64mm = 1 << 11, + SveBf16 = 1 << 12, + I8mm = 1 << 13, + Bf16 = 1 << 14, + Dgh = 1 << 15, + Rng = 1 << 16, + Bti = 1 << 17, + Mte = 1 << 18, + Ecv = 1 << 19, + Afp = 1 << 20, + Rpres = 1 << 21, + Mte3 = 1 << 22, + Sme = 1 << 23, + Sme_i16i64 = 1 << 24, + Sme_f64f64 = 1 << 25, + Sme_i8i32 = 1 << 26, + Sme_f16f32 = 1 << 27, + Sme_b16f32 = 1 << 28, + Sme_f32f32 = 1 << 29, + Sme_fa64 = 1 << 30, + Wfxt = 1UL << 31, + Ebf16 = 1UL << 32, + Sve_Ebf16 = 1UL << 33, + Cssc = 1UL << 34, + Rprfm = 1UL << 35, + Sve2p1 = 1UL << 36, + } + + public static LinuxFeatureFlagsHwCap LinuxFeatureInfoHwCap { get; } = 0; + public static LinuxFeatureFlagsHwCap2 LinuxFeatureInfoHwCap2 { get; } = 0; + + #endregion + + #region macOS + + [LibraryImport("libSystem.dylib", SetLastError = true)] + private static unsafe partial int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string name, out int oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize); + + [SupportedOSPlatform("macos")] + private static bool CheckSysctlName(string name) + { + ulong size = sizeof(int); + if (sysctlbyname(name, out int val, ref size, IntPtr.Zero, 0) == 0 && size == sizeof(int)) + { + return val != 0; + } + return false; + } + + private static readonly string[] _sysctlNames = new string[] + { + "hw.optional.floatingpoint", + "hw.optional.AdvSIMD", + "hw.optional.arm.FEAT_FP16", + "hw.optional.arm.FEAT_AES", + "hw.optional.arm.FEAT_PMULL", + "hw.optional.arm.FEAT_LSE", + "hw.optional.armv8_crc32", + "hw.optional.arm.FEAT_SHA1", + "hw.optional.arm.FEAT_SHA256", + }; + + [Flags] + public enum MacOsFeatureFlags + { + Fp = 1 << 0, + AdvSimd = 1 << 1, + Fp16 = 1 << 2, + Aes = 1 << 3, + Pmull = 1 << 4, + Lse = 1 << 5, + Crc32 = 1 << 6, + Sha1 = 1 << 7, + Sha256 = 1 << 8, + } + + public static MacOsFeatureFlags MacOsFeatureInfo { get; } = 0; + + #endregion + + public static bool SupportsAdvSimd => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Asimd) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.AdvSimd); + public static bool SupportsAes => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Aes) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Aes); + public static bool SupportsPmull => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Pmull) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Pmull); + public static bool SupportsLse => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Atomics) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Lse); + public static bool SupportsCrc32 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Crc32) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Crc32); + public static bool SupportsSha1 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Sha1) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Sha1); + public static bool SupportsSha256 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Sha2) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Sha256); + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/IntrinsicInfo.cs b/src/ARMeilleure/CodeGen/Arm64/IntrinsicInfo.cs new file mode 100644 index 00000000..956fc778 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/IntrinsicInfo.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.CodeGen.Arm64 +{ + readonly struct IntrinsicInfo + { + public uint Inst { get; } + public IntrinsicType Type { get; } + + public IntrinsicInfo(uint inst, IntrinsicType type) + { + Inst = inst; + Type = type; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/IntrinsicTable.cs b/src/ARMeilleure/CodeGen/Arm64/IntrinsicTable.cs new file mode 100644 index 00000000..dbd5bdd1 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/IntrinsicTable.cs @@ -0,0 +1,465 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class IntrinsicTable + { + private static readonly IntrinsicInfo[] _intrinTable; + + static IntrinsicTable() + { + _intrinTable = new IntrinsicInfo[EnumUtils.GetCount(typeof(Intrinsic))]; + +#pragma warning disable IDE0055 // Disable formatting + Add(Intrinsic.Arm64AbsS, new IntrinsicInfo(0x5e20b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64AbsV, new IntrinsicInfo(0x0e20b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64AddhnV, new IntrinsicInfo(0x0e204000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64AddpS, new IntrinsicInfo(0x5e31b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64AddpV, new IntrinsicInfo(0x0e20bc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64AddvV, new IntrinsicInfo(0x0e31b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64AddS, new IntrinsicInfo(0x5e208400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64AddV, new IntrinsicInfo(0x0e208400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64AesdV, new IntrinsicInfo(0x4e285800u, IntrinsicType.Vector128BinaryRd)); + Add(Intrinsic.Arm64AeseV, new IntrinsicInfo(0x4e284800u, IntrinsicType.Vector128BinaryRd)); + Add(Intrinsic.Arm64AesimcV, new IntrinsicInfo(0x4e287800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64AesmcV, new IntrinsicInfo(0x4e286800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64AndV, new IntrinsicInfo(0x0e201c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64BicVi, new IntrinsicInfo(0x2f001400u, IntrinsicType.VectorBinaryBitwiseImm)); + Add(Intrinsic.Arm64BicV, new IntrinsicInfo(0x0e601c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64BifV, new IntrinsicInfo(0x2ee01c00u, IntrinsicType.VectorTernaryRdBitwise)); + Add(Intrinsic.Arm64BitV, new IntrinsicInfo(0x2ea01c00u, IntrinsicType.VectorTernaryRdBitwise)); + Add(Intrinsic.Arm64BslV, new IntrinsicInfo(0x2e601c00u, IntrinsicType.VectorTernaryRdBitwise)); + Add(Intrinsic.Arm64ClsV, new IntrinsicInfo(0x0e204800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64ClzV, new IntrinsicInfo(0x2e204800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmeqS, new IntrinsicInfo(0x7e208c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmeqV, new IntrinsicInfo(0x2e208c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmeqSz, new IntrinsicInfo(0x5e209800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmeqVz, new IntrinsicInfo(0x0e209800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmgeS, new IntrinsicInfo(0x5e203c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmgeV, new IntrinsicInfo(0x0e203c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmgeSz, new IntrinsicInfo(0x7e208800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmgeVz, new IntrinsicInfo(0x2e208800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmgtS, new IntrinsicInfo(0x5e203400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmgtV, new IntrinsicInfo(0x0e203400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmgtSz, new IntrinsicInfo(0x5e208800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmgtVz, new IntrinsicInfo(0x0e208800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmhiS, new IntrinsicInfo(0x7e203400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmhiV, new IntrinsicInfo(0x2e203400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmhsS, new IntrinsicInfo(0x7e203c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmhsV, new IntrinsicInfo(0x2e203c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmleSz, new IntrinsicInfo(0x7e209800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmleVz, new IntrinsicInfo(0x2e209800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmltSz, new IntrinsicInfo(0x5e20a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmltVz, new IntrinsicInfo(0x0e20a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmtstS, new IntrinsicInfo(0x5e208c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmtstV, new IntrinsicInfo(0x0e208c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CntV, new IntrinsicInfo(0x0e205800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64DupSe, new IntrinsicInfo(0x5e000400u, IntrinsicType.ScalarUnaryByElem)); + Add(Intrinsic.Arm64DupVe, new IntrinsicInfo(0x0e000400u, IntrinsicType.VectorUnaryByElem)); + Add(Intrinsic.Arm64DupGp, new IntrinsicInfo(0x0e000c00u, IntrinsicType.VectorUnaryByElem)); + Add(Intrinsic.Arm64EorV, new IntrinsicInfo(0x2e201c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64ExtV, new IntrinsicInfo(0x2e000000u, IntrinsicType.VectorExt)); + Add(Intrinsic.Arm64FabdS, new IntrinsicInfo(0x7ea0d400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FabdV, new IntrinsicInfo(0x2ea0d400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FabsV, new IntrinsicInfo(0x0ea0f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FabsS, new IntrinsicInfo(0x1e20c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FacgeS, new IntrinsicInfo(0x7e20ec00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FacgeV, new IntrinsicInfo(0x2e20ec00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FacgtS, new IntrinsicInfo(0x7ea0ec00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FacgtV, new IntrinsicInfo(0x2ea0ec00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FaddpS, new IntrinsicInfo(0x7e30d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FaddpV, new IntrinsicInfo(0x2e20d400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FaddV, new IntrinsicInfo(0x0e20d400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FaddS, new IntrinsicInfo(0x1e202800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FccmpeS, new IntrinsicInfo(0x1e200410u, IntrinsicType.ScalarFPCompareCond)); + Add(Intrinsic.Arm64FccmpS, new IntrinsicInfo(0x1e200400u, IntrinsicType.ScalarFPCompareCond)); + Add(Intrinsic.Arm64FcmeqS, new IntrinsicInfo(0x5e20e400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FcmeqV, new IntrinsicInfo(0x0e20e400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FcmeqSz, new IntrinsicInfo(0x5ea0d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmeqVz, new IntrinsicInfo(0x0ea0d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmgeS, new IntrinsicInfo(0x7e20e400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FcmgeV, new IntrinsicInfo(0x2e20e400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FcmgeSz, new IntrinsicInfo(0x7ea0c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmgeVz, new IntrinsicInfo(0x2ea0c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmgtS, new IntrinsicInfo(0x7ea0e400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FcmgtV, new IntrinsicInfo(0x2ea0e400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FcmgtSz, new IntrinsicInfo(0x5ea0c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmgtVz, new IntrinsicInfo(0x0ea0c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmleSz, new IntrinsicInfo(0x7ea0d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmleVz, new IntrinsicInfo(0x2ea0d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmltSz, new IntrinsicInfo(0x5ea0e800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmltVz, new IntrinsicInfo(0x0ea0e800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmpeS, new IntrinsicInfo(0x1e202010u, IntrinsicType.ScalarFPCompare)); + Add(Intrinsic.Arm64FcmpS, new IntrinsicInfo(0x1e202000u, IntrinsicType.ScalarFPCompare)); + Add(Intrinsic.Arm64FcselS, new IntrinsicInfo(0x1e200c00u, IntrinsicType.ScalarFcsel)); + Add(Intrinsic.Arm64FcvtasS, new IntrinsicInfo(0x5e21c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtasV, new IntrinsicInfo(0x0e21c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtasGp, new IntrinsicInfo(0x1e240000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtauS, new IntrinsicInfo(0x7e21c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtauV, new IntrinsicInfo(0x2e21c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtauGp, new IntrinsicInfo(0x1e250000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtlV, new IntrinsicInfo(0x0e217800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtmsS, new IntrinsicInfo(0x5e21b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtmsV, new IntrinsicInfo(0x0e21b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtmsGp, new IntrinsicInfo(0x1e300000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtmuS, new IntrinsicInfo(0x7e21b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtmuV, new IntrinsicInfo(0x2e21b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtmuGp, new IntrinsicInfo(0x1e310000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtnsS, new IntrinsicInfo(0x5e21a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtnsV, new IntrinsicInfo(0x0e21a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtnsGp, new IntrinsicInfo(0x1e200000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtnuS, new IntrinsicInfo(0x7e21a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtnuV, new IntrinsicInfo(0x2e21a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtnuGp, new IntrinsicInfo(0x1e210000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtnV, new IntrinsicInfo(0x0e216800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64FcvtpsS, new IntrinsicInfo(0x5ea1a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtpsV, new IntrinsicInfo(0x0ea1a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtpsGp, new IntrinsicInfo(0x1e280000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtpuS, new IntrinsicInfo(0x7ea1a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtpuV, new IntrinsicInfo(0x2ea1a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtpuGp, new IntrinsicInfo(0x1e290000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtxnS, new IntrinsicInfo(0x7e216800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtxnV, new IntrinsicInfo(0x2e216800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtzsSFixed, new IntrinsicInfo(0x5f00fc00u, IntrinsicType.ScalarFPConvFixed)); + Add(Intrinsic.Arm64FcvtzsVFixed, new IntrinsicInfo(0x0f00fc00u, IntrinsicType.VectorFPConvFixed)); + Add(Intrinsic.Arm64FcvtzsS, new IntrinsicInfo(0x5ea1b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtzsV, new IntrinsicInfo(0x0ea1b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtzsGpFixed, new IntrinsicInfo(0x1e180000u, IntrinsicType.ScalarFPConvFixedGpr)); + Add(Intrinsic.Arm64FcvtzsGp, new IntrinsicInfo(0x1e380000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtzuSFixed, new IntrinsicInfo(0x7f00fc00u, IntrinsicType.ScalarFPConvFixed)); + Add(Intrinsic.Arm64FcvtzuVFixed, new IntrinsicInfo(0x2f00fc00u, IntrinsicType.VectorFPConvFixed)); + Add(Intrinsic.Arm64FcvtzuS, new IntrinsicInfo(0x7ea1b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtzuV, new IntrinsicInfo(0x2ea1b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtzuGpFixed, new IntrinsicInfo(0x1e190000u, IntrinsicType.ScalarFPConvFixedGpr)); + Add(Intrinsic.Arm64FcvtzuGp, new IntrinsicInfo(0x1e390000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtS, new IntrinsicInfo(0x1e224000u, IntrinsicType.ScalarFPConv)); + Add(Intrinsic.Arm64FdivV, new IntrinsicInfo(0x2e20fc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FdivS, new IntrinsicInfo(0x1e201800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FmaddS, new IntrinsicInfo(0x1f000000u, IntrinsicType.ScalarTernary)); + Add(Intrinsic.Arm64FmaxnmpS, new IntrinsicInfo(0x7e30c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FmaxnmpV, new IntrinsicInfo(0x2e20c400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmaxnmvV, new IntrinsicInfo(0x2e30c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FmaxnmV, new IntrinsicInfo(0x0e20c400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmaxnmS, new IntrinsicInfo(0x1e206800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FmaxpS, new IntrinsicInfo(0x7e30f800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FmaxpV, new IntrinsicInfo(0x2e20f400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmaxvV, new IntrinsicInfo(0x2e30f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FmaxV, new IntrinsicInfo(0x0e20f400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmaxS, new IntrinsicInfo(0x1e204800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FminnmpS, new IntrinsicInfo(0x7eb0c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FminnmpV, new IntrinsicInfo(0x2ea0c400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FminnmvV, new IntrinsicInfo(0x2eb0c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FminnmV, new IntrinsicInfo(0x0ea0c400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FminnmS, new IntrinsicInfo(0x1e207800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FminpS, new IntrinsicInfo(0x7eb0f800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FminpV, new IntrinsicInfo(0x2ea0f400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FminvV, new IntrinsicInfo(0x2eb0f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FminV, new IntrinsicInfo(0x0ea0f400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FminS, new IntrinsicInfo(0x1e205800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FmlaSe, new IntrinsicInfo(0x5f801000u, IntrinsicType.ScalarTernaryFPRdByElem)); + Add(Intrinsic.Arm64FmlaVe, new IntrinsicInfo(0x0f801000u, IntrinsicType.VectorTernaryFPRdByElem)); + Add(Intrinsic.Arm64FmlaV, new IntrinsicInfo(0x0e20cc00u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64FmlsSe, new IntrinsicInfo(0x5f805000u, IntrinsicType.ScalarTernaryFPRdByElem)); + Add(Intrinsic.Arm64FmlsVe, new IntrinsicInfo(0x0f805000u, IntrinsicType.VectorTernaryFPRdByElem)); + Add(Intrinsic.Arm64FmlsV, new IntrinsicInfo(0x0ea0cc00u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64FmovVi, new IntrinsicInfo(0x0f00f400u, IntrinsicType.VectorFmovi)); + Add(Intrinsic.Arm64FmovS, new IntrinsicInfo(0x1e204000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FmovGp, new IntrinsicInfo(0x1e260000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FmovSi, new IntrinsicInfo(0x1e201000u, IntrinsicType.ScalarFmovi)); + Add(Intrinsic.Arm64FmsubS, new IntrinsicInfo(0x1f008000u, IntrinsicType.ScalarTernary)); + Add(Intrinsic.Arm64FmulxSe, new IntrinsicInfo(0x7f809000u, IntrinsicType.ScalarBinaryFPByElem)); + Add(Intrinsic.Arm64FmulxVe, new IntrinsicInfo(0x2f809000u, IntrinsicType.VectorBinaryFPByElem)); + Add(Intrinsic.Arm64FmulxS, new IntrinsicInfo(0x5e20dc00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FmulxV, new IntrinsicInfo(0x0e20dc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmulSe, new IntrinsicInfo(0x5f809000u, IntrinsicType.ScalarBinaryFPByElem)); + Add(Intrinsic.Arm64FmulVe, new IntrinsicInfo(0x0f809000u, IntrinsicType.VectorBinaryFPByElem)); + Add(Intrinsic.Arm64FmulV, new IntrinsicInfo(0x2e20dc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmulS, new IntrinsicInfo(0x1e200800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FnegV, new IntrinsicInfo(0x2ea0f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FnegS, new IntrinsicInfo(0x1e214000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FnmaddS, new IntrinsicInfo(0x1f200000u, IntrinsicType.ScalarTernary)); + Add(Intrinsic.Arm64FnmsubS, new IntrinsicInfo(0x1f208000u, IntrinsicType.ScalarTernary)); + Add(Intrinsic.Arm64FnmulS, new IntrinsicInfo(0x1e208800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FrecpeS, new IntrinsicInfo(0x5ea1d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrecpeV, new IntrinsicInfo(0x0ea1d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrecpsS, new IntrinsicInfo(0x5e20fc00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FrecpsV, new IntrinsicInfo(0x0e20fc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FrecpxS, new IntrinsicInfo(0x5ea1f800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintaV, new IntrinsicInfo(0x2e218800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintaS, new IntrinsicInfo(0x1e264000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintiV, new IntrinsicInfo(0x2ea19800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintiS, new IntrinsicInfo(0x1e27c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintmV, new IntrinsicInfo(0x0e219800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintmS, new IntrinsicInfo(0x1e254000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintnV, new IntrinsicInfo(0x0e218800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintnS, new IntrinsicInfo(0x1e244000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintpV, new IntrinsicInfo(0x0ea18800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintpS, new IntrinsicInfo(0x1e24c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintxV, new IntrinsicInfo(0x2e219800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintxS, new IntrinsicInfo(0x1e274000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintzV, new IntrinsicInfo(0x0ea19800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintzS, new IntrinsicInfo(0x1e25c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrsqrteS, new IntrinsicInfo(0x7ea1d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrsqrteV, new IntrinsicInfo(0x2ea1d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrsqrtsS, new IntrinsicInfo(0x5ea0fc00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FrsqrtsV, new IntrinsicInfo(0x0ea0fc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FsqrtV, new IntrinsicInfo(0x2ea1f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FsqrtS, new IntrinsicInfo(0x1e21c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FsubV, new IntrinsicInfo(0x0ea0d400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FsubS, new IntrinsicInfo(0x1e203800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64InsVe, new IntrinsicInfo(0x6e000400u, IntrinsicType.VectorInsertByElem)); + Add(Intrinsic.Arm64InsGp, new IntrinsicInfo(0x4e001c00u, IntrinsicType.ScalarUnaryByElem)); + Add(Intrinsic.Arm64Ld1rV, new IntrinsicInfo(0x0d40c000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld1Vms, new IntrinsicInfo(0x0c402000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld1Vss, new IntrinsicInfo(0x0d400000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64Ld2rV, new IntrinsicInfo(0x0d60c000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld2Vms, new IntrinsicInfo(0x0c408000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld2Vss, new IntrinsicInfo(0x0d600000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64Ld3rV, new IntrinsicInfo(0x0d40e000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld3Vms, new IntrinsicInfo(0x0c404000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld3Vss, new IntrinsicInfo(0x0d402000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64Ld4rV, new IntrinsicInfo(0x0d60e000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld4Vms, new IntrinsicInfo(0x0c400000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld4Vss, new IntrinsicInfo(0x0d602000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64MlaVe, new IntrinsicInfo(0x2f000000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64MlaV, new IntrinsicInfo(0x0e209400u, IntrinsicType.VectorTernaryRd)); + 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)); + Add(Intrinsic.Arm64MulV, new IntrinsicInfo(0x0e209c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64MvniV, new IntrinsicInfo(0x2f000400u, IntrinsicType.VectorMvni)); + Add(Intrinsic.Arm64NegS, new IntrinsicInfo(0x7e20b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64NegV, new IntrinsicInfo(0x2e20b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64NotV, new IntrinsicInfo(0x2e205800u, IntrinsicType.VectorUnaryBitwise)); + Add(Intrinsic.Arm64OrnV, new IntrinsicInfo(0x0ee01c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64OrrVi, new IntrinsicInfo(0x0f001400u, IntrinsicType.VectorBinaryBitwiseImm)); + Add(Intrinsic.Arm64OrrV, new IntrinsicInfo(0x0ea01c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64PmullV, new IntrinsicInfo(0x0e20e000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64PmulV, new IntrinsicInfo(0x2e209c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64RaddhnV, new IntrinsicInfo(0x2e204000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64RbitV, new IntrinsicInfo(0x2e605800u, IntrinsicType.VectorUnaryBitwise)); + Add(Intrinsic.Arm64Rev16V, new IntrinsicInfo(0x0e201800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64Rev32V, new IntrinsicInfo(0x2e200800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64Rev64V, new IntrinsicInfo(0x0e200800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64RshrnV, new IntrinsicInfo(0x0f008c00u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64RsubhnV, new IntrinsicInfo(0x2e206000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SabalV, new IntrinsicInfo(0x0e205000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SabaV, new IntrinsicInfo(0x0e207c00u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SabdlV, new IntrinsicInfo(0x0e207000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SabdV, new IntrinsicInfo(0x0e207400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SadalpV, new IntrinsicInfo(0x0e206800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64SaddlpV, new IntrinsicInfo(0x0e202800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SaddlvV, new IntrinsicInfo(0x0e303800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SaddlV, new IntrinsicInfo(0x0e200000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SaddwV, new IntrinsicInfo(0x0e201000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64ScvtfSFixed, new IntrinsicInfo(0x5f00e400u, IntrinsicType.ScalarFPConvFixed)); + Add(Intrinsic.Arm64ScvtfVFixed, new IntrinsicInfo(0x0f00e400u, IntrinsicType.VectorFPConvFixed)); + Add(Intrinsic.Arm64ScvtfS, new IntrinsicInfo(0x5e21d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64ScvtfV, new IntrinsicInfo(0x0e21d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64ScvtfGpFixed, new IntrinsicInfo(0x1e020000u, IntrinsicType.ScalarFPConvFixedGpr)); + Add(Intrinsic.Arm64ScvtfGp, new IntrinsicInfo(0x1e220000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64Sha1cV, new IntrinsicInfo(0x5e000000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha1hV, new IntrinsicInfo(0x5e280800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64Sha1mV, new IntrinsicInfo(0x5e002000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha1pV, new IntrinsicInfo(0x5e001000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha1su0V, new IntrinsicInfo(0x5e003000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha1su1V, new IntrinsicInfo(0x5e281800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64Sha256h2V, new IntrinsicInfo(0x5e005000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha256hV, new IntrinsicInfo(0x5e004000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha256su0V, new IntrinsicInfo(0x5e282800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64Sha256su1V, new IntrinsicInfo(0x5e006000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64ShaddV, new IntrinsicInfo(0x0e200400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64ShllV, new IntrinsicInfo(0x2e213800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64ShlS, new IntrinsicInfo(0x5f005400u, IntrinsicType.ScalarBinaryShl)); + Add(Intrinsic.Arm64ShlV, new IntrinsicInfo(0x0f005400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64ShrnV, new IntrinsicInfo(0x0f008400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64ShsubV, new IntrinsicInfo(0x0e202400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SliS, new IntrinsicInfo(0x7f005400u, IntrinsicType.ScalarTernaryShlRd)); + Add(Intrinsic.Arm64SliV, new IntrinsicInfo(0x2f005400u, IntrinsicType.VectorTernaryShlRd)); + Add(Intrinsic.Arm64SmaxpV, new IntrinsicInfo(0x0e20a400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SmaxvV, new IntrinsicInfo(0x0e30a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SmaxV, new IntrinsicInfo(0x0e206400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SminpV, new IntrinsicInfo(0x0e20ac00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SminvV, new IntrinsicInfo(0x0e31a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SminV, new IntrinsicInfo(0x0e206c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SmlalVe, new IntrinsicInfo(0x0f002000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64SmlalV, new IntrinsicInfo(0x0e208000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SmlslVe, new IntrinsicInfo(0x0f006000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64SmlslV, new IntrinsicInfo(0x0e20a000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SmovV, new IntrinsicInfo(0x0e002c00u, IntrinsicType.VectorUnaryByElem)); + Add(Intrinsic.Arm64SmullVe, new IntrinsicInfo(0x0f00a000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SmullV, new IntrinsicInfo(0x0e20c000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqabsS, new IntrinsicInfo(0x5e207800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64SqabsV, new IntrinsicInfo(0x0e207800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SqaddS, new IntrinsicInfo(0x5e200c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqaddV, new IntrinsicInfo(0x0e200c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqdmlalSe, new IntrinsicInfo(0x5f003000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqdmlalVe, new IntrinsicInfo(0x0f003000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqdmlalS, new IntrinsicInfo(0x5e209000u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqdmlalV, new IntrinsicInfo(0x0e209000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqdmlslSe, new IntrinsicInfo(0x5f007000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqdmlslVe, new IntrinsicInfo(0x0f007000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqdmlslS, new IntrinsicInfo(0x5e20b000u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqdmlslV, new IntrinsicInfo(0x0e20b000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqdmulhSe, new IntrinsicInfo(0x5f00c000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqdmulhVe, new IntrinsicInfo(0x0f00c000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqdmulhS, new IntrinsicInfo(0x5e20b400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqdmulhV, new IntrinsicInfo(0x0e20b400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqdmullSe, new IntrinsicInfo(0x5f00b000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqdmullVe, new IntrinsicInfo(0x0f00b000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqdmullS, new IntrinsicInfo(0x5e20d000u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqdmullV, new IntrinsicInfo(0x0e20d000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqnegS, new IntrinsicInfo(0x7e207800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64SqnegV, new IntrinsicInfo(0x2e207800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SqrdmulhSe, new IntrinsicInfo(0x5f00d000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqrdmulhVe, new IntrinsicInfo(0x0f00d000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqrdmulhS, new IntrinsicInfo(0x7e20b400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqrdmulhV, new IntrinsicInfo(0x2e20b400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqrshlS, new IntrinsicInfo(0x5e205c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqrshlV, new IntrinsicInfo(0x0e205c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqrshrnS, new IntrinsicInfo(0x5f009c00u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SqrshrnV, new IntrinsicInfo(0x0f009c00u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SqrshrunS, new IntrinsicInfo(0x7f008c00u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SqrshrunV, new IntrinsicInfo(0x2f008c00u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SqshluS, new IntrinsicInfo(0x7f006400u, IntrinsicType.ScalarBinaryShl)); + Add(Intrinsic.Arm64SqshluV, new IntrinsicInfo(0x2f006400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64SqshlSi, new IntrinsicInfo(0x5f007400u, IntrinsicType.ScalarBinaryShl)); + Add(Intrinsic.Arm64SqshlVi, new IntrinsicInfo(0x0f007400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64SqshlS, new IntrinsicInfo(0x5e204c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqshlV, new IntrinsicInfo(0x0e204c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqshrnS, new IntrinsicInfo(0x5f009400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SqshrnV, new IntrinsicInfo(0x0f009400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SqshrunS, new IntrinsicInfo(0x7f008400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SqshrunV, new IntrinsicInfo(0x2f008400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SqsubS, new IntrinsicInfo(0x5e202c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqsubV, new IntrinsicInfo(0x0e202c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqxtnS, new IntrinsicInfo(0x5e214800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64SqxtnV, new IntrinsicInfo(0x0e214800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64SqxtunS, new IntrinsicInfo(0x7e212800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64SqxtunV, new IntrinsicInfo(0x2e212800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64SrhaddV, new IntrinsicInfo(0x0e201400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SriS, new IntrinsicInfo(0x7f004400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SriV, new IntrinsicInfo(0x2f004400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SrshlS, new IntrinsicInfo(0x5e205400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SrshlV, new IntrinsicInfo(0x0e205400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SrshrS, new IntrinsicInfo(0x5f002400u, IntrinsicType.ScalarBinaryShr)); + Add(Intrinsic.Arm64SrshrV, new IntrinsicInfo(0x0f002400u, IntrinsicType.VectorBinaryShr)); + Add(Intrinsic.Arm64SrsraS, new IntrinsicInfo(0x5f003400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SrsraV, new IntrinsicInfo(0x0f003400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SshllV, new IntrinsicInfo(0x0f00a400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64SshlS, new IntrinsicInfo(0x5e204400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SshlV, new IntrinsicInfo(0x0e204400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SshrS, new IntrinsicInfo(0x5f000400u, IntrinsicType.ScalarBinaryShr)); + Add(Intrinsic.Arm64SshrV, new IntrinsicInfo(0x0f000400u, IntrinsicType.VectorBinaryShr)); + Add(Intrinsic.Arm64SsraS, new IntrinsicInfo(0x5f001400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SsraV, new IntrinsicInfo(0x0f001400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SsublV, new IntrinsicInfo(0x0e202000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SsubwV, new IntrinsicInfo(0x0e203000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64St1Vms, new IntrinsicInfo(0x0c002000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64St1Vss, new IntrinsicInfo(0x0d000000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64St2Vms, new IntrinsicInfo(0x0c008000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64St2Vss, new IntrinsicInfo(0x0d200000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64St3Vms, new IntrinsicInfo(0x0c004000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64St3Vss, new IntrinsicInfo(0x0d002000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64St4Vms, new IntrinsicInfo(0x0c000000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64St4Vss, new IntrinsicInfo(0x0d202000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64SubhnV, new IntrinsicInfo(0x0e206000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SubS, new IntrinsicInfo(0x7e208400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SubV, new IntrinsicInfo(0x2e208400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SuqaddS, new IntrinsicInfo(0x5e203800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64SuqaddV, new IntrinsicInfo(0x0e203800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64TblV, new IntrinsicInfo(0x0e000000u, IntrinsicType.VectorLookupTable)); + Add(Intrinsic.Arm64TbxV, new IntrinsicInfo(0x0e001000u, IntrinsicType.VectorLookupTable)); + Add(Intrinsic.Arm64Trn1V, new IntrinsicInfo(0x0e002800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64Trn2V, new IntrinsicInfo(0x0e006800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UabalV, new IntrinsicInfo(0x2e205000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64UabaV, new IntrinsicInfo(0x2e207c00u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64UabdlV, new IntrinsicInfo(0x2e207000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UabdV, new IntrinsicInfo(0x2e207400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UadalpV, new IntrinsicInfo(0x2e206800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64UaddlpV, new IntrinsicInfo(0x2e202800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UaddlvV, new IntrinsicInfo(0x2e303800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UaddlV, new IntrinsicInfo(0x2e200000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UaddwV, new IntrinsicInfo(0x2e201000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UcvtfSFixed, new IntrinsicInfo(0x7f00e400u, IntrinsicType.ScalarFPConvFixed)); + Add(Intrinsic.Arm64UcvtfVFixed, new IntrinsicInfo(0x2f00e400u, IntrinsicType.VectorFPConvFixed)); + Add(Intrinsic.Arm64UcvtfS, new IntrinsicInfo(0x7e21d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64UcvtfV, new IntrinsicInfo(0x2e21d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UcvtfGpFixed, new IntrinsicInfo(0x1e030000u, IntrinsicType.ScalarFPConvFixedGpr)); + Add(Intrinsic.Arm64UcvtfGp, new IntrinsicInfo(0x1e230000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64UhaddV, new IntrinsicInfo(0x2e200400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UhsubV, new IntrinsicInfo(0x2e202400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UmaxpV, new IntrinsicInfo(0x2e20a400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UmaxvV, new IntrinsicInfo(0x2e30a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UmaxV, new IntrinsicInfo(0x2e206400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UminpV, new IntrinsicInfo(0x2e20ac00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UminvV, new IntrinsicInfo(0x2e31a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UminV, new IntrinsicInfo(0x2e206c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UmlalVe, new IntrinsicInfo(0x2f002000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64UmlalV, new IntrinsicInfo(0x2e208000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64UmlslVe, new IntrinsicInfo(0x2f006000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64UmlslV, new IntrinsicInfo(0x2e20a000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64UmovV, new IntrinsicInfo(0x0e003c00u, IntrinsicType.VectorUnaryByElem)); + Add(Intrinsic.Arm64UmullVe, new IntrinsicInfo(0x2f00a000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64UmullV, new IntrinsicInfo(0x2e20c000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqaddS, new IntrinsicInfo(0x7e200c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UqaddV, new IntrinsicInfo(0x2e200c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqrshlS, new IntrinsicInfo(0x7e205c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UqrshlV, new IntrinsicInfo(0x2e205c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqrshrnS, new IntrinsicInfo(0x7f009c00u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64UqrshrnV, new IntrinsicInfo(0x2f009c00u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64UqshlSi, new IntrinsicInfo(0x7f007400u, IntrinsicType.ScalarBinaryShl)); + Add(Intrinsic.Arm64UqshlVi, new IntrinsicInfo(0x2f007400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64UqshlS, new IntrinsicInfo(0x7e204c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UqshlV, new IntrinsicInfo(0x2e204c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqshrnS, new IntrinsicInfo(0x7f009400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64UqshrnV, new IntrinsicInfo(0x2f009400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64UqsubS, new IntrinsicInfo(0x7e202c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UqsubV, new IntrinsicInfo(0x2e202c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqxtnS, new IntrinsicInfo(0x7e214800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64UqxtnV, new IntrinsicInfo(0x2e214800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64UrecpeV, new IntrinsicInfo(0x0ea1c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UrhaddV, new IntrinsicInfo(0x2e201400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UrshlS, new IntrinsicInfo(0x7e205400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UrshlV, new IntrinsicInfo(0x2e205400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UrshrS, new IntrinsicInfo(0x7f002400u, IntrinsicType.ScalarBinaryShr)); + Add(Intrinsic.Arm64UrshrV, new IntrinsicInfo(0x2f002400u, IntrinsicType.VectorBinaryShr)); + Add(Intrinsic.Arm64UrsqrteV, new IntrinsicInfo(0x2ea1c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UrsraS, new IntrinsicInfo(0x7f003400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64UrsraV, new IntrinsicInfo(0x2f003400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64UshllV, new IntrinsicInfo(0x2f00a400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64UshlS, new IntrinsicInfo(0x7e204400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UshlV, new IntrinsicInfo(0x2e204400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UshrS, new IntrinsicInfo(0x7f000400u, IntrinsicType.ScalarBinaryShr)); + Add(Intrinsic.Arm64UshrV, new IntrinsicInfo(0x2f000400u, IntrinsicType.VectorBinaryShr)); + Add(Intrinsic.Arm64UsqaddS, new IntrinsicInfo(0x7e203800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64UsqaddV, new IntrinsicInfo(0x2e203800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64UsraS, new IntrinsicInfo(0x7f001400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64UsraV, new IntrinsicInfo(0x2f001400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64UsublV, new IntrinsicInfo(0x2e202000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UsubwV, new IntrinsicInfo(0x2e203000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64Uzp1V, new IntrinsicInfo(0x0e001800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64Uzp2V, new IntrinsicInfo(0x0e005800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64XtnV, new IntrinsicInfo(0x0e212800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64Zip1V, new IntrinsicInfo(0x0e003800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64Zip2V, new IntrinsicInfo(0x0e007800u, IntrinsicType.VectorBinary)); +#pragma warning restore IDE0055 + } + + private static void Add(Intrinsic intrin, IntrinsicInfo info) + { + _intrinTable[(int)intrin] = info; + } + + public static IntrinsicInfo GetInfo(Intrinsic intrin) + { + return _intrinTable[(int)intrin]; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/IntrinsicType.cs b/src/ARMeilleure/CodeGen/Arm64/IntrinsicType.cs new file mode 100644 index 00000000..7538575c --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/IntrinsicType.cs @@ -0,0 +1,60 @@ +namespace ARMeilleure.CodeGen.Arm64 +{ + enum IntrinsicType + { + ScalarUnary, + ScalarUnaryByElem, + ScalarBinary, + ScalarBinaryByElem, + ScalarBinaryFPByElem, + ScalarBinaryRd, + ScalarBinaryShl, + ScalarBinaryShr, + ScalarFcsel, + ScalarFmovi, + ScalarFPCompare, + ScalarFPCompareCond, + ScalarFPConv, + ScalarFPConvFixed, + ScalarFPConvFixedGpr, + ScalarFPConvGpr, + ScalarTernary, + ScalarTernaryFPRdByElem, + ScalarTernaryShlRd, + ScalarTernaryShrRd, + + Vector128Unary, + Vector128Binary, + Vector128BinaryRd, + + VectorUnary, + VectorUnaryBitwise, + VectorUnaryByElem, + VectorBinary, + VectorBinaryBitwise, + VectorBinaryBitwiseImm, + VectorBinaryByElem, + VectorBinaryFPByElem, + VectorBinaryRd, + VectorBinaryShl, + VectorBinaryShr, + VectorExt, + VectorFmovi, + VectorFPConvFixed, + VectorInsertByElem, + VectorLdSt, + VectorLdStSs, + VectorLookupTable, + VectorMovi, + VectorMvni, + VectorTernaryFPRdByElem, + VectorTernaryRd, + VectorTernaryRdBitwise, + VectorTernaryRdByElem, + VectorTernaryShlRd, + VectorTernaryShrRd, + + GetRegister, + SetRegister, + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs b/src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs new file mode 100644 index 00000000..f66bb66e --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs @@ -0,0 +1,887 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class PreAllocator + { + private class ConstantDict + { + private readonly Dictionary<(ulong, OperandType), Operand> _constants; + + public ConstantDict() + { + _constants = new Dictionary<(ulong, OperandType), Operand>(); + } + + public void Add(ulong value, OperandType type, Operand local) + { + _constants.Add((value, type), local); + } + + public bool TryGetValue(ulong value, OperandType type, out Operand local) + { + return _constants.TryGetValue((value, type), out local); + } + } + + public static void RunPass(CompilerContext cctx, out int maxCallArgs) + { + maxCallArgs = -1; + + Span buffer = default; + + Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()]; + + for (BasicBlock block = cctx.Cfg.Blocks.First; block != null; block = block.ListNext) + { + ConstantDict constants = new(); + + Operation nextNode; + + for (Operation node = block.Operations.First; node != default; node = nextNode) + { + nextNode = node.ListNext; + + if (node.Instruction == Instruction.Phi) + { + continue; + } + + InsertConstantRegCopies(constants, block.Operations, node); + InsertDestructiveRegCopies(block.Operations, node); + + switch (node.Instruction) + { + case Instruction.Call: + // Get the maximum number of arguments used on a call. + // On windows, when a struct is returned from the call, + // we also need to pass the pointer where the struct + // should be written on the first argument. + int argsCount = node.SourcesCount - 1; + + if (node.Destination != default && node.Destination.Type == OperandType.V128) + { + argsCount++; + } + + if (maxCallArgs < argsCount) + { + maxCallArgs = argsCount; + } + + // Copy values to registers expected by the function + // being called, as mandated by the ABI. + InsertCallCopies(constants, block.Operations, node); + break; + case Instruction.CompareAndSwap: + case Instruction.CompareAndSwap16: + case Instruction.CompareAndSwap8: + nextNode = GenerateCompareAndSwap(block.Operations, node); + break; + case Instruction.LoadArgument: + nextNode = InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node); + break; + case Instruction.Return: + InsertReturnCopy(block.Operations, node); + break; + case Instruction.Tailcall: + InsertTailcallCopies(constants, block.Operations, node, node); + break; + } + } + } + } + + private static void InsertConstantRegCopies(ConstantDict constants, IntrusiveList nodes, Operation node) + { + if (node.SourcesCount == 0 || IsIntrinsicWithConst(node)) + { + return; + } + + Instruction inst = node.Instruction; + + Operand src1 = node.GetSource(0); + Operand src2; + + if (src1.Kind == OperandKind.Constant) + { + if (!src1.Type.IsInteger()) + { + // Handle non-integer types (FP32, FP64 and V128). + // For instructions without an immediate operand, we do the following: + // - Insert a copy with the constant value (as integer) to a GPR. + // - Insert a copy from the GPR to a XMM register. + // - Replace the constant use with the XMM register. + src1 = AddFloatConstantCopy(constants, nodes, node, src1); + + node.SetSource(0, src1); + } + else if (!HasConstSrc1(node, src1.Value)) + { + // Handle integer types. + // Most ALU instructions accepts a 32-bits immediate on the second operand. + // We need to ensure the following: + // - If the constant is on operand 1, we need to move it. + // -- But first, we try to swap operand 1 and 2 if the instruction is commutative. + // -- Doing so may allow us to encode the constant as operand 2 and avoid a copy. + // - If the constant is on operand 2, we check if the instruction supports it, + // if not, we also add a copy. 64-bits constants are usually not supported. + if (IsCommutative(node)) + { + src2 = node.GetSource(1); + + (src2, src1) = (src1, src2); + + node.SetSource(0, src1); + node.SetSource(1, src2); + } + + if (src1.Kind == OperandKind.Constant) + { + src1 = AddIntConstantCopy(constants, nodes, node, src1); + + node.SetSource(0, src1); + } + } + } + + if (node.SourcesCount < 2) + { + return; + } + + src2 = node.GetSource(1); + + if (src2.Kind == OperandKind.Constant) + { + if (!src2.Type.IsInteger()) + { + src2 = AddFloatConstantCopy(constants, nodes, node, src2); + + node.SetSource(1, src2); + } + else if (!HasConstSrc2(inst, src2)) + { + src2 = AddIntConstantCopy(constants, nodes, node, src2); + + node.SetSource(1, src2); + } + } + + if (node.SourcesCount < 3 || + node.Instruction == Instruction.BranchIf || + node.Instruction == Instruction.Compare || + node.Instruction == Instruction.VectorInsert || + node.Instruction == Instruction.VectorInsert16 || + node.Instruction == Instruction.VectorInsert8) + { + return; + } + + for (int srcIndex = 2; srcIndex < node.SourcesCount; srcIndex++) + { + Operand src = node.GetSource(srcIndex); + + if (src.Kind == OperandKind.Constant) + { + if (!src.Type.IsInteger()) + { + src = AddFloatConstantCopy(constants, nodes, node, src); + + node.SetSource(srcIndex, src); + } + else + { + src = AddIntConstantCopy(constants, nodes, node, src); + + node.SetSource(srcIndex, src); + } + } + } + } + + private static void InsertDestructiveRegCopies(IntrusiveList nodes, Operation node) + { + if (node.Destination == default || node.SourcesCount == 0) + { + return; + } + + Operand dest = node.Destination; + Operand src1 = node.GetSource(0); + + if (IsSameOperandDestSrc1(node) && src1.Kind == OperandKind.LocalVariable) + { + bool useNewLocal = false; + + for (int srcIndex = 1; srcIndex < node.SourcesCount; srcIndex++) + { + if (node.GetSource(srcIndex) == dest) + { + useNewLocal = true; + + break; + } + } + + if (useNewLocal) + { + // Dest is being used as some source already, we need to use a new + // local to store the temporary value, otherwise the value on dest + // local would be overwritten. + Operand temp = Local(dest.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, temp, src1)); + + node.SetSource(0, temp); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, temp)); + + node.Destination = temp; + } + else + { + nodes.AddBefore(node, Operation(Instruction.Copy, dest, src1)); + + node.SetSource(0, dest); + } + } + } + + private static void InsertCallCopies(ConstantDict constants, IntrusiveList nodes, Operation node) + { + Operation operation = node; + + Operand dest = operation.Destination; + + List sources = new() + { + operation.GetSource(0), + }; + + int argsCount = operation.SourcesCount - 1; + + int intMax = CallingConvention.GetArgumentsOnRegsCount(); + int vecMax = CallingConvention.GetArgumentsOnRegsCount(); + + int intCount = 0; + int vecCount = 0; + + int stackOffset = 0; + + for (int index = 0; index < argsCount; index++) + { + Operand source = operation.GetSource(index + 1); + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount < intMax; + } + else if (source.Type == OperandType.V128) + { + passOnReg = intCount + 1 < intMax; + } + else + { + passOnReg = vecCount < vecMax; + } + + if (source.Type == OperandType.V128 && passOnReg) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1))); + + continue; + } + + if (passOnReg) + { + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp)); + + sources.Add(argReg); + } + else + { + Operand offset = Const(stackOffset); + + Operation spillOp = Operation(Instruction.SpillArg, default, offset, source); + + InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, spillOp)); + + stackOffset += source.Type.GetSizeInBytes(); + } + } + + if (dest != default) + { + if (dest.Type == OperandType.V128) + { + Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64); + + node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, retLReg)); + nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, retHReg, Const(1))); + + operation.Destination = default; + } + else + { + Operand retReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, dest, retReg); + + nodes.AddAfter(node, copyOp); + + operation.Destination = retReg; + } + } + + operation.SetSources(sources.ToArray()); + } + + private static void InsertTailcallCopies(ConstantDict constants, + IntrusiveList nodes, + Operation node, + Operation operation) + { + List sources = new() + { + operation.GetSource(0), + }; + + int argsCount = operation.SourcesCount - 1; + + int intMax = CallingConvention.GetArgumentsOnRegsCount(); + int vecMax = CallingConvention.GetArgumentsOnRegsCount(); + + int intCount = 0; + int vecCount = 0; + + // Handle arguments passed on registers. + for (int index = 0; index < argsCount; index++) + { + Operand source = operation.GetSource(1 + index); + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount + 1 < intMax; + } + else + { + passOnReg = vecCount < vecMax; + } + + if (source.Type == OperandType.V128 && passOnReg) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1))); + + continue; + } + + if (passOnReg) + { + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp)); + + sources.Add(argReg); + } + else + { + throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)"); + } + } + + // The target address must be on the return registers, since we + // don't return anything and it is guaranteed to not be a + // callee saved register (which would be trashed on the epilogue). + Operand tcAddress = Gpr(CodeGenCommon.TcAddressRegister, OperandType.I64); + + Operation addrCopyOp = Operation(Instruction.Copy, tcAddress, operation.GetSource(0)); + + nodes.AddBefore(node, addrCopyOp); + + sources[0] = tcAddress; + + operation.SetSources(sources.ToArray()); + } + + private static Operation GenerateCompareAndSwap(IntrusiveList nodes, Operation node) + { + Operand expected = node.GetSource(1); + + if (expected.Type == OperandType.V128) + { + Operand dest = node.Destination; + Operand expectedLow = Local(OperandType.I64); + Operand expectedHigh = Local(OperandType.I64); + Operand desiredLow = Local(OperandType.I64); + Operand desiredHigh = Local(OperandType.I64); + Operand actualLow = Local(OperandType.I64); + Operand actualHigh = Local(OperandType.I64); + + Operand address = node.GetSource(0); + Operand desired = node.GetSource(2); + + void SplitOperand(Operand source, Operand low, Operand high) + { + nodes.AddBefore(node, Operation(Instruction.VectorExtract, low, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, high, source, Const(1))); + } + + SplitOperand(expected, expectedLow, expectedHigh); + SplitOperand(desired, desiredLow, desiredHigh); + + Operation operation = node; + + // Update the sources and destinations with split 64-bit halfs of the whole 128-bit values. + // We also need a additional registers that will be used to store temporary information. + operation.SetDestinations(new[] { actualLow, actualHigh, Local(OperandType.I64), Local(OperandType.I64) }); + operation.SetSources(new[] { address, expectedLow, expectedHigh, desiredLow, desiredHigh }); + + // Add some dummy uses of the input operands, as the CAS operation will be a loop, + // so they can't be used as destination operand. + for (int i = 0; i < operation.SourcesCount; i++) + { + Operand src = operation.GetSource(i); + node = nodes.AddAfter(node, Operation(Instruction.Copy, src, src)); + } + + // Assemble the vector with the 64-bit values at the given memory location. + node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, actualLow)); + node = nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, actualHigh, Const(1))); + } + else + { + // We need a additional register where the store result will be written to. + node.SetDestinations(new[] { node.Destination, Local(OperandType.I32) }); + + // Add some dummy uses of the input operands, as the CAS operation will be a loop, + // so they can't be used as destination operand. + Operation operation = node; + + for (int i = 0; i < operation.SourcesCount; i++) + { + Operand src = operation.GetSource(i); + node = nodes.AddAfter(node, Operation(Instruction.Copy, src, src)); + } + } + + return node.ListNext; + } + + private static void InsertReturnCopy(IntrusiveList nodes, Operation node) + { + if (node.SourcesCount == 0) + { + return; + } + + Operand source = node.GetSource(0); + + if (source.Type == OperandType.V128) + { + Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, retLReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, retHReg, source, Const(1))); + } + else + { + Operand retReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), source.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), source.Type); + + Operation retCopyOp = Operation(Instruction.Copy, retReg, source); + + nodes.AddBefore(node, retCopyOp); + } + } + + private static Operation InsertLoadArgumentCopy( + CompilerContext cctx, + ref Span buffer, + IntrusiveList nodes, + Operand[] preservedArgs, + Operation node) + { + Operand source = node.GetSource(0); + + Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind."); + + int index = source.AsInt32(); + + int intCount = 0; + int vecCount = 0; + + for (int cIndex = 0; cIndex < index; cIndex++) + { + OperandType argType = cctx.FuncArgTypes[cIndex]; + + if (argType.IsInteger()) + { + intCount++; + } + else if (argType == OperandType.V128) + { + intCount += 2; + } + else + { + vecCount++; + } + } + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount < CallingConvention.GetArgumentsOnRegsCount(); + } + else if (source.Type == OperandType.V128) + { + passOnReg = intCount + 1 < CallingConvention.GetArgumentsOnRegsCount(); + } + else + { + passOnReg = vecCount < CallingConvention.GetArgumentsOnRegsCount(); + } + + if (passOnReg) + { + Operand dest = node.Destination; + + if (preservedArgs[index] == default) + { + if (dest.Type == OperandType.V128) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand pArg = Local(OperandType.V128); + + Operand argLReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount), OperandType.I64); + Operand argHReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount + 1), OperandType.I64); + + Operation copyL = Operation(Instruction.VectorCreateScalar, pArg, argLReg); + Operation copyH = Operation(Instruction.VectorInsert, pArg, pArg, argHReg, Const(1)); + + cctx.Cfg.Entry.Operations.AddFirst(copyH); + cctx.Cfg.Entry.Operations.AddFirst(copyL); + + preservedArgs[index] = pArg; + } + else + { + Operand pArg = Local(dest.Type); + + Operand argReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, pArg, argReg); + + cctx.Cfg.Entry.Operations.AddFirst(copyOp); + + preservedArgs[index] = pArg; + } + } + + Operation nextNode; + + if (dest.AssignmentsCount == 1) + { + // Let's propagate the argument if we can to avoid copies. + PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]); + nextNode = node.ListNext; + } + else + { + Operation argCopyOp = Operation(Instruction.Copy, dest, preservedArgs[index]); + nextNode = nodes.AddBefore(node, argCopyOp); + } + + Delete(nodes, node); + return nextNode; + } + else + { + // TODO: Pass on stack. + return node; + } + } + + private static Operand AddFloatConstantCopy( + ConstantDict constants, + IntrusiveList nodes, + Operation node, + Operand source) + { + Operand temp = Local(source.Type); + + Operand intConst = AddIntConstantCopy(constants, nodes, node, GetIntConst(source)); + + Operation copyOp = Operation(Instruction.VectorCreateScalar, temp, intConst); + + nodes.AddBefore(node, copyOp); + + return temp; + } + + private static Operand AddIntConstantCopy( + ConstantDict constants, + IntrusiveList nodes, + Operation node, + Operand source) + { + if (constants.TryGetValue(source.Value, source.Type, out Operand temp)) + { + return temp; + } + + temp = Local(source.Type); + + Operation copyOp = Operation(Instruction.Copy, temp, source); + + nodes.AddBefore(node, copyOp); + + constants.Add(source.Value, source.Type, temp); + + return temp; + } + + private static Operand GetIntConst(Operand value) + { + if (value.Type == OperandType.FP32) + { + return Const(value.AsInt32()); + } + else if (value.Type == OperandType.FP64) + { + return Const(value.AsInt64()); + } + + return value; + } + + private static void Delete(IntrusiveList nodes, Operation node) + { + node.Destination = default; + + for (int index = 0; index < node.SourcesCount; index++) + { + node.SetSource(index, default); + } + + nodes.Remove(node); + } + + private static Operand Gpr(int register, OperandType type) + { + return Register(register, RegisterType.Integer, type); + } + + private static Operand Xmm(int register, OperandType type) + { + return Register(register, RegisterType.Vector, type); + } + + private static bool IsSameOperandDestSrc1(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Extended: + return IsSameOperandDestSrc1(operation.Intrinsic); + case Instruction.VectorInsert: + case Instruction.VectorInsert16: + case Instruction.VectorInsert8: + return true; + } + + return false; + } + + private static bool IsSameOperandDestSrc1(Intrinsic intrinsic) + { + IntrinsicInfo info = IntrinsicTable.GetInfo(intrinsic & ~(Intrinsic.Arm64VTypeMask | Intrinsic.Arm64VSizeMask)); + + return info.Type == IntrinsicType.ScalarBinaryRd || + info.Type == IntrinsicType.ScalarTernaryFPRdByElem || + info.Type == IntrinsicType.ScalarTernaryShlRd || + info.Type == IntrinsicType.ScalarTernaryShrRd || + info.Type == IntrinsicType.Vector128BinaryRd || + info.Type == IntrinsicType.VectorBinaryRd || + info.Type == IntrinsicType.VectorInsertByElem || + info.Type == IntrinsicType.VectorTernaryRd || + info.Type == IntrinsicType.VectorTernaryRdBitwise || + info.Type == IntrinsicType.VectorTernaryFPRdByElem || + info.Type == IntrinsicType.VectorTernaryRdByElem || + info.Type == IntrinsicType.VectorTernaryShlRd || + info.Type == IntrinsicType.VectorTernaryShrRd; + } + + private static bool HasConstSrc1(Operation node, ulong value) + { + switch (node.Instruction) + { + case Instruction.Add: + case Instruction.BranchIf: + case Instruction.Compare: + case Instruction.Subtract: + // The immediate encoding of those instructions does not allow Rn to be + // XZR (it will be SP instead), so we can't allow a Rn constant in this case. + return value == 0 && NotConstOrConst0(node.GetSource(1)); + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseNot: + case Instruction.BitwiseOr: + case Instruction.ByteSwap: + case Instruction.CountLeadingZeros: + case Instruction.Multiply: + case Instruction.Negate: + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + return value == 0; + case Instruction.Copy: + case Instruction.LoadArgument: + case Instruction.Spill: + case Instruction.SpillArg: + return true; + case Instruction.Extended: + return value == 0; + } + + return false; + } + + private static bool NotConstOrConst0(Operand operand) + { + return operand.Kind != OperandKind.Constant || operand.Value == 0; + } + + private static bool HasConstSrc2(Instruction inst, Operand operand) + { + ulong value = operand.Value; + + switch (inst) + { + case Instruction.Add: + case Instruction.BranchIf: + case Instruction.Compare: + case Instruction.Subtract: + return ConstFitsOnUImm12Sh(value); + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseOr: + return value == 0 || CodeGenCommon.TryEncodeBitMask(operand, out _, out _, out _); + case Instruction.Multiply: + case Instruction.Store: + case Instruction.Store16: + case Instruction.Store8: + return value == 0; + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + case Instruction.VectorExtract: + case Instruction.VectorExtract16: + case Instruction.VectorExtract8: + return true; + case Instruction.Extended: + // TODO: Check if actual intrinsic is supposed to have consts here? + // Right now we only hit this case for fixed-point int <-> FP conversion instructions. + return true; + } + + return false; + } + + private static bool IsCommutative(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Add: + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseOr: + case Instruction.Multiply: + return true; + + case Instruction.BranchIf: + case Instruction.Compare: + { + Operand comp = operation.GetSource(2); + + Debug.Assert(comp.Kind == OperandKind.Constant); + + var compType = (Comparison)comp.AsInt32(); + + return compType == Comparison.Equal || compType == Comparison.NotEqual; + } + } + + return false; + } + + private static bool ConstFitsOnUImm12Sh(ulong value) + { + return (value & ~0xfffUL) == 0 || (value & ~0xfff000UL) == 0; + } + + private static bool IsIntrinsicWithConst(Operation operation) + { + bool isIntrinsic = IsIntrinsic(operation.Instruction); + + if (isIntrinsic) + { + Intrinsic intrinsic = operation.Intrinsic; + IntrinsicInfo info = IntrinsicTable.GetInfo(intrinsic & ~(Intrinsic.Arm64VTypeMask | Intrinsic.Arm64VSizeMask)); + + // Those have integer inputs that don't support consts. + return info.Type != IntrinsicType.ScalarFPConvGpr && + info.Type != IntrinsicType.ScalarFPConvFixedGpr && + info.Type != IntrinsicType.SetRegister; + } + + return false; + } + + private static bool IsIntrinsic(Instruction inst) + { + return inst == Instruction.Extended; + } + } +} diff --git a/src/ARMeilleure/CodeGen/CompiledFunction.cs b/src/ARMeilleure/CodeGen/CompiledFunction.cs new file mode 100644 index 00000000..3844cbfc --- /dev/null +++ b/src/ARMeilleure/CodeGen/CompiledFunction.cs @@ -0,0 +1,68 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Translation.Cache; +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.CodeGen +{ + /// + /// Represents a compiled function. + /// + readonly struct CompiledFunction + { + /// + /// Gets the machine code of the . + /// + public byte[] Code { get; } + + /// + /// Gets the of the . + /// + public UnwindInfo UnwindInfo { get; } + + /// + /// Gets the of the . + /// + public RelocInfo RelocInfo { get; } + + /// + /// Initializes a new instance of the struct with the specified machine code, + /// unwind info and relocation info. + /// + /// Machine code + /// Unwind info + /// Relocation info + internal CompiledFunction(byte[] code, UnwindInfo unwindInfo, RelocInfo relocInfo) + { + Code = code; + UnwindInfo = unwindInfo; + RelocInfo = relocInfo; + } + + /// + /// Maps the onto the and returns a delegate of type + /// pointing to the mapped function. + /// + /// Type of delegate + /// A delegate of type pointing to the mapped function + public T Map() + { + return MapWithPointer(out _); + } + + /// + /// Maps the onto the and returns a delegate of type + /// pointing to the mapped function. + /// + /// Type of delegate + /// Pointer to the function code in memory + /// A delegate of type pointing to the mapped function + public T MapWithPointer(out IntPtr codePointer) + { + codePointer = JitCache.Map(this); + + return Marshal.GetDelegateForFunctionPointer(codePointer); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Linking/RelocEntry.cs b/src/ARMeilleure/CodeGen/Linking/RelocEntry.cs new file mode 100644 index 00000000..d103bc39 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Linking/RelocEntry.cs @@ -0,0 +1,38 @@ +namespace ARMeilleure.CodeGen.Linking +{ + /// + /// Represents a relocation. + /// + readonly struct RelocEntry + { + public const int Stride = 13; // Bytes. + + /// + /// Gets the position of the relocation. + /// + public int Position { get; } + + /// + /// Gets the of the relocation. + /// + public Symbol Symbol { get; } + + /// + /// Initializes a new instance of the struct with the specified position and + /// . + /// + /// Position of relocation + /// Symbol of relocation + public RelocEntry(int position, Symbol symbol) + { + Position = position; + Symbol = symbol; + } + + /// + public override string ToString() + { + return $"({nameof(Position)} = {Position}, {nameof(Symbol)} = {Symbol})"; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Linking/RelocInfo.cs b/src/ARMeilleure/CodeGen/Linking/RelocInfo.cs new file mode 100644 index 00000000..01ff0347 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Linking/RelocInfo.cs @@ -0,0 +1,32 @@ +using System; + +namespace ARMeilleure.CodeGen.Linking +{ + /// + /// Represents relocation information about a . + /// + readonly struct RelocInfo + { + /// + /// Gets an empty . + /// + public static RelocInfo Empty { get; } = new RelocInfo(null); + + private readonly RelocEntry[] _entries; + + /// + /// Gets the set of . + /// + public ReadOnlySpan Entries => _entries; + + /// + /// Initializes a new instance of the struct with the specified set of + /// . + /// + /// Set of to use + public RelocInfo(RelocEntry[] entries) + { + _entries = entries; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Linking/Symbol.cs b/src/ARMeilleure/CodeGen/Linking/Symbol.cs new file mode 100644 index 00000000..5559afe0 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Linking/Symbol.cs @@ -0,0 +1,99 @@ +using System; + +namespace ARMeilleure.CodeGen.Linking +{ + /// + /// Represents a symbol. + /// + readonly struct Symbol + { + private readonly ulong _value; + + /// + /// Gets the of the . + /// + public SymbolType Type { get; } + + /// + /// Gets the value of the . + /// + /// is + public ulong Value + { + get + { + if (Type == SymbolType.None) + { + ThrowSymbolNone(); + } + + return _value; + } + } + + /// + /// Initializes a new instance of the structure with the specified and value. + /// + /// Type of symbol + /// Value of symbol + public Symbol(SymbolType type, ulong value) + { + (Type, _value) = (type, value); + } + + /// + /// Determines if the specified instances are equal. + /// + /// First instance + /// Second instance + /// if equal; otherwise + public static bool operator ==(Symbol a, Symbol b) + { + return a.Equals(b); + } + + /// + /// Determines if the specified instances are not equal. + /// + /// First instance + /// Second instance + /// if not equal; otherwise + public static bool operator !=(Symbol a, Symbol b) + { + return !(a == b); + } + + /// + /// Determines if the specified is equal to this instance. + /// + /// Other instance + /// if equal; otherwise + public bool Equals(Symbol other) + { + return other.Type == Type && other._value == _value; + } + + /// + public override bool Equals(object obj) + { + return obj is Symbol sym && Equals(sym); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Type, _value); + } + + /// + public override string ToString() + { + return $"{Type}:{_value}"; + } + + private static void ThrowSymbolNone() + { + throw new InvalidOperationException("Symbol refers to nothing."); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Linking/SymbolType.cs b/src/ARMeilleure/CodeGen/Linking/SymbolType.cs new file mode 100644 index 00000000..29011a76 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Linking/SymbolType.cs @@ -0,0 +1,28 @@ +namespace ARMeilleure.CodeGen.Linking +{ + /// + /// Types of . + /// + enum SymbolType : byte + { + /// + /// Refers to nothing, i.e no symbol. + /// + None, + + /// + /// Refers to an entry in . + /// + DelegateTable, + + /// + /// Refers to an entry in . + /// + FunctionTable, + + /// + /// Refers to a special symbol which is handled by . + /// + Special, + } +} diff --git a/src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs b/src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs new file mode 100644 index 00000000..5f0e3772 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs @@ -0,0 +1,72 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class BlockPlacement + { + public static void RunPass(ControlFlowGraph cfg) + { + bool update = false; + + BasicBlock block; + BasicBlock nextBlock; + + BasicBlock lastBlock = cfg.Blocks.Last; + + // Move cold blocks at the end of the list, so that they are emitted away from hot code. + for (block = cfg.Blocks.First; block != null; block = nextBlock) + { + nextBlock = block.ListNext; + + if (block.Frequency == BasicBlockFrequency.Cold) + { + cfg.Blocks.Remove(block); + cfg.Blocks.AddLast(block); + } + + if (block == lastBlock) + { + break; + } + } + + for (block = cfg.Blocks.First; block != null; block = nextBlock) + { + nextBlock = block.ListNext; + + if (block.SuccessorsCount == 2) + { + Operation branchOp = block.Operations.Last; + + Debug.Assert(branchOp.Instruction == Instruction.BranchIf); + + BasicBlock falseSucc = block.GetSuccessor(0); + BasicBlock trueSucc = block.GetSuccessor(1); + + // If true successor is next block in list, invert the condition. We avoid extra branching by + // making the true side the fallthrough (i.e, convert it to the false side). + if (trueSucc == block.ListNext) + { + Comparison comp = (Comparison)branchOp.GetSource(2).AsInt32(); + Comparison compInv = comp.Invert(); + + branchOp.SetSource(2, Const((int)compInv)); + + block.SetSuccessor(0, trueSucc); + block.SetSuccessor(1, falseSucc); + + update = true; + } + } + } + + if (update) + { + cfg.Update(); + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/Optimizations/ConstantFolding.cs b/src/ARMeilleure/CodeGen/Optimizations/ConstantFolding.cs new file mode 100644 index 00000000..be3dff58 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/ConstantFolding.cs @@ -0,0 +1,346 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class ConstantFolding + { + public static void RunPass(Operation operation) + { + if (operation.Destination == default || operation.SourcesCount == 0) + { + return; + } + + if (!AreAllSourcesConstant(operation)) + { + return; + } + + OperandType type = operation.Destination.Type; + + switch (operation.Instruction) + { + case Instruction.Add: + if (operation.GetSource(0).Relocatable || + operation.GetSource(1).Relocatable) + { + break; + } + + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x + y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x + y); + } + break; + + case Instruction.BitwiseAnd: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x & y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x & y); + } + break; + + case Instruction.BitwiseExclusiveOr: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x ^ y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x ^ y); + } + break; + + case Instruction.BitwiseNot: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => ~x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => ~x); + } + break; + + case Instruction.BitwiseOr: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x | y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x | y); + } + break; + + case Instruction.ConvertI64ToI32: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => x); + } + break; + + case Instruction.Compare: + if (type == OperandType.I32 && + operation.GetSource(0).Type == type && + operation.GetSource(1).Type == type) + { + switch ((Comparison)operation.GetSource(2).Value) + { + case Comparison.Equal: + EvaluateBinaryI32(operation, (x, y) => x == y ? 1 : 0); + break; + case Comparison.NotEqual: + EvaluateBinaryI32(operation, (x, y) => x != y ? 1 : 0); + break; + case Comparison.Greater: + EvaluateBinaryI32(operation, (x, y) => x > y ? 1 : 0); + break; + case Comparison.LessOrEqual: + EvaluateBinaryI32(operation, (x, y) => x <= y ? 1 : 0); + break; + case Comparison.GreaterUI: + EvaluateBinaryI32(operation, (x, y) => (uint)x > (uint)y ? 1 : 0); + break; + case Comparison.LessOrEqualUI: + EvaluateBinaryI32(operation, (x, y) => (uint)x <= (uint)y ? 1 : 0); + break; + case Comparison.GreaterOrEqual: + EvaluateBinaryI32(operation, (x, y) => x >= y ? 1 : 0); + break; + case Comparison.Less: + EvaluateBinaryI32(operation, (x, y) => x < y ? 1 : 0); + break; + case Comparison.GreaterOrEqualUI: + EvaluateBinaryI32(operation, (x, y) => (uint)x >= (uint)y ? 1 : 0); + break; + case Comparison.LessUI: + EvaluateBinaryI32(operation, (x, y) => (uint)x < (uint)y ? 1 : 0); + break; + } + } + break; + + case Instruction.Copy: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => x); + } + break; + + case Instruction.Divide: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => y != 0 ? x / y : 0); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => y != 0 ? x / y : 0); + } + break; + + case Instruction.DivideUI: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => y != 0 ? (int)((uint)x / (uint)y) : 0); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => y != 0 ? (long)((ulong)x / (ulong)y) : 0); + } + break; + + case Instruction.Multiply: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x * y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x * y); + } + break; + + case Instruction.Negate: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => -x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => -x); + } + break; + + case Instruction.ShiftLeft: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x << y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x << (int)y); + } + break; + + case Instruction.ShiftRightSI: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x >> y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x >> (int)y); + } + break; + + case Instruction.ShiftRightUI: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => (int)((uint)x >> y)); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => (long)((ulong)x >> (int)y)); + } + break; + + case Instruction.SignExtend16: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => (short)x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => (short)x); + } + break; + + case Instruction.SignExtend32: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => (int)x); + } + break; + + case Instruction.SignExtend8: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => (sbyte)x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => (sbyte)x); + } + break; + + case Instruction.ZeroExtend16: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => (ushort)x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => (ushort)x); + } + break; + + case Instruction.ZeroExtend32: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => (uint)x); + } + break; + + case Instruction.ZeroExtend8: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => (byte)x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => (byte)x); + } + break; + + case Instruction.Subtract: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x - y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x - y); + } + break; + } + } + + private static bool AreAllSourcesConstant(Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + Operand srcOp = operation.GetSource(index); + + if (srcOp.Kind != OperandKind.Constant) + { + return false; + } + } + + return true; + } + + private static void EvaluateUnaryI32(Operation operation, Func op) + { + int x = operation.GetSource(0).AsInt32(); + + operation.TurnIntoCopy(Const(op(x))); + } + + private static void EvaluateUnaryI64(Operation operation, Func op) + { + long x = operation.GetSource(0).AsInt64(); + + operation.TurnIntoCopy(Const(op(x))); + } + + private static void EvaluateBinaryI32(Operation operation, Func op) + { + int x = operation.GetSource(0).AsInt32(); + int y = operation.GetSource(1).AsInt32(); + + operation.TurnIntoCopy(Const(op(x, y))); + } + + private static void EvaluateBinaryI64(Operation operation, Func op) + { + long x = operation.GetSource(0).AsInt64(); + long y = operation.GetSource(1).AsInt64(); + + operation.TurnIntoCopy(Const(op(x, y))); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs b/src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs new file mode 100644 index 00000000..1afc3a78 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs @@ -0,0 +1,252 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class Optimizer + { + public static void RunPass(ControlFlowGraph cfg) + { + // Scratch buffer used to store uses. + Span buffer = default; + + bool modified; + + do + { + modified = false; + + for (BasicBlock block = cfg.Blocks.Last; block != null; block = block.ListPrevious) + { + Operation node; + Operation prevNode; + + for (node = block.Operations.Last; node != default; node = prevNode) + { + prevNode = node.ListPrevious; + + if (IsUnused(node)) + { + RemoveNode(block, node); + + modified = true; + + continue; + } + else if (node.Instruction == Instruction.Phi) + { + continue; + } + + ConstantFolding.RunPass(node); + Simplification.RunPass(node); + + if (DestIsSingleLocalVar(node)) + { + if (IsPropagableCompare(node)) + { + modified |= PropagateCompare(ref buffer, node); + + if (modified && IsUnused(node)) + { + RemoveNode(block, node); + } + } + else if (IsPropagableCopy(node)) + { + PropagateCopy(ref buffer, node); + + RemoveNode(block, node); + + modified = true; + } + } + } + } + } + while (modified); + } + + public static void RemoveUnusedNodes(ControlFlowGraph cfg) + { + bool modified; + + do + { + modified = false; + + for (BasicBlock block = cfg.Blocks.Last; block != null; block = block.ListPrevious) + { + Operation node; + Operation prevNode; + + for (node = block.Operations.Last; node != default; node = prevNode) + { + prevNode = node.ListPrevious; + + if (IsUnused(node)) + { + RemoveNode(block, node); + + modified = true; + } + } + } + } + while (modified); + } + + private static bool PropagateCompare(ref Span buffer, Operation compOp) + { + // Try to propagate Compare operations into their BranchIf uses, when these BranchIf uses are in the form + // of: + // + // - BranchIf %x, 0x0, Equal ;; i.e BranchIfFalse %x + // - BranchIf %x, 0x0, NotEqual ;; i.e BranchIfTrue %x + // + // The commutative property of Equal and NotEqual is taken into consideration as well. + // + // For example: + // + // %x = Compare %a, %b, comp + // BranchIf %x, 0x0, NotEqual + // + // => + // + // BranchIf %a, %b, comp + + static bool IsZeroBranch(Operation operation, out Comparison compType) + { + compType = Comparison.Equal; + + if (operation.Instruction != Instruction.BranchIf) + { + return false; + } + + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand comp = operation.GetSource(2); + + compType = (Comparison)comp.AsInt32(); + + return (src1.Kind == OperandKind.Constant && src1.Value == 0) || + (src2.Kind == OperandKind.Constant && src2.Value == 0); + } + + bool modified = false; + + Operand dest = compOp.Destination; + Operand src1 = compOp.GetSource(0); + Operand src2 = compOp.GetSource(1); + Operand comp = compOp.GetSource(2); + + Comparison compType = (Comparison)comp.AsInt32(); + + Span uses = dest.GetUses(ref buffer); + + foreach (Operation use in uses) + { + // If operation is a BranchIf and has a constant value 0 in its RHS or LHS source operands. + if (IsZeroBranch(use, out Comparison otherCompType)) + { + Comparison propCompType; + + if (otherCompType == Comparison.NotEqual) + { + propCompType = compType; + } + else if (otherCompType == Comparison.Equal) + { + propCompType = compType.Invert(); + } + else + { + continue; + } + + use.SetSource(0, src1); + use.SetSource(1, src2); + use.SetSource(2, Const((int)propCompType)); + + modified = true; + } + } + + return modified; + } + + private static void PropagateCopy(ref Span buffer, Operation copyOp) + { + // Propagate copy source operand to all uses of the destination operand. + Operand dest = copyOp.Destination; + Operand source = copyOp.GetSource(0); + + Span uses = dest.GetUses(ref buffer); + + foreach (Operation use in uses) + { + for (int index = 0; index < use.SourcesCount; index++) + { + if (use.GetSource(index) == dest) + { + use.SetSource(index, source); + } + } + } + } + + private static void RemoveNode(BasicBlock block, Operation node) + { + // Remove a node from the nodes list, and also remove itself + // from all the use lists on the operands that this node uses. + block.Operations.Remove(node); + + for (int index = 0; index < node.SourcesCount; index++) + { + node.SetSource(index, default); + } + + Debug.Assert(node.Destination == default || node.Destination.UsesCount == 0); + + node.Destination = default; + } + + private static bool IsUnused(Operation node) + { + return DestIsSingleLocalVar(node) && node.Destination.UsesCount == 0 && !HasSideEffects(node); + } + + private static bool DestIsSingleLocalVar(Operation node) + { + return node.DestinationsCount == 1 && node.Destination.Kind == OperandKind.LocalVariable; + } + + private static bool HasSideEffects(Operation node) + { + return node.Instruction == Instruction.Call + || node.Instruction == Instruction.Tailcall + || node.Instruction == Instruction.CompareAndSwap + || node.Instruction == Instruction.CompareAndSwap16 + || node.Instruction == Instruction.CompareAndSwap8; + } + + private static bool IsPropagableCompare(Operation operation) + { + return operation.Instruction == Instruction.Compare; + } + + private static bool IsPropagableCopy(Operation operation) + { + if (operation.Instruction != Instruction.Copy) + { + return false; + } + + return operation.Destination.Type == operation.GetSource(0).Type; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Optimizations/Simplification.cs b/src/ARMeilleure/CodeGen/Optimizations/Simplification.cs new file mode 100644 index 00000000..53a7f3ed --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/Simplification.cs @@ -0,0 +1,182 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class Simplification + { + public static void RunPass(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Add: + if (operation.GetSource(0).Relocatable || + operation.GetSource(1).Relocatable) + { + break; + } + + TryEliminateBinaryOpComutative(operation, 0); + break; + + case Instruction.BitwiseAnd: + TryEliminateBitwiseAnd(operation); + break; + + case Instruction.BitwiseOr: + TryEliminateBitwiseOr(operation); + break; + + case Instruction.BitwiseExclusiveOr: + TryEliminateBitwiseExclusiveOr(operation); + break; + + case Instruction.ConditionalSelect: + TryEliminateConditionalSelect(operation); + break; + + case Instruction.Divide: + TryEliminateBinaryOpY(operation, 1); + break; + + case Instruction.Multiply: + TryEliminateBinaryOpComutative(operation, 1); + break; + + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + case Instruction.Subtract: + TryEliminateBinaryOpY(operation, 0); + break; + } + } + + private static void TryEliminateBitwiseAnd(Operation operation) + { + // Try to recognize and optimize those 3 patterns (in order): + // x & 0xFFFFFFFF == x, 0xFFFFFFFF & y == y, + // x & 0x00000000 == 0x00000000, 0x00000000 & y == 0x00000000 + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, AllOnes(x.Type))) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, AllOnes(y.Type))) + { + operation.TurnIntoCopy(x); + } + else if (IsConstEqual(x, 0) || IsConstEqual(y, 0)) + { + operation.TurnIntoCopy(Const(x.Type, 0)); + } + } + + private static void TryEliminateBitwiseOr(Operation operation) + { + // Try to recognize and optimize those 3 patterns (in order): + // x | 0x00000000 == x, 0x00000000 | y == y, + // x | 0xFFFFFFFF == 0xFFFFFFFF, 0xFFFFFFFF | y == 0xFFFFFFFF + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, 0)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, 0)) + { + operation.TurnIntoCopy(x); + } + else if (IsConstEqual(x, AllOnes(x.Type)) || IsConstEqual(y, AllOnes(y.Type))) + { + operation.TurnIntoCopy(Const(AllOnes(x.Type))); + } + } + + private static void TryEliminateBitwiseExclusiveOr(Operation operation) + { + // Try to recognize and optimize those 2 patterns (in order): + // x ^ y == 0x00000000 when x == y + // 0x00000000 ^ y == y, x ^ 0x00000000 == x + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (x == y && x.Type.IsInteger()) + { + operation.TurnIntoCopy(Const(x.Type, 0)); + } + else + { + TryEliminateBinaryOpComutative(operation, 0); + } + } + + private static void TryEliminateBinaryOpY(Operation operation, ulong comparand) + { + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(y, comparand)) + { + operation.TurnIntoCopy(x); + } + } + + private static void TryEliminateBinaryOpComutative(Operation operation, ulong comparand) + { + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, comparand)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, comparand)) + { + operation.TurnIntoCopy(x); + } + } + + private static void TryEliminateConditionalSelect(Operation operation) + { + Operand cond = operation.GetSource(0); + + if (cond.Kind != OperandKind.Constant) + { + return; + } + + // The condition is constant, we can turn it into a copy, and select + // the source based on the condition value. + int srcIndex = cond.Value != 0 ? 1 : 2; + + Operand source = operation.GetSource(srcIndex); + + operation.TurnIntoCopy(source); + } + + private static bool IsConstEqual(Operand operand, ulong comparand) + { + if (operand.Kind != OperandKind.Constant || !operand.Type.IsInteger()) + { + return false; + } + + return operand.Value == comparand; + } + + private static ulong AllOnes(OperandType type) + { + return type switch + { + OperandType.I32 => ~0U, + OperandType.I64 => ~0UL, + _ => throw new ArgumentException("Invalid operand type \"" + type + "\"."), + }; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Optimizations/TailMerge.cs b/src/ARMeilleure/CodeGen/Optimizations/TailMerge.cs new file mode 100644 index 00000000..e63c4da0 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/TailMerge.cs @@ -0,0 +1,83 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class TailMerge + { + public static void RunPass(in CompilerContext cctx) + { + ControlFlowGraph cfg = cctx.Cfg; + + BasicBlock mergedReturn = new(cfg.Blocks.Count); + + Operand returnValue; + Operation returnOp; + + if (cctx.FuncReturnType == OperandType.None) + { + returnValue = default; + returnOp = Operation(Instruction.Return, default); + } + else + { + returnValue = cfg.AllocateLocal(cctx.FuncReturnType); + returnOp = Operation(Instruction.Return, default, returnValue); + } + + mergedReturn.Frequency = BasicBlockFrequency.Cold; + mergedReturn.Operations.AddLast(returnOp); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + Operation op = block.Operations.Last; + + if (op != default && op.Instruction == Instruction.Return) + { + block.Operations.Remove(op); + + if (cctx.FuncReturnType == OperandType.None) + { + PrepareMerge(block, mergedReturn); + } + else + { + Operation copyOp = Operation(Instruction.Copy, returnValue, op.GetSource(0)); + + PrepareMerge(block, mergedReturn).Append(copyOp); + } + } + } + + cfg.Blocks.AddLast(mergedReturn); + cfg.Update(); + } + + private static BasicBlock PrepareMerge(BasicBlock from, BasicBlock to) + { + BasicBlock fromPred = from.Predecessors.Count == 1 ? from.Predecessors[0] : null; + + // If the block is empty, we can try to append to the predecessor and avoid unnecessary jumps. + if (from.Operations.Count == 0 && fromPred != null && fromPred.SuccessorsCount == 1) + { + for (int i = 0; i < fromPred.SuccessorsCount; i++) + { + if (fromPred.GetSuccessor(i) == from) + { + fromPred.SetSuccessor(i, to); + } + } + + // NOTE: `from` becomes unreachable and the call to `cfg.Update()` will remove it. + return fromPred; + } + else + { + from.AddSuccessor(to); + + return from; + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/PreAllocatorCommon.cs b/src/ARMeilleure/CodeGen/PreAllocatorCommon.cs new file mode 100644 index 00000000..53f279fb --- /dev/null +++ b/src/ARMeilleure/CodeGen/PreAllocatorCommon.cs @@ -0,0 +1,57 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen +{ + static class PreAllocatorCommon + { + public static void Propagate(ref Span buffer, Operand dest, Operand value) + { + ReadOnlySpan uses = dest.GetUses(ref buffer); + + foreach (Operation use in uses) + { + for (int srcIndex = 0; srcIndex < use.SourcesCount; srcIndex++) + { + Operand useSrc = use.GetSource(srcIndex); + + if (useSrc == dest) + { + use.SetSource(srcIndex, value); + } + else if (useSrc.Kind == OperandKind.Memory) + { + MemoryOperand memoryOp = useSrc.GetMemory(); + + Operand baseAddr = memoryOp.BaseAddress; + Operand index = memoryOp.Index; + bool changed = false; + + if (baseAddr == dest) + { + baseAddr = value; + changed = true; + } + + if (index == dest) + { + index = value; + changed = true; + } + + if (changed) + { + use.SetSource(srcIndex, MemoryOp( + useSrc.Type, + baseAddr, + index, + memoryOp.Scale, + memoryOp.Displacement)); + } + } + } + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/AllocationResult.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/AllocationResult.cs new file mode 100644 index 00000000..7b9c2f77 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/AllocationResult.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + readonly struct AllocationResult + { + public int IntUsedRegisters { get; } + public int VecUsedRegisters { get; } + public int SpillRegionSize { get; } + + public AllocationResult( + int intUsedRegisters, + int vecUsedRegisters, + int spillRegionSize) + { + IntUsedRegisters = intUsedRegisters; + VecUsedRegisters = vecUsedRegisters; + SpillRegionSize = spillRegionSize; + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs new file mode 100644 index 00000000..af10330b --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs @@ -0,0 +1,249 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + class CopyResolver + { + private class ParallelCopy + { + private readonly struct Copy + { + public Register Dest { get; } + public Register Source { get; } + + public OperandType Type { get; } + + public Copy(Register dest, Register source, OperandType type) + { + Dest = dest; + Source = source; + Type = type; + } + } + + private readonly List _copies; + + public int Count => _copies.Count; + + public ParallelCopy() + { + _copies = new List(); + } + + public void AddCopy(Register dest, Register source, OperandType type) + { + _copies.Add(new Copy(dest, source, type)); + } + + public void Sequence(List sequence) + { + Dictionary locations = new(); + Dictionary sources = new(); + + Dictionary types = new(); + + Queue pendingQueue = new(); + Queue readyQueue = new(); + + foreach (Copy copy in _copies) + { + locations[copy.Source] = copy.Source; + sources[copy.Dest] = copy.Source; + types[copy.Dest] = copy.Type; + + pendingQueue.Enqueue(copy.Dest); + } + + foreach (Copy copy in _copies) + { + // If the destination is not used anywhere, we can assign it immediately. + if (!locations.ContainsKey(copy.Dest)) + { + readyQueue.Enqueue(copy.Dest); + } + } + + while (pendingQueue.TryDequeue(out Register current)) + { + Register copyDest; + Register origSource; + Register copySource; + + while (readyQueue.TryDequeue(out copyDest)) + { + origSource = sources[copyDest]; + copySource = locations[origSource]; + + OperandType type = types[copyDest]; + + EmitCopy(sequence, GetRegister(copyDest, type), GetRegister(copySource, type)); + + locations[origSource] = copyDest; + + if (origSource == copySource && sources.ContainsKey(origSource)) + { + readyQueue.Enqueue(origSource); + } + } + + copyDest = current; + origSource = sources[copyDest]; + copySource = locations[origSource]; + + if (copyDest != copySource) + { + OperandType type = types[copyDest]; + + type = type.IsInteger() ? OperandType.I64 : OperandType.V128; + + EmitXorSwap(sequence, GetRegister(copyDest, type), GetRegister(copySource, type)); + + locations[origSource] = copyDest; + + Register swapOther = copySource; + + if (copyDest != locations[sources[copySource]]) + { + // Find the other swap destination register. + // To do that, we search all the pending registers, and pick + // the one where the copy source register is equal to the + // current destination register being processed (copyDest). + foreach (Register pending in pendingQueue) + { + // Is this a copy of pending <- copyDest? + if (copyDest == locations[sources[pending]]) + { + swapOther = pending; + + break; + } + } + } + + // The value that was previously at "copyDest" now lives on + // "copySource" thanks to the swap, now we need to update the + // location for the next copy that is supposed to copy the value + // that used to live on "copyDest". + locations[sources[swapOther]] = copySource; + } + } + } + + private static void EmitCopy(List sequence, Operand x, Operand y) + { + sequence.Add(Operation(Instruction.Copy, x, y)); + } + + private static void EmitXorSwap(List sequence, Operand x, Operand y) + { + sequence.Add(Operation(Instruction.BitwiseExclusiveOr, x, x, y)); + sequence.Add(Operation(Instruction.BitwiseExclusiveOr, y, y, x)); + sequence.Add(Operation(Instruction.BitwiseExclusiveOr, x, x, y)); + } + } + + private Queue _fillQueue = null; + private Queue _spillQueue = null; + private ParallelCopy _parallelCopy = null; + + public bool HasCopy { get; private set; } + + public void AddSplit(LiveInterval left, LiveInterval right) + { + if (left.Local != right.Local) + { + throw new ArgumentException("Intervals of different variables are not allowed."); + } + + OperandType type = left.Local.Type; + + if (left.IsSpilled && !right.IsSpilled) + { + // Move from the stack to a register. + AddSplitFill(left, right, type); + } + else if (!left.IsSpilled && right.IsSpilled) + { + // Move from a register to the stack. + AddSplitSpill(left, right, type); + } + else if (!left.IsSpilled && !right.IsSpilled && left.Register != right.Register) + { + // Move from one register to another. + AddSplitCopy(left, right, type); + } + else if (left.SpillOffset != right.SpillOffset) + { + // This would be the stack-to-stack move case, but this is not supported. + throw new ArgumentException("Both intervals were spilled."); + } + } + + private void AddSplitFill(LiveInterval left, LiveInterval right, OperandType type) + { + _fillQueue ??= new Queue(); + + Operand register = GetRegister(right.Register, type); + Operand offset = Const(left.SpillOffset); + + _fillQueue.Enqueue(Operation(Instruction.Fill, register, offset)); + + HasCopy = true; + } + + private void AddSplitSpill(LiveInterval left, LiveInterval right, OperandType type) + { + _spillQueue ??= new Queue(); + + Operand offset = Const(right.SpillOffset); + Operand register = GetRegister(left.Register, type); + + _spillQueue.Enqueue(Operation(Instruction.Spill, default, offset, register)); + + HasCopy = true; + } + + private void AddSplitCopy(LiveInterval left, LiveInterval right, OperandType type) + { + _parallelCopy ??= new ParallelCopy(); + + _parallelCopy.AddCopy(right.Register, left.Register, type); + + HasCopy = true; + } + + public Operation[] Sequence() + { + List sequence = new(); + + if (_spillQueue != null) + { + while (_spillQueue.TryDequeue(out Operation spillOp)) + { + sequence.Add(spillOp); + } + } + + _parallelCopy?.Sequence(sequence); + + if (_fillQueue != null) + { + while (_fillQueue.TryDequeue(out Operation fillOp)) + { + sequence.Add(fillOp); + } + } + + return sequence.ToArray(); + } + + private static Operand GetRegister(Register reg, OperandType type) + { + return Register(reg.Index, reg.Type, type); + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs new file mode 100644 index 00000000..5f1d6ce8 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs @@ -0,0 +1,454 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + class HybridAllocator : IRegisterAllocator + { + private readonly struct BlockInfo + { + public bool HasCall { get; } + + public int IntFixedRegisters { get; } + public int VecFixedRegisters { get; } + + public BlockInfo(bool hasCall, int intFixedRegisters, int vecFixedRegisters) + { + HasCall = hasCall; + IntFixedRegisters = intFixedRegisters; + VecFixedRegisters = vecFixedRegisters; + } + } + + private struct LocalInfo + { + public int Uses { get; set; } + public int UsesAllocated { get; set; } + public int Sequence { get; set; } + public Operand Temp { get; set; } + public Operand Register { get; set; } + public Operand SpillOffset { get; set; } + public OperandType Type { get; } + + private int _first; + private int _last; + + public readonly bool IsBlockLocal => _first == _last; + + public LocalInfo(OperandType type, int uses, int blkIndex) + { + Uses = uses; + Type = type; + + UsesAllocated = 0; + Sequence = 0; + Temp = default; + Register = default; + SpillOffset = default; + + _first = -1; + _last = -1; + + SetBlockIndex(blkIndex); + } + + public void SetBlockIndex(int blkIndex) + { + if (_first == -1 || blkIndex < _first) + { + _first = blkIndex; + } + + if (_last == -1 || blkIndex > _last) + { + _last = blkIndex; + } + } + } + + private const int MaxIROperands = 4; + // The "visited" state is stored in the MSB of the local's value. + private const ulong VisitedMask = 1ul << 63; + + private BlockInfo[] _blockInfo; + private LocalInfo[] _localInfo; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsVisited(Operand local) + { + Debug.Assert(local.Kind == OperandKind.LocalVariable); + + return (local.GetValueUnsafe() & VisitedMask) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetVisited(Operand local) + { + Debug.Assert(local.Kind == OperandKind.LocalVariable); + + local.GetValueUnsafe() |= VisitedMask; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ref LocalInfo GetLocalInfo(Operand local) + { + Debug.Assert(local.Kind == OperandKind.LocalVariable); + Debug.Assert(IsVisited(local), "Local variable not visited. Used before defined?"); + + return ref _localInfo[(uint)local.GetValueUnsafe() - 1]; + } + + public AllocationResult RunPass(ControlFlowGraph cfg, StackAllocator stackAlloc, RegisterMasks regMasks) + { + int intUsedRegisters = 0; + int vecUsedRegisters = 0; + + int intFreeRegisters = regMasks.IntAvailableRegisters; + int vecFreeRegisters = regMasks.VecAvailableRegisters; + + _blockInfo = new BlockInfo[cfg.Blocks.Count]; + _localInfo = new LocalInfo[cfg.Blocks.Count * 3]; + + int localInfoCount = 0; + + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + int intFixedRegisters = 0; + int vecFixedRegisters = 0; + + bool hasCall = false; + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + if (node.Instruction == Instruction.Call) + { + hasCall = true; + } + + foreach (Operand source in node.SourcesUnsafe) + { + if (source.Kind == OperandKind.LocalVariable) + { + GetLocalInfo(source).SetBlockIndex(block.Index); + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress != default) + { + GetLocalInfo(memOp.BaseAddress).SetBlockIndex(block.Index); + } + + if (memOp.Index != default) + { + GetLocalInfo(memOp.Index).SetBlockIndex(block.Index); + } + } + } + + foreach (Operand dest in node.DestinationsUnsafe) + { + if (dest.Kind == OperandKind.LocalVariable) + { + if (IsVisited(dest)) + { + GetLocalInfo(dest).SetBlockIndex(block.Index); + } + else + { + dest.NumberLocal(++localInfoCount); + + if (localInfoCount > _localInfo.Length) + { + Array.Resize(ref _localInfo, localInfoCount * 2); + } + + SetVisited(dest); + GetLocalInfo(dest) = new LocalInfo(dest.Type, UsesCount(dest), block.Index); + } + } + else if (dest.Kind == OperandKind.Register) + { + if (dest.Type.IsInteger()) + { + intFixedRegisters |= 1 << dest.GetRegister().Index; + } + else + { + vecFixedRegisters |= 1 << dest.GetRegister().Index; + } + } + } + } + + _blockInfo[block.Index] = new BlockInfo(hasCall, intFixedRegisters, vecFixedRegisters); + } + + int sequence = 0; + + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + ref BlockInfo blkInfo = ref _blockInfo[block.Index]; + + int intLocalFreeRegisters = intFreeRegisters & ~blkInfo.IntFixedRegisters; + int vecLocalFreeRegisters = vecFreeRegisters & ~blkInfo.VecFixedRegisters; + + int intCallerSavedRegisters = blkInfo.HasCall ? regMasks.IntCallerSavedRegisters : 0; + int vecCallerSavedRegisters = blkInfo.HasCall ? regMasks.VecCallerSavedRegisters : 0; + + int intSpillTempRegisters = SelectSpillTemps( + intCallerSavedRegisters & ~blkInfo.IntFixedRegisters, + intLocalFreeRegisters); + int vecSpillTempRegisters = SelectSpillTemps( + vecCallerSavedRegisters & ~blkInfo.VecFixedRegisters, + vecLocalFreeRegisters); + + intLocalFreeRegisters &= ~(intSpillTempRegisters | intCallerSavedRegisters); + vecLocalFreeRegisters &= ~(vecSpillTempRegisters | vecCallerSavedRegisters); + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + int intLocalUse = 0; + int vecLocalUse = 0; + + Operand AllocateRegister(Operand local) + { + ref LocalInfo info = ref GetLocalInfo(local); + + info.UsesAllocated++; + + Debug.Assert(info.UsesAllocated <= info.Uses); + + if (info.Register != default) + { + if (info.UsesAllocated == info.Uses) + { + Register reg = info.Register.GetRegister(); + + if (local.Type.IsInteger()) + { + intLocalFreeRegisters |= 1 << reg.Index; + } + else + { + vecLocalFreeRegisters |= 1 << reg.Index; + } + } + + return info.Register; + } + else + { + Operand temp = info.Temp; + + if (temp == default || info.Sequence != sequence) + { + temp = local.Type.IsInteger() + ? GetSpillTemp(local, intSpillTempRegisters, ref intLocalUse) + : GetSpillTemp(local, vecSpillTempRegisters, ref vecLocalUse); + + info.Sequence = sequence; + info.Temp = temp; + } + + Operation fillOp = Operation(Instruction.Fill, temp, info.SpillOffset); + + block.Operations.AddBefore(node, fillOp); + + return temp; + } + } + + bool folded = false; + + // If operation is a copy of a local and that local is living on the stack, we turn the copy into + // a fill, instead of inserting a fill before it. + if (node.Instruction == Instruction.Copy) + { + Operand source = node.GetSource(0); + + if (source.Kind == OperandKind.LocalVariable) + { + ref LocalInfo info = ref GetLocalInfo(source); + + if (info.Register == default) + { + Operation fillOp = Operation(Instruction.Fill, node.Destination, info.SpillOffset); + + block.Operations.AddBefore(node, fillOp); + block.Operations.Remove(node); + + node = fillOp; + + folded = true; + } + } + } + + if (!folded) + { + foreach (ref Operand source in node.SourcesUnsafe) + { + if (source.Kind == OperandKind.LocalVariable) + { + source = AllocateRegister(source); + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress != default) + { + memOp.BaseAddress = AllocateRegister(memOp.BaseAddress); + } + + if (memOp.Index != default) + { + memOp.Index = AllocateRegister(memOp.Index); + } + } + } + } + + int intLocalAsg = 0; + int vecLocalAsg = 0; + + foreach (ref Operand dest in node.DestinationsUnsafe) + { + if (dest.Kind != OperandKind.LocalVariable) + { + continue; + } + + ref LocalInfo info = ref GetLocalInfo(dest); + + if (info.UsesAllocated == 0) + { + int mask = dest.Type.IsInteger() + ? intLocalFreeRegisters + : vecLocalFreeRegisters; + + if (info.IsBlockLocal && mask != 0) + { + int selectedReg = BitOperations.TrailingZeroCount(mask); + + info.Register = Register(selectedReg, info.Type.ToRegisterType(), info.Type); + + if (dest.Type.IsInteger()) + { + intLocalFreeRegisters &= ~(1 << selectedReg); + intUsedRegisters |= 1 << selectedReg; + } + else + { + vecLocalFreeRegisters &= ~(1 << selectedReg); + vecUsedRegisters |= 1 << selectedReg; + } + } + else + { + info.Register = default; + info.SpillOffset = Const(stackAlloc.Allocate(dest.Type.GetSizeInBytes())); + } + } + + info.UsesAllocated++; + + Debug.Assert(info.UsesAllocated <= info.Uses); + + if (info.Register != default) + { + dest = info.Register; + } + else + { + Operand temp = info.Temp; + + if (temp == default || info.Sequence != sequence) + { + temp = dest.Type.IsInteger() + ? GetSpillTemp(dest, intSpillTempRegisters, ref intLocalAsg) + : GetSpillTemp(dest, vecSpillTempRegisters, ref vecLocalAsg); + + info.Sequence = sequence; + info.Temp = temp; + } + + dest = temp; + + Operation spillOp = Operation(Instruction.Spill, default, info.SpillOffset, temp); + + block.Operations.AddAfter(node, spillOp); + + node = spillOp; + } + } + + sequence++; + + intUsedRegisters |= intLocalAsg | intLocalUse; + vecUsedRegisters |= vecLocalAsg | vecLocalUse; + } + } + + return new AllocationResult(intUsedRegisters, vecUsedRegisters, stackAlloc.TotalSize); + } + + private static int SelectSpillTemps(int mask0, int mask1) + { + int selection = 0; + int count = 0; + + while (count < MaxIROperands && mask0 != 0) + { + int mask = mask0 & -mask0; + + selection |= mask; + + mask0 &= ~mask; + + count++; + } + + while (count < MaxIROperands && mask1 != 0) + { + int mask = mask1 & -mask1; + + selection |= mask; + + mask1 &= ~mask; + + count++; + } + + Debug.Assert(count == MaxIROperands, "No enough registers for spill temps."); + + return selection; + } + + private static Operand GetSpillTemp(Operand local, int freeMask, ref int useMask) + { + int selectedReg = BitOperations.TrailingZeroCount(freeMask & ~useMask); + + useMask |= 1 << selectedReg; + + return Register(selectedReg, local.Type.ToRegisterType(), local.Type); + } + + private static int UsesCount(Operand local) + { + return local.AssignmentsCount + local.UsesCount; + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/IRegisterAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/IRegisterAllocator.cs new file mode 100644 index 00000000..7d4ce2ea --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/IRegisterAllocator.cs @@ -0,0 +1,12 @@ +using ARMeilleure.Translation; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + interface IRegisterAllocator + { + AllocationResult RunPass( + ControlFlowGraph cfg, + StackAllocator stackAlloc, + RegisterMasks regMasks); + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs new file mode 100644 index 00000000..16feeb91 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs @@ -0,0 +1,1127 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Numerics; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + // Based on: + // "Linear Scan Register Allocation for the Java(tm) HotSpot Client Compiler". + // http://www.christianwimmer.at/Publications/Wimmer04a/Wimmer04a.pdf + class LinearScanAllocator : IRegisterAllocator + { + private const int InstructionGap = 2; + private const int InstructionGapMask = InstructionGap - 1; + + private HashSet _blockEdges; + private LiveRange[] _blockRanges; + private BitMap[] _blockLiveIn; + + private List _intervals; + private LiveInterval[] _parentIntervals; + + private List<(IntrusiveList, Operation)> _operationNodes; + private int _operationsCount; + + private class AllocationContext + { + public RegisterMasks Masks { get; } + + public StackAllocator StackAlloc { get; } + + public BitMap Active { get; } + public BitMap Inactive { get; } + + public int IntUsedRegisters { get; set; } + public int VecUsedRegisters { get; set; } + + private readonly int[] _intFreePositions; + private readonly int[] _vecFreePositions; + private readonly int _intFreePositionsCount; + private readonly int _vecFreePositionsCount; + + public AllocationContext(StackAllocator stackAlloc, RegisterMasks masks, int intervalsCount) + { + StackAlloc = stackAlloc; + Masks = masks; + + Active = new BitMap(Allocators.Default, intervalsCount); + Inactive = new BitMap(Allocators.Default, intervalsCount); + + PopulateFreePositions(RegisterType.Integer, out _intFreePositions, out _intFreePositionsCount); + PopulateFreePositions(RegisterType.Vector, out _vecFreePositions, out _vecFreePositionsCount); + + void PopulateFreePositions(RegisterType type, out int[] positions, out int count) + { + positions = new int[masks.RegistersCount]; + count = BitOperations.PopCount((uint)masks.GetAvailableRegisters(type)); + + int mask = masks.GetAvailableRegisters(type); + + for (int i = 0; i < positions.Length; i++) + { + if ((mask & (1 << i)) != 0) + { + positions[i] = int.MaxValue; + } + } + } + } + + public void GetFreePositions(RegisterType type, in Span positions, out int count) + { + if (type == RegisterType.Integer) + { + _intFreePositions.CopyTo(positions); + + count = _intFreePositionsCount; + } + else + { + Debug.Assert(type == RegisterType.Vector); + + _vecFreePositions.CopyTo(positions); + + count = _vecFreePositionsCount; + } + } + + public void MoveActiveToInactive(int bit) + { + Move(Active, Inactive, bit); + } + + public void MoveInactiveToActive(int bit) + { + Move(Inactive, Active, bit); + } + + private static void Move(BitMap source, BitMap dest, int bit) + { + source.Clear(bit); + + dest.Set(bit); + } + } + + public AllocationResult RunPass( + ControlFlowGraph cfg, + StackAllocator stackAlloc, + RegisterMasks regMasks) + { + NumberLocals(cfg, regMasks.RegistersCount); + + var context = new AllocationContext(stackAlloc, regMasks, _intervals.Count); + + BuildIntervals(cfg, context); + + for (int index = 0; index < _intervals.Count; index++) + { + LiveInterval current = _intervals[index]; + + if (current.IsEmpty) + { + continue; + } + + if (current.IsFixed) + { + context.Active.Set(index); + + if (current.IsFixedAndUsed) + { + if (current.Register.Type == RegisterType.Integer) + { + context.IntUsedRegisters |= 1 << current.Register.Index; + } + else /* if (interval.Register.Type == RegisterType.Vector) */ + { + context.VecUsedRegisters |= 1 << current.Register.Index; + } + } + + continue; + } + + AllocateInterval(context, current, index, regMasks.RegistersCount); + } + + for (int index = regMasks.RegistersCount * 2; index < _intervals.Count; index++) + { + if (!_intervals[index].IsSpilled) + { + ReplaceLocalWithRegister(_intervals[index]); + } + } + + InsertSplitCopies(); + InsertSplitCopiesAtEdges(cfg); + + return new AllocationResult(context.IntUsedRegisters, context.VecUsedRegisters, context.StackAlloc.TotalSize); + } + + private void AllocateInterval(AllocationContext context, LiveInterval current, int cIndex, int registersCount) + { + // Check active intervals that already ended. + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + + interval.Forward(current.GetStart()); + + if (interval.GetEnd() < current.GetStart()) + { + context.Active.Clear(iIndex); + } + else if (!interval.Overlaps(current.GetStart())) + { + context.MoveActiveToInactive(iIndex); + } + } + + // Check inactive intervals that already ended or were reactivated. + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + + interval.Forward(current.GetStart()); + + if (interval.GetEnd() < current.GetStart()) + { + context.Inactive.Clear(iIndex); + } + else if (interval.Overlaps(current.GetStart())) + { + context.MoveInactiveToActive(iIndex); + } + } + + if (!TryAllocateRegWithoutSpill(context, current, cIndex, registersCount)) + { + AllocateRegWithSpill(context, current, cIndex, registersCount); + } + } + + private bool TryAllocateRegWithoutSpill(AllocationContext context, LiveInterval current, int cIndex, int registersCount) + { + RegisterType regType = current.Local.Type.ToRegisterType(); + + Span freePositions = stackalloc int[registersCount]; + + context.GetFreePositions(regType, freePositions, out int freePositionsCount); + + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + Register reg = interval.Register; + + if (reg.Type == regType) + { + freePositions[reg.Index] = 0; + freePositionsCount--; + } + } + + // If all registers are already active, return early. No point in inspecting the inactive set to look for + // holes. + if (freePositionsCount == 0) + { + return false; + } + + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + Register reg = interval.Register; + + ref int freePosition = ref freePositions[reg.Index]; + + if (reg.Type == regType && freePosition != 0) + { + int overlapPosition = interval.GetOverlapPosition(current); + + if (overlapPosition != LiveInterval.NotFound && freePosition > overlapPosition) + { + freePosition = overlapPosition; + } + } + } + + // If this is a copy destination variable, we prefer the register used for the copy source. + // If the register is available, then the copy can be eliminated later as both source + // and destination will use the same register. + int selectedReg; + + if (current.TryGetCopySourceRegister(out int preferredReg) && freePositions[preferredReg] >= current.GetEnd()) + { + selectedReg = preferredReg; + } + else + { + selectedReg = GetHighestValueIndex(freePositions); + } + + int selectedNextUse = freePositions[selectedReg]; + + // Intervals starts and ends at odd positions, unless they span an entire + // block, in this case they will have ranges at a even position. + // When a interval is loaded from the stack to a register, we can only + // do the split at a odd position, because otherwise the split interval + // that is inserted on the list to be processed may clobber a register + // used by the instruction at the same position as the split. + // The problem only happens when a interval ends exactly at this instruction, + // because otherwise they would interfere, and the register wouldn't be selected. + // When the interval is aligned and the above happens, there's no problem as + // the instruction that is actually with the last use is the one + // before that position. + selectedNextUse &= ~InstructionGapMask; + + if (selectedNextUse <= current.GetStart()) + { + return false; + } + else if (selectedNextUse < current.GetEnd()) + { + LiveInterval splitChild = current.Split(selectedNextUse); + + if (splitChild.UsesCount != 0) + { + Debug.Assert(splitChild.GetStart() > current.GetStart(), "Split interval has an invalid start position."); + + InsertInterval(splitChild, registersCount); + } + else + { + Spill(context, splitChild); + } + } + + current.Register = new Register(selectedReg, regType); + + if (regType == RegisterType.Integer) + { + context.IntUsedRegisters |= 1 << selectedReg; + } + else /* if (regType == RegisterType.Vector) */ + { + context.VecUsedRegisters |= 1 << selectedReg; + } + + context.Active.Set(cIndex); + + return true; + } + + private void AllocateRegWithSpill(AllocationContext context, LiveInterval current, int cIndex, int registersCount) + { + RegisterType regType = current.Local.Type.ToRegisterType(); + + Span usePositions = stackalloc int[registersCount]; + Span blockedPositions = stackalloc int[registersCount]; + + context.GetFreePositions(regType, usePositions, out _); + context.GetFreePositions(regType, blockedPositions, out _); + + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + Register reg = interval.Register; + + if (reg.Type == regType) + { + ref int usePosition = ref usePositions[reg.Index]; + ref int blockedPosition = ref blockedPositions[reg.Index]; + + if (interval.IsFixed) + { + usePosition = 0; + blockedPosition = 0; + } + else + { + int nextUse = interval.NextUseAfter(current.GetStart()); + + if (nextUse != LiveInterval.NotFound && usePosition > nextUse) + { + usePosition = nextUse; + } + } + } + } + + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + Register reg = interval.Register; + + if (reg.Type == regType) + { + ref int usePosition = ref usePositions[reg.Index]; + ref int blockedPosition = ref blockedPositions[reg.Index]; + + if (interval.IsFixed) + { + int overlapPosition = interval.GetOverlapPosition(current); + + if (overlapPosition != LiveInterval.NotFound) + { + blockedPosition = Math.Min(blockedPosition, overlapPosition); + usePosition = Math.Min(usePosition, overlapPosition); + } + } + else if (interval.Overlaps(current)) + { + int nextUse = interval.NextUseAfter(current.GetStart()); + + if (nextUse != LiveInterval.NotFound && usePosition > nextUse) + { + usePosition = nextUse; + } + } + } + } + + int selectedReg = GetHighestValueIndex(usePositions); + int currentFirstUse = current.FirstUse(); + + Debug.Assert(currentFirstUse >= 0, "Current interval has no uses."); + + if (usePositions[selectedReg] < currentFirstUse) + { + // All intervals on inactive and active are being used before current, + // so spill the current interval. + Debug.Assert(currentFirstUse > current.GetStart(), "Trying to spill a interval currently being used."); + + LiveInterval splitChild = current.Split(currentFirstUse); + + Debug.Assert(splitChild.GetStart() > current.GetStart(), "Split interval has an invalid start position."); + + InsertInterval(splitChild, registersCount); + + Spill(context, current); + } + else if (blockedPositions[selectedReg] > current.GetEnd()) + { + // Spill made the register available for the entire current lifetime, + // so we only need to split the intervals using the selected register. + current.Register = new Register(selectedReg, regType); + + SplitAndSpillOverlappingIntervals(context, current, registersCount); + + context.Active.Set(cIndex); + } + else + { + // There are conflicts even after spill due to the use of fixed registers + // that can't be spilled, so we need to also split current at the point of + // the first fixed register use. + current.Register = new Register(selectedReg, regType); + + int splitPosition = blockedPositions[selectedReg] & ~InstructionGapMask; + + Debug.Assert(splitPosition > current.GetStart(), "Trying to split a interval at a invalid position."); + + LiveInterval splitChild = current.Split(splitPosition); + + if (splitChild.UsesCount != 0) + { + Debug.Assert(splitChild.GetStart() > current.GetStart(), "Split interval has an invalid start position."); + + InsertInterval(splitChild, registersCount); + } + else + { + Spill(context, splitChild); + } + + SplitAndSpillOverlappingIntervals(context, current, registersCount); + + context.Active.Set(cIndex); + } + } + + private static int GetHighestValueIndex(ReadOnlySpan span) + { + int highest = int.MinValue; + + int selected = 0; + + for (int index = 0; index < span.Length; index++) + { + int current = span[index]; + + if (highest < current) + { + highest = current; + selected = index; + + if (current == int.MaxValue) + { + break; + } + } + } + + return selected; + } + + private void SplitAndSpillOverlappingIntervals(AllocationContext context, LiveInterval current, int registersCount) + { + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + + if (!interval.IsFixed && interval.Register == current.Register) + { + SplitAndSpillOverlappingInterval(context, current, interval, registersCount); + + context.Active.Clear(iIndex); + } + } + + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + + if (!interval.IsFixed && interval.Register == current.Register && interval.Overlaps(current)) + { + SplitAndSpillOverlappingInterval(context, current, interval, registersCount); + + context.Inactive.Clear(iIndex); + } + } + } + + private void SplitAndSpillOverlappingInterval( + AllocationContext context, + LiveInterval current, + LiveInterval interval, + int registersCount) + { + // If there's a next use after the start of the current interval, + // we need to split the spilled interval twice, and re-insert it + // on the "pending" list to ensure that it will get a new register + // on that use position. + int nextUse = interval.NextUseAfter(current.GetStart()); + + LiveInterval splitChild; + + if (interval.GetStart() < current.GetStart()) + { + splitChild = interval.Split(current.GetStart()); + } + else + { + splitChild = interval; + } + + if (nextUse != -1) + { + Debug.Assert(nextUse > current.GetStart(), "Trying to spill a interval currently being used."); + + if (nextUse > splitChild.GetStart()) + { + LiveInterval right = splitChild.Split(nextUse); + + Spill(context, splitChild); + + splitChild = right; + } + + InsertInterval(splitChild, registersCount); + } + else + { + Spill(context, splitChild); + } + } + + private void InsertInterval(LiveInterval interval, int registersCount) + { + Debug.Assert(interval.UsesCount != 0, "Trying to insert a interval without uses."); + Debug.Assert(!interval.IsEmpty, "Trying to insert a empty interval."); + Debug.Assert(!interval.IsSpilled, "Trying to insert a spilled interval."); + + int startIndex = registersCount * 2; + + int insertIndex = _intervals.BinarySearch(startIndex, _intervals.Count - startIndex, interval, null); + + if (insertIndex < 0) + { + insertIndex = ~insertIndex; + } + + _intervals.Insert(insertIndex, interval); + } + + private static void Spill(AllocationContext context, LiveInterval interval) + { + Debug.Assert(!interval.IsFixed, "Trying to spill a fixed interval."); + Debug.Assert(interval.UsesCount == 0, "Trying to spill a interval with uses."); + + // We first check if any of the siblings were spilled, if so we can reuse + // the stack offset. Otherwise, we allocate a new space on the stack. + // This prevents stack-to-stack copies being necessary for a split interval. + if (!interval.TrySpillWithSiblingOffset()) + { + interval.Spill(context.StackAlloc.Allocate(interval.Local.Type)); + } + } + + private void InsertSplitCopies() + { + Dictionary copyResolvers = new(); + + CopyResolver GetCopyResolver(int position) + { + if (!copyResolvers.TryGetValue(position, out CopyResolver copyResolver)) + { + copyResolver = new CopyResolver(); + + copyResolvers.Add(position, copyResolver); + } + + return copyResolver; + } + + foreach (LiveInterval interval in _intervals.Where(x => x.IsSplit)) + { + LiveInterval previous = interval; + + foreach (LiveInterval splitChild in interval.SplitChildren()) + { + int splitPosition = splitChild.GetStart(); + + if (!_blockEdges.Contains(splitPosition) && previous.GetEnd() == splitPosition) + { + GetCopyResolver(splitPosition).AddSplit(previous, splitChild); + } + + previous = splitChild; + } + } + + foreach (KeyValuePair kv in copyResolvers) + { + CopyResolver copyResolver = kv.Value; + + if (!copyResolver.HasCopy) + { + continue; + } + + int splitPosition = kv.Key; + + (IntrusiveList nodes, Operation node) = GetOperationNode(splitPosition); + + Operation[] sequence = copyResolver.Sequence(); + + nodes.AddBefore(node, sequence[0]); + + node = sequence[0]; + + for (int index = 1; index < sequence.Length; index++) + { + nodes.AddAfter(node, sequence[index]); + + node = sequence[index]; + } + } + } + + private void InsertSplitCopiesAtEdges(ControlFlowGraph cfg) + { + int blocksCount = cfg.Blocks.Count; + + bool IsSplitEdgeBlock(BasicBlock block) + { + return block.Index >= blocksCount; + } + + // Reset iterators to beginning because GetSplitChild depends on the state of the iterator. + foreach (LiveInterval interval in _intervals) + { + interval.Reset(); + } + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + if (IsSplitEdgeBlock(block)) + { + continue; + } + + bool hasSingleOrNoSuccessor = block.SuccessorsCount <= 1; + + for (int i = 0; i < block.SuccessorsCount; i++) + { + BasicBlock successor = block.GetSuccessor(i); + + int succIndex = successor.Index; + + // If the current node is a split node, then the actual successor node + // (the successor before the split) should be right after it. + if (IsSplitEdgeBlock(successor)) + { + succIndex = successor.GetSuccessor(0).Index; + } + + CopyResolver copyResolver = null; + + foreach (int iIndex in _blockLiveIn[succIndex]) + { + LiveInterval interval = _parentIntervals[iIndex]; + + if (!interval.IsSplit) + { + continue; + } + + int lEnd = _blockRanges[block.Index].End - 1; + int rStart = _blockRanges[succIndex].Start; + + LiveInterval left = interval.GetSplitChild(lEnd); + LiveInterval right = interval.GetSplitChild(rStart); + + if (left != default && right != default && left != right) + { + copyResolver ??= new CopyResolver(); + + copyResolver.AddSplit(left, right); + } + } + + if (copyResolver == null || !copyResolver.HasCopy) + { + continue; + } + + Operation[] sequence = copyResolver.Sequence(); + + if (hasSingleOrNoSuccessor) + { + foreach (Operation operation in sequence) + { + block.Append(operation); + } + } + else if (successor.Predecessors.Count == 1) + { + successor.Operations.AddFirst(sequence[0]); + + Operation prependNode = sequence[0]; + + for (int index = 1; index < sequence.Length; index++) + { + Operation operation = sequence[index]; + + successor.Operations.AddAfter(prependNode, operation); + + prependNode = operation; + } + } + else + { + // Split the critical edge. + BasicBlock splitBlock = cfg.SplitEdge(block, successor); + + foreach (Operation operation in sequence) + { + splitBlock.Append(operation); + } + } + } + } + } + + private void ReplaceLocalWithRegister(LiveInterval current) + { + Operand register = GetRegister(current); + + foreach (int usePosition in current.UsePositions()) + { + (_, Operation operation) = GetOperationNode(usePosition); + + for (int index = 0; index < operation.SourcesCount; index++) + { + Operand source = operation.GetSource(index); + + if (source == current.Local) + { + operation.SetSource(index, register); + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress == current.Local) + { + memOp.BaseAddress = register; + } + + if (memOp.Index == current.Local) + { + memOp.Index = register; + } + } + } + + for (int index = 0; index < operation.DestinationsCount; index++) + { + Operand dest = operation.GetDestination(index); + + if (dest == current.Local) + { + operation.SetDestination(index, register); + } + } + } + } + + private static Operand GetRegister(LiveInterval interval) + { + Debug.Assert(!interval.IsSpilled, "Spilled intervals are not allowed."); + + return Operand.Factory.Register( + interval.Register.Index, + interval.Register.Type, + interval.Local.Type); + } + + private (IntrusiveList, Operation) GetOperationNode(int position) + { + return _operationNodes[position / InstructionGap]; + } + + private void NumberLocals(ControlFlowGraph cfg, int registersCount) + { + _operationNodes = new List<(IntrusiveList, Operation)>(); + _intervals = new List(); + + for (int index = 0; index < registersCount; index++) + { + _intervals.Add(new LiveInterval(new Register(index, RegisterType.Integer))); + _intervals.Add(new LiveInterval(new Register(index, RegisterType.Vector))); + } + + // The "visited" state is stored in the MSB of the local's value. + const ulong VisitedMask = 1ul << 63; + + static bool IsVisited(Operand local) + { + return (local.GetValueUnsafe() & VisitedMask) != 0; + } + + static void SetVisited(Operand local) + { + local.GetValueUnsafe() |= VisitedMask; + } + + _operationsCount = 0; + + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + _operationNodes.Add((block.Operations, node)); + + for (int i = 0; i < node.DestinationsCount; i++) + { + Operand dest = node.GetDestination(i); + + if (dest.Kind == OperandKind.LocalVariable && !IsVisited(dest)) + { + dest.NumberLocal(_intervals.Count); + + LiveInterval interval = new LiveInterval(dest); + _intervals.Add(interval); + + SetVisited(dest); + + // If this is a copy (or copy-like operation), set the copy source interval as well. + // This is used for register preferencing later on, which allows the copy to be eliminated + // in some cases. + if (node.Instruction == Instruction.Copy || node.Instruction == Instruction.ZeroExtend32) + { + Operand source = node.GetSource(0); + + if (source.Kind == OperandKind.LocalVariable && + source.GetLocalNumber() > 0 && + (node.Instruction == Instruction.Copy || source.Type == OperandType.I32)) + { + interval.SetCopySource(_intervals[source.GetLocalNumber()]); + } + } + } + } + } + + _operationsCount += block.Operations.Count * InstructionGap; + + if (block.Operations.Count == 0) + { + // Pretend we have a dummy instruction on the empty block. + _operationNodes.Add((default, default)); + + _operationsCount += InstructionGap; + } + } + + _parentIntervals = _intervals.ToArray(); + } + + private void BuildIntervals(ControlFlowGraph cfg, AllocationContext context) + { + _blockRanges = new LiveRange[cfg.Blocks.Count]; + + int mapSize = _intervals.Count; + + BitMap[] blkLiveGen = new BitMap[cfg.Blocks.Count]; + BitMap[] blkLiveKill = new BitMap[cfg.Blocks.Count]; + + // Compute local live sets. + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + BitMap liveGen = new(Allocators.Default, mapSize); + BitMap liveKill = new(Allocators.Default, mapSize); + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + for (int i = 0; i < node.SourcesCount; i++) + { + VisitSource(node.GetSource(i)); + } + + for (int i = 0; i < node.DestinationsCount; i++) + { + VisitDestination(node.GetDestination(i)); + } + + void VisitSource(Operand source) + { + if (IsLocalOrRegister(source.Kind)) + { + int id = GetOperandId(source); + + if (!liveKill.IsSet(id)) + { + liveGen.Set(id); + } + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress != default) + { + VisitSource(memOp.BaseAddress); + } + + if (memOp.Index != default) + { + VisitSource(memOp.Index); + } + } + } + + void VisitDestination(Operand dest) + { + liveKill.Set(GetOperandId(dest)); + } + } + + blkLiveGen[block.Index] = liveGen; + blkLiveKill[block.Index] = liveKill; + } + + // Compute global live sets. + BitMap[] blkLiveIn = new BitMap[cfg.Blocks.Count]; + BitMap[] blkLiveOut = new BitMap[cfg.Blocks.Count]; + + for (int index = 0; index < cfg.Blocks.Count; index++) + { + blkLiveIn[index] = new BitMap(Allocators.Default, mapSize); + blkLiveOut[index] = new BitMap(Allocators.Default, mapSize); + } + + bool modified; + + do + { + modified = false; + + for (int index = 0; index < cfg.PostOrderBlocks.Length; index++) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + BitMap liveOut = blkLiveOut[block.Index]; + + for (int i = 0; i < block.SuccessorsCount; i++) + { + BasicBlock succ = block.GetSuccessor(i); + + modified |= liveOut.Set(blkLiveIn[succ.Index]); + } + + BitMap liveIn = blkLiveIn[block.Index]; + + liveIn.Set(liveOut); + liveIn.Clear(blkLiveKill[block.Index]); + liveIn.Set(blkLiveGen[block.Index]); + } + } + while (modified); + + _blockLiveIn = blkLiveIn; + + _blockEdges = new HashSet(); + + // Compute lifetime intervals. + int operationPos = _operationsCount; + + for (int index = 0; index < cfg.PostOrderBlocks.Length; index++) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + // We handle empty blocks by pretending they have a dummy instruction, + // because otherwise the block would have the same start and end position, + // and this is not valid. + int instCount = Math.Max(block.Operations.Count, 1); + + int blockStart = operationPos - instCount * InstructionGap; + int blockEnd = operationPos; + + _blockRanges[block.Index] = new LiveRange(blockStart, blockEnd); + + _blockEdges.Add(blockStart); + + BitMap liveOut = blkLiveOut[block.Index]; + + foreach (int id in liveOut) + { + _intervals[id].AddRange(blockStart, blockEnd); + } + + if (block.Operations.Count == 0) + { + operationPos -= InstructionGap; + + continue; + } + + for (Operation node = block.Operations.Last; node != default; node = node.ListPrevious) + { + operationPos -= InstructionGap; + + for (int i = 0; i < node.DestinationsCount; i++) + { + VisitDestination(node.GetDestination(i)); + } + + for (int i = 0; i < node.SourcesCount; i++) + { + VisitSource(node.GetSource(i)); + } + + if (node.Instruction == Instruction.Call) + { + AddIntervalCallerSavedReg(context.Masks.IntCallerSavedRegisters, operationPos, RegisterType.Integer); + AddIntervalCallerSavedReg(context.Masks.VecCallerSavedRegisters, operationPos, RegisterType.Vector); + } + + void VisitSource(Operand source) + { + if (IsLocalOrRegister(source.Kind)) + { + LiveInterval interval = _intervals[GetOperandId(source)]; + + interval.AddRange(blockStart, operationPos + 1); + interval.AddUsePosition(operationPos); + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress != default) + { + VisitSource(memOp.BaseAddress); + } + + if (memOp.Index != default) + { + VisitSource(memOp.Index); + } + } + } + + void VisitDestination(Operand dest) + { + LiveInterval interval = _intervals[GetOperandId(dest)]; + + if (interval.IsFixed) + { + interval.IsFixedAndUsed = true; + } + + interval.SetStart(operationPos + 1); + interval.AddUsePosition(operationPos + 1); + } + } + } + + foreach (LiveInterval interval in _parentIntervals) + { + interval.Reset(); + } + } + + private void AddIntervalCallerSavedReg(int mask, int operationPos, RegisterType regType) + { + while (mask != 0) + { + int regIndex = BitOperations.TrailingZeroCount(mask); + + Register callerSavedReg = new(regIndex, regType); + + LiveInterval interval = _intervals[GetRegisterId(callerSavedReg)]; + + interval.AddRange(operationPos + 1, operationPos + InstructionGap); + + mask &= ~(1 << regIndex); + } + } + + private static int GetOperandId(Operand operand) + { + if (operand.Kind == OperandKind.LocalVariable) + { + return operand.GetLocalNumber(); + } + else if (operand.Kind == OperandKind.Register) + { + return GetRegisterId(operand.GetRegister()); + } + else + { + throw new ArgumentException($"Invalid operand kind \"{operand.Kind}\"."); + } + } + + private static int GetRegisterId(Register register) + { + return (register.Index << 1) | (register.Type == RegisterType.Vector ? 1 : 0); + } + + private static bool IsLocalOrRegister(OperandKind kind) + { + return kind == OperandKind.LocalVariable || + kind == OperandKind.Register; + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs new file mode 100644 index 00000000..cfe1bc7c --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs @@ -0,0 +1,419 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + unsafe readonly struct LiveInterval : IComparable + { + public const int NotFound = -1; + + private struct Data + { + public int End; + public int SpillOffset; + + public LiveRange FirstRange; + public LiveRange PrevRange; + public LiveRange CurrRange; + + public LiveInterval Parent; + public LiveInterval CopySource; + + public UseList Uses; + public LiveIntervalList Children; + + public Operand Local; + public Register Register; + + public bool IsFixed; + public bool IsFixedAndUsed; + } + + private readonly Data* _data; + + private ref int End => ref _data->End; + private ref LiveRange FirstRange => ref _data->FirstRange; + private ref LiveRange CurrRange => ref _data->CurrRange; + private ref LiveRange PrevRange => ref _data->PrevRange; + private ref LiveInterval Parent => ref _data->Parent; + private ref LiveInterval CopySource => ref _data->CopySource; + private ref UseList Uses => ref _data->Uses; + private ref LiveIntervalList Children => ref _data->Children; + + public Operand Local => _data->Local; + public ref Register Register => ref _data->Register; + public ref int SpillOffset => ref _data->SpillOffset; + + public bool IsFixed => _data->IsFixed; + public ref bool IsFixedAndUsed => ref _data->IsFixedAndUsed; + public bool IsEmpty => FirstRange == default; + public bool IsSplit => Children.Count != 0; + public bool IsSpilled => SpillOffset != -1; + + public int UsesCount => Uses.Count; + + public LiveInterval(Operand local = default, LiveInterval parent = default) + { + _data = Allocators.LiveIntervals.Allocate(); + *_data = default; + + _data->IsFixed = false; + _data->Local = local; + + Parent = parent == default ? this : parent; + Uses = new UseList(); + Children = new LiveIntervalList(); + + FirstRange = default; + CurrRange = default; + PrevRange = default; + + SpillOffset = -1; + } + + public LiveInterval(Register register) : this(local: default, parent: default) + { + _data->IsFixed = true; + + Register = register; + } + + public void SetCopySource(LiveInterval copySource) + { + CopySource = copySource; + } + + public bool TryGetCopySourceRegister(out int copySourceRegIndex) + { + if (CopySource._data != null) + { + copySourceRegIndex = CopySource.Register.Index; + + return true; + } + + copySourceRegIndex = 0; + + return false; + } + + public void Reset() + { + PrevRange = default; + CurrRange = FirstRange; + } + + public void Forward(int position) + { + LiveRange prev = PrevRange; + LiveRange curr = CurrRange; + + while (curr != default && curr.Start < position && !curr.Overlaps(position)) + { + prev = curr; + curr = curr.Next; + } + + PrevRange = prev; + CurrRange = curr; + } + + public int GetStart() + { + Debug.Assert(!IsEmpty, "Empty LiveInterval cannot have a start position."); + + return FirstRange.Start; + } + + public void SetStart(int position) + { + if (FirstRange != default) + { + Debug.Assert(position != FirstRange.End); + + FirstRange.Start = position; + } + else + { + FirstRange = new LiveRange(position, position + 1); + End = position + 1; + } + } + + public int GetEnd() + { + Debug.Assert(!IsEmpty, "Empty LiveInterval cannot have an end position."); + + return End; + } + + public void AddRange(int start, int end) + { + Debug.Assert(start < end, $"Invalid range start position {start}, {end}"); + + if (FirstRange != default) + { + // If the new range ends exactly where the first range start, then coalesce together. + if (end == FirstRange.Start) + { + FirstRange.Start = start; + + return; + } + // If the new range is already contained, then coalesce together. + else if (FirstRange.Overlaps(start, end)) + { + FirstRange.Start = Math.Min(FirstRange.Start, start); + FirstRange.End = Math.Max(FirstRange.End, end); + End = Math.Max(End, end); + + Debug.Assert(FirstRange.Next == default || !FirstRange.Overlaps(FirstRange.Next)); + return; + } + } + + FirstRange = new LiveRange(start, end, FirstRange); + End = Math.Max(End, end); + + Debug.Assert(FirstRange.Next == default || !FirstRange.Overlaps(FirstRange.Next)); + } + + public void AddUsePosition(int position) + { + Uses.Add(position); + } + + public bool Overlaps(int position) + { + LiveRange curr = CurrRange; + + while (curr != default && curr.Start <= position) + { + if (curr.Overlaps(position)) + { + return true; + } + + curr = curr.Next; + } + + return false; + } + + public bool Overlaps(LiveInterval other) + { + return GetOverlapPosition(other) != NotFound; + } + + public int GetOverlapPosition(LiveInterval other) + { + LiveRange a = CurrRange; + LiveRange b = other.CurrRange; + + while (a != default) + { + while (b != default && b.Start < a.Start) + { + if (a.Overlaps(b)) + { + return a.Start; + } + + b = b.Next; + } + + if (b == default) + { + break; + } + else if (a.Overlaps(b)) + { + return a.Start; + } + + a = a.Next; + } + + return NotFound; + } + + public ReadOnlySpan SplitChildren() + { + return Parent.Children.Span; + } + + public ReadOnlySpan UsePositions() + { + return Uses.Span; + } + + public int FirstUse() + { + return Uses.FirstUse; + } + + public int NextUseAfter(int position) + { + return Uses.NextUse(position); + } + + public LiveInterval Split(int position) + { + LiveInterval result = new(Local, Parent) + { + End = End, + }; + + LiveRange prev = PrevRange; + LiveRange curr = CurrRange; + + while (curr != default && curr.Start < position && !curr.Overlaps(position)) + { + prev = curr; + curr = curr.Next; + } + + if (curr.Start >= position) + { + prev.Next = default; + + result.FirstRange = curr; + + End = prev.End; + } + else + { + result.FirstRange = new LiveRange(position, curr.End, curr.Next); + + curr.End = position; + curr.Next = default; + + End = curr.End; + } + + result.Uses = Uses.Split(position); + + AddSplitChild(result); + + Debug.Assert(!IsEmpty, "Left interval is empty after split."); + Debug.Assert(!result.IsEmpty, "Right interval is empty after split."); + + // Make sure the iterator in the new split is pointing to the start. + result.Reset(); + + return result; + } + + private void AddSplitChild(LiveInterval child) + { + Debug.Assert(!child.IsEmpty, "Trying to insert an empty interval."); + + Parent.Children.Add(child); + } + + public LiveInterval GetSplitChild(int position) + { + if (Overlaps(position)) + { + return this; + } + + foreach (LiveInterval splitChild in SplitChildren()) + { + if (splitChild.Overlaps(position)) + { + return splitChild; + } + else if (splitChild.GetStart() > position) + { + break; + } + } + + return default; + } + + public bool TrySpillWithSiblingOffset() + { + foreach (LiveInterval splitChild in SplitChildren()) + { + if (splitChild.IsSpilled) + { + Spill(splitChild.SpillOffset); + + return true; + } + } + + return false; + } + + public void Spill(int offset) + { + SpillOffset = offset; + } + + public int CompareTo(LiveInterval interval) + { + if (FirstRange == default || interval.FirstRange == default) + { + return 0; + } + + return GetStart().CompareTo(interval.GetStart()); + } + + public bool Equals(LiveInterval interval) + { + return interval._data == _data; + } + + public override bool Equals(object obj) + { + return obj is LiveInterval interval && Equals(interval); + } + + public static bool operator ==(LiveInterval a, LiveInterval b) + { + return a.Equals(b); + } + + public static bool operator !=(LiveInterval a, LiveInterval b) + { + return !a.Equals(b); + } + + public override int GetHashCode() + { + return HashCode.Combine((IntPtr)_data); + } + + public override string ToString() + { + LiveInterval self = this; + + IEnumerable GetRanges() + { + LiveRange curr = self.CurrRange; + + while (curr != default) + { + if (curr == self.CurrRange) + { + yield return "*" + curr; + } + else + { + yield return curr.ToString(); + } + + curr = curr.Next; + } + } + + return string.Join(", ", GetRanges()); + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveIntervalList.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveIntervalList.cs new file mode 100644 index 00000000..84b892f4 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveIntervalList.cs @@ -0,0 +1,40 @@ +using System; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + unsafe struct LiveIntervalList + { + private LiveInterval* _items; + private int _count; + private int _capacity; + + public readonly int Count => _count; + public readonly Span Span => new(_items, _count); + + public void Add(LiveInterval interval) + { + if (_count + 1 > _capacity) + { + var oldSpan = Span; + + _capacity = Math.Max(4, _capacity * 2); + _items = Allocators.References.Allocate((uint)_capacity); + + var newSpan = Span; + + oldSpan.CopyTo(newSpan); + } + + int position = interval.GetStart(); + int i = _count - 1; + + while (i >= 0 && _items[i].GetStart() > position) + { + _items[i + 1] = _items[i--]; + } + + _items[i + 1] = interval; + _count++; + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveRange.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveRange.cs new file mode 100644 index 00000000..412d597e --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveRange.cs @@ -0,0 +1,74 @@ +using System; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + unsafe readonly struct LiveRange : IEquatable + { + private struct Data + { + public int Start; + public int End; + public LiveRange Next; + } + + private readonly Data* _data; + + public ref int Start => ref _data->Start; + public ref int End => ref _data->End; + public ref LiveRange Next => ref _data->Next; + + public LiveRange(int start, int end, LiveRange next = default) + { + _data = Allocators.LiveRanges.Allocate(); + + Start = start; + End = end; + Next = next; + } + + public bool Overlaps(int start, int end) + { + return Start < end && start < End; + } + + public bool Overlaps(LiveRange range) + { + return Start < range.End && range.Start < End; + } + + public bool Overlaps(int position) + { + return position >= Start && position < End; + } + + public bool Equals(LiveRange range) + { + return range._data == _data; + } + + public override bool Equals(object obj) + { + return obj is LiveRange range && Equals(range); + } + + public static bool operator ==(LiveRange a, LiveRange b) + { + return a.Equals(b); + } + + public static bool operator !=(LiveRange a, LiveRange b) + { + return !a.Equals(b); + } + + public override int GetHashCode() + { + return HashCode.Combine((IntPtr)_data); + } + + public override string ToString() + { + return $"[{Start}, {End})"; + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/RegisterMasks.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/RegisterMasks.cs new file mode 100644 index 00000000..e6972cf0 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/RegisterMasks.cs @@ -0,0 +1,50 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + readonly struct RegisterMasks + { + public int IntAvailableRegisters { get; } + public int VecAvailableRegisters { get; } + public int IntCallerSavedRegisters { get; } + public int VecCallerSavedRegisters { get; } + public int IntCalleeSavedRegisters { get; } + public int VecCalleeSavedRegisters { get; } + public int RegistersCount { get; } + + public RegisterMasks( + int intAvailableRegisters, + int vecAvailableRegisters, + int intCallerSavedRegisters, + int vecCallerSavedRegisters, + int intCalleeSavedRegisters, + int vecCalleeSavedRegisters, + int registersCount) + { + IntAvailableRegisters = intAvailableRegisters; + VecAvailableRegisters = vecAvailableRegisters; + IntCallerSavedRegisters = intCallerSavedRegisters; + VecCallerSavedRegisters = vecCallerSavedRegisters; + IntCalleeSavedRegisters = intCalleeSavedRegisters; + VecCalleeSavedRegisters = vecCalleeSavedRegisters; + RegistersCount = registersCount; + } + + public int GetAvailableRegisters(RegisterType type) + { + if (type == RegisterType.Integer) + { + return IntAvailableRegisters; + } + else if (type == RegisterType.Vector) + { + return VecAvailableRegisters; + } + else + { + throw new ArgumentException($"Invalid register type \"{type}\"."); + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs new file mode 100644 index 00000000..13995bc8 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs @@ -0,0 +1,25 @@ +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + class StackAllocator + { + private int _offset; + + public int TotalSize => _offset; + + public int Allocate(OperandType type) + { + return Allocate(type.GetSizeInBytes()); + } + + public int Allocate(int sizeInBytes) + { + int offset = _offset; + + _offset += sizeInBytes; + + return offset; + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/UseList.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/UseList.cs new file mode 100644 index 00000000..806002f8 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/UseList.cs @@ -0,0 +1,86 @@ +using System; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + unsafe struct UseList + { + private int* _items; + private int _capacity; + + public int Count { get; private set; } + + public readonly int FirstUse => Count > 0 ? _items[Count - 1] : LiveInterval.NotFound; + public readonly Span Span => new(_items, Count); + + public void Add(int position) + { + if (Count + 1 > _capacity) + { + var oldSpan = Span; + + _capacity = Math.Max(4, _capacity * 2); + _items = Allocators.Default.Allocate((uint)_capacity); + + var newSpan = Span; + + oldSpan.CopyTo(newSpan); + } + + // Use positions are usually inserted in descending order, so inserting in descending order is faster, + // since the number of half exchanges is reduced. + int i = Count - 1; + + while (i >= 0 && _items[i] < position) + { + _items[i + 1] = _items[i--]; + } + + _items[i + 1] = position; + Count++; + } + + public readonly int NextUse(int position) + { + int index = NextUseIndex(position); + + return index != LiveInterval.NotFound ? _items[index] : LiveInterval.NotFound; + } + + public readonly int NextUseIndex(int position) + { + int i = Count - 1; + + if (i == -1 || position > _items[0]) + { + return LiveInterval.NotFound; + } + + while (i >= 0 && _items[i] < position) + { + i--; + } + + return i; + } + + public UseList Split(int position) + { + int index = NextUseIndex(position); + + // Since the list is in descending order, the new split list takes the front of the list and the current + // list takes the back of the list. + UseList result = new() + { + Count = index + 1, + }; + result._capacity = result.Count; + result._items = _items; + + Count -= result.Count; + _capacity = Count; + _items += result.Count; + + return result; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs b/src/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs new file mode 100644 index 00000000..127b8423 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.CodeGen.Unwinding +{ + struct UnwindInfo + { + public const int Stride = 4; // Bytes. + + public UnwindPushEntry[] PushEntries { get; } + public int PrologSize { get; } + + public UnwindInfo(UnwindPushEntry[] pushEntries, int prologSize) + { + PushEntries = pushEntries; + PrologSize = prologSize; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Unwinding/UnwindPseudoOp.cs b/src/ARMeilleure/CodeGen/Unwinding/UnwindPseudoOp.cs new file mode 100644 index 00000000..2045019a --- /dev/null +++ b/src/ARMeilleure/CodeGen/Unwinding/UnwindPseudoOp.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.CodeGen.Unwinding +{ + enum UnwindPseudoOp + { + PushReg = 0, + SetFrame = 1, + AllocStack = 2, + SaveReg = 3, + SaveXmm128 = 4, + } +} diff --git a/src/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs b/src/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs new file mode 100644 index 00000000..507ace59 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.CodeGen.Unwinding +{ + struct UnwindPushEntry + { + public const int Stride = 16; // Bytes. + + public UnwindPseudoOp PseudoOp { get; } + public int PrologOffset { get; } + public int RegIndex { get; } + public int StackOffsetOrAllocSize { get; } + + public UnwindPushEntry(UnwindPseudoOp pseudoOp, int prologOffset, int regIndex = -1, int stackOffsetOrAllocSize = -1) + { + PseudoOp = pseudoOp; + PrologOffset = prologOffset; + RegIndex = regIndex; + StackOffsetOrAllocSize = stackOffsetOrAllocSize; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/Assembler.cs b/src/ARMeilleure/CodeGen/X86/Assembler.cs new file mode 100644 index 00000000..96f4de04 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/Assembler.cs @@ -0,0 +1,1580 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.IntermediateRepresentation; +using Ryujinx.Common.Memory; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace ARMeilleure.CodeGen.X86 +{ + partial class Assembler + { + private const int ReservedBytesForJump = 1; + + private const int OpModRMBits = 24; + + private const byte RexPrefix = 0x40; + private const byte RexWPrefix = 0x48; + private const byte LockPrefix = 0xf0; + + private const int MaxRegNumber = 15; + + private struct Jump + { + public bool IsConditional { get; } + public X86Condition Condition { get; } + public Operand JumpLabel { get; } + public long? JumpTarget { get; set; } + public long JumpPosition { get; } + public long Offset { get; set; } + public int InstSize { get; set; } + + public Jump(Operand jumpLabel, long jumpPosition) + { + IsConditional = false; + Condition = 0; + JumpLabel = jumpLabel; + JumpTarget = null; + JumpPosition = jumpPosition; + + Offset = 0; + InstSize = 0; + } + + public Jump(X86Condition condition, Operand jumpLabel, long jumpPosition) + { + IsConditional = true; + Condition = condition; + JumpLabel = jumpLabel; + JumpTarget = null; + JumpPosition = jumpPosition; + + Offset = 0; + InstSize = 0; + } + } + + private struct Reloc + { + public int JumpIndex { get; set; } + public int Position { get; set; } + public Symbol Symbol { get; set; } + } + + private readonly List _jumps; + private readonly List _relocs; + private readonly Dictionary _labels; + private readonly Stream _stream; + + public bool HasRelocs => _relocs != null; + + public Assembler(Stream stream, bool relocatable) + { + _stream = stream; + _labels = new Dictionary(); + _jumps = new List(); + + _relocs = relocatable ? new List() : null; + } + + public void MarkLabel(Operand label) + { + _labels.Add(label, _stream.Position); + } + + public void Add(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Add); + } + + public void Addsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Addsd); + } + + public void Addss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Addss); + } + + public void And(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.And); + } + + public void Bsr(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Bsr); + } + + public void Bswap(Operand dest) + { + WriteInstruction(dest, default, dest.Type, X86Instruction.Bswap); + } + + public void Call(Operand dest) + { + WriteInstruction(dest, default, OperandType.None, X86Instruction.Call); + } + + public void Cdq() + { + WriteByte(0x99); + } + + public void Cmovcc(Operand dest, Operand source, OperandType type, X86Condition condition) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Cmovcc]; + + WriteOpCode(dest, default, source, type, info.Flags, info.OpRRM | (int)condition, rrm: true); + } + + public void Cmp(Operand src1, Operand src2, OperandType type) + { + WriteInstruction(src1, src2, type, X86Instruction.Cmp); + } + + public void Cqo() + { + WriteByte(0x48); + WriteByte(0x99); + } + + public void Cmpxchg(Operand memOp, Operand src) + { + Debug.Assert(memOp.Kind == OperandKind.Memory); + + WriteByte(LockPrefix); + + WriteInstruction(memOp, src, src.Type, X86Instruction.Cmpxchg); + } + + public void Cmpxchg16(Operand memOp, Operand src) + { + Debug.Assert(memOp.Kind == OperandKind.Memory); + + WriteByte(LockPrefix); + WriteByte(0x66); + + WriteInstruction(memOp, src, src.Type, X86Instruction.Cmpxchg); + } + + public void Cmpxchg16b(Operand memOp) + { + Debug.Assert(memOp.Kind == OperandKind.Memory); + + WriteByte(LockPrefix); + + WriteInstruction(memOp, default, OperandType.None, X86Instruction.Cmpxchg16b); + } + + public void Cmpxchg8(Operand memOp, Operand src) + { + Debug.Assert(memOp.Kind == OperandKind.Memory); + + WriteByte(LockPrefix); + + WriteInstruction(memOp, src, src.Type, X86Instruction.Cmpxchg8); + } + + public void Comisd(Operand src1, Operand src2) + { + WriteInstruction(src1, default, src2, X86Instruction.Comisd); + } + + public void Comiss(Operand src1, Operand src2) + { + WriteInstruction(src1, default, src2, X86Instruction.Comiss); + } + + public void Cvtsd2ss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Cvtsd2ss); + } + + public void Cvtsi2sd(Operand dest, Operand src1, Operand src2, OperandType type) + { + WriteInstruction(dest, src1, src2, X86Instruction.Cvtsi2sd, type); + } + + public void Cvtsi2ss(Operand dest, Operand src1, Operand src2, OperandType type) + { + WriteInstruction(dest, src1, src2, X86Instruction.Cvtsi2ss, type); + } + + public void Cvtss2sd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Cvtss2sd); + } + + public void Div(Operand source) + { + WriteInstruction(default, source, source.Type, X86Instruction.Div); + } + + public void Divsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Divsd); + } + + public void Divss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Divss); + } + + public void Idiv(Operand source) + { + WriteInstruction(default, source, source.Type, X86Instruction.Idiv); + } + + public void Imul(Operand source) + { + WriteInstruction(default, source, source.Type, X86Instruction.Imul128); + } + + public void Imul(Operand dest, Operand source, OperandType type) + { + if (source.Kind != OperandKind.Register) + { + throw new ArgumentException($"Invalid source operand kind \"{source.Kind}\"."); + } + + WriteInstruction(dest, source, type, X86Instruction.Imul); + } + + public void Imul(Operand dest, Operand src1, Operand src2, OperandType type) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Imul]; + + if (src2.Kind != OperandKind.Constant) + { + throw new ArgumentException($"Invalid source 2 operand kind \"{src2.Kind}\"."); + } + + if (IsImm8(src2.Value, src2.Type) && info.OpRMImm8 != BadOp) + { + WriteOpCode(dest, default, src1, type, info.Flags, info.OpRMImm8, rrm: true); + + WriteByte(src2.AsByte()); + } + else if (IsImm32(src2.Value, src2.Type) && info.OpRMImm32 != BadOp) + { + WriteOpCode(dest, default, src1, type, info.Flags, info.OpRMImm32, rrm: true); + + WriteInt32(src2.AsInt32()); + } + else + { + throw new ArgumentException($"Failed to encode constant 0x{src2.Value:X}."); + } + } + + public void Insertps(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Insertps); + + WriteByte(imm); + } + + public void Jcc(X86Condition condition, Operand dest) + { + if (dest.Kind == OperandKind.Label) + { + _jumps.Add(new Jump(condition, dest, _stream.Position)); + + // ReservedBytesForJump + WriteByte(0); + } + else + { + throw new ArgumentException("Destination operand must be of kind Label", nameof(dest)); + } + } + + public void Jcc(X86Condition condition, long offset) + { + if (ConstFitsOnS8(offset)) + { + WriteByte((byte)(0x70 | (int)condition)); + + WriteByte((byte)offset); + } + else if (ConstFitsOnS32(offset)) + { + WriteByte(0x0f); + WriteByte((byte)(0x80 | (int)condition)); + + WriteInt32((int)offset); + } + else + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + } + + public void Jmp(long offset) + { + if (ConstFitsOnS8(offset)) + { + WriteByte(0xeb); + + WriteByte((byte)offset); + } + else if (ConstFitsOnS32(offset)) + { + WriteByte(0xe9); + + WriteInt32((int)offset); + } + else + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + } + + public void Jmp(Operand dest) + { + if (dest.Kind == OperandKind.Label) + { + _jumps.Add(new Jump(dest, _stream.Position)); + + // ReservedBytesForJump + WriteByte(0); + } + else + { + WriteInstruction(dest, default, OperandType.None, X86Instruction.Jmp); + } + } + + public void Ldmxcsr(Operand dest) + { + WriteInstruction(dest, default, OperandType.I32, X86Instruction.Ldmxcsr); + } + + public void Lea(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Lea); + } + + public void LockOr(Operand dest, Operand source, OperandType type) + { + WriteByte(LockPrefix); + WriteInstruction(dest, source, type, X86Instruction.Or); + } + + public void Mov(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Mov); + } + + public void Mov16(Operand dest, Operand source) + { + WriteInstruction(dest, source, OperandType.None, X86Instruction.Mov16); + } + + public void Mov8(Operand dest, Operand source) + { + WriteInstruction(dest, source, OperandType.None, X86Instruction.Mov8); + } + + public void Movd(Operand dest, Operand source) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Movd]; + + if (source.Type.IsInteger() || source.Kind == OperandKind.Memory) + { + WriteOpCode(dest, default, source, OperandType.None, info.Flags, info.OpRRM, rrm: true); + } + else + { + WriteOpCode(dest, default, source, OperandType.None, info.Flags, info.OpRMR); + } + } + + public void Movdqu(Operand dest, Operand source) + { + WriteInstruction(dest, default, source, X86Instruction.Movdqu); + } + + public void Movhlps(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Movhlps); + } + + public void Movlhps(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Movlhps); + } + + public void Movq(Operand dest, Operand source) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Movd]; + + InstructionFlags flags = info.Flags | InstructionFlags.RexW; + + if (source.Type.IsInteger() || source.Kind == OperandKind.Memory) + { + WriteOpCode(dest, default, source, OperandType.None, flags, info.OpRRM, rrm: true); + } + else if (dest.Type.IsInteger() || dest.Kind == OperandKind.Memory) + { + WriteOpCode(dest, default, source, OperandType.None, flags, info.OpRMR); + } + else + { + WriteInstruction(dest, source, OperandType.None, X86Instruction.Movq); + } + } + + public void Movsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Movsd); + } + + public void Movss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Movss); + } + + public void Movsx16(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movsx16); + } + + public void Movsx32(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movsx32); + } + + public void Movsx8(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movsx8); + } + + public void Movzx16(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movzx16); + } + + public void Movzx8(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movzx8); + } + + public void Mul(Operand source) + { + WriteInstruction(default, source, source.Type, X86Instruction.Mul128); + } + + public void Mulsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Mulsd); + } + + public void Mulss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Mulss); + } + + public void Neg(Operand dest) + { + WriteInstruction(dest, default, dest.Type, X86Instruction.Neg); + } + + public void Not(Operand dest) + { + WriteInstruction(dest, default, dest.Type, X86Instruction.Not); + } + + public void Or(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Or); + } + + public void Pclmulqdq(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pclmulqdq); + + WriteByte(imm); + } + + public void Pcmpeqw(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pcmpeqw); + } + + public void Pextrb(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pextrb); + + WriteByte(imm); + } + + public void Pextrd(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pextrd); + + WriteByte(imm); + } + + public void Pextrq(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pextrq); + + WriteByte(imm); + } + + public void Pextrw(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pextrw); + + WriteByte(imm); + } + + public void Pinsrb(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pinsrb); + + WriteByte(imm); + } + + public void Pinsrd(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pinsrd); + + WriteByte(imm); + } + + public void Pinsrq(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pinsrq); + + WriteByte(imm); + } + + public void Pinsrw(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pinsrw); + + WriteByte(imm); + } + + public void Pop(Operand dest) + { + if (dest.Kind == OperandKind.Register) + { + WriteCompactInst(dest, 0x58); + } + else + { + WriteInstruction(dest, default, dest.Type, X86Instruction.Pop); + } + } + + public void Popcnt(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Popcnt); + } + + public void Pshufd(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pshufd); + + WriteByte(imm); + } + + public void Push(Operand source) + { + if (source.Kind == OperandKind.Register) + { + WriteCompactInst(source, 0x50); + } + else + { + WriteInstruction(default, source, source.Type, X86Instruction.Push); + } + } + + public void Return() + { + WriteByte(0xc3); + } + + public void Ror(Operand dest, Operand source, OperandType type) + { + WriteShiftInst(dest, source, type, X86Instruction.Ror); + } + + public void Sar(Operand dest, Operand source, OperandType type) + { + WriteShiftInst(dest, source, type, X86Instruction.Sar); + } + + public void Shl(Operand dest, Operand source, OperandType type) + { + WriteShiftInst(dest, source, type, X86Instruction.Shl); + } + + public void Shr(Operand dest, Operand source, OperandType type) + { + WriteShiftInst(dest, source, type, X86Instruction.Shr); + } + + public void Setcc(Operand dest, X86Condition condition) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Setcc]; + + WriteOpCode(dest, default, default, OperandType.None, info.Flags, info.OpRRM | (int)condition); + } + + public void Stmxcsr(Operand dest) + { + WriteInstruction(dest, default, OperandType.I32, X86Instruction.Stmxcsr); + } + + public void Sub(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Sub); + } + + public void Subsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Subsd); + } + + public void Subss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Subss); + } + + public void Test(Operand src1, Operand src2, OperandType type) + { + WriteInstruction(src1, src2, type, X86Instruction.Test); + } + + public void Xor(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Xor); + } + + public void Xorps(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Xorps); + } + + public void WriteInstruction( + X86Instruction inst, + Operand dest, + Operand source, + OperandType type = OperandType.None) + { + WriteInstruction(dest, default, source, inst, type); + } + + public void WriteInstruction(X86Instruction inst, Operand dest, Operand src1, Operand src2) + { + if (src2.Kind == OperandKind.Constant) + { + WriteInstruction(src1, dest, src2, inst); + } + else + { + WriteInstruction(dest, src1, src2, inst); + } + } + + public void WriteInstruction( + X86Instruction inst, + Operand dest, + Operand src1, + Operand src2, + OperandType type) + { + WriteInstruction(dest, src1, src2, inst, type); + } + + public void WriteInstruction(X86Instruction inst, Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, inst); + + WriteByte(imm); + } + + public void WriteInstruction( + X86Instruction inst, + Operand dest, + Operand src1, + Operand src2, + Operand src3) + { + // 3+ operands can only be encoded with the VEX encoding scheme. + Debug.Assert(HardwareCapabilities.SupportsVexEncoding); + + WriteInstruction(dest, src1, src2, inst); + + WriteByte((byte)(src3.AsByte() << 4)); + } + + public void WriteInstruction( + X86Instruction inst, + Operand dest, + Operand src1, + Operand src2, + byte imm) + { + WriteInstruction(dest, src1, src2, inst); + + WriteByte(imm); + } + + private void WriteShiftInst(Operand dest, Operand source, OperandType type, X86Instruction inst) + { + if (source.Kind == OperandKind.Register) + { + X86Register shiftReg = (X86Register)source.GetRegister().Index; + + Debug.Assert(shiftReg == X86Register.Rcx, $"Invalid shift register \"{shiftReg}\"."); + + source = default; + } + else if (source.Kind == OperandKind.Constant) + { + source = Operand.Factory.Const((int)source.Value & (dest.Type == OperandType.I32 ? 0x1f : 0x3f)); + } + + WriteInstruction(dest, source, type, inst); + } + + private void WriteInstruction(Operand dest, Operand source, OperandType type, X86Instruction inst) + { + ref readonly InstructionInfo info = ref _instTable[(int)inst]; + + if (source != default) + { + if (source.Kind == OperandKind.Constant) + { + ulong imm = source.Value; + + if (inst == X86Instruction.Mov8) + { + WriteOpCode(dest, default, default, type, info.Flags, info.OpRMImm8); + + WriteByte((byte)imm); + } + else if (inst == X86Instruction.Mov16) + { + WriteOpCode(dest, default, default, type, info.Flags, info.OpRMImm32); + + WriteInt16((short)imm); + } + else if (IsImm8(imm, type) && info.OpRMImm8 != BadOp) + { + WriteOpCode(dest, default, default, type, info.Flags, info.OpRMImm8); + + WriteByte((byte)imm); + } + else if (!source.Relocatable && IsImm32(imm, type) && info.OpRMImm32 != BadOp) + { + WriteOpCode(dest, default, default, type, info.Flags, info.OpRMImm32); + + WriteInt32((int)imm); + } + else if (dest != default && dest.Kind == OperandKind.Register && info.OpRImm64 != BadOp) + { + int rexPrefix = GetRexPrefix(dest, source, type, rrm: false); + + if (rexPrefix != 0) + { + WriteByte((byte)rexPrefix); + } + + WriteByte((byte)(info.OpRImm64 + (dest.GetRegister().Index & 0b111))); + + if (HasRelocs && source.Relocatable) + { + _relocs.Add(new Reloc + { + JumpIndex = _jumps.Count - 1, + Position = (int)_stream.Position, + Symbol = source.Symbol, + }); + } + + WriteUInt64(imm); + } + else + { + throw new ArgumentException($"Failed to encode constant 0x{imm:X}."); + } + } + else if (source.Kind == OperandKind.Register && info.OpRMR != BadOp) + { + WriteOpCode(dest, default, source, type, info.Flags, info.OpRMR); + } + else if (info.OpRRM != BadOp) + { + WriteOpCode(dest, default, source, type, info.Flags, info.OpRRM, rrm: true); + } + else + { + throw new ArgumentException($"Invalid source operand kind \"{source.Kind}\"."); + } + } + else if (info.OpRRM != BadOp) + { + WriteOpCode(dest, default, source, type, info.Flags, info.OpRRM, rrm: true); + } + else if (info.OpRMR != BadOp) + { + WriteOpCode(dest, default, source, type, info.Flags, info.OpRMR); + } + else + { + throw new ArgumentNullException(nameof(source)); + } + } + + private void WriteInstruction( + Operand dest, + Operand src1, + Operand src2, + X86Instruction inst, + OperandType type = OperandType.None) + { + ref readonly InstructionInfo info = ref _instTable[(int)inst]; + + if (src2 != default) + { + if (src2.Kind == OperandKind.Constant) + { + ulong imm = src2.Value; + + if ((byte)imm == imm && info.OpRMImm8 != BadOp) + { + WriteOpCode(dest, src1, default, type, info.Flags, info.OpRMImm8); + + WriteByte((byte)imm); + } + else + { + throw new ArgumentException($"Failed to encode constant 0x{imm:X}."); + } + } + else if (src2.Kind == OperandKind.Register && info.OpRMR != BadOp) + { + WriteOpCode(dest, src1, src2, type, info.Flags, info.OpRMR); + } + else if (info.OpRRM != BadOp) + { + WriteOpCode(dest, src1, src2, type, info.Flags, info.OpRRM, rrm: true); + } + else + { + throw new ArgumentException($"Invalid source operand kind \"{src2.Kind}\"."); + } + } + else if (info.OpRRM != BadOp) + { + WriteOpCode(dest, src1, src2, type, info.Flags, info.OpRRM, rrm: true); + } + else if (info.OpRMR != BadOp) + { + WriteOpCode(dest, src1, src2, type, info.Flags, info.OpRMR); + } + else + { + throw new ArgumentNullException(nameof(src2)); + } + } + + private void WriteOpCode( + Operand dest, + Operand src1, + Operand src2, + OperandType type, + InstructionFlags flags, + int opCode, + bool rrm = false) + { + int rexPrefix = GetRexPrefix(dest, src2, type, rrm); + + if ((flags & InstructionFlags.RexW) != 0) + { + rexPrefix |= RexWPrefix; + } + + int modRM = (opCode >> OpModRMBits) << 3; + + MemoryOperand memOp = default; + bool hasMemOp = false; + + if (dest != default) + { + if (dest.Kind == OperandKind.Register) + { + int regIndex = dest.GetRegister().Index; + + modRM |= (regIndex & 0b111) << (rrm ? 3 : 0); + + if ((flags & InstructionFlags.Reg8Dest) != 0 && regIndex >= 4) + { + rexPrefix |= RexPrefix; + } + } + else if (dest.Kind == OperandKind.Memory) + { + memOp = dest.GetMemory(); + hasMemOp = true; + } + else + { + throw new ArgumentException("Invalid destination operand kind \"" + dest.Kind + "\"."); + } + } + + if (src2 != default) + { + if (src2.Kind == OperandKind.Register) + { + int regIndex = src2.GetRegister().Index; + + modRM |= (regIndex & 0b111) << (rrm ? 0 : 3); + + if ((flags & InstructionFlags.Reg8Src) != 0 && regIndex >= 4) + { + rexPrefix |= RexPrefix; + } + } + else if (src2.Kind == OperandKind.Memory && !hasMemOp) + { + memOp = src2.GetMemory(); + hasMemOp = true; + } + else + { + throw new ArgumentException("Invalid source operand kind \"" + src2.Kind + "\"."); + } + } + + bool needsSibByte = false; + bool needsDisplacement = false; + + int sib = 0; + + if (hasMemOp) + { + // Either source or destination is a memory operand. + Register baseReg = memOp.BaseAddress.GetRegister(); + + X86Register baseRegLow = (X86Register)(baseReg.Index & 0b111); + + needsSibByte = memOp.Index != default || baseRegLow == X86Register.Rsp; + needsDisplacement = memOp.Displacement != 0 || baseRegLow == X86Register.Rbp; + + if (needsDisplacement) + { + if (ConstFitsOnS8(memOp.Displacement)) + { + modRM |= 0x40; + } + else /* if (ConstFitsOnS32(memOp.Displacement)) */ + { + modRM |= 0x80; + } + } + + if (baseReg.Index >= 8) + { + Debug.Assert((uint)baseReg.Index <= MaxRegNumber); + + rexPrefix |= RexPrefix | (baseReg.Index >> 3); + } + + if (needsSibByte) + { + sib = (int)baseRegLow; + + if (memOp.Index != default) + { + int indexReg = memOp.Index.GetRegister().Index; + + Debug.Assert(indexReg != (int)X86Register.Rsp, "Using RSP as index register on the memory operand is not allowed."); + + if (indexReg >= 8) + { + Debug.Assert((uint)indexReg <= MaxRegNumber); + + rexPrefix |= RexPrefix | (indexReg >> 3) << 1; + } + + sib |= (indexReg & 0b111) << 3; + } + else + { + sib |= 0b100 << 3; + } + + sib |= (int)memOp.Scale << 6; + + modRM |= 0b100; + } + else + { + modRM |= (int)baseRegLow; + } + } + else + { + // Source and destination are registers. + modRM |= 0xc0; + } + + Debug.Assert(opCode != BadOp, "Invalid opcode value."); + + if ((flags & InstructionFlags.Evex) != 0 && HardwareCapabilities.SupportsEvexEncoding) + { + WriteEvexInst(dest, src1, src2, type, flags, opCode); + + opCode &= 0xff; + } + else if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding) + { + // In a vex encoding, only one prefix can be active at a time. The active prefix is encoded in the second byte using two bits. + + int vexByte2 = (flags & InstructionFlags.PrefixMask) switch + { + InstructionFlags.Prefix66 => 1, + InstructionFlags.PrefixF3 => 2, + InstructionFlags.PrefixF2 => 3, + _ => 0, + }; + + if (src1 != default) + { + vexByte2 |= (src1.GetRegister().Index ^ 0xf) << 3; + } + else + { + vexByte2 |= 0b1111 << 3; + } + + ushort opCodeHigh = (ushort)(opCode >> 8); + + if ((rexPrefix & 0b1011) == 0 && opCodeHigh == 0xf) + { + // Two-byte form. + WriteByte(0xc5); + + vexByte2 |= (~rexPrefix & 4) << 5; + + WriteByte((byte)vexByte2); + } + else + { + // Three-byte form. + WriteByte(0xc4); + + int vexByte1 = (~rexPrefix & 7) << 5; + + switch (opCodeHigh) + { + case 0xf: + vexByte1 |= 1; + break; + case 0xf38: + vexByte1 |= 2; + break; + case 0xf3a: + vexByte1 |= 3; + break; + + default: + Debug.Assert(false, $"Failed to VEX encode opcode 0x{opCode:X}."); + break; + } + + vexByte2 |= (rexPrefix & 8) << 4; + + WriteByte((byte)vexByte1); + WriteByte((byte)vexByte2); + } + + opCode &= 0xff; + } + else + { + if (flags.HasFlag(InstructionFlags.Prefix66)) + { + WriteByte(0x66); + } + + if (flags.HasFlag(InstructionFlags.PrefixF2)) + { + WriteByte(0xf2); + } + + if (flags.HasFlag(InstructionFlags.PrefixF3)) + { + WriteByte(0xf3); + } + + if (rexPrefix != 0) + { + WriteByte((byte)rexPrefix); + } + } + + if (dest != default && (flags & InstructionFlags.RegOnly) != 0) + { + opCode += dest.GetRegister().Index & 7; + } + + if ((opCode & 0xff0000) != 0) + { + WriteByte((byte)(opCode >> 16)); + } + + if ((opCode & 0xff00) != 0) + { + WriteByte((byte)(opCode >> 8)); + } + + WriteByte((byte)opCode); + + if ((flags & InstructionFlags.RegOnly) == 0) + { + WriteByte((byte)modRM); + + if (needsSibByte) + { + WriteByte((byte)sib); + } + + if (needsDisplacement) + { + if (ConstFitsOnS8(memOp.Displacement)) + { + WriteByte((byte)memOp.Displacement); + } + else /* if (ConstFitsOnS32(memOp.Displacement)) */ + { + WriteInt32(memOp.Displacement); + } + } + } + } + + private void WriteEvexInst( + Operand dest, + Operand src1, + Operand src2, + OperandType type, + InstructionFlags flags, + int opCode, + bool broadcast = false, + int registerWidth = 128, + int maskRegisterIdx = 0, + bool zeroElements = false) + { + int op1Idx = dest.GetRegister().Index; + int op2Idx = src1.GetRegister().Index; + int op3Idx = src2.GetRegister().Index; + + WriteByte(0x62); + + // P0 + // Extend operand 1 register + bool r = (op1Idx & 8) == 0; + // Extend operand 3 register + bool x = (op3Idx & 16) == 0; + // Extend operand 3 register + bool b = (op3Idx & 8) == 0; + // Extend operand 1 register + bool rp = (op1Idx & 16) == 0; + // Escape code index + byte mm = 0b00; + + switch ((ushort)(opCode >> 8)) + { + case 0xf00: + mm = 0b01; + break; + case 0xf38: + mm = 0b10; + break; + case 0xf3a: + mm = 0b11; + break; + + default: + Debug.Fail($"Failed to EVEX encode opcode 0x{opCode:X}."); + break; + } + + WriteByte( + (byte)( + (r ? 0x80 : 0) | + (x ? 0x40 : 0) | + (b ? 0x20 : 0) | + (rp ? 0x10 : 0) | + mm)); + + // P1 + // Specify 64-bit lane mode + bool w = Is64Bits(type); + // Operand 2 register index + byte vvvv = (byte)(~op2Idx & 0b1111); + // Opcode prefix + byte pp = (flags & InstructionFlags.PrefixMask) switch + { + InstructionFlags.Prefix66 => 0b01, + InstructionFlags.PrefixF3 => 0b10, + InstructionFlags.PrefixF2 => 0b11, + _ => 0, + }; + WriteByte( + (byte)( + (w ? 0x80 : 0) | + (vvvv << 3) | + 0b100 | + pp)); + + // P2 + // Mask register determines what elements to zero, rather than what elements to merge + bool z = zeroElements; + // Specifies register-width + byte ll = 0b00; + switch (registerWidth) + { + case 128: + ll = 0b00; + break; + case 256: + ll = 0b01; + break; + case 512: + ll = 0b10; + break; + + default: + Debug.Fail($"Invalid EVEX vector register width {registerWidth}."); + break; + } + // Embedded broadcast in the case of a memory operand + bool bcast = broadcast; + // Extend operand 2 register + bool vp = (op2Idx & 16) == 0; + // Mask register index + Debug.Assert(maskRegisterIdx < 8, $"Invalid mask register index {maskRegisterIdx}."); + byte aaa = (byte)(maskRegisterIdx & 0b111); + + WriteByte( + (byte)( + (z ? 0x80 : 0) | + (ll << 5) | + (bcast ? 0x10 : 0) | + (vp ? 8 : 0) | + aaa)); + } + + private void WriteCompactInst(Operand operand, int opCode) + { + int regIndex = operand.GetRegister().Index; + + if (regIndex >= 8) + { + WriteByte(0x41); + } + + WriteByte((byte)(opCode + (regIndex & 0b111))); + } + + private static int GetRexPrefix(Operand dest, Operand source, OperandType type, bool rrm) + { + int rexPrefix = 0; + + if (Is64Bits(type)) + { + rexPrefix = RexWPrefix; + } + + void SetRegisterHighBit(Register reg, int bit) + { + if (reg.Index >= 8) + { + rexPrefix |= RexPrefix | (reg.Index >> 3) << bit; + } + } + + if (dest != default && dest.Kind == OperandKind.Register) + { + SetRegisterHighBit(dest.GetRegister(), rrm ? 2 : 0); + } + + if (source != default && source.Kind == OperandKind.Register) + { + SetRegisterHighBit(source.GetRegister(), rrm ? 0 : 2); + } + + return rexPrefix; + } + + public (byte[], RelocInfo) GetCode() + { + var jumps = CollectionsMarshal.AsSpan(_jumps); + var relocs = CollectionsMarshal.AsSpan(_relocs); + + // Write jump relative offsets. + bool modified; + + do + { + modified = false; + + for (int i = 0; i < jumps.Length; i++) + { + ref Jump jump = ref jumps[i]; + + // If jump target not resolved yet, resolve it. + jump.JumpTarget ??= _labels[jump.JumpLabel]; + + long jumpTarget = jump.JumpTarget.Value; + long offset = jumpTarget - jump.JumpPosition; + + if (offset < 0) + { + for (int j = i - 1; j >= 0; j--) + { + ref Jump jump2 = ref jumps[j]; + + if (jump2.JumpPosition < jumpTarget) + { + break; + } + + offset -= jump2.InstSize - ReservedBytesForJump; + } + } + else + { + for (int j = i + 1; j < jumps.Length; j++) + { + ref Jump jump2 = ref jumps[j]; + + if (jump2.JumpPosition >= jumpTarget) + { + break; + } + + offset += jump2.InstSize - ReservedBytesForJump; + } + + offset -= ReservedBytesForJump; + } + + if (jump.IsConditional) + { + jump.InstSize = GetJccLength(offset); + } + else + { + jump.InstSize = GetJmpLength(offset); + } + + // The jump is relative to the next instruction, not the current one. + // Since we didn't know the next instruction address when calculating + // the offset (as the size of the current jump instruction was not known), + // we now need to compensate the offset with the jump instruction size. + // It's also worth noting that: + // - This is only needed for backward jumps. + // - The GetJmpLength and GetJccLength also compensates the offset + // internally when computing the jump instruction size. + if (offset < 0) + { + offset -= jump.InstSize; + } + + if (jump.Offset != offset) + { + jump.Offset = offset; + + modified = true; + } + } + } + while (modified); + + // Write the code, ignoring the dummy bytes after jumps, into a new stream. + _stream.Seek(0, SeekOrigin.Begin); + + using var codeStream = MemoryStreamManager.Shared.GetStream(); + var assembler = new Assembler(codeStream, HasRelocs); + + bool hasRelocs = HasRelocs; + int relocIndex = 0; + int relocOffset = 0; + var relocEntries = hasRelocs + ? new RelocEntry[relocs.Length] + : Array.Empty(); + + for (int i = 0; i < jumps.Length; i++) + { + ref Jump jump = ref jumps[i]; + + // If has relocations, calculate their new positions compensating for jumps. + if (hasRelocs) + { + relocOffset += jump.InstSize - ReservedBytesForJump; + + for (; relocIndex < relocEntries.Length; relocIndex++) + { + ref Reloc reloc = ref relocs[relocIndex]; + + if (reloc.JumpIndex > i) + { + break; + } + + relocEntries[relocIndex] = new RelocEntry(reloc.Position + relocOffset, reloc.Symbol); + } + } + + Span buffer = new byte[jump.JumpPosition - _stream.Position]; + + _stream.ReadExactly(buffer); + _stream.Seek(ReservedBytesForJump, SeekOrigin.Current); + + codeStream.Write(buffer); + + if (jump.IsConditional) + { + assembler.Jcc(jump.Condition, jump.Offset); + } + else + { + assembler.Jmp(jump.Offset); + } + } + + // Write remaining relocations. This case happens when there are no jumps assembled. + for (; relocIndex < relocEntries.Length; relocIndex++) + { + ref Reloc reloc = ref relocs[relocIndex]; + + relocEntries[relocIndex] = new RelocEntry(reloc.Position + relocOffset, reloc.Symbol); + } + + _stream.CopyTo(codeStream); + + var code = codeStream.ToArray(); + var relocInfo = new RelocInfo(relocEntries); + + return (code, relocInfo); + } + + private static bool Is64Bits(OperandType type) + { + return type == OperandType.I64 || type == OperandType.FP64; + } + + private static bool IsImm8(ulong immediate, OperandType type) + { + long value = type == OperandType.I32 ? (int)immediate : (long)immediate; + + return ConstFitsOnS8(value); + } + + private static bool IsImm32(ulong immediate, OperandType type) + { + long value = type == OperandType.I32 ? (int)immediate : (long)immediate; + + return ConstFitsOnS32(value); + } + + private static int GetJccLength(long offset) + { + if (ConstFitsOnS8(offset < 0 ? offset - 2 : offset)) + { + return 2; + } + else if (ConstFitsOnS32(offset < 0 ? offset - 6 : offset)) + { + return 6; + } + else + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + } + + private static int GetJmpLength(long offset) + { + if (ConstFitsOnS8(offset < 0 ? offset - 2 : offset)) + { + return 2; + } + else if (ConstFitsOnS32(offset < 0 ? offset - 5 : offset)) + { + return 5; + } + else + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + } + + private static bool ConstFitsOnS8(long value) + { + return value == (sbyte)value; + } + + private static bool ConstFitsOnS32(long value) + { + return value == (int)value; + } + + private void WriteInt16(short value) + { + WriteUInt16((ushort)value); + } + + private void WriteInt32(int value) + { + WriteUInt32((uint)value); + } + + private void WriteByte(byte value) + { + _stream.WriteByte(value); + } + + private void WriteUInt16(ushort value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + } + + private void WriteUInt32(uint value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)(value >> 16)); + _stream.WriteByte((byte)(value >> 24)); + } + + private void WriteUInt64(ulong value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)(value >> 16)); + _stream.WriteByte((byte)(value >> 24)); + _stream.WriteByte((byte)(value >> 32)); + _stream.WriteByte((byte)(value >> 40)); + _stream.WriteByte((byte)(value >> 48)); + _stream.WriteByte((byte)(value >> 56)); + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/AssemblerTable.cs b/src/ARMeilleure/CodeGen/X86/AssemblerTable.cs new file mode 100644 index 00000000..8910e889 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/AssemblerTable.cs @@ -0,0 +1,299 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace ARMeilleure.CodeGen.X86 +{ + partial class Assembler + { + public static bool SupportsVexPrefix(X86Instruction inst) + { + return _instTable[(int)inst].Flags.HasFlag(InstructionFlags.Vex); + } + + private const int BadOp = 0; + + [Flags] + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + private enum InstructionFlags + { + None = 0, + RegOnly = 1 << 0, + Reg8Src = 1 << 1, + Reg8Dest = 1 << 2, + RexW = 1 << 3, + Vex = 1 << 4, + Evex = 1 << 5, + + PrefixBit = 16, + PrefixMask = 7 << PrefixBit, + Prefix66 = 1 << PrefixBit, + PrefixF3 = 2 << PrefixBit, + PrefixF2 = 4 << PrefixBit, + } + + private readonly struct InstructionInfo + { + public int OpRMR { get; } + public int OpRMImm8 { get; } + public int OpRMImm32 { get; } + public int OpRImm64 { get; } + public int OpRRM { get; } + + public InstructionFlags Flags { get; } + + public InstructionInfo( + int opRMR, + int opRMImm8, + int opRMImm32, + int opRImm64, + int opRRM, + InstructionFlags flags) + { + OpRMR = opRMR; + OpRMImm8 = opRMImm8; + OpRMImm32 = opRMImm32; + OpRImm64 = opRImm64; + OpRRM = opRRM; + Flags = flags; + } + } + + private readonly static InstructionInfo[] _instTable; + + static Assembler() + { + _instTable = new InstructionInfo[(int)X86Instruction.Count]; + +#pragma warning disable IDE0055 // Disable formatting + // Name RM/R RM/I8 RM/I32 R/I64 R/RM Flags + Add(X86Instruction.Add, new InstructionInfo(0x00000001, 0x00000083, 0x00000081, BadOp, 0x00000003, InstructionFlags.None)); + Add(X86Instruction.Addpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f58, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Addps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f58, InstructionFlags.Vex)); + Add(X86Instruction.Addsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f58, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Addss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f58, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Aesdec, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38de, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Aesdeclast, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38df, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Aesenc, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38dc, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Aesenclast, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38dd, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Aesimc, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38db, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.And, new InstructionInfo(0x00000021, 0x04000083, 0x04000081, BadOp, 0x00000023, InstructionFlags.None)); + Add(X86Instruction.Andnpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f55, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Andnps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f55, InstructionFlags.Vex)); + Add(X86Instruction.Andpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f54, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Andps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f54, InstructionFlags.Vex)); + Add(X86Instruction.Blendvpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3815, InstructionFlags.Prefix66)); + Add(X86Instruction.Blendvps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3814, InstructionFlags.Prefix66)); + Add(X86Instruction.Bsr, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fbd, InstructionFlags.None)); + Add(X86Instruction.Bswap, new InstructionInfo(0x00000fc8, BadOp, BadOp, BadOp, BadOp, InstructionFlags.RegOnly)); + Add(X86Instruction.Call, new InstructionInfo(0x020000ff, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Cmovcc, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f40, InstructionFlags.None)); + Add(X86Instruction.Cmp, new InstructionInfo(0x00000039, 0x07000083, 0x07000081, BadOp, 0x0000003b, InstructionFlags.None)); + Add(X86Instruction.Cmppd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc2, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Cmpps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc2, InstructionFlags.Vex)); + Add(X86Instruction.Cmpsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc2, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Cmpss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc2, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Cmpxchg, new InstructionInfo(0x00000fb1, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Cmpxchg16b, new InstructionInfo(0x01000fc7, BadOp, BadOp, BadOp, BadOp, InstructionFlags.RexW)); + Add(X86Instruction.Cmpxchg8, new InstructionInfo(0x00000fb0, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Reg8Src)); + Add(X86Instruction.Comisd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2f, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Comiss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2f, InstructionFlags.Vex)); + Add(X86Instruction.Crc32, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38f1, InstructionFlags.PrefixF2)); + Add(X86Instruction.Crc32_16, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38f1, InstructionFlags.PrefixF2 | InstructionFlags.Prefix66)); + Add(X86Instruction.Crc32_8, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38f0, InstructionFlags.PrefixF2 | InstructionFlags.Reg8Src)); + Add(X86Instruction.Cvtdq2pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fe6, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Cvtdq2ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5b, InstructionFlags.Vex)); + Add(X86Instruction.Cvtpd2dq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fe6, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Cvtpd2ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Cvtps2dq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5b, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Cvtps2pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex)); + Add(X86Instruction.Cvtsd2si, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2d, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Cvtsd2ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Cvtsi2sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2a, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Cvtsi2ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2a, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Cvtss2sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Cvtss2si, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2d, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Div, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x060000f7, InstructionFlags.None)); + Add(X86Instruction.Divpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Divps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex)); + Add(X86Instruction.Divsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Divss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Gf2p8affineqb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3ace, InstructionFlags.Prefix66)); + Add(X86Instruction.Haddpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f7c, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Haddps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f7c, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Idiv, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x070000f7, InstructionFlags.None)); + Add(X86Instruction.Imul, new InstructionInfo(BadOp, 0x0000006b, 0x00000069, BadOp, 0x00000faf, InstructionFlags.None)); + Add(X86Instruction.Imul128, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x050000f7, InstructionFlags.None)); + Add(X86Instruction.Insertps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a21, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Jmp, new InstructionInfo(0x040000ff, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Ldmxcsr, new InstructionInfo(0x02000fae, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex)); + Add(X86Instruction.Lea, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x0000008d, InstructionFlags.None)); + Add(X86Instruction.Maxpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5f, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Maxps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5f, InstructionFlags.Vex)); + Add(X86Instruction.Maxsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5f, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Maxss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5f, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Minpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5d, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Minps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5d, InstructionFlags.Vex)); + Add(X86Instruction.Minsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5d, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Minss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5d, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Mov, new InstructionInfo(0x00000089, BadOp, 0x000000c7, 0x000000b8, 0x0000008b, InstructionFlags.None)); + Add(X86Instruction.Mov16, new InstructionInfo(0x00000089, BadOp, 0x000000c7, BadOp, 0x0000008b, InstructionFlags.Prefix66)); + Add(X86Instruction.Mov8, new InstructionInfo(0x00000088, 0x000000c6, BadOp, BadOp, 0x0000008a, InstructionFlags.Reg8Src | InstructionFlags.Reg8Dest)); + Add(X86Instruction.Movd, new InstructionInfo(0x00000f7e, BadOp, BadOp, BadOp, 0x00000f6e, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Movdqu, new InstructionInfo(0x00000f7f, BadOp, BadOp, BadOp, 0x00000f6f, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Movhlps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f12, InstructionFlags.Vex)); + Add(X86Instruction.Movlhps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f16, InstructionFlags.Vex)); + Add(X86Instruction.Movq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f7e, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Movsd, new InstructionInfo(0x00000f11, BadOp, BadOp, BadOp, 0x00000f10, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Movss, new InstructionInfo(0x00000f11, BadOp, BadOp, BadOp, 0x00000f10, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Movsx16, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fbf, InstructionFlags.None)); + Add(X86Instruction.Movsx32, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000063, InstructionFlags.None)); + Add(X86Instruction.Movsx8, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fbe, InstructionFlags.Reg8Src)); + Add(X86Instruction.Movzx16, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fb7, InstructionFlags.None)); + Add(X86Instruction.Movzx8, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fb6, InstructionFlags.Reg8Src)); + Add(X86Instruction.Mul128, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x040000f7, InstructionFlags.None)); + Add(X86Instruction.Mulpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f59, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Mulps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f59, InstructionFlags.Vex)); + Add(X86Instruction.Mulsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f59, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Mulss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f59, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Neg, new InstructionInfo(0x030000f7, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Not, new InstructionInfo(0x020000f7, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Or, new InstructionInfo(0x00000009, 0x01000083, 0x01000081, BadOp, 0x0000000b, InstructionFlags.None)); + Add(X86Instruction.Paddb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffc, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Paddd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffe, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Paddq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fd4, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Paddw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffd, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Palignr, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a0f, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pand, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fdb, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pandn, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fdf, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pavgb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fe0, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pavgw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fe3, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3810, InstructionFlags.Prefix66)); + Add(X86Instruction.Pclmulqdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a44, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpeqb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f74, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpeqd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f76, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpeqq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3829, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpeqw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f75, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpgtb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f64, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpgtd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f66, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpgtq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3837, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpgtw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f65, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pextrb, new InstructionInfo(0x000f3a14, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pextrd, new InstructionInfo(0x000f3a16, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pextrq, new InstructionInfo(0x000f3a16, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.RexW | InstructionFlags.Prefix66)); + Add(X86Instruction.Pextrw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc5, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pinsrb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a20, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pinsrd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a22, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pinsrq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a22, InstructionFlags.Vex | InstructionFlags.RexW | InstructionFlags.Prefix66)); + Add(X86Instruction.Pinsrw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc4, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmaxsb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383c, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmaxsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383d, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmaxsw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fee, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmaxub, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fde, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmaxud, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383f, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmaxuw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383e, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pminsb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3838, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pminsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3839, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pminsw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fea, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pminub, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fda, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pminud, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383b, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pminuw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383a, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmovsxbw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3820, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmovsxdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3825, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmovsxwd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3823, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmovzxbw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3830, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmovzxdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3835, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmovzxwd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3833, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmulld, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3840, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmullw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fd5, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pop, new InstructionInfo(0x0000008f, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Popcnt, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fb8, InstructionFlags.PrefixF3)); + Add(X86Instruction.Por, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000feb, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pshufb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3800, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pshufd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f70, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pslld, new InstructionInfo(BadOp, 0x06000f72, BadOp, BadOp, 0x00000ff2, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pslldq, new InstructionInfo(BadOp, 0x07000f73, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psllq, new InstructionInfo(BadOp, 0x06000f73, BadOp, BadOp, 0x00000ff3, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psllw, new InstructionInfo(BadOp, 0x06000f71, BadOp, BadOp, 0x00000ff1, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psrad, new InstructionInfo(BadOp, 0x04000f72, BadOp, BadOp, 0x00000fe2, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psraw, new InstructionInfo(BadOp, 0x04000f71, BadOp, BadOp, 0x00000fe1, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psrld, new InstructionInfo(BadOp, 0x02000f72, BadOp, BadOp, 0x00000fd2, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psrlq, new InstructionInfo(BadOp, 0x02000f73, BadOp, BadOp, 0x00000fd3, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psrldq, new InstructionInfo(BadOp, 0x03000f73, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psrlw, new InstructionInfo(BadOp, 0x02000f71, BadOp, BadOp, 0x00000fd1, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psubb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ff8, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psubd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffa, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psubq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffb, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psubw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ff9, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpckhbw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f68, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpckhdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f6a, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpckhqdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f6d, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpckhwd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f69, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpcklbw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f60, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpckldq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f62, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpcklqdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f6c, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpcklwd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f61, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Push, new InstructionInfo(BadOp, 0x0000006a, 0x00000068, BadOp, 0x060000ff, InstructionFlags.None)); + Add(X86Instruction.Pxor, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fef, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Rcpps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f53, InstructionFlags.Vex)); + Add(X86Instruction.Rcpss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f53, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Ror, new InstructionInfo(0x010000d3, 0x010000c1, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Roundpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a09, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Roundps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a08, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Roundsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a0b, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Roundss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a0a, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Rsqrtps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f52, InstructionFlags.Vex)); + Add(X86Instruction.Rsqrtss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f52, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Sar, new InstructionInfo(0x070000d3, 0x070000c1, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Setcc, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f90, InstructionFlags.Reg8Dest)); + Add(X86Instruction.Sha256Msg1, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38cc, InstructionFlags.None)); + Add(X86Instruction.Sha256Msg2, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38cd, InstructionFlags.None)); + Add(X86Instruction.Sha256Rnds2, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38cb, InstructionFlags.None)); + Add(X86Instruction.Shl, new InstructionInfo(0x040000d3, 0x040000c1, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Shr, new InstructionInfo(0x050000d3, 0x050000c1, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Shufpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc6, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Shufps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc6, InstructionFlags.Vex)); + Add(X86Instruction.Sqrtpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f51, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Sqrtps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f51, InstructionFlags.Vex)); + Add(X86Instruction.Sqrtsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f51, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Sqrtss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f51, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Stmxcsr, new InstructionInfo(0x03000fae, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex)); + Add(X86Instruction.Sub, new InstructionInfo(0x00000029, 0x05000083, 0x05000081, BadOp, 0x0000002b, InstructionFlags.None)); + Add(X86Instruction.Subpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5c, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Subps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5c, InstructionFlags.Vex)); + Add(X86Instruction.Subsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5c, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Subss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5c, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Test, new InstructionInfo(0x00000085, BadOp, 0x000000f7, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Unpckhpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f15, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Unpckhps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f15, InstructionFlags.Vex)); + Add(X86Instruction.Unpcklpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f14, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Unpcklps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f14, InstructionFlags.Vex)); + Add(X86Instruction.Vblendvpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4b, InstructionFlags.Vex | InstructionFlags.Prefix66)); + 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)); + Add(X86Instruction.Vfnmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW)); + Add(X86Instruction.Vfnmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Vpternlogd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a25, InstructionFlags.Evex | InstructionFlags.Prefix66)); + Add(X86Instruction.Xor, new InstructionInfo(0x00000031, 0x06000083, 0x06000081, BadOp, 0x00000033, InstructionFlags.None)); + Add(X86Instruction.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex)); +#pragma warning restore IDE0055 + + static void Add(X86Instruction inst, in InstructionInfo info) + { + _instTable[(int)inst] = info; + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/CallConvName.cs b/src/ARMeilleure/CodeGen/X86/CallConvName.cs new file mode 100644 index 00000000..6208da1e --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CallConvName.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.CodeGen.X86 +{ + enum CallConvName + { + SystemV, + Windows, + } +} diff --git a/src/ARMeilleure/CodeGen/X86/CallingConvention.cs b/src/ARMeilleure/CodeGen/X86/CallingConvention.cs new file mode 100644 index 00000000..8433aaea --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CallingConvention.cs @@ -0,0 +1,170 @@ +using System; + +namespace ARMeilleure.CodeGen.X86 +{ + static class CallingConvention + { + private const int RegistersMask = 0xffff; + + public static int GetIntAvailableRegisters() + { + return RegistersMask & ~(1 << (int)X86Register.Rsp); + } + + public static int GetVecAvailableRegisters() + { + return RegistersMask; + } + + public static int GetIntCallerSavedRegisters() + { + if (GetCurrentCallConv() == CallConvName.Windows) + { +#pragma warning disable IDE0055 // Disable formatting + return (1 << (int)X86Register.Rax) | + (1 << (int)X86Register.Rcx) | + (1 << (int)X86Register.Rdx) | + (1 << (int)X86Register.R8) | + (1 << (int)X86Register.R9) | + (1 << (int)X86Register.R10) | + (1 << (int)X86Register.R11); + } + else /* if (GetCurrentCallConv() == CallConvName.SystemV) */ + { + return (1 << (int)X86Register.Rax) | + (1 << (int)X86Register.Rcx) | + (1 << (int)X86Register.Rdx) | + (1 << (int)X86Register.Rsi) | + (1 << (int)X86Register.Rdi) | + (1 << (int)X86Register.R8) | + (1 << (int)X86Register.R9) | + (1 << (int)X86Register.R10) | + (1 << (int)X86Register.R11); +#pragma warning restore IDE0055 + } + } + + public static int GetVecCallerSavedRegisters() + { + if (GetCurrentCallConv() == CallConvName.Windows) + { + return (1 << (int)X86Register.Xmm0) | + (1 << (int)X86Register.Xmm1) | + (1 << (int)X86Register.Xmm2) | + (1 << (int)X86Register.Xmm3) | + (1 << (int)X86Register.Xmm4) | + (1 << (int)X86Register.Xmm5); + } + else /* if (GetCurrentCallConv() == CallConvName.SystemV) */ + { + return RegistersMask; + } + } + + public static int GetIntCalleeSavedRegisters() + { + return GetIntCallerSavedRegisters() ^ RegistersMask; + } + + public static int GetVecCalleeSavedRegisters() + { + return GetVecCallerSavedRegisters() ^ RegistersMask; + } + + public static int GetArgumentsOnRegsCount() + { + return 4; + } + + public static int GetIntArgumentsOnRegsCount() + { + return 6; + } + + public static int GetVecArgumentsOnRegsCount() + { + return 8; + } + + public static X86Register GetIntArgumentRegister(int index) + { + if (GetCurrentCallConv() == CallConvName.Windows) + { + switch (index) + { + case 0: + return X86Register.Rcx; + case 1: + return X86Register.Rdx; + case 2: + return X86Register.R8; + case 3: + return X86Register.R9; + } + } + else /* if (GetCurrentCallConv() == CallConvName.SystemV) */ + { + switch (index) + { + case 0: + return X86Register.Rdi; + case 1: + return X86Register.Rsi; + case 2: + return X86Register.Rdx; + case 3: + return X86Register.Rcx; + case 4: + return X86Register.R8; + case 5: + return X86Register.R9; + } + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public static X86Register GetVecArgumentRegister(int index) + { + int count; + + if (GetCurrentCallConv() == CallConvName.Windows) + { + count = 4; + } + else /* if (GetCurrentCallConv() == CallConvName.SystemV) */ + { + count = 8; + } + + if ((uint)index < count) + { + return X86Register.Xmm0 + index; + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public static X86Register GetIntReturnRegister() + { + return X86Register.Rax; + } + + public static X86Register GetIntReturnRegisterHigh() + { + return X86Register.Rdx; + } + + public static X86Register GetVecReturnRegister() + { + return X86Register.Xmm0; + } + + public static CallConvName GetCurrentCallConv() + { + return OperatingSystem.IsWindows() + ? CallConvName.Windows + : CallConvName.SystemV; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/CodeGenCommon.cs b/src/ARMeilleure/CodeGen/X86/CodeGenCommon.cs new file mode 100644 index 00000000..ae83ea80 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CodeGenCommon.cs @@ -0,0 +1,19 @@ +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.CodeGen.X86 +{ + static class CodeGenCommon + { + public static bool IsLongConst(Operand op) + { + long value = op.Type == OperandType.I32 ? op.AsInt32() : op.AsInt64(); + + return !ConstFitsOnS32(value); + } + + private static bool ConstFitsOnS32(long value) + { + return value == (int)value; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/CodeGenContext.cs b/src/ARMeilleure/CodeGen/X86/CodeGenContext.cs new file mode 100644 index 00000000..d4d4c205 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CodeGenContext.cs @@ -0,0 +1,105 @@ +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using Ryujinx.Common.Memory; +using System.IO; +using System.Numerics; + +namespace ARMeilleure.CodeGen.X86 +{ + class CodeGenContext + { + private readonly Stream _stream; + private readonly Operand[] _blockLabels; + + public int StreamOffset => (int)_stream.Length; + + public AllocationResult AllocResult { get; } + + public Assembler Assembler { get; } + public BasicBlock CurrBlock { get; private set; } + + public int CallArgsRegionSize { get; } + public int XmmSaveRegionSize { get; } + + public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable) + { + _stream = MemoryStreamManager.Shared.GetStream(); + _blockLabels = new Operand[blocksCount]; + + AllocResult = allocResult; + Assembler = new Assembler(_stream, relocatable); + + CallArgsRegionSize = GetCallArgsRegionSize(allocResult, maxCallArgs, out int xmmSaveRegionSize); + XmmSaveRegionSize = xmmSaveRegionSize; + } + + private static int GetCallArgsRegionSize(AllocationResult allocResult, int maxCallArgs, out int xmmSaveRegionSize) + { + // We need to add 8 bytes to the total size, as the call to this function already pushed 8 bytes (the + // return address). + int intMask = CallingConvention.GetIntCalleeSavedRegisters() & allocResult.IntUsedRegisters; + int vecMask = CallingConvention.GetVecCalleeSavedRegisters() & allocResult.VecUsedRegisters; + + xmmSaveRegionSize = BitOperations.PopCount((uint)vecMask) * 16; + + int calleeSaveRegionSize = BitOperations.PopCount((uint)intMask) * 8 + xmmSaveRegionSize + 8; + + int argsCount = maxCallArgs; + + if (argsCount < 0) + { + // When the function has no calls, argsCount is -1. In this case, we don't need to allocate the shadow + // space. + argsCount = 0; + } + else if (argsCount < 4) + { + // The ABI mandates that the space for at least 4 arguments is reserved on the stack (this is called + // shadow space). + argsCount = 4; + } + + // TODO: Align XMM save region to 16 bytes because unwinding on Windows requires it. + int frameSize = calleeSaveRegionSize + allocResult.SpillRegionSize; + + // TODO: Instead of always multiplying by 16 (the largest possible size of a variable, since a V128 has 16 + // bytes), we should calculate the exact size consumed by the arguments passed to the called functions on + // the stack. + int callArgsAndFrameSize = frameSize + argsCount * 16; + + // Ensure that the Stack Pointer will be aligned to 16 bytes. + callArgsAndFrameSize = (callArgsAndFrameSize + 0xf) & ~0xf; + + return callArgsAndFrameSize - frameSize; + } + + public void EnterBlock(BasicBlock block) + { + Assembler.MarkLabel(GetLabel(block)); + + CurrBlock = block; + } + + public void JumpTo(BasicBlock target) + { + Assembler.Jmp(GetLabel(target)); + } + + public void JumpTo(X86Condition condition, BasicBlock target) + { + Assembler.Jcc(condition, GetLabel(target)); + } + + private Operand GetLabel(BasicBlock block) + { + ref Operand label = ref _blockLabels[block.Index]; + + if (label == default) + { + label = Operand.Factory.Label(); + } + + return label; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/CodeGenerator.cs b/src/ARMeilleure/CodeGen/X86/CodeGenerator.cs new file mode 100644 index 00000000..9e94a077 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CodeGenerator.cs @@ -0,0 +1,1891 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Common; +using ARMeilleure.Diagnostics; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + static class CodeGenerator + { + private const int RegistersCount = 16; + private const int PageSize = 0x1000; + private const int StackGuardSize = 0x2000; + + private static readonly Action[] _instTable; + + static CodeGenerator() + { + _instTable = new Action[EnumUtils.GetCount(typeof(Instruction))]; + +#pragma warning disable IDE0055 // Disable formatting + Add(Instruction.Add, GenerateAdd); + Add(Instruction.BitwiseAnd, GenerateBitwiseAnd); + Add(Instruction.BitwiseExclusiveOr, GenerateBitwiseExclusiveOr); + Add(Instruction.BitwiseNot, GenerateBitwiseNot); + Add(Instruction.BitwiseOr, GenerateBitwiseOr); + Add(Instruction.BranchIf, GenerateBranchIf); + Add(Instruction.ByteSwap, GenerateByteSwap); + Add(Instruction.Call, GenerateCall); + Add(Instruction.Clobber, GenerateClobber); + Add(Instruction.Compare, GenerateCompare); + Add(Instruction.CompareAndSwap, GenerateCompareAndSwap); + Add(Instruction.CompareAndSwap16, GenerateCompareAndSwap16); + Add(Instruction.CompareAndSwap8, GenerateCompareAndSwap8); + Add(Instruction.ConditionalSelect, GenerateConditionalSelect); + Add(Instruction.ConvertI64ToI32, GenerateConvertI64ToI32); + Add(Instruction.ConvertToFP, GenerateConvertToFP); + Add(Instruction.Copy, GenerateCopy); + Add(Instruction.CountLeadingZeros, GenerateCountLeadingZeros); + Add(Instruction.Divide, GenerateDivide); + Add(Instruction.DivideUI, GenerateDivideUI); + Add(Instruction.Fill, GenerateFill); + Add(Instruction.Load, GenerateLoad); + Add(Instruction.Load16, GenerateLoad16); + Add(Instruction.Load8, GenerateLoad8); + Add(Instruction.MemoryBarrier, GenerateMemoryBarrier); + Add(Instruction.Multiply, GenerateMultiply); + Add(Instruction.Multiply64HighSI, GenerateMultiply64HighSI); + Add(Instruction.Multiply64HighUI, GenerateMultiply64HighUI); + Add(Instruction.Negate, GenerateNegate); + Add(Instruction.Return, GenerateReturn); + Add(Instruction.RotateRight, GenerateRotateRight); + Add(Instruction.ShiftLeft, GenerateShiftLeft); + Add(Instruction.ShiftRightSI, GenerateShiftRightSI); + Add(Instruction.ShiftRightUI, GenerateShiftRightUI); + Add(Instruction.SignExtend16, GenerateSignExtend16); + Add(Instruction.SignExtend32, GenerateSignExtend32); + Add(Instruction.SignExtend8, GenerateSignExtend8); + Add(Instruction.Spill, GenerateSpill); + Add(Instruction.SpillArg, GenerateSpillArg); + Add(Instruction.StackAlloc, GenerateStackAlloc); + Add(Instruction.Store, GenerateStore); + Add(Instruction.Store16, GenerateStore16); + Add(Instruction.Store8, GenerateStore8); + Add(Instruction.Subtract, GenerateSubtract); + Add(Instruction.Tailcall, GenerateTailcall); + Add(Instruction.VectorCreateScalar, GenerateVectorCreateScalar); + Add(Instruction.VectorExtract, GenerateVectorExtract); + Add(Instruction.VectorExtract16, GenerateVectorExtract16); + Add(Instruction.VectorExtract8, GenerateVectorExtract8); + Add(Instruction.VectorInsert, GenerateVectorInsert); + Add(Instruction.VectorInsert16, GenerateVectorInsert16); + Add(Instruction.VectorInsert8, GenerateVectorInsert8); + Add(Instruction.VectorOne, GenerateVectorOne); + Add(Instruction.VectorZero, GenerateVectorZero); + Add(Instruction.VectorZeroUpper64, GenerateVectorZeroUpper64); + Add(Instruction.VectorZeroUpper96, GenerateVectorZeroUpper96); + Add(Instruction.ZeroExtend16, GenerateZeroExtend16); + Add(Instruction.ZeroExtend32, GenerateZeroExtend32); + Add(Instruction.ZeroExtend8, GenerateZeroExtend8); +#pragma warning restore IDE0055 + + static void Add(Instruction inst, Action func) + { + _instTable[(int)inst] = func; + } + } + + public static CompiledFunction Generate(CompilerContext cctx) + { + ControlFlowGraph cfg = cctx.Cfg; + + Logger.StartPass(PassName.Optimization); + + if (cctx.Options.HasFlag(CompilerOptions.Optimize)) + { + if (cctx.Options.HasFlag(CompilerOptions.SsaForm)) + { + Optimizer.RunPass(cfg); + } + + BlockPlacement.RunPass(cfg); + } + + X86Optimizer.RunPass(cfg); + + Logger.EndPass(PassName.Optimization, cfg); + + Logger.StartPass(PassName.PreAllocation); + + StackAllocator stackAlloc = new(); + + PreAllocator.RunPass(cctx, stackAlloc, out int maxCallArgs); + + Logger.EndPass(PassName.PreAllocation, cfg); + + Logger.StartPass(PassName.RegisterAllocation); + + if (cctx.Options.HasFlag(CompilerOptions.SsaForm)) + { + Ssa.Deconstruct(cfg); + } + + IRegisterAllocator regAlloc; + + if (cctx.Options.HasFlag(CompilerOptions.Lsra)) + { + regAlloc = new LinearScanAllocator(); + } + else + { + regAlloc = new HybridAllocator(); + } + + RegisterMasks regMasks = new( + CallingConvention.GetIntAvailableRegisters(), + CallingConvention.GetVecAvailableRegisters(), + CallingConvention.GetIntCallerSavedRegisters(), + CallingConvention.GetVecCallerSavedRegisters(), + CallingConvention.GetIntCalleeSavedRegisters(), + CallingConvention.GetVecCalleeSavedRegisters(), + RegistersCount); + + AllocationResult allocResult = regAlloc.RunPass(cfg, stackAlloc, regMasks); + + Logger.EndPass(PassName.RegisterAllocation, cfg); + + Logger.StartPass(PassName.CodeGeneration); + + bool relocatable = (cctx.Options & CompilerOptions.Relocatable) != 0; + + CodeGenContext context = new(allocResult, maxCallArgs, cfg.Blocks.Count, relocatable); + + UnwindInfo unwindInfo = WritePrologue(context); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + context.EnterBlock(block); + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + GenerateOperation(context, node); + } + + if (block.SuccessorsCount == 0) + { + // The only blocks which can have 0 successors are exit blocks. + Operation last = block.Operations.Last; + + Debug.Assert(last.Instruction == Instruction.Tailcall || + last.Instruction == Instruction.Return); + } + else + { + BasicBlock succ = block.GetSuccessor(0); + + if (succ != block.ListNext) + { + context.JumpTo(succ); + } + } + } + + (byte[] code, RelocInfo relocInfo) = context.Assembler.GetCode(); + + Logger.EndPass(PassName.CodeGeneration); + + return new CompiledFunction(code, unwindInfo, relocInfo); + } + + private static void GenerateOperation(CodeGenContext context, Operation operation) + { + if (operation.Instruction == Instruction.Extended) + { + IntrinsicInfo info = IntrinsicTable.GetInfo(operation.Intrinsic); + + switch (info.Type) + { + case IntrinsicType.Comis_: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + switch (operation.Intrinsic) + { + case Intrinsic.X86Comisdeq: + context.Assembler.Comisd(src1, src2); + context.Assembler.Setcc(dest, X86Condition.Equal); + break; + + case Intrinsic.X86Comisdge: + context.Assembler.Comisd(src1, src2); + context.Assembler.Setcc(dest, X86Condition.AboveOrEqual); + break; + + case Intrinsic.X86Comisdlt: + context.Assembler.Comisd(src1, src2); + context.Assembler.Setcc(dest, X86Condition.Below); + break; + + case Intrinsic.X86Comisseq: + context.Assembler.Comiss(src1, src2); + context.Assembler.Setcc(dest, X86Condition.Equal); + break; + + case Intrinsic.X86Comissge: + context.Assembler.Comiss(src1, src2); + context.Assembler.Setcc(dest, X86Condition.AboveOrEqual); + break; + + case Intrinsic.X86Comisslt: + context.Assembler.Comiss(src1, src2); + context.Assembler.Setcc(dest, X86Condition.Below); + break; + } + + context.Assembler.Movzx8(dest, dest, OperandType.I32); + + break; + } + + case IntrinsicType.Mxcsr: + { + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + Debug.Assert(offset.Type == OperandType.I32); + + int offs = offset.AsInt32() + context.CallArgsRegionSize; + + Operand rsp = Register(X86Register.Rsp); + Operand memOp = MemoryOp(OperandType.I32, rsp, default, Multiplier.x1, offs); + + Debug.Assert(HardwareCapabilities.SupportsSse || HardwareCapabilities.SupportsVexEncoding); + + if (operation.Intrinsic == Intrinsic.X86Ldmxcsr) + { + Operand bits = operation.GetSource(1); + Debug.Assert(bits.Type == OperandType.I32); + + context.Assembler.Mov(memOp, bits, OperandType.I32); + context.Assembler.Ldmxcsr(memOp); + } + else if (operation.Intrinsic == Intrinsic.X86Stmxcsr) + { + Operand dest = operation.Destination; + Debug.Assert(dest.Type == OperandType.I32); + + context.Assembler.Stmxcsr(memOp); + context.Assembler.Mov(dest, memOp, OperandType.I32); + } + + break; + } + + case IntrinsicType.PopCount: + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Popcnt(dest, source, dest.Type); + + break; + } + + case IntrinsicType.Unary: + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.WriteInstruction(info.Inst, dest, source); + + break; + } + + case IntrinsicType.UnaryToGpr: + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && !source.Type.IsInteger()); + + if (operation.Intrinsic == Intrinsic.X86Cvtsi2si) + { + if (dest.Type == OperandType.I32) + { + context.Assembler.Movd(dest, source); // int _mm_cvtsi128_si32(__m128i a) + } + else /* if (dest.Type == OperandType.I64) */ + { + context.Assembler.Movq(dest, source); // __int64 _mm_cvtsi128_si64(__m128i a) + } + } + else + { + context.Assembler.WriteInstruction(info.Inst, dest, source, dest.Type); + } + + break; + } + + case IntrinsicType.Binary: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(!dest.Type.IsInteger()); + Debug.Assert(!src2.Type.IsInteger() || src2.Kind == OperandKind.Constant); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2); + + break; + } + + case IntrinsicType.BinaryGpr: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(!dest.Type.IsInteger() && src2.Type.IsInteger()); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2, src2.Type); + + break; + } + + case IntrinsicType.Crc32: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameReg(dest, src1); + + Debug.Assert(dest.Type.IsInteger() && src1.Type.IsInteger() && src2.Type.IsInteger()); + + context.Assembler.WriteInstruction(info.Inst, dest, src2, dest.Type); + + break; + } + + case IntrinsicType.BinaryImm: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(!dest.Type.IsInteger() && src2.Kind == OperandKind.Constant); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2.AsByte()); + + break; + } + + case IntrinsicType.Ternary: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(dest, src1, src2, src3); + + Debug.Assert(!dest.Type.IsInteger()); + + if (info.Inst == X86Instruction.Blendvpd && HardwareCapabilities.SupportsVexEncoding) + { + context.Assembler.WriteInstruction(X86Instruction.Vblendvpd, dest, src1, src2, src3); + } + else if (info.Inst == X86Instruction.Blendvps && HardwareCapabilities.SupportsVexEncoding) + { + context.Assembler.WriteInstruction(X86Instruction.Vblendvps, dest, src1, src2, src3); + } + else if (info.Inst == X86Instruction.Pblendvb && HardwareCapabilities.SupportsVexEncoding) + { + context.Assembler.WriteInstruction(X86Instruction.Vpblendvb, dest, src1, src2, src3); + } + else + { + EnsureSameReg(dest, src1); + + Debug.Assert(src3.GetRegister().Index == 0); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2); + } + + break; + } + + case IntrinsicType.TernaryImm: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(dest, src1, src2); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(!dest.Type.IsInteger() && src3.Kind == OperandKind.Constant); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2, src3.AsByte()); + + break; + } + + case IntrinsicType.Fma: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + Debug.Assert(HardwareCapabilities.SupportsVexEncoding); + + Debug.Assert(dest.Kind == OperandKind.Register && src1.Kind == OperandKind.Register && src2.Kind == OperandKind.Register); + Debug.Assert(src3.Kind == OperandKind.Register || src3.Kind == OperandKind.Memory); + + EnsureSameType(dest, src1, src2, src3); + Debug.Assert(dest.Type == OperandType.V128); + + Debug.Assert(dest.Value == src1.Value); + + context.Assembler.WriteInstruction(info.Inst, dest, src2, src3); + + break; + } + } + } + else + { + Action func = _instTable[(int)operation.Instruction]; + + if (func != null) + { + func(context, operation); + } + else + { + throw new ArgumentException($"Invalid instruction \"{operation.Instruction}\"."); + } + } + } + + private static void GenerateAdd(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + if (dest.Type.IsInteger()) + { + // If Destination and Source 1 Operands are the same, perform a standard add as there are no benefits to using LEA. + if (dest.Kind == src1.Kind && dest.Value == src1.Value) + { + ValidateBinOp(dest, src1, src2); + + context.Assembler.Add(dest, src2, dest.Type); + } + else + { + EnsureSameType(dest, src1, src2); + + int offset; + Operand index; + + if (src2.Kind == OperandKind.Constant) + { + offset = src2.AsInt32(); + index = default; + } + else + { + offset = 0; + index = src2; + } + + Operand memOp = MemoryOp(dest.Type, src1, index, Multiplier.x1, offset); + + context.Assembler.Lea(dest, memOp, dest.Type); + } + } + else + { + ValidateBinOp(dest, src1, src2); + + if (dest.Type == OperandType.FP32) + { + context.Assembler.Addss(dest, src1, src2); + } + else /* if (dest.Type == OperandType.FP64) */ + { + context.Assembler.Addsd(dest, src1, src2); + } + } + } + + private static void GenerateBitwiseAnd(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + Debug.Assert(dest.Type.IsInteger()); + + // Note: GenerateCompareCommon makes the assumption that BitwiseAnd will emit only a single `and` + // instruction. + context.Assembler.And(dest, src2, dest.Type); + } + + private static void GenerateBitwiseExclusiveOr(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Xor(dest, src2, dest.Type); + } + else + { + context.Assembler.Xorps(dest, src1, src2); + } + } + + private static void GenerateBitwiseNot(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Not(dest); + } + + private static void GenerateBitwiseOr(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Or(dest, src2, dest.Type); + } + + private static void GenerateBranchIf(CodeGenContext context, Operation operation) + { + Operand comp = operation.GetSource(2); + + Debug.Assert(comp.Kind == OperandKind.Constant); + + var cond = ((Comparison)comp.AsInt32()).ToX86Condition(); + + GenerateCompareCommon(context, operation); + + context.JumpTo(cond, context.CurrBlock.GetSuccessor(1)); + } + + private static void GenerateByteSwap(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Bswap(dest); + } + + private static void GenerateCall(CodeGenContext context, Operation operation) + { + context.Assembler.Call(operation.GetSource(0)); + } + + private static void GenerateClobber(CodeGenContext context, Operation operation) + { + // This is only used to indicate that a register is clobbered to the + // register allocator, we don't need to produce any code. + } + + private static void GenerateCompare(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand comp = operation.GetSource(2); + + Debug.Assert(dest.Type == OperandType.I32); + Debug.Assert(comp.Kind == OperandKind.Constant); + + var cond = ((Comparison)comp.AsInt32()).ToX86Condition(); + + GenerateCompareCommon(context, operation); + + context.Assembler.Setcc(dest, cond); + context.Assembler.Movzx8(dest, dest, OperandType.I32); + } + + private static void GenerateCompareCommon(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(src1, src2); + + Debug.Assert(src1.Type.IsInteger()); + + if (src2.Kind == OperandKind.Constant && src2.Value == 0) + { + if (MatchOperation(operation.ListPrevious, Instruction.BitwiseAnd, src1.Type, src1.GetRegister())) + { + // Since the `test` and `and` instruction set the status flags in the same way, we can omit the + // `test r,r` instruction when it is immediately preceded by an `and r,*` instruction. + // + // For example: + // + // and eax, 0x3 + // test eax, eax + // jz .L0 + // + // => + // + // and eax, 0x3 + // jz .L0 + } + else + { + context.Assembler.Test(src1, src1, src1.Type); + } + } + else + { + context.Assembler.Cmp(src1, src2, src1.Type); + } + } + + private static void GenerateCompareAndSwap(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + + if (operation.SourcesCount == 5) // CompareAndSwap128 has 5 sources, compared to CompareAndSwap64/32's 3. + { + Operand memOp = MemoryOp(OperandType.I64, src1); + + context.Assembler.Cmpxchg16b(memOp); + } + else + { + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(src2, src3); + + Operand memOp = MemoryOp(src3.Type, src1); + + context.Assembler.Cmpxchg(memOp, src3); + } + } + + private static void GenerateCompareAndSwap16(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(src2, src3); + + Operand memOp = MemoryOp(src3.Type, src1); + + context.Assembler.Cmpxchg16(memOp, src3); + } + + private static void GenerateCompareAndSwap8(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(src2, src3); + + Operand memOp = MemoryOp(src3.Type, src1); + + context.Assembler.Cmpxchg8(memOp, src3); + } + + private static void GenerateConditionalSelect(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameReg(dest, src3); + EnsureSameType(dest, src2, src3); + + Debug.Assert(dest.Type.IsInteger()); + Debug.Assert(src1.Type == OperandType.I32); + + context.Assembler.Test(src1, src1, src1.Type); + context.Assembler.Cmovcc(dest, src2, dest.Type, X86Condition.NotEqual); + } + + private static void GenerateConvertI64ToI32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.I32 && source.Type == OperandType.I64); + + context.Assembler.Mov(dest, source, OperandType.I32); + } + + private static void GenerateConvertToFP(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.FP32 || dest.Type == OperandType.FP64); + + if (dest.Type == OperandType.FP32) + { + Debug.Assert(source.Type.IsInteger() || source.Type == OperandType.FP64); + + if (source.Type.IsInteger()) + { + context.Assembler.Xorps(dest, dest, dest); + context.Assembler.Cvtsi2ss(dest, dest, source, source.Type); + } + else /* if (source.Type == OperandType.FP64) */ + { + context.Assembler.Cvtsd2ss(dest, dest, source); + + GenerateZeroUpper96(context, dest, dest); + } + } + else /* if (dest.Type == OperandType.FP64) */ + { + Debug.Assert(source.Type.IsInteger() || source.Type == OperandType.FP32); + + if (source.Type.IsInteger()) + { + context.Assembler.Xorps(dest, dest, dest); + context.Assembler.Cvtsi2sd(dest, dest, source, source.Type); + } + else /* if (source.Type == OperandType.FP32) */ + { + context.Assembler.Cvtss2sd(dest, dest, source); + + GenerateZeroUpper64(context, dest, dest); + } + } + } + + private static void GenerateCopy(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger() || source.Kind != OperandKind.Constant); + + // Moves to the same register are useless. + if (dest.Kind == source.Kind && dest.Value == source.Value) + { + return; + } + + if (dest.Kind == OperandKind.Register && + source.Kind == OperandKind.Constant && source.Value == 0) + { + // Assemble "mov reg, 0" as "xor reg, reg" as the later is more efficient. + context.Assembler.Xor(dest, dest, OperandType.I32); + } + else if (dest.Type.IsInteger()) + { + context.Assembler.Mov(dest, source, dest.Type); + } + else + { + context.Assembler.Movdqu(dest, source); + } + } + + private static void GenerateCountLeadingZeros(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Bsr(dest, source, dest.Type); + + int operandSize = dest.Type == OperandType.I32 ? 32 : 64; + int operandMask = operandSize - 1; + + // When the input operand is 0, the result is undefined, however the + // ZF flag is set. We are supposed to return the operand size on that + // case. So, add an additional jump to handle that case, by moving the + // operand size constant to the destination register. + Operand neLabel = Label(); + + context.Assembler.Jcc(X86Condition.NotEqual, neLabel); + + context.Assembler.Mov(dest, Const(operandSize | operandMask), OperandType.I32); + + context.Assembler.MarkLabel(neLabel); + + // BSR returns the zero based index of the last bit set on the operand, + // starting from the least significant bit. However we are supposed to + // return the number of 0 bits on the high end. So, we invert the result + // of the BSR using XOR to get the correct value. + context.Assembler.Xor(dest, Const(operandMask), OperandType.I32); + } + + private static void GenerateDivide(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand dividend = operation.GetSource(0); + Operand divisor = operation.GetSource(1); + + if (!dest.Type.IsInteger()) + { + ValidateBinOp(dest, dividend, divisor); + } + + if (dest.Type.IsInteger()) + { + divisor = operation.GetSource(2); + + EnsureSameType(dest, divisor); + + if (divisor.Type == OperandType.I32) + { + context.Assembler.Cdq(); + } + else + { + context.Assembler.Cqo(); + } + + context.Assembler.Idiv(divisor); + } + else if (dest.Type == OperandType.FP32) + { + context.Assembler.Divss(dest, dividend, divisor); + } + else /* if (dest.Type == OperandType.FP64) */ + { + context.Assembler.Divsd(dest, dividend, divisor); + } + } + + private static void GenerateDivideUI(CodeGenContext context, Operation operation) + { + Operand divisor = operation.GetSource(2); + + Operand rdx = Register(X86Register.Rdx); + + Debug.Assert(divisor.Type.IsInteger()); + + context.Assembler.Xor(rdx, rdx, OperandType.I32); + context.Assembler.Div(divisor); + } + + private static void GenerateFill(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + context.CallArgsRegionSize; + + Operand rsp = Register(X86Register.Rsp); + + Operand memOp = MemoryOp(dest.Type, rsp, default, Multiplier.x1, offs); + + GenerateLoad(context, memOp, dest); + } + + private static void GenerateLoad(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = Memory(operation.GetSource(0), value.Type); + + GenerateLoad(context, address, value); + } + + private static void GenerateLoad16(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = Memory(operation.GetSource(0), value.Type); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.Movzx16(value, address, value.Type); + } + + private static void GenerateLoad8(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = Memory(operation.GetSource(0), value.Type); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.Movzx8(value, address, value.Type); + } + + private static void GenerateMemoryBarrier(CodeGenContext context, Operation operation) + { + context.Assembler.LockOr(MemoryOp(OperandType.I64, Register(X86Register.Rsp)), Const(0), OperandType.I32); + } + + private static void GenerateMultiply(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + if (src2.Kind != OperandKind.Constant) + { + EnsureSameReg(dest, src1); + } + + EnsureSameType(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + if (src2.Kind == OperandKind.Constant) + { + context.Assembler.Imul(dest, src1, src2, dest.Type); + } + else + { + context.Assembler.Imul(dest, src2, dest.Type); + } + } + else if (dest.Type == OperandType.FP32) + { + context.Assembler.Mulss(dest, src1, src2); + } + else /* if (dest.Type == OperandType.FP64) */ + { + context.Assembler.Mulsd(dest, src1, src2); + } + } + + private static void GenerateMultiply64HighSI(CodeGenContext context, Operation operation) + { + Operand source = operation.GetSource(1); + + Debug.Assert(source.Type == OperandType.I64); + + context.Assembler.Imul(source); + } + + private static void GenerateMultiply64HighUI(CodeGenContext context, Operation operation) + { + Operand source = operation.GetSource(1); + + Debug.Assert(source.Type == OperandType.I64); + + context.Assembler.Mul(source); + } + + private static void GenerateNegate(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Neg(dest); + } + + private static void GenerateReturn(CodeGenContext context, Operation operation) + { + WriteEpilogue(context); + + context.Assembler.Return(); + } + + private static void GenerateRotateRight(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Ror(dest, src2, dest.Type); + } + + private static void GenerateShiftLeft(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Shl(dest, src2, dest.Type); + } + + private static void GenerateShiftRightSI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Sar(dest, src2, dest.Type); + } + + private static void GenerateShiftRightUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Shr(dest, src2, dest.Type); + } + + private static void GenerateSignExtend16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movsx16(dest, source, dest.Type); + } + + private static void GenerateSignExtend32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movsx32(dest, source, dest.Type); + } + + private static void GenerateSignExtend8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movsx8(dest, source, dest.Type); + } + + private static void GenerateSpill(CodeGenContext context, Operation operation) + { + GenerateSpill(context, operation, context.CallArgsRegionSize); + } + + private static void GenerateSpillArg(CodeGenContext context, Operation operation) + { + GenerateSpill(context, operation, 0); + } + + private static void GenerateSpill(CodeGenContext context, Operation operation, int baseOffset) + { + Operand offset = operation.GetSource(0); + Operand source = operation.GetSource(1); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + baseOffset; + + Operand rsp = Register(X86Register.Rsp); + + Operand memOp = MemoryOp(source.Type, rsp, default, Multiplier.x1, offs); + + GenerateStore(context, memOp, source); + } + + private static void GenerateStackAlloc(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + context.CallArgsRegionSize; + + Operand rsp = Register(X86Register.Rsp); + + Operand memOp = MemoryOp(OperandType.I64, rsp, default, Multiplier.x1, offs); + + context.Assembler.Lea(dest, memOp, OperandType.I64); + } + + private static void GenerateStore(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = Memory(operation.GetSource(0), value.Type); + + GenerateStore(context, address, value); + } + + private static void GenerateStore16(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = Memory(operation.GetSource(0), value.Type); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.Mov16(address, value); + } + + private static void GenerateStore8(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = Memory(operation.GetSource(0), value.Type); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.Mov8(address, value); + } + + private static void GenerateSubtract(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Sub(dest, src2, dest.Type); + } + else if (dest.Type == OperandType.FP32) + { + context.Assembler.Subss(dest, src1, src2); + } + else /* if (dest.Type == OperandType.FP64) */ + { + context.Assembler.Subsd(dest, src1, src2); + } + } + + private static void GenerateTailcall(CodeGenContext context, Operation operation) + { + WriteEpilogue(context); + + context.Assembler.Jmp(operation.GetSource(0)); + } + + private static void GenerateVectorCreateScalar(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(!dest.Type.IsInteger() && source.Type.IsInteger()); + + if (source.Type == OperandType.I32) + { + context.Assembler.Movd(dest, source); // (__m128i _mm_cvtsi32_si128(int a)) + } + else /* if (source.Type == OperandType.I64) */ + { + context.Assembler.Movq(dest, source); // (__m128i _mm_cvtsi64_si128(__int64 a)) + } + } + + private static void GenerateVectorExtract(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; //Value + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < OperandType.V128.GetSizeInBytes() / dest.Type.GetSizeInBytes()); + + if (dest.Type == OperandType.I32) + { + if (index == 0) + { + context.Assembler.Movd(dest, src1); + } + else if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pextrd(dest, src1, index); + } + else + { + int mask0 = 0b11_10_01_00; + int mask1 = 0b11_10_01_00; + + mask0 = BitUtils.RotateRight(mask0, index * 2, 8); + mask1 = BitUtils.RotateRight(mask1, 8 - index * 2, 8); + + context.Assembler.Pshufd(src1, src1, (byte)mask0); + context.Assembler.Movd(dest, src1); + context.Assembler.Pshufd(src1, src1, (byte)mask1); + } + } + else if (dest.Type == OperandType.I64) + { + if (index == 0) + { + context.Assembler.Movq(dest, src1); + } + else if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pextrq(dest, src1, index); + } + else + { + const byte Mask = 0b01_00_11_10; + + context.Assembler.Pshufd(src1, src1, Mask); + context.Assembler.Movq(dest, src1); + context.Assembler.Pshufd(src1, src1, Mask); + } + } + else + { + // Floating-point types. + if ((index >= 2 && dest.Type == OperandType.FP32) || + (index == 1 && dest.Type == OperandType.FP64)) + { + context.Assembler.Movhlps(dest, dest, src1); + context.Assembler.Movq(dest, dest); + } + else + { + context.Assembler.Movq(dest, src1); + } + + if (dest.Type == OperandType.FP32) + { + context.Assembler.Pshufd(dest, dest, (byte)(0xfc | (index & 1))); + } + } + } + + private static void GenerateVectorExtract16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; //Value + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < 8); + + context.Assembler.Pextrw(dest, src1, index); + } + + private static void GenerateVectorExtract8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; //Value + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < 16); + + if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pextrb(dest, src1, index); + } + else + { + context.Assembler.Pextrw(dest, src1, (byte)(index >> 1)); + + if ((index & 1) != 0) + { + context.Assembler.Shr(dest, Const(8), OperandType.I32); + } + else + { + context.Assembler.Movzx8(dest, dest, OperandType.I32); + } + } + } + + private static void GenerateVectorInsert(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Value + Operand src3 = operation.GetSource(2); //Index + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + void InsertIntSse2(int words) + { + if (dest.GetRegister() != src1.GetRegister()) + { + context.Assembler.Movdqu(dest, src1); + } + + for (int word = 0; word < words; word++) + { + // Insert lower 16-bits. + context.Assembler.Pinsrw(dest, dest, src2, (byte)(index * words + word)); + + // Move next word down. + context.Assembler.Ror(src2, Const(16), src2.Type); + } + } + + if (src2.Type == OperandType.I32) + { + Debug.Assert(index < 4); + + if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pinsrd(dest, src1, src2, index); + } + else + { + InsertIntSse2(2); + } + } + else if (src2.Type == OperandType.I64) + { + Debug.Assert(index < 2); + + if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pinsrq(dest, src1, src2, index); + } + else + { + InsertIntSse2(4); + } + } + else if (src2.Type == OperandType.FP32) + { + Debug.Assert(index < 4); + + if (index != 0) + { + if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Insertps(dest, src1, src2, (byte)(index << 4)); + } + else + { + if (src1.GetRegister() == src2.GetRegister()) + { + int mask = 0b11_10_01_00; + + mask &= ~(0b11 << index * 2); + + context.Assembler.Pshufd(dest, src1, (byte)mask); + } + else + { + int mask0 = 0b11_10_01_00; + int mask1 = 0b11_10_01_00; + + mask0 = BitUtils.RotateRight(mask0, index * 2, 8); + mask1 = BitUtils.RotateRight(mask1, 8 - index * 2, 8); + + context.Assembler.Pshufd(src1, src1, (byte)mask0); // Lane to be inserted in position 0. + context.Assembler.Movss(dest, src1, src2); // dest[127:0] = src1[127:32] | src2[31:0] + context.Assembler.Pshufd(dest, dest, (byte)mask1); // Inserted lane in original position. + + if (dest.GetRegister() != src1.GetRegister()) + { + context.Assembler.Pshufd(src1, src1, (byte)mask1); // Restore src1. + } + } + } + } + else + { + context.Assembler.Movss(dest, src1, src2); + } + } + else /* if (src2.Type == OperandType.FP64) */ + { + Debug.Assert(index < 2); + + if (index != 0) + { + context.Assembler.Movlhps(dest, src1, src2); + } + else + { + context.Assembler.Movsd(dest, src1, src2); + } + } + } + + private static void GenerateVectorInsert16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Value + Operand src3 = operation.GetSource(2); //Index + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + context.Assembler.Pinsrw(dest, src1, src2, index); + } + + private static void GenerateVectorInsert8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Value + Operand src3 = operation.GetSource(2); //Index + + // It's not possible to emulate this instruction without + // SSE 4.1 support without the use of a temporary register, + // so we instead handle that case on the pre-allocator when + // SSE 4.1 is not supported on the CPU. + Debug.Assert(HardwareCapabilities.SupportsSse41); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + context.Assembler.Pinsrb(dest, src1, src2, index); + } + + private static void GenerateVectorOne(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.Pcmpeqw(dest, dest, dest); + } + + private static void GenerateVectorZero(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.Xorps(dest, dest, dest); + } + + private static void GenerateVectorZeroUpper64(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.V128 && source.Type == OperandType.V128); + + GenerateZeroUpper64(context, dest, source); + } + + private static void GenerateVectorZeroUpper96(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.V128 && source.Type == OperandType.V128); + + GenerateZeroUpper96(context, dest, source); + } + + private static void GenerateZeroExtend16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movzx16(dest, source, OperandType.I32); + } + + private static void GenerateZeroExtend32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + // We can eliminate the move if source is already 32-bit and the registers are the same. + if (dest.Value == source.Value && source.Type == OperandType.I32) + { + return; + } + + context.Assembler.Mov(dest, source, OperandType.I32); + } + + private static void GenerateZeroExtend8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movzx8(dest, source, OperandType.I32); + } + + private static void GenerateLoad(CodeGenContext context, Operand address, Operand value) + { + switch (value.Type) + { + case OperandType.I32: + context.Assembler.Mov(value, address, OperandType.I32); + break; + case OperandType.I64: + context.Assembler.Mov(value, address, OperandType.I64); + break; + case OperandType.FP32: + context.Assembler.Movd(value, address); + break; + case OperandType.FP64: + context.Assembler.Movq(value, address); + break; + case OperandType.V128: + context.Assembler.Movdqu(value, address); + break; + + default: + Debug.Assert(false); + break; + } + } + + private static void GenerateStore(CodeGenContext context, Operand address, Operand value) + { + switch (value.Type) + { + case OperandType.I32: + context.Assembler.Mov(address, value, OperandType.I32); + break; + case OperandType.I64: + context.Assembler.Mov(address, value, OperandType.I64); + break; + case OperandType.FP32: + context.Assembler.Movd(address, value); + break; + case OperandType.FP64: + context.Assembler.Movq(address, value); + break; + case OperandType.V128: + context.Assembler.Movdqu(address, value); + break; + + default: + Debug.Assert(false); + break; + } + } + + private static void GenerateZeroUpper64(CodeGenContext context, Operand dest, Operand source) + { + context.Assembler.Movq(dest, source); + } + + private static void GenerateZeroUpper96(CodeGenContext context, Operand dest, Operand source) + { + context.Assembler.Movq(dest, source); + context.Assembler.Pshufd(dest, dest, 0xfc); + } + + private static bool MatchOperation(Operation node, Instruction inst, OperandType destType, Register destReg) + { + if (node == default || node.DestinationsCount == 0) + { + return false; + } + + if (node.Instruction != inst) + { + return false; + } + + Operand dest = node.Destination; + + return dest.Kind == OperandKind.Register && + dest.Type == destType && + dest.GetRegister() == destReg; + } + + [Conditional("DEBUG")] + private static void ValidateUnOp(Operand dest, Operand source) + { + EnsureSameReg(dest, source); + EnsureSameType(dest, source); + } + + [Conditional("DEBUG")] + private static void ValidateBinOp(Operand dest, Operand src1, Operand src2) + { + EnsureSameReg(dest, src1); + EnsureSameType(dest, src1, src2); + } + + [Conditional("DEBUG")] + private static void ValidateShift(Operand dest, Operand src1, Operand src2) + { + EnsureSameReg(dest, src1); + EnsureSameType(dest, src1); + + Debug.Assert(dest.Type.IsInteger() && src2.Type == OperandType.I32); + } + + private static void EnsureSameReg(Operand op1, Operand op2) + { + if (!op1.Type.IsInteger() && HardwareCapabilities.SupportsVexEncoding) + { + return; + } + + Debug.Assert(op1.Kind == OperandKind.Register || op1.Kind == OperandKind.Memory); + Debug.Assert(op1.Kind == op2.Kind); + Debug.Assert(op1.Value == op2.Value); + } + + private static void EnsureSameType(Operand op1, Operand op2) + { + Debug.Assert(op1.Type == op2.Type); + } + + private static void EnsureSameType(Operand op1, Operand op2, Operand op3) + { + Debug.Assert(op1.Type == op2.Type); + Debug.Assert(op1.Type == op3.Type); + } + + private static void EnsureSameType(Operand op1, Operand op2, Operand op3, Operand op4) + { + Debug.Assert(op1.Type == op2.Type); + Debug.Assert(op1.Type == op3.Type); + Debug.Assert(op1.Type == op4.Type); + } + + private static UnwindInfo WritePrologue(CodeGenContext context) + { + List pushEntries = new(); + + Operand rsp = Register(X86Register.Rsp); + + int mask = CallingConvention.GetIntCalleeSavedRegisters() & context.AllocResult.IntUsedRegisters; + + while (mask != 0) + { + int bit = BitOperations.TrailingZeroCount(mask); + + context.Assembler.Push(Register((X86Register)bit)); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.PushReg, context.StreamOffset, regIndex: bit)); + + mask &= ~(1 << bit); + } + + int reservedStackSize = context.CallArgsRegionSize + context.AllocResult.SpillRegionSize; + + reservedStackSize += context.XmmSaveRegionSize; + + if (reservedStackSize >= StackGuardSize) + { + GenerateInlineStackProbe(context, reservedStackSize); + } + + if (reservedStackSize != 0) + { + context.Assembler.Sub(rsp, Const(reservedStackSize), OperandType.I64); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.AllocStack, context.StreamOffset, stackOffsetOrAllocSize: reservedStackSize)); + } + + int offset = reservedStackSize; + + mask = CallingConvention.GetVecCalleeSavedRegisters() & context.AllocResult.VecUsedRegisters; + + while (mask != 0) + { + int bit = BitOperations.TrailingZeroCount(mask); + + offset -= 16; + + Operand memOp = MemoryOp(OperandType.V128, rsp, default, Multiplier.x1, offset); + + context.Assembler.Movdqu(memOp, Xmm((X86Register)bit)); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.SaveXmm128, context.StreamOffset, bit, offset)); + + mask &= ~(1 << bit); + } + + return new UnwindInfo(pushEntries.ToArray(), context.StreamOffset); + } + + private static void WriteEpilogue(CodeGenContext context) + { + Operand rsp = Register(X86Register.Rsp); + + int reservedStackSize = context.CallArgsRegionSize + context.AllocResult.SpillRegionSize; + + reservedStackSize += context.XmmSaveRegionSize; + + int offset = reservedStackSize; + + int mask = CallingConvention.GetVecCalleeSavedRegisters() & context.AllocResult.VecUsedRegisters; + + while (mask != 0) + { + int bit = BitOperations.TrailingZeroCount(mask); + + offset -= 16; + + Operand memOp = MemoryOp(OperandType.V128, rsp, default, Multiplier.x1, offset); + + context.Assembler.Movdqu(Xmm((X86Register)bit), memOp); + + mask &= ~(1 << bit); + } + + if (reservedStackSize != 0) + { + context.Assembler.Add(rsp, Const(reservedStackSize), OperandType.I64); + } + + mask = CallingConvention.GetIntCalleeSavedRegisters() & context.AllocResult.IntUsedRegisters; + + while (mask != 0) + { + int bit = BitUtils.HighestBitSet(mask); + + context.Assembler.Pop(Register((X86Register)bit)); + + mask &= ~(1 << bit); + } + } + + private static void GenerateInlineStackProbe(CodeGenContext context, int size) + { + // Windows does lazy stack allocation, and there are just 2 + // guard pages on the end of the stack. So, if the allocation + // size we make is greater than this guard size, we must ensure + // that the OS will map all pages that we'll use. We do that by + // doing a dummy read on those pages, forcing a page fault and + // the OS to map them. If they are already mapped, nothing happens. + const int PageMask = PageSize - 1; + + size = (size + PageMask) & ~PageMask; + + Operand rsp = Register(X86Register.Rsp); + Operand temp = Register(CallingConvention.GetIntReturnRegister()); + + for (int offset = PageSize; offset < size; offset += PageSize) + { + Operand memOp = MemoryOp(OperandType.I32, rsp, default, Multiplier.x1, -offset); + + context.Assembler.Mov(temp, memOp, OperandType.I32); + } + } + + private static Operand Memory(Operand operand, OperandType type) + { + if (operand.Kind == OperandKind.Memory) + { + return operand; + } + + return MemoryOp(type, operand); + } + + private static Operand Register(X86Register register, OperandType type = OperandType.I64) + { + return Operand.Factory.Register((int)register, RegisterType.Integer, type); + } + + private static Operand Xmm(X86Register register) + { + return Operand.Factory.Register((int)register, RegisterType.Vector, OperandType.V128); + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/HardwareCapabilities.cs b/src/ARMeilleure/CodeGen/X86/HardwareCapabilities.cs new file mode 100644 index 00000000..4f6f1e87 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/HardwareCapabilities.cs @@ -0,0 +1,144 @@ +using Ryujinx.Memory; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.X86; + +namespace ARMeilleure.CodeGen.X86 +{ + static class HardwareCapabilities + { + private delegate uint GetXcr0(); + + static HardwareCapabilities() + { + if (!X86Base.IsSupported) + { + return; + } + + (int maxNum, _, _, _) = X86Base.CpuId(0x00000000, 0x00000000); + + (_, _, int ecx1, int edx1) = X86Base.CpuId(0x00000001, 0x00000000); + FeatureInfo1Edx = (FeatureFlags1Edx)edx1; + FeatureInfo1Ecx = (FeatureFlags1Ecx)ecx1; + + if (maxNum >= 7) + { + (_, int ebx7, int ecx7, _) = X86Base.CpuId(0x00000007, 0x00000000); + FeatureInfo7Ebx = (FeatureFlags7Ebx)ebx7; + FeatureInfo7Ecx = (FeatureFlags7Ecx)ecx7; + } + + Xcr0InfoEax = (Xcr0FlagsEax)GetXcr0Eax(); + } + + private static uint GetXcr0Eax() + { + if (!FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave)) + { + // XSAVE feature required for xgetbv + return 0; + } + + ReadOnlySpan asmGetXcr0 = new byte[] + { + 0x31, 0xc9, // xor ecx, ecx + 0xf, 0x01, 0xd0, // xgetbv + 0xc3, // ret + }; + + using MemoryBlock memGetXcr0 = new((ulong)asmGetXcr0.Length); + + memGetXcr0.Write(0, asmGetXcr0); + + memGetXcr0.Reprotect(0, (ulong)asmGetXcr0.Length, MemoryPermission.ReadAndExecute); + + var fGetXcr0 = Marshal.GetDelegateForFunctionPointer(memGetXcr0.Pointer); + + return fGetXcr0(); + } + + [Flags] + public enum FeatureFlags1Edx + { + Sse = 1 << 25, + Sse2 = 1 << 26, + } + + [Flags] + public enum FeatureFlags1Ecx + { + Sse3 = 1 << 0, + Pclmulqdq = 1 << 1, + Ssse3 = 1 << 9, + Fma = 1 << 12, + Sse41 = 1 << 19, + Sse42 = 1 << 20, + Popcnt = 1 << 23, + Aes = 1 << 25, + Xsave = 1 << 26, + Osxsave = 1 << 27, + Avx = 1 << 28, + F16c = 1 << 29, + } + + [Flags] + public enum FeatureFlags7Ebx + { + Avx2 = 1 << 5, + Avx512f = 1 << 16, + Avx512dq = 1 << 17, + Sha = 1 << 29, + Avx512bw = 1 << 30, + Avx512vl = 1 << 31, + } + + [Flags] + public enum FeatureFlags7Ecx + { + Gfni = 1 << 8, + } + + [Flags] + public enum Xcr0FlagsEax + { + Sse = 1 << 1, + YmmHi128 = 1 << 2, + Opmask = 1 << 5, + ZmmHi256 = 1 << 6, + Hi16Zmm = 1 << 7, + } + + public static FeatureFlags1Edx FeatureInfo1Edx { get; } + public static FeatureFlags1Ecx FeatureInfo1Ecx { get; } + public static FeatureFlags7Ebx FeatureInfo7Ebx { get; } = 0; + public static FeatureFlags7Ecx FeatureInfo7Ecx { get; } = 0; + public static Xcr0FlagsEax Xcr0InfoEax { get; } = 0; + + public static bool SupportsSse => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse); + public static bool SupportsSse2 => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse2); + public static bool SupportsSse3 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse3); + public static bool SupportsPclmulqdq => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Pclmulqdq); + public static bool SupportsSsse3 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Ssse3); + public static bool SupportsFma => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Fma); + public static bool SupportsSse41 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse41); + public static bool SupportsSse42 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse42); + public static bool SupportsPopcnt => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Popcnt); + public static bool SupportsAesni => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Aes); + public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx | FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave) && Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128); + public static bool SupportsAvx2 => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx2) && SupportsAvx; + public static bool SupportsAvx512F => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512f) && FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave) + && Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128 | Xcr0FlagsEax.Opmask | Xcr0FlagsEax.ZmmHi256 | Xcr0FlagsEax.Hi16Zmm); + public static bool SupportsAvx512Vl => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512vl) && SupportsAvx512F; + public static bool SupportsAvx512Bw => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512bw) && SupportsAvx512F; + public static bool SupportsAvx512Dq => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512dq) && SupportsAvx512F; + public static bool SupportsF16c => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.F16c); + public static bool SupportsSha => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Sha); + public static bool SupportsGfni => FeatureInfo7Ecx.HasFlag(FeatureFlags7Ecx.Gfni); + + public static bool ForceLegacySse { get; set; } + + public static bool SupportsVexEncoding => SupportsAvx && !ForceLegacySse; + public static bool SupportsEvexEncoding => SupportsAvx512F && !ForceLegacySse; + } +} diff --git a/src/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs b/src/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs new file mode 100644 index 00000000..16054c61 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.CodeGen.X86 +{ + readonly struct IntrinsicInfo + { + public X86Instruction Inst { get; } + public IntrinsicType Type { get; } + + public IntrinsicInfo(X86Instruction inst, IntrinsicType type) + { + Inst = inst; + Type = type; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/IntrinsicTable.cs b/src/ARMeilleure/CodeGen/X86/IntrinsicTable.cs new file mode 100644 index 00000000..daa1f8f6 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/IntrinsicTable.cs @@ -0,0 +1,202 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.CodeGen.X86 +{ + static class IntrinsicTable + { + private static readonly IntrinsicInfo[] _intrinTable; + + static IntrinsicTable() + { + _intrinTable = new IntrinsicInfo[EnumUtils.GetCount(typeof(Intrinsic))]; + +#pragma warning disable IDE0055 // Disable formatting + Add(Intrinsic.X86Addpd, new IntrinsicInfo(X86Instruction.Addpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Addps, new IntrinsicInfo(X86Instruction.Addps, IntrinsicType.Binary)); + Add(Intrinsic.X86Addsd, new IntrinsicInfo(X86Instruction.Addsd, IntrinsicType.Binary)); + Add(Intrinsic.X86Addss, new IntrinsicInfo(X86Instruction.Addss, IntrinsicType.Binary)); + Add(Intrinsic.X86Aesdec, new IntrinsicInfo(X86Instruction.Aesdec, IntrinsicType.Binary)); + Add(Intrinsic.X86Aesdeclast, new IntrinsicInfo(X86Instruction.Aesdeclast, IntrinsicType.Binary)); + Add(Intrinsic.X86Aesenc, new IntrinsicInfo(X86Instruction.Aesenc, IntrinsicType.Binary)); + Add(Intrinsic.X86Aesenclast, new IntrinsicInfo(X86Instruction.Aesenclast, IntrinsicType.Binary)); + Add(Intrinsic.X86Aesimc, new IntrinsicInfo(X86Instruction.Aesimc, IntrinsicType.Unary)); + Add(Intrinsic.X86Andnpd, new IntrinsicInfo(X86Instruction.Andnpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Andnps, new IntrinsicInfo(X86Instruction.Andnps, IntrinsicType.Binary)); + Add(Intrinsic.X86Andpd, new IntrinsicInfo(X86Instruction.Andpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Andps, new IntrinsicInfo(X86Instruction.Andps, IntrinsicType.Binary)); + Add(Intrinsic.X86Blendvpd, new IntrinsicInfo(X86Instruction.Blendvpd, IntrinsicType.Ternary)); + Add(Intrinsic.X86Blendvps, new IntrinsicInfo(X86Instruction.Blendvps, IntrinsicType.Ternary)); + Add(Intrinsic.X86Cmppd, new IntrinsicInfo(X86Instruction.Cmppd, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Cmpps, new IntrinsicInfo(X86Instruction.Cmpps, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Cmpsd, new IntrinsicInfo(X86Instruction.Cmpsd, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Cmpss, new IntrinsicInfo(X86Instruction.Cmpss, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Comisdeq, new IntrinsicInfo(X86Instruction.Comisd, IntrinsicType.Comis_)); + Add(Intrinsic.X86Comisdge, new IntrinsicInfo(X86Instruction.Comisd, IntrinsicType.Comis_)); + Add(Intrinsic.X86Comisdlt, new IntrinsicInfo(X86Instruction.Comisd, IntrinsicType.Comis_)); + Add(Intrinsic.X86Comisseq, new IntrinsicInfo(X86Instruction.Comiss, IntrinsicType.Comis_)); + Add(Intrinsic.X86Comissge, new IntrinsicInfo(X86Instruction.Comiss, IntrinsicType.Comis_)); + Add(Intrinsic.X86Comisslt, new IntrinsicInfo(X86Instruction.Comiss, IntrinsicType.Comis_)); + Add(Intrinsic.X86Crc32, new IntrinsicInfo(X86Instruction.Crc32, IntrinsicType.Crc32)); + Add(Intrinsic.X86Crc32_16, new IntrinsicInfo(X86Instruction.Crc32_16, IntrinsicType.Crc32)); + Add(Intrinsic.X86Crc32_8, new IntrinsicInfo(X86Instruction.Crc32_8, IntrinsicType.Crc32)); + Add(Intrinsic.X86Cvtdq2pd, new IntrinsicInfo(X86Instruction.Cvtdq2pd, IntrinsicType.Unary)); + Add(Intrinsic.X86Cvtdq2ps, new IntrinsicInfo(X86Instruction.Cvtdq2ps, IntrinsicType.Unary)); + Add(Intrinsic.X86Cvtpd2dq, new IntrinsicInfo(X86Instruction.Cvtpd2dq, IntrinsicType.Unary)); + Add(Intrinsic.X86Cvtpd2ps, new IntrinsicInfo(X86Instruction.Cvtpd2ps, IntrinsicType.Unary)); + Add(Intrinsic.X86Cvtps2dq, new IntrinsicInfo(X86Instruction.Cvtps2dq, IntrinsicType.Unary)); + Add(Intrinsic.X86Cvtps2pd, new IntrinsicInfo(X86Instruction.Cvtps2pd, IntrinsicType.Unary)); + Add(Intrinsic.X86Cvtsd2si, new IntrinsicInfo(X86Instruction.Cvtsd2si, IntrinsicType.UnaryToGpr)); + Add(Intrinsic.X86Cvtsd2ss, new IntrinsicInfo(X86Instruction.Cvtsd2ss, IntrinsicType.Binary)); + Add(Intrinsic.X86Cvtsi2sd, new IntrinsicInfo(X86Instruction.Cvtsi2sd, IntrinsicType.BinaryGpr)); + Add(Intrinsic.X86Cvtsi2si, new IntrinsicInfo(X86Instruction.Movd, IntrinsicType.UnaryToGpr)); + Add(Intrinsic.X86Cvtsi2ss, new IntrinsicInfo(X86Instruction.Cvtsi2ss, IntrinsicType.BinaryGpr)); + Add(Intrinsic.X86Cvtss2sd, new IntrinsicInfo(X86Instruction.Cvtss2sd, IntrinsicType.Binary)); + Add(Intrinsic.X86Cvtss2si, new IntrinsicInfo(X86Instruction.Cvtss2si, IntrinsicType.UnaryToGpr)); + Add(Intrinsic.X86Divpd, new IntrinsicInfo(X86Instruction.Divpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Divps, new IntrinsicInfo(X86Instruction.Divps, IntrinsicType.Binary)); + Add(Intrinsic.X86Divsd, new IntrinsicInfo(X86Instruction.Divsd, IntrinsicType.Binary)); + Add(Intrinsic.X86Divss, new IntrinsicInfo(X86Instruction.Divss, IntrinsicType.Binary)); + Add(Intrinsic.X86Gf2p8affineqb, new IntrinsicInfo(X86Instruction.Gf2p8affineqb, IntrinsicType.TernaryImm)); + 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)); + Add(Intrinsic.X86Maxss, new IntrinsicInfo(X86Instruction.Maxss, IntrinsicType.Binary)); + Add(Intrinsic.X86Minpd, new IntrinsicInfo(X86Instruction.Minpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Minps, new IntrinsicInfo(X86Instruction.Minps, IntrinsicType.Binary)); + Add(Intrinsic.X86Minsd, new IntrinsicInfo(X86Instruction.Minsd, IntrinsicType.Binary)); + Add(Intrinsic.X86Minss, new IntrinsicInfo(X86Instruction.Minss, IntrinsicType.Binary)); + Add(Intrinsic.X86Movhlps, new IntrinsicInfo(X86Instruction.Movhlps, IntrinsicType.Binary)); + Add(Intrinsic.X86Movlhps, new IntrinsicInfo(X86Instruction.Movlhps, IntrinsicType.Binary)); + Add(Intrinsic.X86Movss, new IntrinsicInfo(X86Instruction.Movss, IntrinsicType.Binary)); + Add(Intrinsic.X86Mulpd, new IntrinsicInfo(X86Instruction.Mulpd, IntrinsicType.Binary)); + 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.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)); + Add(Intrinsic.X86Paddw, new IntrinsicInfo(X86Instruction.Paddw, IntrinsicType.Binary)); + Add(Intrinsic.X86Palignr, new IntrinsicInfo(X86Instruction.Palignr, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Pand, new IntrinsicInfo(X86Instruction.Pand, IntrinsicType.Binary)); + Add(Intrinsic.X86Pandn, new IntrinsicInfo(X86Instruction.Pandn, IntrinsicType.Binary)); + Add(Intrinsic.X86Pavgb, new IntrinsicInfo(X86Instruction.Pavgb, IntrinsicType.Binary)); + Add(Intrinsic.X86Pavgw, new IntrinsicInfo(X86Instruction.Pavgw, IntrinsicType.Binary)); + Add(Intrinsic.X86Pblendvb, new IntrinsicInfo(X86Instruction.Pblendvb, IntrinsicType.Ternary)); + Add(Intrinsic.X86Pclmulqdq, new IntrinsicInfo(X86Instruction.Pclmulqdq, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Pcmpeqb, new IntrinsicInfo(X86Instruction.Pcmpeqb, IntrinsicType.Binary)); + Add(Intrinsic.X86Pcmpeqd, new IntrinsicInfo(X86Instruction.Pcmpeqd, IntrinsicType.Binary)); + Add(Intrinsic.X86Pcmpeqq, new IntrinsicInfo(X86Instruction.Pcmpeqq, IntrinsicType.Binary)); + Add(Intrinsic.X86Pcmpeqw, new IntrinsicInfo(X86Instruction.Pcmpeqw, IntrinsicType.Binary)); + Add(Intrinsic.X86Pcmpgtb, new IntrinsicInfo(X86Instruction.Pcmpgtb, IntrinsicType.Binary)); + Add(Intrinsic.X86Pcmpgtd, new IntrinsicInfo(X86Instruction.Pcmpgtd, IntrinsicType.Binary)); + Add(Intrinsic.X86Pcmpgtq, new IntrinsicInfo(X86Instruction.Pcmpgtq, IntrinsicType.Binary)); + Add(Intrinsic.X86Pcmpgtw, new IntrinsicInfo(X86Instruction.Pcmpgtw, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmaxsb, new IntrinsicInfo(X86Instruction.Pmaxsb, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmaxsd, new IntrinsicInfo(X86Instruction.Pmaxsd, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmaxsw, new IntrinsicInfo(X86Instruction.Pmaxsw, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmaxub, new IntrinsicInfo(X86Instruction.Pmaxub, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmaxud, new IntrinsicInfo(X86Instruction.Pmaxud, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmaxuw, new IntrinsicInfo(X86Instruction.Pmaxuw, IntrinsicType.Binary)); + Add(Intrinsic.X86Pminsb, new IntrinsicInfo(X86Instruction.Pminsb, IntrinsicType.Binary)); + Add(Intrinsic.X86Pminsd, new IntrinsicInfo(X86Instruction.Pminsd, IntrinsicType.Binary)); + Add(Intrinsic.X86Pminsw, new IntrinsicInfo(X86Instruction.Pminsw, IntrinsicType.Binary)); + Add(Intrinsic.X86Pminub, new IntrinsicInfo(X86Instruction.Pminub, IntrinsicType.Binary)); + Add(Intrinsic.X86Pminud, new IntrinsicInfo(X86Instruction.Pminud, IntrinsicType.Binary)); + Add(Intrinsic.X86Pminuw, new IntrinsicInfo(X86Instruction.Pminuw, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmovsxbw, new IntrinsicInfo(X86Instruction.Pmovsxbw, IntrinsicType.Unary)); + Add(Intrinsic.X86Pmovsxdq, new IntrinsicInfo(X86Instruction.Pmovsxdq, IntrinsicType.Unary)); + Add(Intrinsic.X86Pmovsxwd, new IntrinsicInfo(X86Instruction.Pmovsxwd, IntrinsicType.Unary)); + Add(Intrinsic.X86Pmovzxbw, new IntrinsicInfo(X86Instruction.Pmovzxbw, IntrinsicType.Unary)); + Add(Intrinsic.X86Pmovzxdq, new IntrinsicInfo(X86Instruction.Pmovzxdq, IntrinsicType.Unary)); + Add(Intrinsic.X86Pmovzxwd, new IntrinsicInfo(X86Instruction.Pmovzxwd, IntrinsicType.Unary)); + Add(Intrinsic.X86Pmulld, new IntrinsicInfo(X86Instruction.Pmulld, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmullw, new IntrinsicInfo(X86Instruction.Pmullw, IntrinsicType.Binary)); + Add(Intrinsic.X86Popcnt, new IntrinsicInfo(X86Instruction.Popcnt, IntrinsicType.PopCount)); + Add(Intrinsic.X86Por, new IntrinsicInfo(X86Instruction.Por, IntrinsicType.Binary)); + Add(Intrinsic.X86Pshufb, new IntrinsicInfo(X86Instruction.Pshufb, IntrinsicType.Binary)); + Add(Intrinsic.X86Pshufd, new IntrinsicInfo(X86Instruction.Pshufd, IntrinsicType.BinaryImm)); + Add(Intrinsic.X86Pslld, new IntrinsicInfo(X86Instruction.Pslld, IntrinsicType.Binary)); + Add(Intrinsic.X86Pslldq, new IntrinsicInfo(X86Instruction.Pslldq, IntrinsicType.Binary)); + Add(Intrinsic.X86Psllq, new IntrinsicInfo(X86Instruction.Psllq, IntrinsicType.Binary)); + Add(Intrinsic.X86Psllw, new IntrinsicInfo(X86Instruction.Psllw, IntrinsicType.Binary)); + Add(Intrinsic.X86Psrad, new IntrinsicInfo(X86Instruction.Psrad, IntrinsicType.Binary)); + Add(Intrinsic.X86Psraw, new IntrinsicInfo(X86Instruction.Psraw, IntrinsicType.Binary)); + Add(Intrinsic.X86Psrld, new IntrinsicInfo(X86Instruction.Psrld, IntrinsicType.Binary)); + Add(Intrinsic.X86Psrlq, new IntrinsicInfo(X86Instruction.Psrlq, IntrinsicType.Binary)); + Add(Intrinsic.X86Psrldq, new IntrinsicInfo(X86Instruction.Psrldq, IntrinsicType.Binary)); + Add(Intrinsic.X86Psrlw, new IntrinsicInfo(X86Instruction.Psrlw, IntrinsicType.Binary)); + Add(Intrinsic.X86Psubb, new IntrinsicInfo(X86Instruction.Psubb, IntrinsicType.Binary)); + Add(Intrinsic.X86Psubd, new IntrinsicInfo(X86Instruction.Psubd, IntrinsicType.Binary)); + Add(Intrinsic.X86Psubq, new IntrinsicInfo(X86Instruction.Psubq, IntrinsicType.Binary)); + Add(Intrinsic.X86Psubw, new IntrinsicInfo(X86Instruction.Psubw, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpckhbw, new IntrinsicInfo(X86Instruction.Punpckhbw, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpckhdq, new IntrinsicInfo(X86Instruction.Punpckhdq, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpckhqdq, new IntrinsicInfo(X86Instruction.Punpckhqdq, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpckhwd, new IntrinsicInfo(X86Instruction.Punpckhwd, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpcklbw, new IntrinsicInfo(X86Instruction.Punpcklbw, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpckldq, new IntrinsicInfo(X86Instruction.Punpckldq, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpcklqdq, new IntrinsicInfo(X86Instruction.Punpcklqdq, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpcklwd, new IntrinsicInfo(X86Instruction.Punpcklwd, IntrinsicType.Binary)); + Add(Intrinsic.X86Pxor, new IntrinsicInfo(X86Instruction.Pxor, IntrinsicType.Binary)); + Add(Intrinsic.X86Rcpps, new IntrinsicInfo(X86Instruction.Rcpps, IntrinsicType.Unary)); + Add(Intrinsic.X86Rcpss, new IntrinsicInfo(X86Instruction.Rcpss, IntrinsicType.Unary)); + Add(Intrinsic.X86Roundpd, new IntrinsicInfo(X86Instruction.Roundpd, IntrinsicType.BinaryImm)); + Add(Intrinsic.X86Roundps, new IntrinsicInfo(X86Instruction.Roundps, IntrinsicType.BinaryImm)); + Add(Intrinsic.X86Roundsd, new IntrinsicInfo(X86Instruction.Roundsd, IntrinsicType.BinaryImm)); + Add(Intrinsic.X86Roundss, new IntrinsicInfo(X86Instruction.Roundss, IntrinsicType.BinaryImm)); + Add(Intrinsic.X86Rsqrtps, new IntrinsicInfo(X86Instruction.Rsqrtps, IntrinsicType.Unary)); + Add(Intrinsic.X86Rsqrtss, new IntrinsicInfo(X86Instruction.Rsqrtss, IntrinsicType.Unary)); + Add(Intrinsic.X86Sha256Msg1, new IntrinsicInfo(X86Instruction.Sha256Msg1, IntrinsicType.Binary)); + Add(Intrinsic.X86Sha256Msg2, new IntrinsicInfo(X86Instruction.Sha256Msg2, IntrinsicType.Binary)); + Add(Intrinsic.X86Sha256Rnds2, new IntrinsicInfo(X86Instruction.Sha256Rnds2, IntrinsicType.Ternary)); + Add(Intrinsic.X86Shufpd, new IntrinsicInfo(X86Instruction.Shufpd, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Shufps, new IntrinsicInfo(X86Instruction.Shufps, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Sqrtpd, new IntrinsicInfo(X86Instruction.Sqrtpd, IntrinsicType.Unary)); + 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)); + Add(Intrinsic.X86Subss, new IntrinsicInfo(X86Instruction.Subss, IntrinsicType.Binary)); + Add(Intrinsic.X86Unpckhpd, new IntrinsicInfo(X86Instruction.Unpckhpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Unpckhps, new IntrinsicInfo(X86Instruction.Unpckhps, IntrinsicType.Binary)); + Add(Intrinsic.X86Unpcklpd, new IntrinsicInfo(X86Instruction.Unpcklpd, IntrinsicType.Binary)); + 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)); + Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma)); + Add(Intrinsic.X86Vfnmsub231ss, new IntrinsicInfo(X86Instruction.Vfnmsub231ss, IntrinsicType.Fma)); + Add(Intrinsic.X86Vpternlogd, new IntrinsicInfo(X86Instruction.Vpternlogd, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, IntrinsicType.Binary)); +#pragma warning restore IDE0055 + } + + private static void Add(Intrinsic intrin, IntrinsicInfo info) + { + _intrinTable[(int)intrin] = info; + } + + public static IntrinsicInfo GetInfo(Intrinsic intrin) + { + return _intrinTable[(int)intrin]; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/IntrinsicType.cs b/src/ARMeilleure/CodeGen/X86/IntrinsicType.cs new file mode 100644 index 00000000..7c3ef354 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/IntrinsicType.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.CodeGen.X86 +{ + enum IntrinsicType + { + Comis_, + Mxcsr, + PopCount, + Unary, + UnaryToGpr, + Binary, + BinaryGpr, + BinaryImm, + Crc32, + Ternary, + TernaryImm, + Fma, + } +} diff --git a/src/ARMeilleure/CodeGen/X86/Mxcsr.cs b/src/ARMeilleure/CodeGen/X86/Mxcsr.cs new file mode 100644 index 00000000..719afe59 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/Mxcsr.cs @@ -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. + } +} diff --git a/src/ARMeilleure/CodeGen/X86/PreAllocator.cs b/src/ARMeilleure/CodeGen/X86/PreAllocator.cs new file mode 100644 index 00000000..590c35c7 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/PreAllocator.cs @@ -0,0 +1,788 @@ +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + class PreAllocator + { + public static void RunPass(CompilerContext cctx, StackAllocator stackAlloc, out int maxCallArgs) + { + maxCallArgs = -1; + + Span buffer = default; + + CallConvName callConv = CallingConvention.GetCurrentCallConv(); + + Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()]; + + for (BasicBlock block = cctx.Cfg.Blocks.First; block != null; block = block.ListNext) + { + Operation nextNode; + + for (Operation node = block.Operations.First; node != default; node = nextNode) + { + nextNode = node.ListNext; + + if (node.Instruction == Instruction.Phi) + { + continue; + } + + InsertConstantRegCopies(block.Operations, node); + InsertDestructiveRegCopies(block.Operations, node); + InsertConstrainedRegCopies(block.Operations, node); + + switch (node.Instruction) + { + case Instruction.Call: + // Get the maximum number of arguments used on a call. + // On windows, when a struct is returned from the call, + // we also need to pass the pointer where the struct + // should be written on the first argument. + int argsCount = node.SourcesCount - 1; + + if (node.Destination != default && node.Destination.Type == OperandType.V128) + { + argsCount++; + } + + if (maxCallArgs < argsCount) + { + maxCallArgs = argsCount; + } + + // Copy values to registers expected by the function + // being called, as mandated by the ABI. + if (callConv == CallConvName.Windows) + { + PreAllocatorWindows.InsertCallCopies(block.Operations, stackAlloc, node); + } + else /* if (callConv == CallConvName.SystemV) */ + { + PreAllocatorSystemV.InsertCallCopies(block.Operations, node); + } + break; + + case Instruction.ConvertToFPUI: + GenerateConvertToFPUI(block.Operations, node); + break; + + case Instruction.LoadArgument: + if (callConv == CallConvName.Windows) + { + nextNode = PreAllocatorWindows.InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node); + } + else /* if (callConv == CallConvName.SystemV) */ + { + nextNode = PreAllocatorSystemV.InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node); + } + break; + + case Instruction.Negate: + if (!node.GetSource(0).Type.IsInteger()) + { + GenerateNegate(block.Operations, node); + } + break; + + case Instruction.Return: + if (callConv == CallConvName.Windows) + { + PreAllocatorWindows.InsertReturnCopy(cctx, block.Operations, preservedArgs, node); + } + else /* if (callConv == CallConvName.SystemV) */ + { + PreAllocatorSystemV.InsertReturnCopy(block.Operations, node); + } + break; + + case Instruction.Tailcall: + if (callConv == CallConvName.Windows) + { + PreAllocatorWindows.InsertTailcallCopies(block.Operations, node); + } + else + { + PreAllocatorSystemV.InsertTailcallCopies(block.Operations, node); + } + break; + + case Instruction.VectorInsert8: + if (!HardwareCapabilities.SupportsSse41) + { + GenerateVectorInsert8(block.Operations, node); + } + break; + + case Instruction.Extended: + 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; + } + } + } + } + + protected static void InsertConstantRegCopies(IntrusiveList nodes, Operation node) + { + if (node.SourcesCount == 0 || IsXmmIntrinsic(node)) + { + return; + } + + Instruction inst = node.Instruction; + + Operand src1 = node.GetSource(0); + Operand src2; + + if (src1.Kind == OperandKind.Constant) + { + if (!src1.Type.IsInteger()) + { + // Handle non-integer types (FP32, FP64 and V128). + // For instructions without an immediate operand, we do the following: + // - Insert a copy with the constant value (as integer) to a GPR. + // - Insert a copy from the GPR to a XMM register. + // - Replace the constant use with the XMM register. + src1 = AddXmmCopy(nodes, node, src1); + + node.SetSource(0, src1); + } + else if (!HasConstSrc1(inst)) + { + // Handle integer types. + // Most ALU instructions accepts a 32-bits immediate on the second operand. + // We need to ensure the following: + // - If the constant is on operand 1, we need to move it. + // -- But first, we try to swap operand 1 and 2 if the instruction is commutative. + // -- Doing so may allow us to encode the constant as operand 2 and avoid a copy. + // - If the constant is on operand 2, we check if the instruction supports it, + // if not, we also add a copy. 64-bits constants are usually not supported. + if (IsCommutative(node)) + { + src2 = node.GetSource(1); + + (src2, src1) = (src1, src2); + + node.SetSource(0, src1); + node.SetSource(1, src2); + } + + if (src1.Kind == OperandKind.Constant) + { + src1 = AddCopy(nodes, node, src1); + + node.SetSource(0, src1); + } + } + } + + if (node.SourcesCount < 2) + { + return; + } + + src2 = node.GetSource(1); + + if (src2.Kind == OperandKind.Constant) + { + if (!src2.Type.IsInteger()) + { + src2 = AddXmmCopy(nodes, node, src2); + + node.SetSource(1, src2); + } + else if (!HasConstSrc2(inst) || CodeGenCommon.IsLongConst(src2)) + { + src2 = AddCopy(nodes, node, src2); + + node.SetSource(1, src2); + } + } + } + + protected static void InsertConstrainedRegCopies(IntrusiveList nodes, Operation node) + { + Operand dest = node.Destination; + + switch (node.Instruction) + { + case Instruction.CompareAndSwap: + case Instruction.CompareAndSwap16: + case Instruction.CompareAndSwap8: + { + OperandType type = node.GetSource(1).Type; + + if (type == OperandType.V128) + { + // Handle the many restrictions of the compare and exchange (16 bytes) instruction: + // - The expected value should be in RDX:RAX. + // - The new value to be written should be in RCX:RBX. + // - The value at the memory location is loaded to RDX:RAX. + void SplitOperand(Operand source, Operand lr, Operand hr) + { + nodes.AddBefore(node, Operation(Instruction.VectorExtract, lr, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, hr, source, Const(1))); + } + + Operand rax = Gpr(X86Register.Rax, OperandType.I64); + Operand rbx = Gpr(X86Register.Rbx, OperandType.I64); + Operand rcx = Gpr(X86Register.Rcx, OperandType.I64); + Operand rdx = Gpr(X86Register.Rdx, OperandType.I64); + + SplitOperand(node.GetSource(1), rax, rdx); + SplitOperand(node.GetSource(2), rbx, rcx); + + Operation operation = node; + + node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, rax)); + nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, rdx, Const(1))); + + operation.SetDestinations(new Operand[] { rdx, rax }); + operation.SetSources(new Operand[] { operation.GetSource(0), rdx, rax, rcx, rbx }); + } + else + { + // Handle the many restrictions of the compare and exchange (32/64) instruction: + // - The expected value should be in (E/R)AX. + // - The value at the memory location is loaded to (E/R)AX. + Operand expected = node.GetSource(1); + Operand newValue = node.GetSource(2); + + Operand rax = Gpr(X86Register.Rax, expected.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, rax, expected)); + + // We need to store the new value into a temp, since it may + // be a constant, and this instruction does not support immediate operands. + Operand temp = Local(newValue.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, temp, newValue)); + + node.SetSources(new Operand[] { node.GetSource(0), rax, temp }); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, rax)); + + node.Destination = rax; + } + + break; + } + + case Instruction.Divide: + case Instruction.DivideUI: + { + // Handle the many restrictions of the division instructions: + // - The dividend is always in RDX:RAX. + // - The result is always in RAX. + // - Additionally it also writes the remainder in RDX. + if (dest.Type.IsInteger()) + { + Operand src1 = node.GetSource(0); + + Operand rax = Gpr(X86Register.Rax, src1.Type); + Operand rdx = Gpr(X86Register.Rdx, src1.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, rax, src1)); + nodes.AddBefore(node, Operation(Instruction.Clobber, rdx)); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, rax)); + + node.SetSources(new Operand[] { rdx, rax, node.GetSource(1) }); + node.Destination = rax; + } + + break; + } + + case Instruction.Extended: + { + bool isBlend = node.Intrinsic == Intrinsic.X86Blendvpd || + node.Intrinsic == Intrinsic.X86Blendvps || + node.Intrinsic == Intrinsic.X86Pblendvb; + + // BLENDVPD, BLENDVPS, PBLENDVB last operand is always implied to be XMM0 when VEX is not supported. + // SHA256RNDS2 always has an implied XMM0 as a last operand. + if ((isBlend && !HardwareCapabilities.SupportsVexEncoding) || node.Intrinsic == Intrinsic.X86Sha256Rnds2) + { + Operand xmm0 = Xmm(X86Register.Xmm0, OperandType.V128); + + nodes.AddBefore(node, Operation(Instruction.Copy, xmm0, node.GetSource(2))); + + node.SetSource(2, xmm0); + } + + break; + } + + case Instruction.Multiply64HighSI: + case Instruction.Multiply64HighUI: + { + // Handle the many restrictions of the i64 * i64 = i128 multiply instructions: + // - The multiplicand is always in RAX. + // - The lower 64-bits of the result is always in RAX. + // - The higher 64-bits of the result is always in RDX. + Operand src1 = node.GetSource(0); + + Operand rax = Gpr(X86Register.Rax, src1.Type); + Operand rdx = Gpr(X86Register.Rdx, src1.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, rax, src1)); + + node.SetSource(0, rax); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, rdx)); + + node.SetDestinations(new Operand[] { rdx, rax }); + + break; + } + + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + { + // The shift register is always implied to be CL (low 8-bits of RCX or ECX). + if (node.GetSource(1).Kind == OperandKind.LocalVariable) + { + Operand rcx = Gpr(X86Register.Rcx, OperandType.I32); + + nodes.AddBefore(node, Operation(Instruction.Copy, rcx, node.GetSource(1))); + + node.SetSource(1, rcx); + } + + break; + } + } + } + + protected static void InsertDestructiveRegCopies(IntrusiveList nodes, Operation node) + { + if (node.Destination == default || node.SourcesCount == 0) + { + return; + } + + Instruction inst = node.Instruction; + + Operand dest = node.Destination; + Operand src1 = node.GetSource(0); + + // The multiply instruction (that maps to IMUL) is somewhat special, it has + // a three operand form where the second source is a immediate value. + bool threeOperandForm = inst == Instruction.Multiply && node.GetSource(1).Kind == OperandKind.Constant; + + if (IsSameOperandDestSrc1(node) && src1.Kind == OperandKind.LocalVariable && !threeOperandForm) + { + bool useNewLocal = false; + + for (int srcIndex = 1; srcIndex < node.SourcesCount; srcIndex++) + { + if (node.GetSource(srcIndex) == dest) + { + useNewLocal = true; + + break; + } + } + + if (useNewLocal) + { + // Dest is being used as some source already, we need to use a new + // local to store the temporary value, otherwise the value on dest + // local would be overwritten. + Operand temp = Local(dest.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, temp, src1)); + + node.SetSource(0, temp); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, temp)); + + node.Destination = temp; + } + else + { + nodes.AddBefore(node, Operation(Instruction.Copy, dest, src1)); + + node.SetSource(0, dest); + } + } + else if (inst == Instruction.ConditionalSelect) + { + Operand src2 = node.GetSource(1); + Operand src3 = node.GetSource(2); + + if (src1 == dest || src2 == dest) + { + Operand temp = Local(dest.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, temp, src3)); + + node.SetSource(2, temp); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, temp)); + + node.Destination = temp; + } + else + { + nodes.AddBefore(node, Operation(Instruction.Copy, dest, src3)); + + node.SetSource(2, dest); + } + } + } + + private static void GenerateConvertToFPUI(IntrusiveList nodes, Operation node) + { + // Unsigned integer to FP conversions are not supported on X86. + // We need to turn them into signed integer to FP conversions, and + // adjust the final result. + Operand dest = node.Destination; + Operand source = node.GetSource(0); + + Debug.Assert(source.Type.IsInteger(), $"Invalid source type \"{source.Type}\"."); + + Operation currentNode = node; + + if (source.Type == OperandType.I32) + { + // For 32-bits integers, we can just zero-extend to 64-bits, + // and then use the 64-bits signed conversion instructions. + Operand zex = Local(OperandType.I64); + + node = nodes.AddAfter(node, Operation(Instruction.ZeroExtend32, zex, source)); + nodes.AddAfter(node, Operation(Instruction.ConvertToFP, dest, zex)); + } + else /* if (source.Type == OperandType.I64) */ + { + // For 64-bits integers, we need to do the following: + // - Ensure that the integer has the most significant bit clear. + // -- This can be done by shifting the value right by 1, that is, dividing by 2. + // -- The least significant bit is lost in this case though. + // - We can then convert the shifted value with a signed integer instruction. + // - The result still needs to be corrected after that. + // -- First, we need to multiply the result by 2, as we divided it by 2 before. + // --- This can be done efficiently by adding the result to itself. + // -- Then, we need to add the least significant bit that was shifted out. + // --- We can convert the least significant bit to float, and add it to the result. + Operand lsb = Local(OperandType.I64); + Operand half = Local(OperandType.I64); + + Operand lsbF = Local(dest.Type); + + node = nodes.AddAfter(node, Operation(Instruction.Copy, lsb, source)); + node = nodes.AddAfter(node, Operation(Instruction.Copy, half, source)); + + node = nodes.AddAfter(node, Operation(Instruction.BitwiseAnd, lsb, lsb, Const(1L))); + node = nodes.AddAfter(node, Operation(Instruction.ShiftRightUI, half, half, Const(1))); + + node = nodes.AddAfter(node, Operation(Instruction.ConvertToFP, lsbF, lsb)); + node = nodes.AddAfter(node, Operation(Instruction.ConvertToFP, dest, half)); + + node = nodes.AddAfter(node, Operation(Instruction.Add, dest, dest, dest)); + nodes.AddAfter(node, Operation(Instruction.Add, dest, dest, lsbF)); + } + + Delete(nodes, currentNode); + } + + private static void GenerateNegate(IntrusiveList nodes, Operation node) + { + // There's no SSE FP negate instruction, so we need to transform that into + // a XOR of the value to be negated with a mask with the highest bit set. + // This also produces -0 for a negation of the value 0. + Operand dest = node.Destination; + Operand source = node.GetSource(0); + + Debug.Assert(dest.Type == OperandType.FP32 || + dest.Type == OperandType.FP64, $"Invalid destination type \"{dest.Type}\"."); + + Operation currentNode = node; + + Operand res = Local(dest.Type); + + node = nodes.AddAfter(node, Operation(Instruction.VectorOne, res)); + + if (dest.Type == OperandType.FP32) + { + node = nodes.AddAfter(node, Operation(Intrinsic.X86Pslld, res, res, Const(31))); + } + else /* if (dest.Type == OperandType.FP64) */ + { + node = nodes.AddAfter(node, Operation(Intrinsic.X86Psllq, res, res, Const(63))); + } + + node = nodes.AddAfter(node, Operation(Intrinsic.X86Xorps, res, res, source)); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, res)); + + Delete(nodes, currentNode); + } + + private static void GenerateVectorInsert8(IntrusiveList nodes, Operation node) + { + // Handle vector insertion, when SSE 4.1 is not supported. + Operand dest = node.Destination; + Operand src1 = node.GetSource(0); // Vector + Operand src2 = node.GetSource(1); // Value + Operand src3 = node.GetSource(2); // Index + + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + Debug.Assert(index < 16); + + Operation currentNode = node; + + Operand temp1 = Local(OperandType.I32); + Operand temp2 = Local(OperandType.I32); + + node = nodes.AddAfter(node, Operation(Instruction.Copy, temp2, src2)); + + Operation vextOp = Operation(Instruction.VectorExtract16, temp1, src1, Const(index >> 1)); + + node = nodes.AddAfter(node, vextOp); + + if ((index & 1) != 0) + { + node = nodes.AddAfter(node, Operation(Instruction.ZeroExtend8, temp1, temp1)); + node = nodes.AddAfter(node, Operation(Instruction.ShiftLeft, temp2, temp2, Const(8))); + node = nodes.AddAfter(node, Operation(Instruction.BitwiseOr, temp1, temp1, temp2)); + } + else + { + node = nodes.AddAfter(node, Operation(Instruction.ZeroExtend8, temp2, temp2)); + node = nodes.AddAfter(node, Operation(Instruction.BitwiseAnd, temp1, temp1, Const(0xff00))); + node = nodes.AddAfter(node, Operation(Instruction.BitwiseOr, temp1, temp1, temp2)); + } + + Operation vinsOp = Operation(Instruction.VectorInsert16, dest, src1, temp1, Const(index >> 1)); + + nodes.AddAfter(node, vinsOp); + + Delete(nodes, currentNode); + } + + protected static Operand AddXmmCopy(IntrusiveList nodes, Operation node, Operand source) + { + Operand temp = Local(source.Type); + Operand intConst = AddCopy(nodes, node, GetIntConst(source)); + + Operation copyOp = Operation(Instruction.VectorCreateScalar, temp, intConst); + + nodes.AddBefore(node, copyOp); + + return temp; + } + + protected static Operand AddCopy(IntrusiveList nodes, Operation node, Operand source) + { + Operand temp = Local(source.Type); + + Operation copyOp = Operation(Instruction.Copy, temp, source); + + nodes.AddBefore(node, copyOp); + + return temp; + } + + private static Operand GetIntConst(Operand value) + { + if (value.Type == OperandType.FP32) + { + return Const(value.AsInt32()); + } + else if (value.Type == OperandType.FP64) + { + return Const(value.AsInt64()); + } + + return value; + } + + protected static void Delete(IntrusiveList nodes, Operation node) + { + node.Destination = default; + + for (int index = 0; index < node.SourcesCount; index++) + { + node.SetSource(index, default); + } + + nodes.Remove(node); + } + + protected static Operand Gpr(X86Register register, OperandType type) + { + return Register((int)register, RegisterType.Integer, type); + } + + protected static Operand Xmm(X86Register register, OperandType type) + { + return Register((int)register, RegisterType.Vector, type); + } + + private static bool IsSameOperandDestSrc1(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Add: + return !HardwareCapabilities.SupportsVexEncoding && !operation.Destination.Type.IsInteger(); + case Instruction.Multiply: + case Instruction.Subtract: + return !HardwareCapabilities.SupportsVexEncoding || operation.Destination.Type.IsInteger(); + + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseNot: + case Instruction.BitwiseOr: + case Instruction.ByteSwap: + case Instruction.Negate: + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + return true; + + case Instruction.Divide: + return !HardwareCapabilities.SupportsVexEncoding && !operation.Destination.Type.IsInteger(); + + case Instruction.VectorInsert: + case Instruction.VectorInsert16: + case Instruction.VectorInsert8: + return !HardwareCapabilities.SupportsVexEncoding; + + case Instruction.Extended: + return IsIntrinsicSameOperandDestSrc1(operation); + } + + return IsVexSameOperandDestSrc1(operation); + } + + private static bool IsIntrinsicSameOperandDestSrc1(Operation operation) + { + IntrinsicInfo info = IntrinsicTable.GetInfo(operation.Intrinsic); + + return info.Type == IntrinsicType.Crc32 || info.Type == IntrinsicType.Fma || IsVexSameOperandDestSrc1(operation); + } + + private static bool IsVexSameOperandDestSrc1(Operation operation) + { + if (IsIntrinsic(operation.Instruction)) + { + IntrinsicInfo info = IntrinsicTable.GetInfo(operation.Intrinsic); + + bool hasVex = HardwareCapabilities.SupportsVexEncoding && Assembler.SupportsVexPrefix(info.Inst); + + bool isUnary = operation.SourcesCount < 2; + + bool hasVecDest = operation.Destination != default && operation.Destination.Type == OperandType.V128; + + return !hasVex && !isUnary && hasVecDest; + } + + return false; + } + + private static bool HasConstSrc1(Instruction inst) + { + return inst switch + { + Instruction.Copy or Instruction.LoadArgument or Instruction.Spill or Instruction.SpillArg => true, + _ => false, + }; + } + + private static bool HasConstSrc2(Instruction inst) + { + switch (inst) + { + case Instruction.Add: + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseOr: + case Instruction.BranchIf: + case Instruction.Compare: + case Instruction.Multiply: + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + case Instruction.Store: + case Instruction.Store16: + case Instruction.Store8: + case Instruction.Subtract: + case Instruction.VectorExtract: + case Instruction.VectorExtract16: + case Instruction.VectorExtract8: + return true; + } + + return false; + } + + private static bool IsCommutative(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Add: + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseOr: + case Instruction.Multiply: + return true; + + case Instruction.BranchIf: + case Instruction.Compare: + { + Operand comp = operation.GetSource(2); + + Debug.Assert(comp.Kind == OperandKind.Constant); + + var compType = (Comparison)comp.AsInt32(); + + return compType == Comparison.Equal || compType == Comparison.NotEqual; + } + } + + return false; + } + + private static bool IsIntrinsic(Instruction inst) + { + return inst == Instruction.Extended; + } + + private static bool IsXmmIntrinsic(Operation operation) + { + if (operation.Instruction != Instruction.Extended) + { + return false; + } + + IntrinsicInfo info = IntrinsicTable.GetInfo(operation.Intrinsic); + + return info.Type != IntrinsicType.Crc32; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs b/src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs new file mode 100644 index 00000000..e754cb09 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs @@ -0,0 +1,333 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + class PreAllocatorSystemV : PreAllocator + { + public static void InsertCallCopies(IntrusiveList nodes, Operation node) + { + Operand dest = node.Destination; + + List sources = new() + { + node.GetSource(0), + }; + + int argsCount = node.SourcesCount - 1; + + int intMax = CallingConvention.GetIntArgumentsOnRegsCount(); + int vecMax = CallingConvention.GetVecArgumentsOnRegsCount(); + + int intCount = 0; + int vecCount = 0; + + int stackOffset = 0; + + for (int index = 0; index < argsCount; index++) + { + Operand source = node.GetSource(index + 1); + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount < intMax; + } + else if (source.Type == OperandType.V128) + { + passOnReg = intCount + 1 < intMax; + } + else + { + passOnReg = vecCount < vecMax; + } + + if (source.Type == OperandType.V128 && passOnReg) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1))); + + continue; + } + + if (passOnReg) + { + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp)); + + sources.Add(argReg); + } + else + { + Operand offset = Const(stackOffset); + + Operation spillOp = Operation(Instruction.SpillArg, default, offset, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, spillOp)); + + stackOffset += source.Type.GetSizeInBytes(); + } + } + + node.SetSources(sources.ToArray()); + + if (dest != default) + { + if (dest.Type == OperandType.V128) + { + Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64); + + Operation operation = node; + + node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, retLReg)); + nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, retHReg, Const(1))); + + operation.Destination = default; + } + else + { + Operand retReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, dest, retReg); + + nodes.AddAfter(node, copyOp); + + node.Destination = retReg; + } + } + } + + public static void InsertTailcallCopies(IntrusiveList nodes, Operation node) + { + List sources = new() + { + node.GetSource(0), + }; + + int argsCount = node.SourcesCount - 1; + + int intMax = CallingConvention.GetIntArgumentsOnRegsCount(); + int vecMax = CallingConvention.GetVecArgumentsOnRegsCount(); + + int intCount = 0; + int vecCount = 0; + + // Handle arguments passed on registers. + for (int index = 0; index < argsCount; index++) + { + Operand source = node.GetSource(1 + index); + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount + 1 < intMax; + } + else + { + passOnReg = vecCount < vecMax; + } + + if (source.Type == OperandType.V128 && passOnReg) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1))); + + continue; + } + + if (passOnReg) + { + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp)); + + sources.Add(argReg); + } + else + { + throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)"); + } + } + + // The target address must be on the return registers, since we + // don't return anything and it is guaranteed to not be a + // callee saved register (which would be trashed on the epilogue). + Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + + Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0)); + + nodes.AddBefore(node, addrCopyOp); + + sources[0] = retReg; + + node.SetSources(sources.ToArray()); + } + + public static Operation InsertLoadArgumentCopy( + CompilerContext cctx, + ref Span buffer, + IntrusiveList nodes, + Operand[] preservedArgs, + Operation node) + { + Operand source = node.GetSource(0); + + Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind."); + + int index = source.AsInt32(); + + int intCount = 0; + int vecCount = 0; + + for (int cIndex = 0; cIndex < index; cIndex++) + { + OperandType argType = cctx.FuncArgTypes[cIndex]; + + if (argType.IsInteger()) + { + intCount++; + } + else if (argType == OperandType.V128) + { + intCount += 2; + } + else + { + vecCount++; + } + } + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount < CallingConvention.GetIntArgumentsOnRegsCount(); + } + else if (source.Type == OperandType.V128) + { + passOnReg = intCount + 1 < CallingConvention.GetIntArgumentsOnRegsCount(); + } + else + { + passOnReg = vecCount < CallingConvention.GetVecArgumentsOnRegsCount(); + } + + if (passOnReg) + { + Operand dest = node.Destination; + + if (preservedArgs[index] == default) + { + if (dest.Type == OperandType.V128) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand pArg = Local(OperandType.V128); + + Operand argLReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount), OperandType.I64); + Operand argHReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount + 1), OperandType.I64); + + Operation copyL = Operation(Instruction.VectorCreateScalar, pArg, argLReg); + Operation copyH = Operation(Instruction.VectorInsert, pArg, pArg, argHReg, Const(1)); + + cctx.Cfg.Entry.Operations.AddFirst(copyH); + cctx.Cfg.Entry.Operations.AddFirst(copyL); + + preservedArgs[index] = pArg; + } + else + { + Operand pArg = Local(dest.Type); + + Operand argReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, pArg, argReg); + + cctx.Cfg.Entry.Operations.AddFirst(copyOp); + + preservedArgs[index] = pArg; + } + } + + Operation nextNode; + + if (dest.AssignmentsCount == 1) + { + // Let's propagate the argument if we can to avoid copies. + PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]); + nextNode = node.ListNext; + } + else + { + Operation argCopyOp = Operation(Instruction.Copy, dest, preservedArgs[index]); + nextNode = nodes.AddBefore(node, argCopyOp); + } + + Delete(nodes, node); + return nextNode; + } + else + { + // TODO: Pass on stack. + return node; + } + } + + public static void InsertReturnCopy(IntrusiveList nodes, Operation node) + { + if (node.SourcesCount == 0) + { + return; + } + + Operand source = node.GetSource(0); + + if (source.Type == OperandType.V128) + { + Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, retLReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, retHReg, source, Const(1))); + } + else + { + Operand retReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), source.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), source.Type); + + Operation retCopyOp = Operation(Instruction.Copy, retReg, source); + + nodes.AddBefore(node, retCopyOp); + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs b/src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs new file mode 100644 index 00000000..10a2bd12 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs @@ -0,0 +1,327 @@ +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + class PreAllocatorWindows : PreAllocator + { + public static void InsertCallCopies(IntrusiveList nodes, StackAllocator stackAlloc, Operation node) + { + Operand dest = node.Destination; + + // Handle struct arguments. + int retArgs = 0; + int stackAllocOffset = 0; + + int AllocateOnStack(int size) + { + // We assume that the stack allocator is initially empty (TotalSize = 0). + // Taking that into account, we can reuse the space allocated for other + // calls by keeping track of our own allocated size (stackAllocOffset). + // If the space allocated is not big enough, then we just expand it. + int offset = stackAllocOffset; + + if (stackAllocOffset + size > stackAlloc.TotalSize) + { + stackAlloc.Allocate((stackAllocOffset + size) - stackAlloc.TotalSize); + } + + stackAllocOffset += size; + + return offset; + } + + Operand arg0Reg = default; + + if (dest != default && dest.Type == OperandType.V128) + { + int stackOffset = AllocateOnStack(dest.Type.GetSizeInBytes()); + + arg0Reg = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64); + + Operation allocOp = Operation(Instruction.StackAlloc, arg0Reg, Const(stackOffset)); + + nodes.AddBefore(node, allocOp); + + retArgs = 1; + } + + int argsCount = node.SourcesCount - 1; + int maxArgs = CallingConvention.GetArgumentsOnRegsCount() - retArgs; + + if (argsCount > maxArgs) + { + argsCount = maxArgs; + } + + Operand[] sources = new Operand[1 + retArgs + argsCount]; + + sources[0] = node.GetSource(0); + + if (arg0Reg != default) + { + sources[1] = arg0Reg; + } + + for (int index = 1; index < node.SourcesCount; index++) + { + Operand source = node.GetSource(index); + + if (source.Type == OperandType.V128) + { + Operand stackAddr = Local(OperandType.I64); + + int stackOffset = AllocateOnStack(source.Type.GetSizeInBytes()); + + nodes.AddBefore(node, Operation(Instruction.StackAlloc, stackAddr, Const(stackOffset))); + + Operation storeOp = Operation(Instruction.Store, default, stackAddr, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, storeOp)); + + node.SetSource(index, stackAddr); + } + } + + // Handle arguments passed on registers. + for (int index = 0; index < argsCount; index++) + { + Operand source = node.GetSource(index + 1); + Operand argReg; + + int argIndex = index + retArgs; + + if (source.Type.IsInteger()) + { + argReg = Gpr(CallingConvention.GetIntArgumentRegister(argIndex), source.Type); + } + else + { + argReg = Xmm(CallingConvention.GetVecArgumentRegister(argIndex), source.Type); + } + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp)); + + sources[1 + retArgs + index] = argReg; + } + + // The remaining arguments (those that are not passed on registers) + // should be passed on the stack, we write them to the stack with "SpillArg". + for (int index = argsCount; index < node.SourcesCount - 1; index++) + { + Operand source = node.GetSource(index + 1); + Operand offset = Const((index + retArgs) * 8); + + Operation spillOp = Operation(Instruction.SpillArg, default, offset, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, spillOp)); + } + + if (dest != default) + { + if (dest.Type == OperandType.V128) + { + Operand retValueAddr = Local(OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.Copy, retValueAddr, arg0Reg)); + + Operation loadOp = Operation(Instruction.Load, dest, retValueAddr); + + nodes.AddAfter(node, loadOp); + + node.Destination = default; + } + else + { + Operand retReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, dest, retReg); + + nodes.AddAfter(node, copyOp); + + node.Destination = retReg; + } + } + + node.SetSources(sources); + } + + public static void InsertTailcallCopies(IntrusiveList nodes, Operation node) + { + int argsCount = node.SourcesCount - 1; + int maxArgs = CallingConvention.GetArgumentsOnRegsCount(); + + if (argsCount > maxArgs) + { + throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)"); + } + + Operand[] sources = new Operand[1 + argsCount]; + + // Handle arguments passed on registers. + for (int index = 0; index < argsCount; index++) + { + Operand source = node.GetSource(1 + index); + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(index), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(index), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp)); + + sources[1 + index] = argReg; + } + + // The target address must be on the return registers, since we + // don't return anything and it is guaranteed to not be a + // callee saved register (which would be trashed on the epilogue). + Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + + Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0)); + + nodes.AddBefore(node, addrCopyOp); + + sources[0] = retReg; + + node.SetSources(sources); + } + + public static Operation InsertLoadArgumentCopy( + CompilerContext cctx, + ref Span buffer, + IntrusiveList nodes, + Operand[] preservedArgs, + Operation node) + { + Operand source = node.GetSource(0); + + Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind."); + + int retArgs = cctx.FuncReturnType == OperandType.V128 ? 1 : 0; + + int index = source.AsInt32() + retArgs; + + if (index < CallingConvention.GetArgumentsOnRegsCount()) + { + Operand dest = node.Destination; + + if (preservedArgs[index] == default) + { + Operand argReg, pArg; + + if (dest.Type.IsInteger()) + { + argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), dest.Type); + pArg = Local(dest.Type); + } + else if (dest.Type == OperandType.V128) + { + argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), OperandType.I64); + pArg = Local(OperandType.I64); + } + else + { + argReg = Xmm(CallingConvention.GetVecArgumentRegister(index), dest.Type); + pArg = Local(dest.Type); + } + + Operation copyOp = Operation(Instruction.Copy, pArg, argReg); + + cctx.Cfg.Entry.Operations.AddFirst(copyOp); + + preservedArgs[index] = pArg; + } + + Operation nextNode; + + if (dest.Type != OperandType.V128 && dest.AssignmentsCount == 1) + { + // Let's propagate the argument if we can to avoid copies. + PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]); + nextNode = node.ListNext; + } + else + { + Operation argCopyOp = Operation(dest.Type == OperandType.V128 + ? Instruction.Load + : Instruction.Copy, dest, preservedArgs[index]); + + nextNode = nodes.AddBefore(node, argCopyOp); + } + + Delete(nodes, node); + return nextNode; + } + else + { + // TODO: Pass on stack. + return node; + } + } + + public static void InsertReturnCopy( + CompilerContext cctx, + IntrusiveList nodes, + Operand[] preservedArgs, + Operation node) + { + if (node.SourcesCount == 0) + { + return; + } + + Operand source = node.GetSource(0); + Operand retReg; + + if (source.Type.IsInteger()) + { + retReg = Gpr(CallingConvention.GetIntReturnRegister(), source.Type); + } + else if (source.Type == OperandType.V128) + { + if (preservedArgs[0] == default) + { + Operand preservedArg = Local(OperandType.I64); + Operand arg0 = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64); + + Operation copyOp = Operation(Instruction.Copy, preservedArg, arg0); + + cctx.Cfg.Entry.Operations.AddFirst(copyOp); + + preservedArgs[0] = preservedArg; + } + + retReg = preservedArgs[0]; + } + else + { + retReg = Xmm(CallingConvention.GetVecReturnRegister(), source.Type); + } + + if (source.Type == OperandType.V128) + { + Operation retStoreOp = Operation(Instruction.Store, default, retReg, source); + + nodes.AddBefore(node, retStoreOp); + } + else + { + Operation retCopyOp = Operation(Instruction.Copy, retReg, source); + + nodes.AddBefore(node, retCopyOp); + } + + node.SetSources(Array.Empty()); + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/X86Condition.cs b/src/ARMeilleure/CodeGen/X86/X86Condition.cs new file mode 100644 index 00000000..70699a20 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/X86Condition.cs @@ -0,0 +1,49 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +namespace ARMeilleure.CodeGen.X86 +{ + enum X86Condition + { + Overflow = 0x0, + NotOverflow = 0x1, + Below = 0x2, + AboveOrEqual = 0x3, + Equal = 0x4, + NotEqual = 0x5, + BelowOrEqual = 0x6, + Above = 0x7, + Sign = 0x8, + NotSign = 0x9, + ParityEven = 0xa, + ParityOdd = 0xb, + Less = 0xc, + GreaterOrEqual = 0xd, + LessOrEqual = 0xe, + Greater = 0xf, + } + + static class ComparisonX86Extensions + { + public static X86Condition ToX86Condition(this Comparison comp) + { + return comp switch + { +#pragma warning disable IDE0055 // Disable formatting + Comparison.Equal => X86Condition.Equal, + Comparison.NotEqual => X86Condition.NotEqual, + Comparison.Greater => X86Condition.Greater, + Comparison.LessOrEqual => X86Condition.LessOrEqual, + Comparison.GreaterUI => X86Condition.Above, + Comparison.LessOrEqualUI => X86Condition.BelowOrEqual, + Comparison.GreaterOrEqual => X86Condition.GreaterOrEqual, + Comparison.Less => X86Condition.Less, + Comparison.GreaterOrEqualUI => X86Condition.AboveOrEqual, + Comparison.LessUI => X86Condition.Below, +#pragma warning restore IDE0055 + + _ => throw new ArgumentException(null, nameof(comp)), + }; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/X86Instruction.cs b/src/ARMeilleure/CodeGen/X86/X86Instruction.cs new file mode 100644 index 00000000..e1979011 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/X86Instruction.cs @@ -0,0 +1,231 @@ +namespace ARMeilleure.CodeGen.X86 +{ + enum X86Instruction + { + None, + Add, + Addpd, + Addps, + Addsd, + Addss, + Aesdec, + Aesdeclast, + Aesenc, + Aesenclast, + Aesimc, + And, + Andnpd, + Andnps, + Andpd, + Andps, + Blendvpd, + Blendvps, + Bsr, + Bswap, + Call, + Cmovcc, + Cmp, + Cmppd, + Cmpps, + Cmpsd, + Cmpss, + Cmpxchg, + Cmpxchg16b, + Cmpxchg8, + Comisd, + Comiss, + Crc32, + Crc32_16, + Crc32_8, + Cvtdq2pd, + Cvtdq2ps, + Cvtpd2dq, + Cvtpd2ps, + Cvtps2dq, + Cvtps2pd, + Cvtsd2si, + Cvtsd2ss, + Cvtsi2sd, + Cvtsi2ss, + Cvtss2sd, + Cvtss2si, + Div, + Divpd, + Divps, + Divsd, + Divss, + Gf2p8affineqb, + Haddpd, + Haddps, + Idiv, + Imul, + Imul128, + Insertps, + Jmp, + Ldmxcsr, + Lea, + Maxpd, + Maxps, + Maxsd, + Maxss, + Minpd, + Minps, + Minsd, + Minss, + Mov, + Mov16, + Mov8, + Movd, + Movdqu, + Movhlps, + Movlhps, + Movq, + Movsd, + Movss, + Movsx16, + Movsx32, + Movsx8, + Movzx16, + Movzx8, + Mul128, + Mulpd, + Mulps, + Mulsd, + Mulss, + Neg, + Not, + Or, + Paddb, + Paddd, + Paddq, + Paddw, + Palignr, + Pand, + Pandn, + Pavgb, + Pavgw, + Pblendvb, + Pclmulqdq, + Pcmpeqb, + Pcmpeqd, + Pcmpeqq, + Pcmpeqw, + Pcmpgtb, + Pcmpgtd, + Pcmpgtq, + Pcmpgtw, + Pextrb, + Pextrd, + Pextrq, + Pextrw, + Pinsrb, + Pinsrd, + Pinsrq, + Pinsrw, + Pmaxsb, + Pmaxsd, + Pmaxsw, + Pmaxub, + Pmaxud, + Pmaxuw, + Pminsb, + Pminsd, + Pminsw, + Pminub, + Pminud, + Pminuw, + Pmovsxbw, + Pmovsxdq, + Pmovsxwd, + Pmovzxbw, + Pmovzxdq, + Pmovzxwd, + Pmulld, + Pmullw, + Pop, + Popcnt, + Por, + Pshufb, + Pshufd, + Pslld, + Pslldq, + Psllq, + Psllw, + Psrad, + Psraw, + Psrld, + Psrlq, + Psrldq, + Psrlw, + Psubb, + Psubd, + Psubq, + Psubw, + Punpckhbw, + Punpckhdq, + Punpckhqdq, + Punpckhwd, + Punpcklbw, + Punpckldq, + Punpcklqdq, + Punpcklwd, + Push, + Pxor, + Rcpps, + Rcpss, + Ror, + Roundpd, + Roundps, + Roundsd, + Roundss, + Rsqrtps, + Rsqrtss, + Sar, + Setcc, + Sha256Msg1, + Sha256Msg2, + Sha256Rnds2, + Shl, + Shr, + Shufpd, + Shufps, + Sqrtpd, + Sqrtps, + Sqrtsd, + Sqrtss, + Stmxcsr, + Sub, + Subpd, + Subps, + Subsd, + Subss, + Test, + Unpckhpd, + Unpckhps, + Unpcklpd, + Unpcklps, + Vblendvpd, + Vblendvps, + Vcvtph2ps, + Vcvtps2ph, + Vfmadd231pd, + Vfmadd231ps, + Vfmadd231sd, + Vfmadd231ss, + Vfmsub231sd, + Vfmsub231ss, + Vfnmadd231pd, + Vfnmadd231ps, + Vfnmadd231sd, + Vfnmadd231ss, + Vfnmsub231sd, + Vfnmsub231ss, + Vpblendvb, + Vpternlogd, + Xor, + Xorpd, + Xorps, + + Count, + } +} diff --git a/src/ARMeilleure/CodeGen/X86/X86Optimizer.cs b/src/ARMeilleure/CodeGen/X86/X86Optimizer.cs new file mode 100644 index 00000000..690ca504 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/X86Optimizer.cs @@ -0,0 +1,259 @@ +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Collections.Generic; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + static class X86Optimizer + { + private const int MaxConstantUses = 10000; + + public static void RunPass(ControlFlowGraph cfg) + { + var constants = new Dictionary(); + + Operand GetConstantCopy(BasicBlock block, Operation operation, Operand source) + { + // If the constant has many uses, we also force a new constant mov to be added, in order + // to avoid overflow of the counts field (that is limited to 16 bits). + if (!constants.TryGetValue(source.Value, out var constant) || constant.UsesCount > MaxConstantUses) + { + constant = Local(source.Type); + + Operation copyOp = Operation(Instruction.Copy, constant, source); + + block.Operations.AddBefore(operation, copyOp); + + constants[source.Value] = constant; + } + + return constant; + } + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + constants.Clear(); + + Operation nextNode; + + for (Operation node = block.Operations.First; node != default; node = nextNode) + { + nextNode = node.ListNext; + + // Insert copies for constants that can't fit on a 32-bits immediate. + // Doing this early unblocks a few optimizations. + if (node.Instruction == Instruction.Add) + { + Operand src1 = node.GetSource(0); + Operand src2 = node.GetSource(1); + + if (src1.Kind == OperandKind.Constant && (src1.Relocatable || CodeGenCommon.IsLongConst(src1))) + { + node.SetSource(0, GetConstantCopy(block, node, src1)); + } + + if (src2.Kind == OperandKind.Constant && (src2.Relocatable || CodeGenCommon.IsLongConst(src2))) + { + node.SetSource(1, GetConstantCopy(block, node, src2)); + } + } + + // Try to fold something like: + // shl rbx, 2 + // add rax, rbx + // add rax, 0xcafe + // mov rax, [rax] + // Into: + // mov rax, [rax+rbx*4+0xcafe] + if (IsMemoryLoadOrStore(node.Instruction)) + { + OperandType type; + + if (node.Destination != default) + { + type = node.Destination.Type; + } + else + { + type = node.GetSource(1).Type; + } + + Operand memOp = GetMemoryOperandOrNull(node.GetSource(0), type); + + if (memOp != default) + { + node.SetSource(0, memOp); + } + } + } + } + + Optimizer.RemoveUnusedNodes(cfg); + } + + private static Operand GetMemoryOperandOrNull(Operand addr, OperandType type) + { + Operand baseOp = addr; + + // First we check if the address is the result of a local X with 32-bits immediate + // addition. If that is the case, then the baseOp is X, and the memory operand immediate + // becomes the addition immediate. Otherwise baseOp keeps being the address. + int imm = GetConstOp(ref baseOp); + + // Now we check if the baseOp is the result of a local Y with a local Z addition. + // If that is the case, we now set baseOp to Y and indexOp to Z. We further check + // if Z is the result of a left shift of local W by a value >= 0 and <= 3, if that + // is the case, we set indexOp to W and adjust the scale value of the memory operand + // to match that of the left shift. + // There is one missed case, which is the address being a shift result, but this is + // probably not worth optimizing as it should never happen. + (Operand indexOp, Multiplier scale) = GetIndexOp(ref baseOp); + + // If baseOp is still equal to address, then there's nothing that can be optimized. + if (baseOp == addr) + { + return default; + } + + if (imm == 0 && scale == Multiplier.x1 && indexOp != default) + { + imm = GetConstOp(ref indexOp); + } + + return MemoryOp(type, baseOp, indexOp, scale, imm); + } + + private static int GetConstOp(ref Operand baseOp) + { + Operation operation = GetAsgOpWithInst(baseOp, Instruction.Add); + + if (operation == default) + { + return 0; + } + + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + Operand constOp; + Operand otherOp; + + if (src1.Kind == OperandKind.Constant && src2.Kind == OperandKind.LocalVariable) + { + constOp = src1; + otherOp = src2; + } + else if (src1.Kind == OperandKind.LocalVariable && src2.Kind == OperandKind.Constant) + { + constOp = src2; + otherOp = src1; + } + else + { + return 0; + } + + // If we have addition by 64-bits constant, then we can't optimize it further, + // as we can't encode a 64-bits immediate on the memory operand. + if (CodeGenCommon.IsLongConst(constOp)) + { + return 0; + } + + baseOp = otherOp; + + return constOp.AsInt32(); + } + + private static (Operand, Multiplier) GetIndexOp(ref Operand baseOp) + { + Operand indexOp = default; + + Multiplier scale = Multiplier.x1; + + Operation addOp = GetAsgOpWithInst(baseOp, Instruction.Add); + + if (addOp == default) + { + return (indexOp, scale); + } + + Operand src1 = addOp.GetSource(0); + Operand src2 = addOp.GetSource(1); + + if (src1.Kind != OperandKind.LocalVariable || src2.Kind != OperandKind.LocalVariable) + { + return (indexOp, scale); + } + + baseOp = src1; + indexOp = src2; + + Operation shlOp = GetAsgOpWithInst(src1, Instruction.ShiftLeft); + + bool indexOnSrc2 = false; + + if (shlOp == default) + { + shlOp = GetAsgOpWithInst(src2, Instruction.ShiftLeft); + + indexOnSrc2 = true; + } + + if (shlOp != default) + { + Operand shSrc = shlOp.GetSource(0); + Operand shift = shlOp.GetSource(1); + + if (shSrc.Kind == OperandKind.LocalVariable && shift.Kind == OperandKind.Constant && shift.Value <= 3) + { + scale = shift.Value switch + { + 1 => Multiplier.x2, + 2 => Multiplier.x4, + 3 => Multiplier.x8, + _ => Multiplier.x1, + }; + + baseOp = indexOnSrc2 ? src1 : src2; + indexOp = shSrc; + } + } + + return (indexOp, scale); + } + + private static Operation GetAsgOpWithInst(Operand op, Instruction inst) + { + // If we have multiple assignments, folding is not safe + // as the value may be different depending on the + // control flow path. + if (op.AssignmentsCount != 1) + { + return default; + } + + Operation asgOp = op.Assignments[0]; + + if (asgOp.Instruction != inst) + { + return default; + } + + return asgOp; + } + + private static bool IsMemoryLoadOrStore(Instruction inst) + { + return inst == Instruction.Load || + inst == Instruction.Load16 || + inst == Instruction.Load8 || + inst == Instruction.Store || + inst == Instruction.Store16 || + inst == Instruction.Store8; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/X86Register.cs b/src/ARMeilleure/CodeGen/X86/X86Register.cs new file mode 100644 index 00000000..0a656366 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/X86Register.cs @@ -0,0 +1,44 @@ +using System.Diagnostics.CodeAnalysis; + +namespace ARMeilleure.CodeGen.X86 +{ + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + enum X86Register + { + Invalid = -1, + + Rax = 0, + Rcx = 1, + Rdx = 2, + Rbx = 3, + Rsp = 4, + Rbp = 5, + Rsi = 6, + Rdi = 7, + R8 = 8, + R9 = 9, + R10 = 10, + R11 = 11, + R12 = 12, + R13 = 13, + R14 = 14, + R15 = 15, + + Xmm0 = 0, + Xmm1 = 1, + Xmm2 = 2, + Xmm3 = 3, + Xmm4 = 4, + Xmm5 = 5, + Xmm6 = 6, + Xmm7 = 7, + Xmm8 = 8, + Xmm9 = 9, + Xmm10 = 10, + Xmm11 = 11, + Xmm12 = 12, + Xmm13 = 13, + Xmm14 = 14, + Xmm15 = 15, + } +} diff --git a/src/ARMeilleure/Common/AddressTable.cs b/src/ARMeilleure/Common/AddressTable.cs new file mode 100644 index 00000000..fcab3a20 --- /dev/null +++ b/src/ARMeilleure/Common/AddressTable.cs @@ -0,0 +1,252 @@ +using ARMeilleure.Diagnostics; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Common +{ + /// + /// Represents a table of guest address to a value. + /// + /// Type of the value + public unsafe class AddressTable : IDisposable where TEntry : unmanaged + { + /// + /// Represents a level in an . + /// + public readonly struct Level + { + /// + /// Gets the index of the in the guest address. + /// + public int Index { get; } + + /// + /// Gets the length of the in the guest address. + /// + public int Length { get; } + + /// + /// Gets the mask which masks the bits used by the . + /// + public ulong Mask => ((1ul << Length) - 1) << Index; + + /// + /// Initializes a new instance of the structure with the specified + /// and . + /// + /// Index of the + /// Length of the + public Level(int index, int length) + { + (Index, Length) = (index, length); + } + + /// + /// Gets the value of the from the specified guest . + /// + /// Guest address + /// Value of the from the specified guest + public int GetValue(ulong address) + { + return (int)((address & Mask) >> Index); + } + } + + private bool _disposed; + private TEntry** _table; + private readonly List _pages; + + /// + /// Gets the bits used by the of the instance. + /// + public ulong Mask { get; } + + /// + /// Gets the s used by the instance. + /// + public Level[] Levels { get; } + + /// + /// Gets or sets the default fill value of newly created leaf pages. + /// + public TEntry Fill { get; set; } + + /// + /// Gets the base address of the . + /// + /// instance was disposed + public IntPtr Base + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_pages) + { + return (IntPtr)GetRootPage(); + } + } + } + + /// + /// Constructs a new instance of the class with the specified list of + /// . + /// + /// is null + /// Length of is less than 2 + public AddressTable(Level[] levels) + { + ArgumentNullException.ThrowIfNull(levels); + + if (levels.Length < 2) + { + throw new ArgumentException("Table must be at least 2 levels deep.", nameof(levels)); + } + + _pages = new List(capacity: 16); + + Levels = levels; + Mask = 0; + + foreach (var level in Levels) + { + Mask |= level.Mask; + } + } + + /// + /// Determines if the specified is in the range of the + /// . + /// + /// Guest address + /// if is valid; otherwise + public bool IsValid(ulong address) + { + return (address & ~Mask) == 0; + } + + /// + /// Gets a reference to the value at the specified guest . + /// + /// Guest address + /// Reference to the value at the specified guest + /// instance was disposed + /// is not mapped + public ref TEntry GetValue(ulong address) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (!IsValid(address)) + { + throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address)); + } + + lock (_pages) + { + return ref GetPage(address)[Levels[^1].GetValue(address)]; + } + } + + /// + /// Gets the leaf page for the specified guest . + /// + /// Guest address + /// Leaf page for the specified guest + private TEntry* GetPage(ulong address) + { + TEntry** page = GetRootPage(); + + for (int i = 0; i < Levels.Length - 1; i++) + { + ref Level level = ref Levels[i]; + ref TEntry* nextPage = ref page[level.GetValue(address)]; + + if (nextPage == null) + { + ref Level nextLevel = ref Levels[i + 1]; + + nextPage = i == Levels.Length - 2 ? + (TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true) : + (TEntry*)Allocate(1 << nextLevel.Length, IntPtr.Zero, leaf: false); + } + + page = (TEntry**)nextPage; + } + + return (TEntry*)page; + } + + /// + /// Lazily initialize and get the root page of the . + /// + /// Root page of the + private TEntry** GetRootPage() + { + if (_table == null) + { + _table = (TEntry**)Allocate(1 << Levels[0].Length, fill: IntPtr.Zero, leaf: false); + } + + return _table; + } + + /// + /// Allocates a block of memory of the specified type and length. + /// + /// Type of elements + /// Number of elements + /// Fill value + /// if leaf; otherwise + /// Allocated block + private IntPtr Allocate(int length, T fill, bool leaf) where T : unmanaged + { + var size = sizeof(T) * length; + var page = (IntPtr)NativeAllocator.Instance.Allocate((uint)size); + var span = new Span((void*)page, length); + + span.Fill(fill); + + _pages.Add(page); + + TranslatorEventSource.Log.AddressTableAllocated(size, leaf); + + return page; + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the + /// instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resouces + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + foreach (var page in _pages) + { + Marshal.FreeHGlobal(page); + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~AddressTable() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/Allocator.cs b/src/ARMeilleure/Common/Allocator.cs new file mode 100644 index 00000000..6905a614 --- /dev/null +++ b/src/ARMeilleure/Common/Allocator.cs @@ -0,0 +1,24 @@ +using System; + +namespace ARMeilleure.Common +{ + unsafe abstract class Allocator : IDisposable + { + public T* Allocate(ulong count = 1) where T : unmanaged + { + return (T*)Allocate(count * (uint)sizeof(T)); + } + + public abstract void* Allocate(ulong size); + + public abstract void Free(void* block); + + protected virtual void Dispose(bool disposing) { } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/ARMeilleure/Common/ArenaAllocator.cs b/src/ARMeilleure/Common/ArenaAllocator.cs new file mode 100644 index 00000000..ce8e3391 --- /dev/null +++ b/src/ARMeilleure/Common/ArenaAllocator.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.Common +{ + unsafe sealed class ArenaAllocator : Allocator + { + private class PageInfo + { + public byte* Pointer; + public byte Unused; + public int UnusedCounter; + } + + private int _lastReset; + private ulong _index; + private int _pageIndex; + private PageInfo _page; + private List _pages; + private readonly ulong _pageSize; + private readonly uint _pageCount; + private readonly List _extras; + + public ArenaAllocator(uint pageSize, uint pageCount) + { + _lastReset = Environment.TickCount; + + // Set _index to pageSize so that the first allocation goes through the slow path. + _index = pageSize; + _pageIndex = -1; + + _page = null; + _pages = new List(); + _pageSize = pageSize; + _pageCount = pageCount; + + _extras = new List(); + } + + public Span AllocateSpan(ulong count) where T : unmanaged + { + return new Span(Allocate(count), (int)count); + } + + public override void* Allocate(ulong size) + { + if (_index + size <= _pageSize) + { + byte* result = _page.Pointer + _index; + + _index += size; + + return result; + } + + return AllocateSlow(size); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void* AllocateSlow(ulong size) + { + if (size > _pageSize) + { + void* extra = NativeAllocator.Instance.Allocate(size); + + _extras.Add((IntPtr)extra); + + return extra; + } + + if (_index + size > _pageSize) + { + _index = 0; + _pageIndex++; + } + + if (_pageIndex < _pages.Count) + { + _page = _pages[_pageIndex]; + _page.Unused = 0; + } + else + { + _page = new PageInfo + { + Pointer = (byte*)NativeAllocator.Instance.Allocate(_pageSize), + }; + + _pages.Add(_page); + } + + byte* result = _page.Pointer + _index; + + _index += size; + + return result; + } + + public override void Free(void* block) { } + + public void Reset() + { + _index = _pageSize; + _pageIndex = -1; + _page = null; + + // Free excess pages that was allocated. + while (_pages.Count > _pageCount) + { + NativeAllocator.Instance.Free(_pages[^1].Pointer); + + _pages.RemoveAt(_pages.Count - 1); + } + + // Free extra blocks that are not page-sized + foreach (IntPtr ptr in _extras) + { + NativeAllocator.Instance.Free((void*)ptr); + } + + _extras.Clear(); + + // Free pooled pages that has not been used in a while. Remove pages at the back first, because we try to + // keep the pages at the front alive, since they're more likely to be hot and in the d-cache. + bool removing = true; + + // If arena is used frequently, keep pages for longer. Otherwise keep pages for a shorter amount of time. + int now = Environment.TickCount; + int count = (now - _lastReset) switch + { + >= 5000 => 0, + >= 2500 => 50, + >= 1000 => 100, + >= 10 => 1500, + _ => 5000, + }; + + for (int i = _pages.Count - 1; i >= 0; i--) + { + PageInfo page = _pages[i]; + + if (page.Unused == 0) + { + page.UnusedCounter = 0; + } + + page.UnusedCounter += page.Unused; + page.Unused = 1; + + // If page not used after `count` resets, remove it. + if (removing && page.UnusedCounter >= count) + { + NativeAllocator.Instance.Free(page.Pointer); + + _pages.RemoveAt(i); + } + else + { + removing = false; + } + } + + _lastReset = now; + } + + protected override void Dispose(bool disposing) + { + if (_pages != null) + { + foreach (PageInfo info in _pages) + { + NativeAllocator.Instance.Free(info.Pointer); + } + + foreach (IntPtr ptr in _extras) + { + NativeAllocator.Instance.Free((void*)ptr); + } + + _pages = null; + } + } + + ~ArenaAllocator() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/BitMap.cs b/src/ARMeilleure/Common/BitMap.cs new file mode 100644 index 00000000..94d47ea5 --- /dev/null +++ b/src/ARMeilleure/Common/BitMap.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.Common +{ + unsafe class BitMap : IEnumerable, IDisposable + { + private const int IntSize = 64; + private const int IntMask = IntSize - 1; + + private int _count; + private long* _masks; + private readonly Allocator _allocator; + + public BitMap(Allocator allocator) + { + _allocator = allocator; + } + + public BitMap(Allocator allocator, int capacity) : this(allocator) + { + EnsureCapacity(capacity); + } + + public bool Set(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + if ((_masks[wordIndex] & wordMask) != 0) + { + return false; + } + + _masks[wordIndex] |= wordMask; + + return true; + } + + public void Clear(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + _masks[wordIndex] &= ~wordMask; + } + + public bool IsSet(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + return (_masks[wordIndex] & (1L << wordBit)) != 0; + } + + public int FindFirstUnset() + { + for (int index = 0; index < _count; index++) + { + long mask = _masks[index]; + + if (mask != -1L) + { + return BitOperations.TrailingZeroCount(~mask) + index * IntSize; + } + } + + return _count * IntSize; + } + + public bool Set(BitMap map) + { + EnsureCapacity(map._count * IntSize); + + bool modified = false; + + for (int index = 0; index < _count; index++) + { + long newValue = _masks[index] | map._masks[index]; + + if (_masks[index] != newValue) + { + _masks[index] = newValue; + + modified = true; + } + } + + return modified; + } + + public bool Clear(BitMap map) + { + EnsureCapacity(map._count * IntSize); + + bool modified = false; + + for (int index = 0; index < _count; index++) + { + long newValue = _masks[index] & ~map._masks[index]; + + if (_masks[index] != newValue) + { + _masks[index] = newValue; + + modified = true; + } + } + + return modified; + } + + private void EnsureCapacity(int size) + { + int count = (size + IntMask) / IntSize; + + if (count > _count) + { + var oldMask = _masks; + var oldSpan = new Span(_masks, _count); + + _masks = _allocator.Allocate((uint)count); + _count = count; + + var newSpan = new Span(_masks, _count); + + oldSpan.CopyTo(newSpan); + newSpan[oldSpan.Length..].Clear(); + + _allocator.Free(oldMask); + } + } + + public void Dispose() + { + if (_masks != null) + { + _allocator.Free(_masks); + + _masks = null; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public struct Enumerator : IEnumerator + { + private long _index; + private long _mask; + private int _bit; + private readonly BitMap _map; + + public readonly int Current => (int)_index * IntSize + _bit; + readonly object IEnumerator.Current => Current; + + public Enumerator(BitMap map) + { + _index = -1; + _mask = 0; + _bit = 0; + _map = map; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + if (_mask != 0) + { + _mask &= ~(1L << _bit); + } + + // Manually hoist these loads, because RyuJIT does not. + long count = (uint)_map._count; + long* masks = _map._masks; + + while (_mask == 0) + { + if (++_index >= count) + { + return false; + } + + _mask = masks[_index]; + } + + _bit = BitOperations.TrailingZeroCount(_mask); + + return true; + } + + public readonly void Reset() { } + + public readonly void Dispose() { } + } + } +} diff --git a/src/ARMeilleure/Common/BitUtils.cs b/src/ARMeilleure/Common/BitUtils.cs new file mode 100644 index 00000000..e7697ff3 --- /dev/null +++ b/src/ARMeilleure/Common/BitUtils.cs @@ -0,0 +1,57 @@ +using System; +using System.Numerics; + +namespace ARMeilleure.Common +{ + static class BitUtils + { + private static ReadOnlySpan HbsNibbleLut => new sbyte[] { -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3 }; + + public static long FillWithOnes(int bits) + { + return bits == 64 ? -1L : (1L << bits) - 1; + } + + public static int HighestBitSet(int value) + { + return 31 - BitOperations.LeadingZeroCount((uint)value); + } + + public static int HighestBitSetNibble(int value) + { + return HbsNibbleLut[value]; + } + + public static long Replicate(long bits, int size) + { + long output = 0; + + for (int bit = 0; bit < 64; bit += size) + { + output |= bits << bit; + } + + return output; + } + + public static int RotateRight(int bits, int shift, int size) + { + return (int)RotateRight((uint)bits, shift, size); + } + + public static uint RotateRight(uint bits, int shift, int size) + { + return (bits >> shift) | (bits << (size - shift)); + } + + public static long RotateRight(long bits, int shift, int size) + { + return (long)RotateRight((ulong)bits, shift, size); + } + + public static ulong RotateRight(ulong bits, int shift, int size) + { + return (bits >> shift) | (bits << (size - shift)); + } + } +} diff --git a/src/ARMeilleure/Common/Counter.cs b/src/ARMeilleure/Common/Counter.cs new file mode 100644 index 00000000..6db9561c --- /dev/null +++ b/src/ARMeilleure/Common/Counter.cs @@ -0,0 +1,98 @@ +using System; + +namespace ARMeilleure.Common +{ + /// + /// Represents a numeric counter which can be used for instrumentation of compiled code. + /// + /// Type of the counter + class Counter : IDisposable where T : unmanaged + { + private bool _disposed; + /// + /// Index in the + /// + private readonly int _index; + private readonly EntryTable _countTable; + + /// + /// Initializes a new instance of the class from the specified + /// instance and index. + /// + /// instance + /// is + /// is unsupported + public Counter(EntryTable countTable) + { + if (typeof(T) != typeof(byte) && typeof(T) != typeof(sbyte) && + typeof(T) != typeof(short) && typeof(T) != typeof(ushort) && + typeof(T) != typeof(int) && typeof(T) != typeof(uint) && + typeof(T) != typeof(long) && typeof(T) != typeof(ulong) && + typeof(T) != typeof(nint) && typeof(T) != typeof(nuint) && + typeof(T) != typeof(float) && typeof(T) != typeof(double)) + { + throw new ArgumentException("Counter does not support the specified type."); + } + + _countTable = countTable ?? throw new ArgumentNullException(nameof(countTable)); + _index = countTable.Allocate(); + } + + /// + /// Gets a reference to the value of the counter. + /// + /// instance was disposed + /// + /// This can refer to freed memory if the owning is disposed. + /// + public ref T Value + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return ref _countTable.GetValue(_index); + } + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resources + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + try + { + // The index into the EntryTable is essentially an unmanaged resource since we allocate and free the + // resource ourselves. + _countTable.Free(_index); + } + catch (ObjectDisposedException) + { + // Can happen because _countTable may be disposed before the Counter instance. + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~Counter() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/EntryTable.cs b/src/ARMeilleure/Common/EntryTable.cs new file mode 100644 index 00000000..625e3f73 --- /dev/null +++ b/src/ARMeilleure/Common/EntryTable.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace ARMeilleure.Common +{ + /// + /// Represents an expandable table of the type , whose entries will remain at the same + /// address through out the table's lifetime. + /// + /// Type of the entry in the table + class EntryTable : IDisposable where TEntry : unmanaged + { + private bool _disposed; + private int _freeHint; + private readonly int _pageCapacity; // Number of entries per page. + private readonly int _pageLogCapacity; + private readonly Dictionary _pages; + private readonly BitMap _allocated; + + /// + /// Initializes a new instance of the class with the desired page size in + /// bytes. + /// + /// Desired page size in bytes + /// is less than 0 + /// 's size is zero + /// + /// The actual page size may be smaller or larger depending on the size of . + /// + public unsafe EntryTable(int pageSize = 4096) + { + if (pageSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(pageSize), "Page size cannot be negative."); + } + + if (sizeof(TEntry) == 0) + { + throw new ArgumentException("Size of TEntry cannot be zero."); + } + + _allocated = new BitMap(NativeAllocator.Instance); + _pages = new Dictionary(); + _pageLogCapacity = BitOperations.Log2((uint)(pageSize / sizeof(TEntry))); + _pageCapacity = 1 << _pageLogCapacity; + } + + /// + /// Allocates an entry in the . + /// + /// Index of entry allocated in the table + /// instance was disposed + public int Allocate() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_allocated) + { + if (_allocated.IsSet(_freeHint)) + { + _freeHint = _allocated.FindFirstUnset(); + } + + int index = _freeHint++; + var page = GetPage(index); + + _allocated.Set(index); + + GetValue(page, index) = default; + + return index; + } + } + + /// + /// Frees the entry at the specified . + /// + /// Index of entry to free + /// instance was disposed + public void Free(int index) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_allocated) + { + if (_allocated.IsSet(index)) + { + _allocated.Clear(index); + + _freeHint = index; + } + } + } + + /// + /// Gets a reference to the entry at the specified allocated . + /// + /// Index of the entry + /// Reference to the entry at the specified + /// instance was disposed + /// Entry at is not allocated + public ref TEntry GetValue(int index) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_allocated) + { + if (!_allocated.IsSet(index)) + { + throw new ArgumentException("Entry at the specified index was not allocated", nameof(index)); + } + + var page = GetPage(index); + + return ref GetValue(page, index); + } + } + + /// + /// Gets a reference to the entry at using the specified from the specified + /// . + /// + /// Page to use + /// Index to use + /// Reference to the entry + private ref TEntry GetValue(Span page, int index) + { + return ref page[index & (_pageCapacity - 1)]; + } + + /// + /// Gets the page for the specified . + /// + /// Index to use + /// Page for the specified + private unsafe Span GetPage(int index) + { + var pageIndex = (int)((uint)(index & ~(_pageCapacity - 1)) >> _pageLogCapacity); + + if (!_pages.TryGetValue(pageIndex, out IntPtr page)) + { + page = (IntPtr)NativeAllocator.Instance.Allocate((uint)sizeof(TEntry) * (uint)_pageCapacity); + + _pages.Add(pageIndex, page); + } + + return new Span((void*)page, _pageCapacity); + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the + /// instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resouces + protected unsafe virtual void Dispose(bool disposing) + { + if (!_disposed) + { + _allocated.Dispose(); + + foreach (var page in _pages.Values) + { + NativeAllocator.Instance.Free((void*)page); + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~EntryTable() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/EnumUtils.cs b/src/ARMeilleure/Common/EnumUtils.cs new file mode 100644 index 00000000..2a4aa645 --- /dev/null +++ b/src/ARMeilleure/Common/EnumUtils.cs @@ -0,0 +1,12 @@ +using System; + +namespace ARMeilleure.Common +{ + static class EnumUtils + { + public static int GetCount(Type enumType) + { + return Enum.GetNames(enumType).Length; + } + } +} diff --git a/src/ARMeilleure/Common/NativeAllocator.cs b/src/ARMeilleure/Common/NativeAllocator.cs new file mode 100644 index 00000000..93c48add --- /dev/null +++ b/src/ARMeilleure/Common/NativeAllocator.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Common +{ + unsafe sealed class NativeAllocator : Allocator + { + public static NativeAllocator Instance { get; } = new(); + + public override void* Allocate(ulong size) + { + void* result = (void*)Marshal.AllocHGlobal((IntPtr)size); + + if (result == null) + { + throw new OutOfMemoryException(); + } + + return result; + } + + public override void Free(void* block) + { + Marshal.FreeHGlobal((IntPtr)block); + } + } +} diff --git a/src/ARMeilleure/Decoders/Block.cs b/src/ARMeilleure/Decoders/Block.cs new file mode 100644 index 00000000..bb88170d --- /dev/null +++ b/src/ARMeilleure/Decoders/Block.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; + +namespace ARMeilleure.Decoders +{ + class Block + { + public ulong Address { get; set; } + public ulong EndAddress { get; set; } + + public Block Next { get; set; } + public Block Branch { get; set; } + + public bool Exit { get; set; } + + public List OpCodes { get; } + + public Block() + { + OpCodes = new List(); + } + + public Block(ulong address) : this() + { + Address = address; + } + + public void Split(Block rightBlock) + { + int splitIndex = BinarySearch(OpCodes, rightBlock.Address); + + if (OpCodes[splitIndex].Address < rightBlock.Address) + { + splitIndex++; + } + + int splitCount = OpCodes.Count - splitIndex; + + if (splitCount <= 0) + { + throw new ArgumentException("Can't split at right block address."); + } + + rightBlock.EndAddress = EndAddress; + + rightBlock.Next = Next; + rightBlock.Branch = Branch; + + rightBlock.OpCodes.AddRange(OpCodes.GetRange(splitIndex, splitCount)); + + EndAddress = rightBlock.Address; + + Next = rightBlock; + Branch = null; + + OpCodes.RemoveRange(splitIndex, splitCount); + } + + private static int BinarySearch(List opCodes, ulong address) + { + int left = 0; + int middle = 0; + int right = opCodes.Count - 1; + + while (left <= right) + { + int size = right - left; + + middle = left + (size >> 1); + + OpCode opCode = opCodes[middle]; + + if (address == (ulong)opCode.Address) + { + break; + } + + if (address < (ulong)opCode.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return middle; + } + + public OpCode GetLastOp() + { + if (OpCodes.Count > 0) + { + return OpCodes[^1]; + } + + return null; + } + } +} diff --git a/src/ARMeilleure/Decoders/Condition.cs b/src/ARMeilleure/Decoders/Condition.cs new file mode 100644 index 00000000..961825a1 --- /dev/null +++ b/src/ARMeilleure/Decoders/Condition.cs @@ -0,0 +1,32 @@ +namespace ARMeilleure.Decoders +{ + enum Condition + { + Eq = 0, + Ne = 1, + GeUn = 2, + LtUn = 3, + Mi = 4, + Pl = 5, + Vs = 6, + Vc = 7, + GtUn = 8, + LeUn = 9, + Ge = 10, + Lt = 11, + Gt = 12, + Le = 13, + Al = 14, + Nv = 15, + } + + static class ConditionExtensions + { + public static Condition Invert(this Condition cond) + { + // Bit 0 of all conditions is basically a negation bit, so + // inverting this bit has the effect of inverting the condition. + return (Condition)((int)cond ^ 1); + } + } +} diff --git a/src/ARMeilleure/Decoders/DataOp.cs b/src/ARMeilleure/Decoders/DataOp.cs new file mode 100644 index 00000000..f99fd5e7 --- /dev/null +++ b/src/ARMeilleure/Decoders/DataOp.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + enum DataOp + { + Adr = 0, + Arithmetic = 1, + Logical = 2, + BitField = 3, + } +} diff --git a/src/ARMeilleure/Decoders/Decoder.cs b/src/ARMeilleure/Decoders/Decoder.cs new file mode 100644 index 00000000..66d28692 --- /dev/null +++ b/src/ARMeilleure/Decoders/Decoder.cs @@ -0,0 +1,393 @@ +using ARMeilleure.Decoders.Optimizations; +using ARMeilleure.Instructions; +using ARMeilleure.Memory; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ARMeilleure.Decoders +{ + static class Decoder + { + // We define a limit on the number of instructions that a function may have, + // this prevents functions being potentially too large, which would + // take too long to compile and use too much memory. + private const int MaxInstsPerFunction = 2500; + + // For lower code quality translation, we set a lower limit since we're blocking execution. + private const int MaxInstsPerFunctionLowCq = 500; + + public static Block[] Decode(IMemoryManager memory, ulong address, ExecutionMode mode, bool highCq, DecoderMode dMode) + { + List blocks = new(); + + Queue workQueue = new(); + + Dictionary visited = new(); + + Debug.Assert(MaxInstsPerFunctionLowCq <= MaxInstsPerFunction); + + int opsCount = 0; + + int instructionLimit = highCq ? MaxInstsPerFunction : MaxInstsPerFunctionLowCq; + + Block GetBlock(ulong blkAddress) + { + if (!visited.TryGetValue(blkAddress, out Block block)) + { + block = new Block(blkAddress); + + if ((dMode != DecoderMode.MultipleBlocks && visited.Count >= 1) || + opsCount > instructionLimit || + (visited.Count > 0 && !memory.IsMapped(blkAddress))) + { + block.Exit = true; + block.EndAddress = blkAddress; + } + + workQueue.Enqueue(block); + + visited.Add(blkAddress, block); + } + + return block; + } + + GetBlock(address); + + while (workQueue.TryDequeue(out Block currBlock)) + { + // Check if the current block is inside another block. + if (BinarySearch(blocks, currBlock.Address, out int nBlkIndex)) + { + Block nBlock = blocks[nBlkIndex]; + + if (nBlock.Address == currBlock.Address) + { + throw new InvalidOperationException("Found duplicate block address on the list."); + } + + currBlock.Exit = false; + + nBlock.Split(currBlock); + + blocks.Insert(nBlkIndex + 1, currBlock); + + continue; + } + + if (!currBlock.Exit) + { + // If we have a block after the current one, set the limit address. + ulong limitAddress = ulong.MaxValue; + + if (nBlkIndex != blocks.Count) + { + Block nBlock = blocks[nBlkIndex]; + + int nextIndex = nBlkIndex + 1; + + if (nBlock.Address < currBlock.Address && nextIndex < blocks.Count) + { + limitAddress = blocks[nextIndex].Address; + } + else if (nBlock.Address > currBlock.Address) + { + limitAddress = blocks[nBlkIndex].Address; + } + } + + if (dMode == DecoderMode.SingleInstruction) + { + // Only read at most one instruction + limitAddress = currBlock.Address + 1; + } + + FillBlock(memory, mode, currBlock, limitAddress); + + opsCount += currBlock.OpCodes.Count; + + if (currBlock.OpCodes.Count != 0) + { + // Set child blocks. "Branch" is the block the branch instruction + // points to (when taken), "Next" is the block at the next address, + // executed when the branch is not taken. For Unconditional Branches + // (except BL/BLR that are sub calls) or end of executable, Next is null. + OpCode lastOp = currBlock.GetLastOp(); + + bool isCall = IsCall(lastOp); + + if (lastOp is IOpCodeBImm op && !isCall) + { + currBlock.Branch = GetBlock((ulong)op.Immediate); + } + + if (isCall || !(IsUnconditionalBranch(lastOp) || IsTrap(lastOp))) + { + currBlock.Next = GetBlock(currBlock.EndAddress); + } + } + } + + // Insert the new block on the list (sorted by address). + if (blocks.Count != 0) + { + Block nBlock = blocks[nBlkIndex]; + + blocks.Insert(nBlkIndex + (nBlock.Address < currBlock.Address ? 1 : 0), currBlock); + } + else + { + blocks.Add(currBlock); + } + } + + if (blocks.Count == 1 && blocks[0].OpCodes.Count == 0) + { + Debug.Assert(blocks[0].Exit); + Debug.Assert(blocks[0].Address == blocks[0].EndAddress); + + throw new InvalidOperationException($"Decoded a single empty exit block. Entry point = 0x{address:X}."); + } + + if (dMode == DecoderMode.MultipleBlocks) + { + return TailCallRemover.RunPass(address, blocks); + } + else + { + return blocks.ToArray(); + } + } + + public static bool BinarySearch(List blocks, ulong address, out int index) + { + index = 0; + + int left = 0; + int right = blocks.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Block block = blocks[middle]; + + index = middle; + + if (address >= block.Address && address < block.EndAddress) + { + return true; + } + + if (address < block.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return false; + } + + private static void FillBlock( + IMemoryManager memory, + ExecutionMode mode, + Block block, + ulong limitAddress) + { + ulong address = block.Address; + int itBlockSize = 0; + + OpCode opCode; + + do + { + if (address >= limitAddress && itBlockSize == 0) + { + break; + } + + opCode = DecodeOpCode(memory, address, mode); + + block.OpCodes.Add(opCode); + + address += (ulong)opCode.OpCodeSizeInBytes; + + if (opCode is OpCodeT16IfThen it) + { + itBlockSize = it.IfThenBlockSize; + } + else if (itBlockSize > 0) + { + itBlockSize--; + } + } + while (!(IsBranch(opCode) || IsException(opCode))); + + block.EndAddress = address; + } + + private static bool IsBranch(OpCode opCode) + { + return opCode is OpCodeBImm || + opCode is OpCodeBReg || IsAarch32Branch(opCode); + } + + private static bool IsUnconditionalBranch(OpCode opCode) + { + return opCode is OpCodeBImmAl || + opCode is OpCodeBReg || IsAarch32UnconditionalBranch(opCode); + } + + private static bool IsAarch32UnconditionalBranch(OpCode opCode) + { + if (opCode is not OpCode32 op) + { + return false; + } + + // Compare and branch instructions are always conditional. + if (opCode.Instruction.Name == InstName.Cbz || + opCode.Instruction.Name == InstName.Cbnz) + { + return false; + } + + // Note: On ARM32, most instructions have conditional execution, + // so there's no "Always" (unconditional) branch like on ARM64. + // We need to check if the condition is "Always" instead. + return IsAarch32Branch(op) && op.Cond >= Condition.Al; + } + + private static bool IsAarch32Branch(OpCode opCode) + { + // Note: On ARM32, most ALU operations can write to R15 (PC), + // so we must consider such operations as a branch in potential aswell. + if (opCode is IOpCode32Alu opAlu && opAlu.Rd == RegisterAlias.Aarch32Pc) + { + if (opCode is OpCodeT32) + { + return opCode.Instruction.Name != InstName.Tst && opCode.Instruction.Name != InstName.Teq && + opCode.Instruction.Name != InstName.Cmp && opCode.Instruction.Name != InstName.Cmn; + } + return true; + } + + // Same thing for memory operations. We have the cases where PC is a target + // register (Rt == 15 or (mask & (1 << 15)) != 0), and cases where there is + // a write back to PC (wback == true && Rn == 15), however the later may + // be "undefined" depending on the CPU, so compilers should not produce that. + if (opCode is IOpCode32Mem || opCode is IOpCode32MemMult) + { + int rt, rn; + + bool wBack, isLoad; + + if (opCode is IOpCode32Mem opMem) + { + rt = opMem.Rt; + rn = opMem.Rn; + wBack = opMem.WBack; + isLoad = opMem.IsLoad; + + // For the dual load, we also need to take into account the + // case were Rt2 == 15 (PC). + if (rt == 14 && opMem.Instruction.Name == InstName.Ldrd) + { + rt = RegisterAlias.Aarch32Pc; + } + } + else if (opCode is IOpCode32MemMult opMemMult) + { + const int PCMask = 1 << RegisterAlias.Aarch32Pc; + + rt = (opMemMult.RegisterMask & PCMask) != 0 ? RegisterAlias.Aarch32Pc : 0; + rn = opMemMult.Rn; + wBack = opMemMult.PostOffset != 0; + isLoad = opMemMult.IsLoad; + } + else + { + throw new NotImplementedException($"The type \"{opCode.GetType().Name}\" is not implemented on the decoder."); + } + + if ((rt == RegisterAlias.Aarch32Pc && isLoad) || + (rn == RegisterAlias.Aarch32Pc && wBack)) + { + return true; + } + } + + // Explicit branch instructions. + return opCode is IOpCode32BImm || + opCode is IOpCode32BReg; + } + + private static bool IsCall(OpCode opCode) + { + return opCode.Instruction.Name == InstName.Bl || + opCode.Instruction.Name == InstName.Blr || + opCode.Instruction.Name == InstName.Blx; + } + + private static bool IsException(OpCode opCode) + { + return IsTrap(opCode) || opCode.Instruction.Name == InstName.Svc; + } + + private static bool IsTrap(OpCode opCode) + { + return opCode.Instruction.Name == InstName.Brk || + opCode.Instruction.Name == InstName.Trap || + opCode.Instruction.Name == InstName.Und; + } + + public static OpCode DecodeOpCode(IMemoryManager memory, ulong address, ExecutionMode mode) + { + int opCode = memory.Read(address); + + InstDescriptor inst; + + OpCodeTable.MakeOp makeOp; + + if (mode == ExecutionMode.Aarch64) + { + (inst, makeOp) = OpCodeTable.GetInstA64(opCode); + } + else + { + if (mode == ExecutionMode.Aarch32Arm) + { + (inst, makeOp) = OpCodeTable.GetInstA32(opCode); + } + else /* if (mode == ExecutionMode.Aarch32Thumb) */ + { + (inst, makeOp) = OpCodeTable.GetInstT32(opCode); + } + } + + if (makeOp != null) + { + return makeOp(inst, address, opCode); + } + else + { + if (mode == ExecutionMode.Aarch32Thumb) + { + return new OpCodeT16(inst, address, opCode); + } + else + { + return new OpCode(inst, address, opCode); + } + } + } + } +} diff --git a/src/ARMeilleure/Decoders/DecoderHelper.cs b/src/ARMeilleure/Decoders/DecoderHelper.cs new file mode 100644 index 00000000..35e57395 --- /dev/null +++ b/src/ARMeilleure/Decoders/DecoderHelper.cs @@ -0,0 +1,167 @@ +using ARMeilleure.Common; + +namespace ARMeilleure.Decoders +{ + static class DecoderHelper + { + static DecoderHelper() + { + Imm8ToFP32Table = BuildImm8ToFP32Table(); + Imm8ToFP64Table = BuildImm8ToFP64Table(); + } + + public static readonly uint[] Imm8ToFP32Table; + public static readonly ulong[] Imm8ToFP64Table; + + private static uint[] BuildImm8ToFP32Table() + { + uint[] tbl = new uint[256]; + + for (int idx = 0; idx < tbl.Length; idx++) + { + tbl[idx] = ExpandImm8ToFP32((uint)idx); + } + + return tbl; + } + + private static ulong[] BuildImm8ToFP64Table() + { + ulong[] tbl = new ulong[256]; + + for (int idx = 0; idx < tbl.Length; idx++) + { + tbl[idx] = ExpandImm8ToFP64((ulong)idx); + } + + return tbl; + } + + // abcdefgh -> aBbbbbbc defgh000 00000000 00000000 (B = ~b) + private static uint ExpandImm8ToFP32(uint imm) + { + static uint MoveBit(uint bits, int from, int to) + { + return ((bits >> from) & 1U) << to; + } + + return MoveBit(imm, 7, 31) | MoveBit(~imm, 6, 30) | + MoveBit(imm, 6, 29) | MoveBit(imm, 6, 28) | + MoveBit(imm, 6, 27) | MoveBit(imm, 6, 26) | + MoveBit(imm, 6, 25) | MoveBit(imm, 5, 24) | + MoveBit(imm, 4, 23) | MoveBit(imm, 3, 22) | + MoveBit(imm, 2, 21) | MoveBit(imm, 1, 20) | + MoveBit(imm, 0, 19); + } + + // abcdefgh -> aBbbbbbb bbcdefgh 00000000 00000000 00000000 00000000 00000000 00000000 (B = ~b) + private static ulong ExpandImm8ToFP64(ulong imm) + { + static ulong MoveBit(ulong bits, int from, int to) + { + return ((bits >> from) & 1UL) << to; + } + + return MoveBit(imm, 7, 63) | MoveBit(~imm, 6, 62) | + MoveBit(imm, 6, 61) | MoveBit(imm, 6, 60) | + MoveBit(imm, 6, 59) | MoveBit(imm, 6, 58) | + MoveBit(imm, 6, 57) | MoveBit(imm, 6, 56) | + MoveBit(imm, 6, 55) | MoveBit(imm, 6, 54) | + MoveBit(imm, 5, 53) | MoveBit(imm, 4, 52) | + MoveBit(imm, 3, 51) | MoveBit(imm, 2, 50) | + MoveBit(imm, 1, 49) | MoveBit(imm, 0, 48); + } + + public struct BitMask + { + public long WMask; + public long TMask; + public int Pos; + public int Shift; + public bool IsUndefined; + + public static BitMask Invalid => new() { IsUndefined = true }; + } + + public static BitMask DecodeBitMask(int opCode, bool immediate) + { + int immS = (opCode >> 10) & 0x3f; + int immR = (opCode >> 16) & 0x3f; + + int n = (opCode >> 22) & 1; + int sf = (opCode >> 31) & 1; + + int length = BitUtils.HighestBitSet((~immS & 0x3f) | (n << 6)); + + if (length < 1 || (sf == 0 && n != 0)) + { + return BitMask.Invalid; + } + + int size = 1 << length; + + int levels = size - 1; + + int s = immS & levels; + int r = immR & levels; + + if (immediate && s == levels) + { + return BitMask.Invalid; + } + + long wMask = BitUtils.FillWithOnes(s + 1); + long tMask = BitUtils.FillWithOnes(((s - r) & levels) + 1); + + if (r > 0) + { + wMask = BitUtils.RotateRight(wMask, r, size); + wMask &= BitUtils.FillWithOnes(size); + } + + return new BitMask() + { + WMask = BitUtils.Replicate(wMask, size), + TMask = BitUtils.Replicate(tMask, size), + + Pos = immS, + Shift = immR, + }; + } + + public static long DecodeImm24_2(int opCode) + { + return ((long)opCode << 40) >> 38; + } + + public static long DecodeImm26_2(int opCode) + { + return ((long)opCode << 38) >> 36; + } + + public static long DecodeImmS19_2(int opCode) + { + return (((long)opCode << 40) >> 43) & ~3; + } + + public static long DecodeImmS14_2(int opCode) + { + return (((long)opCode << 45) >> 48) & ~3; + } + + public static bool VectorArgumentsInvalid(bool q, params int[] args) + { + if (q) + { + for (int i = 0; i < args.Length; i++) + { + if ((args[i] & 1) == 1) + { + return true; + } + } + } + return false; + } + } +} diff --git a/src/ARMeilleure/Decoders/DecoderMode.cs b/src/ARMeilleure/Decoders/DecoderMode.cs new file mode 100644 index 00000000..708d5c8f --- /dev/null +++ b/src/ARMeilleure/Decoders/DecoderMode.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + enum DecoderMode + { + MultipleBlocks, + SingleBlock, + SingleInstruction, + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode.cs b/src/ARMeilleure/Decoders/IOpCode.cs new file mode 100644 index 00000000..9d5e3bf7 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode.cs @@ -0,0 +1,17 @@ +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.Decoders +{ + interface IOpCode + { + ulong Address { get; } + + InstDescriptor Instruction { get; } + + RegisterSize RegisterSize { get; } + + int GetBitsCount(); + + OperandType GetOperandType(); + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32.cs b/src/ARMeilleure/Decoders/IOpCode32.cs new file mode 100644 index 00000000..578925de --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32 : IOpCode + { + Condition Cond { get; } + + uint GetPc(); + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32Adr.cs b/src/ARMeilleure/Decoders/IOpCode32Adr.cs new file mode 100644 index 00000000..40a4f526 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Adr.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Adr + { + int Rd { get; } + + int Immediate { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32Alu.cs b/src/ARMeilleure/Decoders/IOpCode32Alu.cs new file mode 100644 index 00000000..a85ef44a --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Alu.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Alu : IOpCode32, IOpCode32HasSetFlags + { + int Rd { get; } + int Rn { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluBf.cs b/src/ARMeilleure/Decoders/IOpCode32AluBf.cs new file mode 100644 index 00000000..d1fe5903 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluBf.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluBf + { + int Rd { get; } + int Rn { get; } + + int Msb { get; } + int Lsb { get; } + + int SourceMask => (int)(0xFFFFFFFF >> (31 - Msb)); + int DestMask => SourceMask & (int)(0xFFFFFFFF << Lsb); + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluImm.cs b/src/ARMeilleure/Decoders/IOpCode32AluImm.cs new file mode 100644 index 00000000..b8999018 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluImm.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluImm : IOpCode32Alu + { + int Immediate { get; } + + bool IsRotated { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluImm16.cs b/src/ARMeilleure/Decoders/IOpCode32AluImm16.cs new file mode 100644 index 00000000..dd42a70b --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluImm16.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluImm16 : IOpCode32Alu + { + int Immediate { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluMla.cs b/src/ARMeilleure/Decoders/IOpCode32AluMla.cs new file mode 100644 index 00000000..79b16425 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluMla.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluMla : IOpCode32AluReg + { + int Ra { get; } + + bool NHigh { get; } + bool MHigh { get; } + bool R { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluReg.cs b/src/ARMeilleure/Decoders/IOpCode32AluReg.cs new file mode 100644 index 00000000..1a35e664 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluReg.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluReg : IOpCode32Alu + { + int Rm { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluRsImm.cs b/src/ARMeilleure/Decoders/IOpCode32AluRsImm.cs new file mode 100644 index 00000000..37a2c100 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluRsImm.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluRsImm : IOpCode32Alu + { + int Rm { get; } + int Immediate { get; } + + ShiftType ShiftType { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluRsReg.cs b/src/ARMeilleure/Decoders/IOpCode32AluRsReg.cs new file mode 100644 index 00000000..ed9859fc --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluRsReg.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluRsReg : IOpCode32Alu + { + int Rm { get; } + int Rs { get; } + + ShiftType ShiftType { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluUmull.cs b/src/ARMeilleure/Decoders/IOpCode32AluUmull.cs new file mode 100644 index 00000000..79d2bb9b --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluUmull.cs @@ -0,0 +1,13 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluUmull : IOpCode32, IOpCode32HasSetFlags + { + int RdLo { get; } + int RdHi { get; } + int Rn { get; } + int Rm { get; } + + bool NHigh { get; } + bool MHigh { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluUx.cs b/src/ARMeilleure/Decoders/IOpCode32AluUx.cs new file mode 100644 index 00000000..d390f6b8 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluUx.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluUx : IOpCode32AluReg + { + int RotateBits { get; } + bool Add { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32BImm.cs b/src/ARMeilleure/Decoders/IOpCode32BImm.cs new file mode 100644 index 00000000..8d22d5c4 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32BImm.cs @@ -0,0 +1,4 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32BImm : IOpCode32, IOpCodeBImm { } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32BReg.cs b/src/ARMeilleure/Decoders/IOpCode32BReg.cs new file mode 100644 index 00000000..9badc985 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32BReg.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32BReg : IOpCode32 + { + int Rm { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32Exception.cs b/src/ARMeilleure/Decoders/IOpCode32Exception.cs new file mode 100644 index 00000000..c38af907 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Exception.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Exception + { + int Id { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32HasSetFlags.cs b/src/ARMeilleure/Decoders/IOpCode32HasSetFlags.cs new file mode 100644 index 00000000..fd9337d9 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32HasSetFlags.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32HasSetFlags + { + bool? SetFlags { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32Mem.cs b/src/ARMeilleure/Decoders/IOpCode32Mem.cs new file mode 100644 index 00000000..a34bc0e2 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Mem.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Mem : IOpCode32 + { + int Rt { get; } + int Rt2 => Rt | 1; + int Rn { get; } + + bool WBack { get; } + bool IsLoad { get; } + bool Index { get; } + bool Add { get; } + + int Immediate { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32MemEx.cs b/src/ARMeilleure/Decoders/IOpCode32MemEx.cs new file mode 100644 index 00000000..5f6b9321 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32MemEx.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32MemEx : IOpCode32Mem + { + int Rd { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32MemMult.cs b/src/ARMeilleure/Decoders/IOpCode32MemMult.cs new file mode 100644 index 00000000..0c5e48f2 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32MemMult.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32MemMult : IOpCode32 + { + int Rn { get; } + + int RegisterMask { get; } + + int PostOffset { get; } + + bool IsLoad { get; } + + int Offset { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32MemReg.cs b/src/ARMeilleure/Decoders/IOpCode32MemReg.cs new file mode 100644 index 00000000..6a63f7f6 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32MemReg.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32MemReg : IOpCode32Mem + { + int Rm { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32MemRsImm.cs b/src/ARMeilleure/Decoders/IOpCode32MemRsImm.cs new file mode 100644 index 00000000..3407e98a --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32MemRsImm.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32MemRsImm : IOpCode32Mem + { + int Rm { get; } + ShiftType ShiftType { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32Simd.cs b/src/ARMeilleure/Decoders/IOpCode32Simd.cs new file mode 100644 index 00000000..0dccd267 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Simd.cs @@ -0,0 +1,4 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Simd : IOpCode32, IOpCodeSimd { } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32SimdImm.cs b/src/ARMeilleure/Decoders/IOpCode32SimdImm.cs new file mode 100644 index 00000000..a8e64609 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32SimdImm.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32SimdImm : IOpCode32Simd + { + int Vd { get; } + long Immediate { get; } + int Elems { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeAlu.cs b/src/ARMeilleure/Decoders/IOpCodeAlu.cs new file mode 100644 index 00000000..059769ba --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeAlu.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeAlu : IOpCode + { + int Rd { get; } + int Rn { get; } + + DataOp DataOp { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeAluImm.cs b/src/ARMeilleure/Decoders/IOpCodeAluImm.cs new file mode 100644 index 00000000..40a69cc9 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeAluImm.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeAluImm : IOpCodeAlu + { + long Immediate { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeAluRs.cs b/src/ARMeilleure/Decoders/IOpCodeAluRs.cs new file mode 100644 index 00000000..eec95698 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeAluRs.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeAluRs : IOpCodeAlu + { + int Shift { get; } + int Rm { get; } + + ShiftType ShiftType { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeAluRx.cs b/src/ARMeilleure/Decoders/IOpCodeAluRx.cs new file mode 100644 index 00000000..e5a8559d --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeAluRx.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeAluRx : IOpCodeAlu + { + int Shift { get; } + int Rm { get; } + + IntType IntType { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeBImm.cs b/src/ARMeilleure/Decoders/IOpCodeBImm.cs new file mode 100644 index 00000000..9ce7512a --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeBImm.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeBImm : IOpCode + { + long Immediate { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeCond.cs b/src/ARMeilleure/Decoders/IOpCodeCond.cs new file mode 100644 index 00000000..6604f19a --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeCond.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeCond : IOpCode + { + Condition Cond { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeLit.cs b/src/ARMeilleure/Decoders/IOpCodeLit.cs new file mode 100644 index 00000000..434e4da8 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeLit.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeLit : IOpCode + { + int Rt { get; } + long Immediate { get; } + int Size { get; } + bool Signed { get; } + bool Prefetch { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeSimd.cs b/src/ARMeilleure/Decoders/IOpCodeSimd.cs new file mode 100644 index 00000000..598d9d7f --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeSimd.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeSimd : IOpCode + { + int Size { get; } + } +} diff --git a/src/ARMeilleure/Decoders/InstDescriptor.cs b/src/ARMeilleure/Decoders/InstDescriptor.cs new file mode 100644 index 00000000..c35c754a --- /dev/null +++ b/src/ARMeilleure/Decoders/InstDescriptor.cs @@ -0,0 +1,18 @@ +using ARMeilleure.Instructions; + +namespace ARMeilleure.Decoders +{ + readonly struct InstDescriptor + { + public static InstDescriptor Undefined => new(InstName.Und, InstEmit.Und); + + public InstName Name { get; } + public InstEmitter Emitter { get; } + + public InstDescriptor(InstName name, InstEmitter emitter) + { + Name = name; + Emitter = emitter; + } + } +} diff --git a/src/ARMeilleure/Decoders/InstEmitter.cs b/src/ARMeilleure/Decoders/InstEmitter.cs new file mode 100644 index 00000000..43bfcdca --- /dev/null +++ b/src/ARMeilleure/Decoders/InstEmitter.cs @@ -0,0 +1,6 @@ +using ARMeilleure.Translation; + +namespace ARMeilleure.Decoders +{ + delegate void InstEmitter(ArmEmitterContext context); +} diff --git a/src/ARMeilleure/Decoders/IntType.cs b/src/ARMeilleure/Decoders/IntType.cs new file mode 100644 index 00000000..937a569a --- /dev/null +++ b/src/ARMeilleure/Decoders/IntType.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + enum IntType + { + UInt8 = 0, + UInt16 = 1, + UInt32 = 2, + UInt64 = 3, + Int8 = 4, + Int16 = 5, + Int32 = 6, + Int64 = 7, + } +} diff --git a/src/ARMeilleure/Decoders/OpCode.cs b/src/ARMeilleure/Decoders/OpCode.cs new file mode 100644 index 00000000..c8123308 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode.cs @@ -0,0 +1,48 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +namespace ARMeilleure.Decoders +{ + class OpCode : IOpCode + { + public ulong Address { get; } + public int RawOpCode { get; } + + public int OpCodeSizeInBytes { get; protected set; } = 4; + + public InstDescriptor Instruction { get; protected set; } + + public RegisterSize RegisterSize { get; protected set; } + + public static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new(inst, address, opCode); + + public OpCode(InstDescriptor inst, ulong address, int opCode) + { + Instruction = inst; + Address = address; + RawOpCode = opCode; + + RegisterSize = RegisterSize.Int64; + } + + public int GetPairsCount() => GetBitsCount() / 16; + public int GetBytesCount() => GetBitsCount() / 8; + + public int GetBitsCount() + { + return RegisterSize switch + { + RegisterSize.Int32 => 32, + RegisterSize.Int64 => 64, + RegisterSize.Simd64 => 64, + RegisterSize.Simd128 => 128, + _ => throw new InvalidOperationException(), + }; + } + + public OperandType GetOperandType() + { + return RegisterSize == RegisterSize.Int32 ? OperandType.I32 : OperandType.I64; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32.cs b/src/ARMeilleure/Decoders/OpCode32.cs new file mode 100644 index 00000000..a2be01e9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32.cs @@ -0,0 +1,34 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32 : OpCode + { + public Condition Cond { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32(inst, address, opCode); + + public OpCode32(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + RegisterSize = RegisterSize.Int32; + + Cond = (Condition)((uint)opCode >> 28); + } + + public bool IsThumb { get; protected init; } = false; + + public uint GetPc() + { + // Due to backwards compatibility and legacy behavior of ARMv4 CPUs pipeline, + // the PC actually points 2 instructions ahead. + if (IsThumb) + { + // PC is ahead by 4 in thumb mode whether or not the current instruction + // is 16 or 32 bit. + return (uint)Address + 4u; + } + else + { + return (uint)Address + 8u; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Alu.cs b/src/ARMeilleure/Decoders/OpCode32Alu.cs new file mode 100644 index 00000000..8634f5ce --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Alu.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Alu : OpCode32, IOpCode32Alu + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Alu(inst, address, opCode); + + public OpCode32Alu(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + SetFlags = ((opCode >> 20) & 1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluBf.cs b/src/ARMeilleure/Decoders/OpCode32AluBf.cs new file mode 100644 index 00000000..c3478442 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluBf.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluBf : OpCode32, IOpCode32AluBf + { + public int Rd { get; } + public int Rn { get; } + + public int Msb { get; } + public int Lsb { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluBf(inst, address, opCode); + + public OpCode32AluBf(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 12) & 0xf; + Rn = (opCode >> 0) & 0xf; + + Msb = (opCode >> 16) & 0x1f; + Lsb = (opCode >> 7) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluImm.cs b/src/ARMeilleure/Decoders/OpCode32AluImm.cs new file mode 100644 index 00000000..c8b05e6b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluImm.cs @@ -0,0 +1,23 @@ +using ARMeilleure.Common; + +namespace ARMeilleure.Decoders +{ + class OpCode32AluImm : OpCode32Alu, IOpCode32AluImm + { + public int Immediate { get; } + + public bool IsRotated { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluImm(inst, address, opCode); + + public OpCode32AluImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int value = (opCode >> 0) & 0xff; + int shift = (opCode >> 8) & 0xf; + + Immediate = BitUtils.RotateRight(value, shift * 2, 32); + + IsRotated = shift != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluImm16.cs b/src/ARMeilleure/Decoders/OpCode32AluImm16.cs new file mode 100644 index 00000000..2af35bd5 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluImm16.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluImm16 : OpCode32Alu, IOpCode32AluImm16 + { + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluImm16(inst, address, opCode); + + public OpCode32AluImm16(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm12 = opCode & 0xfff; + int imm4 = (opCode >> 16) & 0xf; + + Immediate = (imm4 << 12) | imm12; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluMla.cs b/src/ARMeilleure/Decoders/OpCode32AluMla.cs new file mode 100644 index 00000000..bc5d2390 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluMla.cs @@ -0,0 +1,30 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluMla : OpCode32, IOpCode32AluMla + { + public int Rn { get; } + public int Rm { get; } + public int Ra { get; } + public int Rd { get; } + + public bool NHigh { get; } + public bool MHigh { get; } + public bool R { get; } + public bool? SetFlags { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluMla(inst, address, opCode); + + public OpCode32AluMla(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 0) & 0xf; + Rm = (opCode >> 8) & 0xf; + Ra = (opCode >> 12) & 0xf; + Rd = (opCode >> 16) & 0xf; + R = (opCode & (1 << 5)) != 0; + + NHigh = ((opCode >> 5) & 0x1) == 1; + MHigh = ((opCode >> 6) & 0x1) == 1; + SetFlags = ((opCode >> 20) & 1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluReg.cs b/src/ARMeilleure/Decoders/OpCode32AluReg.cs new file mode 100644 index 00000000..9ef7571c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluReg : OpCode32Alu, IOpCode32AluReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluReg(inst, address, opCode); + + public OpCode32AluReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluRsImm.cs b/src/ARMeilleure/Decoders/OpCode32AluRsImm.cs new file mode 100644 index 00000000..4b2c5897 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluRsImm.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluRsImm : OpCode32Alu, IOpCode32AluRsImm + { + public int Rm { get; } + public int Immediate { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluRsImm(inst, address, opCode); + + public OpCode32AluRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Immediate = (opCode >> 7) & 0x1f; + + ShiftType = (ShiftType)((opCode >> 5) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluRsReg.cs b/src/ARMeilleure/Decoders/OpCode32AluRsReg.cs new file mode 100644 index 00000000..6379b3bd --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluRsReg.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluRsReg : OpCode32Alu, IOpCode32AluRsReg + { + public int Rm { get; } + public int Rs { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluRsReg(inst, address, opCode); + + public OpCode32AluRsReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Rs = (opCode >> 8) & 0xf; + + ShiftType = (ShiftType)((opCode >> 5) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluUmull.cs b/src/ARMeilleure/Decoders/OpCode32AluUmull.cs new file mode 100644 index 00000000..44b7ea15 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluUmull.cs @@ -0,0 +1,30 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluUmull : OpCode32, IOpCode32AluUmull + { + public int RdLo { get; } + public int RdHi { get; } + public int Rn { get; } + public int Rm { get; } + + public bool NHigh { get; } + public bool MHigh { get; } + + public bool? SetFlags { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluUmull(inst, address, opCode); + + public OpCode32AluUmull(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + RdLo = (opCode >> 12) & 0xf; + RdHi = (opCode >> 16) & 0xf; + Rm = (opCode >> 8) & 0xf; + Rn = (opCode >> 0) & 0xf; + + NHigh = ((opCode >> 5) & 0x1) == 1; + MHigh = ((opCode >> 6) & 0x1) == 1; + + SetFlags = ((opCode >> 20) & 0x1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluUx.cs b/src/ARMeilleure/Decoders/OpCode32AluUx.cs new file mode 100644 index 00000000..68da302f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluUx.cs @@ -0,0 +1,18 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCode32AluUx : OpCode32AluReg, IOpCode32AluUx + { + public int Rotate { get; } + public int RotateBits => Rotate * 8; + public bool Add => Rn != RegisterAlias.Aarch32Pc; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluUx(inst, address, opCode); + + public OpCode32AluUx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rotate = (opCode >> 10) & 0x3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32BImm.cs b/src/ARMeilleure/Decoders/OpCode32BImm.cs new file mode 100644 index 00000000..e7f5d6db --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32BImm.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32BImm : OpCode32, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32BImm(inst, address, opCode); + + public OpCode32BImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + uint pc = GetPc(); + + // When the condition is never, the instruction is BLX to Thumb mode. + if (Cond != Condition.Nv) + { + pc &= ~3u; + } + + Immediate = pc + DecoderHelper.DecodeImm24_2(opCode); + + if (Cond == Condition.Nv) + { + long H = (opCode >> 23) & 2; + + Immediate |= H; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32BReg.cs b/src/ARMeilleure/Decoders/OpCode32BReg.cs new file mode 100644 index 00000000..8939c0de --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32BReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32BReg : OpCode32, IOpCode32BReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32BReg(inst, address, opCode); + + public OpCode32BReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = opCode & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Exception.cs b/src/ARMeilleure/Decoders/OpCode32Exception.cs new file mode 100644 index 00000000..51a535e4 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Exception.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Exception : OpCode32, IOpCode32Exception + { + public int Id { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Exception(inst, address, opCode); + + public OpCode32Exception(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Id = opCode & 0xFFFFFF; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Mem.cs b/src/ARMeilleure/Decoders/OpCode32Mem.cs new file mode 100644 index 00000000..8a242199 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Mem.cs @@ -0,0 +1,39 @@ +using ARMeilleure.Instructions; + +namespace ARMeilleure.Decoders +{ + class OpCode32Mem : OpCode32, IOpCode32Mem + { + public int Rt { get; protected set; } + public int Rn { get; } + + public int Immediate { get; protected set; } + + public bool Index { get; } + public bool Add { get; } + public bool WBack { get; } + public bool Unprivileged { get; } + + public bool IsLoad { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Mem(inst, address, opCode); + + public OpCode32Mem(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + bool isLoad = (opCode & (1 << 20)) != 0; + bool w = (opCode & (1 << 21)) != 0; + bool u = (opCode & (1 << 23)) != 0; + bool p = (opCode & (1 << 24)) != 0; + + Index = p; + Add = u; + WBack = !p || w; + Unprivileged = !p && w; + + IsLoad = isLoad || inst.Name == InstName.Ldrd; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemImm.cs b/src/ARMeilleure/Decoders/OpCode32MemImm.cs new file mode 100644 index 00000000..fa10e04e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemImm.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemImm : OpCode32Mem + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemImm(inst, address, opCode); + + public OpCode32MemImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = opCode & 0xfff; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemImm8.cs b/src/ARMeilleure/Decoders/OpCode32MemImm8.cs new file mode 100644 index 00000000..248ee8e6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemImm8.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemImm8 : OpCode32Mem + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemImm8(inst, address, opCode); + + public OpCode32MemImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm4L = (opCode >> 0) & 0xf; + int imm4H = (opCode >> 8) & 0xf; + + Immediate = imm4L | (imm4H << 4); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemLdEx.cs b/src/ARMeilleure/Decoders/OpCode32MemLdEx.cs new file mode 100644 index 00000000..0f0b37ea --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemLdEx.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemLdEx : OpCode32Mem, IOpCode32MemEx + { + public int Rd { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemLdEx(inst, address, opCode); + + public OpCode32MemLdEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = opCode & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemMult.cs b/src/ARMeilleure/Decoders/OpCode32MemMult.cs new file mode 100644 index 00000000..6e39e347 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemMult.cs @@ -0,0 +1,52 @@ +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + class OpCode32MemMult : OpCode32, IOpCode32MemMult + { + public int Rn { get; } + + public int RegisterMask { get; } + public int Offset { get; } + public int PostOffset { get; } + + public bool IsLoad { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemMult(inst, address, opCode); + + public OpCode32MemMult(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 16) & 0xf; + + bool isLoad = (opCode & (1 << 20)) != 0; + bool w = (opCode & (1 << 21)) != 0; + bool u = (opCode & (1 << 23)) != 0; + bool p = (opCode & (1 << 24)) != 0; + + RegisterMask = opCode & 0xffff; + + int regsSize = BitOperations.PopCount((uint)RegisterMask) * 4; + + if (!u) + { + Offset -= regsSize; + } + + if (u == p) + { + Offset += 4; + } + + if (w) + { + PostOffset = u ? regsSize : -regsSize; + } + else + { + PostOffset = 0; + } + + IsLoad = isLoad; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemReg.cs b/src/ARMeilleure/Decoders/OpCode32MemReg.cs new file mode 100644 index 00000000..d8f1c29b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemReg : OpCode32Mem, IOpCode32MemReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemReg(inst, address, opCode); + + public OpCode32MemReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemRsImm.cs b/src/ARMeilleure/Decoders/OpCode32MemRsImm.cs new file mode 100644 index 00000000..b0e5aa4b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemRsImm.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemRsImm : OpCode32Mem, IOpCode32MemRsImm + { + public int Rm { get; } + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemRsImm(inst, address, opCode); + + public OpCode32MemRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Immediate = (opCode >> 7) & 0x1f; + + ShiftType = (ShiftType)((opCode >> 5) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemStEx.cs b/src/ARMeilleure/Decoders/OpCode32MemStEx.cs new file mode 100644 index 00000000..180a9b5a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemStEx.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemStEx : OpCode32Mem, IOpCode32MemEx + { + public int Rd { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemStEx(inst, address, opCode); + + public OpCode32MemStEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 12) & 0xf; + Rt = (opCode >> 0) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Mrs.cs b/src/ARMeilleure/Decoders/OpCode32Mrs.cs new file mode 100644 index 00000000..b681b54c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Mrs.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Mrs : OpCode32 + { + public bool R { get; } + public int Rd { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Mrs(inst, address, opCode); + + public OpCode32Mrs(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + R = ((opCode >> 22) & 1) != 0; + Rd = (opCode >> 12) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MsrReg.cs b/src/ARMeilleure/Decoders/OpCode32MsrReg.cs new file mode 100644 index 00000000..dcd06aa0 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MsrReg.cs @@ -0,0 +1,29 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCode32MsrReg : OpCode32 + { + public bool R { get; } + public int Mask { get; } + public int Rd { get; } + public bool Banked { get; } + public int Rn { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MsrReg(inst, address, opCode); + + public OpCode32MsrReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + R = ((opCode >> 22) & 1) != 0; + Mask = (opCode >> 16) & 0xf; + Rd = (opCode >> 12) & 0xf; + Banked = ((opCode >> 9) & 1) != 0; + Rn = (opCode >> 0) & 0xf; + + if (Rn == RegisterAlias.Aarch32Pc || Mask == 0) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Sat.cs b/src/ARMeilleure/Decoders/OpCode32Sat.cs new file mode 100644 index 00000000..35c5cf47 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Sat.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Sat : OpCode32 + { + public int Rn { get; } + public int Imm5 { get; } + public int Rd { get; } + public int SatImm { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Sat(inst, address, opCode); + + public OpCode32Sat(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 0) & 0xf; + Imm5 = (opCode >> 7) & 0x1f; + Rd = (opCode >> 12) & 0xf; + SatImm = (opCode >> 16) & 0x1f; + + ShiftType = (ShiftType)((opCode >> 5) & 2); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Sat16.cs b/src/ARMeilleure/Decoders/OpCode32Sat16.cs new file mode 100644 index 00000000..01f4d3b2 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Sat16.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Sat16 : OpCode32 + { + public int Rn { get; } + public int Rd { get; } + public int SatImm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Sat16(inst, address, opCode); + + public OpCode32Sat16(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 0) & 0xf; + Rd = (opCode >> 12) & 0xf; + SatImm = (opCode >> 16) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Simd.cs b/src/ARMeilleure/Decoders/OpCode32Simd.cs new file mode 100644 index 00000000..1e69b234 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Simd.cs @@ -0,0 +1,33 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Simd : OpCode32SimdBase + { + public int Opc { get; protected set; } + public bool Q { get; protected set; } + public bool F { get; protected set; } + public bool U { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Simd(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32Simd(inst, address, opCode, true); + + public OpCode32Simd(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = (opCode >> 20) & 0x3; + Q = ((opCode >> 6) & 0x1) != 0; + F = ((opCode >> 10) & 0x1) != 0; + U = ((opCode >> (isThumb ? 28 : 24)) & 0x1) != 0; + Opc = (opCode >> 7) & 0x3; + + RegisterSize = Q ? RegisterSize.Simd128 : RegisterSize.Simd64; + + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + + // Subclasses have their own handling of Vx to account for before checking. + if (GetType() == typeof(OpCode32Simd) && DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdBase.cs b/src/ARMeilleure/Decoders/OpCode32SimdBase.cs new file mode 100644 index 00000000..d0634a0e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdBase.cs @@ -0,0 +1,49 @@ +using System; + +namespace ARMeilleure.Decoders +{ + abstract class OpCode32SimdBase : OpCode32, IOpCode32Simd + { + public int Vd { get; protected set; } + public int Vm { get; protected set; } + public int Size { get; protected set; } + + // Helpers to index doublewords within quad words. Essentially, looping over the vector starts at quadword Q and index Fx or Ix within it, + // depending on instruction type. + // + // Qx: The quadword register that the target vector is contained in. + // Ix: The starting index of the target vector within the quadword, with size treated as integer. + // Fx: The starting index of the target vector within the quadword, with size treated as floating point. (16 or 32) + public int Qd => GetQuadwordIndex(Vd); + public int Id => GetQuadwordSubindex(Vd) << (3 - Size); + public int Fd => GetQuadwordSubindex(Vd) << (1 - (Size & 1)); // When the top bit is truncated, 1 is fp16 which is an optional extension in ARMv8.2. We always assume 64. + + public int Qm => GetQuadwordIndex(Vm); + public int Im => GetQuadwordSubindex(Vm) << (3 - Size); + public int Fm => GetQuadwordSubindex(Vm) << (1 - (Size & 1)); + + protected int GetQuadwordIndex(int index) + { + return RegisterSize switch + { + RegisterSize.Simd128 or RegisterSize.Simd64 => index >> 1, + _ => throw new InvalidOperationException(), + }; + } + + protected int GetQuadwordSubindex(int index) + { + return RegisterSize switch + { + RegisterSize.Simd128 => 0, + RegisterSize.Simd64 => index & 1, + _ => throw new InvalidOperationException(), + }; + } + + protected OpCode32SimdBase(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdBinary.cs b/src/ARMeilleure/Decoders/OpCode32SimdBinary.cs new file mode 100644 index 00000000..c0c8277a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdBinary.cs @@ -0,0 +1,21 @@ +namespace ARMeilleure.Decoders +{ + /// + /// A special alias that always runs in 64 bit int, to speed up binary ops a little. + /// + class OpCode32SimdBinary : OpCode32SimdReg + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdBinary(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdBinary(inst, address, opCode, true); + + public OpCode32SimdBinary(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = 3; + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm, Vn)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdCmpZ.cs b/src/ARMeilleure/Decoders/OpCode32SimdCmpZ.cs new file mode 100644 index 00000000..d8bc109e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdCmpZ.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdCmpZ : OpCode32Simd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCmpZ(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCmpZ(inst, address, opCode, true); + + public OpCode32SimdCmpZ(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = (opCode >> 18) & 0x3; + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdCvtFFixed.cs b/src/ARMeilleure/Decoders/OpCode32SimdCvtFFixed.cs new file mode 100644 index 00000000..200df73a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdCvtFFixed.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdCvtFFixed : OpCode32Simd + { + public int Fbits { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtFFixed(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtFFixed(inst, address, opCode, true); + + public OpCode32SimdCvtFFixed(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Opc = (opCode >> 8) & 0x1; + + Size = Opc == 1 ? 0 : 2; + Fbits = 64 - ((opCode >> 16) & 0x3f); + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs b/src/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs new file mode 100644 index 00000000..ee8f94a4 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdCvtFI : OpCode32SimdS + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtFI(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtFI(inst, address, opCode, true); + + public OpCode32SimdCvtFI(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Opc = (opCode >> 7) & 0x1; + + bool toInteger = (Opc2 & 0b100) != 0; + + if (toInteger) + { + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdCvtTB.cs b/src/ARMeilleure/Decoders/OpCode32SimdCvtTB.cs new file mode 100644 index 00000000..d3beb4bf --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdCvtTB.cs @@ -0,0 +1,44 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdCvtTB : OpCode32, IOpCode32Simd + { + public int Vd { get; } + public int Vm { get; } + public bool Op { get; } // Convert to Half / Convert from Half + public bool T { get; } // Top / Bottom + public int Size { get; } // Double / Single + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtTB(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtTB(inst, address, opCode, true); + + public OpCode32SimdCvtTB(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Op = ((opCode >> 16) & 0x1) != 0; + T = ((opCode >> 7) & 0x1) != 0; + Size = ((opCode >> 8) & 0x1); + + RegisterSize = Size == 1 ? RegisterSize.Int64 : RegisterSize.Int32; + + if (Size == 1) + { + if (Op) + { + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + } + else + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdDupElem.cs b/src/ARMeilleure/Decoders/OpCode32SimdDupElem.cs new file mode 100644 index 00000000..b6cdff08 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdDupElem.cs @@ -0,0 +1,43 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdDupElem : OpCode32Simd + { + public int Index { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdDupElem(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdDupElem(inst, address, opCode, true); + + public OpCode32SimdDupElem(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + var opc = (opCode >> 16) & 0xf; + + if ((opc & 0b1) == 1) + { + Size = 0; + Index = (opc >> 1) & 0x7; + } + else if ((opc & 0b11) == 0b10) + { + Size = 1; + Index = (opc >> 2) & 0x3; + } + else if ((opc & 0b111) == 0b100) + { + Size = 2; + Index = (opc >> 3) & 0x1; + } + else + { + Instruction = InstDescriptor.Undefined; + } + + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdDupGP.cs b/src/ARMeilleure/Decoders/OpCode32SimdDupGP.cs new file mode 100644 index 00000000..57adea5e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdDupGP.cs @@ -0,0 +1,36 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdDupGP : OpCode32, IOpCode32Simd + { + public int Size { get; } + public int Vd { get; } + public int Rt { get; } + public bool Q { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdDupGP(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdDupGP(inst, address, opCode, true); + + public OpCode32SimdDupGP(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Size = 2 - (((opCode >> 21) & 0x2) | ((opCode >> 5) & 0x1)); // B:E - 0 for 32, 16 then 8. + if (Size == -1) + { + Instruction = InstDescriptor.Undefined; + return; + } + Q = ((opCode >> 21) & 0x1) != 0; + + RegisterSize = Q ? RegisterSize.Simd128 : RegisterSize.Simd64; + + Vd = ((opCode >> 3) & 0x10) | ((opCode >> 16) & 0xf); + Rt = ((opCode >> 12) & 0xf); + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdExt.cs b/src/ARMeilleure/Decoders/OpCode32SimdExt.cs new file mode 100644 index 00000000..4fe9f25d --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdExt.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdExt : OpCode32SimdReg + { + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdExt(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdExt(inst, address, opCode, true); + + public OpCode32SimdExt(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Immediate = (opCode >> 8) & 0xf; + Size = 0; + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm, Vn) || (!Q && Immediate > 7)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdImm.cs b/src/ARMeilleure/Decoders/OpCode32SimdImm.cs new file mode 100644 index 00000000..9e931e79 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdImm.cs @@ -0,0 +1,38 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdImm : OpCode32SimdBase, IOpCode32SimdImm + { + public bool Q { get; } + public long Immediate { get; } + public int Elems => GetBytesCount() >> Size; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdImm(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdImm(inst, address, opCode, true); + + public OpCode32SimdImm(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Vd = (opCode >> 12) & 0xf; + Vd |= (opCode >> 18) & 0x10; + + Q = ((opCode >> 6) & 0x1) > 0; + + int cMode = (opCode >> 8) & 0xf; + int op = (opCode >> 5) & 0x1; + + long imm; + + imm = ((uint)opCode >> 0) & 0xf; + imm |= ((uint)opCode >> 12) & 0x70; + imm |= ((uint)opCode >> (isThumb ? 21 : 17)) & 0x80; + + (Immediate, Size) = OpCodeSimdHelper.GetSimdImmediateAndSize(cMode, op, imm); + + RegisterSize = Q ? RegisterSize.Simd128 : RegisterSize.Simd64; + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdImm44.cs b/src/ARMeilleure/Decoders/OpCode32SimdImm44.cs new file mode 100644 index 00000000..55df1ba6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdImm44.cs @@ -0,0 +1,41 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdImm44 : OpCode32, IOpCode32SimdImm + { + public int Vd { get; } + public long Immediate { get; } + public int Size { get; } + public int Elems { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdImm44(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdImm44(inst, address, opCode, true); + + public OpCode32SimdImm44(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Size = (opCode >> 8) & 0x3; + + bool single = Size != 3; + + if (single) + { + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + + long imm; + + imm = ((uint)opCode >> 0) & 0xf; + imm |= ((uint)opCode >> 12) & 0xf0; + + Immediate = (Size == 3) ? (long)DecoderHelper.Imm8ToFP64Table[(int)imm] : DecoderHelper.Imm8ToFP32Table[(int)imm]; + + RegisterSize = (!single) ? RegisterSize.Int64 : RegisterSize.Int32; + Elems = 1; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdLong.cs b/src/ARMeilleure/Decoders/OpCode32SimdLong.cs new file mode 100644 index 00000000..5c068de1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdLong.cs @@ -0,0 +1,36 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdLong : OpCode32SimdBase + { + public bool U { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdLong(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdLong(inst, address, opCode, true); + + public OpCode32SimdLong(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + int imm3h = (opCode >> 19) & 0x7; + + // The value must be a power of 2, otherwise it is the encoding of another instruction. + switch (imm3h) + { + case 1: + Size = 0; + break; + case 2: + Size = 1; + break; + case 4: + Size = 2; + break; + } + + U = ((opCode >> (isThumb ? 28 : 24)) & 0x1) != 0; + + RegisterSize = RegisterSize.Simd64; + + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMemImm.cs b/src/ARMeilleure/Decoders/OpCode32SimdMemImm.cs new file mode 100644 index 00000000..86870dfe --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMemImm.cs @@ -0,0 +1,40 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMemImm : OpCode32, IOpCode32Simd + { + public int Vd { get; } + public int Rn { get; } + public int Size { get; } + public bool Add { get; } + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemImm(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemImm(inst, address, opCode, true); + + public OpCode32SimdMemImm(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Immediate = opCode & 0xff; + + Rn = (opCode >> 16) & 0xf; + Size = (opCode >> 8) & 0x3; + + Immediate <<= (Size == 1) ? 1 : 2; + + bool u = (opCode & (1 << 23)) != 0; + Add = u; + + bool single = Size != 3; + + if (single) + { + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMemMult.cs b/src/ARMeilleure/Decoders/OpCode32SimdMemMult.cs new file mode 100644 index 00000000..c3b8670f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMemMult.cs @@ -0,0 +1,76 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMemMult : OpCode32 + { + public int Rn { get; } + public int Vd { get; } + + public int RegisterRange { get; } + public int Offset { get; } + public int PostOffset { get; } + public bool IsLoad { get; } + public bool DoubleWidth { get; } + public bool Add { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemMult(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemMult(inst, address, opCode, true); + + public OpCode32SimdMemMult(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Rn = (opCode >> 16) & 0xf; + + bool isLoad = (opCode & (1 << 20)) != 0; + bool w = (opCode & (1 << 21)) != 0; + bool u = (opCode & (1 << 23)) != 0; + bool p = (opCode & (1 << 24)) != 0; + + if (p == u && w) + { + Instruction = InstDescriptor.Undefined; + return; + } + + DoubleWidth = (opCode & (1 << 8)) != 0; + + if (!DoubleWidth) + { + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + + Add = u; + + RegisterRange = opCode & 0xff; + + int regsSize = RegisterRange * 4; // Double mode is still measured in single register size. + + if (!u) + { + Offset -= regsSize; + } + + if (w) + { + PostOffset = u ? regsSize : -regsSize; + } + else + { + PostOffset = 0; + } + + IsLoad = isLoad; + + int regs = DoubleWidth ? RegisterRange / 2 : RegisterRange; + + if (RegisterRange == 0 || RegisterRange > 32 || Vd + regs > 32) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMemPair.cs b/src/ARMeilleure/Decoders/OpCode32SimdMemPair.cs new file mode 100644 index 00000000..6a18211c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMemPair.cs @@ -0,0 +1,50 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMemPair : OpCode32, IOpCode32Simd + { + private static readonly int[] _regsMap = + { + 1, 1, 4, 2, + 1, 1, 3, 1, + 1, 1, 2, 1, + 1, 1, 1, 1, + }; + + public int Vd { get; } + public int Rn { get; } + public int Rm { get; } + public int Align { get; } + public bool WBack { get; } + public bool RegisterIndex { get; } + public int Size { get; } + public int Elems => 8 >> Size; + public int Regs { get; } + public int Increment { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemPair(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemPair(inst, address, opCode, true); + + public OpCode32SimdMemPair(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Vd = (opCode >> 12) & 0xf; + Vd |= (opCode >> 18) & 0x10; + + Size = (opCode >> 6) & 0x3; + + Align = (opCode >> 4) & 0x3; + Rm = (opCode >> 0) & 0xf; + Rn = (opCode >> 16) & 0xf; + + WBack = Rm != RegisterAlias.Aarch32Pc; + RegisterIndex = Rm != RegisterAlias.Aarch32Pc && Rm != RegisterAlias.Aarch32Sp; + + Regs = _regsMap[(opCode >> 8) & 0xf]; + + Increment = ((opCode >> 8) & 0x1) + 1; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMemSingle.cs b/src/ARMeilleure/Decoders/OpCode32SimdMemSingle.cs new file mode 100644 index 00000000..5df45000 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMemSingle.cs @@ -0,0 +1,51 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMemSingle : OpCode32, IOpCode32Simd + { + public int Vd { get; } + public int Rn { get; } + public int Rm { get; } + public int IndexAlign { get; } + public int Index { get; } + public bool WBack { get; } + public bool RegisterIndex { get; } + public int Size { get; } + public bool Replicate { get; } + public int Increment { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemSingle(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemSingle(inst, address, opCode, true); + + public OpCode32SimdMemSingle(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Vd = (opCode >> 12) & 0xf; + Vd |= (opCode >> 18) & 0x10; + + IndexAlign = (opCode >> 4) & 0xf; + + Size = (opCode >> 10) & 0x3; + Replicate = Size == 3; + if (Replicate) + { + Size = (opCode >> 6) & 0x3; + Increment = ((opCode >> 5) & 1) + 1; + Index = 0; + } + else + { + Increment = (((IndexAlign >> Size) & 1) == 0) ? 1 : 2; + Index = IndexAlign >> (1 + Size); + } + + Rm = (opCode >> 0) & 0xf; + Rn = (opCode >> 16) & 0xf; + + WBack = Rm != RegisterAlias.Aarch32Pc; + RegisterIndex = Rm != RegisterAlias.Aarch32Pc && Rm != RegisterAlias.Aarch32Sp; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMovGp.cs b/src/ARMeilleure/Decoders/OpCode32SimdMovGp.cs new file mode 100644 index 00000000..35b8cc9f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMovGp.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMovGp : OpCode32, IOpCode32Simd + { + public int Size => 2; + + public int Vn { get; } + public int Rt { get; } + public int Op { get; } + + public int Opc1 { get; } + public int Opc2 { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGp(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGp(inst, address, opCode, true); + + public OpCode32SimdMovGp(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + // Which one is used is instruction dependant. + Op = (opCode >> 20) & 0x1; + + Opc1 = (opCode >> 21) & 0x3; + Opc2 = (opCode >> 5) & 0x3; + + Vn = ((opCode >> 7) & 0x1) | ((opCode >> 15) & 0x1e); + Rt = (opCode >> 12) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMovGpDouble.cs b/src/ARMeilleure/Decoders/OpCode32SimdMovGpDouble.cs new file mode 100644 index 00000000..4399fb3c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMovGpDouble.cs @@ -0,0 +1,36 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMovGpDouble : OpCode32, IOpCode32Simd + { + public int Size => 3; + + public int Vm { get; } + public int Rt { get; } + public int Rt2 { get; } + public int Op { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGpDouble(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGpDouble(inst, address, opCode, true); + + public OpCode32SimdMovGpDouble(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + // Which one is used is instruction dependant. + Op = (opCode >> 20) & 0x1; + + Rt = (opCode >> 12) & 0xf; + Rt2 = (opCode >> 16) & 0xf; + + bool single = (opCode & (1 << 8)) == 0; + if (single) + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + } + else + { + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMovGpElem.cs b/src/ARMeilleure/Decoders/OpCode32SimdMovGpElem.cs new file mode 100644 index 00000000..f6fce7d9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMovGpElem.cs @@ -0,0 +1,51 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMovGpElem : OpCode32, IOpCode32Simd + { + public int Size { get; } + + public int Vd { get; } + public int Rt { get; } + public int Op { get; } + public bool U { get; } + + public int Index { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGpElem(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGpElem(inst, address, opCode, true); + + public OpCode32SimdMovGpElem(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Op = (opCode >> 20) & 0x1; + U = ((opCode >> 23) & 1) != 0; + + var opc = (((opCode >> 23) & 1) << 4) | (((opCode >> 21) & 0x3) << 2) | ((opCode >> 5) & 0x3); + + if ((opc & 0b01000) == 0b01000) + { + Size = 0; + Index = opc & 0x7; + } + else if ((opc & 0b01001) == 0b00001) + { + Size = 1; + Index = (opc >> 1) & 0x3; + } + else if ((opc & 0b11011) == 0) + { + Size = 2; + Index = (opc >> 2) & 0x1; + } + else + { + Instruction = InstDescriptor.Undefined; + return; + } + + Vd = ((opCode >> 3) & 0x10) | ((opCode >> 16) & 0xf); + Rt = (opCode >> 12) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMovn.cs b/src/ARMeilleure/Decoders/OpCode32SimdMovn.cs new file mode 100644 index 00000000..576e12cc --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMovn.cs @@ -0,0 +1,13 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMovn : OpCode32Simd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovn(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovn(inst, address, opCode, true); + + public OpCode32SimdMovn(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = (opCode >> 18) & 0x3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdReg.cs b/src/ARMeilleure/Decoders/OpCode32SimdReg.cs new file mode 100644 index 00000000..eaf17b8c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdReg.cs @@ -0,0 +1,25 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdReg : OpCode32Simd + { + public int Vn { get; } + + public int Qn => GetQuadwordIndex(Vn); + public int In => GetQuadwordSubindex(Vn) << (3 - Size); + public int Fn => GetQuadwordSubindex(Vn) << (1 - (Size & 1)); + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdReg(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdReg(inst, address, opCode, true); + + public OpCode32SimdReg(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Vn = ((opCode >> 3) & 0x10) | ((opCode >> 16) & 0xf); + + // Subclasses have their own handling of Vx to account for before checking. + if (GetType() == typeof(OpCode32SimdReg) && DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm, Vn)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegElem.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegElem.cs new file mode 100644 index 00000000..147de44b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegElem.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegElem : OpCode32SimdReg + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegElem(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegElem(inst, address, opCode, true); + + public OpCode32SimdRegElem(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = ((opCode >> (isThumb ? 28 : 24)) & 0x1) != 0; + F = ((opCode >> 8) & 0x1) != 0; + Size = (opCode >> 20) & 0x3; + + RegisterSize = Q ? RegisterSize.Simd128 : RegisterSize.Simd64; + + if (Size == 1) + { + Vm = ((opCode >> 3) & 0x1) | ((opCode >> 4) & 0x2) | ((opCode << 2) & 0x1c); + } + else /* if (Size == 2) */ + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + } + + if (GetType() == typeof(OpCode32SimdRegElem) && DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vn) || Size == 0 || (Size == 1 && F)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegElemLong.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegElemLong.cs new file mode 100644 index 00000000..8aea44cb --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegElemLong.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegElemLong : OpCode32SimdRegElem + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegElemLong(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegElemLong(inst, address, opCode, true); + + public OpCode32SimdRegElemLong(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = false; + F = false; + + RegisterSize = RegisterSize.Simd64; + + // (Vd & 1) != 0 || Size == 3 are also invalid, but they are checked on encoding. + if (Size == 0) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegLong.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegLong.cs new file mode 100644 index 00000000..1349fb47 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegLong.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegLong : OpCode32SimdReg + { + public bool Polynomial { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegLong(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegLong(inst, address, opCode, true); + + public OpCode32SimdRegLong(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = false; + RegisterSize = RegisterSize.Simd64; + + Polynomial = ((opCode >> 9) & 0x1) != 0; + + // Subclasses have their own handling of Vx to account for before checking. + if (GetType() == typeof(OpCode32SimdRegLong) && DecoderHelper.VectorArgumentsInvalid(true, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegS.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegS.cs new file mode 100644 index 00000000..2dfb0074 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegS.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegS : OpCode32SimdS + { + public int Vn { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegS(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegS(inst, address, opCode, true); + + public OpCode32SimdRegS(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + bool single = Size != 3; + if (single) + { + Vn = ((opCode >> 7) & 0x1) | ((opCode >> 15) & 0x1e); + } + else + { + Vn = ((opCode >> 3) & 0x10) | ((opCode >> 16) & 0xf); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegWide.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegWide.cs new file mode 100644 index 00000000..6f9c639f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegWide.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegWide : OpCode32SimdReg + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegWide(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegWide(inst, address, opCode, true); + + public OpCode32SimdRegWide(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = false; + RegisterSize = RegisterSize.Simd64; + + // Subclasses have their own handling of Vx to account for before checking. + if (GetType() == typeof(OpCode32SimdRegWide) && DecoderHelper.VectorArgumentsInvalid(true, Vd, Vn)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRev.cs b/src/ARMeilleure/Decoders/OpCode32SimdRev.cs new file mode 100644 index 00000000..26d8be2b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRev.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRev : OpCode32SimdCmpZ + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRev(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRev(inst, address, opCode, true); + + public OpCode32SimdRev(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + if (Opc + Size >= 3) + { + Instruction = InstDescriptor.Undefined; + return; + } + + // Currently, this instruction is treated as though it's OPCODE is the true size, + // which lets us deal with reversing vectors on a single element basis (eg. math magic an I64 rather than insert lots of I8s). + int tempSize = Size; + Size = 3 - Opc; // Op 0 is 64 bit, 1 is 32 and so on. + Opc = tempSize; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdS.cs b/src/ARMeilleure/Decoders/OpCode32SimdS.cs new file mode 100644 index 00000000..0bb62cb5 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdS.cs @@ -0,0 +1,39 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdS : OpCode32, IOpCode32Simd + { + public int Vd { get; protected set; } + public int Vm { get; protected set; } + public int Opc { get; protected set; } // "with_zero" (Opc<1>) [Vcmp, Vcmpe]. + public int Opc2 { get; } // opc2 or RM (opc2<1:0>) [Vcvt, Vrint]. + public int Size { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdS(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdS(inst, address, opCode, true); + + public OpCode32SimdS(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Opc = (opCode >> 15) & 0x3; + Opc2 = (opCode >> 16) & 0x7; + + Size = (opCode >> 8) & 0x3; + + bool single = Size != 3; + + RegisterSize = single ? RegisterSize.Int32 : RegisterSize.Int64; + + if (single) + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdSel.cs b/src/ARMeilleure/Decoders/OpCode32SimdSel.cs new file mode 100644 index 00000000..a6667ba1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdSel.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdSel : OpCode32SimdRegS + { + public OpCode32SimdSelMode Cc { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSel(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSel(inst, address, opCode, true); + + public OpCode32SimdSel(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Cc = (OpCode32SimdSelMode)((opCode >> 20) & 3); + } + } + + enum OpCode32SimdSelMode + { + Eq = 0, + Vs, + Ge, + Gt, + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdShImm.cs b/src/ARMeilleure/Decoders/OpCode32SimdShImm.cs new file mode 100644 index 00000000..040dce6f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdShImm.cs @@ -0,0 +1,46 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdShImm : OpCode32Simd + { + public int Shift { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImm(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImm(inst, address, opCode, true); + + public OpCode32SimdShImm(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + int imm6 = (opCode >> 16) & 0x3f; + int limm6 = ((opCode >> 1) & 0x40) | imm6; + + if ((limm6 & 0x40) == 0b1000000) + { + Size = 3; + Shift = imm6; + } + else if ((limm6 & 0x60) == 0b0100000) + { + Size = 2; + Shift = imm6 - 32; + } + else if ((limm6 & 0x70) == 0b0010000) + { + Size = 1; + Shift = imm6 - 16; + } + else if ((limm6 & 0x78) == 0b0001000) + { + Size = 0; + Shift = imm6 - 8; + } + else + { + Instruction = InstDescriptor.Undefined; + } + + if (GetType() == typeof(OpCode32SimdShImm) && DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdShImmLong.cs b/src/ARMeilleure/Decoders/OpCode32SimdShImmLong.cs new file mode 100644 index 00000000..13d89ca4 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdShImmLong.cs @@ -0,0 +1,43 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdShImmLong : OpCode32Simd + { + public int Shift { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImmLong(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImmLong(inst, address, opCode, true); + + public OpCode32SimdShImmLong(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = false; + RegisterSize = RegisterSize.Simd64; + + int imm6 = (opCode >> 16) & 0x3f; + + if ((imm6 & 0x20) == 0b100000) + { + Size = 2; + Shift = imm6 - 32; + } + else if ((imm6 & 0x30) == 0b010000) + { + Size = 1; + Shift = imm6 - 16; + } + else if ((imm6 & 0x38) == 0b001000) + { + Size = 0; + Shift = imm6 - 8; + } + else + { + Instruction = InstDescriptor.Undefined; + } + + if (GetType() == typeof(OpCode32SimdShImmLong) && DecoderHelper.VectorArgumentsInvalid(true, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdShImmNarrow.cs b/src/ARMeilleure/Decoders/OpCode32SimdShImmNarrow.cs new file mode 100644 index 00000000..ce1e7906 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdShImmNarrow.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdShImmNarrow : OpCode32SimdShImm + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImmNarrow(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImmNarrow(inst, address, opCode, true); + + public OpCode32SimdShImmNarrow(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) { } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdSpecial.cs b/src/ARMeilleure/Decoders/OpCode32SimdSpecial.cs new file mode 100644 index 00000000..9b6f4732 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdSpecial.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdSpecial : OpCode32 + { + public int Rt { get; } + public int Sreg { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSpecial(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSpecial(inst, address, opCode, true); + + public OpCode32SimdSpecial(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Rt = (opCode >> 12) & 0xf; + Sreg = (opCode >> 16) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdSqrte.cs b/src/ARMeilleure/Decoders/OpCode32SimdSqrte.cs new file mode 100644 index 00000000..8f8fa4b0 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdSqrte.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdSqrte : OpCode32Simd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSqrte(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSqrte(inst, address, opCode, true); + + public OpCode32SimdSqrte(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = (opCode >> 18) & 0x1; + F = ((opCode >> 8) & 0x1) != 0; + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdTbl.cs b/src/ARMeilleure/Decoders/OpCode32SimdTbl.cs new file mode 100644 index 00000000..fcac9e01 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdTbl.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdTbl : OpCode32SimdReg + { + public int Length { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdTbl(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdTbl(inst, address, opCode, true); + + public OpCode32SimdTbl(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Length = (opCode >> 8) & 3; + Size = 0; + Opc = Q ? 1 : 0; + Q = false; + RegisterSize = RegisterSize.Simd64; + + if (Vn + Length + 1 > 32) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32System.cs b/src/ARMeilleure/Decoders/OpCode32System.cs new file mode 100644 index 00000000..f6f5e0f9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32System.cs @@ -0,0 +1,28 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32System : OpCode32 + { + public int Opc1 { get; } + public int CRn { get; } + public int Rt { get; } + public int Opc2 { get; } + public int CRm { get; } + public int MrrcOp { get; } + + public int Coproc { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32System(inst, address, opCode); + + public OpCode32System(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Opc1 = (opCode >> 21) & 0x7; + CRn = (opCode >> 16) & 0xf; + Rt = (opCode >> 12) & 0xf; + Opc2 = (opCode >> 5) & 0x7; + CRm = (opCode >> 0) & 0xf; + MrrcOp = (opCode >> 4) & 0xf; + + Coproc = (opCode >> 8) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeAdr.cs b/src/ARMeilleure/Decoders/OpCodeAdr.cs new file mode 100644 index 00000000..08028040 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAdr.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAdr : OpCode + { + public int Rd { get; } + + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAdr(inst, address, opCode); + + public OpCodeAdr(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = opCode & 0x1f; + + Immediate = DecoderHelper.DecodeImmS19_2(opCode); + Immediate |= ((long)opCode >> 29) & 3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeAlu.cs b/src/ARMeilleure/Decoders/OpCodeAlu.cs new file mode 100644 index 00000000..1619ecd8 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAlu.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAlu : OpCode, IOpCodeAlu + { + public int Rd { get; protected set; } + public int Rn { get; } + + public DataOp DataOp { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAlu(inst, address, opCode); + + public OpCodeAlu(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x1f; + Rn = (opCode >> 5) & 0x1f; + DataOp = (DataOp)((opCode >> 24) & 0x3); + + RegisterSize = (opCode >> 31) != 0 + ? RegisterSize.Int64 + : RegisterSize.Int32; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeAluBinary.cs b/src/ARMeilleure/Decoders/OpCodeAluBinary.cs new file mode 100644 index 00000000..4413581c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAluBinary.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAluBinary : OpCodeAlu + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAluBinary(inst, address, opCode); + + public OpCodeAluBinary(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 16) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeAluImm.cs b/src/ARMeilleure/Decoders/OpCodeAluImm.cs new file mode 100644 index 00000000..0d2f7202 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAluImm.cs @@ -0,0 +1,40 @@ +using System; + +namespace ARMeilleure.Decoders +{ + class OpCodeAluImm : OpCodeAlu, IOpCodeAluImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAluImm(inst, address, opCode); + + public OpCodeAluImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + if (DataOp == DataOp.Arithmetic) + { + Immediate = (opCode >> 10) & 0xfff; + + int shift = (opCode >> 22) & 3; + + Immediate <<= shift * 12; + } + else if (DataOp == DataOp.Logical) + { + var bm = DecoderHelper.DecodeBitMask(opCode, true); + + if (bm.IsUndefined) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Immediate = bm.WMask; + } + else + { + throw new ArgumentException($"Invalid data operation: {DataOp}", nameof(opCode)); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeAluRs.cs b/src/ARMeilleure/Decoders/OpCodeAluRs.cs new file mode 100644 index 00000000..47a47e7d --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAluRs.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAluRs : OpCodeAlu, IOpCodeAluRs + { + public int Shift { get; } + public int Rm { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAluRs(inst, address, opCode); + + public OpCodeAluRs(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int shift = (opCode >> 10) & 0x3f; + + if (shift >= GetBitsCount()) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Shift = shift; + + Rm = (opCode >> 16) & 0x1f; + ShiftType = (ShiftType)((opCode >> 22) & 0x3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeAluRx.cs b/src/ARMeilleure/Decoders/OpCodeAluRx.cs new file mode 100644 index 00000000..c2148678 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAluRx.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAluRx : OpCodeAlu, IOpCodeAluRx + { + public int Shift { get; } + public int Rm { get; } + + public IntType IntType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAluRx(inst, address, opCode); + + public OpCodeAluRx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Shift = (opCode >> 10) & 0x7; + IntType = (IntType)((opCode >> 13) & 0x7); + Rm = (opCode >> 16) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeBImm.cs b/src/ARMeilleure/Decoders/OpCodeBImm.cs new file mode 100644 index 00000000..2848c140 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImm.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImm : OpCode, IOpCodeBImm + { + public long Immediate { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImm(inst, address, opCode); + + public OpCodeBImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) { } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeBImmAl.cs b/src/ARMeilleure/Decoders/OpCodeBImmAl.cs new file mode 100644 index 00000000..6c4b28c6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImmAl.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmAl : OpCodeBImm + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImmAl(inst, address, opCode); + + public OpCodeBImmAl(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = (long)address + DecoderHelper.DecodeImm26_2(opCode); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeBImmCmp.cs b/src/ARMeilleure/Decoders/OpCodeBImmCmp.cs new file mode 100644 index 00000000..c477ddec --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImmCmp.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmCmp : OpCodeBImm + { + public int Rt { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImmCmp(inst, address, opCode); + + public OpCodeBImmCmp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = opCode & 0x1f; + + Immediate = (long)address + DecoderHelper.DecodeImmS19_2(opCode); + + RegisterSize = (opCode >> 31) != 0 + ? RegisterSize.Int64 + : RegisterSize.Int32; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeBImmCond.cs b/src/ARMeilleure/Decoders/OpCodeBImmCond.cs new file mode 100644 index 00000000..7a51a072 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImmCond.cs @@ -0,0 +1,25 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmCond : OpCodeBImm, IOpCodeCond + { + public Condition Cond { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImmCond(inst, address, opCode); + + public OpCodeBImmCond(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int o0 = (opCode >> 4) & 1; + + if (o0 != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Cond = (Condition)(opCode & 0xf); + + Immediate = (long)address + DecoderHelper.DecodeImmS19_2(opCode); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeBImmTest.cs b/src/ARMeilleure/Decoders/OpCodeBImmTest.cs new file mode 100644 index 00000000..f989e59e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImmTest.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmTest : OpCodeBImm + { + public int Rt { get; } + public int Bit { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImmTest(inst, address, opCode); + + public OpCodeBImmTest(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = opCode & 0x1f; + + Immediate = (long)address + DecoderHelper.DecodeImmS14_2(opCode); + + Bit = (opCode >> 19) & 0x1f; + Bit |= (opCode >> 26) & 0x20; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeBReg.cs b/src/ARMeilleure/Decoders/OpCodeBReg.cs new file mode 100644 index 00000000..3b84cf5c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBReg.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBReg : OpCode + { + public int Rn { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBReg(inst, address, opCode); + + public OpCodeBReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int op4 = (opCode >> 0) & 0x1f; + int op2 = (opCode >> 16) & 0x1f; + + if (op2 != 0b11111 || op4 != 0b00000) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Rn = (opCode >> 5) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeBfm.cs b/src/ARMeilleure/Decoders/OpCodeBfm.cs new file mode 100644 index 00000000..d51efade --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBfm.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBfm : OpCodeAlu + { + public long WMask { get; } + public long TMask { get; } + public int Pos { get; } + public int Shift { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBfm(inst, address, opCode); + + public OpCodeBfm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + var bm = DecoderHelper.DecodeBitMask(opCode, false); + + if (bm.IsUndefined) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + WMask = bm.WMask; + TMask = bm.TMask; + Pos = bm.Pos; + Shift = bm.Shift; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeCcmp.cs b/src/ARMeilleure/Decoders/OpCodeCcmp.cs new file mode 100644 index 00000000..d4035348 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeCcmp.cs @@ -0,0 +1,32 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeCcmp : OpCodeAlu, IOpCodeCond + { + public int Nzcv { get; } + protected int RmImm; + + public Condition Cond { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeCcmp(inst, address, opCode); + + public OpCodeCcmp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int o3 = (opCode >> 4) & 1; + + if (o3 != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Nzcv = (opCode >> 0) & 0xf; + Cond = (Condition)((opCode >> 12) & 0xf); + RmImm = (opCode >> 16) & 0x1f; + + Rd = RegisterAlias.Zr; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeCcmpImm.cs b/src/ARMeilleure/Decoders/OpCodeCcmpImm.cs new file mode 100644 index 00000000..9d6acf19 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeCcmpImm.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeCcmpImm : OpCodeCcmp, IOpCodeAluImm + { + public long Immediate => RmImm; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeCcmpImm(inst, address, opCode); + + public OpCodeCcmpImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) { } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeCcmpReg.cs b/src/ARMeilleure/Decoders/OpCodeCcmpReg.cs new file mode 100644 index 00000000..349afa12 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeCcmpReg.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeCcmpReg : OpCodeCcmp, IOpCodeAluRs + { + public int Rm => RmImm; + + public int Shift => 0; + + public ShiftType ShiftType => ShiftType.Lsl; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeCcmpReg(inst, address, opCode); + + public OpCodeCcmpReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) { } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeCsel.cs b/src/ARMeilleure/Decoders/OpCodeCsel.cs new file mode 100644 index 00000000..418962e0 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeCsel.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeCsel : OpCodeAlu, IOpCodeCond + { + public int Rm { get; } + + public Condition Cond { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeCsel(inst, address, opCode); + + public OpCodeCsel(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 16) & 0x1f; + Cond = (Condition)((opCode >> 12) & 0xf); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeException.cs b/src/ARMeilleure/Decoders/OpCodeException.cs new file mode 100644 index 00000000..eee63640 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeException.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeException : OpCode + { + public int Id { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeException(inst, address, opCode); + + public OpCodeException(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Id = (opCode >> 5) & 0xffff; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMem.cs b/src/ARMeilleure/Decoders/OpCodeMem.cs new file mode 100644 index 00000000..9b4e5ff3 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMem.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMem : OpCode + { + public int Rt { get; protected set; } + public int Rn { get; protected set; } + public int Size { get; protected set; } + public bool Extend64 { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMem(inst, address, opCode); + + public OpCodeMem(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 0) & 0x1f; + Rn = (opCode >> 5) & 0x1f; + Size = (opCode >> 30) & 0x3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMemEx.cs b/src/ARMeilleure/Decoders/OpCodeMemEx.cs new file mode 100644 index 00000000..1dc73140 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemEx.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemEx : OpCodeMem + { + public int Rt2 { get; } + public int Rs { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemEx(inst, address, opCode); + + public OpCodeMemEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt2 = (opCode >> 10) & 0x1f; + Rs = (opCode >> 16) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMemImm.cs b/src/ARMeilleure/Decoders/OpCodeMemImm.cs new file mode 100644 index 00000000..4d5eeb1e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemImm.cs @@ -0,0 +1,53 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemImm : OpCodeMem + { + public long Immediate { get; protected set; } + public bool WBack { get; protected set; } + public bool PostIdx { get; protected set; } + protected bool Unscaled { get; } + + private enum MemOp + { + Unscaled = 0, + PostIndexed = 1, + Unprivileged = 2, + PreIndexed = 3, + Unsigned, + } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemImm(inst, address, opCode); + + public OpCodeMemImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Extend64 = ((opCode >> 22) & 3) == 2; + WBack = ((opCode >> 24) & 1) == 0; + + // The type is not valid for the Unsigned Immediate 12-bits encoding, + // because the bits 11:10 are used for the larger Immediate offset. + MemOp type = WBack ? (MemOp)((opCode >> 10) & 3) : MemOp.Unsigned; + + PostIdx = type == MemOp.PostIndexed; + Unscaled = type == MemOp.Unscaled || + type == MemOp.Unprivileged; + + // Unscaled and Unprivileged doesn't write back, + // but they do use the 9-bits Signed Immediate. + if (Unscaled) + { + WBack = false; + } + + if (WBack || Unscaled) + { + // 9-bits Signed Immediate. + Immediate = (opCode << 11) >> 23; + } + else + { + // 12-bits Unsigned Immediate. + Immediate = ((opCode >> 10) & 0xfff) << Size; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMemLit.cs b/src/ARMeilleure/Decoders/OpCodeMemLit.cs new file mode 100644 index 00000000..8712a78e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemLit.cs @@ -0,0 +1,44 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemLit : OpCode, IOpCodeLit + { + public int Rt { get; } + public long Immediate { get; } + public int Size { get; } + public bool Signed { get; } + public bool Prefetch { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemLit(inst, address, opCode); + + public OpCodeMemLit(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = opCode & 0x1f; + + Immediate = (long)address + DecoderHelper.DecodeImmS19_2(opCode); + + switch ((opCode >> 30) & 3) + { + case 0: + Size = 2; + Signed = false; + Prefetch = false; + break; + case 1: + Size = 3; + Signed = false; + Prefetch = false; + break; + case 2: + Size = 2; + Signed = true; + Prefetch = false; + break; + case 3: + Size = 0; + Signed = false; + Prefetch = true; + break; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMemPair.cs b/src/ARMeilleure/Decoders/OpCodeMemPair.cs new file mode 100644 index 00000000..eb696cfe --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemPair.cs @@ -0,0 +1,25 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemPair : OpCodeMemImm + { + public int Rt2 { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemPair(inst, address, opCode); + + public OpCodeMemPair(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt2 = (opCode >> 10) & 0x1f; + WBack = ((opCode >> 23) & 0x1) != 0; + PostIdx = ((opCode >> 23) & 0x3) == 1; + Extend64 = ((opCode >> 30) & 0x3) == 1; + Size = ((opCode >> 31) & 0x1) | 2; + + DecodeImm(opCode); + } + + protected void DecodeImm(int opCode) + { + Immediate = ((long)(opCode >> 15) << 57) >> (57 - Size); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMemReg.cs b/src/ARMeilleure/Decoders/OpCodeMemReg.cs new file mode 100644 index 00000000..9b0d1595 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemReg.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemReg : OpCodeMem + { + public bool Shift { get; } + public int Rm { get; } + + public IntType IntType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemReg(inst, address, opCode); + + public OpCodeMemReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Shift = ((opCode >> 12) & 0x1) != 0; + IntType = (IntType)((opCode >> 13) & 0x7); + Rm = (opCode >> 16) & 0x1f; + Extend64 = ((opCode >> 22) & 0x3) == 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMov.cs b/src/ARMeilleure/Decoders/OpCodeMov.cs new file mode 100644 index 00000000..a2914b71 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMov.cs @@ -0,0 +1,38 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMov : OpCode + { + public int Rd { get; } + + public long Immediate { get; } + + public int Bit { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMov(inst, address, opCode); + + public OpCodeMov(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int p1 = (opCode >> 22) & 1; + int sf = (opCode >> 31) & 1; + + if (sf == 0 && p1 != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Rd = (opCode >> 0) & 0x1f; + Immediate = (opCode >> 5) & 0xffff; + Bit = (opCode >> 21) & 0x3; + + Bit <<= 4; + + Immediate <<= Bit; + + RegisterSize = (opCode >> 31) != 0 + ? RegisterSize.Int64 + : RegisterSize.Int32; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeMul.cs b/src/ARMeilleure/Decoders/OpCodeMul.cs new file mode 100644 index 00000000..9b1dd37b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMul.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMul : OpCodeAlu + { + public int Rm { get; } + public int Ra { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMul(inst, address, opCode); + + public OpCodeMul(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Ra = (opCode >> 10) & 0x1f; + Rm = (opCode >> 16) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimd.cs b/src/ARMeilleure/Decoders/OpCodeSimd.cs new file mode 100644 index 00000000..bd34d74d --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimd.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimd : OpCode, IOpCodeSimd + { + public int Rd { get; } + public int Rn { get; } + public int Opc { get; } + public int Size { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimd(inst, address, opCode); + + public OpCodeSimd(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x1f; + Rn = (opCode >> 5) & 0x1f; + Opc = (opCode >> 15) & 0x3; + Size = (opCode >> 22) & 0x3; + + RegisterSize = ((opCode >> 30) & 1) != 0 + ? RegisterSize.Simd128 + : RegisterSize.Simd64; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdCvt.cs b/src/ARMeilleure/Decoders/OpCodeSimdCvt.cs new file mode 100644 index 00000000..e50cf12e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdCvt.cs @@ -0,0 +1,21 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdCvt : OpCodeSimd + { + public int FBits { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdCvt(inst, address, opCode); + + public OpCodeSimdCvt(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int scale = (opCode >> 10) & 0x3f; + int sf = (opCode >> 31) & 0x1; + + FBits = 64 - scale; + + RegisterSize = sf != 0 + ? RegisterSize.Int64 + : RegisterSize.Int32; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdExt.cs b/src/ARMeilleure/Decoders/OpCodeSimdExt.cs new file mode 100644 index 00000000..0a3359e1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdExt.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdExt : OpCodeSimdReg + { + public int Imm4 { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdExt(inst, address, opCode); + + public OpCodeSimdExt(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Imm4 = (opCode >> 11) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdFcond.cs b/src/ARMeilleure/Decoders/OpCodeSimdFcond.cs new file mode 100644 index 00000000..510cd310 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdFcond.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdFcond : OpCodeSimdReg, IOpCodeCond + { + public int Nzcv { get; } + + public Condition Cond { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdFcond(inst, address, opCode); + + public OpCodeSimdFcond(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Nzcv = (opCode >> 0) & 0xf; + Cond = (Condition)((opCode >> 12) & 0xf); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdFmov.cs b/src/ARMeilleure/Decoders/OpCodeSimdFmov.cs new file mode 100644 index 00000000..662abe28 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdFmov.cs @@ -0,0 +1,32 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdFmov : OpCode, IOpCodeSimd + { + public int Rd { get; } + public long Immediate { get; } + public int Size { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdFmov(inst, address, opCode); + + public OpCodeSimdFmov(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int type = (opCode >> 22) & 0x3; + + Size = type; + + long imm; + + Rd = (opCode >> 0) & 0x1f; + imm = (opCode >> 13) & 0xff; + + if (type == 0) + { + Immediate = (long)DecoderHelper.Imm8ToFP32Table[(int)imm]; + } + else /* if (type == 1) */ + { + Immediate = (long)DecoderHelper.Imm8ToFP64Table[(int)imm]; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdHelper.cs b/src/ARMeilleure/Decoders/OpCodeSimdHelper.cs new file mode 100644 index 00000000..b006cc95 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdHelper.cs @@ -0,0 +1,91 @@ +namespace ARMeilleure.Decoders +{ + public static class OpCodeSimdHelper + { + public static (long Immediate, int Size) GetSimdImmediateAndSize(int cMode, int op, long imm) + { + int modeLow = cMode & 1; + int modeHigh = cMode >> 1; + int size = 0; + + if (modeHigh == 0b111) + { + switch (op | (modeLow << 1)) + { + case 0: + // 64-bits Immediate. + // Transform abcd efgh into abcd efgh abcd efgh ... + size = 3; + imm = (long)((ulong)imm * 0x0101010101010101); + break; + + case 1: + // 64-bits Immediate. + // Transform abcd efgh into aaaa aaaa bbbb bbbb ... + size = 3; + imm = (imm & 0xf0) >> 4 | (imm & 0x0f) << 4; + imm = (imm & 0xcc) >> 2 | (imm & 0x33) << 2; + imm = (imm & 0xaa) >> 1 | (imm & 0x55) << 1; + + imm = (long)((ulong)imm * 0x8040201008040201); + imm = (long)((ulong)imm & 0x8080808080808080); + + imm |= imm >> 4; + imm |= imm >> 2; + imm |= imm >> 1; + break; + + case 2: + // 2 x 32-bits floating point Immediate. + size = 3; + imm = (long)DecoderHelper.Imm8ToFP32Table[(int)imm]; + imm |= imm << 32; + break; + + case 3: + // 64-bits floating point Immediate. + size = 3; + imm = (long)DecoderHelper.Imm8ToFP64Table[(int)imm]; + break; + } + } + else if ((modeHigh & 0b110) == 0b100) + { + // 16-bits shifted Immediate. + size = 1; + imm <<= (modeHigh & 1) << 3; + } + else if ((modeHigh & 0b100) == 0b000) + { + // 32-bits shifted Immediate. + size = 2; + imm <<= modeHigh << 3; + } + else if ((modeHigh & 0b111) == 0b110) + { + // 32-bits shifted Immediate (fill with ones). + size = 2; + imm = ShlOnes(imm, 8 << modeLow); + } + else + { + // 8-bits without shift. + size = 0; + } + + return (imm, size); + } + + private static long ShlOnes(long value, int shift) + { + if (shift != 0) + { + return value << shift | (long)(ulong.MaxValue >> (64 - shift)); + } + else + { + return value; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdImm.cs b/src/ARMeilleure/Decoders/OpCodeSimdImm.cs new file mode 100644 index 00000000..3f4bad7f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdImm.cs @@ -0,0 +1,110 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdImm : OpCode, IOpCodeSimd + { + public int Rd { get; } + public long Immediate { get; } + public int Size { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdImm(inst, address, opCode); + + public OpCodeSimdImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = opCode & 0x1f; + + int cMode = (opCode >> 12) & 0xf; + int op = (opCode >> 29) & 0x1; + + int modeLow = cMode & 1; + int modeHigh = cMode >> 1; + + long imm; + + imm = ((uint)opCode >> 5) & 0x1f; + imm |= ((uint)opCode >> 11) & 0xe0; + + if (modeHigh == 0b111) + { + switch (op | (modeLow << 1)) + { + case 0: + // 64-bits Immediate. + // Transform abcd efgh into abcd efgh abcd efgh ... + Size = 3; + imm = (long)((ulong)imm * 0x0101010101010101); + break; + + case 1: + // 64-bits Immediate. + // Transform abcd efgh into aaaa aaaa bbbb bbbb ... + Size = 3; + imm = (imm & 0xf0) >> 4 | (imm & 0x0f) << 4; + imm = (imm & 0xcc) >> 2 | (imm & 0x33) << 2; + imm = (imm & 0xaa) >> 1 | (imm & 0x55) << 1; + + imm = (long)((ulong)imm * 0x8040201008040201); + imm = (long)((ulong)imm & 0x8080808080808080); + + imm |= imm >> 4; + imm |= imm >> 2; + imm |= imm >> 1; + break; + + case 2: + // 2 x 32-bits floating point Immediate. + Size = 0; + imm = (long)DecoderHelper.Imm8ToFP32Table[(int)imm]; + imm |= imm << 32; + break; + + case 3: + // 64-bits floating point Immediate. + Size = 1; + imm = (long)DecoderHelper.Imm8ToFP64Table[(int)imm]; + break; + } + } + else if ((modeHigh & 0b110) == 0b100) + { + // 16-bits shifted Immediate. + Size = 1; + imm <<= (modeHigh & 1) << 3; + } + else if ((modeHigh & 0b100) == 0b000) + { + // 32-bits shifted Immediate. + Size = 2; + imm <<= modeHigh << 3; + } + else if ((modeHigh & 0b111) == 0b110) + { + // 32-bits shifted Immediate (fill with ones). + Size = 2; + imm = ShlOnes(imm, 8 << modeLow); + } + else + { + // 8-bits without shift. + Size = 0; + } + + Immediate = imm; + + RegisterSize = ((opCode >> 30) & 1) != 0 + ? RegisterSize.Simd128 + : RegisterSize.Simd64; + } + + private static long ShlOnes(long value, int shift) + { + if (shift != 0) + { + return value << shift | (long)(ulong.MaxValue >> (64 - shift)); + } + else + { + return value; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdIns.cs b/src/ARMeilleure/Decoders/OpCodeSimdIns.cs new file mode 100644 index 00000000..95436879 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdIns.cs @@ -0,0 +1,44 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdIns : OpCodeSimd + { + public int SrcIndex { get; } + public int DstIndex { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdIns(inst, address, opCode); + + public OpCodeSimdIns(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm4 = (opCode >> 11) & 0xf; + int imm5 = (opCode >> 16) & 0x1f; + + if (imm5 == 0b10000) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Size = imm5 & -imm5; + + switch (Size) + { + case 1: + Size = 0; + break; + case 2: + Size = 1; + break; + case 4: + Size = 2; + break; + case 8: + Size = 3; + break; + } + + SrcIndex = imm4 >> Size; + DstIndex = imm5 >> (Size + 1); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemImm.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemImm.cs new file mode 100644 index 00000000..14a9d7c9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemImm.cs @@ -0,0 +1,28 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemImm : OpCodeMemImm, IOpCodeSimd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemImm(inst, address, opCode); + + public OpCodeSimdMemImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Size |= (opCode >> 21) & 4; + + if (Size > 4) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + // Base class already shifts the immediate, we only + // need to shift it if size (scale) is 4, since this value is only set here. + if (!WBack && !Unscaled && Size == 4) + { + Immediate <<= 4; + } + + Extend64 = false; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemLit.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemLit.cs new file mode 100644 index 00000000..efa558bf --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemLit.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemLit : OpCode, IOpCodeSimd, IOpCodeLit + { + public int Rt { get; } + public long Immediate { get; } + public int Size { get; } + public bool Signed => false; + public bool Prefetch => false; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemLit(inst, address, opCode); + + public OpCodeSimdMemLit(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int opc = (opCode >> 30) & 3; + + if (opc == 3) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Rt = opCode & 0x1f; + + Immediate = (long)address + DecoderHelper.DecodeImmS19_2(opCode); + + Size = opc + 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemMs.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemMs.cs new file mode 100644 index 00000000..c05b5249 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemMs.cs @@ -0,0 +1,71 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemMs : OpCodeMemReg, IOpCodeSimd + { + public int Reps { get; } + public int SElems { get; } + public int Elems { get; } + public bool WBack { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemMs(inst, address, opCode); + + public OpCodeSimdMemMs(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + switch ((opCode >> 12) & 0xf) + { + case 0b0000: + Reps = 1; + SElems = 4; + break; + case 0b0010: + Reps = 4; + SElems = 1; + break; + case 0b0100: + Reps = 1; + SElems = 3; + break; + case 0b0110: + Reps = 3; + SElems = 1; + break; + case 0b0111: + Reps = 1; + SElems = 1; + break; + case 0b1000: + Reps = 1; + SElems = 2; + break; + case 0b1010: + Reps = 2; + SElems = 1; + break; + + default: + Instruction = InstDescriptor.Undefined; + return; + } + + Size = (opCode >> 10) & 3; + WBack = ((opCode >> 23) & 1) != 0; + + bool q = ((opCode >> 30) & 1) != 0; + + if (!q && Size == 3 && SElems != 1) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Extend64 = false; + + RegisterSize = q + ? RegisterSize.Simd128 + : RegisterSize.Simd64; + + Elems = (GetBitsCount() >> 3) >> Size; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemPair.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemPair.cs new file mode 100644 index 00000000..69716389 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemPair.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemPair : OpCodeMemPair, IOpCodeSimd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemPair(inst, address, opCode); + + public OpCodeSimdMemPair(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Size = ((opCode >> 30) & 3) + 2; + + Extend64 = false; + + DecodeImm(opCode); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemReg.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemReg.cs new file mode 100644 index 00000000..be7b25b9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemReg.cs @@ -0,0 +1,21 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemReg : OpCodeMemReg, IOpCodeSimd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemReg(inst, address, opCode); + + public OpCodeSimdMemReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Size |= (opCode >> 21) & 4; + + if (Size > 4) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Extend64 = false; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemSs.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemSs.cs new file mode 100644 index 00000000..5bc614e1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemSs.cs @@ -0,0 +1,97 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemSs : OpCodeMemReg, IOpCodeSimd + { + public int SElems { get; } + public int Index { get; } + public bool Replicate { get; } + public bool WBack { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemSs(inst, address, opCode); + + public OpCodeSimdMemSs(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int size = (opCode >> 10) & 3; + int s = (opCode >> 12) & 1; + int sElems = (opCode >> 12) & 2; + int scale = (opCode >> 14) & 3; + int l = (opCode >> 22) & 1; + int q = (opCode >> 30) & 1; + + sElems |= (opCode >> 21) & 1; + + sElems++; + + int index = (q << 3) | (s << 2) | size; + + switch (scale) + { + case 1: + { + if ((size & 1) != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + index >>= 1; + + break; + } + + case 2: + { + if ((size & 2) != 0 || + ((size & 1) != 0 && s != 0)) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + if ((size & 1) != 0) + { + index >>= 3; + + scale = 3; + } + else + { + index >>= 2; + } + + break; + } + + case 3: + { + if (l == 0 || s != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + scale = size; + + Replicate = true; + + break; + } + } + + Index = index; + SElems = sElems; + Size = scale; + + Extend64 = false; + + WBack = ((opCode >> 23) & 1) != 0; + + RegisterSize = q != 0 + ? RegisterSize.Simd128 + : RegisterSize.Simd64; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdReg.cs b/src/ARMeilleure/Decoders/OpCodeSimdReg.cs new file mode 100644 index 00000000..40f9b1c5 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdReg.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdReg : OpCodeSimd + { + public bool Bit3 { get; } + public int Ra { get; } + public int Rm { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdReg(inst, address, opCode); + + public OpCodeSimdReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Bit3 = ((opCode >> 3) & 0x1) != 0; + Ra = (opCode >> 10) & 0x1f; + Rm = (opCode >> 16) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdRegElem.cs b/src/ARMeilleure/Decoders/OpCodeSimdRegElem.cs new file mode 100644 index 00000000..bb248ab6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdRegElem.cs @@ -0,0 +1,33 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdRegElem : OpCodeSimdReg + { + public int Index { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdRegElem(inst, address, opCode); + + public OpCodeSimdRegElem(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + switch (Size) + { + case 1: + Index = (opCode >> 20) & 3 | + (opCode >> 9) & 4; + + Rm &= 0xf; + + break; + + case 2: + Index = (opCode >> 21) & 1 | + (opCode >> 10) & 2; + + break; + + default: + Instruction = InstDescriptor.Undefined; + break; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs b/src/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs new file mode 100644 index 00000000..c97bd787 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs @@ -0,0 +1,35 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdRegElemF : OpCodeSimdReg + { + public int Index { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdRegElemF(inst, address, opCode); + + public OpCodeSimdRegElemF(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + switch ((opCode >> 21) & 3) // sz:L + { + case 0: // H:0 + Index = (opCode >> 10) & 2; // 0, 2 + + break; + + case 1: // H:1 + Index = (opCode >> 10) & 2; + Index++; // 1, 3 + + break; + + case 2: // H + Index = (opCode >> 11) & 1; // 0, 1 + + break; + + default: + Instruction = InstDescriptor.Undefined; + break; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdShImm.cs b/src/ARMeilleure/Decoders/OpCodeSimdShImm.cs new file mode 100644 index 00000000..7064f1d2 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdShImm.cs @@ -0,0 +1,18 @@ +using ARMeilleure.Common; + +namespace ARMeilleure.Decoders +{ + class OpCodeSimdShImm : OpCodeSimd + { + public int Imm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdShImm(inst, address, opCode); + + public OpCodeSimdShImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Imm = (opCode >> 16) & 0x7f; + + Size = BitUtils.HighestBitSetNibble(Imm >> 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdTbl.cs b/src/ARMeilleure/Decoders/OpCodeSimdTbl.cs new file mode 100644 index 00000000..3a7ef6ab --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdTbl.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdTbl : OpCodeSimdReg + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdTbl(inst, address, opCode); + + public OpCodeSimdTbl(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Size = ((opCode >> 13) & 3) + 1; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSystem.cs b/src/ARMeilleure/Decoders/OpCodeSystem.cs new file mode 100644 index 00000000..21513415 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSystem.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSystem : OpCode + { + public int Rt { get; } + public int Op2 { get; } + public int CRm { get; } + public int CRn { get; } + public int Op1 { get; } + public int Op0 { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSystem(inst, address, opCode); + + public OpCodeSystem(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 0) & 0x1f; + Op2 = (opCode >> 5) & 0x7; + CRm = (opCode >> 8) & 0xf; + CRn = (opCode >> 12) & 0xf; + Op1 = (opCode >> 16) & 0x7; + Op0 = ((opCode >> 19) & 0x1) | 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16.cs b/src/ARMeilleure/Decoders/OpCodeT16.cs new file mode 100644 index 00000000..de946b96 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16 : OpCode32 + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16(inst, address, opCode); + + public OpCodeT16(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Cond = Condition.Al; + + IsThumb = true; + OpCodeSizeInBytes = 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AddSubImm3.cs b/src/ARMeilleure/Decoders/OpCodeT16AddSubImm3.cs new file mode 100644 index 00000000..683d638a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AddSubImm3.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AddSubImm3 : OpCodeT16, IOpCode32AluImm + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public int Immediate { get; } + + public bool IsRotated { get; } + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AddSubImm3(inst, address, opCode); + + public OpCodeT16AddSubImm3(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rn = (opCode >> 3) & 0x7; + Immediate = (opCode >> 6) & 0x7; + IsRotated = false; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AddSubReg.cs b/src/ARMeilleure/Decoders/OpCodeT16AddSubReg.cs new file mode 100644 index 00000000..201fc8aa --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AddSubReg.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AddSubReg : OpCodeT16, IOpCode32AluReg + { + public int Rm { get; } + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AddSubReg(inst, address, opCode); + + public OpCodeT16AddSubReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rn = (opCode >> 3) & 0x7; + Rm = (opCode >> 6) & 0x7; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AddSubSp.cs b/src/ARMeilleure/Decoders/OpCodeT16AddSubSp.cs new file mode 100644 index 00000000..b66fe0cd --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AddSubSp.cs @@ -0,0 +1,23 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16AddSubSp : OpCodeT16, IOpCode32AluImm + { + public int Rd => RegisterAlias.Aarch32Sp; + public int Rn => RegisterAlias.Aarch32Sp; + + public bool? SetFlags => false; + + public int Immediate { get; } + + public bool IsRotated => false; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AddSubSp(inst, address, opCode); + + public OpCodeT16AddSubSp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = ((opCode >> 0) & 0x7f) << 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16Adr.cs b/src/ARMeilleure/Decoders/OpCodeT16Adr.cs new file mode 100644 index 00000000..03abd499 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16Adr.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16Adr : OpCodeT16, IOpCode32Adr + { + public int Rd { get; } + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16Adr(inst, address, opCode); + + public OpCodeT16Adr(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 7; + + int imm = (opCode & 0xff) << 2; + Immediate = (int)(GetPc() & 0xfffffffc) + imm; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluImm8.cs b/src/ARMeilleure/Decoders/OpCodeT16AluImm8.cs new file mode 100644 index 00000000..122698d7 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluImm8.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluImm8 : OpCodeT16, IOpCode32AluImm + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public int Immediate { get; } + + public bool IsRotated { get; } + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluImm8(inst, address, opCode); + + public OpCodeT16AluImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 0x7; + Rn = (opCode >> 8) & 0x7; + Immediate = (opCode >> 0) & 0xff; + IsRotated = false; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluImmZero.cs b/src/ARMeilleure/Decoders/OpCodeT16AluImmZero.cs new file mode 100644 index 00000000..f67a75f9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluImmZero.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluImmZero : OpCodeT16, IOpCode32AluImm + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public int Immediate { get; } + + public bool IsRotated { get; } + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluImmZero(inst, address, opCode); + + public OpCodeT16AluImmZero(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rn = (opCode >> 3) & 0x7; + Immediate = 0; + IsRotated = false; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluRegHigh.cs b/src/ARMeilleure/Decoders/OpCodeT16AluRegHigh.cs new file mode 100644 index 00000000..5458f65f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluRegHigh.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluRegHigh : OpCodeT16, IOpCode32AluReg + { + public int Rm { get; } + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => false; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluRegHigh(inst, address, opCode); + + public OpCodeT16AluRegHigh(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = ((opCode >> 0) & 0x7) | ((opCode >> 4) & 0x8); + Rn = ((opCode >> 0) & 0x7) | ((opCode >> 4) & 0x8); + Rm = (opCode >> 3) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluRegLow.cs b/src/ARMeilleure/Decoders/OpCodeT16AluRegLow.cs new file mode 100644 index 00000000..f86f48bd --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluRegLow.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluRegLow : OpCodeT16, IOpCode32AluReg + { + public int Rm { get; } + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluRegLow(inst, address, opCode); + + public OpCodeT16AluRegLow(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rn = (opCode >> 0) & 0x7; + Rm = (opCode >> 3) & 0x7; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluUx.cs b/src/ARMeilleure/Decoders/OpCodeT16AluUx.cs new file mode 100644 index 00000000..11d3a8fe --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluUx.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluUx : OpCodeT16, IOpCode32AluUx + { + public int Rm { get; } + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => false; + + public int RotateBits => 0; + public bool Add => false; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluUx(inst, address, opCode); + + public OpCodeT16AluUx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rm = (opCode >> 3) & 0x7; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16BImm11.cs b/src/ARMeilleure/Decoders/OpCodeT16BImm11.cs new file mode 100644 index 00000000..5ed8a4e6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16BImm11.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16BImm11 : OpCodeT16, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BImm11(inst, address, opCode); + + public OpCodeT16BImm11(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm = (opCode << 21) >> 20; + Immediate = GetPc() + imm; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16BImm8.cs b/src/ARMeilleure/Decoders/OpCodeT16BImm8.cs new file mode 100644 index 00000000..85318e5b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16BImm8.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16BImm8 : OpCodeT16, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BImm8(inst, address, opCode); + + public OpCodeT16BImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Cond = (Condition)((opCode >> 8) & 0xf); + + int imm = (opCode << 24) >> 23; + Immediate = GetPc() + imm; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16BImmCmp.cs b/src/ARMeilleure/Decoders/OpCodeT16BImmCmp.cs new file mode 100644 index 00000000..68ebac75 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16BImmCmp.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16BImmCmp : OpCodeT16, IOpCode32BImm + { + public int Rn { get; } + + public long Immediate { get; } + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BImmCmp(inst, address, opCode); + + public OpCodeT16BImmCmp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 0) & 0x7; + + int imm = ((opCode >> 2) & 0x3e) | ((opCode >> 3) & 0x40); + Immediate = (int)GetPc() + imm; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16BReg.cs b/src/ARMeilleure/Decoders/OpCodeT16BReg.cs new file mode 100644 index 00000000..da2a007a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16BReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16BReg : OpCodeT16, IOpCode32BReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BReg(inst, address, opCode); + + public OpCodeT16BReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 3) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16Exception.cs b/src/ARMeilleure/Decoders/OpCodeT16Exception.cs new file mode 100644 index 00000000..8ccdf09b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16Exception.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16Exception : OpCodeT16, IOpCode32Exception + { + public int Id { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16Exception(inst, address, opCode); + + public OpCodeT16Exception(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Id = opCode & 0xFF; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16IfThen.cs b/src/ARMeilleure/Decoders/OpCodeT16IfThen.cs new file mode 100644 index 00000000..ea435a79 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16IfThen.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16IfThen : OpCodeT16 + { + public Condition[] IfThenBlockConds { get; } + + public int IfThenBlockSize { get { return IfThenBlockConds.Length; } } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16IfThen(inst, address, opCode); + + public OpCodeT16IfThen(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + List conds = new(); + + int cond = (opCode >> 4) & 0xf; + int mask = opCode & 0xf; + + conds.Add((Condition)cond); + + while ((mask & 7) != 0) + { + int newLsb = (mask >> 3) & 1; + cond = (cond & 0xe) | newLsb; + mask <<= 1; + conds.Add((Condition)cond); + } + + IfThenBlockConds = conds.ToArray(); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemImm5.cs b/src/ARMeilleure/Decoders/OpCodeT16MemImm5.cs new file mode 100644 index 00000000..e9b38398 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemImm5.cs @@ -0,0 +1,48 @@ +using ARMeilleure.Instructions; +using System; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemImm5 : OpCodeT16, IOpCode32Mem + { + public int Rt { get; } + public int Rn { get; } + + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemImm5(inst, address, opCode); + + public OpCodeT16MemImm5(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 0) & 7; + Rn = (opCode >> 3) & 7; + + switch (inst.Name) + { + case InstName.Ldr: + case InstName.Ldrb: + case InstName.Ldrh: + IsLoad = true; + break; + case InstName.Str: + case InstName.Strb: + case InstName.Strh: + IsLoad = false; + break; + } + + Immediate = inst.Name switch + { + InstName.Str or InstName.Ldr => ((opCode >> 6) & 0x1f) << 2, + InstName.Strb or InstName.Ldrb => ((opCode >> 6) & 0x1f), + InstName.Strh or InstName.Ldrh => ((opCode >> 6) & 0x1f) << 1, + _ => throw new InvalidOperationException(), + }; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemLit.cs b/src/ARMeilleure/Decoders/OpCodeT16MemLit.cs new file mode 100644 index 00000000..63a452ad --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemLit.cs @@ -0,0 +1,26 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemLit : OpCodeT16, IOpCode32Mem + { + public int Rt { get; } + public int Rn => RegisterAlias.Aarch32Pc; + + public bool WBack => false; + public bool IsLoad => true; + public bool Index => true; + public bool Add => true; + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemLit(inst, address, opCode); + + public OpCodeT16MemLit(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 8) & 7; + + Immediate = (opCode & 0xff) << 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemMult.cs b/src/ARMeilleure/Decoders/OpCodeT16MemMult.cs new file mode 100644 index 00000000..92b027a6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemMult.cs @@ -0,0 +1,34 @@ +using ARMeilleure.Instructions; +using System; +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemMult : OpCodeT16, IOpCode32MemMult + { + public int Rn { get; } + public int RegisterMask { get; } + public int PostOffset { get; } + public bool IsLoad { get; } + public int Offset { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemMult(inst, address, opCode); + + public OpCodeT16MemMult(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + RegisterMask = opCode & 0xff; + Rn = (opCode >> 8) & 7; + + int regCount = BitOperations.PopCount((uint)RegisterMask); + + Offset = 0; + PostOffset = 4 * regCount; + IsLoad = inst.Name switch + { + InstName.Ldm => true, + InstName.Stm => false, + _ => throw new InvalidOperationException(), + }; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemReg.cs b/src/ARMeilleure/Decoders/OpCodeT16MemReg.cs new file mode 100644 index 00000000..17d6966b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemReg.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemReg : OpCodeT16, IOpCode32MemReg + { + public int Rm { get; } + public int Rt { get; } + public int Rn { get; } + + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + + public int Immediate => throw new System.InvalidOperationException(); + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemReg(inst, address, opCode); + + public OpCodeT16MemReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 0) & 7; + Rn = (opCode >> 3) & 7; + Rm = (opCode >> 6) & 7; + + IsLoad = ((opCode >> 9) & 7) >= 3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemSp.cs b/src/ARMeilleure/Decoders/OpCodeT16MemSp.cs new file mode 100644 index 00000000..ed42679a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemSp.cs @@ -0,0 +1,28 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemSp : OpCodeT16, IOpCode32Mem + { + public int Rt { get; } + public int Rn => RegisterAlias.Aarch32Sp; + + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemSp(inst, address, opCode); + + public OpCodeT16MemSp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 8) & 7; + + IsLoad = ((opCode >> 11) & 1) != 0; + + Immediate = ((opCode >> 0) & 0xff) << 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemStack.cs b/src/ARMeilleure/Decoders/OpCodeT16MemStack.cs new file mode 100644 index 00000000..28d5db4d --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemStack.cs @@ -0,0 +1,42 @@ +using ARMeilleure.Instructions; +using ARMeilleure.State; +using System; +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemStack : OpCodeT16, IOpCode32MemMult + { + public int Rn => RegisterAlias.Aarch32Sp; + public int RegisterMask { get; } + public int PostOffset { get; } + public bool IsLoad { get; } + public int Offset { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemStack(inst, address, opCode); + + public OpCodeT16MemStack(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int extra = (opCode >> 8) & 1; + int regCount = BitOperations.PopCount((uint)opCode & 0x1ff); + + switch (inst.Name) + { + case InstName.Push: + RegisterMask = (opCode & 0xff) | (extra << 14); + IsLoad = false; + Offset = -4 * regCount; + PostOffset = -4 * regCount; + break; + case InstName.Pop: + RegisterMask = (opCode & 0xff) | (extra << 15); + IsLoad = true; + Offset = 0; + PostOffset = 4 * regCount; + break; + default: + throw new InvalidOperationException(); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16ShiftImm.cs b/src/ARMeilleure/Decoders/OpCodeT16ShiftImm.cs new file mode 100644 index 00000000..18e7b9e2 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16ShiftImm.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16ShiftImm : OpCodeT16, IOpCode32AluRsImm + { + public int Rd { get; } + public int Rn { get; } + public int Rm { get; } + + public int Immediate { get; } + public ShiftType ShiftType { get; } + + public bool? SetFlags => null; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16ShiftImm(inst, address, opCode); + + public OpCodeT16ShiftImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rm = (opCode >> 3) & 0x7; + Immediate = (opCode >> 6) & 0x1F; + ShiftType = (ShiftType)((opCode >> 11) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16ShiftReg.cs b/src/ARMeilleure/Decoders/OpCodeT16ShiftReg.cs new file mode 100644 index 00000000..ce47dfb5 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16ShiftReg.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16ShiftReg : OpCodeT16, IOpCode32AluRsReg + { + public int Rm { get; } + public int Rs { get; } + public int Rd { get; } + + public int Rn { get; } + + public ShiftType ShiftType { get; } + + public bool? SetFlags => null; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16ShiftReg(inst, address, opCode); + + public OpCodeT16ShiftReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 7; + Rm = (opCode >> 0) & 7; + Rn = (opCode >> 3) & 7; + Rs = (opCode >> 3) & 7; + + ShiftType = (ShiftType)(((opCode >> 6) & 1) | ((opCode >> 7) & 2)); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16SpRel.cs b/src/ARMeilleure/Decoders/OpCodeT16SpRel.cs new file mode 100644 index 00000000..d737f5bd --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16SpRel.cs @@ -0,0 +1,24 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16SpRel : OpCodeT16, IOpCode32AluImm + { + public int Rd { get; } + public int Rn => RegisterAlias.Aarch32Sp; + + public bool? SetFlags => false; + + public int Immediate { get; } + + public bool IsRotated => false; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16SpRel(inst, address, opCode); + + public OpCodeT16SpRel(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 0x7; + Immediate = ((opCode >> 0) & 0xff) << 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32.cs b/src/ARMeilleure/Decoders/OpCodeT32.cs new file mode 100644 index 00000000..87a0520d --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32 : OpCode32 + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32(inst, address, opCode); + + public OpCodeT32(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Cond = Condition.Al; + + IsThumb = true; + OpCodeSizeInBytes = 4; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32Alu.cs b/src/ARMeilleure/Decoders/OpCodeT32Alu.cs new file mode 100644 index 00000000..cdef007a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32Alu.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32Alu : OpCodeT32, IOpCode32Alu + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32Alu(inst, address, opCode); + + public OpCodeT32Alu(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 0xf; + Rn = (opCode >> 16) & 0xf; + + SetFlags = ((opCode >> 20) & 1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluBf.cs b/src/ARMeilleure/Decoders/OpCodeT32AluBf.cs new file mode 100644 index 00000000..57ad422f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluBf.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluBf : OpCodeT32, IOpCode32AluBf + { + public int Rd { get; } + public int Rn { get; } + + public int Msb { get; } + public int Lsb { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluBf(inst, address, opCode); + + public OpCodeT32AluBf(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 0xf; + Rn = (opCode >> 16) & 0xf; + + Msb = (opCode >> 0) & 0x1f; + Lsb = ((opCode >> 6) & 0x3) | ((opCode >> 10) & 0x1c); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluImm.cs b/src/ARMeilleure/Decoders/OpCodeT32AluImm.cs new file mode 100644 index 00000000..ce88964c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluImm.cs @@ -0,0 +1,38 @@ +using ARMeilleure.Common; +using System.Runtime.Intrinsics; + +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluImm : OpCodeT32Alu, IOpCode32AluImm + { + public int Immediate { get; } + + public bool IsRotated { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluImm(inst, address, opCode); + + private static readonly Vector128 _factor = Vector128.Create(1, 0x00010001, 0x01000100, 0x01010101); + + public OpCodeT32AluImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm8 = (opCode >> 0) & 0xff; + int imm3 = (opCode >> 12) & 7; + int imm1 = (opCode >> 26) & 1; + + int imm12 = imm8 | (imm3 << 8) | (imm1 << 11); + + if ((imm12 >> 10) == 0) + { + Immediate = imm8 * _factor.GetElement((imm12 >> 8) & 3); + IsRotated = false; + } + else + { + int shift = imm12 >> 7; + + Immediate = BitUtils.RotateRight(0x80 | (imm12 & 0x7f), shift, 32); + IsRotated = shift != 0; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluImm12.cs b/src/ARMeilleure/Decoders/OpCodeT32AluImm12.cs new file mode 100644 index 00000000..12b65a10 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluImm12.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluImm12 : OpCodeT32Alu, IOpCode32AluImm + { + public int Immediate { get; } + + public bool IsRotated => false; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluImm12(inst, address, opCode); + + public OpCodeT32AluImm12(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = (opCode & 0xff) | ((opCode >> 4) & 0x700) | ((opCode >> 15) & 0x800); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluMla.cs b/src/ARMeilleure/Decoders/OpCodeT32AluMla.cs new file mode 100644 index 00000000..6cb604da --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluMla.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluMla : OpCodeT32, IOpCode32AluMla + { + public int Rn { get; } + public int Rm { get; } + public int Ra { get; } + public int Rd { get; } + + public bool NHigh { get; } + public bool MHigh { get; } + public bool R { get; } + public bool? SetFlags => false; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluMla(inst, address, opCode); + + public OpCodeT32AluMla(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Rd = (opCode >> 8) & 0xf; + Ra = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + R = (opCode & (1 << 4)) != 0; + + MHigh = ((opCode >> 4) & 0x1) == 1; + NHigh = ((opCode >> 5) & 0x1) == 1; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluReg.cs b/src/ARMeilleure/Decoders/OpCodeT32AluReg.cs new file mode 100644 index 00000000..4ac98347 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluReg : OpCodeT32Alu, IOpCode32AluReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluReg(inst, address, opCode); + + public OpCodeT32AluReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluRsImm.cs b/src/ARMeilleure/Decoders/OpCodeT32AluRsImm.cs new file mode 100644 index 00000000..dad0d957 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluRsImm.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluRsImm : OpCodeT32Alu, IOpCode32AluRsImm + { + public int Rm { get; } + public int Immediate { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluRsImm(inst, address, opCode); + + public OpCodeT32AluRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Immediate = ((opCode >> 6) & 3) | ((opCode >> 10) & 0x1c); + + ShiftType = (ShiftType)((opCode >> 4) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluUmull.cs b/src/ARMeilleure/Decoders/OpCodeT32AluUmull.cs new file mode 100644 index 00000000..a1b2e612 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluUmull.cs @@ -0,0 +1,28 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluUmull : OpCodeT32, IOpCode32AluUmull + { + public int RdLo { get; } + public int RdHi { get; } + public int Rn { get; } + public int Rm { get; } + + public bool NHigh { get; } + public bool MHigh { get; } + + public bool? SetFlags => false; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluUmull(inst, address, opCode); + + public OpCodeT32AluUmull(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + RdHi = (opCode >> 8) & 0xf; + RdLo = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + MHigh = ((opCode >> 4) & 0x1) == 1; + NHigh = ((opCode >> 5) & 0x1) == 1; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluUx.cs b/src/ARMeilleure/Decoders/OpCodeT32AluUx.cs new file mode 100644 index 00000000..861dc904 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluUx.cs @@ -0,0 +1,18 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluUx : OpCodeT32AluReg, IOpCode32AluUx + { + public int Rotate { get; } + public int RotateBits => Rotate * 8; + public bool Add => Rn != RegisterAlias.Aarch32Pc; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluUx(inst, address, opCode); + + public OpCodeT32AluUx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rotate = (opCode >> 4) & 0x3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32BImm20.cs b/src/ARMeilleure/Decoders/OpCodeT32BImm20.cs new file mode 100644 index 00000000..793f8262 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32BImm20.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32BImm20 : OpCodeT32, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm20(inst, address, opCode); + + public OpCodeT32BImm20(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + uint pc = GetPc(); + + int imm11 = (opCode >> 0) & 0x7ff; + int j2 = (opCode >> 11) & 1; + int j1 = (opCode >> 13) & 1; + int imm6 = (opCode >> 16) & 0x3f; + int s = (opCode >> 26) & 1; + + int imm32 = imm11 | (imm6 << 11) | (j1 << 17) | (j2 << 18) | (s << 19); + imm32 = (imm32 << 13) >> 12; + + Immediate = pc + imm32; + + Cond = (Condition)((opCode >> 22) & 0xf); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32BImm24.cs b/src/ARMeilleure/Decoders/OpCodeT32BImm24.cs new file mode 100644 index 00000000..d35ab8a4 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32BImm24.cs @@ -0,0 +1,35 @@ +using ARMeilleure.Instructions; + +namespace ARMeilleure.Decoders +{ + class OpCodeT32BImm24 : OpCodeT32, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm24(inst, address, opCode); + + public OpCodeT32BImm24(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + uint pc = GetPc(); + + if (inst.Name == InstName.Blx) + { + pc &= ~3u; + } + + int imm11 = (opCode >> 0) & 0x7ff; + int j2 = (opCode >> 11) & 1; + int j1 = (opCode >> 13) & 1; + int imm10 = (opCode >> 16) & 0x3ff; + int s = (opCode >> 26) & 1; + + int i1 = j1 ^ s ^ 1; + int i2 = j2 ^ s ^ 1; + + int imm32 = imm11 | (imm10 << 11) | (i2 << 21) | (i1 << 22) | (s << 23); + imm32 = (imm32 << 8) >> 7; + + Immediate = pc + imm32; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemImm12.cs b/src/ARMeilleure/Decoders/OpCodeT32MemImm12.cs new file mode 100644 index 00000000..aac8dbfb --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemImm12.cs @@ -0,0 +1,25 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemImm12 : OpCodeT32, IOpCode32Mem + { + public int Rt { get; } + public int Rn { get; } + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm12(inst, address, opCode); + + public OpCodeT32MemImm12(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + Immediate = opCode & 0xfff; + + IsLoad = ((opCode >> 20) & 1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemImm8.cs b/src/ARMeilleure/Decoders/OpCodeT32MemImm8.cs new file mode 100644 index 00000000..d80ce86c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemImm8.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemImm8 : OpCodeT32, IOpCode32Mem + { + public int Rt { get; } + public int Rn { get; } + public bool WBack { get; } + public bool IsLoad { get; } + public bool Index { get; } + public bool Add { get; } + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm8(inst, address, opCode); + + public OpCodeT32MemImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + Index = ((opCode >> 10) & 1) != 0; + Add = ((opCode >> 9) & 1) != 0; + WBack = ((opCode >> 8) & 1) != 0; + + Immediate = opCode & 0xff; + + IsLoad = ((opCode >> 20) & 1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemImm8D.cs b/src/ARMeilleure/Decoders/OpCodeT32MemImm8D.cs new file mode 100644 index 00000000..51f5042f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemImm8D.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemImm8D : OpCodeT32, IOpCode32Mem + { + public int Rt { get; } + public int Rt2 { get; } + public int Rn { get; } + public bool WBack { get; } + public bool IsLoad { get; } + public bool Index { get; } + public bool Add { get; } + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm8D(inst, address, opCode); + + public OpCodeT32MemImm8D(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt2 = (opCode >> 8) & 0xf; + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + Index = ((opCode >> 24) & 1) != 0; + Add = ((opCode >> 23) & 1) != 0; + WBack = ((opCode >> 21) & 1) != 0; + + Immediate = (opCode & 0xff) << 2; + + IsLoad = ((opCode >> 20) & 1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemLdEx.cs b/src/ARMeilleure/Decoders/OpCodeT32MemLdEx.cs new file mode 100644 index 00000000..c8eb36b3 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemLdEx.cs @@ -0,0 +1,26 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemLdEx : OpCodeT32, IOpCode32MemEx + { + public int Rd => 0; + public int Rt { get; } + public int Rt2 { get; } + public int Rn { get; } + + public bool WBack => false; + public bool IsLoad => true; + public bool Index => false; + public bool Add => false; + + public int Immediate => 0; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemLdEx(inst, address, opCode); + + public OpCodeT32MemLdEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt2 = (opCode >> 8) & 0xf; + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemMult.cs b/src/ARMeilleure/Decoders/OpCodeT32MemMult.cs new file mode 100644 index 00000000..d155842a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemMult.cs @@ -0,0 +1,52 @@ +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemMult : OpCodeT32, IOpCode32MemMult + { + public int Rn { get; } + + public int RegisterMask { get; } + public int Offset { get; } + public int PostOffset { get; } + + public bool IsLoad { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemMult(inst, address, opCode); + + public OpCodeT32MemMult(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 16) & 0xf; + + bool isLoad = (opCode & (1 << 20)) != 0; + bool w = (opCode & (1 << 21)) != 0; + bool u = (opCode & (1 << 23)) != 0; + bool p = (opCode & (1 << 24)) != 0; + + RegisterMask = opCode & 0xffff; + + int regsSize = BitOperations.PopCount((uint)RegisterMask) * 4; + + if (!u) + { + Offset -= regsSize; + } + + if (u == p) + { + Offset += 4; + } + + if (w) + { + PostOffset = u ? regsSize : -regsSize; + } + else + { + PostOffset = 0; + } + + IsLoad = isLoad; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemRsImm.cs b/src/ARMeilleure/Decoders/OpCodeT32MemRsImm.cs new file mode 100644 index 00000000..056d3b46 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemRsImm.cs @@ -0,0 +1,30 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemRsImm : OpCodeT32, IOpCode32MemRsImm + { + public int Rt { get; } + public int Rn { get; } + public int Rm { get; } + public ShiftType ShiftType => ShiftType.Lsl; + + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemRsImm(inst, address, opCode); + + public OpCodeT32MemRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + IsLoad = (opCode & (1 << 20)) != 0; + + Immediate = (opCode >> 4) & 3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemStEx.cs b/src/ARMeilleure/Decoders/OpCodeT32MemStEx.cs new file mode 100644 index 00000000..6a0a6bb1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemStEx.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemStEx : OpCodeT32, IOpCode32MemEx + { + public int Rd { get; } + public int Rt { get; } + public int Rt2 { get; } + public int Rn { get; } + + public bool WBack => false; + public bool IsLoad => false; + public bool Index => false; + public bool Add => false; + + public int Immediate => 0; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemStEx(inst, address, opCode); + + public OpCodeT32MemStEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0xf; + Rt2 = (opCode >> 8) & 0xf; + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MovImm16.cs b/src/ARMeilleure/Decoders/OpCodeT32MovImm16.cs new file mode 100644 index 00000000..2f871c74 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MovImm16.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MovImm16 : OpCodeT32Alu, IOpCode32AluImm16 + { + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MovImm16(inst, address, opCode); + + public OpCodeT32MovImm16(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = (opCode & 0xff) | ((opCode >> 4) & 0x700) | ((opCode >> 15) & 0x800) | ((opCode >> 4) & 0xf000); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32ShiftReg.cs b/src/ARMeilleure/Decoders/OpCodeT32ShiftReg.cs new file mode 100644 index 00000000..36055975 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32ShiftReg.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32ShiftReg : OpCodeT32Alu, IOpCode32AluRsReg + { + public int Rm => Rn; + public int Rs { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32ShiftReg(inst, address, opCode); + + public OpCodeT32ShiftReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rs = (opCode >> 0) & 0xf; + + ShiftType = (ShiftType)((opCode >> 21) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32Tb.cs b/src/ARMeilleure/Decoders/OpCodeT32Tb.cs new file mode 100644 index 00000000..0a4d2a6c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32Tb.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32Tb : OpCodeT32, IOpCode32BReg + { + public int Rm { get; } + public int Rn { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32Tb(inst, address, opCode); + + public OpCodeT32Tb(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Rn = (opCode >> 16) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeTable.cs b/src/ARMeilleure/Decoders/OpCodeTable.cs new file mode 100644 index 00000000..20d567fe --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeTable.cs @@ -0,0 +1,1528 @@ +using ARMeilleure.Instructions; +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + static class OpCodeTable + { + public delegate OpCode MakeOp(InstDescriptor inst, ulong address, int opCode); + + private const int FastLookupSize = 0x1000; + + private readonly struct InstInfo + { + public int Mask { get; } + public int Value { get; } + + public InstDescriptor Inst { get; } + + public MakeOp MakeOp { get; } + + public InstInfo(int mask, int value, InstDescriptor inst, MakeOp makeOp) + { + Mask = mask; + Value = value; + Inst = inst; + MakeOp = makeOp; + } + } + + private static readonly List _allInstA32 = new(); + private static readonly List _allInstT32 = new(); + private static readonly List _allInstA64 = new(); + + private static readonly InstInfo[][] _instA32FastLookup = new InstInfo[FastLookupSize][]; + private static readonly InstInfo[][] _instT32FastLookup = new InstInfo[FastLookupSize][]; + private static readonly InstInfo[][] _instA64FastLookup = new InstInfo[FastLookupSize][]; + + static OpCodeTable() + { +#pragma warning disable IDE0055 // Disable formatting + #region "OpCode Table (AArch64)" + // Base + SetA64("x0011010000xxxxx000000xxxxxxxxxx", InstName.Adc, InstEmit.Adc, OpCodeAluRs.Create); + SetA64("x0111010000xxxxx000000xxxxxxxxxx", InstName.Adcs, InstEmit.Adcs, OpCodeAluRs.Create); + SetA64("x00100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluImm.Create); + SetA64("00001011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluRs.Create); + SetA64("10001011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluRs.Create); + SetA64("x0001011001xxxxxxxx0xxxxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluRx.Create); + SetA64("x0001011001xxxxxxxx100xxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluRx.Create); + SetA64("x01100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluImm.Create); + SetA64("00101011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluRs.Create); + SetA64("10101011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluRs.Create); + SetA64("x0101011001xxxxxxxx0xxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluRx.Create); + SetA64("x0101011001xxxxxxxx100xxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluRx.Create); + SetA64("0xx10000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Adr, InstEmit.Adr, OpCodeAdr.Create); + SetA64("1xx10000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Adrp, InstEmit.Adrp, OpCodeAdr.Create); + SetA64("0001001000xxxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit.And, OpCodeAluImm.Create); + SetA64("100100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit.And, OpCodeAluImm.Create); + SetA64("00001010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.And, InstEmit.And, OpCodeAluRs.Create); + SetA64("10001010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit.And, OpCodeAluRs.Create); + SetA64("0111001000xxxxxxxxxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, OpCodeAluImm.Create); + SetA64("111100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, OpCodeAluImm.Create); + SetA64("01101010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, OpCodeAluRs.Create); + SetA64("11101010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, OpCodeAluRs.Create); + SetA64("x0011010110xxxxx001010xxxxxxxxxx", InstName.Asrv, InstEmit.Asrv, OpCodeAluRs.Create); + SetA64("000101xxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.B, InstEmit.B, OpCodeBImmAl.Create); + SetA64("01010100xxxxxxxxxxxxxxxxxxx0xxxx", InstName.B_Cond, InstEmit.B_Cond, OpCodeBImmCond.Create); + SetA64("00110011000xxxxx0xxxxxxxxxxxxxxx", InstName.Bfm, InstEmit.Bfm, OpCodeBfm.Create); + SetA64("1011001101xxxxxxxxxxxxxxxxxxxxxx", InstName.Bfm, InstEmit.Bfm, OpCodeBfm.Create); + SetA64("00001010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit.Bic, OpCodeAluRs.Create); + SetA64("10001010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Bic, InstEmit.Bic, OpCodeAluRs.Create); + SetA64("01101010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Bics, InstEmit.Bics, OpCodeAluRs.Create); + SetA64("11101010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Bics, InstEmit.Bics, OpCodeAluRs.Create); + SetA64("100101xxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bl, InstEmit.Bl, OpCodeBImmAl.Create); + SetA64("1101011000111111000000xxxxx00000", InstName.Blr, InstEmit.Blr, OpCodeBReg.Create); + SetA64("1101011000011111000000xxxxx00000", InstName.Br, InstEmit.Br, OpCodeBReg.Create); + SetA64("11010100001xxxxxxxxxxxxxxxx00000", InstName.Brk, InstEmit.Brk, OpCodeException.Create); + SetA64("x0110101xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cbnz, InstEmit.Cbnz, OpCodeBImmCmp.Create); + SetA64("x0110100xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cbz, InstEmit.Cbz, OpCodeBImmCmp.Create); + SetA64("x0111010010xxxxxxxxx10xxxxx0xxxx", InstName.Ccmn, InstEmit.Ccmn, OpCodeCcmpImm.Create); + SetA64("x0111010010xxxxxxxxx00xxxxx0xxxx", InstName.Ccmn, InstEmit.Ccmn, OpCodeCcmpReg.Create); + SetA64("x1111010010xxxxxxxxx10xxxxx0xxxx", InstName.Ccmp, InstEmit.Ccmp, OpCodeCcmpImm.Create); + SetA64("x1111010010xxxxxxxxx00xxxxx0xxxx", InstName.Ccmp, InstEmit.Ccmp, OpCodeCcmpReg.Create); + SetA64("11010101000000110011xxxx01011111", InstName.Clrex, InstEmit.Clrex, OpCodeSystem.Create); + SetA64("x101101011000000000101xxxxxxxxxx", InstName.Cls, InstEmit.Cls, OpCodeAlu.Create); + SetA64("x101101011000000000100xxxxxxxxxx", InstName.Clz, InstEmit.Clz, OpCodeAlu.Create); + SetA64("00011010110xxxxx010000xxxxxxxxxx", InstName.Crc32b, InstEmit.Crc32b, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010001xxxxxxxxxx", InstName.Crc32h, InstEmit.Crc32h, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010010xxxxxxxxxx", InstName.Crc32w, InstEmit.Crc32w, OpCodeAluBinary.Create); + SetA64("10011010110xxxxx010011xxxxxxxxxx", InstName.Crc32x, InstEmit.Crc32x, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010100xxxxxxxxxx", InstName.Crc32cb, InstEmit.Crc32cb, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010101xxxxxxxxxx", InstName.Crc32ch, InstEmit.Crc32ch, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010110xxxxxxxxxx", InstName.Crc32cw, InstEmit.Crc32cw, OpCodeAluBinary.Create); + SetA64("10011010110xxxxx010111xxxxxxxxxx", InstName.Crc32cx, InstEmit.Crc32cx, OpCodeAluBinary.Create); + SetA64("11010101000000110010001010011111", InstName.Csdb, InstEmit.Csdb, OpCodeSystem.Create); + SetA64("x0011010100xxxxxxxxx00xxxxxxxxxx", InstName.Csel, InstEmit.Csel, OpCodeCsel.Create); + SetA64("x0011010100xxxxxxxxx01xxxxxxxxxx", InstName.Csinc, InstEmit.Csinc, OpCodeCsel.Create); + SetA64("x1011010100xxxxxxxxx00xxxxxxxxxx", InstName.Csinv, InstEmit.Csinv, OpCodeCsel.Create); + SetA64("x1011010100xxxxxxxxx01xxxxxxxxxx", InstName.Csneg, InstEmit.Csneg, OpCodeCsel.Create); + SetA64("11010101000000110011xxxx10111111", InstName.Dmb, InstEmit.Dmb, OpCodeSystem.Create); + SetA64("11010101000000110011xxxx10011111", InstName.Dsb, InstEmit.Dsb, OpCodeSystem.Create); + SetA64("01001010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Eon, InstEmit.Eon, OpCodeAluRs.Create); + SetA64("11001010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Eon, InstEmit.Eon, OpCodeAluRs.Create); + SetA64("0101001000xxxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluImm.Create); + SetA64("110100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluImm.Create); + SetA64("01001010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluRs.Create); + 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); + SetA64("xx001000010xxxxx1xxxxxxxxxxxxxxx", InstName.Ldaxr, InstEmit.Ldaxr, OpCodeMemEx.Create); + SetA64("<<10100xx1xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldp, InstEmit.Ldp, OpCodeMemPair.Create); + SetA64("xx111000010xxxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeMemImm.Create); + SetA64("xx11100101xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeMemImm.Create); + SetA64("xx111000011xxxxxxxxx10xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeMemReg.Create); + SetA64("xx011000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr_Literal, InstEmit.Ldr_Literal, OpCodeMemLit.Create); + SetA64("0x1110001x0xxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemImm.Create); + SetA64("0x1110011xxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemImm.Create); + SetA64("10111000100xxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemImm.Create); + SetA64("1011100110xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemImm.Create); + SetA64("0x1110001x1xxxxxxxxx10xxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemReg.Create); + SetA64("10111000101xxxxxxxxx10xxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemReg.Create); + SetA64("xx001000010xxxxx0xxxxxxxxxxxxxxx", InstName.Ldxr, InstEmit.Ldxr, OpCodeMemEx.Create); + SetA64("1x001000011xxxxx0xxxxxxxxxxxxxxx", InstName.Ldxp, InstEmit.Ldxp, OpCodeMemEx.Create); + SetA64("x0011010110xxxxx001000xxxxxxxxxx", InstName.Lslv, InstEmit.Lslv, OpCodeAluRs.Create); + SetA64("x0011010110xxxxx001001xxxxxxxxxx", InstName.Lsrv, InstEmit.Lsrv, OpCodeAluRs.Create); + SetA64("x0011011000xxxxx0xxxxxxxxxxxxxxx", InstName.Madd, InstEmit.Madd, OpCodeMul.Create); + SetA64("0111001010xxxxxxxxxxxxxxxxxxxxxx", InstName.Movk, InstEmit.Movk, OpCodeMov.Create); + SetA64("111100101xxxxxxxxxxxxxxxxxxxxxxx", InstName.Movk, InstEmit.Movk, OpCodeMov.Create); + SetA64("0001001010xxxxxxxxxxxxxxxxxxxxxx", InstName.Movn, InstEmit.Movn, OpCodeMov.Create); + SetA64("100100101xxxxxxxxxxxxxxxxxxxxxxx", InstName.Movn, InstEmit.Movn, OpCodeMov.Create); + SetA64("0101001010xxxxxxxxxxxxxxxxxxxxxx", InstName.Movz, InstEmit.Movz, OpCodeMov.Create); + SetA64("110100101xxxxxxxxxxxxxxxxxxxxxxx", InstName.Movz, InstEmit.Movz, OpCodeMov.Create); + SetA64("110101010011xxxxxxxxxxxxxxxxxxxx", InstName.Mrs, InstEmit.Mrs, OpCodeSystem.Create); + SetA64("110101010001xxxxxxxxxxxxxxxxxxxx", InstName.Msr, InstEmit.Msr, OpCodeSystem.Create); + SetA64("x0011011000xxxxx1xxxxxxxxxxxxxxx", InstName.Msub, InstEmit.Msub, OpCodeMul.Create); + SetA64("11010101000000110010000000011111", InstName.Nop, InstEmit.Nop, OpCodeSystem.Create); + SetA64("00101010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Orn, InstEmit.Orn, OpCodeAluRs.Create); + SetA64("10101010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Orn, InstEmit.Orn, OpCodeAluRs.Create); + SetA64("0011001000xxxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, OpCodeAluImm.Create); + SetA64("101100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, OpCodeAluImm.Create); + SetA64("00101010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, OpCodeAluRs.Create); + SetA64("10101010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, OpCodeAluRs.Create); + SetA64("1111100110xxxxxxxxxxxxxxxxxxxxxx", InstName.Prfm, InstEmit.Prfm, OpCodeMemImm.Create); // immediate + SetA64("11111000100xxxxxxxxx00xxxxxxxxxx", InstName.Prfm, InstEmit.Prfm, OpCodeMemImm.Create); // prfum (unscaled offset) + SetA64("11011000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Prfm, InstEmit.Prfm, OpCodeMemLit.Create); // literal + SetA64("11111000101xxxxxxxxx10xxxxxxxxxx", InstName.Prfm, InstEmit.Prfm, OpCodeMemReg.Create); // register + SetA64("x101101011000000000000xxxxxxxxxx", InstName.Rbit, InstEmit.Rbit, OpCodeAlu.Create); + SetA64("1101011001011111000000xxxxx00000", InstName.Ret, InstEmit.Ret, OpCodeBReg.Create); + SetA64("x101101011000000000001xxxxxxxxxx", InstName.Rev16, InstEmit.Rev16, OpCodeAlu.Create); + SetA64("x101101011000000000010xxxxxxxxxx", InstName.Rev32, InstEmit.Rev32, OpCodeAlu.Create); + SetA64("1101101011000000000011xxxxxxxxxx", InstName.Rev64, InstEmit.Rev64, OpCodeAlu.Create); + SetA64("x0011010110xxxxx001011xxxxxxxxxx", InstName.Rorv, InstEmit.Rorv, OpCodeAluRs.Create); + SetA64("x1011010000xxxxx000000xxxxxxxxxx", InstName.Sbc, InstEmit.Sbc, OpCodeAluRs.Create); + SetA64("x1111010000xxxxx000000xxxxxxxxxx", InstName.Sbcs, InstEmit.Sbcs, OpCodeAluRs.Create); + SetA64("00010011000xxxxx0xxxxxxxxxxxxxxx", InstName.Sbfm, InstEmit.Sbfm, OpCodeBfm.Create); + SetA64("1001001101xxxxxxxxxxxxxxxxxxxxxx", InstName.Sbfm, InstEmit.Sbfm, OpCodeBfm.Create); + SetA64("x0011010110xxxxx000011xxxxxxxxxx", InstName.Sdiv, InstEmit.Sdiv, OpCodeAluBinary.Create); + SetA64("11010101000000110010000010011111", InstName.Sev, InstEmit.Nop, OpCodeSystem.Create); + SetA64("11010101000000110010000010111111", InstName.Sevl, InstEmit.Nop, OpCodeSystem.Create); + SetA64("10011011001xxxxx0xxxxxxxxxxxxxxx", InstName.Smaddl, InstEmit.Smaddl, OpCodeMul.Create); + SetA64("10011011001xxxxx1xxxxxxxxxxxxxxx", InstName.Smsubl, InstEmit.Smsubl, OpCodeMul.Create); + SetA64("10011011010xxxxx0xxxxxxxxxxxxxxx", InstName.Smulh, InstEmit.Smulh, OpCodeMul.Create); + SetA64("xx001000100xxxxx1xxxxxxxxxxxxxxx", InstName.Stlr, InstEmit.Stlr, OpCodeMemEx.Create); + SetA64("1x001000001xxxxx1xxxxxxxxxxxxxxx", InstName.Stlxp, InstEmit.Stlxp, OpCodeMemEx.Create); + SetA64("xx001000000xxxxx1xxxxxxxxxxxxxxx", InstName.Stlxr, InstEmit.Stlxr, OpCodeMemEx.Create); + SetA64("x010100xx0xxxxxxxxxxxxxxxxxxxxxx", InstName.Stp, InstEmit.Stp, OpCodeMemPair.Create); + SetA64("xx111000000xxxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeMemImm.Create); + SetA64("xx11100100xxxxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeMemImm.Create); + SetA64("xx111000001xxxxxxxxx10xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeMemReg.Create); + SetA64("1x001000001xxxxx0xxxxxxxxxxxxxxx", InstName.Stxp, InstEmit.Stxp, OpCodeMemEx.Create); + SetA64("xx001000000xxxxx0xxxxxxxxxxxxxxx", InstName.Stxr, InstEmit.Stxr, OpCodeMemEx.Create); + SetA64("x10100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluImm.Create); + SetA64("01001011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluRs.Create); + SetA64("11001011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluRs.Create); + SetA64("x1001011001xxxxxxxx0xxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluRx.Create); + SetA64("x1001011001xxxxxxxx100xxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluRx.Create); + SetA64("x11100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluImm.Create); + SetA64("01101011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluRs.Create); + SetA64("11101011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluRs.Create); + SetA64("x1101011001xxxxxxxx0xxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluRx.Create); + SetA64("x1101011001xxxxxxxx100xxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluRx.Create); + SetA64("11010100000xxxxxxxxxxxxxxxx00001", InstName.Svc, InstEmit.Svc, OpCodeException.Create); + SetA64("1101010100001xxxxxxxxxxxxxxxxxxx", InstName.Sys, InstEmit.Sys, OpCodeSystem.Create); + SetA64("x0110111xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tbnz, InstEmit.Tbnz, OpCodeBImmTest.Create); + SetA64("x0110110xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tbz, InstEmit.Tbz, OpCodeBImmTest.Create); + SetA64("01010011000xxxxx0xxxxxxxxxxxxxxx", InstName.Ubfm, InstEmit.Ubfm, OpCodeBfm.Create); + SetA64("1101001101xxxxxxxxxxxxxxxxxxxxxx", InstName.Ubfm, InstEmit.Ubfm, OpCodeBfm.Create); + SetA64("x0011010110xxxxx000010xxxxxxxxxx", InstName.Udiv, InstEmit.Udiv, OpCodeAluBinary.Create); + SetA64("10011011101xxxxx0xxxxxxxxxxxxxxx", InstName.Umaddl, InstEmit.Umaddl, OpCodeMul.Create); + SetA64("10011011101xxxxx1xxxxxxxxxxxxxxx", InstName.Umsubl, InstEmit.Umsubl, OpCodeMul.Create); + SetA64("10011011110xxxxx0xxxxxxxxxxxxxxx", InstName.Umulh, InstEmit.Umulh, OpCodeMul.Create); + SetA64("11010101000000110010000001011111", InstName.Wfe, InstEmit.Nop, OpCodeSystem.Create); + SetA64("11010101000000110010000001111111", InstName.Wfi, InstEmit.Nop, OpCodeSystem.Create); + SetA64("11010101000000110010000000111111", InstName.Yield, InstEmit.Nop, OpCodeSystem.Create); + + // FP & SIMD + SetA64("0101111011100000101110xxxxxxxxxx", InstName.Abs_S, InstEmit.Abs_S, OpCodeSimd.Create); + SetA64("0>001110<<100000101110xxxxxxxxxx", InstName.Abs_V, InstEmit.Abs_V, OpCodeSimd.Create); + SetA64("01011110111xxxxx100001xxxxxxxxxx", InstName.Add_S, InstEmit.Add_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx100001xxxxxxxxxx", InstName.Add_V, InstEmit.Add_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx010000xxxxxxxxxx", InstName.Addhn_V, InstEmit.Addhn_V, OpCodeSimdReg.Create); + SetA64("0101111011110001101110xxxxxxxxxx", InstName.Addp_S, InstEmit.Addp_S, OpCodeSimd.Create); + SetA64("0>001110<<1xxxxx101111xxxxxxxxxx", InstName.Addp_V, InstEmit.Addp_V, OpCodeSimdReg.Create); + SetA64("000011100x110001101110xxxxxxxxxx", InstName.Addv_V, InstEmit.Addv_V, OpCodeSimd.Create); + SetA64("01001110<<110001101110xxxxxxxxxx", InstName.Addv_V, InstEmit.Addv_V, OpCodeSimd.Create); + SetA64("0100111000101000010110xxxxxxxxxx", InstName.Aesd_V, InstEmit.Aesd_V, OpCodeSimd.Create); + SetA64("0100111000101000010010xxxxxxxxxx", InstName.Aese_V, InstEmit.Aese_V, OpCodeSimd.Create); + SetA64("0100111000101000011110xxxxxxxxxx", InstName.Aesimc_V, InstEmit.Aesimc_V, OpCodeSimd.Create); + SetA64("0100111000101000011010xxxxxxxxxx", InstName.Aesmc_V, InstEmit.Aesmc_V, OpCodeSimd.Create); + SetA64("0x001110001xxxxx000111xxxxxxxxxx", InstName.And_V, InstEmit.And_V, OpCodeSimdReg.Create); + SetA64("0x001110011xxxxx000111xxxxxxxxxx", InstName.Bic_V, InstEmit.Bic_V, OpCodeSimdReg.Create); + SetA64("0x10111100000xxx0xx101xxxxxxxxxx", InstName.Bic_Vi, InstEmit.Bic_Vi, OpCodeSimdImm.Create); + SetA64("0x10111100000xxx10x101xxxxxxxxxx", InstName.Bic_Vi, InstEmit.Bic_Vi, OpCodeSimdImm.Create); + SetA64("0x101110111xxxxx000111xxxxxxxxxx", InstName.Bif_V, InstEmit.Bif_V, OpCodeSimdReg.Create); + SetA64("0x101110101xxxxx000111xxxxxxxxxx", InstName.Bit_V, InstEmit.Bit_V, OpCodeSimdReg.Create); + SetA64("0x101110011xxxxx000111xxxxxxxxxx", InstName.Bsl_V, InstEmit.Bsl_V, OpCodeSimdReg.Create); + SetA64("0x001110<<100000010010xxxxxxxxxx", InstName.Cls_V, InstEmit.Cls_V, OpCodeSimd.Create); + SetA64("0x101110<<100000010010xxxxxxxxxx", InstName.Clz_V, InstEmit.Clz_V, OpCodeSimd.Create); + SetA64("01111110111xxxxx100011xxxxxxxxxx", InstName.Cmeq_S, InstEmit.Cmeq_S, OpCodeSimdReg.Create); + SetA64("0101111011100000100110xxxxxxxxxx", InstName.Cmeq_S, InstEmit.Cmeq_S, OpCodeSimd.Create); + SetA64("0>101110<<1xxxxx100011xxxxxxxxxx", InstName.Cmeq_V, InstEmit.Cmeq_V, OpCodeSimdReg.Create); + SetA64("0>001110<<100000100110xxxxxxxxxx", InstName.Cmeq_V, InstEmit.Cmeq_V, OpCodeSimd.Create); + SetA64("01011110111xxxxx001111xxxxxxxxxx", InstName.Cmge_S, InstEmit.Cmge_S, OpCodeSimdReg.Create); + SetA64("0111111011100000100010xxxxxxxxxx", InstName.Cmge_S, InstEmit.Cmge_S, OpCodeSimd.Create); + SetA64("0>001110<<1xxxxx001111xxxxxxxxxx", InstName.Cmge_V, InstEmit.Cmge_V, OpCodeSimdReg.Create); + SetA64("0>101110<<100000100010xxxxxxxxxx", InstName.Cmge_V, InstEmit.Cmge_V, OpCodeSimd.Create); + SetA64("01011110111xxxxx001101xxxxxxxxxx", InstName.Cmgt_S, InstEmit.Cmgt_S, OpCodeSimdReg.Create); + SetA64("0101111011100000100010xxxxxxxxxx", InstName.Cmgt_S, InstEmit.Cmgt_S, OpCodeSimd.Create); + SetA64("0>001110<<1xxxxx001101xxxxxxxxxx", InstName.Cmgt_V, InstEmit.Cmgt_V, OpCodeSimdReg.Create); + SetA64("0>001110<<100000100010xxxxxxxxxx", InstName.Cmgt_V, InstEmit.Cmgt_V, OpCodeSimd.Create); + SetA64("01111110111xxxxx001101xxxxxxxxxx", InstName.Cmhi_S, InstEmit.Cmhi_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx001101xxxxxxxxxx", InstName.Cmhi_V, InstEmit.Cmhi_V, OpCodeSimdReg.Create); + SetA64("01111110111xxxxx001111xxxxxxxxxx", InstName.Cmhs_S, InstEmit.Cmhs_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx001111xxxxxxxxxx", InstName.Cmhs_V, InstEmit.Cmhs_V, OpCodeSimdReg.Create); + SetA64("0111111011100000100110xxxxxxxxxx", InstName.Cmle_S, InstEmit.Cmle_S, OpCodeSimd.Create); + SetA64("0>101110<<100000100110xxxxxxxxxx", InstName.Cmle_V, InstEmit.Cmle_V, OpCodeSimd.Create); + SetA64("0101111011100000101010xxxxxxxxxx", InstName.Cmlt_S, InstEmit.Cmlt_S, OpCodeSimd.Create); + SetA64("0>001110<<100000101010xxxxxxxxxx", InstName.Cmlt_V, InstEmit.Cmlt_V, OpCodeSimd.Create); + SetA64("01011110111xxxxx100011xxxxxxxxxx", InstName.Cmtst_S, InstEmit.Cmtst_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx100011xxxxxxxxxx", InstName.Cmtst_V, InstEmit.Cmtst_V, OpCodeSimdReg.Create); + SetA64("0x00111000100000010110xxxxxxxxxx", InstName.Cnt_V, InstEmit.Cnt_V, OpCodeSimd.Create); + SetA64("0>001110000x<>>>000011xxxxxxxxxx", InstName.Dup_Gp, InstEmit.Dup_Gp, OpCodeSimdIns.Create); + SetA64("01011110000xxxxx000001xxxxxxxxxx", InstName.Dup_S, InstEmit.Dup_S, OpCodeSimdIns.Create); + SetA64("0>001110000x<>>>000001xxxxxxxxxx", InstName.Dup_V, InstEmit.Dup_V, OpCodeSimdIns.Create); + SetA64("0x101110001xxxxx000111xxxxxxxxxx", InstName.Eor_V, InstEmit.Eor_V, OpCodeSimdReg.Create); + SetA64("0>101110000xxxxx01011101<1xxxxx110101xxxxxxxxxx", InstName.Fabd_V, InstEmit.Fabd_V, OpCodeSimdReg.Create); + SetA64("000111100x100000110000xxxxxxxxxx", InstName.Fabs_S, InstEmit.Fabs_S, OpCodeSimd.Create); + SetA64("0>0011101<100000111110xxxxxxxxxx", InstName.Fabs_V, InstEmit.Fabs_V, OpCodeSimd.Create); + SetA64("011111100x1xxxxx111011xxxxxxxxxx", InstName.Facge_S, InstEmit.Facge_S, OpCodeSimdReg.Create); + SetA64("0>1011100<1xxxxx111011xxxxxxxxxx", InstName.Facge_V, InstEmit.Facge_V, OpCodeSimdReg.Create); + SetA64("011111101x1xxxxx111011xxxxxxxxxx", InstName.Facgt_S, InstEmit.Facgt_S, OpCodeSimdReg.Create); + SetA64("0>1011101<1xxxxx111011xxxxxxxxxx", InstName.Facgt_V, InstEmit.Facgt_V, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx001010xxxxxxxxxx", InstName.Fadd_S, InstEmit.Fadd_S, OpCodeSimdReg.Create); + SetA64("0>0011100<1xxxxx110101xxxxxxxxxx", InstName.Fadd_V, InstEmit.Fadd_V, OpCodeSimdReg.Create); + SetA64("011111100x110000110110xxxxxxxxxx", InstName.Faddp_S, InstEmit.Faddp_S, OpCodeSimd.Create); + SetA64("0>1011100<1xxxxx110101xxxxxxxxxx", InstName.Faddp_V, InstEmit.Faddp_V, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxxxxxx01xxxxx0xxxx", InstName.Fccmp_S, InstEmit.Fccmp_S, OpCodeSimdFcond.Create); + SetA64("000111100x1xxxxxxxxx01xxxxx1xxxx", InstName.Fccmpe_S, InstEmit.Fccmpe_S, OpCodeSimdFcond.Create); + SetA64("010111100x1xxxxx111001xxxxxxxxxx", InstName.Fcmeq_S, InstEmit.Fcmeq_S, OpCodeSimdReg.Create); + SetA64("010111101x100000110110xxxxxxxxxx", InstName.Fcmeq_S, InstEmit.Fcmeq_S, OpCodeSimd.Create); + SetA64("0>0011100<1xxxxx111001xxxxxxxxxx", InstName.Fcmeq_V, InstEmit.Fcmeq_V, OpCodeSimdReg.Create); + SetA64("0>0011101<100000110110xxxxxxxxxx", InstName.Fcmeq_V, InstEmit.Fcmeq_V, OpCodeSimd.Create); + SetA64("011111100x1xxxxx111001xxxxxxxxxx", InstName.Fcmge_S, InstEmit.Fcmge_S, OpCodeSimdReg.Create); + SetA64("011111101x100000110010xxxxxxxxxx", InstName.Fcmge_S, InstEmit.Fcmge_S, OpCodeSimd.Create); + SetA64("0>1011100<1xxxxx111001xxxxxxxxxx", InstName.Fcmge_V, InstEmit.Fcmge_V, OpCodeSimdReg.Create); + SetA64("0>1011101<100000110010xxxxxxxxxx", InstName.Fcmge_V, InstEmit.Fcmge_V, OpCodeSimd.Create); + SetA64("011111101x1xxxxx111001xxxxxxxxxx", InstName.Fcmgt_S, InstEmit.Fcmgt_S, OpCodeSimdReg.Create); + SetA64("010111101x100000110010xxxxxxxxxx", InstName.Fcmgt_S, InstEmit.Fcmgt_S, OpCodeSimd.Create); + SetA64("0>1011101<1xxxxx111001xxxxxxxxxx", InstName.Fcmgt_V, InstEmit.Fcmgt_V, OpCodeSimdReg.Create); + SetA64("0>0011101<100000110010xxxxxxxxxx", InstName.Fcmgt_V, InstEmit.Fcmgt_V, OpCodeSimd.Create); + SetA64("011111101x100000110110xxxxxxxxxx", InstName.Fcmle_S, InstEmit.Fcmle_S, OpCodeSimd.Create); + SetA64("0>1011101<100000110110xxxxxxxxxx", InstName.Fcmle_V, InstEmit.Fcmle_V, OpCodeSimd.Create); + SetA64("010111101x100000111010xxxxxxxxxx", InstName.Fcmlt_S, InstEmit.Fcmlt_S, OpCodeSimd.Create); + SetA64("0>0011101<100000111010xxxxxxxxxx", InstName.Fcmlt_V, InstEmit.Fcmlt_V, OpCodeSimd.Create); + SetA64("000111100x1xxxxx001000xxxxx0x000", InstName.Fcmp_S, InstEmit.Fcmp_S, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx001000xxxxx1x000", InstName.Fcmpe_S, InstEmit.Fcmpe_S, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxxxxxx11xxxxxxxxxx", InstName.Fcsel_S, InstEmit.Fcsel_S, OpCodeSimdFcond.Create); + SetA64("00011110xx10001xx10000xxxxxxxxxx", InstName.Fcvt_S, InstEmit.Fcvt_S, OpCodeSimd.Create); + SetA64("x00111100x100100000000xxxxxxxxxx", InstName.Fcvtas_Gp, InstEmit.Fcvtas_Gp, OpCodeSimdCvt.Create); + SetA64("010111100x100001110010xxxxxxxxxx", InstName.Fcvtas_S, InstEmit.Fcvtas_S, OpCodeSimd.Create); + SetA64("0>0011100<100001110010xxxxxxxxxx", InstName.Fcvtas_V, InstEmit.Fcvtas_V, OpCodeSimd.Create); + SetA64("x00111100x100101000000xxxxxxxxxx", InstName.Fcvtau_Gp, InstEmit.Fcvtau_Gp, OpCodeSimdCvt.Create); + SetA64("011111100x100001110010xxxxxxxxxx", InstName.Fcvtau_S, InstEmit.Fcvtau_S, OpCodeSimd.Create); + SetA64("0>1011100<100001110010xxxxxxxxxx", InstName.Fcvtau_V, InstEmit.Fcvtau_V, OpCodeSimd.Create); + SetA64("0x0011100x100001011110xxxxxxxxxx", InstName.Fcvtl_V, InstEmit.Fcvtl_V, OpCodeSimd.Create); + SetA64("x00111100x110000000000xxxxxxxxxx", InstName.Fcvtms_Gp, InstEmit.Fcvtms_Gp, OpCodeSimdCvt.Create); + SetA64("0>0011100<100001101110xxxxxxxxxx", InstName.Fcvtms_V, InstEmit.Fcvtms_V, OpCodeSimd.Create); + SetA64("x00111100x110001000000xxxxxxxxxx", InstName.Fcvtmu_Gp, InstEmit.Fcvtmu_Gp, OpCodeSimdCvt.Create); + SetA64("0x0011100x100001011010xxxxxxxxxx", InstName.Fcvtn_V, InstEmit.Fcvtn_V, OpCodeSimd.Create); + SetA64("x00111100x100000000000xxxxxxxxxx", InstName.Fcvtns_Gp, InstEmit.Fcvtns_Gp, OpCodeSimdCvt.Create); + SetA64("010111100x100001101010xxxxxxxxxx", InstName.Fcvtns_S, InstEmit.Fcvtns_S, OpCodeSimd.Create); + SetA64("0>0011100<100001101010xxxxxxxxxx", InstName.Fcvtns_V, InstEmit.Fcvtns_V, OpCodeSimd.Create); + SetA64("011111100x100001101010xxxxxxxxxx", InstName.Fcvtnu_S, InstEmit.Fcvtnu_S, OpCodeSimd.Create); + SetA64("0>1011100<100001101010xxxxxxxxxx", InstName.Fcvtnu_V, InstEmit.Fcvtnu_V, OpCodeSimd.Create); + SetA64("x00111100x101000000000xxxxxxxxxx", InstName.Fcvtps_Gp, InstEmit.Fcvtps_Gp, OpCodeSimdCvt.Create); + SetA64("x00111100x101001000000xxxxxxxxxx", InstName.Fcvtpu_Gp, InstEmit.Fcvtpu_Gp, OpCodeSimdCvt.Create); + SetA64("x00111100x111000000000xxxxxxxxxx", InstName.Fcvtzs_Gp, InstEmit.Fcvtzs_Gp, OpCodeSimdCvt.Create); + SetA64(">00111100x011000>xxxxxxxxxxxxxxx", InstName.Fcvtzs_Gp_Fixed, InstEmit.Fcvtzs_Gp_Fixed, OpCodeSimdCvt.Create); + SetA64("010111101x100001101110xxxxxxxxxx", InstName.Fcvtzs_S, InstEmit.Fcvtzs_S, OpCodeSimd.Create); + SetA64("0>0011101<100001101110xxxxxxxxxx", InstName.Fcvtzs_V, InstEmit.Fcvtzs_V, OpCodeSimd.Create); + SetA64("0x001111001xxxxx111111xxxxxxxxxx", InstName.Fcvtzs_V_Fixed, InstEmit.Fcvtzs_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx111111xxxxxxxxxx", InstName.Fcvtzs_V_Fixed, InstEmit.Fcvtzs_V_Fixed, OpCodeSimdShImm.Create); + SetA64("x00111100x111001000000xxxxxxxxxx", InstName.Fcvtzu_Gp, InstEmit.Fcvtzu_Gp, OpCodeSimdCvt.Create); + SetA64(">00111100x011001>xxxxxxxxxxxxxxx", InstName.Fcvtzu_Gp_Fixed, InstEmit.Fcvtzu_Gp_Fixed, OpCodeSimdCvt.Create); + SetA64("011111101x100001101110xxxxxxxxxx", InstName.Fcvtzu_S, InstEmit.Fcvtzu_S, OpCodeSimd.Create); + SetA64("0>1011101<100001101110xxxxxxxxxx", InstName.Fcvtzu_V, InstEmit.Fcvtzu_V, OpCodeSimd.Create); + SetA64("0x101111001xxxxx111111xxxxxxxxxx", InstName.Fcvtzu_V_Fixed, InstEmit.Fcvtzu_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx111111xxxxxxxxxx", InstName.Fcvtzu_V_Fixed, InstEmit.Fcvtzu_V_Fixed, OpCodeSimdShImm.Create); + SetA64("000111100x1xxxxx000110xxxxxxxxxx", InstName.Fdiv_S, InstEmit.Fdiv_S, OpCodeSimdReg.Create); + SetA64("0>1011100<1xxxxx111111xxxxxxxxxx", InstName.Fdiv_V, InstEmit.Fdiv_V, OpCodeSimdReg.Create); + SetA64("000111110x0xxxxx0xxxxxxxxxxxxxxx", InstName.Fmadd_S, InstEmit.Fmadd_S, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx010010xxxxxxxxxx", InstName.Fmax_S, InstEmit.Fmax_S, OpCodeSimdReg.Create); + SetA64("0>0011100<1xxxxx111101xxxxxxxxxx", InstName.Fmax_V, InstEmit.Fmax_V, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx011010xxxxxxxxxx", InstName.Fmaxnm_S, InstEmit.Fmaxnm_S, OpCodeSimdReg.Create); + SetA64("0>0011100<1xxxxx110001xxxxxxxxxx", InstName.Fmaxnm_V, InstEmit.Fmaxnm_V, OpCodeSimdReg.Create); + SetA64("011111100x110000110010xxxxxxxxxx", InstName.Fmaxnmp_S, InstEmit.Fmaxnmp_S, OpCodeSimd.Create); + SetA64("0>1011100<1xxxxx110001xxxxxxxxxx", InstName.Fmaxnmp_V, InstEmit.Fmaxnmp_V, OpCodeSimdReg.Create); + SetA64("0110111000110000110010xxxxxxxxxx", InstName.Fmaxnmv_V, InstEmit.Fmaxnmv_V, OpCodeSimd.Create); + SetA64("011111100x110000111110xxxxxxxxxx", InstName.Fmaxp_S, InstEmit.Fmaxp_S, OpCodeSimd.Create); + SetA64("0>1011100<1xxxxx111101xxxxxxxxxx", InstName.Fmaxp_V, InstEmit.Fmaxp_V, OpCodeSimdReg.Create); + SetA64("0110111000110000111110xxxxxxxxxx", InstName.Fmaxv_V, InstEmit.Fmaxv_V, OpCodeSimd.Create); + SetA64("000111100x1xxxxx010110xxxxxxxxxx", InstName.Fmin_S, InstEmit.Fmin_S, OpCodeSimdReg.Create); + SetA64("0>0011101<1xxxxx111101xxxxxxxxxx", InstName.Fmin_V, InstEmit.Fmin_V, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx011110xxxxxxxxxx", InstName.Fminnm_S, InstEmit.Fminnm_S, OpCodeSimdReg.Create); + SetA64("0>0011101<1xxxxx110001xxxxxxxxxx", InstName.Fminnm_V, InstEmit.Fminnm_V, OpCodeSimdReg.Create); + SetA64("011111101x110000110010xxxxxxxxxx", InstName.Fminnmp_S, InstEmit.Fminnmp_S, OpCodeSimd.Create); + SetA64("0>1011101<1xxxxx110001xxxxxxxxxx", InstName.Fminnmp_V, InstEmit.Fminnmp_V, OpCodeSimdReg.Create); + SetA64("0110111010110000110010xxxxxxxxxx", InstName.Fminnmv_V, InstEmit.Fminnmv_V, OpCodeSimd.Create); + SetA64("011111101x110000111110xxxxxxxxxx", InstName.Fminp_S, InstEmit.Fminp_S, OpCodeSimd.Create); + SetA64("0>1011101<1xxxxx111101xxxxxxxxxx", InstName.Fminp_V, InstEmit.Fminp_V, OpCodeSimdReg.Create); + SetA64("0110111010110000111110xxxxxxxxxx", InstName.Fminv_V, InstEmit.Fminv_V, OpCodeSimd.Create); + SetA64("010111111xxxxxxx0001x0xxxxxxxxxx", InstName.Fmla_Se, InstEmit.Fmla_Se, OpCodeSimdRegElemF.Create); + SetA64("0>0011100<1xxxxx110011xxxxxxxxxx", InstName.Fmla_V, InstEmit.Fmla_V, OpCodeSimdReg.Create); + SetA64("0>00111110011101<1xxxxx110011xxxxxxxxxx", InstName.Fmls_V, InstEmit.Fmls_V, OpCodeSimdReg.Create); + SetA64("0>00111111011100<1xxxxx110111xxxxxxxxxx", InstName.Fmul_V, InstEmit.Fmul_V, OpCodeSimdReg.Create); + SetA64("0>00111110011100<1xxxxx110111xxxxxxxxxx", InstName.Fmulx_V, InstEmit.Fmulx_V, OpCodeSimdReg.Create); + SetA64("0>10111111011101<100000111110xxxxxxxxxx", InstName.Fneg_V, InstEmit.Fneg_V, OpCodeSimd.Create); + SetA64("000111110x1xxxxx0xxxxxxxxxxxxxxx", InstName.Fnmadd_S, InstEmit.Fnmadd_S, OpCodeSimdReg.Create); + SetA64("000111110x1xxxxx1xxxxxxxxxxxxxxx", InstName.Fnmsub_S, InstEmit.Fnmsub_S, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx100010xxxxxxxxxx", InstName.Fnmul_S, InstEmit.Fnmul_S, OpCodeSimdReg.Create); + SetA64("010111101x100001110110xxxxxxxxxx", InstName.Frecpe_S, InstEmit.Frecpe_S, OpCodeSimd.Create); + SetA64("0>0011101<100001110110xxxxxxxxxx", InstName.Frecpe_V, InstEmit.Frecpe_V, OpCodeSimd.Create); + SetA64("010111100x1xxxxx111111xxxxxxxxxx", InstName.Frecps_S, InstEmit.Frecps_S, OpCodeSimdReg.Create); + SetA64("0>0011100<1xxxxx111111xxxxxxxxxx", InstName.Frecps_V, InstEmit.Frecps_V, OpCodeSimdReg.Create); + SetA64("010111101x100001111110xxxxxxxxxx", InstName.Frecpx_S, InstEmit.Frecpx_S, OpCodeSimd.Create); + SetA64("000111100x100110010000xxxxxxxxxx", InstName.Frinta_S, InstEmit.Frinta_S, OpCodeSimd.Create); + SetA64("0>1011100<100001100010xxxxxxxxxx", InstName.Frinta_V, InstEmit.Frinta_V, OpCodeSimd.Create); + SetA64("000111100x100111110000xxxxxxxxxx", InstName.Frinti_S, InstEmit.Frinti_S, OpCodeSimd.Create); + SetA64("0>1011101<100001100110xxxxxxxxxx", InstName.Frinti_V, InstEmit.Frinti_V, OpCodeSimd.Create); + SetA64("000111100x100101010000xxxxxxxxxx", InstName.Frintm_S, InstEmit.Frintm_S, OpCodeSimd.Create); + SetA64("0>0011100<100001100110xxxxxxxxxx", InstName.Frintm_V, InstEmit.Frintm_V, OpCodeSimd.Create); + SetA64("000111100x100100010000xxxxxxxxxx", InstName.Frintn_S, InstEmit.Frintn_S, OpCodeSimd.Create); + SetA64("0>0011100<100001100010xxxxxxxxxx", InstName.Frintn_V, InstEmit.Frintn_V, OpCodeSimd.Create); + SetA64("000111100x100100110000xxxxxxxxxx", InstName.Frintp_S, InstEmit.Frintp_S, OpCodeSimd.Create); + SetA64("0>0011101<100001100010xxxxxxxxxx", InstName.Frintp_V, InstEmit.Frintp_V, OpCodeSimd.Create); + SetA64("000111100x100111010000xxxxxxxxxx", InstName.Frintx_S, InstEmit.Frintx_S, OpCodeSimd.Create); + SetA64("0>1011100<100001100110xxxxxxxxxx", InstName.Frintx_V, InstEmit.Frintx_V, OpCodeSimd.Create); + SetA64("000111100x100101110000xxxxxxxxxx", InstName.Frintz_S, InstEmit.Frintz_S, OpCodeSimd.Create); + SetA64("0>0011101<100001100110xxxxxxxxxx", InstName.Frintz_V, InstEmit.Frintz_V, OpCodeSimd.Create); + SetA64("011111101x100001110110xxxxxxxxxx", InstName.Frsqrte_S, InstEmit.Frsqrte_S, OpCodeSimd.Create); + SetA64("0>1011101<100001110110xxxxxxxxxx", InstName.Frsqrte_V, InstEmit.Frsqrte_V, OpCodeSimd.Create); + SetA64("010111101x1xxxxx111111xxxxxxxxxx", InstName.Frsqrts_S, InstEmit.Frsqrts_S, OpCodeSimdReg.Create); + SetA64("0>0011101<1xxxxx111111xxxxxxxxxx", InstName.Frsqrts_V, InstEmit.Frsqrts_V, OpCodeSimdReg.Create); + SetA64("000111100x100001110000xxxxxxxxxx", InstName.Fsqrt_S, InstEmit.Fsqrt_S, OpCodeSimd.Create); + SetA64("0>1011101<100001111110xxxxxxxxxx", InstName.Fsqrt_V, InstEmit.Fsqrt_V, OpCodeSimd.Create); + SetA64("000111100x1xxxxx001110xxxxxxxxxx", InstName.Fsub_S, InstEmit.Fsub_S, OpCodeSimdReg.Create); + SetA64("0>0011101<1xxxxx110101xxxxxxxxxx", InstName.Fsub_V, InstEmit.Fsub_V, OpCodeSimdReg.Create); + SetA64("01001110000xxxxx000111xxxxxxxxxx", InstName.Ins_Gp, InstEmit.Ins_Gp, OpCodeSimdIns.Create); + SetA64("01101110000xxxxx0xxxx1xxxxxxxxxx", InstName.Ins_V, InstEmit.Ins_V, OpCodeSimdIns.Create); + SetA64("0x00110001000000xxxxxxxxxxxxxxxx", InstName.Ld__Vms, InstEmit.Ld__Vms, OpCodeSimdMemMs.Create); + SetA64("0x001100110xxxxxxxxxxxxxxxxxxxxx", InstName.Ld__Vms, InstEmit.Ld__Vms, OpCodeSimdMemMs.Create); + SetA64("0x00110101x00000xxxxxxxxxxxxxxxx", InstName.Ld__Vss, InstEmit.Ld__Vss, OpCodeSimdMemSs.Create); + SetA64("0x00110111xxxxxxxxxxxxxxxxxxxxxx", InstName.Ld__Vss, InstEmit.Ld__Vss, OpCodeSimdMemSs.Create); + SetA64("<<10110xx1xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldp, InstEmit.Ldp, OpCodeSimdMemPair.Create); + SetA64("xx111100x10xxxxxxxxx00xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemImm.Create); + SetA64("xx111100x10xxxxxxxxx01xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemImm.Create); + SetA64("xx111100x10xxxxxxxxx11xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemImm.Create); + SetA64("xx111101x1xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemImm.Create); + SetA64("xx111100x11xxxxxx1xx10xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemReg.Create); + SetA64("xx011100xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr_Literal, InstEmit.Ldr_Literal, OpCodeSimdMemLit.Create); + SetA64("0x001110<<1xxxxx100101xxxxxxxxxx", InstName.Mla_V, InstEmit.Mla_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx0000x0xxxxxxxxxx", InstName.Mla_Ve, InstEmit.Mla_Ve, OpCodeSimdRegElem.Create); + SetA64("0x101110<<1xxxxx100101xxxxxxxxxx", InstName.Mls_V, InstEmit.Mls_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx0100x0xxxxxxxxxx", InstName.Mls_Ve, InstEmit.Mls_Ve, OpCodeSimdRegElem.Create); + SetA64("0x00111100000xxx0xx001xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, OpCodeSimdImm.Create); + SetA64("0x00111100000xxx10x001xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, OpCodeSimdImm.Create); + SetA64("0x00111100000xxx110x01xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, OpCodeSimdImm.Create); + SetA64("0xx0111100000xxx111001xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, OpCodeSimdImm.Create); + SetA64("0x001110<<1xxxxx100111xxxxxxxxxx", InstName.Mul_V, InstEmit.Mul_V, OpCodeSimdReg.Create); + SetA64("0x001111xxxxxxxx1000x0xxxxxxxxxx", InstName.Mul_Ve, InstEmit.Mul_Ve, OpCodeSimdRegElem.Create); + SetA64("0x10111100000xxx0xx001xxxxxxxxxx", InstName.Mvni_V, InstEmit.Mvni_V, OpCodeSimdImm.Create); + SetA64("0x10111100000xxx10x001xxxxxxxxxx", InstName.Mvni_V, InstEmit.Mvni_V, OpCodeSimdImm.Create); + SetA64("0x10111100000xxx110x01xxxxxxxxxx", InstName.Mvni_V, InstEmit.Mvni_V, OpCodeSimdImm.Create); + SetA64("0111111011100000101110xxxxxxxxxx", InstName.Neg_S, InstEmit.Neg_S, OpCodeSimd.Create); + SetA64("0>101110<<100000101110xxxxxxxxxx", InstName.Neg_V, InstEmit.Neg_V, OpCodeSimd.Create); + SetA64("0x10111000100000010110xxxxxxxxxx", InstName.Not_V, InstEmit.Not_V, OpCodeSimd.Create); + SetA64("0x001110111xxxxx000111xxxxxxxxxx", InstName.Orn_V, InstEmit.Orn_V, OpCodeSimdReg.Create); + SetA64("0x001110101xxxxx000111xxxxxxxxxx", InstName.Orr_V, InstEmit.Orr_V, OpCodeSimdReg.Create); + SetA64("0x00111100000xxx0xx101xxxxxxxxxx", InstName.Orr_Vi, InstEmit.Orr_Vi, OpCodeSimdImm.Create); + SetA64("0x00111100000xxx10x101xxxxxxxxxx", InstName.Orr_Vi, InstEmit.Orr_Vi, OpCodeSimdImm.Create); + SetA64("0x001110001xxxxx111000xxxxxxxxxx", InstName.Pmull_V, InstEmit.Pmull_V, OpCodeSimdReg.Create); + SetA64("0x001110111xxxxx111000xxxxxxxxxx", InstName.Pmull_V, InstEmit.Pmull_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx010000xxxxxxxxxx", InstName.Raddhn_V, InstEmit.Raddhn_V, OpCodeSimdReg.Create); + SetA64("0x10111001100000010110xxxxxxxxxx", InstName.Rbit_V, InstEmit.Rbit_V, OpCodeSimd.Create); + SetA64("0x00111000100000000110xxxxxxxxxx", InstName.Rev16_V, InstEmit.Rev16_V, OpCodeSimd.Create); + SetA64("0x1011100x100000000010xxxxxxxxxx", InstName.Rev32_V, InstEmit.Rev32_V, OpCodeSimd.Create); + SetA64("0x001110<<100000000010xxxxxxxxxx", InstName.Rev64_V, InstEmit.Rev64_V, OpCodeSimd.Create); + SetA64("0x00111100>>>xxx100011xxxxxxxxxx", InstName.Rshrn_V, InstEmit.Rshrn_V, OpCodeSimdShImm.Create); + SetA64("0x101110<<1xxxxx011000xxxxxxxxxx", InstName.Rsubhn_V, InstEmit.Rsubhn_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx011111xxxxxxxxxx", InstName.Saba_V, InstEmit.Saba_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx010100xxxxxxxxxx", InstName.Sabal_V, InstEmit.Sabal_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx011101xxxxxxxxxx", InstName.Sabd_V, InstEmit.Sabd_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx011100xxxxxxxxxx", InstName.Sabdl_V, InstEmit.Sabdl_V, OpCodeSimdReg.Create); + SetA64("0x001110<<100000011010xxxxxxxxxx", InstName.Sadalp_V, InstEmit.Sadalp_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx000000xxxxxxxxxx", InstName.Saddl_V, InstEmit.Saddl_V, OpCodeSimdReg.Create); + SetA64("0x001110<<100000001010xxxxxxxxxx", InstName.Saddlp_V, InstEmit.Saddlp_V, OpCodeSimd.Create); + SetA64("000011100x110000001110xxxxxxxxxx", InstName.Saddlv_V, InstEmit.Saddlv_V, OpCodeSimd.Create); + SetA64("01001110<<110000001110xxxxxxxxxx", InstName.Saddlv_V, InstEmit.Saddlv_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx000100xxxxxxxxxx", InstName.Saddw_V, InstEmit.Saddw_V, OpCodeSimdReg.Create); + SetA64("x00111100x100010000000xxxxxxxxxx", InstName.Scvtf_Gp, InstEmit.Scvtf_Gp, OpCodeSimdCvt.Create); + SetA64(">00111100x000010>xxxxxxxxxxxxxxx", InstName.Scvtf_Gp_Fixed, InstEmit.Scvtf_Gp_Fixed, OpCodeSimdCvt.Create); + SetA64("010111100x100001110110xxxxxxxxxx", InstName.Scvtf_S, InstEmit.Scvtf_S, OpCodeSimd.Create); + SetA64("010111110>>xxxxx111001xxxxxxxxxx", InstName.Scvtf_S_Fixed, InstEmit.Scvtf_S_Fixed, OpCodeSimdShImm.Create); + SetA64("0>0011100<100001110110xxxxxxxxxx", InstName.Scvtf_V, InstEmit.Scvtf_V, OpCodeSimd.Create); + SetA64("0x001111001xxxxx111001xxxxxxxxxx", InstName.Scvtf_V_Fixed, InstEmit.Scvtf_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx111001xxxxxxxxxx", InstName.Scvtf_V_Fixed, InstEmit.Scvtf_V_Fixed, OpCodeSimdShImm.Create); + SetA64("01011110000xxxxx000000xxxxxxxxxx", InstName.Sha1c_V, InstEmit.Sha1c_V, OpCodeSimdReg.Create); + SetA64("0101111000101000000010xxxxxxxxxx", InstName.Sha1h_V, InstEmit.Sha1h_V, OpCodeSimd.Create); + SetA64("01011110000xxxxx001000xxxxxxxxxx", InstName.Sha1m_V, InstEmit.Sha1m_V, OpCodeSimdReg.Create); + SetA64("01011110000xxxxx000100xxxxxxxxxx", InstName.Sha1p_V, InstEmit.Sha1p_V, OpCodeSimdReg.Create); + SetA64("01011110000xxxxx001100xxxxxxxxxx", InstName.Sha1su0_V, InstEmit.Sha1su0_V, OpCodeSimdReg.Create); + SetA64("0101111000101000000110xxxxxxxxxx", InstName.Sha1su1_V, InstEmit.Sha1su1_V, OpCodeSimd.Create); + SetA64("01011110000xxxxx010000xxxxxxxxxx", InstName.Sha256h_V, InstEmit.Sha256h_V, OpCodeSimdReg.Create); + SetA64("01011110000xxxxx010100xxxxxxxxxx", InstName.Sha256h2_V, InstEmit.Sha256h2_V, OpCodeSimdReg.Create); + SetA64("0101111000101000001010xxxxxxxxxx", InstName.Sha256su0_V, InstEmit.Sha256su0_V, OpCodeSimd.Create); + SetA64("01011110000xxxxx011000xxxxxxxxxx", InstName.Sha256su1_V, InstEmit.Sha256su1_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx000001xxxxxxxxxx", InstName.Shadd_V, InstEmit.Shadd_V, OpCodeSimdReg.Create); + SetA64("0101111101xxxxxx010101xxxxxxxxxx", InstName.Shl_S, InstEmit.Shl_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx010101xxxxxxxxxx", InstName.Shl_V, InstEmit.Shl_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx010101xxxxxxxxxx", InstName.Shl_V, InstEmit.Shl_V, OpCodeSimdShImm.Create); + SetA64("0x101110<<100001001110xxxxxxxxxx", InstName.Shll_V, InstEmit.Shll_V, OpCodeSimd.Create); + SetA64("0x00111100>>>xxx100001xxxxxxxxxx", InstName.Shrn_V, InstEmit.Shrn_V, OpCodeSimdShImm.Create); + SetA64("0x001110<<1xxxxx001001xxxxxxxxxx", InstName.Shsub_V, InstEmit.Shsub_V, OpCodeSimdReg.Create); + SetA64("0111111101xxxxxx010101xxxxxxxxxx", InstName.Sli_S, InstEmit.Sli_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx010101xxxxxxxxxx", InstName.Sli_V, InstEmit.Sli_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx010101xxxxxxxxxx", InstName.Sli_V, InstEmit.Sli_V, OpCodeSimdShImm.Create); + SetA64("0x001110<<1xxxxx011001xxxxxxxxxx", InstName.Smax_V, InstEmit.Smax_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx101001xxxxxxxxxx", InstName.Smaxp_V, InstEmit.Smaxp_V, OpCodeSimdReg.Create); + SetA64("000011100x110000101010xxxxxxxxxx", InstName.Smaxv_V, InstEmit.Smaxv_V, OpCodeSimd.Create); + SetA64("01001110<<110000101010xxxxxxxxxx", InstName.Smaxv_V, InstEmit.Smaxv_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx011011xxxxxxxxxx", InstName.Smin_V, InstEmit.Smin_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx101011xxxxxxxxxx", InstName.Sminp_V, InstEmit.Sminp_V, OpCodeSimdReg.Create); + SetA64("000011100x110001101010xxxxxxxxxx", InstName.Sminv_V, InstEmit.Sminv_V, OpCodeSimd.Create); + SetA64("01001110<<110001101010xxxxxxxxxx", InstName.Sminv_V, InstEmit.Sminv_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx100000xxxxxxxxxx", InstName.Smlal_V, InstEmit.Smlal_V, OpCodeSimdReg.Create); + SetA64("0x001111xxxxxxxx0010x0xxxxxxxxxx", InstName.Smlal_Ve, InstEmit.Smlal_Ve, OpCodeSimdRegElem.Create); + SetA64("0x001110<<1xxxxx101000xxxxxxxxxx", InstName.Smlsl_V, InstEmit.Smlsl_V, OpCodeSimdReg.Create); + SetA64("0x001111xxxxxxxx0110x0xxxxxxxxxx", InstName.Smlsl_Ve, InstEmit.Smlsl_Ve, OpCodeSimdRegElem.Create); + SetA64("0x001110000xxxxx001011xxxxxxxxxx", InstName.Smov_S, InstEmit.Smov_S, OpCodeSimdIns.Create); + SetA64("0x001110<<1xxxxx110000xxxxxxxxxx", InstName.Smull_V, InstEmit.Smull_V, OpCodeSimdReg.Create); + SetA64("0x001111xxxxxxxx1010x0xxxxxxxxxx", InstName.Smull_Ve, InstEmit.Smull_Ve, OpCodeSimdRegElem.Create); + SetA64("01011110xx100000011110xxxxxxxxxx", InstName.Sqabs_S, InstEmit.Sqabs_S, OpCodeSimd.Create); + SetA64("0>001110<<100000011110xxxxxxxxxx", InstName.Sqabs_V, InstEmit.Sqabs_V, OpCodeSimd.Create); + SetA64("01011110xx1xxxxx000011xxxxxxxxxx", InstName.Sqadd_S, InstEmit.Sqadd_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx000011xxxxxxxxxx", InstName.Sqadd_V, InstEmit.Sqadd_V, OpCodeSimdReg.Create); + SetA64("01011110011xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_S, InstEmit.Sqdmulh_S, OpCodeSimdReg.Create); + SetA64("01011110101xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_S, InstEmit.Sqdmulh_S, OpCodeSimdReg.Create); + SetA64("0x001110011xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_V, InstEmit.Sqdmulh_V, OpCodeSimdReg.Create); + SetA64("0x001110101xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_V, InstEmit.Sqdmulh_V, OpCodeSimdReg.Create); + SetA64("0x00111101xxxxxx1100x0xxxxxxxxxx", InstName.Sqdmulh_Ve, InstEmit.Sqdmulh_Ve, OpCodeSimdRegElem.Create); + SetA64("0x00111110xxxxxx1100x0xxxxxxxxxx", InstName.Sqdmulh_Ve, InstEmit.Sqdmulh_Ve, OpCodeSimdRegElem.Create); + SetA64("01111110xx100000011110xxxxxxxxxx", InstName.Sqneg_S, InstEmit.Sqneg_S, OpCodeSimd.Create); + SetA64("0>101110<<100000011110xxxxxxxxxx", InstName.Sqneg_V, InstEmit.Sqneg_V, OpCodeSimd.Create); + SetA64("01111110011xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_S, InstEmit.Sqrdmulh_S, OpCodeSimdReg.Create); + SetA64("01111110101xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_S, InstEmit.Sqrdmulh_S, OpCodeSimdReg.Create); + SetA64("0x101110011xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_V, InstEmit.Sqrdmulh_V, OpCodeSimdReg.Create); + SetA64("0x101110101xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_V, InstEmit.Sqrdmulh_V, OpCodeSimdReg.Create); + SetA64("0x00111101xxxxxx1101x0xxxxxxxxxx", InstName.Sqrdmulh_Ve, InstEmit.Sqrdmulh_Ve, OpCodeSimdRegElem.Create); + SetA64("0x00111110xxxxxx1101x0xxxxxxxxxx", InstName.Sqrdmulh_Ve, InstEmit.Sqrdmulh_Ve, OpCodeSimdRegElem.Create); + SetA64("0>001110<<1xxxxx010111xxxxxxxxxx", InstName.Sqrshl_V, InstEmit.Sqrshl_V, OpCodeSimdReg.Create); + SetA64("0101111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_S, InstEmit.Sqrshrn_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_V, InstEmit.Sqrshrn_V, OpCodeSimdShImm.Create); + SetA64("0111111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_S, InstEmit.Sqrshrun_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_V, InstEmit.Sqrshrun_V, OpCodeSimdShImm.Create); + SetA64("010111110>>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Si, InstEmit.Sqshl_Si, OpCodeSimdShImm.Create); + SetA64("0>001110<<1xxxxx010011xxxxxxxxxx", InstName.Sqshl_V, InstEmit.Sqshl_V, OpCodeSimdReg.Create); + SetA64("0000111100>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Vi, InstEmit.Sqshl_Vi, OpCodeSimdShImm.Create); + SetA64("010011110>>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Vi, InstEmit.Sqshl_Vi, OpCodeSimdShImm.Create); + SetA64("0101111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_S, InstEmit.Sqshrn_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_V, InstEmit.Sqshrn_V, OpCodeSimdShImm.Create); + SetA64("0111111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_S, InstEmit.Sqshrun_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_V, InstEmit.Sqshrun_V, OpCodeSimdShImm.Create); + SetA64("01011110xx1xxxxx001011xxxxxxxxxx", InstName.Sqsub_S, InstEmit.Sqsub_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx001011xxxxxxxxxx", InstName.Sqsub_V, InstEmit.Sqsub_V, OpCodeSimdReg.Create); + SetA64("01011110<<100001010010xxxxxxxxxx", InstName.Sqxtn_S, InstEmit.Sqxtn_S, OpCodeSimd.Create); + SetA64("0x001110<<100001010010xxxxxxxxxx", InstName.Sqxtn_V, InstEmit.Sqxtn_V, OpCodeSimd.Create); + SetA64("01111110<<100001001010xxxxxxxxxx", InstName.Sqxtun_S, InstEmit.Sqxtun_S, OpCodeSimd.Create); + SetA64("0x101110<<100001001010xxxxxxxxxx", InstName.Sqxtun_V, InstEmit.Sqxtun_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx000101xxxxxxxxxx", InstName.Srhadd_V, InstEmit.Srhadd_V, OpCodeSimdReg.Create); + SetA64("0111111101xxxxxx010001xxxxxxxxxx", InstName.Sri_S, InstEmit.Sri_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx010001xxxxxxxxxx", InstName.Sri_V, InstEmit.Sri_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx010001xxxxxxxxxx", InstName.Sri_V, InstEmit.Sri_V, OpCodeSimdShImm.Create); + SetA64("0>001110<<1xxxxx010101xxxxxxxxxx", InstName.Srshl_V, InstEmit.Srshl_V, OpCodeSimdReg.Create); + SetA64("0101111101xxxxxx001001xxxxxxxxxx", InstName.Srshr_S, InstEmit.Srshr_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx001001xxxxxxxxxx", InstName.Srshr_V, InstEmit.Srshr_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx001001xxxxxxxxxx", InstName.Srshr_V, InstEmit.Srshr_V, OpCodeSimdShImm.Create); + SetA64("0101111101xxxxxx001101xxxxxxxxxx", InstName.Srsra_S, InstEmit.Srsra_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx001101xxxxxxxxxx", InstName.Srsra_V, InstEmit.Srsra_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx001101xxxxxxxxxx", InstName.Srsra_V, InstEmit.Srsra_V, OpCodeSimdShImm.Create); + SetA64("01011110111xxxxx010001xxxxxxxxxx", InstName.Sshl_S, InstEmit.Sshl_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx010001xxxxxxxxxx", InstName.Sshl_V, InstEmit.Sshl_V, OpCodeSimdReg.Create); + SetA64("0x00111100>>>xxx101001xxxxxxxxxx", InstName.Sshll_V, InstEmit.Sshll_V, OpCodeSimdShImm.Create); + SetA64("0101111101xxxxxx000001xxxxxxxxxx", InstName.Sshr_S, InstEmit.Sshr_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx000001xxxxxxxxxx", InstName.Sshr_V, InstEmit.Sshr_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx000001xxxxxxxxxx", InstName.Sshr_V, InstEmit.Sshr_V, OpCodeSimdShImm.Create); + SetA64("0101111101xxxxxx000101xxxxxxxxxx", InstName.Ssra_S, InstEmit.Ssra_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx000101xxxxxxxxxx", InstName.Ssra_V, InstEmit.Ssra_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx000101xxxxxxxxxx", InstName.Ssra_V, InstEmit.Ssra_V, OpCodeSimdShImm.Create); + SetA64("0x001110<<1xxxxx001000xxxxxxxxxx", InstName.Ssubl_V, InstEmit.Ssubl_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx001100xxxxxxxxxx", InstName.Ssubw_V, InstEmit.Ssubw_V, OpCodeSimdReg.Create); + SetA64("0x00110000000000xxxxxxxxxxxxxxxx", InstName.St__Vms, InstEmit.St__Vms, OpCodeSimdMemMs.Create); + SetA64("0x001100100xxxxxxxxxxxxxxxxxxxxx", InstName.St__Vms, InstEmit.St__Vms, OpCodeSimdMemMs.Create); + SetA64("0x00110100x00000xxxxxxxxxxxxxxxx", InstName.St__Vss, InstEmit.St__Vss, OpCodeSimdMemSs.Create); + SetA64("0x00110110xxxxxxxxxxxxxxxxxxxxxx", InstName.St__Vss, InstEmit.St__Vss, OpCodeSimdMemSs.Create); + SetA64("<<10110xx0xxxxxxxxxxxxxxxxxxxxxx", InstName.Stp, InstEmit.Stp, OpCodeSimdMemPair.Create); + SetA64("xx111100x00xxxxxxxxx00xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemImm.Create); + SetA64("xx111100x00xxxxxxxxx01xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemImm.Create); + SetA64("xx111100x00xxxxxxxxx11xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemImm.Create); + SetA64("xx111101x0xxxxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemImm.Create); + SetA64("xx111100x01xxxxxx1xx10xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemReg.Create); + SetA64("01111110111xxxxx100001xxxxxxxxxx", InstName.Sub_S, InstEmit.Sub_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx100001xxxxxxxxxx", InstName.Sub_V, InstEmit.Sub_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx011000xxxxxxxxxx", InstName.Subhn_V, InstEmit.Subhn_V, OpCodeSimdReg.Create); + SetA64("01011110xx100000001110xxxxxxxxxx", InstName.Suqadd_S, InstEmit.Suqadd_S, OpCodeSimd.Create); + SetA64("0>001110<<100000001110xxxxxxxxxx", InstName.Suqadd_V, InstEmit.Suqadd_V, OpCodeSimd.Create); + SetA64("0x001110000xxxxx0xx000xxxxxxxxxx", InstName.Tbl_V, InstEmit.Tbl_V, OpCodeSimdTbl.Create); + SetA64("0x001110000xxxxx0xx100xxxxxxxxxx", InstName.Tbx_V, InstEmit.Tbx_V, OpCodeSimdTbl.Create); + SetA64("0>001110<<0xxxxx001010xxxxxxxxxx", InstName.Trn1_V, InstEmit.Trn1_V, OpCodeSimdReg.Create); + SetA64("0>001110<<0xxxxx011010xxxxxxxxxx", InstName.Trn2_V, InstEmit.Trn2_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx011111xxxxxxxxxx", InstName.Uaba_V, InstEmit.Uaba_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx010100xxxxxxxxxx", InstName.Uabal_V, InstEmit.Uabal_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx011101xxxxxxxxxx", InstName.Uabd_V, InstEmit.Uabd_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx011100xxxxxxxxxx", InstName.Uabdl_V, InstEmit.Uabdl_V, OpCodeSimdReg.Create); + SetA64("0x101110<<100000011010xxxxxxxxxx", InstName.Uadalp_V, InstEmit.Uadalp_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx000000xxxxxxxxxx", InstName.Uaddl_V, InstEmit.Uaddl_V, OpCodeSimdReg.Create); + SetA64("0x101110<<100000001010xxxxxxxxxx", InstName.Uaddlp_V, InstEmit.Uaddlp_V, OpCodeSimd.Create); + SetA64("001011100x110000001110xxxxxxxxxx", InstName.Uaddlv_V, InstEmit.Uaddlv_V, OpCodeSimd.Create); + SetA64("01101110<<110000001110xxxxxxxxxx", InstName.Uaddlv_V, InstEmit.Uaddlv_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx000100xxxxxxxxxx", InstName.Uaddw_V, InstEmit.Uaddw_V, OpCodeSimdReg.Create); + SetA64("x00111100x100011000000xxxxxxxxxx", InstName.Ucvtf_Gp, InstEmit.Ucvtf_Gp, OpCodeSimdCvt.Create); + SetA64(">00111100x000011>xxxxxxxxxxxxxxx", InstName.Ucvtf_Gp_Fixed, InstEmit.Ucvtf_Gp_Fixed, OpCodeSimdCvt.Create); + SetA64("011111100x100001110110xxxxxxxxxx", InstName.Ucvtf_S, InstEmit.Ucvtf_S, OpCodeSimd.Create); + SetA64("011111110>>xxxxx111001xxxxxxxxxx", InstName.Ucvtf_S_Fixed, InstEmit.Ucvtf_S_Fixed, OpCodeSimdShImm.Create); + SetA64("0>1011100<100001110110xxxxxxxxxx", InstName.Ucvtf_V, InstEmit.Ucvtf_V, OpCodeSimd.Create); + SetA64("0x101111001xxxxx111001xxxxxxxxxx", InstName.Ucvtf_V_Fixed, InstEmit.Ucvtf_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx111001xxxxxxxxxx", InstName.Ucvtf_V_Fixed, InstEmit.Ucvtf_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0x101110<<1xxxxx000001xxxxxxxxxx", InstName.Uhadd_V, InstEmit.Uhadd_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx001001xxxxxxxxxx", InstName.Uhsub_V, InstEmit.Uhsub_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx011001xxxxxxxxxx", InstName.Umax_V, InstEmit.Umax_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx101001xxxxxxxxxx", InstName.Umaxp_V, InstEmit.Umaxp_V, OpCodeSimdReg.Create); + SetA64("001011100x110000101010xxxxxxxxxx", InstName.Umaxv_V, InstEmit.Umaxv_V, OpCodeSimd.Create); + SetA64("01101110<<110000101010xxxxxxxxxx", InstName.Umaxv_V, InstEmit.Umaxv_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx011011xxxxxxxxxx", InstName.Umin_V, InstEmit.Umin_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx101011xxxxxxxxxx", InstName.Uminp_V, InstEmit.Uminp_V, OpCodeSimdReg.Create); + SetA64("001011100x110001101010xxxxxxxxxx", InstName.Uminv_V, InstEmit.Uminv_V, OpCodeSimd.Create); + SetA64("01101110<<110001101010xxxxxxxxxx", InstName.Uminv_V, InstEmit.Uminv_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx100000xxxxxxxxxx", InstName.Umlal_V, InstEmit.Umlal_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx0010x0xxxxxxxxxx", InstName.Umlal_Ve, InstEmit.Umlal_Ve, OpCodeSimdRegElem.Create); + SetA64("0x101110<<1xxxxx101000xxxxxxxxxx", InstName.Umlsl_V, InstEmit.Umlsl_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx0110x0xxxxxxxxxx", InstName.Umlsl_Ve, InstEmit.Umlsl_Ve, OpCodeSimdRegElem.Create); + SetA64("0x001110000xxxxx001111xxxxxxxxxx", InstName.Umov_S, InstEmit.Umov_S, OpCodeSimdIns.Create); + SetA64("0x101110<<1xxxxx110000xxxxxxxxxx", InstName.Umull_V, InstEmit.Umull_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx1010x0xxxxxxxxxx", InstName.Umull_Ve, InstEmit.Umull_Ve, OpCodeSimdRegElem.Create); + SetA64("01111110xx1xxxxx000011xxxxxxxxxx", InstName.Uqadd_S, InstEmit.Uqadd_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx000011xxxxxxxxxx", InstName.Uqadd_V, InstEmit.Uqadd_V, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx010111xxxxxxxxxx", InstName.Uqrshl_V, InstEmit.Uqrshl_V, OpCodeSimdReg.Create); + SetA64("0111111100>>>xxx100111xxxxxxxxxx", InstName.Uqrshrn_S, InstEmit.Uqrshrn_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx100111xxxxxxxxxx", InstName.Uqrshrn_V, InstEmit.Uqrshrn_V, OpCodeSimdShImm.Create); + SetA64("0>101110<<1xxxxx010011xxxxxxxxxx", InstName.Uqshl_V, InstEmit.Uqshl_V, OpCodeSimdReg.Create); + SetA64("0111111100>>>xxx100101xxxxxxxxxx", InstName.Uqshrn_S, InstEmit.Uqshrn_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx100101xxxxxxxxxx", InstName.Uqshrn_V, InstEmit.Uqshrn_V, OpCodeSimdShImm.Create); + SetA64("01111110xx1xxxxx001011xxxxxxxxxx", InstName.Uqsub_S, InstEmit.Uqsub_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx001011xxxxxxxxxx", InstName.Uqsub_V, InstEmit.Uqsub_V, OpCodeSimdReg.Create); + SetA64("01111110<<100001010010xxxxxxxxxx", InstName.Uqxtn_S, InstEmit.Uqxtn_S, OpCodeSimd.Create); + SetA64("0x101110<<100001010010xxxxxxxxxx", InstName.Uqxtn_V, InstEmit.Uqxtn_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx000101xxxxxxxxxx", InstName.Urhadd_V, InstEmit.Urhadd_V, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx010101xxxxxxxxxx", InstName.Urshl_V, InstEmit.Urshl_V, OpCodeSimdReg.Create); + SetA64("0111111101xxxxxx001001xxxxxxxxxx", InstName.Urshr_S, InstEmit.Urshr_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx001001xxxxxxxxxx", InstName.Urshr_V, InstEmit.Urshr_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx001001xxxxxxxxxx", InstName.Urshr_V, InstEmit.Urshr_V, OpCodeSimdShImm.Create); + SetA64("0111111101xxxxxx001101xxxxxxxxxx", InstName.Ursra_S, InstEmit.Ursra_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx001101xxxxxxxxxx", InstName.Ursra_V, InstEmit.Ursra_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx001101xxxxxxxxxx", InstName.Ursra_V, InstEmit.Ursra_V, OpCodeSimdShImm.Create); + SetA64("01111110111xxxxx010001xxxxxxxxxx", InstName.Ushl_S, InstEmit.Ushl_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx010001xxxxxxxxxx", InstName.Ushl_V, InstEmit.Ushl_V, OpCodeSimdReg.Create); + SetA64("0x10111100>>>xxx101001xxxxxxxxxx", InstName.Ushll_V, InstEmit.Ushll_V, OpCodeSimdShImm.Create); + SetA64("0111111101xxxxxx000001xxxxxxxxxx", InstName.Ushr_S, InstEmit.Ushr_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx000001xxxxxxxxxx", InstName.Ushr_V, InstEmit.Ushr_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx000001xxxxxxxxxx", InstName.Ushr_V, InstEmit.Ushr_V, OpCodeSimdShImm.Create); + SetA64("01111110xx100000001110xxxxxxxxxx", InstName.Usqadd_S, InstEmit.Usqadd_S, OpCodeSimd.Create); + SetA64("0>101110<<100000001110xxxxxxxxxx", InstName.Usqadd_V, InstEmit.Usqadd_V, OpCodeSimd.Create); + SetA64("0111111101xxxxxx000101xxxxxxxxxx", InstName.Usra_S, InstEmit.Usra_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx000101xxxxxxxxxx", InstName.Usra_V, InstEmit.Usra_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx000101xxxxxxxxxx", InstName.Usra_V, InstEmit.Usra_V, OpCodeSimdShImm.Create); + SetA64("0x101110<<1xxxxx001000xxxxxxxxxx", InstName.Usubl_V, InstEmit.Usubl_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx001100xxxxxxxxxx", InstName.Usubw_V, InstEmit.Usubw_V, OpCodeSimdReg.Create); + SetA64("0>001110<<0xxxxx000110xxxxxxxxxx", InstName.Uzp1_V, InstEmit.Uzp1_V, OpCodeSimdReg.Create); + SetA64("0>001110<<0xxxxx010110xxxxxxxxxx", InstName.Uzp2_V, InstEmit.Uzp2_V, OpCodeSimdReg.Create); + SetA64("0x001110<<100001001010xxxxxxxxxx", InstName.Xtn_V, InstEmit.Xtn_V, OpCodeSimd.Create); + SetA64("0>001110<<0xxxxx001110xxxxxxxxxx", InstName.Zip1_V, InstEmit.Zip1_V, OpCodeSimdReg.Create); + SetA64("0>001110<<0xxxxx011110xxxxxxxxxx", InstName.Zip2_V, InstEmit.Zip2_V, OpCodeSimdReg.Create); + #endregion + + #region "OpCode Table (AArch32, A32)" + // Base + SetA32("<<<<0010101xxxxxxxxxxxxxxxxxxxxx", InstName.Adc, InstEmit32.Adc, OpCode32AluImm.Create); + SetA32("<<<<0000101xxxxxxxxxxxxxxxx0xxxx", InstName.Adc, InstEmit32.Adc, OpCode32AluRsImm.Create); + SetA32("<<<<0000101xxxxxxxxxxxxx0xx1xxxx", InstName.Adc, InstEmit32.Adc, OpCode32AluRsReg.Create); + SetA32("<<<<0010100xxxxxxxxxxxxxxxxxxxxx", InstName.Add, InstEmit32.Add, OpCode32AluImm.Create); + SetA32("<<<<0000100xxxxxxxxxxxxxxxx0xxxx", InstName.Add, InstEmit32.Add, OpCode32AluRsImm.Create); + SetA32("<<<<0000100xxxxxxxxxxxxx0xx1xxxx", InstName.Add, InstEmit32.Add, OpCode32AluRsReg.Create); + SetA32("<<<<0010000xxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit32.And, OpCode32AluImm.Create); + SetA32("<<<<0000000xxxxxxxxxxxxxxxx0xxxx", InstName.And, InstEmit32.And, OpCode32AluRsImm.Create); + SetA32("<<<<0000000xxxxxxxxxxxxx0xx1xxxx", InstName.And, InstEmit32.And, OpCode32AluRsReg.Create); + SetA32("<<<<1010xxxxxxxxxxxxxxxxxxxxxxxx", InstName.B, InstEmit32.B, OpCode32BImm.Create); + SetA32("<<<<0111110xxxxxxxxxxxxxx0011111", InstName.Bfc, InstEmit32.Bfc, OpCode32AluBf.Create); + SetA32("<<<<0111110xxxxxxxxxxxxxx001xxxx", InstName.Bfi, InstEmit32.Bfi, OpCode32AluBf.Create); + SetA32("<<<<0011110xxxxxxxxxxxxxxxxxxxxx", InstName.Bic, InstEmit32.Bic, OpCode32AluImm.Create); + SetA32("<<<<0001110xxxxxxxxxxxxxxxx0xxxx", InstName.Bic, InstEmit32.Bic, OpCode32AluRsImm.Create); + SetA32("<<<<0001110xxxxxxxxxxxxx0xx1xxxx", InstName.Bic, InstEmit32.Bic, OpCode32AluRsReg.Create); + SetA32("<<<<1011xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bl, InstEmit32.Bl, OpCode32BImm.Create); + SetA32("1111101xxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Blx, InstEmit32.Blx, OpCode32BImm.Create); + SetA32("<<<<000100101111111111110011xxxx", InstName.Blx, InstEmit32.Blxr, OpCode32BReg.Create); + SetA32("<<<<000100101111111111110001xxxx", InstName.Bx, InstEmit32.Bx, OpCode32BReg.Create); + SetA32("11110101011111111111000000011111", InstName.Clrex, InstEmit32.Clrex, OpCode32.Create); + SetA32("<<<<000101101111xxxx11110001xxxx", InstName.Clz, InstEmit32.Clz, OpCode32AluReg.Create); + SetA32("<<<<00110111xxxx0000xxxxxxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCode32AluImm.Create); + SetA32("<<<<00010111xxxx0000xxxxxxx0xxxx", InstName.Cmn, InstEmit32.Cmn, OpCode32AluRsImm.Create); + SetA32("<<<<00010111xxxx0000xxxx0xx1xxxx", InstName.Cmn, InstEmit32.Cmn, OpCode32AluRsReg.Create); + SetA32("<<<<00110101xxxx0000xxxxxxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCode32AluImm.Create); + SetA32("<<<<00010101xxxx0000xxxxxxx0xxxx", InstName.Cmp, InstEmit32.Cmp, OpCode32AluRsImm.Create); + SetA32("<<<<00010101xxxx0000xxxx0xx1xxxx", InstName.Cmp, InstEmit32.Cmp, OpCode32AluRsReg.Create); + SetA32("<<<<00010000xxxxxxxx00000100xxxx", InstName.Crc32b, InstEmit32.Crc32b, OpCode32AluReg.Create); + SetA32("<<<<00010000xxxxxxxx00100100xxxx", InstName.Crc32cb, InstEmit32.Crc32cb, OpCode32AluReg.Create); + SetA32("<<<<00010010xxxxxxxx00100100xxxx", InstName.Crc32ch, InstEmit32.Crc32ch, OpCode32AluReg.Create); + SetA32("<<<<00010100xxxxxxxx00100100xxxx", InstName.Crc32cw, InstEmit32.Crc32cw, OpCode32AluReg.Create); + SetA32("<<<<00010010xxxxxxxx00000100xxxx", InstName.Crc32h, InstEmit32.Crc32h, OpCode32AluReg.Create); + SetA32("<<<<00010100xxxxxxxx00000100xxxx", InstName.Crc32w, InstEmit32.Crc32w, OpCode32AluReg.Create); + SetA32("<<<<0011001000001111000000010100", InstName.Csdb, InstEmit32.Csdb, OpCode32.Create); + SetA32("1111010101111111111100000101xxxx", InstName.Dmb, InstEmit32.Dmb, OpCode32.Create); + SetA32("1111010101111111111100000100xxxx", InstName.Dsb, InstEmit32.Dsb, OpCode32.Create); + SetA32("<<<<0010001xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCode32AluImm.Create); + SetA32("<<<<0000001xxxxxxxxxxxxxxxx0xxxx", InstName.Eor, InstEmit32.Eor, OpCode32AluRsImm.Create); + SetA32("<<<<0000001xxxxxxxxxxxxx0xx1xxxx", InstName.Eor, InstEmit32.Eor, OpCode32AluRsReg.Create); + SetA32("<<<<0011001000001111000000010000", InstName.Esb, InstEmit32.Nop, OpCode32.Create); // Error Synchronization Barrier (FEAT_RAS) + SetA32("<<<<001100100000111100000000011x", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000001xxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000010001", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000010011", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000010101", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<001100100000111100000001011x", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000011xxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<00110010000011110000001xxxxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000001xxxxxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<001100100000111100001xxxxxxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("1111010101111111111100000110xxxx", InstName.Isb, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<00011001xxxxxxxx110010011111", InstName.Lda, InstEmit32.Lda, OpCode32MemLdEx.Create); + SetA32("<<<<00011101xxxxxxxx110010011111", InstName.Ldab, InstEmit32.Ldab, OpCode32MemLdEx.Create); + SetA32("<<<<00011001xxxxxxxx111010011111", InstName.Ldaex, InstEmit32.Ldaex, OpCode32MemLdEx.Create); + SetA32("<<<<00011101xxxxxxxx111010011111", InstName.Ldaexb, InstEmit32.Ldaexb, OpCode32MemLdEx.Create); + SetA32("<<<<00011011xxxxxxxx111010011111", InstName.Ldaexd, InstEmit32.Ldaexd, OpCode32MemLdEx.Create); + SetA32("<<<<00011111xxxxxxxx111010011111", InstName.Ldaexh, InstEmit32.Ldaexh, OpCode32MemLdEx.Create); + SetA32("<<<<00011111xxxxxxxx110010011111", InstName.Ldah, InstEmit32.Ldah, OpCode32MemLdEx.Create); + SetA32("<<<<100xx0x1xxxxxxxxxxxxxxxxxxxx", InstName.Ldm, InstEmit32.Ldm, OpCode32MemMult.Create); + SetA32("<<<<010xx0x1xxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCode32MemImm.Create); + SetA32("<<<<011xx0x1xxxxxxxxxxxxxxx0xxxx", InstName.Ldr, InstEmit32.Ldr, OpCode32MemRsImm.Create); + SetA32("<<<<010xx1x1xxxxxxxxxxxxxxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCode32MemImm.Create); + SetA32("<<<<011xx1x1xxxxxxxxxxxxxxx0xxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCode32MemRsImm.Create); + SetA32("<<<<000xx1x0xxxxxxxxxxxx1101xxxx", InstName.Ldrd, InstEmit32.Ldrd, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x0xxxxxxxx00001101xxxx", InstName.Ldrd, InstEmit32.Ldrd, OpCode32MemReg.Create); + SetA32("<<<<00011001xxxxxxxx111110011111", InstName.Ldrex, InstEmit32.Ldrex, OpCode32MemLdEx.Create); + SetA32("<<<<00011101xxxxxxxx111110011111", InstName.Ldrexb, InstEmit32.Ldrexb, OpCode32MemLdEx.Create); + SetA32("<<<<00011011xxxxxxxx111110011111", InstName.Ldrexd, InstEmit32.Ldrexd, OpCode32MemLdEx.Create); + SetA32("<<<<00011111xxxxxxxx111110011111", InstName.Ldrexh, InstEmit32.Ldrexh, OpCode32MemLdEx.Create); + SetA32("<<<<000xx1x1xxxxxxxxxxxx1011xxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x1xxxxxxxx00001011xxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCode32MemReg.Create); + SetA32("<<<<000xx1x1xxxxxxxxxxxx1101xxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x1xxxxxxxx00001101xxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCode32MemReg.Create); + SetA32("<<<<000xx1x1xxxxxxxxxxxx1111xxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x1xxxxxxxx00001111xxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCode32MemReg.Create); + SetA32("<<<<1110xxx0xxxxxxxx111xxxx1xxxx", InstName.Mcr, InstEmit32.Mcr, OpCode32System.Create); + SetA32("<<<<0000001xxxxxxxxxxxxx1001xxxx", InstName.Mla, InstEmit32.Mla, OpCode32AluMla.Create); + SetA32("<<<<00000110xxxxxxxxxxxx1001xxxx", InstName.Mls, InstEmit32.Mls, OpCode32AluMla.Create); + SetA32("<<<<0011101x0000xxxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluImm.Create); + SetA32("<<<<0001101x0000xxxxxxxxxxx0xxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluRsImm.Create); + SetA32("<<<<0001101x0000xxxxxxxx0xx1xxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluRsReg.Create); + SetA32("<<<<00110000xxxxxxxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluImm16.Create); + SetA32("<<<<00110100xxxxxxxxxxxxxxxxxxxx", InstName.Movt, InstEmit32.Movt, OpCode32AluImm16.Create); + SetA32("<<<<1110xxx1xxxxxxxx111xxxx1xxxx", InstName.Mrc, InstEmit32.Mrc, OpCode32System.Create); + SetA32("<<<<11000101xxxxxxxx111xxxxxxxxx", InstName.Mrrc, InstEmit32.Mrrc, OpCode32System.Create); + SetA32("<<<<00010x001111xxxx000000000000", InstName.Mrs, InstEmit32.Mrs, OpCode32Mrs.Create); + SetA32("<<<<00010x10xxxx111100000000xxxx", InstName.Msr, InstEmit32.Msr, OpCode32MsrReg.Create); + SetA32("<<<<0000000xxxxx0000xxxx1001xxxx", InstName.Mul, InstEmit32.Mul, OpCode32AluMla.Create); + SetA32("<<<<0011111x0000xxxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCode32AluImm.Create); + SetA32("<<<<0001111x0000xxxxxxxxxxx0xxxx", InstName.Mvn, InstEmit32.Mvn, OpCode32AluRsImm.Create); + SetA32("<<<<0001111x0000xxxxxxxx0xx1xxxx", InstName.Mvn, InstEmit32.Mvn, OpCode32AluRsReg.Create); + SetA32("<<<<0011001000001111000000000000", InstName.Nop, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<0011100xxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit32.Orr, OpCode32AluImm.Create); + SetA32("<<<<0001100xxxxxxxxxxxxxxxx0xxxx", InstName.Orr, InstEmit32.Orr, OpCode32AluRsImm.Create); + SetA32("<<<<0001100xxxxxxxxxxxxx0xx1xxxx", InstName.Orr, InstEmit32.Orr, OpCode32AluRsReg.Create); + SetA32("<<<<01101000xxxxxxxxxxxxxx01xxxx", InstName.Pkh, InstEmit32.Pkh, OpCode32AluRsImm.Create); + SetA32("11110101xx01xxxx1111xxxxxxxxxxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create); + SetA32("11110111xx01xxxx1111xxxxxxx0xxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<01100010xxxxxxxx11110001xxxx", InstName.Qadd16, InstEmit32.Qadd16, OpCode32AluReg.Create); + SetA32("<<<<011011111111xxxx11110011xxxx", InstName.Rbit, InstEmit32.Rbit, OpCode32AluReg.Create); + SetA32("<<<<011010111111xxxx11110011xxxx", InstName.Rev, InstEmit32.Rev, OpCode32AluReg.Create); + SetA32("<<<<011010111111xxxx11111011xxxx", InstName.Rev16, InstEmit32.Rev16, OpCode32AluReg.Create); + SetA32("<<<<011011111111xxxx11111011xxxx", InstName.Revsh, InstEmit32.Revsh, OpCode32AluReg.Create); + SetA32("<<<<0010011xxxxxxxxxxxxxxxxxxxxx", InstName.Rsb, InstEmit32.Rsb, OpCode32AluImm.Create); + SetA32("<<<<0000011xxxxxxxxxxxxxxxx0xxxx", InstName.Rsb, InstEmit32.Rsb, OpCode32AluRsImm.Create); + SetA32("<<<<0000011xxxxxxxxxxxxx0xx1xxxx", InstName.Rsb, InstEmit32.Rsb, OpCode32AluRsReg.Create); + SetA32("<<<<0010111xxxxxxxxxxxxxxxxxxxxx", InstName.Rsc, InstEmit32.Rsc, OpCode32AluImm.Create); + SetA32("<<<<0000111xxxxxxxxxxxxxxxx0xxxx", InstName.Rsc, InstEmit32.Rsc, OpCode32AluRsImm.Create); + SetA32("<<<<0000111xxxxxxxxxxxxx0xx1xxxx", InstName.Rsc, InstEmit32.Rsc, OpCode32AluRsReg.Create); + SetA32("<<<<01100001xxxxxxxx11111001xxxx", InstName.Sadd8, InstEmit32.Sadd8, OpCode32AluReg.Create); + SetA32("<<<<0010110xxxxxxxxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCode32AluImm.Create); + SetA32("<<<<0000110xxxxxxxxxxxxxxxx0xxxx", InstName.Sbc, InstEmit32.Sbc, OpCode32AluRsImm.Create); + SetA32("<<<<0000110xxxxxxxxxxxxx0xx1xxxx", InstName.Sbc, InstEmit32.Sbc, OpCode32AluRsReg.Create); + SetA32("<<<<0111101xxxxxxxxxxxxxx101xxxx", InstName.Sbfx, InstEmit32.Sbfx, OpCode32AluBf.Create); + SetA32("<<<<01110001xxxx1111xxxx0001xxxx", InstName.Sdiv, InstEmit32.Sdiv, OpCode32AluMla.Create); + SetA32("<<<<01101000xxxxxxxx11111011xxxx", InstName.Sel, InstEmit32.Sel, OpCode32AluReg.Create); + SetA32("<<<<0011001000001111000000000100", InstName.Sev, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<0011001000001111000000000101", InstName.Sevl, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<01100011xxxxxxxx11111001xxxx", InstName.Shadd8, InstEmit32.Shadd8, OpCode32AluReg.Create); + SetA32("<<<<01100011xxxxxxxx11111111xxxx", InstName.Shsub8, InstEmit32.Shsub8, OpCode32AluReg.Create); + SetA32("<<<<00010000xxxxxxxxxxxx1xx0xxxx", InstName.Smla__, InstEmit32.Smla__, OpCode32AluMla.Create); + SetA32("<<<<0000111xxxxxxxxxxxxx1001xxxx", InstName.Smlal, InstEmit32.Smlal, OpCode32AluUmull.Create); + SetA32("<<<<00010100xxxxxxxxxxxx1xx0xxxx", InstName.Smlal__, InstEmit32.Smlal__, OpCode32AluUmull.Create); + SetA32("<<<<00010010xxxxxxxxxxxx1x00xxxx", InstName.Smlaw_, InstEmit32.Smlaw_, OpCode32AluMla.Create); + SetA32("<<<<01110101xxxxxxxxxxxx00x1xxxx", InstName.Smmla, InstEmit32.Smmla, OpCode32AluMla.Create); + SetA32("<<<<01110101xxxxxxxxxxxx11x1xxxx", InstName.Smmls, InstEmit32.Smmls, OpCode32AluMla.Create); + SetA32("<<<<00010110xxxxxxxxxxxx1xx0xxxx", InstName.Smul__, InstEmit32.Smul__, OpCode32AluMla.Create); + SetA32("<<<<0000110xxxxxxxxxxxxx1001xxxx", InstName.Smull, InstEmit32.Smull, OpCode32AluUmull.Create); + SetA32("<<<<00010010xxxx0000xxxx1x10xxxx", InstName.Smulw_, InstEmit32.Smulw_, OpCode32AluMla.Create); + SetA32("<<<<0110101xxxxxxxxxxxxxxx01xxxx", InstName.Ssat, InstEmit32.Ssat, OpCode32Sat.Create); + SetA32("<<<<01101010xxxxxxxx11110011xxxx", InstName.Ssat16, InstEmit32.Ssat16, OpCode32Sat16.Create); + SetA32("<<<<01100001xxxxxxxx11111111xxxx", InstName.Ssub8, InstEmit32.Ssub8, OpCode32AluReg.Create); + SetA32("<<<<00011000xxxx111111001001xxxx", InstName.Stl, InstEmit32.Stl, OpCode32MemStEx.Create); + SetA32("<<<<00011100xxxx111111001001xxxx", InstName.Stlb, InstEmit32.Stlb, OpCode32MemStEx.Create); + SetA32("<<<<00011000xxxxxxxx11101001xxxx", InstName.Stlex, InstEmit32.Stlex, OpCode32MemStEx.Create); + SetA32("<<<<00011100xxxxxxxx11101001xxxx", InstName.Stlexb, InstEmit32.Stlexb, OpCode32MemStEx.Create); + SetA32("<<<<00011010xxxxxxxx11101001xxxx", InstName.Stlexd, InstEmit32.Stlexd, OpCode32MemStEx.Create); + SetA32("<<<<00011110xxxxxxxx11101001xxxx", InstName.Stlexh, InstEmit32.Stlexh, OpCode32MemStEx.Create); + SetA32("<<<<00011110xxxx111111001001xxxx", InstName.Stlh, InstEmit32.Stlh, OpCode32MemStEx.Create); + SetA32("<<<<100xx0x0xxxxxxxxxxxxxxxxxxxx", InstName.Stm, InstEmit32.Stm, OpCode32MemMult.Create); + SetA32("<<<<010xx0x0xxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit32.Str, OpCode32MemImm.Create); + SetA32("<<<<011xx0x0xxxxxxxxxxxxxxx0xxxx", InstName.Str, InstEmit32.Str, OpCode32MemRsImm.Create); + SetA32("<<<<010xx1x0xxxxxxxxxxxxxxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCode32MemImm.Create); + SetA32("<<<<011xx1x0xxxxxxxxxxxxxxx0xxxx", InstName.Strb, InstEmit32.Strb, OpCode32MemRsImm.Create); + SetA32("<<<<000xx1x0xxxxxxxxxxxx1111xxxx", InstName.Strd, InstEmit32.Strd, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x0xxxxxxxx00001111xxxx", InstName.Strd, InstEmit32.Strd, OpCode32MemReg.Create); + SetA32("<<<<00011000xxxxxxxx11111001xxxx", InstName.Strex, InstEmit32.Strex, OpCode32MemStEx.Create); + SetA32("<<<<00011100xxxxxxxx11111001xxxx", InstName.Strexb, InstEmit32.Strexb, OpCode32MemStEx.Create); + SetA32("<<<<00011010xxxxxxxx11111001xxxx", InstName.Strexd, InstEmit32.Strexd, OpCode32MemStEx.Create); + SetA32("<<<<00011110xxxxxxxx11111001xxxx", InstName.Strexh, InstEmit32.Strexh, OpCode32MemStEx.Create); + SetA32("<<<<000xx1x0xxxxxxxxxxxx1011xxxx", InstName.Strh, InstEmit32.Strh, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x0xxxxxxxx00001011xxxx", InstName.Strh, InstEmit32.Strh, OpCode32MemReg.Create); + SetA32("<<<<0010010xxxxxxxxxxxxxxxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCode32AluImm.Create); + SetA32("<<<<0000010xxxxxxxxxxxxxxxx0xxxx", InstName.Sub, InstEmit32.Sub, OpCode32AluRsImm.Create); + SetA32("<<<<0000010xxxxxxxxxxxxx0xx1xxxx", InstName.Sub, InstEmit32.Sub, OpCode32AluRsReg.Create); + SetA32("<<<<1111xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Svc, InstEmit32.Svc, OpCode32Exception.Create); + SetA32("<<<<01101010xxxxxxxxxx000111xxxx", InstName.Sxtb, InstEmit32.Sxtb, OpCode32AluUx.Create); + SetA32("<<<<01101000xxxxxxxxxx000111xxxx", InstName.Sxtb16, InstEmit32.Sxtb16, OpCode32AluUx.Create); + SetA32("<<<<01101011xxxxxxxxxx000111xxxx", InstName.Sxth, InstEmit32.Sxth, OpCode32AluUx.Create); + SetA32("<<<<00110011xxxx0000xxxxxxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCode32AluImm.Create); + SetA32("<<<<00010011xxxx0000xxxxxxx0xxxx", InstName.Teq, InstEmit32.Teq, OpCode32AluRsImm.Create); + SetA32("<<<<00010011xxxx0000xxxx0xx1xxxx", InstName.Teq, InstEmit32.Teq, OpCode32AluRsReg.Create); + SetA32("<<<<0111111111111101111011111110", InstName.Trap, InstEmit32.Trap, OpCode32Exception.Create); + SetA32("<<<<0011001000001111000000010010", InstName.Tsb, InstEmit32.Nop, OpCode32.Create); // Trace Synchronization Barrier (FEAT_TRF) + SetA32("<<<<00110001xxxx0000xxxxxxxxxxxx", InstName.Tst, InstEmit32.Tst, OpCode32AluImm.Create); + SetA32("<<<<00010001xxxx0000xxxxxxx0xxxx", InstName.Tst, InstEmit32.Tst, OpCode32AluRsImm.Create); + SetA32("<<<<00010001xxxx0000xxxx0xx1xxxx", InstName.Tst, InstEmit32.Tst, OpCode32AluRsReg.Create); + SetA32("<<<<01100101xxxxxxxx11111001xxxx", InstName.Uadd8, InstEmit32.Uadd8, OpCode32AluReg.Create); + SetA32("<<<<0111111xxxxxxxxxxxxxx101xxxx", InstName.Ubfx, InstEmit32.Ubfx, OpCode32AluBf.Create); + SetA32("<<<<01110011xxxx1111xxxx0001xxxx", InstName.Udiv, InstEmit32.Udiv, OpCode32AluMla.Create); + SetA32("<<<<01100111xxxxxxxx11111001xxxx", InstName.Uhadd8, InstEmit32.Uhadd8, OpCode32AluReg.Create); + SetA32("<<<<01100111xxxxxxxx11111111xxxx", InstName.Uhsub8, InstEmit32.Uhsub8, OpCode32AluReg.Create); + SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create); + SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create); + SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create); + SetA32("<<<<01100110xxxxxxxx11110001xxxx", InstName.Uqadd16, InstEmit32.Uqadd16, OpCode32AluReg.Create); + SetA32("<<<<01100110xxxxxxxx11111001xxxx", InstName.Uqadd8, InstEmit32.Uqadd8, OpCode32AluReg.Create); + SetA32("<<<<01100110xxxxxxxx11110111xxxx", InstName.Uqsub16, InstEmit32.Uqsub16, OpCode32AluReg.Create); + SetA32("<<<<01100110xxxxxxxx11111111xxxx", InstName.Uqsub8, InstEmit32.Uqsub8, OpCode32AluReg.Create); + SetA32("<<<<0110111xxxxxxxxxxxxxxx01xxxx", InstName.Usat, InstEmit32.Usat, OpCode32Sat.Create); + SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create); + SetA32("<<<<01100101xxxxxxxx11111111xxxx", InstName.Usub8, InstEmit32.Usub8, OpCode32AluReg.Create); + SetA32("<<<<01101110xxxxxxxxxx000111xxxx", InstName.Uxtb, InstEmit32.Uxtb, OpCode32AluUx.Create); + SetA32("<<<<01101100xxxxxxxxxx000111xxxx", InstName.Uxtb16, InstEmit32.Uxtb16, OpCode32AluUx.Create); + SetA32("<<<<01101111xxxxxxxxxx000111xxxx", InstName.Uxth, InstEmit32.Uxth, OpCode32AluUx.Create); + SetA32("<<<<0011001000001111000000000010", InstName.Wfe, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<0011001000001111000000000011", InstName.Wfi, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<0011001000001111000000000001", InstName.Yield, InstEmit32.Nop, OpCode32.Create); + + // VFP + SetVfp("<<<<11101x110000xxxx101x11x0xxxx", InstName.Vabs, InstEmit32.Vabs_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11100x11xxxxxxxx101xx0x0xxxx", InstName.Vadd, InstEmit32.Vadd_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x11010xxxxx101x01x0xxxx", InstName.Vcmp, InstEmit32.Vcmp, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x11010xxxxx101x11x0xxxx", InstName.Vcmpe, InstEmit32.Vcmpe, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x110111xxxx101x11x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_FD, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); // FP 32 and 64, scalar. + SetVfp("<<<<11101x11110xxxxx101x11x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_FI, OpCode32SimdCvtFI.Create, OpCode32SimdCvtFI.CreateT32); // FP32 to int. + SetVfp("<<<<11101x111000xxxx101xx1x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_FI, OpCode32SimdCvtFI.Create, OpCode32SimdCvtFI.CreateT32); // Int to FP32. + SetVfp("111111101x1111xxxxxx101xx1x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_RM, OpCode32SimdCvtFI.Create, OpCode32SimdCvtFI.CreateT32); // The many FP32 to int encodings (fp). + SetVfp("<<<<11101x11001xxxxx101xx1x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_TB, OpCode32SimdCvtTB.Create, OpCode32SimdCvtTB.CreateT32); + SetVfp("<<<<11101x00xxxxxxxx101xx0x0xxxx", InstName.Vdiv, InstEmit32.Vdiv_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101xx0xxxxxxxx1011x0x10000", InstName.Vdup, InstEmit32.Vdup, OpCode32SimdDupGP.Create, OpCode32SimdDupGP.CreateT32); + SetVfp("<<<<11101x10xxxxxxxx101xx0x0xxxx", InstName.Vfma, InstEmit32.Vfma_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x10xxxxxxxx101xx1x0xxxx", InstName.Vfms, InstEmit32.Vfms_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x01xxxxxxxx101xx1x0xxxx", InstName.Vfnma, InstEmit32.Vfnma_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x01xxxxxxxx101xx0x0xxxx", InstName.Vfnms, InstEmit32.Vfnms_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11001x01xxxxxxxx1011xxxxxxx0", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x11xxxxxxxx1011xxxxxxx0", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11010x11xxxxxxxx1011xxxxxxx0", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x01xxxxxxxx1010xxxxxxxx", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x11xxxxxxxx1010xxxxxxxx", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11010x11xxxxxxxx1010xxxxxxxx", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<1101xx01xxxxxxxx101xxxxxxxxx", InstName.Vldr, InstEmit32.Vldr, OpCode32SimdMemImm.Create, OpCode32SimdMemImm.CreateT32); + SetVfp("111111101x00xxxxxxxx10>>x0x0xxxx", InstName.Vmaxnm, InstEmit32.Vmaxnm_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("111111101x00xxxxxxxx10>>x1x0xxxx", InstName.Vminnm, InstEmit32.Vminnm_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100x00xxxxxxxx101xx0x0xxxx", InstName.Vmla, InstEmit32.Vmla_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100x00xxxxxxxx101xx1x0xxxx", InstName.Vmls, InstEmit32.Vmls_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100xx0xxxxxxxx1011xxx10000", InstName.Vmov, InstEmit32.Vmov_G1, OpCode32SimdMovGpElem.Create, OpCode32SimdMovGpElem.CreateT32); // From gen purpose. + SetVfp("<<<<1110xxx1xxxxxxxx1011xxx10000", InstName.Vmov, InstEmit32.Vmov_G1, OpCode32SimdMovGpElem.Create, OpCode32SimdMovGpElem.CreateT32); // To gen purpose. + SetVfp("<<<<1100010xxxxxxxxx101000x1xxxx", InstName.Vmov, InstEmit32.Vmov_G2, OpCode32SimdMovGpDouble.Create, OpCode32SimdMovGpDouble.CreateT32); // To/from gen purpose x2 and single precision x2. + SetVfp("<<<<1100010xxxxxxxxx101100x1xxxx", InstName.Vmov, InstEmit32.Vmov_GD, OpCode32SimdMovGpDouble.Create, OpCode32SimdMovGpDouble.CreateT32); // To/from gen purpose x2 and double precision. + SetVfp("<<<<1110000xxxxxxxxx1010x0010000", InstName.Vmov, InstEmit32.Vmov_GS, OpCode32SimdMovGp.Create, OpCode32SimdMovGp.CreateT32); // To/from gen purpose and single precision. + SetVfp("<<<<11101x11xxxxxxxx101x0000xxxx", InstName.Vmov, InstEmit32.Vmov_I, OpCode32SimdImm44.Create, OpCode32SimdImm44.CreateT32); // Scalar f16/32/64 based on size 01 10 11. + SetVfp("<<<<11101x110000xxxx101x01x0xxxx", InstName.Vmov, InstEmit32.Vmov_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101111xxxxxxxx101000010000", InstName.Vmrs, InstEmit32.Vmrs, OpCode32SimdSpecial.Create, OpCode32SimdSpecial.CreateT32); + SetVfp("<<<<11101110xxxxxxxx101000010000", InstName.Vmsr, InstEmit32.Vmsr, OpCode32SimdSpecial.Create, OpCode32SimdSpecial.CreateT32); + SetVfp("<<<<11100x10xxxxxxxx101xx0x0xxxx", InstName.Vmul, InstEmit32.Vmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x110001xxxx101x01x0xxxx", InstName.Vneg, InstEmit32.Vneg_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11100x01xxxxxxxx101xx1x0xxxx", InstName.Vnmla, InstEmit32.Vnmla_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100x01xxxxxxxx101xx0x0xxxx", InstName.Vnmls, InstEmit32.Vnmls_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x110110xxxx101x01x0xxxx", InstName.Vrintr, InstEmit32.Vrintr_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create, OpCode32SimdSel.CreateT32); + SetVfp("<<<<11001x00xxxxxxxx1011xxxxxxx0", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x10xxxxxxxx1011xxxxxxx0", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11010x10xxxxxxxx1011xxxxxxx0", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x00xxxxxxxx1010xxxxxxxx", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x10xxxxxxxx1010xxxxxxxx", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11010x10xxxxxxxx1010xxxxxxxx", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<1101xx00xxxxxxxx101xxxxxxxxx", InstName.Vstr, InstEmit32.Vstr, OpCode32SimdMemImm.Create, OpCode32SimdMemImm.CreateT32); + SetVfp("<<<<11100x11xxxxxxxx101xx1x0xxxx", InstName.Vsub, InstEmit32.Vsub_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + + // ASIMD + SetAsimd("111100111x110000xxx0001101x0xxx0", InstName.Aesd_V, InstEmit32.Aesd_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100111x110000xxx0001100x0xxx0", InstName.Aese_V, InstEmit32.Aese_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100111x110000xxx0001111x0xxx0", InstName.Aesimc_V, InstEmit32.Aesimc_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100111x110000xxx0001110x0xxx0", InstName.Aesmc_V, InstEmit32.Aesmc_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100110x00xxx0xxx01100x1x0xxx0", InstName.Sha256h_V, InstEmit32.Sha256h_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("111100110x01xxx0xxx01100x1x0xxx0", InstName.Sha256h2_V, InstEmit32.Sha256h2_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("111100111x111010xxx0001111x0xxx0", InstName.Sha256su0_V, InstEmit32.Sha256su0_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100110x10xxx0xxx01100x1x0xxx0", InstName.Sha256su1_V, InstEmit32.Sha256su1_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("1111001x0x<xxxx", InstName.Vld4, InstEmit32.Vld4, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101000x10xxxxxxxx000x<>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("111100111x>>>xxxxxxx100000x1xxx0", InstName.Vqshrun, InstEmit32.Vqshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("1111001x0xxxxxxxxxxx0010xxx1xxxx", InstName.Vqsub, InstEmit32.Vqsub, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("111100111x111011xxxx010x0xx0xxxx", InstName.Vrecpe, InstEmit32.Vrecpe, OpCode32SimdSqrte.Create, OpCode32SimdSqrte.CreateT32); + SetAsimd("111100100x00xxxxxxxx1111xxx1xxxx", InstName.Vrecps, InstEmit32.Vrecps, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("111100111x11xx00xxxx000<>>xxxxxxx0010>xx1xxxx", InstName.Vrshr, InstEmit32.Vrshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("111100101x>>>xxxxxxx100001x1xxx0", InstName.Vrshrn, InstEmit32.Vrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("111100111x111011xxxx010x1xx0xxxx", InstName.Vrsqrte, InstEmit32.Vrsqrte, OpCode32SimdSqrte.Create, OpCode32SimdSqrte.CreateT32); + SetAsimd("111100100x10xxxxxxxx1111xxx1xxxx", InstName.Vrsqrts, InstEmit32.Vrsqrts, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("1111001x1x>>>xxxxxxx0011>xx1xxxx", InstName.Vrsra, InstEmit32.Vrsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("111100101x>>>xxxxxxx0101>xx1xxxx", InstName.Vshl, InstEmit32.Vshl, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("1111001x0xxxxxxxxxxx0100xxx0xxxx", InstName.Vshl, InstEmit32.Vshl_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("1111001x1x>>>xxxxxxx101000x1xxxx", InstName.Vshll, InstEmit32.Vshll, OpCode32SimdShImmLong.Create, OpCode32SimdShImmLong.CreateT32); // A1 encoding. + SetAsimd("111100111x11<<10xxxx001100x0xxxx", InstName.Vshll, InstEmit32.Vshll2, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32); // A2 encoding. + SetAsimd("1111001x1x>>>xxxxxxx0000>xx1xxxx", InstName.Vshr, InstEmit32.Vshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("111100101x>>>xxxxxxx100000x1xxx0", InstName.Vshrn, InstEmit32.Vshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("111100111x>>>xxxxxxx0101>xx1xxxx", InstName.Vsli, InstEmit32.Vsli_I, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("1111001x1x>>>xxxxxxx0001>xx1xxxx", InstName.Vsra, InstEmit32.Vsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101001x00xxxxxxxx0100xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101001x00xxxxxxxx1000x000xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101001x00xxxxxxxx1000x011xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101000x00xxxxxxxx0111xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemPair.Create, OpCode32SimdMemPair.CreateT32); // Regs = 1. + SetAsimd("111101000x00xxxxxxxx1010xx<>>>", InstName.It, InstEmit32.It, OpCodeT16IfThen.Create); + SetT16("11000xxxxxxxxxxx", InstName.Stm, InstEmit32.Stm, OpCodeT16MemMult.Create); + SetT16("11001xxxxxxxxxxx", InstName.Ldm, InstEmit32.Ldm, OpCodeT16MemMult.Create); + SetT16("1101<< allInsts, Func toFastLookupIndex) + { + List[] temp = new List[FastLookupSize]; + + for (int index = 0; index < temp.Length; index++) + { + temp[index] = new List(); + } + + foreach (InstInfo inst in allInsts) + { + int mask = toFastLookupIndex(inst.Mask); + int value = toFastLookupIndex(inst.Value); + + for (int index = 0; index < temp.Length; index++) + { + if ((index & mask) == value) + { + temp[index].Add(inst); + } + } + } + + for (int index = 0; index < temp.Length; index++) + { + table[index] = temp[index].ToArray(); + } + } + + private static void SetA32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp) + { + Set(encoding, _allInstA32, new InstDescriptor(name, emitter), makeOp); + } + + private static void SetT16(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp) + { + encoding = "xxxxxxxxxxxxxxxx" + encoding; + Set(encoding, _allInstT32, new InstDescriptor(name, emitter), makeOp); + } + + private static void SetT32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp) + { + string reversedEncoding = $"{encoding.AsSpan(16)}{encoding.AsSpan(0, 16)}"; + OpCode ReversedMakeOp(InstDescriptor inst, ulong address, int opCode) + => makeOp(inst, address, (int)BitOperations.RotateRight((uint)opCode, 16)); + Set(reversedEncoding, _allInstT32, new InstDescriptor(name, emitter), ReversedMakeOp); + } + + private static void SetVfp(string encoding, InstName name, InstEmitter emitter, MakeOp makeOpA32, MakeOp makeOpT32) + { + SetA32(encoding, name, emitter, makeOpA32); + + string thumbEncoding = encoding; + if (thumbEncoding.StartsWith("<<<<")) + { + thumbEncoding = $"1110{thumbEncoding.AsSpan(4)}"; + } + SetT32(thumbEncoding, name, emitter, makeOpT32); + } + + private static void SetAsimd(string encoding, InstName name, InstEmitter emitter, MakeOp makeOpA32, MakeOp makeOpT32) + { + SetA32(encoding, name, emitter, makeOpA32); + + string thumbEncoding = encoding; + if (thumbEncoding.StartsWith("11110100")) + { + thumbEncoding = $"11111001{encoding.AsSpan(8)}"; + } + else if (thumbEncoding.StartsWith("1111001x")) + { + thumbEncoding = $"111x1111{encoding.AsSpan(8)}"; + } + else if (thumbEncoding.StartsWith("11110010")) + { + thumbEncoding = $"11101111{encoding.AsSpan(8)}"; + } + else if (thumbEncoding.StartsWith("11110011")) + { + thumbEncoding = $"11111111{encoding.AsSpan(8)}"; + } + else + { + throw new ArgumentException("Invalid ASIMD instruction encoding"); + } + SetT32(thumbEncoding, name, emitter, makeOpT32); + } + + private static void SetA64(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp) + { + Set(encoding, _allInstA64, new InstDescriptor(name, emitter), makeOp); + } + + private static void Set(string encoding, List list, InstDescriptor inst, MakeOp makeOp) + { + int bit = encoding.Length - 1; + int value = 0; + int xMask = 0; + int xBits = 0; + + int[] xPos = new int[encoding.Length]; + + int blacklisted = 0; + + for (int index = 0; index < encoding.Length; index++, bit--) + { + // Note: < and > are used on special encodings. + // The < means that we should never have ALL bits with the '<' set. + // So, when the encoding has <<, it means that 00, 01, and 10 are valid, + // but not 11. <<< is 000, 001, ..., 110 but NOT 111, and so on... + // For >, the invalid value is zero. So, for >> 01, 10 and 11 are valid, + // but 00 isn't. + char chr = encoding[index]; + + if (chr == '1') + { + value |= 1 << bit; + } + else if (chr == 'x') + { + xMask |= 1 << bit; + } + else if (chr == '>') + { + xPos[xBits++] = bit; + } + else if (chr == '<') + { + xPos[xBits++] = bit; + + blacklisted |= 1 << bit; + } + else if (chr != '0') + { + throw new ArgumentException($"Invalid encoding: {encoding}", nameof(encoding)); + } + } + + xMask = ~xMask; + + if (xBits == 0) + { + list.Add(new InstInfo(xMask, value, inst, makeOp)); + + return; + } + + for (int index = 0; index < (1 << xBits); index++) + { + int mask = 0; + + for (int x = 0; x < xBits; x++) + { + mask |= ((index >> x) & 1) << xPos[x]; + } + + if (mask != blacklisted) + { + list.Add(new InstInfo(xMask, value | mask, inst, makeOp)); + } + } + } + + public static (InstDescriptor inst, MakeOp makeOp) GetInstA32(int opCode) + { + return GetInstFromList(_instA32FastLookup[ToFastLookupIndexA(opCode)], opCode); + } + + public static (InstDescriptor inst, MakeOp makeOp) GetInstT32(int opCode) + { + return GetInstFromList(_instT32FastLookup[ToFastLookupIndexT(opCode)], opCode); + } + + public static (InstDescriptor inst, MakeOp makeOp) GetInstA64(int opCode) + { + return GetInstFromList(_instA64FastLookup[ToFastLookupIndexA(opCode)], opCode); + } + + private static (InstDescriptor inst, MakeOp makeOp) GetInstFromList(InstInfo[] insts, int opCode) + { + foreach (InstInfo info in insts) + { + if ((opCode & info.Mask) == info.Value) + { + return (info.Inst, info.MakeOp); + } + } + + return (new InstDescriptor(InstName.Und, InstEmit.Und), null); + } + + private static int ToFastLookupIndexA(int value) + { + return ((value >> 10) & 0x00F) | ((value >> 18) & 0xFF0); + } + + private static int ToFastLookupIndexT(int value) + { + return (value >> 4) & 0xFFF; + } + } +} diff --git a/src/ARMeilleure/Decoders/Optimizations/TailCallRemover.cs b/src/ARMeilleure/Decoders/Optimizations/TailCallRemover.cs new file mode 100644 index 00000000..9d988f0c --- /dev/null +++ b/src/ARMeilleure/Decoders/Optimizations/TailCallRemover.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; + +namespace ARMeilleure.Decoders.Optimizations +{ + static class TailCallRemover + { + public static Block[] RunPass(ulong entryAddress, List blocks) + { + // Detect tail calls: + // - Assume this function spans the space covered by contiguous code blocks surrounding the entry address. + // - A jump to an area outside this contiguous region will be treated as an exit block. + // - Include a small allowance for jumps outside the contiguous range. + + if (!Decoder.BinarySearch(blocks, entryAddress, out int entryBlockId)) + { + throw new InvalidOperationException("Function entry point is not contained in a block."); + } + + const ulong Allowance = 4; + + Block entryBlock = blocks[entryBlockId]; + + Block startBlock = entryBlock; + Block endBlock = entryBlock; + + int startBlockIndex = entryBlockId; + int endBlockIndex = entryBlockId; + + for (int i = entryBlockId + 1; i < blocks.Count; i++) // Search forwards. + { + Block block = blocks[i]; + + if (endBlock.EndAddress < block.Address - Allowance) + { + break; // End of contiguous function. + } + + endBlock = block; + endBlockIndex = i; + } + + for (int i = entryBlockId - 1; i >= 0; i--) // Search backwards. + { + Block block = blocks[i]; + + if (startBlock.Address > block.EndAddress + Allowance) + { + break; // End of contiguous function. + } + + startBlock = block; + startBlockIndex = i; + } + + if (startBlockIndex == 0 && endBlockIndex == blocks.Count - 1) + { + return blocks.ToArray(); // Nothing to do here. + } + + // Mark branches whose target is outside of the contiguous region as an exit block. + for (int i = startBlockIndex; i <= endBlockIndex; i++) + { + Block block = blocks[i]; + + if (block.Branch != null && (block.Branch.Address > endBlock.EndAddress || block.Branch.EndAddress < startBlock.Address)) + { + block.Branch.Exit = true; + } + } + + var newBlocks = new List(blocks.Count); + + // Finally, rebuild decoded block list, ignoring blocks outside the contiguous range. + for (int i = 0; i < blocks.Count; i++) + { + Block block = blocks[i]; + + if (block.Exit || (i >= startBlockIndex && i <= endBlockIndex)) + { + newBlocks.Add(block); + } + } + + return newBlocks.ToArray(); + } + } +} diff --git a/src/ARMeilleure/Decoders/RegisterSize.cs b/src/ARMeilleure/Decoders/RegisterSize.cs new file mode 100644 index 00000000..7c00984e --- /dev/null +++ b/src/ARMeilleure/Decoders/RegisterSize.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + enum RegisterSize + { + Int32, + Int64, + Simd64, + Simd128, + } +} diff --git a/src/ARMeilleure/Decoders/ShiftType.cs b/src/ARMeilleure/Decoders/ShiftType.cs new file mode 100644 index 00000000..43b738f3 --- /dev/null +++ b/src/ARMeilleure/Decoders/ShiftType.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + enum ShiftType + { + Lsl = 0, + Lsr = 1, + Asr = 2, + Ror = 3, + } +} diff --git a/src/ARMeilleure/Diagnostics/IRDumper.cs b/src/ARMeilleure/Diagnostics/IRDumper.cs new file mode 100644 index 00000000..16833d08 --- /dev/null +++ b/src/ARMeilleure/Diagnostics/IRDumper.cs @@ -0,0 +1,327 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ARMeilleure.Diagnostics +{ + class IRDumper + { + private const string Indentation = " "; + + private int _indentLevel; + + private readonly StringBuilder _builder; + + private readonly Dictionary _localNames; + private readonly Dictionary _symbolNames; + + public IRDumper(int indent) + { + _indentLevel = indent; + + _builder = new StringBuilder(); + + _localNames = new Dictionary(); + _symbolNames = new Dictionary(); + } + + private void Indent() + { + _builder.EnsureCapacity(_builder.Capacity + _indentLevel * Indentation.Length); + + for (int index = 0; index < _indentLevel; index++) + { +#pragma warning disable CA1834 // Use StringBuilder.Append(char) for single character strings + _builder.Append(Indentation); +#pragma warning restore CA1834 + } + } + + private void IncreaseIndentation() + { + _indentLevel++; + } + + private void DecreaseIndentation() + { + _indentLevel--; + } + + private void DumpBlockName(BasicBlock block) + { + _builder.Append("block").Append(block.Index); + } + + private void DumpBlockHeader(BasicBlock block) + { + DumpBlockName(block); + + if (block.Frequency == BasicBlockFrequency.Cold) + { + _builder.Append(" cold"); + } + + if (block.SuccessorsCount > 0) + { + _builder.Append(" ("); + + for (int i = 0; i < block.SuccessorsCount; i++) + { + DumpBlockName(block.GetSuccessor(i)); + + if (i < block.SuccessorsCount - 1) + { + _builder.Append(", "); + } + } + + _builder.Append(')'); + } + + _builder.Append(':'); + } + + private void DumpOperand(Operand operand) + { + if (operand == default) + { + _builder.Append(""); + return; + } + + _builder.Append(GetTypeName(operand.Type)).Append(' '); + + switch (operand.Kind) + { + case OperandKind.LocalVariable: + if (!_localNames.TryGetValue(operand, out string localName)) + { + localName = $"%{_localNames.Count}"; + + _localNames.Add(operand, localName); + } + + _builder.Append(localName); + break; + + case OperandKind.Register: + Register reg = operand.GetRegister(); + + switch (reg.Type) + { + case RegisterType.Flag: + _builder.Append('b'); + break; + case RegisterType.FpFlag: + _builder.Append('f'); + break; + case RegisterType.Integer: + _builder.Append('r'); + break; + case RegisterType.Vector: + _builder.Append('v'); + break; + } + + _builder.Append(reg.Index); + break; + + case OperandKind.Constant: + string symbolName = Symbols.Get(operand.Value); + + if (symbolName != null && !_symbolNames.ContainsKey(operand.Value)) + { + _symbolNames.Add(operand.Value, symbolName); + } + + _builder.Append("0x").Append(operand.Value.ToString("X")); + break; + + case OperandKind.Memory: + var memOp = operand.GetMemory(); + + _builder.Append('['); + + DumpOperand(memOp.BaseAddress); + + if (memOp.Index != default) + { + _builder.Append(" + "); + + DumpOperand(memOp.Index); + + switch (memOp.Scale) + { + case Multiplier.x2: + _builder.Append("*2"); + break; + case Multiplier.x4: + _builder.Append("*4"); + break; + case Multiplier.x8: + _builder.Append("*8"); + break; + } + } + + if (memOp.Displacement != 0) + { + _builder.Append(" + 0x").Append(memOp.Displacement.ToString("X")); + } + + _builder.Append(']'); + break; + + default: + _builder.Append(operand.Type); + break; + } + } + + private void DumpNode(ControlFlowGraph cfg, Operation node) + { + for (int index = 0; index < node.DestinationsCount; index++) + { + DumpOperand(node.GetDestination(index)); + + if (index == node.DestinationsCount - 1) + { + _builder.Append(" = "); + } + else + { + _builder.Append(", "); + } + } + + switch (node) + { + case Operation operation: + if (operation.Instruction == Instruction.Phi) + { + PhiOperation phi = operation.AsPhi(); + + _builder.Append("Phi "); + + for (int index = 0; index < phi.SourcesCount; index++) + { + _builder.Append('('); + + DumpBlockName(phi.GetBlock(cfg, index)); + + _builder.Append(": "); + + DumpOperand(phi.GetSource(index)); + + _builder.Append(')'); + + if (index < phi.SourcesCount - 1) + { + _builder.Append(", "); + } + } + + break; + } + + bool comparison = false; + + _builder.Append(operation.Instruction); + + if (operation.Instruction == Instruction.Extended) + { + _builder.Append('.').Append(operation.Intrinsic); + } + else if (operation.Instruction == Instruction.BranchIf || + operation.Instruction == Instruction.Compare) + { + comparison = true; + } + + _builder.Append(' '); + + for (int index = 0; index < operation.SourcesCount; index++) + { + Operand source = operation.GetSource(index); + + if (index < operation.SourcesCount - 1) + { + DumpOperand(source); + + _builder.Append(", "); + } + else if (comparison) + { + _builder.Append((Comparison)source.AsInt32()); + } + else + { + DumpOperand(source); + } + } + break; + } + + if (_symbolNames.Count == 1) + { + _builder.Append(" ;; ").Append(_symbolNames.First().Value); + } + else if (_symbolNames.Count > 1) + { + _builder.Append(" ;;"); + + foreach ((ulong value, string name) in _symbolNames) + { + _builder.Append(" 0x").Append(value.ToString("X")).Append(" = ").Append(name); + } + } + + // Reset the set of symbols for the next Node we're going to dump. + _symbolNames.Clear(); + } + + public static string GetDump(ControlFlowGraph cfg) + { + var dumper = new IRDumper(1); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + dumper.Indent(); + dumper.DumpBlockHeader(block); + + dumper._builder.AppendLine(); + + dumper.IncreaseIndentation(); + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + dumper.Indent(); + dumper.DumpNode(cfg, node); + + dumper._builder.AppendLine(); + } + + dumper.DecreaseIndentation(); + } + + return dumper._builder.ToString(); + } + + private static string GetTypeName(OperandType type) + { + return type switch + { + OperandType.None => "none", + OperandType.I32 => "i32", + OperandType.I64 => "i64", + OperandType.FP32 => "f32", + OperandType.FP64 => "f64", + OperandType.V128 => "v128", + _ => throw new ArgumentException($"Invalid operand type \"{type}\"."), + }; + } + } +} diff --git a/src/ARMeilleure/Diagnostics/Logger.cs b/src/ARMeilleure/Diagnostics/Logger.cs new file mode 100644 index 00000000..d7f61230 --- /dev/null +++ b/src/ARMeilleure/Diagnostics/Logger.cs @@ -0,0 +1,56 @@ +using ARMeilleure.Translation; +using System; +using System.Diagnostics; + +namespace ARMeilleure.Diagnostics +{ + static class Logger + { + private static long _startTime; + + private static readonly long[] _accumulatedTime; + + static Logger() + { + _accumulatedTime = new long[(int)PassName.Count]; + } + + [Conditional("M_DEBUG")] + public static void StartPass(PassName name) + { + WriteOutput(name + " pass started..."); + + _startTime = Stopwatch.GetTimestamp(); + } + + [Conditional("M_DEBUG")] + public static void EndPass(PassName name, ControlFlowGraph cfg) + { + EndPass(name); + + WriteOutput("IR after " + name + " pass:"); + + WriteOutput(IRDumper.GetDump(cfg)); + } + + [Conditional("M_DEBUG")] + public static void EndPass(PassName name) + { + long elapsedTime = Stopwatch.GetTimestamp() - _startTime; + + _accumulatedTime[(int)name] += elapsedTime; + + WriteOutput($"{name} pass ended after {GetMilliseconds(_accumulatedTime[(int)name])} ms..."); + } + + private static long GetMilliseconds(long ticks) + { + return (long)(((double)ticks / Stopwatch.Frequency) * 1000); + } + + private static void WriteOutput(string text) + { + Console.WriteLine(text); + } + } +} diff --git a/src/ARMeilleure/Diagnostics/PassName.cs b/src/ARMeilleure/Diagnostics/PassName.cs new file mode 100644 index 00000000..2d87659f --- /dev/null +++ b/src/ARMeilleure/Diagnostics/PassName.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Diagnostics +{ + enum PassName + { + Decoding, + Translation, + RegisterUsage, + TailMerge, + Dominance, + SsaConstruction, + RegisterToLocal, + Optimization, + PreAllocation, + RegisterAllocation, + CodeGeneration, + + Count, + } +} diff --git a/src/ARMeilleure/Diagnostics/Symbols.cs b/src/ARMeilleure/Diagnostics/Symbols.cs new file mode 100644 index 00000000..be74d2b5 --- /dev/null +++ b/src/ARMeilleure/Diagnostics/Symbols.cs @@ -0,0 +1,85 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace ARMeilleure.Diagnostics +{ + static class Symbols + { + private readonly struct RangedSymbol + { + public readonly ulong Start; + public readonly ulong End; + public readonly ulong ElementSize; + public readonly string Name; + + public RangedSymbol(ulong start, ulong end, ulong elemSize, string name) + { + Start = start; + End = end; + ElementSize = elemSize; + Name = name; + } + } + + private static readonly ConcurrentDictionary _symbols; + private static readonly List _rangedSymbols; + + static Symbols() + { + _symbols = new ConcurrentDictionary(); + _rangedSymbols = new List(); + } + + public static string Get(ulong address) + { + if (_symbols.TryGetValue(address, out string result)) + { + return result; + } + + lock (_rangedSymbols) + { + foreach (RangedSymbol symbol in _rangedSymbols) + { + if (address >= symbol.Start && address <= symbol.End) + { + ulong diff = address - symbol.Start; + ulong rem = diff % symbol.ElementSize; + + StringBuilder resultBuilder = new(); + resultBuilder.Append($"{symbol.Name}_{diff / symbol.ElementSize}"); + + if (rem != 0) + { + resultBuilder.Append($"+{rem}"); + } + + result = resultBuilder.ToString(); + _symbols.TryAdd(address, result); + + return result; + } + } + } + + return null; + } + + [Conditional("M_DEBUG")] + public static void Add(ulong address, string name) + { + _symbols.TryAdd(address, name); + } + + [Conditional("M_DEBUG")] + public static void Add(ulong address, ulong size, ulong elemSize, string name) + { + lock (_rangedSymbols) + { + _rangedSymbols.Add(new RangedSymbol(address, address + size, elemSize, name)); + } + } + } +} diff --git a/src/ARMeilleure/Diagnostics/TranslatorEventSource.cs b/src/ARMeilleure/Diagnostics/TranslatorEventSource.cs new file mode 100644 index 00000000..2e1be8c5 --- /dev/null +++ b/src/ARMeilleure/Diagnostics/TranslatorEventSource.cs @@ -0,0 +1,67 @@ +using System.Diagnostics.Tracing; +using System.Threading; + +namespace ARMeilleure.Diagnostics +{ + [EventSource(Name = "ARMeilleure")] + class TranslatorEventSource : EventSource + { + public static readonly TranslatorEventSource Log = new(); + + private int _rejitQueue; + private ulong _funcTabSize; + private ulong _funcTabLeafSize; + private PollingCounter _rejitQueueCounter; + private PollingCounter _funcTabSizeCounter; + private PollingCounter _funcTabLeafSizeCounter; + + public TranslatorEventSource() + { + _rejitQueueCounter = new PollingCounter("rejit-queue-length", this, () => _rejitQueue) + { + DisplayName = "Rejit Queue Length", + }; + + _funcTabSizeCounter = new PollingCounter("addr-tab-alloc", this, () => _funcTabSize / 1024d / 1024d) + { + DisplayName = "AddressTable Total Bytes Allocated", + DisplayUnits = "MiB", + }; + + _funcTabLeafSizeCounter = new PollingCounter("addr-tab-leaf-alloc", this, () => _funcTabLeafSize / 1024d / 1024d) + { + DisplayName = "AddressTable Total Leaf Bytes Allocated", + DisplayUnits = "MiB", + }; + } + + public void RejitQueueAdd(int count) + { + Interlocked.Add(ref _rejitQueue, count); + } + + public void AddressTableAllocated(int bytes, bool leaf) + { + _funcTabSize += (uint)bytes; + + if (leaf) + { + _funcTabLeafSize += (uint)bytes; + } + } + + protected override void Dispose(bool disposing) + { + _rejitQueueCounter.Dispose(); + _rejitQueueCounter = null; + + _funcTabLeafSizeCounter.Dispose(); + _funcTabLeafSizeCounter = null; + + _funcTabSizeCounter.Dispose(); + _funcTabSizeCounter = null; + + base.Dispose(disposing); + } + } +} diff --git a/src/ARMeilleure/Instructions/CryptoHelper.cs b/src/ARMeilleure/Instructions/CryptoHelper.cs new file mode 100644 index 00000000..ba68cebb --- /dev/null +++ b/src/ARMeilleure/Instructions/CryptoHelper.cs @@ -0,0 +1,282 @@ +// https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf + +using ARMeilleure.State; +using System; + +namespace ARMeilleure.Instructions +{ + static class CryptoHelper + { + #region "LookUp Tables" +#pragma warning disable IDE1006 // Naming rule violation + private static ReadOnlySpan _sBox => new byte[] + { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, + }; + + private static ReadOnlySpan _invSBox => new byte[] + { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d, + }; + + private static ReadOnlySpan _gfMul02 => new byte[] + { + 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, + 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, + 0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, + 0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, + 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, + 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, + 0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, + 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, + 0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05, + 0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25, + 0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45, + 0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65, + 0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85, + 0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, + 0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5, + 0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5, + }; + + private static ReadOnlySpan _gfMul03 => new byte[] + { + 0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11, + 0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21, + 0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71, + 0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41, + 0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1, + 0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1, + 0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1, + 0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81, + 0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a, + 0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba, + 0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea, + 0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda, + 0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a, + 0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a, + 0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a, + 0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a, + }; + + private static ReadOnlySpan _gfMul09 => new byte[] + { + 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, + 0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7, + 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, + 0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc, + 0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01, + 0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91, + 0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a, + 0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa, + 0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b, + 0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b, + 0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0, + 0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30, + 0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed, + 0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d, + 0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6, + 0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46, + }; + + private static ReadOnlySpan _gfMul0B => new byte[] + { + 0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69, + 0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9, + 0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12, + 0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2, + 0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f, + 0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f, + 0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4, + 0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54, + 0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e, + 0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e, + 0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5, + 0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55, + 0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68, + 0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8, + 0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13, + 0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3, + }; + + private static ReadOnlySpan _gfMul0D => new byte[] + { + 0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b, + 0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b, + 0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0, + 0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20, + 0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26, + 0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6, + 0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d, + 0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d, + 0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91, + 0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41, + 0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a, + 0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa, + 0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc, + 0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c, + 0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47, + 0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97, + }; + + private static ReadOnlySpan _gfMul0E => new byte[] + { + 0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a, + 0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba, + 0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81, + 0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61, + 0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7, + 0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17, + 0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c, + 0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc, + 0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b, + 0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb, + 0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0, + 0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20, + 0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6, + 0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56, + 0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d, + 0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d, + }; + + private static ReadOnlySpan _srPerm => new byte[] + { + 0, 13, 10, 7, 4, 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, + }; + + private static ReadOnlySpan _isrPerm => new byte[] + { + 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11, + }; +#pragma warning restore IDE1006 + #endregion + + public static V128 AesInvMixColumns(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int columns = 0; columns <= 3; columns++) + { + int idx = columns << 2; + + byte row0 = inState[idx + 0]; // A, E, I, M: [row0, col0-col3] + byte row1 = inState[idx + 1]; // B, F, J, N: [row1, col0-col3] + byte row2 = inState[idx + 2]; // C, G, K, O: [row2, col0-col3] + byte row3 = inState[idx + 3]; // D, H, L, P: [row3, col0-col3] + + outState[idx + 0] = (byte)((uint)_gfMul0E[row0] ^ _gfMul0B[row1] ^ _gfMul0D[row2] ^ _gfMul09[row3]); + outState[idx + 1] = (byte)((uint)_gfMul09[row0] ^ _gfMul0E[row1] ^ _gfMul0B[row2] ^ _gfMul0D[row3]); + outState[idx + 2] = (byte)((uint)_gfMul0D[row0] ^ _gfMul09[row1] ^ _gfMul0E[row2] ^ _gfMul0B[row3]); + outState[idx + 3] = (byte)((uint)_gfMul0B[row0] ^ _gfMul0D[row1] ^ _gfMul09[row2] ^ _gfMul0E[row3]); + } + + return new V128(outState); + } + + public static V128 AesInvShiftRows(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int idx = 0; idx <= 15; idx++) + { + outState[_isrPerm[idx]] = inState[idx]; + } + + return new V128(outState); + } + + public static V128 AesInvSubBytes(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int idx = 0; idx <= 15; idx++) + { + outState[idx] = _invSBox[inState[idx]]; + } + + return new V128(outState); + } + + public static V128 AesMixColumns(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int columns = 0; columns <= 3; columns++) + { + int idx = columns << 2; + + byte row0 = inState[idx + 0]; // A, E, I, M: [row0, col0-col3] + byte row1 = inState[idx + 1]; // B, F, J, N: [row1, col0-col3] + byte row2 = inState[idx + 2]; // C, G, K, O: [row2, col0-col3] + byte row3 = inState[idx + 3]; // D, H, L, P: [row3, col0-col3] + + outState[idx + 0] = (byte)((uint)_gfMul02[row0] ^ _gfMul03[row1] ^ row2 ^ row3); + outState[idx + 1] = (byte)((uint)row0 ^ _gfMul02[row1] ^ _gfMul03[row2] ^ row3); + outState[idx + 2] = (byte)((uint)row0 ^ row1 ^ _gfMul02[row2] ^ _gfMul03[row3]); + outState[idx + 3] = (byte)((uint)_gfMul03[row0] ^ row1 ^ row2 ^ _gfMul02[row3]); + } + + return new V128(outState); + } + + public static V128 AesShiftRows(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int idx = 0; idx <= 15; idx++) + { + outState[_srPerm[idx]] = inState[idx]; + } + + return new V128(outState); + } + + public static V128 AesSubBytes(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int idx = 0; idx <= 15; idx++) + { + outState[idx] = _sBox[inState[idx]]; + } + + return new V128(outState); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitAlu.cs b/src/ARMeilleure/Instructions/InstEmitAlu.cs new file mode 100644 index 00000000..ac17c32a --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitAlu.cs @@ -0,0 +1,399 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System.Diagnostics; +using static ARMeilleure.Instructions.InstEmitAluHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Adc(ArmEmitterContext context) => EmitAdc(context, setFlags: false); + public static void Adcs(ArmEmitterContext context) => EmitAdc(context, setFlags: true); + + private static void EmitAdc(ArmEmitterContext context, bool setFlags) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.Add(n, m); + + Operand carry = GetFlag(PState.CFlag); + + if (context.CurrOp.RegisterSize == RegisterSize.Int64) + { + carry = context.ZeroExtend32(OperandType.I64, carry); + } + + d = context.Add(d, carry); + + if (setFlags) + { + EmitNZFlagsCheck(context, d); + + EmitAdcsCCheck(context, n, d); + EmitAddsVCheck(context, n, m, d); + } + + SetAluDOrZR(context, d); + } + + public static void Add(ArmEmitterContext context) + { + SetAluD(context, context.Add(GetAluN(context), GetAluM(context))); + } + + public static void Adds(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + context.MarkComparison(n, m); + + Operand d = context.Add(n, m); + + EmitNZFlagsCheck(context, d); + + EmitAddsCCheck(context, n, d); + EmitAddsVCheck(context, n, m, d); + + SetAluDOrZR(context, d); + } + + public static void And(ArmEmitterContext context) + { + SetAluD(context, context.BitwiseAnd(GetAluN(context), GetAluM(context))); + } + + public static void Ands(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.BitwiseAnd(n, m); + + EmitNZFlagsCheck(context, d); + EmitCVFlagsClear(context); + + SetAluDOrZR(context, d); + } + + public static void Asrv(ArmEmitterContext context) + { + SetAluDOrZR(context, context.ShiftRightSI(GetAluN(context), GetAluMShift(context))); + } + + public static void Bic(ArmEmitterContext context) => EmitBic(context, setFlags: false); + public static void Bics(ArmEmitterContext context) => EmitBic(context, setFlags: true); + + private static void EmitBic(ArmEmitterContext context, bool setFlags) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.BitwiseAnd(n, context.BitwiseNot(m)); + + if (setFlags) + { + EmitNZFlagsCheck(context, d); + EmitCVFlagsClear(context); + } + + SetAluD(context, d, setFlags); + } + + public static void Cls(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + Operand nHigh = context.ShiftRightUI(n, Const(1)); + + bool is32Bits = op.RegisterSize == RegisterSize.Int32; + + Operand mask = is32Bits ? Const(int.MaxValue) : Const(long.MaxValue); + + Operand nLow = context.BitwiseAnd(n, mask); + + Operand res = context.CountLeadingZeros(context.BitwiseExclusiveOr(nHigh, nLow)); + + res = context.Subtract(res, Const(res.Type, 1)); + + SetAluDOrZR(context, res); + } + + public static void Clz(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + Operand d = context.CountLeadingZeros(n); + + SetAluDOrZR(context, d); + } + + public static void Eon(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.BitwiseExclusiveOr(n, context.BitwiseNot(m)); + + SetAluD(context, d); + } + + public static void Eor(ArmEmitterContext context) + { + SetAluD(context, context.BitwiseExclusiveOr(GetAluN(context), GetAluM(context))); + } + + public static void Extr(ArmEmitterContext context) + { + OpCodeAluRs op = (OpCodeAluRs)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rm); + + if (op.Shift != 0) + { + if (op.Rn == op.Rm) + { + res = context.RotateRight(res, Const(op.Shift)); + } + else + { + res = context.ShiftRightUI(res, Const(op.Shift)); + + Operand n = GetIntOrZR(context, op.Rn); + + int invShift = op.GetBitsCount() - op.Shift; + + res = context.BitwiseOr(res, context.ShiftLeft(n, Const(invShift))); + } + } + + SetAluDOrZR(context, res); + } + + public static void Lslv(ArmEmitterContext context) + { + SetAluDOrZR(context, context.ShiftLeft(GetAluN(context), GetAluMShift(context))); + } + + public static void Lsrv(ArmEmitterContext context) + { + SetAluDOrZR(context, context.ShiftRightUI(GetAluN(context), GetAluMShift(context))); + } + + public static void Sbc(ArmEmitterContext context) => EmitSbc(context, setFlags: false); + public static void Sbcs(ArmEmitterContext context) => EmitSbc(context, setFlags: true); + + private static void EmitSbc(ArmEmitterContext context, bool setFlags) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.Subtract(n, m); + + Operand borrow = context.BitwiseExclusiveOr(GetFlag(PState.CFlag), Const(1)); + + if (context.CurrOp.RegisterSize == RegisterSize.Int64) + { + borrow = context.ZeroExtend32(OperandType.I64, borrow); + } + + d = context.Subtract(d, borrow); + + if (setFlags) + { + EmitNZFlagsCheck(context, d); + + EmitSbcsCCheck(context, n, m); + EmitSubsVCheck(context, n, m, d); + } + + SetAluDOrZR(context, d); + } + + public static void Sub(ArmEmitterContext context) + { + SetAluD(context, context.Subtract(GetAluN(context), GetAluM(context))); + } + + public static void Subs(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + context.MarkComparison(n, m); + + Operand d = context.Subtract(n, m); + + EmitNZFlagsCheck(context, d); + + EmitSubsCCheck(context, n, m); + EmitSubsVCheck(context, n, m, d); + + SetAluDOrZR(context, d); + } + + public static void Orn(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.BitwiseOr(n, context.BitwiseNot(m)); + + SetAluD(context, d); + } + + public static void Orr(ArmEmitterContext context) + { + SetAluD(context, context.BitwiseOr(GetAluN(context), GetAluM(context))); + } + + public static void Rbit(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand d; + + if (op.RegisterSize == RegisterSize.Int32) + { + d = EmitReverseBits32Op(context, n); + } + else + { + d = EmitReverseBits64Op(context, n); + } + + SetAluDOrZR(context, d); + } + + private static Operand EmitReverseBits64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaaaaaaaaaaaaaaaul)), Const(1)), + context.ShiftLeft(context.BitwiseAnd(op, Const(0x5555555555555555ul)), Const(1))); + + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccccccccccccccccul)), Const(2)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x3333333333333333ul)), Const(2))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xf0f0f0f0f0f0f0f0ul)), Const(4)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x0f0f0f0f0f0f0f0ful)), Const(4))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xff00ff00ff00ff00ul)), Const(8)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x00ff00ff00ff00fful)), Const(8))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xffff0000ffff0000ul)), Const(16)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x0000ffff0000fffful)), Const(16))); + + return context.BitwiseOr(context.ShiftRightUI(val, Const(32)), context.ShiftLeft(val, Const(32))); + } + + public static void Rev16(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand d; + + if (op.RegisterSize == RegisterSize.Int32) + { + d = EmitReverseBytes16_32Op(context, n); + } + else + { + d = EmitReverseBytes16_64Op(context, n); + } + + SetAluDOrZR(context, d); + } + + public static void Rev32(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand d; + + if (op.RegisterSize == RegisterSize.Int32) + { + d = context.ByteSwap(n); + } + else + { + d = EmitReverseBytes32_64Op(context, n); + } + + SetAluDOrZR(context, d); + } + + private static Operand EmitReverseBytes32_64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand val = EmitReverseBytes16_64Op(context, op); + + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xffff0000ffff0000ul)), Const(16)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x0000ffff0000fffful)), Const(16))); + } + + public static void Rev64(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + SetAluDOrZR(context, context.ByteSwap(GetIntOrZR(context, op.Rn))); + } + + public static void Rorv(ArmEmitterContext context) + { + SetAluDOrZR(context, context.RotateRight(GetAluN(context), GetAluMShift(context))); + } + + private static Operand GetAluMShift(ArmEmitterContext context) + { + IOpCodeAluRs op = (IOpCodeAluRs)context.CurrOp; + + Operand m = GetIntOrZR(context, op.Rm); + + if (op.RegisterSize == RegisterSize.Int64) + { + m = context.ConvertI64ToI32(m); + } + + return context.BitwiseAnd(m, Const(context.CurrOp.GetBitsCount() - 1)); + } + + private static void EmitCVFlagsClear(ArmEmitterContext context) + { + SetFlag(context, PState.CFlag, Const(0)); + SetFlag(context, PState.VFlag, Const(0)); + } + + public static void SetAluD(ArmEmitterContext context, Operand d) + { + SetAluD(context, d, x31IsZR: false); + } + + public static void SetAluDOrZR(ArmEmitterContext context, Operand d) + { + SetAluD(context, d, x31IsZR: true); + } + + public static void SetAluD(ArmEmitterContext context, Operand d, bool x31IsZR) + { + IOpCodeAlu op = (IOpCodeAlu)context.CurrOp; + + if ((x31IsZR || op is IOpCodeAluRs) && op.Rd == RegisterConsts.ZeroIndex) + { + return; + } + + SetIntOrSP(context, op.Rd, d); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitAlu32.cs b/src/ARMeilleure/Instructions/InstEmitAlu32.cs new file mode 100644 index 00000000..8eabe093 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitAlu32.cs @@ -0,0 +1,1241 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using static ARMeilleure.Instructions.InstEmitAluHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + [SuppressMessage("Style", "IDE0059: Remove unnecessary value assignment")] + static partial class InstEmit32 + { + public static void Add(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + if (op.Rn == RegisterAlias.Aarch32Pc && op is OpCodeT32AluImm12) + { + // For ADR, PC is always 4 bytes aligned, even in Thumb mode. + n = context.BitwiseAnd(n, Const(~3u)); + } + + Operand res = context.Add(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitAddsCCheck(context, n, res); + EmitAddsVCheck(context, n, m, res); + } + + EmitAluStore(context, res); + } + + public static void Adc(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Add(n, m); + + Operand carry = GetFlag(PState.CFlag); + + res = context.Add(res, carry); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitAdcsCCheck(context, n, res); + EmitAddsVCheck(context, n, m, res); + } + + EmitAluStore(context, res); + } + + public static void And(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseAnd(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Bfc(ArmEmitterContext context) + { + IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; + + Operand d = GetIntA32(context, op.Rd); + Operand res = context.BitwiseAnd(d, Const(~op.DestMask)); + + SetIntA32(context, op.Rd, res); + } + + public static void Bfi(ArmEmitterContext context) + { + IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand d = GetIntA32(context, op.Rd); + Operand part = context.BitwiseAnd(n, Const(op.SourceMask)); + + if (op.Lsb != 0) + { + part = context.ShiftLeft(part, Const(op.Lsb)); + } + + Operand res = context.BitwiseAnd(d, Const(~op.DestMask)); + res = context.BitwiseOr(res, context.BitwiseAnd(part, Const(op.DestMask))); + + SetIntA32(context, op.Rd, res); + } + + public static void Bic(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseAnd(n, context.BitwiseNot(m)); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Clz(ArmEmitterContext context) + { + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.CountLeadingZeros(m); + EmitAluStore(context, res); + } + + public static void Cmp(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Subtract(n, m); + + EmitNZFlagsCheck(context, res); + + EmitSubsCCheck(context, n, res); + EmitSubsVCheck(context, n, m, res); + } + + public static void Cmn(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Add(n, m); + + EmitNZFlagsCheck(context, res); + + EmitAddsCCheck(context, n, res); + EmitAddsVCheck(context, n, m, res); + } + + public static void Eor(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseExclusiveOr(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Mov(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand m = GetAluM(context); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, m); + } + + EmitAluStore(context, m); + } + + public static void Movt(ArmEmitterContext context) + { + IOpCode32AluImm16 op = (IOpCode32AluImm16)context.CurrOp; + + Operand d = GetIntA32(context, op.Rd); + Operand imm = Const(op.Immediate << 16); // Immeditate value as top halfword. + Operand res = context.BitwiseAnd(d, Const(0x0000ffff)); + res = context.BitwiseOr(res, imm); + + EmitAluStore(context, res); + } + + public static void Mul(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.Multiply(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Mvn(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + Operand m = GetAluM(context); + + Operand res = context.BitwiseNot(m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Orr(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseOr(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Orn(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseOr(n, context.BitwiseNot(m)); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Pkh(ArmEmitterContext context) + { + OpCode32AluRsImm op = (OpCode32AluRsImm)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res; + + bool tbform = op.ShiftType == ShiftType.Asr; + if (tbform) + { + res = context.BitwiseOr(context.BitwiseAnd(n, Const(0xFFFF0000)), context.BitwiseAnd(m, Const(0xFFFF))); + } + else + { + res = context.BitwiseOr(context.BitwiseAnd(m, Const(0xFFFF0000)), context.BitwiseAnd(n, Const(0xFFFF))); + } + + EmitAluStore(context, res); + } + + public static void Qadd16(ArmEmitterContext context) + { + OpCode32AluReg op = (OpCode32AluReg)context.CurrOp; + + SetIntA32(context, op.Rd, EmitSigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) => + { + EmitSaturateRange(context, d, context.Add(n, m), 16, unsigned: false, setQ: false); + })); + } + + public static void Rbit(ArmEmitterContext context) + { + Operand m = GetAluM(context); + + Operand res = EmitReverseBits32Op(context, m); + + EmitAluStore(context, res); + } + + public static void Rev(ArmEmitterContext context) + { + Operand m = GetAluM(context); + + Operand res = context.ByteSwap(m); + + EmitAluStore(context, res); + } + + public static void Rev16(ArmEmitterContext context) + { + Operand m = GetAluM(context); + + Operand res = EmitReverseBytes16_32Op(context, m); + + EmitAluStore(context, res); + } + + public static void Revsh(ArmEmitterContext context) + { + Operand m = GetAluM(context); + + Operand res = EmitReverseBytes16_32Op(context, m); + + EmitAluStore(context, context.SignExtend16(OperandType.I32, res)); + } + + public static void Rsc(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Subtract(m, n); + + Operand borrow = context.BitwiseExclusiveOr(GetFlag(PState.CFlag), Const(1)); + + res = context.Subtract(res, borrow); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitSbcsCCheck(context, m, n); + EmitSubsVCheck(context, m, n, res); + } + + EmitAluStore(context, res); + } + + public static void Rsb(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Subtract(m, n); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitSubsCCheck(context, m, res); + EmitSubsVCheck(context, m, n, res); + } + + EmitAluStore(context, res); + } + + public static void Sadd8(ArmEmitterContext context) + { + EmitAddSub8(context, add: true, unsigned: false); + } + + public static void Sbc(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Subtract(n, m); + + Operand borrow = context.BitwiseExclusiveOr(GetFlag(PState.CFlag), Const(1)); + + res = context.Subtract(res, borrow); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitSbcsCCheck(context, n, m); + EmitSubsVCheck(context, n, m, res); + } + + EmitAluStore(context, res); + } + + public static void Sbfx(ArmEmitterContext context) + { + IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; + + var msb = op.Lsb + op.Msb; // For this instruction, the msb is actually a width. + + Operand n = GetIntA32(context, op.Rn); + Operand res = context.ShiftRightSI(context.ShiftLeft(n, Const(31 - msb)), Const(31 - op.Msb)); + + SetIntA32(context, op.Rd, res); + } + + public static void Sdiv(ArmEmitterContext context) + { + EmitDiv(context, unsigned: false); + } + + public static void Sel(ArmEmitterContext context) + { + IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + Operand ge0 = context.ZeroExtend8(OperandType.I32, context.Negate(GetFlag(PState.GE0Flag))); + Operand ge1 = context.ZeroExtend8(OperandType.I32, context.Negate(GetFlag(PState.GE1Flag))); + Operand ge2 = context.ZeroExtend8(OperandType.I32, context.Negate(GetFlag(PState.GE2Flag))); + Operand ge3 = context.Negate(GetFlag(PState.GE3Flag)); + + Operand mask = context.BitwiseOr(ge0, context.ShiftLeft(ge1, Const(8))); + mask = context.BitwiseOr(mask, context.ShiftLeft(ge2, Const(16))); + mask = context.BitwiseOr(mask, context.ShiftLeft(ge3, Const(24))); + + Operand res = context.BitwiseOr(context.BitwiseAnd(n, mask), context.BitwiseAnd(m, context.BitwiseNot(mask))); + + SetIntA32(context, op.Rd, res); + } + + public static void Shadd8(ArmEmitterContext context) + { + EmitHadd8(context, unsigned: false); + } + + public static void Shsub8(ArmEmitterContext context) + { + EmitHsub8(context, unsigned: false); + } + + public static void Ssat(ArmEmitterContext context) + { + OpCode32Sat op = (OpCode32Sat)context.CurrOp; + + EmitSat(context, -(1 << op.SatImm), (1 << op.SatImm) - 1); + } + + public static void Ssat16(ArmEmitterContext context) + { + OpCode32Sat16 op = (OpCode32Sat16)context.CurrOp; + + EmitSat16(context, -(1 << op.SatImm), (1 << op.SatImm) - 1); + } + + public static void Ssub8(ArmEmitterContext context) + { + EmitAddSub8(context, add: false, unsigned: false); + } + + public static void Sub(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + if (op.Rn == RegisterAlias.Aarch32Pc && op is OpCodeT32AluImm12) + { + // For ADR, PC is always 4 bytes aligned, even in Thumb mode. + n = context.BitwiseAnd(n, Const(~3u)); + } + + Operand res = context.Subtract(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitSubsCCheck(context, n, res); + EmitSubsVCheck(context, n, m, res); + } + + EmitAluStore(context, res); + } + + public static void Sxtb(ArmEmitterContext context) + { + EmitSignExtend(context, true, 8); + } + + public static void Sxtb16(ArmEmitterContext context) + { + EmitExtend16(context, true); + } + + public static void Sxth(ArmEmitterContext context) + { + EmitSignExtend(context, true, 16); + } + + public static void Teq(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseExclusiveOr(n, m); + + EmitNZFlagsCheck(context, res); + } + + public static void Tst(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseAnd(n, m); + EmitNZFlagsCheck(context, res); + } + + public static void Uadd8(ArmEmitterContext context) + { + EmitAddSub8(context, add: true, unsigned: true); + } + + public static void Ubfx(ArmEmitterContext context) + { + IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; + + var msb = op.Lsb + op.Msb; // For this instruction, the msb is actually a width. + + Operand n = GetIntA32(context, op.Rn); + Operand res = context.ShiftRightUI(context.ShiftLeft(n, Const(31 - msb)), Const(31 - op.Msb)); + + SetIntA32(context, op.Rd, res); + } + + public static void Udiv(ArmEmitterContext context) + { + EmitDiv(context, unsigned: true); + } + + public static void Uhadd8(ArmEmitterContext context) + { + EmitHadd8(context, unsigned: true); + } + + public static void Uhsub8(ArmEmitterContext context) + { + EmitHsub8(context, unsigned: true); + } + + public static void Uqadd16(ArmEmitterContext context) + { + OpCode32AluReg op = (OpCode32AluReg)context.CurrOp; + + SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) => + { + EmitSaturateUqadd(context, d, context.Add(n, m), 16); + })); + } + + public static void Uqadd8(ArmEmitterContext context) + { + OpCode32AluReg op = (OpCode32AluReg)context.CurrOp; + + SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) => + { + EmitSaturateUqadd(context, d, context.Add(n, m), 8); + })); + } + + public static void Uqsub16(ArmEmitterContext context) + { + OpCode32AluReg op = (OpCode32AluReg)context.CurrOp; + + SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) => + { + EmitSaturateUqsub(context, d, context.Subtract(n, m), 16); + })); + } + + public static void Uqsub8(ArmEmitterContext context) + { + OpCode32AluReg op = (OpCode32AluReg)context.CurrOp; + + SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) => + { + EmitSaturateUqsub(context, d, context.Subtract(n, m), 8); + })); + } + + public static void Usat(ArmEmitterContext context) + { + OpCode32Sat op = (OpCode32Sat)context.CurrOp; + + EmitSat(context, 0, op.SatImm == 32 ? (int)(~0) : (1 << op.SatImm) - 1); + } + + public static void Usat16(ArmEmitterContext context) + { + OpCode32Sat16 op = (OpCode32Sat16)context.CurrOp; + + EmitSat16(context, 0, (1 << op.SatImm) - 1); + } + + public static void Usub8(ArmEmitterContext context) + { + EmitAddSub8(context, add: false, unsigned: true); + } + + public static void Uxtb(ArmEmitterContext context) + { + EmitSignExtend(context, false, 8); + } + + public static void Uxtb16(ArmEmitterContext context) + { + EmitExtend16(context, false); + } + + public static void Uxth(ArmEmitterContext context) + { + EmitSignExtend(context, false, 16); + } + + private static void EmitSignExtend(ArmEmitterContext context, bool signed, int bits) + { + IOpCode32AluUx op = (IOpCode32AluUx)context.CurrOp; + + Operand m = GetAluM(context); + Operand res; + + if (op.RotateBits == 0) + { + res = m; + } + else + { + Operand rotate = Const(op.RotateBits); + res = context.RotateRight(m, rotate); + } + + switch (bits) + { + case 8: + res = (signed) ? context.SignExtend8(OperandType.I32, res) : context.ZeroExtend8(OperandType.I32, res); + break; + case 16: + res = (signed) ? context.SignExtend16(OperandType.I32, res) : context.ZeroExtend16(OperandType.I32, res); + break; + } + + if (op.Add) + { + res = context.Add(res, GetAluN(context)); + } + + EmitAluStore(context, res); + } + + private static void EmitExtend16(ArmEmitterContext context, bool signed) + { + IOpCode32AluUx op = (IOpCode32AluUx)context.CurrOp; + + Operand m = GetAluM(context); + Operand res; + + if (op.RotateBits == 0) + { + res = m; + } + else + { + Operand rotate = Const(op.RotateBits); + res = context.RotateRight(m, rotate); + } + + Operand low16, high16; + if (signed) + { + low16 = context.SignExtend8(OperandType.I32, res); + high16 = context.SignExtend8(OperandType.I32, context.ShiftRightUI(res, Const(16))); + } + else + { + low16 = context.ZeroExtend8(OperandType.I32, res); + high16 = context.ZeroExtend8(OperandType.I32, context.ShiftRightUI(res, Const(16))); + } + + if (op.Add) + { + Operand n = GetAluN(context); + Operand lowAdd, highAdd; + if (signed) + { + lowAdd = context.SignExtend16(OperandType.I32, n); + highAdd = context.SignExtend16(OperandType.I32, context.ShiftRightUI(n, Const(16))); + } + else + { + lowAdd = context.ZeroExtend16(OperandType.I32, n); + highAdd = context.ZeroExtend16(OperandType.I32, context.ShiftRightUI(n, Const(16))); + } + + low16 = context.Add(low16, lowAdd); + high16 = context.Add(high16, highAdd); + } + + res = context.BitwiseOr( + context.ZeroExtend16(OperandType.I32, low16), + context.ShiftLeft(context.ZeroExtend16(OperandType.I32, high16), Const(16))); + + EmitAluStore(context, res); + } + + private static void EmitDiv(ArmEmitterContext context, bool unsigned) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + Operand zero = Const(m.Type, 0); + + Operand divisorIsZero = context.ICompareEqual(m, zero); + + Operand lblBadDiv = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBadDiv, divisorIsZero); + + if (!unsigned) + { + // ARM64 behaviour: If Rn == INT_MIN && Rm == -1, Rd = INT_MIN (overflow). + // TODO: tests to ensure A32 works the same + + Operand intMin = Const(int.MinValue); + Operand minus1 = Const(-1); + + Operand nIsIntMin = context.ICompareEqual(n, intMin); + Operand mIsMinus1 = context.ICompareEqual(m, minus1); + + Operand lblGoodDiv = Label(); + + context.BranchIfFalse(lblGoodDiv, context.BitwiseAnd(nIsIntMin, mIsMinus1)); + + EmitAluStore(context, intMin); + + context.Branch(lblEnd); + + context.MarkLabel(lblGoodDiv); + } + + Operand res = unsigned + ? context.DivideUI(n, m) + : context.Divide(n, m); + + EmitAluStore(context, res); + + context.Branch(lblEnd); + + context.MarkLabel(lblBadDiv); + + EmitAluStore(context, zero); + + context.MarkLabel(lblEnd); + } + + private static void EmitAddSub8(ArmEmitterContext context, bool add, bool unsigned) + { + IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + Operand res = Const(0); + + for (int byteSel = 0; byteSel < 4; byteSel++) + { + Operand shift = Const(byteSel * 8); + + Operand nByte = context.ShiftRightUI(n, shift); + Operand mByte = context.ShiftRightUI(m, shift); + + nByte = unsigned ? context.ZeroExtend8(OperandType.I32, nByte) : context.SignExtend8(OperandType.I32, nByte); + mByte = unsigned ? context.ZeroExtend8(OperandType.I32, mByte) : context.SignExtend8(OperandType.I32, mByte); + + Operand resByte = add ? context.Add(nByte, mByte) : context.Subtract(nByte, mByte); + + res = context.BitwiseOr(res, context.ShiftLeft(context.ZeroExtend8(OperandType.I32, resByte), shift)); + + SetFlag(context, PState.GE0Flag + byteSel, unsigned && add + ? context.ShiftRightUI(resByte, Const(8)) + : context.ShiftRightUI(context.BitwiseNot(resByte), Const(31))); + } + + SetIntA32(context, op.Rd, res); + } + + private static void EmitHadd8(ArmEmitterContext context, bool unsigned) + { + IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; + + Operand m = GetIntA32(context, op.Rm); + Operand n = GetIntA32(context, op.Rn); + + Operand xor, res, carry; + + // This relies on the equality x+y == ((x&y) << 1) + (x^y). + // Note that x^y always contains the LSB of the result. + // Since we want to calculate (x+y)/2, we can instead calculate (x&y) + ((x^y)>>1). + // We mask by 0x7F to remove the LSB so that it doesn't leak into the field below. + + res = context.BitwiseAnd(m, n); + carry = context.BitwiseExclusiveOr(m, n); + xor = context.ShiftRightUI(carry, Const(1)); + xor = context.BitwiseAnd(xor, Const(0x7F7F7F7Fu)); + res = context.Add(res, xor); + + if (!unsigned) + { + // Propagates the sign bit from (x^y)>>1 upwards by one. + carry = context.BitwiseAnd(carry, Const(0x80808080u)); + res = context.BitwiseExclusiveOr(res, carry); + } + + SetIntA32(context, op.Rd, res); + } + + private static void EmitHsub8(ArmEmitterContext context, bool unsigned) + { + IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; + + Operand m = GetIntA32(context, op.Rm); + Operand n = GetIntA32(context, op.Rn); + Operand left, right, carry, res; + + // This relies on the equality x-y == (x^y) - (((x^y)&y) << 1). + // Note that x^y always contains the LSB of the result. + // Since we want to calculate (x+y)/2, we can instead calculate ((x^y)>>1) - ((x^y)&y). + + carry = context.BitwiseExclusiveOr(m, n); + left = context.ShiftRightUI(carry, Const(1)); + right = context.BitwiseAnd(carry, m); + + // We must now perform a partitioned subtraction. + // We can do this because minuend contains 7 bit fields. + // We use the extra bit in minuend as a bit to borrow from; we set this bit. + // We invert this bit at the end as this tells us if that bit was borrowed from. + + res = context.BitwiseOr(left, Const(0x80808080)); + res = context.Subtract(res, right); + res = context.BitwiseExclusiveOr(res, Const(0x80808080)); + + if (!unsigned) + { + // We then sign extend the result into this bit. + carry = context.BitwiseAnd(carry, Const(0x80808080)); + res = context.BitwiseExclusiveOr(res, carry); + } + + SetIntA32(context, op.Rd, res); + } + + private static void EmitSat(ArmEmitterContext context, int intMin, int intMax) + { + OpCode32Sat op = (OpCode32Sat)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + + int shift = DecodeImmShift(op.ShiftType, op.Imm5); + + switch (op.ShiftType) + { + case ShiftType.Lsl: + if (shift == 32) + { + n = Const(0); + } + else + { + n = context.ShiftLeft(n, Const(shift)); + } + break; + case ShiftType.Asr: + if (shift == 32) + { + n = context.ShiftRightSI(n, Const(31)); + } + else + { + n = context.ShiftRightSI(n, Const(shift)); + } + break; + } + + Operand lblCheckLtIntMin = Label(); + Operand lblNoSat = Label(); + Operand lblEnd = Label(); + + context.BranchIfFalse(lblCheckLtIntMin, context.ICompareGreater(n, Const(intMax))); + + SetFlag(context, PState.QFlag, Const(1)); + SetIntA32(context, op.Rd, Const(intMax)); + context.Branch(lblEnd); + + context.MarkLabel(lblCheckLtIntMin); + context.BranchIfFalse(lblNoSat, context.ICompareLess(n, Const(intMin))); + + SetFlag(context, PState.QFlag, Const(1)); + SetIntA32(context, op.Rd, Const(intMin)); + context.Branch(lblEnd); + + context.MarkLabel(lblNoSat); + + SetIntA32(context, op.Rd, n); + + context.MarkLabel(lblEnd); + } + + private static void EmitSat16(ArmEmitterContext context, int intMin, int intMax) + { + OpCode32Sat16 op = (OpCode32Sat16)context.CurrOp; + + void SetD(int part, Operand value) + { + if (part == 0) + { + SetIntA32(context, op.Rd, context.ZeroExtend16(OperandType.I32, value)); + } + else + { + SetIntA32(context, op.Rd, context.BitwiseOr(GetIntA32(context, op.Rd), context.ShiftLeft(value, Const(16)))); + } + } + + Operand n = GetIntA32(context, op.Rn); + + Operand nLow = context.SignExtend16(OperandType.I32, n); + Operand nHigh = context.ShiftRightSI(n, Const(16)); + + for (int part = 0; part < 2; part++) + { + Operand nPart = part == 0 ? nLow : nHigh; + + Operand lblCheckLtIntMin = Label(); + Operand lblNoSat = Label(); + Operand lblEnd = Label(); + + context.BranchIfFalse(lblCheckLtIntMin, context.ICompareGreater(nPart, Const(intMax))); + + SetFlag(context, PState.QFlag, Const(1)); + SetD(part, Const(intMax)); + context.Branch(lblEnd); + + context.MarkLabel(lblCheckLtIntMin); + context.BranchIfFalse(lblNoSat, context.ICompareLess(nPart, Const(intMin))); + + SetFlag(context, PState.QFlag, Const(1)); + SetD(part, Const(intMin)); + context.Branch(lblEnd); + + context.MarkLabel(lblNoSat); + + SetD(part, nPart); + + context.MarkLabel(lblEnd); + } + } + + private static void EmitSaturateRange(ArmEmitterContext context, Operand result, Operand value, uint saturateTo, bool unsigned, bool setQ = true) + { + Debug.Assert(saturateTo <= 32); + Debug.Assert(!unsigned || saturateTo < 32); + + if (!unsigned && saturateTo == 32) + { + // No saturation possible for this case. + + context.Copy(result, value); + + return; + } + else if (saturateTo == 0) + { + // Result is always zero if we saturate 0 bits. + + context.Copy(result, Const(0)); + + return; + } + + Operand satValue; + + if (unsigned) + { + // Negative values always saturate (to zero). + // So we must always ignore the sign bit when masking, so that the truncated value will differ from the original one. + + satValue = context.BitwiseAnd(value, Const((int)(uint.MaxValue >> (32 - (int)saturateTo)))); + } + else + { + satValue = context.ShiftLeft(value, Const(32 - (int)saturateTo)); + satValue = context.ShiftRightSI(satValue, Const(32 - (int)saturateTo)); + } + + // If the result is 0, the values are equal and we don't need saturation. + Operand lblNoSat = Label(); + context.BranchIfFalse(lblNoSat, context.Subtract(value, satValue)); + + // Saturate and set Q flag. + if (unsigned) + { + if (saturateTo == 31) + { + // Only saturation case possible when going from 32 bits signed to 32 or 31 bits unsigned + // is when the signed input is negative, as all positive values are representable on a 31 bits range. + + satValue = Const(0); + } + else + { + satValue = context.ShiftRightSI(value, Const(31)); + satValue = context.BitwiseNot(satValue); + satValue = context.ShiftRightUI(satValue, Const(32 - (int)saturateTo)); + } + } + else + { + if (saturateTo == 1) + { + satValue = context.ShiftRightSI(value, Const(31)); + } + else + { + satValue = Const(uint.MaxValue >> (33 - (int)saturateTo)); + satValue = context.BitwiseExclusiveOr(satValue, context.ShiftRightSI(value, Const(31))); + } + } + + if (setQ) + { + SetFlag(context, PState.QFlag, Const(1)); + } + + context.Copy(result, satValue); + + Operand lblExit = Label(); + context.Branch(lblExit); + + context.MarkLabel(lblNoSat); + + context.Copy(result, value); + + context.MarkLabel(lblExit); + } + + private static void EmitSaturateUqadd(ArmEmitterContext context, Operand result, Operand value, uint saturateTo) + { + Debug.Assert(saturateTo <= 32); + + if (saturateTo == 32) + { + // No saturation possible for this case. + + context.Copy(result, value); + + return; + } + else if (saturateTo == 0) + { + // Result is always zero if we saturate 0 bits. + + context.Copy(result, Const(0)); + + return; + } + + // If the result is 0, the values are equal and we don't need saturation. + Operand lblNoSat = Label(); + context.BranchIfFalse(lblNoSat, context.ShiftRightUI(value, Const((int)saturateTo))); + + // Saturate. + context.Copy(result, Const(uint.MaxValue >> (32 - (int)saturateTo))); + + Operand lblExit = Label(); + context.Branch(lblExit); + + context.MarkLabel(lblNoSat); + + context.Copy(result, value); + + context.MarkLabel(lblExit); + } + + private static void EmitSaturateUqsub(ArmEmitterContext context, Operand result, Operand value, uint saturateTo) + { + Debug.Assert(saturateTo <= 32); + + if (saturateTo == 32) + { + // No saturation possible for this case. + + context.Copy(result, value); + + return; + } + else if (saturateTo == 0) + { + // Result is always zero if we saturate 0 bits. + + context.Copy(result, Const(0)); + + return; + } + + // If the result is 0, the values are equal and we don't need saturation. + Operand lblNoSat = Label(); + context.BranchIf(lblNoSat, value, Const(0), Comparison.GreaterOrEqual); + + // Saturate. + // Assumes that the value can only underflow, since this is only used for unsigned subtraction. + context.Copy(result, Const(0)); + + Operand lblExit = Label(); + context.Branch(lblExit); + + context.MarkLabel(lblNoSat); + + context.Copy(result, value); + + context.MarkLabel(lblExit); + } + + private static Operand EmitSigned16BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action elementAction) + { + Operand tempD = context.AllocateLocal(OperandType.I32); + + Operand tempN = context.SignExtend16(OperandType.I32, rn); + Operand tempM = context.SignExtend16(OperandType.I32, rm); + elementAction(tempD, tempN, tempM); + Operand tempD2 = context.ZeroExtend16(OperandType.I32, tempD); + + tempN = context.ShiftRightSI(rn, Const(16)); + tempM = context.ShiftRightSI(rm, Const(16)); + elementAction(tempD, tempN, tempM); + return context.BitwiseOr(tempD2, context.ShiftLeft(tempD, Const(16))); + } + + private static Operand EmitUnsigned16BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action elementAction) + { + Operand tempD = context.AllocateLocal(OperandType.I32); + + Operand tempN = context.ZeroExtend16(OperandType.I32, rn); + Operand tempM = context.ZeroExtend16(OperandType.I32, rm); + elementAction(tempD, tempN, tempM); + Operand tempD2 = context.ZeroExtend16(OperandType.I32, tempD); + + tempN = context.ShiftRightUI(rn, Const(16)); + tempM = context.ShiftRightUI(rm, Const(16)); + elementAction(tempD, tempN, tempM); + return context.BitwiseOr(tempD2, context.ShiftLeft(tempD, Const(16))); + } + + private static Operand EmitSigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action elementAction) + { + return Emit8BitPair(context, rn, rm, elementAction, unsigned: false); + } + + private static Operand EmitUnsigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action elementAction) + { + return Emit8BitPair(context, rn, rm, elementAction, unsigned: true); + } + + private static Operand Emit8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action elementAction, bool unsigned) + { + Operand tempD = context.AllocateLocal(OperandType.I32); + Operand result = default; + + for (int b = 0; b < 4; b++) + { + Operand nByte = b != 0 ? context.ShiftRightUI(rn, Const(b * 8)) : rn; + Operand mByte = b != 0 ? context.ShiftRightUI(rm, Const(b * 8)) : rm; + + if (unsigned) + { + nByte = context.ZeroExtend8(OperandType.I32, nByte); + mByte = context.ZeroExtend8(OperandType.I32, mByte); + } + else + { + nByte = context.SignExtend8(OperandType.I32, nByte); + mByte = context.SignExtend8(OperandType.I32, mByte); + } + + elementAction(tempD, nByte, mByte); + + if (b == 0) + { + result = context.ZeroExtend8(OperandType.I32, tempD); + } + else if (b < 3) + { + result = context.BitwiseOr(result, context.ShiftLeft(context.ZeroExtend8(OperandType.I32, tempD), Const(b * 8))); + } + else + { + result = context.BitwiseOr(result, context.ShiftLeft(tempD, Const(24))); + } + } + + return result; + } + + private static void EmitAluStore(ArmEmitterContext context, Operand value) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + EmitGenericAluStoreA32(context, op.Rd, ShouldSetFlags(context), value); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitAluHelper.cs b/src/ARMeilleure/Instructions/InstEmitAluHelper.cs new file mode 100644 index 00000000..4d4a31f7 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitAluHelper.cs @@ -0,0 +1,652 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitAluHelper + { + public static bool ShouldSetFlags(ArmEmitterContext context) + { + IOpCode32HasSetFlags op = (IOpCode32HasSetFlags)context.CurrOp; + + if (op.SetFlags == null) + { + return !context.IsInIfThenBlock; + } + + return op.SetFlags.Value; + } + + public static void EmitNZFlagsCheck(ArmEmitterContext context, Operand d) + { + SetFlag(context, PState.NFlag, context.ICompareLess(d, Const(d.Type, 0))); + SetFlag(context, PState.ZFlag, context.ICompareEqual(d, Const(d.Type, 0))); + } + + public static void EmitAdcsCCheck(ArmEmitterContext context, Operand n, Operand d) + { + // C = (Rd == Rn && CIn) || Rd < Rn + Operand cIn = GetFlag(PState.CFlag); + + Operand cOut = context.BitwiseAnd(context.ICompareEqual(d, n), cIn); + + cOut = context.BitwiseOr(cOut, context.ICompareLessUI(d, n)); + + SetFlag(context, PState.CFlag, cOut); + } + + public static void EmitAddsCCheck(ArmEmitterContext context, Operand n, Operand d) + { + // C = Rd < Rn + SetFlag(context, PState.CFlag, context.ICompareLessUI(d, n)); + } + + public static void EmitAddsVCheck(ArmEmitterContext context, Operand n, Operand m, Operand d) + { + // V = (Rd ^ Rn) & ~(Rn ^ Rm) < 0 + Operand vOut = context.BitwiseExclusiveOr(d, n); + + vOut = context.BitwiseAnd(vOut, context.BitwiseNot(context.BitwiseExclusiveOr(n, m))); + + vOut = context.ICompareLess(vOut, Const(vOut.Type, 0)); + + SetFlag(context, PState.VFlag, vOut); + } + + public static void EmitSbcsCCheck(ArmEmitterContext context, Operand n, Operand m) + { + // C = (Rn == Rm && CIn) || Rn > Rm + Operand cIn = GetFlag(PState.CFlag); + + Operand cOut = context.BitwiseAnd(context.ICompareEqual(n, m), cIn); + + cOut = context.BitwiseOr(cOut, context.ICompareGreaterUI(n, m)); + + SetFlag(context, PState.CFlag, cOut); + } + + public static void EmitSubsCCheck(ArmEmitterContext context, Operand n, Operand m) + { + // C = Rn >= Rm + SetFlag(context, PState.CFlag, context.ICompareGreaterOrEqualUI(n, m)); + } + + public static void EmitSubsVCheck(ArmEmitterContext context, Operand n, Operand m, Operand d) + { + // V = (Rd ^ Rn) & (Rn ^ Rm) < 0 + Operand vOut = context.BitwiseExclusiveOr(d, n); + + vOut = context.BitwiseAnd(vOut, context.BitwiseExclusiveOr(n, m)); + + vOut = context.ICompareLess(vOut, Const(vOut.Type, 0)); + + SetFlag(context, PState.VFlag, vOut); + } + + public static Operand EmitReverseBits32Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I32); + + Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaaaaaaau)), Const(1)), + context.ShiftLeft(context.BitwiseAnd(op, Const(0x55555555u)), Const(1))); + + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccccccccu)), Const(2)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x33333333u)), Const(2))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xf0f0f0f0u)), Const(4)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x0f0f0f0fu)), Const(4))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xff00ff00u)), Const(8)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x00ff00ffu)), Const(8))); + + return context.BitwiseOr(context.ShiftRightUI(val, Const(16)), context.ShiftLeft(val, Const(16))); + } + + public static Operand EmitReverseBytes16_64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xff00ff00ff00ff00ul)), Const(8)), + context.ShiftLeft(context.BitwiseAnd(op, Const(0x00ff00ff00ff00fful)), Const(8))); + } + + public static Operand EmitReverseBytes16_32Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I32); + + Operand val = EmitReverseBytes16_64Op(context, context.ZeroExtend32(OperandType.I64, op)); + + return context.ConvertI64ToI32(val); + } + + private static void EmitAluWritePc(ArmEmitterContext context, Operand value) + { + Debug.Assert(value.Type == OperandType.I32); + + if (((OpCode32)context.CurrOp).IsThumb) + { + bool isReturn = IsA32Return(context); + if (!isReturn) + { + context.StoreToContext(); + } + + InstEmitFlowHelper.EmitVirtualJump(context, value, isReturn); + } + else + { + EmitBxWritePc(context, value); + } + } + + public static void EmitGenericAluStoreA32(ArmEmitterContext context, int rd, bool setFlags, Operand value) + { + Debug.Assert(value.Type == OperandType.I32); + + if (rd == RegisterAlias.Aarch32Pc && setFlags) + { + if (setFlags) + { + // TODO: Load SPSR etc. + + EmitBxWritePc(context, value); + } + else + { + EmitAluWritePc(context, value); + } + } + else + { + SetIntA32(context, rd, value); + } + } + + public static Operand GetAluN(ArmEmitterContext context) + { + if (context.CurrOp is IOpCodeAlu op) + { + if (op.DataOp == DataOp.Logical || op is IOpCodeAluRs) + { + return GetIntOrZR(context, op.Rn); + } + else + { + return GetIntOrSP(context, op.Rn); + } + } + else if (context.CurrOp is IOpCode32Alu op32) + { + return GetIntA32(context, op32.Rn); + } + else + { + throw InvalidOpCodeType(context.CurrOp); + } + } + + public static Operand GetAluM(ArmEmitterContext context, bool setCarry = true) + { + switch (context.CurrOp) + { + // ARM32. + case IOpCode32AluImm op: + { + if (ShouldSetFlags(context) && op.IsRotated && setCarry) + { + SetFlag(context, PState.CFlag, Const((uint)op.Immediate >> 31)); + } + + return Const(op.Immediate); + } + + case IOpCode32AluImm16 op: + return Const(op.Immediate); + + case IOpCode32AluRsImm op: + return GetMShiftedByImmediate(context, op, setCarry); + case IOpCode32AluRsReg op: + return GetMShiftedByReg(context, op, setCarry); + + case IOpCode32AluReg op: + return GetIntA32(context, op.Rm); + + // ARM64. + case IOpCodeAluImm op: + { + if (op.GetOperandType() == OperandType.I32) + { + return Const((int)op.Immediate); + } + else + { + return Const(op.Immediate); + } + } + + case IOpCodeAluRs op: + { + Operand value = GetIntOrZR(context, op.Rm); + + switch (op.ShiftType) + { + case ShiftType.Lsl: + value = context.ShiftLeft(value, Const(op.Shift)); + break; + case ShiftType.Lsr: + value = context.ShiftRightUI(value, Const(op.Shift)); + break; + case ShiftType.Asr: + value = context.ShiftRightSI(value, Const(op.Shift)); + break; + case ShiftType.Ror: + value = context.RotateRight(value, Const(op.Shift)); + break; + } + + return value; + } + + case IOpCodeAluRx op: + { + Operand value = GetExtendedM(context, op.Rm, op.IntType); + + value = context.ShiftLeft(value, Const(op.Shift)); + + return value; + } + + default: + throw InvalidOpCodeType(context.CurrOp); + } + } + + private static Exception InvalidOpCodeType(OpCode opCode) + { + return new InvalidOperationException($"Invalid OpCode type \"{opCode?.GetType().Name ?? "null"}\"."); + } + + // ARM32 helpers. + public static Operand GetMShiftedByImmediate(ArmEmitterContext context, IOpCode32AluRsImm op, bool setCarry) + { + Operand m = GetIntA32(context, op.Rm); + + int shift = op.Immediate; + + if (shift == 0) + { + switch (op.ShiftType) + { + case ShiftType.Lsr: + shift = 32; + break; + case ShiftType.Asr: + shift = 32; + break; + case ShiftType.Ror: + shift = 1; + break; + } + } + + if (shift != 0) + { + setCarry &= ShouldSetFlags(context); + + switch (op.ShiftType) + { + case ShiftType.Lsl: + m = GetLslC(context, m, setCarry, shift); + break; + case ShiftType.Lsr: + m = GetLsrC(context, m, setCarry, shift); + break; + case ShiftType.Asr: + m = GetAsrC(context, m, setCarry, shift); + break; + case ShiftType.Ror: + if (op.Immediate != 0) + { + m = GetRorC(context, m, setCarry, shift); + } + else + { + m = GetRrxC(context, m, setCarry); + } + break; + } + } + + return m; + } + + public static int DecodeImmShift(ShiftType shiftType, int shift) + { + if (shift == 0) + { + switch (shiftType) + { + case ShiftType.Lsr: + shift = 32; + break; + case ShiftType.Asr: + shift = 32; + break; + case ShiftType.Ror: + shift = 1; + break; + } + } + + return shift; + } + + public static Operand GetMShiftedByReg(ArmEmitterContext context, IOpCode32AluRsReg op, bool setCarry) + { + Operand m = GetIntA32(context, op.Rm); + Operand s = context.ZeroExtend8(OperandType.I32, GetIntA32(context, op.Rs)); + Operand shiftIsZero = context.ICompareEqual(s, Const(0)); + + Operand zeroResult = m; + Operand shiftResult = m; + + setCarry &= ShouldSetFlags(context); + + switch (op.ShiftType) + { + case ShiftType.Lsl: + shiftResult = EmitLslC(context, m, setCarry, s, shiftIsZero); + break; + case ShiftType.Lsr: + shiftResult = EmitLsrC(context, m, setCarry, s, shiftIsZero); + break; + case ShiftType.Asr: + shiftResult = EmitAsrC(context, m, setCarry, s, shiftIsZero); + break; + case ShiftType.Ror: + shiftResult = EmitRorC(context, m, setCarry, s, shiftIsZero); + break; + } + + return context.ConditionalSelect(shiftIsZero, zeroResult, shiftResult); + } + + public static void EmitIfHelper(ArmEmitterContext context, Operand boolValue, Action action, bool expected = true) + { + Debug.Assert(boolValue.Type == OperandType.I32); + + Operand endLabel = Label(); + + if (expected) + { + context.BranchIfFalse(endLabel, boolValue); + } + else + { + context.BranchIfTrue(endLabel, boolValue); + } + + action(); + + context.MarkLabel(endLabel); + } + + public static Operand EmitLslC(ArmEmitterContext context, Operand m, bool setCarry, Operand shift, Operand shiftIsZero) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32 && shiftIsZero.Type == OperandType.I32); + + Operand shiftLarge = context.ICompareGreaterOrEqual(shift, Const(32)); + Operand result = context.ShiftLeft(m, shift); + if (setCarry) + { + EmitIfHelper(context, shiftIsZero, () => + { + Operand cOut = context.ShiftRightUI(m, context.Subtract(Const(32), shift)); + + cOut = context.BitwiseAnd(cOut, Const(1)); + cOut = context.ConditionalSelect(context.ICompareGreater(shift, Const(32)), Const(0), cOut); + + SetFlag(context, PState.CFlag, cOut); + }, false); + } + + return context.ConditionalSelect(shiftLarge, Const(0), result); + } + + public static Operand GetLslC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + if ((uint)shift > 32) + { + return GetShiftByMoreThan32(context, setCarry); + } + else if (shift == 32) + { + if (setCarry) + { + SetCarryMLsb(context, m); + } + + return Const(0); + } + else + { + if (setCarry) + { + Operand cOut = context.ShiftRightUI(m, Const(32 - shift)); + + cOut = context.BitwiseAnd(cOut, Const(1)); + + SetFlag(context, PState.CFlag, cOut); + } + + return context.ShiftLeft(m, Const(shift)); + } + } + + public static Operand EmitLsrC(ArmEmitterContext context, Operand m, bool setCarry, Operand shift, Operand shiftIsZero) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32 && shiftIsZero.Type == OperandType.I32); + + Operand shiftLarge = context.ICompareGreaterOrEqual(shift, Const(32)); + Operand result = context.ShiftRightUI(m, shift); + if (setCarry) + { + EmitIfHelper(context, shiftIsZero, () => + { + Operand cOut = context.ShiftRightUI(m, context.Subtract(shift, Const(1))); + + cOut = context.BitwiseAnd(cOut, Const(1)); + cOut = context.ConditionalSelect(context.ICompareGreater(shift, Const(32)), Const(0), cOut); + + SetFlag(context, PState.CFlag, cOut); + }, false); + } + + return context.ConditionalSelect(shiftLarge, Const(0), result); + } + + public static Operand GetLsrC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + if ((uint)shift > 32) + { + return GetShiftByMoreThan32(context, setCarry); + } + else if (shift == 32) + { + if (setCarry) + { + SetCarryMMsb(context, m); + } + + return Const(0); + } + else + { + if (setCarry) + { + SetCarryMShrOut(context, m, shift); + } + + return context.ShiftRightUI(m, Const(shift)); + } + } + + private static Operand GetShiftByMoreThan32(ArmEmitterContext context, bool setCarry) + { + if (setCarry) + { + SetFlag(context, PState.CFlag, Const(0)); + } + + return Const(0); + } + + public static Operand EmitAsrC(ArmEmitterContext context, Operand m, bool setCarry, Operand shift, Operand shiftIsZero) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32 && shiftIsZero.Type == OperandType.I32); + + Operand l32Result; + Operand ge32Result; + + Operand less32 = context.ICompareLess(shift, Const(32)); + + ge32Result = context.ShiftRightSI(m, Const(31)); + + if (setCarry) + { + EmitIfHelper(context, context.BitwiseOr(less32, shiftIsZero), () => + { + SetCarryMLsb(context, ge32Result); + }, false); + } + + l32Result = context.ShiftRightSI(m, shift); + if (setCarry) + { + EmitIfHelper(context, context.BitwiseAnd(less32, context.BitwiseNot(shiftIsZero)), () => + { + Operand cOut = context.ShiftRightUI(m, context.Subtract(shift, Const(1))); + + cOut = context.BitwiseAnd(cOut, Const(1)); + + SetFlag(context, PState.CFlag, cOut); + }); + } + + return context.ConditionalSelect(less32, l32Result, ge32Result); + } + + public static Operand GetAsrC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + if ((uint)shift >= 32) + { + m = context.ShiftRightSI(m, Const(31)); + + if (setCarry) + { + SetCarryMLsb(context, m); + } + + return m; + } + else + { + if (setCarry) + { + SetCarryMShrOut(context, m, shift); + } + + return context.ShiftRightSI(m, Const(shift)); + } + } + + public static Operand EmitRorC(ArmEmitterContext context, Operand m, bool setCarry, Operand shift, Operand shiftIsZero) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32 && shiftIsZero.Type == OperandType.I32); + + shift = context.BitwiseAnd(shift, Const(0x1f)); + m = context.RotateRight(m, shift); + + if (setCarry) + { + EmitIfHelper(context, shiftIsZero, () => + { + SetCarryMMsb(context, m); + }, false); + } + + return m; + } + + public static Operand GetRorC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + shift &= 0x1f; + + m = context.RotateRight(m, Const(shift)); + + if (setCarry) + { + SetCarryMMsb(context, m); + } + + return m; + } + + public static Operand GetRrxC(ArmEmitterContext context, Operand m, bool setCarry) + { + Debug.Assert(m.Type == OperandType.I32); + + // Rotate right by 1 with carry. + Operand cIn = context.Copy(GetFlag(PState.CFlag)); + + if (setCarry) + { + SetCarryMLsb(context, m); + } + + m = context.ShiftRightUI(m, Const(1)); + + m = context.BitwiseOr(m, context.ShiftLeft(cIn, Const(31))); + + return m; + } + + private static void SetCarryMLsb(ArmEmitterContext context, Operand m) + { + Debug.Assert(m.Type == OperandType.I32); + + SetFlag(context, PState.CFlag, context.BitwiseAnd(m, Const(1))); + } + + private static void SetCarryMMsb(ArmEmitterContext context, Operand m) + { + Debug.Assert(m.Type == OperandType.I32); + + SetFlag(context, PState.CFlag, context.ShiftRightUI(m, Const(31))); + } + + private static void SetCarryMShrOut(ArmEmitterContext context, Operand m, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + Operand cOut = context.ShiftRightUI(m, Const(shift - 1)); + + cOut = context.BitwiseAnd(cOut, Const(1)); + + SetFlag(context, PState.CFlag, cOut); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitBfm.cs b/src/ARMeilleure/Instructions/InstEmitBfm.cs new file mode 100644 index 00000000..aaf22875 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitBfm.cs @@ -0,0 +1,196 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Bfm(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + Operand d = GetIntOrZR(context, op.Rd); + Operand n = GetIntOrZR(context, op.Rn); + + Operand res; + + if (op.Pos < op.Shift) + { + // BFI. + int shift = op.GetBitsCount() - op.Shift; + + int width = op.Pos + 1; + + long mask = (long)(ulong.MaxValue >> (64 - width)); + + res = context.ShiftLeft(context.BitwiseAnd(n, Const(n.Type, mask)), Const(shift)); + + res = context.BitwiseOr(res, context.BitwiseAnd(d, Const(d.Type, ~(mask << shift)))); + } + else + { + // BFXIL. + int shift = op.Shift; + + int width = op.Pos - shift + 1; + + long mask = (long)(ulong.MaxValue >> (64 - width)); + + res = context.BitwiseAnd(context.ShiftRightUI(n, Const(shift)), Const(n.Type, mask)); + + res = context.BitwiseOr(res, context.BitwiseAnd(d, Const(d.Type, ~mask))); + } + + SetIntOrZR(context, op.Rd, res); + } + + public static void Sbfm(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + int bitsCount = op.GetBitsCount(); + + if (op.Pos + 1 == bitsCount) + { + EmitSbfmShift(context); + } + else if (op.Pos < op.Shift) + { + EmitSbfiz(context); + } + else if (op.Pos == 7 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.SignExtend8(n.Type, n)); + } + else if (op.Pos == 15 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.SignExtend16(n.Type, n)); + } + else if (op.Pos == 31 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.SignExtend32(n.Type, n)); + } + else + { + Operand res = GetIntOrZR(context, op.Rn); + + res = context.ShiftLeft(res, Const(bitsCount - 1 - op.Pos)); + res = context.ShiftRightSI(res, Const(bitsCount - 1)); + res = context.BitwiseAnd(res, Const(res.Type, ~op.TMask)); + + Operand n2 = GetBfmN(context); + + SetIntOrZR(context, op.Rd, context.BitwiseOr(res, n2)); + } + } + + public static void Ubfm(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + if (op.Pos + 1 == op.GetBitsCount()) + { + EmitUbfmShift(context); + } + else if (op.Pos < op.Shift) + { + EmitUbfiz(context); + } + else if (op.Pos + 1 == op.Shift) + { + EmitBfmLsl(context); + } + else if (op.Pos == 7 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.BitwiseAnd(n, Const(n.Type, 0xff))); + } + else if (op.Pos == 15 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.BitwiseAnd(n, Const(n.Type, 0xffff))); + } + else + { + SetIntOrZR(context, op.Rd, GetBfmN(context)); + } + } + + private static void EmitSbfiz(ArmEmitterContext context) => EmitBfiz(context, signed: true); + private static void EmitUbfiz(ArmEmitterContext context) => EmitBfiz(context, signed: false); + + private static void EmitBfiz(ArmEmitterContext context, bool signed) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + int width = op.Pos + 1; + + Operand res = GetIntOrZR(context, op.Rn); + + res = context.ShiftLeft(res, Const(op.GetBitsCount() - width)); + + res = signed + ? context.ShiftRightSI(res, Const(op.Shift - width)) + : context.ShiftRightUI(res, Const(op.Shift - width)); + + SetIntOrZR(context, op.Rd, res); + } + + private static void EmitSbfmShift(ArmEmitterContext context) + { + EmitBfmShift(context, signed: true); + } + + private static void EmitUbfmShift(ArmEmitterContext context) + { + EmitBfmShift(context, signed: false); + } + + private static void EmitBfmShift(ArmEmitterContext context, bool signed) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + res = signed + ? context.ShiftRightSI(res, Const(op.Shift)) + : context.ShiftRightUI(res, Const(op.Shift)); + + SetIntOrZR(context, op.Rd, res); + } + + private static void EmitBfmLsl(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + int shift = op.GetBitsCount() - op.Shift; + + SetIntOrZR(context, op.Rd, context.ShiftLeft(res, Const(shift))); + } + + private static Operand GetBfmN(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + long mask = op.WMask & op.TMask; + + return context.BitwiseAnd(context.RotateRight(res, Const(op.Shift)), Const(res.Type, mask)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitCcmp.cs b/src/ARMeilleure/Instructions/InstEmitCcmp.cs new file mode 100644 index 00000000..a71fc268 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitCcmp.cs @@ -0,0 +1,60 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using static ARMeilleure.Instructions.InstEmitAluHelper; +using static ARMeilleure.Instructions.InstEmitFlowHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Ccmn(ArmEmitterContext context) => EmitCcmp(context, isNegated: true); + public static void Ccmp(ArmEmitterContext context) => EmitCcmp(context, isNegated: false); + + private static void EmitCcmp(ArmEmitterContext context, bool isNegated) + { + OpCodeCcmp op = (OpCodeCcmp)context.CurrOp; + + Operand lblTrue = Label(); + Operand lblEnd = Label(); + + EmitCondBranch(context, lblTrue, op.Cond); + + SetFlag(context, PState.VFlag, Const((op.Nzcv >> 0) & 1)); + SetFlag(context, PState.CFlag, Const((op.Nzcv >> 1) & 1)); + SetFlag(context, PState.ZFlag, Const((op.Nzcv >> 2) & 1)); + SetFlag(context, PState.NFlag, Const((op.Nzcv >> 3) & 1)); + + context.Branch(lblEnd); + + context.MarkLabel(lblTrue); + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + if (isNegated) + { + Operand d = context.Add(n, m); + + EmitNZFlagsCheck(context, d); + + EmitAddsCCheck(context, n, d); + EmitAddsVCheck(context, n, m, d); + } + else + { + Operand d = context.Subtract(n, m); + + EmitNZFlagsCheck(context, d); + + EmitSubsCCheck(context, n, m); + EmitSubsVCheck(context, n, m, d); + } + + context.MarkLabel(lblEnd); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitCsel.cs b/src/ARMeilleure/Instructions/InstEmitCsel.cs new file mode 100644 index 00000000..1cd936b3 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitCsel.cs @@ -0,0 +1,52 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using static ARMeilleure.Instructions.InstEmitFlowHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + private enum CselOperation + { + None, + Increment, + Invert, + Negate, + } + + public static void Csel(ArmEmitterContext context) => EmitCsel(context, CselOperation.None); + public static void Csinc(ArmEmitterContext context) => EmitCsel(context, CselOperation.Increment); + public static void Csinv(ArmEmitterContext context) => EmitCsel(context, CselOperation.Invert); + public static void Csneg(ArmEmitterContext context) => EmitCsel(context, CselOperation.Negate); + + private static void EmitCsel(ArmEmitterContext context, CselOperation cselOp) + { + OpCodeCsel op = (OpCodeCsel)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + if (cselOp == CselOperation.Increment) + { + m = context.Add(m, Const(m.Type, 1)); + } + else if (cselOp == CselOperation.Invert) + { + m = context.BitwiseNot(m); + } + else if (cselOp == CselOperation.Negate) + { + m = context.Negate(m); + } + + Operand condTrue = GetCondTrue(context, op.Cond); + + Operand d = context.ConditionalSelect(condTrue, n, m); + + SetIntOrZR(context, op.Rd, d); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitDiv.cs b/src/ARMeilleure/Instructions/InstEmitDiv.cs new file mode 100644 index 00000000..728462ed --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitDiv.cs @@ -0,0 +1,66 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Sdiv(ArmEmitterContext context) => EmitDiv(context, unsigned: false); + public static void Udiv(ArmEmitterContext context) => EmitDiv(context, unsigned: true); + + private static void EmitDiv(ArmEmitterContext context, bool unsigned) + { + OpCodeAluBinary op = (OpCodeAluBinary)context.CurrOp; + + // If Rm == 0, Rd = 0 (division by zero). + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand divisorIsZero = context.ICompareEqual(m, Const(m.Type, 0)); + + Operand lblBadDiv = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBadDiv, divisorIsZero); + + if (!unsigned) + { + // If Rn == INT_MIN && Rm == -1, Rd = INT_MIN (overflow). + bool is32Bits = op.RegisterSize == RegisterSize.Int32; + + Operand intMin = is32Bits ? Const(int.MinValue) : Const(long.MinValue); + Operand minus1 = is32Bits ? Const(-1) : Const(-1L); + + Operand nIsIntMin = context.ICompareEqual(n, intMin); + Operand mIsMinus1 = context.ICompareEqual(m, minus1); + + Operand lblGoodDiv = Label(); + + context.BranchIfFalse(lblGoodDiv, context.BitwiseAnd(nIsIntMin, mIsMinus1)); + + SetAluDOrZR(context, intMin); + + context.Branch(lblEnd); + + context.MarkLabel(lblGoodDiv); + } + + Operand d = unsigned + ? context.DivideUI(n, m) + : context.Divide(n, m); + + SetAluDOrZR(context, d); + + context.Branch(lblEnd); + + context.MarkLabel(lblBadDiv); + + SetAluDOrZR(context, Const(op.GetOperandType(), 0)); + + context.MarkLabel(lblEnd); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitException.cs b/src/ARMeilleure/Instructions/InstEmitException.cs new file mode 100644 index 00000000..d30fb2fb --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitException.cs @@ -0,0 +1,55 @@ +using ARMeilleure.Decoders; +using ARMeilleure.Translation; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Brk(ArmEmitterContext context) + { + OpCodeException op = (OpCodeException)context.CurrOp; + + string name = nameof(NativeInterface.Break); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id)); + + context.LoadFromContext(); + + context.Return(Const(op.Address)); + } + + public static void Svc(ArmEmitterContext context) + { + OpCodeException op = (OpCodeException)context.CurrOp; + + string name = nameof(NativeInterface.SupervisorCall); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id)); + + context.LoadFromContext(); + + Translator.EmitSynchronization(context); + } + + public static void Und(ArmEmitterContext context) + { + OpCode op = context.CurrOp; + + string name = nameof(NativeInterface.Undefined); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.RawOpCode)); + + context.LoadFromContext(); + + context.Return(Const(op.Address)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitException32.cs b/src/ARMeilleure/Instructions/InstEmitException32.cs new file mode 100644 index 00000000..57af1522 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitException32.cs @@ -0,0 +1,39 @@ +using ARMeilleure.Decoders; +using ARMeilleure.Translation; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Svc(ArmEmitterContext context) + { + IOpCode32Exception op = (IOpCode32Exception)context.CurrOp; + + string name = nameof(NativeInterface.SupervisorCall); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id)); + + context.LoadFromContext(); + + Translator.EmitSynchronization(context); + } + + public static void Trap(ArmEmitterContext context) + { + IOpCode32Exception op = (IOpCode32Exception)context.CurrOp; + + string name = nameof(NativeInterface.Break); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id)); + + context.LoadFromContext(); + + context.Return(Const(context.CurrOp.Address)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitFlow.cs b/src/ARMeilleure/Instructions/InstEmitFlow.cs new file mode 100644 index 00000000..a986bf66 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitFlow.cs @@ -0,0 +1,107 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitFlowHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void B(ArmEmitterContext context) + { + OpCodeBImmAl op = (OpCodeBImmAl)context.CurrOp; + + context.Branch(context.GetLabel((ulong)op.Immediate)); + } + + public static void B_Cond(ArmEmitterContext context) + { + OpCodeBImmCond op = (OpCodeBImmCond)context.CurrOp; + + EmitBranch(context, op.Cond); + } + + public static void Bl(ArmEmitterContext context) + { + OpCodeBImmAl op = (OpCodeBImmAl)context.CurrOp; + + context.Copy(GetIntOrZR(context, RegisterAlias.Lr), Const(op.Address + 4)); + + EmitCall(context, (ulong)op.Immediate); + } + + public static void Blr(ArmEmitterContext context) + { + OpCodeBReg op = (OpCodeBReg)context.CurrOp; + + Operand n = context.Copy(GetIntOrZR(context, op.Rn)); + + context.Copy(GetIntOrZR(context, RegisterAlias.Lr), Const(op.Address + 4)); + + EmitVirtualCall(context, n); + } + + public static void Br(ArmEmitterContext context) + { + OpCodeBReg op = (OpCodeBReg)context.CurrOp; + + EmitVirtualJump(context, GetIntOrZR(context, op.Rn), op.Rn == RegisterAlias.Lr); + } + + public static void Cbnz(ArmEmitterContext context) => EmitCb(context, onNotZero: true); + public static void Cbz(ArmEmitterContext context) => EmitCb(context, onNotZero: false); + + private static void EmitCb(ArmEmitterContext context, bool onNotZero) + { + OpCodeBImmCmp op = (OpCodeBImmCmp)context.CurrOp; + + EmitBranch(context, GetIntOrZR(context, op.Rt), onNotZero); + } + + public static void Ret(ArmEmitterContext context) + { + OpCodeBReg op = (OpCodeBReg)context.CurrOp; + + context.Return(GetIntOrZR(context, op.Rn)); + } + + public static void Tbnz(ArmEmitterContext context) => EmitTb(context, onNotZero: true); + public static void Tbz(ArmEmitterContext context) => EmitTb(context, onNotZero: false); + + private static void EmitTb(ArmEmitterContext context, bool onNotZero) + { + OpCodeBImmTest op = (OpCodeBImmTest)context.CurrOp; + + Operand value = context.BitwiseAnd(GetIntOrZR(context, op.Rt), Const(1L << op.Bit)); + + EmitBranch(context, value, onNotZero); + } + + private static void EmitBranch(ArmEmitterContext context, Condition cond) + { + OpCodeBImm op = (OpCodeBImm)context.CurrOp; + + EmitCondBranch(context, context.GetLabel((ulong)op.Immediate), cond); + } + + private static void EmitBranch(ArmEmitterContext context, Operand value, bool onNotZero) + { + OpCodeBImm op = (OpCodeBImm)context.CurrOp; + + Operand lblTarget = context.GetLabel((ulong)op.Immediate); + + if (onNotZero) + { + context.BranchIfTrue(lblTarget, value); + } + else + { + context.BranchIfFalse(lblTarget, value); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitFlow32.cs b/src/ARMeilleure/Instructions/InstEmitFlow32.cs new file mode 100644 index 00000000..289d3f48 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitFlow32.cs @@ -0,0 +1,136 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitFlowHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void B(ArmEmitterContext context) + { + IOpCode32BImm op = (IOpCode32BImm)context.CurrOp; + + context.Branch(context.GetLabel((ulong)op.Immediate)); + } + + public static void Bl(ArmEmitterContext context) + { + Blx(context, x: false); + } + + public static void Blx(ArmEmitterContext context) + { + Blx(context, x: true); + } + + private static void Blx(ArmEmitterContext context, bool x) + { + IOpCode32BImm op = (IOpCode32BImm)context.CurrOp; + + uint pc = op.GetPc(); + + bool isThumb = ((OpCode32)context.CurrOp).IsThumb; + + uint currentPc = isThumb + ? pc | 1 + : pc - 4; + + SetIntA32(context, GetBankedRegisterAlias(context.Mode, RegisterAlias.Aarch32Lr), Const(currentPc)); + + // If x is true, then this is a branch with link and exchange. + // In this case we need to swap the mode between Arm <-> Thumb. + if (x) + { + SetFlag(context, PState.TFlag, Const(isThumb ? 0 : 1)); + } + + EmitCall(context, (ulong)op.Immediate); + } + + public static void Blxr(ArmEmitterContext context) + { + IOpCode32BReg op = (IOpCode32BReg)context.CurrOp; + + uint pc = op.GetPc(); + + Operand addr = context.Copy(GetIntA32(context, op.Rm)); + Operand bitOne = context.BitwiseAnd(addr, Const(1)); + + bool isThumb = ((OpCode32)context.CurrOp).IsThumb; + + uint currentPc = isThumb + ? (pc - 2) | 1 + : pc - 4; + + SetIntA32(context, GetBankedRegisterAlias(context.Mode, RegisterAlias.Aarch32Lr), Const(currentPc)); + + SetFlag(context, PState.TFlag, bitOne); + + EmitBxWritePc(context, addr); + } + + public static void Bx(ArmEmitterContext context) + { + IOpCode32BReg op = (IOpCode32BReg)context.CurrOp; + + EmitBxWritePc(context, GetIntA32(context, op.Rm), op.Rm); + } + + public static void Cbnz(ArmEmitterContext context) => EmitCb(context, onNotZero: true); + public static void Cbz(ArmEmitterContext context) => EmitCb(context, onNotZero: false); + + private static void EmitCb(ArmEmitterContext context, bool onNotZero) + { + OpCodeT16BImmCmp op = (OpCodeT16BImmCmp)context.CurrOp; + + Operand value = GetIntA32(context, op.Rn); + Operand lblTarget = context.GetLabel((ulong)op.Immediate); + + if (onNotZero) + { + context.BranchIfTrue(lblTarget, value); + } + else + { + context.BranchIfFalse(lblTarget, value); + } + } + + public static void It(ArmEmitterContext context) + { + OpCodeT16IfThen op = (OpCodeT16IfThen)context.CurrOp; + + context.SetIfThenBlockState(op.IfThenBlockConds); + } + + public static void Tbb(ArmEmitterContext context) => EmitTb(context, halfword: false); + public static void Tbh(ArmEmitterContext context) => EmitTb(context, halfword: true); + + private static void EmitTb(ArmEmitterContext context, bool halfword) + { + OpCodeT32Tb op = (OpCodeT32Tb)context.CurrOp; + + Operand halfwords; + + if (halfword) + { + Operand address = context.Add(GetIntA32(context, op.Rn), context.ShiftLeft(GetIntA32(context, op.Rm), Const(1))); + halfwords = InstEmitMemoryHelper.EmitReadInt(context, address, 1); + } + else + { + Operand address = context.Add(GetIntA32(context, op.Rn), GetIntA32(context, op.Rm)); + halfwords = InstEmitMemoryHelper.EmitReadIntAligned(context, address, 0); + } + + Operand targetAddress = context.Add(Const((int)op.GetPc()), context.ShiftLeft(halfwords, Const(1))); + + EmitVirtualJump(context, targetAddress, isReturn: false); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs new file mode 100644 index 00000000..2009bafd --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs @@ -0,0 +1,240 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using ARMeilleure.Translation.PTC; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitFlowHelper + { + public static void EmitCondBranch(ArmEmitterContext context, Operand target, Condition cond) + { + if (cond != Condition.Al) + { + context.BranchIfTrue(target, GetCondTrue(context, cond)); + } + else + { + context.Branch(target); + } + } + + public static Operand GetCondTrue(ArmEmitterContext context, Condition condition) + { + Operand cmpResult = context.TryGetComparisonResult(condition); + + if (cmpResult != default) + { + return cmpResult; + } + + Operand value = Const(1); + + Operand Inverse(Operand val) + { + return context.BitwiseExclusiveOr(val, Const(1)); + } + + switch (condition) + { + case Condition.Eq: + value = GetFlag(PState.ZFlag); + break; + + case Condition.Ne: + value = Inverse(GetFlag(PState.ZFlag)); + break; + + case Condition.GeUn: + value = GetFlag(PState.CFlag); + break; + + case Condition.LtUn: + value = Inverse(GetFlag(PState.CFlag)); + break; + + case Condition.Mi: + value = GetFlag(PState.NFlag); + break; + + case Condition.Pl: + value = Inverse(GetFlag(PState.NFlag)); + break; + + case Condition.Vs: + value = GetFlag(PState.VFlag); + break; + + case Condition.Vc: + value = Inverse(GetFlag(PState.VFlag)); + break; + + case Condition.GtUn: + { + Operand c = GetFlag(PState.CFlag); + Operand z = GetFlag(PState.ZFlag); + + value = context.BitwiseAnd(c, Inverse(z)); + + break; + } + + case Condition.LeUn: + { + Operand c = GetFlag(PState.CFlag); + Operand z = GetFlag(PState.ZFlag); + + value = context.BitwiseOr(Inverse(c), z); + + break; + } + + case Condition.Ge: + { + Operand n = GetFlag(PState.NFlag); + Operand v = GetFlag(PState.VFlag); + + value = context.ICompareEqual(n, v); + + break; + } + + case Condition.Lt: + { + Operand n = GetFlag(PState.NFlag); + Operand v = GetFlag(PState.VFlag); + + value = context.ICompareNotEqual(n, v); + + break; + } + + case Condition.Gt: + { + Operand n = GetFlag(PState.NFlag); + Operand z = GetFlag(PState.ZFlag); + Operand v = GetFlag(PState.VFlag); + + value = context.BitwiseAnd(Inverse(z), context.ICompareEqual(n, v)); + + break; + } + + case Condition.Le: + { + Operand n = GetFlag(PState.NFlag); + Operand z = GetFlag(PState.ZFlag); + Operand v = GetFlag(PState.VFlag); + + value = context.BitwiseOr(z, context.ICompareNotEqual(n, v)); + + break; + } + } + + return value; + } + + public static void EmitCall(ArmEmitterContext context, ulong immediate) + { + bool isRecursive = immediate == context.EntryAddress; + + if (isRecursive) + { + context.Branch(context.GetLabel(immediate)); + } + else + { + EmitTableBranch(context, Const(immediate), isJump: false); + } + } + + public static void EmitVirtualCall(ArmEmitterContext context, Operand target) + { + EmitTableBranch(context, target, isJump: false); + } + + public static void EmitVirtualJump(ArmEmitterContext context, Operand target, bool isReturn) + { + if (isReturn) + { + if (target.Type == OperandType.I32) + { + target = context.ZeroExtend32(OperandType.I64, target); + } + + context.Return(target); + } + else + { + EmitTableBranch(context, target, isJump: true); + } + } + + private static void EmitTableBranch(ArmEmitterContext context, Operand guestAddress, bool isJump) + { + context.StoreToContext(); + + if (guestAddress.Type == OperandType.I32) + { + guestAddress = context.ZeroExtend32(OperandType.I64, guestAddress); + } + + // Store the target guest address into the native context. The stubs uses this address to dispatch into the + // next translation. + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand dispAddressAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())); + context.Store(dispAddressAddr, guestAddress); + + Operand hostAddress; + + // If address is mapped onto the function table, we can skip the table walk. Otherwise we fallback + // onto the dispatch stub. + if (guestAddress.Kind == OperandKind.Constant && context.FunctionTable.IsValid(guestAddress.Value)) + { + Operand hostAddressAddr = !context.HasPtc ? + Const(ref context.FunctionTable.GetValue(guestAddress.Value)) : + Const(ref context.FunctionTable.GetValue(guestAddress.Value), new Symbol(SymbolType.FunctionTable, guestAddress.Value)); + + hostAddress = context.Load(OperandType.I64, hostAddressAddr); + } + else + { + hostAddress = !context.HasPtc ? + Const((long)context.Stubs.DispatchStub) : + Const((long)context.Stubs.DispatchStub, Ptc.DispatchStubSymbol); + } + + if (isJump) + { + context.Tailcall(hostAddress, nativeContext); + } + else + { + OpCode op = context.CurrOp; + + Operand returnAddress = context.Call(hostAddress, OperandType.I64, nativeContext); + + context.LoadFromContext(); + + // Note: The return value of a translated function is always an Int64 with the address execution has + // returned to. We expect this address to be immediately after the current instruction, if it isn't we + // keep returning until we reach the dispatcher. + Operand nextAddr = Const((long)op.Address + op.OpCodeSizeInBytes); + + // Try to continue within this block. + // If the return address isn't to our next instruction, we need to return so the JIT can figure out + // what to do. + Operand lblContinue = context.GetLabel(nextAddr.Value); + context.BranchIf(lblContinue, returnAddress, nextAddr, Comparison.Equal, BasicBlockFrequency.Cold); + + context.Return(returnAddress); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitHash.cs b/src/ARMeilleure/Instructions/InstEmitHash.cs new file mode 100644 index 00000000..82b3e353 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitHash.cs @@ -0,0 +1,69 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHashHelper; +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + private const int ByteSizeLog2 = 0; + private const int HWordSizeLog2 = 1; + private const int WordSizeLog2 = 2; + private const int DWordSizeLog2 = 3; + + public static void Crc32b(ArmEmitterContext context) + { + EmitCrc32Call(context, ByteSizeLog2, false); + } + + public static void Crc32h(ArmEmitterContext context) + { + EmitCrc32Call(context, HWordSizeLog2, false); + } + + public static void Crc32w(ArmEmitterContext context) + { + EmitCrc32Call(context, WordSizeLog2, false); + } + + public static void Crc32x(ArmEmitterContext context) + { + EmitCrc32Call(context, DWordSizeLog2, false); + } + + public static void Crc32cb(ArmEmitterContext context) + { + EmitCrc32Call(context, ByteSizeLog2, true); + } + + public static void Crc32ch(ArmEmitterContext context) + { + EmitCrc32Call(context, HWordSizeLog2, true); + } + + public static void Crc32cw(ArmEmitterContext context) + { + EmitCrc32Call(context, WordSizeLog2, true); + } + + public static void Crc32cx(ArmEmitterContext context) + { + EmitCrc32Call(context, DWordSizeLog2, true); + } + + private static void EmitCrc32Call(ArmEmitterContext context, int size, bool c) + { + OpCodeAluBinary op = (OpCodeAluBinary)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand d = EmitCrc32(context, n, m, size, c); + + SetIntOrZR(context, op.Rd, d); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitHash32.cs b/src/ARMeilleure/Instructions/InstEmitHash32.cs new file mode 100644 index 00000000..30c893a7 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitHash32.cs @@ -0,0 +1,53 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using static ARMeilleure.Instructions.InstEmitHashHelper; +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Crc32b(ArmEmitterContext context) + { + EmitCrc32Call(context, ByteSizeLog2, false); + } + + public static void Crc32h(ArmEmitterContext context) + { + EmitCrc32Call(context, HWordSizeLog2, false); + } + + public static void Crc32w(ArmEmitterContext context) + { + EmitCrc32Call(context, WordSizeLog2, false); + } + + public static void Crc32cb(ArmEmitterContext context) + { + EmitCrc32Call(context, ByteSizeLog2, true); + } + + public static void Crc32ch(ArmEmitterContext context) + { + EmitCrc32Call(context, HWordSizeLog2, true); + } + + public static void Crc32cw(ArmEmitterContext context) + { + EmitCrc32Call(context, WordSizeLog2, true); + } + + private static void EmitCrc32Call(ArmEmitterContext context, int size, bool c) + { + IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + Operand d = EmitCrc32(context, n, m, size, c); + + EmitAluStore(context, d); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitHashHelper.cs b/src/ARMeilleure/Instructions/InstEmitHashHelper.cs new file mode 100644 index 00000000..9b1ad872 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitHashHelper.cs @@ -0,0 +1,124 @@ +// https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf + +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitHashHelper + { + public const uint Crc32RevPoly = 0xedb88320; + public const uint Crc32cRevPoly = 0x82f63b78; + + public static Operand EmitCrc32(ArmEmitterContext context, Operand crc, Operand value, int size, bool castagnoli) + { + Debug.Assert(crc.Type.IsInteger() && value.Type.IsInteger()); + Debug.Assert(size >= 0 && size < 4); + Debug.Assert((size < 3) || (value.Type == OperandType.I64)); + + if (castagnoli && Optimizations.UseSse42) + { + // The CRC32 instruction does not have an immediate variant, so ensure both inputs are in registers. + value = (value.Kind == OperandKind.Constant) ? context.Copy(value) : value; + crc = (crc.Kind == OperandKind.Constant) ? context.Copy(crc) : crc; + + Intrinsic op = size switch + { + 0 => Intrinsic.X86Crc32_8, + 1 => Intrinsic.X86Crc32_16, + _ => Intrinsic.X86Crc32, + }; + + return (size == 3) ? context.ConvertI64ToI32(context.AddIntrinsicLong(op, crc, value)) : context.AddIntrinsicInt(op, crc, value); + } + else if (Optimizations.UsePclmulqdq) + { + return size switch + { + 3 => EmitCrc32Optimized64(context, crc, value, castagnoli), + _ => EmitCrc32Optimized(context, crc, value, castagnoli, size), + }; + } + else + { + string name = (size, castagnoli) switch + { + (0, false) => nameof(SoftFallback.Crc32b), + (1, false) => nameof(SoftFallback.Crc32h), + (2, false) => nameof(SoftFallback.Crc32w), + (3, false) => nameof(SoftFallback.Crc32x), + (0, true) => nameof(SoftFallback.Crc32cb), + (1, true) => nameof(SoftFallback.Crc32ch), + (2, true) => nameof(SoftFallback.Crc32cw), + (3, true) => nameof(SoftFallback.Crc32cx), + _ => throw new ArgumentOutOfRangeException(nameof(size)), + }; + + return context.Call(typeof(SoftFallback).GetMethod(name), crc, value); + } + } + + private static Operand EmitCrc32Optimized(ArmEmitterContext context, Operand crc, Operand data, bool castagnoli, int size) + { + long mu = castagnoli ? 0x0DEA713F1 : 0x1F7011641; // mu' = floor(x^64/P(x))' + long polynomial = castagnoli ? 0x105EC76F0 : 0x1DB710641; // P'(x) << 1 + + crc = context.VectorInsert(context.VectorZero(), crc, 0); + + switch (size) + { + case 0: + data = context.VectorInsert8(context.VectorZero(), data, 0); + break; + case 1: + data = context.VectorInsert16(context.VectorZero(), data, 0); + break; + case 2: + data = context.VectorInsert(context.VectorZero(), data, 0); + break; + } + + int bitsize = 8 << size; + + Operand tmp = context.AddIntrinsic(Intrinsic.X86Pxor, crc, data); + tmp = context.AddIntrinsic(Intrinsic.X86Psllq, tmp, Const(64 - bitsize)); + tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, mu), Const(0)); + tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, polynomial), Const(0)); + + if (bitsize < 32) + { + crc = context.AddIntrinsic(Intrinsic.X86Pslldq, crc, Const((64 - bitsize) / 8)); + tmp = context.AddIntrinsic(Intrinsic.X86Pxor, tmp, crc); + } + + return context.VectorExtract(OperandType.I32, tmp, 2); + } + + private static Operand EmitCrc32Optimized64(ArmEmitterContext context, Operand crc, Operand data, bool castagnoli) + { + long mu = castagnoli ? 0x0DEA713F1 : 0x1F7011641; // mu' = floor(x^64/P(x))' + long polynomial = castagnoli ? 0x105EC76F0 : 0x1DB710641; // P'(x) << 1 + + crc = context.VectorInsert(context.VectorZero(), crc, 0); + data = context.VectorInsert(context.VectorZero(), data, 0); + + Operand tmp = context.AddIntrinsic(Intrinsic.X86Pxor, crc, data); + Operand res = context.AddIntrinsic(Intrinsic.X86Pslldq, tmp, Const(4)); + + tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, res, X86GetScalar(context, mu), Const(0)); + tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, polynomial), Const(0)); + + tmp = context.AddIntrinsic(Intrinsic.X86Pxor, tmp, res); + tmp = context.AddIntrinsic(Intrinsic.X86Psllq, tmp, Const(32)); + + tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, mu), Const(1)); + tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, polynomial), Const(0)); + + return context.VectorExtract(OperandType.I32, tmp, 2); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitHelper.cs b/src/ARMeilleure/Instructions/InstEmitHelper.cs new file mode 100644 index 00000000..7a515f94 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitHelper.cs @@ -0,0 +1,249 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitHelper + { + public static Operand GetExtendedM(ArmEmitterContext context, int rm, IntType type) + { + Operand value = GetIntOrZR(context, rm); + + switch (type) + { + case IntType.UInt8: + value = context.ZeroExtend8(value.Type, value); + break; + case IntType.UInt16: + value = context.ZeroExtend16(value.Type, value); + break; + case IntType.UInt32: + value = context.ZeroExtend32(value.Type, value); + break; + + case IntType.Int8: + value = context.SignExtend8(value.Type, value); + break; + case IntType.Int16: + value = context.SignExtend16(value.Type, value); + break; + case IntType.Int32: + value = context.SignExtend32(value.Type, value); + break; + } + + return value; + } + + public static Operand GetIntA32(ArmEmitterContext context, int regIndex) + { + if (regIndex == RegisterAlias.Aarch32Pc) + { + OpCode32 op = (OpCode32)context.CurrOp; + + return Const((int)op.GetPc()); + } + else + { + return Register(GetRegisterAlias(context.Mode, regIndex), RegisterType.Integer, OperandType.I32); + } + } + + public static Operand GetIntA32AlignedPC(ArmEmitterContext context, int regIndex) + { + if (regIndex == RegisterAlias.Aarch32Pc) + { + OpCode32 op = (OpCode32)context.CurrOp; + + return Const((int)(op.GetPc() & 0xfffffffc)); + } + else + { + return Register(GetRegisterAlias(context.Mode, regIndex), RegisterType.Integer, OperandType.I32); + } + } + + public static Operand GetVecA32(int regIndex) + { + return Register(regIndex, RegisterType.Vector, OperandType.V128); + } + + public static void SetIntA32(ArmEmitterContext context, int regIndex, Operand value) + { + if (regIndex == RegisterAlias.Aarch32Pc) + { + if (!IsA32Return(context)) + { + context.StoreToContext(); + } + + EmitBxWritePc(context, value); + } + else + { + if (value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + Operand reg = Register(GetRegisterAlias(context.Mode, regIndex), RegisterType.Integer, OperandType.I32); + + context.Copy(reg, value); + } + } + + public static int GetRegisterAlias(Aarch32Mode mode, int regIndex) + { + // Only registers >= 8 are banked, + // with registers in the range [8, 12] being + // banked for the FIQ mode, and registers + // 13 and 14 being banked for all modes. + if ((uint)regIndex < 8) + { + return regIndex; + } + + return GetBankedRegisterAlias(mode, regIndex); + } + + public static int GetBankedRegisterAlias(Aarch32Mode mode, int regIndex) + { + return regIndex switch + { +#pragma warning disable IDE0055 // Disable formatting + 8 => mode == Aarch32Mode.Fiq ? RegisterAlias.R8Fiq : RegisterAlias.R8Usr, + 9 => mode == Aarch32Mode.Fiq ? RegisterAlias.R9Fiq : RegisterAlias.R9Usr, + 10 => mode == Aarch32Mode.Fiq ? RegisterAlias.R10Fiq : RegisterAlias.R10Usr, + 11 => mode == Aarch32Mode.Fiq ? RegisterAlias.R11Fiq : RegisterAlias.R11Usr, + 12 => mode == Aarch32Mode.Fiq ? RegisterAlias.R12Fiq : RegisterAlias.R12Usr, + 13 => mode switch + { + Aarch32Mode.User or Aarch32Mode.System => RegisterAlias.SpUsr, + Aarch32Mode.Fiq => RegisterAlias.SpFiq, + Aarch32Mode.Irq => RegisterAlias.SpIrq, + Aarch32Mode.Supervisor => RegisterAlias.SpSvc, + Aarch32Mode.Abort => RegisterAlias.SpAbt, + Aarch32Mode.Hypervisor => RegisterAlias.SpHyp, + Aarch32Mode.Undefined => RegisterAlias.SpUnd, + _ => throw new ArgumentException($"No such AArch32Mode: {mode}", nameof(mode)), + }, + 14 => mode switch + { + Aarch32Mode.User or Aarch32Mode.Hypervisor or Aarch32Mode.System => RegisterAlias.LrUsr, + Aarch32Mode.Fiq => RegisterAlias.LrFiq, + Aarch32Mode.Irq => RegisterAlias.LrIrq, + Aarch32Mode.Supervisor => RegisterAlias.LrSvc, + Aarch32Mode.Abort => RegisterAlias.LrAbt, + Aarch32Mode.Undefined => RegisterAlias.LrUnd, + _ => throw new ArgumentException($"No such AArch32Mode: {mode}", nameof(mode)), + }, + _ => throw new ArgumentOutOfRangeException(nameof(regIndex), regIndex, null), +#pragma warning restore IDE0055 + }; + } + + public static bool IsA32Return(ArmEmitterContext context) + { + return context.CurrOp switch + { + IOpCode32MemMult => true, // Setting PC using LDM is nearly always a return. + OpCode32AluRsImm op => op.Rm == RegisterAlias.Aarch32Lr, + OpCode32AluRsReg op => op.Rm == RegisterAlias.Aarch32Lr, + OpCode32AluReg op => op.Rm == RegisterAlias.Aarch32Lr, + OpCode32Mem op => op.Rn == RegisterAlias.Aarch32Sp && op.WBack && !op.Index, // Setting PC to an address stored on the stack is nearly always a return. + _ => false, + }; + } + + public static void EmitBxWritePc(ArmEmitterContext context, Operand pc, int sourceRegister = 0) + { + bool isReturn = sourceRegister == RegisterAlias.Aarch32Lr || IsA32Return(context); + Operand mode = context.BitwiseAnd(pc, Const(1)); + + SetFlag(context, PState.TFlag, mode); + + Operand addr = context.ConditionalSelect(mode, context.BitwiseAnd(pc, Const(~1)), context.BitwiseAnd(pc, Const(~3))); + + InstEmitFlowHelper.EmitVirtualJump(context, addr, isReturn); + } + + public static Operand GetIntOrZR(ArmEmitterContext context, int regIndex) + { + if (regIndex == RegisterConsts.ZeroIndex) + { + OperandType type = context.CurrOp.GetOperandType(); + + return type == OperandType.I32 ? Const(0) : Const(0L); + } + else + { + return GetIntOrSP(context, regIndex); + } + } + + public static void SetIntOrZR(ArmEmitterContext context, int regIndex, Operand value) + { + if (regIndex == RegisterConsts.ZeroIndex) + { + return; + } + + SetIntOrSP(context, regIndex, value); + } + + public static Operand GetIntOrSP(ArmEmitterContext context, int regIndex) + { + Operand value = Register(regIndex, RegisterType.Integer, OperandType.I64); + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + value = context.ConvertI64ToI32(value); + } + + return value; + } + + public static void SetIntOrSP(ArmEmitterContext context, int regIndex, Operand value) + { + Operand reg = Register(regIndex, RegisterType.Integer, OperandType.I64); + + if (value.Type == OperandType.I32) + { + value = context.ZeroExtend32(OperandType.I64, value); + } + + context.Copy(reg, value); + } + + public static Operand GetVec(int regIndex) + { + return Register(regIndex, RegisterType.Vector, OperandType.V128); + } + + public static Operand GetFlag(PState stateFlag) + { + return Register((int)stateFlag, RegisterType.Flag, OperandType.I32); + } + + public static Operand GetFpFlag(FPState stateFlag) + { + return Register((int)stateFlag, RegisterType.FpFlag, OperandType.I32); + } + + public static void SetFlag(ArmEmitterContext context, PState stateFlag, Operand value) + { + context.Copy(GetFlag(stateFlag), value); + + context.MarkFlagSet(stateFlag); + } + + public static void SetFpFlag(ArmEmitterContext context, FPState stateFlag, Operand value) + { + context.Copy(GetFpFlag(stateFlag), value); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemory.cs b/src/ARMeilleure/Instructions/InstEmitMemory.cs new file mode 100644 index 00000000..840099f9 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemory.cs @@ -0,0 +1,184 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Adr(ArmEmitterContext context) + { + OpCodeAdr op = (OpCodeAdr)context.CurrOp; + + SetIntOrZR(context, op.Rd, Const(op.Address + (ulong)op.Immediate)); + } + + public static void Adrp(ArmEmitterContext context) + { + OpCodeAdr op = (OpCodeAdr)context.CurrOp; + + ulong address = (op.Address & ~0xfffUL) + ((ulong)op.Immediate << 12); + + SetIntOrZR(context, op.Rd, Const(address)); + } + + public static void Ldr(ArmEmitterContext context) => EmitLdr(context, signed: false); + public static void Ldrs(ArmEmitterContext context) => EmitLdr(context, signed: true); + + private static void EmitLdr(ArmEmitterContext context, bool signed) + { + OpCodeMem op = (OpCodeMem)context.CurrOp; + + Operand address = GetAddress(context); + + if (signed && op.Extend64) + { + EmitLoadSx64(context, address, op.Rt, op.Size); + } + else if (signed) + { + EmitLoadSx32(context, address, op.Rt, op.Size); + } + else + { + EmitLoadZx(context, address, op.Rt, op.Size); + } + + EmitWBackIfNeeded(context, address); + } + + public static void Ldr_Literal(ArmEmitterContext context) + { + IOpCodeLit op = (IOpCodeLit)context.CurrOp; + + if (op.Prefetch) + { + return; + } + + if (op.Signed) + { + EmitLoadSx64(context, Const(op.Immediate), op.Rt, op.Size); + } + else + { + EmitLoadZx(context, Const(op.Immediate), op.Rt, op.Size); + } + } + + public static void Ldp(ArmEmitterContext context) + { + OpCodeMemPair op = (OpCodeMemPair)context.CurrOp; + + void EmitLoad(int rt, Operand ldAddr) + { + if (op.Extend64) + { + EmitLoadSx64(context, ldAddr, rt, op.Size); + } + else + { + EmitLoadZx(context, ldAddr, rt, op.Size); + } + } + + Operand address = GetAddress(context); + Operand address2 = GetAddress(context, 1L << op.Size); + + EmitLoad(op.Rt, address); + EmitLoad(op.Rt2, address2); + + EmitWBackIfNeeded(context, address); + } + + public static void Str(ArmEmitterContext context) + { + OpCodeMem op = (OpCodeMem)context.CurrOp; + + Operand address = GetAddress(context); + + EmitStore(context, address, op.Rt, op.Size); + + EmitWBackIfNeeded(context, address); + } + + public static void Stp(ArmEmitterContext context) + { + OpCodeMemPair op = (OpCodeMemPair)context.CurrOp; + + Operand address = GetAddress(context); + Operand address2 = GetAddress(context, 1L << op.Size); + + EmitStore(context, address, op.Rt, op.Size); + EmitStore(context, address2, op.Rt2, op.Size); + + EmitWBackIfNeeded(context, address); + } + + private static Operand GetAddress(ArmEmitterContext context, long addend = 0) + { + Operand address = default; + + switch (context.CurrOp) + { + case OpCodeMemImm op: + { + address = context.Copy(GetIntOrSP(context, op.Rn)); + + // Pre-indexing. + if (!op.PostIdx) + { + address = context.Add(address, Const(op.Immediate + addend)); + } + else if (addend != 0) + { + address = context.Add(address, Const(addend)); + } + + break; + } + + case OpCodeMemReg op: + { + Operand n = GetIntOrSP(context, op.Rn); + + Operand m = GetExtendedM(context, op.Rm, op.IntType); + + if (op.Shift) + { + m = context.ShiftLeft(m, Const(op.Size)); + } + + address = context.Add(n, m); + + if (addend != 0) + { + address = context.Add(address, Const(addend)); + } + + break; + } + } + + return address; + } + + private static void EmitWBackIfNeeded(ArmEmitterContext context, Operand address) + { + // Check whenever the current OpCode has post-indexed write back, if so write it. + if (context.CurrOp is OpCodeMemImm op && op.WBack) + { + if (op.PostIdx) + { + address = context.Add(address, Const(op.Immediate)); + } + + SetIntOrSP(context, op.Rn, address); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemory32.cs b/src/ARMeilleure/Instructions/InstEmitMemory32.cs new file mode 100644 index 00000000..cee06700 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemory32.cs @@ -0,0 +1,264 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + private const int ByteSizeLog2 = 0; + private const int HWordSizeLog2 = 1; + private const int WordSizeLog2 = 2; + private const int DWordSizeLog2 = 3; + + [Flags] + enum AccessType + { + Store = 0, + Signed = 1, + Load = 2, + Ordered = 4, + Exclusive = 8, + + LoadZx = Load, + LoadSx = Load | Signed, + } + + public static void Ldm(ArmEmitterContext context) + { + IOpCode32MemMult op = (IOpCode32MemMult)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + + Operand baseAddress = context.Add(n, Const(op.Offset)); + + bool writesToPc = (op.RegisterMask & (1 << RegisterAlias.Aarch32Pc)) != 0; + + bool writeBack = op.PostOffset != 0 && (op.Rn != RegisterAlias.Aarch32Pc || !writesToPc); + + if (writeBack) + { + SetIntA32(context, op.Rn, context.Add(n, Const(op.PostOffset))); + } + + int mask = op.RegisterMask; + int offset = 0; + + for (int register = 0; mask != 0; mask >>= 1, register++) + { + if ((mask & 1) != 0) + { + Operand address = context.Add(baseAddress, Const(offset)); + + EmitLoadZx(context, address, register, WordSizeLog2); + + offset += 4; + } + } + } + + public static void Ldr(ArmEmitterContext context) + { + EmitLoadOrStore(context, WordSizeLog2, AccessType.LoadZx); + } + + public static void Ldrb(ArmEmitterContext context) + { + EmitLoadOrStore(context, ByteSizeLog2, AccessType.LoadZx); + } + + public static void Ldrd(ArmEmitterContext context) + { + EmitLoadOrStore(context, DWordSizeLog2, AccessType.LoadZx); + } + + public static void Ldrh(ArmEmitterContext context) + { + EmitLoadOrStore(context, HWordSizeLog2, AccessType.LoadZx); + } + + public static void Ldrsb(ArmEmitterContext context) + { + EmitLoadOrStore(context, ByteSizeLog2, AccessType.LoadSx); + } + + public static void Ldrsh(ArmEmitterContext context) + { + EmitLoadOrStore(context, HWordSizeLog2, AccessType.LoadSx); + } + + public static void Stm(ArmEmitterContext context) + { + IOpCode32MemMult op = (IOpCode32MemMult)context.CurrOp; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + + Operand baseAddress = context.Add(n, Const(op.Offset)); + + int mask = op.RegisterMask; + int offset = 0; + + for (int register = 0; mask != 0; mask >>= 1, register++) + { + if ((mask & 1) != 0) + { + Operand address = context.Add(baseAddress, Const(offset)); + + EmitStore(context, address, register, WordSizeLog2); + + // Note: If Rn is also specified on the register list, + // and Rn is the first register on this list, then the + // value that is written to memory is the unmodified value, + // before the write back. If it is on the list, but it's + // not the first one, then the value written to memory + // varies between CPUs. + if (offset == 0 && op.PostOffset != 0) + { + // Emit write back after the first write. + SetIntA32(context, op.Rn, context.Add(n, Const(op.PostOffset))); + } + + offset += 4; + } + } + } + + public static void Str(ArmEmitterContext context) + { + EmitLoadOrStore(context, WordSizeLog2, AccessType.Store); + } + + public static void Strb(ArmEmitterContext context) + { + EmitLoadOrStore(context, ByteSizeLog2, AccessType.Store); + } + + public static void Strd(ArmEmitterContext context) + { + EmitLoadOrStore(context, DWordSizeLog2, AccessType.Store); + } + + public static void Strh(ArmEmitterContext context) + { + EmitLoadOrStore(context, HWordSizeLog2, AccessType.Store); + } + + private static void EmitLoadOrStore(ArmEmitterContext context, int size, AccessType accType) + { + IOpCode32Mem op = (IOpCode32Mem)context.CurrOp; + + Operand n = context.Copy(GetIntA32AlignedPC(context, op.Rn)); + Operand m = GetMemM(context, setCarry: false); + + Operand temp = default; + + if (op.Index || op.WBack) + { + temp = op.Add + ? context.Add(n, m) + : context.Subtract(n, m); + } + + if (op.WBack) + { + SetIntA32(context, op.Rn, temp); + } + + Operand address; + + if (op.Index) + { + address = temp; + } + else + { + address = n; + } + + if ((accType & AccessType.Load) != 0) + { + void Load(int rt, int offs, int loadSize) + { + Operand addr = context.Add(address, Const(offs)); + + if ((accType & AccessType.Signed) != 0) + { + EmitLoadSx32(context, addr, rt, loadSize); + } + else + { + EmitLoadZx(context, addr, rt, loadSize); + } + } + + if (size == DWordSizeLog2) + { + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + Load(op.Rt, 0, WordSizeLog2); + Load(op.Rt2, 4, WordSizeLog2); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + Load(op.Rt2, 0, WordSizeLog2); + Load(op.Rt, 4, WordSizeLog2); + + context.MarkLabel(lblEnd); + } + else + { + Load(op.Rt, 0, size); + } + } + else + { + void Store(int rt, int offs, int storeSize) + { + Operand addr = context.Add(address, Const(offs)); + + EmitStore(context, addr, rt, storeSize); + } + + if (size == DWordSizeLog2) + { + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + Store(op.Rt, 0, WordSizeLog2); + Store(op.Rt2, 4, WordSizeLog2); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + Store(op.Rt2, 0, WordSizeLog2); + Store(op.Rt, 4, WordSizeLog2); + + context.MarkLabel(lblEnd); + } + else + { + Store(op.Rt, 0, size); + } + } + } + + public static void Adr(ArmEmitterContext context) + { + IOpCode32Adr op = (IOpCode32Adr)context.CurrOp; + SetIntA32(context, op.Rd, Const(op.Immediate)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryEx.cs b/src/ARMeilleure/Instructions/InstEmitMemoryEx.cs new file mode 100644 index 00000000..8c95b33c --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemoryEx.cs @@ -0,0 +1,177 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryExHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + [Flags] + private enum AccessType + { + None = 0, + Ordered = 1, + Exclusive = 2, + OrderedEx = Ordered | Exclusive, + } + + public static void Clrex(ArmEmitterContext context) + { + EmitClearExclusive(context); + } + + public static void Csdb(ArmEmitterContext context) + { + // Execute as no-op. + } + + public static void Dmb(ArmEmitterContext context) => EmitBarrier(context); + public static void Dsb(ArmEmitterContext context) => EmitBarrier(context); + + public static void Ldar(ArmEmitterContext context) => EmitLdr(context, AccessType.Ordered); + public static void Ldaxr(ArmEmitterContext context) => EmitLdr(context, AccessType.OrderedEx); + public static void Ldxr(ArmEmitterContext context) => EmitLdr(context, AccessType.Exclusive); + public static void Ldxp(ArmEmitterContext context) => EmitLdp(context, AccessType.Exclusive); + public static void Ldaxp(ArmEmitterContext context) => EmitLdp(context, AccessType.OrderedEx); + + private static void EmitLdr(ArmEmitterContext context, AccessType accType) + { + EmitLoadEx(context, accType, pair: false); + } + + private static void EmitLdp(ArmEmitterContext context, AccessType accType) + { + EmitLoadEx(context, accType, pair: true); + } + + private static void EmitLoadEx(ArmEmitterContext context, AccessType accType, bool pair) + { + OpCodeMemEx op = (OpCodeMemEx)context.CurrOp; + + bool ordered = (accType & AccessType.Ordered) != 0; + bool exclusive = (accType & AccessType.Exclusive) != 0; + + if (ordered) + { + EmitBarrier(context); + } + + Operand address = context.Copy(GetIntOrSP(context, op.Rn)); + + if (pair) + { + // Exclusive loads should be atomic. For pairwise loads, we need to + // read all the data at once. For a 32-bits pairwise load, we do a + // simple 64-bits load, for a 128-bits load, we need to call a special + // method to read 128-bits atomically. + if (op.Size == 2) + { + Operand value = EmitLoadExclusive(context, address, exclusive, 3); + + Operand valueLow = context.ConvertI64ToI32(value); + + valueLow = context.ZeroExtend32(OperandType.I64, valueLow); + + Operand valueHigh = context.ShiftRightUI(value, Const(32)); + + SetIntOrZR(context, op.Rt, valueLow); + SetIntOrZR(context, op.Rt2, valueHigh); + } + else if (op.Size == 3) + { + Operand value = EmitLoadExclusive(context, address, exclusive, 4); + + Operand valueLow = context.VectorExtract(OperandType.I64, value, 0); + Operand valueHigh = context.VectorExtract(OperandType.I64, value, 1); + + SetIntOrZR(context, op.Rt, valueLow); + SetIntOrZR(context, op.Rt2, valueHigh); + } + else + { + throw new InvalidOperationException($"Invalid load size of {1 << op.Size} bytes."); + } + } + else + { + // 8, 16, 32 or 64-bits (non-pairwise) load. + Operand value = EmitLoadExclusive(context, address, exclusive, op.Size); + + SetIntOrZR(context, op.Rt, value); + } + } + + public static void Prfm(ArmEmitterContext context) + { + // Memory Prefetch, execute as no-op. + } + + public static void Stlr(ArmEmitterContext context) => EmitStr(context, AccessType.Ordered); + public static void Stlxr(ArmEmitterContext context) => EmitStr(context, AccessType.OrderedEx); + public static void Stxr(ArmEmitterContext context) => EmitStr(context, AccessType.Exclusive); + public static void Stxp(ArmEmitterContext context) => EmitStp(context, AccessType.Exclusive); + public static void Stlxp(ArmEmitterContext context) => EmitStp(context, AccessType.OrderedEx); + + private static void EmitStr(ArmEmitterContext context, AccessType accType) + { + EmitStoreEx(context, accType, pair: false); + } + + private static void EmitStp(ArmEmitterContext context, AccessType accType) + { + EmitStoreEx(context, accType, pair: true); + } + + private static void EmitStoreEx(ArmEmitterContext context, AccessType accType, bool pair) + { + OpCodeMemEx op = (OpCodeMemEx)context.CurrOp; + + bool ordered = (accType & AccessType.Ordered) != 0; + bool exclusive = (accType & AccessType.Exclusive) != 0; + + Operand address = context.Copy(GetIntOrSP(context, op.Rn)); + + Operand t = GetIntOrZR(context, op.Rt); + + if (pair) + { + Debug.Assert(op.Size == 2 || op.Size == 3, "Invalid size for pairwise store."); + + Operand t2 = GetIntOrZR(context, op.Rt2); + + Operand value; + + if (op.Size == 2) + { + value = context.BitwiseOr(t, context.ShiftLeft(t2, Const(32))); + } + else /* if (op.Size == 3) */ + { + value = context.VectorInsert(context.VectorZero(), t, 0); + value = context.VectorInsert(value, t2, 1); + } + + EmitStoreExclusive(context, address, value, exclusive, op.Size + 1, op.Rs, a32: false); + } + else + { + EmitStoreExclusive(context, address, t, exclusive, op.Size, op.Rs, a32: false); + } + + if (ordered) + { + EmitBarrier(context); + } + } + + private static void EmitBarrier(ArmEmitterContext context) + { + context.MemoryBarrier(); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryEx32.cs b/src/ARMeilleure/Instructions/InstEmitMemoryEx32.cs new file mode 100644 index 00000000..15021882 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemoryEx32.cs @@ -0,0 +1,237 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryExHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Clrex(ArmEmitterContext context) + { + EmitClearExclusive(context); + } + + public static void Csdb(ArmEmitterContext context) + { + // Execute as no-op. + } + + public static void Dmb(ArmEmitterContext context) => EmitBarrier(context); + + public static void Dsb(ArmEmitterContext context) => EmitBarrier(context); + + public static void Ldrex(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.LoadZx | AccessType.Exclusive); + } + + public static void Ldrexb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.LoadZx | AccessType.Exclusive); + } + + public static void Ldrexd(ArmEmitterContext context) + { + EmitExLoadOrStore(context, DWordSizeLog2, AccessType.LoadZx | AccessType.Exclusive); + } + + public static void Ldrexh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.LoadZx | AccessType.Exclusive); + } + + public static void Lda(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.LoadZx | AccessType.Ordered); + } + + public static void Ldab(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.LoadZx | AccessType.Ordered); + } + + public static void Ldaex(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.LoadZx | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Ldaexb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.LoadZx | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Ldaexd(ArmEmitterContext context) + { + EmitExLoadOrStore(context, DWordSizeLog2, AccessType.LoadZx | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Ldaexh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.LoadZx | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Ldah(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.LoadZx | AccessType.Ordered); + } + + // Stores. + + public static void Strex(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.Store | AccessType.Exclusive); + } + + public static void Strexb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.Store | AccessType.Exclusive); + } + + public static void Strexd(ArmEmitterContext context) + { + EmitExLoadOrStore(context, DWordSizeLog2, AccessType.Store | AccessType.Exclusive); + } + + public static void Strexh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.Store | AccessType.Exclusive); + } + + public static void Stl(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.Store | AccessType.Ordered); + } + + public static void Stlb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.Store | AccessType.Ordered); + } + + public static void Stlex(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.Store | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Stlexb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.Store | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Stlexd(ArmEmitterContext context) + { + EmitExLoadOrStore(context, DWordSizeLog2, AccessType.Store | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Stlexh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.Store | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Stlh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.Store | AccessType.Ordered); + } + + private static void EmitExLoadOrStore(ArmEmitterContext context, int size, AccessType accType) + { + IOpCode32MemEx op = (IOpCode32MemEx)context.CurrOp; + + Operand address = context.Copy(GetIntA32(context, op.Rn)); + + var exclusive = (accType & AccessType.Exclusive) != 0; + var ordered = (accType & AccessType.Ordered) != 0; + + if ((accType & AccessType.Load) != 0) + { + if (ordered) + { + EmitBarrier(context); + } + + if (size == DWordSizeLog2) + { + // Keep loads atomic - make the call to get the whole region and then decompose it into parts + // for the registers. + + Operand value = EmitLoadExclusive(context, address, exclusive, size); + + Operand valueLow = context.ConvertI64ToI32(value); + + valueLow = context.ZeroExtend32(OperandType.I64, valueLow); + + Operand valueHigh = context.ShiftRightUI(value, Const(32)); + + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + SetIntA32(context, op.Rt, valueLow); + SetIntA32(context, op.Rt2, valueHigh); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + SetIntA32(context, op.Rt2, valueLow); + SetIntA32(context, op.Rt, valueHigh); + + context.MarkLabel(lblEnd); + } + else + { + SetIntA32(context, op.Rt, EmitLoadExclusive(context, address, exclusive, size)); + } + } + else + { + if (size == DWordSizeLog2) + { + // Split the result into 2 words (based on endianness) + + Operand lo = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rt)); + Operand hi = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rt2)); + + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + Operand leResult = context.BitwiseOr(lo, context.ShiftLeft(hi, Const(32))); + EmitStoreExclusive(context, address, leResult, exclusive, size, op.Rd, a32: true); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + Operand beResult = context.BitwiseOr(hi, context.ShiftLeft(lo, Const(32))); + EmitStoreExclusive(context, address, beResult, exclusive, size, op.Rd, a32: true); + + context.MarkLabel(lblEnd); + } + else + { + Operand value = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rt)); + EmitStoreExclusive(context, address, value, exclusive, size, op.Rd, a32: true); + } + + if (ordered) + { + EmitBarrier(context); + } + } + } + + private static void EmitBarrier(ArmEmitterContext context) + { + // Note: This barrier is most likely not necessary, and probably + // doesn't make any difference since we need to do a ton of stuff + // (software MMU emulation) to read or write anything anyway. + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs b/src/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs new file mode 100644 index 00000000..7fca5b85 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs @@ -0,0 +1,173 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitMemoryExHelper + { + private const int ErgSizeLog2 = 4; + + public static Operand EmitLoadExclusive(ArmEmitterContext context, Operand address, bool exclusive, int size) + { + if (exclusive) + { + Operand value; + + if (size == 4) + { + // Only 128-bit CAS is guaranteed to have a atomic load. + Operand physAddr = InstEmitMemoryHelper.EmitPtPointerLoad(context, address, default, write: false, 4); + + Operand zero = context.VectorZero(); + + value = context.CompareAndSwap(physAddr, zero, zero); + } + else + { + value = InstEmitMemoryHelper.EmitReadIntAligned(context, address, size); + } + + Operand arg0 = context.LoadArgument(OperandType.I64, 0); + + Operand exAddrPtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveAddressOffset())); + Operand exValuePtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveValueOffset())); + + context.Store(exAddrPtr, context.BitwiseAnd(address, Const(address.Type, GetExclusiveAddressMask()))); + + // Make sure the unused higher bits of the value are cleared. + if (size < 3) + { + context.Store(exValuePtr, Const(0UL)); + } + if (size < 4) + { + context.Store(context.Add(exValuePtr, Const(exValuePtr.Type, 8L)), Const(0UL)); + } + + // Store the new exclusive value. + context.Store(exValuePtr, value); + + return value; + } + else + { + return InstEmitMemoryHelper.EmitReadIntAligned(context, address, size); + } + } + + public static void EmitStoreExclusive( + ArmEmitterContext context, + Operand address, + Operand value, + bool exclusive, + int size, + int rs, + bool a32) + { + if (size < 3) + { + value = context.ConvertI64ToI32(value); + } + + if (exclusive) + { + // We overwrite one of the register (Rs), + // keep a copy of the values to ensure we are working with the correct values. + address = context.Copy(address); + value = context.Copy(value); + + void SetRs(Operand value) + { + if (a32) + { + SetIntA32(context, rs, value); + } + else + { + SetIntOrZR(context, rs, value); + } + } + + Operand arg0 = context.LoadArgument(OperandType.I64, 0); + + Operand exAddrPtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveAddressOffset())); + Operand exAddr = context.Load(address.Type, exAddrPtr); + + // STEP 1: Check if we have exclusive access to this memory region. If not, fail and skip store. + Operand maskedAddress = context.BitwiseAnd(address, Const(address.Type, GetExclusiveAddressMask())); + + Operand exFailed = context.ICompareNotEqual(exAddr, maskedAddress); + + Operand lblExit = Label(); + + SetRs(Const(1)); + + context.BranchIfTrue(lblExit, exFailed); + + // STEP 2: We have exclusive access and the address is valid, attempt the store using CAS. + Operand physAddr = InstEmitMemoryHelper.EmitPtPointerLoad(context, address, default, write: true, size); + + Operand exValuePtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveValueOffset())); + Operand exValue = size switch + { + 0 => context.Load8(exValuePtr), + 1 => context.Load16(exValuePtr), + 2 => context.Load(OperandType.I32, exValuePtr), + 3 => context.Load(OperandType.I64, exValuePtr), + _ => context.Load(OperandType.V128, exValuePtr), + }; + + Operand currValue = size switch + { + 0 => context.CompareAndSwap8(physAddr, exValue, value), + 1 => context.CompareAndSwap16(physAddr, exValue, value), + _ => context.CompareAndSwap(physAddr, exValue, value), + }; + + // STEP 3: Check if we succeeded by comparing expected and in-memory values. + Operand storeFailed; + + if (size == 4) + { + Operand currValueLow = context.VectorExtract(OperandType.I64, currValue, 0); + Operand currValueHigh = context.VectorExtract(OperandType.I64, currValue, 1); + + Operand exValueLow = context.VectorExtract(OperandType.I64, exValue, 0); + Operand exValueHigh = context.VectorExtract(OperandType.I64, exValue, 1); + + storeFailed = context.BitwiseOr( + context.ICompareNotEqual(currValueLow, exValueLow), + context.ICompareNotEqual(currValueHigh, exValueHigh)); + } + else + { + storeFailed = context.ICompareNotEqual(currValue, exValue); + } + + SetRs(storeFailed); + + context.MarkLabel(lblExit); + } + else + { + InstEmitMemoryHelper.EmitWriteIntAligned(context, address, value, size); + } + } + + public static void EmitClearExclusive(ArmEmitterContext context) + { + Operand arg0 = context.LoadArgument(OperandType.I64, 0); + + Operand exAddrPtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveAddressOffset())); + + // We store ULONG max to force any exclusive address checks to fail, + // since this value is not aligned to the ERG mask. + context.Store(exAddrPtr, Const(ulong.MaxValue)); + } + + private static long GetExclusiveAddressMask() => ~((4L << ErgSizeLog2) - 1); + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs b/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs new file mode 100644 index 00000000..ace6fe1c --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs @@ -0,0 +1,778 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using ARMeilleure.Translation; +using ARMeilleure.Translation.PTC; +using System; +using System.Reflection; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitMemoryHelper + { + private const int PageBits = 12; + private const int PageMask = (1 << PageBits) - 1; + + private enum Extension + { + Zx, + Sx32, + Sx64, + } + + public static void EmitLoadZx(ArmEmitterContext context, Operand address, int rt, int size) + { + EmitLoad(context, address, Extension.Zx, rt, size); + } + + public static void EmitLoadSx32(ArmEmitterContext context, Operand address, int rt, int size) + { + EmitLoad(context, address, Extension.Sx32, rt, size); + } + + public static void EmitLoadSx64(ArmEmitterContext context, Operand address, int rt, int size) + { + EmitLoad(context, address, Extension.Sx64, rt, size); + } + + private static void EmitLoad(ArmEmitterContext context, Operand address, Extension ext, int rt, int size) + { + bool isSimd = IsSimd(context); + + if ((uint)size > (isSimd ? 4 : 3)) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if (isSimd) + { + EmitReadVector(context, address, context.VectorZero(), rt, 0, size); + } + else + { + EmitReadInt(context, address, rt, size); + } + + if (!isSimd && !(context.CurrOp is OpCode32 && rt == State.RegisterAlias.Aarch32Pc)) + { + Operand value = GetInt(context, rt); + + if (ext == Extension.Sx32 || ext == Extension.Sx64) + { + OperandType destType = ext == Extension.Sx64 ? OperandType.I64 : OperandType.I32; + + switch (size) + { + case 0: + value = context.SignExtend8(destType, value); + break; + case 1: + value = context.SignExtend16(destType, value); + break; + case 2: + value = context.SignExtend32(destType, value); + break; + } + } + + SetInt(context, rt, value); + } + } + + public static void EmitLoadSimd( + ArmEmitterContext context, + Operand address, + Operand vector, + int rt, + int elem, + int size) + { + EmitReadVector(context, address, vector, rt, elem, size); + } + + public static void EmitStore(ArmEmitterContext context, Operand address, int rt, int size) + { + bool isSimd = IsSimd(context); + + if ((uint)size > (isSimd ? 4 : 3)) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if (isSimd) + { + EmitWriteVector(context, address, rt, 0, size); + } + else + { + EmitWriteInt(context, address, rt, size); + } + } + + public static void EmitStoreSimd( + ArmEmitterContext context, + Operand address, + int rt, + int elem, + int size) + { + EmitWriteVector(context, address, rt, elem, size); + } + + private static bool IsSimd(ArmEmitterContext context) + { + return context.CurrOp is IOpCodeSimd && + !(context.CurrOp is OpCodeSimdMemMs || + context.CurrOp is OpCodeSimdMemSs); + } + + public static Operand EmitReadInt(ArmEmitterContext context, Operand address, int size) + { + Operand temp = context.AllocateLocal(size == 3 ? OperandType.I64 : OperandType.I32); + + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size); + + Operand value = default; + + switch (size) + { + case 0: + value = context.Load8(physAddr); + break; + case 1: + value = context.Load16(physAddr); + break; + case 2: + value = context.Load(OperandType.I32, physAddr); + break; + case 3: + value = context.Load(OperandType.I64, physAddr); + break; + } + + context.Copy(temp, value); + + if (!context.Memory.Type.IsHostMappedOrTracked()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + context.Copy(temp, EmitReadIntFallback(context, address, size)); + + context.MarkLabel(lblEnd); + } + + return temp; + } + + private static void EmitReadInt(ArmEmitterContext context, Operand address, int rt, int size) + { + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size); + + Operand value = default; + + switch (size) + { + case 0: + value = context.Load8(physAddr); + break; + case 1: + value = context.Load16(physAddr); + break; + case 2: + value = context.Load(OperandType.I32, physAddr); + break; + case 3: + value = context.Load(OperandType.I64, physAddr); + break; + } + + SetInt(context, rt, value); + + if (!context.Memory.Type.IsHostMappedOrTracked()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + EmitReadIntFallback(context, address, rt, size); + + context.MarkLabel(lblEnd); + } + } + + public static Operand EmitReadIntAligned(ArmEmitterContext context, Operand address, int size) + { + if ((uint)size > 4) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + Operand physAddr = EmitPtPointerLoad(context, address, default, write: false, size); + + return size switch + { + 0 => context.Load8(physAddr), + 1 => context.Load16(physAddr), + 2 => context.Load(OperandType.I32, physAddr), + 3 => context.Load(OperandType.I64, physAddr), + _ => context.Load(OperandType.V128, physAddr), + }; + } + + private static void EmitReadVector( + ArmEmitterContext context, + Operand address, + Operand vector, + int rt, + int elem, + int size) + { + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size); + + Operand value = default; + + switch (size) + { + case 0: + value = context.VectorInsert8(vector, context.Load8(physAddr), elem); + break; + case 1: + value = context.VectorInsert16(vector, context.Load16(physAddr), elem); + break; + case 2: + value = context.VectorInsert(vector, context.Load(OperandType.I32, physAddr), elem); + break; + case 3: + value = context.VectorInsert(vector, context.Load(OperandType.I64, physAddr), elem); + break; + case 4: + value = context.Load(OperandType.V128, physAddr); + break; + } + + context.Copy(GetVec(rt), value); + + if (!context.Memory.Type.IsHostMappedOrTracked()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + EmitReadVectorFallback(context, address, vector, rt, elem, size); + + context.MarkLabel(lblEnd); + } + } + + private static Operand VectorCreate(ArmEmitterContext context, Operand value) + { + return context.VectorInsert(context.VectorZero(), value, 0); + } + + private static void EmitWriteInt(ArmEmitterContext context, Operand address, int rt, int size) + { + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: true, size); + + Operand value = GetInt(context, rt); + + if (size < 3 && value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + + switch (size) + { + case 0: + context.Store8(physAddr, value); + break; + case 1: + context.Store16(physAddr, value); + break; + case 2: + context.Store(physAddr, value); + break; + case 3: + context.Store(physAddr, value); + break; + } + + if (!context.Memory.Type.IsHostMappedOrTracked()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + EmitWriteIntFallback(context, address, rt, size); + + context.MarkLabel(lblEnd); + } + } + + public static void EmitWriteIntAligned(ArmEmitterContext context, Operand address, Operand value, int size) + { + if ((uint)size > 4) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + Operand physAddr = EmitPtPointerLoad(context, address, default, write: true, size); + + if (size < 3 && value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + + if (size == 0) + { + context.Store8(physAddr, value); + } + else if (size == 1) + { + context.Store16(physAddr, value); + } + else + { + context.Store(physAddr, value); + } + } + + private static void EmitWriteVector( + ArmEmitterContext context, + Operand address, + int rt, + int elem, + int size) + { + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: true, size); + + Operand value = GetVec(rt); + + switch (size) + { + case 0: + context.Store8(physAddr, context.VectorExtract8(value, elem)); + break; + case 1: + context.Store16(physAddr, context.VectorExtract16(value, elem)); + break; + case 2: + context.Store(physAddr, context.VectorExtract(OperandType.I32, value, elem)); + break; + case 3: + context.Store(physAddr, context.VectorExtract(OperandType.I64, value, elem)); + break; + case 4: + context.Store(physAddr, value); + break; + } + + if (!context.Memory.Type.IsHostMappedOrTracked()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + EmitWriteVectorFallback(context, address, rt, elem, size); + + context.MarkLabel(lblEnd); + } + } + + public static Operand EmitPtPointerLoad(ArmEmitterContext context, Operand address, Operand lblSlowPath, bool write, int size) + { + if (context.Memory.Type.IsHostMapped()) + { + return EmitHostMappedPointer(context, address); + } + else if (context.Memory.Type.IsHostTracked()) + { + if (address.Type == OperandType.I32) + { + address = context.ZeroExtend32(OperandType.I64, address); + } + + if (context.Memory.Type == MemoryManagerType.HostTracked) + { + Operand mask = Const(ulong.MaxValue >> (64 - context.Memory.AddressSpaceBits)); + address = context.BitwiseAnd(address, mask); + } + + Operand ptBase = !context.HasPtc + ? Const(context.Memory.PageTablePointer.ToInt64()) + : Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol); + + Operand ptOffset = context.ShiftRightUI(address, Const(PageBits)); + + return context.Add(address, context.Load(OperandType.I64, context.Add(ptBase, context.ShiftLeft(ptOffset, Const(3))))); + } + + int ptLevelBits = context.Memory.AddressSpaceBits - PageBits; + int ptLevelSize = 1 << ptLevelBits; + int ptLevelMask = ptLevelSize - 1; + + Operand addrRotated = size != 0 ? context.RotateRight(address, Const(size)) : address; + Operand addrShifted = context.ShiftRightUI(addrRotated, Const(PageBits - size)); + + Operand pte = !context.HasPtc + ? Const(context.Memory.PageTablePointer.ToInt64()) + : Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol); + + Operand pteOffset = context.BitwiseAnd(addrShifted, Const(addrShifted.Type, ptLevelMask)); + + if (pteOffset.Type == OperandType.I32) + { + pteOffset = context.ZeroExtend32(OperandType.I64, pteOffset); + } + + pte = context.Load(OperandType.I64, context.Add(pte, context.ShiftLeft(pteOffset, Const(3)))); + + if (addrShifted.Type == OperandType.I32) + { + addrShifted = context.ZeroExtend32(OperandType.I64, addrShifted); + } + + // If the VA is out of range, or not aligned to the access size, force PTE to 0 by masking it. + pte = context.BitwiseAnd(pte, context.ShiftRightSI(context.Add(addrShifted, Const(-(long)ptLevelSize)), Const(63))); + + if (lblSlowPath != default) + { + if (write) + { + context.BranchIf(lblSlowPath, pte, Const(0L), Comparison.LessOrEqual); + pte = context.BitwiseAnd(pte, Const(0xffffffffffffUL)); // Ignore any software protection bits. (they are still used by C# memory access) + } + else + { + pte = context.ShiftLeft(pte, Const(1)); + context.BranchIf(lblSlowPath, pte, Const(0L), Comparison.LessOrEqual); + pte = context.ShiftRightUI(pte, Const(1)); + } + } + else + { + // When no label is provided to jump to a slow path if the address is invalid, + // we do the validation ourselves, and throw if needed. + + Operand lblNotWatched = Label(); + + // Is the page currently being tracked for read/write? If so we need to call SignalMemoryTracking. + context.BranchIf(lblNotWatched, pte, Const(0L), Comparison.GreaterOrEqual, BasicBlockFrequency.Cold); + + // Signal memory tracking. Size here doesn't matter as address is assumed to be size aligned here. + context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SignalMemoryTracking)), address, Const(1UL), Const(write ? 1 : 0)); + context.MarkLabel(lblNotWatched); + + pte = context.BitwiseAnd(pte, Const(0xffffffffffffUL)); // Ignore any software protection bits. (they are still used by C# memory access) + + Operand lblNonNull = Label(); + + // Skip exception if the PTE address is non-null (not zero). + context.BranchIfTrue(lblNonNull, pte, BasicBlockFrequency.Cold); + + // The call is not expected to return (it should throw). + context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)), address); + context.MarkLabel(lblNonNull); + } + + Operand pageOffset = context.BitwiseAnd(address, Const(address.Type, PageMask)); + + if (pageOffset.Type == OperandType.I32) + { + pageOffset = context.ZeroExtend32(OperandType.I64, pageOffset); + } + + return context.Add(pte, pageOffset); + } + + public static Operand EmitHostMappedPointer(ArmEmitterContext context, Operand address) + { + if (address.Type == OperandType.I32) + { + address = context.ZeroExtend32(OperandType.I64, address); + } + + if (context.Memory.Type == MemoryManagerType.HostMapped) + { + Operand mask = Const(ulong.MaxValue >> (64 - context.Memory.AddressSpaceBits)); + address = context.BitwiseAnd(address, mask); + } + + Operand baseAddr = !context.HasPtc + ? Const(context.Memory.PageTablePointer.ToInt64()) + : Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol); + + return context.Add(baseAddr, address); + } + + private static void EmitReadIntFallback(ArmEmitterContext context, Operand address, int rt, int size) + { + SetInt(context, rt, EmitReadIntFallback(context, address, size)); + } + + private static Operand EmitReadIntFallback(ArmEmitterContext context, Operand address, int size) + { + MethodInfo info = null; + + switch (size) + { + case 0: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)); + break; + case 1: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)); + break; + case 2: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)); + break; + case 3: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)); + break; + } + + return context.Call(info, address); + } + + private static void EmitReadVectorFallback( + ArmEmitterContext context, + Operand address, + Operand vector, + int rt, + int elem, + int size) + { + MethodInfo info = null; + + switch (size) + { + case 0: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)); + break; + case 1: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)); + break; + case 2: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)); + break; + case 3: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)); + break; + case 4: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128)); + break; + } + + Operand value = context.Call(info, address); + + switch (size) + { + case 0: + value = context.VectorInsert8(vector, value, elem); + break; + case 1: + value = context.VectorInsert16(vector, value, elem); + break; + case 2: + value = context.VectorInsert(vector, value, elem); + break; + case 3: + value = context.VectorInsert(vector, value, elem); + break; + } + + context.Copy(GetVec(rt), value); + } + + private static void EmitWriteIntFallback(ArmEmitterContext context, Operand address, int rt, int size) + { + MethodInfo info = null; + + switch (size) + { + case 0: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteByte)); + break; + case 1: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt16)); + break; + case 2: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt32)); + break; + case 3: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt64)); + break; + } + + Operand value = GetInt(context, rt); + + if (size < 3 && value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + + context.Call(info, address, value); + } + + private static void EmitWriteVectorFallback( + ArmEmitterContext context, + Operand address, + int rt, + int elem, + int size) + { + MethodInfo info = null; + + switch (size) + { + case 0: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteByte)); + break; + case 1: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt16)); + break; + case 2: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt32)); + break; + case 3: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt64)); + break; + case 4: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteVector128)); + break; + } + + Operand value = default; + + if (size < 4) + { + switch (size) + { + case 0: + value = context.VectorExtract8(GetVec(rt), elem); + break; + case 1: + value = context.VectorExtract16(GetVec(rt), elem); + break; + case 2: + value = context.VectorExtract(OperandType.I32, GetVec(rt), elem); + break; + case 3: + value = context.VectorExtract(OperandType.I64, GetVec(rt), elem); + break; + } + } + else + { + value = GetVec(rt); + } + + context.Call(info, address, value); + } + + private static Operand GetInt(ArmEmitterContext context, int rt) + { + return context.CurrOp is OpCode32 ? GetIntA32(context, rt) : GetIntOrZR(context, rt); + } + + private static void SetInt(ArmEmitterContext context, int rt, Operand value) + { + if (context.CurrOp is OpCode32) + { + SetIntA32(context, rt, value); + } + else + { + SetIntOrZR(context, rt, value); + } + } + + // ARM32 helpers. + public static Operand GetMemM(ArmEmitterContext context, bool setCarry = true) + { + return context.CurrOp switch + { + IOpCode32MemRsImm op => GetMShiftedByImmediate(context, op, setCarry), + IOpCode32MemReg op => GetIntA32(context, op.Rm), + IOpCode32Mem op => Const(op.Immediate), + OpCode32SimdMemImm op => Const(op.Immediate), + _ => throw InvalidOpCodeType(context.CurrOp), + }; + } + + private static Exception InvalidOpCodeType(OpCode opCode) + { + return new InvalidOperationException($"Invalid OpCode type \"{opCode?.GetType().Name ?? "null"}\"."); + } + + public static Operand GetMShiftedByImmediate(ArmEmitterContext context, IOpCode32MemRsImm op, bool setCarry) + { + Operand m = GetIntA32(context, op.Rm); + + int shift = op.Immediate; + + if (shift == 0) + { + switch (op.ShiftType) + { + case ShiftType.Lsr: + shift = 32; + break; + case ShiftType.Asr: + shift = 32; + break; + case ShiftType.Ror: + shift = 1; + break; + } + } + + if (shift != 0) + { + setCarry &= false; + + switch (op.ShiftType) + { + case ShiftType.Lsl: + m = InstEmitAluHelper.GetLslC(context, m, setCarry, shift); + break; + case ShiftType.Lsr: + m = InstEmitAluHelper.GetLsrC(context, m, setCarry, shift); + break; + case ShiftType.Asr: + m = InstEmitAluHelper.GetAsrC(context, m, setCarry, shift); + break; + case ShiftType.Ror: + if (op.Immediate != 0) + { + m = InstEmitAluHelper.GetRorC(context, m, setCarry, shift); + } + else + { + m = InstEmitAluHelper.GetRrxC(context, m, setCarry); + } + break; + } + } + + return m; + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMove.cs b/src/ARMeilleure/Instructions/InstEmitMove.cs new file mode 100644 index 00000000..f23ac333 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMove.cs @@ -0,0 +1,41 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Movk(ArmEmitterContext context) + { + OpCodeMov op = (OpCodeMov)context.CurrOp; + + OperandType type = op.GetOperandType(); + + Operand res = GetIntOrZR(context, op.Rd); + + res = context.BitwiseAnd(res, Const(type, ~(0xffffL << op.Bit))); + + res = context.BitwiseOr(res, Const(type, op.Immediate)); + + SetIntOrZR(context, op.Rd, res); + } + + public static void Movn(ArmEmitterContext context) + { + OpCodeMov op = (OpCodeMov)context.CurrOp; + + SetIntOrZR(context, op.Rd, Const(op.GetOperandType(), ~op.Immediate)); + } + + public static void Movz(ArmEmitterContext context) + { + OpCodeMov op = (OpCodeMov)context.CurrOp; + + SetIntOrZR(context, op.Rd, Const(op.GetOperandType(), op.Immediate)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMul.cs b/src/ARMeilleure/Instructions/InstEmitMul.cs new file mode 100644 index 00000000..89dc0993 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMul.cs @@ -0,0 +1,101 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics.CodeAnalysis; +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Madd(ArmEmitterContext context) => EmitMul(context, isAdd: true); + public static void Msub(ArmEmitterContext context) => EmitMul(context, isAdd: false); + + private static void EmitMul(ArmEmitterContext context, bool isAdd) + { + OpCodeMul op = (OpCodeMul)context.CurrOp; + + Operand a = GetIntOrZR(context, op.Ra); + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand res = context.Multiply(n, m); + + res = isAdd ? context.Add(a, res) : context.Subtract(a, res); + + SetIntOrZR(context, op.Rd, res); + } + + public static void Smaddl(ArmEmitterContext context) => EmitMull(context, MullFlags.SignedAdd); + public static void Smsubl(ArmEmitterContext context) => EmitMull(context, MullFlags.SignedSubtract); + public static void Umaddl(ArmEmitterContext context) => EmitMull(context, MullFlags.Add); + public static void Umsubl(ArmEmitterContext context) => EmitMull(context, MullFlags.Subtract); + + [Flags] + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + private enum MullFlags + { + Subtract = 0, + Add = 1 << 0, + Signed = 1 << 1, + + SignedAdd = Signed | Add, + SignedSubtract = Signed | Subtract, + } + + private static void EmitMull(ArmEmitterContext context, MullFlags flags) + { + OpCodeMul op = (OpCodeMul)context.CurrOp; + + Operand GetExtendedRegister32(int index) + { + Operand value = GetIntOrZR(context, index); + + if ((flags & MullFlags.Signed) != 0) + { + return context.SignExtend32(value.Type, value); + } + else + { + return context.ZeroExtend32(value.Type, value); + } + } + + Operand a = GetIntOrZR(context, op.Ra); + + Operand n = GetExtendedRegister32(op.Rn); + Operand m = GetExtendedRegister32(op.Rm); + + Operand res = context.Multiply(n, m); + + res = (flags & MullFlags.Add) != 0 ? context.Add(a, res) : context.Subtract(a, res); + + SetIntOrZR(context, op.Rd, res); + } + + public static void Smulh(ArmEmitterContext context) + { + OpCodeMul op = (OpCodeMul)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand d = context.Multiply64HighSI(n, m); + + SetIntOrZR(context, op.Rd, d); + } + + public static void Umulh(ArmEmitterContext context) + { + OpCodeMul op = (OpCodeMul)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand d = context.Multiply64HighUI(n, m); + + SetIntOrZR(context, op.Rd, d); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMul32.cs b/src/ARMeilleure/Instructions/InstEmitMul32.cs new file mode 100644 index 00000000..b9966ad1 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMul32.cs @@ -0,0 +1,378 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using static ARMeilleure.Instructions.InstEmitAluHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + [Flags] + private enum MullFlags + { + Subtract = 1, + Add = 1 << 1, + Signed = 1 << 2, + + SignedAdd = Signed | Add, + SignedSubtract = Signed | Subtract, + } + + public static void Mla(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + Operand a = GetIntA32(context, op.Ra); + + Operand res = context.Add(a, context.Multiply(n, m)); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Mls(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + Operand a = GetIntA32(context, op.Ra); + + Operand res = context.Subtract(a, context.Multiply(n, m)); + + EmitAluStore(context, res); + } + + public static void Smmla(ArmEmitterContext context) + { + EmitSmmul(context, MullFlags.SignedAdd); + } + + public static void Smmls(ArmEmitterContext context) + { + EmitSmmul(context, MullFlags.SignedSubtract); + } + + public static void Smmul(ArmEmitterContext context) + { + EmitSmmul(context, MullFlags.Signed); + } + + private static void EmitSmmul(ArmEmitterContext context, MullFlags flags) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = context.SignExtend32(OperandType.I64, GetIntA32(context, op.Rn)); + Operand m = context.SignExtend32(OperandType.I64, GetIntA32(context, op.Rm)); + + Operand res = context.Multiply(n, m); + + if (flags.HasFlag(MullFlags.Add) && op.Ra != 0xf) + { + res = context.Add(context.ShiftLeft(context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Ra)), Const(32)), res); + } + else if (flags.HasFlag(MullFlags.Subtract)) + { + res = context.Subtract(context.ShiftLeft(context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Ra)), Const(32)), res); + } + + if (op.R) + { + res = context.Add(res, Const(0x80000000L)); + } + + Operand hi = context.ConvertI64ToI32(context.ShiftRightSI(res, Const(32))); + + EmitGenericAluStoreA32(context, op.Rd, false, hi); + } + + public static void Smla__(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + Operand a = GetIntA32(context, op.Ra); + + if (op.NHigh) + { + n = context.SignExtend16(OperandType.I64, context.ShiftRightUI(n, Const(16))); + } + else + { + n = context.SignExtend16(OperandType.I64, n); + } + + if (op.MHigh) + { + m = context.SignExtend16(OperandType.I64, context.ShiftRightUI(m, Const(16))); + } + else + { + m = context.SignExtend16(OperandType.I64, m); + } + + Operand res = context.Multiply(n, m); + + Operand toAdd = context.SignExtend32(OperandType.I64, a); + res = context.Add(res, toAdd); + Operand q = context.ICompareNotEqual(res, context.SignExtend32(OperandType.I64, res)); + res = context.ConvertI64ToI32(res); + + UpdateQFlag(context, q); + + EmitGenericAluStoreA32(context, op.Rd, false, res); + } + + public static void Smlal(ArmEmitterContext context) + { + EmitMlal(context, true); + } + + public static void Smlal__(ArmEmitterContext context) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + if (op.NHigh) + { + n = context.SignExtend16(OperandType.I64, context.ShiftRightUI(n, Const(16))); + } + else + { + n = context.SignExtend16(OperandType.I64, n); + } + + if (op.MHigh) + { + m = context.SignExtend16(OperandType.I64, context.ShiftRightUI(m, Const(16))); + } + else + { + m = context.SignExtend16(OperandType.I64, m); + } + + Operand res = context.Multiply(n, m); + + Operand toAdd = context.ShiftLeft(context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdHi)), Const(32)); + toAdd = context.BitwiseOr(toAdd, context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdLo))); + res = context.Add(res, toAdd); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + EmitGenericAluStoreA32(context, op.RdHi, false, hi); + EmitGenericAluStoreA32(context, op.RdLo, false, lo); + } + + public static void Smlaw_(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + Operand a = GetIntA32(context, op.Ra); + + if (op.MHigh) + { + m = context.SignExtend16(OperandType.I64, context.ShiftRightUI(m, Const(16))); + } + else + { + m = context.SignExtend16(OperandType.I64, m); + } + + Operand res = context.Multiply(context.SignExtend32(OperandType.I64, n), m); + + Operand toAdd = context.ShiftLeft(context.SignExtend32(OperandType.I64, a), Const(16)); + res = context.Add(res, toAdd); + res = context.ShiftRightSI(res, Const(16)); + Operand q = context.ICompareNotEqual(res, context.SignExtend32(OperandType.I64, res)); + res = context.ConvertI64ToI32(res); + + UpdateQFlag(context, q); + + EmitGenericAluStoreA32(context, op.Rd, false, res); + } + + public static void Smul__(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + if (op.NHigh) + { + n = context.ShiftRightSI(n, Const(16)); + } + else + { + n = context.SignExtend16(OperandType.I32, n); + } + + if (op.MHigh) + { + m = context.ShiftRightSI(m, Const(16)); + } + else + { + m = context.SignExtend16(OperandType.I32, m); + } + + Operand res = context.Multiply(n, m); + + EmitGenericAluStoreA32(context, op.Rd, false, res); + } + + public static void Smull(ArmEmitterContext context) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = context.SignExtend32(OperandType.I64, GetIntA32(context, op.Rn)); + Operand m = context.SignExtend32(OperandType.I64, GetIntA32(context, op.Rm)); + + Operand res = context.Multiply(n, m); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitGenericAluStoreA32(context, op.RdHi, ShouldSetFlags(context), hi); + EmitGenericAluStoreA32(context, op.RdLo, ShouldSetFlags(context), lo); + } + + public static void Smulw_(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + if (op.MHigh) + { + m = context.SignExtend16(OperandType.I64, context.ShiftRightUI(m, Const(16))); + } + else + { + m = context.SignExtend16(OperandType.I64, m); + } + + Operand res = context.Multiply(context.SignExtend32(OperandType.I64, n), m); + + res = context.ShiftRightUI(res, Const(16)); + res = context.ConvertI64ToI32(res); + + EmitGenericAluStoreA32(context, op.Rd, false, res); + } + + public static void Umaal(ArmEmitterContext context) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rn)); + Operand m = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rm)); + Operand dHi = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdHi)); + Operand dLo = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdLo)); + + Operand res = context.Multiply(n, m); + res = context.Add(res, dHi); + res = context.Add(res, dLo); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + EmitGenericAluStoreA32(context, op.RdHi, false, hi); + EmitGenericAluStoreA32(context, op.RdLo, false, lo); + } + + public static void Umlal(ArmEmitterContext context) + { + EmitMlal(context, false); + } + + public static void Umull(ArmEmitterContext context) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rn)); + Operand m = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rm)); + + Operand res = context.Multiply(n, m); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitGenericAluStoreA32(context, op.RdHi, ShouldSetFlags(context), hi); + EmitGenericAluStoreA32(context, op.RdLo, ShouldSetFlags(context), lo); + } + + private static void EmitMlal(ArmEmitterContext context, bool signed) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + if (signed) + { + n = context.SignExtend32(OperandType.I64, n); + m = context.SignExtend32(OperandType.I64, m); + } + else + { + n = context.ZeroExtend32(OperandType.I64, n); + m = context.ZeroExtend32(OperandType.I64, m); + } + + Operand res = context.Multiply(n, m); + + Operand toAdd = context.ShiftLeft(context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdHi)), Const(32)); + toAdd = context.BitwiseOr(toAdd, context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdLo))); + res = context.Add(res, toAdd); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitGenericAluStoreA32(context, op.RdHi, ShouldSetFlags(context), hi); + EmitGenericAluStoreA32(context, op.RdLo, ShouldSetFlags(context), lo); + } + + private static void UpdateQFlag(ArmEmitterContext context, Operand q) + { + Operand lblSkipSetQ = Label(); + + context.BranchIfFalse(lblSkipSetQ, q); + + SetFlag(context, PState.QFlag, Const(1)); + + context.MarkLabel(lblSkipSetQ); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs new file mode 100644 index 00000000..13d9fac6 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs @@ -0,0 +1,5284 @@ +// https://github.com/intel/ARM_NEON_2_x86_SSE/blob/master/NEON_2_SSE.h +// https://www.agner.org/optimize/#vectorclass @ vectori128.h + +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper32; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func2I = Func; + + static partial class InstEmit + { + public static void Abs_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOp(context, Intrinsic.Arm64AbsS); + } + else + { + EmitScalarUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + } + + public static void Abs_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64AbsV); + } + else + { + EmitVectorUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + } + + public static void Add_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOp(context, Intrinsic.Arm64AddS); + } + else + { + EmitScalarBinaryOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Add_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64AddV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + Operand res = context.AddIntrinsic(addInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Addhn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64AddhnV); + } + else + { + EmitHighNarrow(context, (op1, op2) => context.Add(op1, op2), round: false); + } + } + + public static void Addp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOp(context, Intrinsic.Arm64AddpS); + } + else + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand ne0 = EmitVectorExtractZx(context, op.Rn, 0, op.Size); + Operand ne1 = EmitVectorExtractZx(context, op.Rn, 1, op.Size); + + Operand res = context.Add(ne0, ne1); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), res, 0, op.Size)); + } + } + + public static void Addp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64AddpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PaddInstruction); + } + else + { + EmitVectorPairwiseOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Addv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64AddvV); + } + else + { + EmitVectorAcrossVectorOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Cls_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64ClsV); + } + else + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + int eSize = 8 << op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + Operand de = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.CountLeadingSigns)), ne, Const(eSize)); + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Clz_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64ClzV); + } + else + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int eSize = 8 << op.Size; + + Operand res = eSize switch + { + 8 => Clz_V_I8(context, GetVec(op.Rn)), + 16 => Clz_V_I16(context, GetVec(op.Rn)), + 32 => Clz_V_I32(context, GetVec(op.Rn)), + _ => default, + }; + + if (res != default) + { + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + } + else + { + int elems = op.GetBytesCount() >> op.Size; + + res = context.VectorZero(); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + Operand de = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.CountLeadingZeros)), ne, Const(eSize)); + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static Operand Clz_V_I8(ArmEmitterContext context, Operand arg) + { + if (!Optimizations.UseSsse3) + { + return default; + } + + // CLZ nibble table. + Operand clzTable = X86GetScalar(context, 0x01_01_01_01_02_02_03_04); + + Operand maskLow = X86GetAllElements(context, 0x0f_0f_0f_0f); + Operand c04 = X86GetAllElements(context, 0x04_04_04_04); + + // CLZ of low 4 bits of elements in arg. + Operand loClz = context.AddIntrinsic(Intrinsic.X86Pshufb, clzTable, arg); + + // Get the high 4 bits of elements in arg. + Operand hiArg = context.AddIntrinsic(Intrinsic.X86Psrlw, arg, Const(4)); + hiArg = context.AddIntrinsic(Intrinsic.X86Pand, hiArg, maskLow); + + // CLZ of high 4 bits of elements in arg. + Operand hiClz = context.AddIntrinsic(Intrinsic.X86Pshufb, clzTable, hiArg); + + // If high 4 bits are not all zero, we discard the CLZ of the low 4 bits. + Operand mask = context.AddIntrinsic(Intrinsic.X86Pcmpeqb, hiClz, c04); + loClz = context.AddIntrinsic(Intrinsic.X86Pand, loClz, mask); + + return context.AddIntrinsic(Intrinsic.X86Paddb, loClz, hiClz); + } + + private static Operand Clz_V_I16(ArmEmitterContext context, Operand arg) + { + if (!Optimizations.UseSsse3) + { + return default; + } + + Operand maskSwap = X86GetElements(context, 0x80_0f_80_0d_80_0b_80_09, 0x80_07_80_05_80_03_80_01); + Operand maskLow = X86GetAllElements(context, 0x00ff_00ff); + Operand c0008 = X86GetAllElements(context, 0x0008_0008); + + // CLZ pair of high 8 and low 8 bits of elements in arg. + Operand hiloClz = Clz_V_I8(context, arg); + // Get CLZ of low 8 bits in each pair. + Operand loClz = context.AddIntrinsic(Intrinsic.X86Pand, hiloClz, maskLow); + // Get CLZ of high 8 bits in each pair. + Operand hiClz = context.AddIntrinsic(Intrinsic.X86Pshufb, hiloClz, maskSwap); + + // If high 8 bits are not all zero, we discard the CLZ of the low 8 bits. + Operand mask = context.AddIntrinsic(Intrinsic.X86Pcmpeqw, hiClz, c0008); + loClz = context.AddIntrinsic(Intrinsic.X86Pand, loClz, mask); + + return context.AddIntrinsic(Intrinsic.X86Paddw, loClz, hiClz); + } + + private static Operand Clz_V_I32(ArmEmitterContext context, Operand arg) + { + // TODO: Use vplzcntd when AVX-512 is supported. + if (!Optimizations.UseSse2) + { + return default; + } + +#pragma warning disable IDE0055 // Disable formatting + Operand AddVectorI32(Operand op0, Operand op1) => context.AddIntrinsic(Intrinsic.X86Paddd, op0, op1); + Operand SubVectorI32(Operand op0, Operand op1) => context.AddIntrinsic(Intrinsic.X86Psubd, op0, op1); + Operand ShiftRightVectorUI32(Operand op0, int imm8) => context.AddIntrinsic(Intrinsic.X86Psrld, op0, Const(imm8)); + Operand OrVector(Operand op0, Operand op1) => context.AddIntrinsic(Intrinsic.X86Por, op0, op1); + Operand AndVector(Operand op0, Operand op1) => context.AddIntrinsic(Intrinsic.X86Pand, op0, op1); + Operand NotVector(Operand op0) => context.AddIntrinsic(Intrinsic.X86Pandn, op0, context.VectorOne()); +#pragma warning restore IDE0055 + + Operand c55555555 = X86GetAllElements(context, 0x55555555); + Operand c33333333 = X86GetAllElements(context, 0x33333333); + Operand c0f0f0f0f = X86GetAllElements(context, 0x0f0f0f0f); + Operand c0000003f = X86GetAllElements(context, 0x0000003f); + + Operand tmp0; + Operand tmp1; + Operand res; + + // Set all bits after highest set bit to 1. + res = OrVector(ShiftRightVectorUI32(arg, 1), arg); + res = OrVector(ShiftRightVectorUI32(res, 2), res); + res = OrVector(ShiftRightVectorUI32(res, 4), res); + res = OrVector(ShiftRightVectorUI32(res, 8), res); + res = OrVector(ShiftRightVectorUI32(res, 16), res); + + // Make leading 0s into leading 1s. + res = NotVector(res); + + // Count leading 1s, which is the population count. + tmp0 = ShiftRightVectorUI32(res, 1); + tmp0 = AndVector(tmp0, c55555555); + res = SubVectorI32(res, tmp0); + + tmp0 = ShiftRightVectorUI32(res, 2); + tmp0 = AndVector(tmp0, c33333333); + tmp1 = AndVector(res, c33333333); + res = AddVectorI32(tmp0, tmp1); + + tmp0 = ShiftRightVectorUI32(res, 4); + tmp0 = AddVectorI32(tmp0, res); + res = AndVector(tmp0, c0f0f0f0f); + + tmp0 = ShiftRightVectorUI32(res, 8); + res = AddVectorI32(tmp0, res); + + tmp0 = ShiftRightVectorUI32(res, 16); + res = AddVectorI32(tmp0, res); + + res = AndVector(res, c0000003f); + + return res; + } + + public static void Cnt_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64CntV); + } + else + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.RegisterSize == RegisterSize.Simd128 ? 16 : 8; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, 0); + + Operand de; + + if (Optimizations.UsePopCnt) + { + de = context.AddIntrinsicLong(Intrinsic.X86Popcnt, ne); + } + else + { + de = EmitCountSetBits8(context, ne); + } + + res = EmitVectorInsert(context, res, de, index, 0); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Fabd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FabdS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Subss, GetVec(op.Rn), GetVec(op.Rm)); + + res = EmitFloatAbs(context, res, true, false); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + Operand res = context.AddIntrinsic(Intrinsic.X86Subsd, GetVec(op.Rn), GetVec(op.Rm)); + + res = EmitFloatAbs(context, res, false, false); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2); + + return EmitUnaryMathCall(context, nameof(Math.Abs), res); + }); + } + } + + public static void Fabd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FabdV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Subps, GetVec(op.Rn), GetVec(op.Rm)); + + res = EmitFloatAbs(context, res, true, true); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand res = context.AddIntrinsic(Intrinsic.X86Subpd, GetVec(op.Rn), GetVec(op.Rm)); + + res = EmitFloatAbs(context, res, false, true); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2); + + return EmitUnaryMathCall(context, nameof(Math.Abs), res); + }); + } + } + + public static void Fabs_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FabsS); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (op.Size == 0) + { + Operand res = EmitFloatAbs(context, GetVec(op.Rn), true, false); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + Operand res = EmitFloatAbs(context, GetVec(op.Rn), false, false); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Abs), op1); + }); + } + } + + public static void Fabs_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FabsV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand res = EmitFloatAbs(context, GetVec(op.Rn), true, true); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand res = EmitFloatAbs(context, GetVec(op.Rn), false, true); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Abs), op1); + }); + } + } + + public static void Fadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF(context, Intrinsic.X86Addss, Intrinsic.X86Addsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Add(op1, op2)); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2); + }); + } + } + + public static void Fadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FaddV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF(context, Intrinsic.X86Addps, Intrinsic.X86Addpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF(context, (op1, op2) => context.Add(op1, op2)); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2); + }); + } + } + + public static void Faddp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FaddpS); + } + else if (Optimizations.FastFP && Optimizations.UseSse3) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if ((op.Size & 1) == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Haddps, GetVec(op.Rn), GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if ((op.Size & 1) == 1) */ + { + Operand res = context.AddIntrinsic(Intrinsic.X86Haddpd, GetVec(op.Rn), GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2); + }); + } + } + + public static void Faddp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FaddpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + Intrinsic addInst = (op.Size & 1) == 0 ? Intrinsic.X86Addps : Intrinsic.X86Addpd; + + return context.AddIntrinsic(addInst, op1, op2); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2); + }); + } + } + + public static void Fdiv_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FdivS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF(context, Intrinsic.X86Divss, Intrinsic.X86Divsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Divide(op1, op2)); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPDiv), op1, op2); + }); + } + } + + public static void Fdiv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FdivV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF(context, Intrinsic.X86Divps, Intrinsic.X86Divpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF(context, (op1, op2) => context.Divide(op1, op2)); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPDiv), op1, op2); + }); + } + } + + public static void Fmadd_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpF(context, Intrinsic.Arm64FmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand a = GetVec(op.Ra); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + 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) */ + { + 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)); + } + } + else + { + EmitScalarTernaryRaOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulAdd), op1, op2, op3); + }); + } + } + + public static void Fmax_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FmaxS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true); + }, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMax), op1, op2); + }); + } + } + + public static void Fmax_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmaxV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true); + }, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMax), op1, op2); + }); + } + } + + public static void Fmaxnm_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FmaxnmS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxnm_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmaxnmV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxnmp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FmaxnmpS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2ScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: true, op1, op2); + }); + } + else + { + EmitScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxnmp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmaxnmpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxnmv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FmaxnmvV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: false, op1, op2); + }); + } + else + { + EmitVectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FmaxpS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2ScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true); + }, scalar: true, op1, op2); + }); + } + else + { + EmitScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMax), op1, op2); + }); + } + } + + public static void Fmaxp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmaxpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMax), op1, op2); + }); + } + } + + public static void Fmaxv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FmaxvV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMax), op1, op2); + }); + } + } + + public static void Fmin_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FminS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false); + }, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMin), op1, op2); + }); + } + } + + public static void Fmin_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FminV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false); + }, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMin), op1, op2); + }); + } + } + + public static void Fminnm_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FminnmS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminnm_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FminnmV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminnmp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FminnmpS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2ScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: true, op1, op2); + }); + } + else + { + EmitScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminnmp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FminnmpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminnmv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FminnmvV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: false, op1, op2); + }); + } + else + { + EmitVectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FminpS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2ScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false); + }, scalar: true, op1, op2); + }); + } + else + { + EmitScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMin), op1, op2); + }); + } + } + + public static void Fminp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FminpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMin), op1, op2); + }); + } + } + + public static void Fminv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FminvV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMin), op1, op2); + }); + } + } + + public static void Fmla_Se(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + 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) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Fmla_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpFRd(context, Intrinsic.Arm64FmlaV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + 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) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else /* if (sizeF == 1) */ + { + 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); + } + } + else + { + EmitVectorTernaryOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulAdd), op1, op2, op3); + }); + } + } + + public static void Fmla_Ve(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpFRdByElem(context, Intrinsic.Arm64FmlaVe); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + 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)); + + 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) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else /* if (sizeF == 1) */ + { + int shuffleMask = op.Index | op.Index << 1; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask)); + + 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); + } + } + else + { + EmitVectorTernaryOpByElemF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulAdd), op1, op2, op3); + }); + } + } + + public static void Fmls_Se(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + 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) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Fmls_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpFRd(context, Intrinsic.Arm64FmlsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + 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) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else /* if (sizeF == 1) */ + { + 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); + } + } + else + { + EmitVectorTernaryOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulSub), op1, op2, op3); + }); + } + } + + public static void Fmls_Ve(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpFRdByElem(context, Intrinsic.Arm64FmlsVe); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + 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)); + + 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) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else /* if (sizeF == 1) */ + { + int shuffleMask = op.Index | op.Index << 1; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask)); + + 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); + } + } + else + { + EmitVectorTernaryOpByElemF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulSub), op1, op2, op3); + }); + } + } + + public static void Fmsub_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpF(context, Intrinsic.Arm64FmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand a = GetVec(op.Ra); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + 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) */ + { + 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)); + } + } + else + { + EmitScalarTernaryRaOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulSub), op1, op2, op3); + }); + } + } + + public static void Fmul_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FmulS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op1, op2); + }); + } + } + + public static void Fmul_Se(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpFByElem(context, Intrinsic.Arm64FmulSe); + } + else + { + EmitScalarBinaryOpByElemF(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Fmul_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmulV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op1, op2); + }); + } + } + + public static void Fmul_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpFByElem(context, Intrinsic.Arm64FmulVe); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + 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.X86Mulps, n, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), 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.X86Mulpd, n, res); + + context.Copy(GetVec(op.Rd), res); + } + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpByElemF(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitVectorBinaryOpByElemF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op1, op2); + }); + } + } + + public static void Fmulx_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FmulxS); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulX), op1, op2); + }); + } + } + + public static void Fmulx_Se(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpFByElem(context, Intrinsic.Arm64FmulxSe); + } + else + { + EmitScalarBinaryOpByElemF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulX), op1, op2); + }); + } + } + + public static void Fmulx_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmulxV); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulX), op1, op2); + }); + } + } + + public static void Fmulx_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpFByElem(context, Intrinsic.Arm64FmulxVe); + } + else + { + EmitVectorBinaryOpByElemF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulX), op1, op2); + }); + } + } + + public static void Fneg_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FnegS); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (op.Size == 0) + { + Operand mask = X86GetScalar(context, -0f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Xorps, mask, GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + Operand mask = X86GetScalar(context, -0d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarUnaryOpF(context, (op1) => context.Negate(op1)); + } + } + + public static void Fneg_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FnegV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand mask = X86GetAllElements(context, -0f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Xorps, mask, GetVec(op.Rn)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand mask = X86GetAllElements(context, -0d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorUnaryOpF(context, (op1) => context.Negate(op1)); + } + } + + public static void Fnmadd_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpF(context, Intrinsic.Arm64FnmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand a = GetVec(op.Ra); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + 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); + + 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) */ + { + 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); + + res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subsd, aNeg, res); + } + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryRaOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPNegMulAdd), op1, op2, op3); + }); + } + } + + public static void Fnmsub_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpF(context, Intrinsic.Arm64FnmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand a = GetVec(op.Ra); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + 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); + + 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) */ + { + 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); + + res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addsd, aNeg, res); + } + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryRaOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPNegMulSub), op1, op2, op3); + }); + } + } + + public static void Fnmul_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FnmulS); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Negate(context.Multiply(op1, op2))); + } + } + + public static void Frecpe_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrecpeS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) + { + Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rcpss, GetVec(op.Rn)), scalar: true); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipEstimate), op1); + }); + } + } + + public static void Frecpe_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrecpeV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) + { + Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rcpps, GetVec(op.Rn)), scalar: false); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipEstimate), op1); + }); + } + } + + public static void Frecps_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FrecpsS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + Operand mask = X86GetScalar(context, 2f); + + 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 = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + Operand mask = X86GetScalar(context, 2d); + + 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 = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipStepFused), op1, op2); + }); + } + } + + public static void Frecps_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FrecpsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + Operand mask = X86GetAllElements(context, 2f); + + 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 = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand mask = X86GetAllElements(context, 2d); + + 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 = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipStepFused), op1, op2); + }); + } + } + + public static void Frecpx_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FrecpxS); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecpX), op1); + }); + } + } + + public static void Frinta_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintaS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.ToNearestAway); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1); + }); + } + } + + public static void Frinta_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintaV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.ToNearestAway); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1); + }); + } + } + + public static void Frinti_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintiS); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + + public static void Frinti_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintiV); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + + public static void Frintm_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintmS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.TowardsMinusInfinity); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Floor), op1); + }); + } + } + + public static void Frintm_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintmV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.TowardsMinusInfinity); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Floor), op1); + }); + } + } + + public static void Frintn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintnS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.ToNearest); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.ToEven, op1); + }); + } + } + + public static void Frintn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintnV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.ToNearest); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.ToEven, op1); + }); + } + } + + public static void Frintp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintpS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.TowardsPlusInfinity); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Ceiling), op1); + }); + } + } + + public static void Frintp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintpV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.TowardsPlusInfinity); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Ceiling), op1); + }); + } + } + + public static void Frintx_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintxS); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + + public static void Frintx_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintxV); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + + public static void Frintz_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintzS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.TowardsZero); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Truncate), op1); + }); + } + } + + public static void Frintz_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintzV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.TowardsZero); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Truncate), op1); + }); + } + } + + public static void Frsqrte_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrsqrteS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) + { + // RSQRTSS handles subnormals as zero, which differs from Arm, so we can't use it here. + + Operand res = context.AddIntrinsic(Intrinsic.X86Sqrtss, GetVec(op.Rn)); + res = context.AddIntrinsic(Intrinsic.X86Rcpss, res); + res = EmitSse41Round32Exp8OpF(context, res, scalar: true); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtEstimate), op1); + }); + } + } + + public static void Frsqrte_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrsqrteV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) + { + // RSQRTPS handles subnormals as zero, which differs from Arm, so we can't use it here. + + Operand res = context.AddIntrinsic(Intrinsic.X86Sqrtps, GetVec(op.Rn)); + res = context.AddIntrinsic(Intrinsic.X86Rcpps, res); + res = EmitSse41Round32Exp8OpF(context, res, scalar: false); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtEstimate), op1); + }); + } + } + + public static void Frsqrts_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FrsqrtsS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + 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); + + 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.X86Mulss, maskHalf, res); + res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + Operand maskHalf = X86GetScalar(context, 0.5d); + Operand maskThree = X86GetScalar(context, 3d); + Operand maskOneHalf = X86GetScalar(context, 1.5d); + + 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.X86Mulsd, maskHalf, res); + res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtStepFused), op1, op2); + }); + } + } + + public static void Frsqrts_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FrsqrtsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + 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); + + 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.X86Mulps, maskHalf, res); + res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand maskHalf = X86GetAllElements(context, 0.5d); + Operand maskThree = X86GetAllElements(context, 3d); + Operand maskOneHalf = X86GetAllElements(context, 1.5d); + + 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.X86Mulpd, maskHalf, res); + res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtStepFused), op1, op2); + }); + } + } + + public static void Fsqrt_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FsqrtS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarUnaryOpF(context, Intrinsic.X86Sqrtss, Intrinsic.X86Sqrtsd); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSqrt), op1); + }); + } + } + + public static void Fsqrt_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FsqrtV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorUnaryOpF(context, Intrinsic.X86Sqrtps, Intrinsic.X86Sqrtpd); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSqrt), op1); + }); + } + } + + public static void Fsub_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF(context, Intrinsic.X86Subss, Intrinsic.X86Subsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Subtract(op1, op2)); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2); + }); + } + } + + public static void Fsub_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FsubV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF(context, Intrinsic.X86Subps, Intrinsic.X86Subpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF(context, (op1, op2) => context.Subtract(op1, op2)); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2); + }); + } + } + + public static void Mla_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64MlaV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorMul_AddSub(context, AddSub.Add); + } + else + { + EmitVectorTernaryOpZx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Mla_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64MlaVe); + } + else + { + EmitVectorTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Mls_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64MlsV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorMul_AddSub(context, AddSub.Subtract); + } + else + { + EmitVectorTernaryOpZx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Mls_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64MlsVe); + } + else + { + EmitVectorTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Mul_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64MulV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorMul_AddSub(context, AddSub.None); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Mul_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpByElem(context, Intrinsic.Arm64MulVe); + } + else + { + EmitVectorBinaryOpByElemZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Neg_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOp(context, Intrinsic.Arm64NegS); + } + else + { + EmitScalarUnaryOpSx(context, (op1) => context.Negate(op1)); + } + } + + public static void Neg_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64NegV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + Operand res = context.AddIntrinsic(subInst, context.VectorZero(), GetVec(op.Rn)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorUnaryOpSx(context, (op1) => context.Negate(op1)); + } + } + + public static void Pmull_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseArm64Pmull) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64PmullV); + } + else if (Optimizations.UsePclmulqdq && op.Size == 3) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int imm8 = op.RegisterSize == RegisterSize.Simd64 ? 0b0000_0000 : 0b0001_0001; + + Operand res = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, n, m, Const(imm8)); + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseSse41) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd64) + { + n = context.VectorZeroUpper64(n); + m = context.VectorZeroUpper64(m); + } + else /* if (op.RegisterSize == RegisterSize.Simd128) */ + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Operand res = context.VectorZero(); + + if (op.Size == 0) + { + n = context.AddIntrinsic(Intrinsic.X86Pmovzxbw, n); + m = context.AddIntrinsic(Intrinsic.X86Pmovzxbw, m); + + for (int i = 0; i < 8; i++) + { + Operand mask = context.AddIntrinsic(Intrinsic.X86Psllw, n, Const(15 - i)); + mask = context.AddIntrinsic(Intrinsic.X86Psraw, mask, Const(15)); + + Operand tmp = context.AddIntrinsic(Intrinsic.X86Psllw, m, Const(i)); + tmp = context.AddIntrinsic(Intrinsic.X86Pand, tmp, mask); + + res = context.AddIntrinsic(Intrinsic.X86Pxor, res, tmp); + } + } + else /* if (op.Size == 3) */ + { + Operand zero = context.VectorZero(); + + for (int i = 0; i < 64; i++) + { + Operand mask = context.AddIntrinsic(Intrinsic.X86Movlhps, n, n); + mask = context.AddIntrinsic(Intrinsic.X86Psllq, mask, Const(63 - i)); + mask = context.AddIntrinsic(Intrinsic.X86Psrlq, mask, Const(63)); + mask = context.AddIntrinsic(Intrinsic.X86Psubq, zero, mask); + + Operand tmp = EmitSse2Sll_128(context, m, i); + tmp = context.AddIntrinsic(Intrinsic.X86Pand, tmp, mask); + + res = context.AddIntrinsic(Intrinsic.X86Pxor, res, tmp); + } + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + res = context.VectorZero(); + + int part = op.RegisterSize == RegisterSize.Simd64 ? 0 : 8; + + for (int index = 0; index < 8; index++) + { + Operand ne = context.VectorExtract8(n, part + index); + Operand me = context.VectorExtract8(m, part + index); + + Operand de = EmitPolynomialMultiply(context, ne, me, 8); + + res = EmitVectorInsert(context, res, de, index, 1); + } + } + else /* if (op.Size == 3) */ + { + int part = op.RegisterSize == RegisterSize.Simd64 ? 0 : 1; + + Operand ne = context.VectorExtract(OperandType.I64, n, part); + Operand me = context.VectorExtract(OperandType.I64, m, part); + + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.PolynomialMult64_128)), ne, me); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Raddhn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64RaddhnV); + } + else + { + EmitHighNarrow(context, (op1, op2) => context.Add(op1, op2), round: true); + } + } + + public static void Rsubhn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64RsubhnV); + } + else + { + EmitHighNarrow(context, (op1, op2) => context.Subtract(op1, op2), round: true); + } + } + + public static void Saba_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SabaV); + } + else + { + EmitVectorTernaryOpSx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + } + + public static void Sabal_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SabalV); + } + else + { + EmitVectorWidenRnRmTernaryOpSx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + } + + public static void Sabd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SabdV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + EmitSse41VectorSabdOp(context, op, n, m, isLong: false); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => + { + return EmitAbs(context, context.Subtract(op1, op2)); + }); + } + } + + public static void Sabdl_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SabdlV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = op.Size == 0 + ? Intrinsic.X86Pmovsxbw + : Intrinsic.X86Pmovsxwd; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + EmitSse41VectorSabdOp(context, op, n, m, isLong: true); + } + else + { + EmitVectorWidenRnRmBinaryOpSx(context, (op1, op2) => + { + return EmitAbs(context, context.Subtract(op1, op2)); + }); + } + } + + public static void Sadalp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpRd(context, Intrinsic.Arm64SadalpV); + } + else + { + EmitAddLongPairwise(context, signed: true, accumulate: true); + } + } + + public static void Saddl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SaddlV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(addInst, n, m)); + } + else + { + EmitVectorWidenRnRmBinaryOpSx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Saddlp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64SaddlpV); + } + else + { + EmitAddLongPairwise(context, signed: true, accumulate: false); + } + } + + public static void Saddlv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64SaddlvV); + } + else + { + EmitVectorLongAcrossVectorOpSx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Saddw_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SaddwV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + m = context.AddIntrinsic(movInst, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(addInst, n, m)); + } + else + { + EmitVectorWidenRmBinaryOpSx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Shadd_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64ShaddV); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pand, n, m); + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pxor, n, m); + + Intrinsic shiftInst = op.Size == 1 ? Intrinsic.X86Psraw : Intrinsic.X86Psrad; + + res2 = context.AddIntrinsic(shiftInst, res2, Const(1)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, res2); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => + { + return context.ShiftRightSI(context.Add(op1, op2), Const(1)); + }); + } + } + + public static void Shsub_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64ShsubV); + } + else if (Optimizations.UseSse2 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand mask = X86GetAllElements(context, (int)(op.Size == 0 ? 0x80808080u : 0x80008000u)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + Operand nPlusMask = context.AddIntrinsic(addInst, n, mask); + Operand mPlusMask = context.AddIntrinsic(addInst, m, mask); + + Intrinsic avgInst = op.Size == 0 ? Intrinsic.X86Pavgb : Intrinsic.X86Pavgw; + + Operand res = context.AddIntrinsic(avgInst, nPlusMask, mPlusMask); + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + res = context.AddIntrinsic(subInst, nPlusMask, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => + { + return context.ShiftRightSI(context.Subtract(op1, op2), Const(1)); + }); + } + } + + public static void Smax_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SmaxV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic maxInst = X86PmaxsInstruction[op.Size]; + + Operand res = context.AddIntrinsic(maxInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: true)); + } + } + + public static void Smaxp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SmaxpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PmaxsInstruction); + } + else + { + EmitVectorPairwiseOpSx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: true)); + } + } + + public static void Smaxv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64SmaxvV); + } + else + { + EmitVectorAcrossVectorOpSx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: true)); + } + } + + public static void Smin_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SminV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic minInst = X86PminsInstruction[op.Size]; + + Operand res = context.AddIntrinsic(minInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: true)); + } + } + + public static void Sminp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SminpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PminsInstruction); + } + else + { + EmitVectorPairwiseOpSx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: true)); + } + } + + public static void Sminv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64SminvV); + } + else + { + EmitVectorAcrossVectorOpSx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: true)); + } + } + + public static void Smlal_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SmlalV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic mullInst = op.Size == 0 ? Intrinsic.X86Pmullw : Intrinsic.X86Pmulld; + + Operand res = context.AddIntrinsic(mullInst, n, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(d, context.AddIntrinsic(addInst, d, res)); + } + else + { + EmitVectorWidenRnRmTernaryOpSx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Smlal_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64SmlalVe); + } + else + { + EmitVectorWidenTernaryOpByElemSx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Smlsl_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SmlslV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = op.Size == 0 ? Intrinsic.X86Pmovsxbw : Intrinsic.X86Pmovsxwd; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic mullInst = op.Size == 0 ? Intrinsic.X86Pmullw : Intrinsic.X86Pmulld; + + Operand res = context.AddIntrinsic(mullInst, n, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(d, context.AddIntrinsic(subInst, d, res)); + } + else + { + EmitVectorWidenRnRmTernaryOpSx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Smlsl_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64SmlslVe); + } + else + { + EmitVectorWidenTernaryOpByElemSx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Smull_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SmullV); + } + else + { + EmitVectorWidenRnRmBinaryOpSx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Smull_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpByElem(context, Intrinsic.Arm64SmullVe); + } + else + { + EmitVectorWidenBinaryOpByElemSx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Sqabs_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingUnaryOp(context, Intrinsic.Arm64SqabsS); + } + else + { + EmitScalarSaturatingUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + } + + public static void Sqabs_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingUnaryOp(context, Intrinsic.Arm64SqabsV); + } + else + { + EmitVectorSaturatingUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + } + + public static void Sqadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64SqaddS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, flags: SaturatingFlags.Add); + } + } + + public static void Sqadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqaddV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, flags: SaturatingFlags.Add); + } + } + + public static void Sqdmulh_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64SqdmulhS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: false)); + } + } + + public static void Sqdmulh_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqdmulhV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: false)); + } + } + + public static void Sqdmulh_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpByElem(context, Intrinsic.Arm64SqdmulhVe); + } + else + { + EmitVectorSaturatingBinaryOpByElemSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: false)); + } + } + + public static void Sqneg_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingUnaryOp(context, Intrinsic.Arm64SqnegS); + } + else + { + EmitScalarSaturatingUnaryOpSx(context, (op1) => context.Negate(op1)); + } + } + + public static void Sqneg_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingUnaryOp(context, Intrinsic.Arm64SqnegV); + } + else + { + EmitVectorSaturatingUnaryOpSx(context, (op1) => context.Negate(op1)); + } + } + + public static void Sqrdmulh_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64SqrdmulhS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: true)); + } + } + + public static void Sqrdmulh_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqrdmulhV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: true)); + } + } + + public static void Sqrdmulh_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpByElem(context, Intrinsic.Arm64SqrdmulhVe); + } + else + { + EmitVectorSaturatingBinaryOpByElemSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: true)); + } + } + + public static void Sqsub_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64SqsubS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, flags: SaturatingFlags.Sub); + } + } + + public static void Sqsub_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqsubV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, flags: SaturatingFlags.Sub); + } + } + + public static void Sqxtn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64SqxtnS); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.ScalarSxSx); + } + } + + public static void Sqxtn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64SqxtnV); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.VectorSxSx); + } + } + + public static void Sqxtun_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64SqxtunS); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.ScalarSxZx); + } + } + + public static void Sqxtun_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64SqxtunV); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.VectorSxZx); + } + } + + public static void Srhadd_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SrhaddV); + } + else if (Optimizations.UseSse2 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand mask = X86GetAllElements(context, (int)(op.Size == 0 ? 0x80808080u : 0x80008000u)); + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + Operand nMinusMask = context.AddIntrinsic(subInst, n, mask); + Operand mMinusMask = context.AddIntrinsic(subInst, m, mask); + + Intrinsic avgInst = op.Size == 0 ? Intrinsic.X86Pavgb : Intrinsic.X86Pavgw; + + Operand res = context.AddIntrinsic(avgInst, nMinusMask, mMinusMask); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, mask, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => + { + Operand res = context.Add(op1, op2); + + res = context.Add(res, Const(1L)); + + return context.ShiftRightSI(res, Const(1)); + }); + } + } + + public static void Ssubl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SsublV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(subInst, n, m)); + } + else + { + EmitVectorWidenRnRmBinaryOpSx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Ssubw_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SsubwV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + m = context.AddIntrinsic(movInst, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(subInst, n, m)); + } + else + { + EmitVectorWidenRmBinaryOpSx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Sub_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOp(context, Intrinsic.Arm64SubS); + } + else + { + EmitScalarBinaryOpZx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Sub_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SubV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + Operand res = context.AddIntrinsic(subInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Subhn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SubhnV); + } + else + { + EmitHighNarrow(context, (op1, op2) => context.Subtract(op1, op2), round: false); + } + } + + public static void Suqadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64SuqaddS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, flags: SaturatingFlags.Accumulate); + } + } + + public static void Suqadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64SuqaddV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, flags: SaturatingFlags.Accumulate); + } + } + + public static void Uaba_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64UabaV); + } + else + { + EmitVectorTernaryOpZx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + } + + public static void Uabal_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64UabalV); + } + else + { + EmitVectorWidenRnRmTernaryOpZx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + } + + public static void Uabd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UabdV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + EmitSse41VectorUabdOp(context, op, n, m, isLong: false); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + return EmitAbs(context, context.Subtract(op1, op2)); + }); + } + } + + public static void Uabdl_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UabdlV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = op.Size == 0 + ? Intrinsic.X86Pmovzxbw + : Intrinsic.X86Pmovzxwd; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + EmitSse41VectorUabdOp(context, op, n, m, isLong: true); + } + else + { + EmitVectorWidenRnRmBinaryOpZx(context, (op1, op2) => + { + return EmitAbs(context, context.Subtract(op1, op2)); + }); + } + } + + public static void Uadalp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpRd(context, Intrinsic.Arm64UadalpV); + } + else + { + EmitAddLongPairwise(context, signed: false, accumulate: true); + } + } + + public static void Uaddl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UaddlV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(addInst, n, m)); + } + else + { + EmitVectorWidenRnRmBinaryOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Uaddlp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64UaddlpV); + } + else + { + EmitAddLongPairwise(context, signed: false, accumulate: false); + } + } + + public static void Uaddlv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64UaddlvV); + } + else + { + EmitVectorLongAcrossVectorOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Uaddw_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UaddwV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + m = context.AddIntrinsic(movInst, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(addInst, n, m)); + } + else + { + EmitVectorWidenRmBinaryOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Uhadd_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UhaddV); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pand, n, m); + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pxor, n, m); + + Intrinsic shiftInst = op.Size == 1 ? Intrinsic.X86Psrlw : Intrinsic.X86Psrld; + + res2 = context.AddIntrinsic(shiftInst, res2, Const(1)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, res2); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + return context.ShiftRightUI(context.Add(op1, op2), Const(1)); + }); + } + } + + public static void Uhsub_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UhsubV); + } + else if (Optimizations.UseSse2 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic avgInst = op.Size == 0 ? Intrinsic.X86Pavgb : Intrinsic.X86Pavgw; + + Operand res = context.AddIntrinsic(avgInst, n, m); + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + res = context.AddIntrinsic(subInst, n, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + return context.ShiftRightUI(context.Subtract(op1, op2), Const(1)); + }); + } + } + + public static void Umax_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UmaxV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic maxInst = X86PmaxuInstruction[op.Size]; + + Operand res = context.AddIntrinsic(maxInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: false)); + } + } + + public static void Umaxp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UmaxpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PmaxuInstruction); + } + else + { + EmitVectorPairwiseOpZx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: false)); + } + } + + public static void Umaxv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64UmaxvV); + } + else + { + EmitVectorAcrossVectorOpZx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: false)); + } + } + + public static void Umin_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UminV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic minInst = X86PminuInstruction[op.Size]; + + Operand res = context.AddIntrinsic(minInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: false)); + } + } + + public static void Uminp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UminpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PminuInstruction); + } + else + { + EmitVectorPairwiseOpZx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: false)); + } + } + + public static void Uminv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64UminvV); + } + else + { + EmitVectorAcrossVectorOpZx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: false)); + } + } + + public static void Umlal_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64UmlalV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic mullInst = op.Size == 0 ? Intrinsic.X86Pmullw : Intrinsic.X86Pmulld; + + Operand res = context.AddIntrinsic(mullInst, n, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(d, context.AddIntrinsic(addInst, d, res)); + } + else + { + EmitVectorWidenRnRmTernaryOpZx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Umlal_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64UmlalVe); + } + else + { + EmitVectorWidenTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Umlsl_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64UmlslV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = op.Size == 0 ? Intrinsic.X86Pmovzxbw : Intrinsic.X86Pmovzxwd; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic mullInst = op.Size == 0 ? Intrinsic.X86Pmullw : Intrinsic.X86Pmulld; + + Operand res = context.AddIntrinsic(mullInst, n, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(d, context.AddIntrinsic(subInst, d, res)); + } + else + { + EmitVectorWidenRnRmTernaryOpZx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Umlsl_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64UmlslVe); + } + else + { + EmitVectorWidenTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Umull_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UmullV); + } + else + { + EmitVectorWidenRnRmBinaryOpZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Umull_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpByElem(context, Intrinsic.Arm64UmullVe); + } + else + { + EmitVectorWidenBinaryOpByElemZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Uqadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64UqaddS); + } + else + { + EmitScalarSaturatingBinaryOpZx(context, SaturatingFlags.Add); + } + } + + public static void Uqadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64UqaddV); + } + else + { + EmitVectorSaturatingBinaryOpZx(context, SaturatingFlags.Add); + } + } + + public static void Uqsub_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64UqsubS); + } + else + { + EmitScalarSaturatingBinaryOpZx(context, SaturatingFlags.Sub); + } + } + + public static void Uqsub_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64UqsubV); + } + else + { + EmitVectorSaturatingBinaryOpZx(context, SaturatingFlags.Sub); + } + } + + public static void Uqxtn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64UqxtnS); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.ScalarZxZx); + } + } + + public static void Uqxtn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64UqxtnV); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.VectorZxZx); + } + } + + public static void Urhadd_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UrhaddV); + } + else if (Optimizations.UseSse2 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic avgInst = op.Size == 0 ? Intrinsic.X86Pavgb : Intrinsic.X86Pavgw; + + Operand res = context.AddIntrinsic(avgInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + Operand res = context.Add(op1, op2); + + res = context.Add(res, Const(1L)); + + return context.ShiftRightUI(res, Const(1)); + }); + } + } + + public static void Usqadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64UsqaddS); + } + else + { + EmitScalarSaturatingBinaryOpZx(context, SaturatingFlags.Accumulate); + } + } + + public static void Usqadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64UsqaddV); + } + else + { + EmitVectorSaturatingBinaryOpZx(context, SaturatingFlags.Accumulate); + } + } + + public static void Usubl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UsublV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(subInst, n, m)); + } + else + { + EmitVectorWidenRnRmBinaryOpZx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Usubw_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UsubwV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + m = context.AddIntrinsic(movInst, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(subInst, n, m)); + } + else + { + EmitVectorWidenRmBinaryOpZx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + private static Operand EmitAbs(ArmEmitterContext context, Operand value) + { + Operand isPositive = context.ICompareGreaterOrEqual(value, Const(value.Type, 0)); + + return context.ConditionalSelect(isPositive, value, context.Negate(value)); + } + + private static void EmitAddLongPairwise(ArmEmitterContext context, bool signed, bool accumulate) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand ne0 = EmitVectorExtract(context, op.Rn, pairIndex, op.Size, signed); + Operand ne1 = EmitVectorExtract(context, op.Rn, pairIndex + 1, op.Size, signed); + + Operand e = context.Add(ne0, ne1); + + if (accumulate) + { + Operand de = EmitVectorExtract(context, op.Rd, index, op.Size + 1, signed); + + e = context.Add(e, de); + } + + res = EmitVectorInsert(context, res, e, index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static Operand EmitDoublingMultiplyHighHalf( + ArmEmitterContext context, + Operand n, + Operand m, + bool round) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int eSize = 8 << op.Size; + + Operand res = context.Multiply(n, m); + + if (!round) + { + res = context.ShiftRightSI(res, Const(eSize - 1)); + } + else + { + long roundConst = 1L << (eSize - 1); + + res = context.ShiftLeft(res, Const(1)); + + res = context.Add(res, Const(roundConst)); + + res = context.ShiftRightSI(res, Const(eSize)); + + Operand isIntMin = context.ICompareEqual(res, Const((long)int.MinValue)); + + res = context.ConditionalSelect(isIntMin, context.Negate(res), res); + } + + return res; + } + + private static void EmitHighNarrow(ArmEmitterContext context, Func2I emit, bool round) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int elems = 8 >> op.Size; + int eSize = 8 << op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + long roundConst = 1L << (eSize - 1); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size + 1); + Operand me = EmitVectorExtractZx(context, op.Rm, index, op.Size + 1); + + Operand de = emit(ne, me); + + if (round) + { + de = context.Add(de, Const(roundConst)); + } + + de = context.ShiftRightUI(de, Const(eSize)); + + res = EmitVectorInsert(context, res, de, part + index, op.Size); + } + + context.Copy(d, res); + } + + private static Operand EmitMax64Op(ArmEmitterContext context, Operand op1, Operand op2, bool signed) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand cmp = signed + ? context.ICompareGreaterOrEqual(op1, op2) + : context.ICompareGreaterOrEqualUI(op1, op2); + + return context.ConditionalSelect(cmp, op1, op2); + } + + private static Operand EmitMin64Op(ArmEmitterContext context, Operand op1, Operand op2, bool signed) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand cmp = signed + ? context.ICompareLessOrEqual(op1, op2) + : context.ICompareLessOrEqualUI(op1, op2); + + return context.ConditionalSelect(cmp, op1, op2); + } + + private static void EmitSse41ScalarRoundOpF(ArmEmitterContext context, FPRoundingMode roundMode) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res; + + if (roundMode != FPRoundingMode.ToNearestAway) + { + Intrinsic inst = (op.Size & 1) != 0 ? Intrinsic.X86Roundsd : Intrinsic.X86Roundss; + + res = context.AddIntrinsic(inst, n, Const(X86GetRoundControl(roundMode))); + } + else + { + res = EmitSse41RoundToNearestWithTiesToAwayOpF(context, n, scalar: true); + } + + if ((op.Size & 1) != 0) + { + res = context.VectorZeroUpper64(res); + } + else + { + res = context.VectorZeroUpper96(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitSse41VectorRoundOpF(ArmEmitterContext context, FPRoundingMode roundMode) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res; + + if (roundMode != FPRoundingMode.ToNearestAway) + { + Intrinsic inst = (op.Size & 1) != 0 ? Intrinsic.X86Roundpd : Intrinsic.X86Roundps; + + res = context.AddIntrinsic(inst, n, Const(X86GetRoundControl(roundMode))); + } + else + { + res = EmitSse41RoundToNearestWithTiesToAwayOpF(context, n, scalar: false); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static Operand EmitSse41Round32Exp8OpF(ArmEmitterContext context, Operand value, bool scalar) + { + Operand roundMask; + Operand truncMask; + Operand expMask; + + if (scalar) + { + roundMask = X86GetScalar(context, 0x4000); + truncMask = X86GetScalar(context, unchecked((int)0xFFFF8000)); + expMask = X86GetScalar(context, 0x7F800000); + } + else + { + roundMask = X86GetAllElements(context, 0x4000); + truncMask = X86GetAllElements(context, unchecked((int)0xFFFF8000)); + expMask = X86GetAllElements(context, 0x7F800000); + } + + Operand oValue = value; + Operand masked = context.AddIntrinsic(Intrinsic.X86Pand, value, expMask); + Operand isNaNInf = context.AddIntrinsic(Intrinsic.X86Pcmpeqd, masked, expMask); + + value = context.AddIntrinsic(Intrinsic.X86Paddd, value, roundMask); + value = context.AddIntrinsic(Intrinsic.X86Pand, value, truncMask); + + return context.AddIntrinsic(Intrinsic.X86Blendvps, value, oValue, isNaNInf); + } + + private static Operand EmitSse41RecipStepSelectOpF( + ArmEmitterContext context, + Operand n, + Operand m, + Operand res, + Operand mask, + bool scalar, + int sizeF) + { + Intrinsic cmpOp; + Intrinsic shlOp; + Intrinsic blendOp; + Operand zero = context.VectorZero(); + Operand expMask; + + if (sizeF == 0) + { + cmpOp = Intrinsic.X86Pcmpeqd; + shlOp = Intrinsic.X86Pslld; + blendOp = Intrinsic.X86Blendvps; + expMask = scalar ? X86GetScalar(context, 0x7F800000 << 1) : X86GetAllElements(context, 0x7F800000 << 1); + } + else /* if (sizeF == 1) */ + { + cmpOp = Intrinsic.X86Pcmpeqq; + shlOp = Intrinsic.X86Psllq; + blendOp = Intrinsic.X86Blendvpd; + expMask = scalar ? X86GetScalar(context, 0x7FF0000000000000L << 1) : X86GetAllElements(context, 0x7FF0000000000000L << 1); + } + + n = context.AddIntrinsic(shlOp, n, Const(1)); + m = context.AddIntrinsic(shlOp, m, Const(1)); + + Operand nZero = context.AddIntrinsic(cmpOp, n, zero); + Operand mZero = context.AddIntrinsic(cmpOp, m, zero); + Operand nInf = context.AddIntrinsic(cmpOp, n, expMask); + Operand mInf = context.AddIntrinsic(cmpOp, m, expMask); + + Operand nmZero = context.AddIntrinsic(Intrinsic.X86Por, nZero, mZero); + Operand nmInf = context.AddIntrinsic(Intrinsic.X86Por, nInf, mInf); + Operand nmZeroInf = context.AddIntrinsic(Intrinsic.X86Pand, nmZero, nmInf); + + return context.AddIntrinsic(blendOp, res, mask, nmZeroInf); + } + + public static void EmitSse2VectorIsNaNOpF( + ArmEmitterContext context, + Operand opF, + out Operand qNaNMask, + out Operand sNaNMask, + bool? isQNaN = null) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + if ((op.Size & 1) == 0) + { + const int QBit = 22; + + Operand qMask = X86GetAllElements(context, 1 << QBit); + + Operand mask1 = context.AddIntrinsic(Intrinsic.X86Cmpps, opF, opF, Const((int)CmpCondition.UnorderedQ)); + + Operand mask2 = context.AddIntrinsic(Intrinsic.X86Pand, opF, qMask); + mask2 = context.AddIntrinsic(Intrinsic.X86Cmpps, mask2, qMask, Const((int)CmpCondition.Equal)); + + qNaNMask = isQNaN == null || (bool)isQNaN ? context.AddIntrinsic(Intrinsic.X86Andps, mask2, mask1) : default; + sNaNMask = isQNaN == null || !(bool)isQNaN ? context.AddIntrinsic(Intrinsic.X86Andnps, mask2, mask1) : default; + } + else /* if ((op.Size & 1) == 1) */ + { + const int QBit = 51; + + Operand qMask = X86GetAllElements(context, 1L << QBit); + + Operand mask1 = context.AddIntrinsic(Intrinsic.X86Cmppd, opF, opF, Const((int)CmpCondition.UnorderedQ)); + + Operand mask2 = context.AddIntrinsic(Intrinsic.X86Pand, opF, qMask); + mask2 = context.AddIntrinsic(Intrinsic.X86Cmppd, mask2, qMask, Const((int)CmpCondition.Equal)); + + qNaNMask = isQNaN == null || (bool)isQNaN ? context.AddIntrinsic(Intrinsic.X86Andpd, mask2, mask1) : default; + sNaNMask = isQNaN == null || !(bool)isQNaN ? context.AddIntrinsic(Intrinsic.X86Andnpd, mask2, mask1) : default; + } + } + + public static Operand EmitSse41ProcessNaNsOpF( + 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; + + EmitSse2VectorIsNaNOpF(context, nCopy, out Operand nQNaNMask, out Operand nSNaNMask); + EmitSse2VectorIsNaNOpF(context, mCopy, out _, out Operand mSNaNMask, isQNaN: false); + + int sizeF = ((IOpCodeSimd)context.CurrOp).Size & 1; + + if (sizeF == 0) + { + const int QBit = 22; + + Operand qMask = scalar ? X86GetScalar(context, 1 << QBit) : X86GetAllElements(context, 1 << QBit); + + Operand resNaNMask = context.AddIntrinsic(Intrinsic.X86Pandn, mSNaNMask, nQNaNMask); + resNaNMask = context.AddIntrinsic(Intrinsic.X86Por, resNaNMask, nSNaNMask); + + Operand resNaN = context.AddIntrinsic(Intrinsic.X86Blendvps, mCopy, nCopy, resNaNMask); + resNaN = context.AddIntrinsic(Intrinsic.X86Por, resNaN, qMask); + + Operand resMask = context.AddIntrinsic(Intrinsic.X86Cmpps, nCopy, mCopy, Const((int)CmpCondition.OrderedQ)); + + Operand res = context.AddIntrinsic(Intrinsic.X86Blendvps, resNaN, emit(nCopy, mCopy), resMask); + + if (n != default || m != default) + { + return res; + } + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (((OpCodeSimdReg)context.CurrOp).RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res); + + return default; + } + else /* if (sizeF == 1) */ + { + const int QBit = 51; + + Operand qMask = scalar ? X86GetScalar(context, 1L << QBit) : X86GetAllElements(context, 1L << QBit); + + Operand resNaNMask = context.AddIntrinsic(Intrinsic.X86Pandn, mSNaNMask, nQNaNMask); + resNaNMask = context.AddIntrinsic(Intrinsic.X86Por, resNaNMask, nSNaNMask); + + Operand resNaN = context.AddIntrinsic(Intrinsic.X86Blendvpd, mCopy, nCopy, resNaNMask); + resNaN = context.AddIntrinsic(Intrinsic.X86Por, resNaN, qMask); + + Operand resMask = context.AddIntrinsic(Intrinsic.X86Cmppd, nCopy, mCopy, Const((int)CmpCondition.OrderedQ)); + + Operand res = context.AddIntrinsic(Intrinsic.X86Blendvpd, resNaN, emit(nCopy, mCopy), resMask); + + if (n != default || m != default) + { + return res; + } + + 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; + + if ((op.Size & 1) == 0) + { + Operand mask = X86GetAllElements(context, -0f); + + Operand res = context.AddIntrinsic(isMax ? Intrinsic.X86Maxps : Intrinsic.X86Minps, n, m); + res = context.AddIntrinsic(Intrinsic.X86Andnps, mask, res); + + Operand resSign = context.AddIntrinsic(isMax ? Intrinsic.X86Pand : Intrinsic.X86Por, n, m); + resSign = context.AddIntrinsic(Intrinsic.X86Andps, mask, resSign); + + return context.AddIntrinsic(Intrinsic.X86Por, res, resSign); + } + else /* if ((op.Size & 1) == 1) */ + { + Operand mask = X86GetAllElements(context, -0d); + + Operand res = context.AddIntrinsic(isMax ? Intrinsic.X86Maxpd : Intrinsic.X86Minpd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Andnpd, mask, res); + + Operand resSign = context.AddIntrinsic(isMax ? Intrinsic.X86Pand : Intrinsic.X86Por, n, m); + resSign = context.AddIntrinsic(Intrinsic.X86Andpd, mask, resSign); + + return context.AddIntrinsic(Intrinsic.X86Por, res, resSign); + } + } + + private static Operand EmitSse41MaxMinNumOpF( + ArmEmitterContext context, + bool isMaxNum, + 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; + + EmitSse2VectorIsNaNOpF(context, nCopy, out Operand nQNaNMask, out _, isQNaN: true); + EmitSse2VectorIsNaNOpF(context, mCopy, out Operand mQNaNMask, out _, isQNaN: true); + + int sizeF = ((IOpCodeSimd)context.CurrOp).Size & 1; + + if (sizeF == 0) + { + Operand negInfMask = scalar + ? X86GetScalar(context, isMaxNum ? float.NegativeInfinity : float.PositiveInfinity) + : X86GetAllElements(context, isMaxNum ? float.NegativeInfinity : float.PositiveInfinity); + + Operand nMask = context.AddIntrinsic(Intrinsic.X86Andnps, mQNaNMask, nQNaNMask); + Operand mMask = context.AddIntrinsic(Intrinsic.X86Andnps, nQNaNMask, mQNaNMask); + + nCopy = context.AddIntrinsic(Intrinsic.X86Blendvps, nCopy, negInfMask, nMask); + mCopy = context.AddIntrinsic(Intrinsic.X86Blendvps, mCopy, negInfMask, mMask); + + Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum); + }, scalar: scalar, nCopy, mCopy); + + if (n != default || m != default) + { + return res; + } + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (((OpCodeSimdReg)context.CurrOp).RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res); + + return default; + } + else /* if (sizeF == 1) */ + { + Operand negInfMask = scalar + ? X86GetScalar(context, isMaxNum ? double.NegativeInfinity : double.PositiveInfinity) + : X86GetAllElements(context, isMaxNum ? double.NegativeInfinity : double.PositiveInfinity); + + Operand nMask = context.AddIntrinsic(Intrinsic.X86Andnpd, mQNaNMask, nQNaNMask); + Operand mMask = context.AddIntrinsic(Intrinsic.X86Andnpd, nQNaNMask, mQNaNMask); + + nCopy = context.AddIntrinsic(Intrinsic.X86Blendvpd, nCopy, negInfMask, nMask); + mCopy = context.AddIntrinsic(Intrinsic.X86Blendvpd, mCopy, negInfMask, mMask); + + Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum); + }, scalar: scalar, nCopy, mCopy); + + if (n != default || m != default) + { + return res; + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res); + + return default; + } + } + + private enum AddSub + { + None, + Add, + Subtract, + } + + private static void EmitSse41VectorMul_AddSub(ArmEmitterContext context, AddSub addSub) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + Operand ns8 = context.AddIntrinsic(Intrinsic.X86Psrlw, n, Const(8)); + Operand ms8 = context.AddIntrinsic(Intrinsic.X86Psrlw, m, Const(8)); + + res = context.AddIntrinsic(Intrinsic.X86Pmullw, ns8, ms8); + + res = context.AddIntrinsic(Intrinsic.X86Psllw, res, Const(8)); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pmullw, n, m); + + Operand mask = X86GetAllElements(context, 0x00FF00FF); + + res = context.AddIntrinsic(Intrinsic.X86Pblendvb, res, res2, mask); + } + else if (op.Size == 1) + { + res = context.AddIntrinsic(Intrinsic.X86Pmullw, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Pmulld, n, m); + } + + Operand d = GetVec(op.Rd); + + if (addSub == AddSub.Add) + { + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, d, res); + } + else if (addSub == AddSub.Subtract) + { + Intrinsic subInst = X86PsubInstruction[op.Size]; + + res = context.AddIntrinsic(subInst, d, res); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + + private static void EmitSse41VectorSabdOp( + ArmEmitterContext context, + OpCodeSimdReg op, + Operand n, + Operand m, + bool isLong) + { + int size = isLong ? op.Size + 1 : op.Size; + + Intrinsic cmpgtInst = X86PcmpgtInstruction[size]; + + Operand cmpMask = context.AddIntrinsic(cmpgtInst, n, m); + + Intrinsic subInst = X86PsubInstruction[size]; + + Operand res = context.AddIntrinsic(subInst, n, m); + + res = context.AddIntrinsic(Intrinsic.X86Pand, cmpMask, res); + + Operand res2 = context.AddIntrinsic(subInst, m, n); + + res2 = context.AddIntrinsic(Intrinsic.X86Pandn, cmpMask, res2); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, res2); + + if (!isLong && op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitSse41VectorUabdOp( + ArmEmitterContext context, + OpCodeSimdReg op, + Operand n, + Operand m, + bool isLong) + { + int size = isLong ? op.Size + 1 : op.Size; + + Intrinsic maxInst = X86PmaxuInstruction[size]; + + Operand max = context.AddIntrinsic(maxInst, m, n); + + Intrinsic cmpeqInst = X86PcmpeqInstruction[size]; + + Operand cmpMask = context.AddIntrinsic(cmpeqInst, max, m); + + Operand onesMask = X86GetAllElements(context, -1L); + + cmpMask = context.AddIntrinsic(Intrinsic.X86Pandn, cmpMask, onesMask); + + Intrinsic subInst = X86PsubInstruction[size]; + + Operand res = context.AddIntrinsic(subInst, n, m); + Operand res2 = context.AddIntrinsic(subInst, m, n); + + res = context.AddIntrinsic(Intrinsic.X86Pand, cmpMask, res); + res2 = context.AddIntrinsic(Intrinsic.X86Pandn, cmpMask, res2); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, res2); + + if (!isLong && op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static Operand EmitSse2Sll_128(ArmEmitterContext context, Operand op, int shift) + { + // The upper part of op is assumed to be zero. + Debug.Assert(shift >= 0 && shift < 64); + + if (shift == 0) + { + return op; + } + + Operand high = context.AddIntrinsic(Intrinsic.X86Pslldq, op, Const(8)); + high = context.AddIntrinsic(Intrinsic.X86Psrlq, high, Const(64 - shift)); + + Operand low = context.AddIntrinsic(Intrinsic.X86Psllq, op, Const(shift)); + + return context.AddIntrinsic(Intrinsic.X86Por, high, low); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs new file mode 100644 index 00000000..c807fc85 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs @@ -0,0 +1,1736 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using static ARMeilleure.Instructions.InstEmitFlowHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper32; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Vabd_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorBinaryOpI32(context, (op1, op2) => EmitAbs(context, context.Subtract(op1, op2)), !op.U); + } + + public static void Vabdl_I(ArmEmitterContext context) + { + OpCode32SimdRegLong op = (OpCode32SimdRegLong)context.CurrOp; + + EmitVectorBinaryLongOpI32(context, (op1, op2) => EmitAbs(context, context.Subtract(op1, op2)), !op.U); + } + + public static void Vabs_S(ArmEmitterContext context) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FabsS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarUnaryOpSimd32(context, (m) => + { + return EmitFloatAbs(context, m, (op.Size & 1) == 0, false); + }); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Abs), op1)); + } + } + + public static void Vabs_V(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FabsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + return EmitFloatAbs(context, m, (op.Size & 1) == 0, true); + }); + } + else + { + EmitVectorUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Abs), op1)); + } + } + else + { + EmitVectorUnaryOpSx32(context, (op1) => EmitAbs(context, op1)); + } + } + + private static Operand EmitAbs(ArmEmitterContext context, Operand value) + { + Operand isPositive = context.ICompareGreaterOrEqual(value, Const(value.Type, 0)); + + return context.ConditionalSelect(isPositive, value, context.Negate(value)); + } + + public static void Vadd_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF32(context, Intrinsic.X86Addss, Intrinsic.X86Addsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Add(op1, op2)); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2)); + } + } + + public static void Vadd_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FaddV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Addps, Intrinsic.X86Addpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF32(context, (op1, op2) => context.Add(op1, op2)); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPAddFpscr), op1, op2)); + } + } + + public static void Vadd_I(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PaddInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Vaddl_I(ArmEmitterContext context) + { + OpCode32SimdRegLong op = (OpCode32SimdRegLong)context.CurrOp; + + EmitVectorBinaryLongOpI32(context, (op1, op2) => context.Add(op1, op2), !op.U); + } + + public static void Vaddw_I(ArmEmitterContext context) + { + OpCode32SimdRegWide op = (OpCode32SimdRegWide)context.CurrOp; + + EmitVectorBinaryWideOpI32(context, (op1, op2) => context.Add(op1, op2), !op.U); + } + + public static void Vcnt(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount(); + + for (int index = 0; index < elems; index++) + { + Operand de; + Operand me = EmitVectorExtractZx32(context, op.Qm, op.Im + index, op.Size); + + if (Optimizations.UsePopCnt) + { + de = context.AddIntrinsicInt(Intrinsic.X86Popcnt, me); + } + else + { + de = EmitCountSetBits8(context, me); + } + + res = EmitVectorInsert(context, res, de, op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Vdup(ArmEmitterContext context) + { + OpCode32SimdDupGP op = (OpCode32SimdDupGP)context.CurrOp; + + Operand insert = GetIntA32(context, op.Rt); + + // Zero extend into an I64, then replicate. Saves the most time over elementwise inserts. + insert = op.Size switch + { + 2 => context.Multiply(context.ZeroExtend32(OperandType.I64, insert), Const(0x0000000100000001u)), + 1 => context.Multiply(context.ZeroExtend16(OperandType.I64, insert), Const(0x0001000100010001u)), + 0 => context.Multiply(context.ZeroExtend8(OperandType.I64, insert), Const(0x0101010101010101u)), + _ => throw new InvalidOperationException($"Invalid Vdup size \"{op.Size}\"."), + }; + + InsertScalar(context, op.Vd, insert); + if (op.Q) + { + InsertScalar(context, op.Vd + 1, insert); + } + } + + public static void Vdup_1(ArmEmitterContext context) + { + OpCode32SimdDupElem op = (OpCode32SimdDupElem)context.CurrOp; + + Operand insert = EmitVectorExtractZx32(context, op.Vm >> 1, ((op.Vm & 1) << (3 - op.Size)) + op.Index, op.Size); + + // Zero extend into an I64, then replicate. Saves the most time over elementwise inserts. + insert = op.Size switch + { + 2 => context.Multiply(context.ZeroExtend32(OperandType.I64, insert), Const(0x0000000100000001u)), + 1 => context.Multiply(context.ZeroExtend16(OperandType.I64, insert), Const(0x0001000100010001u)), + 0 => context.Multiply(context.ZeroExtend8(OperandType.I64, insert), Const(0x0101010101010101u)), + _ => throw new InvalidOperationException($"Invalid Vdup size \"{op.Size}\"."), + }; + + InsertScalar(context, op.Vd, insert); + if (op.Q) + { + InsertScalar(context, op.Vd | 1, insert); + } + } + + private static (long, long) MaskHelperByteSequence(int start, int length, int startByte) + { + int end = start + length; + int b = startByte; + long result = 0; + long result2 = 0; + for (int i = 0; i < 8; i++) + { + result |= (long)((i >= end || i < start) ? 0x80 : b++) << (i * 8); + } + for (int i = 8; i < 16; i++) + { + result2 |= (long)((i >= end || i < start) ? 0x80 : b++) << ((i - 8) * 8); + } + return (result2, result); + } + + public static void Vext(ArmEmitterContext context) + { + OpCode32SimdExt op = (OpCode32SimdExt)context.CurrOp; + int elems = op.GetBytesCount(); + int byteOff = op.Immediate; + + if (Optimizations.UseSsse3) + { + EmitVectorBinaryOpSimd32(context, (n, m) => + { + // Writing low to high of d: start into n, overlap into m. + // Then rotate n down by , m up by (elems)-imm. + // Then OR them together for the result. + + (long nMaskHigh, long nMaskLow) = MaskHelperByteSequence(0, elems - byteOff, byteOff); + (long mMaskHigh, long mMaskLow) = MaskHelperByteSequence(elems - byteOff, byteOff, 0); + Operand nMask, mMask; + if (!op.Q) + { + // Do the same operation to the bytes in the top doubleword too, as our target could be in either. + nMaskHigh = nMaskLow + 0x0808080808080808L; + mMaskHigh = mMaskLow + 0x0808080808080808L; + } + nMask = X86GetElements(context, nMaskHigh, nMaskLow); + mMask = X86GetElements(context, mMaskHigh, mMaskLow); + Operand nPart = context.AddIntrinsic(Intrinsic.X86Pshufb, n, nMask); + Operand mPart = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mMask); + + return context.AddIntrinsic(Intrinsic.X86Por, nPart, mPart); + }); + } + else + { + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand extract; + + if (byteOff >= elems) + { + extract = EmitVectorExtractZx32(context, op.Qm, op.Im + (byteOff - elems), op.Size); + } + else + { + extract = EmitVectorExtractZx32(context, op.Qn, op.In + byteOff, op.Size); + } + byteOff++; + + res = EmitVectorInsert(context, res, extract, op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + } + + public static void Vfma_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Vfmadd231ss, Intrinsic.X86Vfmadd231sd); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Addss, Intrinsic.X86Addsd); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulAdd), op1, op2, op3); + }); + } + } + + public static void Vfma_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpF32(context, Intrinsic.Arm64FmlaV); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitVectorTernaryOpF32(context, Intrinsic.X86Vfmadd231ps); + } + else + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulAddFpscr), op1, op2, op3); + }); + } + } + + public static void Vfms_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Vfnmadd231ss, Intrinsic.X86Vfnmadd231sd); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Subss, Intrinsic.X86Subsd); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulSub), op1, op2, op3); + }); + } + } + + public static void Vfms_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpF32(context, Intrinsic.Arm64FmlsV); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitVectorTernaryOpF32(context, Intrinsic.X86Vfnmadd231ps); + } + else + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulSubFpscr), op1, op2, op3); + }); + } + } + + public static void Vfnma_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FnmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Vfnmsub231ss, Intrinsic.X86Vfnmsub231sd); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Subss, Intrinsic.X86Subsd, isNegD: true); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPNegMulAdd), op1, op2, op3); + }); + } + } + + public static void Vfnms_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FnmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Vfmsub231ss, Intrinsic.X86Vfmsub231sd); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Addss, Intrinsic.X86Addsd, isNegD: true); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPNegMulSub), op1, op2, op3); + }); + } + } + + public static void Vhadd(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.ShiftRightUI(context.Add(op1, op2), Const(1))); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => context.ShiftRightSI(context.Add(op1, op2), Const(1))); + } + } + + public static void Vmov_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarUnaryOpF32(context, 0, 0); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => op1); + } + } + + public static void Vmovn(ArmEmitterContext context) + { + EmitVectorUnaryNarrowOp32(context, (op1) => op1); + } + + public static void Vneg_S(ArmEmitterContext context) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FnegS); + } + else if (Optimizations.UseSse2) + { + EmitScalarUnaryOpSimd32(context, (m) => + { + if ((op.Size & 1) == 0) + { + Operand mask = X86GetScalar(context, -0f); + return context.AddIntrinsic(Intrinsic.X86Xorps, mask, m); + } + else + { + Operand mask = X86GetScalar(context, -0d); + return context.AddIntrinsic(Intrinsic.X86Xorpd, mask, m); + } + }); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => context.Negate(op1)); + } + } + + public static void Vnmul_S(ArmEmitterContext context) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FnmulS); + } + else if (Optimizations.UseSse2) + { + EmitScalarBinaryOpSimd32(context, (n, m) => + { + if ((op.Size & 1) == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + Operand mask = X86GetScalar(context, -0f); + return context.AddIntrinsic(Intrinsic.X86Xorps, mask, res); + } + else + { + Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + Operand mask = X86GetScalar(context, -0d); + return context.AddIntrinsic(Intrinsic.X86Xorpd, mask, res); + } + }); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Negate(context.Multiply(op1, op2))); + } + } + + public static void Vnmla_S(ArmEmitterContext context) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FnmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Subss, Intrinsic.X86Subsd, isNegD: true); + } + else if (Optimizations.FastFP) + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return context.Subtract(context.Negate(op1), context.Multiply(op2, op3)); + }); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op2, op3); + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), context.Negate(op1), res); + }); + } + } + + public static void Vnmls_S(ArmEmitterContext context) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FnmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Addss, Intrinsic.X86Addsd, isNegD: true); + } + else if (Optimizations.FastFP) + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return context.Add(context.Negate(op1), context.Multiply(op2, op3)); + }); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op2, op3); + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), context.Negate(op1), res); + }); + } + } + + public static void Vneg_V(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FnegV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + if ((op.Size & 1) == 0) + { + Operand mask = X86GetAllElements(context, -0f); + return context.AddIntrinsic(Intrinsic.X86Xorps, mask, m); + } + else + { + Operand mask = X86GetAllElements(context, -0d); + return context.AddIntrinsic(Intrinsic.X86Xorpd, mask, m); + } + }); + } + else + { + EmitVectorUnaryOpF32(context, (op1) => context.Negate(op1)); + } + } + else + { + EmitVectorUnaryOpSx32(context, (op1) => context.Negate(op1)); + } + } + + public static void Vdiv_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FdivS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF32(context, Intrinsic.X86Divss, Intrinsic.X86Divsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Divide(op1, op2)); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPDiv), op1, op2); + }); + } + } + + public static void Vmaxnm_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FmaxnmS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF32(context, true, true); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2)); + } + } + + public static void Vmaxnm_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FmaxnmV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF32(context, true, false); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMaxNumFpscr), op1, op2)); + } + } + + public static void Vminnm_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FminnmS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF32(context, false, true); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2)); + } + } + + public static void Vminnm_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FminnmV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF32(context, false, false); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMinNumFpscr), op1, op2)); + } + } + + public static void Vmax_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FmaxV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Maxps, Intrinsic.X86Maxpd); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMaxFpscr), op1, op2); + }); + } + } + + public static void Vmax_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) + { + if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PmaxuInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.ConditionalSelect(context.ICompareGreaterUI(op1, op2), op1, op2)); + } + } + else + { + if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PmaxsInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => context.ConditionalSelect(context.ICompareGreater(op1, op2), op1, op2)); + } + } + } + + public static void Vmin_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FminV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Minps, Intrinsic.X86Minpd); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMinFpscr), op1, op2); + }); + } + } + + public static void Vmin_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) + { + if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PminuInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.ConditionalSelect(context.ICompareLessUI(op1, op2), op1, op2)); + } + } + else + { + if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PminsInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => context.ConditionalSelect(context.ICompareLess(op1, op2), op1, op2)); + } + } + } + + public static void Vmla_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Addss, Intrinsic.X86Addsd); + } + else if (Optimizations.FastFP) + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op2, op3); + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, res); + }); + } + } + + public static void Vmla_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpF32(context, Intrinsic.Arm64FmlaV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorTernaryOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd, Intrinsic.X86Addps, Intrinsic.X86Addpd); + } + else if (Optimizations.FastFP) + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => context.Add(op1, context.Multiply(op2, op3))); + } + else + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulAddFpscr), op1, op2, op3); + }); + } + } + + public static void Vmla_I(ArmEmitterContext context) + { + EmitVectorTernaryOpZx32(context, (op1, op2, op3) => context.Add(op1, context.Multiply(op2, op3))); + } + + public static void Vmla_1(ArmEmitterContext context) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorsByScalarOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd, Intrinsic.X86Addps, Intrinsic.X86Addpd); + } + else if (Optimizations.FastFP) + { + EmitVectorsByScalarOpF32(context, (op1, op2, op3) => context.Add(op1, context.Multiply(op2, op3))); + } + else + { + EmitVectorsByScalarOpF32(context, (op1, op2, op3) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulAddFpscr), op1, op2, op3)); + } + } + else + { + EmitVectorsByScalarOpI32(context, (op1, op2, op3) => context.Add(op1, context.Multiply(op2, op3)), false); + } + } + + public static void Vmlal_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorTernaryLongOpI32(context, (d, n, m) => context.Add(d, context.Multiply(n, m)), !op.U); + } + + public static void Vmls_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FmlsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Subss, Intrinsic.X86Subsd); + } + else if (Optimizations.FastFP) + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op2, op3); + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, res); + }); + } + } + + public static void Vmls_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpF32(context, Intrinsic.Arm64FmlsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorTernaryOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd, Intrinsic.X86Subps, Intrinsic.X86Subpd); + } + else if (Optimizations.FastFP) + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => context.Subtract(op1, context.Multiply(op2, op3))); + } + else + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulSubFpscr), op1, op2, op3); + }); + } + } + + public static void Vmls_I(ArmEmitterContext context) + { + EmitVectorTernaryOpZx32(context, (op1, op2, op3) => context.Subtract(op1, context.Multiply(op2, op3))); + } + + public static void Vmls_1(ArmEmitterContext context) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorsByScalarOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd, Intrinsic.X86Subps, Intrinsic.X86Subpd); + } + else if (Optimizations.FastFP) + { + EmitVectorsByScalarOpF32(context, (op1, op2, op3) => context.Subtract(op1, context.Multiply(op2, op3))); + } + else + { + EmitVectorsByScalarOpF32(context, (op1, op2, op3) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulSubFpscr), op1, op2, op3)); + } + } + else + { + EmitVectorsByScalarOpI32(context, (op1, op2, op3) => context.Subtract(op1, context.Multiply(op2, op3)), false); + } + } + + public static void Vmlsl_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorTernaryLongOpI32(context, (opD, op1, op2) => context.Subtract(opD, context.Multiply(op1, op2)), !op.U); + } + + public static void Vmul_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FmulS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op1, op2); + }); + } + } + + public static void Vmul_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FmulV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF32(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulFpscr), op1, op2); + }); + } + } + + public static void Vmul_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) // This instruction is always signed, U indicates polynomial mode. + { + EmitVectorBinaryOpZx32(context, (op1, op2) => EmitPolynomialMultiply(context, op1, op2, 8 << op.Size)); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Vmul_1(ArmEmitterContext context) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorByScalarOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd); + } + else if (Optimizations.FastFP) + { + EmitVectorByScalarOpF32(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitVectorByScalarOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulFpscr), op1, op2)); + } + } + else + { + EmitVectorByScalarOpI32(context, (op1, op2) => context.Multiply(op1, op2), false); + } + } + + public static void Vmull_1(ArmEmitterContext context) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + EmitVectorByScalarLongOpI32(context, (op1, op2) => context.Multiply(op1, op2), !op.U); + } + + public static void Vmull_I(ArmEmitterContext context) + { + OpCode32SimdRegLong op = (OpCode32SimdRegLong)context.CurrOp; + + if (op.Polynomial) + { + if (op.Size == 0) // P8 + { + EmitVectorBinaryLongOpI32(context, (op1, op2) => EmitPolynomialMultiply(context, op1, op2, 8 << op.Size), false); + } + else /* if (op.Size == 2) // P64 */ + { + Operand ne = context.VectorExtract(OperandType.I64, GetVec(op.Qn), op.Vn & 1); + Operand me = context.VectorExtract(OperandType.I64, GetVec(op.Qm), op.Vm & 1); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.PolynomialMult64_128)), ne, me); + + context.Copy(GetVecA32(op.Qd), res); + } + } + else + { + EmitVectorBinaryLongOpI32(context, (op1, op2) => context.Multiply(op1, op2), !op.U); + } + } + + public static void Vpadd_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorPairwiseOpF32(context, Intrinsic.Arm64FaddpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2VectorPairwiseOpF32(context, Intrinsic.X86Addps); + } + else + { + EmitVectorPairwiseOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPAddFpscr), op1, op2)); + } + } + + public static void Vpadd_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp32(context, X86PaddInstruction); + } + else + { + EmitVectorPairwiseOpI32(context, (op1, op2) => context.Add(op1, op2), !op.U); + } + } + + public static void Vpadal(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + EmitVectorPairwiseTernaryLongOpI32(context, (op1, op2, op3) => context.Add(context.Add(op1, op2), op3), op.Opc != 1); + } + + public static void Vpaddl(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + EmitVectorPairwiseLongOpI32(context, (op1, op2) => context.Add(op1, op2), (op.Opc & 1) == 0); + } + + public static void Vpmax_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorPairwiseOpF32(context, Intrinsic.Arm64FmaxpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2VectorPairwiseOpF32(context, Intrinsic.X86Maxps); + } + else + { + EmitVectorPairwiseOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat64.FPMaxFpscr), op1, op2)); + } + } + + public static void Vpmax_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp32(context, op.U ? X86PmaxuInstruction : X86PmaxsInstruction); + } + else + { + EmitVectorPairwiseOpI32(context, (op1, op2) => + { + Operand greater = op.U ? context.ICompareGreaterUI(op1, op2) : context.ICompareGreater(op1, op2); + return context.ConditionalSelect(greater, op1, op2); + }, !op.U); + } + } + + public static void Vpmin_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorPairwiseOpF32(context, Intrinsic.Arm64FminpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2VectorPairwiseOpF32(context, Intrinsic.X86Minps); + } + else + { + EmitVectorPairwiseOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMinFpscr), op1, op2)); + } + } + + public static void Vpmin_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp32(context, op.U ? X86PminuInstruction : X86PminsInstruction); + } + else + { + EmitVectorPairwiseOpI32(context, (op1, op2) => + { + Operand greater = op.U ? context.ICompareLessUI(op1, op2) : context.ICompareLess(op1, op2); + return context.ConditionalSelect(greater, op1, op2); + }, !op.U); + } + } + + public static void Vqadd(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitSaturatingAddSubBinaryOp(context, add: true, !op.U); + } + + public static void Vqdmulh(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + int eSize = 8 << op.Size; + + EmitVectorBinaryOpI32(context, (op1, op2) => + { + if (op.Size == 2) + { + op1 = context.SignExtend32(OperandType.I64, op1); + op2 = context.SignExtend32(OperandType.I64, op2); + } + + Operand res = context.Multiply(op1, op2); + res = context.ShiftRightSI(res, Const(eSize - 1)); + res = EmitSatQ(context, res, eSize, signedSrc: true, signedDst: true); + + if (op.Size == 2) + { + res = context.ConvertI64ToI32(res); + } + + return res; + }, signed: true); + } + + public static void Vqmovn(ArmEmitterContext context) + { + OpCode32SimdMovn op = (OpCode32SimdMovn)context.CurrOp; + + bool signed = !op.Q; + + EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signed, signed), signed); + } + + public static void Vqmovun(ArmEmitterContext context) + { + OpCode32SimdMovn op = (OpCode32SimdMovn)context.CurrOp; + + EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true); + } + + public static void Vqrdmulh(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + int eSize = 8 << op.Size; + + EmitVectorBinaryOpI32(context, (op1, op2) => + { + if (op.Size == 2) + { + op1 = context.SignExtend32(OperandType.I64, op1); + op2 = context.SignExtend32(OperandType.I64, op2); + } + + Operand res = context.Multiply(op1, op2); + res = context.Add(res, Const(res.Type, 1L << (eSize - 2))); + res = context.ShiftRightSI(res, Const(eSize - 1)); + res = EmitSatQ(context, res, eSize, signedSrc: true, signedDst: true); + + if (op.Size == 2) + { + res = context.ConvertI64ToI32(res); + } + + return res; + }, signed: true); + } + + public static void Vqsub(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitSaturatingAddSubBinaryOp(context, add: false, !op.U); + } + + public static void Vrev(ArmEmitterContext context) + { + OpCode32SimdRev op = (OpCode32SimdRev)context.CurrOp; + + if (Optimizations.UseSsse3) + { + EmitVectorUnaryOpSimd32(context, (op1) => + { + Operand mask; + switch (op.Size) + { + case 3: + // Rev64 + switch (op.Opc) + { + case 0: + mask = X86GetElements(context, 0x08090a0b0c0d0e0fL, 0x0001020304050607L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + case 1: + mask = X86GetElements(context, 0x09080b0a0d0c0f0eL, 0x0100030205040706L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + case 2: + return context.AddIntrinsic(Intrinsic.X86Shufps, op1, op1, Const(1 | (0 << 2) | (3 << 4) | (2 << 6))); + } + break; + case 2: + // Rev32 + switch (op.Opc) + { + case 0: + mask = X86GetElements(context, 0x0c0d0e0f_08090a0bL, 0x04050607_00010203L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + case 1: + mask = X86GetElements(context, 0x0d0c0f0e_09080b0aL, 0x05040706_01000302L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + } + break; + case 1: + // Rev16 + mask = X86GetElements(context, 0x0e0f_0c0d_0a0b_0809L, 0x_0607_0405_0203_0001L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + } + + throw new InvalidOperationException("Invalid VREV Opcode + Size combo."); // Should be unreachable. + }); + } + else + { + EmitVectorUnaryOpZx32(context, (op1) => + { + switch (op.Opc) + { + case 0: + switch (op.Size) // Swap bytes. + { + case 1: + return InstEmitAluHelper.EmitReverseBytes16_32Op(context, op1); + case 2: + case 3: + return context.ByteSwap(op1); + } + break; + case 1: + switch (op.Size) + { + case 2: + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op1, Const(0xffff0000)), Const(16)), + context.ShiftLeft(context.BitwiseAnd(op1, Const(0x0000ffff)), Const(16))); + case 3: + return context.BitwiseOr( + context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op1, Const(0xffff000000000000ul)), Const(48)), + context.ShiftLeft(context.BitwiseAnd(op1, Const(0x000000000000fffful)), Const(48))), + context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op1, Const(0x0000ffff00000000ul)), Const(16)), + context.ShiftLeft(context.BitwiseAnd(op1, Const(0x00000000ffff0000ul)), Const(16)))); + } + break; + case 2: + // Swap upper and lower halves. + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op1, Const(0xffffffff00000000ul)), Const(32)), + context.ShiftLeft(context.BitwiseAnd(op1, Const(0x00000000fffffffful)), Const(32))); + } + + throw new InvalidOperationException("Invalid VREV Opcode + Size combo."); // Should be unreachable. + }); + } + } + + public static void Vrecpe(ArmEmitterContext context) + { + OpCode32SimdSqrte op = (OpCode32SimdSqrte)context.CurrOp; + + if (op.F) + { + int sizeF = op.Size & 1; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrecpeV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2 && sizeF == 0) + { + EmitVectorUnaryOpF32(context, Intrinsic.X86Rcpps, 0); + } + else + { + EmitVectorUnaryOpF32(context, (op1) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPRecipEstimateFpscr), op1); + }); + } + } + else + { + throw new NotImplementedException("Integer Vrecpe not currently implemented."); + } + } + + public static void Vrecps(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FrecpsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + bool single = (op.Size & 1) == 0; + + // (2 - (n*m)) + EmitVectorBinaryOpSimd32(context, (n, m) => + { + if (single) + { + Operand maskTwo = X86GetAllElements(context, 2f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); + + return context.AddIntrinsic(Intrinsic.X86Subps, maskTwo, res); + } + else + { + Operand maskTwo = X86GetAllElements(context, 2d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); + + return context.AddIntrinsic(Intrinsic.X86Subpd, maskTwo, res); + } + }); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipStep), op1, op2); + }); + } + } + + public static void Vrhadd(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorBinaryOpI32(context, (op1, op2) => + { + if (op.Size == 2) + { + op1 = context.ZeroExtend32(OperandType.I64, op1); + op2 = context.ZeroExtend32(OperandType.I64, op2); + } + + Operand res = context.Add(context.Add(op1, op2), Const(op1.Type, 1L)); + res = context.ShiftRightUI(res, Const(1)); + + if (op.Size == 2) + { + res = context.ConvertI64ToI32(res); + } + + return res; + }, !op.U); + } + + public static void Vrsqrte(ArmEmitterContext context) + { + OpCode32SimdSqrte op = (OpCode32SimdSqrte)context.CurrOp; + + if (op.F) + { + int sizeF = op.Size & 1; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrsqrteV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2 && sizeF == 0) + { + EmitVectorUnaryOpF32(context, Intrinsic.X86Rsqrtps, 0); + } + else + { + EmitVectorUnaryOpF32(context, (op1) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPRSqrtEstimateFpscr), op1); + }); + } + } + else + { + throw new NotImplementedException("Integer Vrsqrte not currently implemented."); + } + } + + public static void Vrsqrts(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FrsqrtsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + bool single = (op.Size & 1) == 0; + + // (3 - (n*m)) / 2 + EmitVectorBinaryOpSimd32(context, (n, m) => + { + if (single) + { + Operand maskHalf = X86GetAllElements(context, 0.5f); + Operand maskThree = X86GetAllElements(context, 3f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); + + res = context.AddIntrinsic(Intrinsic.X86Subps, maskThree, res); + return context.AddIntrinsic(Intrinsic.X86Mulps, maskHalf, res); + } + else + { + Operand maskHalf = X86GetAllElements(context, 0.5d); + Operand maskThree = X86GetAllElements(context, 3d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); + + res = context.AddIntrinsic(Intrinsic.X86Subpd, maskThree, res); + return context.AddIntrinsic(Intrinsic.X86Mulpd, maskHalf, res); + } + }); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtStep), op1, op2); + }); + } + } + + public static void Vsel(ArmEmitterContext context) + { + OpCode32SimdSel op = (OpCode32SimdSel)context.CurrOp; + + Operand condition = default; + + switch (op.Cc) + { + case OpCode32SimdSelMode.Eq: + condition = GetCondTrue(context, Condition.Eq); + break; + case OpCode32SimdSelMode.Ge: + condition = GetCondTrue(context, Condition.Ge); + break; + case OpCode32SimdSelMode.Gt: + condition = GetCondTrue(context, Condition.Gt); + break; + case OpCode32SimdSelMode.Vs: + condition = GetCondTrue(context, Condition.Vs); + break; + } + + EmitScalarBinaryOpI32(context, (op1, op2) => + { + return context.ConditionalSelect(condition, op1, op2); + }); + } + + public static void Vsqrt_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FsqrtS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarUnaryOpF32(context, Intrinsic.X86Sqrtss, Intrinsic.X86Sqrtsd); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSqrt), op1); + }); + } + } + + public static void Vsub_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF32(context, Intrinsic.X86Subss, Intrinsic.X86Subsd); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Vsub_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FsubV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Subps, Intrinsic.X86Subpd); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Vsub_I(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PsubInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Vsubl_I(ArmEmitterContext context) + { + OpCode32SimdRegLong op = (OpCode32SimdRegLong)context.CurrOp; + + EmitVectorBinaryLongOpI32(context, (op1, op2) => context.Subtract(op1, op2), !op.U); + } + + public static void Vsubw_I(ArmEmitterContext context) + { + OpCode32SimdRegWide op = (OpCode32SimdRegWide)context.CurrOp; + + EmitVectorBinaryWideOpI32(context, (op1, op2) => context.Subtract(op1, op2), !op.U); + } + + private static void EmitSaturatingAddSubBinaryOp(ArmEmitterContext context, bool add, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + EmitVectorBinaryOpI32(context, (ne, me) => + { + if (op.Size <= 2) + { + if (op.Size == 2) + { + ne = signed ? context.SignExtend32(OperandType.I64, ne) : context.ZeroExtend32(OperandType.I64, ne); + me = signed ? context.SignExtend32(OperandType.I64, me) : context.ZeroExtend32(OperandType.I64, me); + } + + Operand res = add ? context.Add(ne, me) : context.Subtract(ne, me); + + res = EmitSatQ(context, res, 8 << op.Size, signedSrc: true, signed); + + if (op.Size == 2) + { + res = context.ConvertI64ToI32(res); + } + + return res; + } + else if (add) /* if (op.Size == 3) */ + { + return signed + ? EmitBinarySignedSatQAdd(context, ne, me) + : EmitBinaryUnsignedSatQAdd(context, ne, me); + } + else /* if (sub) */ + { + return signed + ? EmitBinarySignedSatQSub(context, ne, me) + : EmitBinaryUnsignedSatQSub(context, ne, me); + } + }, signed); + } + + private static void EmitSse41MaxMinNumOpF32(ArmEmitterContext context, bool isMaxNum, bool scalar) + { + IOpCode32Simd op = (IOpCode32Simd)context.CurrOp; + + Operand genericEmit(Operand n, Operand m) + { + Operand nNum = context.Copy(n); + Operand mNum = context.Copy(m); + + InstEmit.EmitSse2VectorIsNaNOpF(context, nNum, out Operand nQNaNMask, out _, isQNaN: true); + InstEmit.EmitSse2VectorIsNaNOpF(context, mNum, out Operand mQNaNMask, out _, isQNaN: true); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand negInfMask = X86GetAllElements(context, isMaxNum ? float.NegativeInfinity : float.PositiveInfinity); + + Operand nMask = context.AddIntrinsic(Intrinsic.X86Andnps, mQNaNMask, nQNaNMask); + Operand mMask = context.AddIntrinsic(Intrinsic.X86Andnps, nQNaNMask, mQNaNMask); + + nNum = context.AddIntrinsic(Intrinsic.X86Blendvps, nNum, negInfMask, nMask); + mNum = context.AddIntrinsic(Intrinsic.X86Blendvps, mNum, negInfMask, mMask); + + return context.AddIntrinsic(isMaxNum ? Intrinsic.X86Maxps : Intrinsic.X86Minps, nNum, mNum); + } + else /* if (sizeF == 1) */ + { + Operand negInfMask = X86GetAllElements(context, isMaxNum ? double.NegativeInfinity : double.PositiveInfinity); + + Operand nMask = context.AddIntrinsic(Intrinsic.X86Andnpd, mQNaNMask, nQNaNMask); + Operand mMask = context.AddIntrinsic(Intrinsic.X86Andnpd, nQNaNMask, mQNaNMask); + + nNum = context.AddIntrinsic(Intrinsic.X86Blendvpd, nNum, negInfMask, nMask); + mNum = context.AddIntrinsic(Intrinsic.X86Blendvpd, mNum, negInfMask, mMask); + + return context.AddIntrinsic(isMaxNum ? Intrinsic.X86Maxpd : Intrinsic.X86Minpd, nNum, mNum); + } + } + + if (scalar) + { + EmitScalarBinaryOpSimd32(context, genericEmit); + } + else + { + EmitVectorBinaryOpSimd32(context, genericEmit); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCmp.cs b/src/ARMeilleure/Instructions/InstEmitSimdCmp.cs new file mode 100644 index 00000000..aab67786 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCmp.cs @@ -0,0 +1,798 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func2I = Func; + + static partial class InstEmit + { + public static void Cmeq_S(ArmEmitterContext context) + { + EmitCmpOp(context, (op1, op2) => context.ICompareEqual(op1, op2), scalar: true); + } + + public static void Cmeq_V(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m; + + if (op is OpCodeSimdReg binOp) + { + m = GetVec(binOp.Rm); + } + else + { + m = context.VectorZero(); + } + + Intrinsic cmpInst = X86PcmpeqInstruction[op.Size]; + + Operand res = context.AddIntrinsic(cmpInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitCmpOp(context, (op1, op2) => context.ICompareEqual(op1, op2), scalar: false); + } + } + + public static void Cmge_S(ArmEmitterContext context) + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreaterOrEqual(op1, op2), scalar: true); + } + + public static void Cmge_V(ArmEmitterContext context) + { + if (Optimizations.UseSse42) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m; + + if (op is OpCodeSimdReg binOp) + { + m = GetVec(binOp.Rm); + } + else + { + m = context.VectorZero(); + } + + Intrinsic cmpInst = X86PcmpgtInstruction[op.Size]; + + Operand res = context.AddIntrinsic(cmpInst, m, n); + + Operand mask = X86GetAllElements(context, -1L); + + res = context.AddIntrinsic(Intrinsic.X86Pandn, res, mask); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreaterOrEqual(op1, op2), scalar: false); + } + } + + public static void Cmgt_S(ArmEmitterContext context) + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreater(op1, op2), scalar: true); + } + + public static void Cmgt_V(ArmEmitterContext context) + { + if (Optimizations.UseSse42) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m; + + if (op is OpCodeSimdReg binOp) + { + m = GetVec(binOp.Rm); + } + else + { + m = context.VectorZero(); + } + + Intrinsic cmpInst = X86PcmpgtInstruction[op.Size]; + + Operand res = context.AddIntrinsic(cmpInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreater(op1, op2), scalar: false); + } + } + + public static void Cmhi_S(ArmEmitterContext context) + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreaterUI(op1, op2), scalar: true); + } + + public static void Cmhi_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSse41 && op.Size < 3) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic maxInst = X86PmaxuInstruction[op.Size]; + + Operand res = context.AddIntrinsic(maxInst, m, n); + + Intrinsic cmpInst = X86PcmpeqInstruction[op.Size]; + + res = context.AddIntrinsic(cmpInst, res, m); + + Operand mask = X86GetAllElements(context, -1L); + + res = context.AddIntrinsic(Intrinsic.X86Pandn, res, mask); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreaterUI(op1, op2), scalar: false); + } + } + + public static void Cmhs_S(ArmEmitterContext context) + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreaterOrEqualUI(op1, op2), scalar: true); + } + + public static void Cmhs_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSse41 && op.Size < 3) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic maxInst = X86PmaxuInstruction[op.Size]; + + Operand res = context.AddIntrinsic(maxInst, n, m); + + Intrinsic cmpInst = X86PcmpeqInstruction[op.Size]; + + res = context.AddIntrinsic(cmpInst, res, n); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreaterOrEqualUI(op1, op2), scalar: false); + } + } + + public static void Cmle_S(ArmEmitterContext context) + { + EmitCmpOp(context, (op1, op2) => context.ICompareLessOrEqual(op1, op2), scalar: true); + } + + public static void Cmle_V(ArmEmitterContext context) + { + if (Optimizations.UseSse42) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Intrinsic cmpInst = X86PcmpgtInstruction[op.Size]; + + Operand res = context.AddIntrinsic(cmpInst, n, context.VectorZero()); + + Operand mask = X86GetAllElements(context, -1L); + + res = context.AddIntrinsic(Intrinsic.X86Pandn, res, mask); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitCmpOp(context, (op1, op2) => context.ICompareLessOrEqual(op1, op2), scalar: false); + } + } + + public static void Cmlt_S(ArmEmitterContext context) + { + EmitCmpOp(context, (op1, op2) => context.ICompareLess(op1, op2), scalar: true); + } + + public static void Cmlt_V(ArmEmitterContext context) + { + if (Optimizations.UseSse42) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Intrinsic cmpInst = X86PcmpgtInstruction[op.Size]; + + Operand res = context.AddIntrinsic(cmpInst, context.VectorZero(), n); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitCmpOp(context, (op1, op2) => context.ICompareLess(op1, op2), scalar: false); + } + } + + public static void Cmtst_S(ArmEmitterContext context) + { + EmitCmtstOp(context, scalar: true); + } + + public static void Cmtst_V(ArmEmitterContext context) + { + EmitCmtstOp(context, scalar: false); + } + + public static void Facge_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: true, absolute: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGE), scalar: true, absolute: true); + } + } + + public static void Facge_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: false, absolute: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGE), scalar: false, absolute: true); + } + } + + public static void Facgt_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThan, scalar: true, absolute: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGT), scalar: true, absolute: true); + } + } + + public static void Facgt_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThan, scalar: false, absolute: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGT), scalar: false, absolute: true); + } + } + + public static void Fccmp_S(ArmEmitterContext context) + { + EmitFccmpOrFccmpe(context, signalNaNs: false); + } + + public static void Fccmpe_S(ArmEmitterContext context) + { + EmitFccmpOrFccmpe(context, signalNaNs: true); + } + + public static void Fcmeq_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.Equal, scalar: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareEQ), scalar: true); + } + } + + public static void Fcmeq_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.Equal, scalar: false); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareEQ), scalar: false); + } + } + + public static void Fcmge_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGE), scalar: true); + } + } + + public static void Fcmge_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: false); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGE), scalar: false); + } + } + + public static void Fcmgt_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThan, scalar: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGT), scalar: true); + } + } + + public static void Fcmgt_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThan, scalar: false); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGT), scalar: false); + } + } + + public static void Fcmle_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.LessThanOrEqual, scalar: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareLE), scalar: true); + } + } + + public static void Fcmle_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.LessThanOrEqual, scalar: false); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareLE), scalar: false); + } + } + + public static void Fcmlt_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.LessThan, scalar: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareLT), scalar: true); + } + } + + public static void Fcmlt_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.LessThan, scalar: false); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareLT), scalar: false); + } + } + + public static void Fcmp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitFcmpOrFcmpe(context, signalNaNs: false); + } + else + { + EmitFcmpOrFcmpe(context, signalNaNs: false); + } + } + + public static void Fcmpe_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitFcmpOrFcmpe(context, signalNaNs: true); + } + else + { + EmitFcmpOrFcmpe(context, signalNaNs: true); + } + } + + private static void EmitFccmpOrFccmpe(ArmEmitterContext context, bool signalNaNs) + { + OpCodeSimdFcond op = (OpCodeSimdFcond)context.CurrOp; + + Operand lblTrue = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblTrue, InstEmitFlowHelper.GetCondTrue(context, op.Cond)); + + EmitSetNzcv(context, op.Nzcv); + + context.Branch(lblEnd); + + context.MarkLabel(lblTrue); + + EmitFcmpOrFcmpe(context, signalNaNs); + + context.MarkLabel(lblEnd); + } + + private static void EmitSetNzcv(ArmEmitterContext context, int nzcv) + { + static Operand Extract(int value, int bit) + { + if (bit != 0) + { + value >>= bit; + } + + value &= 1; + + return Const(value); + } + + SetFlag(context, PState.VFlag, Extract(nzcv, 0)); + SetFlag(context, PState.CFlag, Extract(nzcv, 1)); + SetFlag(context, PState.ZFlag, Extract(nzcv, 2)); + SetFlag(context, PState.NFlag, Extract(nzcv, 3)); + } + + private static void EmitFcmpOrFcmpe(ArmEmitterContext context, bool signalNaNs) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + bool cmpWithZero = op is not OpCodeSimdFcond && op.Bit3; + + if (Optimizations.FastFP && (signalNaNs ? Optimizations.UseAvx : Optimizations.UseSse2)) + { + Operand n = GetVec(op.Rn); + Operand m = cmpWithZero ? context.VectorZero() : GetVec(op.Rm); + + CmpCondition cmpOrdered = signalNaNs ? CmpCondition.OrderedS : CmpCondition.OrderedQ; + + Operand lblNaN = Label(); + Operand lblEnd = Label(); + + if (op.Size == 0) + { + Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpss, n, m, Const((int)cmpOrdered)); + + Operand isOrdered = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, ordMask); + + context.BranchIfFalse(lblNaN, isOrdered); + + Operand nCopy = context.Copy(n); + Operand mCopy = cmpWithZero ? context.VectorZero() : context.Copy(m); + + Operand cf = context.AddIntrinsicInt(Intrinsic.X86Comissge, nCopy, mCopy); + Operand zf = context.AddIntrinsicInt(Intrinsic.X86Comisseq, nCopy, mCopy); + Operand nf = context.AddIntrinsicInt(Intrinsic.X86Comisslt, nCopy, mCopy); + + SetFlag(context, PState.VFlag, Const(0)); + SetFlag(context, PState.CFlag, cf); + SetFlag(context, PState.ZFlag, zf); + SetFlag(context, PState.NFlag, nf); + } + else /* if (op.Size == 1) */ + { + Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, m, Const((int)cmpOrdered)); + + Operand isOrdered = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, ordMask); + + context.BranchIfFalse(lblNaN, isOrdered); + + Operand nCopy = context.Copy(n); + Operand mCopy = cmpWithZero ? context.VectorZero() : context.Copy(m); + + Operand cf = context.AddIntrinsicInt(Intrinsic.X86Comisdge, nCopy, mCopy); + Operand zf = context.AddIntrinsicInt(Intrinsic.X86Comisdeq, nCopy, mCopy); + Operand nf = context.AddIntrinsicInt(Intrinsic.X86Comisdlt, nCopy, mCopy); + + SetFlag(context, PState.VFlag, Const(0)); + SetFlag(context, PState.CFlag, cf); + SetFlag(context, PState.ZFlag, zf); + SetFlag(context, PState.NFlag, nf); + } + + context.Branch(lblEnd); + + context.MarkLabel(lblNaN); + + SetFlag(context, PState.VFlag, Const(1)); + SetFlag(context, PState.CFlag, Const(1)); + SetFlag(context, PState.ZFlag, Const(0)); + SetFlag(context, PState.NFlag, Const(0)); + + context.MarkLabel(lblEnd); + } + else + { + OperandType type = op.Size != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand me; + + if (cmpWithZero) + { + me = op.Size == 0 ? ConstF(0f) : ConstF(0d); + } + else + { + me = context.VectorExtract(type, GetVec(op.Rm), 0); + } + + Operand nzcv = EmitSoftFloatCall(context, nameof(SoftFloat32.FPCompare), ne, me, Const(signalNaNs)); + + EmitSetNzcv(context, nzcv); + } + } + + private static void EmitSetNzcv(ArmEmitterContext context, Operand nzcv) + { + Operand Extract(Operand value, int bit) + { + if (bit != 0) + { + value = context.ShiftRightUI(value, Const(bit)); + } + + value = context.BitwiseAnd(value, Const(1)); + + return value; + } + + SetFlag(context, PState.VFlag, Extract(nzcv, 0)); + SetFlag(context, PState.CFlag, Extract(nzcv, 1)); + SetFlag(context, PState.ZFlag, Extract(nzcv, 2)); + SetFlag(context, PState.NFlag, Extract(nzcv, 3)); + } + + private static void EmitCmpOp(ArmEmitterContext context, Func2I emitCmp, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + ulong szMask = ulong.MaxValue >> (64 - (8 << op.Size)); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + Operand me; + + if (op is OpCodeSimdReg binOp) + { + me = EmitVectorExtractSx(context, binOp.Rm, index, op.Size); + } + else + { + me = Const(0L); + } + + Operand isTrue = emitCmp(ne, me); + + Operand mask = context.ConditionalSelect(isTrue, Const(szMask), Const(0L)); + + res = EmitVectorInsert(context, res, mask, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitCmtstOp(ArmEmitterContext context, bool scalar) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + ulong szMask = ulong.MaxValue >> (64 - (8 << op.Size)); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, index, op.Size); + + Operand test = context.BitwiseAnd(ne, me); + + Operand isTrue = context.ICompareNotEqual(test, Const(0L)); + + Operand mask = context.ConditionalSelect(isTrue, Const(szMask), Const(0L)); + + res = EmitVectorInsert(context, res, mask, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitCmpOpF(ArmEmitterContext context, string name, bool scalar, bool absolute = false) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = !scalar ? op.GetBytesCount() >> sizeF + 2 : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + Operand me; + + if (op is OpCodeSimdReg binOp) + { + me = context.VectorExtract(type, GetVec(binOp.Rm), index); + } + else + { + me = sizeF == 0 ? ConstF(0f) : ConstF(0d); + } + + if (absolute) + { + ne = EmitUnaryMathCall(context, nameof(Math.Abs), ne); + me = EmitUnaryMathCall(context, nameof(Math.Abs), me); + } + + Operand e = EmitSoftFloatCall(context, name, ne, me); + + res = context.VectorInsert(res, e, index); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitSse2OrAvxCmpOpF(ArmEmitterContext context, CmpCondition cond, bool scalar, bool absolute = false) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = op is OpCodeSimdReg binOp ? GetVec(binOp.Rm) : context.VectorZero(); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + if (absolute) + { + Operand mask = scalar ? X86GetScalar(context, int.MaxValue) : X86GetAllElements(context, int.MaxValue); + + n = context.AddIntrinsic(Intrinsic.X86Andps, n, mask); + m = context.AddIntrinsic(Intrinsic.X86Andps, m, mask); + } + + Intrinsic inst = scalar ? Intrinsic.X86Cmpss : Intrinsic.X86Cmpps; + + Operand res = context.AddIntrinsic(inst, n, m, Const((int)cond)); + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + if (absolute) + { + Operand mask = scalar ? X86GetScalar(context, long.MaxValue) : X86GetAllElements(context, long.MaxValue); + + n = context.AddIntrinsic(Intrinsic.X86Andpd, n, mask); + m = context.AddIntrinsic(Intrinsic.X86Andpd, m, mask); + } + + Intrinsic inst = scalar ? Intrinsic.X86Cmpsd : Intrinsic.X86Cmppd; + + Operand res = context.AddIntrinsic(inst, n, m, Const((int)cond)); + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCmp32.cs b/src/ARMeilleure/Instructions/InstEmitSimdCmp32.cs new file mode 100644 index 00000000..1d68bce6 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCmp32.cs @@ -0,0 +1,437 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper32; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func2I = Func; + + static partial class InstEmit32 + { + public static void Vceq_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.Equal, false); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.Equal, false); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareEQFpscr), false); + } + } + + public static void Vceq_I(ArmEmitterContext context) + { + EmitCmpOpI32(context, context.ICompareEqual, context.ICompareEqual, false, false); + } + + public static void Vceq_Z(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.Equal, true); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.Equal, true); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareEQFpscr), true); + } + } + else + { + EmitCmpOpI32(context, context.ICompareEqual, context.ICompareEqual, true, false); + } + } + + public static void Vcge_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.GreaterThanOrEqual, false); + } + else if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.GreaterThanOrEqual, false); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareGEFpscr), false); + } + } + + public static void Vcge_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitCmpOpI32(context, context.ICompareGreaterOrEqual, context.ICompareGreaterOrEqualUI, false, !op.U); + } + + public static void Vcge_Z(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.GreaterThanOrEqual, true); + } + else if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.GreaterThanOrEqual, true); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareGEFpscr), true); + } + } + else + { + EmitCmpOpI32(context, context.ICompareGreaterOrEqual, context.ICompareGreaterOrEqualUI, true, true); + } + } + + public static void Vcgt_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.GreaterThan, false); + } + else if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.GreaterThan, false); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareGTFpscr), false); + } + } + + public static void Vcgt_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitCmpOpI32(context, context.ICompareGreater, context.ICompareGreaterUI, false, !op.U); + } + + public static void Vcgt_Z(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.GreaterThan, true); + } + else if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.GreaterThan, true); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareGTFpscr), true); + } + } + else + { + EmitCmpOpI32(context, context.ICompareGreater, context.ICompareGreaterUI, true, true); + } + } + + public static void Vcle_Z(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.LessThanOrEqual, true); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.LessThanOrEqual, true); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareLEFpscr), true); + } + } + else + { + EmitCmpOpI32(context, context.ICompareLessOrEqual, context.ICompareLessOrEqualUI, true, true); + } + } + + public static void Vclt_Z(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.LessThan, true); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.LessThan, true); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareLTFpscr), true); + } + } + else + { + EmitCmpOpI32(context, context.ICompareLess, context.ICompareLessUI, true, true); + } + } + + private static void EmitCmpOpF32(ArmEmitterContext context, string name, bool zero) + { + if (zero) + { + EmitVectorUnaryOpF32(context, (m) => + { + Operand zeroOp = m.Type == OperandType.FP64 ? ConstF(0.0d) : ConstF(0.0f); + + return EmitSoftFloatCallDefaultFpscr(context, name, m, zeroOp); + }); + } + else + { + EmitVectorBinaryOpF32(context, (n, m) => + { + return EmitSoftFloatCallDefaultFpscr(context, name, n, m); + }); + } + } + + private static Operand ZerosOrOnes(ArmEmitterContext context, Operand fromBool, OperandType baseType) + { + var ones = (baseType == OperandType.I64) ? Const(-1L) : Const(-1); + + return context.ConditionalSelect(fromBool, ones, Const(baseType, 0L)); + } + + private static void EmitCmpOpI32( + ArmEmitterContext context, + Func2I signedOp, + Func2I unsignedOp, + bool zero, + bool signed) + { + if (zero) + { + if (signed) + { + EmitVectorUnaryOpSx32(context, (m) => + { + OperandType type = m.Type; + Operand zeroV = (type == OperandType.I64) ? Const(0L) : Const(0); + + return ZerosOrOnes(context, signedOp(m, zeroV), type); + }); + } + else + { + EmitVectorUnaryOpZx32(context, (m) => + { + OperandType type = m.Type; + Operand zeroV = (type == OperandType.I64) ? Const(0L) : Const(0); + + return ZerosOrOnes(context, unsignedOp(m, zeroV), type); + }); + } + } + else + { + if (signed) + { + EmitVectorBinaryOpSx32(context, (n, m) => ZerosOrOnes(context, signedOp(n, m), n.Type)); + } + else + { + EmitVectorBinaryOpZx32(context, (n, m) => ZerosOrOnes(context, unsignedOp(n, m), n.Type)); + } + } + } + + public static void Vcmp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVcmpOrVcmpe(context, false); + } + else + { + EmitVcmpOrVcmpe(context, false); + } + } + + public static void Vcmpe(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVcmpOrVcmpe(context, true); + } + else + { + EmitVcmpOrVcmpe(context, true); + } + } + + private static void EmitVcmpOrVcmpe(ArmEmitterContext context, bool signalNaNs) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + bool cmpWithZero = (op.Opc & 2) != 0; + int sizeF = op.Size & 1; + + if (Optimizations.FastFP && (signalNaNs ? Optimizations.UseAvx : Optimizations.UseSse2)) + { + CmpCondition cmpOrdered = signalNaNs ? CmpCondition.OrderedS : CmpCondition.OrderedQ; + + bool doubleSize = sizeF != 0; + int shift = doubleSize ? 1 : 2; + Operand m = GetVecA32(op.Vm >> shift); + Operand n = GetVecA32(op.Vd >> shift); + + n = EmitSwapScalar(context, n, op.Vd, doubleSize); + m = cmpWithZero ? context.VectorZero() : EmitSwapScalar(context, m, op.Vm, doubleSize); + + Operand lblNaN = Label(); + Operand lblEnd = Label(); + + if (!doubleSize) + { + Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpss, n, m, Const((int)cmpOrdered)); + + Operand isOrdered = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, ordMask); + + context.BranchIfFalse(lblNaN, isOrdered); + + Operand cf = context.AddIntrinsicInt(Intrinsic.X86Comissge, n, m); + Operand zf = context.AddIntrinsicInt(Intrinsic.X86Comisseq, n, m); + Operand nf = context.AddIntrinsicInt(Intrinsic.X86Comisslt, n, m); + + SetFpFlag(context, FPState.VFlag, Const(0)); + SetFpFlag(context, FPState.CFlag, cf); + SetFpFlag(context, FPState.ZFlag, zf); + SetFpFlag(context, FPState.NFlag, nf); + } + else + { + Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, m, Const((int)cmpOrdered)); + + Operand isOrdered = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, ordMask); + + context.BranchIfFalse(lblNaN, isOrdered); + + Operand cf = context.AddIntrinsicInt(Intrinsic.X86Comisdge, n, m); + Operand zf = context.AddIntrinsicInt(Intrinsic.X86Comisdeq, n, m); + Operand nf = context.AddIntrinsicInt(Intrinsic.X86Comisdlt, n, m); + + SetFpFlag(context, FPState.VFlag, Const(0)); + SetFpFlag(context, FPState.CFlag, cf); + SetFpFlag(context, FPState.ZFlag, zf); + SetFpFlag(context, FPState.NFlag, nf); + } + + context.Branch(lblEnd); + + context.MarkLabel(lblNaN); + + SetFpFlag(context, FPState.VFlag, Const(1)); + SetFpFlag(context, FPState.CFlag, Const(1)); + SetFpFlag(context, FPState.ZFlag, Const(0)); + SetFpFlag(context, FPState.NFlag, Const(0)); + + context.MarkLabel(lblEnd); + } + else + { + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand ne = ExtractScalar(context, type, op.Vd); + Operand me; + + if (cmpWithZero) + { + me = sizeF == 0 ? ConstF(0f) : ConstF(0d); + } + else + { + me = ExtractScalar(context, type, op.Vm); + } + + Operand nzcv = EmitSoftFloatCall(context, nameof(SoftFloat32.FPCompare), ne, me, Const(signalNaNs)); + + EmitSetFpscrNzcv(context, nzcv); + } + } + + private static void EmitSetFpscrNzcv(ArmEmitterContext context, Operand nzcv) + { + Operand Extract(Operand value, int bit) + { + if (bit != 0) + { + value = context.ShiftRightUI(value, Const(bit)); + } + + value = context.BitwiseAnd(value, Const(1)); + + return value; + } + + SetFpFlag(context, FPState.VFlag, Extract(nzcv, 0)); + SetFpFlag(context, FPState.CFlag, Extract(nzcv, 1)); + SetFpFlag(context, FPState.ZFlag, Extract(nzcv, 2)); + SetFpFlag(context, FPState.NFlag, Extract(nzcv, 3)); + } + + private static void EmitSse2OrAvxCmpOpF32(ArmEmitterContext context, CmpCondition cond, bool zero) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int sizeF = op.Size & 1; + Intrinsic inst = (sizeF == 0) ? Intrinsic.X86Cmpps : Intrinsic.X86Cmppd; + + if (zero) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + return context.AddIntrinsic(inst, m, context.VectorZero(), Const((int)cond)); + }); + } + else + { + EmitVectorBinaryOpSimd32(context, (n, m) => + { + return context.AddIntrinsic(inst, n, m, Const((int)cond)); + }); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCrypto.cs b/src/ARMeilleure/Instructions/InstEmitSimdCrypto.cs new file mode 100644 index 00000000..6226e35a --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCrypto.cs @@ -0,0 +1,115 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Aesd_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AesdV, d, n); + } + else if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesdeclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero()); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Decrypt)), d, n); + } + + context.Copy(d, res); + } + + public static void Aese_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AeseV, d, n); + } + else if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesenclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero()); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Encrypt)), d, n); + } + + context.Copy(d, res); + } + + public static void Aesimc_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AesimcV, n); + } + else if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesimc, n); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.InverseMixColumns)), n); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void Aesmc_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AesmcV, n); + } + else if (Optimizations.UseAesni) + { + Operand roundKey = context.VectorZero(); + + // Inverse Shift Rows, Inverse Sub Bytes, xor 0 so nothing happens + res = context.AddIntrinsic(Intrinsic.X86Aesdeclast, n, roundKey); + + // Shift Rows, Sub Bytes, Mix Columns (!), xor 0 so nothing happens + res = context.AddIntrinsic(Intrinsic.X86Aesenc, res, roundKey); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.MixColumns)), n); + } + + context.Copy(GetVec(op.Rd), res); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCrypto32.cs b/src/ARMeilleure/Instructions/InstEmitSimdCrypto32.cs new file mode 100644 index 00000000..7a0c981e --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCrypto32.cs @@ -0,0 +1,115 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + partial class InstEmit32 + { + public static void Aesd_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qm); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AesdV, d, n); + } + else if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesdeclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero()); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Decrypt)), d, n); + } + + context.Copy(d, res); + } + + public static void Aese_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qm); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AeseV, d, n); + } + else if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesenclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero()); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Encrypt)), d, n); + } + + context.Copy(d, res); + } + + public static void Aesimc_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand n = GetVecA32(op.Qm); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AesimcV, n); + } + else if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesimc, n); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.InverseMixColumns)), n); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Aesmc_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand n = GetVecA32(op.Qm); + + Operand res; + + if (Optimizations.UseArm64Aes) + { + res = context.AddIntrinsic(Intrinsic.Arm64AesmcV, n); + } + else if (Optimizations.UseAesni) + { + Operand roundKey = context.VectorZero(); + + // Inverse Shift Rows, Inverse Sub Bytes, xor 0 so nothing happens. + res = context.AddIntrinsic(Intrinsic.X86Aesdeclast, n, roundKey); + + // Shift Rows, Sub Bytes, Mix Columns (!), xor 0 so nothing happens. + res = context.AddIntrinsic(Intrinsic.X86Aesenc, res, roundKey); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.MixColumns)), n); + } + + context.Copy(GetVecA32(op.Qd), res); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCvt.cs b/src/ARMeilleure/Instructions/InstEmitSimdCvt.cs new file mode 100644 index 00000000..3363a7c7 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCvt.cs @@ -0,0 +1,1890 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Reflection; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func1I = Func; + + static partial class InstEmit + { + public static void Fcvt_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (op.Size == 0 && op.Opc == 1) // Single -> Double. + { + if (Optimizations.UseSse2) + { + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtss2sd, context.VectorZero(), n); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), 0); + + Operand res = context.ConvertToFP(OperandType.FP64, ne); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + else if (op.Size == 1 && op.Opc == 0) // Double -> Single. + { + if (Optimizations.UseSse2) + { + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtsd2ss, context.VectorZero(), n); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = context.VectorExtract(OperandType.FP64, GetVec(op.Rn), 0); + + Operand res = context.ConvertToFP(OperandType.FP32, ne); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + else if (op.Size == 0 && op.Opc == 3) // Single -> Half. + { + if (Optimizations.UseF16c) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vcvtps2ph, n, Const(X86GetRoundControl(FPRoundingMode.ToNearest))); + res = context.AddIntrinsic(Intrinsic.X86Pslldq, res, Const(14)); // VectorZeroUpper112() + res = context.AddIntrinsic(Intrinsic.X86Psrldq, res, Const(14)); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), 0); + + context.StoreToContext(); + Operand res = context.Call(typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert)), ne); + context.LoadFromContext(); + + res = context.ZeroExtend16(OperandType.I64, res); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), res, 0, 1)); + } + } + else if (op.Size == 3 && op.Opc == 0) // Half -> Single. + { + if (Optimizations.UseF16c) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vcvtph2ps, GetVec(op.Rn)); + res = context.VectorZeroUpper96(res); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = EmitVectorExtractZx(context, op.Rn, 0, 1); + + context.StoreToContext(); + Operand res = context.Call(typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert)), ne); + context.LoadFromContext(); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + else if (op.Size == 1 && op.Opc == 3) // Double -> Half. + { + if (Optimizations.UseF16c) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtsd2ss, context.VectorZero(), n); + res = context.AddIntrinsic(Intrinsic.X86Vcvtps2ph, res, Const(X86GetRoundControl(FPRoundingMode.ToNearest))); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = context.VectorExtract(OperandType.FP64, GetVec(op.Rn), 0); + + context.StoreToContext(); + Operand res = context.Call(typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert)), ne); + context.LoadFromContext(); + + res = context.ZeroExtend16(OperandType.I64, res); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), res, 0, 1)); + } + } + else if (op.Size == 3 && op.Opc == 1) // Half -> Double. + { + if (Optimizations.UseF16c) + { + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vcvtph2ps, GetVec(op.Rn)); + res = context.AddIntrinsic(Intrinsic.X86Cvtss2sd, context.VectorZero(), res); + res = context.VectorZeroUpper64(res); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = EmitVectorExtractZx(context, op.Rn, 0, 1); + + context.StoreToContext(); + Operand res = context.Call(typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert)), ne); + context.LoadFromContext(); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + else // Invalid encoding. + { + Debug.Assert(false, $"type == {op.Size} && opc == {op.Opc}"); + } + } + + public static void Fcvtas_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtasGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.ToNearestAway, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1)); + } + } + + public static void Fcvtas_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtasS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.ToNearestAway, scalar: true); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: true, scalar: true); + } + } + + public static void Fcvtas_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtasS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.ToNearestAway, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: true, scalar: false); + } + } + + public static void Fcvtau_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtauGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.ToNearestAway, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1)); + } + } + + public static void Fcvtau_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtauS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.ToNearestAway, scalar: true); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: false, scalar: true); + } + } + + public static void Fcvtau_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtauV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.ToNearestAway, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: false, scalar: false); + } + } + + public static void Fcvtl_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtlV); + } + else if (Optimizations.UseSse2 && sizeF == 1) + { + Operand n = GetVec(op.Rn); + + Operand res = op.RegisterSize == RegisterSize.Simd128 ? context.AddIntrinsic(Intrinsic.X86Movhlps, n, n) : n; + res = context.AddIntrinsic(Intrinsic.X86Cvtps2pd, res); + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseF16c && sizeF == 0) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand n = GetVec(op.Rn); + + Operand res = op.RegisterSize == RegisterSize.Simd128 ? context.AddIntrinsic(Intrinsic.X86Movhlps, n, n) : n; + res = context.AddIntrinsic(Intrinsic.X86Vcvtph2ps, res); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.VectorZero(); + + int elems = 4 >> sizeF; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + if (sizeF == 0) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, part + index, 1); + + context.StoreToContext(); + Operand e = context.Call(typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert)), ne); + context.LoadFromContext(); + + res = context.VectorInsert(res, e, index); + } + else /* if (sizeF == 1) */ + { + Operand ne = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), part + index); + + Operand e = context.ConvertToFP(OperandType.FP64, ne); + + res = context.VectorInsert(res, e, index); + } + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Fcvtms_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtmsGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsMinusInfinity, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1)); + } + } + + public static void Fcvtms_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtmsV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.TowardsMinusInfinity, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1), signed: true, scalar: false); + } + } + + public static void Fcvtmu_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtmuGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsMinusInfinity, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1)); + } + } + + public static void Fcvtn_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpFRd(context, Intrinsic.Arm64FcvtnV); + } + else if (Optimizations.UseSse2 && sizeF == 1) + { + Operand d = GetVec(op.Rd); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 ? Intrinsic.X86Movlhps : Intrinsic.X86Movhlps; + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtpd2ps, GetVec(op.Rn)); + nInt = context.AddIntrinsic(Intrinsic.X86Movlhps, nInt, nInt); + + Operand res = context.VectorZeroUpper64(d); + res = context.AddIntrinsic(movInst, res, nInt); + + context.Copy(d, res); + } + else if (Optimizations.UseF16c && sizeF == 0) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 ? Intrinsic.X86Movlhps : Intrinsic.X86Movhlps; + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Vcvtps2ph, n, Const(X86GetRoundControl(FPRoundingMode.ToNearest))); + nInt = context.AddIntrinsic(Intrinsic.X86Movlhps, nInt, nInt); + + Operand res = context.VectorZeroUpper64(d); + res = context.AddIntrinsic(movInst, res, nInt); + + context.Copy(d, res); + } + else + { + OperandType type = sizeF == 0 ? OperandType.FP32 : OperandType.FP64; + + int elems = 4 >> sizeF; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + + if (sizeF == 0) + { + context.StoreToContext(); + Operand e = context.Call(typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert)), ne); + context.LoadFromContext(); + + res = EmitVectorInsert(context, res, e, part + index, 1); + } + else /* if (sizeF == 1) */ + { + Operand e = context.ConvertToFP(OperandType.FP32, ne); + + res = context.VectorInsert(res, e, part + index); + } + } + + context.Copy(d, res); + } + } + + public static void Fcvtns_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtnsGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.ToNearest, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1)); + } + } + + public static void Fcvtns_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtnsS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.ToNearest, scalar: true); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1), signed: true, scalar: true); + } + } + + public static void Fcvtns_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtnsV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.ToNearest, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1), signed: true, scalar: false); + } + } + + public static void Fcvtnu_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtnuS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.ToNearest, scalar: true); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1), signed: false, scalar: true); + } + } + + public static void Fcvtnu_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtnuV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.ToNearest, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1), signed: false, scalar: false); + } + } + + public static void Fcvtps_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtpsGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsPlusInfinity, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Ceiling), op1)); + } + } + + public static void Fcvtpu_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtpuGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsPlusInfinity, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Ceiling), op1)); + } + } + + public static void Fcvtzs_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtzsGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsZero, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => op1); + } + } + + public static void Fcvtzs_Gp_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpFToGp(context, Intrinsic.Arm64FcvtzsGpFixed, op.FBits); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsZero, isFixed: true); + } + else + { + EmitFcvtzs_Gp_Fixed(context); + } + } + + public static void Fcvtzs_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtzsS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.TowardsZero, scalar: true); + } + else + { + EmitFcvtz(context, signed: true, scalar: true); + } + } + + public static void Fcvtzs_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtzsV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: true, scalar: false); + } + } + + public static void Fcvtzs_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorConvertBinaryOpF(context, Intrinsic.Arm64FcvtzsVFixed, GetFBits(context)); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: true, scalar: false); + } + } + + public static void Fcvtzu_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtzuGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsZero, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => op1); + } + } + + public static void Fcvtzu_Gp_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpFToGp(context, Intrinsic.Arm64FcvtzuGpFixed, op.FBits); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsZero, isFixed: true); + } + else + { + EmitFcvtzu_Gp_Fixed(context); + } + } + + public static void Fcvtzu_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtzuS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.TowardsZero, scalar: true); + } + else + { + EmitFcvtz(context, signed: false, scalar: true); + } + } + + public static void Fcvtzu_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtzuV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: false, scalar: false); + } + } + + public static void Fcvtzu_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorConvertBinaryOpF(context, Intrinsic.Arm64FcvtzuVFixed, GetFBits(context)); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: false, scalar: false); + } + } + + public static void Scvtf_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFFromGp(context, Intrinsic.Arm64ScvtfGp); + } + else + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + if (op.RegisterSize == RegisterSize.Int32) + { + res = context.SignExtend32(OperandType.I64, res); + } + + res = EmitFPConvert(context, res, op.Size, signed: true); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Scvtf_Gp_Fixed(ArmEmitterContext context) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpFFromGp(context, Intrinsic.Arm64ScvtfGpFixed, op.FBits); + } + else + { + Operand res = GetIntOrZR(context, op.Rn); + + if (op.RegisterSize == RegisterSize.Int32) + { + res = context.SignExtend32(OperandType.I64, res); + } + + res = EmitFPConvert(context, res, op.Size, signed: true); + + res = EmitI2fFBitsMul(context, res, op.FBits); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Scvtf_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64ScvtfS); + } + else if (Optimizations.UseSse2) + { + EmitSse2ScvtfOp(context, scalar: true); + } + else + { + EmitCvtf(context, signed: true, scalar: true); + } + } + + public static void Scvtf_S_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpF(context, Intrinsic.Arm64ScvtfSFixed, GetFBits(context)); + } + else if (Optimizations.UseSse2) + { + EmitSse2ScvtfOp(context, scalar: true); + } + else + { + EmitCvtf(context, signed: true, scalar: true); + } + } + + public static void Scvtf_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64ScvtfV); + } + else if (Optimizations.UseSse2) + { + EmitSse2ScvtfOp(context, scalar: false); + } + else + { + EmitCvtf(context, signed: true, scalar: false); + } + } + + public static void Scvtf_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorConvertBinaryOpF(context, Intrinsic.Arm64ScvtfVFixed, GetFBits(context)); + } + else if (Optimizations.UseSse2) + { + EmitSse2ScvtfOp(context, scalar: false); + } + else + { + EmitCvtf(context, signed: true, scalar: false); + } + } + + public static void Ucvtf_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFFromGp(context, Intrinsic.Arm64UcvtfGp); + } + else + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + res = EmitFPConvert(context, res, op.Size, signed: false); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Ucvtf_Gp_Fixed(ArmEmitterContext context) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpFFromGp(context, Intrinsic.Arm64UcvtfGpFixed, op.FBits); + } + else + { + Operand res = GetIntOrZR(context, op.Rn); + + res = EmitFPConvert(context, res, op.Size, signed: false); + + res = EmitI2fFBitsMul(context, res, op.FBits); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Ucvtf_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64UcvtfS); + } + else if (Optimizations.UseSse2) + { + EmitSse2UcvtfOp(context, scalar: true); + } + else + { + EmitCvtf(context, signed: false, scalar: true); + } + } + + public static void Ucvtf_S_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpF(context, Intrinsic.Arm64UcvtfSFixed, GetFBits(context)); + } + else if (Optimizations.UseSse2) + { + EmitSse2UcvtfOp(context, scalar: true); + } + else + { + EmitCvtf(context, signed: false, scalar: true); + } + } + + public static void Ucvtf_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64UcvtfV); + } + else if (Optimizations.UseSse2) + { + EmitSse2UcvtfOp(context, scalar: false); + } + else + { + EmitCvtf(context, signed: false, scalar: false); + } + } + + public static void Ucvtf_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorConvertBinaryOpF(context, Intrinsic.Arm64UcvtfVFixed, GetFBits(context)); + } + else if (Optimizations.UseSse2) + { + EmitSse2UcvtfOp(context, scalar: false); + } + else + { + EmitCvtf(context, signed: false, scalar: false); + } + } + + private static void EmitFcvt(ArmEmitterContext context, Func1I emit, bool signed, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand n = GetVec(op.Rn); + + int sizeF = op.Size & 1; + int sizeI = sizeF + 2; + + OperandType type = sizeF == 0 ? OperandType.FP32 : OperandType.FP64; + + int elems = !scalar ? op.GetBytesCount() >> sizeI : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, n, index); + + Operand e = emit(ne); + + if (sizeF == 0) + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)); + + e = context.Call(info, e); + + e = context.ZeroExtend32(OperandType.I64, e); + } + else /* if (sizeF == 1) */ + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU64)); + + e = context.Call(info, e); + } + + res = EmitVectorInsert(context, res, e, index, sizeI); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitFcvtz(ArmEmitterContext context, bool signed, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand n = GetVec(op.Rn); + + int sizeF = op.Size & 1; + int sizeI = sizeF + 2; + + OperandType type = sizeF == 0 ? OperandType.FP32 : OperandType.FP64; + + int fBits = GetFBits(context); + + int elems = !scalar ? op.GetBytesCount() >> sizeI : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, n, index); + + Operand e = EmitF2iFBitsMul(context, ne, fBits); + + if (sizeF == 0) + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)); + + e = context.Call(info, e); + + e = context.ZeroExtend32(OperandType.I64, e); + } + else /* if (sizeF == 1) */ + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU64)); + + e = context.Call(info, e); + } + + res = EmitVectorInsert(context, res, e, index, sizeI); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitFcvt_s_Gp(ArmEmitterContext context, Func1I emit) + { + EmitFcvt___Gp(context, emit, signed: true); + } + + private static void EmitFcvt_u_Gp(ArmEmitterContext context, Func1I emit) + { + EmitFcvt___Gp(context, emit, signed: false); + } + + private static void EmitFcvt___Gp(ArmEmitterContext context, Func1I emit, bool signed) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + OperandType type = op.Size == 0 ? OperandType.FP32 : OperandType.FP64; + + Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); + + Operand res = signed + ? EmitScalarFcvts(context, emit(ne), 0) + : EmitScalarFcvtu(context, emit(ne), 0); + + SetIntOrZR(context, op.Rd, res); + } + + private static void EmitFcvtzs_Gp_Fixed(ArmEmitterContext context) + { + EmitFcvtz__Gp_Fixed(context, signed: true); + } + + private static void EmitFcvtzu_Gp_Fixed(ArmEmitterContext context) + { + EmitFcvtz__Gp_Fixed(context, signed: false); + } + + private static void EmitFcvtz__Gp_Fixed(ArmEmitterContext context, bool signed) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + OperandType type = op.Size == 0 ? OperandType.FP32 : OperandType.FP64; + + Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); + + Operand res = signed + ? EmitScalarFcvts(context, ne, op.FBits) + : EmitScalarFcvtu(context, ne, op.FBits); + + SetIntOrZR(context, op.Rd, res); + } + + private static void EmitCvtf(ArmEmitterContext context, bool signed, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + int sizeI = sizeF + 2; + + int fBits = GetFBits(context); + + int elems = !scalar ? op.GetBytesCount() >> sizeI : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorLongExtract(context, op.Rn, index, sizeI); + + Operand e = EmitFPConvert(context, ne, sizeF, signed); + + e = EmitI2fFBitsMul(context, e, fBits); + + res = context.VectorInsert(res, e, index); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static int GetFBits(ArmEmitterContext context) + { + if (context.CurrOp is OpCodeSimdShImm op) + { + return GetImmShr(op); + } + + return 0; + } + + private static Operand EmitFPConvert(ArmEmitterContext context, Operand value, int size, bool signed) + { + Debug.Assert(value.Type == OperandType.I32 || value.Type == OperandType.I64); + Debug.Assert((uint)size < 2); + + OperandType type = size == 0 ? OperandType.FP32 : OperandType.FP64; + + if (signed) + { + return context.ConvertToFP(type, value); + } + else + { + return context.ConvertToFPUI(type, value); + } + } + + private static Operand EmitScalarFcvts(ArmEmitterContext context, Operand value, int fBits) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); + + value = EmitF2iFBitsMul(context, value, fBits); + + MethodInfo info; + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + info = value.Type == OperandType.FP32 + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS32)); + } + else + { + info = value.Type == OperandType.FP32 + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS64)); + } + + return context.Call(info, value); + } + + private static Operand EmitScalarFcvtu(ArmEmitterContext context, Operand value, int fBits) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); + + value = EmitF2iFBitsMul(context, value, fBits); + + MethodInfo info; + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + info = value.Type == OperandType.FP32 + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU32)); + } + else + { + info = value.Type == OperandType.FP32 + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU64)); + } + + return context.Call(info, value); + } + + private static Operand EmitF2iFBitsMul(ArmEmitterContext context, Operand value, int fBits) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); + + if (fBits == 0) + { + return value; + } + + if (value.Type == OperandType.FP32) + { + return context.Multiply(value, ConstF(MathF.Pow(2f, fBits))); + } + else /* if (value.Type == OperandType.FP64) */ + { + return context.Multiply(value, ConstF(Math.Pow(2d, fBits))); + } + } + + private static Operand EmitI2fFBitsMul(ArmEmitterContext context, Operand value, int fBits) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); + + if (fBits == 0) + { + return value; + } + + if (value.Type == OperandType.FP32) + { + return context.Multiply(value, ConstF(1f / MathF.Pow(2f, fBits))); + } + else /* if (value.Type == OperandType.FP64) */ + { + return context.Multiply(value, ConstF(1d / Math.Pow(2d, fBits))); + } + } + + public static Operand EmitSse2CvtDoubleToInt64OpF(ArmEmitterContext context, Operand opF, bool scalar) + { + Debug.Assert(opF.Type == OperandType.V128); + + Operand longL = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, opF); // opFL + Operand res = context.VectorCreateScalar(longL); + + if (!scalar) + { + Operand opFH = context.AddIntrinsic(Intrinsic.X86Movhlps, res, opF); // res doesn't matter. + Operand longH = context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, opFH); + Operand resH = context.VectorCreateScalar(longH); + res = context.AddIntrinsic(Intrinsic.X86Movlhps, res, resH); + } + + return res; + } + + private static Operand EmitSse2CvtInt64ToDoubleOp(ArmEmitterContext context, Operand op, bool scalar) + { + Debug.Assert(op.Type == OperandType.V128); + + Operand longL = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, op); // opL + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtsi2sd, context.VectorZero(), longL); + + if (!scalar) + { + Operand opH = context.AddIntrinsic(Intrinsic.X86Movhlps, res, op); // res doesn't matter. + Operand longH = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, opH); + Operand resH = context.AddIntrinsic(Intrinsic.X86Cvtsi2sd, res, longH); // res doesn't matter. + res = context.AddIntrinsic(Intrinsic.X86Movlhps, res, resH); + } + + return res; + } + + private static void EmitSse2ScvtfOp(ArmEmitterContext context, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == 1f / MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 - fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand res = EmitSse2CvtInt64ToDoubleOp(context, n, scalar); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == 1d / Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L - fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulpd, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitSse2UcvtfOp(ArmEmitterContext context, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand mask = scalar // 65536.000f (1 << 16) + ? X86GetScalar(context, 0x47800000) + : X86GetAllElements(context, 0x47800000); + + Operand res = context.AddIntrinsic(Intrinsic.X86Psrld, n, Const(16)); + res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res); + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, mask); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pslld, n, Const(16)); + res2 = context.AddIntrinsic(Intrinsic.X86Psrld, res2, Const(16)); + res2 = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res2); + + res = context.AddIntrinsic(Intrinsic.X86Addps, res, res2); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == 1f / MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 - fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand mask = scalar // 4294967296.0000000d (1L << 32) + ? X86GetScalar(context, 0x41F0000000000000L) + : X86GetAllElements(context, 0x41F0000000000000L); + + Operand res = context.AddIntrinsic(Intrinsic.X86Psrlq, n, Const(32)); + res = EmitSse2CvtInt64ToDoubleOp(context, res, scalar); + res = context.AddIntrinsic(Intrinsic.X86Mulpd, res, mask); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Psllq, n, Const(32)); + res2 = context.AddIntrinsic(Intrinsic.X86Psrlq, res2, Const(32)); + res2 = EmitSse2CvtInt64ToDoubleOp(context, res2, scalar); + + res = context.AddIntrinsic(Intrinsic.X86Addpd, res, res2); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == 1d / Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L - fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulpd, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitSse41FcvtsOpF(ArmEmitterContext context, FPRoundingMode roundMode, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 + fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulps, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar); + } + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + + Operand fpMaxValMask = scalar // 2.14748365E9f (2147483648) + ? X86GetScalar(context, 0x4F000000) + : X86GetAllElements(context, 0x4F000000); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nInt, nRes); + + if (scalar) + { + dRes = context.VectorZeroUpper96(dRes); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + dRes = context.VectorZeroUpper64(dRes); + } + + context.Copy(GetVec(op.Rd), dRes); + } + else /* if (sizeF == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L + fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulpd, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar); + } + + Operand nLong = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); + + Operand fpMaxValMask = scalar // 9.2233720368547760E18d (9223372036854775808) + ? X86GetScalar(context, 0x43E0000000000000L) + : X86GetAllElements(context, 0x43E0000000000000L); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nLong, nRes); + + if (scalar) + { + dRes = context.VectorZeroUpper64(dRes); + } + + context.Copy(GetVec(op.Rd), dRes); + } + } + + private static void EmitSse41FcvtuOpF(ArmEmitterContext context, FPRoundingMode roundMode, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 + fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulps, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar); + } + + Operand zero = context.VectorZero(); + + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand fpMaxValMask = scalar // 2.14748365E9f (2147483648) + ? X86GetScalar(context, 0x4F000000) + : X86GetAllElements(context, 0x4F000000); + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Subps, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand nInt2 = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nInt2, nRes); + dRes = context.AddIntrinsic(Intrinsic.X86Paddd, dRes, nInt); + + if (scalar) + { + dRes = context.VectorZeroUpper96(dRes); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + dRes = context.VectorZeroUpper64(dRes); + } + + context.Copy(GetVec(op.Rd), dRes); + } + else /* if (sizeF == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L + fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar(context, fpScaled) + : X86GetAllElements(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulpd, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar); + } + + Operand zero = context.VectorZero(); + + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand fpMaxValMask = scalar // 9.2233720368547760E18d (9223372036854775808) + ? X86GetScalar(context, 0x43E0000000000000L) + : X86GetAllElements(context, 0x43E0000000000000L); + + Operand nLong = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); + + nRes = context.AddIntrinsic(Intrinsic.X86Subpd, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand nLong2 = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nLong2, nRes); + dRes = context.AddIntrinsic(Intrinsic.X86Paddq, dRes, nLong); + + if (scalar) + { + dRes = context.VectorZeroUpper64(dRes); + } + + context.Copy(GetVec(op.Rd), dRes); + } + } + + private static void EmitSse41Fcvts_Gp(ArmEmitterContext context, FPRoundingMode roundMode, bool isFixed) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if (op.Size == 0) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (isFixed) + { + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, op.FBits) + int fpScaled = 0x3F800000 + op.FBits * 0x800000; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulss, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true); + } + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + int fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x4F000000 // 2.14748365E9f (2147483648) + : 0x5F000000; // 9.223372E18f (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nInt = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int64) + { + nInt = context.SignExtend32(OperandType.I64, nInt); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong, nInt); + + SetIntOrZR(context, op.Rd, dRes); + } + else /* if (op.Size == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (isFixed) + { + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, op.FBits) + long fpScaled = 0x3FF0000000000000L + op.FBits * 0x10000000000000L; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulsd, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true); + } + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + long fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x41E0000000000000L // 2147483648.0000000d (2147483648) + : 0x43E0000000000000L; // 9.2233720368547760E18d (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nLong = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int32) + { + nLong = context.ConvertI64ToI32(nLong); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong, nLong); + + SetIntOrZR(context, op.Rd, dRes); + } + } + + private static void EmitSse41Fcvtu_Gp(ArmEmitterContext context, FPRoundingMode roundMode, bool isFixed) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if (op.Size == 0) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (isFixed) + { + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, op.FBits) + int fpScaled = 0x3F800000 + op.FBits * 0x800000; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulss, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true); + } + + Operand zero = context.VectorZero(); + + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + int fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x4F000000 // 2.14748365E9f (2147483648) + : 0x5F000000; // 9.223372E18f (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Subss, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand nIntOrLong2 = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nInt = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int64) + { + nInt = context.SignExtend32(OperandType.I64, nInt); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong2, nInt); + dRes = context.Add(dRes, nIntOrLong); + + SetIntOrZR(context, op.Rd, dRes); + } + else /* if (op.Size == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (isFixed) + { + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, op.FBits) + long fpScaled = 0x3FF0000000000000L + op.FBits * 0x10000000000000L; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulsd, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true); + } + + Operand zero = context.VectorZero(); + + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + long fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x41E0000000000000L // 2147483648.0000000d (2147483648) + : 0x43E0000000000000L; // 9.2233720368547760E18d (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Subsd, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand nIntOrLong2 = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nLong = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int32) + { + nLong = context.ConvertI64ToI32(nLong); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong2, nLong); + dRes = context.Add(dRes, nIntOrLong); + + SetIntOrZR(context, op.Rd, dRes); + } + } + + private static Operand EmitVectorLongExtract(ArmEmitterContext context, int reg, int index, int size) + { + OperandType type = size == 3 ? OperandType.I64 : OperandType.I32; + + return context.VectorExtract(type, GetVec(reg), index); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs b/src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs new file mode 100644 index 00000000..8eef6b14 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs @@ -0,0 +1,874 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Reflection; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper32; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + private static int FlipVdBits(int vd, bool lowBit) + { + if (lowBit) + { + // Move the low bit to the top. + return ((vd & 0x1) << 4) | (vd >> 1); + } + else + { + // Move the high bit to the bottom. + return ((vd & 0xf) << 1) | (vd >> 4); + } + } + + private static Operand EmitSaturateFloatToInt(ArmEmitterContext context, Operand op1, bool unsigned) + { + MethodInfo info; + + if (op1.Type == OperandType.FP64) + { + info = unsigned + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS32)); + } + else + { + info = unsigned + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)); + } + + return context.Call(info, op1); + } + + public static void Vcvt_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + bool unsigned = (op.Opc & 1) != 0; + bool toInteger = (op.Opc & 2) != 0; + OperandType floatSize = (op.Size == 2) ? OperandType.FP32 : OperandType.FP64; + + if (toInteger) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, unsigned ? Intrinsic.Arm64FcvtzuV : Intrinsic.Arm64FcvtzsV); + } + else if (Optimizations.UseSse41) + { + EmitSse41ConvertVector32(context, FPRoundingMode.TowardsZero, !unsigned); + } + else + { + EmitVectorUnaryOpF32(context, (op1) => + { + return EmitSaturateFloatToInt(context, op1, unsigned); + }); + } + } + else + { + if (Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (n) => + { + if (unsigned) + { + Operand mask = X86GetAllElements(context, 0x47800000); + + Operand res = context.AddIntrinsic(Intrinsic.X86Psrld, n, Const(16)); + res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res); + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, mask); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pslld, n, Const(16)); + res2 = context.AddIntrinsic(Intrinsic.X86Psrld, res2, Const(16)); + res2 = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res2); + + return context.AddIntrinsic(Intrinsic.X86Addps, res, res2); + } + else + { + return context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, n); + } + }); + } + else + { + if (unsigned) + { + EmitVectorUnaryOpZx32(context, (op1) => EmitFPConvert(context, op1, floatSize, false)); + } + else + { + EmitVectorUnaryOpSx32(context, (op1) => EmitFPConvert(context, op1, floatSize, true)); + } + } + } + } + + public static void Vcvt_V_Fixed(ArmEmitterContext context) + { + OpCode32SimdCvtFFixed op = (OpCode32SimdCvtFFixed)context.CurrOp; + + var toFixed = op.Opc == 1; + int fracBits = op.Fbits; + var unsigned = op.U; + + if (toFixed) // F32 to S32 or U32 (fixed) + { + EmitVectorUnaryOpF32(context, (op1) => + { + var scaledValue = context.Multiply(op1, ConstF(MathF.Pow(2f, fracBits))); + MethodInfo info = unsigned ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)) : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)); + + return context.Call(info, scaledValue); + }); + } + else // S32 or U32 (fixed) to F32 + { + EmitVectorUnaryOpI32(context, (op1) => + { + var floatValue = unsigned ? context.ConvertToFPUI(OperandType.FP32, op1) : context.ConvertToFP(OperandType.FP32, op1); + + return context.Multiply(floatValue, ConstF(1f / MathF.Pow(2f, fracBits))); + }, !unsigned); + } + } + + public static void Vcvt_FD(ArmEmitterContext context) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + int vm = op.Vm; + int vd; + if (op.Size == 3) + { + vd = FlipVdBits(op.Vd, false); + // Double to single. + Operand fp = ExtractScalar(context, OperandType.FP64, vm); + + Operand res = context.ConvertToFP(OperandType.FP32, fp); + + InsertScalar(context, vd, res); + } + else + { + vd = FlipVdBits(op.Vd, true); + // Single to double. + Operand fp = ExtractScalar(context, OperandType.FP32, vm); + + Operand res = context.ConvertToFP(OperandType.FP64, fp); + + InsertScalar(context, vd, res); + } + } + + // VCVT (floating-point to integer, floating-point) | VCVT (integer to floating-point, floating-point). + public static void Vcvt_FI(ArmEmitterContext context) + { + OpCode32SimdCvtFI op = (OpCode32SimdCvtFI)context.CurrOp; + + bool toInteger = (op.Opc2 & 0b100) != 0; + + OperandType floatSize = op.RegisterSize == RegisterSize.Int64 ? OperandType.FP64 : OperandType.FP32; + + if (toInteger) + { + bool unsigned = (op.Opc2 & 1) == 0; + bool roundWithFpscr = op.Opc != 1; + + if (!roundWithFpscr && Optimizations.UseAdvSimd) + { + bool doubleSize = floatSize == OperandType.FP64; + + if (doubleSize) + { + Operand m = GetVecA32(op.Vm >> 1); + + Operand toConvert = InstEmitSimdHelper32Arm64.EmitExtractScalar(context, m, op.Vm, true); + + Intrinsic inst = (unsigned ? Intrinsic.Arm64FcvtzuGp : Intrinsic.Arm64FcvtzsGp) | Intrinsic.Arm64VDouble; + + Operand asInteger = context.AddIntrinsicInt(inst, toConvert); + + InsertScalar(context, op.Vd, asInteger); + } + else + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, unsigned ? Intrinsic.Arm64FcvtzuS : Intrinsic.Arm64FcvtzsS, false); + } + } + else if (!roundWithFpscr && Optimizations.UseSse41) + { + EmitSse41ConvertInt32(context, FPRoundingMode.TowardsZero, !unsigned); + } + else + { + Operand toConvert = ExtractScalar(context, floatSize, op.Vm); + + // TODO: Fast Path. + if (roundWithFpscr) + { + toConvert = EmitRoundByRMode(context, toConvert); + } + + // Round towards zero. + Operand asInteger = EmitSaturateFloatToInt(context, toConvert, unsigned); + + InsertScalar(context, op.Vd, asInteger); + } + } + else + { + bool unsigned = op.Opc == 0; + + Operand toConvert = ExtractScalar(context, OperandType.I32, op.Vm); + + Operand asFloat = EmitFPConvert(context, toConvert, floatSize, !unsigned); + + InsertScalar(context, op.Vd, asFloat); + } + } + + private static Operand EmitRoundMathCall(ArmEmitterContext context, MidpointRounding roundMode, Operand n) + { + IOpCode32Simd op = (IOpCode32Simd)context.CurrOp; + + string name = nameof(Math.Round); + + MethodInfo info = (op.Size & 1) == 0 + ? typeof(MathF).GetMethod(name, new Type[] { typeof(float), typeof(MidpointRounding) }) + : typeof(Math).GetMethod(name, new Type[] { typeof(double), typeof(MidpointRounding) }); + + return context.Call(info, n, Const((int)roundMode)); + } + + private static FPRoundingMode RMToRoundMode(int rm) + { + return rm switch + { + 0b00 => FPRoundingMode.ToNearestAway, + 0b01 => FPRoundingMode.ToNearest, + 0b10 => FPRoundingMode.TowardsPlusInfinity, + 0b11 => FPRoundingMode.TowardsMinusInfinity, + _ => throw new ArgumentOutOfRangeException(nameof(rm)), + }; + } + + // VCVTA/M/N/P (floating-point). + public static void Vcvt_RM(ArmEmitterContext context) + { + OpCode32SimdCvtFI op = (OpCode32SimdCvtFI)context.CurrOp; // toInteger == true (opCode<18> == 1 => Opc2<2> == 1). + + OperandType floatSize = op.RegisterSize == RegisterSize.Int64 ? OperandType.FP64 : OperandType.FP32; + + bool unsigned = op.Opc == 0; + int rm = op.Opc2 & 3; + + Intrinsic inst; + + if (Optimizations.UseAdvSimd) + { + bool doubleSize = floatSize == OperandType.FP64; + + if (doubleSize) + { + Operand m = GetVecA32(op.Vm >> 1); + + Operand toConvert = InstEmitSimdHelper32Arm64.EmitExtractScalar(context, m, op.Vm, true); + + if (unsigned) + { + inst = rm switch + { + 0b00 => Intrinsic.Arm64FcvtauGp, + 0b01 => Intrinsic.Arm64FcvtnuGp, + 0b10 => Intrinsic.Arm64FcvtpuGp, + 0b11 => Intrinsic.Arm64FcvtmuGp, + _ => throw new InvalidOperationException($"{nameof(rm)} contains an invalid value: {rm}"), + }; + } + else + { + inst = rm switch + { + 0b00 => Intrinsic.Arm64FcvtasGp, + 0b01 => Intrinsic.Arm64FcvtnsGp, + 0b10 => Intrinsic.Arm64FcvtpsGp, + 0b11 => Intrinsic.Arm64FcvtmsGp, + _ => throw new InvalidOperationException($"{nameof(rm)} contains an invalid value: {rm}"), + }; + } + + Operand asInteger = context.AddIntrinsicInt(inst | Intrinsic.Arm64VDouble, toConvert); + + InsertScalar(context, op.Vd, asInteger); + } + else + { + if (unsigned) + { + inst = rm switch + { + 0b00 => Intrinsic.Arm64FcvtauS, + 0b01 => Intrinsic.Arm64FcvtnuS, + 0b10 => Intrinsic.Arm64FcvtpuS, + 0b11 => Intrinsic.Arm64FcvtmuS, + _ => throw new InvalidOperationException($"{nameof(rm)} contains an invalid value: {rm}"), + }; + } + else + { + inst = rm switch + { + 0b00 => Intrinsic.Arm64FcvtasS, + 0b01 => Intrinsic.Arm64FcvtnsS, + 0b10 => Intrinsic.Arm64FcvtpsS, + 0b11 => Intrinsic.Arm64FcvtmsS, + _ => throw new InvalidOperationException($"{nameof(rm)} contains an invalid value: {rm}"), + }; + } + + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, inst); + } + } + else if (Optimizations.UseSse41) + { + EmitSse41ConvertInt32(context, RMToRoundMode(rm), !unsigned); + } + else + { + Operand toConvert = ExtractScalar(context, floatSize, op.Vm); + + switch (rm) + { + case 0b00: // Away + toConvert = EmitRoundMathCall(context, MidpointRounding.AwayFromZero, toConvert); + break; + case 0b01: // Nearest + toConvert = EmitRoundMathCall(context, MidpointRounding.ToEven, toConvert); + break; + case 0b10: // Towards positive infinity + toConvert = EmitUnaryMathCall(context, nameof(Math.Ceiling), toConvert); + break; + case 0b11: // Towards negative infinity + toConvert = EmitUnaryMathCall(context, nameof(Math.Floor), toConvert); + break; + } + + Operand asInteger = EmitSaturateFloatToInt(context, toConvert, unsigned); + + InsertScalar(context, op.Vd, asInteger); + } + } + + public static void Vcvt_TB(ArmEmitterContext context) + { + OpCode32SimdCvtTB op = (OpCode32SimdCvtTB)context.CurrOp; + + if (Optimizations.UseF16c) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + if (op.Op) + { + Operand res = ExtractScalar(context, op.Size == 1 ? OperandType.FP64 : OperandType.FP32, op.Vm); + if (op.Size == 1) + { + res = context.AddIntrinsic(Intrinsic.X86Cvtsd2ss, context.VectorZero(), res); + } + res = context.AddIntrinsic(Intrinsic.X86Vcvtps2ph, res, Const(X86GetRoundControl(FPRoundingMode.ToNearest))); + res = context.VectorExtract16(res, 0); + InsertScalar16(context, op.Vd, op.T, res); + } + else + { + Operand res = context.VectorCreateScalar(ExtractScalar16(context, op.Vm, op.T)); + res = context.AddIntrinsic(Intrinsic.X86Vcvtph2ps, res); + if (op.Size == 1) + { + res = context.AddIntrinsic(Intrinsic.X86Cvtss2sd, context.VectorZero(), res); + } + res = context.VectorExtract(op.Size == 1 ? OperandType.I64 : OperandType.I32, res, 0); + InsertScalar(context, op.Vd, res); + } + } + else + { + if (op.Op) + { + // Convert to half. + + Operand src = ExtractScalar(context, op.Size == 1 ? OperandType.FP64 : OperandType.FP32, op.Vm); + + MethodInfo method = op.Size == 1 + ? 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); + } + else + { + // Convert from half. + + Operand src = ExtractScalar16(context, op.Vm, op.T); + + MethodInfo method = op.Size == 1 + ? 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); + } + } + } + + // VRINTA/M/N/P (floating-point). + public static void Vrint_RM(ArmEmitterContext context) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + OperandType floatSize = op.RegisterSize == RegisterSize.Int64 ? OperandType.FP64 : OperandType.FP32; + + int rm = op.Opc2 & 3; + + if (Optimizations.UseAdvSimd) + { + Intrinsic inst = rm switch + { + 0b00 => Intrinsic.Arm64FrintaS, + 0b01 => Intrinsic.Arm64FrintnS, + 0b10 => Intrinsic.Arm64FrintpS, + 0b11 => Intrinsic.Arm64FrintmS, + _ => throw new InvalidOperationException($"{nameof(rm)} contains an invalid value: {rm}"), + }; + + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, inst); + } + else if (Optimizations.UseSse41) + { + EmitScalarUnaryOpSimd32(context, (m) => + { + FPRoundingMode roundMode = RMToRoundMode(rm); + + if (roundMode != FPRoundingMode.ToNearestAway) + { + Intrinsic inst = (op.Size & 1) == 0 ? Intrinsic.X86Roundss : Intrinsic.X86Roundsd; + return context.AddIntrinsic(inst, m, Const(X86GetRoundControl(roundMode))); + } + else + { + return EmitSse41RoundToNearestWithTiesToAwayOpF(context, m, scalar: true); + } + }); + } + else + { + Operand toConvert = ExtractScalar(context, floatSize, op.Vm); + + switch (rm) + { + case 0b00: // Away + toConvert = EmitRoundMathCall(context, MidpointRounding.AwayFromZero, toConvert); + break; + case 0b01: // Nearest + toConvert = EmitRoundMathCall(context, MidpointRounding.ToEven, toConvert); + break; + case 0b10: // Towards positive infinity + toConvert = EmitUnaryMathCall(context, nameof(Math.Ceiling), toConvert); + break; + case 0b11: // Towards negative infinity + toConvert = EmitUnaryMathCall(context, nameof(Math.Floor), toConvert); + break; + } + + InsertScalar(context, op.Vd, toConvert); + } + } + + // VRINTA (vector). + public static void Vrinta_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrintaS); + } + else + { + EmitVectorUnaryOpF32(context, (m) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, m)); + } + } + + // VRINTM (vector). + public static void Vrintm_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrintmS); + } + else if (Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + return context.AddIntrinsic(Intrinsic.X86Roundps, m, Const(X86GetRoundControl(FPRoundingMode.TowardsMinusInfinity))); + }); + } + else + { + EmitVectorUnaryOpF32(context, (m) => EmitUnaryMathCall(context, nameof(Math.Floor), m)); + } + } + + // VRINTN (vector). + public static void Vrintn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrintnS); + } + else if (Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + return context.AddIntrinsic(Intrinsic.X86Roundps, m, Const(X86GetRoundControl(FPRoundingMode.ToNearest))); + }); + } + else + { + EmitVectorUnaryOpF32(context, (m) => EmitRoundMathCall(context, MidpointRounding.ToEven, m)); + } + } + + // VRINTP (vector). + public static void Vrintp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrintpS); + } + else if (Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + return context.AddIntrinsic(Intrinsic.X86Roundps, m, Const(X86GetRoundControl(FPRoundingMode.TowardsPlusInfinity))); + }); + } + else + { + EmitVectorUnaryOpF32(context, (m) => EmitUnaryMathCall(context, nameof(Math.Ceiling), m)); + } + } + + // VRINTR (floating-point). + public static void Vrintr_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintiS); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + + // VRINTZ (floating-point). + public static void Vrint_Z(ArmEmitterContext context) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintzS); + } + else if (Optimizations.UseSse2) + { + EmitScalarUnaryOpSimd32(context, (m) => + { + Intrinsic inst = (op.Size & 1) == 0 ? Intrinsic.X86Roundss : Intrinsic.X86Roundsd; + return context.AddIntrinsic(inst, m, Const(X86GetRoundControl(FPRoundingMode.TowardsZero))); + }); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Truncate), op1)); + } + } + + // VRINTX (floating-point). + public static void Vrintx_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + 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) + { + Debug.Assert(value.Type == OperandType.I32 || value.Type == OperandType.I64); + + if (signed) + { + return context.ConvertToFP(type, value); + } + else + { + return context.ConvertToFPUI(type, value); + } + } + + private static void EmitSse41ConvertInt32(ArmEmitterContext context, FPRoundingMode roundMode, bool signed) + { + // A port of the similar round function in InstEmitSimdCvt. + OpCode32SimdCvtFI op = (OpCode32SimdCvtFI)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vm >> shift); + n = EmitSwapScalar(context, n, op.Vm, doubleSize); + + if (!doubleSize) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true); + } + + Operand zero = context.VectorZero(); + + Operand nCmp; + Operand nIntOrLong2 = default; + + if (!signed) + { + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + } + + int fpMaxVal = 0x4F000000; // 2.14748365E9f (2147483648) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + Operand nIntOrLong = context.AddIntrinsicInt(Intrinsic.X86Cvtss2si, nRes); + + if (!signed) + { + nRes = context.AddIntrinsic(Intrinsic.X86Subss, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + nIntOrLong2 = context.AddIntrinsicInt(Intrinsic.X86Cvtss2si, nRes); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nInt = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, nRes); + + Operand dRes; + if (signed) + { + dRes = context.BitwiseExclusiveOr(nIntOrLong, nInt); + } + else + { + dRes = context.BitwiseExclusiveOr(nIntOrLong2, nInt); + dRes = context.Add(dRes, nIntOrLong); + } + + InsertScalar(context, op.Vd, dRes); + } + else + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true); + } + + Operand zero = context.VectorZero(); + + Operand nCmp; + Operand nIntOrLong2 = default; + + if (!signed) + { + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + } + + long fpMaxVal = 0x41E0000000000000L; // 2147483648.0000000d (2147483648) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + Operand nIntOrLong = context.AddIntrinsicInt(Intrinsic.X86Cvtsd2si, nRes); + + if (!signed) + { + nRes = context.AddIntrinsic(Intrinsic.X86Subsd, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + nIntOrLong2 = context.AddIntrinsicInt(Intrinsic.X86Cvtsd2si, nRes); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nLong = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, nRes); + nLong = context.ConvertI64ToI32(nLong); + + Operand dRes; + if (signed) + { + dRes = context.BitwiseExclusiveOr(nIntOrLong, nLong); + } + else + { + dRes = context.BitwiseExclusiveOr(nIntOrLong2, nLong); + dRes = context.Add(dRes, nIntOrLong); + } + + InsertScalar(context, op.Vd, dRes); + } + } + + private static void EmitSse41ConvertVector32(ArmEmitterContext context, FPRoundingMode roundMode, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + EmitVectorUnaryOpSimd32(context, (n) => + { + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode))); + + Operand zero = context.VectorZero(); + Operand nCmp; + if (!signed) + { + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + } + + Operand fpMaxValMask = X86GetAllElements(context, 0x4F000000); // 2.14748365E9f (2147483648) + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + Operand nInt2 = default; + + if (!signed) + { + nRes = context.AddIntrinsic(Intrinsic.X86Subps, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + nInt2 = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + if (signed) + { + return context.AddIntrinsic(Intrinsic.X86Pxor, nInt, nRes); + } + else + { + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nInt2, nRes); + return context.AddIntrinsic(Intrinsic.X86Paddd, dRes, nInt); + } + } + else /* if (sizeF == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode))); + + Operand zero = context.VectorZero(); + Operand nCmp; + if (!signed) + { + nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + } + + Operand fpMaxValMask = X86GetAllElements(context, 0x43E0000000000000L); // 9.2233720368547760E18d (9223372036854775808) + + Operand nLong = InstEmit.EmitSse2CvtDoubleToInt64OpF(context, nRes, false); + Operand nLong2 = default; + + if (!signed) + { + nRes = context.AddIntrinsic(Intrinsic.X86Subpd, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + nLong2 = InstEmit.EmitSse2CvtDoubleToInt64OpF(context, nRes, false); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + if (signed) + { + return context.AddIntrinsic(Intrinsic.X86Pxor, nLong, nRes); + } + else + { + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nLong2, nRes); + return context.AddIntrinsic(Intrinsic.X86Paddq, dRes, nLong); + } + } + }); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHash.cs b/src/ARMeilleure/Instructions/InstEmitSimdHash.cs new file mode 100644 index 00000000..aee12d7d --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHash.cs @@ -0,0 +1,147 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + #region "Sha1" + public static void Sha1c_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + + Operand ne = context.VectorExtract(OperandType.I32, GetVec(op.Rn), 0); + + Operand m = GetVec(op.Rm); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashChoose)), d, ne, m); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha1h_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand ne = context.VectorExtract(OperandType.I32, GetVec(op.Rn), 0); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.FixedRotate)), ne); + + context.Copy(GetVec(op.Rd), context.VectorCreateScalar(res)); + } + + public static void Sha1m_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + + Operand ne = context.VectorExtract(OperandType.I32, GetVec(op.Rn), 0); + + Operand m = GetVec(op.Rm); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashMajority)), d, ne, m); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha1p_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + + Operand ne = context.VectorExtract(OperandType.I32, GetVec(op.Rn), 0); + + Operand m = GetVec(op.Rm); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashParity)), d, ne, m); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha1su0_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha1SchedulePart1)), d, n, m); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha1su1_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha1SchedulePart2)), d, n); + + context.Copy(GetVec(op.Rd), res); + } + #endregion + + #region "Sha256" + public static void Sha256h_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = InstEmitSimdHashHelper.EmitSha256h(context, d, n, m, part2: false); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha256h2_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = InstEmitSimdHashHelper.EmitSha256h(context, n, d, m, part2: true); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha256su0_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand res = InstEmitSimdHashHelper.EmitSha256su0(context, d, n); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha256su1_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = InstEmitSimdHashHelper.EmitSha256su1(context, d, n, m); + + context.Copy(GetVec(op.Rd), res); + } + #endregion + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHash32.cs b/src/ARMeilleure/Instructions/InstEmitSimdHash32.cs new file mode 100644 index 00000000..c2bb951a --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHash32.cs @@ -0,0 +1,64 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + #region "Sha256" + public static void Sha256h_V(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + + Operand res = InstEmitSimdHashHelper.EmitSha256h(context, d, n, m, part2: false); + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Sha256h2_V(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + + Operand res = InstEmitSimdHashHelper.EmitSha256h(context, n, d, m, part2: true); + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Sha256su0_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand m = GetVecA32(op.Qm); + + Operand res = InstEmitSimdHashHelper.EmitSha256su0(context, d, m); + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Sha256su1_V(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + + Operand res = InstEmitSimdHashHelper.EmitSha256su1(context, d, n, m); + + context.Copy(GetVecA32(op.Qd), res); + } + #endregion + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHashHelper.cs b/src/ARMeilleure/Instructions/InstEmitSimdHashHelper.cs new file mode 100644 index 00000000..a672b159 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHashHelper.cs @@ -0,0 +1,56 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitSimdHashHelper + { + public static Operand EmitSha256h(ArmEmitterContext context, Operand x, Operand y, Operand w, bool part2) + { + if (Optimizations.UseSha) + { + Operand src1 = context.AddIntrinsic(Intrinsic.X86Shufps, y, x, Const(0xbb)); + Operand src2 = context.AddIntrinsic(Intrinsic.X86Shufps, y, x, Const(0x11)); + Operand w2 = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, w, w); + + Operand round2 = context.AddIntrinsic(Intrinsic.X86Sha256Rnds2, src1, src2, w); + Operand round4 = context.AddIntrinsic(Intrinsic.X86Sha256Rnds2, src2, round2, w2); + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, round4, round2, Const(part2 ? 0x11 : 0xbb)); + + return res; + } + + String method = part2 ? nameof(SoftFallback.HashUpper) : nameof(SoftFallback.HashLower); + return context.Call(typeof(SoftFallback).GetMethod(method), x, y, w); + } + + public static Operand EmitSha256su0(ArmEmitterContext context, Operand x, Operand y) + { + if (Optimizations.UseSha) + { + return context.AddIntrinsic(Intrinsic.X86Sha256Msg1, x, y); + } + + return context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha256SchedulePart1)), x, y); + } + + public static Operand EmitSha256su1(ArmEmitterContext context, Operand x, Operand y, Operand z) + { + if (Optimizations.UseSha && Optimizations.UseSsse3) + { + Operand extr = context.AddIntrinsic(Intrinsic.X86Palignr, z, y, Const(4)); + Operand tmp = context.AddIntrinsic(Intrinsic.X86Paddd, extr, x); + + Operand res = context.AddIntrinsic(Intrinsic.X86Sha256Msg2, tmp, z); + + return res; + } + + return context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha256SchedulePart2)), x, y, z); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHelper.cs b/src/ARMeilleure/Instructions/InstEmitSimdHelper.cs new file mode 100644 index 00000000..abd0d9ac --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHelper.cs @@ -0,0 +1,2108 @@ +using ARMeilleure.CodeGen.X86; +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Reflection; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func1I = Func; + using Func2I = Func; + using Func3I = Func; + + static class InstEmitSimdHelper + { + #region "Masks" + public static readonly long[] EvenMasks = new long[] + { + 14L << 56 | 12L << 48 | 10L << 40 | 08L << 32 | 06L << 24 | 04L << 16 | 02L << 8 | 00L << 0, // B + 13L << 56 | 12L << 48 | 09L << 40 | 08L << 32 | 05L << 24 | 04L << 16 | 01L << 8 | 00L << 0, // H + 11L << 56 | 10L << 48 | 09L << 40 | 08L << 32 | 03L << 24 | 02L << 16 | 01L << 8 | 00L << 0, // S + }; + + public static readonly long[] OddMasks = new long[] + { + 15L << 56 | 13L << 48 | 11L << 40 | 09L << 32 | 07L << 24 | 05L << 16 | 03L << 8 | 01L << 0, // B + 15L << 56 | 14L << 48 | 11L << 40 | 10L << 32 | 07L << 24 | 06L << 16 | 03L << 8 | 02L << 0, // H + 15L << 56 | 14L << 48 | 13L << 40 | 12L << 32 | 07L << 24 | 06L << 16 | 05L << 8 | 04L << 0, // S + }; + + public const long ZeroMask = 128L << 56 | 128L << 48 | 128L << 40 | 128L << 32 | 128L << 24 | 128L << 16 | 128L << 8 | 128L << 0; + + public static ulong X86GetGf2p8LogicalShiftLeft(int shift) + { + ulong identity = (0b00000001UL << 56) | (0b00000010UL << 48) | (0b00000100UL << 40) | (0b00001000UL << 32) | + (0b00010000UL << 24) | (0b00100000UL << 16) | (0b01000000UL << 8) | (0b10000000UL << 0); + + return shift >= 0 ? identity >> (shift * 8) : identity << (-shift * 8); + } + #endregion + + #region "X86 SSE Intrinsics" + public static readonly Intrinsic[] X86PaddInstruction = new Intrinsic[] + { + Intrinsic.X86Paddb, + Intrinsic.X86Paddw, + Intrinsic.X86Paddd, + Intrinsic.X86Paddq, + }; + + public static readonly Intrinsic[] X86PcmpeqInstruction = new Intrinsic[] + { + Intrinsic.X86Pcmpeqb, + Intrinsic.X86Pcmpeqw, + Intrinsic.X86Pcmpeqd, + Intrinsic.X86Pcmpeqq, + }; + + public static readonly Intrinsic[] X86PcmpgtInstruction = new Intrinsic[] + { + Intrinsic.X86Pcmpgtb, + Intrinsic.X86Pcmpgtw, + Intrinsic.X86Pcmpgtd, + Intrinsic.X86Pcmpgtq, + }; + + public static readonly Intrinsic[] X86PmaxsInstruction = new Intrinsic[] + { + Intrinsic.X86Pmaxsb, + Intrinsic.X86Pmaxsw, + Intrinsic.X86Pmaxsd, + }; + + public static readonly Intrinsic[] X86PmaxuInstruction = new Intrinsic[] + { + Intrinsic.X86Pmaxub, + Intrinsic.X86Pmaxuw, + Intrinsic.X86Pmaxud, + }; + + public static readonly Intrinsic[] X86PminsInstruction = new Intrinsic[] + { + Intrinsic.X86Pminsb, + Intrinsic.X86Pminsw, + Intrinsic.X86Pminsd, + }; + + public static readonly Intrinsic[] X86PminuInstruction = new Intrinsic[] + { + Intrinsic.X86Pminub, + Intrinsic.X86Pminuw, + Intrinsic.X86Pminud, + }; + + public static readonly Intrinsic[] X86PmovsxInstruction = new Intrinsic[] + { + Intrinsic.X86Pmovsxbw, + Intrinsic.X86Pmovsxwd, + Intrinsic.X86Pmovsxdq, + }; + + public static readonly Intrinsic[] X86PmovzxInstruction = new Intrinsic[] + { + Intrinsic.X86Pmovzxbw, + Intrinsic.X86Pmovzxwd, + Intrinsic.X86Pmovzxdq, + }; + + public static readonly Intrinsic[] X86PsllInstruction = new Intrinsic[] + { + 0, + Intrinsic.X86Psllw, + Intrinsic.X86Pslld, + Intrinsic.X86Psllq, + }; + + public static readonly Intrinsic[] X86PsraInstruction = new Intrinsic[] + { + 0, + Intrinsic.X86Psraw, + Intrinsic.X86Psrad, + }; + + public static readonly Intrinsic[] X86PsrlInstruction = new Intrinsic[] + { + 0, + Intrinsic.X86Psrlw, + Intrinsic.X86Psrld, + Intrinsic.X86Psrlq, + }; + + public static readonly Intrinsic[] X86PsubInstruction = new Intrinsic[] + { + Intrinsic.X86Psubb, + Intrinsic.X86Psubw, + Intrinsic.X86Psubd, + Intrinsic.X86Psubq, + }; + + public static readonly Intrinsic[] X86PunpckhInstruction = new Intrinsic[] + { + Intrinsic.X86Punpckhbw, + Intrinsic.X86Punpckhwd, + Intrinsic.X86Punpckhdq, + Intrinsic.X86Punpckhqdq, + }; + + public static readonly Intrinsic[] X86PunpcklInstruction = new Intrinsic[] + { + Intrinsic.X86Punpcklbw, + Intrinsic.X86Punpcklwd, + Intrinsic.X86Punpckldq, + Intrinsic.X86Punpcklqdq, + }; + #endregion + + public static void EnterArmFpMode(EmitterContext context, Func 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 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); + } + + public static int GetImmShr(OpCodeSimdShImm op) + { + return (8 << (op.Size + 1)) - op.Imm; + } + + public static Operand X86GetScalar(ArmEmitterContext context, float value) + { + return X86GetScalar(context, BitConverter.SingleToInt32Bits(value)); + } + + public static Operand X86GetScalar(ArmEmitterContext context, double value) + { + return X86GetScalar(context, BitConverter.DoubleToInt64Bits(value)); + } + + public static Operand X86GetScalar(ArmEmitterContext context, int value) + { + return context.VectorCreateScalar(Const(value)); + } + + public static Operand X86GetScalar(ArmEmitterContext context, long value) + { + return context.VectorCreateScalar(Const(value)); + } + + public static Operand X86GetAllElements(ArmEmitterContext context, float value) + { + return X86GetAllElements(context, BitConverter.SingleToInt32Bits(value)); + } + + public static Operand X86GetAllElements(ArmEmitterContext context, double value) + { + return X86GetAllElements(context, BitConverter.DoubleToInt64Bits(value)); + } + + public static Operand X86GetAllElements(ArmEmitterContext context, short value) + { + ulong value1 = (ushort)value; + ulong value2 = value1 << 16 | value1; + ulong value4 = value2 << 32 | value2; + + return X86GetAllElements(context, (long)value4); + } + + public static Operand X86GetAllElements(ArmEmitterContext context, int value) + { + Operand vector = context.VectorCreateScalar(Const(value)); + + vector = context.AddIntrinsic(Intrinsic.X86Shufps, vector, vector, Const(0)); + + return vector; + } + + public static Operand X86GetAllElements(ArmEmitterContext context, long value) + { + Operand vector = context.VectorCreateScalar(Const(value)); + + vector = context.AddIntrinsic(Intrinsic.X86Movlhps, vector, vector); + + return vector; + } + + public static Operand X86GetElements(ArmEmitterContext context, long e1, long e0) + { + return X86GetElements(context, (ulong)e1, (ulong)e0); + } + + public static Operand X86GetElements(ArmEmitterContext context, ulong e1, ulong e0) + { + Operand vector0 = context.VectorCreateScalar(Const(e0)); + Operand vector1 = context.VectorCreateScalar(Const(e1)); + + return context.AddIntrinsic(Intrinsic.X86Punpcklqdq, vector0, vector1); + } + + public static int X86GetRoundControl(FPRoundingMode roundMode) + { + return roundMode switch + { +#pragma warning disable IDE0055 // Disable formatting + FPRoundingMode.ToNearest => 8 | 0, // even + FPRoundingMode.TowardsPlusInfinity => 8 | 2, + FPRoundingMode.TowardsMinusInfinity => 8 | 1, + FPRoundingMode.TowardsZero => 8 | 3, + _ => throw new ArgumentException($"Invalid rounding mode \"{roundMode}\"."), +#pragma warning restore IDE0055 + }; + } + + public static Operand EmitSse41RoundToNearestWithTiesToAwayOpF(ArmEmitterContext context, Operand n, bool scalar) + { + Debug.Assert(n.Type == OperandType.V128); + + Operand nCopy = context.Copy(n); + + Operand rC = Const(X86GetRoundControl(FPRoundingMode.TowardsZero)); + + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + if ((op.Size & 1) == 0) + { + Operand signMask = scalar ? X86GetScalar(context, int.MinValue) : X86GetAllElements(context, int.MinValue); + signMask = context.AddIntrinsic(Intrinsic.X86Pand, signMask, nCopy); + + // 0x3EFFFFFF == BitConverter.SingleToInt32Bits(0.5f) - 1 + Operand valueMask = scalar ? X86GetScalar(context, 0x3EFFFFFF) : X86GetAllElements(context, 0x3EFFFFFF); + valueMask = context.AddIntrinsic(Intrinsic.X86Por, valueMask, signMask); + + nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Addss : Intrinsic.X86Addps, nCopy, valueMask); + + nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Roundss : Intrinsic.X86Roundps, nCopy, rC); + } + else + { + Operand signMask = scalar ? X86GetScalar(context, long.MinValue) : X86GetAllElements(context, long.MinValue); + signMask = context.AddIntrinsic(Intrinsic.X86Pand, signMask, nCopy); + + // 0x3FDFFFFFFFFFFFFFL == BitConverter.DoubleToInt64Bits(0.5d) - 1L + Operand valueMask = scalar ? X86GetScalar(context, 0x3FDFFFFFFFFFFFFFL) : X86GetAllElements(context, 0x3FDFFFFFFFFFFFFFL); + valueMask = context.AddIntrinsic(Intrinsic.X86Por, valueMask, signMask); + + nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Addsd : Intrinsic.X86Addpd, nCopy, valueMask); + + nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Roundsd : Intrinsic.X86Roundpd, nCopy, rC); + } + + return nCopy; + } + + public static Operand EmitCountSetBits8(ArmEmitterContext context, Operand op) // "size" is 8 (SIMD&FP Inst.). + { + Debug.Assert(op.Type == OperandType.I32 || op.Type == OperandType.I64); + + Operand op0 = context.Subtract(op, context.BitwiseAnd(context.ShiftRightUI(op, Const(1)), Const(op.Type, 0x55L))); + + Operand c1 = Const(op.Type, 0x33L); + Operand op1 = context.Add(context.BitwiseAnd(context.ShiftRightUI(op0, Const(2)), c1), context.BitwiseAnd(op0, c1)); + + return context.BitwiseAnd(context.Add(op1, context.ShiftRightUI(op1, Const(4))), Const(op.Type, 0x0fL)); + } + + public static void EmitScalarUnaryOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + Operand res = context.AddIntrinsic(inst, n); + + if ((op.Size & 1) != 0) + { + res = context.VectorZeroUpper64(res); + } + else + { + res = context.VectorZeroUpper96(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitScalarBinaryOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + Operand res = context.AddIntrinsic(inst, n, m); + + if ((op.Size & 1) != 0) + { + res = context.VectorZeroUpper64(res); + } + else + { + res = context.VectorZeroUpper96(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorUnaryOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + Operand res = context.AddIntrinsic(inst, n); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + Operand res = context.AddIntrinsic(inst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static Operand EmitUnaryMathCall(ArmEmitterContext context, string name, Operand n) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + MethodInfo info = (op.Size & 1) == 0 + ? typeof(MathF).GetMethod(name, new Type[] { typeof(float) }) + : typeof(Math).GetMethod(name, new Type[] { typeof(double) }); + + return context.Call(info, n); + } + + public static Operand EmitRoundMathCall(ArmEmitterContext context, MidpointRounding roundMode, Operand n) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + string name = nameof(Math.Round); + + MethodInfo info = (op.Size & 1) == 0 + ? typeof(MathF).GetMethod(name, new Type[] { typeof(float), typeof(MidpointRounding) }) + : typeof(Math).GetMethod(name, new Type[] { typeof(double), typeof(MidpointRounding) }); + + return context.Call(info, n, Const((int)roundMode)); + } + + public static Operand EmitGetRoundingMode(ArmEmitterContext context) + { + Operand rMode = context.ShiftLeft(GetFpFlag(FPState.RMode1Flag), Const(1)); + rMode = context.BitwiseOr(rMode, GetFpFlag(FPState.RMode0Flag)); + + return rMode; + } + + public static Operand EmitRoundByRMode(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.FP32 || op.Type == OperandType.FP64); + + Operand lbl1 = Label(); + Operand lbl2 = Label(); + Operand lbl3 = Label(); + Operand lblEnd = Label(); + + Operand rN = Const((int)FPRoundingMode.ToNearest); + Operand rP = Const((int)FPRoundingMode.TowardsPlusInfinity); + Operand rM = Const((int)FPRoundingMode.TowardsMinusInfinity); + + Operand res = context.AllocateLocal(op.Type); + + Operand rMode = EmitGetRoundingMode(context); + + context.BranchIf(lbl1, rMode, rN, Comparison.NotEqual); + context.Copy(res, EmitRoundMathCall(context, MidpointRounding.ToEven, op)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lbl2, rMode, rP, Comparison.NotEqual); + context.Copy(res, EmitUnaryMathCall(context, nameof(Math.Ceiling), op)); + context.Branch(lblEnd); + + context.MarkLabel(lbl2); + context.BranchIf(lbl3, rMode, rM, Comparison.NotEqual); + context.Copy(res, EmitUnaryMathCall(context, nameof(Math.Floor), op)); + context.Branch(lblEnd); + + context.MarkLabel(lbl3); + context.Copy(res, EmitUnaryMathCall(context, nameof(Math.Truncate), op)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + public static Operand EmitSoftFloatCall(ArmEmitterContext context, string name, params Operand[] callArgs) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + MethodInfo info = (op.Size & 1) == 0 + ? typeof(SoftFloat32).GetMethod(name) + : typeof(SoftFloat64).GetMethod(name); + + context.ExitArmFpMode(); + context.StoreToContext(); + Operand res = context.Call(info, callArgs); + context.LoadFromContext(); + context.EnterArmFpMode(); + + return res; + } + + public static void EmitScalarBinaryOpByElemF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand m = context.VectorExtract(type, GetVec(op.Rm), op.Index); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(n, m), 0)); + } + + public static void EmitScalarTernaryOpByElemF(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand d = context.VectorExtract(type, GetVec(op.Rd), 0); + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand m = context.VectorExtract(type, GetVec(op.Rm), op.Index); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(d, n, m), 0)); + } + + public static void EmitScalarUnaryOpSx(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = EmitVectorExtractSx(context, op.Rn, 0, op.Size); + + Operand d = EmitVectorInsert(context, context.VectorZero(), emit(n), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarBinaryOpSx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = EmitVectorExtractSx(context, op.Rn, 0, op.Size); + Operand m = EmitVectorExtractSx(context, op.Rm, 0, op.Size); + + Operand d = EmitVectorInsert(context, context.VectorZero(), emit(n, m), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarUnaryOpZx(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = EmitVectorExtractZx(context, op.Rn, 0, op.Size); + + Operand d = EmitVectorInsert(context, context.VectorZero(), emit(n), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarBinaryOpZx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = EmitVectorExtractZx(context, op.Rn, 0, op.Size); + Operand m = EmitVectorExtractZx(context, op.Rm, 0, op.Size); + + Operand d = EmitVectorInsert(context, context.VectorZero(), emit(n, m), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarTernaryOpZx(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = EmitVectorExtractZx(context, op.Rd, 0, op.Size); + Operand n = EmitVectorExtractZx(context, op.Rn, 0, op.Size); + Operand m = EmitVectorExtractZx(context, op.Rm, 0, op.Size); + + d = EmitVectorInsert(context, context.VectorZero(), emit(d, n, m), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarUnaryOpF(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(n), 0)); + } + + public static void EmitScalarBinaryOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand m = context.VectorExtract(type, GetVec(op.Rm), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(n, m), 0)); + } + + public static void EmitScalarTernaryRaOpF(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand a = context.VectorExtract(type, GetVec(op.Ra), 0); + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand m = context.VectorExtract(type, GetVec(op.Rm), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(a, n, m), 0)); + } + + public static void EmitVectorUnaryOpF(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + + res = context.VectorInsert(res, emit(ne), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + Operand me = context.VectorExtract(type, GetVec(op.Rm), index); + + res = context.VectorInsert(res, emit(ne, me), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpF(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand de = context.VectorExtract(type, GetVec(op.Rd), index); + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + Operand me = context.VectorExtract(type, GetVec(op.Rm), index); + + res = context.VectorInsert(res, emit(de, ne, me), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpByElemF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + Operand me = context.VectorExtract(type, GetVec(op.Rm), op.Index); + + res = context.VectorInsert(res, emit(ne, me), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpByElemF(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand de = context.VectorExtract(type, GetVec(op.Rd), index); + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + Operand me = context.VectorExtract(type, GetVec(op.Rm), op.Index); + + res = context.VectorInsert(res, emit(de, ne, me), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorUnaryOpSx(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpSx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + Operand me = EmitVectorExtractSx(context, op.Rm, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpSx(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtractSx(context, op.Rd, index, op.Size); + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + Operand me = EmitVectorExtractSx(context, op.Rm, index, op.Size); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorUnaryOpZx(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpZx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpZx(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, index, op.Size); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpByElemSx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtractSx(context, op.Rm, op.Index, op.Size); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpByElemZx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtractZx(context, op.Rm, op.Index, op.Size); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpByElemZx(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtractZx(context, op.Rm, op.Index, op.Size); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorImmUnaryOp(ArmEmitterContext context, Func1I emit) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + Operand imm = Const(op.Immediate); + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + res = EmitVectorInsert(context, res, emit(imm), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorImmBinaryOp(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + Operand imm = Const(op.Immediate); + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + + res = EmitVectorInsert(context, res, emit(de, imm), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenRmBinaryOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenRmBinaryOp(context, emit, signed: true); + } + + public static void EmitVectorWidenRmBinaryOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenRmBinaryOp(context, emit, signed: false); + } + + private static void EmitVectorWidenRmBinaryOp(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size + 1, signed); + Operand me = EmitVectorExtract(context, op.Rm, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenRnRmBinaryOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenRnRmBinaryOp(context, emit, signed: true); + } + + public static void EmitVectorWidenRnRmBinaryOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenRnRmBinaryOp(context, emit, signed: false); + } + + private static void EmitVectorWidenRnRmBinaryOp(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + Operand me = EmitVectorExtract(context, op.Rm, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenRnRmTernaryOpSx(ArmEmitterContext context, Func3I emit) + { + EmitVectorWidenRnRmTernaryOp(context, emit, signed: true); + } + + public static void EmitVectorWidenRnRmTernaryOpZx(ArmEmitterContext context, Func3I emit) + { + EmitVectorWidenRnRmTernaryOp(context, emit, signed: false); + } + + private static void EmitVectorWidenRnRmTernaryOp(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract(context, op.Rd, index, op.Size + 1, signed); + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + Operand me = EmitVectorExtract(context, op.Rm, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenBinaryOpByElemSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenBinaryOpByElem(context, emit, signed: true); + } + + public static void EmitVectorWidenBinaryOpByElemZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenBinaryOpByElem(context, emit, signed: false); + } + + private static void EmitVectorWidenBinaryOpByElem(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtract(context, op.Rm, op.Index, op.Size, signed); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenTernaryOpByElemSx(ArmEmitterContext context, Func3I emit) + { + EmitVectorWidenTernaryOpByElem(context, emit, signed: true); + } + + public static void EmitVectorWidenTernaryOpByElemZx(ArmEmitterContext context, Func3I emit) + { + EmitVectorWidenTernaryOpByElem(context, emit, signed: false); + } + + private static void EmitVectorWidenTernaryOpByElem(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtract(context, op.Rm, op.Index, op.Size, signed); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract(context, op.Rd, index, op.Size + 1, signed); + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorPairwiseOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorPairwiseOp(context, emit, signed: true); + } + + public static void EmitVectorPairwiseOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorPairwiseOp(context, emit, signed: false); + } + + private static void EmitVectorPairwiseOp(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand n0 = EmitVectorExtract(context, op.Rn, pairIndex, op.Size, signed); + Operand n1 = EmitVectorExtract(context, op.Rn, pairIndex + 1, op.Size, signed); + + Operand m0 = EmitVectorExtract(context, op.Rm, pairIndex, op.Size, signed); + Operand m1 = EmitVectorExtract(context, op.Rm, pairIndex + 1, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(n0, n1), index, op.Size); + res = EmitVectorInsert(context, res, emit(m0, m1), pairs + index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitSsse3VectorPairwiseOp(ArmEmitterContext context, Intrinsic[] inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd64) + { + Operand zeroEvenMask = X86GetElements(context, ZeroMask, EvenMasks[op.Size]); + Operand zeroOddMask = X86GetElements(context, ZeroMask, OddMasks[op.Size]); + + Operand mN = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); // m:n + + Operand left = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroEvenMask); // 0:even from m:n + Operand right = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroOddMask); // 0:odd from m:n + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[op.Size], left, right)); + } + else if (op.Size < 3) + { + Operand oddEvenMask = X86GetElements(context, OddMasks[op.Size], EvenMasks[op.Size]); + + Operand oddEvenN = context.AddIntrinsic(Intrinsic.X86Pshufb, n, oddEvenMask); // odd:even from n + Operand oddEvenM = context.AddIntrinsic(Intrinsic.X86Pshufb, m, oddEvenMask); // odd:even from m + + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, oddEvenN, oddEvenM); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, oddEvenN, oddEvenM); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[op.Size], left, right)); + } + else + { + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, n, m); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[3], left, right)); + } + } + + public static void EmitVectorAcrossVectorOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorAcrossVectorOp(context, emit, signed: true, isLong: false); + } + + public static void EmitVectorAcrossVectorOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorAcrossVectorOp(context, emit, signed: false, isLong: false); + } + + public static void EmitVectorLongAcrossVectorOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorAcrossVectorOp(context, emit, signed: true, isLong: true); + } + + public static void EmitVectorLongAcrossVectorOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorAcrossVectorOp(context, emit, signed: false, isLong: true); + } + + private static void EmitVectorAcrossVectorOp( + ArmEmitterContext context, + Func2I emit, + bool signed, + bool isLong) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int elems = op.GetBytesCount() >> op.Size; + + Operand res = EmitVectorExtract(context, op.Rn, 0, op.Size, signed); + + for (int index = 1; index < elems; index++) + { + Operand n = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + + res = emit(res, n); + } + + int size = isLong ? op.Size + 1 : op.Size; + + Operand d = EmitVectorInsert(context, context.VectorZero(), res, 0, size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitVectorAcrossVectorOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Debug.Assert((op.Size & 1) == 0 && op.RegisterSize == RegisterSize.Simd128); + + Operand res = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), 0); + + for (int index = 1; index < 4; index++) + { + Operand n = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), index); + + res = emit(res, n); + } + + Operand d = context.VectorInsert(context.VectorZero(), res, 0); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitSse2VectorAcrossVectorOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Debug.Assert((op.Size & 1) == 0 && op.RegisterSize == RegisterSize.Simd128); + + const int SM0 = 0 << 6 | 0 << 4 | 0 << 2 | 0 << 0; + const int SM1 = 1 << 6 | 1 << 4 | 1 << 2 | 1 << 0; + const int SM2 = 2 << 6 | 2 << 4 | 2 << 2 | 2 << 0; + const int SM3 = 3 << 6 | 3 << 4 | 3 << 2 | 3 << 0; + + Operand nCopy = context.Copy(GetVec(op.Rn)); + + Operand part0 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, nCopy, Const(SM0)); + Operand part1 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, nCopy, Const(SM1)); + Operand part2 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, nCopy, Const(SM2)); + Operand part3 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, nCopy, Const(SM3)); + + Operand res = emit(emit(part0, part1), emit(part2, part3)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + + public static void EmitScalarPairwiseOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand ne0 = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand ne1 = context.VectorExtract(type, GetVec(op.Rn), 1); + + Operand res = context.VectorInsert(context.VectorZero(), emit(ne0, ne1), 0); + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitSse2ScalarPairwiseOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand op0, op1; + + if ((op.Size & 1) == 0) + { + const int SM0 = 2 << 6 | 2 << 4 | 2 << 2 | 0 << 0; + const int SM1 = 2 << 6 | 2 << 4 | 2 << 2 | 1 << 0; + + Operand zeroN = context.VectorZeroUpper64(n); + + op0 = context.AddIntrinsic(Intrinsic.X86Pshufd, zeroN, Const(SM0)); + op1 = context.AddIntrinsic(Intrinsic.X86Pshufd, zeroN, Const(SM1)); + } + else /* if ((op.Size & 1) == 1) */ + { + Operand zero = context.VectorZero(); + + op0 = context.AddIntrinsic(Intrinsic.X86Movlhps, n, zero); + op1 = context.AddIntrinsic(Intrinsic.X86Movhlps, zero, n); + } + + context.Copy(GetVec(op.Rd), emit(op0, op1)); + } + + public static void EmitVectorPairwiseOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int pairs = op.GetPairsCount() >> sizeF + 2; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand n0 = context.VectorExtract(type, GetVec(op.Rn), pairIndex); + Operand n1 = context.VectorExtract(type, GetVec(op.Rn), pairIndex + 1); + + Operand m0 = context.VectorExtract(type, GetVec(op.Rm), pairIndex); + Operand m1 = context.VectorExtract(type, GetVec(op.Rm), pairIndex + 1); + + res = context.VectorInsert(res, emit(n0, n1), index); + res = context.VectorInsert(res, emit(m0, m1), pairs + index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitSse2VectorPairwiseOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand nCopy = context.Copy(GetVec(op.Rn)); + Operand mCopy = context.Copy(GetVec(op.Rm)); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + if (op.RegisterSize == RegisterSize.Simd64) + { + Operand unpck = context.AddIntrinsic(Intrinsic.X86Unpcklps, nCopy, mCopy); + + Operand zero = context.VectorZero(); + + Operand part0 = context.AddIntrinsic(Intrinsic.X86Movlhps, unpck, zero); + Operand part1 = context.AddIntrinsic(Intrinsic.X86Movhlps, zero, unpck); + + context.Copy(GetVec(op.Rd), emit(part0, part1)); + } + else /* if (op.RegisterSize == RegisterSize.Simd128) */ + { + const int SM0 = 2 << 6 | 0 << 4 | 2 << 2 | 0 << 0; + const int SM1 = 3 << 6 | 1 << 4 | 3 << 2 | 1 << 0; + + Operand part0 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, mCopy, Const(SM0)); + Operand part1 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, mCopy, Const(SM1)); + + context.Copy(GetVec(op.Rd), emit(part0, part1)); + } + } + else /* if (sizeF == 1) */ + { + Operand part0 = context.AddIntrinsic(Intrinsic.X86Unpcklpd, nCopy, mCopy); + Operand part1 = context.AddIntrinsic(Intrinsic.X86Unpckhpd, nCopy, mCopy); + + context.Copy(GetVec(op.Rd), emit(part0, part1)); + } + } + + public enum CmpCondition + { + // Legacy Sse. + Equal = 0, // Ordered, non-signaling. + LessThan = 1, // Ordered, signaling. + LessThanOrEqual = 2, // Ordered, signaling. + UnorderedQ = 3, // Non-signaling. + NotLessThan = 5, // Unordered, signaling. + NotLessThanOrEqual = 6, // Unordered, signaling. + OrderedQ = 7, // Non-signaling. + + // Vex. + GreaterThanOrEqual = 13, // Ordered, signaling. + GreaterThan = 14, // Ordered, signaling. + OrderedS = 23, // Signaling. + } + + [Flags] + public enum SaturatingFlags + { + None = 0, + + ByElem = 1 << 0, + Scalar = 1 << 1, + Signed = 1 << 2, + + Add = 1 << 3, + Sub = 1 << 4, + + Accumulate = 1 << 5, + } + + public static void EmitScalarSaturatingUnaryOpSx(ArmEmitterContext context, Func1I emit) + { + EmitSaturatingUnaryOpSx(context, emit, SaturatingFlags.Scalar | SaturatingFlags.Signed); + } + + public static void EmitVectorSaturatingUnaryOpSx(ArmEmitterContext context, Func1I emit) + { + EmitSaturatingUnaryOpSx(context, emit, SaturatingFlags.Signed); + } + + public static void EmitSaturatingUnaryOpSx(ArmEmitterContext context, Func1I emit, SaturatingFlags flags) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + bool scalar = (flags & SaturatingFlags.Scalar) != 0; + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + Operand de; + + if (op.Size <= 2) + { + de = EmitSignedSrcSatQ(context, emit(ne), op.Size, signedDst: true); + } + else /* if (op.Size == 3) */ + { + de = EmitUnarySignedSatQAbsOrNeg(context, emit(ne)); + } + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitScalarSaturatingBinaryOpSx(ArmEmitterContext context, Func2I emit = null, SaturatingFlags flags = SaturatingFlags.None) + { + EmitSaturatingBinaryOp(context, emit, SaturatingFlags.Scalar | SaturatingFlags.Signed | flags); + } + + public static void EmitScalarSaturatingBinaryOpZx(ArmEmitterContext context, SaturatingFlags flags) + { + EmitSaturatingBinaryOp(context, null, SaturatingFlags.Scalar | flags); + } + + public static void EmitVectorSaturatingBinaryOpSx(ArmEmitterContext context, Func2I emit = null, SaturatingFlags flags = SaturatingFlags.None) + { + EmitSaturatingBinaryOp(context, emit, SaturatingFlags.Signed | flags); + } + + public static void EmitVectorSaturatingBinaryOpZx(ArmEmitterContext context, SaturatingFlags flags) + { + EmitSaturatingBinaryOp(context, null, flags); + } + + public static void EmitVectorSaturatingBinaryOpByElemSx(ArmEmitterContext context, Func2I emit) + { + EmitSaturatingBinaryOp(context, emit, SaturatingFlags.ByElem | SaturatingFlags.Signed); + } + + public static void EmitSaturatingBinaryOp(ArmEmitterContext context, Func2I emit, SaturatingFlags flags) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + bool byElem = (flags & SaturatingFlags.ByElem) != 0; + bool scalar = (flags & SaturatingFlags.Scalar) != 0; + bool signed = (flags & SaturatingFlags.Signed) != 0; + + bool add = (flags & SaturatingFlags.Add) != 0; + bool sub = (flags & SaturatingFlags.Sub) != 0; + + bool accumulate = (flags & SaturatingFlags.Accumulate) != 0; + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + if (add || sub) + { + for (int index = 0; index < elems; index++) + { + Operand de; + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + Operand me = EmitVectorExtract(context, ((OpCodeSimdReg)op).Rm, index, op.Size, signed); + + if (op.Size <= 2) + { + Operand temp = add ? context.Add(ne, me) : context.Subtract(ne, me); + + de = EmitSignedSrcSatQ(context, temp, op.Size, signedDst: signed); + } + else /* if (op.Size == 3) */ + { + if (add) + { + de = signed ? EmitBinarySignedSatQAdd(context, ne, me) : EmitBinaryUnsignedSatQAdd(context, ne, me); + } + else /* if (sub) */ + { + de = signed ? EmitBinarySignedSatQSub(context, ne, me) : EmitBinaryUnsignedSatQSub(context, ne, me); + } + } + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + } + else if (accumulate) + { + for (int index = 0; index < elems; index++) + { + Operand de; + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, !signed); + Operand me = EmitVectorExtract(context, op.Rd, index, op.Size, signed); + + if (op.Size <= 2) + { + Operand temp = context.Add(ne, me); + + de = EmitSignedSrcSatQ(context, temp, op.Size, signedDst: signed); + } + else /* if (op.Size == 3) */ + { + de = signed ? EmitBinarySignedSatQAcc(context, ne, me) : EmitBinaryUnsignedSatQAcc(context, ne, me); + } + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + } + else + { + Operand me = default; + + if (byElem) + { + OpCodeSimdRegElem opRegElem = (OpCodeSimdRegElem)op; + + me = EmitVectorExtract(context, opRegElem.Rm, opRegElem.Index, op.Size, signed); + } + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + + if (!byElem) + { + me = EmitVectorExtract(context, ((OpCodeSimdReg)op).Rm, index, op.Size, signed); + } + + Operand de = EmitSignedSrcSatQ(context, emit(ne, me), op.Size, signedDst: signed); + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + } + + context.Copy(GetVec(op.Rd), res); + } + + [Flags] + public enum SaturatingNarrowFlags + { + Scalar = 1 << 0, + SignedSrc = 1 << 1, + SignedDst = 1 << 2, + + ScalarSxSx = Scalar | SignedSrc | SignedDst, + ScalarSxZx = Scalar | SignedSrc, + ScalarZxZx = Scalar, + + VectorSxSx = SignedSrc | SignedDst, + VectorSxZx = SignedSrc, + VectorZxZx = 0, + } + + public static void EmitSaturatingNarrowOp(ArmEmitterContext context, SaturatingNarrowFlags flags) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + bool scalar = (flags & SaturatingNarrowFlags.Scalar) != 0; + bool signedSrc = (flags & SaturatingNarrowFlags.SignedSrc) != 0; + bool signedDst = (flags & SaturatingNarrowFlags.SignedDst) != 0; + + int elems = !scalar ? 8 >> op.Size : 1; + + int part = !scalar && (op.RegisterSize == RegisterSize.Simd128) ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size + 1, signedSrc); + + Operand temp = signedSrc + ? EmitSignedSrcSatQ(context, ne, op.Size, signedDst) + : EmitUnsignedSrcSatQ(context, ne, op.Size, signedDst); + + res = EmitVectorInsert(context, res, temp, part + index, op.Size); + } + + context.Copy(d, res); + } + + // long SignedSignSatQ(long op, int size); + public static Operand EmitSignedSignSatQ(ArmEmitterContext context, Operand op, int size) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand lbl1 = Label(); + Operand lblEnd = Label(); + + Operand zeroL = Const(0L); + Operand maxT = Const((1L << (eSize - 1)) - 1L); + Operand minT = Const(-(1L << (eSize - 1))); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), zeroL); + + context.BranchIf(lbl1, op, zeroL, Comparison.LessOrEqual); + context.Copy(res, maxT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, op, zeroL, Comparison.GreaterOrEqual); + context.Copy(res, minT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // private static ulong UnsignedSignSatQ(ulong op, int size); + public static Operand EmitUnsignedSignSatQ(ArmEmitterContext context, Operand op, int size) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand lblEnd = Label(); + + Operand zeroUL = Const(0UL); + Operand maxT = Const(ulong.MaxValue >> (64 - eSize)); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), zeroUL); + + context.BranchIf(lblEnd, op, zeroUL, Comparison.LessOrEqualUI); + context.Copy(res, maxT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // TSrc (16bit, 32bit, 64bit; signed) > TDst (8bit, 16bit, 32bit; signed, unsigned). + // long SignedSrcSignedDstSatQ(long op, int size); ulong SignedSrcUnsignedDstSatQ(long op, int size); + public static Operand EmitSignedSrcSatQ(ArmEmitterContext context, Operand op, int sizeDst, bool signedDst) + { + int eSizeDst = 8 << sizeDst; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSizeDst == 8 || eSizeDst == 16 || eSizeDst == 32); + + Operand lbl1 = Label(); + Operand lblEnd = Label(); + + Operand maxT = signedDst ? Const((1L << (eSizeDst - 1)) - 1L) : Const((1UL << eSizeDst) - 1UL); + Operand minT = signedDst ? Const(-(1L << (eSizeDst - 1))) : Const(0UL); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lbl1, op, maxT, Comparison.LessOrEqual); + context.Copy(res, maxT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, op, minT, Comparison.GreaterOrEqual); + context.Copy(res, minT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // TSrc (16bit, 32bit, 64bit; unsigned) > TDst (8bit, 16bit, 32bit; signed, unsigned). + // long UnsignedSrcSignedDstSatQ(ulong op, int size); ulong UnsignedSrcUnsignedDstSatQ(ulong op, int size); + public static Operand EmitUnsignedSrcSatQ(ArmEmitterContext context, Operand op, int sizeDst, bool signedDst) + { + int eSizeDst = 8 << sizeDst; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSizeDst == 8 || eSizeDst == 16 || eSizeDst == 32); + + Operand lblEnd = Label(); + + Operand maxT = signedDst ? Const((1L << (eSizeDst - 1)) - 1L) : Const((1UL << eSizeDst) - 1UL); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lblEnd, op, maxT, Comparison.LessOrEqualUI); + context.Copy(res, maxT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long UnarySignedSatQAbsOrNeg(long op); + private static Operand EmitUnarySignedSatQAbsOrNeg(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand minL = Const(long.MinValue); + Operand maxL = Const(long.MaxValue); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lblEnd, op, minL, Comparison.NotEqual); + context.Copy(res, maxL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long BinarySignedSatQAdd(long op1, long op2); + public static Operand EmitBinarySignedSatQAdd(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand minL = Const(long.MinValue); + Operand maxL = Const(long.MaxValue); + Operand zeroL = Const(0L); + + Operand add = context.Add(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), add); + + Operand left = context.BitwiseNot(context.BitwiseExclusiveOr(op1, op2)); + Operand right = context.BitwiseExclusiveOr(op1, add); + context.BranchIf(lblEnd, context.BitwiseAnd(left, right), zeroL, Comparison.GreaterOrEqual); + + Operand isPositive = context.ICompareGreaterOrEqual(op1, zeroL); + context.Copy(res, context.ConditionalSelect(isPositive, maxL, minL)); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // ulong BinaryUnsignedSatQAdd(ulong op1, ulong op2); + public static Operand EmitBinaryUnsignedSatQAdd(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand maxUL = Const(ulong.MaxValue); + + Operand add = context.Add(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), add); + + context.BranchIf(lblEnd, add, op1, Comparison.GreaterOrEqualUI); + context.Copy(res, maxUL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long BinarySignedSatQSub(long op1, long op2); + public static Operand EmitBinarySignedSatQSub(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand minL = Const(long.MinValue); + Operand maxL = Const(long.MaxValue); + Operand zeroL = Const(0L); + + Operand sub = context.Subtract(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), sub); + + Operand left = context.BitwiseExclusiveOr(op1, op2); + Operand right = context.BitwiseExclusiveOr(op1, sub); + context.BranchIf(lblEnd, context.BitwiseAnd(left, right), zeroL, Comparison.GreaterOrEqual); + + Operand isPositive = context.ICompareGreaterOrEqual(op1, zeroL); + context.Copy(res, context.ConditionalSelect(isPositive, maxL, minL)); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // ulong BinaryUnsignedSatQSub(ulong op1, ulong op2); + public static Operand EmitBinaryUnsignedSatQSub(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand zeroL = Const(0L); + + Operand sub = context.Subtract(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), sub); + + context.BranchIf(lblEnd, op1, op2, Comparison.GreaterOrEqualUI); + context.Copy(res, zeroL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long BinarySignedSatQAcc(ulong op1, long op2); + private static Operand EmitBinarySignedSatQAcc(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lbl1 = Label(); + Operand lbl2 = Label(); + Operand lblEnd = Label(); + + Operand maxL = Const(long.MaxValue); + Operand zeroL = Const(0L); + + Operand add = context.Add(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), add); + + context.BranchIf(lbl1, op1, maxL, Comparison.GreaterUI); + Operand notOp2AndRes = context.BitwiseAnd(context.BitwiseNot(op2), add); + context.BranchIf(lblEnd, notOp2AndRes, zeroL, Comparison.GreaterOrEqual); + context.Copy(res, maxL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lbl2, op2, zeroL, Comparison.Less); + context.Copy(res, maxL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl2); + context.BranchIf(lblEnd, add, maxL, Comparison.LessOrEqualUI); + context.Copy(res, maxL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // ulong BinaryUnsignedSatQAcc(long op1, ulong op2); + private static Operand EmitBinaryUnsignedSatQAcc(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lbl1 = Label(); + Operand lblEnd = Label(); + + Operand maxUL = Const(ulong.MaxValue); + Operand maxL = Const(long.MaxValue); + Operand zeroL = Const(0L); + + Operand add = context.Add(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), add); + + context.BranchIf(lbl1, op1, zeroL, Comparison.Less); + context.BranchIf(lblEnd, add, op1, Comparison.GreaterOrEqualUI); + context.Copy(res, maxUL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, op2, maxL, Comparison.GreaterUI); + context.BranchIf(lblEnd, add, zeroL, Comparison.GreaterOrEqual); + context.Copy(res, zeroL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + public static Operand EmitFloatAbs(ArmEmitterContext context, Operand value, bool single, bool vector) + { + Operand mask; + if (single) + { + mask = vector ? X86GetAllElements(context, -0f) : X86GetScalar(context, -0f); + } + else + { + mask = vector ? X86GetAllElements(context, -0d) : X86GetScalar(context, -0d); + } + + return context.AddIntrinsic(single ? Intrinsic.X86Andnps : Intrinsic.X86Andnpd, mask, value); + } + + public static Operand EmitVectorExtractSx(ArmEmitterContext context, int reg, int index, int size) + { + return EmitVectorExtract(context, reg, index, size, true); + } + + public static Operand EmitVectorExtractZx(ArmEmitterContext context, int reg, int index, int size) + { + return EmitVectorExtract(context, reg, index, size, false); + } + + public static Operand EmitVectorExtract(ArmEmitterContext context, int reg, int index, int size, bool signed) + { + ThrowIfInvalid(index, size); + + Operand res = default; + + switch (size) + { + case 0: + res = context.VectorExtract8(GetVec(reg), index); + break; + + case 1: + res = context.VectorExtract16(GetVec(reg), index); + break; + + case 2: + res = context.VectorExtract(OperandType.I32, GetVec(reg), index); + break; + + case 3: + res = context.VectorExtract(OperandType.I64, GetVec(reg), index); + break; + } + + if (signed) + { + switch (size) + { + case 0: + res = context.SignExtend8(OperandType.I64, res); + break; + case 1: + res = context.SignExtend16(OperandType.I64, res); + break; + case 2: + res = context.SignExtend32(OperandType.I64, res); + break; + } + } + else + { + switch (size) + { + case 0: + res = context.ZeroExtend8(OperandType.I64, res); + break; + case 1: + res = context.ZeroExtend16(OperandType.I64, res); + break; + case 2: + res = context.ZeroExtend32(OperandType.I64, res); + break; + } + } + + return res; + } + + public static Operand EmitVectorInsert(ArmEmitterContext context, Operand vector, Operand value, int index, int size) + { + ThrowIfInvalid(index, size); + + if (size < 3 && value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + + switch (size) + { + case 0: + vector = context.VectorInsert8(vector, value, index); + break; + case 1: + vector = context.VectorInsert16(vector, value, index); + break; + case 2: + vector = context.VectorInsert(vector, value, index); + break; + case 3: + vector = context.VectorInsert(vector, value, index); + break; + } + + return vector; + } + + public static void ThrowIfInvalid(int index, int size) + { + if ((uint)size > 3u) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if ((uint)index >= 16u >> size) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs b/src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs new file mode 100644 index 00000000..2f021a1a --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs @@ -0,0 +1,1320 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Reflection; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func1I = Func; + using Func2I = Func; + using Func3I = Func; + + static class InstEmitSimdHelper32 + { + public static (int, int) GetQuadwordAndSubindex(int index, RegisterSize size) + { + return size switch + { + RegisterSize.Simd128 => (index >> 1, 0), + RegisterSize.Simd64 or RegisterSize.Int64 => (index >> 1, index & 1), + RegisterSize.Int32 => (index >> 2, index & 3), + _ => throw new ArgumentException("Unrecognized Vector Register Size."), + }; + } + + public static Operand ExtractScalar(ArmEmitterContext context, OperandType type, int reg) + { + Debug.Assert(type != OperandType.V128); + + if (type == OperandType.FP64 || type == OperandType.I64) + { + // From dreg. + return context.VectorExtract(type, GetVecA32(reg >> 1), reg & 1); + } + else + { + // From sreg. + return context.VectorExtract(type, GetVecA32(reg >> 2), reg & 3); + } + } + + public static void InsertScalar(ArmEmitterContext context, int reg, Operand value) + { + Debug.Assert(value.Type != OperandType.V128); + + Operand vec, insert; + if (value.Type == OperandType.FP64 || value.Type == OperandType.I64) + { + // From dreg. + vec = GetVecA32(reg >> 1); + insert = context.VectorInsert(vec, value, reg & 1); + } + else + { + // From sreg. + vec = GetVecA32(reg >> 2); + insert = context.VectorInsert(vec, value, reg & 3); + } + + context.Copy(vec, insert); + } + + public static Operand ExtractScalar16(ArmEmitterContext context, int reg, bool top) + { + return context.VectorExtract16(GetVecA32(reg >> 2), ((reg & 3) << 1) | (top ? 1 : 0)); + } + + public static void InsertScalar16(ArmEmitterContext context, int reg, bool top, Operand value) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.I32); + + Operand vec, insert; + vec = GetVecA32(reg >> 2); + insert = context.VectorInsert16(vec, value, ((reg & 3) << 1) | (top ? 1 : 0)); + + context.Copy(vec, insert); + } + + public static Operand ExtractElement(ArmEmitterContext context, int reg, int size, bool signed) + { + return EmitVectorExtract32(context, reg >> (4 - size), reg & ((16 >> size) - 1), size, signed); + } + + public static void EmitVectorImmUnaryOp32(ArmEmitterContext context, Func1I emit) + { + IOpCode32SimdImm op = (IOpCode32SimdImm)context.CurrOp; + + Operand imm = Const(op.Immediate); + + int elems = op.Elems; + (int index, int subIndex) = GetQuadwordAndSubindex(op.Vd, op.RegisterSize); + + Operand vec = GetVecA32(index); + Operand res = vec; + + for (int item = 0; item < elems; item++) + { + res = EmitVectorInsert(context, res, emit(imm), item + subIndex * elems, op.Size); + } + + context.Copy(vec, res); + } + + public static void EmitScalarUnaryOpF32(ArmEmitterContext context, Func1I emit) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand m = ExtractScalar(context, type, op.Vm); + + InsertScalar(context, op.Vd, emit(m)); + } + + public static void EmitScalarBinaryOpF32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand n = ExtractScalar(context, type, op.Vn); + Operand m = ExtractScalar(context, type, op.Vm); + + InsertScalar(context, op.Vd, emit(n, m)); + } + + public static void EmitScalarBinaryOpI32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.I64 : OperandType.I32; + + if (op.Size < 2) + { + throw new NotSupportedException("Cannot perform a scalar SIMD operation on integers smaller than 32 bits."); + } + + Operand n = ExtractScalar(context, type, op.Vn); + Operand m = ExtractScalar(context, type, op.Vm); + + InsertScalar(context, op.Vd, emit(n, m)); + } + + public static void EmitScalarTernaryOpF32(ArmEmitterContext context, Func3I emit) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand a = ExtractScalar(context, type, op.Vd); + Operand n = ExtractScalar(context, type, op.Vn); + Operand m = ExtractScalar(context, type, op.Vm); + + InsertScalar(context, op.Vd, emit(a, n, m)); + } + + public static void EmitVectorUnaryOpF32(ArmEmitterContext context, Func1I emit) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand me = context.VectorExtract(type, GetVecA32(op.Qm), op.Fm + index); + + res = context.VectorInsert(res, emit(me), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorBinaryOpF32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> (sizeF + 2); + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVecA32(op.Qn), op.Fn + index); + Operand me = context.VectorExtract(type, GetVecA32(op.Qm), op.Fm + index); + + res = context.VectorInsert(res, emit(ne, me), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorTernaryOpF32(ArmEmitterContext context, Func3I emit) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand de = context.VectorExtract(type, GetVecA32(op.Qd), op.Fd + index); + Operand ne = context.VectorExtract(type, GetVecA32(op.Qn), op.Fn + index); + Operand me = context.VectorExtract(type, GetVecA32(op.Qm), op.Fm + index); + + res = context.VectorInsert(res, emit(de, ne, me), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + // Integer + + public static void EmitVectorUnaryAccumulateOpI32(ArmEmitterContext context, Func1I emit, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, context.Add(de, emit(me)), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorUnaryOpI32(ArmEmitterContext context, Func1I emit, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(me), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorBinaryOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, me), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorBinaryLongOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + if (op.Size == 2) + { + ne = signed ? context.SignExtend32(OperandType.I64, ne) : context.ZeroExtend32(OperandType.I64, ne); + me = signed ? context.SignExtend32(OperandType.I64, me) : context.ZeroExtend32(OperandType.I64, me); + } + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorBinaryWideOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size + 1, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + if (op.Size == 2) + { + me = signed ? context.SignExtend32(OperandType.I64, me) : context.ZeroExtend32(OperandType.I64, me); + } + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorImmBinaryQdQmOpZx32(ArmEmitterContext context, Func2I emit) + { + EmitVectorImmBinaryQdQmOpI32(context, emit, false); + } + + public static void EmitVectorImmBinaryQdQmOpSx32(ArmEmitterContext context, Func2I emit) + { + EmitVectorImmBinaryQdQmOpI32(context, emit, true); + } + + public static void EmitVectorImmBinaryQdQmOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, me), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorTernaryLongOpI32(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size + 1, signed); + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + if (op.Size == 2) + { + ne = signed ? context.SignExtend32(OperandType.I64, ne) : context.ZeroExtend32(OperandType.I64, ne); + me = signed ? context.SignExtend32(OperandType.I64, me) : context.ZeroExtend32(OperandType.I64, me); + } + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorTernaryOpI32(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size, signed); + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, ne, me), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorUnaryOpSx32(ArmEmitterContext context, Func1I emit) + { + EmitVectorUnaryOpI32(context, emit, true); + } + + public static void EmitVectorUnaryOpSx32(ArmEmitterContext context, Func1I emit, bool accumulate) + { + if (accumulate) + { + EmitVectorUnaryAccumulateOpI32(context, emit, true); + } + else + { + EmitVectorUnaryOpI32(context, emit, true); + } + } + + public static void EmitVectorBinaryOpSx32(ArmEmitterContext context, Func2I emit) + { + EmitVectorBinaryOpI32(context, emit, true); + } + + public static void EmitVectorTernaryOpSx32(ArmEmitterContext context, Func3I emit) + { + EmitVectorTernaryOpI32(context, emit, true); + } + + public static void EmitVectorUnaryOpZx32(ArmEmitterContext context, Func1I emit) + { + EmitVectorUnaryOpI32(context, emit, false); + } + + public static void EmitVectorUnaryOpZx32(ArmEmitterContext context, Func1I emit, bool accumulate) + { + if (accumulate) + { + EmitVectorUnaryAccumulateOpI32(context, emit, false); + } + else + { + EmitVectorUnaryOpI32(context, emit, false); + } + } + + public static void EmitVectorBinaryOpZx32(ArmEmitterContext context, Func2I emit) + { + EmitVectorBinaryOpI32(context, emit, false); + } + + public static void EmitVectorTernaryOpZx32(ArmEmitterContext context, Func3I emit) + { + EmitVectorTernaryOpI32(context, emit, false); + } + + // Vector by scalar + + public static void EmitVectorByScalarOpF32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + Operand m = ExtractScalar(context, type, op.Vm); + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVecA32(op.Qn), op.Fn + index); + + res = context.VectorInsert(res, emit(ne, m), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorByScalarOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand m = ExtractElement(context, op.Vm, op.Size, signed); + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, m), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorByScalarLongOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand m = ExtractElement(context, op.Vm, op.Size, signed); + + if (op.Size == 2) + { + m = signed ? context.SignExtend32(OperandType.I64, m) : context.ZeroExtend32(OperandType.I64, m); + } + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + + if (op.Size == 2) + { + ne = signed ? context.SignExtend32(OperandType.I64, ne) : context.ZeroExtend32(OperandType.I64, ne); + } + + res = EmitVectorInsert(context, res, emit(ne, m), index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorsByScalarOpF32(ArmEmitterContext context, Func3I emit) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + Operand m = ExtractScalar(context, type, op.Vm); + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand de = context.VectorExtract(type, GetVecA32(op.Qd), op.Fd + index); + Operand ne = context.VectorExtract(type, GetVecA32(op.Qn), op.Fn + index); + + res = context.VectorInsert(res, emit(de, ne, m), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorsByScalarOpI32(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand m = EmitVectorExtract32(context, op.Vm >> (4 - op.Size), op.Vm & ((1 << (4 - op.Size)) - 1), op.Size, signed); + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size, signed); + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, ne, m), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + // Pairwise + + public static void EmitVectorPairwiseOpF32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> (sizeF + 2); + int pairs = elems >> 1; + + Operand res = GetVecA32(op.Qd); + Operand mvec = GetVecA32(op.Qm); + Operand nvec = GetVecA32(op.Qn); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand n1 = context.VectorExtract(type, nvec, op.Fn + pairIndex); + Operand n2 = context.VectorExtract(type, nvec, op.Fn + pairIndex + 1); + + res = context.VectorInsert(res, emit(n1, n2), op.Fd + index); + + Operand m1 = context.VectorExtract(type, mvec, op.Fm + pairIndex); + Operand m2 = context.VectorExtract(type, mvec, op.Fm + pairIndex + 1); + + res = context.VectorInsert(res, emit(m1, m2), op.Fd + index + pairs); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorPairwiseOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + int elems = op.GetBytesCount() >> op.Size; + int pairs = elems >> 1; + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + Operand n1 = EmitVectorExtract32(context, op.Qn, op.In + pairIndex, op.Size, signed); + Operand n2 = EmitVectorExtract32(context, op.Qn, op.In + pairIndex + 1, op.Size, signed); + + Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed); + Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(n1, n2), op.Id + index, op.Size); + res = EmitVectorInsert(context, res, emit(m1, m2), op.Id + index + pairs, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorPairwiseLongOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int elems = (op.Q ? 16 : 8) >> op.Size; + int pairs = elems >> 1; + int id = (op.Vd & 1) * pairs; + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed); + Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed); + + if (op.Size == 2) + { + m1 = signed ? context.SignExtend32(OperandType.I64, m1) : context.ZeroExtend32(OperandType.I64, m1); + m2 = signed ? context.SignExtend32(OperandType.I64, m2) : context.ZeroExtend32(OperandType.I64, m2); + } + + res = EmitVectorInsert(context, res, emit(m1, m2), id + index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorPairwiseTernaryLongOpI32(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int elems = op.GetBytesCount() >> op.Size; + int pairs = elems >> 1; + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index * 2; + Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed); + Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed); + + if (op.Size == 2) + { + m1 = signed ? context.SignExtend32(OperandType.I64, m1) : context.ZeroExtend32(OperandType.I64, m1); + m2 = signed ? context.SignExtend32(OperandType.I64, m2) : context.ZeroExtend32(OperandType.I64, m2); + } + + Operand d1 = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size + 1, signed); + + res = EmitVectorInsert(context, res, emit(m1, m2, d1), op.Id + index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + // Narrow + + public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int elems = 8 >> op.Size; // Size contains the target element size. (for when it becomes a doubleword) + + Operand res = GetVecA32(op.Qd); + int id = (op.Vd & 1) << (3 - op.Size); // Target doubleword base. + + for (int index = 0; index < elems; index++) + { + Operand m = EmitVectorExtract32(context, op.Qm, index, op.Size + 1, signed); + + res = EmitVectorInsert(context, res, emit(m), id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + // Intrinsic Helpers + + public static Operand EmitMoveDoubleWordToSide(ArmEmitterContext context, Operand input, int originalV, int targetV) + { + Debug.Assert(input.Type == OperandType.V128); + + int originalSide = originalV & 1; + int targetSide = targetV & 1; + + if (originalSide == targetSide) + { + return input; + } + + if (targetSide == 1) + { + return context.AddIntrinsic(Intrinsic.X86Movlhps, input, input); // Low to high. + } + else + { + return context.AddIntrinsic(Intrinsic.X86Movhlps, input, input); // High to low. + } + } + + public static Operand EmitDoubleWordInsert(ArmEmitterContext context, Operand target, Operand value, int targetV) + { + Debug.Assert(target.Type == OperandType.V128 && value.Type == OperandType.V128); + + int targetSide = targetV & 1; + int shuffleMask = 2; + + if (targetSide == 1) + { + return context.AddIntrinsic(Intrinsic.X86Shufpd, target, value, Const(shuffleMask)); + } + else + { + return context.AddIntrinsic(Intrinsic.X86Shufpd, value, target, Const(shuffleMask)); + } + } + + public static Operand EmitScalarInsert(ArmEmitterContext context, Operand target, Operand value, int reg, bool doubleWidth) + { + Debug.Assert(target.Type == OperandType.V128 && value.Type == OperandType.V128); + + // Insert from index 0 in value to index in target. + int index = reg & (doubleWidth ? 1 : 3); + + if (doubleWidth) + { + if (index == 1) + { + return context.AddIntrinsic(Intrinsic.X86Movlhps, target, value); // Low to high. + } + else + { + return context.AddIntrinsic(Intrinsic.X86Shufpd, value, target, Const(2)); // Low to low, keep high from original. + } + } + else + { + if (Optimizations.UseSse41) + { + return context.AddIntrinsic(Intrinsic.X86Insertps, target, value, Const(index << 4)); + } + else + { + target = EmitSwapScalar(context, target, index, doubleWidth); // Swap value to replace into element 0. + target = context.AddIntrinsic(Intrinsic.X86Movss, target, value); // Move the value into element 0 of the vector. + return EmitSwapScalar(context, target, index, doubleWidth); // Swap new value back to the correct index. + } + } + } + + public static Operand EmitSwapScalar(ArmEmitterContext context, Operand target, int reg, bool doubleWidth) + { + // Index into 0, 0 into index. This swap happens at the start of an A32 scalar op if required. + int index = reg & (doubleWidth ? 1 : 3); + if (index == 0) + { + return target; + } + + if (doubleWidth) + { + int shuffleMask = 1; // Swap top and bottom. (b0 = 1, b1 = 0) + return context.AddIntrinsic(Intrinsic.X86Shufpd, target, target, Const(shuffleMask)); + } + else + { + int shuffleMask = (3 << 6) | (2 << 4) | (1 << 2) | index; // Swap index and 0. (others remain) + shuffleMask &= ~(3 << (index * 2)); + + return context.AddIntrinsic(Intrinsic.X86Shufps, target, target, Const(shuffleMask)); + } + } + + // Vector Operand Templates + + public static void EmitVectorUnaryOpSimd32(ArmEmitterContext context, Func1I vectorFunc) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + m = EmitMoveDoubleWordToSide(context, m, op.Vm, op.Vd); + } + + Operand res = vectorFunc(m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorUnaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + EmitVectorUnaryOpSimd32(context, (m) => context.AddIntrinsic(inst, m)); + } + + public static void EmitVectorBinaryOpSimd32(ArmEmitterContext context, Func2I vectorFunc, int side = -1) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + + if (side == -1) + { + side = op.Vd; + } + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, side); + m = EmitMoveDoubleWordToSide(context, m, op.Vm, side); + } + + Operand res = vectorFunc(n, m); + + if (!op.Q) // Register insert. + { + if (side != op.Vd) + { + res = EmitMoveDoubleWordToSide(context, res, side, op.Vd); + } + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorBinaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorTernaryOpSimd32(ArmEmitterContext context, Func3I vectorFunc) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + Operand initialD = d; + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, op.Vd); + m = EmitMoveDoubleWordToSide(context, m, op.Vm, op.Vd); + } + + Operand res = vectorFunc(d, n, m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, initialD, res, op.Vd); + } + + context.Copy(initialD, res); + } + + public static void EmitVectorTernaryOpF32(ArmEmitterContext context, Intrinsic inst32pt1, Intrinsic inst64pt1, Intrinsic inst32pt2, Intrinsic inst64pt2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Intrinsic inst1 = (op.Size & 1) != 0 ? inst64pt1 : inst32pt1; + Intrinsic inst2 = (op.Size & 1) != 0 ? inst64pt2 : inst32pt2; + + EmitVectorTernaryOpSimd32(context, (d, n, m) => + { + Operand res = context.AddIntrinsic(inst1, n, m); + return res = context.AddIntrinsic(inst2, d, res); + }); + } + + public static void EmitVectorTernaryOpF32(ArmEmitterContext context, Intrinsic inst32) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Debug.Assert((op.Size & 1) == 0); + + EmitVectorTernaryOpSimd32(context, (d, n, m) => + { + return context.AddIntrinsic(inst32, d, n, m); + }); + } + + public static void EmitScalarUnaryOpSimd32(ArmEmitterContext context, Func1I scalarFunc) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + + m = EmitSwapScalar(context, m, op.Vm, doubleSize); + + Operand res = scalarFunc(m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, d, res, op.Vd, doubleSize); + + context.Copy(d, res); + } + + public static void EmitScalarUnaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + EmitScalarUnaryOpSimd32(context, (m) => (inst == 0) ? m : context.AddIntrinsic(inst, m)); + } + + public static void EmitScalarBinaryOpSimd32(ArmEmitterContext context, Func2I scalarFunc) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vn >> shift); + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + + n = EmitSwapScalar(context, n, op.Vn, doubleSize); + m = EmitSwapScalar(context, m, op.Vm, doubleSize); + + Operand res = scalarFunc(n, m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, d, res, op.Vd, doubleSize); + + context.Copy(d, res); + } + + public static void EmitScalarBinaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + EmitScalarBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitScalarTernaryOpSimd32(ArmEmitterContext context, Func3I scalarFunc) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vn >> shift); + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + Operand initialD = d; + + n = EmitSwapScalar(context, n, op.Vn, doubleSize); + m = EmitSwapScalar(context, m, op.Vm, doubleSize); + d = EmitSwapScalar(context, d, op.Vd, doubleSize); + + Operand res = scalarFunc(d, n, m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, initialD, res, op.Vd, doubleSize); + + context.Copy(initialD, res); + } + + public static void EmitScalarTernaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + + Intrinsic inst = doubleSize ? inst64 : inst32; + + EmitScalarTernaryOpSimd32(context, (d, n, m) => + { + return context.AddIntrinsic(inst, d, n, m); + }); + } + + public static void EmitScalarTernaryOpF32( + ArmEmitterContext context, + Intrinsic inst32pt1, + Intrinsic inst64pt1, + Intrinsic inst32pt2, + Intrinsic inst64pt2, + bool isNegD = false) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + + Intrinsic inst1 = doubleSize ? inst64pt1 : inst32pt1; + Intrinsic inst2 = doubleSize ? inst64pt2 : inst32pt2; + + EmitScalarTernaryOpSimd32(context, (d, n, m) => + { + Operand res = context.AddIntrinsic(inst1, n, m); + + if (isNegD) + { + Operand mask = doubleSize + ? X86GetScalar(context, -0d) + : X86GetScalar(context, -0f); + + d = doubleSize + ? context.AddIntrinsic(Intrinsic.X86Xorpd, mask, d) + : context.AddIntrinsic(Intrinsic.X86Xorps, mask, d); + } + + return context.AddIntrinsic(inst2, d, res); + }); + } + + // By Scalar + + public static void EmitVectorByScalarOpSimd32(ArmEmitterContext context, Func2I vectorFunc) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand d = GetVecA32(op.Qd); + + int index = op.Vm & 3; + int dupeMask = (index << 6) | (index << 4) | (index << 2) | index; + Operand m = GetVecA32(op.Vm >> 2); + m = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(dupeMask)); + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, op.Vd); + } + + Operand res = vectorFunc(n, m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorByScalarOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + EmitVectorByScalarOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorsByScalarOpSimd32(ArmEmitterContext context, Func3I vectorFunc) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand d = GetVecA32(op.Qd); + Operand initialD = d; + + int index = op.Vm & 3; + int dupeMask = (index << 6) | (index << 4) | (index << 2) | index; + Operand m = GetVecA32(op.Vm >> 2); + m = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(dupeMask)); + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, op.Vd); + } + + Operand res = vectorFunc(d, n, m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, initialD, res, op.Vd); + } + + context.Copy(initialD, res); + } + + public static void EmitVectorsByScalarOpF32(ArmEmitterContext context, Intrinsic inst32pt1, Intrinsic inst64pt1, Intrinsic inst32pt2, Intrinsic inst64pt2) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Intrinsic inst1 = (op.Size & 1) != 0 ? inst64pt1 : inst32pt1; + Intrinsic inst2 = (op.Size & 1) != 0 ? inst64pt2 : inst32pt2; + + EmitVectorsByScalarOpSimd32(context, (d, n, m) => + { + Operand res = context.AddIntrinsic(inst1, n, m); + return res = context.AddIntrinsic(inst2, d, res); + }); + } + + // Pairwise + + public static void EmitSse2VectorPairwiseOpF32(ArmEmitterContext context, Intrinsic inst32) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorBinaryOpSimd32(context, (n, m) => + { + Operand unpck = context.AddIntrinsic(Intrinsic.X86Unpcklps, n, m); + + Operand part0 = unpck; + Operand part1 = context.AddIntrinsic(Intrinsic.X86Movhlps, unpck, unpck); + + return context.AddIntrinsic(inst32, part0, part1); + }, 0); + } + + public static void EmitSsse3VectorPairwiseOp32(ArmEmitterContext context, Intrinsic[] inst) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorBinaryOpSimd32(context, (n, m) => + { + if (op.RegisterSize == RegisterSize.Simd64) + { + Operand zeroEvenMask = X86GetElements(context, ZeroMask, EvenMasks[op.Size]); + Operand zeroOddMask = X86GetElements(context, ZeroMask, OddMasks[op.Size]); + + Operand mN = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); // m:n + + Operand left = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroEvenMask); // 0:even from m:n + Operand right = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroOddMask); // 0:odd from m:n + + return context.AddIntrinsic(inst[op.Size], left, right); + } + else if (op.Size < 3) + { + Operand oddEvenMask = X86GetElements(context, OddMasks[op.Size], EvenMasks[op.Size]); + + Operand oddEvenN = context.AddIntrinsic(Intrinsic.X86Pshufb, n, oddEvenMask); // odd:even from n + Operand oddEvenM = context.AddIntrinsic(Intrinsic.X86Pshufb, m, oddEvenMask); // odd:even from m + + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, oddEvenN, oddEvenM); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, oddEvenN, oddEvenM); + + return context.AddIntrinsic(inst[op.Size], left, right); + } + else + { + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, n, m); + + return context.AddIntrinsic(inst[3], left, right); + } + }, 0); + } + + // Generic Functions + + public static Operand EmitSoftFloatCallDefaultFpscr(ArmEmitterContext context, string name, params Operand[] callArgs) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + MethodInfo info = (op.Size & 1) == 0 + ? typeof(SoftFloat32).GetMethod(name) + : typeof(SoftFloat64).GetMethod(name); + + Array.Resize(ref callArgs, callArgs.Length + 1); + callArgs[^1] = Const(1); + + context.ExitArmFpMode(); + context.StoreToContext(); + Operand res = context.Call(info, callArgs); + context.LoadFromContext(); + context.EnterArmFpMode(); + + return res; + } + + public static Operand EmitVectorExtractSx32(ArmEmitterContext context, int reg, int index, int size) + { + return EmitVectorExtract32(context, reg, index, size, true); + } + + public static Operand EmitVectorExtractZx32(ArmEmitterContext context, int reg, int index, int size) + { + return EmitVectorExtract32(context, reg, index, size, false); + } + + public static Operand EmitVectorExtract32(ArmEmitterContext context, int reg, int index, int size, bool signed) + { + ThrowIfInvalid(index, size); + + Operand res = default; + + switch (size) + { + case 0: + res = context.VectorExtract8(GetVec(reg), index); + break; + + case 1: + res = context.VectorExtract16(GetVec(reg), index); + break; + + case 2: + res = context.VectorExtract(OperandType.I32, GetVec(reg), index); + break; + + case 3: + res = context.VectorExtract(OperandType.I64, GetVec(reg), index); + break; + } + + if (signed) + { + switch (size) + { + case 0: + res = context.SignExtend8(OperandType.I32, res); + break; + case 1: + res = context.SignExtend16(OperandType.I32, res); + break; + } + } + else + { + switch (size) + { + case 0: + res = context.ZeroExtend8(OperandType.I32, res); + break; + case 1: + res = context.ZeroExtend16(OperandType.I32, res); + break; + } + } + + return res; + } + + public static Operand EmitPolynomialMultiply(ArmEmitterContext context, Operand op1, Operand op2, int eSize) + { + Debug.Assert(eSize <= 32); + + Operand result = eSize == 32 ? Const(0L) : Const(0); + + if (eSize == 32) + { + op1 = context.ZeroExtend32(OperandType.I64, op1); + op2 = context.ZeroExtend32(OperandType.I64, op2); + } + + for (int i = 0; i < eSize; i++) + { + Operand mask = context.BitwiseAnd(op1, Const(op1.Type, 1L << i)); + + result = context.BitwiseExclusiveOr(result, context.Multiply(op2, mask)); + } + + return result; + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHelper32Arm64.cs b/src/ARMeilleure/Instructions/InstEmitSimdHelper32Arm64.cs new file mode 100644 index 00000000..568c0712 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHelper32Arm64.cs @@ -0,0 +1,372 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func1I = Func; + using Func2I = Func; + using Func3I = Func; + + static class InstEmitSimdHelper32Arm64 + { + // Intrinsic Helpers + + public static Operand EmitMoveDoubleWordToSide(ArmEmitterContext context, Operand input, int originalV, int targetV) + { + Debug.Assert(input.Type == OperandType.V128); + + int originalSide = originalV & 1; + int targetSide = targetV & 1; + + if (originalSide == targetSide) + { + return input; + } + + Intrinsic vType = Intrinsic.Arm64VDWord | Intrinsic.Arm64V128; + + if (targetSide == 1) + { + return context.AddIntrinsic(Intrinsic.Arm64DupVe | vType, input, Const(OperandType.I32, 0)); // Low to high. + } + else + { + return context.AddIntrinsic(Intrinsic.Arm64DupVe | vType, input, Const(OperandType.I32, 1)); // High to low. + } + } + + public static Operand EmitDoubleWordInsert(ArmEmitterContext context, Operand target, Operand value, int targetV) + { + Debug.Assert(target.Type == OperandType.V128 && value.Type == OperandType.V128); + + int targetSide = targetV & 1; + Operand idx = Const(targetSide); + + return context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, target, idx, value, idx); + } + + public static Operand EmitScalarInsert(ArmEmitterContext context, Operand target, Operand value, int reg, bool doubleWidth) + { + Debug.Assert(target.Type == OperandType.V128 && value.Type == OperandType.V128); + + // Insert from index 0 in value to index in target. + int index = reg & (doubleWidth ? 1 : 3); + + if (doubleWidth) + { + return context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, target, Const(index), value, Const(0)); + } + else + { + return context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VWord, target, Const(index), value, Const(0)); + } + } + + public static Operand EmitExtractScalar(ArmEmitterContext context, Operand target, int reg, bool doubleWidth) + { + int index = reg & (doubleWidth ? 1 : 3); + if (index == 0) + { + return target; // Element is already at index 0, so just return the vector directly. + } + + if (doubleWidth) + { + return context.AddIntrinsic(Intrinsic.Arm64DupSe | Intrinsic.Arm64VDWord, target, Const(1)); // Extract high (index 1). + } + else + { + return context.AddIntrinsic(Intrinsic.Arm64DupSe | Intrinsic.Arm64VWord, target, Const(index)); // Extract element at index. + } + } + + // Vector Operand Templates + + public static void EmitVectorUnaryOpSimd32(ArmEmitterContext context, Func1I vectorFunc) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + m = EmitMoveDoubleWordToSide(context, m, op.Vm, op.Vd); + } + + Operand res = vectorFunc(m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorUnaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitVectorUnaryOpSimd32(context, (m) => context.AddIntrinsic(inst, m)); + } + + public static void EmitVectorBinaryOpSimd32(ArmEmitterContext context, Func2I vectorFunc, int side = -1) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + + if (side == -1) + { + side = op.Vd; + } + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, side); + m = EmitMoveDoubleWordToSide(context, m, op.Vm, side); + } + + Operand res = vectorFunc(n, m); + + if (!op.Q) // Register insert. + { + if (side != op.Vd) + { + res = EmitMoveDoubleWordToSide(context, res, side, op.Vd); + } + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorBinaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorTernaryOpSimd32(ArmEmitterContext context, Func3I vectorFunc) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + Operand initialD = d; + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, op.Vd); + m = EmitMoveDoubleWordToSide(context, m, op.Vm, op.Vd); + } + + Operand res = vectorFunc(d, n, m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, initialD, res, op.Vd); + } + + context.Copy(initialD, res); + } + + public static void EmitVectorTernaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitVectorTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(inst, d, n, m)); + } + + public static void EmitScalarUnaryOpSimd32(ArmEmitterContext context, Func1I scalarFunc, bool doubleSize) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + int shift = doubleSize ? 1 : 2; + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + + m = EmitExtractScalar(context, m, op.Vm, doubleSize); + + Operand res = scalarFunc(m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, d, res, op.Vd, doubleSize); + + context.Copy(d, res); + } + + public static void EmitScalarUnaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + EmitScalarUnaryOpF32(context, inst, (op.Size & 1) != 0); + } + + public static void EmitScalarUnaryOpF32(ArmEmitterContext context, Intrinsic inst, bool doubleSize) + { + inst |= (doubleSize ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitScalarUnaryOpSimd32(context, (m) => (inst == 0) ? m : context.AddIntrinsic(inst, m), doubleSize); + } + + public static void EmitScalarBinaryOpSimd32(ArmEmitterContext context, Func2I scalarFunc) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vn >> shift); + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + + n = EmitExtractScalar(context, n, op.Vn, doubleSize); + m = EmitExtractScalar(context, m, op.Vm, doubleSize); + + Operand res = scalarFunc(n, m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, d, res, op.Vd, doubleSize); + + context.Copy(d, res); + } + + public static void EmitScalarBinaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitScalarBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitScalarTernaryOpSimd32(ArmEmitterContext context, Func3I scalarFunc) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vn >> shift); + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + Operand initialD = d; + + n = EmitExtractScalar(context, n, op.Vn, doubleSize); + m = EmitExtractScalar(context, m, op.Vm, doubleSize); + d = EmitExtractScalar(context, d, op.Vd, doubleSize); + + Operand res = scalarFunc(d, n, m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, initialD, res, op.Vd, doubleSize); + + context.Copy(initialD, res); + } + + public static void EmitScalarTernaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitScalarTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(inst, d, n, m)); + } + + // Pairwise + + public static void EmitVectorPairwiseOpF32(ArmEmitterContext context, Intrinsic inst32) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + inst32 |= Intrinsic.Arm64V64 | Intrinsic.Arm64VFloat; + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst32, n, m), 0); + } + + public static void EmitVcmpOrVcmpe(ArmEmitterContext context, bool signalNaNs) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + bool cmpWithZero = (op.Opc & 2) != 0; + + Intrinsic inst = signalNaNs ? Intrinsic.Arm64FcmpeS : Intrinsic.Arm64FcmpS; + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vd >> shift); + Operand m = GetVecA32(op.Vm >> shift); + + n = EmitExtractScalar(context, n, op.Vd, doubleSize); + m = cmpWithZero ? Const(0) : EmitExtractScalar(context, m, op.Vm, doubleSize); + + Operand nzcv = context.AddIntrinsicInt(inst, n, m); + + Operand one = Const(1); + + SetFpFlag(context, FPState.VFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(28)), one)); + SetFpFlag(context, FPState.CFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(29)), one)); + SetFpFlag(context, FPState.ZFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(30)), one)); + SetFpFlag(context, FPState.NFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(31)), one)); + } + + public static void EmitCmpOpF32(ArmEmitterContext context, CmpCondition cond, bool zero) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int sizeF = op.Size & 1; + + Intrinsic inst; + if (zero) + { + inst = cond switch + { + CmpCondition.Equal => Intrinsic.Arm64FcmeqVz, + CmpCondition.GreaterThan => Intrinsic.Arm64FcmgtVz, + CmpCondition.GreaterThanOrEqual => Intrinsic.Arm64FcmgeVz, + CmpCondition.LessThan => Intrinsic.Arm64FcmltVz, + CmpCondition.LessThanOrEqual => Intrinsic.Arm64FcmleVz, + _ => throw new InvalidOperationException(), + }; + } + else + { + inst = cond switch + { + CmpCondition.Equal => Intrinsic.Arm64FcmeqV, + CmpCondition.GreaterThan => Intrinsic.Arm64FcmgtV, + CmpCondition.GreaterThanOrEqual => Intrinsic.Arm64FcmgeV, + _ => throw new InvalidOperationException(), + }; + } + + inst |= (sizeF != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + + if (zero) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + return context.AddIntrinsic(inst, m); + }); + } + else + { + EmitVectorBinaryOpSimd32(context, (n, m) => + { + return context.AddIntrinsic(inst, n, m); + }); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHelperArm64.cs b/src/ARMeilleure/Instructions/InstEmitSimdHelperArm64.cs new file mode 100644 index 00000000..70dfc0fb --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHelperArm64.cs @@ -0,0 +1,720 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitSimdHelperArm64 + { + public static void EmitScalarUnaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitScalarUnaryOpFFromGp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitScalarUnaryOpFToGp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + SetIntOrZR(context, op.Rd, op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(inst, n) + : context.AddIntrinsicLong(inst, n)); + } + + public static void EmitScalarBinaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m)); + } + + public static void EmitScalarBinaryOpFByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m, Const(op.Index))); + } + + public static void EmitScalarTernaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + Operand a = GetVec(op.Ra); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, a, n, m)); + } + + public static void EmitScalarTernaryOpFRdByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m, Const(op.Index))); + } + + public static void EmitScalarUnaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitScalarBinaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m)); + } + + public static void EmitScalarBinaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n)); + } + + public static void EmitScalarTernaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(d, context.AddIntrinsic(inst, d, n, m)); + } + + public static void EmitScalarShiftBinaryOp(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(shift))); + } + + public static void EmitScalarShiftTernaryOpRd(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n, Const(shift))); + } + + public static void EmitScalarSaturatingShiftTernaryOpRd(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n, Const(shift))); + + context.SetPendingQcFlagSync(); + } + + public static void EmitScalarSaturatingUnaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + Operand result = context.AddIntrinsic(inst, n); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitScalarSaturatingBinaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + Operand result = context.AddIntrinsic(inst, n, m); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitScalarSaturatingBinaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + Operand result = context.AddIntrinsic(inst, d, n); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitScalarConvertBinaryOpF(ArmEmitterContext context, Intrinsic inst, int fBits) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(fBits))); + } + + public static void EmitScalarConvertBinaryOpFFromGp(ArmEmitterContext context, Intrinsic inst, int fBits) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(fBits))); + } + + public static void EmitScalarConvertBinaryOpFToGp(ArmEmitterContext context, Intrinsic inst, int fBits) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + SetIntOrZR(context, op.Rd, op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt(inst, n, Const(fBits)) + : context.AddIntrinsicLong(inst, n, Const(fBits))); + } + + public static void EmitVectorUnaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitVectorBinaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorBinaryOpFRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n)); + } + + public static void EmitVectorBinaryOpFByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m, Const(op.Index))); + } + + public static void EmitVectorTernaryOpFRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m)); + } + + public static void EmitVectorTernaryOpFRdByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m, Const(op.Index))); + } + + public static void EmitVectorUnaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitVectorBinaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorBinaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n)); + } + + public static void EmitVectorBinaryOpByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m, Const(op.Index))); + } + + public static void EmitVectorTernaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m)); + } + + public static void EmitVectorTernaryOpRdByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m, Const(op.Index))); + } + + public static void EmitVectorShiftBinaryOp(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(shift))); + } + + public static void EmitVectorShiftTernaryOpRd(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n, Const(shift))); + } + + public static void EmitVectorSaturatingShiftTernaryOpRd(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n, Const(shift))); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorSaturatingUnaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + Operand result = context.AddIntrinsic(inst, n); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorSaturatingBinaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + Operand result = context.AddIntrinsic(inst, n, m); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorSaturatingBinaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + Operand result = context.AddIntrinsic(inst, d, n); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorSaturatingBinaryOpByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + Operand result = context.AddIntrinsic(inst, n, m, Const(op.Index)); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorConvertBinaryOpF(ArmEmitterContext context, Intrinsic inst, int fBits) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(fBits))); + } + + public static void EmitVectorLookupTable(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdTbl op = (OpCodeSimdTbl)context.CurrOp; + + Operand[] operands = new Operand[op.Size + 1]; + + operands[op.Size] = GetVec(op.Rm); + + for (int index = 0; index < op.Size; index++) + { + operands[index] = GetVec((op.Rn + index) & 0x1F); + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, operands)); + } + + public static void EmitFcmpOrFcmpe(ArmEmitterContext context, bool signalNaNs) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + bool cmpWithZero = op is not OpCodeSimdFcond && op.Bit3; + + Intrinsic inst = signalNaNs ? Intrinsic.Arm64FcmpeS : Intrinsic.Arm64FcmpS; + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + Operand n = GetVec(op.Rn); + Operand m = cmpWithZero ? Const(0) : GetVec(op.Rm); + + Operand nzcv = context.AddIntrinsicInt(inst, n, m); + + Operand one = Const(1); + + SetFlag(context, PState.VFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(28)), one)); + SetFlag(context, PState.CFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(29)), one)); + SetFlag(context, PState.ZFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(30)), one)); + SetFlag(context, PState.NFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(31)), one)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdLogical.cs b/src/ARMeilleure/Instructions/InstEmitSimdLogical.cs new file mode 100644 index 00000000..ace8e4c5 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdLogical.cs @@ -0,0 +1,613 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void And_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64AndV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pand, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.BitwiseAnd(op1, op2)); + } + } + + public static void Bic_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64BicV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pandn, m, n); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + return context.BitwiseAnd(op1, context.BitwiseNot(op2)); + }); + } + } + + public static void Bic_Vi(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + int eSize = 8 << op.Size; + + Operand d = GetVec(op.Rd); + Operand imm = eSize switch + { + 16 => X86GetAllElements(context, (short)~op.Immediate), + 32 => X86GetAllElements(context, (int)~op.Immediate), + _ => throw new InvalidOperationException($"Invalid element size {eSize}."), + }; + + Operand res = context.AddIntrinsic(Intrinsic.X86Pand, d, imm); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorImmBinaryOp(context, (op1, op2) => + { + return context.BitwiseAnd(op1, context.BitwiseNot(op2)); + }); + } + } + + public static void Bif_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64BifV); + } + else + { + EmitBifBit(context, notRm: true); + } + } + + public static void Bit_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64BitV); + } + else + { + EmitBifBit(context, notRm: false); + } + } + + private static void EmitBifBit(ArmEmitterContext context, bool notRm) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSse2) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, d); + + if (notRm) + { + res = context.AddIntrinsic(Intrinsic.X86Pandn, m, res); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Pand, m, res); + } + + res = context.AddIntrinsic(Intrinsic.X86Pxor, d, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + Operand res = context.VectorZero(); + + int elems = op.RegisterSize == RegisterSize.Simd128 ? 2 : 1; + + for (int index = 0; index < elems; index++) + { + Operand d = EmitVectorExtractZx(context, op.Rd, index, 3); + Operand n = EmitVectorExtractZx(context, op.Rn, index, 3); + Operand m = EmitVectorExtractZx(context, op.Rm, index, 3); + + if (notRm) + { + m = context.BitwiseNot(m); + } + + Operand e = context.BitwiseExclusiveOr(d, n); + + e = context.BitwiseAnd(e, m); + e = context.BitwiseExclusiveOr(e, d); + + res = EmitVectorInsert(context, res, e, index, 3); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Bsl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64BslV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, m); + + res = context.AddIntrinsic(Intrinsic.X86Pand, res, d); + res = context.AddIntrinsic(Intrinsic.X86Pxor, res, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + EmitVectorTernaryOpZx(context, (op1, op2, op3) => + { + return context.BitwiseExclusiveOr( + context.BitwiseAnd(op1, + context.BitwiseExclusiveOr(op2, op3)), op3); + }); + } + } + + public static void Eor_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64EorV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.BitwiseExclusiveOr(op1, op2)); + } + } + + public static void Not_V(ArmEmitterContext context) + { + if (Optimizations.UseAvx512Ortho) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, n, Const(~0b10101010)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand mask = X86GetAllElements(context, -1L); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pandn, n, mask); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorUnaryOpZx(context, (op1) => context.BitwiseNot(op1)); + } + } + + public static void Orn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrnV); + } + else if (Optimizations.UseAvx512Ortho) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand mask = X86GetAllElements(context, -1L); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pandn, m, mask); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, n); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + return context.BitwiseOr(op1, context.BitwiseNot(op2)); + }); + } + } + + public static void Orr_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrrV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.BitwiseOr(op1, op2)); + } + } + + public static void Orr_Vi(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + int eSize = 8 << op.Size; + + Operand d = GetVec(op.Rd); + Operand imm = eSize switch + { + 16 => X86GetAllElements(context, (short)op.Immediate), + 32 => X86GetAllElements(context, (int)op.Immediate), + _ => throw new InvalidOperationException($"Invalid element size {eSize}."), + }; + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, d, imm); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorImmBinaryOp(context, (op1, op2) => context.BitwiseOr(op1, op2)); + } + } + + public static void Rbit_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (Optimizations.UseGfni) + { + const long BitMatrix = + (0b10000000L << 56) | + (0b01000000L << 48) | + (0b00100000L << 40) | + (0b00010000L << 32) | + (0b00001000L << 24) | + (0b00000100L << 16) | + (0b00000010L << 8) | + (0b00000001L << 0); + + Operand vBitMatrix = X86GetAllElements(context, BitMatrix); + + Operand res = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, GetVec(op.Rn), vBitMatrix, Const(0)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.VectorZero(); + int elems = op.RegisterSize == RegisterSize.Simd128 ? 16 : 8; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, 0); + + Operand de = EmitReverseBits8Op(context, ne); + + res = EmitVectorInsert(context, res, de, index, 0); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static Operand EmitReverseBits8Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaul)), Const(1)), + context.ShiftLeft(context.BitwiseAnd(op, Const(0x55ul)), Const(1))); + + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccul)), Const(2)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x33ul)), Const(2))); + + return context.BitwiseOr(context.ShiftRightUI(val, Const(4)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x0ful)), Const(4))); + } + + public static void Rev16_V(ArmEmitterContext context) + { + if (Optimizations.UseSsse3) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + const long MaskE0 = 06L << 56 | 07L << 48 | 04L << 40 | 05L << 32 | 02L << 24 | 03L << 16 | 00L << 8 | 01L << 0; + const long MaskE1 = 14L << 56 | 15L << 48 | 12L << 40 | 13L << 32 | 10L << 24 | 11L << 16 | 08L << 8 | 09L << 0; + + Operand mask = X86GetScalar(context, MaskE0); + + mask = EmitVectorInsert(context, mask, Const(MaskE1), 1, 3); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitRev_V(context, containerSize: 1); + } + } + + public static void Rev32_V(ArmEmitterContext context) + { + if (Optimizations.UseSsse3) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand mask; + + if (op.Size == 0) + { + const long MaskE0 = 04L << 56 | 05L << 48 | 06L << 40 | 07L << 32 | 00L << 24 | 01L << 16 | 02L << 8 | 03L << 0; + const long MaskE1 = 12L << 56 | 13L << 48 | 14L << 40 | 15L << 32 | 08L << 24 | 09L << 16 | 10L << 8 | 11L << 0; + + mask = X86GetScalar(context, MaskE0); + + mask = EmitVectorInsert(context, mask, Const(MaskE1), 1, 3); + } + else /* if (op.Size == 1) */ + { + const long MaskE0 = 05L << 56 | 04L << 48 | 07L << 40 | 06L << 32 | 01L << 24 | 00L << 16 | 03L << 8 | 02L << 0; + const long MaskE1 = 13L << 56 | 12L << 48 | 15L << 40 | 14L << 32 | 09L << 24 | 08L << 16 | 11L << 8 | 10L << 0; + + mask = X86GetScalar(context, MaskE0); + + mask = EmitVectorInsert(context, mask, Const(MaskE1), 1, 3); + } + + Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitRev_V(context, containerSize: 2); + } + } + + public static void Rev64_V(ArmEmitterContext context) + { + if (Optimizations.UseSsse3) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand mask; + + if (op.Size == 0) + { + const long MaskE0 = 00L << 56 | 01L << 48 | 02L << 40 | 03L << 32 | 04L << 24 | 05L << 16 | 06L << 8 | 07L << 0; + const long MaskE1 = 08L << 56 | 09L << 48 | 10L << 40 | 11L << 32 | 12L << 24 | 13L << 16 | 14L << 8 | 15L << 0; + + mask = X86GetScalar(context, MaskE0); + + mask = EmitVectorInsert(context, mask, Const(MaskE1), 1, 3); + } + else if (op.Size == 1) + { + const long MaskE0 = 01L << 56 | 00L << 48 | 03L << 40 | 02L << 32 | 05L << 24 | 04L << 16 | 07L << 8 | 06L << 0; + const long MaskE1 = 09L << 56 | 08L << 48 | 11L << 40 | 10L << 32 | 13L << 24 | 12L << 16 | 15L << 8 | 14L << 0; + + mask = X86GetScalar(context, MaskE0); + + mask = EmitVectorInsert(context, mask, Const(MaskE1), 1, 3); + } + else /* if (op.Size == 2) */ + { + const long MaskE0 = 03L << 56 | 02L << 48 | 01L << 40 | 00L << 32 | 07L << 24 | 06L << 16 | 05L << 8 | 04L << 0; + const long MaskE1 = 11L << 56 | 10L << 48 | 09L << 40 | 08L << 32 | 15L << 24 | 14L << 16 | 13L << 8 | 12L << 0; + + mask = X86GetScalar(context, MaskE0); + + mask = EmitVectorInsert(context, mask, Const(MaskE1), 1, 3); + } + + Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitRev_V(context, containerSize: 3); + } + } + + private static void EmitRev_V(ArmEmitterContext context, int containerSize) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + int containerMask = (1 << (containerSize - op.Size)) - 1; + + for (int index = 0; index < elems; index++) + { + int revIndex = index ^ containerMask; + + Operand ne = EmitVectorExtractZx(context, op.Rn, revIndex, op.Size); + + res = EmitVectorInsert(context, res, ne, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdLogical32.cs b/src/ARMeilleure/Instructions/InstEmitSimdLogical32.cs new file mode 100644 index 00000000..26d09344 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdLogical32.cs @@ -0,0 +1,278 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper32; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Vand_I(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64AndV | Intrinsic.Arm64V128, n, m)); + } + else if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.X86Pand, n, m)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseAnd(op1, op2)); + } + } + + public static void Vbic_I(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64BicV | Intrinsic.Arm64V128, n, m)); + } + else if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.X86Pandn, m, n)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseAnd(op1, context.BitwiseNot(op2))); + } + } + + public static void Vbic_II(ArmEmitterContext context) + { + OpCode32SimdImm op = (OpCode32SimdImm)context.CurrOp; + + long immediate = op.Immediate; + + // Replicate fields to fill the 64-bits, if size is < 64-bits. + switch (op.Size) + { + case 0: + immediate *= 0x0101010101010101L; + break; + case 1: + immediate *= 0x0001000100010001L; + break; + case 2: + immediate *= 0x0000000100000001L; + break; + } + + Operand imm = Const(immediate); + Operand res = GetVecA32(op.Qd); + + if (op.Q) + { + for (int elem = 0; elem < 2; elem++) + { + Operand de = EmitVectorExtractZx(context, op.Qd, elem, 3); + + res = EmitVectorInsert(context, res, context.BitwiseAnd(de, context.BitwiseNot(imm)), elem, 3); + } + } + else + { + Operand de = EmitVectorExtractZx(context, op.Qd, op.Vd & 1, 3); + + res = EmitVectorInsert(context, res, context.BitwiseAnd(de, context.BitwiseNot(imm)), op.Vd & 1, 3); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Vbif(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(Intrinsic.Arm64BifV | Intrinsic.Arm64V128, d, n, m)); + } + else + { + EmitBifBit(context, true); + } + } + + public static void Vbit(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(Intrinsic.Arm64BitV | Intrinsic.Arm64V128, d, n, m)); + } + else + { + EmitBifBit(context, false); + } + } + + public static void Vbsl(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(Intrinsic.Arm64BslV | Intrinsic.Arm64V128, d, n, m)); + } + else if (Optimizations.UseSse2) + { + EmitVectorTernaryOpSimd32(context, (d, n, m) => + { + Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, m); + res = context.AddIntrinsic(Intrinsic.X86Pand, res, d); + return context.AddIntrinsic(Intrinsic.X86Pxor, res, m); + }); + } + else + { + EmitVectorTernaryOpZx32(context, (op1, op2, op3) => + { + return context.BitwiseExclusiveOr( + context.BitwiseAnd(op1, + context.BitwiseExclusiveOr(op2, op3)), op3); + }); + } + } + + public static void Veor_I(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64EorV | Intrinsic.Arm64V128, n, m)); + } + else if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.X86Pxor, n, m)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseExclusiveOr(op1, op2)); + } + } + + public static void Vorn_I(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64OrnV | Intrinsic.Arm64V128, n, m)); + } + else if (Optimizations.UseAvx512Ortho) + { + EmitVectorBinaryOpSimd32(context, (n, m) => + { + return context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010)); + }); + } + else if (Optimizations.UseSse2) + { + Operand mask = context.VectorOne(); + + EmitVectorBinaryOpSimd32(context, (n, m) => + { + m = context.AddIntrinsic(Intrinsic.X86Pandn, m, mask); + return context.AddIntrinsic(Intrinsic.X86Por, n, m); + }); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseOr(op1, context.BitwiseNot(op2))); + } + } + + public static void Vorr_I(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64OrrV | Intrinsic.Arm64V128, n, m)); + } + else if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.X86Por, n, m)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseOr(op1, op2)); + } + } + + public static void Vorr_II(ArmEmitterContext context) + { + OpCode32SimdImm op = (OpCode32SimdImm)context.CurrOp; + + long immediate = op.Immediate; + + // Replicate fields to fill the 64-bits, if size is < 64-bits. + switch (op.Size) + { + case 0: + immediate *= 0x0101010101010101L; + break; + case 1: + immediate *= 0x0001000100010001L; + break; + case 2: + immediate *= 0x0000000100000001L; + break; + } + + Operand imm = Const(immediate); + Operand res = GetVecA32(op.Qd); + + if (op.Q) + { + for (int elem = 0; elem < 2; elem++) + { + Operand de = EmitVectorExtractZx(context, op.Qd, elem, 3); + + res = EmitVectorInsert(context, res, context.BitwiseOr(de, imm), elem, 3); + } + } + else + { + Operand de = EmitVectorExtractZx(context, op.Qd, op.Vd & 1, 3); + + res = EmitVectorInsert(context, res, context.BitwiseOr(de, imm), op.Vd & 1, 3); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Vtst(ArmEmitterContext context) + { + EmitVectorBinaryOpZx32(context, (op1, op2) => + { + Operand isZero = context.ICompareEqual(context.BitwiseAnd(op1, op2), Const(0)); + return context.ConditionalSelect(isZero, Const(0), Const(-1)); + }); + } + + private static void EmitBifBit(ArmEmitterContext context, bool notRm) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (Optimizations.UseSse2) + { + EmitVectorTernaryOpSimd32(context, (d, n, m) => + { + Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, d); + res = context.AddIntrinsic((notRm) ? Intrinsic.X86Pandn : Intrinsic.X86Pand, m, res); + return context.AddIntrinsic(Intrinsic.X86Pxor, d, res); + }); + } + else + { + EmitVectorTernaryOpZx32(context, (d, n, m) => + { + if (notRm) + { + m = context.BitwiseNot(m); + } + return context.BitwiseExclusiveOr( + context.BitwiseAnd(m, + context.BitwiseExclusiveOr(d, n)), d); + }); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdMemory.cs b/src/ARMeilleure/Instructions/InstEmitSimdMemory.cs new file mode 100644 index 00000000..dedf0fa0 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdMemory.cs @@ -0,0 +1,162 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System.Diagnostics; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Ld__Vms(ArmEmitterContext context) + { + EmitSimdMemMs(context, isLoad: true); + } + + public static void Ld__Vss(ArmEmitterContext context) + { + EmitSimdMemSs(context, isLoad: true); + } + + public static void St__Vms(ArmEmitterContext context) + { + EmitSimdMemMs(context, isLoad: false); + } + + public static void St__Vss(ArmEmitterContext context) + { + EmitSimdMemSs(context, isLoad: false); + } + + private static void EmitSimdMemMs(ArmEmitterContext context, bool isLoad) + { + OpCodeSimdMemMs op = (OpCodeSimdMemMs)context.CurrOp; + + Operand n = GetIntOrSP(context, op.Rn); + + long offset = 0; + +#pragma warning disable IDE0055 // Disable formatting + for (int rep = 0; rep < op.Reps; rep++) + for (int elem = 0; elem < op.Elems; elem++) + for (int sElem = 0; sElem < op.SElems; sElem++) + { + int rtt = (op.Rt + rep + sElem) & 0x1f; + + Operand tt = GetVec(rtt); + + Operand address = context.Add(n, Const(offset)); + + if (isLoad) + { + EmitLoadSimd(context, address, tt, rtt, elem, op.Size); + + if (op.RegisterSize == RegisterSize.Simd64 && elem == op.Elems - 1) + { + context.Copy(tt, context.VectorZeroUpper64(tt)); + } + } + else + { + EmitStoreSimd(context, address, rtt, elem, op.Size); + } + + offset += 1 << op.Size; + } +#pragma warning restore IDE0055 + + if (op.WBack) + { + EmitSimdMemWBack(context, offset); + } + } + + private static void EmitSimdMemSs(ArmEmitterContext context, bool isLoad) + { + OpCodeSimdMemSs op = (OpCodeSimdMemSs)context.CurrOp; + + Operand n = GetIntOrSP(context, op.Rn); + + long offset = 0; + + if (op.Replicate) + { + // Only loads uses the replicate mode. + Debug.Assert(isLoad, "Replicate mode is not valid for stores."); + + int elems = op.GetBytesCount() >> op.Size; + + for (int sElem = 0; sElem < op.SElems; sElem++) + { + int rt = (op.Rt + sElem) & 0x1f; + + Operand t = GetVec(rt); + + Operand address = context.Add(n, Const(offset)); + + for (int index = 0; index < elems; index++) + { + EmitLoadSimd(context, address, t, rt, index, op.Size); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + context.Copy(t, context.VectorZeroUpper64(t)); + } + + offset += 1 << op.Size; + } + } + else + { + for (int sElem = 0; sElem < op.SElems; sElem++) + { + int rt = (op.Rt + sElem) & 0x1f; + + Operand t = GetVec(rt); + + Operand address = context.Add(n, Const(offset)); + + if (isLoad) + { + EmitLoadSimd(context, address, t, rt, op.Index, op.Size); + } + else + { + EmitStoreSimd(context, address, rt, op.Index, op.Size); + } + + offset += 1 << op.Size; + } + } + + if (op.WBack) + { + EmitSimdMemWBack(context, offset); + } + } + + private static void EmitSimdMemWBack(ArmEmitterContext context, long offset) + { + OpCodeMemReg op = (OpCodeMemReg)context.CurrOp; + + Operand n = GetIntOrSP(context, op.Rn); + Operand m; + + if (op.Rm != RegisterAlias.Zr) + { + m = GetIntOrZR(context, op.Rm); + } + else + { + m = Const(offset); + } + + context.Copy(n, context.Add(n, m)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdMemory32.cs b/src/ARMeilleure/Instructions/InstEmitSimdMemory32.cs new file mode 100644 index 00000000..35c6dd32 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdMemory32.cs @@ -0,0 +1,352 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Vld1(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 1, true); + } + + public static void Vld2(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 2, true); + } + + public static void Vld3(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 3, true); + } + + public static void Vld4(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 4, true); + } + + public static void Vst1(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 1, false); + } + + public static void Vst2(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 2, false); + } + + public static void Vst3(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 3, false); + } + + public static void Vst4(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 4, false); + } + + public static void EmitVStoreOrLoadN(ArmEmitterContext context, int count, bool load) + { + if (context.CurrOp is OpCode32SimdMemSingle) + { + OpCode32SimdMemSingle op = (OpCode32SimdMemSingle)context.CurrOp; + + int eBytes = 1 << op.Size; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + + // TODO: Check alignment. + int offset = 0; + int d = op.Vd; + + for (int i = 0; i < count; i++) + { + // Accesses an element from a double simd register. + Operand address = context.Add(n, Const(offset)); + if (eBytes == 8) + { + if (load) + { + EmitDVectorLoad(context, address, d); + } + else + { + EmitDVectorStore(context, address, d); + } + } + else + { + int index = ((d & 1) << (3 - op.Size)) + op.Index; + if (load) + { + if (op.Replicate) + { + var regs = (count > 1) ? 1 : op.Increment; + for (int reg = 0; reg < regs; reg++) + { + int dreg = reg + d; + int rIndex = ((dreg & 1) << (3 - op.Size)); + int limit = rIndex + (1 << (3 - op.Size)); + + while (rIndex < limit) + { + EmitLoadSimd(context, address, GetVecA32(dreg >> 1), dreg >> 1, rIndex++, op.Size); + } + } + } + else + { + EmitLoadSimd(context, address, GetVecA32(d >> 1), d >> 1, index, op.Size); + } + } + else + { + EmitStoreSimd(context, address, d >> 1, index, op.Size); + } + } + offset += eBytes; + d += op.Increment; + } + + if (op.WBack) + { + if (op.RegisterIndex) + { + Operand m = GetIntA32(context, op.Rm); + SetIntA32(context, op.Rn, context.Add(n, m)); + } + else + { + SetIntA32(context, op.Rn, context.Add(n, Const(count * eBytes))); + } + } + } + else + { + OpCode32SimdMemPair op = (OpCode32SimdMemPair)context.CurrOp; + + int increment = count > 1 ? op.Increment : 1; + int eBytes = 1 << op.Size; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + int offset = 0; + int d = op.Vd; + + for (int reg = 0; reg < op.Regs; reg++) + { + for (int elem = 0; elem < op.Elems; elem++) + { + int elemD = d + reg; + for (int i = 0; i < count; i++) + { + // Accesses an element from a double simd register, + // add ebytes for each element. + Operand address = context.Add(n, Const(offset)); + int index = ((elemD & 1) << (3 - op.Size)) + elem; + if (eBytes == 8) + { + if (load) + { + EmitDVectorLoad(context, address, elemD); + } + else + { + EmitDVectorStore(context, address, elemD); + } + } + else + { + if (load) + { + EmitLoadSimd(context, address, GetVecA32(elemD >> 1), elemD >> 1, index, op.Size); + } + else + { + EmitStoreSimd(context, address, elemD >> 1, index, op.Size); + } + } + + offset += eBytes; + elemD += increment; + } + } + } + + if (op.WBack) + { + if (op.RegisterIndex) + { + Operand m = GetIntA32(context, op.Rm); + SetIntA32(context, op.Rn, context.Add(n, m)); + } + else + { + SetIntA32(context, op.Rn, context.Add(n, Const(count * 8 * op.Regs))); + } + } + } + } + + public static void Vldm(ArmEmitterContext context) + { + OpCode32SimdMemMult op = (OpCode32SimdMemMult)context.CurrOp; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + + Operand baseAddress = context.Add(n, Const(op.Offset)); + + bool writeBack = op.PostOffset != 0; + + if (writeBack) + { + SetIntA32(context, op.Rn, context.Add(n, Const(op.PostOffset))); + } + + int range = op.RegisterRange; + + int sReg = (op.DoubleWidth) ? (op.Vd << 1) : op.Vd; + int offset = 0; + int byteSize = 4; + + for (int num = 0; num < range; num++, sReg++) + { + Operand address = context.Add(baseAddress, Const(offset)); + Operand vec = GetVecA32(sReg >> 2); + + EmitLoadSimd(context, address, vec, sReg >> 2, sReg & 3, WordSizeLog2); + offset += byteSize; + } + } + + public static void Vstm(ArmEmitterContext context) + { + OpCode32SimdMemMult op = (OpCode32SimdMemMult)context.CurrOp; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + + Operand baseAddress = context.Add(n, Const(op.Offset)); + + bool writeBack = op.PostOffset != 0; + + if (writeBack) + { + SetIntA32(context, op.Rn, context.Add(n, Const(op.PostOffset))); + } + + int offset = 0; + + int range = op.RegisterRange; + int sReg = (op.DoubleWidth) ? (op.Vd << 1) : op.Vd; + int byteSize = 4; + + for (int num = 0; num < range; num++, sReg++) + { + Operand address = context.Add(baseAddress, Const(offset)); + + EmitStoreSimd(context, address, sReg >> 2, sReg & 3, WordSizeLog2); + + offset += byteSize; + } + } + + public static void Vldr(ArmEmitterContext context) + { + EmitVLoadOrStore(context, AccessType.Load); + } + + public static void Vstr(ArmEmitterContext context) + { + EmitVLoadOrStore(context, AccessType.Store); + } + + private static void EmitDVectorStore(ArmEmitterContext context, Operand address, int vecD) + { + int vecQ = vecD >> 1; + int vecSElem = (vecD & 1) << 1; + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + EmitStoreSimd(context, address, vecQ, vecSElem, WordSizeLog2); + EmitStoreSimd(context, context.Add(address, Const(4)), vecQ, vecSElem | 1, WordSizeLog2); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + EmitStoreSimd(context, address, vecQ, vecSElem | 1, WordSizeLog2); + EmitStoreSimd(context, context.Add(address, Const(4)), vecQ, vecSElem, WordSizeLog2); + + context.MarkLabel(lblEnd); + } + + private static void EmitDVectorLoad(ArmEmitterContext context, Operand address, int vecD) + { + int vecQ = vecD >> 1; + int vecSElem = (vecD & 1) << 1; + Operand vec = GetVecA32(vecQ); + + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + EmitLoadSimd(context, address, vec, vecQ, vecSElem, WordSizeLog2); + EmitLoadSimd(context, context.Add(address, Const(4)), vec, vecQ, vecSElem | 1, WordSizeLog2); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + EmitLoadSimd(context, address, vec, vecQ, vecSElem | 1, WordSizeLog2); + EmitLoadSimd(context, context.Add(address, Const(4)), vec, vecQ, vecSElem, WordSizeLog2); + + context.MarkLabel(lblEnd); + } + + private static void EmitVLoadOrStore(ArmEmitterContext context, AccessType accType) + { + OpCode32SimdMemImm op = (OpCode32SimdMemImm)context.CurrOp; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + Operand m = GetMemM(context, setCarry: false); + + Operand address = op.Add + ? context.Add(n, m) + : context.Subtract(n, m); + + int size = op.Size; + + if ((accType & AccessType.Load) != 0) + { + if (size == DWordSizeLog2) + { + EmitDVectorLoad(context, address, op.Vd); + } + else + { + Operand vec = GetVecA32(op.Vd >> 2); + EmitLoadSimd(context, address, vec, op.Vd >> 2, (op.Vd & 3) << (2 - size), size); + } + } + else + { + if (size == DWordSizeLog2) + { + EmitDVectorStore(context, address, op.Vd); + } + else + { + EmitStoreSimd(context, address, op.Vd >> 2, (op.Vd & 3) << (2 - size), size); + } + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdMove.cs b/src/ARMeilleure/Instructions/InstEmitSimdMove.cs new file mode 100644 index 00000000..85c98fe3 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdMove.cs @@ -0,0 +1,877 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Collections.Generic; +using System.Reflection; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + #region "Masks" + private static readonly long[] _masksE0_Uzp = new long[] + { + 13L << 56 | 09L << 48 | 05L << 40 | 01L << 32 | 12L << 24 | 08L << 16 | 04L << 8 | 00L << 0, + 11L << 56 | 10L << 48 | 03L << 40 | 02L << 32 | 09L << 24 | 08L << 16 | 01L << 8 | 00L << 0, + }; + + private static readonly long[] _masksE1_Uzp = new long[] + { + 15L << 56 | 11L << 48 | 07L << 40 | 03L << 32 | 14L << 24 | 10L << 16 | 06L << 8 | 02L << 0, + 15L << 56 | 14L << 48 | 07L << 40 | 06L << 32 | 13L << 24 | 12L << 16 | 05L << 8 | 04L << 0, + }; + #endregion + + public static void Dup_Gp(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + if (Optimizations.UseSse2) + { + switch (op.Size) + { + case 0: + n = context.ZeroExtend8(n.Type, n); + n = context.Multiply(n, Const(n.Type, 0x01010101)); + break; + case 1: + n = context.ZeroExtend16(n.Type, n); + n = context.Multiply(n, Const(n.Type, 0x00010001)); + break; + case 2: + n = context.ZeroExtend32(n.Type, n); + break; + } + + Operand res = context.VectorInsert(context.VectorZero(), n, 0); + + if (op.Size < 3) + { + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(0xf0)); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(0)); + } + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Movlhps, res, res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + res = EmitVectorInsert(context, res, n, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Dup_S(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand ne = EmitVectorExtractZx(context, op.Rn, op.DstIndex, op.Size); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), ne, 0, op.Size)); + } + + public static void Dup_V(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + if (Optimizations.UseSse2) + { + Operand res = GetVec(op.Rn); + + if (op.Size == 0) + { + if (op.DstIndex != 0) + { + res = context.AddIntrinsic(Intrinsic.X86Psrldq, res, Const(op.DstIndex)); + } + + res = context.AddIntrinsic(Intrinsic.X86Punpcklbw, res, res); + res = context.AddIntrinsic(Intrinsic.X86Punpcklwd, res, res); + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(0)); + } + else if (op.Size == 1) + { + if (op.DstIndex != 0) + { + res = context.AddIntrinsic(Intrinsic.X86Psrldq, res, Const(op.DstIndex * 2)); + } + + res = context.AddIntrinsic(Intrinsic.X86Punpcklwd, res, res); + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(0)); + } + else if (op.Size == 2) + { + int mask = op.DstIndex * 0b01010101; + + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(mask)); + } + else if (op.DstIndex == 0 && op.RegisterSize != RegisterSize.Simd64) + { + res = context.AddIntrinsic(Intrinsic.X86Movlhps, res, res); + } + else if (op.DstIndex == 1) + { + res = context.AddIntrinsic(Intrinsic.X86Movhlps, res, res); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = EmitVectorExtractZx(context, op.Rn, op.DstIndex, op.Size); + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + res = EmitVectorInsert(context, res, ne, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Ext_V(ArmEmitterContext context) + { + OpCodeSimdExt op = (OpCodeSimdExt)context.CurrOp; + + if (Optimizations.UseSse2) + { + Operand nShifted = GetVec(op.Rn); + + if (op.RegisterSize == RegisterSize.Simd64) + { + nShifted = context.VectorZeroUpper64(nShifted); + } + + nShifted = context.AddIntrinsic(Intrinsic.X86Psrldq, nShifted, Const(op.Imm4)); + + Operand mShifted = GetVec(op.Rm); + + mShifted = context.AddIntrinsic(Intrinsic.X86Pslldq, mShifted, Const(op.GetBytesCount() - op.Imm4)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + mShifted = context.VectorZeroUpper64(mShifted); + } + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, mShifted); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.VectorZero(); + + int bytes = op.GetBytesCount(); + + int position = op.Imm4 & (bytes - 1); + + for (int index = 0; index < bytes; index++) + { + int reg = op.Imm4 + index < bytes ? op.Rn : op.Rm; + + Operand e = EmitVectorExtractZx(context, reg, position, 0); + + position = (position + 1) & (bytes - 1); + + res = EmitVectorInsert(context, res, e, index, 0); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Fcsel_S(ArmEmitterContext context) + { + OpCodeSimdFcond op = (OpCodeSimdFcond)context.CurrOp; + + Operand lblTrue = Label(); + Operand lblEnd = Label(); + + Operand isTrue = InstEmitFlowHelper.GetCondTrue(context, op.Cond); + + context.BranchIfTrue(lblTrue, isTrue); + + OperandType type = op.Size == 0 ? OperandType.FP32 : OperandType.FP64; + + Operand me = context.VectorExtract(type, GetVec(op.Rm), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), me, 0)); + + context.Branch(lblEnd); + + context.MarkLabel(lblTrue); + + Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), ne, 0)); + + context.MarkLabel(lblEnd); + } + + public static void Fmov_Ftoi(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand ne = EmitVectorExtractZx(context, op.Rn, 0, op.Size + 2); + + SetIntOrZR(context, op.Rd, ne); + } + + public static void Fmov_Ftoi1(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand ne = EmitVectorExtractZx(context, op.Rn, 1, 3); + + SetIntOrZR(context, op.Rd, ne); + } + + public static void Fmov_Itof(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), n, 0, op.Size + 2)); + } + + public static void Fmov_Itof1(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetIntOrZR(context, op.Rn); + + context.Copy(d, EmitVectorInsert(context, d, n, 1, 3)); + } + + public static void Fmov_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + OperandType type = op.Size == 0 ? OperandType.FP32 : OperandType.FP64; + + Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), ne, 0)); + } + + public static void Fmov_Si(ArmEmitterContext context) + { + OpCodeSimdFmov op = (OpCodeSimdFmov)context.CurrOp; + + if (Optimizations.UseSse2) + { + if (op.Size == 0) + { + context.Copy(GetVec(op.Rd), X86GetScalar(context, (int)op.Immediate)); + } + else + { + context.Copy(GetVec(op.Rd), X86GetScalar(context, op.Immediate)); + } + } + else + { + Operand e = Const(op.Immediate); + + Operand res = context.VectorZero(); + + res = EmitVectorInsert(context, res, e, 0, op.Size + 2); + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Fmov_Vi(ArmEmitterContext context) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + if (Optimizations.UseSse2) + { + if (op.RegisterSize == RegisterSize.Simd128) + { + context.Copy(GetVec(op.Rd), X86GetAllElements(context, op.Immediate)); + } + else + { + context.Copy(GetVec(op.Rd), X86GetScalar(context, op.Immediate)); + } + } + else + { + Operand e = Const(op.Immediate); + + Operand res = context.VectorZero(); + + int elems = op.RegisterSize == RegisterSize.Simd128 ? 2 : 1; + + for (int index = 0; index < elems; index++) + { + res = EmitVectorInsert(context, res, e, index, 3); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Ins_Gp(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetIntOrZR(context, op.Rn); + + context.Copy(d, EmitVectorInsert(context, d, n, op.DstIndex, op.Size)); + } + + public static void Ins_V(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand ne = EmitVectorExtractZx(context, op.Rn, op.SrcIndex, op.Size); + + context.Copy(d, EmitVectorInsert(context, d, ne, op.DstIndex, op.Size)); + } + + public static void Movi_V(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + EmitSse2VectorMoviMvniOp(context, not: false); + } + else + { + EmitVectorImmUnaryOp(context, (op1) => op1); + } + } + + public static void Mvni_V(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + EmitSse2VectorMoviMvniOp(context, not: true); + } + else + { + EmitVectorImmUnaryOp(context, (op1) => context.BitwiseNot(op1)); + } + } + + public static void Smov_S(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand ne = EmitVectorExtractSx(context, op.Rn, op.DstIndex, op.Size); + + if (op.RegisterSize == RegisterSize.Simd64) + { + ne = context.ZeroExtend32(OperandType.I64, ne); + } + + SetIntOrZR(context, op.Rd, ne); + } + + public static void Tbl_V(ArmEmitterContext context) + { + EmitTableVectorLookup(context, isTbl: true); + } + + public static void Tbx_V(ArmEmitterContext context) + { + EmitTableVectorLookup(context, isTbl: false); + } + + public static void Trn1_V(ArmEmitterContext context) + { + EmitVectorTranspose(context, part: 0); + } + + public static void Trn2_V(ArmEmitterContext context) + { + EmitVectorTranspose(context, part: 1); + } + + public static void Umov_S(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand ne = EmitVectorExtractZx(context, op.Rn, op.DstIndex, op.Size); + + SetIntOrZR(context, op.Rd, ne); + } + + public static void Uzp1_V(ArmEmitterContext context) + { + EmitVectorUnzip(context, part: 0); + } + + public static void Uzp2_V(ArmEmitterContext context) + { + EmitVectorUnzip(context, part: 1); + } + + public static void Xtn_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (Optimizations.UseSsse3) + { + Operand d = GetVec(op.Rd); + + Operand res = context.VectorZeroUpper64(d); + + Operand mask = X86GetAllElements(context, EvenMasks[op.Size]); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pshufb, GetVec(op.Rn), mask); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 + ? Intrinsic.X86Movlhps + : Intrinsic.X86Movhlps; + + res = context.AddIntrinsic(movInst, res, res2); + + context.Copy(d, res); + } + else + { + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size + 1); + + res = EmitVectorInsert(context, res, ne, part + index, op.Size); + } + + context.Copy(d, res); + } + } + + public static void Zip1_V(ArmEmitterContext context) + { + EmitVectorZip(context, part: 0); + } + + public static void Zip2_V(ArmEmitterContext context) + { + EmitVectorZip(context, part: 1); + } + + private static void EmitSse2VectorMoviMvniOp(ArmEmitterContext context, bool not) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + long imm = op.Immediate; + + switch (op.Size) + { + case 0: + imm *= 0x01010101; + break; + case 1: + imm *= 0x00010001; + break; + } + + if (not) + { + imm = ~imm; + } + + Operand mask; + + if (op.Size < 3) + { + mask = X86GetAllElements(context, (int)imm); + } + else + { + mask = X86GetAllElements(context, imm); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + mask = context.VectorZeroUpper64(mask); + } + + context.Copy(GetVec(op.Rd), mask); + } + + private static void EmitTableVectorLookup(ArmEmitterContext context, bool isTbl) + { + OpCodeSimdTbl op = (OpCodeSimdTbl)context.CurrOp; + + if (Optimizations.UseSsse3) + { + Operand d = GetVec(op.Rd); + Operand m = GetVec(op.Rm); + + Operand res; + + Operand mask = X86GetAllElements(context, 0x0F0F0F0F0F0F0F0FL); + + // Fast path for single register table. + { + Operand n = GetVec(op.Rn); + + Operand mMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, m, mask); + mMask = context.AddIntrinsic(Intrinsic.X86Por, mMask, m); + + res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mMask); + } + + for (int index = 1; index < op.Size; index++) + { + Operand ni = GetVec((op.Rn + index) & 0x1F); + + Operand idxMask = X86GetAllElements(context, 0x1010101010101010L * index); + + Operand mSubMask = context.AddIntrinsic(Intrinsic.X86Psubb, m, idxMask); + + Operand mMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, mSubMask, mask); + mMask = context.AddIntrinsic(Intrinsic.X86Por, mMask, mSubMask); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pshufb, ni, mMask); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, res2); + } + + if (!isTbl) + { + Operand idxMask = X86GetAllElements(context, (0x1010101010101010L * op.Size) - 0x0101010101010101L); + Operand zeroMask = context.VectorZero(); + + Operand mPosMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, m, idxMask); + Operand mNegMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, zeroMask, m); + + Operand mMask = context.AddIntrinsic(Intrinsic.X86Por, mPosMask, mNegMask); + + Operand dMask = context.AddIntrinsic(Intrinsic.X86Pand, d, mMask); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, dMask); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + Operand d = GetVec(op.Rd); + + List args = new(); + + if (!isTbl) + { + args.Add(d); + } + + args.Add(GetVec(op.Rm)); + + args.Add(Const(op.RegisterSize == RegisterSize.Simd64 ? 8 : 16)); + + for (int index = 0; index < op.Size; index++) + { + args.Add(GetVec((op.Rn + index) & 0x1F)); + } + + MethodInfo info = null; + + if (isTbl) + { + switch (op.Size) + { + case 1: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl1)); + break; + case 2: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl2)); + break; + case 3: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl3)); + break; + case 4: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl4)); + break; + } + } + else + { + switch (op.Size) + { + case 1: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx1)); + break; + case 2: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx2)); + break; + case 3: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx3)); + break; + case 4: + info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx4)); + break; + } + } + + context.Copy(d, context.Call(info, args.ToArray())); + } + } + + private static void EmitVectorTranspose(ArmEmitterContext context, int part) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + Operand mask = default; + + if (op.Size < 3) + { + long maskE0 = EvenMasks[op.Size]; + long maskE1 = OddMasks[op.Size]; + + mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + } + + Operand n = GetVec(op.Rn); + + if (op.Size < 3) + { + n = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask); + } + + Operand m = GetVec(op.Rm); + + if (op.Size < 3) + { + m = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mask); + } + + Intrinsic punpckInst = part == 0 + ? X86PunpcklInstruction[op.Size] + : X86PunpckhInstruction[op.Size]; + + Operand res = context.AddIntrinsic(punpckInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand ne = EmitVectorExtractZx(context, op.Rn, pairIndex + part, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, pairIndex + part, op.Size); + + res = EmitVectorInsert(context, res, ne, pairIndex, op.Size); + res = EmitVectorInsert(context, res, me, pairIndex + 1, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitVectorUnzip(ArmEmitterContext context, int part) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + if (op.RegisterSize == RegisterSize.Simd128) + { + Operand mask = default; + + if (op.Size < 3) + { + long maskE0 = EvenMasks[op.Size]; + long maskE1 = OddMasks[op.Size]; + + mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + } + + Operand n = GetVec(op.Rn); + + if (op.Size < 3) + { + n = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask); + } + + Operand m = GetVec(op.Rm); + + if (op.Size < 3) + { + m = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mask); + } + + Intrinsic punpckInst = part == 0 + ? Intrinsic.X86Punpcklqdq + : Intrinsic.X86Punpckhqdq; + + Operand res = context.AddIntrinsic(punpckInst, n, m); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic punpcklInst = X86PunpcklInstruction[op.Size]; + + Operand res = context.AddIntrinsic(punpcklInst, n, m); + + if (op.Size < 2) + { + long maskE0 = _masksE0_Uzp[op.Size]; + long maskE1 = _masksE1_Uzp[op.Size]; + + Operand mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + + res = context.AddIntrinsic(Intrinsic.X86Pshufb, res, mask); + } + + Intrinsic punpckInst = part == 0 + ? Intrinsic.X86Punpcklqdq + : Intrinsic.X86Punpckhqdq; + + res = context.AddIntrinsic(punpckInst, res, context.VectorZero()); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + for (int index = 0; index < pairs; index++) + { + int idx = index << 1; + + Operand ne = EmitVectorExtractZx(context, op.Rn, idx + part, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, idx + part, op.Size); + + res = EmitVectorInsert(context, res, ne, index, op.Size); + res = EmitVectorInsert(context, res, me, pairs + index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitVectorZip(ArmEmitterContext context, int part) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSse2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + Intrinsic punpckInst = part == 0 + ? X86PunpcklInstruction[op.Size] + : X86PunpckhInstruction[op.Size]; + + Operand res = context.AddIntrinsic(punpckInst, n, m); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.AddIntrinsic(X86PunpcklInstruction[op.Size], n, m); + + Intrinsic punpckInst = part == 0 + ? Intrinsic.X86Punpcklqdq + : Intrinsic.X86Punpckhqdq; + + res = context.AddIntrinsic(punpckInst, res, context.VectorZero()); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + int baseIndex = part != 0 ? pairs : 0; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand ne = EmitVectorExtractZx(context, op.Rn, baseIndex + index, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, baseIndex + index, op.Size); + + res = EmitVectorInsert(context, res, ne, pairIndex, op.Size); + res = EmitVectorInsert(context, res, me, pairIndex + 1, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs b/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs new file mode 100644 index 00000000..fb2641f6 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs @@ -0,0 +1,675 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper32; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + #region "Masks" + // Same as InstEmitSimdMove, as the instructions do the same thing. + private static readonly long[] _masksE0_Uzp = new long[] + { + 13L << 56 | 09L << 48 | 05L << 40 | 01L << 32 | 12L << 24 | 08L << 16 | 04L << 8 | 00L << 0, + 11L << 56 | 10L << 48 | 03L << 40 | 02L << 32 | 09L << 24 | 08L << 16 | 01L << 8 | 00L << 0, + }; + + private static readonly long[] _masksE1_Uzp = new long[] + { + 15L << 56 | 11L << 48 | 07L << 40 | 03L << 32 | 14L << 24 | 10L << 16 | 06L << 8 | 02L << 0, + 15L << 56 | 14L << 48 | 07L << 40 | 06L << 32 | 13L << 24 | 12L << 16 | 05L << 8 | 04L << 0, + }; + #endregion + + public static void Vmov_I(ArmEmitterContext context) + { + EmitVectorImmUnaryOp32(context, (op1) => op1); + } + + public static void Vmvn_I(ArmEmitterContext context) + { + if (Optimizations.UseAvx512Ortho) + { + EmitVectorUnaryOpSimd32(context, (op1) => + { + return context.AddIntrinsic(Intrinsic.X86Vpternlogd, op1, op1, Const(0b01010101)); + }); + } + else if (Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (op1) => + { + Operand mask = X86GetAllElements(context, -1L); + return context.AddIntrinsic(Intrinsic.X86Pandn, op1, mask); + }); + } + else + { + EmitVectorUnaryOpZx32(context, (op1) => context.BitwiseNot(op1)); + } + } + + public static void Vmvn_II(ArmEmitterContext context) + { + EmitVectorImmUnaryOp32(context, (op1) => context.BitwiseNot(op1)); + } + + public static void Vmov_GS(ArmEmitterContext context) + { + OpCode32SimdMovGp op = (OpCode32SimdMovGp)context.CurrOp; + + Operand vec = GetVecA32(op.Vn >> 2); + if (op.Op == 1) + { + // To general purpose. + Operand value = context.VectorExtract(OperandType.I32, vec, op.Vn & 0x3); + SetIntA32(context, op.Rt, value); + } + else + { + // From general purpose. + Operand value = GetIntA32(context, op.Rt); + context.Copy(vec, context.VectorInsert(vec, value, op.Vn & 0x3)); + } + } + + public static void Vmov_G1(ArmEmitterContext context) + { + OpCode32SimdMovGpElem op = (OpCode32SimdMovGpElem)context.CurrOp; + + int index = op.Index + ((op.Vd & 1) << (3 - op.Size)); + if (op.Op == 1) + { + // To general purpose. + Operand value = EmitVectorExtract32(context, op.Vd >> 1, index, op.Size, !op.U); + SetIntA32(context, op.Rt, value); + } + else + { + // From general purpose. + Operand vec = GetVecA32(op.Vd >> 1); + Operand value = GetIntA32(context, op.Rt); + context.Copy(vec, EmitVectorInsert(context, vec, value, index, op.Size)); + } + } + + public static void Vmov_G2(ArmEmitterContext context) + { + OpCode32SimdMovGpDouble op = (OpCode32SimdMovGpDouble)context.CurrOp; + + Operand vec = GetVecA32(op.Vm >> 2); + int vm1 = op.Vm + 1; + bool sameOwnerVec = (op.Vm >> 2) == (vm1 >> 2); + Operand vec2 = sameOwnerVec ? vec : GetVecA32(vm1 >> 2); + if (op.Op == 1) + { + // To general purpose. + Operand lowValue = context.VectorExtract(OperandType.I32, vec, op.Vm & 3); + SetIntA32(context, op.Rt, lowValue); + + Operand highValue = context.VectorExtract(OperandType.I32, vec2, vm1 & 3); + SetIntA32(context, op.Rt2, highValue); + } + else + { + // From general purpose. + Operand lowValue = GetIntA32(context, op.Rt); + Operand resultVec = context.VectorInsert(vec, lowValue, op.Vm & 3); + + Operand highValue = GetIntA32(context, op.Rt2); + + if (sameOwnerVec) + { + context.Copy(vec, context.VectorInsert(resultVec, highValue, vm1 & 3)); + } + else + { + context.Copy(vec, resultVec); + context.Copy(vec2, context.VectorInsert(vec2, highValue, vm1 & 3)); + } + } + } + + public static void Vmov_GD(ArmEmitterContext context) + { + OpCode32SimdMovGpDouble op = (OpCode32SimdMovGpDouble)context.CurrOp; + + Operand vec = GetVecA32(op.Vm >> 1); + if (op.Op == 1) + { + // To general purpose. + Operand value = context.VectorExtract(OperandType.I64, vec, op.Vm & 1); + SetIntA32(context, op.Rt, context.ConvertI64ToI32(value)); + SetIntA32(context, op.Rt2, context.ConvertI64ToI32(context.ShiftRightUI(value, Const(32)))); + } + else + { + // From general purpose. + Operand lowValue = GetIntA32(context, op.Rt); + Operand highValue = GetIntA32(context, op.Rt2); + + Operand value = context.BitwiseOr( + context.ZeroExtend32(OperandType.I64, lowValue), + context.ShiftLeft(context.ZeroExtend32(OperandType.I64, highValue), Const(32))); + + context.Copy(vec, context.VectorInsert(vec, value, op.Vm & 1)); + } + } + + public static void Vmovl(ArmEmitterContext context) + { + OpCode32SimdLong op = (OpCode32SimdLong)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, !op.U); + + if (op.Size == 2) + { + if (op.U) + { + me = context.ZeroExtend32(OperandType.I64, me); + } + else + { + me = context.SignExtend32(OperandType.I64, me); + } + } + + res = EmitVectorInsert(context, res, me, index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Vswp(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + if (op.Q) + { + Operand temp = context.Copy(GetVecA32(op.Qd)); + + context.Copy(GetVecA32(op.Qd), GetVecA32(op.Qm)); + context.Copy(GetVecA32(op.Qm), temp); + } + else + { + Operand temp = ExtractScalar(context, OperandType.I64, op.Vd); + + InsertScalar(context, op.Vd, ExtractScalar(context, OperandType.I64, op.Vm)); + InsertScalar(context, op.Vm, temp); + } + } + + public static void Vtbl(ArmEmitterContext context) + { + OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp; + + bool extension = op.Opc == 1; + int length = op.Length + 1; + + if (Optimizations.UseSsse3) + { + Operand d = GetVecA32(op.Qd); + Operand m = EmitMoveDoubleWordToSide(context, GetVecA32(op.Qm), op.Vm, 0); + + Operand res; + Operand mask = X86GetAllElements(context, 0x0707070707070707L); + + // Fast path for single register table. + { + Operand n = EmitMoveDoubleWordToSide(context, GetVecA32(op.Qn), op.Vn, 0); + + Operand mMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, m, mask); + mMask = context.AddIntrinsic(Intrinsic.X86Por, mMask, m); + + res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mMask); + } + + for (int index = 1; index < length; index++) + { + int newVn = (op.Vn + index) & 0x1F; + (int qn, _) = GetQuadwordAndSubindex(newVn, op.RegisterSize); + Operand ni = EmitMoveDoubleWordToSide(context, GetVecA32(qn), newVn, 0); + + Operand idxMask = X86GetAllElements(context, 0x0808080808080808L * index); + + Operand mSubMask = context.AddIntrinsic(Intrinsic.X86Psubb, m, idxMask); + + Operand mMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, mSubMask, mask); + mMask = context.AddIntrinsic(Intrinsic.X86Por, mMask, mSubMask); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pshufb, ni, mMask); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, res2); + } + + if (extension) + { + Operand idxMask = X86GetAllElements(context, (0x0808080808080808L * length) - 0x0101010101010101L); + Operand zeroMask = context.VectorZero(); + + Operand mPosMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, m, idxMask); + Operand mNegMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, zeroMask, m); + + Operand mMask = context.AddIntrinsic(Intrinsic.X86Por, mPosMask, mNegMask); + + Operand dMask = context.AddIntrinsic(Intrinsic.X86Pand, EmitMoveDoubleWordToSide(context, d, op.Vd, 0), mMask); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, dMask); + } + + res = EmitMoveDoubleWordToSide(context, res, 0, op.Vd); + + context.Copy(d, EmitDoubleWordInsert(context, d, res, op.Vd)); + } + else + { + int elems = op.GetBytesCount() >> op.Size; + + (int Qx, int Ix)[] tableTuples = new (int, int)[length]; + for (int i = 0; i < length; i++) + { + tableTuples[i] = GetQuadwordAndSubindex(op.Vn + i, op.RegisterSize); + } + + int byteLength = length * 8; + + Operand res = GetVecA32(op.Qd); + Operand m = GetVecA32(op.Qm); + + for (int index = 0; index < elems; index++) + { + Operand selectedIndex = context.ZeroExtend8(OperandType.I32, context.VectorExtract8(m, index + op.Im)); + + Operand inRange = context.ICompareLess(selectedIndex, Const(byteLength)); + Operand elemRes = default; // Note: This is I64 for ease of calculation. + + // TODO: Branching rather than conditional select. + + // Get indexed byte. + // To simplify (ha) the il, we get bytes from every vector and use a nested conditional select to choose the right result. + // This does have to extract `length` times for every element but certainly not as bad as it could be. + + // Which vector number is the index on. + Operand vecIndex = context.ShiftRightUI(selectedIndex, Const(3)); + // What should we shift by to extract it. + Operand subVecIndexShift = context.ShiftLeft(context.BitwiseAnd(selectedIndex, Const(7)), Const(3)); + + for (int i = 0; i < length; i++) + { + (int qx, int ix) = tableTuples[i]; + // Get the whole vector, we'll get a byte out of it. + Operand lookupResult; + if (qx == op.Qd) + { + // Result contains the current state of the vector. + lookupResult = context.VectorExtract(OperandType.I64, res, ix); + } + else + { + lookupResult = EmitVectorExtract32(context, qx, ix, 3, false); // I64 + } + + lookupResult = context.ShiftRightUI(lookupResult, subVecIndexShift); // Get the relevant byte from this vector. + + if (i == 0) + { + elemRes = lookupResult; // First result is always default. + } + else + { + Operand isThisElem = context.ICompareEqual(vecIndex, Const(i)); + elemRes = context.ConditionalSelect(isThisElem, lookupResult, elemRes); + } + } + + Operand fallback = (extension) ? context.ZeroExtend32(OperandType.I64, EmitVectorExtract32(context, op.Qd, index + op.Id, 0, false)) : Const(0L); + + res = EmitVectorInsert(context, res, context.ConditionalSelect(inRange, elemRes, fallback), index + op.Id, 0); + } + + context.Copy(GetVecA32(op.Qd), res); + } + } + + public static void Vtrn(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + if (Optimizations.UseSsse3) + { + EmitVectorShuffleOpSimd32(context, (m, d) => + { + Operand mask = default; + + if (op.Size < 3) + { + long maskE0 = EvenMasks[op.Size]; + long maskE1 = OddMasks[op.Size]; + + mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + } + + if (op.Size < 3) + { + d = context.AddIntrinsic(Intrinsic.X86Pshufb, d, mask); + m = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mask); + } + + Operand resD = context.AddIntrinsic(X86PunpcklInstruction[op.Size], d, m); + Operand resM = context.AddIntrinsic(X86PunpckhInstruction[op.Size], d, m); + + return (resM, resD); + }); + } + else + { + int elems = op.GetBytesCount() >> op.Size; + int pairs = elems >> 1; + + bool overlap = op.Qm == op.Qd; + + Operand resD = GetVecA32(op.Qd); + Operand resM = GetVecA32(op.Qm); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + Operand d2 = EmitVectorExtract32(context, op.Qd, pairIndex + 1 + op.Id, op.Size, false); + Operand m1 = EmitVectorExtract32(context, op.Qm, pairIndex + op.Im, op.Size, false); + + resD = EmitVectorInsert(context, resD, m1, pairIndex + 1 + op.Id, op.Size); + + if (overlap) + { + resM = resD; + } + + resM = EmitVectorInsert(context, resM, d2, pairIndex + op.Im, op.Size); + + if (overlap) + { + resD = resM; + } + } + + context.Copy(GetVecA32(op.Qd), resD); + if (!overlap) + { + context.Copy(GetVecA32(op.Qm), resM); + } + } + } + + public static void Vzip(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + EmitVectorZipUzpOpSimd32(context, Intrinsic.Arm64Zip1V, Intrinsic.Arm64Zip2V); + } + else if (Optimizations.UseSse2) + { + EmitVectorShuffleOpSimd32(context, (m, d) => + { + if (op.RegisterSize == RegisterSize.Simd128) + { + Operand resD = context.AddIntrinsic(X86PunpcklInstruction[op.Size], d, m); + Operand resM = context.AddIntrinsic(X86PunpckhInstruction[op.Size], d, m); + + return (resM, resD); + } + else + { + Operand res = context.AddIntrinsic(X86PunpcklInstruction[op.Size], d, m); + + Operand resD = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, res, context.VectorZero()); + Operand resM = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, res, context.VectorZero()); + return (resM, resD); + } + }); + } + else + { + int elems = op.GetBytesCount() >> op.Size; + int pairs = elems >> 1; + + bool overlap = op.Qm == op.Qd; + + Operand resD = GetVecA32(op.Qd); + Operand resM = GetVecA32(op.Qm); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + Operand dRowD = EmitVectorExtract32(context, op.Qd, index + op.Id, op.Size, false); + Operand mRowD = EmitVectorExtract32(context, op.Qm, index + op.Im, op.Size, false); + + Operand dRowM = EmitVectorExtract32(context, op.Qd, index + op.Id + pairs, op.Size, false); + Operand mRowM = EmitVectorExtract32(context, op.Qm, index + op.Im + pairs, op.Size, false); + + resD = EmitVectorInsert(context, resD, dRowD, pairIndex + op.Id, op.Size); + resD = EmitVectorInsert(context, resD, mRowD, pairIndex + 1 + op.Id, op.Size); + + if (overlap) + { + resM = resD; + } + + resM = EmitVectorInsert(context, resM, dRowM, pairIndex + op.Im, op.Size); + resM = EmitVectorInsert(context, resM, mRowM, pairIndex + 1 + op.Im, op.Size); + + if (overlap) + { + resD = resM; + } + } + + context.Copy(GetVecA32(op.Qd), resD); + if (!overlap) + { + context.Copy(GetVecA32(op.Qm), resM); + } + } + } + + public static void Vuzp(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + EmitVectorZipUzpOpSimd32(context, Intrinsic.Arm64Uzp1V, Intrinsic.Arm64Uzp2V); + } + else if (Optimizations.UseSsse3) + { + EmitVectorShuffleOpSimd32(context, (m, d) => + { + if (op.RegisterSize == RegisterSize.Simd128) + { + Operand mask = default; + + if (op.Size < 3) + { + long maskE0 = EvenMasks[op.Size]; + long maskE1 = OddMasks[op.Size]; + + mask = X86GetScalar(context, maskE0); + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + + d = context.AddIntrinsic(Intrinsic.X86Pshufb, d, mask); + m = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mask); + } + + Operand resD = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, d, m); + Operand resM = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, d, m); + + return (resM, resD); + } + else + { + Intrinsic punpcklInst = X86PunpcklInstruction[op.Size]; + + Operand res = context.AddIntrinsic(punpcklInst, d, m); + + if (op.Size < 2) + { + long maskE0 = _masksE0_Uzp[op.Size]; + long maskE1 = _masksE1_Uzp[op.Size]; + + Operand mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + + res = context.AddIntrinsic(Intrinsic.X86Pshufb, res, mask); + } + + Operand resD = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, res, context.VectorZero()); + Operand resM = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, res, context.VectorZero()); + + return (resM, resD); + } + }); + } + else + { + int elems = op.GetBytesCount() >> op.Size; + int pairs = elems >> 1; + + bool overlap = op.Qm == op.Qd; + + Operand resD = GetVecA32(op.Qd); + Operand resM = GetVecA32(op.Qm); + + for (int index = 0; index < elems; index++) + { + Operand dIns, mIns; + if (index >= pairs) + { + int pairIndex = index - pairs; + dIns = EmitVectorExtract32(context, op.Qm, (pairIndex << 1) + op.Im, op.Size, false); + mIns = EmitVectorExtract32(context, op.Qm, ((pairIndex << 1) | 1) + op.Im, op.Size, false); + } + else + { + dIns = EmitVectorExtract32(context, op.Qd, (index << 1) + op.Id, op.Size, false); + mIns = EmitVectorExtract32(context, op.Qd, ((index << 1) | 1) + op.Id, op.Size, false); + } + + resD = EmitVectorInsert(context, resD, dIns, index + op.Id, op.Size); + + if (overlap) + { + resM = resD; + } + + resM = EmitVectorInsert(context, resM, mIns, index + op.Im, op.Size); + + if (overlap) + { + resD = resM; + } + } + + context.Copy(GetVecA32(op.Qd), resD); + if (!overlap) + { + context.Copy(GetVecA32(op.Qm), resM); + } + } + } + + private static void EmitVectorZipUzpOpSimd32(ArmEmitterContext context, Intrinsic inst1, Intrinsic inst2) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + bool overlap = op.Qm == op.Qd; + + Operand d = GetVecA32(op.Qd); + Operand m = GetVecA32(op.Qm); + + Operand dPart = d; + Operand mPart = m; + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + dPart = InstEmitSimdHelper32Arm64.EmitMoveDoubleWordToSide(context, d, op.Vd, 0); + mPart = InstEmitSimdHelper32Arm64.EmitMoveDoubleWordToSide(context, m, op.Vm, 0); + } + + Intrinsic vSize = op.Q ? Intrinsic.Arm64V128 : Intrinsic.Arm64V64; + + vSize |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + Operand resD = context.AddIntrinsic(inst1 | vSize, dPart, mPart); + Operand resM = context.AddIntrinsic(inst2 | vSize, dPart, mPart); + + if (!op.Q) // Register insert. + { + resD = context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, d, Const(op.Vd & 1), resD, Const(0)); + + if (overlap) + { + resD = context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, resD, Const(op.Vm & 1), resM, Const(0)); + } + else + { + resM = context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, m, Const(op.Vm & 1), resM, Const(0)); + } + } + + context.Copy(d, resD); + if (!overlap) + { + context.Copy(m, resM); + } + } + + private static void EmitVectorShuffleOpSimd32(ArmEmitterContext context, Func shuffleFunc) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + Operand initialM = m; + Operand initialD = d; + + if (!op.Q) // Register swap: move relevant doubleword to side 0, for consistency. + { + m = EmitMoveDoubleWordToSide(context, m, op.Vm, 0); + d = EmitMoveDoubleWordToSide(context, d, op.Vd, 0); + } + + (Operand resM, Operand resD) = shuffleFunc(m, d); + + bool overlap = op.Qm == op.Qd; + + if (!op.Q) // Register insert. + { + resM = EmitDoubleWordInsert(context, initialM, EmitMoveDoubleWordToSide(context, resM, 0, op.Vm), op.Vm); + resD = EmitDoubleWordInsert(context, overlap ? resM : initialD, EmitMoveDoubleWordToSide(context, resD, 0, op.Vd), op.Vd); + } + + if (!overlap) + { + context.Copy(initialM, resM); + } + + context.Copy(initialD, resD); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdShift.cs b/src/ARMeilleure/Instructions/InstEmitSimdShift.cs new file mode 100644 index 00000000..94e91257 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdShift.cs @@ -0,0 +1,1935 @@ +// https://github.com/intel/ARM_NEON_2_x86_SSE/blob/master/NEON_2_SSE.h + +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Reflection; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func2I = Func; + + static partial class InstEmit + { + #region "Masks" + private static readonly long[] _masks_SliSri = new long[] // Replication masks. + { + 0x0101010101010101L, 0x0001000100010001L, 0x0000000100000001L, 0x0000000000000001L, + }; + #endregion + + public static void Rshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64RshrnV, shift); + } + else if (Optimizations.UseSsse3) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + long roundConst = 1L << (shift - 1); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand dLow = context.VectorZeroUpper64(d); + + Operand mask = default; + + switch (op.Size + 1) + { + case 1: + mask = X86GetAllElements(context, (int)roundConst * 0x00010001); + break; + case 2: + mask = X86GetAllElements(context, (int)roundConst); + break; + case 3: + mask = X86GetAllElements(context, roundConst); + break; + } + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + Operand res = context.AddIntrinsic(addInst, n, mask); + + Intrinsic srlInst = X86PsrlInstruction[op.Size + 1]; + + res = context.AddIntrinsic(srlInst, res, Const(shift)); + + Operand mask2 = X86GetAllElements(context, EvenMasks[op.Size]); + + res = context.AddIntrinsic(Intrinsic.X86Pshufb, res, mask2); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 + ? Intrinsic.X86Movlhps + : Intrinsic.X86Movhlps; + + res = context.AddIntrinsic(movInst, dLow, res); + + context.Copy(d, res); + } + else + { + EmitVectorShrImmNarrowOpZx(context, round: true); + } + } + + public static void Shl_S(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64ShlS, shift); + } + else + { + EmitScalarUnaryOpZx(context, (op1) => context.ShiftLeft(op1, Const(shift))); + } + } + + public static void Shl_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + int eSize = 8 << op.Size; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64ShlV, shift); + } + else if (shift >= eSize) + { + if (op.RegisterSize == RegisterSize.Simd64) + { + Operand res = context.VectorZeroUpper64(GetVec(op.Rd)); + + context.Copy(GetVec(op.Rd), res); + } + } + else if (Optimizations.UseGfni && op.Size == 0) + { + Operand n = GetVec(op.Rn); + + ulong bitMatrix = X86GetGf2p8LogicalShiftLeft(shift); + + Operand vBitMatrix = X86GetElements(context, bitMatrix, bitMatrix); + + Operand res = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, n, vBitMatrix, Const(0)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(shift)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorUnaryOpZx(context, (op1) => context.ShiftLeft(op1, Const(shift))); + } + } + + public static void Shll_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int shift = 8 << op.Size; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64ShllV); + } + else if (Optimizations.UseSse41) + { + Operand n = GetVec(op.Rn); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + } + + Intrinsic movsxInst = X86PmovsxInstruction[op.Size]; + + Operand res = context.AddIntrinsic(movsxInst, n); + + Intrinsic sllInst = X86PsllInstruction[op.Size + 1]; + + res = context.AddIntrinsic(sllInst, res, Const(shift)); + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShImmWidenBinaryZx(context, (op1, op2) => context.ShiftLeft(op1, op2), shift); + } + } + + public static void Shrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64ShrnV, shift); + } + else if (Optimizations.UseSsse3) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand dLow = context.VectorZeroUpper64(d); + + Intrinsic srlInst = X86PsrlInstruction[op.Size + 1]; + + Operand nShifted = context.AddIntrinsic(srlInst, n, Const(shift)); + + Operand mask = X86GetAllElements(context, EvenMasks[op.Size]); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, nShifted, mask); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 + ? Intrinsic.X86Movlhps + : Intrinsic.X86Movhlps; + + res = context.AddIntrinsic(movInst, dLow, res); + + context.Copy(d, res); + } + else + { + EmitVectorShrImmNarrowOpZx(context, round: false); + } + } + + public static void Sli_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64SliS, shift); + } + else + { + EmitSli(context, scalar: true); + } + } + + public static void Sli_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64SliV, shift); + } + else + { + EmitSli(context, scalar: false); + } + } + + public static void Sqrshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqrshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Signed | ShlRegFlags.Round | ShlRegFlags.Saturating); + } + } + + public static void Sqrshrn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqrshrnS, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxSx); + } + } + + public static void Sqrshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqrshrnV, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxSx); + } + } + + public static void Sqrshrun_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqrshrunS, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxZx); + } + } + + public static void Sqrshrun_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqrshrunV, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } + } + + public static void Sqshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Signed | ShlRegFlags.Saturating); + } + } + + public static void Sqshl_Si(ArmEmitterContext context) + { + EmitShlImmOp(context, signedDst: true, ShlRegFlags.Signed | ShlRegFlags.Scalar | ShlRegFlags.Saturating); + } + + public static void Sqshl_Vi(ArmEmitterContext context) + { + EmitShlImmOp(context, signedDst: true, ShlRegFlags.Signed | ShlRegFlags.Saturating); + } + + public static void Sqshrn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqshrnS, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxSx); + } + } + + public static void Sqshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqshrnV, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxSx); + } + } + + public static void Sqshrun_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqshrunS, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxZx); + } + } + + public static void Sqshrun_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqshrunV, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } + } + + public static void Sri_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64SriS, shift); + } + else + { + EmitSri(context, scalar: true); + } + } + + public static void Sri_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64SriV, shift); + } + else + { + EmitSri(context, scalar: false); + } + } + + public static void Srshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SrshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Signed | ShlRegFlags.Round); + } + } + + public static void Srshr_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64SrshrS, shift); + } + else + { + EmitScalarShrImmOpSx(context, ShrImmFlags.Round); + } + } + + public static void Srshr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64SrshrV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0 && op.Size < 3) + { + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(eSize - shift)); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + res = context.AddIntrinsic(srlInst, res, Const(eSize - 1)); + + Intrinsic sraInst = X86PsraInstruction[op.Size]; + + Operand nSra = context.AddIntrinsic(sraInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, nSra); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShrImmOpSx(context, ShrImmFlags.Round); + } + } + + public static void Srsra_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64SrsraS, shift); + } + else + { + EmitScalarShrImmOpSx(context, ShrImmFlags.Round | ShrImmFlags.Accumulate); + } + } + + public static void Srsra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64SrsraV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0 && op.Size < 3) + { + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(eSize - shift)); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + res = context.AddIntrinsic(srlInst, res, Const(eSize - 1)); + + Intrinsic sraInst = X86PsraInstruction[op.Size]; + + Operand nSra = context.AddIntrinsic(sraInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, nSra); + res = context.AddIntrinsic(addInst, res, d); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + EmitVectorShrImmOpSx(context, ShrImmFlags.Round | ShrImmFlags.Accumulate); + } + } + + public static void Sshl_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOp(context, Intrinsic.Arm64SshlS); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Scalar | ShlRegFlags.Signed); + } + } + + public static void Sshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Signed); + } + } + + public static void Sshll_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64SshllV, shift); + } + else if (Optimizations.UseSse41) + { + Operand n = GetVec(op.Rn); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + } + + Intrinsic movsxInst = X86PmovsxInstruction[op.Size]; + + Operand res = context.AddIntrinsic(movsxInst, n); + + if (shift != 0) + { + Intrinsic sllInst = X86PsllInstruction[op.Size + 1]; + + res = context.AddIntrinsic(sllInst, res, Const(shift)); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShImmWidenBinarySx(context, (op1, op2) => context.ShiftLeft(op1, op2), shift); + } + } + + public static void Sshr_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64SshrS, shift); + } + else + { + EmitShrImmOp(context, ShrImmFlags.ScalarSx); + } + } + + public static void Sshr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64SshrV, shift); + } + else if (Optimizations.UseGfni && op.Size == 0) + { + Operand n = GetVec(op.Rn); + + ulong bitMatrix; + + if (shift < 8) + { + bitMatrix = X86GetGf2p8LogicalShiftLeft(-shift); + + // Extend sign-bit + bitMatrix |= 0x8080808080808080UL >> (64 - shift * 8); + } + else + { + // Replicate sign-bit into all bits + bitMatrix = 0x8080808080808080UL; + } + + Operand vBitMatrix = X86GetElements(context, bitMatrix, bitMatrix); + + Operand res = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, n, vBitMatrix, Const(0)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseSse2 && op.Size > 0 && op.Size < 3) + { + Operand n = GetVec(op.Rn); + + Intrinsic sraInst = X86PsraInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sraInst, n, Const(shift)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitShrImmOp(context, ShrImmFlags.VectorSx); + } + } + + public static void Ssra_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64SsraS, shift); + } + else + { + EmitScalarShrImmOpSx(context, ShrImmFlags.Accumulate); + } + } + + public static void Ssra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64SsraV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0 && op.Size < 3) + { + int shift = GetImmShr(op); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic sraInst = X86PsraInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sraInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, d); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + EmitVectorShrImmOpSx(context, ShrImmFlags.Accumulate); + } + } + + public static void Uqrshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64UqrshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Round | ShlRegFlags.Saturating); + } + } + + public static void Uqrshrn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64UqrshrnS, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarZxZx); + } + } + + public static void Uqrshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64UqrshrnV, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorZxZx); + } + } + + public static void Uqshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64UqshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Saturating); + } + } + + public static void Uqshrn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64UqshrnS, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarZxZx); + } + } + + public static void Uqshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64UqshrnV, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorZxZx); + } + } + + public static void Urshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UrshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Round); + } + } + + public static void Urshr_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64UrshrS, shift); + } + else + { + EmitScalarShrImmOpZx(context, ShrImmFlags.Round); + } + } + + public static void Urshr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64UrshrV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(eSize - shift)); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + res = context.AddIntrinsic(srlInst, res, Const(eSize - 1)); + + Operand nSrl = context.AddIntrinsic(srlInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, nSrl); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShrImmOpZx(context, ShrImmFlags.Round); + } + } + + public static void Ursra_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64UrsraS, shift); + } + else + { + EmitScalarShrImmOpZx(context, ShrImmFlags.Round | ShrImmFlags.Accumulate); + } + } + + public static void Ursra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64UrsraV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(eSize - shift)); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + res = context.AddIntrinsic(srlInst, res, Const(eSize - 1)); + + Operand nSrl = context.AddIntrinsic(srlInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, nSrl); + res = context.AddIntrinsic(addInst, res, d); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + EmitVectorShrImmOpZx(context, ShrImmFlags.Round | ShrImmFlags.Accumulate); + } + } + + public static void Ushl_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOp(context, Intrinsic.Arm64UshlS); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Scalar); + } + } + + public static void Ushl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.None); + } + } + + public static void Ushll_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64UshllV, shift); + } + else if (Optimizations.UseSse41) + { + Operand n = GetVec(op.Rn); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + } + + Intrinsic movzxInst = X86PmovzxInstruction[op.Size]; + + Operand res = context.AddIntrinsic(movzxInst, n); + + if (shift != 0) + { + Intrinsic sllInst = X86PsllInstruction[op.Size + 1]; + + res = context.AddIntrinsic(sllInst, res, Const(shift)); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShImmWidenBinaryZx(context, (op1, op2) => context.ShiftLeft(op1, op2), shift); + } + } + + public static void Ushr_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64UshrS, shift); + } + else + { + EmitShrImmOp(context, ShrImmFlags.ScalarZx); + } + } + + public static void Ushr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64UshrV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + int shift = GetImmShr(op); + + Operand n = GetVec(op.Rn); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + Operand res = context.AddIntrinsic(srlInst, n, Const(shift)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitShrImmOp(context, ShrImmFlags.VectorZx); + } + } + + public static void Usra_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64UsraS, shift); + } + else + { + EmitScalarShrImmOpZx(context, ShrImmFlags.Accumulate); + } + } + + public static void Usra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64UsraV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + int shift = GetImmShr(op); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + Operand res = context.AddIntrinsic(srlInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, d); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + EmitVectorShrImmOpZx(context, ShrImmFlags.Accumulate); + } + } + + [Flags] + private enum ShrImmFlags + { + Scalar = 1 << 0, + Signed = 1 << 1, + + Round = 1 << 2, + Accumulate = 1 << 3, + + ScalarSx = Scalar | Signed, + ScalarZx = Scalar, + + VectorSx = Signed, + VectorZx = 0, + } + + private static void EmitScalarShrImmOpSx(ArmEmitterContext context, ShrImmFlags flags) + { + EmitShrImmOp(context, ShrImmFlags.ScalarSx | flags); + } + + private static void EmitScalarShrImmOpZx(ArmEmitterContext context, ShrImmFlags flags) + { + EmitShrImmOp(context, ShrImmFlags.ScalarZx | flags); + } + + private static void EmitVectorShrImmOpSx(ArmEmitterContext context, ShrImmFlags flags) + { + EmitShrImmOp(context, ShrImmFlags.VectorSx | flags); + } + + private static void EmitVectorShrImmOpZx(ArmEmitterContext context, ShrImmFlags flags) + { + EmitShrImmOp(context, ShrImmFlags.VectorZx | flags); + } + + private static void EmitShrImmOp(ArmEmitterContext context, ShrImmFlags flags) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + Operand res = context.VectorZero(); + + bool scalar = (flags & ShrImmFlags.Scalar) != 0; + bool signed = (flags & ShrImmFlags.Signed) != 0; + bool round = (flags & ShrImmFlags.Round) != 0; + bool accumulate = (flags & ShrImmFlags.Accumulate) != 0; + + int shift = GetImmShr(op); + + long roundConst = 1L << (shift - 1); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand e = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + + if (op.Size <= 2) + { + if (round) + { + e = context.Add(e, Const(roundConst)); + } + + e = signed ? context.ShiftRightSI(e, Const(shift)) : context.ShiftRightUI(e, Const(shift)); + } + else /* if (op.Size == 3) */ + { + e = EmitShrImm64(context, e, signed, round ? roundConst : 0L, shift); + } + + if (accumulate) + { + Operand de = EmitVectorExtract(context, op.Rd, index, op.Size, signed); + + e = context.Add(e, de); + } + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitVectorShrImmNarrowOpZx(ArmEmitterContext context, bool round) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + long roundConst = 1L << (shift - 1); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand e = EmitVectorExtractZx(context, op.Rn, index, op.Size + 1); + + if (round) + { + e = context.Add(e, Const(roundConst)); + } + + e = context.ShiftRightUI(e, Const(shift)); + + res = EmitVectorInsert(context, res, e, part + index, op.Size); + } + + context.Copy(d, res); + } + + [Flags] + private enum ShrImmSaturatingNarrowFlags + { + Scalar = 1 << 0, + SignedSrc = 1 << 1, + SignedDst = 1 << 2, + + Round = 1 << 3, + + ScalarSxSx = Scalar | SignedSrc | SignedDst, + ScalarSxZx = Scalar | SignedSrc, + ScalarZxZx = Scalar, + + VectorSxSx = SignedSrc | SignedDst, + VectorSxZx = SignedSrc, + VectorZxZx = 0, + } + + private static void EmitRoundShrImmSaturatingNarrowOp(ArmEmitterContext context, ShrImmSaturatingNarrowFlags flags) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.Round | flags); + } + + private static void EmitShrImmSaturatingNarrowOp(ArmEmitterContext context, ShrImmSaturatingNarrowFlags flags) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + bool scalar = (flags & ShrImmSaturatingNarrowFlags.Scalar) != 0; + bool signedSrc = (flags & ShrImmSaturatingNarrowFlags.SignedSrc) != 0; + bool signedDst = (flags & ShrImmSaturatingNarrowFlags.SignedDst) != 0; + bool round = (flags & ShrImmSaturatingNarrowFlags.Round) != 0; + + int shift = GetImmShr(op); + + long roundConst = 1L << (shift - 1); + + int elems = !scalar ? 8 >> op.Size : 1; + + int part = !scalar && (op.RegisterSize == RegisterSize.Simd128) ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand e = EmitVectorExtract(context, op.Rn, index, op.Size + 1, signedSrc); + + if (op.Size <= 1 || !round) + { + if (round) + { + e = context.Add(e, Const(roundConst)); + } + + e = signedSrc ? context.ShiftRightSI(e, Const(shift)) : context.ShiftRightUI(e, Const(shift)); + } + else /* if (op.Size == 2 && round) */ + { + e = EmitShrImm64(context, e, signedSrc, roundConst, shift); // shift <= 32 + } + + e = signedSrc ? EmitSignedSrcSatQ(context, e, op.Size, signedDst) : EmitUnsignedSrcSatQ(context, e, op.Size, signedDst); + + res = EmitVectorInsert(context, res, e, part + index, op.Size); + } + + context.Copy(d, res); + } + + // dst64 = (Int(src64, signed) + roundConst) >> shift; + private static Operand EmitShrImm64( + ArmEmitterContext context, + Operand value, + bool signed, + long roundConst, + int shift) + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SignedShrImm64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnsignedShrImm64)); + + return context.Call(info, value, Const(roundConst), Const(shift)); + } + + private static void EmitVectorShImmWidenBinarySx(ArmEmitterContext context, Func2I emit, int imm) + { + EmitVectorShImmWidenBinaryOp(context, emit, imm, signed: true); + } + + private static void EmitVectorShImmWidenBinaryZx(ArmEmitterContext context, Func2I emit, int imm) + { + EmitVectorShImmWidenBinaryOp(context, emit, imm, signed: false); + } + + private static void EmitVectorShImmWidenBinaryOp(ArmEmitterContext context, Func2I emit, int imm, bool signed) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, Const(imm)), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitSli(ArmEmitterContext context, bool scalar) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + int eSize = 8 << op.Size; + + ulong mask = shift != 0 ? ulong.MaxValue >> (64 - shift) : 0UL; + + if (shift >= eSize) + { + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + Operand res = context.VectorZeroUpper64(GetVec(op.Rd)); + + context.Copy(GetVec(op.Rd), res); + } + } + else if (Optimizations.UseGfni && op.Size == 0) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + ulong bitMatrix = X86GetGf2p8LogicalShiftLeft(shift); + + Operand vBitMatrix = X86GetElements(context, bitMatrix, bitMatrix); + + Operand nShifted = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, n, vBitMatrix, Const(0)); + + Operand dMask = X86GetAllElements(context, (long)mask * _masks_SliSri[op.Size]); + + Operand dMasked = context.AddIntrinsic(Intrinsic.X86Pand, d, dMask); + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, dMasked); + + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand nShifted = context.AddIntrinsic(sllInst, n, Const(shift)); + + Operand dMask = X86GetAllElements(context, (long)mask * _masks_SliSri[op.Size]); + + Operand dMasked = context.AddIntrinsic(Intrinsic.X86Pand, d, dMask); + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, dMasked); + + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + Operand neShifted = context.ShiftLeft(ne, Const(shift)); + + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + + Operand deMasked = context.BitwiseAnd(de, Const(mask)); + + Operand e = context.BitwiseOr(neShifted, deMasked); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitSri(ArmEmitterContext context, bool scalar) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + ulong mask = (ulong.MaxValue << (eSize - shift)) & (ulong.MaxValue >> (64 - eSize)); + + if (shift >= eSize) + { + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + Operand res = context.VectorZeroUpper64(GetVec(op.Rd)); + + context.Copy(GetVec(op.Rd), res); + } + } + else if (Optimizations.UseGfni && op.Size == 0) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + ulong bitMatrix = X86GetGf2p8LogicalShiftLeft(-shift); + + Operand vBitMatrix = X86GetElements(context, bitMatrix, bitMatrix); + + Operand nShifted = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, n, vBitMatrix, Const(0)); + + Operand dMask = X86GetAllElements(context, (long)mask * _masks_SliSri[op.Size]); + + Operand dMasked = context.AddIntrinsic(Intrinsic.X86Pand, d, dMask); + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, dMasked); + + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + Operand nShifted = context.AddIntrinsic(srlInst, n, Const(shift)); + + Operand dMask = X86GetAllElements(context, (long)mask * _masks_SliSri[op.Size]); + + Operand dMasked = context.AddIntrinsic(Intrinsic.X86Pand, d, dMask); + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, dMasked); + + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + Operand neShifted = shift != 64 ? context.ShiftRightUI(ne, Const(shift)) : Const(0UL); + + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + + Operand deMasked = context.BitwiseAnd(de, Const(mask)); + + Operand e = context.BitwiseOr(neShifted, deMasked); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + [Flags] + private enum ShlRegFlags + { + None = 0, + Scalar = 1 << 0, + Signed = 1 << 1, + Round = 1 << 2, + Saturating = 1 << 3, + } + + private static void EmitShlImmOp(ArmEmitterContext context, bool signedDst, ShlRegFlags flags = ShlRegFlags.None) + { + bool scalar = flags.HasFlag(ShlRegFlags.Scalar); + bool signed = flags.HasFlag(ShlRegFlags.Signed); + bool saturating = flags.HasFlag(ShlRegFlags.Saturating); + + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + + Operand e = !saturating + ? EmitShlImm(context, ne, GetImmShl(op), op.Size) + : EmitShlImmSatQ(context, ne, GetImmShl(op), op.Size, signed, signedDst); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static Operand EmitShlImm(ArmEmitterContext context, Operand op, int shiftLsB, int size) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand res = context.AllocateLocal(OperandType.I64); + + if (shiftLsB >= eSize) + { + Operand shl = context.ShiftLeft(op, Const(shiftLsB)); + context.Copy(res, shl); + } + else + { + Operand zeroL = Const(0L); + context.Copy(res, zeroL); + } + + return res; + } + + private static Operand EmitShlImmSatQ(ArmEmitterContext context, Operand op, int shiftLsB, int size, bool signedSrc, bool signedDst) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand lblEnd = Label(); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + if (shiftLsB >= eSize) + { + context.Copy(res, signedSrc + ? EmitSignedSignSatQ(context, op, size) + : EmitUnsignedSignSatQ(context, op, size)); + } + else + { + Operand shl = context.ShiftLeft(op, Const(shiftLsB)); + if (eSize == 64) + { + Operand sarOrShr = signedSrc + ? context.ShiftRightSI(shl, Const(shiftLsB)) + : context.ShiftRightUI(shl, Const(shiftLsB)); + context.Copy(res, shl); + context.BranchIf(lblEnd, sarOrShr, op, Comparison.Equal); + context.Copy(res, signedSrc + ? EmitSignedSignSatQ(context, op, size) + : EmitUnsignedSignSatQ(context, op, size)); + } + else + { + context.Copy(res, signedSrc + ? EmitSignedSrcSatQ(context, shl, size, signedDst) + : EmitUnsignedSrcSatQ(context, shl, size, signedDst)); + } + } + + context.MarkLabel(lblEnd); + + return res; + } + + private static void EmitShlRegOp(ArmEmitterContext context, ShlRegFlags flags = ShlRegFlags.None) + { + bool scalar = flags.HasFlag(ShlRegFlags.Scalar); + bool signed = flags.HasFlag(ShlRegFlags.Signed); + bool round = flags.HasFlag(ShlRegFlags.Round); + bool saturating = flags.HasFlag(ShlRegFlags.Saturating); + + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + Operand me = EmitVectorExtractSx(context, op.Rm, index << op.Size, size: 0); + + Operand e = !saturating + ? EmitShlReg(context, ne, context.ConvertI64ToI32(me), round, op.Size, signed) + : EmitShlRegSatQ(context, ne, context.ConvertI64ToI32(me), round, op.Size, signed); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + // long SignedShlReg(long op, int shiftLsB, bool round, int size); + // ulong UnsignedShlReg(ulong op, int shiftLsB, bool round, int size); + private static Operand EmitShlReg(ArmEmitterContext context, Operand op, Operand shiftLsB, bool round, int size, bool signed) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(shiftLsB.Type == OperandType.I32); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand lbl1 = Label(); + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zero = Const(0); + Operand zeroL = Const(0L); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lbl1, shiftLsB, zero, Comparison.GreaterOrEqual); + context.Copy(res, signed + ? EmitSignedShrReg(context, op, context.Negate(shiftLsB), round, eSize) + : EmitUnsignedShrReg(context, op, context.Negate(shiftLsB), round, eSize)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, shiftLsB, zero, Comparison.LessOrEqual); + Operand shl = context.ShiftLeft(op, shiftLsB); + Operand isGreaterOrEqual = context.ICompareGreaterOrEqual(shiftLsB, eSizeOp); + context.Copy(res, context.ConditionalSelect(isGreaterOrEqual, zeroL, shl)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long SignedShlRegSatQ(long op, int shiftLsB, bool round, int size); + // ulong UnsignedShlRegSatQ(ulong op, int shiftLsB, bool round, int size); + private static Operand EmitShlRegSatQ(ArmEmitterContext context, Operand op, Operand shiftLsB, bool round, int size, bool signed) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(shiftLsB.Type == OperandType.I32); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand lbl1 = Label(); + Operand lbl2 = Label(); + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zero = Const(0); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lbl1, shiftLsB, zero, Comparison.GreaterOrEqual); + context.Copy(res, signed + ? EmitSignedShrReg(context, op, context.Negate(shiftLsB), round, eSize) + : EmitUnsignedShrReg(context, op, context.Negate(shiftLsB), round, eSize)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, shiftLsB, zero, Comparison.LessOrEqual); + context.BranchIf(lbl2, shiftLsB, eSizeOp, Comparison.Less); + context.Copy(res, signed + ? EmitSignedSignSatQ(context, op, size) + : EmitUnsignedSignSatQ(context, op, size)); + context.Branch(lblEnd); + + context.MarkLabel(lbl2); + Operand shl = context.ShiftLeft(op, shiftLsB); + if (eSize == 64) + { + Operand sarOrShr = signed + ? context.ShiftRightSI(shl, shiftLsB) + : context.ShiftRightUI(shl, shiftLsB); + context.Copy(res, shl); + context.BranchIf(lblEnd, sarOrShr, op, Comparison.Equal); + context.Copy(res, signed + ? EmitSignedSignSatQ(context, op, size) + : EmitUnsignedSignSatQ(context, op, size)); + } + else + { + context.Copy(res, signed + ? EmitSignedSrcSatQ(context, shl, size, signedDst: true) + : EmitUnsignedSrcSatQ(context, shl, size, signedDst: false)); + } + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // shift := [1, 128]; eSize := {8, 16, 32, 64}. + // long SignedShrReg(long op, int shift, bool round, int eSize); + private static Operand EmitSignedShrReg(ArmEmitterContext context, Operand op, Operand shift, bool round, int eSize) + { + if (round) + { + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zeroL = Const(0L); + Operand one = Const(1); + Operand oneL = Const(1L); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), zeroL); + + context.BranchIf(lblEnd, shift, eSizeOp, Comparison.GreaterOrEqual); + Operand roundConst = context.ShiftLeft(oneL, context.Subtract(shift, one)); + Operand add = context.Add(op, roundConst); + Operand sar = context.ShiftRightSI(add, shift); + if (eSize == 64) + { + Operand shr = context.ShiftRightUI(add, shift); + Operand left = context.BitwiseAnd(context.Negate(op), context.BitwiseExclusiveOr(op, add)); + Operand isLess = context.ICompareLess(left, zeroL); + context.Copy(res, context.ConditionalSelect(isLess, shr, sar)); + } + else + { + context.Copy(res, sar); + } + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + else + { + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zeroL = Const(0L); + Operand negOneL = Const(-1L); + + Operand sar = context.ShiftRightSI(op, shift); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), sar); + + context.BranchIf(lblEnd, shift, eSizeOp, Comparison.Less); + Operand isLess = context.ICompareLess(op, zeroL); + context.Copy(res, context.ConditionalSelect(isLess, negOneL, zeroL)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + } + + // shift := [1, 128]; eSize := {8, 16, 32, 64}. + // ulong UnsignedShrReg(ulong op, int shift, bool round, int eSize); + private static Operand EmitUnsignedShrReg(ArmEmitterContext context, Operand op, Operand shift, bool round, int eSize) + { + if (round) + { + Operand lblEnd = Label(); + + Operand zeroUL = Const(0UL); + Operand one = Const(1); + Operand oneUL = Const(1UL); + Operand eSizeMaxOp = Const(64); + Operand oneShl63UL = Const(1UL << 63); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), zeroUL); + + context.BranchIf(lblEnd, shift, eSizeMaxOp, Comparison.Greater); + Operand roundConst = context.ShiftLeft(oneUL, context.Subtract(shift, one)); + Operand add = context.Add(op, roundConst); + Operand shr = context.ShiftRightUI(add, shift); + Operand isEqual = context.ICompareEqual(shift, eSizeMaxOp); + context.Copy(res, context.ConditionalSelect(isEqual, zeroUL, shr)); + if (eSize == 64) + { + context.BranchIf(lblEnd, add, op, Comparison.GreaterOrEqualUI); + Operand right = context.BitwiseOr(shr, context.ShiftRightUI(oneShl63UL, context.Subtract(shift, one))); + context.Copy(res, context.ConditionalSelect(isEqual, oneUL, right)); + } + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + else + { + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zeroUL = Const(0UL); + + Operand shr = context.ShiftRightUI(op, shift); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), shr); + + context.BranchIf(lblEnd, shift, eSizeOp, Comparison.Less); + context.Copy(res, zeroUL); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs b/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs new file mode 100644 index 00000000..eb28a0c5 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs @@ -0,0 +1,450 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Reflection; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper32; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Vqrshrn(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + EmitRoundShrImmSaturatingNarrowOp(context, op.U ? ShrImmSaturatingNarrowFlags.VectorZxZx : ShrImmSaturatingNarrowFlags.VectorSxSx); + } + + public static void Vqrshrun(ArmEmitterContext context) + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } + + public static void Vqshrn(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + EmitShrImmSaturatingNarrowOp(context, op.U ? ShrImmSaturatingNarrowFlags.VectorZxZx : ShrImmSaturatingNarrowFlags.VectorSxSx); + } + + public static void Vqshrun(ArmEmitterContext context) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } + + public static void Vrshr(ArmEmitterContext context) + { + EmitRoundShrImmOp(context, accumulate: false); + } + + public static void Vrshrn(ArmEmitterContext context) + { + EmitRoundShrImmNarrowOp(context, signed: false); + } + + public static void Vrsra(ArmEmitterContext context) + { + EmitRoundShrImmOp(context, accumulate: true); + } + + public static void Vshl(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + EmitVectorUnaryOpZx32(context, (op1) => context.ShiftLeft(op1, Const(op.Shift))); + } + + public static void Vshl_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) + { + EmitVectorBinaryOpZx32(context, (op1, op2) => EmitShlRegOp(context, op2, op1, op.Size, true)); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => EmitShlRegOp(context, op2, op1, op.Size, false)); + } + } + + public static void Vshll(ArmEmitterContext context) + { + OpCode32SimdShImmLong op = (OpCode32SimdShImmLong)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, !op.U); + + if (op.Size == 2) + { + if (op.U) + { + me = context.ZeroExtend32(OperandType.I64, me); + } + else + { + me = context.SignExtend32(OperandType.I64, me); + } + } + + me = context.ShiftLeft(me, Const(op.Shift)); + + res = EmitVectorInsert(context, res, me, index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Vshll2(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, !op.U); + + if (op.Size == 2) + { + if (op.U) + { + me = context.ZeroExtend32(OperandType.I64, me); + } + else + { + me = context.SignExtend32(OperandType.I64, me); + } + } + + me = context.ShiftLeft(me, Const(8 << op.Size)); + + res = EmitVectorInsert(context, res, me, index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Vshr(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + int shift = GetImmShr(op); + int maxShift = (8 << op.Size) - 1; + + if (op.U) + { + EmitVectorUnaryOpZx32(context, (op1) => (shift > maxShift) ? Const(op1.Type, 0) : context.ShiftRightUI(op1, Const(shift))); + } + else + { + EmitVectorUnaryOpSx32(context, (op1) => context.ShiftRightSI(op1, Const(Math.Min(maxShift, shift)))); + } + } + + public static void Vshrn(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + int shift = GetImmShr(op); + + EmitVectorUnaryNarrowOp32(context, (op1) => context.ShiftRightUI(op1, Const(shift))); + } + + public static void Vsli_I(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + int shift = op.Shift; + int eSize = 8 << op.Size; + + ulong mask = shift != 0 ? ulong.MaxValue >> (64 - shift) : 0UL; + + Operand res = GetVec(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand me = EmitVectorExtractZx(context, op.Qm, op.Im + index, op.Size); + + Operand neShifted = context.ShiftLeft(me, Const(shift)); + + Operand de = EmitVectorExtractZx(context, op.Qd, op.Id + index, op.Size); + + Operand deMasked = context.BitwiseAnd(de, Const(mask)); + + Operand e = context.BitwiseOr(neShifted, deMasked); + + res = EmitVectorInsert(context, res, e, op.Id + index, op.Size); + } + + context.Copy(GetVec(op.Qd), res); + } + + public static void Vsra(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + int shift = GetImmShr(op); + int maxShift = (8 << op.Size) - 1; + + if (op.U) + { + EmitVectorImmBinaryQdQmOpZx32(context, (op1, op2) => + { + Operand shiftRes = shift > maxShift ? Const(op2.Type, 0) : context.ShiftRightUI(op2, Const(shift)); + + return context.Add(op1, shiftRes); + }); + } + else + { + EmitVectorImmBinaryQdQmOpSx32(context, (op1, op2) => context.Add(op1, context.ShiftRightSI(op2, Const(Math.Min(maxShift, shift))))); + } + } + + public static void EmitRoundShrImmOp(ArmEmitterContext context, bool accumulate) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + int shift = GetImmShr(op); + long roundConst = 1L << (shift - 1); + + if (op.U) + { + if (op.Size < 2) + { + EmitVectorUnaryOpZx32(context, (op1) => + { + op1 = context.Add(op1, Const(op1.Type, roundConst)); + + return context.ShiftRightUI(op1, Const(shift)); + }, accumulate); + } + else if (op.Size == 2) + { + EmitVectorUnaryOpZx32(context, (op1) => + { + op1 = context.ZeroExtend32(OperandType.I64, op1); + op1 = context.Add(op1, Const(op1.Type, roundConst)); + + return context.ConvertI64ToI32(context.ShiftRightUI(op1, Const(shift))); + }, accumulate); + } + else /* if (op.Size == 3) */ + { + EmitVectorUnaryOpZx32(context, (op1) => EmitShrImm64(context, op1, signed: false, roundConst, shift), accumulate); + } + } + else + { + if (op.Size < 2) + { + EmitVectorUnaryOpSx32(context, (op1) => + { + op1 = context.Add(op1, Const(op1.Type, roundConst)); + + return context.ShiftRightSI(op1, Const(shift)); + }, accumulate); + } + else if (op.Size == 2) + { + EmitVectorUnaryOpSx32(context, (op1) => + { + op1 = context.SignExtend32(OperandType.I64, op1); + op1 = context.Add(op1, Const(op1.Type, roundConst)); + + return context.ConvertI64ToI32(context.ShiftRightSI(op1, Const(shift))); + }, accumulate); + } + else /* if (op.Size == 3) */ + { + EmitVectorUnaryOpZx32(context, (op1) => EmitShrImm64(context, op1, signed: true, roundConst, shift), accumulate); + } + } + } + + private static void EmitRoundShrImmNarrowOp(ArmEmitterContext context, bool signed) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + long roundConst = 1L << (shift - 1); + + EmitVectorUnaryNarrowOp32(context, (op1) => + { + if (op.Size <= 1) + { + op1 = context.Add(op1, Const(op1.Type, roundConst)); + op1 = signed ? context.ShiftRightSI(op1, Const(shift)) : context.ShiftRightUI(op1, Const(shift)); + } + else /* if (op.Size == 2 && round) */ + { + op1 = EmitShrImm64(context, op1, signed, roundConst, shift); // shift <= 32 + } + + return op1; + }, signed); + } + + private static Operand EmitShlRegOp(ArmEmitterContext context, Operand op, Operand shiftLsB, int size, bool unsigned) + { + if (shiftLsB.Type == OperandType.I64) + { + shiftLsB = context.ConvertI64ToI32(shiftLsB); + } + + shiftLsB = context.SignExtend8(OperandType.I32, shiftLsB); + Debug.Assert((uint)size < 4u); + + Operand negShiftLsB = context.Negate(shiftLsB); + + Operand isPositive = context.ICompareGreaterOrEqual(shiftLsB, Const(0)); + + Operand shl = context.ShiftLeft(op, shiftLsB); + Operand shr = unsigned ? context.ShiftRightUI(op, negShiftLsB) : context.ShiftRightSI(op, negShiftLsB); + + Operand res = context.ConditionalSelect(isPositive, shl, shr); + + if (unsigned) + { + Operand isOutOfRange = context.BitwiseOr( + context.ICompareGreaterOrEqual(shiftLsB, Const(8 << size)), + context.ICompareGreaterOrEqual(negShiftLsB, Const(8 << size))); + + return context.ConditionalSelect(isOutOfRange, Const(op.Type, 0), res); + } + else + { + Operand isOutOfRange0 = context.ICompareGreaterOrEqual(shiftLsB, Const(8 << size)); + Operand isOutOfRangeN = context.ICompareGreaterOrEqual(negShiftLsB, Const(8 << size)); + + // Also zero if shift is too negative, but value was positive. + isOutOfRange0 = context.BitwiseOr(isOutOfRange0, context.BitwiseAnd(isOutOfRangeN, context.ICompareGreaterOrEqual(op, Const(op.Type, 0)))); + + Operand min = (op.Type == OperandType.I64) ? Const(-1L) : Const(-1); + + return context.ConditionalSelect(isOutOfRange0, Const(op.Type, 0), context.ConditionalSelect(isOutOfRangeN, min, res)); + } + } + + [Flags] + private enum ShrImmSaturatingNarrowFlags + { + Scalar = 1 << 0, + SignedSrc = 1 << 1, + SignedDst = 1 << 2, + + Round = 1 << 3, + + ScalarSxSx = Scalar | SignedSrc | SignedDst, + ScalarSxZx = Scalar | SignedSrc, + ScalarZxZx = Scalar, + + VectorSxSx = SignedSrc | SignedDst, + VectorSxZx = SignedSrc, + VectorZxZx = 0, + } + + private static void EmitRoundShrImmSaturatingNarrowOp(ArmEmitterContext context, ShrImmSaturatingNarrowFlags flags) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.Round | flags); + } + + private static void EmitShrImmSaturatingNarrowOp(ArmEmitterContext context, ShrImmSaturatingNarrowFlags flags) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + bool scalar = (flags & ShrImmSaturatingNarrowFlags.Scalar) != 0; + bool signedSrc = (flags & ShrImmSaturatingNarrowFlags.SignedSrc) != 0; + bool signedDst = (flags & ShrImmSaturatingNarrowFlags.SignedDst) != 0; + bool round = (flags & ShrImmSaturatingNarrowFlags.Round) != 0; + + if (scalar) + { + // TODO: Support scalar operation. + throw new NotImplementedException(); + } + + int shift = GetImmShr(op); + long roundConst = 1L << (shift - 1); + + EmitVectorUnaryNarrowOp32(context, (op1) => + { + if (op.Size <= 1 || !round) + { + if (round) + { + op1 = context.Add(op1, Const(op1.Type, roundConst)); + } + + op1 = signedSrc ? context.ShiftRightSI(op1, Const(shift)) : context.ShiftRightUI(op1, Const(shift)); + } + else /* if (op.Size == 2 && round) */ + { + op1 = EmitShrImm64(context, op1, signedSrc, roundConst, shift); // shift <= 32 + } + + return EmitSatQ(context, op1, 8 << op.Size, signedSrc, signedDst); + }, signedSrc); + } + + private static int GetImmShr(OpCode32SimdShImm op) + { + return (8 << op.Size) - op.Shift; // Shr amount is flipped. + } + + // dst64 = (Int(src64, signed) + roundConst) >> shift; + private static Operand EmitShrImm64( + ArmEmitterContext context, + Operand value, + bool signed, + long roundConst, + int shift) + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SignedShrImm64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnsignedShrImm64)); + + return context.Call(info, value, Const(roundConst), Const(shift)); + } + + private static Operand EmitSatQ(ArmEmitterContext context, Operand value, int eSize, bool signedSrc, bool signedDst) + { + Debug.Assert(eSize <= 32); + + long intMin = signedDst ? -(1L << (eSize - 1)) : 0; + long intMax = signedDst ? (1L << (eSize - 1)) - 1 : (1L << eSize) - 1; + + Operand gt = signedSrc + ? context.ICompareGreater(value, Const(value.Type, intMax)) + : context.ICompareGreaterUI(value, Const(value.Type, intMax)); + + Operand lt = signedSrc + ? context.ICompareLess(value, Const(value.Type, intMin)) + : context.ICompareLessUI(value, Const(value.Type, intMin)); + + value = context.ConditionalSelect(gt, Const(value.Type, intMax), value); + value = context.ConditionalSelect(lt, Const(value.Type, intMin), value); + + Operand lblNoSat = Label(); + + context.BranchIfFalse(lblNoSat, context.BitwiseOr(gt, lt)); + + SetFpFlag(context, FPState.QcFlag, Const(1)); + + context.MarkLabel(lblNoSat); + + return value; + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSystem.cs b/src/ARMeilleure/Instructions/InstEmitSystem.cs new file mode 100644 index 00000000..8c430fc2 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSystem.cs @@ -0,0 +1,278 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Reflection; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + private const int DczSizeLog2 = 4; // Log2 size in words + public const int DczSizeInBytes = 4 << DczSizeLog2; + + public static void Isb(ArmEmitterContext context) + { + // Execute as no-op. + } + + public static void Mrs(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + MethodInfo info; + + switch (GetPackedId(op)) + { + case 0b11_011_0000_0000_001: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCtrEl0)); + break; + case 0b11_011_0000_0000_111: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetDczidEl0)); + break; + 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: + 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; + + default: + throw new NotImplementedException($"Unknown MRS 0x{op.RawOpCode:X8} at 0x{op.Address:X16}."); + } + + SetIntOrZR(context, op.Rt, context.Call(info)); + } + + public static void Msr(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + 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: + EmitSetTpidrEl0(context); + return; + + default: + throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}."); + } + } + + public static void Nop(ArmEmitterContext context) + { + // Do nothing. + } + + public static void Sys(ArmEmitterContext context) + { + // This instruction is used to do some operations on the CPU like cache invalidation, + // address translation and the like. + // We treat it as no-op here since we don't have any cache being emulated anyway. + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + switch (GetPackedId(op)) + { + case 0b11_011_0111_0100_001: + { + // DC ZVA + Operand t = GetIntOrZR(context, op.Rt); + + for (long offset = 0; offset < DczSizeInBytes; offset += 8) + { + Operand address = context.Add(t, Const(offset)); + + InstEmitMemoryHelper.EmitStore(context, address, RegisterConsts.ZeroIndex, 3); + } + + break; + } + + // No-op + case 0b11_011_0111_1110_001: // DC CIVAC + break; + + case 0b11_011_0111_0101_001: // IC IVAU + Operand target = Register(op.Rt, RegisterType.Integer, OperandType.I64); + context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)), target); + break; + } + } + + private static int GetPackedId(OpCodeSystem op) + { + int id; + + id = op.Op2 << 0; + id |= op.CRm << 3; + id |= op.CRn << 7; + id |= op.Op1 << 11; + id |= op.Op0 << 14; + + return id; + } + + private static void EmitGetNzcv(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand nzcv = context.ShiftLeft(GetFlag(PState.VFlag), Const((int)PState.VFlag)); + nzcv = context.BitwiseOr(nzcv, context.ShiftLeft(GetFlag(PState.CFlag), Const((int)PState.CFlag))); + nzcv = context.BitwiseOr(nzcv, context.ShiftLeft(GetFlag(PState.ZFlag), Const((int)PState.ZFlag))); + nzcv = context.BitwiseOr(nzcv, context.ShiftLeft(GetFlag(PState.NFlag), Const((int)PState.NFlag))); + + SetIntOrZR(context, op.Rt, nzcv); + } + + private static void EmitGetFpcr(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand fpcr = Const(0); + + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + if (FPCR.Mask.HasFlag((FPCR)(1u << flag))) + { + fpcr = context.BitwiseOr(fpcr, context.ShiftLeft(GetFpFlag((FPState)flag), Const(flag))); + } + } + + SetIntOrZR(context, op.Rt, fpcr); + } + + private static void EmitGetFpsr(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + context.SyncQcFlag(); + + Operand fpsr = Const(0); + + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + if (FPSR.Mask.HasFlag((FPSR)(1u << flag))) + { + fpsr = context.BitwiseOr(fpsr, context.ShiftLeft(GetFpFlag((FPState)flag), Const(flag))); + } + } + + 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; + + Operand nzcv = GetIntOrZR(context, op.Rt); + nzcv = context.ConvertI64ToI32(nzcv); + + SetFlag(context, PState.VFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const((int)PState.VFlag)), Const(1))); + SetFlag(context, PState.CFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const((int)PState.CFlag)), Const(1))); + SetFlag(context, PState.ZFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const((int)PState.ZFlag)), Const(1))); + SetFlag(context, PState.NFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const((int)PState.NFlag)), Const(1))); + } + + private static void EmitSetFpcr(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand fpcr = GetIntOrZR(context, op.Rt); + fpcr = context.ConvertI64ToI32(fpcr); + + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + if (FPCR.Mask.HasFlag((FPCR)(1u << flag))) + { + SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpcr, Const(flag)), Const(1))); + } + } + + context.UpdateArmFpMode(); + } + + private static void EmitSetFpsr(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + context.ClearQcFlagIfModified(); + + Operand fpsr = GetIntOrZR(context, op.Rt); + fpsr = context.ConvertI64ToI32(fpsr); + + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + if (FPSR.Mask.HasFlag((FPSR)(1u << flag))) + { + 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); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSystem32.cs b/src/ARMeilleure/Instructions/InstEmitSystem32.cs new file mode 100644 index 00000000..74d6169c --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSystem32.cs @@ -0,0 +1,338 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Reflection; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Mcr(ArmEmitterContext context) + { + OpCode32System op = (OpCode32System)context.CurrOp; + + if (op.Coproc != 15 || op.Opc1 != 0) + { + InstEmit.Und(context); + + return; + } + + switch (op.CRn) + { + case 13: // Process and Thread Info. + if (op.CRm != 0) + { + throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X} at 0x{op.Address:X} (0x{op.RawOpCode:X})."); + } + + switch (op.Opc2) + { + case 2: + EmitSetTpidrEl0(context); + return; + + default: + throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X})."); + } + + case 7: + switch (op.CRm) // Cache and Memory barrier. + { + case 10: + switch (op.Opc2) + { + case 5: // Data Memory Barrier Register. + return; // No-op. + + default: + throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X16} at 0x{op.Address:X16} (0x{op.RawOpCode:X})."); + } + + default: + throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X16} at 0x{op.Address:X16} (0x{op.RawOpCode:X})."); + } + + default: + throw new NotImplementedException($"Unknown MRC 0x{op.RawOpCode:X8} at 0x{op.Address:X16}."); + } + } + + public static void Mrc(ArmEmitterContext context) + { + OpCode32System op = (OpCode32System)context.CurrOp; + + if (op.Coproc != 15 || op.Opc1 != 0) + { + InstEmit.Und(context); + + return; + } + + Operand result; + + switch (op.CRn) + { + case 13: // Process and Thread Info. + if (op.CRm != 0) + { + throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X} at 0x{op.Address:X} (0x{op.RawOpCode:X})."); + } + + result = op.Opc2 switch + { + 2 => EmitGetTpidrEl0(context), + 3 => EmitGetTpidrroEl0(context), + _ => throw new NotImplementedException( + $"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X})."), + }; + + break; + + default: + throw new NotImplementedException($"Unknown MRC 0x{op.RawOpCode:X} at 0x{op.Address:X}."); + } + + if (op.Rt == RegisterAlias.Aarch32Pc) + { + // Special behavior: copy NZCV flags into APSR. + EmitSetNzcv(context, result); + + return; + } + else + { + SetIntA32(context, op.Rt, result); + } + } + + public static void Mrrc(ArmEmitterContext context) + { + OpCode32System op = (OpCode32System)context.CurrOp; + + if (op.Coproc != 15) + { + InstEmit.Und(context); + + return; + } + + int opc = op.MrrcOp; + MethodInfo info = op.CRm switch + { + // Timer. + 14 => opc switch + { + 0 => typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)), + _ => throw new NotImplementedException($"Unknown MRRC Opc1 0x{opc:X} at 0x{op.Address:X} (0x{op.RawOpCode:X})."), + }, + _ => throw new NotImplementedException($"Unknown MRRC 0x{op.RawOpCode:X} at 0x{op.Address:X}."), + }; + Operand result = context.Call(info); + + SetIntA32(context, op.Rt, context.ConvertI64ToI32(result)); + SetIntA32(context, op.CRn, context.ConvertI64ToI32(context.ShiftRightUI(result, Const(32)))); + } + + public static void Mrs(ArmEmitterContext context) + { + OpCode32Mrs op = (OpCode32Mrs)context.CurrOp; + + if (op.R) + { + throw new NotImplementedException("SPSR"); + } + else + { + Operand spsr = context.ShiftLeft(GetFlag(PState.VFlag), Const((int)PState.VFlag)); + spsr = context.BitwiseOr(spsr, context.ShiftLeft(GetFlag(PState.CFlag), Const((int)PState.CFlag))); + spsr = context.BitwiseOr(spsr, context.ShiftLeft(GetFlag(PState.ZFlag), Const((int)PState.ZFlag))); + spsr = context.BitwiseOr(spsr, context.ShiftLeft(GetFlag(PState.NFlag), Const((int)PState.NFlag))); + spsr = context.BitwiseOr(spsr, context.ShiftLeft(GetFlag(PState.QFlag), Const((int)PState.QFlag))); + + // TODO: Remaining flags. + + SetIntA32(context, op.Rd, spsr); + } + } + + public static void Msr(ArmEmitterContext context) + { + OpCode32MsrReg op = (OpCode32MsrReg)context.CurrOp; + + if (op.R) + { + throw new NotImplementedException("SPSR"); + } + else + { + if ((op.Mask & 8) != 0) + { + Operand value = GetIntA32(context, op.Rn); + + EmitSetNzcv(context, value); + + Operand q = context.BitwiseAnd(context.ShiftRightUI(value, Const((int)PState.QFlag)), Const(1)); + + SetFlag(context, PState.QFlag, q); + } + + if ((op.Mask & 4) != 0) + { + throw new NotImplementedException("APSR_g"); + } + + if ((op.Mask & 2) != 0) + { + throw new NotImplementedException("CPSR_x"); + } + + if ((op.Mask & 1) != 0) + { + throw new NotImplementedException("CPSR_c"); + } + } + } + + public static void Nop(ArmEmitterContext context) { } + + public static void Vmrs(ArmEmitterContext context) + { + OpCode32SimdSpecial op = (OpCode32SimdSpecial)context.CurrOp; + + if (op.Rt == RegisterAlias.Aarch32Pc && op.Sreg == 0b0001) + { + // Special behavior: copy NZCV flags into APSR. + SetFlag(context, PState.VFlag, GetFpFlag(FPState.VFlag)); + SetFlag(context, PState.CFlag, GetFpFlag(FPState.CFlag)); + SetFlag(context, PState.ZFlag, GetFpFlag(FPState.ZFlag)); + SetFlag(context, PState.NFlag, GetFpFlag(FPState.NFlag)); + + return; + } + + switch (op.Sreg) + { + case 0b0000: // FPSID + throw new NotImplementedException("Supervisor Only"); + case 0b0001: // FPSCR + EmitGetFpscr(context); + return; + case 0b0101: // MVFR2 + throw new NotImplementedException("MVFR2"); + case 0b0110: // MVFR1 + throw new NotImplementedException("MVFR1"); + case 0b0111: // MVFR0 + throw new NotImplementedException("MVFR0"); + case 0b1000: // FPEXC + throw new NotImplementedException("Supervisor Only"); + default: + throw new NotImplementedException($"Unknown VMRS 0x{op.RawOpCode:X} at 0x{op.Address:X}."); + } + } + + public static void Vmsr(ArmEmitterContext context) + { + OpCode32SimdSpecial op = (OpCode32SimdSpecial)context.CurrOp; + + switch (op.Sreg) + { + case 0b0000: // FPSID + throw new NotImplementedException("Supervisor Only"); + case 0b0001: // FPSCR + EmitSetFpscr(context); + return; + case 0b0101: // MVFR2 + throw new NotImplementedException("MVFR2"); + case 0b0110: // MVFR1 + throw new NotImplementedException("MVFR1"); + case 0b0111: // MVFR0 + throw new NotImplementedException("MVFR0"); + case 0b1000: // FPEXC + throw new NotImplementedException("Supervisor Only"); + default: + throw new NotImplementedException($"Unknown VMSR 0x{op.RawOpCode:X} at 0x{op.Address:X}."); + } + } + + private static void EmitSetNzcv(ArmEmitterContext context, Operand t) + { + Operand v = context.BitwiseAnd(context.ShiftRightUI(t, Const((int)PState.VFlag)), Const(1)); + Operand c = context.BitwiseAnd(context.ShiftRightUI(t, Const((int)PState.CFlag)), Const(1)); + Operand z = context.BitwiseAnd(context.ShiftRightUI(t, Const((int)PState.ZFlag)), Const(1)); + Operand n = context.BitwiseAnd(context.ShiftRightUI(t, Const((int)PState.NFlag)), Const(1)); + + SetFlag(context, PState.VFlag, v); + SetFlag(context, PState.CFlag, c); + SetFlag(context, PState.ZFlag, z); + SetFlag(context, PState.NFlag, n); + } + + private static void EmitGetFpscr(ArmEmitterContext context) + { + OpCode32SimdSpecial op = (OpCode32SimdSpecial)context.CurrOp; + + Operand fpscr = Const(0); + + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + if (FPSCR.Mask.HasFlag((FPSCR)(1u << flag))) + { + fpscr = context.BitwiseOr(fpscr, context.ShiftLeft(GetFpFlag((FPState)flag), Const(flag))); + } + } + + SetIntA32(context, op.Rt, fpscr); + } + + private static void EmitSetFpscr(ArmEmitterContext context) + { + OpCode32SimdSpecial op = (OpCode32SimdSpecial)context.CurrOp; + + Operand fpscr = GetIntA32(context, op.Rt); + + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + if (FPSCR.Mask.HasFlag((FPSCR)(1u << flag))) + { + 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)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstName.cs b/src/ARMeilleure/Instructions/InstName.cs new file mode 100644 index 00000000..74c33155 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstName.cs @@ -0,0 +1,699 @@ +namespace ARMeilleure.Instructions +{ + enum InstName + { + // Base (AArch64) + Adc, + Adcs, + Add, + Adds, + Adr, + Adrp, + And, + Ands, + Asrv, + B, + B_Cond, + Bfm, + Bic, + Bics, + Bl, + Blr, + Br, + Brk, + Cbnz, + Cbz, + Ccmn, + Ccmp, + Clrex, + Cls, + Clz, + Crc32b, + Crc32h, + Crc32w, + Crc32x, + Crc32cb, + Crc32ch, + Crc32cw, + Crc32cx, + Csdb, + Csel, + Csinc, + Csinv, + Csneg, + Dmb, + Dsb, + Eon, + Eor, + Esb, + Extr, + Hint, + Isb, + It, + Ldar, + Ldaxp, + Ldaxr, + Ldp, + Ldr, + Ldr_Literal, + Ldrs, + Ldxr, + Ldxp, + Lslv, + Lsrv, + Madd, + Movk, + Movn, + Movz, + Mrs, + Msr, + Msub, + Nop, + Orn, + Orr, + Prfm, + Rbit, + Ret, + Rev16, + Rev32, + Rev64, + Rorv, + Sbc, + Sbcs, + Sbfm, + Sdiv, + Sel, + Sev, + Sevl, + Shsub8, + Smaddl, + Smsubl, + Smulh, + Smull, + Smulw_, + Ssat, + Ssat16, + Stlr, + Stlxp, + Stlxr, + Stp, + Str, + Stxp, + Stxr, + Sub, + Subs, + Svc, + Sxtb, + Sxth, + Sys, + Tbnz, + Tbz, + Tsb, + Ubfm, + Udiv, + Umaddl, + Umsubl, + Umulh, + Und, + Wfe, + Wfi, + Yield, + + // FP & SIMD (AArch64) + Abs_S, + Abs_V, + Add_S, + Add_V, + Addhn_V, + Addp_S, + Addp_V, + Addv_V, + Aesd_V, + Aese_V, + Aesimc_V, + Aesmc_V, + And_V, + Bic_V, + Bic_Vi, + Bif_V, + Bit_V, + Bsl_V, + Cls_V, + Clz_V, + Cmeq_S, + Cmeq_V, + Cmge_S, + Cmge_V, + Cmgt_S, + Cmgt_V, + Cmhi_S, + Cmhi_V, + Cmhs_S, + Cmhs_V, + Cmle_S, + Cmle_V, + Cmlt_S, + Cmlt_V, + Cmtst_S, + Cmtst_V, + Cnt_V, + Dup_Gp, + Dup_S, + Dup_V, + Eor_V, + Ext_V, + Fabd_S, + Fabd_V, + Fabs_S, + Fabs_V, + Facge_S, + Facge_V, + Facgt_S, + Facgt_V, + Fadd_S, + Fadd_V, + Faddp_S, + Faddp_V, + Fccmp_S, + Fccmpe_S, + Fcmeq_S, + Fcmeq_V, + Fcmge_S, + Fcmge_V, + Fcmgt_S, + Fcmgt_V, + Fcmle_S, + Fcmle_V, + Fcmlt_S, + Fcmlt_V, + Fcmp_S, + Fcmpe_S, + Fcsel_S, + Fcvt_S, + Fcvtas_Gp, + Fcvtas_S, + Fcvtas_V, + Fcvtau_Gp, + Fcvtau_S, + Fcvtau_V, + Fcvtl_V, + Fcvtms_Gp, + Fcvtms_V, + Fcvtmu_Gp, + Fcvtn_V, + Fcvtns_Gp, + Fcvtns_S, + Fcvtns_V, + Fcvtnu_S, + Fcvtnu_V, + Fcvtps_Gp, + Fcvtpu_Gp, + Fcvtzs_Gp, + Fcvtzs_Gp_Fixed, + Fcvtzs_S, + Fcvtzs_V, + Fcvtzs_V_Fixed, + Fcvtzu_Gp, + Fcvtzu_Gp_Fixed, + Fcvtzu_S, + Fcvtzu_V, + Fcvtzu_V_Fixed, + Fdiv_S, + Fdiv_V, + Fmadd_S, + Fmax_S, + Fmax_V, + Fmaxnm_S, + Fmaxnm_V, + Fmaxnmp_S, + Fmaxnmp_V, + Fmaxnmv_V, + Fmaxp_S, + Fmaxp_V, + Fmaxv_V, + Fmin_S, + Fmin_V, + Fminnm_S, + Fminnm_V, + Fminnmp_S, + Fminnmp_V, + Fminnmv_V, + Fminp_S, + Fminp_V, + Fminv_V, + Fmla_Se, + Fmla_V, + Fmla_Ve, + Fmls_Se, + Fmls_V, + Fmls_Ve, + Fmov_S, + Fmov_Si, + Fmov_Vi, + Fmov_Ftoi, + Fmov_Itof, + Fmov_Ftoi1, + Fmov_Itof1, + Fmsub_S, + Fmul_S, + Fmul_Se, + Fmul_V, + Fmul_Ve, + Fmulx_S, + Fmulx_Se, + Fmulx_V, + Fmulx_Ve, + Fneg_S, + Fneg_V, + Fnmadd_S, + Fnmsub_S, + Fnmul_S, + Frecpe_S, + Frecpe_V, + Frecps_S, + Frecps_V, + Frecpx_S, + Frinta_S, + Frinta_V, + Frinti_S, + Frinti_V, + Frintm_S, + Frintm_V, + Frintn_S, + Frintn_V, + Frintp_S, + Frintp_V, + Frintx_S, + Frintx_V, + Frintz_S, + Frintz_V, + Frsqrte_S, + Frsqrte_V, + Frsqrts_S, + Frsqrts_V, + Fsqrt_S, + Fsqrt_V, + Fsub_S, + Fsub_V, + Ins_Gp, + Ins_V, + Ld__Vms, + Ld__Vss, + Mla_V, + Mla_Ve, + Mls_V, + Mls_Ve, + Movi_V, + Mul_V, + Mul_Ve, + Mvni_V, + Neg_S, + Neg_V, + Not_V, + Orn_V, + Orr_V, + Orr_Vi, + Pmull_V, + Raddhn_V, + Rbit_V, + Rev16_V, + Rev32_V, + Rev64_V, + Rshrn_V, + Rsubhn_V, + Saba_V, + Sabal_V, + Sabd_V, + Sabdl_V, + Sadalp_V, + Saddl_V, + Saddlp_V, + Saddlv_V, + Saddw_V, + Scvtf_Gp, + Scvtf_Gp_Fixed, + Scvtf_S, + Scvtf_S_Fixed, + Scvtf_V, + Scvtf_V_Fixed, + Sha1c_V, + Sha1h_V, + Sha1m_V, + Sha1p_V, + Sha1su0_V, + Sha1su1_V, + Sha256h_V, + Sha256h2_V, + Sha256su0_V, + Sha256su1_V, + Shadd_V, + Shl_S, + Shl_V, + Shll_V, + Shrn_V, + Shsub_V, + Sli_S, + Sli_V, + Smax_V, + Smaxp_V, + Smaxv_V, + Smin_V, + Sminp_V, + Sminv_V, + Smlal_V, + Smlal_Ve, + Smlsl_V, + Smlsl_Ve, + Smov_S, + Smull_V, + Smull_Ve, + Sqabs_S, + Sqabs_V, + Sqadd_S, + Sqadd_V, + Sqdmulh_S, + Sqdmulh_V, + Sqdmulh_Ve, + Sqneg_S, + Sqneg_V, + Sqrdmulh_S, + Sqrdmulh_V, + Sqrdmulh_Ve, + Sqrshl_V, + Sqrshrn_S, + Sqrshrn_V, + Sqrshrun_S, + Sqrshrun_V, + Sqshl_Si, + Sqshl_V, + Sqshl_Vi, + Sqshrn_S, + Sqshrn_V, + Sqshrun_S, + Sqshrun_V, + Sqsub_S, + Sqsub_V, + Sqxtn_S, + Sqxtn_V, + Sqxtun_S, + Sqxtun_V, + Srhadd_V, + Sri_S, + Sri_V, + Srshl_V, + Srshr_S, + Srshr_V, + Srsra_S, + Srsra_V, + Sshl_S, + Sshl_V, + Sshll_V, + Sshr_S, + Sshr_V, + Ssra_S, + Ssra_V, + Ssubl_V, + Ssubw_V, + St__Vms, + St__Vss, + Sub_S, + Sub_V, + Subhn_V, + Suqadd_S, + Suqadd_V, + Tbl_V, + Tbx_V, + Trn1_V, + Trn2_V, + Uaba_V, + Uabal_V, + Uabd_V, + Uabdl_V, + Uadalp_V, + Uaddl_V, + Uaddlp_V, + Uaddlv_V, + Uaddw_V, + Ucvtf_Gp, + Ucvtf_Gp_Fixed, + Ucvtf_S, + Ucvtf_S_Fixed, + Ucvtf_V, + Ucvtf_V_Fixed, + Uhadd_V, + Uhsub_V, + Umax_V, + Umaxp_V, + Umaxv_V, + Umin_V, + Uminp_V, + Uminv_V, + Umlal_V, + Umlal_Ve, + Umlsl_V, + Umlsl_Ve, + Umov_S, + Umull_V, + Umull_Ve, + Uqadd_S, + Uqadd_V, + Uqrshl_V, + Uqrshrn_S, + Uqrshrn_V, + Uqshl_V, + Uqshrn_S, + Uqshrn_V, + Uqsub_S, + Uqsub_V, + Uqxtn_S, + Uqxtn_V, + Urhadd_V, + Urshl_V, + Urshr_S, + Urshr_V, + Ursra_S, + Ursra_V, + Ushl_S, + Ushl_V, + Ushll_V, + Ushr_S, + Ushr_V, + Usqadd_S, + Usqadd_V, + Usra_S, + Usra_V, + Usubl_V, + Usubw_V, + Uzp1_V, + Uzp2_V, + Xtn_V, + Zip1_V, + Zip2_V, + + // Base (AArch32) + Bfc, + Bfi, + Blx, + Bx, + Cmp, + Cmn, + Movt, + Mul, + Lda, + Ldab, + Ldaex, + Ldaexb, + Ldaexd, + Ldaexh, + Ldah, + Ldm, + Ldrb, + Ldrd, + Ldrex, + Ldrexb, + Ldrexd, + Ldrexh, + Ldrh, + Ldrsb, + Ldrsh, + Mcr, + Mla, + Mls, + Mov, + Mrc, + Mrrc, + Mvn, + Pkh, + Pld, + Pop, + Push, + Qadd16, + Rev, + Revsh, + Rsb, + Rsc, + Sadd8, + Sbfx, + Shadd8, + Smla__, + Smlal, + Smlal__, + Smlaw_, + Smmla, + Smmls, + Smul__, + Smmul, + Ssub8, + Stl, + Stlb, + Stlex, + Stlexb, + Stlexd, + Stlexh, + Stlh, + Stm, + Strb, + Strd, + Strex, + Strexb, + Strexd, + Strexh, + Strh, + Sxtb16, + Tbb, + Tbh, + Teq, + Trap, + Tst, + Uadd8, + Ubfx, + Uhadd8, + Uhsub8, + Umaal, + Umlal, + Umull, + Uqadd16, + Uqadd8, + Uqsub16, + Uqsub8, + Usat, + Usat16, + Usub8, + Uxtb, + Uxtb16, + Uxth, + + // FP & SIMD (AArch32) + Vabd, + Vabdl, + Vabs, + Vadd, + Vaddl, + Vaddw, + Vand, + Vbic, + Vbif, + Vbit, + Vbsl, + Vceq, + Vcge, + Vcgt, + Vcle, + Vclt, + Vcmp, + Vcmpe, + Vcnt, + Vcvt, + Vdiv, + Vdup, + Veor, + Vext, + Vfma, + Vfms, + Vfnma, + Vfnms, + Vhadd, + Vld1, + Vld2, + Vld3, + Vld4, + Vldm, + Vldr, + Vmax, + Vmaxnm, + Vmin, + Vminnm, + Vmla, + Vmlal, + Vmls, + Vmlsl, + Vmov, + Vmovl, + Vmovn, + Vmrs, + Vmsr, + Vmul, + Vmull, + Vmvn, + Vneg, + Vnmul, + Vnmla, + Vnmls, + Vorn, + Vorr, + Vpadd, + Vpadal, + Vpaddl, + Vpmax, + Vpmin, + Vqadd, + Vqdmulh, + Vqmovn, + Vqmovun, + Vqrdmulh, + Vqrshrn, + Vqrshrun, + Vqshrn, + Vqshrun, + Vqsub, + Vrev, + Vrhadd, + Vrint, + Vrinta, + Vrintm, + Vrintn, + Vrintp, + Vrintr, + Vrintx, + Vrshr, + Vrshrn, + Vsel, + Vshl, + Vshll, + Vshr, + Vshrn, + Vsli, + Vst1, + Vst2, + Vst3, + Vst4, + Vstm, + Vstr, + Vsqrt, + Vrecpe, + Vrecps, + Vrsqrte, + Vrsqrts, + Vrsra, + Vsra, + Vsub, + Vsubl, + Vsubw, + Vswp, + Vtbl, + Vtrn, + Vtst, + Vuzp, + Vzip, + } +} diff --git a/src/ARMeilleure/Instructions/NativeInterface.cs b/src/ARMeilleure/Instructions/NativeInterface.cs new file mode 100644 index 00000000..0cd3754f --- /dev/null +++ b/src/ARMeilleure/Instructions/NativeInterface.cs @@ -0,0 +1,195 @@ +using ARMeilleure.Memory; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; + +namespace ARMeilleure.Instructions +{ + static class NativeInterface + { + private class ThreadContext + { + public ExecutionContext Context { get; } + public IMemoryManager Memory { get; } + public Translator Translator { get; } + + public ThreadContext(ExecutionContext context, IMemoryManager memory, Translator translator) + { + Context = context; + Memory = memory; + Translator = translator; + } + } + + [ThreadStatic] + private static ThreadContext Context; + + public static void RegisterThread(ExecutionContext context, IMemoryManager memory, Translator translator) + { + Context = new ThreadContext(context, memory, translator); + } + + public static void UnregisterThread() + { + Context = null; + } + + public static void Break(ulong address, int imm) + { + Statistics.PauseTimer(); + + GetContext().OnBreak(address, imm); + + Statistics.ResumeTimer(); + } + + public static void SupervisorCall(ulong address, int imm) + { + Statistics.PauseTimer(); + + GetContext().OnSupervisorCall(address, imm); + + Statistics.ResumeTimer(); + } + + public static void Undefined(ulong address, int opCode) + { + Statistics.PauseTimer(); + + GetContext().OnUndefined(address, opCode); + + Statistics.ResumeTimer(); + } + + #region "System registers" + public static ulong GetCtrEl0() + { + return GetContext().CtrEl0; + } + + public static ulong GetDczidEl0() + { + return GetContext().DczidEl0; + } + + public static ulong GetCntfrqEl0() + { + return GetContext().CntfrqEl0; + } + + public static ulong GetCntpctEl0() + { + return GetContext().CntpctEl0; + } + + public static ulong GetCntvctEl0() + { + return GetContext().CntvctEl0; + } + #endregion + + #region "Read" + public static byte ReadByte(ulong address) + { + return GetMemoryManager().ReadGuest(address); + } + + public static ushort ReadUInt16(ulong address) + { + return GetMemoryManager().ReadGuest(address); + } + + public static uint ReadUInt32(ulong address) + { + return GetMemoryManager().ReadGuest(address); + } + + public static ulong ReadUInt64(ulong address) + { + return GetMemoryManager().ReadGuest(address); + } + + public static V128 ReadVector128(ulong address) + { + return GetMemoryManager().ReadGuest(address); + } + #endregion + + #region "Write" + public static void WriteByte(ulong address, byte value) + { + GetMemoryManager().WriteGuest(address, value); + } + + public static void WriteUInt16(ulong address, ushort value) + { + GetMemoryManager().WriteGuest(address, value); + } + + public static void WriteUInt32(ulong address, uint value) + { + GetMemoryManager().WriteGuest(address, value); + } + + public static void WriteUInt64(ulong address, ulong value) + { + GetMemoryManager().WriteGuest(address, value); + } + + public static void WriteVector128(ulong address, V128 value) + { + GetMemoryManager().WriteGuest(address, value); + } + #endregion + + public static void EnqueueForRejit(ulong address) + { + Context.Translator.EnqueueForRejit(address, GetContext().ExecutionMode); + } + + public static void SignalMemoryTracking(ulong address, ulong size, bool write) + { + GetMemoryManager().SignalMemoryTracking(address, size, write); + } + + public static void ThrowInvalidMemoryAccess(ulong address) + { + throw new InvalidAccessException(address); + } + + public static ulong GetFunctionAddress(ulong address) + { + TranslatedFunction function = Context.Translator.GetOrTranslate(address, GetContext().ExecutionMode); + + return (ulong)function.FuncPointer.ToInt64(); + } + + public static void InvalidateCacheLine(ulong address) + { + Context.Translator.InvalidateJitCacheRegion(address, InstEmit.DczSizeInBytes); + } + + public static bool CheckSynchronization() + { + Statistics.PauseTimer(); + + ExecutionContext context = GetContext(); + + context.CheckInterrupt(); + + Statistics.ResumeTimer(); + + return context.Running; + } + + public static ExecutionContext GetContext() + { + return Context.Context; + } + + public static IMemoryManager GetMemoryManager() + { + return Context.Memory; + } + } +} diff --git a/src/ARMeilleure/Instructions/SoftFallback.cs b/src/ARMeilleure/Instructions/SoftFallback.cs new file mode 100644 index 00000000..c4fe677b --- /dev/null +++ b/src/ARMeilleure/Instructions/SoftFallback.cs @@ -0,0 +1,648 @@ +using ARMeilleure.State; +using System; + +namespace ARMeilleure.Instructions +{ + static class SoftFallback + { + #region "ShrImm64" + public static long SignedShrImm64(long value, long roundConst, int shift) + { + if (roundConst == 0L) + { + if (shift <= 63) + { + return value >> shift; + } + else /* if (shift == 64) */ + { + if (value < 0L) + { + return -1L; + } + else /* if (value >= 0L) */ + { + return 0L; + } + } + } + else /* if (roundConst == 1L << (shift - 1)) */ + { + if (shift <= 63) + { + long add = value + roundConst; + + if ((~value & (value ^ add)) < 0L) + { + return (long)((ulong)add >> shift); + } + else + { + return add >> shift; + } + } + else /* if (shift == 64) */ + { + return 0L; + } + } + } + + public static ulong UnsignedShrImm64(ulong value, long roundConst, int shift) + { + if (roundConst == 0L) + { + if (shift <= 63) + { + return value >> shift; + } + else /* if (shift == 64) */ + { + return 0UL; + } + } + else /* if (roundConst == 1L << (shift - 1)) */ + { + ulong add = value + (ulong)roundConst; + + if ((add < value) && (add < (ulong)roundConst)) + { + if (shift <= 63) + { + return (add >> shift) | (0x8000000000000000UL >> (shift - 1)); + } + else /* if (shift == 64) */ + { + return 1UL; + } + } + else + { + if (shift <= 63) + { + return add >> shift; + } + else /* if (shift == 64) */ + { + return 0UL; + } + } + } + } + #endregion + + #region "Saturation" + public static int SatF32ToS32(float value) + { + if (float.IsNaN(value)) + { + return 0; + } + + return value >= int.MaxValue ? int.MaxValue : + value <= int.MinValue ? int.MinValue : (int)value; + } + + public static long SatF32ToS64(float value) + { + if (float.IsNaN(value)) + { + return 0; + } + + return value >= long.MaxValue ? long.MaxValue : + value <= long.MinValue ? long.MinValue : (long)value; + } + + public static uint SatF32ToU32(float value) + { + if (float.IsNaN(value)) + { + return 0; + } + + return value >= uint.MaxValue ? uint.MaxValue : + value <= uint.MinValue ? uint.MinValue : (uint)value; + } + + public static ulong SatF32ToU64(float value) + { + if (float.IsNaN(value)) + { + return 0; + } + + return value >= ulong.MaxValue ? ulong.MaxValue : + value <= ulong.MinValue ? ulong.MinValue : (ulong)value; + } + + public static int SatF64ToS32(double value) + { + if (double.IsNaN(value)) + { + return 0; + } + + return value >= int.MaxValue ? int.MaxValue : + value <= int.MinValue ? int.MinValue : (int)value; + } + + public static long SatF64ToS64(double value) + { + if (double.IsNaN(value)) + { + return 0; + } + + return value >= long.MaxValue ? long.MaxValue : + value <= long.MinValue ? long.MinValue : (long)value; + } + + public static uint SatF64ToU32(double value) + { + if (double.IsNaN(value)) + { + return 0; + } + + return value >= uint.MaxValue ? uint.MaxValue : + value <= uint.MinValue ? uint.MinValue : (uint)value; + } + + public static ulong SatF64ToU64(double value) + { + if (double.IsNaN(value)) + { + return 0; + } + + return value >= ulong.MaxValue ? ulong.MaxValue : + value <= ulong.MinValue ? ulong.MinValue : (ulong)value; + } + #endregion + + #region "Count" + public static ulong CountLeadingSigns(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.). + { + value ^= value >> 1; + + int highBit = size - 2; + + for (int bit = highBit; bit >= 0; bit--) + { + if (((int)(value >> bit) & 0b1) != 0) + { + return (ulong)(highBit - bit); + } + } + + return (ulong)(size - 1); + } + + private static ReadOnlySpan ClzNibbleTbl => new byte[] { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }; + + public static ulong CountLeadingZeros(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.). + { + if (value == 0ul) + { + return (ulong)size; + } + + int nibbleIdx = size; + int preCount, count = 0; + + do + { + nibbleIdx -= 4; + preCount = ClzNibbleTbl[(int)(value >> nibbleIdx) & 0b1111]; + count += preCount; + } + while (preCount == 4); + + return (ulong)count; + } + #endregion + + #region "Table" + public static V128 Tbl1(V128 vector, int bytes, V128 tb0) + { + return TblOrTbx(default, vector, bytes, tb0); + } + + public static V128 Tbl2(V128 vector, int bytes, V128 tb0, V128 tb1) + { + return TblOrTbx(default, vector, bytes, tb0, tb1); + } + + public static V128 Tbl3(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2) + { + return TblOrTbx(default, vector, bytes, tb0, tb1, tb2); + } + + public static V128 Tbl4(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3) + { + return TblOrTbx(default, vector, bytes, tb0, tb1, tb2, tb3); + } + + public static V128 Tbx1(V128 dest, V128 vector, int bytes, V128 tb0) + { + return TblOrTbx(dest, vector, bytes, tb0); + } + + public static V128 Tbx2(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1) + { + return TblOrTbx(dest, vector, bytes, tb0, tb1); + } + + public static V128 Tbx3(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2) + { + return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2); + } + + public static V128 Tbx4(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3) + { + return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2, tb3); + } + + private static V128 TblOrTbx(V128 dest, V128 vector, int bytes, params V128[] tb) + { + byte[] res = new byte[16]; + + if (dest != default) + { + Buffer.BlockCopy(dest.ToArray(), 0, res, 0, bytes); + } + + byte[] table = new byte[tb.Length * 16]; + + for (byte index = 0; index < tb.Length; index++) + { + Buffer.BlockCopy(tb[index].ToArray(), 0, table, index * 16, 16); + } + + byte[] v = vector.ToArray(); + + for (byte index = 0; index < bytes; index++) + { + byte tblIndex = v[index]; + + if (tblIndex < table.Length) + { + res[index] = table[tblIndex]; + } + } + + return new V128(res); + } + #endregion + + #region "Crc32" + private const uint Crc32RevPoly = 0xedb88320; + private const uint Crc32cRevPoly = 0x82f63b78; + + public static uint Crc32b(uint crc, byte value) => Crc32(crc, Crc32RevPoly, value); + public static uint Crc32h(uint crc, ushort value) => Crc32h(crc, Crc32RevPoly, value); + public static uint Crc32w(uint crc, uint value) => Crc32w(crc, Crc32RevPoly, value); + public static uint Crc32x(uint crc, ulong value) => Crc32x(crc, Crc32RevPoly, value); + + public static uint Crc32cb(uint crc, byte value) => Crc32(crc, Crc32cRevPoly, value); + public static uint Crc32ch(uint crc, ushort value) => Crc32h(crc, Crc32cRevPoly, value); + public static uint Crc32cw(uint crc, uint value) => Crc32w(crc, Crc32cRevPoly, value); + public static uint Crc32cx(uint crc, ulong value) => Crc32x(crc, Crc32cRevPoly, value); + + private static uint Crc32h(uint crc, uint poly, ushort val) + { + crc = Crc32(crc, poly, (byte)(val >> 0)); + crc = Crc32(crc, poly, (byte)(val >> 8)); + + return crc; + } + + private static uint Crc32w(uint crc, uint poly, uint val) + { + crc = Crc32(crc, poly, (byte)(val >> 0)); + crc = Crc32(crc, poly, (byte)(val >> 8)); + crc = Crc32(crc, poly, (byte)(val >> 16)); + crc = Crc32(crc, poly, (byte)(val >> 24)); + + return crc; + } + + private static uint Crc32x(uint crc, uint poly, ulong val) + { + crc = Crc32(crc, poly, (byte)(val >> 0)); + crc = Crc32(crc, poly, (byte)(val >> 8)); + crc = Crc32(crc, poly, (byte)(val >> 16)); + crc = Crc32(crc, poly, (byte)(val >> 24)); + crc = Crc32(crc, poly, (byte)(val >> 32)); + crc = Crc32(crc, poly, (byte)(val >> 40)); + crc = Crc32(crc, poly, (byte)(val >> 48)); + crc = Crc32(crc, poly, (byte)(val >> 56)); + + return crc; + } + + private static uint Crc32(uint crc, uint poly, byte val) + { + crc ^= val; + + for (int bit = 7; bit >= 0; bit--) + { + uint mask = (uint)(-(int)(crc & 1)); + + crc = (crc >> 1) ^ (poly & mask); + } + + return crc; + } + #endregion + + #region "Aes" + public static V128 Decrypt(V128 value, V128 roundKey) + { + return CryptoHelper.AesInvSubBytes(CryptoHelper.AesInvShiftRows(value ^ roundKey)); + } + + public static V128 Encrypt(V128 value, V128 roundKey) + { + return CryptoHelper.AesSubBytes(CryptoHelper.AesShiftRows(value ^ roundKey)); + } + + public static V128 InverseMixColumns(V128 value) + { + return CryptoHelper.AesInvMixColumns(value); + } + + public static V128 MixColumns(V128 value) + { + return CryptoHelper.AesMixColumns(value); + } + #endregion + + #region "Sha1" + public static V128 HashChoose(V128 hash_abcd, uint hash_e, V128 wk) + { + for (int e = 0; e <= 3; e++) + { + uint t = ShaChoose(hash_abcd.Extract(1), + hash_abcd.Extract(2), + hash_abcd.Extract(3)); + + hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e); + + t = Rol(hash_abcd.Extract(1), 30); + + hash_abcd.Insert(1, t); + + Rol32_160(ref hash_e, ref hash_abcd); + } + + return hash_abcd; + } + + public static uint FixedRotate(uint hash_e) + { + return hash_e.Rol(30); + } + + public static V128 HashMajority(V128 hash_abcd, uint hash_e, V128 wk) + { + for (int e = 0; e <= 3; e++) + { + uint t = ShaMajority(hash_abcd.Extract(1), + hash_abcd.Extract(2), + hash_abcd.Extract(3)); + + hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e); + + t = Rol(hash_abcd.Extract(1), 30); + + hash_abcd.Insert(1, t); + + Rol32_160(ref hash_e, ref hash_abcd); + } + + return hash_abcd; + } + + public static V128 HashParity(V128 hash_abcd, uint hash_e, V128 wk) + { + for (int e = 0; e <= 3; e++) + { + uint t = ShaParity(hash_abcd.Extract(1), + hash_abcd.Extract(2), + hash_abcd.Extract(3)); + + hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e); + + t = Rol(hash_abcd.Extract(1), 30); + + hash_abcd.Insert(1, t); + + Rol32_160(ref hash_e, ref hash_abcd); + } + + return hash_abcd; + } + + public static V128 Sha1SchedulePart1(V128 w0_3, V128 w4_7, V128 w8_11) + { + ulong t2 = w4_7.Extract(0); + ulong t1 = w0_3.Extract(1); + + V128 result = new(t1, t2); + + return result ^ (w0_3 ^ w8_11); + } + + public static V128 Sha1SchedulePart2(V128 tw0_3, V128 w12_15) + { + V128 t = tw0_3 ^ (w12_15 >> 32); + + uint tE0 = t.Extract(0); + uint tE1 = t.Extract(1); + uint tE2 = t.Extract(2); + uint tE3 = t.Extract(3); + + return new V128(tE0.Rol(1), tE1.Rol(1), tE2.Rol(1), tE3.Rol(1) ^ tE0.Rol(2)); + } + + private static void Rol32_160(ref uint y, ref V128 x) + { + uint xE3 = x.Extract(3); + + x <<= 32; + x.Insert(0, y); + + y = xE3; + } + + private static uint ShaChoose(uint x, uint y, uint z) + { + return ((y ^ z) & x) ^ z; + } + + private static uint ShaMajority(uint x, uint y, uint z) + { + return (x & y) | ((x | y) & z); + } + + private static uint ShaParity(uint x, uint y, uint z) + { + return x ^ y ^ z; + } + + private static uint Rol(this uint value, int count) + { + return (value << count) | (value >> (32 - count)); + } + #endregion + + #region "Sha256" + public static V128 HashLower(V128 hash_abcd, V128 hash_efgh, V128 wk) + { + return Sha256Hash(hash_abcd, hash_efgh, wk, part1: true); + } + + public static V128 HashUpper(V128 hash_abcd, V128 hash_efgh, V128 wk) + { + return Sha256Hash(hash_abcd, hash_efgh, wk, part1: false); + } + + public static V128 Sha256SchedulePart1(V128 w0_3, V128 w4_7) + { + V128 result = new(); + + for (int e = 0; e <= 3; e++) + { + uint elt = (e <= 2 ? w0_3 : w4_7).Extract(e <= 2 ? e + 1 : 0); + + elt = elt.Ror(7) ^ elt.Ror(18) ^ elt.Lsr(3); + + elt += w0_3.Extract(e); + + result.Insert(e, elt); + } + + return result; + } + + public static V128 Sha256SchedulePart2(V128 w0_3, V128 w8_11, V128 w12_15) + { + V128 result = new(); + + ulong t1 = w12_15.Extract(1); + + for (int e = 0; e <= 1; e++) + { + uint elt = t1.ULongPart(e); + + elt = elt.Ror(17) ^ elt.Ror(19) ^ elt.Lsr(10); + + elt += w0_3.Extract(e) + w8_11.Extract(e + 1); + + result.Insert(e, elt); + } + + t1 = result.Extract(0); + + for (int e = 2; e <= 3; e++) + { + uint elt = t1.ULongPart(e - 2); + + elt = elt.Ror(17) ^ elt.Ror(19) ^ elt.Lsr(10); + + elt += w0_3.Extract(e) + (e == 2 ? w8_11 : w12_15).Extract(e == 2 ? 3 : 0); + + result.Insert(e, elt); + } + + return result; + } + + private static V128 Sha256Hash(V128 x, V128 y, V128 w, bool part1) + { + for (int e = 0; e <= 3; e++) + { + uint chs = ShaChoose(y.Extract(0), + y.Extract(1), + y.Extract(2)); + + uint maj = ShaMajority(x.Extract(0), + x.Extract(1), + x.Extract(2)); + + uint t1 = y.Extract(3) + ShaHashSigma1(y.Extract(0)) + chs + w.Extract(e); + + uint t2 = t1 + x.Extract(3); + + x.Insert(3, t2); + + t2 = t1 + ShaHashSigma0(x.Extract(0)) + maj; + + y.Insert(3, t2); + + Rol32_256(ref y, ref x); + } + + return part1 ? x : y; + } + + private static void Rol32_256(ref V128 y, ref V128 x) + { + uint yE3 = y.Extract(3); + uint xE3 = x.Extract(3); + + y <<= 32; + x <<= 32; + + y.Insert(0, xE3); + x.Insert(0, yE3); + } + + private static uint ShaHashSigma0(uint x) + { + return x.Ror(2) ^ x.Ror(13) ^ x.Ror(22); + } + + private static uint ShaHashSigma1(uint x) + { + return x.Ror(6) ^ x.Ror(11) ^ x.Ror(25); + } + + private static uint Ror(this uint value, int count) + { + return (value >> count) | (value << (32 - count)); + } + + private static uint Lsr(this uint value, int count) + { + return value >> count; + } + + private static uint ULongPart(this ulong value, int part) + { + return part == 0 + ? (uint)(value & 0xFFFFFFFFUL) + : (uint)(value >> 32); + } + #endregion + + public static V128 PolynomialMult64_128(ulong op1, ulong op2) + { + V128 result = V128.Zero; + + V128 op2_128 = new(op2, 0); + + for (int i = 0; i < 64; i++) + { + if (((op1 >> i) & 1) == 1) + { + result ^= op2_128 << i; + } + } + + return result; + } + } +} diff --git a/src/ARMeilleure/Instructions/SoftFloat.cs b/src/ARMeilleure/Instructions/SoftFloat.cs new file mode 100644 index 00000000..a67349e6 --- /dev/null +++ b/src/ARMeilleure/Instructions/SoftFloat.cs @@ -0,0 +1,3526 @@ +using ARMeilleure.State; +using System; +using System.Diagnostics; + +namespace ARMeilleure.Instructions +{ + static class SoftFloat + { + static SoftFloat() + { + RecipEstimateTable = BuildRecipEstimateTable(); + RecipSqrtEstimateTable = BuildRecipSqrtEstimateTable(); + } + + public static readonly byte[] RecipEstimateTable; + public static readonly byte[] RecipSqrtEstimateTable; + + private static byte[] BuildRecipEstimateTable() + { + byte[] tbl = new byte[256]; + + for (int idx = 0; idx < 256; idx++) + { + uint src = (uint)idx + 256u; + + Debug.Assert(256u <= src && src < 512u); + + src = (src << 1) + 1u; + + uint aux = (1u << 19) / src; + + uint dst = (aux + 1u) >> 1; + + Debug.Assert(256u <= dst && dst < 512u); + + tbl[idx] = (byte)(dst - 256u); + } + + return tbl; + } + + private static byte[] BuildRecipSqrtEstimateTable() + { + byte[] tbl = new byte[384]; + + for (int idx = 0; idx < 384; idx++) + { + uint src = (uint)idx + 128u; + + Debug.Assert(128u <= src && src < 512u); + + if (src < 256u) + { + src = (src << 1) + 1u; + } + else + { + src = (src >> 1) << 1; + src = (src + 1u) << 1; + } + + uint aux = 512u; + + while (src * (aux + 1u) * (aux + 1u) < (1u << 28)) + { + aux++; + } + + uint dst = (aux + 1u) >> 1; + + Debug.Assert(256u <= dst && dst < 512u); + + tbl[idx] = (byte)(dst - 256u); + } + + return tbl; + } + + public static void FPProcessException(FPException exc, ExecutionContext context) + { + FPProcessException(exc, context, context.Fpcr); + } + + public static void FPProcessException(FPException exc, ExecutionContext context, FPCR fpcr) + { + int enable = (int)exc + 8; + + if ((fpcr & (FPCR)(1 << enable)) != 0) + { + throw new NotImplementedException("Floating-point trap handling."); + } + else + { + context.Fpsr |= (FPSR)(1 << (int)exc); + } + } + + public static FPRoundingMode GetRoundingMode(this FPCR fpcr) + { + const int RModeShift = 22; + + return (FPRoundingMode)(((uint)fpcr >> RModeShift) & 3u); + } + } + + static class SoftFloat16 + { + public static ushort FPDefaultNaN() + { + return (ushort)0x7E00u; + } + + public static ushort FPInfinity(bool sign) + { + return sign ? (ushort)0xFC00u : (ushort)0x7C00u; + } + + public static ushort FPZero(bool sign) + { + return sign ? (ushort)0x8000u : (ushort)0x0000u; + } + + public static ushort FPMaxNormal(bool sign) + { + return sign ? (ushort)0xFBFFu : (ushort)0x7BFFu; + } + + public static double FPUnpackCv( + this ushort valueBits, + out FPType type, + out bool sign, + ExecutionContext context) + { + sign = (~(uint)valueBits & 0x8000u) == 0u; + + uint exp16 = ((uint)valueBits & 0x7C00u) >> 10; + uint frac16 = (uint)valueBits & 0x03FFu; + + double real; + + if (exp16 == 0u) + { + if (frac16 == 0u) + { + type = FPType.Zero; + real = 0d; + } + else + { + type = FPType.Nonzero; // Subnormal. + real = Math.Pow(2d, -14) * ((double)frac16 * Math.Pow(2d, -10)); + } + } + else if (exp16 == 0x1Fu && (context.Fpcr & FPCR.Ahp) == 0) + { + if (frac16 == 0u) + { + type = FPType.Infinity; + real = Math.Pow(2d, 1000); + } + else + { + type = (~frac16 & 0x0200u) == 0u ? FPType.QNaN : FPType.SNaN; + real = 0d; + } + } + else + { + type = FPType.Nonzero; // Normal. + real = Math.Pow(2d, (int)exp16 - 15) * (1d + (double)frac16 * Math.Pow(2d, -10)); + } + + return sign ? -real : real; + } + + public static ushort FPRoundCv(double real, ExecutionContext context) + { + const int MinimumExp = -14; + + const int E = 5; + const int F = 10; + + bool sign; + double mantissa; + + if (real < 0d) + { + sign = true; + mantissa = -real; + } + else + { + sign = false; + mantissa = real; + } + + int exponent = 0; + + while (mantissa < 1d) + { + mantissa *= 2d; + exponent--; + } + + while (mantissa >= 2d) + { + mantissa /= 2d; + exponent++; + } + + uint biasedExp = (uint)Math.Max(exponent - MinimumExp + 1, 0); + + if (biasedExp == 0u) + { + mantissa /= Math.Pow(2d, MinimumExp - exponent); + } + + uint intMant = (uint)Math.Floor(mantissa * Math.Pow(2d, F)); + double error = mantissa * Math.Pow(2d, F) - (double)intMant; + + if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0)) + { + SoftFloat.FPProcessException(FPException.Underflow, context); + } + + bool overflowToInf; + bool roundUp; + + switch (context.Fpcr.GetRoundingMode()) + { + case FPRoundingMode.ToNearest: + roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u)); + overflowToInf = true; + break; + + case FPRoundingMode.TowardsPlusInfinity: + roundUp = (error != 0d && !sign); + overflowToInf = !sign; + break; + + case FPRoundingMode.TowardsMinusInfinity: + roundUp = (error != 0d && sign); + overflowToInf = sign; + break; + + case FPRoundingMode.TowardsZero: + roundUp = false; + overflowToInf = false; + break; + + default: + throw new ArgumentException($"Invalid rounding mode \"{context.Fpcr.GetRoundingMode()}\"."); + } + + if (roundUp) + { + intMant++; + + if (intMant == 1u << F) + { + biasedExp = 1u; + } + + if (intMant == 1u << (F + 1)) + { + biasedExp++; + intMant >>= 1; + } + } + + ushort resultBits; + + if ((context.Fpcr & FPCR.Ahp) == 0) + { + if (biasedExp >= (1u << E) - 1u) + { + resultBits = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context); + + error = 1d; + } + else + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | (biasedExp & 0x1Fu) << 10 | (intMant & 0x03FFu)); + } + } + else + { + if (biasedExp >= 1u << E) + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu); + + SoftFloat.FPProcessException(FPException.InvalidOp, context); + + error = 0d; + } + else + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | (biasedExp & 0x1Fu) << 10 | (intMant & 0x03FFu)); + } + } + + if (error != 0d) + { + SoftFloat.FPProcessException(FPException.Inexact, context); + } + + return resultBits; + } + } + + static class SoftFloat16_32 + { + public static float FPConvert(ushort valueBits) + { + ExecutionContext context = NativeInterface.GetContext(); + + double real = valueBits.FPUnpackCv(out FPType type, out bool sign, context); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + if ((context.Fpcr & FPCR.Dn) != 0) + { + result = SoftFloat32.FPDefaultNaN(); + } + else + { + result = FPConvertNaN(valueBits); + } + + if (type == FPType.SNaN) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + } + else if (type == FPType.Infinity) + { + result = SoftFloat32.FPInfinity(sign); + } + else if (type == FPType.Zero) + { + result = SoftFloat32.FPZero(sign); + } + else + { + result = FPRoundCv(real, context); + } + + return result; + } + + private static float FPRoundCv(double real, ExecutionContext context) + { + const int MinimumExp = -126; + + const int E = 8; + const int F = 23; + + bool sign; + double mantissa; + + if (real < 0d) + { + sign = true; + mantissa = -real; + } + else + { + sign = false; + mantissa = real; + } + + int exponent = 0; + + while (mantissa < 1d) + { + mantissa *= 2d; + exponent--; + } + + while (mantissa >= 2d) + { + mantissa /= 2d; + exponent++; + } + + if ((context.Fpcr & FPCR.Fz) != 0 && exponent < MinimumExp) + { + context.Fpsr |= FPSR.Ufc; + + return SoftFloat32.FPZero(sign); + } + + uint biasedExp = (uint)Math.Max(exponent - MinimumExp + 1, 0); + + if (biasedExp == 0u) + { + mantissa /= Math.Pow(2d, MinimumExp - exponent); + } + + uint intMant = (uint)Math.Floor(mantissa * Math.Pow(2d, F)); + double error = mantissa * Math.Pow(2d, F) - (double)intMant; + + if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0)) + { + SoftFloat.FPProcessException(FPException.Underflow, context); + } + + bool overflowToInf; + bool roundUp; + + switch (context.Fpcr.GetRoundingMode()) + { + case FPRoundingMode.ToNearest: + roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u)); + overflowToInf = true; + break; + + case FPRoundingMode.TowardsPlusInfinity: + roundUp = (error != 0d && !sign); + overflowToInf = !sign; + break; + + case FPRoundingMode.TowardsMinusInfinity: + roundUp = (error != 0d && sign); + overflowToInf = sign; + break; + + case FPRoundingMode.TowardsZero: + roundUp = false; + overflowToInf = false; + break; + + default: + throw new ArgumentException($"Invalid rounding mode \"{context.Fpcr.GetRoundingMode()}\"."); + } + + if (roundUp) + { + intMant++; + + if (intMant == 1u << F) + { + biasedExp = 1u; + } + + if (intMant == 1u << (F + 1)) + { + biasedExp++; + intMant >>= 1; + } + } + + float result; + + if (biasedExp >= (1u << E) - 1u) + { + result = overflowToInf ? SoftFloat32.FPInfinity(sign) : SoftFloat32.FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context); + + error = 1d; + } + else + { + result = BitConverter.Int32BitsToSingle( + (int)((sign ? 1u : 0u) << 31 | (biasedExp & 0xFFu) << 23 | (intMant & 0x007FFFFFu))); + } + + if (error != 0d) + { + SoftFloat.FPProcessException(FPException.Inexact, context); + } + + return result; + } + + private static float FPConvertNaN(ushort valueBits) + { + return BitConverter.Int32BitsToSingle( + (int)(((uint)valueBits & 0x8000u) << 16 | 0x7FC00000u | ((uint)valueBits & 0x01FFu) << 13)); + } + } + + static class SoftFloat16_64 + { + public static double FPConvert(ushort valueBits) + { + ExecutionContext context = NativeInterface.GetContext(); + + double real = valueBits.FPUnpackCv(out FPType type, out bool sign, context); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + if ((context.Fpcr & FPCR.Dn) != 0) + { + result = SoftFloat64.FPDefaultNaN(); + } + else + { + result = FPConvertNaN(valueBits); + } + + if (type == FPType.SNaN) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + } + else if (type == FPType.Infinity) + { + result = SoftFloat64.FPInfinity(sign); + } + else if (type == FPType.Zero) + { + result = SoftFloat64.FPZero(sign); + } + else + { + result = FPRoundCv(real, context); + } + + return result; + } + + private static double FPRoundCv(double real, ExecutionContext context) + { + const int MinimumExp = -1022; + + const int E = 11; + const int F = 52; + + bool sign; + double mantissa; + + if (real < 0d) + { + sign = true; + mantissa = -real; + } + else + { + sign = false; + mantissa = real; + } + + int exponent = 0; + + while (mantissa < 1d) + { + mantissa *= 2d; + exponent--; + } + + while (mantissa >= 2d) + { + mantissa /= 2d; + exponent++; + } + + if ((context.Fpcr & FPCR.Fz) != 0 && exponent < MinimumExp) + { + context.Fpsr |= FPSR.Ufc; + + return SoftFloat64.FPZero(sign); + } + + uint biasedExp = (uint)Math.Max(exponent - MinimumExp + 1, 0); + + if (biasedExp == 0u) + { + mantissa /= Math.Pow(2d, MinimumExp - exponent); + } + + ulong intMant = (ulong)Math.Floor(mantissa * Math.Pow(2d, F)); + double error = mantissa * Math.Pow(2d, F) - (double)intMant; + + if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0)) + { + SoftFloat.FPProcessException(FPException.Underflow, context); + } + + bool overflowToInf; + bool roundUp; + + switch (context.Fpcr.GetRoundingMode()) + { + case FPRoundingMode.ToNearest: + roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u)); + overflowToInf = true; + break; + + case FPRoundingMode.TowardsPlusInfinity: + roundUp = (error != 0d && !sign); + overflowToInf = !sign; + break; + + case FPRoundingMode.TowardsMinusInfinity: + roundUp = (error != 0d && sign); + overflowToInf = sign; + break; + + case FPRoundingMode.TowardsZero: + roundUp = false; + overflowToInf = false; + break; + + default: + throw new ArgumentException($"Invalid rounding mode \"{context.Fpcr.GetRoundingMode()}\"."); + } + + if (roundUp) + { + intMant++; + + if (intMant == 1ul << F) + { + biasedExp = 1u; + } + + if (intMant == 1ul << (F + 1)) + { + biasedExp++; + intMant >>= 1; + } + } + + double result; + + if (biasedExp >= (1u << E) - 1u) + { + result = overflowToInf ? SoftFloat64.FPInfinity(sign) : SoftFloat64.FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context); + + error = 1d; + } + else + { + result = BitConverter.Int64BitsToDouble( + (long)((sign ? 1ul : 0ul) << 63 | (biasedExp & 0x7FFul) << 52 | (intMant & 0x000FFFFFFFFFFFFFul))); + } + + if (error != 0d) + { + SoftFloat.FPProcessException(FPException.Inexact, context); + } + + return result; + } + + private static double FPConvertNaN(ushort valueBits) + { + return BitConverter.Int64BitsToDouble( + (long)(((ulong)valueBits & 0x8000ul) << 48 | 0x7FF8000000000000ul | ((ulong)valueBits & 0x01FFul) << 42)); + } + } + + static class SoftFloat32_16 + { + public static ushort FPConvert(float value) + { + ExecutionContext context = NativeInterface.GetContext(); + + double real = value.FPUnpackCv(out FPType type, out bool sign, out uint valueBits, context); + + bool altHp = (context.Fpcr & FPCR.Ahp) != 0; + + ushort resultBits; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + if (altHp) + { + resultBits = SoftFloat16.FPZero(sign); + } + else if ((context.Fpcr & FPCR.Dn) != 0) + { + resultBits = SoftFloat16.FPDefaultNaN(); + } + else + { + resultBits = FPConvertNaN(valueBits); + } + + if (type == FPType.SNaN || altHp) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + } + else if (type == FPType.Infinity) + { + if (altHp) + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu); + + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + else + { + resultBits = SoftFloat16.FPInfinity(sign); + } + } + else if (type == FPType.Zero) + { + resultBits = SoftFloat16.FPZero(sign); + } + else + { + resultBits = SoftFloat16.FPRoundCv(real, context); + } + + return resultBits; + } + + private static double FPUnpackCv( + this float value, + out FPType type, + out bool sign, + out uint valueBits, + ExecutionContext context) + { + valueBits = (uint)BitConverter.SingleToInt32Bits(value); + + sign = (~valueBits & 0x80000000u) == 0u; + + uint exp32 = (valueBits & 0x7F800000u) >> 23; + uint frac32 = valueBits & 0x007FFFFFu; + + double real; + + if (exp32 == 0u) + { + if (frac32 == 0u || (context.Fpcr & FPCR.Fz) != 0) + { + type = FPType.Zero; + real = 0d; + + if (frac32 != 0u) + { + SoftFloat.FPProcessException(FPException.InputDenorm, context); + } + } + else + { + type = FPType.Nonzero; // Subnormal. + real = Math.Pow(2d, -126) * ((double)frac32 * Math.Pow(2d, -23)); + } + } + else if (exp32 == 0xFFu) + { + if (frac32 == 0u) + { + type = FPType.Infinity; + real = Math.Pow(2d, 1000); + } + else + { + type = (~frac32 & 0x00400000u) == 0u ? FPType.QNaN : FPType.SNaN; + real = 0d; + } + } + else + { + type = FPType.Nonzero; // Normal. + real = Math.Pow(2d, (int)exp32 - 127) * (1d + (double)frac32 * Math.Pow(2d, -23)); + } + + return sign ? -real : real; + } + + private static ushort FPConvertNaN(uint valueBits) + { + return (ushort)((valueBits & 0x80000000u) >> 16 | 0x7E00u | (valueBits & 0x003FE000u) >> 13); + } + } + + static class SoftFloat32 + { + public static float FPAdd(float value1, float value2) + { + return FPAddFpscr(value1, value2, false); + } + + public static float FPAddFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == !sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && !sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == sign2) + { + result = FPZero(sign1); + } + else + { + result = value1 + value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static int FPCompare(float value1, float value2, bool signalNaNs) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + int result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = 0b0011; + + if (type1 == FPType.SNaN || type2 == FPType.SNaN || signalNaNs) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + } + else + { + if (value1 == value2) + { + result = 0b0110; + } + else if (value1 < value2) + { + result = 0b1000; + } + else + { + result = 0b0010; + } + } + + return result; + } + + public static float FPCompareEQ(float value1, float value2) + { + return FPCompareEQFpscr(value1, value2, false); + } + + public static float FPCompareEQFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + float result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + if (type1 == FPType.SNaN || type2 == FPType.SNaN) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + } + else + { + result = ZerosOrOnes(value1 == value2); + } + + return result; + } + + public static float FPCompareGE(float value1, float value2) + { + return FPCompareGEFpscr(value1, value2, false); + } + + public static float FPCompareGEFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + float result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = ZerosOrOnes(value1 >= value2); + } + + return result; + } + + public static float FPCompareGT(float value1, float value2) + { + return FPCompareGTFpscr(value1, value2, false); + } + + public static float FPCompareGTFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + float result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = ZerosOrOnes(value1 > value2); + } + + return result; + } + + public static float FPCompareLE(float value1, float value2) + { + return FPCompareGE(value2, value1); + } + + public static float FPCompareLT(float value1, float value2) + { + return FPCompareGT(value2, value1); + } + + public static float FPCompareLEFpscr(float value1, float value2, bool standardFpscr) + { + return FPCompareGEFpscr(value2, value1, standardFpscr); + } + + public static float FPCompareLTFpscr(float value1, float value2, bool standardFpscr) + { + return FPCompareGTFpscr(value2, value1, standardFpscr); + } + + public static float FPDiv(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && inf2) || (zero1 && zero2)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (inf1 || zero2) + { + result = FPInfinity(sign1 ^ sign2); + + if (!inf1) + { + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + } + else if (zero1 || inf2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 / value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPMax(float value1, float value2) + { + return FPMaxFpscr(value1, value2, false); + } + + public static float FPMaxFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + if (value1 > value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value1; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + } + + return result; + } + + public static float FPMaxNum(float value1, float value2) + { + return FPMaxNumFpscr(value1, value2, false); + } + + public static float FPMaxNumFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(true); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(true); + } + + return FPMaxFpscr(value1, value2, standardFpscr); + } + + public static float FPMin(float value1, float value2) + { + return FPMinFpscr(value1, value2, false); + } + + public static float FPMinFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + if (value1 < value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value1; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + } + + return result; + } + + public static float FPMinNum(float value1, float value2) + { + return FPMinNumFpscr(value1, value2, false); + } + + public static float FPMinNumFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(false); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(false); + } + + return FPMinFpscr(value1, value2, standardFpscr); + } + + public static float FPMul(float value1, float value2) + { + return FPMulFpscr(value1, value2, false); + } + + public static float FPMulFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else if (zero1 || zero2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 * value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPMulAdd(float valueA, float value1, float value2) + { + return FPMulAddFpscr(valueA, value1, value2, false); + } + + public static float FPMulAddFpscr(float valueA, float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + valueA = valueA.FPUnpack(out FPType typeA, out bool signA, out uint addend, context, fpcr); + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + float result = FPProcessNaNs3(typeA, type1, type2, addend, op1, op2, out bool done, context, fpcr); + + if (typeA == FPType.QNaN && ((inf1 && zero2) || (zero1 && inf2))) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + + if (!done) + { + bool infA = typeA == FPType.Infinity; + bool zeroA = typeA == FPType.Zero; + + bool signP = sign1 ^ sign2; + bool infP = inf1 || inf2; + bool zeroP = zero1 || zero2; + + if ((inf1 && zero2) || (zero1 && inf2) || (infA && infP && signA != signP)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((infA && !signA) || (infP && !signP)) + { + result = FPInfinity(false); + } + else if ((infA && signA) || (infP && signP)) + { + result = FPInfinity(true); + } + else if (zeroA && zeroP && signA == signP) + { + result = FPZero(signA); + } + else + { + result = MathF.FusedMultiplyAdd(value1, value2, valueA); + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPMulSub(float valueA, float value1, float value2) + { + value1 = value1.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static float FPMulSubFpscr(float valueA, float value1, float value2, bool standardFpscr) + { + value1 = value1.FPNeg(); + + return FPMulAddFpscr(valueA, value1, value2, standardFpscr); + } + + public static float FPMulX(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPTwo(sign1 ^ sign2); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else if (zero1 || zero2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 * value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPNegMulAdd(float valueA, float value1, float value2) + { + valueA = valueA.FPNeg(); + value1 = value1.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static float FPNegMulSub(float valueA, float value1, float value2) + { + valueA = valueA.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static float FPRecipEstimate(float value) + { + return FPRecipEstimateFpscr(value, false); + } + + public static float FPRecipEstimateFpscr(float value, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Infinity) + { + result = FPZero(sign); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + else if (MathF.Abs(value) < MathF.Pow(2f, -128)) + { + var overflowToInf = fpcr.GetRoundingMode() switch + { + FPRoundingMode.ToNearest => true, + FPRoundingMode.TowardsPlusInfinity => !sign, + FPRoundingMode.TowardsMinusInfinity => sign, + FPRoundingMode.TowardsZero => false, + _ => throw new ArgumentException($"Invalid rounding mode \"{fpcr.GetRoundingMode()}\"."), + }; + result = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context, fpcr); + SoftFloat.FPProcessException(FPException.Inexact, context, fpcr); + } + else if ((fpcr & FPCR.Fz) != 0 && (MathF.Abs(value) >= MathF.Pow(2f, 126))) + { + result = FPZero(sign); + + context.Fpsr |= FPSR.Ufc; + } + else + { + ulong fraction = (ulong)(op & 0x007FFFFFu) << 29; + uint exp = (op & 0x7F800000u) >> 23; + + if (exp == 0u) + { + if ((fraction & 0x0008000000000000ul) == 0ul) + { + fraction = (fraction & 0x0003FFFFFFFFFFFFul) << 2; + exp -= 1u; + } + else + { + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + } + } + + uint scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44); + + uint resultExp = 253u - exp; + + uint estimate = (uint)SoftFloat.RecipEstimateTable[scaled - 256u] + 256u; + + fraction = (ulong)(estimate & 0xFFu) << 44; + + if (resultExp == 0u) + { + fraction = ((fraction & 0x000FFFFFFFFFFFFEul) | 0x0010000000000000ul) >> 1; + } + else if (resultExp + 1u == 0u) + { + fraction = ((fraction & 0x000FFFFFFFFFFFFCul) | 0x0010000000000000ul) >> 2; + resultExp = 0u; + } + + result = BitConverter.Int32BitsToSingle( + (int)((sign ? 1u : 0u) << 31 | (resultExp & 0xFFu) << 23 | (uint)(fraction >> 29) & 0x007FFFFFu)); + } + + return result; + } + + public static float FPRecipStep(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.StandardFpcrValue; + + value1 = value1.FPUnpack(out FPType type1, out _, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + float product; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + product = FPZero(false); + } + else + { + product = FPMulFpscr(value1, value2, true); + } + + result = FPSubFpscr(FPTwo(false), product, true); + } + + return result; + } + + public static float FPRecipStepFused(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPTwo(false); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else + { + result = MathF.FusedMultiplyAdd(value1, value2, 2f); + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPRecpX(float value) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else + { + uint notExp = (~op >> 23) & 0xFFu; + uint maxExp = 0xFEu; + + result = BitConverter.Int32BitsToSingle( + (int)((sign ? 1u : 0u) << 31 | (notExp == 0xFFu ? maxExp : notExp) << 23)); + } + + return result; + } + + public static float FPRSqrtEstimate(float value) + { + return FPRSqrtEstimateFpscr(value, false); + } + + public static float FPRSqrtEstimateFpscr(float value, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + else if (sign) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (type == FPType.Infinity) + { + result = FPZero(false); + } + else + { + ulong fraction = (ulong)(op & 0x007FFFFFu) << 29; + uint exp = (op & 0x7F800000u) >> 23; + + if (exp == 0u) + { + while ((fraction & 0x0008000000000000ul) == 0ul) + { + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + exp -= 1u; + } + + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + } + + uint scaled; + + if ((exp & 1u) == 0u) + { + scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44); + } + else + { + scaled = (uint)(((fraction & 0x000FE00000000000ul) | 0x0010000000000000ul) >> 45); + } + + uint resultExp = (380u - exp) >> 1; + + uint estimate = (uint)SoftFloat.RecipSqrtEstimateTable[scaled - 128u] + 256u; + + result = BitConverter.Int32BitsToSingle((int)((resultExp & 0xFFu) << 23 | (estimate & 0xFFu) << 15)); + } + + return result; + } + + public static float FPHalvedSub(float value1, float value2, ExecutionContext context, FPCR fpcr) + { + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && !sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == !sign2) + { + result = FPZero(sign1); + } + else + { + result = (value1 - value2) / 2.0f; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPRSqrtStep(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.StandardFpcrValue; + + value1 = value1.FPUnpack(out FPType type1, out _, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + float product; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + product = FPZero(false); + } + else + { + product = FPMulFpscr(value1, value2, true); + } + + result = FPHalvedSub(FPThree(false), product, context, fpcr); + } + + return result; + } + + public static float FPRSqrtStepFused(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPOnePointFive(false); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else + { + result = MathF.FusedMultiplyAdd(value1, value2, 3f) / 2f; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPSqrt(float value) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value = value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Zero) + { + result = FPZero(sign); + } + else if (type == FPType.Infinity && !sign) + { + result = FPInfinity(sign); + } + else if (sign) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = MathF.Sqrt(value); + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + + return result; + } + + public static float FPSub(float value1, float value2) + { + return FPSubFpscr(value1, value2, false); + } + + public static float FPSubFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && !sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == !sign2) + { + result = FPZero(sign1); + } + else + { + result = value1 - value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPDefaultNaN() + { + return BitConverter.Int32BitsToSingle(0x7fc00000); + } + + public static float FPInfinity(bool sign) + { + return sign ? float.NegativeInfinity : float.PositiveInfinity; + } + + public static float FPZero(bool sign) + { + return sign ? -0f : +0f; + } + + public static float FPMaxNormal(bool sign) + { + return sign ? float.MinValue : float.MaxValue; + } + + private static float FPTwo(bool sign) + { + return sign ? -2f : +2f; + } + + private static float FPThree(bool sign) + { + return sign ? -3f : +3f; + } + + private static float FPOnePointFive(bool sign) + { + return sign ? -1.5f : +1.5f; + } + + private static float FPNeg(this float value) + { + return -value; + } + + private static float ZerosOrOnes(bool ones) + { + return BitConverter.Int32BitsToSingle(ones ? -1 : 0); + } + + private static float FPUnpack( + this float value, + out FPType type, + out bool sign, + out uint valueBits, + ExecutionContext context, + FPCR fpcr) + { + valueBits = (uint)BitConverter.SingleToInt32Bits(value); + + sign = (~valueBits & 0x80000000u) == 0u; + + if ((valueBits & 0x7F800000u) == 0u) + { + if ((valueBits & 0x007FFFFFu) == 0u || (fpcr & FPCR.Fz) != 0) + { + type = FPType.Zero; + value = FPZero(sign); + + if ((valueBits & 0x007FFFFFu) != 0u) + { + SoftFloat.FPProcessException(FPException.InputDenorm, context, fpcr); + } + } + else + { + type = FPType.Nonzero; + } + } + else if ((~valueBits & 0x7F800000u) == 0u) + { + if ((valueBits & 0x007FFFFFu) == 0u) + { + type = FPType.Infinity; + } + else + { + type = (~valueBits & 0x00400000u) == 0u ? FPType.QNaN : FPType.SNaN; + value = FPZero(sign); + } + } + else + { + type = FPType.Nonzero; + } + + return value; + } + + private static float FPProcessNaNs( + FPType type1, + FPType type2, + uint op1, + uint op2, + out bool done, + ExecutionContext context, + FPCR fpcr) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + + done = false; + + return FPZero(false); + } + + private static float FPProcessNaNs3( + FPType type1, + FPType type2, + FPType type3, + uint op1, + uint op2, + uint op3, + out bool done, + ExecutionContext context, + FPCR fpcr) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type3 == FPType.SNaN) + { + return FPProcessNaN(type3, op3, context, fpcr); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type3 == FPType.QNaN) + { + return FPProcessNaN(type3, op3, context, fpcr); + } + + done = false; + + return FPZero(false); + } + + private static float FPProcessNaN(FPType type, uint op, ExecutionContext context, FPCR fpcr) + { + if (type == FPType.SNaN) + { + op |= 1u << 22; + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + + if ((fpcr & FPCR.Dn) != 0) + { + return FPDefaultNaN(); + } + + return BitConverter.Int32BitsToSingle((int)op); + } + } + + static class SoftFloat64_16 + { + public static ushort FPConvert(double value) + { + ExecutionContext context = NativeInterface.GetContext(); + + double real = value.FPUnpackCv(out FPType type, out bool sign, out ulong valueBits, context); + + bool altHp = (context.Fpcr & FPCR.Ahp) != 0; + + ushort resultBits; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + if (altHp) + { + resultBits = SoftFloat16.FPZero(sign); + } + else if ((context.Fpcr & FPCR.Dn) != 0) + { + resultBits = SoftFloat16.FPDefaultNaN(); + } + else + { + resultBits = FPConvertNaN(valueBits); + } + + if (type == FPType.SNaN || altHp) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + } + else if (type == FPType.Infinity) + { + if (altHp) + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu); + + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + else + { + resultBits = SoftFloat16.FPInfinity(sign); + } + } + else if (type == FPType.Zero) + { + resultBits = SoftFloat16.FPZero(sign); + } + else + { + resultBits = SoftFloat16.FPRoundCv(real, context); + } + + return resultBits; + } + + private static double FPUnpackCv( + this double value, + out FPType type, + out bool sign, + out ulong valueBits, + ExecutionContext context) + { + valueBits = (ulong)BitConverter.DoubleToInt64Bits(value); + + sign = (~valueBits & 0x8000000000000000ul) == 0u; + + ulong exp64 = (valueBits & 0x7FF0000000000000ul) >> 52; + ulong frac64 = valueBits & 0x000FFFFFFFFFFFFFul; + + double real; + + if (exp64 == 0u) + { + if (frac64 == 0u || (context.Fpcr & FPCR.Fz) != 0) + { + type = FPType.Zero; + real = 0d; + + if (frac64 != 0u) + { + SoftFloat.FPProcessException(FPException.InputDenorm, context); + } + } + else + { + type = FPType.Nonzero; // Subnormal. + real = Math.Pow(2d, -1022) * ((double)frac64 * Math.Pow(2d, -52)); + } + } + else if (exp64 == 0x7FFul) + { + if (frac64 == 0u) + { + type = FPType.Infinity; + real = Math.Pow(2d, 1000000); + } + else + { + type = (~frac64 & 0x0008000000000000ul) == 0u ? FPType.QNaN : FPType.SNaN; + real = 0d; + } + } + else + { + type = FPType.Nonzero; // Normal. + real = Math.Pow(2d, (int)exp64 - 1023) * (1d + (double)frac64 * Math.Pow(2d, -52)); + } + + return sign ? -real : real; + } + + private static ushort FPConvertNaN(ulong valueBits) + { + return (ushort)((valueBits & 0x8000000000000000ul) >> 48 | 0x7E00u | (valueBits & 0x0007FC0000000000ul) >> 42); + } + } + + static class SoftFloat64 + { + public static double FPAdd(double value1, double value2) + { + return FPAddFpscr(value1, value2, false); + } + + public static double FPAddFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == !sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && !sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == sign2) + { + result = FPZero(sign1); + } + else + { + result = value1 + value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static int FPCompare(double value1, double value2, bool signalNaNs) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + int result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = 0b0011; + + if (type1 == FPType.SNaN || type2 == FPType.SNaN || signalNaNs) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + } + else + { + if (value1 == value2) + { + result = 0b0110; + } + else if (value1 < value2) + { + result = 0b1000; + } + else + { + result = 0b0010; + } + } + + return result; + } + + public static double FPCompareEQ(double value1, double value2) + { + return FPCompareEQFpscr(value1, value2, false); + } + + public static double FPCompareEQFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + double result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + if (type1 == FPType.SNaN || type2 == FPType.SNaN) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + } + else + { + result = ZerosOrOnes(value1 == value2); + } + + return result; + } + + public static double FPCompareGE(double value1, double value2) + { + return FPCompareGEFpscr(value1, value2, false); + } + + public static double FPCompareGEFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + double result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = ZerosOrOnes(value1 >= value2); + } + + return result; + } + + public static double FPCompareGT(double value1, double value2) + { + return FPCompareGTFpscr(value1, value2, false); + } + + public static double FPCompareGTFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + double result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = ZerosOrOnes(value1 > value2); + } + + return result; + } + + public static double FPCompareLE(double value1, double value2) + { + return FPCompareGE(value2, value1); + } + + public static double FPCompareLT(double value1, double value2) + { + return FPCompareGT(value2, value1); + } + + public static double FPCompareLEFpscr(double value1, double value2, bool standardFpscr) + { + return FPCompareGEFpscr(value2, value1, standardFpscr); + } + + public static double FPCompareLTFpscr(double value1, double value2, bool standardFpscr) + { + return FPCompareGTFpscr(value2, value1, standardFpscr); + } + + public static double FPDiv(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && inf2) || (zero1 && zero2)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (inf1 || zero2) + { + result = FPInfinity(sign1 ^ sign2); + + if (!inf1) + { + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + } + else if (zero1 || inf2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 / value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPMax(double value1, double value2) + { + return FPMaxFpscr(value1, value2, false); + } + + public static double FPMaxFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + if (value1 > value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value1; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + } + + return result; + } + + public static double FPMaxNum(double value1, double value2) + { + return FPMaxNumFpscr(value1, value2, false); + } + + public static double FPMaxNumFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(true); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(true); + } + + return FPMaxFpscr(value1, value2, standardFpscr); + } + + public static double FPMin(double value1, double value2) + { + return FPMinFpscr(value1, value2, false); + } + + public static double FPMinFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + if (value1 < value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value1; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + } + + return result; + } + + public static double FPMinNum(double value1, double value2) + { + return FPMinNumFpscr(value1, value2, false); + } + + public static double FPMinNumFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(false); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(false); + } + + return FPMinFpscr(value1, value2, standardFpscr); + } + + public static double FPMul(double value1, double value2) + { + return FPMulFpscr(value1, value2, false); + } + + public static double FPMulFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else if (zero1 || zero2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 * value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPMulAdd(double valueA, double value1, double value2) + { + return FPMulAddFpscr(valueA, value1, value2, false); + } + + public static double FPMulAddFpscr(double valueA, double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + valueA = valueA.FPUnpack(out FPType typeA, out bool signA, out ulong addend, context, fpcr); + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + double result = FPProcessNaNs3(typeA, type1, type2, addend, op1, op2, out bool done, context, fpcr); + + if (typeA == FPType.QNaN && ((inf1 && zero2) || (zero1 && inf2))) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + + if (!done) + { + bool infA = typeA == FPType.Infinity; + bool zeroA = typeA == FPType.Zero; + + bool signP = sign1 ^ sign2; + bool infP = inf1 || inf2; + bool zeroP = zero1 || zero2; + + if ((inf1 && zero2) || (zero1 && inf2) || (infA && infP && signA != signP)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((infA && !signA) || (infP && !signP)) + { + result = FPInfinity(false); + } + else if ((infA && signA) || (infP && signP)) + { + result = FPInfinity(true); + } + else if (zeroA && zeroP && signA == signP) + { + result = FPZero(signA); + } + else + { + result = Math.FusedMultiplyAdd(value1, value2, valueA); + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPMulSub(double valueA, double value1, double value2) + { + value1 = value1.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static double FPMulSubFpscr(double valueA, double value1, double value2, bool standardFpscr) + { + value1 = value1.FPNeg(); + + return FPMulAddFpscr(valueA, value1, value2, standardFpscr); + } + + public static double FPMulX(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPTwo(sign1 ^ sign2); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else if (zero1 || zero2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 * value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPNegMulAdd(double valueA, double value1, double value2) + { + valueA = valueA.FPNeg(); + value1 = value1.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static double FPNegMulSub(double valueA, double value1, double value2) + { + valueA = valueA.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static double FPRecipEstimate(double value) + { + return FPRecipEstimateFpscr(value, false); + } + + public static double FPRecipEstimateFpscr(double value, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Infinity) + { + result = FPZero(sign); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + else if (Math.Abs(value) < Math.Pow(2d, -1024)) + { + var overflowToInf = fpcr.GetRoundingMode() switch + { + FPRoundingMode.ToNearest => true, + FPRoundingMode.TowardsPlusInfinity => !sign, + FPRoundingMode.TowardsMinusInfinity => sign, + FPRoundingMode.TowardsZero => false, + _ => throw new ArgumentException($"Invalid rounding mode \"{fpcr.GetRoundingMode()}\"."), + }; + result = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context, fpcr); + SoftFloat.FPProcessException(FPException.Inexact, context, fpcr); + } + else if ((fpcr & FPCR.Fz) != 0 && (Math.Abs(value) >= Math.Pow(2d, 1022))) + { + result = FPZero(sign); + + context.Fpsr |= FPSR.Ufc; + } + else + { + ulong fraction = op & 0x000FFFFFFFFFFFFFul; + uint exp = (uint)((op & 0x7FF0000000000000ul) >> 52); + + if (exp == 0u) + { + if ((fraction & 0x0008000000000000ul) == 0ul) + { + fraction = (fraction & 0x0003FFFFFFFFFFFFul) << 2; + exp -= 1u; + } + else + { + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + } + } + + uint scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44); + + uint resultExp = 2045u - exp; + + uint estimate = (uint)SoftFloat.RecipEstimateTable[scaled - 256u] + 256u; + + fraction = (ulong)(estimate & 0xFFu) << 44; + + if (resultExp == 0u) + { + fraction = ((fraction & 0x000FFFFFFFFFFFFEul) | 0x0010000000000000ul) >> 1; + } + else if (resultExp + 1u == 0u) + { + fraction = ((fraction & 0x000FFFFFFFFFFFFCul) | 0x0010000000000000ul) >> 2; + resultExp = 0u; + } + + result = BitConverter.Int64BitsToDouble( + (long)((sign ? 1ul : 0ul) << 63 | (resultExp & 0x7FFul) << 52 | (fraction & 0x000FFFFFFFFFFFFFul))); + } + + return result; + } + + public static double FPRecipStep(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.StandardFpcrValue; + + value1 = value1.FPUnpack(out FPType type1, out _, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + double product; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + product = FPZero(false); + } + else + { + product = FPMulFpscr(value1, value2, true); + } + + result = FPSubFpscr(FPTwo(false), product, true); + } + + return result; + } + + public static double FPRecipStepFused(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPTwo(false); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else + { + result = Math.FusedMultiplyAdd(value1, value2, 2d); + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPRecpX(double value) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else + { + ulong notExp = (~op >> 52) & 0x7FFul; + ulong maxExp = 0x7FEul; + + result = BitConverter.Int64BitsToDouble( + (long)((sign ? 1ul : 0ul) << 63 | (notExp == 0x7FFul ? maxExp : notExp) << 52)); + } + + return result; + } + + public static double FPRSqrtEstimate(double value) + { + return FPRSqrtEstimateFpscr(value, false); + } + + public static double FPRSqrtEstimateFpscr(double value, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + else if (sign) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (type == FPType.Infinity) + { + result = FPZero(false); + } + else + { + ulong fraction = op & 0x000FFFFFFFFFFFFFul; + uint exp = (uint)((op & 0x7FF0000000000000ul) >> 52); + + if (exp == 0u) + { + while ((fraction & 0x0008000000000000ul) == 0ul) + { + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + exp -= 1u; + } + + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + } + + uint scaled; + + if ((exp & 1u) == 0u) + { + scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44); + } + else + { + scaled = (uint)(((fraction & 0x000FE00000000000ul) | 0x0010000000000000ul) >> 45); + } + + uint resultExp = (3068u - exp) >> 1; + + uint estimate = (uint)SoftFloat.RecipSqrtEstimateTable[scaled - 128u] + 256u; + + result = BitConverter.Int64BitsToDouble((long)((resultExp & 0x7FFul) << 52 | (estimate & 0xFFul) << 44)); + } + + return result; + } + + public static double FPHalvedSub(double value1, double value2, ExecutionContext context, FPCR fpcr) + { + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && !sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == !sign2) + { + result = FPZero(sign1); + } + else + { + result = (value1 - value2) / 2.0; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPRSqrtStep(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.StandardFpcrValue; + + value1 = value1.FPUnpack(out FPType type1, out _, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + double product; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + product = FPZero(false); + } + else + { + product = FPMulFpscr(value1, value2, true); + } + + result = FPHalvedSub(FPThree(false), product, context, fpcr); + } + + return result; + } + + public static double FPRSqrtStepFused(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPOnePointFive(false); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else + { + result = Math.FusedMultiplyAdd(value1, value2, 3d) / 2d; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPSqrt(double value) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value = value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Zero) + { + result = FPZero(sign); + } + else if (type == FPType.Infinity && !sign) + { + result = FPInfinity(sign); + } + else if (sign) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = Math.Sqrt(value); + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + + return result; + } + + public static double FPSub(double value1, double value2) + { + return FPSubFpscr(value1, value2, false); + } + + public static double FPSubFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; + bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; + bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && !sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == !sign2) + { + result = FPZero(sign1); + } + else + { + result = value1 - value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPDefaultNaN() + { + return BitConverter.Int64BitsToDouble(0x7ff8000000000000); + } + + public static double FPInfinity(bool sign) + { + return sign ? double.NegativeInfinity : double.PositiveInfinity; + } + + public static double FPZero(bool sign) + { + return sign ? -0d : +0d; + } + + public static double FPMaxNormal(bool sign) + { + return sign ? double.MinValue : double.MaxValue; + } + + private static double FPTwo(bool sign) + { + return sign ? -2d : +2d; + } + + private static double FPThree(bool sign) + { + return sign ? -3d : +3d; + } + + private static double FPOnePointFive(bool sign) + { + return sign ? -1.5d : +1.5d; + } + + private static double FPNeg(this double value) + { + return -value; + } + + private static double ZerosOrOnes(bool ones) + { + return BitConverter.Int64BitsToDouble(ones ? -1L : 0L); + } + + private static double FPUnpack( + this double value, + out FPType type, + out bool sign, + out ulong valueBits, + ExecutionContext context, + FPCR fpcr) + { + valueBits = (ulong)BitConverter.DoubleToInt64Bits(value); + + sign = (~valueBits & 0x8000000000000000ul) == 0ul; + + if ((valueBits & 0x7FF0000000000000ul) == 0ul) + { + if ((valueBits & 0x000FFFFFFFFFFFFFul) == 0ul || (fpcr & FPCR.Fz) != 0) + { + type = FPType.Zero; + value = FPZero(sign); + + if ((valueBits & 0x000FFFFFFFFFFFFFul) != 0ul) + { + SoftFloat.FPProcessException(FPException.InputDenorm, context, fpcr); + } + } + else + { + type = FPType.Nonzero; + } + } + else if ((~valueBits & 0x7FF0000000000000ul) == 0ul) + { + if ((valueBits & 0x000FFFFFFFFFFFFFul) == 0ul) + { + type = FPType.Infinity; + } + else + { + type = (~valueBits & 0x0008000000000000ul) == 0ul ? FPType.QNaN : FPType.SNaN; + value = FPZero(sign); + } + } + else + { + type = FPType.Nonzero; + } + + return value; + } + + private static double FPProcessNaNs( + FPType type1, + FPType type2, + ulong op1, + ulong op2, + out bool done, + ExecutionContext context, + FPCR fpcr) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + + done = false; + + return FPZero(false); + } + + private static double FPProcessNaNs3( + FPType type1, + FPType type2, + FPType type3, + ulong op1, + ulong op2, + ulong op3, + out bool done, + ExecutionContext context, + FPCR fpcr) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type3 == FPType.SNaN) + { + return FPProcessNaN(type3, op3, context, fpcr); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type3 == FPType.QNaN) + { + return FPProcessNaN(type3, op3, context, fpcr); + } + + done = false; + + return FPZero(false); + } + + private static double FPProcessNaN(FPType type, ulong op, ExecutionContext context, FPCR fpcr) + { + if (type == FPType.SNaN) + { + op |= 1ul << 51; + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + + if ((fpcr & FPCR.Dn) != 0) + { + return FPDefaultNaN(); + } + + return BitConverter.Int64BitsToDouble((long)op); + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/BasicBlock.cs b/src/ARMeilleure/IntermediateRepresentation/BasicBlock.cs new file mode 100644 index 00000000..810461d7 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/BasicBlock.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.IntermediateRepresentation +{ + class BasicBlock : IEquatable, IIntrusiveListNode + { + private const uint MaxSuccessors = 2; + + private int _succCount; + private BasicBlock _succ0; + private readonly BasicBlock _succ1; + private HashSet _domFrontiers; + + public int Index { get; set; } + public BasicBlockFrequency Frequency { get; set; } + public BasicBlock ListPrevious { get; set; } + public BasicBlock ListNext { get; set; } + public IntrusiveList Operations { get; } + public List Predecessors { get; } + public BasicBlock ImmediateDominator { get; set; } + + public int SuccessorsCount => _succCount; + + public HashSet DominanceFrontiers + { + get + { + _domFrontiers ??= new HashSet(); + + return _domFrontiers; + } + } + + public BasicBlock() : this(index: -1) { } + + public BasicBlock(int index) + { + Operations = new IntrusiveList(); + Predecessors = new List(); + + Index = index; + } + + public void AddSuccessor(BasicBlock block) + { + ArgumentNullException.ThrowIfNull(block); + + if ((uint)_succCount + 1 > MaxSuccessors) + { + ThrowSuccessorOverflow(); + } + + block.Predecessors.Add(this); + + GetSuccessorUnsafe(_succCount++) = block; + } + + public void RemoveSuccessor(int index) + { + if ((uint)index >= (uint)_succCount) + { + ThrowOutOfRange(nameof(index)); + } + + ref BasicBlock oldBlock = ref GetSuccessorUnsafe(index); + + oldBlock.Predecessors.Remove(this); + oldBlock = null; + + if (index == 0) + { + _succ0 = _succ1; + } + + _succCount--; + } + + public BasicBlock GetSuccessor(int index) + { + if ((uint)index >= (uint)_succCount) + { + ThrowOutOfRange(nameof(index)); + } + + return GetSuccessorUnsafe(index); + } + + private ref BasicBlock GetSuccessorUnsafe(int index) + { + return ref Unsafe.Add(ref _succ0, index); + } + + public void SetSuccessor(int index, BasicBlock block) + { + ArgumentNullException.ThrowIfNull(block); + + if ((uint)index >= (uint)_succCount) + { + ThrowOutOfRange(nameof(index)); + } + + ref BasicBlock oldBlock = ref GetSuccessorUnsafe(index); + + oldBlock.Predecessors.Remove(this); + block.Predecessors.Add(this); + + oldBlock = block; + } + + public void Append(Operation node) + { + Operation last = Operations.Last; + + // Append node before terminal or to end if no terminal. + if (last == default) + { + Operations.AddLast(node); + + return; + } + + switch (last.Instruction) + { + case Instruction.Return: + case Instruction.Tailcall: + case Instruction.BranchIf: + Operations.AddBefore(last, node); + break; + + default: + Operations.AddLast(node); + break; + } + } + + private static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name); + private static void ThrowSuccessorOverflow() => throw new OverflowException($"BasicBlock can only have {MaxSuccessors} successors."); + + public bool Equals(BasicBlock other) + { + return other == this; + } + + public override bool Equals(object obj) + { + return Equals(obj as BasicBlock); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/BasicBlockFrequency.cs b/src/ARMeilleure/IntermediateRepresentation/BasicBlockFrequency.cs new file mode 100644 index 00000000..e4f7ae80 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/BasicBlockFrequency.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum BasicBlockFrequency + { + Default, + Cold, + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Comparison.cs b/src/ARMeilleure/IntermediateRepresentation/Comparison.cs new file mode 100644 index 00000000..3d6a9d81 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Comparison.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum Comparison + { + Equal = 0, + NotEqual = 1, + Greater = 2, + LessOrEqual = 3, + GreaterUI = 4, + LessOrEqualUI = 5, + GreaterOrEqual = 6, + Less = 7, + GreaterOrEqualUI = 8, + LessUI = 9, + } + + static class ComparisonExtensions + { + public static Comparison Invert(this Comparison comp) + { + return (Comparison)((int)comp ^ 1); + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/IIntrusiveListNode.cs b/src/ARMeilleure/IntermediateRepresentation/IIntrusiveListNode.cs new file mode 100644 index 00000000..05be8a5a --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/IIntrusiveListNode.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + interface IIntrusiveListNode + { + T ListPrevious { get; set; } + T ListNext { get; set; } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Instruction.cs b/src/ARMeilleure/IntermediateRepresentation/Instruction.cs new file mode 100644 index 00000000..9bae8d1f --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Instruction.cs @@ -0,0 +1,72 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum Instruction : ushort + { + Add, + BitwiseAnd, + BitwiseExclusiveOr, + BitwiseNot, + BitwiseOr, + BranchIf, + ByteSwap, + Call, + Compare, + CompareAndSwap, + CompareAndSwap16, + CompareAndSwap8, + ConditionalSelect, + ConvertI64ToI32, + ConvertToFP, + ConvertToFPUI, + Copy, + CountLeadingZeros, + Divide, + DivideUI, + Load, + Load16, + Load8, + LoadArgument, + MemoryBarrier, + Multiply, + Multiply64HighSI, + Multiply64HighUI, + Negate, + Return, + RotateRight, + ShiftLeft, + ShiftRightSI, + ShiftRightUI, + SignExtend16, + SignExtend32, + SignExtend8, + StackAlloc, + Store, + Store16, + Store8, + Subtract, + Tailcall, + VectorCreateScalar, + VectorExtract, + VectorExtract16, + VectorExtract8, + VectorInsert, + VectorInsert16, + VectorInsert8, + VectorOne, + VectorZero, + VectorZeroUpper64, + VectorZeroUpper96, + ZeroExtend16, + ZeroExtend32, + ZeroExtend8, + + Clobber, + Extended, + Fill, + LoadFromContext, + Phi, + Spill, + SpillArg, + StoreToContext, + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Intrinsic.cs b/src/ARMeilleure/IntermediateRepresentation/Intrinsic.cs new file mode 100644 index 00000000..b9cab667 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Intrinsic.cs @@ -0,0 +1,641 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace ARMeilleure.IntermediateRepresentation +{ + [Flags] + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + enum Intrinsic : ushort + { + // X86 (SSE and AVX) + + X86Addpd, + X86Addps, + X86Addsd, + X86Addss, + X86Aesdec, + X86Aesdeclast, + X86Aesenc, + X86Aesenclast, + X86Aesimc, + X86Andnpd, + X86Andnps, + X86Andpd, + X86Andps, + X86Blendvpd, + X86Blendvps, + X86Cmppd, + X86Cmpps, + X86Cmpsd, + X86Cmpss, + X86Comisdeq, + X86Comisdge, + X86Comisdlt, + X86Comisseq, + X86Comissge, + X86Comisslt, + X86Crc32, + X86Crc32_16, + X86Crc32_8, + X86Cvtdq2pd, + X86Cvtdq2ps, + X86Cvtpd2dq, + X86Cvtpd2ps, + X86Cvtps2dq, + X86Cvtps2pd, + X86Cvtsd2si, + X86Cvtsd2ss, + X86Cvtsi2sd, + X86Cvtsi2si, + X86Cvtsi2ss, + X86Cvtss2sd, + X86Cvtss2si, + X86Divpd, + X86Divps, + X86Divsd, + X86Divss, + X86Gf2p8affineqb, + X86Haddpd, + X86Haddps, + X86Insertps, + X86Ldmxcsr, + X86Maxpd, + X86Maxps, + X86Maxsd, + X86Maxss, + X86Minpd, + X86Minps, + X86Minsd, + X86Minss, + X86Movhlps, + X86Movlhps, + X86Movss, + X86Mulpd, + X86Mulps, + X86Mulsd, + X86Mulss, + X86Paddb, + X86Paddd, + X86Paddq, + X86Paddw, + X86Palignr, + X86Pand, + X86Pandn, + X86Pavgb, + X86Pavgw, + X86Pblendvb, + X86Pclmulqdq, + X86Pcmpeqb, + X86Pcmpeqd, + X86Pcmpeqq, + X86Pcmpeqw, + X86Pcmpgtb, + X86Pcmpgtd, + X86Pcmpgtq, + X86Pcmpgtw, + X86Pmaxsb, + X86Pmaxsd, + X86Pmaxsw, + X86Pmaxub, + X86Pmaxud, + X86Pmaxuw, + X86Pminsb, + X86Pminsd, + X86Pminsw, + X86Pminub, + X86Pminud, + X86Pminuw, + X86Pmovsxbw, + X86Pmovsxdq, + X86Pmovsxwd, + X86Pmovzxbw, + X86Pmovzxdq, + X86Pmovzxwd, + X86Pmulld, + X86Pmullw, + X86Popcnt, + X86Por, + X86Pshufb, + X86Pshufd, + X86Pslld, + X86Pslldq, + X86Psllq, + X86Psllw, + X86Psrad, + X86Psraw, + X86Psrld, + X86Psrlq, + X86Psrldq, + X86Psrlw, + X86Psubb, + X86Psubd, + X86Psubq, + X86Psubw, + X86Punpckhbw, + X86Punpckhdq, + X86Punpckhqdq, + X86Punpckhwd, + X86Punpcklbw, + X86Punpckldq, + X86Punpcklqdq, + X86Punpcklwd, + X86Pxor, + X86Rcpps, + X86Rcpss, + X86Roundpd, + X86Roundps, + X86Roundsd, + X86Roundss, + X86Rsqrtps, + X86Rsqrtss, + X86Sha256Msg1, + X86Sha256Msg2, + X86Sha256Rnds2, + X86Shufpd, + X86Shufps, + X86Sqrtpd, + X86Sqrtps, + X86Sqrtsd, + X86Sqrtss, + X86Stmxcsr, + X86Subpd, + X86Subps, + X86Subsd, + X86Subss, + X86Unpckhpd, + X86Unpckhps, + X86Unpcklpd, + X86Unpcklps, + X86Vcvtph2ps, + X86Vcvtps2ph, + X86Vfmadd231pd, + X86Vfmadd231ps, + X86Vfmadd231sd, + X86Vfmadd231ss, + X86Vfmsub231sd, + X86Vfmsub231ss, + X86Vfnmadd231pd, + X86Vfnmadd231ps, + X86Vfnmadd231sd, + X86Vfnmadd231ss, + X86Vfnmsub231sd, + X86Vfnmsub231ss, + X86Vpternlogd, + X86Xorpd, + X86Xorps, + + // Arm64 (FP and Advanced SIMD) + + Arm64AbsS, + Arm64AbsV, + Arm64AddhnV, + Arm64AddpS, + Arm64AddpV, + Arm64AddvV, + Arm64AddS, + Arm64AddV, + Arm64AesdV, + Arm64AeseV, + Arm64AesimcV, + Arm64AesmcV, + Arm64AndV, + Arm64BicVi, + Arm64BicV, + Arm64BifV, + Arm64BitV, + Arm64BslV, + Arm64ClsV, + Arm64ClzV, + Arm64CmeqS, + Arm64CmeqV, + Arm64CmeqSz, + Arm64CmeqVz, + Arm64CmgeS, + Arm64CmgeV, + Arm64CmgeSz, + Arm64CmgeVz, + Arm64CmgtS, + Arm64CmgtV, + Arm64CmgtSz, + Arm64CmgtVz, + Arm64CmhiS, + Arm64CmhiV, + Arm64CmhsS, + Arm64CmhsV, + Arm64CmleSz, + Arm64CmleVz, + Arm64CmltSz, + Arm64CmltVz, + Arm64CmtstS, + Arm64CmtstV, + Arm64CntV, + Arm64DupSe, + Arm64DupVe, + Arm64DupGp, + Arm64EorV, + Arm64ExtV, + Arm64FabdS, + Arm64FabdV, + Arm64FabsV, + Arm64FabsS, + Arm64FacgeS, + Arm64FacgeV, + Arm64FacgtS, + Arm64FacgtV, + Arm64FaddpS, + Arm64FaddpV, + Arm64FaddV, + Arm64FaddS, + Arm64FccmpeS, + Arm64FccmpS, + Arm64FcmeqS, + Arm64FcmeqV, + Arm64FcmeqSz, + Arm64FcmeqVz, + Arm64FcmgeS, + Arm64FcmgeV, + Arm64FcmgeSz, + Arm64FcmgeVz, + Arm64FcmgtS, + Arm64FcmgtV, + Arm64FcmgtSz, + Arm64FcmgtVz, + Arm64FcmleSz, + Arm64FcmleVz, + Arm64FcmltSz, + Arm64FcmltVz, + Arm64FcmpeS, + Arm64FcmpS, + Arm64FcselS, + Arm64FcvtasS, + Arm64FcvtasV, + Arm64FcvtasGp, + Arm64FcvtauS, + Arm64FcvtauV, + Arm64FcvtauGp, + Arm64FcvtlV, + Arm64FcvtmsS, + Arm64FcvtmsV, + Arm64FcvtmsGp, + Arm64FcvtmuS, + Arm64FcvtmuV, + Arm64FcvtmuGp, + Arm64FcvtnsS, + Arm64FcvtnsV, + Arm64FcvtnsGp, + Arm64FcvtnuS, + Arm64FcvtnuV, + Arm64FcvtnuGp, + Arm64FcvtnV, + Arm64FcvtpsS, + Arm64FcvtpsV, + Arm64FcvtpsGp, + Arm64FcvtpuS, + Arm64FcvtpuV, + Arm64FcvtpuGp, + Arm64FcvtxnS, + Arm64FcvtxnV, + Arm64FcvtzsSFixed, + Arm64FcvtzsVFixed, + Arm64FcvtzsS, + Arm64FcvtzsV, + Arm64FcvtzsGpFixed, + Arm64FcvtzsGp, + Arm64FcvtzuSFixed, + Arm64FcvtzuVFixed, + Arm64FcvtzuS, + Arm64FcvtzuV, + Arm64FcvtzuGpFixed, + Arm64FcvtzuGp, + Arm64FcvtS, + Arm64FdivV, + Arm64FdivS, + Arm64FmaddS, + Arm64FmaxnmpS, + Arm64FmaxnmpV, + Arm64FmaxnmvV, + Arm64FmaxnmV, + Arm64FmaxnmS, + Arm64FmaxpS, + Arm64FmaxpV, + Arm64FmaxvV, + Arm64FmaxV, + Arm64FmaxS, + Arm64FminnmpS, + Arm64FminnmpV, + Arm64FminnmvV, + Arm64FminnmV, + Arm64FminnmS, + Arm64FminpS, + Arm64FminpV, + Arm64FminvV, + Arm64FminV, + Arm64FminS, + Arm64FmlaSe, + Arm64FmlaVe, + Arm64FmlaV, + Arm64FmlsSe, + Arm64FmlsVe, + Arm64FmlsV, + Arm64FmovVi, + Arm64FmovS, + Arm64FmovGp, + Arm64FmovSi, + Arm64FmsubS, + Arm64FmulxSe, + Arm64FmulxVe, + Arm64FmulxS, + Arm64FmulxV, + Arm64FmulSe, + Arm64FmulVe, + Arm64FmulV, + Arm64FmulS, + Arm64FnegV, + Arm64FnegS, + Arm64FnmaddS, + Arm64FnmsubS, + Arm64FnmulS, + Arm64FrecpeS, + Arm64FrecpeV, + Arm64FrecpsS, + Arm64FrecpsV, + Arm64FrecpxS, + Arm64FrintaV, + Arm64FrintaS, + Arm64FrintiV, + Arm64FrintiS, + Arm64FrintmV, + Arm64FrintmS, + Arm64FrintnV, + Arm64FrintnS, + Arm64FrintpV, + Arm64FrintpS, + Arm64FrintxV, + Arm64FrintxS, + Arm64FrintzV, + Arm64FrintzS, + Arm64FrsqrteS, + Arm64FrsqrteV, + Arm64FrsqrtsS, + Arm64FrsqrtsV, + Arm64FsqrtV, + Arm64FsqrtS, + Arm64FsubV, + Arm64FsubS, + Arm64InsVe, + Arm64InsGp, + Arm64Ld1rV, + Arm64Ld1Vms, + Arm64Ld1Vss, + Arm64Ld2rV, + Arm64Ld2Vms, + Arm64Ld2Vss, + Arm64Ld3rV, + Arm64Ld3Vms, + Arm64Ld3Vss, + Arm64Ld4rV, + Arm64Ld4Vms, + Arm64Ld4Vss, + Arm64MlaVe, + Arm64MlaV, + Arm64MlsVe, + Arm64MlsV, + Arm64MoviV, + Arm64MrsFpcr, + Arm64MsrFpcr, + Arm64MrsFpsr, + Arm64MsrFpsr, + Arm64MulVe, + Arm64MulV, + Arm64MvniV, + Arm64NegS, + Arm64NegV, + Arm64NotV, + Arm64OrnV, + Arm64OrrVi, + Arm64OrrV, + Arm64PmullV, + Arm64PmulV, + Arm64RaddhnV, + Arm64RbitV, + Arm64Rev16V, + Arm64Rev32V, + Arm64Rev64V, + Arm64RshrnV, + Arm64RsubhnV, + Arm64SabalV, + Arm64SabaV, + Arm64SabdlV, + Arm64SabdV, + Arm64SadalpV, + Arm64SaddlpV, + Arm64SaddlvV, + Arm64SaddlV, + Arm64SaddwV, + Arm64ScvtfSFixed, + Arm64ScvtfVFixed, + Arm64ScvtfS, + Arm64ScvtfV, + Arm64ScvtfGpFixed, + Arm64ScvtfGp, + Arm64Sha1cV, + Arm64Sha1hV, + Arm64Sha1mV, + Arm64Sha1pV, + Arm64Sha1su0V, + Arm64Sha1su1V, + Arm64Sha256h2V, + Arm64Sha256hV, + Arm64Sha256su0V, + Arm64Sha256su1V, + Arm64ShaddV, + Arm64ShllV, + Arm64ShlS, + Arm64ShlV, + Arm64ShrnV, + Arm64ShsubV, + Arm64SliS, + Arm64SliV, + Arm64SmaxpV, + Arm64SmaxvV, + Arm64SmaxV, + Arm64SminpV, + Arm64SminvV, + Arm64SminV, + Arm64SmlalVe, + Arm64SmlalV, + Arm64SmlslVe, + Arm64SmlslV, + Arm64SmovV, + Arm64SmullVe, + Arm64SmullV, + Arm64SqabsS, + Arm64SqabsV, + Arm64SqaddS, + Arm64SqaddV, + Arm64SqdmlalSe, + Arm64SqdmlalVe, + Arm64SqdmlalS, + Arm64SqdmlalV, + Arm64SqdmlslSe, + Arm64SqdmlslVe, + Arm64SqdmlslS, + Arm64SqdmlslV, + Arm64SqdmulhSe, + Arm64SqdmulhVe, + Arm64SqdmulhS, + Arm64SqdmulhV, + Arm64SqdmullSe, + Arm64SqdmullVe, + Arm64SqdmullS, + Arm64SqdmullV, + Arm64SqnegS, + Arm64SqnegV, + Arm64SqrdmulhSe, + Arm64SqrdmulhVe, + Arm64SqrdmulhS, + Arm64SqrdmulhV, + Arm64SqrshlS, + Arm64SqrshlV, + Arm64SqrshrnS, + Arm64SqrshrnV, + Arm64SqrshrunS, + Arm64SqrshrunV, + Arm64SqshluS, + Arm64SqshluV, + Arm64SqshlSi, + Arm64SqshlVi, + Arm64SqshlS, + Arm64SqshlV, + Arm64SqshrnS, + Arm64SqshrnV, + Arm64SqshrunS, + Arm64SqshrunV, + Arm64SqsubS, + Arm64SqsubV, + Arm64SqxtnS, + Arm64SqxtnV, + Arm64SqxtunS, + Arm64SqxtunV, + Arm64SrhaddV, + Arm64SriS, + Arm64SriV, + Arm64SrshlS, + Arm64SrshlV, + Arm64SrshrS, + Arm64SrshrV, + Arm64SrsraS, + Arm64SrsraV, + Arm64SshllV, + Arm64SshlS, + Arm64SshlV, + Arm64SshrS, + Arm64SshrV, + Arm64SsraS, + Arm64SsraV, + Arm64SsublV, + Arm64SsubwV, + Arm64St1Vms, + Arm64St1Vss, + Arm64St2Vms, + Arm64St2Vss, + Arm64St3Vms, + Arm64St3Vss, + Arm64St4Vms, + Arm64St4Vss, + Arm64SubhnV, + Arm64SubS, + Arm64SubV, + Arm64SuqaddS, + Arm64SuqaddV, + Arm64TblV, + Arm64TbxV, + Arm64Trn1V, + Arm64Trn2V, + Arm64UabalV, + Arm64UabaV, + Arm64UabdlV, + Arm64UabdV, + Arm64UadalpV, + Arm64UaddlpV, + Arm64UaddlvV, + Arm64UaddlV, + Arm64UaddwV, + Arm64UcvtfSFixed, + Arm64UcvtfVFixed, + Arm64UcvtfS, + Arm64UcvtfV, + Arm64UcvtfGpFixed, + Arm64UcvtfGp, + Arm64UhaddV, + Arm64UhsubV, + Arm64UmaxpV, + Arm64UmaxvV, + Arm64UmaxV, + Arm64UminpV, + Arm64UminvV, + Arm64UminV, + Arm64UmlalVe, + Arm64UmlalV, + Arm64UmlslVe, + Arm64UmlslV, + Arm64UmovV, + Arm64UmullVe, + Arm64UmullV, + Arm64UqaddS, + Arm64UqaddV, + Arm64UqrshlS, + Arm64UqrshlV, + Arm64UqrshrnS, + Arm64UqrshrnV, + Arm64UqshlSi, + Arm64UqshlVi, + Arm64UqshlS, + Arm64UqshlV, + Arm64UqshrnS, + Arm64UqshrnV, + Arm64UqsubS, + Arm64UqsubV, + Arm64UqxtnS, + Arm64UqxtnV, + Arm64UrecpeV, + Arm64UrhaddV, + Arm64UrshlS, + Arm64UrshlV, + Arm64UrshrS, + Arm64UrshrV, + Arm64UrsqrteV, + Arm64UrsraS, + Arm64UrsraV, + Arm64UshllV, + Arm64UshlS, + Arm64UshlV, + Arm64UshrS, + Arm64UshrV, + Arm64UsqaddS, + Arm64UsqaddV, + Arm64UsraS, + Arm64UsraV, + Arm64UsublV, + Arm64UsubwV, + Arm64Uzp1V, + Arm64Uzp2V, + Arm64XtnV, + Arm64Zip1V, + Arm64Zip2V, + + Arm64VTypeShift = 13, + Arm64VTypeMask = 1 << Arm64VTypeShift, + Arm64V64 = 0 << Arm64VTypeShift, + Arm64V128 = 1 << Arm64VTypeShift, + + Arm64VSizeShift = 14, + Arm64VSizeMask = 3 << Arm64VSizeShift, + Arm64VFloat = 0 << Arm64VSizeShift, + Arm64VDouble = 1 << Arm64VSizeShift, + Arm64VByte = 0 << Arm64VSizeShift, + Arm64VHWord = 1 << Arm64VSizeShift, + Arm64VWord = 2 << Arm64VSizeShift, + Arm64VDWord = 3 << Arm64VSizeShift, + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/IntrusiveList.cs b/src/ARMeilleure/IntermediateRepresentation/IntrusiveList.cs new file mode 100644 index 00000000..8d300075 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/IntrusiveList.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.IntermediateRepresentation +{ + /// + /// Represents a efficient linked list that stores the pointer on the object directly and does not allocate. + /// + /// Type of the list items + class IntrusiveList where T : IEquatable, IIntrusiveListNode + { + /// + /// First item of the list, or null if empty. + /// + public T First { get; private set; } + + /// + /// Last item of the list, or null if empty. + /// + public T Last { get; private set; } + + /// + /// Total number of items on the list. + /// + public int Count { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// is not pointer sized. + public IntrusiveList() + { + if (Unsafe.SizeOf() != IntPtr.Size) + { + throw new ArgumentException("T must be a reference type or a pointer sized struct."); + } + } + + /// + /// Adds a item as the first item of the list. + /// + /// Item to be added + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T AddFirst(T newNode) + { + if (!EqualsNull(First)) + { + return AddBefore(First, newNode); + } + else + { + Debug.Assert(EqualsNull(newNode.ListPrevious)); + Debug.Assert(EqualsNull(newNode.ListNext)); + Debug.Assert(EqualsNull(Last)); + + First = newNode; + Last = newNode; + + Debug.Assert(Count == 0); + + Count = 1; + + return newNode; + } + } + + /// + /// Adds a item as the last item of the list. + /// + /// Item to be added + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T AddLast(T newNode) + { + if (!EqualsNull(Last)) + { + return AddAfter(Last, newNode); + } + else + { + Debug.Assert(EqualsNull(newNode.ListPrevious)); + Debug.Assert(EqualsNull(newNode.ListNext)); + Debug.Assert(EqualsNull(First)); + + First = newNode; + Last = newNode; + + Debug.Assert(Count == 0); + + Count = 1; + + return newNode; + } + } + + /// + /// Adds a item before a existing item on the list. + /// + /// Item on the list that will succeed the new item + /// Item to be added + /// New item + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T AddBefore(T node, T newNode) + { + Debug.Assert(EqualsNull(newNode.ListPrevious)); + Debug.Assert(EqualsNull(newNode.ListNext)); + + newNode.ListPrevious = node.ListPrevious; + newNode.ListNext = node; + + node.ListPrevious = newNode; + + if (!EqualsNull(newNode.ListPrevious)) + { + newNode.ListPrevious.ListNext = newNode; + } + + if (Equals(First, node)) + { + First = newNode; + } + + Count++; + + return newNode; + } + + /// + /// Adds a item after a existing item on the list. + /// + /// Item on the list that will preceed the new item + /// Item to be added + /// New item + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T AddAfter(T node, T newNode) + { + Debug.Assert(EqualsNull(newNode.ListPrevious)); + Debug.Assert(EqualsNull(newNode.ListNext)); + + newNode.ListPrevious = node; + newNode.ListNext = node.ListNext; + + node.ListNext = newNode; + + if (!EqualsNull(newNode.ListNext)) + { + newNode.ListNext.ListPrevious = newNode; + } + + if (Equals(Last, node)) + { + Last = newNode; + } + + Count++; + + return newNode; + } + + /// + /// Removes a item from the list. + /// + /// The item to be removed + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Remove(T node) + { + if (!EqualsNull(node.ListPrevious)) + { + node.ListPrevious.ListNext = node.ListNext; + } + else + { + Debug.Assert(Equals(First, node)); + + First = node.ListNext; + } + + if (!EqualsNull(node.ListNext)) + { + node.ListNext.ListPrevious = node.ListPrevious; + } + else + { + Debug.Assert(Equals(Last, node)); + + Last = node.ListPrevious; + } + + node.ListPrevious = default; + node.ListNext = default; + + Count--; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool EqualsNull(T a) + { + return EqualityComparer.Default.Equals(a, default); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool Equals(T a, T b) + { + return EqualityComparer.Default.Equals(a, b); + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/MemoryOperand.cs b/src/ARMeilleure/IntermediateRepresentation/MemoryOperand.cs new file mode 100644 index 00000000..9b3df8ca --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/MemoryOperand.cs @@ -0,0 +1,54 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.IntermediateRepresentation +{ + readonly unsafe struct MemoryOperand + { + private struct Data + { +#pragma warning disable CS0649 // Field is never assigned to + public byte Kind; + public byte Type; +#pragma warning restore CS0649 + public byte Scale; + public Operand BaseAddress; + public Operand Index; + public int Displacement; + } + + private readonly Data* _data; + + public MemoryOperand(Operand operand) + { + Debug.Assert(operand.Kind == OperandKind.Memory); + + _data = (Data*)Unsafe.As(ref operand); + } + + public Operand BaseAddress + { + get => _data->BaseAddress; + set => _data->BaseAddress = value; + } + + public Operand Index + { + get => _data->Index; + set => _data->Index = value; + } + + public Multiplier Scale + { + get => (Multiplier)_data->Scale; + set => _data->Scale = (byte)value; + } + + public int Displacement + { + get => _data->Displacement; + set => _data->Displacement = value; + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Multiplier.cs b/src/ARMeilleure/IntermediateRepresentation/Multiplier.cs new file mode 100644 index 00000000..6bcdda01 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Multiplier.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum Multiplier + { + x1 = 0, + x2 = 1, + x4 = 2, + x8 = 3, + x16 = 4, + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Operand.cs b/src/ARMeilleure/IntermediateRepresentation/Operand.cs new file mode 100644 index 00000000..89aefacb --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Operand.cs @@ -0,0 +1,598 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.Common; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.IntermediateRepresentation +{ + unsafe struct Operand : IEquatable + { + internal struct Data + { + public byte Kind; + public byte Type; + public byte SymbolType; + public byte Padding; // Unused space. + public ushort AssignmentsCount; + public ushort AssignmentsCapacity; + public uint UsesCount; + public uint UsesCapacity; + public Operation* Assignments; + public Operation* Uses; + public ulong Value; + public ulong SymbolValue; + } + + private Data* _data; + + public readonly OperandKind Kind + { + get => (OperandKind)_data->Kind; + private set => _data->Kind = (byte)value; + } + + public readonly OperandType Type + { + get => (OperandType)_data->Type; + private set => _data->Type = (byte)value; + } + + public readonly ulong Value + { + get => _data->Value; + private set => _data->Value = value; + } + + public readonly Symbol Symbol + { + get + { + Debug.Assert(Kind != OperandKind.Memory); + + return new Symbol((SymbolType)_data->SymbolType, _data->SymbolValue); + } + private set + { + Debug.Assert(Kind != OperandKind.Memory); + + if (value.Type == SymbolType.None) + { + _data->SymbolType = (byte)SymbolType.None; + } + else + { + _data->SymbolType = (byte)value.Type; + _data->SymbolValue = value.Value; + } + } + } + + public readonly ReadOnlySpan Assignments + { + get + { + Debug.Assert(Kind != OperandKind.Memory); + + return new ReadOnlySpan(_data->Assignments, _data->AssignmentsCount); + } + } + + public readonly ReadOnlySpan Uses + { + get + { + Debug.Assert(Kind != OperandKind.Memory); + + return new ReadOnlySpan(_data->Uses, (int)_data->UsesCount); + } + } + + public readonly int UsesCount => (int)_data->UsesCount; + public readonly int AssignmentsCount => _data->AssignmentsCount; + + public readonly bool Relocatable => Symbol.Type != SymbolType.None; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Register GetRegister() + { + Debug.Assert(Kind == OperandKind.Register); + + return new Register((int)Value & 0xffffff, (RegisterType)(Value >> 24)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly MemoryOperand GetMemory() + { + Debug.Assert(Kind == OperandKind.Memory); + + return new MemoryOperand(this); + } + + public readonly int GetLocalNumber() + { + Debug.Assert(Kind == OperandKind.LocalVariable); + + return (int)Value; + } + + public readonly byte AsByte() + { + return (byte)Value; + } + + public readonly short AsInt16() + { + return (short)Value; + } + + public readonly int AsInt32() + { + return (int)Value; + } + + public readonly long AsInt64() + { + return (long)Value; + } + + public readonly float AsFloat() + { + return BitConverter.Int32BitsToSingle((int)Value); + } + + public readonly double AsDouble() + { + return BitConverter.Int64BitsToDouble((long)Value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly ref ulong GetValueUnsafe() + { + return ref _data->Value; + } + + internal void NumberLocal(int number) + { + if (Kind != OperandKind.LocalVariable) + { + throw new InvalidOperationException("The operand is not a local variable."); + } + + Value = (ulong)number; + } + + public readonly void AddAssignment(Operation operation) + { + if (Kind == OperandKind.LocalVariable) + { + Add(operation, ref _data->Assignments, ref _data->AssignmentsCount, ref _data->AssignmentsCapacity); + } + else if (Kind == OperandKind.Memory) + { + MemoryOperand memOp = GetMemory(); + Operand addr = memOp.BaseAddress; + Operand index = memOp.Index; + + if (addr != default) + { + Add(operation, ref addr._data->Assignments, ref addr._data->AssignmentsCount, ref addr._data->AssignmentsCapacity); + } + + if (index != default) + { + Add(operation, ref index._data->Assignments, ref index._data->AssignmentsCount, ref index._data->AssignmentsCapacity); + } + } + } + + public readonly void RemoveAssignment(Operation operation) + { + if (Kind == OperandKind.LocalVariable) + { + Remove(operation, ref _data->Assignments, ref _data->AssignmentsCount); + } + else if (Kind == OperandKind.Memory) + { + MemoryOperand memOp = GetMemory(); + Operand addr = memOp.BaseAddress; + Operand index = memOp.Index; + + if (addr != default) + { + Remove(operation, ref addr._data->Assignments, ref addr._data->AssignmentsCount); + } + + if (index != default) + { + Remove(operation, ref index._data->Assignments, ref index._data->AssignmentsCount); + } + } + } + + public readonly void AddUse(Operation operation) + { + if (Kind == OperandKind.LocalVariable) + { + Add(operation, ref _data->Uses, ref _data->UsesCount, ref _data->UsesCapacity); + } + else if (Kind == OperandKind.Memory) + { + MemoryOperand memOp = GetMemory(); + Operand addr = memOp.BaseAddress; + Operand index = memOp.Index; + + if (addr != default) + { + Add(operation, ref addr._data->Uses, ref addr._data->UsesCount, ref addr._data->UsesCapacity); + } + + if (index != default) + { + Add(operation, ref index._data->Uses, ref index._data->UsesCount, ref index._data->UsesCapacity); + } + } + } + + public readonly void RemoveUse(Operation operation) + { + if (Kind == OperandKind.LocalVariable) + { + Remove(operation, ref _data->Uses, ref _data->UsesCount); + } + else if (Kind == OperandKind.Memory) + { + MemoryOperand memOp = GetMemory(); + Operand addr = memOp.BaseAddress; + Operand index = memOp.Index; + + if (addr != default) + { + Remove(operation, ref addr._data->Uses, ref addr._data->UsesCount); + } + + if (index != default) + { + Remove(operation, ref index._data->Uses, ref index._data->UsesCount); + } + } + } + + public readonly Span GetUses(ref Span buffer) + { + ReadOnlySpan uses = Uses; + + if (buffer.Length < uses.Length) + { + buffer = Allocators.Default.AllocateSpan((uint)uses.Length); + } + + uses.CopyTo(buffer); + + return buffer[..uses.Length]; + } + + private static void New(ref T* data, ref ushort count, ref ushort capacity, ushort initialCapacity) where T : unmanaged + { + count = 0; + capacity = initialCapacity; + data = Allocators.References.Allocate(initialCapacity); + } + + private static void New(ref T* data, ref uint count, ref uint capacity, uint initialCapacity) where T : unmanaged + { + count = 0; + capacity = initialCapacity; + data = Allocators.References.Allocate(initialCapacity); + } + + private static void Add(T item, ref T* data, ref ushort count, ref ushort capacity) where T : unmanaged + { + if (count < capacity) + { + data[(uint)count++] = item; + + return; + } + + // Could not add item in the fast path, fallback onto the slow path. + ExpandAdd(item, ref data, ref count, ref capacity); + + static void ExpandAdd(T item, ref T* data, ref ushort count, ref ushort capacity) + { + ushort newCount = checked((ushort)(count + 1)); + ushort newCapacity = (ushort)Math.Min(capacity * 2, ushort.MaxValue); + + var oldSpan = new Span(data, count); + + capacity = newCapacity; + data = Allocators.References.Allocate(capacity); + + oldSpan.CopyTo(new Span(data, count)); + + data[count] = item; + count = newCount; + } + } + + private static void Add(T item, ref T* data, ref uint count, ref uint capacity) where T : unmanaged + { + if (count < capacity) + { + data[count++] = item; + + return; + } + + // Could not add item in the fast path, fallback onto the slow path. + ExpandAdd(item, ref data, ref count, ref capacity); + + static void ExpandAdd(T item, ref T* data, ref uint count, ref uint capacity) + { + uint newCount = checked(count + 1); + uint newCapacity = (uint)Math.Min(capacity * 2, int.MaxValue); + + if (newCapacity <= capacity) + { + throw new OverflowException(); + } + + var oldSpan = new Span(data, (int)count); + + capacity = newCapacity; + data = Allocators.References.Allocate(capacity); + + oldSpan.CopyTo(new Span(data, (int)count)); + + data[count] = item; + count = newCount; + } + } + + private static void Remove(in T item, ref T* data, ref ushort count) where T : unmanaged + { + var span = new Span(data, count); + + for (int i = 0; i < span.Length; i++) + { + if (EqualityComparer.Default.Equals(span[i], item)) + { + if (i + 1 < count) + { + span[(i + 1)..].CopyTo(span[i..]); + } + + count--; + + return; + } + } + } + + private static void Remove(in T item, ref T* data, ref uint count) where T : unmanaged + { + var span = new Span(data, (int)count); + + for (int i = 0; i < span.Length; i++) + { + if (EqualityComparer.Default.Equals(span[i], item)) + { + if (i + 1 < count) + { + span[(i + 1)..].CopyTo(span[i..]); + } + + count--; + + return; + } + } + } + + public readonly override int GetHashCode() + { + return ((ulong)_data).GetHashCode(); + } + + public readonly bool Equals(Operand operand) + { + return operand._data == _data; + } + + public readonly override bool Equals(object obj) + { + return obj is Operand operand && Equals(operand); + } + + public static bool operator ==(Operand a, Operand b) + { + return a.Equals(b); + } + + public static bool operator !=(Operand a, Operand b) + { + return !a.Equals(b); + } + + public static class Factory + { + private const int InternTableSize = 256; + private const int InternTableProbeLength = 8; + + [ThreadStatic] + private static Data* _internTable; + + private static Data* InternTable + { + get + { + if (_internTable == null) + { + _internTable = (Data*)NativeAllocator.Instance.Allocate((uint)sizeof(Data) * InternTableSize); + + // Make sure the table is zeroed. + new Span(_internTable, InternTableSize).Clear(); + } + + return _internTable; + } + } + + private static Operand Make(OperandKind kind, OperandType type, ulong value, Symbol symbol = default) + { + Debug.Assert(kind != OperandKind.None); + + Data* data = null; + + // If constant or register, then try to look up in the intern table before allocating. + if (kind == OperandKind.Constant || kind == OperandKind.Register) + { + uint hash = (uint)HashCode.Combine(kind, type, value); + + // Look in the next InternTableProbeLength slots for a match. + for (uint i = 0; i < InternTableProbeLength; i++) + { + Operand interned = new() + { + _data = &InternTable[(hash + i) % InternTableSize], + }; + + // If slot matches the allocation request then return that slot. + if (interned.Kind == kind && interned.Type == type && interned.Value == value && interned.Symbol == symbol) + { + return interned; + } + // Otherwise if the slot is not occupied, we store in that slot. + else if (interned.Kind == OperandKind.None) + { + data = interned._data; + + break; + } + } + } + + // If we could not get a slot from the intern table, we allocate somewhere else and store there. + if (data == null) + { + data = Allocators.Operands.Allocate(); + } + + *data = default; + + Operand result = new() + { + _data = data, + Value = value, + Kind = kind, + Type = type, + }; + + if (kind != OperandKind.Memory) + { + result.Symbol = symbol; + } + + // If local variable, then the use and def list is initialized with default sizes. + if (kind == OperandKind.LocalVariable) + { + New(ref result._data->Assignments, ref result._data->AssignmentsCount, ref result._data->AssignmentsCapacity, 1); + New(ref result._data->Uses, ref result._data->UsesCount, ref result._data->UsesCapacity, 4); + } + + return result; + } + + public static Operand Const(OperandType type, long value) + { + Debug.Assert(type is OperandType.I32 or OperandType.I64); + + return type == OperandType.I32 ? Const((int)value) : Const(value); + } + + public static Operand Const(bool value) + { + return Const(value ? 1 : 0); + } + + public static Operand Const(int value) + { + return Const((uint)value); + } + + public static Operand Const(uint value) + { + return Make(OperandKind.Constant, OperandType.I32, value); + } + + public static Operand Const(long value) + { + return Const(value, symbol: default); + } + + public static Operand Const(ref T reference, Symbol symbol = default) + { + return Const((long)Unsafe.AsPointer(ref reference), symbol); + } + + public static Operand Const(long value, Symbol symbol) + { + return Make(OperandKind.Constant, OperandType.I64, (ulong)value, symbol); + } + + public static Operand Const(ulong value) + { + return Make(OperandKind.Constant, OperandType.I64, value); + } + + public static Operand ConstF(float value) + { + return Make(OperandKind.Constant, OperandType.FP32, (ulong)BitConverter.SingleToInt32Bits(value)); + } + + public static Operand ConstF(double value) + { + return Make(OperandKind.Constant, OperandType.FP64, (ulong)BitConverter.DoubleToInt64Bits(value)); + } + + public static Operand Label() + { + return Make(OperandKind.Label, OperandType.None, 0); + } + + public static Operand Local(OperandType type) + { + return Make(OperandKind.LocalVariable, type, 0); + } + + public static Operand Register(int index, RegisterType regType, OperandType type) + { + return Make(OperandKind.Register, type, (ulong)((int)regType << 24 | index)); + } + + public static Operand Undef() + { + return Make(OperandKind.Undefined, OperandType.None, 0); + } + + public static Operand MemoryOp( + OperandType type, + Operand baseAddress, + Operand index = default, + Multiplier scale = Multiplier.x1, + int displacement = 0) + { + Operand result = Make(OperandKind.Memory, type, 0); + + MemoryOperand memory = result.GetMemory(); + memory.BaseAddress = baseAddress; + memory.Index = index; + memory.Scale = scale; + memory.Displacement = displacement; + + return result; + } + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/OperandKind.cs b/src/ARMeilleure/IntermediateRepresentation/OperandKind.cs new file mode 100644 index 00000000..2b973f00 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/OperandKind.cs @@ -0,0 +1,13 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum OperandKind + { + None, + Constant, + Label, + LocalVariable, + Memory, + Register, + Undefined, + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/OperandType.cs b/src/ARMeilleure/IntermediateRepresentation/OperandType.cs new file mode 100644 index 00000000..67ebdcde --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/OperandType.cs @@ -0,0 +1,62 @@ +using System; + +namespace ARMeilleure.IntermediateRepresentation +{ + enum OperandType + { + None, + I32, + I64, + FP32, + FP64, + V128, + } + + static class OperandTypeExtensions + { + public static bool IsInteger(this OperandType type) + { + return type == OperandType.I32 || + type == OperandType.I64; + } + + public static RegisterType ToRegisterType(this OperandType type) + { + return type switch + { + OperandType.FP32 => RegisterType.Vector, + OperandType.FP64 => RegisterType.Vector, + OperandType.I32 => RegisterType.Integer, + OperandType.I64 => RegisterType.Integer, + OperandType.V128 => RegisterType.Vector, + _ => throw new InvalidOperationException($"Invalid operand type \"{type}\"."), + }; + } + + public static int GetSizeInBytes(this OperandType type) + { + return type switch + { + OperandType.FP32 => 4, + OperandType.FP64 => 8, + OperandType.I32 => 4, + OperandType.I64 => 8, + OperandType.V128 => 16, + _ => throw new InvalidOperationException($"Invalid operand type \"{type}\"."), + }; + } + + public static int GetSizeInBytesLog2(this OperandType type) + { + return type switch + { + OperandType.FP32 => 2, + OperandType.FP64 => 3, + OperandType.I32 => 2, + OperandType.I64 => 3, + OperandType.V128 => 4, + _ => throw new InvalidOperationException($"Invalid operand type \"{type}\"."), + }; + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Operation.cs b/src/ARMeilleure/IntermediateRepresentation/Operation.cs new file mode 100644 index 00000000..bc3a71b3 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Operation.cs @@ -0,0 +1,378 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.IntermediateRepresentation +{ + unsafe struct Operation : IEquatable, IIntrusiveListNode + { + internal struct Data + { + public ushort Instruction; + public ushort Intrinsic; + public ushort SourcesCount; + public ushort DestinationsCount; + public Operation ListPrevious; + public Operation ListNext; + public Operand* Destinations; + public Operand* Sources; + } + + private Data* _data; + + public readonly Instruction Instruction + { + get => (Instruction)_data->Instruction; + private set => _data->Instruction = (ushort)value; + } + + public readonly Intrinsic Intrinsic + { + get => (Intrinsic)_data->Intrinsic; + private set => _data->Intrinsic = (ushort)value; + } + + public readonly Operation ListPrevious + { + get => _data->ListPrevious; + set => _data->ListPrevious = value; + } + + public readonly Operation ListNext + { + get => _data->ListNext; + set => _data->ListNext = value; + } + + public readonly Operand Destination + { + get => _data->DestinationsCount != 0 ? GetDestination(0) : default; + set => SetDestination(value); + } + + public readonly int DestinationsCount => _data->DestinationsCount; + public readonly int SourcesCount => _data->SourcesCount; + + internal readonly Span DestinationsUnsafe => new(_data->Destinations, _data->DestinationsCount); + internal readonly Span SourcesUnsafe => new(_data->Sources, _data->SourcesCount); + + public readonly PhiOperation AsPhi() + { + Debug.Assert(Instruction == Instruction.Phi); + + return new PhiOperation(this); + } + + public readonly Operand GetDestination(int index) + { + return DestinationsUnsafe[index]; + } + + public readonly Operand GetSource(int index) + { + return SourcesUnsafe[index]; + } + + public readonly void SetDestination(int index, Operand dest) + { + ref Operand curDest = ref DestinationsUnsafe[index]; + + RemoveAssignment(curDest); + AddAssignment(dest); + + curDest = dest; + } + + public readonly void SetSource(int index, Operand src) + { + ref Operand curSrc = ref SourcesUnsafe[index]; + + RemoveUse(curSrc); + AddUse(src); + + curSrc = src; + } + + private readonly void RemoveOldDestinations() + { + for (int i = 0; i < _data->DestinationsCount; i++) + { + RemoveAssignment(_data->Destinations[i]); + } + } + + public readonly void SetDestination(Operand dest) + { + RemoveOldDestinations(); + + if (dest == default) + { + _data->DestinationsCount = 0; + } + else + { + EnsureCapacity(ref _data->Destinations, ref _data->DestinationsCount, 1); + + _data->Destinations[0] = dest; + + AddAssignment(dest); + } + } + + public readonly void SetDestinations(Operand[] dests) + { + RemoveOldDestinations(); + + EnsureCapacity(ref _data->Destinations, ref _data->DestinationsCount, dests.Length); + + for (int index = 0; index < dests.Length; index++) + { + Operand newOp = dests[index]; + + _data->Destinations[index] = newOp; + + AddAssignment(newOp); + } + } + + private readonly void RemoveOldSources() + { + for (int index = 0; index < _data->SourcesCount; index++) + { + RemoveUse(_data->Sources[index]); + } + } + + public readonly void SetSource(Operand src) + { + RemoveOldSources(); + + if (src == default) + { + _data->SourcesCount = 0; + } + else + { + EnsureCapacity(ref _data->Sources, ref _data->SourcesCount, 1); + + _data->Sources[0] = src; + + AddUse(src); + } + } + + public readonly void SetSources(Operand[] srcs) + { + RemoveOldSources(); + + EnsureCapacity(ref _data->Sources, ref _data->SourcesCount, srcs.Length); + + for (int index = 0; index < srcs.Length; index++) + { + Operand newOp = srcs[index]; + + _data->Sources[index] = newOp; + + AddUse(newOp); + } + } + + public void TurnIntoCopy(Operand source) + { + Instruction = Instruction.Copy; + + SetSource(source); + } + + private readonly void AddAssignment(Operand op) + { + if (op != default) + { + op.AddAssignment(this); + } + } + + private readonly void RemoveAssignment(Operand op) + { + if (op != default) + { + op.RemoveAssignment(this); + } + } + + private readonly void AddUse(Operand op) + { + if (op != default) + { + op.AddUse(this); + } + } + + private readonly void RemoveUse(Operand op) + { + if (op != default) + { + op.RemoveUse(this); + } + } + + public readonly bool Equals(Operation operation) + { + return operation._data == _data; + } + + public readonly override bool Equals(object obj) + { + return obj is Operation operation && Equals(operation); + } + + public readonly override int GetHashCode() + { + return HashCode.Combine((IntPtr)_data); + } + + public static bool operator ==(Operation a, Operation b) + { + return a.Equals(b); + } + + public static bool operator !=(Operation a, Operation b) + { + return !a.Equals(b); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EnsureCapacity(ref Operand* list, ref ushort capacity, int newCapacity) + { + if (newCapacity > ushort.MaxValue) + { + ThrowOverflow(newCapacity); + } + // We only need to allocate a new buffer if we're increasing the size. + else if (newCapacity > capacity) + { + list = Allocators.References.Allocate((uint)newCapacity); + } + + capacity = (ushort)newCapacity; + } + + private static void ThrowOverflow(int count) => + throw new OverflowException($"Exceeded maximum size for Source or Destinations. Required {count}."); + + public static class Factory + { + private static Operation Make(Instruction inst, int destCount, int srcCount) + { + Data* data = Allocators.Operations.Allocate(); + *data = default; + + Operation result = new() + { + _data = data, + Instruction = inst, + }; + + EnsureCapacity(ref result._data->Destinations, ref result._data->DestinationsCount, destCount); + EnsureCapacity(ref result._data->Sources, ref result._data->SourcesCount, srcCount); + + result.DestinationsUnsafe.Clear(); + result.SourcesUnsafe.Clear(); + + return result; + } + + public static Operation Operation(Instruction inst, Operand dest) + { + Operation result = Make(inst, 0, 0); + result.SetDestination(dest); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, Operand src0) + { + Operation result = Make(inst, 0, 1); + result.SetDestination(dest); + result.SetSource(0, src0); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, Operand src0, Operand src1) + { + Operation result = Make(inst, 0, 2); + result.SetDestination(dest); + result.SetSource(0, src0); + result.SetSource(1, src1); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, Operand src0, Operand src1, Operand src2) + { + Operation result = Make(inst, 0, 3); + result.SetDestination(dest); + result.SetSource(0, src0); + result.SetSource(1, src1); + result.SetSource(2, src2); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, int srcCount) + { + Operation result = Make(inst, 0, srcCount); + result.SetDestination(dest); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, Operand[] srcs) + { + Operation result = Make(inst, 0, srcs.Length); + + result.SetDestination(dest); + + for (int index = 0; index < srcs.Length; index++) + { + result.SetSource(index, srcs[index]); + } + + return result; + } + + public static Operation Operation(Intrinsic intrin, Operand dest, params Operand[] srcs) + { + Operation result = Make(Instruction.Extended, 0, srcs.Length); + + result.Intrinsic = intrin; + result.SetDestination(dest); + + for (int index = 0; index < srcs.Length; index++) + { + result.SetSource(index, srcs[index]); + } + + return result; + } + + public static Operation Operation(Instruction inst, Operand[] dests, Operand[] srcs) + { + Operation result = Make(inst, dests.Length, srcs.Length); + + for (int index = 0; index < dests.Length; index++) + { + result.SetDestination(index, dests[index]); + } + + for (int index = 0; index < srcs.Length; index++) + { + result.SetSource(index, srcs[index]); + } + + return result; + } + + public static Operation PhiOperation(Operand dest, int srcCount) + { + return Operation(Instruction.Phi, dest, srcCount * 2); + } + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/PhiOperation.cs b/src/ARMeilleure/IntermediateRepresentation/PhiOperation.cs new file mode 100644 index 00000000..672c280f --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/PhiOperation.cs @@ -0,0 +1,37 @@ +using ARMeilleure.Translation; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.IntermediateRepresentation +{ + readonly struct PhiOperation + { + private readonly Operation _operation; + + public PhiOperation(Operation operation) + { + _operation = operation; + } + + public int SourcesCount => _operation.SourcesCount / 2; + + public BasicBlock GetBlock(ControlFlowGraph cfg, int index) + { + return cfg.PostOrderBlocks[cfg.PostOrderMap[_operation.GetSource(index * 2).AsInt32()]]; + } + + public void SetBlock(int index, BasicBlock block) + { + _operation.SetSource(index * 2, Const(block.Index)); + } + + public Operand GetSource(int index) + { + return _operation.GetSource(index * 2 + 1); + } + + public void SetSource(int index, Operand operand) + { + _operation.SetSource(index * 2 + 1, operand); + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Register.cs b/src/ARMeilleure/IntermediateRepresentation/Register.cs new file mode 100644 index 00000000..208f94be --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Register.cs @@ -0,0 +1,43 @@ +using System; + +namespace ARMeilleure.IntermediateRepresentation +{ + readonly struct Register : IEquatable + { + public int Index { get; } + + public RegisterType Type { get; } + + public Register(int index, RegisterType type) + { + Index = index; + Type = type; + } + + public override int GetHashCode() + { + return (ushort)Index | ((int)Type << 16); + } + + public static bool operator ==(Register x, Register y) + { + return x.Equals(y); + } + + public static bool operator !=(Register x, Register y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is Register reg && Equals(reg); + } + + public bool Equals(Register other) + { + return other.Index == Index && + other.Type == Type; + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/RegisterType.cs b/src/ARMeilleure/IntermediateRepresentation/RegisterType.cs new file mode 100644 index 00000000..2b4c9068 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/RegisterType.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum RegisterType + { + Integer, + Vector, + Flag, + FpFlag, + } +} diff --git a/src/ARMeilleure/Memory/IJitMemoryAllocator.cs b/src/ARMeilleure/Memory/IJitMemoryAllocator.cs new file mode 100644 index 00000000..ff64bf13 --- /dev/null +++ b/src/ARMeilleure/Memory/IJitMemoryAllocator.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.Memory +{ + public interface IJitMemoryAllocator + { + IJitMemoryBlock Allocate(ulong size); + IJitMemoryBlock Reserve(ulong size); + } +} diff --git a/src/ARMeilleure/Memory/IJitMemoryBlock.cs b/src/ARMeilleure/Memory/IJitMemoryBlock.cs new file mode 100644 index 00000000..c103fe8d --- /dev/null +++ b/src/ARMeilleure/Memory/IJitMemoryBlock.cs @@ -0,0 +1,15 @@ +using System; + +namespace ARMeilleure.Memory +{ + public interface IJitMemoryBlock : IDisposable + { + IntPtr Pointer { get; } + + void Commit(ulong offset, ulong size); + + void MapAsRw(ulong offset, ulong size); + void MapAsRx(ulong offset, ulong size); + void MapAsRwx(ulong offset, ulong size); + } +} diff --git a/src/ARMeilleure/Memory/IMemoryManager.cs b/src/ARMeilleure/Memory/IMemoryManager.cs new file mode 100644 index 00000000..46d44265 --- /dev/null +++ b/src/ARMeilleure/Memory/IMemoryManager.cs @@ -0,0 +1,99 @@ +using System; + +namespace ARMeilleure.Memory +{ + public interface IMemoryManager + { + int AddressSpaceBits { get; } + + IntPtr PageTablePointer { get; } + + MemoryManagerType Type { get; } + + event Action UnmapEvent; + + /// + /// Reads data from CPU mapped memory. + /// + /// Type of the data being read + /// Virtual address of the data in memory + /// The data + T Read(ulong va) where T : unmanaged; + + /// + /// Reads data from CPU mapped memory, with read tracking + /// + /// Type of the data being read + /// Virtual address of the data in memory + /// The data + T ReadTracked(ulong va) where T : unmanaged; + + /// + /// Reads data from CPU mapped memory, from guest code. (with read tracking) + /// + /// Type of the data being read + /// Virtual address of the data in memory + /// The data + T ReadGuest(ulong va) where T : unmanaged + { + return ReadTracked(va); + } + + /// + /// Writes data to CPU mapped memory. + /// + /// Type of the data being written + /// Virtual address to write the data into + /// Data to be written + void Write(ulong va, T value) where T : unmanaged; + + /// + /// Writes data to CPU mapped memory, from guest code. + /// + /// Type of the data being written + /// Virtual address to write the data into + /// Data to be written + void WriteGuest(ulong va, T value) where T : unmanaged + { + Write(va, value); + } + + /// + /// Gets a read-only span of data from CPU mapped memory. + /// + /// Virtual address of the data + /// Size of the data + /// True if read tracking is triggered on the span + /// A read-only span of the data + ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false); + + /// + /// Gets a reference for the given type at the specified virtual memory address. + /// + /// + /// The data must be located at a contiguous memory region. + /// + /// Type of the data to get the reference + /// Virtual address of the data + /// A reference to the data in memory + ref T GetRef(ulong va) where T : unmanaged; + + /// + /// Checks if the page at a given CPU virtual address is mapped. + /// + /// Virtual address to check + /// True if the address is mapped, false otherwise + bool IsMapped(ulong va); + + /// + /// Alerts the memory tracking that a given region has been read from or written to. + /// This should be called before read/write is performed. + /// + /// Virtual address of the region + /// Size of the region + /// True if the region was written, false if read + /// True if the access is precise, false otherwise + /// Optional ID of the handles that should not be signalled + void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null); + } +} diff --git a/src/ARMeilleure/Memory/InvalidAccessException.cs b/src/ARMeilleure/Memory/InvalidAccessException.cs new file mode 100644 index 00000000..ad540719 --- /dev/null +++ b/src/ARMeilleure/Memory/InvalidAccessException.cs @@ -0,0 +1,23 @@ +using System; + +namespace ARMeilleure.Memory +{ + class InvalidAccessException : Exception + { + public InvalidAccessException() + { + } + + public InvalidAccessException(ulong address) : base($"Invalid memory access at virtual address 0x{address:X16}.") + { + } + + public InvalidAccessException(string message) : base(message) + { + } + + public InvalidAccessException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/ARMeilleure/Memory/MemoryManagerType.cs b/src/ARMeilleure/Memory/MemoryManagerType.cs new file mode 100644 index 00000000..bc8ae263 --- /dev/null +++ b/src/ARMeilleure/Memory/MemoryManagerType.cs @@ -0,0 +1,63 @@ +namespace ARMeilleure.Memory +{ + /// + /// Indicates the type of a memory manager and the method it uses for memory mapping + /// and address translation. This controls the code generated for memory accesses on the JIT. + /// + public enum MemoryManagerType + { + /// + /// Complete software MMU implementation, the read/write methods are always called, + /// without any attempt to perform faster memory access. + /// + SoftwareMmu, + + /// + /// High level implementation using a software flat page table for address translation, + /// used to speed up address translation if possible without calling the read/write methods. + /// + SoftwarePageTable, + + /// + /// High level implementation with mappings managed by the host OS, effectively using hardware + /// page tables. No address translation is performed in software and the memory is just accessed directly. + /// + HostMapped, + + /// + /// Same as the host mapped memory manager type, but without masking the address within the address space. + /// Allows invalid access from JIT code to the rest of the program, but is faster. + /// + HostMappedUnsafe, + + /// + /// High level implementation using a software flat page table for address translation + /// with no support for handling invalid or non-contiguous memory access. + /// + HostTracked, + + /// + /// High level implementation using a software flat page table for address translation + /// without masking the address and no support for handling invalid or non-contiguous memory access. + /// + HostTrackedUnsafe, + } + + public static class MemoryManagerTypeExtensions + { + public static bool IsHostMapped(this MemoryManagerType type) + { + return type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe; + } + + public static bool IsHostTracked(this MemoryManagerType type) + { + return type == MemoryManagerType.HostTracked || type == MemoryManagerType.HostTrackedUnsafe; + } + + public static bool IsHostMappedOrTracked(this MemoryManagerType type) + { + return type.IsHostMapped() || type.IsHostTracked(); + } + } +} diff --git a/src/ARMeilleure/Memory/ReservedRegion.cs b/src/ARMeilleure/Memory/ReservedRegion.cs new file mode 100644 index 00000000..3870d4c8 --- /dev/null +++ b/src/ARMeilleure/Memory/ReservedRegion.cs @@ -0,0 +1,58 @@ +using System; + +namespace ARMeilleure.Memory +{ + public class ReservedRegion + { + public const int DefaultGranularity = 65536; // Mapping granularity in Windows. + + public IJitMemoryBlock Block { get; } + + public IntPtr Pointer => Block.Pointer; + + private readonly ulong _maxSize; + private readonly ulong _sizeGranularity; + private ulong _currentSize; + + public ReservedRegion(IJitMemoryAllocator allocator, ulong maxSize, ulong granularity = 0) + { + if (granularity == 0) + { + granularity = DefaultGranularity; + } + + Block = allocator.Reserve(maxSize); + _maxSize = maxSize; + _sizeGranularity = granularity; + _currentSize = 0; + } + + public void ExpandIfNeeded(ulong desiredSize) + { + if (desiredSize > _maxSize) + { + throw new OutOfMemoryException(); + } + + if (desiredSize > _currentSize) + { + // Lock, and then check again. We only want to commit once. + lock (this) + { + if (desiredSize >= _currentSize) + { + ulong overflowBytes = desiredSize - _currentSize; + ulong moreToCommit = (((_sizeGranularity - 1) + overflowBytes) / _sizeGranularity) * _sizeGranularity; // Round up. + Block.Commit(_currentSize, moreToCommit); + _currentSize += moreToCommit; + } + } + } + } + + public void Dispose() + { + Block.Dispose(); + } + } +} diff --git a/src/ARMeilleure/Native/JitSupportDarwin.cs b/src/ARMeilleure/Native/JitSupportDarwin.cs new file mode 100644 index 00000000..33946039 --- /dev/null +++ b/src/ARMeilleure/Native/JitSupportDarwin.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace ARMeilleure.Native +{ + [SupportedOSPlatform("macos")] + static partial class JitSupportDarwin + { + [LibraryImport("libarmeilleure-jitsupport", EntryPoint = "armeilleure_jit_memcpy")] + public static partial void Copy(IntPtr dst, IntPtr src, ulong n); + } +} diff --git a/src/ARMeilleure/Native/libs/libarmeilleure-jitsupport.dylib b/src/ARMeilleure/Native/libs/libarmeilleure-jitsupport.dylib new file mode 100644 index 0000000000000000000000000000000000000000..c65b0a4efb797fdc0280c4edf5557d16142d2347 GIT binary patch literal 33564 zcmeI*UuYaf90%~ZyJ zO&aP&snmZ+Az~CoK}GM37{rHAh>Z$b`chIsQK7{8pb6+hZ5u5{{rzU=uG@pO4+WpT zLuO}xGkd??{p{v(c8;Chj`_Oy9;CdltNY%Ltz9MXJauw0)mD_W z8}*O*delpzhp(cxjVhlv*gt4*PiBWKdvMUoWv%92w&&&?CzJJBQo}SRZl}!Z&C2^e zn{+%uag1`YDd(Kc^CPGIF(DGvUP<+Ss=>nN&zdgNl z=Qkg}^UIA^)4uO-qrMnb&MO4J9kqdTd~Mu9J^p_NkNZcoQOmjf_hAFQ_94-lbF-~< z!(4lZUC$b7*`f*fXQGKJ=b6q6QA6!U9rJy2xt=PIJ9Pb%{>5-N3Irek0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_a1nvC{WFErJ)y$#UZb%5j5zp5*eIS3hl*#yqDWJlAD}tB z;wp+`)W5piUsqh-yM=PZ3upVk{NM^9>ghLAQN#R87#qG-`_+N{ z)&4$D32xNa6>ND9GS8i-dDg1FqUWu1%=4sqHgifpXF**~k3C~Nx^EU#5%Z-=T zR;TX}sKV=<3zJqXE+S2zPsEg7tA!o2Ro~6cJ#O?QBL9wlIy$odhnW{T zpS$(_OO2-@&sIHjc;uuv-t@-Yk2Sm8&4;#~uwOrL$KMBE?&xfPB7611tEVD`>4tb+ z%i`xp$BxWReOTYM?%jr_rBw^he>Q)q{Fgg^=#hIy;-`+RYb|b!ytugdTIP52&n34v O_VfJwTcb0Z+W!MamVHnF literal 0 HcmV?d00001 diff --git a/src/ARMeilleure/Native/macos_jit_support/Makefile b/src/ARMeilleure/Native/macos_jit_support/Makefile new file mode 100644 index 00000000..d6da35d5 --- /dev/null +++ b/src/ARMeilleure/Native/macos_jit_support/Makefile @@ -0,0 +1,8 @@ +NAME = libarmeilleure-jitsupport.dylib + +all: ${NAME} + +${NAME}: + clang -O3 -dynamiclib support.c -o ${NAME} +clean: + rm -f ${NAME} diff --git a/src/ARMeilleure/Native/macos_jit_support/support.c b/src/ARMeilleure/Native/macos_jit_support/support.c new file mode 100644 index 00000000..1b13d906 --- /dev/null +++ b/src/ARMeilleure/Native/macos_jit_support/support.c @@ -0,0 +1,14 @@ +#include +#include +#include + +#include + +void armeilleure_jit_memcpy(void *dst, const void *src, size_t n) { + pthread_jit_write_protect_np(0); + memcpy(dst, src, n); + pthread_jit_write_protect_np(1); + + // Ensure that the instruction cache for this range is invalidated. + sys_icache_invalidate(dst, n); +} diff --git a/src/ARMeilleure/Optimizations.cs b/src/ARMeilleure/Optimizations.cs new file mode 100644 index 00000000..8fe478e4 --- /dev/null +++ b/src/ARMeilleure/Optimizations.cs @@ -0,0 +1,70 @@ +namespace ARMeilleure +{ + using Arm64HardwareCapabilities = ARMeilleure.CodeGen.Arm64.HardwareCapabilities; + using X86HardwareCapabilities = ARMeilleure.CodeGen.X86.HardwareCapabilities; + + public static class Optimizations + { + public static bool FastFP { get; set; } = true; + + public static bool AllowLcqInFunctionTable { get; set; } = true; + public static bool UseUnmanagedDispatchLoop { get; set; } = true; + + public static bool UseAdvSimdIfAvailable { get; set; } = true; + public static bool UseArm64AesIfAvailable { get; set; } = true; + public static bool UseArm64PmullIfAvailable { get; set; } = true; + + public static bool UseSseIfAvailable { get; set; } = true; + public static bool UseSse2IfAvailable { get; set; } = true; + public static bool UseSse3IfAvailable { get; set; } = true; + public static bool UseSsse3IfAvailable { get; set; } = true; + public static bool UseSse41IfAvailable { get; set; } = true; + public static bool UseSse42IfAvailable { get; set; } = true; + public static bool UsePopCntIfAvailable { get; set; } = true; + public static bool UseAvxIfAvailable { get; set; } = true; + public static bool UseAvx512FIfAvailable { get; set; } = true; + public static bool UseAvx512VlIfAvailable { get; set; } = true; + public static bool UseAvx512BwIfAvailable { get; set; } = true; + public static bool UseAvx512DqIfAvailable { get; set; } = true; + public static bool UseF16cIfAvailable { get; set; } = true; + public static bool UseFmaIfAvailable { get; set; } = true; + public static bool UseAesniIfAvailable { get; set; } = true; + public static bool UsePclmulqdqIfAvailable { get; set; } = true; + public static bool UseShaIfAvailable { get; set; } = true; + public static bool UseGfniIfAvailable { get; set; } = true; + + public static bool ForceLegacySse + { + get => X86HardwareCapabilities.ForceLegacySse; + set => X86HardwareCapabilities.ForceLegacySse = value; + } + +#pragma warning disable IDE0055 // Disable formatting + internal static bool UseAdvSimd => UseAdvSimdIfAvailable && Arm64HardwareCapabilities.SupportsAdvSimd; + internal static bool UseArm64Aes => UseArm64AesIfAvailable && Arm64HardwareCapabilities.SupportsAes; + internal static bool UseArm64Pmull => UseArm64PmullIfAvailable && Arm64HardwareCapabilities.SupportsPmull; + + internal static bool UseSse => UseSseIfAvailable && X86HardwareCapabilities.SupportsSse; + internal static bool UseSse2 => UseSse2IfAvailable && X86HardwareCapabilities.SupportsSse2; + internal static bool UseSse3 => UseSse3IfAvailable && X86HardwareCapabilities.SupportsSse3; + internal static bool UseSsse3 => UseSsse3IfAvailable && X86HardwareCapabilities.SupportsSsse3; + internal static bool UseSse41 => UseSse41IfAvailable && X86HardwareCapabilities.SupportsSse41; + internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42; + internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt; + internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse; + internal static bool UseAvx512F => UseAvx512FIfAvailable && X86HardwareCapabilities.SupportsAvx512F && !ForceLegacySse; + internal static bool UseAvx512Vl => UseAvx512VlIfAvailable && X86HardwareCapabilities.SupportsAvx512Vl && !ForceLegacySse; + internal static bool UseAvx512Bw => UseAvx512BwIfAvailable && X86HardwareCapabilities.SupportsAvx512Bw && !ForceLegacySse; + internal static bool UseAvx512Dq => UseAvx512DqIfAvailable && X86HardwareCapabilities.SupportsAvx512Dq && !ForceLegacySse; + internal static bool UseF16c => UseF16cIfAvailable && X86HardwareCapabilities.SupportsF16c; + internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma; + internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni; + internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq; + internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha; + internal static bool UseGfni => UseGfniIfAvailable && X86HardwareCapabilities.SupportsGfni; +#pragma warning restore IDE0055 + + internal static bool UseAvx512Ortho => UseAvx512F && UseAvx512Vl; + internal static bool UseAvx512OrthoFloat => UseAvx512Ortho && UseAvx512Dq; + } +} diff --git a/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs b/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs new file mode 100644 index 00000000..2ec5bc1b --- /dev/null +++ b/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs @@ -0,0 +1,341 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Runtime.InteropServices; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Signal +{ + public static class NativeSignalHandlerGenerator + { + public const int MaxTrackedRanges = 8; + + private const int StructAddressOffset = 0; + private const int StructWriteOffset = 4; + private const int UnixOldSigaction = 8; + private const int UnixOldSigaction3Arg = 16; + private const int RangeOffset = 20; + + private const int EXCEPTION_CONTINUE_SEARCH = 0; + private const int EXCEPTION_CONTINUE_EXECUTION = -1; + + private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005; + + private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite, int rangeStructSize) + { + Operand inRegionLocal = context.AllocateLocal(OperandType.I32); + context.Copy(inRegionLocal, Const(0)); + + Operand endLabel = Label(); + + for (int i = 0; i < MaxTrackedRanges; i++) + { + ulong rangeBaseOffset = (ulong)(RangeOffset + i * rangeStructSize); + + Operand nextLabel = Label(); + + Operand isActive = context.Load(OperandType.I32, Const((ulong)signalStructPtr + rangeBaseOffset)); + + context.BranchIfFalse(nextLabel, isActive); + + Operand rangeAddress = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 4)); + Operand rangeEndAddress = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 12)); + + // Is the fault address within this tracked region? + Operand inRange = context.BitwiseAnd( + context.ICompare(faultAddress, rangeAddress, Comparison.GreaterOrEqualUI), + context.ICompare(faultAddress, rangeEndAddress, Comparison.LessUI)); + + // Only call tracking if in range. + context.BranchIfFalse(nextLabel, inRange, BasicBlockFrequency.Cold); + + Operand offset = context.Subtract(faultAddress, rangeAddress); + + // Call the tracking action, with the pointer's relative offset to the base address. + Operand trackingActionPtr = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 20)); + + context.Copy(inRegionLocal, Const(0)); + + Operand skipActionLabel = Label(); + + // Tracking action should be non-null to call it, otherwise assume false return. + context.BranchIfFalse(skipActionLabel, trackingActionPtr); + Operand result = context.Call(trackingActionPtr, OperandType.I64, offset, Const(1UL), isWrite); + context.Copy(inRegionLocal, context.ICompareNotEqual(result, Const(0UL))); + + GenerateFaultAddressPatchCode(context, faultAddress, result); + + context.MarkLabel(skipActionLabel); + + // If the tracking action returns false or does not exist, it might be an invalid access due to a partial overlap on Windows. + if (OperatingSystem.IsWindows()) + { + context.BranchIfTrue(endLabel, inRegionLocal); + + context.Copy(inRegionLocal, WindowsPartialUnmapHandler.EmitRetryFromAccessViolation(context)); + } + + context.Branch(endLabel); + + context.MarkLabel(nextLabel); + } + + context.MarkLabel(endLabel); + + return context.Copy(inRegionLocal); + } + + private static Operand GenerateUnixFaultAddress(EmitterContext context, Operand sigInfoPtr) + { + ulong structAddressOffset = OperatingSystem.IsMacOS() ? 24ul : 16ul; // si_addr + return context.Load(OperandType.I64, context.Add(sigInfoPtr, Const(structAddressOffset))); + } + + private static Operand GenerateUnixWriteFlag(EmitterContext context, Operand ucontextPtr) + { + if (OperatingSystem.IsMacOS()) + { + const ulong McontextOffset = 48; // uc_mcontext + Operand ctxPtr = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(McontextOffset))); + + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + const ulong EsrOffset = 8; // __es.__esr + Operand esr = context.Load(OperandType.I64, context.Add(ctxPtr, Const(EsrOffset))); + return context.BitwiseAnd(esr, Const(0x40ul)); + } + else if (RuntimeInformation.ProcessArchitecture == Architecture.X64) + { + const ulong ErrOffset = 4; // __es.__err + Operand err = context.Load(OperandType.I64, context.Add(ctxPtr, Const(ErrOffset))); + return context.BitwiseAnd(err, Const(2ul)); + } + } + else if (OperatingSystem.IsLinux()) + { + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + Operand auxPtr = context.AllocateLocal(OperandType.I64); + + Operand loopLabel = Label(); + Operand successLabel = Label(); + + const ulong AuxOffset = 464; // uc_mcontext.__reserved + const uint EsrMagic = 0x45535201; + + context.Copy(auxPtr, context.Add(ucontextPtr, Const(AuxOffset))); + + context.MarkLabel(loopLabel); + + // _aarch64_ctx::magic + Operand magic = context.Load(OperandType.I32, auxPtr); + // _aarch64_ctx::size + Operand size = context.Load(OperandType.I32, context.Add(auxPtr, Const(4ul))); + + context.BranchIf(successLabel, magic, Const(EsrMagic), Comparison.Equal); + + context.Copy(auxPtr, context.Add(auxPtr, context.ZeroExtend32(OperandType.I64, size))); + + context.Branch(loopLabel); + + context.MarkLabel(successLabel); + + // esr_context::esr + Operand esr = context.Load(OperandType.I64, context.Add(auxPtr, Const(8ul))); + return context.BitwiseAnd(esr, Const(0x40ul)); + } + else if (RuntimeInformation.ProcessArchitecture == Architecture.X64) + { + const int ErrOffset = 192; // uc_mcontext.gregs[REG_ERR] + Operand err = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(ErrOffset))); + return context.BitwiseAnd(err, Const(2ul)); + } + } + + throw new PlatformNotSupportedException(); + } + + public static byte[] GenerateUnixSignalHandler(IntPtr signalStructPtr, int rangeStructSize) + { + EmitterContext context = new(); + + // (int sig, SigInfo* sigInfo, void* ucontext) + Operand sigInfoPtr = context.LoadArgument(OperandType.I64, 1); + Operand ucontextPtr = context.LoadArgument(OperandType.I64, 2); + + Operand faultAddress = GenerateUnixFaultAddress(context, sigInfoPtr); + Operand writeFlag = GenerateUnixWriteFlag(context, ucontextPtr); + + Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1. + + Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize); + + Operand endLabel = Label(); + + context.BranchIfTrue(endLabel, isInRegion); + + Operand unixOldSigaction = context.Load(OperandType.I64, Const((ulong)signalStructPtr + UnixOldSigaction)); + Operand unixOldSigaction3Arg = context.Load(OperandType.I64, Const((ulong)signalStructPtr + UnixOldSigaction3Arg)); + Operand threeArgLabel = Label(); + + context.BranchIfTrue(threeArgLabel, unixOldSigaction3Arg); + + context.Call(unixOldSigaction, OperandType.None, context.LoadArgument(OperandType.I32, 0)); + context.Branch(endLabel); + + context.MarkLabel(threeArgLabel); + + context.Call(unixOldSigaction, + OperandType.None, + context.LoadArgument(OperandType.I32, 0), + sigInfoPtr, + context.LoadArgument(OperandType.I64, 2) + ); + + context.MarkLabel(endLabel); + + context.Return(); + + ControlFlowGraph cfg = context.GetControlFlowGraph(); + + OperandType[] argTypes = new OperandType[] { OperandType.I32, OperandType.I64, OperandType.I64 }; + + return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Code; + } + + public static byte[] GenerateWindowsSignalHandler(IntPtr signalStructPtr, int rangeStructSize) + { + EmitterContext context = new(); + + // (ExceptionPointers* exceptionInfo) + Operand exceptionInfoPtr = context.LoadArgument(OperandType.I64, 0); + Operand exceptionRecordPtr = context.Load(OperandType.I64, exceptionInfoPtr); + + // First thing's first - this catches a number of exceptions, but we only want access violations. + Operand validExceptionLabel = Label(); + + Operand exceptionCode = context.Load(OperandType.I32, exceptionRecordPtr); + + context.BranchIf(validExceptionLabel, exceptionCode, Const(EXCEPTION_ACCESS_VIOLATION), Comparison.Equal); + + context.Return(Const(EXCEPTION_CONTINUE_SEARCH)); // Don't handle this one. + + context.MarkLabel(validExceptionLabel); + + // Next, read the address of the invalid access, and whether it is a write or not. + + Operand structAddressOffset = context.Load(OperandType.I32, Const((ulong)signalStructPtr + StructAddressOffset)); + Operand structWriteOffset = context.Load(OperandType.I32, Const((ulong)signalStructPtr + StructWriteOffset)); + + Operand faultAddress = context.Load(OperandType.I64, context.Add(exceptionRecordPtr, context.ZeroExtend32(OperandType.I64, structAddressOffset))); + Operand writeFlag = context.Load(OperandType.I64, context.Add(exceptionRecordPtr, context.ZeroExtend32(OperandType.I64, structWriteOffset))); + + Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1. + + Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize); + + Operand endLabel = Label(); + + // If the region check result is false, then run the next vectored exception handler. + + context.BranchIfTrue(endLabel, isInRegion); + + context.Return(Const(EXCEPTION_CONTINUE_SEARCH)); + + context.MarkLabel(endLabel); + + // Otherwise, return to execution. + + context.Return(Const(EXCEPTION_CONTINUE_EXECUTION)); + + // 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).Code; + } + + private static void GenerateFaultAddressPatchCode(EmitterContext context, Operand faultAddress, Operand newAddress) + { + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + if (SupportsFaultAddressPatchingForHostOs()) + { + Operand lblSkip = Label(); + + context.BranchIf(lblSkip, faultAddress, newAddress, Comparison.Equal); + + Operand ucontextPtr = context.LoadArgument(OperandType.I64, 2); + Operand pcCtxAddress = default; + ulong baseRegsOffset = 0; + + if (OperatingSystem.IsLinux()) + { + pcCtxAddress = context.Add(ucontextPtr, Const(440UL)); + baseRegsOffset = 184UL; + } + else if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) + { + ucontextPtr = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(48UL))); + + pcCtxAddress = context.Add(ucontextPtr, Const(272UL)); + baseRegsOffset = 16UL; + } + + Operand pc = context.Load(OperandType.I64, pcCtxAddress); + + Operand reg = GetAddressRegisterFromArm64Instruction(context, pc); + Operand reg64 = context.ZeroExtend32(OperandType.I64, reg); + Operand regCtxAddress = context.Add(ucontextPtr, context.Add(context.ShiftLeft(reg64, Const(3)), Const(baseRegsOffset))); + Operand regAddress = context.Load(OperandType.I64, regCtxAddress); + + Operand addressDelta = context.Subtract(regAddress, faultAddress); + + context.Store(regCtxAddress, context.Add(newAddress, addressDelta)); + + context.MarkLabel(lblSkip); + } + } + } + + private static Operand GetAddressRegisterFromArm64Instruction(EmitterContext context, Operand pc) + { + Operand inst = context.Load(OperandType.I32, pc); + Operand reg = context.AllocateLocal(OperandType.I32); + + Operand isSysInst = context.ICompareEqual(context.BitwiseAnd(inst, Const(0xFFF80000)), Const(0xD5080000)); + + Operand lblSys = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblSys, isSysInst, BasicBlockFrequency.Cold); + + context.Copy(reg, context.BitwiseAnd(context.ShiftRightUI(inst, Const(5)), Const(0x1F))); + context.Branch(lblEnd); + + context.MarkLabel(lblSys); + context.Copy(reg, context.BitwiseAnd(inst, Const(0x1F))); + + context.MarkLabel(lblEnd); + + return reg; + } + + public static bool SupportsFaultAddressPatchingForHost() + { + return SupportsFaultAddressPatchingForHostArch() && SupportsFaultAddressPatchingForHostOs(); + } + + private static bool SupportsFaultAddressPatchingForHostArch() + { + return RuntimeInformation.ProcessArchitecture == Architecture.Arm64; + } + + private static bool SupportsFaultAddressPatchingForHostOs() + { + return OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS(); + } + } +} diff --git a/src/ARMeilleure/Signal/TestMethods.cs b/src/ARMeilleure/Signal/TestMethods.cs new file mode 100644 index 00000000..0a8b3f5f --- /dev/null +++ b/src/ARMeilleure/Signal/TestMethods.cs @@ -0,0 +1,84 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Runtime.InteropServices; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Signal +{ + public struct NativeWriteLoopState + { + public int Running; + public int Error; + } + + public static class TestMethods + { + public delegate bool DebugPartialUnmap(); + public delegate int DebugThreadLocalMapGetOrReserve(int threadId, int initialState); + public delegate void DebugNativeWriteLoop(IntPtr nativeWriteLoopPtr, IntPtr writePtr); + + public static DebugPartialUnmap GenerateDebugPartialUnmap() + { + EmitterContext context = new(); + + var result = WindowsPartialUnmapHandler.EmitRetryFromAccessViolation(context); + + context.Return(result); + + // 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(); + } + + public static DebugThreadLocalMapGetOrReserve GenerateDebugThreadLocalMapGetOrReserve(IntPtr structPtr) + { + EmitterContext context = new(); + + var result = WindowsPartialUnmapHandler.EmitThreadLocalMapIntGetOrReserve(context, structPtr, context.LoadArgument(OperandType.I32, 0), context.LoadArgument(OperandType.I32, 1)); + + context.Return(result); + + // 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(); + } + + public static DebugNativeWriteLoop GenerateDebugNativeWriteLoop() + { + EmitterContext context = new(); + + // Loop a write to the target address until "running" is false. + + Operand structPtr = context.Copy(context.LoadArgument(OperandType.I64, 0)); + Operand writePtr = context.Copy(context.LoadArgument(OperandType.I64, 1)); + + Operand loopLabel = Label(); + context.MarkLabel(loopLabel); + + context.Store(writePtr, Const(12345)); + + Operand running = context.Load(OperandType.I32, structPtr); + + context.BranchIfTrue(loopLabel, running); + + context.Return(); + + // Compile and return the function. + + ControlFlowGraph cfg = context.GetControlFlowGraph(); + + OperandType[] argTypes = new OperandType[] { OperandType.I64 }; + + return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + } + } +} diff --git a/src/ARMeilleure/Signal/WindowsPartialUnmapHandler.cs b/src/ARMeilleure/Signal/WindowsPartialUnmapHandler.cs new file mode 100644 index 00000000..3bf6a449 --- /dev/null +++ b/src/ARMeilleure/Signal/WindowsPartialUnmapHandler.cs @@ -0,0 +1,197 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using Ryujinx.Common.Memory.PartialUnmaps; +using System; +using System.Runtime.InteropServices; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Signal +{ + /// + /// Methods to handle signals caused by partial unmaps. See the structs for C# implementations of the methods. + /// + internal static partial class WindowsPartialUnmapHandler + { + [LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "LoadLibraryA")] + private static partial IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName); + + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string procName); + + private static IntPtr _getCurrentThreadIdPtr; + + public static IntPtr GetCurrentThreadIdFunc() + { + if (_getCurrentThreadIdPtr == IntPtr.Zero) + { + IntPtr handle = LoadLibrary("kernel32.dll"); + + _getCurrentThreadIdPtr = GetProcAddress(handle, "GetCurrentThreadId"); + } + + return _getCurrentThreadIdPtr; + } + + public static Operand EmitRetryFromAccessViolation(EmitterContext context) + { + IntPtr partialRemapStatePtr = PartialUnmapState.GlobalState; + IntPtr localCountsPtr = IntPtr.Add(partialRemapStatePtr, PartialUnmapState.LocalCountsOffset); + + // Get the lock first. + EmitNativeReaderLockAcquire(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset)); + + IntPtr getCurrentThreadId = GetCurrentThreadIdFunc(); + Operand threadId = context.Call(Const((ulong)getCurrentThreadId), OperandType.I32); + Operand threadIndex = EmitThreadLocalMapIntGetOrReserve(context, localCountsPtr, threadId, Const(0)); + + Operand endLabel = Label(); + Operand retry = context.AllocateLocal(OperandType.I32); + Operand threadIndexValidLabel = Label(); + + context.BranchIfFalse(threadIndexValidLabel, context.ICompareEqual(threadIndex, Const(-1))); + + context.Copy(retry, Const(1)); // Always retry when thread local cannot be allocated. + + context.Branch(endLabel); + + context.MarkLabel(threadIndexValidLabel); + + Operand threadLocalPartialUnmapsPtr = EmitThreadLocalMapIntGetValuePtr(context, localCountsPtr, threadIndex); + Operand threadLocalPartialUnmaps = context.Load(OperandType.I32, threadLocalPartialUnmapsPtr); + Operand partialUnmapsCount = context.Load(OperandType.I32, Const((ulong)IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapsCountOffset))); + + context.Copy(retry, context.ICompareNotEqual(threadLocalPartialUnmaps, partialUnmapsCount)); + + Operand noRetryLabel = Label(); + + context.BranchIfFalse(noRetryLabel, retry); + + // if (retry) { + + context.Store(threadLocalPartialUnmapsPtr, partialUnmapsCount); + + context.Branch(endLabel); + + context.MarkLabel(noRetryLabel); + + // } + + context.MarkLabel(endLabel); + + // Finally, release the lock and return the retry value. + EmitNativeReaderLockRelease(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset)); + + return retry; + } + + public static Operand EmitThreadLocalMapIntGetOrReserve(EmitterContext context, IntPtr threadLocalMapPtr, Operand threadId, Operand initialState) + { + Operand idsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap.ThreadIdsOffset)); + + Operand i = context.AllocateLocal(OperandType.I32); + + context.Copy(i, Const(0)); + + // (Loop 1) Check all slots for a matching Thread ID (while also trying to allocate) + + Operand endLabel = Label(); + + Operand loopLabel = Label(); + context.MarkLabel(loopLabel); + + Operand offset = context.Multiply(i, Const(sizeof(int))); + Operand idPtr = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset)); + + // Check that this slot has the thread ID. + Operand existingId = context.CompareAndSwap(idPtr, threadId, threadId); + + // If it was already the thread ID, then we just need to return i. + context.BranchIfTrue(endLabel, context.ICompareEqual(existingId, threadId)); + + context.Copy(i, context.Add(i, Const(1))); + + context.BranchIfTrue(loopLabel, context.ICompareLess(i, Const(ThreadLocalMap.MapSize))); + + // (Loop 2) Try take a slot that is 0 with our Thread ID. + + context.Copy(i, Const(0)); // Reset i. + + Operand loop2Label = Label(); + context.MarkLabel(loop2Label); + + Operand offset2 = context.Multiply(i, Const(sizeof(int))); + Operand idPtr2 = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset2)); + + // Try and swap in the thread id on top of 0. + Operand existingId2 = context.CompareAndSwap(idPtr2, Const(0), threadId); + + Operand idNot0Label = Label(); + + // If it was 0, then we need to initialize the struct entry and return i. + context.BranchIfFalse(idNot0Label, context.ICompareEqual(existingId2, Const(0))); + + Operand structsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap.StructsOffset)); + Operand structPtr = context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset2)); + context.Store(structPtr, initialState); + + context.Branch(endLabel); + + context.MarkLabel(idNot0Label); + + context.Copy(i, context.Add(i, Const(1))); + + context.BranchIfTrue(loop2Label, context.ICompareLess(i, Const(ThreadLocalMap.MapSize))); + + context.Copy(i, Const(-1)); // Could not place the thread in the list. + + context.MarkLabel(endLabel); + + return context.Copy(i); + } + + private static Operand EmitThreadLocalMapIntGetValuePtr(EmitterContext context, IntPtr threadLocalMapPtr, Operand index) + { + Operand offset = context.Multiply(index, Const(sizeof(int))); + Operand structsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap.StructsOffset)); + + return context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset)); + } + + private static void EmitAtomicAddI32(EmitterContext context, Operand ptr, Operand additive) + { + Operand loop = Label(); + context.MarkLabel(loop); + + Operand initial = context.Load(OperandType.I32, ptr); + Operand newValue = context.Add(initial, additive); + + Operand replaced = context.CompareAndSwap(ptr, initial, newValue); + + context.BranchIfFalse(loop, context.ICompareEqual(initial, replaced)); + } + + private static void EmitNativeReaderLockAcquire(EmitterContext context, IntPtr nativeReaderLockPtr) + { + Operand writeLockPtr = Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.WriteLockOffset)); + + // Spin until we can acquire the write lock. + Operand spinLabel = Label(); + context.MarkLabel(spinLabel); + + // Old value must be 0 to continue (we gained the write lock) + context.BranchIfTrue(spinLabel, context.CompareAndSwap(writeLockPtr, Const(0), Const(1))); + + // Increment reader count. + EmitAtomicAddI32(context, Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.ReaderCountOffset)), Const(1)); + + // Release write lock. + context.CompareAndSwap(writeLockPtr, Const(1), Const(0)); + } + + private static void EmitNativeReaderLockRelease(EmitterContext context, IntPtr nativeReaderLockPtr) + { + // Decrement reader count. + EmitAtomicAddI32(context, Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.ReaderCountOffset)), Const(-1)); + } + } +} diff --git a/src/ARMeilleure/State/Aarch32Mode.cs b/src/ARMeilleure/State/Aarch32Mode.cs new file mode 100644 index 00000000..add1cd26 --- /dev/null +++ b/src/ARMeilleure/State/Aarch32Mode.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.State +{ + enum Aarch32Mode + { + User = 0b10000, + Fiq = 0b10001, + Irq = 0b10010, + Supervisor = 0b10011, + Monitor = 0b10110, + Abort = 0b10111, + Hypervisor = 0b11010, + Undefined = 0b11011, + System = 0b11111, + } +} diff --git a/src/ARMeilleure/State/ExceptionCallback.cs b/src/ARMeilleure/State/ExceptionCallback.cs new file mode 100644 index 00000000..2a4e9656 --- /dev/null +++ b/src/ARMeilleure/State/ExceptionCallback.cs @@ -0,0 +1,5 @@ +namespace ARMeilleure.State +{ + public delegate void ExceptionCallbackNoArgs(ExecutionContext context); + public delegate void ExceptionCallback(ExecutionContext context, ulong address, int id); +} diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs new file mode 100644 index 00000000..ce10a591 --- /dev/null +++ b/src/ARMeilleure/State/ExecutionContext.cs @@ -0,0 +1,175 @@ +using ARMeilleure.Memory; +using System; + +namespace ARMeilleure.State +{ + public class ExecutionContext + { + private const int MinCountForCheck = 4000; + + private readonly NativeContext _nativeContext; + + internal IntPtr NativeContextPtr => _nativeContext.BasePtr; + + private bool _interrupted; + + private readonly ICounter _counter; + + public ulong Pc => _nativeContext.GetPc(); + +#pragma warning disable CA1822 // Mark member as static + public uint CtrEl0 => 0x8444c004; + public uint DczidEl0 => 0x00000004; +#pragma warning restore CA1822 + + public ulong CntfrqEl0 => _counter.Frequency; + public ulong CntpctEl0 => _counter.Counter; + + // CNTVCT_EL0 = CNTPCT_EL0 - CNTVOFF_EL2 + // Since EL2 isn't implemented, CNTVOFF_EL2 = 0 + public ulong CntvctEl0 => CntpctEl0; + + public long TpidrEl0 + { + get => _nativeContext.GetTpidrEl0(); + set => _nativeContext.SetTpidrEl0(value); + } + + public long TpidrroEl0 + { + get => _nativeContext.GetTpidrroEl0(); + set => _nativeContext.SetTpidrroEl0(value); + } + + public uint Pstate + { + get => _nativeContext.GetPstate(); + set => _nativeContext.SetPstate(value); + } + + public FPSR Fpsr + { + get => (FPSR)_nativeContext.GetFPState((uint)FPSR.Mask); + set => _nativeContext.SetFPState((uint)value, (uint)FPSR.Mask); + } + + public FPCR Fpcr + { + get => (FPCR)_nativeContext.GetFPState((uint)FPCR.Mask); + set => _nativeContext.SetFPState((uint)value, (uint)FPCR.Mask); + } + public FPCR StandardFpcrValue => (Fpcr & (FPCR.Ahp)) | FPCR.Dn | FPCR.Fz; + + public FPSCR Fpscr + { + get => (FPSCR)_nativeContext.GetFPState((uint)FPSCR.Mask); + set => _nativeContext.SetFPState((uint)value, (uint)FPSCR.Mask); + } + + public bool IsAarch32 { get; set; } + + internal ExecutionMode ExecutionMode + { + get + { + if (IsAarch32) + { + return GetPstateFlag(PState.TFlag) + ? ExecutionMode.Aarch32Thumb + : ExecutionMode.Aarch32Arm; + } + else + { + return ExecutionMode.Aarch64; + } + } + } + + public bool Running + { + get => _nativeContext.GetRunning(); + private set => _nativeContext.SetRunning(value); + } + + private readonly ExceptionCallbackNoArgs _interruptCallback; + private readonly ExceptionCallback _breakCallback; + private readonly ExceptionCallback _supervisorCallback; + private readonly ExceptionCallback _undefinedCallback; + + public ExecutionContext( + IJitMemoryAllocator allocator, + ICounter counter, + ExceptionCallbackNoArgs interruptCallback = null, + ExceptionCallback breakCallback = null, + ExceptionCallback supervisorCallback = null, + ExceptionCallback undefinedCallback = null) + { + _nativeContext = new NativeContext(allocator); + _counter = counter; + _interruptCallback = interruptCallback; + _breakCallback = breakCallback; + _supervisorCallback = supervisorCallback; + _undefinedCallback = undefinedCallback; + + Running = true; + + _nativeContext.SetCounter(MinCountForCheck); + } + + public ulong GetX(int index) => _nativeContext.GetX(index); + public void SetX(int index, ulong value) => _nativeContext.SetX(index, value); + + public V128 GetV(int index) => _nativeContext.GetV(index); + public void SetV(int index, V128 value) => _nativeContext.SetV(index, value); + + public bool GetPstateFlag(PState flag) => _nativeContext.GetPstateFlag(flag); + public void SetPstateFlag(PState flag, bool value) => _nativeContext.SetPstateFlag(flag, value); + + public bool GetFPstateFlag(FPState flag) => _nativeContext.GetFPStateFlag(flag); + public void SetFPstateFlag(FPState flag, bool value) => _nativeContext.SetFPStateFlag(flag, value); + + internal void CheckInterrupt() + { + if (_interrupted) + { + _interrupted = false; + + _interruptCallback?.Invoke(this); + } + + _nativeContext.SetCounter(MinCountForCheck); + } + + public void RequestInterrupt() + { + _interrupted = true; + } + + internal void OnBreak(ulong address, int imm) + { + _breakCallback?.Invoke(this, address, imm); + } + + internal void OnSupervisorCall(ulong address, int imm) + { + _supervisorCallback?.Invoke(this, address, imm); + } + + internal void OnUndefined(ulong address, int opCode) + { + _undefinedCallback?.Invoke(this, address, opCode); + } + + public void StopRunning() + { + Running = false; + + _nativeContext.SetCounter(0); + } + + public void Dispose() + { + _nativeContext.Dispose(); + } + } +} diff --git a/src/ARMeilleure/State/ExecutionMode.cs b/src/ARMeilleure/State/ExecutionMode.cs new file mode 100644 index 00000000..e1fb722b --- /dev/null +++ b/src/ARMeilleure/State/ExecutionMode.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.State +{ + enum ExecutionMode + { + Aarch32Arm = 0, + Aarch32Thumb = 1, + Aarch64 = 2, + } +} diff --git a/src/ARMeilleure/State/FPCR.cs b/src/ARMeilleure/State/FPCR.cs new file mode 100644 index 00000000..427300ad --- /dev/null +++ b/src/ARMeilleure/State/FPCR.cs @@ -0,0 +1,22 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum FPCR : uint + { + Ioe = 1u << 8, + Dze = 1u << 9, + Ofe = 1u << 10, + Ufe = 1u << 11, + Ixe = 1u << 12, + Ide = 1u << 15, + RMode0 = 1u << 22, + RMode1 = 1u << 23, + Fz = 1u << 24, + Dn = 1u << 25, + Ahp = 1u << 26, + + Mask = Ahp | Dn | Fz | RMode1 | RMode0 | Ide | Ixe | Ufe | Ofe | Dze | Ioe, // 0x07C09F00u + } +} diff --git a/src/ARMeilleure/State/FPException.cs b/src/ARMeilleure/State/FPException.cs new file mode 100644 index 00000000..5b13659a --- /dev/null +++ b/src/ARMeilleure/State/FPException.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.State +{ + enum FPException + { + InvalidOp = 0, + DivideByZero = 1, + Overflow = 2, + Underflow = 3, + Inexact = 4, + InputDenorm = 7, + } +} diff --git a/src/ARMeilleure/State/FPRoundingMode.cs b/src/ARMeilleure/State/FPRoundingMode.cs new file mode 100644 index 00000000..0913175e --- /dev/null +++ b/src/ARMeilleure/State/FPRoundingMode.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.State +{ + public enum FPRoundingMode + { + ToNearest = 0, // With ties to even. + TowardsPlusInfinity = 1, + TowardsMinusInfinity = 2, + TowardsZero = 3, + ToNearestAway = 4, // With ties to away. + } +} diff --git a/src/ARMeilleure/State/FPSCR.cs b/src/ARMeilleure/State/FPSCR.cs new file mode 100644 index 00000000..65a060eb --- /dev/null +++ b/src/ARMeilleure/State/FPSCR.cs @@ -0,0 +1,15 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum FPSCR : uint + { + V = 1u << 28, + C = 1u << 29, + Z = 1u << 30, + N = 1u << 31, + + Mask = N | Z | C | V | FPSR.Mask | FPCR.Mask, // 0xFFC09F9Fu + } +} diff --git a/src/ARMeilleure/State/FPSR.cs b/src/ARMeilleure/State/FPSR.cs new file mode 100644 index 00000000..915b2fb3 --- /dev/null +++ b/src/ARMeilleure/State/FPSR.cs @@ -0,0 +1,18 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum FPSR : uint + { + Ioc = 1u << 0, + Dzc = 1u << 1, + Ofc = 1u << 2, + Ufc = 1u << 3, + Ixc = 1u << 4, + Idc = 1u << 7, + Qc = 1u << 27, + + Mask = Qc | Idc | Ixc | Ufc | Ofc | Dzc | Ioc, // 0x0800009Fu + } +} diff --git a/src/ARMeilleure/State/FPState.cs b/src/ARMeilleure/State/FPState.cs new file mode 100644 index 00000000..e76f4824 --- /dev/null +++ b/src/ARMeilleure/State/FPState.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.State +{ + public enum FPState + { + // FPSR Flags. + IocFlag = 0, + DzcFlag = 1, + OfcFlag = 2, + UfcFlag = 3, + IxcFlag = 4, + IdcFlag = 7, + QcFlag = 27, + VFlag = 28, + CFlag = 29, + ZFlag = 30, + NFlag = 31, + + // FPCR Flags. + IoeFlag = 8, + DzeFlag = 9, + OfeFlag = 10, + UfeFlag = 11, + IxeFlag = 12, + IdeFlag = 15, + RMode0Flag = 22, + RMode1Flag = 23, + FzFlag = 24, + DnFlag = 25, + AhpFlag = 26, + } +} diff --git a/src/ARMeilleure/State/FPType.cs b/src/ARMeilleure/State/FPType.cs new file mode 100644 index 00000000..367082ff --- /dev/null +++ b/src/ARMeilleure/State/FPType.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.State +{ + enum FPType + { + Nonzero, + Zero, + Infinity, + QNaN, + SNaN, + } +} diff --git a/src/ARMeilleure/State/ICounter.cs b/src/ARMeilleure/State/ICounter.cs new file mode 100644 index 00000000..7aa1cce7 --- /dev/null +++ b/src/ARMeilleure/State/ICounter.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.State +{ + /// + /// CPU Counter interface. + /// + public interface ICounter + { + /// + /// Counter frequency in Hertz. + /// + ulong Frequency { get; } + + /// + /// Current counter value. + /// + ulong Counter { get; } + } +} diff --git a/src/ARMeilleure/State/NativeContext.cs b/src/ARMeilleure/State/NativeContext.cs new file mode 100644 index 00000000..5403042e --- /dev/null +++ b/src/ARMeilleure/State/NativeContext.cs @@ -0,0 +1,269 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using System; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.State +{ + class NativeContext : IDisposable + { + private unsafe struct NativeCtxStorage + { + public fixed ulong X[RegisterConsts.IntRegsCount]; + 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; + public ulong ExclusiveValueLow; + public ulong ExclusiveValueHigh; + public int Running; + } + + private static NativeCtxStorage _dummyStorage = new(); + + private readonly IJitMemoryBlock _block; + + public IntPtr BasePtr => _block.Pointer; + + public NativeContext(IJitMemoryAllocator allocator) + { + _block = allocator.Allocate((ulong)Unsafe.SizeOf()); + + GetStorage().ExclusiveAddress = ulong.MaxValue; + } + + public ulong GetPc() + { + // TODO: More precise tracking of PC value. + return GetStorage().DispatchAddress; + } + + public unsafe ulong GetX(int index) + { + if ((uint)index >= RegisterConsts.IntRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return GetStorage().X[index]; + } + + public unsafe void SetX(int index, ulong value) + { + if ((uint)index >= RegisterConsts.IntRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + GetStorage().X[index] = value; + } + + public unsafe V128 GetV(int index) + { + if ((uint)index >= RegisterConsts.VecRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return new V128(GetStorage().V[index * 2 + 0], GetStorage().V[index * 2 + 1]); + } + + public unsafe void SetV(int index, V128 value) + { + if ((uint)index >= RegisterConsts.VecRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + GetStorage().V[index * 2 + 0] = value.Extract(0); + GetStorage().V[index * 2 + 1] = value.Extract(1); + } + + public unsafe bool GetPstateFlag(PState flag) + { + if ((uint)flag >= RegisterConsts.FlagsCount) + { + throw new ArgumentException($"Invalid flag \"{flag}\" specified."); + } + + return GetStorage().Flags[(int)flag] != 0; + } + + public unsafe void SetPstateFlag(PState flag, bool value) + { + if ((uint)flag >= RegisterConsts.FlagsCount) + { + throw new ArgumentException($"Invalid flag \"{flag}\" specified."); + } + + GetStorage().Flags[(int)flag] = value ? 1u : 0u; + } + + public unsafe uint GetPstate() + { + uint value = 0; + for (int flag = 0; flag < RegisterConsts.FlagsCount; flag++) + { + value |= GetStorage().Flags[flag] != 0 ? 1u << flag : 0u; + } + return value; + } + + public unsafe void SetPstate(uint value) + { + for (int flag = 0; flag < RegisterConsts.FlagsCount; flag++) + { + uint bit = 1u << flag; + GetStorage().Flags[flag] = (value & bit) == bit ? 1u : 0u; + } + } + + public unsafe bool GetFPStateFlag(FPState flag) + { + if ((uint)flag >= RegisterConsts.FpFlagsCount) + { + throw new ArgumentException($"Invalid flag \"{flag}\" specified."); + } + + return GetStorage().FpFlags[(int)flag] != 0; + } + + public unsafe void SetFPStateFlag(FPState flag, bool value) + { + if ((uint)flag >= RegisterConsts.FpFlagsCount) + { + throw new ArgumentException($"Invalid flag \"{flag}\" specified."); + } + + GetStorage().FpFlags[(int)flag] = value ? 1u : 0u; + } + + public unsafe uint GetFPState(uint mask = uint.MaxValue) + { + uint value = 0; + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + uint bit = 1u << flag; + + if ((mask & bit) == bit) + { + value |= GetStorage().FpFlags[flag] != 0 ? bit : 0u; + } + } + return value; + } + + public unsafe void SetFPState(uint value, uint mask = uint.MaxValue) + { + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + uint bit = 1u << flag; + + if ((mask & bit) == bit) + { + GetStorage().FpFlags[flag] = (value & bit) == bit ? 1u : 0u; + } + } + } + + 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; + + public bool GetRunning() => GetStorage().Running != 0; + public void SetRunning(bool value) => GetStorage().Running = value ? 1 : 0; + + public unsafe static int GetRegisterOffset(Register reg) + { + if (reg.Type == RegisterType.Integer) + { + if ((uint)reg.Index >= RegisterConsts.IntRegsCount) + { + throw new ArgumentException("Invalid register."); + } + + return StorageOffset(ref _dummyStorage, ref _dummyStorage.X[reg.Index]); + } + else if (reg.Type == RegisterType.Vector) + { + if ((uint)reg.Index >= RegisterConsts.VecRegsCount) + { + throw new ArgumentException("Invalid register."); + } + + return StorageOffset(ref _dummyStorage, ref _dummyStorage.V[reg.Index * 2]); + } + else if (reg.Type == RegisterType.Flag) + { + if ((uint)reg.Index >= RegisterConsts.FlagsCount) + { + throw new ArgumentException("Invalid register."); + } + + return StorageOffset(ref _dummyStorage, ref _dummyStorage.Flags[reg.Index]); + } + else /* if (reg.Type == RegisterType.FpFlag) */ + { + if ((uint)reg.Index >= RegisterConsts.FpFlagsCount) + { + throw new ArgumentException("Invalid register."); + } + + return StorageOffset(ref _dummyStorage, ref _dummyStorage.FpFlags[reg.Index]); + } + } + + 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); + } + + public static int GetDispatchAddressOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.DispatchAddress); + } + + public static int GetExclusiveAddressOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.ExclusiveAddress); + } + + public static int GetExclusiveValueOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.ExclusiveValueLow); + } + + public static int GetRunningOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.Running); + } + + private static int StorageOffset(ref NativeCtxStorage storage, ref T target) + { + return (int)Unsafe.ByteOffset(ref Unsafe.As(ref storage), ref target); + } + + private unsafe ref NativeCtxStorage GetStorage() => ref Unsafe.AsRef((void*)_block.Pointer); + + public void Dispose() => _block.Dispose(); + } +} diff --git a/src/ARMeilleure/State/PState.cs b/src/ARMeilleure/State/PState.cs new file mode 100644 index 00000000..d4ddc865 --- /dev/null +++ b/src/ARMeilleure/State/PState.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.State +{ + public enum PState + { + TFlag = 5, + EFlag = 9, + GE0Flag = 16, + GE1Flag = 17, + GE2Flag = 18, + GE3Flag = 19, + QFlag = 27, + VFlag = 28, + CFlag = 29, + ZFlag = 30, + NFlag = 31, + } +} diff --git a/src/ARMeilleure/State/RegisterAlias.cs b/src/ARMeilleure/State/RegisterAlias.cs new file mode 100644 index 00000000..a9574089 --- /dev/null +++ b/src/ARMeilleure/State/RegisterAlias.cs @@ -0,0 +1,42 @@ +namespace ARMeilleure.State +{ + static class RegisterAlias + { + public const int R8Usr = 8; + public const int R9Usr = 9; + public const int R10Usr = 10; + public const int R11Usr = 11; + public const int R12Usr = 12; + public const int SpUsr = 13; + public const int LrUsr = 14; + + public const int SpHyp = 15; + + public const int LrIrq = 16; + public const int SpIrq = 17; + + public const int LrSvc = 18; + public const int SpSvc = 19; + + public const int LrAbt = 20; + public const int SpAbt = 21; + + public const int LrUnd = 22; + public const int SpUnd = 23; + + public const int R8Fiq = 24; + public const int R9Fiq = 25; + public const int R10Fiq = 26; + public const int R11Fiq = 27; + public const int R12Fiq = 28; + public const int SpFiq = 29; + public const int LrFiq = 30; + + public const int Aarch32Sp = 13; + public const int Aarch32Lr = 14; + public const int Aarch32Pc = 15; + + public const int Lr = 30; + public const int Zr = 31; + } +} diff --git a/src/ARMeilleure/State/RegisterConsts.cs b/src/ARMeilleure/State/RegisterConsts.cs new file mode 100644 index 00000000..b43f8d64 --- /dev/null +++ b/src/ARMeilleure/State/RegisterConsts.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.State +{ + static class RegisterConsts + { + public const int IntRegsCount = 32; + public const int VecRegsCount = 32; + public const int FlagsCount = 32; + public const int FpFlagsCount = 32; + public const int IntAndVecRegsCount = IntRegsCount + VecRegsCount; + public const int FpFlagsOffset = IntRegsCount + VecRegsCount + FlagsCount; + public const int TotalCount = IntRegsCount + VecRegsCount + FlagsCount + FpFlagsCount; + + public const int ZeroIndex = 31; + } +} diff --git a/src/ARMeilleure/State/V128.cs b/src/ARMeilleure/State/V128.cs new file mode 100644 index 00000000..cbcaddfc --- /dev/null +++ b/src/ARMeilleure/State/V128.cs @@ -0,0 +1,316 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ARMeilleure.State +{ + /// + /// Represents a 128-bit vector. + /// + [StructLayout(LayoutKind.Sequential, Size = 16)] + public struct V128 : IEquatable + { + // _e0 & _e1 could be marked as readonly, however they are not readonly because we modify them through the Unsafe + // APIs. This also means that one should be careful when changing the layout of this struct. + + private readonly ulong _e0; + private readonly ulong _e1; + + /// + /// Gets a new with all bits set to zero. + /// + public static V128 Zero => new(0, 0); + + /// + /// Initializes a new instance of the struct with the specified value + /// as a scalar. + /// + /// Scalar value + public V128(double value) : this(value, 0) { } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + public V128(double e0, double e1) + { + _e0 = (ulong)BitConverter.DoubleToInt64Bits(e0); + _e1 = (ulong)BitConverter.DoubleToInt64Bits(e1); + } + + /// + /// Initializes a new instance of the struct with the specified value as a + /// scalar. + /// + /// Scalar value + public V128(float value) : this(value, 0, 0, 0) { } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + /// Element 2 + /// Element 3 + public V128(float e0, float e1, float e2, float e3) + { + _e0 = (ulong)(uint)BitConverter.SingleToInt32Bits(e0) << 0; + _e0 |= (ulong)(uint)BitConverter.SingleToInt32Bits(e1) << 32; + _e1 = (ulong)(uint)BitConverter.SingleToInt32Bits(e2) << 0; + _e1 |= (ulong)(uint)BitConverter.SingleToInt32Bits(e3) << 32; + } + + /// + /// Initializes a new instance of the struct with the specified + /// elements. + /// + /// Element 0 + /// Element 1 + public V128(long e0, long e1) : this((ulong)e0, (ulong)e1) { } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + public V128(ulong e0, ulong e1) + { + _e0 = e0; + _e1 = e1; + } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + /// Element 2 + /// Element 3 + public V128(int e0, int e1, int e2, int e3) : this((uint)e0, (uint)e1, (uint)e2, (uint)e3) { } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + /// Element 2 + /// Element 3 + public V128(uint e0, uint e1, uint e2, uint e3) + { + _e0 = (ulong)e0 << 0; + _e0 |= (ulong)e1 << 32; + _e1 = (ulong)e2 << 0; + _e1 |= (ulong)e3 << 32; + } + + /// + /// Initializes a new instance of the struct from the specified array. + /// + /// array to use + public V128(byte[] data) + { + _e0 = (ulong)BitConverter.ToInt64(data, 0); + _e1 = (ulong)BitConverter.ToInt64(data, 8); + } + + /// + /// Returns the value of the as a scalar. + /// + /// Type of scalar + /// Value of the as a scalar + /// Size of is larger than 16 bytes + public T As() where T : unmanaged + { + return Extract(0); + } + + /// + /// Extracts the element at the specified index as a from the . + /// + /// Element type + /// Index of element + /// Element at the specified index as a from the + /// + /// is out of bound or the size of is larger than 16 bytes + /// + public T Extract(int index) where T : unmanaged + { + if ((uint)index >= GetElementCount()) + { + ThrowIndexOutOfRange(); + } + + // Performs: + // return *((*T)this + index); + return Unsafe.Add(ref Unsafe.As(ref this), index); + } + + /// + /// Inserts the specified value into the element at the specified index in the . + /// + /// Element type + /// Index of element + /// Value to insert + /// + /// is out of bound or the size of is larger than 16 bytes + /// + public void Insert(int index, T value) where T : unmanaged + { + if ((uint)index >= GetElementCount()) + { + ThrowIndexOutOfRange(); + } + + // Performs: + // *((*T)this + index) = value; + Unsafe.Add(ref Unsafe.As(ref this), index) = value; + } + + /// + /// Returns a new array which represents the . + /// + /// A new array which represents the + public readonly byte[] ToArray() + { + byte[] data = new byte[16]; + Span span = data; + + BitConverter.TryWriteBytes(span, _e0); + BitConverter.TryWriteBytes(span[8..], _e1); + + return data; + } + + /// + /// Performs a bitwise logical left shift on the specified by the specified shift count. + /// + /// instance + /// Number of shifts + /// Result of left shift + /// + /// This supports shift counts up to 63; anything above may result in unexpected behaviour. + /// + public static V128 operator <<(V128 x, int shift) + { + if (shift == 0) + { + return new V128(x._e0, x._e1); + } + + ulong shiftOut = x._e0 >> (64 - shift); + + return new V128(x._e0 << shift, (x._e1 << shift) | shiftOut); + } + + /// + /// Performs a bitwise logical right shift on the specified by the specified shift count. + /// + /// instance + /// Number of shifts + /// Result of right shift + /// + /// This supports shift counts up to 63; anything above may result in unexpected behaviour. + /// + public static V128 operator >>(V128 x, int shift) + { + if (shift == 0) + { + return new V128(x._e0, x._e1); + } + + ulong shiftOut = x._e1 & ((1UL << shift) - 1); + + return new V128((x._e0 >> shift) | (shiftOut << (64 - shift)), x._e1 >> shift); + } + + /// + /// Performs a bitwise not on the specified . + /// + /// Target + /// Result of not operation + public static V128 operator ~(V128 x) => new(~x._e0, ~x._e1); + + /// + /// Performs a bitwise and on the specified instances. + /// + /// First instance + /// Second instance + /// Result of and operation + public static V128 operator &(V128 x, V128 y) => new(x._e0 & y._e0, x._e1 & y._e1); + + /// + /// Performs a bitwise or on the specified instances. + /// + /// First instance + /// Second instance + /// Result of or operation + public static V128 operator |(V128 x, V128 y) => new(x._e0 | y._e0, x._e1 | y._e1); + + /// + /// Performs a bitwise exlusive or on the specified instances. + /// + /// First instance + /// Second instance + /// Result of exclusive or operation + public static V128 operator ^(V128 x, V128 y) => new(x._e0 ^ y._e0, x._e1 ^ y._e1); + + /// + /// Determines if the specified instances are equal. + /// + /// First instance + /// Second instance + /// true if equal; otherwise false + public static bool operator ==(V128 x, V128 y) => x.Equals(y); + + /// + /// Determines if the specified instances are not equal. + /// + /// First instance + /// Second instance + /// true if not equal; otherwise false + public static bool operator !=(V128 x, V128 y) => !x.Equals(y); + + /// + /// Determines if the specified is equal to this instance. + /// + /// Other instance + /// true if equal; otherwise false + public readonly bool Equals(V128 other) + { + return other._e0 == _e0 && other._e1 == _e1; + } + + /// + /// Determines if the specified is equal to this instance. + /// + /// Other instance + /// true if equal; otherwise false + public readonly override bool Equals(object obj) + { + return obj is V128 vector && Equals(vector); + } + + /// + public readonly override int GetHashCode() + { + return HashCode.Combine(_e0, _e1); + } + + /// + public readonly override string ToString() + { + return $"0x{_e1:X16}{_e0:X16}"; + } + + private static uint GetElementCount() where T : unmanaged + { + return (uint)(Unsafe.SizeOf() / Unsafe.SizeOf()); + } + + private static void ThrowIndexOutOfRange() + { + throw new ArgumentOutOfRangeException("index"); + } + } +} diff --git a/src/ARMeilleure/Statistics.cs b/src/ARMeilleure/Statistics.cs new file mode 100644 index 00000000..2f873bcf --- /dev/null +++ b/src/ARMeilleure/Statistics.cs @@ -0,0 +1,96 @@ +#if M_PROFILE +using System; +#endif +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace ARMeilleure +{ + public static class Statistics + { + private const int ReportMaxFunctions = 100; + +#if M_PROFILE + [ThreadStatic] + private static Stopwatch _executionTimer; +#endif + + private static readonly ConcurrentDictionary _ticksPerFunction; + + static Statistics() + { + _ticksPerFunction = new ConcurrentDictionary(); + } + + public static void InitializeTimer() + { +#if M_PROFILE + if (_executionTimer == null) + { + _executionTimer = new Stopwatch(); + } +#endif + } + + internal static void StartTimer() + { +#if M_PROFILE + _executionTimer.Restart(); +#endif + } + + internal static void StopTimer(ulong funcAddr) + { +#if M_PROFILE + _executionTimer.Stop(); + + long ticks = _executionTimer.ElapsedTicks; + + TicksPerFunction.AddOrUpdate(funcAddr, ticks, (key, oldTicks) => oldTicks + ticks); +#endif + } + + internal static void ResumeTimer() + { +#if M_PROFILE + _executionTimer.Start(); +#endif + } + + internal static void PauseTimer() + { +#if M_PROFILE + _executionTimer.Stop(); +#endif + } + + public static string GetReport() + { + int count = 0; + + StringBuilder sb = new(); + + sb.AppendLine(" Function address | Time"); + sb.AppendLine("--------------------------"); + + KeyValuePair[] funcTable = _ticksPerFunction.ToArray(); + + foreach (KeyValuePair kv in funcTable.OrderByDescending(x => x.Value)) + { + long timeInMs = (kv.Value * 1000) / Stopwatch.Frequency; + + sb.AppendLine($" 0x{kv.Key:X16} | {timeInMs} ms"); + + if (count++ >= ReportMaxFunctions) + { + break; + } + } + + return sb.ToString(); + } + } +} diff --git a/src/ARMeilleure/Translation/ArmEmitterContext.cs b/src/ARMeilleure/Translation/ArmEmitterContext.cs new file mode 100644 index 00000000..e2407473 --- /dev/null +++ b/src/ARMeilleure/Translation/ArmEmitterContext.cs @@ -0,0 +1,286 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.Common; +using ARMeilleure.Decoders; +using ARMeilleure.Diagnostics; +using ARMeilleure.Instructions; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Reflection; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + class ArmEmitterContext : EmitterContext + { + private readonly Dictionary _labels; + + private OpCode _optOpLastCompare; + private OpCode _optOpLastFlagSet; + + private Operand _optCmpTempN; + private Operand _optCmpTempM; + + private Block _currBlock; + + public Block CurrBlock + { + get + { + return _currBlock; + } + set + { + _currBlock = value; + + ResetBlockState(); + } + } + + private bool _pendingQcFlagSync; + + public OpCode CurrOp { get; set; } + + public IMemoryManager Memory { get; } + + public EntryTable CountTable { get; } + public AddressTable FunctionTable { get; } + public TranslatorStubs Stubs { get; } + + public ulong EntryAddress { get; } + public bool HighCq { get; } + public bool HasPtc { get; } + public Aarch32Mode Mode { get; } + + private int _ifThenBlockStateIndex = 0; + private Condition[] _ifThenBlockState = Array.Empty(); + public bool IsInIfThenBlock => _ifThenBlockStateIndex < _ifThenBlockState.Length; + public Condition CurrentIfThenBlockCond => _ifThenBlockState[_ifThenBlockStateIndex]; + + public ArmEmitterContext( + IMemoryManager memory, + EntryTable countTable, + AddressTable funcTable, + TranslatorStubs stubs, + ulong entryAddress, + bool highCq, + bool hasPtc, + Aarch32Mode mode) + { + Memory = memory; + CountTable = countTable; + FunctionTable = funcTable; + Stubs = stubs; + EntryAddress = entryAddress; + HighCq = highCq; + HasPtc = hasPtc; + Mode = mode; + + _labels = new Dictionary(); + } + + public override Operand Call(MethodInfo info, params Operand[] callArgs) + { + SyncQcFlag(); + + if (!HasPtc) + { + return base.Call(info, callArgs); + } + else + { + int index = Delegates.GetDelegateIndex(info); + IntPtr funcPtr = Delegates.GetDelegateFuncPtrByIndex(index); + + OperandType returnType = GetOperandType(info.ReturnType); + + Symbol symbol = new(SymbolType.DelegateTable, (ulong)index); + + Symbols.Add((ulong)funcPtr.ToInt64(), info.Name); + + return Call(Const(funcPtr.ToInt64(), symbol), returnType, callArgs); + } + } + + public Operand GetLabel(ulong address) + { + if (!_labels.TryGetValue(address, out Operand label)) + { + label = Label(); + + _labels.Add(address, label); + } + + return label; + } + + public void MarkComparison(Operand n, Operand m) + { + _optOpLastCompare = CurrOp; + + _optCmpTempN = Copy(n); + _optCmpTempM = Copy(m); + } + + public void MarkFlagSet(PState stateFlag) + { + // Set this only if any of the NZCV flag bits were modified. + // This is used to ensure that when emiting a direct IL branch + // instruction for compare + branch sequences, we're not expecting + // to use comparison values from an old instruction, when in fact + // the flags were already overwritten by another instruction further along. + if (stateFlag >= PState.VFlag) + { + _optOpLastFlagSet = CurrOp; + } + } + + private void ResetBlockState() + { + _optOpLastCompare = null; + _optOpLastFlagSet = null; + } + + public void SetPendingQcFlagSync() + { + _pendingQcFlagSync = true; + } + + public void SyncQcFlag() + { + if (_pendingQcFlagSync) + { + if (Optimizations.UseAdvSimd) + { + Operand fpsr = AddIntrinsicInt(Intrinsic.Arm64MrsFpsr); + + uint qcFlagMask = (uint)FPSR.Qc; + + Operand qcClearLabel = Label(); + + BranchIfFalse(qcClearLabel, BitwiseAnd(fpsr, Const(qcFlagMask))); + + AddIntrinsicNoRet(Intrinsic.Arm64MsrFpsr, Const(0)); + InstEmitHelper.SetFpFlag(this, FPState.QcFlag, Const(1)); + + MarkLabel(qcClearLabel); + } + + _pendingQcFlagSync = false; + } + } + + public void ClearQcFlag() + { + if (Optimizations.UseAdvSimd) + { + AddIntrinsicNoRet(Intrinsic.Arm64MsrFpsr, Const(0)); + } + } + + public void ClearQcFlagIfModified() + { + if (_pendingQcFlagSync && Optimizations.UseAdvSimd) + { + AddIntrinsicNoRet(Intrinsic.Arm64MsrFpsr, Const(0)); + } + } + + 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) + { + return default; + } + + Operand n = _optCmpTempN; + Operand m = _optCmpTempM; + + InstName cmpName = _optOpLastCompare.Instruction.Name; + + if (cmpName == InstName.Subs) + { + switch (condition) + { +#pragma warning disable IDE0055 // Disable formatting + case Condition.Eq: return ICompareEqual (n, m); + case Condition.Ne: return ICompareNotEqual (n, m); + case Condition.GeUn: return ICompareGreaterOrEqualUI(n, m); + case Condition.LtUn: return ICompareLessUI (n, m); + case Condition.GtUn: return ICompareGreaterUI (n, m); + case Condition.LeUn: return ICompareLessOrEqualUI (n, m); + case Condition.Ge: return ICompareGreaterOrEqual (n, m); + case Condition.Lt: return ICompareLess (n, m); + case Condition.Gt: return ICompareGreater (n, m); + case Condition.Le: return ICompareLessOrEqual (n, m); +#pragma warning restore IDE0055 + } + } + else if (cmpName == InstName.Adds && _optOpLastCompare is IOpCodeAluImm op) + { + // There are several limitations that needs to be taken into account for CMN comparisons: + // - The unsigned comparisons are not valid, as they depend on the + // carry flag value, and they will have different values for addition and + // subtraction. For addition, it's carry, and for subtraction, it's borrow. + // So, we need to make sure we're not doing a unsigned compare for the CMN case. + // - We can only do the optimization for the immediate variants, + // because when the second operand value is exactly INT_MIN, we can't + // negate the value as theres no positive counterpart. + // Such invalid values can't be encoded on the immediate encodings. + if (op.RegisterSize == RegisterSize.Int32) + { + m = Const((int)-op.Immediate); + } + else + { + m = Const(-op.Immediate); + } + + switch (condition) + { +#pragma warning disable IDE0055 // Disable formatting + case Condition.Eq: return ICompareEqual (n, m); + case Condition.Ne: return ICompareNotEqual (n, m); + case Condition.Ge: return ICompareGreaterOrEqual(n, m); + case Condition.Lt: return ICompareLess (n, m); + case Condition.Gt: return ICompareGreater (n, m); + case Condition.Le: return ICompareLessOrEqual (n, m); +#pragma warning restore IDE0055 + } + } + + return default; + } + + public void SetIfThenBlockState(Condition[] state) + { + _ifThenBlockState = state; + _ifThenBlockStateIndex = 0; + } + + public void AdvanceIfThenBlockState() + { + if (IsInIfThenBlock) + { + _ifThenBlockStateIndex++; + } + } + } +} diff --git a/src/ARMeilleure/Translation/Cache/CacheEntry.cs b/src/ARMeilleure/Translation/Cache/CacheEntry.cs new file mode 100644 index 00000000..25b06f78 --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/CacheEntry.cs @@ -0,0 +1,26 @@ +using ARMeilleure.CodeGen.Unwinding; +using System; +using System.Diagnostics.CodeAnalysis; + +namespace ARMeilleure.Translation.Cache +{ + readonly struct CacheEntry : IComparable + { + public int Offset { get; } + public int Size { get; } + + public UnwindInfo UnwindInfo { get; } + + public CacheEntry(int offset, int size, UnwindInfo unwindInfo) + { + Offset = offset; + Size = size; + UnwindInfo = unwindInfo; + } + + public int CompareTo([AllowNull] CacheEntry other) + { + return Offset.CompareTo(other.Offset); + } + } +} diff --git a/src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs b/src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs new file mode 100644 index 00000000..f36bf7a3 --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace ARMeilleure.Translation.Cache +{ + class CacheMemoryAllocator + { + private readonly struct MemoryBlock : IComparable + { + public int Offset { get; } + public int Size { get; } + + public MemoryBlock(int offset, int size) + { + Offset = offset; + Size = size; + } + + public int CompareTo([AllowNull] MemoryBlock other) + { + return Offset.CompareTo(other.Offset); + } + } + + private readonly List _blocks = new(); + + public CacheMemoryAllocator(int capacity) + { + _blocks.Add(new MemoryBlock(0, capacity)); + } + + public int Allocate(int size) + { + for (int i = 0; i < _blocks.Count; i++) + { + MemoryBlock block = _blocks[i]; + + if (block.Size > size) + { + _blocks[i] = new MemoryBlock(block.Offset + size, block.Size - size); + return block.Offset; + } + else if (block.Size == size) + { + _blocks.RemoveAt(i); + return block.Offset; + } + } + + // We don't have enough free memory to perform the allocation. + return -1; + } + + public void Free(int offset, int size) + { + Insert(new MemoryBlock(offset, size)); + } + + private void Insert(MemoryBlock block) + { + int index = _blocks.BinarySearch(block); + + if (index < 0) + { + index = ~index; + } + + if (index < _blocks.Count) + { + MemoryBlock next = _blocks[index]; + + int endOffs = block.Offset + block.Size; + + if (next.Offset == endOffs) + { + block = new MemoryBlock(block.Offset, block.Size + next.Size); + _blocks.RemoveAt(index); + } + } + + if (index > 0) + { + MemoryBlock prev = _blocks[index - 1]; + + if (prev.Offset + prev.Size == block.Offset) + { + block = new MemoryBlock(block.Offset - prev.Size, block.Size + prev.Size); + _blocks.RemoveAt(--index); + } + } + + _blocks.Insert(index, block); + } + } +} diff --git a/src/ARMeilleure/Translation/Cache/JitCache.cs b/src/ARMeilleure/Translation/Cache/JitCache.cs new file mode 100644 index 00000000..e2b5e2d1 --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/JitCache.cs @@ -0,0 +1,207 @@ +using ARMeilleure.CodeGen; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Memory; +using ARMeilleure.Native; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace ARMeilleure.Translation.Cache +{ + static partial class JitCache + { + private static readonly int _pageSize = (int)MemoryBlock.GetPageSize(); + private static readonly int _pageMask = _pageSize - 1; + + private const int CodeAlignment = 4; // Bytes. + private const int CacheSize = 2047 * 1024 * 1024; + + private static ReservedRegion _jitRegion; + private static JitCacheInvalidation _jitCacheInvalidator; + + private static CacheMemoryAllocator _cacheAllocator; + + private static readonly List _cacheEntries = new(); + + private static readonly object _lock = new(); + private static bool _initialized; + + [SupportedOSPlatform("windows")] + [LibraryImport("kernel32.dll", SetLastError = true)] + public static partial IntPtr FlushInstructionCache(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize); + + public static void Initialize(IJitMemoryAllocator allocator) + { + if (_initialized) + { + return; + } + + lock (_lock) + { + if (_initialized) + { + return; + } + + _jitRegion = new ReservedRegion(allocator, CacheSize); + + if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS()) + { + _jitCacheInvalidator = new JitCacheInvalidation(allocator); + } + + _cacheAllocator = new CacheMemoryAllocator(CacheSize); + + if (OperatingSystem.IsWindows()) + { + JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize)); + } + + _initialized = true; + } + } + + public static IntPtr Map(CompiledFunction func) + { + byte[] code = func.Code; + + lock (_lock) + { + Debug.Assert(_initialized); + + int funcOffset = Allocate(code.Length); + + IntPtr funcPtr = _jitRegion.Pointer + funcOffset; + + if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + unsafe + { + fixed (byte* codePtr = code) + { + JitSupportDarwin.Copy(funcPtr, (IntPtr)codePtr, (ulong)code.Length); + } + } + } + else + { + ReprotectAsWritable(funcOffset, code.Length); + Marshal.Copy(code, 0, funcPtr, code.Length); + ReprotectAsExecutable(funcOffset, code.Length); + + if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (UIntPtr)code.Length); + } + else + { + _jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length); + } + } + + Add(funcOffset, code.Length, func.UnwindInfo); + + return funcPtr; + } + } + + public static void Unmap(IntPtr pointer) + { + lock (_lock) + { + Debug.Assert(_initialized); + + int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64()); + + if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) + { + _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); + _cacheEntries.RemoveAt(entryIndex); + } + } + } + + private static void ReprotectAsWritable(int offset, int size) + { + int endOffs = offset + size; + + int regionStart = offset & ~_pageMask; + int regionEnd = (endOffs + _pageMask) & ~_pageMask; + + _jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart)); + } + + private static void ReprotectAsExecutable(int offset, int size) + { + int endOffs = offset + size; + + int regionStart = offset & ~_pageMask; + int regionEnd = (endOffs + _pageMask) & ~_pageMask; + + _jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart)); + } + + private static int Allocate(int codeSize) + { + codeSize = AlignCodeSize(codeSize); + + int allocOffset = _cacheAllocator.Allocate(codeSize); + + if (allocOffset < 0) + { + throw new OutOfMemoryException("JIT Cache exhausted."); + } + + _jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); + + return allocOffset; + } + + private static int AlignCodeSize(int codeSize) + { + return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1); + } + + private static void Add(int offset, int size, UnwindInfo unwindInfo) + { + CacheEntry entry = new(offset, size, unwindInfo); + + int index = _cacheEntries.BinarySearch(entry); + + if (index < 0) + { + index = ~index; + } + + _cacheEntries.Insert(index, entry); + } + + public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex) + { + lock (_lock) + { + int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default)); + + if (index < 0) + { + index = ~index - 1; + } + + if (index >= 0) + { + entry = _cacheEntries[index]; + entryIndex = index; + return true; + } + } + + entry = default; + entryIndex = 0; + return false; + } + } +} diff --git a/src/ARMeilleure/Translation/Cache/JitCacheInvalidation.cs b/src/ARMeilleure/Translation/Cache/JitCacheInvalidation.cs new file mode 100644 index 00000000..3aa2e19f --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/JitCacheInvalidation.cs @@ -0,0 +1,79 @@ +using ARMeilleure.Memory; +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation.Cache +{ + class JitCacheInvalidation + { + private static readonly int[] _invalidationCode = new int[] + { + unchecked((int)0xd53b0022), // mrs x2, ctr_el0 + unchecked((int)0xd3504c44), // ubfx x4, x2, #16, #4 + unchecked((int)0x52800083), // mov w3, #0x4 + unchecked((int)0x12000c45), // and w5, w2, #0xf + unchecked((int)0x1ac42064), // lsl w4, w3, w4 + unchecked((int)0x51000482), // sub w2, w4, #0x1 + unchecked((int)0x8a220002), // bic x2, x0, x2 + unchecked((int)0x1ac52063), // lsl w3, w3, w5 + unchecked((int)0xeb01005f), // cmp x2, x1 + unchecked((int)0x93407c84), // sxtw x4, w4 + unchecked((int)0x540000a2), // b.cs 3c + unchecked((int)0xd50b7b22), // dc cvau, x2 + unchecked((int)0x8b040042), // add x2, x2, x4 + unchecked((int)0xeb02003f), // cmp x1, x2 + unchecked((int)0x54ffffa8), // b.hi 2c + unchecked((int)0xd5033b9f), // dsb ish + unchecked((int)0x51000462), // sub w2, w3, #0x1 + unchecked((int)0x93407c63), // sxtw x3, w3 + unchecked((int)0x8a220000), // bic x0, x0, x2 + unchecked((int)0xeb00003f), // cmp x1, x0 + unchecked((int)0x540000a9), // b.ls 64 + unchecked((int)0xd50b7520), // ic ivau, x0 + unchecked((int)0x8b030000), // add x0, x0, x3 + unchecked((int)0xeb00003f), // cmp x1, x0 + unchecked((int)0x54ffffa8), // b.hi 54 + unchecked((int)0xd5033b9f), // dsb ish + unchecked((int)0xd5033fdf), // isb + unchecked((int)0xd65f03c0), // ret + }; + + private delegate void InvalidateCache(ulong start, ulong end); + + private readonly InvalidateCache _invalidateCache; + private readonly ReservedRegion _invalidateCacheCodeRegion; + + private readonly bool _needsInvalidation; + + public JitCacheInvalidation(IJitMemoryAllocator allocator) + { + // On macOS and Windows, a different path is used to write to the JIT cache, which does the invalidation. + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + ulong size = (ulong)_invalidationCode.Length * sizeof(int); + ulong mask = (ulong)ReservedRegion.DefaultGranularity - 1; + + size = (size + mask) & ~mask; + + _invalidateCacheCodeRegion = new ReservedRegion(allocator, size); + _invalidateCacheCodeRegion.ExpandIfNeeded(size); + + Marshal.Copy(_invalidationCode, 0, _invalidateCacheCodeRegion.Pointer, _invalidationCode.Length); + + _invalidateCacheCodeRegion.Block.MapAsRx(0, size); + + _invalidateCache = Marshal.GetDelegateForFunctionPointer(_invalidateCacheCodeRegion.Pointer); + + _needsInvalidation = true; + } + } + + public void Invalidate(IntPtr basePointer, ulong size) + { + if (_needsInvalidation) + { + _invalidateCache((ulong)basePointer, (ulong)basePointer + size); + } + } + } +} diff --git a/src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs b/src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs new file mode 100644 index 00000000..3957a755 --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs @@ -0,0 +1,190 @@ +// https://github.com/MicrosoftDocs/cpp-docs/blob/master/docs/build/exception-handling-x64.md + +using ARMeilleure.CodeGen.Unwinding; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation.Cache +{ + static partial class JitUnwindWindows + { + private const int MaxUnwindCodesArraySize = 32; // Must be an even value. + + private struct RuntimeFunction + { + public uint BeginAddress; + public uint EndAddress; + public uint UnwindData; + } + + private struct UnwindInfo + { + public byte VersionAndFlags; + public byte SizeOfProlog; + public byte CountOfUnwindCodes; + public byte FrameRegister; + public unsafe fixed ushort UnwindCodes[MaxUnwindCodesArraySize]; + } + + private enum UnwindOp + { + PushNonvol = 0, + AllocLarge = 1, + AllocSmall = 2, + SetFpreg = 3, + SaveNonvol = 4, + SaveNonvolFar = 5, + SaveXmm128 = 8, + SaveXmm128Far = 9, + PushMachframe = 10, + } + + private unsafe delegate RuntimeFunction* GetRuntimeFunctionCallback(ulong controlPc, IntPtr context); + + [LibraryImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static unsafe partial bool RtlInstallFunctionTableCallback( + ulong tableIdentifier, + ulong baseAddress, + uint length, + GetRuntimeFunctionCallback callback, + IntPtr context, + [MarshalAs(UnmanagedType.LPWStr)] string outOfProcessCallbackDll); + + private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback; + + private static int _sizeOfRuntimeFunction; + + private unsafe static RuntimeFunction* _runtimeFunction; + + private unsafe static UnwindInfo* _unwindInfo; + + public static void InstallFunctionTableHandler(IntPtr codeCachePointer, uint codeCacheLength, IntPtr workBufferPtr) + { + ulong codeCachePtr = (ulong)codeCachePointer.ToInt64(); + + _sizeOfRuntimeFunction = Marshal.SizeOf(); + + bool result; + + unsafe + { + _runtimeFunction = (RuntimeFunction*)workBufferPtr; + + _unwindInfo = (UnwindInfo*)(workBufferPtr + _sizeOfRuntimeFunction); + + _getRuntimeFunctionCallback = new GetRuntimeFunctionCallback(FunctionTableHandler); + + result = RtlInstallFunctionTableCallback( + codeCachePtr | 3, + codeCachePtr, + codeCacheLength, + _getRuntimeFunctionCallback, + codeCachePointer, + null); + } + + if (!result) + { + throw new InvalidOperationException("Failure installing function table callback."); + } + } + + private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, IntPtr context) + { + int offset = (int)((long)controlPc - context.ToInt64()); + + if (!JitCache.TryFind(offset, out CacheEntry funcEntry, out _)) + { + return null; // Not found. + } + + var unwindInfo = funcEntry.UnwindInfo; + + int codeIndex = 0; + + for (int index = unwindInfo.PushEntries.Length - 1; index >= 0; index--) + { + var entry = unwindInfo.PushEntries[index]; + + switch (entry.PseudoOp) + { + case UnwindPseudoOp.SaveXmm128: + { + int stackOffset = entry.StackOffsetOrAllocSize; + + Debug.Assert(stackOffset % 16 == 0); + + if (stackOffset <= 0xFFFF0) + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128, entry.PrologOffset, entry.RegIndex); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset / 16); + } + else + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128Far, entry.PrologOffset, entry.RegIndex); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 0); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 16); + } + + break; + } + + case UnwindPseudoOp.AllocStack: + { + int allocSize = entry.StackOffsetOrAllocSize; + + Debug.Assert(allocSize % 8 == 0); + + if (allocSize <= 128) + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocSmall, entry.PrologOffset, (allocSize / 8) - 1); + } + else if (allocSize <= 0x7FFF8) + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 0); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize / 8); + } + else + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 1); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 0); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 16); + } + + break; + } + + case UnwindPseudoOp.PushReg: + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.PushNonvol, entry.PrologOffset, entry.RegIndex); + + break; + } + + default: + throw new NotImplementedException($"({nameof(entry.PseudoOp)} = {entry.PseudoOp})"); + } + } + + Debug.Assert(codeIndex <= MaxUnwindCodesArraySize); + + _unwindInfo->VersionAndFlags = 1; // Flags: The function has no handler. + _unwindInfo->SizeOfProlog = (byte)unwindInfo.PrologSize; + _unwindInfo->CountOfUnwindCodes = (byte)codeIndex; + _unwindInfo->FrameRegister = 0; + + _runtimeFunction->BeginAddress = (uint)funcEntry.Offset; + _runtimeFunction->EndAddress = (uint)(funcEntry.Offset + funcEntry.Size); + _runtimeFunction->UnwindData = (uint)_sizeOfRuntimeFunction; + + return _runtimeFunction; + } + + private static ushort PackUnwindOp(UnwindOp op, int prologOffset, int opInfo) + { + return (ushort)(prologOffset | ((int)op << 8) | (opInfo << 12)); + } + } +} diff --git a/src/ARMeilleure/Translation/Compiler.cs b/src/ARMeilleure/Translation/Compiler.cs new file mode 100644 index 00000000..293e6349 --- /dev/null +++ b/src/ARMeilleure/Translation/Compiler.cs @@ -0,0 +1,68 @@ +using ARMeilleure.CodeGen; +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.Diagnostics; +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation +{ + static class Compiler + { + public static CompiledFunction Compile( + ControlFlowGraph cfg, + OperandType[] argTypes, + OperandType retType, + CompilerOptions options, + Architecture target) + { + CompilerContext cctx = new(cfg, argTypes, retType, options); + + if (options.HasFlag(CompilerOptions.Optimize)) + { + Logger.StartPass(PassName.TailMerge); + + TailMerge.RunPass(cctx); + + Logger.EndPass(PassName.TailMerge, cfg); + } + + if (options.HasFlag(CompilerOptions.SsaForm)) + { + Logger.StartPass(PassName.Dominance); + + Dominance.FindDominators(cfg); + Dominance.FindDominanceFrontiers(cfg); + + Logger.EndPass(PassName.Dominance); + + Logger.StartPass(PassName.SsaConstruction); + + Ssa.Construct(cfg); + + Logger.EndPass(PassName.SsaConstruction, cfg); + } + else + { + Logger.StartPass(PassName.RegisterToLocal); + + RegisterToLocal.Rename(cfg); + + Logger.EndPass(PassName.RegisterToLocal, cfg); + } + + if (target == Architecture.X64) + { + return CodeGen.X86.CodeGenerator.Generate(cctx); + } + else if (target == Architecture.Arm64) + { + return CodeGen.Arm64.CodeGenerator.Generate(cctx); + } + else + { + throw new NotImplementedException(target.ToString()); + } + } + } +} diff --git a/src/ARMeilleure/Translation/CompilerContext.cs b/src/ARMeilleure/Translation/CompilerContext.cs new file mode 100644 index 00000000..5b10686b --- /dev/null +++ b/src/ARMeilleure/Translation/CompilerContext.cs @@ -0,0 +1,26 @@ +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.Translation +{ + readonly struct CompilerContext + { + public ControlFlowGraph Cfg { get; } + + public OperandType[] FuncArgTypes { get; } + public OperandType FuncReturnType { get; } + + public CompilerOptions Options { get; } + + public CompilerContext( + ControlFlowGraph cfg, + OperandType[] funcArgTypes, + OperandType funcReturnType, + CompilerOptions options) + { + Cfg = cfg; + FuncArgTypes = funcArgTypes; + FuncReturnType = funcReturnType; + Options = options; + } + } +} diff --git a/src/ARMeilleure/Translation/CompilerOptions.cs b/src/ARMeilleure/Translation/CompilerOptions.cs new file mode 100644 index 00000000..d454de7f --- /dev/null +++ b/src/ARMeilleure/Translation/CompilerOptions.cs @@ -0,0 +1,17 @@ +using System; + +namespace ARMeilleure.Translation +{ + [Flags] + enum CompilerOptions + { + None = 0, + SsaForm = 1 << 0, + Optimize = 1 << 1, + Lsra = 1 << 2, + Relocatable = 1 << 3, + + MediumCq = SsaForm | Optimize, + HighCq = SsaForm | Optimize | Lsra, + } +} diff --git a/src/ARMeilleure/Translation/ControlFlowGraph.cs b/src/ARMeilleure/Translation/ControlFlowGraph.cs new file mode 100644 index 00000000..45b092ec --- /dev/null +++ b/src/ARMeilleure/Translation/ControlFlowGraph.cs @@ -0,0 +1,164 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ARMeilleure.Translation +{ + class ControlFlowGraph + { + private BasicBlock[] _postOrderBlocks; + private int[] _postOrderMap; + + public int LocalsCount { get; private set; } + public BasicBlock Entry { get; private set; } + public IntrusiveList Blocks { get; } + public BasicBlock[] PostOrderBlocks => _postOrderBlocks; + public int[] PostOrderMap => _postOrderMap; + + public ControlFlowGraph(BasicBlock entry, IntrusiveList blocks, int localsCount) + { + Entry = entry; + Blocks = blocks; + LocalsCount = localsCount; + + Update(); + } + + public Operand AllocateLocal(OperandType type) + { + Operand result = Operand.Factory.Local(type); + + result.NumberLocal(++LocalsCount); + + return result; + } + + public void UpdateEntry(BasicBlock newEntry) + { + newEntry.AddSuccessor(Entry); + + Entry = newEntry; + Blocks.AddFirst(newEntry); + Update(); + } + + public void Update() + { + RemoveUnreachableBlocks(Blocks); + + var visited = new HashSet(); + var blockStack = new Stack(); + + Array.Resize(ref _postOrderBlocks, Blocks.Count); + Array.Resize(ref _postOrderMap, Blocks.Count); + + visited.Add(Entry); + blockStack.Push(Entry); + + int index = 0; + + while (blockStack.TryPop(out BasicBlock block)) + { + bool visitedNew = false; + + for (int i = 0; i < block.SuccessorsCount; i++) + { + BasicBlock succ = block.GetSuccessor(i); + + if (visited.Add(succ)) + { + blockStack.Push(block); + blockStack.Push(succ); + + visitedNew = true; + + break; + } + } + + if (!visitedNew) + { + PostOrderMap[block.Index] = index; + + PostOrderBlocks[index++] = block; + } + } + } + + private void RemoveUnreachableBlocks(IntrusiveList blocks) + { + var visited = new HashSet(); + var workQueue = new Queue(); + + visited.Add(Entry); + workQueue.Enqueue(Entry); + + while (workQueue.TryDequeue(out BasicBlock block)) + { + Debug.Assert(block.Index != -1, "Invalid block index."); + + for (int i = 0; i < block.SuccessorsCount; i++) + { + BasicBlock succ = block.GetSuccessor(i); + + if (visited.Add(succ)) + { + workQueue.Enqueue(succ); + } + } + } + + if (visited.Count < blocks.Count) + { + // Remove unreachable blocks and renumber. + int index = 0; + + for (BasicBlock block = blocks.First; block != null;) + { + BasicBlock nextBlock = block.ListNext; + + if (!visited.Contains(block)) + { + while (block.SuccessorsCount > 0) + { + block.RemoveSuccessor(index: block.SuccessorsCount - 1); + } + + blocks.Remove(block); + } + else + { + block.Index = index++; + } + + block = nextBlock; + } + } + } + + public BasicBlock SplitEdge(BasicBlock predecessor, BasicBlock successor) + { + BasicBlock splitBlock = new(Blocks.Count); + + for (int i = 0; i < predecessor.SuccessorsCount; i++) + { + if (predecessor.GetSuccessor(i) == successor) + { + predecessor.SetSuccessor(i, splitBlock); + } + } + + if (splitBlock.Predecessors.Count == 0) + { + throw new ArgumentException("Predecessor and successor are not connected."); + } + + splitBlock.AddSuccessor(successor); + + Blocks.AddBefore(successor, splitBlock); + + return splitBlock; + } + } +} diff --git a/src/ARMeilleure/Translation/DelegateInfo.cs b/src/ARMeilleure/Translation/DelegateInfo.cs new file mode 100644 index 00000000..70662543 --- /dev/null +++ b/src/ARMeilleure/Translation/DelegateInfo.cs @@ -0,0 +1,19 @@ +using System; + +namespace ARMeilleure.Translation +{ + class DelegateInfo + { +#pragma warning disable IDE0052 // Remove unread private member + private readonly Delegate _dlg; // Ensure that this delegate will not be garbage collected. +#pragma warning restore IDE0052 + + public IntPtr FuncPtr { get; } + + public DelegateInfo(Delegate dlg, IntPtr funcPtr) + { + _dlg = dlg; + FuncPtr = funcPtr; + } + } +} diff --git a/src/ARMeilleure/Translation/Delegates.cs b/src/ARMeilleure/Translation/Delegates.cs new file mode 100644 index 00000000..66412b8e --- /dev/null +++ b/src/ARMeilleure/Translation/Delegates.cs @@ -0,0 +1,610 @@ +using ARMeilleure.Instructions; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation +{ + static class Delegates + { + public static bool TryGetDelegateFuncPtrByIndex(int index, out IntPtr funcPtr) + { + if (index >= 0 && index < _delegates.Count) + { + funcPtr = _delegates.Values[index].FuncPtr; // O(1). + + return true; + } + else + { + funcPtr = default; + + return false; + } + } + + public static IntPtr GetDelegateFuncPtrByIndex(int index) + { + if (index < 0 || index >= _delegates.Count) + { + throw new ArgumentOutOfRangeException($"({nameof(index)} = {index})"); + } + + return _delegates.Values[index].FuncPtr; // O(1). + } + + public static IntPtr GetDelegateFuncPtr(MethodInfo info) + { + ArgumentNullException.ThrowIfNull(info); + + string key = GetKey(info); + + if (!_delegates.TryGetValue(key, out DelegateInfo dlgInfo)) // O(log(n)). + { + throw new KeyNotFoundException($"({nameof(key)} = {key})"); + } + + return dlgInfo.FuncPtr; + } + + public static int GetDelegateIndex(MethodInfo info) + { + ArgumentNullException.ThrowIfNull(info); + + string key = GetKey(info); + + int index = _delegates.IndexOfKey(key); // O(log(n)). + + if (index == -1) + { + throw new KeyNotFoundException($"({nameof(key)} = {key})"); + } + + return index; + } + + private static void SetDelegateInfo(Delegate dlg, IntPtr funcPtr) + { + string key = GetKey(dlg.Method); + + _delegates.Add(key, new DelegateInfo(dlg, funcPtr)); // ArgumentException (key). + } + + private static string GetKey(MethodInfo info) + { + return $"{info.DeclaringType.Name}.{info.Name}"; + } + + private static readonly SortedList _delegates; + + static Delegates() + { + _delegates = new SortedList(); + + var dlgMathAbs = new MathAbs(Math.Abs); + var dlgMathCeiling = new MathCeiling(Math.Ceiling); + var dlgMathFloor = new MathFloor(Math.Floor); + var dlgMathRound = new MathRound(Math.Round); + var dlgMathTruncate = new MathTruncate(Math.Truncate); + + var dlgMathFAbs = new MathFAbs(MathF.Abs); + var dlgMathFCeiling = new MathFCeiling(MathF.Ceiling); + var dlgMathFFloor = new MathFFloor(MathF.Floor); + var dlgMathFRound = new MathFRound(MathF.Round); + var dlgMathFTruncate = new MathFTruncate(MathF.Truncate); + + var dlgNativeInterfaceBreak = new NativeInterfaceBreak(NativeInterface.Break); + var dlgNativeInterfaceCheckSynchronization = new NativeInterfaceCheckSynchronization(NativeInterface.CheckSynchronization); + var dlgNativeInterfaceEnqueueForRejit = new NativeInterfaceEnqueueForRejit(NativeInterface.EnqueueForRejit); + var dlgNativeInterfaceGetCntfrqEl0 = new NativeInterfaceGetCntfrqEl0(NativeInterface.GetCntfrqEl0); + var dlgNativeInterfaceGetCntpctEl0 = new NativeInterfaceGetCntpctEl0(NativeInterface.GetCntpctEl0); + var dlgNativeInterfaceGetCntvctEl0 = new NativeInterfaceGetCntvctEl0(NativeInterface.GetCntvctEl0); + var dlgNativeInterfaceGetCtrEl0 = new NativeInterfaceGetCtrEl0(NativeInterface.GetCtrEl0); + var dlgNativeInterfaceGetDczidEl0 = new NativeInterfaceGetDczidEl0(NativeInterface.GetDczidEl0); + var dlgNativeInterfaceGetFunctionAddress = new NativeInterfaceGetFunctionAddress(NativeInterface.GetFunctionAddress); + var dlgNativeInterfaceInvalidateCacheLine = new NativeInterfaceInvalidateCacheLine(NativeInterface.InvalidateCacheLine); + var dlgNativeInterfaceReadByte = new NativeInterfaceReadByte(NativeInterface.ReadByte); + var dlgNativeInterfaceReadUInt16 = new NativeInterfaceReadUInt16(NativeInterface.ReadUInt16); + var dlgNativeInterfaceReadUInt32 = new NativeInterfaceReadUInt32(NativeInterface.ReadUInt32); + var dlgNativeInterfaceReadUInt64 = new NativeInterfaceReadUInt64(NativeInterface.ReadUInt64); + var dlgNativeInterfaceReadVector128 = new NativeInterfaceReadVector128(NativeInterface.ReadVector128); + var dlgNativeInterfaceSignalMemoryTracking = new NativeInterfaceSignalMemoryTracking(NativeInterface.SignalMemoryTracking); + var dlgNativeInterfaceSupervisorCall = new NativeInterfaceSupervisorCall(NativeInterface.SupervisorCall); + var dlgNativeInterfaceThrowInvalidMemoryAccess = new NativeInterfaceThrowInvalidMemoryAccess(NativeInterface.ThrowInvalidMemoryAccess); + var dlgNativeInterfaceUndefined = new NativeInterfaceUndefined(NativeInterface.Undefined); + var dlgNativeInterfaceWriteByte = new NativeInterfaceWriteByte(NativeInterface.WriteByte); + var dlgNativeInterfaceWriteUInt16 = new NativeInterfaceWriteUInt16(NativeInterface.WriteUInt16); + var dlgNativeInterfaceWriteUInt32 = new NativeInterfaceWriteUInt32(NativeInterface.WriteUInt32); + var dlgNativeInterfaceWriteUInt64 = new NativeInterfaceWriteUInt64(NativeInterface.WriteUInt64); + var dlgNativeInterfaceWriteVector128 = new NativeInterfaceWriteVector128(NativeInterface.WriteVector128); + + var dlgSoftFallbackCountLeadingSigns = new SoftFallbackCountLeadingSigns(SoftFallback.CountLeadingSigns); + var dlgSoftFallbackCountLeadingZeros = new SoftFallbackCountLeadingZeros(SoftFallback.CountLeadingZeros); + var dlgSoftFallbackCrc32b = new SoftFallbackCrc32b(SoftFallback.Crc32b); + var dlgSoftFallbackCrc32cb = new SoftFallbackCrc32cb(SoftFallback.Crc32cb); + var dlgSoftFallbackCrc32ch = new SoftFallbackCrc32ch(SoftFallback.Crc32ch); + var dlgSoftFallbackCrc32cw = new SoftFallbackCrc32cw(SoftFallback.Crc32cw); + var dlgSoftFallbackCrc32cx = new SoftFallbackCrc32cx(SoftFallback.Crc32cx); + var dlgSoftFallbackCrc32h = new SoftFallbackCrc32h(SoftFallback.Crc32h); + var dlgSoftFallbackCrc32w = new SoftFallbackCrc32w(SoftFallback.Crc32w); + var dlgSoftFallbackCrc32x = new SoftFallbackCrc32x(SoftFallback.Crc32x); + var dlgSoftFallbackDecrypt = new SoftFallbackDecrypt(SoftFallback.Decrypt); + var dlgSoftFallbackEncrypt = new SoftFallbackEncrypt(SoftFallback.Encrypt); + var dlgSoftFallbackFixedRotate = new SoftFallbackFixedRotate(SoftFallback.FixedRotate); + var dlgSoftFallbackHashChoose = new SoftFallbackHashChoose(SoftFallback.HashChoose); + var dlgSoftFallbackHashLower = new SoftFallbackHashLower(SoftFallback.HashLower); + var dlgSoftFallbackHashMajority = new SoftFallbackHashMajority(SoftFallback.HashMajority); + var dlgSoftFallbackHashParity = new SoftFallbackHashParity(SoftFallback.HashParity); + var dlgSoftFallbackHashUpper = new SoftFallbackHashUpper(SoftFallback.HashUpper); + var dlgSoftFallbackInverseMixColumns = new SoftFallbackInverseMixColumns(SoftFallback.InverseMixColumns); + var dlgSoftFallbackMixColumns = new SoftFallbackMixColumns(SoftFallback.MixColumns); + var dlgSoftFallbackPolynomialMult64_128 = new SoftFallbackPolynomialMult64_128(SoftFallback.PolynomialMult64_128); + var dlgSoftFallbackSatF32ToS32 = new SoftFallbackSatF32ToS32(SoftFallback.SatF32ToS32); + var dlgSoftFallbackSatF32ToS64 = new SoftFallbackSatF32ToS64(SoftFallback.SatF32ToS64); + var dlgSoftFallbackSatF32ToU32 = new SoftFallbackSatF32ToU32(SoftFallback.SatF32ToU32); + var dlgSoftFallbackSatF32ToU64 = new SoftFallbackSatF32ToU64(SoftFallback.SatF32ToU64); + var dlgSoftFallbackSatF64ToS32 = new SoftFallbackSatF64ToS32(SoftFallback.SatF64ToS32); + var dlgSoftFallbackSatF64ToS64 = new SoftFallbackSatF64ToS64(SoftFallback.SatF64ToS64); + var dlgSoftFallbackSatF64ToU32 = new SoftFallbackSatF64ToU32(SoftFallback.SatF64ToU32); + var dlgSoftFallbackSatF64ToU64 = new SoftFallbackSatF64ToU64(SoftFallback.SatF64ToU64); + var dlgSoftFallbackSha1SchedulePart1 = new SoftFallbackSha1SchedulePart1(SoftFallback.Sha1SchedulePart1); + var dlgSoftFallbackSha1SchedulePart2 = new SoftFallbackSha1SchedulePart2(SoftFallback.Sha1SchedulePart2); + var dlgSoftFallbackSha256SchedulePart1 = new SoftFallbackSha256SchedulePart1(SoftFallback.Sha256SchedulePart1); + var dlgSoftFallbackSha256SchedulePart2 = new SoftFallbackSha256SchedulePart2(SoftFallback.Sha256SchedulePart2); + var dlgSoftFallbackSignedShrImm64 = new SoftFallbackSignedShrImm64(SoftFallback.SignedShrImm64); + var dlgSoftFallbackTbl1 = new SoftFallbackTbl1(SoftFallback.Tbl1); + var dlgSoftFallbackTbl2 = new SoftFallbackTbl2(SoftFallback.Tbl2); + var dlgSoftFallbackTbl3 = new SoftFallbackTbl3(SoftFallback.Tbl3); + var dlgSoftFallbackTbl4 = new SoftFallbackTbl4(SoftFallback.Tbl4); + var dlgSoftFallbackTbx1 = new SoftFallbackTbx1(SoftFallback.Tbx1); + var dlgSoftFallbackTbx2 = new SoftFallbackTbx2(SoftFallback.Tbx2); + var dlgSoftFallbackTbx3 = new SoftFallbackTbx3(SoftFallback.Tbx3); + var dlgSoftFallbackTbx4 = new SoftFallbackTbx4(SoftFallback.Tbx4); + var dlgSoftFallbackUnsignedShrImm64 = new SoftFallbackUnsignedShrImm64(SoftFallback.UnsignedShrImm64); + + var dlgSoftFloat16_32FPConvert = new SoftFloat16_32FPConvert(SoftFloat16_32.FPConvert); + var dlgSoftFloat16_64FPConvert = new SoftFloat16_64FPConvert(SoftFloat16_64.FPConvert); + + var dlgSoftFloat32FPAdd = new SoftFloat32FPAdd(SoftFloat32.FPAdd); + var dlgSoftFloat32FPAddFpscr = new SoftFloat32FPAddFpscr(SoftFloat32.FPAddFpscr); // A32 only. + var dlgSoftFloat32FPCompare = new SoftFloat32FPCompare(SoftFloat32.FPCompare); + var dlgSoftFloat32FPCompareEQ = new SoftFloat32FPCompareEQ(SoftFloat32.FPCompareEQ); + var dlgSoftFloat32FPCompareEQFpscr = new SoftFloat32FPCompareEQFpscr(SoftFloat32.FPCompareEQFpscr); // A32 only. + var dlgSoftFloat32FPCompareGE = new SoftFloat32FPCompareGE(SoftFloat32.FPCompareGE); + var dlgSoftFloat32FPCompareGEFpscr = new SoftFloat32FPCompareGEFpscr(SoftFloat32.FPCompareGEFpscr); // A32 only. + var dlgSoftFloat32FPCompareGT = new SoftFloat32FPCompareGT(SoftFloat32.FPCompareGT); + var dlgSoftFloat32FPCompareGTFpscr = new SoftFloat32FPCompareGTFpscr(SoftFloat32.FPCompareGTFpscr); // A32 only. + var dlgSoftFloat32FPCompareLE = new SoftFloat32FPCompareLE(SoftFloat32.FPCompareLE); + var dlgSoftFloat32FPCompareLEFpscr = new SoftFloat32FPCompareLEFpscr(SoftFloat32.FPCompareLEFpscr); // A32 only. + var dlgSoftFloat32FPCompareLT = new SoftFloat32FPCompareLT(SoftFloat32.FPCompareLT); + var dlgSoftFloat32FPCompareLTFpscr = new SoftFloat32FPCompareLTFpscr(SoftFloat32.FPCompareLTFpscr); // A32 only. + var dlgSoftFloat32FPDiv = new SoftFloat32FPDiv(SoftFloat32.FPDiv); + var dlgSoftFloat32FPMax = new SoftFloat32FPMax(SoftFloat32.FPMax); + var dlgSoftFloat32FPMaxFpscr = new SoftFloat32FPMaxFpscr(SoftFloat32.FPMaxFpscr); // A32 only. + var dlgSoftFloat32FPMaxNum = new SoftFloat32FPMaxNum(SoftFloat32.FPMaxNum); + var dlgSoftFloat32FPMaxNumFpscr = new SoftFloat32FPMaxNumFpscr(SoftFloat32.FPMaxNumFpscr); // A32 only. + var dlgSoftFloat32FPMin = new SoftFloat32FPMin(SoftFloat32.FPMin); + var dlgSoftFloat32FPMinFpscr = new SoftFloat32FPMinFpscr(SoftFloat32.FPMinFpscr); // A32 only. + var dlgSoftFloat32FPMinNum = new SoftFloat32FPMinNum(SoftFloat32.FPMinNum); + var dlgSoftFloat32FPMinNumFpscr = new SoftFloat32FPMinNumFpscr(SoftFloat32.FPMinNumFpscr); // A32 only. + var dlgSoftFloat32FPMul = new SoftFloat32FPMul(SoftFloat32.FPMul); + var dlgSoftFloat32FPMulFpscr = new SoftFloat32FPMulFpscr(SoftFloat32.FPMulFpscr); // A32 only. + var dlgSoftFloat32FPMulAdd = new SoftFloat32FPMulAdd(SoftFloat32.FPMulAdd); + var dlgSoftFloat32FPMulAddFpscr = new SoftFloat32FPMulAddFpscr(SoftFloat32.FPMulAddFpscr); // A32 only. + var dlgSoftFloat32FPMulSub = new SoftFloat32FPMulSub(SoftFloat32.FPMulSub); + var dlgSoftFloat32FPMulSubFpscr = new SoftFloat32FPMulSubFpscr(SoftFloat32.FPMulSubFpscr); // A32 only. + var dlgSoftFloat32FPMulX = new SoftFloat32FPMulX(SoftFloat32.FPMulX); + var dlgSoftFloat32FPNegMulAdd = new SoftFloat32FPNegMulAdd(SoftFloat32.FPNegMulAdd); + var dlgSoftFloat32FPNegMulSub = new SoftFloat32FPNegMulSub(SoftFloat32.FPNegMulSub); + var dlgSoftFloat32FPRecipEstimate = new SoftFloat32FPRecipEstimate(SoftFloat32.FPRecipEstimate); + var dlgSoftFloat32FPRecipEstimateFpscr = new SoftFloat32FPRecipEstimateFpscr(SoftFloat32.FPRecipEstimateFpscr); // A32 only. + var dlgSoftFloat32FPRecipStep = new SoftFloat32FPRecipStep(SoftFloat32.FPRecipStep); // A32 only. + var dlgSoftFloat32FPRecipStepFused = new SoftFloat32FPRecipStepFused(SoftFloat32.FPRecipStepFused); + var dlgSoftFloat32FPRecpX = new SoftFloat32FPRecpX(SoftFloat32.FPRecpX); + var dlgSoftFloat32FPRSqrtEstimate = new SoftFloat32FPRSqrtEstimate(SoftFloat32.FPRSqrtEstimate); + var dlgSoftFloat32FPRSqrtEstimateFpscr = new SoftFloat32FPRSqrtEstimateFpscr(SoftFloat32.FPRSqrtEstimateFpscr); // A32 only. + var dlgSoftFloat32FPRSqrtStep = new SoftFloat32FPRSqrtStep(SoftFloat32.FPRSqrtStep); // A32 only. + var dlgSoftFloat32FPRSqrtStepFused = new SoftFloat32FPRSqrtStepFused(SoftFloat32.FPRSqrtStepFused); + var dlgSoftFloat32FPSqrt = new SoftFloat32FPSqrt(SoftFloat32.FPSqrt); + var dlgSoftFloat32FPSub = new SoftFloat32FPSub(SoftFloat32.FPSub); + + var dlgSoftFloat32_16FPConvert = new SoftFloat32_16FPConvert(SoftFloat32_16.FPConvert); + + var dlgSoftFloat64FPAdd = new SoftFloat64FPAdd(SoftFloat64.FPAdd); + var dlgSoftFloat64FPAddFpscr = new SoftFloat64FPAddFpscr(SoftFloat64.FPAddFpscr); // A32 only. + var dlgSoftFloat64FPCompare = new SoftFloat64FPCompare(SoftFloat64.FPCompare); + var dlgSoftFloat64FPCompareEQ = new SoftFloat64FPCompareEQ(SoftFloat64.FPCompareEQ); + var dlgSoftFloat64FPCompareEQFpscr = new SoftFloat64FPCompareEQFpscr(SoftFloat64.FPCompareEQFpscr); // A32 only. + var dlgSoftFloat64FPCompareGE = new SoftFloat64FPCompareGE(SoftFloat64.FPCompareGE); + var dlgSoftFloat64FPCompareGEFpscr = new SoftFloat64FPCompareGEFpscr(SoftFloat64.FPCompareGEFpscr); // A32 only. + var dlgSoftFloat64FPCompareGT = new SoftFloat64FPCompareGT(SoftFloat64.FPCompareGT); + var dlgSoftFloat64FPCompareGTFpscr = new SoftFloat64FPCompareGTFpscr(SoftFloat64.FPCompareGTFpscr); // A32 only. + var dlgSoftFloat64FPCompareLE = new SoftFloat64FPCompareLE(SoftFloat64.FPCompareLE); + var dlgSoftFloat64FPCompareLEFpscr = new SoftFloat64FPCompareLEFpscr(SoftFloat64.FPCompareLEFpscr); // A32 only. + var dlgSoftFloat64FPCompareLT = new SoftFloat64FPCompareLT(SoftFloat64.FPCompareLT); + var dlgSoftFloat64FPCompareLTFpscr = new SoftFloat64FPCompareLTFpscr(SoftFloat64.FPCompareLTFpscr); // A32 only. + var dlgSoftFloat64FPDiv = new SoftFloat64FPDiv(SoftFloat64.FPDiv); + var dlgSoftFloat64FPMax = new SoftFloat64FPMax(SoftFloat64.FPMax); + var dlgSoftFloat64FPMaxFpscr = new SoftFloat64FPMaxFpscr(SoftFloat64.FPMaxFpscr); // A32 only. + var dlgSoftFloat64FPMaxNum = new SoftFloat64FPMaxNum(SoftFloat64.FPMaxNum); + var dlgSoftFloat64FPMaxNumFpscr = new SoftFloat64FPMaxNumFpscr(SoftFloat64.FPMaxNumFpscr); // A32 only. + var dlgSoftFloat64FPMin = new SoftFloat64FPMin(SoftFloat64.FPMin); + var dlgSoftFloat64FPMinFpscr = new SoftFloat64FPMinFpscr(SoftFloat64.FPMinFpscr); // A32 only. + var dlgSoftFloat64FPMinNum = new SoftFloat64FPMinNum(SoftFloat64.FPMinNum); + var dlgSoftFloat64FPMinNumFpscr = new SoftFloat64FPMinNumFpscr(SoftFloat64.FPMinNumFpscr); // A32 only. + var dlgSoftFloat64FPMul = new SoftFloat64FPMul(SoftFloat64.FPMul); + var dlgSoftFloat64FPMulFpscr = new SoftFloat64FPMulFpscr(SoftFloat64.FPMulFpscr); // A32 only. + var dlgSoftFloat64FPMulAdd = new SoftFloat64FPMulAdd(SoftFloat64.FPMulAdd); + var dlgSoftFloat64FPMulAddFpscr = new SoftFloat64FPMulAddFpscr(SoftFloat64.FPMulAddFpscr); // A32 only. + var dlgSoftFloat64FPMulSub = new SoftFloat64FPMulSub(SoftFloat64.FPMulSub); + var dlgSoftFloat64FPMulSubFpscr = new SoftFloat64FPMulSubFpscr(SoftFloat64.FPMulSubFpscr); // A32 only. + var dlgSoftFloat64FPMulX = new SoftFloat64FPMulX(SoftFloat64.FPMulX); + var dlgSoftFloat64FPNegMulAdd = new SoftFloat64FPNegMulAdd(SoftFloat64.FPNegMulAdd); + var dlgSoftFloat64FPNegMulSub = new SoftFloat64FPNegMulSub(SoftFloat64.FPNegMulSub); + var dlgSoftFloat64FPRecipEstimate = new SoftFloat64FPRecipEstimate(SoftFloat64.FPRecipEstimate); + var dlgSoftFloat64FPRecipEstimateFpscr = new SoftFloat64FPRecipEstimateFpscr(SoftFloat64.FPRecipEstimateFpscr); // A32 only. + var dlgSoftFloat64FPRecipStep = new SoftFloat64FPRecipStep(SoftFloat64.FPRecipStep); // A32 only. + var dlgSoftFloat64FPRecipStepFused = new SoftFloat64FPRecipStepFused(SoftFloat64.FPRecipStepFused); + var dlgSoftFloat64FPRecpX = new SoftFloat64FPRecpX(SoftFloat64.FPRecpX); + var dlgSoftFloat64FPRSqrtEstimate = new SoftFloat64FPRSqrtEstimate(SoftFloat64.FPRSqrtEstimate); + var dlgSoftFloat64FPRSqrtEstimateFpscr = new SoftFloat64FPRSqrtEstimateFpscr(SoftFloat64.FPRSqrtEstimateFpscr); // A32 only. + var dlgSoftFloat64FPRSqrtStep = new SoftFloat64FPRSqrtStep(SoftFloat64.FPRSqrtStep); // A32 only. + var dlgSoftFloat64FPRSqrtStepFused = new SoftFloat64FPRSqrtStepFused(SoftFloat64.FPRSqrtStepFused); + var dlgSoftFloat64FPSqrt = new SoftFloat64FPSqrt(SoftFloat64.FPSqrt); + var dlgSoftFloat64FPSub = new SoftFloat64FPSub(SoftFloat64.FPSub); + + var dlgSoftFloat64_16FPConvert = new SoftFloat64_16FPConvert(SoftFloat64_16.FPConvert); + + SetDelegateInfo(dlgMathAbs, Marshal.GetFunctionPointerForDelegate(dlgMathAbs)); + SetDelegateInfo(dlgMathCeiling, Marshal.GetFunctionPointerForDelegate(dlgMathCeiling)); + SetDelegateInfo(dlgMathFloor, Marshal.GetFunctionPointerForDelegate(dlgMathFloor)); + SetDelegateInfo(dlgMathRound, Marshal.GetFunctionPointerForDelegate(dlgMathRound)); + SetDelegateInfo(dlgMathTruncate, Marshal.GetFunctionPointerForDelegate(dlgMathTruncate)); + + SetDelegateInfo(dlgMathFAbs, Marshal.GetFunctionPointerForDelegate(dlgMathFAbs)); + SetDelegateInfo(dlgMathFCeiling, Marshal.GetFunctionPointerForDelegate(dlgMathFCeiling)); + SetDelegateInfo(dlgMathFFloor, Marshal.GetFunctionPointerForDelegate(dlgMathFFloor)); + SetDelegateInfo(dlgMathFRound, Marshal.GetFunctionPointerForDelegate(dlgMathFRound)); + SetDelegateInfo(dlgMathFTruncate, Marshal.GetFunctionPointerForDelegate(dlgMathFTruncate)); + + SetDelegateInfo(dlgNativeInterfaceBreak, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceBreak)); + SetDelegateInfo(dlgNativeInterfaceCheckSynchronization, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceCheckSynchronization)); + SetDelegateInfo(dlgNativeInterfaceEnqueueForRejit, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceEnqueueForRejit)); + SetDelegateInfo(dlgNativeInterfaceGetCntfrqEl0, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceGetCntfrqEl0)); + SetDelegateInfo(dlgNativeInterfaceGetCntpctEl0, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceGetCntpctEl0)); + SetDelegateInfo(dlgNativeInterfaceGetCntvctEl0, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceGetCntvctEl0)); + SetDelegateInfo(dlgNativeInterfaceGetCtrEl0, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceGetCtrEl0)); + SetDelegateInfo(dlgNativeInterfaceGetDczidEl0, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceGetDczidEl0)); + SetDelegateInfo(dlgNativeInterfaceGetFunctionAddress, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceGetFunctionAddress)); + SetDelegateInfo(dlgNativeInterfaceInvalidateCacheLine, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceInvalidateCacheLine)); + SetDelegateInfo(dlgNativeInterfaceReadByte, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceReadByte)); + SetDelegateInfo(dlgNativeInterfaceReadUInt16, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceReadUInt16)); + SetDelegateInfo(dlgNativeInterfaceReadUInt32, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceReadUInt32)); + SetDelegateInfo(dlgNativeInterfaceReadUInt64, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceReadUInt64)); + SetDelegateInfo(dlgNativeInterfaceReadVector128, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceReadVector128)); + SetDelegateInfo(dlgNativeInterfaceSignalMemoryTracking, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceSignalMemoryTracking)); + SetDelegateInfo(dlgNativeInterfaceSupervisorCall, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceSupervisorCall)); + SetDelegateInfo(dlgNativeInterfaceThrowInvalidMemoryAccess, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceThrowInvalidMemoryAccess)); + SetDelegateInfo(dlgNativeInterfaceUndefined, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceUndefined)); + SetDelegateInfo(dlgNativeInterfaceWriteByte, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceWriteByte)); + SetDelegateInfo(dlgNativeInterfaceWriteUInt16, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceWriteUInt16)); + SetDelegateInfo(dlgNativeInterfaceWriteUInt32, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceWriteUInt32)); + SetDelegateInfo(dlgNativeInterfaceWriteUInt64, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceWriteUInt64)); + SetDelegateInfo(dlgNativeInterfaceWriteVector128, Marshal.GetFunctionPointerForDelegate(dlgNativeInterfaceWriteVector128)); + + SetDelegateInfo(dlgSoftFallbackCountLeadingSigns, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCountLeadingSigns)); + SetDelegateInfo(dlgSoftFallbackCountLeadingZeros, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCountLeadingZeros)); + SetDelegateInfo(dlgSoftFallbackCrc32b, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32b)); + SetDelegateInfo(dlgSoftFallbackCrc32cb, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32cb)); + SetDelegateInfo(dlgSoftFallbackCrc32ch, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32ch)); + SetDelegateInfo(dlgSoftFallbackCrc32cw, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32cw)); + SetDelegateInfo(dlgSoftFallbackCrc32cx, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32cx)); + SetDelegateInfo(dlgSoftFallbackCrc32h, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32h)); + SetDelegateInfo(dlgSoftFallbackCrc32w, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32w)); + SetDelegateInfo(dlgSoftFallbackCrc32x, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackCrc32x)); + SetDelegateInfo(dlgSoftFallbackDecrypt, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackDecrypt)); + SetDelegateInfo(dlgSoftFallbackEncrypt, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackEncrypt)); + SetDelegateInfo(dlgSoftFallbackFixedRotate, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackFixedRotate)); + SetDelegateInfo(dlgSoftFallbackHashChoose, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackHashChoose)); + SetDelegateInfo(dlgSoftFallbackHashLower, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackHashLower)); + SetDelegateInfo(dlgSoftFallbackHashMajority, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackHashMajority)); + SetDelegateInfo(dlgSoftFallbackHashParity, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackHashParity)); + SetDelegateInfo(dlgSoftFallbackHashUpper, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackHashUpper)); + SetDelegateInfo(dlgSoftFallbackInverseMixColumns, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackInverseMixColumns)); + SetDelegateInfo(dlgSoftFallbackMixColumns, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackMixColumns)); + SetDelegateInfo(dlgSoftFallbackPolynomialMult64_128, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackPolynomialMult64_128)); + SetDelegateInfo(dlgSoftFallbackSatF32ToS32, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF32ToS32)); + SetDelegateInfo(dlgSoftFallbackSatF32ToS64, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF32ToS64)); + SetDelegateInfo(dlgSoftFallbackSatF32ToU32, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF32ToU32)); + SetDelegateInfo(dlgSoftFallbackSatF32ToU64, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF32ToU64)); + SetDelegateInfo(dlgSoftFallbackSatF64ToS32, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF64ToS32)); + SetDelegateInfo(dlgSoftFallbackSatF64ToS64, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF64ToS64)); + SetDelegateInfo(dlgSoftFallbackSatF64ToU32, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF64ToU32)); + SetDelegateInfo(dlgSoftFallbackSatF64ToU64, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSatF64ToU64)); + SetDelegateInfo(dlgSoftFallbackSha1SchedulePart1, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSha1SchedulePart1)); + SetDelegateInfo(dlgSoftFallbackSha1SchedulePart2, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSha1SchedulePart2)); + SetDelegateInfo(dlgSoftFallbackSha256SchedulePart1, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSha256SchedulePart1)); + SetDelegateInfo(dlgSoftFallbackSha256SchedulePart2, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSha256SchedulePart2)); + SetDelegateInfo(dlgSoftFallbackSignedShrImm64, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackSignedShrImm64)); + SetDelegateInfo(dlgSoftFallbackTbl1, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbl1)); + SetDelegateInfo(dlgSoftFallbackTbl2, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbl2)); + SetDelegateInfo(dlgSoftFallbackTbl3, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbl3)); + SetDelegateInfo(dlgSoftFallbackTbl4, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbl4)); + SetDelegateInfo(dlgSoftFallbackTbx1, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbx1)); + SetDelegateInfo(dlgSoftFallbackTbx2, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbx2)); + SetDelegateInfo(dlgSoftFallbackTbx3, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbx3)); + SetDelegateInfo(dlgSoftFallbackTbx4, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackTbx4)); + SetDelegateInfo(dlgSoftFallbackUnsignedShrImm64, Marshal.GetFunctionPointerForDelegate(dlgSoftFallbackUnsignedShrImm64)); + + SetDelegateInfo(dlgSoftFloat16_32FPConvert, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat16_32FPConvert)); + SetDelegateInfo(dlgSoftFloat16_64FPConvert, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat16_64FPConvert)); + + SetDelegateInfo(dlgSoftFloat32FPAdd, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPAdd)); + SetDelegateInfo(dlgSoftFloat32FPAddFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPAddFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPCompare, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompare)); + SetDelegateInfo(dlgSoftFloat32FPCompareEQ, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareEQ)); + SetDelegateInfo(dlgSoftFloat32FPCompareEQFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareEQFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPCompareGE, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareGE)); + SetDelegateInfo(dlgSoftFloat32FPCompareGEFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareGEFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPCompareGT, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareGT)); + SetDelegateInfo(dlgSoftFloat32FPCompareGTFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareGTFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPCompareLE, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareLE)); + SetDelegateInfo(dlgSoftFloat32FPCompareLEFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareLEFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPCompareLT, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareLT)); + SetDelegateInfo(dlgSoftFloat32FPCompareLTFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPCompareLTFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPDiv, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPDiv)); + SetDelegateInfo(dlgSoftFloat32FPMax, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMax)); + SetDelegateInfo(dlgSoftFloat32FPMaxFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMaxFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPMaxNum, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMaxNum)); + SetDelegateInfo(dlgSoftFloat32FPMaxNumFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMaxNumFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPMin, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMin)); + SetDelegateInfo(dlgSoftFloat32FPMinFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMinFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPMinNum, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMinNum)); + SetDelegateInfo(dlgSoftFloat32FPMinNumFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMinNumFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPMul, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMul)); + SetDelegateInfo(dlgSoftFloat32FPMulFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMulFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPMulAdd, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMulAdd)); + SetDelegateInfo(dlgSoftFloat32FPMulAddFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMulAddFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPMulSub, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMulSub)); + SetDelegateInfo(dlgSoftFloat32FPMulSubFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMulSubFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPMulX, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPMulX)); + SetDelegateInfo(dlgSoftFloat32FPNegMulAdd, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPNegMulAdd)); + SetDelegateInfo(dlgSoftFloat32FPNegMulSub, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPNegMulSub)); + SetDelegateInfo(dlgSoftFloat32FPRecipEstimate, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRecipEstimate)); + SetDelegateInfo(dlgSoftFloat32FPRecipEstimateFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRecipEstimateFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPRecipStep, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRecipStep)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPRecipStepFused, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRecipStepFused)); + SetDelegateInfo(dlgSoftFloat32FPRecpX, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRecpX)); + SetDelegateInfo(dlgSoftFloat32FPRSqrtEstimate, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRSqrtEstimate)); + SetDelegateInfo(dlgSoftFloat32FPRSqrtEstimateFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRSqrtEstimateFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPRSqrtStep, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRSqrtStep)); // A32 only. + SetDelegateInfo(dlgSoftFloat32FPRSqrtStepFused, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPRSqrtStepFused)); + SetDelegateInfo(dlgSoftFloat32FPSqrt, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPSqrt)); + SetDelegateInfo(dlgSoftFloat32FPSub, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32FPSub)); + + SetDelegateInfo(dlgSoftFloat32_16FPConvert, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat32_16FPConvert)); + + SetDelegateInfo(dlgSoftFloat64FPAdd, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPAdd)); + SetDelegateInfo(dlgSoftFloat64FPAddFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPAddFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPCompare, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompare)); + SetDelegateInfo(dlgSoftFloat64FPCompareEQ, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareEQ)); + SetDelegateInfo(dlgSoftFloat64FPCompareEQFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareEQFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPCompareGE, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareGE)); + SetDelegateInfo(dlgSoftFloat64FPCompareGEFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareGEFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPCompareGT, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareGT)); + SetDelegateInfo(dlgSoftFloat64FPCompareGTFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareGTFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPCompareLE, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareLE)); + SetDelegateInfo(dlgSoftFloat64FPCompareLEFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareLEFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPCompareLT, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareLT)); + SetDelegateInfo(dlgSoftFloat64FPCompareLTFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPCompareLTFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPDiv, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPDiv)); + SetDelegateInfo(dlgSoftFloat64FPMax, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMax)); + SetDelegateInfo(dlgSoftFloat64FPMaxFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMaxFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPMaxNum, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMaxNum)); + SetDelegateInfo(dlgSoftFloat64FPMaxNumFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMaxNumFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPMin, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMin)); + SetDelegateInfo(dlgSoftFloat64FPMinFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMinFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPMinNum, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMinNum)); + SetDelegateInfo(dlgSoftFloat64FPMinNumFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMinNumFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPMul, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMul)); + SetDelegateInfo(dlgSoftFloat64FPMulFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMulFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPMulAdd, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMulAdd)); + SetDelegateInfo(dlgSoftFloat64FPMulAddFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMulAddFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPMulSub, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMulSub)); + SetDelegateInfo(dlgSoftFloat64FPMulSubFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMulSubFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPMulX, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPMulX)); + SetDelegateInfo(dlgSoftFloat64FPNegMulAdd, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPNegMulAdd)); + SetDelegateInfo(dlgSoftFloat64FPNegMulSub, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPNegMulSub)); + SetDelegateInfo(dlgSoftFloat64FPRecipEstimate, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRecipEstimate)); + SetDelegateInfo(dlgSoftFloat64FPRecipEstimateFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRecipEstimateFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPRecipStep, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRecipStep)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPRecipStepFused, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRecipStepFused)); + SetDelegateInfo(dlgSoftFloat64FPRecpX, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRecpX)); + SetDelegateInfo(dlgSoftFloat64FPRSqrtEstimate, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRSqrtEstimate)); + SetDelegateInfo(dlgSoftFloat64FPRSqrtEstimateFpscr, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRSqrtEstimateFpscr)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPRSqrtStep, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRSqrtStep)); // A32 only. + SetDelegateInfo(dlgSoftFloat64FPRSqrtStepFused, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPRSqrtStepFused)); + SetDelegateInfo(dlgSoftFloat64FPSqrt, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPSqrt)); + SetDelegateInfo(dlgSoftFloat64FPSub, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64FPSub)); + + SetDelegateInfo(dlgSoftFloat64_16FPConvert, Marshal.GetFunctionPointerForDelegate(dlgSoftFloat64_16FPConvert)); + } + + private delegate double MathAbs(double value); + private delegate double MathCeiling(double a); + private delegate double MathFloor(double d); + private delegate double MathRound(double value, MidpointRounding mode); + private delegate double MathTruncate(double d); + + private delegate float MathFAbs(float x); + private delegate float MathFCeiling(float x); + private delegate float MathFFloor(float x); + private delegate float MathFRound(float x, MidpointRounding mode); + private delegate float MathFTruncate(float x); + + private delegate void NativeInterfaceBreak(ulong address, int imm); + private delegate bool NativeInterfaceCheckSynchronization(); + private delegate void NativeInterfaceEnqueueForRejit(ulong address); + private delegate ulong NativeInterfaceGetCntfrqEl0(); + private delegate ulong NativeInterfaceGetCntpctEl0(); + private delegate ulong NativeInterfaceGetCntvctEl0(); + private delegate ulong NativeInterfaceGetCtrEl0(); + private delegate ulong NativeInterfaceGetDczidEl0(); + private delegate ulong NativeInterfaceGetFunctionAddress(ulong address); + private delegate void NativeInterfaceInvalidateCacheLine(ulong address); + private delegate byte NativeInterfaceReadByte(ulong address); + private delegate ushort NativeInterfaceReadUInt16(ulong address); + private delegate uint NativeInterfaceReadUInt32(ulong address); + private delegate ulong NativeInterfaceReadUInt64(ulong address); + private delegate V128 NativeInterfaceReadVector128(ulong address); + private delegate void NativeInterfaceSignalMemoryTracking(ulong address, ulong size, bool write); + private delegate void NativeInterfaceSupervisorCall(ulong address, int imm); + private delegate void NativeInterfaceThrowInvalidMemoryAccess(ulong address); + private delegate void NativeInterfaceUndefined(ulong address, int opCode); + private delegate void NativeInterfaceWriteByte(ulong address, byte value); + private delegate void NativeInterfaceWriteUInt16(ulong address, ushort value); + private delegate void NativeInterfaceWriteUInt32(ulong address, uint value); + private delegate void NativeInterfaceWriteUInt64(ulong address, ulong value); + private delegate void NativeInterfaceWriteVector128(ulong address, V128 value); + + private delegate ulong SoftFallbackCountLeadingSigns(ulong value, int size); + private delegate ulong SoftFallbackCountLeadingZeros(ulong value, int size); + private delegate uint SoftFallbackCrc32b(uint crc, byte value); + private delegate uint SoftFallbackCrc32cb(uint crc, byte value); + private delegate uint SoftFallbackCrc32ch(uint crc, ushort value); + private delegate uint SoftFallbackCrc32cw(uint crc, uint value); + private delegate uint SoftFallbackCrc32cx(uint crc, ulong value); + private delegate uint SoftFallbackCrc32h(uint crc, ushort value); + private delegate uint SoftFallbackCrc32w(uint crc, uint value); + private delegate uint SoftFallbackCrc32x(uint crc, ulong value); + private delegate V128 SoftFallbackDecrypt(V128 value, V128 roundKey); + private delegate V128 SoftFallbackEncrypt(V128 value, V128 roundKey); + private delegate uint SoftFallbackFixedRotate(uint hash_e); + private delegate V128 SoftFallbackHashChoose(V128 hash_abcd, uint hash_e, V128 wk); + private delegate V128 SoftFallbackHashLower(V128 hash_abcd, V128 hash_efgh, V128 wk); + private delegate V128 SoftFallbackHashMajority(V128 hash_abcd, uint hash_e, V128 wk); + private delegate V128 SoftFallbackHashParity(V128 hash_abcd, uint hash_e, V128 wk); + private delegate V128 SoftFallbackHashUpper(V128 hash_abcd, V128 hash_efgh, V128 wk); + private delegate V128 SoftFallbackInverseMixColumns(V128 value); + private delegate V128 SoftFallbackMixColumns(V128 value); + private delegate V128 SoftFallbackPolynomialMult64_128(ulong op1, ulong op2); + private delegate int SoftFallbackSatF32ToS32(float value); + private delegate long SoftFallbackSatF32ToS64(float value); + private delegate uint SoftFallbackSatF32ToU32(float value); + private delegate ulong SoftFallbackSatF32ToU64(float value); + private delegate int SoftFallbackSatF64ToS32(double value); + private delegate long SoftFallbackSatF64ToS64(double value); + private delegate uint SoftFallbackSatF64ToU32(double value); + private delegate ulong SoftFallbackSatF64ToU64(double value); + private delegate V128 SoftFallbackSha1SchedulePart1(V128 w0_3, V128 w4_7, V128 w8_11); + private delegate V128 SoftFallbackSha1SchedulePart2(V128 tw0_3, V128 w12_15); + private delegate V128 SoftFallbackSha256SchedulePart1(V128 w0_3, V128 w4_7); + private delegate V128 SoftFallbackSha256SchedulePart2(V128 w0_3, V128 w8_11, V128 w12_15); + private delegate long SoftFallbackSignedShrImm64(long value, long roundConst, int shift); + private delegate V128 SoftFallbackTbl1(V128 vector, int bytes, V128 tb0); + private delegate V128 SoftFallbackTbl2(V128 vector, int bytes, V128 tb0, V128 tb1); + private delegate V128 SoftFallbackTbl3(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2); + private delegate V128 SoftFallbackTbl4(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3); + private delegate V128 SoftFallbackTbx1(V128 dest, V128 vector, int bytes, V128 tb0); + private delegate V128 SoftFallbackTbx2(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1); + private delegate V128 SoftFallbackTbx3(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2); + private delegate V128 SoftFallbackTbx4(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3); + private delegate ulong SoftFallbackUnsignedShrImm64(ulong value, long roundConst, int shift); + + private delegate float SoftFloat16_32FPConvert(ushort valueBits); + + private delegate double SoftFloat16_64FPConvert(ushort valueBits); + + private delegate float SoftFloat32FPAdd(float value1, float value2); + private delegate float SoftFloat32FPAddFpscr(float value1, float value2, bool standardFpscr); + private delegate int SoftFloat32FPCompare(float value1, float value2, bool signalNaNs); + private delegate float SoftFloat32FPCompareEQ(float value1, float value2); + private delegate float SoftFloat32FPCompareEQFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPCompareGE(float value1, float value2); + private delegate float SoftFloat32FPCompareGEFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPCompareGT(float value1, float value2); + private delegate float SoftFloat32FPCompareGTFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPCompareLE(float value1, float value2); + private delegate float SoftFloat32FPCompareLEFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPCompareLT(float value1, float value2); + private delegate float SoftFloat32FPCompareLTFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPDiv(float value1, float value2); + private delegate float SoftFloat32FPMax(float value1, float value2); + private delegate float SoftFloat32FPMaxFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPMaxNum(float value1, float value2); + private delegate float SoftFloat32FPMaxNumFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPMin(float value1, float value2); + private delegate float SoftFloat32FPMinFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPMinNum(float value1, float value2); + private delegate float SoftFloat32FPMinNumFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPMul(float value1, float value2); + private delegate float SoftFloat32FPMulFpscr(float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPMulAdd(float valueA, float value1, float value2); + private delegate float SoftFloat32FPMulAddFpscr(float valueA, float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPMulSub(float valueA, float value1, float value2); + private delegate float SoftFloat32FPMulSubFpscr(float valueA, float value1, float value2, bool standardFpscr); + private delegate float SoftFloat32FPMulX(float value1, float value2); + private delegate float SoftFloat32FPNegMulAdd(float valueA, float value1, float value2); + private delegate float SoftFloat32FPNegMulSub(float valueA, float value1, float value2); + private delegate float SoftFloat32FPRecipEstimate(float value); + private delegate float SoftFloat32FPRecipEstimateFpscr(float value, bool standardFpscr); + private delegate float SoftFloat32FPRecipStep(float value1, float value2); + private delegate float SoftFloat32FPRecipStepFused(float value1, float value2); + private delegate float SoftFloat32FPRecpX(float value); + private delegate float SoftFloat32FPRSqrtEstimate(float value); + private delegate float SoftFloat32FPRSqrtEstimateFpscr(float value, bool standardFpscr); + private delegate float SoftFloat32FPRSqrtStep(float value1, float value2); + private delegate float SoftFloat32FPRSqrtStepFused(float value1, float value2); + private delegate float SoftFloat32FPSqrt(float value); + private delegate float SoftFloat32FPSub(float value1, float value2); + + private delegate ushort SoftFloat32_16FPConvert(float value); + + private delegate double SoftFloat64FPAdd(double value1, double value2); + private delegate double SoftFloat64FPAddFpscr(double value1, double value2, bool standardFpscr); + private delegate int SoftFloat64FPCompare(double value1, double value2, bool signalNaNs); + private delegate double SoftFloat64FPCompareEQ(double value1, double value2); + private delegate double SoftFloat64FPCompareEQFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPCompareGE(double value1, double value2); + private delegate double SoftFloat64FPCompareGEFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPCompareGT(double value1, double value2); + private delegate double SoftFloat64FPCompareGTFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPCompareLE(double value1, double value2); + private delegate double SoftFloat64FPCompareLEFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPCompareLT(double value1, double value2); + private delegate double SoftFloat64FPCompareLTFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPDiv(double value1, double value2); + private delegate double SoftFloat64FPMax(double value1, double value2); + private delegate double SoftFloat64FPMaxFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPMaxNum(double value1, double value2); + private delegate double SoftFloat64FPMaxNumFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPMin(double value1, double value2); + private delegate double SoftFloat64FPMinFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPMinNum(double value1, double value2); + private delegate double SoftFloat64FPMinNumFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPMul(double value1, double value2); + private delegate double SoftFloat64FPMulFpscr(double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPMulAdd(double valueA, double value1, double value2); + private delegate double SoftFloat64FPMulAddFpscr(double valueA, double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPMulSub(double valueA, double value1, double value2); + private delegate double SoftFloat64FPMulSubFpscr(double valueA, double value1, double value2, bool standardFpscr); + private delegate double SoftFloat64FPMulX(double value1, double value2); + private delegate double SoftFloat64FPNegMulAdd(double valueA, double value1, double value2); + private delegate double SoftFloat64FPNegMulSub(double valueA, double value1, double value2); + private delegate double SoftFloat64FPRecipEstimate(double value); + private delegate double SoftFloat64FPRecipEstimateFpscr(double value, bool standardFpscr); + private delegate double SoftFloat64FPRecipStep(double value1, double value2); + private delegate double SoftFloat64FPRecipStepFused(double value1, double value2); + private delegate double SoftFloat64FPRecpX(double value); + private delegate double SoftFloat64FPRSqrtEstimate(double value); + private delegate double SoftFloat64FPRSqrtEstimateFpscr(double value, bool standardFpscr); + private delegate double SoftFloat64FPRSqrtStep(double value1, double value2); + private delegate double SoftFloat64FPRSqrtStepFused(double value1, double value2); + private delegate double SoftFloat64FPSqrt(double value); + private delegate double SoftFloat64FPSub(double value1, double value2); + + private delegate ushort SoftFloat64_16FPConvert(double value); + } +} diff --git a/src/ARMeilleure/Translation/DispatcherFunction.cs b/src/ARMeilleure/Translation/DispatcherFunction.cs new file mode 100644 index 00000000..649fa0f5 --- /dev/null +++ b/src/ARMeilleure/Translation/DispatcherFunction.cs @@ -0,0 +1,7 @@ +using System; + +namespace ARMeilleure.Translation +{ + delegate void DispatcherFunction(IntPtr nativeContext, ulong startAddress); + delegate ulong WrapperFunction(IntPtr nativeContext, ulong startAddress); +} diff --git a/src/ARMeilleure/Translation/Dominance.cs b/src/ARMeilleure/Translation/Dominance.cs new file mode 100644 index 00000000..e2185bd8 --- /dev/null +++ b/src/ARMeilleure/Translation/Dominance.cs @@ -0,0 +1,95 @@ +using ARMeilleure.IntermediateRepresentation; +using System.Diagnostics; + +namespace ARMeilleure.Translation +{ + static class Dominance + { + // Those methods are an implementation of the algorithms on "A Simple, Fast Dominance Algorithm". + // https://www.cs.rice.edu/~keith/EMBED/dom.pdf + public static void FindDominators(ControlFlowGraph cfg) + { + BasicBlock Intersect(BasicBlock block1, BasicBlock block2) + { + while (block1 != block2) + { + while (cfg.PostOrderMap[block1.Index] < cfg.PostOrderMap[block2.Index]) + { + block1 = block1.ImmediateDominator; + } + + while (cfg.PostOrderMap[block2.Index] < cfg.PostOrderMap[block1.Index]) + { + block2 = block2.ImmediateDominator; + } + } + + return block1; + } + + cfg.Entry.ImmediateDominator = cfg.Entry; + + Debug.Assert(cfg.Entry == cfg.PostOrderBlocks[^1]); + + bool modified; + + do + { + modified = false; + + for (int blkIndex = cfg.PostOrderBlocks.Length - 2; blkIndex >= 0; blkIndex--) + { + BasicBlock block = cfg.PostOrderBlocks[blkIndex]; + + BasicBlock newIDom = null; + + foreach (BasicBlock predecessor in block.Predecessors) + { + if (predecessor.ImmediateDominator != null) + { + if (newIDom != null) + { + newIDom = Intersect(predecessor, newIDom); + } + else + { + newIDom = predecessor; + } + } + } + + if (block.ImmediateDominator != newIDom) + { + block.ImmediateDominator = newIDom; + + modified = true; + } + } + } + while (modified); + } + + public static void FindDominanceFrontiers(ControlFlowGraph cfg) + { + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + if (block.Predecessors.Count < 2) + { + continue; + } + + for (int pBlkIndex = 0; pBlkIndex < block.Predecessors.Count; pBlkIndex++) + { + BasicBlock current = block.Predecessors[pBlkIndex]; + + while (current != block.ImmediateDominator) + { + current.DominanceFrontiers.Add(block); + + current = current.ImmediateDominator; + } + } + } + } + } +} diff --git a/src/ARMeilleure/Translation/EmitterContext.cs b/src/ARMeilleure/Translation/EmitterContext.cs new file mode 100644 index 00000000..88bfe133 --- /dev/null +++ b/src/ARMeilleure/Translation/EmitterContext.cs @@ -0,0 +1,680 @@ +using ARMeilleure.Diagnostics; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Reflection; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + class EmitterContext + { + private int _localsCount; + + private readonly Dictionary _irLabels; + private readonly IntrusiveList _irBlocks; + + private BasicBlock _irBlock; + private BasicBlock _ifBlock; + + private bool _needsNewBlock; + private BasicBlockFrequency _nextBlockFreq; + + public EmitterContext() + { + _localsCount = 0; + + _irLabels = new Dictionary(); + _irBlocks = new IntrusiveList(); + + _needsNewBlock = true; + _nextBlockFreq = BasicBlockFrequency.Default; + } + + public Operand AllocateLocal(OperandType type) + { + Operand local = Local(type); + + local.NumberLocal(++_localsCount); + + return local; + } + + public Operand Add(Operand op1, Operand op2) + { + return Add(Instruction.Add, Local(op1.Type), op1, op2); + } + + public Operand BitwiseAnd(Operand op1, Operand op2) + { + return Add(Instruction.BitwiseAnd, Local(op1.Type), op1, op2); + } + + public Operand BitwiseExclusiveOr(Operand op1, Operand op2) + { + return Add(Instruction.BitwiseExclusiveOr, Local(op1.Type), op1, op2); + } + + public Operand BitwiseNot(Operand op1) + { + return Add(Instruction.BitwiseNot, Local(op1.Type), op1); + } + + public Operand BitwiseOr(Operand op1, Operand op2) + { + return Add(Instruction.BitwiseOr, Local(op1.Type), op1, op2); + } + + public void Branch(Operand label) + { + NewNextBlockIfNeeded(); + + BranchToLabel(label, uncond: true, BasicBlockFrequency.Default); + } + + public void BranchIf(Operand label, Operand op1, Operand op2, Comparison comp, BasicBlockFrequency falseFreq = default) + { + Add(Instruction.BranchIf, default, op1, op2, Const((int)comp)); + + BranchToLabel(label, uncond: false, falseFreq); + } + + public void BranchIfFalse(Operand label, Operand op1, BasicBlockFrequency falseFreq = default) + { + BranchIf(label, op1, Const(op1.Type, 0), Comparison.Equal, falseFreq); + } + + public void BranchIfTrue(Operand label, Operand op1, BasicBlockFrequency falseFreq = default) + { + BranchIf(label, op1, Const(op1.Type, 0), Comparison.NotEqual, falseFreq); + } + + public Operand ByteSwap(Operand op1) + { + return Add(Instruction.ByteSwap, Local(op1.Type), op1); + } + + public virtual Operand Call(MethodInfo info, params Operand[] callArgs) + { + IntPtr funcPtr = Delegates.GetDelegateFuncPtr(info); + + OperandType returnType = GetOperandType(info.ReturnType); + + Symbols.Add((ulong)funcPtr.ToInt64(), info.Name); + + return Call(Const(funcPtr.ToInt64()), returnType, callArgs); + } + + protected static OperandType GetOperandType(Type type) + { + if (type == typeof(bool) || type == typeof(byte) || + type == typeof(char) || type == typeof(short) || + type == typeof(int) || type == typeof(sbyte) || + type == typeof(ushort) || type == typeof(uint)) + { + return OperandType.I32; + } + else if (type == typeof(long) || type == typeof(ulong)) + { + return OperandType.I64; + } + else if (type == typeof(double)) + { + return OperandType.FP64; + } + else if (type == typeof(float)) + { + return OperandType.FP32; + } + else if (type == typeof(V128)) + { + return OperandType.V128; + } + else if (type == typeof(void)) + { + return OperandType.None; + } + else + { + throw new ArgumentException($"Invalid type \"{type.Name}\"."); + } + } + + public Operand Call(Operand address, OperandType returnType, params Operand[] callArgs) + { + Operand[] args = new Operand[callArgs.Length + 1]; + + args[0] = address; + + Array.Copy(callArgs, 0, args, 1, callArgs.Length); + + if (returnType != OperandType.None) + { + return Add(Instruction.Call, Local(returnType), args); + } + else + { + return Add(Instruction.Call, default, args); + } + } + + public void Tailcall(Operand address, params Operand[] callArgs) + { + Operand[] args = new Operand[callArgs.Length + 1]; + + args[0] = address; + + Array.Copy(callArgs, 0, args, 1, callArgs.Length); + + Add(Instruction.Tailcall, default, args); + + _needsNewBlock = true; + } + + public Operand CompareAndSwap(Operand address, Operand expected, Operand desired) + { + return Add(Instruction.CompareAndSwap, Local(desired.Type), address, expected, desired); + } + + public Operand CompareAndSwap16(Operand address, Operand expected, Operand desired) + { + return Add(Instruction.CompareAndSwap16, Local(OperandType.I32), address, expected, desired); + } + + public Operand CompareAndSwap8(Operand address, Operand expected, Operand desired) + { + return Add(Instruction.CompareAndSwap8, Local(OperandType.I32), address, expected, desired); + } + + public Operand ConditionalSelect(Operand op1, Operand op2, Operand op3) + { + return Add(Instruction.ConditionalSelect, Local(op2.Type), op1, op2, op3); + } + + public Operand ConvertI64ToI32(Operand op1) + { + if (op1.Type != OperandType.I64) + { + throw new ArgumentException($"Invalid operand type \"{op1.Type}\"."); + } + + return Add(Instruction.ConvertI64ToI32, Local(OperandType.I32), op1); + } + + public Operand ConvertToFP(OperandType type, Operand op1) + { + return Add(Instruction.ConvertToFP, Local(type), op1); + } + + public Operand ConvertToFPUI(OperandType type, Operand op1) + { + return Add(Instruction.ConvertToFPUI, Local(type), op1); + } + + public Operand Copy(Operand op1) + { + return Add(Instruction.Copy, Local(op1.Type), op1); + } + + public Operand Copy(Operand dest, Operand op1) + { + if (dest.Kind != OperandKind.Register && + (dest.Kind != OperandKind.LocalVariable || dest.GetLocalNumber() == 0)) + { + throw new ArgumentException($"Destination operand must be a Register or a numbered LocalVariable."); + } + + return Add(Instruction.Copy, dest, op1); + } + + public Operand CountLeadingZeros(Operand op1) + { + return Add(Instruction.CountLeadingZeros, Local(op1.Type), op1); + } + + public Operand Divide(Operand op1, Operand op2) + { + return Add(Instruction.Divide, Local(op1.Type), op1, op2); + } + + public Operand DivideUI(Operand op1, Operand op2) + { + return Add(Instruction.DivideUI, Local(op1.Type), op1, op2); + } + + public Operand ICompare(Operand op1, Operand op2, Comparison comp) + { + return Add(Instruction.Compare, Local(OperandType.I32), op1, op2, Const((int)comp)); + } + + public Operand ICompareEqual(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.Equal); + } + + public Operand ICompareGreater(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.Greater); + } + + public Operand ICompareGreaterOrEqual(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.GreaterOrEqual); + } + + public Operand ICompareGreaterOrEqualUI(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.GreaterOrEqualUI); + } + + public Operand ICompareGreaterUI(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.GreaterUI); + } + + public Operand ICompareLess(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.Less); + } + + public Operand ICompareLessOrEqual(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.LessOrEqual); + } + + public Operand ICompareLessOrEqualUI(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.LessOrEqualUI); + } + + public Operand ICompareLessUI(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.LessUI); + } + + public Operand ICompareNotEqual(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.NotEqual); + } + + public Operand Load(OperandType type, Operand address) + { + return Add(Instruction.Load, Local(type), address); + } + + public Operand Load16(Operand address) + { + return Add(Instruction.Load16, Local(OperandType.I32), address); + } + + public Operand Load8(Operand address) + { + return Add(Instruction.Load8, Local(OperandType.I32), address); + } + + public Operand LoadArgument(OperandType type, int index) + { + return Add(Instruction.LoadArgument, Local(type), Const(index)); + } + + public void LoadFromContext() + { + _needsNewBlock = true; + + Add(Instruction.LoadFromContext); + } + + public void MemoryBarrier() + { + Add(Instruction.MemoryBarrier); + } + + public Operand Multiply(Operand op1, Operand op2) + { + return Add(Instruction.Multiply, Local(op1.Type), op1, op2); + } + + public Operand Multiply64HighSI(Operand op1, Operand op2) + { + return Add(Instruction.Multiply64HighSI, Local(OperandType.I64), op1, op2); + } + + public Operand Multiply64HighUI(Operand op1, Operand op2) + { + return Add(Instruction.Multiply64HighUI, Local(OperandType.I64), op1, op2); + } + + public Operand Negate(Operand op1) + { + return Add(Instruction.Negate, Local(op1.Type), op1); + } + + public void Return() + { + Add(Instruction.Return); + + _needsNewBlock = true; + } + + public void Return(Operand op1) + { + Add(Instruction.Return, default, op1); + + _needsNewBlock = true; + } + + public Operand RotateRight(Operand op1, Operand op2) + { + return Add(Instruction.RotateRight, Local(op1.Type), op1, op2); + } + + public Operand ShiftLeft(Operand op1, Operand op2) + { + return Add(Instruction.ShiftLeft, Local(op1.Type), op1, op2); + } + + public Operand ShiftRightSI(Operand op1, Operand op2) + { + return Add(Instruction.ShiftRightSI, Local(op1.Type), op1, op2); + } + + public Operand ShiftRightUI(Operand op1, Operand op2) + { + return Add(Instruction.ShiftRightUI, Local(op1.Type), op1, op2); + } + + public Operand SignExtend16(OperandType type, Operand op1) + { + return Add(Instruction.SignExtend16, Local(type), op1); + } + + public Operand SignExtend32(OperandType type, Operand op1) + { + return Add(Instruction.SignExtend32, Local(type), op1); + } + + public Operand SignExtend8(OperandType type, Operand op1) + { + return Add(Instruction.SignExtend8, Local(type), op1); + } + + public void Store(Operand address, Operand value) + { + Add(Instruction.Store, default, address, value); + } + + public void Store16(Operand address, Operand value) + { + Add(Instruction.Store16, default, address, value); + } + + public void Store8(Operand address, Operand value) + { + Add(Instruction.Store8, default, address, value); + } + + public void StoreToContext() + { + Add(Instruction.StoreToContext); + + _needsNewBlock = true; + } + + public Operand Subtract(Operand op1, Operand op2) + { + return Add(Instruction.Subtract, Local(op1.Type), op1, op2); + } + + public Operand VectorCreateScalar(Operand value) + { + return Add(Instruction.VectorCreateScalar, Local(OperandType.V128), value); + } + + public Operand VectorExtract(OperandType type, Operand vector, int index) + { + return Add(Instruction.VectorExtract, Local(type), vector, Const(index)); + } + + public Operand VectorExtract16(Operand vector, int index) + { + return Add(Instruction.VectorExtract16, Local(OperandType.I32), vector, Const(index)); + } + + public Operand VectorExtract8(Operand vector, int index) + { + return Add(Instruction.VectorExtract8, Local(OperandType.I32), vector, Const(index)); + } + + public Operand VectorInsert(Operand vector, Operand value, int index) + { + return Add(Instruction.VectorInsert, Local(OperandType.V128), vector, value, Const(index)); + } + + public Operand VectorInsert16(Operand vector, Operand value, int index) + { + return Add(Instruction.VectorInsert16, Local(OperandType.V128), vector, value, Const(index)); + } + + public Operand VectorInsert8(Operand vector, Operand value, int index) + { + return Add(Instruction.VectorInsert8, Local(OperandType.V128), vector, value, Const(index)); + } + + public Operand VectorOne() + { + return Add(Instruction.VectorOne, Local(OperandType.V128)); + } + + public Operand VectorZero() + { + return Add(Instruction.VectorZero, Local(OperandType.V128)); + } + + public Operand VectorZeroUpper64(Operand vector) + { + return Add(Instruction.VectorZeroUpper64, Local(OperandType.V128), vector); + } + + public Operand VectorZeroUpper96(Operand vector) + { + return Add(Instruction.VectorZeroUpper96, Local(OperandType.V128), vector); + } + + public Operand ZeroExtend16(OperandType type, Operand op1) + { + return Add(Instruction.ZeroExtend16, Local(type), op1); + } + + public Operand ZeroExtend32(OperandType type, Operand op1) + { + return Add(Instruction.ZeroExtend32, Local(type), op1); + } + + public Operand ZeroExtend8(OperandType type, Operand op1) + { + return Add(Instruction.ZeroExtend8, Local(type), op1); + } + + private void NewNextBlockIfNeeded() + { + if (_needsNewBlock) + { + NewNextBlock(); + } + } + + private Operand Add(Instruction inst, Operand dest = default) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private Operand Add(Instruction inst, Operand dest, Operand[] sources) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest, sources); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private Operand Add(Instruction inst, Operand dest, Operand source0) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest, source0); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private Operand Add(Instruction inst, Operand dest, Operand source0, Operand source1) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest, source0, source1); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private Operand Add(Instruction inst, Operand dest, Operand source0, Operand source1, Operand source2) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest, source0, source1, source2); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + public Operand AddIntrinsic(Intrinsic intrin, params Operand[] args) + { + return Add(intrin, Local(OperandType.V128), args); + } + + public Operand AddIntrinsicInt(Intrinsic intrin, params Operand[] args) + { + return Add(intrin, Local(OperandType.I32), args); + } + + public Operand AddIntrinsicLong(Intrinsic intrin, params Operand[] args) + { + return Add(intrin, Local(OperandType.I64), args); + } + + public void AddIntrinsicNoRet(Intrinsic intrin, params Operand[] args) + { + Add(intrin, default, args); + } + + private Operand Add(Intrinsic intrin, Operand dest, params Operand[] sources) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(intrin, dest, sources); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private void BranchToLabel(Operand label, bool uncond, BasicBlockFrequency nextFreq) + { + if (!_irLabels.TryGetValue(label, out BasicBlock branchBlock)) + { + branchBlock = new BasicBlock(); + + _irLabels.Add(label, branchBlock); + } + + if (uncond) + { + _irBlock.AddSuccessor(branchBlock); + } + else + { + // Defer registration of successor to _irBlock so that the order of successors is correct. + _ifBlock = branchBlock; + } + + _needsNewBlock = true; + _nextBlockFreq = nextFreq; + } + + public void MarkLabel(Operand label, BasicBlockFrequency nextFreq = default) + { + _nextBlockFreq = nextFreq; + + if (_irLabels.TryGetValue(label, out BasicBlock nextBlock)) + { + nextBlock.Index = _irBlocks.Count; + + _irBlocks.AddLast(nextBlock); + + NextBlock(nextBlock); + } + else + { + NewNextBlock(); + + _irLabels.Add(label, _irBlock); + } + } + + private void NewNextBlock() + { + BasicBlock block = new(_irBlocks.Count); + + _irBlocks.AddLast(block); + + NextBlock(block); + } + + private void NextBlock(BasicBlock nextBlock) + { + if (_irBlock?.SuccessorsCount == 0 && !EndsWithUnconditional(_irBlock)) + { + _irBlock.AddSuccessor(nextBlock); + + if (_ifBlock != null) + { + _irBlock.AddSuccessor(_ifBlock); + + _ifBlock = null; + } + } + + _irBlock = nextBlock; + _irBlock.Frequency = _nextBlockFreq; + + _needsNewBlock = false; + _nextBlockFreq = BasicBlockFrequency.Default; + } + + private static bool EndsWithUnconditional(BasicBlock block) + { + Operation last = block.Operations.Last; + + return last != default && + (last.Instruction == Instruction.Return || + last.Instruction == Instruction.Tailcall); + } + + public ControlFlowGraph GetControlFlowGraph() + { + return new ControlFlowGraph(_irBlocks.First, _irBlocks, _localsCount); + } + } +} diff --git a/src/ARMeilleure/Translation/GuestFunction.cs b/src/ARMeilleure/Translation/GuestFunction.cs new file mode 100644 index 00000000..6414d6bd --- /dev/null +++ b/src/ARMeilleure/Translation/GuestFunction.cs @@ -0,0 +1,6 @@ +using System; + +namespace ARMeilleure.Translation +{ + delegate ulong GuestFunction(IntPtr nativeContextPtr); +} diff --git a/src/ARMeilleure/Translation/IntervalTree.cs b/src/ARMeilleure/Translation/IntervalTree.cs new file mode 100644 index 00000000..a5f9b5d5 --- /dev/null +++ b/src/ARMeilleure/Translation/IntervalTree.cs @@ -0,0 +1,745 @@ +using System; +using System.Collections.Generic; + +namespace ARMeilleure.Translation +{ + /// + /// An Augmented Interval Tree based off of the "TreeDictionary"'s Red-Black Tree. Allows fast overlap checking of ranges. + /// + /// Key + /// Value + public class IntervalTree where TK : IComparable + { + private const int ArrayGrowthSize = 32; + + private const bool Black = true; + private const bool Red = false; + private IntervalTreeNode _root = null; + private int _count = 0; + + public int Count => _count; + + #region Public Methods + + /// + /// Gets the values of the interval whose key is . + /// + /// Key of the node value to get + /// Value with the given + /// True if the key is on the dictionary, false otherwise + public bool TryGet(TK key, out TV value) + { + IntervalTreeNode node = GetNode(key); + + if (node == null) + { + value = default; + return false; + } + + value = node.Value; + return true; + } + + /// + /// Returns the start addresses of the intervals whose start and end keys overlap the given range. + /// + /// Start of the range + /// End of the range + /// Overlaps array to place results in + /// Index to start writing results into the array. Defaults to 0 + /// Number of intervals found + public int Get(TK start, TK end, ref TK[] overlaps, int overlapCount = 0) + { + GetKeys(_root, start, end, ref overlaps, ref overlapCount); + + return overlapCount; + } + + /// + /// Adds a new interval into the tree whose start is , end is and value is . + /// + /// Start of the range to add + /// End of the range to insert + /// Value to add + /// Optional factory used to create a new value if is already on the tree + /// is null + /// True if the value was added, false if the start key was already in the dictionary + public bool AddOrUpdate(TK start, TK end, TV value, Func updateFactoryCallback) + { + ArgumentNullException.ThrowIfNull(value); + + return BSTInsert(start, end, value, updateFactoryCallback, out _); + } + + /// + /// Gets an existing or adds a new interval into the tree whose start is , end is and value is . + /// + /// Start of the range to add + /// End of the range to insert + /// Value to add + /// is null + /// if is not yet on the tree, or the existing value otherwise + public TV GetOrAdd(TK start, TK end, TV value) + { + ArgumentNullException.ThrowIfNull(value); + + BSTInsert(start, end, value, null, out IntervalTreeNode node); + return node.Value; + } + + /// + /// Removes a value from the tree, searching for it with . + /// + /// Key of the node to remove + /// Number of deleted values + public int Remove(TK key) + { + int removed = Delete(key); + + _count -= removed; + + return removed; + } + + /// + /// Adds all the nodes in the dictionary into . + /// + /// A list of all values sorted by Key Order + public List AsList() + { + List list = new(); + + AddToList(_root, list); + + return list; + } + + #endregion + + #region Private Methods (BST) + + /// + /// Adds all values that are children of or contained within into , in Key Order. + /// + /// The node to search for values within + /// The list to add values to + private void AddToList(IntervalTreeNode node, List list) + { + if (node == null) + { + return; + } + + AddToList(node.Left, list); + + list.Add(node.Value); + + AddToList(node.Right, list); + } + + /// + /// Retrieve the node reference whose key is , or null if no such node exists. + /// + /// Key of the node to get + /// is null + /// Node reference in the tree + private IntervalTreeNode GetNode(TK key) + { + ArgumentNullException.ThrowIfNull(key); + + IntervalTreeNode node = _root; + while (node != null) + { + int cmp = key.CompareTo(node.Start); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + return node; + } + } + return null; + } + + /// + /// Retrieve all keys that overlap the given start and end keys. + /// + /// Start of the range + /// End of the range + /// Overlaps array to place results in + /// Overlaps count to update + private void GetKeys(IntervalTreeNode node, TK start, TK end, ref TK[] overlaps, ref int overlapCount) + { + if (node == null || start.CompareTo(node.Max) >= 0) + { + return; + } + + GetKeys(node.Left, start, end, ref overlaps, ref overlapCount); + + bool endsOnRight = end.CompareTo(node.Start) > 0; + if (endsOnRight) + { + if (start.CompareTo(node.End) < 0) + { + if (overlaps.Length <= overlapCount) + { + Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize); + } + + overlaps[overlapCount++] = node.Start; + } + + GetKeys(node.Right, start, end, ref overlaps, ref overlapCount); + } + } + + /// + /// Propagate an increase in max value starting at the given node, heading up the tree. + /// This should only be called if the max increases - not for rebalancing or removals. + /// + /// The node to start propagating from + private static void PropagateIncrease(IntervalTreeNode node) + { + TK max = node.Max; + IntervalTreeNode ptr = node; + + while ((ptr = ptr.Parent) != null) + { + if (max.CompareTo(ptr.Max) > 0) + { + ptr.Max = max; + } + else + { + break; + } + } + } + + /// + /// Propagate recalculating max value starting at the given node, heading up the tree. + /// This fully recalculates the max value from all children when there is potential for it to decrease. + /// + /// The node to start propagating from + private static void PropagateFull(IntervalTreeNode node) + { + IntervalTreeNode ptr = node; + + do + { + TK max = ptr.End; + + if (ptr.Left != null && ptr.Left.Max.CompareTo(max) > 0) + { + max = ptr.Left.Max; + } + + if (ptr.Right != null && ptr.Right.Max.CompareTo(max) > 0) + { + max = ptr.Right.Max; + } + + ptr.Max = max; + } while ((ptr = ptr.Parent) != null); + } + + /// + /// Insertion Mechanism for the interval tree. Similar to a BST insert, with the start of the range as the key. + /// Iterates the tree starting from the root and inserts a new node where all children in the left subtree are less than , and all children in the right subtree are greater than . + /// Each node can contain multiple values, and has an end address which is the maximum of all those values. + /// Post insertion, the "max" value of the node and all parents are updated. + /// + /// Start of the range to insert + /// End of the range to insert + /// Value to insert + /// Optional factory used to create a new value if is already on the tree + /// Node that was inserted or modified + /// True if was not yet on the tree, false otherwise + private bool BSTInsert(TK start, TK end, TV value, Func updateFactoryCallback, out IntervalTreeNode outNode) + { + IntervalTreeNode parent = null; + IntervalTreeNode node = _root; + + while (node != null) + { + parent = node; + int cmp = start.CompareTo(node.Start); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + outNode = node; + + if (updateFactoryCallback != null) + { + // Replace + node.Value = updateFactoryCallback(start, node.Value); + + int endCmp = end.CompareTo(node.End); + + if (endCmp > 0) + { + node.End = end; + if (end.CompareTo(node.Max) > 0) + { + node.Max = end; + PropagateIncrease(node); + RestoreBalanceAfterInsertion(node); + } + } + else if (endCmp < 0) + { + node.End = end; + PropagateFull(node); + } + } + + return false; + } + } + IntervalTreeNode newNode = new(start, end, value, parent); + if (newNode.Parent == null) + { + _root = newNode; + } + else if (start.CompareTo(parent.Start) < 0) + { + parent.Left = newNode; + } + else + { + parent.Right = newNode; + } + + PropagateIncrease(newNode); + _count++; + RestoreBalanceAfterInsertion(newNode); + outNode = newNode; + return true; + } + + /// + /// Removes the value from the dictionary after searching for it with . + /// + /// Key to search for + /// Number of deleted values + private int Delete(TK key) + { + IntervalTreeNode nodeToDelete = GetNode(key); + + if (nodeToDelete == null) + { + return 0; + } + + IntervalTreeNode replacementNode; + + if (LeftOf(nodeToDelete) == null || RightOf(nodeToDelete) == null) + { + replacementNode = nodeToDelete; + } + else + { + replacementNode = PredecessorOf(nodeToDelete); + } + + IntervalTreeNode tmp = LeftOf(replacementNode) ?? RightOf(replacementNode); + + if (tmp != null) + { + tmp.Parent = ParentOf(replacementNode); + } + + if (ParentOf(replacementNode) == null) + { + _root = tmp; + } + else if (replacementNode == LeftOf(ParentOf(replacementNode))) + { + ParentOf(replacementNode).Left = tmp; + } + else + { + ParentOf(replacementNode).Right = tmp; + } + + if (replacementNode != nodeToDelete) + { + nodeToDelete.Start = replacementNode.Start; + nodeToDelete.Value = replacementNode.Value; + nodeToDelete.End = replacementNode.End; + nodeToDelete.Max = replacementNode.Max; + } + + PropagateFull(replacementNode); + + if (tmp != null && ColorOf(replacementNode) == Black) + { + RestoreBalanceAfterRemoval(tmp); + } + + return 1; + } + + /// + /// Returns the node with the largest key where is considered the root node. + /// + /// Root Node + /// Node with the maximum key in the tree of + private static IntervalTreeNode Maximum(IntervalTreeNode node) + { + IntervalTreeNode tmp = node; + while (tmp.Right != null) + { + tmp = tmp.Right; + } + + return tmp; + } + + /// + /// Finds the node whose key is immediately less than . + /// + /// Node to find the predecessor of + /// Predecessor of + private static IntervalTreeNode PredecessorOf(IntervalTreeNode node) + { + if (node.Left != null) + { + return Maximum(node.Left); + } + IntervalTreeNode parent = node.Parent; + while (parent != null && node == parent.Left) + { + node = parent; + parent = parent.Parent; + } + return parent; + } + + #endregion + + #region Private Methods (RBL) + + private void RestoreBalanceAfterRemoval(IntervalTreeNode balanceNode) + { + IntervalTreeNode ptr = balanceNode; + + while (ptr != _root && ColorOf(ptr) == Black) + { + if (ptr == LeftOf(ParentOf(ptr))) + { + IntervalTreeNode sibling = RightOf(ParentOf(ptr)); + + if (ColorOf(sibling) == Red) + { + SetColor(sibling, Black); + SetColor(ParentOf(ptr), Red); + RotateLeft(ParentOf(ptr)); + sibling = RightOf(ParentOf(ptr)); + } + if (ColorOf(LeftOf(sibling)) == Black && ColorOf(RightOf(sibling)) == Black) + { + SetColor(sibling, Red); + ptr = ParentOf(ptr); + } + else + { + if (ColorOf(RightOf(sibling)) == Black) + { + SetColor(LeftOf(sibling), Black); + SetColor(sibling, Red); + RotateRight(sibling); + sibling = RightOf(ParentOf(ptr)); + } + SetColor(sibling, ColorOf(ParentOf(ptr))); + SetColor(ParentOf(ptr), Black); + SetColor(RightOf(sibling), Black); + RotateLeft(ParentOf(ptr)); + ptr = _root; + } + } + else + { + IntervalTreeNode sibling = LeftOf(ParentOf(ptr)); + + if (ColorOf(sibling) == Red) + { + SetColor(sibling, Black); + SetColor(ParentOf(ptr), Red); + RotateRight(ParentOf(ptr)); + sibling = LeftOf(ParentOf(ptr)); + } + if (ColorOf(RightOf(sibling)) == Black && ColorOf(LeftOf(sibling)) == Black) + { + SetColor(sibling, Red); + ptr = ParentOf(ptr); + } + else + { + if (ColorOf(LeftOf(sibling)) == Black) + { + SetColor(RightOf(sibling), Black); + SetColor(sibling, Red); + RotateLeft(sibling); + sibling = LeftOf(ParentOf(ptr)); + } + SetColor(sibling, ColorOf(ParentOf(ptr))); + SetColor(ParentOf(ptr), Black); + SetColor(LeftOf(sibling), Black); + RotateRight(ParentOf(ptr)); + ptr = _root; + } + } + } + SetColor(ptr, Black); + } + + private void RestoreBalanceAfterInsertion(IntervalTreeNode balanceNode) + { + SetColor(balanceNode, Red); + while (balanceNode != null && balanceNode != _root && ColorOf(ParentOf(balanceNode)) == Red) + { + if (ParentOf(balanceNode) == LeftOf(ParentOf(ParentOf(balanceNode)))) + { + IntervalTreeNode sibling = RightOf(ParentOf(ParentOf(balanceNode))); + + if (ColorOf(sibling) == Red) + { + SetColor(ParentOf(balanceNode), Black); + SetColor(sibling, Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + balanceNode = ParentOf(ParentOf(balanceNode)); + } + else + { + if (balanceNode == RightOf(ParentOf(balanceNode))) + { + balanceNode = ParentOf(balanceNode); + RotateLeft(balanceNode); + } + SetColor(ParentOf(balanceNode), Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + RotateRight(ParentOf(ParentOf(balanceNode))); + } + } + else + { + IntervalTreeNode sibling = LeftOf(ParentOf(ParentOf(balanceNode))); + + if (ColorOf(sibling) == Red) + { + SetColor(ParentOf(balanceNode), Black); + SetColor(sibling, Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + balanceNode = ParentOf(ParentOf(balanceNode)); + } + else + { + if (balanceNode == LeftOf(ParentOf(balanceNode))) + { + balanceNode = ParentOf(balanceNode); + RotateRight(balanceNode); + } + SetColor(ParentOf(balanceNode), Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + RotateLeft(ParentOf(ParentOf(balanceNode))); + } + } + } + SetColor(_root, Black); + } + + private void RotateLeft(IntervalTreeNode node) + { + if (node != null) + { + IntervalTreeNode right = RightOf(node); + node.Right = LeftOf(right); + if (node.Right != null) + { + node.Right.Parent = node; + } + IntervalTreeNode nodeParent = ParentOf(node); + right.Parent = nodeParent; + if (nodeParent == null) + { + _root = right; + } + else if (node == LeftOf(nodeParent)) + { + nodeParent.Left = right; + } + else + { + nodeParent.Right = right; + } + right.Left = node; + node.Parent = right; + + PropagateFull(node); + } + } + + private void RotateRight(IntervalTreeNode node) + { + if (node != null) + { + IntervalTreeNode left = LeftOf(node); + node.Left = RightOf(left); + if (node.Left != null) + { + node.Left.Parent = node; + } + IntervalTreeNode nodeParent = ParentOf(node); + left.Parent = nodeParent; + if (nodeParent == null) + { + _root = left; + } + else if (node == RightOf(nodeParent)) + { + nodeParent.Right = left; + } + else + { + nodeParent.Left = left; + } + left.Right = node; + node.Parent = left; + + PropagateFull(node); + } + } + + #endregion + + #region Safety-Methods + + // These methods save memory by allowing us to forego sentinel nil nodes, as well as serve as protection against NullReferenceExceptions. + + /// + /// Returns the color of , or Black if it is null. + /// + /// Node + /// The boolean color of , or black if null + private static bool ColorOf(IntervalTreeNode node) + { + return node == null || node.Color; + } + + /// + /// Sets the color of node to . + ///

+ /// This method does nothing if is null. + ///
+ /// Node to set the color of + /// Color (Boolean) + private static void SetColor(IntervalTreeNode node, bool color) + { + if (node != null) + { + node.Color = color; + } + } + + /// + /// This method returns the left node of , or null if is null. + /// + /// Node to retrieve the left child from + /// Left child of + private static IntervalTreeNode LeftOf(IntervalTreeNode node) + { + return node?.Left; + } + + /// + /// This method returns the right node of , or null if is null. + /// + /// Node to retrieve the right child from + /// Right child of + private static IntervalTreeNode RightOf(IntervalTreeNode node) + { + return node?.Right; + } + + /// + /// Returns the parent node of , or null if is null. + /// + /// Node to retrieve the parent from + /// Parent of + private static IntervalTreeNode ParentOf(IntervalTreeNode node) + { + return node?.Parent; + } + + #endregion + + public bool ContainsKey(TK key) + { + return GetNode(key) != null; + } + + public void Clear() + { + _root = null; + _count = 0; + } + } + + /// + /// Represents a node in the IntervalTree which contains start and end keys of type K, and a value of generic type V. + /// + /// Key type of the node + /// Value type of the node + class IntervalTreeNode + { + public bool Color = true; + public IntervalTreeNode Left = null; + public IntervalTreeNode Right = null; + public IntervalTreeNode Parent = null; + + /// + /// The start of the range. + /// + public TK Start; + + /// + /// The end of the range. + /// + public TK End; + + /// + /// The maximum end value of this node and all its children. + /// + public TK Max; + + /// + /// Value stored on this node. + /// + public TV Value; + + public IntervalTreeNode(TK start, TK end, TV value, IntervalTreeNode parent) + { + Start = start; + End = end; + Max = end; + Value = value; + Parent = parent; + } + } +} diff --git a/src/ARMeilleure/Translation/PTC/EncodingCache.cs b/src/ARMeilleure/Translation/PTC/EncodingCache.cs new file mode 100644 index 00000000..d9b38ace --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/EncodingCache.cs @@ -0,0 +1,9 @@ +using System.Text; + +namespace ARMeilleure.Translation.PTC +{ + static class EncodingCache + { + public static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + } +} diff --git a/src/ARMeilleure/Translation/PTC/IPtcLoadState.cs b/src/ARMeilleure/Translation/PTC/IPtcLoadState.cs new file mode 100644 index 00000000..efff45a9 --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/IPtcLoadState.cs @@ -0,0 +1,10 @@ +using System; + +namespace ARMeilleure.Translation.PTC +{ + public interface IPtcLoadState + { + event Action PtcStateChanged; + void Continue(); + } +} diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs new file mode 100644 index 00000000..c2eed7a5 --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -0,0 +1,1142 @@ +using ARMeilleure.CodeGen; +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Common; +using ARMeilleure.Memory; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using static ARMeilleure.Translation.PTC.PtcFormatter; + +namespace ARMeilleure.Translation.PTC +{ + using Arm64HardwareCapabilities = CodeGen.Arm64.HardwareCapabilities; + using X86HardwareCapabilities = CodeGen.X86.HardwareCapabilities; + + class Ptc : IPtcLoadState + { + private const string OuterHeaderMagicString = "PTCohd\0\0"; + private const string InnerHeaderMagicString = "PTCihd\0\0"; + + private const uint InternalVersion = 6950; //! To be incremented manually for each change to the ARMeilleure project. + + private const string ActualDir = "0"; + private const string BackupDir = "1"; + + private const string TitleIdTextDefault = "0000000000000000"; + private const string DisplayVersionDefault = "0"; + + public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1); + public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2); + public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3); + + private const byte FillingByte = 0x00; + private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest; + + public PtcProfiler Profiler { get; } + + // Carriers. + private MemoryStream _infosStream; + private List _codesList; + private MemoryStream _relocsStream; + private MemoryStream _unwindInfosStream; + + private readonly ulong _outerHeaderMagic; + private readonly ulong _innerHeaderMagic; + + private readonly ManualResetEvent _waitEvent; + + private readonly object _lock; + + private bool _disposed; + + public string TitleIdText { get; private set; } + public string DisplayVersion { get; private set; } + + private MemoryManagerType _memoryMode; + + public string CachePathActual { get; private set; } + public string CachePathBackup { get; private set; } + + public PtcState State { get; private set; } + + // Progress reporting helpers. + private volatile int _translateCount; + private volatile int _translateTotalCount; + public event Action PtcStateChanged; + + public Ptc() + { + Profiler = new PtcProfiler(this); + + InitializeCarriers(); + + _outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan()); + _innerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(InnerHeaderMagicString).AsSpan()); + + _waitEvent = new ManualResetEvent(true); + + _lock = new object(); + + _disposed = false; + + TitleIdText = TitleIdTextDefault; + DisplayVersion = DisplayVersionDefault; + + CachePathActual = string.Empty; + CachePathBackup = string.Empty; + + Disable(); + } + + public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode) + { + Wait(); + + Profiler.Wait(); + Profiler.ClearEntries(); + + Logger.Info?.Print(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache (enabled: {enabled})."); + + if (!enabled || string.IsNullOrEmpty(titleIdText) || titleIdText == TitleIdTextDefault) + { + TitleIdText = TitleIdTextDefault; + DisplayVersion = DisplayVersionDefault; + + CachePathActual = string.Empty; + CachePathBackup = string.Empty; + + Disable(); + + return; + } + + TitleIdText = titleIdText; + DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault; + _memoryMode = memoryMode; + + string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir); + string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir); + + if (!Directory.Exists(workPathActual)) + { + Directory.CreateDirectory(workPathActual); + } + + if (!Directory.Exists(workPathBackup)) + { + Directory.CreateDirectory(workPathBackup); + } + + CachePathActual = Path.Combine(workPathActual, DisplayVersion); + CachePathBackup = Path.Combine(workPathBackup, DisplayVersion); + + PreLoad(); + Profiler.PreLoad(); + + Enable(); + } + + private void InitializeCarriers() + { + _infosStream = MemoryStreamManager.Shared.GetStream(); + _codesList = new List(); + _relocsStream = MemoryStreamManager.Shared.GetStream(); + _unwindInfosStream = MemoryStreamManager.Shared.GetStream(); + } + + private void DisposeCarriers() + { + _infosStream.Dispose(); + _codesList.Clear(); + _relocsStream.Dispose(); + _unwindInfosStream.Dispose(); + } + + private bool AreCarriersEmpty() + { + return _infosStream.Length == 0L && _codesList.Count == 0 && _relocsStream.Length == 0L && _unwindInfosStream.Length == 0L; + } + + private void ResetCarriersIfNeeded() + { + if (AreCarriersEmpty()) + { + return; + } + + DisposeCarriers(); + + InitializeCarriers(); + } + + private void PreLoad() + { + string fileNameActual = $"{CachePathActual}.cache"; + string fileNameBackup = $"{CachePathBackup}.cache"; + + FileInfo fileInfoActual = new(fileNameActual); + FileInfo fileInfoBackup = new(fileNameBackup); + + if (fileInfoActual.Exists && fileInfoActual.Length != 0L) + { + if (!Load(fileNameActual, false)) + { + if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L) + { + Load(fileNameBackup, true); + } + } + } + else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L) + { + Load(fileNameBackup, true); + } + } + + private unsafe bool Load(string fileName, bool isBackup) + { + using (FileStream compressedStream = new(fileName, FileMode.Open)) + using (DeflateStream deflateStream = new(compressedStream, CompressionMode.Decompress, true)) + { + OuterHeader outerHeader = DeserializeStructure(compressedStream); + + if (!outerHeader.IsHeaderValid()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Magic != _outerHeaderMagic) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.CacheFileVersion != InternalVersion) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Endianness != GetEndianness()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.FeatureInfo != GetFeatureInfo()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.MemoryManagerMode != GetMemoryManagerMode()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.OSPlatform != GetOSPlatform()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Architecture != (uint)RuntimeInformation.ProcessArchitecture) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + IntPtr intPtr = IntPtr.Zero; + + try + { + intPtr = Marshal.AllocHGlobal(new IntPtr(outerHeader.UncompressedStreamSize)); + + using UnmanagedMemoryStream stream = new((byte*)intPtr.ToPointer(), outerHeader.UncompressedStreamSize, outerHeader.UncompressedStreamSize, FileAccess.ReadWrite); + try + { + deflateStream.CopyTo(stream); + } + catch + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + Debug.Assert(stream.Position == stream.Length); + + stream.Seek(0L, SeekOrigin.Begin); + + InnerHeader innerHeader = DeserializeStructure(stream); + + if (!innerHeader.IsHeaderValid()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (innerHeader.Magic != _innerHeaderMagic) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + ReadOnlySpan infosBytes = new(stream.PositionPointer, innerHeader.InfosLength); + stream.Seek(innerHeader.InfosLength, SeekOrigin.Current); + + Hash128 infosHash = XXHash128.ComputeHash(infosBytes); + + if (innerHeader.InfosHash != infosHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + ReadOnlySpan codesBytes = (int)innerHeader.CodesLength > 0 ? new(stream.PositionPointer, (int)innerHeader.CodesLength) : ReadOnlySpan.Empty; + stream.Seek(innerHeader.CodesLength, SeekOrigin.Current); + + Hash128 codesHash = XXHash128.ComputeHash(codesBytes); + + if (innerHeader.CodesHash != codesHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + ReadOnlySpan relocsBytes = new(stream.PositionPointer, innerHeader.RelocsLength); + stream.Seek(innerHeader.RelocsLength, SeekOrigin.Current); + + Hash128 relocsHash = XXHash128.ComputeHash(relocsBytes); + + if (innerHeader.RelocsHash != relocsHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + ReadOnlySpan unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength); + stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current); + + Hash128 unwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes); + + if (innerHeader.UnwindInfosHash != unwindInfosHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + Debug.Assert(stream.Position == stream.Length); + + stream.Seek((long)Unsafe.SizeOf(), SeekOrigin.Begin); + + _infosStream.Write(infosBytes); + stream.Seek(innerHeader.InfosLength, SeekOrigin.Current); + + _codesList.ReadFrom(stream); + + _relocsStream.Write(relocsBytes); + stream.Seek(innerHeader.RelocsLength, SeekOrigin.Current); + + _unwindInfosStream.Write(unwindInfosBytes); + stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current); + + Debug.Assert(stream.Position == stream.Length); + } + finally + { + if (intPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(intPtr); + } + } + } + + long fileSize = new FileInfo(fileName).Length; + + Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Translation Cache" : "Loaded Translation Cache")} (size: {fileSize} bytes, translated functions: {GetEntriesCount()})."); + + return true; + } + + private static void InvalidateCompressedStream(FileStream compressedStream) + { + compressedStream.SetLength(0L); + } + + private void PreSave() + { + _waitEvent.Reset(); + + try + { + string fileNameActual = $"{CachePathActual}.cache"; + string fileNameBackup = $"{CachePathBackup}.cache"; + + FileInfo fileInfoActual = new(fileNameActual); + + if (fileInfoActual.Exists && fileInfoActual.Length != 0L) + { + File.Copy(fileNameActual, fileNameBackup, true); + } + + Save(fileNameActual); + } + finally + { + ResetCarriersIfNeeded(); + + GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; + } + + _waitEvent.Set(); + } + + private unsafe void Save(string fileName) + { + int translatedFuncsCount; + + InnerHeader innerHeader = new() + { + Magic = _innerHeaderMagic, + + InfosLength = (int)_infosStream.Length, + CodesLength = _codesList.Length(), + RelocsLength = (int)_relocsStream.Length, + UnwindInfosLength = (int)_unwindInfosStream.Length, + }; + + OuterHeader outerHeader = new() + { + Magic = _outerHeaderMagic, + + CacheFileVersion = InternalVersion, + Endianness = GetEndianness(), + FeatureInfo = GetFeatureInfo(), + MemoryManagerMode = GetMemoryManagerMode(), + OSPlatform = GetOSPlatform(), + Architecture = (uint)RuntimeInformation.ProcessArchitecture, + + UncompressedStreamSize = + (long)Unsafe.SizeOf() + + innerHeader.InfosLength + + innerHeader.CodesLength + + innerHeader.RelocsLength + + innerHeader.UnwindInfosLength, + }; + + outerHeader.SetHeaderHash(); + + IntPtr intPtr = IntPtr.Zero; + + try + { + intPtr = Marshal.AllocHGlobal(new IntPtr(outerHeader.UncompressedStreamSize)); + + using UnmanagedMemoryStream stream = new((byte*)intPtr.ToPointer(), outerHeader.UncompressedStreamSize, outerHeader.UncompressedStreamSize, FileAccess.ReadWrite); + stream.Seek((long)Unsafe.SizeOf(), SeekOrigin.Begin); + + ReadOnlySpan infosBytes = new(stream.PositionPointer, innerHeader.InfosLength); + _infosStream.WriteTo(stream); + + ReadOnlySpan codesBytes = (int)innerHeader.CodesLength > 0 ? new(stream.PositionPointer, (int)innerHeader.CodesLength) : ReadOnlySpan.Empty; + _codesList.WriteTo(stream); + + ReadOnlySpan relocsBytes = new(stream.PositionPointer, innerHeader.RelocsLength); + _relocsStream.WriteTo(stream); + + ReadOnlySpan unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength); + _unwindInfosStream.WriteTo(stream); + + Debug.Assert(stream.Position == stream.Length); + + innerHeader.InfosHash = XXHash128.ComputeHash(infosBytes); + innerHeader.CodesHash = XXHash128.ComputeHash(codesBytes); + innerHeader.RelocsHash = XXHash128.ComputeHash(relocsBytes); + innerHeader.UnwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes); + + innerHeader.SetHeaderHash(); + + stream.Seek(0L, SeekOrigin.Begin); + SerializeStructure(stream, innerHeader); + + translatedFuncsCount = GetEntriesCount(); + + ResetCarriersIfNeeded(); + + using FileStream compressedStream = new(fileName, FileMode.OpenOrCreate); + using DeflateStream deflateStream = new(compressedStream, SaveCompressionLevel, true); + try + { + SerializeStructure(compressedStream, outerHeader); + + stream.Seek(0L, SeekOrigin.Begin); + stream.CopyTo(deflateStream); + } + catch + { + compressedStream.Position = 0L; + } + + if (compressedStream.Position < compressedStream.Length) + { + compressedStream.SetLength(compressedStream.Position); + } + } + finally + { + if (intPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(intPtr); + } + } + + long fileSize = new FileInfo(fileName).Length; + + if (fileSize != 0L) + { + Logger.Info?.Print(LogClass.Ptc, $"Saved Translation Cache (size: {fileSize} bytes, translated functions: {translatedFuncsCount})."); + } + } + + public void LoadTranslations(Translator translator) + { + if (AreCarriersEmpty()) + { + return; + } + + long infosStreamLength = _infosStream.Length; + long relocsStreamLength = _relocsStream.Length; + long unwindInfosStreamLength = _unwindInfosStream.Length; + + _infosStream.Seek(0L, SeekOrigin.Begin); + _relocsStream.Seek(0L, SeekOrigin.Begin); + _unwindInfosStream.Seek(0L, SeekOrigin.Begin); + + using (BinaryReader relocsReader = new(_relocsStream, EncodingCache.UTF8NoBOM, true)) + using (BinaryReader unwindInfosReader = new(_unwindInfosStream, EncodingCache.UTF8NoBOM, true)) + { + for (int index = 0; index < GetEntriesCount(); index++) + { + InfoEntry infoEntry = DeserializeStructure(_infosStream); + + if (infoEntry.Stubbed) + { + SkipCode(index, infoEntry.CodeLength); + SkipReloc(infoEntry.RelocEntriesCount); + SkipUnwindInfo(unwindInfosReader); + + continue; + } + + bool isEntryChanged = infoEntry.Hash != ComputeHash(translator.Memory, infoEntry.Address, infoEntry.GuestSize); + + if (isEntryChanged || (!infoEntry.HighCq && Profiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out var value) && value.HighCq)) + { + infoEntry.Stubbed = true; + infoEntry.CodeLength = 0; + UpdateInfo(infoEntry); + + StubCode(index); + StubReloc(infoEntry.RelocEntriesCount); + StubUnwindInfo(unwindInfosReader); + + if (isEntryChanged) + { + Logger.Info?.Print(LogClass.Ptc, $"Invalidated translated function (address: 0x{infoEntry.Address:X16})"); + } + + continue; + } + + byte[] code = ReadCode(index, infoEntry.CodeLength); + + Counter callCounter = null; + + if (infoEntry.RelocEntriesCount != 0) + { + RelocEntry[] relocEntries = GetRelocEntries(relocsReader, infoEntry.RelocEntriesCount); + + PatchCode(translator, code, relocEntries, out callCounter); + } + + UnwindInfo unwindInfo = ReadUnwindInfo(unwindInfosReader); + + TranslatedFunction func = FastTranslate(code, callCounter, infoEntry.GuestSize, unwindInfo, infoEntry.HighCq); + + translator.RegisterFunction(infoEntry.Address, func); + + bool isAddressUnique = translator.Functions.TryAdd(infoEntry.Address, infoEntry.GuestSize, func); + + Debug.Assert(isAddressUnique, $"The address 0x{infoEntry.Address:X16} is not unique."); + } + } + + if (_infosStream.Length != infosStreamLength || _infosStream.Position != infosStreamLength || + _relocsStream.Length != relocsStreamLength || _relocsStream.Position != relocsStreamLength || + _unwindInfosStream.Length != unwindInfosStreamLength || _unwindInfosStream.Position != unwindInfosStreamLength) + { + throw new Exception("The length of a memory stream has changed, or its position has not reached or has exceeded its end."); + } + + Logger.Info?.Print(LogClass.Ptc, $"{translator.Functions.Count} translated functions loaded"); + } + + private int GetEntriesCount() + { + return _codesList.Count; + } + + [Conditional("DEBUG")] + private void SkipCode(int index, int codeLength) + { + Debug.Assert(_codesList[index].Length == 0); + Debug.Assert(codeLength == 0); + } + + private void SkipReloc(int relocEntriesCount) + { + _relocsStream.Seek(relocEntriesCount * RelocEntry.Stride, SeekOrigin.Current); + } + + private void SkipUnwindInfo(BinaryReader unwindInfosReader) + { + int pushEntriesLength = unwindInfosReader.ReadInt32(); + + _unwindInfosStream.Seek(pushEntriesLength * UnwindPushEntry.Stride + UnwindInfo.Stride, SeekOrigin.Current); + } + + private byte[] ReadCode(int index, int codeLength) + { + Debug.Assert(_codesList[index].Length == codeLength); + + return _codesList[index]; + } + + private static RelocEntry[] GetRelocEntries(BinaryReader relocsReader, int relocEntriesCount) + { + RelocEntry[] relocEntries = new RelocEntry[relocEntriesCount]; + + for (int i = 0; i < relocEntriesCount; i++) + { + int position = relocsReader.ReadInt32(); + SymbolType type = (SymbolType)relocsReader.ReadByte(); + ulong value = relocsReader.ReadUInt64(); + + relocEntries[i] = new RelocEntry(position, new Symbol(type, value)); + } + + return relocEntries; + } + + private static void PatchCode(Translator translator, Span code, RelocEntry[] relocEntries, out Counter callCounter) + { + callCounter = null; + + foreach (RelocEntry relocEntry in relocEntries) + { + IntPtr? imm = null; + Symbol symbol = relocEntry.Symbol; + + if (symbol.Type == SymbolType.FunctionTable) + { + ulong guestAddress = symbol.Value; + + if (translator.FunctionTable.IsValid(guestAddress)) + { + unsafe + { + imm = (IntPtr)Unsafe.AsPointer(ref translator.FunctionTable.GetValue(guestAddress)); + } + } + } + else if (symbol.Type == SymbolType.DelegateTable) + { + int index = (int)symbol.Value; + + if (Delegates.TryGetDelegateFuncPtrByIndex(index, out IntPtr funcPtr)) + { + imm = funcPtr; + } + } + else if (symbol == PageTableSymbol) + { + imm = translator.Memory.PageTablePointer; + } + else if (symbol == CountTableSymbol) + { + callCounter ??= new Counter(translator.CountTable); + + unsafe + { + imm = (IntPtr)Unsafe.AsPointer(ref callCounter.Value); + } + } + else if (symbol == DispatchStubSymbol) + { + imm = translator.Stubs.DispatchStub; + } + + if (imm == null) + { + throw new Exception($"Unexpected reloc entry {relocEntry}."); + } + + BinaryPrimitives.WriteUInt64LittleEndian(code.Slice(relocEntry.Position, 8), (ulong)imm.Value); + } + } + + private static UnwindInfo ReadUnwindInfo(BinaryReader unwindInfosReader) + { + int pushEntriesLength = unwindInfosReader.ReadInt32(); + + UnwindPushEntry[] pushEntries = new UnwindPushEntry[pushEntriesLength]; + + for (int i = 0; i < pushEntriesLength; i++) + { + int pseudoOp = unwindInfosReader.ReadInt32(); + int prologOffset = unwindInfosReader.ReadInt32(); + int regIndex = unwindInfosReader.ReadInt32(); + int stackOffsetOrAllocSize = unwindInfosReader.ReadInt32(); + + pushEntries[i] = new UnwindPushEntry((UnwindPseudoOp)pseudoOp, prologOffset, regIndex, stackOffsetOrAllocSize); + } + + int prologueSize = unwindInfosReader.ReadInt32(); + + return new UnwindInfo(pushEntries, prologueSize); + } + + private static TranslatedFunction FastTranslate( + byte[] code, + Counter callCounter, + ulong guestSize, + UnwindInfo unwindInfo, + bool highCq) + { + var cFunc = new CompiledFunction(code, unwindInfo, RelocInfo.Empty); + var gFunc = cFunc.MapWithPointer(out IntPtr gFuncPointer); + + return new TranslatedFunction(gFunc, gFuncPointer, callCounter, guestSize, highCq); + } + + private void UpdateInfo(InfoEntry infoEntry) + { + _infosStream.Seek(-Unsafe.SizeOf(), SeekOrigin.Current); + + SerializeStructure(_infosStream, infoEntry); + } + + private void StubCode(int index) + { + _codesList[index] = Array.Empty(); + } + + private void StubReloc(int relocEntriesCount) + { + for (int i = 0; i < relocEntriesCount * RelocEntry.Stride; i++) + { + _relocsStream.WriteByte(FillingByte); + } + } + + private void StubUnwindInfo(BinaryReader unwindInfosReader) + { + int pushEntriesLength = unwindInfosReader.ReadInt32(); + + for (int i = 0; i < pushEntriesLength * UnwindPushEntry.Stride + UnwindInfo.Stride; i++) + { + _unwindInfosStream.WriteByte(FillingByte); + } + } + + public void MakeAndSaveTranslations(Translator translator) + { + var profiledFuncsToTranslate = Profiler.GetProfiledFuncsToTranslate(translator.Functions); + + _translateCount = 0; + _translateTotalCount = profiledFuncsToTranslate.Count; + + if (_translateTotalCount == 0) + { + ResetCarriersIfNeeded(); + + GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; + + return; + } + + int degreeOfParallelism = Environment.ProcessorCount; + + // If there are enough cores lying around, we leave one alone for other tasks. + if (degreeOfParallelism > 4) + { + degreeOfParallelism--; + } + + Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {_translateTotalCount} functions translated | Thread count: {degreeOfParallelism}"); + + PtcStateChanged?.Invoke(PtcLoadingState.Start, _translateCount, _translateTotalCount); + + using AutoResetEvent progressReportEvent = new(false); + + Thread progressReportThread = new(ReportProgress) + { + Name = "Ptc.ProgressReporter", + Priority = ThreadPriority.Lowest, + IsBackground = true, + }; + + progressReportThread.Start(progressReportEvent); + + void TranslateFuncs() + { + while (profiledFuncsToTranslate.TryDequeue(out var item)) + { + ulong address = item.address; + + Debug.Assert(Profiler.IsAddressInStaticCodeRange(address)); + + TranslatedFunction func = translator.Translate(address, item.funcProfile.Mode, item.funcProfile.HighCq); + + bool isAddressUnique = translator.Functions.TryAdd(address, func.GuestSize, func); + + Debug.Assert(isAddressUnique, $"The address 0x{address:X16} is not unique."); + + Interlocked.Increment(ref _translateCount); + + translator.RegisterFunction(address, func); + + if (State != PtcState.Enabled) + { + break; + } + } + } + + List threads = new(); + + for (int i = 0; i < degreeOfParallelism; i++) + { + Thread thread = new(TranslateFuncs) + { + IsBackground = true, + }; + + threads.Add(thread); + } + + Stopwatch sw = Stopwatch.StartNew(); + + foreach (var thread in threads) + { + thread.Start(); + } + foreach (var thread in threads) + { + thread.Join(); + } + + threads.Clear(); + + progressReportEvent.Set(); + progressReportThread.Join(); + + sw.Stop(); + + PtcStateChanged?.Invoke(PtcLoadingState.Loaded, _translateCount, _translateTotalCount); + + Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {_translateTotalCount} functions translated | Thread count: {degreeOfParallelism} in {sw.Elapsed.TotalSeconds} s"); + + Thread preSaveThread = new(PreSave) + { + IsBackground = true, + }; + preSaveThread.Start(); + } + + private void ReportProgress(object state) + { + const int RefreshRate = 50; // ms. + + AutoResetEvent endEvent = (AutoResetEvent)state; + + int count = 0; + + do + { + int newCount = _translateCount; + + if (count != newCount) + { + PtcStateChanged?.Invoke(PtcLoadingState.Loading, newCount, _translateTotalCount); + count = newCount; + } + } + while (!endEvent.WaitOne(RefreshRate)); + } + + public static Hash128 ComputeHash(IMemoryManager memory, ulong address, ulong guestSize) + { + return XXHash128.ComputeHash(memory.GetSpan(address, checked((int)(guestSize)))); + } + + public void WriteCompiledFunction(ulong address, ulong guestSize, Hash128 hash, bool highCq, CompiledFunction compiledFunc) + { + lock (_lock) + { + byte[] code = compiledFunc.Code; + RelocInfo relocInfo = compiledFunc.RelocInfo; + UnwindInfo unwindInfo = compiledFunc.UnwindInfo; + + InfoEntry infoEntry = new() + { + Address = address, + GuestSize = guestSize, + Hash = hash, + HighCq = highCq, + Stubbed = false, + CodeLength = code.Length, + RelocEntriesCount = relocInfo.Entries.Length, + }; + + SerializeStructure(_infosStream, infoEntry); + + WriteCode(code.AsSpan()); + + // WriteReloc. + using var relocInfoWriter = new BinaryWriter(_relocsStream, EncodingCache.UTF8NoBOM, true); + + foreach (RelocEntry entry in relocInfo.Entries) + { + relocInfoWriter.Write(entry.Position); + relocInfoWriter.Write((byte)entry.Symbol.Type); + relocInfoWriter.Write(entry.Symbol.Value); + } + + // WriteUnwindInfo. + using var unwindInfoWriter = new BinaryWriter(_unwindInfosStream, EncodingCache.UTF8NoBOM, true); + + unwindInfoWriter.Write(unwindInfo.PushEntries.Length); + + foreach (UnwindPushEntry unwindPushEntry in unwindInfo.PushEntries) + { + unwindInfoWriter.Write((int)unwindPushEntry.PseudoOp); + unwindInfoWriter.Write(unwindPushEntry.PrologOffset); + unwindInfoWriter.Write(unwindPushEntry.RegIndex); + unwindInfoWriter.Write(unwindPushEntry.StackOffsetOrAllocSize); + } + + unwindInfoWriter.Write(unwindInfo.PrologSize); + } + } + + private void WriteCode(ReadOnlySpan code) + { + _codesList.Add(code.ToArray()); + } + + public static bool GetEndianness() + { + return BitConverter.IsLittleEndian; + } + + private static FeatureInfo GetFeatureInfo() + { + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + return new FeatureInfo( + (ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap, + (ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2, + (ulong)Arm64HardwareCapabilities.MacOsFeatureInfo, + 0, + 0); + } + else if (RuntimeInformation.ProcessArchitecture == Architecture.X64) + { + return new FeatureInfo( + (ulong)X86HardwareCapabilities.FeatureInfo1Ecx, + (ulong)X86HardwareCapabilities.FeatureInfo1Edx, + (ulong)X86HardwareCapabilities.FeatureInfo7Ebx, + (ulong)X86HardwareCapabilities.FeatureInfo7Ecx, + (ulong)X86HardwareCapabilities.Xcr0InfoEax); + } + else + { + return new FeatureInfo(0, 0, 0, 0, 0); + } + } + + private byte GetMemoryManagerMode() + { + return (byte)_memoryMode; + } + + private static uint GetOSPlatform() + { + uint osPlatform = 0u; + +#pragma warning disable IDE0055 // Disable formatting + osPlatform |= (OperatingSystem.IsFreeBSD() ? 1u : 0u) << 0; + osPlatform |= (OperatingSystem.IsLinux() ? 1u : 0u) << 1; + osPlatform |= (OperatingSystem.IsMacOS() ? 1u : 0u) << 2; + osPlatform |= (OperatingSystem.IsWindows() ? 1u : 0u) << 3; +#pragma warning restore IDE0055 + + return osPlatform; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)] + private struct OuterHeader + { + public ulong Magic; + + public uint CacheFileVersion; + + public bool Endianness; + public FeatureInfo FeatureInfo; + public byte MemoryManagerMode; + public uint OSPlatform; + public uint Architecture; + + public long UncompressedStreamSize; + + public Hash128 HeaderHash; + + public void SetHeaderHash() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf() - Unsafe.SizeOf())]); + } + + public bool IsHeaderValid() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf() - Unsafe.SizeOf())]) == HeaderHash; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 40*/)] + private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3, ulong FeatureInfo4); + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)] + private struct InnerHeader + { + public ulong Magic; + + public int InfosLength; + public long CodesLength; + public int RelocsLength; + public int UnwindInfosLength; + + public Hash128 InfosHash; + public Hash128 CodesHash; + public Hash128 RelocsHash; + public Hash128 UnwindInfosHash; + + public Hash128 HeaderHash; + + public void SetHeaderHash() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf() - Unsafe.SizeOf())]); + } + + public bool IsHeaderValid() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf() - Unsafe.SizeOf())]) == HeaderHash; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 42*/)] + private struct InfoEntry + { + public ulong Address; + public ulong GuestSize; + public Hash128 Hash; + public bool HighCq; + public bool Stubbed; + public int CodeLength; + public int RelocEntriesCount; + } + + private void Enable() + { + State = PtcState.Enabled; + } + + public void Continue() + { + if (State == PtcState.Enabled) + { + State = PtcState.Continuing; + } + } + + public void Close() + { + if (State == PtcState.Enabled || + State == PtcState.Continuing) + { + State = PtcState.Closing; + } + } + + public void Disable() + { + State = PtcState.Disabled; + } + + private void Wait() + { + _waitEvent.WaitOne(); + } + + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + + Wait(); + _waitEvent.Dispose(); + + DisposeCarriers(); + } + } + } +} diff --git a/src/ARMeilleure/Translation/PTC/PtcFormatter.cs b/src/ARMeilleure/Translation/PTC/PtcFormatter.cs new file mode 100644 index 00000000..60953dcd --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/PtcFormatter.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation.PTC +{ + static class PtcFormatter + { + #region "Deserialize" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Dictionary DeserializeDictionary(Stream stream, Func valueFunc) where TKey : struct + { + Dictionary dictionary = new(); + + int count = DeserializeStructure(stream); + + for (int i = 0; i < count; i++) + { + TKey key = DeserializeStructure(stream); + TValue value = valueFunc(stream); + + dictionary.Add(key, value); + } + + return dictionary; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Dictionary DeserializeAndUpdateDictionary(Stream stream, Func valueFunc, Func updateFunc) where TKey : struct + { + Dictionary dictionary = new(); + + int count = DeserializeStructure(stream); + + for (int i = 0; i < count; i++) + { + TKey key = DeserializeStructure(stream); + TValue value = valueFunc(stream); + + (key, value) = updateFunc(key, value); + + dictionary.Add(key, value); + } + + return dictionary; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List DeserializeList(Stream stream) where T : struct + { + List list = new(); + + int count = DeserializeStructure(stream); + + for (int i = 0; i < count; i++) + { + T item = DeserializeStructure(stream); + + list.Add(item); + } + + return list; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T DeserializeStructure(Stream stream) where T : struct + { + T structure = default; + + Span spanT = MemoryMarshal.CreateSpan(ref structure, 1); + int bytesCount = stream.Read(MemoryMarshal.AsBytes(spanT)); + + if (bytesCount != Unsafe.SizeOf()) + { + throw new EndOfStreamException(); + } + + return structure; + } + #endregion + + #region "GetSerializeSize" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetSerializeSizeDictionary(Dictionary dictionary, Func valueFunc) where TKey : struct + { + int size = 0; + + size += Unsafe.SizeOf(); + + foreach ((_, TValue value) in dictionary) + { + size += Unsafe.SizeOf(); + size += valueFunc(value); + } + + return size; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetSerializeSizeList(List list) where T : struct + { + int size = 0; + + size += Unsafe.SizeOf(); + + size += list.Count * Unsafe.SizeOf(); + + return size; + } + #endregion + + #region "Serialize" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializeDictionary(Stream stream, Dictionary dictionary, Action valueAction) where TKey : struct + { + SerializeStructure(stream, dictionary.Count); + + foreach ((TKey key, TValue value) in dictionary) + { + SerializeStructure(stream, key); + valueAction(stream, value); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializeList(Stream stream, List list) where T : struct + { + SerializeStructure(stream, list.Count); + + foreach (T item in list) + { + SerializeStructure(stream, item); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializeStructure(Stream stream, T structure) where T : struct + { + Span spanT = MemoryMarshal.CreateSpan(ref structure, 1); + stream.Write(MemoryMarshal.AsBytes(spanT)); + } + #endregion + + #region "Extension methods" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadFrom(this List list, Stream stream) where T : struct + { + int count = DeserializeStructure(stream); + + for (int i = 0; i < count; i++) + { + int itemLength = DeserializeStructure(stream); + + T[] item = new T[itemLength]; + + int bytesCount = stream.Read(MemoryMarshal.AsBytes(item.AsSpan())); + + if (bytesCount != itemLength) + { + throw new EndOfStreamException(); + } + + list.Add(item); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long Length(this List list) where T : struct + { + long size = 0L; + + size += Unsafe.SizeOf(); + + foreach (T[] item in list) + { + size += Unsafe.SizeOf(); + size += item.Length; + } + + return size; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteTo(this List list, Stream stream) where T : struct + { + SerializeStructure(stream, list.Count); + + foreach (T[] item in list) + { + SerializeStructure(stream, item.Length); + + stream.Write(MemoryMarshal.AsBytes(item.AsSpan())); + } + } + #endregion + } +} diff --git a/src/ARMeilleure/Translation/PTC/PtcLoadingState.cs b/src/ARMeilleure/Translation/PTC/PtcLoadingState.cs new file mode 100644 index 00000000..587be793 --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/PtcLoadingState.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Translation.PTC +{ + public enum PtcLoadingState + { + Start, + Loading, + Loaded, + } +} diff --git a/src/ARMeilleure/Translation/PTC/PtcProfiler.cs b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs new file mode 100644 index 00000000..0fe78eda --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs @@ -0,0 +1,441 @@ +using ARMeilleure.State; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using System; +using System.Buffers.Binary; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using System.Timers; +using static ARMeilleure.Translation.PTC.PtcFormatter; +using Timer = System.Timers.Timer; + +namespace ARMeilleure.Translation.PTC +{ + class PtcProfiler + { + private const string OuterHeaderMagicString = "Pohd\0\0\0\0"; + + private const uint InternalVersion = 5518; //! Not to be incremented manually for each change to the ARMeilleure project. + + private static readonly uint[] _migrateInternalVersions = { + 1866, + }; + + private const int SaveInterval = 30; // Seconds. + + private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest; + + private readonly Ptc _ptc; + + private readonly Timer _timer; + + private readonly ulong _outerHeaderMagic; + + private readonly ManualResetEvent _waitEvent; + + private readonly object _lock; + + private bool _disposed; + + private Hash128 _lastHash; + + public Dictionary ProfiledFuncs { get; private set; } + + public bool Enabled { get; private set; } + + public ulong StaticCodeStart { get; set; } + public ulong StaticCodeSize { get; set; } + + public PtcProfiler(Ptc ptc) + { + _ptc = ptc; + + _timer = new Timer(SaveInterval * 1000d); + _timer.Elapsed += PreSave; + + _outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan()); + + _waitEvent = new ManualResetEvent(true); + + _lock = new object(); + + _disposed = false; + + ProfiledFuncs = new Dictionary(); + + Enabled = false; + } + + public void AddEntry(ulong address, ExecutionMode mode, bool highCq) + { + if (IsAddressInStaticCodeRange(address)) + { + Debug.Assert(!highCq); + + lock (_lock) + { + ProfiledFuncs.TryAdd(address, new FuncProfile(mode, highCq: false)); + } + } + } + + public void UpdateEntry(ulong address, ExecutionMode mode, bool highCq) + { + if (IsAddressInStaticCodeRange(address)) + { + Debug.Assert(highCq); + + lock (_lock) + { + Debug.Assert(ProfiledFuncs.ContainsKey(address)); + + ProfiledFuncs[address] = new FuncProfile(mode, highCq: true); + } + } + } + + public bool IsAddressInStaticCodeRange(ulong address) + { + return address >= StaticCodeStart && address < StaticCodeStart + StaticCodeSize; + } + + public ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(TranslatorCache funcs) + { + var profiledFuncsToTranslate = new ConcurrentQueue<(ulong address, FuncProfile funcProfile)>(); + + foreach (var profiledFunc in ProfiledFuncs) + { + if (!funcs.ContainsKey(profiledFunc.Key)) + { + profiledFuncsToTranslate.Enqueue((profiledFunc.Key, profiledFunc.Value)); + } + } + + return profiledFuncsToTranslate; + } + + public void ClearEntries() + { + ProfiledFuncs.Clear(); + ProfiledFuncs.TrimExcess(); + } + + public void PreLoad() + { + _lastHash = default; + + string fileNameActual = $"{_ptc.CachePathActual}.info"; + string fileNameBackup = $"{_ptc.CachePathBackup}.info"; + + FileInfo fileInfoActual = new(fileNameActual); + FileInfo fileInfoBackup = new(fileNameBackup); + + if (fileInfoActual.Exists && fileInfoActual.Length != 0L) + { + if (!Load(fileNameActual, false)) + { + if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L) + { + Load(fileNameBackup, true); + } + } + } + else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L) + { + Load(fileNameBackup, true); + } + } + + private bool Load(string fileName, bool isBackup) + { + using (FileStream compressedStream = new(fileName, FileMode.Open)) + using (DeflateStream deflateStream = new(compressedStream, CompressionMode.Decompress, true)) + { + OuterHeader outerHeader = DeserializeStructure(compressedStream); + + if (!outerHeader.IsHeaderValid()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Magic != _outerHeaderMagic) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.InfoFileVersion != InternalVersion && !_migrateInternalVersions.Contains(outerHeader.InfoFileVersion)) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Endianness != Ptc.GetEndianness()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L); + + try + { + deflateStream.CopyTo(stream); + } + catch + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + Debug.Assert(stream.Position == stream.Length); + + stream.Seek(0L, SeekOrigin.Begin); + + Hash128 expectedHash = DeserializeStructure(stream); + + Hash128 actualHash = XXHash128.ComputeHash(GetReadOnlySpan(stream)); + + if (actualHash != expectedHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + switch (outerHeader.InfoFileVersion) + { + case InternalVersion: + ProfiledFuncs = Deserialize(stream); + break; + case 1866: + ProfiledFuncs = Deserialize(stream, (address, profile) => (address + 0x500000UL, profile)); + break; + default: + Logger.Error?.Print(LogClass.Ptc, $"No migration path for {nameof(outerHeader.InfoFileVersion)} '{outerHeader.InfoFileVersion}'. Discarding cache."); + InvalidateCompressedStream(compressedStream); + return false; + } + + Debug.Assert(stream.Position == stream.Length); + + _lastHash = actualHash; + } + + long fileSize = new FileInfo(fileName).Length; + + Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Profiling Info" : "Loaded Profiling Info")} (size: {fileSize} bytes, profiled functions: {ProfiledFuncs.Count})."); + + return true; + } + + private static Dictionary Deserialize(Stream stream, Func migrateEntryFunc = null) + { + if (migrateEntryFunc != null) + { + return DeserializeAndUpdateDictionary(stream, DeserializeStructure, migrateEntryFunc); + } + + return DeserializeDictionary(stream, DeserializeStructure); + } + + private static ReadOnlySpan GetReadOnlySpan(MemoryStream memoryStream) + { + return new(memoryStream.GetBuffer(), (int)memoryStream.Position, (int)memoryStream.Length - (int)memoryStream.Position); + } + + private static void InvalidateCompressedStream(FileStream compressedStream) + { + compressedStream.SetLength(0L); + } + + private void PreSave(object source, ElapsedEventArgs e) + { + _waitEvent.Reset(); + + string fileNameActual = $"{_ptc.CachePathActual}.info"; + string fileNameBackup = $"{_ptc.CachePathBackup}.info"; + + FileInfo fileInfoActual = new(fileNameActual); + + if (fileInfoActual.Exists && fileInfoActual.Length != 0L) + { + File.Copy(fileNameActual, fileNameBackup, true); + } + + Save(fileNameActual); + + _waitEvent.Set(); + } + + private void Save(string fileName) + { + int profiledFuncsCount; + + OuterHeader outerHeader = new() + { + Magic = _outerHeaderMagic, + + InfoFileVersion = InternalVersion, + Endianness = Ptc.GetEndianness(), + }; + + outerHeader.SetHeaderHash(); + + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) + { + Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L); + + stream.Seek(Unsafe.SizeOf(), SeekOrigin.Begin); + + lock (_lock) + { + Serialize(stream, ProfiledFuncs); + + profiledFuncsCount = ProfiledFuncs.Count; + } + + Debug.Assert(stream.Position == stream.Length); + + stream.Seek(Unsafe.SizeOf(), SeekOrigin.Begin); + Hash128 hash = XXHash128.ComputeHash(GetReadOnlySpan(stream)); + + stream.Seek(0L, SeekOrigin.Begin); + SerializeStructure(stream, hash); + + if (hash == _lastHash) + { + return; + } + + using FileStream compressedStream = new(fileName, FileMode.OpenOrCreate); + using DeflateStream deflateStream = new(compressedStream, SaveCompressionLevel, true); + try + { + SerializeStructure(compressedStream, outerHeader); + + stream.WriteTo(deflateStream); + + _lastHash = hash; + } + catch + { + compressedStream.Position = 0L; + + _lastHash = default; + } + + if (compressedStream.Position < compressedStream.Length) + { + compressedStream.SetLength(compressedStream.Position); + } + } + + long fileSize = new FileInfo(fileName).Length; + + if (fileSize != 0L) + { + Logger.Info?.Print(LogClass.Ptc, $"Saved Profiling Info (size: {fileSize} bytes, profiled functions: {profiledFuncsCount})."); + } + } + + private static void Serialize(Stream stream, Dictionary profiledFuncs) + { + SerializeDictionary(stream, profiledFuncs, SerializeStructure); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 29*/)] + private struct OuterHeader + { + public ulong Magic; + + public uint InfoFileVersion; + + public bool Endianness; + + public Hash128 HeaderHash; + + public void SetHeaderHash() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf() - Unsafe.SizeOf())]); + } + + public bool IsHeaderValid() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf() - Unsafe.SizeOf())]) == HeaderHash; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 5*/)] + public struct FuncProfile + { + public ExecutionMode Mode; + public bool HighCq; + + public FuncProfile(ExecutionMode mode, bool highCq) + { + Mode = mode; + HighCq = highCq; + } + } + + public void Start() + { + if (_ptc.State == PtcState.Enabled || + _ptc.State == PtcState.Continuing) + { + Enabled = true; + + _timer.Enabled = true; + } + } + + public void Stop() + { + Enabled = false; + + if (!_disposed) + { + _timer.Enabled = false; + } + } + + public void Wait() + { + _waitEvent.WaitOne(); + } + + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + + _timer.Elapsed -= PreSave; + _timer.Dispose(); + + Wait(); + _waitEvent.Dispose(); + } + } + } +} diff --git a/src/ARMeilleure/Translation/PTC/PtcState.cs b/src/ARMeilleure/Translation/PTC/PtcState.cs new file mode 100644 index 00000000..f6692e87 --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/PtcState.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Translation.PTC +{ + enum PtcState + { + Enabled, + Continuing, + Closing, + Disabled, + } +} diff --git a/src/ARMeilleure/Translation/RegisterToLocal.cs b/src/ARMeilleure/Translation/RegisterToLocal.cs new file mode 100644 index 00000000..91372eb0 --- /dev/null +++ b/src/ARMeilleure/Translation/RegisterToLocal.cs @@ -0,0 +1,52 @@ +using ARMeilleure.IntermediateRepresentation; +using System.Collections.Generic; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + static class RegisterToLocal + { + public static void Rename(ControlFlowGraph cfg) + { + Dictionary registerToLocalMap = new(); + + Operand GetLocal(Operand op) + { + Register register = op.GetRegister(); + + if (!registerToLocalMap.TryGetValue(register, out Operand local)) + { + local = Local(op.Type); + + registerToLocalMap.Add(register, local); + } + + return local; + } + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + Operand dest = node.Destination; + + if (dest != default && dest.Kind == OperandKind.Register) + { + node.Destination = GetLocal(dest); + } + + for (int index = 0; index < node.SourcesCount; index++) + { + Operand source = node.GetSource(index); + + if (source.Kind == OperandKind.Register) + { + node.SetSource(index, GetLocal(source)); + } + } + } + } + } + } +} diff --git a/src/ARMeilleure/Translation/RegisterUsage.cs b/src/ARMeilleure/Translation/RegisterUsage.cs new file mode 100644 index 00000000..472b0f67 --- /dev/null +++ b/src/ARMeilleure/Translation/RegisterUsage.cs @@ -0,0 +1,406 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using System; +using System.Numerics; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.Translation +{ + static class RegisterUsage + { + private const int RegsCount = 32; + private const int RegsMask = RegsCount - 1; + + private readonly struct RegisterMask : IEquatable + { + public long IntMask => Mask.GetElement(0); + public long VecMask => Mask.GetElement(1); + + public Vector128 Mask { get; } + + public RegisterMask(Vector128 mask) + { + Mask = mask; + } + + public RegisterMask(long intMask, long vecMask) + { + Mask = Vector128.Create(intMask, vecMask); + } + + public static RegisterMask operator &(RegisterMask x, RegisterMask y) + { + if (Sse2.IsSupported) + { + return new RegisterMask(Sse2.And(x.Mask, y.Mask)); + } + + return new RegisterMask(x.IntMask & y.IntMask, x.VecMask & y.VecMask); + } + + public static RegisterMask operator |(RegisterMask x, RegisterMask y) + { + if (Sse2.IsSupported) + { + return new RegisterMask(Sse2.Or(x.Mask, y.Mask)); + } + + return new RegisterMask(x.IntMask | y.IntMask, x.VecMask | y.VecMask); + } + + public static RegisterMask operator ~(RegisterMask x) + { + if (Sse2.IsSupported) + { + return new RegisterMask(Sse2.AndNot(x.Mask, Vector128.AllBitsSet)); + } + + return new RegisterMask(~x.IntMask, ~x.VecMask); + } + + public static bool operator ==(RegisterMask x, RegisterMask y) + { + return x.Equals(y); + } + + public static bool operator !=(RegisterMask x, RegisterMask y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is RegisterMask regMask && Equals(regMask); + } + + public bool Equals(RegisterMask other) + { + return Mask.Equals(other.Mask); + } + + public override int GetHashCode() + { + return Mask.GetHashCode(); + } + } + + public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode) + { + if (cfg.Entry.Predecessors.Count != 0) + { + // We expect the entry block to have no predecessors. + // This is required because we have a implicit context load at the start of the function, + // but if there is a jump to the start of the function, the context load would trash the modified values. + // Here we insert a new entry block that will jump to the existing entry block. + BasicBlock newEntry = new BasicBlock(cfg.Blocks.Count); + + cfg.UpdateEntry(newEntry); + } + + // Compute local register inputs and outputs used inside blocks. + RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count]; + RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count]; + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + for (int index = 0; index < node.SourcesCount; index++) + { + Operand source = node.GetSource(index); + + if (source.Kind == OperandKind.Register) + { + Register register = source.GetRegister(); + + localInputs[block.Index] |= GetMask(register) & ~localOutputs[block.Index]; + } + } + + if (node.Destination != default && node.Destination.Kind == OperandKind.Register) + { + localOutputs[block.Index] |= GetMask(node.Destination.GetRegister()); + } + } + } + + // Compute global register inputs and outputs used across blocks. + RegisterMask[] globalCmnOutputs = new RegisterMask[cfg.Blocks.Count]; + + RegisterMask[] globalInputs = new RegisterMask[cfg.Blocks.Count]; + RegisterMask[] globalOutputs = new RegisterMask[cfg.Blocks.Count]; + + bool modified; + bool firstPass = true; + + do + { + modified = false; + + // Compute register outputs. + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + if (block.Predecessors.Count != 0 && !HasContextLoad(block)) + { + BasicBlock predecessor = block.Predecessors[0]; + + RegisterMask cmnOutputs = localOutputs[predecessor.Index] | globalCmnOutputs[predecessor.Index]; + RegisterMask outputs = globalOutputs[predecessor.Index]; + + for (int pIndex = 1; pIndex < block.Predecessors.Count; pIndex++) + { + predecessor = block.Predecessors[pIndex]; + + cmnOutputs &= localOutputs[predecessor.Index] | globalCmnOutputs[predecessor.Index]; + outputs |= globalOutputs[predecessor.Index]; + } + + globalInputs[block.Index] |= outputs & ~cmnOutputs; + + if (!firstPass) + { + cmnOutputs &= globalCmnOutputs[block.Index]; + } + + modified |= Exchange(globalCmnOutputs, block.Index, cmnOutputs); + outputs |= localOutputs[block.Index]; + modified |= Exchange(globalOutputs, block.Index, globalOutputs[block.Index] | outputs); + } + else + { + modified |= Exchange(globalOutputs, block.Index, localOutputs[block.Index]); + } + } + + // Compute register inputs. + for (int index = 0; index < cfg.PostOrderBlocks.Length; index++) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + RegisterMask inputs = localInputs[block.Index]; + + for (int i = 0; i < block.SuccessorsCount; i++) + { + inputs |= globalInputs[block.GetSuccessor(i).Index]; + } + + inputs &= ~globalCmnOutputs[block.Index]; + + modified |= Exchange(globalInputs, block.Index, globalInputs[block.Index] | inputs); + } + + firstPass = false; + } + while (modified); + + // Insert load and store context instructions where needed. + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + bool hasContextLoad = HasContextLoad(block); + + if (hasContextLoad) + { + block.Operations.Remove(block.Operations.First); + } + + Operand arg = default; + + // The only block without any predecessor should be the entry block. + // It always needs a context load as it is the first block to run. + if (block == cfg.Entry || hasContextLoad) + { + long vecMask = globalInputs[block.Index].VecMask; + long intMask = globalInputs[block.Index].IntMask; + + if (vecMask != 0 || intMask != 0) + { + arg = Local(OperandType.I64); + + Operation loadArg = block.Operations.AddFirst(Operation(Instruction.LoadArgument, arg, Const(0))); + + LoadLocals(block, vecMask, RegisterType.Vector, mode, loadArg, arg); + LoadLocals(block, intMask, RegisterType.Integer, mode, loadArg, arg); + } + } + + bool hasContextStore = HasContextStore(block); + + if (hasContextStore) + { + block.Operations.Remove(block.Operations.Last); + } + + if (EndsWithReturn(block) || hasContextStore) + { + long vecMask = globalOutputs[block.Index].VecMask; + long intMask = globalOutputs[block.Index].IntMask; + + if (vecMask != 0 || intMask != 0) + { + if (arg == default) + { + arg = Local(OperandType.I64); + + block.Append(Operation(Instruction.LoadArgument, arg, Const(0))); + } + + StoreLocals(block, intMask, RegisterType.Integer, mode, arg); + StoreLocals(block, vecMask, RegisterType.Vector, mode, arg); + } + } + } + } + + private static bool HasContextLoad(BasicBlock block) + { + return StartsWith(block, Instruction.LoadFromContext) && block.Operations.First.SourcesCount == 0; + } + + private static bool HasContextStore(BasicBlock block) + { + return EndsWith(block, Instruction.StoreToContext) && block.Operations.Last.SourcesCount == 0; + } + + private static bool StartsWith(BasicBlock block, Instruction inst) + { + if (block.Operations.Count > 0) + { + Operation first = block.Operations.First; + + return first != default && first.Instruction == inst; + } + + return false; + } + + private static bool EndsWith(BasicBlock block, Instruction inst) + { + if (block.Operations.Count > 0) + { + Operation last = block.Operations.Last; + + return last != default && last.Instruction == inst; + } + + return false; + } + + private static RegisterMask GetMask(Register register) + { + long intMask = 0; + long vecMask = 0; + + switch (register.Type) + { +#pragma warning disable IDE0055 // Disable formatting + case RegisterType.Flag: intMask = (1L << RegsCount) << register.Index; break; + case RegisterType.Integer: intMask = 1L << register.Index; break; + case RegisterType.FpFlag: vecMask = (1L << RegsCount) << register.Index; break; + case RegisterType.Vector: vecMask = 1L << register.Index; break; +#pragma warning restore IDE0055 + } + + return new RegisterMask(intMask, vecMask); + } + + private static bool Exchange(RegisterMask[] masks, int blkIndex, RegisterMask value) + { + ref RegisterMask curValue = ref masks[blkIndex]; + + bool changed = curValue != value; + + curValue = value; + + return changed; + } + + private static void LoadLocals( + BasicBlock block, + long inputs, + RegisterType baseType, + ExecutionMode mode, + Operation loadArg, + Operand arg) + { + while (inputs != 0) + { + int bit = 63 - BitOperations.LeadingZeroCount((ulong)inputs); + + Operand dest = GetRegFromBit(bit, baseType, mode); + Operand offset = Const((long)NativeContext.GetRegisterOffset(dest.GetRegister())); + Operand addr = Local(OperandType.I64); + + block.Operations.AddAfter(loadArg, Operation(Instruction.Load, dest, addr)); + block.Operations.AddAfter(loadArg, Operation(Instruction.Add, addr, arg, offset)); + + inputs &= ~(1L << bit); + } + } + + private static void StoreLocals( + BasicBlock block, + long outputs, + RegisterType baseType, + ExecutionMode mode, + Operand arg) + { + while (outputs != 0) + { + int bit = BitOperations.TrailingZeroCount(outputs); + + Operand source = GetRegFromBit(bit, baseType, mode); + Operand offset = Const((long)NativeContext.GetRegisterOffset(source.GetRegister())); + Operand addr = Local(OperandType.I64); + + block.Append(Operation(Instruction.Add, addr, arg, offset)); + block.Append(Operation(Instruction.Store, default, addr, source)); + + outputs &= ~(1L << bit); + } + } + + private static Operand GetRegFromBit(int bit, RegisterType baseType, ExecutionMode mode) + { + if (bit < RegsCount) + { + return Register(bit, baseType, GetOperandType(baseType, mode)); + } + else if (baseType == RegisterType.Integer) + { + return Register(bit & RegsMask, RegisterType.Flag, OperandType.I32); + } + else if (baseType == RegisterType.Vector) + { + return Register(bit & RegsMask, RegisterType.FpFlag, OperandType.I32); + } + else + { + throw new ArgumentOutOfRangeException(nameof(bit)); + } + } + + private static OperandType GetOperandType(RegisterType type, ExecutionMode mode) + { + return type switch + { + RegisterType.Flag => OperandType.I32, + RegisterType.FpFlag => OperandType.I32, + RegisterType.Integer => (mode == ExecutionMode.Aarch64) ? OperandType.I64 : OperandType.I32, + RegisterType.Vector => OperandType.V128, + _ => throw new ArgumentException($"Invalid register type \"{type}\"."), + }; + } + + private static bool EndsWithReturn(BasicBlock block) + { + Operation last = block.Operations.Last; + + return last != default && last.Instruction == Instruction.Return; + } + } +} diff --git a/src/ARMeilleure/Translation/RejitRequest.cs b/src/ARMeilleure/Translation/RejitRequest.cs new file mode 100644 index 00000000..1bed5c0a --- /dev/null +++ b/src/ARMeilleure/Translation/RejitRequest.cs @@ -0,0 +1,16 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Translation +{ + struct RejitRequest + { + public ulong Address; + public ExecutionMode Mode; + + public RejitRequest(ulong address, ExecutionMode mode) + { + Address = address; + Mode = mode; + } + } +} diff --git a/src/ARMeilleure/Translation/SsaConstruction.cs b/src/ARMeilleure/Translation/SsaConstruction.cs new file mode 100644 index 00000000..cddcfcd4 --- /dev/null +++ b/src/ARMeilleure/Translation/SsaConstruction.cs @@ -0,0 +1,289 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + static partial class Ssa + { + private class DefMap + { + private readonly Dictionary _map; + private readonly BitMap _phiMasks; + + public DefMap() + { + _map = new Dictionary(); + _phiMasks = new BitMap(Allocators.Default, RegisterConsts.TotalCount); + } + + public bool TryAddOperand(int key, Operand operand) + { + return _map.TryAdd(key, operand); + } + + public bool TryGetOperand(int key, out Operand operand) + { + return _map.TryGetValue(key, out operand); + } + + public bool AddPhi(int key) + { + return _phiMasks.Set(key); + } + + public bool HasPhi(int key) + { + return _phiMasks.IsSet(key); + } + } + + public static void Construct(ControlFlowGraph cfg) + { + var globalDefs = new DefMap[cfg.Blocks.Count]; + var localDefs = new Operand[cfg.LocalsCount + RegisterConsts.TotalCount]; + + var dfPhiBlocks = new Queue(); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + globalDefs[block.Index] = new DefMap(); + } + + // First pass, get all defs and locals uses. + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + for (int index = 0; index < node.SourcesCount; index++) + { + Operand src = node.GetSource(index); + + if (TryGetId(src, out int srcKey)) + { + Operand local = localDefs[srcKey]; + + if (local == default) + { + local = src; + } + + node.SetSource(index, local); + } + } + + Operand dest = node.Destination; + + if (TryGetId(dest, out int destKey)) + { + Operand local = Local(dest.Type); + + localDefs[destKey] = local; + + node.Destination = local; + } + } + + for (int key = 0; key < localDefs.Length; key++) + { + Operand local = localDefs[key]; + + if (local == default) + { + continue; + } + + globalDefs[block.Index].TryAddOperand(key, local); + + dfPhiBlocks.Enqueue(block); + + while (dfPhiBlocks.TryDequeue(out BasicBlock dfPhiBlock)) + { + foreach (BasicBlock domFrontier in dfPhiBlock.DominanceFrontiers) + { + if (globalDefs[domFrontier.Index].AddPhi(key)) + { + dfPhiBlocks.Enqueue(domFrontier); + } + } + } + } + + Array.Clear(localDefs); + } + + // Second pass, rename variables with definitions on different blocks. + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + for (int index = 0; index < node.SourcesCount; index++) + { + Operand src = node.GetSource(index); + + if (TryGetId(src, out int key)) + { + Operand local = localDefs[key]; + + if (local == default) + { + local = FindDef(globalDefs, block, src); + localDefs[key] = local; + } + + node.SetSource(index, local); + } + } + } + + Array.Clear(localDefs); + } + } + + private static Operand FindDef(DefMap[] globalDefs, BasicBlock current, Operand operand) + { + if (globalDefs[current.Index].HasPhi(GetId(operand))) + { + return InsertPhi(globalDefs, current, operand); + } + + if (current != current.ImmediateDominator) + { + return FindDefOnPred(globalDefs, current.ImmediateDominator, operand); + } + + return Undef(); + } + + private static Operand FindDefOnPred(DefMap[] globalDefs, BasicBlock current, Operand operand) + { + BasicBlock previous; + + do + { + DefMap defMap = globalDefs[current.Index]; + + int key = GetId(operand); + + if (defMap.TryGetOperand(key, out Operand lastDef)) + { + return lastDef; + } + + if (defMap.HasPhi(key)) + { + return InsertPhi(globalDefs, current, operand); + } + + previous = current; + current = current.ImmediateDominator; + } + while (previous != current); + + return Undef(); + } + + private static Operand InsertPhi(DefMap[] globalDefs, BasicBlock block, Operand operand) + { + // This block has a Phi that has not been materialized yet, but that + // would define a new version of the variable we're looking for. We need + // to materialize the Phi, add all the block/operand pairs into the Phi, and + // then use the definition from that Phi. + Operand local = Local(operand.Type); + + Operation operation = Operation.Factory.PhiOperation(local, block.Predecessors.Count); + + AddPhi(block, operation); + + globalDefs[block.Index].TryAddOperand(GetId(operand), local); + + PhiOperation phi = operation.AsPhi(); + + for (int index = 0; index < block.Predecessors.Count; index++) + { + BasicBlock predecessor = block.Predecessors[index]; + + phi.SetBlock(index, predecessor); + phi.SetSource(index, FindDefOnPred(globalDefs, predecessor, operand)); + } + + return local; + } + + private static void AddPhi(BasicBlock block, Operation phi) + { + Operation node = block.Operations.First; + + if (node != default) + { + while (node.ListNext != default && node.ListNext.Instruction == Instruction.Phi) + { + node = node.ListNext; + } + } + + if (node != default && node.Instruction == Instruction.Phi) + { + block.Operations.AddAfter(node, phi); + } + else + { + block.Operations.AddFirst(phi); + } + } + + private static bool TryGetId(Operand operand, out int result) + { + if (operand != default) + { + if (operand.Kind == OperandKind.Register) + { + Register reg = operand.GetRegister(); + + if (reg.Type == RegisterType.Integer) + { + result = reg.Index; + } + else if (reg.Type == RegisterType.Vector) + { + result = RegisterConsts.IntRegsCount + reg.Index; + } + else if (reg.Type == RegisterType.Flag) + { + result = RegisterConsts.IntAndVecRegsCount + reg.Index; + } + else /* if (reg.Type == RegisterType.FpFlag) */ + { + result = RegisterConsts.FpFlagsOffset + reg.Index; + } + + return true; + } + else if (operand.Kind == OperandKind.LocalVariable && operand.GetLocalNumber() > 0) + { + result = RegisterConsts.TotalCount + operand.GetLocalNumber() - 1; + + return true; + } + } + + result = -1; + + return false; + } + + private static int GetId(Operand operand) + { + if (!TryGetId(operand, out int key)) + { + Debug.Fail("OperandKind must be Register or a numbered LocalVariable."); + } + + return key; + } + } +} diff --git a/src/ARMeilleure/Translation/SsaDeconstruction.cs b/src/ARMeilleure/Translation/SsaDeconstruction.cs new file mode 100644 index 00000000..68af54e5 --- /dev/null +++ b/src/ARMeilleure/Translation/SsaDeconstruction.cs @@ -0,0 +1,48 @@ +using ARMeilleure.IntermediateRepresentation; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.Translation +{ + static partial class Ssa + { + public static void Deconstruct(ControlFlowGraph cfg) + { + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + Operation operation = block.Operations.First; + + while (operation != default && operation.Instruction == Instruction.Phi) + { + Operation nextNode = operation.ListNext; + + Operand local = Local(operation.Destination.Type); + + PhiOperation phi = operation.AsPhi(); + + for (int index = 0; index < phi.SourcesCount; index++) + { + BasicBlock predecessor = phi.GetBlock(cfg, index); + + Operand source = phi.GetSource(index); + + predecessor.Append(Operation(Instruction.Copy, local, source)); + + phi.SetSource(index, default); + } + + Operation copyOp = Operation(Instruction.Copy, operation.Destination, local); + + block.Operations.AddBefore(operation, copyOp); + + operation.Destination = default; + + block.Operations.Remove(operation); + + operation = nextNode; + } + } + } + } +} diff --git a/src/ARMeilleure/Translation/TranslatedFunction.cs b/src/ARMeilleure/Translation/TranslatedFunction.cs new file mode 100644 index 00000000..1446c254 --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatedFunction.cs @@ -0,0 +1,34 @@ +using ARMeilleure.Common; +using System; + +namespace ARMeilleure.Translation +{ + class TranslatedFunction + { + private readonly GuestFunction _func; // Ensure that this delegate will not be garbage collected. + + public IntPtr FuncPointer { get; } + public Counter CallCounter { get; } + public ulong GuestSize { get; } + public bool HighCq { get; } + + public TranslatedFunction(GuestFunction func, IntPtr funcPointer, Counter callCounter, ulong guestSize, bool highCq) + { + _func = func; + FuncPointer = funcPointer; + CallCounter = callCounter; + GuestSize = guestSize; + HighCq = highCq; + } + + public ulong Execute(State.ExecutionContext context) + { + return _func(context.NativeContextPtr); + } + + public ulong Execute(WrapperFunction dispatcher, State.ExecutionContext context) + { + return dispatcher(context.NativeContextPtr, (ulong)FuncPointer); + } + } +} diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs new file mode 100644 index 00000000..014b1203 --- /dev/null +++ b/src/ARMeilleure/Translation/Translator.cs @@ -0,0 +1,582 @@ +using ARMeilleure.CodeGen; +using ARMeilleure.Common; +using ARMeilleure.Decoders; +using ARMeilleure.Diagnostics; +using ARMeilleure.Instructions; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using ARMeilleure.Signal; +using ARMeilleure.State; +using ARMeilleure.Translation.Cache; +using ARMeilleure.Translation.PTC; +using Ryujinx.Common; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + public class Translator + { + private static readonly AddressTable.Level[] _levels64Bit = + new AddressTable.Level[] + { + new(31, 17), + new(23, 8), + new(15, 8), + new( 7, 8), + new( 2, 5), + }; + + private static readonly AddressTable.Level[] _levels32Bit = + new AddressTable.Level[] + { + new(31, 17), + new(23, 8), + new(15, 8), + new( 7, 8), + new( 1, 6), + }; + + private readonly IJitMemoryAllocator _allocator; + private readonly ConcurrentQueue> _oldFuncs; + + private readonly Ptc _ptc; + + internal TranslatorCache Functions { get; } + internal AddressTable FunctionTable { get; } + internal EntryTable CountTable { get; } + internal TranslatorStubs Stubs { get; } + internal TranslatorQueue Queue { get; } + internal IMemoryManager Memory { get; } + + private Thread[] _backgroundTranslationThreads; + private volatile int _threadCount; + + public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits) + { + _allocator = allocator; + Memory = memory; + + _oldFuncs = new ConcurrentQueue>(); + + _ptc = new Ptc(); + + Queue = new TranslatorQueue(); + + JitCache.Initialize(allocator); + + CountTable = new EntryTable(); + Functions = new TranslatorCache(); + FunctionTable = new AddressTable(for64Bits ? _levels64Bit : _levels32Bit); + Stubs = new TranslatorStubs(FunctionTable); + + FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; + } + + public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled) + { + _ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type); + return _ptc; + } + + public void PrepareCodeRange(ulong address, ulong size) + { + if (_ptc.Profiler.StaticCodeSize == 0) + { + _ptc.Profiler.StaticCodeStart = address; + _ptc.Profiler.StaticCodeSize = size; + } + } + + public void Execute(State.ExecutionContext context, ulong address) + { + if (Interlocked.Increment(ref _threadCount) == 1) + { + if (_ptc.State == PtcState.Enabled) + { + Debug.Assert(Functions.Count == 0); + _ptc.LoadTranslations(this); + _ptc.MakeAndSaveTranslations(this); + } + + _ptc.Profiler.Start(); + + _ptc.Disable(); + + // Simple heuristic, should be user configurable in future. (1 for 4 core/ht or less, 2 for 6 core + ht + // etc). All threads are normal priority except from the last, which just fills as much of the last core + // as the os lets it with a low priority. If we only have one rejit thread, it should be normal priority + // as highCq code is performance critical. + // + // TODO: Use physical cores rather than logical. This only really makes sense for processors with + // hyperthreading. Requires OS specific code. + int unboundedThreadCount = Math.Max(1, (Environment.ProcessorCount - 6) / 3); + int threadCount = Math.Min(4, unboundedThreadCount); + + Thread[] backgroundTranslationThreads = new Thread[threadCount]; + + for (int i = 0; i < threadCount; i++) + { + bool last = i != 0 && i == unboundedThreadCount - 1; + + backgroundTranslationThreads[i] = new(BackgroundTranslate) + { + Name = "CPU.BackgroundTranslatorThread." + i, + Priority = last ? ThreadPriority.Lowest : ThreadPriority.Normal, + }; + + backgroundTranslationThreads[i].Start(); + } + + Interlocked.Exchange(ref _backgroundTranslationThreads, backgroundTranslationThreads); + } + + Statistics.InitializeTimer(); + + NativeInterface.RegisterThread(context, Memory, this); + + if (Optimizations.UseUnmanagedDispatchLoop) + { + Stubs.DispatchLoop(context.NativeContextPtr, address); + } + else + { + do + { + address = ExecuteSingle(context, address); + } + while (context.Running && address != 0); + } + + NativeInterface.UnregisterThread(); + + if (Interlocked.Decrement(ref _threadCount) == 0) + { + Queue.Dispose(); + + Thread[] backgroundTranslationThreads = Interlocked.Exchange(ref _backgroundTranslationThreads, null); + + if (backgroundTranslationThreads != null) + { + foreach (Thread thread in backgroundTranslationThreads) + { + thread.Join(); + } + } + + ClearJitCache(); + + Stubs.Dispose(); + FunctionTable.Dispose(); + CountTable.Dispose(); + + _ptc.Close(); + _ptc.Profiler.Stop(); + + _ptc.Dispose(); + _ptc.Profiler.Dispose(); + } + } + + private ulong ExecuteSingle(State.ExecutionContext context, ulong address) + { + TranslatedFunction func = GetOrTranslate(address, context.ExecutionMode); + + Statistics.StartTimer(); + + ulong nextAddr = func.Execute(Stubs.ContextWrapper, context); + + Statistics.StopTimer(address); + + return nextAddr; + } + + public ulong Step(State.ExecutionContext context, ulong address) + { + TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true); + + address = func.Execute(Stubs.ContextWrapper, context); + + EnqueueForDeletion(address, func); + + return address; + } + + internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode) + { + if (!Functions.TryGetValue(address, out TranslatedFunction func)) + { + func = Translate(address, mode, highCq: false); + + TranslatedFunction oldFunc = Functions.GetOrAdd(address, func.GuestSize, func); + + if (oldFunc != func) + { + JitCache.Unmap(func.FuncPointer); + func = oldFunc; + } + + if (_ptc.Profiler.Enabled) + { + _ptc.Profiler.AddEntry(address, mode, highCq: false); + } + + RegisterFunction(address, func); + } + + return func; + } + + internal void RegisterFunction(ulong guestAddress, TranslatedFunction func) + { + if (FunctionTable.IsValid(guestAddress) && (Optimizations.AllowLcqInFunctionTable || func.HighCq)) + { + Volatile.Write(ref FunctionTable.GetValue(guestAddress), (ulong)func.FuncPointer); + } + } + + internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false) + { + var context = new ArmEmitterContext( + Memory, + CountTable, + FunctionTable, + Stubs, + address, + highCq, + _ptc.State != PtcState.Disabled, + mode: Aarch32Mode.User); + + Logger.StartPass(PassName.Decoding); + + Block[] blocks = Decoder.Decode(Memory, address, mode, highCq, singleStep ? DecoderMode.SingleInstruction : DecoderMode.MultipleBlocks); + + Logger.EndPass(PassName.Decoding); + + Logger.StartPass(PassName.Translation); + + EmitSynchronization(context); + + if (blocks[0].Address != address) + { + context.Branch(context.GetLabel(address)); + } + + ControlFlowGraph cfg = EmitAndGetCFG(context, blocks, out Range funcRange, out Counter counter); + + ulong funcSize = funcRange.End - funcRange.Start; + + Logger.EndPass(PassName.Translation, cfg); + + Logger.StartPass(PassName.RegisterUsage); + + RegisterUsage.RunPass(cfg, mode); + + Logger.EndPass(PassName.RegisterUsage); + + var retType = OperandType.I64; + var argTypes = new OperandType[] { OperandType.I64 }; + + var options = highCq ? CompilerOptions.HighCq : CompilerOptions.None; + + if (context.HasPtc && !singleStep) + { + options |= CompilerOptions.Relocatable; + } + + CompiledFunction compiledFunc = Compiler.Compile(cfg, argTypes, retType, options, RuntimeInformation.ProcessArchitecture); + + if (context.HasPtc && !singleStep) + { + Hash128 hash = Ptc.ComputeHash(Memory, address, funcSize); + + _ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc); + } + + GuestFunction func = compiledFunc.MapWithPointer(out IntPtr funcPointer); + + Allocators.ResetAll(); + + return new TranslatedFunction(func, funcPointer, counter, funcSize, highCq); + } + + private void BackgroundTranslate() + { + while (_threadCount != 0 && Queue.TryDequeue(out RejitRequest request)) + { + TranslatedFunction func = Translate(request.Address, request.Mode, highCq: true); + + Functions.AddOrUpdate(request.Address, func.GuestSize, func, (key, oldFunc) => + { + EnqueueForDeletion(key, oldFunc); + return func; + }); + + if (_ptc.Profiler.Enabled) + { + _ptc.Profiler.UpdateEntry(request.Address, request.Mode, highCq: true); + } + + RegisterFunction(request.Address, func); + } + } + + private readonly struct Range + { + public ulong Start { get; } + public ulong End { get; } + + public Range(ulong start, ulong end) + { + Start = start; + End = end; + } + } + + private static ControlFlowGraph EmitAndGetCFG( + ArmEmitterContext context, + Block[] blocks, + out Range range, + out Counter counter) + { + counter = null; + + ulong rangeStart = ulong.MaxValue; + ulong rangeEnd = 0; + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + Block block = blocks[blkIndex]; + + if (!block.Exit) + { + if (rangeStart > block.Address) + { + rangeStart = block.Address; + } + + if (rangeEnd < block.EndAddress) + { + rangeEnd = block.EndAddress; + } + } + + if (block.Address == context.EntryAddress) + { + if (!context.HighCq) + { + EmitRejitCheck(context, out counter); + } + + context.ClearQcFlag(); + } + + context.CurrBlock = block; + + context.MarkLabel(context.GetLabel(block.Address)); + + if (block.Exit) + { + // Left option here as it may be useful if we need to return to managed rather than tail call in + // future. (eg. for debug) + bool useReturns = false; + + InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns); + } + else + { + for (int opcIndex = 0; opcIndex < block.OpCodes.Count; opcIndex++) + { + OpCode opCode = block.OpCodes[opcIndex]; + + context.CurrOp = opCode; + + bool isLastOp = opcIndex == block.OpCodes.Count - 1; + + if (isLastOp) + { + context.SyncQcFlag(); + + if (block.Branch != null && !block.Branch.Exit && block.Branch.Address <= block.Address) + { + EmitSynchronization(context); + } + } + + Operand lblPredicateSkip = default; + + if (context.IsInIfThenBlock && context.CurrentIfThenBlockCond != Condition.Al) + { + lblPredicateSkip = Label(); + + InstEmitFlowHelper.EmitCondBranch(context, lblPredicateSkip, context.CurrentIfThenBlockCond.Invert()); + } + + if (opCode is OpCode32 op && op.Cond < Condition.Al) + { + lblPredicateSkip = Label(); + + InstEmitFlowHelper.EmitCondBranch(context, lblPredicateSkip, op.Cond.Invert()); + } + + if (opCode.Instruction.Emitter != null) + { + opCode.Instruction.Emitter(context); + } + else + { + throw new InvalidOperationException($"Invalid instruction \"{opCode.Instruction.Name}\"."); + } + + if (lblPredicateSkip != default) + { + context.MarkLabel(lblPredicateSkip); + } + + if (context.IsInIfThenBlock && opCode.Instruction.Name != InstName.It) + { + context.AdvanceIfThenBlockState(); + } + } + } + } + + range = new Range(rangeStart, rangeEnd); + + return context.GetControlFlowGraph(); + } + + internal static void EmitRejitCheck(ArmEmitterContext context, out Counter counter) + { + const int MinsCallForRejit = 100; + + counter = new Counter(context.CountTable); + + Operand lblEnd = Label(); + + Operand address = !context.HasPtc ? + Const(ref counter.Value) : + Const(ref counter.Value, Ptc.CountTableSymbol); + + Operand curCount = context.Load(OperandType.I32, address); + Operand count = context.Add(curCount, Const(1)); + context.Store(address, count); + context.BranchIf(lblEnd, curCount, Const(MinsCallForRejit), Comparison.NotEqual, BasicBlockFrequency.Cold); + + context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.EnqueueForRejit)), Const(context.EntryAddress)); + + context.MarkLabel(lblEnd); + } + + internal static void EmitSynchronization(EmitterContext context) + { + long countOffs = NativeContext.GetCounterOffset(); + + Operand lblNonZero = Label(); + Operand lblExit = Label(); + + Operand countAddr = context.Add(context.LoadArgument(OperandType.I64, 0), Const(countOffs)); + Operand count = context.Load(OperandType.I32, countAddr); + context.BranchIfTrue(lblNonZero, count, BasicBlockFrequency.Cold); + + Operand running = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.CheckSynchronization))); + context.BranchIfTrue(lblExit, running, BasicBlockFrequency.Cold); + + context.Return(Const(0L)); + + context.MarkLabel(lblNonZero); + count = context.Subtract(count, Const(1)); + context.Store(countAddr, count); + + context.MarkLabel(lblExit); + } + + public void InvalidateJitCacheRegion(ulong address, ulong size) + { + ulong[] overlapAddresses = Array.Empty(); + + int overlapsCount = Functions.GetOverlaps(address, size, ref overlapAddresses); + + if (overlapsCount != 0) + { + // If rejit is running, stop it as it may be trying to rejit a function on the invalidated region. + ClearRejitQueue(allowRequeue: true); + } + + for (int index = 0; index < overlapsCount; index++) + { + ulong overlapAddress = overlapAddresses[index]; + + if (Functions.TryGetValue(overlapAddress, out TranslatedFunction overlap)) + { + Functions.Remove(overlapAddress); + Volatile.Write(ref FunctionTable.GetValue(overlapAddress), FunctionTable.Fill); + EnqueueForDeletion(overlapAddress, overlap); + } + } + + // TODO: Remove overlapping functions from the JitCache aswell. + // This should be done safely, with a mechanism to ensure the function is not being executed. + } + + internal void EnqueueForRejit(ulong guestAddress, ExecutionMode mode) + { + Queue.Enqueue(guestAddress, mode); + } + + private void EnqueueForDeletion(ulong guestAddress, TranslatedFunction func) + { + _oldFuncs.Enqueue(new(guestAddress, func)); + } + + private void ClearJitCache() + { + // Ensure no attempt will be made to compile new functions due to rejit. + ClearRejitQueue(allowRequeue: false); + + List functions = Functions.AsList(); + + foreach (var func in functions) + { + JitCache.Unmap(func.FuncPointer); + + func.CallCounter?.Dispose(); + } + + Functions.Clear(); + + while (_oldFuncs.TryDequeue(out var kv)) + { + JitCache.Unmap(kv.Value.FuncPointer); + + kv.Value.CallCounter?.Dispose(); + } + } + + private void ClearRejitQueue(bool allowRequeue) + { + if (!allowRequeue) + { + Queue.Clear(); + + return; + } + + lock (Queue.Sync) + { + while (Queue.Count > 0 && Queue.TryDequeue(out RejitRequest request)) + { + if (Functions.TryGetValue(request.Address, out var func) && func.CallCounter != null) + { + Volatile.Write(ref func.CallCounter.Value, 0); + } + } + } + } + } +} diff --git a/src/ARMeilleure/Translation/TranslatorCache.cs b/src/ARMeilleure/Translation/TranslatorCache.cs new file mode 100644 index 00000000..99ca58dc --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatorCache.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace ARMeilleure.Translation +{ + internal class TranslatorCache + { + private readonly IntervalTree _tree; + private readonly ReaderWriterLockSlim _treeLock; + + public int Count => _tree.Count; + + public TranslatorCache() + { + _tree = new IntervalTree(); + _treeLock = new ReaderWriterLockSlim(); + } + + public bool TryAdd(ulong address, ulong size, T value) + { + return AddOrUpdate(address, size, value, null); + } + + public bool AddOrUpdate(ulong address, ulong size, T value, Func updateFactoryCallback) + { + _treeLock.EnterWriteLock(); + bool result = _tree.AddOrUpdate(address, address + size, value, updateFactoryCallback); + _treeLock.ExitWriteLock(); + + return result; + } + + public T GetOrAdd(ulong address, ulong size, T value) + { + _treeLock.EnterWriteLock(); + value = _tree.GetOrAdd(address, address + size, value); + _treeLock.ExitWriteLock(); + + return value; + } + + public bool Remove(ulong address) + { + _treeLock.EnterWriteLock(); + bool removed = _tree.Remove(address) != 0; + _treeLock.ExitWriteLock(); + + return removed; + } + + public void Clear() + { + _treeLock.EnterWriteLock(); + _tree.Clear(); + _treeLock.ExitWriteLock(); + } + + public bool ContainsKey(ulong address) + { + _treeLock.EnterReadLock(); + bool result = _tree.ContainsKey(address); + _treeLock.ExitReadLock(); + + return result; + } + + public bool TryGetValue(ulong address, out T value) + { + _treeLock.EnterReadLock(); + bool result = _tree.TryGet(address, out value); + _treeLock.ExitReadLock(); + + return result; + } + + public int GetOverlaps(ulong address, ulong size, ref ulong[] overlaps) + { + _treeLock.EnterReadLock(); + int count = _tree.Get(address, address + size, ref overlaps); + _treeLock.ExitReadLock(); + + return count; + } + + public List AsList() + { + _treeLock.EnterReadLock(); + List list = _tree.AsList(); + _treeLock.ExitReadLock(); + + return list; + } + } +} diff --git a/src/ARMeilleure/Translation/TranslatorQueue.cs b/src/ARMeilleure/Translation/TranslatorQueue.cs new file mode 100644 index 00000000..831522bc --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatorQueue.cs @@ -0,0 +1,124 @@ +using ARMeilleure.Diagnostics; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace ARMeilleure.Translation +{ + /// + /// Represents a queue of . + /// + /// + /// This does not necessarily behave like a queue, i.e: a FIFO collection. + /// + sealed class TranslatorQueue : IDisposable + { + private bool _disposed; + private readonly Stack _requests; + private readonly HashSet _requestAddresses; + + /// + /// Gets the object used to synchronize access to the . + /// + public object Sync { get; } + + /// + /// Gets the number of requests in the . + /// + public int Count => _requests.Count; + + /// + /// Initializes a new instance of the class. + /// + public TranslatorQueue() + { + Sync = new object(); + + _requests = new Stack(); + _requestAddresses = new HashSet(); + } + + /// + /// Enqueues a request with the specified and . + /// + /// Address of request + /// of request + public void Enqueue(ulong address, ExecutionMode mode) + { + lock (Sync) + { + if (_requestAddresses.Add(address)) + { + _requests.Push(new RejitRequest(address, mode)); + + TranslatorEventSource.Log.RejitQueueAdd(1); + + Monitor.Pulse(Sync); + } + } + } + + /// + /// Tries to dequeue a . This will block the thread until a + /// is enqueued or the is disposed. + /// + /// dequeued + /// on success; otherwise + public bool TryDequeue(out RejitRequest result) + { + while (!_disposed) + { + lock (Sync) + { + if (_requests.TryPop(out result)) + { + _requestAddresses.Remove(result.Address); + + TranslatorEventSource.Log.RejitQueueAdd(-1); + + return true; + } + + if (!_disposed) + { + Monitor.Wait(Sync); + } + } + } + + result = default; + + return false; + } + + /// + /// Clears the . + /// + public void Clear() + { + lock (Sync) + { + TranslatorEventSource.Log.RejitQueueAdd(-_requests.Count); + + _requests.Clear(); + _requestAddresses.Clear(); + + Monitor.PulseAll(Sync); + } + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + + Clear(); + } + } + } +} diff --git a/src/ARMeilleure/Translation/TranslatorStubs.cs b/src/ARMeilleure/Translation/TranslatorStubs.cs new file mode 100644 index 00000000..d80823a8 --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatorStubs.cs @@ -0,0 +1,313 @@ +using ARMeilleure.Common; +using ARMeilleure.Instructions; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation.Cache; +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + /// + /// Represents a stub manager. + /// + class TranslatorStubs : IDisposable + { + private readonly Lazy _slowDispatchStub; + + private bool _disposed; + + private readonly AddressTable _functionTable; + private readonly Lazy _dispatchStub; + private readonly Lazy _dispatchLoop; + private readonly Lazy _contextWrapper; + + /// + /// Gets the dispatch stub. + /// + /// instance was disposed + public IntPtr DispatchStub + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _dispatchStub.Value; + } + } + + /// + /// Gets the slow dispatch stub. + /// + /// instance was disposed + public IntPtr SlowDispatchStub + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _slowDispatchStub.Value; + } + } + + /// + /// Gets the dispatch loop function. + /// + /// instance was disposed + public DispatcherFunction DispatchLoop + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _dispatchLoop.Value; + } + } + + /// + /// Gets the context wrapper function. + /// + /// instance was disposed + public WrapperFunction ContextWrapper + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _contextWrapper.Value; + } + } + + /// + /// Initializes a new instance of the class with the specified + /// instance. + /// + /// Function table used to store pointers to the functions that the guest code will call + /// is null + public TranslatorStubs(AddressTable functionTable) + { + ArgumentNullException.ThrowIfNull(functionTable); + + _functionTable = functionTable; + _slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true); + _dispatchStub = new(GenerateDispatchStub, isThreadSafe: true); + _dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true); + _contextWrapper = new(GenerateContextWrapper, isThreadSafe: true); + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resouces + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (_dispatchStub.IsValueCreated) + { + JitCache.Unmap(_dispatchStub.Value); + } + + if (_dispatchLoop.IsValueCreated) + { + JitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value)); + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~TranslatorStubs() + { + Dispose(false); + } + + /// + /// Generates a . + /// + /// Generated + private IntPtr GenerateDispatchStub() + { + var context = new EmitterContext(); + + Operand lblFallback = Label(); + Operand lblEnd = Label(); + + // Load the target guest address from the native context. + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand guestAddress = context.Load(OperandType.I64, + context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()))); + + // Check if guest address is within range of the AddressTable. + Operand masked = context.BitwiseAnd(guestAddress, Const(~_functionTable.Mask)); + context.BranchIfTrue(lblFallback, masked); + + Operand index = default; + Operand page = Const((long)_functionTable.Base); + + for (int i = 0; i < _functionTable.Levels.Length; i++) + { + ref var level = ref _functionTable.Levels[i]; + + // level.Mask is not used directly because it is more often bigger than 32-bits, so it will not + // be encoded as an immediate on x86's bitwise and operation. + Operand mask = Const(level.Mask >> level.Index); + + index = context.BitwiseAnd(context.ShiftRightUI(guestAddress, Const(level.Index)), mask); + + if (i < _functionTable.Levels.Length - 1) + { + page = context.Load(OperandType.I64, context.Add(page, context.ShiftLeft(index, Const(3)))); + context.BranchIfFalse(lblFallback, page); + } + } + + Operand hostAddress; + Operand hostAddressAddr = context.Add(page, context.ShiftLeft(index, Const(3))); + hostAddress = context.Load(OperandType.I64, hostAddressAddr); + context.Tailcall(hostAddress, nativeContext); + + context.MarkLabel(lblFallback); + hostAddress = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), guestAddress); + context.Tailcall(hostAddress, nativeContext); + + var cfg = context.GetControlFlowGraph(); + var retType = OperandType.I64; + var argTypes = new[] { OperandType.I64 }; + + var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + + return Marshal.GetFunctionPointerForDelegate(func); + } + + /// + /// Generates a . + /// + /// Generated + private IntPtr GenerateSlowDispatchStub() + { + var context = new EmitterContext(); + + // Load the target guest address from the native context. + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand guestAddress = context.Load(OperandType.I64, + context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()))); + + Operand hostAddress = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), guestAddress); + context.Tailcall(hostAddress, nativeContext); + + var cfg = context.GetControlFlowGraph(); + var retType = OperandType.I64; + var argTypes = new[] { OperandType.I64 }; + + var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + + return Marshal.GetFunctionPointerForDelegate(func); + } + + /// + /// Emits code that syncs FP state before executing guest code, or returns it to normal. + /// + /// Emitter context for the method + /// Pointer to the native context + /// True if entering guest code, false otherwise + private static 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); + }); + } + } + + /// + /// Generates a function. + /// + /// function + private DispatcherFunction GenerateDispatchLoop() + { + var context = new EmitterContext(); + + Operand beginLbl = Label(); + Operand endLbl = Label(); + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand guestAddress = context.Copy( + context.AllocateLocal(OperandType.I64), + context.LoadArgument(OperandType.I64, 1)); + + 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)); + context.BranchIfFalse(endLbl, guestAddress); + context.BranchIfFalse(endLbl, context.Load(OperandType.I32, runningAddress)); + context.Branch(beginLbl); + + context.MarkLabel(endLbl); + + EmitSyncFpContext(context, nativeContext, false); + + context.Return(); + + var cfg = context.GetControlFlowGraph(); + var retType = OperandType.None; + var argTypes = new[] { OperandType.I64, OperandType.I64 }; + + return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + } + + /// + /// Generates a function. + /// + /// function + 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(); + } + } +} diff --git a/src/ARMeilleure/Translation/TranslatorTestMethods.cs b/src/ARMeilleure/Translation/TranslatorTestMethods.cs new file mode 100644 index 00000000..8cc7a3cf --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatorTestMethods.cs @@ -0,0 +1,147 @@ +using ARMeilleure.CodeGen.X86; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +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(); + + 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(); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.OpenAL/OpenALAudioBuffer.cs b/src/Ryujinx.Audio.Backends.OpenAL/OpenALAudioBuffer.cs new file mode 100644 index 00000000..bd7a9c25 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.OpenAL/OpenALAudioBuffer.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Audio.Backends.OpenAL +{ + class OpenALAudioBuffer + { + public int BufferId; + public ulong DriverIdentifier; + public ulong SampleCount; + } +} diff --git a/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs new file mode 100644 index 00000000..01286992 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs @@ -0,0 +1,189 @@ +using OpenTK.Audio.OpenAL; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading; +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace Ryujinx.Audio.Backends.OpenAL +{ + public class OpenALHardwareDeviceDriver : IHardwareDeviceDriver + { + private readonly ALDevice _device; + private readonly ALContext _context; + private readonly ManualResetEvent _updateRequiredEvent; + private readonly ManualResetEvent _pauseEvent; + private readonly ConcurrentDictionary _sessions; + private bool _stillRunning; + private readonly Thread _updaterThread; + + private float _volume; + + public float Volume + { + get + { + return _volume; + } + set + { + _volume = value; + + foreach (OpenALHardwareDeviceSession session in _sessions.Keys) + { + session.UpdateMasterVolume(value); + } + } + } + + public OpenALHardwareDeviceDriver() + { + _device = ALC.OpenDevice(""); + _context = ALC.CreateContext(_device, new ALContextAttributes()); + _updateRequiredEvent = new ManualResetEvent(false); + _pauseEvent = new ManualResetEvent(true); + _sessions = new ConcurrentDictionary(); + + _stillRunning = true; + _updaterThread = new Thread(Update) + { + Name = "HardwareDeviceDriver.OpenAL", + }; + + _volume = 1f; + + _updaterThread.Start(); + } + + public static bool IsSupported + { + get + { + try + { + return ALC.GetStringList(GetEnumerationStringList.DeviceSpecifier).Any(); + } + catch + { + return false; + } + } + } + + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) + { + if (channelCount == 0) + { + channelCount = 2; + } + + if (sampleRate == 0) + { + sampleRate = Constants.TargetSampleRate; + } + + if (direction != Direction.Output) + { + throw new ArgumentException($"{direction}"); + } + else if (!SupportsChannelCount(channelCount)) + { + throw new ArgumentException($"{channelCount}"); + } + + OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount); + + _sessions.TryAdd(session, 0); + + return session; + } + + internal bool Unregister(OpenALHardwareDeviceSession session) + { + return _sessions.TryRemove(session, out _); + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _updateRequiredEvent; + } + + public ManualResetEvent GetPauseEvent() + { + return _pauseEvent; + } + + private void Update() + { + ALC.MakeContextCurrent(_context); + + while (_stillRunning) + { + bool updateRequired = false; + + foreach (OpenALHardwareDeviceSession session in _sessions.Keys) + { + if (session.Update()) + { + updateRequired = true; + } + } + + if (updateRequired) + { + _updateRequiredEvent.Set(); + } + + // If it's not slept it will waste cycles. + Thread.Sleep(10); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _stillRunning = false; + + foreach (OpenALHardwareDeviceSession session in _sessions.Keys) + { + session.Dispose(); + } + + ALC.DestroyContext(_context); + ALC.CloseDevice(_device); + + _pauseEvent.Dispose(); + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return true; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return true; + } + + public bool SupportsChannelCount(uint channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 6; + } + + public bool SupportsDirection(Direction direction) + { + return direction == Direction.Output; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs new file mode 100644 index 00000000..3b912913 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs @@ -0,0 +1,212 @@ +using OpenTK.Audio.OpenAL; +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Audio.Backends.OpenAL +{ + class OpenALHardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private readonly OpenALHardwareDeviceDriver _driver; + private readonly int _sourceId; + private readonly ALFormat _targetFormat; + private bool _isActive; + private readonly Queue _queuedBuffers; + private ulong _playedSampleCount; + private float _volume; + + private readonly object _lock = new(); + + public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _driver = driver; + _queuedBuffers = new Queue(); + _sourceId = AL.GenSource(); + _targetFormat = GetALFormat(); + _isActive = false; + _playedSampleCount = 0; + SetVolume(1f); + } + + private ALFormat GetALFormat() + { + return RequestedSampleFormat switch + { + SampleFormat.PcmInt16 => RequestedChannelCount switch + { + 1 => ALFormat.Mono16, + 2 => ALFormat.Stereo16, + 6 => ALFormat.Multi51Chn16Ext, + _ => throw new NotImplementedException($"Unsupported channel config {RequestedChannelCount}"), + }, + _ => throw new NotImplementedException($"Unsupported sample format {RequestedSampleFormat}"), + }; + } + + public override void PrepareToClose() { } + + private void StartIfNotPlaying() + { + AL.GetSource(_sourceId, ALGetSourcei.SourceState, out int stateInt); + + ALSourceState State = (ALSourceState)stateInt; + + if (State != ALSourceState.Playing) + { + AL.SourcePlay(_sourceId); + } + } + + public override void QueueBuffer(AudioBuffer buffer) + { + lock (_lock) + { + OpenALAudioBuffer driverBuffer = new() + { + DriverIdentifier = buffer.DataPointer, + BufferId = AL.GenBuffer(), + SampleCount = GetSampleCount(buffer), + }; + + AL.BufferData(driverBuffer.BufferId, _targetFormat, buffer.Data, (int)RequestedSampleRate); + + _queuedBuffers.Enqueue(driverBuffer); + + AL.SourceQueueBuffer(_sourceId, driverBuffer.BufferId); + + if (_isActive) + { + StartIfNotPlaying(); + } + } + } + + public override void SetVolume(float volume) + { + _volume = volume; + + UpdateMasterVolume(_driver.Volume); + } + + public override float GetVolume() + { + return _volume; + } + + public void UpdateMasterVolume(float newVolume) + { + lock (_lock) + { + AL.Source(_sourceId, ALSourcef.Gain, newVolume * _volume); + } + } + + public override void Start() + { + lock (_lock) + { + _isActive = true; + + StartIfNotPlaying(); + } + } + + public override void Stop() + { + lock (_lock) + { + SetVolume(0.0f); + + AL.SourceStop(_sourceId); + + _isActive = false; + } + } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + lock (_lock) + { + if (!_queuedBuffers.TryPeek(out OpenALAudioBuffer driverBuffer)) + { + return true; + } + + return driverBuffer.DriverIdentifier != buffer.DataPointer; + } + } + + public override ulong GetPlayedSampleCount() + { + lock (_lock) + { + return _playedSampleCount; + } + } + + public bool Update() + { + lock (_lock) + { + if (_isActive) + { + AL.GetSource(_sourceId, ALGetSourcei.BuffersProcessed, out int releasedCount); + + if (releasedCount > 0) + { + int[] bufferIds = new int[releasedCount]; + + AL.SourceUnqueueBuffers(_sourceId, releasedCount, bufferIds); + + int i = 0; + + while (_queuedBuffers.TryPeek(out OpenALAudioBuffer buffer) && i < bufferIds.Length) + { + if (buffer.BufferId == bufferIds[i]) + { + _playedSampleCount += buffer.SampleCount; + + _queuedBuffers.TryDequeue(out _); + + i++; + } + } + + Debug.Assert(i == bufferIds.Length, "Unknown buffer ids found!"); + + AL.DeleteBuffers(bufferIds); + } + + return releasedCount > 0; + } + + return false; + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && _driver.Unregister(this)) + { + lock (_lock) + { + PrepareToClose(); + Stop(); + + AL.DeleteSource(_sourceId); + } + } + } + + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj b/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj new file mode 100644 index 00000000..b5fd8f9e --- /dev/null +++ b/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + + + + + + + + + + + diff --git a/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj b/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj new file mode 100644 index 00000000..dd18e70a --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + true + + + + + + + + diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs new file mode 100644 index 00000000..a390c546 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Audio.Backends.SDL2 +{ + class SDL2AudioBuffer + { + public readonly ulong DriverIdentifier; + public readonly ulong SampleCount; + public ulong SamplePlayed; + + public SDL2AudioBuffer(ulong driverIdentifier, ulong sampleCount) + { + DriverIdentifier = driverIdentifier; + SampleCount = sampleCount; + SamplePlayed = 0; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs new file mode 100644 index 00000000..e39bfe54 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs @@ -0,0 +1,208 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using Ryujinx.SDL2.Common; +using System; +using System.Collections.Concurrent; +using System.Runtime.InteropServices; +using System.Threading; +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; +using static SDL2.SDL; + +namespace Ryujinx.Audio.Backends.SDL2 +{ + public class SDL2HardwareDeviceDriver : IHardwareDeviceDriver + { + private readonly ManualResetEvent _updateRequiredEvent; + private readonly ManualResetEvent _pauseEvent; + private readonly ConcurrentDictionary _sessions; + + private readonly bool _supportSurroundConfiguration; + + public float Volume { get; set; } + + // TODO: Add this to SDL2-CS + // NOTE: We use a DllImport here because of marshaling issue for spec. +#pragma warning disable SYSLIB1054 + [DllImport("SDL2")] + private static extern int SDL_GetDefaultAudioInfo(IntPtr name, out SDL_AudioSpec spec, int isCapture); +#pragma warning restore SYSLIB1054 + + public SDL2HardwareDeviceDriver() + { + _updateRequiredEvent = new ManualResetEvent(false); + _pauseEvent = new ManualResetEvent(true); + _sessions = new ConcurrentDictionary(); + + SDL2Driver.Instance.Initialize(); + + int res = SDL_GetDefaultAudioInfo(IntPtr.Zero, out var spec, 0); + + if (res != 0) + { + Logger.Error?.Print(LogClass.Application, + $"SDL_GetDefaultAudioInfo failed with error \"{SDL_GetError()}\""); + + _supportSurroundConfiguration = true; + } + else + { + _supportSurroundConfiguration = spec.channels >= 6; + } + + Volume = 1f; + } + + public static bool IsSupported => IsSupportedInternal(); + + private static bool IsSupportedInternal() + { + uint device = OpenStream(SampleFormat.PcmInt16, Constants.TargetSampleRate, Constants.ChannelCountMax, Constants.TargetSampleCount, null); + + if (device != 0) + { + SDL_CloseAudioDevice(device); + } + + return device != 0; + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _updateRequiredEvent; + } + + public ManualResetEvent GetPauseEvent() + { + return _pauseEvent; + } + + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) + { + if (channelCount == 0) + { + channelCount = 2; + } + + if (sampleRate == 0) + { + sampleRate = Constants.TargetSampleRate; + } + + if (direction != Direction.Output) + { + throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!"); + } + + SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount); + + _sessions.TryAdd(session, 0); + + return session; + } + + internal bool Unregister(SDL2HardwareDeviceSession session) + { + return _sessions.TryRemove(session, out _); + } + + private static SDL_AudioSpec GetSDL2Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount) + { + return new SDL_AudioSpec + { + channels = (byte)requestedChannelCount, + format = GetSDL2Format(requestedSampleFormat), + freq = (int)requestedSampleRate, + samples = (ushort)sampleCount, + }; + } + + internal static ushort GetSDL2Format(SampleFormat format) + { + return format switch + { + SampleFormat.PcmInt8 => AUDIO_S8, + SampleFormat.PcmInt16 => AUDIO_S16, + SampleFormat.PcmInt32 => AUDIO_S32, + SampleFormat.PcmFloat => AUDIO_F32, + _ => throw new ArgumentException($"Unsupported sample format {format}"), + }; + } + + internal static uint OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount, SDL_AudioCallback callback) + { + SDL_AudioSpec desired = GetSDL2Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount, sampleCount); + + desired.callback = callback; + + uint device = SDL_OpenAudioDevice(IntPtr.Zero, 0, ref desired, out SDL_AudioSpec got, 0); + + if (device == 0) + { + Logger.Error?.Print(LogClass.Application, $"SDL2 open audio device initialization failed with error \"{SDL_GetError()}\""); + + return 0; + } + + bool isValid = got.format == desired.format && got.freq == desired.freq && got.channels == desired.channels; + + if (!isValid) + { + Logger.Error?.Print(LogClass.Application, "SDL2 open audio device is not valid"); + SDL_CloseAudioDevice(device); + + return 0; + } + + return device; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (SDL2HardwareDeviceSession session in _sessions.Keys) + { + session.Dispose(); + } + + SDL2Driver.Instance.Dispose(); + + _pauseEvent.Dispose(); + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return true; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return sampleFormat != SampleFormat.PcmInt24; + } + + public bool SupportsChannelCount(uint channelCount) + { + if (channelCount == 6) + { + return _supportSurroundConfiguration; + } + + return true; + } + + public bool SupportsDirection(Direction direction) + { + // TODO: add direction input when supported. + return direction == Direction.Output; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs new file mode 100644 index 00000000..4eb75a57 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs @@ -0,0 +1,234 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Collections.Concurrent; +using System.Threading; + +using static SDL2.SDL; + +namespace Ryujinx.Audio.Backends.SDL2 +{ + class SDL2HardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private readonly SDL2HardwareDeviceDriver _driver; + private readonly ConcurrentQueue _queuedBuffers; + private readonly DynamicRingBuffer _ringBuffer; + private ulong _playedSampleCount; + private readonly ManualResetEvent _updateRequiredEvent; + private uint _outputStream; + private bool _hasSetupError; + private readonly SDL_AudioCallback _callbackDelegate; + private readonly int _bytesPerFrame; + private uint _sampleCount; + private bool _started; + private float _volume; + private readonly ushort _nativeSampleFormat; + + public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _driver = driver; + _updateRequiredEvent = _driver.GetUpdateRequiredEvent(); + _queuedBuffers = new ConcurrentQueue(); + _ringBuffer = new DynamicRingBuffer(); + _callbackDelegate = Update; + _bytesPerFrame = BackendHelper.GetSampleSize(RequestedSampleFormat) * (int)RequestedChannelCount; + _nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat); + _sampleCount = uint.MaxValue; + _started = false; + _volume = 1f; + } + + private void EnsureAudioStreamSetup(AudioBuffer buffer) + { + uint bufferSampleCount = (uint)GetSampleCount(buffer); + bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) || + (bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount); + + if (needAudioSetup) + { + _sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount); + + uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate); + + _hasSetupError = newOutputStream == 0; + + if (!_hasSetupError) + { + if (_outputStream != 0) + { + SDL_CloseAudioDevice(_outputStream); + } + + _outputStream = newOutputStream; + + SDL_PauseAudioDevice(_outputStream, _started ? 0 : 1); + + Logger.Info?.Print(LogClass.Audio, $"New audio stream setup with a target sample count of {_sampleCount}"); + } + } + } + + private unsafe void Update(IntPtr userdata, IntPtr stream, int streamLength) + { + Span streamSpan = new((void*)stream, streamLength); + + int maxFrameCount = (int)GetSampleCount(streamLength); + int bufferedFrames = _ringBuffer.Length / _bytesPerFrame; + + int frameCount = Math.Min(bufferedFrames, maxFrameCount); + + if (frameCount == 0) + { + // SDL2 left the responsibility to the user to clear the buffer. + streamSpan.Clear(); + + return; + } + + using SpanOwner samplesOwner = SpanOwner.Rent(frameCount * _bytesPerFrame); + + Span samples = samplesOwner.Span; + + _ringBuffer.Read(samples, 0, samples.Length); + + fixed (byte* p = samples) + { + IntPtr pStreamSrc = (IntPtr)p; + + // Zero the dest buffer + streamSpan.Clear(); + + // Apply volume to written data + SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_driver.Volume * _volume * SDL_MIX_MAXVOLUME)); + } + + ulong sampleCount = GetSampleCount(samples.Length); + + ulong availaibleSampleCount = sampleCount; + + bool needUpdate = false; + + while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer)) + { + ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed); + ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount); + + ulong currentSamplePlayed = Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount); + availaibleSampleCount -= playedAudioBufferSampleCount; + + if (currentSamplePlayed == driverBuffer.SampleCount) + { + _queuedBuffers.TryDequeue(out _); + + needUpdate = true; + } + + Interlocked.Add(ref _playedSampleCount, playedAudioBufferSampleCount); + } + + // Notify the output if needed. + if (needUpdate) + { + _updateRequiredEvent.Set(); + } + } + + public override ulong GetPlayedSampleCount() + { + return Interlocked.Read(ref _playedSampleCount); + } + + public override float GetVolume() + { + return _volume; + } + + public override void PrepareToClose() { } + + public override void QueueBuffer(AudioBuffer buffer) + { + EnsureAudioStreamSetup(buffer); + + if (_outputStream != 0) + { + SDL2AudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer)); + + _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); + + _queuedBuffers.Enqueue(driverBuffer); + } + else + { + Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer)); + + _updateRequiredEvent.Set(); + } + } + + public override void SetVolume(float volume) + { + _volume = volume; + } + + public override void Start() + { + if (!_started) + { + if (_outputStream != 0) + { + SDL_PauseAudioDevice(_outputStream, 0); + } + + _started = true; + } + } + + public override void Stop() + { + if (_started) + { + if (_outputStream != 0) + { + SDL_PauseAudioDevice(_outputStream, 1); + } + + _started = false; + } + } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + if (!_queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer)) + { + return true; + } + + return driverBuffer.DriverIdentifier != buffer.DataPointer; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && _driver.Unregister(this)) + { + PrepareToClose(); + Stop(); + + if (_outputStream != 0) + { + SDL_CloseAudioDevice(_outputStream); + } + } + } + + public override void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs new file mode 100644 index 00000000..7fdb1fc0 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs @@ -0,0 +1,178 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public static partial class SoundIo + { + private const string LibraryName = "libsoundio"; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void OnDeviceChangeNativeDelegate(IntPtr ctx); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void OnBackendDisconnectedDelegate(IntPtr ctx, SoundIoError err); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void OnEventsSignalDelegate(IntPtr ctx); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void EmitRtPrioWarningDelegate(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void JackCallbackDelegate(IntPtr msg); + + [StructLayout(LayoutKind.Sequential)] + public struct SoundIoStruct + { + public IntPtr UserData; + public IntPtr OnDeviceChange; + public IntPtr OnBackendDisconnected; + public IntPtr OnEventsSignal; + public SoundIoBackend CurrentBackend; + public IntPtr ApplicationName; + public IntPtr EmitRtPrioWarning; + public IntPtr JackInfoCallback; + public IntPtr JackErrorCallback; + } + + public struct SoundIoChannelLayout + { + public IntPtr Name; + public int ChannelCount; + public Array24 Channels; + + public static IntPtr GetDefault(int channelCount) + { + return soundio_channel_layout_get_default(channelCount); + } + + public static unsafe SoundIoChannelLayout GetDefaultValue(int channelCount) + { + return Unsafe.AsRef((SoundIoChannelLayout*)GetDefault(channelCount)); + } + } + + public struct SoundIoSampleRateRange + { + public int Min; + public int Max; + } + + public struct SoundIoDevice + { + public IntPtr SoundIo; + public IntPtr Id; + public IntPtr Name; + public SoundIoDeviceAim Aim; + public IntPtr Layouts; + public int LayoutCount; + public SoundIoChannelLayout CurrentLayout; + public IntPtr Formats; + public int FormatCount; + public SoundIoFormat CurrentFormat; + public IntPtr SampleRates; + public int SampleRateCount; + public int SampleRateCurrent; + public double SoftwareLatencyMin; + public double SoftwareLatencyMax; + public double SoftwareLatencyCurrent; + public bool IsRaw; + public int RefCount; + public SoundIoError ProbeError; + } + + public struct SoundIoOutStream + { + public IntPtr Device; + public SoundIoFormat Format; + public int SampleRate; + public SoundIoChannelLayout Layout; + public double SoftwareLatency; + public float Volume; + public IntPtr UserData; + public IntPtr WriteCallback; + public IntPtr UnderflowCallback; + public IntPtr ErrorCallback; + public IntPtr Name; + public bool NonTerminalHint; + public int BytesPerFrame; + public int BytesPerSample; + public SoundIoError LayoutError; + } + + public struct SoundIoChannelArea + { + public IntPtr Pointer; + public int Step; + } + + [LibraryImport(LibraryName)] + internal static partial IntPtr soundio_create(); + + [LibraryImport(LibraryName)] + internal static partial SoundIoError soundio_connect(IntPtr ctx); + + [LibraryImport(LibraryName)] + internal static partial void soundio_disconnect(IntPtr ctx); + + [LibraryImport(LibraryName)] + internal static partial void soundio_flush_events(IntPtr ctx); + + [LibraryImport(LibraryName)] + internal static partial int soundio_output_device_count(IntPtr ctx); + + [LibraryImport(LibraryName)] + internal static partial int soundio_default_output_device_index(IntPtr ctx); + + [LibraryImport(LibraryName)] + internal static partial IntPtr soundio_get_output_device(IntPtr ctx, int index); + + [LibraryImport(LibraryName)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool soundio_device_supports_format(IntPtr devCtx, SoundIoFormat format); + + [LibraryImport(LibraryName)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool soundio_device_supports_layout(IntPtr devCtx, IntPtr layout); + + [LibraryImport(LibraryName)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool soundio_device_supports_sample_rate(IntPtr devCtx, int sampleRate); + + [LibraryImport(LibraryName)] + internal static partial IntPtr soundio_outstream_create(IntPtr devCtx); + + [LibraryImport(LibraryName)] + internal static partial SoundIoError soundio_outstream_open(IntPtr outStreamCtx); + + [LibraryImport(LibraryName)] + internal static partial SoundIoError soundio_outstream_start(IntPtr outStreamCtx); + + [LibraryImport(LibraryName)] + internal static partial SoundIoError soundio_outstream_begin_write(IntPtr outStreamCtx, IntPtr areas, IntPtr frameCount); + + [LibraryImport(LibraryName)] + internal static partial SoundIoError soundio_outstream_end_write(IntPtr outStreamCtx); + + [LibraryImport(LibraryName)] + internal static partial SoundIoError soundio_outstream_pause(IntPtr devCtx, [MarshalAs(UnmanagedType.Bool)] bool pause); + + [LibraryImport(LibraryName)] + internal static partial SoundIoError soundio_outstream_set_volume(IntPtr devCtx, double volume); + + [LibraryImport(LibraryName)] + internal static partial void soundio_outstream_destroy(IntPtr streamCtx); + + [LibraryImport(LibraryName)] + internal static partial void soundio_destroy(IntPtr ctx); + + [LibraryImport(LibraryName)] + internal static partial IntPtr soundio_channel_layout_get_default(int channelCount); + + [LibraryImport(LibraryName)] + internal static partial IntPtr soundio_strerror(SoundIoError err); + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs new file mode 100644 index 00000000..d91eca48 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoBackend + { + None = 0, + Jack = 1, + PulseAudio = 2, + Alsa = 3, + CoreAudio = 4, + Wasapi = 5, + Dummy = 6, + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs new file mode 100644 index 00000000..c1a14e1e --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs @@ -0,0 +1,75 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoChannelId + { + Invalid = 0, + FrontLeft = 1, + FrontRight = 2, + FrontCenter = 3, + Lfe = 4, + BackLeft = 5, + BackRight = 6, + FrontLeftCenter = 7, + FrontRightCenter = 8, + BackCenter = 9, + SideLeft = 10, + SideRight = 11, + TopCenter = 12, + TopFrontLeft = 13, + TopFrontCenter = 14, + TopFrontRight = 15, + TopBackLeft = 16, + TopBackCenter = 17, + TopBackRight = 18, + BackLeftCenter = 19, + BackRightCenter = 20, + FrontLeftWide = 21, + FrontRightWide = 22, + FrontLeftHigh = 23, + FrontCenterHigh = 24, + FrontRightHigh = 25, + TopFrontLeftCenter = 26, + TopFrontRightCenter = 27, + TopSideLeft = 28, + TopSideRight = 29, + LeftLfe = 30, + RightLfe = 31, + Lfe2 = 32, + BottomCenter = 33, + BottomLeftCenter = 34, + BottomRightCenter = 35, + MsMid = 36, + MsSide = 37, + AmbisonicW = 38, + AmbisonicX = 39, + AmbisonicY = 40, + AmbisonicZ = 41, + XyX = 42, + XyY = 43, + HeadphonesLeft = 44, + HeadphonesRight = 45, + ClickTrack = 46, + ForeignLanguage = 47, + HearingImpaired = 48, + Narration = 49, + Haptic = 50, + DialogCentricMix = 51, + Aux = 52, + Aux0 = 53, + Aux1 = 54, + Aux2 = 55, + Aux3 = 56, + Aux4 = 57, + Aux5 = 58, + Aux6 = 59, + Aux7 = 60, + Aux8 = 61, + Aux9 = 62, + Aux10 = 63, + Aux11 = 64, + Aux12 = 65, + Aux13 = 66, + Aux14 = 67, + Aux15 = 68, + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs new file mode 100644 index 00000000..f2e91fcd --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public class SoundIoContext : IDisposable + { + private IntPtr _context; + private Action _onBackendDisconnect; + private OnBackendDisconnectedDelegate _onBackendDisconnectNative; + + public IntPtr Context => _context; + + internal SoundIoContext(IntPtr context) + { + _context = context; + _onBackendDisconnect = null; + _onBackendDisconnectNative = null; + } + + public SoundIoError Connect() => soundio_connect(_context); + public void Disconnect() => soundio_disconnect(_context); + + public void FlushEvents() => soundio_flush_events(_context); + + public int OutputDeviceCount => soundio_output_device_count(_context); + + public int DefaultOutputDeviceIndex => soundio_default_output_device_index(_context); + + public Action OnBackendDisconnect + { + get { return _onBackendDisconnect; } + set + { + _onBackendDisconnect = value; + + if (_onBackendDisconnect == null) + { + _onBackendDisconnectNative = null; + } + else + { + _onBackendDisconnectNative = (ctx, err) => _onBackendDisconnect(err); + } + + GetContext().OnBackendDisconnected = Marshal.GetFunctionPointerForDelegate(_onBackendDisconnectNative); + } + } + + private ref SoundIoStruct GetContext() + { + unsafe + { + return ref Unsafe.AsRef((SoundIoStruct*)_context); + } + } + + public SoundIoDeviceContext GetOutputDevice(int index) + { + IntPtr deviceContext = soundio_get_output_device(_context, index); + + if (deviceContext == IntPtr.Zero) + { + return null; + } + + return new SoundIoDeviceContext(deviceContext); + } + + public static SoundIoContext Create() + { + IntPtr context = soundio_create(); + + if (context == IntPtr.Zero) + { + return null; + } + + return new SoundIoContext(context); + } + + protected virtual void Dispose(bool disposing) + { + IntPtr currentContext = Interlocked.Exchange(ref _context, IntPtr.Zero); + + if (currentContext != IntPtr.Zero) + { + soundio_destroy(currentContext); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~SoundIoContext() + { + Dispose(false); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs new file mode 100644 index 00000000..58bdce39 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoDeviceAim + { + SoundIoDeviceAimInput = 0, + SoundIoDeviceAimOutput = 1, + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs new file mode 100644 index 00000000..7923e9b1 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public class SoundIoDeviceContext + { + private readonly IntPtr _context; + + public IntPtr Context => _context; + + internal SoundIoDeviceContext(IntPtr context) + { + _context = context; + } + + private ref SoundIoDevice GetDeviceContext() + { + unsafe + { + return ref Unsafe.AsRef((SoundIoDevice*)_context); + } + } + + public bool IsRaw => GetDeviceContext().IsRaw; + + public string Id => Marshal.PtrToStringAnsi(GetDeviceContext().Id); + + public bool SupportsSampleRate(int sampleRate) => soundio_device_supports_sample_rate(_context, sampleRate); + + public bool SupportsFormat(SoundIoFormat format) => soundio_device_supports_format(_context, format); + + public bool SupportsChannelCount(int channelCount) => soundio_device_supports_layout(_context, SoundIoChannelLayout.GetDefault(channelCount)); + + public SoundIoOutStreamContext CreateOutStream() + { + IntPtr context = soundio_outstream_create(_context); + + if (context == IntPtr.Zero) + { + return null; + } + + return new SoundIoOutStreamContext(context); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs new file mode 100644 index 00000000..64329450 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoError + { + None = 0, + NoMem = 1, + InitAudioBackend = 2, + SystemResources = 3, + OpeningDevice = 4, + NoSuchDevice = 5, + Invalid = 6, + BackendUnavailable = 7, + Streaming = 8, + IncompatibleDevice = 9, + NoSuchClient = 10, + IncompatibleBackend = 11, + BackendDisconnected = 12, + Interrupted = 13, + Underflow = 14, + EncodingString = 15, + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs new file mode 100644 index 00000000..1a33472d --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs @@ -0,0 +1,11 @@ +using System; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + internal class SoundIoException : Exception + { + internal SoundIoException(SoundIoError error) : base(Marshal.PtrToStringAnsi(soundio_strerror(error))) { } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs new file mode 100644 index 00000000..7e8c22a9 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoFormat + { + Invalid = 0, + S8 = 1, + U8 = 2, + S16LE = 3, + S16BE = 4, + U16LE = 5, + U16BE = 6, + S24LE = 7, + S24BE = 8, + U24LE = 9, + U24BE = 10, + S32LE = 11, + S32BE = 12, + U32LE = 13, + U32BE = 14, + Float32LE = 15, + Float32BE = 16, + Float64LE = 17, + Float64BE = 18, + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs new file mode 100644 index 00000000..4148ea0d --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs @@ -0,0 +1,164 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public class SoundIoOutStreamContext : IDisposable + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private unsafe delegate void WriteCallbackDelegate(IntPtr ctx, int frameCountMin, int frameCountMax); + + private IntPtr _context; + private IntPtr _nameStored; + private Action _writeCallback; + private WriteCallbackDelegate _writeCallbackNative; + + public IntPtr Context => _context; + + internal SoundIoOutStreamContext(IntPtr context) + { + _context = context; + _nameStored = IntPtr.Zero; + _writeCallback = null; + _writeCallbackNative = null; + } + + private ref SoundIoOutStream GetOutContext() + { + unsafe + { + return ref Unsafe.AsRef((SoundIoOutStream*)_context); + } + } + + public string Name + { + get => Marshal.PtrToStringAnsi(GetOutContext().Name); + set + { + var context = GetOutContext(); + + if (_nameStored != IntPtr.Zero && context.Name == _nameStored) + { + Marshal.FreeHGlobal(_nameStored); + } + + _nameStored = Marshal.StringToHGlobalAnsi(value); + GetOutContext().Name = _nameStored; + } + } + + public SoundIoChannelLayout Layout + { + get => GetOutContext().Layout; + set => GetOutContext().Layout = value; + } + + public SoundIoFormat Format + { + get => GetOutContext().Format; + set => GetOutContext().Format = value; + } + + public int SampleRate + { + get => GetOutContext().SampleRate; + set => GetOutContext().SampleRate = value; + } + + public float Volume + { + get => GetOutContext().Volume; + set => GetOutContext().Volume = value; + } + + public int BytesPerFrame + { + get => GetOutContext().BytesPerFrame; + set => GetOutContext().BytesPerFrame = value; + } + + public int BytesPerSample + { + get => GetOutContext().BytesPerSample; + set => GetOutContext().BytesPerSample = value; + } + + public Action WriteCallback + { + get { return _writeCallback; } + set + { + _writeCallback = value; + + if (_writeCallback == null) + { + _writeCallbackNative = null; + } + else + { + _writeCallbackNative = (ctx, frameCountMin, frameCountMax) => _writeCallback(frameCountMin, frameCountMax); + } + + GetOutContext().WriteCallback = Marshal.GetFunctionPointerForDelegate(_writeCallbackNative); + } + } + + private static void CheckError(SoundIoError error) + { + if (error != SoundIoError.None) + { + throw new SoundIoException(error); + } + } + + public void Open() => CheckError(soundio_outstream_open(_context)); + + public void Start() => CheckError(soundio_outstream_start(_context)); + + public void Pause(bool pause) => CheckError(soundio_outstream_pause(_context, pause)); + + public void SetVolume(double volume) => CheckError(soundio_outstream_set_volume(_context, volume)); + + public Span BeginWrite(ref int frameCount) + { + IntPtr arenas = default; + int nativeFrameCount = frameCount; + + unsafe + { + var frameCountPtr = &nativeFrameCount; + var arenasPtr = &arenas; + CheckError(soundio_outstream_begin_write(_context, (IntPtr)arenasPtr, (IntPtr)frameCountPtr)); + + frameCount = *frameCountPtr; + + return new Span((void*)arenas, Layout.ChannelCount); + } + } + + public void EndWrite() => CheckError(soundio_outstream_end_write(_context)); + + protected virtual void Dispose(bool disposing) + { + if (_context != IntPtr.Zero) + { + soundio_outstream_destroy(_context); + _context = IntPtr.Zero; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~SoundIoOutStreamContext() + { + Dispose(false); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll new file mode 100644 index 0000000000000000000000000000000000000000..48804312e0ec19ca7c6827982b7f62e5fa6d1634 GIT binary patch literal 85504 zcmdqKdwf*Y)i*wqWFSbw2}m$rkkJNB6nPRaEvZmvk_n!HK|wC!9UG1KXcc9qqC!YG zGnm8aXe!#*THA*{tu3v#-YQqClLRsWyo4ag#Y@CX&oH0?Y6xgO@Atd+naKq3`8@CQ z`~CC2`Ft|_oPAk)?X}llYwfi!XUg?Wj%)1=L{D-$9K(Unsdz46wwycoE1t6?-cE

@V^=V8GjzfoY>g=WyXT>Na)7@kMO@UBNHBx&og$O%qQB2whDM7`Zv(yDD#Y6 zAoD#%8hMb;`ioDJ@5l4!vz8U?uc`gh8PC6Z;&;1*Culy;hR6Cd{zScZ%6y_v&^Myq z@h9Yqjjg(728+(-PZQEfYxVeip4iyn-2y+s1uN`)xfI zkf|CI@=?ebsP$$k;k_ubb`*Zhrq>~N2tn00l-zLhEjM{@^xo83tBmrYu47J-6#C?L z)yNrL#$3k8RlN-6<(6BjanT@FdYUq-9%&A>qD={}#)Ck@+OF!o#NiU)5c-7KFI$GP z#sIC?sf6D`K4Soy6@FGciO4t}BbE|gf>1SFXNsyEC?Cw9qMPf;R{{NFt3z|W_ajK2 z6A>?uB`&cnOX3gu{u0^D4QOM0Aj&$_M=m+B3^IqAC@GnknJ!-*Qy-TQFyw* zC_f89d;rO2Uh#K_BQXJaBt1QR9gA0Fe)9-hk^d?Jvk39=0c=3xOr#3>rhD5B(p-&P zkxo^AN8c9EH|irow;Ha)6WH;=E6kVYI2?Z6EJylLbNfB$VSPhnPtc`(ogFGhg1(yx z&m$rAf(+*K|FSHQKD)a7Q=i@yAAou-bhBG_b8C}%1HeYBtW+6v!2`cFe=14^H>YCF znE)G!0buBC?Q3V~B_v2h0?3(swsvfaeoPIRvu+SrtP@NHtjgEBc&``v^IJL&Aml6u)WvRdC6ZJ%UxqI}S(i6tqxH{PtVRY6|GS z;&J+n;!9OMP+Veua6S5=9dadLAl_iSfq0SsKIT{T&0v{YNVr7vdPuUFs{R?qLb`o? z$JlhM+Jx9n)>Ve=KX@O?yU>5HQ?Tj3snNnq5TokNR^fO%auBjLp@7wahtp7yop4hk z%I=7|et{tQ0mr|ruPOc4YE4t6N%u!8o>TY9PuVG;KCtOU$?K z7LdoaQh>N$bS`zRzuf5v$3mB3)ONgwVM+G0wgJVKJpx7ZML_P0JtSiVizJ^CK`Z`L zV^E@Eox>p|liTp-t)~Q6^|!U&0%gk1ifxN>oO_bJ~iy z@NcfF572MUOS*)p(t8Eun{R2AlFslVeTS-ViB5HzPXQsY?5Tm8s$A6=9MD@P>6bQJ zE&B9al!AZ)Hcqvs6Y{7d^%%t?ffz%rb{*kUNNllz1FXKotdvl*_4z3%$dBrhyVaU$ zC*#Gwy+rvE_vWIJ4wAdXuWt&}w3z=3V5-p}=-gnw-xxY6I=n?SPEqwNRc|MH zf>cA$>i=U>{dUd&Bcvjoo(7$Kpi}S+^vPAxoEEQ<>oun4_;m;P!VOvt>X77$x~>4v zX-5_1k)M;LqAm~K^-XHc@J7|yVRa#9_b&EA)YWB&U)$l(-7h4(Hk7B0Kz<9X@G?*NS@3`}$UMI$DIW`w5a%y~TWH9i#&p14-j#(g8cEA(dpl zkAx-{c35bKZacit4ll98%k8kr4kz2;xi-A*c-M{&c)$;sAL6-xFBx|3 zMms$IeVHAv+u;lmf`sNpLtx8n!l1upEmg+4+4wbgy~;=m4^&t9z}JHOVU$tzskw-` z4f0ym)e>~d4NTOh79jR&@u-eNiU)%fu|C?LoIA zm(@lXvnBWLoWjmFAFNin4D`q z^fyLNE->#C55|j3k7^E(TCHdf4pA>Ra`7B=nNyh|D&EYoB&T7lkeQ3@%r0|`gyvSG zRb?#hGS6U)zDo%9L_S|=X0%!jqLHd~>m2;g#s567HV!%4>rRJOnW8uQ^$rXuJO_-Q zvmJhG@0tM!L0%`-u>tAa@DvfSx(N`RHsGb=#4RDCz8H`shk@S5GYGS(=HXJXjM zLXcJvZN?y>LP>W!phU-L6j>d2@n=zCn*_xNJp~igZ@;Ia#!#mb+Qbrkky44wp>G5D5 zduSm*>b*&qKHqH*RoIGh@Vl5FwbBv;N1lesY-sp83XC*6E<{plgrl;jwhK+Ajc<1- zPsUm~e1uk9gLsHlBd4h~Jq;@>@A2xGIdAb*e7@*1%)-hoZ^h9%Ods|EybqT47+TtH zRoghve5=So+j^K4_hA+>hI0ycvZFkf%vbfD$y}>V)mrBzUj=)W+4C)MRIo4*%|Gv- z;E|e6@0xkQCGo;vfsiq+1mfSFeBWm@urteJ-ilb=2*`vLc&j$@8dM8WTSMlHPU;Ms z$mM%rgo6hwZZ3g9y=%)nRX?U$t!nmFZu266U+~lPfCmwMhQ7vz-duNPz-T~!Ii^H& z0J>oD0Pp2fbLw(YQL+nFpd!>_tYrbOw#BM0@ATdadB-iVlzROdE12ziIV^ zdxAOPJ)r_m?uS8Fb;Xv&1FH31UOncwTB^|vdMx><-U0HprraW`QHuW278N;zrwH~+ zd;@tNASh!NM~rQ)GKbx(olj@TyHZU73Bias)A6CYJ}Wt&qBd~9zXo7_HdpfRIsjo zy{uphIkzg$sLItgTgK!ft#{Z1LjqCPC!Juz7O(R9@B-z9t@`rUP#!s$JQzxA1Co)J zr5aNl`tleOp?e^67D(B2%S}`*LQLIqQzj)+;|t_~>Te}bG*}6*#S>@%5-5-Th0kg7 z{0H9pTKU5fnXOz2p&pL?H(L1_GM=oJ|JvHWl`sP`Tlpc9`&vo160K~A@yTvoWB_{#MUNElAWAPT2p;7P0Srvl($t8nzq$eS|i#{F6J z5{$Y&aS0Els&Dtlp&?uQVK#*D4X=>D;YZY2IW>k0x!d;e=6_%wdG?~&@;VZJj5u(Ck zRuG*GVCQC5EsY^`JGdp753n#^fXC}1_kHkE9JC5`S-VxZQj|QH&SfG&YLPMtLHJ=V0^}P#GOBb7M)RkYIvoKF zgfcqA##n_3fCC?#vW{Gm_&I*Gg~eka6UD>`8OK?Lg@6@_i5$WEIdsPTH-=6@{vX~# z+oSn^EFnQ|ZU>F>x8T_-ya{z0uGM(1E{~-UTZ32Kb-l=Nqd-at-@#7pgxL=dWvk(u zfW%hswcc6%_o(y z$_%u6_Eyxh4X-V-u9oBoLRZ(xs!$i|iK*oss`A3lYD!I2+XR~!Lz_9gYjW@#%{h~X zlvTL(I9v=iTQJGt8EFBihvy# z=Hf8CtmTjS+ut)9R*SX*uQpjbgNx%?G=k!lIRyyHWAVyduCvA~^SG24uPn$H!Em@@ z@K!{#JYIPg5sFv32Z^AVi;wZjlA$6P!`kANWgKSl%1en&ymB1P^LXWW+T-y`4{?uI z`UH)Itfb7t1YlV=5>zg8fG5nkKn$EbIq#P=p-Yaer`dprSa}Rg-F(Qa43(lpG@QkI zGmDoVU!1npNsGglXk1dn&irdPbTnQdJH(5W+WI!*@lR1$d0{zGfD?pxnysLTp)sLU z>%wnJ1qyvtu+r5TU7gd+XrszeI!(f;%UDAOZ0mBPwmvGsOEHlCe?VnfW96{SHL1~ZwY zjZFqr#3=sBita!~sR@X3ZA>+?+5{t^Wte?!Lz^HHm((NwLlZ+jisa5XnXsUqwLnyE z!E;j#&k&{U1C$xf%FHdwEDGDrjL8x)n-K%0BH$RB90dhhJxVDrROKN>W5Z~~Ed+Em zy1pB)(4k;WM2|4(WB;8jQL;60j6svN}E3g zF(9BRM+I?1kY}RUiu zk4YimfOH}>G|8#!$*Gbe1^EvFxFErG)W=2_lKPS3`)F}La=gj8%f93klA{F@4Y?tC zj;v2f4M`UvctG%az%#Gu>7x<(B2A-uz&f5r$EowVN-0#9Qo69X%%_z4iaq!Z6i-#7 zYuO6OZ{kr4JP=}{S%#<5ZW|pgxyE!c4(kS^lk{EI@~2R$8ssiNU>-eeS)k2?J2pb8 zzDoVv?Q1lb3ML6-Rs9b1hxsltAq9>`!R5#=XjI+_mAwNAAJzJl5|Hj@S`}kj@N+>I zqW6O<_YvfFWyS|yWoCymYa2BGyI`}e2zQ`#hxz(fSmj}jWC3_WE@E5OLs^J6Aw01i z%43hB0f{H@vrI4y5RJSnC~?%hg+=r>l$PDW73@)nOA`bH$`Or5mjQz|L&`3W0C2xg z8Pn}8{i3?!pVj&i_#?i7V13W${Fj*p7>HdoMo@4CYK!#fuTZ?z4e{#T8I(1jJtWB0 zojebw$fJMZ&=DE?XG=VRgj%msagTNNO zTg+l9j-L>ns#&JGoUSpYPDOiU9jZxn@Sd) z6S(V>d6>{xt%2;@-DWKc3I7DFs(}8F!_Ovn+a_mf**}GbEwO2txXdaL7EB_z5)oWP zhlGuR9^y%^rj9dSCfU3d<+kTx+u}QIeN4qyBAjF9y#s+}bUF7E3GD|vk1DXFpZ1FZ z&Ua`(4#ht0MtAnimCxBI>&q=|ZFn@x+g0%_0{sEqRQt0c= zdiO8AM|#2jdOLVuJZ>~k5iLn`zOYdAge~{GOTYD2eC5@9!1rH)?|1l|N6a2rV~E8H zlD@0llCr~W{Zevxb>vg!(J#QeKK*xHz^YpBvIjyF-V*PoIowLnsr8*4KBtev(-S?= z{V%{)Y87&n5}pks&>VCy6-mxl!c~Y|SAujo<^KGapa$8yN>#$=BQ5SJMj`??gLO2g zWX{)8IkDa7Tg1W&IwyvVVo43}AZn4RFjF*t7!p){Vyk}*o;UxXQ5O0>Zpt~|g*n9nf2bPW=b0JuwEWMEXO&_zo- z@Qh4_?_hIRTomcJ zqM=0c@RmV@)oc&R1tJ_3h4X*Ns`?S(6lRL%Uw{Nbgk%2;5B-R+mPJVf;%+{O^&^`I zp&{nu5DI(?o<>We@e_0(oG`B;kKUX3HPAu(Mq1ch)kmAry?n(=2$C*(m;0{L^g%0s zL!#Jp>PM45#E{TOlRhG#&|gU!5Cgv?mf7|JvBKU0;PlVE5~mX2BqlovZpwm7^)y7N z(FyNRfa%kra0_1$j}Om7Y#f>kv9l37SF8=MuP5y2gb^&IPoIN?F(S|UCYkUxUH1C) zxk%`NIiTw6SIC69l$-kW1xV0Dp7o?7Kphir%Nxa8;Zm6W)ZZE7G5FWx?};$8|3Wf8$~$WB`NP>Qxlw`cm5q7k)yr` zhGO+T>iW7F+3wm@gbg#foDyMIjPLTw+|Wq5{%|T*HX8DL)&CswZdN-Dj0{SL%2h1Mu?FdzCgQx7i9Z3Dc*4#nH~x#tJCEFlodasNH@CIhIjsnBXB`)J7M7XLLe^63 zoWg_-z3n#lf$ZMW82XFVvykpB;sjaNjg3~2=CcoQ#D&g?SBXK0RD4h5cK9|zpPK`eGRV*vjaby9Hc3Jx?+$1~EgJS_V((e?#)&Dej+ z>S;ri)!r=Sg_h>t>{{KMr>u@CFL-mX%Zn3E!R4U+KrSI$a|&!L1(Or6F*E>E(h!QA zuE7$0IXvHxOipWR5uAUyzSj||j z3phcGsom1()Tj0)-wWsiY}1N{2}z2nEr+r3IP1bl5Y@Sxdq-(qbdHt?s(S1RFwhhp z5H&iZc)ZA2mO|`octXaDsfq9#z!H%@eA^u&=4755Y=?-kjN*Z{VJcd(`7nLA+|D2x zx^@s7T5y;i+%e^wkSIiwpm*5~w#xQs>u9Y(!~i2$jqfa;W8+BI>UcH!I8A(#aU9^L zU3q+PqnJa~^x!FLNYn%dnxhBkXK|CIu3d_fx<>H9!I)SJ9|Ce111m(Na+L@IhL}iB ziFE23^_)I1c!STlp(qeFJa`pCW#^*thPH-fg@eSeHPd&3Ns_4ntISp@jQF zyIRwXEo1>_hhO)a$v4!7)^PxZ<$>ftH4b|Vj7g^VP)3s5*)K7~{(tWm)%bZY0ZErg zVuqP=h{&!&1?I+oU@#lY*>m7h=5Ety;5g0mkQ-4&E0!b`1$N_0A%N32-W@-|G zcr^@~3T>KCK=@Ro$Spj#odALUNWB0VO>bX5pu(s&tz1C(Pnp6fF|rdQ89@ebNT@9O z5{Io68JW@C_;-?M?c~vS_eI-F83H#F1A8`q_qm{uB&0zLOMivTWV(n8Cd1RAw&a9G zh>paAC?JWXt?L2|c4Y9rwk5(i6Ow9~Xb=R(0IP5uXq6)KI9Zk58I&sKihfZvj)GGYAlCE8iF?ON=|L$jOoWwpZ9BxcC|1P5iB5{qi z^3SYbbX<6B_%6C6ZEYL2GiF)+PNX$1ags*AVF}7u0UK+j(yQ|UOnGT0bs?|0Af1a< z1$Uc0Y>FsG6oQ-T*Ato}SS}swT&uGRN3cmE6I+#r-9Xqm$ejq|2VO|AyDVT#EQ0}h zEBr7R%UwvJvR?~RQW%ygFO?64vE^e*sz?3+MC#X{&dTjk5*mUuLA&RHb#gCOF3d-e z%h1k3F6dP#7vZ@&3a^Ze6m*j7MNoGB+(5MO>6j2E@yg+V?5OqWld`HSnw3Y}@K$N~ z8uF}mt#_#M$baJ9Uz4*0(!=V~CJlEcocrZveKd;q1AllhQ~he|#a;Gu#KZakFn!GnkM`2ax4v-to( z$wKjv<-lqr&NslaH}jjg$tsq>ERWN5IAbwXwM3TKS4%$-44dIG1T}fsRoiZL!NreQ zM=gB(;`i(VM_Z-4zOC{cgy-V_dw*`LEJiqbTwCRNziO*I|32g$lFJgqavcdbO8*aG z6bKjvj>P*w;C~3iEnpNm5>NHPNDc%rT8ICqjgr9Jj^tJULjWFujmLqFKhgXKOnJ;j z@N!{$Fgl{2x1tQ^W%Mg>V24XFUVST;&%OHgDS8{11=>>!WNEFsHi2oUwH@Q6t!`3O zb;`x6noZ{4!Fgf;wgxYP0TSe57c_CTo|vL{*`+Gl;KSj*1LRHS`LQN$Wp2N>7su(C zvK5QAPRL1HX0n5A7kU}_Cn98K4v@zvq{=aKJ_fHD%Wd^TNnqaHFNz#yUMHetiCtYR zAtbq3_Yt1sLPDS-0!>u|nfaFqLS{0?P88#Ql`b-}DimcD|4dziWpx>X*1TUv3;g23 z0V8sMAuDDy|N4p27qR1Xn`#V$XWP>l_bgpsfm}l`-|D3>0Cq-V)ij2A&LI5;%zOu#a$UGrt*^?Br z*L--Km>t?DW?8d&1rVuUkBX3A_N5TBB(YfytfiQBBbT9h5Sbxn+k}{9WJ=7YR!G#e z7WXvnFy99b*yaWBLdO)?98lh4ehRS3PwUrFGXf*+A)VJCg&-d&&>s$QiW6tpbAs!6C+S)*wuW zg5+RN*a{91FW87+?~AzQro_|RZ7V{|PtEf!+sBWbqM?aG#9)DvY(`jc7==(Fh!1Y zWd!Lea}1Rcq=v-xt#>WU-YSS)g77swBzDka+D$jRcMVjI!~D@w(XrV0QMkSnf=R^E zhd){*aJl1bZn1^QH5E(UP&?9kUX$f<)TcVXpP=)2x#DkJH2!j1*g?|y^&L3PC?p*v z+&I*X@#-rs<3mdJ3E`C%fI{}Q2Is){E&Sc)h6r04gUc&8kyW6JP5^~v{}PBnArVuu zu$8j#pcz8(8m3(SCg5ZeF-h`AXC!sG%WSJSUW{ay$6dz*|s zDnbUE5JxEL?w`ut%HU>B`U>hFp2Jgc(a9dGaI?%k+b;Z^e8rBRO=t;syn6~n56XRa zm}S6E)2%dnb8*a6?wS*CR?OBCVrUEVw68vJ^G4w=OPZ;qI42ouX#LfLiU63$qmrp4Rhv+Sv}@wkvS1mI51>$7Zsfpi48 zReRpOhLdz)8u~%o67*0?^9m9D%r3G}KRuhXTZjsg97b|#hS<+-)v&*jK#fwE?%p-n z&WZ}QV2Ce(sT+vC5)+A0YEkfk*jOWG@k-+in5gA3+wxbV3yWv3J%zcV!Q$D6pR2`y zoBgfDVD1P<@G`i0T^m1|Yo#z~zdgFZ3865Z>2==JW7l*>!V8O2wjw4_mvXOG5}E zRa#REu{DWqB1NAHq$Zs1&>NM=nF2o>bGt)i=PHe(GSb)}jBY_&5FhF(Mx60y_d~Ss z@P-q}{sGc$qK9UkoIu=RB?yEDr^&PP`;Z50_R%zT%)xL8+f6?tyu%00m-bVKlVyZk z$*#bzP|`v8m`)&uTnfL=)%7eE~q5BP6@9h)rL3**1v%V ztn@sE;0?@p9EnHqlx|%=A};t4>)Pqf1H(cEV6D+Sw}d5t-_@fm-Zh3c(s=zf1p`*@ zuz+3gWDL((w%#H)Q+((43IPJUNwxJWQNR)UQQ|KMP9%-3y;DTV+!0_+oeSCV_rwJ- z0aEM~6-$a3U%9ett*P_K(*s2oQ58oiJj7ACi{q~<4>uXaK|Ru`;&g=Bfv)1DIP?=F zP+AHH;&LHd$x|f+xa!-?I@x~I8Ezp_kww(GTxKzm27Af80R{~k!&kuNfN#?{v38oT zAq9gGvxFwDg4jN%3q^jB(JrR1=M+0tYN<>oyyU;tuXiKK5h|0->BX~|w*$bU>uJe& zuss0IS>ZFm4uP%ab{HfHrkGpsWcMU8#85C;E!qHSt2F)&@7OJuyx7S$AOo!)?oQqz zKyvB!C1kPXB99DoSj(FblY{~9L=h>qZ%H+37HR{N6rPLC2K|WvNwvHl1m(^+ zek`MeintZe-=QL&BJk3`71tx|(-YCc=`mukC18|7RkS0~FU1*Jo~bJCv{i+ey=1D2 zyT40SJpH0j6__MLQ@qBuVSitLRk0XZ(yHPbP@VPDN1mZ7x=|83K?p-?i3)Fy@}%&W zS%q=Xre16ka!U1ZdQUkHuY7uBN5kiU)F|}?R%C2F@gbCg5SIJQjaYl&Igv0ujIv^( zC$Rm)r;BrHP(Wn>0^*1*rhR&1E-o%=`^7yY{E)C!Ittb zttYmCP{+~yE2P`=#n3(JYWh(JM*MeD=TER$Z0d+*sXnc7dXKq{N`fkLUy4}f%T$>l z$$fBm%6V5xe-K$45lNqvsH7h1BT`C#tiVDW^@j+*Q-8cp)sMSDNKNVwF45ziHpS~g zf4q`Gz%)-lGA`(1^)L@mpX*Xl7xqe|=_9%U0^qW9L=rd4&2B@9G1y?G3XqT(=%EF_ z%xI+DI?=97`7g8wxD;~l+{BXzQq>q@(~IbBYKUD4RcZ*53WC3jS)+xYV+mH|5z>0J z)DQ%h))3uz!6KsmHUKk0J+ecM&a%wEigh?F_(z^+tYLH^kXnL81W=(RI;0A)>lGR# zT6lB6dbzpD#v7=aEROl#78{B{ZCRfp!G4~`R1JcW?~!Q~Fqm$eJ#Q0Qrj^>J(XHS# zuYD(_WY$5yp++H-+C=_Lk)^-lLH<;!b5P38OC`f~jkJ=XEpPsj8KSRH`*<3Amttlh z01G8E55N;i$R@KEPj)XVVJK!Oj1rW96c!4D;?Jgmpq)@XlTZ+I*LhgNL(7ni5hGO3 zc2N;k&r)RJi$>{;DrYSd?jbB)pI^@_H-KwZCR|K4l*U7dZ_}9SOpdm*1uTS<4-p%P6TAu?0QW!7{dVWB370UUI*#hai#$4N(Ni@uzdV!YrS!FAXvh}B5d-0|k| z{DHy9ga_ZE*6r7UrH}T$ukFo`k^+_ zQ`{yA4_*@Y;T*{9zAI(-*`?5nXQgVnNKhEy7yKaAb!Viy4#$pVpDj33rlnb_k6f7+ z!b;$myc(;_vt=KZcEH%vWCzh?!?C;})!_p;n3%FBWS3ot5{N?2GOt%r-(po!PK5c2=hgq6Lz-}yM~R#cp!I*}swC7v?+|oxM7;30o+wSD5Qni#kfFTu z@N+f-0UYr5u91+?OYR9xIRoqAHaZiRF<=J2C=tUjfX`PGcdV}VRSkd#D!`8Al$~g%&#D!*Rk3}IdDybi3$@F za+X5W?0}j@-$RUlj9F|7V@ycrNt6Wu0XL2JAmFV;d>sA2(gYMoa84S$#PaQu6R~8@ zOe|4pwse8`Qk$b!PY;sr9p24jT=`v8-^zHl+^VI}-)RE8aaAOQk5 z129c4Y>n|K&B@_q6aIiA06jNB&kMy-xVTBA-P}f6_2L;l$`d6^Nf^kcB}853Bmp3` z+L#;`B!-O;L!SB=Qi8)o!wbj;gu_3=+=-B*r3!mL$Y>PC&!WNguN6~oq#beI^U*oc z`5v(902akvCGBA5u*ijVha-Nyo7`(-X)~2-JWrZ>I2Q4})7j+c&{!Y?2EQ-rc59q%a4NZ_qUKcs}e2WMJhIDS-&2KIw${7nEwhp{fzP;_~h8G|M|7cvu4c{k$BKPqgT>ig3k_R#)TFT!mIVQ9KnX=DRK_Qb7MQU1nse%p8_kAze?>A&6V!NWV$& zGT466ckJu}8mQNVcMia(L_DZSX=RVPRKF?uPtj#@G5Q20e~-)=uTfEUQUQu_9LqgD z`jzk=ru)4B{r;cu7^DRX>;s(ZJ$Nn;MpmWdNm`IZ3s<+2obeNd$$|p*oEt5#^=gY8 z-v&!lEEuovLhVE_RdHWT;!U&w#Lw|+?^xy7F8(bftVcz|S290d=J@drdJc}qXAh?a1+`VLJ~KyXK5HtXekQ?#$_G!hnLxlxiWi%Uj9{@En&s7xB&P{4r1C_whASqYXvy6g8aUxv==jiB<_lgdJO<_k zf<=_b0f=H5a}i>E`Z#@_XlC*hxsl~9q(dwQaD+IJr9?`pbF_Gls#yUgaw^^g(DVGT zMP&Prn(w?!zEmQQ6EwK-UA)_X(WkU&ywGTCKmkAAS;~+17x9k9dBEU+E+shneL0fV z=;Si*T0GzsL{~uHXFhU|Sgym;r_J*jGF-~}3<%RuP#hsyI}i}O=$sOS4)dXZ2V@FY z*l|jj>lZYr*g3-4AJD%z{4D!;1!72XhWxAMlB(!`m+~8KnaLYx{&Sm!jjy;N1*aF= z%*P<318^94=moTSfSv8({hg=>1jkqrJ-h;MSW1)DO92=&v!;j@I@ezVe8piF+|{Sv z6`SYJ2EJZ!3wAu<_KoDF4Hmu5=xNPDf5&oLLgzGC?C>C#Gyj&^pf5$2qRjc&bDQJW ztRf}yeRk+^*^pzDZ8&}>ebKBEl1ySg3(kLH&M5w+S`C>pTQt@~!61eG0^9)hgk88Z zcvh|U!*7El`UV@pc+y~OiOf^cK5Bc6UQsP>nEW+5Q>@c^jYT`M}RB35SMzMh)Oan;y;gRNrYD&xK-<7ou1%sEK{y2!CI_aq4vtSbvnlHg*3`y>f8 z5m$Onl0a&%tUpOYE)se+I8Ty7w5|zVP5S-2@X4k{DT$QErPHxvl4IplX5KDi#W63HStVn=KatZBe#6K{@7Yi)WT*f^Ih>2~8GnW#QJ>`ammEs1nRZha$Mod^D9u(qV68F4FnFc!_ zx@J*M-}q4NJc&iHHRw#3hX^iGAn4~zkZbF04V;Ceja$r?b-)3GP8a@nTm%Ah_Y)`q zAw(08A)EOh=`3th@B`CIN04B_H_b275o}>_J&gTZ>LJ3SVsi|<4p)DPt5=3Q%TQ(N2-a|i0yyA!-d9Z;^r8}Xc$<#x)qyTC!n z*(KkOekk5d`8HCPBV$YFkGx^R8vXQXPDfJ{?rX8bhwSirJG|Hq&$Yu-?C==K%W|9T zaGf3g*$$t!!)12(kRATm4zIDpN;|yR4u{!cmK`1{l=bYj!*zD}XFGh_4wu>CLw5M@ zcDUKbcby&n*$(@+J9U4xaFiLY(RJt+gZ5EyByWIp4QE7@^1_3~kwO<=q4hrDR*=@F zzWbr=UF^nXm;8zl2H?IkoO&t6Wl)N^?_1w#4R6zu&QK3H`Y{?G0D!v(!_Q*Ojq>1t z5zYa%MyKNntMNO);T@ra$(9WGhU?$=3gd6U;a294yJq2?qahjQk(u-X;xb(LY%Kx~ zYcBrJk1qP@L=*yMo76oTmSpH*a!x)^Y&6eu<5Evb~SKGeKZ+1NS_xjYRVoehmA(a29bgM&1EZ za4jS{b0TWzX$;V!+@WWF{v;23k zaoj3AiWjT!>tRmEIsy?l*K9|e*?lu1RKpAE`ZZCAR)f`C7vnLp0E87MqQtiDW7)ye zaAV@BLcR(POy6H`UI@kA7vGfPM;9Ia+PJLXfH<@!9*<=OPsQc_K~Fj}1Ooeip#zy^OimY)Ywi2kkH26I$GQNuumAXNAMFx>z;B!y zrF$4Rx)L=`yv23qb7;=a6}@5xZy`bQ^Fcv$r+3Yn$SCF#=F>NkYS;lS3Y(Lek%|HL z%66M0k!U`JpC%i>jC+=5t2GyW{3&ze3Wgn+FGmh2kA6)A4A)zT$HKi9gT_Xax74{y90iNK5QPA_zJo_9?dvpYM~D0~jcgIZmah?)^7!L@H64HbMGT!;yxFFN%b zZSU8?OEuG@b-fWBA*X)Q>r~tek0m^AaOAVabXZ@8uTv2l8WA9&Ju1E*J>0d!nsIk{W+%>zLFxd zf{D@m#}UCD#~0wWDb0^F<*rcfKo!Hb%x4tN1sGhk7(7+g_oH@s*zqQaGam=nL}Ezt z3?(_AQlmNb_}_RoDWr9Eo6A5o>tF9RG`x%sOh`UZjG5(R2+!!{4@lK&ribIvX z1M8l^@8G&(pE0-^uG7dVsKIAcirBh_3{3w3U=7?5 zdCaLq_Mjj$VHhadcu)#o-Dm_3f#^?q{jU=NlFyi&V`M90n2{S1rJO{6sG$|3zH2kG8Ol05$3+OR5+vyc2=^#jYP5-~BB}A3?|G{vLr7Y?x z--C+ZP}r@D&rZAon#=E9Xsuw$wvx+74n#I3`ZW6nRLFLH{V`h%y2pZhRD-+IR4~U; zd?qBhMKtDKWChIiPmqR-dhPKdS|Ts(;`MA00BBFPlX$)K4PaLszW@Ei9cZ$hpOeUW z$|@Ya{L-&?`WrTr9^p>EqIRmKEt8@-S#lvkcnbjJZU9M~-B(^&olz8JYw;13%(4^^ zsj_7(yO%AD=D+zE(2iI4vIopR!e5ob-v&2j(4pfX3&Sslq( zZ!#7}AqZ>5()&yI;|&kV zv26nf`Sd&Bwrg`5Q}Z}h_38i2hOr1aTWZ2zn5$QPqfhU}k#9E~klcq(-yrPAjpD+) z`5*%p1}OjVX%t|*6m?sL3vlmr;#v}pRVF{By(hOs^Um-sMwZ^(fWtMGyWwb6)s=1~ zO#ifCRppT|AIYwY299zFmJMF~fJd-uuvD;V$d_W#vu6Pv!J=--qQO#|J%v$+-od_b zfuG<9C2SC;;E4~Rs#ZfnlA^Hrkyf-1l$;aibp^R@USkTQyc zQVgImcFxda8C-c9A`_>eP@lb$!IMX4Aa#F#j{J;qJJWFj^j5@~@1lbfZy+Jl*uiu9 zgiJKwD>^v|SpYpY951{w_7dzn=@`VQ2?vqVWDMpvl-jKKz$`<+EJ0jajL)LL|BqS8 z4LCipC-goT=Bp1$LwR`suF4z&2vQ%|M?VbLdgRdGfuRFH@@gL(i>z&0R|J|1-?Dip z@CMmr4NgurhF0vxG6hbn8!8V= zguXzL0isArU$p>7jz?Kz8n%E>EijGg0{Hu2O{?%*08hRv)E-^bg?~V7^8NI9qGk_s z^T_>Ww2RNwj%A0=G+d3y1yqKbSE3)2rwINxzlMD36CAk0^T#5W5?O-shHD1iFp$tG zM!pBnbP!{k$aMwgv1|B-Tb*`=zC;tMm>72zA>be6ll^2BcHjb& zY25YY`}T}Oj7iw*`9~3B6%J+-_cWQmf~XSTtCEbT)_BL`Qo-_ALj^ze7tA`0J2Pq< z9xNK11!p;Ct_>Aat`81^*)Ho#zD=t1@xNUjS1=5RW#ueVR{IB_Nu^rCsYdg3Tn^BV zgSDPKe?>Q>wEL!fg8Z6LM}+=R%CwZt?p;~5S^BkFS$1>e;60g>U4gGe1E zHoucrfr@5))ad?PYKV%h_n(!fm2hUuYEt}+$xf}QCJB4Qq+RN0bZ%`1p4Gk z^i0x6S?wzrATr4QwcEFS*?-l(ICv=4KG`zWZrLW;vQE4E;Rk3h>G7QJhFF3@f3C_F zqvuuhyqVFOK7Kk`dqD4M;8Wgd(|R!69(J2x>ouF>1Ufiu0RMqZ=!0js$HnpxUFq)1 zEZ++|G1Z*^qPxobcbAkWC+V+(bbr|fkKrjk!cV-|b^5ThlKx(p#Bb#7x}djLZ$}$1 z@6S)W%xlvKP97g{?lKyhAQ6D^9^8Hc)}ApmHS9L0huw_L;3(9DyU@W;I8(C;{4@#t z)J}eC!Px5zeUi}wDSiq#cZN<)ehy}k?elW5XtCjjngiK75atW|F^*Zn;?I1Bnm5I{ zE5&|Np6vOg-}Jp{+EjG{kFC=qRCNF8xEw;fX@eZb9B?Vnnuz=x_LOu$M);BjyX`cuRxJuU}O1l7jlR*T0G&L0a`S`_BZgt z?6+ZxMX$$7B|Ki>9DJDs$E5U~=AD34(W>uOuI|8{FKsy^s9AZv3ngQ}+(5#-ob}3d zR)GGVjmtrC$1AR;#|#MFz1LbCI*S+bHt%x}>If}@0}367nY6OH3U{9;&qtnP)?y`G zpNeD-nyPvW3p*SQ`+nqcI7T~=AprM10<4K;ai)u7PPq7*E#NgRK4^nq-3g`;jx79) zn}YV7UMw)2E%3($@y`%E=TQQIb**nex&1E14>q2t*RmE5Y6&XLoP*4`_#p;+jIY_L zvN!|Fsb8jl$3C*h*7vk|Lb*m&aYa>0-LU%4-J$QPHC4C(`P9a|EJSNDr;b;l#xll> zajLmwGCOI#*E@`?b4%BIufh<5k^NqPx_+^UAPL9;|jrT1FoZBFi%x~;(P!l?{v0j!$cctHa$jop zOdOP>Ps~#y4*?D;!ZY+wsywIk8RbKtC#EBt{z2l02>R${>r=0>pa2{l&|C58%tifjJ-ba0s#Gh!4JDJ`Ps@kW z-kvI;S(O*Wx-ZIx+KFjZUaCJ(PPR9j_*U8Noi5ue6uADL@Lt;bNtW)zKV7~RLLTLH zYHwTtRFxO{R8*)$*zV*Xfj(-mk}Lb$Ct^I8z-fDGJ(`4X2-aLfE0OX-@$G~_0K=bY z{$e79MmI0T<^+Wf_f|l|e83!xo=$#}ZZF2~xX31N+{4bbf*4WwuuHWuJGTvY%U=|T zPIG#-Puvi!mCCA|Q?#y+e1mpYHRhaxtB9O_?PzxBAO7ey*=p%#^T((FD0sUpd{Ox>>ADe zSE5=2t)Fr%5S{XkeeV3*N>P7-vU=vRTJ9v9I6N^9bXQi-DoSEP>~ZwX@1c{h`B;C? z$Lp!KcnyD^@@O_l>aA#39>aXc;VW&%MNoP8;uB(`M}T5!Gs;XxzRw}&+P#)_9ib|9_dm}yeSjVC$X!+>n_!Vsq55ZGn6R_&zFN|M0Im{|R0_iYqeiYLx zs#3Fwc2`gE{KR_%E$3_Bfxk(2c|2s>se@-fFnMDjPtA+Fm^<1eVBgfBxbUvy&DI#L=p*?B>*Bl~=% zEn;eS8dBl)ee`04xKOnNswX)__6PUTa$o`#*sKSEP9lM3r{RNcxk}^<0>B;|+GWme zy{aIcPSgbbvVj_8Y3Pf@Kasg#`h`UOlU|_y7C^(w)Tj58pQ3y#()%?&u@0G=fESrM zaUNbc9@4~GPI&wXn~-cIE&5QkX7 zF~HK1P>?3rVDSJpJ@{KP!XtE#j%2gsdeU5uNBrq+)n3_Y-~1%6=G&VyzoO> zwto2?t|{+FAG*_O8@*cWG|Ke?$o0Xs+Cy%zIQPk_ zH8Z6u`|An@z)5sG3r`~u*%)(Wbf|02ofg172wh zbZ|%Ac~lJWm_%m`M2Iy3*gr96o5#X8A6r6)dA(kA;Xo(K21fw1(9pJlWt3`t^o;DO zyFsE@+8Wt|F96pCKr)Jh-~cRxBLan$#;-tFk`k6?=)w%Mr|#^uLG+(rp)WHHDxYz4 zE{PeuOO4**Y#VryzvBJ*V^u&^UdI&jkU0+}5Et5?QLOHaG{9-c15~58uY}CvdK6cq z>e<_%T$Z=Z;~bNCS@3^KkK;p=tnIjz}*mA+{>5#;c&Y&SbIZ3`LS3<3COR4$wckP;uI3Y=WOl zg8n|TE3pc7lKNi?Eysv>Nr|51;1pNu`t(^44wMeNbY3W1o~&!nBRNTJOIW-Um_dt%xnYQ>wYG zhzCZTK8`mJO`zn%ldm)eW~?eu8at4V2Fw-``j9yX5=A^cM2}P4o%S2l5FX zoT7dd=nHKy6!JLvjIiL8M|lR<*@0?d1r!Eki=c5;yrC%mGiTHo%~K6B%4w`~bu<9v&E5Es)! z*6Q?~)9#DTEA!TWTk+G6wb&1sV~~jqja3$W(-j{n;bDY8bMH@NEPU1xv!l{pDfpE~ zpFm_<_-8}aEzIJoa1Q&KB9?K?#42 zIt-j}(RXtpi?<+R=JguLkB;9K#75&!eU132*!5X$mCiEdv6tBPijU@Bk|E-wUm|nG zNB8eXW^eSyVyh1KT*?_Z^niH}lC`g`P%<@5;ne%4Rv6H<6OZiEcM5^++?KcxoFe44 zq`FV9&6Cq)Ac$?SO1Mp6zduXdnoo}cX@`Bt(n~|*zu}m){<{#zA`vzin`=W4SHnYL zRqOk~e_m}rh-v!>-a$uuF;<;$w|!2dB6_$wm$MUd}6`U zqA`;A5JM2n*WrcAXrDL(!DjG2glOPYG-r3;c!BTH*;YyWU~A~`l$uY8@1*FA?>U3{ zsZLIeKy~P0+0{Zz^_9=qpEEar?%1RaOUa8Sz0dT0M-9fen*$5n>a3 zLzlG}zrFC%or}F8p^;eo-Fh!Ar=8{(;GaYt3a9l=ZNuku)on*n$j;h^FRUxL;|6=- zhbWJHhMfGgn$p)E)!F&r*K4``+1^C4bzf<7^T%2KZP*ynqBM50h_iF8f*c}2-L?-K zQ?T@g{kf|CuWG%uT5mzGaCmsNz3$|>{)(dyoPx>&`djGiQ~c38oiOFRjdZ~);pZ@@ z@TItq6|7fObSUA;9Auo=)eVp`7DiwW_6$K*tkRASV(Fj8wB{f925n8QhyB3D5Kv?* zYOlNMq#wK;$KR* z7I{@T@s!u6K&p?GmlbJ8#{`>Em0606$@lE>f&34gS&dcK5k8})0Ca>rs2kST-*9d? z+o~NfL$SARC~j;}x0>)uVM!3rp+oSP@<>`=eo_B+)K3ETQSQ+;BU!ek0kW1%|~IS?FxQRnsItNtvqSJ zd9@8r*l?wrVe$W?T6yEs>Wa^-E zwcETKMnw}WLCo;v-F)yr?J|4z1GpE!n`}=<;unxrVtyPgEVx??UVO6i-;k|;;n%Zb zmbCyoKM)9cs<%Te7TbUyDbM?#z-L$RkdK=rh+cH_T`WYGmsfv> zUay9KIOfo&^!ws^@&pwctYS=;+W*%Bm{dEYVpFabn|yQmOiI3>t4gvtdku zO3&kU8}*<^bZQqTT>w{q?0V&qL)gZntm;&2W;_WAy3g7x=o!%WnhS7u4=Yi1%#h}@ z0K|IOkXYhC1BsksC-GG#ueL7>ipr@hJpiD7e1h2@4PnI>tHWQJPovZO#w)0lxDc&N z*_+k)PU#Te<&w5aI06IBZ_E_sc@ysfRK*>~453lJ;@;MI<{x4CCtsEQk1wM8jcVBU zlQ8xK65$~Lf~TzDDfq=a{%3eMo)9$lYa4U*g2YdOfwae9&t7U?eKB|f+8Ar4`^kdb zGi1vvW!05VuedhYD{jvje|KmMsF;QLEU)B9%q zx8svb?e=|FWoLpdPhQz|xPT!s1C>!8d?&Jlzq9-%OqmkH?eek*VmY!$uxAq};wY@> zDSvb(lof8QP{QjO&92n9!=H$r)Q*N=;G&qgh_uE08!7Mx1`v5IgWpjIZ1)EcgiuMj z5)u}rgpU_2(})VpLYy5*@MCk|*&c#8IX(V_XWuF&+@*ewWvC6 zKQVs%_c5TsV&gAS!Y0B@K1>+>8@Vz^iSVoBf~UM6RFls^6v0&;-Kh93RGdK+=I6)B zPnO>UMV`3)#Qa}L~EN^KEmF$JDMIf zW=dRwWY|NLSGT3o07|5tCem0+r2A6oxpw*yAZE)GmM^wD*&_c~9R&WTrvSf> zG<2Bx`NwEpGrX@&QzCnm@I=Tb?-5=fZ7Gr6sFl3W(G-b!^=zeavE4F^rt0!Ni3Oti zFww)Dv~2t!<&lq&LsDovo3W)$XuJB5)*+Wg#*=xkXVTuL4^9nS(jpyBpSHp z`zYIQ80G+mP=ulc1VO^pHxQ6n)I{}M;C&sw@zD9ozVuFQ! zI8UpURVtj>%`#yS+$#|)2q1MULG^23z&EjK{9vU9dk8xMZPtN8jUU07$RK-bO+yxDU z;W5iGoS0921DfUpO0hTtdDEhMzu}f4Z7=<6^Re5p4es9vCW`Pu#c0J<3N%I;lfHp% zotGRT?41H!B9>YE7pxtl0kv=wQ|f|q+%+opL-nJ7!+x5^ya8;5V7#})w<$JwjS9>? z^j@v&9k1?WI8f`&fdvl7HJl|Cb0{HmmS8KUwby4B8Ux$BfQZU6y)VdbDAzPQKq zo(36qRmeE4B4yo`-Xd)KX-YRzx#e(q;-$-mSXdO zxF(^*oCZfd9NSWGR%6+VVpoYA!)UKIyl_9pl*nJu1m2hMEO1w$70Rd$AX~>VjOJf{ z3kI`rMaLb8!3TmM*Th9^EAV!*sm4iw9J-l6C>P>5MuH>VDu5){$?Gjr8Q%+^$;u8lt})E_0a%9qe4@TsZIU%2&7xV0?BQ56kb zY&E>?aCF1A>&E?l&1!R(8@m?OK`mTT=k%onOG3CE3A8G2hi@HU;myAq7+rK3=oZ22 zb*|ZXMVRjq;p|k{UN@YT0;d)y*CYA??3usJHwW|vc4a^f+rjXg(*}qem8ExsvOt+Y zkeN5q4(@J zm-6UAzR8Ju&i`)jJ;0*6y8h8K6vaBA7_m>RprW8yP>eG$z#vtzpiu!Sg3^RRP@|%N zG9rp4wrIpI#TI)Xtk^-+SfZlQWRMtpFWlcc1%?xO-}~Gr`M!JqH^Z~|tzFJ8Yp-3- z-uvurtL5$S^0uKCYSg@|^h&@zX@rpcC_B;=8pTwEkV;o7(07c6yV#Q{@FGvI!I(I*SAeVV$cILfWbI!B+Zs~IU${W46~j%nfrtiV;0N7)vljg_W!p@?vt7(I>yhmZoli~;C(&rqdInGY#=q_-HzDv)VCdjv@t zmP3=Y(4@=y$x}#2*TsztbQd$aKL``*hy5QmX<(ozcxL1=&;=y9KstK?G6H++t>3aq zzY3Q^xSW;F{u6Pc{!_N$p~d&`fR51q7~gzM?^e14Ws04UxG=l&seP4C>HC(>OMz!p z5o``Pg^h(za2NM*ToB(`E2Pc^hn$T(Mv0oZ1^-WFL}MS~rO7po=+$mqZyYLqHksZ%u=jV12eL!#NnN@Js}g^B{bV8?z!f2BCBKr4&y1diZr!;d}xdKSxp~()V9S{%|zMwWeM*J7Gf#>tQ5}2~8 zFtT1PdWJ1lXT*$BYg9g*Y=)6s^QE(|A?c)r_mN^b<%arCnzl~c1OxOOH*TNq>ufHa zzZ7PC&ryOxM=<935Dn!5TfS*-il%EIR|lx(d;{>*Z%jtmDAPu%h>>kH@mvHox?QeH zlSjc3Uxo=S3JNw^iyI8eF(Re%Q5=y`d>JvAOt&S~wb8XV!B~0j&(w# zi;d6{?l3vjL)H`0l=cE*M>GL^=TuF5FHi=I)P<{kcAeM?Y3R1DMh+Sm&akP7Dq+# z?_=DjPOnja(2J*$oGHa9^pq=*Aib{ifqx<6tfO@PO*pad+^L1&(w&V^nU&Z^oRLpP zZiXtEvcq0{0ZxS#JuEAr_Os=O(FBrCvC(AnGNtrz#0(2X@~c+LP7%n+;I;vZ z%Wb8UU>8XqTOE`d9kv!WpbFlYD!Aq}UnAikLoK-B(iBpTcWeeZxd)T-^+&3dRKE0H zJpVfsI-ef{>AWbI*an%}+0^?a015;JcmTsEM%NiNA5miX$>v>m2vH+Uc8wq%8k~I6 zpmZoIr_g=~AycP@=NV~AZB%X#gFKw`(!s1n8l@U0=5x&o|^^A%s$ z0h41CozP`DP@{uw^az0OvT*eVlD7{N2GqW=-6xtMs)Y1|aM_jv%E`{}KrJe27wQ$} z)5#|t5`fA@P3N22rR$JPw0BS)OHwX`M&(1lH`hreN*m%GEHv}Yb;PG4Ygh)9-o-l@ zWsU|h)V5HIJP>xt5RIhN6&z`@4QV|hF6{QE(?L-9%7 zAA4#(NmI+Avq%N?FqADewRGAf?RQXVR<^YOmL7u(eG=2C9ujoD4%ZUnvvsHj`FqVs z;_E_oa@t3hWJ0y!wKOe)2F7)`>K~7f(JK5R?Qp^ttvWc~PLAX4?jsG16sm_6;EKrdM0;>}xamUK_)a6rvUIAr@%4ooEuCixvDiLV9BzfsN~b^zaOS~K zfS|#piI^|tmO)WQhoCOde@z~I=m~%=Z(TW6aMVk#`AO|r*<>1EPcWyHc*B4orSoPZ z0N#o${!-F7Yz1c{M6`7%!;AVmL3p~Ah@wJlbaor2RNhe$>5gIc6Y`>>QugJVjOi!o zS{U?Z)cVLU9#4L2fD=xMnQo3&{8yB=bPv`fp&~wI=G5P*aP?&hoeC=5;{%zVN_uav z%5W)H|BiKht|Lk_<(iqbC``5w8i4RTQy-}7d15c33GG6j4JWQ~kd%&Qho7{Fj3(R% z-5#ogUS`oxV>KuNqp#6t`H}w_RHg!)(UsO1%>yh);&q_QsmTWFSTxb3etpq=;QTuj zKZ0w~bP#pomcJnn4rNl_3zjic+OQsmA4kebKzKW#hbh7@MK%;8EWOzFBF+=>J17^_ zAD_eu)$7vL&;%^GLba7Bvlwd3!gNhZy%BjBIt%EwhvuyAXXKx^Vo%pgK*vYzK#bA~ zxtFHQhD>nQLTNYhhmY_P~r>3spxs3JH^xdMRg{GT7Gh_dyZjvXAEH z${O+gF$y6cGX#83Kto_`BdW^{i8~h3AehRM2J9~77%)dsxSC{UATsBJ} z8B6IN7s#d$ymOVr6dPHjDNZOA;YrjFJgK9pJ|Bz&5CUIM3?QA-2$C>1>CxdrT$@cT zzdOmAt%CE3qsG<)eS}TCducmZP!>_+(P&a_=33ZwXiV7Vm1}W5q{_GO;-)#F)4;`p zc1U7(!fD+>tiGTb@YFnHj}O8AL+B^=6Xhmc~aUQ z4HDWCc7jf~8yd{Fd2FaBso8mpuL>?;b~L`WJn11me8>k+x#0TH=_aLTDeY)8reVJi zH5xBUi{OPiKfd%yMQpX5Fcc~M47EBXL90(smbLn9kz(Vto!qV;-11QqHO>|As zNMV-H6al*hxthCW3^Vn}FiJ(M&FJP~%7&j&e5R8i&1m6EPec8o1JoZMlg>U4f8YwK zXQmCX6cH6Hm*`UZObL<^3P+l9AN~Tx*y1oRorjbFVK*wf3!S^9^0#lO7gYTh5E(M)M8^nVmNuPszEec1V9 zF4?RWmd-}q<1^3;89?#+Lz}5Art6ZYTQKZ8fl})HwA6w`>WahJrKt@N47FmTV0Eg7 zePz)fut%P`fa}2z1t3iwizwim#(G!4R%ixI#^`HCF1^J;Gv+NK!F{qIAr9Kr$_^$`he%#87^IXu&Q+vV+~Zhyht`AdE4o-;~b(28yKD zk(TD5s`I`17F0ec>+6$H)<8BPG6Ye2h044_Z>95JQo^QNNSvFzR=l~UNdC>V^xj{s z=!=W=ilP48MhLs~1e!V=Thfg7m2f}BQ1)}Ia~>IP7OEgOa}Bnf8QT|3O;9aMzTe6- zqqzq2n=^27Qw>a{8y%(eXo{?UF4lS4AD7NL0HdeotaG}J<{eE(V{+X{QT&^xNLlNt z0yEHxbooctYr-#XRxjK#d_O>w4BRpXFTlY>8UytNOaf!VnC7hw%DkvQKkdZ6&YyO& zK+v?y0k`|Q*}p(9g$cch_plpAYm}}+BY?>8k?Vku36F8L%zzA|mJ|j)4oF4sjzg<- z2DInbT)l@20+Wj=6dyoG3$T-iSq*R#qvN4RN0vx_Q&Enrn_0#YT5Z| z3wD0GHfG1`Q}oGN84e9d`18f_7t%v#Bn3E^08`T3_k8KbFE=2rpl2Ass3XuLbi@X# zcw9l+V&y6RxQg=PbmL3#1PAq>GBG4?Y~f*o2jv^?f}upXF7k7U#>8cIA&>8F@*G{g|NBW7tz zIPzbXR5>4g+PEhlQrRx@&wdZf&Z^Bemaz{RU{WTTmS(v5oAegu-b7dYB!8|oy)P!=)$8$45N-={IE(jYX$qBAa=$IGMNd=H zLt}I{ei$Mli}FTKPGB#(ij80Mj2%`N|C-@cPKRyPP3=HZ*oQW?9Q_QV`Z`}{i7_zF zH9g#lIpAXI5Yq z!WS*fTg=d_pu8F8-?NdTL(6}MdB8`8n#(Flk!r>}ny`@0_Cs3W38%|6y4@M|`8${e zeZ)t}nfm(2eSge;0IKesJjtZibZbazBeiHjz9uMH_jM$g#rSWaN=E@}nuF@#JVdrk zXt$W`+fpyQ1q<`(^(MhDUl{FnqkF+yV`X^@=eeX$FjrxV7Fq$fpzFvN)|^=RVr@!; zMW$ma3HF>HS7>0ua7UG2CbPdd{XTmh$Xj)zz6o`wHo*c94O>Ufw$LOXFcKs5*?-jW zbXOCsa_J-}ZnhXss@t<2Bzi}ce(xBA30HbM&^*z-g5R>O5Y7QM6_x2_<%5IvsFLWm zkyuc>YHR!~PRnuBA)nFXmHw-IZ!J>=4LA1)ilx;#45F0wFQ(SlT*sQFThJZ)`Z(w* z)_yV^S}OWZtt(xfuQ2p9(YbSRNK=X_^o%gHl0W0~C+yk6xpnprr14_&zYvoK=O2bu_eEWsmjFHx%>%+we6PG{yKFOPZR7 z{N;Zzz+O&lOc;Qwun7~2sHO4drPKiweV{|>d(q)OJ+b}m^>K;%4?1fN%Z1Z~B0r@L zhgpT~Xw4wcq7U&{%GFgU2v?f4zk(0PHM!qUSfd?qqDH55mk#j)q>SLHW}}9Zv-D$Y^aF?Ax|UK2yF!yuNxb0zsvXt znbv_@CHL!@g}9}ubdn(A4}=N#6>3p)Ww7BMvcu4dYg5!MPs=UnTUtQ_HO8|kC_a)M zI3y6 zF_DHzA!p4%BdPOke5-)n3kqxsG1v8hWUwA86fS*@rdJ=?^dJ~ga)hJM4E&f-R(2FC z)}>?EvZGI7^x7w!U2cx)NA??@cssZJ5(CsU3-tFA?8-N=C^N5 zo2bj0Xp;*hZ2L{e_vp)8&~KbAV{VVGp50R*O>Klq?2)0s8hzbV5|vwq5gJ>M+7@NC z-0X1%nK9f|$)#*0=}qh{)TPp7YHu(oVf!CCEEZ!vFB4renm?fV3lh01XegREFAI$y z-T{VTP@%G5Ct=Ww+bvK94a3_(=Y?$3fdNJOKnQpQCmE}o9#+@@P)PbP5H((hh5QmM zHFqSAd^|q-fNoNFRi!j$_;Sp$9c7|qh{kuuS5I3nnH3%XXM zQ1h-Lsj6B9rO}WVn>yPw74~HZ_~6QRuss_;9%366deuS(f5ul?x+{1qVAZGuQQ~_w z+RNdrIjdj~%$rV!vS7JekzZ=A#M<{++9ZUTMZ7^>#ug8E+cSL8u)2Dexe`)XpbmS1^d}dHDjYHThI2FlqeeS;=3b^yfSrm#>gPk z5s&YKMS1BI{6hj2X%f~)>3A~3Ki{7EQ7V;fy;Mkt7k7RMKG}sXt8j`5iL|%wITTHE zQuhKt((H}v`CONy*}&=@rcmHThThsk>Wrsu=5VFXz@KB_XFlVdzwj&j6z zff&S5gxLG&rn%^eS*CO@y6*sb+^jHe#P`9^bdxc-z+3()5Bp!^ zMxz`@;N8dVGzKX6Ap>$6h;T;)L%(mvp2k$so_J_b0k9=l%MC9fi7w0c74XCq}-*|9CFkNTmi@M2!-g%op2!?x6gbZrn6r9B2iWhSAH z$qou!l7gWjMmXB|C;Iw=!wsuw4DNZ@gDUib5Ac}7;|d<<@Hm;raXb#-u@8?Od2GO=0gKQvbyL-81vl4lT0y-98U+>C zMR8}3>Yq1RF`G~-Rn zwy}L0+wbd)NcU(i#ZcW+m61uyJzOlX@-=0rHrNiMYyp?V2zW>?C@-7I_wW382XhUa z`(O$HFOTW|e3DP|(=ex9S<$FY(<~07YfJvnUbn@^(>NvYP6BSBVvqHILKZko*8a}K z$Fzgm7nxx5$_`<;{l=Pex|aw9M>1rXyJ4S}5gk=egPh}>J5JlexVDJ0W`8|Fy1yaC zc#Jh3pNKP`+-+Fjm`3}Aw=0^DAvYY~XN6&BA-*Ir3fJc5@e#60;hg310FOC5uHbP5j~#hz%HyN$ z-19eiJj)`TPlgwG<2E$h!Xh0t*goto3o=^j&TmB)lX2)6JFrt>+Vf@;&T9xdpquFM^)_yv%P>CBl~Qt3nQn(q6`$$M@s_~0?>LFjExm4APt2s#VFujI*uB}c zYicwaLp+_!8|6W|$Yke=pfTcTp$bZ+fr;$lu>x4}-rDpAZ+lX|OLHz`yGk^M&}RJrm^lcjs?1A0OrAOZxJ6*TZSm z#HXLXzf)&4Ux-g!et&0t-+v)KDnB7kA)kMjzAXPQ@%`O8x`B_6^!`iwBINJyl4@d2s+#!3`toYIRIppRQfUhO5Kk$8-sbj_ev36&xFq5Ier>*pR4@ zxbV2H3E|_zy9!ZSb5UBWqpcC8HAQJHL}|@OXI@xkHIvN3@{<3pl?nYc+1P^2t2BrZBWHYkM6 zP2j}9@CXutUI5WCAyKm6kcr_zAq=96iw_DDjNwre5pu8$|Kmd#K421}UNEsCLD3T- zF_LSX8X}B@q-CLqCq6cW36Fw^Vjxa(7DW;i5grnyuIfcdybxD#cwA6)R8&Y1N&r4l z;<52D;QPwdv6EzhV*|sZm@)C8p&_wIR&YpcXhd`ZLy?CBQz?fa`d~I)NEBl!vRQjn1r_vXSAW$u<^isu)P&jkXisV)?!@Z)E;sPUMpf#b`A~C?vsN!ex_q-A88pri(m+Uwc#ve2;_{&c|U(VS*MK4Rg5+@EB zCYZ8ojTVQ?xXe3V9fRga$u$z3D=Ho<6`5O1 zdZ3L64&jo>T60PC9mLslNgNyE}`AFjub zVEn9y%lz2?!xIyF9+) zQSye%hY^paJT~RA1&L_O zRE_=ZYV7~0#{N|`_T|;s8^8N@B%0~g#g^%g zw+N8Tat!)aE3meV9jpe%2L`kMWR`L0*{xt{%k-%9$FiJD57xz22%#r=;#^pk9r#(H zgH8+_8yyuG!SsZGP^Evcyr*7>8Kh^ng&FP%Y#J(A;bHF}H6Nn-V9_I#xTs%{^jLaplr}jpo5@%&}q;G&^6FqkPh?= z^d4j=FE6hPY6NNt>HreO%SB%`Ud|42zCsxGc*a9fNmE7Ue3AJqZ?1!9#CfK9aGDpU zHMhE;uRz$q&;DpWl;;1*oz_tCp&eres_rLm1@}%2lk^S4v>pmyQ*rs;B8IWRj&LPt z;Xb(GBMYD7c*{G%Fll~>Jwd{l?m$d`8ZfdkcvGYR*oTEVS5qd5VRD$>KTM#s(C&Z1 zOs2vHK?*{I$iK%4?|Y1#wbuKWHH4e5)%sMOw-vku#C*B^5!{(9?l}`4J%(~-o6IkV zSMeOrcw)4fV4cv@P8J&@TFD_2jE=Cp>7>AiMS$HG{s+h40 zqKQC8CL0FN>_9Hr#Kj!AIi(O z^JL277#@A}++x5#UBUS+1n%bfOdf?W{-0m~A4WSKUo9^$KMDFCMB&BoNW708FOG{@ zS(z8XO}6Z>c7j_4@_r@&pMpx3q74Fd2gQKM&J;-gOO{~m0+a-zu;oA@9Aoh0Pd`q9 zCwnRt6ar1_PGU<06GypO<>iH|Ig+hGTrgLb-rN3xPSHV|W?$~VvmhYda?Yv#A11B# zX{apjZ}fiZs;{gUJ9Hc2I)u#JGeSLWE9cB6JT~XioJXqe`ahwb>)UZW%`;&GB{e($ z;U|uFxSaJY%tNIe=gi~YvY3?8g>7Gib|&TLrK~yR!UaN^f0ltGP&ZN4i*W7YrjbDT z!<9B`ftf8&9J&UyYyWC|T_+}~q}Qb53MVG(dg?H{iB3#`*VxGXTqmaAf`A8p_nhQ& zcPF;k)=25|k>P2H6XuX@(F^fXC{aM|A>&Gn@}U0&(XebeTvZC<1 z9=pGzmXUkP!z+S^-qb}Wg)VbjbGKd5sGhF7b9L*cd^=iZq*+s(Z0n$%#GqUX|C_iY!3 zHC-OJ!fXEWy{(q7+WB7oyv?P-9j~i)nBQq+QEuF&Yu2=&a|avA=XG9ldcStJ%QN=gxbOVj%caeQOEr2QXzn<#=?qn#=GQ*yMt?kL*xofMY3XR^ohGrxjKlM{tG_!q zsE-ErKdr`>u%fD&cnxAH<>y5@s?Wqo471@d-ZdRygKLZfBNt&AUi~{I?X3^lG(*2_qvU< zU*YzXd_mjnai3!*j;(*{*QCV%;d&T+twaV7HK!{eh||7^f0GDo@pkl^coAl=+?%=kXZ z>+cr1%zRt&qg|oZHN~M&+hx-Xy>@#2A@%R{^}H5yq7pmoQaUZHS?l*YzZ}W7ip*Pl zF>a$GX?0N0fv+t?^^)q`o%+|tqpjxc`OCiuGT$boim`hAu4$t-K`0^PHmNu5sg z$#GX@&+q)AV|Hyz8?%5e1ASL{&NFIf6?&j>@YwE~M>{_3N2lb6tD&&E=aLS`9R5n6gIE zBiH-Tj5m#jKCd(2=Je5XkLs2TSdqH%exJEPBO95<8JIe^cV7P6!!^?mH+^*I=&cc6 zHP?6^=zS}_=T8MMYP@UkXToK7`JS5(-!~l=`KqC3Q=e^)R?5{yo4=mhex7XI6V=3( zx4-e)9qRMWs`r>AMe3-wPWG*p+uJqVl=6Dg-cMJI`krpy#yhupQTN0qCl3DVJZ1i( zWA+xzk4>;~6NQ8jtMi zKlA<}vdnaNce$nOo|AR&=J#^ZG`(|tq-|=lgTe9!mWOJ4&-kO*YDC72Q48nguW-oe z;I*@+WNR;*308aNIxJok-NA9o?9z3g|1c@Keymp7kR4YX58bS1H+0~RTQ+e_iRRT~ z$Mup?w@iOeE#5TddDz#l!{z%osMKHmaNlA3BC811{8p+%r;{k+hC2qaf~q)NuD!SC#xHEKJuM$snc?s75QF2 zELYy{JMqn0+va{Jay3hx=XF}SGFMrDd~~i&TiJ%S$|aIF&-)Iv+mX{?=!UkhPdTo4 zid$M3to4rD*yBy8EXh0T7G8WxqeJh&8a6W>fb$E<8kmDrKZEhDevm<9JTPJ$Fj$L)0CUvInFvW z5`gIk21W;3G5Js+Q!t?x~}$ z$Gqw7bEu?EfZLLr-E+4M@A1dHKl^E(bi#+)r*@C3Wwpe^D9&Km>j6fJg#o|b7^M2x zv;MJ^PHnc_^;~qzxcu<6j~i6ZQiuLpPmlf2+f2I?7Bo>ie)$rgEq}N!>>I6^?RlfD zq}zoV-4kCZXZMPk@gk#D%;wc|+ia=*Sdsj4>5;ab=gV&8zqYzL>!fP+t>=5Dv{K$o zdbMfU8%d68rBvCYv(-bbrsY!a!+#D@Zoc#5aHINhu8lSpFJ8X+Mn z$7a13zkaG%l3M=lPXTwkyjy!>%E#;B%I#CSmnDwK%kmAau_+>BvwT7QHLu)LPCpFI zx$(jG`=#;&%EwWwLLTkAYjopJ#~sOv9d$;SEnSxw+LQ0D37FVI(7f^e2MiDmo5A4=iENItNe$x zIgOpt7rWfMYwI{{E~x319XH6e_L?lV$|J7Tb=I?*gM{R+m<$=dvYXQ!uZy6x^M<=l3Na^A@r_E=7uYJx;cOk8O z?@{~MGv4#p);!~LWM!n;!B_WVjL*vRZuiM*kZ?zLe$IlnJA?Ck&COPCj9NZy^Ro%r zM@M@elrI{+B*y;ekq24Ja}usK^H!yw?i}K=;)m!umse%o4ZqJ_b6se1d2Y86 zlAmXZ!cP&I{p6ijzG`zvF?;geKF@n*{n$|y{^N$^;qVlHyA#vxUHgAm>?|KR zK)JQ8yr$;Gxfk;8Pg_qN+{|=dhzZlFQ&E#aO&Y`(mOOM?l++?g)#p;IF`W>JpHP1F zMdlhJvs7fJQ-|rNW&blSNghSv^YRPLvM(|LOBc5>t1oh&KJ1FT*Zo`Whr|7zYMh#l zJ9?;f-&yUR4oFRwjk0f+^xZUh&5lchEe0%|-R;c}g*WCe^o*?Ed+a5%B_@!6SiN10 zDE_ZS;dc_5TZ_zhGAAvzJb&3N>kX6SpNcz$?;MWNPj9M7{xE|| zl8;Gn%khCmi^DT$T_Sqn?60ee6-?{S(-Wg?+$-u zQ1beSdUI3L^aC*4)Xg;%nQNQA_Db7UXtvA+_4U~DMb4u1n!J>5Th&13zTm0B;WpQk z2f2vczZID;i|W_8)vLW9tlHIfUta4#Mc$m{U+LcWpZ>MTe~Bpmdm{7RMkVVy zZ@%EU#%Jh?KOgtZ-IMgZPycg;W}24B|DK~K%0&Jjh|GV8%)g4v4@KsuBJ-aj^J9_u zcad2qGCvWSQ(azJeyVZV?5D|0)|u9}OIB^%aQ@7q~+x7 z(!WONwb_lFyk$@-dw${M0F7LHR*8E(j>Ev6=_rhe#FT$e=eA$nU?CAoEZ`0@JE1_ zYuzgKyM?HHx%vM}_e&!E`|Z{9>jr^As@3g#uW*_8`8>&?IxGS0rjUFse%Vf)fx*VKfr4NK>kw{8|*@W-uut!L`!kk=;% zZgbm|Rrljp$1i(l+I}-;UZi)Mb)x>%Oq9NsA~Sn<4dT>t@w!-v+$}`rW+HQ%sJ+vO z%#B6`nip=MS%9_6%-OMz-0PV#$eXW{^+>^F+UfI^}lG&aZxO-f?p0%aRLVYtG zEJi;q`=4=XE;6?enHz}AHZR_<(Qkj5=aNEw^Y-Ea8htZ+B9~r#b_Go`CZ!dbC6`ir zcIrxD_^YdZhc`9d9ghCg`sn1QqWB(^SZ_W1t+jWK%czELKkdw(o01c`ee`+fts9ol z>3p-*2T94>W5& zcDR?1Ba_cCXGHZk!25cybB>km`wYKVt?O78nr+TTeXc7r8n(sa*R0x7=e!Nkxf2X; zG!A?JBw?9%sMCSiVSW3h8BeG!im#T)oYdy(^qOAHOt)Hd=}oHjc1pLS=noEZ{lWE7 z7Oxt}>bU3Jcsl6RVO6IYBKNa>y55@A72`Q08qY0TaZzMGFEVG34Nf^pbDfoYxdhjlXP}X8!o`dSZ3 z9JO`Q=V2Q^8clfpuHz?{oz`s~%#B_Y73QyQ^~3S@?lY!Knv5-0)_=Ju&Zl3;fC0OI zym5NiSI2H2UNFDrS}Un?p_j|r1%sSr?>^i!8)`j%K*rARiWaS(@VvQk)DO2ueeIMP z^SR@>A-^wv$t=!J={L-I@qq@8A3C4eXmIGm56_-7g}xwv7>{O%>Su_{14&-nN`0Cu za_=LOS8tKIY+hvA^SLg{41)^?S{%N#Jkq$a&4Lw~UW=xxBVu!pdWT-UUl3*C=6O^R zUhC1-apxoJm>tp$Qz^1fEs%`(>BWXQUa@C?JKjl|wmy3Mt)w37Zuo!y{z&ghs*M+C zdc3j@Se-0NkG9XDjXAe2nyva3`N4Q3KRb6*vgVT6d>O`PoeP6a9}m3rdCVoVqqCsD zhN_<0@vEPI?Q_vArxC-1ORW0iFbMXi<-kR^zi!!*NtexLcVU>fCMLsIzP{sq=9ZW9 za?_pO4#B%k8*u(A+LM6sRqJ!1t@rRI(+kaZEJl3w3XU>EceHCHDKtA;59!-?_p+Nv zUUVw~{j}_V#>H-2)$l%w%uQ0W{0iFkaN9Mr|MfQA`*^n(J#Wf|QxP8Cck1fD?|dWj zpD!O6*;9SdEXfY(3#j#e+2D(_uZ2BSE^HLJe%jmQ>yrAN)W4ZGGhN^XcZ}EaGrrw% z?{uNrtR$EZpPP^_Dh~@${K9w6LjGtypMF~QKjV_R!hGh&TeFq34nCSc{mJ&Y?uT z_S;l6J~kAYp*HMK%YlnTdqXJz@9k?T&b_eY4d2YdIY`cOCtM{&;xk`&rLF<;OVA8@sb?QtcVKYa;inB6F&1 zfp5E%lb%bPJs;9QLOuHJk88)#e^6`+h@lX8(!bgV&RqBUUS=&U`lFwePMjY zB_Df3MDBd{1?RuG)B3FbPyD>T8}VZI8X^y3cccH}F8;bf{Jb!}6oL`E(>@sepJ$EL z&pMlQ@H!M1HgnCvc^>+1#J8_M`@uzm*G7VOfmTzBEr^dM1@juGWn+%gred)d{ zr<0_6hNS&%>zy0yrym-yZ((MAr`MNQN8CA?52g-!8D|E0HN;;9f%S0|@FGD}ylAy%>;U9h`LNVkChy8kPmfA!(E_|2I3HvQJ`=baWCK16 zxDjLremk%(?%{9%ZwmYhqyjGkehu;gPuLSQ7(AgL5qQG&paAfM8$e;;2{(cwz!Po) z#egT=22z73WbiJQ2%fM9CgfGJmDzNGVp{`Kr6u$CWAJEC(H!pfhSxA z+76y@185g`!poq2;0bSnwBQLlVUq3yc)|csA$Y>^pkKffP6U;JCrkr908h9Pqyta5 z74#B3;X%+_@Pq}RkKhRlL5xDeqNFj(4m{yHP+jnZM?j|F32%Tbz!Uxol7T0D2eJfD zn2VRN&fp0pn4q@-PnZC*15Y>wa1%%gp71b81)i`NOcYD3I6~EgC~3n3Ik905)=WRuz54+26)1*AT@Zx>7YdLghxP0;0cRB zY2XRVKy$$pegJYhG`A@GDtL0a&H2U{}CdGLf+LD#_(c5Vfo0Z$kR`U5;+9Oxx@!bzaF;0b4g zK7uEF0%9B`EY_AmAHWkf1et>;>;tj@PdEY896Vtb$PzqZr>~I?@Ps|^a@`v|Vaqls zYw(2Mg8aY}27v;=6NZC=!4pP@4f%kN0nP)dz!UBRd4MN80`dV*SO^*np706C4?JOid&mkr;WAJd zc*0hFARq8DpfiZB^CWZw%>_^B2g(FbI102FJYfWA8F<18pq1bWCxf!U6D|a;2T!;X zln0)$5VRdUVZ*-24|qZg&1<9{sKJVOwb?T z3Fq{K%)t|02QgUuBrF4&fG4#62J#0_7!5K9Pq-D-96aGkP+RbXzkoV}Cwu|215a4H zKgtU{;UJI_JYhdM$_PB+SOsJXo^ZG$bQL_|eJ98iJmD-QbQC;cnllk}8+g$LWdWXW znJZ)po^U@X4?Lk3v5 zf+uX@jy3{3p&!Tyi^YV8Ky|?regIj3C-n9}e+r&ZItXnhc*0na96aIJ5zuY$gwsI5 z;0bq)gG|5^PK|&Zz-LCHEI@hS3xVyT;19eF&;z6e?*nWf4P7F8V5bvPXC5WL7UlY()gN(q(0Dq1}e*wM(xH2BP0X_@ZI{`WX zUJmS>2ps@#1H3m0brbwk;E2hnkKhA`JaLQ8gvD`Dex=Mb?`D^Am|tHF~IX69r!}viJ6cc@xTtrkSBN>V9ylD z5F6^`z;3C~6XJohL2bcj0z1w^9|7J5*nBo*3SI`>ItOJ8emihRI%G)xfdTW-rhz9M zI3IE(9w^O(+`yXxmn?vMz-Iw#d=L4MJ@CVF$N@aF0)5ST@5FaY!bJmF2yAK*)X zA3<-){|59y8({{Y1l)fR?FJSiw7?UGkbdxm!1{-w58zFKzkuZ6OMtD9ATQu;fXbuL z4e&m|ouB~lT410S`2rsUTmni2p9MUb58VJ?2(&wne1Vq(eNUjh1s^~N+6R6+u;nS# zPw+Be-_yt=`3Jfcpd7*b0M(!e;FEwK&!8N^GiT8UfnI`-0WLU)vKxRj0AGPDz%%F3 zkAp11%YjEhoxy8?Alg)QBWepVhnu@v=SqXZ=m0-eDSSuX z|KAyw5Uv=7G>PNZx7X*Z%eUZ1a2HUp*GGLfeO}*>zKjGu267Sn>&r&4(|?}Id5~T? z>$~YcOZJ9_3|C^{O^g{6M8ku z7N=LR7f^^#FbgQiCIE3$z6SBSr=LfPYbM+&4AWYS>6p4UbpmR~)bcUW)+jV8F?h9E5|Q>jH|?LTMNy(sSWieR&CX0*YlTHn%rp+AlL^WNAiB(?mn2QJ4W^dh82n z7s3u)kl4q3o>>5 z59!i>R-gZpY{X#;{`K=9JV*J9)hib&^H{$81vh$Lkb@Y7aH>Q7v=~c+ASzV@C zmdx!Qa;3OLTzK?+Lvn>vL)=JCwK94^ehqC z3-yxPqP3#7$gGyJ*`Rucykxb;g@z?upU3w>)zv3)`F*J@1o;YSA=LLLtzn_W zr%FEc{Stqle~x6|?F+J}aylj|Css!r|5dkyeCo@U37Fjr7)epRjm11aP{osF zjD%55kT7Aer@3WX51_eZnv0{kHkzNK`8S%6qxm+Pm!^4YS}PFd|7i}J=7h6k-28rR zU_($J&;-J!+}t;q`qj@@-BNXQMX#S7p25J7B z)>VXgY?`;G`E6l+qGlZCi9p>z!$9AG)`E_Leg)M~OBgGVJ!l{(0F(^c1lkKa4|)PJ zh?g+!LC&CHP#S0zXgla6=q~6LsMbXI1N8^_fnq_kL2E%rK-WM|K(%ldO?!|7$QLvY zGz+vIbR6^x=ryQ8BHk@Qt{{KVcc8_fEuhPwx1dIoFgFbv015@Ag4TobL3cqPLG>m} zm^L7LkT)n4G#NxKf&pX5p!H;GFg0;cLoJ-aUkB&&)Wa8vQk;Neh9$>_j5!wZzG50P z7C7~yDbtK;&a_}!GOZXH^EK0&X~VQ-+A-~!4md}}3RgjPVmdR{Oc$mrW5d`o-I(r- z9n*v9$@F4+Gxkg$rZ3~b^kcqZ`ZIDy!8kHbjFNF?To_lqql4Llulciv{ps{4obc=) zgyUt^jMz3RFfydlG9*szkK=QL!f*tRKYxf4f51|Or>fTeW8%Xj)ZtO0;6k(P3zDm}$MK`; zN@?-I_=nSxwg{BY#D*a_B(dTN!3z$k`VW3^5O(w=y=T~fstSV-Jql-GQWii3*7p;&_G_|nA5(%ezhLc000v*P$uwW@XIPTQ*1`9CBqKB`L6Dvs)| z2rD!qJ}%5ZWFijOtxO({O@dVU?8gNKMpb%ePkQ!;@Sv;}wyX+p8ei3p&Rz1yVd=c= zsyGVTTBR~jNcu%v<%ucZAOjUO0%IFP93p zpsHF|VIK@d(AS#^2deB}4vMlA0iB5os#Ld%$LM7Dzs4RD7#~-uV-=p`)Pb>;6{w0d zM0tc;l}sk!`25NMD_w&kpxbC6=ydjqWav3pl|rS1SPYdeB!}u^`;sIopR6K^O6#h! zsIKKK5C!4m_cp!HOU{xRxGaeT=W zo5N~-1qH?g2GQA2Uy6qn`IlX)N?Si(vAp%&Lc=2>M8#jlIW`1&uhI?aJO9V*{xzHb zu~j3Lqni*aa>t*ChTd)5mu$nMzF>=+2!g`?5(0wOZ*UR<^6 z7bM`5L}N4VXPS;ZMKiJ1KBLlZF6?Yluzm`A;g<(H)6`0{U{5IL?Y(#$&7+#f6dqUd zxQoXE9`Eq@l1HOiTs#&$cI2@SkAry(=5ZR23whkm<9QxScznyF$!sqCraap4IDp4c z9>3!;lgAt$kMMYw$2&ZJ7ox&dMY#HBKjXv> z2ApYJuE6RwZ+5}0Ii7(rG3X12;aFJSb})M_63(WTC_^If(&vQHUU*PogfD+Ks;$JC zuG0`(GZqG}A!?`i*jPk@<4=QdzADq?uXb+1Obb0*E;d*)W60JcFi!2v_WDc$nLVR} z<0JTdAmu-i9RWaG=;e1ty?lK}7FwAJyj`QFYjT?aIoY$NGBle(LAKZsTuT;nrn@J4AUH{_Z5{vgz_Wfa{(I9mMB47kn-IaP-ws+sj<=Mfz}K0Ze93?;y|R7@_p@Q39NiAun`GiJ<;d_&Z%vSIgu zF?gqD5`~EXXlJl3TK*2@P13TmBmIf zGbR2wLwGz>VBjAg#d0ZzfnzXlzI)l&*=rbzNsJ)okYOkW^4Mo&7|Oj~F;5NY z0OAPT?G-hVVO|?X@=ts+#06wQkr)YoX1N%Yd3~b^G;(Je7@=8V18#0amq}sfhv{g9 z!8Pl@D{=}*Po7^M^OW5_$yjc%*^rd849{%N*`Bj6N1IcSQx14?%s%9l^2kgn3tKiIZvB+J@09r$rjm`-dlXOgl$RMvTV!tEd^Ujw!Gb9y47;4 ze5>Eqn5}7Bv$pQrTDVoWmDy&o&1RcwTfnx&ZJFCPZ_{qOzU}EY2#CSDDE^yd$+CK9 z`DBG-%!jp!l3WRzADaugbOOh1DK5QXo@EWddOUfKS&kU4T88cHC*Wjn zrY#eP$Fra^stDdfQ60g6so20yXR_PP|<91S1=r5GKrD9XB2McMbDqIANivQQeWk3dh9#Pf>MbiAVU16Y8Ee$l1< zlY^(HpY=IKSW%`cXB` z!$WZ?uTo1EW!%iAHGW_DICts%>cZ-!~EGG%xcPh(SNE-zfkplqc(qXWPidAS2yfn-tS0w1@o82yq=vN_5COit=FUi34vo?>#>-@S zYYlJH-ZT0#7qT47rFtPhPOwP{MUrZnCbsipGgJ`Pu8hRX-tQl%df!y_ z39qVJ3Z8w;O4{Br<3G~&2y9pVN7_z{(RTRfR`uEcmZ}H*f3NCb*tQrYZ&5=7_RNFk zggvRD)LoY;imZ%a_?X2u$#L8@ilV2~&E3|BjE1AZEa3RU7y+ARMg{aTlejaM<_toHb>os%Rcjn9L7h!7s+qP9tP`I93pw>6}Q^6Q+Q(J-s)T$_;C8$Qh zoG3Uc3XYG08Bx$41=FKoS`@TKLF}ZNsdY+7Y00${r0O@SYH*usya<*p8;PR5Z|rcx z7v&Mn+}JTlQL=WSX?vb0Nn2*aHMW2$YZsNug<6;p2D zc!gvd5Ab&Yi&a}ibFr3 z3aB;%xDE%#7hmSZaV0vK0(7A6&;?J;M#679^mE!{Q4a`cylVJTXt})S81I{IHp} z)z#pkl)x#_z{a_Xg7m_J(P!7c?e9xz|MG`$xTBX-TVc<7TicVEkm&F8>ks(nIf5&! z%8FrGZ@ctfs($_+!vQ_0;cYJORI8f3ZJ7avbY;Z}#4t5YaGVt3NGFcZFfthJ7b}WO zUy)|)1%UKvAZULbaAZW78(uJ54m>kd(b~kw5nO3k{K>k*Dm9?$-36xqSm3B13$)9? ztR|7pX<1G62bS0=5lZ}c3~bk1SD>w9;ORphMY#4I=vBrEuZhAO|8x=mWHWv`#ZO-` zNW^zRH}L|@cJ8-^brR+8%?(G@gZrrZqOD%P(P}*8BFnom&G@wdHj5S)3R0_7vs*B% zOx{e5)t_oCmkfj%*Gss8xI#f{F?ZwW@s=oCNDo^|Ms{LbtwV9i^kascJ8vNzQeI_coG!at&yms*aH`Th(v7NAT^O z8~%WV2~EiUf$ZP-H4a=*|M0edEGZ#F)zZU3QW0!n8gT!Qqv9>`FsSjWklGI?d z3;614#5KEsz0^iS7tF+Hi!R_xWKa}LPD{{K9kXVlb9U=zlVw{1$2#=Ydm$gqYk)pI zJ$NX6#XwQ2YaDZA(+u8dRRZU^ysJf~>YH5pU6i2}X;}w?=l{j#Pglb`)$E#B)$P4a7H-#ybXC8*0jyc!jh5^Q&-nYQ z;TARfm1SRNHOYFW?hZP1X$M?d*b!Wrp{y9=%=(x*h?RD?u{kzPI&M;Tply<5MVj9*|*bfzNzxqAP%a`k0z zrDPwpl>MH=jQRiCWty;Xf}E%HHKML$(* zfzraSBE&f9`*1kglD@*=5o48VOeNnc57Sba^1zGNQ&HVySgbVH>T}vptd&>+5L&OCz1~h;I=25a?fqA=1LUO zYC2RV#nK$%j~daUkyXqBeFn!wHtkPS6kUD!p$ruSlK=i7-MC@&wdDo zQ*3u|Cm`lA-0x%f3=Mb|-L05OQ-zBz;&SR}gI{E5`7f#3FiQ=Qg_aK$A1;OSCfTP42|u!&OazVXp8FS8!)le)bQ` zK5>P&yC?fL>iI7%+u4z#YIECE-Ps0pWsud(jZUi6`mle9>OFuw(%Hjjs^H9R-v;ZJ z+1D?zik>EWhIM5x+xjNk(_BQMZ_E}D|2WlKMYGhG&_A(&n<4^K?`>olsLT~3(05Y% z%nD~u_->^Y9jO1il;vg*+F|yfwx?H9<=CFSjlAsUz;Jz9`sAtUfx)W25+mp$t8os* z*Ed1kG)-hZhf;rqxwJ&YyLT7RWSyzjeL)81(!N8}IL($>KMy&#WrnJRcX_QXbM87@ zrmw-4S-#Ph8ECR)E^l*dq1gy_YxM>2R1L4%iTAX{W@G!MLfffhQe}*pK>(<^hZgvoQ_VaxaYCM+%=RP>`MpE|$y^*-IgR1u2 zo+F|;pMyN7At~h9F1$M_VO!??lp9;-gOo*E=A*J$PgMzQc`b@y1&AUj@KA(KNJA0! zk*B9FDWJTd;lfZt^?OB_vx!U6*y4OrmSkg^C<#I|XM-)n2@N^d!5ZD6{Eb=RtOHrQ z)s|++59e#0#!ehEc4*rjp}bKRw|Czb6olr##xtO++BPxCLBw^DNYbV$s!l9=J(ZZI zLCJZV-KD*Ye72!2Q9dU-+X6$}nxWQhCk4eAw&6ltQX(2ynts88Vnhf9I*p4#)}5XE zRom#+{kGB0@AuoTO|8R(XuoZCGa5&lG@01;nXYR2tuC6=eS`5_ZJ`1va}#k}T05fQVBf4a4H<1^$2 zx^hq|_F$(aaDpTJCb(9DE9-z;17EwbRWc1Hful$ul?kO!kI^9VT3|t!(dyPRvYI-g z^Hfn|wKodwdq79IV+#+miRK6=UQaKXGtQ!J3WRFMVa zLE@H1F)-@@Nz0U3?N9JARED=ZQg-I%SX6#zll2uDKZZy1(Jo|}iwmXk`Iy`OCJ18e zZQsq%OwNg@niE89VV=hr7X#6fvvy$)rP}iT(b1Q3(_iGKhkOjdH0a~=V-;Z#7Bg4{ zzZh@368(y^4bMJ2dsXxiR1w83PUAqh#rXwbi+&NTU@sa-n|S}u+U3NY{Yl6|UII2_ z4ndPr@IrH$ls}#g_zD_Y<_ViULN#^_vb^6m$a#6yvbpfU;Xe(n{)n}f#Rg?}n+Ctr)%< z)Y7fW3-?V{Gcgm&-{8PJ8rG|#M?l^iMBW|3%#4GRMdtCgSO0ckP{;AcJs?mP2T(zT z37sd)GB5IOjP(N)Cns7Q!8R0ZQimu6x~y&r5tEOYJUFv<8C8kd7}O;jPY@-zJtvY4 z%-nWdPCYaNY)_{SnNKCL_{Z!_?E}#A{E4VB<52`^sXr6~OeX=MA?<*(4rndLf%6Ez zppInBL73SF^wgKZ(zWZEjXA+u^mnvo!2L2U{Ol~YRSx(9LtU|LJ{Ey8hU^pYoUO&S zWle7HsdW&x-(eg54m!7OsOl3pAl#O*cfW1UM^u~J)lJAK{c6>$R-&{O+s@pk+H%`S z{D%oh^Q7(9R}XM(W`lWFixTMMi3xLxAgAoDETQ?TGP9Frw3&0Mw|UNFoLlC)W(bKw+!@JEY{{ zT_L!Ac1%AvJv96x3|hN4C1F%^@H^Em%w3Hokc4TpEBJlbU-S&+VG|bf6c~@_Mvpi( z!Q3T8DywEC!AL)mL|RD^#EU?b-E{`|j05MAhNfb% z2oX*}nA(zKM*#DrBbu>W@U-ODhi$9qB8Q_ztQ%1Y^R;u)8ji>4W|UDDwQVlVgJsYQ z1`_EikX+idG$EWcfL~zN1~3MrK4>w|R(}Fh9*I!;fP!Qikpv*HcdDsWE|(@= z4iQ9H&Pjsndw|NwD-^ZyzI*An=_0VT5%+U94t{Lf2RE#`C|`TLi3rca!7nW*Uwfz( z00a?<)*T!z)2Otx#D_)UmNc@;A)nHoj;%fk0a9r~AQ!q6!&CBvojV74j(9g+hK0fE1Dp-I78A0||S8F+Zk9VdK^>XwG-gco>PauSJCa zy>?h9-D?6V2u+X{Y@d)8{HIYqdT~BZK|VH9fz5^2$O{SfA$lPIMpXdNiYf&-8{qL0 zoCokC36=nyCc!FzQzWU!faRqM&U++QF0lrQEtlAa zIBcWDHps9hiTx5wXh@Gt0y6hWj7$hvuiheTkHi8J+bgkOOU#JF_5sU!yZu82 zSlXmEood{1kh+DIDs;? zJzra3mwgRpuKu~`OzPiWaRt1ru(1rWc!>=i+fJ;qi3)Q(M*?V3D{0x7e0_mE-|_|e z3AchYYCE$X>#{GIbtSk{OW8JPYX7Hu!$^1^a9je)J-Jz5(&)7|f(;g%P9|qMsvsIR+!~A7`@~|S=mjkzC4)wS z2sVcD9l^%K5~J7z7+e&a1jC79=fhN@SdJ*CnX^2!U@Sz%t?1T1FLtKLK@o`ICOi{562^n+w3vOiKLSIcD60NEbvjuG=C&{Vmn6l^Kxhs4 z8&yyKvt_kTyJ%UvR_j^tF;rkC5UfS!iZOx2h~FGI_l9#oTiXO0{2^1&d+)c+T}SN! z(+A8Jwr))-w#~vM<8n+_uGorss1N|=0Zzeu73LCy^u36n=k~WfF4kuu9dBp|pujO- zyFW1}OG$bw91|DY?y4gpF}1b*@2?*^&2&~naZa4$7VIH4vMC&ZzeH# z7qXL3U4$FE_5*GS3SNYQOiadW-v~0n^i%7$iMAZsdI-brsDiL1Cze4}{mSte6#ojd zQbV_rw7Kn9el2T^nmTQ*y&_*w9lA{{98T8h=>CIeITEv&+Kp*C}7y!tLOIuk=#WjRgB z=Rz`9HdQkou49L7P_LMy6QV~nO`1&EE(-CJro8z4f7_G?2}7WlrW+7RnlUYnb~R&C z4Oyyzz9+$qRRndMM}a9;Q8JPs&DgjKRSt8d`u`uBYZD@|xssCEKw+-elTdv#g&V{6 z3A-eiex?IPsAxv+B#kT5M z}$hY9NzHKZr>|PaP14?jSkM} zJ+=(IJ#KBLSHA(}ct1fOPOp9-UWGn<_IAYF0V|O3s^@Q9ZK1q81M5j|I#vwQZWkR8EiFks z=373^*S;0`-XQW_{|07W9gDEQj(Rz0e3wFv^`PO0pI9SfxkfrhK(>(BfHEU7nM(cc z75xMU*I0Rntg)tOYS63`y%uWGR(~}ua|X9hN|+)=CUdk^;L^S~PJ@_@7RTyUqe$_j z>G{xYe&|VoyR|@Le&~@=WSd!U3(2j+3zhR{y?T%4oG8i3u zsQpU3dL-2T4#eH=(w`kg9TKX4wXnxDXRG7pRimiIY_Q

oCE({O2}@n-Z0+BSkt zC#X5|9}fM=QPgm>J@+0wqB&c&R`{?CV~WTvRXdh3+@mmVbH(F*xXF>(;>)8-m3R)Y@jruejD}6TTw?t9NHbD z_97Qzd6Xip141=}rsY`Oi>&uaSvQGWC&6&uqCS}9-#Q-M_8`1whEj5{3-SR5faozj zH;&EaO`d8EoVd~AL_1E;+O50q?myXmSN{NZKDN5Ew=Vl7*dx*BOSq7*h()q zx8*{jg%9|sUL5eN-zAnRRt(PCo$bH7KkY(qRkL4MwjcYoZD#aoR(EKK2XV1W><*>6 z0Oc=yu_`w^&teUnguI;X)?Upzpbwfn>#zL-1F2IDHmA^5{TChS*Z_6~w^liVhlf?~ zQd`hPLq$y;eWvRvSbgoi(IQg`zY+}hHcxvkm|~VZ(qGbMICp#L^5l*?H1>H~{K7Iy zc`FtR+t4?;^ttpp>mP$=WcV`B4PXWZKcoOZb0I$f;roQhp7S8#_FYck2pUPNEh zriPo{dU-2|IBgO<ux%GG9@3mafa`Udnj zcv}_A&9s^aiy4&Fg#F+(7;M($+Vx~DHv?x33*#)-ZN+aial0CFz% zXUktKt8Z7)>{ka}4`cTzvXsCnEzUjun(7*`tWRWl zT(C%6D@THIrLJQj)!h zwpL;^Y9Q=a65A`W2P9@lY_r7n0c-ywFKS2&!uIH4J#|hN)vAG7y%-SIiua<*UIe!r z`yQfJ(f+Ao`<)_oQMpoEz!4kzm3X6Vm0%Otq9DR{5U(JLF+MG15_B znpUTZodeW;K{7B6kTU=(56uAnf#nESX#FU{Rqg(C z+Iz($fOu(QK*MqSE#S#&a;$cW7;mJ}p!}I}<_Pg3kLh5hQ#cz5Y9MDFIkj-=Y5SOb z+D&$A50WRlQFPRbvF~I`w7vvX(VmARH9d9JIMi$XY$EEZl|aBko6;g;)^7B9p{EFU zhn%k)nGiusrTeh9qdQ;Mwl#Mo*;a*s<>MGwDDb-K?bJjz#L~F2Tqe21O!9f;!>v7< z7AxN!dQ`~Q{+X}sb7{@S7Q5&f=CwFq$7D`v5!ta%0wAe$*Aw=Qy)uN`T(qey1>ofHmTDeZk@s4Gn>v44%l zDy)%eVlN&8W<(Gd7CZO`+LT*+O6=$B^`sGf_*(Q&4orsi^`ml-fJ0w3Di3~mTd*@t zYkme*uh`r#IJ8%CL;WvuWWT@cTL%tBj-}b0c)Rx=#5IN_V+RT(YP1#EaA~VVpi_HR z2y^;`rrtWTl>JsMSG;0ttS!QRU$tw{T3lgn^X6_ zE4kI&;WXNkXnLUyaF_^L3G{GeoXz^=pwG?f(8KGBU8ycvi;U z7?Y!o$Wf#5#tAYix%KBLf?N9zvqWPq7^mz^nT+PV zya5A}OWd<@u8pR!E+z$Iq{uTJAfUlj@t;cb!bG`-HD2hR$cC2&MYwcaxS?qiEyl|V zE~==m(d(^LdeK6Dx;kHm$xwC9$GbIZQTCq~z_L-58-(-`klt-ohB)fySW<;uHb9>n zbZgxqMJZBjx6!l;6;hu{f>mqq(9mbnQBYg{3)+lH2)Ol0^nIw@{xGxgIJMx={!s^Q zVp(!b-4PnTS6&*rHXJ-W#n4t~1mnz>o{65w4TKFS}wy!-M^tzqsxWd#$tmz}! zj!<^A*HrJ|CsfN@;SU{+=RjI}2kO7>$QZaQ$JXtf1Qi93^tP>e9R!%&i6lEN5+>(L z`_m%QZ4_yA-OksLzu=J++nNVtq}M4W?}WdomN&znIvU4Nj8!7WI=Hs_C2$>$6NoJ4 zk&P1xq}OprBi@*m_A3QAi9ojivk9ad1dhh@2%IQDybUSsX9y6lj!HX?*N(;u2pl57 z3kmEiz>5fk7Qv81;I{&F5ZErjDFnVJK)mlM?XL+ikHA&|;;lz%e@=iI1pY;UnFKy0 zz)=LQ7vQM`{z8DG30y6}GvIcta5Ro3f03i{O!6xnjpN8Kb~M5wl#Xj1Vkx@g3P&Sm zze>j?jz-LEm5!;7MmzZz%JU!{6CL8LWXG9~#_>cy&C!U7pwcnIABubc!c8?0u(ny0;JyJZ~)p7v}qoI@6T;BV#WhE4EdSGrR!s?*L1SJk(pMR93F2JCq9Xw)(9Q&@xSI1GP*$ANB>oFir7^ zS_sZjh0>B`GHQDj%k&}YdXjmuAS5dbkwxlQC~;)Bel+MZi&RgXMXIOvu_A^Qxv;5bxaT~k+u!bKjnX$39QW>ZGn--T0msFzX!d9>3GdA&eFX?{vx%d`Tj z!S-JX2`N;D`ujxGN$4s_0<}rCkqne9lXBEDO(o0hxUTMyRjERRyMX<45il@PbX9G{ z)QWctMGO)C@|*6`DF8d*60Pi?~)n$%{jSa(i?cni9608$cCPNYRn|QH?Fas$IGWYRO5V zDifK~Yl#^QEGOjXQT7ENYCIf!Fi zgO*N%O(U|46|{OHVhvJ^yT)4x1TjQoD)tRqF}%{OHIQ`UP?e|5H=%kqla0{i_gl0A z?EE!hnWsUkdsbxoPMR5DEf+5hwpE>xe4j%3PRwwZoElpP%j}gPdwF_54u-V4I~G~P zc-SP=crrq}#HAEzdb5;8yot;?n*W(XlL1U%u|`N8M$EYiRhOq~LpM=2`REDs-UJm; zv9PH*8r!ECuVRV4jh5I``;HWgtXPzt4dR#=w3{g=pd71q)As<~Au*qAe*|z#9kqU8 zjrvnGkx@f;5xc7Wf~Jk~Adj}1qIIlvYJVkenBNv^`o@JQgqb6nUyMUxF#Tfr$jgZ< zPR5jF8Z_U8zBL7_f>ux+oybg^+|YU3EGHjx3bpDf-Q#KCG~tw<8tu+@JEbyKP+dc7Ul z*>5|Bjrm&aYY(bziX`Q&@R$8)S>1YwD$P~b{6#cjI`ce~OSnj+C10x1D~Ov|!!dPD z%bWZZy=?}1>1w*B+W9r8V>sw;p|2Ju1^QrCrm3SapEYjAacub}Gl%51I2({E$|5G( zSoRGbNwVGa7uuKB#S0~ktCK@?0Gx%{Zn+uTfb@b$MrLe!T_R$3{^CxGC$dT}iA0Mq z-UmjtMTAj`JDsf-v#sBScPd(5HSyKJ#p^O1>N)~uUVRVPNE-EtSh8v6gvs)O{spL5 z>7O+UvwgAdLCoW2=+-xAr(|_9t>E7Zfnug(CU?A<9G8$^4Lj;gh65NwM1DF>GD#m1 z39^-r{wApnB$m*gOZw-YtP%9~vtkGjnc}Ex8;SthC!r-DTE8j6ps)omHeyNU6mWLn zj_PMvUpyW#PPfxSPRzd0K%CdO8_#d?Jc_3g&o(?SL=-=Quf~OB?gYle%XEdHFg#R4RI>`D@9?HX6c+SH! z1<%EJF2i#*p80sL!&8mt20S<6xfu_y`m5SuEbi)?*@hl|4WU7rV{fqdvG6F5NPPM# z=Kh;$mah2w)NU>iKpqbDkUi6YeY#t(QL&z@Vl$j*^fMzrfyj8j!)cq2vrXCqpy3U- zW8_Y8WgR=CGNCn~>rYTZB1Rf`Fa*&$VSC*+nP;(6U|dMnlW=(FtKE_#=J3vAkjMM) zYsgLzB<+80`|TVQT|W$du|yuCcI%VTqX%nI2z2P;7|)O!j&>L!prX)?!i9Lsi z2V#K^H>9VeHnFubh|-ZPLidWUL&^wQD(EM6WR z`m_|9L6C(+PmEF3V4t%6K5$TL<1H{cBBu{%gc)f0`&50v9{^!X)6g3!0y6cbmArsK zIdUOI&JArC3^)8yf^h}GF~@A^HjcSVx1|-rj2w|++nsa+58r)}fpo3WyJZi;-TJN6 zxWxOTV{&%QGHmPK^$sY3YPI>Z6o;Od-9Kyh!KZ1yq^;gdu0?#hATM8^m7V4e-P#En zrjcT7#fsVLCc?e1;N(rK+k$VkT->Rz7O`>srbYEm<>NOk%U;8cn7W-5-KBkLH zGIL>ZeobB<&sVZ(Q+K?e_OJyr{yCRO1SG_WQAN=@BGpURWX4J{a7m2q8*J%+3w~h-aYuaO`bS)|!y!kPeiC(fYibcWrh~+I zKuX5snF3@7X-m_afS^0Qo$Bd-&^_XmnvjDYkXCM6oG;OeZKRWY9nutivnX>2$5HSK zLQTUsjJ8jWZYBCXTA2MS6onj7;^GBHM}x*WD2hk-45KmW1*VZBPeEj}bKHmy8O+po z$$t4s*)KP8zl>Xb#zw>w(rFbgKlBhq5PkEf#&1~US4h{TpFhgjz=ZceXmLIQSI|*_ zM;y;lyWa8|I7Cmfi2`L0W&}CfRY(ER5YTNjgyQf3$C<%#P6d_HX8EFfB*qx|E^Uut zXZ8a~)-1i;=Q1b7G&JDQ$uu!G)M8tJ#s-JJ=1B3XkzlC^N5Znw-764>N!M|qsgBI#pJeA zfE-QkkpUco0!SC4&xUO%&%|gV1Gq61D;DS*9cn0bF7nFVv|D?QdQ_MGd=r@5A-Uo9 z231vz0MYO7!BPVT>4{vnx41{OPBt>6)$PBzkeD~Tz~H%b#i}y6~XdwrR?ulMF~bR z!7afiq(eU*7mQ{}Zk1H5Ma2#SO`sat;6Uq-9|rV3qTin^hJZkS^u~L^t7>PN9d65N z>bK};QgGn538?6IJCac?sm6Qoy6<;M9cr}SjdYkedW=t8JfB0Bw_+xCC_`g&N0aTG zWBq`p#QANQQcgms*p&QuddPpOLMM9mQRIb34x@2dNrT36r$KG5Uxt|5VHcJs35 zu%aS_Q~Gq2CjRcaJkq0ThoQbsuo!l{i{@!!c1`CZ?IebSY~nSQ&%zr^B*w1<*Ab?@ zPm}QQf^<>e1&~^MV~G;5(kCr?!wgjV3e9_e6+pU?j?9B%ms|fgpn(9(FzEl60hnZT zN=l9~2p>lPMKwN^lmg>j3Ct4(n%n*|P)agZsc(u==zHT#gl$nQik05tPp5b|uN2;f zyc*5GT-uICQhk2?m;Q;Hh!3xgm@cc@TgI0Nz~%O1^0jevGA<1nb7(je-GMXnUm4WL zinY;hv?^Xd+?q}n(;^O&0>Io_e1axV+nHw`LWTWCtxA?gd__jpUJ3uA0dY)QqoU0$ zaB4eYF8TUxWZe$kf)3<(S7>Qk*4uo%jBZYaw`Fgu{)+n`r?$^1L^;4YSWIMbO4Jd) zn?$$200+iJvVxN=4t+*?z)9Oqw7RV7IccuYZ3e)s-7c?K{fa;7?$G}#dUQJK?(!~< z9ChE_aV?f^uNDHdHG3fdeSaf)+8Ppvt1szE%gz&8&)VhE#T${T&+2P;X?NK63I4k! z{~b0mNj2Q$@?OdE+m`(eYjLDA6eLeOkf-fOan=Fva-3f1@E#F@lULzWzfZ$H*r`W^ zt6;@8d}@+#Q-s?~xK`oz6)vsnhEE+R+%(}HBizBlJx;jk!W|~u;ldpuT)S{b3U`!n zPZKWPdI_I8TDW6`d!}&53->JHP7v-y;Z73ndBQzkxEBgHN4QgjnnYK9kZL&?_!y-ZM;pzQ?pM@Q5C3_&9Z+DOn?g8o2|o1pasT|>|>3A&D; zwFK1>w1S{@1o;VifS~ILdW@iX1kwH8_A3Z_ksvogZxJ+wpo0WWB4`lC%Jwq}nn2Jf zf@TvmjG#(_(g>n~xxE)bza{8KU6?-7)Y2~hj%1RYDz3k01- zP!mB8f}SR*fS^YR@(}b}f+`8RhoBV%-3kaBUKN#gUv+h5b*54o@Y^ek?d87m%Ic*` zprUYona}R8w3p&vf|VAQmEPd97t+5uv(GQ|F7#CtDK+9>qS>o`_}6gNUZ1_9(r+(Z zR9IR@F=GT&R{1LIMZQI)UY~;GY69L86D_S+1j$8qxB;KSg6&8qfqbzYaq)2>XB)f4q!XpZ+DJ-u-1r}FUmlygo$t){;naahE-04a| zpsYszgFVIJp6O5`|8GyZ(lOIf;8OAe<>gDGqPS)=m6`NU^IVlm7XD}9cm@BEj{k+> zd8P&1V=MnW`>8d`;=<|*Sl4-Ef^7aZKEJ)BbU}%|s=BnYy41ha?k|B67M*9Gfq#`M z{wJTXd?4keeppUlWv0((FY)`UYR(%sZb7NPBryL>80omeilS=YV*Gc&1-=SjO=-=z z#ia{N$C*ivl}V2ESB^!JV=2k8W|Ct$$+3|n$D*-~jY%@LbSOvUHx5l=Tun*&I4-;S zbp(@PoWHy(F|3I6ITqc4)U16*z+Z*FAy2ko8H!dR)NXfG#DrzIxIO2V6rxR)Whzsv zD=T2#zGA;J)vXj0HnvP)BH%KV5+RpyNVP;oP%fG-{uS-iTn_U}+&$H)lok7wDd>cx z+$nAa^-(Rw3bK?sptaZ0XC zk>$)RMb99LKGTiXUF4Hl6l9sjnU_hr%;K!dDl@%Vxr)EC%FGZ2#3*tWG)bW`+Kh5= zR%StOQ6A1vluIT*3yP02MKclA?JOvlIHC$B8L?%ag+fO-V$zAJh*_wK2#ZW5l7d;_ zD7#6C7TA6zbjOw?G9F71HLAjHm_e4`9z&5)1c5RiBiGbbgeqBM$k8gKs4+Dsk|kS| zQ3%3gKTDZX>GxNbn}uLZaI;t@#U+(rS~N}w2cSd{q9~5?`K2|L6{TMLl`-(_82G9f z_-bYL*sJWbg?p8vqMNEJK~GvE%`gTR$t$^KrD#R7s?nB|srWah7_RK@!ioif!UaBx zP5;2z?!xd|T8+{1lEP})-zjQgmA}-hY)8X3$lU7|Pw;<1k>$etzKvxIxL za3=`&9N|tB?zzI9B;0J_W^rU0J4;5-lJT>ofUI+r{K|?-WhS}^U!`)^nOVx&=;QI& zGv=c-nEC@d0=J);Kt%mgt@IVZ{|KpAq52+1-fOe7{5B0$mHGNG0wg$pVx z3d@v<;Kvi?2gpq4B4s==couMkFBW--f@b6yUO!7UduHil<|qCyxk<$|IgD91-nr%f zr^Wo2K5rr(;{A6XEBNR!f4;x{%^~5vxzgW9$Nl&>9<=dJ6O%sC=x5URGJjgmDT{nX zv=}DCWj<-qjaHb6W-gWyOl263ht4rgz(YUssXX{uEcknMcB&nC=C3!g#xHeg_Oog(( zOi?UOOoykZDk(ptD#_O)GxK{ZmflcMnN_iDg@3@Rqz=R`$a#I0RIRUKp#y0~KgDuk zf0U=ck~$NKl@CxXe}O-Dppw*%&7OB{ishy>C8-7Pbw3PJk_I2ESgtr0|N9sId+_(e z_YB4=%U~sWFiur1IZjD>I$g1Rn64zP8Ln714_A_WCn}aZP~OKzD3%Z4e>Xx&JXSNX3$M3RZPaQBroFqFDNjQk3IIDai$+l%!D^ ziZU%zN!p#MB%eN7vD9I?Ed2~6WzJZHpQ$LfoT((WI+WyPQxwaurzpxxQDs(QhkY%bodg*a{de~2hC7YY73xr{7^LU zOTOVU#j+&LlGeYsbshBo|2H~kvqx`zl??g=`y1I8pPNL`cE(?3e-HZ~vi}A92iRAx zm-70uKbZXy>}Rll7W?P3Kb8Fg_OE8Yi2Vxom#}{m`?s)v7yAwD|B3x4+26u`EBm|I ze~0}~+26+tS9tE-kNu(SpU?i)>{qeBlKl|- zzhS?b{WsYEnEgZS5A@4)Pi221`zr&Iel6n**}s8(o&EI*{^N`{vHvRjAG3dueJjf! z%KjMkFJgZN`yTcy**CRxH{-u%|99*^#{LuRKga%d_RaX0EHq z_UEu)%s!@A;#b4|3ij*RzlHs~*}sqd2ibp;{YLg%*x$qcJM4eS{txV1mdbPov40Z# zr?8*JzJq-y`%45A+>9&|mF={`Vf} z@Ag3dvIqK+9_W2H_CUX&2YNvd^jSU7ukL|9uLpW@5A?Dg=+!;Y zm-Iki(F1)|5A>URps(wJes>S_U-v+Num}1hJEU%g zZq4N9)a|F;q=%&e1xo_|mEV3&kIEEVf27a6vNa1Yg`bqpa!woqJX{=}~`X>htF= z-}#&#m8Cm-`MKL+P7mqDmhb1Znd#l6mso#4XL?t5lOENdnWkCJ|DBaYV_)+V>HXX` zx`5N8_4V%T#Vp^?rPGB!k)Bz;pDVpZJ)~!r>*r2yZ4c=s+RM+??u&~{Bl{QhMHq80 z14jh{CQOIs&HW65=`=A}9DOCjq!Vn?>3XwCC)lLZ^}&vhBjaxe0kqGA)wiD#VIH9_^598d1l%Y0?N!WtpFKoDm7=};Ilm@6Sl zb#{D++gGu`UqS)%;sf#vmz0(V$|bct^Y>*kw30FXL>8qm%MuC2s5z$`(T+8!f^l=ilI~ zt|Yuk;^M$L;jI#nnk=1Fr+_vYun6xNs6l@**)!4i5WR4IrPRY-!n}oLWtCnEGz1C+ zrtkB6{H5i>6!tM$YQB>&ob0X?`lwAJ42Ob+fVBdv_K9fg1mdp_RCo*hf^v_bEXJ{F zfi@6YQd#2{sd+`ZHNI+ba{#&7K*0-ri|9~4(2WAUKH$UE0icb9mKSF`&vkKY>7bOdql9mnrmn;qeTH(35=HcMA7yMbSd585VhS&I5JXNA#z!p0(4Qaw0H=|qKt=6 z!h3K=a6pX`AiGx9TZ)zamifvfNkz&RNnYi5$aK>vZC5*d}vML#JdLvcpvUdU8G0e8u2K9?yDr>+6rB=QSP=tClTJV z!lg)|MtK|maqJTux=Wd_v{_2&Kt1Y582u+HV2HtglWL|@fgVWtQy(R1soz)Q!GPkC zS16P(Eu6_$A$3hIw(OAZnxwKoO^HV|of_qXWF-mLEHGeEMwN$>ygp74)lyiYypWo# z{DERhNgn0RBv>};$L}qnl9_ps`B&bwDoG+cpCx&5<-)JLgp^w;zE3%l6uDoc{5n}l zmJQ_I*Ob=Wk}U7#C@&?mZe@BNWn-UYWw#UD zf1hTF)X6)^$x4F?;twvQ9Mzde=BYjfN#pL2NBLXtWTpP%?s)zf;duob_$~3o)`o|M z_9e>O@pZz~y(q!+7bE$1$T@_#snxz>WqoR(g8V&w}$bId9HF>AB;3z$yy*dn<-L zjAMVangxCgt&OSR)-Us*8yd}zmFyAc*ODen$P4icM zN-N+sfcWAZib~E0bFjO&F@zQ5RB0Vw_n99kE%T$Qr8yt$Bl@i$`V_+`%?7F5+qVeQ z60aY1CYY56`lcvvKun6V>Q)F#GU>O2Q8Yp2L2%rLuoUGF!hKn|Pu@n9*WfC!qtC!C z8}8bE!qPD8RV#P&D=b<>lQ55RUq8~Opl<3{tnBD#w&9!l6;>5`acN4qwI3Oi($OzP zc@GDHQg0-})j1}=dSke26e&-YL?h{weuEVKJ>Ng4BC?+JghJf1Y{>|=YaPbwRwTh1LZFuT)IHfs5~ z1G5`b)+e7juHW$)#V6^O%g-3sx6iPF!v>}ev29QJ)-qzmv!^|28F}mpuk~7Z;{BFk z{YD=%?RVCjEGM3P%;;m*TCT3qERI1>@PQ2S=)XYF`FQ%Bgop5Z8FwrFj8K2JhD~y*j{vG3VR-EGZ zwnH#}&oX|MU7*VQjBAW1K>?z?jMIl%2>&DFPR6$~Uc&fCjNig|pOY2w`!nMwF#aav zV;QIKERg*37`G=Y%0S@GQ)GPlwk6@y8NY<_rHn6R{8h&33Np#lM#=be{g~oka=OHu z8DGZuyNtguT81BCyp!>v*zlqFcaD|eqk)tBQk?atAAQb)@PXqbUcmUw@e=njt}(un z@rEoJejjj>{~_ZKarn24H!+^gr=8wn{5rGwY4XEFXg zbnn(-ryU%>caY)Df2vl%~~ z@rR3K{0kUA&L{Cp89$rxg^af`9%TG;#(&NDzZm}u<0lnMc`q@Z#rP+TFJYX%wMY5; z)pe4;e_x58xKQFJF@7H7=Q4f`;yBS}~_#YX+oAInNDgTd*>x?%ueuVK? z8DCQ_`9Eg7mGPv0GQa;|d@$qv#ZD|fY(jXdqBQXSv6JyR%cZ>OgyTuON#b)E-x!p5 z8R7jErRrvhFJas~|M?5X)9AzC`2B(K0k{vu_!Eq`GQOGdml$tlyp8eKfZI?XFOHG& zK4-l3WQpT)n+R{`@Y5JinI^;Cj9If0X0zW&E+R zGCUP`>?l9?Wk~!~#&pzq6rJQ~)p;BRN0I8Go4dv!3zcEbm#yJ30JK z#vkDN{hIOjIGny1MEaY^@y9ZL)9JE2PR5ULeEQ-J@n6CG%NYNd!|!DLHMZx+8K3Wx z^4?(lJjTCg{CLKP9V6x6$@n>pU#rUamookY;}wioG5@WM*E0SX<0|7XF>YtPgYf{{ zYoB9f`aZU&6BwU&7Ab#ECE%|o z;9p4`QRL4ySj3OuXC&a$6Yz=z{AP(q(r-w>pGv@AO29uz!1pKMeTKxB?<9#QnfV`` zfM1w^Uz&jXC5~CF{4GzwS0~{2B;XGx;7=#uEeZI`3HX}{_@@c@mkIc{3Hadz+;W`I zPozBk6L7ktjLMCKk4eBMB;e;K;O+!`RsudJ0WV6x%MadxWjwFoX~Xj>p51u^f8cos&%1cu z!}C6#y?8#r^C6y(@O+Ht6Fi^d`3#SNrybAdcslTWf#*v+|HShZo_%<}#aVR+iWU>ORm{QG-u!VVjWG_PXaPtis}RQ4jL*!7uEY^$>{F#UBa8rfl~K zVriYW!MhJEEGqI~W4VXWvO=uNBTqjTOlzLq%l3+;&F;f$78h1wt(o@W<6_SgAH2E@ z%kP)rgDbTE(naBkEI+39MBZ+cpUBcp6(q8BQxRQQ5~?FHyqii%Wa+MI5_!6vY|OmEyPrR_Mz;Jw>Ju z>|qCgaZFtCSv|CJbCWWX(W*jxjVPLLv90T=;Vsq(S7lA~#6T>O_seKB`Au zNFtq1O<-HD5c~gK!n#mpFAyW53g2S#$c`N61%1sd*DKN}|0?6GQCy-Dfi( zYVowU*Oi)(=_tF{1MJElvr*bLFtTsjHAwUq=4L*ZDw>$sUhEnvW>?V+jI_bn;zWoE zrHRnv3KOBqvWPu5&QT=D+}iAtY+Ozw?A>Iv3N4MprL(BorjOB&pitmXg7@@aN!9X492I;K90(^XoXnYh1!>LdUQRO*_xNOClAxWb) zX4;^{g#i-S)%ry8kZZ*y2Qp(b7anF(wx$F=B6V)M#!a0unQeuZafc*LbT-=SL2ol<|p3 z^d&VV0e?~D;)=-e4%D>h^Kh5~)n8)T2dc2TvfSe>HV?DN;}4N>OcWoc6K%0W`i9iEx<( z;*&U$LWu46p12auB=p244?6S|8h6g2CnmBn^N7Qb@tB7nqC9-mD3M^+X>{ODoE4bU zuWk+sMTD6=Fv6H~N%AyN43j)!5knAXqY~7@=F#0rqQsbV?kJH)XYwLu*=1_S!fVXa zJ$!_xix4wh3h0`M8D1k^a^yLuL?QBARFoP$Cl;Z__T>Tv>J$=J#nGU+(}R&jV#V+gmY6W^vZEbuR9vKYO0>pE0C(%% zL@_(;Zi1))=7F%T8KJSGn@FOo?}jeMkSIM)F^PqZ_RcY5W?WoWM(hCDZFu6GJu;Ga zQAjk+$Y|W1#BOyGjT^6&C_O#sS->W(XV z%Bovo(M6r^gz(FAw?PT2k4?La@?!&wqldY=%@M|X^MF^k;XhvNZi@TkeIQwyII9~; z%slH`F3#(9C5!WWUC8pRUl%gO)0x`{J!WzmOBBaF<;9V%S0WPVu7t}FJp|J=h@ZNO z5*t1Z*An>Rfw+Y7BVpqDNo;rz7f)hYW_B-L43W{R85|>qO6yJHLQR*;7*Vn-dJ7{; zbCG!20=PKhPscwhLqSK%q=3l*Mnx;lXc9C3xhT-=GjI}uN%88ha#v6n2+ zfyUgH=pjsAlZXVG!=x!lT#Sf>CafEoOp+X*gV=?ncs_F#N#1&(mkOzi9_~5N>x$43 fe7e~{I9+M*;Gb%UyA0+fhUo2uE_cjm)8hXCLkN!+ literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so new file mode 100644 index 0000000000000000000000000000000000000000..87c8b506570dc97d2d9b5a5ce63b40be37bf9af3 GIT binary patch literal 88584 zcmeFa3wTu3*#?t*04?E$1J6%H?nDT5+$9qgVYo$**0-Pq=1ZV)N0f{#*|P zwg1vCNBfoW*X1Xv^iEb+C1*E4KDTbD9e>@(_^X={@QGfXHgr%%eut>=#A~*S7rinr zKc)ZnPT_saj{=Xn&QKXduhHj5;W^6rKmDlUIj&RrCDV($r=4Q-YM0(FTLyx<<2&M- zv5o;xJvL|e5X*7lhj;fgukKggy&>lbJ~Gh6VQ!?(#S#xgAiR-ucvhO;(C)3)6 zZ}jtLb@LXke^K`>xW0?;d-(np-@oDecYOKTV_)R&2l)FTzW>1YpTamCdvW~~-_P*< zH@=_a%g-11ekFGf$39%Y#&jyp0$@9@E@4+&%sw`48D3$&fQ;Jv~|wf9*?XDzVH0*#QhrzUy`))aM?Xvc>Pcyn7s{iKigWR=Ghrc;z+cx*)uYVi*eD9*|W8dn3#fN`6 zykX51Zy!EpQ0lfzN5A{sdFkg(YHNA#H`l*<`-MX`fB9UmRTpo*usv<*2^)U>5iHg-w2$wa>eAH zwJTfC?Ur}#%UK)#b>AH)j__{1;Hlc?yQeMs;=t9zj@xIw@%Q^T{bSDuw@x^D@O{@F z&{Q9HTX3SoaeN$OgEjrS<_{m&HGlrGUGvAB&^5ojZ`b@UTwU{vlH_}4l6=oSrt5T8 z_3D~`sZZDZ=SkxK1Ow8Q-kiO==D$ml&g)6a^+*!_!X)*zIEkJgfRXA-&i*i#UGc9c zDOaB)dTvTmkC!G%e|8ditR(sVB8lGqo9T)4mqgC*li2fKNz#8RiGJQlQoo-hk-u+}^gT)B>6WBD=Op3J zOQMJ0Cei<{B=XEoBF{HT`peQJ?c|Om?ai5_-mgs3ZmW~%b8QlPb$OC{zY_X6qYFFy zSrR!9C&_n75`I~dbe1NOGbc&AdOeB$4yz{gXA(V(OCsmAB<=Z%B>4_ZqW|@bOzQV?%6#pwF&xfiWqxE%`ir=Qn8%@7O zm8)3EGeMgJ~;v~f5=RTGG?^XILd&kEr{XeOC z>}yAN+@Q+!cP0Pvioajwdx6ULmx>RmbRJjfM9Vc?$#b*HFxpO5;W@UqcWUhfo{grX z;$NcbC7N%Q(&sg*U7*Y%2jS;ap^=^@%~J5)WsugV*3hd)TUPIR;HQt?kD@Z(iFzf|c&+s^|^{_B(;E>ZH&Q0WAf-X2!`ekEt3{#UE^aIY$F z)UGy#u&DC|@s@gFlhtHTjt7=TeRAvC#k{S$vZ|~Sf_Y;>vwhyAVNnbZ|#X^uIE2m;+*<@7ztg<<^)zD{DXPu$ce6lcA2}eIPr3z`!p4UZk()7CEYbtzo zU1eW6qpG5|Y_f0a)T+)#)F~a6GXo7_R%Mr&A@sJ{GkjXRsh*`uogjORj~6gTCzdD{ z5HEO)wv`I~PKgyZ7DknJ9nw{Crc~Ee&Ym@^sZ_O$3#^+ByA%Ilo#R%r zu_)2D+EvVGLyN}o)zlzbU72k+j!GgLoFHN1)3y;RtED|j$YB;59}C@8l+H#tyQZqM zU&WWMO!Vlhp5d+Tj1p@IaStl7l)}8Vl`}iL@oiPLb=6|H<1Ay8o>_5=W@2M(^{g(~ z8rGlVwy8Bp$vaidcBP|YGOOUWx><@X;}B6f9aS~GY-&aI3`Zr5`g9h?TaC_C;n%Og|(V^oK ziG@|vR?LhAsa_n7In(E@ntPNGHY-xZM`WR5N!hV{v*JkfpeaYCN3zS(#scEaN!eA5kSI z=53nKJ7xBqS#}<@?PznhZ>mwYNfn1+rtSWTuoacw>f6}b1FD1fMXi*EBI-}y|It_d0)#$-YPWd1hL}uNr!l)wgeaohz8+u{B?Ig%o&zd^B z3tB5NMk5;hqxGuduxJUPoqCZvv2!D-RfVIbLVR+#eVW*0ZIccYJ+q=3HgI+ggj8hL z!M2@@C0C6mGP`zq)f6oY5`<_`sywzE++}uA%Nt8^O6}~LuCtCB$XHbB6eE^bQ%Pq* z87S5%rp(ST&AI9%V?rudQFO@EwW8TBapyC{oHyC3@8p@eZJ@fyoq?DqRn<;ikfN)9n-Xm z94#VX*_>+cv{(eagwoV~lj|yLt0%`d_vqsiIgeF>;~CVo1$?R2Y0^&b8pa!R1$6O>GqhH+a^uI2BW>;}9HU6-GbBo{q*S>D4;o zR3Vx)@}YAERn&NA*VdtztC_!wP;nc4*D||3#h$VUyl5KJ=9EFaikWp~R9i(Q$GTWl z>A0Q33QUl;a`w!b^ltSWV@NDmGziM6LPT%vEE(9<16uwVM`vS+Kn2)ygQH?q%<5FW z_AF%Q`~@1YjeRVc_&CbJ=qzD;ussD*d8);jnrZXus$sj(BByw#B}q3r0Xs@uDpmIr zIH5W^jiVWRqELmI!W7%0M%BUuE2oJgt1_$WtigB_>b^`jF;EW0hiBpn?dW7zQH8l;+2s{|8+9W~W8RfA$r)85);>#QGD2{S9|rk7#S ztg{H66-R>-t=BSLzRoDN(qy_`2UjI&GSY;SCn3TA;c>m9yH16&#>K)6Fskl&arIbJ zt7|opb(PTf)cTAbGz%-4v9$F7C?m$l=c&c&A_isu&oQWGcskKGTh?`B7ek{LFHWPk z`l@Q@CAI>)ildDl7JO>;&C0CFC|!2*jMruOb&|>30bVHD z?w@+NG|bqOl@+yEi-n6}XXnMtk&Fzt3H6c4RWh>7H|o?vgGdZEX6+jr-iV!y$IzBENESw8xkr>iex~MGI;VBvIDa*Ye|AO4w%8O$N`xM>qANO;*;Z3jZ za)(V0Ab!b{>xQ?-j=|ecWRJlcP{+s}L(_2A4VXJ%-Q^uKC*tu5#%1hozzij0$L1Z= z>VYp)=Pn%Y?DTnM%G`rv9HzzRc(r9=Jl@&)M0ut|Nf^p!DUY27WuP>6`52!(uY971 zAX{o%${vnh63TeT%AKvBs0?-~_yl9v`P*gbrP8!xG6uu#GBO{^7md$1@`xz`OG_E~ zU&{Y0|AUD5q(J(=$exzNQ2<=mzZyGt$2#2Du)~oUhi?FJ568(0*GhPrBS&rMrJxQz zR?n9renvGo4(B9mTQqQXDU-^sQ=Dr?lG-(o+W{I@EiXQ9dhraAE`= z$0y3Se=E?DnZV!roy}(@@OLUcCxKtA_}m2kb;S=!;I}KjAc22d@kRo_Lh+>u{1b|w zkif52e0c(&cGxc0v?P2@0^d!gGdF?%r{e1qcuVn16Zrj#U!K69e#n+*RRVv$;u{nA zixs~*fnTrqH3@tVmG8O)zL(-d34Du6XGa2mnu_0&z@Mr3Jqi3zif>Ked#UuT1ip{r z+Y|UN6yK4+=d1J`JV<~~v^`(0cxM9tol3`@z+b82XD0B)iqA^m$0$B0fiF{hZUR49 z@k0{$nTjt+;O|hpk-*n0zBGZqSMd`P_(sK-C-6@xep&+ms^V)B_-%@xo51f>e0>7{ zf#R1Y@Lwo?c>*6+{Hg^0d&M^<@I6&Mu1?^4D}GG^e~RMQCGh7dK9s=cDt<=-e}&>( z6ZlfacO>wW74I&NZ=bb_&q?6@iZ4juS15i$0{^t)YZCa^6u&fqe@pR=3H-klzb=9Q zM)55PyhGVfD}nE+c*j-odz*j4N zbpr2Gd?C}{DcJl zV#U`a@RurnX#!uM_{IeOO2w~B;7b(WlE7c5cq@UQpm>M+or>sqRHpdM1b(XGa})TP ziZ>E?uj0!S`1y)on!s26-mb6Z3H*B-ZGKe(|BHP#zb1jtx!2|$HdUr@YzQhfaN zimy-LH!8k8f#0e4@*CpQd0+7@3H%<#7u*;h|6htybIePRnMB{&olDHOVeDrKCgAI-6>PPfbs`1oO^fN)@S%&DRT;rpO5>wvh#A<*s1Z+pI{>I*7)eVALKJN{uC1Glcn*|Gt&&p(fBMC zmzP|PKSnF>5RE@wi(jDeXK1{k@n>p$sm8yj@e?#YTjR?${w$53rtxQMe2vDRqw#Y! zK1bv0HGZJRFV*;SHGa9qN8ia|@G6ZzPmABE@#ky&YK^}@*d=W2YB#@B27l^VZP<40)xa*a1MewD^s8sDh# z#Tvg_YG(P%n9)k^y|3Qmis_{2!`~;1^N#n~kzD(n%X?(fH*Jyl&#?RIG$r@j;@s%3C zRO6>;{Bn&ysPU^bzDna8HGZnbuh#gRHGYl8Pt*8y8egsPA&tL9<9BHMbd7J(_`aI` z*`x6@wD_$WKU3o^ji06Q?HWH@<2y9IM&li0Y$4eGZ`F9G#@A}RTjT3AK2zhr)c7oo z_iB8O#``orSL1Kf_#qlUN8<}Ley+wF8b43tOEvy>jh~?L^EJL)otC<#xK(NewoI%YJ5QBEsg)R#CIB zO5-zfI%!X#94KZ`JtE zG~UwqW{q#x_#GPmzqkLZf&bOO|93QS&~@q&!~d1j2&BEfAJf@}khfc;#qe)++SVsB z_*2l4vp&OL*J%Z~BbFIkTWbWLvpyuIMQUpiyqmZiaY*nk;_k$21aBcuC2kbFo;Zzo zx!{+Hdl1(PewLWirnVZvPZB$c%LPA7+>^Le@O{L+hzkTSBR-ZmSMXiL>BL!rZzt|e z>=s;0%%%M{hu~X?UBvA_gE(t4v75M6@I>N1#4UoaBR-xuB={=gzQk(;4e#O*(^{GSqM5w{Bdkoa`s7QwrT&may7-bH*S@fyKfh_i_s z1+OPQi+H)Zt|h*J*dh29;tPq}f0X(s&LwUYJdrq$xJB@F#Dj=Kg0CXJhxi!-4hg=Bcr@`E!NZ8h z5H||Glz1%ha>0X$#}U^HK9_hragE?Jh$j%23m!mxJ#nevZB zQ-~V{uP3e|UM~1$;;F>-f}bV6nYc#qlf)c=+R6n#Ok7P|D)>I)TZjt;FC(5#oGbV) z;u*wQf^R3DN$eI}OFWC%A@~;J*~IM~Qvbv?#I1rS65mSPBKSJuTH=u4tBC7}*9aa) z>?Lj#d?~Syc)8#~#J3UG3qF^44sng(Gl=IBmkS<1Jde0k@bSdA6Bh{XMLeH4S8zAt zJBYIc9|;5BN$eJUfOrA1L-3cx3yIqgOZ^kq6SoTfka!Vsi{RbFcM*pK?;`dSuMxb3 zcrkIK;Pu1}#LET0OuU4+UhuQTcN5nLev)`8ak=1!iA~~C!S@j_BQ6lUj5t7?EBG$r zUlV5uzMVKo>=s;0d=Ifh@GZpm61N|c`X^pa+$wk?@o$J*1YbvdA8|lK64r_V1+ri5rPq1%F8V1aXVt-Na84hXn5;{vGie z!CQ!*B5oACp7?3v<$_-({ylNM;Ae@SA+8bpB=KtEa={N1|ADwv@O{M35*G+wM*JLc zuHd_fpC`@|d^_2*MdJ2vrT&T65Vs1RNc<9Ui{R^sUnUL-zKVD) z@fyLyh+iRY6nrW1tHjF%4ZNbC^&CGjTW_JdOY#3ABV!5GVcMz8gewcVCajD?@h<6be2wq0~ zXX0GJcM-ouoF({n;=qn0unBMFh!yJ>HYnUb3LrToT?1B>98H+>SOI!mBvrAnAOR~#d1IJ|7 zxCTCwU5{s61E0#K;d2dqKD$w_uVk|$xCXwF9l~{r5lB5N2R8;%%ghTIfm0ejA^O=c zKQ)4jvs)3?oSN-ql)z<-fpO0K@y>-kT#tlYuZ5b{ zq%29>cfjFT9P*tr8p<<}F3Nj-JuZQZtUL_IxPl3SnbdYVa1%a8;EJ|wFjby0V@C%@ zq;GH|TYrb!HF8h>!8=n^J_>gf<$adF$9uM6{$x}hM08{G=iRL*;oXbQ4712#-k2Wl z;dvtkj~1G(lWz3f&@|RCzZ-3sKa2{Da0Y8qtVbbqL&(?HsI0?k!Lf#Uz%bAICbGpG z?VLF2Mo4jtQF#b0pgnpU#4VmR0w+dxn7FP&;FhMbDphNwonCiyx;$^^5^gmr$K#P> zj6h*aM^h9837SW+;W(PCXYgeH9H+N0l4hR4i~~lXmtl4@%pIbQKf!SQm)f`=zW)DD zw2=;N^fAm7!)$3nKY0(noIm1n-Ay0G2==>uk;7p|jLOvYMoLSh6){rkp;cVBA||hU z)%81d?GEpcd@pr%tLw??da=3|sOt!I9iy%l>RO|&^VGGN>6e;^t-HU5@`FR~25*?# z^Fr1*)CL1)*#RqJ0Ye#JeS!vn?&FUfKc_c}5=bd6ys_%QanHz3RG1U4N~vv(@!>b)}qq?ml((scXHu2Go^eGv062^`O${ zqY6KXt3T3XPBCPh0h!`!Ws{2afVwVPYNxhb;b`u^QFtn^qpWT&B(x9)Uu4=@LWEgX4R1~_fBfN1vgERyD3o9=)8}lyg5c-gh6Re zX*e{ue}0W~!3jnHZ4CDB}TW^dn|HudF1A4k(+CbfXja=dPav4$Y0RcFyAc++)XWs3Eu9Bw65zx;b=86)CPt` zp5gRh^v1?i#`!@S0M>ZjmJ6cljKZq?G9LHyb6^PeehRZ+!(6^v zGFsY*3nU9+W!OE573Dn-6@v_aK!&KHdPYOiX@>bRYv?j-82c_2IB-7{s0oL5jPdM5 zDbJ+^uGjvCK5qo>Wk!)tw0MmY&&7BgYY-Ct8q~_$soE9<99{e!!s^8zzE2hZT<8JCKdQVI z9$`VGfGC>zu3b!2F1+ABTI~VjZ0^NW57OJ+k%u=?(<4e* z)vEsmS9BofywK|>P%QKB2cK&mo`qY8INz{lz&abv!wn&thnK4B1a&P}*J231-mdPvUDJ8HuJbn3dApoG=dIOw+pcaW zH8oM!R_T|jibp_~vzh5&x02Ifd{tgZ)l|t8WA{wyZk_Pt#(auRI|1$jr!Y zX!Llf9IkM>uVrU+hgn-fjKaz z3$xntVEh6PvL5q7`CAsiEx)%BAdtSuh=lUL@rBoYC|{Rzf6|HhmJ{f+5A z)RdT?8{O~BG9pdZt}iH=_cQihQAgljwgRNN8F2#-vUR{^+>$?NVOoA|>YYxc9{v{+ zM)HM$#~Ih--x(>&3l*9>i}ITmrWV4j_%yvBIx@K5v;Q0I&SIL(A?cPFj z%fJI3bGK*nk<$wEb{7uZ?f*XFJw5*$kLwYHyB=#=m{zoSx6f7Re9xQa$$xiYTA{hi z+)*3}dCYgs&CKV~Nhlj?I&h)?&dd~VFKJC}Y?XnAR{U)Uxf(dH^)JlKT==Pfp_9hl z>-5jhboi>n=WWEm4`#phhc;0_&*tGyQsIw{Ktm&L0uFPy6HVrs@LRS|kc%dS2Vh!@ z&8^mC)Tj|0?S}4pzkV{}qhmv@;dCS5%Zd^QpZ3pBclg{J>#?H1%r3`3XAV!th+G(` z%(VUq<@4V4Vr!xKJFYTFeHei|5&L7)9|AK8)?_XkM#P?Hbe0(dKG#85GS_0R*aXIB z`j2F~?tPs1F&%Ji3?+wybD3R^X<@w_4?~3iz8qdOKCK-6;>y9*8mk-Ajh2J01m(Cp zgsT)qDodJMUTE1$R@#sU`PX-5;F`?*YcqW(l^TKDFtb_cw3b5e6H%M@bJpi?O=;p6 zJ(K_TN7Fjk+vs1m_VLB$VY`>wc3{z09f-h=2#Z{jJ%Ka#a?aeRq2h*e=8pOLue0kB z$u;nPHD7-uyHW5{YQFxw%-3B5U&(F}{tcO$$Iah|V64ZKJs0AXm`C!^`wO$(MzF-5 zvKQnL}?Tqfn_n2M;Gu0EL9^DfsiVAnRXSiuP?9&?*f3Abpkb(gfh zZ5&eul$hTj6SNS+Y_|F%`pAaQys1UzdD%2BL#7xPWvGP(3$B@y^}T2-^`IFTr~?kt z!qQ0TguKwiCK$6EX&!N<;;IDXby}(aibM_=u5(!)*xXEHkt$h`L`e_0Zc58VeH?I2 z*o-V6psDw7%~76D{X&CH(0eR0EXX@#9YWivpFhOmMcMaSbN`LV^n5P-9OjKR7!gT4 z^^ZZwCgdq`#U1ub42$P>>b2L+!LsRR1k%<_Et5bq7vYT z?0vI4Gt9&Q9-dGVtSd0g4={v1DHDZ{OE4k36GcPZiy}LGX~@kd4^d3(Ux*Yw(-V20 zO@&X*0KrGDl1F@h+dkY#DK>A!V2I%&R-+~>^b%Msd%TMqE;2{gFU%1=r&(j9wLqsI zeFmM5-pIygm-JFrjfsj0c~52D&DLtvEl@fNl=qSKGE0-XeRvKg#Bj#j9)=Rys+b)7 zre7k9T$RPUdsQX&G=lRlw(f&Hte-#4;Uz4v-eWDWQYTV?u^Vfm0*oGI{fYA1B8)eJ z(=MiAT!AJ5U0=d_Q5CS?dJJiTJi{=*Mp{-Gk^=0zeRysPL}xZ%eX7fTy^?(zWtUZ! zhC|-8wg+1yk+DW_b^+ZGWL9cz6D91m?nKB$FmU1Sg1{WyX*TSooP%g(Lk_TGLjT%Q zT@Kn>!y#X~5iCQEqx~YWlHiQ&Oi^%&`L*>osOELYu7@u}7lre2z~x52IpDg!ckfzs z5$Q2m>?ycCFJwGq9RA)&L8qdX$|^Cxw3cATZZz!kN%v@@tmtz&u|9_;a51f`f9nvt zL&pYrS%DJaN`f~R(C_&Oy5?vPIotURQteI6@xiuxkhB=#QJBqsvKP(rc|@-t`V(#) zz7sc{ip%!2{Xi+!b&$zSdj$80`zqtopQGsrq%8rYgCyROZFl`&O(>~6eOY!COt%A)`C6ysgX=-Eyx1g(S)cwFbc4TG|Is_VkEFd9xBqn{TFpD@*xv*Up4 zhTeYzNoupBzuO4jfEC?!=TDFi8unh8>GJ?!$hPcBL4I%3CKuqQ$u;KBuCZHO<2Kt(!j+R& z#zfFOa&cd&?oYPz(Sd1W=JjRjCsXrhD{5|Eq6ugn=x=?{>WBU_5?Mzh(#92M2$l-z zCZNHw=q@E3xdVogJS=1MX~F}f6^QI*t*;9J1>?5FFvU#Z>I-q4X>Yp z^zwGY4b6*Ke@8be#y~>rR>}yb^?DzKuV>C`6j{qS`P+PFjSSv#ild82C1%9BMK*GqRnarhuXYb{cwQv3YW~5x`nG8gRM{8fd_E=*wup z6SNUYdM5^a8k)XbxlK7_JrB)ca^ytfj!8}Ro8&B~uh;gwd6N>k*@?NuMtbY$+0A#; z89pB0P5fh(@>z3>Vfq^ZMLj5=ehCa8tA%Xag#1!nA2F-8+XUq$Zg0Zgn1k0 zuuNlJPuy{p9Vp9KM-4I`pd3Rg(UxNv8Wd*M@?&cV8)8V#-RzhJaI&#f9NgG0_^&AQ z!BXpQP{sk*xmTw@ba(@uC)|hfLhJ%)Yef!PGCpH_GU}NpwP)x!cJWMJXz9u<=*jg$ zJ7xk)SLUgEW*T_yGd$#aaV_)1au4OP?)d=jsGk7?{jK;*VLfA%w8iFIFpQGB)em;l z2(HaRf`O|u;rnAQUWf_9*?|q~@SNe_?)JD|?C?ZFMrFiE!T7tNJs?Fh7j%UG>ItM~ zo088YK01~@A(!))wD94;_*|HnUt*EQ2;LI{(`I3&&EP$q!RMguB6tUbcOuv>6H9?I zQ6DH1^<_N_yA|*=V4(ji{AHO+w=bciM#UI`jm#8INedQm#1jf^;;LBQp|*`+;A(6P zAp)xTp!dScNh-CyeFp||9l3@1DdwxJOlC_y@CAN+cN{XLJZ5z<;n1L`V z1C}vfuuJR=Bqi?eXh|RgN^kLKgDNmWN%4ch7a4fQy2ghvYv2?Rd5kgwJ+?1lvtTY$ zT^B5=$L;OE^j!?|Q_oCr?Kfk(T$hG`VT-oZrosen!QJLM-mzWE$^+sC>Jca4XFDnh zK1W5nUL2kd^ME)3mWBi0{gNK-`}KJCymspvbUTl^6?U+p8XqQ)>EXh5P}j=?YN;b4lePtgdr?78;=zR zFrx@aCxuFO94K*}_kGd8{e}5=WIKyo*B*xDIAnGID+Z3r?ZqkEgm(qGMT^lhvZ4_; z=5y@I4f_`KHov433wjj@>z9&OpVVxuuE1dU+58~z;${Z+mq7RLS^-qY$vcF`+ycQe zvT?HqwRwK+G-)cRxEtuR-Sj?3TyZ0IAj&uJ3l7jP_?v6Y*RHWGxPwcu4TCnS`ueT7 zask49M^?!j!g2C0}uIYwoN^)Or_Y_Vo>f^sewtWJl^^y-Hor@vQYbRWw_ zD=fiuuCRnZ%-bDKTMyYBW($YPs3R&3U0V5RSapF0*(^R|rO-gKS@6dRt!@(N{vF#2 z6hsTyMYEVR_5YS;@i270%unmP81{h8!qvdTDkb*lKqqS_Tqj)7C0!q5=nBxVVrpul zZ+?&E04$TTn|=?s)M;IbWf;A0MO5Fioe1cQ~bk zZ4IA*4g}4pK6C(U{kF2$DBnioEHs~Mg%E|YX1N@wZ7o9VX8C*EB{a)U-Pc3CuvD}> zs8ZKr6F1gZHpvFddY-#62dq6meHdNCFo(-_QMu7vkcAM(MrzCszg@y*9mK;enc&cl zDL5uk{|`h%B!~6T2S^||^eeDvM^4Km_TS0nKX}VUZtS^W2szPu_kCux*V>K!8*$Ov zeh(+js)X^R=lT=U!V}h`=uE`>h=UExqq!)PImxWFVGZxr6`Y348RNi74BO|k7yTO6 zF-9Qm2@$9vWFxlv=Q4SHXRHjIM;1Gx7W_N8}ic2&#nMxp1K@LdmuVE9tmhV zcmSOWI!Kdbp@RluO$V;UKe5i3#<#e!WMP z`*69vZ6O5e)C5F^%=3-^QX4BO@t7IahR;Sy8w+hjZCFo#EYe^myuYoOG6(ye4neTt zwjW)|Fh`eyIgFFH*p_`s2F0rQn{F4!Y{ z+FV=Df&>Szm4FpX1@+r4D8L4fm^>(fn5*oV)q-R^LV^!Sj)B#7Odc>nOlcg!!Kd44 zh9s=tv+`U(3LES_+fGxeJ~+78PE%}Au-|(kdZ3lhnQJY%tZ3&2`vp8i5=@&knVaH; zbNa|G#*OIs#`H)gmMexd1;%4l?6PrA-vHSl=o2WpI~|Mnmz6AWdiUpr!rifg-?VN3 zlEYRB=BMa&-WBR_D;=q{m9W?skKD68Z9|rh#7voG5GUc>}>sL60;ExcZyj8yP6s=NQ?r(>vI}&P9p&4(3`f7>D0AB0CGQadHCI zR_10$9e`* z4Q%4-9M~+w9FfTx-e~R?!5Q#klL&qJWK5egp%a-)sQpHn@ANfC=U@(MekaP6l|ih@ zoryHE=l+q2LpIC~t_)Vzc=KgdGn^Gz6AWwyv!bf0cb5R#qN7Lpw=QHn-z|3OGS7(x;% zA(dQjfl+d0YI2e02Xav&c5)>V_N_LC0O4y{=g`7-&Khw70)b*45ZKEOB;*GUn{wyJ zzL^fprEshaYRi*Yv$r+pa5c=wh=%#)YCegaN&*-7j)T#78Aih}myizJ&9Te~j>*oD zWmhb|xf{e71vW6;o^*-O_ROP~G!&WQavU9!7qM5tC-0WwAww&OYkg}^*IkE^zQ47= z-;vUPpa0)S{M&l9ea0-gD*qu2?oR;L`@5h5+neNIdqm`A=tvGvxBWRTltaD!>?nrj zppR+KZj6QYU}!J4*J#S-Hdn)AvG73%FUrIJhpdd9y#1^sC^OdZNyTQ0CopmddTr&F z{C8dV`teY4WP7oHb7o+8W?_E2>z)@uT^YDx?aACtau#xX;0A0DT#Ay^%ki2Nl`Rb0 zs0v8je<%D_td;mbaT}4%Rv#3qFn~u&ur`K8uaIwd; zseXdo4F8tQ@XiCpu5&T_!4g(V68a40sDN_4P27P>~%CoY?;1IYFo}&<~!jH*iR0>R%&j{kj`>g zj&ag}fs!tZy`uY3r=+yWo^`e4BFU@FtV1@%j3>3_0_gEi0^aTyOPe6vCO#G~+(9g< zs+rq$M&M00lKe;69&lJ@dQ&Ec_`v1ISX0pl!h5l{+jax)?3Jg=k4FXPr(ikI+R==3 zF`!^G3?=|W%m)Xb+Mb#z%bZHG4kT$$TaWztOE?pA`fQRCUKOq zf4=fgBa5bOMNp4infIRbrV|;2J;l>;>>}`d_g0{h*cmytII^`czcO>-=i#$r&1~Q{ z$uNXC*xSK^vNZw>QrR6|H!*r#I7*)JGO1E0RfRANofpf>k;T9vZgNfx@OIbqPxoHEjxBZ1RwTaALYZT@>vLfGh zp$K?Kw%aHatefEA1CuCNvvm_}y~o^AVzw7G>~=N0hz7sRy?D24@zb~~ z2^OT(AGy?bS;@e5W|u?bn=_Jgpd|<{G&?+-_jkj_5yG;7MwQoN{n4~$9SWn;3_@A!9k6f}_Kxom{n_pcK3j_hegmz6nv zd$@;d6bG)5Z4;b7;7D;pA$I#FSignHg$SNNX>Mo;l^_8$TbxhuJ<+xlij6?Qo=tAt z`R~ZibQiit;=(nt^aW`}DUtBEg_nWw<`h>#$NPD_A=9WnM47DpZ(}6^>19H(*#B`Y zejlt+i3AHF8={36tvLAWv>eR;SgaB=Z>cApWoY(fuv$%z8z{&0IE_kAhQH&)g*QUj z+>+@XIVJ49CD{eR+DNc81+j8}fp;X#Q5iV%lW*4MmYB7f*eEIC_6UlaW3@|1!X1lO zfJMM8sYi3z#a9K#z!Df}Cw@F$qV@VyPXd2V|L^KPKDl3Vx494PxZVpTtorHm}5_6w0+M$X#5yFk>S- z1jfK1%`^0k81CZW5}IA4y#|4S1{!mu{yYFTp%6RdF(nGvbzl&Vyq2Ugp1FieXc$m; zc+8D75^&D?+|v@h2}N&c8?4FH9?K3(;&I)-8CkZ2H=wEeH+y>SWDmYSPzW6z}whx_JT%GYa$fF1Wa)(i+))mb*5A% zca|)<97;gVLl5CkMh0i)7A@ZC?QeaJ<^qcffxQ{BpvWwq4m-IJe;sQzxP*1-@h`|s z@ibq_?JibjBQBnRRN_c;Ru*b*H5dk>%J_hZf+7yz4j3ix%D1zy%%&C9~(sWzSZZzd6U(2)B#vH?T%*d*eCafEie z{tuG~K4x!JXp@L-aJR82$c78$cTOTs*&drjFpM92648MYB}^jz4JtN?IQ zvVzM=#1DAdo+eacI&S5mCBkuFx(xkiEc9lE{;PACW$30@=*bM-ra#L`#N)AW1L0~C z@y;93Nkl7xkDf#<2AeR6pmw^LM11!qClO)~l*pJwq>n{;^htz&b2sbHD0BQQ!cUJs3eo?&S%ifrVzUSu8PEUeEMgC085k;f z;7prE3}$V`&mz<@VGo=T+ua`aQ6y5Zcpr}v#QEGU{{X-2Id%pHXVh;vh26#1bGx56 zDL4qb4=QATwz{h{a6}wO!OgwbZ=|y`=|;W>w1WOUb`!ecT<<{kyt!zD{*K<>gXqQS z+x=1ZpC>|xW2o4RrJ_hHoMOD-APY5DQP<{P8BHd&T@Hjj=fSv$6YT#gD{)$n%+jV{ zNI-^X^CX?^5cl8ADw~g&6u8av9NOUaw4oe0u-apFg9n9<#=@|p@$9s<3TupFA=)OQ zXItfV6dtIcA^a!O%IRDWz}rQc=#-r+_`VC6#u%i5K_Fut90+vLPchH$jl}$)7Wj{F z#iq5xAL`Y1glU?b#*dJd%x3HP4PxX(4!%FT4i7onb~B#Zo08~hh;EglV37I!xRB5B zQl>REBq8hLLYf)!mbm7=#<-B)Ol{EXoaaO6>9o9Tw6Of zF*jF(Vb_;jRxA1ZhSdPF;Av|umNL_)E)-da^NIm#lJ1!TgA7Og2Me47=A_^>p@(}i;ptZ{d z4gSFnTV;nGw8I+hu#hCUVzr=ta|H!xgOJJw5n*k}j+t%8q}_nfgY1|scFdQ*7g?ll z1_z&M$82So8DJlWob287df9I?qGx^Jn#RnK`DaGLjqSVRYxpW$ZFoxWu%ZlOI{7 zy_fOKEqLvua@i|g1TR;?=&tV(y zWH^_?J&SarK|k9X#>;LJRSd=Lh)zQhds~V^!o6xm3aHwRAzViL+}5Y^tapf=ba7zi zj|d1Xv76z+f$|UPxL(|X*Mz$-vG}NIUoI23zb+MiVTSdnEx-n>Beo(AKio7}&k|#> znFX7uR$N2}_ds>FjpW*;9my(2Bv?6njf&i)w$~D#8;9pQAIRhb_T!V<0unXwIv+I5 zKj$6VzKAwB#X7K#v4-Q&>)uI`R{O1n$3hU0FWX&LZ2qV!3TJVxL(rLYN{l?ckXQ8n zS12Byw|>9YzTSk)SY1+>J`|(5fl2Zsje*Bw8UcSJ z;vv0>vbN9B8sdtRnj$+V(rmBCs@5+dI|uUkc10f$uyszbhFMvTSf1%?ZZUAiyl4eWjFR$ zsS9-&?IHG_$^gxstw%YUfnvkt!Pdz^8hZgK`ox25(sq-#d+uux*lw3RTyOoUGj)5k ze*r_7^$lcFDcJ0<$xgzfaqMkmACOLCCw@I*U{MqLP>o^-i;YHcOMIjF1nQ19ga+gl zy>HX_eHx#~JkVI?y<}hWkWZ|!d`4lL8<-V8(pVA>meSYV#6Q+yG{-IumR@0qH$#u8 zd03Vc7dc;=iW|=LaTe4`w`+~DxfO2{409W@T6c^cgk2jR43)S2-$UIm$hBHf5KQ%k z!1v0+b5@Ahzk&Bi%+J}VeU_nDvmCDwKH6>3p4#qxl}Fn1c=(UK{5scbCE2;=wN7mH zdW$ixZeEZX7_<1|yhHvYgXW~VUc2~2)9dFJariT%nZrY-h;Y(0K=yEZBN1`~8nr)cf!>N&N;lCnl+1J%9i|w24rRLSj_d#}9b$<@Bz`eL% zH__P)Y{+GSue0yPbcl1i;1vlp5NM~kPgEo-vA(fWq*^AmeFi|wua2xn&8vBkYr^)S zrJ&uPg0HorpjIW?u(_RXULzp-2MsMZZSCIC>}RjzhmL`NLo07hQM>OrkBQPZ7m;#= zt3A*~zJy?o;D9u)YiwlxEpMmkry1u1f%%#F-`{zGy?2346u0Ze&5_MC=aLmJt%!+s zg6$2;Vh+RRhGF_(x5H_+fil}UcCfRvU12Mbr#tb+!%#N!ZZFdsrFhSVUBJR!Hy_rH z_gd*_=dz+2#*TyC-yQ-L#xeK3)_&B3>Um>5Z{mO^w)oL<7JaoEx0)}pIGcVEHnJ9E zX1VUpjawf02|~yo<84<`dRZR8;YeD&H7VQeqp2`&OKg=W5l%qHm?Oav^miOT=U6zL zPvL3T#*A(OoFolS$<8o>b#5$1VP)nEj-nVhurTYkUippr%=c~bVaQGAo(Ej-unlmZlI{6})!(RD743f_eG7|wea%YQgi zo70Lzg`ejB`cK!-&B?*44lyCvFqRp%JDku$_Ugr zt&^XZfdDVcOXYfG{%`d_X=O3lv#f}-$C$75C<+<=QYC3%dm%XZUX5A**k70((^8-SNCx=aE(Xzg@J!l;njl*JXp!s*AqwhRyok+1?LwKH&+->m zt!tk{ES=pBqlvRw)(KRf9&jdF6jp?-Q=VnI*hJb(>pAR4dq2w6a5~bL&85B8`7ttG zNO^T9Ge#apxdL+57^m|Z+JIo9XgU3Jal#G9<=FZbx?*IeV_{KvVsk+@6@pzTh{f22 zm)-2V^TR^(bE{4UZ#l|u1m-%e0Z=9?8?Wj~vV~@OHeOl|H>;C)aY~D5cf^Wo#ug$_UBEc%uZ@dIqlwhg(9BWk- zz#1$Wis{$#vBG~8)pHa8%{2M3-Y4BGVx-v1Vi{iwT62j#4bO}XRSe5iqH;^3#=8vSS)w=STr_o4=JM)-Vt6c<6zjfWD{I% zd)+68dB)a#1^~eo?~98X{(r#mZ@2WHU!-(Jj@RX_lfauH3KD$aEne8$|00^=3YTcy z?(F>`R(QlR@n!73ZugRx)_}Lr3ndk4#Rm?euRpX}#9?25>3ilU(a~62pfl659WgbM z8up+$I)E4K4&n?B2SwM5S7M1~K`VwXY>8OQp253sPVlJ0@#f}292hq2-x-nDM4F9C zIr&|Lo#!!&1DC@+%CUZi2*upH!~s{Q)lPx@^La3JxZAZ@ zYL(A6p|~DQ5l>eBL$e%jK!S+4Vht|qdD?U+|Csjs2G_I<<>6CP+>Q0T#cTGMt@>pf z2}xx}LjI29WMqB$3RKx^8Tj$1z2*w;Ls=OZL{VASVxV6RT#UC}vtK`5virio54>O{ z1x~)4i(IsoG4QBWkJT(ZGyv_1_5@?8I&Nkigb|0&AxnHS|A3*gfr_K};#Kpo!a*wo zhS1-U=FReV^zf$pJ5qgPw)=-5Qs=~TQrkO-RKJ0&!<*hLo)ec}!+q>~m~R;L_=l|D z(!$23l|0@q9!w+RCh=h8z?kdqT9zVqR4V4i+&z?qlMNnwDrleRuwAE{&@STrmah=~ zf8e*sid|qW2f7k7>auW!$Z73D0l49Azx>Z>*Xhxb^9q(t-h8p%!pXaJR7~4?{E2xk zvRPqqSd#u-grzy|4V{3N8FgRa$tVwo4UVG)eHCQa!i1W|ks*8AIQ!^%U^JW`crNaK z6E0zAr_+KLDsi^_zjtK*!7P-|SBs_r2PV-Gz#82Ij-HFQHX^dLnE1Z99_A|67q5T{ zy6lT%Xzx{DJQk6T?u)h1QfFVBLY;N?MbQQOVl#yJf37c{eR*PET>6c4D>~i(p)a0u zKr|HXi{HY#Ks&+U)sTs@+I7%Om&yb{JR+8_V<^wIrM=7yu}<4P%3SVjXs2RJcg z$4i3ER@3jP&~4UyR2v$LJq3e;4ltWQ9k%$f1H9k*QJScq9=JS?UsdUBom|eg9>(~9 zF-BrYBmD~gw^i8bO4(}<)?E-)iTx&fy7fn>rj18B)Cgr2ATJcVE{a}tzA3C*X<|mc|<0K z8o&o(B44ELRK6=1E11@SehTxB_5ZXJJLDFp)6)*Z#|B%D+gHDmTP*yi)!MgM_)oio z6>JX2!hhP~uX$^G(807bY_ybW4W>h{?Gb(2F)|w9%n2C5#3KVSjBC zw8lmsp!wjrkd169?0Hi#ty(0tZ9_2aSt*S@*9oSL2+MP=_H(`L95{9(hc7J&YX`Dj z2rgsgh=S$q&I&A~;Pp)oo^8FbYK#%|uN)Y#Ld&CYAv~v&`hw)1%%C$MCV0+S|L z#^ZX$puC+!R$h)GdvnIx#dW=QV3f6r9upUqFs15Qu=F{`$lE#5+<_%9`6(T|$%Tb$ z|Ie-7^YFg>&(Hdj={Mrh&h)iq82XG~wIcNBjgrOLoI&S> z0!t*knOz<~%h}!4@E~pq16RU}TuJN3F+*O3@Z^UUWCsH45Nt1>?BLtRJB^gT*aMeg z*0!5hb%a|yfsE|@6`W1Eu91r=TeluFBipFlZOy~^c69K$Cm5B&Z*0ZrXXJ139n&@& z&)|3n+?_dach)L1jLR&s4~t+$Hh)(@^5O4>WG=X{&=#^7 zZZ3Wr2{U^5y2w~Lx<#hZQ4CMlFf;zbjjYX{{ETcI$<4@)7xPnSIf0U`T+6L;g~vQRiSu2X15N0!=vqV=XRz$ z@4fJ}l7=xZr*CAy>q>{yzWM9!<}1gtD+dZ)&dpYLa|^c7Trb*oBBjOXo{FQMi;tDD z$-miMgriq)@~EKOl>JzG_2FaIvj>EG8;hExmSKQzL^F9qngx~#?Cn9b1>JcFS|I#` zR>T+oAijZhv2Px*cSd4u%2yJ&4b3UQK`w81{@)gy6R@oxgu|8!&N6Wo&Vf+F^kDhz z4di5c@FJrTSi$+AtnjtCMs8{Q9wBsIC#Vp%)4ZKV@HHllHzbj=^g+HMX?8?c__%Fj zr;oScF==WKzpqvL5I%~w+4dR|XVuvYKB&P~5(zc90m}G~HF&MRqt#pP?|9Zb$zHOb zZP(tIxY{$2X+rHuZk@Hq5*Z1#XN!v3J8m6+Ga;NIKan7nD7uBsW+mdRVXOvGe{-Ki z{c)F&b2#|tM)3Tii)8sy?A%dyUzxFrM``PvbM~9)=7kLoBgKlea&fRnczD#*z5OtZ zUsV*HMl?QPzcCe9+W`^9+qTVOABX|XU3;i7yGOon9Ok7Qt?(@4VJXt87rQoZ6Mid< zCIPFia5ykh?zO7L;c}T3z_Ju8X0;K|g^MuIiTi`8_h;ctI+6DmQL*+s#HD8T<+ZNG zry>db{;2&9Gab6NbY_Cy;e^goqg%X%Pl(RkqBHIj4MlaxJ`v_DY^k#P>pgw@61p$; z97TBRcG=mG){Fp_FT@LmBe!+aDx})^ih(M-^~@ubK$aO1N_FsKEw^b4_n>KX__5}Z z?pz{DQ?F9d^w*VmrWaB#A6QFJwMWODz<{xPp& zzQ)CN!Z9;=O3y(K$EcFv^o+v5nI-1WI4)Ocerg?Tl-lf#;Q?-5Mxj~d2p`Aa`76Qi z{QXsa&`*C=OMDIW)83UU*^bpXt+l?zvKP!DiAClfA-u}hMD}ecbNFiZ{7daFkG?Ku zb-|goDM43<5rGYqm8GaLNfitKp7lAJ9*+gtTMs#C4{QznF#w4q`CB-tb4Z_Z6}5!1 zeBbKrC3YSC*q63mexL+1C)Po12~}+4$&VwgMO#}CfVnSzj|cDS@t4MA8f^O!S_y5x z%C2cE1-(V}uVVJE61#tSvwO5Xj^tE>ux@z}9`9z2*vTw0EG|8jTFjT#8sBNLHoXc_ z*$e3W@ttd)j|m5&sok*>nz*gt)s&Lp?-;|hJWEou8Si5}7g)j8jPc%$3x4!0`qa3` zSlOKPWzJyWpDY{IktjioXM}@?HoEi=n>bw)=T&uexK|H}IUa@-CNK8NyHea;u0j2V$t@Rw)t1PrBTC zB22YfZXF1tg0iY(b|(R0MK1%F(qbGx!Vq%ZV7+f zcHCpg3_d@Pk$ErFe151fgcPku(MEOGpAj|J-*$%mR(EXV-8NEeR^>{zZ}mJZ$Q~y1 z`TZy&nrOdx7q-RM1E)ax+AiFySnEZ1_*$HRNqiYo<_y@&*mE96o#7A_LrE6(&y2k1 zZI}5PhmycXRQRQWfD*xuWWi=j%I^th`EZV`@L60RX*@y9aFR@>@anp47 zeh`}=hIvmt(#Lw%atat-6~i_9TQ@E83pd(t-4q6oH0 z()4fGPE4VtY%nW}Z^A~9a_sdQqgIWeP(CL?w`fM1!CVf?_2kHzbd@L-C~1Hmyq0V(<4``>d0DljgR4zYm}1{ho*OJm>z;UVH6n z?X}n5XYYOXg?iIk$yM!NW0JPNW9QMsqZrS+71U%V*Cp(|%pK5BIr~AP4`w}Qf6E}h zG3&Y3%gMa*bL4~Yo<#A)-d>9aFdwL%X+IagXQ*xQn<;*s)^M8#Q3Y80c+A#H~Gfoi$^S)^Djn zW|x|t7|w&t&0cPTCpDaCvh#$fkoceXcq*^G!J!#re|`O_jhVOgOZ$0fml&CNwmtR? z=B;Pf3@UInt=kXb{Iks1WfW!qfhytI z7*(@#=7NU};gAT)5PsoIxJrL(|B>OS+VzJiN#nDUynUZ}lpg7d*4ufOLs-8xn~7wX z(j_^y%U+ci)mQkAlZltwL#G5j%Xlt@1ab=wo5eW$EsB=w9OWY?pZ?iigGfznwT;dS z`cgUXztR6b@5%f4UWa`vC&@Vyyeh}%>eS`%LIK%u5+SyZt$y& z8FVM|`pIc@>+*tG8zM%@(yQ2$)*!$LV?Gjh_pM1j9{Tl7h;imRT4bLLb39>dJRJtD zLOgdEi@ErKX_?Vr2lTr+LF^a zWrG-sz*cxOf;}6v9uCIXrN){pzDXt_W^U6^Pnl4T`PQ}A%*N&Tej2&7Si2S3G-hqh z3_gphvb1QlrzvX(je&D>HrNxiLHt0*0ZjqvyX?ahR2s=iVyyWF{4I=hk71`vQ=GYj z9W9)#^`o~t%d<9?yB102`bFO6R+PPHyBx zE&CE_0}(Zrfjh5h*?^?n+E3liH}u6ra^%uN6ji==4cVVHF-oWO)K$p-KpZj=DUNr< zA&pubuZly40OHsojvngxZ*j=vulW2}95Rn4j$6cm;V_Omafq1~pGD%3C4O;SEe_ew zBaSP@A#0xEm?jQ6^h6w!#3Ap2;z$>V>@gI_AaNwCqn|jARYwvI_iU3t+bt5YpIA1djNNI~TV!aqyePkj(LO!wFOsy63A;s@_O}>))U3~uFY zk5x6~ixuC08s%m+E=Vysl#fge7sK59rG7GkFUqJy#<|~;)!g7JX>3LZ8mUh^l~xzo z&{D+B(YK6qb80_!ni{(C#Y4G`J#(90Y$kBzIQMDOXqO}-EE(qx{+uCU26~g*Xjth} z?fR9*?7c4$bWl5ua|=;z8Rx2ZZqpt6C69!Ciula_HNTs}>z|RrNOG>sroaEaokrQj zl=(4b8I70mDXU2zOG+&7u5vstGoE~?8s4h--uJ@8+%5LA*xJUKTekEJWO$!Z`=|?Y84Etn4A2 zyHm!%yBX?!bHTs0m;|wj@5I}PFX^&R*m2lujyF!4@$00SZ|IIg64_bZM@QCKJKBzS z6G0j22a=vyzT<+hSejRQVk#%8VZ0f@LJJ%36b94sM=fvL@?lFaIgT!T#Pig? z8NoZuP>d}NO*5M{Vl%dORnY!SE&2x-sR-c(>Y3QsN%uNXwMN!AYybYoK%b1~#RRm@ zK-kXa$JSgU_oqZ>&A@sNy;EjzV8+;YuRo2Y{X4oJS?|}>>~W^^q#u86m8=681GBKM zv&I*RTQojW-;s@|yip?CugZ0D=W;xcT1h#Q8b%q#TD6}fbWEH@k8Z}oSxJvQAaZiX z#IIxoSA7DP42rKOU7DjUPL`;TcZ8u^Tt9Bb>P&Sr@C6Md$oJ8!+F>TaHBA^*;xIju zeKv(8B4>Em?0-r_u|Cg0c(L12NQlnnOHQMIkRd=9^ObQ~+B+QwPkf~#t1&r^iSncSqh{gm9T{9yuFi@8AXa! zTlpAN^GWaL_IMO6bL^q(&&Z@_9o9hGo{)Ghi7)HLZ@(3B8KGA>1E=jbnZau~f#F*- z?lrpvL-yMu7ryzHn&GdkNJP)8wQI;iFS3xy*zb2}fX2BziP-Gd$w=l{d&Ng>KQu|S z3D=WwCmi7)L%J9o{5PZmTW=@NzvWpzg-h*tDdH$! z)ap`%&1C2pS@F>S#Bm1gqFB6%$cWwfiZDW_%usy;btX+Jk->xAc!T5kJ0pwmW?y?0 zb|p91VqQkfX(jE9EOKgpfhNz~z?@jiDU5(W$PNC;Yk!U4B)USPV>9N4;5~)3)!;qF z{N@B#E;f!*^IIvTMnjUn`I5h9n@oX;Zz=KFQ?JLnX>>KuY!{Ioz>hfHN?n8KVbkaz z;UlB!`EBx9MIZp_QYB4wNb@0Gq@?i&;~|}=q>~)dAV}#-igQT(uf^zL zN@~Y`He}V*HTz|Yed`K(N4FqNqfdf~_CS&B)yt*OSq&()zn`3~;y*r*7wejDdIx&6 z@WM;zLv5cLW=9N7qu-}vlc1MK(Awn5_7Ccw~8&kJ*&pvgxa(vv}D!9Ji*#|k4NY=YrkLu(&Vn;9`oINqjB&zL8dsg z^4>MeYhsdCp330GNiT*IH)@_2X8!7gMU9mqT0N!ok3!JH$;0G4NSGaK@PUlaAq z6eW#!Y5VOB6!CadBLwuvi%jjg63E>L=kLj5vc;!y*o&X65IgdzeG^8)u-?Ff4A>r) z_kzad8|(oTkx5q$1oKY%H;9W#}_&?xW|Z#}g#P%=yY@&MIk!KEk1A23O`G zE_)u~%*#Y(qahIbbhuJ9bb!$OZIt6D%JQ;Sa9}LkP>~h8ST9b_LP>jPup}Q`DJ_$I zgzA#k%Ap?KP+)?1bGD1OEnWAovdrX$OAM}*My}y-6*}PtgaVv;lATDn0cPbkC)gxP zWyhQS)8?7pKUlk;jBu8hz26zdtlTC;q*|jk@l0S`bt2wlZx=03TKNbg1CiJdR;YaY z%R{YcMTxDOd4;HbUUFkCPHZ&UYD=npUNU4Y?%n9*jO^4tFC{fR@*q4?Si@sVO;Jr{ z#bJ4JnwGD9Eo&|QA^TTI6hSt~hCO>d4%z4)etVW4m(dLq*^b4tt6`zxEG8`7E++en zhwK$MN{+)5-WB4G7#6{0-UB#GvvK)Gd!sRj%h%eMnY0&AZ=AW7W1AXIqW$DG`LTUt>2BHpLHD5vfnf6acB?8;tWY)7WaauYUE zvOJsNqG>g)v*E8j^pB)EnT-*z^TbPf(NB9U^aG%YbBQ z@gGpyoW|RwtZDFmEr;hN!n+S%yqodP<&ddFE2G8Pj>R7ku}jDK$nY3Z&U=u1>!pMj zJ09pWrRn-)lg+jsQ-XhErF=>QGRq)P3NJ{FRP9`2BtDga$fu0F%zO<)Gv~jp?Ol#} zHc?NMs4Wq7ZB`AfqeljB&^hO3)g;vw&}>@o;3wBPKg?dh#4XQb5P&^|iMT1jC*?V} z@ySDoBBN!ctXIXrAwskN1~((1jL^&!H-aBRlBJ@V@-BXYyUF5%6;iF8ree*%QRz@t z#v8MfJ8ev?8;}Emg0GT!(jsPg$Hy4RjIofyn8sC7-#kr_1~kTuPG#65?~i6?iIs0V zg-Xw;e@v#KS!PYDyHom)L!rP4_K(rH+A;C|H_XIS>tZ}sHede&158%E&yPu}`-U(w z3z?JA_@d!s4Ac&&o;q>B~vA&*5y_B>m2s zpYfO%vp+BRvE(s1=iIpilB*wLTUdO?-(E_@raRw2Wh6cG;rBA-K&Ye}uO$foZr3xZ zet`t$13j}t0e!laSrAdxw@`SFW0QUjd9aC(a2btv$jXOAl$c~@k1`rpje-(-Im66a z@mJiOan_zW_OEij*}=Ah9Jc8nky~((cM_S~<}1Jy{*XJ~K9(+8@?IxXR=m83r<^#E z6?{tK%WX6i-Z3RT*>CsKN2t||L4jNo-2+JaWsZHv&(pCn{7>Jh8NtunPL#vt z*l&eLU_)C^5m>LP1AGsOMU6{ty`1KN5@g5t**`VcD1fM%Q2;SKBNwUy46|or&Wr*q zN(xv0jq)cEGNgLIjL(zme7J)xZ6_nr6_2<5ftB{8x=Wz7{kCI1^qvohv(eFc+D*si zqz6cVO()VeB1h|6WAx20CFeEMK4{#bdUGtMef4bw2|6EvV*QR0`A)8y$wlFtqOxhTCe8B0MMk zPF(IWF|WQSF87$2%YS7!H#nT1m?#=I8rLz)c|m}4i*b#<%Y-xUg2ef~-#EBS)1kUPIh@&UnG6+8&+NtaVPGC>wojL zD8H(_q{G=Pma8E33y18@8atUmTb}w4kDFSblk7m2XkiHNzJ8%6-)( z)x#E*TvIa4O>&5npMWCv{XUdbmFu$Z! zgkXgusHpUndkcLFOA34z2~-CPie0ChKDh|^ER9&i7!ggCub^TfG84&G`w?Opvi25{ zPN2$Xm6Ri-N(3t2T@ooME%BB6JF~c%chf2?sV=A}FZUHt2XOk4M4*!AbG`nmCEol6 z`6cDn{6JBWuZnCH`l^abD;8OjxUW#^*+=q)CSPRAstEYK6-C}MUs*-f605XiK9VU% zWaC_!c_Wh>=RLc+6BEvk8mZ1NtE5Cl6;);V{y`SW=KH`m zx=RR@cP8a!Q3FaE;RNf3L`)rABS)CHBDs_Vmy>>&rIWkEU-K6k#Tbb+Tr3s4QMGd4 zAZuEnwAz;;YGqBTsM4p)GNxxt%e68CWo1jO3o|BNWK9=2=2lo~L(_&1w|cDzg>GR0 zb0_zG+z)d9jQeTsm$-k<{WkY|+#hnca{q<cci@v#l*yN#dlnZ^jr}aJ%q(5 z&Z1{Be-I~LzBk{@5VWjzzRi?t|0D-3YZ!Vyth%^tn5x$>e_5sbEdN?qD1sY;GCCsk zP7U}g(acPz2|B*0wUMtkw>-knO+heug5%nM!F4A*I{Cw;br;-qr(928keaJzV^VQG zO{a8_Ra)e;vhyZcMO78${voBlBL9$zq9FyeK3|oUeW~kFg?WnfnVyFt7y3*9Yx<>* z!!TJhDk{B`obWRyS^kPjH#YIemSntnBxB9U&NIH@lub~F!;K)@iNWE~T!rJgRB}Z` zmqtVs&csBlohCKWNeUBGH?Iw`9pU@|VlW0~-I*;$qo zM4q_4Bdm!Pet$)oTL^VUrlzD$Nlhs!9A+Zt!Rl}b#mXp~Us7FBUQ*z_EaG`~#Pghp z=gY0xL*{sAo8LKB4!v(>aRq&-Q-%n)Q;JEYCDincD(c(H#ui~=y?OcN3j+BId=k1! zx}9A5nvyEIii`8BoNnC8$*=U66j+(`krfL}S*yt4l#<0(M!?+O;qv57GruFu?@04I z%KVNtzv<@p9P>NI{EjuhX&TrGo#q5jbHb-N5u}Z_rc{(ySkq~2z6xu^&@^i#R+8JB zI-k-E;xWw{1?iGNexdpCrdA`*K@iid(P2LG==h8_K51?Y>B8jrm`Br;1|gQk`3ow_ z^GmIC__@RUJSx*Uq|6=RPQ#7=B9n*kqZ@c?fnQ_g74xo8=y>i^x%+dcau4H9=RS{H zdxqTd>1JqPjU3^XF4F0r$`Fe)XmNWGOrbJ5#?Ac3#K!gPk&xIczIUI&Lxv3|&K-Z=`4>#cm^dl3D|6ptl03=B$&LapIt@OfO76pkg`V1_)YJ{t ze#2pr->Nr5p})OBM!ygm+m_EIFpLkNr$)}uls>-h4>EAoU+uu6#tO4DIbBV~OM?xI6 z)2nBq3S7l->S+>1|1-~WSvxSYPnC%=_Y!)(RasDGU7Rr`%PJ3)mO8!GIIB9qD421^ zh_vwYsNoUM5%kd!^wAOYb9m0onm)3G| zyz26bib}CBI)aU44F}B4nlyIwm|;1YQ*tfSZCg&yF5^x*aqH}AGo)l(Ug#^z50v`J zd&jd?ylC9nh0gC_9?q_o;U{BXe^o{4Bpp*Qs^^M1E$-Y>^hws2rxmaU#IyHWj-x~E z9^_4TEAEHDSHS1MyQJZ>Tmo(Y>%fP>o55!A zKJZ=eY49-k5}3?}onL|jz~6ymz>mQ^FvcGW%>#RZesDIp5nKiC0&fNPf%kyz;A3Ds z8-88`2ZL{c6Tp4o46qd}2LA#s1Cs)w&|2_k;3n{OP!5Oq7<>b41^0s?@JsMPrdSeb zk0G!hIG^dRQJ@dZ0j~q+g13N`-~(VC_&m4)yo@=zZQzfq4R3-~y(7+QB8D%(1);jsfGBlP~Zja52~qTn+vJ z+z8$Y+Tau5VemyTnYp8nzyaX#jNPY!=Yxgd3t$cSIAiS{;Egq;3qB1dqNgu`{lM42 z;ozHKCb%2)gWoVw*bMe!wtX)+4{Qf-1^Y6|ysH*{2M>ULus^f=Yr%owCh&*g4sZeZ zF8Bd>81$^9USygboCo%-r(VHKa4UEb_zJkXf%2VTS&xFl!I!~Ia3?q$46r)n2Ok9Q z2Hyu?0b9Yn;8);h;F)YFNMu2z5gY|R1m=K02j_wZ!NuV5jpQHf4{ic)1e?K+!F}Mb zR-;$^UD_v2v@1|HhHn7h1Gj;(YbhtV1hiOidlyUvXWvRb!540)9>IO!Hc&o%+6DFm z_kpK??chi-o`uXha6GsNoCZ$0gY-ZjxD6};cY)V~`@mITJ9sx3&(ib#U<$Yu91Ok) z=77Hd=Yn5@i^20YkS}l|xC5L5?glGB8(afgrxGuC0{9@93cd=C2j2mwfqTJ1@KbOx z_*ZZ(c*32b&=znc*bHWX?}Ar?Hh4977+eA-GfG$w4hEk9bHKX0L!oux=inRQta~V5 zKlnf|_+Q{Ca3h!lJ^{`HTfoKOL2w;7^2g{GSP1R~*Mb(CnqCLJVD7!>6}Ssr3%1>l zT>%e)hrrjjpnq&y3p|M30M~#U!P~)Y;G&0#7d#Ap2_`;*-ko7t-v|4F+2C;SEpQq* z`%&y0_z`$Fxau*=2Yw0KVC+`p0*(Vyywp253TyzUfqw>fFFX*;M{GL58MZya3<~RN$fp%@^I2(Lr7x9BPyiWeXCx3yw!I#0rftEFcG1~y}9xx9)1g-<$ ze-r%%fBY8t2Y(5se4n@Y-IN#H2QCJ`0@s0u!7bo`w@C+_555cj7_?IH2m67?yhFW$ z4}p2$?Y|)&a3i=H{NY~W0k?zu!9!r;Ao`8>sZVg$N9Zf~$S3skp!ak12)v@5a{RzD z7iP>{F-tGCViqUIoYuEz;@TMU1{GDdnlGo#(nndI8+ zC?j_!_g_kw-w^_r&}MW09vWuZT$m55lzShvQ9_yGNv@5}-|0zo_!}U7*(HvN_AE4_G8dw-_HsW4ZK_ZvdXiViW_VKS<1#$n+W6SXo|Ftv@JehKZ) z2tJWlVl3?r+J7Q1Ij3LKA;4Th+lS*sXp!l(Lp!+(O+JM=r3)(0~mnLWY7UQ=CzjHeL0>)3`T8H0G{36S;1={0XXtF2u zzAm)g(AGl3oSIAG`vlrgp(QCz?Ajq{KZZ6}XeMu$c)YPWo@5%k zIverx;WwLS>za;qCVPfo<=V}Roh_Dho7Nlf-U9DP;WaiNJFYgrIBfDwIS;|R!A%o( zDQ91lcRe(`%q280v<7IrR+%f4-%-$RgjOLG(z!V{%agJuZlcHA6hG0Ey1GY(XLx|dLD^;8Sy+wJPc9ICA78B9)refX9sN)v?F!SD3q|PCH=h8e zmoxSeKNELE-}zB*PfC_4!o)t1sV<`SCBf7}BtvW{dfk z)6PDVbgtFD&DgX{jZG8TBr-p9HGbzx-i&OPm^LUfm${!HewYh$2~FmKmPXJd4w)0W z2HJSxGVxcM_y^!D#;*~-#o|Z&^)w5{Sd7PFou-N1O~q?1e7}Rwj4dt_KH64o++>gU z;z+Y%^z=D+e-E#zPwK2Gmgcou#%=ZSnV!_z9+N%uV(;~&k_eg#1Ij0Q`X%A6s$y); zbJw<0f|`;&II|+v+ti=TSuG&kc@hq$>r_uOp|6HclQNgo+iYknpvi|*jy{pMNE;E> zr%cb#poy-E|ck;~{mv;z1Z z7lMf=cB@1r^&|6UU*jk85kIXTWSMUSLiHHF;qdifzU^P}N&2(lI~Kl3J64LLR|HM+ zR0Ayr+7L~*Huf4*HqnK<@jDK`67e&32%V{y`eh7{I!spEBk9R}-Bfsatu>d>_CdQm zf+p#;L-Tc$p2)5*1IC5;oh~sTgDc31#4`Z;bHe^Zxq0`B|R)oz<}ba5TL*TQ>+#-;CLldu)jTxFE9JqO<=`0ApS&Gf@+ zv!}%sM=M(@aV0W1+{b)sk#rbF{}I2~o#Anp$GM5(CHgQ5-jlBH{EmY(+9=8gkIPc}gNKD0}O&*({Rhi#+NsqHbzGkj5J(`M4# zO*q+SeW}JT&FgJUjM1uVYLs z+iqnqNWA!Qney`^`$U-HG?&mS`MC=kT~P^%8Xoj{7 z8bwsSlit=ze;He(*EQUzTMDn=)(62w58A@_3#k1QfNkQgp+*A+`Gu8YyJm7 zzZ`nk{EvrT2)%3mXF$IOdSw2Kq4}Ui=5-mgGH8)yl6n3jXl}lw&ScI%ADU~IYaLT- zY;7}s^SkjA`>+qc0{q^N)(0E=pkt?baZg0)g3VY%))nMCLANhzl5xrExE!(DlcYV0 z-M-M1ijnGMw`CpT6xKWbC*h>bvTku9;U@6x=;>0Ur&7n;@GHm9)jy%_f_7B|P3luV zBbWvaFmYU>diN!Si@MMy&AtT6hCW>RYh$lAX^LMeewX9tj(uul7a2ckH?nqe9e!yV zE^M#oceESfD}}EPzQLXO=<4Qm@QHq`gRcR;amrU4Th-B*jDofe|DWK$Li~+wG~+(g zhh|G%P_JbDpXx%^Y+C<^;ii$q01Vi0)~3#q1~fz7_n1J89LpI!%5}!zTVvw>*rPiO z%4_NyZ~pQvqLBKMb)gWS{J6R=`nnq0A!zUXXZpG=n!b)FuD!%{{Y|0JfAj3vGV^|p z&5ym3AuiU25#7W-erLFAp3z&37LQWjDNGu*65h-g7&|$=L;ptMcK|LBbu*1*S;V0{TgYYwR1SXvGjv(dx1i#_v zOSl>dXX-H47=0gaDgDUnEyw}~v-T~n?b;#J!tY^q1os-Wo zcpZlC0r-MyZg! zOP<7beMY#SvSwYRHd@;@b}sJ9SXYqn$t7*EAI9@N)~sDyE9Dvv?GtFl!en&I)P9u`#@p3%$;Uc)*TefG;hk#g{UT`w68Bd8_TtCuo4JJl6==VP zc8^dTJ-JQvMAF@l->irDYA_y$(UYuW5;AX&TO(ieu1=`!8Mil{C4ZCezFyV~AM5;n z?S!pAFx=9eJO0Ov1#Q<0h|(Z|ae4GT$hf7v5d)Ua^gDKstD{4R6+(_&hD>CgIJ2 z_e*&Hw1atgwF5Vc9r&m5ylE4YJ(Y3k(Z<=5?u7o#n?4f?RVU&w@+FViBHyMSQ%n}G zNEG=>x~1?=*vTBb%rS-AVwZIzov&KkKdxU~x6RSi>ki@?#=ezQDT9-)w*RF+Drihow@f6(C{qBu(N8N~$U>#ew3 z>1U|>EX8kKKJ`CV@l(aI>OWrbE2U?vJ6Car;#G?C70VQtDb^_7thiBelj4(#uPXjr z@jb;)726cQR_vwuI!5ty#lech6)#ZiTAS0N(C0^?S4E-MMxoypg?@h&`t~UFH=@u# zjza%S6uPDQ)>RI@qR@|zLO&%6{rgeqJrIR{Llk;b6#AV}==VmU z-yenkWE6T!6#6?+=pRR+e;I}Tk0|sWhq~6d<4%b}?-zxBW)ymA6#CF8^ifgh)e-!$4QRrQ3%eUE~{OSboDYiG-P1Q%P*%~h4+U>|j>(~>0t zb4_#dqtRL$Wub{n!E)E1wAj|x$&SBE&l%^q-Fy~n!FxGmS3K4~^z)VCv5rSb-I`si zUESl(as0nlx7j13as6+A9aCIQ>YSVOd5$|T6BNs0p%(-pH7XDJpbE>x^nT(7uUal2xR;vU5ViU$=F=4<+j0~OO1vlVA47AY=N ztXEvGxLI+#VvFJ)#RG~56%z_HeZ_%_>5AEkvlNRI7b?~(u2lN24ZdTl`*rK>c@qpq%#RQ+GuQ*UKT`^m6mLi9>nQNh9 zz2bVs&5GL-4=5f~Oeog$6$dJ&D`qRsQY=zjs93MK zUU9SHcEuLOJ&Feu4=N^-4=5f~Oeod#6$dJ&D`qRsQY=zjs93MKUU9SHcEuLOJ&Feu4=N^R4l-DXvFO~=; zO~LQ-TBGSn+1&KvzrzG??IykNQm*T}Nw3#;Sibu-J&~a+PbuGbCDZ$(r1xEw@xgA= z>p}Svz9X4F+D&@BNvqFyl-@I%o|GxF{zRVNMIF8nCB5%TrazC8-ghO_cQrlHk4SmC z=^bgA?v0Y3E6*dH-p5hWbIW(6+u@(ONzYBICo)dtK9bir-J~b>%$4VnE?@lDPVS{F z-RaAbZik6c(u*wLk<^*vyGgHW{T<2l&gdpRsXsSOx15gD|KESl`ZwtvX&oJ=>51NV zr!Q{#j+9JC{+smN@*S!4&X1CwTdpIW-n1y`b=8+6)$S)xnl#RvI{D(6gS=yga{RM* z#PGB+X=x*vHXB-9%zq~M^XFSb`R@nc&;{jzq4NVJrG-N{T-&&c^Q()kp@mDztCy5H z=&y2o_|t`I{->go<0|~Be5LskK%Xj0{nk+VlL%|5-?y0GA{_Wt6z2Q$t)af+tBR`f z%Y0WA7ZTQWIU%l+L$sGTA>3yPB}ov0$qs+_P(T(ch?t+DCN(GK`PJ2y{6$8Y{K>p|uy-SFV{8o$mY8biQ5hD&I7yX&ig96iNk#s9K$Vhux@yF-ql63&l?^$|1O`4S@_AE zuIqN^bzR(#zg&`kx!wE=O|BW-LU+Tv^T;l`^T=-cZg{u+@~wh|pU5qjJ1^~`JC7~l zPvq_zz8IRwwPPGU4~Jay%&+Cn&pR$BE%#ZlfIBk0cb-r!%SCgBp+hpi@-2m2F3NXW zk>T6*J=?{C8w`8Lj~mVg=>` z#2p!a_hE;@MLAPC^6HxYT0A4ePv{{o%X0CP{>Ity(VF5R_dDg@3%^NAvU{zrs1Z3*8Ox fuBW-)POMHCp^4i~+vO3wOVj^jB#~z~yi5K+7x2dX literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj b/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj new file mode 100644 index 00000000..5c942346 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + true + win-x64;osx-x64;linux-x64 + + + + + + + + + PreserveNewest + libsoundio.dll + + + PreserveNewest + libsoundio.dylib + + + PreserveNewest + libsoundio.so + + + + diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs new file mode 100644 index 00000000..e1a62de7 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Audio.Backends.SoundIo +{ + class SoundIoAudioBuffer + { + public readonly ulong DriverIdentifier; + public readonly ulong SampleCount; + public ulong SamplePlayed; + + public SoundIoAudioBuffer(ulong driverIdentifier, ulong sampleCount) + { + DriverIdentifier = driverIdentifier; + SampleCount = sampleCount; + SamplePlayed = 0; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs new file mode 100644 index 00000000..e3e5d291 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs @@ -0,0 +1,266 @@ +using Ryujinx.Audio.Backends.SoundIo.Native; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Threading; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace Ryujinx.Audio.Backends.SoundIo +{ + public class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver + { + private readonly SoundIoContext _audioContext; + private readonly SoundIoDeviceContext _audioDevice; + private readonly ManualResetEvent _updateRequiredEvent; + private readonly ManualResetEvent _pauseEvent; + private readonly ConcurrentDictionary _sessions; + private int _disposeState; + + private float _volume = 1f; + + public float Volume + { + get + { + return _volume; + } + set + { + _volume = value; + + foreach (SoundIoHardwareDeviceSession session in _sessions.Keys) + { + session.UpdateMasterVolume(value); + } + } + } + + public SoundIoHardwareDeviceDriver() + { + _audioContext = SoundIoContext.Create(); + _updateRequiredEvent = new ManualResetEvent(false); + _pauseEvent = new ManualResetEvent(true); + _sessions = new ConcurrentDictionary(); + + _audioContext.Connect(); + _audioContext.FlushEvents(); + + _audioDevice = FindValidAudioDevice(_audioContext, true); + } + + public static bool IsSupported => IsSupportedInternal(); + + private static bool IsSupportedInternal() + { + SoundIoContext context = null; + SoundIoDeviceContext device = null; + SoundIoOutStreamContext stream = null; + + bool backendDisconnected = false; + + try + { + context = SoundIoContext.Create(); + context.OnBackendDisconnect = err => + { + backendDisconnected = true; + }; + + context.Connect(); + context.FlushEvents(); + + if (backendDisconnected) + { + return false; + } + + if (context.OutputDeviceCount == 0) + { + return false; + } + + device = FindValidAudioDevice(context); + + if (device == null || backendDisconnected) + { + return false; + } + + stream = device.CreateOutStream(); + + if (stream == null || backendDisconnected) + { + return false; + } + + return true; + } + catch + { + return false; + } + finally + { + stream?.Dispose(); + context?.Dispose(); + } + } + + private static SoundIoDeviceContext FindValidAudioDevice(SoundIoContext audioContext, bool fallback = false) + { + SoundIoDeviceContext defaultAudioDevice = audioContext.GetOutputDevice(audioContext.DefaultOutputDeviceIndex); + + if (!defaultAudioDevice.IsRaw) + { + return defaultAudioDevice; + } + + for (int i = 0; i < audioContext.OutputDeviceCount; i++) + { + SoundIoDeviceContext audioDevice = audioContext.GetOutputDevice(i); + + if (audioDevice.Id == defaultAudioDevice.Id && !audioDevice.IsRaw) + { + return audioDevice; + } + } + + return fallback ? defaultAudioDevice : null; + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _updateRequiredEvent; + } + + public ManualResetEvent GetPauseEvent() + { + return _pauseEvent; + } + + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) + { + if (channelCount == 0) + { + channelCount = 2; + } + + if (sampleRate == 0) + { + sampleRate = Constants.TargetSampleRate; + } + + if (direction != Direction.Output) + { + throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!"); + } + + SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount); + + _sessions.TryAdd(session, 0); + + return session; + } + + internal bool Unregister(SoundIoHardwareDeviceSession session) + { + return _sessions.TryRemove(session, out _); + } + + public static SoundIoFormat GetSoundIoFormat(SampleFormat format) + { + return format switch + { + SampleFormat.PcmInt8 => SoundIoFormat.S8, + SampleFormat.PcmInt16 => SoundIoFormat.S16LE, + SampleFormat.PcmInt24 => SoundIoFormat.S24LE, + SampleFormat.PcmInt32 => SoundIoFormat.S32LE, + SampleFormat.PcmFloat => SoundIoFormat.Float32LE, + _ => throw new ArgumentException($"Unsupported sample format {format}"), + }; + } + + internal SoundIoOutStreamContext OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) + { + SoundIoFormat driverSampleFormat = GetSoundIoFormat(requestedSampleFormat); + + if (!_audioDevice.SupportsSampleRate((int)requestedSampleRate)) + { + throw new ArgumentException($"This sound device does not support a sample rate of {requestedSampleRate}Hz"); + } + + if (!_audioDevice.SupportsFormat(driverSampleFormat)) + { + throw new ArgumentException($"This sound device does not support {requestedSampleFormat}"); + } + + if (!_audioDevice.SupportsChannelCount((int)requestedChannelCount)) + { + throw new ArgumentException($"This sound device does not support channel count {requestedChannelCount}"); + } + + SoundIoOutStreamContext result = _audioDevice.CreateOutStream(); + + result.Name = "Ryujinx"; + result.Layout = SoundIoChannelLayout.GetDefaultValue((int)requestedChannelCount); + result.Format = driverSampleFormat; + result.SampleRate = (int)requestedSampleRate; + + return result; + } + + internal void FlushContextEvents() + { + _audioContext.FlushEvents(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (SoundIoHardwareDeviceSession session in _sessions.Keys) + { + session.Dispose(); + } + + _audioContext.Disconnect(); + _audioContext.Dispose(); + _pauseEvent.Dispose(); + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return _audioDevice.SupportsSampleRate((int)sampleRate); + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return _audioDevice.SupportsFormat(GetSoundIoFormat(sampleFormat)); + } + + public bool SupportsChannelCount(uint channelCount) + { + return _audioDevice.SupportsChannelCount((int)channelCount); + } + + public bool SupportsDirection(Direction direction) + { + // TODO: add direction input when supported. + return direction == Direction.Output; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs new file mode 100644 index 00000000..e9cc6a8e --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs @@ -0,0 +1,451 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Backends.SoundIo.Native; +using Ryujinx.Audio.Common; +using Ryujinx.Common.Memory; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Threading; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo +{ + class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private readonly SoundIoHardwareDeviceDriver _driver; + private readonly ConcurrentQueue _queuedBuffers; + private SoundIoOutStreamContext _outputStream; + private readonly DynamicRingBuffer _ringBuffer; + private ulong _playedSampleCount; + private readonly ManualResetEvent _updateRequiredEvent; + private float _volume; + private int _disposeState; + + public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _driver = driver; + _updateRequiredEvent = _driver.GetUpdateRequiredEvent(); + _queuedBuffers = new ConcurrentQueue(); + _ringBuffer = new DynamicRingBuffer(); + _volume = 1f; + + SetupOutputStream(driver.Volume); + } + + private void SetupOutputStream(float requestedVolume) + { + _outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount); + _outputStream.WriteCallback += Update; + _outputStream.Volume = requestedVolume; + // TODO: Setup other callbacks (errors, etc.) + + _outputStream.Open(); + } + + public override ulong GetPlayedSampleCount() + { + return Interlocked.Read(ref _playedSampleCount); + } + + public override float GetVolume() + { + return _volume; + } + + public override void PrepareToClose() { } + + public override void QueueBuffer(AudioBuffer buffer) + { + SoundIoAudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer)); + + _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); + + _queuedBuffers.Enqueue(driverBuffer); + } + + public override void SetVolume(float volume) + { + _volume = volume; + + _outputStream.SetVolume(_driver.Volume * volume); + } + + public void UpdateMasterVolume(float newVolume) + { + _outputStream.SetVolume(newVolume * _volume); + } + + public override void Start() + { + _outputStream.Start(); + _outputStream.Pause(false); + + _driver.FlushContextEvents(); + } + + public override void Stop() + { + _outputStream.Pause(true); + + _driver.FlushContextEvents(); + } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + if (!_queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer)) + { + return true; + } + + return driverBuffer.DriverIdentifier != buffer.DataPointer; + } + + private unsafe void Update(int minFrameCount, int maxFrameCount) + { + int bytesPerFrame = _outputStream.BytesPerFrame; + uint bytesPerSample = (uint)_outputStream.BytesPerSample; + + int bufferedFrames = _ringBuffer.Length / bytesPerFrame; + + int frameCount = Math.Min(bufferedFrames, maxFrameCount); + + if (frameCount == 0) + { + return; + } + + Span areas = _outputStream.BeginWrite(ref frameCount); + + int channelCount = areas.Length; + + using SpanOwner samplesOwner = SpanOwner.Rent(frameCount * bytesPerFrame); + + Span samples = samplesOwner.Span; + + _ringBuffer.Read(samples, 0, samples.Length); + + // This is a huge ugly block of code, but we save + // a significant amount of time over the generic + // loop that handles other channel counts. + // TODO: Is this still right in 2022? + + // Mono + if (channelCount == 1) + { + ref SoundIoChannelArea area = ref areas[0]; + + fixed (byte* srcptr = samples) + { + if (bytesPerSample == 1) + { + for (int frame = 0; frame < frameCount; frame++) + { + ((byte*)area.Pointer)[0] = srcptr[frame * bytesPerFrame]; + + area.Pointer += area.Step; + } + } + else if (bytesPerSample == 2) + { + for (int frame = 0; frame < frameCount; frame++) + { + ((short*)area.Pointer)[0] = ((short*)srcptr)[frame * bytesPerFrame >> 1]; + + area.Pointer += area.Step; + } + } + else if (bytesPerSample == 4) + { + for (int frame = 0; frame < frameCount; frame++) + { + ((int*)area.Pointer)[0] = ((int*)srcptr)[frame * bytesPerFrame >> 2]; + + area.Pointer += area.Step; + } + } + else + { + for (int frame = 0; frame < frameCount; frame++) + { + Unsafe.CopyBlockUnaligned((byte*)area.Pointer, srcptr + (frame * bytesPerFrame), bytesPerSample); + + area.Pointer += area.Step; + } + } + } + } + // Stereo + else if (channelCount == 2) + { + ref SoundIoChannelArea area1 = ref areas[0]; + ref SoundIoChannelArea area2 = ref areas[1]; + + fixed (byte* srcptr = samples) + { + if (bytesPerSample == 1) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0]; + + // Channel 2 + ((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + } + } + else if (bytesPerSample == 2) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0]; + + // Channel 2 + ((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + } + } + else if (bytesPerSample == 4) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0]; + + // Channel 2 + ((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + } + } + else + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample); + + // Channel 2 + Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample); + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + } + } + } + } + // Surround + else if (channelCount == 6) + { + ref SoundIoChannelArea area1 = ref areas[0]; + ref SoundIoChannelArea area2 = ref areas[1]; + ref SoundIoChannelArea area3 = ref areas[2]; + ref SoundIoChannelArea area4 = ref areas[3]; + ref SoundIoChannelArea area5 = ref areas[4]; + ref SoundIoChannelArea area6 = ref areas[5]; + + fixed (byte* srcptr = samples) + { + if (bytesPerSample == 1) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0]; + + // Channel 2 + ((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1]; + + // Channel 3 + ((byte*)area3.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 2]; + + // Channel 4 + ((byte*)area4.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 3]; + + // Channel 5 + ((byte*)area5.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 4]; + + // Channel 6 + ((byte*)area6.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 5]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + area3.Pointer += area3.Step; + area4.Pointer += area4.Step; + area5.Pointer += area5.Step; + area6.Pointer += area6.Step; + } + } + else if (bytesPerSample == 2) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0]; + + // Channel 2 + ((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1]; + + // Channel 3 + ((short*)area3.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 2]; + + // Channel 4 + ((short*)area4.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 3]; + + // Channel 5 + ((short*)area5.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 4]; + + // Channel 6 + ((short*)area6.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 5]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + area3.Pointer += area3.Step; + area4.Pointer += area4.Step; + area5.Pointer += area5.Step; + area6.Pointer += area6.Step; + } + } + else if (bytesPerSample == 4) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0]; + + // Channel 2 + ((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1]; + + // Channel 3 + ((int*)area3.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 2]; + + // Channel 4 + ((int*)area4.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 3]; + + // Channel 5 + ((int*)area5.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 4]; + + // Channel 6 + ((int*)area6.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 5]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + area3.Pointer += area3.Step; + area4.Pointer += area4.Step; + area5.Pointer += area5.Step; + area6.Pointer += area6.Step; + } + } + else + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample); + + // Channel 2 + Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample); + + // Channel 3 + Unsafe.CopyBlockUnaligned((byte*)area3.Pointer, srcptr + (frame * bytesPerFrame) + (2 * bytesPerSample), bytesPerSample); + + // Channel 4 + Unsafe.CopyBlockUnaligned((byte*)area4.Pointer, srcptr + (frame * bytesPerFrame) + (3 * bytesPerSample), bytesPerSample); + + // Channel 5 + Unsafe.CopyBlockUnaligned((byte*)area5.Pointer, srcptr + (frame * bytesPerFrame) + (4 * bytesPerSample), bytesPerSample); + + // Channel 6 + Unsafe.CopyBlockUnaligned((byte*)area6.Pointer, srcptr + (frame * bytesPerFrame) + (5 * bytesPerSample), bytesPerSample); + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + area3.Pointer += area3.Step; + area4.Pointer += area4.Step; + area5.Pointer += area5.Step; + area6.Pointer += area6.Step; + } + } + } + } + // Every other channel count + else + { + fixed (byte* srcptr = samples) + { + for (int frame = 0; frame < frameCount; frame++) + { + for (int channel = 0; channel < areas.Length; channel++) + { + // Copy channel by channel, frame by frame. This is slow! + Unsafe.CopyBlockUnaligned((byte*)areas[channel].Pointer, srcptr + (frame * bytesPerFrame) + (channel * bytesPerSample), bytesPerSample); + + areas[channel].Pointer += areas[channel].Step; + } + } + } + } + + _outputStream.EndWrite(); + + ulong sampleCount = (ulong)(samples.Length / bytesPerSample / channelCount); + + ulong availaibleSampleCount = sampleCount; + + bool needUpdate = false; + + while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer)) + { + ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed); + ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount); + + Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount); + availaibleSampleCount -= playedAudioBufferSampleCount; + + if (Interlocked.Read(ref driverBuffer.SamplePlayed) == driverBuffer.SampleCount) + { + _queuedBuffers.TryDequeue(out _); + + needUpdate = true; + } + + Interlocked.Add(ref _playedSampleCount, playedAudioBufferSampleCount); + } + + // Notify the output if needed. + if (needUpdate) + { + _updateRequiredEvent.Set(); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && _driver.Unregister(this)) + { + PrepareToClose(); + Stop(); + + _outputStream.Dispose(); + } + } + + public override void Dispose() + { + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + } +} diff --git a/src/Ryujinx.Audio/AudioManager.cs b/src/Ryujinx.Audio/AudioManager.cs new file mode 100644 index 00000000..370d3d09 --- /dev/null +++ b/src/Ryujinx.Audio/AudioManager.cs @@ -0,0 +1,133 @@ +using System; +using System.Threading; + +namespace Ryujinx.Audio +{ + /// + /// Manage audio input and output system. + /// + public class AudioManager : IDisposable + { + /// + /// Lock used to control the waiters registration. + /// + private readonly object _lock = new(); + + /// + /// Events signaled when the driver played audio buffers. + /// + private readonly ManualResetEvent[] _updateRequiredEvents; + + /// + /// Action to execute when the driver played audio buffers. + /// + private readonly Action[] _actions; + + /// + /// The worker thread in charge of handling sessions update. + /// + private readonly Thread _workerThread; + + private bool _isRunning; + + /// + /// Create a new . + /// + public AudioManager() + { + _updateRequiredEvents = new ManualResetEvent[2]; + _actions = new Action[2]; + _isRunning = false; + + // Termination event. + _updateRequiredEvents[1] = new ManualResetEvent(false); + + _workerThread = new Thread(Update) + { + Name = "AudioManager.Worker", + }; + } + + /// + /// Start the . + /// + public void Start() + { + if (_workerThread.IsAlive) + { + throw new InvalidOperationException(); + } + + _isRunning = true; + _workerThread.Start(); + } + + /// + /// Initialize update handlers. + /// + /// The driver event that will get signaled by the device driver when an audio buffer finished playing/being captured + /// The callback to call when an audio buffer finished playing + /// The callback to call when an audio buffer was captured + public void Initialize(ManualResetEvent updatedRequiredEvent, Action outputCallback, Action inputCallback) + { + lock (_lock) + { + _updateRequiredEvents[0] = updatedRequiredEvent; + _actions[0] = outputCallback; + _actions[1] = inputCallback; + } + } + + /// + /// Entrypoint of the in charge of updating the . + /// + private void Update() + { + while (_isRunning) + { + int index = WaitHandle.WaitAny(_updateRequiredEvents); + + // Last index is here to indicate thread termination. + if (index + 1 == _updateRequiredEvents.Length) + { + break; + } + + lock (_lock) + { + foreach (Action action in _actions) + { + action?.Invoke(); + } + + _updateRequiredEvents[0].Reset(); + } + } + } + + /// + /// Stop updating the without stopping the worker thread. + /// + public void StopUpdates() + { + _isRunning = false; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _updateRequiredEvents[1].Set(); + _workerThread.Join(); + + _updateRequiredEvents[1].Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Backends/Common/BackendHelper.cs b/src/Ryujinx.Audio/Backends/Common/BackendHelper.cs new file mode 100644 index 00000000..124d8364 --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Common/BackendHelper.cs @@ -0,0 +1,26 @@ +using Ryujinx.Audio.Common; +using System; + +namespace Ryujinx.Audio.Backends.Common +{ + public static class BackendHelper + { + public static int GetSampleSize(SampleFormat format) + { + return format switch + { + SampleFormat.PcmInt8 => sizeof(byte), + SampleFormat.PcmInt16 => sizeof(ushort), + SampleFormat.PcmInt24 => 3, + SampleFormat.PcmInt32 => sizeof(int), + SampleFormat.PcmFloat => sizeof(float), + _ => throw new ArgumentException($"{format}"), + }; + } + + public static int GetSampleCount(SampleFormat format, int channelCount, int bufferSize) + { + return bufferSize / GetSampleSize(format) / channelCount; + } + } +} diff --git a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs new file mode 100644 index 00000000..7aefe886 --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs @@ -0,0 +1,173 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using System; +using System.Buffers; + +namespace Ryujinx.Audio.Backends.Common +{ + /// + /// A ring buffer that grow if data written to it is too big to fit. + /// + public class DynamicRingBuffer + { + private const int RingBufferAlignment = 2048; + + private readonly object _lock = new(); + + private MemoryOwner _bufferOwner; + private Memory _buffer; + private int _size; + private int _headOffset; + private int _tailOffset; + + public int Length => _size; + + public DynamicRingBuffer(int initialCapacity = RingBufferAlignment) + { + _bufferOwner = MemoryOwner.RentCleared(initialCapacity); + _buffer = _bufferOwner.Memory; + } + + public void Clear() + { + _size = 0; + _headOffset = 0; + _tailOffset = 0; + } + + public void Clear(int size) + { + if (size == 0) + { + return; + } + + lock (_lock) + { + if (size > _size) + { + size = _size; + } + + _headOffset = (_headOffset + size) % _buffer.Length; + _size -= size; + + if (_size == 0) + { + _headOffset = 0; + _tailOffset = 0; + } + } + } + + private void SetCapacityLocked(int capacity) + { + MemoryOwner newBufferOwner = MemoryOwner.RentCleared(capacity); + Memory newBuffer = newBufferOwner.Memory; + + if (_size > 0) + { + if (_headOffset < _tailOffset) + { + _buffer.Slice(_headOffset, _size).CopyTo(newBuffer); + } + else + { + _buffer[_headOffset..].CopyTo(newBuffer); + _buffer[.._tailOffset].CopyTo(newBuffer[(_buffer.Length - _headOffset)..]); + } + } + + _bufferOwner.Dispose(); + + _bufferOwner = newBufferOwner; + _buffer = newBuffer; + _headOffset = 0; + _tailOffset = _size; + } + + public void Write(ReadOnlySpan buffer, int index, int count) + { + if (count == 0) + { + return; + } + + lock (_lock) + { + if ((_size + count) > _buffer.Length) + { + SetCapacityLocked(BitUtils.AlignUp(_size + count, RingBufferAlignment)); + } + + if (_headOffset < _tailOffset) + { + int tailLength = _buffer.Length - _tailOffset; + + if (tailLength >= count) + { + buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]); + } + else + { + buffer.Slice(index, tailLength).CopyTo(_buffer.Span[_tailOffset..]); + buffer.Slice(index + tailLength, count - tailLength).CopyTo(_buffer.Span); + } + } + else + { + buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]); + } + + _size += count; + _tailOffset = (_tailOffset + count) % _buffer.Length; + } + } + + public int Read(Span buffer, int index, int count) + { + if (count == 0) + { + return 0; + } + + lock (_lock) + { + if (count > _size) + { + count = _size; + } + + if (_headOffset < _tailOffset) + { + _buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]); + } + else + { + int tailLength = _buffer.Length - _headOffset; + + if (tailLength >= count) + { + _buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]); + } + else + { + _buffer.Span.Slice(_headOffset, tailLength).CopyTo(buffer[index..]); + _buffer.Span[..(count - tailLength)].CopyTo(buffer[(index + tailLength)..]); + } + } + + _size -= count; + _headOffset = (_headOffset + count) % _buffer.Length; + + if (_size == 0) + { + _headOffset = 0; + _tailOffset = 0; + } + + return count; + } + } + } +} diff --git a/src/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs b/src/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs new file mode 100644 index 00000000..5599c082 --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs @@ -0,0 +1,76 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Backends.Common +{ + public abstract class HardwareDeviceSessionOutputBase : IHardwareDeviceSession + { + public IVirtualMemoryManager MemoryManager { get; } + public SampleFormat RequestedSampleFormat { get; } + public uint RequestedSampleRate { get; } + public uint RequestedChannelCount { get; } + + public HardwareDeviceSessionOutputBase(IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) + { + MemoryManager = memoryManager; + RequestedSampleFormat = requestedSampleFormat; + RequestedSampleRate = requestedSampleRate; + RequestedChannelCount = requestedChannelCount; + } + + private byte[] GetBufferSamples(AudioBuffer buffer) + { + if (buffer.DataPointer == 0) + { + return null; + } + + byte[] data = new byte[buffer.DataSize]; + + MemoryManager.Read(buffer.DataPointer, data); + + return data; + } + + protected ulong GetSampleCount(AudioBuffer buffer) + { + return GetSampleCount((int)buffer.DataSize); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected ulong GetSampleCount(int dataSize) + { + return (ulong)BackendHelper.GetSampleCount(RequestedSampleFormat, (int)RequestedChannelCount, dataSize); + } + + public abstract void Dispose(); + public abstract void PrepareToClose(); + public abstract void QueueBuffer(AudioBuffer buffer); + public abstract void SetVolume(float volume); + public abstract float GetVolume(); + public abstract void Start(); + public abstract void Stop(); + public abstract ulong GetPlayedSampleCount(); + public abstract bool WasBufferFullyConsumed(AudioBuffer buffer); + public virtual bool RegisterBuffer(AudioBuffer buffer) + { + return RegisterBuffer(buffer, GetBufferSamples(buffer)); + } + + public virtual bool RegisterBuffer(AudioBuffer buffer, byte[] samples) + { + if (samples == null) + { + return false; + } + + buffer.Data ??= samples; + + return true; + } + + public virtual void UnregisterBuffer(AudioBuffer buffer) { } + } +} diff --git a/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs new file mode 100644 index 00000000..a2c2cdcd --- /dev/null +++ b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs @@ -0,0 +1,190 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Backends.Dummy; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Threading; +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace Ryujinx.Audio.Backends.CompatLayer +{ + public class CompatLayerHardwareDeviceDriver : IHardwareDeviceDriver + { + private readonly IHardwareDeviceDriver _realDriver; + + public static bool IsSupported => true; + + public float Volume + { + get => _realDriver.Volume; + set => _realDriver.Volume = value; + } + + public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice) + { + _realDriver = realDevice; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _realDriver.Dispose(); + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _realDriver.GetUpdateRequiredEvent(); + } + + public ManualResetEvent GetPauseEvent() + { + return _realDriver.GetPauseEvent(); + } + + private uint SelectHardwareChannelCount(uint targetChannelCount) + { + if (_realDriver.SupportsChannelCount(targetChannelCount)) + { + return targetChannelCount; + } + + return targetChannelCount switch + { + 6 => SelectHardwareChannelCount(2), + 2 => SelectHardwareChannelCount(1), + 1 => throw new ArgumentException("No valid channel configuration found!"), + _ => throw new ArgumentException($"Invalid targetChannelCount {targetChannelCount}"), + }; + } + + private SampleFormat SelectHardwareSampleFormat(SampleFormat targetSampleFormat) + { + if (_realDriver.SupportsSampleFormat(targetSampleFormat)) + { + return targetSampleFormat; + } + + // Attempt conversion from PCM16. + if (targetSampleFormat == SampleFormat.PcmInt16) + { + // Prefer PCM32 if we need to convert. + if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt32)) + { + return SampleFormat.PcmInt32; + } + + // If not supported, PCM float provides the best quality with a cost lower than PCM24. + if (_realDriver.SupportsSampleFormat(SampleFormat.PcmFloat)) + { + return SampleFormat.PcmFloat; + } + + if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt24)) + { + return SampleFormat.PcmInt24; + } + + // If nothing is truly supported, attempt PCM8 at the cost of losing quality. + if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt8)) + { + return SampleFormat.PcmInt8; + } + } + + throw new ArgumentException("No valid sample format configuration found!"); + } + + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) + { + if (channelCount == 0) + { + channelCount = 2; + } + + if (sampleRate == 0) + { + sampleRate = Constants.TargetSampleRate; + } + + if (!_realDriver.SupportsDirection(direction)) + { + if (direction == Direction.Input) + { + Logger.Warning?.Print(LogClass.Audio, "The selected audio backend doesn't support audio input, fallback to dummy..."); + + return new DummyHardwareDeviceSessionInput(this, memoryManager); + } + + throw new NotImplementedException(); + } + + SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat); + uint hardwareChannelCount = SelectHardwareChannelCount(channelCount); + + IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount); + + if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat) + { + return realSession; + } + + if (hardwareSampleFormat != sampleFormat) + { + Logger.Warning?.Print(LogClass.Audio, $"{sampleFormat} isn't supported by the audio device, conversion to {hardwareSampleFormat} will happen."); + + if (hardwareSampleFormat < sampleFormat) + { + Logger.Warning?.Print(LogClass.Audio, $"{hardwareSampleFormat} has lower quality than {sampleFormat}, expect some loss in audio fidelity."); + } + } + + if (direction == Direction.Input) + { + Logger.Warning?.Print(LogClass.Audio, "The selected audio backend doesn't support the requested audio input configuration, fallback to dummy..."); + + // TODO: We currently don't support audio input upsampling/downsampling, implement this. + realSession.Dispose(); + + return new DummyHardwareDeviceSessionInput(this, memoryManager); + } + + // It must be a HardwareDeviceSessionOutputBase. + if (realSession is not HardwareDeviceSessionOutputBase realSessionOutputBase) + { + throw new InvalidOperationException($"Real driver session class type isn't based on {typeof(HardwareDeviceSessionOutputBase).Name}."); + } + + // If we need to do post processing before sending to the hardware device, wrap around it. + return new CompatLayerHardwareDeviceSession(realSessionOutputBase, sampleFormat, channelCount); + } + + public bool SupportsChannelCount(uint channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 6; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + // TODO: More formats. + return sampleFormat == SampleFormat.PcmInt16; + } + + public bool SupportsSampleRate(uint sampleRate) + { + // TODO: More sample rates. + return sampleRate == Constants.TargetSampleRate; + } + + public IHardwareDeviceDriver GetRealDeviceDriver() + { + return _realDriver; + } + + public bool SupportsDirection(Direction direction) + { + return direction == Direction.Input || direction == Direction.Output; + } + } +} diff --git a/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs new file mode 100644 index 00000000..a9acabec --- /dev/null +++ b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs @@ -0,0 +1,162 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Dsp; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Backends.CompatLayer +{ + class CompatLayerHardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private readonly HardwareDeviceSessionOutputBase _realSession; + private readonly SampleFormat _userSampleFormat; + private readonly uint _userChannelCount; + + public CompatLayerHardwareDeviceSession(HardwareDeviceSessionOutputBase realSession, SampleFormat userSampleFormat, uint userChannelCount) : base(realSession.MemoryManager, realSession.RequestedSampleFormat, realSession.RequestedSampleRate, userChannelCount) + { + _realSession = realSession; + _userSampleFormat = userSampleFormat; + _userChannelCount = userChannelCount; + } + + public override void Dispose() + { + _realSession.Dispose(); + } + + public override ulong GetPlayedSampleCount() + { + return _realSession.GetPlayedSampleCount(); + } + + public override float GetVolume() + { + return _realSession.GetVolume(); + } + + public override void PrepareToClose() + { + _realSession.PrepareToClose(); + } + + public override void QueueBuffer(AudioBuffer buffer) + { + SampleFormat realSampleFormat = _realSession.RequestedSampleFormat; + + if (_userSampleFormat != realSampleFormat) + { + if (_userSampleFormat != SampleFormat.PcmInt16) + { + throw new NotImplementedException("Converting formats other than PCM16 is not supported."); + } + + int userSampleCount = buffer.Data.Length / BackendHelper.GetSampleSize(_userSampleFormat); + + ReadOnlySpan samples = MemoryMarshal.Cast(buffer.Data); + byte[] convertedSamples = new byte[BackendHelper.GetSampleSize(realSampleFormat) * userSampleCount]; + + switch (realSampleFormat) + { + case SampleFormat.PcmInt8: + PcmHelper.ConvertSampleToPcm8(MemoryMarshal.Cast(convertedSamples), samples); + break; + case SampleFormat.PcmInt24: + PcmHelper.ConvertSampleToPcm24(convertedSamples, samples); + break; + case SampleFormat.PcmInt32: + PcmHelper.ConvertSampleToPcm32(MemoryMarshal.Cast(convertedSamples), samples); + break; + case SampleFormat.PcmFloat: + PcmHelper.ConvertSampleToPcmFloat(MemoryMarshal.Cast(convertedSamples), samples); + break; + default: + throw new NotImplementedException($"Sample format conversion from {_userSampleFormat} to {realSampleFormat} not implemented."); + } + + buffer.Data = convertedSamples; + } + + _realSession.QueueBuffer(buffer); + } + + public override bool RegisterBuffer(AudioBuffer buffer, byte[] samples) + { + if (samples == null) + { + return false; + } + + if (_userChannelCount != _realSession.RequestedChannelCount) + { + if (_userSampleFormat != SampleFormat.PcmInt16) + { + throw new NotImplementedException("Downmixing formats other than PCM16 is not supported."); + } + + ReadOnlySpan samplesPCM16 = MemoryMarshal.Cast(samples); + + if (_userChannelCount == 6) + { + samplesPCM16 = Downmixing.DownMixSurroundToStereo(samplesPCM16); + + if (_realSession.RequestedChannelCount == 1) + { + samplesPCM16 = Downmixing.DownMixStereoToMono(samplesPCM16); + } + } + else if (_userChannelCount == 2 && _realSession.RequestedChannelCount == 1) + { + samplesPCM16 = Downmixing.DownMixStereoToMono(samplesPCM16); + } + else + { + throw new NotImplementedException($"Downmixing from {_userChannelCount} to {_realSession.RequestedChannelCount} not implemented."); + } + + samples = MemoryMarshal.Cast(samplesPCM16).ToArray(); + } + + AudioBuffer fakeBuffer = new() + { + BufferTag = buffer.BufferTag, + DataPointer = buffer.DataPointer, + DataSize = (ulong)samples.Length, + }; + + bool result = _realSession.RegisterBuffer(fakeBuffer, samples); + + if (result) + { + buffer.Data = fakeBuffer.Data; + buffer.DataSize = fakeBuffer.DataSize; + } + + return result; + } + + public override void SetVolume(float volume) + { + _realSession.SetVolume(volume); + } + + public override void Start() + { + _realSession.Start(); + } + + public override void Stop() + { + _realSession.Stop(); + } + + public override void UnregisterBuffer(AudioBuffer buffer) + { + _realSession.UnregisterBuffer(buffer); + } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + return _realSession.WasBufferFullyConsumed(buffer); + } + } +} diff --git a/src/Ryujinx.Audio/Backends/CompatLayer/Downmixing.cs b/src/Ryujinx.Audio/Backends/CompatLayer/Downmixing.cs new file mode 100644 index 00000000..7a5ea0de --- /dev/null +++ b/src/Ryujinx.Audio/Backends/CompatLayer/Downmixing.cs @@ -0,0 +1,129 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Backends.CompatLayer +{ + public static class Downmixing + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct Channel51FormatPCM16 + { + public short FrontLeft; + public short FrontRight; + public short FrontCenter; + public short LowFrequency; + public short BackLeft; + public short BackRight; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct ChannelStereoFormatPCM16 + { + public short Left; + public short Right; + } + + private const int Q15Bits = 16; + private const int RawQ15One = 1 << Q15Bits; + private const int RawQ15HalfOne = (int)(0.5f * RawQ15One); + private const int Minus3dBInQ15 = (int)(0.707f * RawQ15One); + private const int Minus6dBInQ15 = (int)(0.501f * RawQ15One); + private const int Minus12dBInQ15 = (int)(0.251f * RawQ15One); + + private static readonly long[] _defaultSurroundToStereoCoefficients = new long[4] + { + RawQ15One, + Minus3dBInQ15, + Minus12dBInQ15, + Minus3dBInQ15, + }; + + private static readonly long[] _defaultStereoToMonoCoefficients = new long[2] + { + Minus6dBInQ15, + Minus6dBInQ15, + }; + + private const int SurroundChannelCount = 6; + private const int StereoChannelCount = 2; + private const int MonoChannelCount = 1; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ReadOnlySpan GetSurroundBuffer(ReadOnlySpan data) + { + return MemoryMarshal.Cast(data); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ReadOnlySpan GetStereoBuffer(ReadOnlySpan data) + { + return MemoryMarshal.Cast(data); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short DownMixStereoToMono(ReadOnlySpan coefficients, short left, short right) + { + return (short)Math.Clamp((left * coefficients[0] + right * coefficients[1]) >> Q15Bits, short.MinValue, short.MaxValue); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short DownMixSurroundToStereo(ReadOnlySpan coefficients, short back, short lfe, short center, short front) + { + return (short)Math.Clamp( + (coefficients[3] * back + + coefficients[2] * lfe + + coefficients[1] * center + + coefficients[0] * front + RawQ15HalfOne) >> Q15Bits, short.MinValue, short.MaxValue); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short[] DownMixSurroundToStereo(ReadOnlySpan coefficients, ReadOnlySpan data) + { + int samplePerChannelCount = data.Length / SurroundChannelCount; + + short[] downmixedBuffer = new short[samplePerChannelCount * StereoChannelCount]; + + ReadOnlySpan channels = GetSurroundBuffer(data); + + for (int i = 0; i < samplePerChannelCount; i++) + { + Channel51FormatPCM16 channel = channels[i]; + + downmixedBuffer[i * 2] = DownMixSurroundToStereo(coefficients, channel.BackLeft, channel.LowFrequency, channel.FrontCenter, channel.FrontLeft); + downmixedBuffer[i * 2 + 1] = DownMixSurroundToStereo(coefficients, channel.BackRight, channel.LowFrequency, channel.FrontCenter, channel.FrontRight); + } + + return downmixedBuffer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short[] DownMixStereoToMono(ReadOnlySpan coefficients, ReadOnlySpan data) + { + int samplePerChannelCount = data.Length / StereoChannelCount; + + short[] downmixedBuffer = new short[samplePerChannelCount * MonoChannelCount]; + + ReadOnlySpan channels = GetStereoBuffer(data); + + for (int i = 0; i < samplePerChannelCount; i++) + { + ChannelStereoFormatPCM16 channel = channels[i]; + + downmixedBuffer[i] = DownMixStereoToMono(coefficients, channel.Left, channel.Right); + } + + return downmixedBuffer; + } + + public static short[] DownMixStereoToMono(ReadOnlySpan data) + { + return DownMixStereoToMono(_defaultStereoToMonoCoefficients, data); + } + + public static short[] DownMixSurroundToStereo(ReadOnlySpan data) + { + return DownMixSurroundToStereo(_defaultSurroundToStereoCoefficients, data); + } + } +} diff --git a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs new file mode 100644 index 00000000..3a3c1d1b --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs @@ -0,0 +1,92 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System; +using System.Threading; +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace Ryujinx.Audio.Backends.Dummy +{ + public class DummyHardwareDeviceDriver : IHardwareDeviceDriver + { + private readonly ManualResetEvent _updateRequiredEvent; + private readonly ManualResetEvent _pauseEvent; + + public static bool IsSupported => true; + + public float Volume { get; set; } + + public DummyHardwareDeviceDriver() + { + _updateRequiredEvent = new ManualResetEvent(false); + _pauseEvent = new ManualResetEvent(true); + + Volume = 1f; + } + + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) + { + if (sampleRate == 0) + { + sampleRate = Constants.TargetSampleRate; + } + + if (channelCount == 0) + { + channelCount = 2; + } + + if (direction == Direction.Output) + { + return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount); + } + + return new DummyHardwareDeviceSessionInput(this, memoryManager); + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _updateRequiredEvent; + } + + public ManualResetEvent GetPauseEvent() + { + return _pauseEvent; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // NOTE: The _updateRequiredEvent will be disposed somewhere else. + _pauseEvent.Dispose(); + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return true; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return true; + } + + public bool SupportsDirection(Direction direction) + { + return direction == Direction.Output || direction == Direction.Input; + } + + public bool SupportsChannelCount(uint channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 6; + } + } +} diff --git a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionInput.cs b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionInput.cs new file mode 100644 index 00000000..f51a6339 --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionInput.cs @@ -0,0 +1,67 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.Audio.Backends.Dummy +{ + class DummyHardwareDeviceSessionInput : IHardwareDeviceSession + { + private float _volume; + private readonly IHardwareDeviceDriver _manager; + private readonly IVirtualMemoryManager _memoryManager; + + public DummyHardwareDeviceSessionInput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager) + { + _volume = 1.0f; + _manager = manager; + _memoryManager = memoryManager; + } + + public void Dispose() + { + // Nothing to do. + } + + public ulong GetPlayedSampleCount() + { + // Not implemented for input. + throw new NotSupportedException(); + } + + public float GetVolume() + { + return _volume; + } + + public void PrepareToClose() { } + + public void QueueBuffer(AudioBuffer buffer) + { + _memoryManager.Fill(buffer.DataPointer, buffer.DataSize, 0); + + _manager.GetUpdateRequiredEvent().Set(); + } + + public bool RegisterBuffer(AudioBuffer buffer) + { + return buffer.DataPointer != 0; + } + + public void SetVolume(float volume) + { + _volume = volume; + } + + public void Start() { } + + public void Stop() { } + + public void UnregisterBuffer(AudioBuffer buffer) { } + + public bool WasBufferFullyConsumed(AudioBuffer buffer) + { + return true; + } + } +} diff --git a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs new file mode 100644 index 00000000..34cf653c --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs @@ -0,0 +1,62 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System.Threading; + +namespace Ryujinx.Audio.Backends.Dummy +{ + internal class DummyHardwareDeviceSessionOutput : HardwareDeviceSessionOutputBase + { + private float _volume; + private readonly IHardwareDeviceDriver _manager; + + private ulong _playedSampleCount; + + public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _volume = 1f; + _manager = manager; + } + + public override void Dispose() + { + // Nothing to do. + } + + public override ulong GetPlayedSampleCount() + { + return Interlocked.Read(ref _playedSampleCount); + } + + public override float GetVolume() + { + return _volume; + } + + public override void PrepareToClose() { } + + public override void QueueBuffer(AudioBuffer buffer) + { + Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer)); + + _manager.GetUpdateRequiredEvent().Set(); + } + + public override void SetVolume(float volume) + { + _volume = volume; + } + + public override void Start() { } + + public override void Stop() { } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + return true; + } + } +} diff --git a/src/Ryujinx.Audio/Common/AudioBuffer.cs b/src/Ryujinx.Audio/Common/AudioBuffer.cs new file mode 100644 index 00000000..87a7d5f3 --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioBuffer.cs @@ -0,0 +1,37 @@ +using Ryujinx.Audio.Integration; + +namespace Ryujinx.Audio.Common +{ + /// + /// Represent an audio buffer that will be used by an . + /// + public class AudioBuffer + { + /// + /// Unique tag of this buffer. + /// + /// Unique per session + public ulong BufferTag; + + /// + /// Pointer to the user samples. + /// + public ulong DataPointer; + + /// + /// Size of the user samples region. + /// + public ulong DataSize; + + /// + /// The timestamp at which the buffer was played. + /// + /// Not used but useful for debugging + public ulong PlayedTimestamp; + + /// + /// The user samples. + /// + public byte[] Data; + } +} diff --git a/src/Ryujinx.Audio/Common/AudioDeviceSession.cs b/src/Ryujinx.Audio/Common/AudioDeviceSession.cs new file mode 100644 index 00000000..a0e04c80 --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioDeviceSession.cs @@ -0,0 +1,516 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Common; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Common +{ + /// + /// An audio device session. + /// + class AudioDeviceSession : IDisposable + { + /// + /// The volume of the . + /// + private float _volume; + + /// + /// The state of the . + /// + private AudioDeviceState _state; + + /// + /// Array of all buffers currently used or released. + /// + private readonly AudioBuffer[] _buffers; + + /// + /// The server index inside (appended but not queued to device driver). + /// + private uint _serverBufferIndex; + + /// + /// The hardware index inside (queued to device driver). + /// + private uint _hardwareBufferIndex; + + /// + /// The released index inside (released by the device driver). + /// + private uint _releasedBufferIndex; + + /// + /// The count of buffer appended (server side). + /// + private uint _bufferAppendedCount; + + /// + /// The count of buffer registered (driver side). + /// + private uint _bufferRegisteredCount; + + /// + /// The count of buffer released (released by the driver side). + /// + private uint _bufferReleasedCount; + + /// + /// The released buffer event. + /// + private readonly IWritableEvent _bufferEvent; + + /// + /// The session on the device driver. + /// + private readonly IHardwareDeviceSession _hardwareDeviceSession; + + /// + /// Max number of buffers that can be registered to the device driver at a time. + /// + private readonly uint _bufferRegisteredLimit; + + /// + /// Create a new . + /// + /// The device driver session associated + /// The release buffer event + /// The max number of buffers that can be registered to the device driver at a time + public AudioDeviceSession(IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent, uint bufferRegisteredLimit = 4) + { + _bufferEvent = bufferEvent; + _hardwareDeviceSession = deviceSession; + _bufferRegisteredLimit = bufferRegisteredLimit; + + _buffers = new AudioBuffer[Constants.AudioDeviceBufferCountMax]; + _serverBufferIndex = 0; + _hardwareBufferIndex = 0; + _releasedBufferIndex = 0; + + _bufferAppendedCount = 0; + _bufferRegisteredCount = 0; + _bufferReleasedCount = 0; + _volume = deviceSession.GetVolume(); + _state = AudioDeviceState.Stopped; + } + + /// + /// Get the released buffer event. + /// + /// The released buffer event + public IWritableEvent GetBufferEvent() + { + return _bufferEvent; + } + + /// + /// Get the state of the session. + /// + /// The state of the session + public AudioDeviceState GetState() + { + Debug.Assert(_state == AudioDeviceState.Started || _state == AudioDeviceState.Stopped); + + return _state; + } + + /// + /// Get the total buffer count (server + driver + released). + /// + /// Return the total buffer count + private uint GetTotalBufferCount() + { + uint bufferCount = _bufferAppendedCount + _bufferRegisteredCount + _bufferReleasedCount; + + Debug.Assert(bufferCount <= Constants.AudioDeviceBufferCountMax); + + return bufferCount; + } + + /// + /// Register a new on the server side. + /// + /// The to register + /// True if the operation succeeded + private bool RegisterBuffer(AudioBuffer buffer) + { + if (GetTotalBufferCount() == Constants.AudioDeviceBufferCountMax) + { + return false; + } + + _buffers[_serverBufferIndex] = buffer; + _serverBufferIndex = (_serverBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + _bufferAppendedCount++; + + return true; + } + + /// + /// Flush server buffers to hardware. + /// + private void FlushToHardware() + { + uint bufferToFlushCount = Math.Min(Math.Min(_bufferAppendedCount, 4), _bufferRegisteredLimit - _bufferRegisteredCount); + + AudioBuffer[] buffersToFlush = new AudioBuffer[bufferToFlushCount]; + + uint hardwareBufferIndex = _hardwareBufferIndex; + + for (int i = 0; i < buffersToFlush.Length; i++) + { + buffersToFlush[i] = _buffers[hardwareBufferIndex]; + + _bufferAppendedCount--; + _bufferRegisteredCount++; + + hardwareBufferIndex = (hardwareBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + _hardwareBufferIndex = hardwareBufferIndex; + + for (int i = 0; i < buffersToFlush.Length; i++) + { + _hardwareDeviceSession.QueueBuffer(buffersToFlush[i]); + } + } + + /// + /// Get the current index of the playing on the driver side. + /// + /// The output index of the playing on the driver side + /// True if any buffer is playing + private bool TryGetPlayingBufferIndex(out uint playingIndex) + { + if (_bufferRegisteredCount > 0) + { + playingIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax; + + return true; + } + + playingIndex = 0; + + return false; + } + + /// + /// Try to pop the playing on the driver side. + /// + /// The output playing on the driver side + /// True if any buffer is playing + private bool TryPopPlayingBuffer(out AudioBuffer buffer) + { + if (_bufferRegisteredCount > 0) + { + uint bufferIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax; + + buffer = _buffers[bufferIndex]; + + _buffers[bufferIndex] = null; + + _bufferRegisteredCount--; + + return true; + } + + buffer = null; + + return false; + } + + /// + /// Try to pop a released by the driver side. + /// + /// The output released by the driver side + /// True if any buffer has been released + public bool TryPopReleasedBuffer(out AudioBuffer buffer) + { + if (_bufferReleasedCount > 0) + { + uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax; + + buffer = _buffers[bufferIndex]; + + _buffers[bufferIndex] = null; + + _bufferReleasedCount--; + + return true; + } + + buffer = null; + + return false; + } + + /// + /// Release a . + /// + /// The to release + private void ReleaseBuffer(AudioBuffer buffer) + { + buffer.PlayedTimestamp = (ulong)PerformanceCounter.ElapsedNanoseconds; + + _bufferRegisteredCount--; + _bufferReleasedCount++; + + _releasedBufferIndex = (_releasedBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + /// + /// Update the released buffers. + /// + /// True if the session is currently stopping + private void UpdateReleaseBuffers(bool updateForStop = false) + { + bool wasAnyBuffersReleased = false; + + while (TryGetPlayingBufferIndex(out uint playingIndex)) + { + if (!updateForStop && !_hardwareDeviceSession.WasBufferFullyConsumed(_buffers[playingIndex])) + { + break; + } + + if (updateForStop) + { + _hardwareDeviceSession.UnregisterBuffer(_buffers[playingIndex]); + } + + ReleaseBuffer(_buffers[playingIndex]); + + wasAnyBuffersReleased = true; + } + + if (wasAnyBuffersReleased) + { + _bufferEvent.Signal(); + } + } + + /// + /// Append a new . + /// + /// The to append + /// True if the buffer was appended + public bool AppendBuffer(AudioBuffer buffer) + { + if (_hardwareDeviceSession.RegisterBuffer(buffer)) + { + if (RegisterBuffer(buffer)) + { + FlushToHardware(); + + return true; + } + + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + return false; + } + + public static bool AppendUacBuffer(AudioBuffer buffer, uint handle) + { + // NOTE: On hardware, there is another RegisterBuffer method taking a handle. + // This variant of the call always return false (stubbed?) as a result this logic will never succeed. + + return false; + } + + /// + /// Start the audio session. + /// + /// A reporting an error or a success + public ResultCode Start() + { + if (_state == AudioDeviceState.Started) + { + return ResultCode.OperationFailed; + } + + _hardwareDeviceSession.Start(); + + _state = AudioDeviceState.Started; + + FlushToHardware(); + + _hardwareDeviceSession.SetVolume(_volume); + + return ResultCode.Success; + } + + /// + /// Stop the audio session. + /// + /// A reporting an error or a success + public ResultCode Stop() + { + if (_state == AudioDeviceState.Started) + { + _hardwareDeviceSession.Stop(); + + UpdateReleaseBuffers(true); + + _state = AudioDeviceState.Stopped; + } + + return ResultCode.Success; + } + + /// + /// Get the volume of the session. + /// + /// The volume of the session + public float GetVolume() + { + return _hardwareDeviceSession.GetVolume(); + } + + /// + /// Set the volume of the session. + /// + /// The new volume to set + public void SetVolume(float volume) + { + _volume = volume; + + if (_state == AudioDeviceState.Started) + { + _hardwareDeviceSession.SetVolume(volume); + } + } + + /// + /// Get the count of buffer currently in use (server + driver side). + /// + /// The count of buffer currently in use + public uint GetBufferCount() + { + return _bufferAppendedCount + _bufferRegisteredCount; + } + + /// + /// Check if a buffer is present. + /// + /// The unique tag of the buffer + /// Return true if a buffer is present + public bool ContainsBuffer(ulong bufferTag) + { + uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax; + + uint totalBufferCount = GetTotalBufferCount(); + + for (int i = 0; i < totalBufferCount; i++) + { + if (_buffers[bufferIndex].BufferTag == bufferTag) + { + return true; + } + + bufferIndex = (bufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + return false; + } + + /// + /// Get the count of sample played in this session. + /// + /// The count of sample played in this session + public ulong GetPlayedSampleCount() + { + if (_state == AudioDeviceState.Stopped) + { + return 0; + } + + return _hardwareDeviceSession.GetPlayedSampleCount(); + } + + /// + /// Flush all buffers to the initial state. + /// + /// True if any buffer was flushed + public bool FlushBuffers() + { + if (_state == AudioDeviceState.Stopped) + { + return false; + } + + uint bufferCount = GetBufferCount(); + + while (TryPopReleasedBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + while (TryPopPlayingBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + if (_bufferRegisteredCount == 0 || (_bufferReleasedCount + _bufferAppendedCount) > Constants.AudioDeviceBufferCountMax) + { + return false; + } + + _bufferReleasedCount += _bufferAppendedCount; + _releasedBufferIndex = (_releasedBufferIndex + _bufferAppendedCount) % Constants.AudioDeviceBufferCountMax; + _bufferAppendedCount = 0; + _hardwareBufferIndex = _serverBufferIndex; + + if (bufferCount > 0) + { + _bufferEvent.Signal(); + } + + return true; + } + + /// + /// Update the session. + /// + public void Update() + { + if (_state == AudioDeviceState.Started) + { + UpdateReleaseBuffers(); + FlushToHardware(); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Tell the hardware session that we are ending. + _hardwareDeviceSession.PrepareToClose(); + + // Unregister all buffers + + while (TryPopReleasedBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + while (TryPopPlayingBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + // Finally dispose hardware session. + _hardwareDeviceSession.Dispose(); + + _bufferEvent.Signal(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Common/AudioDeviceState.cs b/src/Ryujinx.Audio/Common/AudioDeviceState.cs new file mode 100644 index 00000000..8705e802 --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioDeviceState.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Audio.Common +{ + /// + /// Audio device state. + /// + public enum AudioDeviceState : uint + { + /// + /// The audio device is started. + /// + Started, + + /// + /// The audio device is stopped. + /// + Stopped, + } +} diff --git a/src/Ryujinx.Audio/Common/AudioInputConfiguration.cs b/src/Ryujinx.Audio/Common/AudioInputConfiguration.cs new file mode 100644 index 00000000..078c3a39 --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioInputConfiguration.cs @@ -0,0 +1,29 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio user input configuration. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioInputConfiguration + { + /// + /// The target sample rate of the user. + /// + /// Only 48000Hz is considered valid, other sample rates will be refused. + public uint SampleRate; + + /// + /// The target channel count of the user. + /// + /// Only Stereo and Surround are considered valid, other configurations will be refused. + /// Not used in audin. + public ushort ChannelCount; + + /// + /// Reserved/unused. + /// + private readonly ushort _reserved; + } +} diff --git a/src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs b/src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs new file mode 100644 index 00000000..594f1225 --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs @@ -0,0 +1,37 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio system output configuration. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioOutputConfiguration + { + /// + /// The target sample rate of the system. + /// + public uint SampleRate; + + /// + /// The target channel count of the system. + /// + public uint ChannelCount; + + /// + /// Reserved/unused + /// + public SampleFormat SampleFormat; + + /// + /// Reserved/unused. + /// + private Array3 _padding; + + /// + /// The initial audio system state. + /// + public AudioDeviceState AudioOutState; + } +} diff --git a/src/Ryujinx.Audio/Common/AudioUserBuffer.cs b/src/Ryujinx.Audio/Common/AudioUserBuffer.cs new file mode 100644 index 00000000..bb71165f --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioUserBuffer.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio user buffer. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioUserBuffer + { + /// + /// Pointer to the next buffer (ignored). + /// + public ulong NextBuffer; + + /// + /// Pointer to the user samples. + /// + public ulong Data; + + /// + /// Capacity of the buffer (unused). + /// + public ulong Capacity; + + /// + /// Size of the user samples region. + /// + public ulong DataSize; + + /// + /// Offset in the user samples region (unused). + /// + public ulong DataOffset; + } +} diff --git a/src/Ryujinx.Audio/Common/SampleFormat.cs b/src/Ryujinx.Audio/Common/SampleFormat.cs new file mode 100644 index 00000000..39e525e8 --- /dev/null +++ b/src/Ryujinx.Audio/Common/SampleFormat.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.Audio.Common +{ + /// + /// Sample format definition. + /// + public enum SampleFormat : byte + { + /// + /// Invalid sample format. + /// + Invalid = 0, + + /// + /// PCM8 sample format. (unsupported) + /// + PcmInt8 = 1, + + /// + /// PCM16 sample format. + /// + PcmInt16 = 2, + + /// + /// PCM24 sample format. (unsupported) + /// + PcmInt24 = 3, + + /// + /// PCM32 sample format. + /// + PcmInt32 = 4, + + /// + /// PCM Float sample format. + /// + PcmFloat = 5, + + /// + /// ADPCM sample format. (Also known as GC-ADPCM) + /// + Adpcm = 6, + } +} diff --git a/src/Ryujinx.Audio/Constants.cs b/src/Ryujinx.Audio/Constants.cs new file mode 100644 index 00000000..eb5b3901 --- /dev/null +++ b/src/Ryujinx.Audio/Constants.cs @@ -0,0 +1,175 @@ +namespace Ryujinx.Audio +{ + /// + /// Define constants used by the audio system. + /// + public static class Constants + { + /// + /// The default device output name. + /// + public const string DefaultDeviceOutputName = "DeviceOut"; + + /// + /// The default device input name. + /// + public const string DefaultDeviceInputName = "BuiltInHeadset"; + + /// + /// The maximum number of channels supported. (6 channels for 5.1 surround) + /// + public const int ChannelCountMax = 6; + + /// + /// The maximum number of channels supported per voice. + /// + public const int VoiceChannelCountMax = ChannelCountMax; + + /// + /// The maximum count of mix buffer supported per operations (volumes, mix effect, ...) + /// + public const int MixBufferCountMax = 24; + + /// + /// The maximum count of wavebuffer per voice. + /// + public const int VoiceWaveBufferCount = 4; + + /// + /// The maximum count of biquad filter per voice. + /// + public const int VoiceBiquadFilterCount = 2; + + /// + /// The lowest priority that a voice can have. + /// + public const int VoiceLowestPriority = 0xFF; + + /// + /// The highest priority that a voice can have. + /// + /// Voices with the highest priority will not be dropped if a voice drop needs to occur. + public const int VoiceHighestPriority = 0; + + /// + /// Maximum that can be returned by . + /// + public const int MaxErrorInfos = 10; + + /// + /// Default alignment for buffers. + /// + public const int BufferAlignment = 0x40; + + /// + /// Alignment required for the work buffer. + /// + public const int WorkBufferAlignment = 0x1000; + + /// + /// Alignment required for every performance metrics frame. + /// + public const int PerformanceMetricsPerFramesSizeAlignment = 0x100; + + /// + /// The id of the final mix. + /// + public const int FinalMixId = 0; + + /// + /// The id defining an unused mix id. + /// + public const int UnusedMixId = int.MaxValue; + + /// + /// The id defining an unused splitter id as a signed integer. + /// + public const int UnusedSplitterIdInt = -1; + + /// + /// The id defining an unused splitter id. + /// + public const uint UnusedSplitterId = uint.MaxValue; + + /// + /// The id of invalid/unused node id. + /// + public const int InvalidNodeId = -268435456; + + /// + /// The indice considered invalid for processing order. + /// + public const int InvalidProcessingOrder = -1; + + /// + /// The maximum number of audio renderer sessions allowed to be created system wide. + /// + public const int AudioRendererSessionCountMax = 2; + + /// + /// The maximum number of audio output sessions allowed to be created system wide. + /// + public const int AudioOutSessionCountMax = 12; + + /// + /// The maximum number of audio input sessions allowed to be created system wide. + /// + public const int AudioInSessionCountMax = 4; + + /// + /// Maximum buffers supported by one audio device session. + /// + public const int AudioDeviceBufferCountMax = 32; + + /// + /// The target sample rate of the audio renderer. (48kHz) + /// + public const uint TargetSampleRate = 48000; + + /// + /// The target sample size of the audio renderer. (PCM16) + /// + public const int TargetSampleSize = sizeof(ushort); + + /// + /// The target sample count per audio renderer update. + /// + public const int TargetSampleCount = 240; + + /// + /// The size of an upsampler entry to process upsampling to . + /// + public const int UpSampleEntrySize = TargetSampleCount * VoiceChannelCountMax; + + /// + /// The target audio latency computed from and . + /// + public const int AudioProcessorMaxUpdateTimeTarget = 1000000000 / ((int)TargetSampleRate / TargetSampleCount); // 5.00 ms + + /// + /// The maximum update time of the DSP on original hardware. + /// + public const int AudioProcessorMaxUpdateTime = 5760000; // 5.76 ms + + /// + /// The maximum update time per audio renderer session. + /// + public const int AudioProcessorMaxUpdateTimePerSessions = AudioProcessorMaxUpdateTime / AudioRendererSessionCountMax; + + /// + /// Guest timer frequency used for system ticks. + /// + public const int TargetTimerFrequency = 19200000; + + /// + /// The default coefficients used for standard 5.1 surround to stereo downmixing. + /// + public static readonly float[] DefaultSurroundToStereoCoefficients = new float[4] + { + 1.0f, + 0.707f, + 0.251f, + 0.707f, + }; + } +} diff --git a/src/Ryujinx.Audio/Input/AudioInputManager.cs b/src/Ryujinx.Audio/Input/AudioInputManager.cs new file mode 100644 index 00000000..d56997e9 --- /dev/null +++ b/src/Ryujinx.Audio/Input/AudioInputManager.cs @@ -0,0 +1,264 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace Ryujinx.Audio.Input +{ + /// + /// The audio input manager. + /// + public class AudioInputManager : IDisposable + { + private readonly object _lock = new(); + + /// + /// Lock used for session allocation. + /// + private readonly object _sessionLock = new(); + + /// + /// The session ids allocation table. + /// + private readonly int[] _sessionIds; + + /// + /// The device driver. + /// + private IHardwareDeviceDriver _deviceDriver; + + /// + /// The events linked to each session. + /// + private IWritableEvent[] _sessionsBufferEvents; + + /// + /// The session instances. + /// + private readonly AudioInputSystem[] _sessions; + + /// + /// The count of active sessions. + /// + private int _activeSessionCount; + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + public AudioInputManager() + { + _sessionIds = new int[Constants.AudioInSessionCountMax]; + _sessions = new AudioInputSystem[Constants.AudioInSessionCountMax]; + _activeSessionCount = 0; + + for (int i = 0; i < _sessionIds.Length; i++) + { + _sessionIds[i] = i; + } + } + + /// + /// Initialize the . + /// + /// The device driver. + /// The events associated to each session. + public void Initialize(IHardwareDeviceDriver deviceDriver, IWritableEvent[] sessionRegisterEvents) + { + _deviceDriver = deviceDriver; + _sessionsBufferEvents = sessionRegisterEvents; + } + + /// + /// Acquire a new session id. + /// + /// A new session id. + private int AcquireSessionId() + { + lock (_sessionLock) + { + int index = _activeSessionCount; + + Debug.Assert(index < _sessionIds.Length); + + int sessionId = _sessionIds[index]; + + _sessionIds[index] = -1; + + _activeSessionCount++; + + Logger.Info?.Print(LogClass.AudioRenderer, $"Registered new input ({sessionId})"); + + return sessionId; + } + } + + /// + /// Release a given . + /// + /// The session id to release. + private void ReleaseSessionId(int sessionId) + { + lock (_sessionLock) + { + Debug.Assert(_activeSessionCount > 0); + + int newIndex = --_activeSessionCount; + + _sessionIds[newIndex] = sessionId; + } + + Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered input ({sessionId})"); + } + + /// + /// Used to update audio input system. + /// + public void Update() + { + lock (_sessionLock) + { + foreach (AudioInputSystem input in _sessions) + { + input?.Update(); + } + } + } + + /// + /// Register a new . + /// + /// The to register. + private void Register(AudioInputSystem input) + { + lock (_sessionLock) + { + _sessions[input.GetSessionId()] = input; + } + } + + /// + /// Unregister a new . + /// + /// The to unregister. + internal void Unregister(AudioInputSystem input) + { + lock (_sessionLock) + { + int sessionId = input.GetSessionId(); + + _sessions[input.GetSessionId()] = null; + + ReleaseSessionId(sessionId); + } + } + + /// + /// Get the list of all audio inputs names. + /// + /// If true, filter disconnected devices + /// The list of all audio inputs name + public string[] ListAudioIns(bool filtered) + { + if (filtered) + { + // TODO: Detect if the driver supports audio input + } + + return new[] { Constants.DefaultDeviceInputName }; + } + + /// + /// Open a new . + /// + /// The output device name selected by the + /// The output audio configuration selected by the + /// The new + /// The memory manager that will be used for all guest memory operations + /// The input device name wanted by the user + /// The sample format to use + /// The user configuration + /// A reporting an error or a success + public ResultCode OpenAudioIn(out string outputDeviceName, + out AudioOutputConfiguration outputConfiguration, + out AudioInputSystem obj, + IVirtualMemoryManager memoryManager, + string inputDeviceName, + SampleFormat sampleFormat, + ref AudioInputConfiguration parameter) + { + int sessionId = AcquireSessionId(); + + _sessionsBufferEvents[sessionId].Clear(); + + IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Input, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount); + + AudioInputSystem audioIn = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]); + + ResultCode result = audioIn.Initialize(inputDeviceName, sampleFormat, ref parameter, sessionId); + + if (result == ResultCode.Success) + { + outputDeviceName = audioIn.DeviceName; + outputConfiguration = new AudioOutputConfiguration + { + ChannelCount = audioIn.ChannelCount, + SampleFormat = audioIn.SampleFormat, + SampleRate = audioIn.SampleRate, + AudioOutState = audioIn.GetState(), + }; + + obj = audioIn; + + Register(audioIn); + } + else + { + ReleaseSessionId(sessionId); + + obj = null; + outputDeviceName = null; + outputConfiguration = default; + } + + return result; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Clone the sessions array to dispose them outside the lock. + AudioInputSystem[] sessions; + + lock (_sessionLock) + { + sessions = _sessions.ToArray(); + } + + foreach (AudioInputSystem input in sessions) + { + input?.Dispose(); + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Input/AudioInputSystem.cs b/src/Ryujinx.Audio/Input/AudioInputSystem.cs new file mode 100644 index 00000000..34623b34 --- /dev/null +++ b/src/Ryujinx.Audio/Input/AudioInputSystem.cs @@ -0,0 +1,396 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using System; +using System.Threading; + +namespace Ryujinx.Audio.Input +{ + /// + /// Audio input system. + /// + public class AudioInputSystem : IDisposable + { + /// + /// The session id associated to the . + /// + private int _sessionId; + + /// + /// The session the . + /// + private readonly AudioDeviceSession _session; + + /// + /// The target device name of the . + /// + public string DeviceName { get; private set; } + + /// + /// The target sample rate of the . + /// + public uint SampleRate { get; private set; } + + /// + /// The target channel count of the . + /// + public uint ChannelCount { get; private set; } + + /// + /// The target sample format of the . + /// + public SampleFormat SampleFormat { get; private set; } + + /// + /// The owning this. + /// + private readonly AudioInputManager _manager; + + /// + /// The lock of the parent. + /// + private readonly object _parentLock; + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + /// The manager instance + /// The lock of the manager + /// The hardware device session + /// The buffer release event of the audio input + public AudioInputSystem(AudioInputManager manager, object parentLock, IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent) + { + _manager = manager; + _parentLock = parentLock; + _session = new AudioDeviceSession(deviceSession, bufferEvent); + } + + /// + /// Get the default device name on the system. + /// + /// The default device name on the system. + private static string GetDeviceDefaultName() + { + return Constants.DefaultDeviceInputName; + } + + /// + /// Check if a given configuration and device name is valid on the system. + /// + /// The configuration to check. + /// The device name to check. + /// A reporting an error or a success. + private static ResultCode IsConfigurationValid(ref AudioInputConfiguration configuration, string deviceName) + { + if (deviceName.Length != 0 && !deviceName.Equals(GetDeviceDefaultName())) + { + return ResultCode.DeviceNotFound; + } + + if (configuration.SampleRate != 0 && configuration.SampleRate != Constants.TargetSampleRate) + { + return ResultCode.UnsupportedSampleRate; + } + + if (configuration.ChannelCount != 0 && configuration.ChannelCount != 1 && configuration.ChannelCount != 2 && configuration.ChannelCount != 6) + { + return ResultCode.UnsupportedChannelConfiguration; + } + + return ResultCode.Success; + } + + /// + /// Get the released buffer event. + /// + /// The released buffer event + public IWritableEvent RegisterBufferEvent() + { + lock (_parentLock) + { + return _session.GetBufferEvent(); + } + } + + /// + /// Update the . + /// + public void Update() + { + lock (_parentLock) + { + _session.Update(); + } + } + + /// + /// Get the id of this session. + /// + /// The id of this session + public int GetSessionId() + { + return _sessionId; + } + + /// + /// Initialize the . + /// + /// The input device name wanted by the user + /// The sample format to use + /// The user configuration + /// The session id associated to this + /// A reporting an error or a success. + public ResultCode Initialize(string inputDeviceName, SampleFormat sampleFormat, ref AudioInputConfiguration parameter, int sessionId) + { + _sessionId = sessionId; + + ResultCode result = IsConfigurationValid(ref parameter, inputDeviceName); + + if (result == ResultCode.Success) + { + if (inputDeviceName.Length == 0) + { + DeviceName = GetDeviceDefaultName(); + } + else + { + DeviceName = inputDeviceName; + } + + if (parameter.ChannelCount == 6) + { + ChannelCount = 6; + } + else + { + ChannelCount = 2; + } + + SampleFormat = sampleFormat; + SampleRate = Constants.TargetSampleRate; + } + + return result; + } + + /// + /// Append a new audio buffer to the audio input. + /// + /// The unique tag of this buffer. + /// The buffer informations. + /// A reporting an error or a success. + public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer userBuffer) + { + lock (_parentLock) + { + AudioBuffer buffer = new() + { + BufferTag = bufferTag, + DataPointer = userBuffer.Data, + DataSize = userBuffer.DataSize, + }; + + if (_session.AppendBuffer(buffer)) + { + return ResultCode.Success; + } + + return ResultCode.BufferRingFull; + } + } + + /// + /// Append a new audio buffer to the audio input. + /// + /// This is broken by design, only added for completness. + /// The unique tag of this buffer. + /// The buffer informations. + /// Some unknown handle. + /// A reporting an error or a success. + public ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer userBuffer, uint handle) + { + lock (_parentLock) + { + AudioBuffer buffer = new() + { + BufferTag = bufferTag, + DataPointer = userBuffer.Data, + DataSize = userBuffer.DataSize, + }; + + if (AudioDeviceSession.AppendUacBuffer(buffer, handle)) + { + return ResultCode.Success; + } + + return ResultCode.BufferRingFull; + } + } + + /// + /// Get the release buffers. + /// + /// The buffer to write the release buffers + /// The count of released buffers + /// A reporting an error or a success. + public ResultCode GetReleasedBuffers(Span releasedBuffers, out uint releasedCount) + { + releasedCount = 0; + + // Ensure that the first entry is set to zero if no entries are returned. + if (releasedBuffers.Length > 0) + { + releasedBuffers[0] = 0; + } + + lock (_parentLock) + { + for (int i = 0; i < releasedBuffers.Length; i++) + { + if (!_session.TryPopReleasedBuffer(out AudioBuffer buffer)) + { + break; + } + + releasedBuffers[i] = buffer.BufferTag; + releasedCount++; + } + } + + return ResultCode.Success; + } + + /// + /// Get the current state of the . + /// + /// Return the curent sta\te of the + public AudioDeviceState GetState() + { + lock (_parentLock) + { + return _session.GetState(); + } + } + + /// + /// Start the audio session. + /// + /// A reporting an error or a success + public ResultCode Start() + { + lock (_parentLock) + { + return _session.Start(); + } + } + + /// + /// Stop the audio session. + /// + /// A reporting an error or a success + public ResultCode Stop() + { + lock (_parentLock) + { + return _session.Stop(); + } + } + + /// + /// Get the volume of the session. + /// + /// The volume of the session + public float GetVolume() + { + lock (_parentLock) + { + return _session.GetVolume(); + } + } + + /// + /// Set the volume of the session. + /// + /// The new volume to set + public void SetVolume(float volume) + { + lock (_parentLock) + { + _session.SetVolume(volume); + } + } + + /// + /// Get the count of buffer currently in use (server + driver side). + /// + /// The count of buffer currently in use + public uint GetBufferCount() + { + lock (_parentLock) + { + return _session.GetBufferCount(); + } + } + + /// + /// Check if a buffer is present. + /// + /// The unique tag of the buffer + /// Return true if a buffer is present + public bool ContainsBuffer(ulong bufferTag) + { + lock (_parentLock) + { + return _session.ContainsBuffer(bufferTag); + } + } + + /// + /// Get the count of sample played in this session. + /// + /// The count of sample played in this session + public ulong GetPlayedSampleCount() + { + lock (_parentLock) + { + return _session.GetPlayedSampleCount(); + } + } + + /// + /// Flush all buffers to the initial state. + /// + /// True if any buffers was flushed + public bool FlushBuffers() + { + lock (_parentLock) + { + return _session.FlushBuffers(); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _session.Dispose(); + + _manager.Unregister(this); + } + } + } +} diff --git a/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs b/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs new file mode 100644 index 00000000..1369f953 --- /dev/null +++ b/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs @@ -0,0 +1,76 @@ +using Ryujinx.Audio.Common; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Integration +{ + public class HardwareDeviceImpl : IHardwareDevice + { + private readonly IHardwareDeviceSession _session; + private readonly uint _channelCount; + private readonly uint _sampleRate; + private uint _currentBufferTag; + + private readonly byte[] _buffer; + + public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate) + { + _session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount); + _channelCount = channelCount; + _sampleRate = sampleRate; + _currentBufferTag = 0; + + _buffer = new byte[Constants.TargetSampleCount * channelCount * sizeof(ushort)]; + + _session.Start(); + } + + public void AppendBuffer(ReadOnlySpan data, uint channelCount) + { + data.CopyTo(MemoryMarshal.Cast(_buffer)); + + _session.QueueBuffer(new AudioBuffer + { + DataPointer = _currentBufferTag++, + Data = _buffer, + DataSize = (ulong)_buffer.Length, + }); + + _currentBufferTag %= 4; + } + + public void SetVolume(float volume) + { + _session.SetVolume(volume); + } + + public float GetVolume() + { + return _session.GetVolume(); + } + + public uint GetChannelCount() + { + return _channelCount; + } + + public uint GetSampleRate() + { + return _sampleRate; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _session.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Integration/IHardwareDevice.cs b/src/Ryujinx.Audio/Integration/IHardwareDevice.cs new file mode 100644 index 00000000..f9ade9db --- /dev/null +++ b/src/Ryujinx.Audio/Integration/IHardwareDevice.cs @@ -0,0 +1,55 @@ +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Integration +{ + /// + /// Represent an hardware device used in + /// + public interface IHardwareDevice : IDisposable + { + /// + /// Sets the volume level for this device. + /// + /// The volume level to set. + void SetVolume(float volume); + + /// + /// Gets the volume level for this device. + /// + /// The volume level of this device. + float GetVolume(); + + /// + /// Get the supported sample rate of this device. + /// + /// The supported sample rate of this device. + uint GetSampleRate(); + + /// + /// Get the channel count supported by this device. + /// + /// The channel count supported by this device. + uint GetChannelCount(); + + /// + /// Appends new PCM16 samples to the device. + /// + /// The new PCM16 samples. + /// The number of channels. + void AppendBuffer(ReadOnlySpan data, uint channelCount); + + /// + /// Check if the audio renderer needs to perform downmixing. + /// + /// True if downmixing is needed. + public bool NeedDownmixing() + { + uint channelCount = GetChannelCount(); + + Debug.Assert(channelCount > 0 && channelCount <= Constants.ChannelCountMax); + + return channelCount != Constants.ChannelCountMax; + } + } +} diff --git a/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs new file mode 100644 index 00000000..95b0e4e5 --- /dev/null +++ b/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs @@ -0,0 +1,38 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Memory; +using System; +using System.Threading; + +namespace Ryujinx.Audio.Integration +{ + /// + /// Represent an hardware device driver used in . + /// + public interface IHardwareDeviceDriver : IDisposable + { + public enum Direction + { + Input, + Output, + } + + float Volume { get; set; } + + IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount); + + ManualResetEvent GetUpdateRequiredEvent(); + ManualResetEvent GetPauseEvent(); + + bool SupportsDirection(Direction direction); + bool SupportsSampleRate(uint sampleRate); + bool SupportsSampleFormat(SampleFormat sampleFormat); + bool SupportsChannelCount(uint channelCount); + + static abstract bool IsSupported { get; } + + IHardwareDeviceDriver GetRealDeviceDriver() + { + return this; + } + } +} diff --git a/src/Ryujinx.Audio/Integration/IHardwareDeviceSession.cs b/src/Ryujinx.Audio/Integration/IHardwareDeviceSession.cs new file mode 100644 index 00000000..f29c109c --- /dev/null +++ b/src/Ryujinx.Audio/Integration/IHardwareDeviceSession.cs @@ -0,0 +1,28 @@ +using Ryujinx.Audio.Common; +using System; + +namespace Ryujinx.Audio.Integration +{ + public interface IHardwareDeviceSession : IDisposable + { + bool RegisterBuffer(AudioBuffer buffer); + + void UnregisterBuffer(AudioBuffer buffer); + + void QueueBuffer(AudioBuffer buffer); + + bool WasBufferFullyConsumed(AudioBuffer buffer); + + void SetVolume(float volume); + + float GetVolume(); + + ulong GetPlayedSampleCount(); + + void Start(); + + void Stop(); + + void PrepareToClose(); + } +} diff --git a/src/Ryujinx.Audio/Integration/IWritableEvent.cs b/src/Ryujinx.Audio/Integration/IWritableEvent.cs new file mode 100644 index 00000000..a3b3bc0b --- /dev/null +++ b/src/Ryujinx.Audio/Integration/IWritableEvent.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Audio.Integration +{ + /// + /// Represent a writable event with manual clear. + /// + public interface IWritableEvent + { + /// + /// Signal the event. + /// + void Signal(); + + /// + /// Clear the signaled state of the event. + /// + void Clear(); + } +} diff --git a/src/Ryujinx.Audio/Output/AudioOutputManager.cs b/src/Ryujinx.Audio/Output/AudioOutputManager.cs new file mode 100644 index 00000000..308cd156 --- /dev/null +++ b/src/Ryujinx.Audio/Output/AudioOutputManager.cs @@ -0,0 +1,258 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace Ryujinx.Audio.Output +{ + /// + /// The audio output manager. + /// + public class AudioOutputManager : IDisposable + { + private readonly object _lock = new(); + + /// + /// Lock used for session allocation. + /// + private readonly object _sessionLock = new(); + + /// + /// The session ids allocation table. + /// + private readonly int[] _sessionIds; + + /// + /// The device driver. + /// + private IHardwareDeviceDriver _deviceDriver; + + /// + /// The events linked to each session. + /// + private IWritableEvent[] _sessionsBufferEvents; + + /// + /// The session instances. + /// + private readonly AudioOutputSystem[] _sessions; + + /// + /// The count of active sessions. + /// + private int _activeSessionCount; + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + public AudioOutputManager() + { + _sessionIds = new int[Constants.AudioOutSessionCountMax]; + _sessions = new AudioOutputSystem[Constants.AudioOutSessionCountMax]; + _activeSessionCount = 0; + + for (int i = 0; i < _sessionIds.Length; i++) + { + _sessionIds[i] = i; + } + } + + /// + /// Initialize the . + /// + /// The device driver. + /// The events associated to each session. + public void Initialize(IHardwareDeviceDriver deviceDriver, IWritableEvent[] sessionRegisterEvents) + { + _deviceDriver = deviceDriver; + _sessionsBufferEvents = sessionRegisterEvents; + } + + /// + /// Acquire a new session id. + /// + /// A new session id. + private int AcquireSessionId() + { + lock (_sessionLock) + { + int index = _activeSessionCount; + + Debug.Assert(index < _sessionIds.Length); + + int sessionId = _sessionIds[index]; + + _sessionIds[index] = -1; + + _activeSessionCount++; + + Logger.Info?.Print(LogClass.AudioRenderer, $"Registered new output ({sessionId})"); + + return sessionId; + } + } + + /// + /// Release a given . + /// + /// The session id to release. + private void ReleaseSessionId(int sessionId) + { + lock (_sessionLock) + { + Debug.Assert(_activeSessionCount > 0); + + int newIndex = --_activeSessionCount; + + _sessionIds[newIndex] = sessionId; + } + + Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered output ({sessionId})"); + } + + /// + /// Used to update audio output system. + /// + public void Update() + { + lock (_sessionLock) + { + foreach (AudioOutputSystem output in _sessions) + { + output?.Update(); + } + } + } + + /// + /// Register a new . + /// + /// The to register. + private void Register(AudioOutputSystem output) + { + lock (_sessionLock) + { + _sessions[output.GetSessionId()] = output; + } + } + + /// + /// Unregister a new . + /// + /// The to unregister. + internal void Unregister(AudioOutputSystem output) + { + lock (_sessionLock) + { + int sessionId = output.GetSessionId(); + + _sessions[output.GetSessionId()] = null; + + ReleaseSessionId(sessionId); + } + } + + /// + /// Get the list of all audio outputs name. + /// + /// The list of all audio outputs name + public string[] ListAudioOuts() + { + return new[] { Constants.DefaultDeviceOutputName }; + } + + /// + /// Open a new . + /// + /// The output device name selected by the + /// The output audio configuration selected by the + /// The new + /// The memory manager that will be used for all guest memory operations + /// The input device name wanted by the user + /// The sample format to use + /// The user configuration + /// A reporting an error or a success + public ResultCode OpenAudioOut(out string outputDeviceName, + out AudioOutputConfiguration outputConfiguration, + out AudioOutputSystem obj, + IVirtualMemoryManager memoryManager, + string inputDeviceName, + SampleFormat sampleFormat, + ref AudioInputConfiguration parameter) + { + int sessionId = AcquireSessionId(); + + _sessionsBufferEvents[sessionId].Clear(); + + IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount); + + AudioOutputSystem audioOut = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]); + + ResultCode result = audioOut.Initialize(inputDeviceName, sampleFormat, ref parameter, sessionId); + + if (result == ResultCode.Success) + { + outputDeviceName = audioOut.DeviceName; + outputConfiguration = new AudioOutputConfiguration + { + ChannelCount = audioOut.ChannelCount, + SampleFormat = audioOut.SampleFormat, + SampleRate = audioOut.SampleRate, + AudioOutState = audioOut.GetState(), + }; + + obj = audioOut; + + Register(audioOut); + } + else + { + ReleaseSessionId(sessionId); + + obj = null; + outputDeviceName = null; + outputConfiguration = default; + } + + return result; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Clone the sessions array to dispose them outside the lock. + AudioOutputSystem[] sessions; + + lock (_sessionLock) + { + sessions = _sessions.ToArray(); + } + + foreach (AudioOutputSystem output in sessions) + { + output?.Dispose(); + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Output/AudioOutputSystem.cs b/src/Ryujinx.Audio/Output/AudioOutputSystem.cs new file mode 100644 index 00000000..f9b9bdcf --- /dev/null +++ b/src/Ryujinx.Audio/Output/AudioOutputSystem.cs @@ -0,0 +1,369 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using System; +using System.Threading; + +namespace Ryujinx.Audio.Output +{ + /// + /// Audio output system. + /// + public class AudioOutputSystem : IDisposable + { + /// + /// The session id associated to the . + /// + private int _sessionId; + + /// + /// The session the . + /// + private readonly AudioDeviceSession _session; + + /// + /// The target device name of the . + /// + public string DeviceName { get; private set; } + + /// + /// The target sample rate of the . + /// + public uint SampleRate { get; private set; } + + /// + /// The target channel count of the . + /// + public uint ChannelCount { get; private set; } + + /// + /// The target sample format of the . + /// + public SampleFormat SampleFormat { get; private set; } + + /// + /// The owning this. + /// + private readonly AudioOutputManager _manager; + + /// + /// THe lock of the parent. + /// + private readonly object _parentLock; + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + /// The manager instance + /// The lock of the manager + /// The hardware device session + /// The buffer release event of the audio output + public AudioOutputSystem(AudioOutputManager manager, object parentLock, IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent) + { + _manager = manager; + _parentLock = parentLock; + _session = new AudioDeviceSession(deviceSession, bufferEvent); + } + + /// + /// Get the default device name on the system. + /// + /// The default device name on the system. + private static string GetDeviceDefaultName() + { + return Constants.DefaultDeviceOutputName; + } + + /// + /// Check if a given configuration and device name is valid on the system. + /// + /// The configuration to check. + /// The device name to check. + /// A reporting an error or a success. + private static ResultCode IsConfigurationValid(ref AudioInputConfiguration configuration, string deviceName) + { + if (deviceName.Length != 0 && !deviceName.Equals(GetDeviceDefaultName())) + { + return ResultCode.DeviceNotFound; + } + + if (configuration.SampleRate != 0 && configuration.SampleRate != Constants.TargetSampleRate) + { + return ResultCode.UnsupportedSampleRate; + } + + if (configuration.ChannelCount != 0 && configuration.ChannelCount != 1 && configuration.ChannelCount != 2 && configuration.ChannelCount != 6) + { + return ResultCode.UnsupportedChannelConfiguration; + } + + return ResultCode.Success; + } + + /// + /// Get the released buffer event. + /// + /// The released buffer event + public IWritableEvent RegisterBufferEvent() + { + lock (_parentLock) + { + return _session.GetBufferEvent(); + } + } + + /// + /// Update the . + /// + public void Update() + { + lock (_parentLock) + { + _session.Update(); + } + } + + /// + /// Get the id of this session. + /// + /// The id of this session + public int GetSessionId() + { + return _sessionId; + } + + /// + /// Initialize the . + /// + /// The input device name wanted by the user + /// The sample format to use + /// The user configuration + /// The session id associated to this + /// A reporting an error or a success. + public ResultCode Initialize(string inputDeviceName, SampleFormat sampleFormat, ref AudioInputConfiguration parameter, int sessionId) + { + _sessionId = sessionId; + + ResultCode result = IsConfigurationValid(ref parameter, inputDeviceName); + + if (result == ResultCode.Success) + { + if (inputDeviceName.Length == 0) + { + DeviceName = GetDeviceDefaultName(); + } + else + { + DeviceName = inputDeviceName; + } + + if (parameter.ChannelCount == 6) + { + ChannelCount = 6; + } + else + { + ChannelCount = 2; + } + + SampleFormat = sampleFormat; + SampleRate = Constants.TargetSampleRate; + } + + return result; + } + + /// + /// Append a new audio buffer to the audio output. + /// + /// The unique tag of this buffer. + /// The buffer informations. + /// A reporting an error or a success. + public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer userBuffer) + { + lock (_parentLock) + { + AudioBuffer buffer = new() + { + BufferTag = bufferTag, + DataPointer = userBuffer.Data, + DataSize = userBuffer.DataSize, + }; + + if (_session.AppendBuffer(buffer)) + { + return ResultCode.Success; + } + + return ResultCode.BufferRingFull; + } + } + + /// + /// Get the release buffers. + /// + /// The buffer to write the release buffers + /// The count of released buffers + /// A reporting an error or a success. + public ResultCode GetReleasedBuffer(Span releasedBuffers, out uint releasedCount) + { + releasedCount = 0; + + // Ensure that the first entry is set to zero if no entries are returned. + if (releasedBuffers.Length > 0) + { + releasedBuffers[0] = 0; + } + + lock (_parentLock) + { + for (int i = 0; i < releasedBuffers.Length; i++) + { + if (!_session.TryPopReleasedBuffer(out AudioBuffer buffer)) + { + break; + } + + releasedBuffers[i] = buffer.BufferTag; + releasedCount++; + } + } + + return ResultCode.Success; + } + + /// + /// Get the current state of the . + /// + /// Return the curent sta\te of the + /// + public AudioDeviceState GetState() + { + lock (_parentLock) + { + return _session.GetState(); + } + } + + /// + /// Start the audio session. + /// + /// A reporting an error or a success + public ResultCode Start() + { + lock (_parentLock) + { + return _session.Start(); + } + } + + /// + /// Stop the audio session. + /// + /// A reporting an error or a success + public ResultCode Stop() + { + lock (_parentLock) + { + return _session.Stop(); + } + } + + /// + /// Get the volume of the session. + /// + /// The volume of the session + public float GetVolume() + { + lock (_parentLock) + { + return _session.GetVolume(); + } + } + + /// + /// Set the volume of the session. + /// + /// The new volume to set + public void SetVolume(float volume) + { + lock (_parentLock) + { + _session.SetVolume(volume); + } + } + + /// + /// Get the count of buffer currently in use (server + driver side). + /// + /// The count of buffer currently in use + public uint GetBufferCount() + { + lock (_parentLock) + { + return _session.GetBufferCount(); + } + } + + /// + /// Check if a buffer is present. + /// + /// The unique tag of the buffer + /// Return true if a buffer is present + public bool ContainsBuffer(ulong bufferTag) + { + lock (_parentLock) + { + return _session.ContainsBuffer(bufferTag); + } + } + + /// + /// Get the count of sample played in this session. + /// + /// The count of sample played in this session + public ulong GetPlayedSampleCount() + { + lock (_parentLock) + { + return _session.GetPlayedSampleCount(); + } + } + + /// + /// Flush all buffers to the initial state. + /// + /// True if any buffers was flushed + public bool FlushBuffers() + { + lock (_parentLock) + { + return _session.FlushBuffers(); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _session.Dispose(); + + _manager.Unregister(this); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs b/src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs new file mode 100644 index 00000000..b7b97d5d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AuxiliaryBufferAddresses + { + public ulong SendBufferInfo; + public ulong SendBufferInfoBase; + public ulong ReturnBufferInfo; + public ulong ReturnBufferInfoBase; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs b/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs new file mode 100644 index 00000000..3b8d15dc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs @@ -0,0 +1,50 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Represents the input parameter for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BehaviourParameter + { + /// + /// The current audio renderer revision in use. + /// + public int UserRevision; + + /// + /// Reserved/padding. + /// + private readonly uint _padding; + + /// + /// The flags given controlling behaviour of the audio renderer + /// + /// See and . + public ulong Flags; + + /// + /// Represents an error during . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ErrorInfo + { + /// + /// The error code to report. + /// + public ResultCode ErrorCode; + + /// + /// Reserved/padding. + /// + private readonly uint _padding; + + /// + /// Extra information given with the + /// + /// This is usually used to report a faulting cpu address when a mapping fail. + public ulong ExtraErrorInfo; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs b/src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs new file mode 100644 index 00000000..3beb6239 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs @@ -0,0 +1,150 @@ +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Represents a adjacent matrix. + /// + /// This is used for splitter routing. + public class EdgeMatrix + { + /// + /// Backing used for node connections. + /// + private BitArray _storage; + + /// + /// The count of nodes of the current instance. + /// + private int _nodeCount; + + /// + /// Get the required work buffer size memory needed for the . + /// + /// The count of nodes. + /// The size required for the given . + public static int GetWorkBufferSize(int nodeCount) + { + int size = BitUtils.AlignUp(nodeCount * nodeCount, Constants.BufferAlignment); + + return size / Unsafe.SizeOf(); + } + + /// + /// Initializes the instance with backing memory. + /// + /// The backing memory. + /// The count of nodes. + public void Initialize(Memory edgeMatrixWorkBuffer, int nodeCount) + { + Debug.Assert(edgeMatrixWorkBuffer.Length >= GetWorkBufferSize(nodeCount)); + + _storage = new BitArray(edgeMatrixWorkBuffer); + + _nodeCount = nodeCount; + + _storage.Reset(); + } + + /// + /// Test if the bit at the given index is set. + /// + /// A bit index. + /// Returns true if the bit at the given index is set + public bool Test(int index) + { + return _storage.Test(index); + } + + /// + /// Reset all bits in the storage. + /// + public void Reset() + { + _storage.Reset(); + } + + /// + /// Reset the bit at the given index. + /// + /// A bit index. + public void Reset(int index) + { + _storage.Reset(index); + } + + /// + /// Set the bit at the given index. + /// + /// A bit index. + public void Set(int index) + { + _storage.Set(index); + } + + /// + /// Connect a given source to a given destination. + /// + /// The source index. + /// The destination index. + public void Connect(int source, int destination) + { + Debug.Assert(source < _nodeCount); + Debug.Assert(destination < _nodeCount); + + _storage.Set(_nodeCount * source + destination); + } + + /// + /// Check if the given source is connected to the given destination. + /// + /// The source index. + /// The destination index. + /// Returns true if the given source is connected to the given destination. + public bool Connected(int source, int destination) + { + Debug.Assert(source < _nodeCount); + Debug.Assert(destination < _nodeCount); + + return _storage.Test(_nodeCount * source + destination); + } + + /// + /// Disconnect a given source from a given destination. + /// + /// The source index. + /// The destination index. + public void Disconnect(int source, int destination) + { + Debug.Assert(source < _nodeCount); + Debug.Assert(destination < _nodeCount); + + _storage.Reset(_nodeCount * source + destination); + } + + /// + /// Remove all edges from a given source. + /// + /// The source index. + public void RemoveEdges(int source) + { + for (int i = 0; i < _nodeCount; i++) + { + Disconnect(source, i); + } + } + + /// + /// Get the total node count. + /// + /// The total node count. + public int GetNodeCount() + { + return _nodeCount; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/EffectType.cs b/src/Ryujinx.Audio/Renderer/Common/EffectType.cs new file mode 100644 index 00000000..7c8713b1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/EffectType.cs @@ -0,0 +1,58 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// The type of an effect. + /// + public enum EffectType : byte + { + /// + /// Invalid effect. + /// + Invalid, + + /// + /// Effect applying additional mixing capability. + /// + BufferMix, + + /// + /// Effect applying custom user effect (via auxiliary buffers). + /// + AuxiliaryBuffer, + + /// + /// Effect applying a delay. + /// + Delay, + + /// + /// Effect applying a reverberation effect via a given preset. + /// + Reverb, + + /// + /// Effect applying a 3D reverberation effect via a given preset. + /// + Reverb3d, + + /// + /// Effect applying a biquad filter. + /// + BiquadFilter, + + /// + /// Effect applying a limiter (DRC). + /// + Limiter, + + /// + /// Effect to capture mixes (via auxiliary buffers). + /// + CaptureBuffer, + + /// + /// Effect applying a compressor filter (DRC). + /// + Compressor, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs b/src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs new file mode 100644 index 00000000..6d835879 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Represents the state of a memory pool. + /// + public enum MemoryPoolUserState : uint + { + /// + /// Invalid state. + /// + Invalid = 0, + + /// + /// The memory pool is new. (client side only) + /// + New = 1, + + /// + /// The user asked to detach the memory pool from the . + /// + RequestDetach = 2, + + /// + /// The memory pool is detached from the . + /// + Detached = 3, + + /// + /// The user asked to attach the memory pool to the . + /// + RequestAttach = 4, + + /// + /// The memory pool is attached to the . + /// + Attached = 5, + + /// + /// The memory pool is released. (client side only) + /// + Released = 6, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs b/src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs new file mode 100644 index 00000000..76fba54b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Helper for manipulating node ids. + /// + public static class NodeIdHelper + { + /// + /// Get the type of a node from a given node id. + /// + /// Id of the node. + /// The type of the node. + public static NodeIdType GetType(int nodeId) + { + return (NodeIdType)(nodeId >> 28); + } + + /// + /// Get the base of a node from a given node id. + /// + /// Id of the node. + /// The base of the node. + public static int GetBase(int nodeId) + { + return (nodeId >> 16) & 0xFFF; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs b/src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs new file mode 100644 index 00000000..b226da14 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// The type of a node. + /// + public enum NodeIdType : byte + { + /// + /// Invalid node id. + /// + Invalid = 0, + + /// + /// Voice related node id. (data source, biquad filter, ...) + /// + Voice = 1, + + /// + /// Mix related node id. (mix, effects, splitters, ...) + /// + Mix = 2, + + /// + /// Sink related node id. (device & circular buffer sink) + /// + Sink = 3, + + /// + /// Performance monitoring related node id (performance commands) + /// + Performance = 15, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/NodeStates.cs b/src/Ryujinx.Audio/Renderer/Common/NodeStates.cs new file mode 100644 index 00000000..75290a74 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/NodeStates.cs @@ -0,0 +1,230 @@ +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Common +{ + public class NodeStates + { + private class Stack + { + private Memory _storage; + private int _index; + + private int _nodeCount; + + public void Reset(Memory storage, int nodeCount) + { + Debug.Assert(storage.Length * sizeof(int) >= CalcBufferSize(nodeCount)); + + _storage = storage; + _index = 0; + _nodeCount = nodeCount; + } + + public int GetCurrentCount() + { + return _index; + } + + public void Push(int data) + { + Debug.Assert(_index + 1 <= _nodeCount); + + _storage.Span[_index++] = data; + } + + public int Pop() + { + Debug.Assert(_index > 0); + + return _storage.Span[--_index]; + } + + public int Top() + { + return _storage.Span[_index - 1]; + } + + public static int CalcBufferSize(int nodeCount) + { + return nodeCount * sizeof(int); + } + } + + private int _nodeCount; + private readonly EdgeMatrix _discovered; + private readonly EdgeMatrix _finished; + private Memory _resultArray; + private readonly Stack _stack; + private int _tsortResultIndex; + + private enum NodeState : byte + { + Unknown, + Discovered, + Finished, + } + + public NodeStates() + { + _stack = new Stack(); + _discovered = new EdgeMatrix(); + _finished = new EdgeMatrix(); + } + + public static int GetWorkBufferSize(int nodeCount) + { + return Stack.CalcBufferSize(nodeCount * nodeCount) + 0xC * nodeCount + 2 * EdgeMatrix.GetWorkBufferSize(nodeCount); + } + + public void Initialize(Memory nodeStatesWorkBuffer, int nodeCount) + { + int workBufferSize = GetWorkBufferSize(nodeCount); + + Debug.Assert(nodeStatesWorkBuffer.Length >= workBufferSize); + + _nodeCount = nodeCount; + + int edgeMatrixWorkBufferSize = EdgeMatrix.GetWorkBufferSize(nodeCount); + + _discovered.Initialize(nodeStatesWorkBuffer[..edgeMatrixWorkBufferSize], nodeCount); + _finished.Initialize(nodeStatesWorkBuffer.Slice(edgeMatrixWorkBufferSize, edgeMatrixWorkBufferSize), nodeCount); + + nodeStatesWorkBuffer = nodeStatesWorkBuffer[(edgeMatrixWorkBufferSize * 2)..]; + + _resultArray = SpanMemoryManager.Cast(nodeStatesWorkBuffer[..(sizeof(int) * nodeCount)]); + + nodeStatesWorkBuffer = nodeStatesWorkBuffer[(sizeof(int) * nodeCount)..]; + + Memory stackWorkBuffer = SpanMemoryManager.Cast(nodeStatesWorkBuffer[..Stack.CalcBufferSize(nodeCount * nodeCount)]); + + _stack.Reset(stackWorkBuffer, nodeCount * nodeCount); + } + + private void Reset() + { + _discovered.Reset(); + _finished.Reset(); + _tsortResultIndex = 0; + _resultArray.Span.Fill(-1); + } + + private NodeState GetState(int index) + { + Debug.Assert(index < _nodeCount); + + if (_discovered.Test(index)) + { + Debug.Assert(!_finished.Test(index)); + + return NodeState.Discovered; + } + + if (_finished.Test(index)) + { + Debug.Assert(!_discovered.Test(index)); + + return NodeState.Finished; + } + + return NodeState.Unknown; + } + + private void SetState(int index, NodeState state) + { + switch (state) + { + case NodeState.Unknown: + _discovered.Reset(index); + _finished.Reset(index); + break; + case NodeState.Discovered: + _discovered.Set(index); + _finished.Reset(index); + break; + case NodeState.Finished: + _finished.Set(index); + _discovered.Reset(index); + break; + } + } + + private void PushTsortResult(int index) + { + Debug.Assert(index < _nodeCount); + + _resultArray.Span[_tsortResultIndex++] = index; + } + + public ReadOnlySpan GetTsortResult() + { + return _resultArray.Span[.._tsortResultIndex]; + } + + public bool Sort(EdgeMatrix edgeMatrix) + { + Reset(); + + if (_nodeCount <= 0) + { + return true; + } + + for (int i = 0; i < _nodeCount; i++) + { + if (GetState(i) == NodeState.Unknown) + { + _stack.Push(i); + } + + while (_stack.GetCurrentCount() > 0) + { + int topIndex = _stack.Top(); + + NodeState topState = GetState(topIndex); + + if (topState == NodeState.Discovered) + { + SetState(topIndex, NodeState.Finished); + PushTsortResult(topIndex); + _stack.Pop(); + } + else if (topState == NodeState.Finished) + { + _stack.Pop(); + } + else + { + if (topState == NodeState.Unknown) + { + SetState(topIndex, NodeState.Discovered); + } + + for (int j = 0; j < edgeMatrix.GetNodeCount(); j++) + { + if (edgeMatrix.Connected(topIndex, j)) + { + NodeState jState = GetState(j); + + if (jState == NodeState.Unknown) + { + _stack.Push(j); + } + // Found a loop, reset and propagate rejection. + else if (jState == NodeState.Discovered) + { + Reset(); + + return false; + } + } + } + } + } + } + + return true; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs b/src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs new file mode 100644 index 00000000..bde32a70 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + public enum PerformanceDetailType : byte + { + Unknown, + PcmInt16, + Adpcm, + VolumeRamp, + BiquadFilter, + Mix, + Delay, + Aux, + Reverb, + Reverb3d, + PcmFloat, + Limiter, + CaptureBuffer, + Compressor, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs b/src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs new file mode 100644 index 00000000..e32095e6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + public enum PerformanceEntryType : byte + { + Invalid, + Voice, + SubMix, + FinalMix, + Sink, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/PlayState.cs b/src/Ryujinx.Audio/Renderer/Common/PlayState.cs new file mode 100644 index 00000000..a83d16af --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/PlayState.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Common play state. + /// + public enum PlayState : byte + { + /// + /// The user request the voice to be started. + /// + Start, + + /// + /// The user request the voice to be stopped. + /// + Stop, + + /// + /// The user request the voice to be paused. + /// + Pause, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs b/src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs new file mode 100644 index 00000000..c7443cc4 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Early reverb reflection. + /// + public enum ReverbEarlyMode : uint + { + /// + /// Room early reflection. (small acoustic space, fast reflection) + /// + Room, + + /// + /// Chamber early reflection. (bigger than 's acoustic space, short reflection) + /// + Chamber, + + /// + /// Hall early reflection. (large acoustic space, warm reflection) + /// + Hall, + + /// + /// Cathedral early reflection. (very large acoustic space, pronounced bright reflection) + /// + Cathedral, + + /// + /// No early reflection. + /// + Disabled, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs b/src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs new file mode 100644 index 00000000..78f91cf0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs @@ -0,0 +1,38 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Late reverb reflection. + /// + public enum ReverbLateMode : uint + { + /// + /// Room late reflection. (small acoustic space, fast reflection) + /// + Room, + + /// + /// Hall late reflection. (large acoustic space, warm reflection) + /// + Hall, + + /// + /// Classic plate late reflection. (clean distinctive reverb) + /// + Plate, + + /// + /// Cathedral late reflection. (very large acoustic space, pronounced bright reflection) + /// + Cathedral, + + /// + /// Do not apply any delay. (max delay) + /// + NoDelay, + + /// + /// Max delay. (used for delay line limits) + /// + Limit = NoDelay, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/SinkType.cs b/src/Ryujinx.Audio/Renderer/Common/SinkType.cs new file mode 100644 index 00000000..5a08df4e --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/SinkType.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// The type of a sink. + /// + public enum SinkType : byte + { + /// + /// The sink is in an invalid state. + /// + Invalid, + + /// + /// The sink is a device. + /// + Device, + + /// + /// The sink is a circular buffer. + /// + CircularBuffer, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs b/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs new file mode 100644 index 00000000..98b224eb --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs @@ -0,0 +1,36 @@ +using Ryujinx.Common.Memory; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Update data header used for input and output of . + /// + public struct UpdateDataHeader + { + public int Revision; + public uint BehaviourSize; + public uint MemoryPoolsSize; + public uint VoicesSize; + public uint VoiceResourcesSize; + public uint EffectsSize; + public uint MixesSize; + public uint SinksSize; + public uint PerformanceBufferSize; + public uint Unknown24; + public uint RenderInfoSize; + +#pragma warning disable IDE0051, CS0169 // Remove unused field + private Array4 _reserved; +#pragma warning restore IDE0051, CS0169 + + public uint TotalSize; + + public void Initialize(int revision) + { + Revision = revision; + + TotalSize = (uint)Unsafe.SizeOf(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs new file mode 100644 index 00000000..7f881373 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs @@ -0,0 +1,103 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Represent the update state of a voice. + /// + /// This is shared between the server and audio processor. + [StructLayout(LayoutKind.Sequential, Pack = Align)] + public struct VoiceUpdateState + { + public const int Align = 0x10; + public const int BiquadStateOffset = 0x0; + + /// + /// The state of the biquad filters of this voice. + /// + public Array2 BiquadFilterState; + + /// + /// The total amount of samples that was played. + /// + /// This is reset to 0 when a finishes playing and is set. + /// This is reset to 0 when looping while is set. + public ulong PlayedSampleCount; + + /// + /// The current sample offset in the pointed by . + /// + public int Offset; + + /// + /// The current index of the in use. + /// + public uint WaveBufferIndex; + + private WaveBufferValidArray _isWaveBufferValid; + + /// + /// The total amount of consumed. + /// + public uint WaveBufferConsumed; + + /// + /// Pitch used for Sample Rate Conversion. + /// + public Array8 Pitch; + + public float Fraction; + + /// + /// The ADPCM loop context when is in use. + /// + public AdpcmLoopContext LoopContext; + + /// + /// The last samples after a mix ramp. + /// + /// This is used for depop (to perform voice drop). + public Array24 LastSamples; + + /// + /// The current count of loop performed. + /// + public int LoopCount; + + [StructLayout(LayoutKind.Sequential, Size = 1 * Constants.VoiceWaveBufferCount, Pack = 1)] + private struct WaveBufferValidArray { } + + /// + /// Contains information of validity. + /// + public Span IsWaveBufferValid => SpanHelpers.AsSpan(ref _isWaveBufferValid); + + /// + /// Mark the current as played and switch to the next one. + /// + /// The current + /// The wavebuffer index. + /// The amount of wavebuffers consumed. + /// The total count of sample played. + public void MarkEndOfBufferWaveBufferProcessing(ref WaveBuffer waveBuffer, ref int waveBufferIndex, ref uint waveBufferConsumed, ref ulong playedSampleCount) + { + IsWaveBufferValid[waveBufferIndex++] = false; + LoopCount = 0; + waveBufferConsumed++; + + if (waveBufferIndex >= Constants.VoiceWaveBufferCount) + { + waveBufferIndex = 0; + } + + if (waveBuffer.IsEndOfStream) + { + playedSampleCount = 0; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs b/src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs new file mode 100644 index 00000000..5109d3fa --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs @@ -0,0 +1,81 @@ +using System.Runtime.InteropServices; +using DspAddr = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// A wavebuffer used for data source commands. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct WaveBuffer + { + /// + /// The DSP address of the sample data of the wavebuffer. + /// + public DspAddr Buffer; + + /// + /// The DSP address of the context of the wavebuffer. + /// + /// Only used by . + public DspAddr Context; + + /// + /// The size of the sample buffer data. + /// + public uint BufferSize; + + /// + /// The size of the context buffer. + /// + public uint ContextSize; + + /// + /// First sample to play on the wavebuffer. + /// + public uint StartSampleOffset; + + /// + /// Last sample to play on the wavebuffer. + /// + public uint EndSampleOffset; + + /// + /// First sample to play when looping the wavebuffer. + /// + /// + /// If or is equal to zero,, it will default to and . + /// + public uint LoopStartSampleOffset; + + /// + /// Last sample to play when looping the wavebuffer. + /// + /// + /// If or is equal to zero, it will default to and . + /// + public uint LoopEndSampleOffset; + + /// + /// The max loop count. + /// + public int LoopCount; + + /// + /// Set to true if the wavebuffer is looping. + /// + [MarshalAs(UnmanagedType.I1)] + public bool Looping; + + /// + /// Set to true if the wavebuffer is the end of stream. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEndOfStream; + + /// + /// Padding/Reserved. + /// + private readonly ushort _padding; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs b/src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs new file mode 100644 index 00000000..54673f2f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs @@ -0,0 +1,61 @@ +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + public class WorkBufferAllocator + { + public Memory BackingMemory { get; } + + public ulong Offset { get; private set; } + + public WorkBufferAllocator(Memory backingMemory) + { + BackingMemory = backingMemory; + } + + public Memory Allocate(ulong size, int align) + { + Debug.Assert(align != 0); + + if (size != 0) + { + ulong alignedOffset = BitUtils.AlignUp(Offset, (ulong)align); + + if (alignedOffset + size <= (ulong)BackingMemory.Length) + { + Memory result = BackingMemory.Slice((int)alignedOffset, (int)size); + + Offset = alignedOffset + size; + + // Clear the memory to be sure that is does not contain any garbage. + result.Span.Clear(); + + return result; + } + } + + return Memory.Empty; + } + + public Memory Allocate(ulong count, int align) where T : unmanaged + { + Memory allocatedMemory = Allocate((ulong)Unsafe.SizeOf() * count, align); + + if (allocatedMemory.IsEmpty) + { + return Memory.Empty; + } + + return SpanMemoryManager.Cast(allocatedMemory); + } + + public static ulong GetTargetSize(ulong currentSize, ulong count, int align) where T : unmanaged + { + return BitUtils.AlignUp(currentSize, (ulong)align) + (ulong)Unsafe.SizeOf() * count; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs b/src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs new file mode 100644 index 00000000..91956fda --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs @@ -0,0 +1,89 @@ +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Device +{ + /// + /// Represents a virtual device used by IAudioDevice. + /// + public class VirtualDevice + { + /// + /// All the defined virtual devices. + /// + public static readonly VirtualDevice[] Devices = new VirtualDevice[5] + { + new("AudioStereoJackOutput", 2, true), + new("AudioBuiltInSpeakerOutput", 2, false), + new("AudioTvOutput", 6, false), + new("AudioUsbDeviceOutput", 2, true), + new("AudioExternalOutput", 6, true), + }; + + /// + /// The name of the . + /// + public string Name { get; } + + /// + /// The count of channels supported by the . + /// + public uint ChannelCount { get; } + + /// + /// The system master volume of the . + /// + public float MasterVolume { get; private set; } + + /// + /// Define if the is provided by an external interface. + /// + public bool IsExternalOutput { get; } + + /// + /// Create a new instance. + /// + /// The name of the . + /// The count of channels supported by the . + /// Indicate if the is provided by an external interface. + public VirtualDevice(string name, uint channelCount, bool isExternalOutput) + { + Name = name; + ChannelCount = channelCount; + IsExternalOutput = isExternalOutput; + } + + /// + /// Update the master volume of the . + /// + /// The new master volume. + public void UpdateMasterVolume(float volume) + { + Debug.Assert(volume >= 0.0f && volume <= 1.0f); + + MasterVolume = volume; + } + + /// + /// Check if the is a usb device. + /// + /// Returns true if the is a usb device. + public bool IsUsbDevice() + { + return Name.Equals("AudioUsbDeviceOutput"); + } + + /// + /// Get the output device name of the . + /// + /// The output device name of the . + public string GetOutputDeviceName() + { + if (IsExternalOutput) + { + return "AudioExternalOutput"; + } + + return Name; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs new file mode 100644 index 00000000..09fa71ed --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.Audio.Renderer.Device +{ + /// + /// Represents a virtual device session used by IAudioDevice. + /// + public class VirtualDeviceSession + { + /// + /// The associated to this session. + /// + public VirtualDevice Device { get; } + + /// + /// The user volume of this session. + /// + public float Volume { get; set; } + + /// + /// Create a new instance. + /// + /// The associated to this session. + public VirtualDeviceSession(VirtualDevice virtualDevice) + { + Device = virtualDevice; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs new file mode 100644 index 00000000..4ad70619 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs @@ -0,0 +1,81 @@ +using Ryujinx.Audio.Integration; +using System.Collections.Generic; + +namespace Ryujinx.Audio.Renderer.Device +{ + /// + /// Represent an instance containing a registry of . + /// + public class VirtualDeviceSessionRegistry + { + /// + /// The session registry, used to store the sessions of a given AppletResourceId. + /// + private readonly Dictionary _sessionsRegistry = new(); + + /// + /// The default . + /// + /// This is used when the USB device is the default one on older revision. +#pragma warning disable CA1822 // Mark member as static + public VirtualDevice DefaultDevice => VirtualDevice.Devices[0]; +#pragma warning restore CA1822 + + /// + /// The current active . + /// + // TODO: make this configurable + public VirtualDevice ActiveDevice { get; } + + public VirtualDeviceSessionRegistry(IHardwareDeviceDriver driver) + { + uint channelCount; + + if (driver.GetRealDeviceDriver().SupportsChannelCount(6)) + { + channelCount = 6; + } + else + { + channelCount = 2; + } + + ActiveDevice = new VirtualDevice("AudioTvOutput", channelCount, false); + } + + /// + /// Get the associated from an AppletResourceId. + /// + /// The AppletResourceId used. + /// The associated from an AppletResourceId. + public VirtualDeviceSession[] GetSessionByAppletResourceId(ulong resourceAppletId) + { + if (_sessionsRegistry.TryGetValue(resourceAppletId, out VirtualDeviceSession[] result)) + { + return result; + } + + result = CreateSessionsFromBehaviourContext(); + + _sessionsRegistry.Add(resourceAppletId, result); + + return result; + } + + /// + /// Create a new array of sessions for each . + /// + /// A new array of sessions for each . + private static VirtualDeviceSession[] CreateSessionsFromBehaviourContext() + { + VirtualDeviceSession[] virtualDeviceSession = new VirtualDeviceSession[VirtualDevice.Devices.Length]; + + for (int i = 0; i < virtualDeviceSession.Length; i++) + { + virtualDeviceSession[i] = new VirtualDeviceSession(VirtualDevice.Devices[i]); + } + + return virtualDeviceSession; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs new file mode 100644 index 00000000..5cb4509f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs @@ -0,0 +1,222 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Common.Logging; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class AdpcmHelper + { + private const int FixedPointPrecision = 11; + private const int SamplesPerFrame = 14; + private const int NibblesPerFrame = SamplesPerFrame + 2; + private const int BytesPerFrame = 8; +#pragma warning disable IDE0051 // Remove unused private member + private const int BitsPerFrame = BytesPerFrame * 8; +#pragma warning restore IDE0051 + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint GetAdpcmDataSize(int sampleCount) + { + Debug.Assert(sampleCount >= 0); + + int frames = sampleCount / SamplesPerFrame; + int extraSize = 0; + + if ((sampleCount % SamplesPerFrame) != 0) + { + extraSize = (sampleCount % SamplesPerFrame) / 2 + 1 + (sampleCount % 2); + } + + return (uint)(BytesPerFrame * frames + extraSize); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetAdpcmOffsetFromSampleOffset(int sampleOffset) + { + Debug.Assert(sampleOffset >= 0); + + return GetNibblesFromSampleCount(sampleOffset) / 2; + } + + public static int NibbleToSample(int nibble) + { + int frames = nibble / NibblesPerFrame; + int extraNibbles = nibble % NibblesPerFrame; + int samples = SamplesPerFrame * frames; + + return samples + extraNibbles - 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetNibblesFromSampleCount(int sampleCount) + { + byte headerSize = 0; + + if ((sampleCount % SamplesPerFrame) != 0) + { + headerSize = 2; + } + + return sampleCount % SamplesPerFrame + NibblesPerFrame * (sampleCount / SamplesPerFrame) + headerSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short Saturate(int value) + { + if (value > short.MaxValue) + { + value = short.MaxValue; + } + + if (value < short.MinValue) + { + value = short.MinValue; + } + + return (short)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short GetCoefficientAtIndex(ReadOnlySpan coefficients, int index) + { + if ((uint)index > (uint)coefficients.Length) + { + Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}"); + + return 0; + } + + return coefficients[index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Decode(Span output, ReadOnlySpan input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan coefficients, ref AdpcmLoopContext loopContext) + { + if (input.IsEmpty || endSampleOffset < startSampleOffset) + { + return 0; + } + + byte predScale = (byte)loopContext.PredScale; + byte scale = (byte)(predScale & 0xF); + byte coefficientIndex = (byte)((predScale >> 4) & 0xF); + short history0 = loopContext.History0; + short history1 = loopContext.History1; + short coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 0); + short coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1); + + int decodedCount = Math.Min(count, endSampleOffset - startSampleOffset - offset); + int nibbles = GetNibblesFromSampleCount(offset + startSampleOffset); + int remaining = decodedCount; + int outputBufferIndex = 0; + int inputIndex = 0; + + ReadOnlySpan targetInput; + + targetInput = input[(nibbles / 2)..]; + + while (remaining > 0) + { + int samplesCount; + + if (((uint)nibbles % NibblesPerFrame) == 0) + { + predScale = targetInput[inputIndex++]; + + scale = (byte)(predScale & 0xF); + + coefficientIndex = (byte)((predScale >> 4) & 0xF); + + coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2); + coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1); + + nibbles += 2; + + samplesCount = Math.Min(remaining, SamplesPerFrame); + } + else + { + samplesCount = 1; + } + + int scaleFixedPoint = FixedPointHelper.ToFixed(1.0f, FixedPointPrecision) << scale; + + if (samplesCount < SamplesPerFrame) + { + for (int i = 0; i < samplesCount; i++) + { + int value = targetInput[inputIndex]; + + int sample; + + if ((nibbles & 1) != 0) + { + sample = (value << 28) >> 28; + + inputIndex++; + } + else + { + sample = (value << 24) >> 28; + } + + nibbles++; + + int prediction = coefficient0 * history0 + coefficient1 * history1; + + sample = FixedPointHelper.RoundUpAndToInt(sample * scaleFixedPoint + prediction, FixedPointPrecision); + + short saturatedSample = Saturate(sample); + + history1 = history0; + history0 = saturatedSample; + + output[outputBufferIndex++] = saturatedSample; + + remaining--; + } + } + else + { + for (int i = 0; i < SamplesPerFrame / 2; i++) + { + int value = targetInput[inputIndex]; + + int sample0; + int sample1; + + sample0 = (value << 24) >> 28; + sample1 = (value << 28) >> 28; + + inputIndex++; + + int prediction0 = coefficient0 * history0 + coefficient1 * history1; + sample0 = FixedPointHelper.RoundUpAndToInt(sample0 * scaleFixedPoint + prediction0, FixedPointPrecision); + short saturatedSample0 = Saturate(sample0); + + int prediction1 = coefficient0 * saturatedSample0 + coefficient1 * history0; + sample1 = FixedPointHelper.RoundUpAndToInt(sample1 * scaleFixedPoint + prediction1, FixedPointPrecision); + short saturatedSample1 = Saturate(sample1); + + history1 = saturatedSample0; + history0 = saturatedSample1; + + output[outputBufferIndex++] = saturatedSample0; + output[outputBufferIndex++] = saturatedSample1; + } + + nibbles += SamplesPerFrame; + remaining -= SamplesPerFrame; + } + } + + loopContext.PredScale = predScale; + loopContext.History0 = history0; + loopContext.History1 = history1; + + return decodedCount; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs b/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs new file mode 100644 index 00000000..3e11df05 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs @@ -0,0 +1,247 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using System; +using System.Threading; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public class AudioProcessor : IDisposable + { + private const int MaxBufferedFrames = 5; + private const int TargetBufferedFrames = 3; + + private enum MailboxMessage : uint + { + Start, + Stop, + RenderStart, + RenderEnd, + } + + private class RendererSession + { + public CommandList CommandList; + public int RenderingLimit; + public ulong AppletResourceId; + } + + private Mailbox _mailbox; + private RendererSession[] _sessionCommandList; + private Thread _workerThread; + + public IHardwareDevice[] OutputDevices { get; private set; } + + private long _lastTime; + private long _playbackEnds; + private readonly ManualResetEvent _event; + + private ManualResetEvent _pauseEvent; + + public AudioProcessor() + { + _event = new ManualResetEvent(false); + } + + private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver) + { + // Get the real device driver (In case the compat layer is on top of it). + deviceDriver = deviceDriver.GetRealDeviceDriver(); + + if (deviceDriver.SupportsChannelCount(6)) + { + return 6; + } + + // NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible. + return 2; + } + + public void Start(IHardwareDeviceDriver deviceDriver) + { + OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax]; + + uint channelCount = GetHardwareChannelCount(deviceDriver); + + for (int i = 0; i < OutputDevices.Length; i++) + { + // TODO: Don't hardcode sample rate. + OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate); + } + + _mailbox = new Mailbox(); + _sessionCommandList = new RendererSession[Constants.AudioRendererSessionCountMax]; + _event.Reset(); + _lastTime = PerformanceCounter.ElapsedNanoseconds; + _pauseEvent = deviceDriver.GetPauseEvent(); + + StartThread(); + + _mailbox.SendMessage(MailboxMessage.Start); + + if (_mailbox.ReceiveResponse() != MailboxMessage.Start) + { + throw new InvalidOperationException("Audio Processor Start response was invalid!"); + } + } + + public void Stop() + { + _mailbox.SendMessage(MailboxMessage.Stop); + + if (_mailbox.ReceiveResponse() != MailboxMessage.Stop) + { + throw new InvalidOperationException("Audio Processor Stop response was invalid!"); + } + + foreach (IHardwareDevice device in OutputDevices) + { + device.Dispose(); + } + } + + public void Send(int sessionId, CommandList commands, int renderingLimit, ulong appletResourceId) + { + _sessionCommandList[sessionId] = new RendererSession + { + CommandList = commands, + RenderingLimit = renderingLimit, + AppletResourceId = appletResourceId, + }; + } + + public bool HasRemainingCommands(int sessionId) + { + return _sessionCommandList[sessionId] != null; + } + + public void Signal() + { + _mailbox.SendMessage(MailboxMessage.RenderStart); + } + + public void Wait() + { + if (_mailbox.ReceiveResponse() != MailboxMessage.RenderEnd) + { + throw new InvalidOperationException("Audio Processor Wait response was invalid!"); + } + + long increment = Constants.AudioProcessorMaxUpdateTimeTarget; + + long timeNow = PerformanceCounter.ElapsedNanoseconds; + + if (timeNow > _playbackEnds) + { + // Playback has restarted. + _playbackEnds = timeNow; + } + + _playbackEnds += increment; + + // The number of frames we are behind where the timer says we should be. + long framesBehind = (timeNow - _lastTime) / increment; + + // The number of frames yet to play on the backend. + long bufferedFrames = (_playbackEnds - timeNow) / increment + framesBehind; + + // If we've entered a situation where a lot of buffers will be queued on the backend, + // Skip some audio frames so that playback can catch up. + if (bufferedFrames > MaxBufferedFrames) + { + // Skip a few frames so that we're not too far behind. (the target number of frames) + _lastTime += increment * (bufferedFrames - TargetBufferedFrames); + } + + while (timeNow < _lastTime + increment) + { + _event.WaitOne(1); + + timeNow = PerformanceCounter.ElapsedNanoseconds; + } + + _lastTime += increment; + } + + private void StartThread() + { + _workerThread = new Thread(Work) + { + Name = "AudioProcessor.Worker", + }; + + _workerThread.Start(); + } + + private void Work() + { + if (_mailbox.ReceiveMessage() != MailboxMessage.Start) + { + throw new InvalidOperationException("Audio Processor Start message was invalid!"); + } + + _mailbox.SendResponse(MailboxMessage.Start); + _mailbox.SendResponse(MailboxMessage.RenderEnd); + + Logger.Info?.Print(LogClass.AudioRenderer, "Starting audio processor"); + + while (true) + { + _pauseEvent?.WaitOne(); + + MailboxMessage message = _mailbox.ReceiveMessage(); + + if (message == MailboxMessage.Stop) + { + break; + } + + if (message == MailboxMessage.RenderStart) + { + long startTicks = PerformanceCounter.ElapsedNanoseconds; + + for (int i = 0; i < _sessionCommandList.Length; i++) + { + if (_sessionCommandList[i] != null) + { + _sessionCommandList[i].CommandList.Process(OutputDevices[i]); + _sessionCommandList[i].CommandList.Dispose(); + _sessionCommandList[i] = null; + } + } + + long endTicks = PerformanceCounter.ElapsedNanoseconds; + + long elapsedTime = endTicks - startTicks; + + if (Constants.AudioProcessorMaxUpdateTime < elapsedTime) + { + Logger.Debug?.Print(LogClass.AudioRenderer, $"DSP too slow (exceeded by {elapsedTime - Constants.AudioProcessorMaxUpdateTime}ns)"); + } + + _mailbox.SendResponse(MailboxMessage.RenderEnd); + } + } + + Logger.Info?.Print(LogClass.AudioRenderer, "Stopping audio processor"); + _mailbox.SendResponse(MailboxMessage.Stop); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _event.Dispose(); + _mailbox?.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs new file mode 100644 index 00000000..31f614d6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs @@ -0,0 +1,307 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class BiquadFilterHelper + { + private const int FixedPointPrecisionForParameter = 14; + + /// + /// Apply a single biquad filter. + /// + /// This is implemented with a direct form 2. + /// The biquad filter parameter + /// The biquad filter state + /// The output buffer to write the result + /// The input buffer to read the samples from + /// The count of samples to process + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ProcessBiquadFilter( + ref BiquadFilterParameter parameter, + ref BiquadFilterState state, + Span outputBuffer, + ReadOnlySpan inputBuffer, + uint sampleCount) + { + float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); + float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); + float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter); + + float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter); + float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter); + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i]; + float output = input * a0 + state.State0; + + state.State0 = input * a1 + output * b1 + state.State1; + state.State1 = input * a2 + output * b2; + + outputBuffer[i] = output; + } + } + + /// + /// Apply a single biquad filter and mix the result into the output buffer. + /// + /// This is implemented with a direct form 1. + /// The biquad filter parameter + /// The biquad filter state + /// The output buffer to write the result + /// The input buffer to read the samples from + /// The count of samples to process + /// Mix volume + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ProcessBiquadFilterAndMix( + ref BiquadFilterParameter parameter, + ref BiquadFilterState state, + Span outputBuffer, + ReadOnlySpan inputBuffer, + uint sampleCount, + float volume) + { + float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); + float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); + float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter); + + float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter); + float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter); + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i]; + float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2; + + state.State1 = state.State0; + state.State0 = input; + state.State3 = state.State2; + state.State2 = output; + + outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume); + } + } + + /// + /// Apply a single biquad filter and mix the result into the output buffer with volume ramp. + /// + /// This is implemented with a direct form 1. + /// The biquad filter parameter + /// The biquad filter state + /// The output buffer to write the result + /// The input buffer to read the samples from + /// The count of samples to process + /// Initial mix volume + /// Volume increment step + /// Last filtered sample value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ProcessBiquadFilterAndMixRamp( + ref BiquadFilterParameter parameter, + ref BiquadFilterState state, + Span outputBuffer, + ReadOnlySpan inputBuffer, + uint sampleCount, + float volume, + float ramp) + { + float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); + float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); + float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter); + + float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter); + float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter); + + float mixState = 0f; + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i]; + float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2; + + state.State1 = state.State0; + state.State0 = input; + state.State3 = state.State2; + state.State2 = output; + + mixState = FloatingPointHelper.MultiplyRoundUp(output, volume); + + outputBuffer[i] += mixState; + volume += ramp; + } + + return mixState; + } + + /// + /// Apply multiple biquad filter. + /// + /// This is implemented with a direct form 1. + /// The biquad filter parameter + /// The biquad filter state + /// The output buffer to write the result + /// The input buffer to read the samples from + /// The count of samples to process + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ProcessBiquadFilter( + ReadOnlySpan parameters, + Span states, + Span outputBuffer, + ReadOnlySpan inputBuffer, + uint sampleCount) + { + for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++) + { + BiquadFilterParameter parameter = parameters[stageIndex]; + + ref BiquadFilterState state = ref states[stageIndex]; + + float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); + float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); + float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter); + + float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter); + float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter); + + for (int i = 0; i < sampleCount; i++) + { + float input = stageIndex != 0 ? outputBuffer[i] : inputBuffer[i]; + float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2; + + state.State1 = state.State0; + state.State0 = input; + state.State3 = state.State2; + state.State2 = output; + + outputBuffer[i] = output; + } + } + } + + /// + /// Apply double biquad filter and mix the result into the output buffer. + /// + /// This is implemented with a direct form 1. + /// The biquad filter parameter + /// The biquad filter state + /// The output buffer to write the result + /// The input buffer to read the samples from + /// The count of samples to process + /// Mix volume + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ProcessDoubleBiquadFilterAndMix( + ref BiquadFilterParameter parameter0, + ref BiquadFilterParameter parameter1, + ref BiquadFilterState state0, + ref BiquadFilterState state1, + Span outputBuffer, + ReadOnlySpan inputBuffer, + uint sampleCount, + float volume) + { + float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter); + float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter); + float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter); + + float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter); + float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter); + + float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter); + float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter); + float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter); + + float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter); + float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter); + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i]; + float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20; + + state0.State1 = state0.State0; + state0.State0 = input; + state0.State3 = state0.State2; + state0.State2 = output; + + input = output; + output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21; + + state1.State1 = state1.State0; + state1.State0 = input; + state1.State3 = state1.State2; + state1.State2 = output; + + outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume); + } + } + + /// + /// Apply double biquad filter and mix the result into the output buffer with volume ramp. + /// + /// This is implemented with a direct form 1. + /// The biquad filter parameter + /// The biquad filter state + /// The output buffer to write the result + /// The input buffer to read the samples from + /// The count of samples to process + /// Initial mix volume + /// Volume increment step + /// Last filtered sample value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ProcessDoubleBiquadFilterAndMixRamp( + ref BiquadFilterParameter parameter0, + ref BiquadFilterParameter parameter1, + ref BiquadFilterState state0, + ref BiquadFilterState state1, + Span outputBuffer, + ReadOnlySpan inputBuffer, + uint sampleCount, + float volume, + float ramp) + { + float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter); + float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter); + float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter); + + float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter); + float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter); + + float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter); + float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter); + float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter); + + float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter); + float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter); + + float mixState = 0f; + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i]; + float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20; + + state0.State1 = state0.State0; + state0.State0 = input; + state0.State3 = state0.State2; + state0.State2 = output; + + input = output; + output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21; + + state1.State1 = state1.State0; + state1.State0 = input; + state1.State3 = state1.State2; + state1.State2 = output; + + mixState = FloatingPointHelper.MultiplyRoundUp(output, volume); + + outputBuffer[i] += mixState; + volume += ramp; + } + + return mixState; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs new file mode 100644 index 00000000..51a12b4e --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs @@ -0,0 +1,77 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Voice; +using System; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; +using WaveBuffer = Ryujinx.Audio.Renderer.Common.WaveBuffer; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class AdpcmDataSourceCommandVersion1 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.AdpcmDataSourceVersion1; + + public uint EstimatedProcessingTime { get; set; } + + public ushort OutputBufferIndex { get; } + public uint SampleRate { get; } + + public float Pitch { get; } + + public WaveBuffer[] WaveBuffers { get; } + + public Memory State { get; } + + public ulong AdpcmParameter { get; } + public ulong AdpcmParameterSize { get; } + + public DecodingBehaviour DecodingBehaviour { get; } + + public AdpcmDataSourceCommandVersion1(ref VoiceState serverState, Memory state, ushort outputBufferIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + OutputBufferIndex = outputBufferIndex; + SampleRate = serverState.SampleRate; + Pitch = serverState.Pitch; + + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; + + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; + + WaveBuffers[i] = voiceWaveBuffer.ToCommon(1); + } + + AdpcmParameter = serverState.DataSourceStateAddressInfo.GetReference(true); + AdpcmParameterSize = serverState.DataSourceStateAddressInfo.Size; + State = state; + DecodingBehaviour = serverState.DecodingBehaviour; + } + + public void Process(CommandList context) + { + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + DataSourceHelper.WaveBufferInformation info = new() + { + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat.Adpcm, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + ExtraParameter = AdpcmParameter, + ExtraParameterSize = AdpcmParameterSize, + ChannelIndex = 0, + ChannelCount = 1, + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs new file mode 100644 index 00000000..73d66dcf --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs @@ -0,0 +1,182 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Dsp.State.AuxiliaryBufferHeader; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class AuxiliaryBufferCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.AuxiliaryBuffer; + + public uint EstimatedProcessingTime { get; set; } + + public uint InputBufferIndex { get; } + public uint OutputBufferIndex { get; } + + public AuxiliaryBufferAddresses BufferInfo { get; } + + public CpuAddress InputBuffer { get; } + public CpuAddress OutputBuffer { get; } + public uint CountMax { get; } + public uint UpdateCount { get; } + public uint WriteOffset { get; } + + public bool IsEffectEnabled { get; } + + public AuxiliaryBufferCommand( + uint bufferOffset, + byte inputBufferOffset, + byte outputBufferOffset, + ref AuxiliaryBufferAddresses sendBufferInfo, + bool isEnabled, + uint countMax, + CpuAddress outputBuffer, + CpuAddress inputBuffer, + uint updateCount, + uint writeOffset, + int nodeId) + { + Enabled = true; + NodeId = nodeId; + InputBufferIndex = bufferOffset + inputBufferOffset; + OutputBufferIndex = bufferOffset + outputBufferOffset; + BufferInfo = sendBufferInfo; + InputBuffer = inputBuffer; + OutputBuffer = outputBuffer; + CountMax = countMax; + UpdateCount = updateCount; + WriteOffset = writeOffset; + IsEffectEnabled = isEnabled; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private uint Read(IVirtualMemoryManager memoryManager, ulong bufferAddress, uint countMax, Span outBuffer, uint count, uint readOffset, uint updateCount) + { + if (countMax == 0 || bufferAddress == 0) + { + return 0; + } + + uint targetReadOffset = readOffset + AuxiliaryBufferInfo.GetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo); + + if (targetReadOffset > countMax) + { + return 0; + } + + uint remaining = count; + + uint outBufferOffset = 0; + + while (remaining != 0) + { + uint countToWrite = Math.Min(countMax - targetReadOffset, remaining); + + memoryManager.Read(bufferAddress + targetReadOffset * sizeof(int), MemoryMarshal.Cast(outBuffer.Slice((int)outBufferOffset, (int)countToWrite))); + + targetReadOffset = (targetReadOffset + countToWrite) % countMax; + remaining -= countToWrite; + outBufferOffset += countToWrite; + } + + if (updateCount != 0) + { + uint newReadOffset = (AuxiliaryBufferInfo.GetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo) + updateCount) % countMax; + + AuxiliaryBufferInfo.SetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo, newReadOffset); + } + + return count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private uint Write(IVirtualMemoryManager memoryManager, ulong outBufferAddress, uint countMax, ReadOnlySpan buffer, uint count, uint writeOffset, uint updateCount) + { + if (countMax == 0 || outBufferAddress == 0) + { + return 0; + } + + uint targetWriteOffset = writeOffset + AuxiliaryBufferInfo.GetWriteOffset(memoryManager, BufferInfo.SendBufferInfo); + + if (targetWriteOffset > countMax) + { + return 0; + } + + uint remaining = count; + + uint inBufferOffset = 0; + + while (remaining != 0) + { + uint countToWrite = Math.Min(countMax - targetWriteOffset, remaining); + + memoryManager.Write(outBufferAddress + targetWriteOffset * sizeof(int), MemoryMarshal.Cast(buffer.Slice((int)inBufferOffset, (int)countToWrite))); + + targetWriteOffset = (targetWriteOffset + countToWrite) % countMax; + remaining -= countToWrite; + inBufferOffset += countToWrite; + } + + if (updateCount != 0) + { + uint newWriteOffset = (AuxiliaryBufferInfo.GetWriteOffset(memoryManager, BufferInfo.SendBufferInfo) + updateCount) % countMax; + + AuxiliaryBufferInfo.SetWriteOffset(memoryManager, BufferInfo.SendBufferInfo, newWriteOffset); + } + + return count; + } + + public void Process(CommandList context) + { + Span inputBuffer = context.GetBuffer((int)InputBufferIndex); + Span outputBuffer = context.GetBuffer((int)OutputBufferIndex); + + if (IsEffectEnabled) + { + Span inputBufferInt = MemoryMarshal.Cast(inputBuffer); + Span outputBufferInt = MemoryMarshal.Cast(outputBuffer); + + // Convert input data to the target format for user (int) + DataSourceHelper.ToInt(inputBufferInt, inputBuffer, inputBuffer.Length); + + // Send the input to the user + Write(context.MemoryManager, OutputBuffer, CountMax, inputBufferInt, context.SampleCount, WriteOffset, UpdateCount); + + // Convert back to float just in case it's reused + DataSourceHelper.ToFloat(inputBuffer, inputBufferInt, inputBuffer.Length); + + // Retrieve the input from user + uint readResult = Read(context.MemoryManager, InputBuffer, CountMax, outputBufferInt, context.SampleCount, WriteOffset, UpdateCount); + + // Convert the outputBuffer back to the target format of the renderer (float) + DataSourceHelper.ToFloat(outputBuffer, outputBufferInt, outputBuffer.Length); + + if (readResult != context.SampleCount) + { + outputBuffer[(int)readResult..(int)context.SampleCount].Clear(); + } + } + else + { + AuxiliaryBufferInfo.Reset(context.MemoryManager, BufferInfo.SendBufferInfo); + AuxiliaryBufferInfo.Reset(context.MemoryManager, BufferInfo.ReturnBufferInfo); + + if (InputBufferIndex != OutputBufferIndex) + { + inputBuffer.CopyTo(outputBuffer); + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs new file mode 100644 index 00000000..106fc035 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs @@ -0,0 +1,123 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class BiquadFilterAndMixCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.BiquadFilterAndMix; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + private BiquadFilterParameter _parameter; + + public Memory BiquadFilterState { get; } + public Memory PreviousBiquadFilterState { get; } + + public Memory State { get; } + + public int LastSampleIndex { get; } + + public float Volume0 { get; } + public float Volume1 { get; } + + public bool NeedInitialization { get; } + public bool HasVolumeRamp { get; } + public bool IsFirstMixBuffer { get; } + + public BiquadFilterAndMixCommand( + float volume0, + float volume1, + uint inputBufferIndex, + uint outputBufferIndex, + int lastSampleIndex, + Memory state, + ref BiquadFilterParameter filter, + Memory biquadFilterState, + Memory previousBiquadFilterState, + bool needInitialization, + bool hasVolumeRamp, + bool isFirstMixBuffer, + int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)inputBufferIndex; + OutputBufferIndex = (ushort)outputBufferIndex; + + _parameter = filter; + BiquadFilterState = biquadFilterState; + PreviousBiquadFilterState = previousBiquadFilterState; + + State = state; + LastSampleIndex = lastSampleIndex; + + Volume0 = volume0; + Volume1 = volume1; + + NeedInitialization = needInitialization; + HasVolumeRamp = hasVolumeRamp; + IsFirstMixBuffer = isFirstMixBuffer; + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + if (NeedInitialization) + { + // If there is no previous state, initialize to zero. + + BiquadFilterState.Span[0] = new BiquadFilterState(); + } + else if (IsFirstMixBuffer) + { + // This is the first buffer, set previous state to current state. + + PreviousBiquadFilterState.Span[0] = BiquadFilterState.Span[0]; + } + else + { + // Rewind the current state by copying back the previous state. + + BiquadFilterState.Span[0] = PreviousBiquadFilterState.Span[0]; + } + + if (HasVolumeRamp) + { + float volume = Volume0; + float ramp = (Volume1 - Volume0) / (int)context.SampleCount; + + State.Span[0].LastSamples[LastSampleIndex] = BiquadFilterHelper.ProcessBiquadFilterAndMixRamp( + ref _parameter, + ref BiquadFilterState.Span[0], + outputBuffer, + inputBuffer, + context.SampleCount, + volume, + ramp); + } + else + { + BiquadFilterHelper.ProcessBiquadFilterAndMix( + ref _parameter, + ref BiquadFilterState.Span[0], + outputBuffer, + inputBuffer, + context.SampleCount, + Volume1); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs new file mode 100644 index 00000000..ac1e581f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs @@ -0,0 +1,58 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class BiquadFilterCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.BiquadFilter; + + public uint EstimatedProcessingTime { get; set; } + + public Memory BiquadFilterState { get; } + public int InputBufferIndex { get; } + public int OutputBufferIndex { get; } + public bool NeedInitialization { get; } + + private BiquadFilterParameter _parameter; + + public BiquadFilterCommand( + int baseIndex, + ref BiquadFilterParameter filter, + Memory biquadFilterStateMemory, + int inputBufferOffset, + int outputBufferOffset, + bool needInitialization, + int nodeId) + { + _parameter = filter; + BiquadFilterState = biquadFilterStateMemory; + InputBufferIndex = baseIndex + inputBufferOffset; + OutputBufferIndex = baseIndex + outputBufferOffset; + NeedInitialization = needInitialization; + + Enabled = true; + NodeId = nodeId; + } + + public void Process(CommandList context) + { + ref BiquadFilterState state = ref BiquadFilterState.Span[0]; + + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + if (NeedInitialization) + { + state = new BiquadFilterState(); + } + + BiquadFilterHelper.ProcessBiquadFilter(ref _parameter, ref state, outputBuffer, inputBuffer, context.SampleCount); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs new file mode 100644 index 00000000..01bff1e7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs @@ -0,0 +1,136 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Dsp.State.AuxiliaryBufferHeader; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CaptureBufferCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.CaptureBuffer; + + public uint EstimatedProcessingTime { get; set; } + + public uint InputBufferIndex { get; } + + public ulong CpuBufferInfoAddress { get; } + public ulong DspBufferInfoAddress { get; } + + public CpuAddress OutputBuffer { get; } + public uint CountMax { get; } + public uint UpdateCount { get; } + public uint WriteOffset { get; } + + public bool IsEffectEnabled { get; } + + public CaptureBufferCommand(uint bufferOffset, byte inputBufferOffset, ulong sendBufferInfo, bool isEnabled, + uint countMax, CpuAddress outputBuffer, uint updateCount, uint writeOffset, int nodeId) + { + Enabled = true; + NodeId = nodeId; + InputBufferIndex = bufferOffset + inputBufferOffset; + CpuBufferInfoAddress = sendBufferInfo; + DspBufferInfoAddress = sendBufferInfo + (ulong)Unsafe.SizeOf(); + OutputBuffer = outputBuffer; + CountMax = countMax; + UpdateCount = updateCount; + WriteOffset = writeOffset; + IsEffectEnabled = isEnabled; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private uint Write(IVirtualMemoryManager memoryManager, ulong outBufferAddress, uint countMax, ReadOnlySpan buffer, uint count, uint writeOffset, uint updateCount) + { + if (countMax == 0 || outBufferAddress == 0) + { + return 0; + } + + uint targetWriteOffset = writeOffset + AuxiliaryBufferInfo.GetWriteOffset(memoryManager, DspBufferInfoAddress); + + if (targetWriteOffset > countMax) + { + return 0; + } + + uint remaining = count; + + uint inBufferOffset = 0; + + while (remaining != 0) + { + uint countToWrite = Math.Min(countMax - targetWriteOffset, remaining); + + memoryManager.Write(outBufferAddress + targetWriteOffset * sizeof(int), MemoryMarshal.Cast(buffer.Slice((int)inBufferOffset, (int)countToWrite))); + + targetWriteOffset = (targetWriteOffset + countToWrite) % countMax; + remaining -= countToWrite; + inBufferOffset += countToWrite; + } + + if (updateCount != 0) + { + uint dspTotalSampleCount = AuxiliaryBufferInfo.GetTotalSampleCount(memoryManager, DspBufferInfoAddress); + uint cpuTotalSampleCount = AuxiliaryBufferInfo.GetTotalSampleCount(memoryManager, CpuBufferInfoAddress); + + uint totalSampleCountDiff = dspTotalSampleCount - cpuTotalSampleCount; + + if (totalSampleCountDiff >= countMax) + { + uint dspLostSampleCount = AuxiliaryBufferInfo.GetLostSampleCount(memoryManager, DspBufferInfoAddress); + uint cpuLostSampleCount = AuxiliaryBufferInfo.GetLostSampleCount(memoryManager, CpuBufferInfoAddress); + + uint lostSampleCountDiff = dspLostSampleCount - cpuLostSampleCount; + uint newLostSampleCount = lostSampleCountDiff + updateCount; + + if (lostSampleCountDiff > newLostSampleCount) + { + newLostSampleCount = cpuLostSampleCount - 1; + } + + AuxiliaryBufferInfo.SetLostSampleCount(memoryManager, DspBufferInfoAddress, newLostSampleCount); + } + + uint newWriteOffset = (AuxiliaryBufferInfo.GetWriteOffset(memoryManager, DspBufferInfoAddress) + updateCount) % countMax; + + AuxiliaryBufferInfo.SetWriteOffset(memoryManager, DspBufferInfoAddress, newWriteOffset); + + uint newTotalSampleCount = totalSampleCountDiff + newWriteOffset; + + AuxiliaryBufferInfo.SetTotalSampleCount(memoryManager, DspBufferInfoAddress, newTotalSampleCount); + } + + return count; + } + + public void Process(CommandList context) + { + Span inputBuffer = context.GetBuffer((int)InputBufferIndex); + + if (IsEffectEnabled) + { + Span inputBufferInt = MemoryMarshal.Cast(inputBuffer); + + // Convert input data to the target format for user (int) + DataSourceHelper.ToInt(inputBufferInt, inputBuffer, inputBuffer.Length); + + // Send the input to the user + Write(context.MemoryManager, OutputBuffer, CountMax, inputBufferInt, context.SampleCount, WriteOffset, UpdateCount); + + // Convert back to float + DataSourceHelper.ToFloat(inputBuffer, inputBufferInt, inputBuffer.Length); + } + else + { + AuxiliaryBufferInfo.Reset(context.MemoryManager, DspBufferInfoAddress); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs new file mode 100644 index 00000000..59ef7093 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs @@ -0,0 +1,76 @@ +using Ryujinx.Audio.Renderer.Parameter.Sink; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CircularBufferSinkCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.CircularBufferSink; + + public uint EstimatedProcessingTime { get; set; } + + public ushort[] Input { get; } + public uint InputCount { get; } + + public ulong CircularBuffer { get; } + public ulong CircularBufferSize { get; } + public ulong CurrentOffset { get; } + + public CircularBufferSinkCommand(uint bufferOffset, ref CircularBufferParameter parameter, ref AddressInfo circularBufferAddressInfo, uint currentOffset, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + Input = new ushort[Constants.ChannelCountMax]; + InputCount = parameter.InputCount; + + for (int i = 0; i < InputCount; i++) + { + Input[i] = (ushort)(bufferOffset + parameter.Input[i]); + } + + CircularBuffer = circularBufferAddressInfo.GetReference(true); + CircularBufferSize = parameter.BufferSize; + CurrentOffset = currentOffset; + + Debug.Assert(CircularBuffer != 0); + } + + public void Process(CommandList context) + { + const int TargetChannelCount = 2; + + ulong currentOffset = CurrentOffset; + + if (CircularBufferSize > 0) + { + for (int i = 0; i < InputCount; i++) + { + unsafe + { + float* inputBuffer = (float*)context.GetBufferPointer(Input[i]); + + ulong targetOffset = CircularBuffer + currentOffset; + + for (int y = 0; y < context.SampleCount; y++) + { + context.MemoryManager.Write(targetOffset + (ulong)y * TargetChannelCount, PcmHelper.Saturate(inputBuffer[y])); + } + + currentOffset += context.SampleCount * TargetChannelCount; + + if (currentOffset >= CircularBufferSize) + { + currentOffset = 0; + } + } + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs new file mode 100644 index 00000000..f0f85b0b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class ClearMixBufferCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.ClearMixBuffer; + + public uint EstimatedProcessingTime { get; set; } + + public ClearMixBufferCommand(int nodeId) + { + Enabled = true; + NodeId = nodeId; + } + + public void Process(CommandList context) + { + context.ClearBuffers(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs new file mode 100644 index 00000000..3fe106dd --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs @@ -0,0 +1,156 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CommandList : IDisposable + { + public ulong StartTime { get; private set; } + public ulong EndTime { get; private set; } + public uint SampleCount { get; } + public uint SampleRate { get; } + + public Memory Buffers { get; } + public uint BufferCount { get; } + + public List Commands { get; } + + public IVirtualMemoryManager MemoryManager { get; } + + public IHardwareDevice OutputDevice { get; private set; } + + private readonly int _sampleCount; + private readonly int _buffersEntryCount; + private readonly MemoryHandle _buffersMemoryHandle; + + public CommandList(AudioRenderSystem renderSystem) : this(renderSystem.MemoryManager, + renderSystem.GetMixBuffer(), + renderSystem.GetSampleCount(), + renderSystem.GetSampleRate(), + renderSystem.GetMixBufferCount(), + renderSystem.GetVoiceChannelCountMax()) + { + } + + public CommandList(IVirtualMemoryManager memoryManager, Memory mixBuffer, uint sampleCount, uint sampleRate, uint mixBufferCount, uint voiceChannelCountMax) + { + SampleCount = sampleCount; + _sampleCount = (int)SampleCount; + SampleRate = sampleRate; + BufferCount = mixBufferCount + voiceChannelCountMax; + Buffers = mixBuffer; + Commands = new List(); + MemoryManager = memoryManager; + + _buffersEntryCount = Buffers.Length; + _buffersMemoryHandle = Buffers.Pin(); + } + + public void AddCommand(ICommand command) + { + Commands.Add(command); + } + + public void AddCommand(T command) where T : unmanaged, ICommand + { + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe IntPtr GetBufferPointer(int index) + { + if (index >= 0 && index < _buffersEntryCount) + { + return (IntPtr)((float*)_buffersMemoryHandle.Pointer + index * _sampleCount); + } + + throw new ArgumentOutOfRangeException(nameof(index), index, null); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ClearBuffer(int index) + { + Unsafe.InitBlock((void*)GetBufferPointer(index), 0, SampleCount * sizeof(float)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ClearBuffers() + { + Unsafe.InitBlock(_buffersMemoryHandle.Pointer, 0, (uint)_buffersEntryCount * sizeof(float)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void CopyBuffer(int outputBufferIndex, int inputBufferIndex) + { + Unsafe.CopyBlock((void*)GetBufferPointer(outputBufferIndex), (void*)GetBufferPointer(inputBufferIndex), SampleCount * sizeof(float)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetBuffer(int index) + { + if (index < 0 || index >= _buffersEntryCount) + { + return Span.Empty; + } + + unsafe + { + return new Span((float*)_buffersMemoryHandle.Pointer + index * _sampleCount, _sampleCount); + } + } + + public ulong GetTimeElapsedSinceDspStartedProcessing() + { + return (ulong)PerformanceCounter.ElapsedNanoseconds - StartTime; + } + + public void Process(IHardwareDevice outputDevice) + { + OutputDevice = outputDevice; + + StartTime = (ulong)PerformanceCounter.ElapsedNanoseconds; + + foreach (ICommand command in Commands) + { + if (command.Enabled) + { + bool shouldMeter = command.ShouldMeter(); + + long startTime = 0; + + if (shouldMeter) + { + startTime = PerformanceCounter.ElapsedNanoseconds; + } + + command.Process(this); + + if (shouldMeter) + { + ulong effectiveElapsedTime = (ulong)(PerformanceCounter.ElapsedNanoseconds - startTime); + + if (effectiveElapsedTime > command.EstimatedProcessingTime) + { + Logger.Warning?.Print(LogClass.AudioRenderer, $"Command {command.GetType().Name} took {effectiveElapsedTime}ns (expected {command.EstimatedProcessingTime}ns)"); + } + } + } + } + + EndTime = (ulong)PerformanceCounter.ElapsedNanoseconds; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _buffersMemoryHandle.Dispose(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs new file mode 100644 index 00000000..de5c0ea2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs @@ -0,0 +1,39 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public enum CommandType : byte + { + Invalid, + PcmInt16DataSourceVersion1, + PcmInt16DataSourceVersion2, + PcmFloatDataSourceVersion1, + PcmFloatDataSourceVersion2, + AdpcmDataSourceVersion1, + AdpcmDataSourceVersion2, + Volume, + VolumeRamp, + BiquadFilter, + Mix, + MixRamp, + MixRampGrouped, + DepopPrepare, + DepopForMixBuffers, + Delay, + Upsample, + DownMixSurroundToStereo, + AuxiliaryBuffer, + DeviceSink, + CircularBufferSink, + Reverb, + Reverb3d, + Performance, + ClearMixBuffer, + CopyMixBuffer, + LimiterVersion1, + LimiterVersion2, + MultiTapBiquadFilter, + CaptureBuffer, + Compressor, + BiquadFilterAndMix, + MultiTapBiquadFilterAndMix, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs new file mode 100644 index 00000000..09f415d2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs @@ -0,0 +1,174 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Effect; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CompressorCommand : ICommand + { + private const int FixedPointPrecision = 15; + + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Compressor; + + public uint EstimatedProcessingTime { get; set; } + + public CompressorParameter Parameter => _parameter; + public Memory State { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsEffectEnabled { get; } + + private CompressorParameter _parameter; + + public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory state, bool isEnabled, int nodeId) + { + Enabled = true; + NodeId = nodeId; + _parameter = parameter; + State = state; + + IsEffectEnabled = isEnabled; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < _parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]); + } + } + + public void Process(CommandList context) + { + ref CompressorState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (_parameter.Status == UsageState.Invalid) + { + state = new CompressorState(ref _parameter); + } + else if (_parameter.Status == UsageState.New) + { + state.UpdateParameter(ref _parameter); + } + } + + ProcessCompressor(context, ref state); + } + + private unsafe void ProcessCompressor(CommandList context, ref CompressorState state) + { + Debug.Assert(_parameter.IsChannelCountValid()); + + if (IsEffectEnabled && _parameter.IsChannelCountValid()) + { + Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span channelInput = stackalloc float[Parameter.ChannelCount]; + ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage; + float unknown4 = state.Unknown4; + ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage; + float previousCompressionEmaAlpha = state.PreviousCompressionEmaAlpha; + + for (int i = 0; i < _parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]); + } + + for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++) + { + for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++) + { + channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex); + } + + float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain); + float y = FloatingPointHelper.Log10(newMean) * 10.0f; + float z = 1.0f; + + bool unknown10OutOfRange = y >= state.Unknown10; + + if (newMean < 1.0e-10f) + { + y = -100.0f; + + unknown10OutOfRange = state.Unknown10 <= -100.0f; + } + + if (unknown10OutOfRange) + { + float tmpGain; + + if (y >= state.Unknown14) + { + tmpGain = ((1.0f / Parameter.Ratio) - 1.0f) * (y - Parameter.Threshold); + } + else + { + tmpGain = (y - state.Unknown10) * ((y - state.Unknown10) * -state.CompressorGainReduction); + } + + z = FloatingPointHelper.DecibelToLinear(tmpGain); + } + + float unknown4New = z; + float compressionEmaAlpha; + + if ((unknown4 - z) <= 0.08f) + { + compressionEmaAlpha = Parameter.ReleaseCoefficient; + + if ((unknown4 - z) >= -0.08f) + { + if (MathF.Abs(compressionGainAverage.Read() - z) >= 0.001f) + { + unknown4New = unknown4; + } + + compressionEmaAlpha = previousCompressionEmaAlpha; + } + } + else + { + compressionEmaAlpha = Parameter.AttackCoefficient; + } + + float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha); + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + *((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain; + } + + unknown4 = unknown4New; + previousCompressionEmaAlpha = compressionEmaAlpha; + } + + state.InputMovingAverage = inputMovingAverage; + state.Unknown4 = unknown4; + state.CompressionGainAverage = compressionGainAverage; + state.PreviousCompressionEmaAlpha = previousCompressionEmaAlpha; + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]); + } + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs new file mode 100644 index 00000000..3f6aa839 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CopyMixBufferCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.CopyMixBuffer; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public CopyMixBufferCommand(uint inputBufferIndex, uint outputBufferIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)inputBufferIndex; + OutputBufferIndex = (ushort)outputBufferIndex; + } + + public void Process(CommandList context) + { + context.CopyBuffer(OutputBufferIndex, InputBufferIndex); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs new file mode 100644 index 00000000..e82d403b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs @@ -0,0 +1,106 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Voice; +using System; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; +using WaveBuffer = Ryujinx.Audio.Renderer.Common.WaveBuffer; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DataSourceVersion2Command : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType { get; } + + public uint EstimatedProcessingTime { get; set; } + + public ushort OutputBufferIndex { get; } + public uint SampleRate { get; } + + public float Pitch { get; } + + public WaveBuffer[] WaveBuffers { get; } + + public Memory State { get; } + + public ulong ExtraParameter { get; } + public ulong ExtraParameterSize { get; } + + public uint ChannelIndex { get; } + + public uint ChannelCount { get; } + + public DecodingBehaviour DecodingBehaviour { get; } + + public SampleFormat SampleFormat { get; } + + public SampleRateConversionQuality SrcQuality { get; } + + public DataSourceVersion2Command(ref VoiceState serverState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + ChannelIndex = channelIndex; + ChannelCount = serverState.ChannelsCount; + SampleFormat = serverState.SampleFormat; + SrcQuality = serverState.SrcQuality; + CommandType = GetCommandTypeBySampleFormat(SampleFormat); + + OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex); + SampleRate = serverState.SampleRate; + Pitch = serverState.Pitch; + + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; + + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; + + WaveBuffers[i] = voiceWaveBuffer.ToCommon(2); + } + + if (SampleFormat == SampleFormat.Adpcm) + { + ExtraParameter = serverState.DataSourceStateAddressInfo.GetReference(true); + ExtraParameterSize = serverState.DataSourceStateAddressInfo.Size; + } + + State = state; + DecodingBehaviour = serverState.DecodingBehaviour; + } + + private static CommandType GetCommandTypeBySampleFormat(SampleFormat sampleFormat) + { + return sampleFormat switch + { + SampleFormat.Adpcm => CommandType.AdpcmDataSourceVersion2, + SampleFormat.PcmInt16 => CommandType.PcmInt16DataSourceVersion2, + SampleFormat.PcmFloat => CommandType.PcmFloatDataSourceVersion2, + _ => throw new NotImplementedException($"{sampleFormat}"), + }; + } + + public void Process(CommandList context) + { + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + DataSourceHelper.WaveBufferInformation info = new() + { + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + ExtraParameter = ExtraParameter, + ExtraParameterSize = ExtraParameterSize, + ChannelIndex = (int)ChannelIndex, + ChannelCount = (int)ChannelCount, + SrcQuality = SrcQuality, + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs new file mode 100644 index 00000000..6fa3777f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs @@ -0,0 +1,280 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Utils.Math; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DelayCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Delay; + + public uint EstimatedProcessingTime { get; set; } + + public DelayParameter Parameter => _parameter; + public Memory State { get; } + public ulong WorkBuffer { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsEffectEnabled { get; } + + private DelayParameter _parameter; + + private const int FixedPointPrecision = 14; + + public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported) + { + Enabled = true; + NodeId = nodeId; + _parameter = parameter; + State = state; + WorkBuffer = workBuffer; + + IsEffectEnabled = isEnabled; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + } + + DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, InputBufferIndices, Parameter.ChannelCount); + DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, OutputBufferIndices, Parameter.ChannelCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount) + { + const ushort ChannelCount = 1; + + float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision); + float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision); + float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision); + float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i] * 64; + float delayLineValue = state.DelayLines[0].Read(); + + float temp = input * inGain + delayLineValue * feedbackGain; + + state.UpdateLowPassFilter(ref temp, ChannelCount); + + outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private unsafe void ProcessDelayStereo(ref DelayState state, Span outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + const ushort ChannelCount = 2; + + float delayFeedbackBaseGain = state.DelayFeedbackBaseGain; + float delayFeedbackCrossGain = state.DelayFeedbackCrossGain; + float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision); + float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision); + float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); + + Matrix2x2 delayFeedback = new(delayFeedbackBaseGain, delayFeedbackCrossGain, + delayFeedbackCrossGain, delayFeedbackBaseGain); + + for (int i = 0; i < sampleCount; i++) + { + Vector2 channelInput = new() + { + X = *((float*)inputBuffers[0] + i) * 64, + Y = *((float*)inputBuffers[1] + i) * 64, + }; + + Vector2 delayLineValues = new() + { + X = state.DelayLines[0].Read(), + Y = state.DelayLines[1].Read(), + }; + + Vector2 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain; + + state.UpdateLowPassFilter(ref Unsafe.As(ref temp), ChannelCount); + + *((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64; + *((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private unsafe void ProcessDelayQuadraphonic(ref DelayState state, Span outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + const ushort ChannelCount = 4; + + float delayFeedbackBaseGain = state.DelayFeedbackBaseGain; + float delayFeedbackCrossGain = state.DelayFeedbackCrossGain; + float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision); + float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision); + float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); + + Matrix4x4 delayFeedback = new(delayFeedbackBaseGain, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f, + delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, + delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, + 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain); + + + for (int i = 0; i < sampleCount; i++) + { + Vector4 channelInput = new() + { + X = *((float*)inputBuffers[0] + i) * 64, + Y = *((float*)inputBuffers[1] + i) * 64, + Z = *((float*)inputBuffers[2] + i) * 64, + W = *((float*)inputBuffers[3] + i) * 64, + }; + + Vector4 delayLineValues = new() + { + X = state.DelayLines[0].Read(), + Y = state.DelayLines[1].Read(), + Z = state.DelayLines[2].Read(), + W = state.DelayLines[3].Read(), + }; + + Vector4 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain; + + state.UpdateLowPassFilter(ref Unsafe.As(ref temp), ChannelCount); + + *((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64; + *((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64; + *((float*)outputBuffers[2] + i) = (channelInput.Z * dryGain + delayLineValues.Z * outGain) / 64; + *((float*)outputBuffers[3] + i) = (channelInput.W * dryGain + delayLineValues.W * outGain) / 64; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private unsafe void ProcessDelaySurround(ref DelayState state, Span outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + const ushort ChannelCount = 6; + + float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision); + float delayFeedbackBaseGain = state.DelayFeedbackBaseGain; + float delayFeedbackCrossGain = state.DelayFeedbackCrossGain; + float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision); + float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision); + float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); + + Matrix6x6 delayFeedback = new(delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, 0.0f, delayFeedbackCrossGain, 0.0f, + 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, + delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f, + delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, + 0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain); + + for (int i = 0; i < sampleCount; i++) + { + Vector6 channelInput = new() + { + X = *((float*)inputBuffers[0] + i) * 64, + Y = *((float*)inputBuffers[1] + i) * 64, + Z = *((float*)inputBuffers[2] + i) * 64, + W = *((float*)inputBuffers[3] + i) * 64, + V = *((float*)inputBuffers[4] + i) * 64, + U = *((float*)inputBuffers[5] + i) * 64, + }; + + Vector6 delayLineValues = new() + { + X = state.DelayLines[0].Read(), + Y = state.DelayLines[1].Read(), + Z = state.DelayLines[2].Read(), + W = state.DelayLines[3].Read(), + V = state.DelayLines[4].Read(), + U = state.DelayLines[5].Read(), + }; + + Vector6 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain; + + state.UpdateLowPassFilter(ref Unsafe.As(ref temp), ChannelCount); + + *((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64; + *((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64; + *((float*)outputBuffers[2] + i) = (channelInput.Z * dryGain + delayLineValues.Z * outGain) / 64; + *((float*)outputBuffers[3] + i) = (channelInput.W * dryGain + delayLineValues.W * outGain) / 64; + *((float*)outputBuffers[4] + i) = (channelInput.V * dryGain + delayLineValues.V * outGain) / 64; + *((float*)outputBuffers[5] + i) = (channelInput.U * dryGain + delayLineValues.U * outGain) / 64; + } + } + + private unsafe void ProcessDelay(CommandList context, ref DelayState state) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]); + } + + switch (Parameter.ChannelCount) + { + case 1: + ProcessDelayMono(ref state, (float*)outputBuffers[0], (float*)inputBuffers[0], context.SampleCount); + break; + case 2: + ProcessDelayStereo(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 4: + ProcessDelayQuadraphonic(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 6: + ProcessDelaySurround(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + default: + throw new NotImplementedException(Parameter.ChannelCount.ToString()); + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]); + } + } + } + } + + public void Process(CommandList context) + { + ref DelayState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (Parameter.Status == UsageState.Invalid) + { + state = new DelayState(ref _parameter, WorkBuffer); + } + else if (Parameter.Status == UsageState.New) + { + state.UpdateParameter(ref _parameter); + } + } + + ProcessDelay(context, ref state); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs new file mode 100644 index 00000000..ff38f38c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs @@ -0,0 +1,90 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DepopForMixBuffersCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.DepopForMixBuffers; + + public uint EstimatedProcessingTime { get; set; } + + public uint MixBufferOffset { get; } + + public uint MixBufferCount { get; } + + public float Decay { get; } + + public Memory DepopBuffer { get; } + + public DepopForMixBuffersCommand(Memory depopBuffer, uint bufferOffset, uint mixBufferCount, int nodeId, uint sampleRate) + { + Enabled = true; + NodeId = nodeId; + MixBufferOffset = bufferOffset; + MixBufferCount = mixBufferCount; + DepopBuffer = depopBuffer; + + if (sampleRate == 48000) + { + Decay = 0.962189f; + } + else // if (sampleRate == 32000) + { + Decay = 0.943695f; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe float ProcessDepopMix(float* buffer, float depopValue, uint sampleCount) + { + if (depopValue < 0) + { + depopValue = -depopValue; + + for (int i = 0; i < sampleCount; i++) + { + depopValue = FloatingPointHelper.MultiplyRoundDown(Decay, depopValue); + + buffer[i] -= depopValue; + } + + return -depopValue; + } + + for (int i = 0; i < sampleCount; i++) + { + depopValue = FloatingPointHelper.MultiplyRoundDown(Decay, depopValue); + + buffer[i] += depopValue; + } + + return depopValue; + } + + public void Process(CommandList context) + { + Span depopBuffer = DepopBuffer.Span; + + uint bufferCount = Math.Min(MixBufferOffset + MixBufferCount, context.BufferCount); + + for (int i = (int)MixBufferOffset; i < bufferCount; i++) + { + float depopValue = depopBuffer[i]; + if (depopValue != 0) + { + unsafe + { + float* buffer = (float*)context.GetBufferPointer(i); + + depopBuffer[i] = ProcessDepopMix(buffer, depopValue, context.SampleCount); + } + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs new file mode 100644 index 00000000..c64bbdc5 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs @@ -0,0 +1,57 @@ +using Ryujinx.Audio.Renderer.Common; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DepopPrepareCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.DepopPrepare; + + public uint EstimatedProcessingTime { get; set; } + + public uint MixBufferCount { get; } + + public ushort[] OutputBufferIndices { get; } + + public Memory State { get; } + public Memory DepopBuffer { get; } + + public DepopPrepareCommand(Memory state, Memory depopBuffer, uint mixBufferCount, uint bufferOffset, int nodeId, bool enabled) + { + Enabled = enabled; + NodeId = nodeId; + MixBufferCount = mixBufferCount; + + OutputBufferIndices = new ushort[Constants.MixBufferCountMax]; + + for (int i = 0; i < Constants.MixBufferCountMax; i++) + { + OutputBufferIndices[i] = (ushort)(bufferOffset + i); + } + + State = state; + DepopBuffer = depopBuffer; + } + + public void Process(CommandList context) + { + ref VoiceUpdateState state = ref State.Span[0]; + + Span depopBuffer = DepopBuffer.Span; + + for (int i = 0; i < MixBufferCount; i++) + { + if (state.LastSamples[i] != 0) + { + depopBuffer[OutputBufferIndices[i]] += state.LastSamples[i]; + + state.LastSamples[i] = 0; + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs new file mode 100644 index 00000000..19afc66f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs @@ -0,0 +1,103 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Server.Sink; +using System; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DeviceSinkCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.DeviceSink; + + public uint EstimatedProcessingTime { get; set; } + + public string DeviceName { get; } + + public int SessionId { get; } + + public uint InputCount { get; } + public ushort[] InputBufferIndices { get; } + + public Memory Buffers { get; } + + public DeviceSinkCommand(uint bufferOffset, DeviceSink sink, int sessionId, Memory buffers, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + DeviceName = Encoding.ASCII.GetString(sink.Parameter.DeviceName).TrimEnd('\0'); + SessionId = sessionId; + InputCount = sink.Parameter.InputCount; + InputBufferIndices = new ushort[InputCount]; + + for (int i = 0; i < Math.Min(InputCount, Constants.ChannelCountMax); i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + sink.Parameter.Input[i]); + } + + if (sink.UpsamplerState != null) + { + Buffers = sink.UpsamplerState.OutputBuffer; + } + else + { + Buffers = buffers; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span GetBuffer(int index, int sampleCount) + { + return Buffers.Span.Slice(index * sampleCount, sampleCount); + } + + public void Process(CommandList context) + { + IHardwareDevice device = context.OutputDevice; + + if (device.GetSampleRate() == Constants.TargetSampleRate) + { + int channelCount = (int)device.GetChannelCount(); + uint bufferCount = Math.Min(device.GetChannelCount(), InputCount); + + const int SampleCount = Constants.TargetSampleCount; + + uint inputCount; + + // In case of upmixing to 5.1, we allocate the right amount. + if (bufferCount != channelCount && channelCount == 6) + { + inputCount = (uint)channelCount; + } + else + { + inputCount = bufferCount; + } + + short[] outputBuffer = new short[inputCount * SampleCount]; + + for (int i = 0; i < bufferCount; i++) + { + ReadOnlySpan inputBuffer = GetBuffer(InputBufferIndices[i], SampleCount); + + for (int j = 0; j < SampleCount; j++) + { + outputBuffer[i + j * channelCount] = PcmHelper.Saturate(inputBuffer[j]); + } + } + + device.AppendBuffer(outputBuffer, inputCount); + } + else + { + // TODO: support resampling for device only supporting something different + throw new NotImplementedException(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs new file mode 100644 index 00000000..8997b0db --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs @@ -0,0 +1,68 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DownMixSurroundToStereoCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.DownMixSurroundToStereo; + + public uint EstimatedProcessingTime { get; set; } + + public ushort[] InputBufferIndices { get; } + public ushort[] OutputBufferIndices { get; } + + public float[] Coefficients { get; } + + public DownMixSurroundToStereoCommand(uint bufferOffset, Span inputBufferOffset, Span outputBufferOffset, float[] downMixParameter, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Constants.VoiceChannelCountMax; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + outputBufferOffset[i]); + } + + Coefficients = downMixParameter; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float DownMixSurroundToStereo(ReadOnlySpan coefficients, float back, float lfe, float center, float front) + { + return FloatingPointHelper.RoundUp(coefficients[3] * back + coefficients[2] * lfe + coefficients[1] * center + coefficients[0] * front); + } + + public void Process(CommandList context) + { + ReadOnlySpan frontLeft = context.GetBuffer(InputBufferIndices[0]); + ReadOnlySpan frontRight = context.GetBuffer(InputBufferIndices[1]); + ReadOnlySpan frontCenter = context.GetBuffer(InputBufferIndices[2]); + ReadOnlySpan lowFrequency = context.GetBuffer(InputBufferIndices[3]); + ReadOnlySpan backLeft = context.GetBuffer(InputBufferIndices[4]); + ReadOnlySpan backRight = context.GetBuffer(InputBufferIndices[5]); + + Span stereoLeft = context.GetBuffer(OutputBufferIndices[0]); + Span stereoRight = context.GetBuffer(OutputBufferIndices[1]); + + for (int i = 0; i < context.SampleCount; i++) + { + stereoLeft[i] = DownMixSurroundToStereo(Coefficients, backLeft[i], lowFrequency[i], frontCenter[i], frontLeft[i]); + stereoRight[i] = DownMixSurroundToStereo(Coefficients, backRight[i], lowFrequency[i], frontCenter[i], frontRight[i]); + } + + context.ClearBuffer(OutputBufferIndices[2]); + context.ClearBuffer(OutputBufferIndices[3]); + context.ClearBuffer(OutputBufferIndices[4]); + context.ClearBuffer(OutputBufferIndices[5]); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs new file mode 100644 index 00000000..34a62c58 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public interface ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType { get; } + + public uint EstimatedProcessingTime { get; } + + public void Process(CommandList context); + + public bool ShouldMeter() + { + return false; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs new file mode 100644 index 00000000..3ba0b588 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs @@ -0,0 +1,145 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Effect; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class LimiterCommandVersion1 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.LimiterVersion1; + + public uint EstimatedProcessingTime { get; set; } + + public LimiterParameter Parameter => _parameter; + public Memory State { get; } + public ulong WorkBuffer { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsEffectEnabled { get; } + + private LimiterParameter _parameter; + + public LimiterCommandVersion1(uint bufferOffset, LimiterParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId) + { + Enabled = true; + NodeId = nodeId; + _parameter = parameter; + State = state; + WorkBuffer = workBuffer; + + IsEffectEnabled = isEnabled; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + } + } + + public void Process(CommandList context) + { + ref LimiterState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (Parameter.Status == UsageState.Invalid) + { + state = new LimiterState(ref _parameter, WorkBuffer); + } + else if (Parameter.Status == UsageState.New) + { + LimiterState.UpdateParameter(ref _parameter); + } + } + + ProcessLimiter(context, ref state); + } + + private unsafe void ProcessLimiter(CommandList context, ref LimiterState state) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]); + } + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++) + { + float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex); + + float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain; + + float sampleInputMax = Math.Abs(inputSample); + + float inputCoefficient = Parameter.ReleaseCoefficient; + + if (sampleInputMax > state.DetectorAverage[channelIndex].Read()) + { + inputCoefficient = Parameter.AttackCoefficient; + } + + float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient); + float attenuation = 1.0f; + + if (detectorValue > Parameter.Threshold) + { + attenuation = Parameter.Threshold / detectorValue; + } + + float outputCoefficient = Parameter.ReleaseCoefficient; + + if (state.CompressionGainAverage[channelIndex].Read() > attenuation) + { + outputCoefficient = Parameter.AttackCoefficient; + } + + float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient); + + ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]]; + + float outputSample = delayedSample * compressionGain * Parameter.OutputGain; + + *((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue; + + delayedSample = inputSample; + + state.DelayedSampleBufferPosition[channelIndex]++; + + while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin) + { + state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin; + } + } + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]); + } + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs new file mode 100644 index 00000000..f6e1654d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs @@ -0,0 +1,171 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Effect; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class LimiterCommandVersion2 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.LimiterVersion2; + + public uint EstimatedProcessingTime { get; set; } + + public LimiterParameter Parameter => _parameter; + public Memory State { get; } + public Memory ResultState { get; } + public ulong WorkBuffer { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsEffectEnabled { get; } + + private LimiterParameter _parameter; + + public LimiterCommandVersion2( + uint bufferOffset, + LimiterParameter parameter, + Memory state, + Memory resultState, + bool isEnabled, + ulong workBuffer, + int nodeId) + { + Enabled = true; + NodeId = nodeId; + _parameter = parameter; + State = state; + ResultState = resultState; + WorkBuffer = workBuffer; + + IsEffectEnabled = isEnabled; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + } + } + + public void Process(CommandList context) + { + ref LimiterState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (Parameter.Status == UsageState.Invalid) + { + state = new LimiterState(ref _parameter, WorkBuffer); + } + else if (Parameter.Status == UsageState.New) + { + LimiterState.UpdateParameter(ref _parameter); + } + } + + ProcessLimiter(context, ref state); + } + + private unsafe void ProcessLimiter(CommandList context, ref LimiterState state) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + if (!ResultState.IsEmpty && Parameter.StatisticsReset) + { + ref LimiterStatistics statistics = ref MemoryMarshal.Cast(ResultState.Span[0].SpecificData)[0]; + + statistics.Reset(); + } + + Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]); + } + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++) + { + float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex); + + float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain; + + float sampleInputMax = Math.Abs(inputSample); + + float inputCoefficient = Parameter.ReleaseCoefficient; + + if (sampleInputMax > state.DetectorAverage[channelIndex].Read()) + { + inputCoefficient = Parameter.AttackCoefficient; + } + + float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient); + float attenuation = 1.0f; + + if (detectorValue > Parameter.Threshold) + { + attenuation = Parameter.Threshold / detectorValue; + } + + float outputCoefficient = Parameter.ReleaseCoefficient; + + if (state.CompressionGainAverage[channelIndex].Read() > attenuation) + { + outputCoefficient = Parameter.AttackCoefficient; + } + + float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient); + + ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]]; + + float outputSample = delayedSample * compressionGain * Parameter.OutputGain; + + *((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue; + + delayedSample = inputSample; + + state.DelayedSampleBufferPosition[channelIndex]++; + + while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin) + { + state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin; + } + + if (!ResultState.IsEmpty) + { + ref LimiterStatistics statistics = ref MemoryMarshal.Cast(ResultState.Span[0].SpecificData)[0]; + + statistics.InputMax[channelIndex] = Math.Max(statistics.InputMax[channelIndex], sampleInputMax); + statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], compressionGain); + } + } + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]); + } + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs new file mode 100644 index 00000000..c701f80e --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs @@ -0,0 +1,137 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class MixCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Mix; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public float Volume { get; } + + public MixCommand(uint inputBufferIndex, uint outputBufferIndex, int nodeId, float volume) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)inputBufferIndex; + OutputBufferIndex = (ushort)outputBufferIndex; + + Volume = volume; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMixAvx(Span outputMix, ReadOnlySpan inputMix) + { + Vector256 volumeVec = Vector256.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputMix); + Span> outputVec = MemoryMarshal.Cast>(outputMix); + + int sisdStart = inputVec.Length * 8; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Avx.Add(outputVec[i], Avx.Ceiling(Avx.Multiply(inputVec[i], volumeVec))); + } + + for (int i = sisdStart; i < inputMix.Length; i++) + { + outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMixSse41(Span outputMix, ReadOnlySpan inputMix) + { + Vector128 volumeVec = Vector128.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputMix); + Span> outputVec = MemoryMarshal.Cast>(outputMix); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Sse.Add(outputVec[i], Sse41.Ceiling(Sse.Multiply(inputVec[i], volumeVec))); + } + + for (int i = sisdStart; i < inputMix.Length; i++) + { + outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMixAdvSimd(Span outputMix, ReadOnlySpan inputMix) + { + Vector128 volumeVec = Vector128.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputMix); + Span> outputVec = MemoryMarshal.Cast>(outputMix); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = AdvSimd.Add(outputVec[i], AdvSimd.Ceiling(AdvSimd.Multiply(inputVec[i], volumeVec))); + } + + for (int i = sisdStart; i < inputMix.Length; i++) + { + outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMixSlowPath(Span outputMix, ReadOnlySpan inputMix) + { + for (int i = 0; i < inputMix.Length; i++) + { + outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMix(Span outputMix, ReadOnlySpan inputMix) + { + if (Avx.IsSupported) + { + ProcessMixAvx(outputMix, inputMix); + } + else if (Sse41.IsSupported) + { + ProcessMixSse41(outputMix, inputMix); + } + else if (AdvSimd.IsSupported) + { + ProcessMixAdvSimd(outputMix, inputMix); + } + else + { + ProcessMixSlowPath(outputMix, inputMix); + } + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + ProcessMix(outputBuffer, inputBuffer); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs new file mode 100644 index 00000000..f77a233e --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs @@ -0,0 +1,68 @@ +using Ryujinx.Audio.Renderer.Common; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class MixRampCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.MixRamp; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public float Volume0 { get; } + public float Volume1 { get; } + + public Memory State { get; } + + public int LastSampleIndex { get; } + + public MixRampCommand(float volume0, float volume1, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory state, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)inputBufferIndex; + OutputBufferIndex = (ushort)outputBufferIndex; + + Volume0 = volume0; + Volume1 = volume1; + + State = state; + LastSampleIndex = lastSampleIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float ProcessMixRamp(Span outputBuffer, ReadOnlySpan inputBuffer, int sampleCount) + { + float ramp = (Volume1 - Volume0) / sampleCount; + float volume = Volume0; + float state = 0; + + for (int i = 0; i < sampleCount; i++) + { + state = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume); + + outputBuffer[i] += state; + volume += ramp; + } + + return state; + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + State.Span[0].LastSamples[LastSampleIndex] = ProcessMixRamp(outputBuffer, inputBuffer, (int)context.SampleCount); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs new file mode 100644 index 00000000..41ac84c1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs @@ -0,0 +1,103 @@ +using Ryujinx.Audio.Renderer.Common; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class MixRampGroupedCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.MixRampGrouped; + + public uint EstimatedProcessingTime { get; set; } + + public uint MixBufferCount { get; } + + public ushort[] InputBufferIndices { get; } + public ushort[] OutputBufferIndices { get; } + + public float[] Volume0 { get; } + public float[] Volume1 { get; } + + public Memory State { get; } + + public MixRampGroupedCommand( + uint mixBufferCount, + uint inputBufferIndex, + uint outputBufferIndex, + ReadOnlySpan volume0, + ReadOnlySpan volume1, + Memory state, + int nodeId) + { + Enabled = true; + MixBufferCount = mixBufferCount; + NodeId = nodeId; + + InputBufferIndices = new ushort[Constants.MixBufferCountMax]; + OutputBufferIndices = new ushort[Constants.MixBufferCountMax]; + Volume0 = new float[Constants.MixBufferCountMax]; + Volume1 = new float[Constants.MixBufferCountMax]; + + for (int i = 0; i < mixBufferCount; i++) + { + InputBufferIndices[i] = (ushort)inputBufferIndex; + OutputBufferIndices[i] = (ushort)(outputBufferIndex + i); + + Volume0[i] = volume0[i]; + Volume1[i] = volume1[i]; + } + + State = state; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float ProcessMixRampGrouped( + Span outputBuffer, + ReadOnlySpan inputBuffer, + float volume0, + float volume1, + int sampleCount) + { + float ramp = (volume1 - volume0) / sampleCount; + float volume = volume0; + float state = 0; + + for (int i = 0; i < sampleCount; i++) + { + state = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume); + + outputBuffer[i] += state; + volume += ramp; + } + + return state; + } + + public void Process(CommandList context) + { + for (int i = 0; i < MixBufferCount; i++) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndices[i]); + Span outputBuffer = context.GetBuffer(OutputBufferIndices[i]); + + float volume0 = Volume0[i]; + float volume1 = Volume1[i]; + + ref VoiceUpdateState state = ref State.Span[0]; + + if (volume0 != 0 || volume1 != 0) + { + state.LastSamples[i] = ProcessMixRampGrouped(outputBuffer, inputBuffer, volume0, volume1, (int)context.SampleCount); + } + else + { + state.LastSamples[i] = 0; + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs new file mode 100644 index 00000000..e359371b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs @@ -0,0 +1,145 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class MultiTapBiquadFilterAndMixCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.MultiTapBiquadFilterAndMix; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + private BiquadFilterParameter _parameter0; + private BiquadFilterParameter _parameter1; + + public Memory BiquadFilterState0 { get; } + public Memory BiquadFilterState1 { get; } + public Memory PreviousBiquadFilterState0 { get; } + public Memory PreviousBiquadFilterState1 { get; } + + public Memory State { get; } + + public int LastSampleIndex { get; } + + public float Volume0 { get; } + public float Volume1 { get; } + + public bool NeedInitialization0 { get; } + public bool NeedInitialization1 { get; } + public bool HasVolumeRamp { get; } + public bool IsFirstMixBuffer { get; } + + public MultiTapBiquadFilterAndMixCommand( + float volume0, + float volume1, + uint inputBufferIndex, + uint outputBufferIndex, + int lastSampleIndex, + Memory state, + ref BiquadFilterParameter filter0, + ref BiquadFilterParameter filter1, + Memory biquadFilterState0, + Memory biquadFilterState1, + Memory previousBiquadFilterState0, + Memory previousBiquadFilterState1, + bool needInitialization0, + bool needInitialization1, + bool hasVolumeRamp, + bool isFirstMixBuffer, + int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)inputBufferIndex; + OutputBufferIndex = (ushort)outputBufferIndex; + + _parameter0 = filter0; + _parameter1 = filter1; + BiquadFilterState0 = biquadFilterState0; + BiquadFilterState1 = biquadFilterState1; + PreviousBiquadFilterState0 = previousBiquadFilterState0; + PreviousBiquadFilterState1 = previousBiquadFilterState1; + + State = state; + LastSampleIndex = lastSampleIndex; + + Volume0 = volume0; + Volume1 = volume1; + + NeedInitialization0 = needInitialization0; + NeedInitialization1 = needInitialization1; + HasVolumeRamp = hasVolumeRamp; + IsFirstMixBuffer = isFirstMixBuffer; + } + + private void UpdateState(Memory state, Memory previousState, bool needInitialization) + { + if (needInitialization) + { + // If there is no previous state, initialize to zero. + + state.Span[0] = new BiquadFilterState(); + } + else if (IsFirstMixBuffer) + { + // This is the first buffer, set previous state to current state. + + previousState.Span[0] = state.Span[0]; + } + else + { + // Rewind the current state by copying back the previous state. + + state.Span[0] = previousState.Span[0]; + } + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + UpdateState(BiquadFilterState0, PreviousBiquadFilterState0, NeedInitialization0); + UpdateState(BiquadFilterState1, PreviousBiquadFilterState1, NeedInitialization1); + + if (HasVolumeRamp) + { + float volume = Volume0; + float ramp = (Volume1 - Volume0) / (int)context.SampleCount; + + State.Span[0].LastSamples[LastSampleIndex] = BiquadFilterHelper.ProcessDoubleBiquadFilterAndMixRamp( + ref _parameter0, + ref _parameter1, + ref BiquadFilterState0.Span[0], + ref BiquadFilterState1.Span[0], + outputBuffer, + inputBuffer, + context.SampleCount, + volume, + ramp); + } + else + { + BiquadFilterHelper.ProcessDoubleBiquadFilterAndMix( + ref _parameter0, + ref _parameter1, + ref BiquadFilterState0.Span[0], + ref BiquadFilterState1.Span[0], + outputBuffer, + inputBuffer, + context.SampleCount, + Volume1); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs new file mode 100644 index 00000000..e159f8ef --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs @@ -0,0 +1,62 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class MultiTapBiquadFilterCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.MultiTapBiquadFilter; + + public uint EstimatedProcessingTime { get; set; } + + private readonly BiquadFilterParameter[] _parameters; + private readonly Memory _biquadFilterStates; + private readonly int _inputBufferIndex; + private readonly int _outputBufferIndex; + private readonly bool[] _isInitialized; + + public MultiTapBiquadFilterCommand(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId) + { + _parameters = filters.ToArray(); + _biquadFilterStates = biquadFilterStateMemory; + _inputBufferIndex = baseIndex + inputBufferOffset; + _outputBufferIndex = baseIndex + outputBufferOffset; + _isInitialized = isInitialized.ToArray(); + + Enabled = true; + NodeId = nodeId; + } + + public void Process(CommandList context) + { + Span states = _biquadFilterStates.Span; + + ReadOnlySpan inputBuffer = context.GetBuffer(_inputBufferIndex); + Span outputBuffer = context.GetBuffer(_outputBufferIndex); + + for (int i = 0; i < _parameters.Length; i++) + { + if (!_isInitialized[i]) + { + states[i] = new BiquadFilterState(); + } + } + + // NOTE: Nintendo only implement single and double biquad filters but no generic path when the command definition suggests it could be done. + // As such we currently only implement a generic path for simplicity for double biquad. + if (_parameters.Length == 1) + { + BiquadFilterHelper.ProcessBiquadFilter(ref _parameters[0], ref states[0], outputBuffer, inputBuffer, context.SampleCount); + } + else + { + BiquadFilterHelper.ProcessBiquadFilter(_parameters, states, outputBuffer, inputBuffer, context.SampleCount); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs new file mode 100644 index 00000000..585edc05 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs @@ -0,0 +1,76 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Voice; +using System; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; +using WaveBuffer = Ryujinx.Audio.Renderer.Common.WaveBuffer; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class PcmFloatDataSourceCommandVersion1 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.PcmFloatDataSourceVersion1; + + public uint EstimatedProcessingTime { get; set; } + + public ushort OutputBufferIndex { get; } + public uint SampleRate { get; } + public uint ChannelIndex { get; } + + public uint ChannelCount { get; } + + public float Pitch { get; } + + public WaveBuffer[] WaveBuffers { get; } + + public Memory State { get; } + public DecodingBehaviour DecodingBehaviour { get; } + + public PcmFloatDataSourceCommandVersion1(ref VoiceState serverState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex); + SampleRate = serverState.SampleRate; + ChannelIndex = channelIndex; + ChannelCount = serverState.ChannelsCount; + Pitch = serverState.Pitch; + + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; + + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; + + WaveBuffers[i] = voiceWaveBuffer.ToCommon(1); + } + + State = state; + DecodingBehaviour = serverState.DecodingBehaviour; + } + + public void Process(CommandList context) + { + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + DataSourceHelper.WaveBufferInformation info = new() + { + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat.PcmFloat, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + ExtraParameter = 0, + ExtraParameterSize = 0, + ChannelIndex = (int)ChannelIndex, + ChannelCount = (int)ChannelCount, + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs new file mode 100644 index 00000000..6f01219f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs @@ -0,0 +1,76 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Voice; +using System; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; +using WaveBuffer = Ryujinx.Audio.Renderer.Common.WaveBuffer; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class PcmInt16DataSourceCommandVersion1 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.PcmInt16DataSourceVersion1; + + public uint EstimatedProcessingTime { get; set; } + + public ushort OutputBufferIndex { get; } + public uint SampleRate { get; } + public uint ChannelIndex { get; } + + public uint ChannelCount { get; } + + public float Pitch { get; } + + public WaveBuffer[] WaveBuffers { get; } + + public Memory State { get; } + public DecodingBehaviour DecodingBehaviour { get; } + + public PcmInt16DataSourceCommandVersion1(ref VoiceState serverState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex); + SampleRate = serverState.SampleRate; + ChannelIndex = channelIndex; + ChannelCount = serverState.ChannelsCount; + Pitch = serverState.Pitch; + + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; + + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; + + WaveBuffers[i] = voiceWaveBuffer.ToCommon(1); + } + + State = state; + DecodingBehaviour = serverState.DecodingBehaviour; + } + + public void Process(CommandList context) + { + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + DataSourceHelper.WaveBufferInformation info = new() + { + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat.PcmInt16, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + ExtraParameter = 0, + ExtraParameterSize = 0, + ChannelIndex = (int)ChannelIndex, + ChannelCount = (int)ChannelCount, + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs new file mode 100644 index 00000000..d3d2ee30 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs @@ -0,0 +1,47 @@ +using Ryujinx.Audio.Renderer.Server.Performance; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class PerformanceCommand : ICommand + { + public enum Type + { + Invalid, + Start, + End, + } + + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Performance; + + public uint EstimatedProcessingTime { get; set; } + + public PerformanceEntryAddresses PerformanceEntryAddresses { get; } + + public Type PerformanceType { get; set; } + + public PerformanceCommand(ref PerformanceEntryAddresses performanceEntryAddresses, Type performanceType, int nodeId) + { + Enabled = true; + PerformanceEntryAddresses = performanceEntryAddresses; + PerformanceType = performanceType; + NodeId = nodeId; + } + + public void Process(CommandList context) + { + if (PerformanceType == Type.Start) + { + PerformanceEntryAddresses.SetStartTime(context.GetTimeElapsedSinceDspStartedProcessing()); + } + else if (PerformanceType == Type.End) + { + PerformanceEntryAddresses.SetProcessingTime(context.GetTimeElapsedSinceDspStartedProcessing()); + PerformanceEntryAddresses.IncrementEntryCount(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs new file mode 100644 index 00000000..8cdd4843 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs @@ -0,0 +1,254 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Effect; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class Reverb3dCommand : ICommand + { + private static readonly int[] _outputEarlyIndicesTableMono = new int[20] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + private static readonly int[] _targetEarlyDelayLineIndicesTableMono = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }; + private static readonly int[] _targetOutputFeedbackIndicesTableMono = new int[1] { 0 }; + + private static readonly int[] _outputEarlyIndicesTableStereo = new int[20] { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1 }; + private static readonly int[] _targetEarlyDelayLineIndicesTableStereo = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }; + private static readonly int[] _targetOutputFeedbackIndicesTableStereo = new int[2] { 0, 1 }; + + private static readonly int[] _outputEarlyIndicesTableQuadraphonic = new int[20] { 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3 }; + private static readonly int[] _targetEarlyDelayLineIndicesTableQuadraphonic = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }; + private static readonly int[] _targetOutputFeedbackIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 }; + + private static readonly int[] _outputEarlyIndicesTableSurround = new int[40] { 4, 5, 0, 5, 0, 5, 1, 5, 1, 5, 1, 5, 1, 5, 2, 5, 2, 5, 2, 5, 1, 5, 1, 5, 1, 5, 0, 5, 0, 5, 0, 5, 0, 5, 3, 5, 3, 5, 3, 5 }; + private static readonly int[] _targetEarlyDelayLineIndicesTableSurround = new int[40] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19 }; + private static readonly int[] _targetOutputFeedbackIndicesTableSurround = new int[6] { 0, 1, 2, 3, -1, 3 }; + + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Reverb3d; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public Reverb3dParameter Parameter => _parameter; + public Memory State { get; } + public ulong WorkBuffer { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + + public bool IsEffectEnabled { get; } + + private Reverb3dParameter _parameter; + + public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported) + { + Enabled = true; + IsEffectEnabled = isEnabled; + NodeId = nodeId; + _parameter = parameter; + State = state; + WorkBuffer = workBuffer; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + } + + // NOTE: We do the opposite as Nintendo here for now to restore previous behaviour + // TODO: Update reverb 3d processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping. + DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices, Parameter.ChannelCount); + DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices, Parameter.ChannelCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverb3dMono(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, _outputEarlyIndicesTableMono, _targetEarlyDelayLineIndicesTableMono, _targetOutputFeedbackIndicesTableMono); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverb3dStereo(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, _outputEarlyIndicesTableStereo, _targetEarlyDelayLineIndicesTableStereo, _targetOutputFeedbackIndicesTableStereo); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverb3dQuadraphonic(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, _outputEarlyIndicesTableQuadraphonic, _targetEarlyDelayLineIndicesTableQuadraphonic, _targetOutputFeedbackIndicesTableQuadraphonic); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverb3dSurround(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, _outputEarlyIndicesTableSurround, _targetEarlyDelayLineIndicesTableSurround, _targetOutputFeedbackIndicesTableSurround); + } + + private unsafe void ProcessReverb3dGeneric(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount, ReadOnlySpan outputEarlyIndicesTable, ReadOnlySpan targetEarlyDelayLineIndicesTable, ReadOnlySpan targetOutputFeedbackIndicesTable) + { + const int DelayLineSampleIndexOffset = 1; + + bool isMono = Parameter.ChannelCount == 1; + bool isSurround = Parameter.ChannelCount == 6; + + Span outputValues = stackalloc float[Constants.ChannelCountMax]; + Span channelInput = stackalloc float[Parameter.ChannelCount]; + Span feedbackValues = stackalloc float[4]; + Span feedbackOutputValues = stackalloc float[4]; + Span values = stackalloc float[4]; + + for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + { + outputValues.Clear(); + + float tapOut = state.PreDelayLine.TapUnsafe(state.ReflectionDelayTime, DelayLineSampleIndexOffset); + + for (int i = 0; i < targetEarlyDelayLineIndicesTable.Length; i++) + { + int earlyDelayIndex = targetEarlyDelayLineIndicesTable[i]; + int outputIndex = outputEarlyIndicesTable[earlyDelayIndex]; + + float tempTapOut = state.PreDelayLine.TapUnsafe(state.EarlyDelayTime[earlyDelayIndex], DelayLineSampleIndexOffset); + + outputValues[outputIndex] += tempTapOut * state.EarlyGain[earlyDelayIndex]; + } + + float targetPreDelayValue = 0; + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex); + targetPreDelayValue += channelInput[channelIndex]; + } + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + outputValues[i] *= state.EarlyReflectionsGain; + } + + state.PreviousPreDelayValue = (targetPreDelayValue * state.TargetPreDelayGain) + (state.PreviousPreDelayValue * state.PreviousPreDelayGain); + + state.PreDelayLine.Update(state.PreviousPreDelayValue); + + for (int i = 0; i < state.FdnDelayLines.Length; i++) + { + float fdnValue = state.FdnDelayLines[i].Read(); + + float feedbackOutputValue = fdnValue * state.DecayDirectFdnGain[i] + state.PreviousFeedbackOutputDecayed[i]; + + state.PreviousFeedbackOutputDecayed[i] = (fdnValue * state.DecayCurrentFdnGain[i]) + (feedbackOutputValue * state.DecayCurrentOutputGain[i]); + + feedbackOutputValues[i] = feedbackOutputValue; + } + + feedbackValues[0] = feedbackOutputValues[2] + feedbackOutputValues[1]; + feedbackValues[1] = -feedbackOutputValues[0] - feedbackOutputValues[3]; + feedbackValues[2] = feedbackOutputValues[0] - feedbackOutputValues[3]; + feedbackValues[3] = feedbackOutputValues[1] - feedbackOutputValues[2]; + + for (int i = 0; i < state.DecayDelays1.Length; i++) + { + float temp = state.DecayDelays1[i].Update(tapOut * state.LateReverbGain + feedbackValues[i]); + + values[i] = state.DecayDelays2[i].Update(temp); + + state.FdnDelayLines[i].Update(values[i]); + } + + for (int channelIndex = 0; channelIndex < targetOutputFeedbackIndicesTable.Length; channelIndex++) + { + int targetOutputFeedbackIndex = targetOutputFeedbackIndicesTable[channelIndex]; + + if (targetOutputFeedbackIndex >= 0) + { + *((float*)outputBuffers[channelIndex] + sampleIndex) = (outputValues[channelIndex] + values[targetOutputFeedbackIndex] + channelInput[channelIndex] * state.DryGain); + } + } + + if (isMono) + { + *((float*)outputBuffers[0] + sampleIndex) += values[1]; + } + + if (isSurround) + { + *((float*)outputBuffers[4] + sampleIndex) += (outputValues[4] + state.FrontCenterDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain); + } + } + } + + public void ProcessReverb3d(CommandList context, ref Reverb3dState state) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]); + } + + switch (Parameter.ChannelCount) + { + case 1: + ProcessReverb3dMono(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 2: + ProcessReverb3dStereo(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 4: + ProcessReverb3dQuadraphonic(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 6: + ProcessReverb3dSurround(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + default: + throw new NotImplementedException(Parameter.ChannelCount.ToString()); + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]); + } + } + } + } + + public void Process(CommandList context) + { + ref Reverb3dState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (Parameter.ParameterStatus == UsageState.Invalid) + { + state = new Reverb3dState(ref _parameter, WorkBuffer); + } + else if (Parameter.ParameterStatus == UsageState.New) + { + state.UpdateParameter(ref _parameter); + } + } + + ProcessReverb3d(context, ref state); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs new file mode 100644 index 00000000..874eb8e8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs @@ -0,0 +1,284 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Effect; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class ReverbCommand : ICommand + { + private static readonly int[] _outputEarlyIndicesTableMono = new int[10] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + private static readonly int[] _targetEarlyDelayLineIndicesTableMono = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + private static readonly int[] _outputIndicesTableMono = new int[4] { 0, 0, 0, 0 }; + private static readonly int[] _targetOutputFeedbackIndicesTableMono = new int[4] { 0, 1, 2, 3 }; + + private static readonly int[] _outputEarlyIndicesTableStereo = new int[10] { 0, 0, 1, 1, 0, 1, 0, 0, 1, 1 }; + private static readonly int[] _targetEarlyDelayLineIndicesTableStereo = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + private static readonly int[] _outputIndicesTableStereo = new int[4] { 0, 0, 1, 1 }; + private static readonly int[] _targetOutputFeedbackIndicesTableStereo = new int[4] { 2, 0, 3, 1 }; + + private static readonly int[] _outputEarlyIndicesTableQuadraphonic = new int[10] { 0, 0, 1, 1, 0, 1, 2, 2, 3, 3 }; + private static readonly int[] _targetEarlyDelayLineIndicesTableQuadraphonic = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + private static readonly int[] _outputIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 }; + private static readonly int[] _targetOutputFeedbackIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 }; + + private static readonly int[] _outputEarlyIndicesTableSurround = new int[20] { 0, 5, 0, 5, 1, 5, 1, 5, 4, 5, 4, 5, 2, 5, 2, 5, 3, 5, 3, 5 }; + private static readonly int[] _targetEarlyDelayLineIndicesTableSurround = new int[20] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9 }; + private static readonly int[] _outputIndicesTableSurround = new int[Constants.ChannelCountMax] { 0, 1, 2, 3, 4, 5 }; + private static readonly int[] _targetOutputFeedbackIndicesTableSurround = new int[Constants.ChannelCountMax] { 0, 1, 2, 3, -1, 3 }; + + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Reverb; + + public uint EstimatedProcessingTime { get; set; } + + public ReverbParameter Parameter => _parameter; + public Memory State { get; } + public ulong WorkBuffer { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsLongSizePreDelaySupported { get; } + + public bool IsEffectEnabled { get; } + + private ReverbParameter _parameter; + + private const int FixedPointPrecision = 14; + + public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported) + { + Enabled = true; + IsEffectEnabled = isEnabled; + NodeId = nodeId; + _parameter = parameter; + State = state; + WorkBuffer = workBuffer; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + } + + IsLongSizePreDelaySupported = isLongSizePreDelaySupported; + + // NOTE: We do the opposite as Nintendo here for now to restore previous behaviour + // TODO: Update reverb processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping. + DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices, Parameter.ChannelCount); + DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices, Parameter.ChannelCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverbMono(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverbGeneric( + ref state, + outputBuffers, + inputBuffers, + sampleCount, + _outputEarlyIndicesTableMono, + _targetEarlyDelayLineIndicesTableMono, + _targetOutputFeedbackIndicesTableMono, + _outputIndicesTableMono); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverbStereo(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverbGeneric( + ref state, + outputBuffers, + inputBuffers, + sampleCount, + _outputEarlyIndicesTableStereo, + _targetEarlyDelayLineIndicesTableStereo, + _targetOutputFeedbackIndicesTableStereo, + _outputIndicesTableStereo); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverbQuadraphonic(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverbGeneric( + ref state, + outputBuffers, + inputBuffers, + sampleCount, + _outputEarlyIndicesTableQuadraphonic, + _targetEarlyDelayLineIndicesTableQuadraphonic, + _targetOutputFeedbackIndicesTableQuadraphonic, + _outputIndicesTableQuadraphonic); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverbSurround(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverbGeneric( + ref state, + outputBuffers, + inputBuffers, + sampleCount, + _outputEarlyIndicesTableSurround, + _targetEarlyDelayLineIndicesTableSurround, + _targetOutputFeedbackIndicesTableSurround, + _outputIndicesTableSurround); + } + + private unsafe void ProcessReverbGeneric(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount, ReadOnlySpan outputEarlyIndicesTable, ReadOnlySpan targetEarlyDelayLineIndicesTable, ReadOnlySpan targetOutputFeedbackIndicesTable, ReadOnlySpan outputIndicesTable) + { + bool isSurround = Parameter.ChannelCount == 6; + + float reverbGain = FixedPointHelper.ToFloat(Parameter.ReverbGain, FixedPointPrecision); + float lateGain = FixedPointHelper.ToFloat(Parameter.LateGain, FixedPointPrecision); + float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); + float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision); + + Span outputValues = stackalloc float[Constants.ChannelCountMax]; + Span feedbackValues = stackalloc float[4]; + Span feedbackOutputValues = stackalloc float[4]; + Span channelInput = stackalloc float[Parameter.ChannelCount]; + + for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + { + outputValues.Clear(); + + for (int i = 0; i < targetEarlyDelayLineIndicesTable.Length; i++) + { + int earlyDelayIndex = targetEarlyDelayLineIndicesTable[i]; + int outputIndex = outputEarlyIndicesTable[i]; + + float tapOutput = state.PreDelayLine.TapUnsafe(state.EarlyDelayTime[earlyDelayIndex], 0); + + outputValues[outputIndex] += tapOutput * state.EarlyGain[earlyDelayIndex]; + } + + if (isSurround) + { + outputValues[5] *= 0.2f; + } + + float targetPreDelayValue = 0; + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex) * 64; + targetPreDelayValue += channelInput[channelIndex] * reverbGain; + } + + state.PreDelayLine.Update(targetPreDelayValue); + + float lateValue = state.PreDelayLine.Tap(state.PreDelayLineDelayTime) * lateGain; + + for (int i = 0; i < state.FdnDelayLines.Length; i++) + { + feedbackOutputValues[i] = state.FdnDelayLines[i].Read() * state.HighFrequencyDecayDirectGain[i] + state.PreviousFeedbackOutput[i] * state.HighFrequencyDecayPreviousGain[i]; + state.PreviousFeedbackOutput[i] = feedbackOutputValues[i]; + } + + feedbackValues[0] = feedbackOutputValues[2] + feedbackOutputValues[1]; + feedbackValues[1] = -feedbackOutputValues[0] - feedbackOutputValues[3]; + feedbackValues[2] = feedbackOutputValues[0] - feedbackOutputValues[3]; + feedbackValues[3] = feedbackOutputValues[1] - feedbackOutputValues[2]; + + for (int i = 0; i < state.FdnDelayLines.Length; i++) + { + feedbackOutputValues[i] = state.DecayDelays[i].Update(feedbackValues[i] + lateValue); + state.FdnDelayLines[i].Update(feedbackOutputValues[i]); + } + + for (int i = 0; i < targetOutputFeedbackIndicesTable.Length; i++) + { + int targetOutputFeedbackIndex = targetOutputFeedbackIndicesTable[i]; + int outputIndex = outputIndicesTable[i]; + + if (targetOutputFeedbackIndex >= 0) + { + outputValues[outputIndex] += feedbackOutputValues[targetOutputFeedbackIndex]; + } + } + + if (isSurround) + { + outputValues[4] += state.FrontCenterDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f); + } + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + *((float*)outputBuffers[channelIndex] + sampleIndex) = (outputValues[channelIndex] * outGain + channelInput[channelIndex] * dryGain) / 64; + } + } + } + + private void ProcessReverb(CommandList context, ref ReverbState state) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]); + } + + switch (Parameter.ChannelCount) + { + case 1: + ProcessReverbMono(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 2: + ProcessReverbStereo(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 4: + ProcessReverbQuadraphonic(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 6: + ProcessReverbSurround(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + default: + throw new NotImplementedException(Parameter.ChannelCount.ToString()); + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]); + } + } + } + } + + public void Process(CommandList context) + { + ref ReverbState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (Parameter.Status == UsageState.Invalid) + { + state = new ReverbState(ref _parameter, WorkBuffer, IsLongSizePreDelaySupported); + } + else if (Parameter.Status == UsageState.New) + { + state.UpdateParameter(ref _parameter); + } + } + + ProcessReverb(context, ref state); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs new file mode 100644 index 00000000..8882500c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs @@ -0,0 +1,70 @@ +using Ryujinx.Audio.Renderer.Server.Upsampler; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class UpsampleCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Upsample; + + public uint EstimatedProcessingTime { get; set; } + + public uint BufferCount { get; } + public uint InputBufferIndex { get; } + public uint InputSampleCount { get; } + public uint InputSampleRate { get; } + + public UpsamplerState UpsamplerInfo { get; } + + public Memory OutBuffer { get; } + + public UpsampleCommand(uint bufferOffset, UpsamplerState info, uint inputCount, Span inputBufferOffset, uint bufferCount, uint sampleCount, uint sampleRate, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = 0; + OutBuffer = info.OutputBuffer; + BufferCount = bufferCount; + InputSampleCount = sampleCount; + InputSampleRate = sampleRate; + info.SourceSampleCount = inputCount; + info.InputBufferIndices = new ushort[inputCount]; + + for (int i = 0; i < inputCount; i++) + { + info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]); + } + + if (info.BufferStates?.Length != (int)inputCount) + { + // Keep state if possible. + info.BufferStates = new UpsamplerBufferState[(int)inputCount]; + } + + UpsamplerInfo = info; + } + + private Span GetBuffer(int index, int sampleCount) + { + return UpsamplerInfo.OutputBuffer.Span.Slice(index * sampleCount, sampleCount); + } + + public void Process(CommandList context) + { + uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount); + + for (int i = 0; i < bufferCount; i++) + { + Span inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]); + Span outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount); + + UpsamplerHelper.Upsample(outputBuffer, inputBuffer, (int)UpsamplerInfo.SampleCount, (int)InputSampleCount, ref UpsamplerInfo.BufferStates[i]); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs new file mode 100644 index 00000000..5deeb07f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs @@ -0,0 +1,137 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class VolumeCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Volume; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public float Volume { get; } + + public VolumeCommand(float volume, uint bufferIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)bufferIndex; + OutputBufferIndex = (ushort)bufferIndex; + + Volume = volume; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeAvx(Span outputBuffer, ReadOnlySpan inputBuffer) + { + Vector256 volumeVec = Vector256.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputBuffer); + Span> outputVec = MemoryMarshal.Cast>(outputBuffer); + + int sisdStart = inputVec.Length * 8; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Avx.Ceiling(Avx.Multiply(inputVec[i], volumeVec)); + } + + for (int i = sisdStart; i < inputBuffer.Length; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeSse41(Span outputBuffer, ReadOnlySpan inputBuffer) + { + Vector128 volumeVec = Vector128.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputBuffer); + Span> outputVec = MemoryMarshal.Cast>(outputBuffer); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Sse41.Ceiling(Sse.Multiply(inputVec[i], volumeVec)); + } + + for (int i = sisdStart; i < inputBuffer.Length; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeAdvSimd(Span outputBuffer, ReadOnlySpan inputBuffer) + { + Vector128 volumeVec = Vector128.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputBuffer); + Span> outputVec = MemoryMarshal.Cast>(outputBuffer); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = AdvSimd.Ceiling(AdvSimd.Multiply(inputVec[i], volumeVec)); + } + + for (int i = sisdStart; i < inputBuffer.Length; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolume(Span outputBuffer, ReadOnlySpan inputBuffer) + { + if (Avx.IsSupported) + { + ProcessVolumeAvx(outputBuffer, inputBuffer); + } + else if (Sse41.IsSupported) + { + ProcessVolumeSse41(outputBuffer, inputBuffer); + } + else if (AdvSimd.IsSupported) + { + ProcessVolumeAdvSimd(outputBuffer, inputBuffer); + } + else + { + ProcessVolumeSlowPath(outputBuffer, inputBuffer); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeSlowPath(Span outputBuffer, ReadOnlySpan inputBuffer) + { + for (int i = 0; i < outputBuffer.Length; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume); + } + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + ProcessVolume(outputBuffer, inputBuffer); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs new file mode 100644 index 00000000..bffbcbc6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs @@ -0,0 +1,56 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class VolumeRampCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.VolumeRamp; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public float Volume0 { get; } + public float Volume1 { get; } + + public VolumeRampCommand(float volume0, float volume1, uint bufferIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)bufferIndex; + OutputBufferIndex = (ushort)bufferIndex; + + Volume0 = volume0; + Volume1 = volume1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeRamp(Span outputBuffer, ReadOnlySpan inputBuffer, int sampleCount) + { + float ramp = (Volume1 - Volume0) / sampleCount; + + float volume = Volume0; + + for (int i = 0; i < sampleCount; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume); + volume += ramp; + } + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + ProcessVolumeRamp(outputBuffer, inputBuffer, (int)context.SampleCount); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs new file mode 100644 index 00000000..98657bd1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs @@ -0,0 +1,466 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class DataSourceHelper + { + private const int FixedPointPrecision = 15; + + public struct WaveBufferInformation + { + public uint SourceSampleRate; + public float Pitch; + public ulong ExtraParameter; + public ulong ExtraParameterSize; + public int ChannelIndex; + public int ChannelCount; + public DecodingBehaviour DecodingBehaviour; + public SampleRateConversionQuality SrcQuality; + public SampleFormat SampleFormat; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetPitchLimitBySrcQuality(SampleRateConversionQuality quality) + { + return quality switch + { + SampleRateConversionQuality.Default or SampleRateConversionQuality.Low => 4, + SampleRateConversionQuality.High => 8, + _ => throw new ArgumentException(quality.ToString()), + }; + } + + public static void ProcessWaveBuffers(IVirtualMemoryManager memoryManager, Span outputBuffer, ref WaveBufferInformation info, Span wavebuffers, ref VoiceUpdateState voiceState, uint targetSampleRate, int sampleCount) + { + const int TempBufferSize = 0x3F00; + + Span tempBuffer = stackalloc short[TempBufferSize]; + + float sampleRateRatio = (float)info.SourceSampleRate / targetSampleRate * info.Pitch; + + float fraction = voiceState.Fraction; + int waveBufferIndex = (int)voiceState.WaveBufferIndex; + ulong playedSampleCount = voiceState.PlayedSampleCount; + int offset = voiceState.Offset; + uint waveBufferConsumed = voiceState.WaveBufferConsumed; + + int pitchMaxLength = GetPitchLimitBySrcQuality(info.SrcQuality); + + int totalNeededSize = (int)MathF.Truncate(fraction + sampleRateRatio * sampleCount); + + if (totalNeededSize + pitchMaxLength <= TempBufferSize && totalNeededSize >= 0) + { + int sourceSampleCountToProcess = sampleCount; + + int maxSampleCountPerIteration = Math.Min((int)MathF.Truncate((TempBufferSize - fraction) / sampleRateRatio), sampleCount); + + bool isStarving = false; + + int i = 0; + + while (i < sourceSampleCountToProcess) + { + int tempBufferIndex = 0; + + if (!info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion)) + { + voiceState.Pitch.AsSpan()[..pitchMaxLength].CopyTo(tempBuffer); + tempBufferIndex += pitchMaxLength; + } + + int sampleCountToProcess = Math.Min(sourceSampleCountToProcess, maxSampleCountPerIteration); + + int y = 0; + + int sampleCountToDecode = (int)MathF.Truncate(fraction + sampleRateRatio * sampleCountToProcess); + + while (y < sampleCountToDecode) + { + if (waveBufferIndex >= Constants.VoiceWaveBufferCount) + { + waveBufferIndex = 0; + playedSampleCount = 0; + } + + if (!voiceState.IsWaveBufferValid[waveBufferIndex]) + { + isStarving = true; + break; + } + + ref WaveBuffer waveBuffer = ref wavebuffers[waveBufferIndex]; + + if (offset == 0 && info.SampleFormat == SampleFormat.Adpcm && waveBuffer.Context != 0) + { + voiceState.LoopContext = memoryManager.Read(waveBuffer.Context); + } + + Span tempSpan = tempBuffer[(tempBufferIndex + y)..]; + + int decodedSampleCount = -1; + + int targetSampleStartOffset; + int targetSampleEndOffset; + + if (voiceState.LoopCount > 0 && waveBuffer.LoopStartSampleOffset != 0 && waveBuffer.LoopEndSampleOffset != 0 && waveBuffer.LoopStartSampleOffset <= waveBuffer.LoopEndSampleOffset) + { + targetSampleStartOffset = (int)waveBuffer.LoopStartSampleOffset; + targetSampleEndOffset = (int)waveBuffer.LoopEndSampleOffset; + } + else + { + targetSampleStartOffset = (int)waveBuffer.StartSampleOffset; + targetSampleEndOffset = (int)waveBuffer.EndSampleOffset; + } + + int targetWaveBufferSampleCount = targetSampleEndOffset - targetSampleStartOffset; + + switch (info.SampleFormat) + { + case SampleFormat.Adpcm: + ReadOnlySpan waveBufferAdpcm = ReadOnlySpan.Empty; + + if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0) + { + // TODO: we are possibly copying a lot of unneeded data here, we should only take what we need. + waveBufferAdpcm = memoryManager.GetSpan(waveBuffer.Buffer, (int)waveBuffer.BufferSize); + } + + ReadOnlySpan coefficients = MemoryMarshal.Cast(memoryManager.GetSpan(info.ExtraParameter, (int)info.ExtraParameterSize)); + decodedSampleCount = AdpcmHelper.Decode(tempSpan, waveBufferAdpcm, targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y, coefficients, ref voiceState.LoopContext); + break; + case SampleFormat.PcmInt16: + ReadOnlySpan waveBufferPcm16 = ReadOnlySpan.Empty; + + if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0) + { + ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset(targetSampleStartOffset, offset, info.ChannelCount); + int bufferSize = PcmHelper.GetBufferSize(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount; + + waveBufferPcm16 = MemoryMarshal.Cast(memoryManager.GetSpan(bufferOffset, bufferSize)); + } + + decodedSampleCount = PcmHelper.Decode(tempSpan, waveBufferPcm16, targetSampleStartOffset, targetSampleEndOffset, info.ChannelIndex, info.ChannelCount); + break; + case SampleFormat.PcmFloat: + ReadOnlySpan waveBufferPcmFloat = ReadOnlySpan.Empty; + + if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0) + { + ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset(targetSampleStartOffset, offset, info.ChannelCount); + int bufferSize = PcmHelper.GetBufferSize(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount; + + waveBufferPcmFloat = MemoryMarshal.Cast(memoryManager.GetSpan(bufferOffset, bufferSize)); + } + + decodedSampleCount = PcmHelper.Decode(tempSpan, waveBufferPcmFloat, targetSampleStartOffset, targetSampleEndOffset, info.ChannelIndex, info.ChannelCount); + break; + default: + Logger.Error?.Print(LogClass.AudioRenderer, "Unsupported sample format " + info.SampleFormat); + break; + } + + Debug.Assert(decodedSampleCount <= sampleCountToDecode); + + if (decodedSampleCount < 0) + { + Logger.Warning?.Print(LogClass.AudioRenderer, "Decoding failed, skipping WaveBuffer"); + + voiceState.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount); + decodedSampleCount = 0; + } + + y += decodedSampleCount; + offset += decodedSampleCount; + playedSampleCount += (uint)decodedSampleCount; + + if (offset >= targetWaveBufferSampleCount || decodedSampleCount == 0) + { + offset = 0; + + if (waveBuffer.Looping) + { + voiceState.LoopCount++; + + if (waveBuffer.LoopCount >= 0) + { + if (decodedSampleCount == 0 || voiceState.LoopCount > waveBuffer.LoopCount) + { + voiceState.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount); + } + } + + if (decodedSampleCount == 0) + { + isStarving = true; + break; + } + + if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.PlayedSampleCountResetWhenLooping)) + { + playedSampleCount = 0; + } + } + else + { + voiceState.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount); + } + } + } + + Span outputSpanInt = MemoryMarshal.Cast(outputBuffer[i..]); + + if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion)) + { + for (int j = 0; j < y; j++) + { + outputBuffer[j] = tempBuffer[j]; + } + } + else + { + Span tempSpan = tempBuffer[(tempBufferIndex + y)..]; + + tempSpan[..(sampleCountToDecode - y)].Clear(); + + ToFloat(outputBuffer, outputSpanInt, sampleCountToProcess); + + ResamplerHelper.Resample(outputBuffer, tempBuffer, sampleRateRatio, ref fraction, sampleCountToProcess, info.SrcQuality, y != sourceSampleCountToProcess || info.Pitch != 1.0f); + + tempBuffer.Slice(sampleCountToDecode, pitchMaxLength).CopyTo(voiceState.Pitch.AsSpan()); + } + + i += sampleCountToProcess; + } + + Debug.Assert(sourceSampleCountToProcess == i || !isStarving); + + voiceState.WaveBufferConsumed = waveBufferConsumed; + voiceState.Offset = offset; + voiceState.PlayedSampleCount = playedSampleCount; + voiceState.WaveBufferIndex = (uint)waveBufferIndex; + voiceState.Fraction = fraction; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ToFloatAvx(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 8; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Avx.ConvertToVector256Single(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ToFloatSse2(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Sse2.ConvertToVector128Single(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ToFloatAdvSimd(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = AdvSimd.ConvertToSingle(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToFloatSlow(Span output, ReadOnlySpan input, int sampleCount) + { + for (int i = 0; i < sampleCount; i++) + { + output[i] = input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToFloat(Span output, ReadOnlySpan input, int sampleCount) + { + if (Avx.IsSupported) + { + ToFloatAvx(output, input, sampleCount); + } + else if (Sse2.IsSupported) + { + ToFloatSse2(output, input, sampleCount); + } + else if (AdvSimd.IsSupported) + { + ToFloatAdvSimd(output, input, sampleCount); + } + else + { + ToFloatSlow(output, input, sampleCount); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToIntAvx(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 8; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Avx.ConvertToVector256Int32(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = (int)input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToIntSse2(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Sse2.ConvertToVector128Int32(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = (int)input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToIntAdvSimd(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = AdvSimd.ConvertToInt32RoundToZero(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = (int)input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToIntSlow(Span output, ReadOnlySpan input, int sampleCount) + { + for (int i = 0; i < sampleCount; i++) + { + output[i] = (int)input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToInt(Span output, ReadOnlySpan input, int sampleCount) + { + if (Avx.IsSupported) + { + ToIntAvx(output, input, sampleCount); + } + else if (Sse2.IsSupported) + { + ToIntSse2(output, input, sampleCount); + } + else if (AdvSimd.IsSupported) + { + ToIntAdvSimd(output, input, sampleCount); + } + else + { + ToIntSlow(output, input, sampleCount); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RemapLegacyChannelEffectMappingToChannelResourceMapping(bool isSupported, Span bufferIndices, uint channelCount) + { + if (!isSupported && channelCount == 6) + { + ushort backLeft = bufferIndices[2]; + ushort backRight = bufferIndices[3]; + ushort frontCenter = bufferIndices[4]; + ushort lowFrequency = bufferIndices[5]; + + bufferIndices[2] = frontCenter; + bufferIndices[3] = lowFrequency; + bufferIndices[4] = backLeft; + bufferIndices[5] = backRight; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RemapChannelResourceMappingToLegacy(bool isSupported, Span bufferIndices, uint channelCount) + { + if (isSupported && channelCount == 6) + { + ushort frontCenter = bufferIndices[2]; + ushort lowFrequency = bufferIndices[3]; + ushort backLeft = bufferIndices[4]; + ushort backRight = bufferIndices[5]; + + bufferIndices[2] = backLeft; + bufferIndices[3] = backRight; + bufferIndices[4] = frontCenter; + bufferIndices[5] = lowFrequency; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs new file mode 100644 index 00000000..7253fdc9 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs @@ -0,0 +1,52 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public class DecayDelay : IDelayLine + { + private readonly IDelayLine _delayLine; + + public uint CurrentSampleCount => _delayLine.CurrentSampleCount; + + public uint SampleCountMax => _delayLine.SampleCountMax; + + private float _decayRate; + + public DecayDelay(IDelayLine delayLine) + { + _decayRate = 0.0f; + _delayLine = delayLine; + } + + public void SetDecayRate(float decayRate) + { + _decayRate = decayRate; + } + + public float Update(float value) + { + float delayLineValue = _delayLine.Read(); + float processedValue = value - (_decayRate * delayLineValue); + + return _delayLine.Update(processedValue) + processedValue * _decayRate; + } + + public void SetDelay(float delayTime) + { + _delayLine.SetDelay(delayTime); + } + + public float Read() + { + return _delayLine.Read(); + } + + public float TapUnsafe(uint sampleIndex, int offset) + { + return _delayLine.TapUnsafe(sampleIndex, offset); + } + + public float Tap(uint sampleIndex) + { + return _delayLine.Tap(sampleIndex); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs new file mode 100644 index 00000000..8a3590a2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs @@ -0,0 +1,78 @@ +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public class DelayLine : IDelayLine + { + private readonly float[] _workBuffer; + private readonly uint _sampleRate; + private uint _currentSampleIndex; + private uint _lastSampleIndex; + + public uint CurrentSampleCount { get; private set; } + public uint SampleCountMax { get; private set; } + + public DelayLine(uint sampleRate, float delayTimeMax) + { + _sampleRate = sampleRate; + SampleCountMax = IDelayLine.GetSampleCount(_sampleRate, delayTimeMax); + _workBuffer = new float[SampleCountMax + 1]; + + SetDelay(delayTimeMax); + } + + private void ConfigureDelay(uint targetSampleCount) + { + CurrentSampleCount = Math.Min(SampleCountMax, targetSampleCount); + _currentSampleIndex = 0; + + if (CurrentSampleCount == 0) + { + _lastSampleIndex = 0; + } + else + { + _lastSampleIndex = CurrentSampleCount - 1; + } + } + + public void SetDelay(float delayTime) + { + ConfigureDelay(IDelayLine.GetSampleCount(_sampleRate, delayTime)); + } + + public float Read() + { + return _workBuffer[_currentSampleIndex]; + } + + public float Update(float value) + { + float output = Read(); + + _workBuffer[_currentSampleIndex++] = value; + + if (_currentSampleIndex >= _lastSampleIndex) + { + _currentSampleIndex = 0; + } + + return output; + } + + public float TapUnsafe(uint sampleIndex, int offset) + { + return IDelayLine.Tap(_workBuffer, (int)_currentSampleIndex, (int)sampleIndex + offset, (int)CurrentSampleCount); + } + + public float Tap(uint sampleIndex) + { + if (sampleIndex >= CurrentSampleCount) + { + sampleIndex = CurrentSampleCount - 1; + } + + return TapUnsafe(sampleIndex, -1); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs new file mode 100644 index 00000000..ed8e7cfe --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs @@ -0,0 +1,76 @@ +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public class DelayLine3d : IDelayLine + { + private readonly float[] _workBuffer; + private readonly uint _sampleRate; + private uint _currentSampleIndex; + private uint _lastSampleIndex; + + public uint CurrentSampleCount { get; private set; } + public uint SampleCountMax { get; private set; } + + public DelayLine3d(uint sampleRate, float delayTimeMax) + { + _sampleRate = sampleRate; + SampleCountMax = IDelayLine.GetSampleCount(_sampleRate, delayTimeMax); + _workBuffer = new float[SampleCountMax + 1]; + + SetDelay(delayTimeMax); + } + + private void ConfigureDelay(uint targetSampleCount) + { + if (SampleCountMax >= targetSampleCount) + { + CurrentSampleCount = targetSampleCount; + _lastSampleIndex = (_currentSampleIndex + targetSampleCount) % (SampleCountMax + 1); + } + } + + public void SetDelay(float delayTime) + { + ConfigureDelay(IDelayLine.GetSampleCount(_sampleRate, delayTime)); + } + + public float Read() + { + return _workBuffer[_currentSampleIndex]; + } + + public float Update(float value) + { + Debug.Assert(!float.IsNaN(value) && !float.IsInfinity(value)); + + _workBuffer[_lastSampleIndex++] = value; + + float output = Read(); + + _currentSampleIndex++; + + if (_currentSampleIndex >= SampleCountMax) + { + _currentSampleIndex = 0; + } + + if (_lastSampleIndex >= SampleCountMax) + { + _lastSampleIndex = 0; + } + + return output; + } + + public float TapUnsafe(uint sampleIndex, int offset) + { + return IDelayLine.Tap(_workBuffer, (int)_lastSampleIndex, (int)sampleIndex + offset, (int)SampleCountMax + 1); + } + + public float Tap(uint sampleIndex) + { + return TapUnsafe(sampleIndex, -1); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs new file mode 100644 index 00000000..d44aec0a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public struct ExponentialMovingAverage + { + private float _mean; + + public ExponentialMovingAverage(float mean) + { + _mean = mean; + } + + public readonly float Read() + { + return _mean; + } + + public float Update(float value, float alpha) + { + _mean += alpha * (value - _mean); + + return _mean; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs new file mode 100644 index 00000000..b408e294 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public interface IDelayLine + { + uint CurrentSampleCount { get; } + uint SampleCountMax { get; } + + void SetDelay(float delayTime); + float Read(); + float Update(float value); + + float TapUnsafe(uint sampleIndex, int offset); + float Tap(uint sampleIndex); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Tap(Span workBuffer, int baseIndex, int sampleIndex, int delaySampleCount) + { + int targetIndex = baseIndex - sampleIndex; + + if (targetIndex < 0) + { + targetIndex += delaySampleCount; + } + + return workBuffer[targetIndex]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint GetSampleCount(uint sampleRate, float delayTime) + { + return (uint)MathF.Round(sampleRate * delayTime); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs new file mode 100644 index 00000000..d519de33 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs @@ -0,0 +1,39 @@ +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class FixedPointHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ToInt(long value, int qBits) + { + return (int)(value >> qBits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ToFloat(long value, int qBits) + { + return (float)value / (1 << qBits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ConvertFloat(float value, int qBits) + { + return value / (1 << qBits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ToFixed(float value, int qBits) + { + return (int)(value * (1 << qBits)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int RoundUpAndToInt(long value, int qBits) + { + int half = 1 << (qBits - 1); + + return ToInt(value + half, qBits); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs new file mode 100644 index 00000000..415e1c19 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs @@ -0,0 +1,103 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class FloatingPointHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MultiplyRoundDown(float a, float b) + { + return RoundDown(a * b); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float RoundDown(float a) + { + return MathF.Round(a, 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float RoundUp(float a) + { + return MathF.Round(a); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MultiplyRoundUp(float a, float b) + { + return RoundUp(a * b); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Pow10(float x) + { + // NOTE: Nintendo implementation uses Q15 and a LUT for this, we don't. + // As such, we support the same ranges as Nintendo to avoid unexpected behaviours. + if (x >= 0.0f) + { + return 1.0f; + } + + if (x <= -5.3f) + { + return 0.0f; + } + + return MathF.Pow(10, x); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Log10(float x) + { + // NOTE: Nintendo uses an approximation of log10, we don't. + // As such, we support the same ranges as Nintendo to avoid unexpected behaviours. + return MathF.Log10(MathF.Max(x, 1.0e-10f)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MeanSquare(ReadOnlySpan inputs) + { + float res = 0.0f; + + foreach (float input in inputs) + { + float normInput = input * (1f / 32768f); + res += normInput * normInput; + } + + res /= inputs.Length; + + return res; + } + + /// + /// Map decibel to linear. + /// + /// The decibel value to convert + /// Converted linear value/returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DecibelToLinear(float db) + { + return MathF.Pow(10.0f, db / 20.0f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DegreesToRadians(float degrees) + { + return degrees * MathF.PI / 180.0f; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Cos(float value) + { + return MathF.Cos(DegreesToRadians(value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Sin(float value) + { + return MathF.Sin(DegreesToRadians(value)); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs new file mode 100644 index 00000000..8134e6b7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs @@ -0,0 +1,134 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class PcmHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetCountToDecode(int startSampleOffset, int endSampleOffset, int offset, int count) + { + return Math.Min(count, endSampleOffset - startSampleOffset - offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong GetBufferOffset(int startSampleOffset, int offset, int channelCount) where T : unmanaged + { + return (ulong)(Unsafe.SizeOf() * channelCount * (startSampleOffset + offset)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBufferSize(int startSampleOffset, int endSampleOffset, int offset, int count) where T : unmanaged + { + if (endSampleOffset < startSampleOffset) + { + return 0; + } + + return GetCountToDecode(startSampleOffset, endSampleOffset, offset, count) * Unsafe.SizeOf(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ConvertSampleToPcmFloat(short sample) + { + return (float)sample / short.MaxValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ConvertSampleToPcmInt16(float sample) + { + return Saturate(sample * short.MaxValue); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertSampleToPcm8(Span output, ReadOnlySpan input) + { + for (int i = 0; i < input.Length; i++) + { + // Output most significant byte + output[i] = (sbyte)(input[i] >> 8); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertSampleToPcm24(Span output, ReadOnlySpan input) + { + for (int i = 0; i < input.Length; i++) + { + output[i * 3 + 2] = (byte)(input[i] >> 8); + output[i * 3 + 1] = (byte)(input[i] & 0xff); + output[i * 3 + 0] = 0; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertSampleToPcm32(Span output, ReadOnlySpan input) + { + for (int i = 0; i < input.Length; i++) + { + output[i] = input[i] << 16; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertSampleToPcmFloat(Span output, ReadOnlySpan input) + { + for (int i = 0; i < input.Length; i++) + { + output[i] = ConvertSampleToPcmFloat(input[i]); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Decode(Span output, ReadOnlySpan input, int startSampleOffset, int endSampleOffset, int channelIndex, int channelCount) + { + if (input.IsEmpty || endSampleOffset < startSampleOffset) + { + return 0; + } + + int decodedCount = input.Length / channelCount; + + for (int i = 0; i < decodedCount; i++) + { + output[i] = input[i * channelCount + channelIndex]; + } + + return decodedCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Decode(Span output, ReadOnlySpan input, int startSampleOffset, int endSampleOffset, int channelIndex, int channelCount) + { + if (input.IsEmpty || endSampleOffset < startSampleOffset) + { + return 0; + } + + int decodedCount = input.Length / channelCount; + + for (int i = 0; i < decodedCount; i++) + { + output[i] = ConvertSampleToPcmInt16(input[i * channelCount + channelIndex]); + } + + return decodedCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short Saturate(float value) + { + if (value > short.MaxValue) + { + return short.MaxValue; + } + + if (value < short.MinValue) + { + return short.MinValue; + } + + return (short)value; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs new file mode 100644 index 00000000..e44e9f41 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs @@ -0,0 +1,599 @@ +using System; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class ResamplerHelper + { + #region "Default Quality Lookup Tables" + private static readonly short[] _normalCurveLut0 = { + 6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239, 19412, 7093, 22, + 6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377, 7472, 41, 5773, 19361, 7600, 48, + 5659, 19342, 7728, 55, 5546, 19321, 7857, 62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77, + 5213, 19245, 8249, 84, 5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109, + 4785, 19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988, 9183, 147, + 4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590, 179, 4083, 18793, 9726, 190, + 3987, 18738, 9863, 202, 3893, 18682, 10000, 215, 3800, 18624, 10137, 228, 3709, 18563, 10274, 241, + 3618, 18500, 10411, 255, 3529, 18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300, + 3269, 18230, 10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375, 369, + 2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428, 2709, 17681, 11925, 449, + 2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489, 17418, 12334, 517, 2418, 17327, 12470, 541, + 2348, 17234, 12606, 566, 2280, 17140, 12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647, + 2083, 16846, 13144, 675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766, + 1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667, 16109, 14062, 901, + 1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772, 14445, 1013, 1457, 15657, 14571, 1052, + 1407, 15540, 14695, 1093, 1359, 15423, 14819, 1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221, + 1221, 15064, 15185, 1266, 1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407, + 1052, 14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191, 15998, 1613, + 901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327, 1780, 798, 13673, 16434, 1838, + 766, 13541, 16539, 1897, 735, 13409, 16643, 1958, 704, 13277, 16745, 2020, 675, 13144, 16846, 2083, + 647, 13010, 16946, 2147, 619, 12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348, + 541, 12470, 17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595, 2635, + 449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863, 388, 11513, 17927, 2942, + 369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334, 11100, 18157, 3186, 317, 10962, 18230, 3269, + 300, 10824, 18300, 3355, 285, 10687, 18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618, + 241, 10274, 18563, 3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987, + 190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157, 9318, 18942, 4377, + 147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914, 19073, 4681, 118, 8780, 19112, 4785, + 109, 8646, 19148, 4890, 101, 8513, 19183, 4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213, + 77, 8118, 19273, 5323, 69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659, + 48, 7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219, 19403, 6121, + 22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424, 6479, 3, 6722, 19426, 6600, + }; + + private static readonly short[] _normalCurveLut1 = { + -68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450, 32586, 512, -36, + -568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454, 1000, -69, -891, 32393, 1174, -80, + -990, 32323, 1352, -92, -1084, 32244, 1536, -103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128, + -1338, 31956, 2118, -140, -1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180, + -1617, 31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995, 3657, -240, + -1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393, -289, -1951, 30272, 4648, -307, + -1984, 30072, 4908, -325, -2014, 29866, 5172, -343, -2040, 29652, 5442, -362, -2063, 29431, 5716, -382, + -2083, 29203, 5994, -403, -2100, 28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468, + -2133, 28226, 7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066, -563, + -2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641, -2121, 26285, 9336, -668, + -2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084, 25375, 10326, -753, -2067, 25063, 10662, -783, + -2049, 24746, 11000, -813, -2030, 24425, 11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907, + -1962, 23438, 12382, -939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039, + -1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764, 21027, 14877, -1176, + -1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959, 15965, -1282, -1633, 19600, 16329, -1317, + -1599, 19239, 16694, -1353, -1564, 18878, 17058, -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459, + -1459, 17787, 18151, -1495, -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599, + -1317, 16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239, 20673, -1732, + -1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729, -1825, -1072, 13798, 22077, -1855, + -1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911, -972, 12733, 23103, -1937, -939, 12382, 23438, -1962, + -907, 12033, 23771, -1986, -875, 11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049, + -783, 10662, 25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987, -2111, + -668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136, -588, 8378, 27151, -2141, + -563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514, 7453, 27966, -2139, -490, 7153, 28226, -2133, + -468, 6857, 28480, -2125, -445, 6565, 28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083, + -382, 5716, 29431, -2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984, + -307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256, 3897, 30826, -1830, + -240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192, 31310, -1676, -194, 2967, 31456, -1617, + -180, 2747, 31593, -1554, -167, 2532, 31723, -1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338, + -128, 1919, 32061, -1258, -115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990, + -80, 1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669, 32551, -568, + -36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630, -200, -5, 69, 32639, -68, + }; + + private static readonly short[] _normalCurveLut2 = { + 3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811, 26253, 3751, -42, + 2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169, 4199, -54, 2338, 26130, 4354, -58, + 2227, 26085, 4512, -63, 2120, 26035, 4673, -67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76, + 1813, 25852, 5174, -81, 1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98, + 1442, 25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239, 6442, -121, + 1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027, -140, 897, 24776, 7227, -146, + 829, 24648, 7430, -153, 764, 24516, 7635, -159, 701, 24379, 7842, -166, 641, 24237, 8052, -174, + 583, 24091, 8264, -181, 526, 23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202, + 371, 23462, 9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809, -230, + 194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250, 81, 22208, 10735, -258, + 47, 22015, 10970, -265, 15, 21818, 11206, -271, -16, 21618, 11444, -277, -44, 21415, 11684, -283, + -71, 21208, 11924, -290, -97, 20999, 12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306, + -163, 20354, 12898, -311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325, + -234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273, 18765, 14625, -337, + -284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057, 15369, -341, -310, 17817, 15617, -341, + -317, 17577, 15864, -340, -323, 17335, 16111, -340, -328, 17092, 16357, -338, -332, 16848, 16603, -336, + -336, 16603, 16848, -332, -338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317, + -341, 15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873, 18531, -284, + -337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230, -248, -328, 13882, 19459, -234, + -325, 13635, 19686, -218, -321, 13389, 19911, -201, -316, 13143, 20134, -183, -311, 12898, 20354, -163, + -306, 12653, 20571, -143, -302, 12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71, + -283, 11684, 21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015, 47, + -258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154, -237, 10038, 22769, 194, + -230, 9809, 22948, 236, -222, 9583, 23123, 279, -215, 9358, 23295, 324, -209, 9135, 23462, 371, + -202, 8914, 23626, 420, -194, 8695, 23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583, + -174, 8052, 24237, 641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829, + -146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127, 6635, 25131, 1115, + -121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066, 25440, 1357, -103, 5882, 25533, 1442, + -98, 5701, 25621, 1531, -92, 5522, 25704, 1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813, + -76, 5004, 25919, 1912, -72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227, + -58, 4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897, 26230, 2688, + -42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281, 3064, -32, 3329, 26287, 3195, + }; + #endregion + + #region "High Quality Lookup Tables" + private static readonly short[] _highCurveLut0 = { + -582, -23, 8740, 16386, 8833, 8, -590, 0, -573, -54, 8647, 16385, 8925, 40, -598, -1, + -565, -84, 8555, 16383, 9018, 72, -606, -1, -557, -113, 8462, 16379, 9110, 105, -614, -2, + -549, -142, 8370, 16375, 9203, 139, -622, -2, -541, -170, 8277, 16369, 9295, 173, -630, -3, + -533, -198, 8185, 16362, 9387, 208, -638, -4, -525, -225, 8093, 16354, 9480, 244, -646, -5, + -516, -251, 8000, 16344, 9572, 280, -654, -5, -508, -277, 7908, 16334, 9664, 317, -662, -6, + -500, -302, 7816, 16322, 9756, 355, -670, -7, -492, -327, 7724, 16310, 9847, 393, -678, -8, + -484, -351, 7632, 16296, 9939, 432, -686, -9, -476, -374, 7540, 16281, 10030, 471, -694, -10, + -468, -397, 7449, 16265, 10121, 511, -702, -11, -460, -419, 7357, 16247, 10212, 552, -709, -13, + -452, -441, 7266, 16229, 10303, 593, -717, -14, -445, -462, 7175, 16209, 10394, 635, -724, -15, + -437, -483, 7084, 16189, 10484, 678, -732, -16, -429, -503, 6994, 16167, 10574, 722, -739, -18, + -421, -523, 6903, 16144, 10664, 766, -747, -19, -414, -542, 6813, 16120, 10754, 810, -754, -21, + -406, -560, 6723, 16095, 10843, 856, -761, -22, -398, -578, 6633, 16068, 10932, 902, -768, -24, + -391, -596, 6544, 16041, 11021, 949, -775, -26, -383, -612, 6454, 16012, 11109, 996, -782, -27, + -376, -629, 6366, 15983, 11197, 1044, -789, -29, -368, -645, 6277, 15952, 11285, 1093, -796, -31, + -361, -660, 6189, 15920, 11372, 1142, -802, -33, -354, -675, 6100, 15887, 11459, 1192, -809, -35, + -347, -689, 6013, 15853, 11546, 1243, -815, -37, -339, -703, 5925, 15818, 11632, 1294, -821, -39, + -332, -717, 5838, 15782, 11718, 1346, -827, -41, -325, -730, 5751, 15745, 11803, 1399, -833, -43, + -318, -742, 5665, 15707, 11888, 1452, -839, -46, -312, -754, 5579, 15668, 11973, 1506, -845, -48, + -305, -766, 5493, 15627, 12057, 1561, -850, -50, -298, -777, 5408, 15586, 12140, 1616, -855, -53, + -291, -787, 5323, 15544, 12224, 1672, -861, -56, -285, -798, 5239, 15500, 12306, 1729, -866, -58, + -278, -807, 5155, 15456, 12388, 1786, -871, -61, -272, -817, 5071, 15410, 12470, 1844, -875, -64, + -265, -826, 4988, 15364, 12551, 1902, -880, -67, -259, -834, 4905, 15317, 12631, 1962, -884, -70, + -253, -842, 4823, 15268, 12711, 2022, -888, -73, -247, -850, 4741, 15219, 12790, 2082, -892, -76, + -241, -857, 4659, 15168, 12869, 2143, -896, -79, -235, -864, 4578, 15117, 12947, 2205, -899, -82, + -229, -870, 4498, 15065, 13025, 2267, -903, -85, -223, -876, 4417, 15012, 13102, 2331, -906, -89, + -217, -882, 4338, 14958, 13178, 2394, -909, -92, -211, -887, 4259, 14903, 13254, 2459, -911, -96, + -206, -892, 4180, 14847, 13329, 2523, -914, -100, -200, -896, 4102, 14790, 13403, 2589, -916, -103, + -195, -900, 4024, 14732, 13477, 2655, -918, -107, -190, -904, 3947, 14673, 13550, 2722, -919, -111, + -184, -908, 3871, 14614, 13622, 2789, -921, -115, -179, -911, 3795, 14553, 13693, 2857, -922, -119, + -174, -913, 3719, 14492, 13764, 2926, -923, -123, -169, -916, 3644, 14430, 13834, 2995, -923, -127, + -164, -918, 3570, 14367, 13904, 3065, -924, -132, -159, -920, 3496, 14303, 13972, 3136, -924, -136, + -154, -921, 3423, 14239, 14040, 3207, -924, -140, -150, -922, 3350, 14173, 14107, 3278, -923, -145, + -145, -923, 3278, 14107, 14173, 3350, -922, -150, -140, -924, 3207, 14040, 14239, 3423, -921, -154, + -136, -924, 3136, 13972, 14303, 3496, -920, -159, -132, -924, 3065, 13904, 14367, 3570, -918, -164, + -127, -923, 2995, 13834, 14430, 3644, -916, -169, -123, -923, 2926, 13764, 14492, 3719, -913, -174, + -119, -922, 2857, 13693, 14553, 3795, -911, -179, -115, -921, 2789, 13622, 14614, 3871, -908, -184, + -111, -919, 2722, 13550, 14673, 3947, -904, -190, -107, -918, 2655, 13477, 14732, 4024, -900, -195, + -103, -916, 2589, 13403, 14790, 4102, -896, -200, -100, -914, 2523, 13329, 14847, 4180, -892, -206, + -96, -911, 2459, 13254, 14903, 4259, -887, -211, -92, -909, 2394, 13178, 14958, 4338, -882, -217, + -89, -906, 2331, 13102, 15012, 4417, -876, -223, -85, -903, 2267, 13025, 15065, 4498, -870, -229, + -82, -899, 2205, 12947, 15117, 4578, -864, -235, -79, -896, 2143, 12869, 15168, 4659, -857, -241, + -76, -892, 2082, 12790, 15219, 4741, -850, -247, -73, -888, 2022, 12711, 15268, 4823, -842, -253, + -70, -884, 1962, 12631, 15317, 4905, -834, -259, -67, -880, 1902, 12551, 15364, 4988, -826, -265, + -64, -875, 1844, 12470, 15410, 5071, -817, -272, -61, -871, 1786, 12388, 15456, 5155, -807, -278, + -58, -866, 1729, 12306, 15500, 5239, -798, -285, -56, -861, 1672, 12224, 15544, 5323, -787, -291, + -53, -855, 1616, 12140, 15586, 5408, -777, -298, -50, -850, 1561, 12057, 15627, 5493, -766, -305, + -48, -845, 1506, 11973, 15668, 5579, -754, -312, -46, -839, 1452, 11888, 15707, 5665, -742, -318, + -43, -833, 1399, 11803, 15745, 5751, -730, -325, -41, -827, 1346, 11718, 15782, 5838, -717, -332, + -39, -821, 1294, 11632, 15818, 5925, -703, -339, -37, -815, 1243, 11546, 15853, 6013, -689, -347, + -35, -809, 1192, 11459, 15887, 6100, -675, -354, -33, -802, 1142, 11372, 15920, 6189, -660, -361, + -31, -796, 1093, 11285, 15952, 6277, -645, -368, -29, -789, 1044, 11197, 15983, 6366, -629, -376, + -27, -782, 996, 11109, 16012, 6454, -612, -383, -26, -775, 949, 11021, 16041, 6544, -596, -391, + -24, -768, 902, 10932, 16068, 6633, -578, -398, -22, -761, 856, 10843, 16095, 6723, -560, -406, + -21, -754, 810, 10754, 16120, 6813, -542, -414, -19, -747, 766, 10664, 16144, 6903, -523, -421, + -18, -739, 722, 10574, 16167, 6994, -503, -429, -16, -732, 678, 10484, 16189, 7084, -483, -437, + -15, -724, 635, 10394, 16209, 7175, -462, -445, -14, -717, 593, 10303, 16229, 7266, -441, -452, + -13, -709, 552, 10212, 16247, 7357, -419, -460, -11, -702, 511, 10121, 16265, 7449, -397, -468, + -10, -694, 471, 10030, 16281, 7540, -374, -476, -9, -686, 432, 9939, 16296, 7632, -351, -484, + -8, -678, 393, 9847, 16310, 7724, -327, -492, -7, -670, 355, 9756, 16322, 7816, -302, -500, + -6, -662, 317, 9664, 16334, 7908, -277, -508, -5, -654, 280, 9572, 16344, 8000, -251, -516, + -5, -646, 244, 9480, 16354, 8093, -225, -525, -4, -638, 208, 9387, 16362, 8185, -198, -533, + -3, -630, 173, 9295, 16369, 8277, -170, -541, -2, -622, 139, 9203, 16375, 8370, -142, -549, + -2, -614, 105, 9110, 16379, 8462, -113, -557, -1, -606, 72, 9018, 16383, 8555, -84, -565, + -1, -598, 40, 8925, 16385, 8647, -54, -573, 0, -590, 8, 8833, 16386, 8740, -23, -582, + }; + + private static readonly short[] _highCurveLut1 = { + -12, 47, -134, 32767, 81, -16, 2, 0, -26, 108, -345, 32760, 301, -79, 17, -1, + -40, 168, -552, 32745, 526, -144, 32, -2, -53, 226, -753, 32723, 755, -210, 47, -3, + -66, 284, -950, 32694, 989, -277, 63, -5, -78, 340, -1143, 32658, 1226, -346, 79, -6, + -90, 394, -1331, 32615, 1469, -415, 96, -8, -101, 447, -1514, 32564, 1715, -486, 113, -9, + -112, 499, -1692, 32506, 1966, -557, 130, -11, -123, 550, -1865, 32441, 2221, -630, 148, -13, + -133, 599, -2034, 32369, 2480, -703, 166, -14, -143, 646, -2198, 32290, 2743, -778, 185, -16, + -152, 693, -2357, 32204, 3010, -853, 204, -18, -162, 738, -2512, 32110, 3281, -929, 223, -20, + -170, 781, -2662, 32010, 3555, -1007, 242, -23, -178, 823, -2807, 31903, 3834, -1084, 262, -25, + -186, 864, -2947, 31789, 4116, -1163, 282, -27, -194, 903, -3082, 31668, 4403, -1242, 303, -30, + -201, 940, -3213, 31540, 4692, -1322, 323, -32, -207, 977, -3339, 31406, 4985, -1403, 344, -35, + -214, 1011, -3460, 31265, 5282, -1484, 365, -37, -220, 1045, -3577, 31117, 5582, -1566, 387, -40, + -225, 1077, -3688, 30963, 5885, -1648, 409, -43, -230, 1107, -3796, 30802, 6191, -1730, 431, -46, + -235, 1136, -3898, 30635, 6501, -1813, 453, -49, -240, 1164, -3996, 30462, 6813, -1896, 475, -52, + -244, 1190, -4089, 30282, 7128, -1980, 498, -55, -247, 1215, -4178, 30097, 7446, -2064, 520, -58, + -251, 1239, -4262, 29905, 7767, -2148, 543, -62, -254, 1261, -4342, 29707, 8091, -2231, 566, -65, + -257, 1281, -4417, 29503, 8416, -2315, 589, -69, -259, 1301, -4488, 29293, 8745, -2399, 613, -72, + -261, 1319, -4555, 29078, 9075, -2483, 636, -76, -263, 1336, -4617, 28857, 9408, -2567, 659, -80, + -265, 1351, -4674, 28631, 9743, -2651, 683, -83, -266, 1365, -4728, 28399, 10080, -2734, 706, -87, + -267, 1378, -4777, 28161, 10418, -2817, 730, -91, -267, 1389, -4822, 27919, 10759, -2899, 753, -95, + -268, 1400, -4863, 27671, 11100, -2981, 777, -99, -268, 1409, -4900, 27418, 11444, -3063, 800, -103, + -268, 1416, -4933, 27161, 11789, -3144, 824, -107, -267, 1423, -4962, 26898, 12135, -3224, 847, -112, + -267, 1428, -4987, 26631, 12482, -3303, 870, -116, -266, 1433, -5008, 26359, 12830, -3382, 893, -120, + -265, 1436, -5026, 26083, 13179, -3460, 916, -125, -264, 1438, -5039, 25802, 13529, -3537, 939, -129, + -262, 1438, -5049, 25517, 13880, -3613, 962, -133, -260, 1438, -5055, 25228, 14231, -3687, 984, -138, + -258, 1437, -5058, 24935, 14582, -3761, 1006, -142, -256, 1435, -5058, 24639, 14934, -3833, 1028, -147, + -254, 1431, -5053, 24338, 15286, -3904, 1049, -151, -252, 1427, -5046, 24034, 15638, -3974, 1071, -155, + -249, 1422, -5035, 23726, 15989, -4042, 1091, -160, -246, 1416, -5021, 23415, 16341, -4109, 1112, -164, + -243, 1408, -5004, 23101, 16691, -4174, 1132, -169, -240, 1400, -4984, 22783, 17042, -4237, 1152, -173, + -237, 1392, -4960, 22463, 17392, -4299, 1171, -178, -234, 1382, -4934, 22140, 17740, -4358, 1190, -182, + -230, 1371, -4905, 21814, 18088, -4416, 1209, -186, -227, 1360, -4873, 21485, 18435, -4472, 1226, -191, + -223, 1348, -4839, 21154, 18781, -4526, 1244, -195, -219, 1335, -4801, 20821, 19125, -4578, 1260, -199, + -215, 1321, -4761, 20486, 19468, -4627, 1277, -203, -211, 1307, -4719, 20148, 19809, -4674, 1292, -207, + -207, 1292, -4674, 19809, 20148, -4719, 1307, -211, -203, 1277, -4627, 19468, 20486, -4761, 1321, -215, + -199, 1260, -4578, 19125, 20821, -4801, 1335, -219, -195, 1244, -4526, 18781, 21154, -4839, 1348, -223, + -191, 1226, -4472, 18435, 21485, -4873, 1360, -227, -186, 1209, -4416, 18088, 21814, -4905, 1371, -230, + -182, 1190, -4358, 17740, 22140, -4934, 1382, -234, -178, 1171, -4299, 17392, 22463, -4960, 1392, -237, + -173, 1152, -4237, 17042, 22783, -4984, 1400, -240, -169, 1132, -4174, 16691, 23101, -5004, 1408, -243, + -164, 1112, -4109, 16341, 23415, -5021, 1416, -246, -160, 1091, -4042, 15989, 23726, -5035, 1422, -249, + -155, 1071, -3974, 15638, 24034, -5046, 1427, -252, -151, 1049, -3904, 15286, 24338, -5053, 1431, -254, + -147, 1028, -3833, 14934, 24639, -5058, 1435, -256, -142, 1006, -3761, 14582, 24935, -5058, 1437, -258, + -138, 984, -3687, 14231, 25228, -5055, 1438, -260, -133, 962, -3613, 13880, 25517, -5049, 1438, -262, + -129, 939, -3537, 13529, 25802, -5039, 1438, -264, -125, 916, -3460, 13179, 26083, -5026, 1436, -265, + -120, 893, -3382, 12830, 26359, -5008, 1433, -266, -116, 870, -3303, 12482, 26631, -4987, 1428, -267, + -112, 847, -3224, 12135, 26898, -4962, 1423, -267, -107, 824, -3144, 11789, 27161, -4933, 1416, -268, + -103, 800, -3063, 11444, 27418, -4900, 1409, -268, -99, 777, -2981, 11100, 27671, -4863, 1400, -268, + -95, 753, -2899, 10759, 27919, -4822, 1389, -267, -91, 730, -2817, 10418, 28161, -4777, 1378, -267, + -87, 706, -2734, 10080, 28399, -4728, 1365, -266, -83, 683, -2651, 9743, 28631, -4674, 1351, -265, + -80, 659, -2567, 9408, 28857, -4617, 1336, -263, -76, 636, -2483, 9075, 29078, -4555, 1319, -261, + -72, 613, -2399, 8745, 29293, -4488, 1301, -259, -69, 589, -2315, 8416, 29503, -4417, 1281, -257, + -65, 566, -2231, 8091, 29707, -4342, 1261, -254, -62, 543, -2148, 7767, 29905, -4262, 1239, -251, + -58, 520, -2064, 7446, 30097, -4178, 1215, -247, -55, 498, -1980, 7128, 30282, -4089, 1190, -244, + -52, 475, -1896, 6813, 30462, -3996, 1164, -240, -49, 453, -1813, 6501, 30635, -3898, 1136, -235, + -46, 431, -1730, 6191, 30802, -3796, 1107, -230, -43, 409, -1648, 5885, 30963, -3688, 1077, -225, + -40, 387, -1566, 5582, 31117, -3577, 1045, -220, -37, 365, -1484, 5282, 31265, -3460, 1011, -214, + -35, 344, -1403, 4985, 31406, -3339, 977, -207, -32, 323, -1322, 4692, 31540, -3213, 940, -201, + -30, 303, -1242, 4403, 31668, -3082, 903, -194, -27, 282, -1163, 4116, 31789, -2947, 864, -186, + -25, 262, -1084, 3834, 31903, -2807, 823, -178, -23, 242, -1007, 3555, 32010, -2662, 781, -170, + -20, 223, -929, 3281, 32110, -2512, 738, -162, -18, 204, -853, 3010, 32204, -2357, 693, -152, + -16, 185, -778, 2743, 32290, -2198, 646, -143, -14, 166, -703, 2480, 32369, -2034, 599, -133, + -13, 148, -630, 2221, 32441, -1865, 550, -123, -11, 130, -557, 1966, 32506, -1692, 499, -112, + -9, 113, -486, 1715, 32564, -1514, 447, -101, -8, 96, -415, 1469, 32615, -1331, 394, -90, + -6, 79, -346, 1226, 32658, -1143, 340, -78, -5, 63, -277, 989, 32694, -950, 284, -66, + -3, 47, -210, 755, 32723, -753, 226, -53, -2, 32, -144, 526, 32745, -552, 168, -40, + -1, 17, -79, 301, 32760, -345, 108, -26, 0, 2, -16, 81, 32767, -134, 47, -12, + }; + + private static readonly short[] _highCurveLut2 = { + 418, -2538, 6118, 24615, 6298, -2563, 417, 0, 420, -2512, 5939, 24611, 6479, -2588, 415, 1, + 421, -2485, 5761, 24605, 6662, -2612, 412, 2, 422, -2458, 5585, 24595, 6846, -2635, 409, 3, + 423, -2430, 5410, 24582, 7030, -2658, 406, 4, 423, -2402, 5236, 24565, 7216, -2680, 403, 5, + 423, -2373, 5064, 24546, 7403, -2701, 399, 6, 423, -2343, 4893, 24523, 7591, -2721, 395, 7, + 423, -2313, 4724, 24496, 7780, -2741, 391, 8, 422, -2283, 4556, 24467, 7970, -2759, 386, 9, + 421, -2252, 4390, 24434, 8161, -2777, 381, 11, 420, -2221, 4225, 24398, 8353, -2794, 376, 12, + 419, -2190, 4062, 24359, 8545, -2810, 370, 14, 418, -2158, 3900, 24316, 8739, -2825, 364, 15, + 416, -2126, 3740, 24271, 8933, -2839, 358, 17, 414, -2093, 3582, 24222, 9127, -2851, 351, 19, + 412, -2060, 3425, 24170, 9323, -2863, 344, 21, 410, -2027, 3270, 24115, 9519, -2874, 336, 22, + 407, -1993, 3117, 24056, 9715, -2884, 328, 24, 404, -1960, 2966, 23995, 9912, -2893, 319, 26, + 402, -1926, 2816, 23930, 10110, -2900, 311, 29, 398, -1892, 2668, 23863, 10308, -2907, 301, 31, + 395, -1858, 2522, 23792, 10506, -2912, 292, 33, 392, -1823, 2378, 23718, 10705, -2916, 282, 35, + 389, -1789, 2235, 23641, 10904, -2919, 271, 38, 385, -1754, 2095, 23561, 11103, -2920, 261, 40, + 381, -1719, 1956, 23478, 11303, -2921, 249, 43, 377, -1684, 1819, 23393, 11502, -2920, 238, 45, + 373, -1649, 1684, 23304, 11702, -2917, 225, 48, 369, -1615, 1551, 23212, 11902, -2914, 213, 51, + 365, -1580, 1420, 23118, 12102, -2909, 200, 54, 361, -1545, 1291, 23020, 12302, -2902, 186, 57, + 356, -1510, 1163, 22920, 12502, -2895, 173, 60, 352, -1475, 1038, 22817, 12702, -2885, 158, 63, + 347, -1440, 915, 22711, 12901, -2875, 143, 66, 342, -1405, 793, 22602, 13101, -2863, 128, 69, + 338, -1370, 674, 22491, 13300, -2849, 113, 73, 333, -1335, 557, 22377, 13499, -2834, 97, 76, + 328, -1301, 441, 22260, 13698, -2817, 80, 80, 323, -1266, 328, 22141, 13896, -2799, 63, 83, + 318, -1232, 217, 22019, 14094, -2779, 46, 87, 313, -1197, 107, 21894, 14291, -2758, 28, 91, + 307, -1163, 0, 21767, 14488, -2735, 9, 95, 302, -1129, -105, 21637, 14684, -2710, -9, 98, + 297, -1096, -208, 21506, 14879, -2684, -29, 102, 292, -1062, -310, 21371, 15074, -2656, -48, 106, + 286, -1029, -409, 21234, 15268, -2626, -69, 111, 281, -996, -506, 21095, 15461, -2595, -89, 115, + 276, -963, -601, 20954, 15654, -2562, -110, 119, 270, -930, -694, 20810, 15846, -2527, -132, 123, + 265, -898, -785, 20664, 16036, -2490, -154, 128, 260, -866, -874, 20516, 16226, -2452, -176, 132, + 254, -834, -961, 20366, 16415, -2411, -199, 137, 249, -803, -1046, 20213, 16602, -2369, -222, 141, + 243, -771, -1129, 20059, 16789, -2326, -246, 146, 238, -740, -1209, 19902, 16974, -2280, -270, 151, + 233, -710, -1288, 19744, 17158, -2232, -294, 156, 227, -680, -1365, 19583, 17341, -2183, -319, 160, + 222, -650, -1440, 19421, 17523, -2132, -345, 165, 217, -620, -1513, 19257, 17703, -2079, -370, 170, + 211, -591, -1583, 19091, 17882, -2023, -396, 175, 206, -562, -1652, 18923, 18059, -1966, -423, 180, + 201, -533, -1719, 18754, 18235, -1907, -450, 185, 196, -505, -1784, 18582, 18410, -1847, -477, 191, + 191, -477, -1847, 18410, 18582, -1784, -505, 196, 185, -450, -1907, 18235, 18754, -1719, -533, 201, + 180, -423, -1966, 18059, 18923, -1652, -562, 206, 175, -396, -2023, 17882, 19091, -1583, -591, 211, + 170, -370, -2079, 17703, 19257, -1513, -620, 217, 165, -345, -2132, 17523, 19421, -1440, -650, 222, + 160, -319, -2183, 17341, 19583, -1365, -680, 227, 156, -294, -2232, 17158, 19744, -1288, -710, 233, + 151, -270, -2280, 16974, 19902, -1209, -740, 238, 146, -246, -2326, 16789, 20059, -1129, -771, 243, + 141, -222, -2369, 16602, 20213, -1046, -803, 249, 137, -199, -2411, 16415, 20366, -961, -834, 254, + 132, -176, -2452, 16226, 20516, -874, -866, 260, 128, -154, -2490, 16036, 20664, -785, -898, 265, + 123, -132, -2527, 15846, 20810, -694, -930, 270, 119, -110, -2562, 15654, 20954, -601, -963, 276, + 115, -89, -2595, 15461, 21095, -506, -996, 281, 111, -69, -2626, 15268, 21234, -409, -1029, 286, + 106, -48, -2656, 15074, 21371, -310, -1062, 292, 102, -29, -2684, 14879, 21506, -208, -1096, 297, + 98, -9, -2710, 14684, 21637, -105, -1129, 302, 95, 9, -2735, 14488, 21767, 0, -1163, 307, + 91, 28, -2758, 14291, 21894, 107, -1197, 313, 87, 46, -2779, 14094, 22019, 217, -1232, 318, + 83, 63, -2799, 13896, 22141, 328, -1266, 323, 80, 80, -2817, 13698, 22260, 441, -1301, 328, + 76, 97, -2834, 13499, 22377, 557, -1335, 333, 73, 113, -2849, 13300, 22491, 674, -1370, 338, + 69, 128, -2863, 13101, 22602, 793, -1405, 342, 66, 143, -2875, 12901, 22711, 915, -1440, 347, + 63, 158, -2885, 12702, 22817, 1038, -1475, 352, 60, 173, -2895, 12502, 22920, 1163, -1510, 356, + 57, 186, -2902, 12302, 23020, 1291, -1545, 361, 54, 200, -2909, 12102, 23118, 1420, -1580, 365, + 51, 213, -2914, 11902, 23212, 1551, -1615, 369, 48, 225, -2917, 11702, 23304, 1684, -1649, 373, + 45, 238, -2920, 11502, 23393, 1819, -1684, 377, 43, 249, -2921, 11303, 23478, 1956, -1719, 381, + 40, 261, -2920, 11103, 23561, 2095, -1754, 385, 38, 271, -2919, 10904, 23641, 2235, -1789, 389, + 35, 282, -2916, 10705, 23718, 2378, -1823, 392, 33, 292, -2912, 10506, 23792, 2522, -1858, 395, + 31, 301, -2907, 10308, 23863, 2668, -1892, 398, 29, 311, -2900, 10110, 23930, 2816, -1926, 402, + 26, 319, -2893, 9912, 23995, 2966, -1960, 404, 24, 328, -2884, 9715, 24056, 3117, -1993, 407, + 22, 336, -2874, 9519, 24115, 3270, -2027, 410, 21, 344, -2863, 9323, 24170, 3425, -2060, 412, + 19, 351, -2851, 9127, 24222, 3582, -2093, 414, 17, 358, -2839, 8933, 24271, 3740, -2126, 416, + 15, 364, -2825, 8739, 24316, 3900, -2158, 418, 14, 370, -2810, 8545, 24359, 4062, -2190, 419, + 12, 376, -2794, 8353, 24398, 4225, -2221, 420, 11, 381, -2777, 8161, 24434, 4390, -2252, 421, + 9, 386, -2759, 7970, 24467, 4556, -2283, 422, 8, 391, -2741, 7780, 24496, 4724, -2313, 423, + 7, 395, -2721, 7591, 24523, 4893, -2343, 423, 6, 399, -2701, 7403, 24546, 5064, -2373, 423, + 5, 403, -2680, 7216, 24565, 5236, -2402, 423, 4, 406, -2658, 7030, 24582, 5410, -2430, 423, + 3, 409, -2635, 6846, 24595, 5585, -2458, 422, 2, 412, -2612, 6662, 24605, 5761, -2485, 421, + 1, 415, -2588, 6479, 24611, 5939, -2512, 420, 0, 417, -2563, 6298, 24615, 6118, -2538, 418, + }; + #endregion + + private static readonly float[] _normalCurveLut0F; + private static readonly float[] _normalCurveLut1F; + private static readonly float[] _normalCurveLut2F; + + private static readonly float[] _highCurveLut0F; + private static readonly float[] _highCurveLut1F; + private static readonly float[] _highCurveLut2F; + + static ResamplerHelper() + { + _normalCurveLut0F = _normalCurveLut0.Select(x => x / 32768f).ToArray(); + _normalCurveLut1F = _normalCurveLut1.Select(x => x / 32768f).ToArray(); + _normalCurveLut2F = _normalCurveLut2.Select(x => x / 32768f).ToArray(); + + _highCurveLut0F = _highCurveLut0.Select(x => x / 32768f).ToArray(); + _highCurveLut1F = _highCurveLut1.Select(x => x / 32768f).ToArray(); + _highCurveLut2F = _highCurveLut2.Select(x => x / 32768f).ToArray(); + } + + private const int FixedPointPrecision = 15; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Resample(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount, SampleRateConversionQuality srcQuality, bool needPitch) + { + switch (srcQuality) + { + case SampleRateConversionQuality.Default: + ResampleDefaultQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount, needPitch); + break; + case SampleRateConversionQuality.Low: + ResampleLowQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount); + break; + case SampleRateConversionQuality.High: + ResampleHighQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount); + break; + default: + throw new NotImplementedException($"{srcQuality}"); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ReadOnlySpan GetDefaultParameter(float ratio) + { + if (ratio <= 1.0f) + { + return _normalCurveLut1F; + } + + if (ratio > 1.333313f) + { + return _normalCurveLut0F; + } + + return _normalCurveLut2F; + } + + private unsafe static void ResampleDefaultQuality(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount, bool needPitch) + { + ReadOnlySpan parameters = GetDefaultParameter(ratio); + + int inputBufferIndex = 0, i = 0; + + // TODO: REV8 fast path (when needPitch == false the input index progression is constant + we need SIMD) + + if (Sse41.IsSupported) + { + if (ratio == 1f) + { + fixed (short* pInput = inputBuffer) + fixed (float* pOutput = outputBuffer, pParameters = parameters) + { + Vector128 parameter = Sse.LoadVector128(pParameters); + + for (; i < (sampleCount & ~3); i += 4) + { + Vector128 intInput0 = Sse41.ConvertToVector128Int32(pInput + (uint)i); + Vector128 intInput1 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 1); + Vector128 intInput2 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 2); + Vector128 intInput3 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 3); + + Vector128 input0 = Sse2.ConvertToVector128Single(intInput0); + Vector128 input1 = Sse2.ConvertToVector128Single(intInput1); + Vector128 input2 = Sse2.ConvertToVector128Single(intInput2); + Vector128 input3 = Sse2.ConvertToVector128Single(intInput3); + + Vector128 mix0 = Sse.Multiply(input0, parameter); + Vector128 mix1 = Sse.Multiply(input1, parameter); + Vector128 mix2 = Sse.Multiply(input2, parameter); + Vector128 mix3 = Sse.Multiply(input3, parameter); + + Vector128 mix01 = Sse3.HorizontalAdd(mix0, mix1); + Vector128 mix23 = Sse3.HorizontalAdd(mix2, mix3); + + Vector128 mix0123 = Sse3.HorizontalAdd(mix01, mix23); + + Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123)); + } + } + + inputBufferIndex = i; + } + else + { + fixed (short* pInput = inputBuffer) + fixed (float* pOutput = outputBuffer, pParameters = parameters) + { + for (; i < (sampleCount & ~3); i += 4) + { + uint baseIndex0 = (uint)(fraction * 128) * 4; + uint inputIndex0 = (uint)inputBufferIndex; + + fraction += ratio; + + uint baseIndex1 = ((uint)(fraction * 128) & 127) * 4; + uint inputIndex1 = (uint)inputBufferIndex + (uint)fraction; + + fraction += ratio; + + uint baseIndex2 = ((uint)(fraction * 128) & 127) * 4; + uint inputIndex2 = (uint)inputBufferIndex + (uint)fraction; + + fraction += ratio; + + uint baseIndex3 = ((uint)(fraction * 128) & 127) * 4; + uint inputIndex3 = (uint)inputBufferIndex + (uint)fraction; + + fraction += ratio; + inputBufferIndex += (int)fraction; + + // Only keep lower part (safe as fraction isn't supposed to be negative) + fraction -= (int)fraction; + + Vector128 parameter0 = Sse.LoadVector128(pParameters + baseIndex0); + Vector128 parameter1 = Sse.LoadVector128(pParameters + baseIndex1); + Vector128 parameter2 = Sse.LoadVector128(pParameters + baseIndex2); + Vector128 parameter3 = Sse.LoadVector128(pParameters + baseIndex3); + + Vector128 intInput0 = Sse41.ConvertToVector128Int32(pInput + inputIndex0); + Vector128 intInput1 = Sse41.ConvertToVector128Int32(pInput + inputIndex1); + Vector128 intInput2 = Sse41.ConvertToVector128Int32(pInput + inputIndex2); + Vector128 intInput3 = Sse41.ConvertToVector128Int32(pInput + inputIndex3); + + Vector128 input0 = Sse2.ConvertToVector128Single(intInput0); + Vector128 input1 = Sse2.ConvertToVector128Single(intInput1); + Vector128 input2 = Sse2.ConvertToVector128Single(intInput2); + Vector128 input3 = Sse2.ConvertToVector128Single(intInput3); + + Vector128 mix0 = Sse.Multiply(input0, parameter0); + Vector128 mix1 = Sse.Multiply(input1, parameter1); + Vector128 mix2 = Sse.Multiply(input2, parameter2); + Vector128 mix3 = Sse.Multiply(input3, parameter3); + + Vector128 mix01 = Sse3.HorizontalAdd(mix0, mix1); + Vector128 mix23 = Sse3.HorizontalAdd(mix2, mix3); + + Vector128 mix0123 = Sse3.HorizontalAdd(mix01, mix23); + + Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123)); + } + } + } + } + + for (; i < sampleCount; i++) + { + int baseIndex = (int)(fraction * 128) * 4; + ReadOnlySpan parameter = parameters.Slice(baseIndex, 4); + ReadOnlySpan currentInput = inputBuffer.Slice(inputBufferIndex, 4); + + outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] + + currentInput[1] * parameter[1] + + currentInput[2] * parameter[2] + + currentInput[3] * parameter[3]); + + fraction += ratio; + inputBufferIndex += (int)fraction; + + // Only keep lower part (safe as fraction isn't supposed to be negative) + fraction -= (int)fraction; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ReadOnlySpan GetHighParameter(float ratio) + { + if (ratio <= 1.0f) + { + return _highCurveLut1F; + } + + if (ratio > 1.333313f) + { + return _highCurveLut0F; + } + + return _highCurveLut2F; + } + + private static unsafe void ResampleHighQuality(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount) + { + ReadOnlySpan parameters = GetHighParameter(ratio); + + int inputBufferIndex = 0; + + if (Avx2.IsSupported) + { + // Fast path; assumes 256-bit vectors for simplicity because the filter is 8 taps + fixed (short* pInput = inputBuffer) + fixed (float* pParameters = parameters) + { + for (int i = 0; i < sampleCount; i++) + { + int baseIndex = (int)(fraction * 128) * 8; + + Vector256 intInput = Avx2.ConvertToVector256Int32(pInput + inputBufferIndex); + Vector256 floatInput = Avx.ConvertToVector256Single(intInput); + Vector256 parameter = Avx.LoadVector256(pParameters + baseIndex); + Vector256 dp = Avx.DotProduct(floatInput, parameter, control: 0xFF); + + // avx2 does an 8-element dot product piecewise so we have to sum up 2 intermediate results + outputBuffer[i] = (float)Math.Round(dp[0] + dp[4]); + + fraction += ratio; + inputBufferIndex += (int)MathF.Truncate(fraction); + + fraction -= (int)fraction; + } + } + } + else + { + for (int i = 0; i < sampleCount; i++) + { + int baseIndex = (int)(fraction * 128) * 8; + ReadOnlySpan parameter = parameters.Slice(baseIndex, 8); + ReadOnlySpan currentInput = inputBuffer.Slice(inputBufferIndex, 8); + + outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] + + currentInput[1] * parameter[1] + + currentInput[2] * parameter[2] + + currentInput[3] * parameter[3] + + currentInput[4] * parameter[4] + + currentInput[5] * parameter[5] + + currentInput[6] * parameter[6] + + currentInput[7] * parameter[7]); + + fraction += ratio; + inputBufferIndex += (int)MathF.Truncate(fraction); + + fraction -= (int)fraction; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ResampleLowQuality(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount) + { + int inputBufferIndex = 0; + + for (int i = 0; i < sampleCount; i++) + { + int outputData = inputBuffer[inputBufferIndex]; + + if (fraction > 1.0f) + { + outputData = inputBuffer[inputBufferIndex + 1]; + } + + outputBuffer[i] = outputData; + + fraction += ratio; + inputBufferIndex += (int)MathF.Truncate(fraction); + + fraction -= (int)fraction; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs new file mode 100644 index 00000000..f9ef201f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 6)] + public struct AdpcmLoopContext + { + public short PredScale; + public short History0; + public short History1; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs new file mode 100644 index 00000000..97bbc80c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs @@ -0,0 +1,74 @@ +using Ryujinx.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x80)] + public struct AuxiliaryBufferHeader + { + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x40)] + public struct AuxiliaryBufferInfo + { + private const uint ReadOffsetPosition = 0x0; + private const uint WriteOffsetPosition = 0x4; + private const uint LostSampleCountPosition = 0x8; + private const uint TotalSampleCountPosition = 0xC; + + public uint ReadOffset; + public uint WriteOffset; + public uint LostSampleCount; + public uint TotalSampleCount; + private unsafe fixed uint _unknown[12]; + + public static uint GetReadOffset(IVirtualMemoryManager manager, ulong bufferAddress) + { + return manager.Read(bufferAddress + ReadOffsetPosition); + } + + public static uint GetWriteOffset(IVirtualMemoryManager manager, ulong bufferAddress) + { + return manager.Read(bufferAddress + WriteOffsetPosition); + } + + public static uint GetLostSampleCount(IVirtualMemoryManager manager, ulong bufferAddress) + { + return manager.Read(bufferAddress + LostSampleCountPosition); + } + + public static uint GetTotalSampleCount(IVirtualMemoryManager manager, ulong bufferAddress) + { + return manager.Read(bufferAddress + TotalSampleCountPosition); + } + + public static void SetReadOffset(IVirtualMemoryManager manager, ulong bufferAddress, uint value) + { + manager.Write(bufferAddress + ReadOffsetPosition, value); + } + + public static void SetWriteOffset(IVirtualMemoryManager manager, ulong bufferAddress, uint value) + { + manager.Write(bufferAddress + WriteOffsetPosition, value); + } + + public static void SetLostSampleCount(IVirtualMemoryManager manager, ulong bufferAddress, uint value) + { + manager.Write(bufferAddress + LostSampleCountPosition, value); + } + + public static void SetTotalSampleCount(IVirtualMemoryManager manager, ulong bufferAddress, uint value) + { + manager.Write(bufferAddress + TotalSampleCountPosition, value); + } + + public static void Reset(IVirtualMemoryManager manager, ulong bufferAddress) + { + // NOTE: Lost sample count is never reset, since REV10. + manager.Write(bufferAddress + ReadOffsetPosition, 0UL); + manager.Write(bufferAddress + TotalSampleCountPosition, 0); + } + } + + public AuxiliaryBufferInfo CpuBufferInfo; + public AuxiliaryBufferInfo DspBufferInfo; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs new file mode 100644 index 00000000..58a2d9cc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x20)] + public struct BiquadFilterState + { + public float State0; + public float State1; + public float State2; + public float State3; + public float State4; + public float State5; + public float State6; + public float State7; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs new file mode 100644 index 00000000..5ca8cd20 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs @@ -0,0 +1,51 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public struct CompressorState + { + public ExponentialMovingAverage InputMovingAverage; + public float Unknown4; + public ExponentialMovingAverage CompressionGainAverage; + public float CompressorGainReduction; + public float Unknown10; + public float Unknown14; + public float PreviousCompressionEmaAlpha; + public float MakeupGain; + public float OutputGain; + + public CompressorState(ref CompressorParameter parameter) + { + InputMovingAverage = new ExponentialMovingAverage(0.0f); + Unknown4 = 1.0f; + CompressionGainAverage = new ExponentialMovingAverage(1.0f); + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref CompressorParameter parameter) + { + float threshold = parameter.Threshold; + float ratio = 1.0f / parameter.Ratio; + float attackCoefficient = parameter.AttackCoefficient; + float makeupGain; + + if (parameter.MakeupGainEnabled) + { + makeupGain = (threshold * 0.5f * (ratio - 1.0f)) - 3.0f; + } + else + { + makeupGain = 0.0f; + } + + PreviousCompressionEmaAlpha = attackCoefficient; + MakeupGain = makeupGain; + CompressorGainReduction = (1.0f - ratio) / Constants.ChannelCountMax; + Unknown10 = threshold - 1.5f; + Unknown14 = threshold + 1.5f; + OutputGain = FloatingPointHelper.DecibelToLinear(parameter.OutputGain + makeupGain); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs new file mode 100644 index 00000000..17ad2a40 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs @@ -0,0 +1,67 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public struct DelayState + { + public DelayLine[] DelayLines { get; } + public float[] LowPassZ { get; set; } + public float FeedbackGain { get; private set; } + public float DelayFeedbackBaseGain { get; private set; } + public float DelayFeedbackCrossGain { get; private set; } + public float LowPassFeedbackGain { get; private set; } + public float LowPassBaseGain { get; private set; } + + private const int FixedPointPrecision = 14; + + public DelayState(ref DelayParameter parameter, ulong workBuffer) + { + DelayLines = new DelayLine[parameter.ChannelCount]; + LowPassZ = new float[parameter.ChannelCount]; + + uint sampleRate = (uint)FixedPointHelper.ToInt(parameter.SampleRate, FixedPointPrecision) / 1000; + + for (int i = 0; i < DelayLines.Length; i++) + { + DelayLines[i] = new DelayLine(sampleRate, parameter.DelayTimeMax); + DelayLines[i].SetDelay(parameter.DelayTime); + } + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref DelayParameter parameter) + { + FeedbackGain = FixedPointHelper.ToFloat(parameter.FeedbackGain, FixedPointPrecision) * 0.98f; + + float channelSpread = FixedPointHelper.ToFloat(parameter.ChannelSpread, FixedPointPrecision); + + DelayFeedbackBaseGain = (1.0f - channelSpread) * FeedbackGain; + + if (parameter.ChannelCount == 4 || parameter.ChannelCount == 6) + { + DelayFeedbackCrossGain = channelSpread * 0.5f * FeedbackGain; + } + else + { + DelayFeedbackCrossGain = channelSpread * FeedbackGain; + } + + LowPassFeedbackGain = 0.95f * FixedPointHelper.ToFloat(parameter.LowPassAmount, FixedPointPrecision); + LowPassBaseGain = 1.0f - LowPassFeedbackGain; + } + + public readonly void UpdateLowPassFilter(ref float tempRawRef, uint channelCount) + { + for (int i = 0; i < channelCount; i++) + { + float lowPassResult = LowPassFeedbackGain * LowPassZ[i] + Unsafe.Add(ref tempRawRef, i) * LowPassBaseGain; + + LowPassZ[i] = lowPassResult; + DelayLines[i].Update(lowPassResult); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs new file mode 100644 index 00000000..1388bfce --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs @@ -0,0 +1,31 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public struct LimiterState + { + public ExponentialMovingAverage[] DetectorAverage; + public ExponentialMovingAverage[] CompressionGainAverage; + public float[] DelayedSampleBuffer; + public int[] DelayedSampleBufferPosition; + + public LimiterState(ref LimiterParameter parameter, ulong workBuffer) + { + DetectorAverage = new ExponentialMovingAverage[parameter.ChannelCount]; + CompressionGainAverage = new ExponentialMovingAverage[parameter.ChannelCount]; + DelayedSampleBuffer = new float[parameter.ChannelCount * parameter.DelayBufferSampleCountMax]; + DelayedSampleBufferPosition = new int[parameter.ChannelCount]; + + DetectorAverage.AsSpan().Fill(new ExponentialMovingAverage(0.0f)); + CompressionGainAverage.AsSpan().Fill(new ExponentialMovingAverage(1.0f)); + DelayedSampleBufferPosition.AsSpan().Clear(); + DelayedSampleBuffer.AsSpan().Clear(); + + UpdateParameter(ref parameter); + } + + public static void UpdateParameter(ref LimiterParameter parameter) { } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs new file mode 100644 index 00000000..e83e0d5f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs @@ -0,0 +1,119 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public struct Reverb3dState + { + private readonly float[] _fdnDelayMinTimes = new float[4] { 5.0f, 6.0f, 13.0f, 14.0f }; + private readonly float[] _fdnDelayMaxTimes = new float[4] { 45.704f, 82.782f, 149.94f, 271.58f }; + private readonly float[] _decayDelayMaxTimes1 = new float[4] { 17.0f, 13.0f, 9.0f, 7.0f }; + private readonly float[] _decayDelayMaxTimes2 = new float[4] { 19.0f, 11.0f, 10.0f, 6.0f }; + private readonly float[] _earlyDelayTimes = new float[20] { 0.017136f, 0.059154f, 0.16173f, 0.39019f, 0.42526f, 0.45541f, 0.68974f, 0.74591f, 0.83384f, 0.8595f, 0.0f, 0.075024f, 0.16879f, 0.2999f, 0.33744f, 0.3719f, 0.59901f, 0.71674f, 0.81786f, 0.85166f }; + public readonly float[] EarlyGain = new float[20] { 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f, 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f, 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f }; + + public IDelayLine[] FdnDelayLines { get; } + public DecayDelay[] DecayDelays1 { get; } + public DecayDelay[] DecayDelays2 { get; } + public IDelayLine PreDelayLine { get; } + public IDelayLine FrontCenterDelayLine { get; } + public float DryGain { get; private set; } + public uint[] EarlyDelayTime { get; private set; } + public float PreviousPreDelayValue { get; set; } + public float PreviousPreDelayGain { get; private set; } + public float TargetPreDelayGain { get; private set; } + public float EarlyReflectionsGain { get; private set; } + public float LateReverbGain { get; private set; } + public uint ReflectionDelayTime { get; private set; } + public float EchoLateReverbDecay { get; private set; } + public float[] DecayDirectFdnGain { get; private set; } + public float[] DecayCurrentFdnGain { get; private set; } + public float[] DecayCurrentOutputGain { get; private set; } + public float[] PreviousFeedbackOutputDecayed { get; private set; } + + public Reverb3dState(ref Reverb3dParameter parameter, ulong workBuffer) + { + FdnDelayLines = new IDelayLine[4]; + DecayDelays1 = new DecayDelay[4]; + DecayDelays2 = new DecayDelay[4]; + DecayDirectFdnGain = new float[4]; + DecayCurrentFdnGain = new float[4]; + DecayCurrentOutputGain = new float[4]; + PreviousFeedbackOutputDecayed = new float[4]; + + uint sampleRate = parameter.SampleRate / 1000; + + for (int i = 0; i < 4; i++) + { + FdnDelayLines[i] = new DelayLine3d(sampleRate, _fdnDelayMaxTimes[i]); + DecayDelays1[i] = new DecayDelay(new DelayLine3d(sampleRate, _decayDelayMaxTimes1[i])); + DecayDelays2[i] = new DecayDelay(new DelayLine3d(sampleRate, _decayDelayMaxTimes2[i])); + } + + PreDelayLine = new DelayLine3d(sampleRate, 400); + FrontCenterDelayLine = new DelayLine3d(sampleRate, 5); + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref Reverb3dParameter parameter) + { + uint sampleRate = parameter.SampleRate / 1000; + + EarlyDelayTime = new uint[20]; + DryGain = parameter.DryGain; + PreviousFeedbackOutputDecayed.AsSpan().Clear(); + PreviousPreDelayValue = 0; + + EarlyReflectionsGain = FloatingPointHelper.Pow10(Math.Min(parameter.RoomGain + parameter.ReflectionsGain, 5000.0f) / 2000.0f); + LateReverbGain = FloatingPointHelper.Pow10(Math.Min(parameter.RoomGain + parameter.ReverbGain, 5000.0f) / 2000.0f); + + float highFrequencyRoomGain = FloatingPointHelper.Pow10(parameter.RoomHf / 2000.0f); + + if (highFrequencyRoomGain < 1.0f) + { + float tempA = 1.0f - highFrequencyRoomGain; + float tempB = 2.0f - ((2.0f * highFrequencyRoomGain) * FloatingPointHelper.Cos(256.0f * parameter.HfReference / parameter.SampleRate)); + float tempC = MathF.Sqrt(MathF.Pow(tempB, 2) - (4.0f * (1.0f - highFrequencyRoomGain) * (1.0f - highFrequencyRoomGain))); + + PreviousPreDelayGain = (tempB - tempC) / (2.0f * tempA); + TargetPreDelayGain = 1.0f - PreviousPreDelayGain; + } + else + { + PreviousPreDelayGain = 0.0f; + TargetPreDelayGain = 1.0f; + } + + ReflectionDelayTime = IDelayLine.GetSampleCount(sampleRate, 1000.0f * (parameter.ReflectionDelay + parameter.ReverbDelayTime)); + EchoLateReverbDecay = 0.6f * parameter.Diffusion * 0.01f; + + for (int i = 0; i < FdnDelayLines.Length; i++) + { + FdnDelayLines[i].SetDelay(_fdnDelayMinTimes[i] + (parameter.Density / 100 * (_fdnDelayMaxTimes[i] - _fdnDelayMinTimes[i]))); + + uint tempSampleCount = FdnDelayLines[i].CurrentSampleCount + DecayDelays1[i].CurrentSampleCount + DecayDelays2[i].CurrentSampleCount; + + float tempA = (-60.0f * tempSampleCount) / (parameter.DecayTime * parameter.SampleRate); + float tempB = tempA / parameter.HfDecayRatio; + float tempC = FloatingPointHelper.Cos(128.0f * 0.5f * parameter.HfReference / parameter.SampleRate) / FloatingPointHelper.Sin(128.0f * 0.5f * parameter.HfReference / parameter.SampleRate); + float tempD = FloatingPointHelper.Pow10((tempB - tempA) / 40.0f); + float tempE = FloatingPointHelper.Pow10((tempB + tempA) / 40.0f) * 0.7071f; + + DecayDirectFdnGain[i] = tempE * ((tempD * tempC) + 1.0f) / (tempC + tempD); + DecayCurrentFdnGain[i] = tempE * (1.0f - (tempD * tempC)) / (tempC + tempD); + DecayCurrentOutputGain[i] = (tempC - tempD) / (tempC + tempD); + + DecayDelays1[i].SetDecayRate(EchoLateReverbDecay); + DecayDelays2[i].SetDecayRate(EchoLateReverbDecay * -0.9f); + } + + for (int i = 0; i < EarlyDelayTime.Length; i++) + { + uint sampleCount = Math.Min(IDelayLine.GetSampleCount(sampleRate, (parameter.ReflectionDelay * 1000.0f) + (_earlyDelayTimes[i] * 1000.0f * ((parameter.ReverbDelayTime * 0.9998f) + 0.02f))), PreDelayLine.SampleCountMax); + EarlyDelayTime[i] = sampleCount; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs new file mode 100644 index 00000000..f1927b71 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs @@ -0,0 +1,204 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public struct ReverbState + { + private static readonly float[] _fdnDelayTimes = new float[20] + { + // Room + 53.953247f, 79.192566f, 116.238770f, 130.615295f, + // Hall + 53.953247f, 79.192566f, 116.238770f, 170.615295f, + // Plate + 5f, 10f, 5f, 10f, + // Cathedral + 47.03f, 71f, 103f, 170f, + // Max delay (Hall is the one with the highest values so identical to Hall) + 53.953247f, 79.192566f, 116.238770f, 170.615295f, + }; + + private static readonly float[] _decayDelayTimes = new float[20] + { + // Room + 7f, 9f, 13f, 17f, + // Hall + 7f, 9f, 13f, 17f, + // Plate (no decay) + 1f, 1f, 1f, 1f, + // Cathedral + 7f, 7f, 13f, 9f, + // Max delay (Hall is the one with the highest values so identical to Hall) + 7f, 9f, 13f, 17f, + }; + + private static readonly float[] _earlyDelayTimes = new float[50] + { + // Room + 0.0f, 3.5f, 2.8f, 3.9f, 2.7f, 13.4f, 7.9f, 8.4f, 9.9f, 12.0f, + // Chamber + 0.0f, 11.8f, 5.5f, 11.2f, 10.4f, 38.1f, 22.2f, 29.6f, 21.2f, 24.8f, + // Hall + 0.0f, 41.5f, 20.5f, 41.3f, 0.0f, 29.5f, 33.8f, 45.2f, 46.8f, 0.0f, + // Cathedral + 33.1f, 43.3f, 22.8f, 37.9f, 14.9f, 35.3f, 17.9f, 34.2f, 0.0f, 43.3f, + // Disabled + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + }; + + private static readonly float[] _earlyGainBase = new float[50] + { + // Room + 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, + // Chamber + 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, 0.68f, 0.68f, + // Hall + 0.50f, 0.70f, 0.70f, 0.68f, 0.50f, 0.68f, 0.68f, 0.70f, 0.68f, 0.00f, + // Cathedral + 0.93f, 0.92f, 0.87f, 0.86f, 0.94f, 0.81f, 0.80f, 0.77f, 0.76f, 0.65f, + // Disabled + 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, + }; + + private static readonly float[] _preDelayTimes = new float[5] + { + // Room + 12.5f, + // Chamber + 40.0f, + // Hall + 50.0f, + // Cathedral + 50.0f, + // Disabled + 0.0f, + }; + + public DelayLine[] FdnDelayLines { get; } + public DecayDelay[] DecayDelays { get; } + public DelayLine PreDelayLine { get; } + public DelayLine FrontCenterDelayLine { get; } + public uint[] EarlyDelayTime { get; } + public float[] EarlyGain { get; } + public uint PreDelayLineDelayTime { get; private set; } + + public float[] HighFrequencyDecayDirectGain { get; } + public float[] HighFrequencyDecayPreviousGain { get; } + public float[] PreviousFeedbackOutput { get; } + + public const int EarlyModeCount = 10; + + private const int FixedPointPrecision = 14; + + private static ReadOnlySpan GetFdnDelayTimesByLateMode(ReverbLateMode lateMode) + { + return _fdnDelayTimes.AsSpan((int)lateMode * 4, 4); + } + + private static ReadOnlySpan GetDecayDelayTimesByLateMode(ReverbLateMode lateMode) + { + return _decayDelayTimes.AsSpan((int)lateMode * 4, 4); + } + + public ReverbState(ref ReverbParameter parameter, ulong workBuffer, bool isLongSizePreDelaySupported) + { + FdnDelayLines = new DelayLine[4]; + DecayDelays = new DecayDelay[4]; + EarlyDelayTime = new uint[EarlyModeCount]; + EarlyGain = new float[EarlyModeCount]; + HighFrequencyDecayDirectGain = new float[4]; + HighFrequencyDecayPreviousGain = new float[4]; + PreviousFeedbackOutput = new float[4]; + + ReadOnlySpan fdnDelayTimes = GetFdnDelayTimesByLateMode(ReverbLateMode.Limit); + ReadOnlySpan decayDelayTimes = GetDecayDelayTimesByLateMode(ReverbLateMode.Limit); + + uint sampleRate = (uint)FixedPointHelper.ToFloat((uint)parameter.SampleRate, FixedPointPrecision); + + for (int i = 0; i < 4; i++) + { + FdnDelayLines[i] = new DelayLine(sampleRate, fdnDelayTimes[i]); + DecayDelays[i] = new DecayDelay(new DelayLine(sampleRate, decayDelayTimes[i])); + } + + float preDelayTimeMax = 150.0f; + + if (isLongSizePreDelaySupported) + { + preDelayTimeMax = 350.0f; + } + + PreDelayLine = new DelayLine(sampleRate, preDelayTimeMax); + FrontCenterDelayLine = new DelayLine(sampleRate, 5.0f); + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref ReverbParameter parameter) + { + uint sampleRate = (uint)FixedPointHelper.ToFloat((uint)parameter.SampleRate, FixedPointPrecision); + + float preDelayTimeInMilliseconds = FixedPointHelper.ToFloat(parameter.PreDelayTime, FixedPointPrecision); + float earlyGain = FixedPointHelper.ToFloat(parameter.EarlyGain, FixedPointPrecision); + float coloration = FixedPointHelper.ToFloat(parameter.Coloration, FixedPointPrecision); + float decayTime = FixedPointHelper.ToFloat(parameter.DecayTime, FixedPointPrecision); + + for (int i = 0; i < 10; i++) + { + EarlyDelayTime[i] = Math.Min(IDelayLine.GetSampleCount(sampleRate, _earlyDelayTimes[i] + preDelayTimeInMilliseconds), PreDelayLine.SampleCountMax) + 1; + EarlyGain[i] = _earlyGainBase[i] * earlyGain; + } + + if (parameter.ChannelCount == 2) + { + EarlyGain[4] = EarlyGain[4] * 0.5f; + EarlyGain[5] = EarlyGain[5] * 0.5f; + } + + PreDelayLineDelayTime = Math.Min(IDelayLine.GetSampleCount(sampleRate, _preDelayTimes[(int)parameter.EarlyMode] + preDelayTimeInMilliseconds), PreDelayLine.SampleCountMax); + + ReadOnlySpan fdnDelayTimes = GetFdnDelayTimesByLateMode(parameter.LateMode); + ReadOnlySpan decayDelayTimes = GetDecayDelayTimesByLateMode(parameter.LateMode); + + float highFrequencyDecayRatio = FixedPointHelper.ToFloat(parameter.HighFrequencyDecayRatio, FixedPointPrecision); + float highFrequencyUnknownValue = FloatingPointHelper.Cos(1280.0f / sampleRate); + + for (int i = 0; i < 4; i++) + { + FdnDelayLines[i].SetDelay(fdnDelayTimes[i]); + DecayDelays[i].SetDelay(decayDelayTimes[i]); + + float tempA = -3 * (DecayDelays[i].CurrentSampleCount + FdnDelayLines[i].CurrentSampleCount); + float tempB = tempA / (decayTime * sampleRate); + float tempC; + float tempD; + + if (highFrequencyDecayRatio < 0.995f) + { + float tempE = FloatingPointHelper.Pow10((((1.0f / highFrequencyDecayRatio) - 1.0f) * 2) / 100 * (tempB / 10)); + float tempF = 1.0f - tempE; + float tempG = 2.0f - (tempE * 2 * highFrequencyUnknownValue); + float tempH = MathF.Sqrt((tempG * tempG) - (tempF * tempF * 4)); + + tempC = (tempG - tempH) / (tempF * 2); + tempD = 1.0f - tempC; + } + else + { + // no high frequency decay ratio + tempC = 0.0f; + tempD = 1.0f; + } + + HighFrequencyDecayDirectGain[i] = FloatingPointHelper.Pow10(tempB / 1000) * tempD * 0.7071f; + HighFrequencyDecayPreviousGain[i] = tempC; + PreviousFeedbackOutput[i] = 0.0f; + + DecayDelays[i].SetDecayRate(0.6f * (1.0f - coloration)); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs new file mode 100644 index 00000000..5732cdb2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs @@ -0,0 +1,192 @@ +using Ryujinx.Audio.Renderer.Server.Upsampler; +using Ryujinx.Common.Memory; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public class UpsamplerHelper + { + private const int HistoryLength = UpsamplerBufferState.HistoryLength; + private const int FilterBankLength = 20; + // Bank0 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + private const int Bank0CenterIndex = 9; + private static readonly Array20 _bank1 = PrecomputeFilterBank(1.0f / 6.0f); + private static readonly Array20 _bank2 = PrecomputeFilterBank(2.0f / 6.0f); + private static readonly Array20 _bank3 = PrecomputeFilterBank(3.0f / 6.0f); + private static readonly Array20 _bank4 = PrecomputeFilterBank(4.0f / 6.0f); + private static readonly Array20 _bank5 = PrecomputeFilterBank(5.0f / 6.0f); + + private static Array20 PrecomputeFilterBank(float offset) + { + float Sinc(float x) + { + if (x == 0) + { + return 1.0f; + } + return (MathF.Sin(MathF.PI * x) / (MathF.PI * x)); + } + + float BlackmanWindow(float x) + { + const float A = 0.18f; + const float A0 = 0.5f - 0.5f * A; + const float A1 = -0.5f; + const float A2 = 0.5f * A; + return A0 + A1 * MathF.Cos(2 * MathF.PI * x) + A2 * MathF.Cos(4 * MathF.PI * x); + } + + Array20 result = new(); + + for (int i = 0; i < FilterBankLength; i++) + { + float x = (Bank0CenterIndex - i) + offset; + result[i] = Sinc(x) * BlackmanWindow(x / FilterBankLength + 0.5f); + } + + return result; + } + + // Polyphase upsampling algorithm + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Upsample(Span outputBuffer, ReadOnlySpan inputBuffer, int outputSampleCount, int inputSampleCount, ref UpsamplerBufferState state) + { + if (!state.Initialized) + { + state.Scale = inputSampleCount switch + { + 40 => 6.0f, + 80 => 3.0f, + 160 => 1.5f, + _ => throw new ArgumentOutOfRangeException(nameof(inputSampleCount), inputSampleCount, null), + }; + state.Initialized = true; + } + + if (outputSampleCount == 0) + { + return; + } + + float DoFilterBank(ref UpsamplerBufferState state, in Array20 bank) + { + float result = 0.0f; + + Debug.Assert(state.History.Length == HistoryLength); + Debug.Assert(bank.Length == FilterBankLength); + + int curIdx = 0; + if (Vector.IsHardwareAccelerated) + { + // Do SIMD-accelerated block operations where possible. + // Only about a 2x speedup since filter bank length is short + int stopIdx = FilterBankLength - (FilterBankLength % Vector.Count); + while (curIdx < stopIdx) + { + result += Vector.Dot( + new Vector(bank.AsSpan().Slice(curIdx, Vector.Count)), + new Vector(state.History.AsSpan().Slice(curIdx, Vector.Count))); + curIdx += Vector.Count; + } + } + + while (curIdx < FilterBankLength) + { + result += bank[curIdx] * state.History[curIdx]; + curIdx++; + } + + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void NextInput(ref UpsamplerBufferState state, float input) + { + state.History.AsSpan()[1..].CopyTo(state.History.AsSpan()); + state.History[HistoryLength - 1] = input; + } + + int inputBufferIndex = 0; + + switch (state.Scale) + { + case 6.0f: + for (int i = 0; i < outputSampleCount; i++) + { + switch (state.Phase) + { + case 0: + NextInput(ref state, inputBuffer[inputBufferIndex++]); + outputBuffer[i] = state.History[Bank0CenterIndex]; + break; + case 1: + outputBuffer[i] = DoFilterBank(ref state, _bank1); + break; + case 2: + outputBuffer[i] = DoFilterBank(ref state, _bank2); + break; + case 3: + outputBuffer[i] = DoFilterBank(ref state, _bank3); + break; + case 4: + outputBuffer[i] = DoFilterBank(ref state, _bank4); + break; + case 5: + outputBuffer[i] = DoFilterBank(ref state, _bank5); + break; + } + + state.Phase = (state.Phase + 1) % 6; + } + break; + case 3.0f: + for (int i = 0; i < outputSampleCount; i++) + { + switch (state.Phase) + { + case 0: + NextInput(ref state, inputBuffer[inputBufferIndex++]); + outputBuffer[i] = state.History[Bank0CenterIndex]; + break; + case 1: + outputBuffer[i] = DoFilterBank(ref state, _bank2); + break; + case 2: + outputBuffer[i] = DoFilterBank(ref state, _bank4); + break; + } + + state.Phase = (state.Phase + 1) % 3; + } + break; + case 1.5f: + // Upsample by 3 then decimate by 2. + for (int i = 0; i < outputSampleCount; i++) + { + switch (state.Phase) + { + case 0: + NextInput(ref state, inputBuffer[inputBufferIndex++]); + outputBuffer[i] = state.History[Bank0CenterIndex]; + break; + case 1: + outputBuffer[i] = DoFilterBank(ref state, _bank4); + break; + case 2: + NextInput(ref state, inputBuffer[inputBufferIndex++]); + outputBuffer[i] = DoFilterBank(ref state, _bank2); + break; + } + + state.Phase = (state.Phase + 1) % 3; + } + break; + default: + throw new ArgumentOutOfRangeException(nameof(state), state.Scale, null); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs b/src/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs new file mode 100644 index 00000000..491a05c8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs @@ -0,0 +1,99 @@ +using Ryujinx.Audio.Renderer.Server.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Audio Renderer user configuration. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioRendererConfiguration + { + /// + /// The target sample rate of the user. + /// + /// Only 32000Hz and 48000Hz are considered valid, other sample rates will cause undefined behaviour. + public uint SampleRate; + + /// + /// The target sample count per updates. + /// + public uint SampleCount; + + /// + /// The maximum mix buffer count. + /// + public uint MixBufferCount; + + /// + /// The maximum amount of sub mixes that could be used by the user. + /// + public uint SubMixBufferCount; + + /// + /// The maximum amount of voices that could be used by the user. + /// + public uint VoiceCount; + + /// + /// The maximum amount of sinks that could be used by the user. + /// + public uint SinkCount; + + /// + /// The maximum amount of effects that could be used by the user. + /// + public uint EffectCount; + + /// + /// The maximum amount of performance metric frames that could be used by the user. + /// + public uint PerformanceMetricFramesCount; + + /// + /// Set to true if the user allows the to drop voices. + /// + /// + [MarshalAs(UnmanagedType.I1)] + public bool VoiceDropEnabled; + + /// + /// Reserved/unused + /// + private readonly byte _reserved; + + /// + /// The target rendering device. + /// + /// Must be + public AudioRendererRenderingDevice RenderingDevice; + + /// + /// The target execution mode. + /// + /// Must be + public AudioRendererExecutionMode ExecutionMode; + + /// + /// The maximum amount of splitters that could be used by the user. + /// + public uint SplitterCount; + + /// + /// The maximum amount of splitters destinations that could be used by the user. + /// + public uint SplitterDestinationCount; + + /// + /// The size of the external context. + /// + /// This is a leftover of the old "codec" interface system that was present between 1.0.0 and 3.0.0. This was entirely removed from the server side with REV8. + public uint ExternalContextSize; + + /// + /// The user audio revision + /// + /// + public int Revision; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs new file mode 100644 index 00000000..72438be0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs @@ -0,0 +1,30 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for behaviour. + /// + /// This is used to report errors to the user during processing. + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BehaviourErrorInfoOutStatus + { + /// + /// The reported errors. + /// + public Array10 ErrorInfos; + + /// + /// The amount of error that got reported. + /// + public uint ErrorInfosCount; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs new file mode 100644 index 00000000..f1492b0b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs @@ -0,0 +1,34 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Biquad filter parameters. + /// + [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 1)] + public struct BiquadFilterParameter + { + /// + /// Set to true if the biquad filter is active. + /// + [MarshalAs(UnmanagedType.I1)] + public bool Enable; + + /// + /// Reserved/padding. + /// + private readonly byte _reserved; + + /// + /// Biquad filter numerator (b0, b1, b2). + /// + public Array3 Numerator; + + /// + /// Biquad filter denominator (a1, a2). + /// + /// a0 = 1 + public Array2 Denominator; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs new file mode 100644 index 00000000..65f265a3 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs @@ -0,0 +1,84 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for and . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AuxiliaryBufferParameter + { + /// + /// The input channel indices that will be used by the to write data to . + /// + public Array24 Input; + + /// + /// The output channel indices that will be used by the to read data from . + /// + public Array24 Output; + + /// + /// The total channel count used. + /// + public uint ChannelCount; + + /// + /// The target sample rate. + /// + public uint SampleRate; + + /// + /// The buffer storage total size. + /// + public uint BufferStorageSize; + + /// + /// The maximum number of channels supported. + /// + /// This is unused. + public uint ChannelCountMax; + + /// + /// The address of the start of the region containing two followed by the data that will be written by the . + /// + public ulong SendBufferInfoAddress; + + /// + /// The address of the start of the region containling data that will be written by the . + /// + /// This is unused. + public ulong SendBufferStorageAddress; + + /// + /// The address of the start of the region containing two followed by the data that will be read by the . + /// + /// Unused with . + public ulong ReturnBufferInfoAddress; + + /// + /// The address of the start of the region containling data that will be read by the . + /// + /// This is unused. + public ulong ReturnBufferStorageAddress; + + /// + /// Size of a sample of the mix buffer. + /// + /// This is unused. + public uint MixBufferSampleSize; + + /// + /// The total count of sample that can be stored. + /// + /// This is unused. + public uint TotalSampleCount; + + /// + /// The count of sample of the mix buffer. + /// + /// This is unused. + public uint MixBufferSampleCount; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs new file mode 100644 index 00000000..b12a941a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs @@ -0,0 +1,44 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BiquadFilterEffectParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// Biquad filter numerator (b0, b1, b2). + /// + public Array3 Numerator; + + /// + /// Biquad filter denominator (a1, a2). + /// + /// a0 = 1 + public Array2 Denominator; + + /// + /// The total channel count used. + /// + public byte ChannelCount; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs new file mode 100644 index 00000000..49b70e50 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs @@ -0,0 +1,32 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BufferMixParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array24 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array24 Output; + + /// + /// The output volumes of the mixes. + /// + public Array24 Volumes; + + /// + /// The total count of mixes used. + /// + public uint MixesCount; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs new file mode 100644 index 00000000..b403f137 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs @@ -0,0 +1,115 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CompressorParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The target sample rate. + /// + /// This is in kHz. + public int SampleRate; + + /// + /// The threshold. + /// + public float Threshold; + + /// + /// The compressor ratio. + /// + public float Ratio; + + /// + /// The attack time. + /// This is in microseconds. + /// + public int AttackTime; + + /// + /// The release time. + /// This is in microseconds. + /// + public int ReleaseTime; + + /// + /// The input gain. + /// + public float InputGain; + + /// + /// The attack coefficient. + /// + public float AttackCoefficient; + + /// + /// The release coefficient. + /// + public float ReleaseCoefficient; + + /// + /// The output gain. + /// + public float OutputGain; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Indicate if the makeup gain should be used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool MakeupGainEnabled; + + /// + /// Reserved/padding. + /// + private Array2 _reserved; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs new file mode 100644 index 00000000..99c97d9d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs @@ -0,0 +1,101 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DelayParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The maximum delay time in milliseconds. + /// + public uint DelayTimeMax; + + /// + /// The delay time in milliseconds. + /// + public uint DelayTime; + + /// + /// The target sample rate. (Q15) + /// + public uint SampleRate; + + /// + /// The input gain. (Q15) + /// + public uint InGain; + + /// + /// The feedback gain. (Q15) + /// + public uint FeedbackGain; + + /// + /// The output gain. (Q15) + /// + public uint OutGain; + + /// + /// The dry gain. (Q15) + /// + public uint DryGain; + + /// + /// The channel spread of the . (Q15) + /// + public uint ChannelSpread; + + /// + /// The low pass amount. (Q15) + /// + public uint LowPassAmount; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs new file mode 100644 index 00000000..23ccb8c8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs @@ -0,0 +1,138 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LimiterParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The target sample rate. + /// + /// This is in kHz. + public int SampleRate; + + /// + /// The look ahead max time. + /// This is in microseconds. + /// + public int LookAheadTimeMax; + + /// + /// The attack time. + /// This is in microseconds. + /// + public int AttackTime; + + /// + /// The release time. + /// This is in microseconds. + /// + public int ReleaseTime; + + /// + /// The look ahead time. + /// This is in microseconds. + /// + public int LookAheadTime; + + /// + /// The attack coefficient. + /// + public float AttackCoefficient; + + /// + /// The release coefficient. + /// + public float ReleaseCoefficient; + + /// + /// The threshold. + /// + public float Threshold; + + /// + /// The input gain. + /// + public float InputGain; + + /// + /// The output gain. + /// + public float OutputGain; + + /// + /// The minimum samples stored in the delay buffer. + /// + public int DelayBufferSampleCountMin; + + /// + /// The maximum samples stored in the delay buffer. + /// + public int DelayBufferSampleCountMax; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Indicate if the limiter effect should output statistics. + /// + [MarshalAs(UnmanagedType.I1)] + public bool StatisticsEnabled; + + /// + /// Indicate to the DSP that the user did a statistics reset. + /// + [MarshalAs(UnmanagedType.I1)] + public bool StatisticsReset; + + /// + /// Reserved/padding. + /// + private readonly byte _reserved; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs new file mode 100644 index 00000000..97e2f39f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs @@ -0,0 +1,31 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// Effect result state for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LimiterStatistics + { + /// + /// The max input sample value recorded by the limiter. + /// + public Array6 InputMax; + + /// + /// Compression gain min value. + /// + public Array6 CompressionGainMin; + + /// + /// Reset the statistics. + /// + public void Reset() + { + InputMax.AsSpan().Clear(); + CompressionGainMin.AsSpan().Fill(1.0f); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs new file mode 100644 index 00000000..d2cd7870 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs @@ -0,0 +1,127 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct Reverb3dParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// Reserved/unused. + /// + private readonly uint _reserved; + + /// + /// The target sample rate. + /// + /// This is in kHz. + public uint SampleRate; + + /// + /// Gain of the room high-frequency effect. + /// + public float RoomHf; + + /// + /// Reference high frequency. + /// + public float HfReference; + + /// + /// Reverberation decay time at low frequencies. + /// + public float DecayTime; + + /// + /// Ratio of the decay time at high frequencies to the decay time at low frequencies. + /// + public float HfDecayRatio; + + /// + /// Gain of the room effect. + /// + public float RoomGain; + + /// + /// Gain of the early reflections relative to . + /// + public float ReflectionsGain; + + /// + /// Gain of the late reverberation relative to . + /// + public float ReverbGain; + + /// + /// Echo density in the late reverberation decay. + /// + public float Diffusion; + + /// + /// Modal density in the late reverberation decay. + /// + public float ReflectionDelay; + + /// + /// Time limit between the early reflections and the late reverberation relative to the time of the first reflection. + /// + public float ReverbDelayTime; + + /// + /// Modal density in the late reverberation decay. + /// + public float Density; + + /// + /// The dry gain. + /// + public float DryGain; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState ParameterStatus; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs new file mode 100644 index 00000000..51ab156d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs @@ -0,0 +1,119 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ReverbParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The target sample rate. (Q15) + /// + /// This is in kHz. + public int SampleRate; + + /// + /// The early mode to use. + /// + public ReverbEarlyMode EarlyMode; + + /// + /// The gain to apply to the result of the early reflection. (Q15) + /// + public int EarlyGain; + + /// + /// The pre-delay time in milliseconds. (Q15) + /// + public int PreDelayTime; + + /// + /// The late mode to use. + /// + public ReverbLateMode LateMode; + + /// + /// The gain to apply to the result of the late reflection. (Q15) + /// + public int LateGain; + + /// + /// The decay time. (Q15) + /// + public int DecayTime; + + /// + /// The high frequency decay ratio. (Q15) + /// + /// If >= 0.995f, it is considered disabled. + public int HighFrequencyDecayRatio; + + /// + /// The coloration of the decay. (Q15) + /// + public int Coloration; + + /// + /// The reverb gain. (Q15) + /// + public int ReverbGain; + + /// + /// The output gain. (Q15) + /// + public int OutGain; + + /// + /// The dry gain. (Q15) + /// + public int DryGain; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public readonly bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs new file mode 100644 index 00000000..46686e3b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs @@ -0,0 +1,97 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for an effect version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectInParameterVersion1 : IEffectInParameter + { + /// + /// Type of the effect. + /// + public EffectType Type; + + /// + /// Set to true if the effect is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + /// + /// Set to true if the effect must be active. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEnabled; + + /// + /// Reserved/padding. + /// + private readonly byte _reserved1; + + /// + /// The target mix id of the effect. + /// + public int MixId; + + /// + /// Address of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferBase; + + /// + /// Size of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferSize; + + /// + /// Position of the effect while processing effects. + /// + public uint ProcessingOrder; + + /// + /// Reserved/padding. + /// + private readonly uint _reserved2; + + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)] + private struct SpecificDataStruct { } + + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + + readonly EffectType IEffectInParameter.Type => Type; + + readonly bool IEffectInParameter.IsNew => IsNew; + + readonly bool IEffectInParameter.IsEnabled => IsEnabled; + + readonly int IEffectInParameter.MixId => MixId; + + readonly ulong IEffectInParameter.BufferBase => BufferBase; + + readonly ulong IEffectInParameter.BufferSize => BufferSize; + + readonly uint IEffectInParameter.ProcessingOrder => ProcessingOrder; + + /// + /// Check if the given channel count is valid. + /// + /// The channel count to check + /// Returns true if the channel count is valid. + public static bool IsChannelCountValid(int channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs new file mode 100644 index 00000000..3854c714 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs @@ -0,0 +1,97 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for an effect version 2. (added with REV9) + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectInParameterVersion2 : IEffectInParameter + { + /// + /// Type of the effect. + /// + public EffectType Type; + + /// + /// Set to true if the effect is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + /// + /// Set to true if the effect must be active. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEnabled; + + /// + /// Reserved/padding. + /// + private readonly byte _reserved1; + + /// + /// The target mix id of the effect. + /// + public int MixId; + + /// + /// Address of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferBase; + + /// + /// Size of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferSize; + + /// + /// Position of the effect while processing effects. + /// + public uint ProcessingOrder; + + /// + /// Reserved/padding. + /// + private readonly uint _reserved2; + + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)] + private struct SpecificDataStruct { } + + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + + readonly EffectType IEffectInParameter.Type => Type; + + readonly bool IEffectInParameter.IsNew => IsNew; + + readonly bool IEffectInParameter.IsEnabled => IsEnabled; + + readonly int IEffectInParameter.MixId => MixId; + + readonly ulong IEffectInParameter.BufferBase => BufferBase; + + readonly ulong IEffectInParameter.BufferSize => BufferSize; + + readonly uint IEffectInParameter.ProcessingOrder => ProcessingOrder; + + /// + /// Check if the given channel count is valid. + /// + /// The channel count to check + /// Returns true if the channel count is valid. + public static bool IsChannelCountValid(int channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs new file mode 100644 index 00000000..3c3e9553 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for an effect version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectOutStatusVersion1 : IEffectOutStatus + { + /// + /// Current effect state. + /// + public EffectState State; + + /// + /// Unused/Reserved. + /// + private unsafe fixed byte _reserved[15]; + + EffectState IEffectOutStatus.State { readonly get => State; set => State = value; } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs new file mode 100644 index 00000000..ee058d3a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs @@ -0,0 +1,28 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for an effect version 2. (added with REV9) + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectOutStatusVersion2 : IEffectOutStatus + { + /// + /// Current effect state. + /// + public EffectState State; + + /// + /// Unused/Reserved. + /// + private unsafe fixed byte _reserved[15]; + + /// + /// Current result state. + /// + public EffectResultState ResultState; + + EffectState IEffectOutStatus.State { readonly get => State; set => State = value; } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs new file mode 100644 index 00000000..b3a4bae1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs @@ -0,0 +1,26 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Effect result state (added in REV9). + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectResultState + { + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0x80, Pack = 1)] + private struct SpecificDataStruct { } + + /// + /// Specific data changing depending of the type of effect. See also the namespace. + /// + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectState.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectState.cs new file mode 100644 index 00000000..c4d06f12 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectState.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// The state of an effect. + /// + public enum EffectState : byte + { + /// + /// The effect is enabled. + /// + Enabled = 3, + + /// + /// The effect is disabled. + /// + Disabled = 4, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs new file mode 100644 index 00000000..703c3e6d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs @@ -0,0 +1,53 @@ +using Ryujinx.Audio.Renderer.Common; +using System; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Generic interface to represent input information for an effect. + /// + public interface IEffectInParameter + { + /// + /// Type of the effect. + /// + EffectType Type { get; } + + /// + /// Set to true if the effect is new. + /// + bool IsNew { get; } + + /// + /// Set to true if the effect must be active. + /// + bool IsEnabled { get; } + + /// + /// The target mix id of the effect. + /// + int MixId { get; } + + /// + /// Address of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + ulong BufferBase { get; } + + /// + /// Size of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + ulong BufferSize { get; } + + /// + /// Position of the effect while processing effects. + /// + uint ProcessingOrder { get; } + + /// + /// Specific data changing depending of the . See also the namespace. + /// + Span SpecificData { get; } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs new file mode 100644 index 00000000..74d13220 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Generic interface to represent output information for an effect. + /// + public interface IEffectOutStatus + { + /// + /// Current effect state. + /// + EffectState State { get; set; } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs new file mode 100644 index 00000000..807232f2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs @@ -0,0 +1,43 @@ +using Ryujinx.Common.Memory; +using System; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Generic interface for the splitter destination parameters. + /// + public interface ISplitterDestinationInParameter + { + /// + /// Target splitter destination data id. + /// + int Id { get; } + + /// + /// The mix to output the result of the splitter. + /// + int DestinationId { get; } + + /// + /// Biquad filter parameters. + /// + Array2 BiquadFilters { get; } + + /// + /// Set to true if in use. + /// + bool IsUsed { get; } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + Span MixBufferVolume { get; } + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + bool IsMagicValid(); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs new file mode 100644 index 00000000..60250858 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs @@ -0,0 +1,33 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a memory pool. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MemoryPoolInParameter + { + /// + /// The CPU address used by the memory pool. + /// + public CpuAddress CpuAddress; + + /// + /// The size used by the memory pool. + /// + public ulong Size; + + /// + /// The target state the user wants. + /// + public MemoryPoolUserState State; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs new file mode 100644 index 00000000..a78937d0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs @@ -0,0 +1,22 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for a memory pool. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MemoryPoolOutStatus + { + /// + /// The current server memory pool state. + /// + public MemoryPoolUserState State; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs b/src/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs new file mode 100644 index 00000000..733b5ad7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs @@ -0,0 +1,27 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information header for mix updates on REV7 and later + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MixInParameterDirtyOnlyUpdate + { + /// + /// Magic of the header + /// + /// Never checked on hardware. + public uint Magic; + + /// + /// The count of following this header. + /// + public uint MixCount; + + /// + /// Reserved/unused. + /// + private unsafe fixed byte _reserved[24]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs new file mode 100644 index 00000000..2eec04a2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs @@ -0,0 +1,95 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a mix. + /// + /// Also used on the client side for mix tracking. + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MixParameter + { + /// + /// Base volume of the mix. + /// + public float Volume; + + /// + /// Target sample rate of the mix. + /// + public uint SampleRate; + + /// + /// Target buffer count. + /// + public uint BufferCount; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Set to true if it was changed. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsDirty; + + /// + /// Reserved/padding. + /// + private readonly ushort _reserved1; + + /// + /// The id of the mix. + /// + public int MixId; + + /// + /// The effect count. (client side) + /// + public uint EffectCount; + + /// + /// The mix node id. + /// + public int NodeId; + + /// + /// Reserved/padding. + /// + private readonly ulong _reserved2; + + /// + /// Mix buffer volumes storage. + /// + private MixVolumeArray _mixBufferVolumeArray; + + /// + /// The mix to output the result of this mix. + /// + public int DestinationMixId; + + /// + /// The splitter to output the result of this mix. + /// + public uint DestinationSplitterId; + + /// + /// Reserved/padding. + /// + private readonly uint _reserved3; + + [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax * Constants.MixBufferCountMax, Pack = 1)] + private struct MixVolumeArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when no splitter id is specified. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolumeArray); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs new file mode 100644 index 00000000..806f7fa8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Performance +{ + /// + /// Input information for performance monitoring. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PerformanceInParameter + { + /// + /// The target node id to monitor performance on. + /// + public int TargetNodeId; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs new file mode 100644 index 00000000..839d6eb6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Performance +{ + /// + /// Output information for performance monitoring. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PerformanceOutStatus + { + /// + /// Indicates the total size output to the performance buffer. + /// + public uint HistorySize; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs new file mode 100644 index 00000000..c97ce296 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Renderer output information on REV5 and later. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RendererInfoOutStatus + { + /// + /// The count of updates sent to the . + /// + public ulong ElapsedFrameCount; + + /// + /// Reserved/Unused. + /// + private readonly ulong _reserved; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs new file mode 100644 index 00000000..0d4b276e --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs @@ -0,0 +1,61 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Parameter.Sink +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CircularBufferParameter + { + /// + /// The CPU address of the user circular buffer. + /// + public CpuAddress BufferAddress; + + /// + /// The size of the user circular buffer. + /// + public uint BufferSize; + + /// + /// The total count of channels to output to the circular buffer. + /// + public uint InputCount; + + /// + /// The target sample count to output per update in the circular buffer. + /// + public uint SampleCount; + + /// + /// Last read offset on the CPU side. + /// + public uint LastReadOffset; + + /// + /// The target . + /// + /// Only is supported. + public SampleFormat SampleFormat; + + /// + /// Reserved/padding. + /// + private unsafe fixed byte _reserved1[3]; + + /// + /// The input channels index that will be used. + /// + public Array6 Input; + + /// + /// Reserved/padding. + /// + private readonly ushort _reserved2; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs new file mode 100644 index 00000000..652d02a6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs @@ -0,0 +1,58 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Sink +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DeviceParameter + { + /// + /// Device name storage. + /// + private DeviceNameStruct _deviceName; + + /// + /// Reserved/padding. + /// + private readonly byte _padding; + + /// + /// The total count of channels to output to the device. + /// + public uint InputCount; + + /// + /// The input channels index that will be used. + /// + public Array6 Input; + + /// + /// Reserved/padding. + /// + private readonly byte _reserved; + + /// + /// Set to true if the user controls Surround to Stereo downmixing coefficients. + /// + [MarshalAs(UnmanagedType.I1)] + public bool DownMixParameterEnabled; + + /// + /// The user Surround to Stereo downmixing coefficients. + /// + public Array4 DownMixParameter; + + [StructLayout(LayoutKind.Sequential, Size = 0xFF, Pack = 1)] + private struct DeviceNameStruct { } + + /// + /// The output device name. + /// + public Span DeviceName => SpanHelpers.AsSpan(ref _deviceName); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs new file mode 100644 index 00000000..3c1ac09c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs @@ -0,0 +1,53 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a sink. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SinkInParameter + { + /// + /// Type of the sink. + /// + public SinkType Type; + + /// + /// Set to true if the sink is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Reserved/padding. + /// + private readonly ushort _reserved1; + + /// + /// The node id of the sink. + /// + public int NodeId; + + /// + /// Reserved/padding. + /// + private unsafe fixed ulong _reserved2[3]; + + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0x120, Pack = 1)] + private struct SpecificDataStruct { } + + /// + /// Specific data changing depending of the . See also the namespace. + /// + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs new file mode 100644 index 00000000..dd0f867b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for a sink. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SinkOutStatus + { + /// + /// Last written offset if the sink type is . + /// + public uint LastWrittenOffset; + + /// + /// Reserved/padding. + /// + private readonly uint _padding; + + /// + /// Reserved/padding. + /// + private unsafe fixed ulong _reserved[3]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs new file mode 100644 index 00000000..029c001e --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs @@ -0,0 +1,76 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input header for a splitter destination version 1 update. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SplitterDestinationInParameterVersion1 : ISplitterDestinationInParameter + { + /// + /// Magic of the input header. + /// + public uint Magic; + + /// + /// Target splitter destination data id. + /// + public int Id; + + /// + /// Mix buffer volumes storage. + /// + private MixArray _mixBufferVolume; + + /// + /// The mix to output the result of the splitter. + /// + public int DestinationId; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Reserved/padding. + /// + private unsafe fixed byte _reserved[3]; + + [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolume); + + readonly int ISplitterDestinationInParameter.Id => Id; + + readonly int ISplitterDestinationInParameter.DestinationId => DestinationId; + + readonly Array2 ISplitterDestinationInParameter.BiquadFilters => default; + + readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed; + + /// + /// The expected constant of any input header. + /// + private const uint ValidMagic = 0x44444E53; + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + public readonly bool IsMagicValid() + { + return Magic == ValidMagic; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs new file mode 100644 index 00000000..312be8b7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs @@ -0,0 +1,81 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input header for a splitter destination version 2 update. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SplitterDestinationInParameterVersion2 : ISplitterDestinationInParameter + { + /// + /// Magic of the input header. + /// + public uint Magic; + + /// + /// Target splitter destination data id. + /// + public int Id; + + /// + /// Mix buffer volumes storage. + /// + private MixArray _mixBufferVolume; + + /// + /// The mix to output the result of the splitter. + /// + public int DestinationId; + + /// + /// Biquad filter parameters. + /// + public Array2 BiquadFilters; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Reserved/padding. + /// + private unsafe fixed byte _reserved[11]; + + [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolume); + + readonly int ISplitterDestinationInParameter.Id => Id; + + readonly int ISplitterDestinationInParameter.DestinationId => DestinationId; + + readonly Array2 ISplitterDestinationInParameter.BiquadFilters => BiquadFilters; + + readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed; + + /// + /// The expected constant of any input header. + /// + private const uint ValidMagic = 0x44444E53; + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + public readonly bool IsMagicValid() + { + return Magic == ValidMagic; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs new file mode 100644 index 00000000..2567b15a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs @@ -0,0 +1,46 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input header for a splitter state update. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SplitterInParameter + { + /// + /// Magic of the input header. + /// + public uint Magic; + + /// + /// Target splitter id. + /// + public int Id; + + /// + /// Target sample rate to use on the splitter. + /// + public uint SampleRate; + + /// + /// Count of splitter destinations. + /// + /// Splitter destination ids are defined right after this header. + public int DestinationCount; + + /// + /// The expected constant of any input header. + /// + private const uint ValidMagic = 0x49444E53; + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + public readonly bool IsMagicValid() + { + return Magic == ValidMagic; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs new file mode 100644 index 00000000..10fa866e --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs @@ -0,0 +1,45 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input header for splitter update. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SplitterInParameterHeader + { + /// + /// Magic of the input header. + /// + public uint Magic; + + /// + /// The count of after the header. + /// + public uint SplitterCount; + + /// + /// The count of splitter destinations after the header and splitter info. + /// + public uint SplitterDestinationCount; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[5]; + + /// + /// The expected constant of any input splitter header. + /// + private const uint ValidMagic = 0x48444E53; + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + public readonly bool IsMagicValid() + { + return Magic == ValidMagic; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs new file mode 100644 index 00000000..6cff1a25 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs @@ -0,0 +1,28 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a voice channel resources. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x70, Pack = 1)] + public struct VoiceChannelResourceInParameter + { + /// + /// The id of the voice channel resource. + /// + public uint Id; + + /// + /// Mix volumes for the voice channel resource. + /// + public Array24 Mix; + + /// + /// Indicate if the voice channel resource is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs new file mode 100644 index 00000000..f33d82aa --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs @@ -0,0 +1,332 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp; +using Ryujinx.Common.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a voice. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x170, Pack = 1)] + public struct VoiceInParameter + { + /// + /// Id of the voice. + /// + public int Id; + + /// + /// Node id of the voice. + /// + public int NodeId; + + /// + /// Set to true if the voice is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + /// + /// Set to true if the voice is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool InUse; + + /// + /// The voice wanted by the user. + /// + public PlayState PlayState; + + /// + /// The of the voice. + /// + public SampleFormat SampleFormat; + + /// + /// The sample rate of the voice. + /// + public uint SampleRate; + + /// + /// The priority of the voice. + /// + public uint Priority; + + /// + /// Target sorting position of the voice. (Used to sort voices with the same ) + /// + public uint SortingOrder; + + /// + /// The total channel count used. + /// + public uint ChannelCount; + + /// + /// The pitch used on the voice. + /// + public float Pitch; + + /// + /// The output volume of the voice. + /// + public float Volume; + + /// + /// Biquad filters to apply to the output of the voice. + /// + public Array2 BiquadFilters; + + /// + /// Total count of of the voice. + /// + public uint WaveBuffersCount; + + /// + /// Current playing of the voice. + /// + public uint WaveBuffersIndex; + + /// + /// Reserved/unused. + /// + private readonly uint _reserved1; + + /// + /// User state address required by the data source. + /// + /// Only used for as the address of the GC-ADPCM coefficients. + public ulong DataSourceStateAddress; + + /// + /// User state size required by the data source. + /// + /// Only used for as the size of the GC-ADPCM coefficients. + public ulong DataSourceStateSize; + + /// + /// The target mix id of the voice. + /// + public int MixId; + + /// + /// The target splitter id of the voice. + /// + public uint SplitterId; + + /// + /// The wavebuffer parameters of this voice. + /// + public Array4 WaveBuffers; + + /// + /// The channel resource ids associated to the voice. + /// + public Array6 ChannelResourceIds; + + /// + /// Reset the voice drop flag during voice server update. + /// + [MarshalAs(UnmanagedType.I1)] + public bool ResetVoiceDropFlag; + + /// + /// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played. + /// + /// This was added on REV5. + public byte FlushWaveBufferCount; + + /// + /// Reserved/unused. + /// + private readonly ushort _reserved2; + + /// + /// Change the behaviour of the voice. + /// + /// This was added on REV5. + public DecodingBehaviour DecodingBehaviourFlags; + + /// + /// Change the Sample Rate Conversion (SRC) quality of the voice. + /// + /// This was added on REV8. + public SampleRateConversionQuality SrcQuality; + + /// + /// This was previously used for opus codec support on the Audio Renderer and was removed on REV3. + /// + public uint ExternalContext; + + /// + /// This was previously used for opus codec support on the Audio Renderer and was removed on REV3. + /// + public uint ExternalContextSize; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved3[2]; + + /// + /// Input information for a voice wavebuffer. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x38, Pack = 1)] + public struct WaveBufferInternal + { + /// + /// Address of the wavebuffer data. + /// + public ulong Address; + + /// + /// Size of the wavebuffer data. + /// + public ulong Size; + + /// + /// Offset of the first sample to play. + /// + public uint StartSampleOffset; + + /// + /// Offset of the last sample to play. + /// + public uint EndSampleOffset; + + /// + /// If set to true, the wavebuffer will loop when reaching . + /// + /// + /// Starting with REV8, you can specify how many times to loop the wavebuffer () and where it should start and end when looping ( and ) + /// + [MarshalAs(UnmanagedType.I1)] + public bool ShouldLoop; + + /// + /// Indicates that this is the last wavebuffer to play of the voice. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEndOfStream; + + /// + /// Indicates if the server should update its internal state. + /// + [MarshalAs(UnmanagedType.I1)] + public bool SentToServer; + + /// + /// Reserved/unused. + /// + private readonly byte _reserved; + + /// + /// If set to anything other than 0, specifies how many times to loop the wavebuffer. + /// + /// This was added in REV8. + public int LoopCount; + + /// + /// Address of the context used by the sample decoder. + /// + /// This is only currently used by . + public ulong ContextAddress; + + /// + /// Size of the context used by the sample decoder. + /// + /// This is only currently used by . + public ulong ContextSize; + + /// + /// If set to anything other than 0, specifies the offset of the first sample to play when looping. + /// + /// This was added in REV8. + public uint LoopFirstSampleOffset; + + /// + /// If set to anything other than 0, specifies the offset of the last sample to play when looping. + /// + /// This was added in REV8. + public uint LoopLastSampleOffset; + + /// + /// Check if the sample offsets are in a valid range for generic PCM. + /// + /// The PCM sample type + /// Returns true if the sample offset are in range of the size. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private readonly bool IsSampleOffsetInRangeForPcm() where T : unmanaged + { + uint dataTypeSize = (uint)Unsafe.SizeOf(); + + return (ulong)StartSampleOffset * dataTypeSize <= Size && + (ulong)EndSampleOffset * dataTypeSize <= Size; + } + + /// + /// Check if the sample offsets are in a valid range for the given . + /// + /// The target + /// Returns true if the sample offset are in range of the size. + public readonly bool IsSampleOffsetValid(SampleFormat format) + { + return format switch + { + SampleFormat.PcmInt16 => IsSampleOffsetInRangeForPcm(), + SampleFormat.PcmFloat => IsSampleOffsetInRangeForPcm(), + SampleFormat.Adpcm => AdpcmHelper.GetAdpcmDataSize((int)StartSampleOffset) <= Size && AdpcmHelper.GetAdpcmDataSize((int)EndSampleOffset) <= Size, + _ => throw new NotImplementedException($"{format} not implemented!"), + }; + } + } + + /// + /// Flag altering the behaviour of wavebuffer decoding. + /// + [Flags] + public enum DecodingBehaviour : ushort + { + /// + /// Default decoding behaviour. + /// + Default = 0, + + /// + /// Reset the played samples accumulator when looping. + /// + PlayedSampleCountResetWhenLooping = 1, + + /// + /// Skip pitch and Sample Rate Conversion (SRC). + /// + SkipPitchAndSampleRateConversion = 2, + } + + /// + /// Specify the quality to use during Sample Rate Conversion (SRC) and pitch handling. + /// + /// This was added in REV8. + public enum SampleRateConversionQuality : byte + { + /// + /// Resample interpolating 4 samples per output sample. + /// + Default, + + /// + /// Resample interpolating 8 samples per output sample. + /// + High, + + /// + /// Resample interpolating 1 samples per output sample. + /// + Low, + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs new file mode 100644 index 00000000..a7c74983 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs @@ -0,0 +1,35 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information about a voice. + /// + /// See + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct VoiceOutStatus + { + /// + /// The total amount of samples that was played. + /// + /// This is reset to 0 when a finishes playing and is set. + /// This is reset to 0 when looping while is set. + public ulong PlayedSampleCount; + + /// + /// The total amount of consumed. + /// + public uint PlayedWaveBuffersCount; + + /// + /// If set to true, the voice was dropped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool VoiceDropFlag; + + /// + /// Reserved/unused. + /// + private unsafe fixed byte _reserved[3]; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs new file mode 100644 index 00000000..246889c4 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs @@ -0,0 +1,897 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Audio.Renderer.Server.Mix; +using Ryujinx.Audio.Renderer.Server.Performance; +using Ryujinx.Audio.Renderer.Server.Sink; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Audio.Renderer.Server.Types; +using Ryujinx.Audio.Renderer.Server.Upsampler; +using Ryujinx.Audio.Renderer.Server.Voice; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Diagnostics; +using System.Threading; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server +{ + public class AudioRenderSystem : IDisposable + { + private readonly object _lock = new(); + + private AudioRendererRenderingDevice _renderingDevice; + private AudioRendererExecutionMode _executionMode; + private readonly IWritableEvent _systemEvent; + private MemoryPoolState _dspMemoryPoolState; + private readonly VoiceContext _voiceContext; + private readonly MixContext _mixContext; + private readonly SinkContext _sinkContext; + private readonly SplitterContext _splitterContext; + private readonly EffectContext _effectContext; + private PerformanceManager _performanceManager; + private UpsamplerManager _upsamplerManager; + private bool _isActive; + private BehaviourContext _behaviourContext; +#pragma warning disable IDE0052 // Remove unread private member + private ulong _totalElapsedTicksUpdating; + private ulong _totalElapsedTicks; +#pragma warning restore IDE0052 + private int _sessionId; + private Memory _memoryPools; + + private uint _sampleRate; + private uint _sampleCount; + private uint _mixBufferCount; + private uint _voiceChannelCountMax; + private uint _upsamplerCount; + private uint _memoryPoolCount; + private uint _processHandle; + private ulong _appletResourceId; + + private MemoryHandle _workBufferMemoryPin; + + private Memory _mixBuffer; + private Memory _depopBuffer; + + private uint _renderingTimeLimitPercent; + private bool _voiceDropEnabled; + private uint _voiceDropCount; + private float _voiceDropParameter; + private bool _isDspRunningBehind; + + private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator; + + private Memory _performanceBuffer; + + public IVirtualMemoryManager MemoryManager { get; private set; } + + private ulong _elapsedFrameCount; + private ulong _renderingStartTick; + + private readonly AudioRendererManager _manager; + + private int _disposeState; + + public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent) + { + _manager = manager; + _dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp); + _voiceContext = new VoiceContext(); + _mixContext = new MixContext(); + _sinkContext = new SinkContext(); + _splitterContext = new SplitterContext(); + _effectContext = new EffectContext(); + + _commandProcessingTimeEstimator = null; + _systemEvent = systemEvent; + _behaviourContext = new BehaviourContext(); + + _totalElapsedTicksUpdating = 0; + _sessionId = 0; + _voiceDropParameter = 1.0f; + } + + public ResultCode Initialize( + ref AudioRendererConfiguration parameter, + uint processHandle, + Memory workBufferMemory, + CpuAddress workBuffer, + ulong workBufferSize, + int sessionId, + ulong appletResourceId, + IVirtualMemoryManager memoryManager) + { + if (!BehaviourContext.CheckValidRevision(parameter.Revision)) + { + return ResultCode.OperationFailed; + } + + if (GetWorkBufferSize(ref parameter) > workBufferSize) + { + return ResultCode.WorkBufferTooSmall; + } + + Debug.Assert(parameter.RenderingDevice == AudioRendererRenderingDevice.Dsp && parameter.ExecutionMode == AudioRendererExecutionMode.Auto); + + Logger.Info?.Print(LogClass.AudioRenderer, $"Initializing with REV{BehaviourContext.GetRevisionNumber(parameter.Revision)}"); + + _behaviourContext.SetUserRevision(parameter.Revision); + + _sampleRate = parameter.SampleRate; + _sampleCount = parameter.SampleCount; + _mixBufferCount = parameter.MixBufferCount; + _voiceChannelCountMax = Constants.VoiceChannelCountMax; + _upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount; + _appletResourceId = appletResourceId; + _memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount; + _renderingDevice = parameter.RenderingDevice; + _executionMode = parameter.ExecutionMode; + _sessionId = sessionId; + MemoryManager = memoryManager; + + if (memoryManager is IRefCounted rc) + { + rc.IncrementReferenceCount(); + } + + WorkBufferAllocator workBufferAllocator; + + workBufferMemory.Span.Clear(); + _workBufferMemoryPin = workBufferMemory.Pin(); + + workBufferAllocator = new WorkBufferAllocator(workBufferMemory); + + PoolMapper poolMapper = new(processHandle, false); + poolMapper.InitializeSystemPool(ref _dspMemoryPoolState, workBuffer, workBufferSize); + + _mixBuffer = workBufferAllocator.Allocate(_sampleCount * (_voiceChannelCountMax + _mixBufferCount), 0x10); + + if (_mixBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + Memory upSamplerWorkBuffer = workBufferAllocator.Allocate(Constants.TargetSampleCount * (_voiceChannelCountMax + _mixBufferCount) * _upsamplerCount, 0x10); + + if (upSamplerWorkBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + _depopBuffer = workBufferAllocator.Allocate(BitUtils.AlignUp(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment); + + if (_depopBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + Memory splitterBqfStates = Memory.Empty; + + if (_behaviourContext.IsBiquadFilterParameterForSplitterEnabled() && + parameter.SplitterCount > 0 && + parameter.SplitterDestinationCount > 0) + { + splitterBqfStates = workBufferAllocator.Allocate(parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10); + + if (splitterBqfStates.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + splitterBqfStates.Span.Clear(); + } + + // Invalidate DSP cache on what was currently allocated with workBuffer. + AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset); + + Debug.Assert((workBufferAllocator.Offset % Constants.BufferAlignment) == 0); + + Memory voices = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceState.Alignment); + + if (voices.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + foreach (ref VoiceState voice in voices.Span) + { + voice.Initialize(); + } + + // A pain to handle as we can't have VoiceState*, use indices to be a bit more safe + Memory sortedVoices = workBufferAllocator.Allocate(parameter.VoiceCount, 0x10); + + if (sortedVoices.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + // Clear memory (use -1 as it's an invalid index) + sortedVoices.Span.Fill(-1); + + Memory voiceChannelResources = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceChannelResource.Alignment); + + if (voiceChannelResources.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + for (uint id = 0; id < voiceChannelResources.Length; id++) + { + ref VoiceChannelResource voiceChannelResource = ref voiceChannelResources.Span[(int)id]; + + voiceChannelResource.Id = id; + voiceChannelResource.IsUsed = false; + } + + Memory voiceUpdateStates = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceUpdateState.Align); + + if (voiceUpdateStates.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + uint mixesCount = parameter.SubMixBufferCount + 1; + + Memory mixes = workBufferAllocator.Allocate(mixesCount, MixState.Alignment); + + if (mixes.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + if (parameter.EffectCount == 0) + { + foreach (ref MixState mix in mixes.Span) + { + mix = new MixState(Memory.Empty, ref _behaviourContext); + } + } + else + { + Memory effectProcessingOrderArray = workBufferAllocator.Allocate(parameter.EffectCount * mixesCount, 0x10); + + foreach (ref MixState mix in mixes.Span) + { + mix = new MixState(effectProcessingOrderArray[..(int)parameter.EffectCount], ref _behaviourContext); + + effectProcessingOrderArray = effectProcessingOrderArray[(int)parameter.EffectCount..]; + } + } + + // Initialize the final mix id + mixes.Span[0].MixId = Constants.FinalMixId; + + Memory sortedMixesState = workBufferAllocator.Allocate(mixesCount, 0x10); + + if (sortedMixesState.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + // Clear memory (use -1 as it's an invalid index) + sortedMixesState.Span.Fill(-1); + + Memory nodeStatesWorkBuffer = Memory.Empty; + Memory edgeMatrixWorkBuffer = Memory.Empty; + + if (_behaviourContext.IsSplitterSupported()) + { + nodeStatesWorkBuffer = workBufferAllocator.Allocate((uint)NodeStates.GetWorkBufferSize((int)mixesCount), 1); + edgeMatrixWorkBuffer = workBufferAllocator.Allocate((uint)EdgeMatrix.GetWorkBufferSize((int)mixesCount), 1); + + if (nodeStatesWorkBuffer.IsEmpty || edgeMatrixWorkBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + } + + _mixContext.Initialize(sortedMixesState, mixes, nodeStatesWorkBuffer, edgeMatrixWorkBuffer); + + _memoryPools = workBufferAllocator.Allocate(_memoryPoolCount, MemoryPoolState.Alignment); + + if (_memoryPools.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + foreach (ref MemoryPoolState state in _memoryPools.Span) + { + state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + } + + if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator, splitterBqfStates)) + { + return ResultCode.WorkBufferTooSmall; + } + + _processHandle = processHandle; + + _upsamplerManager = new UpsamplerManager(upSamplerWorkBuffer, _upsamplerCount); + + _effectContext.Initialize(parameter.EffectCount, _behaviourContext.IsEffectInfoVersion2Supported() ? parameter.EffectCount : 0); + _sinkContext.Initialize(parameter.SinkCount); + + Memory voiceUpdateStatesDsp = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceUpdateState.Align); + + if (voiceUpdateStatesDsp.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + _voiceContext.Initialize(sortedVoices, voices, voiceChannelResources, voiceUpdateStates, voiceUpdateStatesDsp, parameter.VoiceCount); + + if (parameter.PerformanceMetricFramesCount > 0) + { + ulong performanceBufferSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref _behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC; + + _performanceBuffer = workBufferAllocator.Allocate(performanceBufferSize, Constants.BufferAlignment); + + if (_performanceBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + _performanceManager = PerformanceManager.Create(_performanceBuffer, ref parameter, _behaviourContext); + } + else + { + _performanceManager = null; + } + + _totalElapsedTicksUpdating = 0; + _totalElapsedTicks = 0; + _renderingTimeLimitPercent = 100; + _voiceDropEnabled = parameter.VoiceDropEnabled && _executionMode == AudioRendererExecutionMode.Auto; + + AudioProcessorMemoryManager.InvalidateDataCache(workBuffer, workBufferSize); + + _processHandle = processHandle; + _elapsedFrameCount = 0; + _voiceDropParameter = 1.0f; + + _commandProcessingTimeEstimator = _behaviourContext.GetCommandProcessingTimeEstimatorVersion() switch + { + 1 => new CommandProcessingTimeEstimatorVersion1(_sampleCount, _mixBufferCount), + 2 => new CommandProcessingTimeEstimatorVersion2(_sampleCount, _mixBufferCount), + 3 => new CommandProcessingTimeEstimatorVersion3(_sampleCount, _mixBufferCount), + 4 => new CommandProcessingTimeEstimatorVersion4(_sampleCount, _mixBufferCount), + 5 => new CommandProcessingTimeEstimatorVersion5(_sampleCount, _mixBufferCount), + _ => throw new NotImplementedException($"Unsupported processing time estimator version {_behaviourContext.GetCommandProcessingTimeEstimatorVersion()}."), + }; + + return ResultCode.Success; + } + + public void Start() + { + Logger.Info?.Print(LogClass.AudioRenderer, $"Starting renderer id {_sessionId}"); + + lock (_lock) + { + _elapsedFrameCount = 0; + _isActive = true; + } + } + + public void Stop() + { + Logger.Info?.Print(LogClass.AudioRenderer, $"Stopping renderer id {_sessionId}"); + + lock (_lock) + { + _isActive = false; + } + + Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}"); + } + + public void Disable() + { + lock (_lock) + { + _isActive = false; + } + } + + public ResultCode Update(Memory output, Memory performanceOutput, ReadOnlySequence input) + { + lock (_lock) + { + ulong updateStartTicks = GetSystemTicks(); + + output.Span.Clear(); + + StateUpdater stateUpdater = new(input, output, _processHandle, _behaviourContext); + + ResultCode result; + + result = stateUpdater.UpdateBehaviourContext(); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateMemoryPools(_memoryPools.Span); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateVoiceChannelResources(_voiceContext); + + if (result != ResultCode.Success) + { + return result; + } + + PoolMapper poolMapper = new PoolMapper(_processHandle, _memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + result = stateUpdater.UpdateVoices(_voiceContext, poolMapper); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateEffects(_effectContext, _isActive, poolMapper); + + if (result != ResultCode.Success) + { + return result; + } + + if (_behaviourContext.IsSplitterSupported()) + { + result = stateUpdater.UpdateSplitter(_splitterContext); + + if (result != ResultCode.Success) + { + return result; + } + } + + result = stateUpdater.UpdateMixes(_mixContext, GetMixBufferCount(), _effectContext, _splitterContext); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateSinks(_sinkContext, poolMapper); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdatePerformanceBuffer(_performanceManager, performanceOutput.Span); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateErrorInfo(); + + if (result != ResultCode.Success) + { + return result; + } + + if (_behaviourContext.IsElapsedFrameCountSupported()) + { + result = stateUpdater.UpdateRendererInfo(_elapsedFrameCount); + + if (result != ResultCode.Success) + { + return result; + } + } + + result = stateUpdater.CheckConsumedSize(); + + if (result != ResultCode.Success) + { + return result; + } + + _systemEvent.Clear(); + + ulong updateEndTicks = GetSystemTicks(); + + _totalElapsedTicksUpdating += (updateEndTicks - updateStartTicks); + + return result; + } + } + + private ulong GetSystemTicks() + { + return (ulong)(_manager.TickSource.ElapsedSeconds * Constants.TargetTimerFrequency); + } + + private uint ComputeVoiceDrop(CommandBuffer commandBuffer, uint voicesEstimatedTime, long deltaTimeDsp) + { + int i; + + for (i = 0; i < commandBuffer.CommandList.Commands.Count; i++) + { + ICommand command = commandBuffer.CommandList.Commands[i]; + + CommandType commandType = command.CommandType; + + if (commandType == CommandType.AdpcmDataSourceVersion1 || + commandType == CommandType.AdpcmDataSourceVersion2 || + commandType == CommandType.PcmInt16DataSourceVersion1 || + commandType == CommandType.PcmInt16DataSourceVersion2 || + commandType == CommandType.PcmFloatDataSourceVersion1 || + commandType == CommandType.PcmFloatDataSourceVersion2 || + commandType == CommandType.Performance) + { + break; + } + } + + uint voiceDropped = 0; + + for (; i < commandBuffer.CommandList.Commands.Count; i++) + { + ICommand targetCommand = commandBuffer.CommandList.Commands[i]; + + int targetNodeId = targetCommand.NodeId; + + if (voicesEstimatedTime <= deltaTimeDsp || NodeIdHelper.GetType(targetNodeId) != NodeIdType.Voice) + { + break; + } + + ref VoiceState voice = ref _voiceContext.GetState(NodeIdHelper.GetBase(targetNodeId)); + + if (voice.Priority == Constants.VoiceHighestPriority) + { + break; + } + + // We can safely drop this voice, disable all associated commands while activating depop preparation commands. + voiceDropped++; + voice.VoiceDropFlag = true; + + Logger.Warning?.Print(LogClass.AudioRenderer, $"Dropping voice {voice.NodeId}"); + + for (; i < commandBuffer.CommandList.Commands.Count; i++) + { + ICommand command = commandBuffer.CommandList.Commands[i]; + + if (command.NodeId != targetNodeId) + { + break; + } + + if (command.CommandType == CommandType.DepopPrepare) + { + command.Enabled = true; + } + else if (command.CommandType == CommandType.Performance || !command.Enabled) + { + continue; + } + else + { + command.Enabled = false; + + voicesEstimatedTime -= (uint)(_voiceDropParameter * command.EstimatedProcessingTime); + } + } + } + + return voiceDropped; + } + + private void GenerateCommandList(out CommandList commandList) + { + Debug.Assert(_executionMode == AudioRendererExecutionMode.Auto); + + PoolMapper.ClearUsageState(_memoryPools); + + ulong startTicks = GetSystemTicks(); + + commandList = new CommandList(this); + + if (_performanceManager != null) + { + _performanceManager.TapFrame(_isDspRunningBehind, _voiceDropCount, _renderingStartTick); + + _isDspRunningBehind = false; + _voiceDropCount = 0; + _renderingStartTick = 0; + } + + CommandBuffer commandBuffer = new(commandList, _commandProcessingTimeEstimator); + + CommandGenerator commandGenerator = new(commandBuffer, GetContext(), _voiceContext, _mixContext, _effectContext, _sinkContext, _splitterContext, _performanceManager); + + _voiceContext.Sort(); + commandGenerator.GenerateVoices(); + + uint voicesEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime); + + commandGenerator.GenerateSubMixes(); + commandGenerator.GenerateFinalMixes(); + commandGenerator.GenerateSinks(); + + uint totalEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime); + + if (_voiceDropEnabled) + { + long maxDspTime = GetMaxAllocatedTimeForDsp(); + + long restEstimateTime = totalEstimatedTime - voicesEstimatedTime; + + long deltaTimeDsp = Math.Max(maxDspTime - restEstimateTime, 0); + + _voiceDropCount = ComputeVoiceDrop(commandBuffer, voicesEstimatedTime, deltaTimeDsp); + } + + _voiceContext.UpdateForCommandGeneration(); + + if (_behaviourContext.IsEffectInfoVersion2Supported()) + { + _effectContext.UpdateResultStateForCommandGeneration(); + } + + ulong endTicks = GetSystemTicks(); + + _totalElapsedTicks = endTicks - startTicks; + + _renderingStartTick = GetSystemTicks(); + _elapsedFrameCount++; + } + + private int GetMaxAllocatedTimeForDsp() + { + return (int)(Constants.AudioProcessorMaxUpdateTimePerSessions * _behaviourContext.GetAudioRendererProcessingTimeLimit() * (GetRenderingTimeLimit() / 100.0f)); + } + + public void SendCommands() + { + lock (_lock) + { + if (_isActive) + { + if (!_manager.Processor.HasRemainingCommands(_sessionId)) + { + GenerateCommandList(out CommandList commands); + + _manager.Processor.Send(_sessionId, + commands, + GetMaxAllocatedTimeForDsp(), + _appletResourceId); + + _systemEvent.Signal(); + } + else + { + _isDspRunningBehind = true; + } + } + } + } + + public uint GetMixBufferCount() + { + return _mixBufferCount; + } + + public void SetRenderingTimeLimitPercent(uint percent) + { + Debug.Assert(percent <= 100); + + _renderingTimeLimitPercent = percent; + } + + public uint GetRenderingTimeLimit() + { + return _renderingTimeLimitPercent; + } + + public Memory GetMixBuffer() + { + return _mixBuffer; + } + + public uint GetSampleCount() + { + return _sampleCount; + } + + public uint GetSampleRate() + { + return _sampleRate; + } + + public uint GetVoiceChannelCountMax() + { + return _voiceChannelCountMax; + } + + public bool IsActive() + { + return _isActive; + } + + private RendererSystemContext GetContext() + { + return new RendererSystemContext + { + ChannelCount = _manager.Processor.OutputDevices[_sessionId].GetChannelCount(), + BehaviourContext = _behaviourContext, + DepopBuffer = _depopBuffer, + MixBufferCount = GetMixBufferCount(), + SessionId = _sessionId, + UpsamplerManager = _upsamplerManager, + }; + } + + public int GetSessionId() + { + return _sessionId; + } + + public static ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter) + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(parameter.Revision); + + uint mixesCount = parameter.SubMixBufferCount + 1; + + uint memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount; + + ulong size = 0; + + // Mix Buffers + size = WorkBufferAllocator.GetTargetSize(size, parameter.SampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount), 0x10); + + // Upsampler workbuffer + size = WorkBufferAllocator.GetTargetSize(size, Constants.TargetSampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount) * (parameter.SinkCount + parameter.SubMixBufferCount), 0x10); + + // Depop buffer + size = WorkBufferAllocator.GetTargetSize(size, BitUtils.AlignUp(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment); + + // Voice + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceState.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, 0x10); + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceChannelResource.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceUpdateState.Align); + + // Mix + size = WorkBufferAllocator.GetTargetSize(size, mixesCount, MixState.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.EffectCount * mixesCount, 0x10); + size = WorkBufferAllocator.GetTargetSize(size, mixesCount, 0x10); + + if (behaviourContext.IsSplitterSupported()) + { + size += (ulong)BitUtils.AlignUp(NodeStates.GetWorkBufferSize((int)mixesCount) + EdgeMatrix.GetWorkBufferSize((int)mixesCount), 0x10); + } + + // Memory Pool + size = WorkBufferAllocator.GetTargetSize(size, memoryPoolCount, MemoryPoolState.Alignment); + + // Splitter + size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter); + + if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled() && + parameter.SplitterCount > 0 && + parameter.SplitterDestinationCount > 0) + { + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10); + } + + // DSP Voice + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceUpdateState.Align); + + // Performance + if (parameter.PerformanceMetricFramesCount > 0) + { + ulong performanceMetricsPerFramesSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC; + + size += BitUtils.AlignUp(performanceMetricsPerFramesSize, Constants.PerformanceMetricsPerFramesSizeAlignment); + } + + return BitUtils.AlignUp(size, Constants.WorkBufferAlignment); + } + + public ResultCode QuerySystemEvent(out IWritableEvent systemEvent) + { + systemEvent = default; + + if (_executionMode == AudioRendererExecutionMode.Manual) + { + return ResultCode.UnsupportedOperation; + } + + systemEvent = _systemEvent; + + return ResultCode.Success; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (_isActive) + { + Stop(); + } + + PoolMapper mapper = new(_processHandle, false); + mapper.Unmap(ref _dspMemoryPoolState); + + PoolMapper.ClearUsageState(_memoryPools); + + for (int i = 0; i < _memoryPoolCount; i++) + { + ref MemoryPoolState memoryPool = ref _memoryPools.Span[i]; + + if (memoryPool.IsMapped()) + { + mapper.Unmap(ref memoryPool); + } + } + + _manager.Unregister(this); + _workBufferMemoryPin.Dispose(); + + if (MemoryManager is IRefCounted rc) + { + rc.DecrementReferenceCount(); + + MemoryManager = null; + } + } + } + + public void SetVoiceDropParameter(float voiceDropParameter) + { + _voiceDropParameter = Math.Clamp(voiceDropParameter, 0.0f, 2.0f); + } + + public float GetVoiceDropParameter() + { + return _voiceDropParameter; + } + + public ResultCode ExecuteAudioRendererRendering() + { + if (_executionMode == AudioRendererExecutionMode.Manual && _renderingDevice == AudioRendererRenderingDevice.Cpu) + { + // NOTE: Here Nintendo aborts with this error code, we don't want that. + return ResultCode.InvalidExecutionContextOperation; + } + + return ResultCode.UnsupportedOperation; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs new file mode 100644 index 00000000..e334a89f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs @@ -0,0 +1,391 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Dsp; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// The audio renderer manager. + /// + public class AudioRendererManager : IDisposable + { + /// + /// Lock used for session allocation. + /// + private readonly object _sessionLock = new(); + + /// + /// Lock used to control the running state. + /// + private readonly object _audioProcessorLock = new(); + + /// + /// The session ids allocation table. + /// + private readonly int[] _sessionIds; + + /// + /// The events linked to each session. + /// + private IWritableEvent[] _sessionsSystemEvent; + + /// + /// The sessions instances. + /// + private readonly AudioRenderSystem[] _sessions; + + /// + /// The count of active sessions. + /// + private int _activeSessionCount; + + /// + /// The worker thread used to run . + /// + private Thread _workerThread; + + /// + /// Indicate if the worker thread and are running. + /// + private bool _isRunning; + + /// + /// The audio device driver to create audio outputs. + /// + private IHardwareDeviceDriver _deviceDriver; + + /// + /// Tick source used to measure elapsed time. + /// + public ITickSource TickSource { get; } + + /// + /// The instance associated to this manager. + /// + public AudioProcessor Processor { get; } + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + /// Tick source used to measure elapsed time. + public AudioRendererManager(ITickSource tickSource) + { + Processor = new AudioProcessor(); + TickSource = tickSource; + _sessionIds = new int[Constants.AudioRendererSessionCountMax]; + _sessions = new AudioRenderSystem[Constants.AudioRendererSessionCountMax]; + _activeSessionCount = 0; + + for (int i = 0; i < _sessionIds.Length; i++) + { + _sessionIds[i] = i; + } + } + + /// + /// Initialize the . + /// + /// The events associated to each session. + /// The device driver to use to create audio outputs. + public void Initialize(IWritableEvent[] sessionSystemEvents, IHardwareDeviceDriver deviceDriver) + { + _sessionsSystemEvent = sessionSystemEvents; + _deviceDriver = deviceDriver; + } + + /// + /// Get the work buffer size required by a session. + /// + /// The user configuration + /// The work buffer size required by a session. + public static ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter) + { + return AudioRenderSystem.GetWorkBufferSize(ref parameter); + } + + /// + /// Acquire a new session id. + /// + /// A new session id. + private int AcquireSessionId() + { + lock (_sessionLock) + { + int index = _activeSessionCount; + + Debug.Assert(index < _sessionIds.Length); + + int sessionId = _sessionIds[index]; + + _sessionIds[index] = -1; + + _activeSessionCount++; + + Logger.Info?.Print(LogClass.AudioRenderer, $"Registered new renderer ({sessionId})"); + + return sessionId; + } + } + + /// + /// Release a given . + /// + /// The session id to release. + private void ReleaseSessionId(int sessionId) + { + lock (_sessionLock) + { + Debug.Assert(_activeSessionCount > 0); + + int newIndex = --_activeSessionCount; + + _sessionIds[newIndex] = sessionId; + } + + Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered renderer ({sessionId})"); + } + + /// + /// Check if there is any audio renderer active. + /// + /// Returns true if there is any audio renderer active. + private bool HasAnyActiveRendererLocked() + { + foreach (AudioRenderSystem renderer in _sessions) + { + if (renderer != null) + { + return true; + } + } + + return false; + } + + /// + /// Start the and worker thread. + /// + private void StartLocked() + { + _isRunning = true; + + // TODO: virtual device mapping (IAudioDevice) + Processor.Start(_deviceDriver); + + _workerThread = new Thread(SendCommands) + { + Name = "AudioRendererManager.Worker", + }; + + _workerThread.Start(); + } + + /// + /// Stop the and worker thread. + /// + private void StopLocked() + { + _isRunning = false; + + _workerThread.Join(); + Processor.Stop(); + + Logger.Info?.Print(LogClass.AudioRenderer, "Stopped audio renderer"); + } + + /// + /// Stop sending commands to the without stopping the worker thread. + /// + public void StopSendingCommands() + { + lock (_sessionLock) + { + foreach (AudioRenderSystem renderer in _sessions) + { + renderer?.Disable(); + } + } + + lock (_audioProcessorLock) + { + if (_isRunning) + { + StopLocked(); + } + } + } + + /// + /// Worker main function. This is used to dispatch audio renderer commands to the . + /// + private void SendCommands() + { + Logger.Info?.Print(LogClass.AudioRenderer, "Starting audio renderer"); + Processor.Wait(); + + while (_isRunning) + { + lock (_sessionLock) + { + foreach (AudioRenderSystem renderer in _sessions) + { + renderer?.SendCommands(); + } + } + + Processor.Signal(); + Processor.Wait(); + } + } + + /// + /// Register a new . + /// + /// The to register. + private void Register(AudioRenderSystem renderer) + { + lock (_sessionLock) + { + _sessions[renderer.GetSessionId()] = renderer; + } + + lock (_audioProcessorLock) + { + if (!_isRunning) + { + StartLocked(); + } + } + } + + /// + /// Unregister a new . + /// + /// The to unregister. + internal void Unregister(AudioRenderSystem renderer) + { + lock (_sessionLock) + { + int sessionId = renderer.GetSessionId(); + + _sessions[renderer.GetSessionId()] = null; + + ReleaseSessionId(sessionId); + } + + lock (_audioProcessorLock) + { + if (_isRunning && !HasAnyActiveRendererLocked()) + { + StopLocked(); + } + } + } + + /// + /// Open a new + /// + /// The new + /// The memory manager that will be used for all guest memory operations. + /// The user configuration + /// The applet resource user id of the application. + /// The guest work buffer address. + /// The guest work buffer size. + /// The process handle of the application. + /// A reporting an error or a success. + public ResultCode OpenAudioRenderer( + out AudioRenderSystem renderer, + IVirtualMemoryManager memoryManager, + ref AudioRendererConfiguration parameter, + ulong appletResourceUserId, + ulong workBufferAddress, + ulong workBufferSize, + uint processHandle) + { + int sessionId = AcquireSessionId(); + + AudioRenderSystem audioRenderer = new(this, _sessionsSystemEvent[sessionId]); + + // TODO: Eventually, we should try to use the guest supplied work buffer instead of allocating + // our own. However, it was causing problems on some applications that would unmap the memory + // before the audio renderer was fully disposed. + Memory workBufferMemory = GC.AllocateArray((int)workBufferSize, pinned: true); + + ResultCode result = audioRenderer.Initialize( + ref parameter, + processHandle, + workBufferMemory, + workBufferAddress, + workBufferSize, + sessionId, + appletResourceUserId, + memoryManager); + + if (result == ResultCode.Success) + { + renderer = audioRenderer; + + Register(renderer); + } + else + { + ReleaseSessionId(sessionId); + + renderer = null; + } + + return result; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Clone the sessions array to dispose them outside the lock. + AudioRenderSystem[] sessions; + + lock (_sessionLock) + { + sessions = _sessions.ToArray(); + } + + foreach (AudioRenderSystem renderer in sessions) + { + renderer?.Dispose(); + } + + lock (_audioProcessorLock) + { + if (_isRunning) + { + StopLocked(); + } + } + + Processor.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs new file mode 100644 index 00000000..32c7de6c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs @@ -0,0 +1,469 @@ +using System; +using System.Buffers; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// Behaviour context. + /// + /// This handles features based on the audio renderer revision provided by the user. + public class BehaviourContext + { + /// + /// The base magic of the Audio Renderer revision. + /// + public const int BaseRevisionMagic = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('0' << 24); + + /// + /// REV1: first revision. + /// + public const int Revision1 = 1 << 24; + + /// + /// REV2: Added support for splitter and fix GC-ADPCM context not being provided to the DSP. + /// + /// This was added in system update 2.0.0 + public const int Revision2 = 2 << 24; + + /// + /// REV3: Incremented the max pre-delay from 150 to 350 for the reverb command and removed the (unused) codec system. + /// + /// This was added in system update 3.0.0 + public const int Revision3 = 3 << 24; + + /// + /// REV4: Added USB audio device support and incremented the rendering limit percent to 75%. + /// + /// This was added in system update 4.0.0 + public const int Revision4 = 4 << 24; + + /// + /// REV5: , were added to voice. + /// A new performance frame format (version 2) was added with support for more information about DSP timing. + /// was added to supply the count of update done sent to the DSP. + /// A new version of the command estimator was added to address timing changes caused by the voice changes. + /// Additionally, the rendering limit percent was incremented to 80%. + /// + /// This was added in system update 6.0.0 + public const int Revision5 = 5 << 24; + + /// + /// REV6: This fixed a bug in the biquad filter command not clearing up with usage state. + /// + /// This was added in system update 6.1.0 + public const int Revision6 = 6 << 24; + + /// + /// REV7: Client side (finally) doesn't send all the mix client state to the server and can do partial updates. + /// + /// This was added in system update 8.0.0 + public const int Revision7 = 7 << 24; + + /// + /// REV8: + /// Wavebuffer was changed to support more control over loop (you can now specify where to start and end a loop, and how many times to loop). + /// was added (see for more info). + /// Final leftovers of the codec system were removed. + /// support was added. + /// A new version of the command estimator was added to address timing changes caused by the voice and command changes. + /// + /// This was added in system update 9.0.0 + public const int Revision8 = 8 << 24; + + /// + /// REV9: + /// EffectInfo parameters were revisited with a new revision (version 2) allowing more data control between the client and server. + /// A new effect was added: Limiter. This effect is effectively implemented with a DRC while providing statistics on the processing on . + /// + /// This was added in system update 12.0.0 + public const int Revision9 = 9 << 24; + + /// + /// REV10: + /// Added Bluetooth audio device support and removed the unused "GetAudioSystemMasterVolumeSetting" audio device API. + /// A new effect was added: Capture. This effect allows the client side to capture audio buffers of a mix. + /// A new command was added for double biquad filters on voices. This is implemented using a direct form 1 (instead of the usual direct form 2). + /// A new version of the command estimator was added to support the new commands. + /// + /// This was added in system update 13.0.0 + public const int Revision10 = 10 << 24; + + /// + /// REV11: + /// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer. + /// A new effect was added: Compressor. This effect is effectively implemented with a DRC. + /// A new version of the command estimator was added to address timing changes caused by the legacy effects changes. + /// A voice drop parameter was added in 15.0.0: This allows an application to amplify or attenuate the estimated time of DSP commands. + /// + /// This was added in system update 14.0.0 but some changes were made in 15.0.0 + public const int Revision11 = 11 << 24; + + /// + /// REV12: + /// Two new commands were added to for biquad filtering and mixing (with optinal volume ramp) on the same command. + /// Splitter destinations can now specify up to two biquad filtering parameters, used for filtering the buffer before mixing. + /// + /// This was added in system update 17.0.0 + public const int Revision12 = 12 << 24; + + /// + /// Last revision supported by the implementation. + /// + public const int LastRevision = Revision12; + + /// + /// Target revision magic supported by the implementation. + /// + public const int ProcessRevision = BaseRevisionMagic + LastRevision; + + /// + /// Get the revision number from the revision magic. + /// + /// The revision magic. + /// The revision number. + public static int GetRevisionNumber(int revision) => (revision - BaseRevisionMagic) >> 24; + + /// + /// Current active revision. + /// + public int UserRevision { get; private set; } + + /// + /// Error storage. + /// + private readonly ErrorInfo[] _errorInfos; + + /// + /// Current position in the array. + /// + private uint _errorIndex; + + /// + /// Current flags of the . + /// + private ulong _flags; + + /// + /// Create a new instance of . + /// + public BehaviourContext() + { + UserRevision = 0; + _errorInfos = new ErrorInfo[Constants.MaxErrorInfos]; + _errorIndex = 0; + } + + /// + /// Set the active revision. + /// + /// The active revision. + public void SetUserRevision(int userRevision) + { + UserRevision = userRevision; + } + + /// + /// Update flags of the . + /// + /// The new flags. + public void UpdateFlags(ulong flags) + { + _flags = flags; + } + + /// + /// Check if a given revision is valid/supported. + /// + /// The revision magic to check. + /// Returns true if the given revision is valid/supported + public static bool CheckValidRevision(int revision) + { + return GetRevisionNumber(revision) <= GetRevisionNumber(ProcessRevision); + } + + /// + /// Check if the given revision is greater than or equal the supported revision. + /// + /// The revision magic to check. + /// The revision magic of the supported revision. + /// Returns true if the given revision is greater than or equal the supported revision. + public static bool CheckFeatureSupported(int revision, int supportedRevision) + { + int revA = GetRevisionNumber(revision); + int revB = GetRevisionNumber(supportedRevision); + + if (revA > LastRevision) + { + revA = 1; + } + + if (revB > LastRevision) + { + revB = 1; + } + + return revA >= revB; + } + + /// + /// Check if the memory pool mapping bypass flag is active. + /// + /// True if the memory pool mapping bypass flag is active. + public bool IsMemoryPoolForceMappingEnabled() + { + return (_flags & 1) != 0; + } + + /// + /// Check if the audio renderer should fix the GC-ADPCM context not being provided to the DSP. + /// + /// True if the audio renderer should fix it. + public bool IsAdpcmLoopContextBugFixed() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2); + } + + /// + /// Check if the audio renderer should accept splitters. + /// + /// True if the audio renderer should accept splitters. + public bool IsSplitterSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2); + } + + /// + /// Check if the audio renderer should use a max pre-delay of 350 instead of 150. + /// + /// True if the max pre-delay must be 350. + public bool IsLongSizePreDelaySupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision3); + } + + /// + /// Check if the audio renderer should expose USB audio device. + /// + /// True if the audio renderer should expose USB audio device. + public bool IsAudioUsbDeviceOutputSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision4); + } + + /// + /// Get the percentage allocated to the audio renderer on the DSP for processing. + /// + /// The percentage allocated to the audio renderer on the DSP for processing. + public float GetAudioRendererProcessingTimeLimit() + { + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5)) + { + return 0.80f; + } + + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision4)) + { + return 0.75f; + } + + return 0.70f; + } + + /// + /// Check if the audio render should support voice flushing. + /// + /// True if the audio render should support voice flushing. + public bool IsFlushVoiceWaveBuffersSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Check if the audio renderer should trust the user destination count in . + /// + /// True if the audio renderer should trust the user destination count. + public bool IsSplitterBugFixed() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Check if the audio renderer should supply the elapsed frame count to the user when updating. + /// + /// True if the audio renderer should supply the elapsed frame count to the user when updating. + public bool IsElapsedFrameCountSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Get the performance metric data format version. + /// + /// The performance metric data format version. + public uint GetPerformanceMetricsDataFormat() + { + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5)) + { + return 2; + } + + return 1; + } + + /// + /// Check if the audio renderer should support . + /// + /// True if the audio renderer should support . + public bool IsDecodingBehaviourFlagSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Check if the audio renderer should fix the biquad filter command not clearing up with usage state. + /// + /// True if the biquad filter state should be cleared. + public bool IsBiquadFilterEffectStateClearBugFixed() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision6); + } + + /// + /// Check if the audio renderer should accept partial mix updates. + /// + /// True if the audio renderer should accept partial mix updates. + public bool IsMixInParameterDirtyOnlyUpdateSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision7); + } + + /// + /// Check if the audio renderer should use the new wavebuffer format. + /// + /// True if the audio renderer should use the new wavebuffer format. + public bool IsWaveBufferVersion2Supported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8); + } + + /// + /// Check if the audio renderer should use the new effect info format. + /// + /// True if the audio renderer should use the new effect info format. + public bool IsEffectInfoVersion2Supported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision9); + } + + /// + /// Check if the audio renderer should use an optimized Biquad Filter (Direct Form 1) in case of two biquad filters are defined on a voice. + /// + /// True if the audio renderer should use the optimization. + public bool UseMultiTapBiquadFilterProcessing() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10); + } + + /// + /// Check if the audio renderer should support new channel resource mapping for 5.1 on Delay, Reverb and Reverb 3D effects. + /// + /// True if the audio renderer support new channel resource mapping for 5.1. + public bool IsNewEffectChannelMappingSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11); + } + + /// + /// Check if the audio renderer should support biquad filter on splitter. + /// + /// True if the audio renderer support biquad filter on splitter + public bool IsBiquadFilterParameterForSplitterEnabled() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12); + } + + /// + /// Get the version of the . + /// + /// The version of the . + public int GetCommandProcessingTimeEstimatorVersion() + { + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11)) + { + return 5; + } + + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10)) + { + return 4; + } + + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8)) + { + return 3; + } + + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5)) + { + return 2; + } + + return 1; + } + + /// + /// Append a new to the error array. + /// + /// The new to add. + public void AppendError(ref ErrorInfo errorInfo) + { + Debug.Assert(errorInfo.ErrorCode == ResultCode.Success); + + if (_errorIndex <= Constants.MaxErrorInfos - 1) + { + _errorInfos[_errorIndex++] = errorInfo; + } + } + + /// + /// Copy the internal array to the given and output the count copied. + /// + /// The output . + /// The output error count containing the count of copied. + public void CopyErrorInfo(Span errorInfos, out uint errorCount) + { + if (errorInfos.Length != Constants.MaxErrorInfos) + { + throw new ArgumentException("Invalid size of errorInfos span!"); + } + + errorCount = Math.Min(_errorIndex, Constants.MaxErrorInfos); + + for (int i = 0; i < Constants.MaxErrorInfos; i++) + { + if (i < errorCount) + { + errorInfos[i] = _errorInfos[i]; + } + else + { + errorInfos[i] = new ErrorInfo + { + ErrorCode = 0, + ExtraErrorInfo = 0, + }; + } + } + } + + /// + /// Clear the array. + /// + public void ClearError() + { + _errorIndex = 0; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs new file mode 100644 index 00000000..702f0546 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs @@ -0,0 +1,682 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Performance; +using Ryujinx.Audio.Renderer.Server.Sink; +using Ryujinx.Audio.Renderer.Server.Upsampler; +using Ryujinx.Audio.Renderer.Server.Voice; +using System; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// An API to generate commands and aggregate them into a . + /// + public class CommandBuffer + { + /// + /// The command processing time estimator in use. + /// + private readonly ICommandProcessingTimeEstimator _commandProcessingTimeEstimator; + + /// + /// The estimated total processing time. + /// + public uint EstimatedProcessingTime { get; set; } + + /// + /// The command list that is populated by the . + /// + public CommandList CommandList { get; } + + /// + /// Create a new . + /// + /// The command list that will store the generated commands. + /// The command processing time estimator to use. + public CommandBuffer(CommandList commandList, ICommandProcessingTimeEstimator commandProcessingTimeEstimator) + { + CommandList = commandList; + EstimatedProcessingTime = 0; + _commandProcessingTimeEstimator = commandProcessingTimeEstimator; + } + + /// + /// Add a new generated command to the . + /// + /// The command to add. + private void AddCommand(ICommand command) + { + EstimatedProcessingTime += command.EstimatedProcessingTime; + + CommandList.AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The node id associated to this command. + public void GenerateClearMixBuffer(int nodeId) + { + ClearMixBufferCommand command = new(nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The voice state associated. + /// The depop buffer. + /// The buffer count. + /// The target buffer offset. + /// The node id associated to this command. + /// Set to true if the voice was playing previously. + public void GenerateDepopPrepare(Memory state, Memory depopBuffer, uint bufferCount, uint bufferOffset, int nodeId, bool wasPlaying) + { + DepopPrepareCommand command = new(state, depopBuffer, bufferCount, bufferOffset, nodeId, wasPlaying); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The . + /// The performance operation to perform. + /// The node id associated to this command. + public void GeneratePerformance(ref PerformanceEntryAddresses performanceEntryAddresses, PerformanceCommand.Type type, int nodeId) + { + PerformanceCommand command = new(ref performanceEntryAddresses, type, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The previous volume. + /// The new volume. + /// The index of the mix buffer to use. + /// The node id associated to this command. + public void GenerateVolumeRamp(float previousVolume, float volume, uint bufferIndex, int nodeId) + { + VolumeRampCommand command = new(previousVolume, volume, bufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GenerateDataSourceVersion2(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + DataSourceVersion2Command command = new(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GeneratePcmInt16DataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + PcmInt16DataSourceCommandVersion1 command = new(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GeneratePcmFloatDataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + PcmFloatDataSourceCommandVersion1 command = new(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The node id associated to this command. + public void GenerateAdpcmDataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, int nodeId) + { + AdpcmDataSourceCommandVersion1 command = new(ref voiceState, state, outputBufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The base index of the input and output buffer. + /// The biquad filter parameter. + /// The biquad state. + /// The input buffer offset. + /// The output buffer offset. + /// Set to true if the biquad filter state needs to be initialized. + /// The node id associated to this command. + public void GenerateBiquadFilter(int baseIndex, ref BiquadFilterParameter filter, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId) + { + BiquadFilterCommand command = new(baseIndex, ref filter, biquadFilterStateMemory, inputBufferOffset, outputBufferOffset, needInitialization, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The base index of the input and output buffer. + /// The biquad filter parameters. + /// The biquad states. + /// The input buffer offset. + /// The output buffer offset. + /// Set to true if the biquad filter state is initialized. + /// The node id associated to this command. + public void GenerateMultiTapBiquadFilter(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId) + { + MultiTapBiquadFilterCommand command = new(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The mix buffer count. + /// The base input index. + /// The base output index. + /// The previous volume. + /// The new volume. + /// The to generate the command from. + /// The node id associated to this command. + public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, ReadOnlySpan previousVolume, ReadOnlySpan volume, Memory state, int nodeId) + { + MixRampGroupedCommand command = new(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The previous volume. + /// The new volume. + /// The input buffer index. + /// The output buffer index. + /// The index in the array to store the ramped sample. + /// The to generate the command from. + /// The node id associated to this command. + public void GenerateMixRamp(float previousVolume, float volume, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory state, int nodeId) + { + MixRampCommand command = new(previousVolume, volume, inputBufferIndex, outputBufferIndex, lastSampleIndex, state, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The previous volume. + /// The new volume. + /// The input buffer index. + /// The output buffer index. + /// The index in the array to store the ramped sample. + /// The to generate the command from. + /// The biquad filter parameter. + /// The biquad state. + /// The previous biquad state. + /// Set to true if the biquad filter state needs to be initialized. + /// Set to true if the mix has volume ramp, and should be taken into account. + /// Set to true if the buffer is the first mix buffer. + /// The node id associated to this command. + public void GenerateBiquadFilterAndMix( + float previousVolume, + float volume, + uint inputBufferIndex, + uint outputBufferIndex, + int lastSampleIndex, + Memory state, + ref BiquadFilterParameter filter, + Memory biquadFilterState, + Memory previousBiquadFilterState, + bool needInitialization, + bool hasVolumeRamp, + bool isFirstMixBuffer, + int nodeId) + { + BiquadFilterAndMixCommand command = new( + previousVolume, + volume, + inputBufferIndex, + outputBufferIndex, + lastSampleIndex, + state, + ref filter, + biquadFilterState, + previousBiquadFilterState, + needInitialization, + hasVolumeRamp, + isFirstMixBuffer, + nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The previous volume. + /// The new volume. + /// The input buffer index. + /// The output buffer index. + /// The index in the array to store the ramped sample. + /// The to generate the command from. + /// First biquad filter parameter. + /// Second biquad filter parameter. + /// First biquad state. + /// Second biquad state. + /// First previous biquad state. + /// Second previous biquad state. + /// Set to true if the first biquad filter state needs to be initialized. + /// Set to true if the second biquad filter state needs to be initialized. + /// Set to true if the mix has volume ramp, and should be taken into account. + /// Set to true if the buffer is the first mix buffer. + /// The node id associated to this command. + public void GenerateMultiTapBiquadFilterAndMix( + float previousVolume, + float volume, + uint inputBufferIndex, + uint outputBufferIndex, + int lastSampleIndex, + Memory state, + ref BiquadFilterParameter filter0, + ref BiquadFilterParameter filter1, + Memory biquadFilterState0, + Memory biquadFilterState1, + Memory previousBiquadFilterState0, + Memory previousBiquadFilterState1, + bool needInitialization0, + bool needInitialization1, + bool hasVolumeRamp, + bool isFirstMixBuffer, + int nodeId) + { + MultiTapBiquadFilterAndMixCommand command = new( + previousVolume, + volume, + inputBufferIndex, + outputBufferIndex, + lastSampleIndex, + state, + ref filter0, + ref filter1, + biquadFilterState0, + biquadFilterState1, + previousBiquadFilterState0, + previousBiquadFilterState1, + needInitialization0, + needInitialization1, + hasVolumeRamp, + isFirstMixBuffer, + nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The depop buffer. + /// The target buffer offset. + /// The buffer count. + /// The node id associated to this command. + /// The target sample rate in use. + public void GenerateDepopForMixBuffers(Memory depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate) + { + DepopForMixBuffersCommand command = new(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The input buffer index. + /// The output buffer index. + /// The node id associated to this command. + public void GenerateCopyMixBuffer(uint inputBufferIndex, uint outputBufferIndex, int nodeId) + { + CopyMixBufferCommand command = new(inputBufferIndex, outputBufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The input buffer index. + /// The output buffer index. + /// The node id associated to this command. + /// The mix volume. + public void GenerateMix(uint inputBufferIndex, uint outputBufferIndex, int nodeId, float volume) + { + MixCommand command = new(inputBufferIndex, outputBufferIndex, nodeId, volume); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The reverb parameter. + /// The reverb state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + /// If set to true, the long size pre-delay is supported. + /// If set to true, the new effect channel mapping for 5.1 is supported. + public void GenerateReverbEffect(uint bufferOffset, ReverbParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported) + { + if (parameter.IsChannelCountValid()) + { + ReverbCommand command = new(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, isLongSizePreDelaySupported, newEffectChannelMappingSupported); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The reverb 3d parameter. + /// The reverb 3d state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + /// If set to true, the new effect channel mapping for 5.1 is supported. + public void GenerateReverb3dEffect(uint bufferOffset, Reverb3dParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool newEffectChannelMappingSupported) + { + if (parameter.IsChannelCountValid()) + { + Reverb3dCommand command = new(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, newEffectChannelMappingSupported); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The delay parameter. + /// The delay state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + /// If set to true, the new effect channel mapping for 5.1 is supported. + public void GenerateDelayEffect(uint bufferOffset, DelayParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool newEffectChannelMappingSupported) + { + if (parameter.IsChannelCountValid()) + { + DelayCommand command = new(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, newEffectChannelMappingSupported); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The limiter parameter. + /// The limiter state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + public void GenerateLimiterEffectVersion1(uint bufferOffset, LimiterParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + LimiterCommandVersion1 command = new(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The limiter parameter. + /// The limiter state. + /// The DSP effect result state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + public void GenerateLimiterEffectVersion2(uint bufferOffset, LimiterParameter parameter, Memory state, Memory effectResultState, bool isEnabled, ulong workBuffer, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + LimiterCommandVersion2 command = new(bufferOffset, parameter, state, effectResultState, isEnabled, workBuffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The input buffer offset. + /// The output buffer offset. + /// The aux state. + /// Set to true if the effect should be active. + /// The limit of the circular buffer. + /// The guest address of the output buffer. + /// The guest address of the input buffer. + /// The count to add on the offset after write/read operations. + /// The write offset. + /// The node id associated to this command. + public void GenerateAuxEffect(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset, ref AuxiliaryBufferAddresses state, bool isEnabled, uint countMax, CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId) + { + if (state.SendBufferInfoBase != 0 && state.ReturnBufferInfoBase != 0) + { + AuxiliaryBufferCommand command = new(bufferOffset, inputBufferOffset, outputBufferOffset, ref state, isEnabled, countMax, outputBuffer, inputBuffer, updateCount, writeOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The input buffer offset. + /// The capture state. + /// Set to true if the effect should be active. + /// The limit of the circular buffer. + /// The guest address of the output buffer. + /// The count to add on the offset after write operations. + /// The write offset. + /// The node id associated to this command. + public void GenerateCaptureEffect(uint bufferOffset, byte inputBufferOffset, ulong sendBufferInfo, bool isEnabled, uint countMax, CpuAddress outputBuffer, uint updateCount, uint writeOffset, int nodeId) + { + if (sendBufferInfo != 0) + { + CaptureBufferCommand command = new(bufferOffset, inputBufferOffset, sendBufferInfo, isEnabled, countMax, outputBuffer, updateCount, writeOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory state, bool isEnabled, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + CompressorCommand command = new(bufferOffset, parameter, state, isEnabled, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target volume to apply. + /// The offset of the mix buffer. + /// The node id associated to this command. + public void GenerateVolume(float volume, uint bufferOffset, int nodeId) + { + VolumeCommand command = new(volume, bufferOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The of the circular buffer. + /// The node id associated to this command. + public void GenerateCircularBuffer(uint bufferOffset, CircularBufferSink sink, int nodeId) + { + CircularBufferSinkCommand command = new(bufferOffset, ref sink.Parameter, ref sink.CircularBufferAddressInfo, sink.CurrentWriteOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The input buffer offset. + /// The output buffer offset. + /// The downmixer parameters to use. + /// The node id associated to this command. + public void GenerateDownMixSurroundToStereo(uint bufferOffset, Span inputBufferOffset, Span outputBufferOffset, float[] downMixParameter, int nodeId) + { + DownMixSurroundToStereoCommand command = new(bufferOffset, inputBufferOffset, outputBufferOffset, downMixParameter, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The associated. + /// The total input count. + /// The input buffer mix offset. + /// The buffer count per sample. + /// The source sample count. + /// The source sample rate. + /// The node id associated to this command. + public void GenerateUpsample(uint bufferOffset, UpsamplerState upsampler, uint inputCount, Span inputBufferOffset, uint bufferCountPerSample, uint sampleCount, uint sampleRate, int nodeId) + { + UpsampleCommand command = new(bufferOffset, upsampler, inputCount, inputBufferOffset, bufferCountPerSample, sampleCount, sampleRate, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The of the device sink. + /// The current audio renderer session id. + /// The mix buffer in use. + /// The node id associated to this command. + public void GenerateDeviceSink(uint bufferOffset, DeviceSink sink, int sessionId, Memory buffer, int nodeId) + { + DeviceSinkCommand command = new(bufferOffset, sink, sessionId, buffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs new file mode 100644 index 00000000..d798230c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs @@ -0,0 +1,1262 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Server.Mix; +using Ryujinx.Audio.Renderer.Server.Performance; +using Ryujinx.Audio.Renderer.Server.Sink; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Audio.Renderer.Server.Voice; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Server +{ + public class CommandGenerator + { + private readonly CommandBuffer _commandBuffer; + private readonly RendererSystemContext _rendererContext; + private readonly VoiceContext _voiceContext; + private readonly MixContext _mixContext; + private readonly EffectContext _effectContext; + private readonly SinkContext _sinkContext; + private readonly SplitterContext _splitterContext; + private readonly PerformanceManager _performanceManager; + + public CommandGenerator(CommandBuffer commandBuffer, RendererSystemContext rendererContext, VoiceContext voiceContext, MixContext mixContext, EffectContext effectContext, SinkContext sinkContext, SplitterContext splitterContext, PerformanceManager performanceManager) + { + _commandBuffer = commandBuffer; + _rendererContext = rendererContext; + _voiceContext = voiceContext; + _mixContext = mixContext; + _effectContext = effectContext; + _sinkContext = sinkContext; + _splitterContext = splitterContext; + _performanceManager = performanceManager; + + _commandBuffer.GenerateClearMixBuffer(Constants.InvalidNodeId); + } + + private void GenerateDataSource(ref VoiceState voiceState, Memory dspState, int channelIndex) + { + if (voiceState.MixId != Constants.UnusedMixId) + { + ref MixState mix = ref _mixContext.GetState(voiceState.MixId); + + _commandBuffer.GenerateDepopPrepare( + dspState, + _rendererContext.DepopBuffer, + mix.BufferCount, + mix.BufferOffset, + voiceState.NodeId, + voiceState.WasPlaying); + } + else if (voiceState.SplitterId != Constants.UnusedSplitterId) + { + int destinationId = 0; + + while (true) + { + SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++); + + if (destination.IsNull) + { + break; + } + + if (destination.IsConfigured()) + { + int mixId = destination.DestinationId; + + if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt) + { + ref MixState mix = ref _mixContext.GetState(mixId); + + _commandBuffer.GenerateDepopPrepare( + dspState, + _rendererContext.DepopBuffer, + mix.BufferCount, + mix.BufferOffset, + voiceState.NodeId, + voiceState.WasPlaying); + + destination.MarkAsNeedToUpdateInternalState(); + } + } + } + } + + if (!voiceState.WasPlaying) + { + Debug.Assert(voiceState.SampleFormat != SampleFormat.Adpcm || channelIndex == 0); + + if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported()) + { + _commandBuffer.GenerateDataSourceVersion2( + ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); + } + else + { + switch (voiceState.SampleFormat) + { + case SampleFormat.PcmInt16: + _commandBuffer.GeneratePcmInt16DataSourceVersion1( + ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); + break; + case SampleFormat.PcmFloat: + _commandBuffer.GeneratePcmFloatDataSourceVersion1( + ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); + break; + case SampleFormat.Adpcm: + _commandBuffer.GenerateAdpcmDataSourceVersion1( + ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + voiceState.NodeId); + break; + default: + throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}"); + } + } + } + } + + private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory state, int baseIndex, int bufferOffset, int nodeId) + { + bool supportsOptimizedPath = _rendererContext.BehaviourContext.UseMultiTapBiquadFilterProcessing(); + + if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable) + { + Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(Unsafe.SizeOf() * Constants.VoiceBiquadFilterCount)]; + Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory); + + _commandBuffer.GenerateMultiTapBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId); + } + else + { + for (int i = 0; i < voiceState.BiquadFilters.Length; i++) + { + ref BiquadFilterParameter filter = ref voiceState.BiquadFilters[i]; + + if (filter.Enable) + { + Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(Unsafe.SizeOf() * Constants.VoiceBiquadFilterCount)]; + Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory); + + _commandBuffer.GenerateBiquadFilter( + baseIndex, + ref filter, + stateMemory.Slice(i, 1), + bufferOffset, + bufferOffset, + !voiceState.BiquadFilterNeedInitialization[i], + nodeId); + } + } + } + } + + private void GenerateVoiceMixWithSplitter( + SplitterDestination destination, + Memory state, + uint bufferOffset, + uint bufferCount, + uint bufferIndex, + int nodeId) + { + ReadOnlySpan mixVolumes = destination.MixBufferVolume; + ReadOnlySpan previousMixVolumes = destination.PreviousMixBufferVolume; + + ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0); + ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1); + + Memory bqfState = _splitterContext.GetBiquadFilterState(destination); + + bool isFirstMixBuffer = true; + + for (int i = 0; i < bufferCount; i++) + { + float previousMixVolume = previousMixVolumes[i]; + float mixVolume = mixVolumes[i]; + + if (mixVolume != 0.0f || previousMixVolume != 0.0f) + { + if (bqf0.Enable && bqf1.Enable) + { + _commandBuffer.GenerateMultiTapBiquadFilterAndMix( + previousMixVolume, + mixVolume, + bufferIndex, + bufferOffset + (uint)i, + i, + state, + ref bqf0, + ref bqf1, + bqfState[..1], + bqfState.Slice(1, 1), + bqfState.Slice(2, 1), + bqfState.Slice(3, 1), + !destination.IsBiquadFilterEnabledPrev(), + !destination.IsBiquadFilterEnabledPrev(), + true, + isFirstMixBuffer, + nodeId); + + destination.UpdateBiquadFilterEnabledPrev(0); + destination.UpdateBiquadFilterEnabledPrev(1); + } + else if (bqf0.Enable) + { + _commandBuffer.GenerateBiquadFilterAndMix( + previousMixVolume, + mixVolume, + bufferIndex, + bufferOffset + (uint)i, + i, + state, + ref bqf0, + bqfState[..1], + bqfState.Slice(1, 1), + !destination.IsBiquadFilterEnabledPrev(), + true, + isFirstMixBuffer, + nodeId); + + destination.UpdateBiquadFilterEnabledPrev(0); + } + else if (bqf1.Enable) + { + _commandBuffer.GenerateBiquadFilterAndMix( + previousMixVolume, + mixVolume, + bufferIndex, + bufferOffset + (uint)i, + i, + state, + ref bqf1, + bqfState[..1], + bqfState.Slice(1, 1), + !destination.IsBiquadFilterEnabledPrev(), + true, + isFirstMixBuffer, + nodeId); + + destination.UpdateBiquadFilterEnabledPrev(1); + } + + isFirstMixBuffer = false; + } + } + } + + private void GenerateVoiceMix( + ReadOnlySpan mixVolumes, + ReadOnlySpan previousMixVolumes, + Memory state, + uint bufferOffset, + uint bufferCount, + uint bufferIndex, + int nodeId) + { + if (bufferCount > Constants.VoiceChannelCountMax) + { + _commandBuffer.GenerateMixRampGrouped( + bufferCount, + bufferIndex, + bufferOffset, + previousMixVolumes, + mixVolumes, + state, + nodeId); + } + else + { + for (int i = 0; i < bufferCount; i++) + { + float previousMixVolume = previousMixVolumes[i]; + float mixVolume = mixVolumes[i]; + + if (mixVolume != 0.0f || previousMixVolume != 0.0f) + { + _commandBuffer.GenerateMixRamp( + previousMixVolume, + mixVolume, + bufferIndex, + bufferOffset + (uint)i, + i, + state, + nodeId); + } + } + } + } + + private void GenerateVoice(ref VoiceState voiceState) + { + int nodeId = voiceState.NodeId; + uint channelsCount = voiceState.ChannelsCount; + + for (int channelIndex = 0; channelIndex < channelsCount; channelIndex++) + { + Memory dspStateMemory = _voiceContext.GetUpdateStateForDsp(voiceState.ChannelResourceIds[channelIndex]); + + ref VoiceChannelResource channelResource = ref _voiceContext.GetChannelResource(voiceState.ChannelResourceIds[channelIndex]); + + PerformanceDetailType dataSourceDetailType = PerformanceDetailType.Adpcm; + + if (voiceState.SampleFormat == SampleFormat.PcmInt16) + { + dataSourceDetailType = PerformanceDetailType.PcmInt16; + } + else if (voiceState.SampleFormat == SampleFormat.PcmFloat) + { + dataSourceDetailType = PerformanceDetailType.PcmFloat; + } + + bool performanceInitialized = false; + + PerformanceEntryAddresses performanceEntry = new(); + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, dataSourceDetailType, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateDataSource(ref voiceState, dspStateMemory, channelIndex); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + if (voiceState.WasPlaying) + { + voiceState.PreviousVolume = 0.0f; + } + else if (voiceState.HasAnyDestination()) + { + performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.BiquadFilter, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateBiquadFilterForVoice(ref voiceState, dspStateMemory, (int)_rendererContext.MixBufferCount, channelIndex, nodeId); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.VolumeRamp, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + _commandBuffer.GenerateVolumeRamp( + voiceState.PreviousVolume, + voiceState.Volume, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + voiceState.PreviousVolume = voiceState.Volume; + + if (voiceState.MixId == Constants.UnusedMixId) + { + if (voiceState.SplitterId != Constants.UnusedSplitterId) + { + int destinationId = channelIndex; + + while (true) + { + SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId); + + if (destination.IsNull) + { + break; + } + + destinationId += (int)channelsCount; + + if (destination.IsConfigured()) + { + int mixId = destination.DestinationId; + + if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt) + { + ref MixState mix = ref _mixContext.GetState(mixId); + + if (destination.IsBiquadFilterEnabled()) + { + GenerateVoiceMixWithSplitter( + destination, + dspStateMemory, + mix.BufferOffset, + mix.BufferCount, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + } + else + { + GenerateVoiceMix( + destination.MixBufferVolume, + destination.PreviousMixBufferVolume, + dspStateMemory, + mix.BufferOffset, + mix.BufferCount, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + } + + destination.MarkAsNeedToUpdateInternalState(); + } + } + } + } + } + else + { + ref MixState mix = ref _mixContext.GetState(voiceState.MixId); + + performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateVoiceMix( + channelResource.Mix.AsSpan(), + channelResource.PreviousMix.AsSpan(), + dspStateMemory, + mix.BufferOffset, + mix.BufferCount, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + channelResource.UpdateState(); + } + + for (int i = 0; i < voiceState.BiquadFilterNeedInitialization.Length; i++) + { + voiceState.BiquadFilterNeedInitialization[i] = voiceState.BiquadFilters[i].Enable; + } + } + } + } + + public void GenerateVoices() + { + for (int i = 0; i < _voiceContext.GetCount(); i++) + { + ref VoiceState sortedState = ref _voiceContext.GetSortedState(i); + + if (!sortedState.ShouldSkip() && sortedState.UpdateForCommandGeneration(_voiceContext)) + { + int nodeId = sortedState.NodeId; + + PerformanceEntryAddresses performanceEntry = new(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateVoice(ref sortedState); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + } + + _splitterContext.UpdateInternalState(); + } + + public void GeneratePerformance(ref PerformanceEntryAddresses performanceEntryAddresses, PerformanceCommand.Type type, int nodeId) + { + _commandBuffer.GeneratePerformance(ref performanceEntryAddresses, type, nodeId); + } + + private void GenerateBufferMixerEffect(int bufferOffset, BufferMixEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.BufferMix); + + if (effect.IsEnabled) + { + for (int i = 0; i < effect.Parameter.MixesCount; i++) + { + if (effect.Parameter.Volumes[i] != 0.0f) + { + _commandBuffer.GenerateMix( + (uint)bufferOffset + effect.Parameter.Input[i], + (uint)bufferOffset + effect.Parameter.Output[i], + nodeId, + effect.Parameter.Volumes[i]); + } + } + } + } + + private void GenerateAuxEffect(uint bufferOffset, AuxiliaryBufferEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.AuxiliaryBuffer); + + if (effect.IsEnabled) + { + effect.GetWorkBuffer(0); + effect.GetWorkBuffer(1); + } + + if (effect.State.SendBufferInfoBase != 0 && effect.State.ReturnBufferInfoBase != 0) + { + int i = 0; + uint writeOffset = 0; + for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--) + { + uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount; + + uint updateCount; + + if (channelIndex != 1) + { + updateCount = 0; + } + else + { + updateCount = newUpdateCount; + } + + _commandBuffer.GenerateAuxEffect( + bufferOffset, + effect.Parameter.Input[i], + effect.Parameter.Output[i], + ref effect.State, + effect.IsEnabled, + effect.Parameter.BufferStorageSize, + effect.State.SendBufferInfoBase, + effect.State.ReturnBufferInfoBase, + updateCount, + writeOffset, + nodeId); + + writeOffset = newUpdateCount; + + i++; + } + } + } + + private void GenerateDelayEffect(uint bufferOffset, DelayEffect effect, int nodeId, bool newEffectChannelMappingSupported) + { + Debug.Assert(effect.Type == EffectType.Delay); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + _commandBuffer.GenerateDelayEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, newEffectChannelMappingSupported); + } + + private void GenerateReverbEffect(uint bufferOffset, ReverbEffect effect, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported) + { + Debug.Assert(effect.Type == EffectType.Reverb); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + _commandBuffer.GenerateReverbEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, isLongSizePreDelaySupported, newEffectChannelMappingSupported); + } + + private void GenerateReverb3dEffect(uint bufferOffset, Reverb3dEffect effect, int nodeId, bool newEffectChannelMappingSupported) + { + Debug.Assert(effect.Type == EffectType.Reverb3d); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + _commandBuffer.GenerateReverb3dEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, newEffectChannelMappingSupported); + } + + private void GenerateBiquadFilterEffect(uint bufferOffset, BiquadFilterEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.BiquadFilter); + + if (effect.IsEnabled) + { + bool needInitialization = effect.Parameter.Status == UsageState.Invalid || + (effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + + BiquadFilterParameter parameter = new() + { + Enable = true, + }; + + effect.Parameter.Denominator.AsSpan().CopyTo(parameter.Denominator.AsSpan()); + effect.Parameter.Numerator.AsSpan().CopyTo(parameter.Numerator.AsSpan()); + + for (int i = 0; i < effect.Parameter.ChannelCount; i++) + { + _commandBuffer.GenerateBiquadFilter( + (int)bufferOffset, + ref parameter, + effect.State.Slice(i, 1), + effect.Parameter.Input[i], + effect.Parameter.Output[i], + needInitialization, + nodeId); + } + } + else + { + for (int i = 0; i < effect.Parameter.ChannelCount; i++) + { + uint inputBufferIndex = bufferOffset + effect.Parameter.Input[i]; + uint outputBufferIndex = bufferOffset + effect.Parameter.Output[i]; + + // If the input and output isn't the same, generate a command. + if (inputBufferIndex != outputBufferIndex) + { + _commandBuffer.GenerateCopyMixBuffer(inputBufferIndex, outputBufferIndex, nodeId); + } + } + } + } + + private void GenerateLimiterEffect(uint bufferOffset, LimiterEffect effect, int nodeId, int effectId) + { + Debug.Assert(effect.Type == EffectType.Limiter); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + if (_rendererContext.BehaviourContext.IsEffectInfoVersion2Supported()) + { + Memory dspResultState; + + if (effect.Parameter.StatisticsEnabled) + { + dspResultState = _effectContext.GetDspStateMemory(effectId); + } + else + { + dspResultState = Memory.Empty; + } + + _commandBuffer.GenerateLimiterEffectVersion2(bufferOffset, effect.Parameter, effect.State, dspResultState, effect.IsEnabled, workBuffer, nodeId); + } + else + { + _commandBuffer.GenerateLimiterEffectVersion1(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId); + } + } + + private void GenerateCaptureEffect(uint bufferOffset, CaptureBufferEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.CaptureBuffer); + + if (effect.IsEnabled) + { + effect.GetWorkBuffer(0); + } + + if (effect.State.SendBufferInfoBase != 0) + { + int i = 0; + uint writeOffset = 0; + + for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--) + { + uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount; + + uint updateCount; + + if (channelIndex != 1) + { + updateCount = 0; + } + else + { + updateCount = newUpdateCount; + } + + _commandBuffer.GenerateCaptureEffect( + bufferOffset, + effect.Parameter.Input[i], + effect.State.SendBufferInfo, + effect.IsEnabled, + effect.Parameter.BufferStorageSize, + effect.State.SendBufferInfoBase, + updateCount, + writeOffset, + nodeId); + + writeOffset = newUpdateCount; + + i++; + } + } + } + + private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.Compressor); + + _commandBuffer.GenerateCompressorEffect( + bufferOffset, + effect.Parameter, + effect.State, + effect.IsEnabled, + nodeId); + } + + private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect) + { + int nodeId = mix.NodeId; + + bool isFinalMix = mix.MixId == Constants.FinalMixId; + + PerformanceEntryAddresses performanceEntry = new(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry( + out performanceEntry, + effect.GetPerformanceDetailType(), + isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix, + nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + switch (effect.Type) + { + case EffectType.BufferMix: + GenerateBufferMixerEffect((int)mix.BufferOffset, (BufferMixEffect)effect, nodeId); + break; + case EffectType.AuxiliaryBuffer: + GenerateAuxEffect(mix.BufferOffset, (AuxiliaryBufferEffect)effect, nodeId); + break; + case EffectType.Delay: + GenerateDelayEffect(mix.BufferOffset, (DelayEffect)effect, nodeId, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported()); + break; + case EffectType.Reverb: + GenerateReverbEffect(mix.BufferOffset, (ReverbEffect)effect, nodeId, mix.IsLongSizePreDelaySupported, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported()); + break; + case EffectType.Reverb3d: + GenerateReverb3dEffect(mix.BufferOffset, (Reverb3dEffect)effect, nodeId, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported()); + break; + case EffectType.BiquadFilter: + GenerateBiquadFilterEffect(mix.BufferOffset, (BiquadFilterEffect)effect, nodeId); + break; + case EffectType.Limiter: + GenerateLimiterEffect(mix.BufferOffset, (LimiterEffect)effect, nodeId, effectId); + break; + case EffectType.CaptureBuffer: + GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId); + break; + case EffectType.Compressor: + GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId); + break; + default: + throw new NotImplementedException($"Unsupported effect type {effect.Type}"); + } + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + effect.UpdateForCommandGeneration(); + } + + private void GenerateEffects(ref MixState mix) + { + ReadOnlySpan effectProcessingOrderArray = mix.EffectProcessingOrderArray; + + Debug.Assert(_effectContext.GetCount() == 0 || !effectProcessingOrderArray.IsEmpty); + + for (int i = 0; i < _effectContext.GetCount(); i++) + { + int effectOrder = effectProcessingOrderArray[i]; + + if (effectOrder == Constants.InvalidProcessingOrder) + { + break; + } + + // BaseEffect is a class, we don't need to pass it by ref + BaseEffect effect = _effectContext.GetEffect(effectOrder); + + Debug.Assert(effect.Type != EffectType.Invalid); + Debug.Assert(effect.MixId == mix.MixId); + + if (!effect.ShouldSkip()) + { + GenerateEffect(ref mix, effectOrder, effect); + } + } + } + + private void GenerateMixWithSplitter( + uint inputBufferIndex, + uint outputBufferIndex, + float volume, + SplitterDestination destination, + ref bool isFirstMixBuffer, + int nodeId) + { + ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0); + ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1); + + Memory bqfState = _splitterContext.GetBiquadFilterState(destination); + + if (bqf0.Enable && bqf1.Enable) + { + _commandBuffer.GenerateMultiTapBiquadFilterAndMix( + 0f, + volume, + inputBufferIndex, + outputBufferIndex, + 0, + Memory.Empty, + ref bqf0, + ref bqf1, + bqfState[..1], + bqfState.Slice(1, 1), + bqfState.Slice(2, 1), + bqfState.Slice(3, 1), + !destination.IsBiquadFilterEnabledPrev(), + !destination.IsBiquadFilterEnabledPrev(), + false, + isFirstMixBuffer, + nodeId); + + destination.UpdateBiquadFilterEnabledPrev(0); + destination.UpdateBiquadFilterEnabledPrev(1); + } + else if (bqf0.Enable) + { + _commandBuffer.GenerateBiquadFilterAndMix( + 0f, + volume, + inputBufferIndex, + outputBufferIndex, + 0, + Memory.Empty, + ref bqf0, + bqfState[..1], + bqfState.Slice(1, 1), + !destination.IsBiquadFilterEnabledPrev(), + false, + isFirstMixBuffer, + nodeId); + + destination.UpdateBiquadFilterEnabledPrev(0); + } + else if (bqf1.Enable) + { + _commandBuffer.GenerateBiquadFilterAndMix( + 0f, + volume, + inputBufferIndex, + outputBufferIndex, + 0, + Memory.Empty, + ref bqf1, + bqfState[..1], + bqfState.Slice(1, 1), + !destination.IsBiquadFilterEnabledPrev(), + false, + isFirstMixBuffer, + nodeId); + + destination.UpdateBiquadFilterEnabledPrev(1); + } + + isFirstMixBuffer = false; + } + + private void GenerateMix(ref MixState mix) + { + if (mix.HasAnyDestination()) + { + Debug.Assert(mix.DestinationMixId != Constants.UnusedMixId || mix.DestinationSplitterId != Constants.UnusedSplitterId); + + if (mix.DestinationMixId == Constants.UnusedMixId) + { + if (mix.DestinationSplitterId != Constants.UnusedSplitterId) + { + int destinationId = 0; + + while (true) + { + int destinationIndex = destinationId++; + + SplitterDestination destination = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex); + + if (destination.IsNull) + { + break; + } + + if (destination.IsConfigured()) + { + int mixId = destination.DestinationId; + + if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt) + { + ref MixState destinationMix = ref _mixContext.GetState(mixId); + + uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount); + + bool isFirstMixBuffer = true; + + for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++) + { + float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex); + + if (volume != 0.0f) + { + if (destination.IsBiquadFilterEnabled()) + { + GenerateMixWithSplitter( + inputBufferIndex, + destinationMix.BufferOffset + bufferDestinationIndex, + volume, + destination, + ref isFirstMixBuffer, + mix.NodeId); + } + else + { + _commandBuffer.GenerateMix( + inputBufferIndex, + destinationMix.BufferOffset + bufferDestinationIndex, + mix.NodeId, + volume); + } + } + } + } + } + } + } + } + else + { + ref MixState destinationMix = ref _mixContext.GetState(mix.DestinationMixId); + + for (uint bufferIndex = 0; bufferIndex < mix.BufferCount; bufferIndex++) + { + for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++) + { + float volume = mix.Volume * mix.GetMixBufferVolume((int)bufferIndex, (int)bufferDestinationIndex); + + if (volume != 0.0f) + { + _commandBuffer.GenerateMix( + mix.BufferOffset + bufferIndex, + destinationMix.BufferOffset + bufferDestinationIndex, + mix.NodeId, + volume); + } + } + } + } + } + } + + private void GenerateSubMix(ref MixState subMix) + { + _commandBuffer.GenerateDepopForMixBuffers( + _rendererContext.DepopBuffer, + subMix.BufferOffset, + subMix.BufferCount, + subMix.NodeId, + subMix.SampleRate); + + GenerateEffects(ref subMix); + + PerformanceEntryAddresses performanceEntry = new(); + + int nodeId = subMix.NodeId; + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.SubMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateMix(ref subMix); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + + public void GenerateSubMixes() + { + for (int id = 0; id < _mixContext.GetCount(); id++) + { + ref MixState sortedState = ref _mixContext.GetSortedState(id); + + if (sortedState.IsUsed && sortedState.MixId != Constants.FinalMixId) + { + int nodeId = sortedState.NodeId; + + PerformanceEntryAddresses performanceEntry = new(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.SubMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateSubMix(ref sortedState); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + } + } + + private void GenerateFinalMix() + { + ref MixState finalMix = ref _mixContext.GetFinalState(); + + _commandBuffer.GenerateDepopForMixBuffers( + _rendererContext.DepopBuffer, + finalMix.BufferOffset, + finalMix.BufferCount, + finalMix.NodeId, + finalMix.SampleRate); + + GenerateEffects(ref finalMix); + + PerformanceEntryAddresses performanceEntry = new(); + + int nodeId = finalMix.NodeId; + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.FinalMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + // Only generate volume command if the volume isn't 100%. + if (finalMix.Volume != 1.0f) + { + for (uint bufferIndex = 0; bufferIndex < finalMix.BufferCount; bufferIndex++) + { + bool performanceSubInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.VolumeRamp, PerformanceEntryType.FinalMix, nodeId)) + { + performanceSubInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + _commandBuffer.GenerateVolume( + finalMix.Volume, + finalMix.BufferOffset + bufferIndex, + nodeId); + + if (performanceSubInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + } + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + + public void GenerateFinalMixes() + { + int nodeId = _mixContext.GetFinalState().NodeId; + + PerformanceEntryAddresses performanceEntry = new(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.FinalMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateFinalMix(); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + + private void GenerateCircularBuffer(CircularBufferSink sink, ref MixState finalMix) + { + _commandBuffer.GenerateCircularBuffer(finalMix.BufferOffset, sink, Constants.InvalidNodeId); + } + + private void GenerateDevice(DeviceSink sink, ref MixState finalMix) + { + if (_commandBuffer.CommandList.SampleRate != 48000 && sink.UpsamplerState == null) + { + sink.UpsamplerState = _rendererContext.UpsamplerManager.Allocate(); + } + + bool useCustomDownMixingCommand = _rendererContext.ChannelCount == 2 && sink.Parameter.DownMixParameterEnabled; + + if (useCustomDownMixingCommand) + { + _commandBuffer.GenerateDownMixSurroundToStereo( + finalMix.BufferOffset, + sink.Parameter.Input.AsSpan(), + sink.Parameter.Input.AsSpan(), + sink.DownMixCoefficients, + Constants.InvalidNodeId); + } + // NOTE: We do the downmixing at the DSP level as it's easier that way. + else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6) + { + _commandBuffer.GenerateDownMixSurroundToStereo( + finalMix.BufferOffset, + sink.Parameter.Input.AsSpan(), + sink.Parameter.Input.AsSpan(), + Constants.DefaultSurroundToStereoCoefficients, + Constants.InvalidNodeId); + } + + CommandList commandList = _commandBuffer.CommandList; + + if (sink.UpsamplerState != null) + { + _commandBuffer.GenerateUpsample( + finalMix.BufferOffset, + sink.UpsamplerState, + sink.Parameter.InputCount, + sink.Parameter.Input.AsSpan(), + commandList.BufferCount, + commandList.SampleCount, + commandList.SampleRate, + Constants.InvalidNodeId); + } + + _commandBuffer.GenerateDeviceSink( + finalMix.BufferOffset, + sink, + _rendererContext.SessionId, + commandList.Buffers, + Constants.InvalidNodeId); + } + + private void GenerateSink(BaseSink sink, ref MixState finalMix) + { + bool performanceInitialized = false; + + PerformanceEntryAddresses performanceEntry = new(); + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.Sink, sink.NodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, sink.NodeId); + } + + if (!sink.ShouldSkip) + { + switch (sink.Type) + { + case SinkType.CircularBuffer: + GenerateCircularBuffer((CircularBufferSink)sink, ref finalMix); + break; + case SinkType.Device: + GenerateDevice((DeviceSink)sink, ref finalMix); + break; + default: + throw new NotImplementedException($"Unsupported sink type {sink.Type}"); + } + + sink.UpdateForCommandGeneration(); + } + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, sink.NodeId); + } + } + + public void GenerateSinks() + { + ref MixState finalMix = ref _mixContext.GetFinalState(); + + for (int i = 0; i < _sinkContext.GetCount(); i++) + { + // BaseSink is a class, we don't need to pass it by ref + BaseSink sink = _sinkContext.GetSink(i); + + if (sink.IsUsed) + { + GenerateSink(sink, ref finalMix); + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs new file mode 100644 index 00000000..cff754b8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs @@ -0,0 +1,198 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 1. + /// + public class CommandProcessingTimeEstimatorVersion1 : ICommandProcessingTimeEstimator + { + private readonly uint _sampleCount; + private readonly uint _bufferCount; + + public CommandProcessingTimeEstimatorVersion1(uint sampleCount, uint bufferCount) + { + _sampleCount = sampleCount; + _bufferCount = bufferCount; + } + + public uint Estimate(PerformanceCommand command) + { + return 1454; + } + + public uint Estimate(ClearMixBufferCommand command) + { + return (uint)(_sampleCount * 0.83f * _bufferCount * 1.2f); + } + + public uint Estimate(BiquadFilterCommand command) + { + return (uint)(_sampleCount * 58.0f * 1.2f); + } + + public uint Estimate(MixRampGroupedCommand command) + { + int volumeCount = 0; + + for (int i = 0; i < command.MixBufferCount; i++) + { + if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f) + { + volumeCount++; + } + } + + return (uint)(_sampleCount * 14.4f * 1.2f * volumeCount); + } + + public uint Estimate(MixRampCommand command) + { + return (uint)(_sampleCount * 14.4f * 1.2f); + } + + public uint Estimate(DepopPrepareCommand command) + { + return 1080; + } + + public uint Estimate(VolumeRampCommand command) + { + return (uint)(_sampleCount * 9.8f * 1.2f); + } + + public uint Estimate(PcmInt16DataSourceCommandVersion1 command) + { + return (uint)(command.Pitch * 0.25f * 1.2f); + } + + public uint Estimate(AdpcmDataSourceCommandVersion1 command) + { + return (uint)(command.Pitch * 0.46f * 1.2f); + } + + public uint Estimate(DepopForMixBuffersCommand command) + { + return (uint)(_sampleCount * 8.9f * command.MixBufferCount); + } + + public uint Estimate(CopyMixBufferCommand command) + { + // NOTE: Nintendo returns 0 here for some reasons even if it will generate a command like that on version 1.. maybe a mistake? + return 0; + } + + public uint Estimate(MixCommand command) + { + return (uint)(_sampleCount * 10.0f * 1.2f); + } + + public uint Estimate(DelayCommand command) + { + return (uint)(_sampleCount * command.Parameter.ChannelCount * 202.5f); + } + + public uint Estimate(ReverbCommand command) + { + Debug.Assert(command.Parameter.IsChannelCountValid()); + + if (command.Enabled) + { + return (uint)(750 * _sampleCount * command.Parameter.ChannelCount * 1.2f); + } + + return 0; + } + + public uint Estimate(Reverb3dCommand command) + { + if (command.Enabled) + { + return (uint)(530 * _sampleCount * command.Parameter.ChannelCount * 1.2f); + } + + return 0; + } + + public uint Estimate(AuxiliaryBufferCommand command) + { + if (command.Enabled) + { + return 15956; + } + + return 3765; + } + + public uint Estimate(VolumeCommand command) + { + return (uint)(_sampleCount * 8.8f * 1.2f); + } + + public uint Estimate(CircularBufferSinkCommand command) + { + return 55; + } + + public uint Estimate(DownMixSurroundToStereoCommand command) + { + return 16108; + } + + public uint Estimate(UpsampleCommand command) + { + return 357915; + } + + public uint Estimate(DeviceSinkCommand command) + { + return 10042; + } + + public uint Estimate(PcmFloatDataSourceCommandVersion1 command) + { + return 0; + } + + public uint Estimate(DataSourceVersion2Command command) + { + return 0; + } + + public uint Estimate(LimiterCommandVersion1 command) + { + return 0; + } + + public uint Estimate(LimiterCommandVersion2 command) + { + return 0; + } + + public uint Estimate(MultiTapBiquadFilterCommand command) + { + return 0; + } + + public uint Estimate(CaptureBufferCommand command) + { + return 0; + } + + public uint Estimate(CompressorCommand command) + { + return 0; + } + + public uint Estimate(BiquadFilterAndMixCommand command) + { + return 0; + } + + public uint Estimate(MultiTapBiquadFilterAndMixCommand command) + { + return 0; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs new file mode 100644 index 00000000..ef132692 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs @@ -0,0 +1,490 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 2. (added with REV5) + /// + public class CommandProcessingTimeEstimatorVersion2 : ICommandProcessingTimeEstimator + { + private readonly uint _sampleCount; + private readonly uint _bufferCount; + + public CommandProcessingTimeEstimatorVersion2(uint sampleCount, uint bufferCount) + { + _sampleCount = sampleCount; + _bufferCount = bufferCount; + } + + public uint Estimate(PerformanceCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)489.35f; + } + + return (uint)491.18f; + } + + public uint Estimate(ClearMixBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerBuffer = 668.8f; + float baseCost = 193.2f; + + if (_sampleCount == 160) + { + costPerBuffer = 260.4f; + baseCost = 139.65f; + } + + return (uint)(baseCost + costPerBuffer * _bufferCount); + } + + public uint Estimate(BiquadFilterCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)4813.2f; + } + + return (uint)6915.4f; + } + + public uint Estimate(MixRampGroupedCommand command) + { + const float CostPerSample = 7.245f; + + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + int volumeCount = 0; + + for (int i = 0; i < command.MixBufferCount; i++) + { + if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f) + { + volumeCount++; + } + } + + return (uint)(_sampleCount * CostPerSample * volumeCount); + } + + public uint Estimate(MixRampCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1859.0f; + } + + return (uint)2286.1f; + } + + public uint Estimate(DepopPrepareCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)306.62f; + } + + return (uint)293.22f; + } + + public uint Estimate(VolumeRampCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1403.9f; + } + + return (uint)1884.3f; + } + + public uint Estimate(PcmInt16DataSourceCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 1195.5f; + float baseCost = 7797.0f; + + if (_sampleCount == 160) + { + costPerSample = 749.27f; + baseCost = 6138.9f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(AdpcmDataSourceCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 3564.1f; + float baseCost = 6225.5f; + + if (_sampleCount == 160) + { + costPerSample = 2125.6f; + baseCost = 9039.5f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DepopForMixBuffersCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)762.96f; + } + + return (uint)726.96f; + } + + public uint Estimate(CopyMixBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)836.32f; + } + + return (uint)1000.9f; + } + + public uint Estimate(MixCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1342.2f; + } + + return (uint)1833.2f; + } + + public uint Estimate(DelayCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)41636.0f, + 2 => (uint)97861.0f, + 4 => (uint)192520.0f, + 6 => (uint)301760.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)578.53f, + 2 => (uint)663.06f, + 4 => (uint)703.98f, + 6 => (uint)760.03f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)8770.3f, + 2 => (uint)25741.0f, + 4 => (uint)47551.0f, + 6 => (uint)81629.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)521.28f, + 2 => (uint)585.4f, + 4 => (uint)629.88f, + 6 => (uint)713.57f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public uint Estimate(ReverbCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)97192.0f, + 2 => (uint)103280.0f, + 4 => (uint)109580.0f, + 6 => (uint)115070.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)492.01f, + 2 => (uint)554.46f, + 4 => (uint)595.86f, + 6 => (uint)656.62f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)136460.0f, + 2 => (uint)145750.0f, + 4 => (uint)154800.0f, + 6 => (uint)161970.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)495.79f, + 2 => (uint)527.16f, + 4 => (uint)598.75f, + 6 => (uint)666.03f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public uint Estimate(Reverb3dCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)138840.0f, + 2 => (uint)135430.0f, + 4 => (uint)199180.0f, + 6 => (uint)247350.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)718.7f, + 2 => (uint)751.3f, + 4 => (uint)797.46f, + 6 => (uint)867.43f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)199950.0f, + 2 => (uint)195200.0f, + 4 => (uint)290580.0f, + 6 => (uint)363490.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)534.24f, + 2 => (uint)570.87f, + 4 => (uint)660.93f, + 6 => (uint)694.6f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public uint Estimate(AuxiliaryBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + return (uint)7177.9f; + } + + return (uint)489.16f; + } + + if (command.Enabled) + { + return (uint)9499.8f; + } + + return (uint)485.56f; + } + + public uint Estimate(VolumeCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1280.3f; + } + + return (uint)1737.8f; + } + + public uint Estimate(CircularBufferSinkCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerBuffer = 1726.0f; + float baseCost = 1369.7f; + + if (_sampleCount == 160) + { + costPerBuffer = 853.63f; + baseCost = 1284.5f; + } + + return (uint)(baseCost + costPerBuffer * command.InputCount); + } + + public uint Estimate(DownMixSurroundToStereoCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)10009.0f; + } + + return (uint)14577.0f; + } + + public uint Estimate(UpsampleCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)292000.0f; + } + + return (uint)0.0f; + } + + public uint Estimate(DeviceSinkCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + Debug.Assert(command.InputCount == 2 || command.InputCount == 6); + + if (command.InputCount == 2) + { + if (_sampleCount == 160) + { + return (uint)9261.5f; + } + + return (uint)9336.1f; + } + + if (_sampleCount == 160) + { + return (uint)9111.8f; + } + + return (uint)9566.7f; + } + + public uint Estimate(PcmFloatDataSourceCommandVersion1 command) + { + // NOTE: This was added between REV7 and REV8 and for some reasons the estimator v2 was changed... + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 3490.9f; + float baseCost = 10091.0f; + + if (_sampleCount == 160) + { + costPerSample = 2310.4f; + baseCost = 7845.3f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DataSourceVersion2Command command) + { + return 0; + } + + public uint Estimate(LimiterCommandVersion1 command) + { + return 0; + } + + public uint Estimate(LimiterCommandVersion2 command) + { + return 0; + } + + public uint Estimate(MultiTapBiquadFilterCommand command) + { + return 0; + } + + public uint Estimate(CaptureBufferCommand command) + { + return 0; + } + + public uint Estimate(CompressorCommand command) + { + return 0; + } + + public uint Estimate(BiquadFilterAndMixCommand command) + { + return 0; + } + + public uint Estimate(MultiTapBiquadFilterAndMixCommand command) + { + return 0; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs new file mode 100644 index 00000000..31a5347b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs @@ -0,0 +1,660 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 3. (added with REV8) + /// + public class CommandProcessingTimeEstimatorVersion3 : ICommandProcessingTimeEstimator + { + protected uint SampleCount; + protected uint BufferCount; + + public CommandProcessingTimeEstimatorVersion3(uint sampleCount, uint bufferCount) + { + SampleCount = sampleCount; + BufferCount = bufferCount; + } + + public uint Estimate(PerformanceCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)498.17f; + } + + return (uint)489.42f; + } + + public uint Estimate(ClearMixBufferCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + float costPerBuffer = 440.68f; + float baseCost = 0; + + if (SampleCount == 160) + { + costPerBuffer = 266.65f; + } + + return (uint)(baseCost + costPerBuffer * BufferCount); + } + + public uint Estimate(BiquadFilterCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)4173.2f; + } + + return (uint)5585.1f; + } + + public uint Estimate(MixRampGroupedCommand command) + { + float costPerSample = 6.4434f; + + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + costPerSample = 6.708f; + } + + int volumeCount = 0; + + for (int i = 0; i < command.MixBufferCount; i++) + { + if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f) + { + volumeCount++; + } + } + + return (uint)(SampleCount * costPerSample * volumeCount); + } + + public uint Estimate(MixRampCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)1968.7f; + } + + return (uint)2459.4f; + } + + public uint Estimate(DepopPrepareCommand command) + { + return 0; + } + + public uint Estimate(VolumeRampCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)1425.3f; + } + + return (uint)1700.0f; + } + + public uint Estimate(PcmInt16DataSourceCommandVersion1 command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + float costPerSample = 710.143f; + float baseCost = 7853.286f; + + if (SampleCount == 160) + { + costPerSample = 427.52f; + baseCost = 6329.442f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / SampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(AdpcmDataSourceCommandVersion1 command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + float costPerSample = 3564.1f; + float baseCost = 9736.702f; + + if (SampleCount == 160) + { + costPerSample = 2125.6f; + baseCost = 7913.808f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / SampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DepopForMixBuffersCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)739.64f; + } + + return (uint)910.97f; + } + + public uint Estimate(CopyMixBufferCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)842.59f; + } + + return (uint)986.72f; + } + + public uint Estimate(MixCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)1402.8f; + } + + return (uint)1853.2f; + } + + public virtual uint Estimate(DelayCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)8929.04f, + 2 => (uint)25500.75f, + 4 => (uint)47759.62f, + 6 => (uint)82203.07f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)1295.20f, + 2 => (uint)1213.60f, + 4 => (uint)942.03f, + 6 => (uint)1001.55f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)11941.05f, + 2 => (uint)37197.37f, + 4 => (uint)69749.84f, + 6 => (uint)120042.40f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)997.67f, + 2 => (uint)977.63f, + 4 => (uint)792.30f, + 6 => (uint)875.43f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public virtual uint Estimate(ReverbCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)81475.05f, + 2 => (uint)84975.0f, + 4 => (uint)91625.15f, + 6 => (uint)95332.27f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)536.30f, + 2 => (uint)588.70f, + 4 => (uint)643.70f, + 6 => (uint)706.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)120174.47f, + 2 => (uint)25262.22f, + 4 => (uint)135751.23f, + 6 => (uint)141129.23f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)617.64f, + 2 => (uint)659.54f, + 4 => (uint)711.43f, + 6 => (uint)778.07f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public virtual uint Estimate(Reverb3dCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)116754.0f, + 2 => (uint)125912.05f, + 4 => (uint)146336.03f, + 6 => (uint)165812.66f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)734.0f, + 2 => (uint)766.62f, + 4 => (uint)797.46f, + 6 => (uint)867.43f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)170292.34f, + 2 => (uint)183875.63f, + 4 => (uint)214696.19f, + 6 => (uint)243846.77f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)508.47f, + 2 => (uint)582.45f, + 4 => (uint)626.42f, + 6 => (uint)682.47f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public uint Estimate(AuxiliaryBufferCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return (uint)7182.14f; + } + + return (uint)472.11f; + } + + if (command.Enabled) + { + return (uint)9435.96f; + } + + return (uint)462.62f; + } + + public uint Estimate(VolumeCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)1311.1f; + } + + return (uint)1713.6f; + } + + public uint Estimate(CircularBufferSinkCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + float costPerBuffer = 770.26f; + float baseCost = 0f; + + if (SampleCount == 160) + { + costPerBuffer = 531.07f; + } + + return (uint)(baseCost + costPerBuffer * command.InputCount); + } + + public uint Estimate(DownMixSurroundToStereoCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)9949.7f; + } + + return (uint)14679.0f; + } + + public uint Estimate(UpsampleCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)312990.0f; + } + + return (uint)0.0f; + } + + public uint Estimate(DeviceSinkCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + Debug.Assert(command.InputCount == 2 || command.InputCount == 6); + + if (command.InputCount == 2) + { + if (SampleCount == 160) + { + return (uint)8980.0f; + } + + return (uint)9221.9f; + } + + if (SampleCount == 160) + { + return (uint)9177.9f; + } + + return (uint)9725.9f; + } + + public uint Estimate(PcmFloatDataSourceCommandVersion1 command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + float costPerSample = 3490.9f; + float baseCost = 10090.9f; + + if (SampleCount == 160) + { + costPerSample = 2310.4f; + baseCost = 7845.25f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / SampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DataSourceVersion2Command command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + (float baseCost, float costPerSample) = GetCostByFormat(SampleCount, command.SampleFormat, command.SrcQuality); + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / SampleCount) * (command.Pitch * 0.000030518f) - 1.0f))); + } + + private static (float, float) GetCostByFormat(uint sampleCount, SampleFormat format, SampleRateConversionQuality quality) + { + Debug.Assert(sampleCount == 160 || sampleCount == 240); + + switch (format) + { + case SampleFormat.PcmInt16: + switch (quality) + { + case SampleRateConversionQuality.Default: + if (sampleCount == 160) + { + return (6329.44f, 427.52f); + } + + return (7853.28f, 710.14f); + case SampleRateConversionQuality.High: + if (sampleCount == 160) + { + return (8049.42f, 371.88f); + } + + return (10138.84f, 610.49f); + case SampleRateConversionQuality.Low: + if (sampleCount == 160) + { + return (5062.66f, 423.43f); + } + + return (5810.96f, 676.72f); + default: + throw new NotImplementedException($"{format} {quality}"); + } + case SampleFormat.PcmFloat: + switch (quality) + { + case SampleRateConversionQuality.Default: + if (sampleCount == 160) + { + return (7845.25f, 2310.4f); + } + + return (10090.9f, 3490.9f); + case SampleRateConversionQuality.High: + if (sampleCount == 160) + { + return (9446.36f, 2308.91f); + } + + return (12520.85f, 3480.61f); + case SampleRateConversionQuality.Low: + if (sampleCount == 160) + { + return (9446.36f, 2308.91f); + } + + return (12520.85f, 3480.61f); + default: + throw new NotImplementedException($"{format} {quality}"); + } + case SampleFormat.Adpcm: + switch (quality) + { + case SampleRateConversionQuality.Default: + if (sampleCount == 160) + { + return (7913.81f, 1827.66f); + } + + return (9736.70f, 2756.37f); + case SampleRateConversionQuality.High: + if (sampleCount == 160) + { + return (9607.81f, 1829.29f); + } + + return (12154.38f, 2731.31f); + case SampleRateConversionQuality.Low: + if (sampleCount == 160) + { + return (6517.48f, 1824.61f); + } + + return (7929.44f, 2732.15f); + default: + throw new NotImplementedException($"{format} {quality}"); + } + default: + throw new NotImplementedException($"{format}"); + } + } + + private uint EstimateLimiterCommandCommon(LimiterParameter parameter, bool enabled) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (enabled) + { + return parameter.ChannelCount switch + { + 1 => (uint)21392.0f, + 2 => (uint)26829.0f, + 4 => (uint)32405.0f, + 6 => (uint)52219.0f, + _ => throw new NotImplementedException($"{parameter.ChannelCount}"), + }; + } + + return parameter.ChannelCount switch + { + 1 => (uint)897.0f, + 2 => (uint)931.55f, + 4 => (uint)975.39f, + 6 => (uint)1016.8f, + _ => throw new NotImplementedException($"{parameter.ChannelCount}"), + }; + } + + if (enabled) + { + return parameter.ChannelCount switch + { + 1 => (uint)30556.0f, + 2 => (uint)39011.0f, + 4 => (uint)48270.0f, + 6 => (uint)76712.0f, + _ => throw new NotImplementedException($"{parameter.ChannelCount}"), + }; + } + + return parameter.ChannelCount switch + { + 1 => (uint)874.43f, + 2 => (uint)921.55f, + 4 => (uint)945.26f, + 6 => (uint)992.26f, + _ => throw new NotImplementedException($"{parameter.ChannelCount}"), + }; + } + + public uint Estimate(LimiterCommandVersion1 command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + return EstimateLimiterCommandCommon(command.Parameter, command.IsEffectEnabled); + } + + public uint Estimate(LimiterCommandVersion2 command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (!command.Parameter.StatisticsEnabled || !command.IsEffectEnabled) + { + return EstimateLimiterCommandCommon(command.Parameter, command.IsEffectEnabled); + } + + if (SampleCount == 160) + { + return command.Parameter.ChannelCount switch + { + 1 => (uint)23309.0f, + 2 => (uint)29954.0f, + 4 => (uint)35807.0f, + 6 => (uint)58340.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)33526.0f, + 2 => (uint)43549.0f, + 4 => (uint)52190.0f, + 6 => (uint)85527.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public virtual uint Estimate(MultiTapBiquadFilterCommand command) + { + return 0; + } + + public virtual uint Estimate(CaptureBufferCommand command) + { + return 0; + } + + public virtual uint Estimate(CompressorCommand command) + { + return 0; + } + + public virtual uint Estimate(BiquadFilterAndMixCommand command) + { + return 0; + } + + public virtual uint Estimate(MultiTapBiquadFilterAndMixCommand command) + { + return 0; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs new file mode 100644 index 00000000..fb357120 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs @@ -0,0 +1,47 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 4. (added with REV10) + /// + public class CommandProcessingTimeEstimatorVersion4 : CommandProcessingTimeEstimatorVersion3 + { + public CommandProcessingTimeEstimatorVersion4(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { } + + public override uint Estimate(MultiTapBiquadFilterCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + return (uint)7424.5f; + } + + return (uint)9730.4f; + } + + public override uint Estimate(CaptureBufferCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return (uint)435.2f; + } + + return (uint)4261.0f; + } + + if (command.Enabled) + { + return (uint)5858.26f; + } + + return (uint)435.2f; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs new file mode 100644 index 00000000..06f135a8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs @@ -0,0 +1,262 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 5. (added with REV11) + /// + public class CommandProcessingTimeEstimatorVersion5 : CommandProcessingTimeEstimatorVersion4 + { + public CommandProcessingTimeEstimatorVersion5(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { } + + public override uint Estimate(DelayCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 8929, + 2 => 25501, + 4 => 47760, + 6 => 82203, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)1295.20f, + 2 => (uint)1213.60f, + 4 => (uint)942.03f, + 6 => (uint)1001.6f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 11941, + 2 => 37197, + 4 => 69750, + 6 => 12004, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)997.67f, + 2 => (uint)977.63f, + 4 => (uint)792.31f, + 6 => (uint)875.43f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public override uint Estimate(ReverbCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 81475, + 2 => 84975, + 4 => 91625, + 6 => 95332, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)536.30f, + 2 => (uint)588.80f, + 4 => (uint)643.70f, + 6 => (uint)706.0f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 120170, + 2 => 125260, + 4 => 135750, + 6 => 141130, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)617.64f, + 2 => (uint)659.54f, + 4 => (uint)711.44f, + 6 => (uint)778.07f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public override uint Estimate(Reverb3dCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 116750, + 2 => 125910, + 4 => 146340, + 6 => 165810, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => 735, + 2 => (uint)766.62f, + 4 => (uint)834.07f, + 6 => (uint)875.44f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 170290, + 2 => 183880, + 4 => 214700, + 6 => 243850, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)508.47f, + 2 => (uint)582.45f, + 4 => (uint)626.42f, + 6 => (uint)682.47f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public override uint Estimate(CompressorCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (SampleCount == 160) + { + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 34431, + 2 => 44253, + 4 => 63827, + 6 => 83361, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)630.12f, + 2 => (uint)638.27f, + 4 => (uint)705.86f, + 6 => (uint)782.02f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + if (command.Enabled) + { + return command.Parameter.ChannelCount switch + { + 1 => 51095, + 2 => 65693, + 4 => 95383, + 6 => 124510, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + return command.Parameter.ChannelCount switch + { + 1 => (uint)840.14f, + 2 => (uint)826.1f, + 4 => (uint)901.88f, + 6 => (uint)965.29f, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + + public override uint Estimate(BiquadFilterAndMixCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (command.HasVolumeRamp) + { + if (SampleCount == 160) + { + return 5204; + } + + return 6683; + } + else + { + if (SampleCount == 160) + { + return 3427; + } + + return 4752; + } + } + + public override uint Estimate(MultiTapBiquadFilterAndMixCommand command) + { + Debug.Assert(SampleCount == 160 || SampleCount == 240); + + if (command.HasVolumeRamp) + { + if (SampleCount == 160) + { + return 7939; + } + + return 10669; + } + else + { + if (SampleCount == 160) + { + return 6256; + } + + return 8683; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs new file mode 100644 index 00000000..74a9baff --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs @@ -0,0 +1,85 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Dsp.State.AuxiliaryBufferHeader; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for an auxiliary buffer effect. + /// + public class AuxiliaryBufferEffect : BaseEffect + { + /// + /// The auxiliary buffer parameter. + /// + public AuxiliaryBufferParameter Parameter; + + /// + /// Auxiliary buffer state. + /// + public AuxiliaryBufferAddresses State; + + public override EffectType TargetEffectType => EffectType.AuxiliaryBuffer; + + public override DspAddress GetWorkBuffer(int index) + { + return WorkBuffers[index].GetReference(true); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(in parameter)); + + UpdateParameterBase(in parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (BufferUnmapped || parameter.IsNew) + { + ulong bufferSize = (ulong)Unsafe.SizeOf() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf(); + + bool sendBufferUnmapped = !mapper.TryAttachBuffer(out _, ref WorkBuffers[0], Parameter.SendBufferInfoAddress, bufferSize); + bool returnBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[1], Parameter.ReturnBufferInfoAddress, bufferSize); + + BufferUnmapped = sendBufferUnmapped && returnBufferUnmapped; + + if (!BufferUnmapped) + { + DspAddress sendDspAddress = WorkBuffers[0].GetReference(false); + DspAddress returnDspAddress = WorkBuffers[1].GetReference(false); + + State.SendBufferInfo = sendDspAddress + (uint)Unsafe.SizeOf(); + State.SendBufferInfoBase = sendDspAddress + (uint)Unsafe.SizeOf(); + + State.ReturnBufferInfo = returnDspAddress + (uint)Unsafe.SizeOf(); + State.ReturnBufferInfoBase = returnDspAddress + (uint)Unsafe.SizeOf(); + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs new file mode 100644 index 00000000..77d9b5c2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs @@ -0,0 +1,262 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Base class used as a server state for an effect. + /// + public class BaseEffect + { + /// + /// The of the effect. + /// + public EffectType Type; + + /// + /// Set to true if the effect must be active. + /// + public bool IsEnabled; + + /// + /// Set to true if the internal effect work buffers used wasn't mapped. + /// + public bool BufferUnmapped; + + /// + /// The current state of the effect. + /// + public UsageState UsageState; + + /// + /// The target mix id of the effect. + /// + public int MixId; + + /// + /// Position of the effect while processing effects. + /// + public uint ProcessingOrder; + + /// + /// Array of all the work buffer used by the effect. + /// + protected AddressInfo[] WorkBuffers; + + /// + /// Create a new . + /// + public BaseEffect() + { + Type = TargetEffectType; + UsageState = UsageState.Invalid; + + IsEnabled = false; + BufferUnmapped = false; + MixId = Constants.UnusedMixId; + ProcessingOrder = uint.MaxValue; + + WorkBuffers = new AddressInfo[2]; + + foreach (ref AddressInfo info in WorkBuffers.AsSpan()) + { + info = AddressInfo.Create(); + } + } + + /// + /// The target handled by this . + /// + public virtual EffectType TargetEffectType => EffectType.Invalid; + + /// + /// Check if the sent by the user match the internal . + /// + /// The user parameter. + /// Returns true if the sent by the user matches the internal . + public bool IsTypeValid(in T parameter) where T : unmanaged, IEffectInParameter + { + return parameter.Type == TargetEffectType; + } + + /// + /// Update the usage state during command generation. + /// + protected void UpdateUsageStateForCommandGeneration() + { + UsageState = IsEnabled ? UsageState.Enabled : UsageState.Disabled; + } + + /// + /// Update the internal common parameters from a user parameter. + /// + /// The user parameter. + protected void UpdateParameterBase(in T parameter) where T : unmanaged, IEffectInParameter + { + MixId = parameter.MixId; + ProcessingOrder = parameter.ProcessingOrder; + } + + /// + /// Force unmap all the work buffers. + /// + /// The mapper to use. + public void ForceUnmapBuffers(PoolMapper mapper) + { + foreach (ref AddressInfo info in WorkBuffers.AsSpan()) + { + if (info.GetReference(false) != 0) + { + mapper.ForceUnmap(ref info); + } + } + } + + /// + /// Check if the effect needs to be skipped. + /// + /// Returns true if the effect needs to be skipped. + public bool ShouldSkip() + { + return BufferUnmapped; + } + + /// + /// Update the state during command generation. + /// + public virtual void UpdateForCommandGeneration() + { + Debug.Assert(Type == TargetEffectType); + } + + /// + /// Initialize the given result state. + /// + /// The state to initialize + public virtual void InitializeResultState(ref EffectResultState state) { } + + /// + /// Update the result state with . + /// + /// The destination result state + /// The source result state + public virtual void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState) { } + + /// + /// Update the internal state from a user version 1 parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The mapper to use. + public virtual void Update(out ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(in parameter)); + + updateErrorInfo = new ErrorInfo(); + } + + /// + /// Update the internal state from a user version 2 parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The mapper to use. + public virtual void Update(out ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(in parameter)); + + updateErrorInfo = new ErrorInfo(); + } + + /// + /// Get the work buffer DSP address at the given index. + /// + /// The index of the work buffer + /// The work buffer DSP address at the given index. + public virtual DspAddress GetWorkBuffer(int index) + { + throw new InvalidOperationException(); + } + + /// + /// Get the first work buffer DSP address. + /// + /// The first work buffer DSP address. + protected DspAddress GetSingleBuffer() + { + if (IsEnabled) + { + return WorkBuffers[0].GetReference(true); + } + + if (UsageState != UsageState.Disabled) + { + DspAddress address = WorkBuffers[0].GetReference(false); + ulong size = WorkBuffers[0].Size; + + if (address != 0 && size != 0) + { + AudioProcessorMemoryManager.InvalidateDataCache(address, size); + } + } + + return 0; + } + + /// + /// Store the output status to the given user output. + /// + /// The given user output. + /// If set to true, the is active. + public void StoreStatus(ref T outStatus, bool isAudioRendererActive) where T : unmanaged, IEffectOutStatus + { + if (isAudioRendererActive) + { + if (UsageState == UsageState.Disabled) + { + outStatus.State = EffectState.Disabled; + } + else + { + outStatus.State = EffectState.Enabled; + } + } + else if (UsageState == UsageState.New) + { + outStatus.State = EffectState.Enabled; + } + else + { + outStatus.State = EffectState.Disabled; + } + } + + /// + /// Get the associated to the of this effect. + /// + /// The associated to the of this effect. + public PerformanceDetailType GetPerformanceDetailType() + { + return Type switch + { + EffectType.BiquadFilter => PerformanceDetailType.BiquadFilter, + EffectType.AuxiliaryBuffer => PerformanceDetailType.Aux, + EffectType.Delay => PerformanceDetailType.Delay, + EffectType.Reverb => PerformanceDetailType.Reverb, + EffectType.Reverb3d => PerformanceDetailType.Reverb3d, + EffectType.BufferMix => PerformanceDetailType.Mix, + EffectType.Limiter => PerformanceDetailType.Limiter, + EffectType.CaptureBuffer => PerformanceDetailType.CaptureBuffer, + EffectType.Compressor => PerformanceDetailType.Compressor, + _ => throw new NotImplementedException($"{Type}"), + }; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs new file mode 100644 index 00000000..3b3e1021 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs @@ -0,0 +1,67 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a biquad filter effect. + /// + public class BiquadFilterEffect : BaseEffect + { + /// + /// The biquad filter parameter. + /// + public BiquadFilterEffectParameter Parameter; + + /// + /// The biquad filter state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + public BiquadFilterEffect() + { + Parameter = new BiquadFilterEffectParameter(); + State = new BiquadFilterState[Constants.ChannelCountMax]; + } + + public override EffectType TargetEffectType => EffectType.BiquadFilter; + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(in parameter)); + + UpdateParameterBase(in parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs new file mode 100644 index 00000000..5d82b5ae --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs @@ -0,0 +1,49 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a buffer mix effect. + /// + public class BufferMixEffect : BaseEffect + { + /// + /// The buffer mix parameter. + /// + public BufferMixParameter Parameter; + + public override EffectType TargetEffectType => EffectType.BufferMix; + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(in parameter)); + + UpdateParameterBase(in parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs new file mode 100644 index 00000000..6917222f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs @@ -0,0 +1,82 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for an capture buffer effect. + /// + public class CaptureBufferEffect : BaseEffect + { + /// + /// The capture buffer parameter. + /// + public AuxiliaryBufferParameter Parameter; + + /// + /// Capture buffer state. + /// + public AuxiliaryBufferAddresses State; + + public override EffectType TargetEffectType => EffectType.CaptureBuffer; + + public override DspAddress GetWorkBuffer(int index) + { + return WorkBuffers[index].GetReference(true); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(in parameter)); + + UpdateParameterBase(in parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (BufferUnmapped || parameter.IsNew) + { + ulong bufferSize = (ulong)Unsafe.SizeOf() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf(); + + bool sendBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], Parameter.SendBufferInfoAddress, bufferSize); + + BufferUnmapped = sendBufferUnmapped; + + if (!BufferUnmapped) + { + DspAddress sendDspAddress = WorkBuffers[0].GetReference(false); + + // NOTE: Nintendo directly interact with the CPU side structure in the processing of the DSP command. + State.SendBufferInfo = sendDspAddress; + State.SendBufferInfoBase = sendDspAddress + (ulong)Unsafe.SizeOf(); + State.ReturnBufferInfo = 0; + State.ReturnBufferInfoBase = 0; + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs new file mode 100644 index 00000000..eff60e7d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs @@ -0,0 +1,67 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a compressor effect. + /// + public class CompressorEffect : BaseEffect + { + /// + /// The compressor parameter. + /// + public CompressorParameter Parameter; + + /// + /// The compressor state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + public CompressorEffect() + { + State = new CompressorState[1]; + } + + public override EffectType TargetEffectType => EffectType.Compressor; + + public override ulong GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) + { + // Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised. + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(in parameter)); + + UpdateParameterBase(in parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs new file mode 100644 index 00000000..9db1ce46 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs @@ -0,0 +1,93 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a delay effect. + /// + public class DelayEffect : BaseEffect + { + /// + /// The delay parameter. + /// + public DelayParameter Parameter; + + /// + /// The delay state. + /// + public Memory State { get; } + + public DelayEffect() + { + State = new DelayState[1]; + } + + public override EffectType TargetEffectType => EffectType.Delay; + + public override DspAddress GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(in parameter)); + + ref DelayParameter delayParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (delayParameter.IsChannelCountMaxValid()) + { + UpdateParameterBase(in parameter); + + UsageState oldParameterStatus = Parameter.Status; + + Parameter = delayParameter; + + if (delayParameter.IsChannelCountValid()) + { + IsEnabled = parameter.IsEnabled; + + if (oldParameterStatus != UsageState.Enabled) + { + Parameter.Status = oldParameterStatus; + } + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.Status = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs new file mode 100644 index 00000000..619f3110 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs @@ -0,0 +1,123 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Effect context. + /// + public class EffectContext + { + /// + /// Storage for . + /// + private BaseEffect[] _effects; + + /// + /// The total effect count. + /// + private uint _effectCount; + + private EffectResultState[] _resultStatesCpu; + private EffectResultState[] _resultStatesDsp; + + /// + /// Create a new . + /// + public EffectContext() + { + _effects = null; + _effectCount = 0; + } + + /// + /// Initialize the . + /// + /// The total effect count. + /// The total result state count. + public void Initialize(uint effectCount, uint resultStateCount) + { + _effectCount = effectCount; + _effects = new BaseEffect[effectCount]; + + for (int i = 0; i < _effectCount; i++) + { + _effects[i] = new BaseEffect(); + } + + _resultStatesCpu = new EffectResultState[resultStateCount]; + _resultStatesDsp = new EffectResultState[resultStateCount]; + } + + /// + /// Get the total effect count. + /// + /// The total effect count. + public uint GetCount() + { + return _effectCount; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref BaseEffect GetEffect(int index) + { + Debug.Assert(index >= 0 && index < _effectCount); + + return ref _effects[index]; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + /// The returned should only be used when updating the server state. + public ref EffectResultState GetState(int index) + { + Debug.Assert(index >= 0 && index < _resultStatesCpu.Length); + + return ref _resultStatesCpu[index]; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + /// The returned should only be used in the context of processing on the . + public ref EffectResultState GetDspState(int index) + { + Debug.Assert(index >= 0 && index < _resultStatesDsp.Length); + + return ref _resultStatesDsp[index]; + } + + /// + /// Get a memory instance to a at the given . + /// + /// The index to use. + /// A memory instance to a at the given . + /// The returned should only be used in the context of processing on the . + public Memory GetDspStateMemory(int index) + { + return SpanIOHelper.GetMemory(_resultStatesDsp.AsMemory(), index, (uint)_resultStatesDsp.Length); + } + + /// + /// Update internal state during command generation. + /// + public void UpdateResultStateForCommandGeneration() + { + for (int index = 0; index < _resultStatesCpu.Length; index++) + { + _effects[index].UpdateResultState(ref _resultStatesCpu[index], ref _resultStatesDsp[index]); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs new file mode 100644 index 00000000..d9b3d566 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs @@ -0,0 +1,95 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a limiter effect. + /// + public class LimiterEffect : BaseEffect + { + /// + /// The limiter parameter. + /// + public LimiterParameter Parameter; + + /// + /// The limiter state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + public LimiterEffect() + { + State = new LimiterState[1]; + } + + public override EffectType TargetEffectType => EffectType.Limiter; + + public override ulong GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(in parameter)); + + ref LimiterParameter limiterParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + UpdateParameterBase(in parameter); + + Parameter = limiterParameter; + + IsEnabled = parameter.IsEnabled; + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.Status = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + Parameter.StatisticsReset = false; + } + + public override void InitializeResultState(ref EffectResultState state) + { + ref LimiterStatistics statistics = ref MemoryMarshal.Cast(state.SpecificData)[0]; + + statistics.Reset(); + } + + public override void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState) + { + destState = srcState; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs new file mode 100644 index 00000000..4b13cfec --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs @@ -0,0 +1,92 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a 3D reverberation effect. + /// + public class Reverb3dEffect : BaseEffect + { + /// + /// The 3D reverberation parameter. + /// + public Reverb3dParameter Parameter; + + /// + /// The 3D reverberation state. + /// + public Memory State { get; } + + public Reverb3dEffect() + { + State = new Reverb3dState[1]; + } + + public override EffectType TargetEffectType => EffectType.Reverb3d; + + public override ulong GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(in parameter)); + + ref Reverb3dParameter reverbParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (reverbParameter.IsChannelCountMaxValid()) + { + UpdateParameterBase(in parameter); + + UsageState oldParameterStatus = Parameter.ParameterStatus; + + Parameter = reverbParameter; + + if (reverbParameter.IsChannelCountValid()) + { + IsEnabled = parameter.IsEnabled; + + if (oldParameterStatus != UsageState.Enabled) + { + Parameter.ParameterStatus = oldParameterStatus; + } + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.ParameterStatus = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.ParameterStatus = UsageState.Enabled; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs new file mode 100644 index 00000000..aa6e6744 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs @@ -0,0 +1,95 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a reverberation effect. + /// + public class ReverbEffect : BaseEffect + { + /// + /// The reverberation parameter. + /// + public ReverbParameter Parameter; + + /// + /// The reverberation state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + public ReverbEffect() + { + State = new ReverbState[1]; + } + + public override EffectType TargetEffectType => EffectType.Reverb; + + public override ulong GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, in parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(in parameter)); + + ref ReverbParameter reverbParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (reverbParameter.IsChannelCountMaxValid()) + { + UpdateParameterBase(in parameter); + + UsageState oldParameterStatus = Parameter.Status; + + Parameter = reverbParameter; + + if (reverbParameter.IsChannelCountValid()) + { + IsEnabled = parameter.IsEnabled; + + if (oldParameterStatus != UsageState.Enabled) + { + Parameter.Status = oldParameterStatus; + } + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.Status = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs new file mode 100644 index 00000000..da717224 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// The usage state of an effect. + /// + public enum UsageState : byte + { + /// + /// The effect is in an invalid state. + /// + Invalid, + + /// + /// The effect is new. + /// + New, + + /// + /// The effect is enabled. + /// + Enabled, + + /// + /// The effect is disabled. + /// + Disabled, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs b/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs new file mode 100644 index 00000000..9c4312ad --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs @@ -0,0 +1,42 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// Estimate the time that a should take. + /// + /// This is used for voice dropping. + public interface ICommandProcessingTimeEstimator + { + uint Estimate(AuxiliaryBufferCommand command); + uint Estimate(BiquadFilterCommand command); + uint Estimate(ClearMixBufferCommand command); + uint Estimate(DelayCommand command); + uint Estimate(Reverb3dCommand command); + uint Estimate(ReverbCommand command); + uint Estimate(DepopPrepareCommand command); + uint Estimate(DepopForMixBuffersCommand command); + uint Estimate(MixCommand command); + uint Estimate(MixRampCommand command); + uint Estimate(MixRampGroupedCommand command); + uint Estimate(CopyMixBufferCommand command); + uint Estimate(PerformanceCommand command); + uint Estimate(VolumeCommand command); + uint Estimate(VolumeRampCommand command); + uint Estimate(PcmInt16DataSourceCommandVersion1 command); + uint Estimate(PcmFloatDataSourceCommandVersion1 command); + uint Estimate(AdpcmDataSourceCommandVersion1 command); + uint Estimate(DataSourceVersion2Command command); + uint Estimate(CircularBufferSinkCommand command); + uint Estimate(DeviceSinkCommand command); + uint Estimate(DownMixSurroundToStereoCommand command); + uint Estimate(UpsampleCommand command); + uint Estimate(LimiterCommandVersion1 command); + uint Estimate(LimiterCommandVersion2 command); + uint Estimate(MultiTapBiquadFilterCommand command); + uint Estimate(CaptureBufferCommand command); + uint Estimate(CompressorCommand command); + uint Estimate(BiquadFilterAndMixCommand command); + uint Estimate(MultiTapBiquadFilterAndMixCommand command); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs new file mode 100644 index 00000000..a7ec4cf5 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs @@ -0,0 +1,133 @@ +using System; +using System.Runtime.InteropServices; +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.MemoryPool +{ + /// + /// Represents the information of a region shared between the CPU and DSP. + /// + public struct AddressInfo + { + /// + /// The target CPU address of the region. + /// + public CpuAddress CpuAddress; + + /// + /// The size of the region. + /// + public ulong Size; + + private unsafe MemoryPoolState* _memoryPools; + + /// + /// The forced DSP address of the region. + /// + public DspAddress ForceMappedDspAddress; + + private readonly unsafe ref MemoryPoolState MemoryPoolState => ref *_memoryPools; + + public readonly unsafe bool HasMemoryPoolState => (IntPtr)_memoryPools != IntPtr.Zero; + + /// + /// Create an new empty . + /// + /// A new empty . + public static AddressInfo Create() + { + return Create(0, 0); + } + + /// + /// Create a new . + /// + /// The target of the region. + /// The target size of the region. + /// A new . + public static AddressInfo Create(CpuAddress cpuAddress, ulong size) + { + unsafe + { + return new AddressInfo + { + CpuAddress = cpuAddress, + _memoryPools = MemoryPoolState.Null, + Size = size, + ForceMappedDspAddress = 0, + }; + } + } + + /// + /// Setup the CPU address and size of the . + /// + /// The target of the region. + /// The size of the region. + public void Setup(CpuAddress cpuAddress, ulong size) + { + CpuAddress = cpuAddress; + Size = size; + ForceMappedDspAddress = 0; + + unsafe + { + _memoryPools = MemoryPoolState.Null; + } + } + + /// + /// Set the associated. + /// + /// The associated. + public void SetupMemoryPool(Span memoryPoolState) + { + unsafe + { + fixed (MemoryPoolState* ptr = &MemoryMarshal.GetReference(memoryPoolState)) + { + SetupMemoryPool(ptr); + } + } + } + + /// + /// Set the associated. + /// + /// The associated. + public unsafe void SetupMemoryPool(MemoryPoolState* memoryPoolState) + { + _memoryPools = memoryPoolState; + } + + /// + /// Check if the is mapped. + /// + /// Returns true if the is mapped. + public readonly bool HasMappedMemoryPool() + { + return HasMemoryPoolState && MemoryPoolState.IsMapped(); + } + + /// + /// Get the DSP address associated to the . + /// + /// If true, mark the as used. + /// Returns the DSP address associated to the . + public readonly DspAddress GetReference(bool markUsed) + { + if (!HasMappedMemoryPool()) + { + return ForceMappedDspAddress; + } + + if (markUsed) + { + MemoryPoolState.IsUsed = true; + } + + return MemoryPoolState.Translate(CpuAddress, Size); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs new file mode 100644 index 00000000..91bd5dbf --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs @@ -0,0 +1,130 @@ +using System; +using System.Runtime.InteropServices; +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.MemoryPool +{ + /// + /// Server state for a memory pool. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = Alignment)] + public struct MemoryPoolState + { + public const int Alignment = 0x10; + + /// + /// The location of the . + /// + public enum LocationType : uint + { + /// + /// located on the CPU side for user use. + /// + Cpu, + + /// + /// located on the DSP side for system use. + /// + Dsp, + } + + /// + /// The CPU address associated to the . + /// + public CpuAddress CpuAddress; + + /// + /// The DSP address associated to the . + /// + public DspAddress DspAddress; + + /// + /// The size associated to the . + /// + public ulong Size; + + /// + /// The associated to the . + /// + public LocationType Location; + + /// + /// Set to true if the is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + public static unsafe MemoryPoolState* Null => (MemoryPoolState*)IntPtr.Zero.ToPointer(); + + /// + /// Create a new with the given . + /// + /// The location type to use. + /// A new with the given . + public static MemoryPoolState Create(LocationType location) + { + return new MemoryPoolState + { + CpuAddress = 0, + DspAddress = 0, + Size = 0, + Location = location, + }; + } + + /// + /// Set the and size of the . + /// + /// The . + /// The size. + public void SetCpuAddress(CpuAddress cpuAddress, ulong size) + { + CpuAddress = cpuAddress; + Size = size; + } + + /// + /// Check if the given and size is contains in the . + /// + /// The . + /// The size. + /// True if the is contained inside the . + public readonly bool Contains(CpuAddress targetCpuAddress, ulong size) + { + if (CpuAddress <= targetCpuAddress && size + targetCpuAddress <= Size + CpuAddress) + { + return true; + } + + return false; + } + + /// + /// Translate the given CPU address to a DSP address. + /// + /// The . + /// The size. + /// the target DSP address. + public readonly DspAddress Translate(CpuAddress targetCpuAddress, ulong size) + { + if (Contains(targetCpuAddress, size) && IsMapped()) + { + ulong offset = targetCpuAddress - CpuAddress; + + return DspAddress + offset; + } + + return 0; + } + + /// + /// Is the mapped on the DSP? + /// + /// Returns true if the is mapped on the DSP. + public readonly bool IsMapped() + { + return DspAddress != 0; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs new file mode 100644 index 00000000..f67d0c12 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs @@ -0,0 +1,365 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common.Logging; +using System; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.MemoryPool +{ + /// + /// Memory pool mapping helper. + /// + public class PoolMapper + { + const uint CurrentProcessPseudoHandle = 0xFFFF8001; + + /// + /// The result of . + /// + public enum UpdateResult : uint + { + /// + /// No error reported. + /// + Success = 0, + + /// + /// The user parameters were invalid. + /// + InvalidParameter = 1, + + /// + /// mapping failed. + /// + MapError = 2, + + /// + /// unmapping failed. + /// + UnmapError = 3, + } + + /// + /// The handle of the process owning the CPU memory manipulated. + /// + private readonly uint _processHandle; + + /// + /// The that will be manipulated. + /// + private readonly Memory _memoryPools; + + /// + /// If set to true, this will try to force map memory pool even if their state are considered invalid. + /// + private readonly bool _isForceMapEnabled; + + /// + /// Create a new used for system mapping. + /// + /// The handle of the process owning the CPU memory manipulated. + /// If set to true, this will try to force map memory pool even if their state are considered invalid. + public PoolMapper(uint processHandle, bool isForceMapEnabled) + { + _processHandle = processHandle; + _isForceMapEnabled = isForceMapEnabled; + _memoryPools = Memory.Empty; + } + + /// + /// Create a new used for user mapping. + /// + /// The handle of the process owning the CPU memory manipulated. + /// The user memory pools. + /// If set to true, this will try to force map memory pool even if their state are considered invalid. + public PoolMapper(uint processHandle, Memory memoryPool, bool isForceMapEnabled) + { + _processHandle = processHandle; + _memoryPools = memoryPool; + _isForceMapEnabled = isForceMapEnabled; + } + + /// + /// Initialize the for system use. + /// + /// The for system use. + /// The to assign. + /// The size to assign. + /// Returns true if mapping on the succeeded. + public bool InitializeSystemPool(ref MemoryPoolState memoryPool, CpuAddress cpuAddress, ulong size) + { + if (memoryPool.Location != MemoryPoolState.LocationType.Dsp) + { + return false; + } + + return InitializePool(ref memoryPool, cpuAddress, size); + } + + /// + /// Initialize the . + /// + /// The . + /// The to assign. + /// The size to assign. + /// Returns true if mapping on the succeeded. + public bool InitializePool(ref MemoryPoolState memoryPool, CpuAddress cpuAddress, ulong size) + { + memoryPool.SetCpuAddress(cpuAddress, size); + + return Map(ref memoryPool) != 0; + } + + /// + /// Get the process handle associated to the . + /// + /// The . + /// Returns the process handle associated to the . + public uint GetProcessHandle(ref MemoryPoolState memoryPool) + { + if (memoryPool.Location == MemoryPoolState.LocationType.Cpu) + { + return CurrentProcessPseudoHandle; + } + + if (memoryPool.Location == MemoryPoolState.LocationType.Dsp) + { + return _processHandle; + } + + return 0; + } + + /// + /// Map the on the . + /// + /// The to map. + /// Returns the DSP address mapped. + public DspAddress Map(ref MemoryPoolState memoryPool) + { + DspAddress result = AudioProcessorMemoryManager.Map(GetProcessHandle(ref memoryPool), memoryPool.CpuAddress, memoryPool.Size); + + if (result != 0) + { + memoryPool.DspAddress = result; + } + + return result; + } + + /// + /// Unmap the from the . + /// + /// The to unmap. + /// Returns true if unmapped. + public bool Unmap(ref MemoryPoolState memoryPool) + { + if (memoryPool.IsUsed) + { + return false; + } + + AudioProcessorMemoryManager.Unmap(GetProcessHandle(ref memoryPool), memoryPool.CpuAddress, memoryPool.Size); + + memoryPool.SetCpuAddress(0, 0); + memoryPool.DspAddress = 0; + + return true; + } + + /// + /// Find a associated to the region given. + /// + /// The region . + /// The region size. + /// Returns the found or if not found. + private Span FindMemoryPool(CpuAddress cpuAddress, ulong size) + { + if (!_memoryPools.IsEmpty && _memoryPools.Length > 0) + { + for (int i = 0; i < _memoryPools.Length; i++) + { + if (_memoryPools.Span[i].Contains(cpuAddress, size)) + { + return _memoryPools.Span.Slice(i, 1); + } + } + } + + return Span.Empty; + } + + /// + /// Force unmap the given . + /// + /// The to force unmap + public void ForceUnmap(ref AddressInfo addressInfo) + { + if (_isForceMapEnabled) + { + Span memoryPool = FindMemoryPool(addressInfo.CpuAddress, addressInfo.Size); + + if (!memoryPool.IsEmpty) + { + AudioProcessorMemoryManager.Unmap(_processHandle, memoryPool[0].CpuAddress, memoryPool[0].Size); + + return; + } + + AudioProcessorMemoryManager.Unmap(_processHandle, addressInfo.CpuAddress, 0); + } + } + + /// + /// Try to attach the given region to the . + /// + /// The error information if an error was generated. + /// The to attach the region to. + /// The region . + /// The region size. + /// Returns true if mapping was performed. + public bool TryAttachBuffer(out ErrorInfo errorInfo, ref AddressInfo addressInfo, CpuAddress cpuAddress, ulong size) + { + errorInfo = new ErrorInfo(); + + addressInfo.Setup(cpuAddress, size); + + if (AssignDspAddress(ref addressInfo)) + { + errorInfo.ErrorCode = 0x0; + errorInfo.ExtraErrorInfo = 0x0; + + return true; + } + + errorInfo.ErrorCode = ResultCode.InvalidAddressInfo; + errorInfo.ExtraErrorInfo = addressInfo.CpuAddress; + + return _isForceMapEnabled; + } + + /// + /// Update a using user parameters. + /// + /// The to update. + /// Input user parameter. + /// Output user parameter. + /// Returns the of the operations performed. + public UpdateResult Update(ref MemoryPoolState memoryPool, in MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus) + { + MemoryPoolUserState inputState = inParameter.State; + + MemoryPoolUserState outputState; + + const uint PageSize = 0x1000; + + if (inputState != MemoryPoolUserState.RequestAttach && inputState != MemoryPoolUserState.RequestDetach) + { + return UpdateResult.Success; + } + + if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress % PageSize) != 0) + { + return UpdateResult.InvalidParameter; + } + + if (inParameter.Size == 0 || (inParameter.Size % PageSize) != 0) + { + return UpdateResult.InvalidParameter; + } + + if (inputState == MemoryPoolUserState.RequestAttach) + { + bool initializeSuccess = InitializePool(ref memoryPool, inParameter.CpuAddress, inParameter.Size); + + if (!initializeSuccess) + { + memoryPool.SetCpuAddress(0, 0); + + Logger.Error?.Print(LogClass.AudioRenderer, $"Map of memory pool (address: 0x{inParameter.CpuAddress:x}, size 0x{inParameter.Size:x}) failed!"); + return UpdateResult.MapError; + } + + outputState = MemoryPoolUserState.Attached; + } + else + { + if (memoryPool.CpuAddress != inParameter.CpuAddress || memoryPool.Size != inParameter.Size) + { + return UpdateResult.InvalidParameter; + } + + if (!Unmap(ref memoryPool)) + { + Logger.Error?.Print(LogClass.AudioRenderer, $"Unmap of memory pool (address: 0x{memoryPool.CpuAddress:x}, size 0x{memoryPool.Size:x}) failed!"); + return UpdateResult.UnmapError; + } + + outputState = MemoryPoolUserState.Detached; + } + + outStatus.State = outputState; + + return UpdateResult.Success; + } + + /// + /// Map the to the . + /// + /// The to map. + /// Returns true if mapping was performed. + private bool AssignDspAddress(ref AddressInfo addressInfo) + { + if (addressInfo.CpuAddress == 0) + { + return false; + } + + if (_memoryPools.Length > 0) + { + Span memoryPool = FindMemoryPool(addressInfo.CpuAddress, addressInfo.Size); + + if (!memoryPool.IsEmpty) + { + addressInfo.SetupMemoryPool(memoryPool); + + return true; + } + } + + if (_isForceMapEnabled) + { + DspAddress dspAddress = AudioProcessorMemoryManager.Map(_processHandle, addressInfo.CpuAddress, addressInfo.Size); + + addressInfo.ForceMappedDspAddress = dspAddress; + + AudioProcessorMemoryManager.Map(_processHandle, addressInfo.CpuAddress, addressInfo.Size); + } + else + { + unsafe + { + addressInfo.SetupMemoryPool(MemoryPoolState.Null); + } + } + + return false; + } + + /// + /// Remove the usage flag from all the . + /// + /// The to reset. + public static void ClearUsageState(Memory memoryPool) + { + foreach (ref MemoryPoolState info in memoryPool.Span) + { + info.IsUsed = false; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs new file mode 100644 index 00000000..8991ceaf --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs @@ -0,0 +1,257 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Mix +{ + /// + /// Mix context. + /// + public class MixContext + { + /// + /// The total mix count. + /// + private uint _mixesCount; + + /// + /// Storage for . + /// + private Memory _mixes; + + /// + /// Storage of the sorted indices to . + /// + private Memory _sortedMixes; + + /// + /// Graph state. + /// + public NodeStates NodeStates { get; } + + /// + /// The instance of the adjacent matrix. + /// + public EdgeMatrix EdgeMatrix { get; } + + /// + /// Create a new instance of . + /// + public MixContext() + { + NodeStates = new NodeStates(); + EdgeMatrix = new EdgeMatrix(); + } + + /// + /// Initialize the . + /// + /// The storage for sorted indices. + /// The storage of . + /// The storage used for the . + /// The storage used for the . + public void Initialize(Memory sortedMixes, Memory mixes, Memory nodeStatesWorkBuffer, Memory edgeMatrixWorkBuffer) + { + _mixesCount = (uint)mixes.Length; + _mixes = mixes; + _sortedMixes = sortedMixes; + + if (!nodeStatesWorkBuffer.IsEmpty && !edgeMatrixWorkBuffer.IsEmpty) + { + NodeStates.Initialize(nodeStatesWorkBuffer, mixes.Length); + EdgeMatrix.Initialize(edgeMatrixWorkBuffer, mixes.Length); + } + + int sortedId = 0; + for (int i = 0; i < _mixes.Length; i++) + { + SetSortedState(sortedId++, i); + } + } + + /// + /// Associate the given to a given . + /// + /// The sorted id. + /// The index to associate. + private void SetSortedState(int id, int targetIndex) + { + _sortedMixes.Span[id] = targetIndex; + } + + /// + /// Get a reference to the final . + /// + /// A reference to the final . + public ref MixState GetFinalState() + { + return ref GetState(Constants.FinalMixId); + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref MixState GetState(int id) + { + return ref SpanIOHelper.GetFromMemory(_mixes, id, _mixesCount); + } + + /// + /// Get a reference to a at the given of the sorted mix info. + /// + /// The index to use. + /// A reference to a at the given . + public ref MixState GetSortedState(int id) + { + Debug.Assert(id >= 0 && id < _mixesCount); + + return ref GetState(_sortedMixes.Span[id]); + } + + /// + /// Get the total mix count. + /// + /// The total mix count. + public uint GetCount() + { + return _mixesCount; + } + + /// + /// Update the internal distance from the final mix value of every . + /// + private void UpdateDistancesFromFinalMix() + { + foreach (ref MixState mix in _mixes.Span) + { + mix.ClearDistanceFromFinalMix(); + } + + for (int i = 0; i < GetCount(); i++) + { + ref MixState mix = ref GetState(i); + + SetSortedState(i, i); + + if (mix.IsUsed) + { + uint distance; + + if (mix.MixId != Constants.FinalMixId) + { + int mixId = mix.MixId; + + for (distance = 0; distance < GetCount(); distance++) + { + if (mixId == Constants.UnusedMixId) + { + distance = MixState.InvalidDistanceFromFinalMix; + break; + } + + ref MixState distanceMix = ref GetState(mixId); + + if (distanceMix.DistanceFromFinalMix != MixState.InvalidDistanceFromFinalMix) + { + distance = distanceMix.DistanceFromFinalMix + 1; + break; + } + + mixId = distanceMix.DestinationMixId; + + if (mixId == Constants.FinalMixId) + { + break; + } + } + + if (distance > GetCount()) + { + distance = MixState.InvalidDistanceFromFinalMix; + } + } + else + { + distance = MixState.InvalidDistanceFromFinalMix; + } + + mix.DistanceFromFinalMix = distance; + } + } + } + + /// + /// Update the internal mix buffer offset of all . + /// + private void UpdateMixBufferOffset() + { + uint offset = 0; + + foreach (ref MixState mix in _mixes.Span) + { + mix.BufferOffset = offset; + + offset += mix.BufferCount; + } + } + + /// + /// Sort the mixes using distance from the final mix. + /// + public void Sort() + { + UpdateDistancesFromFinalMix(); + + int[] sortedMixesTemp = _sortedMixes[..(int)GetCount()].ToArray(); + + Array.Sort(sortedMixesTemp, (a, b) => + { + ref MixState stateA = ref GetState(a); + ref MixState stateB = ref GetState(b); + + return stateB.DistanceFromFinalMix.CompareTo(stateA.DistanceFromFinalMix); + }); + + sortedMixesTemp.AsSpan().CopyTo(_sortedMixes.Span); + + UpdateMixBufferOffset(); + } + + /// + /// Sort the mixes and splitters using an adjacency matrix. + /// + /// The used. + /// Return true, if no errors in the graph were detected. + public bool Sort(SplitterContext splitterContext) + { + if (splitterContext.UsingSplitter()) + { + bool isValid = NodeStates.Sort(EdgeMatrix); + + if (isValid) + { + ReadOnlySpan sortedMixesIndex = NodeStates.GetTsortResult(); + + int id = 0; + + for (int i = sortedMixesIndex.Length - 1; i >= 0; i--) + { + SetSortedState(id++, sortedMixesIndex[i]); + } + + UpdateMixBufferOffset(); + } + + return isValid; + } + + UpdateMixBufferOffset(); + + return true; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs new file mode 100644 index 00000000..5ba58ea5 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs @@ -0,0 +1,312 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Constants; + +namespace Ryujinx.Audio.Renderer.Server.Mix +{ + /// + /// Server state for a mix. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x940, Pack = Alignment)] + public struct MixState + { + public const uint InvalidDistanceFromFinalMix = 0x80000000; + + public const int Alignment = 0x10; + + /// + /// Base volume of the mix. + /// + public float Volume; + + /// + /// Target sample rate of the mix. + /// + public uint SampleRate; + + /// + /// Target buffer count. + /// + public uint BufferCount; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// The id of the mix. + /// + public int MixId; + + /// + /// The mix node id. + /// + public int NodeId; + + /// + /// the buffer offset to use for command generation. + /// + public uint BufferOffset; + + /// + /// The distance of the mix from the final mix. + /// + public uint DistanceFromFinalMix; + + /// + /// The effect processing order storage. + /// + private readonly IntPtr _effectProcessingOrderArrayPointer; + + /// + /// The max element count that can be found in the effect processing order storage. + /// + public uint EffectProcessingOrderArrayMaxCount; + + /// + /// The mix to output the result of this mix. + /// + public int DestinationMixId; + + /// + /// Mix buffer volumes storage. + /// + private MixVolumeArray _mixVolumeArray; + + /// + /// The splitter to output the result of this mix. + /// + public uint DestinationSplitterId; + + /// + /// If set to true, the long size pre-delay is supported on the reverb command. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsLongSizePreDelaySupported; + + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] + private struct MixVolumeArray + { + private const int Size = 4 * MixBufferCountMax * MixBufferCountMax; + } + + /// + /// Mix buffer volumes. + /// + /// Used when no splitter id is specified. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixVolumeArray); + + /// + /// Get the volume for a given connection destination. + /// + /// The source node index. + /// The destination node index + /// The volume for the given connection destination. + public float GetMixBufferVolume(int sourceIndex, int destinationIndex) + { + return MixBufferVolume[sourceIndex * MixBufferCountMax + destinationIndex]; + } + + /// + /// The array used to order effects associated to this mix. + /// + public readonly Span EffectProcessingOrderArray + { + get + { + if (_effectProcessingOrderArrayPointer == IntPtr.Zero) + { + return Span.Empty; + } + + unsafe + { + return new Span((void*)_effectProcessingOrderArrayPointer, (int)EffectProcessingOrderArrayMaxCount); + } + } + } + + /// + /// Create a new + /// + /// + /// + public MixState(Memory effectProcessingOrderArray, ref BehaviourContext behaviourContext) : this() + { + MixId = UnusedMixId; + + DistanceFromFinalMix = InvalidDistanceFromFinalMix; + + DestinationMixId = UnusedMixId; + + DestinationSplitterId = UnusedSplitterId; + + unsafe + { + // SAFETY: safe as effectProcessingOrderArray comes from the work buffer memory that is pinned. + _effectProcessingOrderArrayPointer = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetReference(effectProcessingOrderArray.Span)); + } + + EffectProcessingOrderArrayMaxCount = (uint)effectProcessingOrderArray.Length; + + IsLongSizePreDelaySupported = behaviourContext.IsLongSizePreDelaySupported(); + + ClearEffectProcessingOrder(); + } + + /// + /// Clear the value to its default state. + /// + public void ClearDistanceFromFinalMix() + { + DistanceFromFinalMix = InvalidDistanceFromFinalMix; + } + + /// + /// Clear the to its default state. + /// + public readonly void ClearEffectProcessingOrder() + { + EffectProcessingOrderArray.Fill(-1); + } + + /// + /// Return true if the mix has any destinations. + /// + /// True if the mix has any destinations. + public readonly bool HasAnyDestination() + { + return DestinationMixId != UnusedMixId || DestinationSplitterId != UnusedSplitterId; + } + + /// + /// Update the mix connection on the adjacency matrix. + /// + /// The adjacency matrix. + /// The input parameter of the mix. + /// The splitter context. + /// Return true, new connections were done on the adjacency matrix. + private bool UpdateConnection(EdgeMatrix edgeMatrix, in MixParameter parameter, ref SplitterContext splitterContext) + { + bool hasNewConnections; + + if (DestinationSplitterId == UnusedSplitterId) + { + hasNewConnections = false; + } + else + { + ref SplitterState splitter = ref splitterContext.GetState((int)DestinationSplitterId); + + hasNewConnections = splitter.HasNewConnection; + } + + if (DestinationMixId == parameter.DestinationMixId && DestinationSplitterId == parameter.DestinationSplitterId && !hasNewConnections) + { + return false; + } + + edgeMatrix.RemoveEdges(MixId); + + if (parameter.DestinationMixId == UnusedMixId) + { + if (parameter.DestinationSplitterId != UnusedSplitterId) + { + ref SplitterState splitter = ref splitterContext.GetState((int)parameter.DestinationSplitterId); + + for (int i = 0; i < splitter.DestinationCount; i++) + { + SplitterDestination destination = splitter.GetData(i); + + if (!destination.IsNull) + { + int destinationMixId = destination.DestinationId; + + if (destinationMixId != UnusedMixId) + { + edgeMatrix.Connect(MixId, destinationMixId); + } + } + } + } + } + else + { + edgeMatrix.Connect(MixId, parameter.DestinationMixId); + } + + DestinationMixId = parameter.DestinationMixId; + DestinationSplitterId = parameter.DestinationSplitterId; + + return true; + } + + /// + /// Update the mix from user information. + /// + /// The adjacency matrix. + /// The input parameter of the mix. + /// The effect context. + /// The splitter context. + /// The behaviour context. + /// Return true if the mix was changed. + public bool Update(EdgeMatrix edgeMatrix, in MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext) + { + bool isDirty; + + Volume = parameter.Volume; + SampleRate = parameter.SampleRate; + BufferCount = parameter.BufferCount; + IsUsed = parameter.IsUsed; + MixId = parameter.MixId; + NodeId = parameter.NodeId; + parameter.MixBufferVolume.CopyTo(MixBufferVolume); + + if (behaviourContext.IsSplitterSupported()) + { + isDirty = UpdateConnection(edgeMatrix, in parameter, ref splitterContext); + } + else + { + isDirty = DestinationMixId != parameter.DestinationMixId; + + if (DestinationMixId != parameter.DestinationMixId) + { + DestinationMixId = parameter.DestinationMixId; + } + + DestinationSplitterId = UnusedSplitterId; + } + + ClearEffectProcessingOrder(); + + for (int i = 0; i < effectContext.GetCount(); i++) + { + ref BaseEffect effect = ref effectContext.GetEffect(i); + + if (effect.MixId == MixId) + { + Debug.Assert(effect.ProcessingOrder <= EffectProcessingOrderArrayMaxCount); + + if (effect.ProcessingOrder > EffectProcessingOrderArrayMaxCount) + { + return isDirty; + } + + EffectProcessingOrderArray[(int)effect.ProcessingOrder] = i; + } + } + + return isDirty; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs new file mode 100644 index 00000000..ffabf467 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs @@ -0,0 +1,52 @@ +using Ryujinx.Audio.Renderer.Common; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Represents a detailed entry in a performance frame. + /// + public interface IPerformanceDetailEntry + { + /// + /// Get the start time of this entry event (in microseconds). + /// + /// The start time of this entry event (in microseconds). + int GetStartTime(); + + /// + /// Get the start time offset in this structure. + /// + /// The start time offset in this structure. + int GetStartTimeOffset(); + + /// + /// Get the processing time of this entry event (in microseconds). + /// + /// The processing time of this entry event (in microseconds). + int GetProcessingTime(); + + /// + /// Get the processing time offset in this structure. + /// + /// The processing time offset in this structure. + int GetProcessingTimeOffset(); + + /// + /// Set the of this entry. + /// + /// The node id of this entry. + void SetNodeId(int nodeId); + + /// + /// Set the of this entry. + /// + /// The type to use. + void SetEntryType(PerformanceEntryType type); + + /// + /// Set the of this entry. + /// + /// The type to use. + void SetDetailType(PerformanceDetailType detailType); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs new file mode 100644 index 00000000..a0178187 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs @@ -0,0 +1,46 @@ +using Ryujinx.Audio.Renderer.Common; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Represents an entry in a performance frame. + /// + public interface IPerformanceEntry + { + /// + /// Get the start time of this entry event (in microseconds). + /// + /// The start time of this entry event (in microseconds). + int GetStartTime(); + + /// + /// Get the start time offset in this structure. + /// + /// The start time offset in this structure. + int GetStartTimeOffset(); + + /// + /// Get the processing time of this entry event (in microseconds). + /// + /// The processing time of this entry event (in microseconds). + int GetProcessingTime(); + + /// + /// Get the processing time offset in this structure. + /// + /// The processing time offset in this structure. + int GetProcessingTimeOffset(); + + /// + /// Set the of this entry. + /// + /// The node id of this entry. + void SetNodeId(int nodeId); + + /// + /// Set the of this entry. + /// + /// The type to use. + void SetEntryType(PerformanceEntryType type); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs new file mode 100644 index 00000000..deacd8cc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs @@ -0,0 +1,80 @@ +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// The header of a performance frame. + /// + public interface IPerformanceHeader + { + /// + /// Get the entry count offset in this structure. + /// + /// The entry count offset in this structure. + int GetEntryCountOffset(); + + /// + /// Set the DSP running behind flag. + /// + /// The flag. + void SetDspRunningBehind(bool isRunningBehind); + + /// + /// Set the count of voices that were dropped. + /// + /// The count of voices that were dropped. + void SetVoiceDropCount(uint voiceCount); + + /// + /// Set the start ticks of the . (before sending commands) + /// + /// The start ticks of the . (before sending commands) + void SetStartRenderingTicks(ulong startTicks); + + /// + /// Set the header magic. + /// + /// The header magic. + void SetMagic(uint magic); + + /// + /// Set the offset of the next performance header. + /// + /// The offset of the next performance header. + void SetNextOffset(int nextOffset); + + /// + /// Set the total time taken by all the commands profiled. + /// + /// The total time taken by all the commands profiled. + void SetTotalProcessingTime(int totalProcessingTime); + + /// + /// Set the index of this performance frame. + /// + /// The index of this performance frame. + void SetIndex(uint index); + + /// + /// Get the total count of entries in this frame. + /// + /// The total count of entries in this frame. + int GetEntryCount(); + + /// + /// Get the total count of detailed entries in this frame. + /// + /// The total count of detailed entries in this frame. + int GetEntryDetailCount(); + + /// + /// Set the total count of entries in this frame. + /// + /// The total count of entries in this frame. + void SetEntryCount(int entryCount); + + /// + /// Set the total count of detailed entries in this frame. + /// + /// The total count of detailed entries in this frame. + void SetEntryDetailCount(int entryDetailCount); + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs new file mode 100644 index 00000000..a4024607 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs @@ -0,0 +1,72 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + public struct PerformanceDetailVersion1 : IPerformanceDetailEntry + { + /// + /// The node id associated to this detailed entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this detailed entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this detailed entry. + /// + public int ProcessingTime; + + /// + /// The detailed entry type associated to this detailed entry. + /// + public PerformanceDetailType DetailType; + + /// + /// The entry type associated to this detailed entry. + /// + public PerformanceEntryType EntryType; + + public readonly int GetProcessingTime() + { + return ProcessingTime; + } + + public readonly int GetProcessingTimeOffset() + { + return 8; + } + + public readonly int GetStartTime() + { + return StartTime; + } + + public readonly int GetStartTimeOffset() + { + return 4; + } + + public void SetDetailType(PerformanceDetailType detailType) + { + DetailType = detailType; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs new file mode 100644 index 00000000..f10e2937 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs @@ -0,0 +1,72 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 2. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] + public struct PerformanceDetailVersion2 : IPerformanceDetailEntry + { + /// + /// The node id associated to this detailed entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this detailed entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this detailed entry. + /// + public int ProcessingTime; + + /// + /// The detailed entry type associated to this detailed entry. + /// + public PerformanceDetailType DetailType; + + /// + /// The entry type associated to this detailed entry. + /// + public PerformanceEntryType EntryType; + + public readonly int GetProcessingTime() + { + return ProcessingTime; + } + + public readonly int GetProcessingTimeOffset() + { + return 8; + } + + public readonly int GetStartTime() + { + return StartTime; + } + + public readonly int GetStartTimeOffset() + { + return 4; + } + + public void SetDetailType(PerformanceDetailType detailType) + { + DetailType = detailType; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs new file mode 100644 index 00000000..d24b96a2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs @@ -0,0 +1,56 @@ +using System; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Information used by the performance command to store informations in the performance entry. + /// + public class PerformanceEntryAddresses + { + /// + /// The memory storing the performance entry. + /// + public Memory BaseMemory; + + /// + /// The offset to the start time field. + /// + public uint StartTimeOffset; + + /// + /// The offset to the entry count field. + /// + public uint EntryCountOffset; + + /// + /// The offset to the processing time field. + /// + public uint ProcessingTimeOffset; + + /// + /// Increment the entry count. + /// + public void IncrementEntryCount() + { + BaseMemory.Span[(int)EntryCountOffset / 4]++; + } + + /// + /// Set the start time in the entry. + /// + /// The start time in nanoseconds. + public void SetStartTime(ulong startTimeNano) + { + BaseMemory.Span[(int)StartTimeOffset / 4] = (int)(startTimeNano / 1000); + } + + /// + /// Set the processing time in the entry. + /// + /// The end time in nanoseconds. + public void SetProcessingTime(ulong endTimeNano) + { + BaseMemory.Span[(int)ProcessingTimeOffset / 4] = (int)(endTimeNano / 1000) - BaseMemory.Span[(int)StartTimeOffset / 4]; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs new file mode 100644 index 00000000..2c407670 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs @@ -0,0 +1,62 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + public struct PerformanceEntryVersion1 : IPerformanceEntry + { + /// + /// The node id associated to this entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this entry. + /// + public int ProcessingTime; + + /// + /// The entry type associated to this entry. + /// + public PerformanceEntryType EntryType; + + public readonly int GetProcessingTime() + { + return ProcessingTime; + } + + public readonly int GetProcessingTimeOffset() + { + return 8; + } + + public readonly int GetStartTime() + { + return StartTime; + } + + public readonly int GetStartTimeOffset() + { + return 4; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs new file mode 100644 index 00000000..eb96a314 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs @@ -0,0 +1,62 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 2. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] + public struct PerformanceEntryVersion2 : IPerformanceEntry + { + /// + /// The node id associated to this entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this entry. + /// + public int ProcessingTime; + + /// + /// The entry type associated to this entry. + /// + public PerformanceEntryType EntryType; + + public readonly int GetProcessingTime() + { + return ProcessingTime; + } + + public readonly int GetProcessingTimeOffset() + { + return 8; + } + + public readonly int GetStartTime() + { + return StartTime; + } + + public readonly int GetStartTimeOffset() + { + return 4; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs new file mode 100644 index 00000000..5aeb703c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs @@ -0,0 +1,101 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] + public struct PerformanceFrameHeaderVersion1 : IPerformanceHeader + { + /// + /// The magic of the performance header. + /// + public uint Magic; + + /// + /// The total count of entries in this frame. + /// + public int EntryCount; + + /// + /// The total count of detailed entries in this frame. + /// + public int EntryDetailCount; + + /// + /// The offset of the next performance header. + /// + public int NextOffset; + + /// + /// The total time taken by all the commands profiled. + /// + public int TotalProcessingTime; + + /// + /// The count of voices that were dropped. + /// + public uint VoiceDropCount; + + public readonly int GetEntryCount() + { + return EntryCount; + } + + public readonly int GetEntryCountOffset() + { + return 4; + } + + public readonly int GetEntryDetailCount() + { + return EntryDetailCount; + } + + public readonly void SetDspRunningBehind(bool isRunningBehind) + { + // NOTE: Not present in version 1 + } + + public void SetEntryCount(int entryCount) + { + EntryCount = entryCount; + } + + public void SetEntryDetailCount(int entryDetailCount) + { + EntryDetailCount = entryDetailCount; + } + + public readonly void SetIndex(uint index) + { + // NOTE: Not present in version 1 + } + + public void SetMagic(uint magic) + { + Magic = magic; + } + + public void SetNextOffset(int nextOffset) + { + NextOffset = nextOffset; + } + + public readonly void SetStartRenderingTicks(ulong startTicks) + { + // NOTE: not present in version 1 + } + + public void SetTotalProcessingTime(int totalProcessingTime) + { + TotalProcessingTime = totalProcessingTime; + } + + public void SetVoiceDropCount(uint voiceCount) + { + VoiceDropCount = voiceCount; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs new file mode 100644 index 00000000..d6e0ffc8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs @@ -0,0 +1,117 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 2. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x30)] + public struct PerformanceFrameHeaderVersion2 : IPerformanceHeader + { + /// + /// The magic of the performance header. + /// + public uint Magic; + + /// + /// The total count of entries in this frame. + /// + public int EntryCount; + + /// + /// The total count of detailed entries in this frame. + /// + public int EntryDetailCount; + + /// + /// The offset of the next performance header. + /// + public int NextOffset; + + /// + /// The total time taken by all the commands profiled. + /// + public int TotalProcessingTime; + + /// + /// The count of voices that were dropped. + /// + public uint VoiceDropCount; + + /// + /// The start ticks of the . (before sending commands) + /// + public ulong StartRenderingTicks; + + /// + /// The index of this performance frame. + /// + public uint Index; + + /// + /// If set to true, the DSP is running behind. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsDspRunningBehind; + + public readonly int GetEntryCount() + { + return EntryCount; + } + + public readonly int GetEntryCountOffset() + { + return 4; + } + + public readonly int GetEntryDetailCount() + { + return EntryDetailCount; + } + + public void SetDspRunningBehind(bool isRunningBehind) + { + IsDspRunningBehind = isRunningBehind; + } + + public void SetEntryCount(int entryCount) + { + EntryCount = entryCount; + } + + public void SetEntryDetailCount(int entryDetailCount) + { + EntryDetailCount = entryDetailCount; + } + + public void SetIndex(uint index) + { + Index = index; + } + + public void SetMagic(uint magic) + { + Magic = magic; + } + + public void SetNextOffset(int nextOffset) + { + NextOffset = nextOffset; + } + + public void SetStartRenderingTicks(ulong startTicks) + { + StartRenderingTicks = startTicks; + } + + public void SetTotalProcessingTime(int totalProcessingTime) + { + TotalProcessingTime = totalProcessingTime; + } + + public void SetVoiceDropCount(uint voiceCount) + { + VoiceDropCount = voiceCount; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs new file mode 100644 index 00000000..da5a0ad4 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs @@ -0,0 +1,98 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + public abstract class PerformanceManager + { + /// + /// Get the required size for a single performance frame. + /// + /// The audio renderer configuration. + /// The behaviour context. + /// The required size for a single performance frame. + public static ulong GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref AudioRendererConfiguration parameter, ref BehaviourContext behaviourContext) + { + uint version = behaviourContext.GetPerformanceMetricsDataFormat(); + + if (version == 2) + { + return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + } + + if (version == 1) + { + return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + } + + throw new NotImplementedException($"Unknown Performance metrics data format version {version}"); + } + + /// + /// Copy the performance frame history to the supplied user buffer and returns the size copied. + /// + /// The supplied user buffer to store the performance frame into. + /// The size copied to the supplied buffer. + public abstract uint CopyHistories(Span performanceOutput); + + /// + /// Set the target node id to profile. + /// + /// The target node id to profile. + public abstract void SetTargetNodeId(int target); + + /// + /// Check if the given target node id is profiled. + /// + /// The target node id to check. + /// Return true, if the given target node id is profiled. + public abstract bool IsTargetNodeId(int target); + + /// + /// Get the next buffer to store a performance entry. + /// + /// The output . + /// The info. + /// The node id of the entry. + /// Return true, if a valid was returned. + public abstract bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceEntryType entryType, int nodeId); + + /// + /// Get the next buffer to store a performance detailed entry. + /// + /// The output . + /// The info. + /// The info. + /// The node id of the entry. + /// Return true, if a valid was returned. + public abstract bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceDetailType detailType, PerformanceEntryType entryType, int nodeId); + + /// + /// Finalize the current performance frame. + /// + /// Indicate if the DSP is running behind. + /// The count of voices that were dropped. + /// The start ticks of the audio rendering. + public abstract void TapFrame(bool dspRunningBehind, uint voiceDropCount, ulong startRenderingTicks); + + /// + /// Create a new . + /// + /// The backing memory available for use by the manager. + /// The audio renderer configuration. + /// The behaviour context; + /// A new . + public static PerformanceManager Create(Memory performanceBuffer, ref AudioRendererConfiguration parameter, BehaviourContext behaviourContext) + { + uint version = behaviourContext.GetPerformanceMetricsDataFormat(); + + return version switch + { + 1 => new PerformanceManagerGeneric(performanceBuffer, ref parameter), + 2 => new PerformanceManagerGeneric(performanceBuffer, ref parameter), + _ => throw new NotImplementedException($"Unknown Performance metrics data format version {version}"), + }; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs new file mode 100644 index 00000000..2e5d25b9 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs @@ -0,0 +1,308 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// A Generic implementation of . + /// + /// The header implementation of the performance frame. + /// The entry implementation of the performance frame. + /// A detailed implementation of the performance frame. + public class PerformanceManagerGeneric : PerformanceManager where THeader : unmanaged, IPerformanceHeader where TEntry : unmanaged, IPerformanceEntry where TEntryDetail : unmanaged, IPerformanceDetailEntry + { + /// + /// The magic used for the . + /// + private const uint MagicPerformanceBuffer = 0x46524550; + + /// + /// The fixed amount of that can be stored in a frame. + /// + private const int MaxFrameDetailCount = 100; + + private readonly Memory _buffer; + private readonly Memory _historyBuffer; + + private Memory CurrentBuffer => _buffer[.._frameSize]; + private Memory CurrentBufferData => CurrentBuffer[Unsafe.SizeOf()..]; + + private ref THeader CurrentHeader => ref MemoryMarshal.Cast(CurrentBuffer.Span)[0]; + + private Span Entries => MemoryMarshal.Cast(CurrentBufferData.Span[..GetEntriesSize()]); + private Span EntriesDetail => MemoryMarshal.Cast(CurrentBufferData.Span.Slice(GetEntriesSize(), GetEntriesDetailSize())); + + private readonly int _frameSize; + private readonly int _availableFrameCount; + private readonly int _entryCountPerFrame; + private int _detailTarget; + private int _entryIndex; + private int _entryDetailIndex; + private int _indexHistoryWrite; + private int _indexHistoryRead; + private uint _historyFrameIndex; + + public PerformanceManagerGeneric(Memory buffer, ref AudioRendererConfiguration parameter) + { + _buffer = buffer; + _frameSize = GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + + _entryCountPerFrame = (int)GetEntryCount(ref parameter); + _availableFrameCount = buffer.Length / _frameSize - 1; + + _historyFrameIndex = 0; + + _historyBuffer = _buffer[_frameSize..]; + + SetupNewHeader(); + } + + private Span GetBufferFromIndex(Span data, int index) + { + return data.Slice(index * _frameSize, _frameSize); + } + + private ref THeader GetHeaderFromBuffer(Span data, int index) + { + return ref MemoryMarshal.Cast(GetBufferFromIndex(data, index))[0]; + } + + private Span GetEntriesFromBuffer(Span data, int index) + { + return MemoryMarshal.Cast(GetBufferFromIndex(data, index).Slice(Unsafe.SizeOf(), GetEntriesSize())); + } + + private Span GetEntriesDetailFromBuffer(Span data, int index) + { + return MemoryMarshal.Cast(GetBufferFromIndex(data, index).Slice(Unsafe.SizeOf() + GetEntriesSize(), GetEntriesDetailSize())); + } + + private void SetupNewHeader() + { + _entryIndex = 0; + _entryDetailIndex = 0; + + CurrentHeader.SetEntryCount(0); + CurrentHeader.SetEntryDetailCount(0); + } + + public static uint GetEntryCount(ref AudioRendererConfiguration parameter) + { + return parameter.VoiceCount + parameter.EffectCount + parameter.SubMixBufferCount + parameter.SinkCount + 1; + } + + public int GetEntriesSize() + { + return Unsafe.SizeOf() * _entryCountPerFrame; + } + + public static int GetEntriesDetailSize() + { + return Unsafe.SizeOf() * MaxFrameDetailCount; + } + + public static int GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref AudioRendererConfiguration parameter) + { + return Unsafe.SizeOf() * (int)GetEntryCount(ref parameter) + GetEntriesDetailSize() + Unsafe.SizeOf(); + } + + public override uint CopyHistories(Span performanceOutput) + { + if (performanceOutput.IsEmpty) + { + return 0; + } + + int nextOffset = 0; + + while (_indexHistoryRead != _indexHistoryWrite) + { + if (nextOffset >= performanceOutput.Length) + { + break; + } + + ref THeader inputHeader = ref GetHeaderFromBuffer(_historyBuffer.Span, _indexHistoryRead); + Span inputEntries = GetEntriesFromBuffer(_historyBuffer.Span, _indexHistoryRead); + Span inputEntriesDetail = GetEntriesDetailFromBuffer(_historyBuffer.Span, _indexHistoryRead); + + Span targetSpan = performanceOutput[nextOffset..]; + + // NOTE: We check for the space for two headers for the final blank header. + int requiredSpace = Unsafe.SizeOf() + Unsafe.SizeOf() * inputHeader.GetEntryCount() + + Unsafe.SizeOf() * inputHeader.GetEntryDetailCount() + + Unsafe.SizeOf(); + + if (targetSpan.Length < requiredSpace) + { + break; + } + + ref THeader outputHeader = ref MemoryMarshal.Cast(targetSpan)[0]; + + nextOffset += Unsafe.SizeOf(); + + Span outputEntries = MemoryMarshal.Cast(performanceOutput[nextOffset..]); + + int totalProcessingTime = 0; + + int effectiveEntryCount = 0; + + for (int entryIndex = 0; entryIndex < inputHeader.GetEntryCount(); entryIndex++) + { + ref TEntry input = ref inputEntries[entryIndex]; + + if (input.GetProcessingTime() != 0 || input.GetStartTime() != 0) + { + ref TEntry output = ref outputEntries[effectiveEntryCount++]; + + output = input; + + nextOffset += Unsafe.SizeOf(); + + totalProcessingTime += input.GetProcessingTime(); + } + } + + Span outputEntriesDetail = MemoryMarshal.Cast(performanceOutput[nextOffset..]); + + int effectiveEntryDetailCount = 0; + + for (int entryDetailIndex = 0; entryDetailIndex < inputHeader.GetEntryDetailCount(); entryDetailIndex++) + { + ref TEntryDetail input = ref inputEntriesDetail[entryDetailIndex]; + + if (input.GetProcessingTime() != 0 || input.GetStartTime() != 0) + { + ref TEntryDetail output = ref outputEntriesDetail[effectiveEntryDetailCount++]; + + output = input; + + nextOffset += Unsafe.SizeOf(); + } + } + + outputHeader = inputHeader; + outputHeader.SetMagic(MagicPerformanceBuffer); + outputHeader.SetTotalProcessingTime(totalProcessingTime); + outputHeader.SetNextOffset(nextOffset); + outputHeader.SetEntryCount(effectiveEntryCount); + outputHeader.SetEntryDetailCount(effectiveEntryDetailCount); + + _indexHistoryRead = (_indexHistoryRead + 1) % _availableFrameCount; + } + + if (nextOffset < performanceOutput.Length && (performanceOutput.Length - nextOffset) >= Unsafe.SizeOf()) + { + ref THeader outputHeader = ref MemoryMarshal.Cast(performanceOutput[nextOffset..])[0]; + + outputHeader = default; + } + + return (uint)nextOffset; + } + + public override bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceEntryType entryType, int nodeId) + { + performanceEntry = new PerformanceEntryAddresses + { + BaseMemory = SpanMemoryManager.Cast(CurrentBuffer), + EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(), + }; + + uint baseEntryOffset = (uint)(Unsafe.SizeOf() + Unsafe.SizeOf() * _entryIndex); + + ref TEntry entry = ref Entries[_entryIndex]; + + performanceEntry.StartTimeOffset = baseEntryOffset + (uint)entry.GetStartTimeOffset(); + performanceEntry.ProcessingTimeOffset = baseEntryOffset + (uint)entry.GetProcessingTimeOffset(); + + entry = default; + entry.SetEntryType(entryType); + entry.SetNodeId(nodeId); + + _entryIndex++; + + return true; + } + + public override bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceDetailType detailType, PerformanceEntryType entryType, int nodeId) + { + performanceEntry = null; + + if (_entryDetailIndex >= MaxFrameDetailCount) + { + return false; + } + + performanceEntry = new PerformanceEntryAddresses + { + BaseMemory = SpanMemoryManager.Cast(CurrentBuffer), + EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(), + }; + + uint baseEntryOffset = (uint)(Unsafe.SizeOf() + GetEntriesSize() + Unsafe.SizeOf() * _entryDetailIndex); + + ref TEntryDetail entryDetail = ref EntriesDetail[_entryDetailIndex]; + + performanceEntry.StartTimeOffset = baseEntryOffset + (uint)entryDetail.GetStartTimeOffset(); + performanceEntry.ProcessingTimeOffset = baseEntryOffset + (uint)entryDetail.GetProcessingTimeOffset(); + + entryDetail = default; + entryDetail.SetDetailType(detailType); + entryDetail.SetEntryType(entryType); + entryDetail.SetNodeId(nodeId); + + _entryDetailIndex++; + + return true; + } + + public override bool IsTargetNodeId(int target) + { + return _detailTarget == target; + } + + public override void SetTargetNodeId(int target) + { + _detailTarget = target; + } + + public override void TapFrame(bool dspRunningBehind, uint voiceDropCount, ulong startRenderingTicks) + { + if (_availableFrameCount > 0) + { + int targetIndexForHistory = _indexHistoryWrite; + + _indexHistoryWrite = (_indexHistoryWrite + 1) % _availableFrameCount; + + ref THeader targetHeader = ref GetHeaderFromBuffer(_historyBuffer.Span, targetIndexForHistory); + + CurrentBuffer.Span.CopyTo(GetBufferFromIndex(_historyBuffer.Span, targetIndexForHistory)); + + uint targetHistoryFrameIndex = _historyFrameIndex; + + if (_historyFrameIndex == uint.MaxValue) + { + _historyFrameIndex = 0; + } + else + { + _historyFrameIndex++; + } + + targetHeader.SetDspRunningBehind(dspRunningBehind); + targetHeader.SetVoiceDropCount(voiceDropCount); + targetHeader.SetStartRenderingTicks(startRenderingTicks); + targetHeader.SetIndex(targetHistoryFrameIndex); + + // Finally setup the new header + SetupNewHeader(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs b/src/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs new file mode 100644 index 00000000..09085001 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs @@ -0,0 +1,48 @@ +using Ryujinx.Audio.Renderer.Server.Upsampler; +using System; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// Represents a lite version of used by the + /// + /// + /// This also allows to reduce dependencies on the for unit testing. + /// + public sealed class RendererSystemContext + { + /// + /// The session id of the current renderer. + /// + public int SessionId; + + /// + /// The target channel count for sink. + /// + /// See for usage. + public uint ChannelCount; + + /// + /// The total count of mix buffer. + /// + public uint MixBufferCount; + + /// + /// Instance of the used to derive bug fixes and features of the current audio renderer revision. + /// + public BehaviourContext BehaviourContext; + + /// + /// Instance of the used for upsampling (see ) + /// + public UpsamplerManager UpsamplerManager; + + /// + /// The memory to use for depop processing. + /// + /// + /// See and + /// + public Memory DepopBuffer; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs new file mode 100644 index 00000000..8c65e09b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs @@ -0,0 +1,102 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Base class used for server information of a sink. + /// + public class BaseSink + { + /// + /// The type of this . + /// + public SinkType Type; + + /// + /// Set to true if the sink is used. + /// + public bool IsUsed; + + /// + /// Set to true if the sink need to be skipped because of invalid state. + /// + public bool ShouldSkip; + + /// + /// The node id of the sink. + /// + public int NodeId; + + /// + /// Create a new . + /// + public BaseSink() + { + CleanUp(); + } + + /// + /// Clean up the internal state of the . + /// + public virtual void CleanUp() + { + Type = TargetSinkType; + IsUsed = false; + ShouldSkip = false; + } + + /// + /// The target handled by this . + /// + public virtual SinkType TargetSinkType => SinkType.Invalid; + + /// + /// Check if the sent by the user match the internal . + /// + /// The user parameter. + /// Return true, if the sent by the user match the internal . + public bool IsTypeValid(in SinkInParameter parameter) + { + return parameter.Type == TargetSinkType; + } + + /// + /// Update the state during command generation. + /// + public virtual void UpdateForCommandGeneration() + { + Debug.Assert(Type == TargetSinkType); + } + + /// + /// Update the internal common parameters from user parameter. + /// + /// The user parameter. + protected void UpdateStandardParameter(in SinkInParameter parameter) + { + if (IsUsed != parameter.IsUsed) + { + IsUsed = parameter.IsUsed; + NodeId = parameter.NodeId; + } + } + + /// + /// Update the internal state from user parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The user output status. + /// The mapper to use. + public virtual void Update(out ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(in parameter)); + + errorInfo = new ErrorInfo(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs new file mode 100644 index 00000000..f2751cf2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs @@ -0,0 +1,109 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Sink; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Server information for a circular buffer sink. + /// + public class CircularBufferSink : BaseSink + { + /// + /// The circular buffer parameter. + /// + public CircularBufferParameter Parameter; + + /// + /// The last written data offset on the circular buffer. + /// + private uint _lastWrittenOffset; + + /// + /// THe previous written offset of the circular buffer. + /// + private uint _oldWrittenOffset; + + /// + /// The current offset to write data on the circular buffer. + /// + public uint CurrentWriteOffset { get; private set; } + + /// + /// The of the circular buffer. + /// + public AddressInfo CircularBufferAddressInfo; + + public CircularBufferSink() + { + CircularBufferAddressInfo = AddressInfo.Create(); + } + + public override SinkType TargetSinkType => SinkType.CircularBuffer; + + public override void Update(out BehaviourParameter.ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + { + errorInfo = new BehaviourParameter.ErrorInfo(); + outStatus = new SinkOutStatus(); + + Debug.Assert(IsTypeValid(in parameter)); + + ref CircularBufferParameter inputDeviceParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + if (parameter.IsUsed != IsUsed || ShouldSkip) + { + UpdateStandardParameter(in parameter); + + if (parameter.IsUsed) + { + Debug.Assert(CircularBufferAddressInfo.CpuAddress == 0); + Debug.Assert(CircularBufferAddressInfo.GetReference(false) == 0); + + ShouldSkip = !mapper.TryAttachBuffer(out errorInfo, ref CircularBufferAddressInfo, inputDeviceParameter.BufferAddress, inputDeviceParameter.BufferSize); + } + else + { + Debug.Assert(CircularBufferAddressInfo.CpuAddress != 0); + Debug.Assert(CircularBufferAddressInfo.GetReference(false) != 0); + } + + Parameter = inputDeviceParameter; + } + + outStatus.LastWrittenOffset = _lastWrittenOffset; + } + + public override void UpdateForCommandGeneration() + { + Debug.Assert(Type == TargetSinkType); + + if (IsUsed) + { + uint frameSize = Constants.TargetSampleSize * Parameter.SampleCount * Parameter.InputCount; + + _lastWrittenOffset = _oldWrittenOffset; + + _oldWrittenOffset = CurrentWriteOffset; + + CurrentWriteOffset += frameSize; + + if (Parameter.BufferSize > 0) + { + CurrentWriteOffset %= Parameter.BufferSize; + } + } + } + + public override void CleanUp() + { + CircularBufferAddressInfo = AddressInfo.Create(); + _lastWrittenOffset = 0; + _oldWrittenOffset = 0; + CurrentWriteOffset = 0; + base.CleanUp(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs new file mode 100644 index 00000000..afe2d4b1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs @@ -0,0 +1,75 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Sink; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Audio.Renderer.Server.Upsampler; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Server information for a device sink. + /// + public class DeviceSink : BaseSink + { + /// + /// The downmix coefficients. + /// + public float[] DownMixCoefficients; + + /// + /// The device parameters. + /// + public DeviceParameter Parameter; + + /// + /// The upsampler instance used by this sink. + /// + /// Null if no upsampling is needed. + public UpsamplerState UpsamplerState; + + /// + /// Create a new . + /// + public DeviceSink() + { + DownMixCoefficients = new float[4]; + } + + public override void CleanUp() + { + UpsamplerState?.Release(); + + UpsamplerState = null; + + base.CleanUp(); + } + + public override SinkType TargetSinkType => SinkType.Device; + + public override void Update(out BehaviourParameter.ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(in parameter)); + + ref DeviceParameter inputDeviceParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + if (parameter.IsUsed != IsUsed) + { + UpdateStandardParameter(in parameter); + Parameter = inputDeviceParameter; + } + else + { + Parameter.DownMixParameterEnabled = inputDeviceParameter.DownMixParameterEnabled; + inputDeviceParameter.DownMixParameter.AsSpan().CopyTo(Parameter.DownMixParameter.AsSpan()); + } + + Parameter.DownMixParameter.AsSpan().CopyTo(DownMixCoefficients.AsSpan()); + + errorInfo = new BehaviourParameter.ErrorInfo(); + outStatus = new SinkOutStatus(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs new file mode 100644 index 00000000..951984d8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs @@ -0,0 +1,56 @@ +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Sink context. + /// + public class SinkContext + { + /// + /// Storage for . + /// + private BaseSink[] _sinks; + + /// + /// The total sink count. + /// + private uint _sinkCount; + + /// + /// Initialize the . + /// + /// The total sink count. + public void Initialize(uint sinksCount) + { + _sinkCount = sinksCount; + _sinks = new BaseSink[_sinkCount]; + + for (int i = 0; i < _sinkCount; i++) + { + _sinks[i] = new BaseSink(); + } + } + + /// + /// Get the total sink count. + /// + /// The total sink count. + public uint GetCount() + { + return _sinkCount; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref BaseSink GetSink(int id) + { + Debug.Assert(id >= 0 && id < _sinkCount); + + return ref _sinks[id]; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs new file mode 100644 index 00000000..a7b82a6b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs @@ -0,0 +1,426 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using Ryujinx.Common.Extensions; +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Splitter context. + /// + public class SplitterContext + { + /// + /// Amount of biquad filter states per splitter destination. + /// + public const int BqfStatesPerDestination = 4; + + /// + /// Storage for . + /// + private Memory _splitters; + + /// + /// Storage for . + /// + private Memory _splitterDestinationsV1; + + /// + /// Storage for . + /// + private Memory _splitterDestinationsV2; + + /// + /// Splitter biquad filtering states. + /// + private Memory _splitterBqfStates; + + /// + /// Version of the splitter context that is being used, currently can be 1 or 2. + /// + public int Version { get; private set; } + + /// + /// If set to true, trust the user destination count in . + /// + public bool IsBugFixed { get; private set; } + + /// + /// Initialize . + /// + /// The behaviour context. + /// The audio renderer configuration. + /// The . + /// Memory to store the biquad filtering state for splitters during processing. + /// Return true if the initialization was successful. + public bool Initialize( + ref BehaviourContext behaviourContext, + ref AudioRendererConfiguration parameter, + WorkBufferAllocator workBufferAllocator, + Memory splitterBqfStates) + { + if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0) + { + Setup(Memory.Empty, Memory.Empty, Memory.Empty, false); + + return true; + } + + Memory splitters = workBufferAllocator.Allocate(parameter.SplitterCount, SplitterState.Alignment); + + if (splitters.IsEmpty) + { + return false; + } + + int splitterId = 0; + + foreach (ref SplitterState splitter in splitters.Span) + { + splitter = new SplitterState(splitterId++); + } + + Memory splitterDestinationsV1 = Memory.Empty; + Memory splitterDestinationsV2 = Memory.Empty; + + if (!behaviourContext.IsBiquadFilterParameterForSplitterEnabled()) + { + Version = 1; + + splitterDestinationsV1 = workBufferAllocator.Allocate(parameter.SplitterDestinationCount, + SplitterDestinationVersion1.Alignment); + + if (splitterDestinationsV1.IsEmpty) + { + return false; + } + + int splitterDestinationId = 0; + foreach (ref SplitterDestinationVersion1 data in splitterDestinationsV1.Span) + { + data = new SplitterDestinationVersion1(splitterDestinationId++); + } + } + else + { + Version = 2; + + splitterDestinationsV2 = workBufferAllocator.Allocate(parameter.SplitterDestinationCount, + SplitterDestinationVersion2.Alignment); + + if (splitterDestinationsV2.IsEmpty) + { + return false; + } + + int splitterDestinationId = 0; + foreach (ref SplitterDestinationVersion2 data in splitterDestinationsV2.Span) + { + data = new SplitterDestinationVersion2(splitterDestinationId++); + } + + if (parameter.SplitterDestinationCount > 0) + { + // Official code stores it in the SplitterDestinationVersion2 struct, + // but we don't to avoid using unsafe code. + + splitterBqfStates.Span.Clear(); + _splitterBqfStates = splitterBqfStates; + } + else + { + _splitterBqfStates = Memory.Empty; + } + } + + SplitterState.InitializeSplitters(splitters.Span); + + Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed()); + + return true; + } + + /// + /// Get the work buffer size while adding the size needed for splitter to operate. + /// + /// The current size. + /// The behaviour context. + /// The renderer configuration. + /// Return the new size taking splitter into account. + public static ulong GetWorkBufferSize(ulong size, ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter) + { + if (behaviourContext.IsSplitterSupported()) + { + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterCount, SplitterState.Alignment); + + if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled()) + { + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestinationVersion2.Alignment); + } + else + { + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestinationVersion1.Alignment); + } + + if (behaviourContext.IsSplitterBugFixed()) + { + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, 0x10); + } + + return size; + } + + return size; + } + + /// + /// Setup the instance. + /// + /// The storage. + /// The storage. + /// The storage. + /// If set to true, trust the user destination count in . + private void Setup( + Memory splitters, + Memory splitterDestinationsV1, + Memory splitterDestinationsV2, + bool isBugFixed) + { + _splitters = splitters; + _splitterDestinationsV1 = splitterDestinationsV1; + _splitterDestinationsV2 = splitterDestinationsV2; + IsBugFixed = isBugFixed; + } + + /// + /// Clear the new connection flag. + /// + private void ClearAllNewConnectionFlag() + { + foreach (ref SplitterState splitter in _splitters.Span) + { + splitter.ClearNewConnectionFlag(); + } + } + + /// + /// Get the destination count using the count of splitter. + /// + /// The destination count using the count of splitter. + public int GetDestinationCountPerStateForCompatibility() + { + if (_splitters.IsEmpty) + { + return 0; + } + + int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length; + + return length / _splitters.Length; + } + + /// + /// Update one or multiple from user parameters. + /// + /// The splitter header. + /// The raw data after the splitter header. + private void UpdateState(in SplitterInParameterHeader inputHeader, ref SequenceReader input) + { + for (int i = 0; i < inputHeader.SplitterCount; i++) + { + ref readonly SplitterInParameter parameter = ref input.GetRefOrRefToCopy(out _); + + Debug.Assert(parameter.IsMagicValid()); + + if (parameter.IsMagicValid()) + { + if (parameter.Id >= 0 && parameter.Id < _splitters.Length) + { + ref SplitterState splitter = ref GetState(parameter.Id); + + splitter.Update(this, in parameter, ref input); + } + + // NOTE: there are 12 bytes of unused/unknown data after the destination IDs array. + input.Advance(0xC); + } + else + { + input.Rewind(Unsafe.SizeOf()); + break; + } + } + } + + /// + /// Update one splitter destination data from user parameters. + /// + /// The raw data after the splitter header. + /// True if the update was successful, false otherwise + private bool UpdateData(ref SequenceReader input) where T : unmanaged, ISplitterDestinationInParameter + { + ref readonly T parameter = ref input.GetRefOrRefToCopy(out _); + + Debug.Assert(parameter.IsMagicValid()); + + if (parameter.IsMagicValid()) + { + int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length; + + if (parameter.Id >= 0 && parameter.Id < length) + { + SplitterDestination destination = GetDestination(parameter.Id); + + destination.Update(parameter); + } + + return true; + } + else + { + input.Rewind(Unsafe.SizeOf()); + + return false; + } + } + + /// + /// Update one or multiple splitter destination data from user parameters. + /// + /// The splitter header. + /// The raw data after the splitter header. + private void UpdateData(in SplitterInParameterHeader inputHeader, ref SequenceReader input) + { + for (int i = 0; i < inputHeader.SplitterDestinationCount; i++) + { + if (Version == 1) + { + if (!UpdateData(ref input)) + { + break; + } + } + else if (Version == 2) + { + if (!UpdateData(ref input)) + { + break; + } + } + else + { + Debug.Fail($"Invalid splitter context version {Version}."); + } + } + } + + /// + /// Update splitter from user parameters. + /// + /// The input raw user data. + /// Return true if the update was successful. + public bool Update(ref SequenceReader input) + { + if (!UsingSplitter()) + { + return true; + } + + ref readonly SplitterInParameterHeader header = ref input.GetRefOrRefToCopy(out _); + + if (header.IsMagicValid()) + { + ClearAllNewConnectionFlag(); + + UpdateState(in header, ref input); + UpdateData(in header, ref input); + + input.SetConsumed(BitUtils.AlignUp(input.Consumed, 0x10)); + + return true; + } + else + { + input.Rewind(Unsafe.SizeOf()); + + return false; + } + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref SplitterState GetState(int id) + { + return ref SpanIOHelper.GetFromMemory(_splitters, id, (uint)_splitters.Length); + } + + /// + /// Get a reference to the splitter destination data at the given . + /// + /// The index to use. + /// A reference to the splitter destination data at the given . + public SplitterDestination GetDestination(int id) + { + if (_splitterDestinationsV2.IsEmpty) + { + return new SplitterDestination(ref SpanIOHelper.GetFromMemory(_splitterDestinationsV1, id, (uint)_splitterDestinationsV1.Length)); + } + else + { + return new SplitterDestination(ref SpanIOHelper.GetFromMemory(_splitterDestinationsV2, id, (uint)_splitterDestinationsV2.Length)); + } + } + + /// + /// Get a in the at and pass to . + /// + /// The index to use to get the . + /// The index of the . + /// A . + public SplitterDestination GetDestination(int id, int destinationId) + { + ref SplitterState splitter = ref GetState(id); + + return splitter.GetData(destinationId); + } + + /// + /// Gets the biquad filter state for a given splitter destination. + /// + /// The splitter destination. + /// Biquad filter state for the specified destination. + public Memory GetBiquadFilterState(SplitterDestination destination) + { + return _splitterBqfStates.Slice(destination.Id * BqfStatesPerDestination, BqfStatesPerDestination); + } + + /// + /// Return true if the audio renderer has any splitters. + /// + /// True if the audio renderer has any splitters. + public bool UsingSplitter() + { + return !_splitters.IsEmpty && (!_splitterDestinationsV1.IsEmpty || !_splitterDestinationsV2.IsEmpty); + } + + /// + /// Update the internal state of all splitters. + /// + public void UpdateInternalState() + { + foreach (ref SplitterState splitter in _splitters.Span) + { + splitter.UpdateInternalState(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs new file mode 100644 index 00000000..36dfa5e4 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs @@ -0,0 +1,368 @@ +using Ryujinx.Audio.Renderer.Parameter; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Server state for a splitter destination. + /// + public ref struct SplitterDestination + { + private ref SplitterDestinationVersion1 _v1; + private ref SplitterDestinationVersion2 _v2; + + /// + /// Checks if the splitter destination data reference is null. + /// + public bool IsNull => Unsafe.IsNullRef(ref _v1) && Unsafe.IsNullRef(ref _v2); + + /// + /// The splitter unique id. + /// + public int Id + { + get + { + if (Unsafe.IsNullRef(ref _v2)) + { + if (Unsafe.IsNullRef(ref _v1)) + { + return 0; + } + else + { + return _v1.Id; + } + } + else + { + return _v2.Id; + } + } + } + + /// + /// The mix to output the result of the splitter. + /// + public int DestinationId + { + get + { + if (Unsafe.IsNullRef(ref _v2)) + { + if (Unsafe.IsNullRef(ref _v1)) + { + return 0; + } + else + { + return _v1.DestinationId; + } + } + else + { + return _v2.DestinationId; + } + } + } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume + { + get + { + if (Unsafe.IsNullRef(ref _v2)) + { + if (Unsafe.IsNullRef(ref _v1)) + { + return Span.Empty; + } + else + { + return _v1.MixBufferVolume; + } + } + else + { + return _v2.MixBufferVolume; + } + } + } + + /// + /// Previous mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span PreviousMixBufferVolume + { + get + { + if (Unsafe.IsNullRef(ref _v2)) + { + if (Unsafe.IsNullRef(ref _v1)) + { + return Span.Empty; + } + else + { + return _v1.PreviousMixBufferVolume; + } + } + else + { + return _v2.PreviousMixBufferVolume; + } + } + } + + /// + /// Get the of the next element or null if not present. + /// + public readonly SplitterDestination Next + { + get + { + unsafe + { + if (Unsafe.IsNullRef(ref _v2)) + { + if (Unsafe.IsNullRef(ref _v1)) + { + return new SplitterDestination(); + } + else + { + return new SplitterDestination(ref _v1.Next); + } + } + else + { + return new SplitterDestination(ref _v2.Next); + } + } + } + } + + /// + /// Creates a new splitter destination wrapper for the version 1 splitter destination data. + /// + /// Version 1 splitter destination data + public SplitterDestination(ref SplitterDestinationVersion1 v1) + { + _v1 = ref v1; + _v2 = ref Unsafe.NullRef(); + } + + /// + /// Creates a new splitter destination wrapper for the version 2 splitter destination data. + /// + /// Version 2 splitter destination data + public SplitterDestination(ref SplitterDestinationVersion2 v2) + { + + _v1 = ref Unsafe.NullRef(); + _v2 = ref v2; + } + + /// + /// Creates a new splitter destination wrapper for the splitter destination data. + /// + /// Version 1 splitter destination data + /// Version 2 splitter destination data + public unsafe SplitterDestination(SplitterDestinationVersion1* v1, SplitterDestinationVersion2* v2) + { + _v1 = ref Unsafe.AsRef(v1); + _v2 = ref Unsafe.AsRef(v2); + } + + /// + /// Update the splitter destination data from user parameter. + /// + /// The user parameter. + public void Update(in T parameter) where T : ISplitterDestinationInParameter + { + if (Unsafe.IsNullRef(ref _v2)) + { + _v1.Update(parameter); + } + else + { + _v2.Update(parameter); + } + } + + /// + /// Update the internal state of the instance. + /// + public void UpdateInternalState() + { + if (Unsafe.IsNullRef(ref _v2)) + { + _v1.UpdateInternalState(); + } + else + { + _v2.UpdateInternalState(); + } + } + + /// + /// Set the update internal state marker. + /// + public void MarkAsNeedToUpdateInternalState() + { + if (Unsafe.IsNullRef(ref _v2)) + { + _v1.MarkAsNeedToUpdateInternalState(); + } + else + { + _v2.MarkAsNeedToUpdateInternalState(); + } + } + + /// + /// Return true if the splitter destination is used and has a destination. + /// + /// True if the splitter destination is used and has a destination. + public readonly bool IsConfigured() + { + return Unsafe.IsNullRef(ref _v2) ? _v1.IsConfigured() : _v2.IsConfigured(); + } + + /// + /// Get the volume for a given destination. + /// + /// The destination index to use. + /// The volume for the given destination. + public float GetMixVolume(int destinationIndex) + { + return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolume(destinationIndex) : _v2.GetMixVolume(destinationIndex); + } + + /// + /// Get the previous volume for a given destination. + /// + /// The destination index to use. + /// The volume for the given destination. + public float GetMixVolumePrev(int destinationIndex) + { + return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolumePrev(destinationIndex) : _v2.GetMixVolumePrev(destinationIndex); + } + + /// + /// Clear the volumes. + /// + public void ClearVolumes() + { + if (Unsafe.IsNullRef(ref _v2)) + { + _v1.ClearVolumes(); + } + else + { + _v2.ClearVolumes(); + } + } + + /// + /// Link the next element to the given splitter destination. + /// + /// The given splitter destination to link. + public void Link(SplitterDestination next) + { + if (Unsafe.IsNullRef(ref _v2)) + { + Debug.Assert(!Unsafe.IsNullRef(ref next._v1)); + + _v1.Link(ref next._v1); + } + else + { + Debug.Assert(!Unsafe.IsNullRef(ref next._v2)); + + _v2.Link(ref next._v2); + } + } + + /// + /// Remove the link to the next element. + /// + public void Unlink() + { + if (Unsafe.IsNullRef(ref _v2)) + { + _v1.Unlink(); + } + else + { + _v2.Unlink(); + } + } + + /// + /// Checks if any biquad filter is enabled. + /// + /// True if any biquad filter is enabled. + public bool IsBiquadFilterEnabled() + { + return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabled(); + } + + /// + /// Checks if any biquad filter was previously enabled. + /// + /// True if any biquad filter was previously enabled. + public bool IsBiquadFilterEnabledPrev() + { + return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabledPrev(); + } + + /// + /// Gets the biquad filter parameters. + /// + /// Biquad filter index (0 or 1). + /// Biquad filter parameters. + public ref BiquadFilterParameter GetBiquadFilterParameter(int index) + { + Debug.Assert(!Unsafe.IsNullRef(ref _v2)); + + return ref _v2.GetBiquadFilterParameter(index); + } + + /// + /// Checks if any biquad filter was previously enabled. + /// + /// Biquad filter index (0 or 1). + public void UpdateBiquadFilterEnabledPrev(int index) + { + if (!Unsafe.IsNullRef(ref _v2)) + { + _v2.UpdateBiquadFilterEnabledPrev(index); + } + } + + /// + /// Get the reference for the version 1 splitter destination data, or null if version 2 is being used or the destination is null. + /// + /// Reference for the version 1 splitter destination data. + public ref SplitterDestinationVersion1 GetV1RefOrNull() + { + return ref _v1; + } + + /// + /// Get the reference for the version 2 splitter destination data, or null if version 1 is being used or the destination is null. + /// + /// Reference for the version 2 splitter destination data. + public ref SplitterDestinationVersion2 GetV2RefOrNull() + { + return ref _v2; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs new file mode 100644 index 00000000..5d2b8fb0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs @@ -0,0 +1,206 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Server state for a splitter destination (version 1). + /// + [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)] + public struct SplitterDestinationVersion1 + { + public const int Alignment = 0x10; + + /// + /// The unique id of this . + /// + public int Id; + + /// + /// The mix to output the result of the splitter. + /// + public int DestinationId; + + /// + /// Mix buffer volumes storage. + /// + private MixArray _mix; + private MixArray _previousMix; + + /// + /// Pointer to the next linked element. + /// + private unsafe SplitterDestinationVersion1* _next; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Set to true if the internal state need to be updated. + /// + [MarshalAs(UnmanagedType.I1)] + public bool NeedToUpdateInternalState; + + [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mix); + + /// + /// Previous mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span PreviousMixBufferVolume => SpanHelpers.AsSpan(ref _previousMix); + + /// + /// Get the reference of the next element or null if not present. + /// + public readonly ref SplitterDestinationVersion1 Next + { + get + { + unsafe + { + return ref Unsafe.AsRef(_next); + } + } + } + + /// + /// Create a new . + /// + /// The unique id of this . + public SplitterDestinationVersion1(int id) : this() + { + Id = id; + DestinationId = Constants.UnusedMixId; + + ClearVolumes(); + } + + /// + /// Update the from user parameter. + /// + /// The user parameter. + public void Update(in T parameter) where T : ISplitterDestinationInParameter + { + Debug.Assert(Id == parameter.Id); + + if (parameter.IsMagicValid() && Id == parameter.Id) + { + DestinationId = parameter.DestinationId; + + parameter.MixBufferVolume.CopyTo(MixBufferVolume); + + if (!IsUsed && parameter.IsUsed) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + + NeedToUpdateInternalState = false; + } + + IsUsed = parameter.IsUsed; + } + } + + /// + /// Update the internal state of the instance. + /// + public void UpdateInternalState() + { + if (IsUsed && NeedToUpdateInternalState) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + } + + NeedToUpdateInternalState = false; + } + + /// + /// Set the update internal state marker. + /// + public void MarkAsNeedToUpdateInternalState() + { + NeedToUpdateInternalState = true; + } + + /// + /// Return true if the is used and has a destination. + /// + /// True if the is used and has a destination. + public readonly bool IsConfigured() + { + return IsUsed && DestinationId != Constants.UnusedMixId; + } + + /// + /// Get the volume for a given destination. + /// + /// The destination index to use. + /// The volume for the given destination. + public float GetMixVolume(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + + return MixBufferVolume[destinationIndex]; + } + + /// + /// Get the previous volume for a given destination. + /// + /// The destination index to use. + /// The volume for the given destination. + public float GetMixVolumePrev(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + + return PreviousMixBufferVolume[destinationIndex]; + } + + /// + /// Clear the volumes. + /// + public void ClearVolumes() + { + MixBufferVolume.Clear(); + PreviousMixBufferVolume.Clear(); + } + + /// + /// Link the next element to the given . + /// + /// The given to link. + public void Link(ref SplitterDestinationVersion1 next) + { + unsafe + { + fixed (SplitterDestinationVersion1* nextPtr = &next) + { + _next = nextPtr; + } + } + } + + /// + /// Remove the link to the next element. + /// + public void Unlink() + { + unsafe + { + _next = null; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs new file mode 100644 index 00000000..f9487909 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs @@ -0,0 +1,250 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Server state for a splitter destination (version 2). + /// + [StructLayout(LayoutKind.Sequential, Size = 0x110, Pack = Alignment)] + public struct SplitterDestinationVersion2 + { + public const int Alignment = 0x10; + + /// + /// The unique id of this . + /// + public int Id; + + /// + /// The mix to output the result of the splitter. + /// + public int DestinationId; + + /// + /// Mix buffer volumes storage. + /// + private MixArray _mix; + private MixArray _previousMix; + + /// + /// Pointer to the next linked element. + /// + private unsafe SplitterDestinationVersion2* _next; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Set to true if the internal state need to be updated. + /// + [MarshalAs(UnmanagedType.I1)] + public bool NeedToUpdateInternalState; + + [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mix); + + /// + /// Previous mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span PreviousMixBufferVolume => SpanHelpers.AsSpan(ref _previousMix); + + /// + /// Get the reference of the next element or null if not present. + /// + public readonly ref SplitterDestinationVersion2 Next + { + get + { + unsafe + { + return ref Unsafe.AsRef(_next); + } + } + } + + private Array2 _biquadFilters; + + private Array2 _isPreviousBiquadFilterEnabled; + + /// + /// Create a new . + /// + /// The unique id of this . + public SplitterDestinationVersion2(int id) : this() + { + Id = id; + DestinationId = Constants.UnusedMixId; + + ClearVolumes(); + } + + /// + /// Update the from user parameter. + /// + /// The user parameter. + public void Update(in T parameter) where T : ISplitterDestinationInParameter + { + Debug.Assert(Id == parameter.Id); + + if (parameter.IsMagicValid() && Id == parameter.Id) + { + DestinationId = parameter.DestinationId; + + parameter.MixBufferVolume.CopyTo(MixBufferVolume); + + _biquadFilters = parameter.BiquadFilters; + + if (!IsUsed && parameter.IsUsed) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + + NeedToUpdateInternalState = false; + } + + IsUsed = parameter.IsUsed; + } + } + + /// + /// Update the internal state of the instance. + /// + public void UpdateInternalState() + { + if (IsUsed && NeedToUpdateInternalState) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + } + + NeedToUpdateInternalState = false; + } + + /// + /// Set the update internal state marker. + /// + public void MarkAsNeedToUpdateInternalState() + { + NeedToUpdateInternalState = true; + } + + /// + /// Return true if the is used and has a destination. + /// + /// True if the is used and has a destination. + public readonly bool IsConfigured() + { + return IsUsed && DestinationId != Constants.UnusedMixId; + } + + /// + /// Get the volume for a given destination. + /// + /// The destination index to use. + /// The volume for the given destination. + public float GetMixVolume(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + + return MixBufferVolume[destinationIndex]; + } + + /// + /// Get the previous volume for a given destination. + /// + /// The destination index to use. + /// The volume for the given destination. + public float GetMixVolumePrev(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + + return PreviousMixBufferVolume[destinationIndex]; + } + + /// + /// Clear the volumes. + /// + public void ClearVolumes() + { + MixBufferVolume.Clear(); + PreviousMixBufferVolume.Clear(); + } + + /// + /// Link the next element to the given . + /// + /// The given to link. + public void Link(ref SplitterDestinationVersion2 next) + { + unsafe + { + fixed (SplitterDestinationVersion2* nextPtr = &next) + { + _next = nextPtr; + } + } + } + + /// + /// Remove the link to the next element. + /// + public void Unlink() + { + unsafe + { + _next = null; + } + } + + /// + /// Checks if any biquad filter is enabled. + /// + /// True if any biquad filter is enabled. + public bool IsBiquadFilterEnabled() + { + return _biquadFilters[0].Enable || _biquadFilters[1].Enable; + } + + /// + /// Checks if any biquad filter was previously enabled. + /// + /// True if any biquad filter was previously enabled. + public bool IsBiquadFilterEnabledPrev() + { + return _isPreviousBiquadFilterEnabled[0]; + } + + /// + /// Gets the biquad filter parameters. + /// + /// Biquad filter index (0 or 1). + /// Biquad filter parameters. + public ref BiquadFilterParameter GetBiquadFilterParameter(int index) + { + return ref _biquadFilters[index]; + } + + /// + /// Checks if any biquad filter was previously enabled. + /// + /// Biquad filter index (0 or 1). + public void UpdateBiquadFilterEnabledPrev(int index) + { + _isPreviousBiquadFilterEnabled[index] = _biquadFilters[index].Enable; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs new file mode 100644 index 00000000..3e7dce55 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs @@ -0,0 +1,243 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Extensions; +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Server state for a splitter. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = Alignment)] + public struct SplitterState + { + public const int Alignment = 0x10; + + private delegate void SplitterDestinationAction(SplitterDestination destination, int index); + + /// + /// The unique id of this . + /// + public int Id; + + /// + /// Target sample rate to use on the splitter. + /// + public uint SampleRate; + + /// + /// Count of splitter destinations. + /// + public int DestinationCount; + + /// + /// Set to true if the splitter has a new connection. + /// + [MarshalAs(UnmanagedType.I1)] + public bool HasNewConnection; + + /// + /// Linked list of . + /// + private unsafe SplitterDestinationVersion1* _destinationDataV1; + + /// + /// Linked list of . + /// + private unsafe SplitterDestinationVersion2* _destinationDataV2; + + /// + /// First element of the linked list of splitter destinations data. + /// + public readonly SplitterDestination Destination + { + get + { + unsafe + { + return new SplitterDestination(_destinationDataV1, _destinationDataV2); + } + } + } + + /// + /// Create a new . + /// + /// The unique id of this . + public SplitterState(int id) : this() + { + Id = id; + } + + public readonly SplitterDestination GetData(int index) + { + int i = 0; + + SplitterDestination result = Destination; + + while (i < index) + { + if (result.IsNull) + { + break; + } + + result = result.Next; + i++; + } + + return result; + } + + /// + /// Clear the new connection flag. + /// + public void ClearNewConnectionFlag() + { + HasNewConnection = false; + } + + /// + /// Utility function to apply an action to all . + /// + /// The action to execute on each elements. + private readonly void ForEachDestination(SplitterDestinationAction action) + { + SplitterDestination temp = Destination; + + int i = 0; + + while (true) + { + if (temp.IsNull) + { + break; + } + + SplitterDestination next = temp.Next; + + action(temp, i++); + + temp = next; + } + } + + /// + /// Update the from user parameter. + /// + /// The splitter context. + /// The user parameter. + /// The raw input data after the . + public void Update(SplitterContext context, in SplitterInParameter parameter, ref SequenceReader input) + { + ClearLinks(); + + int destinationCount; + + if (context.IsBugFixed) + { + destinationCount = parameter.DestinationCount; + } + else + { + destinationCount = Math.Min(context.GetDestinationCountPerStateForCompatibility(), parameter.DestinationCount); + } + + if (destinationCount > 0) + { + input.ReadLittleEndian(out int destinationId); + + SplitterDestination destination = context.GetDestination(destinationId); + + SetDestination(destination); + + DestinationCount = destinationCount; + + for (int i = 1; i < destinationCount; i++) + { + input.ReadLittleEndian(out destinationId); + + SplitterDestination nextDestination = context.GetDestination(destinationId); + + destination.Link(nextDestination); + destination = nextDestination; + } + } + + if (destinationCount < parameter.DestinationCount) + { + input.Advance((parameter.DestinationCount - destinationCount) * sizeof(int)); + } + + Debug.Assert(parameter.Id == Id); + + if (parameter.Id == Id) + { + SampleRate = parameter.SampleRate; + HasNewConnection = true; + } + } + + /// + /// Set the head of the linked list of . + /// + /// New destination value. + public void SetDestination(SplitterDestination newValue) + { + unsafe + { + fixed (SplitterDestinationVersion1* newValuePtr = &newValue.GetV1RefOrNull()) + { + _destinationDataV1 = newValuePtr; + } + + fixed (SplitterDestinationVersion2* newValuePtr = &newValue.GetV2RefOrNull()) + { + _destinationDataV2 = newValuePtr; + } + } + } + + /// + /// Update the internal state of this instance. + /// + public readonly void UpdateInternalState() + { + ForEachDestination((destination, _) => destination.UpdateInternalState()); + } + + /// + /// Clear all links from the . + /// + public void ClearLinks() + { + ForEachDestination((destination, _) => destination.Unlink()); + + unsafe + { + _destinationDataV1 = null; + _destinationDataV2 = null; + } + } + + /// + /// Initialize a given . + /// + /// All the to initialize. + public static void InitializeSplitters(Span splitters) + { + foreach (ref SplitterState splitter in splitters) + { + unsafe + { + splitter._destinationDataV1 = null; + splitter._destinationDataV2 = null; + } + + splitter.DestinationCount = 0; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs b/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs new file mode 100644 index 00000000..f8d87f2d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs @@ -0,0 +1,598 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Performance; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Audio.Renderer.Server.Mix; +using Ryujinx.Audio.Renderer.Server.Performance; +using Ryujinx.Audio.Renderer.Server.Sink; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Audio.Renderer.Server.Voice; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common.Extensions; +using Ryujinx.Common.Logging; +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Server +{ + public ref struct StateUpdater + { + private SequenceReader _inputReader; + + private readonly ReadOnlyMemory _outputOrigin; + + private Memory _output; + private readonly uint _processHandle; + private BehaviourContext _behaviourContext; + + private readonly ref readonly UpdateDataHeader _inputHeader; + private readonly Memory _outputHeader; + + private readonly ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0]; + + public StateUpdater(ReadOnlySequence input, Memory output, uint processHandle, BehaviourContext behaviourContext) + { + _inputReader = new SequenceReader(input); + _output = output; + _outputOrigin = _output; + _processHandle = processHandle; + _behaviourContext = behaviourContext; + + _inputHeader = ref _inputReader.GetRefOrRefToCopy(out _); + + _outputHeader = SpanMemoryManager.Cast(_output[..Unsafe.SizeOf()]); + OutputHeader.Initialize(_behaviourContext.UserRevision); + _output = _output[Unsafe.SizeOf()..]; + } + + public ResultCode UpdateBehaviourContext() + { + ref readonly BehaviourParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); + + if (!BehaviourContext.CheckValidRevision(parameter.UserRevision) || parameter.UserRevision != _behaviourContext.UserRevision) + { + return ResultCode.InvalidUpdateInfo; + } + + _behaviourContext.ClearError(); + _behaviourContext.UpdateFlags(parameter.Flags); + + if (_inputHeader.BehaviourSize != Unsafe.SizeOf()) + { + return ResultCode.InvalidUpdateInfo; + } + + return ResultCode.Success; + } + + public ResultCode UpdateMemoryPools(Span memoryPools) + { + PoolMapper mapper = new(_processHandle, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + if (memoryPools.Length * Unsafe.SizeOf() != _inputHeader.MemoryPoolsSize) + { + return ResultCode.InvalidUpdateInfo; + } + + foreach (ref MemoryPoolState memoryPool in memoryPools) + { + ref readonly MemoryPoolInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); + + ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, in parameter, ref outStatus); + + if (updateResult != PoolMapper.UpdateResult.Success && + updateResult != PoolMapper.UpdateResult.MapError && + updateResult != PoolMapper.UpdateResult.UnmapError) + { + if (updateResult != PoolMapper.UpdateResult.InvalidParameter) + { + throw new InvalidOperationException($"{updateResult}"); + } + + return ResultCode.InvalidUpdateInfo; + } + } + + OutputHeader.MemoryPoolsSize = (uint)(Unsafe.SizeOf() * memoryPools.Length); + OutputHeader.TotalSize += OutputHeader.MemoryPoolsSize; + + return ResultCode.Success; + } + + public ResultCode UpdateVoiceChannelResources(VoiceContext context) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.VoiceResourcesSize) + { + return ResultCode.InvalidUpdateInfo; + } + + for (int i = 0; i < context.GetCount(); i++) + { + ref readonly VoiceChannelResourceInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); + + ref VoiceChannelResource resource = ref context.GetChannelResource(i); + + resource.Id = parameter.Id; + parameter.Mix.AsSpan().CopyTo(resource.Mix.AsSpan()); + resource.IsUsed = parameter.IsUsed; + } + + return ResultCode.Success; + } + + public ResultCode UpdateVoices(VoiceContext context, PoolMapper mapper) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.VoicesSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + long initialInputConsumed = _inputReader.Consumed; + + // First make everything not in use. + for (int i = 0; i < context.GetCount(); i++) + { + ref VoiceState state = ref context.GetState(i); + + state.InUse = false; + } + + Memory[] voiceUpdateStatesArray = ArrayPool>.Shared.Rent(Constants.VoiceChannelCountMax); + + Span> voiceUpdateStates = voiceUpdateStatesArray.AsSpan(0, Constants.VoiceChannelCountMax); + + // Start processing + for (int i = 0; i < context.GetCount(); i++) + { + ref readonly VoiceInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); + + voiceUpdateStates.Fill(Memory.Empty); + + ref VoiceOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + if (parameter.InUse) + { + ref VoiceState currentVoiceState = ref context.GetState(i); + + for (int channelResourceIndex = 0; channelResourceIndex < parameter.ChannelCount; channelResourceIndex++) + { + int channelId = parameter.ChannelResourceIds[channelResourceIndex]; + + Debug.Assert(channelId >= 0 && channelId < context.GetCount()); + + voiceUpdateStates[channelResourceIndex] = context.GetUpdateStateForCpu(channelId); + } + + if (parameter.IsNew) + { + currentVoiceState.Initialize(); + } + + currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, in parameter, mapper, ref _behaviourContext); + + if (updateParameterError.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref updateParameterError); + } + + currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, in parameter, voiceUpdateStates, mapper, ref _behaviourContext); + + foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan()) + { + if (errorInfo.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref errorInfo); + } + } + + currentVoiceState.WriteOutStatus(ref outStatus, in parameter, voiceUpdateStates); + } + } + + ArrayPool>.Shared.Return(voiceUpdateStatesArray); + + int currentOutputSize = _output.Length; + + OutputHeader.VoicesSize = (uint)(Unsafe.SizeOf() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.VoicesSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize); + + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.VoicesSize); + + return ResultCode.Success; + } + + private static void ResetEffect(ref BaseEffect effect, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + effect.ForceUnmapBuffers(mapper); + + effect = parameter.Type switch + { + EffectType.Invalid => new BaseEffect(), + EffectType.BufferMix => new BufferMixEffect(), + EffectType.AuxiliaryBuffer => new AuxiliaryBufferEffect(), + EffectType.Delay => new DelayEffect(), + EffectType.Reverb => new ReverbEffect(), + EffectType.Reverb3d => new Reverb3dEffect(), + EffectType.BiquadFilter => new BiquadFilterEffect(), + EffectType.Limiter => new LimiterEffect(), + EffectType.CaptureBuffer => new CaptureBufferEffect(), + EffectType.Compressor => new CompressorEffect(), + _ => throw new NotImplementedException($"EffectType {parameter.Type} not implemented!"), + }; + } + + public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) + { + if (_behaviourContext.IsEffectInfoVersion2Supported()) + { + return UpdateEffectsVersion2(context, isAudioRendererActive, mapper); + } + + return UpdateEffectsVersion1(context, isAudioRendererActive, mapper); + } + + public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + long initialInputConsumed = _inputReader.Consumed; + + for (int i = 0; i < context.GetCount(); i++) + { + ref readonly EffectInParameterVersion2 parameter = ref _inputReader.GetRefOrRefToCopy(out _); + + ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + ref BaseEffect effect = ref context.GetEffect(i); + + if (!effect.IsTypeValid(in parameter)) + { + ResetEffect(ref effect, in parameter, mapper); + } + + effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper); + + if (updateErrorInfo.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref updateErrorInfo); + } + + effect.StoreStatus(ref outStatus, isAudioRendererActive); + + if (parameter.IsNew) + { + effect.InitializeResultState(ref context.GetDspState(i)); + effect.InitializeResultState(ref context.GetState(i)); + } + + effect.UpdateResultState(ref outStatus.ResultState, ref context.GetState(i)); + } + + int currentOutputSize = _output.Length; + + OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.EffectsSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); + + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize); + + return ResultCode.Success; + } + + public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + long initialInputConsumed = _inputReader.Consumed; + + for (int i = 0; i < context.GetCount(); i++) + { + ref readonly EffectInParameterVersion1 parameter = ref _inputReader.GetRefOrRefToCopy(out _); + + ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + ref BaseEffect effect = ref context.GetEffect(i); + + if (!effect.IsTypeValid(in parameter)) + { + ResetEffect(ref effect, in parameter, mapper); + } + + effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper); + + if (updateErrorInfo.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref updateErrorInfo); + } + + effect.StoreStatus(ref outStatus, isAudioRendererActive); + } + + int currentOutputSize = _output.Length; + + OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.EffectsSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); + + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize); + + return ResultCode.Success; + } + + public ResultCode UpdateSplitter(SplitterContext context) + { + if (context.Update(ref _inputReader)) + { + return ResultCode.Success; + } + + return ResultCode.InvalidUpdateInfo; + } + + private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, SequenceReader parameters) + { + uint maxMixStateCount = mixContext.GetCount(); + uint totalRequiredMixBufferCount = 0; + + for (int i = 0; i < inputMixCount; i++) + { + ref readonly MixParameter parameter = ref parameters.GetRefOrRefToCopy(out _); + + if (parameter.IsUsed) + { + if (parameter.DestinationMixId != Constants.UnusedMixId && + parameter.DestinationMixId > maxMixStateCount && + parameter.MixId != Constants.FinalMixId) + { + return true; + } + + totalRequiredMixBufferCount += parameter.BufferCount; + } + } + + return totalRequiredMixBufferCount > mixBufferCount; + } + + public ResultCode UpdateMixes(MixContext mixContext, uint mixBufferCount, EffectContext effectContext, SplitterContext splitterContext) + { + uint mixCount; + uint inputMixSize; + uint inputSize = 0; + + if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) + { + ref readonly MixInParameterDirtyOnlyUpdate parameter = ref _inputReader.GetRefOrRefToCopy(out _); + + mixCount = parameter.MixCount; + + inputSize += (uint)Unsafe.SizeOf(); + } + else + { + mixCount = mixContext.GetCount(); + } + + inputMixSize = mixCount * (uint)Unsafe.SizeOf(); + + inputSize += inputMixSize; + + if (inputSize != _inputHeader.MixesSize) + { + return ResultCode.InvalidUpdateInfo; + } + + long initialInputConsumed = _inputReader.Consumed; + + int parameterCount = (int)inputMixSize / Unsafe.SizeOf(); + + if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, _inputReader)) + { + return ResultCode.InvalidUpdateInfo; + } + + bool isMixContextDirty = false; + + for (int i = 0; i < parameterCount; i++) + { + ref readonly MixParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); + + int mixId = i; + + if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) + { + mixId = parameter.MixId; + } + + ref MixState mix = ref mixContext.GetState(mixId); + + if (parameter.IsUsed != mix.IsUsed) + { + mix.IsUsed = parameter.IsUsed; + + if (parameter.IsUsed) + { + mix.ClearEffectProcessingOrder(); + } + + isMixContextDirty = true; + } + + if (mix.IsUsed) + { + isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, in parameter, effectContext, splitterContext, _behaviourContext); + } + } + + if (isMixContextDirty) + { + if (_behaviourContext.IsSplitterSupported() && splitterContext.UsingSplitter()) + { + if (!mixContext.Sort(splitterContext)) + { + return ResultCode.InvalidMixSorting; + } + } + else + { + mixContext.Sort(); + } + } + + _inputReader.SetConsumed(initialInputConsumed + inputMixSize); + + return ResultCode.Success; + } + + private static void ResetSink(ref BaseSink sink, in SinkInParameter parameter) + { + sink.CleanUp(); + + sink = parameter.Type switch + { + SinkType.Invalid => new BaseSink(), + SinkType.CircularBuffer => new CircularBufferSink(), + SinkType.Device => new DeviceSink(), + _ => throw new NotImplementedException($"SinkType {parameter.Type} not implemented!"), + }; + } + + public ResultCode UpdateSinks(SinkContext context, PoolMapper mapper) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.SinksSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + long initialInputConsumed = _inputReader.Consumed; + + for (int i = 0; i < context.GetCount(); i++) + { + ref readonly SinkInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); + ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + ref BaseSink sink = ref context.GetSink(i); + + if (!sink.IsTypeValid(in parameter)) + { + ResetSink(ref sink, in parameter); + } + + sink.Update(out ErrorInfo updateErrorInfo, in parameter, ref outStatus, mapper); + + if (updateErrorInfo.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref updateErrorInfo); + } + } + + int currentOutputSize = _output.Length; + + OutputHeader.SinksSize = (uint)(Unsafe.SizeOf() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.SinksSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize); + + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.SinksSize); + + return ResultCode.Success; + } + + public ResultCode UpdatePerformanceBuffer(PerformanceManager manager, Span performanceOutput) + { + if (Unsafe.SizeOf() != _inputHeader.PerformanceBufferSize) + { + return ResultCode.InvalidUpdateInfo; + } + + ref readonly PerformanceInParameter parameter = ref _inputReader.GetRefOrRefToCopy(out _); + + ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + if (manager != null) + { + outStatus.HistorySize = manager.CopyHistories(performanceOutput); + + manager.SetTargetNodeId(parameter.TargetNodeId); + } + else + { + outStatus.HistorySize = 0; + } + + OutputHeader.PerformanceBufferSize = (uint)Unsafe.SizeOf(); + OutputHeader.TotalSize += OutputHeader.PerformanceBufferSize; + + return ResultCode.Success; + } + + public ResultCode UpdateErrorInfo() + { + ref BehaviourErrorInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + _behaviourContext.CopyErrorInfo(outStatus.ErrorInfos.AsSpan(), out outStatus.ErrorInfosCount); + + OutputHeader.BehaviourSize = (uint)Unsafe.SizeOf(); + OutputHeader.TotalSize += OutputHeader.BehaviourSize; + + return ResultCode.Success; + } + + public ResultCode UpdateRendererInfo(ulong elapsedFrameCount) + { + ref RendererInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + outStatus.ElapsedFrameCount = elapsedFrameCount; + + OutputHeader.RenderInfoSize = (uint)Unsafe.SizeOf(); + OutputHeader.TotalSize += OutputHeader.RenderInfoSize; + + return ResultCode.Success; + } + + public readonly ResultCode CheckConsumedSize() + { + long consumedInputSize = _inputReader.Consumed; + int consumedOutputSize = _outputOrigin.Length - _output.Length; + + if (consumedInputSize != _inputHeader.TotalSize) + { + Logger.Error?.Print(LogClass.AudioRenderer, $"Consumed input size mismatch (got {consumedInputSize} expected {_inputHeader.TotalSize})"); + + return ResultCode.InvalidUpdateInfo; + } + + if (consumedOutputSize != OutputHeader.TotalSize) + { + Logger.Error?.Print(LogClass.AudioRenderer, $"Consumed output size mismatch (got {consumedOutputSize} expected {OutputHeader.TotalSize})"); + + return ResultCode.InvalidUpdateInfo; + } + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs b/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs new file mode 100644 index 00000000..0db61c5e --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.Audio.Renderer.Server.Types +{ + /// + /// The execution mode of an . + /// + public enum AudioRendererExecutionMode : byte + { + /// + /// Automatically send commands to the DSP at a fixed rate (see + /// + Auto, + + /// + /// Audio renderer operation needs to be done manually via ExecuteAudioRenderer. + /// + /// This is not supported on the DSP and is as such stubbed. + Manual, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs b/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs new file mode 100644 index 00000000..fd9e231c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Audio.Renderer.Server.Types +{ + /// + /// The rendering device of an . + /// + public enum AudioRendererRenderingDevice : byte + { + /// + /// Rendering is performed on the DSP. + /// + /// + /// Only supports . + /// + Dsp, + + /// + /// Rendering is performed on the CPU. + /// + /// + /// Only supports . + /// + Cpu, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs b/src/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs new file mode 100644 index 00000000..46aae05a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs @@ -0,0 +1,39 @@ +namespace Ryujinx.Audio.Renderer.Server.Types +{ + /// + /// The internal play state of a + /// + public enum PlayState + { + /// + /// The voice has been started and is playing. + /// + Started, + + /// + /// The voice has been stopped. + /// + /// + /// This cannot be directly set by user. + /// See for correct usage. + /// + Stopped, + + /// + /// The user asked the voice to be stopped. + /// + /// + /// This is changed to the state after command generation. + /// + /// + Stopping, + + /// + /// The voice has been paused by user request. + /// + /// + /// The user can resume to the state. + /// + Paused, + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs new file mode 100644 index 00000000..a3c442a4 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs @@ -0,0 +1,14 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Audio.Renderer.Server.Upsampler +{ + public struct UpsamplerBufferState + { + public const int HistoryLength = 20; + + public float Scale; + public Array20 History; + public bool Initialized; + public int Phase; + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs new file mode 100644 index 00000000..dbc2c9b3 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Upsampler +{ + /// + /// Upsampler manager. + /// + public class UpsamplerManager + { + /// + /// Work buffer for upsampler. + /// + private readonly Memory _upSamplerWorkBuffer; + + /// + /// Global lock of the object. + /// + private readonly object _lock = new(); + + /// + /// The upsamplers instances. + /// + private readonly UpsamplerState[] _upsamplers; + + /// + /// The count of upsamplers. + /// + private readonly uint _count; + + /// + /// Create a new . + /// + /// Work buffer for upsampler. + /// The count of upsamplers. + public UpsamplerManager(Memory upSamplerWorkBuffer, uint count) + { + _upSamplerWorkBuffer = upSamplerWorkBuffer; + _count = count; + + _upsamplers = new UpsamplerState[_count]; + } + + /// + /// Allocate a new . + /// + /// A new or null if out of memory. + public UpsamplerState Allocate() + { + int workBufferOffset = 0; + + lock (_lock) + { + for (int i = 0; i < _count; i++) + { + if (_upsamplers[i] == null) + { + _upsamplers[i] = new UpsamplerState(this, i, _upSamplerWorkBuffer.Slice(workBufferOffset, Constants.UpSampleEntrySize), Constants.TargetSampleCount); + + return _upsamplers[i]; + } + + workBufferOffset += Constants.UpSampleEntrySize; + } + } + + return null; + } + + /// + /// Free a at the given index. + /// + /// The index of the to free. + public void Free(int index) + { + lock (_lock) + { + Debug.Assert(_upsamplers[index] != null); + + _upsamplers[index] = null; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs new file mode 100644 index 00000000..39a58c91 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs @@ -0,0 +1,68 @@ +using System; + +namespace Ryujinx.Audio.Renderer.Server.Upsampler +{ + /// + /// Server state for a upsampling. + /// + public class UpsamplerState + { + /// + /// The output buffer containing the target samples. + /// + public Memory OutputBuffer { get; } + + /// + /// The target sample count. + /// + public uint SampleCount { get; } + + /// + /// The index of the . (used to free it) + /// + private readonly int _index; + + /// + /// The . + /// + private readonly UpsamplerManager _manager; + + /// + /// The source sample count. + /// + public uint SourceSampleCount; + + /// + /// The input buffer indices of the buffers holding the samples that need upsampling. + /// + public ushort[] InputBufferIndices; + + /// + /// State of each input buffer index kept across invocations of the upsampler. + /// + public UpsamplerBufferState[] BufferStates; + + /// + /// Create a new . + /// + /// The upsampler manager. + /// The index of the . (used to free it) + /// The output buffer used to contain the target samples. + /// The target sample count. + public UpsamplerState(UpsamplerManager manager, int index, Memory outputBuffer, uint sampleCount) + { + _manager = manager; + _index = index; + OutputBuffer = outputBuffer; + SampleCount = sampleCount; + } + + /// + /// Release the . + /// + public void Release() + { + _manager.Free(_index); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs new file mode 100644 index 00000000..e3d5f797 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs @@ -0,0 +1,40 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + /// + /// Server state for a voice channel resource. + /// + [StructLayout(LayoutKind.Sequential, Size = 0xD0, Pack = Alignment)] + public struct VoiceChannelResource + { + public const int Alignment = 0x10; + + /// + /// Mix volumes for the resource. + /// + public Array24 Mix; + + /// + /// Previous mix volumes for resource. + /// + public Array24 PreviousMix; + + /// + /// The id of the resource. + /// + public uint Id; + + /// + /// Indicate if the resource is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + public void UpdateState() + { + Mix.AsSpan().CopyTo(PreviousMix.AsSpan()); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs new file mode 100644 index 00000000..7ce7143e --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs @@ -0,0 +1,149 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + /// + /// Voice context. + /// + public class VoiceContext + { + /// + /// Storage of the sorted indices to . + /// + private Memory _sortedVoices; + + /// + /// Storage for . + /// + private Memory _voices; + + /// + /// Storage for . + /// + private Memory _voiceChannelResources; + + /// + /// Storage for that are used during audio renderer server updates. + /// + private Memory _voiceUpdateStatesCpu; + + /// + /// Storage for for the . + /// + private Memory _voiceUpdateStatesDsp; + + /// + /// The total voice count. + /// + private uint _voiceCount; + + public void Initialize(Memory sortedVoices, Memory voices, Memory voiceChannelResources, Memory voiceUpdateStatesCpu, Memory voiceUpdateStatesDsp, uint voiceCount) + { + _sortedVoices = sortedVoices; + _voices = voices; + _voiceChannelResources = voiceChannelResources; + _voiceUpdateStatesCpu = voiceUpdateStatesCpu; + _voiceUpdateStatesDsp = voiceUpdateStatesDsp; + _voiceCount = voiceCount; + } + + /// + /// Get the total voice count. + /// + /// The total voice count. + public uint GetCount() + { + return _voiceCount; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref VoiceChannelResource GetChannelResource(int id) + { + return ref SpanIOHelper.GetFromMemory(_voiceChannelResources, id, _voiceCount); + } + + /// + /// Get a at the given . + /// + /// The index to use. + /// A at the given . + /// The returned should only be used when updating the server state. + public Memory GetUpdateStateForCpu(int id) + { + return SpanIOHelper.GetMemory(_voiceUpdateStatesCpu, id, _voiceCount); + } + + /// + /// Get a at the given . + /// + /// The index to use. + /// A at the given . + /// The returned should only be used in the context of processing on the . + public Memory GetUpdateStateForDsp(int id) + { + return SpanIOHelper.GetMemory(_voiceUpdateStatesDsp, id, _voiceCount); + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref VoiceState GetState(int id) + { + return ref SpanIOHelper.GetFromMemory(_voices, id, _voiceCount); + } + + public ref VoiceState GetSortedState(int id) + { + Debug.Assert(id >= 0 && id < _voiceCount); + + return ref GetState(_sortedVoices.Span[id]); + } + + /// + /// Update internal state during command generation. + /// + public void UpdateForCommandGeneration() + { + _voiceUpdateStatesDsp.CopyTo(_voiceUpdateStatesCpu); + } + + /// + /// Sort the internal voices by priority and sorting order (if the priorities match). + /// + public void Sort() + { + for (int i = 0; i < _voiceCount; i++) + { + _sortedVoices.Span[i] = i; + } + + int[] sortedVoicesTemp = _sortedVoices[..(int)GetCount()].ToArray(); + + Array.Sort(sortedVoicesTemp, (a, b) => + { + ref VoiceState aState = ref GetState(a); + ref VoiceState bState = ref GetState(b); + + int result = aState.Priority.CompareTo(bState.Priority); + + if (result == 0) + { + return aState.SortingOrder.CompareTo(bState.SortingOrder); + } + + return result; + }); + + sortedVoicesTemp.AsSpan().CopyTo(_sortedVoices.Span); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs new file mode 100644 index 00000000..040c70e6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs @@ -0,0 +1,713 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; +using PlayState = Ryujinx.Audio.Renderer.Server.Types.PlayState; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + [StructLayout(LayoutKind.Sequential, Pack = Alignment)] + public struct VoiceState + { + public const int Alignment = 0x10; + + /// + /// Set to true if the voice is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool InUse; + + /// + /// Set to true if the voice is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + [MarshalAs(UnmanagedType.I1)] + public bool WasPlaying; + + /// + /// The of the voice. + /// + public SampleFormat SampleFormat; + + /// + /// The sample rate of the voice. + /// + public uint SampleRate; + + /// + /// The total channel count used. + /// + public uint ChannelsCount; + + /// + /// Id of the voice. + /// + public int Id; + + /// + /// Node id of the voice. + /// + public int NodeId; + + /// + /// The target mix id of the voice. + /// + public int MixId; + + /// + /// The current voice . + /// + public PlayState PlayState; + + /// + /// The previous voice . + /// + public PlayState PreviousPlayState; + + /// + /// The priority of the voice. + /// + public uint Priority; + + /// + /// Target sorting position of the voice. (used to sort voice with the same ) + /// + public uint SortingOrder; + + /// + /// The pitch used on the voice. + /// + public float Pitch; + + /// + /// The output volume of the voice. + /// + public float Volume; + + /// + /// The previous output volume of the voice. + /// + public float PreviousVolume; + + /// + /// Biquad filters to apply to the output of the voice. + /// + public Array2 BiquadFilters; + + /// + /// Total count of of the voice. + /// + public uint WaveBuffersCount; + + /// + /// Current playing of the voice. + /// + public uint WaveBuffersIndex; + + /// + /// Change the behaviour of the voice. + /// + /// This was added on REV5. + public DecodingBehaviour DecodingBehaviour; + + /// + /// User state required by the data source. + /// + /// Only used for as the GC-ADPCM coefficients. + public AddressInfo DataSourceStateAddressInfo; + + /// + /// The wavebuffers of this voice. + /// + public Array4 WaveBuffers; + + /// + /// The channel resource ids associated to the voice. + /// + public Array6 ChannelResourceIds; + + /// + /// The target splitter id of the voice. + /// + public uint SplitterId; + + /// + /// Change the Sample Rate Conversion (SRC) quality of the voice. + /// + /// This was added on REV8. + public SampleRateConversionQuality SrcQuality; + + /// + /// If set to true, the voice was dropped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool VoiceDropFlag; + + /// + /// Set to true if the data source state work buffer wasn't mapped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool DataSourceStateUnmapped; + + /// + /// Set to true if any of the work buffer wasn't mapped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool BufferInfoUnmapped; + + /// + /// The biquad filter initialization state storage. + /// + private BiquadFilterNeedInitializationArrayStruct _biquadFilterNeedInitialization; + + /// + /// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played. + /// + /// This was added on REV5. + public byte FlushWaveBufferCount; + + [StructLayout(LayoutKind.Sequential, Size = Constants.VoiceBiquadFilterCount)] + private struct BiquadFilterNeedInitializationArrayStruct { } + + /// + /// The biquad filter initialization state array. + /// + public Span BiquadFilterNeedInitialization => SpanHelpers.AsSpan(ref _biquadFilterNeedInitialization); + + /// + /// Initialize the . + /// + public void Initialize() + { + IsNew = false; + VoiceDropFlag = false; + DataSourceStateUnmapped = false; + BufferInfoUnmapped = false; + FlushWaveBufferCount = 0; + PlayState = PlayState.Stopped; + Priority = Constants.VoiceLowestPriority; + Id = 0; + NodeId = 0; + SampleRate = 0; + SampleFormat = SampleFormat.Invalid; + ChannelsCount = 0; + Pitch = 0.0f; + Volume = 0.0f; + PreviousVolume = 0.0f; + BiquadFilters.AsSpan().Clear(); + WaveBuffersCount = 0; + WaveBuffersIndex = 0; + MixId = Constants.UnusedMixId; + SplitterId = Constants.UnusedSplitterId; + DataSourceStateAddressInfo.Setup(0, 0); + + InitializeWaveBuffers(); + } + + /// + /// Initialize the in this . + /// + private void InitializeWaveBuffers() + { + for (int i = 0; i < WaveBuffers.Length; i++) + { + WaveBuffers[i].StartSampleOffset = 0; + WaveBuffers[i].EndSampleOffset = 0; + WaveBuffers[i].ShouldLoop = false; + WaveBuffers[i].IsEndOfStream = false; + WaveBuffers[i].BufferAddressInfo.Setup(0, 0); + WaveBuffers[i].ContextAddressInfo.Setup(0, 0); + WaveBuffers[i].IsSendToAudioProcessor = true; + } + } + + /// + /// Check if the voice needs to be skipped. + /// + /// Returns true if the voice needs to be skipped. + public readonly bool ShouldSkip() + { + return !InUse || WaveBuffersCount == 0 || DataSourceStateUnmapped || BufferInfoUnmapped || VoiceDropFlag; + } + + /// + /// Return true if the mix has any destinations. + /// + /// True if the mix has any destinations. + public readonly bool HasAnyDestination() + { + return MixId != Constants.UnusedMixId || SplitterId != Constants.UnusedSplitterId; + } + + /// + /// Indicate if the server voice information needs to be updated. + /// + /// The user parameter. + /// Return true, if the server voice information needs to be updated. + private readonly bool ShouldUpdateParameters(in VoiceInParameter parameter) + { + if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress) + { + return DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize; + } + + return DataSourceStateAddressInfo.CpuAddress != parameter.DataSourceStateAddress || + DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize || + DataSourceStateUnmapped; + } + + /// + /// Update the internal state from a user parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The mapper to use. + /// The behaviour context. + public void UpdateParameters(out ErrorInfo outErrorInfo, in VoiceInParameter parameter, PoolMapper poolMapper, ref BehaviourContext behaviourContext) + { + InUse = parameter.InUse; + Id = parameter.Id; + NodeId = parameter.NodeId; + + UpdatePlayState(parameter.PlayState); + + SrcQuality = parameter.SrcQuality; + + Priority = parameter.Priority; + SortingOrder = parameter.SortingOrder; + SampleRate = parameter.SampleRate; + SampleFormat = parameter.SampleFormat; + ChannelsCount = parameter.ChannelCount; + Pitch = parameter.Pitch; + Volume = parameter.Volume; + parameter.BiquadFilters.AsSpan().CopyTo(BiquadFilters.AsSpan()); + WaveBuffersCount = parameter.WaveBuffersCount; + WaveBuffersIndex = parameter.WaveBuffersIndex; + + if (behaviourContext.IsFlushVoiceWaveBuffersSupported()) + { + FlushWaveBufferCount += parameter.FlushWaveBufferCount; + } + + MixId = parameter.MixId; + + if (behaviourContext.IsSplitterSupported()) + { + SplitterId = parameter.SplitterId; + } + else + { + SplitterId = Constants.UnusedSplitterId; + } + + parameter.ChannelResourceIds.AsSpan().CopyTo(ChannelResourceIds.AsSpan()); + + DecodingBehaviour behaviour = DecodingBehaviour.Default; + + if (behaviourContext.IsDecodingBehaviourFlagSupported()) + { + behaviour = parameter.DecodingBehaviourFlags; + } + + DecodingBehaviour = behaviour; + + if (parameter.ResetVoiceDropFlag) + { + VoiceDropFlag = false; + } + + if (ShouldUpdateParameters(in parameter)) + { + DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize); + } + else + { + outErrorInfo = new ErrorInfo(); + } + } + + /// + /// Update the internal play state from user play state. + /// + /// The target user play state. + public void UpdatePlayState(Common.PlayState userPlayState) + { + PlayState oldServerPlayState = PlayState; + + PreviousPlayState = oldServerPlayState; + + PlayState newServerPlayState; + + switch (userPlayState) + { + case Common.PlayState.Start: + newServerPlayState = PlayState.Started; + break; + + case Common.PlayState.Stop: + if (oldServerPlayState == PlayState.Stopped) + { + return; + } + + newServerPlayState = PlayState.Stopping; + break; + + case Common.PlayState.Pause: + newServerPlayState = PlayState.Paused; + break; + + default: + throw new NotImplementedException($"Unhandled PlayState.{userPlayState}"); + } + + PlayState = newServerPlayState; + } + + /// + /// Write the status of the voice to the given user output. + /// + /// The given user output. + /// The user parameter. + /// The voice states associated to the . + public void WriteOutStatus(ref VoiceOutStatus outStatus, in VoiceInParameter parameter, ReadOnlySpan> voiceUpdateStates) + { +#if DEBUG + // Sanity check in debug mode of the internal state + if (!parameter.IsNew && !IsNew) + { + for (int i = 1; i < ChannelsCount; i++) + { + ref VoiceUpdateState stateA = ref voiceUpdateStates[i - 1].Span[0]; + ref VoiceUpdateState stateB = ref voiceUpdateStates[i].Span[0]; + + Debug.Assert(stateA.WaveBufferConsumed == stateB.WaveBufferConsumed); + Debug.Assert(stateA.PlayedSampleCount == stateB.PlayedSampleCount); + Debug.Assert(stateA.Offset == stateB.Offset); + Debug.Assert(stateA.WaveBufferIndex == stateB.WaveBufferIndex); + Debug.Assert(stateA.Fraction == stateB.Fraction); + Debug.Assert(stateA.IsWaveBufferValid.SequenceEqual(stateB.IsWaveBufferValid)); + } + } +#endif + if (parameter.IsNew || IsNew) + { + IsNew = true; + + outStatus.VoiceDropFlag = false; + outStatus.PlayedWaveBuffersCount = 0; + outStatus.PlayedSampleCount = 0; + } + else + { + ref VoiceUpdateState state = ref voiceUpdateStates[0].Span[0]; + + outStatus.VoiceDropFlag = VoiceDropFlag; + outStatus.PlayedWaveBuffersCount = state.WaveBufferConsumed; + outStatus.PlayedSampleCount = state.PlayedSampleCount; + } + } + + /// + /// Update the internal state of all the of the . + /// + /// An array of used to report errors when mapping any of the . + /// The user parameter. + /// The voice states associated to the . + /// The mapper to use. + /// The behaviour context. + public void UpdateWaveBuffers( + out ErrorInfo[] errorInfos, + in VoiceInParameter parameter, + ReadOnlySpan> voiceUpdateStates, + PoolMapper mapper, + ref BehaviourContext behaviourContext) + { + errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2]; + + if (parameter.IsNew) + { + InitializeWaveBuffers(); + + for (int i = 0; i < parameter.ChannelCount; i++) + { + voiceUpdateStates[i].Span[0].IsWaveBufferValid.Clear(); + } + } + + ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[0].Span[0]; + + for (int i = 0; i < Constants.VoiceWaveBufferCount; i++) + { + UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], mapper, ref behaviourContext); + } + } + + /// + /// Update the internal state of one of the of the . + /// + /// A used to report errors when mapping the . + /// The to update. + /// The from the user input. + /// The from the user input. + /// If set to true, the server side wavebuffer is considered valid. + /// The mapper to use. + /// The behaviour context. + private void UpdateWaveBuffer( + Span errorInfos, + ref WaveBuffer waveBuffer, + ref WaveBufferInternal inputWaveBuffer, + SampleFormat sampleFormat, + bool isValid, + PoolMapper mapper, + ref BehaviourContext behaviourContext) + { + if (!isValid && waveBuffer.IsSendToAudioProcessor && waveBuffer.BufferAddressInfo.CpuAddress != 0) + { + mapper.ForceUnmap(ref waveBuffer.BufferAddressInfo); + waveBuffer.BufferAddressInfo.Setup(0, 0); + } + + if (!inputWaveBuffer.SentToServer || BufferInfoUnmapped) + { + if (inputWaveBuffer.IsSampleOffsetValid(sampleFormat)) + { + Debug.Assert(waveBuffer.IsSendToAudioProcessor); + + waveBuffer.IsSendToAudioProcessor = false; + waveBuffer.StartSampleOffset = inputWaveBuffer.StartSampleOffset; + waveBuffer.EndSampleOffset = inputWaveBuffer.EndSampleOffset; + waveBuffer.ShouldLoop = inputWaveBuffer.ShouldLoop; + waveBuffer.IsEndOfStream = inputWaveBuffer.IsEndOfStream; + waveBuffer.LoopStartSampleOffset = inputWaveBuffer.LoopFirstSampleOffset; + waveBuffer.LoopEndSampleOffset = inputWaveBuffer.LoopLastSampleOffset; + waveBuffer.LoopCount = inputWaveBuffer.LoopCount; + + BufferInfoUnmapped = !mapper.TryAttachBuffer(out ErrorInfo bufferInfoError, ref waveBuffer.BufferAddressInfo, inputWaveBuffer.Address, inputWaveBuffer.Size); + + errorInfos[0] = bufferInfoError; + + if (sampleFormat == SampleFormat.Adpcm && behaviourContext.IsAdpcmLoopContextBugFixed() && inputWaveBuffer.ContextAddress != 0) + { + bool adpcmLoopContextMapped = mapper.TryAttachBuffer(out ErrorInfo adpcmLoopContextInfoError, + ref waveBuffer.ContextAddressInfo, + inputWaveBuffer.ContextAddress, + inputWaveBuffer.ContextSize); + + errorInfos[1] = adpcmLoopContextInfoError; + + if (adpcmLoopContextMapped) + { + BufferInfoUnmapped = DataSourceStateUnmapped; + } + else + { + BufferInfoUnmapped = true; + } + } + else + { + waveBuffer.ContextAddressInfo.Setup(0, 0); + } + } + else + { + errorInfos[0].ErrorCode = ResultCode.InvalidAddressInfo; + errorInfos[0].ExtraErrorInfo = inputWaveBuffer.Address; + } + } + } + + /// + /// Reset the resources associated to this . + /// + /// The voice context. + private void ResetResources(VoiceContext context) + { + for (int i = 0; i < ChannelsCount; i++) + { + int channelResourceId = ChannelResourceIds[i]; + + ref VoiceChannelResource voiceChannelResource = ref context.GetChannelResource(channelResourceId); + + Debug.Assert(voiceChannelResource.IsUsed); + + Memory dspSharedState = context.GetUpdateStateForDsp(channelResourceId); + + MemoryMarshal.Cast(dspSharedState.Span).Clear(); + + voiceChannelResource.UpdateState(); + } + } + + /// + /// Flush a certain amount of . + /// + /// The amount of wavebuffer to flush. + /// The voice states associated to the . + /// The channel count from user input. + private void FlushWaveBuffers(uint waveBufferCount, Memory[] voiceUpdateStates, uint channelCount) + { + uint waveBufferIndex = WaveBuffersIndex; + + for (int i = 0; i < waveBufferCount; i++) + { + WaveBuffers[(int)waveBufferIndex].IsSendToAudioProcessor = true; + + for (int j = 0; j < channelCount; j++) + { + ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0]; + + voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % Constants.VoiceWaveBufferCount; + voiceUpdateState.WaveBufferConsumed++; + voiceUpdateState.IsWaveBufferValid[(int)waveBufferIndex] = false; + } + + waveBufferIndex = (waveBufferIndex + 1) % Constants.VoiceWaveBufferCount; + } + } + + /// + /// Update the internal parameters for command generation. + /// + /// The voice states associated to the . + /// Return true if this voice should be played. + public bool UpdateParametersForCommandGeneration(Memory[] voiceUpdateStates) + { + if (FlushWaveBufferCount != 0) + { + FlushWaveBuffers(FlushWaveBufferCount, voiceUpdateStates, ChannelsCount); + + FlushWaveBufferCount = 0; + } + + switch (PlayState) + { + case PlayState.Started: + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref WaveBuffer wavebuffer = ref WaveBuffers[i]; + + if (!wavebuffer.IsSendToAudioProcessor) + { + for (int y = 0; y < ChannelsCount; y++) + { + Debug.Assert(!voiceUpdateStates[y].Span[0].IsWaveBufferValid[i]); + + voiceUpdateStates[y].Span[0].IsWaveBufferValid[i] = true; + } + + wavebuffer.IsSendToAudioProcessor = true; + } + } + + WasPlaying = false; + + ref VoiceUpdateState primaryVoiceUpdateState = ref voiceUpdateStates[0].Span[0]; + + for (int i = 0; i < primaryVoiceUpdateState.IsWaveBufferValid.Length; i++) + { + if (primaryVoiceUpdateState.IsWaveBufferValid[i]) + { + return true; + } + } + + return false; + + case PlayState.Stopping: + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref WaveBuffer wavebuffer = ref WaveBuffers[i]; + + wavebuffer.IsSendToAudioProcessor = true; + + for (int j = 0; j < ChannelsCount; j++) + { + ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0]; + + if (voiceUpdateState.IsWaveBufferValid[i]) + { + voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % Constants.VoiceWaveBufferCount; + voiceUpdateState.WaveBufferConsumed++; + } + + voiceUpdateState.IsWaveBufferValid[i] = false; + } + } + + for (int i = 0; i < ChannelsCount; i++) + { + ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[i].Span[0]; + + voiceUpdateState.Offset = 0; + voiceUpdateState.PlayedSampleCount = 0; + voiceUpdateState.Pitch.AsSpan().Clear(); + voiceUpdateState.Fraction = 0; + voiceUpdateState.LoopContext = new AdpcmLoopContext(); + } + + PlayState = PlayState.Stopped; + WasPlaying = PreviousPlayState == PlayState.Started; + + return WasPlaying; + + case PlayState.Stopped: + case PlayState.Paused: + foreach (ref WaveBuffer wavebuffer in WaveBuffers.AsSpan()) + { + wavebuffer.BufferAddressInfo.GetReference(true); + wavebuffer.ContextAddressInfo.GetReference(true); + } + + if (SampleFormat == SampleFormat.Adpcm) + { + if (DataSourceStateAddressInfo.CpuAddress != 0) + { + DataSourceStateAddressInfo.GetReference(true); + } + } + + WasPlaying = PreviousPlayState == PlayState.Started; + + return WasPlaying; + default: + throw new NotImplementedException($"{PlayState}"); + } + } + + /// + /// Update the internal state for command generation. + /// + /// The voice context. + /// Return true if this voice should be played. + public bool UpdateForCommandGeneration(VoiceContext context) + { + if (IsNew) + { + ResetResources(context); + PreviousVolume = Volume; + IsNew = false; + } + + Memory[] voiceUpdateStates = new Memory[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < ChannelsCount; i++) + { + voiceUpdateStates[i] = context.GetUpdateStateForDsp(ChannelResourceIds[i]); + } + + return UpdateParametersForCommandGeneration(voiceUpdateStates); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs new file mode 100644 index 00000000..a9946ba4 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs @@ -0,0 +1,105 @@ +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + /// + /// A wavebuffer used for server update. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 1)] + public struct WaveBuffer + { + /// + /// The of the sample data of the wavebuffer. + /// + public AddressInfo BufferAddressInfo; + + /// + /// The of the context of the wavebuffer. + /// + /// Only used by . + public AddressInfo ContextAddressInfo; + + + /// + /// First sample to play of the wavebuffer. + /// + public uint StartSampleOffset; + + /// + /// Last sample to play of the wavebuffer. + /// + public uint EndSampleOffset; + + /// + /// Set to true if the wavebuffer is looping. + /// + [MarshalAs(UnmanagedType.I1)] + public bool ShouldLoop; + + /// + /// Set to true if the wavebuffer is the end of stream. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEndOfStream; + + /// + /// Set to true if the wavebuffer wasn't sent to the . + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsSendToAudioProcessor; + + /// + /// First sample to play when looping the wavebuffer. + /// + public uint LoopStartSampleOffset; + + /// + /// Last sample to play when looping the wavebuffer. + /// + public uint LoopEndSampleOffset; + + /// + /// The max loop count. + /// + public int LoopCount; + + /// + /// Create a new for use by the . + /// + /// The target version of the wavebuffer. + /// A new for use by the . + public Common.WaveBuffer ToCommon(int version) + { + Common.WaveBuffer waveBuffer = new() + { + Buffer = BufferAddressInfo.GetReference(true), + BufferSize = (uint)BufferAddressInfo.Size, + }; + + if (ContextAddressInfo.CpuAddress != 0) + { + waveBuffer.Context = ContextAddressInfo.GetReference(true); + waveBuffer.ContextSize = (uint)ContextAddressInfo.Size; + } + + waveBuffer.StartSampleOffset = StartSampleOffset; + waveBuffer.EndSampleOffset = EndSampleOffset; + waveBuffer.Looping = ShouldLoop; + waveBuffer.IsEndOfStream = IsEndOfStream; + + if (version == 2) + { + waveBuffer.LoopCount = LoopCount; + waveBuffer.LoopStartSampleOffset = LoopStartSampleOffset; + waveBuffer.LoopEndSampleOffset = LoopEndSampleOffset; + } + else + { + waveBuffer.LoopCount = -1; + } + + return waveBuffer; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs b/src/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs new file mode 100644 index 00000000..8ebf2034 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs @@ -0,0 +1,57 @@ +using System.Runtime.CompilerServices; +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// The memory management + /// + /// This is stub for the most part but is kept to permit LLE if wanted. + static class AudioProcessorMemoryManager + { + /// + /// Map the given to the address space. + /// + /// The process owning the CPU memory. + /// The to map. + /// The size of the CPU memory region to map. + /// The address on the address space. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static DspAddress Map(uint processHandle, CpuAddress cpuAddress, ulong size) + { + return cpuAddress; + } + + /// + /// Unmap the given from the address space. + /// + /// The process owning the CPU memory. + /// The to unmap. + /// The size of the CPU memory region to unmap. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Unmap(uint processHandle, CpuAddress cpuAddress, ulong size) + { + } + + /// + /// Invalidate the data cache at the given address. + /// + /// The base DSP address to invalidate + /// The size of the DSP memory region to invalidate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvalidateDspCache(DspAddress address, ulong size) + { + } + + /// + /// Invalidate the CPU data cache at the given address. + /// + /// The base to invalidate + /// The size of the CPU memory region to invalidate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvalidateDataCache(CpuAddress address, ulong size) + { + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/BitArray.cs b/src/Ryujinx.Audio/Renderer/Utils/BitArray.cs new file mode 100644 index 00000000..8fd0baea --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/BitArray.cs @@ -0,0 +1,103 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// A simple bit array implementation backed by a . + /// + public class BitArray + { + /// + /// The backing storage of the . + /// + private readonly Memory _storage; + + /// + /// Create a new from . + /// + /// The backing storage of the . + public BitArray(Memory storage) + { + _storage = storage; + } + + /// + /// Get the byte position of a given bit index. + /// + /// A bit index. + /// The byte position of a given bit index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ToPosition(int index) => index / 8; + + /// + /// Get the bit position of a given bit index inside a byte. + /// + /// A bit index. + /// The bit position of a given bit index inside a byte. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ToBitPosition(int index) => index % 8; + + /// + /// Test if the bit at the given index is set. + /// + /// A bit index. + /// Return true if the bit at the given index is set + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Test(int index) + { + ulong mask = 1ul << ToBitPosition(index); + + return (_storage.Span[ToPosition(index)] & mask) == mask; + } + + /// + /// Set the bit at the given index. + /// + /// A bit index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Set(int index) + { + Set(index, true); + } + + /// + /// Reset the bit at the given index. + /// + /// A bit index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset(int index) + { + Set(index, false); + } + + /// + /// Set a bit value at the given index. + /// + /// A bit index. + /// The new bit value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Set(int index, bool value) + { + byte mask = (byte)(1 << ToBitPosition(index)); + + if (value) + { + _storage.Span[ToPosition(index)] |= mask; + } + else + { + _storage.Span[ToPosition(index)] &= (byte)~mask; + } + } + + /// + /// Reset all bits in the storage. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() + { + _storage.Span.Clear(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs b/src/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs new file mode 100644 index 00000000..bc2313cc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs @@ -0,0 +1,99 @@ +using Ryujinx.Audio.Integration; +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// A that outputs to a wav file. + /// + public class FileHardwareDevice : IHardwareDevice + { + private FileStream _stream; + private readonly uint _channelCount; + private readonly uint _sampleRate; + + private const int HeaderSize = 44; + + public FileHardwareDevice(string path, uint channelCount, uint sampleRate) + { + _stream = File.OpenWrite(path); + _channelCount = channelCount; + _sampleRate = sampleRate; + + _stream.Seek(HeaderSize, SeekOrigin.Begin); + } + + private void UpdateHeader() + { + var writer = new BinaryWriter(_stream); + + long currentPos = writer.Seek(0, SeekOrigin.Current); + + writer.Seek(0, SeekOrigin.Begin); + + writer.Write("RIFF"u8); + writer.Write((int)(writer.BaseStream.Length - 8)); + writer.Write("WAVE"u8); + writer.Write("fmt "u8); + writer.Write(16); + writer.Write((short)1); + writer.Write((short)GetChannelCount()); + writer.Write(GetSampleRate()); + writer.Write(GetSampleRate() * GetChannelCount() * sizeof(short)); + writer.Write((short)(GetChannelCount() * sizeof(short))); + writer.Write((short)(sizeof(short) * 8)); + writer.Write("data"u8); + writer.Write((int)(writer.BaseStream.Length - HeaderSize)); + + writer.Seek((int)currentPos, SeekOrigin.Begin); + } + + public void AppendBuffer(ReadOnlySpan data, uint channelCount) + { + _stream.Write(MemoryMarshal.Cast(data)); + + UpdateHeader(); + _stream.Flush(); + } + + public void SetVolume(float volume) + { + // Do nothing, volume is not used for FileHardwareDevice at the moment. + } + + public float GetVolume() + { + // FileHardwareDevice does not incorporate volume. + return 0; + } + + public uint GetChannelCount() + { + return _channelCount; + } + + public uint GetSampleRate() + { + return _sampleRate; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _stream?.Flush(); + _stream?.Dispose(); + + _stream = null; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs b/src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs new file mode 100644 index 00000000..26907af9 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Concurrent; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// A simple generic message queue for unmanaged types. + /// + /// The target unmanaged type used + public class Mailbox : IDisposable where T : unmanaged + { + private readonly BlockingCollection _messageQueue; + private readonly BlockingCollection _responseQueue; + + public Mailbox() + { + _messageQueue = new BlockingCollection(1); + _responseQueue = new BlockingCollection(1); + } + + public void SendMessage(T data) + { + _messageQueue.Add(data); + } + + public void SendResponse(T data) + { + _responseQueue.Add(data); + } + + public T ReceiveMessage() + { + return _messageQueue.Take(); + } + + public T ReceiveResponse() + { + return _responseQueue.Take(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _messageQueue.Dispose(); + _responseQueue.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs b/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs new file mode 100644 index 00000000..7a6edab1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs @@ -0,0 +1,71 @@ +namespace Ryujinx.Audio.Renderer.Utils.Math +{ + record struct Matrix2x2 + { + public float M11; + public float M12; + public float M21; + public float M22; + + public Matrix2x2(float m11, float m12, + float m21, float m22) + { + M11 = m11; + M12 = m12; + + M21 = m21; + M22 = m22; + } + + public static Matrix2x2 operator +(Matrix2x2 value1, Matrix2x2 value2) + { + Matrix2x2 m; + + m.M11 = value1.M11 + value2.M11; + m.M12 = value1.M12 + value2.M12; + m.M21 = value1.M21 + value2.M21; + m.M22 = value1.M22 + value2.M22; + + return m; + } + + public static Matrix2x2 operator -(Matrix2x2 value1, float value2) + { + Matrix2x2 m; + + m.M11 = value1.M11 - value2; + m.M12 = value1.M12 - value2; + m.M21 = value1.M21 - value2; + m.M22 = value1.M22 - value2; + + return m; + } + + public static Matrix2x2 operator *(Matrix2x2 value1, float value2) + { + Matrix2x2 m; + + m.M11 = value1.M11 * value2; + m.M12 = value1.M12 * value2; + m.M21 = value1.M21 * value2; + m.M22 = value1.M22 * value2; + + return m; + } + + public static Matrix2x2 operator *(Matrix2x2 value1, Matrix2x2 value2) + { + Matrix2x2 m; + + // First row + m.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21; + m.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22; + + // Second row + m.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21; + m.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22; + + return m; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs b/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs new file mode 100644 index 00000000..ff012302 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs @@ -0,0 +1,97 @@ +namespace Ryujinx.Audio.Renderer.Utils.Math +{ + record struct Matrix6x6 + { + public float M11; + public float M12; + public float M13; + public float M14; + public float M15; + public float M16; + + public float M21; + public float M22; + public float M23; + public float M24; + public float M25; + public float M26; + + public float M31; + public float M32; + public float M33; + public float M34; + public float M35; + public float M36; + + public float M41; + public float M42; + public float M43; + public float M44; + public float M45; + public float M46; + + public float M51; + public float M52; + public float M53; + public float M54; + public float M55; + public float M56; + + public float M61; + public float M62; + public float M63; + public float M64; + public float M65; + public float M66; + + public Matrix6x6(float m11, float m12, float m13, float m14, float m15, float m16, + float m21, float m22, float m23, float m24, float m25, float m26, + float m31, float m32, float m33, float m34, float m35, float m36, + float m41, float m42, float m43, float m44, float m45, float m46, + float m51, float m52, float m53, float m54, float m55, float m56, + float m61, float m62, float m63, float m64, float m65, float m66) + { + M11 = m11; + M12 = m12; + M13 = m13; + M14 = m14; + M15 = m15; + M16 = m16; + + M21 = m21; + M22 = m22; + M23 = m23; + M24 = m24; + M25 = m25; + M26 = m26; + + M31 = m31; + M32 = m32; + M33 = m33; + M34 = m34; + M35 = m35; + M36 = m36; + + M41 = m41; + M42 = m42; + M43 = m43; + M44 = m44; + M45 = m45; + M46 = m46; + + M51 = m51; + M52 = m52; + M53 = m53; + M54 = m54; + M55 = m55; + M56 = m56; + + M61 = m61; + M62 = m62; + M63 = m63; + M64 = m64; + M65 = m65; + M66 = m66; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs b/src/Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs new file mode 100644 index 00000000..7b4b7ad1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs @@ -0,0 +1,45 @@ +using Ryujinx.Audio.Renderer.Utils.Math; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + static class MatrixHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector6 Transform(ref Vector6 value1, ref Matrix6x6 value2) + { + return new Vector6 + { + X = value2.M11 * value1.X + value2.M12 * value1.Y + value2.M13 * value1.Z + value2.M14 * value1.W + value2.M15 * value1.V + value2.M16 * value1.U, + Y = value2.M21 * value1.X + value2.M22 * value1.Y + value2.M23 * value1.Z + value2.M24 * value1.W + value2.M25 * value1.V + value2.M26 * value1.U, + Z = value2.M31 * value1.X + value2.M32 * value1.Y + value2.M33 * value1.Z + value2.M34 * value1.W + value2.M35 * value1.V + value2.M36 * value1.U, + W = value2.M41 * value1.X + value2.M42 * value1.Y + value2.M43 * value1.Z + value2.M44 * value1.W + value2.M45 * value1.V + value2.M46 * value1.U, + V = value2.M51 * value1.X + value2.M52 * value1.Y + value2.M53 * value1.Z + value2.M54 * value1.W + value2.M55 * value1.V + value2.M56 * value1.U, + U = value2.M61 * value1.X + value2.M62 * value1.Y + value2.M63 * value1.Z + value2.M64 * value1.W + value2.M65 * value1.V + value2.M66 * value1.U, + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Transform(ref Vector4 value1, ref Matrix4x4 value2) + { + return new Vector4 + { + X = value2.M11 * value1.X + value2.M12 * value1.Y + value2.M13 * value1.Z + value2.M14 * value1.W, + Y = value2.M21 * value1.X + value2.M22 * value1.Y + value2.M23 * value1.Z + value2.M24 * value1.W, + Z = value2.M31 * value1.X + value2.M32 * value1.Y + value2.M33 * value1.Z + value2.M34 * value1.W, + W = value2.M41 * value1.X + value2.M42 * value1.Y + value2.M43 * value1.Z + value2.M44 * value1.W, + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Transform(ref Vector2 value1, ref Matrix2x2 value2) + { + return new Vector2 + { + X = value2.M11 * value1.X + value2.M12 * value1.Y, + Y = value2.M21 * value1.X + value2.M22 * value1.Y, + }; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs b/src/Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs new file mode 100644 index 00000000..303c5e9d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs @@ -0,0 +1,56 @@ +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Utils.Math +{ + record struct Vector6 + { + public float X; + public float Y; + public float Z; + public float W; + public float V; + public float U; + + public Vector6(float value) : this(value, value, value, value, value, value) + { + } + + public Vector6(float x, float y, float z, float w, float v, float u) + { + X = x; + Y = y; + Z = z; + W = w; + V = v; + U = u; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector6 operator +(Vector6 left, Vector6 right) + { + return new Vector6(left.X + right.X, + left.Y + right.Y, + left.Z + right.Z, + left.W + right.W, + left.V + right.V, + left.U + right.U); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector6 operator *(Vector6 left, Vector6 right) + { + return new Vector6(left.X * right.X, + left.Y * right.Y, + left.Z * right.Z, + left.W * right.W, + left.V * right.V, + left.U * right.U); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector6 operator *(Vector6 left, float right) + { + return left * new Vector6(right); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs b/src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs new file mode 100644 index 00000000..abbb6ea6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs @@ -0,0 +1,171 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// Helper for IO operations on and . + /// + public static class SpanIOHelper + { + /// + /// Write the given data to the given backing and move cursor after the written data. + /// + /// The data type. + /// The backing to store the data. + /// The data to write to the backing . + public static void Write(ref Memory backingMemory, ref T data) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null); + } + + MemoryMarshal.Write(backingMemory.Span[..size], in data); + + backingMemory = backingMemory[size..]; + } + + /// + /// Write the given data to the given backing and move cursor after the written data. + /// + /// The data type. + /// The backing to store the data. + /// The data to write to the backing . + public static void Write(ref Span backingMemory, ref T data) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null); + } + + MemoryMarshal.Write(backingMemory[..size], in data); + + backingMemory = backingMemory[size..]; + } + + /// + /// Get a out of a and move cursor after T size. + /// + /// The data type. + /// The backing to get a from. + /// A from backing . + public static Span GetWriteRef(ref Memory backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null); + } + + Span result = MemoryMarshal.Cast(backingMemory.Span[..size]); + + backingMemory = backingMemory[size..]; + + return result; + } + + /// + /// Get a out of a backingMemory and move cursor after T size. + /// + /// The data type. + /// The backing to get a from. + /// A from backing . + public static Span GetWriteRef(ref Span backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null); + } + + Span result = MemoryMarshal.Cast(backingMemory[..size]); + + backingMemory = backingMemory[size..]; + + return result; + } + + /// + /// Read data from the given backing and move cursor after the read data. + /// + /// The data type. + /// The backing to read data from. + /// Return the read data. + public static T Read(ref ReadOnlyMemory backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null); + } + + T result = MemoryMarshal.Read(backingMemory.Span[..size]); + + backingMemory = backingMemory[size..]; + + return result; + } + + /// + /// Read data from the given backing and move cursor after the read data. + /// + /// The data type. + /// The backing to read data from. + /// Return the read data. + public static T Read(ref ReadOnlySpan backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null); + } + + T result = MemoryMarshal.Read(backingMemory[..size]); + + backingMemory = backingMemory[size..]; + + return result; + } + + /// + /// Extract a at the given index. + /// + /// The data type. + /// The to extract the data from. + /// The id in the provided memory. + /// The max allowed count. (for bound checking of the id in debug mode) + /// a at the given id. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory GetMemory(Memory memory, int id, uint count) where T : unmanaged + { + Debug.Assert(id >= 0 && id < count); + + return memory.Slice(id, 1); + } + + /// + /// Extract a ref T at the given index. + /// + /// The data type. + /// The to extract the data from. + /// The id in the provided memory. + /// The max allowed count. (for bound checking of the id in debug mode) + /// a ref T at the given id. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetFromMemory(Memory memory, int id, uint count) where T : unmanaged + { + return ref GetMemory(memory, id, count).Span[0]; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs b/src/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs new file mode 100644 index 00000000..b6bafbe0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs @@ -0,0 +1,43 @@ +using System; +using System.Buffers; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Utils +{ + public sealed unsafe class SpanMemoryManager : MemoryManager + where T : unmanaged + { + private readonly T* _pointer; + private readonly int _length; + + public SpanMemoryManager(Span span) + { + fixed (T* ptr = &MemoryMarshal.GetReference(span)) + { + _pointer = ptr; + _length = span.Length; + } + } + + public override Span GetSpan() => new(_pointer, _length); + + public override MemoryHandle Pin(int elementIndex = 0) + { + if (elementIndex < 0 || elementIndex >= _length) + { + throw new ArgumentOutOfRangeException(nameof(elementIndex)); + } + + return new MemoryHandle(_pointer + elementIndex); + } + + public override void Unpin() { } + + protected override void Dispose(bool disposing) { } + + public static Memory Cast(Memory memory) where TFrom : unmanaged + { + return new SpanMemoryManager(MemoryMarshal.Cast(memory.Span)).Memory; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs b/src/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs new file mode 100644 index 00000000..32d50e12 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs @@ -0,0 +1,59 @@ +using Ryujinx.Audio.Integration; +using System; + +namespace Ryujinx.Audio.Renderer.Utils +{ + public class SplitterHardwareDevice : IHardwareDevice + { + private readonly IHardwareDevice _baseDevice; + private readonly IHardwareDevice _secondaryDevice; + + public SplitterHardwareDevice(IHardwareDevice baseDevice, IHardwareDevice secondaryDevice) + { + _baseDevice = baseDevice; + _secondaryDevice = secondaryDevice; + } + + public void AppendBuffer(ReadOnlySpan data, uint channelCount) + { + _baseDevice.AppendBuffer(data, channelCount); + _secondaryDevice?.AppendBuffer(data, channelCount); + } + + public void SetVolume(float volume) + { + _baseDevice.SetVolume(volume); + _secondaryDevice.SetVolume(volume); + } + + public float GetVolume() + { + return _baseDevice.GetVolume(); + } + + public uint GetChannelCount() + { + return _baseDevice.GetChannelCount(); + } + + public uint GetSampleRate() + { + return _baseDevice.GetSampleRate(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _baseDevice.Dispose(); + _secondaryDevice?.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Audio/ResultCode.cs b/src/Ryujinx.Audio/ResultCode.cs new file mode 100644 index 00000000..eab27c16 --- /dev/null +++ b/src/Ryujinx.Audio/ResultCode.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Audio +{ + public enum ResultCode + { + ModuleId = 153, + ErrorCodeShift = 9, + + Success = 0, + + DeviceNotFound = (1 << ErrorCodeShift) | ModuleId, + OperationFailed = (2 << ErrorCodeShift) | ModuleId, + UnsupportedSampleRate = (3 << ErrorCodeShift) | ModuleId, + WorkBufferTooSmall = (4 << ErrorCodeShift) | ModuleId, + BufferRingFull = (8 << ErrorCodeShift) | ModuleId, + UnsupportedChannelConfiguration = (10 << ErrorCodeShift) | ModuleId, + InvalidUpdateInfo = (41 << ErrorCodeShift) | ModuleId, + InvalidAddressInfo = (42 << ErrorCodeShift) | ModuleId, + InvalidMixSorting = (43 << ErrorCodeShift) | ModuleId, + UnsupportedOperation = (513 << ErrorCodeShift) | ModuleId, + InvalidExecutionContextOperation = (514 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.Audio/Ryujinx.Audio.csproj b/src/Ryujinx.Audio/Ryujinx.Audio.csproj new file mode 100644 index 00000000..fc20f4ec --- /dev/null +++ b/src/Ryujinx.Audio/Ryujinx.Audio.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + true + + + + + + + + + diff --git a/src/Ryujinx.Common/AsyncWorkQueue.cs b/src/Ryujinx.Common/AsyncWorkQueue.cs new file mode 100644 index 00000000..abb5867b --- /dev/null +++ b/src/Ryujinx.Common/AsyncWorkQueue.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.Common +{ + public sealed class AsyncWorkQueue : IDisposable + { + private readonly Thread _workerThread; + private readonly CancellationTokenSource _cts; + private readonly Action _workerAction; + private readonly BlockingCollection _queue; + + public bool IsCancellationRequested => _cts.IsCancellationRequested; + + public AsyncWorkQueue(Action callback, string name = null) : this(callback, name, new BlockingCollection()) + { + } + + public AsyncWorkQueue(Action callback, string name, BlockingCollection collection) + { + _cts = new CancellationTokenSource(); + _queue = collection; + _workerAction = callback; + _workerThread = new Thread(DoWork) + { + Name = name, + IsBackground = true, + }; + _workerThread.Start(); + } + + private void DoWork() + { + try + { + foreach (var item in _queue.GetConsumingEnumerable(_cts.Token)) + { + _workerAction(item); + } + } + catch (OperationCanceledException) + { + } + } + + public void Cancel() + { + _cts.Cancel(); + } + + public void CancelAfter(int millisecondsDelay) + { + _cts.CancelAfter(millisecondsDelay); + } + + public void CancelAfter(TimeSpan delay) + { + _cts.CancelAfter(delay); + } + + public void Add(T workItem) + { + _queue.Add(workItem); + } + + public void Add(T workItem, CancellationToken cancellationToken) + { + _queue.Add(workItem, cancellationToken); + } + + public bool TryAdd(T workItem) + { + return _queue.TryAdd(workItem); + } + + public bool TryAdd(T workItem, int millisecondsDelay) + { + return _queue.TryAdd(workItem, millisecondsDelay); + } + + public bool TryAdd(T workItem, int millisecondsDelay, CancellationToken cancellationToken) + { + return _queue.TryAdd(workItem, millisecondsDelay, cancellationToken); + } + + public bool TryAdd(T workItem, TimeSpan timeout) + { + return _queue.TryAdd(workItem, timeout); + } + + public void Dispose() + { + _queue.CompleteAdding(); + _cts.Cancel(); + _workerThread.Join(); + + _queue.Dispose(); + _cts.Dispose(); + } + } +} diff --git a/src/Ryujinx.Common/Collections/IntervalTree.cs b/src/Ryujinx.Common/Collections/IntervalTree.cs new file mode 100644 index 00000000..d3a5e7fc --- /dev/null +++ b/src/Ryujinx.Common/Collections/IntervalTree.cs @@ -0,0 +1,499 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Common.Collections +{ + /// + /// An Augmented Interval Tree based off of the "TreeDictionary"'s Red-Black Tree. Allows fast overlap checking of ranges. + /// + /// Key + /// Value + public class IntervalTree : IntrusiveRedBlackTreeImpl> where TKey : IComparable + { + private const int ArrayGrowthSize = 32; + + #region Public Methods + + /// + /// Gets the values of the interval whose key is . + /// + /// Key of the node value to get + /// Overlaps array to place results in + /// Number of values found + /// is null + public int Get(TKey key, ref TValue[] overlaps) + { + ArgumentNullException.ThrowIfNull(key); + + IntervalTreeNode node = GetNode(key); + + if (node == null) + { + return 0; + } + + if (node.Values.Count > overlaps.Length) + { + Array.Resize(ref overlaps, node.Values.Count); + } + + int overlapsCount = 0; + foreach (RangeNode value in node.Values) + { + overlaps[overlapsCount++] = value.Value; + } + + return overlapsCount; + } + + /// + /// Returns the values of the intervals whose start and end keys overlap the given range. + /// + /// Start of the range + /// End of the range + /// Overlaps array to place results in + /// Index to start writing results into the array. Defaults to 0 + /// Number of values found + /// or is null + public int Get(TKey start, TKey end, ref TValue[] overlaps, int overlapCount = 0) + { + ArgumentNullException.ThrowIfNull(start); + ArgumentNullException.ThrowIfNull(end); + + GetValues(Root, start, end, ref overlaps, ref overlapCount); + + return overlapCount; + } + + /// + /// Adds a new interval into the tree whose start is , end is and value is . + /// + /// Start of the range to add + /// End of the range to insert + /// Value to add + /// , or are null + public void Add(TKey start, TKey end, TValue value) + { + ArgumentNullException.ThrowIfNull(start); + ArgumentNullException.ThrowIfNull(end); + ArgumentNullException.ThrowIfNull(value); + + Insert(start, end, value); + } + + /// + /// Removes the given from the tree, searching for it with . + /// + /// Key of the node to remove + /// Value to remove + /// is null + /// Number of deleted values + public int Remove(TKey key, TValue value) + { + ArgumentNullException.ThrowIfNull(key); + + int removed = Delete(key, value); + + Count -= removed; + + return removed; + } + + /// + /// Adds all the nodes in the dictionary into . + /// + /// A list of all RangeNodes sorted by Key Order + public List> AsList() + { + List> list = new(); + + AddToList(Root, list); + + return list; + } + + #endregion + + #region Private Methods (BST) + + /// + /// Adds all RangeNodes that are children of or contained within into , in Key Order. + /// + /// The node to search for RangeNodes within + /// The list to add RangeNodes to + private void AddToList(IntervalTreeNode node, List> list) + { + if (node == null) + { + return; + } + + AddToList(node.Left, list); + + list.AddRange(node.Values); + + AddToList(node.Right, list); + } + + /// + /// Retrieve the node reference whose key is , or null if no such node exists. + /// + /// Key of the node to get + /// Node reference in the tree + /// is null + private IntervalTreeNode GetNode(TKey key) + { + ArgumentNullException.ThrowIfNull(key); + + IntervalTreeNode node = Root; + while (node != null) + { + int cmp = key.CompareTo(node.Start); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + return node; + } + } + return null; + } + + /// + /// Retrieve all values that overlap the given start and end keys. + /// + /// Start of the range + /// End of the range + /// Overlaps array to place results in + /// Overlaps count to update + private void GetValues(IntervalTreeNode node, TKey start, TKey end, ref TValue[] overlaps, ref int overlapCount) + { + if (node == null || start.CompareTo(node.Max) >= 0) + { + return; + } + + GetValues(node.Left, start, end, ref overlaps, ref overlapCount); + + bool endsOnRight = end.CompareTo(node.Start) > 0; + if (endsOnRight) + { + if (start.CompareTo(node.End) < 0) + { + // Contains this node. Add overlaps to list. + foreach (RangeNode overlap in node.Values) + { + if (start.CompareTo(overlap.End) < 0) + { + if (overlaps.Length <= overlapCount) + { + Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize); + } + + overlaps[overlapCount++] = overlap.Value; + } + } + } + + GetValues(node.Right, start, end, ref overlaps, ref overlapCount); + } + } + + /// + /// Inserts a new node into the tree with a given , and . + /// + /// Start of the range to insert + /// End of the range to insert + /// Value to insert + private void Insert(TKey start, TKey end, TValue value) + { + IntervalTreeNode newNode = BSTInsert(start, end, value); + RestoreBalanceAfterInsertion(newNode); + } + + /// + /// Propagate an increase in max value starting at the given node, heading up the tree. + /// This should only be called if the max increases - not for rebalancing or removals. + /// + /// The node to start propagating from + private static void PropagateIncrease(IntervalTreeNode node) + { + TKey max = node.Max; + IntervalTreeNode ptr = node; + + while ((ptr = ptr.Parent) != null) + { + if (max.CompareTo(ptr.Max) > 0) + { + ptr.Max = max; + } + else + { + break; + } + } + } + + /// + /// Propagate recalculating max value starting at the given node, heading up the tree. + /// This fully recalculates the max value from all children when there is potential for it to decrease. + /// + /// The node to start propagating from + private static void PropagateFull(IntervalTreeNode node) + { + IntervalTreeNode ptr = node; + + do + { + TKey max = ptr.End; + + if (ptr.Left != null && ptr.Left.Max.CompareTo(max) > 0) + { + max = ptr.Left.Max; + } + + if (ptr.Right != null && ptr.Right.Max.CompareTo(max) > 0) + { + max = ptr.Right.Max; + } + + ptr.Max = max; + } while ((ptr = ptr.Parent) != null); + } + + /// + /// Insertion Mechanism for the interval tree. Similar to a BST insert, with the start of the range as the key. + /// Iterates the tree starting from the root and inserts a new node where all children in the left subtree are less than , and all children in the right subtree are greater than . + /// Each node can contain multiple values, and has an end address which is the maximum of all those values. + /// Post insertion, the "max" value of the node and all parents are updated. + /// + /// Start of the range to insert + /// End of the range to insert + /// Value to insert + /// The inserted Node + private IntervalTreeNode BSTInsert(TKey start, TKey end, TValue value) + { + IntervalTreeNode parent = null; + IntervalTreeNode node = Root; + + while (node != null) + { + parent = node; + int cmp = start.CompareTo(node.Start); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + node.Values.Add(new RangeNode(start, end, value)); + + if (end.CompareTo(node.End) > 0) + { + node.End = end; + if (end.CompareTo(node.Max) > 0) + { + node.Max = end; + PropagateIncrease(node); + } + } + + Count++; + return node; + } + } + IntervalTreeNode newNode = new(start, end, value, parent); + if (newNode.Parent == null) + { + Root = newNode; + } + else if (start.CompareTo(parent.Start) < 0) + { + parent.Left = newNode; + } + else + { + parent.Right = newNode; + } + + PropagateIncrease(newNode); + Count++; + return newNode; + } + + /// + /// Removes instances of from the dictionary after searching for it with . + /// + /// Key to search for + /// Value to delete + /// Number of deleted values + private int Delete(TKey key, TValue value) + { + IntervalTreeNode nodeToDelete = GetNode(key); + + if (nodeToDelete == null) + { + return 0; + } + + int removed = nodeToDelete.Values.RemoveAll(node => node.Value.Equals(value)); + + if (nodeToDelete.Values.Count > 0) + { + if (removed > 0) + { + nodeToDelete.End = nodeToDelete.Values.Max(node => node.End); + + // Recalculate max from children and new end. + PropagateFull(nodeToDelete); + } + + return removed; + } + + IntervalTreeNode replacementNode; + + if (LeftOf(nodeToDelete) == null || RightOf(nodeToDelete) == null) + { + replacementNode = nodeToDelete; + } + else + { + replacementNode = PredecessorOf(nodeToDelete); + } + + IntervalTreeNode tmp = LeftOf(replacementNode) ?? RightOf(replacementNode); + + if (tmp != null) + { + tmp.Parent = ParentOf(replacementNode); + } + + if (ParentOf(replacementNode) == null) + { + Root = tmp; + } + else if (replacementNode == LeftOf(ParentOf(replacementNode))) + { + ParentOf(replacementNode).Left = tmp; + } + else + { + ParentOf(replacementNode).Right = tmp; + } + + if (replacementNode != nodeToDelete) + { + nodeToDelete.Start = replacementNode.Start; + nodeToDelete.Values = replacementNode.Values; + nodeToDelete.End = replacementNode.End; + nodeToDelete.Max = replacementNode.Max; + } + + PropagateFull(replacementNode); + + if (tmp != null && ColorOf(replacementNode) == Black) + { + RestoreBalanceAfterRemoval(tmp); + } + + return removed; + } + + #endregion + + protected override void RotateLeft(IntervalTreeNode node) + { + if (node != null) + { + base.RotateLeft(node); + + PropagateFull(node); + } + } + + protected override void RotateRight(IntervalTreeNode node) + { + if (node != null) + { + base.RotateRight(node); + + PropagateFull(node); + } + } + + public bool ContainsKey(TKey key) + { + ArgumentNullException.ThrowIfNull(key); + + return GetNode(key) != null; + } + } + + /// + /// Represents a value and its start and end keys. + /// + /// + /// + public readonly struct RangeNode + { + public readonly TKey Start; + public readonly TKey End; + public readonly TValue Value; + + public RangeNode(TKey start, TKey end, TValue value) + { + Start = start; + End = end; + Value = value; + } + } + + /// + /// Represents a node in the IntervalTree which contains start and end keys of type K, and a value of generic type V. + /// + /// Key type of the node + /// Value type of the node + public class IntervalTreeNode : IntrusiveRedBlackTreeNode> + { + /// + /// The start of the range. + /// + internal TKey Start; + + /// + /// The end of the range - maximum of all in the Values list. + /// + internal TKey End; + + /// + /// The maximum end value of this node and all its children. + /// + internal TKey Max; + + /// + /// Values contained on the node that shares a common Start value. + /// + internal List> Values; + + internal IntervalTreeNode(TKey start, TKey end, TValue value, IntervalTreeNode parent) + { + Start = start; + End = end; + Max = end; + Values = new List> { new RangeNode(start, end, value) }; + Parent = parent; + } + } +} diff --git a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs new file mode 100644 index 00000000..9e56f707 --- /dev/null +++ b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs @@ -0,0 +1,280 @@ +using System; + +namespace Ryujinx.Common.Collections +{ + /// + /// Tree that provides the ability for O(logN) lookups for keys that exist in the tree, and O(logN) lookups for keys immediately greater than or less than a specified key. + /// + /// Derived node type + public class IntrusiveRedBlackTree : IntrusiveRedBlackTreeImpl where T : IntrusiveRedBlackTreeNode, IComparable + { + #region Public Methods + + /// + /// Adds a new node into the tree. + /// + /// Node to be added + /// is null + public void Add(T node) + { + ArgumentNullException.ThrowIfNull(node); + + Insert(node); + } + + /// + /// Removes a node from the tree. + /// + /// Note to be removed + /// is null + public void Remove(T node) + { + ArgumentNullException.ThrowIfNull(node); + + if (Delete(node) != null) + { + Count--; + } + } + + /// + /// Retrieve the node that is considered equal to the specified node by the comparator. + /// + /// Node to compare with + /// Node that is equal to + /// is null + public T GetNode(T searchNode) + { + ArgumentNullException.ThrowIfNull(searchNode); + + T node = Root; + while (node != null) + { + int cmp = searchNode.CompareTo(node); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + return node; + } + } + return null; + } + + #endregion + + #region Private Methods (BST) + + /// + /// Inserts a new node into the tree. + /// + /// Node to be inserted + private void Insert(T node) + { + T newNode = BSTInsert(node); + RestoreBalanceAfterInsertion(newNode); + } + + /// + /// Insertion Mechanism for a Binary Search Tree (BST). + ///

+ /// Iterates the tree starting from the root and inserts a new node + /// where all children in the left subtree are less than , + /// and all children in the right subtree are greater than . + ///
+ /// Node to be inserted + /// The inserted Node + private T BSTInsert(T newNode) + { + T parent = null; + T node = Root; + + while (node != null) + { + parent = node; + int cmp = newNode.CompareTo(node); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + return node; + } + } + newNode.Parent = parent; + if (parent == null) + { + Root = newNode; + } + else if (newNode.CompareTo(parent) < 0) + { + parent.Left = newNode; + } + else + { + parent.Right = newNode; + } + Count++; + return newNode; + } + + /// + /// Removes from the tree, if it exists. + /// + /// Node to be removed + /// The deleted Node + private T Delete(T nodeToDelete) + { + if (nodeToDelete == null) + { + return null; + } + + T old = nodeToDelete; + T child; + T parent; + bool color; + + if (LeftOf(nodeToDelete) == null) + { + child = RightOf(nodeToDelete); + } + else if (RightOf(nodeToDelete) == null) + { + child = LeftOf(nodeToDelete); + } + else + { + T element = Minimum(RightOf(nodeToDelete)); + + child = RightOf(element); + parent = ParentOf(element); + color = ColorOf(element); + + if (child != null) + { + child.Parent = parent; + } + + if (parent == null) + { + Root = child; + } + else if (element == LeftOf(parent)) + { + parent.Left = child; + } + else + { + parent.Right = child; + } + + element.Color = old.Color; + element.Left = old.Left; + element.Right = old.Right; + element.Parent = old.Parent; + + if (ParentOf(old) == null) + { + Root = element; + } + else if (old == LeftOf(ParentOf(old))) + { + ParentOf(old).Left = element; + } + else + { + ParentOf(old).Right = element; + } + + LeftOf(old).Parent = element; + + if (RightOf(old) != null) + { + RightOf(old).Parent = element; + } + + if (child != null && color == Black) + { + RestoreBalanceAfterRemoval(child); + } + + return old; + } + + parent = ParentOf(nodeToDelete); + color = ColorOf(nodeToDelete); + + if (child != null) + { + child.Parent = parent; + } + + if (parent == null) + { + Root = child; + } + else if (nodeToDelete == LeftOf(parent)) + { + parent.Left = child; + } + else + { + parent.Right = child; + } + + if (child != null && color == Black) + { + RestoreBalanceAfterRemoval(child); + } + + return old; + } + + #endregion + } + + public static class IntrusiveRedBlackTreeExtensions + { + /// + /// Retrieve the node that is considered equal to the key by the comparator. + /// + /// Tree to search at + /// Key of the node to be found + /// Node that is equal to + public static TNode GetNodeByKey(this IntrusiveRedBlackTree tree, TKey key) + where TNode : IntrusiveRedBlackTreeNode, IComparable, IComparable + where TKey : struct + { + TNode node = tree.RootNode; + while (node != null) + { + int cmp = node.CompareTo(key); + if (cmp < 0) + { + node = node.Right; + } + else if (cmp > 0) + { + node = node.Left; + } + else + { + return node; + } + } + return null; + } + } +} diff --git a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs new file mode 100644 index 00000000..49f97223 --- /dev/null +++ b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs @@ -0,0 +1,354 @@ +using System; + +namespace Ryujinx.Common.Collections +{ + /// + /// Tree that provides the ability for O(logN) lookups for keys that exist in the tree, and O(logN) lookups for keys immediately greater than or less than a specified key. + /// + /// Derived node type + public class IntrusiveRedBlackTreeImpl where T : IntrusiveRedBlackTreeNode + { + protected const bool Black = true; + protected const bool Red = false; + protected T Root; + + internal T RootNode => Root; + + /// + /// Number of nodes on the tree. + /// + public int Count { get; protected set; } + + /// + /// Removes all nodes on the tree. + /// + public void Clear() + { + Root = null; + Count = 0; + } + + /// + /// Finds the node whose key is immediately greater than . + /// + /// Node to find the successor of + /// Successor of + internal static T SuccessorOf(T node) + { + if (node.Right != null) + { + return Minimum(node.Right); + } + T parent = node.Parent; + while (parent != null && node == parent.Right) + { + node = parent; + parent = parent.Parent; + } + return parent; + } + + /// + /// Finds the node whose key is immediately less than . + /// + /// Node to find the predecessor of + /// Predecessor of + internal static T PredecessorOf(T node) + { + if (node.Left != null) + { + return Maximum(node.Left); + } + T parent = node.Parent; + while (parent != null && node == parent.Left) + { + node = parent; + parent = parent.Parent; + } + return parent; + } + + /// + /// Returns the node with the largest key where is considered the root node. + /// + /// Root node + /// Node with the maximum key in the tree of + protected static T Maximum(T node) + { + T tmp = node; + while (tmp.Right != null) + { + tmp = tmp.Right; + } + + return tmp; + } + + /// + /// Returns the node with the smallest key where is considered the root node. + /// + /// Root node + /// Node with the minimum key in the tree of + /// is null + protected static T Minimum(T node) + { + ArgumentNullException.ThrowIfNull(node); + + T tmp = node; + while (tmp.Left != null) + { + tmp = tmp.Left; + } + + return tmp; + } + + protected void RestoreBalanceAfterRemoval(T balanceNode) + { + T ptr = balanceNode; + + while (ptr != Root && ColorOf(ptr) == Black) + { + if (ptr == LeftOf(ParentOf(ptr))) + { + T sibling = RightOf(ParentOf(ptr)); + + if (ColorOf(sibling) == Red) + { + SetColor(sibling, Black); + SetColor(ParentOf(ptr), Red); + RotateLeft(ParentOf(ptr)); + sibling = RightOf(ParentOf(ptr)); + } + if (ColorOf(LeftOf(sibling)) == Black && ColorOf(RightOf(sibling)) == Black) + { + SetColor(sibling, Red); + ptr = ParentOf(ptr); + } + else + { + if (ColorOf(RightOf(sibling)) == Black) + { + SetColor(LeftOf(sibling), Black); + SetColor(sibling, Red); + RotateRight(sibling); + sibling = RightOf(ParentOf(ptr)); + } + SetColor(sibling, ColorOf(ParentOf(ptr))); + SetColor(ParentOf(ptr), Black); + SetColor(RightOf(sibling), Black); + RotateLeft(ParentOf(ptr)); + ptr = Root; + } + } + else + { + T sibling = LeftOf(ParentOf(ptr)); + + if (ColorOf(sibling) == Red) + { + SetColor(sibling, Black); + SetColor(ParentOf(ptr), Red); + RotateRight(ParentOf(ptr)); + sibling = LeftOf(ParentOf(ptr)); + } + if (ColorOf(RightOf(sibling)) == Black && ColorOf(LeftOf(sibling)) == Black) + { + SetColor(sibling, Red); + ptr = ParentOf(ptr); + } + else + { + if (ColorOf(LeftOf(sibling)) == Black) + { + SetColor(RightOf(sibling), Black); + SetColor(sibling, Red); + RotateLeft(sibling); + sibling = LeftOf(ParentOf(ptr)); + } + SetColor(sibling, ColorOf(ParentOf(ptr))); + SetColor(ParentOf(ptr), Black); + SetColor(LeftOf(sibling), Black); + RotateRight(ParentOf(ptr)); + ptr = Root; + } + } + } + SetColor(ptr, Black); + } + + protected void RestoreBalanceAfterInsertion(T balanceNode) + { + SetColor(balanceNode, Red); + while (balanceNode != null && balanceNode != Root && ColorOf(ParentOf(balanceNode)) == Red) + { + if (ParentOf(balanceNode) == LeftOf(ParentOf(ParentOf(balanceNode)))) + { + T sibling = RightOf(ParentOf(ParentOf(balanceNode))); + + if (ColorOf(sibling) == Red) + { + SetColor(ParentOf(balanceNode), Black); + SetColor(sibling, Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + balanceNode = ParentOf(ParentOf(balanceNode)); + } + else + { + if (balanceNode == RightOf(ParentOf(balanceNode))) + { + balanceNode = ParentOf(balanceNode); + RotateLeft(balanceNode); + } + SetColor(ParentOf(balanceNode), Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + RotateRight(ParentOf(ParentOf(balanceNode))); + } + } + else + { + T sibling = LeftOf(ParentOf(ParentOf(balanceNode))); + + if (ColorOf(sibling) == Red) + { + SetColor(ParentOf(balanceNode), Black); + SetColor(sibling, Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + balanceNode = ParentOf(ParentOf(balanceNode)); + } + else + { + if (balanceNode == LeftOf(ParentOf(balanceNode))) + { + balanceNode = ParentOf(balanceNode); + RotateRight(balanceNode); + } + SetColor(ParentOf(balanceNode), Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + RotateLeft(ParentOf(ParentOf(balanceNode))); + } + } + } + SetColor(Root, Black); + } + + protected virtual void RotateLeft(T node) + { + if (node != null) + { + T right = RightOf(node); + node.Right = LeftOf(right); + if (node.Right != null) + { + node.Right.Parent = node; + } + T nodeParent = ParentOf(node); + right.Parent = nodeParent; + if (nodeParent == null) + { + Root = right; + } + else if (node == LeftOf(nodeParent)) + { + nodeParent.Left = right; + } + else + { + nodeParent.Right = right; + } + right.Left = node; + node.Parent = right; + } + } + + protected virtual void RotateRight(T node) + { + if (node != null) + { + T left = LeftOf(node); + node.Left = RightOf(left); + if (node.Left != null) + { + node.Left.Parent = node; + } + T nodeParent = ParentOf(node); + left.Parent = nodeParent; + if (nodeParent == null) + { + Root = left; + } + else if (node == RightOf(nodeParent)) + { + nodeParent.Right = left; + } + else + { + nodeParent.Left = left; + } + left.Right = node; + node.Parent = left; + } + } + + #region Safety-Methods + + // These methods save memory by allowing us to forego sentinel nil nodes, as well as serve as protection against NullReferenceExceptions. + + /// + /// Returns the color of , or Black if it is null. + /// + /// Node + /// The boolean color of , or black if null + protected static bool ColorOf(T node) + { + return node == null || node.Color; + } + + /// + /// Sets the color of node to . + ///

+ /// This method does nothing if is null. + ///
+ /// Node to set the color of + /// Color (Boolean) + protected static void SetColor(T node, bool color) + { + if (node != null) + { + node.Color = color; + } + } + + /// + /// This method returns the left node of , or null if is null. + /// + /// Node to retrieve the left child from + /// Left child of + protected static T LeftOf(T node) + { + return node?.Left; + } + + /// + /// This method returns the right node of , or null if is null. + /// + /// Node to retrieve the right child from + /// Right child of + protected static T RightOf(T node) + { + return node?.Right; + } + + /// + /// Returns the parent node of , or null if is null. + /// + /// Node to retrieve the parent from + /// Parent of + protected static T ParentOf(T node) + { + return node?.Parent; + } + + #endregion + } +} diff --git a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs new file mode 100644 index 00000000..29d2d0c9 --- /dev/null +++ b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Common.Collections +{ + /// + /// Represents a node in the Red-Black Tree. + /// + public class IntrusiveRedBlackTreeNode where T : IntrusiveRedBlackTreeNode + { + public bool Color = true; + public T Left; + public T Right; + public T Parent; + + public T Predecessor => IntrusiveRedBlackTreeImpl.PredecessorOf((T)this); + public T Successor => IntrusiveRedBlackTreeImpl.SuccessorOf((T)this); + } +} diff --git a/src/Ryujinx.Common/Collections/TreeDictionary.cs b/src/Ryujinx.Common/Collections/TreeDictionary.cs new file mode 100644 index 00000000..5379d353 --- /dev/null +++ b/src/Ryujinx.Common/Collections/TreeDictionary.cs @@ -0,0 +1,620 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.Common.Collections +{ + /// + /// Dictionary that provides the ability for O(logN) Lookups for keys that exist in the Dictionary, and O(logN) lookups for keys immediately greater than or less than a specified key. + /// + /// Key + /// Value + public class TreeDictionary : IntrusiveRedBlackTreeImpl>, IDictionary where TKey : IComparable + { + #region Public Methods + + /// + /// Returns the value of the node whose key is , or the default value if no such node exists. + /// + /// Key of the node value to get + /// Value associated w/ + /// is null + public TValue Get(TKey key) + { + ArgumentNullException.ThrowIfNull(key); + + Node node = GetNode(key); + + if (node == null) + { + return default; + } + + return node.Value; + } + + /// + /// Adds a new node into the tree whose key is key and value is . + ///

+ /// Note: Adding the same key multiple times will cause the value for that key to be overwritten. + ///
+ /// Key of the node to add + /// Value of the node to add + /// or are null + public void Add(TKey key, TValue value) + { + ArgumentNullException.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(value); + + Insert(key, value); + } + + /// + /// Removes the node whose key is from the tree. + /// + /// Key of the node to remove + /// is null + public void Remove(TKey key) + { + ArgumentNullException.ThrowIfNull(key); + + if (Delete(key) != null) + { + Count--; + } + } + + /// + /// Returns the value whose key is equal to or immediately less than . + /// + /// Key for which to find the floor value of + /// Key of node immediately less than + /// is null + public TKey Floor(TKey key) + { + Node node = FloorNode(key); + if (node != null) + { + return node.Key; + } + return default; + } + + /// + /// Returns the node whose key is equal to or immediately greater than . + /// + /// Key for which to find the ceiling node of + /// Key of node immediately greater than + /// is null + public TKey Ceiling(TKey key) + { + Node node = CeilingNode(key); + if (node != null) + { + return node.Key; + } + return default; + } + + /// + /// Finds the value whose key is immediately greater than . + /// + /// Key to find the successor of + /// Value + public TKey SuccessorOf(TKey key) + { + Node node = GetNode(key); + if (node != null) + { + Node successor = SuccessorOf(node); + + return successor != null ? successor.Key : default; + } + return default; + } + + /// + /// Finds the value whose key is immediately less than . + /// + /// Key to find the predecessor of + /// Value + public TKey PredecessorOf(TKey key) + { + Node node = GetNode(key); + if (node != null) + { + Node predecessor = PredecessorOf(node); + + return predecessor != null ? predecessor.Key : default; + } + return default; + } + + /// + /// Adds all the nodes in the dictionary as key/value pairs into . + ///

+ /// The key/value pairs will be added in Level Order. + ///
+ /// List to add the tree pairs into + public List> AsLevelOrderList() + { + List> list = new(); + + Queue> nodes = new(); + + if (this.Root != null) + { + nodes.Enqueue(this.Root); + } + while (nodes.TryDequeue(out Node node)) + { + list.Add(new KeyValuePair(node.Key, node.Value)); + if (node.Left != null) + { + nodes.Enqueue(node.Left); + } + if (node.Right != null) + { + nodes.Enqueue(node.Right); + } + } + return list; + } + + /// + /// Adds all the nodes in the dictionary into . + /// + /// A list of all KeyValuePairs sorted by Key Order + public List> AsList() + { + List> list = new(); + + AddToList(Root, list); + + return list; + } + + #endregion + + #region Private Methods (BST) + + /// + /// Adds all nodes that are children of or contained within into , in Key Order. + /// + /// The node to search for nodes within + /// The list to add node to + private void AddToList(Node node, List> list) + { + if (node == null) + { + return; + } + + AddToList(node.Left, list); + + list.Add(new KeyValuePair(node.Key, node.Value)); + + AddToList(node.Right, list); + } + + /// + /// Retrieve the node reference whose key is , or null if no such node exists. + /// + /// Key of the node to get + /// Node reference in the tree + /// is null + private Node GetNode(TKey key) + { + ArgumentNullException.ThrowIfNull(key); + + Node node = Root; + while (node != null) + { + int cmp = key.CompareTo(node.Key); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + return node; + } + } + return null; + } + + /// + /// Inserts a new node into the tree whose key is and value is . + ///

+ /// Adding the same key multiple times will overwrite the previous value. + ///
+ /// Key of the node to insert + /// Value of the node to insert + private void Insert(TKey key, TValue value) + { + Node newNode = BSTInsert(key, value); + RestoreBalanceAfterInsertion(newNode); + } + + /// + /// Insertion Mechanism for a Binary Search Tree (BST). + ///

+ /// Iterates the tree starting from the root and inserts a new node where all children in the left subtree are less than , and all children in the right subtree are greater than . + ///

+ /// Note: If a node whose key is already exists, it's value will be overwritten. + ///
+ /// Key of the node to insert + /// Value of the node to insert + /// The inserted Node + private Node BSTInsert(TKey key, TValue value) + { + Node parent = null; + Node node = Root; + + while (node != null) + { + parent = node; + int cmp = key.CompareTo(node.Key); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + node.Value = value; + return node; + } + } + Node newNode = new(key, value, parent); + if (newNode.Parent == null) + { + Root = newNode; + } + else if (key.CompareTo(parent.Key) < 0) + { + parent.Left = newNode; + } + else + { + parent.Right = newNode; + } + Count++; + return newNode; + } + + /// + /// Removes from the dictionary, if it exists. + /// + /// Key of the node to delete + /// The deleted Node + private Node Delete(TKey key) + { + // O(1) Retrieval + Node nodeToDelete = GetNode(key); + + if (nodeToDelete == null) + { + return null; + } + + Node replacementNode; + + if (LeftOf(nodeToDelete) == null || RightOf(nodeToDelete) == null) + { + replacementNode = nodeToDelete; + } + else + { + replacementNode = PredecessorOf(nodeToDelete); + } + + Node tmp = LeftOf(replacementNode) ?? RightOf(replacementNode); + + if (tmp != null) + { + tmp.Parent = ParentOf(replacementNode); + } + + if (ParentOf(replacementNode) == null) + { + Root = tmp; + } + else if (replacementNode == LeftOf(ParentOf(replacementNode))) + { + ParentOf(replacementNode).Left = tmp; + } + else + { + ParentOf(replacementNode).Right = tmp; + } + + if (replacementNode != nodeToDelete) + { + nodeToDelete.Key = replacementNode.Key; + nodeToDelete.Value = replacementNode.Value; + } + + if (tmp != null && ColorOf(replacementNode) == Black) + { + RestoreBalanceAfterRemoval(tmp); + } + + return replacementNode; + } + + /// + /// Returns the node whose key immediately less than or equal to . + /// + /// Key for which to find the floor node of + /// Node whose key is immediately less than or equal to , or null if no such node is found. + /// is null + private Node FloorNode(TKey key) + { + ArgumentNullException.ThrowIfNull(key); + + Node tmp = Root; + + while (tmp != null) + { + int cmp = key.CompareTo(tmp.Key); + if (cmp > 0) + { + if (tmp.Right != null) + { + tmp = tmp.Right; + } + else + { + return tmp; + } + } + else if (cmp < 0) + { + if (tmp.Left != null) + { + tmp = tmp.Left; + } + else + { + Node parent = tmp.Parent; + Node ptr = tmp; + while (parent != null && ptr == parent.Left) + { + ptr = parent; + parent = parent.Parent; + } + return parent; + } + } + else + { + return tmp; + } + } + return null; + } + + /// + /// Returns the node whose key is immediately greater than or equal to than . + /// + /// Key for which to find the ceiling node of + /// Node whose key is immediately greater than or equal to , or null if no such node is found. + /// is null + private Node CeilingNode(TKey key) + { + ArgumentNullException.ThrowIfNull(key); + + Node tmp = Root; + + while (tmp != null) + { + int cmp = key.CompareTo(tmp.Key); + if (cmp < 0) + { + if (tmp.Left != null) + { + tmp = tmp.Left; + } + else + { + return tmp; + } + } + else if (cmp > 0) + { + if (tmp.Right != null) + { + tmp = tmp.Right; + } + else + { + Node parent = tmp.Parent; + Node ptr = tmp; + while (parent != null && ptr == parent.Right) + { + ptr = parent; + parent = parent.Parent; + } + return parent; + } + } + else + { + return tmp; + } + } + return null; + } + + #endregion + + #region Interface Implementations + + // Method descriptions are not provided as they are already included as part of the interface. + public bool ContainsKey(TKey key) + { + ArgumentNullException.ThrowIfNull(key); + + return GetNode(key) != null; + } + + bool IDictionary.Remove(TKey key) + { + int count = Count; + Remove(key); + return count > Count; + } + + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + { + ArgumentNullException.ThrowIfNull(key); + + Node node = GetNode(key); + value = node != null ? node.Value : default; + return node != null; + } + + public void Add(KeyValuePair item) + { + ArgumentNullException.ThrowIfNull(item.Key); + + Add(item.Key, item.Value); + } + + public bool Contains(KeyValuePair item) + { + if (item.Key == null) + { + return false; + } + + Node node = GetNode(item.Key); + if (node != null) + { + return node.Key.Equals(item.Key) && node.Value.Equals(item.Value); + } + return false; + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + if (arrayIndex < 0 || array.Length - arrayIndex < this.Count) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + SortedList list = GetKeyValues(); + + int offset = 0; + + for (int i = arrayIndex; i < array.Length && offset < list.Count; i++) + { + array[i] = new KeyValuePair(list.Keys[i], list.Values[i]); + offset++; + } + } + + public bool Remove(KeyValuePair item) + { + Node node = GetNode(item.Key); + + if (node == null) + { + return false; + } + + if (node.Value.Equals(item.Value)) + { + int count = Count; + Remove(item.Key); + return count > Count; + } + + return false; + } + + public IEnumerator> GetEnumerator() + { + return GetKeyValues().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetKeyValues().GetEnumerator(); + } + + public ICollection Keys => GetKeyValues().Keys; + + public ICollection Values => GetKeyValues().Values; + + public bool IsReadOnly => false; + + public TValue this[TKey key] + { + get => Get(key); + set => Add(key, value); + } + + #endregion + + #region Private Interface Helper Methods + + /// + /// Returns a sorted list of all the node keys / values in the tree. + /// + /// List of node keys + private SortedList GetKeyValues() + { + SortedList set = new(); + Queue> queue = new(); + if (Root != null) + { + queue.Enqueue(Root); + } + + while (queue.TryDequeue(out Node node)) + { + set.Add(node.Key, node.Value); + if (null != node.Left) + { + queue.Enqueue(node.Left); + } + if (null != node.Right) + { + queue.Enqueue(node.Right); + } + } + + return set; + } + + #endregion + } + + /// + /// Represents a node in the TreeDictionary which contains a key and value of generic type K and V, respectively. + /// + /// Key of the node + /// Value of the node + public class Node : IntrusiveRedBlackTreeNode> where TKey : IComparable + { + internal TKey Key; + internal TValue Value; + + internal Node(TKey key, TValue value, Node parent) + { + Key = key; + Value = value; + Parent = parent; + } + } +} diff --git a/src/Ryujinx.Common/Configuration/AntiAliasing.cs b/src/Ryujinx.Common/Configuration/AntiAliasing.cs new file mode 100644 index 00000000..5e3e1b89 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/AntiAliasing.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum AntiAliasing + { + None, + Fxaa, + SmaaLow, + SmaaMedium, + SmaaHigh, + SmaaUltra, + } +} diff --git a/src/Ryujinx.Common/Configuration/AppDataManager.cs b/src/Ryujinx.Common/Configuration/AppDataManager.cs new file mode 100644 index 00000000..deaa03de --- /dev/null +++ b/src/Ryujinx.Common/Configuration/AppDataManager.cs @@ -0,0 +1,326 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using System; +using System.IO; +using System.Runtime.Versioning; + +namespace Ryujinx.Common.Configuration +{ + public static class AppDataManager + { + private const string DefaultBaseDir = "Ryujinx"; + private const string DefaultPortableDir = "portable"; + + // The following 3 are always part of Base Directory + private const string GamesDir = "games"; + private const string ProfilesDir = "profiles"; + private const string KeysDir = "system"; + + public enum LaunchMode + { + UserProfile, + Portable, + Custom, + } + + public static LaunchMode Mode { get; private set; } + + public static string BaseDirPath { get; private set; } + public static string GamesDirPath { get; private set; } + public static string ProfilesDirPath { get; private set; } + public static string KeysDirPath { get; private set; } + public static string KeysDirPathUser { get; } + + public static string LogsDirPath { get; private set; } + + public const string DefaultNandDir = "bis"; + public const string DefaultSdcardDir = "sdcard"; + private const string DefaultModsDir = "mods"; + + public static string CustomModsPath { get; set; } + public static string CustomSdModsPath { get; set; } + public static string CustomNandPath { get; set; } // TODO: Actually implement this into VFS + public static string CustomSdCardPath { get; set; } // TODO: Actually implement this into VFS + + static AppDataManager() + { + KeysDirPathUser = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".switch"); + } + + public static void Initialize(string baseDirPath) + { + string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + + if (appDataPath.Length == 0) + { + appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + } + + string userProfilePath = Path.Combine(appDataPath, DefaultBaseDir); + string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir); + + // On macOS, check for a portable directory next to the app bundle as well. + if (OperatingSystem.IsMacOS() && !Directory.Exists(portablePath)) + { + string bundlePath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..")); + // Make sure we're actually running within an app bundle. + if (bundlePath.EndsWith(".app")) + { + portablePath = Path.GetFullPath(Path.Combine(bundlePath, "..", DefaultPortableDir)); + } + } + + if (Directory.Exists(portablePath)) + { + BaseDirPath = portablePath; + Mode = LaunchMode.Portable; + } + else + { + BaseDirPath = userProfilePath; + Mode = LaunchMode.UserProfile; + } + + if (baseDirPath != null && baseDirPath != userProfilePath) + { + if (!Directory.Exists(baseDirPath)) + { + Logger.Error?.Print(LogClass.Application, $"Custom Data Directory '{baseDirPath}' does not exist. Falling back to {Mode}..."); + } + else + { + BaseDirPath = baseDirPath; + Mode = LaunchMode.Custom; + } + } + + BaseDirPath = Path.GetFullPath(BaseDirPath); // convert relative paths + + if (IsPathSymlink(BaseDirPath)) + { + Logger.Warning?.Print(LogClass.Application, $"Application data directory is a symlink. This may be unintended."); + } + + SetupBasePaths(); + } + + public static string GetOrCreateLogsDir() + { + if (Directory.Exists(LogsDirPath)) + { + return LogsDirPath; + } + + Logger.Notice.Print(LogClass.Application, "Logging directory not found; attempting to create new logging directory."); + LogsDirPath = SetUpLogsDir(); + + return LogsDirPath; + } + + private static string SetUpLogsDir() + { + string logDir = ""; + + if (Mode == LaunchMode.Portable) + { + logDir = Path.Combine(BaseDirPath, "Logs"); + try + { + Directory.CreateDirectory(logDir); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'"); + + return null; + } + } + else + { + if (OperatingSystem.IsMacOS()) + { + // NOTE: Should evaluate to "~/Library/Logs/Ryujinx/". + logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Logs", DefaultBaseDir); + try + { + Directory.CreateDirectory(logDir); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'"); + logDir = ""; + } + + if (string.IsNullOrEmpty(logDir)) + { + // NOTE: Should evaluate to "~/Library/Application Support/Ryujinx/Logs". + logDir = Path.Combine(BaseDirPath, "Logs"); + + try + { + Directory.CreateDirectory(logDir); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'"); + + return null; + } + } + } + else if (OperatingSystem.IsWindows()) + { + // NOTE: Should evaluate to a "Logs" directory in whatever directory Ryujinx was launched from. + logDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); + try + { + Directory.CreateDirectory(logDir); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'"); + logDir = ""; + } + + if (string.IsNullOrEmpty(logDir)) + { + // NOTE: Should evaluate to "C:\Users\user\AppData\Roaming\Ryujinx\Logs". + logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir, "Logs"); + + try + { + Directory.CreateDirectory(logDir); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'"); + + return null; + } + } + } + else if (OperatingSystem.IsLinux()) + { + // NOTE: Should evaluate to "~/.config/Ryujinx/Logs". + logDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir, "Logs"); + + try + { + Directory.CreateDirectory(logDir); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'"); + + return null; + } + } + } + + return logDir; + } + + private static void SetupBasePaths() + { + Directory.CreateDirectory(BaseDirPath); + LogsDirPath = SetUpLogsDir(); + Directory.CreateDirectory(GamesDirPath = Path.Combine(BaseDirPath, GamesDir)); + Directory.CreateDirectory(ProfilesDirPath = Path.Combine(BaseDirPath, ProfilesDir)); + Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir)); + } + + // Check if existing old baseDirPath is a symlink, to prevent possible errors. + // Should be removed, when the existence of the old directory isn't checked anymore. + private static bool IsPathSymlink(string path) + { + try + { + FileAttributes attributes = File.GetAttributes(path); + return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint; + } + catch + { + return false; + } + } + + [SupportedOSPlatform("macos")] + public static void FixMacOSConfigurationFolders() + { + string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".config", DefaultBaseDir); + if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath)) + { + FileSystemUtils.MoveDirectory(oldConfigPath, BaseDirPath); + Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath); + } + + string correctApplicationDataDirectoryPath = + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir); + if (IsPathSymlink(correctApplicationDataDirectoryPath)) + { + //copy the files somewhere temporarily + string tempPath = Path.Combine(Path.GetTempPath(), DefaultBaseDir); + try + { + FileSystemUtils.CopyDirectory(correctApplicationDataDirectoryPath, tempPath, true); + } + catch (Exception exception) + { + Logger.Error?.Print(LogClass.Application, + $"Critical error copying Ryujinx application data into the temp folder. {exception}"); + try + { + FileSystemInfo resolvedDirectoryInfo = + Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true); + string resolvedPath = resolvedDirectoryInfo.FullName; + Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink."); + } + catch (Exception symlinkException) + { + Logger.Error?.Print(LogClass.Application, $"Unable to resolve the symlink for Ryujinx application data: {symlinkException}. Follow the symlink at {correctApplicationDataDirectoryPath} and move your data back to the Application Support folder."); + } + return; + } + + //delete the symlink + try + { + //This will fail if this is an actual directory, so there is no way we can actually delete user data here. + File.Delete(correctApplicationDataDirectoryPath); + } + catch (Exception exception) + { + Logger.Error?.Print(LogClass.Application, + $"Critical error deleting the Ryujinx application data folder symlink at {correctApplicationDataDirectoryPath}. {exception}"); + try + { + FileSystemInfo resolvedDirectoryInfo = + Directory.ResolveLinkTarget(correctApplicationDataDirectoryPath, true); + string resolvedPath = resolvedDirectoryInfo.FullName; + Logger.Error?.Print(LogClass.Application, $"Please manually move your Ryujinx data from {resolvedPath} to {correctApplicationDataDirectoryPath}, and remove the symlink."); + } + catch (Exception symlinkException) + { + Logger.Error?.Print(LogClass.Application, $"Unable to resolve the symlink for Ryujinx application data: {symlinkException}. Follow the symlink at {correctApplicationDataDirectoryPath} and move your data back to the Application Support folder."); + } + return; + } + + //put the files back + try + { + FileSystemUtils.CopyDirectory(tempPath, correctApplicationDataDirectoryPath, true); + } + catch (Exception exception) + { + Logger.Error?.Print(LogClass.Application, + $"Critical error copying Ryujinx application data into the correct location. {exception}. Please manually move your application data from {tempPath} to {correctApplicationDataDirectoryPath}."); + } + } + } + + public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName; + public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere")).FullName; + } +} diff --git a/src/Ryujinx.Common/Configuration/AspectRatioExtensions.cs b/src/Ryujinx.Common/Configuration/AspectRatioExtensions.cs new file mode 100644 index 00000000..bae6e35d --- /dev/null +++ b/src/Ryujinx.Common/Configuration/AspectRatioExtensions.cs @@ -0,0 +1,69 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum AspectRatio + { + Fixed4x3, + Fixed16x9, + Fixed16x10, + Fixed21x9, + Fixed32x9, + Stretched, + } + + public static class AspectRatioExtensions + { + public static float ToFloat(this AspectRatio aspectRatio) + { + return aspectRatio.ToFloatX() / aspectRatio.ToFloatY(); + } + + public static float ToFloatX(this AspectRatio aspectRatio) + { + return aspectRatio switch + { +#pragma warning disable IDE0055 // Disable formatting + AspectRatio.Fixed4x3 => 4.0f, + AspectRatio.Fixed16x9 => 16.0f, + AspectRatio.Fixed16x10 => 16.0f, + AspectRatio.Fixed21x9 => 21.0f, + AspectRatio.Fixed32x9 => 32.0f, + _ => 16.0f, +#pragma warning restore IDE0055 + }; + } + + public static float ToFloatY(this AspectRatio aspectRatio) + { + return aspectRatio switch + { +#pragma warning disable IDE0055 // Disable formatting + AspectRatio.Fixed4x3 => 3.0f, + AspectRatio.Fixed16x9 => 9.0f, + AspectRatio.Fixed16x10 => 10.0f, + AspectRatio.Fixed21x9 => 9.0f, + AspectRatio.Fixed32x9 => 9.0f, + _ => 9.0f, +#pragma warning restore IDE0055 + }; + } + + public static string ToText(this AspectRatio aspectRatio) + { + return aspectRatio switch + { +#pragma warning disable IDE0055 // Disable formatting + AspectRatio.Fixed4x3 => "4:3", + AspectRatio.Fixed16x9 => "16:9", + AspectRatio.Fixed16x10 => "16:10", + AspectRatio.Fixed21x9 => "21:9", + AspectRatio.Fixed32x9 => "32:9", + _ => "Stretched", +#pragma warning restore IDE0055 + }; + } + } +} diff --git a/src/Ryujinx.Common/Configuration/BackendThreading.cs b/src/Ryujinx.Common/Configuration/BackendThreading.cs new file mode 100644 index 00000000..4fbb56bc --- /dev/null +++ b/src/Ryujinx.Common/Configuration/BackendThreading.cs @@ -0,0 +1,13 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum BackendThreading + { + Auto, + Off, + On, + } +} diff --git a/src/Ryujinx.Common/Configuration/DownloadableContentContainer.cs b/src/Ryujinx.Common/Configuration/DownloadableContentContainer.cs new file mode 100644 index 00000000..6f12e976 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/DownloadableContentContainer.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + public struct DownloadableContentContainer + { + [JsonPropertyName("path")] + public string ContainerPath { get; set; } + [JsonPropertyName("dlc_nca_list")] + public List DownloadableContentNcaList { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/DownloadableContentJsonSerializerContext.cs b/src/Ryujinx.Common/Configuration/DownloadableContentJsonSerializerContext.cs new file mode 100644 index 00000000..cc89cb12 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/DownloadableContentJsonSerializerContext.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(List))] + public partial class DownloadableContentJsonSerializerContext : JsonSerializerContext + { + } +} diff --git a/src/Ryujinx.Common/Configuration/DownloadableContentNca.cs b/src/Ryujinx.Common/Configuration/DownloadableContentNca.cs new file mode 100644 index 00000000..60d4a9bd --- /dev/null +++ b/src/Ryujinx.Common/Configuration/DownloadableContentNca.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + public struct DownloadableContentNca + { + [JsonPropertyName("path")] + public string FullPath { get; set; } + [JsonPropertyName("title_id")] + public ulong TitleId { get; set; } + [JsonPropertyName("is_enabled")] + public bool Enabled { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/GraphicsBackend.cs b/src/Ryujinx.Common/Configuration/GraphicsBackend.cs new file mode 100644 index 00000000..e3b4f91b --- /dev/null +++ b/src/Ryujinx.Common/Configuration/GraphicsBackend.cs @@ -0,0 +1,12 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum GraphicsBackend + { + Vulkan, + OpenGl, + } +} diff --git a/src/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs b/src/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs new file mode 100644 index 00000000..dfe28405 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs @@ -0,0 +1,14 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum GraphicsDebugLevel + { + None, + Error, + Slowdowns, + All, + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/GamepadInputId.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/GamepadInputId.cs new file mode 100644 index 00000000..1380813b --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/GamepadInputId.cs @@ -0,0 +1,58 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid.Controller +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum GamepadInputId : byte + { + Unbound, + A, + B, + X, + Y, + LeftStick, + RightStick, + LeftShoulder, + RightShoulder, + + // Likely axis + LeftTrigger, + // Likely axis + RightTrigger, + + DpadUp, + DpadDown, + DpadLeft, + DpadRight, + + // Special buttons + + Minus, + Plus, + + Back = Minus, + Start = Plus, + + Guide, + Misc1, + + // Xbox Elite paddle + Paddle1, + Paddle2, + Paddle3, + Paddle4, + + // PS5 touchpad button + Touchpad, + + // Virtual buttons for single joycon + SingleLeftTrigger0, + SingleRightTrigger0, + + SingleLeftTrigger1, + SingleRightTrigger1, + + Count, + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs new file mode 100644 index 00000000..e6fe74d5 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs @@ -0,0 +1,82 @@ +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using System; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid.Controller +{ + public class GenericControllerInputConfig : GenericInputConfigurationCommon where TButton : unmanaged where TStick : unmanaged + { + [JsonIgnore] + private float _deadzoneLeft; + [JsonIgnore] + private float _deadzoneRight; + [JsonIgnore] + private float _triggerThreshold; + + /// + /// Left JoyCon Controller Stick Bindings + /// + public JoyconConfigControllerStick LeftJoyconStick { get; set; } + + /// + /// Right JoyCon Controller Stick Bindings + /// + public JoyconConfigControllerStick RightJoyconStick { get; set; } + + /// + /// Controller Left Analog Stick Deadzone + /// + public float DeadzoneLeft + { + get => _deadzoneLeft; set + { + _deadzoneLeft = MathF.Round(value, 3); + OnPropertyChanged(); + } + } + + /// + /// Controller Right Analog Stick Deadzone + /// + public float DeadzoneRight + { + get => _deadzoneRight; set + { + _deadzoneRight = MathF.Round(value, 3); + OnPropertyChanged(); + } + } + + /// + /// Controller Left Analog Stick Range + /// + public float RangeLeft { get; set; } + + /// + /// Controller Right Analog Stick Range + /// + public float RangeRight { get; set; } + + /// + /// Controller Trigger Threshold + /// + public float TriggerThreshold + { + get => _triggerThreshold; set + { + _triggerThreshold = MathF.Round(value, 3); + OnPropertyChanged(); + } + } + + /// + /// Controller Motion Settings + /// + public MotionConfigController Motion { get; set; } + + /// + /// Controller Rumble Settings + /// + public RumbleConfigController Rumble { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/JoyconConfigControllerStick.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/JoyconConfigControllerStick.cs new file mode 100644 index 00000000..60868155 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/JoyconConfigControllerStick.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Common.Configuration.Hid.Controller +{ + public class JoyconConfigControllerStick where TButton : unmanaged where TStick : unmanaged + { + public TStick Joystick { get; set; } + public bool InvertStickX { get; set; } + public bool InvertStickY { get; set; } + public bool Rotate90CW { get; set; } + public TButton StickButton { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/CemuHookMotionConfigController.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/CemuHookMotionConfigController.cs new file mode 100644 index 00000000..1c31fff7 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/CemuHookMotionConfigController.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.Common.Configuration.Hid.Controller.Motion +{ + public class CemuHookMotionConfigController : MotionConfigController + { + /// + /// Motion Controller Slot + /// + public int Slot { get; set; } + + /// + /// Motion Controller Alternative Slot, for RightJoyCon in Pair mode + /// + public int AltSlot { get; set; } + + /// + /// Mirror motion input in Pair mode + /// + public bool MirrorInput { get; set; } + + /// + /// Host address of the DSU Server + /// + public string DsuServerHost { get; set; } + + /// + /// Port of the DSU Server + /// + public int DsuServerPort { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/JsonMotionConfigControllerConverter.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/JsonMotionConfigControllerConverter.cs new file mode 100644 index 00000000..40f067ae --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/JsonMotionConfigControllerConverter.cs @@ -0,0 +1,79 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid.Controller.Motion +{ + class JsonMotionConfigControllerConverter : JsonConverter + { + private static readonly MotionConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + private static MotionInputBackendType GetMotionInputBackendType(ref Utf8JsonReader reader) + { + // Temporary reader to get the backend type + Utf8JsonReader tempReader = reader; + + MotionInputBackendType result = MotionInputBackendType.Invalid; + + while (tempReader.Read()) + { + // NOTE: We scan all properties ignoring the depth entirely on purpose. + // The reason behind this is that we cannot track in a reliable way the depth of the object because Utf8JsonReader never emit the first TokenType == StartObject if the json start with an object. + // As such, this code will try to parse very field named "motion_backend" to the correct enum. + if (tempReader.TokenType == JsonTokenType.PropertyName) + { + string propertyName = tempReader.GetString(); + + if (propertyName.Equals("motion_backend")) + { + tempReader.Read(); + + if (tempReader.TokenType == JsonTokenType.String) + { + string backendTypeRaw = tempReader.GetString(); + + if (!Enum.TryParse(backendTypeRaw, out result)) + { + result = MotionInputBackendType.Invalid; + } + else + { + break; + } + } + } + } + } + + return result; + } + + public override MotionConfigController Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + MotionInputBackendType motionBackendType = GetMotionInputBackendType(ref reader); + + return motionBackendType switch + { + MotionInputBackendType.GamepadDriver => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardMotionConfigController), + MotionInputBackendType.CemuHook => JsonSerializer.Deserialize(ref reader, _serializerContext.CemuHookMotionConfigController), + _ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"), + }; + } + + public override void Write(Utf8JsonWriter writer, MotionConfigController value, JsonSerializerOptions options) + { + switch (value.MotionBackend) + { + case MotionInputBackendType.GamepadDriver: + JsonSerializer.Serialize(writer, value as StandardMotionConfigController, _serializerContext.StandardMotionConfigController); + break; + case MotionInputBackendType.CemuHook: + JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, _serializerContext.CemuHookMotionConfigController); + break; + default: + throw new ArgumentException($"Unknown motion backend type {value.MotionBackend}"); + } + } + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigController.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigController.cs new file mode 100644 index 00000000..7905ef06 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigController.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid.Controller.Motion +{ + [JsonConverter(typeof(JsonMotionConfigControllerConverter))] + public class MotionConfigController + { + public MotionInputBackendType MotionBackend { get; set; } + + /// + /// Gyro Sensitivity + /// + public int Sensitivity { get; set; } + + /// + /// Gyro Deadzone + /// + public double GyroDeadzone { get; set; } + + /// + /// Enable Motion Controls + /// + public bool EnableMotion { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigJsonSerializerContext.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigJsonSerializerContext.cs new file mode 100644 index 00000000..784e0cb1 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigJsonSerializerContext.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid.Controller.Motion +{ + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(MotionConfigController))] + [JsonSerializable(typeof(CemuHookMotionConfigController))] + [JsonSerializable(typeof(StandardMotionConfigController))] + public partial class MotionConfigJsonSerializerContext : JsonSerializerContext + { + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs new file mode 100644 index 00000000..fd839128 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs @@ -0,0 +1,13 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid.Controller.Motion +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum MotionInputBackendType : byte + { + Invalid, + GamepadDriver, + CemuHook, + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/StandardMotionConfigController.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/StandardMotionConfigController.cs new file mode 100644 index 00000000..7f1ac40a --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/StandardMotionConfigController.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Common.Configuration.Hid.Controller.Motion +{ + public class StandardMotionConfigController : MotionConfigController { } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/RumbleConfigController.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/RumbleConfigController.cs new file mode 100644 index 00000000..ee8ab457 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/RumbleConfigController.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Common.Configuration.Hid.Controller +{ + public class RumbleConfigController + { + /// + /// Controller Strong Rumble Multiplier + /// + public float StrongRumble { get; set; } + + /// + /// Controller Weak Rumble Multiplier + /// + public float WeakRumble { get; set; } + + /// + /// Enable Rumble + /// + public bool EnableRumble { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/StandardControllerInputConfig.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/StandardControllerInputConfig.cs new file mode 100644 index 00000000..cfd9755b --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/StandardControllerInputConfig.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Common.Configuration.Hid.Controller +{ + public class StandardControllerInputConfig : GenericControllerInputConfig { } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/StickInputId.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/StickInputId.cs new file mode 100644 index 00000000..8f9539c4 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/StickInputId.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid.Controller +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum StickInputId : byte + { + Unbound, + Left, + Right, + + Count, + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/ControllerType.cs b/src/Ryujinx.Common/Configuration/Hid/ControllerType.cs new file mode 100644 index 00000000..cf61b79f --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/ControllerType.cs @@ -0,0 +1,23 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid +{ + // This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical + [Flags] + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum ControllerType + { + None, + ProController = 1 << 0, + Handheld = 1 << 1, + JoyconPair = 1 << 2, + JoyconLeft = 1 << 3, + JoyconRight = 1 << 4, + Invalid = 1 << 5, + Pokeball = 1 << 6, + SystemExternal = 1 << 29, + System = 1 << 30, + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/GenericInputConfigurationCommon.cs b/src/Ryujinx.Common/Configuration/Hid/GenericInputConfigurationCommon.cs new file mode 100644 index 00000000..c70594df --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/GenericInputConfigurationCommon.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public class GenericInputConfigurationCommon : InputConfig where TButton : unmanaged + { + /// + /// Left JoyCon Controller Bindings + /// + public LeftJoyconCommonConfig LeftJoycon { get; set; } + + /// + /// Right JoyCon Controller Bindings + /// + public RightJoyconCommonConfig RightJoycon { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs b/src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs new file mode 100644 index 00000000..c3e4402b --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs @@ -0,0 +1,13 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum InputBackendType + { + Invalid, + WindowKeyboard, + GamepadSDL2, + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/InputConfig.cs b/src/Ryujinx.Common/Configuration/Hid/InputConfig.cs new file mode 100644 index 00000000..a93b721a --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/InputConfig.cs @@ -0,0 +1,41 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid +{ + [JsonConverter(typeof(JsonInputConfigConverter))] + public class InputConfig : INotifyPropertyChanged + { + /// + /// The current version of the input file format + /// + public const int CurrentVersion = 1; + + public int Version { get; set; } + + public InputBackendType Backend { get; set; } + + /// + /// Controller id + /// + public string Id { get; set; } + + /// + /// Controller's Type + /// + public ControllerType ControllerType { get; set; } + + /// + /// Player's Index for the controller + /// + public PlayerIndex PlayerIndex { get; set; } + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/InputConfigJsonSerializerContext.cs b/src/Ryujinx.Common/Configuration/Hid/InputConfigJsonSerializerContext.cs new file mode 100644 index 00000000..bd8be249 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/InputConfigJsonSerializerContext.cs @@ -0,0 +1,14 @@ +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid +{ + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(InputConfig))] + [JsonSerializable(typeof(StandardKeyboardInputConfig))] + [JsonSerializable(typeof(StandardControllerInputConfig))] + public partial class InputConfigJsonSerializerContext : JsonSerializerContext + { + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs b/src/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs new file mode 100644 index 00000000..6c2a69b8 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs @@ -0,0 +1,81 @@ +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using Ryujinx.Common.Utilities; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid +{ + public class JsonInputConfigConverter : JsonConverter + { + private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + private static InputBackendType GetInputBackendType(ref Utf8JsonReader reader) + { + // Temporary reader to get the backend type + Utf8JsonReader tempReader = reader; + + InputBackendType result = InputBackendType.Invalid; + + while (tempReader.Read()) + { + // NOTE: We scan all properties ignoring the depth entirely on purpose. + // The reason behind this is that we cannot track in a reliable way the depth of the object because Utf8JsonReader never emit the first TokenType == StartObject if the json start with an object. + // As such, this code will try to parse very field named "backend" to the correct enum. + if (tempReader.TokenType == JsonTokenType.PropertyName) + { + string propertyName = tempReader.GetString(); + + if (propertyName.Equals("backend")) + { + tempReader.Read(); + + if (tempReader.TokenType == JsonTokenType.String) + { + string backendTypeRaw = tempReader.GetString(); + + if (!Enum.TryParse(backendTypeRaw, out result)) + { + result = InputBackendType.Invalid; + } + else + { + break; + } + } + } + } + } + + return result; + } + + public override InputConfig Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + InputBackendType backendType = GetInputBackendType(ref reader); + + return backendType switch + { + InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardKeyboardInputConfig), + InputBackendType.GamepadSDL2 => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardControllerInputConfig), + _ => throw new InvalidOperationException($"Unknown backend type {backendType}"), + }; + } + + public override void Write(Utf8JsonWriter writer, InputConfig value, JsonSerializerOptions options) + { + switch (value.Backend) + { + case InputBackendType.WindowKeyboard: + JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, _serializerContext.StandardKeyboardInputConfig); + break; + case InputBackendType.GamepadSDL2: + JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, _serializerContext.StandardControllerInputConfig); + break; + default: + throw new ArgumentException($"Unknown backend type {value.Backend}"); + } + } + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Key.cs b/src/Ryujinx.Common/Configuration/Hid/Key.cs new file mode 100644 index 00000000..e3dd8e50 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Key.cs @@ -0,0 +1,143 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum Key + { + Unknown, + ShiftLeft, + ShiftRight, + ControlLeft, + ControlRight, + AltLeft, + AltRight, + WinLeft, + WinRight, + Menu, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + F25, + F26, + F27, + F28, + F29, + F30, + F31, + F32, + F33, + F34, + F35, + Up, + Down, + Left, + Right, + Enter, + Escape, + Space, + Tab, + BackSpace, + Insert, + Delete, + PageUp, + PageDown, + Home, + End, + CapsLock, + ScrollLock, + PrintScreen, + Pause, + NumLock, + Clear, + Keypad0, + Keypad1, + Keypad2, + Keypad3, + Keypad4, + Keypad5, + Keypad6, + Keypad7, + Keypad8, + Keypad9, + KeypadDivide, + KeypadMultiply, + KeypadSubtract, + KeypadAdd, + KeypadDecimal, + KeypadEnter, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + Number0, + Number1, + Number2, + Number3, + Number4, + Number5, + Number6, + Number7, + Number8, + Number9, + Tilde, + Grave, + Minus, + Plus, + BracketLeft, + BracketRight, + Semicolon, + Quote, + Comma, + Period, + Slash, + BackSlash, + Unbound, + + Count, + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Keyboard/GenericKeyboardInputConfig.cs b/src/Ryujinx.Common/Configuration/Hid/Keyboard/GenericKeyboardInputConfig.cs new file mode 100644 index 00000000..ecddd31e --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Keyboard/GenericKeyboardInputConfig.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Common.Configuration.Hid.Keyboard +{ + public class GenericKeyboardInputConfig : GenericInputConfigurationCommon where TKey : unmanaged + { + /// + /// Left JoyCon Controller Stick Bindings + /// + public JoyconConfigKeyboardStick LeftJoyconStick { get; set; } + + /// + /// Right JoyCon Controller Stick Bindings + /// + public JoyconConfigKeyboardStick RightJoyconStick { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Keyboard/JoyconConfigKeyboardStick.cs b/src/Ryujinx.Common/Configuration/Hid/Keyboard/JoyconConfigKeyboardStick.cs new file mode 100644 index 00000000..36292a8b --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Keyboard/JoyconConfigKeyboardStick.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Common.Configuration.Hid.Keyboard +{ + public class JoyconConfigKeyboardStick where TKey : unmanaged + { + public TKey StickUp { get; set; } + public TKey StickDown { get; set; } + public TKey StickLeft { get; set; } + public TKey StickRight { get; set; } + public TKey StickButton { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/Keyboard/StandardKeyboardInputConfig.cs b/src/Ryujinx.Common/Configuration/Hid/Keyboard/StandardKeyboardInputConfig.cs new file mode 100644 index 00000000..1e8b188e --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/Keyboard/StandardKeyboardInputConfig.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Common.Configuration.Hid.Keyboard +{ + public class StandardKeyboardInputConfig : GenericKeyboardInputConfig { } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs new file mode 100644 index 00000000..0cb49ca8 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public class KeyboardHotkeys + { + public Key ToggleVsync { get; set; } + public Key Screenshot { get; set; } + public Key ShowUI { get; set; } + public Key Pause { get; set; } + public Key ToggleMute { get; set; } + public Key ResScaleUp { get; set; } + public Key ResScaleDown { get; set; } + public Key VolumeUp { get; set; } + public Key VolumeDown { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/LeftJoyconCommonConfig.cs b/src/Ryujinx.Common/Configuration/Hid/LeftJoyconCommonConfig.cs new file mode 100644 index 00000000..14045355 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/LeftJoyconCommonConfig.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public class LeftJoyconCommonConfig + { + public TButton ButtonMinus { get; set; } + public TButton ButtonL { get; set; } + public TButton ButtonZl { get; set; } + public TButton ButtonSl { get; set; } + public TButton ButtonSr { get; set; } + public TButton DpadUp { get; set; } + public TButton DpadDown { get; set; } + public TButton DpadLeft { get; set; } + public TButton DpadRight { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs b/src/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs new file mode 100644 index 00000000..05e8f3fa --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs @@ -0,0 +1,22 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration.Hid +{ + // This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum PlayerIndex + { + Player1 = 0, + Player2 = 1, + Player3 = 2, + Player4 = 3, + Player5 = 4, + Player6 = 5, + Player7 = 6, + Player8 = 7, + Handheld = 8, + Unknown = 9, + Auto = 10, // Shouldn't be used directly + } +} diff --git a/src/Ryujinx.Common/Configuration/Hid/RightJoyconCommonConfig.cs b/src/Ryujinx.Common/Configuration/Hid/RightJoyconCommonConfig.cs new file mode 100644 index 00000000..1fb99104 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Hid/RightJoyconCommonConfig.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Common.Configuration.Hid +{ + public class RightJoyconCommonConfig + { + public TButton ButtonPlus { get; set; } + public TButton ButtonR { get; set; } + public TButton ButtonZr { get; set; } + public TButton ButtonSl { get; set; } + public TButton ButtonSr { get; set; } + public TButton ButtonX { get; set; } + public TButton ButtonB { get; set; } + public TButton ButtonY { get; set; } + public TButton ButtonA { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/HideCursorMode.cs b/src/Ryujinx.Common/Configuration/HideCursorMode.cs new file mode 100644 index 00000000..f4480268 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/HideCursorMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Common.Configuration +{ + public enum HideCursorMode + { + Never, + OnIdle, + Always, + } +} diff --git a/src/Ryujinx.Common/Configuration/MemoryManagerMode.cs b/src/Ryujinx.Common/Configuration/MemoryManagerMode.cs new file mode 100644 index 00000000..93031928 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/MemoryManagerMode.cs @@ -0,0 +1,13 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum MemoryManagerMode : byte + { + SoftwarePageTable, + HostMapped, + HostMappedUnsafe, + } +} diff --git a/src/Ryujinx.Common/Configuration/Mod.cs b/src/Ryujinx.Common/Configuration/Mod.cs new file mode 100644 index 00000000..052c7c8d --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Mod.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Common.Configuration +{ + public class Mod + { + public string Name { get; set; } + public string Path { get; set; } + public bool Enabled { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/ModMetadata.cs b/src/Ryujinx.Common/Configuration/ModMetadata.cs new file mode 100644 index 00000000..174320d0 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/ModMetadata.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Ryujinx.Common.Configuration +{ + public struct ModMetadata + { + public List Mods { get; set; } + + public ModMetadata() + { + Mods = new List(); + } + } +} diff --git a/src/Ryujinx.Common/Configuration/ModMetadataJsonSerializerContext.cs b/src/Ryujinx.Common/Configuration/ModMetadataJsonSerializerContext.cs new file mode 100644 index 00000000..8c1e242a --- /dev/null +++ b/src/Ryujinx.Common/Configuration/ModMetadataJsonSerializerContext.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(ModMetadata))] + public partial class ModMetadataJsonSerializerContext : JsonSerializerContext + { + } +} diff --git a/src/Ryujinx.Common/Configuration/Multiplayer/MultiplayerMode.cs b/src/Ryujinx.Common/Configuration/Multiplayer/MultiplayerMode.cs new file mode 100644 index 00000000..69f7d876 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/Multiplayer/MultiplayerMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Common.Configuration.Multiplayer +{ + public enum MultiplayerMode + { + Disabled, + LdnMitm, + } +} diff --git a/src/Ryujinx.Common/Configuration/ScalingFilter.cs b/src/Ryujinx.Common/Configuration/ScalingFilter.cs new file mode 100644 index 00000000..1c6a74b3 --- /dev/null +++ b/src/Ryujinx.Common/Configuration/ScalingFilter.cs @@ -0,0 +1,13 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum ScalingFilter + { + Bilinear, + Nearest, + Fsr, + } +} diff --git a/src/Ryujinx.Common/Configuration/TitleUpdateMetadata.cs b/src/Ryujinx.Common/Configuration/TitleUpdateMetadata.cs new file mode 100644 index 00000000..a1b14f6f --- /dev/null +++ b/src/Ryujinx.Common/Configuration/TitleUpdateMetadata.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Ryujinx.Common.Configuration +{ + public struct TitleUpdateMetadata + { + public string Selected { get; set; } + public List Paths { get; set; } + } +} diff --git a/src/Ryujinx.Common/Configuration/TitleUpdateMetadataJsonSerializerContext.cs b/src/Ryujinx.Common/Configuration/TitleUpdateMetadataJsonSerializerContext.cs new file mode 100644 index 00000000..a4b6efff --- /dev/null +++ b/src/Ryujinx.Common/Configuration/TitleUpdateMetadataJsonSerializerContext.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Configuration +{ + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(TitleUpdateMetadata))] + public partial class TitleUpdateMetadataJsonSerializerContext : JsonSerializerContext + { + } +} diff --git a/src/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs b/src/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs new file mode 100644 index 00000000..dece2384 --- /dev/null +++ b/src/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs @@ -0,0 +1,14 @@ +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common +{ + public static class BinaryReaderExtensions + { + public static T ReadStruct(this BinaryReader reader) where T : unmanaged + { + return MemoryMarshal.Cast(reader.ReadBytes(Unsafe.SizeOf()))[0]; + } + } +} diff --git a/src/Ryujinx.Common/Extensions/BinaryWriterExtensions.cs b/src/Ryujinx.Common/Extensions/BinaryWriterExtensions.cs new file mode 100644 index 00000000..9c3e7232 --- /dev/null +++ b/src/Ryujinx.Common/Extensions/BinaryWriterExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common +{ + public static class BinaryWriterExtensions + { + public static void WriteStruct(this BinaryWriter writer, T value) where T : unmanaged + { + ReadOnlySpan data = MemoryMarshal.Cast(MemoryMarshal.CreateReadOnlySpan(ref value, 1)); + + writer.Write(data); + } + + public static void Write(this BinaryWriter writer, UInt128 value) + { + writer.Write((ulong)value); + writer.Write((ulong)(value >> 64)); + } + + public static void Write(this BinaryWriter writer, MemoryStream stream) + { + stream.CopyTo(writer.BaseStream); + } + } +} diff --git a/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs b/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs new file mode 100644 index 00000000..79b5d743 --- /dev/null +++ b/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs @@ -0,0 +1,181 @@ +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Extensions +{ + public static class SequenceReaderExtensions + { + /// + /// Dumps the entire to a file, restoring its previous location afterward. + /// Useful for debugging purposes. + /// + /// The to write to a file + /// The path and name of the file to create and dump to + public static void DumpToFile(this ref SequenceReader reader, string fileFullName) + { + var initialConsumed = reader.Consumed; + + reader.Rewind(initialConsumed); + + using (var fileStream = System.IO.File.Create(fileFullName, 4096, System.IO.FileOptions.None)) + { + while (reader.End == false) + { + var span = reader.CurrentSpan; + fileStream.Write(span); + reader.Advance(span.Length); + } + } + + reader.SetConsumed(initialConsumed); + } + + /// + /// Returns a reference to the desired value. This ref should always be used. The argument passed in should never be used, as this is only used for storage if the value + /// must be copied from multiple segments held by the . + /// + /// Type to get + /// The to read from + /// A location used as storage if (and only if) the value to be read spans multiple segments + /// A reference to the desired value, either directly to memory in the , or to if it has been used for copying the value in to + /// + /// DO NOT use after calling this method, as it will only + /// contain a value if the value couldn't be referenced directly because it spans multiple segments. + /// To discourage use, it is recommended to call this method like the following: + /// + /// ref readonly MyStruct value = ref sequenceReader.GetRefOrRefToCopy{MyStruct}(out _); + /// + /// + /// The does not contain enough data to read a value of type + public static ref readonly T GetRefOrRefToCopy(this scoped ref SequenceReader reader, out T copyDestinationIfRequiredDoNotUse) where T : unmanaged + { + int lengthRequired = Unsafe.SizeOf(); + + ReadOnlySpan span = reader.UnreadSpan; + if (lengthRequired <= span.Length) + { + reader.Advance(lengthRequired); + + copyDestinationIfRequiredDoNotUse = default; + + ReadOnlySpan spanOfT = MemoryMarshal.Cast(span); + + return ref spanOfT[0]; + } + else + { + copyDestinationIfRequiredDoNotUse = default; + + Span valueSpan = MemoryMarshal.CreateSpan(ref copyDestinationIfRequiredDoNotUse, 1); + + Span valueBytesSpan = MemoryMarshal.AsBytes(valueSpan); + + if (!reader.TryCopyTo(valueBytesSpan)) + { + throw new ArgumentOutOfRangeException(nameof(reader), "The sequence is not long enough to read the desired value."); + } + + reader.Advance(lengthRequired); + + return ref valueSpan[0]; + } + } + + /// + /// Reads an as little endian. + /// + /// The to read from + /// A location to receive the read value + /// Thrown if there wasn't enough data for an + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadLittleEndian(this ref SequenceReader reader, out int value) + { + if (!reader.TryReadLittleEndian(out value)) + { + throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value."); + } + } + + /// + /// Reads the desired unmanaged value by copying it to the specified . + /// + /// Type to read + /// The to read from + /// The target that will receive the read value + /// The does not contain enough data to read a value of type + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadUnmanaged(this ref SequenceReader reader, out T value) where T : unmanaged + { + if (!reader.TryReadUnmanaged(out value)) + { + throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value."); + } + } + + /// + /// Sets the reader's position as bytes consumed. + /// + /// The to set the position + /// The number of bytes consumed + public static void SetConsumed(ref this SequenceReader reader, long consumed) + { + reader.Rewind(reader.Consumed); + reader.Advance(consumed); + } + + /// + /// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary + /// structs - see remarks for full details. + /// + /// Type to read + /// + /// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to + /// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit + /// overloads such as + /// + /// + /// True if successful. will be default if failed (due to lack of space). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe bool TryReadUnmanaged(ref this SequenceReader reader, out T value) where T : unmanaged + { + ReadOnlySpan span = reader.UnreadSpan; + + if (span.Length < sizeof(T)) + { + return TryReadUnmanagedMultiSegment(ref reader, out value); + } + + value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(span)); + + reader.Advance(sizeof(T)); + + return true; + } + + private static unsafe bool TryReadUnmanagedMultiSegment(ref SequenceReader reader, out T value) where T : unmanaged + { + Debug.Assert(reader.UnreadSpan.Length < sizeof(T)); + + // Not enough data in the current segment, try to peek for the data we need. + T buffer = default; + + Span tempSpan = new Span(&buffer, sizeof(T)); + + if (!reader.TryCopyTo(tempSpan)) + { + value = default; + return false; + } + + value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(tempSpan)); + + reader.Advance(sizeof(T)); + + return true; + } + } +} diff --git a/src/Ryujinx.Common/Extensions/StreamExtensions.cs b/src/Ryujinx.Common/Extensions/StreamExtensions.cs new file mode 100644 index 00000000..431d5534 --- /dev/null +++ b/src/Ryujinx.Common/Extensions/StreamExtensions.cs @@ -0,0 +1,138 @@ +using System; +using System.Buffers.Binary; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common +{ + public static class StreamExtensions + { + /// + /// Writes a " /> to this stream. + /// + /// This default implementation converts each buffer value to a stack-allocated + /// byte array, then writes it to the Stream using . + /// + /// The stream to be written to + /// The buffer of values to be written + public static void Write(this Stream stream, ReadOnlySpan buffer) + { + if (buffer.Length == 0) + { + return; + } + + if (BitConverter.IsLittleEndian) + { + ReadOnlySpan byteBuffer = MemoryMarshal.Cast(buffer); + stream.Write(byteBuffer); + } + else + { + Span byteBuffer = stackalloc byte[sizeof(int)]; + + foreach (int value in buffer) + { + BinaryPrimitives.WriteInt32LittleEndian(byteBuffer, value); + stream.Write(byteBuffer); + } + } + } + + /// + /// Writes a four-byte signed integer to this stream. The current position + /// of the stream is advanced by four. + /// + /// The stream to be written to + /// The value to be written + public static void Write(this Stream stream, int value) + { + Span buffer = stackalloc byte[sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(buffer, value); + stream.Write(buffer); + } + + /// + /// Writes an eight-byte signed integer to this stream. The current position + /// of the stream is advanced by eight. + /// + /// The stream to be written to + /// The value to be written + public static void Write(this Stream stream, long value) + { + Span buffer = stackalloc byte[sizeof(long)]; + BinaryPrimitives.WriteInt64LittleEndian(buffer, value); + stream.Write(buffer); + } + + /// + // Writes a four-byte unsigned integer to this stream. The current position + // of the stream is advanced by four. + /// + /// The stream to be written to + /// The value to be written + public static void Write(this Stream stream, uint value) + { + Span buffer = stackalloc byte[sizeof(uint)]; + BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); + stream.Write(buffer); + } + + /// + /// Writes an eight-byte unsigned integer to this stream. The current + /// position of the stream is advanced by eight. + /// + /// The stream to be written to + /// The value to be written + public static void Write(this Stream stream, ulong value) + { + Span buffer = stackalloc byte[sizeof(ulong)]; + BinaryPrimitives.WriteUInt64LittleEndian(buffer, value); + stream.Write(buffer); + } + + /// + /// Writes the contents of source to stream by calling source.CopyTo(stream). + /// Provides consistency with other Stream.Write methods. + /// + /// The stream to be written to + /// The stream to be read from + public static void Write(this Stream stream, Stream source) + { + source.CopyTo(stream); + } + + /// + /// Writes a sequence of bytes to the Stream. + /// + /// The stream to be written to. + /// The byte to be written + /// The number of times the value should be written + public static void WriteByte(this Stream stream, byte value, int count) + { + if (count <= 0) + { + return; + } + + const int BlockSize = 16; + + int blockCount = count / BlockSize; + if (blockCount > 0) + { + Span span = stackalloc byte[BlockSize]; + span.Fill(value); + for (int x = 0; x < blockCount; x++) + { + stream.Write(span); + } + } + + int nonBlockBytes = count % BlockSize; + for (int x = 0; x < nonBlockBytes; x++) + { + stream.WriteByte(value); + } + } + } +} diff --git a/src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs b/src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs new file mode 100644 index 00000000..a9163f34 --- /dev/null +++ b/src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs @@ -0,0 +1,42 @@ +using Ryujinx.Common.Utilities; +using System; + +namespace Ryujinx.Common.GraphicsDriver +{ + public static class DriverUtilities + { + private static void AddMesaFlags(string envVar, string newFlags) + { + string existingFlags = Environment.GetEnvironmentVariable(envVar); + + string flags = existingFlags == null ? newFlags : $"{existingFlags},{newFlags}"; + + OsUtils.SetEnvironmentVariableNoCaching(envVar, flags); + } + + public static void InitDriverConfig(bool oglThreading) + { + if (OperatingSystem.IsLinux()) + { + AddMesaFlags("RADV_DEBUG", "nodcc"); + } + + ToggleOGLThreading(oglThreading); + } + + public static void ToggleOGLThreading(bool enabled) + { + OsUtils.SetEnvironmentVariableNoCaching("mesa_glthread", enabled.ToString().ToLower()); + OsUtils.SetEnvironmentVariableNoCaching("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0"); + + try + { + NVThreadedOptimization.SetThreadedOptimization(enabled); + } + catch + { + // NVAPI is not available, or couldn't change the application profile. + } + } + } +} diff --git a/src/Ryujinx.Common/GraphicsDriver/NVAPI/Nvapi.cs b/src/Ryujinx.Common/GraphicsDriver/NVAPI/Nvapi.cs new file mode 100644 index 00000000..985e5e6d --- /dev/null +++ b/src/Ryujinx.Common/GraphicsDriver/NVAPI/Nvapi.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Common.GraphicsDriver.NVAPI +{ + enum Nvapi : uint + { + OglThreadControlId = 0x20C1221E, + + OglThreadControlDefault = 0, + OglThreadControlEnable = 1, + OglThreadControlDisable = 2, + } +} diff --git a/src/Ryujinx.Common/GraphicsDriver/NVAPI/NvapiUnicodeString.cs b/src/Ryujinx.Common/GraphicsDriver/NVAPI/NvapiUnicodeString.cs new file mode 100644 index 00000000..f3e0ffd0 --- /dev/null +++ b/src/Ryujinx.Common/GraphicsDriver/NVAPI/NvapiUnicodeString.cs @@ -0,0 +1,42 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Common.GraphicsDriver.NVAPI +{ + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public unsafe struct NvapiUnicodeString + { + private fixed byte _data[4096]; + + public NvapiUnicodeString(string text) + { + Set(text); + } + + public readonly string Get() + { + fixed (byte* data = _data) + { + string text = Encoding.Unicode.GetString(data, 4096); + + int index = text.IndexOf('\0'); + if (index > -1) + { + text = text.Remove(index); + } + + return text; + } + } + + public readonly void Set(string text) + { + text += '\0'; + fixed (char* textPtr = text) + fixed (byte* data = _data) + { + int written = Encoding.Unicode.GetBytes(textPtr, text.Length, data, 4096); + } + } + } +} diff --git a/src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsApplicationV4.cs b/src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsApplicationV4.cs new file mode 100644 index 00000000..39820bfe --- /dev/null +++ b/src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsApplicationV4.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.GraphicsDriver.NVAPI +{ + [StructLayout(LayoutKind.Sequential, Pack = 4)] + struct NvdrsApplicationV4 + { + public uint Version; + public uint IsPredefined; + public NvapiUnicodeString AppName; + public NvapiUnicodeString UserFriendlyName; + public NvapiUnicodeString Launcher; + public NvapiUnicodeString FileInFolder; + public uint Flags; + public NvapiUnicodeString CommandLine; + } +} diff --git a/src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsProfile.cs b/src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsProfile.cs new file mode 100644 index 00000000..f9fe4ed4 --- /dev/null +++ b/src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsProfile.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.GraphicsDriver.NVAPI +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct NvdrsProfile + { + public uint Version; + public NvapiUnicodeString ProfileName; + public uint GpuSupport; + public uint IsPredefined; + public uint NumOfApps; + public uint NumOfSettings; + } +} diff --git a/src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsSetting.cs b/src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsSetting.cs new file mode 100644 index 00000000..a36781ba --- /dev/null +++ b/src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsSetting.cs @@ -0,0 +1,49 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.GraphicsDriver.NVAPI +{ + enum NvdrsSettingType : uint + { + NvdrsDwordType, + NvdrsBinaryType, + NvdrsStringType, + NvdrsWstringType, + } + + enum NvdrsSettingLocation : uint + { + NvdrsCurrentProfileLocation, + NvdrsGlobalProfileLocation, + NvdrsBaseProfileLocation, + NvdrsDefaultProfileLocation, + } + + [StructLayout(LayoutKind.Explicit, Size = 0x3020)] + struct NvdrsSetting + { + [FieldOffset(0x0)] + public uint Version; + [FieldOffset(0x4)] + public NvapiUnicodeString SettingName; + [FieldOffset(0x1004)] + public Nvapi SettingId; + [FieldOffset(0x1008)] + public NvdrsSettingType SettingType; + [FieldOffset(0x100C)] + public NvdrsSettingLocation SettingLocation; + [FieldOffset(0x1010)] + public uint IsCurrentPredefined; + [FieldOffset(0x1014)] + public uint IsPredefinedValid; + + [FieldOffset(0x1018)] + public uint PredefinedValue; + [FieldOffset(0x1018)] + public NvapiUnicodeString PredefinedString; + + [FieldOffset(0x201C)] + public uint CurrentValue; + [FieldOffset(0x201C)] + public NvapiUnicodeString CurrentString; + } +} diff --git a/src/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs b/src/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs new file mode 100644 index 00000000..f7b11783 --- /dev/null +++ b/src/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs @@ -0,0 +1,160 @@ +using Ryujinx.Common.GraphicsDriver.NVAPI; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.GraphicsDriver +{ + static partial class NVThreadedOptimization + { + private const string ProfileName = "Ryujinx Nvidia Profile"; + + private const uint NvAPI_Initialize_ID = 0x0150E828; + private const uint NvAPI_DRS_CreateSession_ID = 0x0694D52E; + private const uint NvAPI_DRS_LoadSettings_ID = 0x375DBD6B; + private const uint NvAPI_DRS_FindProfileByName_ID = 0x7E4A9A0B; + private const uint NvAPI_DRS_CreateProfile_ID = 0x0CC176068; + private const uint NvAPI_DRS_CreateApplication_ID = 0x4347A9DE; + private const uint NvAPI_DRS_SetSetting_ID = 0x577DD202; + private const uint NvAPI_DRS_SaveSettings_ID = 0xFCBC7E14; + private const uint NvAPI_DRS_DestroySession_ID = 0x0DAD9CFF8; + + [LibraryImport("nvapi64")] + private static partial IntPtr nvapi_QueryInterface(uint id); + + private delegate int NvAPI_InitializeDelegate(); + private static NvAPI_InitializeDelegate NvAPI_Initialize; + + private delegate int NvAPI_DRS_CreateSessionDelegate(out IntPtr handle); + private static NvAPI_DRS_CreateSessionDelegate NvAPI_DRS_CreateSession; + + private delegate int NvAPI_DRS_LoadSettingsDelegate(IntPtr handle); + private static NvAPI_DRS_LoadSettingsDelegate NvAPI_DRS_LoadSettings; + + private delegate int NvAPI_DRS_FindProfileByNameDelegate(IntPtr handle, NvapiUnicodeString profileName, out IntPtr profileHandle); + private static NvAPI_DRS_FindProfileByNameDelegate NvAPI_DRS_FindProfileByName; + + private delegate int NvAPI_DRS_CreateProfileDelegate(IntPtr handle, ref NvdrsProfile profileInfo, out IntPtr profileHandle); + private static NvAPI_DRS_CreateProfileDelegate NvAPI_DRS_CreateProfile; + + private delegate int NvAPI_DRS_CreateApplicationDelegate(IntPtr handle, IntPtr profileHandle, ref NvdrsApplicationV4 app); + private static NvAPI_DRS_CreateApplicationDelegate NvAPI_DRS_CreateApplication; + + private delegate int NvAPI_DRS_SetSettingDelegate(IntPtr handle, IntPtr profileHandle, ref NvdrsSetting setting); + private static NvAPI_DRS_SetSettingDelegate NvAPI_DRS_SetSetting; + + private delegate int NvAPI_DRS_SaveSettingsDelegate(IntPtr handle); + private static NvAPI_DRS_SaveSettingsDelegate NvAPI_DRS_SaveSettings; + + private delegate int NvAPI_DRS_DestroySessionDelegate(IntPtr handle); + private static NvAPI_DRS_DestroySessionDelegate NvAPI_DRS_DestroySession; + + private static bool _initialized; + + private static void Check(int status) + { + if (status != 0) + { + throw new Exception($"NVAPI Error: {status}"); + } + } + + private static void Initialize() + { + if (!_initialized) + { + NvAPI_Initialize = NvAPI_Delegate(NvAPI_Initialize_ID); + + Check(NvAPI_Initialize()); + + NvAPI_DRS_CreateSession = NvAPI_Delegate(NvAPI_DRS_CreateSession_ID); + NvAPI_DRS_LoadSettings = NvAPI_Delegate(NvAPI_DRS_LoadSettings_ID); + NvAPI_DRS_FindProfileByName = NvAPI_Delegate(NvAPI_DRS_FindProfileByName_ID); + NvAPI_DRS_CreateProfile = NvAPI_Delegate(NvAPI_DRS_CreateProfile_ID); + NvAPI_DRS_CreateApplication = NvAPI_Delegate(NvAPI_DRS_CreateApplication_ID); + NvAPI_DRS_SetSetting = NvAPI_Delegate(NvAPI_DRS_SetSetting_ID); + NvAPI_DRS_SaveSettings = NvAPI_Delegate(NvAPI_DRS_SaveSettings_ID); + NvAPI_DRS_DestroySession = NvAPI_Delegate(NvAPI_DRS_DestroySession_ID); + + _initialized = true; + } + } + + private static uint MakeVersion(uint version) where T : unmanaged + { + return (uint)Unsafe.SizeOf() | version << 16; + } + + public static void SetThreadedOptimization(bool enabled) + { + Initialize(); + + uint targetValue = (uint)(enabled ? Nvapi.OglThreadControlEnable : Nvapi.OglThreadControlDisable); + + Check(NvAPI_Initialize()); + + Check(NvAPI_DRS_CreateSession(out IntPtr handle)); + + Check(NvAPI_DRS_LoadSettings(handle)); + + // Check if the profile already exists. + + int status = NvAPI_DRS_FindProfileByName(handle, new NvapiUnicodeString(ProfileName), out nint profileHandle); + + if (status != 0) + { + NvdrsProfile profile = new() + { + Version = MakeVersion(1), + IsPredefined = 0, + GpuSupport = uint.MaxValue, + }; + profile.ProfileName.Set(ProfileName); + Check(NvAPI_DRS_CreateProfile(handle, ref profile, out profileHandle)); + + NvdrsApplicationV4 application = new() + { + Version = MakeVersion(4), + IsPredefined = 0, + Flags = 3, // IsMetro, IsCommandLine + }; + application.AppName.Set("Ryujinx.exe"); + application.UserFriendlyName.Set("Ryujinx"); + application.Launcher.Set(""); + application.FileInFolder.Set(""); + + Check(NvAPI_DRS_CreateApplication(handle, profileHandle, ref application)); + } + + NvdrsSetting setting = new() + { + Version = MakeVersion(1), + SettingId = Nvapi.OglThreadControlId, + SettingType = NvdrsSettingType.NvdrsDwordType, + SettingLocation = NvdrsSettingLocation.NvdrsCurrentProfileLocation, + IsCurrentPredefined = 0, + IsPredefinedValid = 0, + CurrentValue = targetValue, + PredefinedValue = targetValue, + }; + + Check(NvAPI_DRS_SetSetting(handle, profileHandle, ref setting)); + + Check(NvAPI_DRS_SaveSettings(handle)); + + NvAPI_DRS_DestroySession(handle); + } + + private static T NvAPI_Delegate(uint id) where T : class + { + IntPtr ptr = nvapi_QueryInterface(id); + + if (ptr != IntPtr.Zero) + { + return Marshal.GetDelegateForFunctionPointer(ptr); + } + + return null; + } + } +} diff --git a/src/Ryujinx.Common/Hash128.cs b/src/Ryujinx.Common/Hash128.cs new file mode 100644 index 00000000..e0ffd230 --- /dev/null +++ b/src/Ryujinx.Common/Hash128.cs @@ -0,0 +1,48 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common +{ + [StructLayout(LayoutKind.Sequential)] + public struct Hash128 : IEquatable + { + public ulong Low; + public ulong High; + + public Hash128(ulong low, ulong high) + { + Low = low; + High = high; + } + + public readonly override string ToString() + { + return $"{High:x16}{Low:x16}"; + } + + public static bool operator ==(Hash128 x, Hash128 y) + { + return x.Equals(y); + } + + public static bool operator !=(Hash128 x, Hash128 y) + { + return !x.Equals(y); + } + + public readonly override bool Equals(object obj) + { + return obj is Hash128 hash128 && Equals(hash128); + } + + public readonly bool Equals(Hash128 cmpObj) + { + return Low == cmpObj.Low && High == cmpObj.High; + } + + public readonly override int GetHashCode() + { + return HashCode.Combine(Low, High); + } + } +} diff --git a/src/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs b/src/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs new file mode 100644 index 00000000..b1cffcb5 --- /dev/null +++ b/src/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs @@ -0,0 +1,51 @@ +using System.Diagnostics; +using System.Text; + +namespace Ryujinx.Common.Logging.Formatters +{ + internal class DefaultLogFormatter : ILogFormatter + { + private static readonly ObjectPool _stringBuilderPool = SharedPools.Default(); + + public string Format(LogEventArgs args) + { + StringBuilder sb = _stringBuilderPool.Allocate(); + + try + { + sb.Clear(); + + sb.Append($@"{args.Time:hh\:mm\:ss\.fff}"); + sb.Append($" |{args.Level.ToString()[0]}| "); + + if (args.ThreadName != null) + { + sb.Append(args.ThreadName); + sb.Append(' '); + } + + sb.Append(args.Message); + + if (args.Data is not null) + { + if (args.Data is StackTrace trace) + { + sb.Append('\n'); + sb.Append(trace); + + return sb.ToString(); + } + + sb.Append(' '); + DynamicObjectFormatter.Format(sb, args.Data); + } + + return sb.ToString(); + } + finally + { + _stringBuilderPool.Release(sb); + } + } + } +} diff --git a/src/Ryujinx.Common/Logging/Formatters/DynamicObjectFormatter.cs b/src/Ryujinx.Common/Logging/Formatters/DynamicObjectFormatter.cs new file mode 100644 index 00000000..b1cc0eae --- /dev/null +++ b/src/Ryujinx.Common/Logging/Formatters/DynamicObjectFormatter.cs @@ -0,0 +1,84 @@ +#nullable enable +using System; +using System.Reflection; +using System.Text; + +namespace Ryujinx.Common.Logging.Formatters +{ + internal static class DynamicObjectFormatter + { + private static readonly ObjectPool _stringBuilderPool = SharedPools.Default(); + + public static string? Format(object? dynamicObject) + { + if (dynamicObject is null) + { + return null; + } + + StringBuilder sb = _stringBuilderPool.Allocate(); + + try + { + Format(sb, dynamicObject); + + return sb.ToString(); + } + finally + { + _stringBuilderPool.Release(sb); + } + } + + public static void Format(StringBuilder sb, object? dynamicObject) + { + if (dynamicObject is null) + { + return; + } + + PropertyInfo[] props = dynamicObject.GetType().GetProperties(); + + sb.Append('{'); + + foreach (var prop in props) + { + sb.Append(prop.Name); + sb.Append(": "); + + if (typeof(Array).IsAssignableFrom(prop.PropertyType)) + { + Array? array = (Array?)prop.GetValue(dynamicObject); + + if (array is not null) + { + foreach (var item in array) + { + sb.Append(item); + sb.Append(", "); + } + + if (array.Length > 0) + { + sb.Remove(sb.Length - 2, 2); + } + } + } + else + { + sb.Append(prop.GetValue(dynamicObject)); + } + + sb.Append(" ; "); + } + + // We remove the final ';' from the string + if (props.Length > 0) + { + sb.Remove(sb.Length - 3, 3); + } + + sb.Append('}'); + } + } +} diff --git a/src/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs b/src/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs new file mode 100644 index 00000000..3a2261a6 --- /dev/null +++ b/src/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Common.Logging.Formatters +{ + interface ILogFormatter + { + string Format(LogEventArgs args); + } +} diff --git a/src/Ryujinx.Common/Logging/LogClass.cs b/src/Ryujinx.Common/Logging/LogClass.cs new file mode 100644 index 00000000..1b404a06 --- /dev/null +++ b/src/Ryujinx.Common/Logging/LogClass.cs @@ -0,0 +1,76 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Logging +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum LogClass + { + Application, + Audio, + AudioRenderer, + Configuration, + Cpu, + Emulation, + FFmpeg, + Font, + Gpu, + Hid, + Host1x, + Kernel, + KernelIpc, + KernelScheduler, + KernelSvc, + Loader, + ModLoader, + Nvdec, + Ptc, + Service, + ServiceAcc, + ServiceAm, + ServiceApm, + ServiceAudio, + ServiceBcat, + ServiceBsd, + ServiceBtm, + ServiceCaps, + ServiceFatal, + ServiceFriend, + ServiceFs, + ServiceHid, + ServiceIrs, + ServiceLdn, + ServiceLdr, + ServiceLm, + ServiceMii, + ServiceMm, + ServiceMnpp, + ServiceNfc, + ServiceNfp, + ServiceNgct, + ServiceNifm, + ServiceNim, + ServiceNs, + ServiceNsd, + ServiceNtc, + ServiceNv, + ServiceOlsc, + ServicePctl, + ServicePcv, + ServicePl, + ServicePrepo, + ServicePsm, + ServicePtm, + ServiceSet, + ServiceSfdnsres, + ServiceSm, + ServiceSsl, + ServiceSss, + ServiceTime, + ServiceVi, + SurfaceFlinger, + TamperMachine, + UI, + Vic, + } +} diff --git a/src/Ryujinx.Common/Logging/LogEventArgs.cs b/src/Ryujinx.Common/Logging/LogEventArgs.cs new file mode 100644 index 00000000..264f8f3a --- /dev/null +++ b/src/Ryujinx.Common/Logging/LogEventArgs.cs @@ -0,0 +1,23 @@ +using System; + +namespace Ryujinx.Common.Logging +{ + public class LogEventArgs : EventArgs + { + public readonly LogLevel Level; + public readonly TimeSpan Time; + public readonly string ThreadName; + + public readonly string Message; + public readonly object Data; + + public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data = null) + { + Level = level; + Time = time; + ThreadName = threadName; + Message = message; + Data = data; + } + } +} diff --git a/src/Ryujinx.Common/Logging/LogEventArgsJson.cs b/src/Ryujinx.Common/Logging/LogEventArgsJson.cs new file mode 100644 index 00000000..7c745d63 --- /dev/null +++ b/src/Ryujinx.Common/Logging/LogEventArgsJson.cs @@ -0,0 +1,31 @@ +using Ryujinx.Common.Logging.Formatters; +using System; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Logging +{ + internal class LogEventArgsJson + { + public LogLevel Level { get; } + public TimeSpan Time { get; } + public string ThreadName { get; } + + public string Message { get; } + public string Data { get; } + + [JsonConstructor] + public LogEventArgsJson(LogLevel level, TimeSpan time, string threadName, string message, string data = null) + { + Level = level; + Time = time; + ThreadName = threadName; + Message = message; + Data = data; + } + + public static LogEventArgsJson FromLogEventArgs(LogEventArgs args) + { + return new LogEventArgsJson(args.Level, args.Time, args.ThreadName, args.Message, DynamicObjectFormatter.Format(args.Data)); + } + } +} diff --git a/src/Ryujinx.Common/Logging/LogEventJsonSerializerContext.cs b/src/Ryujinx.Common/Logging/LogEventJsonSerializerContext.cs new file mode 100644 index 00000000..ac63d9a1 --- /dev/null +++ b/src/Ryujinx.Common/Logging/LogEventJsonSerializerContext.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Logging +{ + [JsonSerializable(typeof(LogEventArgsJson))] + internal partial class LogEventJsonSerializerContext : JsonSerializerContext + { + } +} diff --git a/src/Ryujinx.Common/Logging/LogLevel.cs b/src/Ryujinx.Common/Logging/LogLevel.cs new file mode 100644 index 00000000..54261cee --- /dev/null +++ b/src/Ryujinx.Common/Logging/LogLevel.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Logging +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum LogLevel + { + Debug, + Stub, + Info, + Warning, + Error, + Guest, + AccessLog, + Notice, + Trace, + } +} diff --git a/src/Ryujinx.Common/Logging/Logger.cs b/src/Ryujinx.Common/Logging/Logger.cs new file mode 100644 index 00000000..db46739a --- /dev/null +++ b/src/Ryujinx.Common/Logging/Logger.cs @@ -0,0 +1,246 @@ +using Ryujinx.Common.Logging.Targets; +using Ryujinx.Common.SystemInterop; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.Common.Logging +{ + public static class Logger + { + private static readonly Stopwatch _time; + + private static readonly bool[] _enabledClasses; + + private static readonly List _logTargets; + + private static readonly StdErrAdapter _stdErrAdapter; + + public static event EventHandler Updated; + + public readonly struct Log + { + private static readonly string _homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + private static readonly string _homeDirRedacted = Path.Combine(Directory.GetParent(_homeDir).FullName, "[redacted]"); + + internal readonly LogLevel Level; + + internal Log(LogLevel level) + { + Level = level; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PrintMsg(LogClass logClass, string message) + { + if (_enabledClasses[(int)logClass]) + { + Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, "", message))); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Print(LogClass logClass, string message, [CallerMemberName] string caller = "") + { + if (_enabledClasses[(int)logClass]) + { + Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message))); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Print(LogClass logClass, string message, object data, [CallerMemberName] string caller = "") + { + if (_enabledClasses[(int)logClass]) + { + Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message), data)); + } + } + + [StackTraceHidden] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PrintStack(LogClass logClass, string message, [CallerMemberName] string caller = "") + { + if (_enabledClasses[(int)logClass]) + { + Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message), new StackTrace(true))); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PrintStub(LogClass logClass, string message = "", [CallerMemberName] string caller = "") + { + if (_enabledClasses[(int)logClass]) + { + Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed. " + message))); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PrintStub(LogClass logClass, object data, [CallerMemberName] string caller = "") + { + if (_enabledClasses[(int)logClass]) + { + Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed."), data)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PrintStub(LogClass logClass, string message, object data, [CallerMemberName] string caller = "") + { + if (_enabledClasses[(int)logClass]) + { + Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed. " + message), data)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PrintRawMsg(string message) + { + Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, message)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string FormatMessage(LogClass logClass, string caller, string message) + { + message = message.Replace(_homeDir, _homeDirRedacted); + + return $"{logClass} {caller}: {message}"; + } + } + + public static Log? Debug { get; private set; } + public static Log? Info { get; private set; } + public static Log? Warning { get; private set; } + public static Log? Error { get; private set; } + public static Log? Guest { get; private set; } + public static Log? AccessLog { get; private set; } + public static Log? Stub { get; private set; } + public static Log? Trace { get; private set; } + public static Log Notice { get; } // Always enabled + + static Logger() + { + _enabledClasses = new bool[Enum.GetNames().Length]; + + for (int index = 0; index < _enabledClasses.Length; index++) + { + _enabledClasses[index] = true; + } + + _logTargets = new List(); + + _time = Stopwatch.StartNew(); + + // Logger should log to console by default + AddTarget(new AsyncLogTargetWrapper( + new ConsoleLogTarget("console"), + 1000, + AsyncLogTargetOverflowAction.Discard)); + + Notice = new Log(LogLevel.Notice); + + // Enable important log levels before configuration is loaded + Error = new Log(LogLevel.Error); + Warning = new Log(LogLevel.Warning); + Info = new Log(LogLevel.Info); + Trace = new Log(LogLevel.Trace); + + _stdErrAdapter = new StdErrAdapter(); + } + + public static void RestartTime() + { + _time.Restart(); + } + + private static ILogTarget GetTarget(string targetName) + { + foreach (var target in _logTargets) + { + if (target.Name.Equals(targetName)) + { + return target; + } + } + + return null; + } + + public static void AddTarget(ILogTarget target) + { + _logTargets.Add(target); + + Updated += target.Log; + } + + public static void RemoveTarget(string target) + { + ILogTarget logTarget = GetTarget(target); + + if (logTarget != null) + { + Updated -= logTarget.Log; + + _logTargets.Remove(logTarget); + + logTarget.Dispose(); + } + } + + public static void Shutdown() + { + Updated = null; + + _stdErrAdapter.Dispose(); + + foreach (var target in _logTargets) + { + target.Dispose(); + } + + _logTargets.Clear(); + } + + public static IReadOnlyCollection GetEnabledLevels() + { + var logs = new[] { Debug, Info, Warning, Error, Guest, AccessLog, Stub, Trace }; + List levels = new(logs.Length); + foreach (var log in logs) + { + if (log.HasValue) + { + levels.Add(log.Value.Level); + } + } + + return levels; + } + + public static void SetEnable(LogLevel logLevel, bool enabled) + { + switch (logLevel) + { +#pragma warning disable IDE0055 // Disable formatting + case LogLevel.Debug : Debug = enabled ? new Log(LogLevel.Debug) : new Log?(); break; + case LogLevel.Info : Info = enabled ? new Log(LogLevel.Info) : new Log?(); break; + case LogLevel.Warning : Warning = enabled ? new Log(LogLevel.Warning) : new Log?(); break; + case LogLevel.Error : Error = enabled ? new Log(LogLevel.Error) : new Log?(); break; + case LogLevel.Guest : Guest = enabled ? new Log(LogLevel.Guest) : new Log?(); break; + case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog) : new Log?(); break; + case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : new Log?(); break; + case LogLevel.Trace : Trace = enabled ? new Log(LogLevel.Trace) : new Log?(); break; + default: throw new ArgumentException("Unknown Log Level"); +#pragma warning restore IDE0055 + } + } + + public static void SetEnable(LogClass logClass, bool enabled) + { + _enabledClasses[(int)logClass] = enabled; + } + } +} diff --git a/src/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs b/src/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs new file mode 100644 index 00000000..02c6dc97 --- /dev/null +++ b/src/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.Common.Logging.Targets +{ + public enum AsyncLogTargetOverflowAction + { + /// + /// Block until there's more room in the queue + /// + Block = 0, + + /// + /// Discard the overflowing item + /// + Discard = 1, + } + + public class AsyncLogTargetWrapper : ILogTarget + { + private readonly ILogTarget _target; + + private readonly Thread _messageThread; + + private readonly BlockingCollection _messageQueue; + + private readonly int _overflowTimeout; + + string ILogTarget.Name { get => _target.Name; } + + public AsyncLogTargetWrapper(ILogTarget target) + : this(target, -1, AsyncLogTargetOverflowAction.Block) + { } + + public AsyncLogTargetWrapper(ILogTarget target, int queueLimit, AsyncLogTargetOverflowAction overflowAction) + { + _target = target; + _messageQueue = new BlockingCollection(queueLimit); + _overflowTimeout = overflowAction == AsyncLogTargetOverflowAction.Block ? -1 : 0; + + _messageThread = new Thread(() => + { + while (!_messageQueue.IsCompleted) + { + try + { + _target.Log(this, _messageQueue.Take()); + } + catch (InvalidOperationException) + { + // IOE means that Take() was called on a completed collection. + // Some other thread can call CompleteAdding after we pass the + // IsCompleted check but before we call Take. + // We can simply catch the exception since the loop will break + // on the next iteration. + } + } + }) + { + Name = "Logger.MessageThread", + IsBackground = true, + }; + _messageThread.Start(); + } + + public void Log(object sender, LogEventArgs e) + { + if (!_messageQueue.IsAddingCompleted) + { + _messageQueue.TryAdd(e, _overflowTimeout); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _messageQueue.CompleteAdding(); + _messageThread.Join(); + } + } +} diff --git a/src/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs b/src/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs new file mode 100644 index 00000000..42ba00cd --- /dev/null +++ b/src/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs @@ -0,0 +1,44 @@ +using Ryujinx.Common.Logging.Formatters; +using System; + +namespace Ryujinx.Common.Logging.Targets +{ + public class ConsoleLogTarget : ILogTarget + { + private readonly ILogFormatter _formatter; + + private readonly string _name; + + string ILogTarget.Name { get => _name; } + + private static ConsoleColor GetLogColor(LogLevel level) => level switch + { + LogLevel.Info => ConsoleColor.White, + LogLevel.Warning => ConsoleColor.Yellow, + LogLevel.Error => ConsoleColor.Red, + LogLevel.Stub => ConsoleColor.DarkGray, + LogLevel.Notice => ConsoleColor.Cyan, + LogLevel.Trace => ConsoleColor.DarkCyan, + _ => ConsoleColor.Gray, + }; + + public ConsoleLogTarget(string name) + { + _formatter = new DefaultLogFormatter(); + _name = name; + } + + public void Log(object sender, LogEventArgs args) + { + Console.ForegroundColor = GetLogColor(args.Level); + Console.WriteLine(_formatter.Format(args)); + Console.ResetColor(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Console.ResetColor(); + } + } +} diff --git a/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs b/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs new file mode 100644 index 00000000..8d4ede96 --- /dev/null +++ b/src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs @@ -0,0 +1,108 @@ +using Ryujinx.Common.Logging.Formatters; +using System; +using System.IO; +using System.Linq; + +namespace Ryujinx.Common.Logging.Targets +{ + public class FileLogTarget : ILogTarget + { + private readonly StreamWriter _logWriter; + private readonly ILogFormatter _formatter; + private readonly string _name; + + string ILogTarget.Name { get => _name; } + + public FileLogTarget(string name, FileStream fileStream) + { + _name = name; + _logWriter = new StreamWriter(fileStream); + _formatter = new DefaultLogFormatter(); + } + + public static FileStream PrepareLogFile(string path) + { + // Ensure directory is present + DirectoryInfo logDir; + try + { + logDir = new DirectoryInfo(path); + } + catch (ArgumentException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory path ('{path}') was invalid: {exception}"); + + return null; + } + + try + { + logDir.Create(); + } + catch (IOException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}': {exception}"); + + return null; + } + + // Clean up old logs, should only keep 3 + FileInfo[] files = logDir.GetFiles("*.log").OrderBy((info => info.CreationTime)).ToArray(); + for (int i = 0; i < files.Length - 2; i++) + { + try + { + files[i].Delete(); + } + catch (UnauthorizedAccessException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}"); + + return null; + } + catch (IOException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}"); + + return null; + } + } + + string version = ReleaseInformation.Version; + + // Get path for the current time + path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log"); + + try + { + return File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read); + } + catch (UnauthorizedAccessException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}"); + + return null; + } + catch (IOException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}"); + + return null; + } + } + + public void Log(object sender, LogEventArgs args) + { + _logWriter.WriteLine(_formatter.Format(args)); + _logWriter.Flush(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _logWriter.WriteLine("---- End of Log ----"); + _logWriter.Flush(); + _logWriter.Dispose(); + } + } +} diff --git a/src/Ryujinx.Common/Logging/Targets/ILogTarget.cs b/src/Ryujinx.Common/Logging/Targets/ILogTarget.cs new file mode 100644 index 00000000..2d7aca4f --- /dev/null +++ b/src/Ryujinx.Common/Logging/Targets/ILogTarget.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.Common.Logging.Targets +{ + public interface ILogTarget : IDisposable + { + void Log(object sender, LogEventArgs args); + + string Name { get; } + } +} diff --git a/src/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs b/src/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs new file mode 100644 index 00000000..c5bf23cd --- /dev/null +++ b/src/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs @@ -0,0 +1,42 @@ +using Ryujinx.Common.Utilities; +using System; +using System.IO; + +namespace Ryujinx.Common.Logging.Targets +{ + public class JsonLogTarget : ILogTarget + { + private readonly Stream _stream; + private readonly bool _leaveOpen; + private readonly string _name; + + string ILogTarget.Name { get => _name; } + + public JsonLogTarget(Stream stream, string name) + { + _stream = stream; + _name = name; + } + + public JsonLogTarget(Stream stream, bool leaveOpen) + { + _stream = stream; + _leaveOpen = leaveOpen; + } + + public void Log(object sender, LogEventArgs e) + { + var logEventArgsJson = LogEventArgsJson.FromLogEventArgs(e); + JsonHelper.SerializeToStream(_stream, logEventArgsJson, LogEventJsonSerializerContext.Default.LogEventArgsJson); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + if (!_leaveOpen) + { + _stream.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Common/Memory/ArrayPtr.cs b/src/Ryujinx.Common/Memory/ArrayPtr.cs new file mode 100644 index 00000000..7487a1ff --- /dev/null +++ b/src/Ryujinx.Common/Memory/ArrayPtr.cs @@ -0,0 +1,123 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Memory +{ + /// + /// Represents an array of unmanaged resources. + /// + /// Array element type + public unsafe struct ArrayPtr : IEquatable>, IArray where T : unmanaged + { + private IntPtr _ptr; + + /// + /// Null pointer. + /// + public static ArrayPtr Null => new() { _ptr = IntPtr.Zero }; + + /// + /// True if the pointer is null, false otherwise. + /// + public readonly bool IsNull => _ptr == IntPtr.Zero; + + /// + /// Number of elements on the array. + /// + public int Length { get; } + + /// + /// Gets a reference to the item at the given index. + /// + /// + /// No bounds checks are performed, this allows negative indexing, + /// but care must be taken if the index may be out of bounds. + /// + /// Index of the element + /// Reference to the element at the given index + public readonly ref T this[int index] => ref Unsafe.AsRef((T*)_ptr + index); + + /// + /// Creates a new array from a given reference. + /// + /// + /// For data on the heap, proper pinning is necessary during + /// use. Failure to do so will result in memory corruption and crashes. + /// + /// Reference of the first array element + /// Number of elements on the array + public ArrayPtr(ref T value, int length) + { + _ptr = (IntPtr)Unsafe.AsPointer(ref value); + Length = length; + } + + /// + /// Creates a new array from a given pointer. + /// + /// Array base pointer + /// Number of elements on the array + public ArrayPtr(T* ptr, int length) + { + _ptr = (IntPtr)ptr; + Length = length; + } + + /// + /// Creates a new array from a given pointer. + /// + /// Array base pointer + /// Number of elements on the array + public ArrayPtr(IntPtr ptr, int length) + { + _ptr = ptr; + Length = length; + } + + /// + /// Splits the array starting at the specified position. + /// + /// Index where the new array should start + /// New array starting at the specified position + public ArrayPtr Slice(int start) => new(ref this[start], Length - start); + + /// + /// Gets a span from the array. + /// + /// Span of the array + public Span AsSpan() => Length == 0 ? Span.Empty : MemoryMarshal.CreateSpan(ref this[0], Length); + + /// + /// Gets the array base pointer. + /// + /// Base pointer + public readonly T* ToPointer() => (T*)_ptr; + + public readonly override bool Equals(object obj) + { + return obj is ArrayPtr other && Equals(other); + } + + public readonly bool Equals([AllowNull] ArrayPtr other) + { + return _ptr == other._ptr && Length == other.Length; + } + + public readonly override int GetHashCode() + { + return HashCode.Combine(_ptr, Length); + } + + public static bool operator ==(ArrayPtr left, ArrayPtr right) + { + return left.Equals(right); + } + + public static bool operator !=(ArrayPtr left, ArrayPtr right) + { + return !(left == right); + } + } +} diff --git a/src/Ryujinx.Common/Memory/Box.cs b/src/Ryujinx.Common/Memory/Box.cs new file mode 100644 index 00000000..e7dd76a6 --- /dev/null +++ b/src/Ryujinx.Common/Memory/Box.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Common.Memory +{ + public class Box where T : unmanaged + { + public T Data; + + public Box() + { + Data = new T(); + } + } +} diff --git a/src/Ryujinx.Common/Memory/IArray.cs b/src/Ryujinx.Common/Memory/IArray.cs new file mode 100644 index 00000000..3e0385c2 --- /dev/null +++ b/src/Ryujinx.Common/Memory/IArray.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.Common.Memory +{ + /// + /// Array interface. + /// + /// Element type + public interface IArray where T : unmanaged + { + /// + /// Used to index the array. + /// + /// Element index + /// Element at the specified index + ref T this[int index] { get; } + + /// + /// Number of elements on the array. + /// + int Length { get; } + } +} diff --git a/src/Ryujinx.Common/Memory/MemoryOwner.cs b/src/Ryujinx.Common/Memory/MemoryOwner.cs new file mode 100644 index 00000000..b7fe1db7 --- /dev/null +++ b/src/Ryujinx.Common/Memory/MemoryOwner.cs @@ -0,0 +1,140 @@ +#nullable enable +using System; +using System.Buffers; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Common.Memory +{ + /// + /// An implementation with an embedded length and fast + /// accessor, with memory allocated from . + /// + /// The type of item to store. + public sealed class MemoryOwner : IMemoryOwner + { + private readonly int _length; + private T[]? _array; + + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The length of the new memory buffer to use + private MemoryOwner(int length) + { + _length = length; + _array = ArrayPool.Shared.Rent(length); + } + + /// + /// Creates a new instance with the specified length. + /// + /// The length of the new memory buffer to use + /// A instance of the requested length + /// Thrown when is not valid + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryOwner Rent(int length) => new(length); + + /// + /// Creates a new instance with the specified length and the content cleared. + /// + /// The length of the new memory buffer to use + /// A instance of the requested length and the content cleared + /// Thrown when is not valid + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryOwner RentCleared(int length) + { + MemoryOwner result = new(length); + + result._array.AsSpan(0, length).Clear(); + + return result; + } + + /// + /// Creates a new instance with the content copied from the specified buffer. + /// + /// The buffer to copy + /// A instance with the same length and content as + public static MemoryOwner RentCopy(ReadOnlySpan buffer) + { + MemoryOwner result = new(buffer.Length); + + buffer.CopyTo(result._array); + + return result; + } + + /// + /// Gets the number of items in the current instance. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _length; + } + + /// + public Memory Memory + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + T[]? array = _array; + + if (array is null) + { + ThrowObjectDisposedException(); + } + + return new(array, 0, _length); + } + } + + /// + /// Gets a wrapping the memory belonging to the current instance. + /// + /// + /// Uses a trick made possible by the .NET 6+ runtime array layout. + /// + public Span Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + T[]? array = _array; + + if (array is null) + { + ThrowObjectDisposedException(); + } + + ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(array); + + return MemoryMarshal.CreateSpan(ref firstElementRef, _length); + } + } + + /// + public void Dispose() + { + T[]? array = Interlocked.Exchange(ref _array, null); + + if (array is not null) + { + ArrayPool.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences()); + } + } + + /// + /// Throws an when is . + /// + [DoesNotReturn] + private static void ThrowObjectDisposedException() + { + throw new ObjectDisposedException(nameof(MemoryOwner), "The buffer has already been disposed."); + } + } +} diff --git a/src/Ryujinx.Common/Memory/MemoryStreamManager.cs b/src/Ryujinx.Common/Memory/MemoryStreamManager.cs new file mode 100644 index 00000000..834210e0 --- /dev/null +++ b/src/Ryujinx.Common/Memory/MemoryStreamManager.cs @@ -0,0 +1,99 @@ +using Microsoft.IO; +using System; + +namespace Ryujinx.Common.Memory +{ + public static class MemoryStreamManager + { + private static readonly RecyclableMemoryStreamManager _shared = new(); + + /// + /// We don't expose the RecyclableMemoryStreamManager directly because version 2.x + /// returns them as MemoryStream. This Shared class is here to a) offer only the GetStream() versions we use + /// and b) return them as RecyclableMemoryStream so we don't have to cast. + /// + public static class Shared + { + /// + /// Retrieve a new MemoryStream object with no tag and a default initial capacity. + /// + /// A RecyclableMemoryStream + public static RecyclableMemoryStream GetStream() + => new(_shared); + + /// + /// Retrieve a new MemoryStream object with the contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// The byte buffer to copy data from + /// A RecyclableMemoryStream + public static RecyclableMemoryStream GetStream(byte[] buffer) + => GetStream(Guid.NewGuid(), null, buffer, 0, buffer.Length); + + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// The byte buffer to copy data from + /// A RecyclableMemoryStream + public static RecyclableMemoryStream GetStream(ReadOnlySpan buffer) + => GetStream(Guid.NewGuid(), null, buffer); + + /// + /// Retrieve a new RecyclableMemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A unique identifier which can be used to trace usages of the stream + /// A tag which can be used to track the source of the stream + /// The byte buffer to copy data from + /// A RecyclableMemoryStream + public static RecyclableMemoryStream GetStream(Guid id, string tag, ReadOnlySpan buffer) + { + RecyclableMemoryStream stream = null; + try + { + stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length); + stream.Write(buffer); + stream.Position = 0; + return stream; + } + catch + { + stream?.Dispose(); + throw; + } + } + + /// + /// Retrieve a new RecyclableMemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned + /// A unique identifier which can be used to trace usages of the stream + /// A tag which can be used to track the source of the stream + /// The byte buffer to copy data from + /// The offset from the start of the buffer to copy from + /// The number of bytes to copy from the buffer + /// A RecyclableMemoryStream + public static RecyclableMemoryStream GetStream(Guid id, string tag, byte[] buffer, int offset, int count) + { + RecyclableMemoryStream stream = null; + try + { + stream = new RecyclableMemoryStream(_shared, id, tag, count); + stream.Write(buffer, offset, count); + stream.Position = 0; + return stream; + } + catch + { + stream?.Dispose(); + throw; + } + } + } + } +} diff --git a/src/Ryujinx.Common/Memory/PartialUnmaps/NativeReaderWriterLock.cs b/src/Ryujinx.Common/Memory/PartialUnmaps/NativeReaderWriterLock.cs new file mode 100644 index 00000000..17106ae5 --- /dev/null +++ b/src/Ryujinx.Common/Memory/PartialUnmaps/NativeReaderWriterLock.cs @@ -0,0 +1,85 @@ +using System.Runtime.InteropServices; +using System.Threading; +using static Ryujinx.Common.Memory.PartialUnmaps.PartialUnmapHelpers; + +namespace Ryujinx.Common.Memory.PartialUnmaps +{ + /// + /// A simple implementation of a ReaderWriterLock which can be used from native code. + /// + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct NativeReaderWriterLock + { + public int WriteLock; + public int ReaderCount; + + public static readonly int WriteLockOffset; + public static readonly int ReaderCountOffset; + + /// + /// Populates the field offsets for use when emitting native code. + /// + static NativeReaderWriterLock() + { + NativeReaderWriterLock instance = new(); + + WriteLockOffset = OffsetOf(ref instance, ref instance.WriteLock); + ReaderCountOffset = OffsetOf(ref instance, ref instance.ReaderCount); + } + + /// + /// Acquires the reader lock. + /// + public void AcquireReaderLock() + { + // Must take write lock for a very short time to become a reader. + + while (Interlocked.CompareExchange(ref WriteLock, 1, 0) != 0) + { + } + + Interlocked.Increment(ref ReaderCount); + + Interlocked.Exchange(ref WriteLock, 0); + } + + /// + /// Releases the reader lock. + /// + public void ReleaseReaderLock() + { + Interlocked.Decrement(ref ReaderCount); + } + + /// + /// Upgrades to a writer lock. The reader lock is temporarily released while obtaining the writer lock. + /// + public void UpgradeToWriterLock() + { + // Prevent any more threads from entering reader. + // If the write lock is already taken, wait for it to not be taken. + + Interlocked.Decrement(ref ReaderCount); + + while (Interlocked.CompareExchange(ref WriteLock, 1, 0) != 0) + { + } + + // Wait for reader count to drop to 0, then take the lock again as the only reader. + + while (Interlocked.CompareExchange(ref ReaderCount, 1, 0) != 0) + { + } + } + + /// + /// Downgrades from a writer lock, back to a reader one. + /// + public void DowngradeFromWriterLock() + { + // Release the WriteLock. + + Interlocked.Exchange(ref WriteLock, 0); + } + } +} diff --git a/src/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapHelpers.cs b/src/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapHelpers.cs new file mode 100644 index 00000000..0daa7c46 --- /dev/null +++ b/src/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapHelpers.cs @@ -0,0 +1,20 @@ +using System.Runtime.CompilerServices; + +namespace Ryujinx.Common.Memory.PartialUnmaps +{ + static class PartialUnmapHelpers + { + /// + /// Calculates a byte offset of a given field within a struct. + /// + /// Struct type + /// Field type + /// Parent struct + /// Field + /// The byte offset of the given field in the given struct + public static int OffsetOf(ref T2 storage, ref T target) + { + return (int)Unsafe.ByteOffset(ref Unsafe.As(ref storage), ref target); + } + } +} diff --git a/src/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapState.cs b/src/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapState.cs new file mode 100644 index 00000000..93fef5c3 --- /dev/null +++ b/src/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapState.cs @@ -0,0 +1,161 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Threading; +using static Ryujinx.Common.Memory.PartialUnmaps.PartialUnmapHelpers; + +namespace Ryujinx.Common.Memory.PartialUnmaps +{ + /// + /// State for partial unmaps. Intended to be used on Windows. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public partial struct PartialUnmapState + { + public NativeReaderWriterLock PartialUnmapLock; + public int PartialUnmapsCount; + public ThreadLocalMap LocalCounts; + + public readonly static int PartialUnmapLockOffset; + public readonly static int PartialUnmapsCountOffset; + public readonly static int LocalCountsOffset; + + public readonly static IntPtr GlobalState; + + [SupportedOSPlatform("windows")] + [LibraryImport("kernel32.dll")] + private static partial int GetCurrentThreadId(); + + [SupportedOSPlatform("windows")] + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial IntPtr OpenThread(int dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwThreadId); + + [SupportedOSPlatform("windows")] + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool CloseHandle(IntPtr hObject); + + [SupportedOSPlatform("windows")] + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode); + + /// + /// Creates a global static PartialUnmapState and populates the field offsets. + /// + static unsafe PartialUnmapState() + { + PartialUnmapState instance = new(); + + PartialUnmapLockOffset = OffsetOf(ref instance, ref instance.PartialUnmapLock); + PartialUnmapsCountOffset = OffsetOf(ref instance, ref instance.PartialUnmapsCount); + LocalCountsOffset = OffsetOf(ref instance, ref instance.LocalCounts); + + int size = Unsafe.SizeOf(); + GlobalState = Marshal.AllocHGlobal(size); + Unsafe.InitBlockUnaligned((void*)GlobalState, 0, (uint)size); + } + + /// + /// Resets the global state. + /// + public static unsafe void Reset() + { + int size = Unsafe.SizeOf(); + Unsafe.InitBlockUnaligned((void*)GlobalState, 0, (uint)size); + } + + /// + /// Gets a reference to the global state. + /// + /// A reference to the global state + public static unsafe ref PartialUnmapState GetRef() + { + return ref Unsafe.AsRef((void*)GlobalState); + } + + /// + /// Checks if an access violation handler should retry execution due to a fault caused by partial unmap. + /// + /// + /// Due to Windows limitations, might need to unmap more memory than requested. + /// The additional memory that was unmapped is later remapped, however this leaves a time gap where the + /// memory might be accessed but is unmapped. Users of the API must compensate for that by catching the + /// access violation and retrying if it happened between the unmap and remap operation. + /// This method can be used to decide if retrying in such cases is necessary or not. + /// + /// This version of the function is not used, but serves as a reference for the native + /// implementation in ARMeilleure. + /// + /// True if execution should be retried, false otherwise + [SupportedOSPlatform("windows")] + public bool RetryFromAccessViolation() + { + PartialUnmapLock.AcquireReaderLock(); + + int threadID = GetCurrentThreadId(); + int threadIndex = LocalCounts.GetOrReserve(threadID, 0); + + if (threadIndex == -1) + { + // Out of thread local space... try again later. + + PartialUnmapLock.ReleaseReaderLock(); + + return true; + } + + ref int threadLocalPartialUnmapsCount = ref LocalCounts.GetValue(threadIndex); + + bool retry = threadLocalPartialUnmapsCount != PartialUnmapsCount; + if (retry) + { + threadLocalPartialUnmapsCount = PartialUnmapsCount; + } + + PartialUnmapLock.ReleaseReaderLock(); + + return retry; + } + + /// + /// Iterates and trims threads in the thread -> count map that + /// are no longer active. + /// + [SupportedOSPlatform("windows")] + public void TrimThreads() + { + const uint ExitCodeStillActive = 259; + const int ThreadQueryInformation = 0x40; + + Span ids = LocalCounts.ThreadIds.AsSpan(); + + for (int i = 0; i < ids.Length; i++) + { + int id = ids[i]; + + if (id != 0) + { + IntPtr handle = OpenThread(ThreadQueryInformation, false, (uint)id); + + if (handle == IntPtr.Zero) + { + Interlocked.CompareExchange(ref ids[i], 0, id); + } + else + { + GetExitCodeThread(handle, out uint exitCode); + + if (exitCode != ExitCodeStillActive) + { + Interlocked.CompareExchange(ref ids[i], 0, id); + } + + CloseHandle(handle); + } + } + } + } + } +} diff --git a/src/Ryujinx.Common/Memory/PartialUnmaps/ThreadLocalMap.cs b/src/Ryujinx.Common/Memory/PartialUnmaps/ThreadLocalMap.cs new file mode 100644 index 00000000..009aff62 --- /dev/null +++ b/src/Ryujinx.Common/Memory/PartialUnmaps/ThreadLocalMap.cs @@ -0,0 +1,91 @@ +using System.Runtime.InteropServices; +using System.Threading; +using static Ryujinx.Common.Memory.PartialUnmaps.PartialUnmapHelpers; + +namespace Ryujinx.Common.Memory.PartialUnmaps +{ + /// + /// A simple fixed size thread safe map that can be used from native code. + /// Integer thread IDs map to corresponding structs. + /// + /// The value type for the map + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ThreadLocalMap where T : unmanaged + { + public const int MapSize = 20; + + public Array20 ThreadIds; + public Array20 Structs; + + public static readonly int ThreadIdsOffset; + public static readonly int StructsOffset; + + /// + /// Populates the field offsets for use when emitting native code. + /// + static ThreadLocalMap() + { + ThreadLocalMap instance = new(); + + ThreadIdsOffset = OffsetOf(ref instance, ref instance.ThreadIds); + StructsOffset = OffsetOf(ref instance, ref instance.Structs); + } + + /// + /// Gets the index of a given thread ID in the map, or reserves one. + /// When reserving a struct, its value is set to the given initial value. + /// Returns -1 when there is no space to reserve a new entry. + /// + /// Thread ID to use as a key + /// Initial value of the associated struct. + /// The index of the entry, or -1 if none + public int GetOrReserve(int threadId, T initial) + { + // Try get a match first. + + for (int i = 0; i < MapSize; i++) + { + int compare = Interlocked.CompareExchange(ref ThreadIds[i], threadId, threadId); + + if (compare == threadId) + { + return i; + } + } + + // Try get a free entry. Since the id is assumed to be unique to this thread, we know it doesn't exist yet. + + for (int i = 0; i < MapSize; i++) + { + int compare = Interlocked.CompareExchange(ref ThreadIds[i], threadId, 0); + + if (compare == 0) + { + Structs[i] = initial; + return i; + } + } + + return -1; + } + + /// + /// Gets the struct value for a given map entry. + /// + /// Index of the entry + /// A reference to the struct value + public ref T GetValue(int index) + { + return ref Structs[index]; + } + + /// + /// Releases an entry from the map. + /// + /// Index of the entry to release + public void Release(int index) + { + Interlocked.Exchange(ref ThreadIds[index], 0); + } + } +} diff --git a/src/Ryujinx.Common/Memory/Ptr.cs b/src/Ryujinx.Common/Memory/Ptr.cs new file mode 100644 index 00000000..d01748c1 --- /dev/null +++ b/src/Ryujinx.Common/Memory/Ptr.cs @@ -0,0 +1,68 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Common.Memory +{ + /// + /// Represents a pointer to an unmanaged resource. + /// + /// Type of the unmanaged resource + public unsafe struct Ptr : IEquatable> where T : unmanaged + { + private IntPtr _ptr; + + /// + /// Null pointer. + /// + public static Ptr Null => new() { _ptr = IntPtr.Zero }; + + /// + /// True if the pointer is null, false otherwise. + /// + public readonly bool IsNull => _ptr == IntPtr.Zero; + + /// + /// Gets a reference to the value. + /// + public readonly ref T Value => ref Unsafe.AsRef((void*)_ptr); + + /// + /// Creates a new pointer to an unmanaged resource. + /// + /// + /// For data on the heap, proper pinning is necessary during + /// use. Failure to do so will result in memory corruption and crashes. + /// + /// Reference to the unmanaged resource + public Ptr(ref T value) + { + _ptr = (IntPtr)Unsafe.AsPointer(ref value); + } + + public readonly override bool Equals(object obj) + { + return obj is Ptr other && Equals(other); + } + + public readonly bool Equals([AllowNull] Ptr other) + { + return _ptr == other._ptr; + } + + public readonly override int GetHashCode() + { + return _ptr.GetHashCode(); + } + + public static bool operator ==(Ptr left, Ptr right) + { + return left.Equals(right); + } + + public static bool operator !=(Ptr left, Ptr right) + { + return !(left == right); + } + } +} diff --git a/src/Ryujinx.Common/Memory/SpanOwner.cs b/src/Ryujinx.Common/Memory/SpanOwner.cs new file mode 100644 index 00000000..acb20bca --- /dev/null +++ b/src/Ryujinx.Common/Memory/SpanOwner.cs @@ -0,0 +1,114 @@ +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Memory +{ + /// + /// A stack-only type that rents a buffer of a specified length from . + /// It does not implement to avoid being boxed, but should still be disposed. This + /// is easy since C# 8, which allows use of C# `using` constructs on any type that has a public Dispose() method. + /// To keep this type simple, fast, and read-only, it does not check or guard against multiple disposals. + /// For all these reasons, all usage should be with a `using` block or statement. + /// + /// The type of item to store. + public readonly ref struct SpanOwner + { + private readonly int _length; + private readonly T[] _array; + + /// + /// Initializes a new instance of the struct with the specified parameters. + /// + /// The length of the new memory buffer to use + private SpanOwner(int length) + { + _length = length; + _array = ArrayPool.Shared.Rent(length); + } + + /// + /// Gets an empty instance. + /// + public static SpanOwner Empty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(0); + } + + /// + /// Creates a new instance with the specified length. + /// + /// The length of the new memory buffer to use + /// A instance of the requested length + /// Thrown when is not valid + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SpanOwner Rent(int length) => new(length); + + /// + /// Creates a new instance with the length and the content cleared. + /// + /// The length of the new memory buffer to use + /// A instance of the requested length and the content cleared + /// Thrown when is not valid + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SpanOwner RentCleared(int length) + { + SpanOwner result = new(length); + + result._array.AsSpan(0, length).Clear(); + + return result; + } + + /// + /// Creates a new instance with the content copied from the specified buffer. + /// + /// The buffer to copy + /// A instance with the same length and content as + public static SpanOwner RentCopy(ReadOnlySpan buffer) + { + SpanOwner result = new(buffer.Length); + + buffer.CopyTo(result._array); + + return result; + } + + /// + /// Gets the number of items in the current instance + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _length; + } + + /// + /// Gets a wrapping the memory belonging to the current instance. + /// + /// + /// Uses a trick made possible by the .NET 6+ runtime array layout. + /// + public Span Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(_array); + + return MemoryMarshal.CreateSpan(ref firstElementRef, _length); + } + } + + /// + /// Implements the duck-typed method. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + ArrayPool.Shared.Return(_array, RuntimeHelpers.IsReferenceOrContainsReferences()); + } + } +} diff --git a/src/Ryujinx.Common/Memory/SpanReader.cs b/src/Ryujinx.Common/Memory/SpanReader.cs new file mode 100644 index 00000000..946439e3 --- /dev/null +++ b/src/Ryujinx.Common/Memory/SpanReader.cs @@ -0,0 +1,74 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Memory +{ + public ref struct SpanReader + { + private ReadOnlySpan _input; + + public readonly int Length => _input.Length; + + public SpanReader(ReadOnlySpan input) + { + _input = input; + } + + public T Read() where T : unmanaged + { + T value = MemoryMarshal.Cast(_input)[0]; + + _input = _input[Unsafe.SizeOf()..]; + + return value; + } + + public bool TryRead(out T value) where T : unmanaged + { + int valueSize = Unsafe.SizeOf(); + + if (valueSize > _input.Length) + { + value = default; + + return false; + } + + value = MemoryMarshal.Cast(_input)[0]; + + _input = _input[valueSize..]; + + return true; + } + + public ReadOnlySpan GetSpan(int size) + { + ReadOnlySpan data = _input[..size]; + + _input = _input[size..]; + + return data; + } + + public ReadOnlySpan GetSpanSafe(int size) + { + return GetSpan((int)Math.Min((uint)_input.Length, (uint)size)); + } + + public readonly T ReadAt(int offset) where T : unmanaged + { + return MemoryMarshal.Cast(_input[offset..])[0]; + } + + public readonly ReadOnlySpan GetSpanAt(int offset, int size) + { + return _input.Slice(offset, size); + } + + public void Skip(int size) + { + _input = _input[size..]; + } + } +} diff --git a/src/Ryujinx.Common/Memory/SpanWriter.cs b/src/Ryujinx.Common/Memory/SpanWriter.cs new file mode 100644 index 00000000..5866652d --- /dev/null +++ b/src/Ryujinx.Common/Memory/SpanWriter.cs @@ -0,0 +1,45 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Memory +{ + public ref struct SpanWriter + { + private Span _output; + + public readonly int Length => _output.Length; + + public SpanWriter(Span output) + { + _output = output; + } + + public void Write(T value) where T : unmanaged + { + MemoryMarshal.Cast(_output)[0] = value; + _output = _output[Unsafe.SizeOf()..]; + } + + public void Write(ReadOnlySpan data) + { + data.CopyTo(_output[..data.Length]); + _output = _output[data.Length..]; + } + + public readonly void WriteAt(int offset, T value) where T : unmanaged + { + MemoryMarshal.Cast(_output[offset..])[0] = value; + } + + public readonly void WriteAt(int offset, ReadOnlySpan data) + { + data.CopyTo(_output.Slice(offset, data.Length)); + } + + public void Skip(int size) + { + _output = _output[size..]; + } + } +} diff --git a/src/Ryujinx.Common/Memory/StructArrayHelpers.cs b/src/Ryujinx.Common/Memory/StructArrayHelpers.cs new file mode 100644 index 00000000..762c7388 --- /dev/null +++ b/src/Ryujinx.Common/Memory/StructArrayHelpers.cs @@ -0,0 +1,847 @@ +using System; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; + +#pragma warning disable CS0169, IDE0051 // Remove unused private member +namespace Ryujinx.Common.Memory +{ + public struct Array1 : IArray where T : unmanaged + { + T _e0; + public readonly int Length => 1; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array2 : IArray where T : unmanaged + { + T _e0; + Array1 _other; + public readonly int Length => 2; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array3 : IArray where T : unmanaged + { + T _e0; + Array2 _other; + public readonly int Length => 3; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array4 : IArray where T : unmanaged + { + T _e0; + Array3 _other; + public readonly int Length => 4; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array5 : IArray where T : unmanaged + { + T _e0; + Array4 _other; + public readonly int Length => 5; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array6 : IArray where T : unmanaged + { + T _e0; + Array5 _other; + public readonly int Length => 6; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array7 : IArray where T : unmanaged + { + T _e0; + Array6 _other; + public readonly int Length => 7; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array8 : IArray where T : unmanaged + { + T _e0; + Array7 _other; + public readonly int Length => 8; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array9 : IArray where T : unmanaged + { + T _e0; + Array8 _other; + public readonly int Length => 9; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array10 : IArray where T : unmanaged + { + T _e0; + Array9 _other; + public readonly int Length => 10; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array11 : IArray where T : unmanaged + { + T _e0; + Array10 _other; + public readonly int Length => 11; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array12 : IArray where T : unmanaged + { + T _e0; + Array11 _other; + public readonly int Length => 12; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array13 : IArray where T : unmanaged + { + T _e0; + Array12 _other; + public readonly int Length => 13; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array14 : IArray where T : unmanaged + { + T _e0; + Array13 _other; + public readonly int Length => 14; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array15 : IArray where T : unmanaged + { + T _e0; + Array14 _other; + public readonly int Length => 15; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array16 : IArray where T : unmanaged + { + T _e0; + Array15 _other; + public readonly int Length => 16; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array17 : IArray where T : unmanaged + { + T _e0; + Array16 _other; + public readonly int Length => 17; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array18 : IArray where T : unmanaged + { + T _e0; + Array17 _other; + public readonly int Length => 18; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array19 : IArray where T : unmanaged + { + T _e0; + Array18 _other; + public readonly int Length => 19; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array20 : IArray where T : unmanaged + { + T _e0; + Array19 _other; + public readonly int Length => 20; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array21 : IArray where T : unmanaged + { + T _e0; + Array20 _other; + public readonly int Length => 21; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array22 : IArray where T : unmanaged + { + T _e0; + Array21 _other; + public readonly int Length => 22; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array23 : IArray where T : unmanaged + { + T _e0; + Array22 _other; + public readonly int Length => 23; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array24 : IArray where T : unmanaged + { + T _e0; + Array23 _other; + + public readonly int Length => 24; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array25 : IArray where T : unmanaged + { + T _e0; + Array24 _other; + + public readonly int Length => 25; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array26 : IArray where T : unmanaged + { + T _e0; + Array25 _other; + + public readonly int Length => 26; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array27 : IArray where T : unmanaged + { + T _e0; + Array26 _other; + + public readonly int Length => 27; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array28 : IArray where T : unmanaged + { + T _e0; + Array27 _other; + + public readonly int Length => 28; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array29 : IArray where T : unmanaged + { + T _e0; + Array28 _other; + + public readonly int Length => 29; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array30 : IArray where T : unmanaged + { + T _e0; + Array29 _other; + + public readonly int Length => 30; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array31 : IArray where T : unmanaged + { + T _e0; + Array30 _other; + + public readonly int Length => 31; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array32 : IArray where T : unmanaged + { + T _e0; + Array31 _other; + + public readonly int Length => 32; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array33 : IArray where T : unmanaged + { + T _e0; + Array32 _other; + + public readonly int Length => 33; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array34 : IArray where T : unmanaged + { + T _e0; + Array33 _other; + + public readonly int Length => 34; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array35 : IArray where T : unmanaged + { + T _e0; + Array34 _other; + + public readonly int Length => 35; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array36 : IArray where T : unmanaged + { + T _e0; + Array35 _other; + + public readonly int Length => 36; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array37 : IArray where T : unmanaged + { + T _e0; + Array36 _other; + + public readonly int Length => 37; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array38 : IArray where T : unmanaged + { + T _e0; + Array37 _other; + + public readonly int Length => 38; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array39 : IArray where T : unmanaged + { + T _e0; + Array38 _other; + + public readonly int Length => 39; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array40 : IArray where T : unmanaged + { + T _e0; + Array39 _other; + + public readonly int Length => 40; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array41 : IArray where T : unmanaged + { + T _e0; + Array40 _other; + + public readonly int Length => 41; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array42 : IArray where T : unmanaged + { + T _e0; + Array41 _other; + + public readonly int Length => 42; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array43 : IArray where T : unmanaged + { + T _e0; + Array42 _other; + + public readonly int Length => 43; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array44 : IArray where T : unmanaged + { + T _e0; + Array43 _other; + + public readonly int Length => 44; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array45 : IArray where T : unmanaged + { + T _e0; + Array44 _other; + + public readonly int Length => 45; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array46 : IArray where T : unmanaged + { + T _e0; + Array45 _other; + + public readonly int Length => 46; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array47 : IArray where T : unmanaged + { + T _e0; + Array46 _other; + + public readonly int Length => 47; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array48 : IArray where T : unmanaged + { + T _e0; + Array47 _other; + + public readonly int Length => 48; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array49 : IArray where T : unmanaged + { + T _e0; + Array48 _other; + + public readonly int Length => 49; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array50 : IArray where T : unmanaged + { + T _e0; + Array49 _other; + + public readonly int Length => 50; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array51 : IArray where T : unmanaged + { + T _e0; + Array50 _other; + + public readonly int Length => 51; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array52 : IArray where T : unmanaged + { + T _e0; + Array51 _other; + + public readonly int Length => 52; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array53 : IArray where T : unmanaged + { + T _e0; + Array52 _other; + + public readonly int Length => 53; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array54 : IArray where T : unmanaged + { + T _e0; + Array53 _other; + + public readonly int Length => 54; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array55 : IArray where T : unmanaged + { + T _e0; + Array54 _other; + + public readonly int Length => 55; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array56 : IArray where T : unmanaged + { + T _e0; + Array55 _other; + + public readonly int Length => 56; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array57 : IArray where T : unmanaged + { + T _e0; + Array56 _other; + + public readonly int Length => 57; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array58 : IArray where T : unmanaged + { + T _e0; + Array57 _other; + + public readonly int Length => 58; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array59 : IArray where T : unmanaged + { + T _e0; + Array58 _other; + + public readonly int Length => 59; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array60 : IArray where T : unmanaged + { + T _e0; + Array59 _other; + public readonly int Length => 60; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array61 : IArray where T : unmanaged + { + T _e0; + Array60 _other; + public readonly int Length => 61; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array62 : IArray where T : unmanaged + { + T _e0; + Array61 _other; + public readonly int Length => 62; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array63 : IArray where T : unmanaged + { + T _e0; + Array62 _other; + public readonly int Length => 63; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array64 : IArray where T : unmanaged + { + T _e0; + Array63 _other; + public readonly int Length => 64; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array65 : IArray where T : unmanaged + { + T _e0; + Array64 _other; + public readonly int Length => 65; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array73 : IArray where T : unmanaged + { + T _e0; + Array64 _other; + Array8 _other2; + public readonly int Length => 73; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array96 : IArray where T : unmanaged + { + T _e0; + Array64 _other; + Array31 _other2; + public readonly int Length => 96; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array127 : IArray where T : unmanaged + { + T _e0; + Array64 _other; + Array62 _other2; + public readonly int Length => 127; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array128 : IArray where T : unmanaged + { + T _e0; + Array64 _other; + Array63 _other2; + public readonly int Length => 128; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array256 : IArray where T : unmanaged + { + T _e0; + Array128 _other; + Array127 _other2; + public readonly int Length => 256; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array140 : IArray where T : unmanaged + { + T _e0; + Array64 _other; + Array64 _other2; + Array11 _other3; + public readonly int Length => 140; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + + public struct Array384 : IArray where T : unmanaged + { + T _e0; + Array64 _other; + Array64 _other2; + Array64 _other3; + Array64 _other4; + Array64 _other5; + Array63 _other6; + public readonly int Length => 384; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } +} +#pragma warning restore CS0169, IDE0051 diff --git a/src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs b/src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs new file mode 100644 index 00000000..79b7d681 --- /dev/null +++ b/src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs @@ -0,0 +1,89 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Memory +{ + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] + public struct ByteArray128 : IArray + { + private const int Size = 128; + + byte _element; + + public readonly int Length => Size; + public ref byte this[int index] => ref AsSpan()[index]; + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size); + } + + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] + public struct ByteArray256 : IArray + { + private const int Size = 256; + + byte _element; + + public readonly int Length => Size; + public ref byte this[int index] => ref AsSpan()[index]; + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size); + } + + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] + public struct ByteArray512 : IArray + { + private const int Size = 512; + + byte _element; + + public readonly int Length => Size; + public ref byte this[int index] => ref AsSpan()[index]; + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size); + } + + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] + public struct ByteArray1024 : IArray + { + private const int Size = 1024; + + byte _element; + + public readonly int Length => Size; + public ref byte this[int index] => ref AsSpan()[index]; + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size); + } + + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] + public struct ByteArray2048 : IArray + { + private const int Size = 2048; + + byte _element; + + public readonly int Length => Size; + public ref byte this[int index] => ref AsSpan()[index]; + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size); + } + + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] + public struct ByteArray3000 : IArray + { + private const int Size = 3000; + + byte _element; + + public readonly int Length => Size; + public ref byte this[int index] => ref AsSpan()[index]; + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size); + } + + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] + public struct ByteArray4096 : IArray + { + private const int Size = 4096; + + byte _element; + + public readonly int Length => Size; + public ref byte this[int index] => ref AsSpan()[index]; + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size); + } +} diff --git a/src/Ryujinx.Common/PerformanceCounter.cs b/src/Ryujinx.Common/PerformanceCounter.cs new file mode 100644 index 00000000..8c40fd7f --- /dev/null +++ b/src/Ryujinx.Common/PerformanceCounter.cs @@ -0,0 +1,82 @@ +using System.Diagnostics; + +namespace Ryujinx.Common +{ + public static class PerformanceCounter + { + private static readonly double _ticksToNs; + + /// + /// Represents the number of ticks in 1 day. + /// + public static long TicksPerDay { get; } + + /// + /// Represents the number of ticks in 1 hour. + /// + public static long TicksPerHour { get; } + + /// + /// Represents the number of ticks in 1 minute. + /// + public static long TicksPerMinute { get; } + + /// + /// Represents the number of ticks in 1 second. + /// + public static long TicksPerSecond { get; } + + /// + /// Represents the number of ticks in 1 millisecond. + /// + public static long TicksPerMillisecond { get; } + + /// + /// Gets the number of ticks elapsed since the system started. + /// + public static long ElapsedTicks + { + get + { + return Stopwatch.GetTimestamp(); + } + } + + /// + /// Gets the number of milliseconds elapsed since the system started. + /// + public static long ElapsedMilliseconds + { + get + { + long timestamp = Stopwatch.GetTimestamp(); + + return timestamp / TicksPerMillisecond; + } + } + + /// + /// Gets the number of nanoseconds elapsed since the system started. + /// + public static long ElapsedNanoseconds + { + get + { + long timestamp = Stopwatch.GetTimestamp(); + + return (long)(timestamp * _ticksToNs); + } + } + + static PerformanceCounter() + { + TicksPerMillisecond = Stopwatch.Frequency / 1000; + TicksPerSecond = Stopwatch.Frequency; + TicksPerMinute = TicksPerSecond * 60; + TicksPerHour = TicksPerMinute * 60; + TicksPerDay = TicksPerHour * 24; + + _ticksToNs = 1000000000.0 / Stopwatch.Frequency; + } + } +} diff --git a/src/Ryujinx.Common/Pools/ObjectPool.cs b/src/Ryujinx.Common/Pools/ObjectPool.cs new file mode 100644 index 00000000..0b6ce377 --- /dev/null +++ b/src/Ryujinx.Common/Pools/ObjectPool.cs @@ -0,0 +1,75 @@ +using System; +using System.Threading; + +namespace Ryujinx.Common +{ + public class ObjectPool + where T : class + { + private T _firstItem; + private readonly T[] _items; + + private readonly Func _factory; + + public ObjectPool(Func factory, int size) + { + _items = new T[size - 1]; + _factory = factory; + } + + public T Allocate() + { + T instance = _firstItem; + + if (instance == null || instance != Interlocked.CompareExchange(ref _firstItem, null, instance)) + { + instance = AllocateInternal(); + } + + return instance; + } + + private T AllocateInternal() + { + T[] items = _items; + + for (int i = 0; i < items.Length; i++) + { + T instance = items[i]; + + if (instance != null && instance == Interlocked.CompareExchange(ref items[i], null, instance)) + { + return instance; + } + } + + return _factory(); + } + + public void Release(T obj) + { + if (_firstItem == null) + { + _firstItem = obj; + } + else + { + ReleaseInternal(obj); + } + } + + private void ReleaseInternal(T obj) + { + T[] items = _items; + + for (int i = 0; i < items.Length; i++) + { + if (items[i] == null) + { + items[i] = obj; + break; + } + } + } + } +} diff --git a/src/Ryujinx.Common/Pools/SharedPools.cs b/src/Ryujinx.Common/Pools/SharedPools.cs new file mode 100644 index 00000000..342eab4d --- /dev/null +++ b/src/Ryujinx.Common/Pools/SharedPools.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Common +{ + public static class SharedPools + { + private static class DefaultPool + where T : class, new() + { + public static readonly ObjectPool Instance = new(() => new T(), 20); + } + + public static ObjectPool Default() + where T : class, new() + { + return DefaultPool.Instance; + } + } +} diff --git a/src/Ryujinx.Common/Pools/ThreadStaticArray.cs b/src/Ryujinx.Common/Pools/ThreadStaticArray.cs new file mode 100644 index 00000000..a2b9811c --- /dev/null +++ b/src/Ryujinx.Common/Pools/ThreadStaticArray.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.Common.Pools +{ + public static class ThreadStaticArray + { + [ThreadStatic] + private static T[] _array; + + public static ref T[] Get() + { + _array ??= new T[1]; + + return ref _array; + } + } +} diff --git a/src/Ryujinx.Common/PreciseSleep/IPreciseSleepEvent.cs b/src/Ryujinx.Common/PreciseSleep/IPreciseSleepEvent.cs new file mode 100644 index 00000000..26b5ab68 --- /dev/null +++ b/src/Ryujinx.Common/PreciseSleep/IPreciseSleepEvent.cs @@ -0,0 +1,38 @@ +using System; + +namespace Ryujinx.Common.PreciseSleep +{ + /// + /// An event which works similarly to an AutoResetEvent, but is backed by a + /// more precise timer that allows waits of less than a millisecond. + /// + public interface IPreciseSleepEvent : IDisposable + { + /// + /// Adjust a timepoint to better fit the host clock. + /// When no adjustment is made, the input timepoint will be returned. + /// + /// Timepoint to adjust + /// Requested timeout in nanoseconds + /// Adjusted timepoint + long AdjustTimePoint(long timePoint, long timeoutNs); + + /// + /// Sleep until a timepoint, or a signal is received. + /// Given no signal, may wake considerably before, or slightly after the timeout. + /// + /// Timepoint to sleep until + /// True if signalled or waited, false if a wait could not be performed + bool SleepUntil(long timePoint); + + /// + /// Sleep until a signal is received. + /// + void Sleep(); + + /// + /// Signal the event, waking any sleeping thread or the next attempted sleep. + /// + void Signal(); + } +} diff --git a/src/Ryujinx.Common/PreciseSleep/Nanosleep.cs b/src/Ryujinx.Common/PreciseSleep/Nanosleep.cs new file mode 100644 index 00000000..67f067ae --- /dev/null +++ b/src/Ryujinx.Common/PreciseSleep/Nanosleep.cs @@ -0,0 +1,160 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Common.PreciseSleep +{ + /// + /// Access to Linux/MacOS nanosleep, with platform specific bias to improve precision. + /// + [SupportedOSPlatform("macos")] + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("android")] + [SupportedOSPlatform("ios")] + internal static partial class Nanosleep + { + private const long LinuxBaseNanosleepBias = 50000; // 0.05ms + + // Penalty for max allowed sleep duration + private const long LinuxNanosleepAccuracyPenaltyThreshold = 200000; // 0.2ms + private const long LinuxNanosleepAccuracyPenalty = 30000; // 0.03ms + + // Penalty for base sleep duration + private const long LinuxNanosleepBasePenaltyThreshold = 500000; // 0.5ms + private const long LinuxNanosleepBasePenalty = 30000; // 0.03ms + private const long LinuxNanosleepPenaltyPerMillisecond = 18000; // 0.018ms + private const long LinuxNanosleepPenaltyCap = 18000; // 0.018ms + + private const long LinuxStrictBiasOffset = 150_000; // 0.15ms + + // Nanosleep duration is biased depending on the requested timeout on MacOS. + // These match the results when measuring on an M1 processor at AboveNormal priority. + private const long MacosBaseNanosleepBias = 5000; // 0.005ms + private const long MacosBiasPerMillisecond = 140000; // 0.14ms + private const long MacosBiasMaxNanoseconds = 20_000_000; // 20ms + private const long MacosStrictBiasOffset = 150_000; // 0.15ms + + public static long Bias { get; } + + /// + /// Get bias for a given nanosecond timeout. + /// Some platforms calculate their bias differently, this method can be used to counteract it. + /// + /// Nanosecond timeout + /// Bias in nanoseconds + public static long GetBias(long timeoutNs) + { + if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) + { + long biasNs = Math.Min(timeoutNs, MacosBiasMaxNanoseconds); + return MacosBaseNanosleepBias + biasNs * MacosBiasPerMillisecond / 1_000_000; + } + else + { + long bias = LinuxBaseNanosleepBias; + + if (timeoutNs > LinuxNanosleepBasePenaltyThreshold) + { + long penalty = (timeoutNs - LinuxNanosleepBasePenaltyThreshold) * LinuxNanosleepPenaltyPerMillisecond / 1_000_000; + bias += LinuxNanosleepBasePenalty + Math.Min(LinuxNanosleepPenaltyCap, penalty); + } + + return bias; + } + } + + /// + /// Get a stricter bias for a given nanosecond timeout, + /// which can improve the chances the sleep completes before the timeout. + /// Some platforms calculate their bias differently, this method can be used to counteract it. + /// + /// Nanosecond timeout + /// Strict bias in nanoseconds + public static long GetStrictBias(long timeoutNs) + { + if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) + { + return GetBias(timeoutNs) + MacosStrictBiasOffset; + } + else + { + long bias = GetBias(timeoutNs) + LinuxStrictBiasOffset; + + if (timeoutNs > LinuxNanosleepAccuracyPenaltyThreshold) + { + bias += LinuxNanosleepAccuracyPenalty; + } + + return bias; + } + } + + static Nanosleep() + { + Bias = GetBias(0); + } + + [StructLayout(LayoutKind.Sequential)] + private struct Timespec + { + public long tv_sec; // Seconds + public long tv_nsec; // Nanoseconds + } + + [LibraryImport("libc", SetLastError = true)] + private static partial int nanosleep(ref Timespec req, ref Timespec rem); + + /// + /// Convert a timeout in nanoseconds to a timespec for nanosleep. + /// + /// Timeout in nanoseconds + /// Timespec for nanosleep + private static Timespec GetTimespecFromNanoseconds(ulong nanoseconds) + { + return new Timespec + { + tv_sec = (long)(nanoseconds / 1_000_000_000), + tv_nsec = (long)(nanoseconds % 1_000_000_000) + }; + } + + /// + /// Sleep for approximately a given time period in nanoseconds. + /// + /// Time to sleep for in nanoseconds + public static void Sleep(long nanoseconds) + { + nanoseconds -= GetBias(nanoseconds); + + if (nanoseconds >= 0) + { + Timespec req = GetTimespecFromNanoseconds((ulong)nanoseconds); + Timespec rem = new(); + + nanosleep(ref req, ref rem); + } + } + + /// + /// Sleep for at most a given time period in nanoseconds. + /// Uses a stricter bias to wake before the requested duration. + /// + /// + /// Due to OS scheduling behaviour, this timeframe may still be missed. + /// + /// Maximum allowed time for sleep + public static void SleepAtMost(long nanoseconds) + { + // Stricter bias to ensure we wake before the timepoint. + nanoseconds -= GetStrictBias(nanoseconds); + + if (nanoseconds >= 0) + { + Timespec req = GetTimespecFromNanoseconds((ulong)nanoseconds); + Timespec rem = new(); + + nanosleep(ref req, ref rem); + } + } + } +} diff --git a/src/Ryujinx.Common/PreciseSleep/NanosleepEvent.cs b/src/Ryujinx.Common/PreciseSleep/NanosleepEvent.cs new file mode 100644 index 00000000..f54fb09c --- /dev/null +++ b/src/Ryujinx.Common/PreciseSleep/NanosleepEvent.cs @@ -0,0 +1,84 @@ +using System; +using System.Runtime.Versioning; +using System.Threading; + +namespace Ryujinx.Common.PreciseSleep +{ + /// + /// A precise sleep event for linux and macos that uses nanosleep for more precise timeouts. + /// + [SupportedOSPlatform("macos")] + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("android")] + [SupportedOSPlatform("ios")] + internal class NanosleepEvent : IPreciseSleepEvent + { + private readonly AutoResetEvent _waitEvent = new(false); + private readonly NanosleepPool _pool; + + public NanosleepEvent() + { + _pool = new NanosleepPool(_waitEvent); + } + + public long AdjustTimePoint(long timePoint, long timeoutNs) + { + // No adjustment + return timePoint; + } + + public bool SleepUntil(long timePoint) + { + long now = PerformanceCounter.ElapsedTicks; + long delta = (timePoint - now); + long ms = Math.Min(delta / PerformanceCounter.TicksPerMillisecond, int.MaxValue); + long ns = (delta * 1_000_000) / PerformanceCounter.TicksPerMillisecond; + + if (ms > 0) + { + _waitEvent.WaitOne((int)ms); + + return true; + } + else if (ns - Nanosleep.Bias > 0) + { + // Don't bother starting a sleep if there's already a signal active. + if (_waitEvent.WaitOne(0)) + { + return true; + } + + // The 1ms wait will be interrupted by the nanosleep timeout if it completes. + if (!_pool.SleepAndSignal(ns, timePoint)) + { + // Too many threads on the pool. + return false; + } + _waitEvent.WaitOne(1); + _pool.IgnoreSignal(); + + return true; + } + + return false; + } + + public void Sleep() + { + _waitEvent.WaitOne(); + } + + public void Signal() + { + _waitEvent.Set(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + _pool.Dispose(); + _waitEvent.Dispose(); + } + } +} diff --git a/src/Ryujinx.Common/PreciseSleep/NanosleepPool.cs b/src/Ryujinx.Common/PreciseSleep/NanosleepPool.cs new file mode 100644 index 00000000..c0973dcb --- /dev/null +++ b/src/Ryujinx.Common/PreciseSleep/NanosleepPool.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Versioning; +using System.Threading; + +namespace Ryujinx.Common.PreciseSleep +{ + /// + /// A pool of threads used to allow "interruptable" nanosleep for a single target event. + /// + [SupportedOSPlatform("macos")] + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("android")] + [SupportedOSPlatform("ios")] + internal class NanosleepPool : IDisposable + { + public const int MaxThreads = 8; + + /// + /// A thread that nanosleeps and may signal an event on wake. + /// When a thread is assigned a nanosleep to perform, it also gets a signal ID. + /// The pool's target event is only signalled if this ID matches the latest dispatched one. + /// + private class NanosleepThread : IDisposable + { + private static readonly long _timePointEpsilon; + + static NanosleepThread() + { + _timePointEpsilon = PerformanceCounter.TicksPerMillisecond / 100; // 0.01ms + } + + private readonly Thread _thread; + private readonly NanosleepPool _parent; + private readonly AutoResetEvent _newWaitEvent; + private bool _running = true; + + private long _signalId; + private long _nanoseconds; + private long _timePoint; + + public long SignalId => _signalId; + + /// + /// Creates a new NanosleepThread for a parent pool, with a specified thread ID. + /// + /// Parent NanosleepPool + /// Thread ID + public NanosleepThread(NanosleepPool parent, int id) + { + _parent = parent; + _newWaitEvent = new(false); + + _thread = new Thread(Loop) + { + Name = $"Common.Nanosleep.{id}", + Priority = ThreadPriority.AboveNormal, + IsBackground = true + }; + + _thread.Start(); + } + + /// + /// Service requests to perform a nanosleep, signal parent pool when complete. + /// + private void Loop() + { + _newWaitEvent.WaitOne(); + + while (_running) + { + Nanosleep.Sleep(_nanoseconds); + + _parent.Signal(this); + _newWaitEvent.WaitOne(); + } + + _newWaitEvent.Dispose(); + } + + /// + /// Assign a nanosleep for this thread to perform, then signal at the end. + /// + /// Nanoseconds to sleep + /// Signal ID + /// Target timepoint + public void SleepAndSignal(long nanoseconds, long signalId, long timePoint) + { + _signalId = signalId; + _nanoseconds = nanoseconds; + _timePoint = timePoint; + _newWaitEvent.Set(); + } + + /// + /// Resurrect an active nanosleep's signal if its target timepoint is a close enough match. + /// + /// New signal id to assign the nanosleep + /// Target timepoint + /// True if resurrected, false otherwise + public bool Resurrect(long signalId, long timePoint) + { + if (Math.Abs(timePoint - _timePoint) < _timePointEpsilon) + { + _signalId = signalId; + + return true; + } + + return false; + } + + /// + /// Dispose the NanosleepThread, interrupting its worker loop. + /// + public void Dispose() + { + if (_running) + { + _running = false; + _newWaitEvent.Set(); + } + } + } + + private readonly object _lock = new(); + private readonly List _threads = new(); + private readonly List _active = new(); + private readonly Stack _free = new(); + private readonly AutoResetEvent _signalTarget; + + private long _signalId; + + /// + /// Creates a new NanosleepPool with a target event to signal when a nanosleep completes. + /// + /// Event to signal when nanosleeps complete + public NanosleepPool(AutoResetEvent signalTarget) + { + _signalTarget = signalTarget; + } + + /// + /// Signal the target event (if the source sleep has not been superseded) + /// and free the nanosleep thread. + /// + /// Nanosleep thread that completed + private void Signal(NanosleepThread thread) + { + lock (_lock) + { + _active.Remove(thread); + _free.Push(thread); + + if (thread.SignalId == _signalId) + { + _signalTarget.Set(); + } + } + } + + /// + /// Sleep for the given number of nanoseconds and signal the target event. + /// This does not block the caller thread. + /// + /// Nanoseconds to sleep + /// Target timepoint + /// True if the signal will be set, false otherwise + public bool SleepAndSignal(long nanoseconds, long timePoint) + { + lock (_lock) + { + _signalId++; + + // Check active sleeps, if any line up with the requested timepoint then resurrect that nanosleep. + foreach (NanosleepThread existing in _active) + { + if (existing.Resurrect(_signalId, timePoint)) + { + return true; + } + } + + if (!_free.TryPop(out NanosleepThread thread)) + { + if (_threads.Count >= MaxThreads) + { + return false; + } + + thread = new NanosleepThread(this, _threads.Count); + + _threads.Add(thread); + } + + _active.Add(thread); + + thread.SleepAndSignal(nanoseconds, _signalId, timePoint); + + return true; + } + } + + /// + /// Ignore the latest nanosleep. + /// + public void IgnoreSignal() + { + _signalId++; + } + + /// + /// Dispose the NanosleepPool, disposing all of its active threads. + /// + public void Dispose() + { + GC.SuppressFinalize(this); + + foreach (NanosleepThread thread in _threads) + { + thread.Dispose(); + } + + _threads.Clear(); + } + } +} diff --git a/src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs b/src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs new file mode 100644 index 00000000..3c30a7f6 --- /dev/null +++ b/src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs @@ -0,0 +1,104 @@ +using Ryujinx.Common.SystemInterop; +using System; +using System.Threading; + +namespace Ryujinx.Common.PreciseSleep +{ + public static class PreciseSleepHelper + { + /// + /// Create a precise sleep event for the current platform. + /// + /// A precise sleep event + public static IPreciseSleepEvent CreateEvent() + { + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || OperatingSystem.IsAndroid()) + { + return new NanosleepEvent(); + } + else if (OperatingSystem.IsWindows()) + { + return new WindowsSleepEvent(); + } + else + { + return new SleepEvent(); + } + } + + /// + /// Sleeps up to the closest point to the timepoint that the OS reasonably allows. + /// The provided event is used by the timer to wake the current thread, and should not be signalled from any other source. + /// + /// Event used to wake this thread + /// Target timepoint in host ticks + public static void SleepUntilTimePoint(EventWaitHandle evt, long timePoint) + { + if (OperatingSystem.IsWindows()) + { + WindowsGranularTimer.Instance.SleepUntilTimePointWithoutExternalSignal(evt, timePoint); + } + else + { + // Events might oversleep by a little, depending on OS. + // We don't want to miss the timepoint, so bias the wait to be lower. + // Nanosleep can possibly handle it better, too. + long accuracyBias = PerformanceCounter.TicksPerMillisecond / 2; + long now = PerformanceCounter.ElapsedTicks + accuracyBias; + long ms = Math.Min((timePoint - now) / PerformanceCounter.TicksPerMillisecond, int.MaxValue); + + if (ms > 0) + { + evt.WaitOne((int)ms); + } + + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || OperatingSystem.IsAndroid()) + { + // Do a nanosleep. + now = PerformanceCounter.ElapsedTicks; + long ns = ((timePoint - now) * 1_000_000) / PerformanceCounter.TicksPerMillisecond; + + Nanosleep.SleepAtMost(ns); + } + } + } + + /// + /// Spinwait until the given timepoint. If wakeSignal is or becomes 1, return early. + /// Thread is allowed to yield. + /// + /// Target timepoint in host ticks + /// Returns early if this is set to 1 + public static void SpinWaitUntilTimePoint(long timePoint, ref long wakeSignal) + { + SpinWait spinWait = new(); + + while (Interlocked.Read(ref wakeSignal) != 1 && PerformanceCounter.ElapsedTicks < timePoint) + { + // Our time is close - don't let SpinWait go off and potentially Thread.Sleep(). + if (spinWait.NextSpinWillYield) + { + Thread.Yield(); + + spinWait.Reset(); + } + else + { + spinWait.SpinOnce(); + } + } + } + + /// + /// Spinwait until the given timepoint, with no opportunity to wake early. + /// + /// Target timepoint in host ticks + public static void SpinWaitUntilTimePoint(long timePoint) + { + while (PerformanceCounter.ElapsedTicks < timePoint) + { + Thread.SpinWait(5); + } + } + } +} diff --git a/src/Ryujinx.Common/PreciseSleep/SleepEvent.cs b/src/Ryujinx.Common/PreciseSleep/SleepEvent.cs new file mode 100644 index 00000000..f0769d1e --- /dev/null +++ b/src/Ryujinx.Common/PreciseSleep/SleepEvent.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading; + +namespace Ryujinx.Common.PreciseSleep +{ + /// + /// A cross-platform precise sleep event that has millisecond granularity. + /// + internal class SleepEvent : IPreciseSleepEvent + { + private readonly AutoResetEvent _waitEvent = new(false); + + public long AdjustTimePoint(long timePoint, long timeoutNs) + { + // No adjustment + return timePoint; + } + + public bool SleepUntil(long timePoint) + { + long now = PerformanceCounter.ElapsedTicks; + long ms = Math.Min((timePoint - now) / PerformanceCounter.TicksPerMillisecond, int.MaxValue); + + if (ms > 0) + { + _waitEvent.WaitOne((int)ms); + + return true; + } + + return false; + } + + public void Sleep() + { + _waitEvent.WaitOne(); + } + + public void Signal() + { + _waitEvent.Set(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + _waitEvent.Dispose(); + } + } +} diff --git a/src/Ryujinx.Common/PreciseSleep/WindowsGranularTimer.cs b/src/Ryujinx.Common/PreciseSleep/WindowsGranularTimer.cs new file mode 100644 index 00000000..3bf09270 --- /dev/null +++ b/src/Ryujinx.Common/PreciseSleep/WindowsGranularTimer.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Threading; + +namespace Ryujinx.Common.SystemInterop +{ + /// + /// Timer that attempts to align with the hardware timer interrupt, + /// and can alert listeners on ticks. + /// + [SupportedOSPlatform("windows")] + internal partial class WindowsGranularTimer + { + private const int MinimumGranularity = 5000; + + private static readonly WindowsGranularTimer _instance = new(); + public static WindowsGranularTimer Instance => _instance; + + private readonly struct WaitingObject + { + public readonly long Id; + public readonly EventWaitHandle Signal; + public readonly long TimePoint; + + public WaitingObject(long id, EventWaitHandle signal, long timePoint) + { + Id = id; + Signal = signal; + TimePoint = timePoint; + } + } + + [LibraryImport("ntdll.dll", SetLastError = true)] + private static partial int NtSetTimerResolution(int DesiredResolution, [MarshalAs(UnmanagedType.Bool)] bool SetResolution, out int CurrentResolution); + + [LibraryImport("ntdll.dll", SetLastError = true)] + private static partial int NtQueryTimerResolution(out int MaximumResolution, out int MinimumResolution, out int CurrentResolution); + + [LibraryImport("ntdll.dll", SetLastError = true)] + private static partial uint NtDelayExecution([MarshalAs(UnmanagedType.Bool)] bool Alertable, ref long DelayInterval); + + public long GranularityNs => _granularityNs; + public long GranularityTicks => _granularityTicks; + + private readonly Thread _timerThread; + private long _granularityNs = MinimumGranularity * 100L; + private long _granularityTicks; + private long _lastTicks = PerformanceCounter.ElapsedTicks; + private long _lastId; + + private readonly object _lock = new(); + private readonly List _waitingObjects = new(); + + private WindowsGranularTimer() + { + _timerThread = new Thread(Loop) + { + IsBackground = true, + Name = "Common.WindowsTimer", + Priority = ThreadPriority.Highest + }; + + _timerThread.Start(); + } + + /// + /// Measure and initialize the timer's target granularity. + /// + private void Initialize() + { + NtQueryTimerResolution(out _, out int min, out int curr); + + if (min > 0) + { + min = Math.Max(min, MinimumGranularity); + + _granularityNs = min * 100L; + NtSetTimerResolution(min, true, out _); + } + else + { + _granularityNs = curr * 100L; + } + + _granularityTicks = (_granularityNs * PerformanceCounter.TicksPerMillisecond) / 1_000_000; + } + + /// + /// Main loop for the timer thread. Wakes every clock tick and signals any listeners, + /// as well as keeping track of clock alignment. + /// + private void Loop() + { + Initialize(); + while (true) + { + long delayInterval = -1; // Next tick + NtSetTimerResolution((int)(_granularityNs / 100), true, out _); + NtDelayExecution(false, ref delayInterval); + + long newTicks = PerformanceCounter.ElapsedTicks; + long nextTicks = newTicks + _granularityTicks; + + lock (_lock) + { + for (int i = 0; i < _waitingObjects.Count; i++) + { + if (nextTicks > _waitingObjects[i].TimePoint) + { + // The next clock tick will be after the timepoint, we need to signal now. + _waitingObjects[i].Signal.Set(); + + _waitingObjects.RemoveAt(i--); + } + } + + _lastTicks = newTicks; + } + } + } + + /// + /// Sleep until a timepoint. + /// + /// Reset event to use to be awoken by the clock tick, or an external signal + /// Target timepoint + /// True if waited or signalled, false otherwise + public bool SleepUntilTimePoint(AutoResetEvent evt, long timePoint) + { + if (evt.WaitOne(0)) + { + return true; + } + + long id; + + lock (_lock) + { + // Return immediately if the next tick is after the requested timepoint. + long nextTicks = _lastTicks + _granularityTicks; + + if (nextTicks > timePoint) + { + return false; + } + + id = ++_lastId; + + _waitingObjects.Add(new WaitingObject(id, evt, timePoint)); + } + + evt.WaitOne(); + + lock (_lock) + { + for (int i = 0; i < _waitingObjects.Count; i++) + { + if (id == _waitingObjects[i].Id) + { + _waitingObjects.RemoveAt(i--); + break; + } + } + } + + return true; + } + + /// + /// Sleep until a timepoint, but don't expect any external signals. + /// + /// + /// Saves some effort compared to the sleep that expects to be signalled. + /// + /// Reset event to use to be awoken by the clock tick + /// Target timepoint + /// True if waited, false otherwise + public bool SleepUntilTimePointWithoutExternalSignal(EventWaitHandle evt, long timePoint) + { + long id; + + lock (_lock) + { + // Return immediately if the next tick is after the requested timepoint. + long nextTicks = _lastTicks + _granularityTicks; + + if (nextTicks > timePoint) + { + return false; + } + + id = ++_lastId; + + _waitingObjects.Add(new WaitingObject(id, evt, timePoint)); + } + + evt.WaitOne(); + + return true; + } + + /// + /// Returns the two nearest clock ticks for a given timepoint. + /// + /// Target timepoint + /// The nearest clock ticks before and after the given timepoint + public (long, long) ReturnNearestTicks(long timePoint) + { + long last = _lastTicks; + long delta = timePoint - last; + + long lowTicks = delta / _granularityTicks; + long highTicks = (delta + _granularityTicks - 1) / _granularityTicks; + + return (last + lowTicks * _granularityTicks, last + highTicks * _granularityTicks); + } + } +} diff --git a/src/Ryujinx.Common/PreciseSleep/WindowsSleepEvent.cs b/src/Ryujinx.Common/PreciseSleep/WindowsSleepEvent.cs new file mode 100644 index 00000000..87c10d18 --- /dev/null +++ b/src/Ryujinx.Common/PreciseSleep/WindowsSleepEvent.cs @@ -0,0 +1,92 @@ +using Ryujinx.Common.SystemInterop; +using System; +using System.Runtime.Versioning; +using System.Threading; + +namespace Ryujinx.Common.PreciseSleep +{ + /// + /// A precise sleep event that uses Windows specific methods to increase clock resolution beyond 1ms, + /// use the clock's phase for more precise waits, and potentially align timepoints with it. + /// + [SupportedOSPlatform("windows")] + internal class WindowsSleepEvent : IPreciseSleepEvent + { + /// + /// The clock can drift a bit, so add this to encourage the clock to still wait if the next tick is forecasted slightly before it. + /// + private const long ErrorBias = 50000; + + /// + /// Allowed to be 0.05ms away from the clock granularity to reduce precision. + /// + private const long ClockAlignedBias = 50000; + + /// + /// The fraction of clock granularity above the timepoint that will align it down to the lower timepoint. + /// Currently set to the lower 1/4, so for 0.5ms granularity: 0.1ms would be rounded down, 0.2 ms would be rounded up. + /// + private const long ReverseTimePointFraction = 4; + + private readonly AutoResetEvent _waitEvent = new(false); + private readonly WindowsGranularTimer _timer = WindowsGranularTimer.Instance; + + /// + /// Set to true to disable timepoint realignment. + /// + public bool Precise { get; set; } = false; + + public long AdjustTimePoint(long timePoint, long timeoutNs) + { + if (Precise || timePoint == long.MaxValue) + { + return timePoint; + } + + // Does the timeout align with the host clock? + + long granularity = _timer.GranularityNs; + long misalignment = timeoutNs % granularity; + + if ((misalignment < ClockAlignedBias || misalignment > granularity - ClockAlignedBias) && timeoutNs > ClockAlignedBias) + { + // Inaccurate sleep for 0.5ms increments, typically. + + (long low, long high) = _timer.ReturnNearestTicks(timePoint); + + if (timePoint - low < _timer.GranularityTicks / ReverseTimePointFraction) + { + timePoint = low; + } + else + { + timePoint = high; + } + } + + return timePoint; + } + + public bool SleepUntil(long timePoint) + { + return _timer.SleepUntilTimePoint(_waitEvent, timePoint + (ErrorBias * PerformanceCounter.TicksPerMillisecond) / 1_000_000); + } + + public void Sleep() + { + _waitEvent.WaitOne(); + } + + public void Signal() + { + _waitEvent.Set(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + _waitEvent.Dispose(); + } + } +} diff --git a/src/Ryujinx.Common/ReactiveObject.cs b/src/Ryujinx.Common/ReactiveObject.cs new file mode 100644 index 00000000..4831edb5 --- /dev/null +++ b/src/Ryujinx.Common/ReactiveObject.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading; + +namespace Ryujinx.Common +{ + public class ReactiveObject + { + private readonly ReaderWriterLockSlim _readerWriterLock = new(); + private bool _isInitialized; + private T _value; + + public event EventHandler> Event; + + public T Value + { + get + { + _readerWriterLock.EnterReadLock(); + T value = _value; + _readerWriterLock.ExitReadLock(); + + return value; + } + set + { + _readerWriterLock.EnterWriteLock(); + + T oldValue = _value; + + bool oldIsInitialized = _isInitialized; + + _isInitialized = true; + _value = value; + + _readerWriterLock.ExitWriteLock(); + + if (!oldIsInitialized || oldValue == null || !oldValue.Equals(_value)) + { + Event?.Invoke(this, new ReactiveEventArgs(oldValue, value)); + } + } + } + + public static implicit operator T(ReactiveObject obj) + { + return obj.Value; + } + } + + public class ReactiveEventArgs + { + public T OldValue { get; } + public T NewValue { get; } + + public ReactiveEventArgs(T oldValue, T newValue) + { + OldValue = oldValue; + NewValue = newValue; + } + } +} diff --git a/src/Ryujinx.Common/ReferenceEqualityComparer.cs b/src/Ryujinx.Common/ReferenceEqualityComparer.cs new file mode 100644 index 00000000..714a967a --- /dev/null +++ b/src/Ryujinx.Common/ReferenceEqualityComparer.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.Common +{ + public class ReferenceEqualityComparer : IEqualityComparer + where T : class + { + public bool Equals(T x, T y) + { + return x == y; + } + + public int GetHashCode([DisallowNull] T obj) + { + return obj.GetHashCode(); + } + } +} diff --git a/src/Ryujinx.Common/ReleaseInformation.cs b/src/Ryujinx.Common/ReleaseInformation.cs new file mode 100644 index 00000000..774ae012 --- /dev/null +++ b/src/Ryujinx.Common/ReleaseInformation.cs @@ -0,0 +1,31 @@ +using System.Reflection; + +namespace Ryujinx.Common +{ + // DO NOT EDIT, filled by CI + public static class ReleaseInformation + { + private const string FlatHubChannelOwner = "flathub"; + + private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%"; + private const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%"; + private const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%"; + private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%"; + + public const string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%"; + public const string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%"; + + public static string ConfigName => !ConfigFileName.StartsWith("%%") ? ConfigFileName : "Config.json"; + + public static bool IsValid => + !BuildGitHash.StartsWith("%%") && + !ReleaseChannelName.StartsWith("%%") && + !ReleaseChannelOwner.StartsWith("%%") && + !ReleaseChannelRepo.StartsWith("%%") && + !ConfigFileName.StartsWith("%%"); + + public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannelOwner); + + public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute()?.InformationalVersion; + } +} diff --git a/src/Ryujinx.Common/Ryujinx.Common.csproj b/src/Ryujinx.Common/Ryujinx.Common.csproj new file mode 100644 index 00000000..da2f13a2 --- /dev/null +++ b/src/Ryujinx.Common/Ryujinx.Common.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + true + $(DefineConstants);$(ExtraDefineConstants) + + + + + + + + + diff --git a/src/Ryujinx.Common/SystemInterop/DisplaySleep.cs b/src/Ryujinx.Common/SystemInterop/DisplaySleep.cs new file mode 100644 index 00000000..4dff5410 --- /dev/null +++ b/src/Ryujinx.Common/SystemInterop/DisplaySleep.cs @@ -0,0 +1,35 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.SystemInterop +{ + public partial class DisplaySleep + { + [Flags] + enum EXECUTION_STATE : uint + { + ES_CONTINUOUS = 0x80000000, + ES_DISPLAY_REQUIRED = 0x00000002, + ES_SYSTEM_REQUIRED = 0x00000001, + } + + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags); + + static public void Prevent() + { + if (OperatingSystem.IsWindows()) + { + SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS | EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED); + } + } + + static public void Restore() + { + if (OperatingSystem.IsWindows()) + { + SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS); + } + } + } +} diff --git a/src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs b/src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs new file mode 100644 index 00000000..b8e1df7d --- /dev/null +++ b/src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs @@ -0,0 +1,96 @@ +using Ryujinx.Common.Logging; +using System; +using System.Globalization; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.SystemInterop +{ + public static partial class ForceDpiAware + { + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool SetProcessDPIAware(); + + private const string X11LibraryName = "libX11.so.6"; + + [LibraryImport(X11LibraryName)] + private static partial IntPtr XOpenDisplay([MarshalAs(UnmanagedType.LPStr)] string display); + + [LibraryImport(X11LibraryName)] + private static partial IntPtr XGetDefault(IntPtr display, [MarshalAs(UnmanagedType.LPStr)] string program, [MarshalAs(UnmanagedType.LPStr)] string option); + + [LibraryImport(X11LibraryName)] + private static partial int XDisplayWidth(IntPtr display, int screenNumber); + + [LibraryImport(X11LibraryName)] + private static partial int XDisplayWidthMM(IntPtr display, int screenNumber); + + [LibraryImport(X11LibraryName)] + private static partial int XCloseDisplay(IntPtr display); + + private const double StandardDpiScale = 96.0; + private const double MaxScaleFactor = 1.25; + + /// + /// Marks the application as DPI-Aware when running on the Windows operating system. + /// + public static void Windows() + { + // Make process DPI aware for proper window sizing on high-res screens. + if (OperatingSystem.IsWindowsVersionAtLeast(6)) + { + SetProcessDPIAware(); + } + } + + public static double GetActualScaleFactor() + { + double userDpiScale = 96.0; + + try + { + if (OperatingSystem.IsWindows()) + { + userDpiScale = GdiPlusHelper.GetDpiX(IntPtr.Zero); + } + else if (OperatingSystem.IsLinux()) + { + string xdgSessionType = Environment.GetEnvironmentVariable("XDG_SESSION_TYPE")?.ToLower(); + + if (xdgSessionType == null || xdgSessionType == "x11") + { + IntPtr display = XOpenDisplay(null); + string dpiString = Marshal.PtrToStringAnsi(XGetDefault(display, "Xft", "dpi")); + if (dpiString == null || !double.TryParse(dpiString, NumberStyles.Any, CultureInfo.InvariantCulture, out userDpiScale)) + { + userDpiScale = XDisplayWidth(display, 0) * 25.4 / XDisplayWidthMM(display, 0); + } + _ = XCloseDisplay(display); + } + else if (xdgSessionType == "wayland") + { + // TODO + Logger.Warning?.Print(LogClass.Application, "Couldn't determine monitor DPI: Wayland not yet supported"); + } + else + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: Unrecognised XDG_SESSION_TYPE: {xdgSessionType}"); + } + } + } + catch (Exception e) + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}"); + } + + return userDpiScale; + } + + public static double GetWindowScaleFactor() + { + double userDpiScale = GetActualScaleFactor(); + + return Math.Min(userDpiScale / StandardDpiScale, MaxScaleFactor); + } + } +} diff --git a/src/Ryujinx.Common/SystemInterop/GdiPlusHelper.cs b/src/Ryujinx.Common/SystemInterop/GdiPlusHelper.cs new file mode 100644 index 00000000..7e8e9f2a --- /dev/null +++ b/src/Ryujinx.Common/SystemInterop/GdiPlusHelper.cs @@ -0,0 +1,76 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Common.SystemInterop +{ + [SupportedOSPlatform("windows")] + public static partial class GdiPlusHelper + { + private const string LibraryName = "gdiplus.dll"; + + private static readonly IntPtr _initToken; + + static GdiPlusHelper() + { + CheckStatus(GdiplusStartup(out _initToken, StartupInputEx.Default, out _)); + } + + private static void CheckStatus(int gdiStatus) + { + if (gdiStatus != 0) + { + throw new Exception($"GDI Status Error: {gdiStatus}"); + } + } + + private struct StartupInputEx + { + public int GdiplusVersion; + +#pragma warning disable CS0649 // Field is never assigned to + public IntPtr DebugEventCallback; + public int SuppressBackgroundThread; + public int SuppressExternalCodecs; + public int StartupParameters; +#pragma warning restore CS0649 + + public static StartupInputEx Default => new() + { + // We assume Windows 8 and upper + GdiplusVersion = 2, + DebugEventCallback = IntPtr.Zero, + SuppressBackgroundThread = 0, + SuppressExternalCodecs = 0, + StartupParameters = 0, + }; + } + + private struct StartupOutput + { + public IntPtr NotificationHook; + public IntPtr NotificationUnhook; + } + + [LibraryImport(LibraryName)] + private static partial int GdiplusStartup(out IntPtr token, in StartupInputEx input, out StartupOutput output); + + [LibraryImport(LibraryName)] + private static partial int GdipCreateFromHWND(IntPtr hwnd, out IntPtr graphics); + + [LibraryImport(LibraryName)] + private static partial int GdipDeleteGraphics(IntPtr graphics); + + [LibraryImport(LibraryName)] + private static partial int GdipGetDpiX(IntPtr graphics, out float dpi); + + public static float GetDpiX(IntPtr hwnd) + { + CheckStatus(GdipCreateFromHWND(hwnd, out IntPtr graphicsHandle)); + CheckStatus(GdipGetDpiX(graphicsHandle, out float result)); + CheckStatus(GdipDeleteGraphics(graphicsHandle)); + + return result; + } + } +} diff --git a/src/Ryujinx.Common/SystemInterop/StdErrAdapter.cs b/src/Ryujinx.Common/SystemInterop/StdErrAdapter.cs new file mode 100644 index 00000000..a04c404d --- /dev/null +++ b/src/Ryujinx.Common/SystemInterop/StdErrAdapter.cs @@ -0,0 +1,104 @@ +using Microsoft.Win32.SafeHandles; +using Ryujinx.Common.Logging; +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.Common.SystemInterop +{ + public partial class StdErrAdapter : IDisposable + { + private bool _disposable; + private Stream _pipeReader; + private Stream _pipeWriter; + private CancellationTokenSource _cancellationTokenSource; + private Task _worker; + + public StdErrAdapter() + { + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + RegisterPosix(); + } + } + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private void RegisterPosix() + { + const int StdErrFileno = 2; + + (int readFd, int writeFd) = MakePipe(); + dup2(writeFd, StdErrFileno); + + _pipeReader = CreateFileDescriptorStream(readFd); + _pipeWriter = CreateFileDescriptorStream(writeFd); + + _cancellationTokenSource = new CancellationTokenSource(); + _worker = Task.Run(async () => await EventWorkerAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); + _disposable = true; + } + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private async Task EventWorkerAsync(CancellationToken cancellationToken) + { + using TextReader reader = new StreamReader(_pipeReader, leaveOpen: true); + string line; + while (cancellationToken.IsCancellationRequested == false && (line = await reader.ReadLineAsync(cancellationToken)) != null) + { + Logger.Error?.PrintRawMsg(line); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + if (_disposable) + { + _disposable = false; + + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + _cancellationTokenSource.Cancel(); + _worker.Wait(0); + _pipeReader?.Close(); + _pipeWriter?.Close(); + } + } + } + + [LibraryImport("libc", SetLastError = true)] + private static partial int dup2(int fd, int fd2); + + [LibraryImport("libc", SetLastError = true)] + private static partial int pipe(Span pipefd); + + private static (int, int) MakePipe() + { + Span pipefd = stackalloc int[2]; + + if (pipe(pipefd) == 0) + { + return (pipefd[0], pipefd[1]); + } + + throw new(); + } + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private static Stream CreateFileDescriptorStream(int fd) + { + return new FileStream( + new SafeFileHandle(fd, ownsHandle: true), + FileAccess.ReadWrite + ); + } + + } +} diff --git a/src/Ryujinx.Common/SystemInterop/WindowsMultimediaTimerResolution.cs b/src/Ryujinx.Common/SystemInterop/WindowsMultimediaTimerResolution.cs new file mode 100644 index 00000000..0fbce37e --- /dev/null +++ b/src/Ryujinx.Common/SystemInterop/WindowsMultimediaTimerResolution.cs @@ -0,0 +1,114 @@ +using Ryujinx.Common.Logging; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Common.SystemInterop +{ + /// + /// Handle Windows Multimedia timer resolution. + /// + [SupportedOSPlatform("windows")] + public partial class WindowsMultimediaTimerResolution : IDisposable + { + [StructLayout(LayoutKind.Sequential)] + public struct TimeCaps + { + public uint wPeriodMin; + public uint wPeriodMax; + } + + [LibraryImport("winmm.dll", EntryPoint = "timeGetDevCaps", SetLastError = true)] + private static partial uint TimeGetDevCaps(ref TimeCaps timeCaps, uint sizeTimeCaps); + + [LibraryImport("winmm.dll", EntryPoint = "timeBeginPeriod")] + private static partial uint TimeBeginPeriod(uint uMilliseconds); + + [LibraryImport("winmm.dll", EntryPoint = "timeEndPeriod")] + private static partial uint TimeEndPeriod(uint uMilliseconds); + + private uint _targetResolutionInMilliseconds; + private bool _isActive; + + /// + /// Create a new and activate the given resolution. + /// + /// + public WindowsMultimediaTimerResolution(uint targetResolutionInMilliseconds) + { + _targetResolutionInMilliseconds = targetResolutionInMilliseconds; + + EnsureResolutionSupport(); + Activate(); + } + + private void EnsureResolutionSupport() + { + TimeCaps timeCaps = default; + + uint result = TimeGetDevCaps(ref timeCaps, (uint)Unsafe.SizeOf()); + + if (result != 0) + { + Logger.Notice.Print(LogClass.Application, $"timeGetDevCaps failed with result: {result}"); + } + else + { + uint supportedTargetResolutionInMilliseconds = Math.Min(Math.Max(timeCaps.wPeriodMin, _targetResolutionInMilliseconds), timeCaps.wPeriodMax); + + if (supportedTargetResolutionInMilliseconds != _targetResolutionInMilliseconds) + { + Logger.Notice.Print(LogClass.Application, $"Target resolution isn't supported by OS, using closest resolution: {supportedTargetResolutionInMilliseconds}ms"); + + _targetResolutionInMilliseconds = supportedTargetResolutionInMilliseconds; + } + } + } + + private void Activate() + { + uint result = TimeBeginPeriod(_targetResolutionInMilliseconds); + + if (result != 0) + { + Logger.Notice.Print(LogClass.Application, $"timeBeginPeriod failed with result: {result}"); + } + else + { + _isActive = true; + } + } + + private void Disable() + { + if (_isActive) + { + uint result = TimeEndPeriod(_targetResolutionInMilliseconds); + + if (result != 0) + { + Logger.Notice.Print(LogClass.Application, $"timeEndPeriod failed with result: {result}"); + } + else + { + _isActive = false; + } + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Disable(); + } + } + } +} diff --git a/src/Ryujinx.Common/Utilities/BitUtils.cs b/src/Ryujinx.Common/Utilities/BitUtils.cs new file mode 100644 index 00000000..0bf9c8ac --- /dev/null +++ b/src/Ryujinx.Common/Utilities/BitUtils.cs @@ -0,0 +1,59 @@ +using System.Numerics; + +namespace Ryujinx.Common +{ + public static class BitUtils + { + public static T AlignUp(T value, T size) + where T : IBinaryInteger + { + return (value + (size - T.One)) & -size; + } + + public static T AlignDown(T value, T size) + where T : IBinaryInteger + { + return value & -size; + } + + public static T DivRoundUp(T value, T dividend) + where T : IBinaryInteger + { + return (value + (dividend - T.One)) / dividend; + } + + public static int Pow2RoundUp(int value) + { + value--; + + value |= (value >> 1); + value |= (value >> 2); + value |= (value >> 4); + value |= (value >> 8); + value |= (value >> 16); + + return ++value; + } + + public static int Pow2RoundDown(int value) + { + return BitOperations.IsPow2(value) ? value : Pow2RoundUp(value) >> 1; + } + + public static long ReverseBits64(long value) + { + return (long)ReverseBits64((ulong)value); + } + + private static ulong ReverseBits64(ulong value) + { + value = ((value & 0xaaaaaaaaaaaaaaaa) >> 1) | ((value & 0x5555555555555555) << 1); + value = ((value & 0xcccccccccccccccc) >> 2) | ((value & 0x3333333333333333) << 2); + value = ((value & 0xf0f0f0f0f0f0f0f0) >> 4) | ((value & 0x0f0f0f0f0f0f0f0f) << 4); + value = ((value & 0xff00ff00ff00ff00) >> 8) | ((value & 0x00ff00ff00ff00ff) << 8); + value = ((value & 0xffff0000ffff0000) >> 16) | ((value & 0x0000ffff0000ffff) << 16); + + return (value >> 32) | (value << 32); + } + } +} diff --git a/src/Ryujinx.Common/Utilities/BitfieldExtensions.cs b/src/Ryujinx.Common/Utilities/BitfieldExtensions.cs new file mode 100644 index 00000000..8e9b696e --- /dev/null +++ b/src/Ryujinx.Common/Utilities/BitfieldExtensions.cs @@ -0,0 +1,57 @@ +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Common.Utilities +{ + public static class BitfieldExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Extract(this T value, int lsb) where T : IBinaryInteger + { + int bitSize = Unsafe.SizeOf() * 8; + lsb &= bitSize - 1; + + return !T.IsZero((value >>> lsb) & T.One); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Extract(this T value, int lsb, int length) where T : IBinaryInteger + { + int bitSize = Unsafe.SizeOf() * 8; + lsb &= bitSize - 1; + + return (value >>> lsb) & (~T.Zero >>> (bitSize - length)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T ExtractSx(this T value, int lsb, int length) where T : IBinaryInteger + { + int bitSize = Unsafe.SizeOf() * 8; + int shift = lsb & (bitSize - 1); + + return (value << (bitSize - (shift + length))) >> (bitSize - length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Insert(this T value, int lsb, bool toInsert) where T : IBinaryInteger + { + int bitSize = Unsafe.SizeOf() * 8; + lsb &= bitSize - 1; + + T mask = T.One << lsb; + + return (value & ~mask) | (toInsert ? mask : T.Zero); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Insert(this T value, int lsb, int length, T toInsert) where T : IBinaryInteger + { + int bitSize = Unsafe.SizeOf() * 8; + lsb &= bitSize - 1; + + T mask = (~T.Zero >>> (bitSize - length)) << lsb; + + return (value & ~mask) | ((toInsert << lsb) & mask); + } + } +} diff --git a/src/Ryujinx.Common/Utilities/Buffers.cs b/src/Ryujinx.Common/Utilities/Buffers.cs new file mode 100644 index 00000000..b66f6025 --- /dev/null +++ b/src/Ryujinx.Common/Utilities/Buffers.cs @@ -0,0 +1,59 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Utilities +{ + [DebuggerDisplay("{ToString()}")] + [StructLayout(LayoutKind.Sequential, Size = 16)] + public struct Buffer16 + { + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy0; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly ulong _dummy1; + + public byte this[int i] + { + get => Bytes[i]; + set => Bytes[i] = value; + } + + public Span Bytes => SpanHelpers.AsByteSpan(ref this); + + // Prevent a defensive copy by changing the read-only in reference to a reference with Unsafe.AsRef() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Span(in Buffer16 value) + { + return SpanHelpers.AsByteSpan(ref Unsafe.AsRef(in value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Buffer16 value) + { + return SpanHelpers.AsReadOnlyByteSpan(ref Unsafe.AsRef(in value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T As() where T : unmanaged + { + if (Unsafe.SizeOf() > (uint)Unsafe.SizeOf()) + { + throw new ArgumentException(); + } + + return ref MemoryMarshal.GetReference(AsSpan()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AsSpan() where T : unmanaged + { + return SpanHelpers.AsSpan(ref this); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly ReadOnlySpan AsReadOnlySpan() where T : unmanaged + { + return SpanHelpers.AsReadOnlySpan(ref Unsafe.AsRef(in this)); + } + } +} diff --git a/src/Ryujinx.Common/Utilities/CommonJsonContext.cs b/src/Ryujinx.Common/Utilities/CommonJsonContext.cs new file mode 100644 index 00000000..6c59ccae --- /dev/null +++ b/src/Ryujinx.Common/Utilities/CommonJsonContext.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Utilities +{ + [JsonSerializable(typeof(string[]), TypeInfoPropertyName = "StringArray")] + [JsonSerializable(typeof(Dictionary), TypeInfoPropertyName = "StringDictionary")] + public partial class CommonJsonContext : JsonSerializerContext + { + } +} diff --git a/src/Ryujinx.Common/Utilities/EmbeddedResources.cs b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs new file mode 100644 index 00000000..7530c012 --- /dev/null +++ b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs @@ -0,0 +1,152 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Ryujinx.Common +{ + public static class EmbeddedResources + { + private readonly static Assembly _resourceAssembly; + + static EmbeddedResources() + { + _resourceAssembly = Assembly.GetAssembly(typeof(EmbeddedResources)); + } + + public static byte[] Read(string filename) + { + var (assembly, path) = ResolveManifestPath(filename); + + return Read(assembly, path); + } + + public static Task ReadAsync(string filename) + { + var (assembly, path) = ResolveManifestPath(filename); + + return ReadAsync(assembly, path); + } + + public static byte[] Read(Assembly assembly, string filename) + { + using var stream = GetStream(assembly, filename); + if (stream == null) + { + return null; + } + + return StreamUtils.StreamToBytes(stream); + } + + public static MemoryOwner ReadFileToRentedMemory(string filename) + { + var (assembly, path) = ResolveManifestPath(filename); + + return ReadFileToRentedMemory(assembly, path); + } + + public static MemoryOwner ReadFileToRentedMemory(Assembly assembly, string filename) + { + using var stream = GetStream(assembly, filename); + + return stream is null + ? null + : StreamUtils.StreamToRentedMemory(stream); + } + + public async static Task ReadAsync(Assembly assembly, string filename) + { + using var stream = GetStream(assembly, filename); + if (stream == null) + { + return null; + } + + return await StreamUtils.StreamToBytesAsync(stream); + } + + public static string ReadAllText(string filename) + { + var (assembly, path) = ResolveManifestPath(filename); + + return ReadAllText(assembly, path); + } + + public static Task ReadAllTextAsync(string filename) + { + var (assembly, path) = ResolveManifestPath(filename); + + return ReadAllTextAsync(assembly, path); + } + + public static string ReadAllText(Assembly assembly, string filename) + { + using var stream = GetStream(assembly, filename); + if (stream == null) + { + return null; + } + + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + public async static Task ReadAllTextAsync(Assembly assembly, string filename) + { + using var stream = GetStream(assembly, filename); + if (stream == null) + { + return null; + } + + using var reader = new StreamReader(stream); + return await reader.ReadToEndAsync(); + } + + public static Stream GetStream(string filename) + { + var (assembly, path) = ResolveManifestPath(filename); + + return GetStream(assembly, path); + } + + public static Stream GetStream(Assembly assembly, string filename) + { + var @namespace = assembly.GetName().Name; + var manifestUri = @namespace + "." + filename.Replace('/', '.'); + + var stream = assembly.GetManifestResourceStream(manifestUri); + + return stream; + } + + public static string[] GetAllAvailableResources(string path, string ext = "") + { + return ResolveManifestPath(path).Item1.GetManifestResourceNames() + .Where(r => r.EndsWith(ext)) + .ToArray(); + } + + private static (Assembly, string) ResolveManifestPath(string filename) + { + var segments = filename.Split('/', 2, StringSplitOptions.RemoveEmptyEntries); + + if (segments.Length >= 2) + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (assembly.GetName().Name == segments[0]) + { + return (assembly, segments[1]); + } + } + } + + return (_resourceAssembly, filename); + } + } +} diff --git a/src/Ryujinx.Common/Utilities/FileSystemUtils.cs b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs new file mode 100644 index 00000000..a57fa8a7 --- /dev/null +++ b/src/Ryujinx.Common/Utilities/FileSystemUtils.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Ryujinx.Common.Utilities +{ + public static class FileSystemUtils + { + public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive) + { + // Get information about the source directory + var dir = new DirectoryInfo(sourceDir); + + // Check if the source directory exists + if (!dir.Exists) + { + throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); + } + + // Cache directories before we start copying + DirectoryInfo[] dirs = dir.GetDirectories(); + + // Create the destination directory + Directory.CreateDirectory(destinationDir); + + // Get the files in the source directory and copy to the destination directory + foreach (FileInfo file in dir.GetFiles()) + { + string targetFilePath = Path.Combine(destinationDir, file.Name); + file.CopyTo(targetFilePath); + } + + // If recursive and copying subdirectories, recursively call this method + if (recursive) + { + foreach (DirectoryInfo subDir in dirs) + { + string newDestinationDir = Path.Combine(destinationDir, subDir.Name); + CopyDirectory(subDir.FullName, newDestinationDir, true); + } + } + } + + public static void MoveDirectory(string sourceDir, string destinationDir) + { + CopyDirectory(sourceDir, destinationDir, true); + Directory.Delete(sourceDir, true); + } + + public static string SanitizeFileName(string fileName) + { + var reservedChars = new HashSet(Path.GetInvalidFileNameChars()); + return string.Concat(fileName.Select(c => reservedChars.Contains(c) ? '_' : c)); + } + } +} diff --git a/src/Ryujinx.Common/Utilities/HexUtils.cs b/src/Ryujinx.Common/Utilities/HexUtils.cs new file mode 100644 index 00000000..cf975e44 --- /dev/null +++ b/src/Ryujinx.Common/Utilities/HexUtils.cs @@ -0,0 +1,90 @@ +using System; +using System.Text; + +namespace Ryujinx.Common +{ + public static class HexUtils + { + private static readonly char[] _hexChars = "0123456789ABCDEF".ToCharArray(); + + private const int HexTableColumnWidth = 8; + private const int HexTableColumnSpace = 3; + + // Modified for Ryujinx + // Original by Pascal Ganaye - CPOL License + // https://www.codeproject.com/Articles/36747/Quick-and-Dirty-HexDump-of-a-Byte-Array + public static string HexTable(byte[] bytes, int bytesPerLine = 16) + { + if (bytes == null) + { + return ""; + } + + int bytesLength = bytes.Length; + + int firstHexColumn = + HexTableColumnWidth + + HexTableColumnSpace; + + int firstCharColumn = firstHexColumn + + bytesPerLine * HexTableColumnSpace + + (bytesPerLine - 1) / HexTableColumnWidth + + 2; + + int lineLength = firstCharColumn + + bytesPerLine + + Environment.NewLine.Length; + + char[] line = (new String(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray(); + + int expectedLines = (bytesLength + bytesPerLine - 1) / bytesPerLine; + + StringBuilder result = new(expectedLines * lineLength); + + for (int i = 0; i < bytesLength; i += bytesPerLine) + { + line[0] = _hexChars[(i >> 28) & 0xF]; + line[1] = _hexChars[(i >> 24) & 0xF]; + line[2] = _hexChars[(i >> 20) & 0xF]; + line[3] = _hexChars[(i >> 16) & 0xF]; + line[4] = _hexChars[(i >> 12) & 0xF]; + line[5] = _hexChars[(i >> 8) & 0xF]; + line[6] = _hexChars[(i >> 4) & 0xF]; + line[7] = _hexChars[(i >> 0) & 0xF]; + + int hexColumn = firstHexColumn; + int charColumn = firstCharColumn; + + for (int j = 0; j < bytesPerLine; j++) + { + if (j > 0 && (j & 7) == 0) + { + hexColumn++; + } + + if (i + j >= bytesLength) + { + line[hexColumn] = ' '; + line[hexColumn + 1] = ' '; + line[charColumn] = ' '; + } + else + { + byte b = bytes[i + j]; + + line[hexColumn] = _hexChars[(b >> 4) & 0xF]; + line[hexColumn + 1] = _hexChars[b & 0xF]; + line[charColumn] = (b < 32 ? '·' : (char)b); + } + + hexColumn += 3; + charColumn++; + } + + result.Append(line); + } + + return result.ToString(); + } + } +} diff --git a/src/Ryujinx.Common/Utilities/JsonHelper.cs b/src/Ryujinx.Common/Utilities/JsonHelper.cs new file mode 100644 index 00000000..95daec27 --- /dev/null +++ b/src/Ryujinx.Common/Utilities/JsonHelper.cs @@ -0,0 +1,98 @@ +using System.IO; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; + +namespace Ryujinx.Common.Utilities +{ + public class JsonHelper + { + private static readonly JsonNamingPolicy _snakeCasePolicy = new SnakeCaseNamingPolicy(); + private const int DefaultFileWriteBufferSize = 4096; + + /// + /// Creates new serializer options with default settings. + /// + /// + /// It is REQUIRED for you to save returned options statically or as a part of static serializer context + /// in order to avoid performance issues. You can safely modify returned options for your case before storing. + /// + public static JsonSerializerOptions GetDefaultSerializerOptions(bool indented = true) + { + JsonSerializerOptions options = new() + { + DictionaryKeyPolicy = _snakeCasePolicy, + PropertyNamingPolicy = _snakeCasePolicy, + WriteIndented = indented, + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip, + }; + + return options; + } + + public static string Serialize(T value, JsonTypeInfo typeInfo) + { + return JsonSerializer.Serialize(value, typeInfo); + } + + public static T Deserialize(string value, JsonTypeInfo typeInfo) + { + return JsonSerializer.Deserialize(value, typeInfo); + } + + public static void SerializeToFile(string filePath, T value, JsonTypeInfo typeInfo) + { + using FileStream file = File.Create(filePath, DefaultFileWriteBufferSize, FileOptions.WriteThrough); + JsonSerializer.Serialize(file, value, typeInfo); + } + + public static T DeserializeFromFile(string filePath, JsonTypeInfo typeInfo) + { + using FileStream file = File.OpenRead(filePath); + return JsonSerializer.Deserialize(file, typeInfo); + } + + public static void SerializeToStream(Stream stream, T value, JsonTypeInfo typeInfo) + { + JsonSerializer.Serialize(stream, value, typeInfo); + } + + private class SnakeCaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name)) + { + return name; + } + + StringBuilder builder = new(); + + for (int i = 0; i < name.Length; i++) + { + char c = name[i]; + + if (char.IsUpper(c)) + { + if (i == 0 || char.IsUpper(name[i - 1])) + { + builder.Append(char.ToLowerInvariant(c)); + } + else + { + builder.Append('_'); + builder.Append(char.ToLowerInvariant(c)); + } + } + else + { + builder.Append(c); + } + } + + return builder.ToString(); + } + } + } +} diff --git a/src/Ryujinx.Common/Utilities/MessagePackObjectFormatter.cs b/src/Ryujinx.Common/Utilities/MessagePackObjectFormatter.cs new file mode 100644 index 00000000..426cd46b --- /dev/null +++ b/src/Ryujinx.Common/Utilities/MessagePackObjectFormatter.cs @@ -0,0 +1,298 @@ +using MsgPack; +using System; +using System.Text; + +namespace Ryujinx.Common.Utilities +{ + public static class MessagePackObjectFormatter + { + public static string ToString(this MessagePackObject obj, bool pretty) + { + if (pretty) + { + return Format(obj); + } + + return obj.ToString(); + } + + public static string Format(MessagePackObject obj) + { + var builder = new IndentedStringBuilder(); + + FormatMsgPackObj(obj, builder); + + return builder.ToString(); + } + + private static void FormatMsgPackObj(MessagePackObject obj, IndentedStringBuilder builder) + { + if (obj.IsMap || obj.IsDictionary) + { + FormatMsgPackMap(obj, builder); + } + else if (obj.IsArray || obj.IsList) + { + FormatMsgPackArray(obj, builder); + } + else if (obj.IsNil) + { + builder.Append("null"); + } + else + { + var literal = obj.ToObject(); + + if (literal is String) + { + builder.AppendQuotedString(obj.AsStringUtf16()); + } + else if (literal is byte[] byteArray) + { + FormatByteArray(byteArray, builder); + } + else if (literal is MessagePackExtendedTypeObject extObject) + { + builder.Append('{'); + + // Indent + builder.IncreaseIndent() + .AppendLine(); + + // Print TypeCode field + builder.AppendQuotedString("TypeCode") + .Append(": ") + .Append(extObject.TypeCode) + .AppendLine(","); + + // Print Value field + builder.AppendQuotedString("Value") + .Append(": "); + + FormatByteArrayAsString(extObject.GetBody(), builder, true); + + // Unindent + builder.DecreaseIndent() + .AppendLine(); + + builder.Append('}'); + } + else + { + builder.Append(literal); + } + } + } + + private static void FormatByteArray(byte[] arr, IndentedStringBuilder builder) + { + builder.Append("[ "); + + foreach (var b in arr) + { + builder.Append("0x"); + builder.Append(ToHexChar(b >> 4)); + builder.Append(ToHexChar(b & 0xF)); + builder.Append(", "); + } + + // Remove trailing comma + builder.Remove(builder.Length - 2, 2); + + builder.Append(" ]"); + } + + private static void FormatByteArrayAsString(byte[] arr, IndentedStringBuilder builder, bool withPrefix) + { + builder.Append('"'); + + if (withPrefix) + { + builder.Append("0x"); + } + + foreach (var b in arr) + { + builder.Append(ToHexChar(b >> 4)); + builder.Append(ToHexChar(b & 0xF)); + } + + builder.Append('"'); + } + + private static void FormatMsgPackMap(MessagePackObject obj, IndentedStringBuilder builder) + { + var map = obj.AsDictionary(); + + builder.Append('{'); + + // Indent + builder.IncreaseIndent() + .AppendLine(); + + foreach (var item in map) + { + FormatMsgPackObj(item.Key, builder); + + builder.Append(": "); + + FormatMsgPackObj(item.Value, builder); + + builder.AppendLine(","); + } + + // Remove the trailing new line and comma + builder.TrimLastLine() + .Remove(builder.Length - 1, 1); + + // Unindent + builder.DecreaseIndent() + .AppendLine(); + + builder.Append('}'); + } + + private static void FormatMsgPackArray(MessagePackObject obj, IndentedStringBuilder builder) + { + var arr = obj.AsList(); + + builder.Append("[ "); + + foreach (var item in arr) + { + FormatMsgPackObj(item, builder); + + builder.Append(", "); + } + + // Remove trailing comma + builder.Remove(builder.Length - 2, 2); + + builder.Append(" ]"); + } + + private static char ToHexChar(int b) + { + if (b < 10) + { + return unchecked((char)('0' + b)); + } + + return unchecked((char)('A' + (b - 10))); + } + + internal class IndentedStringBuilder + { + const string DefaultIndent = " "; + + private int _indentCount; + private int _newLineIndex; + private readonly StringBuilder _builder; + + public string IndentString { get; set; } = DefaultIndent; + + public IndentedStringBuilder(StringBuilder builder) + { + _builder = builder; + } + + public IndentedStringBuilder() + : this(new StringBuilder()) + { } + + public IndentedStringBuilder(string str) + : this(new StringBuilder(str)) + { } + + public IndentedStringBuilder(int length) + : this(new StringBuilder(length)) + { } + + public int Length { get => _builder.Length; } + + public IndentedStringBuilder IncreaseIndent() + { + _indentCount++; + + return this; + } + + public IndentedStringBuilder DecreaseIndent() + { + _indentCount--; + + return this; + } + + public IndentedStringBuilder Append(char value) + { + _builder.Append(value); + + return this; + } + + public IndentedStringBuilder Append(string value) + { + _builder.Append(value); + + return this; + } + + public IndentedStringBuilder Append(object value) + { + this.Append(value.ToString()); + + return this; + } + + public IndentedStringBuilder AppendQuotedString(string value) + { + _builder.Append('"'); + _builder.Append(value); + _builder.Append('"'); + + return this; + } + + public IndentedStringBuilder AppendLine() + { + _newLineIndex = _builder.Length; + + _builder.AppendLine(); + + for (int i = 0; i < _indentCount; i++) + _builder.Append(IndentString); + + return this; + } + + public IndentedStringBuilder AppendLine(string value) + { + _builder.Append(value); + + this.AppendLine(); + + return this; + } + + public IndentedStringBuilder TrimLastLine() + { + _builder.Remove(_newLineIndex, _builder.Length - _newLineIndex); + + return this; + } + + public IndentedStringBuilder Remove(int startIndex, int length) + { + _builder.Remove(startIndex, length); + + return this; + } + + public override string ToString() + { + return _builder.ToString(); + } + } + } +} diff --git a/src/Ryujinx.Common/Utilities/NetworkHelpers.cs b/src/Ryujinx.Common/Utilities/NetworkHelpers.cs new file mode 100644 index 00000000..71e02184 --- /dev/null +++ b/src/Ryujinx.Common/Utilities/NetworkHelpers.cs @@ -0,0 +1,83 @@ +using System.Buffers.Binary; +using System.Net; +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); + } + + public static uint ConvertIpv4Address(IPAddress ipAddress) + { + return BinaryPrimitives.ReadUInt32BigEndian(ipAddress.GetAddressBytes()); + } + + public static uint ConvertIpv4Address(string ipAddress) + { + return ConvertIpv4Address(IPAddress.Parse(ipAddress)); + } + + public static IPAddress ConvertUint(uint ipAddress) + { + return new IPAddress(new byte[] { (byte)((ipAddress >> 24) & 0xFF), (byte)((ipAddress >> 16) & 0xFF), (byte)((ipAddress >> 8) & 0xFF), (byte)(ipAddress & 0xFF) }); + } + } +} diff --git a/src/Ryujinx.Common/Utilities/OsUtils.cs b/src/Ryujinx.Common/Utilities/OsUtils.cs new file mode 100644 index 00000000..a0791b09 --- /dev/null +++ b/src/Ryujinx.Common/Utilities/OsUtils.cs @@ -0,0 +1,24 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Utilities +{ + public partial class OsUtils + { + [LibraryImport("libc", SetLastError = true)] + private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite); + + public static void SetEnvironmentVariableNoCaching(string key, string value) + { + // Set the value in the cached environment variables, too. + Environment.SetEnvironmentVariable(key, value); + + if (!OperatingSystem.IsWindows()) + { + int res = setenv(key, value, 1); + Debug.Assert(res != -1); + } + } + } +} diff --git a/src/Ryujinx.Common/Utilities/SpanHelpers.cs b/src/Ryujinx.Common/Utilities/SpanHelpers.cs new file mode 100644 index 00000000..8c8e00a4 --- /dev/null +++ b/src/Ryujinx.Common/Utilities/SpanHelpers.cs @@ -0,0 +1,61 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.Utilities +{ + public static class SpanHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span CreateSpan(scoped ref T reference, int length) + { + return MemoryMarshal.CreateSpan(ref reference, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsSpan(scoped ref T reference) where T : unmanaged + { + return CreateSpan(ref reference, 1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsSpan(scoped ref TStruct reference) + where TStruct : unmanaged where TSpan : unmanaged + { + return CreateSpan(ref Unsafe.As(ref reference), + Unsafe.SizeOf() / Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsByteSpan(scoped ref T reference) where T : unmanaged + { + return CreateSpan(ref Unsafe.As(ref reference), Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan CreateReadOnlySpan(scoped ref T reference, int length) + { + return MemoryMarshal.CreateReadOnlySpan(ref reference, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsReadOnlySpan(scoped ref T reference) where T : unmanaged + { + return CreateReadOnlySpan(ref reference, 1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsReadOnlySpan(scoped ref TStruct reference) + where TStruct : unmanaged where TSpan : unmanaged + { + return CreateReadOnlySpan(ref Unsafe.As(ref reference), + Unsafe.SizeOf() / Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsReadOnlyByteSpan(scoped ref T reference) where T : unmanaged + { + return CreateReadOnlySpan(ref Unsafe.As(ref reference), Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Common/Utilities/StreamUtils.cs b/src/Ryujinx.Common/Utilities/StreamUtils.cs new file mode 100644 index 00000000..aeb6e0d5 --- /dev/null +++ b/src/Ryujinx.Common/Utilities/StreamUtils.cs @@ -0,0 +1,89 @@ +using Microsoft.IO; +using Ryujinx.Common.Memory; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.Common.Utilities +{ + public static class StreamUtils + { + public static byte[] StreamToBytes(Stream input) + { + using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input); + + return output.ToArray(); + } + + public static MemoryOwner StreamToRentedMemory(Stream input) + { + if (input is MemoryStream inputMemoryStream) + { + return MemoryStreamToRentedMemory(inputMemoryStream); + } + else if (input.CanSeek) + { + long bytesExpected = input.Length; + + MemoryOwner ownedMemory = MemoryOwner.Rent(checked((int)bytesExpected)); + + var destSpan = ownedMemory.Span; + + int totalBytesRead = 0; + + while (totalBytesRead < bytesExpected) + { + int bytesRead = input.Read(destSpan[totalBytesRead..]); + + if (bytesRead == 0) + { + ownedMemory.Dispose(); + + throw new IOException($"Tried reading {bytesExpected} but the stream closed after reading {totalBytesRead}."); + } + + totalBytesRead += bytesRead; + } + + return ownedMemory; + } + else + { + // If input is (non-seekable) then copy twice: first into a RecyclableMemoryStream, then to a rented IMemoryOwner. + using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input); + + return MemoryStreamToRentedMemory(output); + } + } + + public static async Task StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default) + { + using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + + await input.CopyToAsync(stream, cancellationToken); + + return stream.ToArray(); + } + + private static MemoryOwner MemoryStreamToRentedMemory(MemoryStream input) + { + input.Position = 0; + + MemoryOwner ownedMemory = MemoryOwner.Rent(checked((int)input.Length)); + + // Discard the return value because we assume reading a MemoryStream always succeeds completely. + _ = input.Read(ownedMemory.Span); + + return ownedMemory; + } + + private static RecyclableMemoryStream StreamToRecyclableMemoryStream(Stream input) + { + RecyclableMemoryStream stream = MemoryStreamManager.Shared.GetStream(); + + input.CopyTo(stream); + + return stream; + } + } +} diff --git a/src/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs b/src/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs new file mode 100644 index 00000000..9d3944c1 --- /dev/null +++ b/src/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs @@ -0,0 +1,37 @@ +#nullable enable +using Ryujinx.Common.Logging; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Utilities +{ + /// + /// Specifies that value of will be serialized as string in JSONs + /// + /// + /// Trimming friendly alternative to . + /// Get rid of this converter if dotnet supports similar functionality out of the box. + /// + /// Type of enum to serialize + public sealed class TypedStringEnumConverter : JsonConverter where TEnum : struct, Enum + { + public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var enumValue = reader.GetString(); + + if (Enum.TryParse(enumValue, out TEnum value)) + { + return value; + } + + Logger.Warning?.Print(LogClass.Configuration, $"Failed to parse enum value \"{enumValue}\" for {typeof(TEnum)}, using default \"{default(TEnum)}\""); + return default; + } + + public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } + } +} diff --git a/src/Ryujinx.Common/Utilities/UInt128Utils.cs b/src/Ryujinx.Common/Utilities/UInt128Utils.cs new file mode 100644 index 00000000..11385535 --- /dev/null +++ b/src/Ryujinx.Common/Utilities/UInt128Utils.cs @@ -0,0 +1,18 @@ +using System; +using System.Globalization; + +namespace Ryujinx.Common.Utilities +{ + public static class UInt128Utils + { + public static UInt128 FromHex(string hex) + { + return new UInt128(ulong.Parse(hex.AsSpan(0, 16), NumberStyles.HexNumber), ulong.Parse(hex.AsSpan(16), NumberStyles.HexNumber)); + } + + public static UInt128 CreateRandom() + { + return new UInt128((ulong)Random.Shared.NextInt64(), (ulong)Random.Shared.NextInt64()); + } + } +} diff --git a/src/Ryujinx.Common/XXHash128.cs b/src/Ryujinx.Common/XXHash128.cs new file mode 100644 index 00000000..686867c9 --- /dev/null +++ b/src/Ryujinx.Common/XXHash128.cs @@ -0,0 +1,548 @@ +using System; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Common +{ + public static class XXHash128 + { + private const int StripeLen = 64; + private const int AccNb = StripeLen / sizeof(ulong); + private const int SecretConsumeRate = 8; + private const int SecretLastAccStart = 7; + private const int SecretMergeAccsStart = 11; + private const int SecretSizeMin = 136; + private const int MidSizeStartOffset = 3; + private const int MidSizeLastOffset = 17; + + private const uint Prime32_1 = 0x9E3779B1U; + private const uint Prime32_2 = 0x85EBCA77U; + private const uint Prime32_3 = 0xC2B2AE3DU; + private const uint Prime32_4 = 0x27D4EB2FU; + private const uint Prime32_5 = 0x165667B1U; + + private const ulong Prime64_1 = 0x9E3779B185EBCA87UL; + private const ulong Prime64_2 = 0xC2B2AE3D27D4EB4FUL; + private const ulong Prime64_3 = 0x165667B19E3779F9UL; + private const ulong Prime64_4 = 0x85EBCA77C2B2AE63UL; + private const ulong Prime64_5 = 0x27D4EB2F165667C5UL; + + private static readonly ulong[] _xxh3InitAcc = { + Prime32_3, + Prime64_1, + Prime64_2, + Prime64_3, + Prime64_4, + Prime32_2, + Prime64_5, + Prime32_1, + }; + + private static ReadOnlySpan Xxh3KSecret => new byte[] + { + 0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c, + 0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f, + 0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21, + 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c, + 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3, + 0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8, + 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d, + 0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64, + 0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb, + 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e, + 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce, + 0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e, + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong Mult32To64(ulong x, ulong y) + { + return (uint)x * (ulong)(uint)y; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Hash128 Mult64To128(ulong lhs, ulong rhs) + { + ulong high = Math.BigMul(lhs, rhs, out ulong low); + + return new Hash128 + { + Low = low, + High = high, + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong Mul128Fold64(ulong lhs, ulong rhs) + { + Hash128 product = Mult64To128(lhs, rhs); + + return product.Low ^ product.High; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong XorShift64(ulong v64, int shift) + { + Debug.Assert(0 <= shift && shift < 64); + + return v64 ^ (v64 >> shift); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong Xxh3Avalanche(ulong h64) + { + h64 = XorShift64(h64, 37); + h64 *= 0x165667919E3779F9UL; + h64 = XorShift64(h64, 32); + + return h64; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong Xxh64Avalanche(ulong h64) + { + h64 ^= h64 >> 33; + h64 *= Prime64_2; + h64 ^= h64 >> 29; + h64 *= Prime64_3; + h64 ^= h64 >> 32; + + return h64; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe static void Xxh3Accumulate512(Span acc, ReadOnlySpan input, ReadOnlySpan secret) + { + if (Avx2.IsSupported) + { + fixed (ulong* pAcc = acc) + { + fixed (byte* pInput = input, pSecret = secret) + { + Vector256* xAcc = (Vector256*)pAcc; + Vector256* xInput = (Vector256*)pInput; + Vector256* xSecret = (Vector256*)pSecret; + + for (ulong i = 0; i < StripeLen / 32; i++) + { + Vector256 dataVec = xInput[i]; + Vector256 keyVec = xSecret[i]; + Vector256 dataKey = Avx2.Xor(dataVec, keyVec); + Vector256 dataKeyLo = Avx2.Shuffle(dataKey.AsUInt32(), 0b00110001); + Vector256 product = Avx2.Multiply(dataKey.AsUInt32(), dataKeyLo); + Vector256 dataSwap = Avx2.Shuffle(dataVec.AsUInt32(), 0b01001110); + Vector256 sum = Avx2.Add(xAcc[i], dataSwap.AsUInt64()); + xAcc[i] = Avx2.Add(product, sum); + } + } + } + } + else if (Sse2.IsSupported) + { + fixed (ulong* pAcc = acc) + { + fixed (byte* pInput = input, pSecret = secret) + { + Vector128* xAcc = (Vector128*)pAcc; + Vector128* xInput = (Vector128*)pInput; + Vector128* xSecret = (Vector128*)pSecret; + + for (ulong i = 0; i < StripeLen / 16; i++) + { + Vector128 dataVec = xInput[i]; + Vector128 keyVec = xSecret[i]; + Vector128 dataKey = Sse2.Xor(dataVec, keyVec); + Vector128 dataKeyLo = Sse2.Shuffle(dataKey.AsUInt32(), 0b00110001); + Vector128 product = Sse2.Multiply(dataKey.AsUInt32(), dataKeyLo); + Vector128 dataSwap = Sse2.Shuffle(dataVec.AsUInt32(), 0b01001110); + Vector128 sum = Sse2.Add(xAcc[i], dataSwap.AsUInt64()); + xAcc[i] = Sse2.Add(product, sum); + } + } + } + } + else + { + for (int i = 0; i < AccNb; i++) + { + ulong dataVal = BinaryPrimitives.ReadUInt64LittleEndian(input[(i * sizeof(ulong))..]); + ulong dataKey = dataVal ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[(i * sizeof(ulong))..]); + acc[i ^ 1] += dataVal; + acc[i] += Mult32To64((uint)dataKey, dataKey >> 32); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe static void Xxh3ScrambleAcc(Span acc, ReadOnlySpan secret) + { + if (Avx2.IsSupported) + { + fixed (ulong* pAcc = acc) + { + fixed (byte* pSecret = secret) + { + Vector256 prime32 = Vector256.Create(Prime32_1); + Vector256* xAcc = (Vector256*)pAcc; + Vector256* xSecret = (Vector256*)pSecret; + + for (ulong i = 0; i < StripeLen / 32; i++) + { + Vector256 accVec = xAcc[i]; + Vector256 shifted = Avx2.ShiftRightLogical(accVec, 47); + Vector256 dataVec = Avx2.Xor(accVec, shifted); + + Vector256 keyVec = xSecret[i]; + Vector256 dataKey = Avx2.Xor(dataVec.AsUInt32(), keyVec.AsUInt32()); + + Vector256 dataKeyHi = Avx2.Shuffle(dataKey.AsUInt32(), 0b00110001); + Vector256 prodLo = Avx2.Multiply(dataKey, prime32); + Vector256 prodHi = Avx2.Multiply(dataKeyHi, prime32); + + xAcc[i] = Avx2.Add(prodLo, Avx2.ShiftLeftLogical(prodHi, 32)); + } + } + } + } + else if (Sse2.IsSupported) + { + fixed (ulong* pAcc = acc) + { + fixed (byte* pSecret = secret) + { + Vector128 prime32 = Vector128.Create(Prime32_1); + Vector128* xAcc = (Vector128*)pAcc; + Vector128* xSecret = (Vector128*)pSecret; + + for (ulong i = 0; i < StripeLen / 16; i++) + { + Vector128 accVec = xAcc[i]; + Vector128 shifted = Sse2.ShiftRightLogical(accVec, 47); + Vector128 dataVec = Sse2.Xor(accVec, shifted); + + Vector128 keyVec = xSecret[i]; + Vector128 dataKey = Sse2.Xor(dataVec.AsUInt32(), keyVec.AsUInt32()); + + Vector128 dataKeyHi = Sse2.Shuffle(dataKey.AsUInt32(), 0b00110001); + Vector128 prodLo = Sse2.Multiply(dataKey, prime32); + Vector128 prodHi = Sse2.Multiply(dataKeyHi, prime32); + + xAcc[i] = Sse2.Add(prodLo, Sse2.ShiftLeftLogical(prodHi, 32)); + } + } + } + } + else + { + for (int i = 0; i < AccNb; i++) + { + ulong key64 = BinaryPrimitives.ReadUInt64LittleEndian(secret[(i * sizeof(ulong))..]); + ulong acc64 = acc[i]; + acc64 = XorShift64(acc64, 47); + acc64 ^= key64; + acc64 *= Prime32_1; + acc[i] = acc64; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Xxh3Accumulate(Span acc, ReadOnlySpan input, ReadOnlySpan secret, int nbStripes) + { + for (int n = 0; n < nbStripes; n++) + { + ReadOnlySpan inData = input[(n * StripeLen)..]; + Xxh3Accumulate512(acc, inData, secret[(n * SecretConsumeRate)..]); + } + } + + private static void Xxh3HashLongInternalLoop(Span acc, ReadOnlySpan input, ReadOnlySpan secret) + { + int nbStripesPerBlock = (secret.Length - StripeLen) / SecretConsumeRate; + int blockLen = StripeLen * nbStripesPerBlock; + int nbBlocks = (input.Length - 1) / blockLen; + + Debug.Assert(secret.Length >= SecretSizeMin); + + for (int n = 0; n < nbBlocks; n++) + { + Xxh3Accumulate(acc, input[(n * blockLen)..], secret, nbStripesPerBlock); + Xxh3ScrambleAcc(acc, secret[^StripeLen..]); + } + + Debug.Assert(input.Length > StripeLen); + + int nbStripes = (input.Length - 1 - (blockLen * nbBlocks)) / StripeLen; + Debug.Assert(nbStripes <= (secret.Length / SecretConsumeRate)); + Xxh3Accumulate(acc, input[(nbBlocks * blockLen)..], secret, nbStripes); + + ReadOnlySpan p = input[^StripeLen..]; + Xxh3Accumulate512(acc, p, secret[(secret.Length - StripeLen - SecretLastAccStart)..]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong Xxh3Mix2Accs(Span acc, ReadOnlySpan secret) + { + return Mul128Fold64( + acc[0] ^ BinaryPrimitives.ReadUInt64LittleEndian(secret), + acc[1] ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[8..])); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong Xxh3MergeAccs(Span acc, ReadOnlySpan secret, ulong start) + { + ulong result64 = start; + + for (int i = 0; i < 4; i++) + { + result64 += Xxh3Mix2Accs(acc[(2 * i)..], secret[(16 * i)..]); + } + + return Xxh3Avalanche(result64); + } + + [SkipLocalsInit] + private static Hash128 Xxh3HashLong128bInternal(ReadOnlySpan input, ReadOnlySpan secret) + { + Span acc = stackalloc ulong[AccNb]; + _xxh3InitAcc.CopyTo(acc); + + Xxh3HashLongInternalLoop(acc, input, secret); + + Debug.Assert(acc.Length == 8); + Debug.Assert(secret.Length >= acc.Length * sizeof(ulong) + SecretMergeAccsStart); + + return new Hash128 + { + Low = Xxh3MergeAccs(acc, secret[SecretMergeAccsStart..], (ulong)input.Length * Prime64_1), + High = Xxh3MergeAccs( + acc, + secret[(secret.Length - acc.Length * sizeof(ulong) - SecretMergeAccsStart)..], + ~((ulong)input.Length * Prime64_2)), + }; + } + + private static Hash128 Xxh3Len1To3128b(ReadOnlySpan input, ReadOnlySpan secret, ulong seed) + { + Debug.Assert(1 <= input.Length && input.Length <= 3); + + byte c1 = input[0]; + byte c2 = input[input.Length >> 1]; + byte c3 = input[^1]; + + uint combinedL = ((uint)c1 << 16) | ((uint)c2 << 24) | c3 | ((uint)input.Length << 8); + uint combinedH = BitOperations.RotateLeft(BinaryPrimitives.ReverseEndianness(combinedL), 13); + ulong bitFlipL = (BinaryPrimitives.ReadUInt32LittleEndian(secret) ^ BinaryPrimitives.ReadUInt32LittleEndian(secret[4..])) + seed; + ulong bitFlipH = (BinaryPrimitives.ReadUInt32LittleEndian(secret[8..]) ^ BinaryPrimitives.ReadUInt32LittleEndian(secret[12..])) - seed; + ulong keyedLo = combinedL ^ bitFlipL; + ulong keyedHi = combinedH ^ bitFlipH; + + return new Hash128 + { + Low = Xxh64Avalanche(keyedLo), + High = Xxh64Avalanche(keyedHi), + }; + } + + private static Hash128 Xxh3Len4To8128b(ReadOnlySpan input, ReadOnlySpan secret, ulong seed) + { + Debug.Assert(4 <= input.Length && input.Length <= 8); + + seed ^= BinaryPrimitives.ReverseEndianness((uint)seed) << 32; + + uint inputLo = BinaryPrimitives.ReadUInt32LittleEndian(input); + uint inputHi = BinaryPrimitives.ReadUInt32LittleEndian(input[^4..]); + ulong input64 = inputLo + ((ulong)inputHi << 32); + ulong bitFlip = (BinaryPrimitives.ReadUInt64LittleEndian(secret[16..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[24..])) + seed; + ulong keyed = input64 ^ bitFlip; + + Hash128 m128 = Mult64To128(keyed, Prime64_1 + ((ulong)input.Length << 2)); + + m128.High += m128.Low << 1; + m128.Low ^= m128.High >> 3; + + m128.Low = XorShift64(m128.Low, 35); + m128.Low *= 0x9FB21C651E98DF25UL; + m128.Low = XorShift64(m128.Low, 28); + m128.High = Xxh3Avalanche(m128.High); + + return m128; + } + + private static Hash128 Xxh3Len9To16128b(ReadOnlySpan input, ReadOnlySpan secret, ulong seed) + { + Debug.Assert(9 <= input.Length && input.Length <= 16); + + ulong bitFlipL = (BinaryPrimitives.ReadUInt64LittleEndian(secret[32..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[40..])) - seed; + ulong bitFlipH = (BinaryPrimitives.ReadUInt64LittleEndian(secret[48..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[56..])) + seed; + ulong inputLo = BinaryPrimitives.ReadUInt64LittleEndian(input); + ulong inputHi = BinaryPrimitives.ReadUInt64LittleEndian(input[^8..]); + + Hash128 m128 = Mult64To128(inputLo ^ inputHi ^ bitFlipL, Prime64_1); + m128.Low += ((ulong)input.Length - 1) << 54; + inputHi ^= bitFlipH; + m128.High += inputHi + Mult32To64((uint)inputHi, Prime32_2 - 1); + m128.Low ^= BinaryPrimitives.ReverseEndianness(m128.High); + + Hash128 h128 = Mult64To128(m128.Low, Prime64_2); + h128.High += m128.High * Prime64_2; + h128.Low = Xxh3Avalanche(h128.Low); + h128.High = Xxh3Avalanche(h128.High); + + return h128; + } + + private static Hash128 Xxh3Len0To16128b(ReadOnlySpan input, ReadOnlySpan secret, ulong seed) + { + Debug.Assert(input.Length <= 16); + + if (input.Length > 8) + { + return Xxh3Len9To16128b(input, secret, seed); + } + + if (input.Length >= 4) + { + return Xxh3Len4To8128b(input, secret, seed); + } + + if (input.Length != 0) + { + return Xxh3Len1To3128b(input, secret, seed); + } + + Hash128 h128 = new(); + ulong bitFlipL = BinaryPrimitives.ReadUInt64LittleEndian(secret[64..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[72..]); + ulong bitFlipH = BinaryPrimitives.ReadUInt64LittleEndian(secret[80..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[88..]); + h128.Low = Xxh64Avalanche(seed ^ bitFlipL); + h128.High = Xxh64Avalanche(seed ^ bitFlipH); + + return h128; + } + + private static ulong Xxh3Mix16b(ReadOnlySpan input, ReadOnlySpan secret, ulong seed) + { + ulong inputLo = BinaryPrimitives.ReadUInt64LittleEndian(input); + ulong inputHi = BinaryPrimitives.ReadUInt64LittleEndian(input[8..]); + + return Mul128Fold64( + inputLo ^ (BinaryPrimitives.ReadUInt64LittleEndian(secret) + seed), + inputHi ^ (BinaryPrimitives.ReadUInt64LittleEndian(secret[8..]) - seed)); + } + + private static Hash128 Xxh128Mix32b(Hash128 acc, ReadOnlySpan input, ReadOnlySpan input2, ReadOnlySpan secret, ulong seed) + { + acc.Low += Xxh3Mix16b(input, secret, seed); + acc.Low ^= BinaryPrimitives.ReadUInt64LittleEndian(input2) + BinaryPrimitives.ReadUInt64LittleEndian(input2[8..]); + acc.High += Xxh3Mix16b(input2, secret[16..], seed); + acc.High ^= BinaryPrimitives.ReadUInt64LittleEndian(input) + BinaryPrimitives.ReadUInt64LittleEndian(input[8..]); + + return acc; + } + + private static Hash128 Xxh3Len17To128128b(ReadOnlySpan input, ReadOnlySpan secret, ulong seed) + { + Debug.Assert(secret.Length >= SecretSizeMin); + Debug.Assert(16 < input.Length && input.Length <= 128); + + Hash128 acc = new() + { + Low = (ulong)input.Length * Prime64_1, + High = 0, + }; + + if (input.Length > 32) + { + if (input.Length > 64) + { + if (input.Length > 96) + { + acc = Xxh128Mix32b(acc, input[48..], input[^64..], secret[96..], seed); + } + acc = Xxh128Mix32b(acc, input[32..], input[^48..], secret[64..], seed); + } + acc = Xxh128Mix32b(acc, input[16..], input[^32..], secret[32..], seed); + } + acc = Xxh128Mix32b(acc, input, input[^16..], secret, seed); + + Hash128 h128 = new() + { + Low = acc.Low + acc.High, + High = acc.Low * Prime64_1 + acc.High * Prime64_4 + ((ulong)input.Length - seed) * Prime64_2, + }; + h128.Low = Xxh3Avalanche(h128.Low); + h128.High = 0UL - Xxh3Avalanche(h128.High); + + return h128; + } + + private static Hash128 Xxh3Len129To240128b(ReadOnlySpan input, ReadOnlySpan secret, ulong seed) + { + Debug.Assert(secret.Length >= SecretSizeMin); + Debug.Assert(128 < input.Length && input.Length <= 240); + + Hash128 acc = new(); + + int nbRounds = input.Length / 32; + acc.Low = (ulong)input.Length * Prime64_1; + acc.High = 0; + + for (int i = 0; i < 4; i++) + { + acc = Xxh128Mix32b(acc, input[(32 * i)..], input[(32 * i + 16)..], secret[(32 * i)..], seed); + } + + acc.Low = Xxh3Avalanche(acc.Low); + acc.High = Xxh3Avalanche(acc.High); + Debug.Assert(nbRounds >= 4); + + for (int i = 4; i < nbRounds; i++) + { + acc = Xxh128Mix32b(acc, input[(32 * i)..], input[(32 * i + 16)..], secret[(MidSizeStartOffset + 32 * (i - 4))..], seed); + } + + acc = Xxh128Mix32b(acc, input[^16..], input[^32..], secret[(SecretSizeMin - MidSizeLastOffset - 16)..], 0UL - seed); + + Hash128 h128 = new() + { + Low = acc.Low + acc.High, + High = acc.Low * Prime64_1 + acc.High * Prime64_4 + ((ulong)input.Length - seed) * Prime64_2, + }; + h128.Low = Xxh3Avalanche(h128.Low); + h128.High = 0UL - Xxh3Avalanche(h128.High); + + return h128; + } + + private static Hash128 Xxh3128bitsInternal(ReadOnlySpan input, ReadOnlySpan secret, ulong seed) + { + Debug.Assert(secret.Length >= SecretSizeMin); + + if (input.Length <= 16) + { + return Xxh3Len0To16128b(input, secret, seed); + } + + if (input.Length <= 128) + { + return Xxh3Len17To128128b(input, secret, seed); + } + + if (input.Length <= 240) + { + return Xxh3Len129To240128b(input, secret, seed); + } + + return Xxh3HashLong128bInternal(input, secret); + } + + public static Hash128 ComputeHash(ReadOnlySpan input) + { + return Xxh3128bitsInternal(input, Xxh3KSecret, 0UL); + } + } +} diff --git a/src/Ryujinx.Cpu/AddressSpace.cs b/src/Ryujinx.Cpu/AddressSpace.cs new file mode 100644 index 00000000..6664ed13 --- /dev/null +++ b/src/Ryujinx.Cpu/AddressSpace.cs @@ -0,0 +1,76 @@ +using Ryujinx.Memory; +using System; + +namespace Ryujinx.Cpu +{ + public class AddressSpace : IDisposable + { + private readonly MemoryBlock _backingMemory; + + public MemoryBlock Base { get; } + public MemoryBlock Mirror { get; } + + public ulong AddressSpaceSize { get; } + + public AddressSpace(MemoryBlock backingMemory, MemoryBlock baseMemory, MemoryBlock mirrorMemory, ulong addressSpaceSize) + { + _backingMemory = backingMemory; + + Base = baseMemory; + Mirror = mirrorMemory; + AddressSpaceSize = addressSpaceSize; + } + + public static bool TryCreate(MemoryBlock backingMemory, ulong asSize, out AddressSpace addressSpace) + { + addressSpace = null; + + const MemoryAllocationFlags AsFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible; + + ulong minAddressSpaceSize = Math.Min(asSize, 1UL << 36); + + // Attempt to create the address space with expected size or try to reduce it until it succeed. + for (ulong addressSpaceSize = asSize; addressSpaceSize >= minAddressSpaceSize; addressSpaceSize >>= 1) + { + MemoryBlock baseMemory = null; + MemoryBlock mirrorMemory = null; + + try + { + baseMemory = new MemoryBlock(addressSpaceSize, AsFlags); + mirrorMemory = new MemoryBlock(addressSpaceSize, AsFlags); + addressSpace = new AddressSpace(backingMemory, baseMemory, mirrorMemory, addressSpaceSize); + + break; + } + catch (SystemException) + { + baseMemory?.Dispose(); + mirrorMemory?.Dispose(); + } + } + + return addressSpace != null; + } + + public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) + { + Base.MapView(_backingMemory, pa, va, size); + Mirror.MapView(_backingMemory, pa, va, size); + } + + public void Unmap(ulong va, ulong size) + { + Base.UnmapView(_backingMemory, va, size); + Mirror.UnmapView(_backingMemory, va, size); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + Base.Dispose(); + Mirror.Dispose(); + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/Arm/ApFlags.cs b/src/Ryujinx.Cpu/AppleHv/Arm/ApFlags.cs new file mode 100644 index 00000000..8935f524 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/Arm/ApFlags.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.Cpu.AppleHv.Arm +{ + enum ApFlags : ulong + { + ApShift = 6, + PxnShift = 53, + UxnShift = 54, + + UserExecuteKernelReadWriteExecute = (0UL << (int)ApShift), + UserReadWriteExecuteKernelReadWrite = (1UL << (int)ApShift), + UserExecuteKernelReadExecute = (2UL << (int)ApShift), + UserReadExecuteKernelReadExecute = (3UL << (int)ApShift), + + UserExecuteKernelReadWrite = (1UL << (int)PxnShift) | (0UL << (int)ApShift), + UserExecuteKernelRead = (1UL << (int)PxnShift) | (2UL << (int)ApShift), + UserReadExecuteKernelRead = (1UL << (int)PxnShift) | (3UL << (int)ApShift), + + UserNoneKernelReadWriteExecute = (1UL << (int)UxnShift) | (0UL << (int)ApShift), + UserReadWriteKernelReadWrite = (1UL << (int)UxnShift) | (1UL << (int)ApShift), + UserNoneKernelReadExecute = (1UL << (int)UxnShift) | (2UL << (int)ApShift), + UserReadKernelReadExecute = (1UL << (int)UxnShift) | (3UL << (int)ApShift), + + UserNoneKernelReadWrite = (1UL << (int)PxnShift) | (1UL << (int)UxnShift) | (0UL << (int)ApShift), + UserNoneKernelRead = (1UL << (int)PxnShift) | (1UL << (int)UxnShift) | (2UL << (int)ApShift), + UserReadKernelRead = (1UL << (int)PxnShift) | (1UL << (int)UxnShift) | (3UL << (int)ApShift), + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/Arm/ExceptionClass.cs b/src/Ryujinx.Cpu/AppleHv/Arm/ExceptionClass.cs new file mode 100644 index 00000000..f7e27758 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/Arm/ExceptionClass.cs @@ -0,0 +1,47 @@ +namespace Ryujinx.Cpu.AppleHv.Arm +{ + enum ExceptionClass + { + Unknown = 0b000000, + TrappedWfeWfiWfetWfit = 0b000001, + TrappedMcrMrcCp15 = 0b000011, + TrappedMcrrMrrcCp15 = 0b000100, + TrappedMcrMrcCp14 = 0b000101, + TrappedLdcStc = 0b000110, + TrappedSveFpSimd = 0b000111, + TrappedVmrs = 0b001000, + TrappedPAuth = 0b001001, + TrappedLd64bSt64bSt64bvSt64bv0 = 0b001010, + TrappedMrrcCp14 = 0b001100, + IllegalExecutionState = 0b001110, + SvcAarch32 = 0b010001, + HvcAarch32 = 0b010010, + SmcAarch32 = 0b010011, + SvcAarch64 = 0b010101, + HvcAarch64 = 0b010110, + SmcAarch64 = 0b010111, + TrappedMsrMrsSystem = 0b011000, + TrappedSve = 0b011001, + TrappedEretEretaaEretab = 0b011010, + PointerAuthenticationFailure = 0b011100, + ImplementationDefinedEl3 = 0b011111, + InstructionAbortLowerEl = 0b100000, + InstructionAbortSameEl = 0b100001, + PcAlignmentFault = 0b100010, + DataAbortLowerEl = 0b100100, + DataAbortSameEl = 0b100101, + SpAlignmentFault = 0b100110, + TrappedFpExceptionAarch32 = 0b101000, + TrappedFpExceptionAarch64 = 0b101100, + SErrorInterrupt = 0b101111, + BreakpointLowerEl = 0b110000, + BreakpointSameEl = 0b110001, + SoftwareStepLowerEl = 0b110010, + SoftwareStepSameEl = 0b110011, + WatchpointLowerEl = 0b110100, + WatchpointSameEl = 0b110101, + BkptAarch32 = 0b111000, + VectorCatchAarch32 = 0b111010, + BrkAarch64 = 0b111100, + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/DummyDiskCacheLoadState.cs b/src/Ryujinx.Cpu/AppleHv/DummyDiskCacheLoadState.cs new file mode 100644 index 00000000..8e775f09 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/DummyDiskCacheLoadState.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.Cpu.AppleHv +{ + public class DummyDiskCacheLoadState : IDiskCacheLoadState + { +#pragma warning disable CS0067 // The event is never used + /// + public event Action StateChanged; +#pragma warning restore CS0067 + + /// + public void Cancel() + { + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs b/src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs new file mode 100644 index 00000000..eb7c0ef0 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs @@ -0,0 +1,129 @@ +using Ryujinx.Cpu.AppleHv.Arm; +using Ryujinx.Memory; +using System; +using System.Runtime.Versioning; + +namespace Ryujinx.Cpu.AppleHv +{ + [SupportedOSPlatform("macos")] + class HvAddressSpace : IDisposable + { + private const ulong KernelRegionBase = unchecked((ulong)-(1L << 39)); + private const ulong KernelRegionCodeOffset = 0UL; + private const ulong KernelRegionCodeSize = 0x2000UL; + private const ulong KernelRegionTlbiEretOffset = KernelRegionCodeOffset + 0x1000UL; + private const ulong KernelRegionEretOffset = KernelRegionTlbiEretOffset + 4UL; + + public const ulong KernelRegionEretAddress = KernelRegionBase + KernelRegionEretOffset; + public const ulong KernelRegionTlbiEretAddress = KernelRegionBase + KernelRegionTlbiEretOffset; + + private const ulong AllocationGranule = 1UL << 14; + + private readonly ulong _asBase; + private readonly ulong _backingSize; + + private readonly HvAddressSpaceRange _userRange; + private readonly HvAddressSpaceRange _kernelRange; + + private readonly MemoryBlock _kernelCodeBlock; + + public HvAddressSpace(MemoryBlock backingMemory, ulong asSize) + { + (_asBase, var ipaAllocator) = HvVm.CreateAddressSpace(backingMemory); + _backingSize = backingMemory.Size; + + _userRange = new HvAddressSpaceRange(ipaAllocator); + _kernelRange = new HvAddressSpaceRange(ipaAllocator); + + _kernelCodeBlock = new MemoryBlock(AllocationGranule); + + InitializeKernelCode(ipaAllocator); + } + + private void InitializeKernelCode(HvIpaAllocator ipaAllocator) + { + // Write exception handlers. + for (ulong offset = 0; offset < 0x800; offset += 0x80) + { + // Offsets: + // 0x0: Synchronous + // 0x80: IRQ + // 0x100: FIQ + // 0x180: SError + _kernelCodeBlock.Write(KernelRegionCodeOffset + offset, 0xD41FFFE2u); // HVC #0xFFFF + _kernelCodeBlock.Write(KernelRegionCodeOffset + offset + 4, 0xD69F03E0u); // ERET + } + + _kernelCodeBlock.Write(KernelRegionTlbiEretOffset, 0xD508831Fu); // TLBI VMALLE1IS + _kernelCodeBlock.Write(KernelRegionEretOffset, 0xD69F03E0u); // ERET + + ulong kernelCodePa = ipaAllocator.Allocate(AllocationGranule); + HvApi.hv_vm_map((ulong)_kernelCodeBlock.Pointer, kernelCodePa, AllocationGranule, HvMemoryFlags.Read | HvMemoryFlags.Exec).ThrowOnError(); + + _kernelRange.Map(KernelRegionCodeOffset, kernelCodePa, KernelRegionCodeSize, ApFlags.UserNoneKernelReadExecute); + } + + public void InitializeMmu(ulong vcpu) + { + HvApi.hv_vcpu_set_sys_reg(vcpu, HvSysReg.VBAR_EL1, KernelRegionBase + KernelRegionCodeOffset); + + HvApi.hv_vcpu_set_sys_reg(vcpu, HvSysReg.TTBR0_EL1, _userRange.GetIpaBase()); + HvApi.hv_vcpu_set_sys_reg(vcpu, HvSysReg.TTBR1_EL1, _kernelRange.GetIpaBase()); + HvApi.hv_vcpu_set_sys_reg(vcpu, HvSysReg.MAIR_EL1, 0xffUL); + HvApi.hv_vcpu_set_sys_reg(vcpu, HvSysReg.TCR_EL1, 0x00000011B5193519UL); + HvApi.hv_vcpu_set_sys_reg(vcpu, HvSysReg.SCTLR_EL1, 0x0000000034D5D925UL); + } + + public bool GetAndClearUserTlbInvalidationPending() + { + return _userRange.GetAndClearTlbInvalidationPending(); + } + + public void MapUser(ulong va, ulong pa, ulong size, MemoryPermission permission) + { + pa += _asBase; + + lock (_userRange) + { + _userRange.Map(va, pa, size, GetApFlags(permission)); + } + } + + public void UnmapUser(ulong va, ulong size) + { + lock (_userRange) + { + _userRange.Unmap(va, size); + } + } + + public void ReprotectUser(ulong va, ulong size, MemoryPermission permission) + { + lock (_userRange) + { + _userRange.Reprotect(va, size, GetApFlags(permission)); + } + } + + private static ApFlags GetApFlags(MemoryPermission permission) + { + return permission switch + { + MemoryPermission.None => ApFlags.UserNoneKernelRead, + MemoryPermission.Execute => ApFlags.UserExecuteKernelRead, + MemoryPermission.Read => ApFlags.UserReadKernelRead, + MemoryPermission.ReadAndWrite => ApFlags.UserReadWriteKernelReadWrite, + MemoryPermission.ReadAndExecute => ApFlags.UserReadExecuteKernelRead, + MemoryPermission.ReadWriteExecute => ApFlags.UserReadWriteExecuteKernelReadWrite, + _ => throw new ArgumentException($"Permission \"{permission}\" is invalid."), + }; + } + + public void Dispose() + { + _userRange.Dispose(); + _kernelRange.Dispose(); + HvVm.DestroyAddressSpace(_asBase, _backingSize); + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvAddressSpaceRange.cs b/src/Ryujinx.Cpu/AppleHv/HvAddressSpaceRange.cs new file mode 100644 index 00000000..7754431f --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvAddressSpaceRange.cs @@ -0,0 +1,370 @@ +using Ryujinx.Cpu.AppleHv.Arm; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Threading; + +namespace Ryujinx.Cpu.AppleHv +{ + [SupportedOSPlatform("macos")] + class HvAddressSpaceRange : IDisposable + { + private const ulong AllocationGranule = 1UL << 14; + + private const ulong AttributesMask = (0x3ffUL << 2) | (0x3fffUL << 50); + + private const ulong BaseAttributes = (1UL << 10) | (3UL << 8); // Access flag set, inner shareable. + + private const int LevelBits = 9; + private const int LevelCount = 1 << LevelBits; + private const int LevelMask = LevelCount - 1; + private const int PageBits = 12; + private const int PageSize = 1 << PageBits; + private const int PageMask = PageSize - 1; + private const int AllLevelsMask = PageMask | (LevelMask << PageBits) | (LevelMask << (PageBits + LevelBits)); + + private class PtLevel + { + public ulong Address => Allocation.Ipa + Allocation.Offset; + public int EntriesCount; + public readonly HvMemoryBlockAllocation Allocation; + public readonly PtLevel[] Next; + + public PtLevel(HvMemoryBlockAllocator blockAllocator, int count, bool hasNext) + { + ulong size = (ulong)count * sizeof(ulong); + Allocation = blockAllocator.Allocate(size, PageSize); + + AsSpan().Clear(); + + if (hasNext) + { + Next = new PtLevel[count]; + } + } + + public Span AsSpan() + { + return MemoryMarshal.Cast(Allocation.Memory.GetSpan(Allocation.Offset, (int)Allocation.Size)); + } + } + + private PtLevel _level0; + + private int _tlbInvalidationPending; + + private readonly HvMemoryBlockAllocator _blockAllocator; + + public HvAddressSpaceRange(HvIpaAllocator ipaAllocator) + { + _blockAllocator = new HvMemoryBlockAllocator(ipaAllocator, (int)AllocationGranule); + } + + public ulong GetIpaBase() + { + return EnsureLevel0().Address; + } + + public bool GetAndClearTlbInvalidationPending() + { + return Interlocked.Exchange(ref _tlbInvalidationPending, 0) != 0; + } + + public void Map(ulong va, ulong pa, ulong size, ApFlags accessPermission) + { + MapImpl(va, pa, size, (ulong)accessPermission | BaseAttributes); + } + + public void Unmap(ulong va, ulong size) + { + UnmapImpl(EnsureLevel0(), 0, va, size); + Interlocked.Exchange(ref _tlbInvalidationPending, 1); + } + + public void Reprotect(ulong va, ulong size, ApFlags accessPermission) + { + UpdateAttributes(va, size, (ulong)accessPermission | BaseAttributes); + } + + private void MapImpl(ulong va, ulong pa, ulong size, ulong attr) + { + PtLevel level0 = EnsureLevel0(); + + ulong endVa = va + size; + + while (va < endVa) + { + (ulong mapSize, int depth) = GetMapSizeAndDepth(va, pa, endVa); + + PtLevel currentLevel = level0; + + for (int i = 0; i < depth; i++) + { + int l = (int)(va >> (PageBits + (2 - i) * LevelBits)) & LevelMask; + EnsureTable(currentLevel, l, i == 0); + currentLevel = currentLevel.Next[l]; + } + + (ulong blockSize, int blockShift) = GetBlockSizeAndShift(depth); + + for (ulong i = 0; i < mapSize; i += blockSize) + { + if ((va >> blockShift) << blockShift != va || + (pa >> blockShift) << blockShift != pa) + { + Debug.Fail($"Block size 0x{blockSize:X} (log2: {blockShift}) is invalid for VA 0x{va:X} or PA 0x{pa:X}."); + } + + WriteBlock(currentLevel, (int)(va >> blockShift) & LevelMask, depth, pa, attr); + + va += blockSize; + pa += blockSize; + } + } + } + + private void UnmapImpl(PtLevel level, int depth, ulong va, ulong size) + { + ulong endVa = (va + size + PageMask) & ~((ulong)PageMask); + va &= ~((ulong)PageMask); + + (ulong blockSize, _) = GetBlockSizeAndShift(depth); + + while (va < endVa) + { + ulong nextEntryVa = GetNextAddress(va, blockSize); + ulong chunckSize = Math.Min(endVa - va, nextEntryVa - va); + + int l = (int)(va >> (PageBits + (2 - depth) * LevelBits)) & LevelMask; + + PtLevel nextTable = level.Next?[l]; + + if (nextTable != null) + { + // Entry is a table, visit it and update attributes as required. + UnmapImpl(nextTable, depth + 1, va, chunckSize); + } + else if (chunckSize != blockSize) + { + // Entry is a block but is not aligned, we need to turn it into a table. + ref ulong pte = ref level.AsSpan()[l]; + nextTable = CreateTable(pte, depth + 1); + level.Next[l] = nextTable; + + // Now that we have a table, we can handle it like the first case. + UnmapImpl(nextTable, depth + 1, va, chunckSize); + + // Update PTE to point to the new table. + pte = (nextTable.Address & ~(ulong)PageMask) | 3UL; + } + + // If entry is a block, or if entry is a table but it is empty, we can remove it. + if (nextTable == null || nextTable.EntriesCount == 0) + { + // Entry is a block and is fully aligned, so we can just set it to 0. + if (nextTable != null) + { + nextTable.Allocation.Dispose(); + level.Next[l] = null; + } + + level.AsSpan()[l] = 0UL; + level.EntriesCount--; + ValidateEntriesCount(level.EntriesCount); + } + + va += chunckSize; + } + } + + private void UpdateAttributes(ulong va, ulong size, ulong newAttr) + { + UpdateAttributes(EnsureLevel0(), 0, va, size, newAttr); + + Interlocked.Exchange(ref _tlbInvalidationPending, 1); + } + + private void UpdateAttributes(PtLevel level, int depth, ulong va, ulong size, ulong newAttr) + { + ulong endVa = (va + size + PageSize - 1) & ~((ulong)PageSize - 1); + va &= ~((ulong)PageSize - 1); + + (ulong blockSize, _) = GetBlockSizeAndShift(depth); + + while (va < endVa) + { + ulong nextEntryVa = GetNextAddress(va, blockSize); + ulong chunckSize = Math.Min(endVa - va, nextEntryVa - va); + + int l = (int)(va >> (PageBits + (2 - depth) * LevelBits)) & LevelMask; + + ref ulong pte = ref level.AsSpan()[l]; + + // First check if the region is mapped. + if ((pte & 3) != 0) + { + PtLevel nextTable = level.Next?[l]; + + if (nextTable != null) + { + // Entry is a table, visit it and update attributes as required. + UpdateAttributes(nextTable, depth + 1, va, chunckSize, newAttr); + } + else if (chunckSize != blockSize) + { + // Entry is a block but is not aligned, we need to turn it into a table. + nextTable = CreateTable(pte, depth + 1); + level.Next[l] = nextTable; + + // Now that we have a table, we can handle it like the first case. + UpdateAttributes(nextTable, depth + 1, va, chunckSize, newAttr); + + // Update PTE to point to the new table. + pte = (nextTable.Address & ~(ulong)PageMask) | 3UL; + } + else + { + // Entry is a block and is fully aligned, so we can just update the attributes. + // Update PTE with the new attributes. + pte = (pte & ~AttributesMask) | newAttr; + } + } + + va += chunckSize; + } + } + + private PtLevel CreateTable(ulong pte, int depth) + { + pte &= ~3UL; + pte |= (depth == 2 ? 3UL : 1UL); + + PtLevel level = new(_blockAllocator, LevelCount, depth < 2); + Span currentLevel = level.AsSpan(); + + (_, int blockShift) = GetBlockSizeAndShift(depth); + + // Fill in the blocks. + for (int i = 0; i < LevelCount; i++) + { + ulong offset = (ulong)i << blockShift; + currentLevel[i] = pte + offset; + } + + level.EntriesCount = LevelCount; + + return level; + } + + private static (ulong, int) GetBlockSizeAndShift(int depth) + { + int blockShift = PageBits + (2 - depth) * LevelBits; + ulong blockSize = 1UL << blockShift; + + return (blockSize, blockShift); + } + + private static (ulong, int) GetMapSizeAndDepth(ulong va, ulong pa, ulong endVa) + { + // Both virtual and physical addresses must be aligned to the block size. + ulong combinedAddress = va | pa; + + ulong l0Alignment = 1UL << (PageBits + LevelBits * 2); + ulong l1Alignment = 1UL << (PageBits + LevelBits); + + if ((combinedAddress & (l0Alignment - 1)) == 0 && AlignDown(endVa, l0Alignment) > va) + { + return (AlignDown(endVa, l0Alignment) - va, 0); + } + else if ((combinedAddress & (l1Alignment - 1)) == 0 && AlignDown(endVa, l1Alignment) > va) + { + ulong nextOrderVa = GetNextAddress(va, l0Alignment); + + if (nextOrderVa <= endVa) + { + return (nextOrderVa - va, 1); + } + else + { + return (AlignDown(endVa, l1Alignment) - va, 1); + } + } + else + { + ulong nextOrderVa = GetNextAddress(va, l1Alignment); + + if (nextOrderVa <= endVa) + { + return (nextOrderVa - va, 2); + } + else + { + return (endVa - va, 2); + } + } + } + + private static ulong AlignDown(ulong va, ulong alignment) + { + return va & ~(alignment - 1); + } + + private static ulong GetNextAddress(ulong va, ulong alignment) + { + return (va + alignment) & ~(alignment - 1); + } + + private PtLevel EnsureLevel0() + { + PtLevel level0 = _level0; + + if (level0 == null) + { + level0 = new PtLevel(_blockAllocator, LevelCount, true); + _level0 = level0; + } + + return level0; + } + + private void EnsureTable(PtLevel level, int index, bool hasNext) + { + Span currentTable = level.AsSpan(); + + if ((currentTable[index] & 1) == 0) + { + PtLevel nextLevel = new(_blockAllocator, LevelCount, hasNext); + + currentTable[index] = (nextLevel.Address & ~(ulong)PageMask) | 3UL; + level.Next[index] = nextLevel; + level.EntriesCount++; + ValidateEntriesCount(level.EntriesCount); + } + else if (level.Next[index] == null) + { + Debug.Fail($"Index {index} is block, expected a table."); + } + } + + private static void WriteBlock(PtLevel level, int index, int depth, ulong pa, ulong attr) + { + Span currentTable = level.AsSpan(); + + currentTable[index] = (pa & ~((ulong)AllLevelsMask >> (depth * LevelBits))) | (depth == 2 ? 3UL : 1UL) | attr; + + level.EntriesCount++; + ValidateEntriesCount(level.EntriesCount); + } + + private static void ValidateEntriesCount(int count) + { + Debug.Assert(count >= 0 && count <= LevelCount, $"Entries count {count} is invalid."); + } + + public void Dispose() + { + _blockAllocator.Dispose(); + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvApi.cs b/src/Ryujinx.Cpu/AppleHv/HvApi.cs new file mode 100644 index 00000000..e6e08111 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvApi.cs @@ -0,0 +1,329 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Cpu.AppleHv +{ + struct HvVcpuExitException + { +#pragma warning disable CS0649 // Field is never assigned to + public ulong Syndrome; + public ulong VirtualAddress; + public ulong PhysicalAddress; +#pragma warning restore CS0649 + } + + enum HvExitReason : uint + { + Canceled, + Exception, + VTimerActivated, + Unknown, + } + + struct HvVcpuExit + { +#pragma warning disable CS0649 // Field is never assigned to + public HvExitReason Reason; + public HvVcpuExitException Exception; +#pragma warning restore CS0649 + } + + enum HvReg : uint + { + X0, + X1, + X2, + X3, + X4, + X5, + X6, + X7, + X8, + X9, + X10, + X11, + X12, + X13, + X14, + X15, + X16, + X17, + X18, + X19, + X20, + X21, + X22, + X23, + X24, + X25, + X26, + X27, + X28, + X29, + FP = X29, + X30, + LR = X30, + PC, + FPCR, + FPSR, + CPSR, + } + + enum HvSimdFPReg : uint + { + Q0, + Q1, + Q2, + Q3, + Q4, + Q5, + Q6, + Q7, + Q8, + Q9, + Q10, + Q11, + Q12, + Q13, + Q14, + Q15, + Q16, + Q17, + Q18, + Q19, + Q20, + Q21, + Q22, + Q23, + Q24, + Q25, + Q26, + Q27, + Q28, + Q29, + Q30, + Q31, + } + + enum HvSysReg : ushort + { + DBGBVR0_EL1 = 0x8004, + DBGBCR0_EL1 = 0x8005, + DBGWVR0_EL1 = 0x8006, + DBGWCR0_EL1 = 0x8007, + DBGBVR1_EL1 = 0x800c, + DBGBCR1_EL1 = 0x800d, + DBGWVR1_EL1 = 0x800e, + DBGWCR1_EL1 = 0x800f, + MDCCINT_EL1 = 0x8010, + MDSCR_EL1 = 0x8012, + DBGBVR2_EL1 = 0x8014, + DBGBCR2_EL1 = 0x8015, + DBGWVR2_EL1 = 0x8016, + DBGWCR2_EL1 = 0x8017, + DBGBVR3_EL1 = 0x801c, + DBGBCR3_EL1 = 0x801d, + DBGWVR3_EL1 = 0x801e, + DBGWCR3_EL1 = 0x801f, + DBGBVR4_EL1 = 0x8024, + DBGBCR4_EL1 = 0x8025, + DBGWVR4_EL1 = 0x8026, + DBGWCR4_EL1 = 0x8027, + DBGBVR5_EL1 = 0x802c, + DBGBCR5_EL1 = 0x802d, + DBGWVR5_EL1 = 0x802e, + DBGWCR5_EL1 = 0x802f, + DBGBVR6_EL1 = 0x8034, + DBGBCR6_EL1 = 0x8035, + DBGWVR6_EL1 = 0x8036, + DBGWCR6_EL1 = 0x8037, + DBGBVR7_EL1 = 0x803c, + DBGBCR7_EL1 = 0x803d, + DBGWVR7_EL1 = 0x803e, + DBGWCR7_EL1 = 0x803f, + DBGBVR8_EL1 = 0x8044, + DBGBCR8_EL1 = 0x8045, + DBGWVR8_EL1 = 0x8046, + DBGWCR8_EL1 = 0x8047, + DBGBVR9_EL1 = 0x804c, + DBGBCR9_EL1 = 0x804d, + DBGWVR9_EL1 = 0x804e, + DBGWCR9_EL1 = 0x804f, + DBGBVR10_EL1 = 0x8054, + DBGBCR10_EL1 = 0x8055, + DBGWVR10_EL1 = 0x8056, + DBGWCR10_EL1 = 0x8057, + DBGBVR11_EL1 = 0x805c, + DBGBCR11_EL1 = 0x805d, + DBGWVR11_EL1 = 0x805e, + DBGWCR11_EL1 = 0x805f, + DBGBVR12_EL1 = 0x8064, + DBGBCR12_EL1 = 0x8065, + DBGWVR12_EL1 = 0x8066, + DBGWCR12_EL1 = 0x8067, + DBGBVR13_EL1 = 0x806c, + DBGBCR13_EL1 = 0x806d, + DBGWVR13_EL1 = 0x806e, + DBGWCR13_EL1 = 0x806f, + DBGBVR14_EL1 = 0x8074, + DBGBCR14_EL1 = 0x8075, + DBGWVR14_EL1 = 0x8076, + DBGWCR14_EL1 = 0x8077, + DBGBVR15_EL1 = 0x807c, + DBGBCR15_EL1 = 0x807d, + DBGWVR15_EL1 = 0x807e, + DBGWCR15_EL1 = 0x807f, + MIDR_EL1 = 0xc000, + MPIDR_EL1 = 0xc005, + ID_AA64PFR0_EL1 = 0xc020, + ID_AA64PFR1_EL1 = 0xc021, + ID_AA64DFR0_EL1 = 0xc028, + ID_AA64DFR1_EL1 = 0xc029, + ID_AA64ISAR0_EL1 = 0xc030, + ID_AA64ISAR1_EL1 = 0xc031, + ID_AA64MMFR0_EL1 = 0xc038, + ID_AA64MMFR1_EL1 = 0xc039, + ID_AA64MMFR2_EL1 = 0xc03a, + SCTLR_EL1 = 0xc080, + CPACR_EL1 = 0xc082, + TTBR0_EL1 = 0xc100, + TTBR1_EL1 = 0xc101, + TCR_EL1 = 0xc102, + APIAKEYLO_EL1 = 0xc108, + APIAKEYHI_EL1 = 0xc109, + APIBKEYLO_EL1 = 0xc10a, + APIBKEYHI_EL1 = 0xc10b, + APDAKEYLO_EL1 = 0xc110, + APDAKEYHI_EL1 = 0xc111, + APDBKEYLO_EL1 = 0xc112, + APDBKEYHI_EL1 = 0xc113, + APGAKEYLO_EL1 = 0xc118, + APGAKEYHI_EL1 = 0xc119, + SPSR_EL1 = 0xc200, + ELR_EL1 = 0xc201, + SP_EL0 = 0xc208, + AFSR0_EL1 = 0xc288, + AFSR1_EL1 = 0xc289, + ESR_EL1 = 0xc290, + FAR_EL1 = 0xc300, + PAR_EL1 = 0xc3a0, + MAIR_EL1 = 0xc510, + AMAIR_EL1 = 0xc518, + VBAR_EL1 = 0xc600, + CONTEXTIDR_EL1 = 0xc681, + TPIDR_EL1 = 0xc684, + CNTKCTL_EL1 = 0xc708, + CSSELR_EL1 = 0xd000, + TPIDR_EL0 = 0xde82, + TPIDRRO_EL0 = 0xde83, + CNTV_CTL_EL0 = 0xdf19, + CNTV_CVAL_EL0 = 0xdf1a, + SP_EL1 = 0xe208, + } + + enum HvMemoryFlags : ulong + { + Read = 1UL << 0, + Write = 1UL << 1, + Exec = 1UL << 2, + } + + enum HvResult : uint + { + Success = 0, + Error = 0xfae94001, + Busy = 0xfae94002, + BadArgument = 0xfae94003, + NoResources = 0xfae94005, + NoDevice = 0xfae94006, + Denied = 0xfae94007, + Unsupported = 0xfae9400f, + } + + enum HvInterruptType : uint + { + IRQ, + FIQ, + } + + struct HvSimdFPUchar16 + { + public ulong Low; + public ulong High; + } + + static class HvResultExtensions + { + public static void ThrowOnError(this HvResult result) + { + if (result != HvResult.Success) + { + throw new Exception($"Unexpected result \"{result}\"."); + } + } + } + + [SupportedOSPlatform("macos")] + static partial class HvApi + { + public const string LibraryName = "/System/Library/Frameworks/Hypervisor.framework/Hypervisor"; + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vm_get_max_vcpu_count(out uint max_vcpu_count); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vm_create(IntPtr config); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vm_destroy(); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vm_map(ulong addr, ulong ipa, ulong size, HvMemoryFlags flags); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vm_unmap(ulong ipa, ulong size); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vm_protect(ulong ipa, ulong size, HvMemoryFlags flags); + + [LibraryImport(LibraryName, SetLastError = true)] + public unsafe static partial HvResult hv_vcpu_create(out ulong vcpu, ref HvVcpuExit* exit, IntPtr config); + + [LibraryImport(LibraryName, SetLastError = true)] + public unsafe static partial HvResult hv_vcpu_destroy(ulong vcpu); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vcpu_run(ulong vcpu); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vcpus_exit(ref ulong vcpus, uint vcpu_count); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vcpu_set_vtimer_mask(ulong vcpu, [MarshalAs(UnmanagedType.Bool)] bool vtimer_is_masked); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vcpu_get_reg(ulong vcpu, HvReg reg, out ulong value); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vcpu_set_reg(ulong vcpu, HvReg reg, ulong value); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vcpu_get_simd_fp_reg(ulong vcpu, HvSimdFPReg reg, out HvSimdFPUchar16 value); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vcpu_set_simd_fp_reg(ulong vcpu, HvSimdFPReg reg, HvSimdFPUchar16 value); // DO NOT USE DIRECTLY! + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vcpu_get_sys_reg(ulong vcpu, HvSysReg reg, out ulong value); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vcpu_set_sys_reg(ulong vcpu, HvSysReg reg, ulong value); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vcpu_get_pending_interrupt(ulong vcpu, HvInterruptType type, [MarshalAs(UnmanagedType.Bool)] out bool pending); + + [LibraryImport(LibraryName, SetLastError = true)] + public static partial HvResult hv_vcpu_set_pending_interrupt(ulong vcpu, HvInterruptType type, [MarshalAs(UnmanagedType.Bool)] bool pending); + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs b/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs new file mode 100644 index 00000000..99e4c047 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs @@ -0,0 +1,48 @@ +using ARMeilleure.Memory; +using System.Runtime.Versioning; + +namespace Ryujinx.Cpu.AppleHv +{ + [SupportedOSPlatform("macos")] + class HvCpuContext : ICpuContext + { + private readonly ITickSource _tickSource; + private readonly HvMemoryManager _memoryManager; + + public HvCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit) + { + _tickSource = tickSource; + _memoryManager = (HvMemoryManager)memory; + } + + /// + public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks) + { + return new HvExecutionContext(_tickSource, exceptionCallbacks); + } + + /// + public void Execute(IExecutionContext context, ulong address) + { + ((HvExecutionContext)context).Execute(_memoryManager, address); + } + + /// + public void InvalidateCacheRegion(ulong address, ulong size) + { + } + + public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled) + { + return new DummyDiskCacheLoadState(); + } + + public void PrepareCodeRange(ulong address, ulong size) + { + } + + public void Dispose() + { + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvEngine.cs b/src/Ryujinx.Cpu/AppleHv/HvEngine.cs new file mode 100644 index 00000000..c3c1a448 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvEngine.cs @@ -0,0 +1,22 @@ +using ARMeilleure.Memory; +using System.Runtime.Versioning; + +namespace Ryujinx.Cpu.AppleHv +{ + [SupportedOSPlatform("macos")] + public class HvEngine : ICpuEngine + { + private readonly ITickSource _tickSource; + + public HvEngine(ITickSource tickSource) + { + _tickSource = tickSource; + } + + /// + public ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit) + { + return new HvCpuContext(_tickSource, memoryManager, for64Bit); + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs new file mode 100644 index 00000000..fc2b76d1 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs @@ -0,0 +1,305 @@ +using ARMeilleure.State; +using Ryujinx.Cpu.AppleHv.Arm; +using Ryujinx.Memory.Tracking; +using System; +using System.Runtime.Versioning; +using System.Threading; + +namespace Ryujinx.Cpu.AppleHv +{ + [SupportedOSPlatform("macos")] + class HvExecutionContext : IExecutionContext + { + /// + public ulong Pc => _impl.ElrEl1; + + /// + public long TpidrEl0 + { + get => _impl.TpidrEl0; + set => _impl.TpidrEl0 = value; + } + + /// + public long TpidrroEl0 + { + get => _impl.TpidrroEl0; + set => _impl.TpidrroEl0 = value; + } + + /// + public uint Pstate + { + get => _impl.Pstate; + set => _impl.Pstate = value; + } + + /// + public uint Fpcr + { + get => _impl.Fpcr; + set => _impl.Fpcr = value; + } + + /// + public uint Fpsr + { + get => _impl.Fpsr; + set => _impl.Fpsr = value; + } + + /// + public bool IsAarch32 + { + get => false; + set + { + if (value) + { + throw new NotSupportedException(); + } + } + } + + /// + public bool Running { get; private set; } + + private readonly ICounter _counter; + private readonly IHvExecutionContext _shadowContext; + private IHvExecutionContext _impl; + + private readonly ExceptionCallbacks _exceptionCallbacks; + + private int _interruptRequested; + + public HvExecutionContext(ICounter counter, ExceptionCallbacks exceptionCallbacks) + { + _counter = counter; + _shadowContext = new HvExecutionContextShadow(); + _impl = _shadowContext; + _exceptionCallbacks = exceptionCallbacks; + Running = true; + } + + /// + public ulong GetX(int index) => _impl.GetX(index); + + /// + public void SetX(int index, ulong value) => _impl.SetX(index, value); + + /// + public V128 GetV(int index) => _impl.GetV(index); + + /// + public void SetV(int index, V128 value) => _impl.SetV(index, value); + + private void InterruptHandler() + { + _exceptionCallbacks.InterruptCallback?.Invoke(this); + } + + private void BreakHandler(ulong address, int imm) + { + _exceptionCallbacks.BreakCallback?.Invoke(this, address, imm); + } + + private void SupervisorCallHandler(ulong address, int imm) + { + _exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm); + } + + private void UndefinedHandler(ulong address, int opCode) + { + _exceptionCallbacks.UndefinedCallback?.Invoke(this, address, opCode); + } + + /// + public void RequestInterrupt() + { + if (Interlocked.Exchange(ref _interruptRequested, 1) == 0 && _impl is HvExecutionContextVcpu impl) + { + impl.RequestInterrupt(); + } + } + + private bool GetAndClearInterruptRequested() + { + return Interlocked.Exchange(ref _interruptRequested, 0) != 0; + } + + /// + public void StopRunning() + { + Running = false; + RequestInterrupt(); + } + + public unsafe void Execute(HvMemoryManager memoryManager, ulong address) + { + HvVcpu vcpu = HvVcpuPool.Instance.Create(memoryManager.AddressSpace, _shadowContext, SwapContext); + + HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError(); + + while (Running) + { + HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError(); + + HvExitReason reason = vcpu.ExitInfo->Reason; + + if (reason == HvExitReason.Exception) + { + uint hvEsr = (uint)vcpu.ExitInfo->Exception.Syndrome; + ExceptionClass hvEc = (ExceptionClass)(hvEsr >> 26); + + if (hvEc != ExceptionClass.HvcAarch64) + { + throw new Exception($"Unhandled exception from guest kernel with ESR 0x{hvEsr:X} ({hvEc})."); + } + + address = SynchronousException(memoryManager, ref vcpu); + HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError(); + } + else if (reason == HvExitReason.Canceled || reason == HvExitReason.VTimerActivated) + { + if (GetAndClearInterruptRequested()) + { + ReturnToPool(vcpu); + InterruptHandler(); + vcpu = RentFromPool(memoryManager.AddressSpace, vcpu); + } + + if (reason == HvExitReason.VTimerActivated) + { + vcpu.EnableAndUpdateVTimer(); + + // Unmask VTimer interrupts. + HvApi.hv_vcpu_set_vtimer_mask(vcpu.Handle, false).ThrowOnError(); + } + } + else + { + throw new Exception($"Unhandled exit reason {reason}."); + } + } + + HvVcpuPool.Instance.Destroy(vcpu, SwapContext); + } + + private ulong SynchronousException(HvMemoryManager memoryManager, ref HvVcpu vcpu) + { + ulong vcpuHandle = vcpu.Handle; + + HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.ELR_EL1, out ulong elr).ThrowOnError(); + HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.ESR_EL1, out ulong esr).ThrowOnError(); + + ExceptionClass ec = (ExceptionClass)((uint)esr >> 26); + + switch (ec) + { + case ExceptionClass.DataAbortLowerEl: + DataAbort(memoryManager.Tracking, vcpuHandle, (uint)esr); + break; + case ExceptionClass.TrappedMsrMrsSystem: + InstructionTrap((uint)esr); + HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.ELR_EL1, elr + 4UL).ThrowOnError(); + break; + case ExceptionClass.SvcAarch64: + ReturnToPool(vcpu); + ushort id = (ushort)esr; + SupervisorCallHandler(elr - 4UL, id); + vcpu = RentFromPool(memoryManager.AddressSpace, vcpu); + break; + default: + throw new Exception($"Unhandled guest exception {ec}."); + } + + // Make sure we will continue running at EL0. + if (memoryManager.AddressSpace.GetAndClearUserTlbInvalidationPending()) + { + // TODO: Invalidate only the range that was modified? + return HvAddressSpace.KernelRegionTlbiEretAddress; + } + else + { + return HvAddressSpace.KernelRegionEretAddress; + } + } + + private static void DataAbort(MemoryTracking tracking, ulong vcpu, uint esr) + { + bool write = (esr & (1u << 6)) != 0; + bool farValid = (esr & (1u << 10)) == 0; + int accessSizeLog2 = (int)((esr >> 22) & 3); + + if (farValid) + { + HvApi.hv_vcpu_get_sys_reg(vcpu, HvSysReg.FAR_EL1, out ulong far).ThrowOnError(); + + ulong size = 1UL << accessSizeLog2; + + if (!tracking.VirtualMemoryEvent(far, size, write)) + { + string rw = write ? "write" : "read"; + throw new Exception($"Unhandled invalid memory access at VA 0x{far:X} with size 0x{size:X} ({rw})."); + } + } + else + { + throw new Exception($"Unhandled invalid memory access at unknown VA with ESR 0x{esr:X}."); + } + } + + private void InstructionTrap(uint esr) + { + bool read = (esr & 1) != 0; + uint rt = (esr >> 5) & 0x1f; + + if (read) + { + // Op0 Op2 Op1 CRn 00000 CRm + switch ((esr >> 1) & 0x1ffe0f) + { + case 0b11_000_011_1110_00000_0000: // CNTFRQ_EL0 + WriteRt(rt, _counter.Frequency); + break; + case 0b11_001_011_1110_00000_0000: // CNTPCT_EL0 + WriteRt(rt, _counter.Counter); + break; + default: + throw new Exception($"Unhandled system register read with ESR 0x{esr:X}"); + } + } + else + { + throw new Exception($"Unhandled system register write with ESR 0x{esr:X}"); + } + } + + private void WriteRt(uint rt, ulong value) + { + if (rt < 31) + { + SetX((int)rt, value); + } + } + + private void ReturnToPool(HvVcpu vcpu) + { + HvVcpuPool.Instance.Return(vcpu, SwapContext); + } + + private HvVcpu RentFromPool(HvAddressSpace addressSpace, HvVcpu vcpu) + { + return HvVcpuPool.Instance.Rent(addressSpace, _shadowContext, vcpu, SwapContext); + } + + private void SwapContext(IHvExecutionContext newContext) + { + _impl = newContext; + } + + public void Dispose() + { + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs new file mode 100644 index 00000000..6ce8e180 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs @@ -0,0 +1,50 @@ +using ARMeilleure.State; + +namespace Ryujinx.Cpu.AppleHv +{ + class HvExecutionContextShadow : IHvExecutionContext + { + public ulong Pc { get; set; } + public ulong ElrEl1 { get; set; } + public ulong EsrEl1 { get; set; } + + public long TpidrEl0 { get; set; } + public long TpidrroEl0 { get; set; } + + public uint Pstate { get; set; } + + public uint Fpcr { get; set; } + public uint Fpsr { get; set; } + + public bool IsAarch32 { get; set; } + + private readonly ulong[] _x; + private readonly V128[] _v; + + public HvExecutionContextShadow() + { + _x = new ulong[32]; + _v = new V128[32]; + } + + public ulong GetX(int index) + { + return _x[index]; + } + + public void SetX(int index, ulong value) + { + _x[index] = value; + } + + public V128 GetV(int index) + { + return _v[index]; + } + + public void SetV(int index, V128 value) + { + _v[index] = value; + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs new file mode 100644 index 00000000..bb232940 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs @@ -0,0 +1,188 @@ +using ARMeilleure.State; +using Ryujinx.Memory; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Cpu.AppleHv +{ + [SupportedOSPlatform("macos")] + class HvExecutionContextVcpu : IHvExecutionContext + { + private static readonly MemoryBlock _setSimdFpRegFuncMem; + private delegate HvResult SetSimdFpReg(ulong vcpu, HvSimdFPReg reg, in V128 value, IntPtr funcPtr); + private static readonly SetSimdFpReg _setSimdFpReg; + private static readonly IntPtr _setSimdFpRegNativePtr; + + static HvExecutionContextVcpu() + { + // .NET does not support passing vectors by value, so we need to pass a pointer and use a native + // function to load the value into a vector register. + _setSimdFpRegFuncMem = new MemoryBlock(MemoryBlock.GetPageSize()); + _setSimdFpRegFuncMem.Write(0, 0x3DC00040u); // LDR Q0, [X2] + _setSimdFpRegFuncMem.Write(4, 0xD61F0060u); // BR X3 + _setSimdFpRegFuncMem.Reprotect(0, _setSimdFpRegFuncMem.Size, MemoryPermission.ReadAndExecute); + + _setSimdFpReg = Marshal.GetDelegateForFunctionPointer(_setSimdFpRegFuncMem.Pointer); + + if (NativeLibrary.TryLoad(HvApi.LibraryName, out IntPtr hvLibHandle)) + { + _setSimdFpRegNativePtr = NativeLibrary.GetExport(hvLibHandle, nameof(HvApi.hv_vcpu_set_simd_fp_reg)); + } + } + + public ulong Pc + { + get + { + HvApi.hv_vcpu_get_reg(_vcpu, HvReg.PC, out ulong pc).ThrowOnError(); + return pc; + } + set + { + HvApi.hv_vcpu_set_reg(_vcpu, HvReg.PC, value).ThrowOnError(); + } + } + + public ulong ElrEl1 + { + get + { + HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.ELR_EL1, out ulong elr).ThrowOnError(); + return elr; + } + set + { + HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.ELR_EL1, value).ThrowOnError(); + } + } + + public ulong EsrEl1 + { + get + { + HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.ESR_EL1, out ulong esr).ThrowOnError(); + return esr; + } + set + { + HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.ESR_EL1, value).ThrowOnError(); + } + } + + public long TpidrEl0 + { + get + { + HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.TPIDR_EL0, out ulong tpidrEl0).ThrowOnError(); + return (long)tpidrEl0; + } + set + { + HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.TPIDR_EL0, (ulong)value).ThrowOnError(); + } + } + + public long TpidrroEl0 + { + get + { + HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.TPIDRRO_EL0, out ulong tpidrroEl0).ThrowOnError(); + return (long)tpidrroEl0; + } + set + { + HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.TPIDRRO_EL0, (ulong)value).ThrowOnError(); + } + } + + public uint Pstate + { + get + { + HvApi.hv_vcpu_get_reg(_vcpu, HvReg.CPSR, out ulong cpsr).ThrowOnError(); + return (uint)cpsr; + } + set + { + HvApi.hv_vcpu_set_reg(_vcpu, HvReg.CPSR, (ulong)value).ThrowOnError(); + } + } + + public uint Fpcr + { + get + { + HvApi.hv_vcpu_get_reg(_vcpu, HvReg.FPCR, out ulong fpcr).ThrowOnError(); + return (uint)fpcr; + } + set + { + HvApi.hv_vcpu_set_reg(_vcpu, HvReg.FPCR, (ulong)value).ThrowOnError(); + } + } + + public uint Fpsr + { + get + { + HvApi.hv_vcpu_get_reg(_vcpu, HvReg.FPSR, out ulong fpsr).ThrowOnError(); + return (uint)fpsr; + } + set + { + HvApi.hv_vcpu_set_reg(_vcpu, HvReg.FPSR, (ulong)value).ThrowOnError(); + } + } + + private readonly ulong _vcpu; + + public HvExecutionContextVcpu(ulong vcpu) + { + _vcpu = vcpu; + } + + public ulong GetX(int index) + { + if (index == 31) + { + HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.SP_EL0, out ulong value).ThrowOnError(); + return value; + } + else + { + HvApi.hv_vcpu_get_reg(_vcpu, HvReg.X0 + (uint)index, out ulong value).ThrowOnError(); + return value; + } + } + + public void SetX(int index, ulong value) + { + if (index == 31) + { + HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.SP_EL0, value).ThrowOnError(); + } + else + { + HvApi.hv_vcpu_set_reg(_vcpu, HvReg.X0 + (uint)index, value).ThrowOnError(); + } + } + + public V128 GetV(int index) + { + HvApi.hv_vcpu_get_simd_fp_reg(_vcpu, HvSimdFPReg.Q0 + (uint)index, out HvSimdFPUchar16 value).ThrowOnError(); + return new V128(value.Low, value.High); + } + + public void SetV(int index, V128 value) + { + _setSimdFpReg(_vcpu, HvSimdFPReg.Q0 + (uint)index, value, _setSimdFpRegNativePtr).ThrowOnError(); + } + + public void RequestInterrupt() + { + ulong vcpu = _vcpu; + HvApi.hv_vcpus_exit(ref vcpu, 1); + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvIpaAllocator.cs b/src/Ryujinx.Cpu/AppleHv/HvIpaAllocator.cs new file mode 100644 index 00000000..35375ee6 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvIpaAllocator.cs @@ -0,0 +1,34 @@ +using System; + +namespace Ryujinx.Cpu.AppleHv +{ + class HvIpaAllocator + { + private const ulong AllocationGranule = 1UL << 14; + private const ulong IpaRegionSize = 1UL << 35; + + private readonly PrivateMemoryAllocator.Block _block; + + public HvIpaAllocator() + { + _block = new PrivateMemoryAllocator.Block(null, IpaRegionSize); + } + + public ulong Allocate(ulong size, ulong alignment = AllocationGranule) + { + ulong offset = _block.Allocate(size, alignment); + + if (offset == PrivateMemoryAllocator.InvalidOffset) + { + throw new InvalidOperationException($"No enough free IPA memory to allocate 0x{size:X} bytes with alignment 0x{alignment:X}."); + } + + return offset; + } + + public void Free(ulong offset, ulong size) + { + _block.Free(offset, size); + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocation.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocation.cs new file mode 100644 index 00000000..855d313c --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocation.cs @@ -0,0 +1,36 @@ +using Ryujinx.Memory; +using System; +using System.Runtime.Versioning; + +namespace Ryujinx.Cpu.AppleHv +{ + [SupportedOSPlatform("macos")] + readonly struct HvMemoryBlockAllocation : IDisposable + { + private readonly HvMemoryBlockAllocator _owner; + private readonly HvMemoryBlockAllocator.Block _block; + + public bool IsValid => _owner != null; + public MemoryBlock Memory => _block.Memory; + public ulong Ipa => _block.Ipa; + public ulong Offset { get; } + public ulong Size { get; } + + public HvMemoryBlockAllocation( + HvMemoryBlockAllocator owner, + HvMemoryBlockAllocator.Block block, + ulong offset, + ulong size) + { + _owner = owner; + _block = block; + Offset = offset; + Size = size; + } + + public void Dispose() + { + _owner.Free(_block, Offset, Size); + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs new file mode 100644 index 00000000..86936c59 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs @@ -0,0 +1,58 @@ +using Ryujinx.Memory; +using System.Runtime.Versioning; + +namespace Ryujinx.Cpu.AppleHv +{ + [SupportedOSPlatform("macos")] + class HvMemoryBlockAllocator : PrivateMemoryAllocatorImpl + { + public class Block : PrivateMemoryAllocator.Block + { + private readonly HvIpaAllocator _ipaAllocator; + public ulong Ipa { get; } + + public Block(HvIpaAllocator ipaAllocator, MemoryBlock memory, ulong size) : base(memory, size) + { + _ipaAllocator = ipaAllocator; + + lock (ipaAllocator) + { + Ipa = ipaAllocator.Allocate(size); + } + + HvApi.hv_vm_map((ulong)Memory.Pointer, Ipa, size, HvMemoryFlags.Read | HvMemoryFlags.Write).ThrowOnError(); + } + + public override void Destroy() + { + HvApi.hv_vm_unmap(Ipa, Size).ThrowOnError(); + + lock (_ipaAllocator) + { + _ipaAllocator.Free(Ipa, Size); + } + + base.Destroy(); + } + } + + private readonly HvIpaAllocator _ipaAllocator; + + public HvMemoryBlockAllocator(HvIpaAllocator ipaAllocator, ulong blockAlignment) : base(blockAlignment, MemoryAllocationFlags.None) + { + _ipaAllocator = ipaAllocator; + } + + public HvMemoryBlockAllocation Allocate(ulong size, ulong alignment) + { + var allocation = Allocate(size, alignment, CreateBlock); + + return new HvMemoryBlockAllocation(this, allocation.Block, allocation.Offset, allocation.Size); + } + + private Block CreateBlock(MemoryBlock memory, ulong size) + { + return new Block(_ipaAllocator, memory, size); + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs new file mode 100644 index 00000000..abdddb31 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs @@ -0,0 +1,392 @@ +using ARMeilleure.Memory; +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using Ryujinx.Memory.Tracking; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Cpu.AppleHv +{ + /// + /// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table. + /// + [SupportedOSPlatform("macos")] + public sealed class HvMemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked + { + private readonly InvalidAccessHandler _invalidAccessHandler; + + private readonly HvAddressSpace _addressSpace; + + internal HvAddressSpace AddressSpace => _addressSpace; + + private readonly MemoryBlock _backingMemory; + private readonly PageTable _pageTable; + + private readonly ManagedPageFlags _pages; + + public bool UsesPrivateAllocations => false; + + public int AddressSpaceBits { get; } + + public IntPtr PageTablePointer => IntPtr.Zero; + + public MemoryManagerType Type => MemoryManagerType.SoftwarePageTable; + + public MemoryTracking Tracking { get; } + + public event Action UnmapEvent; + + protected override ulong AddressSpaceSize { get; } + + /// + /// Creates a new instance of the Hypervisor memory manager. + /// + /// Physical backing memory where virtual memory will be mapped to + /// Size of the address space + /// Optional function to handle invalid memory accesses + public HvMemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null) + { + _backingMemory = backingMemory; + _pageTable = new PageTable(); + _invalidAccessHandler = invalidAccessHandler; + AddressSpaceSize = addressSpaceSize; + + ulong asSize = PageSize; + int asBits = PageBits; + + while (asSize < addressSpaceSize) + { + asSize <<= 1; + asBits++; + } + + _addressSpace = new HvAddressSpace(backingMemory, asSize); + + AddressSpaceBits = asBits; + + _pages = new ManagedPageFlags(AddressSpaceBits); + Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler); + } + + /// + public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) + { + AssertValidAddressAndSize(va, size); + + PtMap(va, pa, size); + _addressSpace.MapUser(va, pa, size, MemoryPermission.ReadWriteExecute); + _pages.AddMapping(va, size); + + Tracking.Map(va, size); + } + + private void PtMap(ulong va, ulong pa, ulong size) + { + while (size != 0) + { + _pageTable.Map(va, pa); + + va += PageSize; + pa += PageSize; + size -= PageSize; + } + } + + /// + public void Unmap(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + UnmapEvent?.Invoke(va, size); + Tracking.Unmap(va, size); + + _pages.RemoveMapping(va, size); + _addressSpace.UnmapUser(va, size); + PtUnmap(va, size); + } + + private void PtUnmap(ulong va, ulong size) + { + while (size != 0) + { + _pageTable.Unmap(va); + + va += PageSize; + size -= PageSize; + } + } + + public override T ReadTracked(ulong va) + { + try + { + return base.ReadTracked(va); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return default; + } + } + + public override void Read(ulong va, Span data) + { + try + { + base.Read(va, data); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + public override void Write(ulong va, ReadOnlySpan data) + { + try + { + base.Write(va, data); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + public override void WriteUntracked(ulong va, ReadOnlySpan data) + { + try + { + base.WriteUntracked(va, data); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + public override ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + try + { + return base.GetReadOnlySequence(va, size, tracked); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return ReadOnlySequence.Empty; + } + } + + public ref T GetRef(ulong va) where T : unmanaged + { + if (!IsContiguous(va, Unsafe.SizeOf())) + { + ThrowMemoryNotContiguous(); + } + + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), true); + + return ref _backingMemory.GetRef(GetPhysicalAddressChecked(va)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool IsMapped(ulong va) + { + return ValidateAddress(va) && _pages.IsMapped(va); + } + + /// + public bool IsRangeMapped(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + return _pages.IsRangeMapped(va, size); + } + + /// + public IEnumerable GetHostRegions(ulong va, ulong size) + { + if (size == 0) + { + return Enumerable.Empty(); + } + + var guestRegions = GetPhysicalRegionsImpl(va, size); + if (guestRegions == null) + { + return null; + } + + var regions = new HostMemoryRange[guestRegions.Count]; + + for (int i = 0; i < regions.Length; i++) + { + var guestRegion = guestRegions[i]; + IntPtr pointer = _backingMemory.GetPointer(guestRegion.Address, guestRegion.Size); + regions[i] = new HostMemoryRange((nuint)(ulong)pointer, guestRegion.Size); + } + + return regions; + } + + /// + public IEnumerable GetPhysicalRegions(ulong va, ulong size) + { + if (size == 0) + { + return Enumerable.Empty(); + } + + return GetPhysicalRegionsImpl(va, size); + } + + private List GetPhysicalRegionsImpl(ulong va, ulong size) + { + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) + { + return null; + } + + int pages = GetPagesCount(va, (uint)size, out va); + + var regions = new List(); + + ulong regionStart = GetPhysicalAddressInternal(va); + ulong regionSize = PageSize; + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return null; + } + + ulong newPa = GetPhysicalAddressInternal(va + PageSize); + + if (GetPhysicalAddressInternal(va) + PageSize != newPa) + { + regions.Add(new MemoryRange(regionStart, regionSize)); + regionStart = newPa; + regionSize = 0; + } + + va += PageSize; + regionSize += PageSize; + } + + regions.Add(new MemoryRange(regionStart, regionSize)); + + return regions; + } + + /// + /// This function also validates that the given range is both valid and mapped, and will throw if it is not. + /// + public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + { + AssertValidAddressAndSize(va, size); + + if (precise) + { + Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId); + return; + } + + _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); + } + + public void Reprotect(ulong va, ulong size, MemoryPermission protection) + { + // TODO + } + + /// + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) + { + if (guest) + { + _addressSpace.ReprotectUser(va, size, protection); + } + else + { + _pages.TrackingReprotect(va, size, protection); + } + } + + /// + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) + { + return Tracking.BeginTracking(address, size, id, flags); + } + + /// + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) + { + return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags); + } + + /// + public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id) + { + return Tracking.BeginSmartGranularTracking(address, size, granularity, id); + } + + private nuint GetPhysicalAddressChecked(ulong va) + { + if (!IsMapped(va)) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}"); + } + + return GetPhysicalAddressInternal(va); + } + + private nuint GetPhysicalAddressInternal(ulong va) + { + return (nuint)(_pageTable.Read(va) + (va & PageMask)); + } + + /// + /// Disposes of resources used by the memory manager. + /// + protected override void Destroy() + { + _addressSpace.Dispose(); + } + + protected override Memory GetPhysicalAddressMemory(nuint pa, int size) + => _backingMemory.GetMemory(pa, size); + + protected override Span GetPhysicalAddressSpan(nuint pa, int size) + => _backingMemory.GetSpan(pa, size); + + protected override nuint TranslateVirtualAddressChecked(ulong va) + => GetPhysicalAddressChecked(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) + => GetPhysicalAddressInternal(va); + + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvVcpu.cs b/src/Ryujinx.Cpu/AppleHv/HvVcpu.cs new file mode 100644 index 00000000..ee91c478 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvVcpu.cs @@ -0,0 +1,56 @@ +using System.Diagnostics; +using System.Runtime.Versioning; + +namespace Ryujinx.Cpu.AppleHv +{ + [SupportedOSPlatform("macos")] + unsafe class HvVcpu + { + private const ulong InterruptIntervalNs = 16 * 1000000; // 16 ms + + private static ulong _interruptTimeDeltaTicks = 0; + + public readonly ulong Handle; + public readonly HvVcpuExit* ExitInfo; + public readonly IHvExecutionContext ShadowContext; + public readonly IHvExecutionContext NativeContext; + public readonly bool IsEphemeral; + + public HvVcpu( + ulong handle, + HvVcpuExit* exitInfo, + IHvExecutionContext shadowContext, + IHvExecutionContext nativeContext, + bool isEphemeral) + { + Handle = handle; + ExitInfo = exitInfo; + ShadowContext = shadowContext; + NativeContext = nativeContext; + IsEphemeral = isEphemeral; + } + + public void EnableAndUpdateVTimer() + { + // We need to ensure interrupts will be serviced, + // and for that we set up the VTime to trigger an interrupt at fixed intervals. + + ulong deltaTicks = _interruptTimeDeltaTicks; + + if (deltaTicks == 0) + { + // Calculate our time delta in ticks based on the current clock frequency. + + int result = TimeApi.mach_timebase_info(out var timeBaseInfo); + + Debug.Assert(result == 0); + + deltaTicks = ((InterruptIntervalNs * timeBaseInfo.Denom) + (timeBaseInfo.Numer - 1)) / timeBaseInfo.Numer; + _interruptTimeDeltaTicks = deltaTicks; + } + + HvApi.hv_vcpu_set_sys_reg(Handle, HvSysReg.CNTV_CTL_EL0, 1).ThrowOnError(); + HvApi.hv_vcpu_set_sys_reg(Handle, HvSysReg.CNTV_CVAL_EL0, TimeApi.mach_absolute_time() + deltaTicks).ThrowOnError(); + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvVcpuPool.cs b/src/Ryujinx.Cpu/AppleHv/HvVcpuPool.cs new file mode 100644 index 00000000..2edcd7e4 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvVcpuPool.cs @@ -0,0 +1,107 @@ +using System; +using System.Runtime.Versioning; +using System.Threading; + +namespace Ryujinx.Cpu.AppleHv +{ + [SupportedOSPlatform("macos")] + class HvVcpuPool + { + // Since there's a limit on the number of VCPUs we can create, + // and we assign one VCPU per guest thread, we need to ensure + // there are enough VCPUs available for at least the maximum number of active guest threads. + // To do that, we always destroy and re-create VCPUs that are above a given limit. + // Those VCPUs are called "ephemeral" here because they are not kept for long. + // + // In the future, we might want to consider a smarter approach that only makes + // VCPUs for threads that are not running frequently "ephemeral", but this is + // complicated because VCPUs can only be destroyed by the same thread that created them. + + private const int MaxActiveVcpus = 4; + + public static readonly HvVcpuPool Instance = new(); + + private int _totalVcpus; + private readonly int _maxVcpus; + + public HvVcpuPool() + { + HvApi.hv_vm_get_max_vcpu_count(out uint maxVcpuCount).ThrowOnError(); + _maxVcpus = (int)maxVcpuCount; + } + + public HvVcpu Create(HvAddressSpace addressSpace, IHvExecutionContext shadowContext, Action swapContext) + { + HvVcpu vcpu = CreateNew(addressSpace, shadowContext); + vcpu.NativeContext.Load(shadowContext); + swapContext(vcpu.NativeContext); + return vcpu; + } + + public void Destroy(HvVcpu vcpu, Action swapContext) + { + vcpu.ShadowContext.Load(vcpu.NativeContext); + swapContext(vcpu.ShadowContext); + DestroyVcpu(vcpu); + } + + public void Return(HvVcpu vcpu, Action swapContext) + { + if (vcpu.IsEphemeral) + { + Destroy(vcpu, swapContext); + } + } + + public HvVcpu Rent(HvAddressSpace addressSpace, IHvExecutionContext shadowContext, HvVcpu vcpu, Action swapContext) + { + if (vcpu.IsEphemeral) + { + return Create(addressSpace, shadowContext, swapContext); + } + else + { + return vcpu; + } + } + + private unsafe HvVcpu CreateNew(HvAddressSpace addressSpace, IHvExecutionContext shadowContext) + { + int newCount = IncrementVcpuCount(); + bool isEphemeral = newCount > _maxVcpus - MaxActiveVcpus; + + // Create VCPU. + HvVcpuExit* exitInfo = null; + HvApi.hv_vcpu_create(out ulong vcpuHandle, ref exitInfo, IntPtr.Zero).ThrowOnError(); + + // Enable FP and SIMD instructions. + HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.CPACR_EL1, 0b11 << 20).ThrowOnError(); + + addressSpace.InitializeMmu(vcpuHandle); + + HvExecutionContextVcpu nativeContext = new(vcpuHandle); + + HvVcpu vcpu = new(vcpuHandle, exitInfo, shadowContext, nativeContext, isEphemeral); + + vcpu.EnableAndUpdateVTimer(); + + return vcpu; + } + + private void DestroyVcpu(HvVcpu vcpu) + { + HvApi.hv_vcpu_destroy(vcpu.Handle).ThrowOnError(); + DecrementVcpuCount(); + } + + private int IncrementVcpuCount() + { + return Interlocked.Increment(ref _totalVcpus); + } + + private void DecrementVcpuCount() + { + Interlocked.Decrement(ref _totalVcpus); + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvVm.cs b/src/Ryujinx.Cpu/AppleHv/HvVm.cs new file mode 100644 index 00000000..c4f10753 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvVm.cs @@ -0,0 +1,70 @@ +using Ryujinx.Memory; +using System; +using System.Runtime.Versioning; + +namespace Ryujinx.Cpu.AppleHv +{ + [SupportedOSPlatform("macos")] + static class HvVm + { + // This alignment allows us to use larger blocks on the page table. + private const ulong AsIpaAlignment = 1UL << 30; + + private static int _addressSpaces; + private static HvIpaAllocator _ipaAllocator; + private static readonly object _lock = new(); + + public static (ulong, HvIpaAllocator) CreateAddressSpace(MemoryBlock block) + { + HvIpaAllocator ipaAllocator; + + lock (_lock) + { + if (++_addressSpaces == 1) + { + HvApi.hv_vm_create(IntPtr.Zero).ThrowOnError(); + _ipaAllocator = ipaAllocator = new HvIpaAllocator(); + } + else + { + ipaAllocator = _ipaAllocator; + } + } + + ulong baseAddress; + + lock (ipaAllocator) + { + baseAddress = ipaAllocator.Allocate(block.Size, AsIpaAlignment); + } + + var rwx = HvMemoryFlags.Read | HvMemoryFlags.Write | HvMemoryFlags.Exec; + + HvApi.hv_vm_map((ulong)block.Pointer, baseAddress, block.Size, rwx).ThrowOnError(); + + return (baseAddress, ipaAllocator); + } + + public static void DestroyAddressSpace(ulong address, ulong size) + { + HvApi.hv_vm_unmap(address, size); + + HvIpaAllocator ipaAllocator; + + lock (_lock) + { + if (--_addressSpaces == 0) + { + HvApi.hv_vm_destroy().ThrowOnError(); + } + + ipaAllocator = _ipaAllocator; + } + + lock (ipaAllocator) + { + ipaAllocator.Free(address, size); + } + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs new file mode 100644 index 00000000..54b73acc --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs @@ -0,0 +1,43 @@ +using ARMeilleure.State; + +namespace Ryujinx.Cpu.AppleHv +{ + interface IHvExecutionContext + { + ulong Pc { get; set; } + ulong ElrEl1 { get; set; } + ulong EsrEl1 { get; set; } + + long TpidrEl0 { get; set; } + long TpidrroEl0 { get; set; } + + uint Pstate { get; set; } + + uint Fpcr { get; set; } + uint Fpsr { get; set; } + + ulong GetX(int index); + void SetX(int index, ulong value); + + V128 GetV(int index); + void SetV(int index, V128 value); + + public void Load(IHvExecutionContext context) + { + Pc = context.Pc; + ElrEl1 = context.ElrEl1; + EsrEl1 = context.EsrEl1; + TpidrEl0 = context.TpidrEl0; + TpidrroEl0 = context.TpidrroEl0; + Pstate = context.Pstate; + Fpcr = context.Fpcr; + Fpsr = context.Fpsr; + + for (int i = 0; i < 32; i++) + { + SetX(i, context.GetX(i)); + SetV(i, context.GetV(i)); + } + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/TimeApi.cs b/src/Ryujinx.Cpu/AppleHv/TimeApi.cs new file mode 100644 index 00000000..85bc7717 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/TimeApi.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Cpu.AppleHv +{ + struct MachTimebaseInfo + { + public uint Numer; + public uint Denom; + } + + [SupportedOSPlatform("macos")] + static partial class TimeApi + { + [LibraryImport("libc", SetLastError = true)] + public static partial ulong mach_absolute_time(); + + [LibraryImport("libc", SetLastError = true)] + public static partial int mach_timebase_info(out MachTimebaseInfo info); + } +} diff --git a/src/Ryujinx.Cpu/DummyDiskCacheLoadState.cs b/src/Ryujinx.Cpu/DummyDiskCacheLoadState.cs new file mode 100644 index 00000000..d050bdde --- /dev/null +++ b/src/Ryujinx.Cpu/DummyDiskCacheLoadState.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.Cpu +{ + public class DummyDiskCacheLoadState : IDiskCacheLoadState + { +#pragma warning disable CS0067 // The event is never used + /// + public event Action StateChanged; +#pragma warning restore CS0067 + + /// + public void Cancel() + { + } + } +} diff --git a/src/Ryujinx.Cpu/ExceptionCallbacks.cs b/src/Ryujinx.Cpu/ExceptionCallbacks.cs new file mode 100644 index 00000000..d9293302 --- /dev/null +++ b/src/Ryujinx.Cpu/ExceptionCallbacks.cs @@ -0,0 +1,64 @@ +namespace Ryujinx.Cpu +{ + /// + /// Exception callback without any additional arguments. + /// + /// Context for the thread where the exception was triggered + public delegate void ExceptionCallbackNoArgs(IExecutionContext context); + + /// + /// Exception callback. + /// + /// Context for the thread where the exception was triggered + /// Address of the instruction that caused the exception + /// Immediate value of the instruction that caused the exception, or for undefined instruction, the instruction itself + public delegate void ExceptionCallback(IExecutionContext context, ulong address, int imm); + + /// + /// Stores handlers for the various CPU exceptions. + /// + public readonly struct ExceptionCallbacks + { + /// + /// Handler for CPU interrupts triggered using . + /// + public readonly ExceptionCallbackNoArgs InterruptCallback; + + /// + /// Handler for CPU software interrupts caused by the Arm BRK instruction. + /// + public readonly ExceptionCallback BreakCallback; + + /// + /// Handler for CPU software interrupts caused by the Arm SVC instruction. + /// + public readonly ExceptionCallback SupervisorCallback; + + /// + /// Handler for CPU software interrupts caused by any undefined Arm instruction. + /// + public readonly ExceptionCallback UndefinedCallback; + + /// + /// Creates a new exception callbacks structure. + /// + /// + /// All handlers are optional, and if null, the CPU will just continue executing as if nothing happened. + /// + /// Handler for CPU interrupts triggered using + /// Handler for CPU software interrupts caused by the Arm BRK instruction + /// Handler for CPU software interrupts caused by the Arm SVC instruction + /// Handler for CPU software interrupts caused by any undefined Arm instruction + public ExceptionCallbacks( + ExceptionCallbackNoArgs interruptCallback = null, + ExceptionCallback breakCallback = null, + ExceptionCallback supervisorCallback = null, + ExceptionCallback undefinedCallback = null) + { + InterruptCallback = interruptCallback; + BreakCallback = breakCallback; + SupervisorCallback = supervisorCallback; + UndefinedCallback = undefinedCallback; + } + } +} diff --git a/src/Ryujinx.Cpu/ICpuContext.cs b/src/Ryujinx.Cpu/ICpuContext.cs new file mode 100644 index 00000000..edcebdfc --- /dev/null +++ b/src/Ryujinx.Cpu/ICpuContext.cs @@ -0,0 +1,63 @@ +using System; + +namespace Ryujinx.Cpu +{ + /// + /// CPU context interface. + /// + public interface ICpuContext : IDisposable + { + /// + /// Creates a new execution context that will store thread CPU register state when executing guest code. + /// + /// Optional functions to be called when the CPU receives an interrupt + /// Execution context + IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks); + + /// + /// Starts executing code at a specified entry point address. + /// + /// + /// This function only returns when the execution is stopped, by calling . + /// + /// Execution context to be used for this run + /// Entry point address + void Execute(IExecutionContext context, ulong address); + + /// + /// Invalidates the instruction cache for a given memory region. + /// + /// + /// This should be called if code is modified to make the CPU emulator aware of the modifications, + /// otherwise it might run stale code which will lead to errors and crashes. + /// Calling this function is not necessary if the code memory was modified by guest code, + /// as the expectation is that it will do it on its own using the appropriate cache invalidation instructions, + /// except on Arm32 where those instructions can't be used in unprivileged mode. + /// + /// Address of the region to be invalidated + /// Size of the region to be invalidated + void InvalidateCacheRegion(ulong address, ulong size); + + /// + /// Loads cached code from disk for a given application. + /// + /// + /// If the execution engine is recompiling guest code, this can be used to load cached code from disk. + /// + /// Title ID of the application in padded hex form + /// Version of the application + /// True if the cache should be loaded from disk if it exists, false otherwise + /// Disk cache load progress reporter and manager + IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled); + + /// + /// Indicates that code has been loaded into guest memory, and that it might be executed in the future. + /// + /// + /// Some execution engines might use this information to cache recompiled code on disk or to ensure it can be executed. + /// + /// CPU virtual address where the code starts + /// Size of the code range in bytes + void PrepareCodeRange(ulong address, ulong size); + } +} diff --git a/src/Ryujinx.Cpu/ICpuEngine.cs b/src/Ryujinx.Cpu/ICpuEngine.cs new file mode 100644 index 00000000..b53b23a8 --- /dev/null +++ b/src/Ryujinx.Cpu/ICpuEngine.cs @@ -0,0 +1,18 @@ +using ARMeilleure.Memory; + +namespace Ryujinx.Cpu +{ + /// + /// CPU execution engine interface. + /// + public interface ICpuEngine + { + /// + /// Creates a new CPU context that can be used to run code for multiple threads sharing an address space. + /// + /// Memory manager for the address space of the context + /// Indicates if the context will be used to run 64-bit or 32-bit Arm code + /// CPU context + ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit); + } +} diff --git a/src/Ryujinx.Cpu/IDiskCacheState.cs b/src/Ryujinx.Cpu/IDiskCacheState.cs new file mode 100644 index 00000000..61bbdf92 --- /dev/null +++ b/src/Ryujinx.Cpu/IDiskCacheState.cs @@ -0,0 +1,20 @@ +using System; + +namespace Ryujinx.Cpu +{ + /// + /// Disk cache load state report and management interface. + /// + public interface IDiskCacheLoadState + { + /// + /// Event used to report the cache load progress. + /// + event Action StateChanged; + + /// + /// Cancels the disk cache load process. + /// + void Cancel(); + } +} diff --git a/src/Ryujinx.Cpu/IExecutionContext.cs b/src/Ryujinx.Cpu/IExecutionContext.cs new file mode 100644 index 00000000..c3821080 --- /dev/null +++ b/src/Ryujinx.Cpu/IExecutionContext.cs @@ -0,0 +1,112 @@ +using ARMeilleure.State; +using System; + +namespace Ryujinx.Cpu +{ + /// + /// CPU register state interface. + /// + public interface IExecutionContext : IDisposable + { + /// + /// Current Program Counter. + /// + /// + /// In some implementations, this value might not be accurate and might not point to the last instruction executed. + /// + ulong Pc { get; } + + /// + /// Thread ID Register (EL0). + /// + long TpidrEl0 { get; set; } + + /// + /// Thread ID Register (read-only) (EL0). + /// + long TpidrroEl0 { get; set; } + + /// + /// Processor State Register. + /// + uint Pstate { get; set; } + + /// + /// Floating-point Control Register. + /// + uint Fpcr { get; set; } + + /// + /// Floating-point Status Register. + /// + uint Fpsr { get; set; } + + /// + /// Indicates whenever the CPU is running 64-bit (AArch64 mode) or 32-bit (AArch32 mode) code. + /// + bool IsAarch32 { get; set; } + + /// + /// Indicates whenever the CPU is still running code. + /// + /// + /// Even if this is false, the guest code might be still exiting. + /// One must not assume that the code is no longer running from this property alone. + /// + bool Running { get; } + + /// + /// Gets the value of a general purpose register. + /// + /// + /// The special of 31 can be used to access the SP (Stack Pointer) register. + /// + /// Index of the register, in the range 0-31 (inclusive) + /// The register value + ulong GetX(int index); + + /// + /// Sets the value of a general purpose register. + /// + /// + /// The special of 31 can be used to access the SP (Stack Pointer) register. + /// + /// Index of the register, in the range 0-31 (inclusive) + /// Value to be set + void SetX(int index, ulong value); + + /// + /// Gets the value of a FP/SIMD register. + /// + /// Index of the register, in the range 0-31 (inclusive) + /// The register value + V128 GetV(int index); + + /// + /// Sets the value of a FP/SIMD register. + /// + /// Index of the register, in the range 0-31 (inclusive) + /// Value to be set + void SetV(int index, V128 value); + + /// + /// Requests the thread to stop running temporarily and call . + /// + /// + /// The thread might not pause immediately. + /// One must not assume that guest code is no longer being executed by the thread after calling this function. + /// + void RequestInterrupt(); + + /// + /// Requests the thread to stop running guest code and return as soon as possible. + /// + /// + /// The thread might not stop immediately. + /// One must not assume that guest code is no longer being executed by the thread after calling this function. + /// After a thread has been stopped, it can't be restarted with the same . + /// If you only need to pause the thread temporarily, use instead. + /// + void StopRunning(); + } +} diff --git a/src/Ryujinx.Cpu/ITickSource.cs b/src/Ryujinx.Cpu/ITickSource.cs new file mode 100644 index 00000000..e65e99e2 --- /dev/null +++ b/src/Ryujinx.Cpu/ITickSource.cs @@ -0,0 +1,31 @@ +using ARMeilleure.State; +using System; + +namespace Ryujinx.Cpu +{ + /// + /// Tick source interface. + /// + public interface ITickSource : ICounter + { + /// + /// Time elapsed since the counter was created. + /// + TimeSpan ElapsedTime { get; } + + /// + /// Time elapsed since the counter was created, in seconds. + /// + double ElapsedSeconds { get; } + + /// + /// Stops counting. + /// + void Suspend(); + + /// + /// Resumes counting after a call to . + /// + void Resume(); + } +} diff --git a/src/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs b/src/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs new file mode 100644 index 00000000..e8d11ede --- /dev/null +++ b/src/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs @@ -0,0 +1,57 @@ +using Ryujinx.Memory; +using Ryujinx.Memory.Tracking; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Cpu +{ + public interface IVirtualMemoryManagerTracked : IVirtualMemoryManager + { + /// + /// Reads data from CPU mapped memory, with read tracking + /// + /// Type of the data being read + /// Virtual address of the data in memory + /// The data + T ReadTracked(ulong va) where T : unmanaged; + + /// + /// Writes data to CPU mapped memory, without write tracking. + /// + /// Virtual address to write the data into + /// Data to be written + void WriteUntracked(ulong va, ReadOnlySpan data); + + /// + /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with. + /// + /// CPU virtual address of the region + /// Size of the region + /// Handle ID + /// Region flags + /// The memory tracking handle + RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None); + + /// + /// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with. + /// + /// CPU virtual address of the region + /// Size of the region + /// Handles to inherit state from or reuse. When none are present, provide null + /// Desired granularity of write tracking + /// Handle ID + /// Region flags + /// The memory tracking handle + MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None); + + /// + /// Obtains a smart memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with. + /// + /// CPU virtual address of the region + /// Size of the region + /// Desired granularity of write tracking + /// Handle ID + /// The memory tracking handle + SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id); + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressIntrusiveRedBlackTree.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressIntrusiveRedBlackTree.cs new file mode 100644 index 00000000..0e244330 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressIntrusiveRedBlackTree.cs @@ -0,0 +1,35 @@ +using Ryujinx.Common.Collections; +using System; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + internal class AddressIntrusiveRedBlackTree : IntrusiveRedBlackTree where T : IntrusiveRedBlackTreeNode, IComparable, IComparable + { + /// + /// Retrieve the node that is considered equal to the specified address by the comparator. + /// + /// Address to compare with + /// Node that is equal to + public T GetNode(ulong address) + { + T node = Root; + while (node != null) + { + int cmp = node.CompareTo(address); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + return node; + } + } + return null; + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartition.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartition.cs new file mode 100644 index 00000000..224c5edc --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartition.cs @@ -0,0 +1,708 @@ +using Ryujinx.Common; +using Ryujinx.Common.Collections; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Threading; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + readonly struct PrivateRange + { + public readonly MemoryBlock Memory; + public readonly ulong Offset; + public readonly ulong Size; + + public static PrivateRange Empty => new(null, 0, 0); + + public PrivateRange(MemoryBlock memory, ulong offset, ulong size) + { + Memory = memory; + Offset = offset; + Size = size; + } + } + + class AddressSpacePartition : IDisposable + { + public const ulong GuestPageSize = 0x1000; + + private const int DefaultBlockAlignment = 1 << 20; + + private enum MappingType : byte + { + None, + Private, + } + + private class Mapping : IntrusiveRedBlackTreeNode, IComparable, IComparable + { + public ulong Address { get; private set; } + public ulong Size { get; private set; } + public ulong EndAddress => Address + Size; + public MappingType Type { get; private set; } + + public Mapping(ulong address, ulong size, MappingType type) + { + Address = address; + Size = size; + Type = type; + } + + public Mapping Split(ulong splitAddress) + { + ulong leftSize = splitAddress - Address; + ulong rightSize = EndAddress - splitAddress; + + Mapping left = new(Address, leftSize, Type); + + Address = splitAddress; + Size = rightSize; + + return left; + } + + public void UpdateState(MappingType newType) + { + Type = newType; + } + + public void Extend(ulong sizeDelta) + { + Size += sizeDelta; + } + + public int CompareTo(Mapping other) + { + if (Address < other.Address) + { + return -1; + } + else if (Address <= other.EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + + public int CompareTo(ulong address) + { + if (address < Address) + { + return -1; + } + else if (address <= EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + } + + private class PrivateMapping : IntrusiveRedBlackTreeNode, IComparable, IComparable + { + public ulong Address { get; private set; } + public ulong Size { get; private set; } + public ulong EndAddress => Address + Size; + public PrivateMemoryAllocation PrivateAllocation { get; private set; } + + public PrivateMapping(ulong address, ulong size, PrivateMemoryAllocation privateAllocation) + { + Address = address; + Size = size; + PrivateAllocation = privateAllocation; + } + + public PrivateMapping Split(ulong splitAddress) + { + ulong leftSize = splitAddress - Address; + ulong rightSize = EndAddress - splitAddress; + + Debug.Assert(leftSize > 0); + Debug.Assert(rightSize > 0); + + (var leftAllocation, PrivateAllocation) = PrivateAllocation.Split(leftSize); + + PrivateMapping left = new(Address, leftSize, leftAllocation); + + Address = splitAddress; + Size = rightSize; + + return left; + } + + public void Map(AddressSpacePartitionMultiAllocation baseBlock, ulong baseAddress, PrivateMemoryAllocation newAllocation) + { + baseBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address - baseAddress, Size); + PrivateAllocation = newAllocation; + } + + public void Unmap(AddressSpacePartitionMultiAllocation baseBlock, ulong baseAddress) + { + if (PrivateAllocation.IsValid) + { + baseBlock.UnmapView(PrivateAllocation.Memory, Address - baseAddress, Size); + PrivateAllocation.Dispose(); + } + + PrivateAllocation = default; + } + + public void Extend(ulong sizeDelta) + { + Size += sizeDelta; + } + + public int CompareTo(PrivateMapping other) + { + if (Address < other.Address) + { + return -1; + } + else if (Address <= other.EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + + public int CompareTo(ulong address) + { + if (address < Address) + { + return -1; + } + else if (address <= EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + } + + private readonly MemoryBlock _backingMemory; + private readonly AddressSpacePartitionMultiAllocation _baseMemory; + private readonly PrivateMemoryAllocator _privateMemoryAllocator; + + private readonly AddressIntrusiveRedBlackTree _mappingTree; + private readonly AddressIntrusiveRedBlackTree _privateTree; + + private readonly ReaderWriterLockSlim _treeLock; + + private readonly ulong _hostPageSize; + + private ulong? _firstPagePa; + private ulong? _lastPagePa; + private ulong _cachedFirstPagePa; + private ulong _cachedLastPagePa; + private MemoryBlock _firstPageMemoryForUnmap; + private ulong _firstPageOffsetForLateMap; + private MemoryPermission _firstPageMemoryProtection; + + public ulong Address { get; } + public ulong Size { get; } + public ulong EndAddress => Address + Size; + + public AddressSpacePartition(AddressSpacePartitionAllocation baseMemory, MemoryBlock backingMemory, ulong address, ulong size) + { + _privateMemoryAllocator = new PrivateMemoryAllocator(DefaultBlockAlignment, MemoryAllocationFlags.Mirrorable); + _mappingTree = new AddressIntrusiveRedBlackTree(); + _privateTree = new AddressIntrusiveRedBlackTree(); + _treeLock = new ReaderWriterLockSlim(); + + _mappingTree.Add(new Mapping(address, size, MappingType.None)); + _privateTree.Add(new PrivateMapping(address, size, default)); + + _hostPageSize = MemoryBlock.GetPageSize(); + + _backingMemory = backingMemory; + _baseMemory = new(baseMemory); + + _cachedFirstPagePa = ulong.MaxValue; + _cachedLastPagePa = ulong.MaxValue; + + Address = address; + Size = size; + } + + public bool IsEmpty() + { + _treeLock.EnterReadLock(); + + try + { + Mapping map = _mappingTree.GetNode(Address); + + return map != null && map.Address == Address && map.Size == Size && map.Type == MappingType.None; + } + finally + { + _treeLock.ExitReadLock(); + } + } + + public void Map(ulong va, ulong pa, ulong size) + { + Debug.Assert(va >= Address); + Debug.Assert(va + size <= EndAddress); + + if (va == Address) + { + _firstPagePa = pa; + } + + if (va <= EndAddress - GuestPageSize && va + size > EndAddress - GuestPageSize) + { + _lastPagePa = pa + ((EndAddress - GuestPageSize) - va); + } + + Update(va, pa, size, MappingType.Private); + } + + public void Unmap(ulong va, ulong size) + { + Debug.Assert(va >= Address); + Debug.Assert(va + size <= EndAddress); + + if (va == Address) + { + _firstPagePa = null; + } + + if (va <= EndAddress - GuestPageSize && va + size > EndAddress - GuestPageSize) + { + _lastPagePa = null; + } + + Update(va, 0UL, size, MappingType.None); + } + + public void ReprotectAligned(ulong va, ulong size, MemoryPermission protection) + { + Debug.Assert(va >= Address); + Debug.Assert(va + size <= EndAddress); + + _baseMemory.Reprotect(va - Address, size, protection, false); + + if (va == Address) + { + _firstPageMemoryProtection = protection; + } + } + + public void Reprotect( + ulong va, + ulong size, + MemoryPermission protection, + AddressSpacePartitioned addressSpace, + Action updatePtCallback) + { + if (_baseMemory.LazyInitMirrorForProtection(addressSpace, Address, Size, protection)) + { + LateMap(); + } + + updatePtCallback(va, _baseMemory.GetPointerForProtection(va - Address, size, protection), size); + } + + public IntPtr GetPointer(ulong va, ulong size) + { + Debug.Assert(va >= Address); + Debug.Assert(va + size <= EndAddress); + + return _baseMemory.GetPointer(va - Address, size); + } + + public void InsertBridgeAtEnd(AddressSpacePartition partitionAfter, bool useProtectionMirrors) + { + ulong firstPagePa = partitionAfter?._firstPagePa ?? ulong.MaxValue; + ulong lastPagePa = _lastPagePa ?? ulong.MaxValue; + + if (firstPagePa != _cachedFirstPagePa || lastPagePa != _cachedLastPagePa) + { + if (partitionAfter != null && partitionAfter._firstPagePa.HasValue) + { + (MemoryBlock firstPageMemory, ulong firstPageOffset) = partitionAfter.GetFirstPageMemoryAndOffset(); + + _baseMemory.MapView(firstPageMemory, firstPageOffset, Size, _hostPageSize); + + if (!useProtectionMirrors) + { + _baseMemory.Reprotect(Size, _hostPageSize, partitionAfter._firstPageMemoryProtection, throwOnFail: false); + } + + _firstPageMemoryForUnmap = firstPageMemory; + _firstPageOffsetForLateMap = firstPageOffset; + } + else + { + MemoryBlock firstPageMemoryForUnmap = _firstPageMemoryForUnmap; + + if (firstPageMemoryForUnmap != null) + { + _baseMemory.UnmapView(firstPageMemoryForUnmap, Size, _hostPageSize); + _firstPageMemoryForUnmap = null; + } + } + + _cachedFirstPagePa = firstPagePa; + _cachedLastPagePa = lastPagePa; + } + } + + public void ReprotectBridge(MemoryPermission protection) + { + if (_firstPageMemoryForUnmap != null) + { + _baseMemory.Reprotect(Size, _hostPageSize, protection, throwOnFail: false); + } + } + + private (MemoryBlock, ulong) GetFirstPageMemoryAndOffset() + { + _treeLock.EnterReadLock(); + + try + { + PrivateMapping map = _privateTree.GetNode(Address); + + if (map != null && map.PrivateAllocation.IsValid) + { + return (map.PrivateAllocation.Memory, map.PrivateAllocation.Offset + (Address - map.Address)); + } + } + finally + { + _treeLock.ExitReadLock(); + } + + return (_backingMemory, _firstPagePa.Value); + } + + public PrivateRange GetPrivateAllocation(ulong va) + { + _treeLock.EnterReadLock(); + + try + { + PrivateMapping map = _privateTree.GetNode(va); + + if (map != null && map.PrivateAllocation.IsValid) + { + return new(map.PrivateAllocation.Memory, map.PrivateAllocation.Offset + (va - map.Address), map.Size - (va - map.Address)); + } + } + finally + { + _treeLock.ExitReadLock(); + } + + return PrivateRange.Empty; + } + + private void Update(ulong va, ulong pa, ulong size, MappingType type) + { + _treeLock.EnterWriteLock(); + + try + { + Mapping map = _mappingTree.GetNode(va); + + Update(map, va, pa, size, type); + } + finally + { + _treeLock.ExitWriteLock(); + } + } + + private Mapping Update(Mapping map, ulong va, ulong pa, ulong size, MappingType type) + { + ulong endAddress = va + size; + + for (; map != null; map = map.Successor) + { + if (map.Address < va) + { + _mappingTree.Add(map.Split(va)); + } + + if (map.EndAddress > endAddress) + { + Mapping newMap = map.Split(endAddress); + _mappingTree.Add(newMap); + map = newMap; + } + + switch (type) + { + case MappingType.None: + ulong alignment = _hostPageSize; + + bool unmappedBefore = map.Predecessor == null || + (map.Predecessor.Type == MappingType.None && map.Predecessor.Address <= BitUtils.AlignDown(va, alignment)); + + bool unmappedAfter = map.Successor == null || + (map.Successor.Type == MappingType.None && map.Successor.EndAddress >= BitUtils.AlignUp(endAddress, alignment)); + + UnmapPrivate(va, size, unmappedBefore, unmappedAfter); + break; + case MappingType.Private: + MapPrivate(va, size); + break; + } + + map.UpdateState(type); + map = TryCoalesce(map); + + if (map.EndAddress >= endAddress) + { + break; + } + } + + return map; + } + + private Mapping TryCoalesce(Mapping map) + { + Mapping previousMap = map.Predecessor; + Mapping nextMap = map.Successor; + + if (previousMap != null && CanCoalesce(previousMap, map)) + { + previousMap.Extend(map.Size); + _mappingTree.Remove(map); + map = previousMap; + } + + if (nextMap != null && CanCoalesce(map, nextMap)) + { + map.Extend(nextMap.Size); + _mappingTree.Remove(nextMap); + } + + return map; + } + + private static bool CanCoalesce(Mapping left, Mapping right) + { + return left.Type == right.Type; + } + + private void MapPrivate(ulong va, ulong size) + { + ulong endAddress = va + size; + + ulong alignment = _hostPageSize; + + // Expand the range outwards based on page size to ensure that at least the requested region is mapped. + ulong vaAligned = BitUtils.AlignDown(va, alignment); + ulong endAddressAligned = BitUtils.AlignUp(endAddress, alignment); + + PrivateMapping map = _privateTree.GetNode(va); + + for (; map != null; map = map.Successor) + { + if (!map.PrivateAllocation.IsValid) + { + if (map.Address < vaAligned) + { + _privateTree.Add(map.Split(vaAligned)); + } + + if (map.EndAddress > endAddressAligned) + { + PrivateMapping newMap = map.Split(endAddressAligned); + _privateTree.Add(newMap); + map = newMap; + } + + map.Map(_baseMemory, Address, _privateMemoryAllocator.Allocate(map.Size, _hostPageSize)); + } + + if (map.EndAddress >= endAddressAligned) + { + break; + } + } + } + + private void UnmapPrivate(ulong va, ulong size, bool unmappedBefore, bool unmappedAfter) + { + ulong endAddress = va + size; + + ulong alignment = _hostPageSize; + + // If the adjacent mappings are unmapped, expand the range outwards, + // otherwise shrink it inwards. We must ensure we won't unmap pages that might still be in use. + ulong vaAligned = unmappedBefore ? BitUtils.AlignDown(va, alignment) : BitUtils.AlignUp(va, alignment); + ulong endAddressAligned = unmappedAfter ? BitUtils.AlignUp(endAddress, alignment) : BitUtils.AlignDown(endAddress, alignment); + + if (endAddressAligned <= vaAligned) + { + return; + } + + PrivateMapping map = _privateTree.GetNode(vaAligned); + + for (; map != null; map = map.Successor) + { + if (map.PrivateAllocation.IsValid) + { + if (map.Address < vaAligned) + { + _privateTree.Add(map.Split(vaAligned)); + } + + if (map.EndAddress > endAddressAligned) + { + PrivateMapping newMap = map.Split(endAddressAligned); + _privateTree.Add(newMap); + map = newMap; + } + + map.Unmap(_baseMemory, Address); + map = TryCoalesce(map); + } + + if (map.EndAddress >= endAddressAligned) + { + break; + } + } + } + + private PrivateMapping TryCoalesce(PrivateMapping map) + { + PrivateMapping previousMap = map.Predecessor; + PrivateMapping nextMap = map.Successor; + + if (previousMap != null && CanCoalesce(previousMap, map)) + { + previousMap.Extend(map.Size); + _privateTree.Remove(map); + map = previousMap; + } + + if (nextMap != null && CanCoalesce(map, nextMap)) + { + map.Extend(nextMap.Size); + _privateTree.Remove(nextMap); + } + + return map; + } + + private static bool CanCoalesce(PrivateMapping left, PrivateMapping right) + { + return !left.PrivateAllocation.IsValid && !right.PrivateAllocation.IsValid; + } + + private void LateMap() + { + // Map all existing private allocations. + // This is necessary to ensure mirrors that are lazily created have the same mappings as the main one. + + PrivateMapping map = _privateTree.GetNode(Address); + + for (; map != null; map = map.Successor) + { + if (map.PrivateAllocation.IsValid) + { + _baseMemory.LateMapView(map.PrivateAllocation.Memory, map.PrivateAllocation.Offset, map.Address - Address, map.Size); + } + } + + MemoryBlock firstPageMemory = _firstPageMemoryForUnmap; + ulong firstPageOffset = _firstPageOffsetForLateMap; + + if (firstPageMemory != null) + { + _baseMemory.LateMapView(firstPageMemory, firstPageOffset, Size, _hostPageSize); + } + } + + public PrivateRange GetFirstPrivateAllocation(ulong va, ulong size, out ulong nextVa) + { + _treeLock.EnterReadLock(); + + try + { + PrivateMapping map = _privateTree.GetNode(va); + + nextVa = map.EndAddress; + + if (map != null && map.PrivateAllocation.IsValid) + { + ulong startOffset = va - map.Address; + + return new( + map.PrivateAllocation.Memory, + map.PrivateAllocation.Offset + startOffset, + Math.Min(map.PrivateAllocation.Size - startOffset, size)); + } + } + finally + { + _treeLock.ExitReadLock(); + } + + return PrivateRange.Empty; + } + + public bool HasPrivateAllocation(ulong va, ulong size, ulong startVa, ulong startSize, ref PrivateRange range) + { + ulong endVa = va + size; + + _treeLock.EnterReadLock(); + + try + { + for (PrivateMapping map = _privateTree.GetNode(va); map != null && map.Address < endVa; map = map.Successor) + { + if (map.PrivateAllocation.IsValid) + { + if (map.Address <= startVa && map.EndAddress >= startVa + startSize) + { + ulong startOffset = startVa - map.Address; + + range = new( + map.PrivateAllocation.Memory, + map.PrivateAllocation.Offset + startOffset, + Math.Min(map.PrivateAllocation.Size - startOffset, startSize)); + } + + return true; + } + } + } + finally + { + _treeLock.ExitReadLock(); + } + + return false; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + _privateMemoryAllocator.Dispose(); + _baseMemory.Dispose(); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionAllocator.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionAllocator.cs new file mode 100644 index 00000000..44dedb64 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionAllocator.cs @@ -0,0 +1,202 @@ +using Ryujinx.Common; +using Ryujinx.Common.Collections; +using Ryujinx.Memory; +using Ryujinx.Memory.Tracking; +using System; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + readonly struct AddressSpacePartitionAllocation : IDisposable + { + private readonly AddressSpacePartitionAllocator _owner; + private readonly PrivateMemoryAllocatorImpl.Allocation _allocation; + + public IntPtr Pointer => (IntPtr)((ulong)_allocation.Block.Memory.Pointer + _allocation.Offset); + + public bool IsValid => _owner != null; + + public AddressSpacePartitionAllocation( + AddressSpacePartitionAllocator owner, + PrivateMemoryAllocatorImpl.Allocation allocation) + { + _owner = owner; + _allocation = allocation; + } + + public void RegisterMapping(ulong va, ulong endVa) + { + _allocation.Block.AddMapping(_allocation.Offset, _allocation.Size, va, endVa); + } + + public void MapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size) + { + _allocation.Block.Memory.MapView(srcBlock, srcOffset, _allocation.Offset + dstOffset, size); + } + + public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size) + { + _allocation.Block.Memory.UnmapView(srcBlock, _allocation.Offset + offset, size); + } + + public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail) + { + _allocation.Block.Memory.Reprotect(_allocation.Offset + offset, size, permission, throwOnFail); + } + + public IntPtr GetPointer(ulong offset, ulong size) + { + return _allocation.Block.Memory.GetPointer(_allocation.Offset + offset, size); + } + + public void Dispose() + { + _allocation.Block.RemoveMapping(_allocation.Offset, _allocation.Size); + _owner.Free(_allocation.Block, _allocation.Offset, _allocation.Size); + } + } + + class AddressSpacePartitionAllocator : PrivateMemoryAllocatorImpl + { + private const ulong DefaultBlockAlignment = 1UL << 32; // 4GB + + public class Block : PrivateMemoryAllocator.Block + { + private readonly MemoryTracking _tracking; + private readonly Func _readPtCallback; + private readonly MemoryEhMeilleure _memoryEh; + + private class Mapping : IntrusiveRedBlackTreeNode, IComparable, IComparable + { + public ulong Address { get; } + public ulong Size { get; } + public ulong EndAddress => Address + Size; + public ulong Va { get; } + public ulong EndVa { get; } + + public Mapping(ulong address, ulong size, ulong va, ulong endVa) + { + Address = address; + Size = size; + Va = va; + EndVa = endVa; + } + + public int CompareTo(Mapping other) + { + if (Address < other.Address) + { + return -1; + } + else if (Address <= other.EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + + public int CompareTo(ulong address) + { + if (address < Address) + { + return -1; + } + else if (address <= EndAddress - 1UL) + { + return 0; + } + else + { + return 1; + } + } + } + + private readonly AddressIntrusiveRedBlackTree _mappingTree; + private readonly object _lock; + + public Block(MemoryTracking tracking, Func readPtCallback, MemoryBlock memory, ulong size, object locker) : base(memory, size) + { + _tracking = tracking; + _readPtCallback = readPtCallback; + _memoryEh = new(memory, null, tracking, VirtualMemoryEvent); + _mappingTree = new(); + _lock = locker; + } + + public void AddMapping(ulong offset, ulong size, ulong va, ulong endVa) + { + _mappingTree.Add(new(offset, size, va, endVa)); + } + + public void RemoveMapping(ulong offset, ulong size) + { + _mappingTree.Remove(_mappingTree.GetNode(offset)); + } + + private ulong VirtualMemoryEvent(ulong address, ulong size, bool write) + { + Mapping map; + + lock (_lock) + { + map = _mappingTree.GetNode(address); + } + + if (map == null) + { + return 0; + } + + address -= map.Address; + + ulong addressAligned = BitUtils.AlignDown(address, AddressSpacePartition.GuestPageSize); + ulong endAddressAligned = BitUtils.AlignUp(address + size, AddressSpacePartition.GuestPageSize); + ulong sizeAligned = endAddressAligned - addressAligned; + + if (!_tracking.VirtualMemoryEvent(map.Va + addressAligned, sizeAligned, write)) + { + return 0; + } + + return _readPtCallback(map.Va + address); + } + + public override void Destroy() + { + _memoryEh.Dispose(); + + base.Destroy(); + } + } + + private readonly MemoryTracking _tracking; + private readonly Func _readPtCallback; + private readonly object _lock; + + public AddressSpacePartitionAllocator( + MemoryTracking tracking, + Func readPtCallback, + object locker) : base(DefaultBlockAlignment, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible) + { + _tracking = tracking; + _readPtCallback = readPtCallback; + _lock = locker; + } + + public AddressSpacePartitionAllocation Allocate(ulong va, ulong size) + { + AddressSpacePartitionAllocation allocation = new(this, Allocate(size, MemoryBlock.GetPageSize(), CreateBlock)); + allocation.RegisterMapping(va, va + size); + + return allocation; + } + + private Block CreateBlock(MemoryBlock memory, ulong size) + { + return new Block(_tracking, _readPtCallback, memory, size, _lock); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionMultiAllocation.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionMultiAllocation.cs new file mode 100644 index 00000000..3b065583 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitionMultiAllocation.cs @@ -0,0 +1,101 @@ +using Ryujinx.Memory; +using System; +using System.Diagnostics; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + class AddressSpacePartitionMultiAllocation : IDisposable + { + private readonly AddressSpacePartitionAllocation _baseMemory; + private AddressSpacePartitionAllocation _baseMemoryRo; + private AddressSpacePartitionAllocation _baseMemoryNone; + + public AddressSpacePartitionMultiAllocation(AddressSpacePartitionAllocation baseMemory) + { + _baseMemory = baseMemory; + } + + public void MapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size) + { + _baseMemory.MapView(srcBlock, srcOffset, dstOffset, size); + + if (_baseMemoryRo.IsValid) + { + _baseMemoryRo.MapView(srcBlock, srcOffset, dstOffset, size); + _baseMemoryRo.Reprotect(dstOffset, size, MemoryPermission.Read, false); + } + } + + public void LateMapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size) + { + _baseMemoryRo.MapView(srcBlock, srcOffset, dstOffset, size); + _baseMemoryRo.Reprotect(dstOffset, size, MemoryPermission.Read, false); + } + + public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size) + { + _baseMemory.UnmapView(srcBlock, offset, size); + + if (_baseMemoryRo.IsValid) + { + _baseMemoryRo.UnmapView(srcBlock, offset, size); + } + } + + public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail) + { + _baseMemory.Reprotect(offset, size, permission, throwOnFail); + } + + public IntPtr GetPointer(ulong offset, ulong size) + { + return _baseMemory.GetPointer(offset, size); + } + + public bool LazyInitMirrorForProtection(AddressSpacePartitioned addressSpace, ulong blockAddress, ulong blockSize, MemoryPermission permission) + { + if (permission == MemoryPermission.None && !_baseMemoryNone.IsValid) + { + _baseMemoryNone = addressSpace.CreateAsPartitionAllocation(blockAddress, blockSize); + } + else if (permission == MemoryPermission.Read && !_baseMemoryRo.IsValid) + { + _baseMemoryRo = addressSpace.CreateAsPartitionAllocation(blockAddress, blockSize); + + return true; + } + + return false; + } + + public IntPtr GetPointerForProtection(ulong offset, ulong size, MemoryPermission permission) + { + AddressSpacePartitionAllocation allocation = permission switch + { + MemoryPermission.ReadAndWrite => _baseMemory, + MemoryPermission.Read => _baseMemoryRo, + MemoryPermission.None => _baseMemoryNone, + _ => throw new ArgumentException($"Invalid protection \"{permission}\"."), + }; + + Debug.Assert(allocation.IsValid); + + return allocation.GetPointer(offset, size); + } + + public void Dispose() + { + _baseMemory.Dispose(); + + if (_baseMemoryRo.IsValid) + { + _baseMemoryRo.Dispose(); + } + + if (_baseMemoryNone.IsValid) + { + _baseMemoryNone.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitioned.cs b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitioned.cs new file mode 100644 index 00000000..2cf2c248 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/AddressSpacePartitioned.cs @@ -0,0 +1,407 @@ +using Ryujinx.Common; +using Ryujinx.Memory; +using Ryujinx.Memory.Tracking; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + class AddressSpacePartitioned : IDisposable + { + private const int PartitionBits = 25; + private const ulong PartitionSize = 1UL << PartitionBits; + + private readonly MemoryBlock _backingMemory; + private readonly List _partitions; + private readonly AddressSpacePartitionAllocator _asAllocator; + private readonly Action _updatePtCallback; + private readonly bool _useProtectionMirrors; + + public AddressSpacePartitioned(MemoryTracking tracking, MemoryBlock backingMemory, NativePageTable nativePageTable, bool useProtectionMirrors) + { + _backingMemory = backingMemory; + _partitions = new(); + _asAllocator = new(tracking, nativePageTable.Read, _partitions); + _updatePtCallback = nativePageTable.Update; + _useProtectionMirrors = useProtectionMirrors; + } + + public void Map(ulong va, ulong pa, ulong size) + { + ulong endVa = va + size; + + lock (_partitions) + { + EnsurePartitionsLocked(va, size); + + while (va < endVa) + { + int partitionIndex = FindPartitionIndexLocked(va); + AddressSpacePartition partition = _partitions[partitionIndex]; + + (ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa); + + partition.Map(clampedVa, pa, clampedEndVa - clampedVa); + + ulong currentSize = clampedEndVa - clampedVa; + + va += currentSize; + pa += currentSize; + + InsertOrRemoveBridgeIfNeeded(partitionIndex); + } + } + } + + public void Unmap(ulong va, ulong size) + { + ulong endVa = va + size; + + while (va < endVa) + { + AddressSpacePartition partition; + + lock (_partitions) + { + int partitionIndex = FindPartitionIndexLocked(va); + if (partitionIndex < 0) + { + va += PartitionSize - (va & (PartitionSize - 1)); + + continue; + } + + partition = _partitions[partitionIndex]; + + (ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa); + + partition.Unmap(clampedVa, clampedEndVa - clampedVa); + + va += clampedEndVa - clampedVa; + + InsertOrRemoveBridgeIfNeeded(partitionIndex); + + if (partition.IsEmpty()) + { + _partitions.Remove(partition); + partition.Dispose(); + } + } + } + } + + public void Reprotect(ulong va, ulong size, MemoryPermission protection) + { + ulong endVa = va + size; + + lock (_partitions) + { + while (va < endVa) + { + AddressSpacePartition partition = FindPartitionWithIndex(va, out int partitionIndex); + + if (partition == null) + { + va += PartitionSize - (va & (PartitionSize - 1)); + + continue; + } + + (ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa); + + if (_useProtectionMirrors) + { + partition.Reprotect(clampedVa, clampedEndVa - clampedVa, protection, this, _updatePtCallback); + } + else + { + partition.ReprotectAligned(clampedVa, clampedEndVa - clampedVa, protection); + + if (clampedVa == partition.Address && + partitionIndex > 0 && + _partitions[partitionIndex - 1].EndAddress == partition.Address) + { + _partitions[partitionIndex - 1].ReprotectBridge(protection); + } + } + + va += clampedEndVa - clampedVa; + } + } + } + + public PrivateRange GetPrivateAllocation(ulong va) + { + AddressSpacePartition partition = FindPartition(va); + + if (partition == null) + { + return PrivateRange.Empty; + } + + return partition.GetPrivateAllocation(va); + } + + public PrivateRange GetFirstPrivateAllocation(ulong va, ulong size, out ulong nextVa) + { + AddressSpacePartition partition = FindPartition(va); + + if (partition == null) + { + nextVa = (va & ~(PartitionSize - 1)) + PartitionSize; + + return PrivateRange.Empty; + } + + return partition.GetFirstPrivateAllocation(va, size, out nextVa); + } + + public bool HasAnyPrivateAllocation(ulong va, ulong size, out PrivateRange range) + { + range = PrivateRange.Empty; + + ulong startVa = va; + ulong endVa = va + size; + + while (va < endVa) + { + AddressSpacePartition partition = FindPartition(va); + + if (partition == null) + { + va += PartitionSize - (va & (PartitionSize - 1)); + + continue; + } + + (ulong clampedVa, ulong clampedEndVa) = ClampRange(partition, va, endVa); + + if (partition.HasPrivateAllocation(clampedVa, clampedEndVa - clampedVa, startVa, size, ref range)) + { + return true; + } + + va += clampedEndVa - clampedVa; + } + + return false; + } + + private void InsertOrRemoveBridgeIfNeeded(int partitionIndex) + { + if (partitionIndex > 0) + { + if (_partitions[partitionIndex - 1].EndAddress == _partitions[partitionIndex].Address) + { + _partitions[partitionIndex - 1].InsertBridgeAtEnd(_partitions[partitionIndex], _useProtectionMirrors); + } + else + { + _partitions[partitionIndex - 1].InsertBridgeAtEnd(null, _useProtectionMirrors); + } + } + + if (partitionIndex + 1 < _partitions.Count && _partitions[partitionIndex].EndAddress == _partitions[partitionIndex + 1].Address) + { + _partitions[partitionIndex].InsertBridgeAtEnd(_partitions[partitionIndex + 1], _useProtectionMirrors); + } + else + { + _partitions[partitionIndex].InsertBridgeAtEnd(null, _useProtectionMirrors); + } + } + + public IntPtr GetPointer(ulong va, ulong size) + { + AddressSpacePartition partition = FindPartition(va); + + return partition.GetPointer(va, size); + } + + private static (ulong, ulong) ClampRange(AddressSpacePartition partition, ulong va, ulong endVa) + { + if (va < partition.Address) + { + va = partition.Address; + } + + if (endVa > partition.EndAddress) + { + endVa = partition.EndAddress; + } + + return (va, endVa); + } + + private AddressSpacePartition FindPartition(ulong va) + { + lock (_partitions) + { + int index = FindPartitionIndexLocked(va); + if (index >= 0) + { + return _partitions[index]; + } + } + + return null; + } + + private AddressSpacePartition FindPartitionWithIndex(ulong va, out int index) + { + lock (_partitions) + { + index = FindPartitionIndexLocked(va); + if (index >= 0) + { + return _partitions[index]; + } + } + + return null; + } + + private int FindPartitionIndexLocked(ulong va) + { + int left = 0; + int middle; + int right = _partitions.Count - 1; + + while (left <= right) + { + middle = left + ((right - left) >> 1); + + AddressSpacePartition partition = _partitions[middle]; + + if (partition.Address <= va && partition.EndAddress > va) + { + return middle; + } + + if (partition.Address >= va) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return -1; + } + + private void EnsurePartitionsLocked(ulong va, ulong size) + { + ulong endVa = BitUtils.AlignUp(va + size, PartitionSize); + va = BitUtils.AlignDown(va, PartitionSize); + + for (int i = 0; i < _partitions.Count && va < endVa; i++) + { + AddressSpacePartition partition = _partitions[i]; + + if (partition.Address <= va && partition.EndAddress > va) + { + if (partition.EndAddress >= endVa) + { + // Fully mapped already. + va = endVa; + + break; + } + + ulong gapSize; + + if (i + 1 < _partitions.Count) + { + AddressSpacePartition nextPartition = _partitions[i + 1]; + + if (partition.EndAddress == nextPartition.Address) + { + va = partition.EndAddress; + + continue; + } + + gapSize = Math.Min(endVa, nextPartition.Address) - partition.EndAddress; + } + else + { + gapSize = endVa - partition.EndAddress; + } + + _partitions.Insert(i + 1, CreateAsPartition(partition.EndAddress, gapSize)); + va = partition.EndAddress + gapSize; + i++; + } + else if (partition.EndAddress > va) + { + Debug.Assert(partition.Address > va); + + ulong gapSize; + + if (partition.Address < endVa) + { + gapSize = partition.Address - va; + } + else + { + gapSize = endVa - va; + } + + _partitions.Insert(i, CreateAsPartition(va, gapSize)); + va = Math.Min(partition.EndAddress, endVa); + i++; + } + } + + if (va < endVa) + { + _partitions.Add(CreateAsPartition(va, endVa - va)); + } + + ValidatePartitionList(); + } + + [Conditional("DEBUG")] + private void ValidatePartitionList() + { + for (int i = 1; i < _partitions.Count; i++) + { + Debug.Assert(_partitions[i].Address > _partitions[i - 1].Address); + Debug.Assert(_partitions[i].EndAddress > _partitions[i - 1].EndAddress); + } + } + + private AddressSpacePartition CreateAsPartition(ulong va, ulong size) + { + return new(CreateAsPartitionAllocation(va, size), _backingMemory, va, size); + } + + public AddressSpacePartitionAllocation CreateAsPartitionAllocation(ulong va, ulong size) + { + return _asAllocator.Allocate(va, size + MemoryBlock.GetPageSize()); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (AddressSpacePartition partition in _partitions) + { + partition.Dispose(); + } + + _partitions.Clear(); + _asAllocator.Dispose(); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/HostTracked/NativePageTable.cs b/src/Ryujinx.Cpu/Jit/HostTracked/NativePageTable.cs new file mode 100644 index 00000000..e3174e3f --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/HostTracked/NativePageTable.cs @@ -0,0 +1,223 @@ +using Ryujinx.Cpu.Signal; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.Jit.HostTracked +{ + sealed class NativePageTable : IDisposable + { + private delegate ulong TrackingEventDelegate(ulong address, ulong size, bool write); + + private const int PageBits = 12; + private const int PageSize = 1 << PageBits; + private const int PageMask = PageSize - 1; + + private const int PteSize = 8; + + private readonly int _bitsPerPtPage; + private readonly int _entriesPerPtPage; + private readonly int _pageCommitmentBits; + + private readonly PageTable _pageTable; + private readonly MemoryBlock _nativePageTable; + private readonly ulong[] _pageCommitmentBitmap; + private readonly ulong _hostPageSize; + + private readonly TrackingEventDelegate _trackingEvent; + + private bool _disposed; + + public IntPtr PageTablePointer => _nativePageTable.Pointer; + + public NativePageTable(ulong asSize) + { + ulong hostPageSize = MemoryBlock.GetPageSize(); + + _entriesPerPtPage = (int)(hostPageSize / sizeof(ulong)); + _bitsPerPtPage = BitOperations.Log2((uint)_entriesPerPtPage); + _pageCommitmentBits = PageBits + _bitsPerPtPage; + + _hostPageSize = hostPageSize; + _pageTable = new PageTable(); + _nativePageTable = new MemoryBlock((asSize / PageSize) * PteSize + _hostPageSize, MemoryAllocationFlags.Reserve); + _pageCommitmentBitmap = new ulong[(asSize >> _pageCommitmentBits) / (sizeof(ulong) * 8)]; + + ulong ptStart = (ulong)_nativePageTable.Pointer; + ulong ptEnd = ptStart + _nativePageTable.Size; + + _trackingEvent = VirtualMemoryEvent; + + bool added = NativeSignalHandler.AddTrackedRegion((nuint)ptStart, (nuint)ptEnd, Marshal.GetFunctionPointerForDelegate(_trackingEvent)); + + if (!added) + { + throw new InvalidOperationException("Number of allowed tracked regions exceeded."); + } + } + + public void Map(ulong va, ulong pa, ulong size, AddressSpacePartitioned addressSpace, MemoryBlock backingMemory, bool privateMap) + { + while (size != 0) + { + _pageTable.Map(va, pa); + + EnsureCommitment(va); + + if (privateMap) + { + _nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, addressSpace.GetPointer(va, PageSize))); + } + else + { + _nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, backingMemory.GetPointer(pa, PageSize))); + } + + va += PageSize; + pa += PageSize; + size -= PageSize; + } + } + + public void Unmap(ulong va, ulong size) + { + IntPtr guardPagePtr = GetGuardPagePointer(); + + while (size != 0) + { + _pageTable.Unmap(va); + _nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, guardPagePtr)); + + va += PageSize; + size -= PageSize; + } + } + + public ulong Read(ulong va) + { + ulong pte = _nativePageTable.Read((va / PageSize) * PteSize); + + pte += va & ~(ulong)PageMask; + + return pte + (va & PageMask); + } + + public void Update(ulong va, IntPtr ptr, ulong size) + { + ulong remainingSize = size; + + while (remainingSize != 0) + { + EnsureCommitment(va); + + _nativePageTable.Write((va / PageSize) * PteSize, GetPte(va, ptr)); + + va += PageSize; + ptr += PageSize; + remainingSize -= PageSize; + } + } + + private void EnsureCommitment(ulong va) + { + ulong bit = va >> _pageCommitmentBits; + + int index = (int)(bit / (sizeof(ulong) * 8)); + int shift = (int)(bit % (sizeof(ulong) * 8)); + + ulong mask = 1UL << shift; + + ulong oldMask = _pageCommitmentBitmap[index]; + + if ((oldMask & mask) == 0) + { + lock (_pageCommitmentBitmap) + { + oldMask = _pageCommitmentBitmap[index]; + + if ((oldMask & mask) != 0) + { + return; + } + + _nativePageTable.Commit(bit * _hostPageSize, _hostPageSize); + + Span pageSpan = MemoryMarshal.Cast(_nativePageTable.GetSpan(bit * _hostPageSize, (int)_hostPageSize)); + + Debug.Assert(pageSpan.Length == _entriesPerPtPage); + + IntPtr guardPagePtr = GetGuardPagePointer(); + + for (int i = 0; i < pageSpan.Length; i++) + { + pageSpan[i] = GetPte((bit << _pageCommitmentBits) | ((ulong)i * PageSize), guardPagePtr); + } + + _pageCommitmentBitmap[index] = oldMask | mask; + } + } + } + + private IntPtr GetGuardPagePointer() + { + return _nativePageTable.GetPointer(_nativePageTable.Size - _hostPageSize, _hostPageSize); + } + + private static ulong GetPte(ulong va, IntPtr ptr) + { + Debug.Assert((va & PageMask) == 0); + + return (ulong)ptr - va; + } + + public ulong GetPhysicalAddress(ulong va) + { + return _pageTable.Read(va) + (va & PageMask); + } + + private ulong VirtualMemoryEvent(ulong address, ulong size, bool write) + { + if (address < _nativePageTable.Size - _hostPageSize) + { + // Some prefetch instructions do not cause faults with invalid addresses. + // Retry if we are hitting a case where the page table is unmapped, the next + // run will execute the actual instruction. + // The address loaded from the page table will be invalid, and it should hit the else case + // if the instruction faults on unmapped or protected memory. + + ulong va = address * (PageSize / sizeof(ulong)); + + EnsureCommitment(va); + + return (ulong)_nativePageTable.Pointer + address; + } + else + { + throw new InvalidMemoryRegionException(); + } + } + + private void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + NativeSignalHandler.RemoveTrackedRegion((nuint)_nativePageTable.Pointer); + + _nativePageTable.Dispose(); + } + + _disposed = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/JitCpuContext.cs b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs new file mode 100644 index 00000000..9893c59b --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/JitCpuContext.cs @@ -0,0 +1,65 @@ +using ARMeilleure.Memory; +using ARMeilleure.Translation; +using Ryujinx.Cpu.Signal; +using Ryujinx.Memory; + +namespace Ryujinx.Cpu.Jit +{ + class JitCpuContext : ICpuContext + { + private readonly ITickSource _tickSource; + private readonly Translator _translator; + + public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit) + { + _tickSource = tickSource; + _translator = new Translator(new JitMemoryAllocator(forJit: true), memory, for64Bit); + + if (memory.Type.IsHostMappedOrTracked()) + { + NativeSignalHandler.InitializeSignalHandler(); + } + + memory.UnmapEvent += UnmapHandler; + } + + private void UnmapHandler(ulong address, ulong size) + { + _translator.InvalidateJitCacheRegion(address, size); + } + + /// + public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks) + { + return new JitExecutionContext(new JitMemoryAllocator(), _tickSource, exceptionCallbacks); + } + + /// + public void Execute(IExecutionContext context, ulong address) + { + _translator.Execute(((JitExecutionContext)context).Impl, address); + } + + /// + public void InvalidateCacheRegion(ulong address, ulong size) + { + _translator.InvalidateJitCacheRegion(address, size); + } + + /// + public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled) + { + return new JitDiskCacheLoadState(_translator.LoadDiskCache(titleIdText, displayVersion, enabled)); + } + + /// + public void PrepareCodeRange(ulong address, ulong size) + { + _translator.PrepareCodeRange(address, size); + } + + public void Dispose() + { + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/JitDiskCacheLoadState.cs b/src/Ryujinx.Cpu/Jit/JitDiskCacheLoadState.cs new file mode 100644 index 00000000..08534599 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/JitDiskCacheLoadState.cs @@ -0,0 +1,38 @@ +using ARMeilleure.Translation.PTC; +using System; + +namespace Ryujinx.Cpu.Jit +{ + public class JitDiskCacheLoadState : IDiskCacheLoadState + { + /// + public event Action StateChanged; + + private readonly IPtcLoadState _loadState; + + public JitDiskCacheLoadState(IPtcLoadState loadState) + { + loadState.PtcStateChanged += LoadStateChanged; + _loadState = loadState; + } + + private void LoadStateChanged(PtcLoadingState newState, int current, int total) + { + LoadState state = newState switch + { + PtcLoadingState.Start => LoadState.Unloaded, + PtcLoadingState.Loading => LoadState.Loading, + PtcLoadingState.Loaded => LoadState.Loaded, + _ => throw new ArgumentException($"Invalid load state \"{newState}\"."), + }; + + StateChanged?.Invoke(state, current, total); + } + + /// + public void Cancel() + { + _loadState.Continue(); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/JitEngine.cs b/src/Ryujinx.Cpu/Jit/JitEngine.cs new file mode 100644 index 00000000..deebb8b9 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/JitEngine.cs @@ -0,0 +1,20 @@ +using ARMeilleure.Memory; + +namespace Ryujinx.Cpu.Jit +{ + public class JitEngine : ICpuEngine + { + private readonly ITickSource _tickSource; + + public JitEngine(ITickSource tickSource) + { + _tickSource = tickSource; + } + + /// + public ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit) + { + return new JitCpuContext(_tickSource, memoryManager, for64Bit); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs new file mode 100644 index 00000000..f15486e6 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/JitExecutionContext.cs @@ -0,0 +1,123 @@ +using ARMeilleure.Memory; +using ARMeilleure.State; + +namespace Ryujinx.Cpu.Jit +{ + class JitExecutionContext : IExecutionContext + { + private readonly ExecutionContext _impl; + internal ExecutionContext Impl => _impl; + + /// + public ulong Pc => _impl.Pc; + + /// + public long TpidrEl0 + { + get => _impl.TpidrEl0; + set => _impl.TpidrEl0 = value; + } + + /// + public long TpidrroEl0 + { + get => _impl.TpidrroEl0; + set => _impl.TpidrroEl0 = value; + } + + /// + public uint Pstate + { + get => _impl.Pstate; + set => _impl.Pstate = value; + } + + /// + public uint Fpcr + { + get => (uint)_impl.Fpcr; + set => _impl.Fpcr = (FPCR)value; + } + + /// + public uint Fpsr + { + get => (uint)_impl.Fpsr; + set => _impl.Fpsr = (FPSR)value; + } + + /// + public bool IsAarch32 + { + get => _impl.IsAarch32; + set => _impl.IsAarch32 = value; + } + + /// + public bool Running => _impl.Running; + + private readonly ExceptionCallbacks _exceptionCallbacks; + + public JitExecutionContext(IJitMemoryAllocator allocator, ICounter counter, ExceptionCallbacks exceptionCallbacks) + { + _impl = new ExecutionContext( + allocator, + counter, + InterruptHandler, + BreakHandler, + SupervisorCallHandler, + UndefinedHandler); + + _exceptionCallbacks = exceptionCallbacks; + } + + /// + public ulong GetX(int index) => _impl.GetX(index); + + /// + public void SetX(int index, ulong value) => _impl.SetX(index, value); + + /// + public V128 GetV(int index) => _impl.GetV(index); + + /// + public void SetV(int index, V128 value) => _impl.SetV(index, value); + + private void InterruptHandler(ExecutionContext context) + { + _exceptionCallbacks.InterruptCallback?.Invoke(this); + } + + private void BreakHandler(ExecutionContext context, ulong address, int imm) + { + _exceptionCallbacks.BreakCallback?.Invoke(this, address, imm); + } + + private void SupervisorCallHandler(ExecutionContext context, ulong address, int imm) + { + _exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm); + } + + private void UndefinedHandler(ExecutionContext context, ulong address, int opCode) + { + _exceptionCallbacks.UndefinedCallback?.Invoke(this, address, opCode); + } + + /// + public void RequestInterrupt() + { + _impl.RequestInterrupt(); + } + + /// + public void StopRunning() + { + _impl.StopRunning(); + } + + public void Dispose() + { + _impl.Dispose(); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/JitMemoryAllocator.cs b/src/Ryujinx.Cpu/Jit/JitMemoryAllocator.cs new file mode 100644 index 00000000..06c11b7b --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/JitMemoryAllocator.cs @@ -0,0 +1,18 @@ +using ARMeilleure.Memory; +using Ryujinx.Memory; + +namespace Ryujinx.Cpu.Jit +{ + public class JitMemoryAllocator : IJitMemoryAllocator + { + private readonly MemoryAllocationFlags _jitFlag; + + public JitMemoryAllocator(bool forJit = false) + { + _jitFlag = forJit ? MemoryAllocationFlags.Jit : MemoryAllocationFlags.None; + } + + public IJitMemoryBlock Allocate(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.None); + public IJitMemoryBlock Reserve(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.Reserve | _jitFlag); + } +} diff --git a/src/Ryujinx.Cpu/Jit/JitMemoryBlock.cs b/src/Ryujinx.Cpu/Jit/JitMemoryBlock.cs new file mode 100644 index 00000000..bd07d349 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/JitMemoryBlock.cs @@ -0,0 +1,29 @@ +using ARMeilleure.Memory; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.Cpu.Jit +{ + public class JitMemoryBlock : IJitMemoryBlock + { + private readonly MemoryBlock _impl; + + public IntPtr Pointer => _impl.Pointer; + + public JitMemoryBlock(ulong size, MemoryAllocationFlags flags) + { + _impl = new MemoryBlock(size, flags); + } + + public void Commit(ulong offset, ulong size) => _impl.Commit(offset, size); + public void MapAsRw(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadAndWrite); + public void MapAsRx(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadAndExecute); + public void MapAsRwx(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadWriteExecute); + + public void Dispose() + { + GC.SuppressFinalize(this); + _impl.Dispose(); + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/MemoryManager.cs b/src/Ryujinx.Cpu/Jit/MemoryManager.cs new file mode 100644 index 00000000..6f594ec2 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/MemoryManager.cs @@ -0,0 +1,512 @@ +using ARMeilleure.Memory; +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using Ryujinx.Memory.Tracking; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Cpu.Jit +{ + /// + /// Represents a CPU memory manager. + /// + public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked + { + private const int PteSize = 8; + + private const int PointerTagBit = 62; + + private readonly MemoryBlock _backingMemory; + private readonly InvalidAccessHandler _invalidAccessHandler; + + /// + public bool UsesPrivateAllocations => false; + + /// + /// Address space width in bits. + /// + public int AddressSpaceBits { get; } + + private readonly MemoryBlock _pageTable; + + private readonly ManagedPageFlags _pages; + + /// + /// Page table base pointer. + /// + public IntPtr PageTablePointer => _pageTable.Pointer; + + public MemoryManagerType Type => MemoryManagerType.SoftwarePageTable; + + public MemoryTracking Tracking { get; } + + public event Action UnmapEvent; + + protected override ulong AddressSpaceSize { get; } + + /// + /// Creates a new instance of the memory manager. + /// + /// Physical backing memory where virtual memory will be mapped to + /// Size of the address space + /// Optional function to handle invalid memory accesses + public MemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null) + { + _backingMemory = backingMemory; + _invalidAccessHandler = invalidAccessHandler; + + ulong asSize = PageSize; + int asBits = PageBits; + + while (asSize < addressSpaceSize) + { + asSize <<= 1; + asBits++; + } + + AddressSpaceBits = asBits; + AddressSpaceSize = asSize; + _pageTable = new MemoryBlock((asSize / PageSize) * PteSize); + + _pages = new ManagedPageFlags(AddressSpaceBits); + + Tracking = new MemoryTracking(this, PageSize); + } + + /// + public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) + { + AssertValidAddressAndSize(va, size); + + ulong remainingSize = size; + ulong oVa = va; + while (remainingSize != 0) + { + _pageTable.Write((va / PageSize) * PteSize, PaToPte(pa)); + + va += PageSize; + pa += PageSize; + remainingSize -= PageSize; + } + + _pages.AddMapping(oVa, size); + Tracking.Map(oVa, size); + } + + /// + public void Unmap(ulong va, ulong size) + { + // If size is 0, there's nothing to unmap, just exit early. + if (size == 0) + { + return; + } + + AssertValidAddressAndSize(va, size); + + UnmapEvent?.Invoke(va, size); + Tracking.Unmap(va, size); + _pages.RemoveMapping(va, size); + + ulong remainingSize = size; + while (remainingSize != 0) + { + _pageTable.Write((va / PageSize) * PteSize, 0UL); + + va += PageSize; + remainingSize -= PageSize; + } + } + + public override T ReadTracked(ulong va) + { + try + { + return base.ReadTracked(va); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return default; + } + } + + /// + public T ReadGuest(ulong va) where T : unmanaged + { + try + { + SignalMemoryTrackingImpl(va, (ulong)Unsafe.SizeOf(), false, true); + + return Read(va); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return default; + } + } + + /// + public override void Read(ulong va, Span data) + { + try + { + base.Read(va, data); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + public override void Write(ulong va, ReadOnlySpan data) + { + try + { + base.Write(va, data); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + /// + public void WriteGuest(ulong va, T value) where T : unmanaged + { + Span data = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1)); + + SignalMemoryTrackingImpl(va, (ulong)data.Length, true, true); + + Write(va, data); + } + + public override void WriteUntracked(ulong va, ReadOnlySpan data) + { + try + { + base.WriteUntracked(va, data); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + public override ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + try + { + return base.GetReadOnlySequence(va, size, tracked); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return ReadOnlySequence.Empty; + } + } + + public ref T GetRef(ulong va) where T : unmanaged + { + if (!IsContiguous(va, Unsafe.SizeOf())) + { + ThrowMemoryNotContiguous(); + } + + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), true); + + return ref _backingMemory.GetRef(GetPhysicalAddressInternal(va)); + } + + /// + public IEnumerable GetHostRegions(ulong va, ulong size) + { + if (size == 0) + { + return Enumerable.Empty(); + } + + var guestRegions = GetPhysicalRegionsImpl(va, size); + if (guestRegions == null) + { + return null; + } + + var regions = new HostMemoryRange[guestRegions.Count]; + + for (int i = 0; i < regions.Length; i++) + { + var guestRegion = guestRegions[i]; + IntPtr pointer = _backingMemory.GetPointer(guestRegion.Address, guestRegion.Size); + regions[i] = new HostMemoryRange((nuint)(ulong)pointer, guestRegion.Size); + } + + return regions; + } + + /// + public IEnumerable GetPhysicalRegions(ulong va, ulong size) + { + if (size == 0) + { + return Enumerable.Empty(); + } + + return GetPhysicalRegionsImpl(va, size); + } + + private List GetPhysicalRegionsImpl(ulong va, ulong size) + { + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) + { + return null; + } + + int pages = GetPagesCount(va, (uint)size, out va); + + var regions = new List(); + + ulong regionStart = GetPhysicalAddressInternal(va); + ulong regionSize = PageSize; + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return null; + } + + ulong newPa = GetPhysicalAddressInternal(va + PageSize); + + if (GetPhysicalAddressInternal(va) + PageSize != newPa) + { + regions.Add(new MemoryRange(regionStart, regionSize)); + regionStart = newPa; + regionSize = 0; + } + + va += PageSize; + regionSize += PageSize; + } + + regions.Add(new MemoryRange(regionStart, regionSize)); + + return regions; + } + + /// + public bool IsRangeMapped(ulong va, ulong size) + { + if (size == 0UL) + { + return true; + } + + if (!ValidateAddressAndSize(va, size)) + { + return false; + } + + int pages = GetPagesCount(va, (uint)size, out va); + + for (int page = 0; page < pages; page++) + { + if (!IsMapped(va)) + { + return false; + } + + va += PageSize; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool IsMapped(ulong va) + { + if (!ValidateAddress(va)) + { + return false; + } + + return _pageTable.Read((va / PageSize) * PteSize) != 0; + } + + private nuint GetPhysicalAddressInternal(ulong va) + { + return (nuint)(PteToPa(_pageTable.Read((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask)); + } + + /// + public void Reprotect(ulong va, ulong size, MemoryPermission protection) + { + // TODO + } + + /// + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) + { + AssertValidAddressAndSize(va, size); + + if (guest) + { + // Protection is inverted on software pages, since the default value is 0. + protection = (~protection) & MemoryPermission.ReadAndWrite; + + long tag = protection switch + { + MemoryPermission.None => 0L, + MemoryPermission.Write => 2L << PointerTagBit, + _ => 3L << PointerTagBit, + }; + + int pages = GetPagesCount(va, (uint)size, out va); + ulong pageStart = va >> PageBits; + long invTagMask = ~(0xffffL << 48); + + for (int page = 0; page < pages; page++) + { + ref long pageRef = ref _pageTable.GetRef(pageStart * PteSize); + + long pte; + + do + { + pte = Volatile.Read(ref pageRef); + } + while (pte != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte); + + pageStart++; + } + } + else + { + _pages.TrackingReprotect(va, size, protection); + } + } + + /// + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) + { + return Tracking.BeginTracking(address, size, id, flags); + } + + /// + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) + { + return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags); + } + + /// + public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id) + { + return Tracking.BeginSmartGranularTracking(address, size, granularity, id); + } + + private void SignalMemoryTrackingImpl(ulong va, ulong size, bool write, bool guest, bool precise = false, int? exemptId = null) + { + AssertValidAddressAndSize(va, size); + + if (precise) + { + Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId); + return; + } + + // If the memory tracking is coming from the guest, use the tag bits in the page table entry. + // Otherwise, use the managed page flags. + + if (guest) + { + // We emulate guard pages for software memory access. This makes for an easy transition to + // tracking using host guard pages in future, but also supporting platforms where this is not possible. + + // Write tag includes read protection, since we don't have any read actions that aren't performed before write too. + long tag = (write ? 3L : 1L) << PointerTagBit; + + int pages = GetPagesCount(va, (uint)size, out _); + ulong pageStart = va >> PageBits; + + for (int page = 0; page < pages; page++) + { + ref long pageRef = ref _pageTable.GetRef(pageStart * PteSize); + + long pte = Volatile.Read(ref pageRef); + + if ((pte & tag) != 0) + { + Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId, true); + break; + } + + pageStart++; + } + } + else + { + _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); + } + } + + /// + public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + { + SignalMemoryTrackingImpl(va, size, write, false, precise, exemptId); + } + + private ulong PaToPte(ulong pa) + { + return (ulong)_backingMemory.GetPointer(pa, PageSize); + } + + private ulong PteToPa(ulong pte) + { + return (ulong)((long)pte - _backingMemory.Pointer.ToInt64()); + } + + /// + /// Disposes of resources used by the memory manager. + /// + protected override void Destroy() => _pageTable.Dispose(); + + protected override Memory GetPhysicalAddressMemory(nuint pa, int size) + => _backingMemory.GetMemory(pa, size); + + protected override Span GetPhysicalAddressSpan(nuint pa, int size) + => _backingMemory.GetSpan(pa, size); + + protected override nuint TranslateVirtualAddressChecked(ulong va) + => GetPhysicalAddressInternal(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) + => GetPhysicalAddressInternal(va); + } +} diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs new file mode 100644 index 00000000..4639ab91 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs @@ -0,0 +1,462 @@ +using ARMeilleure.Memory; +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using Ryujinx.Memory.Tracking; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Cpu.Jit +{ + /// + /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. + /// + public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked + { + private readonly InvalidAccessHandler _invalidAccessHandler; + private readonly bool _unsafeMode; + + private readonly AddressSpace _addressSpace; + + private readonly PageTable _pageTable; + + private readonly MemoryEhMeilleure _memoryEh; + + private readonly ManagedPageFlags _pages; + + /// + public bool UsesPrivateAllocations => false; + + public int AddressSpaceBits { get; } + + public IntPtr PageTablePointer => _addressSpace.Base.Pointer; + + public MemoryManagerType Type => _unsafeMode ? MemoryManagerType.HostMappedUnsafe : MemoryManagerType.HostMapped; + + public MemoryTracking Tracking { get; } + + public event Action UnmapEvent; + + protected override ulong AddressSpaceSize { get; } + + /// + /// Creates a new instance of the host mapped memory manager. + /// + /// Address space instance to use + /// True if unmanaged access should not be masked (unsafe), false otherwise. + /// Optional function to handle invalid memory accesses + public MemoryManagerHostMapped(AddressSpace addressSpace, bool unsafeMode, InvalidAccessHandler invalidAccessHandler) + { + _addressSpace = addressSpace; + _pageTable = new PageTable(); + _invalidAccessHandler = invalidAccessHandler; + _unsafeMode = unsafeMode; + AddressSpaceSize = addressSpace.AddressSpaceSize; + + ulong asSize = PageSize; + int asBits = PageBits; + + while (asSize < AddressSpaceSize) + { + asSize <<= 1; + asBits++; + } + + AddressSpaceBits = asBits; + + _pages = new ManagedPageFlags(AddressSpaceBits); + + Tracking = new MemoryTracking(this, (int)MemoryBlock.GetPageSize(), invalidAccessHandler); + _memoryEh = new MemoryEhMeilleure(_addressSpace.Base, _addressSpace.Mirror, Tracking); + } + + /// + /// Ensures the combination of virtual address and size is part of the addressable space and fully mapped. + /// + /// Virtual address of the range + /// Size of the range in bytes + private void AssertMapped(ulong va, ulong size) + { + if (!ValidateAddressAndSize(va, size) || !_pages.IsRangeMapped(va, size)) + { + throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); + } + } + + /// + public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) + { + AssertValidAddressAndSize(va, size); + + _addressSpace.Map(va, pa, size, flags); + _pages.AddMapping(va, size); + PtMap(va, pa, size); + + Tracking.Map(va, size); + } + + /// + public void Unmap(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + UnmapEvent?.Invoke(va, size); + Tracking.Unmap(va, size); + + _pages.RemoveMapping(va, size); + PtUnmap(va, size); + _addressSpace.Unmap(va, size); + } + + private void PtMap(ulong va, ulong pa, ulong size) + { + while (size != 0) + { + _pageTable.Map(va, pa); + + va += PageSize; + pa += PageSize; + size -= PageSize; + } + } + + private void PtUnmap(ulong va, ulong size) + { + while (size != 0) + { + _pageTable.Unmap(va); + + va += PageSize; + size -= PageSize; + } + } + + public override T Read(ulong va) + { + try + { + AssertMapped(va, (ulong)Unsafe.SizeOf()); + + return _addressSpace.Mirror.Read(va); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return default; + } + } + + public override T ReadTracked(ulong va) + { + try + { + return base.ReadTracked(va); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return default; + } + } + + public override void Read(ulong va, Span data) + { + try + { + AssertMapped(va, (ulong)data.Length); + + _addressSpace.Mirror.Read(va, data); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + public override void Write(ulong va, T value) + { + try + { + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), write: true); + + _addressSpace.Mirror.Write(va, value); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + public override void Write(ulong va, ReadOnlySpan data) + { + try + { + SignalMemoryTracking(va, (ulong)data.Length, write: true); + + _addressSpace.Mirror.Write(va, data); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + public override void WriteUntracked(ulong va, ReadOnlySpan data) + { + try + { + AssertMapped(va, (ulong)data.Length); + + _addressSpace.Mirror.Write(va, data); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + public override bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + { + try + { + SignalMemoryTracking(va, (ulong)data.Length, false); + + Span target = _addressSpace.Mirror.GetSpan(va, data.Length); + bool changed = !data.SequenceEqual(target); + + if (changed) + { + data.CopyTo(target); + } + + return changed; + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return true; + } + } + + public override ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, write: false); + } + else + { + AssertMapped(va, (ulong)size); + } + + return new ReadOnlySequence(_addressSpace.Mirror.GetMemory(va, size)); + } + + public override ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + { + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, write: false); + } + else + { + AssertMapped(va, (ulong)size); + } + + return _addressSpace.Mirror.GetSpan(va, size); + } + + public override WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + { + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, true); + } + else + { + AssertMapped(va, (ulong)size); + } + + return _addressSpace.Mirror.GetWritableRegion(va, size); + } + + public ref T GetRef(ulong va) where T : unmanaged + { + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), true); + + return ref _addressSpace.Mirror.GetRef(va); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool IsMapped(ulong va) + { + return ValidateAddress(va) && _pages.IsMapped(va); + } + + /// + public bool IsRangeMapped(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + return _pages.IsRangeMapped(va, size); + } + + /// + public IEnumerable GetHostRegions(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + return Enumerable.Repeat(new HostMemoryRange((nuint)(ulong)_addressSpace.Mirror.GetPointer(va, size), size), 1); + } + + /// + public IEnumerable GetPhysicalRegions(ulong va, ulong size) + { + int pages = GetPagesCount(va, (uint)size, out va); + + var regions = new List(); + + ulong regionStart = GetPhysicalAddressChecked(va); + ulong regionSize = PageSize; + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return null; + } + + ulong newPa = GetPhysicalAddressChecked(va + PageSize); + + if (GetPhysicalAddressChecked(va) + PageSize != newPa) + { + regions.Add(new MemoryRange(regionStart, regionSize)); + regionStart = newPa; + regionSize = 0; + } + + va += PageSize; + regionSize += PageSize; + } + + regions.Add(new MemoryRange(regionStart, regionSize)); + + return regions; + } + + private ulong GetPhysicalAddressChecked(ulong va) + { + if (!IsMapped(va)) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}"); + } + + return GetPhysicalAddressInternal(va); + } + + private ulong GetPhysicalAddressInternal(ulong va) + { + return _pageTable.Read(va) + (va & PageMask); + } + + /// + /// This function also validates that the given range is both valid and mapped, and will throw if it is not. + /// + public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + { + AssertValidAddressAndSize(va, size); + + if (precise) + { + Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId); + return; + } + + _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); + } + + /// + public void Reprotect(ulong va, ulong size, MemoryPermission protection) + { + // TODO + } + + /// + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) + { + if (guest) + { + _addressSpace.Base.Reprotect(va, size, protection, false); + } + else + { + _pages.TrackingReprotect(va, size, protection); + } + } + + /// + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) + { + return Tracking.BeginTracking(address, size, id, flags); + } + + /// + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) + { + return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags); + } + + /// + public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id) + { + return Tracking.BeginSmartGranularTracking(address, size, granularity, id); + } + + /// + /// Disposes of resources used by the memory manager. + /// + protected override void Destroy() + { + _addressSpace.Dispose(); + _memoryEh.Dispose(); + } + + protected override Memory GetPhysicalAddressMemory(nuint pa, int size) + => _addressSpace.Mirror.GetMemory(pa, size); + + protected override Span GetPhysicalAddressSpan(nuint pa, int size) + => _addressSpace.Mirror.GetSpan(pa, size); + + protected override nuint TranslateVirtualAddressChecked(ulong va) + => (nuint)GetPhysicalAddressChecked(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) + => (nuint)GetPhysicalAddressInternal(va); + } +} diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs new file mode 100644 index 00000000..501109b8 --- /dev/null +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs @@ -0,0 +1,634 @@ +using ARMeilleure.Memory; +using Ryujinx.Common.Memory; +using Ryujinx.Cpu.Jit.HostTracked; +using Ryujinx.Cpu.Signal; +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using Ryujinx.Memory.Tracking; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Cpu.Jit +{ + /// + /// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region. + /// + public sealed class MemoryManagerHostTracked : VirtualMemoryManagerRefCountedBase, IMemoryManager, IVirtualMemoryManagerTracked + { + private readonly InvalidAccessHandler _invalidAccessHandler; + private readonly bool _unsafeMode; + + private readonly MemoryBlock _backingMemory; + + public int AddressSpaceBits { get; } + + public MemoryTracking Tracking { get; } + + private readonly NativePageTable _nativePageTable; + private readonly AddressSpacePartitioned _addressSpace; + + private readonly ManagedPageFlags _pages; + + protected override ulong AddressSpaceSize { get; } + + /// + public bool UsesPrivateAllocations => true; + + public IntPtr PageTablePointer => _nativePageTable.PageTablePointer; + + public MemoryManagerType Type => _unsafeMode ? MemoryManagerType.HostTrackedUnsafe : MemoryManagerType.HostTracked; + + public event Action UnmapEvent; + + /// + /// Creates a new instance of the host tracked memory manager. + /// + /// Physical backing memory where virtual memory will be mapped to + /// Size of the address space + /// True if unmanaged access should not be masked (unsafe), false otherwise. + /// Optional function to handle invalid memory accesses + public MemoryManagerHostTracked(MemoryBlock backingMemory, ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler) + { + bool useProtectionMirrors = MemoryBlock.GetPageSize() > PageSize; + + Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler, useProtectionMirrors); + + _backingMemory = backingMemory; + _invalidAccessHandler = invalidAccessHandler; + _unsafeMode = unsafeMode; + AddressSpaceSize = addressSpaceSize; + + ulong asSize = PageSize; + int asBits = PageBits; + + while (asSize < AddressSpaceSize) + { + asSize <<= 1; + asBits++; + } + + AddressSpaceBits = asBits; + + if (useProtectionMirrors && !NativeSignalHandler.SupportsFaultAddressPatching()) + { + // Currently we require being able to change the fault address to something else + // in order to "emulate" 4KB granularity protection on systems with larger page size. + + throw new PlatformNotSupportedException(); + } + + _pages = new ManagedPageFlags(asBits); + _nativePageTable = new(asSize); + _addressSpace = new(Tracking, backingMemory, _nativePageTable, useProtectionMirrors); + } + + public override ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySequence.Empty; + } + + try + { + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, false); + } + else + { + AssertValidAddressAndSize(va, (ulong)size); + } + + ulong endVa = va + (ulong)size; + int offset = 0; + + BytesReadOnlySequenceSegment first = null, last = null; + + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(size - offset)); + + Memory physicalMemory = memory.GetMemory(rangeOffset, (int)copySize); + + if (first is null) + { + first = last = new BytesReadOnlySequenceSegment(physicalMemory); + } + else + { + if (last.IsContiguousWith(physicalMemory, out nuint contiguousStart, out int contiguousSize)) + { + Memory contiguousPhysicalMemory = new NativeMemoryManager(contiguousStart, contiguousSize).Memory; + + last.Replace(contiguousPhysicalMemory); + } + else + { + last = last.Append(physicalMemory); + } + } + + va += copySize; + offset += (int)copySize; + } + + return new ReadOnlySequence(first, 0, last, (int)(size - last.RunningIndex)); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return ReadOnlySequence.Empty; + } + } + + /// + public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) + { + AssertValidAddressAndSize(va, size); + + if (flags.HasFlag(MemoryMapFlags.Private)) + { + _addressSpace.Map(va, pa, size); + } + + _pages.AddMapping(va, size); + _nativePageTable.Map(va, pa, size, _addressSpace, _backingMemory, flags.HasFlag(MemoryMapFlags.Private)); + + Tracking.Map(va, size); + } + + /// + public void Unmap(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + _addressSpace.Unmap(va, size); + + UnmapEvent?.Invoke(va, size); + Tracking.Unmap(va, size); + + _pages.RemoveMapping(va, size); + _nativePageTable.Unmap(va, size); + } + + public override T ReadTracked(ulong va) + { + try + { + return base.ReadTracked(va); + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + + return default; + } + } + + public override void Read(ulong va, Span data) + { + if (data.Length == 0) + { + return; + } + + try + { + AssertValidAddressAndSize(va, (ulong)data.Length); + + ulong endVa = va + (ulong)data.Length; + int offset = 0; + + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset)); + + memory.GetSpan(rangeOffset, (int)copySize).CopyTo(data.Slice(offset, (int)copySize)); + + va += copySize; + offset += (int)copySize; + } + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + public override bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return false; + } + + SignalMemoryTracking(va, (ulong)data.Length, false); + + if (TryGetVirtualContiguous(va, data.Length, out MemoryBlock memoryBlock, out ulong offset)) + { + var target = memoryBlock.GetSpan(offset, data.Length); + + bool changed = !data.SequenceEqual(target); + + if (changed) + { + data.CopyTo(target); + } + + return changed; + } + else + { + WriteImpl(va, data); + + return true; + } + } + + public override ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySpan.Empty; + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, false); + } + + if (TryGetVirtualContiguous(va, size, out MemoryBlock memoryBlock, out ulong offset)) + { + return memoryBlock.GetSpan(offset, size); + } + else + { + Span data = new byte[size]; + + Read(va, data); + + return data; + } + } + + public override WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return new WritableRegion(null, va, Memory.Empty); + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, true); + } + + if (TryGetVirtualContiguous(va, size, out MemoryBlock memoryBlock, out ulong offset)) + { + return new WritableRegion(null, va, memoryBlock.GetMemory(offset, size)); + } + else + { + MemoryOwner memoryOwner = MemoryOwner.Rent(size); + + Read(va, memoryOwner.Span); + + return new WritableRegion(this, va, memoryOwner); + } + } + + public ref T GetRef(ulong va) where T : unmanaged + { + if (!TryGetVirtualContiguous(va, Unsafe.SizeOf(), out MemoryBlock memory, out ulong offset)) + { + ThrowMemoryNotContiguous(); + } + + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), true); + + return ref memory.GetRef(offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool IsMapped(ulong va) + { + return ValidateAddress(va) && _pages.IsMapped(va); + } + + public bool IsRangeMapped(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + return _pages.IsRangeMapped(va, size); + } + + private bool TryGetVirtualContiguous(ulong va, int size, out MemoryBlock memory, out ulong offset) + { + if (_addressSpace.HasAnyPrivateAllocation(va, (ulong)size, out PrivateRange range)) + { + // If we have a private allocation overlapping the range, + // then the access is only considered contiguous if it covers the entire range. + + if (range.Memory != null) + { + memory = range.Memory; + offset = range.Offset; + + return true; + } + + memory = null; + offset = 0; + + return false; + } + + memory = _backingMemory; + offset = GetPhysicalAddressInternal(va); + + return IsPhysicalContiguous(va, size); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsPhysicalContiguous(ulong va, int size) + { + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, (ulong)size)) + { + return false; + } + + int pages = GetPagesCount(va, (uint)size, out va); + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return false; + } + + if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize)) + { + return false; + } + + va += PageSize; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ulong GetContiguousSize(ulong va, ulong size) + { + ulong contiguousSize = PageSize - (va & PageMask); + + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) + { + return contiguousSize; + } + + int pages = GetPagesCount(va, size, out va); + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return contiguousSize; + } + + if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize)) + { + return contiguousSize; + } + + va += PageSize; + contiguousSize += PageSize; + } + + return Math.Min(contiguousSize, size); + } + + private (MemoryBlock, ulong, ulong) GetMemoryOffsetAndSize(ulong va, ulong size) + { + PrivateRange privateRange = _addressSpace.GetFirstPrivateAllocation(va, size, out ulong nextVa); + + if (privateRange.Memory != null) + { + return (privateRange.Memory, privateRange.Offset, privateRange.Size); + } + + ulong physSize = GetContiguousSize(va, Math.Min(size, nextVa - va)); + + return (_backingMemory, GetPhysicalAddressChecked(va), physSize); + } + + public IEnumerable GetHostRegions(ulong va, ulong size) + { + if (!ValidateAddressAndSize(va, size)) + { + return null; + } + + var regions = new List(); + ulong endVa = va + size; + + try + { + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong rangeSize) = GetMemoryOffsetAndSize(va, endVa - va); + + regions.Add(new((UIntPtr)memory.GetPointer(rangeOffset, rangeSize), rangeSize)); + + va += rangeSize; + } + } + catch (InvalidMemoryRegionException) + { + return null; + } + + return regions; + } + + public IEnumerable GetPhysicalRegions(ulong va, ulong size) + { + if (size == 0) + { + return Enumerable.Empty(); + } + + return GetPhysicalRegionsImpl(va, size); + } + + private List GetPhysicalRegionsImpl(ulong va, ulong size) + { + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) + { + return null; + } + + int pages = GetPagesCount(va, (uint)size, out va); + + var regions = new List(); + + ulong regionStart = GetPhysicalAddressInternal(va); + ulong regionSize = PageSize; + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return null; + } + + ulong newPa = GetPhysicalAddressInternal(va + PageSize); + + if (GetPhysicalAddressInternal(va) + PageSize != newPa) + { + regions.Add(new MemoryRange(regionStart, regionSize)); + regionStart = newPa; + regionSize = 0; + } + + va += PageSize; + regionSize += PageSize; + } + + regions.Add(new MemoryRange(regionStart, regionSize)); + + return regions; + } + + /// + /// + /// This function also validates that the given range is both valid and mapped, and will throw if it is not. + /// + public override void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + { + AssertValidAddressAndSize(va, size); + + if (precise) + { + Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId); + return; + } + + // Software table, used for managed memory tracking. + + _pages.SignalMemoryTracking(Tracking, va, size, write, exemptId); + } + + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) + { + return Tracking.BeginTracking(address, size, id, flags); + } + + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) + { + return Tracking.BeginGranularTracking(address, size, handles, granularity, id, flags); + } + + public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id) + { + return Tracking.BeginSmartGranularTracking(address, size, granularity, id); + } + + private ulong GetPhysicalAddressChecked(ulong va) + { + if (!IsMapped(va)) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}"); + } + + return GetPhysicalAddressInternal(va); + } + + private ulong GetPhysicalAddressInternal(ulong va) + { + return _nativePageTable.GetPhysicalAddress(va); + } + + /// + public void Reprotect(ulong va, ulong size, MemoryPermission protection) + { + // TODO + } + + /// + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) + { + if (guest) + { + _addressSpace.Reprotect(va, size, protection); + } + else + { + _pages.TrackingReprotect(va, size, protection); + } + } + + /// + /// Disposes of resources used by the memory manager. + /// + protected override void Destroy() + { + _addressSpace.Dispose(); + _nativePageTable.Dispose(); + } + + protected override Memory GetPhysicalAddressMemory(nuint pa, int size) + => _backingMemory.GetMemory(pa, size); + + protected override Span GetPhysicalAddressSpan(nuint pa, int size) + => _backingMemory.GetSpan(pa, size); + + protected override void WriteImpl(ulong va, ReadOnlySpan data) + { + try + { + AssertValidAddressAndSize(va, (ulong)data.Length); + + ulong endVa = va + (ulong)data.Length; + int offset = 0; + + while (va < endVa) + { + (MemoryBlock memory, ulong rangeOffset, ulong copySize) = GetMemoryOffsetAndSize(va, (ulong)(data.Length - offset)); + + data.Slice(offset, (int)copySize).CopyTo(memory.GetSpan(rangeOffset, (int)copySize)); + + va += copySize; + offset += (int)copySize; + } + } + catch (InvalidMemoryRegionException) + { + if (_invalidAccessHandler == null || !_invalidAccessHandler(va)) + { + throw; + } + } + } + + protected override nuint TranslateVirtualAddressChecked(ulong va) + => (nuint)GetPhysicalAddressChecked(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) + => (nuint)GetPhysicalAddressInternal(va); + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/AarchCompiler.cs b/src/Ryujinx.Cpu/LightningJit/AarchCompiler.cs new file mode 100644 index 00000000..ee4fc439 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/AarchCompiler.cs @@ -0,0 +1,32 @@ +using ARMeilleure.Common; +using ARMeilleure.Memory; +using Ryujinx.Cpu.LightningJit.Arm32; +using Ryujinx.Cpu.LightningJit.Arm64; +using Ryujinx.Cpu.LightningJit.State; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.LightningJit +{ + class AarchCompiler + { + public static CompiledFunction Compile( + CpuPreset cpuPreset, + IMemoryManager memoryManager, + ulong address, + AddressTable funcTable, + IntPtr dispatchStubPtr, + ExecutionMode executionMode, + Architecture targetArch) + { + if (executionMode == ExecutionMode.Aarch64) + { + return A64Compiler.Compile(cpuPreset, memoryManager, address, funcTable, dispatchStubPtr, targetArch); + } + else + { + return A32Compiler.Compile(cpuPreset, memoryManager, address, funcTable, dispatchStubPtr, executionMode == ExecutionMode.Aarch32Thumb, targetArch); + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/AddressForm.cs b/src/Ryujinx.Cpu/LightningJit/AddressForm.cs new file mode 100644 index 00000000..a9ad7e8a --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/AddressForm.cs @@ -0,0 +1,18 @@ + +namespace Ryujinx.Cpu.LightningJit +{ + enum AddressForm : byte + { + None, + OffsetReg, + PostIndexed, + PreIndexed, + SignedScaled, + UnsignedScaled, + BaseRegister, + BasePlusOffset, + Literal, + StructNoOffset, + StructPostIndexedReg, + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/A32Compiler.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/A32Compiler.cs new file mode 100644 index 00000000..7f6024d4 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/A32Compiler.cs @@ -0,0 +1,30 @@ +using ARMeilleure.Common; +using ARMeilleure.Memory; +using Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + static class A32Compiler + { + public static CompiledFunction Compile( + CpuPreset cpuPreset, + IMemoryManager memoryManager, + ulong address, + AddressTable funcTable, + IntPtr dispatchStubPtr, + bool isThumb, + Architecture targetArch) + { + if (targetArch == Architecture.Arm64) + { + return Compiler.Compile(cpuPreset, memoryManager, address, funcTable, dispatchStubPtr, isThumb); + } + else + { + throw new PlatformNotSupportedException(); + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Block.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Block.cs new file mode 100644 index 00000000..c4568995 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Block.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + class Block + { + public readonly ulong Address; + public readonly ulong EndAddress; + public readonly List Instructions; + public readonly bool EndsWithBranch; + public readonly bool HasHostCall; + public readonly bool HasHostCallSkipContext; + public readonly bool IsTruncated; + public readonly bool IsLoopEnd; + public readonly bool IsThumb; + + public Block( + ulong address, + ulong endAddress, + List instructions, + bool endsWithBranch, + bool hasHostCall, + bool hasHostCallSkipContext, + bool isTruncated, + bool isLoopEnd, + bool isThumb) + { + Debug.Assert(isThumb || (int)((endAddress - address) / 4) == instructions.Count); + + Address = address; + EndAddress = endAddress; + Instructions = instructions; + EndsWithBranch = endsWithBranch; + HasHostCall = hasHostCall; + HasHostCallSkipContext = hasHostCallSkipContext; + IsTruncated = isTruncated; + IsLoopEnd = isLoopEnd; + IsThumb = isThumb; + } + + public (Block, Block) SplitAtAddress(ulong address) + { + int splitIndex = FindSplitIndex(address); + + if (splitIndex < 0) + { + return (null, null); + } + + int splitCount = Instructions.Count - splitIndex; + + // Technically those are valid, but we don't want to create empty blocks. + Debug.Assert(splitIndex != 0); + Debug.Assert(splitCount != 0); + + Block leftBlock = new( + Address, + address, + Instructions.GetRange(0, splitIndex), + false, + HasHostCall, + HasHostCallSkipContext, + false, + false, + IsThumb); + + Block rightBlock = new( + address, + EndAddress, + Instructions.GetRange(splitIndex, splitCount), + EndsWithBranch, + HasHostCall, + HasHostCallSkipContext, + IsTruncated, + IsLoopEnd, + IsThumb); + + return (leftBlock, rightBlock); + } + + private int FindSplitIndex(ulong address) + { + if (IsThumb) + { + ulong pc = Address; + + for (int index = 0; index < Instructions.Count; index++) + { + if (pc == address) + { + return index; + } + + pc += Instructions[index].Flags.HasFlag(InstFlags.Thumb16) ? 2UL : 4UL; + } + + return -1; + } + else + { + return (int)((address - Address) / 4); + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/BranchType.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/BranchType.cs new file mode 100644 index 00000000..6d9fdf2c --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/BranchType.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + enum BranchType + { + Branch, + Call, + IndirectBranch, + TableBranchByte, + TableBranchHalfword, + IndirectCall, + SyncPoint, + SoftwareInterrupt, + ReadCntpct, + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/CodeGenContext.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/CodeGenContext.cs new file mode 100644 index 00000000..f55e2bb9 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/CodeGenContext.cs @@ -0,0 +1,198 @@ +using ARMeilleure.Memory; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + class CodeGenContext + { + public CodeWriter CodeWriter { get; } + public Assembler Arm64Assembler { get; } + public RegisterAllocator RegisterAllocator { get; } + + public MemoryManagerType MemoryManagerType { get; } + + private uint _instructionAddress; + + public bool IsThumb { get; } + public uint Pc { get; private set; } + public bool InITBlock { get; private set; } + + private InstInfo _nextInstruction; + private bool _skipNextInstruction; + + private readonly ArmCondition[] _itConditions; + private int _itCount; + + private readonly List _pendingBranches; + + private bool _nzcvModified; + + public CodeGenContext(CodeWriter codeWriter, Assembler arm64Assembler, RegisterAllocator registerAllocator, MemoryManagerType mmType, bool isThumb) + { + CodeWriter = codeWriter; + Arm64Assembler = arm64Assembler; + RegisterAllocator = registerAllocator; + MemoryManagerType = mmType; + _itConditions = new ArmCondition[4]; + _pendingBranches = new(); + IsThumb = isThumb; + } + + public void SetPc(uint address) + { + // Due to historical reasons, the PC value is always 2 instructions ahead on 32-bit Arm CPUs. + Pc = address + (IsThumb ? 4u : 8u); + _instructionAddress = address; + } + + public void SetNextInstruction(InstInfo info) + { + _nextInstruction = info; + } + + public InstInfo PeekNextInstruction() + { + return _nextInstruction; + } + + public void SetSkipNextInstruction() + { + _skipNextInstruction = true; + } + + public bool ConsumeSkipNextInstruction() + { + bool skip = _skipNextInstruction; + _skipNextInstruction = false; + + return skip; + } + + public void AddPendingBranch(InstName name, int offset) + { + _pendingBranches.Add(new(BranchType.Branch, Pc + (uint)offset, 0u, name, CodeWriter.InstructionPointer)); + } + + public void AddPendingCall(uint targetAddress, uint nextAddress) + { + _pendingBranches.Add(new(BranchType.Call, targetAddress, nextAddress, InstName.BlI, CodeWriter.InstructionPointer)); + + RegisterAllocator.EnsureTempGprRegisters(1); + RegisterAllocator.MarkGprAsUsed(RegisterUtils.LrRegister); + } + + public void AddPendingIndirectBranch(InstName name, uint targetRegister) + { + _pendingBranches.Add(new(BranchType.IndirectBranch, targetRegister, 0u, name, CodeWriter.InstructionPointer)); + + RegisterAllocator.MarkGprAsUsed((int)targetRegister); + } + + public void AddPendingTableBranch(uint rn, uint rm, bool halfword) + { + _pendingBranches.Add(new(halfword ? BranchType.TableBranchHalfword : BranchType.TableBranchByte, rn, rm, InstName.Tbb, CodeWriter.InstructionPointer)); + + RegisterAllocator.EnsureTempGprRegisters(2); + RegisterAllocator.MarkGprAsUsed((int)rn); + RegisterAllocator.MarkGprAsUsed((int)rm); + } + + public void AddPendingIndirectCall(uint targetRegister, uint nextAddress) + { + _pendingBranches.Add(new(BranchType.IndirectCall, targetRegister, nextAddress, InstName.BlxR, CodeWriter.InstructionPointer)); + + RegisterAllocator.EnsureTempGprRegisters(targetRegister == RegisterUtils.LrRegister ? 1 : 0); + RegisterAllocator.MarkGprAsUsed((int)targetRegister); + RegisterAllocator.MarkGprAsUsed(RegisterUtils.LrRegister); + } + + public void AddPendingSyncPoint() + { + _pendingBranches.Add(new(BranchType.SyncPoint, 0, 0, default, CodeWriter.InstructionPointer)); + + RegisterAllocator.EnsureTempGprRegisters(1); + } + + public void AddPendingBkpt(uint imm) + { + _pendingBranches.Add(new(BranchType.SoftwareInterrupt, imm, _instructionAddress, InstName.Bkpt, CodeWriter.InstructionPointer)); + + RegisterAllocator.EnsureTempGprRegisters(1); + } + + public void AddPendingSvc(uint imm) + { + _pendingBranches.Add(new(BranchType.SoftwareInterrupt, imm, _instructionAddress, InstName.Svc, CodeWriter.InstructionPointer)); + + RegisterAllocator.EnsureTempGprRegisters(1); + } + + public void AddPendingUdf(uint imm) + { + _pendingBranches.Add(new(BranchType.SoftwareInterrupt, imm, _instructionAddress, InstName.Udf, CodeWriter.InstructionPointer)); + + RegisterAllocator.EnsureTempGprRegisters(1); + } + + public void AddPendingReadCntpct(uint rt, uint rt2) + { + _pendingBranches.Add(new(BranchType.ReadCntpct, rt, rt2, InstName.Mrrc, CodeWriter.InstructionPointer)); + + RegisterAllocator.EnsureTempGprRegisters(1); + } + + public IEnumerable GetPendingBranches() + { + return _pendingBranches; + } + + public void SetItBlockStart(ReadOnlySpan conditions) + { + _itCount = conditions.Length; + + for (int index = 0; index < conditions.Length; index++) + { + _itConditions[index] = conditions[index]; + } + + InITBlock = true; + } + + public bool ConsumeItCondition(out ArmCondition condition) + { + if (_itCount != 0) + { + condition = _itConditions[--_itCount]; + + return true; + } + + condition = ArmCondition.Al; + + return false; + } + + public void UpdateItState() + { + if (_itCount == 0) + { + InITBlock = false; + } + } + + public void SetNzcvModified() + { + _nzcvModified = true; + } + + public bool ConsumeNzcvModified() + { + bool modified = _nzcvModified; + _nzcvModified = false; + + return modified; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Decoder.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Decoder.cs new file mode 100644 index 00000000..8a2b389a --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Decoder.cs @@ -0,0 +1,556 @@ +using ARMeilleure.Memory; +using Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + static class Decoder where T : IInstEmit + { + public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool isThumb) + { + List blocks = new(); + List branchTargets = new(); + + while (true) + { + Block block = Decode(cpuPreset, memoryManager, address, isThumb); + + if (!block.IsTruncated && TryGetBranchTarget(block, out ulong targetAddress)) + { + branchTargets.Add(targetAddress); + } + + blocks.Add(block); + + if (block.IsTruncated || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets)) + { + break; + } + + address = block.EndAddress; + } + + branchTargets.Sort(); + SplitBlocks(blocks, branchTargets); + + return new(blocks); + } + + private static bool TryGetBranchTarget(Block block, out ulong targetAddress) + { + // PC is 2 instructions ahead, since the end address is already one instruction after the last one, we just need to add + // another instruction. + + ulong pc = block.EndAddress + (block.IsThumb ? 2UL : 4UL); + + return TryGetBranchTarget(block.Instructions[^1].Name, block.Instructions[^1].Flags, pc, block.Instructions[^1].Encoding, block.IsThumb, out targetAddress); + } + + private static bool TryGetBranchTarget(InstName name, InstFlags flags, ulong pc, uint encoding, bool isThumb, out ulong targetAddress) + { + int originalOffset; + + switch (name) + { + case InstName.B: + if (isThumb) + { + if (flags.HasFlag(InstFlags.Thumb16)) + { + if ((encoding & (1u << 29)) != 0) + { + InstImm11b16w11 inst = new(encoding); + + originalOffset = ImmUtils.ExtractT16SImm11Times2(inst.Imm11); + } + else + { + InstCondb24w4Imm8b16w8 inst = new(encoding); + + originalOffset = ImmUtils.ExtractT16SImm8Times2(inst.Imm8); + } + } + else + { + if ((encoding & (1u << 12)) != 0) + { + InstSb26w1Imm10b16w10J1b13w1J2b11w1Imm11b0w11 inst = new(encoding); + + originalOffset = ImmUtils.CombineSImm24Times2(inst.Imm11, inst.Imm10, inst.J1, inst.J2, inst.S); + } + else + { + InstSb26w1Condb22w4Imm6b16w6J1b13w1J2b11w1Imm11b0w11 inst = new(encoding); + + originalOffset = ImmUtils.CombineSImm20Times2(inst.Imm11, inst.Imm6, inst.J1, inst.J2, inst.S); + } + } + } + else + { + originalOffset = ImmUtils.ExtractSImm24Times4(encoding); + } + + targetAddress = pc + (ulong)originalOffset; + Debug.Assert((targetAddress & 1) == 0); + + return true; + + case InstName.Cbnz: + originalOffset = ImmUtils.ExtractT16UImm5Times2(encoding); + targetAddress = pc + (ulong)originalOffset; + Debug.Assert((targetAddress & 1) == 0); + + return true; + } + + targetAddress = 0; + + return false; + } + + private static void SplitBlocks(List blocks, List branchTargets) + { + int btIndex = 0; + + while (btIndex < branchTargets.Count) + { + for (int blockIndex = 0; blockIndex < blocks.Count && btIndex < branchTargets.Count; blockIndex++) + { + Block block = blocks[blockIndex]; + ulong currentBranchTarget = branchTargets[btIndex]; + + while (currentBranchTarget >= block.Address && currentBranchTarget < block.EndAddress) + { + if (block.Address != currentBranchTarget) + { + (Block leftBlock, Block rightBlock) = block.SplitAtAddress(currentBranchTarget); + + if (leftBlock != null && rightBlock != null) + { + blocks.Insert(blockIndex, leftBlock); + blocks[blockIndex + 1] = rightBlock; + + block = leftBlock; + } + else + { + // Split can only fail in thumb mode, where the instruction size is not fixed. + + Debug.Assert(block.IsThumb); + } + } + + btIndex++; + + while (btIndex < branchTargets.Count && branchTargets[btIndex] == currentBranchTarget) + { + btIndex++; + } + + if (btIndex >= branchTargets.Count) + { + break; + } + + currentBranchTarget = branchTargets[btIndex]; + } + } + + Debug.Assert(btIndex < int.MaxValue); + btIndex++; + } + } + + private static bool HasNextBlock(in Block block, ulong pc, List branchTargets) + { + InstFlags lastInstFlags = block.Instructions[^1].Flags; + + // Thumb has separate encodings for conditional and unconditional branch instructions. + if (lastInstFlags.HasFlag(InstFlags.Cond) && (block.IsThumb || (ArmCondition)(block.Instructions[^1].Encoding >> 28) < ArmCondition.Al)) + { + return true; + } + + switch (block.Instructions[^1].Name) + { + case InstName.B: + return branchTargets.Contains(pc + 4UL) || + (TryGetBranchTarget(block, out ulong targetAddress) && targetAddress >= pc && targetAddress < pc + 0x1000); + + case InstName.Bx: + case InstName.Bxj: + return branchTargets.Contains(pc + 4UL); + + case InstName.Cbnz: + case InstName.BlI: + case InstName.BlxR: + return true; + } + + if (WritesToPC(block.Instructions[^1].Encoding, block.Instructions[^1].Name, lastInstFlags, block.IsThumb)) + { + return branchTargets.Contains(pc + 4UL); + } + + return !block.EndsWithBranch; + } + + private static Block Decode(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool isThumb) + { + ulong startAddress = address; + + List insts = new(); + + uint encoding; + InstMeta meta; + InstFlags extraFlags = InstFlags.None; + bool hasHostCall = false; + bool hasHostCallSkipContext = false; + bool isTruncated = false; + + do + { + if (!memoryManager.IsMapped(address)) + { + encoding = 0; + meta = default; + isTruncated = true; + break; + } + + if (isThumb) + { + encoding = (uint)memoryManager.Read(address) << 16; + address += 2UL; + + extraFlags = InstFlags.Thumb16; + + if (!InstTableT16.TryGetMeta(encoding, cpuPreset.Version, cpuPreset.Features, out meta)) + { + encoding |= memoryManager.Read(address); + + if (InstTableT32.TryGetMeta(encoding, cpuPreset.Version, cpuPreset.Features, out meta)) + { + address += 2UL; + extraFlags = InstFlags.None; + } + } + } + else + { + encoding = memoryManager.Read(address); + address += 4UL; + + meta = InstTableA32.GetMeta(encoding, cpuPreset.Version, cpuPreset.Features); + } + + if (meta.Name.IsSystemOrCall()) + { + if (!hasHostCall) + { + hasHostCall = InstEmitSystem.NeedsCall(meta.Name); + } + + if (!hasHostCallSkipContext) + { + hasHostCallSkipContext = meta.Name.IsCall() || InstEmitSystem.NeedsCallSkipContext(meta.Name); + } + } + + insts.Add(new(encoding, meta.Name, meta.EmitFunc, meta.Flags | extraFlags)); + } + while (!IsControlFlow(encoding, meta.Name, meta.Flags | extraFlags, isThumb)); + + bool isLoopEnd = false; + + if (!isTruncated && IsBackwardsBranch(meta.Name, encoding)) + { + isLoopEnd = true; + hasHostCallSkipContext = true; + } + + return new( + startAddress, + address, + insts, + !isTruncated, + hasHostCall, + hasHostCallSkipContext, + isTruncated, + isLoopEnd, + isThumb); + } + + private static bool IsControlFlow(uint encoding, InstName name, InstFlags flags, bool isThumb) + { + switch (name) + { + case InstName.B: + case InstName.BlI: + case InstName.BlxR: + case InstName.Bx: + case InstName.Bxj: + case InstName.Cbnz: + case InstName.Tbb: + return true; + } + + return WritesToPC(encoding, name, flags, isThumb); + } + + public static bool WritesToPC(uint encoding, InstName name, InstFlags flags, bool isThumb) + { + return (GetRegisterWriteMask(encoding, name, flags, isThumb) & (1u << RegisterUtils.PcRegister)) != 0; + } + + private static uint GetRegisterWriteMask(uint encoding, InstName name, InstFlags flags, bool isThumb) + { + uint mask = 0; + + if (isThumb) + { + if (flags.HasFlag(InstFlags.Thumb16)) + { + if (flags.HasFlag(InstFlags.Rdn)) + { + mask |= 1u << RegisterUtils.ExtractRdn(flags, encoding); + } + + if (flags.HasFlag(InstFlags.Rd)) + { + mask |= 1u << RegisterUtils.ExtractRdT16(flags, encoding); + } + + Debug.Assert(!flags.HasFlag(InstFlags.RdHi)); + + if (IsRegisterWrite(flags, InstFlags.Rt)) + { + mask |= 1u << RegisterUtils.ExtractRtT16(flags, encoding); + } + + Debug.Assert(!flags.HasFlag(InstFlags.Rt2)); + + if (IsRegisterWrite(flags, InstFlags.Rlist)) + { + mask |= (byte)(encoding >> 16); + + if (name == InstName.Push) + { + mask |= (encoding >> 10) & 0x4000; // LR + } + else if (name == InstName.Pop) + { + mask |= (encoding >> 9) & 0x8000; // PC + } + } + + Debug.Assert(!flags.HasFlag(InstFlags.WBack)); + } + else + { + if (flags.HasFlag(InstFlags.Rd)) + { + mask |= 1u << RegisterUtils.ExtractRdT32(flags, encoding); + } + + if (flags.HasFlag(InstFlags.RdLo)) + { + mask |= 1u << RegisterUtils.ExtractRdLoT32(encoding); + } + + if (flags.HasFlag(InstFlags.RdHi)) + { + mask |= 1u << RegisterUtils.ExtractRdHiT32(encoding); + } + + if (IsRegisterWrite(flags, InstFlags.Rt) && IsRtWrite(name, encoding) && !IsR15RtEncodingSpecial(name, encoding)) + { + mask |= 1u << RegisterUtils.ExtractRtT32(encoding); + } + + if (IsRegisterWrite(flags, InstFlags.Rt2) && IsRtWrite(name, encoding)) + { + mask |= 1u << RegisterUtils.ExtractRt2T32(encoding); + } + + if (IsRegisterWrite(flags, InstFlags.Rlist)) + { + mask |= (ushort)encoding; + } + + if (flags.HasFlag(InstFlags.WBack) && HasWriteBackT32(name, encoding)) + { + mask |= 1u << RegisterUtils.ExtractRn(encoding); // This is at the same bit position as A32. + } + } + } + else + { + if (flags.HasFlag(InstFlags.Rd)) + { + mask |= 1u << RegisterUtils.ExtractRd(flags, encoding); + } + + if (flags.HasFlag(InstFlags.RdHi)) + { + mask |= 1u << RegisterUtils.ExtractRdHi(encoding); + } + + if (IsRegisterWrite(flags, InstFlags.Rt) && IsRtWrite(name, encoding) && !IsR15RtEncodingSpecial(name, encoding)) + { + mask |= 1u << RegisterUtils.ExtractRt(encoding); + } + + if (IsRegisterWrite(flags, InstFlags.Rt2) && IsRtWrite(name, encoding)) + { + mask |= 1u << RegisterUtils.ExtractRt2(encoding); + } + + if (IsRegisterWrite(flags, InstFlags.Rlist)) + { + mask |= (ushort)encoding; + } + + if (flags.HasFlag(InstFlags.WBack) && HasWriteBack(name, encoding)) + { + mask |= 1u << RegisterUtils.ExtractRn(encoding); + } + } + + return mask; + } + + private static bool IsRtWrite(InstName name, uint encoding) + { + // Some instructions can move GPR to FP/SIMD or FP/SIMD to GPR depending on the encoding. + // Detect those cases so that we can tell if we're actually doing a register write. + + switch (name) + { + case InstName.VmovD: + case InstName.VmovH: + case InstName.VmovS: + case InstName.VmovSs: + return (encoding & (1u << 20)) != 0; + } + + return true; + } + + private static bool HasWriteBack(InstName name, uint encoding) + { + if (IsLoadStoreMultiple(name)) + { + return (encoding & (1u << 21)) != 0; + } + + if (IsVLDnVSTn(name)) + { + return (encoding & 0xf) != RegisterUtils.PcRegister; + } + + bool w = (encoding & (1u << 21)) != 0; + bool p = (encoding & (1u << 24)) != 0; + + return !p || w; + } + + private static bool HasWriteBackT32(InstName name, uint encoding) + { + if (IsLoadStoreMultiple(name)) + { + return (encoding & (1u << 21)) != 0; + } + + if (IsVLDnVSTn(name)) + { + return (encoding & 0xf) != RegisterUtils.PcRegister; + } + + return (encoding & (1u << 8)) != 0; + } + + private static bool IsLoadStoreMultiple(InstName name) + { + switch (name) + { + case InstName.Ldm: + case InstName.Ldmda: + case InstName.Ldmdb: + case InstName.LdmE: + case InstName.Ldmib: + case InstName.LdmU: + case InstName.Stm: + case InstName.Stmda: + case InstName.Stmdb: + case InstName.Stmib: + case InstName.StmU: + case InstName.Fldmx: + case InstName.Fstmx: + case InstName.Vldm: + case InstName.Vstm: + return true; + } + + return false; + } + + private static bool IsVLDnVSTn(InstName name) + { + switch (name) + { + case InstName.Vld11: + case InstName.Vld1A: + case InstName.Vld1M: + case InstName.Vld21: + case InstName.Vld2A: + case InstName.Vld2M: + case InstName.Vld31: + case InstName.Vld3A: + case InstName.Vld3M: + case InstName.Vld41: + case InstName.Vld4A: + case InstName.Vld4M: + case InstName.Vst11: + case InstName.Vst1M: + case InstName.Vst21: + case InstName.Vst2M: + case InstName.Vst31: + case InstName.Vst3M: + case InstName.Vst41: + case InstName.Vst4M: + return true; + } + + return false; + } + + private static bool IsR15RtEncodingSpecial(InstName name, uint encoding) + { + if (name == InstName.Vmrs) + { + return ((encoding >> 16) & 0xf) == 1; + } + + return false; + } + + private static bool IsRegisterWrite(InstFlags flags, InstFlags testFlag) + { + return flags.HasFlag(testFlag) && !flags.HasFlag(InstFlags.ReadRd); + } + + private static bool IsBackwardsBranch(InstName name, uint encoding) + { + if (name == InstName.B) + { + return ImmUtils.ExtractSImm24Times4(encoding) < 0; + } + + return false; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/IInstEmit.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/IInstEmit.cs new file mode 100644 index 00000000..32dc5aeb --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/IInstEmit.cs @@ -0,0 +1,1231 @@ +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + interface IInstEmit + { + static abstract void AdcIA1(CodeGenContext context, uint encoding); + static abstract void AdcIT1(CodeGenContext context, uint encoding); + static abstract void AdcRA1(CodeGenContext context, uint encoding); + static abstract void AdcRT1(CodeGenContext context, uint encoding); + static abstract void AdcRT2(CodeGenContext context, uint encoding); + static abstract void AdcRrA1(CodeGenContext context, uint encoding); + static abstract void AddIA1(CodeGenContext context, uint encoding); + static abstract void AddIT1(CodeGenContext context, uint encoding); + static abstract void AddIT2(CodeGenContext context, uint encoding); + static abstract void AddIT3(CodeGenContext context, uint encoding); + static abstract void AddIT4(CodeGenContext context, uint encoding); + static abstract void AddRA1(CodeGenContext context, uint encoding); + static abstract void AddRT1(CodeGenContext context, uint encoding); + static abstract void AddRT2(CodeGenContext context, uint encoding); + static abstract void AddRT3(CodeGenContext context, uint encoding); + static abstract void AddRrA1(CodeGenContext context, uint encoding); + static abstract void AddSpIA1(CodeGenContext context, uint encoding); + static abstract void AddSpIT1(CodeGenContext context, uint encoding); + static abstract void AddSpIT2(CodeGenContext context, uint encoding); + static abstract void AddSpIT3(CodeGenContext context, uint encoding); + static abstract void AddSpIT4(CodeGenContext context, uint encoding); + static abstract void AddSpRA1(CodeGenContext context, uint encoding); + static abstract void AddSpRT1(CodeGenContext context, uint encoding); + static abstract void AddSpRT2(CodeGenContext context, uint encoding); + static abstract void AddSpRT3(CodeGenContext context, uint encoding); + static abstract void AdrA1(CodeGenContext context, uint encoding); + static abstract void AdrA2(CodeGenContext context, uint encoding); + static abstract void AdrT1(CodeGenContext context, uint encoding); + static abstract void AdrT2(CodeGenContext context, uint encoding); + static abstract void AdrT3(CodeGenContext context, uint encoding); + static abstract void AesdA1(CodeGenContext context, uint encoding); + static abstract void AesdT1(CodeGenContext context, uint encoding); + static abstract void AeseA1(CodeGenContext context, uint encoding); + static abstract void AeseT1(CodeGenContext context, uint encoding); + static abstract void AesimcA1(CodeGenContext context, uint encoding); + static abstract void AesimcT1(CodeGenContext context, uint encoding); + static abstract void AesmcA1(CodeGenContext context, uint encoding); + static abstract void AesmcT1(CodeGenContext context, uint encoding); + static abstract void AndIA1(CodeGenContext context, uint encoding); + static abstract void AndIT1(CodeGenContext context, uint encoding); + static abstract void AndRA1(CodeGenContext context, uint encoding); + static abstract void AndRT1(CodeGenContext context, uint encoding); + static abstract void AndRT2(CodeGenContext context, uint encoding); + static abstract void AndRrA1(CodeGenContext context, uint encoding); + static abstract void BA1(CodeGenContext context, uint encoding); + static abstract void BT1(CodeGenContext context, uint encoding); + static abstract void BT2(CodeGenContext context, uint encoding); + static abstract void BT3(CodeGenContext context, uint encoding); + static abstract void BT4(CodeGenContext context, uint encoding); + static abstract void BfcA1(CodeGenContext context, uint encoding); + static abstract void BfcT1(CodeGenContext context, uint encoding); + static abstract void BfiA1(CodeGenContext context, uint encoding); + static abstract void BfiT1(CodeGenContext context, uint encoding); + static abstract void BicIA1(CodeGenContext context, uint encoding); + static abstract void BicIT1(CodeGenContext context, uint encoding); + static abstract void BicRA1(CodeGenContext context, uint encoding); + static abstract void BicRT1(CodeGenContext context, uint encoding); + static abstract void BicRT2(CodeGenContext context, uint encoding); + static abstract void BicRrA1(CodeGenContext context, uint encoding); + static abstract void BkptA1(CodeGenContext context, uint encoding); + static abstract void BkptT1(CodeGenContext context, uint encoding); + static abstract void BlxRA1(CodeGenContext context, uint encoding); + static abstract void BlxRT1(CodeGenContext context, uint encoding); + static abstract void BlIA1(CodeGenContext context, uint encoding); + static abstract void BlIA2(CodeGenContext context, uint encoding); + static abstract void BlIT1(CodeGenContext context, uint encoding); + static abstract void BlIT2(CodeGenContext context, uint encoding); + static abstract void BxA1(CodeGenContext context, uint encoding); + static abstract void BxT1(CodeGenContext context, uint encoding); + static abstract void BxjA1(CodeGenContext context, uint encoding); + static abstract void BxjT1(CodeGenContext context, uint encoding); + static abstract void CbnzT1(CodeGenContext context, uint encoding); + static abstract void ClrbhbA1(CodeGenContext context, uint encoding); + static abstract void ClrbhbT1(CodeGenContext context, uint encoding); + static abstract void ClrexA1(CodeGenContext context, uint encoding); + static abstract void ClrexT1(CodeGenContext context, uint encoding); + static abstract void ClzA1(CodeGenContext context, uint encoding); + static abstract void ClzT1(CodeGenContext context, uint encoding); + static abstract void CmnIA1(CodeGenContext context, uint encoding); + static abstract void CmnIT1(CodeGenContext context, uint encoding); + static abstract void CmnRA1(CodeGenContext context, uint encoding); + static abstract void CmnRT1(CodeGenContext context, uint encoding); + static abstract void CmnRT2(CodeGenContext context, uint encoding); + static abstract void CmnRrA1(CodeGenContext context, uint encoding); + static abstract void CmpIA1(CodeGenContext context, uint encoding); + static abstract void CmpIT1(CodeGenContext context, uint encoding); + static abstract void CmpIT2(CodeGenContext context, uint encoding); + static abstract void CmpRA1(CodeGenContext context, uint encoding); + static abstract void CmpRT1(CodeGenContext context, uint encoding); + static abstract void CmpRT2(CodeGenContext context, uint encoding); + static abstract void CmpRT3(CodeGenContext context, uint encoding); + static abstract void CmpRrA1(CodeGenContext context, uint encoding); + static abstract void CpsA1(CodeGenContext context, uint encoding); + static abstract void CpsT1(CodeGenContext context, uint encoding); + static abstract void CpsT2(CodeGenContext context, uint encoding); + static abstract void Crc32A1(CodeGenContext context, uint encoding); + static abstract void Crc32T1(CodeGenContext context, uint encoding); + static abstract void Crc32cA1(CodeGenContext context, uint encoding); + static abstract void Crc32cT1(CodeGenContext context, uint encoding); + static abstract void CsdbA1(CodeGenContext context, uint encoding); + static abstract void CsdbT1(CodeGenContext context, uint encoding); + static abstract void DbgA1(CodeGenContext context, uint encoding); + static abstract void DbgT1(CodeGenContext context, uint encoding); + static abstract void Dcps1T1(CodeGenContext context, uint encoding); + static abstract void Dcps2T1(CodeGenContext context, uint encoding); + static abstract void Dcps3T1(CodeGenContext context, uint encoding); + static abstract void DmbA1(CodeGenContext context, uint encoding); + static abstract void DmbT1(CodeGenContext context, uint encoding); + static abstract void DsbA1(CodeGenContext context, uint encoding); + static abstract void DsbT1(CodeGenContext context, uint encoding); + static abstract void EorIA1(CodeGenContext context, uint encoding); + static abstract void EorIT1(CodeGenContext context, uint encoding); + static abstract void EorRA1(CodeGenContext context, uint encoding); + static abstract void EorRT1(CodeGenContext context, uint encoding); + static abstract void EorRT2(CodeGenContext context, uint encoding); + static abstract void EorRrA1(CodeGenContext context, uint encoding); + static abstract void EretA1(CodeGenContext context, uint encoding); + static abstract void EretT1(CodeGenContext context, uint encoding); + static abstract void EsbA1(CodeGenContext context, uint encoding); + static abstract void EsbT1(CodeGenContext context, uint encoding); + static abstract void FldmxA1(CodeGenContext context, uint encoding); + static abstract void FldmxT1(CodeGenContext context, uint encoding); + static abstract void FstmxA1(CodeGenContext context, uint encoding); + static abstract void FstmxT1(CodeGenContext context, uint encoding); + static abstract void HltA1(CodeGenContext context, uint encoding); + static abstract void HltT1(CodeGenContext context, uint encoding); + static abstract void HvcA1(CodeGenContext context, uint encoding); + static abstract void HvcT1(CodeGenContext context, uint encoding); + static abstract void IsbA1(CodeGenContext context, uint encoding); + static abstract void IsbT1(CodeGenContext context, uint encoding); + static abstract void ItT1(CodeGenContext context, uint encoding); + static abstract void LdaA1(CodeGenContext context, uint encoding); + static abstract void LdaT1(CodeGenContext context, uint encoding); + static abstract void LdabA1(CodeGenContext context, uint encoding); + static abstract void LdabT1(CodeGenContext context, uint encoding); + static abstract void LdaexA1(CodeGenContext context, uint encoding); + static abstract void LdaexT1(CodeGenContext context, uint encoding); + static abstract void LdaexbA1(CodeGenContext context, uint encoding); + static abstract void LdaexbT1(CodeGenContext context, uint encoding); + static abstract void LdaexdA1(CodeGenContext context, uint encoding); + static abstract void LdaexdT1(CodeGenContext context, uint encoding); + static abstract void LdaexhA1(CodeGenContext context, uint encoding); + static abstract void LdaexhT1(CodeGenContext context, uint encoding); + static abstract void LdahA1(CodeGenContext context, uint encoding); + static abstract void LdahT1(CodeGenContext context, uint encoding); + static abstract void LdcIA1(CodeGenContext context, uint encoding); + static abstract void LdcIT1(CodeGenContext context, uint encoding); + static abstract void LdcLA1(CodeGenContext context, uint encoding); + static abstract void LdcLT1(CodeGenContext context, uint encoding); + static abstract void LdmA1(CodeGenContext context, uint encoding); + static abstract void LdmT1(CodeGenContext context, uint encoding); + static abstract void LdmT2(CodeGenContext context, uint encoding); + static abstract void LdmdaA1(CodeGenContext context, uint encoding); + static abstract void LdmdbA1(CodeGenContext context, uint encoding); + static abstract void LdmdbT1(CodeGenContext context, uint encoding); + static abstract void LdmibA1(CodeGenContext context, uint encoding); + static abstract void LdmEA1(CodeGenContext context, uint encoding); + static abstract void LdmUA1(CodeGenContext context, uint encoding); + static abstract void LdrbtA1(CodeGenContext context, uint encoding); + static abstract void LdrbtA2(CodeGenContext context, uint encoding); + static abstract void LdrbtT1(CodeGenContext context, uint encoding); + static abstract void LdrbIA1(CodeGenContext context, uint encoding); + static abstract void LdrbIT1(CodeGenContext context, uint encoding); + static abstract void LdrbIT2(CodeGenContext context, uint encoding); + static abstract void LdrbIT3(CodeGenContext context, uint encoding); + static abstract void LdrbLA1(CodeGenContext context, uint encoding); + static abstract void LdrbLT1(CodeGenContext context, uint encoding); + static abstract void LdrbRA1(CodeGenContext context, uint encoding); + static abstract void LdrbRT1(CodeGenContext context, uint encoding); + static abstract void LdrbRT2(CodeGenContext context, uint encoding); + static abstract void LdrdIA1(CodeGenContext context, uint encoding); + static abstract void LdrdIT1(CodeGenContext context, uint encoding); + static abstract void LdrdLA1(CodeGenContext context, uint encoding); + static abstract void LdrdLT1(CodeGenContext context, uint encoding); + static abstract void LdrdRA1(CodeGenContext context, uint encoding); + static abstract void LdrexA1(CodeGenContext context, uint encoding); + static abstract void LdrexT1(CodeGenContext context, uint encoding); + static abstract void LdrexbA1(CodeGenContext context, uint encoding); + static abstract void LdrexbT1(CodeGenContext context, uint encoding); + static abstract void LdrexdA1(CodeGenContext context, uint encoding); + static abstract void LdrexdT1(CodeGenContext context, uint encoding); + static abstract void LdrexhA1(CodeGenContext context, uint encoding); + static abstract void LdrexhT1(CodeGenContext context, uint encoding); + static abstract void LdrhtA1(CodeGenContext context, uint encoding); + static abstract void LdrhtA2(CodeGenContext context, uint encoding); + static abstract void LdrhtT1(CodeGenContext context, uint encoding); + static abstract void LdrhIA1(CodeGenContext context, uint encoding); + static abstract void LdrhIT1(CodeGenContext context, uint encoding); + static abstract void LdrhIT2(CodeGenContext context, uint encoding); + static abstract void LdrhIT3(CodeGenContext context, uint encoding); + static abstract void LdrhLA1(CodeGenContext context, uint encoding); + static abstract void LdrhLT1(CodeGenContext context, uint encoding); + static abstract void LdrhRA1(CodeGenContext context, uint encoding); + static abstract void LdrhRT1(CodeGenContext context, uint encoding); + static abstract void LdrhRT2(CodeGenContext context, uint encoding); + static abstract void LdrsbtA1(CodeGenContext context, uint encoding); + static abstract void LdrsbtA2(CodeGenContext context, uint encoding); + static abstract void LdrsbtT1(CodeGenContext context, uint encoding); + static abstract void LdrsbIA1(CodeGenContext context, uint encoding); + static abstract void LdrsbIT1(CodeGenContext context, uint encoding); + static abstract void LdrsbIT2(CodeGenContext context, uint encoding); + static abstract void LdrsbLA1(CodeGenContext context, uint encoding); + static abstract void LdrsbLT1(CodeGenContext context, uint encoding); + static abstract void LdrsbRA1(CodeGenContext context, uint encoding); + static abstract void LdrsbRT1(CodeGenContext context, uint encoding); + static abstract void LdrsbRT2(CodeGenContext context, uint encoding); + static abstract void LdrshtA1(CodeGenContext context, uint encoding); + static abstract void LdrshtA2(CodeGenContext context, uint encoding); + static abstract void LdrshtT1(CodeGenContext context, uint encoding); + static abstract void LdrshIA1(CodeGenContext context, uint encoding); + static abstract void LdrshIT1(CodeGenContext context, uint encoding); + static abstract void LdrshIT2(CodeGenContext context, uint encoding); + static abstract void LdrshLA1(CodeGenContext context, uint encoding); + static abstract void LdrshLT1(CodeGenContext context, uint encoding); + static abstract void LdrshRA1(CodeGenContext context, uint encoding); + static abstract void LdrshRT1(CodeGenContext context, uint encoding); + static abstract void LdrshRT2(CodeGenContext context, uint encoding); + static abstract void LdrtA1(CodeGenContext context, uint encoding); + static abstract void LdrtA2(CodeGenContext context, uint encoding); + static abstract void LdrtT1(CodeGenContext context, uint encoding); + static abstract void LdrIA1(CodeGenContext context, uint encoding); + static abstract void LdrIT1(CodeGenContext context, uint encoding); + static abstract void LdrIT2(CodeGenContext context, uint encoding); + static abstract void LdrIT3(CodeGenContext context, uint encoding); + static abstract void LdrIT4(CodeGenContext context, uint encoding); + static abstract void LdrLA1(CodeGenContext context, uint encoding); + static abstract void LdrLT1(CodeGenContext context, uint encoding); + static abstract void LdrLT2(CodeGenContext context, uint encoding); + static abstract void LdrRA1(CodeGenContext context, uint encoding); + static abstract void LdrRT1(CodeGenContext context, uint encoding); + static abstract void LdrRT2(CodeGenContext context, uint encoding); + static abstract void McrA1(CodeGenContext context, uint encoding); + static abstract void McrT1(CodeGenContext context, uint encoding); + static abstract void McrrA1(CodeGenContext context, uint encoding); + static abstract void McrrT1(CodeGenContext context, uint encoding); + static abstract void MlaA1(CodeGenContext context, uint encoding); + static abstract void MlaT1(CodeGenContext context, uint encoding); + static abstract void MlsA1(CodeGenContext context, uint encoding); + static abstract void MlsT1(CodeGenContext context, uint encoding); + static abstract void MovtA1(CodeGenContext context, uint encoding); + static abstract void MovtT1(CodeGenContext context, uint encoding); + static abstract void MovIA1(CodeGenContext context, uint encoding); + static abstract void MovIA2(CodeGenContext context, uint encoding); + static abstract void MovIT1(CodeGenContext context, uint encoding); + static abstract void MovIT2(CodeGenContext context, uint encoding); + static abstract void MovIT3(CodeGenContext context, uint encoding); + static abstract void MovRA1(CodeGenContext context, uint encoding); + static abstract void MovRT1(CodeGenContext context, uint encoding); + static abstract void MovRT2(CodeGenContext context, uint encoding); + static abstract void MovRT3(CodeGenContext context, uint encoding); + static abstract void MovRrA1(CodeGenContext context, uint encoding); + static abstract void MovRrT1(CodeGenContext context, uint encoding); + static abstract void MovRrT2(CodeGenContext context, uint encoding); + static abstract void MrcA1(CodeGenContext context, uint encoding); + static abstract void MrcT1(CodeGenContext context, uint encoding); + static abstract void MrrcA1(CodeGenContext context, uint encoding); + static abstract void MrrcT1(CodeGenContext context, uint encoding); + static abstract void MrsA1(CodeGenContext context, uint encoding); + static abstract void MrsT1(CodeGenContext context, uint encoding); + static abstract void MrsBrA1(CodeGenContext context, uint encoding); + static abstract void MrsBrT1(CodeGenContext context, uint encoding); + static abstract void MsrBrA1(CodeGenContext context, uint encoding); + static abstract void MsrBrT1(CodeGenContext context, uint encoding); + static abstract void MsrIA1(CodeGenContext context, uint encoding); + static abstract void MsrRA1(CodeGenContext context, uint encoding); + static abstract void MsrRT1(CodeGenContext context, uint encoding); + static abstract void MulA1(CodeGenContext context, uint encoding); + static abstract void MulT1(CodeGenContext context, uint encoding); + static abstract void MulT2(CodeGenContext context, uint encoding); + static abstract void MvnIA1(CodeGenContext context, uint encoding); + static abstract void MvnIT1(CodeGenContext context, uint encoding); + static abstract void MvnRA1(CodeGenContext context, uint encoding); + static abstract void MvnRT1(CodeGenContext context, uint encoding); + static abstract void MvnRT2(CodeGenContext context, uint encoding); + static abstract void MvnRrA1(CodeGenContext context, uint encoding); + static abstract void NopA1(CodeGenContext context, uint encoding); + static abstract void NopT1(CodeGenContext context, uint encoding); + static abstract void NopT2(CodeGenContext context, uint encoding); + static abstract void OrnIT1(CodeGenContext context, uint encoding); + static abstract void OrnRT1(CodeGenContext context, uint encoding); + static abstract void OrrIA1(CodeGenContext context, uint encoding); + static abstract void OrrIT1(CodeGenContext context, uint encoding); + static abstract void OrrRA1(CodeGenContext context, uint encoding); + static abstract void OrrRT1(CodeGenContext context, uint encoding); + static abstract void OrrRT2(CodeGenContext context, uint encoding); + static abstract void OrrRrA1(CodeGenContext context, uint encoding); + static abstract void PkhA1(CodeGenContext context, uint encoding); + static abstract void PkhT1(CodeGenContext context, uint encoding); + static abstract void PldIA1(CodeGenContext context, uint encoding); + static abstract void PldIT1(CodeGenContext context, uint encoding); + static abstract void PldIT2(CodeGenContext context, uint encoding); + static abstract void PldLA1(CodeGenContext context, uint encoding); + static abstract void PldLT1(CodeGenContext context, uint encoding); + static abstract void PldRA1(CodeGenContext context, uint encoding); + static abstract void PldRT1(CodeGenContext context, uint encoding); + static abstract void PliIA1(CodeGenContext context, uint encoding); + static abstract void PliIT1(CodeGenContext context, uint encoding); + static abstract void PliIT2(CodeGenContext context, uint encoding); + static abstract void PliIT3(CodeGenContext context, uint encoding); + static abstract void PliRA1(CodeGenContext context, uint encoding); + static abstract void PliRT1(CodeGenContext context, uint encoding); + static abstract void PopT1(CodeGenContext context, uint encoding); + static abstract void PssbbA1(CodeGenContext context, uint encoding); + static abstract void PssbbT1(CodeGenContext context, uint encoding); + static abstract void PushT1(CodeGenContext context, uint encoding); + static abstract void QaddA1(CodeGenContext context, uint encoding); + static abstract void QaddT1(CodeGenContext context, uint encoding); + static abstract void Qadd16A1(CodeGenContext context, uint encoding); + static abstract void Qadd16T1(CodeGenContext context, uint encoding); + static abstract void Qadd8A1(CodeGenContext context, uint encoding); + static abstract void Qadd8T1(CodeGenContext context, uint encoding); + static abstract void QasxA1(CodeGenContext context, uint encoding); + static abstract void QasxT1(CodeGenContext context, uint encoding); + static abstract void QdaddA1(CodeGenContext context, uint encoding); + static abstract void QdaddT1(CodeGenContext context, uint encoding); + static abstract void QdsubA1(CodeGenContext context, uint encoding); + static abstract void QdsubT1(CodeGenContext context, uint encoding); + static abstract void QsaxA1(CodeGenContext context, uint encoding); + static abstract void QsaxT1(CodeGenContext context, uint encoding); + static abstract void QsubA1(CodeGenContext context, uint encoding); + static abstract void QsubT1(CodeGenContext context, uint encoding); + static abstract void Qsub16A1(CodeGenContext context, uint encoding); + static abstract void Qsub16T1(CodeGenContext context, uint encoding); + static abstract void Qsub8A1(CodeGenContext context, uint encoding); + static abstract void Qsub8T1(CodeGenContext context, uint encoding); + static abstract void RbitA1(CodeGenContext context, uint encoding); + static abstract void RbitT1(CodeGenContext context, uint encoding); + static abstract void RevA1(CodeGenContext context, uint encoding); + static abstract void RevT1(CodeGenContext context, uint encoding); + static abstract void RevT2(CodeGenContext context, uint encoding); + static abstract void Rev16A1(CodeGenContext context, uint encoding); + static abstract void Rev16T1(CodeGenContext context, uint encoding); + static abstract void Rev16T2(CodeGenContext context, uint encoding); + static abstract void RevshA1(CodeGenContext context, uint encoding); + static abstract void RevshT1(CodeGenContext context, uint encoding); + static abstract void RevshT2(CodeGenContext context, uint encoding); + static abstract void RfeA1(CodeGenContext context, uint encoding); + static abstract void RfeT1(CodeGenContext context, uint encoding); + static abstract void RfeT2(CodeGenContext context, uint encoding); + static abstract void RsbIA1(CodeGenContext context, uint encoding); + static abstract void RsbIT1(CodeGenContext context, uint encoding); + static abstract void RsbIT2(CodeGenContext context, uint encoding); + static abstract void RsbRA1(CodeGenContext context, uint encoding); + static abstract void RsbRT1(CodeGenContext context, uint encoding); + static abstract void RsbRrA1(CodeGenContext context, uint encoding); + static abstract void RscIA1(CodeGenContext context, uint encoding); + static abstract void RscRA1(CodeGenContext context, uint encoding); + static abstract void RscRrA1(CodeGenContext context, uint encoding); + static abstract void Sadd16A1(CodeGenContext context, uint encoding); + static abstract void Sadd16T1(CodeGenContext context, uint encoding); + static abstract void Sadd8A1(CodeGenContext context, uint encoding); + static abstract void Sadd8T1(CodeGenContext context, uint encoding); + static abstract void SasxA1(CodeGenContext context, uint encoding); + static abstract void SasxT1(CodeGenContext context, uint encoding); + static abstract void SbA1(CodeGenContext context, uint encoding); + static abstract void SbT1(CodeGenContext context, uint encoding); + static abstract void SbcIA1(CodeGenContext context, uint encoding); + static abstract void SbcIT1(CodeGenContext context, uint encoding); + static abstract void SbcRA1(CodeGenContext context, uint encoding); + static abstract void SbcRT1(CodeGenContext context, uint encoding); + static abstract void SbcRT2(CodeGenContext context, uint encoding); + static abstract void SbcRrA1(CodeGenContext context, uint encoding); + static abstract void SbfxA1(CodeGenContext context, uint encoding); + static abstract void SbfxT1(CodeGenContext context, uint encoding); + static abstract void SdivA1(CodeGenContext context, uint encoding); + static abstract void SdivT1(CodeGenContext context, uint encoding); + static abstract void SelA1(CodeGenContext context, uint encoding); + static abstract void SelT1(CodeGenContext context, uint encoding); + static abstract void SetendA1(CodeGenContext context, uint encoding); + static abstract void SetendT1(CodeGenContext context, uint encoding); + static abstract void SetpanA1(CodeGenContext context, uint encoding); + static abstract void SetpanT1(CodeGenContext context, uint encoding); + static abstract void SevA1(CodeGenContext context, uint encoding); + static abstract void SevT1(CodeGenContext context, uint encoding); + static abstract void SevT2(CodeGenContext context, uint encoding); + static abstract void SevlA1(CodeGenContext context, uint encoding); + static abstract void SevlT1(CodeGenContext context, uint encoding); + static abstract void SevlT2(CodeGenContext context, uint encoding); + static abstract void Sha1cA1(CodeGenContext context, uint encoding); + static abstract void Sha1cT1(CodeGenContext context, uint encoding); + static abstract void Sha1hA1(CodeGenContext context, uint encoding); + static abstract void Sha1hT1(CodeGenContext context, uint encoding); + static abstract void Sha1mA1(CodeGenContext context, uint encoding); + static abstract void Sha1mT1(CodeGenContext context, uint encoding); + static abstract void Sha1pA1(CodeGenContext context, uint encoding); + static abstract void Sha1pT1(CodeGenContext context, uint encoding); + static abstract void Sha1su0A1(CodeGenContext context, uint encoding); + static abstract void Sha1su0T1(CodeGenContext context, uint encoding); + static abstract void Sha1su1A1(CodeGenContext context, uint encoding); + static abstract void Sha1su1T1(CodeGenContext context, uint encoding); + static abstract void Sha256hA1(CodeGenContext context, uint encoding); + static abstract void Sha256hT1(CodeGenContext context, uint encoding); + static abstract void Sha256h2A1(CodeGenContext context, uint encoding); + static abstract void Sha256h2T1(CodeGenContext context, uint encoding); + static abstract void Sha256su0A1(CodeGenContext context, uint encoding); + static abstract void Sha256su0T1(CodeGenContext context, uint encoding); + static abstract void Sha256su1A1(CodeGenContext context, uint encoding); + static abstract void Sha256su1T1(CodeGenContext context, uint encoding); + static abstract void Shadd16A1(CodeGenContext context, uint encoding); + static abstract void Shadd16T1(CodeGenContext context, uint encoding); + static abstract void Shadd8A1(CodeGenContext context, uint encoding); + static abstract void Shadd8T1(CodeGenContext context, uint encoding); + static abstract void ShasxA1(CodeGenContext context, uint encoding); + static abstract void ShasxT1(CodeGenContext context, uint encoding); + static abstract void ShsaxA1(CodeGenContext context, uint encoding); + static abstract void ShsaxT1(CodeGenContext context, uint encoding); + static abstract void Shsub16A1(CodeGenContext context, uint encoding); + static abstract void Shsub16T1(CodeGenContext context, uint encoding); + static abstract void Shsub8A1(CodeGenContext context, uint encoding); + static abstract void Shsub8T1(CodeGenContext context, uint encoding); + static abstract void SmcA1(CodeGenContext context, uint encoding); + static abstract void SmcT1(CodeGenContext context, uint encoding); + static abstract void SmlabbA1(CodeGenContext context, uint encoding); + static abstract void SmlabbT1(CodeGenContext context, uint encoding); + static abstract void SmladA1(CodeGenContext context, uint encoding); + static abstract void SmladT1(CodeGenContext context, uint encoding); + static abstract void SmlalA1(CodeGenContext context, uint encoding); + static abstract void SmlalT1(CodeGenContext context, uint encoding); + static abstract void SmlalbbA1(CodeGenContext context, uint encoding); + static abstract void SmlalbbT1(CodeGenContext context, uint encoding); + static abstract void SmlaldA1(CodeGenContext context, uint encoding); + static abstract void SmlaldT1(CodeGenContext context, uint encoding); + static abstract void SmlawbA1(CodeGenContext context, uint encoding); + static abstract void SmlawbT1(CodeGenContext context, uint encoding); + static abstract void SmlsdA1(CodeGenContext context, uint encoding); + static abstract void SmlsdT1(CodeGenContext context, uint encoding); + static abstract void SmlsldA1(CodeGenContext context, uint encoding); + static abstract void SmlsldT1(CodeGenContext context, uint encoding); + static abstract void SmmlaA1(CodeGenContext context, uint encoding); + static abstract void SmmlaT1(CodeGenContext context, uint encoding); + static abstract void SmmlsA1(CodeGenContext context, uint encoding); + static abstract void SmmlsT1(CodeGenContext context, uint encoding); + static abstract void SmmulA1(CodeGenContext context, uint encoding); + static abstract void SmmulT1(CodeGenContext context, uint encoding); + static abstract void SmuadA1(CodeGenContext context, uint encoding); + static abstract void SmuadT1(CodeGenContext context, uint encoding); + static abstract void SmulbbA1(CodeGenContext context, uint encoding); + static abstract void SmulbbT1(CodeGenContext context, uint encoding); + static abstract void SmullA1(CodeGenContext context, uint encoding); + static abstract void SmullT1(CodeGenContext context, uint encoding); + static abstract void SmulwbA1(CodeGenContext context, uint encoding); + static abstract void SmulwbT1(CodeGenContext context, uint encoding); + static abstract void SmusdA1(CodeGenContext context, uint encoding); + static abstract void SmusdT1(CodeGenContext context, uint encoding); + static abstract void SrsA1(CodeGenContext context, uint encoding); + static abstract void SrsT1(CodeGenContext context, uint encoding); + static abstract void SrsT2(CodeGenContext context, uint encoding); + static abstract void SsatA1(CodeGenContext context, uint encoding); + static abstract void SsatT1(CodeGenContext context, uint encoding); + static abstract void Ssat16A1(CodeGenContext context, uint encoding); + static abstract void Ssat16T1(CodeGenContext context, uint encoding); + static abstract void SsaxA1(CodeGenContext context, uint encoding); + static abstract void SsaxT1(CodeGenContext context, uint encoding); + static abstract void SsbbA1(CodeGenContext context, uint encoding); + static abstract void SsbbT1(CodeGenContext context, uint encoding); + static abstract void Ssub16A1(CodeGenContext context, uint encoding); + static abstract void Ssub16T1(CodeGenContext context, uint encoding); + static abstract void Ssub8A1(CodeGenContext context, uint encoding); + static abstract void Ssub8T1(CodeGenContext context, uint encoding); + static abstract void StcA1(CodeGenContext context, uint encoding); + static abstract void StcT1(CodeGenContext context, uint encoding); + static abstract void StlA1(CodeGenContext context, uint encoding); + static abstract void StlT1(CodeGenContext context, uint encoding); + static abstract void StlbA1(CodeGenContext context, uint encoding); + static abstract void StlbT1(CodeGenContext context, uint encoding); + static abstract void StlexA1(CodeGenContext context, uint encoding); + static abstract void StlexT1(CodeGenContext context, uint encoding); + static abstract void StlexbA1(CodeGenContext context, uint encoding); + static abstract void StlexbT1(CodeGenContext context, uint encoding); + static abstract void StlexdA1(CodeGenContext context, uint encoding); + static abstract void StlexdT1(CodeGenContext context, uint encoding); + static abstract void StlexhA1(CodeGenContext context, uint encoding); + static abstract void StlexhT1(CodeGenContext context, uint encoding); + static abstract void StlhA1(CodeGenContext context, uint encoding); + static abstract void StlhT1(CodeGenContext context, uint encoding); + static abstract void StmA1(CodeGenContext context, uint encoding); + static abstract void StmT1(CodeGenContext context, uint encoding); + static abstract void StmT2(CodeGenContext context, uint encoding); + static abstract void StmdaA1(CodeGenContext context, uint encoding); + static abstract void StmdbA1(CodeGenContext context, uint encoding); + static abstract void StmdbT1(CodeGenContext context, uint encoding); + static abstract void StmibA1(CodeGenContext context, uint encoding); + static abstract void StmUA1(CodeGenContext context, uint encoding); + static abstract void StrbtA1(CodeGenContext context, uint encoding); + static abstract void StrbtA2(CodeGenContext context, uint encoding); + static abstract void StrbtT1(CodeGenContext context, uint encoding); + static abstract void StrbIA1(CodeGenContext context, uint encoding); + static abstract void StrbIT1(CodeGenContext context, uint encoding); + static abstract void StrbIT2(CodeGenContext context, uint encoding); + static abstract void StrbIT3(CodeGenContext context, uint encoding); + static abstract void StrbRA1(CodeGenContext context, uint encoding); + static abstract void StrbRT1(CodeGenContext context, uint encoding); + static abstract void StrbRT2(CodeGenContext context, uint encoding); + static abstract void StrdIA1(CodeGenContext context, uint encoding); + static abstract void StrdIT1(CodeGenContext context, uint encoding); + static abstract void StrdRA1(CodeGenContext context, uint encoding); + static abstract void StrexA1(CodeGenContext context, uint encoding); + static abstract void StrexT1(CodeGenContext context, uint encoding); + static abstract void StrexbA1(CodeGenContext context, uint encoding); + static abstract void StrexbT1(CodeGenContext context, uint encoding); + static abstract void StrexdA1(CodeGenContext context, uint encoding); + static abstract void StrexdT1(CodeGenContext context, uint encoding); + static abstract void StrexhA1(CodeGenContext context, uint encoding); + static abstract void StrexhT1(CodeGenContext context, uint encoding); + static abstract void StrhtA1(CodeGenContext context, uint encoding); + static abstract void StrhtA2(CodeGenContext context, uint encoding); + static abstract void StrhtT1(CodeGenContext context, uint encoding); + static abstract void StrhIA1(CodeGenContext context, uint encoding); + static abstract void StrhIT1(CodeGenContext context, uint encoding); + static abstract void StrhIT2(CodeGenContext context, uint encoding); + static abstract void StrhIT3(CodeGenContext context, uint encoding); + static abstract void StrhRA1(CodeGenContext context, uint encoding); + static abstract void StrhRT1(CodeGenContext context, uint encoding); + static abstract void StrhRT2(CodeGenContext context, uint encoding); + static abstract void StrtA1(CodeGenContext context, uint encoding); + static abstract void StrtA2(CodeGenContext context, uint encoding); + static abstract void StrtT1(CodeGenContext context, uint encoding); + static abstract void StrIA1(CodeGenContext context, uint encoding); + static abstract void StrIT1(CodeGenContext context, uint encoding); + static abstract void StrIT2(CodeGenContext context, uint encoding); + static abstract void StrIT3(CodeGenContext context, uint encoding); + static abstract void StrIT4(CodeGenContext context, uint encoding); + static abstract void StrRA1(CodeGenContext context, uint encoding); + static abstract void StrRT1(CodeGenContext context, uint encoding); + static abstract void StrRT2(CodeGenContext context, uint encoding); + static abstract void SubIA1(CodeGenContext context, uint encoding); + static abstract void SubIT1(CodeGenContext context, uint encoding); + static abstract void SubIT2(CodeGenContext context, uint encoding); + static abstract void SubIT3(CodeGenContext context, uint encoding); + static abstract void SubIT4(CodeGenContext context, uint encoding); + static abstract void SubIT5(CodeGenContext context, uint encoding); + static abstract void SubRA1(CodeGenContext context, uint encoding); + static abstract void SubRT1(CodeGenContext context, uint encoding); + static abstract void SubRT2(CodeGenContext context, uint encoding); + static abstract void SubRrA1(CodeGenContext context, uint encoding); + static abstract void SubSpIA1(CodeGenContext context, uint encoding); + static abstract void SubSpIT1(CodeGenContext context, uint encoding); + static abstract void SubSpIT2(CodeGenContext context, uint encoding); + static abstract void SubSpIT3(CodeGenContext context, uint encoding); + static abstract void SubSpRA1(CodeGenContext context, uint encoding); + static abstract void SubSpRT1(CodeGenContext context, uint encoding); + static abstract void SvcA1(CodeGenContext context, uint encoding); + static abstract void SvcT1(CodeGenContext context, uint encoding); + static abstract void SxtabA1(CodeGenContext context, uint encoding); + static abstract void SxtabT1(CodeGenContext context, uint encoding); + static abstract void Sxtab16A1(CodeGenContext context, uint encoding); + static abstract void Sxtab16T1(CodeGenContext context, uint encoding); + static abstract void SxtahA1(CodeGenContext context, uint encoding); + static abstract void SxtahT1(CodeGenContext context, uint encoding); + static abstract void SxtbA1(CodeGenContext context, uint encoding); + static abstract void SxtbT1(CodeGenContext context, uint encoding); + static abstract void SxtbT2(CodeGenContext context, uint encoding); + static abstract void Sxtb16A1(CodeGenContext context, uint encoding); + static abstract void Sxtb16T1(CodeGenContext context, uint encoding); + static abstract void SxthA1(CodeGenContext context, uint encoding); + static abstract void SxthT1(CodeGenContext context, uint encoding); + static abstract void SxthT2(CodeGenContext context, uint encoding); + static abstract void TbbT1(CodeGenContext context, uint encoding); + static abstract void TeqIA1(CodeGenContext context, uint encoding); + static abstract void TeqIT1(CodeGenContext context, uint encoding); + static abstract void TeqRA1(CodeGenContext context, uint encoding); + static abstract void TeqRT1(CodeGenContext context, uint encoding); + static abstract void TeqRrA1(CodeGenContext context, uint encoding); + static abstract void TsbA1(CodeGenContext context, uint encoding); + static abstract void TsbT1(CodeGenContext context, uint encoding); + static abstract void TstIA1(CodeGenContext context, uint encoding); + static abstract void TstIT1(CodeGenContext context, uint encoding); + static abstract void TstRA1(CodeGenContext context, uint encoding); + static abstract void TstRT1(CodeGenContext context, uint encoding); + static abstract void TstRT2(CodeGenContext context, uint encoding); + static abstract void TstRrA1(CodeGenContext context, uint encoding); + static abstract void Uadd16A1(CodeGenContext context, uint encoding); + static abstract void Uadd16T1(CodeGenContext context, uint encoding); + static abstract void Uadd8A1(CodeGenContext context, uint encoding); + static abstract void Uadd8T1(CodeGenContext context, uint encoding); + static abstract void UasxA1(CodeGenContext context, uint encoding); + static abstract void UasxT1(CodeGenContext context, uint encoding); + static abstract void UbfxA1(CodeGenContext context, uint encoding); + static abstract void UbfxT1(CodeGenContext context, uint encoding); + static abstract void UdfA1(CodeGenContext context, uint encoding); + static abstract void UdfT1(CodeGenContext context, uint encoding); + static abstract void UdfT2(CodeGenContext context, uint encoding); + static abstract void UdivA1(CodeGenContext context, uint encoding); + static abstract void UdivT1(CodeGenContext context, uint encoding); + static abstract void Uhadd16A1(CodeGenContext context, uint encoding); + static abstract void Uhadd16T1(CodeGenContext context, uint encoding); + static abstract void Uhadd8A1(CodeGenContext context, uint encoding); + static abstract void Uhadd8T1(CodeGenContext context, uint encoding); + static abstract void UhasxA1(CodeGenContext context, uint encoding); + static abstract void UhasxT1(CodeGenContext context, uint encoding); + static abstract void UhsaxA1(CodeGenContext context, uint encoding); + static abstract void UhsaxT1(CodeGenContext context, uint encoding); + static abstract void Uhsub16A1(CodeGenContext context, uint encoding); + static abstract void Uhsub16T1(CodeGenContext context, uint encoding); + static abstract void Uhsub8A1(CodeGenContext context, uint encoding); + static abstract void Uhsub8T1(CodeGenContext context, uint encoding); + static abstract void UmaalA1(CodeGenContext context, uint encoding); + static abstract void UmaalT1(CodeGenContext context, uint encoding); + static abstract void UmlalA1(CodeGenContext context, uint encoding); + static abstract void UmlalT1(CodeGenContext context, uint encoding); + static abstract void UmullA1(CodeGenContext context, uint encoding); + static abstract void UmullT1(CodeGenContext context, uint encoding); + static abstract void Uqadd16A1(CodeGenContext context, uint encoding); + static abstract void Uqadd16T1(CodeGenContext context, uint encoding); + static abstract void Uqadd8A1(CodeGenContext context, uint encoding); + static abstract void Uqadd8T1(CodeGenContext context, uint encoding); + static abstract void UqasxA1(CodeGenContext context, uint encoding); + static abstract void UqasxT1(CodeGenContext context, uint encoding); + static abstract void UqsaxA1(CodeGenContext context, uint encoding); + static abstract void UqsaxT1(CodeGenContext context, uint encoding); + static abstract void Uqsub16A1(CodeGenContext context, uint encoding); + static abstract void Uqsub16T1(CodeGenContext context, uint encoding); + static abstract void Uqsub8A1(CodeGenContext context, uint encoding); + static abstract void Uqsub8T1(CodeGenContext context, uint encoding); + static abstract void Usad8A1(CodeGenContext context, uint encoding); + static abstract void Usad8T1(CodeGenContext context, uint encoding); + static abstract void Usada8A1(CodeGenContext context, uint encoding); + static abstract void Usada8T1(CodeGenContext context, uint encoding); + static abstract void UsatA1(CodeGenContext context, uint encoding); + static abstract void UsatT1(CodeGenContext context, uint encoding); + static abstract void Usat16A1(CodeGenContext context, uint encoding); + static abstract void Usat16T1(CodeGenContext context, uint encoding); + static abstract void UsaxA1(CodeGenContext context, uint encoding); + static abstract void UsaxT1(CodeGenContext context, uint encoding); + static abstract void Usub16A1(CodeGenContext context, uint encoding); + static abstract void Usub16T1(CodeGenContext context, uint encoding); + static abstract void Usub8A1(CodeGenContext context, uint encoding); + static abstract void Usub8T1(CodeGenContext context, uint encoding); + static abstract void UxtabA1(CodeGenContext context, uint encoding); + static abstract void UxtabT1(CodeGenContext context, uint encoding); + static abstract void Uxtab16A1(CodeGenContext context, uint encoding); + static abstract void Uxtab16T1(CodeGenContext context, uint encoding); + static abstract void UxtahA1(CodeGenContext context, uint encoding); + static abstract void UxtahT1(CodeGenContext context, uint encoding); + static abstract void UxtbA1(CodeGenContext context, uint encoding); + static abstract void UxtbT1(CodeGenContext context, uint encoding); + static abstract void UxtbT2(CodeGenContext context, uint encoding); + static abstract void Uxtb16A1(CodeGenContext context, uint encoding); + static abstract void Uxtb16T1(CodeGenContext context, uint encoding); + static abstract void UxthA1(CodeGenContext context, uint encoding); + static abstract void UxthT1(CodeGenContext context, uint encoding); + static abstract void UxthT2(CodeGenContext context, uint encoding); + static abstract void VabaA1(CodeGenContext context, uint encoding); + static abstract void VabaT1(CodeGenContext context, uint encoding); + static abstract void VabalA1(CodeGenContext context, uint encoding); + static abstract void VabalT1(CodeGenContext context, uint encoding); + static abstract void VabdlIA1(CodeGenContext context, uint encoding); + static abstract void VabdlIT1(CodeGenContext context, uint encoding); + static abstract void VabdFA1(CodeGenContext context, uint encoding); + static abstract void VabdFT1(CodeGenContext context, uint encoding); + static abstract void VabdIA1(CodeGenContext context, uint encoding); + static abstract void VabdIT1(CodeGenContext context, uint encoding); + static abstract void VabsA1(CodeGenContext context, uint encoding); + static abstract void VabsA2(CodeGenContext context, uint encoding); + static abstract void VabsT1(CodeGenContext context, uint encoding); + static abstract void VabsT2(CodeGenContext context, uint encoding); + static abstract void VacgeA1(CodeGenContext context, uint encoding); + static abstract void VacgeT1(CodeGenContext context, uint encoding); + static abstract void VacgtA1(CodeGenContext context, uint encoding); + static abstract void VacgtT1(CodeGenContext context, uint encoding); + static abstract void VaddhnA1(CodeGenContext context, uint encoding); + static abstract void VaddhnT1(CodeGenContext context, uint encoding); + static abstract void VaddlA1(CodeGenContext context, uint encoding); + static abstract void VaddlT1(CodeGenContext context, uint encoding); + static abstract void VaddwA1(CodeGenContext context, uint encoding); + static abstract void VaddwT1(CodeGenContext context, uint encoding); + static abstract void VaddFA1(CodeGenContext context, uint encoding); + static abstract void VaddFA2(CodeGenContext context, uint encoding); + static abstract void VaddFT1(CodeGenContext context, uint encoding); + static abstract void VaddFT2(CodeGenContext context, uint encoding); + static abstract void VaddIA1(CodeGenContext context, uint encoding); + static abstract void VaddIT1(CodeGenContext context, uint encoding); + static abstract void VandRA1(CodeGenContext context, uint encoding); + static abstract void VandRT1(CodeGenContext context, uint encoding); + static abstract void VbicIA1(CodeGenContext context, uint encoding); + static abstract void VbicIA2(CodeGenContext context, uint encoding); + static abstract void VbicIT1(CodeGenContext context, uint encoding); + static abstract void VbicIT2(CodeGenContext context, uint encoding); + static abstract void VbicRA1(CodeGenContext context, uint encoding); + static abstract void VbicRT1(CodeGenContext context, uint encoding); + static abstract void VbifA1(CodeGenContext context, uint encoding); + static abstract void VbifT1(CodeGenContext context, uint encoding); + static abstract void VbitA1(CodeGenContext context, uint encoding); + static abstract void VbitT1(CodeGenContext context, uint encoding); + static abstract void VbslA1(CodeGenContext context, uint encoding); + static abstract void VbslT1(CodeGenContext context, uint encoding); + static abstract void VcaddA1(CodeGenContext context, uint encoding); + static abstract void VcaddT1(CodeGenContext context, uint encoding); + static abstract void VceqIA1(CodeGenContext context, uint encoding); + static abstract void VceqIT1(CodeGenContext context, uint encoding); + static abstract void VceqRA1(CodeGenContext context, uint encoding); + static abstract void VceqRA2(CodeGenContext context, uint encoding); + static abstract void VceqRT1(CodeGenContext context, uint encoding); + static abstract void VceqRT2(CodeGenContext context, uint encoding); + static abstract void VcgeIA1(CodeGenContext context, uint encoding); + static abstract void VcgeIT1(CodeGenContext context, uint encoding); + static abstract void VcgeRA1(CodeGenContext context, uint encoding); + static abstract void VcgeRA2(CodeGenContext context, uint encoding); + static abstract void VcgeRT1(CodeGenContext context, uint encoding); + static abstract void VcgeRT2(CodeGenContext context, uint encoding); + static abstract void VcgtIA1(CodeGenContext context, uint encoding); + static abstract void VcgtIT1(CodeGenContext context, uint encoding); + static abstract void VcgtRA1(CodeGenContext context, uint encoding); + static abstract void VcgtRA2(CodeGenContext context, uint encoding); + static abstract void VcgtRT1(CodeGenContext context, uint encoding); + static abstract void VcgtRT2(CodeGenContext context, uint encoding); + static abstract void VcleIA1(CodeGenContext context, uint encoding); + static abstract void VcleIT1(CodeGenContext context, uint encoding); + static abstract void VclsA1(CodeGenContext context, uint encoding); + static abstract void VclsT1(CodeGenContext context, uint encoding); + static abstract void VcltIA1(CodeGenContext context, uint encoding); + static abstract void VcltIT1(CodeGenContext context, uint encoding); + static abstract void VclzA1(CodeGenContext context, uint encoding); + static abstract void VclzT1(CodeGenContext context, uint encoding); + static abstract void VcmlaA1(CodeGenContext context, uint encoding); + static abstract void VcmlaT1(CodeGenContext context, uint encoding); + static abstract void VcmlaSA1(CodeGenContext context, uint encoding); + static abstract void VcmlaST1(CodeGenContext context, uint encoding); + static abstract void VcmpA1(CodeGenContext context, uint encoding); + static abstract void VcmpA2(CodeGenContext context, uint encoding); + static abstract void VcmpT1(CodeGenContext context, uint encoding); + static abstract void VcmpT2(CodeGenContext context, uint encoding); + static abstract void VcmpeA1(CodeGenContext context, uint encoding); + static abstract void VcmpeA2(CodeGenContext context, uint encoding); + static abstract void VcmpeT1(CodeGenContext context, uint encoding); + static abstract void VcmpeT2(CodeGenContext context, uint encoding); + static abstract void VcntA1(CodeGenContext context, uint encoding); + static abstract void VcntT1(CodeGenContext context, uint encoding); + static abstract void VcvtaAsimdA1(CodeGenContext context, uint encoding); + static abstract void VcvtaAsimdT1(CodeGenContext context, uint encoding); + static abstract void VcvtaVfpA1(CodeGenContext context, uint encoding); + static abstract void VcvtaVfpT1(CodeGenContext context, uint encoding); + static abstract void VcvtbA1(CodeGenContext context, uint encoding); + static abstract void VcvtbT1(CodeGenContext context, uint encoding); + static abstract void VcvtbBfsA1(CodeGenContext context, uint encoding); + static abstract void VcvtbBfsT1(CodeGenContext context, uint encoding); + static abstract void VcvtmAsimdA1(CodeGenContext context, uint encoding); + static abstract void VcvtmAsimdT1(CodeGenContext context, uint encoding); + static abstract void VcvtmVfpA1(CodeGenContext context, uint encoding); + static abstract void VcvtmVfpT1(CodeGenContext context, uint encoding); + static abstract void VcvtnAsimdA1(CodeGenContext context, uint encoding); + static abstract void VcvtnAsimdT1(CodeGenContext context, uint encoding); + static abstract void VcvtnVfpA1(CodeGenContext context, uint encoding); + static abstract void VcvtnVfpT1(CodeGenContext context, uint encoding); + static abstract void VcvtpAsimdA1(CodeGenContext context, uint encoding); + static abstract void VcvtpAsimdT1(CodeGenContext context, uint encoding); + static abstract void VcvtpVfpA1(CodeGenContext context, uint encoding); + static abstract void VcvtpVfpT1(CodeGenContext context, uint encoding); + static abstract void VcvtrIvA1(CodeGenContext context, uint encoding); + static abstract void VcvtrIvT1(CodeGenContext context, uint encoding); + static abstract void VcvttA1(CodeGenContext context, uint encoding); + static abstract void VcvttT1(CodeGenContext context, uint encoding); + static abstract void VcvttBfsA1(CodeGenContext context, uint encoding); + static abstract void VcvttBfsT1(CodeGenContext context, uint encoding); + static abstract void VcvtBfsA1(CodeGenContext context, uint encoding); + static abstract void VcvtBfsT1(CodeGenContext context, uint encoding); + static abstract void VcvtDsA1(CodeGenContext context, uint encoding); + static abstract void VcvtDsT1(CodeGenContext context, uint encoding); + static abstract void VcvtHsA1(CodeGenContext context, uint encoding); + static abstract void VcvtHsT1(CodeGenContext context, uint encoding); + static abstract void VcvtIsA1(CodeGenContext context, uint encoding); + static abstract void VcvtIsT1(CodeGenContext context, uint encoding); + static abstract void VcvtIvA1(CodeGenContext context, uint encoding); + static abstract void VcvtIvT1(CodeGenContext context, uint encoding); + static abstract void VcvtViA1(CodeGenContext context, uint encoding); + static abstract void VcvtViT1(CodeGenContext context, uint encoding); + static abstract void VcvtXsA1(CodeGenContext context, uint encoding); + static abstract void VcvtXsT1(CodeGenContext context, uint encoding); + static abstract void VcvtXvA1(CodeGenContext context, uint encoding); + static abstract void VcvtXvT1(CodeGenContext context, uint encoding); + static abstract void VdivA1(CodeGenContext context, uint encoding); + static abstract void VdivT1(CodeGenContext context, uint encoding); + static abstract void VdotA1(CodeGenContext context, uint encoding); + static abstract void VdotT1(CodeGenContext context, uint encoding); + static abstract void VdotSA1(CodeGenContext context, uint encoding); + static abstract void VdotST1(CodeGenContext context, uint encoding); + static abstract void VdupRA1(CodeGenContext context, uint encoding); + static abstract void VdupRT1(CodeGenContext context, uint encoding); + static abstract void VdupSA1(CodeGenContext context, uint encoding); + static abstract void VdupST1(CodeGenContext context, uint encoding); + static abstract void VeorA1(CodeGenContext context, uint encoding); + static abstract void VeorT1(CodeGenContext context, uint encoding); + static abstract void VextA1(CodeGenContext context, uint encoding); + static abstract void VextT1(CodeGenContext context, uint encoding); + static abstract void VfmaA1(CodeGenContext context, uint encoding); + static abstract void VfmaA2(CodeGenContext context, uint encoding); + static abstract void VfmaT1(CodeGenContext context, uint encoding); + static abstract void VfmaT2(CodeGenContext context, uint encoding); + static abstract void VfmalA1(CodeGenContext context, uint encoding); + static abstract void VfmalT1(CodeGenContext context, uint encoding); + static abstract void VfmalSA1(CodeGenContext context, uint encoding); + static abstract void VfmalST1(CodeGenContext context, uint encoding); + static abstract void VfmaBfA1(CodeGenContext context, uint encoding); + static abstract void VfmaBfT1(CodeGenContext context, uint encoding); + static abstract void VfmaBfsA1(CodeGenContext context, uint encoding); + static abstract void VfmaBfsT1(CodeGenContext context, uint encoding); + static abstract void VfmsA1(CodeGenContext context, uint encoding); + static abstract void VfmsA2(CodeGenContext context, uint encoding); + static abstract void VfmsT1(CodeGenContext context, uint encoding); + static abstract void VfmsT2(CodeGenContext context, uint encoding); + static abstract void VfmslA1(CodeGenContext context, uint encoding); + static abstract void VfmslT1(CodeGenContext context, uint encoding); + static abstract void VfmslSA1(CodeGenContext context, uint encoding); + static abstract void VfmslST1(CodeGenContext context, uint encoding); + static abstract void VfnmaA1(CodeGenContext context, uint encoding); + static abstract void VfnmaT1(CodeGenContext context, uint encoding); + static abstract void VfnmsA1(CodeGenContext context, uint encoding); + static abstract void VfnmsT1(CodeGenContext context, uint encoding); + static abstract void VhaddA1(CodeGenContext context, uint encoding); + static abstract void VhaddT1(CodeGenContext context, uint encoding); + static abstract void VhsubA1(CodeGenContext context, uint encoding); + static abstract void VhsubT1(CodeGenContext context, uint encoding); + static abstract void VinsA1(CodeGenContext context, uint encoding); + static abstract void VinsT1(CodeGenContext context, uint encoding); + static abstract void VjcvtA1(CodeGenContext context, uint encoding); + static abstract void VjcvtT1(CodeGenContext context, uint encoding); + static abstract void Vld11A1(CodeGenContext context, uint encoding); + static abstract void Vld11A2(CodeGenContext context, uint encoding); + static abstract void Vld11A3(CodeGenContext context, uint encoding); + static abstract void Vld11T1(CodeGenContext context, uint encoding); + static abstract void Vld11T2(CodeGenContext context, uint encoding); + static abstract void Vld11T3(CodeGenContext context, uint encoding); + static abstract void Vld1AA1(CodeGenContext context, uint encoding); + static abstract void Vld1AT1(CodeGenContext context, uint encoding); + static abstract void Vld1MA1(CodeGenContext context, uint encoding); + static abstract void Vld1MA2(CodeGenContext context, uint encoding); + static abstract void Vld1MA3(CodeGenContext context, uint encoding); + static abstract void Vld1MA4(CodeGenContext context, uint encoding); + static abstract void Vld1MT1(CodeGenContext context, uint encoding); + static abstract void Vld1MT2(CodeGenContext context, uint encoding); + static abstract void Vld1MT3(CodeGenContext context, uint encoding); + static abstract void Vld1MT4(CodeGenContext context, uint encoding); + static abstract void Vld21A1(CodeGenContext context, uint encoding); + static abstract void Vld21A2(CodeGenContext context, uint encoding); + static abstract void Vld21A3(CodeGenContext context, uint encoding); + static abstract void Vld21T1(CodeGenContext context, uint encoding); + static abstract void Vld21T2(CodeGenContext context, uint encoding); + static abstract void Vld21T3(CodeGenContext context, uint encoding); + static abstract void Vld2AA1(CodeGenContext context, uint encoding); + static abstract void Vld2AT1(CodeGenContext context, uint encoding); + static abstract void Vld2MA1(CodeGenContext context, uint encoding); + static abstract void Vld2MA2(CodeGenContext context, uint encoding); + static abstract void Vld2MT1(CodeGenContext context, uint encoding); + static abstract void Vld2MT2(CodeGenContext context, uint encoding); + static abstract void Vld31A1(CodeGenContext context, uint encoding); + static abstract void Vld31A2(CodeGenContext context, uint encoding); + static abstract void Vld31A3(CodeGenContext context, uint encoding); + static abstract void Vld31T1(CodeGenContext context, uint encoding); + static abstract void Vld31T2(CodeGenContext context, uint encoding); + static abstract void Vld31T3(CodeGenContext context, uint encoding); + static abstract void Vld3AA1(CodeGenContext context, uint encoding); + static abstract void Vld3AT1(CodeGenContext context, uint encoding); + static abstract void Vld3MA1(CodeGenContext context, uint encoding); + static abstract void Vld3MT1(CodeGenContext context, uint encoding); + static abstract void Vld41A1(CodeGenContext context, uint encoding); + static abstract void Vld41A2(CodeGenContext context, uint encoding); + static abstract void Vld41A3(CodeGenContext context, uint encoding); + static abstract void Vld41T1(CodeGenContext context, uint encoding); + static abstract void Vld41T2(CodeGenContext context, uint encoding); + static abstract void Vld41T3(CodeGenContext context, uint encoding); + static abstract void Vld4AA1(CodeGenContext context, uint encoding); + static abstract void Vld4AT1(CodeGenContext context, uint encoding); + static abstract void Vld4MA1(CodeGenContext context, uint encoding); + static abstract void Vld4MT1(CodeGenContext context, uint encoding); + static abstract void VldmA1(CodeGenContext context, uint encoding); + static abstract void VldmA2(CodeGenContext context, uint encoding); + static abstract void VldmT1(CodeGenContext context, uint encoding); + static abstract void VldmT2(CodeGenContext context, uint encoding); + static abstract void VldrIA1(CodeGenContext context, uint encoding); + static abstract void VldrIT1(CodeGenContext context, uint encoding); + static abstract void VldrLA1(CodeGenContext context, uint encoding); + static abstract void VldrLT1(CodeGenContext context, uint encoding); + static abstract void VmaxnmA1(CodeGenContext context, uint encoding); + static abstract void VmaxnmA2(CodeGenContext context, uint encoding); + static abstract void VmaxnmT1(CodeGenContext context, uint encoding); + static abstract void VmaxnmT2(CodeGenContext context, uint encoding); + static abstract void VmaxFA1(CodeGenContext context, uint encoding); + static abstract void VmaxFT1(CodeGenContext context, uint encoding); + static abstract void VmaxIA1(CodeGenContext context, uint encoding); + static abstract void VmaxIT1(CodeGenContext context, uint encoding); + static abstract void VminnmA1(CodeGenContext context, uint encoding); + static abstract void VminnmA2(CodeGenContext context, uint encoding); + static abstract void VminnmT1(CodeGenContext context, uint encoding); + static abstract void VminnmT2(CodeGenContext context, uint encoding); + static abstract void VminFA1(CodeGenContext context, uint encoding); + static abstract void VminFT1(CodeGenContext context, uint encoding); + static abstract void VminIA1(CodeGenContext context, uint encoding); + static abstract void VminIT1(CodeGenContext context, uint encoding); + static abstract void VmlalIA1(CodeGenContext context, uint encoding); + static abstract void VmlalIT1(CodeGenContext context, uint encoding); + static abstract void VmlalSA1(CodeGenContext context, uint encoding); + static abstract void VmlalST1(CodeGenContext context, uint encoding); + static abstract void VmlaFA1(CodeGenContext context, uint encoding); + static abstract void VmlaFA2(CodeGenContext context, uint encoding); + static abstract void VmlaFT1(CodeGenContext context, uint encoding); + static abstract void VmlaFT2(CodeGenContext context, uint encoding); + static abstract void VmlaIA1(CodeGenContext context, uint encoding); + static abstract void VmlaIT1(CodeGenContext context, uint encoding); + static abstract void VmlaSA1(CodeGenContext context, uint encoding); + static abstract void VmlaST1(CodeGenContext context, uint encoding); + static abstract void VmlslIA1(CodeGenContext context, uint encoding); + static abstract void VmlslIT1(CodeGenContext context, uint encoding); + static abstract void VmlslSA1(CodeGenContext context, uint encoding); + static abstract void VmlslST1(CodeGenContext context, uint encoding); + static abstract void VmlsFA1(CodeGenContext context, uint encoding); + static abstract void VmlsFA2(CodeGenContext context, uint encoding); + static abstract void VmlsFT1(CodeGenContext context, uint encoding); + static abstract void VmlsFT2(CodeGenContext context, uint encoding); + static abstract void VmlsIA1(CodeGenContext context, uint encoding); + static abstract void VmlsIT1(CodeGenContext context, uint encoding); + static abstract void VmlsSA1(CodeGenContext context, uint encoding); + static abstract void VmlsST1(CodeGenContext context, uint encoding); + static abstract void VmmlaA1(CodeGenContext context, uint encoding); + static abstract void VmmlaT1(CodeGenContext context, uint encoding); + static abstract void VmovlA1(CodeGenContext context, uint encoding); + static abstract void VmovlT1(CodeGenContext context, uint encoding); + static abstract void VmovnA1(CodeGenContext context, uint encoding); + static abstract void VmovnT1(CodeGenContext context, uint encoding); + static abstract void VmovxA1(CodeGenContext context, uint encoding); + static abstract void VmovxT1(CodeGenContext context, uint encoding); + static abstract void VmovDA1(CodeGenContext context, uint encoding); + static abstract void VmovDT1(CodeGenContext context, uint encoding); + static abstract void VmovHA1(CodeGenContext context, uint encoding); + static abstract void VmovHT1(CodeGenContext context, uint encoding); + static abstract void VmovIA1(CodeGenContext context, uint encoding); + static abstract void VmovIA2(CodeGenContext context, uint encoding); + static abstract void VmovIA3(CodeGenContext context, uint encoding); + static abstract void VmovIA4(CodeGenContext context, uint encoding); + static abstract void VmovIA5(CodeGenContext context, uint encoding); + static abstract void VmovIT1(CodeGenContext context, uint encoding); + static abstract void VmovIT2(CodeGenContext context, uint encoding); + static abstract void VmovIT3(CodeGenContext context, uint encoding); + static abstract void VmovIT4(CodeGenContext context, uint encoding); + static abstract void VmovIT5(CodeGenContext context, uint encoding); + static abstract void VmovRA2(CodeGenContext context, uint encoding); + static abstract void VmovRT2(CodeGenContext context, uint encoding); + static abstract void VmovRsA1(CodeGenContext context, uint encoding); + static abstract void VmovRsT1(CodeGenContext context, uint encoding); + static abstract void VmovSA1(CodeGenContext context, uint encoding); + static abstract void VmovST1(CodeGenContext context, uint encoding); + static abstract void VmovSrA1(CodeGenContext context, uint encoding); + static abstract void VmovSrT1(CodeGenContext context, uint encoding); + static abstract void VmovSsA1(CodeGenContext context, uint encoding); + static abstract void VmovSsT1(CodeGenContext context, uint encoding); + static abstract void VmrsA1(CodeGenContext context, uint encoding); + static abstract void VmrsT1(CodeGenContext context, uint encoding); + static abstract void VmsrA1(CodeGenContext context, uint encoding); + static abstract void VmsrT1(CodeGenContext context, uint encoding); + static abstract void VmullIA1(CodeGenContext context, uint encoding); + static abstract void VmullIT1(CodeGenContext context, uint encoding); + static abstract void VmullSA1(CodeGenContext context, uint encoding); + static abstract void VmullST1(CodeGenContext context, uint encoding); + static abstract void VmulFA1(CodeGenContext context, uint encoding); + static abstract void VmulFA2(CodeGenContext context, uint encoding); + static abstract void VmulFT1(CodeGenContext context, uint encoding); + static abstract void VmulFT2(CodeGenContext context, uint encoding); + static abstract void VmulIA1(CodeGenContext context, uint encoding); + static abstract void VmulIT1(CodeGenContext context, uint encoding); + static abstract void VmulSA1(CodeGenContext context, uint encoding); + static abstract void VmulST1(CodeGenContext context, uint encoding); + static abstract void VmvnIA1(CodeGenContext context, uint encoding); + static abstract void VmvnIA2(CodeGenContext context, uint encoding); + static abstract void VmvnIA3(CodeGenContext context, uint encoding); + static abstract void VmvnIT1(CodeGenContext context, uint encoding); + static abstract void VmvnIT2(CodeGenContext context, uint encoding); + static abstract void VmvnIT3(CodeGenContext context, uint encoding); + static abstract void VmvnRA1(CodeGenContext context, uint encoding); + static abstract void VmvnRT1(CodeGenContext context, uint encoding); + static abstract void VnegA1(CodeGenContext context, uint encoding); + static abstract void VnegA2(CodeGenContext context, uint encoding); + static abstract void VnegT1(CodeGenContext context, uint encoding); + static abstract void VnegT2(CodeGenContext context, uint encoding); + static abstract void VnmlaA1(CodeGenContext context, uint encoding); + static abstract void VnmlaT1(CodeGenContext context, uint encoding); + static abstract void VnmlsA1(CodeGenContext context, uint encoding); + static abstract void VnmlsT1(CodeGenContext context, uint encoding); + static abstract void VnmulA1(CodeGenContext context, uint encoding); + static abstract void VnmulT1(CodeGenContext context, uint encoding); + static abstract void VornRA1(CodeGenContext context, uint encoding); + static abstract void VornRT1(CodeGenContext context, uint encoding); + static abstract void VorrIA1(CodeGenContext context, uint encoding); + static abstract void VorrIA2(CodeGenContext context, uint encoding); + static abstract void VorrIT1(CodeGenContext context, uint encoding); + static abstract void VorrIT2(CodeGenContext context, uint encoding); + static abstract void VorrRA1(CodeGenContext context, uint encoding); + static abstract void VorrRT1(CodeGenContext context, uint encoding); + static abstract void VpadalA1(CodeGenContext context, uint encoding); + static abstract void VpadalT1(CodeGenContext context, uint encoding); + static abstract void VpaddlA1(CodeGenContext context, uint encoding); + static abstract void VpaddlT1(CodeGenContext context, uint encoding); + static abstract void VpaddFA1(CodeGenContext context, uint encoding); + static abstract void VpaddFT1(CodeGenContext context, uint encoding); + static abstract void VpaddIA1(CodeGenContext context, uint encoding); + static abstract void VpaddIT1(CodeGenContext context, uint encoding); + static abstract void VpmaxFA1(CodeGenContext context, uint encoding); + static abstract void VpmaxFT1(CodeGenContext context, uint encoding); + static abstract void VpmaxIA1(CodeGenContext context, uint encoding); + static abstract void VpmaxIT1(CodeGenContext context, uint encoding); + static abstract void VpminFA1(CodeGenContext context, uint encoding); + static abstract void VpminFT1(CodeGenContext context, uint encoding); + static abstract void VpminIA1(CodeGenContext context, uint encoding); + static abstract void VpminIT1(CodeGenContext context, uint encoding); + static abstract void VqabsA1(CodeGenContext context, uint encoding); + static abstract void VqabsT1(CodeGenContext context, uint encoding); + static abstract void VqaddA1(CodeGenContext context, uint encoding); + static abstract void VqaddT1(CodeGenContext context, uint encoding); + static abstract void VqdmlalA1(CodeGenContext context, uint encoding); + static abstract void VqdmlalA2(CodeGenContext context, uint encoding); + static abstract void VqdmlalT1(CodeGenContext context, uint encoding); + static abstract void VqdmlalT2(CodeGenContext context, uint encoding); + static abstract void VqdmlslA1(CodeGenContext context, uint encoding); + static abstract void VqdmlslA2(CodeGenContext context, uint encoding); + static abstract void VqdmlslT1(CodeGenContext context, uint encoding); + static abstract void VqdmlslT2(CodeGenContext context, uint encoding); + static abstract void VqdmulhA1(CodeGenContext context, uint encoding); + static abstract void VqdmulhA2(CodeGenContext context, uint encoding); + static abstract void VqdmulhT1(CodeGenContext context, uint encoding); + static abstract void VqdmulhT2(CodeGenContext context, uint encoding); + static abstract void VqdmullA1(CodeGenContext context, uint encoding); + static abstract void VqdmullA2(CodeGenContext context, uint encoding); + static abstract void VqdmullT1(CodeGenContext context, uint encoding); + static abstract void VqdmullT2(CodeGenContext context, uint encoding); + static abstract void VqmovnA1(CodeGenContext context, uint encoding); + static abstract void VqmovnT1(CodeGenContext context, uint encoding); + static abstract void VqnegA1(CodeGenContext context, uint encoding); + static abstract void VqnegT1(CodeGenContext context, uint encoding); + static abstract void VqrdmlahA1(CodeGenContext context, uint encoding); + static abstract void VqrdmlahA2(CodeGenContext context, uint encoding); + static abstract void VqrdmlahT1(CodeGenContext context, uint encoding); + static abstract void VqrdmlahT2(CodeGenContext context, uint encoding); + static abstract void VqrdmlshA1(CodeGenContext context, uint encoding); + static abstract void VqrdmlshA2(CodeGenContext context, uint encoding); + static abstract void VqrdmlshT1(CodeGenContext context, uint encoding); + static abstract void VqrdmlshT2(CodeGenContext context, uint encoding); + static abstract void VqrdmulhA1(CodeGenContext context, uint encoding); + static abstract void VqrdmulhA2(CodeGenContext context, uint encoding); + static abstract void VqrdmulhT1(CodeGenContext context, uint encoding); + static abstract void VqrdmulhT2(CodeGenContext context, uint encoding); + static abstract void VqrshlA1(CodeGenContext context, uint encoding); + static abstract void VqrshlT1(CodeGenContext context, uint encoding); + static abstract void VqrshrnA1(CodeGenContext context, uint encoding); + static abstract void VqrshrnT1(CodeGenContext context, uint encoding); + static abstract void VqshlIA1(CodeGenContext context, uint encoding); + static abstract void VqshlIT1(CodeGenContext context, uint encoding); + static abstract void VqshlRA1(CodeGenContext context, uint encoding); + static abstract void VqshlRT1(CodeGenContext context, uint encoding); + static abstract void VqshrnA1(CodeGenContext context, uint encoding); + static abstract void VqshrnT1(CodeGenContext context, uint encoding); + static abstract void VqsubA1(CodeGenContext context, uint encoding); + static abstract void VqsubT1(CodeGenContext context, uint encoding); + static abstract void VraddhnA1(CodeGenContext context, uint encoding); + static abstract void VraddhnT1(CodeGenContext context, uint encoding); + static abstract void VrecpeA1(CodeGenContext context, uint encoding); + static abstract void VrecpeT1(CodeGenContext context, uint encoding); + static abstract void VrecpsA1(CodeGenContext context, uint encoding); + static abstract void VrecpsT1(CodeGenContext context, uint encoding); + static abstract void Vrev16A1(CodeGenContext context, uint encoding); + static abstract void Vrev16T1(CodeGenContext context, uint encoding); + static abstract void Vrev32A1(CodeGenContext context, uint encoding); + static abstract void Vrev32T1(CodeGenContext context, uint encoding); + static abstract void Vrev64A1(CodeGenContext context, uint encoding); + static abstract void Vrev64T1(CodeGenContext context, uint encoding); + static abstract void VrhaddA1(CodeGenContext context, uint encoding); + static abstract void VrhaddT1(CodeGenContext context, uint encoding); + static abstract void VrintaAsimdA1(CodeGenContext context, uint encoding); + static abstract void VrintaAsimdT1(CodeGenContext context, uint encoding); + static abstract void VrintaVfpA1(CodeGenContext context, uint encoding); + static abstract void VrintaVfpT1(CodeGenContext context, uint encoding); + static abstract void VrintmAsimdA1(CodeGenContext context, uint encoding); + static abstract void VrintmAsimdT1(CodeGenContext context, uint encoding); + static abstract void VrintmVfpA1(CodeGenContext context, uint encoding); + static abstract void VrintmVfpT1(CodeGenContext context, uint encoding); + static abstract void VrintnAsimdA1(CodeGenContext context, uint encoding); + static abstract void VrintnAsimdT1(CodeGenContext context, uint encoding); + static abstract void VrintnVfpA1(CodeGenContext context, uint encoding); + static abstract void VrintnVfpT1(CodeGenContext context, uint encoding); + static abstract void VrintpAsimdA1(CodeGenContext context, uint encoding); + static abstract void VrintpAsimdT1(CodeGenContext context, uint encoding); + static abstract void VrintpVfpA1(CodeGenContext context, uint encoding); + static abstract void VrintpVfpT1(CodeGenContext context, uint encoding); + static abstract void VrintrVfpA1(CodeGenContext context, uint encoding); + static abstract void VrintrVfpT1(CodeGenContext context, uint encoding); + static abstract void VrintxAsimdA1(CodeGenContext context, uint encoding); + static abstract void VrintxAsimdT1(CodeGenContext context, uint encoding); + static abstract void VrintxVfpA1(CodeGenContext context, uint encoding); + static abstract void VrintxVfpT1(CodeGenContext context, uint encoding); + static abstract void VrintzAsimdA1(CodeGenContext context, uint encoding); + static abstract void VrintzAsimdT1(CodeGenContext context, uint encoding); + static abstract void VrintzVfpA1(CodeGenContext context, uint encoding); + static abstract void VrintzVfpT1(CodeGenContext context, uint encoding); + static abstract void VrshlA1(CodeGenContext context, uint encoding); + static abstract void VrshlT1(CodeGenContext context, uint encoding); + static abstract void VrshrA1(CodeGenContext context, uint encoding); + static abstract void VrshrT1(CodeGenContext context, uint encoding); + static abstract void VrshrnA1(CodeGenContext context, uint encoding); + static abstract void VrshrnT1(CodeGenContext context, uint encoding); + static abstract void VrsqrteA1(CodeGenContext context, uint encoding); + static abstract void VrsqrteT1(CodeGenContext context, uint encoding); + static abstract void VrsqrtsA1(CodeGenContext context, uint encoding); + static abstract void VrsqrtsT1(CodeGenContext context, uint encoding); + static abstract void VrsraA1(CodeGenContext context, uint encoding); + static abstract void VrsraT1(CodeGenContext context, uint encoding); + static abstract void VrsubhnA1(CodeGenContext context, uint encoding); + static abstract void VrsubhnT1(CodeGenContext context, uint encoding); + static abstract void VsdotA1(CodeGenContext context, uint encoding); + static abstract void VsdotT1(CodeGenContext context, uint encoding); + static abstract void VsdotSA1(CodeGenContext context, uint encoding); + static abstract void VsdotST1(CodeGenContext context, uint encoding); + static abstract void VselA1(CodeGenContext context, uint encoding); + static abstract void VselT1(CodeGenContext context, uint encoding); + static abstract void VshllA1(CodeGenContext context, uint encoding); + static abstract void VshllA2(CodeGenContext context, uint encoding); + static abstract void VshllT1(CodeGenContext context, uint encoding); + static abstract void VshllT2(CodeGenContext context, uint encoding); + static abstract void VshlIA1(CodeGenContext context, uint encoding); + static abstract void VshlIT1(CodeGenContext context, uint encoding); + static abstract void VshlRA1(CodeGenContext context, uint encoding); + static abstract void VshlRT1(CodeGenContext context, uint encoding); + static abstract void VshrA1(CodeGenContext context, uint encoding); + static abstract void VshrT1(CodeGenContext context, uint encoding); + static abstract void VshrnA1(CodeGenContext context, uint encoding); + static abstract void VshrnT1(CodeGenContext context, uint encoding); + static abstract void VsliA1(CodeGenContext context, uint encoding); + static abstract void VsliT1(CodeGenContext context, uint encoding); + static abstract void VsmmlaA1(CodeGenContext context, uint encoding); + static abstract void VsmmlaT1(CodeGenContext context, uint encoding); + static abstract void VsqrtA1(CodeGenContext context, uint encoding); + static abstract void VsqrtT1(CodeGenContext context, uint encoding); + static abstract void VsraA1(CodeGenContext context, uint encoding); + static abstract void VsraT1(CodeGenContext context, uint encoding); + static abstract void VsriA1(CodeGenContext context, uint encoding); + static abstract void VsriT1(CodeGenContext context, uint encoding); + static abstract void Vst11A1(CodeGenContext context, uint encoding); + static abstract void Vst11A2(CodeGenContext context, uint encoding); + static abstract void Vst11A3(CodeGenContext context, uint encoding); + static abstract void Vst11T1(CodeGenContext context, uint encoding); + static abstract void Vst11T2(CodeGenContext context, uint encoding); + static abstract void Vst11T3(CodeGenContext context, uint encoding); + static abstract void Vst1MA1(CodeGenContext context, uint encoding); + static abstract void Vst1MA2(CodeGenContext context, uint encoding); + static abstract void Vst1MA3(CodeGenContext context, uint encoding); + static abstract void Vst1MA4(CodeGenContext context, uint encoding); + static abstract void Vst1MT1(CodeGenContext context, uint encoding); + static abstract void Vst1MT2(CodeGenContext context, uint encoding); + static abstract void Vst1MT3(CodeGenContext context, uint encoding); + static abstract void Vst1MT4(CodeGenContext context, uint encoding); + static abstract void Vst21A1(CodeGenContext context, uint encoding); + static abstract void Vst21A2(CodeGenContext context, uint encoding); + static abstract void Vst21A3(CodeGenContext context, uint encoding); + static abstract void Vst21T1(CodeGenContext context, uint encoding); + static abstract void Vst21T2(CodeGenContext context, uint encoding); + static abstract void Vst21T3(CodeGenContext context, uint encoding); + static abstract void Vst2MA1(CodeGenContext context, uint encoding); + static abstract void Vst2MA2(CodeGenContext context, uint encoding); + static abstract void Vst2MT1(CodeGenContext context, uint encoding); + static abstract void Vst2MT2(CodeGenContext context, uint encoding); + static abstract void Vst31A1(CodeGenContext context, uint encoding); + static abstract void Vst31A2(CodeGenContext context, uint encoding); + static abstract void Vst31A3(CodeGenContext context, uint encoding); + static abstract void Vst31T1(CodeGenContext context, uint encoding); + static abstract void Vst31T2(CodeGenContext context, uint encoding); + static abstract void Vst31T3(CodeGenContext context, uint encoding); + static abstract void Vst3MA1(CodeGenContext context, uint encoding); + static abstract void Vst3MT1(CodeGenContext context, uint encoding); + static abstract void Vst41A1(CodeGenContext context, uint encoding); + static abstract void Vst41A2(CodeGenContext context, uint encoding); + static abstract void Vst41A3(CodeGenContext context, uint encoding); + static abstract void Vst41T1(CodeGenContext context, uint encoding); + static abstract void Vst41T2(CodeGenContext context, uint encoding); + static abstract void Vst41T3(CodeGenContext context, uint encoding); + static abstract void Vst4MA1(CodeGenContext context, uint encoding); + static abstract void Vst4MT1(CodeGenContext context, uint encoding); + static abstract void VstmA1(CodeGenContext context, uint encoding); + static abstract void VstmA2(CodeGenContext context, uint encoding); + static abstract void VstmT1(CodeGenContext context, uint encoding); + static abstract void VstmT2(CodeGenContext context, uint encoding); + static abstract void VstrA1(CodeGenContext context, uint encoding); + static abstract void VstrT1(CodeGenContext context, uint encoding); + static abstract void VsubhnA1(CodeGenContext context, uint encoding); + static abstract void VsubhnT1(CodeGenContext context, uint encoding); + static abstract void VsublA1(CodeGenContext context, uint encoding); + static abstract void VsublT1(CodeGenContext context, uint encoding); + static abstract void VsubwA1(CodeGenContext context, uint encoding); + static abstract void VsubwT1(CodeGenContext context, uint encoding); + static abstract void VsubFA1(CodeGenContext context, uint encoding); + static abstract void VsubFA2(CodeGenContext context, uint encoding); + static abstract void VsubFT1(CodeGenContext context, uint encoding); + static abstract void VsubFT2(CodeGenContext context, uint encoding); + static abstract void VsubIA1(CodeGenContext context, uint encoding); + static abstract void VsubIT1(CodeGenContext context, uint encoding); + static abstract void VsudotSA1(CodeGenContext context, uint encoding); + static abstract void VsudotST1(CodeGenContext context, uint encoding); + static abstract void VswpA1(CodeGenContext context, uint encoding); + static abstract void VswpT1(CodeGenContext context, uint encoding); + static abstract void VtblA1(CodeGenContext context, uint encoding); + static abstract void VtblT1(CodeGenContext context, uint encoding); + static abstract void VtrnA1(CodeGenContext context, uint encoding); + static abstract void VtrnT1(CodeGenContext context, uint encoding); + static abstract void VtstA1(CodeGenContext context, uint encoding); + static abstract void VtstT1(CodeGenContext context, uint encoding); + static abstract void VudotA1(CodeGenContext context, uint encoding); + static abstract void VudotT1(CodeGenContext context, uint encoding); + static abstract void VudotSA1(CodeGenContext context, uint encoding); + static abstract void VudotST1(CodeGenContext context, uint encoding); + static abstract void VummlaA1(CodeGenContext context, uint encoding); + static abstract void VummlaT1(CodeGenContext context, uint encoding); + static abstract void VusdotA1(CodeGenContext context, uint encoding); + static abstract void VusdotT1(CodeGenContext context, uint encoding); + static abstract void VusdotSA1(CodeGenContext context, uint encoding); + static abstract void VusdotST1(CodeGenContext context, uint encoding); + static abstract void VusmmlaA1(CodeGenContext context, uint encoding); + static abstract void VusmmlaT1(CodeGenContext context, uint encoding); + static abstract void VuzpA1(CodeGenContext context, uint encoding); + static abstract void VuzpT1(CodeGenContext context, uint encoding); + static abstract void VzipA1(CodeGenContext context, uint encoding); + static abstract void VzipT1(CodeGenContext context, uint encoding); + static abstract void WfeA1(CodeGenContext context, uint encoding); + static abstract void WfeT1(CodeGenContext context, uint encoding); + static abstract void WfeT2(CodeGenContext context, uint encoding); + static abstract void WfiA1(CodeGenContext context, uint encoding); + static abstract void WfiT1(CodeGenContext context, uint encoding); + static abstract void WfiT2(CodeGenContext context, uint encoding); + static abstract void YieldA1(CodeGenContext context, uint encoding); + static abstract void YieldT1(CodeGenContext context, uint encoding); + static abstract void YieldT2(CodeGenContext context, uint encoding); + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/ImmUtils.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/ImmUtils.cs new file mode 100644 index 00000000..516845fc --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/ImmUtils.cs @@ -0,0 +1,137 @@ +using System.Numerics; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + static class ImmUtils + { + public static uint ExpandImm(uint imm) + { + return BitOperations.RotateRight((byte)imm, (int)(imm >> 8) * 2); + } + + public static bool ExpandedImmRotated(uint imm) + { + return (imm >> 8) != 0; + } + + public static uint ExpandImm(uint imm8, uint imm3, uint i) + { + uint imm = CombineImmU12(imm8, imm3, i); + + if (imm >> 10 == 0) + { + return ((imm >> 8) & 3) switch + { + 0 => (byte)imm, + 1 => (byte)imm * 0x00010001u, + 2 => (byte)imm * 0x01000100u, + 3 => (byte)imm * 0x01010101u, + _ => 0, + }; + } + else + { + return BitOperations.RotateRight(0x80u | (byte)imm, (int)(imm >> 7)); + } + } + + public static bool ExpandedImmRotated(uint imm8, uint imm3, uint i) + { + uint imm = CombineImmU12(imm8, imm3, i); + + return (imm >> 7) != 0; + } + + public static uint CombineImmU5(uint imm2, uint imm3) + { + return imm2 | (imm3 << 2); + } + + public static uint CombineImmU5IImm4(uint i, uint imm4) + { + return i | (imm4 << 1); + } + + public static uint CombineImmU8(uint imm4l, uint imm4h) + { + return imm4l | (imm4h << 4); + } + + public static uint CombineImmU8(uint imm4, uint imm3, uint i) + { + return imm4 | (imm3 << 4) | (i << 7); + } + + public static uint CombineImmU12(uint imm8, uint imm3, uint i) + { + return imm8 | (imm3 << 8) | (i << 11); + } + + public static uint CombineImmU16(uint imm12, uint imm4) + { + return imm12 | (imm4 << 12); + } + + public static uint CombineImmU16(uint imm8, uint imm3, uint i, uint imm4) + { + return imm8 | (imm3 << 8) | (i << 11) | (imm4 << 12); + } + + public static int CombineSImm20Times2(uint imm11, uint imm6, uint j1, uint j2, uint s) + { + int imm32 = (int)(imm11 | (imm6 << 11) | (j1 << 17) | (j2 << 18) | (s << 19)); + + return (imm32 << 13) >> 12; + } + + public static int CombineSImm24Times2(uint imm11, uint imm10, uint j1, uint j2, uint s) + { + uint i1 = j1 ^ s ^ 1; + uint i2 = j2 ^ s ^ 1; + + int imm32 = (int)(imm11 | (imm10 << 11) | (i2 << 21) | (i1 << 22) | (s << 23)); + + return (imm32 << 8) >> 7; + } + + public static int CombineSImm24Times4(uint imm10L, uint imm10H, uint j1, uint j2, uint s) + { + uint i1 = j1 ^ s ^ 1; + uint i2 = j2 ^ s ^ 1; + + int imm32 = (int)(imm10L | (imm10H << 10) | (i2 << 20) | (i1 << 21) | (s << 22)); + + return (imm32 << 9) >> 7; + } + + public static uint CombineRegisterList(uint registerList, uint m) + { + return registerList | (m << 14); + } + + public static uint CombineRegisterList(uint registerList, uint m, uint p) + { + return registerList | (m << 14) | (p << 15); + } + + public static int ExtractSImm24Times4(uint encoding) + { + return (int)(encoding << 8) >> 6; + } + + public static int ExtractT16UImm5Times2(uint encoding) + { + return (int)(encoding >> 18) & 0x3e; + } + + public static int ExtractT16SImm8Times2(uint encoding) + { + return (int)(encoding << 24) >> 23; + } + + public static int ExtractT16SImm11Times2(uint encoding) + { + return (int)(encoding << 21) >> 20; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/InstDecoders.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/InstDecoders.cs new file mode 100644 index 00000000..41b105e4 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/InstDecoders.cs @@ -0,0 +1,2927 @@ +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + readonly struct InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm12b0w12 + { + private readonly uint _value; + public InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint S => (_value >> 20) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstIb26w1Sb20w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8 + { + private readonly uint _value; + public InstIb26w1Sb20w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Imm3 => (_value >> 12) & 0x7; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint S => (_value >> 20) & 0x1; + public readonly uint I => (_value >> 26) & 0x1; + } + + readonly struct InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Stype => (_value >> 5) & 0x3; + public readonly uint Imm5 => (_value >> 7) & 0x1F; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint S => (_value >> 20) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRmb19w3Rdnb16w3 + { + private readonly uint _value; + public InstRmb19w3Rdnb16w3(uint value) => _value = value; + public readonly uint Rdn => (_value >> 16) & 0x7; + public readonly uint Rm => (_value >> 19) & 0x7; + } + + readonly struct InstSb20w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 + { + private readonly uint _value; + public InstSb20w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Stype => (_value >> 4) & 0x3; + public readonly uint Imm2 => (_value >> 6) & 0x3; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Imm3 => (_value >> 12) & 0x7; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint S => (_value >> 20) & 0x1; + } + + readonly struct InstCondb28w4Sb20w1Rnb16w4Rdb12w4Rsb8w4Stypeb5w2Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Sb20w1Rnb16w4Rdb12w4Rsb8w4Stypeb5w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Stype => (_value >> 5) & 0x3; + public readonly uint Rs => (_value >> 8) & 0xF; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint S => (_value >> 20) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstImm3b22w3Rnb19w3Rdb16w3 + { + private readonly uint _value; + public InstImm3b22w3Rnb19w3Rdb16w3(uint value) => _value = value; + public readonly uint Rd => (_value >> 16) & 0x7; + public readonly uint Rn => (_value >> 19) & 0x7; + public readonly uint Imm3 => (_value >> 22) & 0x7; + } + + readonly struct InstRdnb24w3Imm8b16w8 + { + private readonly uint _value; + public InstRdnb24w3Imm8b16w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 16) & 0xFF; + public readonly uint Rdn => (_value >> 24) & 0x7; + } + + readonly struct InstIb26w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8 + { + private readonly uint _value; + public InstIb26w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Imm3 => (_value >> 12) & 0x7; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint I => (_value >> 26) & 0x1; + } + + readonly struct InstRmb22w3Rnb19w3Rdb16w3 + { + private readonly uint _value; + public InstRmb22w3Rnb19w3Rdb16w3(uint value) => _value = value; + public readonly uint Rd => (_value >> 16) & 0x7; + public readonly uint Rn => (_value >> 19) & 0x7; + public readonly uint Rm => (_value >> 22) & 0x7; + } + + readonly struct InstDnb23w1Rmb19w4Rdnb16w3 + { + private readonly uint _value; + public InstDnb23w1Rmb19w4Rdnb16w3(uint value) => _value = value; + public readonly uint Rdn => (_value >> 16) & 0x7; + public readonly uint Rm => (_value >> 19) & 0xF; + public readonly uint Dn => (_value >> 23) & 0x1; + } + + readonly struct InstCondb28w4Sb20w1Rdb12w4Imm12b0w12 + { + private readonly uint _value; + public InstCondb28w4Sb20w1Rdb12w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint S => (_value >> 20) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRdb24w3Imm8b16w8 + { + private readonly uint _value; + public InstRdb24w3Imm8b16w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 16) & 0xFF; + public readonly uint Rd => (_value >> 24) & 0x7; + } + + readonly struct InstImm7b16w7 + { + private readonly uint _value; + public InstImm7b16w7(uint value) => _value = value; + public readonly uint Imm7 => (_value >> 16) & 0x7F; + } + + readonly struct InstIb26w1Sb20w1Imm3b12w3Rdb8w4Imm8b0w8 + { + private readonly uint _value; + public InstIb26w1Sb20w1Imm3b12w3Rdb8w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Imm3 => (_value >> 12) & 0x7; + public readonly uint S => (_value >> 20) & 0x1; + public readonly uint I => (_value >> 26) & 0x1; + } + + readonly struct InstIb26w1Imm3b12w3Rdb8w4Imm8b0w8 + { + private readonly uint _value; + public InstIb26w1Imm3b12w3Rdb8w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Imm3 => (_value >> 12) & 0x7; + public readonly uint I => (_value >> 26) & 0x1; + } + + readonly struct InstCondb28w4Sb20w1Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Sb20w1Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Stype => (_value >> 5) & 0x3; + public readonly uint Imm5 => (_value >> 7) & 0x1F; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint S => (_value >> 20) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstDmb23w1Rdmb16w3 + { + private readonly uint _value; + public InstDmb23w1Rdmb16w3(uint value) => _value = value; + public readonly uint Rdm => (_value >> 16) & 0x7; + public readonly uint Dm => (_value >> 23) & 0x1; + } + + readonly struct InstRmb19w4 + { + private readonly uint _value; + public InstRmb19w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 19) & 0xF; + } + + readonly struct InstSb20w1Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 + { + private readonly uint _value; + public InstSb20w1Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Stype => (_value >> 4) & 0x3; + public readonly uint Imm2 => (_value >> 6) & 0x3; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Imm3 => (_value >> 12) & 0x7; + public readonly uint S => (_value >> 20) & 0x1; + } + + readonly struct InstCondb28w4Rdb12w4Imm12b0w12 + { + private readonly uint _value; + public InstCondb28w4Rdb12w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Size => (_value >> 18) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstCondb28w4Imm24b0w24 + { + private readonly uint _value; + public InstCondb28w4Imm24b0w24(uint value) => _value = value; + public readonly uint Imm24 => (_value >> 0) & 0xFFFFFF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstCondb24w4Imm8b16w8 + { + private readonly uint _value; + public InstCondb24w4Imm8b16w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 16) & 0xFF; + public readonly uint Cond => (_value >> 24) & 0xF; + } + + readonly struct InstImm11b16w11 + { + private readonly uint _value; + public InstImm11b16w11(uint value) => _value = value; + public readonly uint Imm11 => (_value >> 16) & 0x7FF; + } + + readonly struct InstSb26w1Condb22w4Imm6b16w6J1b13w1J2b11w1Imm11b0w11 + { + private readonly uint _value; + public InstSb26w1Condb22w4Imm6b16w6J1b13w1J2b11w1Imm11b0w11(uint value) => _value = value; + public readonly uint Imm11 => (_value >> 0) & 0x7FF; + public readonly uint J2 => (_value >> 11) & 0x1; + public readonly uint J1 => (_value >> 13) & 0x1; + public readonly uint Imm6 => (_value >> 16) & 0x3F; + public readonly uint Cond => (_value >> 22) & 0xF; + public readonly uint S => (_value >> 26) & 0x1; + } + + readonly struct InstSb26w1Imm10b16w10J1b13w1J2b11w1Imm11b0w11 + { + private readonly uint _value; + public InstSb26w1Imm10b16w10J1b13w1J2b11w1Imm11b0w11(uint value) => _value = value; + public readonly uint Imm11 => (_value >> 0) & 0x7FF; + public readonly uint J2 => (_value >> 11) & 0x1; + public readonly uint J1 => (_value >> 13) & 0x1; + public readonly uint Imm10 => (_value >> 16) & 0x3FF; + public readonly uint S => (_value >> 26) & 0x1; + } + + readonly struct InstCondb28w4Msbb16w5Rdb12w4Lsbb7w5 + { + private readonly uint _value; + public InstCondb28w4Msbb16w5Rdb12w4Lsbb7w5(uint value) => _value = value; + public readonly uint Lsb => (_value >> 7) & 0x1F; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Msb => (_value >> 16) & 0x1F; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstImm3b12w3Rdb8w4Imm2b6w2Msbb0w5 + { + private readonly uint _value; + public InstImm3b12w3Rdb8w4Imm2b6w2Msbb0w5(uint value) => _value = value; + public readonly uint Msb => (_value >> 0) & 0x1F; + public readonly uint Imm2 => (_value >> 6) & 0x3; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Imm3 => (_value >> 12) & 0x7; + } + + readonly struct InstCondb28w4Msbb16w5Rdb12w4Lsbb7w5Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Msbb16w5Rdb12w4Lsbb7w5Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint Lsb => (_value >> 7) & 0x1F; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Msb => (_value >> 16) & 0x1F; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Imm3b12w3Rdb8w4Imm2b6w2Msbb0w5 + { + private readonly uint _value; + public InstRnb16w4Imm3b12w3Rdb8w4Imm2b6w2Msbb0w5(uint value) => _value = value; + public readonly uint Msb => (_value >> 0) & 0x1F; + public readonly uint Imm2 => (_value >> 6) & 0x3; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Imm3 => (_value >> 12) & 0x7; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Imm12b8w12Imm4b0w4 + { + private readonly uint _value; + public InstCondb28w4Imm12b8w12Imm4b0w4(uint value) => _value = value; + public readonly uint Imm4 => (_value >> 0) & 0xF; + public readonly uint Imm12 => (_value >> 8) & 0xFFF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstImm8b16w8 + { + private readonly uint _value; + public InstImm8b16w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 16) & 0xFF; + } + + readonly struct InstCondb28w4Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstHb24w1Imm24b0w24 + { + private readonly uint _value; + public InstHb24w1Imm24b0w24(uint value) => _value = value; + public readonly uint Imm24 => (_value >> 0) & 0xFFFFFF; + public readonly uint H => (_value >> 24) & 0x1; + } + + readonly struct InstSb26w1Imm10hb16w10J1b13w1J2b11w1Imm10lb1w10Hb0w1 + { + private readonly uint _value; + public InstSb26w1Imm10hb16w10J1b13w1J2b11w1Imm10lb1w10Hb0w1(uint value) => _value = value; + public readonly uint H => (_value >> 0) & 0x1; + public readonly uint Imm10l => (_value >> 1) & 0x3FF; + public readonly uint J2 => (_value >> 11) & 0x1; + public readonly uint J1 => (_value >> 13) & 0x1; + public readonly uint Imm10h => (_value >> 16) & 0x3FF; + public readonly uint S => (_value >> 26) & 0x1; + } + + readonly struct InstRmb16w4 + { + private readonly uint _value; + public InstRmb16w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 16) & 0xF; + } + + readonly struct InstOpb27w1Ib25w1Imm5b19w5Rnb16w3 + { + private readonly uint _value; + public InstOpb27w1Ib25w1Imm5b19w5Rnb16w3(uint value) => _value = value; + public readonly uint Rn => (_value >> 16) & 0x7; + public readonly uint Imm5 => (_value >> 19) & 0x1F; + public readonly uint I => (_value >> 25) & 0x1; + public readonly uint Op => (_value >> 27) & 0x1; + } + + readonly struct InstCondb28w4 + { + private readonly uint _value; + public InstCondb28w4(uint value) => _value = value; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstCondb28w4Rdb12w4Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdb12w4Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rdb8w4Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rdb8w4Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rnb16w4Imm12b0w12 + { + private readonly uint _value; + public InstCondb28w4Rnb16w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstIb26w1Rnb16w4Imm3b12w3Imm8b0w8 + { + private readonly uint _value; + public InstIb26w1Rnb16w4Imm3b12w3Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Imm3 => (_value >> 12) & 0x7; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint I => (_value >> 26) & 0x1; + } + + readonly struct InstCondb28w4Rnb16w4Imm5b7w5Stypeb5w2Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Rnb16w4Imm5b7w5Stypeb5w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Stype => (_value >> 5) & 0x3; + public readonly uint Imm5 => (_value >> 7) & 0x1F; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRmb19w3Rnb16w3 + { + private readonly uint _value; + public InstRmb19w3Rnb16w3(uint value) => _value = value; + public readonly uint Rn => (_value >> 16) & 0x7; + public readonly uint Rm => (_value >> 19) & 0x7; + } + + readonly struct InstRnb16w4Imm3b12w3Imm2b6w2Stypeb4w2Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Imm3b12w3Imm2b6w2Stypeb4w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Stype => (_value >> 4) & 0x3; + public readonly uint Imm2 => (_value >> 6) & 0x3; + public readonly uint Imm3 => (_value >> 12) & 0x7; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rnb16w4Rsb8w4Stypeb5w2Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Rnb16w4Rsb8w4Stypeb5w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Stype => (_value >> 5) & 0x3; + public readonly uint Rs => (_value >> 8) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb24w3Imm8b16w8 + { + private readonly uint _value; + public InstRnb24w3Imm8b16w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 16) & 0xFF; + public readonly uint Rn => (_value >> 24) & 0x7; + } + + readonly struct InstNb23w1Rmb19w4Rnb16w3 + { + private readonly uint _value; + public InstNb23w1Rmb19w4Rnb16w3(uint value) => _value = value; + public readonly uint Rn => (_value >> 16) & 0x7; + public readonly uint Rm => (_value >> 19) & 0xF; + public readonly uint N => (_value >> 23) & 0x1; + } + + readonly struct InstImodb18w2Mb17w1Ab8w1Ib7w1Fb6w1Modeb0w5 + { + private readonly uint _value; + public InstImodb18w2Mb17w1Ab8w1Ib7w1Fb6w1Modeb0w5(uint value) => _value = value; + public readonly uint Mode => (_value >> 0) & 0x1F; + public readonly uint F => (_value >> 6) & 0x1; + public readonly uint I => (_value >> 7) & 0x1; + public readonly uint A => (_value >> 8) & 0x1; + public readonly uint M => (_value >> 17) & 0x1; + public readonly uint Imod => (_value >> 18) & 0x3; + } + + readonly struct InstImb20w1Ab18w1Ib17w1Fb16w1 + { + private readonly uint _value; + public InstImb20w1Ab18w1Ib17w1Fb16w1(uint value) => _value = value; + public readonly uint F => (_value >> 16) & 0x1; + public readonly uint I => (_value >> 17) & 0x1; + public readonly uint A => (_value >> 18) & 0x1; + public readonly uint Im => (_value >> 20) & 0x1; + } + + readonly struct InstImodb9w2Mb8w1Ab7w1Ib6w1Fb5w1Modeb0w5 + { + private readonly uint _value; + public InstImodb9w2Mb8w1Ab7w1Ib6w1Fb5w1Modeb0w5(uint value) => _value = value; + public readonly uint Mode => (_value >> 0) & 0x1F; + public readonly uint F => (_value >> 5) & 0x1; + public readonly uint I => (_value >> 6) & 0x1; + public readonly uint A => (_value >> 7) & 0x1; + public readonly uint M => (_value >> 8) & 0x1; + public readonly uint Imod => (_value >> 9) & 0x3; + } + + readonly struct InstCondb28w4Szb21w2Rnb16w4Rdb12w4Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Szb21w2Rnb16w4Rdb12w4Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint Sz => (_value >> 21) & 0x3; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rdb8w4Szb4w2Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rdb8w4Szb4w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Sz => (_value >> 4) & 0x3; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Optionb0w4 + { + private readonly uint _value; + public InstCondb28w4Optionb0w4(uint value) => _value = value; + public readonly uint Option => (_value >> 0) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstOptionb0w4 + { + private readonly uint _value; + public InstOptionb0w4(uint value) => _value = value; + public readonly uint Option => (_value >> 0) & 0xF; + } + + readonly struct InstCondb28w4Pb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm871b1w7 + { + private readonly uint _value; + public InstCondb28w4Pb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm871b1w7(uint value) => _value = value; + public readonly uint Imm871 => (_value >> 1) & 0x7F; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstPb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm871b1w7 + { + private readonly uint _value; + public InstPb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm871b1w7(uint value) => _value = value; + public readonly uint Imm871 => (_value >> 1) & 0x7F; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + } + + readonly struct InstImm6b16w6 + { + private readonly uint _value; + public InstImm6b16w6(uint value) => _value = value; + public readonly uint Imm6 => (_value >> 16) & 0x3F; + } + + readonly struct InstFirstcondb20w4Maskb16w4 + { + private readonly uint _value; + public InstFirstcondb20w4Maskb16w4(uint value) => _value = value; + public readonly uint Mask => (_value >> 16) & 0xF; + public readonly uint Firstcond => (_value >> 20) & 0xF; + } + + readonly struct InstCondb28w4Rnb16w4Rtb12w4 + { + private readonly uint _value; + public InstCondb28w4Rnb16w4Rtb12w4(uint value) => _value = value; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rtb12w4 + { + private readonly uint _value; + public InstRnb16w4Rtb12w4(uint value) => _value = value; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstRnb16w4Rtb12w4Rt2b8w4 + { + private readonly uint _value; + public InstRnb16w4Rtb12w4Rt2b8w4(uint value) => _value = value; + public readonly uint Rt2 => (_value >> 8) & 0xF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Imm8b0w8 + { + private readonly uint _value; + public InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstPb24w1Ub23w1Wb21w1Rnb16w4Imm8b0w8 + { + private readonly uint _value; + public InstPb24w1Ub23w1Wb21w1Rnb16w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + } + + readonly struct InstCondb28w4Pb24w1Ub23w1Wb21w1Imm8b0w8 + { + private readonly uint _value; + public InstCondb28w4Pb24w1Ub23w1Wb21w1Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstPb24w1Ub23w1Wb21w1Imm8b0w8 + { + private readonly uint _value; + public InstPb24w1Ub23w1Wb21w1Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + } + + readonly struct InstCondb28w4Wb21w1Rnb16w4RegisterListb0w16 + { + private readonly uint _value; + public InstCondb28w4Wb21w1Rnb16w4RegisterListb0w16(uint value) => _value = value; + public readonly uint RegisterList => (_value >> 0) & 0xFFFF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb24w3RegisterListb16w8 + { + private readonly uint _value; + public InstRnb24w3RegisterListb16w8(uint value) => _value = value; + public readonly uint RegisterList => (_value >> 16) & 0xFF; + public readonly uint Rn => (_value >> 24) & 0x7; + } + + readonly struct InstWb21w1Rnb16w4Pb15w1Mb14w1RegisterListb0w14 + { + private readonly uint _value; + public InstWb21w1Rnb16w4Pb15w1Mb14w1RegisterListb0w14(uint value) => _value = value; + public readonly uint RegisterList => (_value >> 0) & 0x3FFF; + public readonly uint M => (_value >> 14) & 0x1; + public readonly uint P => (_value >> 15) & 0x1; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + } + + readonly struct InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm12b0w12 + { + private readonly uint _value; + public InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm5b7w5Stypeb5w2Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm5b7w5Stypeb5w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Stype => (_value >> 5) & 0x3; + public readonly uint Imm5 => (_value >> 7) & 0x1F; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rtb12w4Imm8b0w8 + { + private readonly uint _value; + public InstRnb16w4Rtb12w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm12b0w12 + { + private readonly uint _value; + public InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstImm5b22w5Rnb19w3Rtb16w3 + { + private readonly uint _value; + public InstImm5b22w5Rnb19w3Rtb16w3(uint value) => _value = value; + public readonly uint Rt => (_value >> 16) & 0x7; + public readonly uint Rn => (_value >> 19) & 0x7; + public readonly uint Imm5 => (_value >> 22) & 0x1F; + } + + readonly struct InstRnb16w4Rtb12w4Imm12b0w12 + { + private readonly uint _value; + public InstRnb16w4Rtb12w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstRnb16w4Rtb12w4Pb10w1Ub9w1Wb8w1Imm8b0w8 + { + private readonly uint _value; + public InstRnb16w4Rtb12w4Pb10w1Ub9w1Wb8w1Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint W => (_value >> 8) & 0x1; + public readonly uint U => (_value >> 9) & 0x1; + public readonly uint P => (_value >> 10) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Pb24w1Ub23w1Wb21w1Rtb12w4Imm12b0w12 + { + private readonly uint _value; + public InstCondb28w4Pb24w1Ub23w1Wb21w1Rtb12w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstUb23w1Rtb12w4Imm12b0w12 + { + private readonly uint _value; + public InstUb23w1Rtb12w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint U => (_value >> 23) & 0x1; + } + + readonly struct InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm5b7w5Stypeb5w2Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm5b7w5Stypeb5w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Stype => (_value >> 5) & 0x3; + public readonly uint Imm5 => (_value >> 7) & 0x1F; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRmb22w3Rnb19w3Rtb16w3 + { + private readonly uint _value; + public InstRmb22w3Rnb19w3Rtb16w3(uint value) => _value = value; + public readonly uint Rt => (_value >> 16) & 0x7; + public readonly uint Rn => (_value >> 19) & 0x7; + public readonly uint Rm => (_value >> 22) & 0x7; + } + + readonly struct InstRnb16w4Rtb12w4Imm2b4w2Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rtb12w4Imm2b4w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Imm2 => (_value >> 4) & 0x3; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm4hb8w4Imm4lb0w4 + { + private readonly uint _value; + public InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm4hb8w4Imm4lb0w4(uint value) => _value = value; + public readonly uint Imm4l => (_value >> 0) & 0xF; + public readonly uint Imm4h => (_value >> 8) & 0xF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstPb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Rt2b8w4Imm8b0w8 + { + private readonly uint _value; + public InstPb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Rt2b8w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Rt2 => (_value >> 8) & 0xF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + } + + readonly struct InstCondb28w4Ub23w1Rtb12w4Imm4hb8w4Imm4lb0w4 + { + private readonly uint _value; + public InstCondb28w4Ub23w1Rtb12w4Imm4hb8w4Imm4lb0w4(uint value) => _value = value; + public readonly uint Imm4l => (_value >> 0) & 0xF; + public readonly uint Imm4h => (_value >> 8) & 0xF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstPb24w1Ub23w1Wb21w1Rtb12w4Rt2b8w4Imm8b0w8 + { + private readonly uint _value; + public InstPb24w1Ub23w1Wb21w1Rtb12w4Rt2b8w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Rt2 => (_value >> 8) & 0xF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + } + + readonly struct InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm4hb8w4Imm4lb0w4 + { + private readonly uint _value; + public InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm4hb8w4Imm4lb0w4(uint value) => _value = value; + public readonly uint Imm4l => (_value >> 0) & 0xF; + public readonly uint Imm4h => (_value >> 8) & 0xF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstCondb28w4Ub23w1Rnb16w4Rtb12w4Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Ub23w1Rnb16w4Rtb12w4Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstCondb28w4Pb24w1Ub23w1Wb21w1Rtb12w4Imm4hb8w4Imm4lb0w4 + { + private readonly uint _value; + public InstCondb28w4Pb24w1Ub23w1Wb21w1Rtb12w4Imm4hb8w4Imm4lb0w4(uint value) => _value = value; + public readonly uint Imm4l => (_value >> 0) & 0xF; + public readonly uint Imm4h => (_value >> 8) & 0xF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRtb24w3Imm8b16w8 + { + private readonly uint _value; + public InstRtb24w3Imm8b16w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 16) & 0xFF; + public readonly uint Rt => (_value >> 24) & 0x7; + } + + readonly struct InstCondb28w4Opc1b21w3Crnb16w4Rtb12w4Coproc0b8w1Opc2b5w3Crmb0w4 + { + private readonly uint _value; + public InstCondb28w4Opc1b21w3Crnb16w4Rtb12w4Coproc0b8w1Opc2b5w3Crmb0w4(uint value) => _value = value; + public readonly uint Crm => (_value >> 0) & 0xF; + public readonly uint Opc2 => (_value >> 5) & 0x7; + public readonly uint Coproc0 => (_value >> 8) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Crn => (_value >> 16) & 0xF; + public readonly uint Opc1 => (_value >> 21) & 0x7; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstOpc1b21w3Crnb16w4Rtb12w4Coproc0b8w1Opc2b5w3Crmb0w4 + { + private readonly uint _value; + public InstOpc1b21w3Crnb16w4Rtb12w4Coproc0b8w1Opc2b5w3Crmb0w4(uint value) => _value = value; + public readonly uint Crm => (_value >> 0) & 0xF; + public readonly uint Opc2 => (_value >> 5) & 0x7; + public readonly uint Coproc0 => (_value >> 8) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Crn => (_value >> 16) & 0xF; + public readonly uint Opc1 => (_value >> 21) & 0x7; + } + + readonly struct InstCondb28w4Rt2b16w4Rtb12w4Coproc0b8w1Opc1b4w4Crmb0w4 + { + private readonly uint _value; + public InstCondb28w4Rt2b16w4Rtb12w4Coproc0b8w1Opc1b4w4Crmb0w4(uint value) => _value = value; + public readonly uint Crm => (_value >> 0) & 0xF; + public readonly uint Opc1 => (_value >> 4) & 0xF; + public readonly uint Coproc0 => (_value >> 8) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rt2 => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRt2b16w4Rtb12w4Coproc0b8w1Opc1b4w4Crmb0w4 + { + private readonly uint _value; + public InstRt2b16w4Rtb12w4Coproc0b8w1Opc1b4w4Crmb0w4(uint value) => _value = value; + public readonly uint Crm => (_value >> 0) & 0xF; + public readonly uint Opc1 => (_value >> 4) & 0xF; + public readonly uint Coproc0 => (_value >> 8) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rt2 => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Sb20w1Rdb16w4Rab12w4Rmb8w4Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Sb20w1Rdb16w4Rab12w4Rmb8w4Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Ra => (_value >> 12) & 0xF; + public readonly uint Rd => (_value >> 16) & 0xF; + public readonly uint S => (_value >> 20) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rab12w4Rdb8w4Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rab12w4Rdb8w4Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Ra => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rdb16w4Rab12w4Rmb8w4Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdb16w4Rab12w4Rmb8w4Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Ra => (_value >> 12) & 0xF; + public readonly uint Rd => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstCondb28w4Imm4b16w4Rdb12w4Imm12b0w12 + { + private readonly uint _value; + public InstCondb28w4Imm4b16w4Rdb12w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Imm4 => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstIb26w1Imm4b16w4Imm3b12w3Rdb8w4Imm8b0w8 + { + private readonly uint _value; + public InstIb26w1Imm4b16w4Imm3b12w3Rdb8w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Imm3 => (_value >> 12) & 0x7; + public readonly uint Imm4 => (_value >> 16) & 0xF; + public readonly uint I => (_value >> 26) & 0x1; + } + + readonly struct InstDb23w1Rmb19w4Rdb16w3 + { + private readonly uint _value; + public InstDb23w1Rmb19w4Rdb16w3(uint value) => _value = value; + public readonly uint Rd => (_value >> 16) & 0x7; + public readonly uint Rm => (_value >> 19) & 0xF; + public readonly uint D => (_value >> 23) & 0x1; + } + + readonly struct InstOpb27w2Imm5b22w5Rmb19w3Rdb16w3 + { + private readonly uint _value; + public InstOpb27w2Imm5b22w5Rmb19w3Rdb16w3(uint value) => _value = value; + public readonly uint Rd => (_value >> 16) & 0x7; + public readonly uint Rm => (_value >> 19) & 0x7; + public readonly uint Imm5 => (_value >> 22) & 0x1F; + public readonly uint Op => (_value >> 27) & 0x3; + } + + readonly struct InstCondb28w4Sb20w1Rdb12w4Rsb8w4Stypeb5w2Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Sb20w1Rdb12w4Rsb8w4Stypeb5w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Stype => (_value >> 5) & 0x3; + public readonly uint Rs => (_value >> 8) & 0xF; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint S => (_value >> 20) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRsb19w3Rdmb16w3 + { + private readonly uint _value; + public InstRsb19w3Rdmb16w3(uint value) => _value = value; + public readonly uint Rdm => (_value >> 16) & 0x7; + public readonly uint Rs => (_value >> 19) & 0x7; + } + + readonly struct InstStypeb21w2Sb20w1Rmb16w4Rdb8w4Rsb0w4 + { + private readonly uint _value; + public InstStypeb21w2Sb20w1Rmb16w4Rdb8w4Rsb0w4(uint value) => _value = value; + public readonly uint Rs => (_value >> 0) & 0xF; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Rm => (_value >> 16) & 0xF; + public readonly uint S => (_value >> 20) & 0x1; + public readonly uint Stype => (_value >> 21) & 0x3; + } + + readonly struct InstCondb28w4Rb22w1Rdb12w4 + { + private readonly uint _value; + public InstCondb28w4Rb22w1Rdb12w4(uint value) => _value = value; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint R => (_value >> 22) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRb20w1Rdb8w4 + { + private readonly uint _value; + public InstRb20w1Rdb8w4(uint value) => _value = value; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint R => (_value >> 20) & 0x1; + } + + readonly struct InstCondb28w4Rb22w1M1b16w4Rdb12w4Mb8w1 + { + private readonly uint _value; + public InstCondb28w4Rb22w1M1b16w4Rdb12w4Mb8w1(uint value) => _value = value; + public readonly uint M => (_value >> 8) & 0x1; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint M1 => (_value >> 16) & 0xF; + public readonly uint R => (_value >> 22) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRb20w1M1b16w4Rdb8w4Mb4w1 + { + private readonly uint _value; + public InstRb20w1M1b16w4Rdb8w4Mb4w1(uint value) => _value = value; + public readonly uint M => (_value >> 4) & 0x1; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint M1 => (_value >> 16) & 0xF; + public readonly uint R => (_value >> 20) & 0x1; + } + + readonly struct InstCondb28w4Rb22w1M1b16w4Mb8w1Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rb22w1M1b16w4Mb8w1Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 8) & 0x1; + public readonly uint M1 => (_value >> 16) & 0xF; + public readonly uint R => (_value >> 22) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRb20w1Rnb16w4M1b8w4Mb4w1 + { + private readonly uint _value; + public InstRb20w1Rnb16w4M1b8w4Mb4w1(uint value) => _value = value; + public readonly uint M => (_value >> 4) & 0x1; + public readonly uint M1 => (_value >> 8) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint R => (_value >> 20) & 0x1; + } + + readonly struct InstCondb28w4Rb22w1Maskb16w4Imm12b0w12 + { + private readonly uint _value; + public InstCondb28w4Rb22w1Maskb16w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Mask => (_value >> 16) & 0xF; + public readonly uint R => (_value >> 22) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstCondb28w4Rb22w1Maskb16w4Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rb22w1Maskb16w4Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint Mask => (_value >> 16) & 0xF; + public readonly uint R => (_value >> 22) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRb20w1Rnb16w4Maskb8w4 + { + private readonly uint _value; + public InstRb20w1Rnb16w4Maskb8w4(uint value) => _value = value; + public readonly uint Mask => (_value >> 8) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint R => (_value >> 20) & 0x1; + } + + readonly struct InstCondb28w4Sb20w1Rdb16w4Rmb8w4Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Sb20w1Rdb16w4Rmb8w4Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Rd => (_value >> 16) & 0xF; + public readonly uint S => (_value >> 20) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb19w3Rdmb16w3 + { + private readonly uint _value; + public InstRnb19w3Rdmb16w3(uint value) => _value = value; + public readonly uint Rdm => (_value >> 16) & 0x7; + public readonly uint Rn => (_value >> 19) & 0x7; + } + + readonly struct InstRmb19w3Rdb16w3 + { + private readonly uint _value; + public InstRmb19w3Rdb16w3(uint value) => _value = value; + public readonly uint Rd => (_value >> 16) & 0x7; + public readonly uint Rm => (_value >> 19) & 0x7; + } + + readonly struct InstCondb28w4Rnb16w4Rdb12w4Imm5b7w5Tbb6w1Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Rnb16w4Rdb12w4Imm5b7w5Tbb6w1Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Tb => (_value >> 6) & 0x1; + public readonly uint Imm5 => (_value >> 7) & 0x1F; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Imm3b12w3Rdb8w4Imm2b6w2Tbb5w1Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Imm3b12w3Rdb8w4Imm2b6w2Tbb5w1Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Tb => (_value >> 5) & 0x1; + public readonly uint Imm2 => (_value >> 6) & 0x3; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Imm3 => (_value >> 12) & 0x7; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstUb23w1Rb22w1Rnb16w4Imm12b0w12 + { + private readonly uint _value; + public InstUb23w1Rb22w1Rnb16w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint R => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + } + + readonly struct InstWb21w1Rnb16w4Imm12b0w12 + { + private readonly uint _value; + public InstWb21w1Rnb16w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + } + + readonly struct InstWb21w1Rnb16w4Imm8b0w8 + { + private readonly uint _value; + public InstWb21w1Rnb16w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + } + + readonly struct InstUb23w1Imm12b0w12 + { + private readonly uint _value; + public InstUb23w1Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint U => (_value >> 23) & 0x1; + } + + readonly struct InstUb23w1Rb22w1Rnb16w4Imm5b7w5Stypeb5w2Rmb0w4 + { + private readonly uint _value; + public InstUb23w1Rb22w1Rnb16w4Imm5b7w5Stypeb5w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Stype => (_value >> 5) & 0x3; + public readonly uint Imm5 => (_value >> 7) & 0x1F; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint R => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + } + + readonly struct InstWb21w1Rnb16w4Imm2b4w2Rmb0w4 + { + private readonly uint _value; + public InstWb21w1Rnb16w4Imm2b4w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Imm2 => (_value >> 4) & 0x3; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + } + + readonly struct InstUb23w1Rnb16w4Imm12b0w12 + { + private readonly uint _value; + public InstUb23w1Rnb16w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint U => (_value >> 23) & 0x1; + } + + readonly struct InstRnb16w4Imm12b0w12 + { + private readonly uint _value; + public InstRnb16w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstRnb16w4Imm8b0w8 + { + private readonly uint _value; + public InstRnb16w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstUb23w1Rnb16w4Imm5b7w5Stypeb5w2Rmb0w4 + { + private readonly uint _value; + public InstUb23w1Rnb16w4Imm5b7w5Stypeb5w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Stype => (_value >> 5) & 0x3; + public readonly uint Imm5 => (_value >> 7) & 0x1F; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint U => (_value >> 23) & 0x1; + } + + readonly struct InstRnb16w4Imm2b4w2Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Imm2b4w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Imm2 => (_value >> 4) & 0x3; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstPb24w1RegisterListb16w8 + { + private readonly uint _value; + public InstPb24w1RegisterListb16w8(uint value) => _value = value; + public readonly uint RegisterList => (_value >> 16) & 0xFF; + public readonly uint P => (_value >> 24) & 0x1; + } + + readonly struct InstMb24w1RegisterListb16w8 + { + private readonly uint _value; + public InstMb24w1RegisterListb16w8(uint value) => _value = value; + public readonly uint RegisterList => (_value >> 16) & 0xFF; + public readonly uint M => (_value >> 24) & 0x1; + } + + readonly struct InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Rnb16w4Rdb12w4Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb19w3Rdb16w3 + { + private readonly uint _value; + public InstRnb19w3Rdb16w3(uint value) => _value = value; + public readonly uint Rd => (_value >> 16) & 0x7; + public readonly uint Rn => (_value >> 19) & 0x7; + } + + readonly struct InstCondb28w4Widthm1b16w5Rdb12w4Lsbb7w5Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Widthm1b16w5Rdb12w4Lsbb7w5Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint Lsb => (_value >> 7) & 0x1F; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Widthm1 => (_value >> 16) & 0x1F; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Imm3b12w3Rdb8w4Imm2b6w2Widthm1b0w5 + { + private readonly uint _value; + public InstRnb16w4Imm3b12w3Rdb8w4Imm2b6w2Widthm1b0w5(uint value) => _value = value; + public readonly uint Widthm1 => (_value >> 0) & 0x1F; + public readonly uint Imm2 => (_value >> 6) & 0x3; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Imm3 => (_value >> 12) & 0x7; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rdb16w4Rmb8w4Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdb16w4Rmb8w4Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Rd => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstEb9w1 + { + private readonly uint _value; + public InstEb9w1(uint value) => _value = value; + public readonly uint E => (_value >> 9) & 0x1; + } + + readonly struct InstEb19w1 + { + private readonly uint _value; + public InstEb19w1(uint value) => _value = value; + public readonly uint E => (_value >> 19) & 0x1; + } + + readonly struct InstImm1b9w1 + { + private readonly uint _value; + public InstImm1b9w1(uint value) => _value = value; + public readonly uint Imm1 => (_value >> 9) & 0x1; + } + + readonly struct InstImm1b19w1 + { + private readonly uint _value; + public InstImm1b19w1(uint value) => _value = value; + public readonly uint Imm1 => (_value >> 19) & 0x1; + } + + readonly struct InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstCondb28w4Rdb16w4Rab12w4Rmb8w4Mb6w1Nb5w1Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdb16w4Rab12w4Rmb8w4Mb6w1Nb5w1Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint N => (_value >> 5) & 0x1; + public readonly uint M => (_value >> 6) & 0x1; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Ra => (_value >> 12) & 0xF; + public readonly uint Rd => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rab12w4Rdb8w4Nb5w1Mb4w1Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rab12w4Rdb8w4Nb5w1Mb4w1Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 4) & 0x1; + public readonly uint N => (_value >> 5) & 0x1; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Ra => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rdb16w4Rab12w4Rmb8w4Mb5w1Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdb16w4Rab12w4Rmb8w4Mb5w1Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Ra => (_value >> 12) & 0xF; + public readonly uint Rd => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rab12w4Rdb8w4Mb4w1Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rab12w4Rdb8w4Mb4w1Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 4) & 0x1; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Ra => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Sb20w1Rdhib16w4Rdlob12w4Rmb8w4Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Sb20w1Rdhib16w4Rdlob12w4Rmb8w4Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Rdlo => (_value >> 12) & 0xF; + public readonly uint Rdhi => (_value >> 16) & 0xF; + public readonly uint S => (_value >> 20) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rdlob12w4Rdhib8w4Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rdlob12w4Rdhib8w4Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Rdhi => (_value >> 8) & 0xF; + public readonly uint Rdlo => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rdhib16w4Rdlob12w4Rmb8w4Mb6w1Nb5w1Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdhib16w4Rdlob12w4Rmb8w4Mb6w1Nb5w1Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint N => (_value >> 5) & 0x1; + public readonly uint M => (_value >> 6) & 0x1; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Rdlo => (_value >> 12) & 0xF; + public readonly uint Rdhi => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rdlob12w4Rdhib8w4Nb5w1Mb4w1Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rdlob12w4Rdhib8w4Nb5w1Mb4w1Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 4) & 0x1; + public readonly uint N => (_value >> 5) & 0x1; + public readonly uint Rdhi => (_value >> 8) & 0xF; + public readonly uint Rdlo => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rdhib16w4Rdlob12w4Rmb8w4Mb5w1Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdhib16w4Rdlob12w4Rmb8w4Mb5w1Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Rdlo => (_value >> 12) & 0xF; + public readonly uint Rdhi => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rdlob12w4Rdhib8w4Mb4w1Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rdlob12w4Rdhib8w4Mb4w1Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 4) & 0x1; + public readonly uint Rdhi => (_value >> 8) & 0xF; + public readonly uint Rdlo => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rdb16w4Rab12w4Rmb8w4Mb6w1Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdb16w4Rab12w4Rmb8w4Mb6w1Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 6) & 0x1; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Ra => (_value >> 12) & 0xF; + public readonly uint Rd => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstCondb28w4Rdb16w4Rab12w4Rmb8w4Rb5w1Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdb16w4Rab12w4Rmb8w4Rb5w1Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint R => (_value >> 5) & 0x1; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Ra => (_value >> 12) & 0xF; + public readonly uint Rd => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rab12w4Rdb8w4Rb4w1Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rab12w4Rdb8w4Rb4w1Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint R => (_value >> 4) & 0x1; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Ra => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rdb16w4Rmb8w4Rb5w1Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdb16w4Rmb8w4Rb5w1Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint R => (_value >> 5) & 0x1; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Rd => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rdb8w4Rb4w1Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rdb8w4Rb4w1Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint R => (_value >> 4) & 0x1; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rdb16w4Rmb8w4Mb5w1Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdb16w4Rmb8w4Mb5w1Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Rd => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rdb8w4Mb4w1Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rdb8w4Mb4w1Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 4) & 0x1; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rdb16w4Rmb8w4Mb6w1Nb5w1Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdb16w4Rmb8w4Mb6w1Nb5w1Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint N => (_value >> 5) & 0x1; + public readonly uint M => (_value >> 6) & 0x1; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Rd => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rdb8w4Nb5w1Mb4w1Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rdb8w4Nb5w1Mb4w1Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 4) & 0x1; + public readonly uint N => (_value >> 5) & 0x1; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rdb16w4Rmb8w4Mb6w1Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdb16w4Rmb8w4Mb6w1Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 6) & 0x1; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Rd => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstCondb28w4SatImmb16w5Rdb12w4Imm5b7w5Shb6w1Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4SatImmb16w5Rdb12w4Imm5b7w5Shb6w1Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint Sh => (_value >> 6) & 0x1; + public readonly uint Imm5 => (_value >> 7) & 0x1F; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint SatImm => (_value >> 16) & 0x1F; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstShb21w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2SatImmb0w5 + { + private readonly uint _value; + public InstShb21w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2SatImmb0w5(uint value) => _value = value; + public readonly uint SatImm => (_value >> 0) & 0x1F; + public readonly uint Imm2 => (_value >> 6) & 0x3; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Imm3 => (_value >> 12) & 0x7; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint Sh => (_value >> 21) & 0x1; + } + + readonly struct InstCondb28w4SatImmb16w4Rdb12w4Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4SatImmb16w4Rdb12w4Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint SatImm => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rdb8w4SatImmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rdb8w4SatImmb0w4(uint value) => _value = value; + public readonly uint SatImm => (_value >> 0) & 0xF; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rnb16w4Rtb0w4 + { + private readonly uint _value; + public InstCondb28w4Rnb16w4Rtb0w4(uint value) => _value = value; + public readonly uint Rt => (_value >> 0) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstCondb28w4Rnb16w4Rdb12w4Rtb0w4 + { + private readonly uint _value; + public InstCondb28w4Rnb16w4Rdb12w4Rtb0w4(uint value) => _value = value; + public readonly uint Rt => (_value >> 0) & 0xF; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rtb12w4Rdb0w4 + { + private readonly uint _value; + public InstRnb16w4Rtb12w4Rdb0w4(uint value) => _value = value; + public readonly uint Rd => (_value >> 0) & 0xF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstRnb16w4Rtb12w4Rt2b8w4Rdb0w4 + { + private readonly uint _value; + public InstRnb16w4Rtb12w4Rt2b8w4Rdb0w4(uint value) => _value = value; + public readonly uint Rd => (_value >> 0) & 0xF; + public readonly uint Rt2 => (_value >> 8) & 0xF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstWb21w1Rnb16w4Mb14w1RegisterListb0w14 + { + private readonly uint _value; + public InstWb21w1Rnb16w4Mb14w1RegisterListb0w14(uint value) => _value = value; + public readonly uint RegisterList => (_value >> 0) & 0x3FFF; + public readonly uint M => (_value >> 14) & 0x1; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + } + + readonly struct InstRnb16w4Rtb12w4Rdb8w4Imm8b0w8 + { + private readonly uint _value; + public InstRnb16w4Rtb12w4Rdb8w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rnb16w4Rdb12w4Rotateb10w2Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Rnb16w4Rdb12w4Rotateb10w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Rotate => (_value >> 10) & 0x3; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRnb16w4Rdb8w4Rotateb4w2Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Rdb8w4Rotateb4w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Rotate => (_value >> 4) & 0x3; + public readonly uint Rd => (_value >> 8) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rdb12w4Rotateb10w2Rmb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdb12w4Rotateb10w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Rotate => (_value >> 10) & 0x3; + public readonly uint Rd => (_value >> 12) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRdb8w4Rotateb4w2Rmb0w4 + { + private readonly uint _value; + public InstRdb8w4Rotateb4w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Rotate => (_value >> 4) & 0x3; + public readonly uint Rd => (_value >> 8) & 0xF; + } + + readonly struct InstRnb16w4Hb4w1Rmb0w4 + { + private readonly uint _value; + public InstRnb16w4Hb4w1Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint H => (_value >> 4) & 0x1; + public readonly uint Rn => (_value >> 16) & 0xF; + } + + readonly struct InstImm12b8w12Imm4b0w4 + { + private readonly uint _value; + public InstImm12b8w12Imm4b0w4(uint value) => _value = value; + public readonly uint Imm4 => (_value >> 0) & 0xF; + public readonly uint Imm12 => (_value >> 8) & 0xFFF; + } + + readonly struct InstImm4b16w4Imm12b0w12 + { + private readonly uint _value; + public InstImm4b16w4Imm12b0w12(uint value) => _value = value; + public readonly uint Imm12 => (_value >> 0) & 0xFFF; + public readonly uint Imm4 => (_value >> 16) & 0xF; + } + + readonly struct InstCondb28w4Rdhib16w4Rdlob12w4Rmb8w4Rnb0w4 + { + private readonly uint _value; + public InstCondb28w4Rdhib16w4Rdlob12w4Rmb8w4Rnb0w4(uint value) => _value = value; + public readonly uint Rn => (_value >> 0) & 0xF; + public readonly uint Rm => (_value >> 8) & 0xF; + public readonly uint Rdlo => (_value >> 12) & 0xF; + public readonly uint Rdhi => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Size => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 24) & 0x1; + } + + readonly struct InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Size => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 28) & 0x1; + } + + readonly struct InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Size => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 24) & 0x1; + } + + readonly struct InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Size => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 28) & 0x1; + } + + readonly struct InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Sz => (_value >> 20) & 0x1; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint F => (_value >> 10) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Size => (_value >> 18) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstCondb28w4Db22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstCondb28w4Db22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Size => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Size => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstIb24w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 + { + private readonly uint _value; + public InstIb24w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4(uint value) => _value = value; + public readonly uint Imm4 => (_value >> 0) & 0xF; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm3 => (_value >> 16) & 0x7; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint I => (_value >> 24) & 0x1; + } + + readonly struct InstIb28w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 + { + private readonly uint _value; + public InstIb28w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4(uint value) => _value = value; + public readonly uint Imm4 => (_value >> 0) & 0xF; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm3 => (_value >> 16) & 0x7; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint I => (_value >> 28) & 0x1; + } + + readonly struct InstRotb24w1Db22w1Sb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstRotb24w1Db22w1Sb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint S => (_value >> 20) & 0x1; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Rot => (_value >> 24) & 0x1; + } + + readonly struct InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Size => (_value >> 18) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstRotb23w2Db22w1Sb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstRotb23w2Db22w1Sb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint S => (_value >> 20) & 0x1; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Rot => (_value >> 23) & 0x3; + } + + readonly struct InstSb23w1Db22w1Rotb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstSb23w1Db22w1Rotb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Rot => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint S => (_value >> 23) & 0x1; + } + + readonly struct InstCondb28w4Db22w1Vdb12w4Sizeb8w2 + { + private readonly uint _value; + public InstCondb28w4Db22w1Vdb12w4Sizeb8w2(uint value) => _value = value; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstDb22w1Vdb12w4Sizeb8w2 + { + private readonly uint _value; + public InstDb22w1Vdb12w4Sizeb8w2(uint value) => _value = value; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Sizeb18w2Vdb12w4Opb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Sizeb18w2Vdb12w4Opb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint Op => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Size => (_value >> 18) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Vdb12w4Sizeb8w2Opb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Vdb12w4Sizeb8w2Opb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Op => (_value >> 7) & 0x1; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstCondb28w4Db22w1Opb16w1Vdb12w4Szb8w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstCondb28w4Db22w1Opb16w1Vdb12w4Szb8w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Sz => (_value >> 8) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Op => (_value >> 16) & 0x1; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstDb22w1Opb16w1Vdb12w4Szb8w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Opb16w1Vdb12w4Szb8w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Sz => (_value >> 8) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Op => (_value >> 16) & 0x1; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstCondb28w4Db22w1Vdb12w4Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstCondb28w4Db22w1Vdb12w4Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstDb22w1Vdb12w4Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Vdb12w4Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Sizeb18w2Vdb12w4Opb8w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Sizeb18w2Vdb12w4Opb8w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Op => (_value >> 8) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Size => (_value >> 18) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Sizeb18w2Vdb12w4Opb7w2Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Sizeb18w2Vdb12w4Opb7w2Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint Op => (_value >> 7) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Size => (_value >> 18) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstCondb28w4Db22w1Vdb12w4Sizeb8w2Opb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstCondb28w4Db22w1Vdb12w4Sizeb8w2Opb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Op => (_value >> 7) & 0x1; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstUb24w1Db22w1Imm6b16w6Vdb12w4Opb8w2Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb24w1Db22w1Imm6b16w6Vdb12w4Opb8w2Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint Op => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm6 => (_value >> 16) & 0x3F; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 24) & 0x1; + } + + readonly struct InstUb28w1Db22w1Imm6b16w6Vdb12w4Opb8w2Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb28w1Db22w1Imm6b16w6Vdb12w4Opb8w2Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint Op => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm6 => (_value >> 16) & 0x3F; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 28) & 0x1; + } + + readonly struct InstCondb28w4Db22w1Opb18w1Ub16w1Vdb12w4Sfb8w2Sxb7w1Ib5w1Imm4b0w4 + { + private readonly uint _value; + public InstCondb28w4Db22w1Opb18w1Ub16w1Vdb12w4Sfb8w2Sxb7w1Ib5w1Imm4b0w4(uint value) => _value = value; + public readonly uint Imm4 => (_value >> 0) & 0xF; + public readonly uint I => (_value >> 5) & 0x1; + public readonly uint Sx => (_value >> 7) & 0x1; + public readonly uint Sf => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint U => (_value >> 16) & 0x1; + public readonly uint Op => (_value >> 18) & 0x1; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstDb22w1Opb18w1Ub16w1Vdb12w4Sfb8w2Sxb7w1Ib5w1Imm4b0w4 + { + private readonly uint _value; + public InstDb22w1Opb18w1Ub16w1Vdb12w4Sfb8w2Sxb7w1Ib5w1Imm4b0w4(uint value) => _value = value; + public readonly uint Imm4 => (_value >> 0) & 0xF; + public readonly uint I => (_value >> 5) & 0x1; + public readonly uint Sx => (_value >> 7) & 0x1; + public readonly uint Sf => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint U => (_value >> 16) & 0x1; + public readonly uint Op => (_value >> 18) & 0x1; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstCondb28w4Bb22w1Qb21w1Vdb16w4Rtb12w4Db7w1Eb5w1 + { + private readonly uint _value; + public InstCondb28w4Bb22w1Qb21w1Vdb16w4Rtb12w4Db7w1Eb5w1(uint value) => _value = value; + public readonly uint E => (_value >> 5) & 0x1; + public readonly uint D => (_value >> 7) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Vd => (_value >> 16) & 0xF; + public readonly uint Q => (_value >> 21) & 0x1; + public readonly uint B => (_value >> 22) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstBb22w1Qb21w1Vdb16w4Rtb12w4Db7w1Eb5w1 + { + private readonly uint _value; + public InstBb22w1Qb21w1Vdb16w4Rtb12w4Db7w1Eb5w1(uint value) => _value = value; + public readonly uint E => (_value >> 5) & 0x1; + public readonly uint D => (_value >> 7) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Vd => (_value >> 16) & 0xF; + public readonly uint Q => (_value >> 21) & 0x1; + public readonly uint B => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Imm4b16w4Vdb12w4Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Imm4b16w4Vdb12w4Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm4 => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Vnb16w4Vdb12w4Imm4b8w4Nb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Vnb16w4Vdb12w4Imm4b8w4Nb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Imm4 => (_value >> 8) & 0xF; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 + { + private readonly uint _value; + public InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint IndexAlign => (_value >> 4) & 0xF; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Tb5w1Ab4w1Rmb0w4 + { + private readonly uint _value; + public InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Tb5w1Ab4w1Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint A => (_value >> 4) & 0x1; + public readonly uint T => (_value >> 5) & 0x1; + public readonly uint Size => (_value >> 6) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 + { + private readonly uint _value; + public InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint Align => (_value >> 4) & 0x3; + public readonly uint Size => (_value >> 6) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Tb5w1Rmb0w4 + { + private readonly uint _value; + public InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Tb5w1Rmb0w4(uint value) => _value = value; + public readonly uint Rm => (_value >> 0) & 0xF; + public readonly uint T => (_value >> 5) & 0x1; + public readonly uint Size => (_value >> 6) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstCondb28w4Pb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm8b0w8 + { + private readonly uint _value; + public InstCondb28w4Pb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstPb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm8b0w8 + { + private readonly uint _value; + public InstPb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint W => (_value >> 21) & 0x1; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint P => (_value >> 24) & 0x1; + } + + readonly struct InstCondb28w4Ub23w1Db22w1Rnb16w4Vdb12w4Sizeb8w2Imm8b0w8 + { + private readonly uint _value; + public InstCondb28w4Ub23w1Db22w1Rnb16w4Vdb12w4Sizeb8w2Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstUb23w1Db22w1Rnb16w4Vdb12w4Sizeb8w2Imm8b0w8 + { + private readonly uint _value; + public InstUb23w1Db22w1Rnb16w4Vdb12w4Sizeb8w2Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Rn => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + } + + readonly struct InstCondb28w4Ub23w1Db22w1Vdb12w4Sizeb8w2Imm8b0w8 + { + private readonly uint _value; + public InstCondb28w4Ub23w1Db22w1Vdb12w4Sizeb8w2Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstUb23w1Db22w1Vdb12w4Sizeb8w2Imm8b0w8 + { + private readonly uint _value; + public InstUb23w1Db22w1Vdb12w4Sizeb8w2Imm8b0w8(uint value) => _value = value; + public readonly uint Imm8 => (_value >> 0) & 0xFF; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 23) & 0x1; + } + + readonly struct InstQb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Fb8w1Nb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstQb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Fb8w1Nb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint F => (_value >> 8) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Size => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Q => (_value >> 24) & 0x1; + } + + readonly struct InstQb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Fb8w1Nb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstQb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Fb8w1Nb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint F => (_value >> 8) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Size => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Q => (_value >> 28) & 0x1; + } + + readonly struct InstDb22w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstUb24w1Db22w1Imm3hb19w3Vdb12w4Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb24w1Db22w1Imm3hb19w3Vdb12w4Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm3h => (_value >> 19) & 0x7; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 24) & 0x1; + } + + readonly struct InstUb28w1Db22w1Imm3hb19w3Vdb12w4Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb28w1Db22w1Imm3hb19w3Vdb12w4Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm3h => (_value >> 19) & 0x7; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 28) & 0x1; + } + + readonly struct InstCondb28w4Opb20w1Rt2b16w4Rtb12w4Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstCondb28w4Opb20w1Rt2b16w4Rtb12w4Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rt2 => (_value >> 16) & 0xF; + public readonly uint Op => (_value >> 20) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstOpb20w1Rt2b16w4Rtb12w4Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstOpb20w1Rt2b16w4Rtb12w4Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Rt2 => (_value >> 16) & 0xF; + public readonly uint Op => (_value >> 20) & 0x1; + } + + readonly struct InstCondb28w4Opb20w1Vnb16w4Rtb12w4Nb7w1 + { + private readonly uint _value; + public InstCondb28w4Opb20w1Vnb16w4Rtb12w4Nb7w1(uint value) => _value = value; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Op => (_value >> 20) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstOpb20w1Vnb16w4Rtb12w4Nb7w1 + { + private readonly uint _value; + public InstOpb20w1Vnb16w4Rtb12w4Nb7w1(uint value) => _value = value; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Op => (_value >> 20) & 0x1; + } + + readonly struct InstCondb28w4Db22w1Imm4hb16w4Vdb12w4Sizeb8w2Imm4lb0w4 + { + private readonly uint _value; + public InstCondb28w4Db22w1Imm4hb16w4Vdb12w4Sizeb8w2Imm4lb0w4(uint value) => _value = value; + public readonly uint Imm4l => (_value >> 0) & 0xF; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm4h => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstDb22w1Imm4hb16w4Vdb12w4Sizeb8w2Imm4lb0w4 + { + private readonly uint _value; + public InstDb22w1Imm4hb16w4Vdb12w4Sizeb8w2Imm4lb0w4(uint value) => _value = value; + public readonly uint Imm4l => (_value >> 0) & 0xF; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm4h => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstCondb28w4Opc1b21w2Vdb16w4Rtb12w4Db7w1Opc2b5w2 + { + private readonly uint _value; + public InstCondb28w4Opc1b21w2Vdb16w4Rtb12w4Db7w1Opc2b5w2(uint value) => _value = value; + public readonly uint Opc2 => (_value >> 5) & 0x3; + public readonly uint D => (_value >> 7) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Vd => (_value >> 16) & 0xF; + public readonly uint Opc1 => (_value >> 21) & 0x3; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstOpc1b21w2Vdb16w4Rtb12w4Db7w1Opc2b5w2 + { + private readonly uint _value; + public InstOpc1b21w2Vdb16w4Rtb12w4Db7w1Opc2b5w2(uint value) => _value = value; + public readonly uint Opc2 => (_value >> 5) & 0x3; + public readonly uint D => (_value >> 7) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Vd => (_value >> 16) & 0xF; + public readonly uint Opc1 => (_value >> 21) & 0x3; + } + + readonly struct InstCondb28w4Ub23w1Opc1b21w2Vnb16w4Rtb12w4Nb7w1Opc2b5w2 + { + private readonly uint _value; + public InstCondb28w4Ub23w1Opc1b21w2Vnb16w4Rtb12w4Nb7w1Opc2b5w2(uint value) => _value = value; + public readonly uint Opc2 => (_value >> 5) & 0x3; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Opc1 => (_value >> 21) & 0x3; + public readonly uint U => (_value >> 23) & 0x1; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstUb23w1Opc1b21w2Vnb16w4Rtb12w4Nb7w1Opc2b5w2 + { + private readonly uint _value; + public InstUb23w1Opc1b21w2Vnb16w4Rtb12w4Nb7w1Opc2b5w2(uint value) => _value = value; + public readonly uint Opc2 => (_value >> 5) & 0x3; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Opc1 => (_value >> 21) & 0x3; + public readonly uint U => (_value >> 23) & 0x1; + } + + readonly struct InstCondb28w4Regb16w4Rtb12w4 + { + private readonly uint _value; + public InstCondb28w4Regb16w4Rtb12w4(uint value) => _value = value; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Reg => (_value >> 16) & 0xF; + public readonly uint Cond => (_value >> 28) & 0xF; + } + + readonly struct InstRegb16w4Rtb12w4 + { + private readonly uint _value; + public InstRegb16w4Rtb12w4(uint value) => _value = value; + public readonly uint Rt => (_value >> 12) & 0xF; + public readonly uint Reg => (_value >> 16) & 0xF; + } + + readonly struct InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Opb9w1Nb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Opb9w1Nb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Op => (_value >> 9) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Size => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 24) & 0x1; + } + + readonly struct InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Opb9w1Nb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Opb9w1Nb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Op => (_value >> 9) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Size => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 28) & 0x1; + } + + readonly struct InstOpb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstOpb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Size => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Op => (_value >> 24) & 0x1; + } + + readonly struct InstOpb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstOpb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Size => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Op => (_value >> 28) & 0x1; + } + + readonly struct InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Sz => (_value >> 20) & 0x1; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstQb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstQb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Size => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Q => (_value >> 24) & 0x1; + } + + readonly struct InstQb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstQb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Size => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint Q => (_value >> 28) & 0x1; + } + + readonly struct InstDb22w1Sizeb18w2Vdb12w4Opb6w2Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Sizeb18w2Vdb12w4Opb6w2Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Op => (_value >> 6) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Size => (_value >> 18) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstUb24w1Db22w1Imm6b16w6Vdb12w4Opb8w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb24w1Db22w1Imm6b16w6Vdb12w4Opb8w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Op => (_value >> 8) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm6 => (_value >> 16) & 0x3F; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 24) & 0x1; + } + + readonly struct InstUb28w1Db22w1Imm6b16w6Vdb12w4Opb8w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb28w1Db22w1Imm6b16w6Vdb12w4Opb8w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Op => (_value >> 8) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm6 => (_value >> 16) & 0x3F; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 28) & 0x1; + } + + readonly struct InstUb24w1Db22w1Imm6b16w6Vdb12w4Opb8w1Lb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb24w1Db22w1Imm6b16w6Vdb12w4Opb8w1Lb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint L => (_value >> 7) & 0x1; + public readonly uint Op => (_value >> 8) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm6 => (_value >> 16) & 0x3F; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 24) & 0x1; + } + + readonly struct InstUb28w1Db22w1Imm6b16w6Vdb12w4Opb8w1Lb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb28w1Db22w1Imm6b16w6Vdb12w4Opb8w1Lb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint L => (_value >> 7) & 0x1; + public readonly uint Op => (_value >> 8) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm6 => (_value >> 16) & 0x3F; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 28) & 0x1; + } + + readonly struct InstDb22w1Sizeb18w2Vdb12w4Fb8w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Sizeb18w2Vdb12w4Fb8w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint F => (_value >> 8) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Size => (_value >> 18) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstUb24w1Db22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb24w1Db22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint L => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm6 => (_value >> 16) & 0x3F; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 24) & 0x1; + } + + readonly struct InstUb28w1Db22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb28w1Db22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint L => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm6 => (_value >> 16) & 0x3F; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 28) & 0x1; + } + + readonly struct InstDb22w1Imm6b16w6Vdb12w4Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Imm6b16w6Vdb12w4Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm6 => (_value >> 16) & 0x3F; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Ccb20w2Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Ccb20w2Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Size => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint Cc => (_value >> 20) & 0x3; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstUb24w1Db22w1Imm6b16w6Vdb12w4Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb24w1Db22w1Imm6b16w6Vdb12w4Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm6 => (_value >> 16) & 0x3F; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 24) & 0x1; + } + + readonly struct InstUb28w1Db22w1Imm6b16w6Vdb12w4Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstUb28w1Db22w1Imm6b16w6Vdb12w4Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm6 => (_value >> 16) & 0x3F; + public readonly uint D => (_value >> 22) & 0x1; + public readonly uint U => (_value >> 28) & 0x1; + } + + readonly struct InstDb22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint L => (_value >> 7) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Imm6 => (_value >> 16) & 0x3F; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Vdb12w4Qb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Vdb12w4Qb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Q => (_value >> 6) & 0x1; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } + + readonly struct InstDb22w1Vnb16w4Vdb12w4Lenb8w2Nb7w1Opb6w1Mb5w1Vmb0w4 + { + private readonly uint _value; + public InstDb22w1Vnb16w4Vdb12w4Lenb8w2Nb7w1Opb6w1Mb5w1Vmb0w4(uint value) => _value = value; + public readonly uint Vm => (_value >> 0) & 0xF; + public readonly uint M => (_value >> 5) & 0x1; + public readonly uint Op => (_value >> 6) & 0x1; + public readonly uint N => (_value >> 7) & 0x1; + public readonly uint Len => (_value >> 8) & 0x3; + public readonly uint Vd => (_value >> 12) & 0xF; + public readonly uint Vn => (_value >> 16) & 0xF; + public readonly uint D => (_value >> 22) & 0x1; + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/InstFlags.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/InstFlags.cs new file mode 100644 index 00000000..a1937e08 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/InstFlags.cs @@ -0,0 +1,63 @@ +using System; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + [Flags] + enum InstFlags + { + None = 0, + Cond = 1 << 0, + Rd = 1 << 1, + RdLo = 1 << 2, + RdHi = 1 << 3, + Rdn = 1 << 4, + Dn = 1 << 5, + Rt = 1 << 6, + Rt2 = 1 << 7, + Rlist = 1 << 8, + Rd16 = 1 << 9, + ReadRd = 1 << 10, + WBack = 1 << 11, + Thumb16 = 1 << 12, + + RdnDn = Rdn | Dn, + RdRd16 = Rd | Rd16, + RtRt2 = Rt | Rt2, + RdLoRdHi = RdLo | RdHi, + RdLoHi = Rd | RdHi, + RdRtRead = Rd | RtRead, + RdRtReadRd16 = Rd | RtRead | Rd16, + RdRt2Read = Rd | Rt2 | RtRead, + RdRt2ReadRd16 = Rd | Rt2 | RtRead | Rd16, + RtRd16 = Rt | Rd16, + RtWBack = Rt | WBack, + Rt2WBack = Rt2 | RtWBack, + RtRead = Rt | ReadRd, + RtReadRd16 = Rt | ReadRd | Rd16, + Rt2Read = Rt2 | RtRead, + RtReadWBack = RtRead | WBack, + Rt2ReadWBack = Rt2 | RtReadWBack, + RlistWBack = Rlist | WBack, + RlistRead = Rlist | ReadRd, + RlistReadWBack = Rlist | ReadRd | WBack, + + CondRd = Cond | Rd, + CondRdLoHi = Cond | Rd | RdHi, + CondRt = Cond | Rt, + CondRt2 = Cond | Rt | Rt2, + CondRd16 = Cond | Rd | Rd16, + CondWBack = Cond | WBack, + CondRdRtRead = Cond | Rd | RtRead, + CondRdRt2Read = Cond | Rd | Rt2 | RtRead, + CondRtWBack = Cond | RtWBack, + CondRt2WBack = Cond | Rt2 | RtWBack, + CondRtRead = Cond | RtRead, + CondRt2Read = Cond | Rt2 | RtRead, + CondRtReadWBack = Cond | RtReadWBack, + CondRt2ReadWBack = Cond | Rt2 | RtReadWBack, + CondRlist = Cond | Rlist, + CondRlistWBack = Cond | Rlist | WBack, + CondRlistRead = Cond | Rlist | ReadRd, + CondRlistReadWBack = Cond | Rlist | ReadRd | WBack, + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/InstInfo.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/InstInfo.cs new file mode 100644 index 00000000..80481ec6 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/InstInfo.cs @@ -0,0 +1,20 @@ +using System; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + readonly struct InstInfo + { + public readonly uint Encoding; + public readonly InstName Name; + public readonly Action EmitFunc; + public readonly InstFlags Flags; + + public InstInfo(uint encoding, InstName name, Action emitFunc, InstFlags flags) + { + Encoding = encoding; + Name = name; + EmitFunc = emitFunc; + Flags = flags; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/InstInfoForTable.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/InstInfoForTable.cs new file mode 100644 index 00000000..25739d56 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/InstInfoForTable.cs @@ -0,0 +1,79 @@ +using Ryujinx.Cpu.LightningJit.Table; +using System; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + readonly struct InstInfoForTable : IInstInfo + { + public uint Encoding { get; } + public uint EncodingMask { get; } + public InstEncoding[] Constraints { get; } + public InstMeta Meta { get; } + public IsaVersion Version => Meta.Version; + public IsaFeature Feature => Meta.Feature; + + public InstInfoForTable( + uint encoding, + uint encodingMask, + InstEncoding[] constraints, + InstName name, + Action emitFunc, + IsaVersion isaVersion, + IsaFeature isaFeature, + InstFlags flags) + { + Encoding = encoding; + EncodingMask = encodingMask; + Constraints = constraints; + Meta = new(name, emitFunc, isaVersion, isaFeature, flags); + } + + public InstInfoForTable( + uint encoding, + uint encodingMask, + InstEncoding[] constraints, + InstName name, + Action emitFunc, + IsaVersion isaVersion, + InstFlags flags) : this(encoding, encodingMask, constraints, name, emitFunc, isaVersion, IsaFeature.None, flags) + { + } + + public InstInfoForTable( + uint encoding, + uint encodingMask, + InstName name, + Action emitFunc, + IsaVersion isaVersion, + IsaFeature isaFeature, + InstFlags flags) : this(encoding, encodingMask, null, name, emitFunc, isaVersion, isaFeature, flags) + { + } + + public InstInfoForTable( + uint encoding, + uint encodingMask, + InstName name, + Action emitFunc, + IsaVersion isaVersion, + InstFlags flags) : this(encoding, encodingMask, null, name, emitFunc, isaVersion, IsaFeature.None, flags) + { + } + + public bool IsConstrained(uint encoding) + { + if (Constraints != null) + { + foreach (InstEncoding constraint in Constraints) + { + if ((encoding & constraint.EncodingMask) == constraint.Encoding) + { + return true; + } + } + } + + return false; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/InstMeta.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/InstMeta.cs new file mode 100644 index 00000000..64eb41bb --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/InstMeta.cs @@ -0,0 +1,22 @@ +using System; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + readonly struct InstMeta + { + public readonly InstName Name; + public readonly Action EmitFunc; + public readonly IsaVersion Version; + public readonly IsaFeature Feature; + public readonly InstFlags Flags; + + public InstMeta(InstName name, Action emitFunc, IsaVersion isaVersion, IsaFeature isaFeature, InstFlags flags) + { + Name = name; + EmitFunc = emitFunc; + Version = isaVersion; + Feature = isaFeature; + Flags = flags; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/InstName.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/InstName.cs new file mode 100644 index 00000000..2ee67aa0 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/InstName.cs @@ -0,0 +1,562 @@ +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + enum InstName + { + AdcI, + AdcR, + AdcRr, + AddI, + AddR, + AddRr, + AddSpI, + AddSpR, + Adr, + Aesd, + Aese, + Aesimc, + Aesmc, + AndI, + AndR, + AndRr, + B, + Bfc, + Bfi, + BicI, + BicR, + BicRr, + Bkpt, + BlxR, + BlI, + Bx, + Bxj, + Cbnz, + Clrbhb, + Clrex, + Clz, + CmnI, + CmnR, + CmnRr, + CmpI, + CmpR, + CmpRr, + Cps, + Crc32, + Crc32c, + Csdb, + Dbg, + Dcps1, + Dcps2, + Dcps3, + Dmb, + Dsb, + EorI, + EorR, + EorRr, + Eret, + Esb, + Fldmx, + Fstmx, + Hlt, + Hvc, + Isb, + It, + Lda, + Ldab, + Ldaex, + Ldaexb, + Ldaexd, + Ldaexh, + Ldah, + LdcI, + LdcL, + Ldm, + Ldmda, + Ldmdb, + Ldmib, + LdmE, + LdmU, + Ldrbt, + LdrbI, + LdrbL, + LdrbR, + LdrdI, + LdrdL, + LdrdR, + Ldrex, + Ldrexb, + Ldrexd, + Ldrexh, + Ldrht, + LdrhI, + LdrhL, + LdrhR, + Ldrsbt, + LdrsbI, + LdrsbL, + LdrsbR, + Ldrsht, + LdrshI, + LdrshL, + LdrshR, + Ldrt, + LdrI, + LdrL, + LdrR, + Mcr, + Mcrr, + Mla, + Mls, + Movt, + MovI, + MovR, + MovRr, + Mrc, + Mrrc, + Mrs, + MrsBr, + MsrBr, + MsrI, + MsrR, + Mul, + MvnI, + MvnR, + MvnRr, + Nop, + OrnI, + OrnR, + OrrI, + OrrR, + OrrRr, + Pkh, + PldI, + PldL, + PldR, + PliI, + PliR, + Pop, + Pssbb, + Push, + Qadd, + Qadd16, + Qadd8, + Qasx, + Qdadd, + Qdsub, + Qsax, + Qsub, + Qsub16, + Qsub8, + Rbit, + Rev, + Rev16, + Revsh, + Rfe, + RsbI, + RsbR, + RsbRr, + RscI, + RscR, + RscRr, + Sadd16, + Sadd8, + Sasx, + Sb, + SbcI, + SbcR, + SbcRr, + Sbfx, + Sdiv, + Sel, + Setend, + Setpan, + Sev, + Sevl, + Sha1c, + Sha1h, + Sha1m, + Sha1p, + Sha1su0, + Sha1su1, + Sha256h, + Sha256h2, + Sha256su0, + Sha256su1, + Shadd16, + Shadd8, + Shasx, + Shsax, + Shsub16, + Shsub8, + Smc, + Smlabb, + Smlad, + Smlal, + Smlalbb, + Smlald, + Smlawb, + Smlsd, + Smlsld, + Smmla, + Smmls, + Smmul, + Smuad, + Smulbb, + Smull, + Smulwb, + Smusd, + Srs, + Ssat, + Ssat16, + Ssax, + Ssbb, + Ssub16, + Ssub8, + Stc, + Stl, + Stlb, + Stlex, + Stlexb, + Stlexd, + Stlexh, + Stlh, + Stm, + Stmda, + Stmdb, + Stmib, + StmU, + Strbt, + StrbI, + StrbR, + StrdI, + StrdR, + Strex, + Strexb, + Strexd, + Strexh, + Strht, + StrhI, + StrhR, + Strt, + StrI, + StrR, + SubI, + SubR, + SubRr, + SubSpI, + SubSpR, + Svc, + Sxtab, + Sxtab16, + Sxtah, + Sxtb, + Sxtb16, + Sxth, + Tbb, + TeqI, + TeqR, + TeqRr, + Tsb, + TstI, + TstR, + TstRr, + Uadd16, + Uadd8, + Uasx, + Ubfx, + Udf, + Udiv, + Uhadd16, + Uhadd8, + Uhasx, + Uhsax, + Uhsub16, + Uhsub8, + Umaal, + Umlal, + Umull, + Uqadd16, + Uqadd8, + Uqasx, + Uqsax, + Uqsub16, + Uqsub8, + Usad8, + Usada8, + Usat, + Usat16, + Usax, + Usub16, + Usub8, + Uxtab, + Uxtab16, + Uxtah, + Uxtb, + Uxtb16, + Uxth, + Vaba, + Vabal, + VabdlI, + VabdF, + VabdI, + Vabs, + Vacge, + Vacgt, + Vaddhn, + Vaddl, + Vaddw, + VaddF, + VaddI, + VandR, + VbicI, + VbicR, + Vbif, + Vbit, + Vbsl, + Vcadd, + VceqI, + VceqR, + VcgeI, + VcgeR, + VcgtI, + VcgtR, + VcleI, + Vcls, + VcltI, + Vclz, + Vcmla, + VcmlaS, + Vcmp, + Vcmpe, + Vcnt, + VcvtaAsimd, + VcvtaVfp, + Vcvtb, + VcvtbBfs, + VcvtmAsimd, + VcvtmVfp, + VcvtnAsimd, + VcvtnVfp, + VcvtpAsimd, + VcvtpVfp, + VcvtrIv, + Vcvtt, + VcvttBfs, + VcvtBfs, + VcvtDs, + VcvtHs, + VcvtIs, + VcvtIv, + VcvtVi, + VcvtXs, + VcvtXv, + Vdiv, + Vdot, + VdotS, + VdupR, + VdupS, + Veor, + Vext, + Vfma, + Vfmal, + VfmalS, + VfmaBf, + VfmaBfs, + Vfms, + Vfmsl, + VfmslS, + Vfnma, + Vfnms, + Vhadd, + Vhsub, + Vins, + Vjcvt, + Vld11, + Vld1A, + Vld1M, + Vld21, + Vld2A, + Vld2M, + Vld31, + Vld3A, + Vld3M, + Vld41, + Vld4A, + Vld4M, + Vldm, + VldrI, + VldrL, + Vmaxnm, + VmaxF, + VmaxI, + Vminnm, + VminF, + VminI, + VmlalI, + VmlalS, + VmlaF, + VmlaI, + VmlaS, + VmlslI, + VmlslS, + VmlsF, + VmlsI, + VmlsS, + Vmmla, + Vmovl, + Vmovn, + Vmovx, + VmovD, + VmovH, + VmovI, + VmovR, + VmovRs, + VmovS, + VmovSr, + VmovSs, + Vmrs, + Vmsr, + VmullI, + VmullS, + VmulF, + VmulI, + VmulS, + VmvnI, + VmvnR, + Vneg, + Vnmla, + Vnmls, + Vnmul, + VornR, + VorrI, + VorrR, + Vpadal, + Vpaddl, + VpaddF, + VpaddI, + VpmaxF, + VpmaxI, + VpminF, + VpminI, + Vqabs, + Vqadd, + Vqdmlal, + Vqdmlsl, + Vqdmulh, + Vqdmull, + Vqmovn, + Vqneg, + Vqrdmlah, + Vqrdmlsh, + Vqrdmulh, + Vqrshl, + Vqrshrn, + VqshlI, + VqshlR, + Vqshrn, + Vqsub, + Vraddhn, + Vrecpe, + Vrecps, + Vrev16, + Vrev32, + Vrev64, + Vrhadd, + VrintaAsimd, + VrintaVfp, + VrintmAsimd, + VrintmVfp, + VrintnAsimd, + VrintnVfp, + VrintpAsimd, + VrintpVfp, + VrintrVfp, + VrintxAsimd, + VrintxVfp, + VrintzAsimd, + VrintzVfp, + Vrshl, + Vrshr, + Vrshrn, + Vrsqrte, + Vrsqrts, + Vrsra, + Vrsubhn, + Vsdot, + VsdotS, + Vsel, + Vshll, + VshlI, + VshlR, + Vshr, + Vshrn, + Vsli, + Vsmmla, + Vsqrt, + Vsra, + Vsri, + Vst11, + Vst1M, + Vst21, + Vst2M, + Vst31, + Vst3M, + Vst41, + Vst4M, + Vstm, + Vstr, + Vsubhn, + Vsubl, + Vsubw, + VsubF, + VsubI, + VsudotS, + Vswp, + Vtbl, + Vtrn, + Vtst, + Vudot, + VudotS, + Vummla, + Vusdot, + VusdotS, + Vusmmla, + Vuzp, + Vzip, + Wfe, + Wfi, + Yield, + } + + static class InstNameExtensions + { + public static bool IsCall(this InstName name) + { + return name == InstName.BlI || name == InstName.BlxR; + } + + public static bool IsSystem(this InstName name) + { + switch (name) + { + case InstName.Mcr: + case InstName.Mcrr: + case InstName.Mrc: + case InstName.Mrs: + case InstName.MrsBr: + case InstName.MsrBr: + case InstName.MsrI: + case InstName.MsrR: + case InstName.Mrrc: + case InstName.Svc: + return true; + } + + return false; + } + + public static bool IsSystemOrCall(this InstName name) + { + return name.IsSystem() || name.IsCall(); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/InstTableA32.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/InstTableA32.cs new file mode 100644 index 00000000..2168c0b9 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/InstTableA32.cs @@ -0,0 +1,1194 @@ +using Ryujinx.Cpu.LightningJit.Table; +using System.Collections.Generic; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + static class InstTableA32 where T : IInstEmit + { + private static readonly InstTableLevel _table; + + static InstTableA32() + { + InstEncoding[] condConstraints = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + }; + + InstEncoding[] condRnsRnConstraints = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + new(0x000F0000, 0x001F0000), + new(0x000D0000, 0x000F0000), + }; + + InstEncoding[] condRnConstraints = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + new(0x000D0000, 0x000F0000), + }; + + InstEncoding[] vdVmConstraints = new InstEncoding[] + { + new(0x00001000, 0x00001000), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] condRnConstraints2 = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + new(0x0000000F, 0x0000000F), + }; + + InstEncoding[] optionConstraints = new InstEncoding[] + { + new(0x00000000, 0x0000000F), + }; + + InstEncoding[] condPuwPwPuwPuwConstraints = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + new(0x00000000, 0x01A00000), + new(0x01000000, 0x01200000), + new(0x00200000, 0x01A00000), + new(0x01A00000, 0x01A00000), + }; + + InstEncoding[] condRnPuwConstraints = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + new(0x000F0000, 0x000F0000), + new(0x00000000, 0x01A00000), + }; + + InstEncoding[] condPuwConstraints = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + new(0x00000000, 0x01A00000), + }; + + InstEncoding[] condRnPwConstraints = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + new(0x000F0000, 0x000F0000), + new(0x00200000, 0x01200000), + }; + + InstEncoding[] condPwConstraints = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + new(0x00200000, 0x01200000), + }; + + InstEncoding[] condRnConstraints3 = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + new(0x000F0000, 0x000F0000), + }; + + InstEncoding[] condMaskrConstraints = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + new(0x00000000, 0x004F0000), + }; + + InstEncoding[] rnConstraints = new InstEncoding[] + { + new(0x000F0000, 0x000F0000), + }; + + InstEncoding[] vdVnVmConstraints = new InstEncoding[] + { + new(0x00001000, 0x00001000), + new(0x00010000, 0x00010000), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] condRaConstraints = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + new(0x0000F000, 0x0000F000), + }; + + InstEncoding[] sizeQvdQvnQvmConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] sizeVdConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x00001000, 0x00001000), + }; + + InstEncoding[] qvdQvnQvmConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] sizeQvdQvmConstraints = new InstEncoding[] + { + new(0x000C0000, 0x000C0000), + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] sizeVnVmConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x00010000, 0x00010000), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] sizeVdOpvnConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x00001000, 0x00001000), + new(0x00010100, 0x00010100), + }; + + InstEncoding[] cmodeCmodeQvdConstraints = new InstEncoding[] + { + new(0x00000000, 0x00000100), + new(0x00000C00, 0x00000C00), + new(0x00001040, 0x00001040), + }; + + InstEncoding[] qvdQvnQvmOpConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + new(0x00000041, 0x00000041), + new(0x00000000, 0x00300000), + }; + + InstEncoding[] qvdQvnQvmSizeConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + new(0x00000041, 0x00000041), + new(0x00300000, 0x00300000), + }; + + InstEncoding[] qvdQvnConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + }; + + InstEncoding[] qvdQvmConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] sizeConstraints = new InstEncoding[] + { + new(0x00000000, 0x00000300), + }; + + InstEncoding[] vmConstraints = new InstEncoding[] + { + new(0x00000001, 0x00000001), + }; + + InstEncoding[] opvdOpvmConstraints = new InstEncoding[] + { + new(0x00001100, 0x00001100), + new(0x00000001, 0x00000101), + }; + + InstEncoding[] imm6Opimm6Imm6QvdQvmConstraints = new InstEncoding[] + { + new(0x00000000, 0x00380000), + new(0x00200000, 0x00300200), + new(0x00000000, 0x00200000), + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] condQvdEbConstraints = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + new(0x00210000, 0x00210000), + new(0x00400020, 0x00400020), + }; + + InstEncoding[] imm4QvdConstraints = new InstEncoding[] + { + new(0x00000000, 0x00070000), + new(0x00001040, 0x00001040), + }; + + InstEncoding[] qvdQvnQvmQimm4Constraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + new(0x00000041, 0x00000041), + new(0x00000800, 0x00000840), + }; + + InstEncoding[] qvdConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + }; + + InstEncoding[] vdVnConstraints = new InstEncoding[] + { + new(0x00001000, 0x00001000), + new(0x00010000, 0x00010000), + }; + + InstEncoding[] sizeConstraints2 = new InstEncoding[] + { + new(0x00000C00, 0x00000C00), + }; + + InstEncoding[] sizeIndexAlignIndexAlignConstraints = new InstEncoding[] + { + new(0x00000C00, 0x00000C00), + new(0x00000010, 0x00000030), + new(0x00000020, 0x00000030), + }; + + InstEncoding[] sizeSizeaConstraints = new InstEncoding[] + { + new(0x000000C0, 0x000000C0), + new(0x00000010, 0x000000D0), + }; + + InstEncoding[] alignConstraints = new InstEncoding[] + { + new(0x00000020, 0x00000020), + }; + + InstEncoding[] alignConstraints2 = new InstEncoding[] + { + new(0x00000030, 0x00000030), + }; + + InstEncoding[] sizeConstraints3 = new InstEncoding[] + { + new(0x000000C0, 0x000000C0), + }; + + InstEncoding[] alignSizeConstraints = new InstEncoding[] + { + new(0x00000030, 0x00000030), + new(0x000000C0, 0x000000C0), + }; + + InstEncoding[] sizeAConstraints = new InstEncoding[] + { + new(0x000000C0, 0x000000C0), + new(0x00000010, 0x00000010), + }; + + InstEncoding[] sizeAlignConstraints = new InstEncoding[] + { + new(0x000000C0, 0x000000C0), + new(0x00000020, 0x00000020), + }; + + InstEncoding[] sizeIndexAlignConstraints = new InstEncoding[] + { + new(0x00000C00, 0x00000C00), + new(0x00000030, 0x00000030), + }; + + InstEncoding[] sizeaConstraints = new InstEncoding[] + { + new(0x000000C0, 0x000000D0), + }; + + InstEncoding[] sizeSizeVdConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x00000000, 0x00300000), + new(0x00001000, 0x00001000), + }; + + InstEncoding[] sizeQvdQvnConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x01001000, 0x01001000), + new(0x01010000, 0x01010000), + }; + + InstEncoding[] imm3hImm3hImm3hImm3hImm3hVdConstraints = new InstEncoding[] + { + new(0x00000000, 0x00380000), + new(0x00180000, 0x00380000), + new(0x00280000, 0x00380000), + new(0x00300000, 0x00380000), + new(0x00380000, 0x00380000), + new(0x00001000, 0x00001000), + }; + + InstEncoding[] sizeVmConstraints = new InstEncoding[] + { + new(0x000C0000, 0x000C0000), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] condOpc1opc2Constraints = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + new(0x00000040, 0x00400060), + }; + + InstEncoding[] condUopc1opc2Uopc1opc2Constraints = new InstEncoding[] + { + new(0xF0000000, 0xF0000000), + new(0x00800000, 0x00C00060), + new(0x00000040, 0x00400060), + }; + + InstEncoding[] sizeOpuOpsizeVdConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x01000200, 0x01000200), + new(0x00100200, 0x00300200), + new(0x00001000, 0x00001000), + }; + + InstEncoding[] sizeOpsizeOpsizeQvdQvnQvmConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x01100000, 0x01300000), + new(0x01200000, 0x01300000), + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] cmodeQvdConstraints = new InstEncoding[] + { + new(0x00000E00, 0x00000E00), + new(0x00001040, 0x00001040), + }; + + InstEncoding[] qConstraints = new InstEncoding[] + { + new(0x00000040, 0x00000040), + }; + + InstEncoding[] sizeQConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x00000040, 0x00000040), + }; + + InstEncoding[] sizeConstraints4 = new InstEncoding[] + { + new(0x00300000, 0x00300000), + }; + + InstEncoding[] qvdQvnQvmSizeSizeConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + new(0x00000041, 0x00000041), + new(0x00000000, 0x00300000), + new(0x00300000, 0x00300000), + }; + + InstEncoding[] sizeSizeQvdQvnConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x00000000, 0x00300000), + new(0x01001000, 0x01001000), + new(0x01010000, 0x01010000), + }; + + InstEncoding[] opSizeVmConstraints = new InstEncoding[] + { + new(0x00000000, 0x000000C0), + new(0x000C0000, 0x000C0000), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] qvdQvmQvnConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + new(0x00010040, 0x00010040), + }; + + InstEncoding[] imm6UopVmConstraints = new InstEncoding[] + { + new(0x00000000, 0x00380000), + new(0x00000000, 0x01000100), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] imm6lUopQvdQvmConstraints = new InstEncoding[] + { + new(0x00000000, 0x00380080), + new(0x00000000, 0x01000100), + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] qvdQvmSizeSizeConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + new(0x00000000, 0x000C0000), + new(0x000C0000, 0x000C0000), + }; + + InstEncoding[] sizeSizeSizeQvdQvmConstraints = new InstEncoding[] + { + new(0x00040000, 0x000C0000), + new(0x00080000, 0x000C0000), + new(0x000C0000, 0x000C0000), + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] sizeSizeQvdQvmConstraints = new InstEncoding[] + { + new(0x00080000, 0x000C0000), + new(0x000C0000, 0x000C0000), + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] imm6lQvdQvmConstraints = new InstEncoding[] + { + new(0x00000000, 0x00380080), + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] imm6VmConstraints = new InstEncoding[] + { + new(0x00000000, 0x00380000), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] imm6VdImm6Imm6Imm6Constraints = new InstEncoding[] + { + new(0x00000000, 0x00380000), + new(0x00001000, 0x00001000), + new(0x00080000, 0x003F0000), + new(0x00100000, 0x003F0000), + new(0x00200000, 0x003F0000), + }; + + InstEncoding[] sizeVdConstraints2 = new InstEncoding[] + { + new(0x000C0000, 0x000C0000), + new(0x00001000, 0x00001000), + }; + + InstEncoding[] sizeQsizeQvdQvmConstraints = new InstEncoding[] + { + new(0x000C0000, 0x000C0000), + new(0x00080000, 0x000C0040), + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + List insts = new() + { + new(0x02A00000, 0x0FE00000, condConstraints, InstName.AdcI, T.AdcIA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00A00000, 0x0FE00010, condConstraints, InstName.AdcR, T.AdcRA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00A00010, 0x0FE00090, condConstraints, InstName.AdcRr, T.AdcRrA1, IsaVersion.v80, InstFlags.CondRd), + new(0x02800000, 0x0FE00000, condRnsRnConstraints, InstName.AddI, T.AddIA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00800000, 0x0FE00010, condRnConstraints, InstName.AddR, T.AddRA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00800010, 0x0FE00090, condConstraints, InstName.AddRr, T.AddRrA1, IsaVersion.v80, InstFlags.CondRd), + new(0x028D0000, 0x0FEF0000, condConstraints, InstName.AddSpI, T.AddSpIA1, IsaVersion.v80, InstFlags.CondRd), + new(0x008D0000, 0x0FEF0010, condConstraints, InstName.AddSpR, T.AddSpRA1, IsaVersion.v80, InstFlags.CondRd), + new(0x028F0000, 0x0FFF0000, condConstraints, InstName.Adr, T.AdrA1, IsaVersion.v80, InstFlags.CondRd), + new(0x024F0000, 0x0FFF0000, condConstraints, InstName.Adr, T.AdrA2, IsaVersion.v80, InstFlags.CondRd), + new(0xF3B00340, 0xFFBF0FD0, vdVmConstraints, InstName.Aesd, T.AesdA1, IsaVersion.v80, IsaFeature.FeatAes, InstFlags.None), + new(0xF3B00300, 0xFFBF0FD0, vdVmConstraints, InstName.Aese, T.AeseA1, IsaVersion.v80, IsaFeature.FeatAes, InstFlags.None), + new(0xF3B003C0, 0xFFBF0FD0, vdVmConstraints, InstName.Aesimc, T.AesimcA1, IsaVersion.v80, IsaFeature.FeatAes, InstFlags.None), + new(0xF3B00380, 0xFFBF0FD0, vdVmConstraints, InstName.Aesmc, T.AesmcA1, IsaVersion.v80, IsaFeature.FeatAes, InstFlags.None), + new(0x02000000, 0x0FE00000, condConstraints, InstName.AndI, T.AndIA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00000000, 0x0FE00010, condConstraints, InstName.AndR, T.AndRA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00000010, 0x0FE00090, condConstraints, InstName.AndRr, T.AndRrA1, IsaVersion.v80, InstFlags.CondRd), + new(0x0A000000, 0x0F000000, condConstraints, InstName.B, T.BA1, IsaVersion.v80, InstFlags.Cond), + new(0x07C0001F, 0x0FE0007F, condConstraints, InstName.Bfc, T.BfcA1, IsaVersion.v80, InstFlags.CondRd), + new(0x07C00010, 0x0FE00070, condRnConstraints2, InstName.Bfi, T.BfiA1, IsaVersion.v80, InstFlags.CondRd), + new(0x03C00000, 0x0FE00000, condConstraints, InstName.BicI, T.BicIA1, IsaVersion.v80, InstFlags.CondRd), + new(0x01C00000, 0x0FE00010, condConstraints, InstName.BicR, T.BicRA1, IsaVersion.v80, InstFlags.CondRd), + new(0x01C00010, 0x0FE00090, condConstraints, InstName.BicRr, T.BicRrA1, IsaVersion.v80, InstFlags.CondRd), + new(0x01200070, 0x0FF000F0, condConstraints, InstName.Bkpt, T.BkptA1, IsaVersion.v80, InstFlags.Cond), + new(0x012FFF30, 0x0FFFFFF0, condConstraints, InstName.BlxR, T.BlxRA1, IsaVersion.v80, InstFlags.Cond), + new(0x0B000000, 0x0F000000, condConstraints, InstName.BlI, T.BlIA1, IsaVersion.v80, InstFlags.Cond), + new(0xFA000000, 0xFE000000, InstName.BlI, T.BlIA2, IsaVersion.v80, InstFlags.None), + new(0x012FFF10, 0x0FFFFFF0, condConstraints, InstName.Bx, T.BxA1, IsaVersion.v80, InstFlags.Cond), + new(0x012FFF20, 0x0FFFFFF0, condConstraints, InstName.Bxj, T.BxjA1, IsaVersion.v80, InstFlags.Cond), + new(0x0320F016, 0x0FFFFFFF, condConstraints, InstName.Clrbhb, T.ClrbhbA1, IsaVersion.v89, IsaFeature.FeatClrbhb, InstFlags.Cond), + new(0xF57FF01F, 0xFFFFFFFF, InstName.Clrex, T.ClrexA1, IsaVersion.v80, InstFlags.None), + new(0x016F0F10, 0x0FFF0FF0, condConstraints, InstName.Clz, T.ClzA1, IsaVersion.v80, InstFlags.CondRd), + new(0x03700000, 0x0FF0F000, condConstraints, InstName.CmnI, T.CmnIA1, IsaVersion.v80, InstFlags.Cond), + new(0x01700000, 0x0FF0F010, condConstraints, InstName.CmnR, T.CmnRA1, IsaVersion.v80, InstFlags.Cond), + new(0x01700010, 0x0FF0F090, condConstraints, InstName.CmnRr, T.CmnRrA1, IsaVersion.v80, InstFlags.Cond), + new(0x03500000, 0x0FF0F000, condConstraints, InstName.CmpI, T.CmpIA1, IsaVersion.v80, InstFlags.Cond), + new(0x01500000, 0x0FF0F010, condConstraints, InstName.CmpR, T.CmpRA1, IsaVersion.v80, InstFlags.Cond), + new(0x01500010, 0x0FF0F090, condConstraints, InstName.CmpRr, T.CmpRrA1, IsaVersion.v80, InstFlags.Cond), + new(0xF1000000, 0xFFF1FE20, InstName.Cps, T.CpsA1, IsaVersion.v80, InstFlags.None), + new(0x01000040, 0x0F900FF0, condConstraints, InstName.Crc32, T.Crc32A1, IsaVersion.v80, IsaFeature.FeatCrc32, InstFlags.CondRd), + new(0x01000240, 0x0F900FF0, condConstraints, InstName.Crc32c, T.Crc32cA1, IsaVersion.v80, IsaFeature.FeatCrc32, InstFlags.CondRd), + new(0x0320F014, 0x0FFFFFFF, condConstraints, InstName.Csdb, T.CsdbA1, IsaVersion.v80, InstFlags.Cond), + new(0x0320F0F0, 0x0FFFFFF0, condConstraints, InstName.Dbg, T.DbgA1, IsaVersion.v80, InstFlags.Cond), + new(0xF57FF050, 0xFFFFFFF0, InstName.Dmb, T.DmbA1, IsaVersion.v80, InstFlags.None), + new(0xF57FF040, 0xFFFFFFF0, optionConstraints, InstName.Dsb, T.DsbA1, IsaVersion.v80, InstFlags.None), + new(0x02200000, 0x0FE00000, condConstraints, InstName.EorI, T.EorIA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00200000, 0x0FE00010, condConstraints, InstName.EorR, T.EorRA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00200010, 0x0FE00090, condConstraints, InstName.EorRr, T.EorRrA1, IsaVersion.v80, InstFlags.CondRd), + new(0x0160006E, 0x0FFFFFFF, condConstraints, InstName.Eret, T.EretA1, IsaVersion.v80, InstFlags.Cond), + new(0x0320F010, 0x0FFFFFFF, condConstraints, InstName.Esb, T.EsbA1, IsaVersion.v82, IsaFeature.FeatRas, InstFlags.Cond), + new(0x0C100B01, 0x0E100F01, condPuwPwPuwPuwConstraints, InstName.Fldmx, T.FldmxA1, IsaVersion.v80, InstFlags.CondWBack), + new(0x0C000B01, 0x0E100F01, condPuwPwPuwPuwConstraints, InstName.Fstmx, T.FstmxA1, IsaVersion.v80, InstFlags.CondWBack), + new(0x01000070, 0x0FF000F0, condConstraints, InstName.Hlt, T.HltA1, IsaVersion.v80, InstFlags.Cond), + new(0x01400070, 0x0FF000F0, condConstraints, InstName.Hvc, T.HvcA1, IsaVersion.v80, InstFlags.Cond), + new(0xF57FF060, 0xFFFFFFF0, InstName.Isb, T.IsbA1, IsaVersion.v80, InstFlags.None), + new(0x01900C9F, 0x0FF00FFF, condConstraints, InstName.Lda, T.LdaA1, IsaVersion.v80, InstFlags.CondRt), + new(0x01D00C9F, 0x0FF00FFF, condConstraints, InstName.Ldab, T.LdabA1, IsaVersion.v80, InstFlags.CondRt), + new(0x01900E9F, 0x0FF00FFF, condConstraints, InstName.Ldaex, T.LdaexA1, IsaVersion.v80, InstFlags.CondRt), + new(0x01D00E9F, 0x0FF00FFF, condConstraints, InstName.Ldaexb, T.LdaexbA1, IsaVersion.v80, InstFlags.CondRt), + new(0x01B00E9F, 0x0FF00FFF, condConstraints, InstName.Ldaexd, T.LdaexdA1, IsaVersion.v80, InstFlags.CondRt2), + new(0x01F00E9F, 0x0FF00FFF, condConstraints, InstName.Ldaexh, T.LdaexhA1, IsaVersion.v80, InstFlags.CondRt), + new(0x01F00C9F, 0x0FF00FFF, condConstraints, InstName.Ldah, T.LdahA1, IsaVersion.v80, InstFlags.CondRt), + new(0x0C105E00, 0x0E50FF00, condRnPuwConstraints, InstName.LdcI, T.LdcIA1, IsaVersion.v80, InstFlags.CondWBack), + new(0x0C1F5E00, 0x0E5FFF00, condPuwConstraints, InstName.LdcL, T.LdcLA1, IsaVersion.v80, InstFlags.CondWBack), + new(0x08900000, 0x0FD00000, condConstraints, InstName.Ldm, T.LdmA1, IsaVersion.v80, InstFlags.CondRlistWBack), + new(0x08100000, 0x0FD00000, condConstraints, InstName.Ldmda, T.LdmdaA1, IsaVersion.v80, InstFlags.CondRlistWBack), + new(0x09100000, 0x0FD00000, condConstraints, InstName.Ldmdb, T.LdmdbA1, IsaVersion.v80, InstFlags.CondRlistWBack), + new(0x09900000, 0x0FD00000, condConstraints, InstName.Ldmib, T.LdmibA1, IsaVersion.v80, InstFlags.CondRlistWBack), + new(0x08508000, 0x0E508000, condConstraints, InstName.LdmE, T.LdmEA1, IsaVersion.v80, InstFlags.CondRlistWBack), + new(0x08500000, 0x0E708000, condConstraints, InstName.LdmU, T.LdmUA1, IsaVersion.v80, InstFlags.CondRlist), + new(0x04700000, 0x0F700000, condConstraints, InstName.Ldrbt, T.LdrbtA1, IsaVersion.v80, InstFlags.CondRt), + new(0x06700000, 0x0F700010, condConstraints, InstName.Ldrbt, T.LdrbtA2, IsaVersion.v80, InstFlags.CondRt), + new(0x04500000, 0x0E500000, condRnPwConstraints, InstName.LdrbI, T.LdrbIA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x045F0000, 0x0E5F0000, condPwConstraints, InstName.LdrbL, T.LdrbLA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x06500000, 0x0E500010, condPwConstraints, InstName.LdrbR, T.LdrbRA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x004000D0, 0x0E5000F0, condRnConstraints3, InstName.LdrdI, T.LdrdIA1, IsaVersion.v80, InstFlags.CondRt2WBack), + new(0x014F00D0, 0x0F7F00F0, condConstraints, InstName.LdrdL, T.LdrdLA1, IsaVersion.v80, InstFlags.CondRt2), + new(0x000000D0, 0x0E500FF0, condConstraints, InstName.LdrdR, T.LdrdRA1, IsaVersion.v80, InstFlags.CondRt2WBack), + new(0x01900F9F, 0x0FF00FFF, condConstraints, InstName.Ldrex, T.LdrexA1, IsaVersion.v80, InstFlags.CondRt), + new(0x01D00F9F, 0x0FF00FFF, condConstraints, InstName.Ldrexb, T.LdrexbA1, IsaVersion.v80, InstFlags.CondRt), + new(0x01B00F9F, 0x0FF00FFF, condConstraints, InstName.Ldrexd, T.LdrexdA1, IsaVersion.v80, InstFlags.CondRt2), + new(0x01F00F9F, 0x0FF00FFF, condConstraints, InstName.Ldrexh, T.LdrexhA1, IsaVersion.v80, InstFlags.CondRt), + new(0x007000B0, 0x0F7000F0, condConstraints, InstName.Ldrht, T.LdrhtA1, IsaVersion.v80, InstFlags.CondRt), + new(0x003000B0, 0x0F700FF0, condConstraints, InstName.Ldrht, T.LdrhtA2, IsaVersion.v80, InstFlags.CondRt), + new(0x005000B0, 0x0E5000F0, condRnPwConstraints, InstName.LdrhI, T.LdrhIA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x005F00B0, 0x0E5F00F0, condPwConstraints, InstName.LdrhL, T.LdrhLA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x001000B0, 0x0E500FF0, condPwConstraints, InstName.LdrhR, T.LdrhRA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x007000D0, 0x0F7000F0, condConstraints, InstName.Ldrsbt, T.LdrsbtA1, IsaVersion.v80, InstFlags.CondRt), + new(0x003000D0, 0x0F700FF0, condConstraints, InstName.Ldrsbt, T.LdrsbtA2, IsaVersion.v80, InstFlags.CondRt), + new(0x005000D0, 0x0E5000F0, condRnPwConstraints, InstName.LdrsbI, T.LdrsbIA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x005F00D0, 0x0E5F00F0, condPwConstraints, InstName.LdrsbL, T.LdrsbLA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x001000D0, 0x0E500FF0, condPwConstraints, InstName.LdrsbR, T.LdrsbRA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x007000F0, 0x0F7000F0, condConstraints, InstName.Ldrsht, T.LdrshtA1, IsaVersion.v80, InstFlags.CondRt), + new(0x003000F0, 0x0F700FF0, condConstraints, InstName.Ldrsht, T.LdrshtA2, IsaVersion.v80, InstFlags.CondRt), + new(0x005000F0, 0x0E5000F0, condRnPwConstraints, InstName.LdrshI, T.LdrshIA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x005F00F0, 0x0E5F00F0, condPwConstraints, InstName.LdrshL, T.LdrshLA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x001000F0, 0x0E500FF0, condPwConstraints, InstName.LdrshR, T.LdrshRA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x04300000, 0x0F700000, condConstraints, InstName.Ldrt, T.LdrtA1, IsaVersion.v80, InstFlags.CondRt), + new(0x06300000, 0x0F700010, condConstraints, InstName.Ldrt, T.LdrtA2, IsaVersion.v80, InstFlags.CondRt), + new(0x04100000, 0x0E500000, condRnPwConstraints, InstName.LdrI, T.LdrIA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x041F0000, 0x0E5F0000, condPwConstraints, InstName.LdrL, T.LdrLA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x06100000, 0x0E500010, condPwConstraints, InstName.LdrR, T.LdrRA1, IsaVersion.v80, InstFlags.CondRtWBack), + new(0x0E000E10, 0x0F100E10, condConstraints, InstName.Mcr, T.McrA1, IsaVersion.v80, InstFlags.CondRt), + new(0x0C400E00, 0x0FF00E00, condConstraints, InstName.Mcrr, T.McrrA1, IsaVersion.v80, InstFlags.CondRt), + new(0x00200090, 0x0FE000F0, condConstraints, InstName.Mla, T.MlaA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x00600090, 0x0FF000F0, condConstraints, InstName.Mls, T.MlsA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x03400000, 0x0FF00000, condConstraints, InstName.Movt, T.MovtA1, IsaVersion.v80, InstFlags.CondRd), + new(0x03A00000, 0x0FEF0000, condConstraints, InstName.MovI, T.MovIA1, IsaVersion.v80, InstFlags.CondRd), + new(0x03000000, 0x0FF00000, condConstraints, InstName.MovI, T.MovIA2, IsaVersion.v80, InstFlags.CondRd), + new(0x01A00000, 0x0FEF0010, condConstraints, InstName.MovR, T.MovRA1, IsaVersion.v80, InstFlags.CondRd), + new(0x01A00010, 0x0FEF0090, condConstraints, InstName.MovRr, T.MovRrA1, IsaVersion.v80, InstFlags.CondRd), + new(0x0E100E10, 0x0F100E10, condConstraints, InstName.Mrc, T.MrcA1, IsaVersion.v80, InstFlags.CondRt), + new(0x0C500E00, 0x0FF00E00, condConstraints, InstName.Mrrc, T.MrrcA1, IsaVersion.v80, InstFlags.CondRt), + new(0x010F0000, 0x0FBF0FFF, condConstraints, InstName.Mrs, T.MrsA1, IsaVersion.v80, InstFlags.CondRd), + new(0x01000200, 0x0FB00EFF, condConstraints, InstName.MrsBr, T.MrsBrA1, IsaVersion.v80, InstFlags.CondRd), + new(0x0120F200, 0x0FB0FEF0, condConstraints, InstName.MsrBr, T.MsrBrA1, IsaVersion.v80, InstFlags.Cond), + new(0x0320F000, 0x0FB0F000, condMaskrConstraints, InstName.MsrI, T.MsrIA1, IsaVersion.v80, InstFlags.Cond), + new(0x0120F000, 0x0FB0FFF0, condConstraints, InstName.MsrR, T.MsrRA1, IsaVersion.v80, InstFlags.Cond), + new(0x00000090, 0x0FE0F0F0, condConstraints, InstName.Mul, T.MulA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x03E00000, 0x0FEF0000, condConstraints, InstName.MvnI, T.MvnIA1, IsaVersion.v80, InstFlags.CondRd), + new(0x01E00000, 0x0FEF0010, condConstraints, InstName.MvnR, T.MvnRA1, IsaVersion.v80, InstFlags.CondRd), + new(0x01E00010, 0x0FEF0090, condConstraints, InstName.MvnRr, T.MvnRrA1, IsaVersion.v80, InstFlags.CondRd), + new(0x0320F000, 0x0FFFFFFF, condConstraints, InstName.Nop, T.NopA1, IsaVersion.v80, InstFlags.Cond), + new(0x03800000, 0x0FE00000, condConstraints, InstName.OrrI, T.OrrIA1, IsaVersion.v80, InstFlags.CondRd), + new(0x01800000, 0x0FE00010, condConstraints, InstName.OrrR, T.OrrRA1, IsaVersion.v80, InstFlags.CondRd), + new(0x01800010, 0x0FE00090, condConstraints, InstName.OrrRr, T.OrrRrA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06800010, 0x0FF00030, condConstraints, InstName.Pkh, T.PkhA1, IsaVersion.v80, InstFlags.CondRd), + new(0xF510F000, 0xFF30F000, rnConstraints, InstName.PldI, T.PldIA1, IsaVersion.v80, InstFlags.None), + new(0xF55FF000, 0xFF7FF000, InstName.PldL, T.PldLA1, IsaVersion.v80, InstFlags.None), + new(0xF710F000, 0xFF30F010, InstName.PldR, T.PldRA1, IsaVersion.v80, InstFlags.None), + new(0xF450F000, 0xFF70F000, InstName.PliI, T.PliIA1, IsaVersion.v80, InstFlags.None), + new(0xF650F000, 0xFF70F010, InstName.PliR, T.PliRA1, IsaVersion.v80, InstFlags.None), + new(0xF57FF044, 0xFFFFFFFF, InstName.Pssbb, T.PssbbA1, IsaVersion.v80, InstFlags.None), + new(0x01000050, 0x0FF00FF0, condConstraints, InstName.Qadd, T.QaddA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06200F10, 0x0FF00FF0, condConstraints, InstName.Qadd16, T.Qadd16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06200F90, 0x0FF00FF0, condConstraints, InstName.Qadd8, T.Qadd8A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06200F30, 0x0FF00FF0, condConstraints, InstName.Qasx, T.QasxA1, IsaVersion.v80, InstFlags.CondRd), + new(0x01400050, 0x0FF00FF0, condConstraints, InstName.Qdadd, T.QdaddA1, IsaVersion.v80, InstFlags.CondRd), + new(0x01600050, 0x0FF00FF0, condConstraints, InstName.Qdsub, T.QdsubA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06200F50, 0x0FF00FF0, condConstraints, InstName.Qsax, T.QsaxA1, IsaVersion.v80, InstFlags.CondRd), + new(0x01200050, 0x0FF00FF0, condConstraints, InstName.Qsub, T.QsubA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06200F70, 0x0FF00FF0, condConstraints, InstName.Qsub16, T.Qsub16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06200FF0, 0x0FF00FF0, condConstraints, InstName.Qsub8, T.Qsub8A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06FF0F30, 0x0FFF0FF0, condConstraints, InstName.Rbit, T.RbitA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06BF0F30, 0x0FFF0FF0, condConstraints, InstName.Rev, T.RevA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06BF0FB0, 0x0FFF0FF0, condConstraints, InstName.Rev16, T.Rev16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06FF0FB0, 0x0FFF0FF0, condConstraints, InstName.Revsh, T.RevshA1, IsaVersion.v80, InstFlags.CondRd), + new(0xF8100A00, 0xFE50FFFF, InstName.Rfe, T.RfeA1, IsaVersion.v80, InstFlags.WBack), + new(0x02600000, 0x0FE00000, condConstraints, InstName.RsbI, T.RsbIA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00600000, 0x0FE00010, condConstraints, InstName.RsbR, T.RsbRA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00600010, 0x0FE00090, condConstraints, InstName.RsbRr, T.RsbRrA1, IsaVersion.v80, InstFlags.CondRd), + new(0x02E00000, 0x0FE00000, condConstraints, InstName.RscI, T.RscIA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00E00000, 0x0FE00010, condConstraints, InstName.RscR, T.RscRA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00E00010, 0x0FE00090, condConstraints, InstName.RscRr, T.RscRrA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06100F10, 0x0FF00FF0, condConstraints, InstName.Sadd16, T.Sadd16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06100F90, 0x0FF00FF0, condConstraints, InstName.Sadd8, T.Sadd8A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06100F30, 0x0FF00FF0, condConstraints, InstName.Sasx, T.SasxA1, IsaVersion.v80, InstFlags.CondRd), + new(0xF57FF070, 0xFFFFFFFF, InstName.Sb, T.SbA1, IsaVersion.v85, IsaFeature.FeatSb, InstFlags.None), + new(0x02C00000, 0x0FE00000, condConstraints, InstName.SbcI, T.SbcIA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00C00000, 0x0FE00010, condConstraints, InstName.SbcR, T.SbcRA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00C00010, 0x0FE00090, condConstraints, InstName.SbcRr, T.SbcRrA1, IsaVersion.v80, InstFlags.CondRd), + new(0x07A00050, 0x0FE00070, condConstraints, InstName.Sbfx, T.SbfxA1, IsaVersion.v80, InstFlags.CondRd), + new(0x0710F010, 0x0FF0F0F0, condConstraints, InstName.Sdiv, T.SdivA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x06800FB0, 0x0FF00FF0, condConstraints, InstName.Sel, T.SelA1, IsaVersion.v80, InstFlags.CondRd), + new(0xF1010000, 0xFFFFFDFF, InstName.Setend, T.SetendA1, IsaVersion.v80, InstFlags.None), + new(0xF1100000, 0xFFFFFDFF, InstName.Setpan, T.SetpanA1, IsaVersion.v81, IsaFeature.FeatPan, InstFlags.None), + new(0x0320F004, 0x0FFFFFFF, condConstraints, InstName.Sev, T.SevA1, IsaVersion.v80, InstFlags.Cond), + new(0x0320F005, 0x0FFFFFFF, condConstraints, InstName.Sevl, T.SevlA1, IsaVersion.v80, InstFlags.Cond), + new(0xF2000C40, 0xFFB00F50, vdVnVmConstraints, InstName.Sha1c, T.Sha1cA1, IsaVersion.v80, IsaFeature.FeatSha1, InstFlags.None), + new(0xF3B902C0, 0xFFBF0FD0, vdVmConstraints, InstName.Sha1h, T.Sha1hA1, IsaVersion.v80, IsaFeature.FeatSha1, InstFlags.None), + new(0xF2200C40, 0xFFB00F50, vdVnVmConstraints, InstName.Sha1m, T.Sha1mA1, IsaVersion.v80, IsaFeature.FeatSha1, InstFlags.None), + new(0xF2100C40, 0xFFB00F50, vdVnVmConstraints, InstName.Sha1p, T.Sha1pA1, IsaVersion.v80, IsaFeature.FeatSha1, InstFlags.None), + new(0xF2300C40, 0xFFB00F50, vdVnVmConstraints, InstName.Sha1su0, T.Sha1su0A1, IsaVersion.v80, IsaFeature.FeatSha1, InstFlags.None), + new(0xF3BA0380, 0xFFBF0FD0, vdVmConstraints, InstName.Sha1su1, T.Sha1su1A1, IsaVersion.v80, IsaFeature.FeatSha1, InstFlags.None), + new(0xF3000C40, 0xFFB00F50, vdVnVmConstraints, InstName.Sha256h, T.Sha256hA1, IsaVersion.v80, IsaFeature.FeatSha256, InstFlags.None), + new(0xF3100C40, 0xFFB00F50, vdVnVmConstraints, InstName.Sha256h2, T.Sha256h2A1, IsaVersion.v80, IsaFeature.FeatSha256, InstFlags.None), + new(0xF3BA03C0, 0xFFBF0FD0, vdVmConstraints, InstName.Sha256su0, T.Sha256su0A1, IsaVersion.v80, IsaFeature.FeatSha256, InstFlags.None), + new(0xF3200C40, 0xFFB00F50, vdVnVmConstraints, InstName.Sha256su1, T.Sha256su1A1, IsaVersion.v80, IsaFeature.FeatSha256, InstFlags.None), + new(0x06300F10, 0x0FF00FF0, condConstraints, InstName.Shadd16, T.Shadd16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06300F90, 0x0FF00FF0, condConstraints, InstName.Shadd8, T.Shadd8A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06300F30, 0x0FF00FF0, condConstraints, InstName.Shasx, T.ShasxA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06300F50, 0x0FF00FF0, condConstraints, InstName.Shsax, T.ShsaxA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06300F70, 0x0FF00FF0, condConstraints, InstName.Shsub16, T.Shsub16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06300FF0, 0x0FF00FF0, condConstraints, InstName.Shsub8, T.Shsub8A1, IsaVersion.v80, InstFlags.CondRd), + new(0x01600070, 0x0FFFFFF0, condConstraints, InstName.Smc, T.SmcA1, IsaVersion.v80, InstFlags.Cond), + new(0x01000080, 0x0FF00090, condConstraints, InstName.Smlabb, T.SmlabbA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x07000010, 0x0FF000D0, condRaConstraints, InstName.Smlad, T.SmladA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x00E00090, 0x0FE000F0, condConstraints, InstName.Smlal, T.SmlalA1, IsaVersion.v80, InstFlags.CondRdLoHi), + new(0x01400080, 0x0FF00090, condConstraints, InstName.Smlalbb, T.SmlalbbA1, IsaVersion.v80, InstFlags.CondRdLoHi), + new(0x07400010, 0x0FF000D0, condConstraints, InstName.Smlald, T.SmlaldA1, IsaVersion.v80, InstFlags.CondRdLoHi), + new(0x01200080, 0x0FF000B0, condConstraints, InstName.Smlawb, T.SmlawbA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x07000050, 0x0FF000D0, condRaConstraints, InstName.Smlsd, T.SmlsdA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x07400050, 0x0FF000D0, condConstraints, InstName.Smlsld, T.SmlsldA1, IsaVersion.v80, InstFlags.CondRdLoHi), + new(0x07500010, 0x0FF000D0, condRaConstraints, InstName.Smmla, T.SmmlaA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x075000D0, 0x0FF000D0, condConstraints, InstName.Smmls, T.SmmlsA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x0750F010, 0x0FF0F0D0, condConstraints, InstName.Smmul, T.SmmulA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x0700F010, 0x0FF0F0D0, condConstraints, InstName.Smuad, T.SmuadA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x01600080, 0x0FF0F090, condConstraints, InstName.Smulbb, T.SmulbbA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x00C00090, 0x0FE000F0, condConstraints, InstName.Smull, T.SmullA1, IsaVersion.v80, InstFlags.CondRdLoHi), + new(0x012000A0, 0x0FF0F0B0, condConstraints, InstName.Smulwb, T.SmulwbA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x0700F050, 0x0FF0F0D0, condConstraints, InstName.Smusd, T.SmusdA1, IsaVersion.v80, InstFlags.CondRd16), + new(0xF84D0500, 0xFE5FFFE0, InstName.Srs, T.SrsA1, IsaVersion.v80, InstFlags.WBack), + new(0x06A00010, 0x0FE00030, condConstraints, InstName.Ssat, T.SsatA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06A00F30, 0x0FF00FF0, condConstraints, InstName.Ssat16, T.Ssat16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06100F50, 0x0FF00FF0, condConstraints, InstName.Ssax, T.SsaxA1, IsaVersion.v80, InstFlags.CondRd), + new(0xF57FF040, 0xFFFFFFFF, InstName.Ssbb, T.SsbbA1, IsaVersion.v80, InstFlags.None), + new(0x06100F70, 0x0FF00FF0, condConstraints, InstName.Ssub16, T.Ssub16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06100FF0, 0x0FF00FF0, condConstraints, InstName.Ssub8, T.Ssub8A1, IsaVersion.v80, InstFlags.CondRd), + new(0x0C005E00, 0x0E50FF00, condPuwConstraints, InstName.Stc, T.StcA1, IsaVersion.v80, InstFlags.CondWBack), + new(0x0180FC90, 0x0FF0FFF0, condConstraints, InstName.Stl, T.StlA1, IsaVersion.v80, InstFlags.CondRtRead), + new(0x01C0FC90, 0x0FF0FFF0, condConstraints, InstName.Stlb, T.StlbA1, IsaVersion.v80, InstFlags.CondRtRead), + new(0x01800E90, 0x0FF00FF0, condConstraints, InstName.Stlex, T.StlexA1, IsaVersion.v80, InstFlags.CondRdRtRead), + new(0x01C00E90, 0x0FF00FF0, condConstraints, InstName.Stlexb, T.StlexbA1, IsaVersion.v80, InstFlags.CondRdRtRead), + new(0x01A00E90, 0x0FF00FF0, condConstraints, InstName.Stlexd, T.StlexdA1, IsaVersion.v80, InstFlags.CondRdRt2Read), + new(0x01E00E90, 0x0FF00FF0, condConstraints, InstName.Stlexh, T.StlexhA1, IsaVersion.v80, InstFlags.CondRdRtRead), + new(0x01E0FC90, 0x0FF0FFF0, condConstraints, InstName.Stlh, T.StlhA1, IsaVersion.v80, InstFlags.CondRtRead), + new(0x08800000, 0x0FD00000, condConstraints, InstName.Stm, T.StmA1, IsaVersion.v80, InstFlags.CondRlistReadWBack), + new(0x08000000, 0x0FD00000, condConstraints, InstName.Stmda, T.StmdaA1, IsaVersion.v80, InstFlags.CondRlistReadWBack), + new(0x09000000, 0x0FD00000, condConstraints, InstName.Stmdb, T.StmdbA1, IsaVersion.v80, InstFlags.CondRlistReadWBack), + new(0x09800000, 0x0FD00000, condConstraints, InstName.Stmib, T.StmibA1, IsaVersion.v80, InstFlags.CondRlistReadWBack), + new(0x08400000, 0x0E700000, condConstraints, InstName.StmU, T.StmUA1, IsaVersion.v80, InstFlags.CondRlistRead), + new(0x04600000, 0x0F700000, condConstraints, InstName.Strbt, T.StrbtA1, IsaVersion.v80, InstFlags.CondRtRead), + new(0x06600000, 0x0F700010, condConstraints, InstName.Strbt, T.StrbtA2, IsaVersion.v80, InstFlags.CondRtRead), + new(0x04400000, 0x0E500000, condPwConstraints, InstName.StrbI, T.StrbIA1, IsaVersion.v80, InstFlags.CondRtReadWBack), + new(0x06400000, 0x0E500010, condPwConstraints, InstName.StrbR, T.StrbRA1, IsaVersion.v80, InstFlags.CondRtReadWBack), + new(0x004000F0, 0x0E5000F0, condConstraints, InstName.StrdI, T.StrdIA1, IsaVersion.v80, InstFlags.CondRt2ReadWBack), + new(0x000000F0, 0x0E500FF0, condConstraints, InstName.StrdR, T.StrdRA1, IsaVersion.v80, InstFlags.CondRt2ReadWBack), + new(0x01800F90, 0x0FF00FF0, condConstraints, InstName.Strex, T.StrexA1, IsaVersion.v80, InstFlags.CondRdRtRead), + new(0x01C00F90, 0x0FF00FF0, condConstraints, InstName.Strexb, T.StrexbA1, IsaVersion.v80, InstFlags.CondRdRtRead), + new(0x01A00F90, 0x0FF00FF0, condConstraints, InstName.Strexd, T.StrexdA1, IsaVersion.v80, InstFlags.CondRdRt2Read), + new(0x01E00F90, 0x0FF00FF0, condConstraints, InstName.Strexh, T.StrexhA1, IsaVersion.v80, InstFlags.CondRdRtRead), + new(0x006000B0, 0x0F7000F0, condConstraints, InstName.Strht, T.StrhtA1, IsaVersion.v80, InstFlags.CondRtRead), + new(0x002000B0, 0x0F700FF0, condConstraints, InstName.Strht, T.StrhtA2, IsaVersion.v80, InstFlags.CondRtRead), + new(0x004000B0, 0x0E5000F0, condPwConstraints, InstName.StrhI, T.StrhIA1, IsaVersion.v80, InstFlags.CondRtReadWBack), + new(0x000000B0, 0x0E500FF0, condPwConstraints, InstName.StrhR, T.StrhRA1, IsaVersion.v80, InstFlags.CondRtReadWBack), + new(0x04200000, 0x0F700000, condConstraints, InstName.Strt, T.StrtA1, IsaVersion.v80, InstFlags.CondRtRead), + new(0x06200000, 0x0F700010, condConstraints, InstName.Strt, T.StrtA2, IsaVersion.v80, InstFlags.CondRtRead), + new(0x04000000, 0x0E500000, condPwConstraints, InstName.StrI, T.StrIA1, IsaVersion.v80, InstFlags.CondRtReadWBack), + new(0x06000000, 0x0E500010, condPwConstraints, InstName.StrR, T.StrRA1, IsaVersion.v80, InstFlags.CondRtReadWBack), + new(0x02400000, 0x0FE00000, condRnsRnConstraints, InstName.SubI, T.SubIA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00400000, 0x0FE00010, condRnConstraints, InstName.SubR, T.SubRA1, IsaVersion.v80, InstFlags.CondRd), + new(0x00400010, 0x0FE00090, condConstraints, InstName.SubRr, T.SubRrA1, IsaVersion.v80, InstFlags.CondRd), + new(0x024D0000, 0x0FEF0000, condConstraints, InstName.SubSpI, T.SubSpIA1, IsaVersion.v80, InstFlags.CondRd), + new(0x004D0000, 0x0FEF0010, condConstraints, InstName.SubSpR, T.SubSpRA1, IsaVersion.v80, InstFlags.CondRd), + new(0x0F000000, 0x0F000000, condConstraints, InstName.Svc, T.SvcA1, IsaVersion.v80, InstFlags.Cond), + new(0x06A00070, 0x0FF003F0, condRnConstraints3, InstName.Sxtab, T.SxtabA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06800070, 0x0FF003F0, condRnConstraints3, InstName.Sxtab16, T.Sxtab16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06B00070, 0x0FF003F0, condRnConstraints3, InstName.Sxtah, T.SxtahA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06AF0070, 0x0FFF03F0, condConstraints, InstName.Sxtb, T.SxtbA1, IsaVersion.v80, InstFlags.CondRd), + new(0x068F0070, 0x0FFF03F0, condConstraints, InstName.Sxtb16, T.Sxtb16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06BF0070, 0x0FFF03F0, condConstraints, InstName.Sxth, T.SxthA1, IsaVersion.v80, InstFlags.CondRd), + new(0x03300000, 0x0FF0F000, condConstraints, InstName.TeqI, T.TeqIA1, IsaVersion.v80, InstFlags.Cond), + new(0x01300000, 0x0FF0F010, condConstraints, InstName.TeqR, T.TeqRA1, IsaVersion.v80, InstFlags.Cond), + new(0x01300010, 0x0FF0F090, condConstraints, InstName.TeqRr, T.TeqRrA1, IsaVersion.v80, InstFlags.Cond), + new(0x0320F012, 0x0FFFFFFF, condConstraints, InstName.Tsb, T.TsbA1, IsaVersion.v84, IsaFeature.FeatTrf, InstFlags.Cond), + new(0x03100000, 0x0FF0F000, condConstraints, InstName.TstI, T.TstIA1, IsaVersion.v80, InstFlags.Cond), + new(0x01100000, 0x0FF0F010, condConstraints, InstName.TstR, T.TstRA1, IsaVersion.v80, InstFlags.Cond), + new(0x01100010, 0x0FF0F090, condConstraints, InstName.TstRr, T.TstRrA1, IsaVersion.v80, InstFlags.Cond), + new(0x06500F10, 0x0FF00FF0, condConstraints, InstName.Uadd16, T.Uadd16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06500F90, 0x0FF00FF0, condConstraints, InstName.Uadd8, T.Uadd8A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06500F30, 0x0FF00FF0, condConstraints, InstName.Uasx, T.UasxA1, IsaVersion.v80, InstFlags.CondRd), + new(0x07E00050, 0x0FE00070, condConstraints, InstName.Ubfx, T.UbfxA1, IsaVersion.v80, InstFlags.CondRd), + new(0xE7F000F0, 0xFFF000F0, InstName.Udf, T.UdfA1, IsaVersion.v80, InstFlags.None), + new(0x0730F010, 0x0FF0F0F0, condConstraints, InstName.Udiv, T.UdivA1, IsaVersion.v80, InstFlags.CondRd16), + new(0x06700F10, 0x0FF00FF0, condConstraints, InstName.Uhadd16, T.Uhadd16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06700F90, 0x0FF00FF0, condConstraints, InstName.Uhadd8, T.Uhadd8A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06700F30, 0x0FF00FF0, condConstraints, InstName.Uhasx, T.UhasxA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06700F50, 0x0FF00FF0, condConstraints, InstName.Uhsax, T.UhsaxA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06700F70, 0x0FF00FF0, condConstraints, InstName.Uhsub16, T.Uhsub16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06700FF0, 0x0FF00FF0, condConstraints, InstName.Uhsub8, T.Uhsub8A1, IsaVersion.v80, InstFlags.CondRd), + new(0x00400090, 0x0FF000F0, condConstraints, InstName.Umaal, T.UmaalA1, IsaVersion.v80, InstFlags.CondRdLoHi), + new(0x00A00090, 0x0FE000F0, condConstraints, InstName.Umlal, T.UmlalA1, IsaVersion.v80, InstFlags.CondRdLoHi), + new(0x00800090, 0x0FE000F0, condConstraints, InstName.Umull, T.UmullA1, IsaVersion.v80, InstFlags.CondRdLoHi), + new(0x06600F10, 0x0FF00FF0, condConstraints, InstName.Uqadd16, T.Uqadd16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06600F90, 0x0FF00FF0, condConstraints, InstName.Uqadd8, T.Uqadd8A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06600F30, 0x0FF00FF0, condConstraints, InstName.Uqasx, T.UqasxA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06600F50, 0x0FF00FF0, condConstraints, InstName.Uqsax, T.UqsaxA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06600F70, 0x0FF00FF0, condConstraints, InstName.Uqsub16, T.Uqsub16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06600FF0, 0x0FF00FF0, condConstraints, InstName.Uqsub8, T.Uqsub8A1, IsaVersion.v80, InstFlags.CondRd), + new(0x0780F010, 0x0FF0F0F0, condConstraints, InstName.Usad8, T.Usad8A1, IsaVersion.v80, InstFlags.CondRd16), + new(0x07800010, 0x0FF000F0, condRaConstraints, InstName.Usada8, T.Usada8A1, IsaVersion.v80, InstFlags.CondRd16), + new(0x06E00010, 0x0FE00030, condConstraints, InstName.Usat, T.UsatA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06E00F30, 0x0FF00FF0, condConstraints, InstName.Usat16, T.Usat16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06500F50, 0x0FF00FF0, condConstraints, InstName.Usax, T.UsaxA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06500F70, 0x0FF00FF0, condConstraints, InstName.Usub16, T.Usub16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06500FF0, 0x0FF00FF0, condConstraints, InstName.Usub8, T.Usub8A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06E00070, 0x0FF003F0, condRnConstraints3, InstName.Uxtab, T.UxtabA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06C00070, 0x0FF003F0, condRnConstraints3, InstName.Uxtab16, T.Uxtab16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06F00070, 0x0FF003F0, condRnConstraints3, InstName.Uxtah, T.UxtahA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06EF0070, 0x0FFF03F0, condConstraints, InstName.Uxtb, T.UxtbA1, IsaVersion.v80, InstFlags.CondRd), + new(0x06CF0070, 0x0FFF03F0, condConstraints, InstName.Uxtb16, T.Uxtb16A1, IsaVersion.v80, InstFlags.CondRd), + new(0x06FF0070, 0x0FFF03F0, condConstraints, InstName.Uxth, T.UxthA1, IsaVersion.v80, InstFlags.CondRd), + new(0xF2000710, 0xFE800F10, sizeQvdQvnQvmConstraints, InstName.Vaba, T.VabaA1, IsaVersion.v80, InstFlags.None), + new(0xF2800500, 0xFE800F50, sizeVdConstraints, InstName.Vabal, T.VabalA1, IsaVersion.v80, InstFlags.None), + new(0xF2800700, 0xFE800F50, sizeVdConstraints, InstName.VabdlI, T.VabdlIA1, IsaVersion.v80, InstFlags.None), + new(0xF3200D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VabdF, T.VabdFA1, IsaVersion.v80, InstFlags.None), + new(0xF3300D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VabdF, T.VabdFA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2000700, 0xFE800F10, sizeQvdQvnQvmConstraints, InstName.VabdI, T.VabdIA1, IsaVersion.v80, InstFlags.None), + new(0xF3B10300, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vabs, T.VabsA1, IsaVersion.v80, InstFlags.None), + new(0xF3B90700, 0xFFBB0F90, sizeQvdQvmConstraints, InstName.Vabs, T.VabsA1, IsaVersion.v80, InstFlags.None), + new(0xF3B50700, 0xFFBF0F90, sizeQvdQvmConstraints, InstName.Vabs, T.VabsA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0EB00AC0, 0x0FBF0ED0, condConstraints, InstName.Vabs, T.VabsA2, IsaVersion.v80, InstFlags.Cond), + new(0x0EB009C0, 0x0FBF0FD0, condConstraints, InstName.Vabs, T.VabsA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF3000E10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vacge, T.VacgeA1, IsaVersion.v80, InstFlags.None), + new(0xF3100E10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vacge, T.VacgeA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF3200E10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vacgt, T.VacgtA1, IsaVersion.v80, InstFlags.None), + new(0xF3300E10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vacgt, T.VacgtA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2800400, 0xFF800F50, sizeVnVmConstraints, InstName.Vaddhn, T.VaddhnA1, IsaVersion.v80, InstFlags.None), + new(0xF2800000, 0xFE800F50, sizeVdOpvnConstraints, InstName.Vaddl, T.VaddlA1, IsaVersion.v80, InstFlags.None), + new(0xF2800100, 0xFE800F50, sizeVdOpvnConstraints, InstName.Vaddw, T.VaddwA1, IsaVersion.v80, InstFlags.None), + new(0xF2000D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VaddF, T.VaddFA1, IsaVersion.v80, InstFlags.None), + new(0xF2100D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VaddF, T.VaddFA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0E300A00, 0x0FB00E50, condConstraints, InstName.VaddF, T.VaddFA2, IsaVersion.v80, InstFlags.Cond), + new(0x0E300900, 0x0FB00F50, condConstraints, InstName.VaddF, T.VaddFA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF2000800, 0xFF800F10, qvdQvnQvmConstraints, InstName.VaddI, T.VaddIA1, IsaVersion.v80, InstFlags.None), + new(0xF2000110, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VandR, T.VandRA1, IsaVersion.v80, InstFlags.None), + new(0xF2800130, 0xFEB809B0, cmodeCmodeQvdConstraints, InstName.VbicI, T.VbicIA1, IsaVersion.v80, InstFlags.None), + new(0xF2800930, 0xFEB80DB0, cmodeCmodeQvdConstraints, InstName.VbicI, T.VbicIA2, IsaVersion.v80, InstFlags.None), + new(0xF2100110, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VbicR, T.VbicRA1, IsaVersion.v80, InstFlags.None), + new(0xF3300110, 0xFFB00F10, qvdQvnQvmOpConstraints, InstName.Vbif, T.VbifA1, IsaVersion.v80, InstFlags.None), + new(0xF3200110, 0xFFB00F10, qvdQvnQvmOpConstraints, InstName.Vbit, T.VbitA1, IsaVersion.v80, InstFlags.None), + new(0xF3100110, 0xFFB00F10, qvdQvnQvmOpConstraints, InstName.Vbsl, T.VbslA1, IsaVersion.v80, InstFlags.None), + new(0xFC900800, 0xFEB00F10, qvdQvnQvmConstraints, InstName.Vcadd, T.VcaddA1, IsaVersion.v83, IsaFeature.FeatFcma, InstFlags.None), + new(0xFC800800, 0xFEB00F10, qvdQvnQvmConstraints, InstName.Vcadd, T.VcaddA1, IsaVersion.v83, IsaFeature.FeatFcma | IsaFeature.FeatFp16, InstFlags.None), + new(0xF3B10100, 0xFFB30F90, sizeQvdQvmConstraints, InstName.VceqI, T.VceqIA1, IsaVersion.v80, InstFlags.None), + new(0xF3B90500, 0xFFBB0F90, sizeQvdQvmConstraints, InstName.VceqI, T.VceqIA1, IsaVersion.v80, InstFlags.None), + new(0xF3B50500, 0xFFBF0F90, sizeQvdQvmConstraints, InstName.VceqI, T.VceqIA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF3000810, 0xFF800F10, qvdQvnQvmSizeConstraints, InstName.VceqR, T.VceqRA1, IsaVersion.v80, InstFlags.None), + new(0xF2000E00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VceqR, T.VceqRA2, IsaVersion.v80, InstFlags.None), + new(0xF2100E00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VceqR, T.VceqRA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF3B10080, 0xFFB30F90, sizeQvdQvmConstraints, InstName.VcgeI, T.VcgeIA1, IsaVersion.v80, InstFlags.None), + new(0xF3B90480, 0xFFBB0F90, sizeQvdQvmConstraints, InstName.VcgeI, T.VcgeIA1, IsaVersion.v80, InstFlags.None), + new(0xF3B50480, 0xFFBF0F90, sizeQvdQvmConstraints, InstName.VcgeI, T.VcgeIA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2000310, 0xFE800F10, qvdQvnQvmSizeConstraints, InstName.VcgeR, T.VcgeRA1, IsaVersion.v80, InstFlags.None), + new(0xF3000E00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VcgeR, T.VcgeRA2, IsaVersion.v80, InstFlags.None), + new(0xF3100E00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VcgeR, T.VcgeRA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF3B10000, 0xFFB30F90, sizeQvdQvmConstraints, InstName.VcgtI, T.VcgtIA1, IsaVersion.v80, InstFlags.None), + new(0xF3B90400, 0xFFBB0F90, sizeQvdQvmConstraints, InstName.VcgtI, T.VcgtIA1, IsaVersion.v80, InstFlags.None), + new(0xF3B50400, 0xFFBF0F90, sizeQvdQvmConstraints, InstName.VcgtI, T.VcgtIA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2000300, 0xFE800F10, qvdQvnQvmSizeConstraints, InstName.VcgtR, T.VcgtRA1, IsaVersion.v80, InstFlags.None), + new(0xF3200E00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VcgtR, T.VcgtRA2, IsaVersion.v80, InstFlags.None), + new(0xF3300E00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VcgtR, T.VcgtRA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF3B10180, 0xFFB30F90, sizeQvdQvmConstraints, InstName.VcleI, T.VcleIA1, IsaVersion.v80, InstFlags.None), + new(0xF3B90580, 0xFFBB0F90, sizeQvdQvmConstraints, InstName.VcleI, T.VcleIA1, IsaVersion.v80, InstFlags.None), + new(0xF3B50580, 0xFFBF0F90, sizeQvdQvmConstraints, InstName.VcleI, T.VcleIA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF3B00400, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vcls, T.VclsA1, IsaVersion.v80, InstFlags.None), + new(0xF3B10200, 0xFFB30F90, sizeQvdQvmConstraints, InstName.VcltI, T.VcltIA1, IsaVersion.v80, InstFlags.None), + new(0xF3B90600, 0xFFBB0F90, sizeQvdQvmConstraints, InstName.VcltI, T.VcltIA1, IsaVersion.v80, InstFlags.None), + new(0xF3B50600, 0xFFBF0F90, sizeQvdQvmConstraints, InstName.VcltI, T.VcltIA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF3B00480, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vclz, T.VclzA1, IsaVersion.v80, InstFlags.None), + new(0xFC200800, 0xFE200F10, qvdQvnQvmConstraints, InstName.Vcmla, T.VcmlaA1, IsaVersion.v83, IsaFeature.FeatFcma, InstFlags.None), + new(0xFE000800, 0xFF000F10, qvdQvnConstraints, InstName.VcmlaS, T.VcmlaSA1, IsaVersion.v83, IsaFeature.FeatFcma, InstFlags.None), + new(0x0EB40A40, 0x0FBF0ED0, condConstraints, InstName.Vcmp, T.VcmpA1, IsaVersion.v80, InstFlags.Cond), + new(0x0EB40940, 0x0FBF0FD0, condConstraints, InstName.Vcmp, T.VcmpA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0x0EB50A40, 0x0FBF0EFF, condConstraints, InstName.Vcmp, T.VcmpA2, IsaVersion.v80, InstFlags.Cond), + new(0x0EB50940, 0x0FBF0FFF, condConstraints, InstName.Vcmp, T.VcmpA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0x0EB40AC0, 0x0FBF0ED0, condConstraints, InstName.Vcmpe, T.VcmpeA1, IsaVersion.v80, InstFlags.Cond), + new(0x0EB409C0, 0x0FBF0FD0, condConstraints, InstName.Vcmpe, T.VcmpeA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0x0EB50AC0, 0x0FBF0EFF, condConstraints, InstName.Vcmpe, T.VcmpeA2, IsaVersion.v80, InstFlags.Cond), + new(0x0EB509C0, 0x0FBF0FFF, condConstraints, InstName.Vcmpe, T.VcmpeA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF3B00500, 0xFFBF0F90, qvdQvmConstraints, InstName.Vcnt, T.VcntA1, IsaVersion.v80, InstFlags.None), + new(0xF3BB0000, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtaAsimd, T.VcvtaAsimdA1, IsaVersion.v80, InstFlags.None), + new(0xF3B70000, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtaAsimd, T.VcvtaAsimdA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEBC0A40, 0xFFBF0E50, sizeConstraints, InstName.VcvtaVfp, T.VcvtaVfpA1, IsaVersion.v80, InstFlags.None), + new(0xFEBC0940, 0xFFBF0F50, sizeConstraints, InstName.VcvtaVfp, T.VcvtaVfpA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0EB20A40, 0x0FBE0ED0, condConstraints, InstName.Vcvtb, T.VcvtbA1, IsaVersion.v80, InstFlags.Cond), + new(0x0EB30940, 0x0FBF0FD0, condConstraints, InstName.VcvtbBfs, T.VcvtbBfsA1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.Cond), + new(0xF3BB0300, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtmAsimd, T.VcvtmAsimdA1, IsaVersion.v80, InstFlags.None), + new(0xF3B70300, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtmAsimd, T.VcvtmAsimdA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEBF0A40, 0xFFBF0E50, sizeConstraints, InstName.VcvtmVfp, T.VcvtmVfpA1, IsaVersion.v80, InstFlags.None), + new(0xFEBF0940, 0xFFBF0F50, sizeConstraints, InstName.VcvtmVfp, T.VcvtmVfpA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF3BB0100, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtnAsimd, T.VcvtnAsimdA1, IsaVersion.v80, InstFlags.None), + new(0xF3B70100, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtnAsimd, T.VcvtnAsimdA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEBD0A40, 0xFFBF0E50, sizeConstraints, InstName.VcvtnVfp, T.VcvtnVfpA1, IsaVersion.v80, InstFlags.None), + new(0xFEBD0940, 0xFFBF0F50, sizeConstraints, InstName.VcvtnVfp, T.VcvtnVfpA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF3BB0200, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtpAsimd, T.VcvtpAsimdA1, IsaVersion.v80, InstFlags.None), + new(0xF3B70200, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtpAsimd, T.VcvtpAsimdA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEBE0A40, 0xFFBF0E50, sizeConstraints, InstName.VcvtpVfp, T.VcvtpVfpA1, IsaVersion.v80, InstFlags.None), + new(0xFEBE0940, 0xFFBF0F50, sizeConstraints, InstName.VcvtpVfp, T.VcvtpVfpA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0EBC0A40, 0x0FBE0ED0, condConstraints, InstName.VcvtrIv, T.VcvtrIvA1, IsaVersion.v80, InstFlags.Cond), + new(0x0EBC0940, 0x0FBE0FD0, condConstraints, InstName.VcvtrIv, T.VcvtrIvA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0x0EB20AC0, 0x0FBE0ED0, condConstraints, InstName.Vcvtt, T.VcvttA1, IsaVersion.v80, InstFlags.Cond), + new(0x0EB309C0, 0x0FBF0FD0, condConstraints, InstName.VcvttBfs, T.VcvttBfsA1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.Cond), + new(0xF3B60640, 0xFFBF0FD0, vmConstraints, InstName.VcvtBfs, T.VcvtBfsA1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.None), + new(0x0EB70AC0, 0x0FBF0ED0, condConstraints, InstName.VcvtDs, T.VcvtDsA1, IsaVersion.v80, InstFlags.Cond), + new(0xF3B60600, 0xFFBF0ED0, opvdOpvmConstraints, InstName.VcvtHs, T.VcvtHsA1, IsaVersion.v80, InstFlags.None), + new(0xF3BB0600, 0xFFBF0E10, qvdQvmConstraints, InstName.VcvtIs, T.VcvtIsA1, IsaVersion.v80, InstFlags.None), + new(0xF3B70600, 0xFFBF0E10, qvdQvmConstraints, InstName.VcvtIs, T.VcvtIsA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0EBC0AC0, 0x0FBE0ED0, condConstraints, InstName.VcvtIv, T.VcvtIvA1, IsaVersion.v80, InstFlags.Cond), + new(0x0EBC09C0, 0x0FBE0FD0, condConstraints, InstName.VcvtIv, T.VcvtIvA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0x0EB80A40, 0x0FBF0E50, condConstraints, InstName.VcvtVi, T.VcvtViA1, IsaVersion.v80, InstFlags.Cond), + new(0x0EB80940, 0x0FBF0F50, condConstraints, InstName.VcvtVi, T.VcvtViA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF2800E10, 0xFE800E90, imm6Opimm6Imm6QvdQvmConstraints, InstName.VcvtXs, T.VcvtXsA1, IsaVersion.v80, InstFlags.None), + new(0xF2800C10, 0xFE800E90, imm6Opimm6Imm6QvdQvmConstraints, InstName.VcvtXs, T.VcvtXsA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0EBA0A40, 0x0FBA0E50, condConstraints, InstName.VcvtXv, T.VcvtXvA1, IsaVersion.v80, InstFlags.Cond), + new(0x0EBA0940, 0x0FBA0F50, condConstraints, InstName.VcvtXv, T.VcvtXvA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0x0E800A00, 0x0FB00E50, condConstraints, InstName.Vdiv, T.VdivA1, IsaVersion.v80, InstFlags.Cond), + new(0x0E800900, 0x0FB00F50, condConstraints, InstName.Vdiv, T.VdivA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xFC000D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vdot, T.VdotA1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.None), + new(0xFE000D00, 0xFFB00F10, qvdQvnConstraints, InstName.VdotS, T.VdotSA1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.None), + new(0x0E800B10, 0x0F900F5F, condQvdEbConstraints, InstName.VdupR, T.VdupRA1, IsaVersion.v80, InstFlags.CondRtRead), + new(0xF3B00C00, 0xFFB00F90, imm4QvdConstraints, InstName.VdupS, T.VdupSA1, IsaVersion.v80, InstFlags.None), + new(0xF3000110, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Veor, T.VeorA1, IsaVersion.v80, InstFlags.None), + new(0xF2B00000, 0xFFB00010, qvdQvnQvmQimm4Constraints, InstName.Vext, T.VextA1, IsaVersion.v80, InstFlags.None), + new(0xF2000C10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vfma, T.VfmaA1, IsaVersion.v80, InstFlags.None), + new(0xF2100C10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vfma, T.VfmaA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0EA00A00, 0x0FB00E50, condConstraints, InstName.Vfma, T.VfmaA2, IsaVersion.v80, InstFlags.Cond), + new(0x0EA00900, 0x0FB00F50, condConstraints, InstName.Vfma, T.VfmaA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xFC200810, 0xFFB00F10, qvdConstraints, InstName.Vfmal, T.VfmalA1, IsaVersion.v82, IsaFeature.FeatFhm, InstFlags.None), + new(0xFE000810, 0xFFB00F10, qvdConstraints, InstName.VfmalS, T.VfmalSA1, IsaVersion.v82, IsaFeature.FeatFhm, InstFlags.None), + new(0xFC300810, 0xFFB00F10, vdVnVmConstraints, InstName.VfmaBf, T.VfmaBfA1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.None), + new(0xFE300810, 0xFFB00F10, vdVnConstraints, InstName.VfmaBfs, T.VfmaBfsA1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.None), + new(0xF2200C10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vfms, T.VfmsA1, IsaVersion.v80, InstFlags.None), + new(0xF2300C10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vfms, T.VfmsA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0EA00A40, 0x0FB00E50, condConstraints, InstName.Vfms, T.VfmsA2, IsaVersion.v80, InstFlags.Cond), + new(0x0EA00940, 0x0FB00F50, condConstraints, InstName.Vfms, T.VfmsA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xFCA00810, 0xFFB00F10, qvdConstraints, InstName.Vfmsl, T.VfmslA1, IsaVersion.v82, IsaFeature.FeatFhm, InstFlags.None), + new(0xFE100810, 0xFFB00F10, qvdConstraints, InstName.VfmslS, T.VfmslSA1, IsaVersion.v82, IsaFeature.FeatFhm, InstFlags.None), + new(0x0E900A40, 0x0FB00E50, condConstraints, InstName.Vfnma, T.VfnmaA1, IsaVersion.v80, InstFlags.Cond), + new(0x0E900940, 0x0FB00F50, condConstraints, InstName.Vfnma, T.VfnmaA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0x0E900A00, 0x0FB00E50, condConstraints, InstName.Vfnms, T.VfnmsA1, IsaVersion.v80, InstFlags.Cond), + new(0x0E900900, 0x0FB00F50, condConstraints, InstName.Vfnms, T.VfnmsA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF2000000, 0xFE800F10, qvdQvnQvmSizeConstraints, InstName.Vhadd, T.VhaddA1, IsaVersion.v80, InstFlags.None), + new(0xF2000200, 0xFE800F10, qvdQvnQvmSizeConstraints, InstName.Vhsub, T.VhsubA1, IsaVersion.v80, InstFlags.None), + new(0xFEB00AC0, 0xFFBF0FD0, InstName.Vins, T.VinsA1, IsaVersion.v82, IsaFeature.FeatFp16, InstFlags.None), + new(0x0EB90BC0, 0x0FBF0FD0, condConstraints, InstName.Vjcvt, T.VjcvtA1, IsaVersion.v83, IsaFeature.FeatJscvt, InstFlags.Cond), + new(0xF4A00000, 0xFFB00F10, sizeConstraints2, InstName.Vld11, T.Vld11A1, IsaVersion.v80, InstFlags.None), + new(0xF4A00400, 0xFFB00F20, sizeConstraints2, InstName.Vld11, T.Vld11A2, IsaVersion.v80, InstFlags.None), + new(0xF4A00800, 0xFFB00F40, sizeIndexAlignIndexAlignConstraints, InstName.Vld11, T.Vld11A3, IsaVersion.v80, InstFlags.None), + new(0xF4A00C00, 0xFFB00F00, sizeSizeaConstraints, InstName.Vld1A, T.Vld1AA1, IsaVersion.v80, InstFlags.None), + new(0xF4200700, 0xFFB00F00, alignConstraints, InstName.Vld1M, T.Vld1MA1, IsaVersion.v80, InstFlags.None), + new(0xF4200A00, 0xFFB00F00, alignConstraints2, InstName.Vld1M, T.Vld1MA2, IsaVersion.v80, InstFlags.None), + new(0xF4200600, 0xFFB00F00, alignConstraints, InstName.Vld1M, T.Vld1MA3, IsaVersion.v80, InstFlags.None), + new(0xF4200200, 0xFFB00F00, InstName.Vld1M, T.Vld1MA4, IsaVersion.v80, InstFlags.None), + new(0xF4A00100, 0xFFB00F00, sizeConstraints2, InstName.Vld21, T.Vld21A1, IsaVersion.v80, InstFlags.None), + new(0xF4A00500, 0xFFB00F00, sizeConstraints2, InstName.Vld21, T.Vld21A2, IsaVersion.v80, InstFlags.None), + new(0xF4A00900, 0xFFB00F20, sizeConstraints2, InstName.Vld21, T.Vld21A3, IsaVersion.v80, InstFlags.None), + new(0xF4A00D00, 0xFFB00F00, sizeConstraints3, InstName.Vld2A, T.Vld2AA1, IsaVersion.v80, InstFlags.None), + new(0xF4200800, 0xFFB00E00, alignSizeConstraints, InstName.Vld2M, T.Vld2MA1, IsaVersion.v80, InstFlags.None), + new(0xF4200300, 0xFFB00F00, sizeConstraints3, InstName.Vld2M, T.Vld2MA2, IsaVersion.v80, InstFlags.None), + new(0xF4A00200, 0xFFB00F10, sizeConstraints2, InstName.Vld31, T.Vld31A1, IsaVersion.v80, InstFlags.None), + new(0xF4A00600, 0xFFB00F10, sizeConstraints2, InstName.Vld31, T.Vld31A2, IsaVersion.v80, InstFlags.None), + new(0xF4A00A00, 0xFFB00F30, sizeConstraints2, InstName.Vld31, T.Vld31A3, IsaVersion.v80, InstFlags.None), + new(0xF4A00E00, 0xFFB00F10, sizeAConstraints, InstName.Vld3A, T.Vld3AA1, IsaVersion.v80, InstFlags.None), + new(0xF4200400, 0xFFB00E00, sizeAlignConstraints, InstName.Vld3M, T.Vld3MA1, IsaVersion.v80, InstFlags.None), + new(0xF4A00300, 0xFFB00F00, sizeConstraints2, InstName.Vld41, T.Vld41A1, IsaVersion.v80, InstFlags.None), + new(0xF4A00700, 0xFFB00F00, sizeConstraints2, InstName.Vld41, T.Vld41A2, IsaVersion.v80, InstFlags.None), + new(0xF4A00B00, 0xFFB00F00, sizeIndexAlignConstraints, InstName.Vld41, T.Vld41A3, IsaVersion.v80, InstFlags.None), + new(0xF4A00F00, 0xFFB00F00, sizeaConstraints, InstName.Vld4A, T.Vld4AA1, IsaVersion.v80, InstFlags.None), + new(0xF4200000, 0xFFB00E00, sizeConstraints3, InstName.Vld4M, T.Vld4MA1, IsaVersion.v80, InstFlags.None), + new(0x0C100B00, 0x0E100F01, condPuwPwPuwPuwConstraints, InstName.Vldm, T.VldmA1, IsaVersion.v80, InstFlags.CondWBack), + new(0x0C100A00, 0x0E100F00, condPuwPwPuwPuwConstraints, InstName.Vldm, T.VldmA2, IsaVersion.v80, InstFlags.CondWBack), + new(0x0D100A00, 0x0F300E00, condRnConstraints3, InstName.VldrI, T.VldrIA1, IsaVersion.v80, InstFlags.Cond), + new(0x0D100900, 0x0F300F00, condRnConstraints3, InstName.VldrI, T.VldrIA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0x0D1F0A00, 0x0F3F0E00, condConstraints, InstName.VldrL, T.VldrLA1, IsaVersion.v80, InstFlags.Cond), + new(0x0D1F0900, 0x0F3F0F00, condConstraints, InstName.VldrL, T.VldrLA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF3000F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vmaxnm, T.VmaxnmA1, IsaVersion.v80, InstFlags.None), + new(0xF3100F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vmaxnm, T.VmaxnmA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFE800A00, 0xFFB00E50, sizeConstraints, InstName.Vmaxnm, T.VmaxnmA2, IsaVersion.v80, InstFlags.None), + new(0xFE800900, 0xFFB00F50, sizeConstraints, InstName.Vmaxnm, T.VmaxnmA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2000F00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmaxF, T.VmaxFA1, IsaVersion.v80, InstFlags.None), + new(0xF2100F00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmaxF, T.VmaxFA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2000600, 0xFE800F10, qvdQvnQvmSizeConstraints, InstName.VmaxI, T.VmaxIA1, IsaVersion.v80, InstFlags.None), + new(0xF3200F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vminnm, T.VminnmA1, IsaVersion.v80, InstFlags.None), + new(0xF3300F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vminnm, T.VminnmA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFE800A40, 0xFFB00E50, sizeConstraints, InstName.Vminnm, T.VminnmA2, IsaVersion.v80, InstFlags.None), + new(0xFE800940, 0xFFB00F50, sizeConstraints, InstName.Vminnm, T.VminnmA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2200F00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VminF, T.VminFA1, IsaVersion.v80, InstFlags.None), + new(0xF2300F00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VminF, T.VminFA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2000610, 0xFE800F10, qvdQvnQvmSizeConstraints, InstName.VminI, T.VminIA1, IsaVersion.v80, InstFlags.None), + new(0xF2800800, 0xFE800F50, sizeVdConstraints, InstName.VmlalI, T.VmlalIA1, IsaVersion.v80, InstFlags.None), + new(0xF2800240, 0xFE800F50, sizeSizeVdConstraints, InstName.VmlalS, T.VmlalSA1, IsaVersion.v80, InstFlags.None), + new(0xF2000D10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmlaF, T.VmlaFA1, IsaVersion.v80, InstFlags.None), + new(0xF2100D10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmlaF, T.VmlaFA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0E000A00, 0x0FB00E50, condConstraints, InstName.VmlaF, T.VmlaFA2, IsaVersion.v80, InstFlags.Cond), + new(0x0E000900, 0x0FB00F50, condConstraints, InstName.VmlaF, T.VmlaFA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF2000900, 0xFF800F10, sizeQvdQvnQvmConstraints, InstName.VmlaI, T.VmlaIA1, IsaVersion.v80, InstFlags.None), + new(0xF2A00040, 0xFEA00E50, sizeQvdQvnConstraints, InstName.VmlaS, T.VmlaSA1, IsaVersion.v80, InstFlags.None), + new(0xF2900040, 0xFEB00F50, sizeQvdQvnConstraints, InstName.VmlaS, T.VmlaSA1, IsaVersion.v80, InstFlags.None), + new(0xF2900140, 0xFEB00F50, sizeQvdQvnConstraints, InstName.VmlaS, T.VmlaSA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2800A00, 0xFE800F50, sizeVdConstraints, InstName.VmlslI, T.VmlslIA1, IsaVersion.v80, InstFlags.None), + new(0xF2800640, 0xFE800F50, sizeSizeVdConstraints, InstName.VmlslS, T.VmlslSA1, IsaVersion.v80, InstFlags.None), + new(0xF2200D10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmlsF, T.VmlsFA1, IsaVersion.v80, InstFlags.None), + new(0xF2300D10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmlsF, T.VmlsFA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0E000A40, 0x0FB00E50, condConstraints, InstName.VmlsF, T.VmlsFA2, IsaVersion.v80, InstFlags.Cond), + new(0x0E000940, 0x0FB00F50, condConstraints, InstName.VmlsF, T.VmlsFA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF3000900, 0xFF800F10, sizeQvdQvnQvmConstraints, InstName.VmlsI, T.VmlsIA1, IsaVersion.v80, InstFlags.None), + new(0xF2A00440, 0xFEA00E50, sizeQvdQvnConstraints, InstName.VmlsS, T.VmlsSA1, IsaVersion.v80, InstFlags.None), + new(0xF2900440, 0xFEB00F50, sizeQvdQvnConstraints, InstName.VmlsS, T.VmlsSA1, IsaVersion.v80, InstFlags.None), + new(0xF2900540, 0xFEB00F50, sizeQvdQvnConstraints, InstName.VmlsS, T.VmlsSA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFC000C40, 0xFFB00F50, vdVnVmConstraints, InstName.Vmmla, T.VmmlaA1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.None), + new(0xF2800A10, 0xFE870FD0, imm3hImm3hImm3hImm3hImm3hVdConstraints, InstName.Vmovl, T.VmovlA1, IsaVersion.v80, InstFlags.None), + new(0xF3B20200, 0xFFB30FD0, sizeVmConstraints, InstName.Vmovn, T.VmovnA1, IsaVersion.v80, InstFlags.None), + new(0xFEB00A40, 0xFFBF0FD0, InstName.Vmovx, T.VmovxA1, IsaVersion.v82, IsaFeature.FeatFp16, InstFlags.None), + new(0x0C400B10, 0x0FE00FD0, condConstraints, InstName.VmovD, T.VmovDA1, IsaVersion.v80, InstFlags.CondRt2Read), + new(0x0E000910, 0x0FE00F7F, condConstraints, InstName.VmovH, T.VmovHA1, IsaVersion.v82, IsaFeature.FeatFp16, InstFlags.CondRt), + new(0xF2800010, 0xFEB809B0, qvdConstraints, InstName.VmovI, T.VmovIA1, IsaVersion.v80, InstFlags.None), + new(0x0EB00A00, 0x0FB00EF0, condConstraints, InstName.VmovI, T.VmovIA2, IsaVersion.v80, InstFlags.Cond), + new(0x0EB00900, 0x0FB00FF0, condConstraints, InstName.VmovI, T.VmovIA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF2800810, 0xFEB80DB0, qvdConstraints, InstName.VmovI, T.VmovIA3, IsaVersion.v80, InstFlags.None), + new(0xF2800C10, 0xFEB80CB0, qvdConstraints, InstName.VmovI, T.VmovIA4, IsaVersion.v80, InstFlags.None), + new(0xF2800E30, 0xFEB80FB0, qvdConstraints, InstName.VmovI, T.VmovIA5, IsaVersion.v80, InstFlags.None), + new(0x0EB00A40, 0x0FBF0ED0, condConstraints, InstName.VmovR, T.VmovRA2, IsaVersion.v80, InstFlags.Cond), + new(0x0E000B10, 0x0F900F1F, condOpc1opc2Constraints, InstName.VmovRs, T.VmovRsA1, IsaVersion.v80, InstFlags.CondRtRead), + new(0x0E000A10, 0x0FE00F7F, condConstraints, InstName.VmovS, T.VmovSA1, IsaVersion.v80, InstFlags.CondRtRead), + new(0x0E100B10, 0x0F100F1F, condUopc1opc2Uopc1opc2Constraints, InstName.VmovSr, T.VmovSrA1, IsaVersion.v80, InstFlags.CondRt), + new(0x0C400A10, 0x0FE00FD0, condConstraints, InstName.VmovSs, T.VmovSsA1, IsaVersion.v80, InstFlags.CondRt2Read), + new(0x0EF00A10, 0x0FF00FFF, condConstraints, InstName.Vmrs, T.VmrsA1, IsaVersion.v80, InstFlags.CondRt), + new(0x0EE00A10, 0x0FF00FFF, condConstraints, InstName.Vmsr, T.VmsrA1, IsaVersion.v80, InstFlags.CondRtRead), + new(0xF2800C00, 0xFE800D50, sizeOpuOpsizeVdConstraints, InstName.VmullI, T.VmullIA1, IsaVersion.v80, InstFlags.None), + new(0xF2800A40, 0xFE800F50, sizeSizeVdConstraints, InstName.VmullS, T.VmullSA1, IsaVersion.v80, InstFlags.None), + new(0xF3000D10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmulF, T.VmulFA1, IsaVersion.v80, InstFlags.None), + new(0xF3100D10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmulF, T.VmulFA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0E200A00, 0x0FB00E50, condConstraints, InstName.VmulF, T.VmulFA2, IsaVersion.v80, InstFlags.Cond), + new(0x0E200900, 0x0FB00F50, condConstraints, InstName.VmulF, T.VmulFA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF2000910, 0xFE800F10, sizeOpsizeOpsizeQvdQvnQvmConstraints, InstName.VmulI, T.VmulIA1, IsaVersion.v80, InstFlags.None), + new(0xF2A00840, 0xFEA00E50, sizeQvdQvnConstraints, InstName.VmulS, T.VmulSA1, IsaVersion.v80, InstFlags.None), + new(0xF2900840, 0xFEB00F50, sizeQvdQvnConstraints, InstName.VmulS, T.VmulSA1, IsaVersion.v80, InstFlags.None), + new(0xF2900940, 0xFEB00F50, sizeQvdQvnConstraints, InstName.VmulS, T.VmulSA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2800030, 0xFEB809B0, cmodeQvdConstraints, InstName.VmvnI, T.VmvnIA1, IsaVersion.v80, InstFlags.None), + new(0xF2800830, 0xFEB80DB0, cmodeQvdConstraints, InstName.VmvnI, T.VmvnIA2, IsaVersion.v80, InstFlags.None), + new(0xF2800C30, 0xFEB80EB0, cmodeQvdConstraints, InstName.VmvnI, T.VmvnIA3, IsaVersion.v80, InstFlags.None), + new(0xF3B00580, 0xFFBF0F90, qvdQvmConstraints, InstName.VmvnR, T.VmvnRA1, IsaVersion.v80, InstFlags.None), + new(0xF3B10380, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vneg, T.VnegA1, IsaVersion.v80, InstFlags.None), + new(0xF3B90780, 0xFFBB0F90, sizeQvdQvmConstraints, InstName.Vneg, T.VnegA1, IsaVersion.v80, InstFlags.None), + new(0xF3B50780, 0xFFBF0F90, sizeQvdQvmConstraints, InstName.Vneg, T.VnegA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0EB10A40, 0x0FBF0ED0, condConstraints, InstName.Vneg, T.VnegA2, IsaVersion.v80, InstFlags.Cond), + new(0x0EB10940, 0x0FBF0FD0, condConstraints, InstName.Vneg, T.VnegA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0x0E100A40, 0x0FB00E50, condConstraints, InstName.Vnmla, T.VnmlaA1, IsaVersion.v80, InstFlags.Cond), + new(0x0E100940, 0x0FB00F50, condConstraints, InstName.Vnmla, T.VnmlaA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0x0E100A00, 0x0FB00E50, condConstraints, InstName.Vnmls, T.VnmlsA1, IsaVersion.v80, InstFlags.Cond), + new(0x0E100900, 0x0FB00F50, condConstraints, InstName.Vnmls, T.VnmlsA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0x0E200A40, 0x0FB00E50, condConstraints, InstName.Vnmul, T.VnmulA1, IsaVersion.v80, InstFlags.Cond), + new(0x0E200940, 0x0FB00F50, condConstraints, InstName.Vnmul, T.VnmulA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF2300110, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VornR, T.VornRA1, IsaVersion.v80, InstFlags.None), + new(0xF2800110, 0xFEB809B0, cmodeCmodeQvdConstraints, InstName.VorrI, T.VorrIA1, IsaVersion.v80, InstFlags.None), + new(0xF2800910, 0xFEB80DB0, cmodeCmodeQvdConstraints, InstName.VorrI, T.VorrIA2, IsaVersion.v80, InstFlags.None), + new(0xF2200110, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VorrR, T.VorrRA1, IsaVersion.v80, InstFlags.None), + new(0xF3B00600, 0xFFB30F10, sizeQvdQvmConstraints, InstName.Vpadal, T.VpadalA1, IsaVersion.v80, InstFlags.None), + new(0xF3B00200, 0xFFB30F10, sizeQvdQvmConstraints, InstName.Vpaddl, T.VpaddlA1, IsaVersion.v80, InstFlags.None), + new(0xF3000D00, 0xFFB00F10, qConstraints, InstName.VpaddF, T.VpaddFA1, IsaVersion.v80, InstFlags.None), + new(0xF3100D00, 0xFFB00F10, qConstraints, InstName.VpaddF, T.VpaddFA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2000B10, 0xFF800F10, sizeQConstraints, InstName.VpaddI, T.VpaddIA1, IsaVersion.v80, InstFlags.None), + new(0xF3000F00, 0xFFB00F50, InstName.VpmaxF, T.VpmaxFA1, IsaVersion.v80, InstFlags.None), + new(0xF3100F00, 0xFFB00F50, InstName.VpmaxF, T.VpmaxFA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2000A00, 0xFE800F50, sizeConstraints4, InstName.VpmaxI, T.VpmaxIA1, IsaVersion.v80, InstFlags.None), + new(0xF3200F00, 0xFFB00F50, InstName.VpminF, T.VpminFA1, IsaVersion.v80, InstFlags.None), + new(0xF3300F00, 0xFFB00F50, InstName.VpminF, T.VpminFA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2000A10, 0xFE800F50, sizeConstraints4, InstName.VpminI, T.VpminIA1, IsaVersion.v80, InstFlags.None), + new(0xF3B00700, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vqabs, T.VqabsA1, IsaVersion.v80, InstFlags.None), + new(0xF2000010, 0xFE800F10, qvdQvnQvmConstraints, InstName.Vqadd, T.VqaddA1, IsaVersion.v80, InstFlags.None), + new(0xF2800900, 0xFF800F50, sizeSizeVdConstraints, InstName.Vqdmlal, T.VqdmlalA1, IsaVersion.v80, InstFlags.None), + new(0xF2800340, 0xFF800F50, sizeSizeVdConstraints, InstName.Vqdmlal, T.VqdmlalA2, IsaVersion.v80, InstFlags.None), + new(0xF2800B00, 0xFF800F50, sizeSizeVdConstraints, InstName.Vqdmlsl, T.VqdmlslA1, IsaVersion.v80, InstFlags.None), + new(0xF2800740, 0xFF800F50, sizeSizeVdConstraints, InstName.Vqdmlsl, T.VqdmlslA2, IsaVersion.v80, InstFlags.None), + new(0xF2000B00, 0xFF800F10, qvdQvnQvmSizeSizeConstraints, InstName.Vqdmulh, T.VqdmulhA1, IsaVersion.v80, InstFlags.None), + new(0xF2800C40, 0xFE800F50, sizeSizeQvdQvnConstraints, InstName.Vqdmulh, T.VqdmulhA2, IsaVersion.v80, InstFlags.None), + new(0xF2800D00, 0xFF800F50, sizeSizeVdConstraints, InstName.Vqdmull, T.VqdmullA1, IsaVersion.v80, InstFlags.None), + new(0xF2800B40, 0xFF800F50, sizeSizeVdConstraints, InstName.Vqdmull, T.VqdmullA2, IsaVersion.v80, InstFlags.None), + new(0xF3B20200, 0xFFB30F10, opSizeVmConstraints, InstName.Vqmovn, T.VqmovnA1, IsaVersion.v80, InstFlags.None), + new(0xF3B00780, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vqneg, T.VqnegA1, IsaVersion.v80, InstFlags.None), + new(0xF3000B10, 0xFF800F10, qvdQvnQvmSizeSizeConstraints, InstName.Vqrdmlah, T.VqrdmlahA1, IsaVersion.v81, IsaFeature.FeatRdm, InstFlags.None), + new(0xF2800E40, 0xFE800F50, sizeSizeQvdQvnConstraints, InstName.Vqrdmlah, T.VqrdmlahA2, IsaVersion.v81, IsaFeature.FeatRdm, InstFlags.None), + new(0xF3000C10, 0xFF800F10, qvdQvnQvmSizeSizeConstraints, InstName.Vqrdmlsh, T.VqrdmlshA1, IsaVersion.v81, IsaFeature.FeatRdm, InstFlags.None), + new(0xF2800F40, 0xFE800F50, sizeSizeQvdQvnConstraints, InstName.Vqrdmlsh, T.VqrdmlshA2, IsaVersion.v81, IsaFeature.FeatRdm, InstFlags.None), + new(0xF3000B00, 0xFF800F10, qvdQvnQvmSizeSizeConstraints, InstName.Vqrdmulh, T.VqrdmulhA1, IsaVersion.v80, InstFlags.None), + new(0xF2800D40, 0xFE800F50, sizeSizeQvdQvnConstraints, InstName.Vqrdmulh, T.VqrdmulhA2, IsaVersion.v80, InstFlags.None), + new(0xF2000510, 0xFE800F10, qvdQvmQvnConstraints, InstName.Vqrshl, T.VqrshlA1, IsaVersion.v80, InstFlags.None), + new(0xF2800850, 0xFE800ED0, imm6UopVmConstraints, InstName.Vqrshrn, T.VqrshrnA1, IsaVersion.v80, InstFlags.None), + new(0xF2800610, 0xFE800E10, imm6lUopQvdQvmConstraints, InstName.VqshlI, T.VqshlIA1, IsaVersion.v80, InstFlags.None), + new(0xF2000410, 0xFE800F10, qvdQvmQvnConstraints, InstName.VqshlR, T.VqshlRA1, IsaVersion.v80, InstFlags.None), + new(0xF2800810, 0xFE800ED0, imm6UopVmConstraints, InstName.Vqshrn, T.VqshrnA1, IsaVersion.v80, InstFlags.None), + new(0xF2000210, 0xFE800F10, qvdQvnQvmConstraints, InstName.Vqsub, T.VqsubA1, IsaVersion.v80, InstFlags.None), + new(0xF3800400, 0xFF800F50, sizeVnVmConstraints, InstName.Vraddhn, T.VraddhnA1, IsaVersion.v80, InstFlags.None), + new(0xF3B30400, 0xFFB30E90, qvdQvmSizeSizeConstraints, InstName.Vrecpe, T.VrecpeA1, IsaVersion.v80, InstFlags.None), + new(0xF2000F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vrecps, T.VrecpsA1, IsaVersion.v80, InstFlags.None), + new(0xF2100F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vrecps, T.VrecpsA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF3B00100, 0xFFB30F90, sizeSizeSizeQvdQvmConstraints, InstName.Vrev16, T.Vrev16A1, IsaVersion.v80, InstFlags.None), + new(0xF3B00080, 0xFFB30F90, sizeSizeQvdQvmConstraints, InstName.Vrev32, T.Vrev32A1, IsaVersion.v80, InstFlags.None), + new(0xF3B00000, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vrev64, T.Vrev64A1, IsaVersion.v80, InstFlags.None), + new(0xF2000100, 0xFE800F10, qvdQvnQvmSizeConstraints, InstName.Vrhadd, T.VrhaddA1, IsaVersion.v80, InstFlags.None), + new(0xF3BA0500, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintaAsimd, T.VrintaAsimdA1, IsaVersion.v80, InstFlags.None), + new(0xF3B60500, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintaAsimd, T.VrintaAsimdA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEB80A40, 0xFFBF0ED0, sizeConstraints, InstName.VrintaVfp, T.VrintaVfpA1, IsaVersion.v80, InstFlags.None), + new(0xFEB80940, 0xFFBF0FD0, sizeConstraints, InstName.VrintaVfp, T.VrintaVfpA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF3BA0680, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintmAsimd, T.VrintmAsimdA1, IsaVersion.v80, InstFlags.None), + new(0xF3B60680, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintmAsimd, T.VrintmAsimdA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEBB0A40, 0xFFBF0ED0, sizeConstraints, InstName.VrintmVfp, T.VrintmVfpA1, IsaVersion.v80, InstFlags.None), + new(0xFEBB0940, 0xFFBF0FD0, sizeConstraints, InstName.VrintmVfp, T.VrintmVfpA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF3BA0400, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintnAsimd, T.VrintnAsimdA1, IsaVersion.v80, InstFlags.None), + new(0xF3B60400, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintnAsimd, T.VrintnAsimdA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEB90A40, 0xFFBF0ED0, sizeConstraints, InstName.VrintnVfp, T.VrintnVfpA1, IsaVersion.v80, InstFlags.None), + new(0xFEB90940, 0xFFBF0FD0, sizeConstraints, InstName.VrintnVfp, T.VrintnVfpA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF3BA0780, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintpAsimd, T.VrintpAsimdA1, IsaVersion.v80, InstFlags.None), + new(0xF3B60780, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintpAsimd, T.VrintpAsimdA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEBA0A40, 0xFFBF0ED0, sizeConstraints, InstName.VrintpVfp, T.VrintpVfpA1, IsaVersion.v80, InstFlags.None), + new(0xFEBA0940, 0xFFBF0FD0, sizeConstraints, InstName.VrintpVfp, T.VrintpVfpA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0EB60A40, 0x0FBF0ED0, condConstraints, InstName.VrintrVfp, T.VrintrVfpA1, IsaVersion.v80, InstFlags.Cond), + new(0x0EB60940, 0x0FBF0FD0, condConstraints, InstName.VrintrVfp, T.VrintrVfpA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF3BA0480, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintxAsimd, T.VrintxAsimdA1, IsaVersion.v80, InstFlags.None), + new(0xF3B60480, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintxAsimd, T.VrintxAsimdA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0EB70A40, 0x0FBF0ED0, condConstraints, InstName.VrintxVfp, T.VrintxVfpA1, IsaVersion.v80, InstFlags.Cond), + new(0x0EB70940, 0x0FBF0FD0, condConstraints, InstName.VrintxVfp, T.VrintxVfpA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF3BA0580, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintzAsimd, T.VrintzAsimdA1, IsaVersion.v80, InstFlags.None), + new(0xF3B60580, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintzAsimd, T.VrintzAsimdA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0EB60AC0, 0x0FBF0ED0, condConstraints, InstName.VrintzVfp, T.VrintzVfpA1, IsaVersion.v80, InstFlags.Cond), + new(0x0EB609C0, 0x0FBF0FD0, condConstraints, InstName.VrintzVfp, T.VrintzVfpA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF2000500, 0xFE800F10, qvdQvmQvnConstraints, InstName.Vrshl, T.VrshlA1, IsaVersion.v80, InstFlags.None), + new(0xF2800210, 0xFE800F10, imm6lQvdQvmConstraints, InstName.Vrshr, T.VrshrA1, IsaVersion.v80, InstFlags.None), + new(0xF2800850, 0xFF800FD0, imm6VmConstraints, InstName.Vrshrn, T.VrshrnA1, IsaVersion.v80, InstFlags.None), + new(0xF3B30480, 0xFFB30E90, qvdQvmSizeSizeConstraints, InstName.Vrsqrte, T.VrsqrteA1, IsaVersion.v80, InstFlags.None), + new(0xF2200F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vrsqrts, T.VrsqrtsA1, IsaVersion.v80, InstFlags.None), + new(0xF2300F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vrsqrts, T.VrsqrtsA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2800310, 0xFE800F10, imm6lQvdQvmConstraints, InstName.Vrsra, T.VrsraA1, IsaVersion.v80, InstFlags.None), + new(0xF3800600, 0xFF800F50, sizeVnVmConstraints, InstName.Vrsubhn, T.VrsubhnA1, IsaVersion.v80, InstFlags.None), + new(0xFC200D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vsdot, T.VsdotA1, IsaVersion.v82, IsaFeature.FeatDotprod, InstFlags.None), + new(0xFE200D00, 0xFFB00F10, qvdQvnConstraints, InstName.VsdotS, T.VsdotSA1, IsaVersion.v82, IsaFeature.FeatDotprod, InstFlags.None), + new(0xFE000A00, 0xFF800E50, sizeConstraints, InstName.Vsel, T.VselA1, IsaVersion.v80, InstFlags.None), + new(0xFE000900, 0xFF800F50, sizeConstraints, InstName.Vsel, T.VselA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xF2800A10, 0xFE800FD0, imm6VdImm6Imm6Imm6Constraints, InstName.Vshll, T.VshllA1, IsaVersion.v80, InstFlags.None), + new(0xF3B20300, 0xFFB30FD0, sizeVdConstraints2, InstName.Vshll, T.VshllA2, IsaVersion.v80, InstFlags.None), + new(0xF2800510, 0xFF800F10, imm6lQvdQvmConstraints, InstName.VshlI, T.VshlIA1, IsaVersion.v80, InstFlags.None), + new(0xF2000400, 0xFE800F10, qvdQvmQvnConstraints, InstName.VshlR, T.VshlRA1, IsaVersion.v80, InstFlags.None), + new(0xF2800010, 0xFE800F10, imm6lQvdQvmConstraints, InstName.Vshr, T.VshrA1, IsaVersion.v80, InstFlags.None), + new(0xF2800810, 0xFF800FD0, imm6VmConstraints, InstName.Vshrn, T.VshrnA1, IsaVersion.v80, InstFlags.None), + new(0xF3800510, 0xFF800F10, imm6lQvdQvmConstraints, InstName.Vsli, T.VsliA1, IsaVersion.v80, InstFlags.None), + new(0xFC200C40, 0xFFB00F50, vdVnVmConstraints, InstName.Vsmmla, T.VsmmlaA1, IsaVersion.v82, IsaFeature.FeatAa32i8mm, InstFlags.None), + new(0x0EB10AC0, 0x0FBF0ED0, condConstraints, InstName.Vsqrt, T.VsqrtA1, IsaVersion.v80, InstFlags.Cond), + new(0x0EB109C0, 0x0FBF0FD0, condConstraints, InstName.Vsqrt, T.VsqrtA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF2800110, 0xFE800F10, imm6lQvdQvmConstraints, InstName.Vsra, T.VsraA1, IsaVersion.v80, InstFlags.None), + new(0xF3800410, 0xFF800F10, imm6lQvdQvmConstraints, InstName.Vsri, T.VsriA1, IsaVersion.v80, InstFlags.None), + new(0xF4800000, 0xFFB00F10, sizeConstraints2, InstName.Vst11, T.Vst11A1, IsaVersion.v80, InstFlags.None), + new(0xF4800400, 0xFFB00F20, sizeConstraints2, InstName.Vst11, T.Vst11A2, IsaVersion.v80, InstFlags.None), + new(0xF4800800, 0xFFB00F40, sizeIndexAlignIndexAlignConstraints, InstName.Vst11, T.Vst11A3, IsaVersion.v80, InstFlags.None), + new(0xF4000700, 0xFFB00F00, alignConstraints, InstName.Vst1M, T.Vst1MA1, IsaVersion.v80, InstFlags.None), + new(0xF4000A00, 0xFFB00F00, alignConstraints2, InstName.Vst1M, T.Vst1MA2, IsaVersion.v80, InstFlags.None), + new(0xF4000600, 0xFFB00F00, alignConstraints, InstName.Vst1M, T.Vst1MA3, IsaVersion.v80, InstFlags.None), + new(0xF4000200, 0xFFB00F00, InstName.Vst1M, T.Vst1MA4, IsaVersion.v80, InstFlags.None), + new(0xF4800100, 0xFFB00F00, sizeConstraints2, InstName.Vst21, T.Vst21A1, IsaVersion.v80, InstFlags.None), + new(0xF4800500, 0xFFB00F00, sizeConstraints2, InstName.Vst21, T.Vst21A2, IsaVersion.v80, InstFlags.None), + new(0xF4800900, 0xFFB00F20, sizeConstraints2, InstName.Vst21, T.Vst21A3, IsaVersion.v80, InstFlags.None), + new(0xF4000800, 0xFFB00E00, alignSizeConstraints, InstName.Vst2M, T.Vst2MA1, IsaVersion.v80, InstFlags.None), + new(0xF4000300, 0xFFB00F00, sizeConstraints3, InstName.Vst2M, T.Vst2MA2, IsaVersion.v80, InstFlags.None), + new(0xF4800200, 0xFFB00F10, sizeConstraints2, InstName.Vst31, T.Vst31A1, IsaVersion.v80, InstFlags.None), + new(0xF4800600, 0xFFB00F10, sizeConstraints2, InstName.Vst31, T.Vst31A2, IsaVersion.v80, InstFlags.None), + new(0xF4800A00, 0xFFB00F30, sizeConstraints2, InstName.Vst31, T.Vst31A3, IsaVersion.v80, InstFlags.None), + new(0xF4000400, 0xFFB00E00, sizeAlignConstraints, InstName.Vst3M, T.Vst3MA1, IsaVersion.v80, InstFlags.None), + new(0xF4800300, 0xFFB00F00, sizeConstraints2, InstName.Vst41, T.Vst41A1, IsaVersion.v80, InstFlags.None), + new(0xF4800700, 0xFFB00F00, sizeConstraints2, InstName.Vst41, T.Vst41A2, IsaVersion.v80, InstFlags.None), + new(0xF4800B00, 0xFFB00F00, sizeIndexAlignConstraints, InstName.Vst41, T.Vst41A3, IsaVersion.v80, InstFlags.None), + new(0xF4000000, 0xFFB00E00, sizeConstraints3, InstName.Vst4M, T.Vst4MA1, IsaVersion.v80, InstFlags.None), + new(0x0C000B00, 0x0E100F01, condPuwPwPuwPuwConstraints, InstName.Vstm, T.VstmA1, IsaVersion.v80, InstFlags.CondWBack), + new(0x0C000A00, 0x0E100F00, condPuwPwPuwPuwConstraints, InstName.Vstm, T.VstmA2, IsaVersion.v80, InstFlags.CondWBack), + new(0x0D000A00, 0x0F300E00, condConstraints, InstName.Vstr, T.VstrA1, IsaVersion.v80, InstFlags.Cond), + new(0x0D000900, 0x0F300F00, condConstraints, InstName.Vstr, T.VstrA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF2800600, 0xFF800F50, sizeVnVmConstraints, InstName.Vsubhn, T.VsubhnA1, IsaVersion.v80, InstFlags.None), + new(0xF2800200, 0xFE800F50, sizeVdOpvnConstraints, InstName.Vsubl, T.VsublA1, IsaVersion.v80, InstFlags.None), + new(0xF2800300, 0xFE800F50, sizeVdOpvnConstraints, InstName.Vsubw, T.VsubwA1, IsaVersion.v80, InstFlags.None), + new(0xF2200D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VsubF, T.VsubFA1, IsaVersion.v80, InstFlags.None), + new(0xF2300D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VsubF, T.VsubFA1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0x0E300A40, 0x0FB00E50, condConstraints, InstName.VsubF, T.VsubFA2, IsaVersion.v80, InstFlags.Cond), + new(0x0E300940, 0x0FB00F50, condConstraints, InstName.VsubF, T.VsubFA2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.Cond), + new(0xF3000800, 0xFF800F10, qvdQvnQvmConstraints, InstName.VsubI, T.VsubIA1, IsaVersion.v80, InstFlags.None), + new(0xFE800D10, 0xFFB00F10, qvdQvnConstraints, InstName.VsudotS, T.VsudotSA1, IsaVersion.v82, IsaFeature.FeatAa32i8mm, InstFlags.None), + new(0xF3B20000, 0xFFBF0F90, qvdQvmConstraints, InstName.Vswp, T.VswpA1, IsaVersion.v80, InstFlags.None), + new(0xF3B00800, 0xFFB00C10, InstName.Vtbl, T.VtblA1, IsaVersion.v80, InstFlags.None), + new(0xF3B20080, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vtrn, T.VtrnA1, IsaVersion.v80, InstFlags.None), + new(0xF2000810, 0xFF800F10, qvdQvnQvmSizeConstraints, InstName.Vtst, T.VtstA1, IsaVersion.v80, InstFlags.None), + new(0xFC200D10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vudot, T.VudotA1, IsaVersion.v82, IsaFeature.FeatDotprod, InstFlags.None), + new(0xFE200D10, 0xFFB00F10, qvdQvnConstraints, InstName.VudotS, T.VudotSA1, IsaVersion.v82, IsaFeature.FeatDotprod, InstFlags.None), + new(0xFC200C50, 0xFFB00F50, vdVnVmConstraints, InstName.Vummla, T.VummlaA1, IsaVersion.v82, IsaFeature.FeatAa32i8mm, InstFlags.None), + new(0xFCA00D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vusdot, T.VusdotA1, IsaVersion.v82, IsaFeature.FeatAa32i8mm, InstFlags.None), + new(0xFE800D00, 0xFFB00F10, qvdQvnConstraints, InstName.VusdotS, T.VusdotSA1, IsaVersion.v82, IsaFeature.FeatAa32i8mm, InstFlags.None), + new(0xFCA00C40, 0xFFB00F50, vdVnVmConstraints, InstName.Vusmmla, T.VusmmlaA1, IsaVersion.v82, IsaFeature.FeatAa32i8mm, InstFlags.None), + new(0xF3B20100, 0xFFB30F90, sizeQsizeQvdQvmConstraints, InstName.Vuzp, T.VuzpA1, IsaVersion.v80, InstFlags.None), + new(0xF3B20180, 0xFFB30F90, sizeQsizeQvdQvmConstraints, InstName.Vzip, T.VzipA1, IsaVersion.v80, InstFlags.None), + new(0x0320F002, 0x0FFFFFFF, condConstraints, InstName.Wfe, T.WfeA1, IsaVersion.v80, InstFlags.Cond), + new(0x0320F003, 0x0FFFFFFF, condConstraints, InstName.Wfi, T.WfiA1, IsaVersion.v80, InstFlags.Cond), + new(0x0320F001, 0x0FFFFFFF, condConstraints, InstName.Yield, T.YieldA1, IsaVersion.v80, InstFlags.Cond), + }; + + _table = new(insts); + } + + public static InstMeta GetMeta(uint encoding, IsaVersion version, IsaFeature features) + { + if (_table.TryFind(encoding, version, features, out InstInfoForTable info)) + { + return info.Meta; + } + + return new(InstName.Udf, T.UdfA1, IsaVersion.v80, IsaFeature.None, InstFlags.None); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/InstTableT16.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/InstTableT16.cs new file mode 100644 index 00000000..7ff5f6c9 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/InstTableT16.cs @@ -0,0 +1,146 @@ +using Ryujinx.Cpu.LightningJit.Table; +using System.Collections.Generic; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + static class InstTableT16 where T : IInstEmit + { + private static readonly InstTableLevel _table; + + static InstTableT16() + { + InstEncoding[] rmRdndnConstraints = new InstEncoding[] + { + new(0x00680000, 0x00780000), + new(0x00850000, 0x00870000), + }; + + InstEncoding[] rmConstraints = new InstEncoding[] + { + new(0x00680000, 0x00780000), + }; + + InstEncoding[] condCondConstraints = new InstEncoding[] + { + new(0x0E000000, 0x0F000000), + new(0x0F000000, 0x0F000000), + }; + + InstEncoding[] maskConstraints = new InstEncoding[] + { + new(0x00000000, 0x000F0000), + }; + + InstEncoding[] opConstraints = new InstEncoding[] + { + new(0x18000000, 0x18000000), + }; + + InstEncoding[] opOpOpOpConstraints = new InstEncoding[] + { + new(0x00000000, 0x03C00000), + new(0x00400000, 0x03C00000), + new(0x01400000, 0x03C00000), + new(0x01800000, 0x03C00000), + }; + + List insts = new() + { + new(0x41400000, 0xFFC00000, InstName.AdcR, T.AdcRT1, IsaVersion.v80, InstFlags.Rdn), + new(0x1C000000, 0xFE000000, InstName.AddI, T.AddIT1, IsaVersion.v80, InstFlags.Rd), + new(0x30000000, 0xF8000000, InstName.AddI, T.AddIT2, IsaVersion.v80, InstFlags.Rdn), + new(0x18000000, 0xFE000000, InstName.AddR, T.AddRT1, IsaVersion.v80, InstFlags.Rd), + new(0x44000000, 0xFF000000, rmRdndnConstraints, InstName.AddR, T.AddRT2, IsaVersion.v80, InstFlags.RdnDn), + new(0xA8000000, 0xF8000000, InstName.AddSpI, T.AddSpIT1, IsaVersion.v80, InstFlags.RdRd16), + new(0xB0000000, 0xFF800000, InstName.AddSpI, T.AddSpIT2, IsaVersion.v80, InstFlags.None), + new(0x44680000, 0xFF780000, InstName.AddSpR, T.AddSpRT1, IsaVersion.v80, InstFlags.None), + new(0x44850000, 0xFF870000, rmConstraints, InstName.AddSpR, T.AddSpRT2, IsaVersion.v80, InstFlags.None), + new(0xA0000000, 0xF8000000, InstName.Adr, T.AdrT1, IsaVersion.v80, InstFlags.RdRd16), + new(0x40000000, 0xFFC00000, InstName.AndR, T.AndRT1, IsaVersion.v80, InstFlags.Rdn), + new(0xD0000000, 0xF0000000, condCondConstraints, InstName.B, T.BT1, IsaVersion.v80, InstFlags.Cond), + new(0xE0000000, 0xF8000000, InstName.B, T.BT2, IsaVersion.v80, InstFlags.None), + new(0x43800000, 0xFFC00000, InstName.BicR, T.BicRT1, IsaVersion.v80, InstFlags.Rdn), + new(0xBE000000, 0xFF000000, InstName.Bkpt, T.BkptT1, IsaVersion.v80, InstFlags.None), + new(0x47800000, 0xFF870000, InstName.BlxR, T.BlxRT1, IsaVersion.v80, InstFlags.None), + new(0x47000000, 0xFF870000, InstName.Bx, T.BxT1, IsaVersion.v80, InstFlags.None), + new(0xB1000000, 0xF5000000, InstName.Cbnz, T.CbnzT1, IsaVersion.v80, InstFlags.None), + new(0x42C00000, 0xFFC00000, InstName.CmnR, T.CmnRT1, IsaVersion.v80, InstFlags.None), + new(0x28000000, 0xF8000000, InstName.CmpI, T.CmpIT1, IsaVersion.v80, InstFlags.None), + new(0x42800000, 0xFFC00000, InstName.CmpR, T.CmpRT1, IsaVersion.v80, InstFlags.None), + new(0x45000000, 0xFF000000, InstName.CmpR, T.CmpRT2, IsaVersion.v80, InstFlags.None), + new(0xB6600000, 0xFFE80000, InstName.Cps, T.CpsT1, IsaVersion.v80, InstFlags.None), + new(0x40400000, 0xFFC00000, InstName.EorR, T.EorRT1, IsaVersion.v80, InstFlags.Rdn), + new(0xBA800000, 0xFFC00000, InstName.Hlt, T.HltT1, IsaVersion.v80, InstFlags.None), + new(0xBF000000, 0xFF000000, maskConstraints, InstName.It, T.ItT1, IsaVersion.v80, InstFlags.None), + new(0xC8000000, 0xF8000000, InstName.Ldm, T.LdmT1, IsaVersion.v80, InstFlags.Rlist), + new(0x78000000, 0xF8000000, InstName.LdrbI, T.LdrbIT1, IsaVersion.v80, InstFlags.Rt), + new(0x5C000000, 0xFE000000, InstName.LdrbR, T.LdrbRT1, IsaVersion.v80, InstFlags.Rt), + new(0x88000000, 0xF8000000, InstName.LdrhI, T.LdrhIT1, IsaVersion.v80, InstFlags.Rt), + new(0x5A000000, 0xFE000000, InstName.LdrhR, T.LdrhRT1, IsaVersion.v80, InstFlags.Rt), + new(0x56000000, 0xFE000000, InstName.LdrsbR, T.LdrsbRT1, IsaVersion.v80, InstFlags.Rt), + new(0x5E000000, 0xFE000000, InstName.LdrshR, T.LdrshRT1, IsaVersion.v80, InstFlags.Rt), + new(0x68000000, 0xF8000000, InstName.LdrI, T.LdrIT1, IsaVersion.v80, InstFlags.Rt), + new(0x98000000, 0xF8000000, InstName.LdrI, T.LdrIT2, IsaVersion.v80, InstFlags.RtRd16), + new(0x48000000, 0xF8000000, InstName.LdrL, T.LdrLT1, IsaVersion.v80, InstFlags.RtRd16), + new(0x58000000, 0xFE000000, InstName.LdrR, T.LdrRT1, IsaVersion.v80, InstFlags.Rt), + new(0x20000000, 0xF8000000, InstName.MovI, T.MovIT1, IsaVersion.v80, InstFlags.RdRd16), + new(0x46000000, 0xFF000000, InstName.MovR, T.MovRT1, IsaVersion.v80, InstFlags.Rd), + new(0x00000000, 0xE0000000, opConstraints, InstName.MovR, T.MovRT2, IsaVersion.v80, InstFlags.Rd), + new(0x40000000, 0xFE000000, opOpOpOpConstraints, InstName.MovRr, T.MovRrT1, IsaVersion.v80, InstFlags.None), + new(0x43400000, 0xFFC00000, InstName.Mul, T.MulT1, IsaVersion.v80, InstFlags.None), + new(0x43C00000, 0xFFC00000, InstName.MvnR, T.MvnRT1, IsaVersion.v80, InstFlags.Rd), + new(0xBF000000, 0xFFFF0000, InstName.Nop, T.NopT1, IsaVersion.v80, InstFlags.None), + new(0x43000000, 0xFFC00000, InstName.OrrR, T.OrrRT1, IsaVersion.v80, InstFlags.Rdn), + new(0xBC000000, 0xFE000000, InstName.Pop, T.PopT1, IsaVersion.v80, InstFlags.Rlist), + new(0xB4000000, 0xFE000000, InstName.Push, T.PushT1, IsaVersion.v80, InstFlags.RlistRead), + new(0xBA000000, 0xFFC00000, InstName.Rev, T.RevT1, IsaVersion.v80, InstFlags.Rd), + new(0xBA400000, 0xFFC00000, InstName.Rev16, T.Rev16T1, IsaVersion.v80, InstFlags.Rd), + new(0xBAC00000, 0xFFC00000, InstName.Revsh, T.RevshT1, IsaVersion.v80, InstFlags.Rd), + new(0x42400000, 0xFFC00000, InstName.RsbI, T.RsbIT1, IsaVersion.v80, InstFlags.Rd), + new(0x41800000, 0xFFC00000, InstName.SbcR, T.SbcRT1, IsaVersion.v80, InstFlags.Rdn), + new(0xB6500000, 0xFFF70000, InstName.Setend, T.SetendT1, IsaVersion.v80, InstFlags.None), + new(0xB6100000, 0xFFF70000, InstName.Setpan, T.SetpanT1, IsaVersion.v81, IsaFeature.FeatPan, InstFlags.None), + new(0xBF400000, 0xFFFF0000, InstName.Sev, T.SevT1, IsaVersion.v80, InstFlags.None), + new(0xBF500000, 0xFFFF0000, InstName.Sevl, T.SevlT1, IsaVersion.v80, InstFlags.None), + new(0xC0000000, 0xF8000000, InstName.Stm, T.StmT1, IsaVersion.v80, InstFlags.RlistRead), + new(0x70000000, 0xF8000000, InstName.StrbI, T.StrbIT1, IsaVersion.v80, InstFlags.RtRead), + new(0x54000000, 0xFE000000, InstName.StrbR, T.StrbRT1, IsaVersion.v80, InstFlags.RtRead), + new(0x80000000, 0xF8000000, InstName.StrhI, T.StrhIT1, IsaVersion.v80, InstFlags.RtRead), + new(0x52000000, 0xFE000000, InstName.StrhR, T.StrhRT1, IsaVersion.v80, InstFlags.RtRead), + new(0x60000000, 0xF8000000, InstName.StrI, T.StrIT1, IsaVersion.v80, InstFlags.RtRead), + new(0x90000000, 0xF8000000, InstName.StrI, T.StrIT2, IsaVersion.v80, InstFlags.RtReadRd16), + new(0x50000000, 0xFE000000, InstName.StrR, T.StrRT1, IsaVersion.v80, InstFlags.RtRead), + new(0x1E000000, 0xFE000000, InstName.SubI, T.SubIT1, IsaVersion.v80, InstFlags.Rd), + new(0x38000000, 0xF8000000, InstName.SubI, T.SubIT2, IsaVersion.v80, InstFlags.Rdn), + new(0x1A000000, 0xFE000000, InstName.SubR, T.SubRT1, IsaVersion.v80, InstFlags.Rd), + new(0xB0800000, 0xFF800000, InstName.SubSpI, T.SubSpIT1, IsaVersion.v80, InstFlags.None), + new(0xDF000000, 0xFF000000, InstName.Svc, T.SvcT1, IsaVersion.v80, InstFlags.None), + new(0xB2400000, 0xFFC00000, InstName.Sxtb, T.SxtbT1, IsaVersion.v80, InstFlags.Rd), + new(0xB2000000, 0xFFC00000, InstName.Sxth, T.SxthT1, IsaVersion.v80, InstFlags.Rd), + new(0x42000000, 0xFFC00000, InstName.TstR, T.TstRT1, IsaVersion.v80, InstFlags.None), + new(0xDE000000, 0xFF000000, InstName.Udf, T.UdfT1, IsaVersion.v80, InstFlags.None), + new(0xB2C00000, 0xFFC00000, InstName.Uxtb, T.UxtbT1, IsaVersion.v80, InstFlags.Rd), + new(0xB2800000, 0xFFC00000, InstName.Uxth, T.UxthT1, IsaVersion.v80, InstFlags.Rd), + new(0xBF200000, 0xFFFF0000, InstName.Wfe, T.WfeT1, IsaVersion.v80, InstFlags.None), + new(0xBF300000, 0xFFFF0000, InstName.Wfi, T.WfiT1, IsaVersion.v80, InstFlags.None), + new(0xBF100000, 0xFFFF0000, InstName.Yield, T.YieldT1, IsaVersion.v80, InstFlags.None), + }; + + _table = new(insts); + } + + public static bool TryGetMeta(uint encoding, IsaVersion version, IsaFeature features, out InstMeta meta) + { + if (_table.TryFind(encoding, version, features, out InstInfoForTable info)) + { + meta = info.Meta; + + return true; + } + + meta = new(InstName.Udf, T.UdfA1, IsaVersion.v80, IsaFeature.None, InstFlags.None); + + return false; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/InstTableT32.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/InstTableT32.cs new file mode 100644 index 00000000..4a11effd --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/InstTableT32.cs @@ -0,0 +1,1212 @@ +using Ryujinx.Cpu.LightningJit.Table; +using System.Collections.Generic; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + static class InstTableT32 where T : IInstEmit + { + private static readonly InstTableLevel _table; + + static InstTableT32() + { + InstEncoding[] rnRdsConstraints = new InstEncoding[] + { + new(0x000D0000, 0x000F0000), + new(0x00100F00, 0x00100F00), + }; + + InstEncoding[] rnRnConstraints = new InstEncoding[] + { + new(0x000D0000, 0x000F0000), + new(0x000F0000, 0x000F0000), + }; + + InstEncoding[] rdsConstraints = new InstEncoding[] + { + new(0x00100F00, 0x00100F00), + }; + + InstEncoding[] vdVmConstraints = new InstEncoding[] + { + new(0x00001000, 0x00001000), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] condCondCondConstraints = new InstEncoding[] + { + new(0x03800000, 0x03C00000), + new(0x03C00000, 0x03C00000), + new(0x03800000, 0x03800000), + }; + + InstEncoding[] rnConstraints = new InstEncoding[] + { + new(0x000F0000, 0x000F0000), + }; + + InstEncoding[] hConstraints = new InstEncoding[] + { + new(0x00000001, 0x00000001), + }; + + InstEncoding[] imodmConstraints = new InstEncoding[] + { + new(0x00000000, 0x00000700), + }; + + InstEncoding[] optionConstraints = new InstEncoding[] + { + new(0x00000000, 0x0000000F), + }; + + InstEncoding[] puwPwPuwPuwConstraints = new InstEncoding[] + { + new(0x00000000, 0x01A00000), + new(0x01000000, 0x01200000), + new(0x00200000, 0x01A00000), + new(0x01A00000, 0x01A00000), + }; + + InstEncoding[] rnPuwConstraints = new InstEncoding[] + { + new(0x000F0000, 0x000F0000), + new(0x00000000, 0x01A00000), + }; + + InstEncoding[] puwConstraints = new InstEncoding[] + { + new(0x00000000, 0x01A00000), + }; + + InstEncoding[] rnRtConstraints = new InstEncoding[] + { + new(0x000F0000, 0x000F0000), + new(0x0000F000, 0x0000F000), + }; + + InstEncoding[] rnRtpuwPuwPwConstraints = new InstEncoding[] + { + new(0x000F0000, 0x000F0000), + new(0x0000F400, 0x0000F700), + new(0x00000600, 0x00000700), + new(0x00000000, 0x00000500), + }; + + InstEncoding[] rtConstraints = new InstEncoding[] + { + new(0x0000F000, 0x0000F000), + }; + + InstEncoding[] rnPwConstraints = new InstEncoding[] + { + new(0x000F0000, 0x000F0000), + new(0x00000000, 0x01200000), + }; + + InstEncoding[] pwConstraints = new InstEncoding[] + { + new(0x00000000, 0x01200000), + }; + + InstEncoding[] rnPuwPwConstraints = new InstEncoding[] + { + new(0x000F0000, 0x000F0000), + new(0x00000600, 0x00000700), + new(0x00000000, 0x00000500), + }; + + InstEncoding[] raConstraints = new InstEncoding[] + { + new(0x0000F000, 0x0000F000), + }; + + InstEncoding[] sTConstraints = new InstEncoding[] + { + new(0x00100000, 0x00100000), + new(0x00000010, 0x00000010), + }; + + InstEncoding[] vdVnVmConstraints = new InstEncoding[] + { + new(0x00001000, 0x00001000), + new(0x00010000, 0x00010000), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] shimm2imm3Constraints = new InstEncoding[] + { + new(0x00200000, 0x002070C0), + }; + + InstEncoding[] rnimm8Constraints = new InstEncoding[] + { + new(0x000E0000, 0x000F00FF), + }; + + InstEncoding[] sizeQvdQvnQvmConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] sizeVdConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x00001000, 0x00001000), + }; + + InstEncoding[] qvdQvnQvmConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] sizeQvdQvmConstraints = new InstEncoding[] + { + new(0x000C0000, 0x000C0000), + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] sizeVnVmConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x00010000, 0x00010000), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] sizeVdOpvnConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x00001000, 0x00001000), + new(0x00010100, 0x00010100), + }; + + InstEncoding[] cmodeCmodeQvdConstraints = new InstEncoding[] + { + new(0x00000000, 0x00000100), + new(0x00000C00, 0x00000C00), + new(0x00001040, 0x00001040), + }; + + InstEncoding[] qvdQvnQvmOpConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + new(0x00000041, 0x00000041), + new(0x00000000, 0x00300000), + }; + + InstEncoding[] qvdQvnQvmSizeConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + new(0x00000041, 0x00000041), + new(0x00300000, 0x00300000), + }; + + InstEncoding[] qvdQvnConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + }; + + InstEncoding[] qvdQvmConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] sizeConstraints = new InstEncoding[] + { + new(0x00000000, 0x00000300), + }; + + InstEncoding[] vmConstraints = new InstEncoding[] + { + new(0x00000001, 0x00000001), + }; + + InstEncoding[] opvdOpvmConstraints = new InstEncoding[] + { + new(0x00001100, 0x00001100), + new(0x00000001, 0x00000101), + }; + + InstEncoding[] imm6Opimm6Imm6QvdQvmConstraints = new InstEncoding[] + { + new(0x00000000, 0x00380000), + new(0x00200000, 0x00300200), + new(0x00000000, 0x00200000), + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] qvdEbConstraints = new InstEncoding[] + { + new(0x00210000, 0x00210000), + new(0x00400020, 0x00400020), + }; + + InstEncoding[] imm4QvdConstraints = new InstEncoding[] + { + new(0x00000000, 0x00070000), + new(0x00001040, 0x00001040), + }; + + InstEncoding[] qvdQvnQvmQimm4Constraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + new(0x00000041, 0x00000041), + new(0x00000800, 0x00000840), + }; + + InstEncoding[] qvdConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + }; + + InstEncoding[] vdVnConstraints = new InstEncoding[] + { + new(0x00001000, 0x00001000), + new(0x00010000, 0x00010000), + }; + + InstEncoding[] sizeConstraints2 = new InstEncoding[] + { + new(0x00000C00, 0x00000C00), + }; + + InstEncoding[] sizeIndexAlignIndexAlignConstraints = new InstEncoding[] + { + new(0x00000C00, 0x00000C00), + new(0x00000010, 0x00000030), + new(0x00000020, 0x00000030), + }; + + InstEncoding[] sizeSizeaConstraints = new InstEncoding[] + { + new(0x000000C0, 0x000000C0), + new(0x00000010, 0x000000D0), + }; + + InstEncoding[] alignConstraints = new InstEncoding[] + { + new(0x00000020, 0x00000020), + }; + + InstEncoding[] alignConstraints2 = new InstEncoding[] + { + new(0x00000030, 0x00000030), + }; + + InstEncoding[] sizeConstraints3 = new InstEncoding[] + { + new(0x000000C0, 0x000000C0), + }; + + InstEncoding[] alignSizeConstraints = new InstEncoding[] + { + new(0x00000030, 0x00000030), + new(0x000000C0, 0x000000C0), + }; + + InstEncoding[] sizeAConstraints = new InstEncoding[] + { + new(0x000000C0, 0x000000C0), + new(0x00000010, 0x00000010), + }; + + InstEncoding[] sizeAlignConstraints = new InstEncoding[] + { + new(0x000000C0, 0x000000C0), + new(0x00000020, 0x00000020), + }; + + InstEncoding[] sizeIndexAlignConstraints = new InstEncoding[] + { + new(0x00000C00, 0x00000C00), + new(0x00000030, 0x00000030), + }; + + InstEncoding[] sizeaConstraints = new InstEncoding[] + { + new(0x000000C0, 0x000000D0), + }; + + InstEncoding[] sizeSizeVdConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x00000000, 0x00300000), + new(0x00001000, 0x00001000), + }; + + InstEncoding[] sizeQvdQvnConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x10001000, 0x10001000), + new(0x10010000, 0x10010000), + }; + + InstEncoding[] imm3hImm3hImm3hImm3hImm3hVdConstraints = new InstEncoding[] + { + new(0x00000000, 0x00380000), + new(0x00180000, 0x00380000), + new(0x00280000, 0x00380000), + new(0x00300000, 0x00380000), + new(0x00380000, 0x00380000), + new(0x00001000, 0x00001000), + }; + + InstEncoding[] sizeVmConstraints = new InstEncoding[] + { + new(0x000C0000, 0x000C0000), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] opc1opc2Constraints = new InstEncoding[] + { + new(0x00000040, 0x00400060), + }; + + InstEncoding[] uopc1opc2Uopc1opc2Constraints = new InstEncoding[] + { + new(0x00800000, 0x00C00060), + new(0x00000040, 0x00400060), + }; + + InstEncoding[] sizeOpuOpsizeVdConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x10000200, 0x10000200), + new(0x00100200, 0x00300200), + new(0x00001000, 0x00001000), + }; + + InstEncoding[] sizeOpsizeOpsizeQvdQvnQvmConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x10100000, 0x10300000), + new(0x10200000, 0x10300000), + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] cmodeQvdConstraints = new InstEncoding[] + { + new(0x00000E00, 0x00000E00), + new(0x00001040, 0x00001040), + }; + + InstEncoding[] qConstraints = new InstEncoding[] + { + new(0x00000040, 0x00000040), + }; + + InstEncoding[] sizeQConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x00000040, 0x00000040), + }; + + InstEncoding[] sizeConstraints4 = new InstEncoding[] + { + new(0x00300000, 0x00300000), + }; + + InstEncoding[] qvdQvnQvmSizeSizeConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00010040, 0x00010040), + new(0x00000041, 0x00000041), + new(0x00000000, 0x00300000), + new(0x00300000, 0x00300000), + }; + + InstEncoding[] sizeSizeQvdQvnConstraints = new InstEncoding[] + { + new(0x00300000, 0x00300000), + new(0x00000000, 0x00300000), + new(0x10001000, 0x10001000), + new(0x10010000, 0x10010000), + }; + + InstEncoding[] opSizeVmConstraints = new InstEncoding[] + { + new(0x00000000, 0x000000C0), + new(0x000C0000, 0x000C0000), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] qvdQvmQvnConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + new(0x00010040, 0x00010040), + }; + + InstEncoding[] imm6UopVmConstraints = new InstEncoding[] + { + new(0x00000000, 0x00380000), + new(0x00000000, 0x10000100), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] imm6lUopQvdQvmConstraints = new InstEncoding[] + { + new(0x00000000, 0x00380080), + new(0x00000000, 0x10000100), + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] qvdQvmSizeSizeConstraints = new InstEncoding[] + { + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + new(0x00000000, 0x000C0000), + new(0x000C0000, 0x000C0000), + }; + + InstEncoding[] sizeSizeSizeQvdQvmConstraints = new InstEncoding[] + { + new(0x00040000, 0x000C0000), + new(0x00080000, 0x000C0000), + new(0x000C0000, 0x000C0000), + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] sizeSizeQvdQvmConstraints = new InstEncoding[] + { + new(0x00080000, 0x000C0000), + new(0x000C0000, 0x000C0000), + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] imm6lQvdQvmConstraints = new InstEncoding[] + { + new(0x00000000, 0x00380080), + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + InstEncoding[] imm6VmConstraints = new InstEncoding[] + { + new(0x00000000, 0x00380000), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] imm6VdImm6Imm6Imm6Constraints = new InstEncoding[] + { + new(0x00000000, 0x00380000), + new(0x00001000, 0x00001000), + new(0x00080000, 0x003F0000), + new(0x00100000, 0x003F0000), + new(0x00200000, 0x003F0000), + }; + + InstEncoding[] sizeVdConstraints2 = new InstEncoding[] + { + new(0x000C0000, 0x000C0000), + new(0x00001000, 0x00001000), + }; + + InstEncoding[] sizeQsizeQvdQvmConstraints = new InstEncoding[] + { + new(0x000C0000, 0x000C0000), + new(0x00080000, 0x000C0040), + new(0x00001040, 0x00001040), + new(0x00000041, 0x00000041), + }; + + List insts = new() + { + new(0xF1400000, 0xFBE08000, InstName.AdcI, T.AdcIT1, IsaVersion.v80, InstFlags.Rd), + new(0xEB400000, 0xFFE08000, InstName.AdcR, T.AdcRT2, IsaVersion.v80, InstFlags.Rd), + new(0xF1000000, 0xFBE08000, rnRdsConstraints, InstName.AddI, T.AddIT3, IsaVersion.v80, InstFlags.Rd), + new(0xF2000000, 0xFBF08000, rnRnConstraints, InstName.AddI, T.AddIT4, IsaVersion.v80, InstFlags.Rd), + new(0xEB000000, 0xFFE08000, rnRdsConstraints, InstName.AddR, T.AddRT3, IsaVersion.v80, InstFlags.Rd), + new(0xF10D0000, 0xFBEF8000, rdsConstraints, InstName.AddSpI, T.AddSpIT3, IsaVersion.v80, InstFlags.Rd), + new(0xF20D0000, 0xFBFF8000, InstName.AddSpI, T.AddSpIT4, IsaVersion.v80, InstFlags.Rd), + new(0xEB0D0000, 0xFFEF8000, rdsConstraints, InstName.AddSpR, T.AddSpRT3, IsaVersion.v80, InstFlags.Rd), + new(0xF2AF0000, 0xFBFF8000, InstName.Adr, T.AdrT2, IsaVersion.v80, InstFlags.Rd), + new(0xF20F0000, 0xFBFF8000, InstName.Adr, T.AdrT3, IsaVersion.v80, InstFlags.Rd), + new(0xFFB00340, 0xFFBF0FD0, vdVmConstraints, InstName.Aesd, T.AesdT1, IsaVersion.v80, IsaFeature.FeatAes, InstFlags.None), + new(0xFFB00300, 0xFFBF0FD0, vdVmConstraints, InstName.Aese, T.AeseT1, IsaVersion.v80, IsaFeature.FeatAes, InstFlags.None), + new(0xFFB003C0, 0xFFBF0FD0, vdVmConstraints, InstName.Aesimc, T.AesimcT1, IsaVersion.v80, IsaFeature.FeatAes, InstFlags.None), + new(0xFFB00380, 0xFFBF0FD0, vdVmConstraints, InstName.Aesmc, T.AesmcT1, IsaVersion.v80, IsaFeature.FeatAes, InstFlags.None), + new(0xF0000000, 0xFBE08000, rdsConstraints, InstName.AndI, T.AndIT1, IsaVersion.v80, InstFlags.Rd), + new(0xEA000000, 0xFFE08000, rdsConstraints, InstName.AndR, T.AndRT2, IsaVersion.v80, InstFlags.Rd), + new(0xF0008000, 0xF800D000, condCondCondConstraints, InstName.B, T.BT3, IsaVersion.v80, InstFlags.Cond), + new(0xF0009000, 0xF800D000, InstName.B, T.BT4, IsaVersion.v80, InstFlags.None), + new(0xF36F0000, 0xFFFF8020, InstName.Bfc, T.BfcT1, IsaVersion.v80, InstFlags.Rd), + new(0xF3600000, 0xFFF08020, rnConstraints, InstName.Bfi, T.BfiT1, IsaVersion.v80, InstFlags.Rd), + new(0xF0200000, 0xFBE08000, InstName.BicI, T.BicIT1, IsaVersion.v80, InstFlags.Rd), + new(0xEA200000, 0xFFE08000, InstName.BicR, T.BicRT2, IsaVersion.v80, InstFlags.Rd), + new(0xF000D000, 0xF800D000, InstName.BlI, T.BlIT1, IsaVersion.v80, InstFlags.None), + new(0xF000C000, 0xF800D000, hConstraints, InstName.BlI, T.BlIT2, IsaVersion.v80, InstFlags.None), + new(0xF3C08F00, 0xFFF0FFFF, InstName.Bxj, T.BxjT1, IsaVersion.v80, InstFlags.None), + new(0xF3AF8016, 0xFFFFFFFF, InstName.Clrbhb, T.ClrbhbT1, IsaVersion.v89, IsaFeature.FeatClrbhb, InstFlags.None), + new(0xF3BF8F2F, 0xFFFFFFFF, InstName.Clrex, T.ClrexT1, IsaVersion.v80, InstFlags.None), + new(0xFAB0F080, 0xFFF0F0F0, InstName.Clz, T.ClzT1, IsaVersion.v80, InstFlags.Rd), + new(0xF1100F00, 0xFBF08F00, InstName.CmnI, T.CmnIT1, IsaVersion.v80, InstFlags.None), + new(0xEB100F00, 0xFFF08F00, InstName.CmnR, T.CmnRT2, IsaVersion.v80, InstFlags.None), + new(0xF1B00F00, 0xFBF08F00, InstName.CmpI, T.CmpIT2, IsaVersion.v80, InstFlags.None), + new(0xEBB00F00, 0xFFF08F00, InstName.CmpR, T.CmpRT3, IsaVersion.v80, InstFlags.None), + new(0xF3AF8000, 0xFFFFF800, imodmConstraints, InstName.Cps, T.CpsT2, IsaVersion.v80, InstFlags.None), + new(0xFAC0F080, 0xFFF0F0C0, InstName.Crc32, T.Crc32T1, IsaVersion.v80, IsaFeature.FeatCrc32, InstFlags.Rd), + new(0xFAD0F080, 0xFFF0F0C0, InstName.Crc32c, T.Crc32cT1, IsaVersion.v80, IsaFeature.FeatCrc32, InstFlags.Rd), + new(0xF3AF8014, 0xFFFFFFFF, InstName.Csdb, T.CsdbT1, IsaVersion.v80, InstFlags.None), + new(0xF3AF80F0, 0xFFFFFFF0, InstName.Dbg, T.DbgT1, IsaVersion.v80, InstFlags.None), + new(0xF78F8001, 0xFFFFFFFF, InstName.Dcps1, T.Dcps1T1, IsaVersion.v80, InstFlags.None), + new(0xF78F8002, 0xFFFFFFFF, InstName.Dcps2, T.Dcps2T1, IsaVersion.v80, InstFlags.None), + new(0xF78F8003, 0xFFFFFFFF, InstName.Dcps3, T.Dcps3T1, IsaVersion.v80, InstFlags.None), + new(0xF3BF8F50, 0xFFFFFFF0, InstName.Dmb, T.DmbT1, IsaVersion.v80, InstFlags.None), + new(0xF3BF8F40, 0xFFFFFFF0, optionConstraints, InstName.Dsb, T.DsbT1, IsaVersion.v80, InstFlags.None), + new(0xF0800000, 0xFBE08000, rdsConstraints, InstName.EorI, T.EorIT1, IsaVersion.v80, InstFlags.Rd), + new(0xEA800000, 0xFFE08000, rdsConstraints, InstName.EorR, T.EorRT2, IsaVersion.v80, InstFlags.Rd), + new(0xF3DE8F00, 0xFFFFFFFF, InstName.Eret, T.EretT1, IsaVersion.v80, InstFlags.None), + new(0xF3AF8010, 0xFFFFFFFF, InstName.Esb, T.EsbT1, IsaVersion.v82, IsaFeature.FeatRas, InstFlags.None), + new(0xEC100B01, 0xFE100F01, puwPwPuwPuwConstraints, InstName.Fldmx, T.FldmxT1, IsaVersion.v80, InstFlags.WBack), + new(0xEC000B01, 0xFE100F01, puwPwPuwPuwConstraints, InstName.Fstmx, T.FstmxT1, IsaVersion.v80, InstFlags.WBack), + new(0xF7E08000, 0xFFF0F000, InstName.Hvc, T.HvcT1, IsaVersion.v80, InstFlags.None), + new(0xF3BF8F60, 0xFFFFFFF0, InstName.Isb, T.IsbT1, IsaVersion.v80, InstFlags.None), + new(0xE8D00FAF, 0xFFF00FFF, InstName.Lda, T.LdaT1, IsaVersion.v80, InstFlags.Rt), + new(0xE8D00F8F, 0xFFF00FFF, InstName.Ldab, T.LdabT1, IsaVersion.v80, InstFlags.Rt), + new(0xE8D00FEF, 0xFFF00FFF, InstName.Ldaex, T.LdaexT1, IsaVersion.v80, InstFlags.Rt), + new(0xE8D00FCF, 0xFFF00FFF, InstName.Ldaexb, T.LdaexbT1, IsaVersion.v80, InstFlags.Rt), + new(0xE8D000FF, 0xFFF000FF, InstName.Ldaexd, T.LdaexdT1, IsaVersion.v80, InstFlags.RtRt2), + new(0xE8D00FDF, 0xFFF00FFF, InstName.Ldaexh, T.LdaexhT1, IsaVersion.v80, InstFlags.Rt), + new(0xE8D00F9F, 0xFFF00FFF, InstName.Ldah, T.LdahT1, IsaVersion.v80, InstFlags.Rt), + new(0xEC105E00, 0xFE50FF00, rnPuwConstraints, InstName.LdcI, T.LdcIT1, IsaVersion.v80, InstFlags.WBack), + new(0xEC1F5E00, 0xFE5FFF00, puwConstraints, InstName.LdcL, T.LdcLT1, IsaVersion.v80, InstFlags.WBack), + new(0xE8900000, 0xFFD00000, InstName.Ldm, T.LdmT2, IsaVersion.v80, InstFlags.RlistWBack), + new(0xE9100000, 0xFFD00000, InstName.Ldmdb, T.LdmdbT1, IsaVersion.v80, InstFlags.RlistWBack), + new(0xF8100E00, 0xFFF00F00, rnConstraints, InstName.Ldrbt, T.LdrbtT1, IsaVersion.v80, InstFlags.Rt), + new(0xF8900000, 0xFFF00000, rnRtConstraints, InstName.LdrbI, T.LdrbIT2, IsaVersion.v80, InstFlags.Rt), + new(0xF8100800, 0xFFF00800, rnRtpuwPuwPwConstraints, InstName.LdrbI, T.LdrbIT3, IsaVersion.v80, InstFlags.RtWBack), + new(0xF81F0000, 0xFF7F0000, rtConstraints, InstName.LdrbL, T.LdrbLT1, IsaVersion.v80, InstFlags.Rt), + new(0xF8100000, 0xFFF00FC0, rnRtConstraints, InstName.LdrbR, T.LdrbRT2, IsaVersion.v80, InstFlags.Rt), + new(0xE8500000, 0xFE500000, rnPwConstraints, InstName.LdrdI, T.LdrdIT1, IsaVersion.v80, InstFlags.Rt2WBack), + new(0xE85F0000, 0xFE5F0000, pwConstraints, InstName.LdrdL, T.LdrdLT1, IsaVersion.v80, InstFlags.Rt2WBack), + new(0xE8500F00, 0xFFF00F00, InstName.Ldrex, T.LdrexT1, IsaVersion.v80, InstFlags.Rt), + new(0xE8D00F4F, 0xFFF00FFF, InstName.Ldrexb, T.LdrexbT1, IsaVersion.v80, InstFlags.Rt), + new(0xE8D0007F, 0xFFF000FF, InstName.Ldrexd, T.LdrexdT1, IsaVersion.v80, InstFlags.RtRt2), + new(0xE8D00F5F, 0xFFF00FFF, InstName.Ldrexh, T.LdrexhT1, IsaVersion.v80, InstFlags.Rt), + new(0xF8300E00, 0xFFF00F00, rnConstraints, InstName.Ldrht, T.LdrhtT1, IsaVersion.v80, InstFlags.Rt), + new(0xF8B00000, 0xFFF00000, rnRtConstraints, InstName.LdrhI, T.LdrhIT2, IsaVersion.v80, InstFlags.Rt), + new(0xF8300800, 0xFFF00800, rnRtpuwPuwPwConstraints, InstName.LdrhI, T.LdrhIT3, IsaVersion.v80, InstFlags.RtWBack), + new(0xF83F0000, 0xFF7F0000, rtConstraints, InstName.LdrhL, T.LdrhLT1, IsaVersion.v80, InstFlags.Rt), + new(0xF8300000, 0xFFF00FC0, rnRtConstraints, InstName.LdrhR, T.LdrhRT2, IsaVersion.v80, InstFlags.Rt), + new(0xF9100E00, 0xFFF00F00, rnConstraints, InstName.Ldrsbt, T.LdrsbtT1, IsaVersion.v80, InstFlags.Rt), + new(0xF9900000, 0xFFF00000, rnRtConstraints, InstName.LdrsbI, T.LdrsbIT1, IsaVersion.v80, InstFlags.Rt), + new(0xF9100800, 0xFFF00800, rnRtpuwPuwPwConstraints, InstName.LdrsbI, T.LdrsbIT2, IsaVersion.v80, InstFlags.RtWBack), + new(0xF91F0000, 0xFF7F0000, rtConstraints, InstName.LdrsbL, T.LdrsbLT1, IsaVersion.v80, InstFlags.Rt), + new(0xF9100000, 0xFFF00FC0, rnRtConstraints, InstName.LdrsbR, T.LdrsbRT2, IsaVersion.v80, InstFlags.Rt), + new(0xF9300E00, 0xFFF00F00, rnConstraints, InstName.Ldrsht, T.LdrshtT1, IsaVersion.v80, InstFlags.Rt), + new(0xF9B00000, 0xFFF00000, rnRtConstraints, InstName.LdrshI, T.LdrshIT1, IsaVersion.v80, InstFlags.Rt), + new(0xF9300800, 0xFFF00800, rnRtpuwPuwPwConstraints, InstName.LdrshI, T.LdrshIT2, IsaVersion.v80, InstFlags.RtWBack), + new(0xF93F0000, 0xFF7F0000, rtConstraints, InstName.LdrshL, T.LdrshLT1, IsaVersion.v80, InstFlags.Rt), + new(0xF9300000, 0xFFF00FC0, rnRtConstraints, InstName.LdrshR, T.LdrshRT2, IsaVersion.v80, InstFlags.Rt), + new(0xF8500E00, 0xFFF00F00, rnConstraints, InstName.Ldrt, T.LdrtT1, IsaVersion.v80, InstFlags.Rt), + new(0xF8D00000, 0xFFF00000, rnConstraints, InstName.LdrI, T.LdrIT3, IsaVersion.v80, InstFlags.Rt), + new(0xF8500800, 0xFFF00800, rnPuwPwConstraints, InstName.LdrI, T.LdrIT4, IsaVersion.v80, InstFlags.RtWBack), + new(0xF85F0000, 0xFF7F0000, InstName.LdrL, T.LdrLT2, IsaVersion.v80, InstFlags.Rt), + new(0xF8500000, 0xFFF00FC0, rnConstraints, InstName.LdrR, T.LdrRT2, IsaVersion.v80, InstFlags.Rt), + new(0xEE000E10, 0xFF100E10, InstName.Mcr, T.McrT1, IsaVersion.v80, InstFlags.Rt), + new(0xEC400E00, 0xFFF00E00, InstName.Mcrr, T.McrrT1, IsaVersion.v80, InstFlags.Rt), + new(0xFB000000, 0xFFF000F0, raConstraints, InstName.Mla, T.MlaT1, IsaVersion.v80, InstFlags.Rd), + new(0xFB000010, 0xFFF000F0, InstName.Mls, T.MlsT1, IsaVersion.v80, InstFlags.Rd), + new(0xF2C00000, 0xFBF08000, InstName.Movt, T.MovtT1, IsaVersion.v80, InstFlags.Rd), + new(0xF04F0000, 0xFBEF8000, InstName.MovI, T.MovIT2, IsaVersion.v80, InstFlags.Rd), + new(0xF2400000, 0xFBF08000, InstName.MovI, T.MovIT3, IsaVersion.v80, InstFlags.Rd), + new(0xEA4F0000, 0xFFEF8000, InstName.MovR, T.MovRT3, IsaVersion.v80, InstFlags.Rd), + new(0xFA00F000, 0xFF80F0F0, InstName.MovRr, T.MovRrT2, IsaVersion.v80, InstFlags.Rd), + new(0xEE100E10, 0xFF100E10, InstName.Mrc, T.MrcT1, IsaVersion.v80, InstFlags.Rt), + new(0xEC500E00, 0xFFF00E00, InstName.Mrrc, T.MrrcT1, IsaVersion.v80, InstFlags.Rt), + new(0xF3EF8000, 0xFFEFF0FF, InstName.Mrs, T.MrsT1, IsaVersion.v80, InstFlags.Rd), + new(0xF3E08020, 0xFFE0F0EF, InstName.MrsBr, T.MrsBrT1, IsaVersion.v80, InstFlags.Rd), + new(0xF3808020, 0xFFE0F0EF, InstName.MsrBr, T.MsrBrT1, IsaVersion.v80, InstFlags.None), + new(0xF3808000, 0xFFE0F0FF, InstName.MsrR, T.MsrRT1, IsaVersion.v80, InstFlags.None), + new(0xFB00F000, 0xFFF0F0F0, InstName.Mul, T.MulT2, IsaVersion.v80, InstFlags.Rd), + new(0xF06F0000, 0xFBEF8000, InstName.MvnI, T.MvnIT1, IsaVersion.v80, InstFlags.Rd), + new(0xEA6F0000, 0xFFEF8000, InstName.MvnR, T.MvnRT2, IsaVersion.v80, InstFlags.Rd), + new(0xF3AF8000, 0xFFFFFFFF, InstName.Nop, T.NopT2, IsaVersion.v80, InstFlags.None), + new(0xF0600000, 0xFBE08000, rnConstraints, InstName.OrnI, T.OrnIT1, IsaVersion.v80, InstFlags.Rd), + new(0xEA600000, 0xFFE08000, rnConstraints, InstName.OrnR, T.OrnRT1, IsaVersion.v80, InstFlags.Rd), + new(0xF0400000, 0xFBE08000, rnConstraints, InstName.OrrI, T.OrrIT1, IsaVersion.v80, InstFlags.Rd), + new(0xEA400000, 0xFFE08000, rnConstraints, InstName.OrrR, T.OrrRT2, IsaVersion.v80, InstFlags.Rd), + new(0xEAC00000, 0xFFF08010, sTConstraints, InstName.Pkh, T.PkhT1, IsaVersion.v80, InstFlags.Rd), + new(0xF890F000, 0xFFD0F000, rnConstraints, InstName.PldI, T.PldIT1, IsaVersion.v80, InstFlags.WBack), + new(0xF810FC00, 0xFFD0FF00, rnConstraints, InstName.PldI, T.PldIT2, IsaVersion.v80, InstFlags.WBack), + new(0xF81FF000, 0xFF7FF000, InstName.PldL, T.PldLT1, IsaVersion.v80, InstFlags.None), + new(0xF810F000, 0xFFD0FFC0, rnConstraints, InstName.PldR, T.PldRT1, IsaVersion.v80, InstFlags.WBack), + new(0xF990F000, 0xFFF0F000, rnConstraints, InstName.PliI, T.PliIT1, IsaVersion.v80, InstFlags.None), + new(0xF910FC00, 0xFFF0FF00, rnConstraints, InstName.PliI, T.PliIT2, IsaVersion.v80, InstFlags.None), + new(0xF91FF000, 0xFF7FF000, InstName.PliI, T.PliIT3, IsaVersion.v80, InstFlags.None), + new(0xF910F000, 0xFFF0FFC0, rnConstraints, InstName.PliR, T.PliRT1, IsaVersion.v80, InstFlags.None), + new(0xF3BF8F44, 0xFFFFFFFF, InstName.Pssbb, T.PssbbT1, IsaVersion.v80, InstFlags.None), + new(0xFA80F080, 0xFFF0F0F0, InstName.Qadd, T.QaddT1, IsaVersion.v80, InstFlags.Rd), + new(0xFA90F010, 0xFFF0F0F0, InstName.Qadd16, T.Qadd16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFA80F010, 0xFFF0F0F0, InstName.Qadd8, T.Qadd8T1, IsaVersion.v80, InstFlags.Rd), + new(0xFAA0F010, 0xFFF0F0F0, InstName.Qasx, T.QasxT1, IsaVersion.v80, InstFlags.Rd), + new(0xFA80F090, 0xFFF0F0F0, InstName.Qdadd, T.QdaddT1, IsaVersion.v80, InstFlags.Rd), + new(0xFA80F0B0, 0xFFF0F0F0, InstName.Qdsub, T.QdsubT1, IsaVersion.v80, InstFlags.Rd), + new(0xFAE0F010, 0xFFF0F0F0, InstName.Qsax, T.QsaxT1, IsaVersion.v80, InstFlags.Rd), + new(0xFA80F0A0, 0xFFF0F0F0, InstName.Qsub, T.QsubT1, IsaVersion.v80, InstFlags.Rd), + new(0xFAD0F010, 0xFFF0F0F0, InstName.Qsub16, T.Qsub16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFAC0F010, 0xFFF0F0F0, InstName.Qsub8, T.Qsub8T1, IsaVersion.v80, InstFlags.Rd), + new(0xFA90F0A0, 0xFFF0F0F0, InstName.Rbit, T.RbitT1, IsaVersion.v80, InstFlags.Rd), + new(0xFA90F080, 0xFFF0F0F0, InstName.Rev, T.RevT2, IsaVersion.v80, InstFlags.Rd), + new(0xFA90F090, 0xFFF0F0F0, InstName.Rev16, T.Rev16T2, IsaVersion.v80, InstFlags.Rd), + new(0xFA90F0B0, 0xFFF0F0F0, InstName.Revsh, T.RevshT2, IsaVersion.v80, InstFlags.Rd), + new(0xE810C000, 0xFFD0FFFF, InstName.Rfe, T.RfeT1, IsaVersion.v80, InstFlags.WBack), + new(0xE990C000, 0xFFD0FFFF, InstName.Rfe, T.RfeT2, IsaVersion.v80, InstFlags.WBack), + new(0xF1C00000, 0xFBE08000, InstName.RsbI, T.RsbIT2, IsaVersion.v80, InstFlags.Rd), + new(0xEBC00000, 0xFFE08000, InstName.RsbR, T.RsbRT1, IsaVersion.v80, InstFlags.Rd), + new(0xFA90F000, 0xFFF0F0F0, InstName.Sadd16, T.Sadd16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFA80F000, 0xFFF0F0F0, InstName.Sadd8, T.Sadd8T1, IsaVersion.v80, InstFlags.Rd), + new(0xFAA0F000, 0xFFF0F0F0, InstName.Sasx, T.SasxT1, IsaVersion.v80, InstFlags.Rd), + new(0xF3BF8F70, 0xFFFFFFFF, InstName.Sb, T.SbT1, IsaVersion.v85, IsaFeature.FeatSb, InstFlags.None), + new(0xF1600000, 0xFBE08000, InstName.SbcI, T.SbcIT1, IsaVersion.v80, InstFlags.Rd), + new(0xEB600000, 0xFFE08000, InstName.SbcR, T.SbcRT2, IsaVersion.v80, InstFlags.Rd), + new(0xF3400000, 0xFFF08020, InstName.Sbfx, T.SbfxT1, IsaVersion.v80, InstFlags.Rd), + new(0xFB90F0F0, 0xFFF0F0F0, InstName.Sdiv, T.SdivT1, IsaVersion.v80, InstFlags.Rd), + new(0xFAA0F080, 0xFFF0F0F0, InstName.Sel, T.SelT1, IsaVersion.v80, InstFlags.Rd), + new(0xF3AF8004, 0xFFFFFFFF, InstName.Sev, T.SevT2, IsaVersion.v80, InstFlags.None), + new(0xF3AF8005, 0xFFFFFFFF, InstName.Sevl, T.SevlT2, IsaVersion.v80, InstFlags.None), + new(0xEF000C40, 0xFFB00F50, vdVnVmConstraints, InstName.Sha1c, T.Sha1cT1, IsaVersion.v80, IsaFeature.FeatSha1, InstFlags.None), + new(0xFFB902C0, 0xFFBF0FD0, vdVmConstraints, InstName.Sha1h, T.Sha1hT1, IsaVersion.v80, IsaFeature.FeatSha1, InstFlags.None), + new(0xEF200C40, 0xFFB00F50, vdVnVmConstraints, InstName.Sha1m, T.Sha1mT1, IsaVersion.v80, IsaFeature.FeatSha1, InstFlags.None), + new(0xEF100C40, 0xFFB00F50, vdVnVmConstraints, InstName.Sha1p, T.Sha1pT1, IsaVersion.v80, IsaFeature.FeatSha1, InstFlags.None), + new(0xEF300C40, 0xFFB00F50, vdVnVmConstraints, InstName.Sha1su0, T.Sha1su0T1, IsaVersion.v80, IsaFeature.FeatSha1, InstFlags.None), + new(0xFFBA0380, 0xFFBF0FD0, vdVmConstraints, InstName.Sha1su1, T.Sha1su1T1, IsaVersion.v80, IsaFeature.FeatSha1, InstFlags.None), + new(0xFF000C40, 0xFFB00F50, vdVnVmConstraints, InstName.Sha256h, T.Sha256hT1, IsaVersion.v80, IsaFeature.FeatSha256, InstFlags.None), + new(0xFF100C40, 0xFFB00F50, vdVnVmConstraints, InstName.Sha256h2, T.Sha256h2T1, IsaVersion.v80, IsaFeature.FeatSha256, InstFlags.None), + new(0xFFBA03C0, 0xFFBF0FD0, vdVmConstraints, InstName.Sha256su0, T.Sha256su0T1, IsaVersion.v80, IsaFeature.FeatSha256, InstFlags.None), + new(0xFF200C40, 0xFFB00F50, vdVnVmConstraints, InstName.Sha256su1, T.Sha256su1T1, IsaVersion.v80, IsaFeature.FeatSha256, InstFlags.None), + new(0xFA90F020, 0xFFF0F0F0, InstName.Shadd16, T.Shadd16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFA80F020, 0xFFF0F0F0, InstName.Shadd8, T.Shadd8T1, IsaVersion.v80, InstFlags.Rd), + new(0xFAA0F020, 0xFFF0F0F0, InstName.Shasx, T.ShasxT1, IsaVersion.v80, InstFlags.Rd), + new(0xFAE0F020, 0xFFF0F0F0, InstName.Shsax, T.ShsaxT1, IsaVersion.v80, InstFlags.Rd), + new(0xFAD0F020, 0xFFF0F0F0, InstName.Shsub16, T.Shsub16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFAC0F020, 0xFFF0F0F0, InstName.Shsub8, T.Shsub8T1, IsaVersion.v80, InstFlags.Rd), + new(0xF7F08000, 0xFFF0FFFF, InstName.Smc, T.SmcT1, IsaVersion.v80, InstFlags.None), + new(0xFB100000, 0xFFF000C0, raConstraints, InstName.Smlabb, T.SmlabbT1, IsaVersion.v80, InstFlags.Rd), + new(0xFB200000, 0xFFF000E0, raConstraints, InstName.Smlad, T.SmladT1, IsaVersion.v80, InstFlags.Rd), + new(0xFBC00000, 0xFFF000F0, InstName.Smlal, T.SmlalT1, IsaVersion.v80, InstFlags.RdLoRdHi), + new(0xFBC00080, 0xFFF000C0, InstName.Smlalbb, T.SmlalbbT1, IsaVersion.v80, InstFlags.RdLoRdHi), + new(0xFBC000C0, 0xFFF000E0, InstName.Smlald, T.SmlaldT1, IsaVersion.v80, InstFlags.RdLoRdHi), + new(0xFB300000, 0xFFF000E0, raConstraints, InstName.Smlawb, T.SmlawbT1, IsaVersion.v80, InstFlags.Rd), + new(0xFB400000, 0xFFF000E0, raConstraints, InstName.Smlsd, T.SmlsdT1, IsaVersion.v80, InstFlags.Rd), + new(0xFBD000C0, 0xFFF000E0, InstName.Smlsld, T.SmlsldT1, IsaVersion.v80, InstFlags.RdLoRdHi), + new(0xFB500000, 0xFFF000E0, raConstraints, InstName.Smmla, T.SmmlaT1, IsaVersion.v80, InstFlags.Rd), + new(0xFB600000, 0xFFF000E0, InstName.Smmls, T.SmmlsT1, IsaVersion.v80, InstFlags.Rd), + new(0xFB50F000, 0xFFF0F0E0, InstName.Smmul, T.SmmulT1, IsaVersion.v80, InstFlags.Rd), + new(0xFB20F000, 0xFFF0F0E0, InstName.Smuad, T.SmuadT1, IsaVersion.v80, InstFlags.Rd), + new(0xFB10F000, 0xFFF0F0C0, InstName.Smulbb, T.SmulbbT1, IsaVersion.v80, InstFlags.Rd), + new(0xFB800000, 0xFFF000F0, InstName.Smull, T.SmullT1, IsaVersion.v80, InstFlags.RdLoRdHi), + new(0xFB30F000, 0xFFF0F0E0, InstName.Smulwb, T.SmulwbT1, IsaVersion.v80, InstFlags.Rd), + new(0xFB40F000, 0xFFF0F0E0, InstName.Smusd, T.SmusdT1, IsaVersion.v80, InstFlags.Rd), + new(0xE80DC000, 0xFFDFFFE0, InstName.Srs, T.SrsT1, IsaVersion.v80, InstFlags.WBack), + new(0xE98DC000, 0xFFDFFFE0, InstName.Srs, T.SrsT2, IsaVersion.v80, InstFlags.WBack), + new(0xF3000000, 0xFFD08020, shimm2imm3Constraints, InstName.Ssat, T.SsatT1, IsaVersion.v80, InstFlags.Rd), + new(0xF3200000, 0xFFF0F0F0, InstName.Ssat16, T.Ssat16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFAE0F000, 0xFFF0F0F0, InstName.Ssax, T.SsaxT1, IsaVersion.v80, InstFlags.Rd), + new(0xF3BF8F40, 0xFFFFFFFF, InstName.Ssbb, T.SsbbT1, IsaVersion.v80, InstFlags.None), + new(0xFAD0F000, 0xFFF0F0F0, InstName.Ssub16, T.Ssub16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFAC0F000, 0xFFF0F0F0, InstName.Ssub8, T.Ssub8T1, IsaVersion.v80, InstFlags.Rd), + new(0xEC005E00, 0xFE50FF00, puwConstraints, InstName.Stc, T.StcT1, IsaVersion.v80, InstFlags.WBack), + new(0xE8C00FAF, 0xFFF00FFF, InstName.Stl, T.StlT1, IsaVersion.v80, InstFlags.RtRead), + new(0xE8C00F8F, 0xFFF00FFF, InstName.Stlb, T.StlbT1, IsaVersion.v80, InstFlags.RtRead), + new(0xE8C00FE0, 0xFFF00FF0, InstName.Stlex, T.StlexT1, IsaVersion.v80, InstFlags.RdRtReadRd16), + new(0xE8C00FC0, 0xFFF00FF0, InstName.Stlexb, T.StlexbT1, IsaVersion.v80, InstFlags.RdRtReadRd16), + new(0xE8C000F0, 0xFFF000F0, InstName.Stlexd, T.StlexdT1, IsaVersion.v80, InstFlags.RdRt2ReadRd16), + new(0xE8C00FD0, 0xFFF00FF0, InstName.Stlexh, T.StlexhT1, IsaVersion.v80, InstFlags.RdRtReadRd16), + new(0xE8C00F9F, 0xFFF00FFF, InstName.Stlh, T.StlhT1, IsaVersion.v80, InstFlags.RtRead), + new(0xE8800000, 0xFFD08000, InstName.Stm, T.StmT2, IsaVersion.v80, InstFlags.RlistReadWBack), + new(0xE9000000, 0xFFD08000, InstName.Stmdb, T.StmdbT1, IsaVersion.v80, InstFlags.RlistReadWBack), + new(0xF8000E00, 0xFFF00F00, rnConstraints, InstName.Strbt, T.StrbtT1, IsaVersion.v80, InstFlags.RtRead), + new(0xF8800000, 0xFFF00000, rnConstraints, InstName.StrbI, T.StrbIT2, IsaVersion.v80, InstFlags.RtRead), + new(0xF8000800, 0xFFF00800, rnPuwPwConstraints, InstName.StrbI, T.StrbIT3, IsaVersion.v80, InstFlags.RtReadWBack), + new(0xF8000000, 0xFFF00FC0, rnConstraints, InstName.StrbR, T.StrbRT2, IsaVersion.v80, InstFlags.RtRead), + new(0xE8400000, 0xFE500000, rnPwConstraints, InstName.StrdI, T.StrdIT1, IsaVersion.v80, InstFlags.Rt2ReadWBack), + new(0xE8400000, 0xFFF00000, InstName.Strex, T.StrexT1, IsaVersion.v80, InstFlags.RdRtRead), + new(0xE8C00F40, 0xFFF00FF0, InstName.Strexb, T.StrexbT1, IsaVersion.v80, InstFlags.RdRtReadRd16), + new(0xE8C00070, 0xFFF000F0, InstName.Strexd, T.StrexdT1, IsaVersion.v80, InstFlags.RdRt2ReadRd16), + new(0xE8C00F50, 0xFFF00FF0, InstName.Strexh, T.StrexhT1, IsaVersion.v80, InstFlags.RdRtReadRd16), + new(0xF8200E00, 0xFFF00F00, rnConstraints, InstName.Strht, T.StrhtT1, IsaVersion.v80, InstFlags.RtRead), + new(0xF8A00000, 0xFFF00000, rnConstraints, InstName.StrhI, T.StrhIT2, IsaVersion.v80, InstFlags.RtRead), + new(0xF8200800, 0xFFF00800, rnPuwPwConstraints, InstName.StrhI, T.StrhIT3, IsaVersion.v80, InstFlags.RtReadWBack), + new(0xF8200000, 0xFFF00FC0, rnConstraints, InstName.StrhR, T.StrhRT2, IsaVersion.v80, InstFlags.RtRead), + new(0xF8400E00, 0xFFF00F00, rnConstraints, InstName.Strt, T.StrtT1, IsaVersion.v80, InstFlags.RtRead), + new(0xF8C00000, 0xFFF00000, rnConstraints, InstName.StrI, T.StrIT3, IsaVersion.v80, InstFlags.RtRead), + new(0xF8400800, 0xFFF00800, rnPuwPwConstraints, InstName.StrI, T.StrIT4, IsaVersion.v80, InstFlags.RtReadWBack), + new(0xF8400000, 0xFFF00FC0, rnConstraints, InstName.StrR, T.StrRT2, IsaVersion.v80, InstFlags.RtRead), + new(0xF1A00000, 0xFBE08000, rnRdsConstraints, InstName.SubI, T.SubIT3, IsaVersion.v80, InstFlags.Rd), + new(0xF2A00000, 0xFBF08000, rnRnConstraints, InstName.SubI, T.SubIT4, IsaVersion.v80, InstFlags.Rd), + new(0xF3D08F00, 0xFFF0FF00, rnimm8Constraints, InstName.SubI, T.SubIT5, IsaVersion.v80, InstFlags.None), + new(0xEBA00000, 0xFFE08000, rnRdsConstraints, InstName.SubR, T.SubRT2, IsaVersion.v80, InstFlags.Rd), + new(0xF1AD0000, 0xFBEF8000, rdsConstraints, InstName.SubSpI, T.SubSpIT2, IsaVersion.v80, InstFlags.Rd), + new(0xF2AD0000, 0xFBFF8000, InstName.SubSpI, T.SubSpIT3, IsaVersion.v80, InstFlags.Rd), + new(0xEBAD0000, 0xFFEF8000, rdsConstraints, InstName.SubSpR, T.SubSpRT1, IsaVersion.v80, InstFlags.Rd), + new(0xFA40F080, 0xFFF0F0C0, rnConstraints, InstName.Sxtab, T.SxtabT1, IsaVersion.v80, InstFlags.Rd), + new(0xFA20F080, 0xFFF0F0C0, rnConstraints, InstName.Sxtab16, T.Sxtab16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFA00F080, 0xFFF0F0C0, rnConstraints, InstName.Sxtah, T.SxtahT1, IsaVersion.v80, InstFlags.Rd), + new(0xFA4FF080, 0xFFFFF0C0, InstName.Sxtb, T.SxtbT2, IsaVersion.v80, InstFlags.Rd), + new(0xFA2FF080, 0xFFFFF0C0, InstName.Sxtb16, T.Sxtb16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFA0FF080, 0xFFFFF0C0, InstName.Sxth, T.SxthT2, IsaVersion.v80, InstFlags.Rd), + new(0xE8D0F000, 0xFFF0FFE0, InstName.Tbb, T.TbbT1, IsaVersion.v80, InstFlags.None), + new(0xF0900F00, 0xFBF08F00, InstName.TeqI, T.TeqIT1, IsaVersion.v80, InstFlags.None), + new(0xEA900F00, 0xFFF08F00, InstName.TeqR, T.TeqRT1, IsaVersion.v80, InstFlags.None), + new(0xF3AF8012, 0xFFFFFFFF, InstName.Tsb, T.TsbT1, IsaVersion.v84, IsaFeature.FeatTrf, InstFlags.None), + new(0xF0100F00, 0xFBF08F00, InstName.TstI, T.TstIT1, IsaVersion.v80, InstFlags.None), + new(0xEA100F00, 0xFFF08F00, InstName.TstR, T.TstRT2, IsaVersion.v80, InstFlags.None), + new(0xFA90F040, 0xFFF0F0F0, InstName.Uadd16, T.Uadd16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFA80F040, 0xFFF0F0F0, InstName.Uadd8, T.Uadd8T1, IsaVersion.v80, InstFlags.Rd), + new(0xFAA0F040, 0xFFF0F0F0, InstName.Uasx, T.UasxT1, IsaVersion.v80, InstFlags.Rd), + new(0xF3C00000, 0xFFF08020, InstName.Ubfx, T.UbfxT1, IsaVersion.v80, InstFlags.Rd), + new(0xF7F0A000, 0xFFF0F000, InstName.Udf, T.UdfT2, IsaVersion.v80, InstFlags.None), + new(0xFBB0F0F0, 0xFFF0F0F0, InstName.Udiv, T.UdivT1, IsaVersion.v80, InstFlags.Rd), + new(0xFA90F060, 0xFFF0F0F0, InstName.Uhadd16, T.Uhadd16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFA80F060, 0xFFF0F0F0, InstName.Uhadd8, T.Uhadd8T1, IsaVersion.v80, InstFlags.Rd), + new(0xFAA0F060, 0xFFF0F0F0, InstName.Uhasx, T.UhasxT1, IsaVersion.v80, InstFlags.Rd), + new(0xFAE0F060, 0xFFF0F0F0, InstName.Uhsax, T.UhsaxT1, IsaVersion.v80, InstFlags.Rd), + new(0xFAD0F060, 0xFFF0F0F0, InstName.Uhsub16, T.Uhsub16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFAC0F060, 0xFFF0F0F0, InstName.Uhsub8, T.Uhsub8T1, IsaVersion.v80, InstFlags.Rd), + new(0xFBE00060, 0xFFF000F0, InstName.Umaal, T.UmaalT1, IsaVersion.v80, InstFlags.RdLoRdHi), + new(0xFBE00000, 0xFFF000F0, InstName.Umlal, T.UmlalT1, IsaVersion.v80, InstFlags.RdLoRdHi), + new(0xFBA00000, 0xFFF000F0, InstName.Umull, T.UmullT1, IsaVersion.v80, InstFlags.RdLoRdHi), + new(0xFA90F050, 0xFFF0F0F0, InstName.Uqadd16, T.Uqadd16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFA80F050, 0xFFF0F0F0, InstName.Uqadd8, T.Uqadd8T1, IsaVersion.v80, InstFlags.Rd), + new(0xFAA0F050, 0xFFF0F0F0, InstName.Uqasx, T.UqasxT1, IsaVersion.v80, InstFlags.Rd), + new(0xFAE0F050, 0xFFF0F0F0, InstName.Uqsax, T.UqsaxT1, IsaVersion.v80, InstFlags.Rd), + new(0xFAD0F050, 0xFFF0F0F0, InstName.Uqsub16, T.Uqsub16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFAC0F050, 0xFFF0F0F0, InstName.Uqsub8, T.Uqsub8T1, IsaVersion.v80, InstFlags.Rd), + new(0xFB70F000, 0xFFF0F0F0, InstName.Usad8, T.Usad8T1, IsaVersion.v80, InstFlags.Rd), + new(0xFB700000, 0xFFF000F0, raConstraints, InstName.Usada8, T.Usada8T1, IsaVersion.v80, InstFlags.Rd), + new(0xF3800000, 0xFFD08020, shimm2imm3Constraints, InstName.Usat, T.UsatT1, IsaVersion.v80, InstFlags.Rd), + new(0xF3A00000, 0xFFF0F0F0, InstName.Usat16, T.Usat16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFAE0F040, 0xFFF0F0F0, InstName.Usax, T.UsaxT1, IsaVersion.v80, InstFlags.Rd), + new(0xFAD0F040, 0xFFF0F0F0, InstName.Usub16, T.Usub16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFAC0F040, 0xFFF0F0F0, InstName.Usub8, T.Usub8T1, IsaVersion.v80, InstFlags.Rd), + new(0xFA50F080, 0xFFF0F0C0, rnConstraints, InstName.Uxtab, T.UxtabT1, IsaVersion.v80, InstFlags.Rd), + new(0xFA30F080, 0xFFF0F0C0, rnConstraints, InstName.Uxtab16, T.Uxtab16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFA10F080, 0xFFF0F0C0, rnConstraints, InstName.Uxtah, T.UxtahT1, IsaVersion.v80, InstFlags.Rd), + new(0xFA5FF080, 0xFFFFF0C0, InstName.Uxtb, T.UxtbT2, IsaVersion.v80, InstFlags.Rd), + new(0xFA3FF080, 0xFFFFF0C0, InstName.Uxtb16, T.Uxtb16T1, IsaVersion.v80, InstFlags.Rd), + new(0xFA1FF080, 0xFFFFF0C0, InstName.Uxth, T.UxthT2, IsaVersion.v80, InstFlags.Rd), + new(0xEF000710, 0xEF800F10, sizeQvdQvnQvmConstraints, InstName.Vaba, T.VabaT1, IsaVersion.v80, InstFlags.None), + new(0xEF800500, 0xEF800F50, sizeVdConstraints, InstName.Vabal, T.VabalT1, IsaVersion.v80, InstFlags.None), + new(0xEF800700, 0xEF800F50, sizeVdConstraints, InstName.VabdlI, T.VabdlIT1, IsaVersion.v80, InstFlags.None), + new(0xFF200D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VabdF, T.VabdFT1, IsaVersion.v80, InstFlags.None), + new(0xFF300D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VabdF, T.VabdFT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF000700, 0xEF800F10, sizeQvdQvnQvmConstraints, InstName.VabdI, T.VabdIT1, IsaVersion.v80, InstFlags.None), + new(0xFFB10300, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vabs, T.VabsT1, IsaVersion.v80, InstFlags.None), + new(0xFFB90700, 0xFFBB0F90, sizeQvdQvmConstraints, InstName.Vabs, T.VabsT1, IsaVersion.v80, InstFlags.None), + new(0xFFB50700, 0xFFBF0F90, sizeQvdQvmConstraints, InstName.Vabs, T.VabsT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEB00AC0, 0xFFBF0ED0, InstName.Vabs, T.VabsT2, IsaVersion.v80, InstFlags.None), + new(0xEEB009C0, 0xFFBF0FD0, InstName.Vabs, T.VabsT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFF000E10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vacge, T.VacgeT1, IsaVersion.v80, InstFlags.None), + new(0xFF100E10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vacge, T.VacgeT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFF200E10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vacgt, T.VacgtT1, IsaVersion.v80, InstFlags.None), + new(0xFF300E10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vacgt, T.VacgtT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF800400, 0xFF800F50, sizeVnVmConstraints, InstName.Vaddhn, T.VaddhnT1, IsaVersion.v80, InstFlags.None), + new(0xEF800000, 0xEF800F50, sizeVdOpvnConstraints, InstName.Vaddl, T.VaddlT1, IsaVersion.v80, InstFlags.None), + new(0xEF800100, 0xEF800F50, sizeVdOpvnConstraints, InstName.Vaddw, T.VaddwT1, IsaVersion.v80, InstFlags.None), + new(0xEF000D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VaddF, T.VaddFT1, IsaVersion.v80, InstFlags.None), + new(0xEF100D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VaddF, T.VaddFT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEE300A00, 0xFFB00E50, InstName.VaddF, T.VaddFT2, IsaVersion.v80, InstFlags.None), + new(0xEE300900, 0xFFB00F50, InstName.VaddF, T.VaddFT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF000800, 0xFF800F10, qvdQvnQvmConstraints, InstName.VaddI, T.VaddIT1, IsaVersion.v80, InstFlags.None), + new(0xEF000110, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VandR, T.VandRT1, IsaVersion.v80, InstFlags.None), + new(0xEF800130, 0xEFB809B0, cmodeCmodeQvdConstraints, InstName.VbicI, T.VbicIT1, IsaVersion.v80, InstFlags.None), + new(0xEF800930, 0xEFB80DB0, cmodeCmodeQvdConstraints, InstName.VbicI, T.VbicIT2, IsaVersion.v80, InstFlags.None), + new(0xEF100110, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VbicR, T.VbicRT1, IsaVersion.v80, InstFlags.None), + new(0xFF300110, 0xFFB00F10, qvdQvnQvmOpConstraints, InstName.Vbif, T.VbifT1, IsaVersion.v80, InstFlags.None), + new(0xFF200110, 0xFFB00F10, qvdQvnQvmOpConstraints, InstName.Vbit, T.VbitT1, IsaVersion.v80, InstFlags.None), + new(0xFF100110, 0xFFB00F10, qvdQvnQvmOpConstraints, InstName.Vbsl, T.VbslT1, IsaVersion.v80, InstFlags.None), + new(0xFC900800, 0xFEB00F10, qvdQvnQvmConstraints, InstName.Vcadd, T.VcaddT1, IsaVersion.v83, IsaFeature.FeatFcma, InstFlags.None), + new(0xFC800800, 0xFEB00F10, qvdQvnQvmConstraints, InstName.Vcadd, T.VcaddT1, IsaVersion.v83, IsaFeature.FeatFcma | IsaFeature.FeatFp16, InstFlags.None), + new(0xFFB10100, 0xFFB30F90, sizeQvdQvmConstraints, InstName.VceqI, T.VceqIT1, IsaVersion.v80, InstFlags.None), + new(0xFFB90500, 0xFFBB0F90, sizeQvdQvmConstraints, InstName.VceqI, T.VceqIT1, IsaVersion.v80, InstFlags.None), + new(0xFFB50500, 0xFFBF0F90, sizeQvdQvmConstraints, InstName.VceqI, T.VceqIT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFF000810, 0xFF800F10, qvdQvnQvmSizeConstraints, InstName.VceqR, T.VceqRT1, IsaVersion.v80, InstFlags.None), + new(0xEF000E00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VceqR, T.VceqRT2, IsaVersion.v80, InstFlags.None), + new(0xEF100E00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VceqR, T.VceqRT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFFB10080, 0xFFB30F90, sizeQvdQvmConstraints, InstName.VcgeI, T.VcgeIT1, IsaVersion.v80, InstFlags.None), + new(0xFFB90480, 0xFFBB0F90, sizeQvdQvmConstraints, InstName.VcgeI, T.VcgeIT1, IsaVersion.v80, InstFlags.None), + new(0xFFB50480, 0xFFBF0F90, sizeQvdQvmConstraints, InstName.VcgeI, T.VcgeIT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF000310, 0xEF800F10, qvdQvnQvmSizeConstraints, InstName.VcgeR, T.VcgeRT1, IsaVersion.v80, InstFlags.None), + new(0xFF000E00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VcgeR, T.VcgeRT2, IsaVersion.v80, InstFlags.None), + new(0xFF100E00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VcgeR, T.VcgeRT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFFB10000, 0xFFB30F90, sizeQvdQvmConstraints, InstName.VcgtI, T.VcgtIT1, IsaVersion.v80, InstFlags.None), + new(0xFFB90400, 0xFFBB0F90, sizeQvdQvmConstraints, InstName.VcgtI, T.VcgtIT1, IsaVersion.v80, InstFlags.None), + new(0xFFB50400, 0xFFBF0F90, sizeQvdQvmConstraints, InstName.VcgtI, T.VcgtIT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF000300, 0xEF800F10, qvdQvnQvmSizeConstraints, InstName.VcgtR, T.VcgtRT1, IsaVersion.v80, InstFlags.None), + new(0xFF200E00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VcgtR, T.VcgtRT2, IsaVersion.v80, InstFlags.None), + new(0xFF300E00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VcgtR, T.VcgtRT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFFB10180, 0xFFB30F90, sizeQvdQvmConstraints, InstName.VcleI, T.VcleIT1, IsaVersion.v80, InstFlags.None), + new(0xFFB90580, 0xFFBB0F90, sizeQvdQvmConstraints, InstName.VcleI, T.VcleIT1, IsaVersion.v80, InstFlags.None), + new(0xFFB50580, 0xFFBF0F90, sizeQvdQvmConstraints, InstName.VcleI, T.VcleIT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFFB00400, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vcls, T.VclsT1, IsaVersion.v80, InstFlags.None), + new(0xFFB10200, 0xFFB30F90, sizeQvdQvmConstraints, InstName.VcltI, T.VcltIT1, IsaVersion.v80, InstFlags.None), + new(0xFFB90600, 0xFFBB0F90, sizeQvdQvmConstraints, InstName.VcltI, T.VcltIT1, IsaVersion.v80, InstFlags.None), + new(0xFFB50600, 0xFFBF0F90, sizeQvdQvmConstraints, InstName.VcltI, T.VcltIT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFFB00480, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vclz, T.VclzT1, IsaVersion.v80, InstFlags.None), + new(0xFC200800, 0xFE200F10, qvdQvnQvmConstraints, InstName.Vcmla, T.VcmlaT1, IsaVersion.v83, IsaFeature.FeatFcma, InstFlags.None), + new(0xFE000800, 0xFF000F10, qvdQvnConstraints, InstName.VcmlaS, T.VcmlaST1, IsaVersion.v83, IsaFeature.FeatFcma, InstFlags.None), + new(0xEEB40A40, 0xFFBF0ED0, InstName.Vcmp, T.VcmpT1, IsaVersion.v80, InstFlags.None), + new(0xEEB40940, 0xFFBF0FD0, InstName.Vcmp, T.VcmpT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEB50A40, 0xFFBF0EFF, InstName.Vcmp, T.VcmpT2, IsaVersion.v80, InstFlags.None), + new(0xEEB50940, 0xFFBF0FFF, InstName.Vcmp, T.VcmpT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEB40AC0, 0xFFBF0ED0, InstName.Vcmpe, T.VcmpeT1, IsaVersion.v80, InstFlags.None), + new(0xEEB409C0, 0xFFBF0FD0, InstName.Vcmpe, T.VcmpeT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEB50AC0, 0xFFBF0EFF, InstName.Vcmpe, T.VcmpeT2, IsaVersion.v80, InstFlags.None), + new(0xEEB509C0, 0xFFBF0FFF, InstName.Vcmpe, T.VcmpeT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFFB00500, 0xFFBF0F90, qvdQvmConstraints, InstName.Vcnt, T.VcntT1, IsaVersion.v80, InstFlags.None), + new(0xFFBB0000, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtaAsimd, T.VcvtaAsimdT1, IsaVersion.v80, InstFlags.None), + new(0xFFB70000, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtaAsimd, T.VcvtaAsimdT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEBC0A40, 0xFFBF0E50, sizeConstraints, InstName.VcvtaVfp, T.VcvtaVfpT1, IsaVersion.v80, InstFlags.None), + new(0xFEBC0940, 0xFFBF0F50, sizeConstraints, InstName.VcvtaVfp, T.VcvtaVfpT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEB20A40, 0xFFBE0ED0, InstName.Vcvtb, T.VcvtbT1, IsaVersion.v80, InstFlags.None), + new(0xEEB30940, 0xFFBF0FD0, InstName.VcvtbBfs, T.VcvtbBfsT1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.None), + new(0xFFBB0300, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtmAsimd, T.VcvtmAsimdT1, IsaVersion.v80, InstFlags.None), + new(0xFFB70300, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtmAsimd, T.VcvtmAsimdT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEBF0A40, 0xFFBF0E50, sizeConstraints, InstName.VcvtmVfp, T.VcvtmVfpT1, IsaVersion.v80, InstFlags.None), + new(0xFEBF0940, 0xFFBF0F50, sizeConstraints, InstName.VcvtmVfp, T.VcvtmVfpT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFFBB0100, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtnAsimd, T.VcvtnAsimdT1, IsaVersion.v80, InstFlags.None), + new(0xFFB70100, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtnAsimd, T.VcvtnAsimdT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEBD0A40, 0xFFBF0E50, sizeConstraints, InstName.VcvtnVfp, T.VcvtnVfpT1, IsaVersion.v80, InstFlags.None), + new(0xFEBD0940, 0xFFBF0F50, sizeConstraints, InstName.VcvtnVfp, T.VcvtnVfpT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFFBB0200, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtpAsimd, T.VcvtpAsimdT1, IsaVersion.v80, InstFlags.None), + new(0xFFB70200, 0xFFBF0F10, qvdQvmConstraints, InstName.VcvtpAsimd, T.VcvtpAsimdT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEBE0A40, 0xFFBF0E50, sizeConstraints, InstName.VcvtpVfp, T.VcvtpVfpT1, IsaVersion.v80, InstFlags.None), + new(0xFEBE0940, 0xFFBF0F50, sizeConstraints, InstName.VcvtpVfp, T.VcvtpVfpT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEBC0A40, 0xFFBE0ED0, InstName.VcvtrIv, T.VcvtrIvT1, IsaVersion.v80, InstFlags.None), + new(0xEEBC0940, 0xFFBE0FD0, InstName.VcvtrIv, T.VcvtrIvT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEB20AC0, 0xFFBE0ED0, InstName.Vcvtt, T.VcvttT1, IsaVersion.v80, InstFlags.None), + new(0xEEB309C0, 0xFFBF0FD0, InstName.VcvttBfs, T.VcvttBfsT1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.None), + new(0xFFB60640, 0xFFBF0FD0, vmConstraints, InstName.VcvtBfs, T.VcvtBfsT1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.None), + new(0xEEB70AC0, 0xFFBF0ED0, InstName.VcvtDs, T.VcvtDsT1, IsaVersion.v80, InstFlags.None), + new(0xFFB60600, 0xFFBF0ED0, opvdOpvmConstraints, InstName.VcvtHs, T.VcvtHsT1, IsaVersion.v80, InstFlags.None), + new(0xFFBB0600, 0xFFBF0E10, qvdQvmConstraints, InstName.VcvtIs, T.VcvtIsT1, IsaVersion.v80, InstFlags.None), + new(0xFFB70600, 0xFFBF0E10, qvdQvmConstraints, InstName.VcvtIs, T.VcvtIsT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEBC0AC0, 0xFFBE0ED0, InstName.VcvtIv, T.VcvtIvT1, IsaVersion.v80, InstFlags.None), + new(0xEEBC09C0, 0xFFBE0FD0, InstName.VcvtIv, T.VcvtIvT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEB80A40, 0xFFBF0E50, InstName.VcvtVi, T.VcvtViT1, IsaVersion.v80, InstFlags.None), + new(0xEEB80940, 0xFFBF0F50, InstName.VcvtVi, T.VcvtViT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF800E10, 0xEF800E90, imm6Opimm6Imm6QvdQvmConstraints, InstName.VcvtXs, T.VcvtXsT1, IsaVersion.v80, InstFlags.None), + new(0xEF800C10, 0xEF800E90, imm6Opimm6Imm6QvdQvmConstraints, InstName.VcvtXs, T.VcvtXsT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEBA0A40, 0xFFBA0E50, InstName.VcvtXv, T.VcvtXvT1, IsaVersion.v80, InstFlags.None), + new(0xEEBA0940, 0xFFBA0F50, InstName.VcvtXv, T.VcvtXvT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEE800A00, 0xFFB00E50, InstName.Vdiv, T.VdivT1, IsaVersion.v80, InstFlags.None), + new(0xEE800900, 0xFFB00F50, InstName.Vdiv, T.VdivT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFC000D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vdot, T.VdotT1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.None), + new(0xFE000D00, 0xFFB00F10, qvdQvnConstraints, InstName.VdotS, T.VdotST1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.None), + new(0xEE800B10, 0xFF900F5F, qvdEbConstraints, InstName.VdupR, T.VdupRT1, IsaVersion.v80, InstFlags.RtRead), + new(0xFFB00C00, 0xFFB00F90, imm4QvdConstraints, InstName.VdupS, T.VdupST1, IsaVersion.v80, InstFlags.None), + new(0xFF000110, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Veor, T.VeorT1, IsaVersion.v80, InstFlags.None), + new(0xEFB00000, 0xFFB00010, qvdQvnQvmQimm4Constraints, InstName.Vext, T.VextT1, IsaVersion.v80, InstFlags.None), + new(0xEF000C10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vfma, T.VfmaT1, IsaVersion.v80, InstFlags.None), + new(0xEF100C10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vfma, T.VfmaT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEA00A00, 0xFFB00E50, InstName.Vfma, T.VfmaT2, IsaVersion.v80, InstFlags.None), + new(0xEEA00900, 0xFFB00F50, InstName.Vfma, T.VfmaT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFC200810, 0xFFB00F10, qvdConstraints, InstName.Vfmal, T.VfmalT1, IsaVersion.v82, IsaFeature.FeatFhm, InstFlags.None), + new(0xFE000810, 0xFFB00F10, qvdConstraints, InstName.VfmalS, T.VfmalST1, IsaVersion.v82, IsaFeature.FeatFhm, InstFlags.None), + new(0xFC300810, 0xFFB00F10, vdVnVmConstraints, InstName.VfmaBf, T.VfmaBfT1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.None), + new(0xFE300810, 0xFFB00F10, vdVnConstraints, InstName.VfmaBfs, T.VfmaBfsT1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.None), + new(0xEF200C10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vfms, T.VfmsT1, IsaVersion.v80, InstFlags.None), + new(0xEF300C10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vfms, T.VfmsT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEA00A40, 0xFFB00E50, InstName.Vfms, T.VfmsT2, IsaVersion.v80, InstFlags.None), + new(0xEEA00940, 0xFFB00F50, InstName.Vfms, T.VfmsT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFCA00810, 0xFFB00F10, qvdConstraints, InstName.Vfmsl, T.VfmslT1, IsaVersion.v82, IsaFeature.FeatFhm, InstFlags.None), + new(0xFE100810, 0xFFB00F10, qvdConstraints, InstName.VfmslS, T.VfmslST1, IsaVersion.v82, IsaFeature.FeatFhm, InstFlags.None), + new(0xEE900A40, 0xFFB00E50, InstName.Vfnma, T.VfnmaT1, IsaVersion.v80, InstFlags.None), + new(0xEE900940, 0xFFB00F50, InstName.Vfnma, T.VfnmaT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEE900A00, 0xFFB00E50, InstName.Vfnms, T.VfnmsT1, IsaVersion.v80, InstFlags.None), + new(0xEE900900, 0xFFB00F50, InstName.Vfnms, T.VfnmsT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF000000, 0xEF800F10, qvdQvnQvmSizeConstraints, InstName.Vhadd, T.VhaddT1, IsaVersion.v80, InstFlags.None), + new(0xEF000200, 0xEF800F10, qvdQvnQvmSizeConstraints, InstName.Vhsub, T.VhsubT1, IsaVersion.v80, InstFlags.None), + new(0xFEB00AC0, 0xFFBF0FD0, InstName.Vins, T.VinsT1, IsaVersion.v82, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEB90BC0, 0xFFBF0FD0, InstName.Vjcvt, T.VjcvtT1, IsaVersion.v83, IsaFeature.FeatJscvt, InstFlags.None), + new(0xF9A00000, 0xFFB00F10, sizeConstraints2, InstName.Vld11, T.Vld11T1, IsaVersion.v80, InstFlags.None), + new(0xF9A00400, 0xFFB00F20, sizeConstraints2, InstName.Vld11, T.Vld11T2, IsaVersion.v80, InstFlags.None), + new(0xF9A00800, 0xFFB00F40, sizeIndexAlignIndexAlignConstraints, InstName.Vld11, T.Vld11T3, IsaVersion.v80, InstFlags.None), + new(0xF9A00C00, 0xFFB00F00, sizeSizeaConstraints, InstName.Vld1A, T.Vld1AT1, IsaVersion.v80, InstFlags.None), + new(0xF9200700, 0xFFB00F00, alignConstraints, InstName.Vld1M, T.Vld1MT1, IsaVersion.v80, InstFlags.None), + new(0xF9200A00, 0xFFB00F00, alignConstraints2, InstName.Vld1M, T.Vld1MT2, IsaVersion.v80, InstFlags.None), + new(0xF9200600, 0xFFB00F00, alignConstraints, InstName.Vld1M, T.Vld1MT3, IsaVersion.v80, InstFlags.None), + new(0xF9200200, 0xFFB00F00, InstName.Vld1M, T.Vld1MT4, IsaVersion.v80, InstFlags.None), + new(0xF9A00100, 0xFFB00F00, sizeConstraints2, InstName.Vld21, T.Vld21T1, IsaVersion.v80, InstFlags.None), + new(0xF9A00500, 0xFFB00F00, sizeConstraints2, InstName.Vld21, T.Vld21T2, IsaVersion.v80, InstFlags.None), + new(0xF9A00900, 0xFFB00F20, sizeConstraints2, InstName.Vld21, T.Vld21T3, IsaVersion.v80, InstFlags.None), + new(0xF9A00D00, 0xFFB00F00, sizeConstraints3, InstName.Vld2A, T.Vld2AT1, IsaVersion.v80, InstFlags.None), + new(0xF9200800, 0xFFB00E00, alignSizeConstraints, InstName.Vld2M, T.Vld2MT1, IsaVersion.v80, InstFlags.None), + new(0xF9200300, 0xFFB00F00, sizeConstraints3, InstName.Vld2M, T.Vld2MT2, IsaVersion.v80, InstFlags.None), + new(0xF9A00200, 0xFFB00F10, sizeConstraints2, InstName.Vld31, T.Vld31T1, IsaVersion.v80, InstFlags.None), + new(0xF9A00600, 0xFFB00F10, sizeConstraints2, InstName.Vld31, T.Vld31T2, IsaVersion.v80, InstFlags.None), + new(0xF9A00A00, 0xFFB00F30, sizeConstraints2, InstName.Vld31, T.Vld31T3, IsaVersion.v80, InstFlags.None), + new(0xF9A00E00, 0xFFB00F10, sizeAConstraints, InstName.Vld3A, T.Vld3AT1, IsaVersion.v80, InstFlags.None), + new(0xF9200400, 0xFFB00E00, sizeAlignConstraints, InstName.Vld3M, T.Vld3MT1, IsaVersion.v80, InstFlags.None), + new(0xF9A00300, 0xFFB00F00, sizeConstraints2, InstName.Vld41, T.Vld41T1, IsaVersion.v80, InstFlags.None), + new(0xF9A00700, 0xFFB00F00, sizeConstraints2, InstName.Vld41, T.Vld41T2, IsaVersion.v80, InstFlags.None), + new(0xF9A00B00, 0xFFB00F00, sizeIndexAlignConstraints, InstName.Vld41, T.Vld41T3, IsaVersion.v80, InstFlags.None), + new(0xF9A00F00, 0xFFB00F00, sizeaConstraints, InstName.Vld4A, T.Vld4AT1, IsaVersion.v80, InstFlags.None), + new(0xF9200000, 0xFFB00E00, sizeConstraints3, InstName.Vld4M, T.Vld4MT1, IsaVersion.v80, InstFlags.None), + new(0xEC100B00, 0xFE100F01, puwPwPuwPuwConstraints, InstName.Vldm, T.VldmT1, IsaVersion.v80, InstFlags.WBack), + new(0xEC100A00, 0xFE100F00, puwPwPuwPuwConstraints, InstName.Vldm, T.VldmT2, IsaVersion.v80, InstFlags.WBack), + new(0xED100A00, 0xFF300E00, rnConstraints, InstName.VldrI, T.VldrIT1, IsaVersion.v80, InstFlags.None), + new(0xED100900, 0xFF300F00, rnConstraints, InstName.VldrI, T.VldrIT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xED1F0A00, 0xFF3F0E00, InstName.VldrL, T.VldrLT1, IsaVersion.v80, InstFlags.None), + new(0xED1F0900, 0xFF3F0F00, InstName.VldrL, T.VldrLT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFF000F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vmaxnm, T.VmaxnmT1, IsaVersion.v80, InstFlags.None), + new(0xFF100F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vmaxnm, T.VmaxnmT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFE800A00, 0xFFB00E50, sizeConstraints, InstName.Vmaxnm, T.VmaxnmT2, IsaVersion.v80, InstFlags.None), + new(0xFE800900, 0xFFB00F50, sizeConstraints, InstName.Vmaxnm, T.VmaxnmT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF000F00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmaxF, T.VmaxFT1, IsaVersion.v80, InstFlags.None), + new(0xEF100F00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmaxF, T.VmaxFT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF000600, 0xEF800F10, qvdQvnQvmSizeConstraints, InstName.VmaxI, T.VmaxIT1, IsaVersion.v80, InstFlags.None), + new(0xFF200F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vminnm, T.VminnmT1, IsaVersion.v80, InstFlags.None), + new(0xFF300F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vminnm, T.VminnmT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFE800A40, 0xFFB00E50, sizeConstraints, InstName.Vminnm, T.VminnmT2, IsaVersion.v80, InstFlags.None), + new(0xFE800940, 0xFFB00F50, sizeConstraints, InstName.Vminnm, T.VminnmT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF200F00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VminF, T.VminFT1, IsaVersion.v80, InstFlags.None), + new(0xEF300F00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VminF, T.VminFT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF000610, 0xEF800F10, qvdQvnQvmSizeConstraints, InstName.VminI, T.VminIT1, IsaVersion.v80, InstFlags.None), + new(0xEF800800, 0xEF800F50, sizeVdConstraints, InstName.VmlalI, T.VmlalIT1, IsaVersion.v80, InstFlags.None), + new(0xEF800240, 0xEF800F50, sizeSizeVdConstraints, InstName.VmlalS, T.VmlalST1, IsaVersion.v80, InstFlags.None), + new(0xEF000D10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmlaF, T.VmlaFT1, IsaVersion.v80, InstFlags.None), + new(0xEF100D10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmlaF, T.VmlaFT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEE000A00, 0xFFB00E50, InstName.VmlaF, T.VmlaFT2, IsaVersion.v80, InstFlags.None), + new(0xEE000900, 0xFFB00F50, InstName.VmlaF, T.VmlaFT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF000900, 0xFF800F10, sizeQvdQvnQvmConstraints, InstName.VmlaI, T.VmlaIT1, IsaVersion.v80, InstFlags.None), + new(0xEFA00040, 0xEFA00E50, sizeQvdQvnConstraints, InstName.VmlaS, T.VmlaST1, IsaVersion.v80, InstFlags.None), + new(0xEF900040, 0xEFB00F50, sizeQvdQvnConstraints, InstName.VmlaS, T.VmlaST1, IsaVersion.v80, InstFlags.None), + new(0xEF900140, 0xEFB00F50, sizeQvdQvnConstraints, InstName.VmlaS, T.VmlaST1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF800A00, 0xEF800F50, sizeVdConstraints, InstName.VmlslI, T.VmlslIT1, IsaVersion.v80, InstFlags.None), + new(0xEF800640, 0xEF800F50, sizeSizeVdConstraints, InstName.VmlslS, T.VmlslST1, IsaVersion.v80, InstFlags.None), + new(0xEF200D10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmlsF, T.VmlsFT1, IsaVersion.v80, InstFlags.None), + new(0xEF300D10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmlsF, T.VmlsFT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEE000A40, 0xFFB00E50, InstName.VmlsF, T.VmlsFT2, IsaVersion.v80, InstFlags.None), + new(0xEE000940, 0xFFB00F50, InstName.VmlsF, T.VmlsFT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFF000900, 0xFF800F10, sizeQvdQvnQvmConstraints, InstName.VmlsI, T.VmlsIT1, IsaVersion.v80, InstFlags.None), + new(0xEFA00440, 0xEFA00E50, sizeQvdQvnConstraints, InstName.VmlsS, T.VmlsST1, IsaVersion.v80, InstFlags.None), + new(0xEF900440, 0xEFB00F50, sizeQvdQvnConstraints, InstName.VmlsS, T.VmlsST1, IsaVersion.v80, InstFlags.None), + new(0xEF900540, 0xEFB00F50, sizeQvdQvnConstraints, InstName.VmlsS, T.VmlsST1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFC000C40, 0xFFB00F50, vdVnVmConstraints, InstName.Vmmla, T.VmmlaT1, IsaVersion.v86, IsaFeature.FeatAa32bf16, InstFlags.None), + new(0xEF800A10, 0xEF870FD0, imm3hImm3hImm3hImm3hImm3hVdConstraints, InstName.Vmovl, T.VmovlT1, IsaVersion.v80, InstFlags.None), + new(0xFFB20200, 0xFFB30FD0, sizeVmConstraints, InstName.Vmovn, T.VmovnT1, IsaVersion.v80, InstFlags.None), + new(0xFEB00A40, 0xFFBF0FD0, InstName.Vmovx, T.VmovxT1, IsaVersion.v82, IsaFeature.FeatFp16, InstFlags.None), + new(0xEC400B10, 0xFFE00FD0, InstName.VmovD, T.VmovDT1, IsaVersion.v80, InstFlags.Rt2Read), + new(0xEE000910, 0xFFE00F7F, InstName.VmovH, T.VmovHT1, IsaVersion.v82, IsaFeature.FeatFp16, InstFlags.Rt), + new(0xEF800010, 0xEFB809B0, qvdConstraints, InstName.VmovI, T.VmovIT1, IsaVersion.v80, InstFlags.None), + new(0xEEB00A00, 0xFFB00EF0, InstName.VmovI, T.VmovIT2, IsaVersion.v80, InstFlags.None), + new(0xEEB00900, 0xFFB00FF0, InstName.VmovI, T.VmovIT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF800810, 0xEFB80DB0, qvdConstraints, InstName.VmovI, T.VmovIT3, IsaVersion.v80, InstFlags.None), + new(0xEF800C10, 0xEFB80CB0, qvdConstraints, InstName.VmovI, T.VmovIT4, IsaVersion.v80, InstFlags.None), + new(0xEF800E30, 0xEFB80FB0, qvdConstraints, InstName.VmovI, T.VmovIT5, IsaVersion.v80, InstFlags.None), + new(0xEEB00A40, 0xFFBF0ED0, InstName.VmovR, T.VmovRT2, IsaVersion.v80, InstFlags.None), + new(0xEE000B10, 0xFF900F1F, opc1opc2Constraints, InstName.VmovRs, T.VmovRsT1, IsaVersion.v80, InstFlags.RtRead), + new(0xEE000A10, 0xFFE00F7F, InstName.VmovS, T.VmovST1, IsaVersion.v80, InstFlags.RtRead), + new(0xEE100B10, 0xFF100F1F, uopc1opc2Uopc1opc2Constraints, InstName.VmovSr, T.VmovSrT1, IsaVersion.v80, InstFlags.Rt), + new(0xEC400A10, 0xFFE00FD0, InstName.VmovSs, T.VmovSsT1, IsaVersion.v80, InstFlags.Rt2Read), + new(0xEEF00A10, 0xFFF00FFF, InstName.Vmrs, T.VmrsT1, IsaVersion.v80, InstFlags.Rt), + new(0xEEE00A10, 0xFFF00FFF, InstName.Vmsr, T.VmsrT1, IsaVersion.v80, InstFlags.RtRead), + new(0xEF800C00, 0xEF800D50, sizeOpuOpsizeVdConstraints, InstName.VmullI, T.VmullIT1, IsaVersion.v80, InstFlags.None), + new(0xEF800A40, 0xEF800F50, sizeSizeVdConstraints, InstName.VmullS, T.VmullST1, IsaVersion.v80, InstFlags.None), + new(0xFF000D10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmulF, T.VmulFT1, IsaVersion.v80, InstFlags.None), + new(0xFF100D10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VmulF, T.VmulFT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEE200A00, 0xFFB00E50, InstName.VmulF, T.VmulFT2, IsaVersion.v80, InstFlags.None), + new(0xEE200900, 0xFFB00F50, InstName.VmulF, T.VmulFT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF000910, 0xEF800F10, sizeOpsizeOpsizeQvdQvnQvmConstraints, InstName.VmulI, T.VmulIT1, IsaVersion.v80, InstFlags.None), + new(0xEFA00840, 0xEFA00E50, sizeQvdQvnConstraints, InstName.VmulS, T.VmulST1, IsaVersion.v80, InstFlags.None), + new(0xEF900840, 0xEFB00F50, sizeQvdQvnConstraints, InstName.VmulS, T.VmulST1, IsaVersion.v80, InstFlags.None), + new(0xEF900940, 0xEFB00F50, sizeQvdQvnConstraints, InstName.VmulS, T.VmulST1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF800030, 0xEFB809B0, cmodeQvdConstraints, InstName.VmvnI, T.VmvnIT1, IsaVersion.v80, InstFlags.None), + new(0xEF800830, 0xEFB80DB0, cmodeQvdConstraints, InstName.VmvnI, T.VmvnIT2, IsaVersion.v80, InstFlags.None), + new(0xEF800C30, 0xEFB80EB0, cmodeQvdConstraints, InstName.VmvnI, T.VmvnIT3, IsaVersion.v80, InstFlags.None), + new(0xFFB00580, 0xFFBF0F90, qvdQvmConstraints, InstName.VmvnR, T.VmvnRT1, IsaVersion.v80, InstFlags.None), + new(0xFFB10380, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vneg, T.VnegT1, IsaVersion.v80, InstFlags.None), + new(0xFFB90780, 0xFFBB0F90, sizeQvdQvmConstraints, InstName.Vneg, T.VnegT1, IsaVersion.v80, InstFlags.None), + new(0xFFB50780, 0xFFBF0F90, sizeQvdQvmConstraints, InstName.Vneg, T.VnegT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEB10A40, 0xFFBF0ED0, InstName.Vneg, T.VnegT2, IsaVersion.v80, InstFlags.None), + new(0xEEB10940, 0xFFBF0FD0, InstName.Vneg, T.VnegT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEE100A40, 0xFFB00E50, InstName.Vnmla, T.VnmlaT1, IsaVersion.v80, InstFlags.None), + new(0xEE100940, 0xFFB00F50, InstName.Vnmla, T.VnmlaT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEE100A00, 0xFFB00E50, InstName.Vnmls, T.VnmlsT1, IsaVersion.v80, InstFlags.None), + new(0xEE100900, 0xFFB00F50, InstName.Vnmls, T.VnmlsT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEE200A40, 0xFFB00E50, InstName.Vnmul, T.VnmulT1, IsaVersion.v80, InstFlags.None), + new(0xEE200940, 0xFFB00F50, InstName.Vnmul, T.VnmulT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF300110, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VornR, T.VornRT1, IsaVersion.v80, InstFlags.None), + new(0xEF800110, 0xEFB809B0, cmodeCmodeQvdConstraints, InstName.VorrI, T.VorrIT1, IsaVersion.v80, InstFlags.None), + new(0xEF800910, 0xEFB80DB0, cmodeCmodeQvdConstraints, InstName.VorrI, T.VorrIT2, IsaVersion.v80, InstFlags.None), + new(0xEF200110, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VorrR, T.VorrRT1, IsaVersion.v80, InstFlags.None), + new(0xFFB00600, 0xFFB30F10, sizeQvdQvmConstraints, InstName.Vpadal, T.VpadalT1, IsaVersion.v80, InstFlags.None), + new(0xFFB00200, 0xFFB30F10, sizeQvdQvmConstraints, InstName.Vpaddl, T.VpaddlT1, IsaVersion.v80, InstFlags.None), + new(0xFF000D00, 0xFFB00F10, qConstraints, InstName.VpaddF, T.VpaddFT1, IsaVersion.v80, InstFlags.None), + new(0xFF100D00, 0xFFB00F10, qConstraints, InstName.VpaddF, T.VpaddFT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF000B10, 0xFF800F10, sizeQConstraints, InstName.VpaddI, T.VpaddIT1, IsaVersion.v80, InstFlags.None), + new(0xFF000F00, 0xFFB00F50, InstName.VpmaxF, T.VpmaxFT1, IsaVersion.v80, InstFlags.None), + new(0xFF100F00, 0xFFB00F50, InstName.VpmaxF, T.VpmaxFT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF000A00, 0xEF800F50, sizeConstraints4, InstName.VpmaxI, T.VpmaxIT1, IsaVersion.v80, InstFlags.None), + new(0xFF200F00, 0xFFB00F50, InstName.VpminF, T.VpminFT1, IsaVersion.v80, InstFlags.None), + new(0xFF300F00, 0xFFB00F50, InstName.VpminF, T.VpminFT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF000A10, 0xEF800F50, sizeConstraints4, InstName.VpminI, T.VpminIT1, IsaVersion.v80, InstFlags.None), + new(0xFFB00700, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vqabs, T.VqabsT1, IsaVersion.v80, InstFlags.None), + new(0xEF000010, 0xEF800F10, qvdQvnQvmConstraints, InstName.Vqadd, T.VqaddT1, IsaVersion.v80, InstFlags.None), + new(0xEF800900, 0xFF800F50, sizeSizeVdConstraints, InstName.Vqdmlal, T.VqdmlalT1, IsaVersion.v80, InstFlags.None), + new(0xEF800340, 0xFF800F50, sizeSizeVdConstraints, InstName.Vqdmlal, T.VqdmlalT2, IsaVersion.v80, InstFlags.None), + new(0xEF800B00, 0xFF800F50, sizeSizeVdConstraints, InstName.Vqdmlsl, T.VqdmlslT1, IsaVersion.v80, InstFlags.None), + new(0xEF800740, 0xFF800F50, sizeSizeVdConstraints, InstName.Vqdmlsl, T.VqdmlslT2, IsaVersion.v80, InstFlags.None), + new(0xEF000B00, 0xFF800F10, qvdQvnQvmSizeSizeConstraints, InstName.Vqdmulh, T.VqdmulhT1, IsaVersion.v80, InstFlags.None), + new(0xEF800C40, 0xEF800F50, sizeSizeQvdQvnConstraints, InstName.Vqdmulh, T.VqdmulhT2, IsaVersion.v80, InstFlags.None), + new(0xEF800D00, 0xFF800F50, sizeSizeVdConstraints, InstName.Vqdmull, T.VqdmullT1, IsaVersion.v80, InstFlags.None), + new(0xEF800B40, 0xFF800F50, sizeSizeVdConstraints, InstName.Vqdmull, T.VqdmullT2, IsaVersion.v80, InstFlags.None), + new(0xFFB20200, 0xFFB30F10, opSizeVmConstraints, InstName.Vqmovn, T.VqmovnT1, IsaVersion.v80, InstFlags.None), + new(0xFFB00780, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vqneg, T.VqnegT1, IsaVersion.v80, InstFlags.None), + new(0xFF000B10, 0xFF800F10, qvdQvnQvmSizeSizeConstraints, InstName.Vqrdmlah, T.VqrdmlahT1, IsaVersion.v81, IsaFeature.FeatRdm, InstFlags.None), + new(0xEF800E40, 0xEF800F50, sizeSizeQvdQvnConstraints, InstName.Vqrdmlah, T.VqrdmlahT2, IsaVersion.v81, IsaFeature.FeatRdm, InstFlags.None), + new(0xFF000C10, 0xFF800F10, qvdQvnQvmSizeSizeConstraints, InstName.Vqrdmlsh, T.VqrdmlshT1, IsaVersion.v81, IsaFeature.FeatRdm, InstFlags.None), + new(0xEF800F40, 0xEF800F50, sizeSizeQvdQvnConstraints, InstName.Vqrdmlsh, T.VqrdmlshT2, IsaVersion.v81, IsaFeature.FeatRdm, InstFlags.None), + new(0xFF000B00, 0xFF800F10, qvdQvnQvmSizeSizeConstraints, InstName.Vqrdmulh, T.VqrdmulhT1, IsaVersion.v80, InstFlags.None), + new(0xEF800D40, 0xEF800F50, sizeSizeQvdQvnConstraints, InstName.Vqrdmulh, T.VqrdmulhT2, IsaVersion.v80, InstFlags.None), + new(0xEF000510, 0xEF800F10, qvdQvmQvnConstraints, InstName.Vqrshl, T.VqrshlT1, IsaVersion.v80, InstFlags.None), + new(0xEF800850, 0xEF800ED0, imm6UopVmConstraints, InstName.Vqrshrn, T.VqrshrnT1, IsaVersion.v80, InstFlags.None), + new(0xEF800610, 0xEF800E10, imm6lUopQvdQvmConstraints, InstName.VqshlI, T.VqshlIT1, IsaVersion.v80, InstFlags.None), + new(0xEF000410, 0xEF800F10, qvdQvmQvnConstraints, InstName.VqshlR, T.VqshlRT1, IsaVersion.v80, InstFlags.None), + new(0xEF800810, 0xEF800ED0, imm6UopVmConstraints, InstName.Vqshrn, T.VqshrnT1, IsaVersion.v80, InstFlags.None), + new(0xEF000210, 0xEF800F10, qvdQvnQvmConstraints, InstName.Vqsub, T.VqsubT1, IsaVersion.v80, InstFlags.None), + new(0xFF800400, 0xFF800F50, sizeVnVmConstraints, InstName.Vraddhn, T.VraddhnT1, IsaVersion.v80, InstFlags.None), + new(0xFFB30400, 0xFFB30E90, qvdQvmSizeSizeConstraints, InstName.Vrecpe, T.VrecpeT1, IsaVersion.v80, InstFlags.None), + new(0xEF000F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vrecps, T.VrecpsT1, IsaVersion.v80, InstFlags.None), + new(0xEF100F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vrecps, T.VrecpsT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFFB00100, 0xFFB30F90, sizeSizeSizeQvdQvmConstraints, InstName.Vrev16, T.Vrev16T1, IsaVersion.v80, InstFlags.None), + new(0xFFB00080, 0xFFB30F90, sizeSizeQvdQvmConstraints, InstName.Vrev32, T.Vrev32T1, IsaVersion.v80, InstFlags.None), + new(0xFFB00000, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vrev64, T.Vrev64T1, IsaVersion.v80, InstFlags.None), + new(0xEF000100, 0xEF800F10, qvdQvnQvmSizeConstraints, InstName.Vrhadd, T.VrhaddT1, IsaVersion.v80, InstFlags.None), + new(0xFFBA0500, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintaAsimd, T.VrintaAsimdT1, IsaVersion.v80, InstFlags.None), + new(0xFFB60500, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintaAsimd, T.VrintaAsimdT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEB80A40, 0xFFBF0ED0, sizeConstraints, InstName.VrintaVfp, T.VrintaVfpT1, IsaVersion.v80, InstFlags.None), + new(0xFEB80940, 0xFFBF0FD0, sizeConstraints, InstName.VrintaVfp, T.VrintaVfpT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFFBA0680, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintmAsimd, T.VrintmAsimdT1, IsaVersion.v80, InstFlags.None), + new(0xFFB60680, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintmAsimd, T.VrintmAsimdT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEBB0A40, 0xFFBF0ED0, sizeConstraints, InstName.VrintmVfp, T.VrintmVfpT1, IsaVersion.v80, InstFlags.None), + new(0xFEBB0940, 0xFFBF0FD0, sizeConstraints, InstName.VrintmVfp, T.VrintmVfpT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFFBA0400, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintnAsimd, T.VrintnAsimdT1, IsaVersion.v80, InstFlags.None), + new(0xFFB60400, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintnAsimd, T.VrintnAsimdT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEB90A40, 0xFFBF0ED0, sizeConstraints, InstName.VrintnVfp, T.VrintnVfpT1, IsaVersion.v80, InstFlags.None), + new(0xFEB90940, 0xFFBF0FD0, sizeConstraints, InstName.VrintnVfp, T.VrintnVfpT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFFBA0780, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintpAsimd, T.VrintpAsimdT1, IsaVersion.v80, InstFlags.None), + new(0xFFB60780, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintpAsimd, T.VrintpAsimdT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFEBA0A40, 0xFFBF0ED0, sizeConstraints, InstName.VrintpVfp, T.VrintpVfpT1, IsaVersion.v80, InstFlags.None), + new(0xFEBA0940, 0xFFBF0FD0, sizeConstraints, InstName.VrintpVfp, T.VrintpVfpT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEB60A40, 0xFFBF0ED0, InstName.VrintrVfp, T.VrintrVfpT1, IsaVersion.v80, InstFlags.None), + new(0xEEB60940, 0xFFBF0FD0, InstName.VrintrVfp, T.VrintrVfpT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFFBA0480, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintxAsimd, T.VrintxAsimdT1, IsaVersion.v80, InstFlags.None), + new(0xFFB60480, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintxAsimd, T.VrintxAsimdT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEB70A40, 0xFFBF0ED0, InstName.VrintxVfp, T.VrintxVfpT1, IsaVersion.v80, InstFlags.None), + new(0xEEB70940, 0xFFBF0FD0, InstName.VrintxVfp, T.VrintxVfpT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFFBA0580, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintzAsimd, T.VrintzAsimdT1, IsaVersion.v80, InstFlags.None), + new(0xFFB60580, 0xFFBF0F90, qvdQvmConstraints, InstName.VrintzAsimd, T.VrintzAsimdT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEEB60AC0, 0xFFBF0ED0, InstName.VrintzVfp, T.VrintzVfpT1, IsaVersion.v80, InstFlags.None), + new(0xEEB609C0, 0xFFBF0FD0, InstName.VrintzVfp, T.VrintzVfpT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF000500, 0xEF800F10, qvdQvmQvnConstraints, InstName.Vrshl, T.VrshlT1, IsaVersion.v80, InstFlags.None), + new(0xEF800210, 0xEF800F10, imm6lQvdQvmConstraints, InstName.Vrshr, T.VrshrT1, IsaVersion.v80, InstFlags.None), + new(0xEF800850, 0xFF800FD0, imm6VmConstraints, InstName.Vrshrn, T.VrshrnT1, IsaVersion.v80, InstFlags.None), + new(0xFFB30480, 0xFFB30E90, qvdQvmSizeSizeConstraints, InstName.Vrsqrte, T.VrsqrteT1, IsaVersion.v80, InstFlags.None), + new(0xEF200F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vrsqrts, T.VrsqrtsT1, IsaVersion.v80, InstFlags.None), + new(0xEF300F10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vrsqrts, T.VrsqrtsT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF800310, 0xEF800F10, imm6lQvdQvmConstraints, InstName.Vrsra, T.VrsraT1, IsaVersion.v80, InstFlags.None), + new(0xFF800600, 0xFF800F50, sizeVnVmConstraints, InstName.Vrsubhn, T.VrsubhnT1, IsaVersion.v80, InstFlags.None), + new(0xFC200D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vsdot, T.VsdotT1, IsaVersion.v82, IsaFeature.FeatDotprod, InstFlags.None), + new(0xFE200D00, 0xFFB00F10, qvdQvnConstraints, InstName.VsdotS, T.VsdotST1, IsaVersion.v82, IsaFeature.FeatDotprod, InstFlags.None), + new(0xFE000A00, 0xFF800E50, sizeConstraints, InstName.Vsel, T.VselT1, IsaVersion.v80, InstFlags.None), + new(0xFE000900, 0xFF800F50, sizeConstraints, InstName.Vsel, T.VselT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF800A10, 0xEF800FD0, imm6VdImm6Imm6Imm6Constraints, InstName.Vshll, T.VshllT1, IsaVersion.v80, InstFlags.None), + new(0xFFB20300, 0xFFB30FD0, sizeVdConstraints2, InstName.Vshll, T.VshllT2, IsaVersion.v80, InstFlags.None), + new(0xEF800510, 0xFF800F10, imm6lQvdQvmConstraints, InstName.VshlI, T.VshlIT1, IsaVersion.v80, InstFlags.None), + new(0xEF000400, 0xEF800F10, qvdQvmQvnConstraints, InstName.VshlR, T.VshlRT1, IsaVersion.v80, InstFlags.None), + new(0xEF800010, 0xEF800F10, imm6lQvdQvmConstraints, InstName.Vshr, T.VshrT1, IsaVersion.v80, InstFlags.None), + new(0xEF800810, 0xFF800FD0, imm6VmConstraints, InstName.Vshrn, T.VshrnT1, IsaVersion.v80, InstFlags.None), + new(0xFF800510, 0xFF800F10, imm6lQvdQvmConstraints, InstName.Vsli, T.VsliT1, IsaVersion.v80, InstFlags.None), + new(0xFC200C40, 0xFFB00F50, vdVnVmConstraints, InstName.Vsmmla, T.VsmmlaT1, IsaVersion.v82, IsaFeature.FeatAa32i8mm, InstFlags.None), + new(0xEEB10AC0, 0xFFBF0ED0, InstName.Vsqrt, T.VsqrtT1, IsaVersion.v80, InstFlags.None), + new(0xEEB109C0, 0xFFBF0FD0, InstName.Vsqrt, T.VsqrtT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF800110, 0xEF800F10, imm6lQvdQvmConstraints, InstName.Vsra, T.VsraT1, IsaVersion.v80, InstFlags.None), + new(0xFF800410, 0xFF800F10, imm6lQvdQvmConstraints, InstName.Vsri, T.VsriT1, IsaVersion.v80, InstFlags.None), + new(0xF9800000, 0xFFB00F10, sizeConstraints2, InstName.Vst11, T.Vst11T1, IsaVersion.v80, InstFlags.None), + new(0xF9800400, 0xFFB00F20, sizeConstraints2, InstName.Vst11, T.Vst11T2, IsaVersion.v80, InstFlags.None), + new(0xF9800800, 0xFFB00F40, sizeIndexAlignIndexAlignConstraints, InstName.Vst11, T.Vst11T3, IsaVersion.v80, InstFlags.None), + new(0xF9000700, 0xFFB00F00, alignConstraints, InstName.Vst1M, T.Vst1MT1, IsaVersion.v80, InstFlags.None), + new(0xF9000A00, 0xFFB00F00, alignConstraints2, InstName.Vst1M, T.Vst1MT2, IsaVersion.v80, InstFlags.None), + new(0xF9000600, 0xFFB00F00, alignConstraints, InstName.Vst1M, T.Vst1MT3, IsaVersion.v80, InstFlags.None), + new(0xF9000200, 0xFFB00F00, InstName.Vst1M, T.Vst1MT4, IsaVersion.v80, InstFlags.None), + new(0xF9800100, 0xFFB00F00, sizeConstraints2, InstName.Vst21, T.Vst21T1, IsaVersion.v80, InstFlags.None), + new(0xF9800500, 0xFFB00F00, sizeConstraints2, InstName.Vst21, T.Vst21T2, IsaVersion.v80, InstFlags.None), + new(0xF9800900, 0xFFB00F20, sizeConstraints2, InstName.Vst21, T.Vst21T3, IsaVersion.v80, InstFlags.None), + new(0xF9000800, 0xFFB00E00, alignSizeConstraints, InstName.Vst2M, T.Vst2MT1, IsaVersion.v80, InstFlags.None), + new(0xF9000300, 0xFFB00F00, sizeConstraints3, InstName.Vst2M, T.Vst2MT2, IsaVersion.v80, InstFlags.None), + new(0xF9800200, 0xFFB00F10, sizeConstraints2, InstName.Vst31, T.Vst31T1, IsaVersion.v80, InstFlags.None), + new(0xF9800600, 0xFFB00F10, sizeConstraints2, InstName.Vst31, T.Vst31T2, IsaVersion.v80, InstFlags.None), + new(0xF9800A00, 0xFFB00F30, sizeConstraints2, InstName.Vst31, T.Vst31T3, IsaVersion.v80, InstFlags.None), + new(0xF9000400, 0xFFB00E00, sizeAlignConstraints, InstName.Vst3M, T.Vst3MT1, IsaVersion.v80, InstFlags.None), + new(0xF9800300, 0xFFB00F00, sizeConstraints2, InstName.Vst41, T.Vst41T1, IsaVersion.v80, InstFlags.None), + new(0xF9800700, 0xFFB00F00, sizeConstraints2, InstName.Vst41, T.Vst41T2, IsaVersion.v80, InstFlags.None), + new(0xF9800B00, 0xFFB00F00, sizeIndexAlignConstraints, InstName.Vst41, T.Vst41T3, IsaVersion.v80, InstFlags.None), + new(0xF9000000, 0xFFB00E00, sizeConstraints3, InstName.Vst4M, T.Vst4MT1, IsaVersion.v80, InstFlags.None), + new(0xEC000B00, 0xFE100F01, puwPwPuwPuwConstraints, InstName.Vstm, T.VstmT1, IsaVersion.v80, InstFlags.WBack), + new(0xEC000A00, 0xFE100F00, puwPwPuwPuwConstraints, InstName.Vstm, T.VstmT2, IsaVersion.v80, InstFlags.WBack), + new(0xED000A00, 0xFF300E00, InstName.Vstr, T.VstrT1, IsaVersion.v80, InstFlags.None), + new(0xED000900, 0xFF300F00, InstName.Vstr, T.VstrT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEF800600, 0xFF800F50, sizeVnVmConstraints, InstName.Vsubhn, T.VsubhnT1, IsaVersion.v80, InstFlags.None), + new(0xEF800200, 0xEF800F50, sizeVdOpvnConstraints, InstName.Vsubl, T.VsublT1, IsaVersion.v80, InstFlags.None), + new(0xEF800300, 0xEF800F50, sizeVdOpvnConstraints, InstName.Vsubw, T.VsubwT1, IsaVersion.v80, InstFlags.None), + new(0xEF200D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VsubF, T.VsubFT1, IsaVersion.v80, InstFlags.None), + new(0xEF300D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.VsubF, T.VsubFT1, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xEE300A40, 0xFFB00E50, InstName.VsubF, T.VsubFT2, IsaVersion.v80, InstFlags.None), + new(0xEE300940, 0xFFB00F50, InstName.VsubF, T.VsubFT2, IsaVersion.v80, IsaFeature.FeatFp16, InstFlags.None), + new(0xFF000800, 0xFF800F10, qvdQvnQvmConstraints, InstName.VsubI, T.VsubIT1, IsaVersion.v80, InstFlags.None), + new(0xFE800D10, 0xFFB00F10, qvdQvnConstraints, InstName.VsudotS, T.VsudotST1, IsaVersion.v82, IsaFeature.FeatAa32i8mm, InstFlags.None), + new(0xFFB20000, 0xFFBF0F90, qvdQvmConstraints, InstName.Vswp, T.VswpT1, IsaVersion.v80, InstFlags.None), + new(0xFFB00800, 0xFFB00C10, InstName.Vtbl, T.VtblT1, IsaVersion.v80, InstFlags.None), + new(0xFFB20080, 0xFFB30F90, sizeQvdQvmConstraints, InstName.Vtrn, T.VtrnT1, IsaVersion.v80, InstFlags.None), + new(0xEF000810, 0xFF800F10, qvdQvnQvmSizeConstraints, InstName.Vtst, T.VtstT1, IsaVersion.v80, InstFlags.None), + new(0xFC200D10, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vudot, T.VudotT1, IsaVersion.v82, IsaFeature.FeatDotprod, InstFlags.None), + new(0xFE200D10, 0xFFB00F10, qvdQvnConstraints, InstName.VudotS, T.VudotST1, IsaVersion.v82, IsaFeature.FeatDotprod, InstFlags.None), + new(0xFC200C50, 0xFFB00F50, vdVnVmConstraints, InstName.Vummla, T.VummlaT1, IsaVersion.v82, IsaFeature.FeatAa32i8mm, InstFlags.None), + new(0xFCA00D00, 0xFFB00F10, qvdQvnQvmConstraints, InstName.Vusdot, T.VusdotT1, IsaVersion.v82, IsaFeature.FeatAa32i8mm, InstFlags.None), + new(0xFE800D00, 0xFFB00F10, qvdQvnConstraints, InstName.VusdotS, T.VusdotST1, IsaVersion.v82, IsaFeature.FeatAa32i8mm, InstFlags.None), + new(0xFCA00C40, 0xFFB00F50, vdVnVmConstraints, InstName.Vusmmla, T.VusmmlaT1, IsaVersion.v82, IsaFeature.FeatAa32i8mm, InstFlags.None), + new(0xFFB20100, 0xFFB30F90, sizeQsizeQvdQvmConstraints, InstName.Vuzp, T.VuzpT1, IsaVersion.v80, InstFlags.None), + new(0xFFB20180, 0xFFB30F90, sizeQsizeQvdQvmConstraints, InstName.Vzip, T.VzipT1, IsaVersion.v80, InstFlags.None), + new(0xF3AF8002, 0xFFFFFFFF, InstName.Wfe, T.WfeT2, IsaVersion.v80, InstFlags.None), + new(0xF3AF8003, 0xFFFFFFFF, InstName.Wfi, T.WfiT2, IsaVersion.v80, InstFlags.None), + new(0xF3AF8001, 0xFFFFFFFF, InstName.Yield, T.YieldT2, IsaVersion.v80, InstFlags.None), + }; + + _table = new(insts); + } + + public static bool TryGetMeta(uint encoding, IsaVersion version, IsaFeature features, out InstMeta meta) + { + if (_table.TryFind(encoding, version, features, out InstInfoForTable info)) + { + meta = info.Meta; + + return true; + } + + meta = new(InstName.Udf, T.UdfA1, IsaVersion.v80, IsaFeature.None, InstFlags.None); + + return false; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/MultiBlock.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/MultiBlock.cs new file mode 100644 index 00000000..ca25057f --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/MultiBlock.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + class MultiBlock + { + public readonly List Blocks; + public readonly bool HasHostCall; + public readonly bool HasHostCallSkipContext; + public readonly bool IsTruncated; + + public MultiBlock(List blocks) + { + Blocks = blocks; + + Block block = blocks[0]; + + HasHostCall = block.HasHostCall; + HasHostCallSkipContext = block.HasHostCallSkipContext; + + for (int index = 1; index < blocks.Count; index++) + { + block = blocks[index]; + + HasHostCall |= block.HasHostCall; + HasHostCallSkipContext |= block.HasHostCallSkipContext; + } + + block = blocks[^1]; + + IsTruncated = block.IsTruncated; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/PendingBranch.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/PendingBranch.cs new file mode 100644 index 00000000..8f48cd07 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/PendingBranch.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + readonly struct PendingBranch + { + public readonly BranchType BranchType; + public readonly uint TargetAddress; + public readonly uint NextAddress; + public readonly InstName Name; + public readonly int WriterPointer; + + public PendingBranch(BranchType branchType, uint targetAddress, uint nextAddress, InstName name, int writerPointer) + { + BranchType = branchType; + TargetAddress = targetAddress; + NextAddress = nextAddress; + Name = name; + WriterPointer = writerPointer; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/RegisterAllocator.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/RegisterAllocator.cs new file mode 100644 index 00000000..4a3f03b8 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/RegisterAllocator.cs @@ -0,0 +1,170 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System; +using System.Numerics; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + class RegisterAllocator + { + public const int MaxTemps = 1; + + private uint _gprMask; + private uint _fpSimdMask; + + public int FixedContextRegister { get; } + public int FixedPageTableRegister { get; } + + public uint UsedGprsMask { get; private set; } + public uint UsedFpSimdMask { get; private set; } + + public RegisterAllocator() + { + _gprMask = ushort.MaxValue; + _fpSimdMask = ushort.MaxValue; + + FixedContextRegister = AllocateTempRegisterWithPreferencing(); + FixedPageTableRegister = AllocateTempRegisterWithPreferencing(); + } + + public void MarkGprAsUsed(int index) + { + UsedGprsMask |= 1u << index; + } + + public void MarkFpSimdAsUsed(int index) + { + UsedFpSimdMask |= 1u << index; + } + + public void MarkFpSimdRangeAsUsed(int index, int count) + { + UsedFpSimdMask |= (uint.MaxValue >> (32 - count)) << index; + } + + public Operand RemapGprRegister(int index) + { + MarkGprAsUsed(index); + + return new Operand(OperandKind.Register, OperandType.I32, (ulong)index); + } + + public Operand RemapFpRegister(int index, bool isFP32) + { + MarkFpSimdAsUsed(index); + + return new Operand(OperandKind.Register, isFP32 ? OperandType.FP32 : OperandType.FP64, (ulong)index); + } + + public Operand RemapSimdRegister(int index) + { + MarkFpSimdAsUsed(index); + + return new Operand(OperandKind.Register, OperandType.V128, (ulong)index); + } + + public Operand RemapSimdRegister(int index, int count) + { + MarkFpSimdRangeAsUsed(index, count); + + return new Operand(OperandKind.Register, OperandType.V128, (ulong)index); + } + + public void EnsureTempGprRegisters(int count) + { + if (count != 0) + { + Span registers = stackalloc int[count]; + + for (int index = 0; index < count; index++) + { + registers[index] = AllocateTempGprRegister(); + } + + for (int index = 0; index < count; index++) + { + FreeTempGprRegister(registers[index]); + } + } + } + + public int AllocateTempGprRegister() + { + int index = AllocateTempRegister(ref _gprMask, AbiConstants.ReservedRegsMask); + + MarkGprAsUsed(index); + + return index; + } + + private int AllocateTempRegisterWithPreferencing() + { + int firstCalleeSaved = BitOperations.TrailingZeroCount(~_gprMask & AbiConstants.GprCalleeSavedRegsMask); + if (firstCalleeSaved < 32) + { + uint regMask = 1u << firstCalleeSaved; + if ((regMask & AbiConstants.ReservedRegsMask) == 0) + { + _gprMask |= regMask; + UsedGprsMask |= regMask; + + return firstCalleeSaved; + } + } + + return AllocateTempRegister(ref _gprMask, AbiConstants.ReservedRegsMask); + } + + public int AllocateTempFpSimdRegister() + { + int index = AllocateTempRegister(ref _fpSimdMask, 0); + + MarkFpSimdAsUsed(index); + + return index; + } + + public ScopedRegister AllocateTempGprRegisterScoped() + { + return new(this, new(OperandKind.Register, OperandType.I32, (ulong)AllocateTempGprRegister())); + } + + public ScopedRegister AllocateTempFpRegisterScoped(bool isFP32) + { + return new(this, new(OperandKind.Register, isFP32 ? OperandType.FP32 : OperandType.FP64, (ulong)AllocateTempFpSimdRegister())); + } + + public ScopedRegister AllocateTempSimdRegisterScoped() + { + return new(this, new(OperandKind.Register, OperandType.V128, (ulong)AllocateTempFpSimdRegister())); + } + + public void FreeTempGprRegister(int index) + { + FreeTempRegister(ref _gprMask, index); + } + + public void FreeTempFpSimdRegister(int index) + { + FreeTempRegister(ref _fpSimdMask, index); + } + + private static int AllocateTempRegister(ref uint mask, uint reservedMask) + { + int index = BitOperations.TrailingZeroCount(~(mask | reservedMask)); + if (index == sizeof(uint) * 8) + { + throw new InvalidOperationException("No free registers."); + } + + mask |= 1u << index; + + return index; + } + + private static void FreeTempRegister(ref uint mask, int index) + { + mask &= ~(1u << index); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/RegisterUtils.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/RegisterUtils.cs new file mode 100644 index 00000000..8fbdeb73 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/RegisterUtils.cs @@ -0,0 +1,109 @@ +using System; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + static class RegisterUtils + { + public const int SpRegister = 13; + public const int LrRegister = 14; + public const int PcRegister = 15; + + private const int RmBit = 0; + private const int RdRtBit = 12; + private const int RdHiRnBit = 16; + + private const int RdRtT16Bit = 16; + private const int RdRtT16AltBit = 24; + + private const int RdRt2RdHiT32Bit = 8; + private const int RdT32AltBit = 0; + private const int RtRdLoT32Bit = 12; + + public static int ExtractRt(uint encoding) + { + return (int)(encoding >> RdRtBit) & 0xf; + } + + public static int ExtractRt2(uint encoding) + { + return (int)GetRt2((uint)ExtractRt(encoding)); + } + + public static int ExtractRd(InstFlags flags, uint encoding) + { + return flags.HasFlag(InstFlags.Rd16) ? ExtractRn(encoding) : ExtractRd(encoding); + } + + public static int ExtractRd(uint encoding) + { + return (int)(encoding >> RdRtBit) & 0xf; + } + + public static int ExtractRdHi(uint encoding) + { + return (int)(encoding >> RdHiRnBit) & 0xf; + } + + public static int ExtractRn(uint encoding) + { + return (int)(encoding >> RdHiRnBit) & 0xf; + } + + public static int ExtractRm(uint encoding) + { + return (int)(encoding >> RmBit) & 0xf; + } + + public static uint GetRt2(uint rt) + { + return Math.Min(rt + 1, PcRegister); + } + + public static int ExtractRdn(InstFlags flags, uint encoding) + { + if (flags.HasFlag(InstFlags.Dn)) + { + return ((int)(encoding >> RdRtT16Bit) & 7) | (int)((encoding >> 4) & 8); + } + else + { + return ExtractRdT16(flags, encoding); + } + } + + public static int ExtractRdT16(InstFlags flags, uint encoding) + { + return flags.HasFlag(InstFlags.Rd16) ? (int)(encoding >> RdRtT16AltBit) & 7 : (int)(encoding >> RdRtT16Bit) & 7; + } + + public static int ExtractRtT16(InstFlags flags, uint encoding) + { + return flags.HasFlag(InstFlags.Rd16) ? (int)(encoding >> RdRtT16AltBit) & 7 : (int)(encoding >> RdRtT16Bit) & 7; + } + + public static int ExtractRdT32(InstFlags flags, uint encoding) + { + return flags.HasFlag(InstFlags.Rd16) ? (int)(encoding >> RdT32AltBit) & 0xf : (int)(encoding >> RdRt2RdHiT32Bit) & 0xf; + } + + public static int ExtractRdLoT32(uint encoding) + { + return (int)(encoding >> RtRdLoT32Bit) & 0xf; + } + + public static int ExtractRdHiT32(uint encoding) + { + return (int)(encoding >> RdRt2RdHiT32Bit) & 0xf; + } + + public static int ExtractRtT32(uint encoding) + { + return (int)(encoding >> RtRdLoT32Bit) & 0xf; + } + + public static int ExtractRt2T32(uint encoding) + { + return (int)(encoding >> RdRt2RdHiT32Bit) & 0xf; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/ScopedRegister.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/ScopedRegister.cs new file mode 100644 index 00000000..18b1416e --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/ScopedRegister.cs @@ -0,0 +1,39 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using System; + +namespace Ryujinx.Cpu.LightningJit.Arm32 +{ + readonly struct ScopedRegister : IDisposable + { + private readonly RegisterAllocator _registerAllocator; + private readonly Operand _operand; + private readonly bool _isAllocated; + + public readonly Operand Operand => _operand; + public readonly bool IsAllocated => _isAllocated; + + public ScopedRegister(RegisterAllocator registerAllocator, Operand operand, bool isAllocated = true) + { + _registerAllocator = registerAllocator; + _operand = operand; + _isAllocated = isAllocated; + } + + public readonly void Dispose() + { + if (!_isAllocated) + { + return; + } + + if (_operand.Type.IsInteger()) + { + _registerAllocator.FreeTempGprRegister(_operand.AsInt32()); + } + else + { + _registerAllocator.FreeTempFpSimdRegister(_operand.AsInt32()); + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs new file mode 100644 index 00000000..a668b577 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs @@ -0,0 +1,808 @@ +using ARMeilleure.Common; +using ARMeilleure.Memory; +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class Compiler + { + public const uint UsableGprsMask = 0x7fff; + public const uint UsableFpSimdMask = 0xffff; + public const uint UsablePStateMask = 0xf0000000; + + private const int Encodable26BitsOffsetLimit = 0x2000000; + + private readonly struct Context + { + public readonly CodeWriter Writer; + public readonly RegisterAllocator RegisterAllocator; + public readonly MemoryManagerType MemoryManagerType; + public readonly TailMerger TailMerger; + public readonly AddressTable FuncTable; + public readonly IntPtr DispatchStubPointer; + + private readonly RegisterSaveRestore _registerSaveRestore; + private readonly IntPtr _pageTablePointer; + + public Context( + CodeWriter writer, + RegisterAllocator registerAllocator, + MemoryManagerType mmType, + TailMerger tailMerger, + AddressTable funcTable, + RegisterSaveRestore registerSaveRestore, + IntPtr dispatchStubPointer, + IntPtr pageTablePointer) + { + Writer = writer; + RegisterAllocator = registerAllocator; + MemoryManagerType = mmType; + TailMerger = tailMerger; + FuncTable = funcTable; + _registerSaveRestore = registerSaveRestore; + DispatchStubPointer = dispatchStubPointer; + _pageTablePointer = pageTablePointer; + } + + public readonly int GetReservedStackOffset() + { + return _registerSaveRestore.GetReservedStackOffset(); + } + + public readonly void WritePrologueAt(int instructionPointer) + { + CodeWriter writer = new(); + Assembler asm = new(writer); + + _registerSaveRestore.WritePrologue(ref asm); + + // If needed, set up the fixed registers with the pointers we will use. + // First one is the context pointer (passed as first argument), + // second one is the page table or address space base, it is at a fixed memory location and considered constant. + + if (RegisterAllocator.FixedContextRegister != 0) + { + asm.Mov(Register(RegisterAllocator.FixedContextRegister), Register(0)); + } + + asm.Mov(Register(RegisterAllocator.FixedPageTableRegister), (ulong)_pageTablePointer); + + LoadFromContext(ref asm); + + // Write the prologue at the specified position in our writer. + Writer.WriteInstructionsAt(instructionPointer, writer); + } + + public readonly void WriteEpilogueWithoutContext() + { + Assembler asm = new(Writer); + + _registerSaveRestore.WriteEpilogue(ref asm); + } + + public void LoadFromContext() + { + Assembler asm = new(Writer); + + LoadFromContext(ref asm); + } + + private void LoadFromContext(ref Assembler asm) + { + LoadGprFromContext(ref asm, RegisterAllocator.UsedGprsMask & UsableGprsMask, NativeContextOffsets.GprBaseOffset); + LoadFpSimdFromContext(ref asm, RegisterAllocator.UsedFpSimdMask & UsableFpSimdMask, NativeContextOffsets.FpSimdBaseOffset); + LoadPStateFromContext(ref asm, UsablePStateMask, NativeContextOffsets.FlagsBaseOffset); + } + + public void StoreToContext() + { + Assembler asm = new(Writer); + + StoreToContext(ref asm); + } + + private void StoreToContext(ref Assembler asm) + { + StoreGprToContext(ref asm, RegisterAllocator.UsedGprsMask & UsableGprsMask, NativeContextOffsets.GprBaseOffset); + StoreFpSimdToContext(ref asm, RegisterAllocator.UsedFpSimdMask & UsableFpSimdMask, NativeContextOffsets.FpSimdBaseOffset); + StorePStateToContext(ref asm, UsablePStateMask, NativeContextOffsets.FlagsBaseOffset); + } + + private void LoadGprFromContext(ref Assembler asm, uint mask, int baseOffset) + { + Operand contextPtr = Register(RegisterAllocator.FixedContextRegister); + + while (mask != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + int offset = baseOffset + reg * 8; + + if (reg < 31 && (mask & (2u << reg)) != 0 && offset < RegisterSaveRestore.Encodable9BitsOffsetLimit) + { + mask &= ~(3u << reg); + + asm.LdpRiUn(Register(reg), Register(reg + 1), contextPtr, offset); + } + else + { + mask &= ~(1u << reg); + + asm.LdrRiUn(Register(reg), contextPtr, offset); + } + } + } + + private void LoadFpSimdFromContext(ref Assembler asm, uint mask, int baseOffset) + { + Operand contextPtr = Register(RegisterAllocator.FixedContextRegister); + + while (mask != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + int offset = baseOffset + reg * 16; + + mask &= ~(1u << reg); + + asm.LdrRiUn(Register(reg, OperandType.V128), contextPtr, offset); + } + } + + private void LoadPStateFromContext(ref Assembler asm, uint mask, int baseOffset) + { + if (mask == 0) + { + return; + } + + Operand contextPtr = Register(RegisterAllocator.FixedContextRegister); + + using ScopedRegister tempRegister = RegisterAllocator.AllocateTempGprRegisterScoped(); + + asm.LdrRiUn(tempRegister.Operand, contextPtr, baseOffset); + asm.MsrNzcv(tempRegister.Operand); + } + + private void StoreGprToContext(ref Assembler asm, uint mask, int baseOffset) + { + Operand contextPtr = Register(RegisterAllocator.FixedContextRegister); + + while (mask != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + int offset = baseOffset + reg * 8; + + if (reg < 31 && (mask & (2u << reg)) != 0 && offset < RegisterSaveRestore.Encodable9BitsOffsetLimit) + { + mask &= ~(3u << reg); + + asm.StpRiUn(Register(reg), Register(reg + 1), contextPtr, offset); + } + else + { + mask &= ~(1u << reg); + + asm.StrRiUn(Register(reg), contextPtr, offset); + } + } + } + + private void StoreFpSimdToContext(ref Assembler asm, uint mask, int baseOffset) + { + Operand contextPtr = Register(RegisterAllocator.FixedContextRegister); + + while (mask != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + int offset = baseOffset + reg * 16; + + mask &= ~(1u << reg); + + asm.StrRiUn(Register(reg, OperandType.V128), contextPtr, offset); + } + } + + private void StorePStateToContext(ref Assembler asm, uint mask, int baseOffset) + { + if (mask == 0) + { + return; + } + + Operand contextPtr = Register(RegisterAllocator.FixedContextRegister); + + using ScopedRegister tempRegister = RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempRegister2 = RegisterAllocator.AllocateTempGprRegisterScoped(); + + asm.LdrRiUn(tempRegister.Operand, contextPtr, baseOffset); + asm.MrsNzcv(tempRegister2.Operand); + asm.And(tempRegister.Operand, tempRegister.Operand, InstEmitCommon.Const(0xfffffff)); + asm.Orr(tempRegister.Operand, tempRegister.Operand, tempRegister2.Operand); + asm.StrRiUn(tempRegister.Operand, contextPtr, baseOffset); + } + } + + public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, AddressTable funcTable, IntPtr dispatchStubPtr, bool isThumb) + { + MultiBlock multiBlock = Decoder.DecodeMulti(cpuPreset, memoryManager, address, isThumb); + + Dictionary targets = new(); + + CodeWriter writer = new(); + RegisterAllocator regAlloc = new(); + Assembler asm = new(writer); + CodeGenContext cgContext = new(writer, asm, regAlloc, memoryManager.Type, isThumb); + ArmCondition lastCondition = ArmCondition.Al; + int lastConditionIp = 0; + + // Required for load/store to context. + regAlloc.EnsureTempGprRegisters(2); + + ulong pc = address; + + for (int blockIndex = 0; blockIndex < multiBlock.Blocks.Count; blockIndex++) + { + Block block = multiBlock.Blocks[blockIndex]; + + Debug.Assert(block.Address == pc); + + targets.Add(pc, writer.InstructionPointer); + + for (int index = 0; index < block.Instructions.Count; index++) + { + InstInfo instInfo = block.Instructions[index]; + + if (index < block.Instructions.Count - 1) + { + cgContext.SetNextInstruction(block.Instructions[index + 1]); + } + else + { + cgContext.SetNextInstruction(default); + } + + SetConditionalStart(cgContext, ref lastCondition, ref lastConditionIp, instInfo.Name, instInfo.Flags, instInfo.Encoding); + + if (block.IsLoopEnd && index == block.Instructions.Count - 1) + { + // If this is a loop, the code might run for a long time uninterrupted. + // We insert a "sync point" here to ensure the loop can be interrupted if needed. + + cgContext.AddPendingSyncPoint(); + + asm.B(0); + } + + cgContext.SetPc((uint)pc); + + instInfo.EmitFunc(cgContext, instInfo.Encoding); + + if (cgContext.ConsumeNzcvModified()) + { + ForceConditionalEnd(cgContext, ref lastCondition, lastConditionIp); + } + + cgContext.UpdateItState(); + + pc += instInfo.Flags.HasFlag(InstFlags.Thumb16) ? 2UL : 4UL; + } + + if (Decoder.WritesToPC(block.Instructions[^1].Encoding, block.Instructions[^1].Name, block.Instructions[^1].Flags, block.IsThumb)) + { + // If the block ends with a PC register write, then we have a branch from register. + + InstEmitCommon.SetThumbFlag(cgContext, regAlloc.RemapGprRegister(RegisterUtils.PcRegister)); + + cgContext.AddPendingIndirectBranch(block.Instructions[^1].Name, RegisterUtils.PcRegister); + + asm.B(0); + } + + ForceConditionalEnd(cgContext, ref lastCondition, lastConditionIp); + } + + int reservedStackSize = 0; + + if (multiBlock.HasHostCall) + { + reservedStackSize = CalculateStackSizeForCallSpill(regAlloc.UsedGprsMask, regAlloc.UsedFpSimdMask, UsablePStateMask); + } + else if (multiBlock.HasHostCallSkipContext) + { + reservedStackSize = 2 * sizeof(ulong); // Context and page table pointers. + } + + RegisterSaveRestore rsr = new( + regAlloc.UsedGprsMask & AbiConstants.GprCalleeSavedRegsMask, + regAlloc.UsedFpSimdMask & AbiConstants.FpSimdCalleeSavedRegsMask, + OperandType.FP64, + multiBlock.HasHostCall || multiBlock.HasHostCallSkipContext, + reservedStackSize); + + TailMerger tailMerger = new(); + + Context context = new(writer, regAlloc, memoryManager.Type, tailMerger, funcTable, rsr, dispatchStubPtr, memoryManager.PageTablePointer); + + InstInfo lastInstruction = multiBlock.Blocks[^1].Instructions[^1]; + bool lastInstIsConditional = GetCondition(lastInstruction, isThumb) != ArmCondition.Al; + + if (multiBlock.IsTruncated || lastInstIsConditional || lastInstruction.Name.IsCall() || IsConditionalBranch(lastInstruction)) + { + WriteTailCallConstant(context, ref asm, (uint)pc); + } + + IEnumerable pendingBranches = cgContext.GetPendingBranches(); + + foreach (PendingBranch pendingBranch in pendingBranches) + { + RewriteBranchInstructionWithTarget(context, pendingBranch, targets); + } + + tailMerger.WriteReturn(writer, context.WriteEpilogueWithoutContext); + + context.WritePrologueAt(0); + + return new(writer.AsByteSpan(), (int)(pc - address)); + } + + private static int CalculateStackSizeForCallSpill(uint gprUseMask, uint fpSimdUseMask, uint pStateUseMask) + { + // Note that we don't discard callee saved FP/SIMD register because only the lower 64 bits is callee saved, + // so if the function is using the full register, that won't be enough. + // We could do better, but it's likely not worth it since this case happens very rarely in practice. + + return BitOperations.PopCount(gprUseMask & ~AbiConstants.GprCalleeSavedRegsMask) * 8 + + BitOperations.PopCount(fpSimdUseMask) * 16 + + (pStateUseMask != 0 ? 8 : 0); + } + + private static void SetConditionalStart( + CodeGenContext context, + ref ArmCondition condition, + ref int instructionPointer, + InstName name, + InstFlags flags, + uint encoding) + { + if (!context.ConsumeItCondition(out ArmCondition currentCond)) + { + currentCond = GetCondition(name, flags, encoding, context.IsThumb); + } + + if (currentCond != condition) + { + WriteConditionalEnd(context, condition, instructionPointer); + + condition = currentCond; + + if (currentCond != ArmCondition.Al) + { + instructionPointer = context.CodeWriter.InstructionPointer; + context.Arm64Assembler.B(currentCond.Invert(), 0); + } + } + } + + private static bool IsConditionalBranch(in InstInfo instInfo) + { + return instInfo.Name == InstName.B && (ArmCondition)(instInfo.Encoding >> 28) != ArmCondition.Al; + } + + private static ArmCondition GetCondition(in InstInfo instInfo, bool isThumb) + { + return GetCondition(instInfo.Name, instInfo.Flags, instInfo.Encoding, isThumb); + } + + private static ArmCondition GetCondition(InstName name, InstFlags flags, uint encoding, bool isThumb) + { + // For branch, we handle conditional execution on the instruction itself. + bool hasCond = flags.HasFlag(InstFlags.Cond) && !CanHandleConditionalInstruction(name, encoding, isThumb); + + return hasCond ? (ArmCondition)(encoding >> 28) : ArmCondition.Al; + } + + private static bool CanHandleConditionalInstruction(InstName name, uint encoding, bool isThumb) + { + if (name == InstName.B) + { + return true; + } + + // We can use CSEL for conditional MOV from registers, as long the instruction is not setting flags. + // We don't handle thumb right now because the condition comes from the IT block which would be more complicated to handle. + if (name == InstName.MovR && !isThumb && (encoding & (1u << 20)) == 0) + { + return true; + } + + return false; + } + + private static void ForceConditionalEnd(CodeGenContext context, ref ArmCondition condition, int instructionPointer) + { + WriteConditionalEnd(context, condition, instructionPointer); + + condition = ArmCondition.Al; + } + + private static void WriteConditionalEnd(CodeGenContext context, ArmCondition condition, int instructionPointer) + { + if (condition != ArmCondition.Al) + { + int delta = context.CodeWriter.InstructionPointer - instructionPointer; + uint branchInst = context.CodeWriter.ReadInstructionAt(instructionPointer) | (((uint)delta & 0x7ffff) << 5); + Debug.Assert((int)((branchInst & ~0x1fu) << 8) >> 11 == delta * 4); + + context.CodeWriter.WriteInstructionAt(instructionPointer, branchInst); + } + } + + private static void RewriteBranchInstructionWithTarget(in Context context, in PendingBranch pendingBranch, Dictionary targets) + { + switch (pendingBranch.BranchType) + { + case BranchType.Branch: + RewriteBranchInstructionWithTarget(context, pendingBranch.Name, pendingBranch.TargetAddress, pendingBranch.WriterPointer, targets); + break; + case BranchType.Call: + RewriteCallInstructionWithTarget(context, pendingBranch.TargetAddress, pendingBranch.NextAddress, pendingBranch.WriterPointer); + break; + case BranchType.IndirectBranch: + RewriteIndirectBranchInstructionWithTarget(context, pendingBranch.Name, pendingBranch.TargetAddress, pendingBranch.WriterPointer); + break; + case BranchType.TableBranchByte: + case BranchType.TableBranchHalfword: + RewriteTableBranchInstructionWithTarget( + context, + pendingBranch.BranchType == BranchType.TableBranchHalfword, + pendingBranch.TargetAddress, + pendingBranch.NextAddress, + pendingBranch.WriterPointer); + break; + case BranchType.IndirectCall: + RewriteIndirectCallInstructionWithTarget(context, pendingBranch.TargetAddress, pendingBranch.NextAddress, pendingBranch.WriterPointer); + break; + case BranchType.SyncPoint: + case BranchType.SoftwareInterrupt: + case BranchType.ReadCntpct: + RewriteHostCall(context, pendingBranch.Name, pendingBranch.BranchType, pendingBranch.TargetAddress, pendingBranch.NextAddress, pendingBranch.WriterPointer); + break; + default: + Debug.Fail($"Invalid branch type '{pendingBranch.BranchType}'"); + break; + } + } + + private static void RewriteBranchInstructionWithTarget(in Context context, InstName name, uint targetAddress, int branchIndex, Dictionary targets) + { + CodeWriter writer = context.Writer; + Assembler asm = new(writer); + + int delta; + int targetIndex; + uint encoding = writer.ReadInstructionAt(branchIndex); + + if (encoding == 0x14000000) + { + // Unconditional branch. + + if (targets.TryGetValue(targetAddress, out targetIndex)) + { + delta = targetIndex - branchIndex; + + if (delta >= -Encodable26BitsOffsetLimit && delta < Encodable26BitsOffsetLimit) + { + writer.WriteInstructionAt(branchIndex, encoding | (uint)(delta & 0x3ffffff)); + + return; + } + } + + targetIndex = writer.InstructionPointer; + delta = targetIndex - branchIndex; + + writer.WriteInstructionAt(branchIndex, encoding | (uint)(delta & 0x3ffffff)); + WriteTailCallConstant(context, ref asm, targetAddress); + } + else + { + // Conditional branch. + + uint branchMask = 0x7ffff; + int branchMax = (int)(branchMask + 1) / 2; + + if (targets.TryGetValue(targetAddress, out targetIndex)) + { + delta = targetIndex - branchIndex; + + if (delta >= -branchMax && delta < branchMax) + { + writer.WriteInstructionAt(branchIndex, encoding | (uint)((delta & branchMask) << 5)); + + return; + } + } + + targetIndex = writer.InstructionPointer; + delta = targetIndex - branchIndex; + + if (delta >= -branchMax && delta < branchMax) + { + writer.WriteInstructionAt(branchIndex, encoding | (uint)((delta & branchMask) << 5)); + WriteTailCallConstant(context, ref asm, targetAddress); + } + else + { + // If the branch target is too far away, we use a regular unconditional branch + // instruction instead which has a much higher range. + // We branch directly to the end of the function, where we put the conditional branch, + // and then branch back to the next instruction or return the branch target depending + // on the branch being taken or not. + + uint branchInst = 0x14000000u | ((uint)delta & 0x3ffffff); + Debug.Assert((int)(branchInst << 6) >> 4 == delta * 4); + + writer.WriteInstructionAt(branchIndex, branchInst); + + int movedBranchIndex = writer.InstructionPointer; + + writer.WriteInstruction(0u); // Placeholder + asm.B((branchIndex + 1 - writer.InstructionPointer) * 4); + + delta = writer.InstructionPointer - movedBranchIndex; + + writer.WriteInstructionAt(movedBranchIndex, encoding | (uint)((delta & branchMask) << 5)); + WriteTailCallConstant(context, ref asm, targetAddress); + } + } + + Debug.Assert(name == InstName.B || name == InstName.Cbnz, $"Unknown branch instruction \"{name}\"."); + } + + private static void RewriteCallInstructionWithTarget(in Context context, uint targetAddress, uint nextAddress, int branchIndex) + { + CodeWriter writer = context.Writer; + Assembler asm = new(writer); + + WriteBranchToCurrentPosition(context, branchIndex); + + asm.Mov(context.RegisterAllocator.RemapGprRegister(RegisterUtils.LrRegister), nextAddress); + + context.StoreToContext(); + InstEmitFlow.WriteCallWithGuestAddress( + writer, + ref asm, + context.RegisterAllocator, + context.TailMerger, + context.WriteEpilogueWithoutContext, + context.FuncTable, + context.DispatchStubPointer, + context.GetReservedStackOffset(), + nextAddress, + InstEmitCommon.Const((int)targetAddress)); + context.LoadFromContext(); + + // Branch back to the next instruction (after the call). + asm.B((branchIndex + 1 - writer.InstructionPointer) * 4); + } + + private static void RewriteIndirectBranchInstructionWithTarget(in Context context, InstName name, uint targetRegister, int branchIndex) + { + CodeWriter writer = context.Writer; + Assembler asm = new(writer); + + WriteBranchToCurrentPosition(context, branchIndex); + + using ScopedRegister target = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + asm.And(target.Operand, context.RegisterAllocator.RemapGprRegister((int)targetRegister), InstEmitCommon.Const(~1)); + + context.StoreToContext(); + + if ((name == InstName.Bx && targetRegister == RegisterUtils.LrRegister) || + name == InstName.Ldm || + name == InstName.Ldmda || + name == InstName.Ldmdb || + name == InstName.Ldmib || + name == InstName.Pop) + { + // Arm32 does not have a return instruction, instead returns are implemented + // either using BX LR (for leaf functions), or POP { ... PC }. + + asm.Mov(Register(0), target.Operand); + + context.TailMerger.AddUnconditionalReturn(writer, asm); + } + else + { + InstEmitFlow.WriteCallWithGuestAddress( + writer, + ref asm, + context.RegisterAllocator, + context.TailMerger, + context.WriteEpilogueWithoutContext, + context.FuncTable, + context.DispatchStubPointer, + context.GetReservedStackOffset(), + 0u, + target.Operand, + isTail: true); + } + } + + private static void RewriteTableBranchInstructionWithTarget(in Context context, bool halfword, uint rn, uint rm, int branchIndex) + { + CodeWriter writer = context.Writer; + Assembler asm = new(writer); + + WriteBranchToCurrentPosition(context, branchIndex); + + using ScopedRegister target = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + asm.Add( + target.Operand, + context.RegisterAllocator.RemapGprRegister((int)rn), + context.RegisterAllocator.RemapGprRegister((int)rm), + ArmShiftType.Lsl, + halfword ? 1 : 0); + + InstEmitMemory.WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, asm, target.Operand, target.Operand); + + if (halfword) + { + asm.LdrhRiUn(target.Operand, target.Operand, 0); + } + else + { + asm.LdrbRiUn(target.Operand, target.Operand, 0); + } + + asm.Add(target.Operand, context.RegisterAllocator.RemapGprRegister(RegisterUtils.PcRegister), target.Operand, ArmShiftType.Lsl, 1); + + context.StoreToContext(); + + InstEmitFlow.WriteCallWithGuestAddress( + writer, + ref asm, + context.RegisterAllocator, + context.TailMerger, + context.WriteEpilogueWithoutContext, + context.FuncTable, + context.DispatchStubPointer, + context.GetReservedStackOffset(), + 0u, + target.Operand, + isTail: true); + } + + private static void RewriteIndirectCallInstructionWithTarget(in Context context, uint targetRegister, uint nextAddress, int branchIndex) + { + CodeWriter writer = context.Writer; + Assembler asm = new(writer); + + WriteBranchToCurrentPosition(context, branchIndex); + + using ScopedRegister target = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + asm.And(target.Operand, context.RegisterAllocator.RemapGprRegister((int)targetRegister), InstEmitCommon.Const(~1)); + asm.Mov(context.RegisterAllocator.RemapGprRegister(RegisterUtils.LrRegister), nextAddress); + + context.StoreToContext(); + InstEmitFlow.WriteCallWithGuestAddress( + writer, + ref asm, + context.RegisterAllocator, + context.TailMerger, + context.WriteEpilogueWithoutContext, + context.FuncTable, + context.DispatchStubPointer, + context.GetReservedStackOffset(), + nextAddress & ~1u, + target.Operand); + context.LoadFromContext(); + + // Branch back to the next instruction (after the call). + asm.B((branchIndex + 1 - writer.InstructionPointer) * 4); + } + + private static void RewriteHostCall(in Context context, InstName name, BranchType type, uint imm, uint pc, int branchIndex) + { + CodeWriter writer = context.Writer; + Assembler asm = new(writer); + + uint encoding = writer.ReadInstructionAt(branchIndex); + int targetIndex = writer.InstructionPointer; + int delta = targetIndex - branchIndex; + + writer.WriteInstructionAt(branchIndex, encoding | (uint)(delta & 0x3ffffff)); + + switch (type) + { + case BranchType.SyncPoint: + InstEmitSystem.WriteSyncPoint( + context.Writer, + ref asm, + context.RegisterAllocator, + context.TailMerger, + context.GetReservedStackOffset(), + context.StoreToContext, + context.LoadFromContext); + break; + case BranchType.SoftwareInterrupt: + context.StoreToContext(); + switch (name) + { + case InstName.Bkpt: + InstEmitSystem.WriteBkpt(context.Writer, context.RegisterAllocator, context.TailMerger, context.GetReservedStackOffset(), pc, imm); + break; + case InstName.Svc: + InstEmitSystem.WriteSvc(context.Writer, context.RegisterAllocator, context.TailMerger, context.GetReservedStackOffset(), pc, imm); + break; + case InstName.Udf: + InstEmitSystem.WriteUdf(context.Writer, context.RegisterAllocator, context.TailMerger, context.GetReservedStackOffset(), pc, imm); + break; + } + context.LoadFromContext(); + break; + case BranchType.ReadCntpct: + InstEmitSystem.WriteReadCntpct(context.Writer, context.RegisterAllocator, context.GetReservedStackOffset(), (int)imm, (int)pc); + break; + default: + Debug.Fail($"Invalid branch type '{type}'"); + break; + } + + // Branch back to the next instruction. + asm.B((branchIndex + 1 - writer.InstructionPointer) * 4); + } + + private static void WriteBranchToCurrentPosition(in Context context, int branchIndex) + { + CodeWriter writer = context.Writer; + + int targetIndex = writer.InstructionPointer; + + if (branchIndex + 1 == targetIndex) + { + writer.RemoveLastInstruction(); + } + else + { + uint encoding = writer.ReadInstructionAt(branchIndex); + int delta = targetIndex - branchIndex; + + writer.WriteInstructionAt(branchIndex, encoding | (uint)(delta & 0x3ffffff)); + } + } + + private static void WriteTailCallConstant(in Context context, ref Assembler asm, uint address) + { + context.StoreToContext(); + InstEmitFlow.WriteCallWithGuestAddress( + context.Writer, + ref asm, + context.RegisterAllocator, + context.TailMerger, + context.WriteEpilogueWithoutContext, + context.FuncTable, + context.DispatchStubPointer, + context.GetReservedStackOffset(), + 0u, + InstEmitCommon.Const((int)address), + isTail: true); + } + + private static Operand Register(int register, OperandType type = OperandType.I64) + { + return new Operand(register, RegisterType.Integer, type); + } + + public static void PrintStats() + { + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmit.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmit.cs new file mode 100644 index 00000000..48891932 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmit.cs @@ -0,0 +1,8502 @@ +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + class InstEmit : IInstEmit + { + public static void AdcIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.AdcI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm12), inst.S != 0); + } + + public static void AdcIT1(CodeGenContext context, uint encoding) + { + InstIb26w1Sb20w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.AdcI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), inst.S != 0); + } + + public static void AdcRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.AdcR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.S != 0); + } + + public static void AdcRT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rdnb16w3 inst = new(encoding); + + InstEmitAlu.AdcR(context, inst.Rdn, inst.Rdn, inst.Rm, 0, 0, !context.InITBlock); + } + + public static void AdcRT2(CodeGenContext context, uint encoding) + { + InstSb20w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.AdcR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.S != 0); + } + + public static void AdcRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.AdcRr(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Rs, inst.S != 0); + } + + public static void AddIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.AddI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm12), inst.S != 0); + } + + public static void AddIT1(CodeGenContext context, uint encoding) + { + InstImm3b22w3Rnb19w3Rdb16w3 inst = new(encoding); + + InstEmitAlu.AddI(context, inst.Rd, inst.Rn, inst.Imm3, !context.InITBlock); + } + + public static void AddIT2(CodeGenContext context, uint encoding) + { + InstRdnb24w3Imm8b16w8 inst = new(encoding); + + InstEmitAlu.AddI(context, inst.Rdn, inst.Rdn, inst.Imm8, !context.InITBlock); + } + + public static void AddIT3(CodeGenContext context, uint encoding) + { + InstIb26w1Sb20w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.AddI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), inst.S != 0); + } + + public static void AddIT4(CodeGenContext context, uint encoding) + { + InstIb26w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.AddI(context, inst.Rd, inst.Rn, ImmUtils.CombineImmU12(inst.Imm8, inst.Imm3, inst.I), false); + } + + public static void AddRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.AddR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.S != 0); + } + + public static void AddRT1(CodeGenContext context, uint encoding) + { + InstRmb22w3Rnb19w3Rdb16w3 inst = new(encoding); + + InstEmitAlu.AddR(context, inst.Rd, inst.Rn, inst.Rm, 0, 0, !context.InITBlock); + } + + public static void AddRT2(CodeGenContext context, uint encoding) + { + InstDnb23w1Rmb19w4Rdnb16w3 inst = new(encoding); + + uint rdn = (inst.Dn << 3) | inst.Rdn; + + InstEmitAlu.AddR(context, rdn, rdn, inst.Rm, 0, 0, false); + } + + public static void AddRT3(CodeGenContext context, uint encoding) + { + InstSb20w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.AddR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.S != 0); + } + + public static void AddRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.AddRr(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Rs, inst.S != 0); + } + + public static void AddSpIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.AddI(context, inst.Rd, RegisterUtils.SpRegister, ImmUtils.ExpandImm(inst.Imm12), inst.S != 0); + } + + public static void AddSpIT1(CodeGenContext context, uint encoding) + { + InstRdb24w3Imm8b16w8 inst = new(encoding); + + InstEmitAlu.AddI(context, inst.Rd, RegisterUtils.SpRegister, inst.Imm8 << 2, false); + } + + public static void AddSpIT2(CodeGenContext context, uint encoding) + { + InstImm7b16w7 inst = new(encoding); + + InstEmitAlu.AddI(context, RegisterUtils.SpRegister, RegisterUtils.SpRegister, inst.Imm7 << 2, false); + } + + public static void AddSpIT3(CodeGenContext context, uint encoding) + { + InstIb26w1Sb20w1Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.AddI(context, inst.Rd, RegisterUtils.SpRegister, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), inst.S != 0); + } + + public static void AddSpIT4(CodeGenContext context, uint encoding) + { + InstIb26w1Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.AddI(context, inst.Rd, RegisterUtils.SpRegister, ImmUtils.CombineImmU12(inst.Imm8, inst.Imm3, inst.I), false); + } + + public static void AddSpRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.AddR(context, inst.Rd, RegisterUtils.SpRegister, inst.Rm, inst.Stype, inst.Imm5, inst.S != 0); + } + + public static void AddSpRT1(CodeGenContext context, uint encoding) + { + InstDmb23w1Rdmb16w3 inst = new(encoding); + + uint rdm = inst.Rdm | (inst.Dm << 3); + + InstEmitAlu.AddR(context, rdm, RegisterUtils.SpRegister, rdm, 0, 0, false); + } + + public static void AddSpRT2(CodeGenContext context, uint encoding) + { + InstRmb19w4 inst = new(encoding); + + InstEmitAlu.AddR(context, RegisterUtils.SpRegister, RegisterUtils.SpRegister, inst.Rm, 0, 0, false); + } + + public static void AddSpRT3(CodeGenContext context, uint encoding) + { + InstSb20w1Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.AddR(context, inst.Rd, RegisterUtils.SpRegister, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.S != 0); + } + + public static void AdrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.Adr(context, inst.Rd, ImmUtils.ExpandImm(inst.Imm12), true); + } + + public static void AdrA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.Adr(context, inst.Rd, ImmUtils.ExpandImm(inst.Imm12), false); + } + + public static void AdrT1(CodeGenContext context, uint encoding) + { + InstRdb24w3Imm8b16w8 inst = new(encoding); + + InstEmitAlu.Adr(context, inst.Rd, inst.Imm8 << 2, true); + } + + public static void AdrT2(CodeGenContext context, uint encoding) + { + InstIb26w1Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.Adr(context, inst.Rd, ImmUtils.CombineImmU12(inst.Imm8, inst.Imm3, inst.I), false); + } + + public static void AdrT3(CodeGenContext context, uint encoding) + { + InstIb26w1Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.Adr(context, inst.Rd, ImmUtils.CombineImmU12(inst.Imm8, inst.Imm3, inst.I), true); + } + + public static void AesdA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCrypto.Aesd(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void AesdT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCrypto.Aesd(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void AeseA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCrypto.Aese(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void AeseT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCrypto.Aese(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void AesimcA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCrypto.Aesimc(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void AesimcT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCrypto.Aesimc(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void AesmcA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCrypto.Aesmc(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void AesmcT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCrypto.Aesmc(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void AndIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.AndI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm12), ImmUtils.ExpandedImmRotated(inst.Imm12), inst.S != 0); + } + + public static void AndIT1(CodeGenContext context, uint encoding) + { + InstIb26w1Sb20w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.AndI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), ImmUtils.ExpandedImmRotated(inst.Imm8, inst.Imm3, inst.I), inst.S != 0); + } + + public static void AndRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.AndR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.S != 0); + } + + public static void AndRT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rdnb16w3 inst = new(encoding); + + InstEmitAlu.AndR(context, inst.Rdn, inst.Rdn, inst.Rm, 0, 0, !context.InITBlock); + } + + public static void AndRT2(CodeGenContext context, uint encoding) + { + InstSb20w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.AndR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.S != 0); + } + + public static void AndRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.AndRr(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Rs, inst.S != 0); + } + + public static void BA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Imm24b0w24 inst = new(encoding); + + InstEmitFlow.B(context, ImmUtils.ExtractSImm24Times4(inst.Imm24), (ArmCondition)inst.Cond); + } + + public static void BT1(CodeGenContext context, uint encoding) + { + InstCondb24w4Imm8b16w8 inst = new(encoding); + + InstEmitFlow.B(context, ImmUtils.ExtractT16SImm8Times2(inst.Imm8), (ArmCondition)inst.Cond); + } + + public static void BT2(CodeGenContext context, uint encoding) + { + InstImm11b16w11 inst = new(encoding); + + InstEmitFlow.B(context, ImmUtils.ExtractT16SImm11Times2(inst.Imm11), ArmCondition.Al); + } + + public static void BT3(CodeGenContext context, uint encoding) + { + InstSb26w1Condb22w4Imm6b16w6J1b13w1J2b11w1Imm11b0w11 inst = new(encoding); + + InstEmitFlow.B(context, ImmUtils.CombineSImm20Times2(inst.Imm11, inst.Imm6, inst.J1, inst.J2, inst.S), (ArmCondition)inst.Cond); + } + + public static void BT4(CodeGenContext context, uint encoding) + { + InstSb26w1Imm10b16w10J1b13w1J2b11w1Imm11b0w11 inst = new(encoding); + + InstEmitFlow.B(context, ImmUtils.CombineSImm24Times2(inst.Imm11, inst.Imm10, inst.J1, inst.J2, inst.S), ArmCondition.Al); + } + + public static void BfcA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Msbb16w5Rdb12w4Lsbb7w5 inst = new(encoding); + + InstEmitBit.Bfc(context, inst.Rd, inst.Lsb, inst.Msb); + } + + public static void BfcT1(CodeGenContext context, uint encoding) + { + InstImm3b12w3Rdb8w4Imm2b6w2Msbb0w5 inst = new(encoding); + + InstEmitBit.Bfc(context, inst.Rd, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.Msb); + } + + public static void BfiA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Msbb16w5Rdb12w4Lsbb7w5Rnb0w4 inst = new(encoding); + + InstEmitBit.Bfi(context, inst.Rd, inst.Rn, inst.Lsb, inst.Msb); + } + + public static void BfiT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Imm3b12w3Rdb8w4Imm2b6w2Msbb0w5 inst = new(encoding); + + InstEmitBit.Bfi(context, inst.Rd, inst.Rn, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.Msb); + } + + public static void BicIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.BicI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm12), ImmUtils.ExpandedImmRotated(inst.Imm12), inst.S != 0); + } + + public static void BicIT1(CodeGenContext context, uint encoding) + { + InstIb26w1Sb20w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.BicI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), ImmUtils.ExpandedImmRotated(inst.Imm8, inst.Imm3, inst.I), inst.S != 0); + } + + public static void BicRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.BicR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.S != 0); + } + + public static void BicRT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rdnb16w3 inst = new(encoding); + + InstEmitAlu.BicR(context, inst.Rdn, inst.Rdn, inst.Rm, 0, 0, !context.InITBlock); + } + + public static void BicRT2(CodeGenContext context, uint encoding) + { + InstSb20w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.BicR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.S != 0); + } + + public static void BicRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.BicRr(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Rs, inst.S != 0); + } + + public static void BkptA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Imm12b8w12Imm4b0w4 inst = new(encoding); + + InstEmitSystem.Bkpt(context, ImmUtils.CombineImmU16(inst.Imm12, inst.Imm4)); + } + + public static void BkptT1(CodeGenContext context, uint encoding) + { + InstImm8b16w8 inst = new(encoding); + + InstEmitSystem.Bkpt(context, inst.Imm8); + } + + public static void BlxRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rmb0w4 inst = new(encoding); + + InstEmitFlow.Blx(context, inst.Rm, false); + } + + public static void BlxRT1(CodeGenContext context, uint encoding) + { + InstRmb19w4 inst = new(encoding); + + InstEmitFlow.Blx(context, inst.Rm, true); + } + + public static void BlIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Imm24b0w24 inst = new(encoding); + + InstEmitFlow.Bl(context, ImmUtils.ExtractSImm24Times4(inst.Imm24), false, false); + } + + public static void BlIA2(CodeGenContext context, uint encoding) + { + InstHb24w1Imm24b0w24 inst = new(encoding); + + InstEmitFlow.Bl(context, ImmUtils.ExtractSImm24Times4(inst.Imm24) | ((int)inst.H << 1), false, true); + } + + public static void BlIT1(CodeGenContext context, uint encoding) + { + InstSb26w1Imm10b16w10J1b13w1J2b11w1Imm11b0w11 inst = new(encoding); + + InstEmitFlow.Bl(context, ImmUtils.CombineSImm24Times2(inst.Imm11, inst.Imm10, inst.J1, inst.J2, inst.S), true, true); + } + + public static void BlIT2(CodeGenContext context, uint encoding) + { + InstSb26w1Imm10hb16w10J1b13w1J2b11w1Imm10lb1w10Hb0w1 inst = new(encoding); + + InstEmitFlow.Bl(context, ImmUtils.CombineSImm24Times4(inst.Imm10l, inst.Imm10h, inst.J1, inst.J2, inst.S), true, false); + } + + public static void BxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rmb0w4 inst = new(encoding); + + InstEmitFlow.Bx(context, inst.Rm); + } + + public static void BxT1(CodeGenContext context, uint encoding) + { + InstRmb19w4 inst = new(encoding); + + InstEmitFlow.Bx(context, inst.Rm); + } + + public static void BxjA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rmb0w4 inst = new(encoding); + + InstEmitFlow.Bx(context, inst.Rm); + } + + public static void BxjT1(CodeGenContext context, uint encoding) + { + InstRmb16w4 inst = new(encoding); + + InstEmitFlow.Bx(context, inst.Rm); + } + + public static void CbnzT1(CodeGenContext context, uint encoding) + { + InstOpb27w1Ib25w1Imm5b19w5Rnb16w3 inst = new(encoding); + + InstEmitFlow.Cbnz(context, inst.Rn, (int)((inst.Imm5 << 1) | (inst.I << 6)), inst.Op != 0); + } + + public static void ClrbhbA1(CodeGenContext context, uint encoding) + { + _ = new InstCondb28w4(encoding); + + throw new NotImplementedException(); + } + + public static void ClrbhbT1(CodeGenContext context, uint encoding) + { + throw new NotImplementedException(); + } + + public static void ClrexA1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Clrex(); + } + + public static void ClrexT1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Clrex(); + } + + public static void ClzA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitBit.Clz(context, inst.Rd, inst.Rm); + } + + public static void ClzT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitBit.Clz(context, inst.Rd, inst.Rm); + } + + public static void CmnIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.CmnI(context, inst.Rn, ImmUtils.ExpandImm(inst.Imm12)); + } + + public static void CmnIT1(CodeGenContext context, uint encoding) + { + InstIb26w1Rnb16w4Imm3b12w3Imm8b0w8 inst = new(encoding); + + InstEmitAlu.CmnI(context, inst.Rn, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I)); + } + + public static void CmnRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.CmnR(context, inst.Rn, inst.Rm, inst.Stype, inst.Imm5); + } + + public static void CmnRT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rnb16w3 inst = new(encoding); + + InstEmitAlu.CmnR(context, inst.Rn, inst.Rm, 0, 0); + } + + public static void CmnRT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Imm3b12w3Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.CmnR(context, inst.Rn, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3)); + } + + public static void CmnRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.CmnRr(context, inst.Rn, inst.Rm, inst.Stype, inst.Rs); + } + + public static void CmpIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.CmpI(context, inst.Rn, ImmUtils.ExpandImm(inst.Imm12)); + } + + public static void CmpIT1(CodeGenContext context, uint encoding) + { + InstRnb24w3Imm8b16w8 inst = new(encoding); + + InstEmitAlu.CmpI(context, inst.Rn, inst.Imm8); + } + + public static void CmpIT2(CodeGenContext context, uint encoding) + { + InstIb26w1Rnb16w4Imm3b12w3Imm8b0w8 inst = new(encoding); + + InstEmitAlu.CmpI(context, inst.Rn, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I)); + } + + public static void CmpRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.CmpR(context, inst.Rn, inst.Rm, inst.Stype, inst.Imm5); + } + + public static void CmpRT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rnb16w3 inst = new(encoding); + + InstEmitAlu.CmpR(context, inst.Rn, inst.Rm, 0, 0); + } + + public static void CmpRT2(CodeGenContext context, uint encoding) + { + InstNb23w1Rmb19w4Rnb16w3 inst = new(encoding); + + InstEmitAlu.CmpR(context, inst.Rn | (inst.N << 3), inst.Rm, 0, 0); + } + + public static void CmpRT3(CodeGenContext context, uint encoding) + { + InstRnb16w4Imm3b12w3Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.CmpR(context, inst.Rn, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3)); + } + + public static void CmpRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.CmpRr(context, inst.Rn, inst.Rm, inst.Stype, inst.Rs); + } + + public static void CpsA1(CodeGenContext context, uint encoding) + { + InstImodb18w2Mb17w1Ab8w1Ib7w1Fb6w1Modeb0w5 inst = new(encoding); + + InstEmitSystem.Cps(context, inst.Imod, inst.M, inst.A, inst.I, inst.F, inst.Mode); + } + + public static void CpsT1(CodeGenContext context, uint encoding) + { + InstImb20w1Ab18w1Ib17w1Fb16w1 inst = new(encoding); + + InstEmitSystem.Cps(context, inst.Im, 0, inst.A, inst.I, inst.F, 0); + } + + public static void CpsT2(CodeGenContext context, uint encoding) + { + InstImodb9w2Mb8w1Ab7w1Ib6w1Fb5w1Modeb0w5 inst = new(encoding); + + InstEmitSystem.Cps(context, inst.Imod, inst.M, inst.A, inst.I, inst.F, inst.Mode); + } + + public static void Crc32A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Szb21w2Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitCrc32.Crc32(context, inst.Rd, inst.Rn, inst.Rm, inst.Sz); + } + + public static void Crc32T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Szb4w2Rmb0w4 inst = new(encoding); + + InstEmitCrc32.Crc32(context, inst.Rd, inst.Rn, inst.Rm, inst.Sz); + } + + public static void Crc32cA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Szb21w2Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitCrc32.Crc32c(context, inst.Rd, inst.Rn, inst.Rm, inst.Sz); + } + + public static void Crc32cT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Szb4w2Rmb0w4 inst = new(encoding); + + InstEmitCrc32.Crc32c(context, inst.Rd, inst.Rn, inst.Rm, inst.Sz); + } + + public static void CsdbA1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Csdb(); + } + + public static void CsdbT1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Csdb(); + } + + public static void DbgA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Optionb0w4 inst = new(encoding); + + InstEmitSystem.Dbg(context, inst.Option); + } + + public static void DbgT1(CodeGenContext context, uint encoding) + { + InstOptionb0w4 inst = new(encoding); + + InstEmitSystem.Dbg(context, inst.Option); + } + + public static void Dcps1T1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void Dcps2T1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void Dcps3T1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void DmbA1(CodeGenContext context, uint encoding) + { + InstOptionb0w4 inst = new(encoding); + + context.Arm64Assembler.Dmb(inst.Option); + } + + public static void DmbT1(CodeGenContext context, uint encoding) + { + InstOptionb0w4 inst = new(encoding); + + context.Arm64Assembler.Dmb(inst.Option); + } + + public static void DsbA1(CodeGenContext context, uint encoding) + { + InstOptionb0w4 inst = new(encoding); + + context.Arm64Assembler.Dsb(inst.Option); + } + + public static void DsbT1(CodeGenContext context, uint encoding) + { + InstOptionb0w4 inst = new(encoding); + + context.Arm64Assembler.Dsb(inst.Option); + } + + public static void EorIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.EorI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm12), ImmUtils.ExpandedImmRotated(inst.Imm12), inst.S != 0); + } + + public static void EorIT1(CodeGenContext context, uint encoding) + { + InstIb26w1Sb20w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.EorI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), ImmUtils.ExpandedImmRotated(inst.Imm8, inst.Imm3, inst.I), inst.S != 0); + } + + public static void EorRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.EorR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.S != 0); + } + + public static void EorRT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rdnb16w3 inst = new(encoding); + + InstEmitAlu.EorR(context, inst.Rdn, inst.Rdn, inst.Rm, 0, 0, !context.InITBlock); + } + + public static void EorRT2(CodeGenContext context, uint encoding) + { + InstSb20w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.EorR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.S != 0); + } + + public static void EorRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.EorRr(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Rs, inst.S != 0); + } + + public static void EretA1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void EretT1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void EsbA1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Esb(); + } + + public static void EsbT1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Esb(); + } + + public static void FldmxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm871b1w7 inst = new(encoding); + + InstEmitNeonMemory.Vldm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Imm871, inst.U != 0, inst.W != 0, singleRegs: false); + } + + public static void FldmxT1(CodeGenContext context, uint encoding) + { + InstPb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm871b1w7 inst = new(encoding); + + InstEmitNeonMemory.Vldm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Imm871, inst.U != 0, inst.W != 0, singleRegs: false); + } + + public static void FstmxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm871b1w7 inst = new(encoding); + + InstEmitNeonMemory.Vstm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Imm871, inst.U != 0, inst.W != 0, singleRegs: false); + } + + public static void FstmxT1(CodeGenContext context, uint encoding) + { + InstPb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm871b1w7 inst = new(encoding); + + InstEmitNeonMemory.Vstm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Imm871, inst.U != 0, inst.W != 0, singleRegs: false); + } + + public static void HltA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Imm12b8w12Imm4b0w4 inst = new(encoding); + + InstEmitSystem.Hlt(context, ImmUtils.CombineImmU16(inst.Imm12, inst.Imm4)); + } + + public static void HltT1(CodeGenContext context, uint encoding) + { + InstImm6b16w6 inst = new(encoding); + + InstEmitSystem.Hlt(context, inst.Imm6); + } + + public static void HvcA1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void HvcT1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void IsbA1(CodeGenContext context, uint encoding) + { + InstOptionb0w4 inst = new(encoding); + + context.Arm64Assembler.Isb(inst.Option); + } + + public static void IsbT1(CodeGenContext context, uint encoding) + { + InstOptionb0w4 inst = new(encoding); + + context.Arm64Assembler.Isb(inst.Option); + } + + public static void ItT1(CodeGenContext context, uint encoding) + { + InstFirstcondb20w4Maskb16w4 inst = new(encoding); + + InstEmitFlow.It(context, inst.Firstcond, inst.Mask); + } + + public static void LdaA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Lda(context, inst.Rt, inst.Rn); + } + + public static void LdaT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Lda(context, inst.Rt, inst.Rn); + } + + public static void LdabA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldab(context, inst.Rt, inst.Rn); + } + + public static void LdabT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldab(context, inst.Rt, inst.Rn); + } + + public static void LdaexA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldaex(context, inst.Rt, inst.Rn); + } + + public static void LdaexT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldaex(context, inst.Rt, inst.Rn); + } + + public static void LdaexbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldaexb(context, inst.Rt, inst.Rn); + } + + public static void LdaexbT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldaexb(context, inst.Rt, inst.Rn); + } + + public static void LdaexdA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldaexd(context, inst.Rt, RegisterUtils.GetRt2(inst.Rt), inst.Rn); + } + + public static void LdaexdT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Rt2b8w4 inst = new(encoding); + + InstEmitMemory.Ldaexd(context, inst.Rt, inst.Rt2, inst.Rn); + } + + public static void LdaexhA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldaexh(context, inst.Rt, inst.Rn); + } + + public static void LdaexhT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldaexh(context, inst.Rt, inst.Rn); + } + + public static void LdahA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldah(context, inst.Rt, inst.Rn); + } + + public static void LdahT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldah(context, inst.Rt, inst.Rn); + } + + public static void LdcIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdcI(context, inst.Rn, (int)inst.Imm8 << 2, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdcIT1(CodeGenContext context, uint encoding) + { + InstPb24w1Ub23w1Wb21w1Rnb16w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdcI(context, inst.Rn, (int)inst.Imm8 << 2, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdcLA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdcL(context, inst.Imm8 << 2, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdcLT1(CodeGenContext context, uint encoding) + { + InstPb24w1Ub23w1Wb21w1Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdcL(context, inst.Imm8 << 2, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdmA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Wb21w1Rnb16w4RegisterListb0w16 inst = new(encoding); + + InstEmitMemory.Ldm(context, inst.Rn, inst.RegisterList, inst.W != 0); + } + + public static void LdmT1(CodeGenContext context, uint encoding) + { + InstRnb24w3RegisterListb16w8 inst = new(encoding); + + InstEmitMemory.Ldm(context, inst.Rn, inst.RegisterList, false); + } + + public static void LdmT2(CodeGenContext context, uint encoding) + { + InstWb21w1Rnb16w4Pb15w1Mb14w1RegisterListb0w14 inst = new(encoding); + + InstEmitMemory.Ldm(context, inst.Rn, ImmUtils.CombineRegisterList(inst.RegisterList, inst.M, inst.P), inst.W != 0); + } + + public static void LdmdaA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Wb21w1Rnb16w4RegisterListb0w16 inst = new(encoding); + + InstEmitMemory.Ldmda(context, inst.Rn, inst.RegisterList, inst.W != 0); + } + + public static void LdmdbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Wb21w1Rnb16w4RegisterListb0w16 inst = new(encoding); + + InstEmitMemory.Ldmdb(context, inst.Rn, inst.RegisterList, inst.W != 0); + } + + public static void LdmdbT1(CodeGenContext context, uint encoding) + { + InstWb21w1Rnb16w4Pb15w1Mb14w1RegisterListb0w14 inst = new(encoding); + + InstEmitMemory.Ldmdb(context, inst.Rn, ImmUtils.CombineRegisterList(inst.RegisterList, inst.M, inst.P), inst.W != 0); + } + + public static void LdmibA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Wb21w1Rnb16w4RegisterListb0w16 inst = new(encoding); + + InstEmitMemory.Ldmib(context, inst.Rn, inst.RegisterList, inst.W != 0); + } + + public static void LdmEA1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void LdmUA1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void LdrbtA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrbtI(context, inst.Rt, inst.Rn, (int)inst.Imm12, postIndex: true, inst.U != 0); + } + + public static void LdrbtA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrbtR(context, inst.Rt, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, postIndex: true, inst.U != 0); + } + + public static void LdrbtT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdrbtI(context, inst.Rt, inst.Rn, (int)inst.Imm8, postIndex: false, true); + } + + public static void LdrbIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrbI(context, inst.Rt, inst.Rn, (int)inst.Imm12, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrbIT1(CodeGenContext context, uint encoding) + { + InstImm5b22w5Rnb19w3Rtb16w3 inst = new(encoding); + + InstEmitMemory.LdrbI(context, inst.Rt, inst.Rn, (int)inst.Imm5, true, true, false); + } + + public static void LdrbIT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrbI(context, inst.Rt, inst.Rn, (int)inst.Imm12, true, true, false); + } + + public static void LdrbIT3(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Pb10w1Ub9w1Wb8w1Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdrbI(context, inst.Rt, inst.Rn, (int)inst.Imm8, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrbLA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrbL(context, inst.Rt, inst.Imm12, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrbLT1(CodeGenContext context, uint encoding) + { + InstUb23w1Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrbL(context, inst.Rt, inst.Imm12, true, inst.U != 0, false); + } + + public static void LdrbRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrbR(context, inst.Rt, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrbRT1(CodeGenContext context, uint encoding) + { + InstRmb22w3Rnb19w3Rtb16w3 inst = new(encoding); + + InstEmitMemory.LdrbR(context, inst.Rt, inst.Rn, inst.Rm, 0, 0, true, true, false); + } + + public static void LdrbRT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm2b4w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrbR(context, inst.Rt, inst.Rn, inst.Rm, 0, inst.Imm2, true, true, false); + } + + public static void LdrdIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm4hb8w4Imm4lb0w4 inst = new(encoding); + + InstEmitMemory.LdrdI(context, inst.Rt, RegisterUtils.GetRt2(inst.Rt), inst.Rn, ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrdIT1(CodeGenContext context, uint encoding) + { + InstPb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Rt2b8w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdrdI(context, inst.Rt, inst.Rt2, inst.Rn, inst.Imm8 << 2, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrdLA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rtb12w4Imm4hb8w4Imm4lb0w4 inst = new(encoding); + + InstEmitMemory.LdrdL(context, inst.Rt, RegisterUtils.GetRt2(inst.Rt), ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), true, inst.U != 0, false); + } + + public static void LdrdLT1(CodeGenContext context, uint encoding) + { + InstPb24w1Ub23w1Wb21w1Rtb12w4Rt2b8w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdrdL(context, inst.Rt, inst.Rt2, inst.Imm8, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrdRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrdR(context, inst.Rt, RegisterUtils.GetRt2(inst.Rt), inst.Rn, inst.Rm, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrexA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldrex(context, inst.Rt, inst.Rn); + } + + public static void LdrexT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.Ldrex(context, inst.Rt, inst.Rn); + } + + public static void LdrexbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldrexb(context, inst.Rt, inst.Rn); + } + + public static void LdrexbT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldrexb(context, inst.Rt, inst.Rn); + } + + public static void LdrexdA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldrexd(context, inst.Rt, RegisterUtils.GetRt2(inst.Rt), inst.Rn); + } + + public static void LdrexdT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Rt2b8w4 inst = new(encoding); + + InstEmitMemory.Ldrexd(context, inst.Rt, inst.Rt2, inst.Rn); + } + + public static void LdrexhA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldrexh(context, inst.Rt, inst.Rn); + } + + public static void LdrexhT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Ldrexh(context, inst.Rt, inst.Rn); + } + + public static void LdrhtA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm4hb8w4Imm4lb0w4 inst = new(encoding); + + InstEmitMemory.LdrhtI(context, inst.Rt, inst.Rn, (int)ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), postIndex: true, inst.U != 0); + } + + public static void LdrhtA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrhtR(context, inst.Rt, inst.Rn, inst.Rm, postIndex: true, inst.U != 0); + } + + public static void LdrhtT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdrhtI(context, inst.Rt, inst.Rn, (int)inst.Imm8, postIndex: false, true); + } + + public static void LdrhIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm4hb8w4Imm4lb0w4 inst = new(encoding); + + InstEmitMemory.LdrhI(context, inst.Rt, inst.Rn, (int)ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrhIT1(CodeGenContext context, uint encoding) + { + InstImm5b22w5Rnb19w3Rtb16w3 inst = new(encoding); + + InstEmitMemory.LdrhI(context, inst.Rt, inst.Rn, (int)inst.Imm5 << 1, true, true, false); + } + + public static void LdrhIT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrhI(context, inst.Rt, inst.Rn, (int)inst.Imm12, true, true, false); + } + + public static void LdrhIT3(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Pb10w1Ub9w1Wb8w1Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdrhI(context, inst.Rt, inst.Rn, (int)inst.Imm8, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrhLA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rtb12w4Imm4hb8w4Imm4lb0w4 inst = new(encoding); + + InstEmitMemory.LdrhL(context, inst.Rt, ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrhLT1(CodeGenContext context, uint encoding) + { + InstUb23w1Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrhL(context, inst.Rt, inst.Imm12, true, inst.U != 0, false); + } + + public static void LdrhRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrhR(context, inst.Rt, inst.Rn, inst.Rm, 0, 0, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrhRT1(CodeGenContext context, uint encoding) + { + InstRmb22w3Rnb19w3Rtb16w3 inst = new(encoding); + + InstEmitMemory.LdrhR(context, inst.Rt, inst.Rn, inst.Rm, 0, 0, true, true, false); + } + + public static void LdrhRT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm2b4w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrhR(context, inst.Rt, inst.Rn, inst.Rm, 0, inst.Imm2, true, true, false); + } + + public static void LdrsbtA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm4hb8w4Imm4lb0w4 inst = new(encoding); + + InstEmitMemory.LdrsbtI(context, inst.Rt, inst.Rn, (int)ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), postIndex: true, inst.U != 0); + } + + public static void LdrsbtA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrsbtR(context, inst.Rt, inst.Rn, inst.Rm, postIndex: true, inst.U != 0); + } + + public static void LdrsbtT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdrsbtI(context, inst.Rt, inst.Rn, (int)inst.Imm8, postIndex: false, true); + } + + public static void LdrsbIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm4hb8w4Imm4lb0w4 inst = new(encoding); + + InstEmitMemory.LdrsbI(context, inst.Rt, inst.Rn, (int)ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrsbIT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrsbI(context, inst.Rt, inst.Rn, (int)inst.Imm12, true, true, false); + } + + public static void LdrsbIT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Pb10w1Ub9w1Wb8w1Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdrsbI(context, inst.Rt, inst.Rn, (int)inst.Imm8, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrsbLA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rtb12w4Imm4hb8w4Imm4lb0w4 inst = new(encoding); + + InstEmitMemory.LdrsbL(context, inst.Rt, ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrsbLT1(CodeGenContext context, uint encoding) + { + InstUb23w1Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrsbL(context, inst.Rt, inst.Imm12, true, inst.U != 0, false); + } + + public static void LdrsbRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrsbR(context, inst.Rt, inst.Rn, inst.Rm, 0, 0, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrsbRT1(CodeGenContext context, uint encoding) + { + InstRmb22w3Rnb19w3Rtb16w3 inst = new(encoding); + + InstEmitMemory.LdrsbR(context, inst.Rt, inst.Rn, inst.Rm, 0, 0, true, true, false); + } + + public static void LdrsbRT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm2b4w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrsbR(context, inst.Rt, inst.Rn, inst.Rm, 0, inst.Imm2, true, true, false); + } + + public static void LdrshtA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm4hb8w4Imm4lb0w4 inst = new(encoding); + + InstEmitMemory.LdrshtI(context, inst.Rt, inst.Rn, (int)ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), postIndex: true, inst.U != 0); + } + + public static void LdrshtA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrshtR(context, inst.Rt, inst.Rn, inst.Rm, postIndex: true, inst.U != 0); + } + + public static void LdrshtT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdrshtI(context, inst.Rt, inst.Rn, (int)inst.Imm8, postIndex: false, true); + } + + public static void LdrshIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm4hb8w4Imm4lb0w4 inst = new(encoding); + + InstEmitMemory.LdrshI(context, inst.Rt, inst.Rn, (int)ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrshIT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrshI(context, inst.Rt, inst.Rn, (int)inst.Imm12, true, true, false); + } + + public static void LdrshIT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Pb10w1Ub9w1Wb8w1Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdrshI(context, inst.Rt, inst.Rn, (int)inst.Imm8, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrshLA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rtb12w4Imm4hb8w4Imm4lb0w4 inst = new(encoding); + + InstEmitMemory.LdrshL(context, inst.Rt, ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrshLT1(CodeGenContext context, uint encoding) + { + InstUb23w1Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrshL(context, inst.Rt, inst.Imm12, true, inst.U != 0, false); + } + + public static void LdrshRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrshR(context, inst.Rt, inst.Rn, inst.Rm, 0, 0, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrshRT1(CodeGenContext context, uint encoding) + { + InstRmb22w3Rnb19w3Rtb16w3 inst = new(encoding); + + InstEmitMemory.LdrshR(context, inst.Rt, inst.Rn, inst.Rm, 0, 0, true, true, false); + } + + public static void LdrshRT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm2b4w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrshR(context, inst.Rt, inst.Rn, inst.Rm, 0, inst.Imm2, true, true, false); + } + + public static void LdrtA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrtI(context, inst.Rt, inst.Rn, (int)inst.Imm12, postIndex: true, inst.U != 0); + } + + public static void LdrtA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrtR(context, inst.Rt, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, postIndex: true, inst.U != 0); + } + + public static void LdrtT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdrtI(context, inst.Rt, inst.Rn, (int)inst.Imm8, postIndex: false, true); + } + + public static void LdrIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrI(context, inst.Rt, inst.Rn, (int)inst.Imm12, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrIT1(CodeGenContext context, uint encoding) + { + InstImm5b22w5Rnb19w3Rtb16w3 inst = new(encoding); + + InstEmitMemory.LdrI(context, inst.Rt, inst.Rn, (int)inst.Imm5 << 2, true, true, false); + } + + public static void LdrIT2(CodeGenContext context, uint encoding) + { + InstRtb24w3Imm8b16w8 inst = new(encoding); + + InstEmitMemory.LdrI(context, inst.Rt, RegisterUtils.SpRegister, (int)inst.Imm8 << 2, true, true, false); + } + + public static void LdrIT3(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrI(context, inst.Rt, inst.Rn, (int)inst.Imm12, true, true, false); + } + + public static void LdrIT4(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Pb10w1Ub9w1Wb8w1Imm8b0w8 inst = new(encoding); + + InstEmitMemory.LdrI(context, inst.Rt, inst.Rn, (int)inst.Imm8, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrLA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrL(context, inst.Rt, inst.Imm12, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrLT1(CodeGenContext context, uint encoding) + { + InstRtb24w3Imm8b16w8 inst = new(encoding); + + InstEmitMemory.LdrL(context, inst.Rt, inst.Imm8 << 2, true, true, false); + } + + public static void LdrLT2(CodeGenContext context, uint encoding) + { + InstUb23w1Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.LdrL(context, inst.Rt, inst.Imm12, true, inst.U != 0, false); + } + + public static void LdrRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrR(context, inst.Rt, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void LdrRT1(CodeGenContext context, uint encoding) + { + InstRmb22w3Rnb19w3Rtb16w3 inst = new(encoding); + + InstEmitMemory.LdrR(context, inst.Rt, inst.Rn, inst.Rm, 0, 0, true, true, false); + } + + public static void LdrRT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm2b4w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.LdrR(context, inst.Rt, inst.Rn, inst.Rm, 0, inst.Imm2, true, true, false); + } + + public static void McrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Opc1b21w3Crnb16w4Rtb12w4Coproc0b8w1Opc2b5w3Crmb0w4 inst = new(encoding); + + InstEmitSystem.Mcr(context, encoding, inst.Coproc0 | 0xe, inst.Opc1, inst.Rt, inst.Crn, inst.Crm, inst.Opc2); + } + + public static void McrT1(CodeGenContext context, uint encoding) + { + InstOpc1b21w3Crnb16w4Rtb12w4Coproc0b8w1Opc2b5w3Crmb0w4 inst = new(encoding); + + InstEmitSystem.Mcr(context, encoding, inst.Coproc0 | 0xe, inst.Opc1, inst.Rt, inst.Crn, inst.Crm, inst.Opc2); + } + + public static void McrrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rt2b16w4Rtb12w4Coproc0b8w1Opc1b4w4Crmb0w4 inst = new(encoding); + + InstEmitSystem.Mcrr(context, encoding, inst.Coproc0 | 0xe, inst.Opc1, inst.Rt, inst.Crm); + } + + public static void McrrT1(CodeGenContext context, uint encoding) + { + InstRt2b16w4Rtb12w4Coproc0b8w1Opc1b4w4Crmb0w4 inst = new(encoding); + + InstEmitSystem.Mcrr(context, encoding, inst.Coproc0 | 0xe, inst.Opc1, inst.Rt, inst.Crm); + } + + public static void MlaA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdb16w4Rab12w4Rmb8w4Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Mla(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra); + } + + public static void MlaT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rab12w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Mla(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra); + } + + public static void MlsA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rab12w4Rmb8w4Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Mls(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra); + } + + public static void MlsT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rab12w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Mls(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra); + } + + public static void MovtA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Imm4b16w4Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMove.Movt(context, inst.Rd, ImmUtils.CombineImmU16(inst.Imm12, inst.Imm4)); + } + + public static void MovtT1(CodeGenContext context, uint encoding) + { + InstIb26w1Imm4b16w4Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitMove.Movt(context, inst.Rd, ImmUtils.CombineImmU16(inst.Imm8, inst.Imm3, inst.I, inst.Imm4)); + } + + public static void MovIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMove.MovI(context, inst.Rd, ImmUtils.ExpandImm(inst.Imm12), ImmUtils.ExpandedImmRotated(inst.Imm12), inst.S != 0); + } + + public static void MovIA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Imm4b16w4Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMove.MovI(context, inst.Rd, ImmUtils.CombineImmU16(inst.Imm12, inst.Imm4), false, false); + } + + public static void MovIT1(CodeGenContext context, uint encoding) + { + InstRdb24w3Imm8b16w8 inst = new(encoding); + + InstEmitMove.MovI(context, inst.Rd, inst.Imm8, false, !context.InITBlock); + } + + public static void MovIT2(CodeGenContext context, uint encoding) + { + InstIb26w1Sb20w1Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitMove.MovI(context, inst.Rd, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), ImmUtils.ExpandedImmRotated(inst.Imm8, inst.Imm3, inst.I), inst.S != 0); + } + + public static void MovIT3(CodeGenContext context, uint encoding) + { + InstIb26w1Imm4b16w4Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitMove.MovI(context, inst.Rd, ImmUtils.CombineImmU16(inst.Imm8, inst.Imm3, inst.I, inst.Imm4), false, false); + } + + public static void MovRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitMove.MovR(context, inst.Cond, inst.Rd, inst.Rm, inst.Stype, inst.Imm5, inst.S != 0); + } + + public static void MovRT1(CodeGenContext context, uint encoding) + { + InstDb23w1Rmb19w4Rdb16w3 inst = new(encoding); + + InstEmitMove.MovR(context, inst.Rd | (inst.D << 3), inst.Rm, 0, 0, false); + } + + public static void MovRT2(CodeGenContext context, uint encoding) + { + InstOpb27w2Imm5b22w5Rmb19w3Rdb16w3 inst = new(encoding); + + InstEmitMove.MovR(context, inst.Rd, inst.Rm, inst.Op, inst.Imm5, !context.InITBlock); + } + + public static void MovRT3(CodeGenContext context, uint encoding) + { + InstSb20w1Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitMove.MovR(context, inst.Rd, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.S != 0); + } + + public static void MovRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdb12w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitMove.MovRr(context, inst.Rd, inst.Rm, inst.Stype, inst.Rs, inst.S != 0); + } + + public static void MovRrT1(CodeGenContext context, uint encoding) + { + InstRsb19w3Rdmb16w3 inst = new(encoding); + + InstEmitMove.MovRr(context, inst.Rdm, inst.Rdm, ((encoding >> 7) & 2) | ((encoding >> 6) & 1), inst.Rs, !context.InITBlock); + } + + public static void MovRrT2(CodeGenContext context, uint encoding) + { + InstStypeb21w2Sb20w1Rmb16w4Rdb8w4Rsb0w4 inst = new(encoding); + + InstEmitMove.MovRr(context, inst.Rd, inst.Rm, inst.Stype, inst.Rs, inst.S != 0); + } + + public static void MrcA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Opc1b21w3Crnb16w4Rtb12w4Coproc0b8w1Opc2b5w3Crmb0w4 inst = new(encoding); + + InstEmitSystem.Mrc(context, encoding, inst.Coproc0 | 0xe, inst.Opc1, inst.Rt, inst.Crn, inst.Crm, inst.Opc2); + } + + public static void MrcT1(CodeGenContext context, uint encoding) + { + InstOpc1b21w3Crnb16w4Rtb12w4Coproc0b8w1Opc2b5w3Crmb0w4 inst = new(encoding); + + InstEmitSystem.Mrc(context, encoding, inst.Coproc0 | 0xe, inst.Opc1, inst.Rt, inst.Crn, inst.Crm, inst.Opc2); + } + + public static void MrrcA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rt2b16w4Rtb12w4Coproc0b8w1Opc1b4w4Crmb0w4 inst = new(encoding); + + InstEmitSystem.Mrrc(context, encoding, inst.Coproc0 | 0xe, inst.Opc1, inst.Rt, inst.Rt2, inst.Crm); + } + + public static void MrrcT1(CodeGenContext context, uint encoding) + { + InstRt2b16w4Rtb12w4Coproc0b8w1Opc1b4w4Crmb0w4 inst = new(encoding); + + InstEmitSystem.Mrrc(context, encoding, inst.Coproc0 | 0xe, inst.Opc1, inst.Rt, inst.Rt2, inst.Crm); + } + + public static void MrsA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rb22w1Rdb12w4 inst = new(encoding); + + InstEmitSystem.Mrs(context, inst.Rd, inst.R != 0); + } + + public static void MrsT1(CodeGenContext context, uint encoding) + { + InstRb20w1Rdb8w4 inst = new(encoding); + + InstEmitSystem.Mrs(context, inst.Rd, inst.R != 0); + } + + public static void MrsBrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rb22w1M1b16w4Rdb12w4Mb8w1 inst = new(encoding); + + InstEmitSystem.MrsBr(context, inst.Rd, inst.M1 | (inst.M << 4), inst.R != 0); + } + + public static void MrsBrT1(CodeGenContext context, uint encoding) + { + InstRb20w1M1b16w4Rdb8w4Mb4w1 inst = new(encoding); + + InstEmitSystem.MrsBr(context, inst.Rd, inst.M1 | (inst.M << 4), inst.R != 0); + } + + public static void MsrBrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rb22w1M1b16w4Mb8w1Rnb0w4 inst = new(encoding); + + InstEmitSystem.MsrBr(context, inst.Rn, inst.M1 | (inst.M << 4), inst.R != 0); + } + + public static void MsrBrT1(CodeGenContext context, uint encoding) + { + InstRb20w1Rnb16w4M1b8w4Mb4w1 inst = new(encoding); + + InstEmitSystem.MsrBr(context, inst.Rn, inst.M1 | (inst.M << 4), inst.R != 0); + } + + public static void MsrIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rb22w1Maskb16w4Imm12b0w12 inst = new(encoding); + + InstEmitSystem.MsrI(context, inst.Imm12, inst.Mask, inst.R != 0); + } + + public static void MsrRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rb22w1Maskb16w4Rnb0w4 inst = new(encoding); + + InstEmitSystem.MsrR(context, inst.Rn, inst.Mask, inst.R != 0); + } + + public static void MsrRT1(CodeGenContext context, uint encoding) + { + InstRb20w1Rnb16w4Maskb8w4 inst = new(encoding); + + InstEmitSystem.MsrR(context, inst.Rn, inst.Mask, inst.R != 0); + } + + public static void MulA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdb16w4Rmb8w4Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Mul(context, inst.Rd, inst.Rn, inst.Rm, inst.S != 0); + } + + public static void MulT1(CodeGenContext context, uint encoding) + { + InstRnb19w3Rdmb16w3 inst = new(encoding); + + InstEmitMultiply.Mul(context, inst.Rdm, inst.Rn, inst.Rdm, !context.InITBlock); + } + + public static void MulT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Mul(context, inst.Rd, inst.Rn, inst.Rm, false); + } + + public static void MvnIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMove.MvnI(context, inst.Rd, ImmUtils.ExpandImm(inst.Imm12), ImmUtils.ExpandedImmRotated(inst.Imm12), inst.S != 0); + } + + public static void MvnIT1(CodeGenContext context, uint encoding) + { + InstIb26w1Sb20w1Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitMove.MvnI(context, inst.Rd, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), ImmUtils.ExpandedImmRotated(inst.Imm8, inst.Imm3, inst.I), inst.S != 0); + } + + public static void MvnRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitMove.MvnR(context, inst.Rd, inst.Rm, inst.Stype, inst.Imm5, inst.S != 0); + } + + public static void MvnRT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rdb16w3 inst = new(encoding); + + InstEmitMove.MvnR(context, inst.Rd, inst.Rm, 0, 0, !context.InITBlock); + } + + public static void MvnRT2(CodeGenContext context, uint encoding) + { + InstSb20w1Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitMove.MvnR(context, inst.Rd, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.S != 0); + } + + public static void MvnRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdb12w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitMove.MvnRr(context, inst.Rd, inst.Rm, inst.Stype, inst.Rs, inst.S != 0); + } + + public static void NopA1(CodeGenContext context, uint encoding) + { + } + + public static void NopT1(CodeGenContext context, uint encoding) + { + } + + public static void NopT2(CodeGenContext context, uint encoding) + { + } + + public static void OrnIT1(CodeGenContext context, uint encoding) + { + InstIb26w1Sb20w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.OrnI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), ImmUtils.ExpandedImmRotated(inst.Imm8, inst.Imm3, inst.I), inst.S != 0); + } + + public static void OrnRT1(CodeGenContext context, uint encoding) + { + InstSb20w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.OrnR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.S != 0); + } + + public static void OrrIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.OrrI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm12), ImmUtils.ExpandedImmRotated(inst.Imm12), inst.S != 0); + } + + public static void OrrIT1(CodeGenContext context, uint encoding) + { + InstIb26w1Sb20w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.OrrI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), ImmUtils.ExpandedImmRotated(inst.Imm8, inst.Imm3, inst.I), inst.S != 0); + } + + public static void OrrRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.OrrR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.S != 0); + } + + public static void OrrRT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rdnb16w3 inst = new(encoding); + + InstEmitAlu.OrrR(context, inst.Rdn, inst.Rdn, inst.Rm, 0, 0, !context.InITBlock); + } + + public static void OrrRT2(CodeGenContext context, uint encoding) + { + InstSb20w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.OrrR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.S != 0); + } + + public static void OrrRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.OrrRr(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Rs, inst.S != 0); + } + + public static void PkhA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Imm5b7w5Tbb6w1Rmb0w4 inst = new(encoding); + + InstEmitMove.Pkh(context, inst.Rd, inst.Rn, inst.Rm, inst.Tb != 0, inst.Imm5); + } + + public static void PkhT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Imm3b12w3Rdb8w4Imm2b6w2Tbb5w1Rmb0w4 inst = new(encoding); + + InstEmitMove.Pkh(context, inst.Rd, inst.Rn, inst.Rm, inst.Tb != 0, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3)); + } + + public static void PldIA1(CodeGenContext context, uint encoding) + { + InstUb23w1Rb22w1Rnb16w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.PldI(context, inst.Rn, inst.Imm12, inst.U != 0, inst.R != 0); + } + + public static void PldIT1(CodeGenContext context, uint encoding) + { + InstWb21w1Rnb16w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.PldI(context, inst.Rn, inst.Imm12, true, inst.W == 0); + } + + public static void PldIT2(CodeGenContext context, uint encoding) + { + InstWb21w1Rnb16w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.PldI(context, inst.Rn, inst.Imm8, false, inst.W == 0); + } + + public static void PldLA1(CodeGenContext context, uint encoding) + { + InstUb23w1Imm12b0w12 inst = new(encoding); + + InstEmitMemory.PldL(context, inst.Imm12, inst.U != 0); + } + + public static void PldLT1(CodeGenContext context, uint encoding) + { + InstUb23w1Imm12b0w12 inst = new(encoding); + + InstEmitMemory.PldL(context, inst.Imm12, inst.U != 0); + } + + public static void PldRA1(CodeGenContext context, uint encoding) + { + InstUb23w1Rb22w1Rnb16w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.PldR(context, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.U != 0, inst.R != 0); + } + + public static void PldRT1(CodeGenContext context, uint encoding) + { + InstWb21w1Rnb16w4Imm2b4w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.PldR(context, inst.Rn, inst.Rm, 0, inst.Imm2, true, inst.W == 0); + } + + public static void PliIA1(CodeGenContext context, uint encoding) + { + InstUb23w1Rnb16w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.PliI(context, inst.Rn, inst.Imm12, inst.U != 0); + } + + public static void PliIT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.PliI(context, inst.Rn, inst.Imm12, true); + } + + public static void PliIT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.PliI(context, inst.Rn, inst.Imm8, false); + } + + public static void PliIT3(CodeGenContext context, uint encoding) + { + InstUb23w1Imm12b0w12 inst = new(encoding); + + InstEmitMemory.PliL(context, inst.Imm12, inst.U != 0); + } + + public static void PliRA1(CodeGenContext context, uint encoding) + { + InstUb23w1Rnb16w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.PliR(context, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.U != 0); + } + + public static void PliRT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Imm2b4w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.PliR(context, inst.Rn, inst.Rm, 0, inst.Imm2, true); + } + + public static void PopT1(CodeGenContext context, uint encoding) + { + InstPb24w1RegisterListb16w8 inst = new(encoding); + + InstEmitMemory.Ldm(context, RegisterUtils.SpRegister, inst.RegisterList | (inst.P << RegisterUtils.PcRegister), true); + } + + public static void PssbbA1(CodeGenContext context, uint encoding) + { + throw new NotImplementedException(); + } + + public static void PssbbT1(CodeGenContext context, uint encoding) + { + throw new NotImplementedException(); + } + + public static void PushT1(CodeGenContext context, uint encoding) + { + InstMb24w1RegisterListb16w8 inst = new(encoding); + + InstEmitMemory.Stmdb(context, RegisterUtils.SpRegister, inst.RegisterList | (inst.M << RegisterUtils.LrRegister), true); + } + + public static void QaddA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qadd(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void QaddT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qadd(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Qadd16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qadd16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Qadd16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qadd16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Qadd8A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qadd8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Qadd8T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qadd8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void QasxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qasx(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void QasxT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qasx(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void QdaddA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qdadd(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void QdaddT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qdadd(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void QdsubA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qdsub(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void QdsubT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qdsub(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void QsaxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qsax(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void QsaxT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qsax(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void QsubA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qsub(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void QsubT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qsub(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Qsub16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qsub16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Qsub16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qsub16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Qsub8A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qsub8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Qsub8T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Qsub8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void RbitA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitBit.Rbit(context, inst.Rd, inst.Rm); + } + + public static void RbitT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitBit.Rbit(context, inst.Rd, inst.Rm); + } + + public static void RevA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitBit.Rev(context, inst.Rd, inst.Rm); + } + + public static void RevT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rdb16w3 inst = new(encoding); + + InstEmitBit.Rev(context, inst.Rd, inst.Rm); + } + + public static void RevT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitBit.Rev(context, inst.Rd, inst.Rm); + } + + public static void Rev16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitBit.Rev16(context, inst.Rd, inst.Rm); + } + + public static void Rev16T1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rdb16w3 inst = new(encoding); + + InstEmitBit.Rev16(context, inst.Rd, inst.Rm); + } + + public static void Rev16T2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitBit.Rev16(context, inst.Rd, inst.Rm); + } + + public static void RevshA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitBit.Revsh(context, inst.Rd, inst.Rm); + } + + public static void RevshT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rdb16w3 inst = new(encoding); + + InstEmitBit.Revsh(context, inst.Rd, inst.Rm); + } + + public static void RevshT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitBit.Revsh(context, inst.Rd, inst.Rm); + } + + public static void RfeA1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void RfeT1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void RfeT2(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void RsbIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.RsbI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm12), inst.S != 0); + } + + public static void RsbIT1(CodeGenContext context, uint encoding) + { + InstRnb19w3Rdb16w3 inst = new(encoding); + + InstEmitAlu.RsbI(context, inst.Rd, inst.Rn, 0, !context.InITBlock); + } + + public static void RsbIT2(CodeGenContext context, uint encoding) + { + InstIb26w1Sb20w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.RsbI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), inst.S != 0); + } + + public static void RsbRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.RsbR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.S != 0); + } + + public static void RsbRT1(CodeGenContext context, uint encoding) + { + InstSb20w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.RsbR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.S != 0); + } + + public static void RsbRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.RsbRr(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Rs, inst.S != 0); + } + + public static void RscIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.RscI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm12), inst.S != 0); + } + + public static void RscRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.RscR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.S != 0); + } + + public static void RscRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.RscRr(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Rs, inst.S != 0); + } + + public static void Sadd16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Sadd16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Sadd16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Sadd16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Sadd8A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Sadd8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Sadd8T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Sadd8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void SasxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Sasx(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void SasxT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Sasx(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void SbA1(CodeGenContext context, uint encoding) + { + throw new NotImplementedException(); + } + + public static void SbT1(CodeGenContext context, uint encoding) + { + throw new NotImplementedException(); + } + + public static void SbcIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.SbcI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm12), inst.S != 0); + } + + public static void SbcIT1(CodeGenContext context, uint encoding) + { + InstIb26w1Sb20w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.SbcI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), inst.S != 0); + } + + public static void SbcRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.SbcR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.S != 0); + } + + public static void SbcRT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rdnb16w3 inst = new(encoding); + + InstEmitAlu.SbcR(context, inst.Rdn, inst.Rdn, inst.Rm, 0, 0, !context.InITBlock); + } + + public static void SbcRT2(CodeGenContext context, uint encoding) + { + InstSb20w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.SbcR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.S != 0); + } + + public static void SbcRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.SbcRr(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Rs, inst.S != 0); + } + + public static void SbfxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Widthm1b16w5Rdb12w4Lsbb7w5Rnb0w4 inst = new(encoding); + + InstEmitBit.Sbfx(context, inst.Rd, inst.Rn, inst.Lsb, inst.Widthm1); + } + + public static void SbfxT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Imm3b12w3Rdb8w4Imm2b6w2Widthm1b0w5 inst = new(encoding); + + InstEmitBit.Sbfx(context, inst.Rd, inst.Rn, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.Widthm1); + } + + public static void SdivA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rmb8w4Rnb0w4 inst = new(encoding); + + InstEmitDivide.Sdiv(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void SdivT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitDivide.Sdiv(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void SelA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Sel(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void SelT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Sel(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void SetendA1(CodeGenContext context, uint encoding) + { + InstEb9w1 inst = new(encoding); + + InstEmitSystem.Setend(context, inst.E != 0); + } + + public static void SetendT1(CodeGenContext context, uint encoding) + { + InstEb19w1 inst = new(encoding); + + InstEmitSystem.Setend(context, inst.E != 0); + } + + public static void SetpanA1(CodeGenContext context, uint encoding) + { + _ = new InstImm1b9w1(encoding); + + throw new NotImplementedException(); + } + + public static void SetpanT1(CodeGenContext context, uint encoding) + { + _ = new InstImm1b19w1(encoding); + + throw new NotImplementedException(); + } + + public static void SevA1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Sev(); + } + + public static void SevT1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Sev(); + } + + public static void SevT2(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Sev(); + } + + public static void SevlA1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Sevl(); + } + + public static void SevlT1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Sevl(); + } + + public static void SevlT2(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Sevl(); + } + + public static void Sha1cA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha1c(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void Sha1cT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha1c(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void Sha1hA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha1h(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void Sha1hT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha1h(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void Sha1mA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha1m(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void Sha1mT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha1m(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void Sha1pA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha1p(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void Sha1pT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha1p(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void Sha1su0A1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha1su0(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void Sha1su0T1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha1su0(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void Sha1su1A1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha1su1(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void Sha1su1T1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha1su1(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void Sha256hA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha256h(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void Sha256hT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha256h(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void Sha256h2A1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha256h2(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void Sha256h2T1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha256h2(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void Sha256su0A1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha256su0(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void Sha256su0T1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha256su0(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void Sha256su1A1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha256su1(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void Sha256su1T1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonHash.Sha256su1(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void Shadd16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Shadd16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Shadd16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Shadd16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Shadd8A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Shadd8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Shadd8T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Shadd8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void ShasxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Shasx(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void ShasxT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Shasx(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void ShsaxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Shsax(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void ShsaxT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Shsax(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Shsub16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Shsub16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Shsub16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Shsub16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Shsub8A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Shsub8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Shsub8T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Shsub8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void SmcA1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void SmcT1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void SmlabbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rab12w4Rmb8w4Mb6w1Nb5w1Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smlabb(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra, inst.N != 0, inst.M != 0); + } + + public static void SmlabbT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rab12w4Rdb8w4Nb5w1Mb4w1Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smlabb(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra, inst.N != 0, inst.M != 0); + } + + public static void SmladA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rab12w4Rmb8w4Mb5w1Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smlad(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra, inst.M != 0); + } + + public static void SmladT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rab12w4Rdb8w4Mb4w1Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smlad(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra, inst.M != 0); + } + + public static void SmlalA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdhib16w4Rdlob12w4Rmb8w4Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smlal(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm, inst.S != 0); + } + + public static void SmlalT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdlob12w4Rdhib8w4Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smlal(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm, false); + } + + public static void SmlalbbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdhib16w4Rdlob12w4Rmb8w4Mb6w1Nb5w1Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smlalbb(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm, inst.N != 0, inst.M != 0); + } + + public static void SmlalbbT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdlob12w4Rdhib8w4Nb5w1Mb4w1Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smlalbb(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm, inst.N != 0, inst.M != 0); + } + + public static void SmlaldA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdhib16w4Rdlob12w4Rmb8w4Mb5w1Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smlald(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm, inst.M != 0); + } + + public static void SmlaldT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdlob12w4Rdhib8w4Mb4w1Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smlald(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm, inst.M != 0); + } + + public static void SmlawbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rab12w4Rmb8w4Mb6w1Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smlawb(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra, inst.M != 0); + } + + public static void SmlawbT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rab12w4Rdb8w4Mb4w1Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smlawb(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra, inst.M != 0); + } + + public static void SmlsdA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rab12w4Rmb8w4Mb5w1Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smlsd(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra, inst.M != 0); + } + + public static void SmlsdT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rab12w4Rdb8w4Mb4w1Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smlsd(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra, inst.M != 0); + } + + public static void SmlsldA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdhib16w4Rdlob12w4Rmb8w4Mb5w1Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smlsld(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm, inst.M != 0); + } + + public static void SmlsldT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdlob12w4Rdhib8w4Mb4w1Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smlsld(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm, inst.M != 0); + } + + public static void SmmlaA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rab12w4Rmb8w4Rb5w1Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smmla(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra, inst.R != 0); + } + + public static void SmmlaT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rab12w4Rdb8w4Rb4w1Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smmla(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra, inst.R != 0); + } + + public static void SmmlsA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rab12w4Rmb8w4Rb5w1Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smmls(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra, inst.R != 0); + } + + public static void SmmlsT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rab12w4Rdb8w4Rb4w1Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smmls(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra, inst.R != 0); + } + + public static void SmmulA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rmb8w4Rb5w1Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smmul(context, inst.Rd, inst.Rn, inst.Rm, inst.R != 0); + } + + public static void SmmulT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rb4w1Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smmul(context, inst.Rd, inst.Rn, inst.Rm, inst.R != 0); + } + + public static void SmuadA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rmb8w4Mb5w1Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smuad(context, inst.Rd, inst.Rn, inst.Rm, inst.M != 0); + } + + public static void SmuadT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Mb4w1Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smuad(context, inst.Rd, inst.Rn, inst.Rm, inst.M != 0); + } + + public static void SmulbbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rmb8w4Mb6w1Nb5w1Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smulbb(context, inst.Rd, inst.Rn, inst.Rm, inst.N != 0, inst.M != 0); + } + + public static void SmulbbT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Nb5w1Mb4w1Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smulbb(context, inst.Rd, inst.Rn, inst.Rm, inst.N != 0, inst.M != 0); + } + + public static void SmullA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdhib16w4Rdlob12w4Rmb8w4Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smull(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm, inst.S != 0); + } + + public static void SmullT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdlob12w4Rdhib8w4Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smull(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm, false); + } + + public static void SmulwbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rmb8w4Mb6w1Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smulwb(context, inst.Rd, inst.Rn, inst.Rm, inst.M != 0); + } + + public static void SmulwbT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Mb4w1Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smulwb(context, inst.Rd, inst.Rn, inst.Rm, inst.M != 0); + } + + public static void SmusdA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rmb8w4Mb5w1Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Smusd(context, inst.Rd, inst.Rn, inst.Rm, inst.M != 0); + } + + public static void SmusdT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Mb4w1Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Smusd(context, inst.Rd, inst.Rn, inst.Rm, inst.M != 0); + } + + public static void SrsA1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void SrsT1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void SrsT2(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void SsatA1(CodeGenContext context, uint encoding) + { + InstCondb28w4SatImmb16w5Rdb12w4Imm5b7w5Shb6w1Rnb0w4 inst = new(encoding); + + InstEmitSaturate.Ssat(context, inst.Rd, inst.SatImm, inst.Rn, inst.Sh != 0, inst.Imm5); + } + + public static void SsatT1(CodeGenContext context, uint encoding) + { + InstShb21w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2SatImmb0w5 inst = new(encoding); + + InstEmitSaturate.Ssat(context, inst.Rd, inst.SatImm, inst.Rn, inst.Sh != 0, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3)); + } + + public static void Ssat16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4SatImmb16w4Rdb12w4Rnb0w4 inst = new(encoding); + + InstEmitSaturate.Ssat16(context, inst.Rd, inst.SatImm, inst.Rn); + } + + public static void Ssat16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4SatImmb0w4 inst = new(encoding); + + InstEmitSaturate.Ssat16(context, inst.Rd, inst.SatImm, inst.Rn); + } + + public static void SsaxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Ssax(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void SsaxT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Ssax(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void SsbbA1(CodeGenContext context, uint encoding) + { + throw new NotImplementedException(); + } + + public static void SsbbT1(CodeGenContext context, uint encoding) + { + throw new NotImplementedException(); + } + + public static void Ssub16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Ssub16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Ssub16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Ssub16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Ssub8A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Ssub8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Ssub8T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Ssub8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void StcA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.Stc(context, inst.Rn, (int)inst.Imm8 << 2, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void StcT1(CodeGenContext context, uint encoding) + { + InstPb24w1Ub23w1Wb21w1Rnb16w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.Stc(context, inst.Rn, (int)inst.Imm8 << 2, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void StlA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rtb0w4 inst = new(encoding); + + InstEmitMemory.Stl(context, inst.Rt, inst.Rn); + } + + public static void StlT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Stl(context, inst.Rt, inst.Rn); + } + + public static void StlbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rtb0w4 inst = new(encoding); + + InstEmitMemory.Stlb(context, inst.Rt, inst.Rn); + } + + public static void StlbT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Stlb(context, inst.Rt, inst.Rn); + } + + public static void StlexA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rtb0w4 inst = new(encoding); + + InstEmitMemory.Stlex(context, inst.Rd, inst.Rt, inst.Rn); + } + + public static void StlexT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Rdb0w4 inst = new(encoding); + + InstEmitMemory.Stlex(context, inst.Rd, inst.Rt, inst.Rn); + } + + public static void StlexbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rtb0w4 inst = new(encoding); + + InstEmitMemory.Stlexb(context, inst.Rd, inst.Rt, inst.Rn); + } + + public static void StlexbT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Rdb0w4 inst = new(encoding); + + InstEmitMemory.Stlexb(context, inst.Rd, inst.Rt, inst.Rn); + } + + public static void StlexdA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rtb0w4 inst = new(encoding); + + InstEmitMemory.Stlexd(context, inst.Rd, inst.Rt, RegisterUtils.GetRt2(inst.Rt), inst.Rn); + } + + public static void StlexdT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Rt2b8w4Rdb0w4 inst = new(encoding); + + InstEmitMemory.Stlexd(context, inst.Rd, inst.Rt, inst.Rt2, inst.Rn); + } + + public static void StlexhA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rtb0w4 inst = new(encoding); + + InstEmitMemory.Stlexh(context, inst.Rd, inst.Rt, inst.Rn); + } + + public static void StlexhT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Rdb0w4 inst = new(encoding); + + InstEmitMemory.Stlexh(context, inst.Rd, inst.Rt, inst.Rn); + } + + public static void StlhA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rtb0w4 inst = new(encoding); + + InstEmitMemory.Stlh(context, inst.Rt, inst.Rn); + } + + public static void StlhT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4 inst = new(encoding); + + InstEmitMemory.Stlh(context, inst.Rt, inst.Rn); + } + + public static void StmA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Wb21w1Rnb16w4RegisterListb0w16 inst = new(encoding); + + InstEmitMemory.Stm(context, inst.Rn, inst.RegisterList, inst.W != 0); + } + + public static void StmT1(CodeGenContext context, uint encoding) + { + InstRnb24w3RegisterListb16w8 inst = new(encoding); + + InstEmitMemory.Stm(context, inst.Rn, inst.RegisterList, false); + } + + public static void StmT2(CodeGenContext context, uint encoding) + { + InstWb21w1Rnb16w4Mb14w1RegisterListb0w14 inst = new(encoding); + + InstEmitMemory.Stm(context, inst.Rn, ImmUtils.CombineRegisterList(inst.RegisterList, inst.M), inst.W != 0); + } + + public static void StmdaA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Wb21w1Rnb16w4RegisterListb0w16 inst = new(encoding); + + InstEmitMemory.Stmda(context, inst.Rn, inst.RegisterList, inst.W != 0); + } + + public static void StmdbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Wb21w1Rnb16w4RegisterListb0w16 inst = new(encoding); + + InstEmitMemory.Stmdb(context, inst.Rn, inst.RegisterList, inst.W != 0); + } + + public static void StmdbT1(CodeGenContext context, uint encoding) + { + InstWb21w1Rnb16w4Mb14w1RegisterListb0w14 inst = new(encoding); + + InstEmitMemory.Stmdb(context, inst.Rn, ImmUtils.CombineRegisterList(inst.RegisterList, inst.M), inst.W != 0); + } + + public static void StmibA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Wb21w1Rnb16w4RegisterListb0w16 inst = new(encoding); + + InstEmitMemory.Stmib(context, inst.Rn, inst.RegisterList, inst.W != 0); + } + + public static void StmUA1(CodeGenContext context, uint encoding) + { + InstEmitSystem.PrivilegedInstruction(context, encoding); + } + + public static void StrbtA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.StrbtI(context, inst.Rt, inst.Rn, (int)inst.Imm12, postIndex: true, inst.U != 0); + } + + public static void StrbtA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.StrbtR(context, inst.Rt, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, postIndex: true, inst.U != 0); + } + + public static void StrbtT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.StrbtI(context, inst.Rt, inst.Rn, (int)inst.Imm8, postIndex: false, true); + } + + public static void StrbIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.StrbI(context, inst.Rt, inst.Rn, (int)inst.Imm12, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void StrbIT1(CodeGenContext context, uint encoding) + { + InstImm5b22w5Rnb19w3Rtb16w3 inst = new(encoding); + + InstEmitMemory.StrbI(context, inst.Rt, inst.Rn, (int)inst.Imm5, true, true, false); + } + + public static void StrbIT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.StrbI(context, inst.Rt, inst.Rn, (int)inst.Imm12, true, true, false); + } + + public static void StrbIT3(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Pb10w1Ub9w1Wb8w1Imm8b0w8 inst = new(encoding); + + InstEmitMemory.StrbI(context, inst.Rt, inst.Rn, (int)inst.Imm8, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void StrbRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.StrbR(context, inst.Rt, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void StrbRT1(CodeGenContext context, uint encoding) + { + InstRmb22w3Rnb19w3Rtb16w3 inst = new(encoding); + + InstEmitMemory.StrbR(context, inst.Rt, inst.Rn, inst.Rm, 0, 0, true, true, false); + } + + public static void StrbRT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm2b4w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.StrbR(context, inst.Rt, inst.Rn, inst.Rm, 0, inst.Imm2, true, true, false); + } + + public static void StrdIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm4hb8w4Imm4lb0w4 inst = new(encoding); + + InstEmitMemory.StrdI(context, inst.Rt, RegisterUtils.GetRt2(inst.Rt), inst.Rn, ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void StrdIT1(CodeGenContext context, uint encoding) + { + InstPb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Rt2b8w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.StrdI(context, inst.Rt, inst.Rt2, inst.Rn, inst.Imm8 << 2, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void StrdRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Rmb0w4 inst = new(encoding); + + InstEmitMemory.StrdR(context, inst.Rt, RegisterUtils.GetRt2(inst.Rt), inst.Rn, inst.Rm, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void StrexA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rtb0w4 inst = new(encoding); + + InstEmitMemory.Strex(context, inst.Rd, inst.Rt, inst.Rn); + } + + public static void StrexT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.Strex(context, inst.Rd, inst.Rt, inst.Rn); + } + + public static void StrexbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rtb0w4 inst = new(encoding); + + InstEmitMemory.Strexb(context, inst.Rd, inst.Rt, inst.Rn); + } + + public static void StrexbT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Rdb0w4 inst = new(encoding); + + InstEmitMemory.Strexb(context, inst.Rd, inst.Rt, inst.Rn); + } + + public static void StrexdA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rtb0w4 inst = new(encoding); + + InstEmitMemory.Strexd(context, inst.Rd, inst.Rt, RegisterUtils.GetRt2(inst.Rt), inst.Rn); + } + + public static void StrexdT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Rt2b8w4Rdb0w4 inst = new(encoding); + + InstEmitMemory.Strexd(context, inst.Rd, inst.Rt, inst.Rt2, inst.Rn); + } + + public static void StrexhA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rtb0w4 inst = new(encoding); + + InstEmitMemory.Strexh(context, inst.Rd, inst.Rt, inst.Rn); + } + + public static void StrexhT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Rdb0w4 inst = new(encoding); + + InstEmitMemory.Strexh(context, inst.Rd, inst.Rt, inst.Rn); + } + + public static void StrhtA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm4hb8w4Imm4lb0w4 inst = new(encoding); + + InstEmitMemory.StrhtI(context, inst.Rt, inst.Rn, (int)ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), postIndex: true, inst.U != 0); + } + + public static void StrhtA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Rmb0w4 inst = new(encoding); + + InstEmitMemory.StrhtR(context, inst.Rt, inst.Rn, inst.Rm, postIndex: true, inst.U != 0); + } + + public static void StrhtT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.StrhtI(context, inst.Rt, inst.Rn, (int)inst.Imm8, postIndex: false, true); + } + + public static void StrhIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm4hb8w4Imm4lb0w4 inst = new(encoding); + + InstEmitMemory.StrhI(context, inst.Rt, inst.Rn, (int)ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void StrhIT1(CodeGenContext context, uint encoding) + { + InstImm5b22w5Rnb19w3Rtb16w3 inst = new(encoding); + + InstEmitMemory.StrhI(context, inst.Rt, inst.Rn, (int)inst.Imm5 << 1, true, true, false); + } + + public static void StrhIT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.StrhI(context, inst.Rt, inst.Rn, (int)inst.Imm12, true, true, false); + } + + public static void StrhIT3(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Pb10w1Ub9w1Wb8w1Imm8b0w8 inst = new(encoding); + + InstEmitMemory.StrhI(context, inst.Rt, inst.Rn, (int)inst.Imm8, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void StrhRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Rmb0w4 inst = new(encoding); + + InstEmitMemory.StrhR(context, inst.Rt, inst.Rn, inst.Rm, 0, 0, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void StrhRT1(CodeGenContext context, uint encoding) + { + InstRmb22w3Rnb19w3Rtb16w3 inst = new(encoding); + + InstEmitMemory.StrhR(context, inst.Rt, inst.Rn, inst.Rm, 0, 0, true, true, false); + } + + public static void StrhRT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm2b4w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.StrhR(context, inst.Rt, inst.Rn, inst.Rm, 0, inst.Imm2, true, true, false); + } + + public static void StrtA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.StrtI(context, inst.Rt, inst.Rn, (int)inst.Imm12, postIndex: true, inst.U != 0); + } + + public static void StrtA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Rnb16w4Rtb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.StrtR(context, inst.Rt, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, postIndex: true, inst.U != 0); + } + + public static void StrtT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm8b0w8 inst = new(encoding); + + InstEmitMemory.StrtI(context, inst.Rt, inst.Rn, (int)inst.Imm8, postIndex: false, true); + } + + public static void StrIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.StrI(context, inst.Rt, inst.Rn, (int)inst.Imm12, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void StrIT1(CodeGenContext context, uint encoding) + { + InstImm5b22w5Rnb19w3Rtb16w3 inst = new(encoding); + + InstEmitMemory.StrI(context, inst.Rt, inst.Rn, (int)inst.Imm5 << 2, true, true, false); + } + + public static void StrIT2(CodeGenContext context, uint encoding) + { + InstRtb24w3Imm8b16w8 inst = new(encoding); + + InstEmitMemory.StrI(context, inst.Rt, RegisterUtils.SpRegister, (int)inst.Imm8 << 2, true, true, false); + } + + public static void StrIT3(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm12b0w12 inst = new(encoding); + + InstEmitMemory.StrI(context, inst.Rt, inst.Rn, (int)inst.Imm12, true, true, false); + } + + public static void StrIT4(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Pb10w1Ub9w1Wb8w1Imm8b0w8 inst = new(encoding); + + InstEmitMemory.StrI(context, inst.Rt, inst.Rn, (int)inst.Imm8, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void StrRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Wb21w1Rnb16w4Rtb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.StrR(context, inst.Rt, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.P != 0, inst.U != 0, inst.W != 0); + } + + public static void StrRT1(CodeGenContext context, uint encoding) + { + InstRmb22w3Rnb19w3Rtb16w3 inst = new(encoding); + + InstEmitMemory.StrR(context, inst.Rt, inst.Rn, inst.Rm, 0, 0, true, true, false); + } + + public static void StrRT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Rtb12w4Imm2b4w2Rmb0w4 inst = new(encoding); + + InstEmitMemory.StrR(context, inst.Rt, inst.Rn, inst.Rm, 0, inst.Imm2, true, true, false); + } + + public static void SubIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.SubI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm12), inst.S != 0); + } + + public static void SubIT1(CodeGenContext context, uint encoding) + { + InstImm3b22w3Rnb19w3Rdb16w3 inst = new(encoding); + + InstEmitAlu.SubI(context, inst.Rd, inst.Rn, inst.Imm3, !context.InITBlock); + } + + public static void SubIT2(CodeGenContext context, uint encoding) + { + InstRdnb24w3Imm8b16w8 inst = new(encoding); + + InstEmitAlu.SubI(context, inst.Rdn, inst.Rdn, inst.Imm8, !context.InITBlock); + } + + public static void SubIT3(CodeGenContext context, uint encoding) + { + InstIb26w1Sb20w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.SubI(context, inst.Rd, inst.Rn, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), inst.S != 0); + } + + public static void SubIT4(CodeGenContext context, uint encoding) + { + InstIb26w1Rnb16w4Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.SubI(context, inst.Rd, inst.Rn, ImmUtils.CombineImmU12(inst.Imm8, inst.Imm3, inst.I), false); + } + + public static void SubIT5(CodeGenContext context, uint encoding) + { + InstRnb16w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.SubI(context, RegisterUtils.PcRegister, inst.Rn, inst.Imm8, true); + } + + public static void SubRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.SubR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Imm5, inst.S != 0); + } + + public static void SubRT1(CodeGenContext context, uint encoding) + { + InstRmb22w3Rnb19w3Rdb16w3 inst = new(encoding); + + InstEmitAlu.SubR(context, inst.Rd, inst.Rn, inst.Rm, 0, 0, !context.InITBlock); + } + + public static void SubRT2(CodeGenContext context, uint encoding) + { + InstSb20w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.SubR(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), !context.InITBlock); + } + + public static void SubRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rnb16w4Rdb12w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.SubRr(context, inst.Rd, inst.Rn, inst.Rm, inst.Stype, inst.Rs, inst.S != 0); + } + + public static void SubSpIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdb12w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.SubI(context, inst.Rd, RegisterUtils.SpRegister, ImmUtils.ExpandImm(inst.Imm12), inst.S != 0); + } + + public static void SubSpIT1(CodeGenContext context, uint encoding) + { + InstImm7b16w7 inst = new(encoding); + + InstEmitAlu.SubI(context, RegisterUtils.SpRegister, RegisterUtils.SpRegister, inst.Imm7 << 2, false); + } + + public static void SubSpIT2(CodeGenContext context, uint encoding) + { + InstIb26w1Sb20w1Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.SubI(context, inst.Rd, RegisterUtils.SpRegister, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), inst.S != 0); + } + + public static void SubSpIT3(CodeGenContext context, uint encoding) + { + InstIb26w1Imm3b12w3Rdb8w4Imm8b0w8 inst = new(encoding); + + InstEmitAlu.SubI(context, inst.Rd, RegisterUtils.SpRegister, ImmUtils.CombineImmU12(inst.Imm8, inst.Imm3, inst.I), false); + } + + public static void SubSpRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.SubR(context, inst.Rd, RegisterUtils.SpRegister, inst.Rm, inst.Stype, inst.Imm5, inst.S != 0); + } + + public static void SubSpRT1(CodeGenContext context, uint encoding) + { + InstSb20w1Imm3b12w3Rdb8w4Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.SubR(context, inst.Rd, RegisterUtils.SpRegister, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.S != 0); + } + + public static void SvcA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Imm24b0w24 inst = new(encoding); + + InstEmitSystem.Svc(context, inst.Imm24); + } + + public static void SvcT1(CodeGenContext context, uint encoding) + { + InstImm8b16w8 inst = new(encoding); + + InstEmitSystem.Svc(context, inst.Imm8); + } + + public static void SxtabA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rotateb10w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Sxtab(context, inst.Rd, inst.Rn, inst.Rm, inst.Rotate); + } + + public static void SxtabT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rotateb4w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Sxtab(context, inst.Rd, inst.Rn, inst.Rm, inst.Rotate); + } + + public static void Sxtab16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rotateb10w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Sxtab16(context, inst.Rd, inst.Rn, inst.Rm, inst.Rotate); + } + + public static void Sxtab16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rotateb4w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Sxtab16(context, inst.Rd, inst.Rn, inst.Rm, inst.Rotate); + } + + public static void SxtahA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rotateb10w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Sxtah(context, inst.Rd, inst.Rn, inst.Rm, inst.Rotate); + } + + public static void SxtahT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rotateb4w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Sxtah(context, inst.Rd, inst.Rn, inst.Rm, inst.Rotate); + } + + public static void SxtbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb12w4Rotateb10w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Sxtb(context, inst.Rd, inst.Rm, inst.Rotate); + } + + public static void SxtbT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rdb16w3 inst = new(encoding); + + InstEmitExtension.Sxtb(context, inst.Rd, inst.Rm, 0); + } + + public static void SxtbT2(CodeGenContext context, uint encoding) + { + InstRdb8w4Rotateb4w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Sxtb(context, inst.Rd, inst.Rm, inst.Rotate); + } + + public static void Sxtb16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb12w4Rotateb10w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Sxtb16(context, inst.Rd, inst.Rm, inst.Rotate); + } + + public static void Sxtb16T1(CodeGenContext context, uint encoding) + { + InstRdb8w4Rotateb4w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Sxtb16(context, inst.Rd, inst.Rm, inst.Rotate); + } + + public static void SxthA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb12w4Rotateb10w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Sxth(context, inst.Rd, inst.Rm, inst.Rotate); + } + + public static void SxthT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rdb16w3 inst = new(encoding); + + InstEmitExtension.Sxth(context, inst.Rd, inst.Rm, 0); + } + + public static void SxthT2(CodeGenContext context, uint encoding) + { + InstRdb8w4Rotateb4w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Sxth(context, inst.Rd, inst.Rm, inst.Rotate); + } + + public static void TbbT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Hb4w1Rmb0w4 inst = new(encoding); + + InstEmitFlow.Tbb(context, inst.Rn, inst.Rm, inst.H != 0); + } + + public static void TeqIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.TeqI(context, inst.Rn, ImmUtils.ExpandImm(inst.Imm12), ImmUtils.ExpandedImmRotated(inst.Imm12)); + } + + public static void TeqIT1(CodeGenContext context, uint encoding) + { + InstIb26w1Rnb16w4Imm3b12w3Imm8b0w8 inst = new(encoding); + + InstEmitAlu.TeqI(context, inst.Rn, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), ImmUtils.ExpandedImmRotated(inst.Imm8, inst.Imm3, inst.I)); + } + + public static void TeqRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.TeqR(context, inst.Rn, inst.Rm, inst.Stype, inst.Imm5); + } + + public static void TeqRT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Imm3b12w3Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.TeqR(context, inst.Rn, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3)); + } + + public static void TeqRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.TeqRr(context, inst.Rn, inst.Rm, inst.Stype, inst.Rs); + } + + public static void TsbA1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Tsb(); + } + + public static void TsbT1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Tsb(); + } + + public static void TstIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Imm12b0w12 inst = new(encoding); + + InstEmitAlu.TstI(context, inst.Rn, ImmUtils.ExpandImm(inst.Imm12), ImmUtils.ExpandedImmRotated(inst.Imm12)); + } + + public static void TstIT1(CodeGenContext context, uint encoding) + { + InstIb26w1Rnb16w4Imm3b12w3Imm8b0w8 inst = new(encoding); + + InstEmitAlu.TstI(context, inst.Rn, ImmUtils.ExpandImm(inst.Imm8, inst.Imm3, inst.I), ImmUtils.ExpandedImmRotated(inst.Imm8, inst.Imm3, inst.I)); + } + + public static void TstRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Imm5b7w5Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.TstR(context, inst.Rn, inst.Rm, inst.Stype, inst.Imm5); + } + + public static void TstRT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rnb16w3 inst = new(encoding); + + InstEmitAlu.TstR(context, inst.Rn, inst.Rm, 0, 0); + } + + public static void TstRT2(CodeGenContext context, uint encoding) + { + InstRnb16w4Imm3b12w3Imm2b6w2Stypeb4w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.TstR(context, inst.Rn, inst.Rm, inst.Stype, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3)); + } + + public static void TstRrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rsb8w4Stypeb5w2Rmb0w4 inst = new(encoding); + + InstEmitAlu.TstRr(context, inst.Rn, inst.Rm, inst.Stype, inst.Rs); + } + + public static void Uadd16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Uadd16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uadd16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Uadd16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uadd8A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Uadd8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uadd8T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Uadd8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UasxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Uasx(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UasxT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Uasx(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UbfxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Widthm1b16w5Rdb12w4Lsbb7w5Rnb0w4 inst = new(encoding); + + InstEmitBit.Ubfx(context, inst.Rd, inst.Rn, inst.Lsb, inst.Widthm1); + } + + public static void UbfxT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Imm3b12w3Rdb8w4Imm2b6w2Widthm1b0w5 inst = new(encoding); + + InstEmitBit.Ubfx(context, inst.Rd, inst.Rn, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3), inst.Widthm1); + } + + public static void UdfA1(CodeGenContext context, uint encoding) + { + InstImm12b8w12Imm4b0w4 inst = new(encoding); + + InstEmitSystem.Udf(context, encoding, ImmUtils.CombineImmU16(inst.Imm12, inst.Imm4)); + } + + public static void UdfT1(CodeGenContext context, uint encoding) + { + InstImm8b16w8 inst = new(encoding); + + InstEmitSystem.Udf(context, encoding, inst.Imm8); + } + + public static void UdfT2(CodeGenContext context, uint encoding) + { + InstImm4b16w4Imm12b0w12 inst = new(encoding); + + InstEmitSystem.Udf(context, encoding, ImmUtils.CombineImmU16(inst.Imm12, inst.Imm4)); + } + + public static void UdivA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rmb8w4Rnb0w4 inst = new(encoding); + + InstEmitDivide.Udiv(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UdivT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitDivide.Udiv(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uhadd16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Uhadd16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uhadd16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Uhadd16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uhadd8A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Uhadd8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uhadd8T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Uhadd8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UhasxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Uhasx(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UhasxT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Uhasx(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UhsaxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Uhsax(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UhsaxT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Uhsax(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uhsub16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Uhsub16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uhsub16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Uhsub16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uhsub8A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Uhsub8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uhsub8T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitHalve.Uhsub8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UmaalA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdhib16w4Rdlob12w4Rmb8w4Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Umaal(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm); + } + + public static void UmaalT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdlob12w4Rdhib8w4Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Umaal(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm); + } + + public static void UmlalA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdhib16w4Rdlob12w4Rmb8w4Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Umlal(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm, inst.S != 0); + } + + public static void UmlalT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdlob12w4Rdhib8w4Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Umlal(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm, false); + } + + public static void UmullA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Sb20w1Rdhib16w4Rdlob12w4Rmb8w4Rnb0w4 inst = new(encoding); + + InstEmitMultiply.Umull(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm, inst.S != 0); + } + + public static void UmullT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdlob12w4Rdhib8w4Rmb0w4 inst = new(encoding); + + InstEmitMultiply.Umull(context, inst.Rdlo, inst.Rdhi, inst.Rn, inst.Rm, false); + } + + public static void Uqadd16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Uqadd16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uqadd16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Uqadd16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uqadd8A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Uqadd8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uqadd8T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Uqadd8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UqasxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Uqasx(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UqasxT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Uqasx(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UqsaxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Uqsax(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UqsaxT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Uqsax(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uqsub16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Uqsub16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uqsub16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Uqsub16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uqsub8A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Uqsub8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Uqsub8T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitSaturate.Uqsub8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Usad8A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rmb8w4Rnb0w4 inst = new(encoding); + + InstEmitAbsDiff.Usad8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Usad8T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitAbsDiff.Usad8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Usada8A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb16w4Rab12w4Rmb8w4Rnb0w4 inst = new(encoding); + + InstEmitAbsDiff.Usada8(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra); + } + + public static void Usada8T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rab12w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitAbsDiff.Usada8(context, inst.Rd, inst.Rn, inst.Rm, inst.Ra); + } + + public static void UsatA1(CodeGenContext context, uint encoding) + { + InstCondb28w4SatImmb16w5Rdb12w4Imm5b7w5Shb6w1Rnb0w4 inst = new(encoding); + + InstEmitSaturate.Usat(context, inst.Rd, inst.SatImm, inst.Rn, inst.Sh != 0, inst.Imm5); + } + + public static void UsatT1(CodeGenContext context, uint encoding) + { + InstShb21w1Rnb16w4Imm3b12w3Rdb8w4Imm2b6w2SatImmb0w5 inst = new(encoding); + + InstEmitSaturate.Usat(context, inst.Rd, inst.SatImm, inst.Rn, inst.Sh != 0, ImmUtils.CombineImmU5(inst.Imm2, inst.Imm3)); + } + + public static void Usat16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4SatImmb16w4Rdb12w4Rnb0w4 inst = new(encoding); + + InstEmitSaturate.Usat16(context, inst.Rd, inst.SatImm, inst.Rn); + } + + public static void Usat16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4SatImmb0w4 inst = new(encoding); + + InstEmitSaturate.Usat16(context, inst.Rd, inst.SatImm, inst.Rn); + } + + public static void UsaxA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Usax(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UsaxT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Usax(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Usub16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Usub16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Usub16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Usub16(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Usub8A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Usub8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void Usub8T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rmb0w4 inst = new(encoding); + + InstEmitGE.Usub8(context, inst.Rd, inst.Rn, inst.Rm); + } + + public static void UxtabA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rotateb10w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Uxtab(context, inst.Rd, inst.Rn, inst.Rm, inst.Rotate); + } + + public static void UxtabT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rotateb4w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Uxtab(context, inst.Rd, inst.Rn, inst.Rm, inst.Rotate); + } + + public static void Uxtab16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rotateb10w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Uxtab16(context, inst.Rd, inst.Rn, inst.Rm, inst.Rotate); + } + + public static void Uxtab16T1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rotateb4w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Uxtab16(context, inst.Rd, inst.Rn, inst.Rm, inst.Rotate); + } + + public static void UxtahA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rnb16w4Rdb12w4Rotateb10w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Uxtah(context, inst.Rd, inst.Rn, inst.Rm, inst.Rotate); + } + + public static void UxtahT1(CodeGenContext context, uint encoding) + { + InstRnb16w4Rdb8w4Rotateb4w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Uxtah(context, inst.Rd, inst.Rn, inst.Rm, inst.Rotate); + } + + public static void UxtbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb12w4Rotateb10w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Uxtb(context, inst.Rd, inst.Rm, inst.Rotate); + } + + public static void UxtbT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rdb16w3 inst = new(encoding); + + InstEmitExtension.Uxtb(context, inst.Rd, inst.Rm, 0); + } + + public static void UxtbT2(CodeGenContext context, uint encoding) + { + InstRdb8w4Rotateb4w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Uxtb(context, inst.Rd, inst.Rm, inst.Rotate); + } + + public static void Uxtb16A1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb12w4Rotateb10w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Uxtb16(context, inst.Rd, inst.Rm, inst.Rotate); + } + + public static void Uxtb16T1(CodeGenContext context, uint encoding) + { + InstRdb8w4Rotateb4w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Uxtb16(context, inst.Rd, inst.Rm, inst.Rotate); + } + + public static void UxthA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Rdb12w4Rotateb10w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Uxth(context, inst.Rd, inst.Rm, inst.Rotate); + } + + public static void UxthT1(CodeGenContext context, uint encoding) + { + InstRmb19w3Rdb16w3 inst = new(encoding); + + InstEmitExtension.Uxth(context, inst.Rd, inst.Rm, 0); + } + + public static void UxthT2(CodeGenContext context, uint encoding) + { + InstRdb8w4Rotateb4w2Rmb0w4 inst = new(encoding); + + InstEmitExtension.Uxth(context, inst.Rd, inst.Rm, inst.Rotate); + } + + public static void VabaA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vaba(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VabaT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vaba(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VabalA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vabal(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VabalT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vabal(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VabdlIA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vabdl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VabdlIT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vabdl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VabdFA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VabdF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VabdFT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VabdF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VabdIA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VabdI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VabdIT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VabdI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VabsA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vabs(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VabsA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VabsF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VabsT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vabs(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VabsT2(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VabsF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VacgeA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.Vacge(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VacgeT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.Vacge(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VacgtA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.Vacgt(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VacgtT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.Vacgt(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VaddhnA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vaddhn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VaddhnT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vaddhn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VaddlA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vaddl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VaddlT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vaddl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VaddwA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vaddw(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VaddwT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vaddw(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VaddFA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VaddF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VaddFA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VaddF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VaddFT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VaddF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VaddFT2(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VaddF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VaddIA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VaddI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VaddIT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VaddI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VandRA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VandR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VandRT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VandR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VbicIA1(CodeGenContext context, uint encoding) + { + InstIb24w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonLogical.VbicI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VbicIA2(CodeGenContext context, uint encoding) + { + InstIb24w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonLogical.VbicI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VbicIT1(CodeGenContext context, uint encoding) + { + InstIb28w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonLogical.VbicI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VbicIT2(CodeGenContext context, uint encoding) + { + InstIb28w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonLogical.VbicI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VbicRA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VbicR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VbicRT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VbicR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VbifA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VbifR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VbifT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VbifR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VbitA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VbitR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VbitT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VbitR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VbslA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VbslR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VbslT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VbslR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VcaddA1(CodeGenContext context, uint encoding) + { + _ = new InstRotb24w1Db22w1Sb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VcaddT1(CodeGenContext context, uint encoding) + { + _ = new InstRotb24w1Db22w1Sb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VceqIA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VceqI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VceqIT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VceqI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VceqRA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VceqR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VceqRA2(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VceqFR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VceqRT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VceqR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VceqRT2(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VceqFR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VcgeIA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcgeI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VcgeIT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcgeI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VcgeRA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcgeR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VcgeRA2(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcgeFR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VcgeRT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcgeR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VcgeRT2(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcgeFR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VcgtIA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcgtI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VcgtIT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcgtI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VcgtRA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcgtR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VcgtRA2(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcgtFR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VcgtRT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcgtR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VcgtRT2(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcgtFR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VcleIA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcleI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VcleIT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcleI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VclsA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonBit.Vcls(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VclsT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonBit.Vcls(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VcltIA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcltI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VcltIT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.VcltI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VclzA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonBit.Vclz(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VclzT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonBit.Vclz(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VcmlaA1(CodeGenContext context, uint encoding) + { + _ = new InstRotb23w2Db22w1Sb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VcmlaT1(CodeGenContext context, uint encoding) + { + _ = new InstRotb23w2Db22w1Sb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VcmlaSA1(CodeGenContext context, uint encoding) + { + _ = new InstSb23w1Db22w1Rotb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VcmlaST1(CodeGenContext context, uint encoding) + { + _ = new InstSb23w1Db22w1Rotb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VcmpA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpCompare.VcmpR(context, inst.Cond, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VcmpA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Sizeb8w2 inst = new(encoding); + + InstEmitVfpCompare.VcmpI(context, inst.Cond, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), inst.Size); + } + + public static void VcmpT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpCompare.VcmpR(context, 0xe, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VcmpT2(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2 inst = new(encoding); + + InstEmitVfpCompare.VcmpI(context, 0xe, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), inst.Size); + } + + public static void VcmpeA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpCompare.VcmpeR(context, inst.Cond, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VcmpeA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Sizeb8w2 inst = new(encoding); + + InstEmitVfpCompare.VcmpeI(context, inst.Cond, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), inst.Size); + } + + public static void VcmpeT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpCompare.VcmpeR(context, 0xe, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VcmpeT2(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2 inst = new(encoding); + + InstEmitVfpCompare.VcmpeI(context, 0xe, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), inst.Size); + } + + public static void VcntA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonBit.Vcnt(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VcntT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonBit.Vcnt(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VcvtaAsimdA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonConvert.Vcvta(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Size, inst.Q); + } + + public static void VcvtaAsimdT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonConvert.Vcvta(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Size, inst.Q); + } + + public static void VcvtaVfpA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Opb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpConvert.Vcvta(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Op != 0, inst.Size); + } + + public static void VcvtaVfpT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Opb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpConvert.Vcvta(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Op != 0, inst.Size); + } + + public static void VcvtbA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Opb16w1Vdb12w4Szb8w1Mb5w1Vmb0w4 inst = new(encoding); + + uint dSize = inst.Sz == 1 && inst.Op == 0 ? 3u : 2u; + uint mSize = inst.Sz == 1 && inst.Op == 1 ? 3u : 2u; + + InstEmitVfpConvert.Vcvtb(context, InstEmitCommon.CombineV(inst.Vd, inst.D, dSize), InstEmitCommon.CombineV(inst.Vm, inst.M, mSize), inst.Sz, inst.Op); + } + + public static void VcvtbT1(CodeGenContext context, uint encoding) + { + InstDb22w1Opb16w1Vdb12w4Szb8w1Mb5w1Vmb0w4 inst = new(encoding); + + uint dSize = inst.Sz == 1 && inst.Op == 0 ? 3u : 2u; + uint mSize = inst.Sz == 1 && inst.Op == 1 ? 3u : 2u; + + InstEmitVfpConvert.Vcvtb(context, InstEmitCommon.CombineV(inst.Vd, inst.D, dSize), InstEmitCommon.CombineV(inst.Vm, inst.M, mSize), inst.Sz, inst.Op); + } + + public static void VcvtbBfsA1(CodeGenContext context, uint encoding) + { + _ = new InstCondb28w4Db22w1Vdb12w4Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VcvtbBfsT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vdb12w4Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VcvtmAsimdA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonConvert.Vcvtm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Size, inst.Q); + } + + public static void VcvtmAsimdT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonConvert.Vcvtm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Size, inst.Q); + } + + public static void VcvtmVfpA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Opb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpConvert.Vcvtm(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Op != 0, inst.Size); + } + + public static void VcvtmVfpT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Opb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpConvert.Vcvtm(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Op != 0, inst.Size); + } + + public static void VcvtnAsimdA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonConvert.Vcvtn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Size, inst.Q); + } + + public static void VcvtnAsimdT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonConvert.Vcvtn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Size, inst.Q); + } + + public static void VcvtnVfpA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Opb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpConvert.Vcvtn(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Op != 0, inst.Size); + } + + public static void VcvtnVfpT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Opb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpConvert.Vcvtn(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Op != 0, inst.Size); + } + + public static void VcvtpAsimdA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonConvert.Vcvtp(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Size, inst.Q); + } + + public static void VcvtpAsimdT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonConvert.Vcvtp(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Size, inst.Q); + } + + public static void VcvtpVfpA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Opb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpConvert.Vcvtp(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Op != 0, inst.Size); + } + + public static void VcvtpVfpT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Opb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpConvert.Vcvtp(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Op != 0, inst.Size); + } + + public static void VcvtrIvA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpConvert.VcvtrIv(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), (encoding >> 16) & 7, inst.Size); + } + + public static void VcvtrIvT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpConvert.VcvtrIv(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), (encoding >> 16) & 7, inst.Size); + } + + public static void VcvttA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Opb16w1Vdb12w4Szb8w1Mb5w1Vmb0w4 inst = new(encoding); + + uint dSize = inst.Sz == 1 && inst.Op == 0 ? 3u : 2u; + uint mSize = inst.Sz == 1 && inst.Op == 1 ? 3u : 2u; + + InstEmitVfpConvert.Vcvtt(context, InstEmitCommon.CombineV(inst.Vd, inst.D, dSize), InstEmitCommon.CombineV(inst.Vm, inst.M, mSize), inst.Sz, inst.Op); + } + + public static void VcvttT1(CodeGenContext context, uint encoding) + { + InstDb22w1Opb16w1Vdb12w4Szb8w1Mb5w1Vmb0w4 inst = new(encoding); + + uint dSize = inst.Sz == 1 && inst.Op == 0 ? 3u : 2u; + uint mSize = inst.Sz == 1 && inst.Op == 1 ? 3u : 2u; + + InstEmitVfpConvert.Vcvtt(context, InstEmitCommon.CombineV(inst.Vd, inst.D, dSize), InstEmitCommon.CombineV(inst.Vm, inst.M, mSize), inst.Sz, inst.Op); + } + + public static void VcvttBfsA1(CodeGenContext context, uint encoding) + { + _ = new InstCondb28w4Db22w1Vdb12w4Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VcvttBfsT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vdb12w4Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VcvtBfsA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vdb12w4Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VcvtBfsT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vdb12w4Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VcvtDsA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + uint size = (encoding >> 8) & 3; + + InstEmitVfpConvert.VcvtDs(context, InstEmitCommon.CombineV(inst.Vd, inst.D, size ^ 1u), InstEmitCommon.CombineV(inst.Vm, inst.M, size), size); + } + + public static void VcvtDsT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + uint size = (encoding >> 8) & 3; + + InstEmitVfpConvert.VcvtDs(context, InstEmitCommon.CombineV(inst.Vd, inst.D, size ^ 1u), InstEmitCommon.CombineV(inst.Vm, inst.M, size), size); + } + + public static void VcvtHsA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb8w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonConvert.VcvtHs(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0); + } + + public static void VcvtHsT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb8w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonConvert.VcvtHs(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0); + } + + public static void VcvtIsA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb7w2Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonConvert.VcvtIs(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op, inst.Size, inst.Q); + } + + public static void VcvtIsT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb7w2Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonConvert.VcvtIs(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op, inst.Size, inst.Q); + } + + public static void VcvtIvA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpConvert.VcvtIv(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), (encoding & (1u << 16)) == 0, inst.Size); + } + + public static void VcvtIvT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpConvert.VcvtIv(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), (encoding & (1u << 16)) == 0, inst.Size); + } + + public static void VcvtViA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Sizeb8w2Opb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpConvert.VcvtVi(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineVF(inst.M, inst.Vm), inst.Op == 0, inst.Size); + } + + public static void VcvtViT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Opb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpConvert.VcvtVi(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineVF(inst.M, inst.Vm), inst.Op == 0, inst.Size); + } + + public static void VcvtXsA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Imm6b16w6Vdb12w4Opb8w2Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonConvert.VcvtXs(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Imm6, inst.Op, inst.U != 0, inst.Q); + } + + public static void VcvtXsT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Imm6b16w6Vdb12w4Opb8w2Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonConvert.VcvtXs(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Imm6, inst.Op, inst.U != 0, inst.Q); + } + + public static void VcvtXvA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Opb18w1Ub16w1Vdb12w4Sfb8w2Sxb7w1Ib5w1Imm4b0w4 inst = new(encoding); + + InstEmitVfpConvert.VcvtXv(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Sf), ImmUtils.CombineImmU5IImm4(inst.I, inst.Imm4), inst.Sx != 0, inst.Sf, inst.Op, inst.U != 0); + } + + public static void VcvtXvT1(CodeGenContext context, uint encoding) + { + InstDb22w1Opb18w1Ub16w1Vdb12w4Sfb8w2Sxb7w1Ib5w1Imm4b0w4 inst = new(encoding); + + InstEmitVfpConvert.VcvtXv(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Sf), ImmUtils.CombineImmU5IImm4(inst.I, inst.Imm4), inst.Sx != 0, inst.Sf, inst.Op, inst.U != 0); + } + + public static void VdivA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VdivF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VdivT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VdivF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VdotA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VdotT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VdotSA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VdotST1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VdupRA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Bb22w1Qb21w1Vdb16w4Rtb12w4Db7w1Eb5w1 inst = new(encoding); + + InstEmitNeonMove.VdupR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rt, inst.B, inst.E, inst.Q); + } + + public static void VdupRT1(CodeGenContext context, uint encoding) + { + InstBb22w1Qb21w1Vdb16w4Rtb12w4Db7w1Eb5w1 inst = new(encoding); + + InstEmitNeonMove.VdupR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rt, inst.B, inst.E, inst.Q); + } + + public static void VdupSA1(CodeGenContext context, uint encoding) + { + InstDb22w1Imm4b16w4Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.VdupS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Imm4, inst.Q); + } + + public static void VdupST1(CodeGenContext context, uint encoding) + { + InstDb22w1Imm4b16w4Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.VdupS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Imm4, inst.Q); + } + + public static void VeorA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VeorR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VeorT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VeorR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VextA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Imm4b8w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vext(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Imm4, inst.Q); + } + + public static void VextT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Imm4b8w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vext(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Imm4, inst.Q); + } + + public static void VfmaA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VfmaF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VfmaA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VfmaF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VfmaT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VfmaF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VfmaT2(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VfmaF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VfmalA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VfmalT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VfmalSA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VfmalST1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VfmaBfA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VfmaBfT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VfmaBfsA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VfmaBfsT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VfmsA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VfmsF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VfmsA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VfmsF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VfmsT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VfmsF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VfmsT2(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VfmsF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VfmslA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VfmslT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VfmslSA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VfmslST1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VfnmaA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VfnmaF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VfnmaT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VfnmaF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VfnmsA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VfnmsF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VfnmsT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VfnmsF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VhaddA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vhadd(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VhaddT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vhadd(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VhsubA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vhsub(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VhsubT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vhsub(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VinsA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vdb12w4Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VinsT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vdb12w4Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VjcvtA1(CodeGenContext context, uint encoding) + { + _ = new InstCondb28w4Db22w1Vdb12w4Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VjcvtT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vdb12w4Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void Vld11A1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld11(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vld11A2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld11(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vld11A3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld11(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vld11T1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld11(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vld11T2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld11(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vld11T3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld11(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vld1AA1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Tb5w1Ab4w1Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld1A(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.A, inst.T, inst.Size); + } + + public static void Vld1AT1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Tb5w1Ab4w1Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld1A(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.A, inst.T, inst.Size); + } + + public static void Vld1MA1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 1, inst.Align, inst.Size); + } + + public static void Vld1MA2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 2, inst.Align, inst.Size); + } + + public static void Vld1MA3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 3, inst.Align, inst.Size); + } + + public static void Vld1MA4(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 4, inst.Align, inst.Size); + } + + public static void Vld1MT1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 1, inst.Align, inst.Size); + } + + public static void Vld1MT2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 2, inst.Align, inst.Size); + } + + public static void Vld1MT3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 3, inst.Align, inst.Size); + } + + public static void Vld1MT4(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 4, inst.Align, inst.Size); + } + + public static void Vld21A1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld21(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vld21A2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld21(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vld21A3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld21(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vld21T1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld21(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vld21T2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld21(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vld21T3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld21(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vld2AA1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Tb5w1Ab4w1Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld2A(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.A, inst.T, inst.Size); + } + + public static void Vld2AT1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Tb5w1Ab4w1Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld2A(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.A, inst.T, inst.Size); + } + + public static void Vld2MA1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld2M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, (encoding >> 8) & 0xf, inst.Align, inst.Size); + } + + public static void Vld2MA2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld2M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.Align, inst.Size); + } + + public static void Vld2MT1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld2M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, (encoding >> 8) & 0xf, inst.Align, inst.Size); + } + + public static void Vld2MT2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld2M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.Align, inst.Size); + } + + public static void Vld31A1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld31(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vld31A2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld31(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vld31A3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld31(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vld31T1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld31(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vld31T2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld31(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vld31T3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld31(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vld3AA1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Tb5w1Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld3A(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 0, inst.T, inst.Size); + } + + public static void Vld3AT1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Tb5w1Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld3A(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 0, inst.T, inst.Size); + } + + public static void Vld3MA1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld3M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, (encoding >> 8) & 0xf, inst.Align, inst.Size); + } + + public static void Vld3MT1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld3M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, (encoding >> 8) & 0xf, inst.Align, inst.Size); + } + + public static void Vld41A1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld41(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vld41A2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld41(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vld41A3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld41(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vld41T1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld41(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vld41T2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld41(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vld41T3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld41(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vld4AA1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Tb5w1Ab4w1Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld4A(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.A, inst.T, inst.Size); + } + + public static void Vld4AT1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Tb5w1Ab4w1Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld4A(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.A, inst.T, inst.Size); + } + + public static void Vld4MA1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld4M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, (encoding >> 8) & 0xf, inst.Align, inst.Size); + } + + public static void Vld4MT1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vld4M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, (encoding >> 8) & 0xf, inst.Align, inst.Size); + } + + public static void VldmA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm871b1w7 inst = new(encoding); + + InstEmitNeonMemory.Vldm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Imm871, inst.U != 0, inst.W != 0, singleRegs: false); + } + + public static void VldmA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm8b0w8 inst = new(encoding); + + InstEmitNeonMemory.Vldm(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), inst.Rn, inst.Imm8, inst.U != 0, inst.W != 0, singleRegs: true); + } + + public static void VldmT1(CodeGenContext context, uint encoding) + { + InstPb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm871b1w7 inst = new(encoding); + + InstEmitNeonMemory.Vldm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Imm871, inst.U != 0, inst.W != 0, singleRegs: false); + } + + public static void VldmT2(CodeGenContext context, uint encoding) + { + InstPb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm8b0w8 inst = new(encoding); + + InstEmitNeonMemory.Vldm(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), inst.Rn, inst.Imm8, inst.U != 0, inst.W != 0, singleRegs: true); + } + + public static void VldrIA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Db22w1Rnb16w4Vdb12w4Sizeb8w2Imm8b0w8 inst = new(encoding); + + InstEmitNeonMemory.Vldr(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), inst.Rn, inst.Imm8, inst.U != 0, inst.Size); + } + + public static void VldrIT1(CodeGenContext context, uint encoding) + { + InstUb23w1Db22w1Rnb16w4Vdb12w4Sizeb8w2Imm8b0w8 inst = new(encoding); + + InstEmitNeonMemory.Vldr(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), inst.Rn, inst.Imm8, inst.U != 0, inst.Size); + } + + public static void VldrLA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Db22w1Vdb12w4Sizeb8w2Imm8b0w8 inst = new(encoding); + + InstEmitNeonMemory.Vldr(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), RegisterUtils.PcRegister, inst.Imm8, inst.U != 0, inst.Size); + } + + public static void VldrLT1(CodeGenContext context, uint encoding) + { + InstUb23w1Db22w1Vdb12w4Sizeb8w2Imm8b0w8 inst = new(encoding); + + InstEmitNeonMemory.Vldr(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), RegisterUtils.PcRegister, inst.Imm8, inst.U != 0, inst.Size); + } + + public static void VmaxnmA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vmaxnm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VmaxnmA2(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.Vmaxnm(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VmaxnmT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vmaxnm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VmaxnmT2(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.Vmaxnm(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VmaxFA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmaxF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VmaxFT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmaxF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VmaxIA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmaxI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VmaxIT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmaxI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VminnmA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vminnm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VminnmA2(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.Vminnm(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VminnmT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vminnm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VminnmT2(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.Vminnm(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VminFA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VminF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VminFT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VminF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VminIA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VminI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VminIT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VminI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VmlalIA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlalI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VmlalIT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlalI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VmlalSA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlalS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VmlalST1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlalS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VmlaFA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlaF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VmlaFA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VmlaF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VmlaFT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlaF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VmlaFT2(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VmlaF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VmlaIA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlaI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VmlaIT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlaI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VmlaSA1(CodeGenContext context, uint encoding) + { + InstQb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Fb8w1Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlaS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VmlaST1(CodeGenContext context, uint encoding) + { + InstQb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Fb8w1Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlaS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VmlslIA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlslI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VmlslIT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlslI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VmlslSA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlslS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VmlslST1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlslS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VmlsFA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlsF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VmlsFA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VmlsF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VmlsFT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlsF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VmlsFT2(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VmlsF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VmlsIA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlsI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VmlsIT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlsI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VmlsSA1(CodeGenContext context, uint encoding) + { + InstQb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Fb8w1Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlsS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VmlsST1(CodeGenContext context, uint encoding) + { + InstQb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Fb8w1Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmlsS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VmmlaA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VmmlaT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VmovlA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Imm3hb19w3Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vmovl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Imm3h); + } + + public static void VmovlT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Imm3hb19w3Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vmovl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Imm3h); + } + + public static void VmovnA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vmovn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VmovnT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vmovn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VmovxA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vmovx(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M)); + } + + public static void VmovxT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vmovx(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M)); + } + + public static void VmovDA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Opb20w1Rt2b16w4Rtb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.VmovD(context, inst.Rt, inst.Rt2, InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0); + } + + public static void VmovDT1(CodeGenContext context, uint encoding) + { + InstOpb20w1Rt2b16w4Rtb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.VmovD(context, inst.Rt, inst.Rt2, InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0); + } + + public static void VmovHA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Opb20w1Vnb16w4Rtb12w4Nb7w1 inst = new(encoding); + + InstEmitNeonMove.VmovH(context, inst.Rt, InstEmitCommon.CombineVF(inst.N, inst.Vn), inst.Op != 0); + } + + public static void VmovHT1(CodeGenContext context, uint encoding) + { + InstOpb20w1Vnb16w4Rtb12w4Nb7w1 inst = new(encoding); + + InstEmitNeonMove.VmovH(context, inst.Rt, InstEmitCommon.CombineVF(inst.N, inst.Vn), inst.Op != 0); + } + + public static void VmovIA1(CodeGenContext context, uint encoding) + { + InstIb24w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonMove.VmovI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), 0, (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VmovIA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Imm4hb16w4Vdb12w4Sizeb8w2Imm4lb0w4 inst = new(encoding); + + InstEmitNeonMove.VmovFI(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), inst.Size); + } + + public static void VmovIA3(CodeGenContext context, uint encoding) + { + InstIb24w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonMove.VmovI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), 0, (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VmovIA4(CodeGenContext context, uint encoding) + { + InstIb24w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonMove.VmovI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), 0, (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VmovIA5(CodeGenContext context, uint encoding) + { + InstIb24w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonMove.VmovI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), 1, (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VmovIT1(CodeGenContext context, uint encoding) + { + InstIb28w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonMove.VmovI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), 0, (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VmovIT2(CodeGenContext context, uint encoding) + { + InstDb22w1Imm4hb16w4Vdb12w4Sizeb8w2Imm4lb0w4 inst = new(encoding); + + InstEmitNeonMove.VmovFI(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), ImmUtils.CombineImmU8(inst.Imm4l, inst.Imm4h), inst.Size); + } + + public static void VmovIT3(CodeGenContext context, uint encoding) + { + InstIb28w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonMove.VmovI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), 0, (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VmovIT4(CodeGenContext context, uint encoding) + { + InstIb28w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonMove.VmovI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), 0, (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VmovIT5(CodeGenContext context, uint encoding) + { + InstIb28w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonMove.VmovI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), 1, (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VmovRA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + uint size = (encoding >> 8) & 3; + + InstEmitNeonMove.VmovR(context, InstEmitCommon.CombineV(inst.Vd, inst.D, size), InstEmitCommon.CombineV(inst.Vm, inst.M, size), size); + } + + public static void VmovRT2(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + uint size = (encoding >> 8) & 3; + + InstEmitNeonMove.VmovR(context, InstEmitCommon.CombineV(inst.Vd, inst.D, size), InstEmitCommon.CombineV(inst.Vm, inst.M, size), size); + } + + public static void VmovRsA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Opc1b21w2Vdb16w4Rtb12w4Db7w1Opc2b5w2 inst = new(encoding); + + InstEmitNeonMove.VmovRs(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rt, inst.Opc1, inst.Opc2); + } + + public static void VmovRsT1(CodeGenContext context, uint encoding) + { + InstOpc1b21w2Vdb16w4Rtb12w4Db7w1Opc2b5w2 inst = new(encoding); + + InstEmitNeonMove.VmovRs(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rt, inst.Opc1, inst.Opc2); + } + + public static void VmovSA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Opb20w1Vnb16w4Rtb12w4Nb7w1 inst = new(encoding); + + InstEmitNeonMove.VmovS(context, inst.Rt, InstEmitCommon.CombineVF(inst.N, inst.Vn), inst.Op != 0); + } + + public static void VmovST1(CodeGenContext context, uint encoding) + { + InstOpb20w1Vnb16w4Rtb12w4Nb7w1 inst = new(encoding); + + InstEmitNeonMove.VmovS(context, inst.Rt, InstEmitCommon.CombineVF(inst.N, inst.Vn), inst.Op != 0); + } + + public static void VmovSrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Opc1b21w2Vnb16w4Rtb12w4Nb7w1Opc2b5w2 inst = new(encoding); + + InstEmitNeonMove.VmovSr(context, inst.Rt, InstEmitCommon.CombineV(inst.Vn, inst.N), inst.U != 0, inst.Opc1, inst.Opc2); + } + + public static void VmovSrT1(CodeGenContext context, uint encoding) + { + InstUb23w1Opc1b21w2Vnb16w4Rtb12w4Nb7w1Opc2b5w2 inst = new(encoding); + + InstEmitNeonMove.VmovSr(context, inst.Rt, InstEmitCommon.CombineV(inst.Vn, inst.N), inst.U != 0, inst.Opc1, inst.Opc2); + } + + public static void VmovSsA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Opb20w1Rt2b16w4Rtb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.VmovSs(context, inst.Rt, inst.Rt2, InstEmitCommon.CombineVF(inst.M, inst.Vm), inst.Op != 0); + } + + public static void VmovSsT1(CodeGenContext context, uint encoding) + { + InstOpb20w1Rt2b16w4Rtb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.VmovSs(context, inst.Rt, inst.Rt2, InstEmitCommon.CombineVF(inst.M, inst.Vm), inst.Op != 0); + } + + public static void VmrsA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Regb16w4Rtb12w4 inst = new(encoding); + + InstEmitNeonSystem.Vmrs(context, inst.Rt, inst.Reg); + } + + public static void VmrsT1(CodeGenContext context, uint encoding) + { + InstRegb16w4Rtb12w4 inst = new(encoding); + + InstEmitNeonSystem.Vmrs(context, inst.Rt, inst.Reg); + } + + public static void VmsrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Regb16w4Rtb12w4 inst = new(encoding); + + InstEmitNeonSystem.Vmsr(context, inst.Rt, inst.Reg); + } + + public static void VmsrT1(CodeGenContext context, uint encoding) + { + InstRegb16w4Rtb12w4 inst = new(encoding); + + InstEmitNeonSystem.Vmsr(context, inst.Rt, inst.Reg); + } + + public static void VmullIA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Opb9w1Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmullI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.U != 0, inst.Size); + } + + public static void VmullIT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Opb9w1Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmullI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.U != 0, inst.Size); + } + + public static void VmullSA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmullS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VmullST1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmullS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VmulFA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmulF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VmulFA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VmulF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VmulFT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmulF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VmulFT2(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VmulF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VmulIA1(CodeGenContext context, uint encoding) + { + InstOpb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmulI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Size, inst.Q); + } + + public static void VmulIT1(CodeGenContext context, uint encoding) + { + InstOpb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmulI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Size, inst.Q); + } + + public static void VmulSA1(CodeGenContext context, uint encoding) + { + InstQb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Fb8w1Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmulS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VmulST1(CodeGenContext context, uint encoding) + { + InstQb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Fb8w1Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VmulS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VmvnIA1(CodeGenContext context, uint encoding) + { + InstIb24w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonMove.VmvnI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VmvnIA2(CodeGenContext context, uint encoding) + { + InstIb24w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonMove.VmvnI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VmvnIA3(CodeGenContext context, uint encoding) + { + InstIb24w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonMove.VmvnI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VmvnIT1(CodeGenContext context, uint encoding) + { + InstIb28w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonMove.VmvnI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VmvnIT2(CodeGenContext context, uint encoding) + { + InstIb28w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonMove.VmvnI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VmvnIT3(CodeGenContext context, uint encoding) + { + InstIb28w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonMove.VmvnI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VmvnRA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.VmvnR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VmvnRT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.VmvnR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VnegA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vneg(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VnegA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VnegF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VnegT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb10w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vneg(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VnegT2(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VnegF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VnmlaA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VnmlaF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VnmlaT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VnmlaF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VnmlsA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VnmlsF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VnmlsT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VnmlsF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VnmulA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VnmulF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VnmulT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VnmulF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VornRA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VornR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VornRT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VornR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VorrIA1(CodeGenContext context, uint encoding) + { + InstIb24w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonLogical.VorrI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VorrIA2(CodeGenContext context, uint encoding) + { + InstIb24w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonLogical.VorrI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VorrIT1(CodeGenContext context, uint encoding) + { + InstIb28w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonLogical.VorrI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VorrIT2(CodeGenContext context, uint encoding) + { + InstIb28w1Db22w1Imm3b16w3Vdb12w4Qb6w1Imm4b0w4 inst = new(encoding); + + InstEmitNeonLogical.VorrI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), (encoding >> 8) & 0xf, ImmUtils.CombineImmU8(inst.Imm4, inst.Imm3, inst.I), inst.Q); + } + + public static void VorrRA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VorrR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VorrRT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonLogical.VorrR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VpadalA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vpadal(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Size, inst.Q); + } + + public static void VpadalT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vpadal(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Size, inst.Q); + } + + public static void VpaddlA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vpaddl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Size, inst.Q); + } + + public static void VpaddlT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vpaddl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Size, inst.Q); + } + + public static void VpaddFA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VpaddF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VpaddFT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VpaddF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VpaddIA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VpaddI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VpaddIT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VpaddI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VpmaxFA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VpmaxF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, 0); + } + + public static void VpmaxFT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VpmaxF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, 0); + } + + public static void VpmaxIA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VpmaxI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, 0); + } + + public static void VpmaxIT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VpmaxI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, 0); + } + + public static void VpminFA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VpminF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, 0); + } + + public static void VpminFT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VpminF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, 0); + } + + public static void VpminIA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VpminI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, 0); + } + + public static void VpminIT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VpminI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, 0); + } + + public static void VqabsA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqabs(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqabsT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqabs(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqaddA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqadd(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VqaddT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqadd(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VqdmlalA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqdmlal(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VqdmlalA2(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqdmlalS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VqdmlalT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqdmlal(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VqdmlalT2(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqdmlalS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VqdmlslA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqdmlsl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VqdmlslA2(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqdmlslS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VqdmlslT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqdmlsl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VqdmlslT2(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqdmlslS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VqdmulhA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqdmulh(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqdmulhA2(CodeGenContext context, uint encoding) + { + InstQb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqdmulhS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqdmulhT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqdmulh(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqdmulhT2(CodeGenContext context, uint encoding) + { + InstQb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqdmulhS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqdmullA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqdmull(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VqdmullA2(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqdmullS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VqdmullT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqdmull(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VqdmullT2(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqdmullS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VqmovnA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb6w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqmovn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op, inst.Size); + } + + public static void VqmovnT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Opb6w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqmovn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op, inst.Size); + } + + public static void VqnegA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqneg(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqnegT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqneg(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrdmlahA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqrdmlah(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrdmlahA2(CodeGenContext context, uint encoding) + { + InstQb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqrdmlahS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrdmlahT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqrdmlah(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrdmlahT2(CodeGenContext context, uint encoding) + { + InstQb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqrdmlahS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrdmlshA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqrdmlsh(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrdmlshA2(CodeGenContext context, uint encoding) + { + InstQb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqrdmlshS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrdmlshT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqrdmlsh(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrdmlshT2(CodeGenContext context, uint encoding) + { + InstQb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqrdmlshS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrdmulhA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqrdmulh(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrdmulhA2(CodeGenContext context, uint encoding) + { + InstQb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqrdmulhS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrdmulhT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqrdmulh(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrdmulhT2(CodeGenContext context, uint encoding) + { + InstQb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqrdmulhS(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrshlA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqrshl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrshlT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqrshl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VqrshrnA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Imm6b16w6Vdb12w4Opb8w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqrshrn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Op, inst.Imm6); + } + + public static void VqrshrnT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Imm6b16w6Vdb12w4Opb8w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqrshrn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Op, inst.Imm6); + } + + public static void VqshlIA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Imm6b16w6Vdb12w4Opb8w1Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqshlI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Op, inst.L, inst.Imm6, inst.Q); + } + + public static void VqshlIT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Imm6b16w6Vdb12w4Opb8w1Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqshlI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Op, inst.L, inst.Imm6, inst.Q); + } + + public static void VqshlRA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqshlR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VqshlRT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.VqshlR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VqshrnA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Imm6b16w6Vdb12w4Opb8w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqshrn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Op, inst.Imm6); + } + + public static void VqshrnT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Imm6b16w6Vdb12w4Opb8w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqshrn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Op, inst.Imm6); + } + + public static void VqsubA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqsub(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VqsubT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonSaturate.Vqsub(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VraddhnA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vraddhn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VraddhnT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vraddhn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VrecpeA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb8w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vrecpe(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VrecpeT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb8w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vrecpe(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VrecpsA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vrecps(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VrecpsT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vrecps(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void Vrev16A1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonBit.Vrev16(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void Vrev16T1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonBit.Vrev16(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void Vrev32A1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonBit.Vrev32(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void Vrev32T1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonBit.Vrev32(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void Vrev64A1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonBit.Vrev64(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void Vrev64T1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonBit.Vrev64(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VrhaddA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrhadd(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VrhaddT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrhadd(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VrintaAsimdA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrinta(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VrintaAsimdT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrinta(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VrintaVfpA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpRound.Vrinta(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VrintaVfpT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpRound.Vrinta(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VrintmAsimdA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrintm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VrintmAsimdT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrintm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VrintmVfpA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpRound.Vrintm(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VrintmVfpT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpRound.Vrintm(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VrintnAsimdA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrintn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VrintnAsimdT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrintn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VrintnVfpA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpRound.Vrintn(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VrintnVfpT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpRound.Vrintn(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VrintpAsimdA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrintp(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VrintpAsimdT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrintp(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VrintpVfpA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpRound.Vrintp(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VrintpVfpT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpRound.Vrintp(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VrintrVfpA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpRound.Vrintr(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VrintrVfpT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpRound.Vrintr(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VrintxAsimdA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrintx(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VrintxAsimdT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrintx(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VrintxVfpA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpRound.Vrintx(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VrintxVfpT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpRound.Vrintx(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VrintzAsimdA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrintz(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VrintzAsimdT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrintz(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VrintzVfpA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpRound.Vrintz(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VrintzVfpT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpRound.Vrintz(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VrshlA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrshl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VrshlT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrshl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VrshrA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrshr(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.L, inst.Imm6, inst.Q); + } + + public static void VrshrT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrshr(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.L, inst.Imm6, inst.Q); + } + + public static void VrshrnA1(CodeGenContext context, uint encoding) + { + InstDb22w1Imm6b16w6Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrshrn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Imm6); + } + + public static void VrshrnT1(CodeGenContext context, uint encoding) + { + InstDb22w1Imm6b16w6Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrshrn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Imm6); + } + + public static void VrsqrteA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb8w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vrsqrte(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VrsqrteT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Fb8w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vrsqrte(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.F != 0, inst.Size, inst.Q); + } + + public static void VrsqrtsA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vrsqrts(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VrsqrtsT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vrsqrts(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VrsraA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrsra(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.L, inst.Imm6, inst.Q); + } + + public static void VrsraT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrsra(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.L, inst.Imm6, inst.Q); + } + + public static void VrsubhnA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrsubhn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VrsubhnT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonRound.Vrsubhn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VsdotA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VsdotT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VsdotSA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VsdotST1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VselA1(CodeGenContext context, uint encoding) + { + InstDb22w1Ccb20w2Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpMove.Vsel(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Cc, inst.Size); + } + + public static void VselT1(CodeGenContext context, uint encoding) + { + InstDb22w1Ccb20w2Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpMove.Vsel(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Cc, inst.Size); + } + + public static void VshllA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Imm6b16w6Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.Vshll(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Imm6, inst.U != 0); + } + + public static void VshllA2(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.Vshll2(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VshllT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Imm6b16w6Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.Vshll(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Imm6, inst.U != 0); + } + + public static void VshllT2(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.Vshll2(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VshlIA1(CodeGenContext context, uint encoding) + { + InstDb22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.VshlI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.L, inst.Imm6, inst.Q); + } + + public static void VshlIT1(CodeGenContext context, uint encoding) + { + InstDb22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.VshlI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.L, inst.Imm6, inst.Q); + } + + public static void VshlRA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.VshlR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VshlRT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.VshlR(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size, inst.Q); + } + + public static void VshrA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.Vshr(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.L, inst.Imm6, inst.Q); + } + + public static void VshrT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.Vshr(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.L, inst.Imm6, inst.Q); + } + + public static void VshrnA1(CodeGenContext context, uint encoding) + { + InstDb22w1Imm6b16w6Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.Vshrn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Imm6); + } + + public static void VshrnT1(CodeGenContext context, uint encoding) + { + InstDb22w1Imm6b16w6Vdb12w4Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.Vshrn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Imm6); + } + + public static void VsliA1(CodeGenContext context, uint encoding) + { + InstDb22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.Vsli(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.L, inst.Imm6, inst.Q); + } + + public static void VsliT1(CodeGenContext context, uint encoding) + { + InstDb22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.Vsli(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.L, inst.Imm6, inst.Q); + } + + public static void VsmmlaA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VsmmlaT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VsqrtA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VsqrtF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VsqrtT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Sizeb8w2Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VsqrtF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VsraA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.Vsra(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.L, inst.Imm6, inst.Q); + } + + public static void VsraT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.Vsra(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.L, inst.Imm6, inst.Q); + } + + public static void VsriA1(CodeGenContext context, uint encoding) + { + InstDb22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.Vsri(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.L, inst.Imm6, inst.Q); + } + + public static void VsriT1(CodeGenContext context, uint encoding) + { + InstDb22w1Imm6b16w6Vdb12w4Lb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonShift.Vsri(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.L, inst.Imm6, inst.Q); + } + + public static void Vst11A1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst11(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vst11A2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst11(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vst11A3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst11(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vst11T1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst11(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vst11T2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst11(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vst11T3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst11(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vst1MA1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 1, inst.Align, inst.Size); + } + + public static void Vst1MA2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 2, inst.Align, inst.Size); + } + + public static void Vst1MA3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 3, inst.Align, inst.Size); + } + + public static void Vst1MA4(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 4, inst.Align, inst.Size); + } + + public static void Vst1MT1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 1, inst.Align, inst.Size); + } + + public static void Vst1MT2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 2, inst.Align, inst.Size); + } + + public static void Vst1MT3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 3, inst.Align, inst.Size); + } + + public static void Vst1MT4(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst1M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, 4, inst.Align, inst.Size); + } + + public static void Vst21A1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst21(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vst21A2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst21(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vst21A3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst21(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vst21T1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst21(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vst21T2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst21(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vst21T3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst21(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vst2MA1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst2M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, (encoding >> 8) & 0xf, inst.Align, inst.Size); + } + + public static void Vst2MA2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst2M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.Align, inst.Size); + } + + public static void Vst2MT1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst2M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, (encoding >> 8) & 0xf, inst.Align, inst.Size); + } + + public static void Vst2MT2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst2M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.Align, inst.Size); + } + + public static void Vst31A1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst31(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vst31A2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst31(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vst31A3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst31(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vst31T1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst31(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vst31T2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst31(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vst31T3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst31(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vst3MA1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst3M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, (encoding >> 8) & 0xf, inst.Align, inst.Size); + } + + public static void Vst3MT1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst3M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, (encoding >> 8) & 0xf, inst.Align, inst.Size); + } + + public static void Vst41A1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst41(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vst41A2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst41(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vst41A3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst41(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vst41T1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst41(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 0); + } + + public static void Vst41T2(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst41(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 1); + } + + public static void Vst41T3(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4IndexAlignb4w4Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst41(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, inst.IndexAlign, 2); + } + + public static void Vst4MA1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst4M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, (encoding >> 8) & 0xf, inst.Align, inst.Size); + } + + public static void Vst4MT1(CodeGenContext context, uint encoding) + { + InstDb22w1Rnb16w4Vdb12w4Sizeb6w2Alignb4w2Rmb0w4 inst = new(encoding); + + InstEmitNeonMemory.Vst4M(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Rm, (encoding >> 8) & 0xf, inst.Align, inst.Size); + } + + public static void VstmA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm871b1w7 inst = new(encoding); + + InstEmitNeonMemory.Vstm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Imm871, inst.U != 0, inst.W != 0, singleRegs: false); + } + + public static void VstmA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Pb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm8b0w8 inst = new(encoding); + + InstEmitNeonMemory.Vstm(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), inst.Rn, inst.Imm8, inst.U != 0, inst.W != 0, singleRegs: true); + } + + public static void VstmT1(CodeGenContext context, uint encoding) + { + InstPb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm871b1w7 inst = new(encoding); + + InstEmitNeonMemory.Vstm(context, InstEmitCommon.CombineV(inst.Vd, inst.D), inst.Rn, inst.Imm871, inst.U != 0, inst.W != 0, singleRegs: false); + } + + public static void VstmT2(CodeGenContext context, uint encoding) + { + InstPb24w1Ub23w1Db22w1Wb21w1Rnb16w4Vdb12w4Imm8b0w8 inst = new(encoding); + + InstEmitNeonMemory.Vstm(context, InstEmitCommon.CombineVF(inst.D, inst.Vd), inst.Rn, inst.Imm8, inst.U != 0, inst.W != 0, singleRegs: true); + } + + public static void VstrA1(CodeGenContext context, uint encoding) + { + InstCondb28w4Ub23w1Db22w1Rnb16w4Vdb12w4Sizeb8w2Imm8b0w8 inst = new(encoding); + + InstEmitNeonMemory.Vstr(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), inst.Rn, inst.Imm8, inst.U != 0, inst.Size); + } + + public static void VstrT1(CodeGenContext context, uint encoding) + { + InstUb23w1Db22w1Rnb16w4Vdb12w4Sizeb8w2Imm8b0w8 inst = new(encoding); + + InstEmitNeonMemory.Vstr(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), inst.Rn, inst.Imm8, inst.U != 0, inst.Size); + } + + public static void VsubhnA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vsubhn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VsubhnT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vsubhn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size); + } + + public static void VsublA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vsubl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VsublT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vsubl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VsubwA1(CodeGenContext context, uint encoding) + { + InstUb24w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vsubw(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VsubwT1(CodeGenContext context, uint encoding) + { + InstUb28w1Db22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.Vsubw(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.U != 0, inst.Size); + } + + public static void VsubFA1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VsubF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VsubFA2(CodeGenContext context, uint encoding) + { + InstCondb28w4Db22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VsubF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VsubFT1(CodeGenContext context, uint encoding) + { + InstDb22w1Szb20w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VsubF(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Sz, inst.Q); + } + + public static void VsubFT2(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Sizeb8w2Nb7w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitVfpArithmetic.VsubF(context, InstEmitCommon.CombineV(inst.Vd, inst.D, inst.Size), InstEmitCommon.CombineV(inst.Vn, inst.N, inst.Size), InstEmitCommon.CombineV(inst.Vm, inst.M, inst.Size), inst.Size); + } + + public static void VsubIA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VsubI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VsubIT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonArithmetic.VsubI(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VsudotSA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VsudotST1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VswpA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vswp(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VswpT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vswp(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Q); + } + + public static void VtblA1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Lenb8w2Nb7w1Opb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vtbl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Len); + } + + public static void VtblT1(CodeGenContext context, uint encoding) + { + InstDb22w1Vnb16w4Vdb12w4Lenb8w2Nb7w1Opb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vtbl(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Op != 0, inst.Len); + } + + public static void VtrnA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vtrn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VtrnT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vtrn(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VtstA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.Vtst(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VtstT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb20w2Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonCompare.Vtst(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vn, inst.N), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VudotA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VudotT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VudotSA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VudotST1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VummlaA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VummlaT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VusdotA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VusdotT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VusdotSA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VusdotST1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Qb6w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VusmmlaA1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VusmmlaT1(CodeGenContext context, uint encoding) + { + _ = new InstDb22w1Vnb16w4Vdb12w4Nb7w1Mb5w1Vmb0w4(encoding); + + throw new NotImplementedException(); + } + + public static void VuzpA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vuzp(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VuzpT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vuzp(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VzipA1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vzip(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void VzipT1(CodeGenContext context, uint encoding) + { + InstDb22w1Sizeb18w2Vdb12w4Qb6w1Mb5w1Vmb0w4 inst = new(encoding); + + InstEmitNeonMove.Vzip(context, InstEmitCommon.CombineV(inst.Vd, inst.D), InstEmitCommon.CombineV(inst.Vm, inst.M), inst.Size, inst.Q); + } + + public static void WfeA1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Wfe(); + } + + public static void WfeT1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Wfe(); + } + + public static void WfeT2(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Wfe(); + } + + public static void WfiA1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Wfi(); + } + + public static void WfiT1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Wfi(); + } + + public static void WfiT2(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Wfi(); + } + + public static void YieldA1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Yield(); + } + + public static void YieldT1(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Yield(); + } + + public static void YieldT2(CodeGenContext context, uint encoding) + { + context.Arm64Assembler.Yield(); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitAbsDiff.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitAbsDiff.cs new file mode 100644 index 00000000..f8100503 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitAbsDiff.cs @@ -0,0 +1,87 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitAbsDiff + { + public static void Usad8(CodeGenContext context, uint rd, uint rn, uint rm) + { + using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + for (int b = 0; b < 4; b++) + { + context.Arm64Assembler.Ubfx(tempN.Operand, rnOperand, b * 8, 8); + context.Arm64Assembler.Ubfx(tempM.Operand, rmOperand, b * 8, 8); + + Operand dest = b == 0 ? tempD.Operand : tempD2.Operand; + + context.Arm64Assembler.Sub(dest, tempN.Operand, tempM.Operand); + + EmitAbs(context, dest); + + if (b > 0) + { + if (b < 3) + { + context.Arm64Assembler.Add(tempD.Operand, tempD.Operand, dest); + } + else + { + context.Arm64Assembler.Add(rdOperand, tempD.Operand, dest); + } + } + } + } + + public static void Usada8(CodeGenContext context, uint rd, uint rn, uint rm, uint ra) + { + using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + Operand raOperand = InstEmitCommon.GetInputGpr(context, ra); + + for (int b = 0; b < 4; b++) + { + context.Arm64Assembler.Ubfx(tempN.Operand, rnOperand, b * 8, 8); + context.Arm64Assembler.Ubfx(tempM.Operand, rmOperand, b * 8, 8); + + Operand dest = b == 0 ? tempD.Operand : tempD2.Operand; + + context.Arm64Assembler.Sub(dest, tempN.Operand, tempM.Operand); + + EmitAbs(context, dest); + + if (b > 0) + { + context.Arm64Assembler.Add(tempD.Operand, tempD.Operand, dest); + } + } + + context.Arm64Assembler.Add(rdOperand, tempD.Operand, raOperand); + } + + private static void EmitAbs(CodeGenContext context, Operand value) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + // r = (value + ((int)value >> 31)) ^ ((int)value >> 31). + // Subtracts 1 and then inverts the value if the sign bit is set, same as a conditional negation. + + context.Arm64Assembler.Add(tempRegister.Operand, value, value, ArmShiftType.Asr, 31); + context.Arm64Assembler.Eor(value, tempRegister.Operand, value, ArmShiftType.Asr, 31); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitAlu.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitAlu.cs new file mode 100644 index 00000000..c0762819 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitAlu.cs @@ -0,0 +1,1105 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System; +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitAlu + { + private const uint Imm12Limit = 0x1000; + + public static void AdcI(CodeGenContext context, uint rd, uint rn, uint imm, bool s) + { + EmitI(context, s ? context.Arm64Assembler.Adcs : context.Arm64Assembler.Adc, rd, rn, imm, s); + } + + public static void AdcR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) + { + EmitR(context, s ? context.Arm64Assembler.Adcs : context.Arm64Assembler.Adc, rd, rn, rm, sType, imm5, s); + } + + public static void AdcRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) + { + EmitRr(context, s ? context.Arm64Assembler.Adcs : context.Arm64Assembler.Adc, rd, rn, rm, sType, rs, s); + } + + public static void AddI(CodeGenContext context, uint rd, uint rn, uint imm, bool s) + { + EmitArithmeticI(context, s ? context.Arm64Assembler.Adds : context.Arm64Assembler.Add, rd, rn, imm, s); + } + + public static void AddR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) + { + EmitArithmeticR(context, s ? context.Arm64Assembler.Adds : context.Arm64Assembler.Add, rd, rn, rm, sType, imm5, s); + } + + public static void AddRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) + { + EmitRr(context, s ? context.Arm64Assembler.Adds : context.Arm64Assembler.Add, rd, rn, rm, sType, rs, s); + } + + public static void Adr(CodeGenContext context, uint rd, uint imm, bool add) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + + uint pc = context.Pc & ~3u; + + if (add) + { + pc += imm; + } + else + { + pc -= imm; + } + + context.Arm64Assembler.Mov(rdOperand, pc); + } + + public static void AndI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s) + { + EmitLogicalI(context, s ? context.Arm64Assembler.Ands : context.Arm64Assembler.And, rd, rn, imm, immRotated, s); + } + + public static void AndR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) + { + EmitLogicalR(context, s ? context.Arm64Assembler.Ands : context.Arm64Assembler.And, rd, rn, rm, sType, imm5, s); + } + + public static void AndRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) + { + EmitLogicalRr(context, s ? context.Arm64Assembler.Ands : context.Arm64Assembler.And, rd, rn, rm, sType, rs, s); + } + + public static void BicI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s) + { + if (!s && CodeGenCommon.TryEncodeBitMask(OperandType.I32, ~imm, out _, out _, out _)) + { + AndI(context, rd, rn, ~imm, immRotated, s); + } + else + { + EmitLogicalI(context, s ? context.Arm64Assembler.Bics : context.Arm64Assembler.Bic, rd, rn, imm, immRotated, s, immForm: false); + } + } + + public static void BicR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) + { + EmitLogicalR(context, s ? context.Arm64Assembler.Bics : context.Arm64Assembler.Bic, rd, rn, rm, sType, imm5, s); + } + + public static void BicRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) + { + EmitLogicalRr(context, s ? context.Arm64Assembler.Bics : context.Arm64Assembler.Bic, rd, rn, rm, sType, rs, s); + } + + public static void CmnI(CodeGenContext context, uint rn, uint imm) + { + EmitCompareI(context, context.Arm64Assembler.Cmn, rn, imm); + } + + public static void CmnR(CodeGenContext context, uint rn, uint rm, uint sType, uint imm5) + { + EmitCompareR(context, context.Arm64Assembler.Cmn, rn, rm, sType, imm5); + } + + public static void CmnRr(CodeGenContext context, uint rn, uint rm, uint sType, uint rs) + { + EmitCompareRr(context, context.Arm64Assembler.Cmn, rn, rm, sType, rs); + } + + public static void CmpI(CodeGenContext context, uint rn, uint imm) + { + EmitCompareI(context, context.Arm64Assembler.Cmp, rn, imm); + } + + public static void CmpR(CodeGenContext context, uint rn, uint rm, uint sType, uint imm5) + { + EmitCompareR(context, context.Arm64Assembler.Cmp, rn, rm, sType, imm5); + } + + public static void CmpRr(CodeGenContext context, uint rn, uint rm, uint sType, uint rs) + { + EmitCompareRr(context, context.Arm64Assembler.Cmp, rn, rm, sType, rs); + } + + public static void EorI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s) + { + EmitLogicalI(context, s ? context.Arm64Assembler.Eors : context.Arm64Assembler.Eor, rd, rn, imm, immRotated, s); + } + + public static void EorR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) + { + EmitLogicalR(context, s ? context.Arm64Assembler.Eors : context.Arm64Assembler.Eor, rd, rn, rm, sType, imm5, s); + } + + public static void EorRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) + { + EmitLogicalRr(context, s ? context.Arm64Assembler.Eors : context.Arm64Assembler.Eor, rd, rn, rm, sType, rs, s); + } + + public static void OrnI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s) + { + if (!s && CodeGenCommon.TryEncodeBitMask(OperandType.I32, ~imm, out _, out _, out _)) + { + OrrI(context, rd, rn, ~imm, immRotated, s); + } + else + { + EmitLogicalI(context, s ? context.Arm64Assembler.Orns : context.Arm64Assembler.Orn, rd, rn, imm, immRotated, s, immForm: false); + } + } + + public static void OrnR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) + { + EmitLogicalR(context, s ? context.Arm64Assembler.Orns : context.Arm64Assembler.Orn, rd, rn, rm, sType, imm5, s); + } + + public static void OrrI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s) + { + EmitLogicalI(context, s ? context.Arm64Assembler.Orrs : context.Arm64Assembler.Orr, rd, rn, imm, immRotated, s); + } + + public static void OrrR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) + { + EmitLogicalR(context, s ? context.Arm64Assembler.Orrs : context.Arm64Assembler.Orr, rd, rn, rm, sType, imm5, s); + } + + public static void OrrRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) + { + EmitLogicalRr(context, s ? context.Arm64Assembler.Orrs : context.Arm64Assembler.Orr, rd, rn, rm, sType, rs, s); + } + + public static void RsbI(CodeGenContext context, uint rd, uint rn, uint imm, bool s) + { + if (imm == 0) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + if (s) + { + context.Arm64Assembler.Negs(rdOperand, rnOperand); + context.SetNzcvModified(); + } + else + { + context.Arm64Assembler.Neg(rdOperand, rnOperand); + } + } + else + { + EmitI(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, imm, s, reverse: true); + } + } + + public static void RsbR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) + { + EmitR(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, rm, sType, imm5, s, reverse: true); + } + + public static void RsbRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) + { + EmitRr(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, rm, sType, rs, s, reverse: true); + } + + public static void RscI(CodeGenContext context, uint rd, uint rn, uint imm, bool s) + { + EmitI(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, imm, s, reverse: true); + } + + public static void RscR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) + { + EmitR(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, rm, sType, imm5, s, reverse: true); + } + + public static void RscRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) + { + EmitRr(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, rm, sType, rs, s, reverse: true); + } + + public static void SbcI(CodeGenContext context, uint rd, uint rn, uint imm, bool s) + { + EmitI(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, imm, s); + } + + public static void SbcR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) + { + EmitR(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, rm, sType, imm5, s); + } + + public static void SbcRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) + { + EmitRr(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, rm, sType, rs, s); + } + + public static void SubI(CodeGenContext context, uint rd, uint rn, uint imm, bool s) + { + EmitArithmeticI(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, imm, s); + } + + public static void SubR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) + { + EmitArithmeticR(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, rm, sType, imm5, s); + } + + public static void SubRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) + { + EmitRr(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, rm, sType, rs, s); + } + + public static void TeqI(CodeGenContext context, uint rn, uint imm, bool immRotated) + { + EmitLogicalI(context, (rnOperand, rmOperand) => EmitTeq(context, rnOperand, rmOperand), rn, imm, immRotated); + } + + public static void TeqR(CodeGenContext context, uint rn, uint rm, uint sType, uint imm5) + { + EmitLogicalR(context, (rnOperand, rmOperand) => EmitTeq(context, rnOperand, rmOperand), rn, rm, sType, imm5); + } + + public static void TeqRr(CodeGenContext context, uint rn, uint rm, uint sType, uint rs) + { + EmitLogicalRr(context, (rnOperand, rmOperand) => EmitTeq(context, rnOperand, rmOperand), rn, rm, sType, rs); + } + + public static void TstI(CodeGenContext context, uint rn, uint imm, bool immRotated) + { + EmitLogicalI(context, context.Arm64Assembler.Tst, rn, imm, immRotated); + } + + public static void TstR(CodeGenContext context, uint rn, uint rm, uint sType, uint imm5) + { + EmitLogicalR(context, context.Arm64Assembler.Tst, rn, rm, sType, imm5); + } + + public static void TstRr(CodeGenContext context, uint rn, uint rm, uint sType, uint rs) + { + EmitLogicalRr(context, context.Arm64Assembler.Tst, rn, rm, sType, rs); + } + + private static void EmitArithmeticI(CodeGenContext context, Action action, uint rd, uint rn, uint imm, bool s) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + if (imm < Imm12Limit) + { + Operand rmOperand = new(OperandKind.Constant, OperandType.I32, imm); + + action(rdOperand, rnOperand, rmOperand); + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Mov(tempRegister.Operand, imm); + + action(rdOperand, rnOperand, tempRegister.Operand); + } + + if (s) + { + context.SetNzcvModified(); + } + } + + private static void EmitArithmeticR( + CodeGenContext context, + Action action, + uint rd, + uint rn, + uint rm, + uint sType, + uint imm5, + bool s) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + if (CanShiftArithmetic(sType, imm5)) + { + action(rdOperand, rnOperand, rmOperand, (ArmShiftType)sType, (int)imm5); + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType); + + action(rdOperand, rnOperand, rmOperand, ArmShiftType.Lsl, 0); + } + + if (s) + { + context.SetNzcvModified(); + } + } + + private static void EmitCompareI(CodeGenContext context, Action action, uint rn, uint imm) + { + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + if (imm < Imm12Limit) + { + Operand rmOperand = new(OperandKind.Constant, OperandType.I32, imm); + + action(rnOperand, rmOperand); + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Mov(tempRegister.Operand, imm); + + action(rnOperand, tempRegister.Operand); + } + + context.SetNzcvModified(); + } + + private static void EmitCompareR( + CodeGenContext context, + Action action, + uint rn, + uint rm, + uint sType, + uint imm5) + { + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + if (CanShiftArithmetic(sType, imm5)) + { + action(rnOperand, rmOperand, (ArmShiftType)sType, (int)imm5); + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType); + + action(rnOperand, rmOperand, ArmShiftType.Lsl, 0); + } + + context.SetNzcvModified(); + } + + private static void EmitCompareRr(CodeGenContext context, Action action, uint rn, uint rm, uint sType, uint rs) + { + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + Operand rsOperand = InstEmitCommon.GetInputGpr(context, rs); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + rmOperand = GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType); + + action(rnOperand, rmOperand); + + context.SetNzcvModified(); + } + + private static void EmitI(CodeGenContext context, Action action, uint rd, uint rn, uint imm, bool s, bool reverse = false) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Mov(tempRegister.Operand, imm); + + if (reverse) + { + action(rdOperand, tempRegister.Operand, rnOperand); + } + else + { + action(rdOperand, rnOperand, tempRegister.Operand); + } + + if (s) + { + context.SetNzcvModified(); + } + } + + private static void EmitR(CodeGenContext context, Action action, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s, bool reverse = false) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType); + + if (reverse) + { + action(rdOperand, rmOperand, rnOperand); + } + else + { + action(rdOperand, rnOperand, rmOperand); + } + + if (s) + { + context.SetNzcvModified(); + } + } + + private static void EmitRr(CodeGenContext context, Action action, uint rd, uint rn, uint rm, uint sType, uint rs, bool s, bool reverse = false) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + Operand rsOperand = InstEmitCommon.GetInputGpr(context, rs); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + rmOperand = GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType); + + if (reverse) + { + action(rdOperand, rmOperand, rnOperand); + } + else + { + action(rdOperand, rnOperand, rmOperand); + } + + if (s) + { + context.SetNzcvModified(); + } + } + + private static void EmitLogicalI(CodeGenContext context, Action action, uint rn, uint imm, bool immRotated) + { + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + if (immRotated) + { + if ((imm & (1u << 31)) != 0) + { + context.Arm64Assembler.Orr(flagsRegister.Operand, flagsRegister.Operand, InstEmitCommon.Const(2)); + } + else + { + context.Arm64Assembler.Bfc(flagsRegister.Operand, 1, 1); + } + } + + if (CodeGenCommon.TryEncodeBitMask(OperandType.I32, imm, out _, out _, out _)) + { + action(rnOperand, InstEmitCommon.Const((int)imm)); + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Mov(tempRegister.Operand, imm); + + action(rnOperand, tempRegister.Operand); + } + + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + + context.SetNzcvModified(); + } + + private static void EmitLogicalI( + CodeGenContext context, + Action action, + uint rd, + uint rn, + uint imm, + bool immRotated, + bool s, + bool immForm = true) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + ScopedRegister flagsRegister = default; + + if (s) + { + flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + if (immRotated) + { + if ((imm & (1u << 31)) != 0) + { + context.Arm64Assembler.Orr(flagsRegister.Operand, flagsRegister.Operand, InstEmitCommon.Const(2)); + } + else + { + context.Arm64Assembler.Bfc(flagsRegister.Operand, 1, 1); + } + } + } + + if (imm == 0 || (immForm && CodeGenCommon.TryEncodeBitMask(OperandType.I32, imm, out _, out _, out _))) + { + action(rdOperand, rnOperand, InstEmitCommon.Const((int)imm)); + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Mov(tempRegister.Operand, imm); + + action(rdOperand, rnOperand, tempRegister.Operand); + } + + if (s) + { + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + + flagsRegister.Dispose(); + + context.SetNzcvModified(); + } + } + + private static void EmitLogicalR(CodeGenContext context, Action action, uint rn, uint rm, uint sType, uint imm5) + { + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType, flagsRegister.Operand); + + action(rnOperand, rmOperand); + + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + + context.SetNzcvModified(); + } + + private static void EmitLogicalR(CodeGenContext context, Action action, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + if (CanShift(sType, imm5) && !s) + { + action(rdOperand, rnOperand, rmOperand, (ArmShiftType)sType, (int)imm5); + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + ScopedRegister flagsRegister = default; + + if (s) + { + flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType, flagsRegister.Operand); + } + else + { + rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType, null); + } + + action(rdOperand, rnOperand, rmOperand, ArmShiftType.Lsl, 0); + + if (s) + { + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + + flagsRegister.Dispose(); + + context.SetNzcvModified(); + } + } + } + + private static void EmitLogicalRr(CodeGenContext context, Action action, uint rn, uint rm, uint sType, uint rs) + { + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + Operand rsOperand = InstEmitCommon.GetInputGpr(context, rs); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + rmOperand = GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType, flagsRegister.Operand); + + action(rnOperand, rmOperand); + + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + + context.SetNzcvModified(); + } + + private static void EmitLogicalRr(CodeGenContext context, Action action, uint rd, uint rn, uint rm, uint sType, uint rs, bool s) + { + if (s) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + Operand rsOperand = InstEmitCommon.GetInputGpr(context, rs); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + rmOperand = GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType, flagsRegister.Operand); + + action(rdOperand, rnOperand, rmOperand); + + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + + context.SetNzcvModified(); + } + else + { + EmitRr(context, action, rd, rn, rm, sType, rs, s); + } + } + + public static bool CanShiftArithmetic(uint sType, uint imm5) + { + // We can't encode ROR or RRX. + + return sType != 3 && (sType == 0 || imm5 != 0); + } + + public static bool CanShift(uint sType, uint imm5) + { + // We can encode all shift types directly, except RRX. + + return imm5 != 0 || sType == 0; + } + + public static Operand GetMShiftedByImmediate(CodeGenContext context, Operand dest, Operand m, uint imm, uint sType, Operand? carryOut = null) + { + int shift = (int)imm; + + if (shift == 0) + { + switch ((ArmShiftType)sType) + { + case ArmShiftType.Lsr: + shift = 32; + break; + case ArmShiftType.Asr: + shift = 32; + break; + case ArmShiftType.Ror: + shift = 1; + break; + } + } + + if (shift != 0) + { + switch ((ArmShiftType)sType) + { + case ArmShiftType.Lsl: + m = GetLslC(context, dest, m, carryOut, shift); + break; + case ArmShiftType.Lsr: + m = GetLsrC(context, dest, m, carryOut, shift); + break; + case ArmShiftType.Asr: + m = GetAsrC(context, dest, m, carryOut, shift); + break; + case ArmShiftType.Ror: + if (imm != 0) + { + m = GetRorC(context, dest, m, carryOut, shift); + } + else + { + m = GetRrxC(context, dest, m, carryOut); + } + break; + } + } + + return m; + } + + public static Operand GetMShiftedByReg(CodeGenContext context, Operand dest, Operand m, Operand s, uint sType, Operand? carryOut = null) + { + Operand shiftResult = m; + + switch ((ArmShiftType)sType) + { + case ArmShiftType.Lsl: + shiftResult = EmitLslC(context, dest, m, carryOut, s); + break; + case ArmShiftType.Lsr: + shiftResult = EmitLsrC(context, dest, m, carryOut, s); + break; + case ArmShiftType.Asr: + shiftResult = EmitAsrC(context, dest, m, carryOut, s); + break; + case ArmShiftType.Ror: + shiftResult = EmitRorC(context, dest, m, carryOut, s); + break; + } + + return shiftResult; + } + + private static void EmitIfHelper(CodeGenContext context, Operand boolValue, Action action, bool expected = true) + { + Debug.Assert(boolValue.Type == OperandType.I32); + + int branchInstructionPointer = context.CodeWriter.InstructionPointer; + + if (expected) + { + context.Arm64Assembler.Cbnz(boolValue, 0); + } + else + { + context.Arm64Assembler.Cbz(boolValue, 0); + } + + action(); + + int offset = context.CodeWriter.InstructionPointer - branchInstructionPointer; + Debug.Assert(offset >= 0); + Debug.Assert((offset << 13) >> 13 == offset); + uint branchInst = context.CodeWriter.ReadInstructionAt(branchInstructionPointer); + context.CodeWriter.WriteInstructionAt(branchInstructionPointer, branchInst | (uint)(offset << 5)); + } + + private static Operand EmitLslC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, Operand shift) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand mask = tempRegister.Operand; + context.Arm64Assembler.Uxtb(mask, shift); + context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(32)); + context.Arm64Assembler.Asr(mask, mask, InstEmitCommon.Const(31)); + + Operand dest64 = new(OperandKind.Register, OperandType.I64, dest.Value); + + if (carryOut.HasValue) + { + context.Arm64Assembler.Lslv(dest64, m, shift); + } + else + { + context.Arm64Assembler.Lslv(dest, m, shift); + } + + // If shift >= 32, force the result to 0. + context.Arm64Assembler.And(dest, dest, mask); + + if (carryOut.HasValue) + { + EmitIfHelper(context, shift, () => + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Uxtb(mask, shift); + context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(33)); + context.Arm64Assembler.Lsr(mask, mask, InstEmitCommon.Const(31)); + context.Arm64Assembler.Lsr(tempRegister.Operand, dest64, InstEmitCommon.Const(32)); + context.Arm64Assembler.And(tempRegister.Operand, tempRegister.Operand, mask); + + UpdateCarryFlag(context, tempRegister.Operand, carryOut.Value); + }, false); + } + + return dest; + } + + private static Operand GetLslC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + if ((uint)shift > 32) + { + return GetShiftByMoreThan32(context, carryOut); + } + else if (shift == 32) + { + if (carryOut.HasValue) + { + SetCarryMLsb(context, m, carryOut.Value); + } + + return InstEmitCommon.Const(0); + } + else + { + if (carryOut.HasValue) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Lsr(tempRegister.Operand, m, InstEmitCommon.Const(32 - shift)); + + UpdateCarryFlag(context, tempRegister.Operand, carryOut.Value); + } + + context.Arm64Assembler.Lsl(dest, m, InstEmitCommon.Const(shift)); + + return dest; + } + } + + private static Operand EmitLsrC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, Operand shift) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand mask = tempRegister.Operand; + context.Arm64Assembler.Uxtb(mask, shift); + context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(32)); + context.Arm64Assembler.Asr(mask, mask, InstEmitCommon.Const(31)); + + context.Arm64Assembler.Lsrv(dest, m, shift); + + // If shift >= 32, force the result to 0. + context.Arm64Assembler.And(dest, dest, mask); + + if (carryOut.HasValue) + { + EmitIfHelper(context, shift, () => + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Uxtb(mask, shift); + context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(33)); + context.Arm64Assembler.Lsr(mask, mask, InstEmitCommon.Const(31)); + context.Arm64Assembler.Sub(tempRegister.Operand, shift, InstEmitCommon.Const(1)); + context.Arm64Assembler.Lsrv(tempRegister.Operand, m, tempRegister.Operand); + context.Arm64Assembler.And(tempRegister.Operand, tempRegister.Operand, mask); + + UpdateCarryFlag(context, tempRegister.Operand, carryOut.Value); + }, false); + } + + return dest; + } + + public static Operand GetLsrC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + if ((uint)shift > 32) + { + return GetShiftByMoreThan32(context, carryOut); + } + else if (shift == 32) + { + if (carryOut.HasValue) + { + SetCarryMMsb(context, m, carryOut.Value); + } + + return InstEmitCommon.Const(0); + } + else + { + if (carryOut.HasValue) + { + SetCarryMShrOut(context, m, shift, carryOut.Value); + } + + context.Arm64Assembler.Lsr(dest, m, InstEmitCommon.Const(shift)); + + return dest; + } + } + + private static Operand GetShiftByMoreThan32(CodeGenContext context, Operand? carryOut) + { + if (carryOut.HasValue) + { + // Clear carry flag. + + context.Arm64Assembler.Bfc(carryOut.Value, 1, 1); + } + + return InstEmitCommon.Const(0); + } + + private static Operand EmitAsrC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, Operand shift) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand mask = tempRegister.Operand; + context.Arm64Assembler.Uxtb(mask, shift); + context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(31)); + context.Arm64Assembler.Orn(mask, shift, mask, ArmShiftType.Asr, 31); + + context.Arm64Assembler.Asrv(dest, m, mask); + + if (carryOut.HasValue) + { + EmitIfHelper(context, shift, () => + { + // If shift >= 32, carry should be equal to the MSB of Rm. + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Sub(tempRegister.Operand, mask, InstEmitCommon.Const(1)); + context.Arm64Assembler.Orr(tempRegister.Operand, tempRegister.Operand, mask, ArmShiftType.Asr, 31); + context.Arm64Assembler.Lsrv(tempRegister.Operand, m, tempRegister.Operand); + + UpdateCarryFlag(context, tempRegister.Operand, carryOut.Value); + }, false); + } + + return dest; + } + + private static Operand GetAsrC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + if ((uint)shift >= 32) + { + context.Arm64Assembler.Asr(dest, m, InstEmitCommon.Const(31)); + + if (carryOut.HasValue) + { + SetCarryMLsb(context, dest, carryOut.Value); + } + + return dest; + } + else + { + if (carryOut.HasValue) + { + SetCarryMShrOut(context, m, shift, carryOut.Value); + } + + context.Arm64Assembler.Asr(dest, m, InstEmitCommon.Const(shift)); + + return dest; + } + } + + private static Operand EmitRorC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, Operand shift) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32); + + context.Arm64Assembler.Rorv(dest, m, shift); + + if (carryOut.HasValue) + { + EmitIfHelper(context, shift, () => + { + SetCarryMMsb(context, m, carryOut.Value); + }, false); + } + + return dest; + } + + private static Operand GetRorC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + shift &= 0x1f; + + context.Arm64Assembler.Ror(dest, m, InstEmitCommon.Const(shift)); + + if (carryOut.HasValue) + { + SetCarryMMsb(context, dest, carryOut.Value); + } + + return dest; + } + + private static Operand GetRrxC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut) + { + Debug.Assert(m.Type == OperandType.I32); + + // Rotate right by 1 with carry. + + if (carryOut.HasValue) + { + SetCarryMLsb(context, m, carryOut.Value); + } + + context.Arm64Assembler.Mov(dest, m); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.MrsNzcv(tempRegister.Operand); + context.Arm64Assembler.Bfxil(dest, tempRegister.Operand, 29, 1); + context.Arm64Assembler.Ror(dest, dest, InstEmitCommon.Const(1)); + + return dest; + } + + private static void SetCarryMLsb(CodeGenContext context, Operand m, Operand carryOut) + { + Debug.Assert(m.Type == OperandType.I32); + + UpdateCarryFlag(context, m, carryOut); + } + + private static void SetCarryMMsb(CodeGenContext context, Operand m, Operand carryOut) + { + Debug.Assert(m.Type == OperandType.I32); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Lsr(tempRegister.Operand, m, InstEmitCommon.Const(31)); + + UpdateCarryFlag(context, tempRegister.Operand, carryOut); + } + + private static void SetCarryMShrOut(CodeGenContext context, Operand m, int shift, Operand carryOut) + { + Debug.Assert(m.Type == OperandType.I32); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Lsr(tempRegister.Operand, m, InstEmitCommon.Const(shift - 1)); + + UpdateCarryFlag(context, tempRegister.Operand, carryOut); + } + + private static void UpdateCarryFlag(CodeGenContext context, Operand value, Operand carryOut) + { + context.Arm64Assembler.Bfi(carryOut, value, 1, 1); + } + + private static void EmitTeq(CodeGenContext context, Operand rnOperand, Operand rmOperand) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Eors(tempRegister.Operand, rnOperand, rmOperand); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitBit.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitBit.cs new file mode 100644 index 00000000..3f91d45f --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitBit.cs @@ -0,0 +1,103 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitBit + { + public static void Bfc(CodeGenContext context, uint rd, uint lsb, uint msb) + { + // This is documented as "unpredictable". + if (msb < lsb) + { + return; + } + + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + + context.Arm64Assembler.Bfc(rdOperand, (int)lsb, (int)(msb - lsb + 1)); + } + + public static void Bfi(CodeGenContext context, uint rd, uint rn, uint lsb, uint msb) + { + // This is documented as "unpredictable". + if (msb < lsb) + { + return; + } + + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + context.Arm64Assembler.Bfi(rdOperand, rnOperand, (int)lsb, (int)(msb - lsb + 1)); + } + + public static void Clz(CodeGenContext context, uint rd, uint rm) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + context.Arm64Assembler.Clz(rdOperand, rmOperand); + } + + public static void Rbit(CodeGenContext context, uint rd, uint rm) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + context.Arm64Assembler.Rbit(rdOperand, rmOperand); + } + + public static void Rev(CodeGenContext context, uint rd, uint rm) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + context.Arm64Assembler.Rev(rdOperand, rmOperand); + } + + public static void Rev16(CodeGenContext context, uint rd, uint rm) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + context.Arm64Assembler.Rev16(rdOperand, rmOperand); + } + + public static void Revsh(CodeGenContext context, uint rd, uint rm) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + context.Arm64Assembler.Rev16(rdOperand, rmOperand); + context.Arm64Assembler.Sxth(rdOperand, rdOperand); + } + + public static void Sbfx(CodeGenContext context, uint rd, uint rn, uint lsb, uint widthMinus1) + { + // This is documented as "unpredictable". + if (lsb + widthMinus1 > 31) + { + return; + } + + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + context.Arm64Assembler.Sbfx(rdOperand, rnOperand, (int)lsb, (int)widthMinus1 + 1); + } + + public static void Ubfx(CodeGenContext context, uint rd, uint rn, uint lsb, uint widthMinus1) + { + // This is documented as "unpredictable". + if (lsb + widthMinus1 > 31) + { + return; + } + + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + context.Arm64Assembler.Ubfx(rdOperand, rnOperand, (int)lsb, (int)widthMinus1 + 1); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitCommon.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitCommon.cs new file mode 100644 index 00000000..1ec4c807 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitCommon.cs @@ -0,0 +1,263 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitCommon + { + public static Operand Const(int value) + { + return new(OperandKind.Constant, OperandType.I32, (uint)value); + } + + public static Operand GetInputGpr(CodeGenContext context, uint register) + { + Operand operand = context.RegisterAllocator.RemapGprRegister((int)register); + + if (register == RegisterUtils.PcRegister) + { + context.Arm64Assembler.Mov(operand, context.Pc); + } + + return operand; + } + + public static Operand GetOutputGpr(CodeGenContext context, uint register) + { + return context.RegisterAllocator.RemapGprRegister((int)register); + } + + public static void GetCurrentFlags(CodeGenContext context, Operand flagsOut) + { + context.Arm64Assembler.MrsNzcv(flagsOut); + context.Arm64Assembler.Lsr(flagsOut, flagsOut, Const(28)); + } + + public static void RestoreNzcvFlags(CodeGenContext context, Operand nzcvFlags) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Lsl(tempRegister.Operand, nzcvFlags, Const(28)); + context.Arm64Assembler.MsrNzcv(tempRegister.Operand); + } + + public static void RestoreCvFlags(CodeGenContext context, Operand cvFlags) + { + // Arm64 zeros the carry and overflow flags for logical operations, but Arm32 keeps them unchanged. + // This will restore carry and overflow after a operation has zeroed them. + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.MrsNzcv(tempRegister.Operand); + context.Arm64Assembler.Bfi(tempRegister.Operand, cvFlags, 28, 2); + context.Arm64Assembler.MsrNzcv(tempRegister.Operand); + } + + public static void SetThumbFlag(CodeGenContext context) + { + Operand ctx = InstEmitSystem.Register(context.RegisterAllocator.FixedContextRegister); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.LdrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + context.Arm64Assembler.Orr(tempRegister.Operand, tempRegister.Operand, Const(1 << 5)); + context.Arm64Assembler.StrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + } + + public static void SetThumbFlag(CodeGenContext context, Operand value) + { + Operand ctx = InstEmitSystem.Register(context.RegisterAllocator.FixedContextRegister); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.LdrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + context.Arm64Assembler.Bfi(tempRegister.Operand, value, 5, 1); + context.Arm64Assembler.StrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + } + + public static void ClearThumbFlag(CodeGenContext context) + { + Operand ctx = InstEmitSystem.Register(context.RegisterAllocator.FixedContextRegister); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.LdrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + context.Arm64Assembler.Bfc(tempRegister.Operand, 5, 1); + context.Arm64Assembler.StrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + } + + public static void EmitSigned16BitPair(CodeGenContext context, uint rd, uint rn, Action elementAction) + { + using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand rdOperand = GetOutputGpr(context, rd); + Operand rnOperand = GetInputGpr(context, rn); + + context.Arm64Assembler.Sxth(tempN.Operand, rnOperand); + elementAction(tempD.Operand, tempN.Operand); + context.Arm64Assembler.Uxth(tempD2.Operand, tempD.Operand); + + context.Arm64Assembler.Asr(tempN.Operand, rnOperand, Const(16)); + elementAction(tempD.Operand, tempN.Operand); + context.Arm64Assembler.Orr(rdOperand, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, 16); + } + + public static void EmitSigned16BitPair(CodeGenContext context, uint rd, uint rn, uint rm, Action elementAction) + { + using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand rdOperand = GetOutputGpr(context, rd); + Operand rnOperand = GetInputGpr(context, rn); + Operand rmOperand = GetInputGpr(context, rm); + + context.Arm64Assembler.Sxth(tempN.Operand, rnOperand); + context.Arm64Assembler.Sxth(tempM.Operand, rmOperand); + elementAction(tempD.Operand, tempN.Operand, tempM.Operand); + context.Arm64Assembler.Uxth(tempD2.Operand, tempD.Operand); + + context.Arm64Assembler.Asr(tempN.Operand, rnOperand, Const(16)); + context.Arm64Assembler.Asr(tempM.Operand, rmOperand, Const(16)); + elementAction(tempD.Operand, tempN.Operand, tempM.Operand); + context.Arm64Assembler.Orr(rdOperand, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, 16); + } + + public static void EmitSigned16BitXPair(CodeGenContext context, uint rd, uint rn, uint rm, Action elementAction) + { + using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand rdOperand = GetOutputGpr(context, rd); + Operand rnOperand = GetInputGpr(context, rn); + Operand rmOperand = GetInputGpr(context, rm); + + context.Arm64Assembler.Sxth(tempN.Operand, rnOperand); + context.Arm64Assembler.Asr(tempM.Operand, rmOperand, Const(16)); + elementAction(tempD.Operand, tempN.Operand, tempM.Operand, 0); + context.Arm64Assembler.Uxth(tempD2.Operand, tempD.Operand); + + context.Arm64Assembler.Asr(tempN.Operand, rnOperand, Const(16)); + context.Arm64Assembler.Sxth(tempM.Operand, rmOperand); + elementAction(tempD.Operand, tempN.Operand, tempM.Operand, 1); + context.Arm64Assembler.Orr(rdOperand, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, 16); + } + + public static void EmitSigned8BitPair(CodeGenContext context, uint rd, uint rn, uint rm, Action elementAction) + { + Emit8BitPair(context, rd, rn, rm, elementAction, unsigned: false); + } + + public static void EmitUnsigned16BitPair(CodeGenContext context, uint rd, uint rn, uint rm, Action elementAction) + { + using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand rdOperand = GetOutputGpr(context, rd); + Operand rnOperand = GetInputGpr(context, rn); + Operand rmOperand = GetInputGpr(context, rm); + + context.Arm64Assembler.Uxth(tempN.Operand, rnOperand); + context.Arm64Assembler.Uxth(tempM.Operand, rmOperand); + elementAction(tempD.Operand, tempN.Operand, tempM.Operand); + context.Arm64Assembler.Uxth(tempD2.Operand, tempD.Operand); + + context.Arm64Assembler.Lsr(tempN.Operand, rnOperand, Const(16)); + context.Arm64Assembler.Lsr(tempM.Operand, rmOperand, Const(16)); + elementAction(tempD.Operand, tempN.Operand, tempM.Operand); + context.Arm64Assembler.Orr(rdOperand, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, 16); + } + + public static void EmitUnsigned16BitXPair(CodeGenContext context, uint rd, uint rn, uint rm, Action elementAction) + { + using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand rdOperand = GetOutputGpr(context, rd); + Operand rnOperand = GetInputGpr(context, rn); + Operand rmOperand = GetInputGpr(context, rm); + + context.Arm64Assembler.Uxth(tempN.Operand, rnOperand); + context.Arm64Assembler.Lsr(tempM.Operand, rmOperand, Const(16)); + elementAction(tempD.Operand, tempN.Operand, tempM.Operand, 0); + context.Arm64Assembler.Uxth(tempD2.Operand, tempD.Operand); + + context.Arm64Assembler.Lsr(tempN.Operand, rnOperand, Const(16)); + context.Arm64Assembler.Uxth(tempM.Operand, rmOperand); + elementAction(tempD.Operand, tempN.Operand, tempM.Operand, 1); + context.Arm64Assembler.Orr(rdOperand, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, 16); + } + + public static void EmitUnsigned8BitPair(CodeGenContext context, uint rd, uint rn, uint rm, Action elementAction) + { + Emit8BitPair(context, rd, rn, rm, elementAction, unsigned: true); + } + + private static void Emit8BitPair(CodeGenContext context, uint rd, uint rn, uint rm, Action elementAction, bool unsigned) + { + using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand rdOperand = GetOutputGpr(context, rd); + Operand rnOperand = GetInputGpr(context, rn); + Operand rmOperand = GetInputGpr(context, rm); + + for (int b = 0; b < 4; b++) + { + if (unsigned) + { + context.Arm64Assembler.Ubfx(tempN.Operand, rnOperand, b * 8, 8); + context.Arm64Assembler.Ubfx(tempM.Operand, rmOperand, b * 8, 8); + } + else + { + context.Arm64Assembler.Sbfx(tempN.Operand, rnOperand, b * 8, 8); + context.Arm64Assembler.Sbfx(tempM.Operand, rmOperand, b * 8, 8); + } + + elementAction(tempD.Operand, tempN.Operand, tempM.Operand); + + if (b == 0) + { + context.Arm64Assembler.Uxtb(tempD2.Operand, tempD.Operand); + } + else if (b < 3) + { + context.Arm64Assembler.Uxtb(tempD.Operand, tempD.Operand); + context.Arm64Assembler.Orr(tempD2.Operand, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, b * 8); + } + else + { + context.Arm64Assembler.Orr(rdOperand, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, 24); + } + } + } + + public static uint CombineV(uint low4, uint high1, uint size) + { + return size == 3 ? CombineV(low4, high1) : CombineVF(high1, low4); + } + + public static uint CombineV(uint low4, uint high1) + { + return low4 | (high1 << 4); + } + + public static uint CombineVF(uint low1, uint high4) + { + return low1 | (high4 << 1); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitCrc32.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitCrc32.cs new file mode 100644 index 00000000..ee634188 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitCrc32.cs @@ -0,0 +1,26 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using System; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitCrc32 + { + public static void Crc32(CodeGenContext context, uint rd, uint rn, uint rm, uint sz) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + context.Arm64Assembler.Crc32(rdOperand, rnOperand, rmOperand, Math.Min(2, sz)); + } + + public static void Crc32c(CodeGenContext context, uint rd, uint rn, uint rm, uint sz) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + context.Arm64Assembler.Crc32c(rdOperand, rnOperand, rmOperand, Math.Min(2, sz)); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitDivide.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitDivide.cs new file mode 100644 index 00000000..31c96dc8 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitDivide.cs @@ -0,0 +1,25 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitDivide + { + public static void Sdiv(CodeGenContext context, uint rd, uint rn, uint rm) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + context.Arm64Assembler.Sdiv(rdOperand, rnOperand, rmOperand); + } + + public static void Udiv(CodeGenContext context, uint rd, uint rn, uint rm) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + context.Arm64Assembler.Udiv(rdOperand, rnOperand, rmOperand); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitExtension.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitExtension.cs new file mode 100644 index 00000000..dafe2974 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitExtension.cs @@ -0,0 +1,191 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitExtension + { + public static void Sxtab(CodeGenContext context, uint rd, uint rn, uint rm, uint rotate) + { + EmitRotated(context, ArmExtensionType.Sxtb, rd, rn, rm, rotate); + } + + public static void Sxtab16(CodeGenContext context, uint rd, uint rn, uint rm, uint rotate) + { + EmitExtendAccumulate8(context, rd, rn, rm, rotate, unsigned: false); + } + + public static void Sxtah(CodeGenContext context, uint rd, uint rn, uint rm, uint rotate) + { + EmitRotated(context, ArmExtensionType.Sxth, rd, rn, rm, rotate); + } + + public static void Sxtb(CodeGenContext context, uint rd, uint rm, uint rotate) + { + EmitRotated(context, context.Arm64Assembler.Sxtb, rd, rm, rotate); + } + + public static void Sxtb16(CodeGenContext context, uint rd, uint rm, uint rotate) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempRegister2 = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + if (rotate != 0) + { + context.Arm64Assembler.Ror(tempRegister.Operand, rmOperand, InstEmitCommon.Const((int)rotate * 8)); + context.Arm64Assembler.And(rdOperand, tempRegister.Operand, InstEmitCommon.Const(0xff00ff)); + } + else + { + context.Arm64Assembler.And(rdOperand, rmOperand, InstEmitCommon.Const(0xff00ff)); + } + + // Sign-extend by broadcasting sign bits. + context.Arm64Assembler.And(tempRegister.Operand, rdOperand, InstEmitCommon.Const(0x800080)); + context.Arm64Assembler.Lsl(tempRegister2.Operand, tempRegister.Operand, InstEmitCommon.Const(9)); + context.Arm64Assembler.Sub(tempRegister.Operand, tempRegister2.Operand, tempRegister.Operand); + context.Arm64Assembler.Orr(rdOperand, rdOperand, tempRegister.Operand); + } + + public static void Sxth(CodeGenContext context, uint rd, uint rm, uint rotate) + { + EmitRotated(context, context.Arm64Assembler.Sxth, rd, rm, rotate); + } + + public static void Uxtab(CodeGenContext context, uint rd, uint rn, uint rm, uint rotate) + { + EmitRotated(context, ArmExtensionType.Uxtb, rd, rn, rm, rotate); + } + + public static void Uxtab16(CodeGenContext context, uint rd, uint rn, uint rm, uint rotate) + { + EmitExtendAccumulate8(context, rd, rn, rm, rotate, unsigned: true); + } + + public static void Uxtah(CodeGenContext context, uint rd, uint rn, uint rm, uint rotate) + { + EmitRotated(context, ArmExtensionType.Uxth, rd, rn, rm, rotate); + } + + public static void Uxtb(CodeGenContext context, uint rd, uint rm, uint rotate) + { + EmitRotated(context, context.Arm64Assembler.Uxtb, rd, rm, rotate); + } + + public static void Uxtb16(CodeGenContext context, uint rd, uint rm, uint rotate) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + if (rotate != 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Ror(tempRegister.Operand, rmOperand, InstEmitCommon.Const((int)rotate * 8)); + context.Arm64Assembler.And(rdOperand, tempRegister.Operand, InstEmitCommon.Const(0xff00ff)); + } + else + { + context.Arm64Assembler.And(rdOperand, rmOperand, InstEmitCommon.Const(0xff00ff)); + } + } + + public static void Uxth(CodeGenContext context, uint rd, uint rm, uint rotate) + { + EmitRotated(context, context.Arm64Assembler.Uxth, rd, rm, rotate); + } + + private static void EmitRotated(CodeGenContext context, Action action, uint rd, uint rm, uint rotate) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + if (rotate != 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Ror(tempRegister.Operand, rmOperand, InstEmitCommon.Const((int)rotate * 8)); + action(rdOperand, tempRegister.Operand); + } + else + { + action(rdOperand, rmOperand); + } + } + + private static void EmitRotated(CodeGenContext context, ArmExtensionType extensionType, uint rd, uint rn, uint rm, uint rotate) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + if (rotate != 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Ror(tempRegister.Operand, rmOperand, InstEmitCommon.Const((int)rotate * 8)); + context.Arm64Assembler.Add(rdOperand, rnOperand, tempRegister.Operand, extensionType); + } + else + { + context.Arm64Assembler.Add(rdOperand, rnOperand, rmOperand, extensionType); + } + } + + private static void EmitExtendAccumulate8(CodeGenContext context, uint rd, uint rn, uint rm, uint rotate, bool unsigned) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + if (rotate != 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Ror(tempRegister.Operand, rmOperand, InstEmitCommon.Const((int)rotate * 8)); + + EmitExtendAccumulate8Core(context, rdOperand, rnOperand, tempRegister.Operand, unsigned); + } + else + { + EmitExtendAccumulate8Core(context, rdOperand, rnOperand, rmOperand, unsigned); + } + } + + private static void EmitExtendAccumulate8Core(CodeGenContext context, Operand rd, Operand rn, Operand rm, bool unsigned) + { + using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + if (unsigned) + { + context.Arm64Assembler.Uxth(tempN.Operand, rn); + } + else + { + context.Arm64Assembler.Sxth(tempN.Operand, rn); + } + + context.Arm64Assembler.Add(tempD.Operand, tempN.Operand, rm, unsigned ? ArmExtensionType.Uxtb : ArmExtensionType.Sxtb); + context.Arm64Assembler.Uxth(tempD2.Operand, tempD.Operand); + + if (unsigned) + { + context.Arm64Assembler.Lsr(tempN.Operand, rn, InstEmitCommon.Const(16)); + } + else + { + context.Arm64Assembler.Asr(tempN.Operand, rn, InstEmitCommon.Const(16)); + } + + context.Arm64Assembler.Lsr(tempD.Operand, rm, InstEmitCommon.Const(16)); + context.Arm64Assembler.Add(tempD.Operand, tempN.Operand, tempD.Operand, unsigned ? ArmExtensionType.Uxtb : ArmExtensionType.Sxtb); + context.Arm64Assembler.Orr(rd, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, 16); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs new file mode 100644 index 00000000..3b1ff5a2 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitFlow.cs @@ -0,0 +1,256 @@ +using ARMeilleure.Common; +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitFlow + { + private const int SpIndex = 31; + + public static void B(CodeGenContext context, int imm, ArmCondition condition) + { + context.AddPendingBranch(InstName.B, imm); + + if (condition == ArmCondition.Al) + { + context.Arm64Assembler.B(0); + } + else + { + context.Arm64Assembler.B(condition, 0); + } + } + + public static void Bl(CodeGenContext context, int imm, bool sourceIsThumb, bool targetIsThumb) + { + uint nextAddress = sourceIsThumb ? context.Pc | 1u : context.Pc - 4; + uint targetAddress = targetIsThumb ? context.Pc + (uint)imm : (context.Pc & ~3u) + (uint)imm; + + if (sourceIsThumb != targetIsThumb) + { + if (targetIsThumb) + { + InstEmitCommon.SetThumbFlag(context); + } + else + { + InstEmitCommon.ClearThumbFlag(context); + } + } + + context.AddPendingCall(targetAddress, nextAddress); + + context.Arm64Assembler.B(0); + } + + public static void Blx(CodeGenContext context, uint rm, bool sourceIsThumb) + { + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + InstEmitCommon.SetThumbFlag(context, rmOperand); + + uint nextAddress = sourceIsThumb ? (context.Pc - 2) | 1u : context.Pc - 4; + + context.AddPendingIndirectCall(rm, nextAddress); + + context.Arm64Assembler.B(0); + } + + public static void Bx(CodeGenContext context, uint rm) + { + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + InstEmitCommon.SetThumbFlag(context, rmOperand); + + context.AddPendingIndirectBranch(InstName.Bx, rm); + + context.Arm64Assembler.B(0); + } + + public static void Cbnz(CodeGenContext context, uint rn, int imm, bool op) + { + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + context.AddPendingBranch(InstName.Cbnz, imm); + + if (op) + { + context.Arm64Assembler.Cbnz(rnOperand, 0); + } + else + { + context.Arm64Assembler.Cbz(rnOperand, 0); + } + } + + public static void It(CodeGenContext context, uint firstCond, uint mask) + { + Debug.Assert(mask != 0); + + int instCount = 4 - BitOperations.TrailingZeroCount(mask); + + Span conditions = stackalloc ArmCondition[instCount]; + + int i = 0; + + for (int index = 5 - instCount; index < 4; index++) + { + bool invert = (mask & (1u << index)) != 0; + + if (invert) + { + conditions[i++] = ((ArmCondition)firstCond).Invert(); + } + else + { + conditions[i++] = (ArmCondition)firstCond; + } + } + + conditions[i] = (ArmCondition)firstCond; + + context.SetItBlockStart(conditions); + } + + public static void Tbb(CodeGenContext context, uint rn, uint rm, bool h) + { + context.Arm64Assembler.Mov(context.RegisterAllocator.RemapGprRegister(RegisterUtils.PcRegister), context.Pc); + + context.AddPendingTableBranch(rn, rm, h); + + context.Arm64Assembler.B(0); + } + + public unsafe static void WriteCallWithGuestAddress( + CodeWriter writer, + ref Assembler asm, + RegisterAllocator regAlloc, + TailMerger tailMerger, + Action writeEpilogue, + AddressTable funcTable, + IntPtr funcPtr, + int spillBaseOffset, + uint nextAddress, + Operand guestAddress, + bool isTail = false) + { + int tempRegister; + + if (guestAddress.Kind == OperandKind.Constant) + { + tempRegister = regAlloc.AllocateTempGprRegister(); + + asm.Mov(Register(tempRegister), guestAddress.Value); + asm.StrRiUn(Register(tempRegister), Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset); + + regAlloc.FreeTempGprRegister(tempRegister); + } + else + { + asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset); + } + + tempRegister = regAlloc.FixedContextRegister == 1 ? 2 : 1; + + if (!isTail) + { + WriteSpillSkipContext(ref asm, regAlloc, spillBaseOffset); + } + + Operand rn = Register(tempRegister); + + if (regAlloc.FixedContextRegister != 0) + { + asm.Mov(Register(0), Register(regAlloc.FixedContextRegister)); + } + + if (guestAddress.Kind == OperandKind.Constant && funcTable != null) + { + ulong funcPtrLoc = (ulong)Unsafe.AsPointer(ref funcTable.GetValue(guestAddress.Value)); + + asm.Mov(rn, funcPtrLoc & ~0xfffUL); + asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL)); + } + else + { + asm.Mov(rn, (ulong)funcPtr); + } + + if (isTail) + { + writeEpilogue(); + asm.Br(rn); + } + else + { + asm.Blr(rn); + + asm.Mov(rn, nextAddress); + asm.Cmp(Register(0), rn); + + tailMerger.AddConditionalReturn(writer, asm, ArmCondition.Ne); + + WriteFillSkipContext(ref asm, regAlloc, spillBaseOffset); + } + } + + public static void WriteSpillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset) + { + WriteSpillOrFillSkipContext(ref asm, regAlloc, spillOffset, spill: true); + } + + public static void WriteFillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset) + { + WriteSpillOrFillSkipContext(ref asm, regAlloc, spillOffset, spill: false); + } + + private static void WriteSpillOrFillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset, bool spill) + { + uint gprMask = regAlloc.UsedGprsMask & ((1u << regAlloc.FixedContextRegister) | (1u << regAlloc.FixedPageTableRegister)); + + while (gprMask != 0) + { + int reg = BitOperations.TrailingZeroCount(gprMask); + + if (reg < 31 && (gprMask & (2u << reg)) != 0 && spillOffset < RegisterSaveRestore.Encodable9BitsOffsetLimit) + { + if (spill) + { + asm.StpRiUn(Register(reg), Register(reg + 1), Register(SpIndex), spillOffset); + } + else + { + asm.LdpRiUn(Register(reg), Register(reg + 1), Register(SpIndex), spillOffset); + } + + gprMask &= ~(3u << reg); + spillOffset += 16; + } + else + { + if (spill) + { + asm.StrRiUn(Register(reg), Register(SpIndex), spillOffset); + } + else + { + asm.LdrRiUn(Register(reg), Register(SpIndex), spillOffset); + } + + gprMask &= ~(1u << reg); + spillOffset += 8; + } + } + } + + private static Operand Register(int register, OperandType type = OperandType.I64) + { + return new Operand(register, RegisterType.Integer, type); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitGE.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitGE.cs new file mode 100644 index 00000000..dffcc511 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitGE.cs @@ -0,0 +1,265 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitGE + { + public static void Sadd16(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAddSub(context, rd, rn, rm, is16Bit: true, add: true, unsigned: false); + } + + public static void Sadd8(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAddSub(context, rd, rn, rm, is16Bit: false, add: true, unsigned: false); + } + + public static void Sasx(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAsxSax(context, rd, rn, rm, isAsx: true, unsigned: false); + } + + public static void Sel(CodeGenContext context, uint rd, uint rn, uint rm) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + using ScopedRegister geFlags = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + ExtractGEFlags(context, geFlags.Operand); + + // Broadcast compact GE flags (one bit to one byte, 0b1111 -> 0x1010101). + context.Arm64Assembler.Mov(tempRegister.Operand, 0x204081u); + context.Arm64Assembler.Mul(geFlags.Operand, geFlags.Operand, tempRegister.Operand); + context.Arm64Assembler.And(geFlags.Operand, geFlags.Operand, InstEmitCommon.Const(0x1010101)); + + // Build mask from expanded flags (0x1010101 -> 0xFFFFFFFF). + context.Arm64Assembler.Lsl(tempRegister.Operand, geFlags.Operand, InstEmitCommon.Const(8)); + context.Arm64Assembler.Sub(geFlags.Operand, tempRegister.Operand, geFlags.Operand); + + // Result = (n & mask) | (m & ~mask). + context.Arm64Assembler.And(tempRegister.Operand, geFlags.Operand, rnOperand); + context.Arm64Assembler.Bic(rdOperand, rmOperand, geFlags.Operand); + context.Arm64Assembler.Orr(rdOperand, rdOperand, tempRegister.Operand); + } + + public static void Ssax(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAsxSax(context, rd, rn, rm, isAsx: false, unsigned: false); + } + + public static void Ssub16(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAddSub(context, rd, rn, rm, is16Bit: true, add: false, unsigned: false); + } + + public static void Ssub8(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAddSub(context, rd, rn, rm, is16Bit: false, add: false, unsigned: false); + } + + public static void Uadd16(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAddSub(context, rd, rn, rm, is16Bit: true, add: true, unsigned: true); + } + + public static void Uadd8(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAddSub(context, rd, rn, rm, is16Bit: false, add: true, unsigned: true); + } + + public static void Uasx(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAsxSax(context, rd, rn, rm, isAsx: true, unsigned: true); + } + + public static void Usax(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAsxSax(context, rd, rn, rm, isAsx: false, unsigned: true); + } + + public static void Usub16(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAddSub(context, rd, rn, rm, is16Bit: true, add: false, unsigned: true); + } + + public static void Usub8(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAddSub(context, rd, rn, rm, is16Bit: false, add: false, unsigned: true); + } + + private static void EmitAddSub(CodeGenContext context, uint rd, uint rn, uint rm, bool is16Bit, bool add, bool unsigned) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + using ScopedRegister geFlags = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + int e = 0; + + void Emit(Operand d, Operand n, Operand m) + { + if (add) + { + context.Arm64Assembler.Add(d, n, m); + } + else + { + context.Arm64Assembler.Sub(d, n, m); + } + + if (unsigned && add) + { + if (e == 0) + { + context.Arm64Assembler.Lsr(geFlags.Operand, d, InstEmitCommon.Const(is16Bit ? 16 : 8)); + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Lsr(tempRegister.Operand, d, InstEmitCommon.Const(is16Bit ? 16 : 8)); + context.Arm64Assembler.Orr(geFlags.Operand, geFlags.Operand, tempRegister.Operand, ArmShiftType.Lsl, e); + } + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Mvn(tempRegister.Operand, d); + + if (e == 0) + { + context.Arm64Assembler.Lsr(geFlags.Operand, tempRegister.Operand, InstEmitCommon.Const(31)); + } + else + { + context.Arm64Assembler.Lsr(tempRegister.Operand, tempRegister.Operand, InstEmitCommon.Const(31)); + context.Arm64Assembler.Orr(geFlags.Operand, geFlags.Operand, tempRegister.Operand, ArmShiftType.Lsl, e); + } + } + + e += is16Bit ? 2 : 1; + } + + if (is16Bit) + { + if (unsigned) + { + InstEmitCommon.EmitUnsigned16BitPair(context, rd, rn, rm, Emit); + } + else + { + InstEmitCommon.EmitSigned16BitPair(context, rd, rn, rm, Emit); + } + + // Duplicate bits. + context.Arm64Assembler.Orr(geFlags.Operand, geFlags.Operand, geFlags.Operand, ArmShiftType.Lsl, 1); + } + else + { + if (unsigned) + { + InstEmitCommon.EmitUnsigned8BitPair(context, rd, rn, rm, Emit); + } + else + { + InstEmitCommon.EmitSigned8BitPair(context, rd, rn, rm, Emit); + } + } + + UpdateGEFlags(context, geFlags.Operand); + } + + private static void EmitAsxSax(CodeGenContext context, uint rd, uint rn, uint rm, bool isAsx, bool unsigned) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + using ScopedRegister geFlags = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + void Emit(Operand d, Operand n, Operand m, int e) + { + bool add = e == (isAsx ? 1 : 0); + + if (add) + { + context.Arm64Assembler.Add(d, n, m); + } + else + { + context.Arm64Assembler.Sub(d, n, m); + } + + if (unsigned && add) + { + if (e == 0) + { + context.Arm64Assembler.Lsr(geFlags.Operand, d, InstEmitCommon.Const(16)); + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Lsr(tempRegister.Operand, d, InstEmitCommon.Const(16)); + context.Arm64Assembler.Orr(geFlags.Operand, geFlags.Operand, tempRegister.Operand, ArmShiftType.Lsl, e * 2); + } + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Mvn(tempRegister.Operand, d); + + if (e == 0) + { + context.Arm64Assembler.Lsr(geFlags.Operand, tempRegister.Operand, InstEmitCommon.Const(31)); + } + else + { + context.Arm64Assembler.Lsr(tempRegister.Operand, tempRegister.Operand, InstEmitCommon.Const(31)); + context.Arm64Assembler.Orr(geFlags.Operand, geFlags.Operand, tempRegister.Operand, ArmShiftType.Lsl, e * 2); + } + } + } + + if (unsigned) + { + InstEmitCommon.EmitUnsigned16BitXPair(context, rd, rn, rm, Emit); + } + else + { + InstEmitCommon.EmitSigned16BitXPair(context, rd, rn, rm, Emit); + } + + // Duplicate bits. + context.Arm64Assembler.Orr(geFlags.Operand, geFlags.Operand, geFlags.Operand, ArmShiftType.Lsl, 1); + + UpdateGEFlags(context, geFlags.Operand); + } + + public static void UpdateGEFlags(CodeGenContext context, Operand flags) + { + Operand ctx = InstEmitSystem.Register(context.RegisterAllocator.FixedContextRegister); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.LdrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + context.Arm64Assembler.Bfi(tempRegister.Operand, flags, 16, 4); + context.Arm64Assembler.StrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + } + + public static void ExtractGEFlags(CodeGenContext context, Operand flags) + { + Operand ctx = InstEmitSystem.Register(context.RegisterAllocator.FixedContextRegister); + + context.Arm64Assembler.LdrRiUn(flags, ctx, NativeContextOffsets.FlagsBaseOffset); + context.Arm64Assembler.Ubfx(flags, flags, 16, 4); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitHalve.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitHalve.cs new file mode 100644 index 00000000..567acfbf --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitHalve.cs @@ -0,0 +1,178 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitHalve + { + public static void Shadd16(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitHadd(context, rd, rn, rm, 0x7fff7fff, unsigned: false); + } + + public static void Shadd8(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitHadd(context, rd, rn, rm, 0x7f7f7f7f, unsigned: false); + } + + public static void Shsub16(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitHsub(context, rd, rn, rm, 0x7fff7fff, unsigned: false); + } + + public static void Shsub8(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitHsub(context, rd, rn, rm, 0x7f7f7f7f, unsigned: false); + } + + public static void Shasx(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitSigned16BitXPair(context, rd, rn, rm, (d, n, m, e) => + { + if (e == 0) + { + context.Arm64Assembler.Sub(d, n, m); + } + else + { + context.Arm64Assembler.Add(d, n, m); + } + + context.Arm64Assembler.Lsr(d, d, InstEmitCommon.Const(1)); + }); + } + + public static void Shsax(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitSigned16BitXPair(context, rd, rn, rm, (d, n, m, e) => + { + if (e == 0) + { + context.Arm64Assembler.Add(d, n, m); + } + else + { + context.Arm64Assembler.Sub(d, n, m); + } + + context.Arm64Assembler.Lsr(d, d, InstEmitCommon.Const(1)); + }); + } + + public static void Uhadd16(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitHadd(context, rd, rn, rm, 0x7fff7fff, unsigned: true); + } + + public static void Uhadd8(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitHadd(context, rd, rn, rm, 0x7f7f7f7f, unsigned: true); + } + + public static void Uhasx(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitUnsigned16BitXPair(context, rd, rn, rm, (d, n, m, e) => + { + if (e == 0) + { + context.Arm64Assembler.Sub(d, n, m); + } + else + { + context.Arm64Assembler.Add(d, n, m); + } + + context.Arm64Assembler.Lsr(d, d, InstEmitCommon.Const(1)); + }); + } + + public static void Uhsax(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitUnsigned16BitXPair(context, rd, rn, rm, (d, n, m, e) => + { + if (e == 0) + { + context.Arm64Assembler.Add(d, n, m); + } + else + { + context.Arm64Assembler.Sub(d, n, m); + } + + context.Arm64Assembler.Lsr(d, d, InstEmitCommon.Const(1)); + }); + } + + public static void Uhsub16(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitHsub(context, rd, rn, rm, 0x7fff7fff, unsigned: true); + } + + public static void Uhsub8(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitHsub(context, rd, rn, rm, 0x7f7f7f7f, unsigned: true); + } + + private static void EmitHadd(CodeGenContext context, uint rd, uint rn, uint rm, int mask, bool unsigned) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + using ScopedRegister res = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister carry = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + // This relies on the equality x+y == ((x&y) << 1) + (x^y). + // Note that x^y always contains the LSB of the result. + // Since we want to calculate (x+y)/2, we can instead calculate (x&y) + ((x^y)>>1). + // We mask by 0x7F/0x7FFF to remove the LSB so that it doesn't leak into the field below. + + context.Arm64Assembler.And(res.Operand, rmOperand, rnOperand); + context.Arm64Assembler.Eor(carry.Operand, rmOperand, rnOperand); + context.Arm64Assembler.Lsr(rdOperand, carry.Operand, InstEmitCommon.Const(1)); + context.Arm64Assembler.And(rdOperand, rdOperand, InstEmitCommon.Const(mask)); + context.Arm64Assembler.Add(rdOperand, rdOperand, res.Operand); + + if (!unsigned) + { + // Propagates the sign bit from (x^y)>>1 upwards by one. + context.Arm64Assembler.And(carry.Operand, carry.Operand, InstEmitCommon.Const(~mask)); + context.Arm64Assembler.Eor(rdOperand, rdOperand, carry.Operand); + } + } + + private static void EmitHsub(CodeGenContext context, uint rd, uint rn, uint rm, int mask, bool unsigned) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + using ScopedRegister carry = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister left = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister right = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + // This relies on the equality x-y == (x^y) - (((x^y)&y) << 1). + // Note that x^y always contains the LSB of the result. + // Since we want to calculate (x+y)/2, we can instead calculate ((x^y)>>1) - ((x^y)&y). + + context.Arm64Assembler.Eor(carry.Operand, rmOperand, rnOperand); + context.Arm64Assembler.Lsr(left.Operand, carry.Operand, InstEmitCommon.Const(1)); + context.Arm64Assembler.And(right.Operand, carry.Operand, rmOperand); + + // We must now perform a partitioned subtraction. + // We can do this because minuend contains 7/15 bit fields. + // We use the extra bit in minuend as a bit to borrow from; we set this bit. + // We invert this bit at the end as this tells us if that bit was borrowed from. + + context.Arm64Assembler.Orr(rdOperand, left.Operand, InstEmitCommon.Const(~mask)); + context.Arm64Assembler.Sub(rdOperand, rdOperand, right.Operand); + context.Arm64Assembler.Eor(rdOperand, rdOperand, InstEmitCommon.Const(~mask)); + + if (!unsigned) + { + // We then sign extend the result into this bit. + context.Arm64Assembler.And(carry.Operand, carry.Operand, InstEmitCommon.Const(~mask)); + context.Arm64Assembler.Eor(rdOperand, rdOperand, carry.Operand); + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs new file mode 100644 index 00000000..d8caee6e --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMemory.cs @@ -0,0 +1,1184 @@ +using ARMeilleure.Memory; +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System; +using System.Diagnostics; +using System.Numerics; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitMemory + { + private enum PrefetchType : uint + { + Pld = 0, + Pli = 1, + Pst = 2, + } + + public static void Lda(CodeGenContext context, uint rt, uint rn) + { + EmitMemoryInstruction(context, rt, rn, isStore: false, context.Arm64Assembler.Ldar); + } + + public static void Ldab(CodeGenContext context, uint rt, uint rn) + { + EmitMemoryInstruction(context, rt, rn, isStore: false, context.Arm64Assembler.Ldarb); + } + + public static void Ldaex(CodeGenContext context, uint rt, uint rn) + { + EmitMemoryInstruction(context, rt, rn, isStore: false, context.Arm64Assembler.Ldaxr); + } + + public static void Ldaexb(CodeGenContext context, uint rt, uint rn) + { + EmitMemoryInstruction(context, rt, rn, isStore: false, context.Arm64Assembler.Ldaxrb); + } + + public static void Ldaexd(CodeGenContext context, uint rt, uint rt2, uint rn) + { + EmitMemoryDWordInstruction(context, rt, rt2, rn, isStore: false, context.Arm64Assembler.Ldaxp); + } + + public static void Ldaexh(CodeGenContext context, uint rt, uint rn) + { + EmitMemoryInstruction(context, rt, rn, isStore: false, context.Arm64Assembler.Ldaxrh); + } + + public static void Ldah(CodeGenContext context, uint rt, uint rn) + { + EmitMemoryInstruction(context, rt, rn, isStore: false, context.Arm64Assembler.Ldarh); + } + + public static void LdcI(CodeGenContext context, uint rn, int imm, bool p, bool u, bool w) + { + // TODO. + } + + public static void LdcL(CodeGenContext context, uint imm, bool p, bool u, bool w) + { + // TODO. + } + + public static void Ldm(CodeGenContext context, uint rn, uint registerList, bool w) + { + Operand baseAddress = InstEmitCommon.GetInputGpr(context, rn); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, baseAddress); + + EmitMemoryMultipleInstructionCore( + context, + tempRegister.Operand, + registerList, + isStore: false, + context.Arm64Assembler.LdrRiUn, + context.Arm64Assembler.LdpRiUn); + + if (w) + { + Operand offset = InstEmitCommon.Const(BitOperations.PopCount(registerList) * 4); + + WriteAddShiftOffset(context.Arm64Assembler, baseAddress, baseAddress, offset, true, ArmShiftType.Lsl, 0); + } + } + + public static void Ldmda(CodeGenContext context, uint rn, uint registerList, bool w) + { + EmitMemoryMultipleDaInstruction(context, rn, registerList, w, isStore: false, context.Arm64Assembler.LdrRiUn, context.Arm64Assembler.LdpRiUn); + } + + public static void Ldmdb(CodeGenContext context, uint rn, uint registerList, bool w) + { + EmitMemoryMultipleDbInstruction(context, rn, registerList, w, isStore: false, context.Arm64Assembler.LdrRiUn, context.Arm64Assembler.LdpRiUn); + } + + public static void Ldmib(CodeGenContext context, uint rn, uint registerList, bool w) + { + EmitMemoryMultipleIbInstruction(context, rn, registerList, w, isStore: false, context.Arm64Assembler.LdrRiUn, context.Arm64Assembler.LdpRiUn); + } + + public static void LdrI(CodeGenContext context, uint rt, uint rn, int imm, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, imm, 2, p, u, w, isStore: false, context.Arm64Assembler.LdrRiUn, context.Arm64Assembler.Ldur); + } + + public static void LdrL(CodeGenContext context, uint rt, uint imm, bool p, bool u, bool w) + { + EmitMemoryLiteralInstruction(context, rt, imm, 2, p, u, w, context.Arm64Assembler.LdrRiUn); + } + + public static void LdrR(CodeGenContext context, uint rt, uint rn, uint rm, uint sType, uint imm5, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, rm, sType, imm5, p, u, w, isStore: false, context.Arm64Assembler.LdrRiUn, context.Arm64Assembler.Ldur); + } + + public static void LdrbI(CodeGenContext context, uint rt, uint rn, int imm, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, imm, 0, p, u, w, isStore: false, context.Arm64Assembler.LdrbRiUn, context.Arm64Assembler.Ldurb); + } + + public static void LdrbL(CodeGenContext context, uint rt, uint imm, bool p, bool u, bool w) + { + EmitMemoryLiteralInstruction(context, rt, imm, 0, p, u, w, context.Arm64Assembler.LdrbRiUn); + } + + public static void LdrbR(CodeGenContext context, uint rt, uint rn, uint rm, uint sType, uint imm5, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, rm, sType, imm5, p, u, w, isStore: false, context.Arm64Assembler.LdrbRiUn, context.Arm64Assembler.Ldurb); + } + + public static void LdrbtI(CodeGenContext context, uint rt, uint rn, int imm, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, imm, 0, !postIndex, u, false, isStore: false, context.Arm64Assembler.LdrbRiUn, context.Arm64Assembler.Ldurb); + } + + public static void LdrbtR(CodeGenContext context, uint rt, uint rn, uint rm, uint sType, uint imm5, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, rm, sType, imm5, !postIndex, u, false, isStore: false, context.Arm64Assembler.LdrbRiUn, context.Arm64Assembler.Ldurb); + } + + public static void LdrdI(CodeGenContext context, uint rt, uint rt2, uint rn, uint imm, bool p, bool u, bool w) + { + EmitMemoryDWordInstructionI(context, rt, rt2, rn, imm, p, u, w, isStore: false, context.Arm64Assembler.LdpRiUn); + } + + public static void LdrdL(CodeGenContext context, uint rt, uint rt2, uint imm, bool p, bool u, bool w) + { + EmitMemoryDWordLiteralInstruction(context, rt, rt2, imm, p, u, w, context.Arm64Assembler.LdpRiUn); + } + + public static void LdrdR(CodeGenContext context, uint rt, uint rt2, uint rn, uint rm, bool p, bool u, bool w) + { + EmitMemoryDWordInstructionR(context, rt, rt2, rn, rm, p, u, w, isStore: false, context.Arm64Assembler.LdpRiUn); + } + + public static void Ldrex(CodeGenContext context, uint rt, uint rn) + { + EmitMemoryInstruction(context, rt, rn, isStore: false, context.Arm64Assembler.Ldaxr); + } + + public static void Ldrexb(CodeGenContext context, uint rt, uint rn) + { + EmitMemoryInstruction(context, rt, rn, isStore: false, context.Arm64Assembler.Ldaxrb); + } + + public static void Ldrexd(CodeGenContext context, uint rt, uint rt2, uint rn) + { + EmitMemoryDWordInstruction(context, rt, rt2, rn, isStore: false, context.Arm64Assembler.Ldaxp); + } + + public static void Ldrexh(CodeGenContext context, uint rt, uint rn) + { + EmitMemoryInstruction(context, rt, rn, isStore: false, context.Arm64Assembler.Ldaxrh); + } + + public static void LdrhI(CodeGenContext context, uint rt, uint rn, int imm, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, imm, 1, p, u, w, isStore: false, context.Arm64Assembler.LdrhRiUn, context.Arm64Assembler.Ldurh); + } + + public static void LdrhL(CodeGenContext context, uint rt, uint imm, bool p, bool u, bool w) + { + EmitMemoryLiteralInstruction(context, rt, imm, 1, p, u, w, context.Arm64Assembler.LdrhRiUn); + } + + public static void LdrhR(CodeGenContext context, uint rt, uint rn, uint rm, uint sType, uint imm5, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, rm, sType, imm5, p, u, w, isStore: false, context.Arm64Assembler.LdrhRiUn, context.Arm64Assembler.Ldurh); + } + + public static void LdrhtI(CodeGenContext context, uint rt, uint rn, int imm, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, imm, 1, !postIndex, u, false, isStore: false, context.Arm64Assembler.LdrhRiUn, context.Arm64Assembler.Ldurh); + } + + public static void LdrhtR(CodeGenContext context, uint rt, uint rn, uint rm, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, rm, 0, 0, !postIndex, u, false, isStore: false, context.Arm64Assembler.LdrhRiUn, context.Arm64Assembler.Ldurh); + } + + public static void LdrsbI(CodeGenContext context, uint rt, uint rn, int imm, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, imm, 0, p, u, w, isStore: false, context.Arm64Assembler.LdrsbRiUn, context.Arm64Assembler.Ldursb); + } + + public static void LdrsbL(CodeGenContext context, uint rt, uint imm, bool p, bool u, bool w) + { + EmitMemoryLiteralInstruction(context, rt, imm, 0, p, u, w, context.Arm64Assembler.LdrsbRiUn); + } + + public static void LdrsbR(CodeGenContext context, uint rt, uint rn, uint rm, uint sType, uint imm5, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, rm, sType, imm5, p, u, w, isStore: false, context.Arm64Assembler.LdrsbRiUn, context.Arm64Assembler.Ldursb); + } + + public static void LdrsbtI(CodeGenContext context, uint rt, uint rn, int imm, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, imm, 0, !postIndex, u, false, isStore: false, context.Arm64Assembler.LdrsbRiUn, context.Arm64Assembler.Ldursb); + } + + public static void LdrsbtR(CodeGenContext context, uint rt, uint rn, uint rm, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, rm, 0, 0, !postIndex, u, false, isStore: false, context.Arm64Assembler.LdrsbRiUn, context.Arm64Assembler.Ldursb); + } + + public static void LdrshI(CodeGenContext context, uint rt, uint rn, int imm, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, imm, 1, p, u, w, isStore: false, context.Arm64Assembler.LdrshRiUn, context.Arm64Assembler.Ldursh); + } + + public static void LdrshL(CodeGenContext context, uint rt, uint imm, bool p, bool u, bool w) + { + EmitMemoryLiteralInstruction(context, rt, imm, 1, p, u, w, context.Arm64Assembler.LdrshRiUn); + } + + public static void LdrshR(CodeGenContext context, uint rt, uint rn, uint rm, uint sType, uint imm5, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, rm, sType, imm5, p, u, w, isStore: false, context.Arm64Assembler.LdrshRiUn, context.Arm64Assembler.Ldursh); + } + + public static void LdrshtI(CodeGenContext context, uint rt, uint rn, int imm, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, imm, 1, !postIndex, u, false, isStore: false, context.Arm64Assembler.LdrshRiUn, context.Arm64Assembler.Ldursh); + } + + public static void LdrshtR(CodeGenContext context, uint rt, uint rn, uint rm, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, rm, 0, 0, !postIndex, u, false, isStore: false, context.Arm64Assembler.LdrshRiUn, context.Arm64Assembler.Ldursh); + } + + public static void LdrtI(CodeGenContext context, uint rt, uint rn, int imm, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, imm, 2, !postIndex, u, false, isStore: false, context.Arm64Assembler.LdrRiUn, context.Arm64Assembler.Ldur); + } + + public static void LdrtR(CodeGenContext context, uint rt, uint rn, uint rm, uint sType, uint imm5, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, rm, sType, imm5, !postIndex, u, false, isStore: false, context.Arm64Assembler.LdrRiUn, context.Arm64Assembler.Ldur); + } + + public static void PldI(CodeGenContext context, uint rn, uint imm, bool u, bool r) + { + EmitMemoryPrefetchInstruction(context, rn, imm, u, r ? PrefetchType.Pld : PrefetchType.Pst); + } + + public static void PldL(CodeGenContext context, uint imm, bool u) + { + EmitMemoryPrefetchLiteralInstruction(context, imm, u, PrefetchType.Pld); + } + + public static void PldR(CodeGenContext context, uint rn, uint rm, uint sType, uint imm5, bool u, bool r) + { + EmitMemoryPrefetchInstruction(context, rn, rm, u, sType, imm5, r ? PrefetchType.Pld : PrefetchType.Pst); + } + + public static void PliI(CodeGenContext context, uint rn, uint imm, bool u) + { + EmitMemoryPrefetchInstruction(context, rn, imm, u, PrefetchType.Pli); + } + + public static void PliL(CodeGenContext context, uint imm, bool u) + { + EmitMemoryPrefetchLiteralInstruction(context, imm, u, PrefetchType.Pli); + } + + public static void PliR(CodeGenContext context, uint rn, uint rm, uint sType, uint imm5, bool u) + { + EmitMemoryPrefetchInstruction(context, rn, rm, u, sType, imm5, PrefetchType.Pli); + } + + public static void Stc(CodeGenContext context, uint rn, int imm, bool p, bool u, bool w) + { + // TODO. + } + + public static void Stl(CodeGenContext context, uint rt, uint rn) + { + EmitMemoryInstruction(context, rt, rn, isStore: true, context.Arm64Assembler.Stlr); + } + + public static void Stlb(CodeGenContext context, uint rt, uint rn) + { + EmitMemoryInstruction(context, rt, rn, isStore: true, context.Arm64Assembler.Stlrb); + } + + public static void Stlex(CodeGenContext context, uint rd, uint rt, uint rn) + { + EmitMemoryStrexInstruction(context, rd, rt, rn, context.Arm64Assembler.Stlxr); + } + + public static void Stlexb(CodeGenContext context, uint rd, uint rt, uint rn) + { + EmitMemoryStrexInstruction(context, rd, rt, rn, context.Arm64Assembler.Stlxrb); + } + + public static void Stlexd(CodeGenContext context, uint rd, uint rt, uint rt2, uint rn) + { + EmitMemoryDWordStrexInstruction(context, rd, rt, rt2, rn, context.Arm64Assembler.Stlxp); + } + + public static void Stlexh(CodeGenContext context, uint rd, uint rt, uint rn) + { + EmitMemoryStrexInstruction(context, rd, rt, rn, context.Arm64Assembler.Stlxrh); + } + + public static void Stlh(CodeGenContext context, uint rt, uint rn) + { + EmitMemoryInstruction(context, rt, rn, isStore: true, context.Arm64Assembler.Stlrh); + } + + public static void Stm(CodeGenContext context, uint rn, uint registerList, bool w) + { + Operand baseAddress = InstEmitCommon.GetInputGpr(context, rn); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, baseAddress); + + EmitMemoryMultipleInstructionCore( + context, + tempRegister.Operand, + registerList, + isStore: true, + context.Arm64Assembler.StrRiUn, + context.Arm64Assembler.StpRiUn); + + if (w) + { + Operand offset = InstEmitCommon.Const(BitOperations.PopCount(registerList) * 4); + + WriteAddShiftOffset(context.Arm64Assembler, baseAddress, baseAddress, offset, true, ArmShiftType.Lsl, 0); + } + } + + public static void Stmda(CodeGenContext context, uint rn, uint registerList, bool w) + { + EmitMemoryMultipleDaInstruction(context, rn, registerList, w, isStore: true, context.Arm64Assembler.StrRiUn, context.Arm64Assembler.StpRiUn); + } + + public static void Stmdb(CodeGenContext context, uint rn, uint registerList, bool w) + { + EmitMemoryMultipleDbInstruction(context, rn, registerList, w, isStore: true, context.Arm64Assembler.StrRiUn, context.Arm64Assembler.StpRiUn); + } + + public static void Stmib(CodeGenContext context, uint rn, uint registerList, bool w) + { + EmitMemoryMultipleIbInstruction(context, rn, registerList, w, isStore: true, context.Arm64Assembler.StrRiUn, context.Arm64Assembler.StpRiUn); + } + + public static void StrI(CodeGenContext context, uint rt, uint rn, int imm, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, imm, 2, p, u, w, isStore: true, context.Arm64Assembler.StrRiUn, context.Arm64Assembler.Stur); + } + + public static void StrR(CodeGenContext context, uint rt, uint rn, uint rm, uint sType, uint imm5, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, rm, sType, imm5, p, u, w, isStore: true, context.Arm64Assembler.StrRiUn, context.Arm64Assembler.Stur); + } + + public static void StrbI(CodeGenContext context, uint rt, uint rn, int imm, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, imm, 0, p, u, w, isStore: true, context.Arm64Assembler.StrbRiUn, context.Arm64Assembler.Sturb); + } + + public static void StrbR(CodeGenContext context, uint rt, uint rn, uint rm, uint sType, uint imm5, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, rm, sType, imm5, p, u, w, isStore: true, context.Arm64Assembler.StrbRiUn, context.Arm64Assembler.Sturb); + } + + public static void StrbtI(CodeGenContext context, uint rt, uint rn, int imm, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, imm, 0, !postIndex, u, false, isStore: true, context.Arm64Assembler.StrbRiUn, context.Arm64Assembler.Sturb); + } + + public static void StrbtR(CodeGenContext context, uint rt, uint rn, uint rm, uint sType, uint imm5, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, rm, sType, imm5, !postIndex, u, false, isStore: true, context.Arm64Assembler.StrbRiUn, context.Arm64Assembler.Sturb); + } + + public static void StrdI(CodeGenContext context, uint rt, uint rt2, uint rn, uint imm, bool p, bool u, bool w) + { + EmitMemoryDWordInstructionI(context, rt, rt2, rn, imm, p, u, w, isStore: true, context.Arm64Assembler.StpRiUn); + } + + public static void StrdR(CodeGenContext context, uint rt, uint rt2, uint rn, uint rm, bool p, bool u, bool w) + { + EmitMemoryDWordInstructionR(context, rt, rt2, rn, rm, p, u, w, isStore: true, context.Arm64Assembler.StpRiUn); + } + + public static void Strex(CodeGenContext context, uint rd, uint rt, uint rn) + { + EmitMemoryStrexInstruction(context, rd, rt, rn, context.Arm64Assembler.Stlxr); + } + + public static void Strexb(CodeGenContext context, uint rd, uint rt, uint rn) + { + EmitMemoryStrexInstruction(context, rd, rt, rn, context.Arm64Assembler.Stlxrb); + } + + public static void Strexd(CodeGenContext context, uint rd, uint rt, uint rt2, uint rn) + { + EmitMemoryDWordStrexInstruction(context, rd, rt, rt2, rn, context.Arm64Assembler.Stlxp); + } + + public static void Strexh(CodeGenContext context, uint rd, uint rt, uint rn) + { + EmitMemoryStrexInstruction(context, rd, rt, rn, context.Arm64Assembler.Stlxrh); + } + + public static void StrhI(CodeGenContext context, uint rt, uint rn, int imm, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, imm, 1, p, u, w, isStore: true, context.Arm64Assembler.StrhRiUn, context.Arm64Assembler.Sturh); + } + + public static void StrhR(CodeGenContext context, uint rt, uint rn, uint rm, uint sType, uint imm5, bool p, bool u, bool w) + { + EmitMemoryInstruction(context, rt, rn, rm, sType, imm5, p, u, w, isStore: true, context.Arm64Assembler.StrhRiUn, context.Arm64Assembler.Sturh); + } + + public static void StrhtI(CodeGenContext context, uint rt, uint rn, int imm, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, imm, 1, !postIndex, u, false, isStore: true, context.Arm64Assembler.StrhRiUn, context.Arm64Assembler.Sturh); + } + + public static void StrhtR(CodeGenContext context, uint rt, uint rn, uint rm, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, rm, 0, 0, !postIndex, u, false, isStore: true, context.Arm64Assembler.StrhRiUn, context.Arm64Assembler.Sturh); + } + + public static void StrtI(CodeGenContext context, uint rt, uint rn, int imm, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, imm, 2, !postIndex, u, false, isStore: true, context.Arm64Assembler.StrRiUn, context.Arm64Assembler.Stur); + } + + public static void StrtR(CodeGenContext context, uint rt, uint rn, uint rm, uint sType, uint imm5, bool postIndex, bool u) + { + EmitMemoryInstruction(context, rt, rn, rm, sType, imm5, !postIndex, u, false, isStore: true, context.Arm64Assembler.StrRiUn, context.Arm64Assembler.Stur); + } + + private static void EmitMemoryMultipleDaInstruction( + CodeGenContext context, + uint rn, + uint registerList, + bool w, + bool isStore, + Action writeInst, + Action writeInstPair) + { + Operand baseAddress = InstEmitCommon.GetInputGpr(context, rn); + Operand offset; + + if (registerList != 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + offset = InstEmitCommon.Const(BitOperations.PopCount(registerList) * 4 - 4); + + WriteAddShiftOffset(context.Arm64Assembler, tempRegister.Operand, baseAddress, offset, false, ArmShiftType.Lsl, 0); + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, tempRegister.Operand); + + EmitMemoryMultipleInstructionCore( + context, + tempRegister.Operand, + registerList, + isStore, + writeInst, + writeInstPair); + } + + if (w) + { + offset = InstEmitCommon.Const(BitOperations.PopCount(registerList) * 4); + + WriteAddShiftOffset(context.Arm64Assembler, baseAddress, baseAddress, offset, false, ArmShiftType.Lsl, 0); + } + } + + private static void EmitMemoryMultipleDbInstruction( + CodeGenContext context, + uint rn, + uint registerList, + bool w, + bool isStore, + Action writeInst, + Action writeInstPair) + { + Operand baseAddress = InstEmitCommon.GetInputGpr(context, rn); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand offset = InstEmitCommon.Const(BitOperations.PopCount(registerList) * 4); + + bool writesToRn = (registerList & (1u << (int)rn)) != 0; + + if (w && !writesToRn) + { + WriteAddShiftOffset(context.Arm64Assembler, baseAddress, baseAddress, offset, false, ArmShiftType.Lsl, 0); + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, baseAddress); + } + else + { + WriteAddShiftOffset(context.Arm64Assembler, tempRegister.Operand, baseAddress, offset, false, ArmShiftType.Lsl, 0); + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, tempRegister.Operand); + } + + EmitMemoryMultipleInstructionCore( + context, + tempRegister.Operand, + registerList, + isStore, + writeInst, + writeInstPair); + + if (w && writesToRn) + { + WriteAddShiftOffset(context.Arm64Assembler, baseAddress, baseAddress, offset, false, ArmShiftType.Lsl, 0); + } + } + + private static void EmitMemoryMultipleIbInstruction( + CodeGenContext context, + uint rn, + uint registerList, + bool w, + bool isStore, + Action writeInst, + Action writeInstPair) + { + Operand baseAddress = InstEmitCommon.GetInputGpr(context, rn); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand offset = InstEmitCommon.Const(4); + + WriteAddShiftOffset(context.Arm64Assembler, tempRegister.Operand, baseAddress, offset, true, ArmShiftType.Lsl, 0); + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, tempRegister.Operand); + + EmitMemoryMultipleInstructionCore( + context, + tempRegister.Operand, + registerList, + isStore, + writeInst, + writeInstPair); + + if (w) + { + offset = InstEmitCommon.Const(BitOperations.PopCount(registerList) * 4); + + WriteAddShiftOffset(context.Arm64Assembler, baseAddress, baseAddress, offset, true, ArmShiftType.Lsl, 0); + } + } + + private static void EmitMemoryMultipleInstructionCore( + CodeGenContext context, + Operand baseAddress, + uint registerList, + bool isStore, + Action writeInst, + Action writeInstPair) + { + uint registers = registerList; + int offs = 0; + + while (registers != 0) + { + int regIndex = BitOperations.TrailingZeroCount(registers); + + registers &= ~(1u << regIndex); + + Operand rt = isStore + ? InstEmitCommon.GetInputGpr(context, (uint)regIndex) + : InstEmitCommon.GetOutputGpr(context, (uint)regIndex); + + int regIndex2 = BitOperations.TrailingZeroCount(registers); + if (regIndex2 < 32) + { + registers &= ~(1u << regIndex2); + + Operand rt2 = isStore + ? InstEmitCommon.GetInputGpr(context, (uint)regIndex2) + : InstEmitCommon.GetOutputGpr(context, (uint)regIndex2); + + writeInstPair(rt, rt2, baseAddress, offs); + + offs += 8; + } + else + { + writeInst(rt, baseAddress, offs); + + offs += 4; + } + } + } + + private static void EmitMemoryInstruction( + CodeGenContext context, + uint rt, + uint rn, + int imm, + int scale, + bool p, + bool u, + bool w, + bool isStore, + Action writeInst, + Action writeInstUnscaled) + { + Operand rtOperand = isStore ? InstEmitCommon.GetInputGpr(context, rt) : InstEmitCommon.GetOutputGpr(context, rt); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand offset = InstEmitCommon.Const(imm); + + EmitMemoryInstruction(context, writeInst, writeInstUnscaled, rtOperand, rnOperand, offset, scale, p, u, w); + } + + private static void EmitMemoryInstruction( + CodeGenContext context, + uint rt, + uint rn, + uint rm, + uint sType, + uint imm5, + bool p, + bool u, + bool w, + bool isStore, + Action writeInst, + Action writeInstUnscaled) + { + Operand rtOperand = isStore ? InstEmitCommon.GetInputGpr(context, rt) : InstEmitCommon.GetOutputGpr(context, rt); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + EmitMemoryInstruction(context, writeInst, writeInstUnscaled, rtOperand, rnOperand, rmOperand, 0, p, u, w, (ArmShiftType)sType, (int)imm5); + } + + private static void EmitMemoryInstruction(CodeGenContext context, uint rt, uint rn, bool isStore, Action action) + { + Operand rtOperand = isStore ? InstEmitCommon.GetInputGpr(context, rt) : InstEmitCommon.GetOutputGpr(context, rt); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, rnOperand); + + action(rtOperand, tempRegister.Operand); + } + + private static void EmitMemoryDWordInstruction(CodeGenContext context, uint rt, uint rt2, uint rn, bool isStore, Action action) + { + Operand rtOperand = isStore ? InstEmitCommon.GetInputGpr(context, rt) : InstEmitCommon.GetOutputGpr(context, rt); + Operand rt2Operand = isStore ? InstEmitCommon.GetInputGpr(context, rt2) : InstEmitCommon.GetOutputGpr(context, rt2); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, rnOperand); + + action(rtOperand, rt2Operand, tempRegister.Operand); + } + + private static void EmitMemoryDWordInstructionI( + CodeGenContext context, + uint rt, + uint rt2, + uint rn, + uint imm, + bool p, + bool u, + bool w, + bool isStore, + Action action) + { + Operand rtOperand = isStore ? InstEmitCommon.GetInputGpr(context, rt) : InstEmitCommon.GetOutputGpr(context, rt); + Operand rt2Operand = isStore ? InstEmitCommon.GetInputGpr(context, rt2) : InstEmitCommon.GetOutputGpr(context, rt2); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand offset = InstEmitCommon.Const((int)imm); + + EmitMemoryDWordInstruction(context, rtOperand, rt2Operand, rnOperand, offset, p, u, w, action); + } + + private static void EmitMemoryDWordInstructionR( + CodeGenContext context, + uint rt, + uint rt2, + uint rn, + uint rm, + bool p, + bool u, + bool w, + bool isStore, + Action action) + { + Operand rtOperand = isStore ? InstEmitCommon.GetInputGpr(context, rt) : InstEmitCommon.GetOutputGpr(context, rt); + Operand rt2Operand = isStore ? InstEmitCommon.GetInputGpr(context, rt2) : InstEmitCommon.GetOutputGpr(context, rt2); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + EmitMemoryDWordInstruction(context, rtOperand, rt2Operand, rnOperand, rmOperand, p, u, w, action); + } + + private static void EmitMemoryDWordInstruction( + CodeGenContext context, + Operand rt, + Operand rt2, + Operand baseAddress, + Operand offset, + bool index, + bool add, + bool wBack, + Action action) + { + Assembler asm = context.Arm64Assembler; + RegisterAllocator regAlloc = context.RegisterAllocator; + + if (index && !wBack) + { + // Offset. + + using ScopedRegister tempRegister = regAlloc.AllocateTempGprRegisterScoped(); + + int signedOffs = add ? offset.AsInt32() : -offset.AsInt32(); + int offs = 0; + + if (offset.Kind == OperandKind.Constant && offset.Value == 0) + { + WriteAddressTranslation(context.MemoryManagerType, regAlloc, asm, tempRegister.Operand, baseAddress); + } + else if (offset.Kind == OperandKind.Constant && CanFoldDWordOffset(context.MemoryManagerType, signedOffs)) + { + WriteAddressTranslation(context.MemoryManagerType, regAlloc, asm, tempRegister.Operand, baseAddress); + offs = signedOffs; + } + else + { + WriteAddShiftOffset(asm, tempRegister.Operand, baseAddress, offset, add, ArmShiftType.Lsl, 0); + WriteAddressTranslation(context.MemoryManagerType, regAlloc, asm, tempRegister.Operand, tempRegister.Operand); + } + + action(rt, rt2, tempRegister.Operand, offs); + } + else if (context.IsThumb ? !index && wBack : !index && !wBack) + { + // Post-indexed. + + using ScopedRegister tempRegister = regAlloc.AllocateTempGprRegisterScoped(); + + WriteAddressTranslation(context.MemoryManagerType, regAlloc, asm, tempRegister.Operand, baseAddress); + + action(rt, rt2, tempRegister.Operand, 0); + + WriteAddShiftOffset(asm, baseAddress, baseAddress, offset, add, ArmShiftType.Lsl, 0); + } + else if (index && wBack) + { + // Pre-indexed. + + using ScopedRegister tempRegister = regAlloc.AllocateTempGprRegisterScoped(); + + if (rt.Value == baseAddress.Value) + { + // If Rt and Rn are the same register, ensure we perform the write back after the read/write. + + WriteAddShiftOffset(asm, tempRegister.Operand, baseAddress, offset, add, ArmShiftType.Lsl, 0); + WriteAddressTranslation(context.MemoryManagerType, regAlloc, asm, tempRegister.Operand, tempRegister.Operand); + + action(rt, rt2, tempRegister.Operand, 0); + + context.Arm64Assembler.Mov(baseAddress, tempRegister.Operand); + } + else + { + WriteAddShiftOffset(asm, baseAddress, baseAddress, offset, add, ArmShiftType.Lsl, 0); + WriteAddressTranslation(context.MemoryManagerType, regAlloc, asm, tempRegister.Operand, baseAddress); + + action(rt, rt2, tempRegister.Operand, 0); + } + } + } + + private static void EmitMemoryStrexInstruction(CodeGenContext context, uint rd, uint rt, uint rn, Action action) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rtOperand = InstEmitCommon.GetInputGpr(context, rt); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, rnOperand); + + action(rdOperand, rtOperand, tempRegister.Operand); + } + + private static void EmitMemoryDWordStrexInstruction(CodeGenContext context, uint rd, uint rt, uint rt2, uint rn, Action action) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rtOperand = InstEmitCommon.GetInputGpr(context, rt); + Operand rt2Operand = InstEmitCommon.GetInputGpr(context, rt2); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, rnOperand); + + action(rdOperand, rtOperand, rt2Operand, tempRegister.Operand); + } + + private static void EmitMemoryInstruction( + CodeGenContext context, + Action writeInst, + Action writeInstUnscaled, + Operand rt, + Operand baseAddress, + Operand offset, + int scale, + bool index, + bool add, + bool wBack, + ArmShiftType shiftType = ArmShiftType.Lsl, + int shift = 0) + { + Assembler asm = context.Arm64Assembler; + RegisterAllocator regAlloc = context.RegisterAllocator; + + if (index && !wBack) + { + // Offset. + + using ScopedRegister tempRegister = regAlloc.AllocateTempGprRegisterScoped(); + + int signedOffs = add ? offset.AsInt32() : -offset.AsInt32(); + int offs = 0; + bool unscaled = false; + + if (offset.Kind == OperandKind.Constant && offset.Value == 0) + { + WriteAddressTranslation(context.MemoryManagerType, regAlloc, asm, tempRegister.Operand, baseAddress); + } + else if (offset.Kind == OperandKind.Constant && shift == 0 && CanFoldOffset(context.MemoryManagerType, signedOffs, scale, writeInstUnscaled != null, out unscaled)) + { + WriteAddressTranslation(context.MemoryManagerType, regAlloc, asm, tempRegister.Operand, baseAddress); + offs = signedOffs; + } + else + { + WriteAddShiftOffset(asm, tempRegister.Operand, baseAddress, offset, add, shiftType, shift); + WriteAddressTranslation(context.MemoryManagerType, regAlloc, asm, tempRegister.Operand, tempRegister.Operand); + } + + if (unscaled) + { + writeInstUnscaled(rt, tempRegister.Operand, offs); + } + else + { + writeInst(rt, tempRegister.Operand, offs); + } + } + else if (context.IsThumb ? !index && wBack : !index && !wBack) + { + // Post-indexed. + + if (rt.Type == offset.Type && rt.Value == offset.Value) + { + // If Rt and Rm are the same register, we must ensure we add the register offset (Rm) + // before the value is loaded, otherwise we will be adding the wrong value. + + if (rt.Type != baseAddress.Type || rt.Value != baseAddress.Value) + { + using ScopedRegister tempRegister = regAlloc.AllocateTempGprRegisterScoped(); + + WriteAddressTranslation(context.MemoryManagerType, regAlloc, asm, tempRegister.Operand, baseAddress); + WriteAddShiftOffset(asm, baseAddress, baseAddress, offset, add, shiftType, shift); + + writeInst(rt, tempRegister.Operand, 0); + } + else + { + using ScopedRegister tempRegister = regAlloc.AllocateTempGprRegisterScoped(); + using ScopedRegister tempRegister2 = regAlloc.AllocateTempGprRegisterScoped(); + + WriteAddressTranslation(context.MemoryManagerType, regAlloc, asm, tempRegister.Operand, baseAddress); + WriteAddShiftOffset(asm, tempRegister2.Operand, baseAddress, offset, add, shiftType, shift); + + writeInst(rt, tempRegister.Operand, 0); + + asm.Mov(baseAddress, tempRegister2.Operand); + } + } + else + { + using ScopedRegister tempRegister = regAlloc.AllocateTempGprRegisterScoped(); + + WriteAddressTranslation(context.MemoryManagerType, regAlloc, asm, tempRegister.Operand, baseAddress); + + writeInst(rt, tempRegister.Operand, 0); + + WriteAddShiftOffset(asm, baseAddress, baseAddress, offset, add, shiftType, shift); + } + } + else if (index && wBack) + { + // Pre-indexed. + + using ScopedRegister tempRegister = regAlloc.AllocateTempGprRegisterScoped(); + + if (rt.Value == baseAddress.Value) + { + // If Rt and Rn are the same register, ensure we perform the write back after the read/write. + + WriteAddShiftOffset(asm, tempRegister.Operand, baseAddress, offset, add, shiftType, shift); + WriteAddressTranslation(context.MemoryManagerType, regAlloc, asm, tempRegister.Operand, tempRegister.Operand); + + writeInst(rt, tempRegister.Operand, 0); + + context.Arm64Assembler.Mov(baseAddress, tempRegister.Operand); + } + else + { + WriteAddShiftOffset(asm, baseAddress, baseAddress, offset, add, shiftType, shift); + WriteAddressTranslation(context.MemoryManagerType, regAlloc, asm, tempRegister.Operand, baseAddress); + + writeInst(rt, tempRegister.Operand, 0); + } + } + else + { + Debug.Fail($"Invalid pre-index and write-back combination."); + } + } + + private static void EmitMemoryLiteralInstruction(CodeGenContext context, uint rt, uint imm, int scale, bool p, bool u, bool w, Action action) + { + if (!p || w) + { + EmitMemoryInstruction(context, rt, RegisterUtils.PcRegister, (int)imm, scale, p, u, w, isStore: false, action, null); + + return; + } + + Operand rtOperand = InstEmitCommon.GetOutputGpr(context, rt); + uint targetAddress = context.Pc & ~3u; + + if (u) + { + targetAddress += imm; + } + else + { + targetAddress -= imm; + } + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, targetAddress); + + action(rtOperand, tempRegister.Operand, 0); + } + + private static void EmitMemoryDWordLiteralInstruction(CodeGenContext context, uint rt, uint rt2, uint imm, bool p, bool u, bool w, Action action) + { + if (!p || w) + { + EmitMemoryDWordInstructionI(context, rt, rt2, RegisterUtils.PcRegister, imm, p, u, w, isStore: false, action); + + return; + } + + Operand rtOperand = InstEmitCommon.GetOutputGpr(context, rt); + Operand rt2Operand = InstEmitCommon.GetOutputGpr(context, rt2); + uint targetAddress = context.Pc & ~3u; + + if (u) + { + targetAddress += imm; + } + else + { + targetAddress -= imm; + } + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, targetAddress); + + action(rtOperand, rt2Operand, tempRegister.Operand, 0); + } + + private static void EmitMemoryPrefetchInstruction(CodeGenContext context, uint rn, uint imm, bool u, PrefetchType type) + { + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + int signedOffs = u ? (int)imm : -(int)imm; + int offs = 0; + bool unscaled = false; + + if (imm == 0) + { + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, rnOperand); + } + else if (CanFoldOffset(context.MemoryManagerType, signedOffs, 3, true, out unscaled)) + { + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, rnOperand); + offs = signedOffs; + } + else + { + WriteAddShiftOffset(context.Arm64Assembler, tempRegister.Operand, rnOperand, InstEmitCommon.Const((int)imm), u, ArmShiftType.Lsl, 0); + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, tempRegister.Operand); + } + + if (unscaled) + { + context.Arm64Assembler.Prfum(tempRegister.Operand, offs, (uint)type, 0, 0); + } + else + { + context.Arm64Assembler.PrfmI(tempRegister.Operand, offs, (uint)type, 0, 0); + } + } + + private static void EmitMemoryPrefetchInstruction(CodeGenContext context, uint rn, uint rm, bool u, uint sType, uint shift, PrefetchType type) + { + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + WriteAddShiftOffset(context.Arm64Assembler, tempRegister.Operand, rnOperand, rmOperand, u, (ArmShiftType)sType, (int)shift); + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, tempRegister.Operand); + + context.Arm64Assembler.PrfmI(tempRegister.Operand, 0, (uint)type, 0, 0); + } + + private static void EmitMemoryPrefetchLiteralInstruction(CodeGenContext context, uint imm, bool u, PrefetchType type) + { + uint targetAddress = context.Pc & ~3u; + + if (u) + { + targetAddress += imm; + } + else + { + targetAddress -= imm; + } + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, targetAddress); + + context.Arm64Assembler.PrfmI(tempRegister.Operand, 0, (uint)type, 0, 0); + } + + public static bool CanFoldOffset(MemoryManagerType mmType, int offset, int scale, bool hasUnscaled, out bool unscaled) + { + if (mmType != MemoryManagerType.HostMappedUnsafe) + { + unscaled = false; + + return false; + } + + int mask = (1 << scale) - 1; + + if ((offset & mask) == 0 && offset >= 0 && offset < 0x1000) + { + // We can use the unsigned, scaled encoding. + + unscaled = false; + + return true; + } + + // Check if we can use the signed, unscaled encoding. + + unscaled = hasUnscaled && offset >= -0x100 && offset < 0x100; + + return unscaled; + } + + private static bool CanFoldDWordOffset(MemoryManagerType mmType, int offset) + { + if (mmType != MemoryManagerType.HostMappedUnsafe) + { + return false; + } + + return offset >= 0 && offset < 0x40 && (offset & 3) == 0; + } + + private static void WriteAddressTranslation(MemoryManagerType mmType, RegisterAllocator regAlloc, in Assembler asm, Operand destination, uint guestAddress) + { + asm.Mov(destination, guestAddress); + + WriteAddressTranslation(mmType, regAlloc, asm, destination, destination); + } + + public static void WriteAddressTranslation(MemoryManagerType mmType, RegisterAllocator regAlloc, in Assembler asm, Operand destination, Operand guestAddress) + { + Operand destination64 = new(destination.Kind, OperandType.I64, destination.Value); + Operand basePointer = new(regAlloc.FixedPageTableRegister, RegisterType.Integer, OperandType.I64); + + // We don't need to mask the address for the safe mode, since it is already naturally limited to 32-bit + // and can never reach out of the guest address space. + + if (mmType.IsHostTracked()) + { + int tempRegister = regAlloc.AllocateTempGprRegister(); + + Operand pte = new(tempRegister, RegisterType.Integer, OperandType.I64); + + asm.Lsr(pte, guestAddress, new Operand(OperandKind.Constant, OperandType.I32, 12)); + asm.LdrRr(pte, basePointer, pte, ArmExtensionType.Uxtx, true); + asm.Add(destination64, pte, guestAddress); + + regAlloc.FreeTempGprRegister(tempRegister); + } + else if (mmType.IsHostMapped()) + { + asm.Add(destination64, basePointer, guestAddress); + } + else + { + throw new NotImplementedException(mmType.ToString()); + } + } + + public static void WriteAddShiftOffset(in Assembler asm, Operand rd, Operand rn, Operand offset, bool add, ArmShiftType shiftType, int shift) + { + Debug.Assert(offset.Kind != OperandKind.Constant || offset.AsInt32() >= 0); + + if (shiftType == ArmShiftType.Ror) + { + asm.Ror(rd, rn, InstEmitCommon.Const(shift & 31)); + + if (add) + { + asm.Add(rd, rd, offset, ArmShiftType.Lsl, 0); + } + else + { + asm.Sub(rd, rd, offset, ArmShiftType.Lsl, 0); + } + } + else + { + if (add) + { + asm.Add(rd, rn, offset, shiftType, shift); + } + else + { + asm.Sub(rd, rn, offset, shiftType, shift); + } + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMove.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMove.cs new file mode 100644 index 00000000..d57750fc --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMove.cs @@ -0,0 +1,349 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitMove + { + public static void MvnI(CodeGenContext context, uint rd, uint imm, bool immRotated, bool s) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + + if (s) + { + using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + if (immRotated) + { + if ((imm & (1u << 31)) != 0) + { + context.Arm64Assembler.Orr(flagsRegister.Operand, flagsRegister.Operand, InstEmitCommon.Const(1 << 29)); + } + else + { + context.Arm64Assembler.Bfc(flagsRegister.Operand, 29, 1); + } + } + + context.Arm64Assembler.Mov(rdOperand, ~imm); + context.Arm64Assembler.Tst(rdOperand, rdOperand); + + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + + context.SetNzcvModified(); + } + else + { + context.Arm64Assembler.Mov(rdOperand, ~imm); + } + } + + public static void MvnR(CodeGenContext context, uint rd, uint rm, uint sType, uint imm5, bool s) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + ScopedRegister flagsRegister = default; + + if (s) + { + flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + rmOperand = InstEmitAlu.GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType, flagsRegister.Operand); + } + else + { + rmOperand = InstEmitAlu.GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType); + } + + context.Arm64Assembler.Mvn(rdOperand, rmOperand); + + if (s) + { + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + + flagsRegister.Dispose(); + + context.SetNzcvModified(); + } + } + + public static void MvnRr(CodeGenContext context, uint rd, uint rm, uint sType, uint rs, bool s) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + Operand rsOperand = InstEmitCommon.GetInputGpr(context, rs); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + ScopedRegister flagsRegister = default; + + if (s) + { + flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + rmOperand = InstEmitAlu.GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType, flagsRegister.Operand); + } + else + { + rmOperand = InstEmitAlu.GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType); + } + + context.Arm64Assembler.Mvn(rdOperand, rmOperand); + + if (s) + { + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + + flagsRegister.Dispose(); + + context.SetNzcvModified(); + } + } + + public static void MovI(CodeGenContext context, uint rd, uint imm, bool immRotated, bool s) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + + if (s) + { + using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + if (immRotated) + { + if ((imm & (1u << 31)) != 0) + { + context.Arm64Assembler.Orr(flagsRegister.Operand, flagsRegister.Operand, InstEmitCommon.Const(2)); + } + else + { + context.Arm64Assembler.Bfc(flagsRegister.Operand, 1, 1); + } + } + + context.Arm64Assembler.Mov(rdOperand, imm); + context.Arm64Assembler.Tst(rdOperand, rdOperand); + + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + + context.SetNzcvModified(); + } + else + { + context.Arm64Assembler.Mov(rdOperand, imm); + } + } + + public static void MovR(CodeGenContext context, uint rd, uint rm, uint sType, uint imm5, bool s) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + if (InstEmitAlu.CanShift(sType, imm5) && !s) + { + if (imm5 != 0) + { + switch ((ArmShiftType)sType) + { + case ArmShiftType.Lsl: + context.Arm64Assembler.Lsl(rdOperand, rmOperand, InstEmitCommon.Const((int)imm5)); + break; + case ArmShiftType.Lsr: + context.Arm64Assembler.Lsr(rdOperand, rmOperand, InstEmitCommon.Const((int)imm5)); + break; + case ArmShiftType.Asr: + context.Arm64Assembler.Asr(rdOperand, rmOperand, InstEmitCommon.Const((int)imm5)); + break; + case ArmShiftType.Ror: + context.Arm64Assembler.Ror(rdOperand, rmOperand, InstEmitCommon.Const((int)imm5)); + break; + } + } + else + { + context.Arm64Assembler.Mov(rdOperand, rmOperand); + } + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + ScopedRegister flagsRegister = default; + + if (s) + { + flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + rmOperand = InstEmitAlu.GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType, flagsRegister.Operand); + } + else + { + rmOperand = InstEmitAlu.GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType, null); + } + + context.Arm64Assembler.Mov(rdOperand, rmOperand); + + if (s) + { + context.Arm64Assembler.Tst(rdOperand, rdOperand); + + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + + flagsRegister.Dispose(); + + context.SetNzcvModified(); + } + } + } + + public static void MovR(CodeGenContext context, uint cond, uint rd, uint rm, uint sType, uint imm5, bool s) + { + if (context.ConsumeSkipNextInstruction()) + { + return; + } + + if ((ArmCondition)cond >= ArmCondition.Al || s) + { + MovR(context, rd, rm, sType, imm5, s); + + return; + } + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + if (InstEmitAlu.CanShift(sType, imm5)) + { + if (imm5 != 0) + { + switch ((ArmShiftType)sType) + { + case ArmShiftType.Lsl: + context.Arm64Assembler.Lsl(tempRegister.Operand, rmOperand, InstEmitCommon.Const((int)imm5)); + break; + case ArmShiftType.Lsr: + context.Arm64Assembler.Lsr(tempRegister.Operand, rmOperand, InstEmitCommon.Const((int)imm5)); + break; + case ArmShiftType.Asr: + context.Arm64Assembler.Asr(tempRegister.Operand, rmOperand, InstEmitCommon.Const((int)imm5)); + break; + case ArmShiftType.Ror: + context.Arm64Assembler.Ror(tempRegister.Operand, rmOperand, InstEmitCommon.Const((int)imm5)); + break; + } + + context.Arm64Assembler.Csel(rdOperand, tempRegister.Operand, rdOperand, (ArmCondition)cond); + } + else + { + Operand other = rdOperand; + + InstInfo nextInstruction = context.PeekNextInstruction(); + + if (nextInstruction.Name == InstName.MovR) + { + // If this instruction is followed by another move with the inverse condition, + // we can just put it into the second operand of the CSEL instruction and skip the next move. + + InstCondb28w4Sb20w1Rdb12w4Imm5b7w5Stypeb5w2Rmb0w4 nextInst = new(nextInstruction.Encoding); + + if (nextInst.Rd == rd && + nextInst.S == 0 && + nextInst.Stype == 0 && + nextInst.Imm5 == 0 && + nextInst.Cond == (cond ^ 1u) && + nextInst.Rm != RegisterUtils.PcRegister) + { + other = InstEmitCommon.GetInputGpr(context, nextInst.Rm); + context.SetSkipNextInstruction(); + } + } + + context.Arm64Assembler.Csel(rdOperand, rmOperand, other, (ArmCondition)cond); + } + } + else + { + rmOperand = InstEmitAlu.GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType, null); + + context.Arm64Assembler.Csel(rdOperand, rmOperand, rdOperand, (ArmCondition)cond); + } + } + + public static void MovRr(CodeGenContext context, uint rd, uint rm, uint sType, uint rs, bool s) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + Operand rsOperand = InstEmitCommon.GetInputGpr(context, rs); + + if (!s) + { + InstEmitAlu.GetMShiftedByReg(context, rdOperand, rmOperand, rsOperand, sType); + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + rmOperand = InstEmitAlu.GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType, flagsRegister.Operand); + + context.Arm64Assembler.Mov(rdOperand, rmOperand); + context.Arm64Assembler.Tst(rdOperand, rdOperand); + + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + + context.SetNzcvModified(); + } + } + + public static void Movt(CodeGenContext context, uint rd, uint imm) + { + Operand rdOperand = InstEmitCommon.GetInputGpr(context, rd); + + context.Arm64Assembler.Movk(rdOperand, (int)imm, 1); + } + + public static void Pkh(CodeGenContext context, uint rd, uint rn, uint rm, bool tb, uint imm5) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + if (!tb && imm5 == 0) + { + context.Arm64Assembler.Extr(rdOperand, rnOperand, rmOperand, 16); + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + if (tb) + { + context.Arm64Assembler.Asr(tempRegister.Operand, rmOperand, InstEmitCommon.Const(imm5 == 0 ? 31 : (int)imm5)); + context.Arm64Assembler.Extr(rdOperand, tempRegister.Operand, rnOperand, 16); + } + else + { + context.Arm64Assembler.Lsl(tempRegister.Operand, rmOperand, InstEmitCommon.Const((int)imm5)); + context.Arm64Assembler.Extr(rdOperand, rnOperand, tempRegister.Operand, 16); + } + } + + context.Arm64Assembler.Ror(rdOperand, rdOperand, InstEmitCommon.Const(16)); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMultiply.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMultiply.cs new file mode 100644 index 00000000..042ab815 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitMultiply.cs @@ -0,0 +1,603 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using System; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitMultiply + { + public static void Mla(CodeGenContext context, uint rd, uint rn, uint rm, uint ra) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + Operand raOperand = InstEmitCommon.GetInputGpr(context, ra); + + context.Arm64Assembler.Madd(rdOperand, rnOperand, rmOperand, raOperand); + } + + public static void Mls(CodeGenContext context, uint rd, uint rn, uint rm, uint ra) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + Operand raOperand = InstEmitCommon.GetInputGpr(context, ra); + + context.Arm64Assembler.Msub(rdOperand, rnOperand, rmOperand, raOperand); + } + + public static void Mul(CodeGenContext context, uint rd, uint rn, uint rm, bool s) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + if (s) + { + using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + context.Arm64Assembler.Mul(rdOperand, rnOperand, rmOperand); + context.Arm64Assembler.Tst(rdOperand, rdOperand); + + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + + context.SetNzcvModified(); + } + else + { + context.Arm64Assembler.Mul(rdOperand, rnOperand, rmOperand); + } + } + + public static void Smlabb(CodeGenContext context, uint rd, uint rn, uint rm, uint ra, bool nHigh, bool mHigh) + { + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempA = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand tempM64 = new(OperandKind.Register, OperandType.I64, tempM.Operand.Value); + Operand tempA64 = new(OperandKind.Register, OperandType.I64, tempA.Operand.Value); + + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + Operand raOperand = InstEmitCommon.GetInputGpr(context, ra); + + SelectSignedHalfword(context, tempN.Operand, rnOperand, nHigh); + SelectSignedHalfword(context, tempM.Operand, rmOperand, mHigh); + + context.Arm64Assembler.Sxtw(tempA64, raOperand); + context.Arm64Assembler.Smaddl(tempN.Operand, tempN.Operand, tempM.Operand, tempA64); + + CheckResultOverflow(context, tempM64, tempN.Operand); + + context.Arm64Assembler.Mov(rdOperand, tempN.Operand); + } + + public static void Smlad(CodeGenContext context, uint rd, uint rn, uint rm, uint ra, bool x) + { + EmitSmladSmlsd(context, rd, rn, rm, ra, x, add: true); + } + + public static void Smlal(CodeGenContext context, uint rdLo, uint rdHi, uint rn, uint rm, bool s) + { + EmitMultiplyAddLong(context, context.Arm64Assembler.Smaddl, rdLo, rdHi, rn, rm, s); + } + + public static void Smlalbb(CodeGenContext context, uint rdLo, uint rdHi, uint rn, uint rm, bool nHigh, bool mHigh) + { + Operand rdLoOperand = InstEmitCommon.GetOutputGpr(context, rdLo); + Operand rdHiOperand = InstEmitCommon.GetOutputGpr(context, rdHi); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + Operand rdLoOperand64 = new(OperandKind.Register, OperandType.I64, rdLoOperand.Value); + Operand rdHiOperand64 = new(OperandKind.Register, OperandType.I64, rdHiOperand.Value); + + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempA = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + SelectSignedHalfword(context, tempN.Operand, rnOperand, nHigh); + SelectSignedHalfword(context, tempM.Operand, rmOperand, mHigh); + + Operand tempA64 = new(OperandKind.Register, OperandType.I64, tempA.Operand.Value); + + context.Arm64Assembler.Lsl(tempA64, rdHiOperand64, InstEmitCommon.Const(32)); + context.Arm64Assembler.Orr(tempA64, tempA64, rdLoOperand); + + context.Arm64Assembler.Smaddl(rdLoOperand64, tempN.Operand, tempM.Operand, tempA64); + + if (rdLo != rdHi) + { + context.Arm64Assembler.Lsr(rdHiOperand64, rdLoOperand64, InstEmitCommon.Const(32)); + } + + context.Arm64Assembler.Mov(rdLoOperand, rdLoOperand); // Zero-extend. + } + + public static void Smlald(CodeGenContext context, uint rdLo, uint rdHi, uint rn, uint rm, bool x) + { + EmitSmlaldSmlsld(context, rdLo, rdHi, rn, rm, x, add: true); + } + + public static void Smlawb(CodeGenContext context, uint rd, uint rn, uint rm, uint ra, bool mHigh) + { + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempA = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand tempN64 = new(OperandKind.Register, OperandType.I64, tempN.Operand.Value); + Operand tempM64 = new(OperandKind.Register, OperandType.I64, tempM.Operand.Value); + Operand tempA64 = new(OperandKind.Register, OperandType.I64, tempA.Operand.Value); + + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + Operand raOperand = InstEmitCommon.GetInputGpr(context, ra); + + SelectSignedHalfword(context, tempM.Operand, rmOperand, mHigh); + + context.Arm64Assembler.Sxtw(tempA64, raOperand); + context.Arm64Assembler.Lsl(tempA64, tempA64, InstEmitCommon.Const(16)); + context.Arm64Assembler.Smaddl(tempN.Operand, rnOperand, tempM.Operand, tempA64); + context.Arm64Assembler.Asr(tempN64, tempN64, InstEmitCommon.Const(16)); + + CheckResultOverflow(context, tempM64, tempN.Operand); + + context.Arm64Assembler.Mov(rdOperand, tempN.Operand); + } + + public static void Smlsd(CodeGenContext context, uint rd, uint rn, uint rm, uint ra, bool x) + { + EmitSmladSmlsd(context, rd, rn, rm, ra, x, add: false); + } + + public static void Smlsld(CodeGenContext context, uint rdLo, uint rdHi, uint rn, uint rm, bool x) + { + EmitSmlaldSmlsld(context, rdLo, rdHi, rn, rm, x, add: false); + } + + public static void Smmla(CodeGenContext context, uint rd, uint rn, uint rm, uint ra, bool r) + { + EmitSmmlaSmmls(context, rd, rn, rm, ra, r, add: true); + } + + public static void Smmls(CodeGenContext context, uint rd, uint rn, uint rm, uint ra, bool r) + { + EmitSmmlaSmmls(context, rd, rn, rm, ra, r, add: false); + } + + public static void Smmul(CodeGenContext context, uint rd, uint rn, uint rm, bool r) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + Operand rdOperand64 = new(OperandKind.Register, OperandType.I64, rdOperand.Value); + + context.Arm64Assembler.Smull(rdOperand64, rnOperand, rmOperand); + + if (r) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Mov(tempRegister.Operand, 0x80000000u); + context.Arm64Assembler.Add(rdOperand64, rdOperand64, tempRegister.Operand); + } + + context.Arm64Assembler.Lsr(rdOperand64, rdOperand64, InstEmitCommon.Const(32)); + } + + public static void Smuad(CodeGenContext context, uint rd, uint rn, uint rm, bool x) + { + EmitSmuadSmusd(context, rd, rn, rm, x, add: true); + } + + public static void Smulbb(CodeGenContext context, uint rd, uint rn, uint rm, bool nHigh, bool mHigh) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + Operand rdOperand64 = new(OperandKind.Register, OperandType.I64, rdOperand.Value); + + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + SelectSignedHalfword(context, tempN.Operand, rnOperand, nHigh); + SelectSignedHalfword(context, tempM.Operand, rmOperand, mHigh); + + context.Arm64Assembler.Smull(rdOperand64, tempN.Operand, tempM.Operand); + + context.Arm64Assembler.Mov(rdOperand, rdOperand); // Zero-extend. + } + + public static void Smull(CodeGenContext context, uint rdLo, uint rdHi, uint rn, uint rm, bool s) + { + EmitMultiplyLong(context, context.Arm64Assembler.Smull, rdLo, rdHi, rn, rm, s); + } + + public static void Smulwb(CodeGenContext context, uint rd, uint rn, uint rm, bool mHigh) + { + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand tempN64 = new(OperandKind.Register, OperandType.I64, tempN.Operand.Value); + Operand tempM64 = new(OperandKind.Register, OperandType.I64, tempM.Operand.Value); + + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + SelectSignedHalfword(context, tempM.Operand, rmOperand, mHigh); + + context.Arm64Assembler.Smull(tempN.Operand, rnOperand, tempM.Operand); + context.Arm64Assembler.Asr(tempN64, tempN64, InstEmitCommon.Const(16)); + + CheckResultOverflow(context, tempM64, tempN.Operand); + + context.Arm64Assembler.Mov(rdOperand, tempN.Operand); + } + + public static void Smusd(CodeGenContext context, uint rd, uint rn, uint rm, bool x) + { + EmitSmuadSmusd(context, rd, rn, rm, x, add: false); + } + + public static void Umaal(CodeGenContext context, uint rdLo, uint rdHi, uint rn, uint rm) + { + Operand rdLoOperand = InstEmitCommon.GetOutputGpr(context, rdLo); + Operand rdHiOperand = InstEmitCommon.GetOutputGpr(context, rdHi); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + Operand rdLoOperand64 = new(OperandKind.Register, OperandType.I64, rdLoOperand.Value); + Operand rdHiOperand64 = new(OperandKind.Register, OperandType.I64, rdHiOperand.Value); + + if (rdLo == rdHi) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand tempRegister64 = new(OperandKind.Register, OperandType.I64, tempRegister.Operand.Value); + + context.Arm64Assembler.Umaddl(tempRegister64, rnOperand, rmOperand, rdLoOperand64); + context.Arm64Assembler.Add(rdLoOperand64, tempRegister64, rdHiOperand64); + } + else + { + context.Arm64Assembler.Umaddl(rdLoOperand64, rnOperand, rmOperand, rdLoOperand64); + context.Arm64Assembler.Add(rdLoOperand64, rdLoOperand64, rdHiOperand64); + } + + if (rdLo != rdHi) + { + context.Arm64Assembler.Lsr(rdHiOperand64, rdLoOperand64, InstEmitCommon.Const(32)); + } + + context.Arm64Assembler.Mov(rdLoOperand, rdLoOperand); // Zero-extend. + } + + public static void Umlal(CodeGenContext context, uint rdLo, uint rdHi, uint rn, uint rm, bool s) + { + EmitMultiplyAddLong(context, context.Arm64Assembler.Umaddl, rdLo, rdHi, rn, rm, s); + } + + public static void Umull(CodeGenContext context, uint rdLo, uint rdHi, uint rn, uint rm, bool s) + { + EmitMultiplyLong(context, context.Arm64Assembler.Umull, rdLo, rdHi, rn, rm, s); + } + + private static void EmitMultiplyLong(CodeGenContext context, Action action, uint rdLo, uint rdHi, uint rn, uint rm, bool s) + { + Operand rdLoOperand = InstEmitCommon.GetOutputGpr(context, rdLo); + Operand rdHiOperand = InstEmitCommon.GetOutputGpr(context, rdHi); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + Operand rdLoOperand64 = new(OperandKind.Register, OperandType.I64, rdLoOperand.Value); + Operand rdHiOperand64 = new(OperandKind.Register, OperandType.I64, rdHiOperand.Value); + + if (s) + { + using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + action(rdLoOperand64, rnOperand, rmOperand); + context.Arm64Assembler.Tst(rdLoOperand64, rdLoOperand64); + + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + } + else + { + action(rdLoOperand64, rnOperand, rmOperand); + } + + if (rdLo != rdHi) + { + context.Arm64Assembler.Lsr(rdHiOperand64, rdLoOperand64, InstEmitCommon.Const(32)); + } + + context.Arm64Assembler.Mov(rdLoOperand, rdLoOperand); // Zero-extend. + } + + private static void EmitMultiplyAddLong(CodeGenContext context, Action action, uint rdLo, uint rdHi, uint rn, uint rm, bool s) + { + Operand rdLoOperand = InstEmitCommon.GetOutputGpr(context, rdLo); + Operand rdHiOperand = InstEmitCommon.GetOutputGpr(context, rdHi); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + Operand rdLoOperand64 = new(OperandKind.Register, OperandType.I64, rdLoOperand.Value); + Operand rdHiOperand64 = new(OperandKind.Register, OperandType.I64, rdHiOperand.Value); + + using ScopedRegister raRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand raOperand64 = new(OperandKind.Register, OperandType.I64, raRegister.Operand.Value); + + context.Arm64Assembler.Lsl(raOperand64, rdHiOperand64, InstEmitCommon.Const(32)); + context.Arm64Assembler.Orr(raOperand64, raOperand64, rdLoOperand); + + if (s) + { + using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + action(rdLoOperand64, rnOperand, rmOperand, raOperand64); + context.Arm64Assembler.Tst(rdLoOperand64, rdLoOperand64); + + InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand); + + context.SetNzcvModified(); + } + else + { + action(rdLoOperand64, rnOperand, rmOperand, raOperand64); + } + + if (rdLo != rdHi) + { + context.Arm64Assembler.Lsr(rdHiOperand64, rdLoOperand64, InstEmitCommon.Const(32)); + } + + context.Arm64Assembler.Mov(rdLoOperand, rdLoOperand); // Zero-extend. + } + + private static void EmitSmladSmlsd(CodeGenContext context, uint rd, uint rn, uint rm, uint ra, bool x, bool add) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + Operand raOperand = InstEmitCommon.GetInputGpr(context, ra); + + Operand rdOperand64 = new(OperandKind.Register, OperandType.I64, rdOperand.Value); + + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempA = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand tempN64 = new(OperandKind.Register, OperandType.I64, tempN.Operand.Value); + Operand tempM64 = new(OperandKind.Register, OperandType.I64, tempM.Operand.Value); + Operand tempA64 = new(OperandKind.Register, OperandType.I64, tempA.Operand.Value); + + ScopedRegister swapTemp = default; + + if (x) + { + swapTemp = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Ror(swapTemp.Operand, rmOperand, InstEmitCommon.Const(16)); + + rmOperand = swapTemp.Operand; + } + + context.Arm64Assembler.Sxth(tempN64, rnOperand); + context.Arm64Assembler.Sxth(tempM64, rmOperand); + context.Arm64Assembler.Sxtw(tempA64, raOperand); + + context.Arm64Assembler.Mul(rdOperand64, tempN64, tempM64); + + context.Arm64Assembler.Asr(tempN.Operand, rnOperand, InstEmitCommon.Const(16)); + context.Arm64Assembler.Asr(tempM.Operand, rmOperand, InstEmitCommon.Const(16)); + + if (add) + { + context.Arm64Assembler.Smaddl(rdOperand64, tempN.Operand, tempM.Operand, rdOperand64); + } + else + { + context.Arm64Assembler.Smsubl(rdOperand64, tempN.Operand, tempM.Operand, rdOperand64); + } + + context.Arm64Assembler.Add(rdOperand64, rdOperand64, tempA64); + + CheckResultOverflow(context, tempM64, rdOperand64); + + context.Arm64Assembler.Mov(rdOperand, rdOperand); // Zero-extend. + + if (x) + { + swapTemp.Dispose(); + } + } + + private static void EmitSmlaldSmlsld(CodeGenContext context, uint rdLo, uint rdHi, uint rn, uint rm, bool x, bool add) + { + Operand rdLoOperand = InstEmitCommon.GetOutputGpr(context, rdLo); + Operand rdHiOperand = InstEmitCommon.GetOutputGpr(context, rdHi); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + Operand rdLoOperand64 = new(OperandKind.Register, OperandType.I64, rdLoOperand.Value); + Operand rdHiOperand64 = new(OperandKind.Register, OperandType.I64, rdHiOperand.Value); + + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempA = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand tempN64 = new(OperandKind.Register, OperandType.I64, tempN.Operand.Value); + Operand tempM64 = new(OperandKind.Register, OperandType.I64, tempM.Operand.Value); + Operand tempA64 = new(OperandKind.Register, OperandType.I64, tempA.Operand.Value); + + ScopedRegister swapTemp = default; + + if (x) + { + swapTemp = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Ror(swapTemp.Operand, rmOperand, InstEmitCommon.Const(16)); + + rmOperand = swapTemp.Operand; + } + + context.Arm64Assembler.Sxth(tempN64, rnOperand); + context.Arm64Assembler.Sxth(tempM64, rmOperand); + + context.Arm64Assembler.Mul(rdLoOperand64, tempN64, tempM64); + + context.Arm64Assembler.Asr(tempN.Operand, rnOperand, InstEmitCommon.Const(16)); + context.Arm64Assembler.Asr(tempM.Operand, rmOperand, InstEmitCommon.Const(16)); + + if (add) + { + context.Arm64Assembler.Smaddl(rdLoOperand64, tempN.Operand, tempM.Operand, rdLoOperand64); + } + else + { + context.Arm64Assembler.Smsubl(rdLoOperand64, tempN.Operand, tempM.Operand, rdLoOperand64); + } + + context.Arm64Assembler.Lsl(tempA64, rdHiOperand64, InstEmitCommon.Const(32)); + context.Arm64Assembler.Orr(tempA64, tempA64, rdLoOperand); + + context.Arm64Assembler.Add(rdLoOperand64, rdLoOperand64, tempA64); + + if (rdLo != rdHi) + { + context.Arm64Assembler.Lsr(rdHiOperand64, rdLoOperand64, InstEmitCommon.Const(32)); + } + + context.Arm64Assembler.Mov(rdLoOperand, rdLoOperand); // Zero-extend. + + if (x) + { + swapTemp.Dispose(); + } + } + + private static void EmitSmmlaSmmls(CodeGenContext context, uint rd, uint rn, uint rm, uint ra, bool r, bool add) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + Operand raOperand = InstEmitCommon.GetInputGpr(context, ra); + + Operand rdOperand64 = new(OperandKind.Register, OperandType.I64, rdOperand.Value); + Operand raOperand64 = new(OperandKind.Register, OperandType.I64, raOperand.Value); + + using ScopedRegister tempA = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand tempA64 = new(OperandKind.Register, OperandType.I64, tempA.Operand.Value); + + context.Arm64Assembler.Lsl(tempA64, raOperand64, InstEmitCommon.Const(32)); + + if (add) + { + context.Arm64Assembler.Smaddl(rdOperand64, rnOperand, rmOperand, tempA64); + } + else + { + context.Arm64Assembler.Smsubl(rdOperand64, rnOperand, rmOperand, tempA64); + } + + if (r) + { + context.Arm64Assembler.Mov(tempA.Operand, 0x80000000u); + context.Arm64Assembler.Add(rdOperand64, rdOperand64, tempA64); + } + + context.Arm64Assembler.Lsr(rdOperand64, rdOperand64, InstEmitCommon.Const(32)); + } + + private static void EmitSmuadSmusd(CodeGenContext context, uint rd, uint rn, uint rm, bool x, bool add) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + Operand rdOperand64 = new(OperandKind.Register, OperandType.I64, rdOperand.Value); + + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand tempN64 = new(OperandKind.Register, OperandType.I64, tempN.Operand.Value); + Operand tempM64 = new(OperandKind.Register, OperandType.I64, tempM.Operand.Value); + + ScopedRegister swapTemp = default; + + if (x) + { + swapTemp = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Ror(swapTemp.Operand, rmOperand, InstEmitCommon.Const(16)); + + rmOperand = swapTemp.Operand; + } + + context.Arm64Assembler.Sxth(tempN64, rnOperand); + context.Arm64Assembler.Sxth(tempM64, rmOperand); + + context.Arm64Assembler.Mul(rdOperand64, tempN64, tempM64); + + context.Arm64Assembler.Asr(tempN.Operand, rnOperand, InstEmitCommon.Const(16)); + context.Arm64Assembler.Asr(tempM.Operand, rmOperand, InstEmitCommon.Const(16)); + + if (add) + { + context.Arm64Assembler.Smaddl(rdOperand64, tempN.Operand, tempM.Operand, rdOperand64); + } + else + { + context.Arm64Assembler.Smsubl(rdOperand64, tempN.Operand, tempM.Operand, rdOperand64); + } + + context.Arm64Assembler.Mov(rdOperand, rdOperand); // Zero-extend. + + if (x) + { + swapTemp.Dispose(); + } + } + + private static void SelectSignedHalfword(CodeGenContext context, Operand dest, Operand source, bool high) + { + if (high) + { + context.Arm64Assembler.Asr(dest, source, InstEmitCommon.Const(16)); + } + else + { + context.Arm64Assembler.Sxth(dest, source); + } + } + + private static void CheckResultOverflow(CodeGenContext context, Operand temp64, Operand result) + { + context.Arm64Assembler.Sxtw(temp64, result); + context.Arm64Assembler.Sub(temp64, temp64, result); + + int branchIndex = context.CodeWriter.InstructionPointer; + + context.Arm64Assembler.Cbz(temp64, 0); + + // Set Q flag if we had an overflow. + InstEmitSaturate.SetQFlag(context); + + int delta = context.CodeWriter.InstructionPointer - branchIndex; + context.CodeWriter.WriteInstructionAt(branchIndex, context.CodeWriter.ReadInstructionAt(branchIndex) | (uint)((delta & 0x7ffff) << 5)); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonArithmetic.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonArithmetic.cs new file mode 100644 index 00000000..a59f00fc --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonArithmetic.cs @@ -0,0 +1,344 @@ +using System; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitNeonArithmetic + { + public static void Vaba(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, u ? context.Arm64Assembler.Uaba : context.Arm64Assembler.Saba, null); + } + + public static void Vabal(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryLong(context, rd, rn, rm, size, u ? context.Arm64Assembler.Uabal : context.Arm64Assembler.Sabal); + } + + public static void VabdF(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FabdV, context.Arm64Assembler.FabdVH); + } + + public static void VabdI(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, u ? context.Arm64Assembler.Uabd : context.Arm64Assembler.Sabd, null); + } + + public static void Vabdl(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryLong(context, rd, rn, rm, size, u ? context.Arm64Assembler.Uabdl : context.Arm64Assembler.Sabdl); + } + + public static void Vabs(CodeGenContext context, uint rd, uint rm, bool f, uint size, uint q) + { + if (f) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FabsSingleAndDouble, context.Arm64Assembler.FabsHalf); + } + else + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.AbsV); + } + } + + public static void VaddF(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FaddSingleAndDouble, context.Arm64Assembler.FaddHalf); + } + + public static void VaddI(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, context.Arm64Assembler.AddV, context.Arm64Assembler.AddS); + } + + public static void Vaddhn(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryNarrow(context, rd, rn, rm, size, context.Arm64Assembler.Addhn); + } + + public static void Vaddl(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryLong(context, rd, rn, rm, size, u ? context.Arm64Assembler.Uaddl : context.Arm64Assembler.Saddl); + } + + public static void Vaddw(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryWide(context, rd, rn, rm, size, u ? context.Arm64Assembler.Uaddw : context.Arm64Assembler.Saddw); + } + + public static void VfmaF(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorTernaryRdF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FmlaVecSingleAndDouble, context.Arm64Assembler.FmlaVecHalf); + } + + public static void VfmsF(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorTernaryRdF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FmlsVecSingleAndDouble, context.Arm64Assembler.FmlsVecHalf); + } + + public static void Vhadd(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, u ? context.Arm64Assembler.Uhadd : context.Arm64Assembler.Shadd, null); + } + + public static void Vhsub(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, u ? context.Arm64Assembler.Uhsub : context.Arm64Assembler.Shsub, null); + } + + public static void Vmaxnm(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FmaxnmSingleAndDouble, context.Arm64Assembler.FmaxnmHalf); + } + + public static void VmaxF(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FmaxSingleAndDouble, context.Arm64Assembler.FmaxHalf); + } + + public static void VmaxI(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, u ? context.Arm64Assembler.Umax : context.Arm64Assembler.Smax, null); + } + + public static void Vminnm(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FminnmSingleAndDouble, context.Arm64Assembler.FminnmHalf); + } + + public static void VminF(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FminSingleAndDouble, context.Arm64Assembler.FminHalf); + } + + public static void VminI(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, u ? context.Arm64Assembler.Umin : context.Arm64Assembler.Smin, null); + } + + public static void VmlaF(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorTernaryMulNegRdF(context, rd, rn, rm, sz, q, negProduct: false); + } + + public static void VmlaI(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorTernaryRd(context, rd, rn, rm, size, q, context.Arm64Assembler.MlaVec); + } + + public static void VmlaS(CodeGenContext context, uint rd, uint rn, uint rm, bool f, uint size, uint q) + { + if (f) + { + InstEmitNeonCommon.EmitVectorTernaryMulNegRdByScalarAnyF(context, rd, rn, rm, size, q, negProduct: false); + } + else + { + InstEmitNeonCommon.EmitVectorTernaryRdByScalar(context, rd, rn, rm, size, q, context.Arm64Assembler.MlaElt); + } + } + + public static void VmlalI(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size) + { + InstEmitNeonCommon.EmitVectorTernaryRdLong(context, rd, rn, rm, size, u ? context.Arm64Assembler.UmlalVec : context.Arm64Assembler.SmlalVec); + } + + public static void VmlalS(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size) + { + InstEmitNeonCommon.EmitVectorTernaryRdLongByScalar(context, rd, rn, rm, size, u ? context.Arm64Assembler.UmlalElt : context.Arm64Assembler.SmlalElt); + } + + public static void VmlsF(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorTernaryMulNegRdF(context, rd, rn, rm, sz, q, negProduct: true); + } + + public static void VmlsI(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorTernaryRd(context, rd, rn, rm, size, q, context.Arm64Assembler.MlsVec); + } + + public static void VmlsS(CodeGenContext context, uint rd, uint rn, uint rm, bool f, uint size, uint q) + { + if (f) + { + InstEmitNeonCommon.EmitVectorTernaryMulNegRdByScalarAnyF(context, rd, rn, rm, size, q, negProduct: true); + } + else + { + InstEmitNeonCommon.EmitVectorTernaryRdByScalar(context, rd, rn, rm, size, q, context.Arm64Assembler.MlsElt); + } + } + + public static void VmlslI(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size) + { + InstEmitNeonCommon.EmitVectorTernaryRdLong(context, rd, rn, rm, size, u ? context.Arm64Assembler.UmlslVec : context.Arm64Assembler.SmlslVec); + } + + public static void VmlslS(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size) + { + InstEmitNeonCommon.EmitVectorTernaryRdLongByScalar(context, rd, rn, rm, size, u ? context.Arm64Assembler.UmlslElt : context.Arm64Assembler.SmlslElt); + } + + public static void VmulF(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FmulVecSingleAndDouble, context.Arm64Assembler.FmulVecHalf); + } + + public static void VmulI(CodeGenContext context, uint rd, uint rn, uint rm, bool op, uint size, uint q) + { + if (op) + { + // TODO: Feature check, emulation if not supported. + + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, context.Arm64Assembler.Pmul, null); + } + else + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, context.Arm64Assembler.MulVec, null); + } + } + + public static void VmulS(CodeGenContext context, uint rd, uint rn, uint rm, bool f, uint size, uint q) + { + if (f) + { + InstEmitNeonCommon.EmitVectorBinaryByScalarAnyF(context, rd, rn, rm, size, q, context.Arm64Assembler.FmulElt2regElementSingleAndDouble, context.Arm64Assembler.FmulElt2regElementHalf); + } + else + { + InstEmitNeonCommon.EmitVectorBinaryByScalar(context, rd, rn, rm, size, q, context.Arm64Assembler.MulElt); + } + } + + public static void VmullI(CodeGenContext context, uint rd, uint rn, uint rm, bool op, bool u, uint size) + { + if (op) + { + // TODO: Feature check, emulation if not supported. + + InstEmitNeonCommon.EmitVectorBinaryLong(context, rd, rn, rm, size == 2 ? 3 : size, context.Arm64Assembler.Pmull); + } + else + { + InstEmitNeonCommon.EmitVectorBinaryLong(context, rd, rn, rm, size, u ? context.Arm64Assembler.UmullVec : context.Arm64Assembler.SmullVec); + } + } + + public static void VmullS(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryLongByScalar(context, rd, rn, rm, size, u ? context.Arm64Assembler.UmullElt : context.Arm64Assembler.SmullElt); + } + + public static void Vneg(CodeGenContext context, uint rd, uint rm, bool f, uint size, uint q) + { + if (f) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FnegSingleAndDouble, context.Arm64Assembler.FnegHalf); + } + else + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.NegV); + } + } + + public static void Vpadal(CodeGenContext context, uint rd, uint rm, bool op, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryRd(context, rd, rm, size, q, op ? context.Arm64Assembler.Uadalp : context.Arm64Assembler.Sadalp); + } + + public static void VpaddF(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FaddpVecSingleAndDouble, context.Arm64Assembler.FaddpVecHalf); + } + + public static void VpaddI(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, context.Arm64Assembler.AddpVec, null); + } + + public static void Vpaddl(CodeGenContext context, uint rd, uint rm, bool op, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, op ? context.Arm64Assembler.Uaddlp : context.Arm64Assembler.Saddlp); + } + + public static void VpmaxF(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FmaxpVecSingleAndDouble, context.Arm64Assembler.FmaxpVecHalf); + } + + public static void VpmaxI(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, u ? context.Arm64Assembler.Umaxp : context.Arm64Assembler.Smaxp, null); + } + + public static void VpminF(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FminpVecSingleAndDouble, context.Arm64Assembler.FminpVecHalf); + } + + public static void VpminI(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, u ? context.Arm64Assembler.Uminp : context.Arm64Assembler.Sminp, null); + } + + public static void Vrecpe(CodeGenContext context, uint rd, uint rm, bool f, uint size, uint q) + { + if (f) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FrecpeV, context.Arm64Assembler.FrecpeVH); + } + else + { + throw new NotImplementedException(); + } + } + + public static void Vrecps(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FrecpsV, context.Arm64Assembler.FrecpsVH); + } + + public static void Vrsqrte(CodeGenContext context, uint rd, uint rm, bool f, uint size, uint q) + { + if (f) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FrsqrteV, context.Arm64Assembler.FrsqrteVH); + } + else + { + throw new NotImplementedException(); + } + } + + public static void Vrsqrts(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FrsqrtsV, context.Arm64Assembler.FrsqrtsVH); + } + + public static void VsubF(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FsubSingleAndDouble, context.Arm64Assembler.FsubHalf); + } + + public static void VsubI(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, context.Arm64Assembler.SubV, context.Arm64Assembler.SubS); + } + + public static void Vsubhn(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryNarrow(context, rd, rn, rm, size, context.Arm64Assembler.Subhn); + } + + public static void Vsubl(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryLong(context, rd, rn, rm, size, u ? context.Arm64Assembler.Usubl : context.Arm64Assembler.Ssubl); + } + + public static void Vsubw(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryWide(context, rd, rn, rm, size, u ? context.Arm64Assembler.Usubw : context.Arm64Assembler.Ssubw); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonBit.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonBit.cs new file mode 100644 index 00000000..9601f4c2 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonBit.cs @@ -0,0 +1,35 @@ +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitNeonBit + { + public static void Vcls(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.Cls); + } + + public static void Vclz(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.Clz); + } + + public static void Vcnt(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.Cnt); + } + + public static void Vrev16(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.Rev16); + } + + public static void Vrev32(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.Rev32); + } + + public static void Vrev64(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.Rev64); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonCommon.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonCommon.cs new file mode 100644 index 00000000..dce6556e --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonCommon.cs @@ -0,0 +1,1513 @@ + +using Ryujinx.Cpu.LightningJit.CodeGen; +using System; +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitNeonCommon + { + public static ScopedRegister MoveScalarToSide(CodeGenContext context, uint srcReg, bool isFP32, bool forceAllocation = false) + { + int shift = isFP32 ? 2 : 1; + uint mask = isFP32 ? 3u : 1u; + uint elt = srcReg & mask; + + if (elt == 0 && !forceAllocation) + { + return new ScopedRegister(context.RegisterAllocator, context.RegisterAllocator.RemapFpRegister((int)(srcReg >> shift), isFP32), false); + } + + Operand source = context.RegisterAllocator.RemapSimdRegister((int)(srcReg >> shift)); + ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempFpRegisterScoped(isFP32); + + uint imm5 = GetImm5ForElementIndex(elt, isFP32); + + context.Arm64Assembler.DupEltScalarFromElement(tempRegister.Operand, source, imm5); + + return tempRegister; + } + + public static ScopedRegister Move16BitScalarToSide(CodeGenContext context, uint srcReg, bool top = false) + { + uint elt = srcReg & 3; + + Operand source = context.RegisterAllocator.RemapSimdRegister((int)(srcReg >> 2)); + ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempFpRegisterScoped(true); + + uint imm5 = GetImm5ForElementIndex((elt << 1) | (top ? 1u : 0u), 1); + + context.Arm64Assembler.DupEltScalarFromElement(tempRegister.Operand, source, imm5); + + return tempRegister; + } + + public static void MoveScalarToSide(CodeGenContext context, Operand dest, uint srcReg, bool isFP32) + { + int shift = isFP32 ? 2 : 1; + uint mask = isFP32 ? 3u : 1u; + uint elt = srcReg & mask; + + Operand source = context.RegisterAllocator.RemapSimdRegister((int)(srcReg >> shift)); + + uint imm5 = GetImm5ForElementIndex(elt, isFP32); + + context.Arm64Assembler.DupEltScalarFromElement(dest, source, imm5); + } + + public static ScopedRegister MoveScalarToSideIntoGpr(CodeGenContext context, uint srcReg, bool isFP32) + { + int shift = isFP32 ? 2 : 1; + uint mask = isFP32 ? 3u : 1u; + uint elt = srcReg & mask; + + Operand source = context.RegisterAllocator.RemapSimdRegister((int)(srcReg >> shift)); + ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Umov(tempRegister.Operand, source, (int)elt, isFP32 ? 2 : 3); + + return tempRegister; + } + + public static void InsertResult(CodeGenContext context, Operand source, uint dstReg, bool isFP32) + { + int shift = isFP32 ? 2 : 1; + uint mask = isFP32 ? 3u : 1u; + uint elt = dstReg & mask; + + uint imm5 = GetImm5ForElementIndex(elt, isFP32); + + Operand dest = context.RegisterAllocator.RemapSimdRegister((int)(dstReg >> shift)); + + context.Arm64Assembler.InsElt(dest, source, 0, imm5); + } + + public static void Insert16BitResult(CodeGenContext context, Operand source, uint dstReg, bool top = false) + { + uint elt = dstReg & 3u; + + uint imm5 = GetImm5ForElementIndex((elt << 1) | (top ? 1u : 0u), 1); + + Operand dest = context.RegisterAllocator.RemapSimdRegister((int)(dstReg >> 2)); + + context.Arm64Assembler.InsElt(dest, source, 0, imm5); + } + + public static void InsertResultFromGpr(CodeGenContext context, Operand source, uint dstReg, bool isFP32) + { + int shift = isFP32 ? 2 : 1; + uint mask = isFP32 ? 3u : 1u; + uint elt = dstReg & mask; + + uint imm5 = GetImm5ForElementIndex(elt, isFP32); + + Operand dest = context.RegisterAllocator.RemapSimdRegister((int)(dstReg >> shift)); + + context.Arm64Assembler.InsGen(dest, source, imm5); + } + + public static uint GetImm5ForElementIndex(uint elt, bool isFP32) + { + return isFP32 ? (4u | (elt << 3)) : (8u | (elt << 4)); + } + + public static uint GetImm5ForElementIndex(uint elt, uint size) + { + return (1u << (int)size) | (elt << ((int)size + 1)); + } + + public static void EmitScalarUnaryF(CodeGenContext context, uint rd, uint rm, uint size, Action action) + { + Debug.Assert(size == 1 || size == 2 || size == 3); + + bool singleRegs = size != 3; + + using ScopedRegister rmReg = MoveScalarToSide(context, rm, singleRegs); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rmReg); + + action(tempRegister.Operand, rmReg.Operand, size ^ 2u); + + InsertResult(context, tempRegister.Operand, rd, singleRegs); + } + + public static void EmitScalarUnaryF(CodeGenContext context, uint rd, uint rm, uint size, Action action, Action actionHalf) + { + Debug.Assert(size == 1 || size == 2 || size == 3); + + bool singleRegs = size != 3; + + using ScopedRegister rmReg = MoveScalarToSide(context, rm, singleRegs); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rmReg); + + if (size == 1) + { + actionHalf(tempRegister.Operand, rmReg.Operand); + } + else + { + action(tempRegister.Operand, rmReg.Operand, size & 1); + } + + InsertResult(context, tempRegister.Operand, rd, singleRegs); + } + + public static void EmitScalarUnaryToGprTempF( + CodeGenContext context, + uint rd, + uint rm, + uint size, + uint sf, + Action action) + { + Debug.Assert(size == 1 || size == 2 || size == 3); + + bool singleRegs = size != 3; + + using ScopedRegister rmReg = MoveScalarToSide(context, rm, singleRegs); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + action(tempRegister.Operand, rmReg.Operand, size ^ 2u, sf); + + InsertResultFromGpr(context, tempRegister.Operand, rd, sf == 0); + } + + public static void EmitScalarUnaryFromGprTempF( + CodeGenContext context, + uint rd, + uint rm, + uint size, + uint sf, + Action action) + { + Debug.Assert(size == 1 || size == 2 || size == 3); + + bool singleRegs = size != 3; + + using ScopedRegister rmReg = MoveScalarToSideIntoGpr(context, rm, sf == 0); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + action(tempRegister.Operand, rmReg.Operand, size ^ 2u, sf); + + InsertResult(context, tempRegister.Operand, rd, singleRegs); + } + + public static void EmitScalarUnaryFixedF(CodeGenContext context, uint rd, uint rm, uint fbits, uint size, bool is16Bit, Action action) + { + Debug.Assert(size == 1 || size == 2 || size == 3); + + bool singleRegs = size != 3; + + (uint immb, uint immh) = GetImmbImmh(fbits, size); + + using ScopedRegister rmReg = is16Bit ? Move16BitScalarToSide(context, rm) : MoveScalarToSide(context, rm, singleRegs); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rmReg); + + action(tempRegister.Operand, rmReg.Operand, immb, immh); + + InsertResult(context, tempRegister.Operand, rd, singleRegs); + } + + public static void EmitScalarBinaryF(CodeGenContext context, uint rd, uint rn, uint rm, uint size, Action action) + { + Debug.Assert(size == 1 || size == 2 || size == 3); + + bool singleRegs = size != 3; + + using ScopedRegister rnReg = MoveScalarToSide(context, rn, singleRegs); + using ScopedRegister rmReg = MoveScalarToSide(context, rm, singleRegs); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rnReg, rmReg); + + action(tempRegister.Operand, rnReg.Operand, rmReg.Operand, size ^ 2u); + + InsertResult(context, tempRegister.Operand, rd, singleRegs); + } + + public static void EmitScalarBinaryShift( + CodeGenContext context, + uint rd, + uint rm, + uint shift, + uint size, + bool isShl, + Action action) + { + bool singleRegs = size != 3; + + (uint immb, uint immh) = GetImmbImmhForShift(shift, size, isShl); + + using ScopedRegister rmReg = MoveScalarToSide(context, rm, singleRegs); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rmReg); + + action(tempRegister.Operand, rmReg.Operand, immb, immh); + + InsertResult(context, tempRegister.Operand, rd, singleRegs); + } + + public static void EmitScalarTernaryRdF(CodeGenContext context, uint rd, uint rn, uint rm, uint size, Action action) + { + Debug.Assert(size == 1 || size == 2 || size == 3); + + bool singleRegs = size != 3; + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + MoveScalarToSide(context, tempRegister.Operand, rd, singleRegs); + + using ScopedRegister rnReg = MoveScalarToSide(context, rn, singleRegs); + using ScopedRegister rmReg = MoveScalarToSide(context, rm, singleRegs); + + action(tempRegister.Operand, rnReg.Operand, rmReg.Operand, size ^ 2u); + + InsertResult(context, tempRegister.Operand, rd, singleRegs); + } + + public static void EmitScalarTernaryRdF( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint size, + Action action) + { + Debug.Assert(size == 1 || size == 2 || size == 3); + + bool singleRegs = size != 3; + + using ScopedRegister rdReg = MoveScalarToSide(context, rd, singleRegs); + using ScopedRegister rnReg = MoveScalarToSide(context, rn, singleRegs); + using ScopedRegister rmReg = MoveScalarToSide(context, rm, singleRegs); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rdReg, rnReg, rmReg); + + action(tempRegister.Operand, rnReg.Operand, rmReg.Operand, rdReg.Operand, size ^ 2u); + + InsertResult(context, tempRegister.Operand, rd, singleRegs); + } + + public static void EmitScalarTernaryMulNegRdF( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint size, + bool negD, + bool negProduct) + { + Debug.Assert(size == 1 || size == 2 || size == 3); + + bool singleRegs = size != 3; + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + MoveScalarToSide(context, tempRegister.Operand, rd, singleRegs); + + using ScopedRegister rnReg = MoveScalarToSide(context, rn, singleRegs); + using ScopedRegister rmReg = MoveScalarToSide(context, rm, singleRegs); + + using ScopedRegister productRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + uint ftype = size ^ 2u; + + context.Arm64Assembler.FmulFloat(productRegister.Operand, rnReg.Operand, rmReg.Operand, ftype); + + if (negD) + { + context.Arm64Assembler.FnegFloat(tempRegister.Operand, tempRegister.Operand, ftype); + } + + if (negProduct) + { + context.Arm64Assembler.FnegFloat(productRegister.Operand, productRegister.Operand, ftype); + } + + context.Arm64Assembler.FaddFloat(tempRegister.Operand, tempRegister.Operand, productRegister.Operand, ftype); + + InsertResult(context, tempRegister.Operand, rd, singleRegs); + } + + public static void EmitVectorUnary(CodeGenContext context, uint rd, uint rm, Action action) + { + Debug.Assert(((rd | rm) & 1) == 0); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + action(rdOperand, rmOperand); + } + + public static void EmitVectorUnary(CodeGenContext context, uint rd, uint rm, uint q, Action action) + { + if (q == 0) + { + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rmReg); + + action(tempRegister.Operand, rmReg.Operand, q); + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + action(rdOperand, rmOperand, q); + } + } + + public static void EmitVectorUnary(CodeGenContext context, uint rd, uint rm, uint size, uint q, Action action) + { + Debug.Assert(size < 3); + + if (q == 0) + { + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rmReg); + + action(tempRegister.Operand, rmReg.Operand, size, q); + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + action(rdOperand, rmOperand, size, q); + } + } + + public static void EmitVectorUnaryLong(CodeGenContext context, uint rd, uint rm, uint size, Action action) + { + Debug.Assert((rd & 1) == 0); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + uint q = rm & 1; + + action(rdOperand, rmOperand, size, q); + } + + public static void EmitVectorUnaryNarrow(CodeGenContext context, uint rd, uint rm, uint size, Action action) + { + Debug.Assert((rm & 1) == 0); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + uint q = rd & 1; + + if (q == 0) + { + // Writing to the lower half would clear the higher bits, we don't want that, so use a temp register and move the element. + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + action(tempRegister.Operand, rmOperand, size, q); + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + action(rdOperand, rmOperand, size, q); + } + } + + public static void EmitVectorBinary(CodeGenContext context, uint rd, uint rn, uint rm, Action action) + { + Debug.Assert(((rd | rn | rm) & 1) == 0); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + action(rdOperand, rnOperand, rmOperand); + } + + public static void EmitVectorBinary(CodeGenContext context, uint rd, uint rn, uint rm, uint q, Action action) + { + if (q == 0) + { + using ScopedRegister rnReg = MoveScalarToSide(context, rn, false); + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rnReg, rmReg); + + action(tempRegister.Operand, rnReg.Operand, rmReg.Operand, q); + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + action(rdOperand, rnOperand, rmOperand, q); + } + } + + public static void EmitVectorBinary( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint size, + uint q, + Action action, + Action actionScalar) + { + Debug.Assert(size <= 3); + + if (q == 0) + { + using ScopedRegister rnReg = MoveScalarToSide(context, rn, false); + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rnReg, rmReg); + + if (size == 3) + { + actionScalar(tempRegister.Operand, rnReg.Operand, rmReg.Operand, size); + } + else + { + action(tempRegister.Operand, rnReg.Operand, rmReg.Operand, size, q); + } + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + action(rdOperand, rnOperand, rmOperand, size, q); + } + } + + public static void EmitVectorBinaryRd(CodeGenContext context, uint rd, uint rm, uint size, uint q, Action action) + { + Debug.Assert(size < 3); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + MoveScalarToSide(context, tempRegister.Operand, rd, false); + + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + action(tempRegister.Operand, rmReg.Operand, size, q); + + InsertResult(context, tempRegister.Operand, rd, false); + } + + public static void EmitVectorBinaryShift( + CodeGenContext context, + uint rd, + uint rm, + uint shift, + uint size, + uint q, + bool isShl, + Action action, + Action actionScalar) + { + (uint immb, uint immh) = GetImmbImmhForShift(shift, size, isShl); + + if (q == 0) + { + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rmReg); + + if (size == 3) + { + actionScalar(tempRegister.Operand, rmReg.Operand, immb, immh); + } + else + { + action(tempRegister.Operand, rmReg.Operand, immb, immh, q); + } + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + action(rdOperand, rmOperand, immb, immh, q); + } + } + + public static void EmitVectorBinaryLong(CodeGenContext context, uint rd, uint rn, uint rm, uint size, Action action) + { + Debug.Assert((rd & 1) == 0); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + + if ((rn & 1) == (rm & 1)) + { + // Both inputs are on the same side of the vector, so we can use the variant that selects the half. + + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + uint q = rn & 1; + + action(rdOperand, rnOperand, rmOperand, size, q); + } + else + { + // Inputs are on different sides of the vector, we have to move them. + + using ScopedRegister rnReg = MoveScalarToSide(context, rn, false); + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + action(rdOperand, rnReg.Operand, rmReg.Operand, size, 0); + } + } + + public static void EmitVectorBinaryLongShift( + CodeGenContext context, + uint rd, + uint rn, + uint shift, + uint size, + bool isShl, + Action action) + { + (uint immb, uint immh) = GetImmbImmhForShift(shift, size, isShl); + + Debug.Assert((rd & 1) == 0); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + + uint q = rn & 1; + + action(rdOperand, rnOperand, immb, immh, q); + } + + public static void EmitVectorBinaryLongByScalar( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint size, + Action action) + { + Debug.Assert((rd & 1) == 0); + + (uint h, uint l, uint m) = GetIndexForReg(ref rm, size); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + uint q = rn & 1; + + action(rdOperand, rnOperand, h, rmOperand, m, l, size, q); + } + + public static void EmitVectorBinaryNarrow( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint size, + Action action) + { + Debug.Assert((rn & 1) == 0); + Debug.Assert((rm & 1) == 0); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + uint q = rd & 1; + + if (q == 0) + { + // Writing to the lower half would clear the higher bits, we don't want that, so use a temp register and move the element. + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + action(tempRegister.Operand, rnOperand, rmOperand, size, q); + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + action(rdOperand, rnOperand, rmOperand, size, q); + } + } + + public static void EmitVectorBinaryNarrowShift( + CodeGenContext context, + uint rd, + uint rm, + uint shift, + uint size, + bool isShl, + Action action) + { + (uint immb, uint immh) = GetImmbImmhForShift(shift, size, isShl); + + Debug.Assert((rm & 1) == 0); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + uint q = rd & 1; + + if (q == 0) + { + // Writing to the lower half would clear the higher bits, we don't want that, so use a temp register and move the element. + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + action(tempRegister.Operand, rmOperand, immb, immh, q); + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + action(rdOperand, rmOperand, immb, immh, q); + } + } + + public static void EmitVectorBinaryWide(CodeGenContext context, uint rd, uint rn, uint rm, uint size, Action action) + { + Debug.Assert(((rd | rn) & 1) == 0); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + uint q = rm & 1; + + action(rdOperand, rnOperand, rmOperand, size, q); + } + + public static void EmitVectorBinaryByScalar( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint size, + uint q, + Action action) + { + EmitVectorByScalarCore(context, rd, rn, rm, size, q, action, isTernary: false); + } + + public static void EmitVectorTernaryRd(CodeGenContext context, uint rd, uint rn, uint rm, uint q, Action action) + { + if (q == 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + MoveScalarToSide(context, tempRegister.Operand, rd, false); + + using ScopedRegister rnReg = MoveScalarToSide(context, rn, false); + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + action(tempRegister.Operand, rnReg.Operand, rmReg.Operand, q); + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + action(rdOperand, rnOperand, rmOperand, q); + } + } + + public static void EmitVectorTernaryRd(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q, Action action) + { + if (q == 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + MoveScalarToSide(context, tempRegister.Operand, rd, false); + + using ScopedRegister rnReg = MoveScalarToSide(context, rn, false); + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + action(tempRegister.Operand, rnReg.Operand, rmReg.Operand, size, q); + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + action(rdOperand, rnOperand, rmOperand, size, q); + } + } + + public static void EmitVectorTernaryRdLong(CodeGenContext context, uint rd, uint rn, uint rm, uint size, Action action) + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + + if ((rn & 1) == (rm & 1)) + { + // Both inputs are on the same side of the vector, so we can use the variant that selects the half. + + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + uint q = rn & 1; + + action(rdOperand, rnOperand, rmOperand, size, q); + } + else + { + // Inputs are on different sides of the vector, we have to move them. + + using ScopedRegister rnReg = MoveScalarToSide(context, rn, false); + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + action(rdOperand, rnReg.Operand, rmReg.Operand, size, 0); + } + } + + public static void EmitVectorTernaryRdLongByScalar( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint size, + Action action) + { + (uint h, uint l, uint m) = GetIndexForReg(ref rm, size); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + uint q = rn & 1; + + action(rdOperand, rnOperand, h, rmOperand, m, l, size, q); + } + + public static void EmitVectorTernaryRdShift( + CodeGenContext context, + uint rd, + uint rm, + uint shift, + uint size, + uint q, + bool isShl, + Action action, + Action actionScalar) + { + (uint immb, uint immh) = GetImmbImmhForShift(shift, size, isShl); + + if (q == 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + MoveScalarToSide(context, tempRegister.Operand, rd, false); + + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + if (size == 3) + { + actionScalar(tempRegister.Operand, rmReg.Operand, immb, immh); + } + else + { + action(tempRegister.Operand, rmReg.Operand, immb, immh, q); + } + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + action(rdOperand, rmOperand, immb, immh, q); + } + } + + public static void EmitVectorTernaryRdByScalar( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint size, + uint q, + Action action) + { + EmitVectorByScalarCore(context, rd, rn, rm, size, q, action, isTernary: true); + } + + private static void EmitVectorByScalarCore( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint size, + uint q, + Action action, + bool isTernary) + { + (uint h, uint l, uint m) = GetIndexForReg(ref rm, size); + + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + if (q == 0) + { + if (isTernary) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + MoveScalarToSide(context, tempRegister.Operand, rd, false); + + using ScopedRegister rnReg = MoveScalarToSide(context, rn, false); + + action(tempRegister.Operand, rnReg.Operand, h, rmOperand, m, l, size, q); + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + using ScopedRegister rnReg = MoveScalarToSide(context, rn, false); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rnReg); + + action(tempRegister.Operand, rnReg.Operand, h, rmOperand, m, l, size, q); + + InsertResult(context, tempRegister.Operand, rd, false); + } + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + + action(rdOperand, rnOperand, h, rmOperand, m, l, size, q); + } + } + + public static void EmitVectorUnaryF( + CodeGenContext context, + uint rd, + uint rm, + uint sz, + uint q, + Action action, + Action actionHalf) + { + Debug.Assert(sz == 0 || sz == 1); + + if (q == 0) + { + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rmReg); + + if (sz == 1) + { + actionHalf(tempRegister.Operand, rmReg.Operand, q); + } + else + { + action(tempRegister.Operand, rmReg.Operand, 0, q); + } + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + if (sz == 1) + { + actionHalf(rdOperand, rmOperand, q); + } + else + { + action(rdOperand, rmOperand, 0, q); + } + } + } + + public static void EmitVectorUnaryAnyF( + CodeGenContext context, + uint rd, + uint rm, + uint size, + uint q, + Action action, + Action actionHalf) + { + Debug.Assert(size == 1 || size == 2 || size == 3); + Debug.Assert(size != 3 || q == 1); + + if (q == 0) + { + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rmReg); + + if (size == 1) + { + actionHalf(tempRegister.Operand, rmReg.Operand, q); + } + else + { + action(tempRegister.Operand, rmReg.Operand, size ^ 2u, q); + } + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + if (size == 1) + { + actionHalf(rdOperand, rmOperand, q); + } + else + { + action(rdOperand, rmOperand, size ^ 2u, q); + } + } + } + + public static void EmitVectorUnaryFixedAnyF( + CodeGenContext context, + uint rd, + uint rm, + uint fbits, + uint size, + uint q, + Action action) + { + Debug.Assert(size == 1 || size == 2 || size == 3); + Debug.Assert(size != 3 || q == 1); + + (uint immb, uint immh) = GetImmbImmh(fbits, size); + + if (q == 0) + { + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rmReg); + + action(tempRegister.Operand, rmReg.Operand, immb, immh, q); + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + action(rdOperand, rmOperand, immb, immh, q); + } + } + + public static void EmitVectorBinaryF( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint sz, + uint q, + Action action, + Action actionHalf) + { + Debug.Assert(sz == 0 || sz == 1); + + if (q == 0) + { + using ScopedRegister rnReg = MoveScalarToSide(context, rn, false); + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rnReg, rmReg); + + if (sz == 1) + { + actionHalf(tempRegister.Operand, rnReg.Operand, rmReg.Operand, q); + } + else + { + action(tempRegister.Operand, rnReg.Operand, rmReg.Operand, 0, q); + } + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + if (sz == 1) + { + actionHalf(rdOperand, rnOperand, rmOperand, q); + } + else + { + action(rdOperand, rnOperand, rmOperand, 0, q); + } + } + } + + public static void EmitVectorBinaryByScalarAnyF( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint size, + uint q, + Action action, + Action actionHalf) + { + EmitVectorByScalarAnyFCore(context, rd, rn, rm, size, q, action, actionHalf, isTernary: false); + } + + public static void EmitVectorTernaryRdF( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint sz, + uint q, + Action action, + Action actionHalf) + { + Debug.Assert(sz == 0 || sz == 1); + + if (q == 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + MoveScalarToSide(context, tempRegister.Operand, rd, false); + + using ScopedRegister rnReg = MoveScalarToSide(context, rn, false); + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + if (sz == 1) + { + actionHalf(tempRegister.Operand, rnReg.Operand, rmReg.Operand, q); + } + else + { + action(tempRegister.Operand, rnReg.Operand, rmReg.Operand, 0, q); + } + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + if (sz == 1) + { + actionHalf(rdOperand, rnOperand, rmOperand, q); + } + else + { + action(rdOperand, rnOperand, rmOperand, 0, q); + } + } + } + + public static void EmitVectorTernaryMulNegRdF( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint sz, + uint q, + bool negProduct) + { + Debug.Assert(sz == 0 || sz == 1); + + if (q == 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + MoveScalarToSide(context, tempRegister.Operand, rd, false); + + using ScopedRegister rnReg = MoveScalarToSide(context, rn, false); + using ScopedRegister rmReg = MoveScalarToSide(context, rm, false); + + EmitMulNegVector(context, tempRegister.Operand, rnReg.Operand, rmReg.Operand, sz, q, negProduct); + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + EmitMulNegVector(context, rdOperand, rnOperand, rmOperand, sz, q, negProduct); + } + } + + private static void EmitMulNegVector( + CodeGenContext context, + Operand rd, + Operand rn, + Operand rm, + uint sz, + uint q, + bool negProduct) + { + using ScopedRegister productRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + if (sz == 1) + { + context.Arm64Assembler.FmulVecHalf(productRegister.Operand, rn, rm, q); + + if (negProduct) + { + context.Arm64Assembler.FnegHalf(productRegister.Operand, productRegister.Operand, q); + } + + context.Arm64Assembler.FaddHalf(rd, rd, productRegister.Operand, q); + } + else + { + context.Arm64Assembler.FmulVecSingleAndDouble(productRegister.Operand, rn, rm, 0, q); + + if (negProduct) + { + context.Arm64Assembler.FnegSingleAndDouble(productRegister.Operand, productRegister.Operand, 0, q); + } + + context.Arm64Assembler.FaddSingleAndDouble(rd, rd, productRegister.Operand, 0, q); + } + } + + public static void EmitVectorTernaryRdByScalarAnyF( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint size, + uint q, + Action action, + Action actionHalf) + { + EmitVectorByScalarAnyFCore(context, rd, rn, rm, size, q, action, actionHalf, isTernary: true); + } + + private static void EmitVectorByScalarAnyFCore( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint size, + uint q, + Action action, + Action actionHalf, + bool isTernary) + { + (uint h, uint l, uint m) = GetIndexForReg(ref rm, size); + + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + if (q == 0) + { + if (isTernary) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + MoveScalarToSide(context, tempRegister.Operand, rd, false); + + using ScopedRegister rnReg = MoveScalarToSide(context, rn, false); + + if (size == 1) + { + actionHalf(tempRegister.Operand, rnReg.Operand, h, rmOperand, m, l, q); + } + else + { + action(tempRegister.Operand, rnReg.Operand, h, rmOperand, m, l, 0, q); + } + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + using ScopedRegister rnReg = MoveScalarToSide(context, rn, false); + + using ScopedRegister tempRegister = PickSimdRegister(context.RegisterAllocator, rnReg); + + if (size == 1) + { + actionHalf(tempRegister.Operand, rnReg.Operand, h, rmOperand, m, l, q); + } + else + { + action(tempRegister.Operand, rnReg.Operand, h, rmOperand, m, l, 0, q); + } + + InsertResult(context, tempRegister.Operand, rd, false); + } + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + + if (size == 1) + { + actionHalf(rdOperand, rnOperand, h, rmOperand, m, l, q); + } + else + { + action(rdOperand, rnOperand, h, rmOperand, m, l, 0, q); + } + } + } + + public static void EmitVectorTernaryMulNegRdByScalarAnyF( + CodeGenContext context, + uint rd, + uint rn, + uint rm, + uint size, + uint q, + bool negProduct) + { + (uint h, uint l, uint m) = GetIndexForReg(ref rm, size); + + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + if (q == 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + MoveScalarToSide(context, tempRegister.Operand, rd, false); + + using ScopedRegister rnReg = MoveScalarToSide(context, rn, false); + + EmitMulNegVectorByScalar(context, tempRegister.Operand, rnReg.Operand, rmOperand, h, l, m, size, q, negProduct); + + InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + + EmitMulNegVectorByScalar(context, rdOperand, rnOperand, rmOperand, h, l, m, size, q, negProduct); + } + } + + private static void EmitMulNegVectorByScalar( + CodeGenContext context, + Operand rd, + Operand rn, + Operand rm, + uint h, + uint l, + uint m, + uint sz, + uint q, + bool negProduct) + { + using ScopedRegister productRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + if (sz == 1) + { + context.Arm64Assembler.FmulElt2regElementHalf(productRegister.Operand, rn, h, rm, m, l, q); + + if (negProduct) + { + context.Arm64Assembler.FnegHalf(productRegister.Operand, productRegister.Operand, q); + } + + context.Arm64Assembler.FaddHalf(rd, rd, productRegister.Operand, q); + } + else + { + context.Arm64Assembler.FmulElt2regElementSingleAndDouble(productRegister.Operand, rn, h, rm, m, l, 0, q); + + if (negProduct) + { + context.Arm64Assembler.FnegSingleAndDouble(productRegister.Operand, productRegister.Operand, 0, q); + } + + context.Arm64Assembler.FaddSingleAndDouble(rd, rd, productRegister.Operand, 0, q); + } + } + + private static (uint, uint, uint) GetIndexForReg(ref uint reg, uint size) + { + int shift = (int)(size + 2); + uint index = reg >> shift; + reg &= (1u << shift) - 1; + index |= (reg & 1) << (5 - shift); + + uint h, l, m; + + if (size == 1) + { + Debug.Assert((index >> 3) == 0); + + m = index & 1; + l = (index >> 1) & 1; + h = index >> 2; + } + else + { + Debug.Assert(size == 2); + Debug.Assert((index >> 2) == 0); + + m = 0; + l = index & 1; + h = (index >> 1) & 1; + } + + return (h, l, m); + } + + private static (uint, uint) GetImmbImmh(uint value, uint size) + { + Debug.Assert(value > 0 && value <= (8u << (int)size)); + + uint imm = (8u << (int)size) | ((8u << (int)size) - value); + + Debug.Assert((imm >> 7) == 0); + + uint immb = imm & 7; + uint immh = imm >> 3; + + return (immb, immh); + } + + public static (uint, uint) GetImmbImmhForShift(uint value, uint size, bool isShl) + { + if (isShl) + { + Debug.Assert(value >= 0 && value < (8u << (int)size)); + + uint imm = (8u << (int)size) | (value & (0x3fu >> (int)(3 - size))); + + Debug.Assert((imm >> 7) == 0); + + uint immb = imm & 7; + uint immh = imm >> 3; + + return (immb, immh); + } + else + { + return GetImmbImmh(value, size); + } + } + + public static uint GetSizeFromImm6(uint imm6) + { + if ((imm6 & 0b100000) != 0) + { + return 2; + } + else if ((imm6 & 0b10000) != 0) + { + return 1; + } + else + { + Debug.Assert((imm6 & 0b1000) != 0); + + return 0; + } + } + + public static uint GetSizeFromImm7(uint imm7) + { + if ((imm7 & 0b1000000) != 0) + { + return 3; + } + else if ((imm7 & 0b100000) != 0) + { + return 2; + } + else if ((imm7 & 0b10000) != 0) + { + return 1; + } + else + { + Debug.Assert((imm7 & 0b1000) != 0); + + return 0; + } + } + + public static ScopedRegister PickSimdRegister(RegisterAllocator registerAllocator, ScopedRegister option1) + { + if (option1.IsAllocated) + { + return option1; + } + + return registerAllocator.AllocateTempSimdRegisterScoped(); + } + + public static ScopedRegister PickSimdRegister(RegisterAllocator registerAllocator, ScopedRegister option1, ScopedRegister option2) + { + if (option1.IsAllocated) + { + return option1; + } + else if (option2.IsAllocated) + { + return option2; + } + + return registerAllocator.AllocateTempSimdRegisterScoped(); + } + + public static ScopedRegister PickSimdRegister(RegisterAllocator registerAllocator, ScopedRegister option1, ScopedRegister option2, ScopedRegister option3) + { + if (option1.IsAllocated) + { + return option1; + } + else if (option2.IsAllocated) + { + return option2; + } + else if (option3.IsAllocated) + { + return option3; + } + + return registerAllocator.AllocateTempSimdRegisterScoped(); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonCompare.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonCompare.cs new file mode 100644 index 00000000..8da99385 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonCompare.cs @@ -0,0 +1,126 @@ +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitNeonCompare + { + public static void Vacge(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FacgeV, context.Arm64Assembler.FacgeVH); + } + + public static void Vacgt(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FacgtV, context.Arm64Assembler.FacgtVH); + } + + public static void VceqI(CodeGenContext context, uint rd, uint rm, bool f, uint size, uint q) + { + if (f) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcmeqZeroV, context.Arm64Assembler.FcmeqZeroVH); + } + else + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.CmeqZeroV); + } + } + + public static void VceqR(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, context.Arm64Assembler.CmeqRegV, context.Arm64Assembler.CmeqRegS); + } + + public static void VceqFR(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FcmeqRegV, context.Arm64Assembler.FcmeqRegVH); + } + + public static void VcgeI(CodeGenContext context, uint rd, uint rm, bool f, uint size, uint q) + { + if (f) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcmgeZeroV, context.Arm64Assembler.FcmgeZeroVH); + } + else + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.CmgeZeroV); + } + } + + public static void VcgeR(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary( + context, + rd, + rn, + rm, + size, + q, + u ? context.Arm64Assembler.CmhsV : context.Arm64Assembler.CmgeRegV, + u ? context.Arm64Assembler.CmhsS : context.Arm64Assembler.CmgeRegS); + } + + public static void VcgeFR(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FcmgeRegV, context.Arm64Assembler.FcmgeRegVH); + } + + public static void VcgtI(CodeGenContext context, uint rd, uint rm, bool f, uint size, uint q) + { + if (f) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcmgtZeroV, context.Arm64Assembler.FcmgtZeroVH); + } + else + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.CmgtZeroV); + } + } + + public static void VcgtR(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary( + context, + rd, + rn, + rm, + size, + q, + u ? context.Arm64Assembler.CmhiV : context.Arm64Assembler.CmgtRegV, + u ? context.Arm64Assembler.CmhiS : context.Arm64Assembler.CmgtRegS); + } + + public static void VcgtFR(CodeGenContext context, uint rd, uint rn, uint rm, uint sz, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryF(context, rd, rn, rm, sz, q, context.Arm64Assembler.FcmgtRegV, context.Arm64Assembler.FcmgtRegVH); + } + + public static void VcleI(CodeGenContext context, uint rd, uint rm, bool f, uint size, uint q) + { + if (f) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcmleV, context.Arm64Assembler.FcmleVH); + } + else + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.CmleV); + } + } + + public static void VcltI(CodeGenContext context, uint rd, uint rm, bool f, uint size, uint q) + { + if (f) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcmltV, context.Arm64Assembler.FcmltVH); + } + else + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.CmltV); + } + } + + public static void Vtst(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, context.Arm64Assembler.CmtstV, context.Arm64Assembler.CmtstS); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonConvert.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonConvert.cs new file mode 100644 index 00000000..81fce678 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonConvert.cs @@ -0,0 +1,137 @@ +using System; +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitNeonConvert + { + public static void Vcvta(CodeGenContext context, uint rd, uint rm, bool op, uint size, uint q) + { + if (op) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcvtauV, context.Arm64Assembler.FcvtauVH); + } + else + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcvtasV, context.Arm64Assembler.FcvtasVH); + } + } + + public static void Vcvtm(CodeGenContext context, uint rd, uint rm, bool op, uint size, uint q) + { + if (op) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcvtmuV, context.Arm64Assembler.FcvtmuVH); + } + else + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcvtmsV, context.Arm64Assembler.FcvtmsVH); + } + } + + public static void Vcvtn(CodeGenContext context, uint rd, uint rm, bool op, uint size, uint q) + { + if (op) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcvtnuV, context.Arm64Assembler.FcvtnuVH); + } + else + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcvtnsV, context.Arm64Assembler.FcvtnsVH); + } + } + + public static void Vcvtp(CodeGenContext context, uint rd, uint rm, bool op, uint size, uint q) + { + if (op) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcvtpuV, context.Arm64Assembler.FcvtpuVH); + } + else + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcvtpsV, context.Arm64Assembler.FcvtpsVH); + } + } + + public static void VcvtHs(CodeGenContext context, uint rd, uint rm, bool op) + { + bool halfToSingle = op; + if (halfToSingle) + { + // Half to single. + + InstEmitNeonCommon.EmitVectorUnaryLong(context, rd, rm, 0, context.Arm64Assembler.Fcvtl); + } + else + { + // Single to half. + + InstEmitNeonCommon.EmitVectorUnaryNarrow(context, rd, rm, 0, context.Arm64Assembler.Fcvtn); + } + } + + public static void VcvtIs(CodeGenContext context, uint rd, uint rm, uint op, uint size, uint q) + { + Debug.Assert(op >> 2 == 0); + + bool unsigned = (op & 1) != 0; + bool toInteger = (op >> 1) != 0; + + if (toInteger) + { + if (unsigned) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcvtzuIntV, context.Arm64Assembler.FcvtzuIntVH); + } + else + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FcvtzsIntV, context.Arm64Assembler.FcvtzsIntVH); + } + } + else + { + if (unsigned) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.UcvtfIntV, context.Arm64Assembler.UcvtfIntVH); + } + else + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.ScvtfIntV, context.Arm64Assembler.ScvtfIntVH); + } + } + } + + public static void VcvtXs(CodeGenContext context, uint rd, uint rm, uint imm6, uint op, bool u, uint q) + { + Debug.Assert(op >> 2 == 0); + + bool unsigned = u; + bool toFixed = (op & 1) != 0; + uint size = 1 + (op >> 1); + uint fbits = Math.Clamp(64u - imm6, 1, 8u << (int)size); + + if (toFixed) + { + if (unsigned) + { + InstEmitNeonCommon.EmitVectorUnaryFixedAnyF(context, rd, rm, fbits, size, q, context.Arm64Assembler.FcvtzuFixV); + } + else + { + InstEmitNeonCommon.EmitVectorUnaryFixedAnyF(context, rd, rm, fbits, size, q, context.Arm64Assembler.FcvtzsFixV); + } + } + else + { + if (unsigned) + { + InstEmitNeonCommon.EmitVectorUnaryFixedAnyF(context, rd, rm, fbits, size, q, context.Arm64Assembler.UcvtfFixV); + } + else + { + InstEmitNeonCommon.EmitVectorUnaryFixedAnyF(context, rd, rm, fbits, size, q, context.Arm64Assembler.ScvtfFixV); + } + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonCrypto.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonCrypto.cs new file mode 100644 index 00000000..a36ae82c --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonCrypto.cs @@ -0,0 +1,43 @@ +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitNeonCrypto + { + public static void Aesd(CodeGenContext context, uint rd, uint rm, uint size) + { + // TODO: Feature check, emulation if not supported. + + Debug.Assert(size == 0); + + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, context.Arm64Assembler.Aesd); + } + + public static void Aese(CodeGenContext context, uint rd, uint rm, uint size) + { + // TODO: Feature check, emulation if not supported. + + Debug.Assert(size == 0); + + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, context.Arm64Assembler.Aese); + } + + public static void Aesimc(CodeGenContext context, uint rd, uint rm, uint size) + { + // TODO: Feature check, emulation if not supported. + + Debug.Assert(size == 0); + + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, context.Arm64Assembler.Aesimc); + } + + public static void Aesmc(CodeGenContext context, uint rd, uint rm, uint size) + { + // TODO: Feature check, emulation if not supported. + + Debug.Assert(size == 0); + + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, context.Arm64Assembler.Aesmc); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonHash.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonHash.cs new file mode 100644 index 00000000..57090ac8 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonHash.cs @@ -0,0 +1,97 @@ +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitNeonHash + { + public static void Sha1c(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + // TODO: Feature check, emulation if not supported. + + Debug.Assert(q == 1); + + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, context.Arm64Assembler.Sha1c); + } + + public static void Sha1h(CodeGenContext context, uint rd, uint rm, uint size) + { + // TODO: Feature check, emulation if not supported. + + Debug.Assert(size == 2); + + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, context.Arm64Assembler.Sha1h); + } + + public static void Sha1m(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + // TODO: Feature check, emulation if not supported. + + Debug.Assert(q == 1); + + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, context.Arm64Assembler.Sha1m); + } + + public static void Sha1p(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + // TODO: Feature check, emulation if not supported. + + Debug.Assert(q == 1); + + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, context.Arm64Assembler.Sha1p); + } + + public static void Sha1su0(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + // TODO: Feature check, emulation if not supported. + + Debug.Assert(q == 1); + + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, context.Arm64Assembler.Sha1su0); + } + + public static void Sha1su1(CodeGenContext context, uint rd, uint rm, uint size) + { + // TODO: Feature check, emulation if not supported. + + Debug.Assert(size == 2); + + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, context.Arm64Assembler.Sha1su1); + } + + public static void Sha256h(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + // TODO: Feature check, emulation if not supported. + + Debug.Assert(q == 1); + + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, context.Arm64Assembler.Sha256h); + } + + public static void Sha256h2(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + // TODO: Feature check, emulation if not supported. + + Debug.Assert(q == 1); + + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, context.Arm64Assembler.Sha256h2); + } + + public static void Sha256su0(CodeGenContext context, uint rd, uint rm, uint size) + { + // TODO: Feature check, emulation if not supported. + + Debug.Assert(size == 2); + + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, context.Arm64Assembler.Sha256su0); + } + + public static void Sha256su1(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + // TODO: Feature check, emulation if not supported. + + Debug.Assert(q == 1); + + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, context.Arm64Assembler.Sha256su1); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonLogical.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonLogical.cs new file mode 100644 index 00000000..af2e54cc --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonLogical.cs @@ -0,0 +1,79 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitNeonLogical + { + public static void VandR(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, q, context.Arm64Assembler.And); + } + + public static void VbicI(CodeGenContext context, uint rd, uint cmode, uint imm8, uint q) + { + EmitMovi(context, rd, cmode, imm8, 1, q); + } + + public static void VbicR(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, q, context.Arm64Assembler.BicReg); + } + + public static void VbifR(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + InstEmitNeonCommon.EmitVectorTernaryRd(context, rd, rn, rm, q, context.Arm64Assembler.Bif); + } + + public static void VbitR(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + InstEmitNeonCommon.EmitVectorTernaryRd(context, rd, rn, rm, q, context.Arm64Assembler.Bit); + } + + public static void VbslR(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + InstEmitNeonCommon.EmitVectorTernaryRd(context, rd, rn, rm, q, context.Arm64Assembler.Bsl); + } + + public static void VeorR(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, q, context.Arm64Assembler.Eor); + } + + public static void VornR(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, q, context.Arm64Assembler.Orn); + } + + public static void VorrI(CodeGenContext context, uint rd, uint cmode, uint imm8, uint q) + { + EmitMovi(context, rd, cmode, imm8, 0, q); + } + + public static void VorrR(CodeGenContext context, uint rd, uint rn, uint rm, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, q, context.Arm64Assembler.OrrReg); + } + + private static void EmitMovi(CodeGenContext context, uint rd, uint cmode, uint imm8, uint op, uint q) + { + (uint a, uint b, uint c, uint d, uint e, uint f, uint g, uint h) = InstEmitNeonMove.Split(imm8); + + if (q == 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + InstEmitNeonCommon.MoveScalarToSide(context, tempRegister.Operand, rd, false); + + context.Arm64Assembler.Movi(tempRegister.Operand, h, g, f, e, d, cmode, c, b, a, op, q); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + + context.Arm64Assembler.Movi(rdOperand, h, g, f, e, d, cmode, c, b, a, op, q); + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonMemory.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonMemory.cs new file mode 100644 index 00000000..e77dc0a2 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonMemory.cs @@ -0,0 +1,797 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System; +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitNeonMemory + { + public static void Vld11(CodeGenContext context, uint rd, uint rn, uint rm, uint indexAlign, uint size) + { + uint index = indexAlign >> ((int)size + 1); + + EmitMemory1234InstructionCore(context, rn, rm, 1 << (int)size, (address) => + { + EmitMemoryLoad1234SingleInstruction(context, address, rd, index, size, 1, 1, context.Arm64Assembler.Ld1SnglAsNoPostIndex); + }); + } + + public static void Vld1A(CodeGenContext context, uint rd, uint rn, uint rm, uint a, uint t, uint size) + { + EmitMemory1234InstructionCore(context, rn, rm, 1 << (int)size, (address) => + { + EmitMemoryLoad1SingleReplicateInstruction(context, address, rd, size, t + 1, 1, context.Arm64Assembler.Ld1rAsNoPostIndex); + }); + } + + public static void Vld1M(CodeGenContext context, uint rd, uint rn, uint rm, uint registersCount, uint align, uint size) + { + EmitMemory1234InstructionCore(context, rn, rm, 8 * (int)registersCount, (address) => + { + EmitMemoryLoad1234MultipleInstruction(context, address, rd, size, registersCount, 1, context.Arm64Assembler.Ld1MultAsNoPostIndex); + }); + } + + public static void Vld21(CodeGenContext context, uint rd, uint rn, uint rm, uint indexAlign, uint size) + { + uint index = indexAlign >> ((int)size + 1); + uint step = size > 0 && (indexAlign & (1u << (int)size)) != 0 ? 2u : 1u; + + EmitMemory1234InstructionCore(context, rn, rm, 2 * (1 << (int)size), (address) => + { + EmitMemoryLoad1234SingleInstruction(context, address, rd, index, size, 2, step, context.Arm64Assembler.Ld2SnglAsNoPostIndex); + }); + } + + public static void Vld2A(CodeGenContext context, uint rd, uint rn, uint rm, uint a, uint t, uint size) + { + EmitMemory1234InstructionCore(context, rn, rm, 2 * (1 << (int)size), (address) => + { + EmitMemoryLoad234SingleReplicateInstruction(context, address, rd, size, 2, t + 1, context.Arm64Assembler.Ld2rAsNoPostIndex); + }); + } + + public static void Vld2M(CodeGenContext context, uint rd, uint rn, uint rm, uint type, uint align, uint size) + { + uint step = (type & 1) + 1; + + EmitMemory1234InstructionCore(context, rn, rm, 16, (address) => + { + EmitMemoryLoad1234MultipleInstruction(context, address, rd, size, 2, step, context.Arm64Assembler.Ld2MultAsNoPostIndex); + }); + } + + public static void Vld2M(CodeGenContext context, uint rd, uint rn, uint rm, uint align, uint size) + { + EmitMemory1234InstructionCore(context, rn, rm, 32, (address) => + { + EmitMemoryLoad1234Multiple2x2Instruction(context, address, rd, size, context.Arm64Assembler.Ld2MultAsNoPostIndex); + }); + } + + public static void Vld31(CodeGenContext context, uint rd, uint rn, uint rm, uint indexAlign, uint size) + { + uint index = indexAlign >> ((int)size + 1); + uint step = size > 0 && (indexAlign & (1u << (int)size)) != 0 ? 2u : 1u; + + EmitMemory1234InstructionCore(context, rn, rm, 3 * (1 << (int)size), (address) => + { + EmitMemoryLoad1234SingleInstruction(context, address, rd, index, size, 3, step, context.Arm64Assembler.Ld3SnglAsNoPostIndex); + }); + } + + public static void Vld3A(CodeGenContext context, uint rd, uint rn, uint rm, uint a, uint t, uint size) + { + EmitMemory1234InstructionCore(context, rn, rm, 3 * (1 << (int)size), (address) => + { + EmitMemoryLoad234SingleReplicateInstruction(context, address, rd, size, 3, t + 1, context.Arm64Assembler.Ld3rAsNoPostIndex); + }); + } + + public static void Vld3M(CodeGenContext context, uint rd, uint rn, uint rm, uint type, uint align, uint size) + { + uint step = (type & 1) + 1; + + EmitMemory1234InstructionCore(context, rn, rm, 24, (address) => + { + EmitMemoryLoad1234MultipleInstruction(context, address, rd, size, 3, step, context.Arm64Assembler.Ld3MultAsNoPostIndex); + }); + } + + public static void Vld41(CodeGenContext context, uint rd, uint rn, uint rm, uint indexAlign, uint size) + { + uint index = indexAlign >> ((int)size + 1); + uint step = size > 0 && (indexAlign & (1u << (int)size)) != 0 ? 2u : 1u; + + EmitMemory1234InstructionCore(context, rn, rm, 4 * (1 << (int)size), (address) => + { + EmitMemoryLoad1234SingleInstruction(context, address, rd, index, size, 4, step, context.Arm64Assembler.Ld4SnglAsNoPostIndex); + }); + } + + public static void Vld4A(CodeGenContext context, uint rd, uint rn, uint rm, uint a, uint t, uint size) + { + EmitMemory1234InstructionCore(context, rn, rm, 4 * (1 << (int)size), (address) => + { + EmitMemoryLoad234SingleReplicateInstruction(context, address, rd, size, 4, t + 1, context.Arm64Assembler.Ld4rAsNoPostIndex); + }); + } + + public static void Vld4M(CodeGenContext context, uint rd, uint rn, uint rm, uint type, uint align, uint size) + { + uint step = (type & 1) + 1; + + EmitMemory1234InstructionCore(context, rn, rm, 32, (address) => + { + EmitMemoryLoad1234MultipleInstruction(context, address, rd, size, 4, step, context.Arm64Assembler.Ld4MultAsNoPostIndex); + }); + } + + public static void Vldm(CodeGenContext context, uint rd, uint rn, uint registerCount, bool u, bool w, bool singleRegs) + { + EmitMemoryMultipleInstruction(context, rd, rn, registerCount, u, w, singleRegs, isStore: false); + } + + public static void Vldr(CodeGenContext context, uint rd, uint rn, uint imm8, bool u, uint size) + { + EmitMemoryInstruction(context, rd, rn, imm8, u, size, isStore: false); + } + + public static void Vst11(CodeGenContext context, uint rd, uint rn, uint rm, uint indexAlign, uint size) + { + uint index = indexAlign >> ((int)size + 1); + + EmitMemory1234InstructionCore(context, rn, rm, 1 << (int)size, (address) => + { + EmitMemoryStore1234SingleInstruction(context, address, rd, index, size, 1, 1, context.Arm64Assembler.St1SnglAsNoPostIndex); + }); + } + + public static void Vst1M(CodeGenContext context, uint rd, uint rn, uint rm, uint registersCount, uint align, uint size) + { + EmitMemory1234InstructionCore(context, rn, rm, 8 * (int)registersCount, (address) => + { + EmitMemoryStore1234MultipleInstruction(context, address, rd, size, registersCount, 1, context.Arm64Assembler.St1MultAsNoPostIndex); + }); + } + + public static void Vst21(CodeGenContext context, uint rd, uint rn, uint rm, uint indexAlign, uint size) + { + uint index = indexAlign >> ((int)size + 1); + uint step = size > 0 && (indexAlign & (1u << (int)size)) != 0 ? 2u : 1u; + + EmitMemory1234InstructionCore(context, rn, rm, 2 * (1 << (int)size), (address) => + { + EmitMemoryStore1234SingleInstruction(context, address, rd, index, size, 2, step, context.Arm64Assembler.St2SnglAsNoPostIndex); + }); + } + + public static void Vst2M(CodeGenContext context, uint rd, uint rn, uint rm, uint type, uint align, uint size) + { + uint step = (type & 1) + 1; + + EmitMemory1234InstructionCore(context, rn, rm, 16, (address) => + { + EmitMemoryStore1234MultipleInstruction(context, address, rd, size, 2, step, context.Arm64Assembler.St2MultAsNoPostIndex); + }); + } + + public static void Vst2M(CodeGenContext context, uint rd, uint rn, uint rm, uint align, uint size) + { + EmitMemory1234InstructionCore(context, rn, rm, 32, (address) => + { + EmitMemoryStore1234Multiple2x2Instruction(context, address, rd, size, context.Arm64Assembler.St2MultAsNoPostIndex); + }); + } + + public static void Vst31(CodeGenContext context, uint rd, uint rn, uint rm, uint indexAlign, uint size) + { + uint index = indexAlign >> ((int)size + 1); + uint step = size > 0 && (indexAlign & (1u << (int)size)) != 0 ? 2u : 1u; + + EmitMemory1234InstructionCore(context, rn, rm, 3 * (1 << (int)size), (address) => + { + EmitMemoryStore1234SingleInstruction(context, address, rd, index, size, 3, step, context.Arm64Assembler.St3SnglAsNoPostIndex); + }); + } + + public static void Vst3M(CodeGenContext context, uint rd, uint rn, uint rm, uint type, uint align, uint size) + { + uint step = (type & 1) + 1; + + EmitMemory1234InstructionCore(context, rn, rm, 24, (address) => + { + EmitMemoryStore1234MultipleInstruction(context, address, rd, size, 3, step, context.Arm64Assembler.St3MultAsNoPostIndex); + }); + } + + public static void Vst41(CodeGenContext context, uint rd, uint rn, uint rm, uint indexAlign, uint size) + { + uint index = indexAlign >> ((int)size + 1); + uint step = size > 0 && (indexAlign & (1u << (int)size)) != 0 ? 2u : 1u; + + EmitMemory1234InstructionCore(context, rn, rm, 4 * (1 << (int)size), (address) => + { + EmitMemoryStore1234SingleInstruction(context, address, rd, index, size, 4, step, context.Arm64Assembler.St4SnglAsNoPostIndex); + }); + } + + public static void Vst4M(CodeGenContext context, uint rd, uint rn, uint rm, uint type, uint align, uint size) + { + uint step = (type & 1) + 1; + + EmitMemory1234InstructionCore(context, rn, rm, 32, (address) => + { + EmitMemoryStore1234MultipleInstruction(context, address, rd, size, 4, step, context.Arm64Assembler.St4MultAsNoPostIndex); + }); + } + + public static void Vstm(CodeGenContext context, uint rd, uint rn, uint registerCount, bool u, bool w, bool singleRegs) + { + EmitMemoryMultipleInstruction(context, rd, rn, registerCount, u, w, singleRegs, isStore: true); + } + + public static void Vstr(CodeGenContext context, uint rd, uint rn, uint imm8, bool u, uint size) + { + EmitMemoryInstruction(context, rd, rn, imm8, u, size, isStore: true); + } + + private static void EmitMemoryMultipleInstruction( + CodeGenContext context, + uint rd, + uint rn, + uint registerCount, + bool add, + bool wBack, + bool singleRegs, + bool isStore) + { + Operand baseAddress = InstEmitCommon.GetInputGpr(context, rn); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand offset = InstEmitCommon.Const((int)registerCount * (singleRegs ? 4 : 8)); + + if (!add) + { + if (wBack) + { + InstEmitMemory.WriteAddShiftOffset(context.Arm64Assembler, baseAddress, baseAddress, offset, false, ArmShiftType.Lsl, 0); + InstEmitMemory.WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, baseAddress); + } + else + { + InstEmitMemory.WriteAddShiftOffset(context.Arm64Assembler, tempRegister.Operand, baseAddress, offset, false, ArmShiftType.Lsl, 0); + InstEmitMemory.WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, tempRegister.Operand); + } + } + else + { + InstEmitMemory.WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, tempRegister.Operand, baseAddress); + } + + EmitMemoryMultipleInstructionCore(context, tempRegister.Operand, rd, registerCount, singleRegs, isStore); + + if (add && wBack) + { + context.Arm64Assembler.Add(baseAddress, baseAddress, offset); + } + } + + private static void EmitMemoryMultipleInstructionCore(CodeGenContext context, Operand baseAddress, uint rd, uint registerCount, bool singleRegs, bool isStore) + { + int offs = 0; + uint r = rd; + uint upperBound = Math.Min(rd + registerCount, 32u); + uint regMask = singleRegs ? 3u : 1u; + + // Read/write misaligned elements first. + + for (; (r & regMask) != 0 && r < upperBound; r++) + { + EmitMemoryInstruction(context, baseAddress, r, offs, singleRegs, isStore); + + offs += singleRegs ? 4 : 8; + } + + // Read/write aligned, full vectors. + + while (upperBound - r >= (singleRegs ? 4 : 2)) + { + int qIndex = (int)(r >> (singleRegs ? 2 : 1)); + + Operand rtOperand = context.RegisterAllocator.RemapSimdRegister(qIndex); + + if (upperBound - r >= (singleRegs ? 8 : 4) && (offs & 0xf) == 0) + { + Operand rt2Operand = context.RegisterAllocator.RemapSimdRegister(qIndex + 1); + + if (isStore) + { + context.Arm64Assembler.StpRiUn(rtOperand, rt2Operand, baseAddress, offs); + } + else + { + context.Arm64Assembler.LdpRiUn(rtOperand, rt2Operand, baseAddress, offs); + } + + r += singleRegs ? 8u : 4u; + offs += 32; + } + else + { + if ((offs & 0xf) == 0) + { + if (isStore) + { + context.Arm64Assembler.StrRiUn(rtOperand, baseAddress, offs); + } + else + { + context.Arm64Assembler.LdrRiUn(rtOperand, baseAddress, offs); + } + } + else + { + if (isStore) + { + context.Arm64Assembler.Stur(rtOperand, baseAddress, offs); + } + else + { + context.Arm64Assembler.Ldur(rtOperand, baseAddress, offs); + } + } + + r += singleRegs ? 4u : 2u; + offs += 16; + } + } + + // Read/write last misaligned elements. + + for (; r < upperBound; r++) + { + EmitMemoryInstruction(context, baseAddress, r, offs, singleRegs, isStore); + + offs += singleRegs ? 4 : 8; + } + } + + private static void EmitMemoryInstruction(CodeGenContext context, Operand baseAddress, uint r, int offs, bool singleRegs, bool isStore) + { + if (isStore) + { + using ScopedRegister tempRegister = InstEmitNeonCommon.MoveScalarToSide(context, r, singleRegs); + + context.Arm64Assembler.StrRiUn(tempRegister.Operand, baseAddress, offs); + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempFpRegisterScoped(singleRegs); + + context.Arm64Assembler.LdrRiUn(tempRegister.Operand, baseAddress, offs); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, r, singleRegs); + } + } + + private static void EmitMemoryInstruction(CodeGenContext context, uint rd, uint rn, uint imm8, bool add, uint size, bool isStore) + { + bool singleRegs = size != 3; + int offs = (int)imm8; + + if (size == 1) + { + offs <<= 1; + } + else + { + offs <<= 2; + } + + using ScopedRegister address = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + if (rn == RegisterUtils.PcRegister) + { + if (!add) + { + offs = -offs; + } + + context.Arm64Assembler.Mov(address.Operand, (context.Pc & ~3u) + (uint)offs); + + InstEmitMemory.WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, address.Operand, address.Operand); + + offs = 0; + } + else + { + Operand rnOperand = context.RegisterAllocator.RemapGprRegister((int)rn); + + if (InstEmitMemory.CanFoldOffset(context.MemoryManagerType, add ? offs : -offs, (int)size, true, out _)) + { + InstEmitMemory.WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, address.Operand, rnOperand); + + if (!add) + { + offs = -offs; + } + } + else + { + InstEmitMemory.WriteAddShiftOffset(context.Arm64Assembler, address.Operand, rnOperand, InstEmitCommon.Const(offs), add, ArmShiftType.Lsl, 0); + InstEmitMemory.WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, address.Operand, address.Operand); + + offs = 0; + } + } + + if ((size == 3 && (offs & 7) != 0) || offs < 0) + { + if (isStore) + { + using ScopedRegister tempRegister = InstEmitNeonCommon.MoveScalarToSide(context, rd, singleRegs); + + context.Arm64Assembler.Stur(tempRegister.Operand, address.Operand, offs, size); + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempFpRegisterScoped(singleRegs); + + context.Arm64Assembler.Ldur(tempRegister.Operand, address.Operand, offs, size); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, singleRegs); + } + } + else + { + if (isStore) + { + using ScopedRegister tempRegister = InstEmitNeonCommon.MoveScalarToSide(context, rd, singleRegs); + + context.Arm64Assembler.StrRiUn(tempRegister.Operand, address.Operand, offs, size); + } + else + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempFpRegisterScoped(singleRegs); + + context.Arm64Assembler.LdrRiUn(tempRegister.Operand, address.Operand, offs, size); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, singleRegs); + } + } + } + + private static void EmitMemory1234InstructionCore(CodeGenContext context, uint rn, uint rm, int bytes, Action callback) + { + bool wBack = rm != RegisterUtils.PcRegister; + bool registerIndex = rm != RegisterUtils.PcRegister && rm != RegisterUtils.SpRegister; + + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + using ScopedRegister address = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + InstEmitMemory.WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, context.Arm64Assembler, address.Operand, rnOperand); + + callback(address.Operand); + + if (wBack) + { + if (registerIndex) + { + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + context.Arm64Assembler.Add(rnOperand, rnOperand, rmOperand); + } + else + { + context.Arm64Assembler.Add(rnOperand, rnOperand, InstEmitCommon.Const(bytes)); + } + } + } + + private static void EmitMemoryLoad1234SingleInstruction( + CodeGenContext context, + Operand baseAddress, + uint rd, + uint index, + uint size, + uint registerCount, + uint step, + Action action) + { + ScopedRegister[] tempRegisters = AllocateSequentialRegisters(context, (int)registerCount); + + MoveDoublewordsToQuadwordsLower(context, rd, registerCount, step, tempRegisters); + + action(tempRegisters[0].Operand, baseAddress, index, size); + + MoveQuadwordsLowerToDoublewords(context, rd, registerCount, step, tempRegisters); + + FreeSequentialRegisters(tempRegisters); + } + + private static void EmitMemoryLoad1SingleReplicateInstruction( + CodeGenContext context, + Operand baseAddress, + uint rd, + uint size, + uint registerCount, + uint step, + Action action) + { + if ((rd & 1) == 0 && registerCount == 2) + { + action(context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)), baseAddress, size, 1); + } + else + { + uint vecsCount = (registerCount + 1) >> 1; + + ScopedRegister[] tempRegisters = AllocateSequentialRegisters(context, (int)vecsCount); + + action(tempRegisters[0].Operand, baseAddress, size, registerCount > 1 ? 1u : 0u); + + MoveQuadwordsToDoublewords(context, rd, registerCount, step, tempRegisters); + + FreeSequentialRegisters(tempRegisters); + } + } + + private static void EmitMemoryLoad234SingleReplicateInstruction( + CodeGenContext context, + Operand baseAddress, + uint rd, + uint size, + uint registerCount, + uint step, + Action action) + { + ScopedRegister[] tempRegisters = AllocateSequentialRegisters(context, (int)registerCount); + + action(tempRegisters[0].Operand, baseAddress, size, 0u); + + MoveQuadwordsLowerToDoublewords(context, rd, registerCount, step, tempRegisters); + + FreeSequentialRegisters(tempRegisters); + } + + private static void EmitMemoryLoad1234MultipleInstruction( + CodeGenContext context, + Operand baseAddress, + uint rd, + uint size, + uint registerCount, + uint step, + Action action) + { + ScopedRegister[] tempRegisters = AllocateSequentialRegisters(context, (int)registerCount); + + action(tempRegisters[0].Operand, baseAddress, size, 0); + + MoveQuadwordsLowerToDoublewords(context, rd, registerCount, step, tempRegisters); + + FreeSequentialRegisters(tempRegisters); + } + + private static void EmitMemoryLoad1234MultipleInstruction( + CodeGenContext context, + Operand baseAddress, + uint rd, + uint size, + uint registerCount, + uint step, + Action action) + { + ScopedRegister[] tempRegisters = AllocateSequentialRegisters(context, (int)registerCount); + + action(tempRegisters[0].Operand, baseAddress, registerCount, size, 0); + + MoveQuadwordsLowerToDoublewords(context, rd, registerCount, step, tempRegisters); + + FreeSequentialRegisters(tempRegisters); + } + + private static void EmitMemoryLoad1234Multiple2x2Instruction( + CodeGenContext context, + Operand baseAddress, + uint rd, + uint size, + Action action) + { + if ((rd & 1) == 0) + { + action(context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1), 2), baseAddress, size, 1); + } + else + { + ScopedRegister[] tempRegisters = AllocateSequentialRegisters(context, 2); + + action(tempRegisters[0].Operand, baseAddress, size, 1); + + MoveQuadwordsToDoublewords2x2(context, rd, tempRegisters); + + FreeSequentialRegisters(tempRegisters); + } + } + + private static void EmitMemoryStore1234SingleInstruction( + CodeGenContext context, + Operand baseAddress, + uint rd, + uint index, + uint size, + uint registerCount, + uint step, + Action action) + { + ScopedRegister[] tempRegisters = AllocateSequentialRegisters(context, (int)registerCount); + + MoveDoublewordsToQuadwordsLower(context, rd, registerCount, step, tempRegisters); + + action(tempRegisters[0].Operand, baseAddress, index, size); + + FreeSequentialRegisters(tempRegisters); + } + + private static void EmitMemoryStore1234MultipleInstruction( + CodeGenContext context, + Operand baseAddress, + uint rd, + uint size, + uint registerCount, + uint step, + Action action) + { + ScopedRegister[] tempRegisters = AllocateSequentialRegisters(context, (int)registerCount); + + MoveDoublewordsToQuadwordsLower(context, rd, registerCount, step, tempRegisters); + + action(tempRegisters[0].Operand, baseAddress, size, 0); + + FreeSequentialRegisters(tempRegisters); + } + + private static void EmitMemoryStore1234MultipleInstruction( + CodeGenContext context, + Operand baseAddress, + uint rd, + uint size, + uint registerCount, + uint step, + Action action) + { + ScopedRegister[] tempRegisters = AllocateSequentialRegisters(context, (int)registerCount); + + MoveDoublewordsToQuadwordsLower(context, rd, registerCount, step, tempRegisters); + + action(tempRegisters[0].Operand, baseAddress, registerCount, size, 0); + + FreeSequentialRegisters(tempRegisters); + } + + private static void EmitMemoryStore1234Multiple2x2Instruction( + CodeGenContext context, + Operand baseAddress, + uint rd, + uint size, + Action action) + { + if ((rd & 1) == 0) + { + action(context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1), 2), baseAddress, size, 1); + } + else + { + ScopedRegister[] tempRegisters = AllocateSequentialRegisters(context, 2); + + MoveDoublewordsToQuadwords2x2(context, rd, tempRegisters); + + action(tempRegisters[0].Operand, baseAddress, size, 1); + + FreeSequentialRegisters(tempRegisters); + } + } + + private static ScopedRegister[] AllocateSequentialRegisters(CodeGenContext context, int count) + { + ScopedRegister[] registers = new ScopedRegister[count]; + + for (int index = 0; index < count; index++) + { + registers[index] = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + } + + AssertSequentialRegisters(registers); + + return registers; + } + + private static void FreeSequentialRegisters(ReadOnlySpan registers) + { + for (int index = 0; index < registers.Length; index++) + { + registers[index].Dispose(); + } + } + + [Conditional("DEBUG")] + private static void AssertSequentialRegisters(ReadOnlySpan registers) + { + for (int index = 1; index < registers.Length; index++) + { + Debug.Assert(registers[index].Operand.GetRegister().Index == registers[0].Operand.GetRegister().Index + index); + } + } + + private static void MoveQuadwordsLowerToDoublewords(CodeGenContext context, uint rd, uint registerCount, uint step, ReadOnlySpan registers) + { + for (int index = 0; index < registerCount; index++) + { + uint r = rd + (uint)index * step; + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(r >> 1)); + uint imm5 = InstEmitNeonCommon.GetImm5ForElementIndex(r & 1u, false); + context.Arm64Assembler.InsElt(rdOperand, registers[index].Operand, 0, imm5); + } + } + + private static void MoveDoublewordsToQuadwordsLower(CodeGenContext context, uint rd, uint registerCount, uint step, ReadOnlySpan registers) + { + for (int index = 0; index < registerCount; index++) + { + uint r = rd + (uint)index * step; + + InstEmitNeonCommon.MoveScalarToSide(context, registers[index].Operand, r, false); + } + } + + private static void MoveDoublewordsToQuadwords2x2(CodeGenContext context, uint rd, ReadOnlySpan registers) + { + for (int index = 0; index < 2; index++) + { + uint r = rd + (uint)index * 2; + uint r2 = r + 1; + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(r >> 1)); + uint imm5 = InstEmitNeonCommon.GetImm5ForElementIndex(0, false); + context.Arm64Assembler.InsElt(registers[index].Operand, rdOperand, (r & 1u) << 3, imm5); + + rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(r2 >> 1)); + imm5 = InstEmitNeonCommon.GetImm5ForElementIndex(1, false); + context.Arm64Assembler.InsElt(registers[index].Operand, rdOperand, (r2 & 1u) << 3, imm5); + } + } + + private static void MoveQuadwordsToDoublewords(CodeGenContext context, uint rd, uint registerCount, uint step, ReadOnlySpan registers) + { + for (int index = 0; index < registerCount; index++) + { + uint r = rd + (uint)index * step; + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(r >> 1)); + uint imm5 = InstEmitNeonCommon.GetImm5ForElementIndex(r & 1u, false); + context.Arm64Assembler.InsElt(rdOperand, registers[index >> 1].Operand, ((uint)index & 1u) << 3, imm5); + } + } + + private static void MoveQuadwordsToDoublewords2x2(CodeGenContext context, uint rd, ReadOnlySpan registers) + { + for (int index = 0; index < 2; index++) + { + uint r = rd + (uint)index * 2; + uint r2 = r + 1; + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(r >> 1)); + uint imm5 = InstEmitNeonCommon.GetImm5ForElementIndex(r & 1u, false); + context.Arm64Assembler.InsElt(rdOperand, registers[index].Operand, 0, imm5); + + rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(r2 >> 1)); + imm5 = InstEmitNeonCommon.GetImm5ForElementIndex(r2 & 1u, false); + context.Arm64Assembler.InsElt(rdOperand, registers[index].Operand, 1u << 3, imm5); + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonMove.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonMove.cs new file mode 100644 index 00000000..08a0673a --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonMove.cs @@ -0,0 +1,665 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using System; +using System.Diagnostics; +using System.Numerics; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitNeonMove + { + public static void VdupR(CodeGenContext context, uint rd, uint rt, uint b, uint e, uint q) + { + uint size = 2 - (e | (b << 1)); + + Debug.Assert(size < 3); + + Operand rtOperand = InstEmitCommon.GetInputGpr(context, rt); + + uint imm5 = InstEmitNeonCommon.GetImm5ForElementIndex(0, size); + + if (q == 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + context.Arm64Assembler.DupGen(tempRegister.Operand, rtOperand, imm5, q); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Debug.Assert((rd & 1) == 0); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + + context.Arm64Assembler.DupGen(rdOperand, rtOperand, imm5, q); + } + } + + public static void VdupS(CodeGenContext context, uint rd, uint rm, uint imm4, uint q) + { + uint size = (uint)BitOperations.TrailingZeroCount(imm4); + + Debug.Assert(size < 3); + + uint index = imm4 >> (int)(size + 1); + + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + uint imm5 = InstEmitNeonCommon.GetImm5ForElementIndex(index | ((rm & 1) << (int)(3 - size)), size); + + if (q == 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + context.Arm64Assembler.DupEltVectorFromElement(tempRegister.Operand, rmOperand, imm5, q); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Debug.Assert((rd & 1) == 0); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + + context.Arm64Assembler.DupEltVectorFromElement(rdOperand, rmOperand, imm5, q); + } + } + + public static void Vext(CodeGenContext context, uint rd, uint rn, uint rm, uint imm4, uint q) + { + if (q == 0) + { + using ScopedRegister rnReg = InstEmitNeonCommon.MoveScalarToSide(context, rn, false); + using ScopedRegister rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, false); + + using ScopedRegister tempRegister = InstEmitNeonCommon.PickSimdRegister(context.RegisterAllocator, rnReg, rmReg); + + context.Arm64Assembler.Ext(tempRegister.Operand, rnReg.Operand, imm4, rmReg.Operand, q); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Debug.Assert(((rd | rn | rm) & 1) == 0); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rnOperand = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + context.Arm64Assembler.Ext(rdOperand, rnOperand, imm4, rmOperand, q); + } + } + + public static void Vmovl(CodeGenContext context, uint rd, uint rm, bool u, uint imm3h) + { + uint size = (uint)BitOperations.TrailingZeroCount(imm3h); + Debug.Assert(size < 3); + + InstEmitNeonCommon.EmitVectorBinaryLongShift( + context, + rd, + rm, + 0, + size, + isShl: true, + u ? context.Arm64Assembler.Ushll : context.Arm64Assembler.Sshll); + } + + public static void Vmovn(CodeGenContext context, uint rd, uint rm, uint size) + { + Debug.Assert(size < 3); + + InstEmitNeonCommon.EmitVectorUnaryNarrow(context, rd, rm, size, context.Arm64Assembler.Xtn); + } + + public static void Vmovx(CodeGenContext context, uint rd, uint rm) + { + InstEmitNeonCommon.EmitScalarBinaryShift(context, rd, rm, 16, 2, isShl: false, context.Arm64Assembler.UshrS); + } + + public static void VmovD(CodeGenContext context, uint rt, uint rt2, uint rm, bool op) + { + Operand rmReg = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + uint top = rm & 1; + uint ftype = top + 1; + + if (op) + { + Operand rtOperand = InstEmitCommon.GetOutputGpr(context, rt); + Operand rt2Operand = InstEmitCommon.GetOutputGpr(context, rt2); + + Operand rtOperand64 = new(OperandKind.Register, OperandType.I64, rtOperand.Value); + Operand rt2Operand64 = new(OperandKind.Register, OperandType.I64, rt2Operand.Value); + + context.Arm64Assembler.FmovFloatGen(rtOperand64, rmReg, ftype, 1, 0, top); + + context.Arm64Assembler.Lsr(rt2Operand64, rtOperand64, InstEmitCommon.Const(32)); + context.Arm64Assembler.Mov(rtOperand, rtOperand); // Zero-extend. + } + else + { + Operand rtOperand = InstEmitCommon.GetInputGpr(context, rt); + Operand rt2Operand = InstEmitCommon.GetInputGpr(context, rt2); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand tempRegister64 = new(OperandKind.Register, OperandType.I64, tempRegister.Operand.Value); + + context.Arm64Assembler.Lsl(tempRegister64, rt2Operand, InstEmitCommon.Const(32)); + context.Arm64Assembler.Orr(tempRegister64, tempRegister64, rtOperand); + + if (top == 0) + { + // Doing FMOV on Rm directly would clear the high bits if we are moving to the bottom. + + using ScopedRegister tempRegister2 = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + context.Arm64Assembler.FmovFloatGen(tempRegister2.Operand, tempRegister64, ftype, 1, 1, top); + + InstEmitNeonCommon.InsertResult(context, tempRegister2.Operand, rm, false); + } + else + { + context.Arm64Assembler.FmovFloatGen(rmReg, tempRegister64, ftype, 1, 1, top); + } + } + } + + public static void VmovH(CodeGenContext context, uint rt, uint rn, bool op) + { + if (op) + { + Operand rtOperand = InstEmitCommon.GetOutputGpr(context, rt); + + using ScopedRegister tempRegister = InstEmitNeonCommon.MoveScalarToSide(context, rn, true); + + context.Arm64Assembler.FmovFloatGen(rtOperand, tempRegister.Operand, 3, 0, 0, 0); + } + else + { + Operand rtOperand = InstEmitCommon.GetInputGpr(context, rt); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + context.Arm64Assembler.FmovFloatGen(tempRegister.Operand, rtOperand, 3, 0, 1, 0); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rn, true); + } + } + + public static void VmovI(CodeGenContext context, uint rd, uint op, uint cmode, uint imm8, uint q) + { + (uint a, uint b, uint c, uint d, uint e, uint f, uint g, uint h) = Split(imm8); + + if (q == 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + context.Arm64Assembler.Movi(tempRegister.Operand, h, g, f, e, d, cmode, c, b, a, op, q); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + + context.Arm64Assembler.Movi(rdOperand, h, g, f, e, d, cmode, c, b, a, op, q); + } + } + + public static void VmovFI(CodeGenContext context, uint rd, uint imm8, uint size) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + context.Arm64Assembler.FmovFloatImm(tempRegister.Operand, imm8, size ^ 2u); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, size != 3); + } + + public static void VmovR(CodeGenContext context, uint rd, uint rm, uint size) + { + bool singleRegister = size == 2; + + int shift = singleRegister ? 2 : 1; + uint mask = singleRegister ? 3u : 1u; + uint dstElt = rd & mask; + uint srcElt = rm & mask; + + uint imm4 = srcElt << (singleRegister ? 2 : 3); + uint imm5 = InstEmitNeonCommon.GetImm5ForElementIndex(dstElt, singleRegister); + + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> shift)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> shift)); + + context.Arm64Assembler.InsElt(rdOperand, rmOperand, imm4, imm5); + } + + public static void VmovRs(CodeGenContext context, uint rd, uint rt, uint opc1, uint opc2) + { + uint index; + uint size; + + if ((opc1 & 2u) != 0) + { + index = opc2 | ((opc1 & 1u) << 2); + size = 0; + } + else if ((opc2 & 1u) != 0) + { + index = (opc2 >> 1) | ((opc1 & 1u) << 1); + size = 1; + } + else + { + Debug.Assert(opc1 == 0 || opc1 == 1); + Debug.Assert(opc2 == 0); + + index = opc1 & 1u; + size = 2; + } + + index |= (rd & 1u) << (int)(3 - size); + + Operand rtOperand = InstEmitCommon.GetInputGpr(context, rt); + + Operand rdReg = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + + context.Arm64Assembler.InsGen(rdReg, rtOperand, InstEmitNeonCommon.GetImm5ForElementIndex(index, size)); + } + + public static void VmovS(CodeGenContext context, uint rt, uint rn, bool op) + { + if (op) + { + Operand rtOperand = InstEmitCommon.GetOutputGpr(context, rt); + + using ScopedRegister tempRegister = InstEmitNeonCommon.MoveScalarToSide(context, rn, true); + + context.Arm64Assembler.FmovFloatGen(rtOperand, tempRegister.Operand, 0, 0, 0, 0); + } + else + { + Operand rtOperand = InstEmitCommon.GetInputGpr(context, rt); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + context.Arm64Assembler.FmovFloatGen(tempRegister.Operand, rtOperand, 0, 0, 1, 0); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rn, true); + } + } + + public static void VmovSr(CodeGenContext context, uint rt, uint rn, bool u, uint opc1, uint opc2) + { + uint index; + uint size; + + if ((opc1 & 2u) != 0) + { + index = opc2 | ((opc1 & 1u) << 2); + size = 0; + } + else if ((opc2 & 1u) != 0) + { + index = (opc2 >> 1) | ((opc1 & 1u) << 1); + size = 1; + } + else + { + Debug.Assert(opc1 == 0 || opc1 == 1); + Debug.Assert(opc2 == 0); + Debug.Assert(!u); + + index = opc1 & 1u; + size = 2; + } + + index |= (rn & 1u) << (int)(3 - size); + + Operand rtOperand = InstEmitCommon.GetOutputGpr(context, rt); + + Operand rnReg = context.RegisterAllocator.RemapSimdRegister((int)(rn >> 1)); + + if (u || size > 1) + { + context.Arm64Assembler.Umov(rtOperand, rnReg, (int)index, (int)size); + } + else + { + context.Arm64Assembler.Smov(rtOperand, rnReg, (int)index, (int)size); + } + } + + public static void VmovSs(CodeGenContext context, uint rt, uint rt2, uint rm, bool op) + { + if ((rm & 1) == 0) + { + // If we are moving an aligned pair of single-precision registers, + // we can just move a single double-precision register. + + VmovD(context, rt, rt2, rm >> 1, op); + + return; + } + + if (op) + { + Operand rtOperand = InstEmitCommon.GetOutputGpr(context, rt); + Operand rt2Operand = InstEmitCommon.GetOutputGpr(context, rt2); + + using ScopedRegister rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, true); + using ScopedRegister rmReg2 = InstEmitNeonCommon.MoveScalarToSide(context, rm + 1, true); + + context.Arm64Assembler.FmovFloatGen(rtOperand, rmReg.Operand, 0, 0, 0, 0); + context.Arm64Assembler.FmovFloatGen(rt2Operand, rmReg2.Operand, 0, 0, 0, 0); + } + else + { + Operand rtOperand = InstEmitCommon.GetInputGpr(context, rt); + Operand rt2Operand = InstEmitCommon.GetInputGpr(context, rt2); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + context.Arm64Assembler.FmovFloatGen(tempRegister.Operand, rtOperand, 0, 0, 1, 0); + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rm, true); + + context.Arm64Assembler.FmovFloatGen(tempRegister.Operand, rt2Operand, 0, 0, 1, 0); + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rm + 1, true); + } + } + + public static void VmvnI(CodeGenContext context, uint rd, uint cmode, uint imm8, uint q) + { + (uint a, uint b, uint c, uint d, uint e, uint f, uint g, uint h) = Split(imm8); + + if (q == 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + context.Arm64Assembler.Mvni(tempRegister.Operand, h, g, f, e, d, cmode, c, b, a, q); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + + context.Arm64Assembler.Mvni(rdOperand, h, g, f, e, d, cmode, c, b, a, q); + } + } + + public static void VmvnR(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, q, context.Arm64Assembler.Not); + } + + public static void Vswp(CodeGenContext context, uint rd, uint rm, uint q) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + if (q == 0) + { + InstEmitNeonCommon.MoveScalarToSide(context, tempRegister.Operand, rd, false); + using ScopedRegister rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, false); + + InstEmitNeonCommon.InsertResult(context, rmReg.Operand, rd, false); + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rm, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + context.Arm64Assembler.Orr(tempRegister.Operand, rdOperand, rdOperand); // Temp = Rd + context.Arm64Assembler.Orr(rdOperand, rmOperand, rmOperand); // Rd = Rm + context.Arm64Assembler.Orr(rmOperand, tempRegister.Operand, tempRegister.Operand); // Rm = Temp + } + } + + public static void Vtbl(CodeGenContext context, uint rd, uint rn, uint rm, bool op, uint len) + { + // On AArch64, TBL/TBX works with 128-bit vectors, while on AArch32 it works with 64-bit vectors. + // We must combine the 64-bit vectors into a larger 128-bit one in some cases. + + // TODO: Peephole optimization to combine adjacent TBL instructions? + + Debug.Assert(len <= 3); + + bool isTbl = !op; + + len = Math.Min(len, 31 - rn); + + bool rangeMismatch = !isTbl && (len & 1) == 0; + + using ScopedRegister indicesReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, false, rangeMismatch); + + if (rangeMismatch) + { + // Force any index >= 8 * regs to be the maximum value, since on AArch64 we are working with a full vector, + // and the out of range value is 16 * regs, not 8 * regs. + + Debug.Assert(indicesReg.IsAllocated); + + using ScopedRegister tempRegister2 = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + if (len == 0) + { + (uint immb, uint immh) = InstEmitNeonCommon.GetImmbImmhForShift(3, 0, isShl: false); + + context.Arm64Assembler.UshrV(tempRegister2.Operand, indicesReg.Operand, immb, immh, 0); + context.Arm64Assembler.CmeqZeroV(tempRegister2.Operand, tempRegister2.Operand, 0, 0); + context.Arm64Assembler.Orn(indicesReg.Operand, indicesReg.Operand, tempRegister2.Operand, 0); + } + else + { + (uint a, uint b, uint c, uint d, uint e, uint f, uint g, uint h) = Split(8u * (len + 1)); + + context.Arm64Assembler.Movi(tempRegister2.Operand, h, g, f, e, d, 0xe, c, b, a, 0, 0); + context.Arm64Assembler.CmgeRegV(tempRegister2.Operand, indicesReg.Operand, tempRegister2.Operand, 0, 0); + context.Arm64Assembler.OrrReg(indicesReg.Operand, indicesReg.Operand, tempRegister2.Operand, 0); + } + } + + ScopedRegister tableReg1 = default; + ScopedRegister tableReg2 = default; + + switch (len) + { + case 0: + tableReg1 = MoveHalfToSideZeroUpper(context, rn); + break; + case 1: + tableReg1 = MoveDoublewords(context, rn, rn + 1); + break; + case 2: + tableReg1 = MoveDoublewords(context, rn, rn + 1, isOdd: true); + tableReg2 = MoveHalfToSideZeroUpper(context, rn + 2); + break; + case 3: + tableReg1 = MoveDoublewords(context, rn, rn + 1); + tableReg2 = MoveDoublewords(context, rn + 2, rn + 3); + break; + } + + // TBL works with consecutive registers, it is assumed that two consecutive calls to the register allocator + // will return consecutive registers. + + Debug.Assert(len < 2 || tableReg1.Operand.GetRegister().Index + 1 == tableReg2.Operand.GetRegister().Index); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + if (isTbl) + { + context.Arm64Assembler.Tbl(tempRegister.Operand, tableReg1.Operand, len >> 1, indicesReg.Operand, 0); + } + else + { + InstEmitNeonCommon.MoveScalarToSide(context, tempRegister.Operand, rd, false); + + context.Arm64Assembler.Tbx(tempRegister.Operand, tableReg1.Operand, len >> 1, indicesReg.Operand, 0); + } + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, false); + + tableReg1.Dispose(); + + if (len > 1) + { + tableReg2.Dispose(); + } + } + + public static void Vtrn(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + EmitVectorBinaryInterleavedTrn(context, rd, rm, size, q, context.Arm64Assembler.Trn1, context.Arm64Assembler.Trn2); + } + + public static void Vuzp(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + EmitVectorBinaryInterleaved(context, rd, rm, size, q, context.Arm64Assembler.Uzp1, context.Arm64Assembler.Uzp2); + } + + public static void Vzip(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + EmitVectorBinaryInterleaved(context, rd, rm, size, q, context.Arm64Assembler.Zip1, context.Arm64Assembler.Zip2); + } + + public static (uint, uint, uint, uint, uint, uint, uint, uint) Split(uint imm8) + { + uint a = (imm8 >> 7) & 1; + uint b = (imm8 >> 6) & 1; + uint c = (imm8 >> 5) & 1; + uint d = (imm8 >> 4) & 1; + uint e = (imm8 >> 3) & 1; + uint f = (imm8 >> 2) & 1; + uint g = (imm8 >> 1) & 1; + uint h = imm8 & 1; + + return (a, b, c, d, e, f, g, h); + } + + private static ScopedRegister MoveHalfToSideZeroUpper(CodeGenContext context, uint srcReg) + { + uint elt = srcReg & 1u; + + Operand source = context.RegisterAllocator.RemapSimdRegister((int)(srcReg >> 1)); + ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempFpRegisterScoped(false); + + uint imm5 = InstEmitNeonCommon.GetImm5ForElementIndex(elt, false); + + context.Arm64Assembler.DupEltScalarFromElement(tempRegister.Operand, source, imm5); + + return tempRegister; + } + + private static ScopedRegister MoveDoublewords(CodeGenContext context, uint lowerReg, uint upperReg, bool isOdd = false) + { + if ((lowerReg & 1) == 0 && upperReg == lowerReg + 1 && !isOdd) + { + return new ScopedRegister(context.RegisterAllocator, context.RegisterAllocator.RemapSimdRegister((int)(lowerReg >> 1)), false); + } + + Operand lowerSrc = context.RegisterAllocator.RemapSimdRegister((int)(lowerReg >> 1)); + Operand upperSrc = context.RegisterAllocator.RemapSimdRegister((int)(upperReg >> 1)); + ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempFpRegisterScoped(false); + + uint imm5 = InstEmitNeonCommon.GetImm5ForElementIndex(lowerReg & 1u, false); + + context.Arm64Assembler.DupEltScalarFromElement(tempRegister.Operand, lowerSrc, imm5); + + imm5 = InstEmitNeonCommon.GetImm5ForElementIndex(1, false); + + context.Arm64Assembler.InsElt(tempRegister.Operand, upperSrc, (upperReg & 1u) << 3, imm5); + + return tempRegister; + } + + private static void EmitVectorBinaryInterleavedTrn( + CodeGenContext context, + uint rd, + uint rm, + uint size, + uint q, + Action action1, + Action action2) + { + if (rd == rm) + { + // The behaviour when the registers are the same is "unpredictable" according to the manual. + + if (q == 0) + { + using ScopedRegister rdReg = InstEmitNeonCommon.MoveScalarToSide(context, rd, false); + using ScopedRegister rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, false); + + using ScopedRegister tempRegister1 = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + using ScopedRegister tempRegister2 = InstEmitNeonCommon.PickSimdRegister(context.RegisterAllocator, rdReg, rmReg); + + action1(tempRegister1.Operand, rdReg.Operand, rmReg.Operand, size, q); + action2(tempRegister2.Operand, rdReg.Operand, tempRegister1.Operand, size, q); + + InstEmitNeonCommon.InsertResult(context, tempRegister2.Operand, rd, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + action1(tempRegister.Operand, rdOperand, rmOperand, size, q); + action2(rmOperand, rdOperand, tempRegister.Operand, size, q); + } + } + else + { + EmitVectorBinaryInterleaved(context, rd, rm, size, q, action1, action2); + } + } + + private static void EmitVectorBinaryInterleaved( + CodeGenContext context, + uint rd, + uint rm, + uint size, + uint q, + Action action1, + Action action2) + { + if (q == 0) + { + using ScopedRegister rdReg = InstEmitNeonCommon.MoveScalarToSide(context, rd, false); + using ScopedRegister rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, false); + + using ScopedRegister tempRegister1 = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + using ScopedRegister tempRegister2 = InstEmitNeonCommon.PickSimdRegister(context.RegisterAllocator, rdReg, rmReg); + + action1(tempRegister1.Operand, rdReg.Operand, rmReg.Operand, size, q); + action2(tempRegister2.Operand, rdReg.Operand, rmReg.Operand, size, q); + + if (rd != rm) + { + InstEmitNeonCommon.InsertResult(context, tempRegister1.Operand, rd, false); + } + + InstEmitNeonCommon.InsertResult(context, tempRegister2.Operand, rm, false); + } + else + { + Operand rdOperand = context.RegisterAllocator.RemapSimdRegister((int)(rd >> 1)); + Operand rmOperand = context.RegisterAllocator.RemapSimdRegister((int)(rm >> 1)); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + action1(tempRegister.Operand, rdOperand, rmOperand, size, q); + action2(rmOperand, rdOperand, rmOperand, size, q); + + if (rd != rm) + { + context.Arm64Assembler.OrrReg(rdOperand, tempRegister.Operand, tempRegister.Operand, 1); + } + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonRound.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonRound.cs new file mode 100644 index 00000000..3c6ca65d --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonRound.cs @@ -0,0 +1,105 @@ +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitNeonRound + { + public static void Vraddhn(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryNarrow(context, rd, rn, rm, size, context.Arm64Assembler.Raddhn); + } + + public static void Vrhadd(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, u ? context.Arm64Assembler.Urhadd : context.Arm64Assembler.Srhadd, null); + } + + public static void Vrshl(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary( + context, + rd, + rm, + rn, + size, + q, + u ? context.Arm64Assembler.UrshlV : context.Arm64Assembler.SrshlV, + u ? context.Arm64Assembler.UrshlS : context.Arm64Assembler.SrshlS); + } + + public static void Vrshr(CodeGenContext context, uint rd, uint rm, bool u, uint l, uint imm6, uint q) + { + uint size = InstEmitNeonCommon.GetSizeFromImm7(imm6 | (l << 6)); + uint shift = InstEmitNeonShift.GetShiftRight(imm6, size); + + InstEmitNeonCommon.EmitVectorBinaryShift( + context, + rd, + rm, + shift, + size, + q, + isShl: false, + u ? context.Arm64Assembler.UrshrV : context.Arm64Assembler.SrshrV, + u ? context.Arm64Assembler.UrshrS : context.Arm64Assembler.SrshrS); + } + + public static void Vrshrn(CodeGenContext context, uint rd, uint rm, uint imm6) + { + uint size = InstEmitNeonCommon.GetSizeFromImm6(imm6); + uint shift = InstEmitNeonShift.GetShiftRight(imm6, size); + + InstEmitNeonCommon.EmitVectorBinaryNarrowShift(context, rd, rm, shift, size, isShl: false, context.Arm64Assembler.Rshrn); + } + + public static void Vrsra(CodeGenContext context, uint rd, uint rm, bool u, uint l, uint imm6, uint q) + { + uint size = InstEmitNeonCommon.GetSizeFromImm7(imm6 | (l << 6)); + uint shift = InstEmitNeonShift.GetShiftRight(imm6, size); + + InstEmitNeonCommon.EmitVectorTernaryRdShift( + context, + rd, + rm, + shift, + size, + q, + isShl: false, + u ? context.Arm64Assembler.UrsraV : context.Arm64Assembler.SrsraV, + u ? context.Arm64Assembler.UrsraS : context.Arm64Assembler.SrsraS); + } + + public static void Vrsubhn(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryNarrow(context, rd, rn, rm, size, context.Arm64Assembler.Rsubhn); + } + + public static void Vrinta(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FrintaSingleAndDouble, context.Arm64Assembler.FrintaHalf); + } + + public static void Vrintm(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FrintmSingleAndDouble, context.Arm64Assembler.FrintmHalf); + } + + public static void Vrintn(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FrintnSingleAndDouble, context.Arm64Assembler.FrintnHalf); + } + + public static void Vrintp(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FrintpSingleAndDouble, context.Arm64Assembler.FrintpHalf); + } + + public static void Vrintx(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FrintxSingleAndDouble, context.Arm64Assembler.FrintxHalf); + } + + public static void Vrintz(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnaryAnyF(context, rd, rm, size, q, context.Arm64Assembler.FrintzSingleAndDouble, context.Arm64Assembler.FrintzHalf); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonSaturate.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonSaturate.cs new file mode 100644 index 00000000..aeab726a --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonSaturate.cs @@ -0,0 +1,205 @@ +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitNeonSaturate + { + public static void Vqabs(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.SqabsV); + } + + public static void Vqadd(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary( + context, + rd, + rn, + rm, + size, + q, + u ? context.Arm64Assembler.UqaddV : context.Arm64Assembler.SqaddV, + u ? context.Arm64Assembler.UqaddS : context.Arm64Assembler.SqaddS); + } + + public static void Vqdmlal(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryLong(context, rd, rn, rm, size, context.Arm64Assembler.SqdmlalVecV); + } + + public static void VqdmlalS(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryLongByScalar(context, rd, rn, rm, size, context.Arm64Assembler.SqdmlalElt2regElement); + } + + public static void Vqdmlsl(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryLong(context, rd, rn, rm, size, context.Arm64Assembler.SqdmlslVecV); + } + + public static void VqdmlslS(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryLongByScalar(context, rd, rn, rm, size, context.Arm64Assembler.SqdmlslElt2regElement); + } + + public static void Vqdmulh(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, context.Arm64Assembler.SqdmulhVecV, context.Arm64Assembler.SqdmulhVecS); + } + + public static void VqdmulhS(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryByScalar(context, rd, rn, rm, size, q, context.Arm64Assembler.SqdmulhElt2regElement); + } + + public static void Vqdmull(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryLong(context, rd, rn, rm, size, context.Arm64Assembler.SqdmullVecV); + } + + public static void VqdmullS(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitVectorBinaryLongByScalar(context, rd, rn, rm, size, context.Arm64Assembler.SqdmullElt2regElement); + } + + public static void Vqmovn(CodeGenContext context, uint rd, uint rm, uint op, uint size) + { + if (op == 3) + { + InstEmitNeonCommon.EmitVectorUnaryNarrow(context, rd, rm, size, context.Arm64Assembler.UqxtnV); + } + else + { + InstEmitNeonCommon.EmitVectorUnaryNarrow(context, rd, rm, size, op == 1 ? context.Arm64Assembler.SqxtunV : context.Arm64Assembler.SqxtnV); + } + } + + public static void Vqneg(CodeGenContext context, uint rd, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorUnary(context, rd, rm, size, q, context.Arm64Assembler.SqnegV); + } + + public static void Vqrdmlah(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorTernaryRd(context, rd, rn, rm, size, q, context.Arm64Assembler.SqrdmlahVecV); + } + + public static void VqrdmlahS(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorTernaryRdByScalar(context, rd, rn, rm, size, q, context.Arm64Assembler.SqrdmlahElt2regElement); + } + + public static void Vqrdmlsh(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorTernaryRd(context, rd, rn, rm, size, q, context.Arm64Assembler.SqrdmlshVecV); + } + + public static void VqrdmlshS(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorTernaryRdByScalar(context, rd, rn, rm, size, q, context.Arm64Assembler.SqrdmlshElt2regElement); + } + + public static void Vqrdmulh(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rn, rm, size, q, context.Arm64Assembler.SqrdmulhVecV, context.Arm64Assembler.SqrdmulhVecS); + } + + public static void VqrdmulhS(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinaryByScalar(context, rd, rn, rm, size, q, context.Arm64Assembler.SqrdmulhElt2regElement); + } + + public static void Vqrshl(CodeGenContext context, uint rd, uint rn, uint rm, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rm, rn, size, q, context.Arm64Assembler.SqrshlV, context.Arm64Assembler.SqrshlS); + } + + public static void Vqrshrn(CodeGenContext context, uint rd, uint rm, bool u, uint op, uint imm6) + { + uint size = InstEmitNeonCommon.GetSizeFromImm6(imm6); + uint shift = InstEmitNeonShift.GetShiftRight(imm6, size); + + if (u && op == 0) + { + InstEmitNeonCommon.EmitVectorBinaryNarrowShift(context, rd, rm, shift, size, isShl: false, context.Arm64Assembler.SqrshrunV); + } + else if (!u && op == 1) + { + InstEmitNeonCommon.EmitVectorBinaryNarrowShift(context, rd, rm, shift, size, isShl: false, context.Arm64Assembler.SqrshrnV); + } + else + { + Debug.Assert(u && op == 1); // !u && op == 0 is the encoding for another instruction. + + InstEmitNeonCommon.EmitVectorBinaryNarrowShift(context, rd, rm, shift, size, isShl: false, context.Arm64Assembler.UqrshrnV); + } + } + + public static void VqshlI(CodeGenContext context, uint rd, uint rm, bool u, uint op, uint l, uint imm6, uint q) + { + uint size = InstEmitNeonCommon.GetSizeFromImm7(imm6 | (l << 6)); + uint shift = InstEmitNeonShift.GetShiftLeft(imm6, size); + + if (u && op == 0) + { + InstEmitNeonCommon.EmitVectorBinaryShift(context, rd, rm, shift, size, q, isShl: true, context.Arm64Assembler.SqshluV, context.Arm64Assembler.SqshluS); + } + else if (!u && op == 1) + { + InstEmitNeonCommon.EmitVectorBinaryShift(context, rd, rm, shift, size, q, isShl: true, context.Arm64Assembler.SqshlImmV, context.Arm64Assembler.SqshlImmS); + } + else + { + Debug.Assert(u && op == 1); // !u && op == 0 is the encoding for another instruction. + + InstEmitNeonCommon.EmitVectorBinaryShift(context, rd, rm, shift, size, q, isShl: true, context.Arm64Assembler.UqshlImmV, context.Arm64Assembler.UqshlImmS); + } + } + + public static void VqshlR(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + if (u) + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rm, rn, size, q, context.Arm64Assembler.UqshlRegV, context.Arm64Assembler.UqshlRegS); + } + else + { + InstEmitNeonCommon.EmitVectorBinary(context, rd, rm, rn, size, q, context.Arm64Assembler.SqshlRegV, context.Arm64Assembler.SqshlRegS); + } + } + + public static void Vqshrn(CodeGenContext context, uint rd, uint rm, bool u, uint op, uint imm6) + { + uint size = InstEmitNeonCommon.GetSizeFromImm6(imm6); + uint shift = InstEmitNeonShift.GetShiftRight(imm6, size); + + if (u && op == 0) + { + InstEmitNeonCommon.EmitVectorBinaryNarrowShift(context, rd, rm, shift, size, isShl: false, context.Arm64Assembler.SqshrunV); + } + else if (!u && op == 1) + { + InstEmitNeonCommon.EmitVectorBinaryNarrowShift(context, rd, rm, shift, size, isShl: false, context.Arm64Assembler.SqshrnV); + } + else + { + Debug.Assert(u && op == 1); // !u && op == 0 is the encoding for another instruction. + + InstEmitNeonCommon.EmitVectorBinaryNarrowShift(context, rd, rm, shift, size, isShl: false, context.Arm64Assembler.UqshrnV); + } + } + + public static void Vqsub(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary( + context, + rd, + rn, + rm, + size, + q, + u ? context.Arm64Assembler.UqsubV : context.Arm64Assembler.SqsubV, + u ? context.Arm64Assembler.UqsubS : context.Arm64Assembler.SqsubS); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonShift.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonShift.cs new file mode 100644 index 00000000..9f8d0bde --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonShift.cs @@ -0,0 +1,123 @@ +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitNeonShift + { + public static void Vshll(CodeGenContext context, uint rd, uint rm, uint imm6, bool u) + { + uint size = InstEmitNeonCommon.GetSizeFromImm7(imm6); + uint shift = GetShiftLeft(imm6, size); + + InstEmitNeonCommon.EmitVectorBinaryLongShift(context, rd, rm, shift, size, isShl: true, u ? context.Arm64Assembler.Ushll : context.Arm64Assembler.Sshll); + } + + public static void Vshll2(CodeGenContext context, uint rd, uint rm, uint size) + { + // Shift can't be encoded, so shift by value - 1 first, then first again by 1. + // Doesn't matter if we do a signed or unsigned shift in this case since all sign bits will be shifted out. + + uint shift = 8u << (int)size; + + InstEmitNeonCommon.EmitVectorBinaryLongShift(context, rd, rm, shift - 1, size, isShl: true, context.Arm64Assembler.Sshll); + InstEmitNeonCommon.EmitVectorBinaryLongShift(context, rd, rd, 1, size, isShl: true, context.Arm64Assembler.Sshll); + } + + public static void VshlI(CodeGenContext context, uint rd, uint rm, uint l, uint imm6, uint q) + { + uint size = InstEmitNeonCommon.GetSizeFromImm7(imm6 | (l << 6)); + uint shift = GetShiftLeft(imm6, size); + + InstEmitNeonCommon.EmitVectorBinaryShift(context, rd, rm, shift, size, q, isShl: true, context.Arm64Assembler.ShlV, context.Arm64Assembler.ShlS); + } + + public static void VshlR(CodeGenContext context, uint rd, uint rn, uint rm, bool u, uint size, uint q) + { + InstEmitNeonCommon.EmitVectorBinary( + context, + rd, + rm, + rn, + size, + q, + u ? context.Arm64Assembler.UshlV : context.Arm64Assembler.SshlV, + u ? context.Arm64Assembler.UshlS : context.Arm64Assembler.SshlS); + } + + public static void Vshr(CodeGenContext context, uint rd, uint rm, bool u, uint l, uint imm6, uint q) + { + uint size = InstEmitNeonCommon.GetSizeFromImm7(imm6 | (l << 6)); + uint shift = GetShiftRight(imm6, size); + + InstEmitNeonCommon.EmitVectorBinaryShift( + context, + rd, + rm, + shift, + size, + q, + isShl: false, + u ? context.Arm64Assembler.UshrV : context.Arm64Assembler.SshrV, + u ? context.Arm64Assembler.UshrS : context.Arm64Assembler.SshrS); + } + + public static void Vshrn(CodeGenContext context, uint rd, uint rm, uint imm6) + { + uint size = InstEmitNeonCommon.GetSizeFromImm6(imm6); + uint shift = GetShiftRight(imm6, size); + + InstEmitNeonCommon.EmitVectorBinaryNarrowShift(context, rd, rm, shift, size, isShl: false, context.Arm64Assembler.Shrn); + } + + public static void Vsli(CodeGenContext context, uint rd, uint rm, uint l, uint imm6, uint q) + { + uint size = InstEmitNeonCommon.GetSizeFromImm7(imm6 | (l << 6)); + uint shift = GetShiftLeft(imm6, size); + + InstEmitNeonCommon.EmitVectorBinaryShift( + context, + rd, + rm, + shift, + size, + q, + isShl: true, + context.Arm64Assembler.SliV, + context.Arm64Assembler.SliS); + } + + public static void Vsra(CodeGenContext context, uint rd, uint rm, bool u, uint l, uint imm6, uint q) + { + uint size = InstEmitNeonCommon.GetSizeFromImm7(imm6 | (l << 6)); + uint shift = GetShiftRight(imm6, size); + + InstEmitNeonCommon.EmitVectorTernaryRdShift( + context, + rd, + rm, + shift, + size, + q, + isShl: false, + u ? context.Arm64Assembler.UsraV : context.Arm64Assembler.SsraV, + u ? context.Arm64Assembler.UsraS : context.Arm64Assembler.SsraS); + } + + public static void Vsri(CodeGenContext context, uint rd, uint rm, uint l, uint imm6, uint q) + { + uint size = InstEmitNeonCommon.GetSizeFromImm7(imm6 | (l << 6)); + uint shift = GetShiftRight(imm6, size); + + InstEmitNeonCommon.EmitVectorBinaryShift(context, rd, rm, shift, size, q, isShl: false, context.Arm64Assembler.SriV, context.Arm64Assembler.SriS); + } + + public static uint GetShiftLeft(uint imm6, uint size) + { + return size < 3 ? imm6 - (8u << (int)size) : imm6; + } + + public static uint GetShiftRight(uint imm6, uint size) + { + return (size == 3 ? 64u : (16u << (int)size)) - imm6; + ; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonSystem.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonSystem.cs new file mode 100644 index 00000000..8c7bf91d --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitNeonSystem.cs @@ -0,0 +1,77 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitNeonSystem + { + public static void Vmrs(CodeGenContext context, uint rt, uint reg) + { + if (context.ConsumeSkipNextInstruction()) + { + // This case means that we managed to combine a VCMP and VMRS instruction, + // so we have nothing to do here as FCMP/FCMPE already set PSTATE.NZCV. + context.SetNzcvModified(); + + return; + } + + if (reg == 1) + { + // FPSCR + + Operand ctx = InstEmitSystem.Register(context.RegisterAllocator.FixedContextRegister); + + if (rt == RegisterUtils.PcRegister) + { + using ScopedRegister fpsrRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.LdrRiUn(fpsrRegister.Operand, ctx, NativeContextOffsets.FpFlagsBaseOffset); + context.Arm64Assembler.Lsr(fpsrRegister.Operand, fpsrRegister.Operand, InstEmitCommon.Const(28)); + + InstEmitCommon.RestoreNzcvFlags(context, fpsrRegister.Operand); + + context.SetNzcvModified(); + } + else + { + // FPSCR is a combination of the FPCR and FPSR registers. + // We also need to set the FPSR NZCV bits that no longer exist on AArch64. + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand rtOperand = InstEmitCommon.GetOutputGpr(context, rt); + + context.Arm64Assembler.MrsFpsr(rtOperand); + context.Arm64Assembler.MrsFpcr(tempRegister.Operand); + context.Arm64Assembler.Orr(rtOperand, rtOperand, tempRegister.Operand); + context.Arm64Assembler.LdrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FpFlagsBaseOffset); + context.Arm64Assembler.Bfc(tempRegister.Operand, 0, 28); + context.Arm64Assembler.Orr(rtOperand, rtOperand, tempRegister.Operand); + } + } + else + { + Operand rtOperand = InstEmitCommon.GetOutputGpr(context, rt); + + context.Arm64Assembler.Mov(rtOperand, 0u); + } + } + + public static void Vmsr(CodeGenContext context, uint rt, uint reg) + { + if (reg == 1) + { + // FPSCR + + // TODO: Do not set bits related to features that are not supported (like FP16)? + + Operand ctx = InstEmitSystem.Register(context.RegisterAllocator.FixedContextRegister); + Operand rtOperand = InstEmitCommon.GetInputGpr(context, rt); + + context.Arm64Assembler.MsrFpcr(rtOperand); + context.Arm64Assembler.MsrFpsr(rtOperand); + context.Arm64Assembler.StrRiUn(rtOperand, ctx, NativeContextOffsets.FpFlagsBaseOffset); + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSaturate.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSaturate.cs new file mode 100644 index 00000000..f1b6e395 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSaturate.cs @@ -0,0 +1,462 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitSaturate + { + public static void Qadd(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAddSubSaturate(context, rd, rn, rm, doubling: false, add: true); + } + + public static void Qadd16(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitSigned16BitPair(context, rd, rn, rm, (d, n, m) => + { + context.Arm64Assembler.Add(d, n, m); + EmitSaturateRange(context, d, d, 16, unsigned: false, setQ: false); + }); + } + + public static void Qadd8(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitSigned8BitPair(context, rd, rn, rm, (d, n, m) => + { + context.Arm64Assembler.Add(d, n, m); + EmitSaturateRange(context, d, d, 8, unsigned: false, setQ: false); + }); + } + + public static void Qasx(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitSigned16BitXPair(context, rd, rn, rm, (d, n, m, e) => + { + if (e == 0) + { + context.Arm64Assembler.Sub(d, n, m); + } + else + { + context.Arm64Assembler.Add(d, n, m); + } + + EmitSaturateRange(context, d, d, 16, unsigned: false, setQ: false); + }); + } + + public static void Qdadd(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAddSubSaturate(context, rd, rn, rm, doubling: true, add: true); + } + + public static void Qdsub(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAddSubSaturate(context, rd, rn, rm, doubling: true, add: false); + } + + public static void Qsax(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitSigned16BitXPair(context, rd, rn, rm, (d, n, m, e) => + { + if (e == 0) + { + context.Arm64Assembler.Add(d, n, m); + } + else + { + context.Arm64Assembler.Sub(d, n, m); + } + + EmitSaturateRange(context, d, d, 16, unsigned: false, setQ: false); + }); + } + + public static void Qsub(CodeGenContext context, uint rd, uint rn, uint rm) + { + EmitAddSubSaturate(context, rd, rn, rm, doubling: false, add: false); + } + + public static void Qsub16(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitSigned16BitPair(context, rd, rn, rm, (d, n, m) => + { + context.Arm64Assembler.Sub(d, n, m); + EmitSaturateRange(context, d, d, 16, unsigned: false, setQ: false); + }); + } + + public static void Qsub8(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitSigned8BitPair(context, rd, rn, rm, (d, n, m) => + { + context.Arm64Assembler.Sub(d, n, m); + EmitSaturateRange(context, d, d, 8, unsigned: false, setQ: false); + }); + } + + public static void Ssat(CodeGenContext context, uint rd, uint imm, uint rn, bool sh, uint shift) + { + EmitSaturate(context, rd, imm + 1, rn, sh, shift, unsigned: false); + } + + public static void Ssat16(CodeGenContext context, uint rd, uint imm, uint rn) + { + InstEmitCommon.EmitSigned16BitPair(context, rd, rn, (d, n) => + { + EmitSaturateRange(context, d, n, imm + 1, unsigned: false); + }); + } + + public static void Uqadd16(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitUnsigned16BitPair(context, rd, rn, rm, (d, n, m) => + { + context.Arm64Assembler.Add(d, n, m); + EmitSaturateUqadd(context, d, 16); + }); + } + + public static void Uqadd8(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitUnsigned8BitPair(context, rd, rn, rm, (d, n, m) => + { + context.Arm64Assembler.Add(d, n, m); + EmitSaturateUqadd(context, d, 8); + }); + } + + public static void Uqasx(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitUnsigned16BitXPair(context, rd, rn, rm, (d, n, m, e) => + { + if (e == 0) + { + context.Arm64Assembler.Sub(d, n, m); + } + else + { + context.Arm64Assembler.Add(d, n, m); + } + + EmitSaturateUq(context, d, 16, e == 0); + }); + } + + public static void Uqsax(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitUnsigned16BitXPair(context, rd, rn, rm, (d, n, m, e) => + { + if (e == 0) + { + context.Arm64Assembler.Add(d, n, m); + } + else + { + context.Arm64Assembler.Sub(d, n, m); + } + + EmitSaturateUq(context, d, 16, e != 0); + }); + } + + public static void Uqsub16(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitUnsigned16BitPair(context, rd, rn, rm, (d, n, m) => + { + context.Arm64Assembler.Sub(d, n, m); + EmitSaturateUqsub(context, d, 16); + }); + } + + public static void Uqsub8(CodeGenContext context, uint rd, uint rn, uint rm) + { + InstEmitCommon.EmitUnsigned8BitPair(context, rd, rn, rm, (d, n, m) => + { + context.Arm64Assembler.Sub(d, n, m); + EmitSaturateUqsub(context, d, 8); + }); + } + + public static void Usat(CodeGenContext context, uint rd, uint imm, uint rn, bool sh, uint shift) + { + EmitSaturate(context, rd, imm, rn, sh, shift, unsigned: true); + } + + public static void Usat16(CodeGenContext context, uint rd, uint imm, uint rn) + { + InstEmitCommon.EmitSigned16BitPair(context, rd, rn, (d, n) => + { + EmitSaturateRange(context, d, n, imm, unsigned: true); + }); + } + + private static void EmitAddSubSaturate(CodeGenContext context, uint rd, uint rn, uint rm, bool doubling, bool add) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm); + + using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand tempN64 = new(OperandKind.Register, OperandType.I64, tempN.Operand.Value); + Operand tempM64 = new(OperandKind.Register, OperandType.I64, tempM.Operand.Value); + + context.Arm64Assembler.Sxtw(tempN64, rnOperand); + context.Arm64Assembler.Sxtw(tempM64, rmOperand); + + if (doubling) + { + context.Arm64Assembler.Lsl(tempN64, tempN64, InstEmitCommon.Const(1)); + + EmitSaturateLongToInt(context, tempN64, tempN64); + } + + if (add) + { + context.Arm64Assembler.Add(tempN64, tempN64, tempM64); + } + else + { + context.Arm64Assembler.Sub(tempN64, tempN64, tempM64); + } + + EmitSaturateLongToInt(context, rdOperand, tempN64); + } + + private static void EmitSaturate(CodeGenContext context, uint rd, uint imm, uint rn, bool sh, uint shift, bool unsigned) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + if (sh && shift == 0) + { + shift = 31; + } + + if (shift != 0) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + if (sh) + { + context.Arm64Assembler.Asr(tempRegister.Operand, rnOperand, InstEmitCommon.Const((int)shift)); + } + else + { + context.Arm64Assembler.Lsl(tempRegister.Operand, rnOperand, InstEmitCommon.Const((int)shift)); + } + + EmitSaturateRange(context, rdOperand, tempRegister.Operand, imm, unsigned); + } + else + { + EmitSaturateRange(context, rdOperand, rnOperand, imm, unsigned); + } + } + + private static void EmitSaturateRange(CodeGenContext context, Operand result, Operand value, uint saturateTo, bool unsigned, bool setQ = true) + { + Debug.Assert(saturateTo <= 32); + Debug.Assert(!unsigned || saturateTo < 32); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + ScopedRegister tempValue = default; + + bool resultValueOverlap = result.Value == value.Value; + + if (!unsigned && saturateTo == 32) + { + // No saturation possible for this case. + + if (!resultValueOverlap) + { + context.Arm64Assembler.Mov(result, value); + } + + return; + } + else if (saturateTo == 0) + { + // Result is always zero if we saturate 0 bits. + + context.Arm64Assembler.Mov(result, 0u); + + return; + } + + if (resultValueOverlap) + { + tempValue = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.Mov(tempValue.Operand, value); + value = tempValue.Operand; + } + + if (unsigned) + { + // Negative values always saturate (to zero). + // So we must always ignore the sign bit when masking, so that the truncated value will differ from the original one. + + context.Arm64Assembler.And(result, value, InstEmitCommon.Const((int)(uint.MaxValue >> (32 - (int)saturateTo)))); + } + else + { + context.Arm64Assembler.Sbfx(result, value, 0, (int)saturateTo); + } + + context.Arm64Assembler.Sub(tempRegister.Operand, value, result); + + int branchIndex = context.CodeWriter.InstructionPointer; + + // If the result is 0, the values are equal and we don't need saturation. + context.Arm64Assembler.Cbz(tempRegister.Operand, 0); + + // Saturate and set Q flag. + if (unsigned) + { + if (saturateTo == 31) + { + // Only saturation case possible when going from 32 bits signed to 32 or 31 bits unsigned + // is when the signed input is negative, as all positive values are representable on a 31 bits range. + + context.Arm64Assembler.Mov(result, 0u); + } + else + { + context.Arm64Assembler.Asr(result, value, InstEmitCommon.Const(31)); + context.Arm64Assembler.Mvn(result, result); + context.Arm64Assembler.Lsr(result, result, InstEmitCommon.Const(32 - (int)saturateTo)); + } + } + else + { + if (saturateTo == 1) + { + context.Arm64Assembler.Asr(result, value, InstEmitCommon.Const(31)); + } + else + { + context.Arm64Assembler.Mov(result, uint.MaxValue >> (33 - (int)saturateTo)); + context.Arm64Assembler.Eor(result, result, value, ArmShiftType.Asr, 31); + } + } + + if (setQ) + { + SetQFlag(context); + } + + int delta = context.CodeWriter.InstructionPointer - branchIndex; + context.CodeWriter.WriteInstructionAt(branchIndex, context.CodeWriter.ReadInstructionAt(branchIndex) | (uint)((delta & 0x7ffff) << 5)); + + if (resultValueOverlap) + { + tempValue.Dispose(); + } + } + + private static void EmitSaturateUqadd(CodeGenContext context, Operand value, uint saturateTo) + { + EmitSaturateUq(context, value, saturateTo, isSub: false); + } + + private static void EmitSaturateUqsub(CodeGenContext context, Operand value, uint saturateTo) + { + EmitSaturateUq(context, value, saturateTo, isSub: true); + } + + private static void EmitSaturateUq(CodeGenContext context, Operand value, uint saturateTo, bool isSub) + { + Debug.Assert(saturateTo <= 32); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + if (saturateTo == 32) + { + // No saturation possible for this case. + + return; + } + else if (saturateTo == 0) + { + // Result is always zero if we saturate 0 bits. + + context.Arm64Assembler.Mov(value, 0u); + + return; + } + + context.Arm64Assembler.Lsr(tempRegister.Operand, value, InstEmitCommon.Const((int)saturateTo)); + + int branchIndex = context.CodeWriter.InstructionPointer; + + // If the result is 0, the values are equal and we don't need saturation. + context.Arm64Assembler.Cbz(tempRegister.Operand, 0); + + // Saturate. + context.Arm64Assembler.Mov(value, isSub ? 0u : uint.MaxValue >> (32 - (int)saturateTo)); + + int delta = context.CodeWriter.InstructionPointer - branchIndex; + context.CodeWriter.WriteInstructionAt(branchIndex, context.CodeWriter.ReadInstructionAt(branchIndex) | (uint)((delta & 0x7ffff) << 5)); + } + + private static void EmitSaturateLongToInt(CodeGenContext context, Operand result, Operand value) + { + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + ScopedRegister tempValue = default; + + bool resultValueOverlap = result.Value == value.Value; + + if (resultValueOverlap) + { + tempValue = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand tempValue64 = new(OperandKind.Register, OperandType.I64, tempValue.Operand.Value); + + context.Arm64Assembler.Mov(tempValue64, value); + value = tempValue64; + } + + Operand temp64 = new(OperandKind.Register, OperandType.I64, tempRegister.Operand.Value); + Operand result64 = new(OperandKind.Register, OperandType.I64, result.Value); + + context.Arm64Assembler.Sxtw(result64, value); + context.Arm64Assembler.Sub(temp64, value, result64); + + int branchIndex = context.CodeWriter.InstructionPointer; + + // If the result is 0, the values are equal and we don't need saturation. + context.Arm64Assembler.Cbz(temp64, 0); + + // Saturate and set Q flag. + context.Arm64Assembler.Mov(result, uint.MaxValue >> 1); + context.Arm64Assembler.Eor(result64, result64, value, ArmShiftType.Asr, 63); + + SetQFlag(context); + + int delta = context.CodeWriter.InstructionPointer - branchIndex; + context.CodeWriter.WriteInstructionAt(branchIndex, context.CodeWriter.ReadInstructionAt(branchIndex) | (uint)((delta & 0x7ffff) << 5)); + + context.Arm64Assembler.Mov(result, result); // Zero-extend. + + if (resultValueOverlap) + { + tempValue.Dispose(); + } + } + + public static void SetQFlag(CodeGenContext context) + { + Operand ctx = InstEmitSystem.Register(context.RegisterAllocator.FixedContextRegister); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.LdrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + context.Arm64Assembler.Orr(tempRegister.Operand, tempRegister.Operand, InstEmitCommon.Const(1 << 27)); + context.Arm64Assembler.StrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSystem.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSystem.cs new file mode 100644 index 00000000..07f9f86a --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitSystem.cs @@ -0,0 +1,660 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitSystem + { + private delegate void SoftwareInterruptHandler(ulong address, int imm); + private delegate ulong Get64(); + private delegate bool GetBool(); + + private const int SpIndex = 31; + + public static void Bkpt(CodeGenContext context, uint imm) + { + context.AddPendingBkpt(imm); + + context.Arm64Assembler.B(0); + } + + public static void Cps(CodeGenContext context, uint imod, uint m, uint a, uint i, uint f, uint mode) + { + // NOP in user mode. + } + + public static void Dbg(CodeGenContext context, uint option) + { + // NOP in ARMv8. + } + + public static void Hlt(CodeGenContext context, uint imm) + { + } + + public static void Mcr(CodeGenContext context, uint encoding, uint coproc, uint opc1, uint rt, uint crn, uint crm, uint opc2) + { + if (coproc != 15 || opc1 != 0) + { + Udf(context, encoding, 0); + + return; + } + + Operand ctx = Register(context.RegisterAllocator.FixedContextRegister); + Operand rtOperand = InstEmitCommon.GetInputGpr(context, rt); + + switch (crn) + { + case 13: // Process and Thread Info. + if (crm == 0) + { + switch (opc2) + { + case 2: + context.Arm64Assembler.StrRiUn(rtOperand, ctx, NativeContextOffsets.TpidrEl0Offset); + return; + } + } + break; + } + } + + public static void Mcrr(CodeGenContext context, uint encoding, uint coproc, uint opc1, uint rt, uint crm) + { + if (coproc != 15 || opc1 != 0) + { + Udf(context, encoding, 0); + + return; + } + + // We don't have any system register that needs to be modified using a 64-bit value. + } + + public static void Mrc(CodeGenContext context, uint encoding, uint coproc, uint opc1, uint rt, uint crn, uint crm, uint opc2) + { + if (coproc != 15 || opc1 != 0) + { + Udf(context, encoding, 0); + + return; + } + + Operand ctx = Register(context.RegisterAllocator.FixedContextRegister); + Operand rtOperand = InstEmitCommon.GetInputGpr(context, rt); + bool hasValue = false; + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + Operand dest = rt == RegisterUtils.PcRegister ? tempRegister.Operand : rtOperand; + + switch (crn) + { + case 13: // Process and Thread Info. + if (crm == 0) + { + switch (opc2) + { + case 2: + context.Arm64Assembler.LdrRiUn(dest, ctx, NativeContextOffsets.TpidrEl0Offset); + hasValue = true; + break; + case 3: + context.Arm64Assembler.LdrRiUn(dest, ctx, NativeContextOffsets.TpidrroEl0Offset); + hasValue = true; + break; + } + } + break; + } + + if (rt == RegisterUtils.PcRegister) + { + context.Arm64Assembler.MsrNzcv(dest); + context.SetNzcvModified(); + } + else if (!hasValue) + { + context.Arm64Assembler.Mov(dest, 0u); + } + } + + public static void Mrrc(CodeGenContext context, uint encoding, uint coproc, uint opc1, uint rt, uint rt2, uint crm) + { + if (coproc != 15) + { + Udf(context, encoding, 0); + + return; + } + + switch (crm) + { + case 14: + switch (opc1) + { + case 0: + context.AddPendingReadCntpct(rt, rt2); + context.Arm64Assembler.B(0); + return; + } + break; + } + + // Unsupported system register. + context.Arm64Assembler.Mov(InstEmitCommon.GetOutputGpr(context, rt), 0u); + context.Arm64Assembler.Mov(InstEmitCommon.GetOutputGpr(context, rt2), 0u); + } + + public static void Mrs(CodeGenContext context, uint rd, bool r) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + + if (r) + { + // Reads SPSR, unpredictable in user mode. + + context.Arm64Assembler.Mov(rdOperand, 0u); + } + else + { + Operand ctx = Register(context.RegisterAllocator.FixedContextRegister); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.LdrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + + // Copy GE flags to destination register. + context.Arm64Assembler.Ubfx(rdOperand, tempRegister.Operand, 16, 4); + + // Insert Q flag. + context.Arm64Assembler.And(tempRegister.Operand, tempRegister.Operand, InstEmitCommon.Const(1 << 27)); + context.Arm64Assembler.Orr(rdOperand, rdOperand, tempRegister.Operand); + + // Insert NZCV flags. + context.Arm64Assembler.MrsNzcv(tempRegister.Operand); + context.Arm64Assembler.Orr(rdOperand, rdOperand, tempRegister.Operand); + + // All other flags can't be accessed in user mode or have "unknown" values. + } + } + + public static void MrsBr(CodeGenContext context, uint rd, uint m1, bool r) + { + Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd); + + // Reads banked register, unpredictable in user mode. + + context.Arm64Assembler.Mov(rdOperand, 0u); + } + + public static void MsrBr(CodeGenContext context, uint rn, uint m1, bool r) + { + // Writes banked register, unpredictable in user mode. + } + + public static void MsrI(CodeGenContext context, uint imm, uint mask, bool r) + { + if (r) + { + // Writes SPSR, unpredictable in user mode. + } + else + { + Operand ctx = Register(context.RegisterAllocator.FixedContextRegister); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempRegister2 = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.LdrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + + if ((mask & 2) != 0) + { + // Endian flag. + + context.Arm64Assembler.Mov(tempRegister2.Operand, (imm >> 9) & 1); + context.Arm64Assembler.Bfi(tempRegister.Operand, tempRegister2.Operand, 9, 1); + } + + if ((mask & 4) != 0) + { + // GE flags. + + context.Arm64Assembler.Mov(tempRegister2.Operand, (imm >> 16) & 0xf); + context.Arm64Assembler.Bfi(tempRegister.Operand, tempRegister2.Operand, 16, 4); + } + + if ((mask & 8) != 0) + { + // NZCVQ flags. + + context.Arm64Assembler.Mov(tempRegister2.Operand, (imm >> 27) & 0x1f); + context.Arm64Assembler.Bfi(tempRegister.Operand, tempRegister2.Operand, 27, 5); + context.Arm64Assembler.Mov(tempRegister2.Operand, (imm >> 28) & 0xf); + InstEmitCommon.RestoreNzcvFlags(context, tempRegister2.Operand); + context.SetNzcvModified(); + } + } + } + + public static void MsrR(CodeGenContext context, uint rn, uint mask, bool r) + { + Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn); + + if (r) + { + // Writes SPSR, unpredictable in user mode. + } + else + { + Operand ctx = Register(context.RegisterAllocator.FixedContextRegister); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister tempRegister2 = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.LdrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + + if ((mask & 2) != 0) + { + // Endian flag. + + context.Arm64Assembler.Lsr(tempRegister2.Operand, rnOperand, InstEmitCommon.Const(9)); + context.Arm64Assembler.Bfi(tempRegister.Operand, tempRegister2.Operand, 9, 1); + } + + if ((mask & 4) != 0) + { + // GE flags. + + context.Arm64Assembler.Lsr(tempRegister2.Operand, rnOperand, InstEmitCommon.Const(16)); + context.Arm64Assembler.Bfi(tempRegister.Operand, tempRegister2.Operand, 16, 4); + } + + if ((mask & 8) != 0) + { + // NZCVQ flags. + + context.Arm64Assembler.Lsr(tempRegister2.Operand, rnOperand, InstEmitCommon.Const(27)); + context.Arm64Assembler.Bfi(tempRegister.Operand, tempRegister2.Operand, 27, 5); + context.Arm64Assembler.Lsr(tempRegister2.Operand, rnOperand, InstEmitCommon.Const(28)); + InstEmitCommon.RestoreNzcvFlags(context, tempRegister2.Operand); + context.SetNzcvModified(); + } + } + } + + public static void Setend(CodeGenContext context, bool e) + { + Operand ctx = Register(context.RegisterAllocator.FixedContextRegister); + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + context.Arm64Assembler.LdrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + + if (e) + { + context.Arm64Assembler.Orr(tempRegister.Operand, tempRegister.Operand, InstEmitCommon.Const(1 << 9)); + } + else + { + context.Arm64Assembler.Bfc(tempRegister.Operand, 9, 1); + } + + context.Arm64Assembler.StrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset); + } + + public static void Svc(CodeGenContext context, uint imm) + { + context.AddPendingSvc(imm); + context.Arm64Assembler.B(0); + } + + public static void Udf(CodeGenContext context, uint encoding, uint imm) + { + context.AddPendingUdf(encoding); + context.Arm64Assembler.B(0); + } + + public static void PrivilegedInstruction(CodeGenContext context, uint encoding) + { + Udf(context, encoding, 0); + } + + private static IntPtr GetBkptHandlerPtr() + { + return Marshal.GetFunctionPointerForDelegate(NativeInterface.Break); + } + + private static IntPtr GetSvcHandlerPtr() + { + return Marshal.GetFunctionPointerForDelegate(NativeInterface.SupervisorCall); + } + + private static IntPtr GetUdfHandlerPtr() + { + return Marshal.GetFunctionPointerForDelegate(NativeInterface.Undefined); + } + + private static IntPtr GetCntpctEl0Ptr() + { + return Marshal.GetFunctionPointerForDelegate(NativeInterface.GetCntpctEl0); + } + + private static IntPtr CheckSynchronizationPtr() + { + return Marshal.GetFunctionPointerForDelegate(NativeInterface.CheckSynchronization); + } + + public static bool NeedsCall(InstName name) + { + // All instructions that might do a host call should be included here. + // That is required to reserve space on the stack for caller saved registers. + + return name == InstName.Mrrc; + } + + public static bool NeedsCallSkipContext(InstName name) + { + // All instructions that might do a host call should be included here. + // That is required to reserve space on the stack for caller saved registers. + + switch (name) + { + case InstName.Mcr: + case InstName.Mrc: + case InstName.Svc: + case InstName.Udf: + return true; + } + + return false; + } + + public static void WriteBkpt(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset, uint pc, uint imm) + { + Assembler asm = new(writer); + + WriteCall(ref asm, regAlloc, GetBkptHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, imm); + WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset); + } + + public static void WriteSvc(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset, uint pc, uint svcId) + { + Assembler asm = new(writer); + + WriteCall(ref asm, regAlloc, GetSvcHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, svcId); + WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset); + } + + public static void WriteUdf(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset, uint pc, uint imm) + { + Assembler asm = new(writer); + + WriteCall(ref asm, regAlloc, GetUdfHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, imm); + WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset); + } + + public static void WriteReadCntpct(CodeWriter writer, RegisterAllocator regAlloc, int spillBaseOffset, int rt, int rt2) + { + Assembler asm = new(writer); + + uint resultMask = (1u << rt) | (1u << rt2); + int tempRegister = 0; + + while ((resultMask & (1u << tempRegister)) != 0 && tempRegister < 32) + { + tempRegister++; + } + + Debug.Assert(tempRegister < 32); + + WriteSpill(ref asm, regAlloc, resultMask, skipContext: false, spillBaseOffset, tempRegister); + + Operand rn = Register(tempRegister); + + asm.Mov(rn, (ulong)GetCntpctEl0Ptr()); + asm.Blr(rn); + + if (rt != rt2) + { + asm.Lsr(Register(rt2), Register(0), InstEmitCommon.Const(32)); + } + + asm.Mov(Register(rt, OperandType.I32), Register(0, OperandType.I32)); // Zero-extend. + + WriteFill(ref asm, regAlloc, resultMask, skipContext: false, spillBaseOffset, tempRegister); + } + + public static void WriteSyncPoint( + CodeWriter writer, + ref Assembler asm, + RegisterAllocator regAlloc, + TailMerger tailMerger, + int spillBaseOffset, + Action storeToContext = null, + Action loadFromContext = null) + { + int tempRegister = regAlloc.AllocateTempGprRegister(); + + Operand rt = Register(tempRegister, OperandType.I32); + + asm.LdrRiUn(rt, Register(regAlloc.FixedContextRegister), NativeContextOffsets.CounterOffset); + + int branchIndex = writer.InstructionPointer; + asm.Cbnz(rt, 0); + + storeToContext?.Invoke(); + WriteSpill(ref asm, regAlloc, 1u << tempRegister, skipContext: true, spillBaseOffset, tempRegister); + + Operand rn = Register(tempRegister == 0 ? 1 : 0); + + asm.Mov(rn, (ulong)CheckSynchronizationPtr()); + asm.Blr(rn); + + tailMerger.AddConditionalZeroReturn(writer, asm, Register(0, OperandType.I32)); + + WriteFill(ref asm, regAlloc, 1u << tempRegister, skipContext: true, spillBaseOffset, tempRegister); + loadFromContext?.Invoke(); + + asm.LdrRiUn(rt, Register(regAlloc.FixedContextRegister), NativeContextOffsets.CounterOffset); + + uint branchInst = writer.ReadInstructionAt(branchIndex); + writer.WriteInstructionAt(branchIndex, branchInst | (((uint)(writer.InstructionPointer - branchIndex) & 0x7ffff) << 5)); + + asm.Sub(rt, rt, new Operand(OperandKind.Constant, OperandType.I32, 1)); + asm.StrRiUn(rt, Register(regAlloc.FixedContextRegister), NativeContextOffsets.CounterOffset); + + regAlloc.FreeTempGprRegister(tempRegister); + } + + private static void WriteCall( + ref Assembler asm, + RegisterAllocator regAlloc, + IntPtr funcPtr, + bool skipContext, + int spillBaseOffset, + int? resultRegister, + params ulong[] callArgs) + { + uint resultMask = 0u; + + if (resultRegister.HasValue) + { + resultMask = 1u << resultRegister.Value; + } + + int tempRegister = callArgs.Length; + + if (resultRegister.HasValue && tempRegister == resultRegister.Value) + { + tempRegister++; + } + + WriteSpill(ref asm, regAlloc, resultMask, skipContext, spillBaseOffset, tempRegister); + + // We only support up to 7 arguments right now. + // ABI defines the first 8 integer arguments to be passed on registers X0-X7. + // We need at least one register to put the function address on, so that reduces the number of + // registers we can use for that by one. + + Debug.Assert(callArgs.Length < 8); + + for (int index = 0; index < callArgs.Length; index++) + { + asm.Mov(Register(index), callArgs[index]); + } + + Operand rn = Register(tempRegister); + + asm.Mov(rn, (ulong)funcPtr); + asm.Blr(rn); + + if (resultRegister.HasValue && resultRegister.Value != 0) + { + asm.Mov(Register(resultRegister.Value), Register(0)); + } + + WriteFill(ref asm, regAlloc, resultMask, skipContext, spillBaseOffset, tempRegister); + } + + private static void WriteSpill(ref Assembler asm, RegisterAllocator regAlloc, uint exceptMask, bool skipContext, int spillOffset, int tempRegister) + { + if (skipContext) + { + InstEmitFlow.WriteSpillSkipContext(ref asm, regAlloc, spillOffset); + } + else + { + WriteSpillOrFill(ref asm, regAlloc, exceptMask, spillOffset, tempRegister, spill: true); + } + } + + private static void WriteFill(ref Assembler asm, RegisterAllocator regAlloc, uint exceptMask, bool skipContext, int spillOffset, int tempRegister) + { + if (skipContext) + { + InstEmitFlow.WriteFillSkipContext(ref asm, regAlloc, spillOffset); + } + else + { + WriteSpillOrFill(ref asm, regAlloc, exceptMask, spillOffset, tempRegister, spill: false); + } + } + + private static void WriteSpillOrFill( + ref Assembler asm, + RegisterAllocator regAlloc, + uint exceptMask, + int spillOffset, + int tempRegister, + bool spill) + { + uint gprMask = regAlloc.UsedGprsMask & ~(AbiConstants.GprCalleeSavedRegsMask | exceptMask); + + if (!spill) + { + // We must reload the status register before reloading the GPRs, + // since we might otherwise trash one of them by using it as temp register. + + Operand rt = Register(tempRegister, OperandType.I32); + + asm.LdrRiUn(rt, Register(SpIndex), spillOffset + BitOperations.PopCount(gprMask) * 8); + asm.MsrNzcv(rt); + } + + while (gprMask != 0) + { + int reg = BitOperations.TrailingZeroCount(gprMask); + + if (reg < 31 && (gprMask & (2u << reg)) != 0 && spillOffset < RegisterSaveRestore.Encodable9BitsOffsetLimit) + { + if (spill) + { + asm.StpRiUn(Register(reg), Register(reg + 1), Register(SpIndex), spillOffset); + } + else + { + asm.LdpRiUn(Register(reg), Register(reg + 1), Register(SpIndex), spillOffset); + } + + gprMask &= ~(3u << reg); + spillOffset += 16; + } + else + { + if (spill) + { + asm.StrRiUn(Register(reg), Register(SpIndex), spillOffset); + } + else + { + asm.LdrRiUn(Register(reg), Register(SpIndex), spillOffset); + } + + gprMask &= ~(1u << reg); + spillOffset += 8; + } + } + + if (spill) + { + Operand rt = Register(tempRegister, OperandType.I32); + + asm.MrsNzcv(rt); + asm.StrRiUn(rt, Register(SpIndex), spillOffset); + } + + spillOffset += 8; + + if ((spillOffset & 8) != 0) + { + spillOffset += 8; + } + + uint fpSimdMask = regAlloc.UsedFpSimdMask; + + while (fpSimdMask != 0) + { + int reg = BitOperations.TrailingZeroCount(fpSimdMask); + + if (reg < 31 && (fpSimdMask & (2u << reg)) != 0 && spillOffset < RegisterSaveRestore.Encodable9BitsOffsetLimit) + { + if (spill) + { + asm.StpRiUn(Register(reg, OperandType.V128), Register(reg + 1, OperandType.V128), Register(SpIndex), spillOffset); + } + else + { + asm.LdpRiUn(Register(reg, OperandType.V128), Register(reg + 1, OperandType.V128), Register(SpIndex), spillOffset); + } + + fpSimdMask &= ~(3u << reg); + spillOffset += 32; + } + else + { + if (spill) + { + asm.StrRiUn(Register(reg, OperandType.V128), Register(SpIndex), spillOffset); + } + else + { + asm.LdrRiUn(Register(reg, OperandType.V128), Register(SpIndex), spillOffset); + } + + fpSimdMask &= ~(1u << reg); + spillOffset += 16; + } + } + } + + public static Operand Register(int register, OperandType type = OperandType.I64) + { + return new Operand(register, RegisterType.Integer, type); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpArithmetic.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpArithmetic.cs new file mode 100644 index 00000000..efb2fc6b --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpArithmetic.cs @@ -0,0 +1,95 @@ +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitVfpArithmetic + { + public static void VabsF(CodeGenContext context, uint rd, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FabsFloat); + } + + public static void VaddF(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarBinaryF(context, rd, rn, rm, size, context.Arm64Assembler.FaddFloat); + } + + public static void VdivF(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarBinaryF(context, rd, rn, rm, size, context.Arm64Assembler.FdivFloat); + } + + public static void VfmaF(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarTernaryRdF(context, rd, rn, rm, size, context.Arm64Assembler.FmaddFloat); + } + + public static void VfmsF(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarTernaryRdF(context, rd, rn, rm, size, context.Arm64Assembler.FmsubFloat); + } + + public static void VfnmaF(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarTernaryRdF(context, rd, rn, rm, size, context.Arm64Assembler.FnmaddFloat); + } + + public static void VfnmsF(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarTernaryRdF(context, rd, rn, rm, size, context.Arm64Assembler.FnmsubFloat); + } + + public static void Vmaxnm(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarBinaryF(context, rd, rn, rm, size, context.Arm64Assembler.FmaxnmFloat); + } + + public static void Vminnm(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarBinaryF(context, rd, rn, rm, size, context.Arm64Assembler.FminnmFloat); + } + + public static void VmlaF(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarTernaryMulNegRdF(context, rd, rn, rm, size, negD: false, negProduct: false); + } + + public static void VmlsF(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarTernaryMulNegRdF(context, rd, rn, rm, size, negD: false, negProduct: true); + } + + public static void VmulF(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarBinaryF(context, rd, rn, rm, size, context.Arm64Assembler.FmulFloat); + } + + public static void VnegF(CodeGenContext context, uint rd, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FnegFloat); + } + + public static void VnmlaF(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarTernaryMulNegRdF(context, rd, rn, rm, size, negD: true, negProduct: true); + } + + public static void VnmlsF(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarTernaryMulNegRdF(context, rd, rn, rm, size, negD: true, negProduct: false); + } + + public static void VnmulF(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarBinaryF(context, rd, rn, rm, size, context.Arm64Assembler.FnmulFloat); + } + + public static void VsqrtF(CodeGenContext context, uint rd, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FsqrtFloat); + } + + public static void VsubF(CodeGenContext context, uint rd, uint rn, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarBinaryF(context, rd, rn, rm, size, context.Arm64Assembler.FsubFloat); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpCompare.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpCompare.cs new file mode 100644 index 00000000..f5f30609 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpCompare.cs @@ -0,0 +1,133 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; +using System; +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitVfpCompare + { + public static void VcmpI(CodeGenContext context, uint cond, uint rd, uint size) + { + EmitVcmpVcmpe(context, cond, rd, 0, size, zero: true, e: false); + } + + public static void VcmpR(CodeGenContext context, uint cond, uint rd, uint rm, uint size) + { + EmitVcmpVcmpe(context, cond, rd, rm, size, zero: false, e: false); + } + + public static void VcmpeI(CodeGenContext context, uint cond, uint rd, uint size) + { + EmitVcmpVcmpe(context, cond, rd, 0, size, zero: true, e: true); + } + + public static void VcmpeR(CodeGenContext context, uint cond, uint rd, uint rm, uint size) + { + EmitVcmpVcmpe(context, cond, rd, rm, size, zero: false, e: true); + } + + private static void EmitVcmpVcmpe(CodeGenContext context, uint cond, uint rd, uint rm, uint size, bool zero, bool e) + { + Debug.Assert(size == 1 || size == 2 || size == 3); + + bool singleRegs = size != 3; + uint ftype = size ^ 2u; + uint opc = zero ? 1u : 0u; + + using ScopedRegister rdReg = InstEmitNeonCommon.MoveScalarToSide(context, rd, singleRegs); + ScopedRegister rmReg; + Operand rmOrZero; + + if (zero) + { + rmReg = default; + rmOrZero = new Operand(0, RegisterType.Vector, OperandType.V128); + } + else + { + rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, singleRegs); + rmOrZero = rmReg.Operand; + } + + using ScopedRegister oldFlags = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + bool canPeepholeOptimize = CanFuseVcmpVmrs(context, cond); + if (!canPeepholeOptimize) + { + InstEmitCommon.GetCurrentFlags(context, oldFlags.Operand); + } + + if (e) + { + context.Arm64Assembler.FcmpeFloat(rdReg.Operand, rmOrZero, opc, ftype); + } + else + { + context.Arm64Assembler.FcmpFloat(rdReg.Operand, rmOrZero, opc, ftype); + } + + // Save result flags from the FCMP operation on FPSCR register, then restore the old flags if needed. + + WriteUpdateFpsrNzcv(context); + + if (!canPeepholeOptimize) + { + InstEmitCommon.RestoreNzcvFlags(context, oldFlags.Operand); + } + + if (!zero) + { + rmReg.Dispose(); + } + } + + private static void WriteUpdateFpsrNzcv(CodeGenContext context) + { + using ScopedRegister fpsrRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped(); + + Operand ctx = InstEmitSystem.Register(context.RegisterAllocator.FixedContextRegister); + + context.Arm64Assembler.LdrRiUn(fpsrRegister.Operand, ctx, NativeContextOffsets.FpFlagsBaseOffset); + + InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand); + + context.Arm64Assembler.Bfi(fpsrRegister.Operand, flagsRegister.Operand, 28, 4); + context.Arm64Assembler.StrRiUn(fpsrRegister.Operand, ctx, NativeContextOffsets.FpFlagsBaseOffset); + } + + private static bool CanFuseVcmpVmrs(CodeGenContext context, uint vcmpCond) + { + // Conditions might be different for the VCMP and VMRS instructions if they are inside a IT block, + // we don't bother to check right now, so just always skip if inside an IT block. + if (context.InITBlock) + { + return false; + } + + InstInfo nextInfo = context.PeekNextInstruction(); + + // We're looking for a VMRS instructions. + if (nextInfo.Name != InstName.Vmrs) + { + return false; + } + + // Conditions must match. + if (vcmpCond != (nextInfo.Encoding >> 28)) + { + return false; + } + + // Reg must be 1, Rt must be PC indicating VMRS to PSTATE.NZCV. + if (((nextInfo.Encoding >> 16) & 0xf) != 1 || ((nextInfo.Encoding >> 12) & 0xf) != RegisterUtils.PcRegister) + { + return false; + } + + context.SetSkipNextInstruction(); + + return true; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpConvert.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpConvert.cs new file mode 100644 index 00000000..8e488695 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpConvert.cs @@ -0,0 +1,305 @@ +using System; +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitVfpConvert + { + public static void Vcvta(CodeGenContext context, uint rd, uint rm, bool op, uint size) + { + if (size == 3) + { + // F64 -> S32/U32 conversion on SIMD is not supported, so we convert it to a GPR, then insert it back into the SIMD register. + + if (op) + { + InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtasFloat); + } + else + { + InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtauFloat); + } + } + else if (op) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtasS, context.Arm64Assembler.FcvtasSH); + } + else + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtauS, context.Arm64Assembler.FcvtauSH); + } + } + + public static void Vcvtb(CodeGenContext context, uint rd, uint rm, uint sz, uint op) + { + EmitVcvtbVcvtt(context, rd, rm, sz, op, top: false); + } + + public static void Vcvtm(CodeGenContext context, uint rd, uint rm, bool op, uint size) + { + if (size == 3) + { + // F64 -> S32/U32 conversion on SIMD is not supported, so we convert it to a GPR, then insert it back into the SIMD register. + + if (op) + { + InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtmsFloat); + } + else + { + InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtmuFloat); + } + } + else if (op) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtmsS, context.Arm64Assembler.FcvtmsSH); + } + else + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtmuS, context.Arm64Assembler.FcvtmuSH); + } + } + + public static void Vcvtn(CodeGenContext context, uint rd, uint rm, bool op, uint size) + { + if (size == 3) + { + // F64 -> S32/U32 conversion on SIMD is not supported, so we convert it to a GPR, then insert it back into the SIMD register. + + if (op) + { + InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtnsFloat); + } + else + { + InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtnuFloat); + } + } + else if (op) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtnsS, context.Arm64Assembler.FcvtnsSH); + } + else + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtnuS, context.Arm64Assembler.FcvtnuSH); + } + } + + public static void Vcvtp(CodeGenContext context, uint rd, uint rm, bool op, uint size) + { + if (size == 3) + { + // F64 -> S32/U32 conversion on SIMD is not supported, so we convert it to a GPR, then insert it back into the SIMD register. + + if (op) + { + InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtpsFloat); + } + else + { + InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtpuFloat); + } + } + else if (op) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtpsS, context.Arm64Assembler.FcvtpsSH); + } + else + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtpuS, context.Arm64Assembler.FcvtpuSH); + } + } + + public static void VcvtDs(CodeGenContext context, uint rd, uint rm, uint size) + { + bool doubleToSingle = size == 3; + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + if (doubleToSingle) + { + // Double to single. + + using ScopedRegister rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, false); + + context.Arm64Assembler.FcvtFloat(tempRegister.Operand, rmReg.Operand, 0, 1); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, true); + } + else + { + // Single to double. + + using ScopedRegister rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, true); + + context.Arm64Assembler.FcvtFloat(tempRegister.Operand, rmReg.Operand, 1, 0); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, false); + } + } + + public static void VcvtIv(CodeGenContext context, uint rd, uint rm, bool unsigned, uint size) + { + if (size == 3) + { + // F64 -> S32/U32 conversion on SIMD is not supported, so we convert it to a GPR, then insert it back into the SIMD register. + + if (unsigned) + { + InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtzuFloatInt); + } + else + { + InstEmitNeonCommon.EmitScalarUnaryToGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.FcvtzsFloatInt); + } + } + else + { + if (unsigned) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtzuIntS, context.Arm64Assembler.FcvtzuIntSH); + } + else + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FcvtzsIntS, context.Arm64Assembler.FcvtzsIntSH); + } + } + } + + public static void VcvtVi(CodeGenContext context, uint rd, uint rm, bool unsigned, uint size) + { + if (size == 3) + { + // S32/U32 -> F64 conversion on SIMD is not supported, so we convert it to a GPR, then insert it back into the SIMD register. + + if (unsigned) + { + InstEmitNeonCommon.EmitScalarUnaryFromGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.UcvtfFloatInt); + } + else + { + InstEmitNeonCommon.EmitScalarUnaryFromGprTempF(context, rd, rm, size, 0, context.Arm64Assembler.ScvtfFloatInt); + } + } + else + { + if (unsigned) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.UcvtfIntS, context.Arm64Assembler.UcvtfIntSH); + } + else + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.ScvtfIntS, context.Arm64Assembler.ScvtfIntSH); + } + } + } + + public static void VcvtXv(CodeGenContext context, uint rd, uint imm5, bool sx, uint sf, uint op, bool u) + { + Debug.Assert(op >> 1 == 0); + + bool unsigned = u; + bool toFixed = op == 1; + uint size = sf; + uint fbits = Math.Clamp((sx ? 32u : 16u) - imm5, 1, 8u << (int)size); + + if (toFixed) + { + if (unsigned) + { + InstEmitNeonCommon.EmitScalarUnaryFixedF(context, rd, rd, fbits, size, is16Bit: false, context.Arm64Assembler.FcvtzuFixS); + } + else + { + InstEmitNeonCommon.EmitScalarUnaryFixedF(context, rd, rd, fbits, size, is16Bit: false, context.Arm64Assembler.FcvtzsFixS); + } + } + else + { + if (unsigned) + { + InstEmitNeonCommon.EmitScalarUnaryFixedF(context, rd, rd, fbits, size, is16Bit: !sx, context.Arm64Assembler.UcvtfFixS); + } + else + { + InstEmitNeonCommon.EmitScalarUnaryFixedF(context, rd, rd, fbits, size, is16Bit: !sx, context.Arm64Assembler.ScvtfFixS); + } + } + } + + public static void VcvtrIv(CodeGenContext context, uint rd, uint rm, uint op, uint size) + { + bool unsigned = (op & 1) == 0; + + Debug.Assert(size == 1 || size == 2 || size == 3); + + bool singleRegs = size != 3; + + using ScopedRegister rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, singleRegs); + + using ScopedRegister tempRegister = InstEmitNeonCommon.PickSimdRegister(context.RegisterAllocator, rmReg); + + // Round using the FPCR rounding mode first, since the FCVTZ instructions will use the round to zero mode. + context.Arm64Assembler.FrintiFloat(tempRegister.Operand, rmReg.Operand, size ^ 2u); + + if (unsigned) + { + if (size == 1) + { + context.Arm64Assembler.FcvtzuIntSH(tempRegister.Operand, tempRegister.Operand); + } + else + { + context.Arm64Assembler.FcvtzuIntS(tempRegister.Operand, tempRegister.Operand, size & 1); + } + } + else + { + if (size == 1) + { + context.Arm64Assembler.FcvtzsIntSH(tempRegister.Operand, tempRegister.Operand); + } + else + { + context.Arm64Assembler.FcvtzsIntS(tempRegister.Operand, tempRegister.Operand, size & 1); + } + } + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, singleRegs); + } + + public static void Vcvtt(CodeGenContext context, uint rd, uint rm, uint sz, uint op) + { + EmitVcvtbVcvtt(context, rd, rm, sz, op, top: true); + } + + public static void EmitVcvtbVcvtt(CodeGenContext context, uint rd, uint rm, uint sz, uint op, bool top) + { + bool usesDouble = sz == 1; + bool convertFromHalf = op == 0; + + using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempSimdRegisterScoped(); + + if (convertFromHalf) + { + // Half to single/double. + + using ScopedRegister rmReg = InstEmitNeonCommon.Move16BitScalarToSide(context, rm, top); + + context.Arm64Assembler.FcvtFloat(tempRegister.Operand, rmReg.Operand, usesDouble ? 1u : 0u, 3u); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, !usesDouble); + } + else + { + // Single/double to half. + + using ScopedRegister rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, !usesDouble); + + context.Arm64Assembler.FcvtFloat(tempRegister.Operand, rmReg.Operand, 3u, usesDouble ? 1u : 0u); + + InstEmitNeonCommon.Insert16BitResult(context, tempRegister.Operand, rd, top); + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpMove.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpMove.cs new file mode 100644 index 00000000..5c1eefac --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpMove.cs @@ -0,0 +1,22 @@ +using Ryujinx.Cpu.LightningJit.CodeGen; + +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitVfpMove + { + public static void Vsel(CodeGenContext context, uint rd, uint rn, uint rm, uint cc, uint size) + { + bool singleRegs = size != 3; + uint cond = (cc << 2) | ((cc & 2) ^ ((cc << 1) & 2)); + + using ScopedRegister rnReg = InstEmitNeonCommon.MoveScalarToSide(context, rn, singleRegs); + using ScopedRegister rmReg = InstEmitNeonCommon.MoveScalarToSide(context, rm, singleRegs); + + using ScopedRegister tempRegister = InstEmitNeonCommon.PickSimdRegister(context.RegisterAllocator, rnReg, rmReg); + + context.Arm64Assembler.FcselFloat(tempRegister.Operand, rnReg.Operand, cond, rmReg.Operand, size ^ 2u); + + InstEmitNeonCommon.InsertResult(context, tempRegister.Operand, rd, singleRegs); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpRound.cs b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpRound.cs new file mode 100644 index 00000000..2826fa64 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitVfpRound.cs @@ -0,0 +1,40 @@ +namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64 +{ + static class InstEmitVfpRound + { + public static void Vrinta(CodeGenContext context, uint rd, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FrintaFloat); + } + + public static void Vrintm(CodeGenContext context, uint rd, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FrintmFloat); + } + + public static void Vrintn(CodeGenContext context, uint rd, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FrintnFloat); + } + + public static void Vrintp(CodeGenContext context, uint rd, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FrintpFloat); + } + + public static void Vrintr(CodeGenContext context, uint rd, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FrintiFloat); + } + + public static void Vrintx(CodeGenContext context, uint rd, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FrintxFloat); + } + + public static void Vrintz(CodeGenContext context, uint rd, uint rm, uint size) + { + InstEmitNeonCommon.EmitScalarUnaryF(context, rd, rm, size, context.Arm64Assembler.FrintzFloat); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/A64Compiler.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/A64Compiler.cs new file mode 100644 index 00000000..b46ae3b0 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/A64Compiler.cs @@ -0,0 +1,29 @@ +using ARMeilleure.Common; +using ARMeilleure.Memory; +using Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.LightningJit.Arm64 +{ + static class A64Compiler + { + public static CompiledFunction Compile( + CpuPreset cpuPreset, + IMemoryManager memoryManager, + ulong address, + AddressTable funcTable, + IntPtr dispatchStubPtr, + Architecture targetArch) + { + if (targetArch == Architecture.Arm64) + { + return Compiler.Compile(cpuPreset, memoryManager, address, funcTable, dispatchStubPtr); + } + else + { + throw new PlatformNotSupportedException(); + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Block.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Block.cs new file mode 100644 index 00000000..188630a1 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Block.cs @@ -0,0 +1,138 @@ +using Ryujinx.Cpu.LightningJit.Graph; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm64 +{ + class Block : IBlock + { + public int Index { get; private set; } + + private readonly List _predecessors; + private readonly List _successors; + + public int PredecessorsCount => _predecessors.Count; + public int SuccessorsCount => _successors.Count; + + public readonly ulong Address; + public readonly ulong EndAddress; + public readonly List Instructions; + public readonly bool EndsWithBranch; + public readonly bool IsTruncated; + public readonly bool IsLoopEnd; + + public Block(ulong address, ulong endAddress, List instructions, bool endsWithBranch, bool isTruncated, bool isLoopEnd) + { + Debug.Assert((int)((endAddress - address) / 4) == instructions.Count); + + _predecessors = new(); + _successors = new(); + Address = address; + EndAddress = endAddress; + Instructions = instructions; + EndsWithBranch = endsWithBranch; + IsTruncated = isTruncated; + IsLoopEnd = isLoopEnd; + } + + public (Block, Block) SplitAtAddress(ulong address) + { + int splitIndex = (int)((address - Address) / 4); + int splitCount = Instructions.Count - splitIndex; + + // Technically those are valid, but we don't want to create empty blocks. + Debug.Assert(splitIndex != 0); + Debug.Assert(splitCount != 0); + + Block leftBlock = new( + Address, + address, + Instructions.GetRange(0, splitIndex), + false, + false, + false); + + Block rightBlock = new( + address, + EndAddress, + Instructions.GetRange(splitIndex, splitCount), + EndsWithBranch, + IsTruncated, + IsLoopEnd); + + return (leftBlock, rightBlock); + } + + public void Number(int index) + { + Index = index; + } + + public void AddSuccessor(Block block) + { + if (!_successors.Contains(block)) + { + _successors.Add(block); + } + } + + public void AddPredecessor(Block block) + { + if (!_predecessors.Contains(block)) + { + _predecessors.Add(block); + } + } + + public IBlock GetSuccessor(int index) + { + return _successors[index]; + } + + public IBlock GetPredecessor(int index) + { + return _predecessors[index]; + } + + public RegisterUse ComputeUseMasks() + { + if (Instructions.Count == 0) + { + return new(0u, 0u, 0u, 0u, 0u, 0u); + } + + RegisterUse use = Instructions[0].RegisterUse; + + for (int index = 1; index < Instructions.Count; index++) + { + RegisterUse currentUse = Instructions[index].RegisterUse; + + use = new(use.Read | (currentUse.Read & ~use.Write), use.Write | currentUse.Write); + } + + return use; + } + + public bool EndsWithContextLoad() + { + return !IsTruncated && EndsWithContextStoreAndLoad(); + } + + public bool EndsWithContextStore() + { + return EndsWithContextStoreAndLoad(); + } + + private bool EndsWithContextStoreAndLoad() + { + if (Instructions.Count == 0) + { + return false; + } + + InstName lastInstructionName = Instructions[^1].Name; + + return lastInstructionName.IsCall() || lastInstructionName.IsException(); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/ImmUtils.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/ImmUtils.cs new file mode 100644 index 00000000..8a12756b --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/ImmUtils.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Cpu.LightningJit.Arm64 +{ + static class ImmUtils + { + public static int ExtractSImm14Times4(uint encoding) + { + return ((int)(encoding >> 5) << 18) >> 16; + } + + public static int ExtractSImm19Times4(uint encoding) + { + return ((int)(encoding >> 5) << 13) >> 11; + } + + public static int ExtractSImm26Times4(uint encoding) + { + return (int)(encoding << 6) >> 4; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/InstFlags.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/InstFlags.cs new file mode 100644 index 00000000..5a6c3eb0 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/InstFlags.cs @@ -0,0 +1,108 @@ +using System; + +namespace Ryujinx.Cpu.LightningJit.Arm64 +{ + [Flags] + enum InstFlags + { + None = 0, + Rd = 1 << 0, + RdSP = Rd | (1 << 1), + ReadRd = 1 << 2, + Rt = 1 << 3, + RtSeq = Rt | (1 << 4), + ReadRt = 1 << 5, + Rt2 = 1 << 6, + Rn = 1 << 7, + RnSeq = Rn | (1 << 8), + RnSP = Rn | (1 << 9), + Rm = 1 << 10, + Rs = 1 << 11, + Ra = 1 << 12, + Nzcv = 1 << 13, + C = 1 << 14, + S = 1 << 15, + Qc = 1 << 16, + FpSimd = 1 << 17, + FpSimdFromGpr = FpSimd | (1 << 18), + FpSimdToGpr = FpSimd | (1 << 19), + FpSimdFromToGpr = FpSimdFromGpr | FpSimdToGpr, + Memory = 1 << 20, + MemWBack = 1 << 21, + + RdFpSimd = Rd | FpSimd, + RdReadRd = Rd | ReadRd, + RdReadRdRn = Rd | ReadRd | Rn, + RdReadRdRnFpSimd = Rd | ReadRd | Rn | FpSimd, + RdReadRdRnFpSimdFromGpr = Rd | ReadRd | Rn | FpSimdFromGpr, + RdReadRdRnQcFpSimd = Rd | ReadRd | Rn | Qc | FpSimd, + RdReadRdRnRmFpSimd = Rd | ReadRd | Rn | Rm | FpSimd, + RdReadRdRnRmQcFpSimd = Rd | ReadRd | Rn | Rm | Qc | FpSimd, + RdRn = Rd | Rn, + RdRnFpSimd = Rd | Rn | FpSimd, + RdRnFpSimdFromGpr = Rd | Rn | FpSimdFromGpr, + RdRnFpSimdToGpr = Rd | Rn | FpSimdToGpr, + RdRnQcFpSimd = Rd | Rn | Qc | FpSimd, + RdRnRm = Rd | Rn | Rm, + RdRnRmC = Rd | Rn | Rm | C, + RdRnRmCS = Rd | Rn | Rm | C | S, + RdRnRmFpSimd = Rd | Rn | Rm | FpSimd, + RdRnRmNzcv = Rd | Rn | Rm | Nzcv, + RdRnRmNzcvFpSimd = Rd | Rn | Rm | Nzcv | FpSimd, + RdRnRmQcFpSimd = Rd | Rn | Rm | Qc | FpSimd, + RdRnRmRa = Rd | Rn | Rm | Ra, + RdRnRmRaFpSimd = Rd | Rn | Rm | Ra | FpSimd, + RdRnRmS = Rd | Rn | Rm | S, + RdRnRsS = Rd | Rn | Rs | S, + RdRnS = Rd | Rn | S, + RdRnSeqRmFpSimd = Rd | RnSeq | Rm | FpSimd, + RdRnSFpSimd = Rd | Rn | S | FpSimd, + RdRnSFpSimdFromToGpr = Rd | Rn | S | FpSimdFromToGpr, + RdRnSP = Rd | RnSP, + RdRnSPRmS = Rd | RnSP | Rm | S, + RdRnSPS = Rd | RnSP | S, + RdSPRn = RdSP | Rn, + RdSPRnSP = RdSP | RnSP, + RdSPRnSPRm = RdSP | RnSP | Rm, + RnC = Rn | C, + RnNzcvS = Rn | Nzcv | S, + RnRm = Rn | Rm, + RnRmNzcvS = Rn | Rm | Nzcv | S, + RnRmNzcvSFpSimd = Rn | Rm | Nzcv | S | FpSimd, + RnRmSFpSimd = Rn | Rm | S | FpSimd, + RnSPRm = RnSP | Rm, + RtFpSimd = Rt | FpSimd, + RtReadRt = Rt | ReadRt, + RtReadRtRnSP = Rt | ReadRt | RnSP, + RtReadRtRnSPFpSimd = Rt | ReadRt | RnSP | FpSimd, + RtReadRtRnSPFpSimdMemWBack = Rt | ReadRt | RnSP | FpSimd | MemWBack, + RtReadRtRnSPMemWBack = Rt | ReadRt | RnSP | MemWBack, + RtReadRtRnSPRm = Rt | ReadRt | RnSP | Rm, + RtReadRtRnSPRmFpSimd = Rt | ReadRt | RnSP | Rm | FpSimd, + RtReadRtRnSPRmFpSimdMemWBack = Rt | ReadRt | RnSP | Rm | FpSimd | MemWBack, + RtReadRtRnSPRs = Rt | ReadRt | RnSP | Rs, + RtReadRtRnSPRsS = Rt | ReadRt | RnSP | Rs | S, + RtReadRtRt2RnSP = Rt | ReadRt | Rt2 | RnSP, + RtReadRtRt2RnSPFpSimd = Rt | ReadRt | Rt2 | RnSP | FpSimd, + RtReadRtRt2RnSPFpSimdMemWBack = Rt | ReadRt | Rt2 | RnSP | FpSimd | MemWBack, + RtReadRtRt2RnSPMemWBack = Rt | ReadRt | Rt2 | RnSP | MemWBack, + RtReadRtRt2RnSPRs = Rt | ReadRt | Rt2 | RnSP | Rs, + RtReadRtRt2RnSPS = Rt | ReadRt | Rt2 | RnSP | S, + RtRnSP = Rt | RnSP, + RtRnSPFpSimd = Rt | RnSP | FpSimd, + RtRnSPFpSimdMemWBack = Rt | RnSP | FpSimd | MemWBack, + RtRnSPMemWBack = Rt | RnSP | MemWBack, + RtRnSPRm = Rt | RnSP | Rm, + RtRnSPRmFpSimd = Rt | RnSP | Rm | FpSimd, + RtRnSPRmFpSimdMemWBack = Rt | RnSP | Rm | FpSimd | MemWBack, + RtRnSPRs = Rt | RnSP | Rs, + RtRt2RnSP = Rt | Rt2 | RnSP, + RtRt2RnSPFpSimd = Rt | Rt2 | RnSP | FpSimd, + RtRt2RnSPFpSimdMemWBack = Rt | Rt2 | RnSP | FpSimd | MemWBack, + RtRt2RnSPMemWBack = Rt | Rt2 | RnSP | MemWBack, + RtSeqReadRtRnSPFpSimd = RtSeq | ReadRt | RnSP | FpSimd, + RtSeqReadRtRnSPRmFpSimdMemWBack = RtSeq | ReadRt | RnSP | Rm | FpSimd | MemWBack, + RtSeqRnSPFpSimd = RtSeq | RnSP | FpSimd, + RtSeqRnSPRmFpSimdMemWBack = RtSeq | RnSP | Rm | FpSimd | MemWBack, + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/InstInfo.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/InstInfo.cs new file mode 100644 index 00000000..031a3657 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/InstInfo.cs @@ -0,0 +1,22 @@ +using Ryujinx.Cpu.LightningJit.Graph; + +namespace Ryujinx.Cpu.LightningJit.Arm64 +{ + readonly struct InstInfo + { + public readonly uint Encoding; + public readonly InstName Name; + public readonly InstFlags Flags; + public readonly AddressForm AddressForm; + public readonly RegisterUse RegisterUse; + + public InstInfo(uint encoding, InstName name, InstFlags flags, AddressForm addressForm, in RegisterUse registerUse) + { + Encoding = encoding; + Name = name; + Flags = flags; + AddressForm = addressForm; + RegisterUse = registerUse; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs new file mode 100644 index 00000000..3391a2c1 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/InstName.cs @@ -0,0 +1,1167 @@ +namespace Ryujinx.Cpu.LightningJit.Arm64 +{ + enum InstName + { + Abs, + AbsAdvsimdS, + AbsAdvsimdV, + Adc, + Adcs, + AddAddsubExt, + AddAddsubImm, + AddAddsubShift, + AddAdvsimdS, + AddAdvsimdV, + Addg, + AddhnAdvsimd, + AddpAdvsimdPair, + AddpAdvsimdVec, + AddsAddsubExt, + AddsAddsubImm, + AddsAddsubShift, + AddvAdvsimd, + Adr, + Adrp, + AesdAdvsimd, + AeseAdvsimd, + AesimcAdvsimd, + AesmcAdvsimd, + AndAdvsimd, + AndLogImm, + AndLogShift, + AndsLogImm, + AndsLogShift, + Asrv, + Autda, + Autdb, + AutiaGeneral, + AutiaSystem, + AutibGeneral, + AutibSystem, + Axflag, + BcaxAdvsimd, + BcCond, + BCond, + BfcvtFloat, + BfcvtnAdvsimd, + BfdotAdvsimdElt, + BfdotAdvsimdVec, + Bfm, + BfmlalAdvsimdElt, + BfmlalAdvsimdVec, + BfmmlaAdvsimd, + BicAdvsimdImm, + BicAdvsimdReg, + BicLogShift, + Bics, + BifAdvsimd, + BitAdvsimd, + Bl, + Blr, + Blra, + Br, + Bra, + Brk, + BslAdvsimd, + Bti, + BUncond, + Cas, + Casb, + Cash, + Casp, + Cbnz, + Cbz, + CcmnImm, + CcmnReg, + CcmpImm, + CcmpReg, + Cfinv, + Chkfeat, + Clrbhb, + Clrex, + ClsAdvsimd, + ClsInt, + ClzAdvsimd, + ClzInt, + CmeqAdvsimdRegS, + CmeqAdvsimdRegV, + CmeqAdvsimdZeroS, + CmeqAdvsimdZeroV, + CmgeAdvsimdRegS, + CmgeAdvsimdRegV, + CmgeAdvsimdZeroS, + CmgeAdvsimdZeroV, + CmgtAdvsimdRegS, + CmgtAdvsimdRegV, + CmgtAdvsimdZeroS, + CmgtAdvsimdZeroV, + CmhiAdvsimdS, + CmhiAdvsimdV, + CmhsAdvsimdS, + CmhsAdvsimdV, + CmleAdvsimdS, + CmleAdvsimdV, + CmltAdvsimdS, + CmltAdvsimdV, + CmtstAdvsimdS, + CmtstAdvsimdV, + Cnt, + CntAdvsimd, + Cpyfp, + Cpyfpn, + Cpyfprn, + Cpyfprt, + Cpyfprtn, + Cpyfprtrn, + Cpyfprtwn, + Cpyfpt, + Cpyfptn, + Cpyfptrn, + Cpyfptwn, + Cpyfpwn, + Cpyfpwt, + Cpyfpwtn, + Cpyfpwtrn, + Cpyfpwtwn, + Cpyp, + Cpypn, + Cpyprn, + Cpyprt, + Cpyprtn, + Cpyprtrn, + Cpyprtwn, + Cpypt, + Cpyptn, + Cpyptrn, + Cpyptwn, + Cpypwn, + Cpypwt, + Cpypwtn, + Cpypwtrn, + Cpypwtwn, + Crc32, + Crc32c, + Csdb, + Csel, + Csinc, + Csinv, + Csneg, + Ctz, + Dcps1, + Dcps2, + Dcps3, + Dgh, + Dmb, + Drps, + DsbDsbMemory, + DsbDsbNxs, + DupAdvsimdEltScalarFromElement, + DupAdvsimdEltVectorFromElement, + DupAdvsimdGen, + Eon, + Eor3Advsimd, + EorAdvsimd, + EorLogImm, + EorLogShift, + Eret, + Ereta, + Esb, + ExtAdvsimd, + Extr, + FabdAdvsimdS, + FabdAdvsimdSH, + FabdAdvsimdV, + FabdAdvsimdVH, + FabsAdvsimdHalf, + FabsAdvsimdSingleAndDouble, + FabsFloat, + FacgeAdvsimdS, + FacgeAdvsimdSH, + FacgeAdvsimdV, + FacgeAdvsimdVH, + FacgtAdvsimdS, + FacgtAdvsimdSH, + FacgtAdvsimdV, + FacgtAdvsimdVH, + FaddAdvsimdHalf, + FaddAdvsimdSingleAndDouble, + FaddFloat, + FaddpAdvsimdPairHalf, + FaddpAdvsimdPairSingleAndDouble, + FaddpAdvsimdVecHalf, + FaddpAdvsimdVecSingleAndDouble, + FcaddAdvsimdVec, + FccmpeFloat, + FccmpFloat, + FcmeqAdvsimdRegS, + FcmeqAdvsimdRegSH, + FcmeqAdvsimdRegV, + FcmeqAdvsimdRegVH, + FcmeqAdvsimdZeroS, + FcmeqAdvsimdZeroSH, + FcmeqAdvsimdZeroV, + FcmeqAdvsimdZeroVH, + FcmgeAdvsimdRegS, + FcmgeAdvsimdRegSH, + FcmgeAdvsimdRegV, + FcmgeAdvsimdRegVH, + FcmgeAdvsimdZeroS, + FcmgeAdvsimdZeroSH, + FcmgeAdvsimdZeroV, + FcmgeAdvsimdZeroVH, + FcmgtAdvsimdRegS, + FcmgtAdvsimdRegSH, + FcmgtAdvsimdRegV, + FcmgtAdvsimdRegVH, + FcmgtAdvsimdZeroS, + FcmgtAdvsimdZeroSH, + FcmgtAdvsimdZeroV, + FcmgtAdvsimdZeroVH, + FcmlaAdvsimdElt, + FcmlaAdvsimdVec, + FcmleAdvsimdS, + FcmleAdvsimdSH, + FcmleAdvsimdV, + FcmleAdvsimdVH, + FcmltAdvsimdS, + FcmltAdvsimdSH, + FcmltAdvsimdV, + FcmltAdvsimdVH, + FcmpeFloat, + FcmpFloat, + FcselFloat, + FcvtasAdvsimdS, + FcvtasAdvsimdSH, + FcvtasAdvsimdV, + FcvtasAdvsimdVH, + FcvtasFloat, + FcvtauAdvsimdS, + FcvtauAdvsimdSH, + FcvtauAdvsimdV, + FcvtauAdvsimdVH, + FcvtauFloat, + FcvtFloat, + FcvtlAdvsimd, + FcvtmsAdvsimdS, + FcvtmsAdvsimdSH, + FcvtmsAdvsimdV, + FcvtmsAdvsimdVH, + FcvtmsFloat, + FcvtmuAdvsimdS, + FcvtmuAdvsimdSH, + FcvtmuAdvsimdV, + FcvtmuAdvsimdVH, + FcvtmuFloat, + FcvtnAdvsimd, + FcvtnsAdvsimdS, + FcvtnsAdvsimdSH, + FcvtnsAdvsimdV, + FcvtnsAdvsimdVH, + FcvtnsFloat, + FcvtnuAdvsimdS, + FcvtnuAdvsimdSH, + FcvtnuAdvsimdV, + FcvtnuAdvsimdVH, + FcvtnuFloat, + FcvtpsAdvsimdS, + FcvtpsAdvsimdSH, + FcvtpsAdvsimdV, + FcvtpsAdvsimdVH, + FcvtpsFloat, + FcvtpuAdvsimdS, + FcvtpuAdvsimdSH, + FcvtpuAdvsimdV, + FcvtpuAdvsimdVH, + FcvtpuFloat, + FcvtxnAdvsimdS, + FcvtxnAdvsimdV, + FcvtzsAdvsimdFixS, + FcvtzsAdvsimdFixV, + FcvtzsAdvsimdIntS, + FcvtzsAdvsimdIntSH, + FcvtzsAdvsimdIntV, + FcvtzsAdvsimdIntVH, + FcvtzsFloatFix, + FcvtzsFloatInt, + FcvtzuAdvsimdFixS, + FcvtzuAdvsimdFixV, + FcvtzuAdvsimdIntS, + FcvtzuAdvsimdIntSH, + FcvtzuAdvsimdIntV, + FcvtzuAdvsimdIntVH, + FcvtzuFloatFix, + FcvtzuFloatInt, + FdivAdvsimdHalf, + FdivAdvsimdSingleAndDouble, + FdivFloat, + Fjcvtzs, + FmaddFloat, + FmaxAdvsimdHalf, + FmaxAdvsimdSingleAndDouble, + FmaxFloat, + FmaxnmAdvsimdHalf, + FmaxnmAdvsimdSingleAndDouble, + FmaxnmFloat, + FmaxnmpAdvsimdPairHalf, + FmaxnmpAdvsimdPairSingleAndDouble, + FmaxnmpAdvsimdVecHalf, + FmaxnmpAdvsimdVecSingleAndDouble, + FmaxnmvAdvsimdHalf, + FmaxnmvAdvsimdSingleAndDouble, + FmaxpAdvsimdPairHalf, + FmaxpAdvsimdPairSingleAndDouble, + FmaxpAdvsimdVecHalf, + FmaxpAdvsimdVecSingleAndDouble, + FmaxvAdvsimdHalf, + FmaxvAdvsimdSingleAndDouble, + FminAdvsimdHalf, + FminAdvsimdSingleAndDouble, + FminFloat, + FminnmAdvsimdHalf, + FminnmAdvsimdSingleAndDouble, + FminnmFloat, + FminnmpAdvsimdPairHalf, + FminnmpAdvsimdPairSingleAndDouble, + FminnmpAdvsimdVecHalf, + FminnmpAdvsimdVecSingleAndDouble, + FminnmvAdvsimdHalf, + FminnmvAdvsimdSingleAndDouble, + FminpAdvsimdPairHalf, + FminpAdvsimdPairSingleAndDouble, + FminpAdvsimdVecHalf, + FminpAdvsimdVecSingleAndDouble, + FminvAdvsimdHalf, + FminvAdvsimdSingleAndDouble, + FmlaAdvsimdElt2regElementHalf, + FmlaAdvsimdElt2regElementSingleAndDouble, + FmlaAdvsimdElt2regScalarHalf, + FmlaAdvsimdElt2regScalarSingleAndDouble, + FmlaAdvsimdVecHalf, + FmlaAdvsimdVecSingleAndDouble, + FmlalAdvsimdEltFmlal, + FmlalAdvsimdEltFmlal2, + FmlalAdvsimdVecFmlal, + FmlalAdvsimdVecFmlal2, + FmlsAdvsimdElt2regElementHalf, + FmlsAdvsimdElt2regElementSingleAndDouble, + FmlsAdvsimdElt2regScalarHalf, + FmlsAdvsimdElt2regScalarSingleAndDouble, + FmlsAdvsimdVecHalf, + FmlsAdvsimdVecSingleAndDouble, + FmlslAdvsimdEltFmlsl, + FmlslAdvsimdEltFmlsl2, + FmlslAdvsimdVecFmlsl, + FmlslAdvsimdVecFmlsl2, + FmovAdvsimdPerHalf, + FmovAdvsimdSingleAndDouble, + FmovFloat, + FmovFloatGen, + FmovFloatImm, + FmsubFloat, + FmulAdvsimdElt2regElementHalf, + FmulAdvsimdElt2regElementSingleAndDouble, + FmulAdvsimdElt2regScalarHalf, + FmulAdvsimdElt2regScalarSingleAndDouble, + FmulAdvsimdVecHalf, + FmulAdvsimdVecSingleAndDouble, + FmulFloat, + FmulxAdvsimdElt2regElementHalf, + FmulxAdvsimdElt2regElementSingleAndDouble, + FmulxAdvsimdElt2regScalarHalf, + FmulxAdvsimdElt2regScalarSingleAndDouble, + FmulxAdvsimdVecS, + FmulxAdvsimdVecSH, + FmulxAdvsimdVecV, + FmulxAdvsimdVecVH, + FnegAdvsimdHalf, + FnegAdvsimdSingleAndDouble, + FnegFloat, + FnmaddFloat, + FnmsubFloat, + FnmulFloat, + FrecpeAdvsimdS, + FrecpeAdvsimdSH, + FrecpeAdvsimdV, + FrecpeAdvsimdVH, + FrecpsAdvsimdS, + FrecpsAdvsimdSH, + FrecpsAdvsimdV, + FrecpsAdvsimdVH, + FrecpxAdvsimdHalf, + FrecpxAdvsimdSingleAndDouble, + Frint32xAdvsimd, + Frint32xFloat, + Frint32zAdvsimd, + Frint32zFloat, + Frint64xAdvsimd, + Frint64xFloat, + Frint64zAdvsimd, + Frint64zFloat, + FrintaAdvsimdHalf, + FrintaAdvsimdSingleAndDouble, + FrintaFloat, + FrintiAdvsimdHalf, + FrintiAdvsimdSingleAndDouble, + FrintiFloat, + FrintmAdvsimdHalf, + FrintmAdvsimdSingleAndDouble, + FrintmFloat, + FrintnAdvsimdHalf, + FrintnAdvsimdSingleAndDouble, + FrintnFloat, + FrintpAdvsimdHalf, + FrintpAdvsimdSingleAndDouble, + FrintpFloat, + FrintxAdvsimdHalf, + FrintxAdvsimdSingleAndDouble, + FrintxFloat, + FrintzAdvsimdHalf, + FrintzAdvsimdSingleAndDouble, + FrintzFloat, + FrsqrteAdvsimdS, + FrsqrteAdvsimdSH, + FrsqrteAdvsimdV, + FrsqrteAdvsimdVH, + FrsqrtsAdvsimdS, + FrsqrtsAdvsimdSH, + FrsqrtsAdvsimdV, + FrsqrtsAdvsimdVH, + FsqrtAdvsimdHalf, + FsqrtAdvsimdSingleAndDouble, + FsqrtFloat, + FsubAdvsimdHalf, + FsubAdvsimdSingleAndDouble, + FsubFloat, + Gcsb, + Gcsstr, + Gcssttr, + Gmi, + Hint, + Hlt, + Hvc, + InsAdvsimdElt, + InsAdvsimdGen, + Irg, + Isb, + Ld1AdvsimdMultAsNoPostIndex, + Ld1AdvsimdMultAsPostIndex, + Ld1AdvsimdSnglAsNoPostIndex, + Ld1AdvsimdSnglAsPostIndex, + Ld1rAdvsimdAsNoPostIndex, + Ld1rAdvsimdAsPostIndex, + Ld2AdvsimdMultAsNoPostIndex, + Ld2AdvsimdMultAsPostIndex, + Ld2AdvsimdSnglAsNoPostIndex, + Ld2AdvsimdSnglAsPostIndex, + Ld2rAdvsimdAsNoPostIndex, + Ld2rAdvsimdAsPostIndex, + Ld3AdvsimdMultAsNoPostIndex, + Ld3AdvsimdMultAsPostIndex, + Ld3AdvsimdSnglAsNoPostIndex, + Ld3AdvsimdSnglAsPostIndex, + Ld3rAdvsimdAsNoPostIndex, + Ld3rAdvsimdAsPostIndex, + Ld4AdvsimdMultAsNoPostIndex, + Ld4AdvsimdMultAsPostIndex, + Ld4AdvsimdSnglAsNoPostIndex, + Ld4AdvsimdSnglAsPostIndex, + Ld4rAdvsimdAsNoPostIndex, + Ld4rAdvsimdAsPostIndex, + Ld64b, + Ldadd, + Ldaddb, + Ldaddh, + Ldap1AdvsimdSngl, + Ldaprb, + LdaprBaseRegister, + Ldaprh, + LdaprPostIndexed, + Ldapurb, + LdapurFpsimd, + LdapurGen, + Ldapurh, + Ldapursb, + Ldapursh, + Ldapursw, + Ldar, + Ldarb, + Ldarh, + Ldaxp, + Ldaxr, + Ldaxrb, + Ldaxrh, + Ldclr, + Ldclrb, + Ldclrh, + Ldclrp, + Ldeor, + Ldeorb, + Ldeorh, + Ldg, + Ldgm, + Ldiapp, + Ldlar, + Ldlarb, + Ldlarh, + LdnpFpsimd, + LdnpGen, + LdpFpsimdPostIndexed, + LdpFpsimdPreIndexed, + LdpFpsimdSignedScaledOffset, + LdpGenPostIndexed, + LdpGenPreIndexed, + LdpGenSignedScaledOffset, + LdpswPostIndexed, + LdpswPreIndexed, + LdpswSignedScaledOffset, + Ldra, + LdrbImmPostIndexed, + LdrbImmPreIndexed, + LdrbImmUnsignedScaledOffset, + LdrbReg, + LdrhImmPostIndexed, + LdrhImmPreIndexed, + LdrhImmUnsignedScaledOffset, + LdrhReg, + LdrImmFpsimdPostIndexed, + LdrImmFpsimdPreIndexed, + LdrImmFpsimdUnsignedScaledOffset, + LdrImmGenPostIndexed, + LdrImmGenPreIndexed, + LdrImmGenUnsignedScaledOffset, + LdrLitFpsimd, + LdrLitGen, + LdrRegFpsimd, + LdrRegGen, + LdrsbImmPostIndexed, + LdrsbImmPreIndexed, + LdrsbImmUnsignedScaledOffset, + LdrsbReg, + LdrshImmPostIndexed, + LdrshImmPreIndexed, + LdrshImmUnsignedScaledOffset, + LdrshReg, + LdrswImmPostIndexed, + LdrswImmPreIndexed, + LdrswImmUnsignedScaledOffset, + LdrswLit, + LdrswReg, + Ldset, + Ldsetb, + Ldseth, + Ldsetp, + Ldsmax, + Ldsmaxb, + Ldsmaxh, + Ldsmin, + Ldsminb, + Ldsminh, + Ldtr, + Ldtrb, + Ldtrh, + Ldtrsb, + Ldtrsh, + Ldtrsw, + Ldumax, + Ldumaxb, + Ldumaxh, + Ldumin, + Lduminb, + Lduminh, + Ldurb, + LdurFpsimd, + LdurGen, + Ldurh, + Ldursb, + Ldursh, + Ldursw, + Ldxp, + Ldxr, + Ldxrb, + Ldxrh, + Lslv, + Lsrv, + Madd, + MlaAdvsimdElt, + MlaAdvsimdVec, + MlsAdvsimdElt, + MlsAdvsimdVec, + MoviAdvsimd, + Movk, + Movn, + Movz, + Mrrs, + Mrs, + MsrImm, + Msrr, + MsrReg, + Msub, + MulAdvsimdElt, + MulAdvsimdVec, + MvniAdvsimd, + NegAdvsimdS, + NegAdvsimdV, + Nop, + NotAdvsimd, + OrnAdvsimd, + OrnLogShift, + OrrAdvsimdImm, + OrrAdvsimdReg, + OrrLogImm, + OrrLogShift, + Pacda, + Pacdb, + Pacga, + PaciaGeneral, + PaciaSystem, + PacibGeneral, + PacibSystem, + PmulAdvsimd, + PmullAdvsimd, + PrfmImm, + PrfmLit, + PrfmReg, + Prfum, + Psb, + RaddhnAdvsimd, + Rax1Advsimd, + RbitAdvsimd, + RbitInt, + Rcwcas, + Rcwcasp, + Rcwclr, + Rcwclrp, + Rcwscas, + Rcwscasp, + Rcwsclr, + Rcwsclrp, + Rcwset, + Rcwsetp, + Rcwsset, + Rcwssetp, + Rcwsswp, + Rcwsswpp, + Rcwswp, + Rcwswpp, + Ret, + Reta, + Rev, + Rev16Advsimd, + Rev16Int, + Rev32Advsimd, + Rev32Int, + Rev64Advsimd, + Rmif, + Rorv, + RprfmReg, + RshrnAdvsimd, + RsubhnAdvsimd, + SabaAdvsimd, + SabalAdvsimd, + SabdAdvsimd, + SabdlAdvsimd, + SadalpAdvsimd, + SaddlAdvsimd, + SaddlpAdvsimd, + SaddlvAdvsimd, + SaddwAdvsimd, + Sb, + Sbc, + Sbcs, + Sbfm, + ScvtfAdvsimdFixS, + ScvtfAdvsimdFixV, + ScvtfAdvsimdIntS, + ScvtfAdvsimdIntSH, + ScvtfAdvsimdIntV, + ScvtfAdvsimdIntVH, + ScvtfFloatFix, + ScvtfFloatInt, + Sdiv, + SdotAdvsimdElt, + SdotAdvsimdVec, + Setf, + Setgp, + Setgpn, + Setgpt, + Setgptn, + Setp, + Setpn, + Setpt, + Setptn, + Sev, + Sevl, + Sha1cAdvsimd, + Sha1hAdvsimd, + Sha1mAdvsimd, + Sha1pAdvsimd, + Sha1su0Advsimd, + Sha1su1Advsimd, + Sha256h2Advsimd, + Sha256hAdvsimd, + Sha256su0Advsimd, + Sha256su1Advsimd, + Sha512h2Advsimd, + Sha512hAdvsimd, + Sha512su0Advsimd, + Sha512su1Advsimd, + ShaddAdvsimd, + ShlAdvsimdS, + ShlAdvsimdV, + ShllAdvsimd, + ShrnAdvsimd, + ShsubAdvsimd, + SliAdvsimdS, + SliAdvsimdV, + Sm3partw1Advsimd, + Sm3partw2Advsimd, + Sm3ss1Advsimd, + Sm3tt1aAdvsimd, + Sm3tt1bAdvsimd, + Sm3tt2aAdvsimd, + Sm3tt2bAdvsimd, + Sm4eAdvsimd, + Sm4ekeyAdvsimd, + Smaddl, + SmaxAdvsimd, + SmaxImm, + SmaxpAdvsimd, + SmaxReg, + SmaxvAdvsimd, + Smc, + SminAdvsimd, + SminImm, + SminpAdvsimd, + SminReg, + SminvAdvsimd, + SmlalAdvsimdElt, + SmlalAdvsimdVec, + SmlslAdvsimdElt, + SmlslAdvsimdVec, + SmmlaAdvsimdVec, + SmovAdvsimd, + Smsubl, + Smulh, + SmullAdvsimdElt, + SmullAdvsimdVec, + SqabsAdvsimdS, + SqabsAdvsimdV, + SqaddAdvsimdS, + SqaddAdvsimdV, + SqdmlalAdvsimdElt2regElement, + SqdmlalAdvsimdElt2regScalar, + SqdmlalAdvsimdVecS, + SqdmlalAdvsimdVecV, + SqdmlslAdvsimdElt2regElement, + SqdmlslAdvsimdElt2regScalar, + SqdmlslAdvsimdVecS, + SqdmlslAdvsimdVecV, + SqdmulhAdvsimdElt2regElement, + SqdmulhAdvsimdElt2regScalar, + SqdmulhAdvsimdVecS, + SqdmulhAdvsimdVecV, + SqdmullAdvsimdElt2regElement, + SqdmullAdvsimdElt2regScalar, + SqdmullAdvsimdVecS, + SqdmullAdvsimdVecV, + SqnegAdvsimdS, + SqnegAdvsimdV, + SqrdmlahAdvsimdElt2regElement, + SqrdmlahAdvsimdElt2regScalar, + SqrdmlahAdvsimdVecS, + SqrdmlahAdvsimdVecV, + SqrdmlshAdvsimdElt2regElement, + SqrdmlshAdvsimdElt2regScalar, + SqrdmlshAdvsimdVecS, + SqrdmlshAdvsimdVecV, + SqrdmulhAdvsimdElt2regElement, + SqrdmulhAdvsimdElt2regScalar, + SqrdmulhAdvsimdVecS, + SqrdmulhAdvsimdVecV, + SqrshlAdvsimdS, + SqrshlAdvsimdV, + SqrshrnAdvsimdS, + SqrshrnAdvsimdV, + SqrshrunAdvsimdS, + SqrshrunAdvsimdV, + SqshlAdvsimdImmS, + SqshlAdvsimdImmV, + SqshlAdvsimdRegS, + SqshlAdvsimdRegV, + SqshluAdvsimdS, + SqshluAdvsimdV, + SqshrnAdvsimdS, + SqshrnAdvsimdV, + SqshrunAdvsimdS, + SqshrunAdvsimdV, + SqsubAdvsimdS, + SqsubAdvsimdV, + SqxtnAdvsimdS, + SqxtnAdvsimdV, + SqxtunAdvsimdS, + SqxtunAdvsimdV, + SrhaddAdvsimd, + SriAdvsimdS, + SriAdvsimdV, + SrshlAdvsimdS, + SrshlAdvsimdV, + SrshrAdvsimdS, + SrshrAdvsimdV, + SrsraAdvsimdS, + SrsraAdvsimdV, + SshlAdvsimdS, + SshlAdvsimdV, + SshllAdvsimd, + SshrAdvsimdS, + SshrAdvsimdV, + SsraAdvsimdS, + SsraAdvsimdV, + SsublAdvsimd, + SsubwAdvsimd, + St1AdvsimdMultAsNoPostIndex, + St1AdvsimdMultAsPostIndex, + St1AdvsimdSnglAsNoPostIndex, + St1AdvsimdSnglAsPostIndex, + St2AdvsimdMultAsNoPostIndex, + St2AdvsimdMultAsPostIndex, + St2AdvsimdSnglAsNoPostIndex, + St2AdvsimdSnglAsPostIndex, + St2gPostIndexed, + St2gPreIndexed, + St2gSignedScaledOffset, + St3AdvsimdMultAsNoPostIndex, + St3AdvsimdMultAsPostIndex, + St3AdvsimdSnglAsNoPostIndex, + St3AdvsimdSnglAsPostIndex, + St4AdvsimdMultAsNoPostIndex, + St4AdvsimdMultAsPostIndex, + St4AdvsimdSnglAsNoPostIndex, + St4AdvsimdSnglAsPostIndex, + St64b, + St64bv, + St64bv0, + Stgm, + StgPostIndexed, + StgpPostIndexed, + StgpPreIndexed, + StgPreIndexed, + StgpSignedScaledOffset, + StgSignedScaledOffset, + Stilp, + Stl1AdvsimdSngl, + Stllr, + Stllrb, + Stllrh, + Stlrb, + StlrBaseRegister, + Stlrh, + StlrPreIndexed, + Stlurb, + StlurFpsimd, + StlurGen, + Stlurh, + Stlxp, + Stlxr, + Stlxrb, + Stlxrh, + StnpFpsimd, + StnpGen, + StpFpsimdPostIndexed, + StpFpsimdPreIndexed, + StpFpsimdSignedScaledOffset, + StpGenPostIndexed, + StpGenPreIndexed, + StpGenSignedScaledOffset, + StrbImmPostIndexed, + StrbImmPreIndexed, + StrbImmUnsignedScaledOffset, + StrbReg, + StrhImmPostIndexed, + StrhImmPreIndexed, + StrhImmUnsignedScaledOffset, + StrhReg, + StrImmFpsimdPostIndexed, + StrImmFpsimdPreIndexed, + StrImmFpsimdUnsignedScaledOffset, + StrImmGenPostIndexed, + StrImmGenPreIndexed, + StrImmGenUnsignedScaledOffset, + StrRegFpsimd, + StrRegGen, + Sttr, + Sttrb, + Sttrh, + Sturb, + SturFpsimd, + SturGen, + Sturh, + Stxp, + Stxr, + Stxrb, + Stxrh, + Stz2gPostIndexed, + Stz2gPreIndexed, + Stz2gSignedScaledOffset, + Stzgm, + StzgPostIndexed, + StzgPreIndexed, + StzgSignedScaledOffset, + SubAddsubExt, + SubAddsubImm, + SubAddsubShift, + SubAdvsimdS, + SubAdvsimdV, + Subg, + SubhnAdvsimd, + Subp, + Subps, + SubsAddsubExt, + SubsAddsubImm, + SubsAddsubShift, + SudotAdvsimdElt, + SuqaddAdvsimdS, + SuqaddAdvsimdV, + Svc, + Swp, + Swpb, + Swph, + Swpp, + Sys, + Sysl, + Sysp, + TblAdvsimd, + Tbnz, + TbxAdvsimd, + Tbz, + Tcancel, + Tcommit, + Trn1Advsimd, + Trn2Advsimd, + Tsb, + Tstart, + Ttest, + UabaAdvsimd, + UabalAdvsimd, + UabdAdvsimd, + UabdlAdvsimd, + UadalpAdvsimd, + UaddlAdvsimd, + UaddlpAdvsimd, + UaddlvAdvsimd, + UaddwAdvsimd, + Ubfm, + UcvtfAdvsimdFixS, + UcvtfAdvsimdFixV, + UcvtfAdvsimdIntS, + UcvtfAdvsimdIntSH, + UcvtfAdvsimdIntV, + UcvtfAdvsimdIntVH, + UcvtfFloatFix, + UcvtfFloatInt, + UdfPermUndef, + Udiv, + UdotAdvsimdElt, + UdotAdvsimdVec, + UhaddAdvsimd, + UhsubAdvsimd, + Umaddl, + UmaxAdvsimd, + UmaxImm, + UmaxpAdvsimd, + UmaxReg, + UmaxvAdvsimd, + UminAdvsimd, + UminImm, + UminpAdvsimd, + UminReg, + UminvAdvsimd, + UmlalAdvsimdElt, + UmlalAdvsimdVec, + UmlslAdvsimdElt, + UmlslAdvsimdVec, + UmmlaAdvsimdVec, + UmovAdvsimd, + Umsubl, + Umulh, + UmullAdvsimdElt, + UmullAdvsimdVec, + UqaddAdvsimdS, + UqaddAdvsimdV, + UqrshlAdvsimdS, + UqrshlAdvsimdV, + UqrshrnAdvsimdS, + UqrshrnAdvsimdV, + UqshlAdvsimdImmS, + UqshlAdvsimdImmV, + UqshlAdvsimdRegS, + UqshlAdvsimdRegV, + UqshrnAdvsimdS, + UqshrnAdvsimdV, + UqsubAdvsimdS, + UqsubAdvsimdV, + UqxtnAdvsimdS, + UqxtnAdvsimdV, + UrecpeAdvsimd, + UrhaddAdvsimd, + UrshlAdvsimdS, + UrshlAdvsimdV, + UrshrAdvsimdS, + UrshrAdvsimdV, + UrsqrteAdvsimd, + UrsraAdvsimdS, + UrsraAdvsimdV, + UsdotAdvsimdElt, + UsdotAdvsimdVec, + UshlAdvsimdS, + UshlAdvsimdV, + UshllAdvsimd, + UshrAdvsimdS, + UshrAdvsimdV, + UsmmlaAdvsimdVec, + UsqaddAdvsimdS, + UsqaddAdvsimdV, + UsraAdvsimdS, + UsraAdvsimdV, + UsublAdvsimd, + UsubwAdvsimd, + Uzp1Advsimd, + Uzp2Advsimd, + Wfe, + Wfet, + Wfi, + Wfit, + Xaflag, + XarAdvsimd, + XpacGeneral, + XpacSystem, + XtnAdvsimd, + Yield, + Zip1Advsimd, + Zip2Advsimd, + } + + static class InstNameExtensions + { + public static bool IsCall(this InstName name) + { + return name == InstName.Bl || name == InstName.Blr; + } + + public static bool IsControlFlowOrException(this InstName name) + { + switch (name) + { + case InstName.BUncond: + case InstName.BCond: + case InstName.Bl: + case InstName.Blr: + case InstName.Br: + case InstName.Brk: + case InstName.Cbnz: + case InstName.Cbz: + case InstName.Ret: + case InstName.Tbnz: + case InstName.Tbz: + case InstName.Svc: + case InstName.UdfPermUndef: + return true; + } + + return false; + } + + public static bool IsException(this InstName name) + { + switch (name) + { + case InstName.Brk: + case InstName.Svc: + case InstName.UdfPermUndef: + return true; + } + + return false; + } + + public static bool IsSystem(this InstName name) + { + switch (name) + { + case InstName.Mrs: + case InstName.MsrImm: + case InstName.MsrReg: + return true; + } + + return name.IsException(); + } + + public static bool IsSystemOrCall(this InstName name) + { + switch (name) + { + case InstName.Bl: + case InstName.Blr: + case InstName.Svc: + case InstName.Mrs: + case InstName.MsrImm: + case InstName.MsrReg: + case InstName.Sysl: + return true; + } + + return false; + } + + public static bool IsPrivileged(this InstName name) + { + switch (name) + { + case InstName.Dcps1: + case InstName.Dcps2: + case InstName.Dcps3: + case InstName.Drps: + case InstName.Eret: + case InstName.Ereta: + case InstName.Hvc: + case InstName.MsrImm: + case InstName.Smc: + return true; + } + + return false; + } + + public static bool IsPartialRegisterUpdateMemory(this InstName name) + { + switch (name) + { + case InstName.Ld1AdvsimdSnglAsNoPostIndex: + case InstName.Ld1AdvsimdSnglAsPostIndex: + case InstName.Ld2AdvsimdSnglAsNoPostIndex: + case InstName.Ld2AdvsimdSnglAsPostIndex: + case InstName.Ld3AdvsimdSnglAsNoPostIndex: + case InstName.Ld3AdvsimdSnglAsPostIndex: + case InstName.Ld4AdvsimdSnglAsNoPostIndex: + case InstName.Ld4AdvsimdSnglAsPostIndex: + return true; + } + + return false; + } + + public static bool IsPrefetchMemory(this InstName name) + { + switch (name) + { + case InstName.PrfmImm: + case InstName.PrfmLit: + case InstName.PrfmReg: + case InstName.Prfum: + return true; + } + + return false; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/MultiBlock.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/MultiBlock.cs new file mode 100644 index 00000000..8ac65059 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/MultiBlock.cs @@ -0,0 +1,64 @@ +using Ryujinx.Cpu.LightningJit.Graph; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Cpu.LightningJit.Arm64 +{ + class MultiBlock : IBlockList + { + public readonly List Blocks; + public readonly RegisterMask[] ReadMasks; + public readonly RegisterMask[] WriteMasks; + public readonly RegisterMask GlobalUseMask; + public readonly bool HasHostCall; + public readonly bool HasMemoryInstruction; + public readonly bool IsTruncated; + + public int Count => Blocks.Count; + + public IBlock this[int index] => Blocks[index]; + + public MultiBlock(List blocks, RegisterMask globalUseMask, bool hasHostCall, bool hasMemoryInstruction) + { + Blocks = blocks; + + (ReadMasks, WriteMasks) = DataFlow.GetGlobalUses(this); + + GlobalUseMask = globalUseMask; + HasHostCall = hasHostCall; + HasMemoryInstruction = hasMemoryInstruction; + IsTruncated = blocks[^1].IsTruncated; + } + + public void PrintDebugInfo() + { + foreach (Block block in Blocks) + { + Console.WriteLine($"bb {block.Index}"); + + List predList = new(); + List succList = new(); + + for (int index = 0; index < block.PredecessorsCount; index++) + { + predList.Add(block.GetPredecessor(index).Index); + } + + for (int index = 0; index < block.SuccessorsCount; index++) + { + succList.Add(block.GetSuccessor(index).Index); + } + + Console.WriteLine($" predecessors: {string.Join(' ', predList)}"); + Console.WriteLine($" successors: {string.Join(' ', succList)}"); + Console.WriteLine($" gpr read mask: 0x{ReadMasks[block.Index].GprMask:X} 0x{block.ComputeUseMasks().Read.GprMask:X}"); + Console.WriteLine($" gpr write mask: 0x{WriteMasks[block.Index].GprMask:X}"); + + for (int index = 0; index < block.Instructions.Count; index++) + { + Console.WriteLine($" {index} 0x{block.Instructions[index].Encoding:X8} {block.Instructions[index].Name}"); + } + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs new file mode 100644 index 00000000..1c6eab0d --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterAllocator.cs @@ -0,0 +1,161 @@ +using ARMeilleure.Memory; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System; +using System.Numerics; + +namespace Ryujinx.Cpu.LightningJit.Arm64 +{ + class RegisterAllocator + { + private uint _gprMask; + private readonly uint _fpSimdMask; + private readonly uint _pStateMask; + + private uint _tempGprsMask; + + private readonly int[] _registerMap; + + public int FixedContextRegister { get; } + public int FixedPageTableRegister { get; } + + public uint AllGprMask => (_gprMask & ~RegisterUtils.ReservedRegsMask) | _tempGprsMask; + public uint AllFpSimdMask => _fpSimdMask; + public uint AllPStateMask => _pStateMask; + + public RegisterAllocator(MemoryManagerType mmType, uint gprMask, uint fpSimdMask, uint pStateMask, bool hasHostCall) + { + _gprMask = gprMask; + _fpSimdMask = fpSimdMask; + _pStateMask = pStateMask; + + if (hasHostCall) + { + // If the function has calls, we can avoid the need to spill those registers across + // calls by puting them on callee saved registers. + + FixedContextRegister = AllocateAndMarkTempGprRegisterWithPreferencing(); + FixedPageTableRegister = AllocateAndMarkTempGprRegisterWithPreferencing(); + } + else + { + FixedContextRegister = AllocateAndMarkTempGprRegister(); + FixedPageTableRegister = AllocateAndMarkTempGprRegister(); + } + + _tempGprsMask = (1u << FixedContextRegister) | (1u << FixedPageTableRegister); + + _registerMap = new int[32]; + + for (int index = 0; index < _registerMap.Length; index++) + { + _registerMap[index] = index; + } + + BuildRegisterMap(_registerMap); + + Span tempRegisters = stackalloc int[CalculateMaxTemps(mmType)]; + + for (int index = 0; index < tempRegisters.Length; index++) + { + tempRegisters[index] = AllocateAndMarkTempGprRegister(); + } + + for (int index = 0; index < tempRegisters.Length; index++) + { + FreeTempGprRegister(tempRegisters[index]); + } + } + + private void BuildRegisterMap(Span map) + { + uint mask = _gprMask & RegisterUtils.ReservedRegsMask; + + while (mask != 0) + { + int index = BitOperations.TrailingZeroCount(mask); + int remapIndex = AllocateAndMarkTempGprRegister(); + + map[index] = remapIndex; + _tempGprsMask |= 1u << remapIndex; + + mask &= ~(1u << index); + } + } + + public int RemapReservedGprRegister(int index) + { + return _registerMap[index]; + } + + private int AllocateAndMarkTempGprRegister() + { + int index = AllocateTempGprRegister(); + _tempGprsMask |= 1u << index; + + return index; + } + + private int AllocateAndMarkTempGprRegisterWithPreferencing() + { + int index = AllocateTempRegisterWithPreferencing(); + _tempGprsMask |= 1u << index; + + return index; + } + + public int AllocateTempGprRegister() + { + return AllocateTempRegister(ref _gprMask); + } + + public void FreeTempGprRegister(int index) + { + FreeTempRegister(ref _gprMask, index); + } + + private int AllocateTempRegisterWithPreferencing() + { + int firstCalleeSaved = BitOperations.TrailingZeroCount(~_gprMask & AbiConstants.GprCalleeSavedRegsMask); + if (firstCalleeSaved < 32) + { + uint regMask = 1u << firstCalleeSaved; + if ((regMask & RegisterUtils.ReservedRegsMask) == 0) + { + _gprMask |= regMask; + + return firstCalleeSaved; + } + } + + return AllocateTempRegister(ref _gprMask); + } + + private static int AllocateTempRegister(ref uint mask) + { + int index = BitOperations.TrailingZeroCount(~(mask | RegisterUtils.ReservedRegsMask)); + if (index == sizeof(uint) * 8) + { + throw new InvalidOperationException("No free registers."); + } + + mask |= 1u << index; + + return index; + } + + private static void FreeTempRegister(ref uint mask, int index) + { + mask &= ~(1u << index); + } + + public static int CalculateMaxTemps(MemoryManagerType mmType) + { + return mmType.IsHostMapped() ? 1 : 2; + } + + public static int CalculateMaxTempsInclFixed(MemoryManagerType mmType) + { + return CalculateMaxTemps(mmType) + 2; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs new file mode 100644 index 00000000..191e03e7 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/RegisterUtils.cs @@ -0,0 +1,495 @@ +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm64 +{ + static class RegisterUtils + { + private const int RdRtBit = 0; + private const int RnBit = 5; + private const int RmRsBit = 16; + private const int RaRt2Bit = 10; + + // Some of those register have specific roles and can't be used as general purpose registers. + // X18 - Reserved for platform specific usage. + // X29 - Frame pointer. + // X30 - Return address. + // X31 - Not an actual register, in some cases maps to SP, and in others to ZR. + public const uint ReservedRegsMask = (1u << 18) | (1u << 29) | (1u << 30) | (1u << 31); + + public const int LrIndex = 30; + public const int SpIndex = 31; + public const int ZrIndex = 31; + public const int SpecialZrIndex = 32; + + public static uint RemapRegisters(RegisterAllocator regAlloc, InstFlags flags, uint encoding) + { + if (flags.HasFlag(InstFlags.Rd) && (!flags.HasFlag(InstFlags.FpSimd) || IsFpToGpr(flags, encoding))) + { + encoding = ReplaceGprRegister(regAlloc, encoding, RdRtBit, flags.HasFlag(InstFlags.RdSP)); + } + + if (flags.HasFlag(InstFlags.Rn) && (!flags.HasFlag(InstFlags.FpSimd) || IsFpFromGpr(flags, encoding) || flags.HasFlag(InstFlags.Memory))) + { + encoding = ReplaceGprRegister(regAlloc, encoding, RnBit, flags.HasFlag(InstFlags.RnSP)); + } + + if (!flags.HasFlag(InstFlags.FpSimd)) + { + if (flags.HasFlag(InstFlags.Rm) || flags.HasFlag(InstFlags.Rs)) + { + encoding = ReplaceGprRegister(regAlloc, encoding, RmRsBit); + } + + if (flags.HasFlag(InstFlags.Ra) || flags.HasFlag(InstFlags.Rt2)) + { + encoding = ReplaceGprRegister(regAlloc, encoding, RaRt2Bit); + } + + if (flags.HasFlag(InstFlags.Rt)) + { + encoding = ReplaceGprRegister(regAlloc, encoding, RdRtBit); + } + } + else if (flags.HasFlag(InstFlags.Rm) && flags.HasFlag(InstFlags.Memory)) + { + encoding = ReplaceGprRegister(regAlloc, encoding, RmRsBit); + } + + return encoding; + } + + public static uint ReplaceRt(uint encoding, int newIndex) + { + return ReplaceRegister(encoding, newIndex, RdRtBit); + } + + public static uint ReplaceRn(uint encoding, int newIndex) + { + return ReplaceRegister(encoding, newIndex, RnBit); + } + + private static uint ReplaceRegister(uint encoding, int newIndex, int bit) + { + encoding &= ~(0x1fu << bit); + encoding |= (uint)newIndex << bit; + + return encoding; + } + + private static uint ReplaceGprRegister(RegisterAllocator regAlloc, uint encoding, int bit, bool hasSP = false) + { + int oldIndex = (int)(encoding >> bit) & 0x1f; + if (oldIndex == ZrIndex && !hasSP) + { + return encoding; + } + + int newIndex = regAlloc.RemapReservedGprRegister(oldIndex); + + encoding &= ~(0x1fu << bit); + encoding |= (uint)newIndex << bit; + + return encoding; + } + + public static (uint, uint) PopulateReadMasks(InstName name, InstFlags flags, uint encoding) + { + uint gprMask = 0; + uint fpSimdMask = 0; + + if (flags.HasFlag(InstFlags.FpSimd)) + { + if (flags.HasFlag(InstFlags.Rd) && flags.HasFlag(InstFlags.ReadRd)) + { + uint mask = MaskFromIndex(ExtractRd(flags, encoding)); + + if (IsFpToGpr(flags, encoding)) + { + gprMask |= mask; + } + else + { + fpSimdMask |= mask; + } + } + + if (flags.HasFlag(InstFlags.Rn)) + { + uint mask = MaskFromIndex(ExtractRn(flags, encoding)); + + if (flags.HasFlag(InstFlags.RnSeq)) + { + int count = GetRnSequenceCount(encoding); + + for (int index = 0; index < count; index++, mask <<= 1) + { + fpSimdMask |= mask; + } + } + else if (IsFpFromGpr(flags, encoding) || flags.HasFlag(InstFlags.Memory)) + { + gprMask |= mask; + } + else + { + fpSimdMask |= mask; + } + } + + if (flags.HasFlag(InstFlags.Rm)) + { + uint mask = MaskFromIndex(ExtractRm(flags, encoding)); + + if (flags.HasFlag(InstFlags.Memory)) + { + gprMask |= mask; + } + else + { + fpSimdMask |= mask; + } + } + + if (flags.HasFlag(InstFlags.Ra)) + { + fpSimdMask |= MaskFromIndex(ExtractRa(flags, encoding)); + } + + if (flags.HasFlag(InstFlags.ReadRt)) + { + if (flags.HasFlag(InstFlags.Rt)) + { + uint mask = MaskFromIndex(ExtractRt(flags, encoding)); + + if (flags.HasFlag(InstFlags.RtSeq)) + { + int count = GetRtSequenceCount(name, encoding); + + for (int index = 0; index < count; index++, mask <<= 1) + { + fpSimdMask |= mask; + } + } + else + { + fpSimdMask |= mask; + } + } + + if (flags.HasFlag(InstFlags.Rt2)) + { + fpSimdMask |= MaskFromIndex(ExtractRt2(flags, encoding)); + } + } + } + else + { + if (flags.HasFlag(InstFlags.Rd) && flags.HasFlag(InstFlags.ReadRd)) + { + gprMask |= MaskFromIndex(ExtractRd(flags, encoding)); + } + + if (flags.HasFlag(InstFlags.Rn)) + { + gprMask |= MaskFromIndex(ExtractRn(flags, encoding)); + } + + if (flags.HasFlag(InstFlags.Rm)) + { + gprMask |= MaskFromIndex(ExtractRm(flags, encoding)); + } + + if (flags.HasFlag(InstFlags.Ra)) + { + gprMask |= MaskFromIndex(ExtractRa(flags, encoding)); + } + + if (flags.HasFlag(InstFlags.ReadRt)) + { + if (flags.HasFlag(InstFlags.Rt)) + { + gprMask |= MaskFromIndex(ExtractRt(flags, encoding)); + } + + if (flags.HasFlag(InstFlags.Rt2)) + { + gprMask |= MaskFromIndex(ExtractRt2(flags, encoding)); + } + } + } + + return (gprMask, fpSimdMask); + } + + public static (uint, uint) PopulateWriteMasks(InstName name, InstFlags flags, uint encoding) + { + uint gprMask = 0; + uint fpSimdMask = 0; + + if (flags.HasFlag(InstFlags.MemWBack)) + { + gprMask |= MaskFromIndex(ExtractRn(flags, encoding)); + } + + if (flags.HasFlag(InstFlags.FpSimd)) + { + if (flags.HasFlag(InstFlags.Rd)) + { + uint mask = MaskFromIndex(ExtractRd(flags, encoding)); + + if (IsFpToGpr(flags, encoding)) + { + gprMask |= mask; + } + else + { + fpSimdMask |= mask; + } + } + + if (!flags.HasFlag(InstFlags.ReadRt) || name.IsPartialRegisterUpdateMemory()) + { + if (flags.HasFlag(InstFlags.Rt)) + { + uint mask = MaskFromIndex(ExtractRt(flags, encoding)); + + if (flags.HasFlag(InstFlags.RtSeq)) + { + int count = GetRtSequenceCount(name, encoding); + + for (int index = 0; index < count; index++, mask <<= 1) + { + fpSimdMask |= mask; + } + } + else + { + fpSimdMask |= mask; + } + } + + if (flags.HasFlag(InstFlags.Rt2)) + { + fpSimdMask |= MaskFromIndex(ExtractRt2(flags, encoding)); + } + } + } + else + { + if (flags.HasFlag(InstFlags.Rd)) + { + gprMask |= MaskFromIndex(ExtractRd(flags, encoding)); + } + + if (!flags.HasFlag(InstFlags.ReadRt) || name.IsPartialRegisterUpdateMemory()) + { + if (flags.HasFlag(InstFlags.Rt)) + { + gprMask |= MaskFromIndex(ExtractRt(flags, encoding)); + } + + if (flags.HasFlag(InstFlags.Rt2)) + { + gprMask |= MaskFromIndex(ExtractRt2(flags, encoding)); + } + } + + if (flags.HasFlag(InstFlags.Rs)) + { + gprMask |= MaskFromIndex(ExtractRs(flags, encoding)); + } + } + + return (gprMask, fpSimdMask); + } + + private static uint MaskFromIndex(int index) + { + if (index < SpecialZrIndex) + { + return 1u << index; + } + + return 0u; + } + + private static bool IsFpFromGpr(InstFlags flags, uint encoding) + { + InstFlags bothFlags = InstFlags.FpSimdFromGpr | InstFlags.FpSimdToGpr; + + if ((flags & bothFlags) == bothFlags) // FMOV (general) + { + return (encoding & (1u << 16)) != 0; + } + + return flags.HasFlag(InstFlags.FpSimdFromGpr); + } + + private static bool IsFpToGpr(InstFlags flags, uint encoding) + { + InstFlags bothFlags = InstFlags.FpSimdFromGpr | InstFlags.FpSimdToGpr; + + if ((flags & bothFlags) == bothFlags) // FMOV (general) + { + return (encoding & (1u << 16)) == 0; + } + + return flags.HasFlag(InstFlags.FpSimdToGpr); + } + + private static int GetRtSequenceCount(InstName name, uint encoding) + { + switch (name) + { + case InstName.Ld1AdvsimdMultAsNoPostIndex: + case InstName.Ld1AdvsimdMultAsPostIndex: + case InstName.St1AdvsimdMultAsNoPostIndex: + case InstName.St1AdvsimdMultAsPostIndex: + return ((encoding >> 12) & 0xf) switch + { + 0b0000 => 4, + 0b0010 => 4, + 0b0100 => 3, + 0b0110 => 3, + 0b0111 => 1, + 0b1000 => 2, + 0b1010 => 2, + _ => 1, + }; + case InstName.Ld1rAdvsimdAsNoPostIndex: + case InstName.Ld1rAdvsimdAsPostIndex: + case InstName.Ld1AdvsimdSnglAsNoPostIndex: + case InstName.Ld1AdvsimdSnglAsPostIndex: + case InstName.St1AdvsimdSnglAsNoPostIndex: + case InstName.St1AdvsimdSnglAsPostIndex: + return 1; + case InstName.Ld2rAdvsimdAsNoPostIndex: + case InstName.Ld2rAdvsimdAsPostIndex: + case InstName.Ld2AdvsimdMultAsNoPostIndex: + case InstName.Ld2AdvsimdMultAsPostIndex: + case InstName.Ld2AdvsimdSnglAsNoPostIndex: + case InstName.Ld2AdvsimdSnglAsPostIndex: + case InstName.St2AdvsimdMultAsNoPostIndex: + case InstName.St2AdvsimdMultAsPostIndex: + case InstName.St2AdvsimdSnglAsNoPostIndex: + case InstName.St2AdvsimdSnglAsPostIndex: + return 2; + case InstName.Ld3rAdvsimdAsNoPostIndex: + case InstName.Ld3rAdvsimdAsPostIndex: + case InstName.Ld3AdvsimdMultAsNoPostIndex: + case InstName.Ld3AdvsimdMultAsPostIndex: + case InstName.Ld3AdvsimdSnglAsNoPostIndex: + case InstName.Ld3AdvsimdSnglAsPostIndex: + case InstName.St3AdvsimdMultAsNoPostIndex: + case InstName.St3AdvsimdMultAsPostIndex: + case InstName.St3AdvsimdSnglAsNoPostIndex: + case InstName.St3AdvsimdSnglAsPostIndex: + return 3; + case InstName.Ld4rAdvsimdAsNoPostIndex: + case InstName.Ld4rAdvsimdAsPostIndex: + case InstName.Ld4AdvsimdMultAsNoPostIndex: + case InstName.Ld4AdvsimdMultAsPostIndex: + case InstName.Ld4AdvsimdSnglAsNoPostIndex: + case InstName.Ld4AdvsimdSnglAsPostIndex: + case InstName.St4AdvsimdMultAsNoPostIndex: + case InstName.St4AdvsimdMultAsPostIndex: + case InstName.St4AdvsimdSnglAsNoPostIndex: + case InstName.St4AdvsimdSnglAsPostIndex: + return 4; + } + + return 1; + } + + private static int GetRnSequenceCount(uint encoding) + { + return ((int)(encoding >> 13) & 3) + 1; + } + + public static int ExtractRd(InstFlags flags, uint encoding) + { + Debug.Assert(flags.HasFlag(InstFlags.Rd)); + int index = (int)(encoding >> RdRtBit) & 0x1f; + + if (!flags.HasFlag(InstFlags.RdSP) && index == ZrIndex) + { + return SpecialZrIndex; + } + + return index; + } + + public static int ExtractRn(uint encoding) + { + return (int)(encoding >> RnBit) & 0x1f; + } + + public static int ExtractRn(InstFlags flags, uint encoding) + { + Debug.Assert(flags.HasFlag(InstFlags.Rn)); + int index = ExtractRn(encoding); + + if (!flags.HasFlag(InstFlags.RnSP) && index == ZrIndex) + { + return SpecialZrIndex; + } + + return index; + } + + public static int ExtractRm(uint encoding) + { + return (int)(encoding >> RmRsBit) & 0x1f; + } + + public static int ExtractRm(InstFlags flags, uint encoding) + { + Debug.Assert(flags.HasFlag(InstFlags.Rm)); + int index = ExtractRm(encoding); + + return index == ZrIndex ? SpecialZrIndex : index; + } + + public static int ExtractRs(uint encoding) + { + return (int)(encoding >> RmRsBit) & 0x1f; + } + + public static int ExtractRs(InstFlags flags, uint encoding) + { + Debug.Assert(flags.HasFlag(InstFlags.Rs)); + int index = ExtractRs(encoding); + + return index == ZrIndex ? SpecialZrIndex : index; + } + + public static int ExtractRa(InstFlags flags, uint encoding) + { + Debug.Assert(flags.HasFlag(InstFlags.Ra)); + int index = (int)(encoding >> RaRt2Bit) & 0x1f; + + return index == ZrIndex ? SpecialZrIndex : index; + } + + public static int ExtractRt(uint encoding) + { + return (int)(encoding >> RdRtBit) & 0x1f; + } + + public static int ExtractRt(InstFlags flags, uint encoding) + { + Debug.Assert(flags.HasFlag(InstFlags.Rt)); + int index = ExtractRt(encoding); + + return index == ZrIndex ? SpecialZrIndex : index; + } + + public static int ExtractRt2(InstFlags flags, uint encoding) + { + Debug.Assert(flags.HasFlag(InstFlags.Rt2)); + int index = (int)(encoding >> RaRt2Bit) & 0x1f; + + return index == ZrIndex ? SpecialZrIndex : index; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/SysUtils.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/SysUtils.cs new file mode 100644 index 00000000..69689a39 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/SysUtils.cs @@ -0,0 +1,48 @@ +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm64 +{ + static class SysUtils + { + public static (uint, uint, uint, uint) UnpackOp1CRnCRmOp2(uint encoding) + { + uint op1 = (encoding >> 16) & 7; + uint crn = (encoding >> 12) & 0xf; + uint crm = (encoding >> 8) & 0xf; + uint op2 = (encoding >> 5) & 7; + + return (op1, crn, crm, op2); + } + + public static bool IsCacheInstEl0(uint encoding) + { + (uint op1, uint crn, uint crm, uint op2) = UnpackOp1CRnCRmOp2(encoding); + + return ((op1 << 11) | (crn << 7) | (crm << 3) | op2) switch + { + 0b011_0111_0100_001 => true, // DC ZVA + 0b011_0111_1010_001 => true, // DC CVAC + 0b011_0111_1100_001 => true, // DC CVAP + 0b011_0111_1011_001 => true, // DC CVAU + 0b011_0111_1110_001 => true, // DC CIVAC + 0b011_0111_0101_001 => true, // IC IVAU + _ => false, + }; + } + + public static bool IsCacheInstUciTrapped(uint encoding) + { + (uint op1, uint crn, uint crm, uint op2) = UnpackOp1CRnCRmOp2(encoding); + + return ((op1 << 11) | (crn << 7) | (crm << 3) | op2) switch + { + 0b011_0111_1010_001 => true, // DC CVAC + 0b011_0111_1100_001 => true, // DC CVAP + 0b011_0111_1011_001 => true, // DC CVAU + 0b011_0111_1110_001 => true, // DC CIVAC + 0b011_0111_0101_001 => true, // IC IVAU + _ => false, + }; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs new file mode 100644 index 00000000..7a6d761e --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Compiler.cs @@ -0,0 +1,743 @@ +using ARMeilleure.Common; +using ARMeilleure.Memory; +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using Ryujinx.Cpu.LightningJit.Graph; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; + +namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 +{ + static class Compiler + { + private const int Encodable26BitsOffsetLimit = 0x2000000; + + private readonly struct Context + { + public readonly CodeWriter Writer; + public readonly RegisterAllocator RegisterAllocator; + public readonly TailMerger TailMerger; + public readonly AddressTable FuncTable; + public readonly IntPtr DispatchStubPointer; + + private readonly MultiBlock _multiBlock; + private readonly RegisterSaveRestore _registerSaveRestore; + private readonly IntPtr _pageTablePointer; + + public Context( + CodeWriter writer, + RegisterAllocator registerAllocator, + TailMerger tailMerger, + RegisterSaveRestore registerSaveRestore, + MultiBlock multiBlock, + AddressTable funcTable, + IntPtr dispatchStubPointer, + IntPtr pageTablePointer) + { + Writer = writer; + RegisterAllocator = registerAllocator; + TailMerger = tailMerger; + _registerSaveRestore = registerSaveRestore; + _multiBlock = multiBlock; + FuncTable = funcTable; + DispatchStubPointer = dispatchStubPointer; + _pageTablePointer = pageTablePointer; + } + + public readonly int GetLrRegisterIndex() + { + return RemapGprRegister(RegisterUtils.LrIndex); + } + + public readonly int RemapGprRegister(int index) + { + return RegisterAllocator.RemapReservedGprRegister(index); + } + + public readonly int GetReservedStackOffset() + { + return _registerSaveRestore.GetReservedStackOffset(); + } + + public readonly void WritePrologue() + { + Assembler asm = new(Writer); + + _registerSaveRestore.WritePrologue(ref asm); + + // If needed, set up the fixed registers with the pointers we will use. + // First one is the context pointer (passed as first argument), + // second one is the page table or address space base, it is at a fixed memory location and considered constant. + + if (RegisterAllocator.FixedContextRegister != 0) + { + asm.Mov(Register(RegisterAllocator.FixedContextRegister), Register(0)); + } + + if (_multiBlock.HasMemoryInstruction) + { + asm.Mov(Register(RegisterAllocator.FixedPageTableRegister), (ulong)_pageTablePointer); + } + + // This assumes that the block with the index 0 is always the entry block. + LoadFromContext(ref asm, _multiBlock.ReadMasks[0]); + } + + public readonly void WriteEpilogueWithoutContext() + { + Assembler asm = new(Writer); + + _registerSaveRestore.WriteEpilogue(ref asm); + } + + public void LoadFromContextAfterCall(int blockIndex) + { + Block block = _multiBlock.Blocks[blockIndex]; + + if (block.SuccessorsCount != 0) + { + Assembler asm = new(Writer); + + RegisterMask readMask = _multiBlock.ReadMasks[block.GetSuccessor(0).Index]; + + for (int sIndex = 1; sIndex < block.SuccessorsCount; sIndex++) + { + IBlock successor = block.GetSuccessor(sIndex); + + readMask |= _multiBlock.ReadMasks[successor.Index]; + } + + LoadFromContext(ref asm, readMask); + } + } + + private void LoadFromContext(ref Assembler asm, RegisterMask readMask) + { + LoadGprFromContext(ref asm, readMask.GprMask, NativeContextOffsets.GprBaseOffset); + LoadFpSimdFromContext(ref asm, readMask.FpSimdMask, NativeContextOffsets.FpSimdBaseOffset); + LoadPStateFromContext(ref asm, readMask.PStateMask, NativeContextOffsets.FlagsBaseOffset); + } + + public void StoreToContextBeforeCall(int blockIndex, ulong? newLrValue = null) + { + Assembler asm = new(Writer); + + StoreToContext(ref asm, _multiBlock.WriteMasks[blockIndex], newLrValue); + } + + private void StoreToContext(ref Assembler asm, RegisterMask writeMask, ulong? newLrValue) + { + StoreGprToContext(ref asm, writeMask.GprMask, NativeContextOffsets.GprBaseOffset, newLrValue); + StoreFpSimdToContext(ref asm, writeMask.FpSimdMask, NativeContextOffsets.FpSimdBaseOffset); + StorePStateToContext(ref asm, writeMask.PStateMask, NativeContextOffsets.FlagsBaseOffset); + } + + private void LoadGprFromContext(ref Assembler asm, uint mask, int baseOffset) + { + Operand contextPtr = Register(RegisterAllocator.FixedContextRegister); + + while (mask != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + int offset = baseOffset + reg * 8; + + if (reg < 31 && (mask & (2u << reg)) != 0 && offset < RegisterSaveRestore.Encodable9BitsOffsetLimit) + { + mask &= ~(3u << reg); + + asm.LdpRiUn( + Register(RegisterAllocator.RemapReservedGprRegister(reg)), + Register(RegisterAllocator.RemapReservedGprRegister(reg + 1)), + contextPtr, + offset); + } + else + { + mask &= ~(1u << reg); + + asm.LdrRiUn(Register(RegisterAllocator.RemapReservedGprRegister(reg)), contextPtr, offset); + } + } + } + + private void LoadFpSimdFromContext(ref Assembler asm, uint mask, int baseOffset) + { + Operand contextPtr = Register(RegisterAllocator.FixedContextRegister); + + while (mask != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + int offset = baseOffset + reg * 16; + + mask &= ~(1u << reg); + + asm.LdrRiUn(Register(reg, OperandType.V128), contextPtr, offset); + } + } + + private void LoadPStateFromContext(ref Assembler asm, uint mask, int baseOffset) + { + if (mask == 0) + { + return; + } + + Operand contextPtr = Register(RegisterAllocator.FixedContextRegister); + + int tempRegister = RegisterAllocator.AllocateTempGprRegister(); + + Operand rt = Register(tempRegister, OperandType.I32); + + asm.LdrRiUn(rt, contextPtr, baseOffset); + asm.MsrNzcv(rt); + + RegisterAllocator.FreeTempGprRegister(tempRegister); + } + + private void StoreGprToContext(ref Assembler asm, uint mask, int baseOffset, ulong? newLrValue) + { + Operand contextPtr = Register(RegisterAllocator.FixedContextRegister); + + int tempRegister = -1; + + if (newLrValue.HasValue) + { + // This is required for BLR X30 instructions, where we need to get the target address + // before it is overwritten with the return address that the call would write there. + + tempRegister = RegisterAllocator.AllocateTempGprRegister(); + + asm.Mov(Register(tempRegister), newLrValue.Value); + } + + while (mask != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + int offset = baseOffset + reg * 8; + + if (reg < 31 && (mask & (2u << reg)) != 0 && offset < RegisterSaveRestore.Encodable9BitsOffsetLimit) + { + mask &= ~(3u << reg); + + asm.StpRiUn( + Register(RemapReservedGprRegister(reg, tempRegister)), + Register(RemapReservedGprRegister(reg + 1, tempRegister)), + contextPtr, + offset); + } + else + { + mask &= ~(1u << reg); + + asm.StrRiUn(Register(RemapReservedGprRegister(reg, tempRegister)), contextPtr, offset); + } + } + + if (tempRegister >= 0) + { + RegisterAllocator.FreeTempGprRegister(tempRegister); + } + } + + private int RemapReservedGprRegister(int index, int tempRegister) + { + if (tempRegister >= 0 && index == RegisterUtils.LrIndex) + { + return tempRegister; + } + + return RegisterAllocator.RemapReservedGprRegister(index); + } + + private void StoreFpSimdToContext(ref Assembler asm, uint mask, int baseOffset) + { + Operand contextPtr = Register(RegisterAllocator.FixedContextRegister); + + while (mask != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + int offset = baseOffset + reg * 16; + + mask &= ~(1u << reg); + + asm.StrRiUn(Register(reg, OperandType.V128), contextPtr, offset); + } + } + + private void StorePStateToContext(ref Assembler asm, uint mask, int baseOffset) + { + if (mask == 0) + { + return; + } + + Operand contextPtr = Register(RegisterAllocator.FixedContextRegister); + + int tempRegister = RegisterAllocator.AllocateTempGprRegister(); + + Operand rt = Register(tempRegister, OperandType.I32); + + asm.MrsNzcv(rt); + asm.StrRiUn(rt, contextPtr, baseOffset); + + RegisterAllocator.FreeTempGprRegister(tempRegister); + } + } + + private readonly struct PendingBranch + { + public readonly int BlockIndex; + public readonly ulong Pc; + public readonly InstName Name; + public readonly uint Encoding; + public readonly int WriterPointer; + + public PendingBranch(int blockIndex, ulong pc, InstName name, uint encoding, int writerPointer) + { + BlockIndex = blockIndex; + Pc = pc; + Name = name; + Encoding = encoding; + WriterPointer = writerPointer; + } + } + + public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, AddressTable funcTable, IntPtr dispatchStubPtr) + { + MultiBlock multiBlock = Decoder.DecodeMulti(cpuPreset, memoryManager, address); + + Dictionary targets = new(); + List pendingBranches = new(); + + uint gprUseMask = multiBlock.GlobalUseMask.GprMask; + uint fpSimdUseMask = multiBlock.GlobalUseMask.FpSimdMask; + uint pStateUseMask = multiBlock.GlobalUseMask.PStateMask; + + CodeWriter writer = new(); + RegisterAllocator regAlloc = new(memoryManager.Type, gprUseMask, fpSimdUseMask, pStateUseMask, multiBlock.HasHostCall); + RegisterSaveRestore rsr = new( + regAlloc.AllGprMask & AbiConstants.GprCalleeSavedRegsMask, + regAlloc.AllFpSimdMask & AbiConstants.FpSimdCalleeSavedRegsMask, + OperandType.FP64, + multiBlock.HasHostCall, + multiBlock.HasHostCall ? CalculateStackSizeForCallSpill(regAlloc.AllGprMask, regAlloc.AllFpSimdMask, regAlloc.AllPStateMask) : 0); + + TailMerger tailMerger = new(); + + Context context = new(writer, regAlloc, tailMerger, rsr, multiBlock, funcTable, dispatchStubPtr, memoryManager.PageTablePointer); + + context.WritePrologue(); + + ulong pc = address; + + for (int blockIndex = 0; blockIndex < multiBlock.Blocks.Count; blockIndex++) + { + Block block = multiBlock.Blocks[blockIndex]; + + Debug.Assert(block.Address == pc); + + targets.Add(pc, writer.InstructionPointer); + + int instCount = block.EndsWithBranch ? block.Instructions.Count - 1 : block.Instructions.Count; + + for (int index = 0; index < instCount; index++) + { + InstInfo instInfo = block.Instructions[index]; + + uint encoding = RegisterUtils.RemapRegisters(regAlloc, instInfo.Flags, instInfo.Encoding); + + if (instInfo.AddressForm != AddressForm.None) + { + InstEmitMemory.RewriteInstruction( + memoryManager.AddressSpaceBits, + memoryManager.Type, + writer, + regAlloc, + instInfo.Name, + instInfo.Flags, + instInfo.AddressForm, + pc, + encoding); + } + else if (instInfo.Name == InstName.Sys) + { + InstEmitMemory.RewriteSysInstruction(memoryManager.AddressSpaceBits, memoryManager.Type, writer, regAlloc, encoding); + } + else if (instInfo.Name.IsSystem()) + { + bool needsContextStoreLoad = InstEmitSystem.NeedsContextStoreLoad(instInfo.Name); + + if (needsContextStoreLoad) + { + context.StoreToContextBeforeCall(blockIndex); + } + + InstEmitSystem.RewriteInstruction(writer, regAlloc, tailMerger, instInfo.Name, pc, encoding, rsr.GetReservedStackOffset()); + + if (needsContextStoreLoad) + { + context.LoadFromContextAfterCall(blockIndex); + } + } + else + { + writer.WriteInstruction(encoding); + } + + pc += 4UL; + } + + if (block.IsLoopEnd) + { + // If this is a loop, the code might run for a long time uninterrupted. + // We insert a "sync point" here to ensure the loop can be interrupted if needed. + + InstEmitSystem.WriteSyncPoint(writer, context.RegisterAllocator, tailMerger, context.GetReservedStackOffset()); + } + + if (blockIndex < multiBlock.Blocks.Count - 1) + { + InstInfo lastInstructionInfo = block.Instructions[^1]; + InstName lastInstructionName = lastInstructionInfo.Name; + InstFlags lastInstructionFlags = lastInstructionInfo.Flags; + uint lastInstructionEncoding = lastInstructionInfo.Encoding; + + lastInstructionEncoding = RegisterUtils.RemapRegisters(regAlloc, lastInstructionFlags, lastInstructionEncoding); + + if (lastInstructionName.IsCall()) + { + context.StoreToContextBeforeCall(blockIndex, pc + 4UL); + + InstEmitSystem.RewriteCallInstruction( + writer, + regAlloc, + tailMerger, + context.WriteEpilogueWithoutContext, + funcTable, + dispatchStubPtr, + lastInstructionName, + pc, + lastInstructionEncoding, + context.GetReservedStackOffset()); + + context.LoadFromContextAfterCall(blockIndex); + + pc += 4UL; + } + else if (lastInstructionName == InstName.Ret) + { + RewriteBranchInstruction(context, blockIndex, lastInstructionName, pc, lastInstructionEncoding); + + pc += 4UL; + } + else if (block.EndsWithBranch) + { + pendingBranches.Add(new(blockIndex, pc, lastInstructionName, lastInstructionEncoding, writer.InstructionPointer)); + writer.WriteInstruction(0u); // Placeholder. + + pc += 4UL; + } + } + } + + int lastBlockIndex = multiBlock.Blocks[^1].Index; + + if (multiBlock.IsTruncated) + { + Assembler asm = new(writer); + + WriteTailCallConstant(context, ref asm, lastBlockIndex, pc); + } + else + { + InstInfo lastInstructionInfo = multiBlock.Blocks[^1].Instructions[^1]; + InstName lastInstructionName = lastInstructionInfo.Name; + InstFlags lastInstructionFlags = lastInstructionInfo.Flags; + uint lastInstructionEncoding = lastInstructionInfo.Encoding; + + lastInstructionEncoding = RegisterUtils.RemapRegisters(regAlloc, lastInstructionFlags, lastInstructionEncoding); + + RewriteBranchInstruction(context, lastBlockIndex, lastInstructionName, pc, lastInstructionEncoding); + + pc += 4; + } + + foreach (PendingBranch pendingBranch in pendingBranches) + { + RewriteBranchInstructionWithTarget( + context, + pendingBranch.BlockIndex, + pendingBranch.Name, + pendingBranch.Pc, + pendingBranch.Encoding, + pendingBranch.WriterPointer, + targets); + } + + tailMerger.WriteReturn(writer, context.WriteEpilogueWithoutContext); + + return new(writer.AsByteSpan(), (int)(pc - address)); + } + + private static int CalculateStackSizeForCallSpill(uint gprUseMask, uint fpSimdUseMask, uint pStateUseMask) + { + // Note that we don't discard callee saved FP/SIMD register because only the lower 64 bits is callee saved, + // so if the function is using the full register, that won't be enough. + // We could do better, but it's likely not worth it since this case happens very rarely in practice. + + return BitOperations.PopCount(gprUseMask & ~AbiConstants.GprCalleeSavedRegsMask) * 8 + + BitOperations.PopCount(fpSimdUseMask) * 16 + + (pStateUseMask != 0 ? 8 : 0); + } + + private static void RewriteBranchInstruction(in Context context, int blockIndex, InstName name, ulong pc, uint encoding) + { + CodeWriter writer = context.Writer; + Assembler asm = new(writer); + + int originalOffset; + ulong nextAddress = pc + 4UL; + ulong targetAddress; + + switch (name) + { + case InstName.BUncond: + originalOffset = ImmUtils.ExtractSImm26Times4(encoding); + targetAddress = pc + (ulong)originalOffset; + + WriteTailCallConstant(context, ref asm, blockIndex, targetAddress); + break; + + case InstName.Bl: + case InstName.Blr: + case InstName.Br: + if (name == InstName.Bl) + { + asm.Mov(Register(context.GetLrRegisterIndex()), nextAddress); + + int imm = ImmUtils.ExtractSImm26Times4(encoding); + + WriteTailCallConstant(context, ref asm, blockIndex, pc + (ulong)imm); + } + else + { + bool isCall = name == InstName.Blr; + if (isCall) + { + context.StoreToContextBeforeCall(blockIndex, nextAddress); + } + else + { + context.StoreToContextBeforeCall(blockIndex); + } + + InstEmitSystem.RewriteCallInstruction( + context.Writer, + context.RegisterAllocator, + context.TailMerger, + context.WriteEpilogueWithoutContext, + context.FuncTable, + context.DispatchStubPointer, + name, + pc, + encoding, + context.GetReservedStackOffset(), + isTail: true); + } + break; + + case InstName.Ret: + int rnIndex = RegisterUtils.ExtractRn(encoding); + if (rnIndex == RegisterUtils.ZrIndex) + { + WriteTailCallConstant(context, ref asm, blockIndex, 0UL); + } + else + { + rnIndex = context.RemapGprRegister(rnIndex); + context.StoreToContextBeforeCall(blockIndex); + + if (rnIndex != 0) + { + asm.Mov(Register(0), Register(rnIndex)); + } + + context.TailMerger.AddUnconditionalReturn(writer, asm); + } + break; + + case InstName.BCond: + case InstName.Cbnz: + case InstName.Cbz: + case InstName.Tbnz: + case InstName.Tbz: + uint branchMask; + + if (name == InstName.Tbnz || name == InstName.Tbz) + { + originalOffset = ImmUtils.ExtractSImm14Times4(encoding); + branchMask = 0x3fff; + } + else + { + originalOffset = ImmUtils.ExtractSImm19Times4(encoding); + branchMask = 0x7ffff; + } + + targetAddress = pc + (ulong)originalOffset; + + int branchIndex = writer.InstructionPointer; + + writer.WriteInstruction(0u); // Reserved for branch. + WriteTailCallConstant(context, ref asm, blockIndex, nextAddress); + + int targetIndex = writer.InstructionPointer; + + writer.WriteInstructionAt(branchIndex, (encoding & ~(branchMask << 5)) | (uint)(((targetIndex - branchIndex) & branchMask) << 5)); + WriteTailCallConstant(context, ref asm, blockIndex, targetAddress); + break; + + default: + Debug.Fail($"Unknown branch instruction \"{name}\"."); + break; + } + } + + private static void RewriteBranchInstructionWithTarget( + in Context context, + int blockIndex, + InstName name, + ulong pc, + uint encoding, + int branchIndex, + Dictionary targets) + { + CodeWriter writer = context.Writer; + Assembler asm = new(writer); + + int delta; + int targetIndex; + int originalOffset; + ulong targetAddress; + + switch (name) + { + case InstName.BUncond: + originalOffset = ImmUtils.ExtractSImm26Times4(encoding); + targetAddress = pc + (ulong)originalOffset; + + if (targets.TryGetValue(targetAddress, out targetIndex)) + { + delta = targetIndex - branchIndex; + + if (delta >= -Encodable26BitsOffsetLimit && delta < Encodable26BitsOffsetLimit) + { + writer.WriteInstructionAt(branchIndex, (encoding & ~0x3ffffffu) | (uint)(delta & 0x3ffffff)); + break; + } + } + + targetIndex = writer.InstructionPointer; + delta = targetIndex - branchIndex; + + writer.WriteInstructionAt(branchIndex, (encoding & ~0x3ffffffu) | (uint)(delta & 0x3ffffff)); + WriteTailCallConstant(context, ref asm, blockIndex, targetAddress); + break; + + case InstName.BCond: + case InstName.Cbnz: + case InstName.Cbz: + case InstName.Tbnz: + case InstName.Tbz: + uint branchMask; + + if (name == InstName.Tbnz || name == InstName.Tbz) + { + originalOffset = ImmUtils.ExtractSImm14Times4(encoding); + branchMask = 0x3fff; + } + else + { + originalOffset = ImmUtils.ExtractSImm19Times4(encoding); + branchMask = 0x7ffff; + } + + int branchMax = (int)(branchMask + 1) / 2; + + targetAddress = pc + (ulong)originalOffset; + + if (targets.TryGetValue(targetAddress, out targetIndex)) + { + delta = targetIndex - branchIndex; + + if (delta >= -branchMax && delta < branchMax) + { + writer.WriteInstructionAt(branchIndex, (encoding & ~(branchMask << 5)) | (uint)((delta & branchMask) << 5)); + break; + } + } + + targetIndex = writer.InstructionPointer; + delta = targetIndex - branchIndex; + + if (delta >= -branchMax && delta < branchMax) + { + writer.WriteInstructionAt(branchIndex, (encoding & ~(branchMask << 5)) | (uint)((delta & branchMask) << 5)); + WriteTailCallConstant(context, ref asm, blockIndex, targetAddress); + } + else + { + // If the branch target is too far away, we use a regular unconditional branch + // instruction instead which has a much higher range. + // We branch directly to the end of the function, where we put the conditional branch, + // and then branch back to the next instruction or return the branch target depending + // on the branch being taken or not. + + uint branchInst = 0x14000000u | ((uint)delta & 0x3ffffff); + Debug.Assert(ImmUtils.ExtractSImm26Times4(branchInst) == delta * 4); + + writer.WriteInstructionAt(branchIndex, branchInst); + + int movedBranchIndex = writer.InstructionPointer; + + writer.WriteInstruction(0u); // Placeholder + asm.B((branchIndex + 1 - writer.InstructionPointer) * 4); + + delta = writer.InstructionPointer - movedBranchIndex; + + writer.WriteInstructionAt(movedBranchIndex, (encoding & ~(branchMask << 5)) | (uint)((delta & branchMask) << 5)); + WriteTailCallConstant(context, ref asm, blockIndex, targetAddress); + } + break; + + default: + Debug.Fail($"Unknown branch instruction \"{name}\"."); + break; + } + } + + private static void WriteTailCallConstant(in Context context, ref Assembler asm, int blockIndex, ulong address) + { + context.StoreToContextBeforeCall(blockIndex); + InstEmitSystem.WriteCallWithGuestAddress( + context.Writer, + ref asm, + context.RegisterAllocator, + context.TailMerger, + context.WriteEpilogueWithoutContext, + context.FuncTable, + context.DispatchStubPointer, + context.GetReservedStackOffset(), + 0UL, + new Operand(OperandKind.Constant, OperandType.I64, address), + isTail: true); + } + + private static Operand Register(int register, OperandType type = OperandType.I64) + { + return new Operand(register, RegisterType.Integer, type); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs new file mode 100644 index 00000000..d5e1eb19 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/Decoder.cs @@ -0,0 +1,392 @@ +using ARMeilleure.Memory; +using Ryujinx.Cpu.LightningJit.Graph; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; + +namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 +{ + static class Decoder + { + private const int MaxInstructionsPerFunction = 10000; + + private const uint NzcvFlags = 0xfu << 28; + private const uint CFlag = 0x1u << 29; + + public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address) + { + List blocks = new(); + List branchTargets = new(); + + RegisterMask useMask = RegisterMask.Zero; + + bool hasHostCall = false; + bool hasMemoryInstruction = false; + int totalInsts = 0; + + while (true) + { + Block block = Decode(cpuPreset, memoryManager, address, ref totalInsts, ref useMask, ref hasHostCall, ref hasMemoryInstruction); + + if (!block.IsTruncated && TryGetBranchTarget(block, out ulong targetAddress)) + { + branchTargets.Add(targetAddress); + } + + blocks.Add(block); + + if (block.IsTruncated || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets)) + { + break; + } + + address = block.EndAddress; + } + + branchTargets.Sort(); + SplitBlocks(blocks, branchTargets); + NumberAndLinkBlocks(blocks); + + return new(blocks, useMask, hasHostCall, hasMemoryInstruction); + } + + private static bool TryGetBranchTarget(Block block, out ulong targetAddress) + { + return TryGetBranchTarget(block.Instructions[^1].Name, block.EndAddress - 4UL, block.Instructions[^1].Encoding, out targetAddress); + } + + private static bool TryGetBranchTarget(InstName name, ulong pc, uint encoding, out ulong targetAddress) + { + int originalOffset; + + switch (name) + { + case InstName.BUncond: + originalOffset = ImmUtils.ExtractSImm26Times4(encoding); + targetAddress = pc + (ulong)originalOffset; + + return true; + + case InstName.BCond: + case InstName.Cbnz: + case InstName.Cbz: + case InstName.Tbnz: + case InstName.Tbz: + if (name == InstName.Tbnz || name == InstName.Tbz) + { + originalOffset = ImmUtils.ExtractSImm14Times4(encoding); + } + else + { + originalOffset = ImmUtils.ExtractSImm19Times4(encoding); + } + + targetAddress = pc + (ulong)originalOffset; + + return true; + } + + targetAddress = 0; + + return false; + } + + private static void SplitBlocks(List blocks, List branchTargets) + { + int btIndex = 0; + + while (btIndex < branchTargets.Count) + { + for (int blockIndex = 0; blockIndex < blocks.Count && btIndex < branchTargets.Count; blockIndex++) + { + Block block = blocks[blockIndex]; + ulong currentBranchTarget = branchTargets[btIndex]; + + while (currentBranchTarget >= block.Address && currentBranchTarget < block.EndAddress) + { + if (block.Address != currentBranchTarget) + { + (Block leftBlock, Block rightBlock) = block.SplitAtAddress(currentBranchTarget); + + blocks.Insert(blockIndex, leftBlock); + blocks[blockIndex + 1] = rightBlock; + + block = leftBlock; + } + + btIndex++; + + while (btIndex < branchTargets.Count && branchTargets[btIndex] == currentBranchTarget) + { + btIndex++; + } + + if (btIndex >= branchTargets.Count) + { + break; + } + + currentBranchTarget = branchTargets[btIndex]; + } + } + + Debug.Assert(btIndex < int.MaxValue); + btIndex++; + } + } + + private static void NumberAndLinkBlocks(List blocks) + { + Dictionary blocksByAddress = new(); + + for (int blockIndex = 0; blockIndex < blocks.Count; blockIndex++) + { + Block block = blocks[blockIndex]; + + blocksByAddress.Add(block.Address, block); + } + + for (int blockIndex = 0; blockIndex < blocks.Count; blockIndex++) + { + Block block = blocks[blockIndex]; + + block.Number(blockIndex); + + if (!block.IsTruncated) + { + bool hasNext = !block.EndsWithBranch; + bool hasBranch = false; + + switch (block.Instructions[^1].Name) + { + case InstName.BUncond: + hasBranch = true; + break; + + case InstName.BCond: + case InstName.Cbnz: + case InstName.Cbz: + case InstName.Tbnz: + case InstName.Tbz: + hasNext = true; + hasBranch = true; + break; + + case InstName.Bl: + case InstName.Blr: + hasNext = true; + break; + + case InstName.Ret: + hasNext = false; + hasBranch = false; + break; + } + + if (hasNext && blocksByAddress.TryGetValue(block.EndAddress, out Block nextBlock)) + { + block.AddSuccessor(nextBlock); + nextBlock.AddPredecessor(block); + } + + if (hasBranch && + TryGetBranchTarget(block, out ulong targetAddress) && + blocksByAddress.TryGetValue(targetAddress, out Block branchBlock)) + { + block.AddSuccessor(branchBlock); + branchBlock.AddPredecessor(block); + } + } + } + } + + private static bool HasNextBlock(in Block block, ulong pc, List branchTargets) + { + switch (block.Instructions[^1].Name) + { + case InstName.BUncond: + return branchTargets.Contains(pc + 4UL) || + (TryGetBranchTarget(block, out ulong targetAddress) && targetAddress >= pc && targetAddress < pc + 0x1000); + + case InstName.BCond: + case InstName.Bl: + case InstName.Blr: + case InstName.Cbnz: + case InstName.Cbz: + case InstName.Tbnz: + case InstName.Tbz: + return true; + + case InstName.Br: + return false; + + case InstName.Ret: + return branchTargets.Contains(pc + 4UL); + } + + return !block.EndsWithBranch; + } + + private static Block Decode( + CpuPreset cpuPreset, + IMemoryManager memoryManager, + ulong address, + ref int totalInsts, + ref RegisterMask useMask, + ref bool hasHostCall, + ref bool hasMemoryInstruction) + { + ulong startAddress = address; + + List insts = new(); + + uint gprUseMask = useMask.GprMask; + uint fpSimdUseMask = useMask.FpSimdMask; + uint pStateUseMask = useMask.PStateMask; + + uint encoding; + InstName name; + InstFlags flags; + bool isControlFlow; + bool isTruncated = false; + + do + { + encoding = memoryManager.Read(address); + address += 4UL; + + (name, flags, AddressForm addressForm) = InstTable.GetInstNameAndFlags(encoding, cpuPreset.Version, cpuPreset.Features); + + if (name.IsPrivileged() || (name == InstName.Sys && IsPrivilegedSys(encoding))) + { + name = InstName.UdfPermUndef; + flags = InstFlags.None; + addressForm = AddressForm.None; + } + + (uint instGprReadMask, uint instFpSimdReadMask) = RegisterUtils.PopulateReadMasks(name, flags, encoding); + (uint instGprWriteMask, uint instFpSimdWriteMask) = RegisterUtils.PopulateWriteMasks(name, flags, encoding); + + if (name.IsCall()) + { + instGprWriteMask |= 1u << RegisterUtils.LrIndex; + } + + uint tempGprUseMask = gprUseMask | instGprReadMask | instGprWriteMask; + + if (CalculateAvailableTemps(tempGprUseMask) < CalculateRequiredGprTemps(memoryManager.Type, tempGprUseMask) || + totalInsts++ >= MaxInstructionsPerFunction) + { + isTruncated = true; + address -= 4UL; + + break; + } + + gprUseMask = tempGprUseMask; + + uint instPStateReadMask = 0; + uint instPStateWriteMask = 0; + + if (flags.HasFlag(InstFlags.Nzcv) || IsMrsNzcv(encoding)) + { + instPStateReadMask = NzcvFlags; + } + else if (flags.HasFlag(InstFlags.C)) + { + instPStateReadMask = CFlag; + } + + if (flags.HasFlag(InstFlags.S) || IsMsrNzcv(encoding)) + { + instPStateWriteMask = NzcvFlags; + } + + if (flags.HasFlag(InstFlags.Memory) || name == InstName.Sys) + { + hasMemoryInstruction = true; + } + + fpSimdUseMask |= instFpSimdReadMask | instFpSimdWriteMask; + pStateUseMask |= instPStateReadMask | instPStateWriteMask; + + if (name.IsSystemOrCall() && !hasHostCall) + { + hasHostCall = name.IsCall() || InstEmitSystem.NeedsCall(encoding); + } + + isControlFlow = name.IsControlFlowOrException(); + + RegisterUse registerUse = new( + instGprReadMask, + instGprWriteMask, + instFpSimdReadMask, + instFpSimdWriteMask, + instPStateReadMask, + instPStateWriteMask); + + insts.Add(new(encoding, name, flags, addressForm, registerUse)); + } + while (!isControlFlow); + + bool isLoopEnd = false; + + if (!isTruncated && IsBackwardsBranch(name, encoding)) + { + hasHostCall = true; + isLoopEnd = true; + } + + useMask = new(gprUseMask, fpSimdUseMask, pStateUseMask); + + return new(startAddress, address, insts, !isTruncated && !name.IsException(), isTruncated, isLoopEnd); + } + + private static bool IsPrivilegedSys(uint encoding) + { + return !SysUtils.IsCacheInstEl0(encoding); + } + + private static bool IsMrsNzcv(uint encoding) + { + return (encoding & ~0x1fu) == 0xd53b4200u; + } + + private static bool IsMsrNzcv(uint encoding) + { + return (encoding & ~0x1fu) == 0xd51b4200u; + } + + private static bool IsBackwardsBranch(InstName name, uint encoding) + { + switch (name) + { + case InstName.BUncond: + return ImmUtils.ExtractSImm26Times4(encoding) < 0; + + case InstName.BCond: + case InstName.Cbnz: + case InstName.Cbz: + case InstName.Tbnz: + case InstName.Tbz: + int imm = name == InstName.Tbnz || name == InstName.Tbz + ? ImmUtils.ExtractSImm14Times4(encoding) + : ImmUtils.ExtractSImm19Times4(encoding); + + return imm < 0; + } + + return false; + } + + private static int CalculateRequiredGprTemps(MemoryManagerType mmType, uint gprUseMask) + { + return BitOperations.PopCount(gprUseMask & RegisterUtils.ReservedRegsMask) + RegisterAllocator.CalculateMaxTempsInclFixed(mmType); + } + + private static int CalculateAvailableTemps(uint gprUseMask) + { + return BitOperations.PopCount(~(gprUseMask | RegisterUtils.ReservedRegsMask)); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs new file mode 100644 index 00000000..790a7de9 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitMemory.cs @@ -0,0 +1,641 @@ +using ARMeilleure.Memory; +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System; +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 +{ + static class InstEmitMemory + { + private const uint XMask = 0x3f808000u; + private const uint XValue = 0x8000000u; + + public static void RewriteSysInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, uint encoding) + { + // TODO: Handle IC instruction, it should invalidate the JIT cache. + + if (InstEmitSystem.IsCacheInstForbidden(encoding)) + { + // Current OS does not allow cache maintenance instructions from user mode, just do nothing. + return; + } + + int rtIndex = RegisterUtils.ExtractRt(encoding); + if (rtIndex == RegisterUtils.ZrIndex) + { + writer.WriteInstruction(encoding); + + return; + } + + int tempRegister = regAlloc.AllocateTempGprRegister(); + Operand rt = new(tempRegister, RegisterType.Integer, OperandType.I64); + Operand guestAddress = new(rtIndex, RegisterType.Integer, OperandType.I64); + + Assembler asm = new(writer); + + WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, rt, guestAddress); + + encoding = RegisterUtils.ReplaceRt(encoding, tempRegister); + + writer.WriteInstruction(encoding); + + regAlloc.FreeTempGprRegister(tempRegister); + } + + public static void RewriteInstruction( + int asBits, + MemoryManagerType mmType, + CodeWriter writer, + RegisterAllocator regAlloc, + InstName name, + InstFlags flags, + AddressForm addressForm, + ulong pc, + uint encoding) + { + if (name.IsPrefetchMemory() && mmType == MemoryManagerType.HostTrackedUnsafe) + { + // Prefetch to invalid addresses do not cause faults, so for memory manager + // types where we need to access the page table before doing the prefetch, + // we should make sure we won't try to access an out of bounds page table region. + // To do this, we force the masked memory manager variant to be used. + + mmType = MemoryManagerType.HostTracked; + } + + switch (addressForm) + { + case AddressForm.OffsetReg: + RewriteOffsetRegMemoryInstruction(asBits, mmType, writer, regAlloc, flags, encoding); + break; + case AddressForm.PostIndexed: + RewritePostIndexedMemoryInstruction(asBits, mmType, writer, regAlloc, flags, encoding); + break; + case AddressForm.PreIndexed: + RewritePreIndexedMemoryInstruction(asBits, mmType, writer, regAlloc, flags, encoding); + break; + case AddressForm.SignedScaled: + RewriteSignedScaledMemoryInstruction(asBits, mmType, writer, regAlloc, flags, encoding); + break; + case AddressForm.UnsignedScaled: + RewriteUnsignedScaledMemoryInstruction(asBits, mmType, writer, regAlloc, flags, encoding); + break; + case AddressForm.BaseRegister: + // Some applications uses unordered memory instructions in places where + // it does need proper ordering, and only work on some CPUs. + // To work around this, make all exclusive access operations ordered. + + if ((encoding & XMask) == XValue) + { + // Set ordered flag. + encoding |= 1u << 15; + } + + RewriteBaseRegisterMemoryInstruction(asBits, mmType, writer, regAlloc, encoding); + break; + case AddressForm.StructNoOffset: + RewriteBaseRegisterMemoryInstruction(asBits, mmType, writer, regAlloc, encoding); + break; + case AddressForm.BasePlusOffset: + RewriteBasePlusOffsetMemoryInstruction(asBits, mmType, writer, regAlloc, encoding); + break; + case AddressForm.Literal: + RewriteLiteralMemoryInstruction(asBits, mmType, writer, regAlloc, name, pc, encoding); + break; + case AddressForm.StructPostIndexedReg: + RewriteStructPostIndexedRegMemoryInstruction(asBits, mmType, writer, regAlloc, encoding); + break; + default: + writer.WriteInstruction(encoding); + break; + } + } + + private static void RewriteOffsetRegMemoryInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, InstFlags flags, uint encoding) + { + // TODO: Some unallocated encoding cases. + + ArmExtensionType extensionType = (ArmExtensionType)((encoding >> 13) & 7); + + uint size = encoding >> 30; + + if (flags.HasFlag(InstFlags.FpSimd)) + { + size |= (encoding >> 21) & 4u; + } + + int shift = (encoding & (1u << 12)) != 0 ? (int)size : 0; + + int tempRegister = regAlloc.AllocateTempGprRegister(); + Operand rn = new(tempRegister, RegisterType.Integer, OperandType.I64); + Operand guestAddress = new(RegisterUtils.ExtractRn(encoding), RegisterType.Integer, OperandType.I64); + Operand guestOffset = new(RegisterUtils.ExtractRm(encoding), RegisterType.Integer, OperandType.I64); + + Assembler asm = new(writer); + + asm.Add(rn, guestAddress, guestOffset, extensionType, shift); + + WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, rn, rn); + + encoding = RegisterUtils.ReplaceRn(encoding, tempRegister); + encoding = (encoding & ~(0xfffu << 10)) | (1u << 24); // Register -> Unsigned offset + + writer.WriteInstruction(encoding); + + regAlloc.FreeTempGprRegister(tempRegister); + } + + private static void RewritePostIndexedMemoryInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, InstFlags flags, uint encoding) + { + bool isPair = flags.HasFlag(InstFlags.Rt2); + int imm = isPair ? ExtractSImm7Scaled(flags, encoding) : ExtractSImm9(encoding); + + int tempRegister = regAlloc.AllocateTempGprRegister(); + Operand rn = new(tempRegister, RegisterType.Integer, OperandType.I64); + Operand guestAddress = new(RegisterUtils.ExtractRn(encoding), RegisterType.Integer, OperandType.I64); + + Assembler asm = new(writer); + + WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, rn, guestAddress); + + encoding = RegisterUtils.ReplaceRn(encoding, tempRegister); + + if (isPair) + { + // Post-index -> Signed offset + encoding &= ~(0x7fu << 15); + encoding ^= 3u << 23; + } + else + { + // Post-index -> Unsigned offset + encoding = (encoding & ~(0xfffu << 10)) | (1u << 24); + } + + writer.WriteInstruction(encoding); + + WriteAddConstant(ref asm, guestAddress, guestAddress, imm); + + regAlloc.FreeTempGprRegister(tempRegister); + } + + private static void RewritePreIndexedMemoryInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, InstFlags flags, uint encoding) + { + bool isPair = flags.HasFlag(InstFlags.Rt2); + int imm = isPair ? ExtractSImm7Scaled(flags, encoding) : ExtractSImm9(encoding); + + int tempRegister = regAlloc.AllocateTempGprRegister(); + Operand rn = new(tempRegister, RegisterType.Integer, OperandType.I64); + Operand guestAddress = new(RegisterUtils.ExtractRn(encoding), RegisterType.Integer, OperandType.I64); + + Assembler asm = new(writer); + + WriteAddConstant(ref asm, guestAddress, guestAddress, imm); + WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, rn, guestAddress); + + encoding = RegisterUtils.ReplaceRn(encoding, tempRegister); + + if (isPair) + { + // Pre-index -> Signed offset + encoding &= ~(0x7fu << 15); + encoding &= ~(1u << 23); + } + else + { + // Pre-index -> Unsigned offset + encoding = (encoding & ~(0xfffu << 10)) | (1u << 24); + } + + writer.WriteInstruction(encoding); + + regAlloc.FreeTempGprRegister(tempRegister); + } + + private static void RewriteSignedScaledMemoryInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, InstFlags flags, uint encoding) + { + RewriteMemoryInstruction(asBits, mmType, writer, regAlloc, encoding, ExtractSImm7Scaled(flags, encoding), 0x7fu << 15); + } + + private static void RewriteUnsignedScaledMemoryInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, InstFlags flags, uint encoding) + { + RewriteMemoryInstruction(asBits, mmType, writer, regAlloc, encoding, ExtractUImm12Scaled(flags, encoding), 0xfffu << 10); + } + + private static void RewriteBaseRegisterMemoryInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, uint encoding) + { + RewriteMemoryInstruction(asBits, mmType, writer, regAlloc, encoding, 0, 0u); + } + + private static void RewriteBasePlusOffsetMemoryInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, uint encoding) + { + RewriteMemoryInstruction(asBits, mmType, writer, regAlloc, encoding, ExtractSImm9(encoding), 0x1ffu << 12); + } + + private static void RewriteMemoryInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, uint encoding, int imm, uint immMask) + { + int tempRegister = regAlloc.AllocateTempGprRegister(); + Operand rn = new(tempRegister, RegisterType.Integer, OperandType.I64); + Operand guestAddress = new(RegisterUtils.ExtractRn(encoding), RegisterType.Integer, OperandType.I64); + + Assembler asm = new(writer); + + bool canFoldOffset = CanFoldOffset(mmType, imm); + if (canFoldOffset) + { + imm = 0; + } + + WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, rn, guestAddress, imm); + + encoding = RegisterUtils.ReplaceRn(encoding, tempRegister); + + if (!canFoldOffset) + { + encoding &= ~immMask; // Clear offset + } + + writer.WriteInstruction(encoding); + + regAlloc.FreeTempGprRegister(tempRegister); + } + + private static void RewriteLiteralMemoryInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, InstName name, ulong pc, uint encoding) + { + Assembler asm = new(writer); + + ulong targetAddress; + long imm; + int rtIndex = (int)(encoding & 0x1f); + + if (rtIndex == RegisterUtils.ZrIndex && name != InstName.PrfmLit) + { + return; + } + + Operand rt; + + if (name == InstName.LdrLitFpsimd) + { + uint opc = encoding >> 30; + + // TODO: Undefined if opc is invalid? + + rt = new(rtIndex, RegisterType.Vector, opc switch + { + 0 => OperandType.FP32, + 1 => OperandType.FP64, + _ => OperandType.V128, + }); + } + else + { + rt = new(rtIndex, RegisterType.Integer, OperandType.I64); + } + + switch (name) + { + case InstName.Adr: + case InstName.Adrp: + imm = ((long)(encoding >> 29) & 3) | ((long)(encoding >> 3) & 0x1ffffc); + imm <<= 43; + + if (name == InstName.Adrp) + { + imm >>= 31; + targetAddress = (pc & ~0xfffUL) + (ulong)imm; + } + else + { + imm >>= 43; + targetAddress = pc + (ulong)imm; + } + + asm.Mov(rt, targetAddress); + break; + case InstName.LdrLitGen: + case InstName.LdrswLit: + case InstName.LdrLitFpsimd: + case InstName.PrfmLit: + imm = encoding & ~0x1fu; + imm <<= 40; + imm >>= 43; + targetAddress = pc + (ulong)imm; + + int tempRegister = regAlloc.AllocateTempGprRegister(); + Operand rn = new(tempRegister, RegisterType.Integer, OperandType.I64); + + WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, rn, targetAddress); + + switch (name) + { + case InstName.LdrLitGen: + case InstName.LdrLitFpsimd: + asm.LdrRiUn(rt, rn, 0); + break; + case InstName.LdrswLit: + asm.LdrswRiUn(rt, rn, 0); + break; + case InstName.PrfmLit: + asm.PrfmR(rt, rn); + break; + } + + regAlloc.FreeTempGprRegister(tempRegister); + break; + default: + Debug.Fail($"Invalid literal memory instruction '{name}'."); + break; + } + } + + private static void RewriteStructPostIndexedRegMemoryInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, uint encoding) + { + // TODO: Some unallocated encoding cases. + + int tempRegister = regAlloc.AllocateTempGprRegister(); + Operand rn = new(tempRegister, RegisterType.Integer, OperandType.I64); + Operand guestAddress = new(RegisterUtils.ExtractRn(encoding), RegisterType.Integer, OperandType.I64); + + int rmIndex = RegisterUtils.ExtractRm(encoding); + + Assembler asm = new(writer); + + WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, rn, guestAddress); + + encoding = RegisterUtils.ReplaceRn(encoding, tempRegister); + encoding &= ~((0x1fu << 16) | (1u << 23)); // Post-index -> No offset + + writer.WriteInstruction(encoding); + + if (rmIndex == RegisterUtils.ZrIndex) + { + bool isSingleStruct = (encoding & (1u << 24)) != 0; + int offset; + + if (isSingleStruct) + { + int sElems = (int)(((encoding >> 12) & 2u) | ((encoding >> 21) & 1u)) + 1; + + int size = (int)(encoding >> 10) & 3; + int s = (int)(encoding >> 12) & 1; + int scale = (int)(encoding >> 14) & 3; + int l = (int)(encoding >> 22) & 1; + + switch (scale) + { + case 1: + if ((size & 1) != 0) + { + // Undef. + } + + break; + + case 2: + if ((size & 2) != 0 || + ((size & 1) != 0 && s != 0)) + { + // Undef. + } + + if ((size & 1) != 0) + { + scale = 3; + } + + break; + + case 3: + if (l == 0 || s != 0) + { + // Undef. + } + + scale = size; + + break; + } + + int eBytes = 1 << scale; + + offset = eBytes * sElems; + } + else + { + int reps; + int sElems; + + switch ((encoding >> 12) & 0xf) + { + case 0b0000: + reps = 1; + sElems = 4; + break; + case 0b0010: + reps = 4; + sElems = 1; + break; + case 0b0100: + reps = 1; + sElems = 3; + break; + case 0b0110: + reps = 3; + sElems = 1; + break; + case 0b0111: + reps = 1; + sElems = 1; + break; + case 0b1000: + reps = 1; + sElems = 2; + break; + case 0b1010: + reps = 2; + sElems = 1; + break; + + default: + // Undef. + reps = 0; + sElems = 0; + break; + } + + int size = (int)(encoding >> 10) & 3; + bool q = (encoding & (1u << 30)) != 0; + + if (!q && size == 3 && sElems != 1) + { + // Undef. + } + + offset = reps * (q ? 16 : 8) * sElems; + } + + asm.Add(guestAddress, guestAddress, new Operand(OperandKind.Constant, OperandType.I32, (ulong)offset)); + } + else + { + Operand guestOffset = new(rmIndex, RegisterType.Integer, OperandType.I64); + + asm.Add(guestAddress, guestAddress, guestOffset); + } + + regAlloc.FreeTempGprRegister(tempRegister); + } + + private static void WriteAddressTranslation( + int asBits, + MemoryManagerType mmType, + RegisterAllocator regAlloc, + ref Assembler asm, + Operand destination, + Operand guestAddress, + int offset) + { + if (offset != 0) + { + // They are assumed to be on different registers, otherwise this operation will thrash the address. + Debug.Assert(destination.Value != guestAddress.Value); + + if (Math.Abs(offset) >= 0x1000) + { + // Too high to encode as 12-bit immediate, do a separate move. + asm.Mov(destination, (ulong)offset); + asm.Add(destination, destination, guestAddress); + } + else + { + // Encode as 12-bit immediate. + WriteAddConstant(ref asm, destination, guestAddress, offset); + } + + guestAddress = destination; + } + + WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, destination, guestAddress); + } + + private static void WriteAddressTranslation( + int asBits, + MemoryManagerType mmType, + RegisterAllocator regAlloc, + ref Assembler asm, + Operand destination, + ulong guestAddress) + { + asm.Mov(destination, guestAddress); + + WriteAddressTranslation(asBits, mmType, regAlloc, ref asm, destination, destination); + } + + private static void WriteAddressTranslation( + int asBits, + MemoryManagerType mmType, + RegisterAllocator regAlloc, + ref Assembler asm, + Operand destination, + Operand guestAddress) + { + Operand basePointer = new(regAlloc.FixedPageTableRegister, RegisterType.Integer, OperandType.I64); + + if (mmType.IsHostTracked()) + { + int tempRegister = regAlloc.AllocateTempGprRegister(); + + Operand pte = new(tempRegister, RegisterType.Integer, OperandType.I64); + + asm.Lsr(pte, guestAddress, new Operand(OperandKind.Constant, OperandType.I32, 12)); + + if (mmType == MemoryManagerType.HostTracked) + { + asm.And(pte, pte, new Operand(OperandKind.Constant, OperandType.I64, ulong.MaxValue >> (64 - (asBits - 12)))); + } + + asm.LdrRr(pte, basePointer, pte, ArmExtensionType.Uxtx, true); + asm.Add(destination, pte, guestAddress); + + regAlloc.FreeTempGprRegister(tempRegister); + } + else if (mmType.IsHostMapped()) + { + if (mmType == MemoryManagerType.HostMapped) + { + asm.And(destination, guestAddress, new Operand(OperandKind.Constant, OperandType.I64, ulong.MaxValue >> (64 - asBits))); + guestAddress = destination; + } + + asm.Add(destination, basePointer, guestAddress); + } + else + { + throw new NotImplementedException(mmType.ToString()); + } + } + + private static void WriteAddConstant(ref Assembler asm, Operand rd, Operand rn, int value) + { + if (value < 0) + { + asm.Sub(rd, rn, new Operand(OperandKind.Constant, OperandType.I32, (ulong)-value)); + } + else + { + asm.Add(rd, rn, new Operand(OperandKind.Constant, OperandType.I32, (ulong)value)); + } + } + + private static bool CanFoldOffset(MemoryManagerType mmType, int offset) + { + return mmType == MemoryManagerType.HostMappedUnsafe; + } + + private static int ExtractSImm7Scaled(InstFlags flags, uint encoding) + { + uint opc = flags.HasFlag(InstFlags.FpSimd) ? encoding >> 30 : encoding >> 31; + return ExtractSImm7(encoding) << (int)(2 + opc); + } + + private static int ExtractSImm7(uint encoding) + { + int imm = (int)(encoding >> 15); + + imm <<= 25; + imm >>= 25; + + return imm; + } + + private static int ExtractSImm9(uint encoding) + { + int imm = (int)(encoding >> 12); + + imm <<= 23; + imm >>= 23; + + return imm; + } + + private static int ExtractUImm12Scaled(InstFlags flags, uint encoding) + { + uint size = encoding >> 30; + + if (flags.HasFlag(InstFlags.FpSimd)) + { + size |= (encoding >> 21) & 4u; + } + + return ExtractUImm12(encoding) << (int)size; + } + + private static int ExtractUImm12(uint encoding) + { + return (int)(encoding >> 10) & 0xfff; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs new file mode 100644 index 00000000..82cb29d7 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstEmitSystem.cs @@ -0,0 +1,617 @@ +using ARMeilleure.Common; +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 +{ + static class InstEmitSystem + { + private delegate void SoftwareInterruptHandler(ulong address, int imm); + private delegate ulong Get64(); + private delegate bool GetBool(); + + public static void RewriteInstruction( + CodeWriter writer, + RegisterAllocator regAlloc, + TailMerger tailMerger, + InstName name, + ulong pc, + uint encoding, + int spillBaseOffset) + { + if (name == InstName.Brk) + { + Assembler asm = new(writer); + + WriteCall(ref asm, regAlloc, GetBrkHandlerPtr(), spillBaseOffset, null, pc, encoding); + WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset); + } + else if (name == InstName.Svc) + { + uint svcId = (ushort)(encoding >> 5); + + Assembler asm = new(writer); + + WriteCall(ref asm, regAlloc, GetSvcHandlerPtr(), spillBaseOffset, null, pc, svcId); + WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset); + } + else if (name == InstName.UdfPermUndef) + { + Assembler asm = new(writer); + + WriteCall(ref asm, regAlloc, GetUdfHandlerPtr(), spillBaseOffset, null, pc, encoding); + WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset); + } + else if ((encoding & ~0x1f) == 0xd53bd060) // mrs x0, tpidrro_el0 + { + uint rd = encoding & 0x1f; + + if (rd != RegisterUtils.ZrIndex) + { + Assembler asm = new(writer); + + asm.LdrRiUn(Register((int)rd), Register(regAlloc.FixedContextRegister), NativeContextOffsets.TpidrroEl0Offset); + } + } + else if ((encoding & ~0x1f) == 0xd53bd040) // mrs x0, tpidr_el0 + { + uint rd = encoding & 0x1f; + + if (rd != RegisterUtils.ZrIndex) + { + Assembler asm = new(writer); + + asm.LdrRiUn(Register((int)rd), Register(regAlloc.FixedContextRegister), NativeContextOffsets.TpidrEl0Offset); + } + } + else if ((encoding & ~0x1f) == 0xd53b0020 && IsCtrEl0AccessForbidden()) // mrs x0, ctr_el0 + { + uint rd = encoding & 0x1f; + + if (rd != RegisterUtils.ZrIndex) + { + Assembler asm = new(writer); + + // TODO: Use host value? But that register can't be accessed on macOS... + asm.Mov(Register((int)rd, OperandType.I32), 0x8444c004); + } + } + else if ((encoding & ~0x1f) == 0xd53be020) // mrs x0, cntpct_el0 + { + uint rd = encoding & 0x1f; + + if (rd != RegisterUtils.ZrIndex) + { + Assembler asm = new(writer); + + WriteCall(ref asm, regAlloc, GetCntpctEl0Ptr(), spillBaseOffset, (int)rd); + } + } + else if ((encoding & ~0x1f) == 0xd51bd040) // msr tpidr_el0, x0 + { + uint rd = encoding & 0x1f; + + if (rd != RegisterUtils.ZrIndex) + { + Assembler asm = new(writer); + + asm.StrRiUn(Register((int)rd), Register(regAlloc.FixedContextRegister), NativeContextOffsets.TpidrEl0Offset); + } + } + else + { + writer.WriteInstruction(encoding); + } + } + + public static bool NeedsCall(uint encoding) + { + if ((encoding & ~(0xffffu << 5)) == 0xd4000001u) // svc #0 + { + return true; + } + else if ((encoding & ~0x1f) == 0xd53b0020 && IsCtrEl0AccessForbidden()) // mrs x0, ctr_el0 + { + return true; + } + else if ((encoding & ~0x1f) == 0xd53be020) // mrs x0, cntpct_el0 + { + return true; + } + + return false; + } + + private static bool IsCtrEl0AccessForbidden() + { + // Only Linux allows accessing CTR_EL0 from user mode. + return OperatingSystem.IsWindows() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS(); + } + + public static bool IsCacheInstForbidden(uint encoding) + { + // Windows does not allow the cache maintenance instructions to be used from user mode. + return OperatingSystem.IsWindows() && SysUtils.IsCacheInstUciTrapped(encoding); + } + + public static bool NeedsContextStoreLoad(InstName name) + { + return name == InstName.Svc; + } + + private static IntPtr GetBrkHandlerPtr() + { + return Marshal.GetFunctionPointerForDelegate(NativeInterface.Break); + } + + private static IntPtr GetSvcHandlerPtr() + { + return Marshal.GetFunctionPointerForDelegate(NativeInterface.SupervisorCall); + } + + private static IntPtr GetUdfHandlerPtr() + { + return Marshal.GetFunctionPointerForDelegate(NativeInterface.Undefined); + } + + private static IntPtr GetCntpctEl0Ptr() + { + return Marshal.GetFunctionPointerForDelegate(NativeInterface.GetCntpctEl0); + } + + private static IntPtr CheckSynchronizationPtr() + { + return Marshal.GetFunctionPointerForDelegate(NativeInterface.CheckSynchronization); + } + + public static void WriteSyncPoint(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset) + { + Assembler asm = new(writer); + + WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset); + } + + private static void WriteSyncPoint(CodeWriter writer, ref Assembler asm, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset) + { + int tempRegister = regAlloc.AllocateTempGprRegister(); + + Operand rt = Register(tempRegister, OperandType.I32); + + asm.LdrRiUn(rt, Register(regAlloc.FixedContextRegister), NativeContextOffsets.CounterOffset); + + int branchIndex = writer.InstructionPointer; + asm.Cbnz(rt, 0); + + WriteSpill(ref asm, regAlloc, 1u << tempRegister, spillBaseOffset, tempRegister); + + Operand rn = Register(tempRegister == 0 ? 1 : 0); + + asm.Mov(rn, (ulong)CheckSynchronizationPtr()); + asm.Blr(rn); + + tailMerger.AddConditionalZeroReturn(writer, asm, Register(0, OperandType.I32)); + + WriteFill(ref asm, regAlloc, 1u << tempRegister, spillBaseOffset, tempRegister); + + asm.LdrRiUn(rt, Register(regAlloc.FixedContextRegister), NativeContextOffsets.CounterOffset); + + uint branchInst = writer.ReadInstructionAt(branchIndex); + writer.WriteInstructionAt(branchIndex, branchInst | (((uint)(writer.InstructionPointer - branchIndex) & 0x7ffff) << 5)); + + asm.Sub(rt, rt, new Operand(OperandKind.Constant, OperandType.I32, 1)); + asm.StrRiUn(rt, Register(regAlloc.FixedContextRegister), NativeContextOffsets.CounterOffset); + + regAlloc.FreeTempGprRegister(tempRegister); + } + + public static void RewriteCallInstruction( + CodeWriter writer, + RegisterAllocator regAlloc, + TailMerger tailMerger, + Action writeEpilogue, + AddressTable funcTable, + IntPtr dispatchStubPtr, + InstName name, + ulong pc, + uint encoding, + int spillBaseOffset, + bool isTail = false) + { + Assembler asm = new(writer); + + switch (name) + { + case InstName.BUncond: + case InstName.Bl: + case InstName.Blr: + case InstName.Br: + if (name == InstName.BUncond || name == InstName.Bl) + { + int imm = ImmUtils.ExtractSImm26Times4(encoding); + + WriteCallWithGuestAddress( + writer, + ref asm, + regAlloc, + tailMerger, + writeEpilogue, + funcTable, + dispatchStubPtr, + spillBaseOffset, + pc, + new(OperandKind.Constant, OperandType.I64, pc + (ulong)imm), + isTail); + } + else + { + int rnIndex = RegisterUtils.ExtractRn(encoding); + if (rnIndex == RegisterUtils.ZrIndex) + { + WriteCallWithGuestAddress( + writer, + ref asm, + regAlloc, + tailMerger, + writeEpilogue, + funcTable, + dispatchStubPtr, + spillBaseOffset, + pc, + new(OperandKind.Constant, OperandType.I64, 0UL), + isTail); + } + else + { + rnIndex = regAlloc.RemapReservedGprRegister(rnIndex); + + WriteCallWithGuestAddress( + writer, + ref asm, + regAlloc, + tailMerger, + writeEpilogue, + funcTable, + dispatchStubPtr, + spillBaseOffset, + pc, + Register(rnIndex), + isTail); + } + } + break; + + default: + Debug.Fail($"Unknown branch instruction \"{name}\"."); + break; + } + } + + public unsafe static void WriteCallWithGuestAddress( + CodeWriter writer, + ref Assembler asm, + RegisterAllocator regAlloc, + TailMerger tailMerger, + Action writeEpilogue, + AddressTable funcTable, + IntPtr funcPtr, + int spillBaseOffset, + ulong pc, + Operand guestAddress, + bool isTail = false) + { + int tempRegister; + + if (guestAddress.Kind == OperandKind.Constant) + { + tempRegister = regAlloc.AllocateTempGprRegister(); + + asm.Mov(Register(tempRegister), guestAddress.Value); + asm.StrRiUn(Register(tempRegister), Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset); + + regAlloc.FreeTempGprRegister(tempRegister); + } + else + { + asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset); + } + + tempRegister = regAlloc.FixedContextRegister == 1 ? 2 : 1; + + if (!isTail) + { + WriteSpillSkipContext(ref asm, regAlloc, spillBaseOffset); + } + + Operand rn = Register(tempRegister); + + if (regAlloc.FixedContextRegister != 0) + { + asm.Mov(Register(0), Register(regAlloc.FixedContextRegister)); + } + + if (guestAddress.Kind == OperandKind.Constant && funcTable != null) + { + ulong funcPtrLoc = (ulong)Unsafe.AsPointer(ref funcTable.GetValue(guestAddress.Value)); + + asm.Mov(rn, funcPtrLoc & ~0xfffUL); + asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL)); + } + else + { + asm.Mov(rn, (ulong)funcPtr); + } + + if (isTail) + { + writeEpilogue(); + asm.Br(rn); + } + else + { + asm.Blr(rn); + + ulong nextAddress = pc + 4UL; + + asm.Mov(rn, nextAddress); + asm.Cmp(Register(0), rn); + + tailMerger.AddConditionalReturn(writer, asm, ArmCondition.Ne); + + WriteFillSkipContext(ref asm, regAlloc, spillBaseOffset); + } + } + + private static void WriteCall( + ref Assembler asm, + RegisterAllocator regAlloc, + IntPtr funcPtr, + int spillBaseOffset, + int? resultRegister, + params ulong[] callArgs) + { + uint resultMask = 0u; + + if (resultRegister.HasValue) + { + resultMask = 1u << resultRegister.Value; + } + + int tempRegister = callArgs.Length; + + if (resultRegister.HasValue && tempRegister == resultRegister.Value) + { + tempRegister++; + } + + WriteSpill(ref asm, regAlloc, resultMask, spillBaseOffset, tempRegister); + + // We only support up to 7 arguments right now. + // ABI defines the first 8 integer arguments to be passed on registers X0-X7. + // We need at least one register to put the function address on, so that reduces the number of + // registers we can use for that by one. + + Debug.Assert(callArgs.Length < 8); + + for (int index = 0; index < callArgs.Length; index++) + { + asm.Mov(Register(index), callArgs[index]); + } + + Operand rn = Register(tempRegister); + + asm.Mov(rn, (ulong)funcPtr); + asm.Blr(rn); + + if (resultRegister.HasValue && resultRegister.Value != 0) + { + asm.Mov(Register(resultRegister.Value), Register(0)); + } + + WriteFill(ref asm, regAlloc, resultMask, spillBaseOffset, tempRegister); + } + + private static void WriteSpill(ref Assembler asm, RegisterAllocator regAlloc, uint exceptMask, int spillOffset, int tempRegister) + { + WriteSpillOrFill(ref asm, regAlloc, exceptMask, spillOffset, tempRegister, spill: true); + } + + private static void WriteFill(ref Assembler asm, RegisterAllocator regAlloc, uint exceptMask, int spillOffset, int tempRegister) + { + WriteSpillOrFill(ref asm, regAlloc, exceptMask, spillOffset, tempRegister, spill: false); + } + + private static void WriteSpillOrFill( + ref Assembler asm, + RegisterAllocator regAlloc, + uint exceptMask, + int spillOffset, + int tempRegister, + bool spill) + { + uint gprMask = regAlloc.AllGprMask & ~(AbiConstants.GprCalleeSavedRegsMask | exceptMask); + + if (regAlloc.AllPStateMask != 0 && !spill) + { + // We must reload the status register before reloading the GPRs, + // since we might otherwise trash one of them by using it as temp register. + + Operand rt = Register(tempRegister, OperandType.I32); + + asm.LdrRiUn(rt, Register(RegisterUtils.SpIndex), spillOffset + BitOperations.PopCount(gprMask) * 8); + asm.MsrNzcv(rt); + } + + while (gprMask != 0) + { + int reg = BitOperations.TrailingZeroCount(gprMask); + + if (reg < 31 && (gprMask & (2u << reg)) != 0 && spillOffset < RegisterSaveRestore.Encodable9BitsOffsetLimit) + { + if (spill) + { + asm.StpRiUn( + Register(regAlloc.RemapReservedGprRegister(reg)), + Register(regAlloc.RemapReservedGprRegister(reg + 1)), + Register(RegisterUtils.SpIndex), + spillOffset); + } + else + { + asm.LdpRiUn( + Register(regAlloc.RemapReservedGprRegister(reg)), + Register(regAlloc.RemapReservedGprRegister(reg + 1)), + Register(RegisterUtils.SpIndex), + spillOffset); + } + + gprMask &= ~(3u << reg); + spillOffset += 16; + } + else + { + if (spill) + { + asm.StrRiUn(Register(regAlloc.RemapReservedGprRegister(reg)), Register(RegisterUtils.SpIndex), spillOffset); + } + else + { + asm.LdrRiUn(Register(regAlloc.RemapReservedGprRegister(reg)), Register(RegisterUtils.SpIndex), spillOffset); + } + + gprMask &= ~(1u << reg); + spillOffset += 8; + } + } + + if (regAlloc.AllPStateMask != 0) + { + if (spill) + { + Operand rt = Register(tempRegister, OperandType.I32); + + asm.MrsNzcv(rt); + asm.StrRiUn(rt, Register(RegisterUtils.SpIndex), spillOffset); + } + + spillOffset += 8; + } + + if ((spillOffset & 8) != 0) + { + spillOffset += 8; + } + + uint fpSimdMask = regAlloc.AllFpSimdMask; + + while (fpSimdMask != 0) + { + int reg = BitOperations.TrailingZeroCount(fpSimdMask); + + if (reg < 31 && (fpSimdMask & (2u << reg)) != 0 && spillOffset < RegisterSaveRestore.Encodable9BitsOffsetLimit) + { + if (spill) + { + asm.StpRiUn( + Register(reg, OperandType.V128), + Register(reg + 1, OperandType.V128), + Register(RegisterUtils.SpIndex), + spillOffset); + } + else + { + asm.LdpRiUn( + Register(reg, OperandType.V128), + Register(reg + 1, OperandType.V128), + Register(RegisterUtils.SpIndex), + spillOffset); + } + + fpSimdMask &= ~(3u << reg); + spillOffset += 32; + } + else + { + if (spill) + { + asm.StrRiUn(Register(reg, OperandType.V128), Register(RegisterUtils.SpIndex), spillOffset); + } + else + { + asm.LdrRiUn(Register(reg, OperandType.V128), Register(RegisterUtils.SpIndex), spillOffset); + } + + fpSimdMask &= ~(1u << reg); + spillOffset += 16; + } + } + } + + private static void WriteSpillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset) + { + WriteSpillOrFillSkipContext(ref asm, regAlloc, spillOffset, spill: true); + } + + private static void WriteFillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset) + { + WriteSpillOrFillSkipContext(ref asm, regAlloc, spillOffset, spill: false); + } + + private static void WriteSpillOrFillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset, bool spill) + { + uint gprMask = regAlloc.AllGprMask & ((1u << regAlloc.FixedContextRegister) | (1u << regAlloc.FixedPageTableRegister)); + gprMask &= ~AbiConstants.GprCalleeSavedRegsMask; + + while (gprMask != 0) + { + int reg = BitOperations.TrailingZeroCount(gprMask); + + if (reg < 31 && (gprMask & (2u << reg)) != 0 && spillOffset < RegisterSaveRestore.Encodable9BitsOffsetLimit) + { + if (spill) + { + asm.StpRiUn( + Register(regAlloc.RemapReservedGprRegister(reg)), + Register(regAlloc.RemapReservedGprRegister(reg + 1)), + Register(RegisterUtils.SpIndex), + spillOffset); + } + else + { + asm.LdpRiUn( + Register(regAlloc.RemapReservedGprRegister(reg)), + Register(regAlloc.RemapReservedGprRegister(reg + 1)), + Register(RegisterUtils.SpIndex), + spillOffset); + } + + gprMask &= ~(3u << reg); + spillOffset += 16; + } + else + { + if (spill) + { + asm.StrRiUn(Register(regAlloc.RemapReservedGprRegister(reg)), Register(RegisterUtils.SpIndex), spillOffset); + } + else + { + asm.LdrRiUn(Register(regAlloc.RemapReservedGprRegister(reg)), Register(RegisterUtils.SpIndex), spillOffset); + } + + gprMask &= ~(1u << reg); + spillOffset += 8; + } + } + } + + private static Operand Register(int register, OperandType type = OperandType.I64) + { + return new Operand(register, RegisterType.Integer, type); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstTable.cs b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstTable.cs new file mode 100644 index 00000000..88718afb --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Arm64/Target/Arm64/InstTable.cs @@ -0,0 +1,1605 @@ +using Ryujinx.Cpu.LightningJit.Table; +using System.Collections.Generic; + +namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64 +{ + static class InstTable + { + private static readonly InstTableLevel _table; + + private readonly struct InstInfo : IInstInfo + { + public uint Encoding { get; } + public uint EncodingMask { get; } + public InstEncoding[] Constraints { get; } + public InstName Name { get; } + public IsaVersion Version { get; } + public IsaFeature Feature { get; } + public InstFlags Flags { get; } + public AddressForm AddressForm { get; } + + public InstInfo( + uint encoding, + uint encodingMask, + InstEncoding[] constraints, + InstName name, + IsaVersion isaVersion, + IsaFeature isaFeature, + InstFlags flags, + AddressForm addressForm = AddressForm.None) + { + if (addressForm != AddressForm.None) + { + flags |= InstFlags.Memory; + } + + Encoding = encoding; + EncodingMask = encodingMask; + Constraints = constraints; + Name = name; + Version = isaVersion; + Feature = isaFeature; + Flags = flags; + AddressForm = addressForm; + } + + public InstInfo( + uint encoding, + uint encodingMask, + InstEncoding[] constraints, + InstName name, + IsaVersion isaVersion, + InstFlags flags, + AddressForm addressForm = AddressForm.None) : this(encoding, encodingMask, constraints, name, isaVersion, IsaFeature.None, flags, addressForm) + { + } + + public InstInfo( + uint encoding, + uint encodingMask, + InstName name, + IsaVersion isaVersion, + IsaFeature isaFeature, + InstFlags flags, + AddressForm addressForm = AddressForm.None) : this(encoding, encodingMask, null, name, isaVersion, isaFeature, flags, addressForm) + { + } + + public InstInfo( + uint encoding, + uint encodingMask, + InstName name, + IsaVersion isaVersion, + InstFlags flags, + AddressForm addressForm = AddressForm.None) : this(encoding, encodingMask, null, name, isaVersion, IsaFeature.None, flags, addressForm) + { + } + + public bool IsConstrained(uint encoding) + { + if (Constraints != null) + { + foreach (InstEncoding constraint in Constraints) + { + if ((encoding & constraint.EncodingMask) == constraint.Encoding) + { + return true; + } + } + } + + return false; + } + } + + static InstTable() + { + InstEncoding[] qsizeConstraints = new InstEncoding[] + { + new(0x00C00000, 0x40C00000), + }; + + InstEncoding[] sizeConstraints = new InstEncoding[] + { + new(0x00C00000, 0x00C00000), + }; + + InstEncoding[] opuOpuOpuConstraints = new InstEncoding[] + { + new(0x00001400, 0x00001C00), + new(0x00001800, 0x00001C00), + new(0x00001C00, 0x00001C00), + }; + + InstEncoding[] shiftSfimm6Constraints = new InstEncoding[] + { + new(0x00C00000, 0x00C00000), + new(0x00008000, 0x80008000), + }; + + InstEncoding[] qsizeSizeConstraints = new InstEncoding[] + { + new(0x00800000, 0x40C00000), + new(0x00C00000, 0x00C00000), + }; + + InstEncoding[] nimmsNimmsNimmsNimmsNimmsNimmsNimmsNimmsSfnConstraints = new InstEncoding[] + { + new(0x0040FC00, 0x0040FC00), + new(0x00007C00, 0x0040FC00), + new(0x0000BC00, 0x0040FC00), + new(0x0000DC00, 0x0040FC00), + new(0x0000EC00, 0x0040FC00), + new(0x0000F400, 0x0040FC00), + new(0x0000F800, 0x0040FC00), + new(0x0000FC00, 0x0040FC00), + new(0x00400000, 0x80400000), + }; + + InstEncoding[] sfimm6Constraints = new InstEncoding[] + { + new(0x00008000, 0x80008000), + }; + + InstEncoding[] sfnSfnSfimmr5Sfimms5Constraints = new InstEncoding[] + { + new(0x80000000, 0x80400000), + new(0x00400000, 0x80400000), + new(0x00200000, 0x80200000), + new(0x00008000, 0x80008000), + }; + + InstEncoding[] cmodeopqConstraints = new InstEncoding[] + { + new(0x2000F000, 0x6000F000), + }; + + InstEncoding[] rsRtConstraints = new InstEncoding[] + { + new(0x00010000, 0x00010000), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] sfszSfszSfszSfszConstraints = new InstEncoding[] + { + new(0x80000000, 0x80000C00), + new(0x80000400, 0x80000C00), + new(0x80000800, 0x80000C00), + new(0x00000C00, 0x80000C00), + }; + + InstEncoding[] imm5Constraints = new InstEncoding[] + { + new(0x00000000, 0x000F0000), + }; + + InstEncoding[] imm5Imm5qConstraints = new InstEncoding[] + { + new(0x00000000, 0x000F0000), + new(0x00080000, 0x400F0000), + }; + + InstEncoding[] nsfNsfSfimmsConstraints = new InstEncoding[] + { + new(0x00400000, 0x80400000), + new(0x80000000, 0x80400000), + new(0x00008000, 0x80008000), + }; + + InstEncoding[] qimm4Constraints = new InstEncoding[] + { + new(0x00004000, 0x40004000), + }; + + InstEncoding[] qszConstraints = new InstEncoding[] + { + new(0x00400000, 0x40400000), + }; + + InstEncoding[] euacEuacEuacConstraints = new InstEncoding[] + { + new(0x00000800, 0x20800800), + new(0x00800000, 0x20800800), + new(0x00800800, 0x20800800), + }; + + InstEncoding[] qszEuacEuacEuacConstraints = new InstEncoding[] + { + new(0x00400000, 0x40400000), + new(0x00000800, 0x20800800), + new(0x00800000, 0x20800800), + new(0x00800800, 0x20800800), + }; + + InstEncoding[] szConstraints = new InstEncoding[] + { + new(0x00400000, 0x00400000), + }; + + InstEncoding[] sizeQsizeConstraints = new InstEncoding[] + { + new(0x00000000, 0x00C00000), + new(0x00C00000, 0x40C00000), + }; + + InstEncoding[] sizeSizeSizelSizeqSizehqConstraints = new InstEncoding[] + { + new(0x00000000, 0x00C00000), + new(0x00C00000, 0x00C00000), + new(0x00A00000, 0x00E00000), + new(0x00800000, 0x40C00000), + new(0x00400800, 0x40C00800), + }; + + InstEncoding[] szConstraints2 = new InstEncoding[] + { + new(0x00000000, 0x00400000), + }; + + InstEncoding[] immhConstraints = new InstEncoding[] + { + new(0x00000000, 0x00780000), + }; + + InstEncoding[] immhQimmhConstraints = new InstEncoding[] + { + new(0x00000000, 0x00780000), + new(0x00400000, 0x40400000), + }; + + InstEncoding[] sfscaleConstraints = new InstEncoding[] + { + new(0x00000000, 0x80008000), + }; + + InstEncoding[] ftypeopcFtypeopcFtypeopcFtypeopcFtypeOpcConstraints = new InstEncoding[] + { + new(0x00000000, 0x00C18000), + new(0x00408000, 0x00C18000), + new(0x00810000, 0x00C18000), + new(0x00C18000, 0x00C18000), + new(0x00800000, 0x00C00000), + new(0x00010000, 0x00018000), + }; + + InstEncoding[] szlConstraints = new InstEncoding[] + { + new(0x00600000, 0x00600000), + }; + + InstEncoding[] szlQszConstraints = new InstEncoding[] + { + new(0x00600000, 0x00600000), + new(0x00400000, 0x40400000), + }; + + InstEncoding[] qConstraints = new InstEncoding[] + { + new(0x00000000, 0x40000000), + }; + + InstEncoding[] sfftypermodeSfftypermodeConstraints = new InstEncoding[] + { + new(0x00400000, 0x80C80000), + new(0x80000000, 0x80C80000), + }; + + InstEncoding[] uo1o2Constraints = new InstEncoding[] + { + new(0x20800000, 0x20801000), + }; + + InstEncoding[] qszUo1o2Constraints = new InstEncoding[] + { + new(0x00400000, 0x40400000), + new(0x20800000, 0x20801000), + }; + + InstEncoding[] sConstraints = new InstEncoding[] + { + new(0x00001000, 0x00001000), + }; + + InstEncoding[] opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints = new InstEncoding[] + { + new(0x00004400, 0x0000C400), + new(0x00008800, 0x0000C800), + new(0x00009400, 0x0000D400), + new(0x0000C000, 0x0000C000), + }; + + InstEncoding[] qsizeConstraints2 = new InstEncoding[] + { + new(0x00000C00, 0x40000C00), + }; + + InstEncoding[] rtRtConstraints = new InstEncoding[] + { + new(0x00000018, 0x00000018), + new(0x00000001, 0x00000001), + }; + + InstEncoding[] opc1sizeOpc1sizeOpc1sizeConstraints = new InstEncoding[] + { + new(0x40800000, 0xC0800000), + new(0x80800000, 0xC0800000), + new(0xC0800000, 0xC0800000), + }; + + InstEncoding[] rtRt2Constraints = new InstEncoding[] + { + new(0x0000001F, 0x0000001F), + new(0x001F0000, 0x001F0000), + }; + + InstEncoding[] opcConstraints = new InstEncoding[] + { + new(0xC0000000, 0xC0000000), + }; + + InstEncoding[] opcConstraints2 = new InstEncoding[] + { + new(0x40000000, 0x40000000), + }; + + InstEncoding[] opclOpcConstraints = new InstEncoding[] + { + new(0x40000000, 0x40400000), + new(0xC0000000, 0xC0000000), + }; + + InstEncoding[] optionConstraints = new InstEncoding[] + { + new(0x00000000, 0x00004000), + }; + + InstEncoding[] opc1sizeOpc1sizeOpc1sizeOptionConstraints = new InstEncoding[] + { + new(0x40800000, 0xC0800000), + new(0x80800000, 0xC0800000), + new(0xC0800000, 0xC0800000), + new(0x00000000, 0x00004000), + }; + + InstEncoding[] sizeSizeConstraints = new InstEncoding[] + { + new(0x00000000, 0x00C00000), + new(0x00C00000, 0x00C00000), + }; + + InstEncoding[] sfhwConstraints = new InstEncoding[] + { + new(0x00400000, 0x80400000), + }; + + InstEncoding[] rtConstraints = new InstEncoding[] + { + new(0x00000001, 0x00000001), + }; + + InstEncoding[] usizeUsizeUsizeSizeConstraints = new InstEncoding[] + { + new(0x20400000, 0x20C00000), + new(0x20800000, 0x20C00000), + new(0x20C00000, 0x20C00000), + new(0x00C00000, 0x00C00000), + }; + + InstEncoding[] sizeSizeConstraints2 = new InstEncoding[] + { + new(0x00400000, 0x00C00000), + new(0x00800000, 0x00C00000), + }; + + InstEncoding[] rtConstraints2 = new InstEncoding[] + { + new(0x00000018, 0x00000018), + }; + + InstEncoding[] sfopcConstraints = new InstEncoding[] + { + new(0x00000400, 0x80000400), + }; + + InstEncoding[] sizeSizeSizeConstraints = new InstEncoding[] + { + new(0x00400000, 0x00C00000), + new(0x00800000, 0x00C00000), + new(0x00C00000, 0x00C00000), + }; + + InstEncoding[] sizeSizeConstraints3 = new InstEncoding[] + { + new(0x00800000, 0x00C00000), + new(0x00C00000, 0x00C00000), + }; + + InstEncoding[] sfConstraints = new InstEncoding[] + { + new(0x00000000, 0x80000000), + }; + + InstEncoding[] immhImmhConstraints = new InstEncoding[] + { + new(0x00000000, 0x00780000), + new(0x00400000, 0x00400000), + }; + + InstEncoding[] sizeSizeConstraints4 = new InstEncoding[] + { + new(0x00C00000, 0x00C00000), + new(0x00000000, 0x00C00000), + }; + + InstEncoding[] ssizeSsizeSsizeConstraints = new InstEncoding[] + { + new(0x00000000, 0x00C00800), + new(0x00400000, 0x00C00800), + new(0x00800000, 0x00C00800), + }; + + InstEncoding[] immhOpuConstraints = new InstEncoding[] + { + new(0x00000000, 0x00780000), + new(0x00000000, 0x20001000), + }; + + InstEncoding[] immhQimmhOpuConstraints = new InstEncoding[] + { + new(0x00000000, 0x00780000), + new(0x00400000, 0x40400000), + new(0x00000000, 0x20001000), + }; + + List insts = new() + { + new(0x5AC02000, 0x7FFFFC00, InstName.Abs, IsaVersion.v89, InstFlags.RdRn), + new(0x5EE0B800, 0xFFFFFC00, InstName.AbsAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E20B800, 0xBF3FFC00, qsizeConstraints, InstName.AbsAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1A000000, 0x7FE0FC00, InstName.Adc, IsaVersion.v80, InstFlags.RdRnRmC), + new(0x3A000000, 0x7FE0FC00, InstName.Adcs, IsaVersion.v80, InstFlags.RdRnRmCS), + new(0x91800000, 0xFFC0C000, InstName.Addg, IsaVersion.v85, InstFlags.None), + new(0x0E204000, 0xBF20FC00, sizeConstraints, InstName.AddhnAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x5EF1B800, 0xFFFFFC00, InstName.AddpAdvsimdPair, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E20BC00, 0xBF20FC00, qsizeConstraints, InstName.AddpAdvsimdVec, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2B200000, 0x7FE00000, opuOpuOpuConstraints, InstName.AddsAddsubExt, IsaVersion.v80, InstFlags.RdRnSPRmS), + new(0x31000000, 0x7F800000, InstName.AddsAddsubImm, IsaVersion.v80, InstFlags.RdRnSPS), + new(0x2B000000, 0x7F200000, shiftSfimm6Constraints, InstName.AddsAddsubShift, IsaVersion.v80, InstFlags.RdRnRmS), + new(0x0E31B800, 0xBF3FFC00, qsizeSizeConstraints, InstName.AddvAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0B200000, 0x7FE00000, opuOpuOpuConstraints, InstName.AddAddsubExt, IsaVersion.v80, InstFlags.RdSPRnSPRm), + new(0x11000000, 0x7F800000, InstName.AddAddsubImm, IsaVersion.v80, InstFlags.RdSPRnSP), + new(0x0B000000, 0x7F200000, shiftSfimm6Constraints, InstName.AddAddsubShift, IsaVersion.v80, InstFlags.RdRnRm), + new(0x5EE08400, 0xFFE0FC00, InstName.AddAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E208400, 0xBF20FC00, qsizeConstraints, InstName.AddAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x10000000, 0x9F000000, InstName.Adr, IsaVersion.v80, InstFlags.Rd, AddressForm.Literal), + new(0x90000000, 0x9F000000, InstName.Adrp, IsaVersion.v80, InstFlags.Rd, AddressForm.Literal), + new(0x4E285800, 0xFFFFFC00, InstName.AesdAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x4E284800, 0xFFFFFC00, InstName.AeseAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x4E287800, 0xFFFFFC00, InstName.AesimcAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x4E286800, 0xFFFFFC00, InstName.AesmcAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x72000000, 0x7F800000, nimmsNimmsNimmsNimmsNimmsNimmsNimmsNimmsSfnConstraints, InstName.AndsLogImm, IsaVersion.v80, InstFlags.RdRnS), + new(0x6A000000, 0x7F200000, sfimm6Constraints, InstName.AndsLogShift, IsaVersion.v80, InstFlags.RdRnRmS), + new(0x0E201C00, 0xBFE0FC00, InstName.AndAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x12000000, 0x7F800000, nimmsNimmsNimmsNimmsNimmsNimmsNimmsNimmsSfnConstraints, InstName.AndLogImm, IsaVersion.v80, InstFlags.RdSPRn), + new(0x0A000000, 0x7F200000, sfimm6Constraints, InstName.AndLogShift, IsaVersion.v80, InstFlags.RdRnRm), + new(0x1AC02800, 0x7FE0FC00, InstName.Asrv, IsaVersion.v80, InstFlags.RdRnRm), + new(0xDAC11800, 0xFFFFDC00, InstName.Autda, IsaVersion.v83, InstFlags.RdRnSP), + new(0xDAC11C00, 0xFFFFDC00, InstName.Autdb, IsaVersion.v83, InstFlags.RdRnSP), + new(0xDAC11000, 0xFFFFDC00, InstName.AutiaGeneral, IsaVersion.v83, InstFlags.RdRnSP), + new(0xD503219F, 0xFFFFFDDF, InstName.AutiaSystem, IsaVersion.v83, InstFlags.None), + new(0xDAC11400, 0xFFFFDC00, InstName.AutibGeneral, IsaVersion.v83, InstFlags.RdRnSP), + new(0xD50321DF, 0xFFFFFDDF, InstName.AutibSystem, IsaVersion.v83, InstFlags.None), + new(0xD500405F, 0xFFFFFFFF, InstName.Axflag, IsaVersion.v85, InstFlags.C), + new(0xCE200000, 0xFFE08000, InstName.BcaxAdvsimd, IsaVersion.v82, InstFlags.RdRnRmRaFpSimd), + new(0x54000010, 0xFF000010, InstName.BcCond, IsaVersion.v88, InstFlags.Nzcv), + new(0x0EA16800, 0xBFFFFC00, InstName.BfcvtnAdvsimd, IsaVersion.v86, InstFlags.RdRnFpSimd), + new(0x1E634000, 0xFFFFFC00, InstName.BfcvtFloat, IsaVersion.v86, InstFlags.RdRnFpSimd), + new(0x0F40F000, 0xBFC0F400, InstName.BfdotAdvsimdElt, IsaVersion.v86, InstFlags.RdReadRdRnRmFpSimd), + new(0x2E40FC00, 0xBFE0FC00, InstName.BfdotAdvsimdVec, IsaVersion.v86, InstFlags.RdReadRdRnRmFpSimd), + new(0x33000000, 0x7F800000, sfnSfnSfimmr5Sfimms5Constraints, InstName.Bfm, IsaVersion.v80, InstFlags.RdReadRdRn), + new(0x0FC0F000, 0xBFC0F400, InstName.BfmlalAdvsimdElt, IsaVersion.v86, InstFlags.RdRnRmFpSimd), + new(0x2EC0FC00, 0xBFE0FC00, InstName.BfmlalAdvsimdVec, IsaVersion.v86, InstFlags.RdRnRmFpSimd), + new(0x6E40EC00, 0xFFE0FC00, InstName.BfmmlaAdvsimd, IsaVersion.v86, InstFlags.RdRnRmFpSimd), + new(0x6A200000, 0x7F200000, sfimm6Constraints, InstName.Bics, IsaVersion.v80, InstFlags.RdRnRmS), + new(0x2F001400, 0xBFF81C00, cmodeopqConstraints, InstName.BicAdvsimdImm, IsaVersion.v80, InstFlags.RdFpSimd), + new(0x0E601C00, 0xBFE0FC00, InstName.BicAdvsimdReg, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0A200000, 0x7F200000, sfimm6Constraints, InstName.BicLogShift, IsaVersion.v80, InstFlags.RdRnRm), + new(0x2EE01C00, 0xBFE0FC00, InstName.BifAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x2EA01C00, 0xBFE0FC00, InstName.BitAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x94000000, 0xFC000000, InstName.Bl, IsaVersion.v80, InstFlags.None), + new(0xD63F0000, 0xFFFFFC1F, InstName.Blr, IsaVersion.v80, InstFlags.Rn), + new(0xD63F0800, 0xFEFFF800, InstName.Blra, IsaVersion.v83, InstFlags.RnRm), + new(0xD61F0000, 0xFFFFFC1F, InstName.Br, IsaVersion.v80, InstFlags.Rn), + new(0xD61F0800, 0xFEFFF800, InstName.Bra, IsaVersion.v83, InstFlags.RnRm), + new(0xD4200000, 0xFFE0001F, InstName.Brk, IsaVersion.v80, InstFlags.None), + new(0x2E601C00, 0xBFE0FC00, InstName.BslAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0xD503241F, 0xFFFFFF3F, InstName.Bti, IsaVersion.v85, InstFlags.None), + new(0x54000000, 0xFF000010, InstName.BCond, IsaVersion.v80, InstFlags.Nzcv), + new(0x14000000, 0xFC000000, InstName.BUncond, IsaVersion.v80, InstFlags.None), + new(0x88A07C00, 0xBFA07C00, InstName.Cas, IsaVersion.v81, InstFlags.RtReadRtRnSPRs, AddressForm.BaseRegister), + new(0x08A07C00, 0xFFA07C00, InstName.Casb, IsaVersion.v81, InstFlags.RtReadRtRnSPRs, AddressForm.BaseRegister), + new(0x48A07C00, 0xFFA07C00, InstName.Cash, IsaVersion.v81, InstFlags.RtReadRtRnSPRs, AddressForm.BaseRegister), + new(0x08207C00, 0xBFA07C00, rsRtConstraints, InstName.Casp, IsaVersion.v81, InstFlags.RtReadRtRnSPRs, AddressForm.BaseRegister), + new(0x35000000, 0x7F000000, InstName.Cbnz, IsaVersion.v80, InstFlags.RtReadRt), + new(0x34000000, 0x7F000000, InstName.Cbz, IsaVersion.v80, InstFlags.RtReadRt), + new(0x3A400800, 0x7FE00C10, InstName.CcmnImm, IsaVersion.v80, InstFlags.RnNzcvS), + new(0x3A400000, 0x7FE00C10, InstName.CcmnReg, IsaVersion.v80, InstFlags.RnRmNzcvS), + new(0x7A400800, 0x7FE00C10, InstName.CcmpImm, IsaVersion.v80, InstFlags.RnNzcvS), + new(0x7A400000, 0x7FE00C10, InstName.CcmpReg, IsaVersion.v80, InstFlags.RnRmNzcvS), + new(0xD500401F, 0xFFFFFFFF, InstName.Cfinv, IsaVersion.v84, InstFlags.C), + new(0xD503251F, 0xFFFFFFFF, InstName.Chkfeat, IsaVersion.None, InstFlags.None), + new(0xD50322DF, 0xFFFFFFFF, InstName.Clrbhb, IsaVersion.v89, InstFlags.None), + new(0xD503305F, 0xFFFFF0FF, InstName.Clrex, IsaVersion.v80, InstFlags.None), + new(0x0E204800, 0xBF3FFC00, sizeConstraints, InstName.ClsAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5AC01400, 0x7FFFFC00, InstName.ClsInt, IsaVersion.v80, InstFlags.RdRn), + new(0x2E204800, 0xBF3FFC00, sizeConstraints, InstName.ClzAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5AC01000, 0x7FFFFC00, InstName.ClzInt, IsaVersion.v80, InstFlags.RdRn), + new(0x7EE08C00, 0xFFE0FC00, InstName.CmeqAdvsimdRegS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E208C00, 0xBF20FC00, qsizeConstraints, InstName.CmeqAdvsimdRegV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5EE09800, 0xFFFFFC00, InstName.CmeqAdvsimdZeroS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E209800, 0xBF3FFC00, qsizeConstraints, InstName.CmeqAdvsimdZeroV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5EE03C00, 0xFFE0FC00, InstName.CmgeAdvsimdRegS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E203C00, 0xBF20FC00, qsizeConstraints, InstName.CmgeAdvsimdRegV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x7EE08800, 0xFFFFFC00, InstName.CmgeAdvsimdZeroS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E208800, 0xBF3FFC00, qsizeConstraints, InstName.CmgeAdvsimdZeroV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5EE03400, 0xFFE0FC00, InstName.CmgtAdvsimdRegS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E203400, 0xBF20FC00, qsizeConstraints, InstName.CmgtAdvsimdRegV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5EE08800, 0xFFFFFC00, InstName.CmgtAdvsimdZeroS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E208800, 0xBF3FFC00, qsizeConstraints, InstName.CmgtAdvsimdZeroV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x7EE03400, 0xFFE0FC00, InstName.CmhiAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E203400, 0xBF20FC00, qsizeConstraints, InstName.CmhiAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x7EE03C00, 0xFFE0FC00, InstName.CmhsAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E203C00, 0xBF20FC00, qsizeConstraints, InstName.CmhsAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x7EE09800, 0xFFFFFC00, InstName.CmleAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E209800, 0xBF3FFC00, qsizeConstraints, InstName.CmleAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5EE0A800, 0xFFFFFC00, InstName.CmltAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E20A800, 0xBF3FFC00, qsizeConstraints, InstName.CmltAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5EE08C00, 0xFFE0FC00, InstName.CmtstAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E208C00, 0xBF20FC00, qsizeConstraints, InstName.CmtstAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5AC01C00, 0x7FFFFC00, InstName.Cnt, IsaVersion.v89, InstFlags.RdRn), + new(0x0E205800, 0xBFFFFC00, InstName.CntAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x19000400, 0x3F20FC00, InstName.Cpyfp, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1900C400, 0x3F20FC00, InstName.Cpyfpn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x19008400, 0x3F20FC00, InstName.Cpyfprn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x19002400, 0x3F20FC00, InstName.Cpyfprt, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1900E400, 0x3F20FC00, InstName.Cpyfprtn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1900A400, 0x3F20FC00, InstName.Cpyfprtrn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x19006400, 0x3F20FC00, InstName.Cpyfprtwn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x19003400, 0x3F20FC00, InstName.Cpyfpt, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1900F400, 0x3F20FC00, InstName.Cpyfptn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1900B400, 0x3F20FC00, InstName.Cpyfptrn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x19007400, 0x3F20FC00, InstName.Cpyfptwn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x19004400, 0x3F20FC00, InstName.Cpyfpwn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x19001400, 0x3F20FC00, InstName.Cpyfpwt, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1900D400, 0x3F20FC00, InstName.Cpyfpwtn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x19009400, 0x3F20FC00, InstName.Cpyfpwtrn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x19005400, 0x3F20FC00, InstName.Cpyfpwtwn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D000400, 0x3F20FC00, InstName.Cpyp, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D00C400, 0x3F20FC00, InstName.Cpypn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D008400, 0x3F20FC00, InstName.Cpyprn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D002400, 0x3F20FC00, InstName.Cpyprt, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D00E400, 0x3F20FC00, InstName.Cpyprtn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D00A400, 0x3F20FC00, InstName.Cpyprtrn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D006400, 0x3F20FC00, InstName.Cpyprtwn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D003400, 0x3F20FC00, InstName.Cpypt, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D00F400, 0x3F20FC00, InstName.Cpyptn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D00B400, 0x3F20FC00, InstName.Cpyptrn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D007400, 0x3F20FC00, InstName.Cpyptwn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D004400, 0x3F20FC00, InstName.Cpypwn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D001400, 0x3F20FC00, InstName.Cpypwt, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D00D400, 0x3F20FC00, InstName.Cpypwtn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D009400, 0x3F20FC00, InstName.Cpypwtrn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1D005400, 0x3F20FC00, InstName.Cpypwtwn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1AC04000, 0x7FE0F000, sfszSfszSfszSfszConstraints, InstName.Crc32, IsaVersion.v80, InstFlags.RdRnRm), + new(0x1AC05000, 0x7FE0F000, sfszSfszSfszSfszConstraints, InstName.Crc32c, IsaVersion.v80, InstFlags.RdRnRm), + new(0xD503229F, 0xFFFFFFFF, InstName.Csdb, IsaVersion.v80, InstFlags.None), + new(0x1A800000, 0x7FE00C00, InstName.Csel, IsaVersion.v80, InstFlags.RdRnRmNzcv), + new(0x1A800400, 0x7FE00C00, InstName.Csinc, IsaVersion.v80, InstFlags.RdRnRmNzcv), + new(0x5A800000, 0x7FE00C00, InstName.Csinv, IsaVersion.v80, InstFlags.RdRnRmNzcv), + new(0x5A800400, 0x7FE00C00, InstName.Csneg, IsaVersion.v80, InstFlags.RdRnRmNzcv), + new(0x5AC01800, 0x7FFFFC00, InstName.Ctz, IsaVersion.v89, InstFlags.RdRn), + new(0xD4A00001, 0xFFE0001F, InstName.Dcps1, IsaVersion.v80, InstFlags.None), + new(0xD4A00002, 0xFFE0001F, InstName.Dcps2, IsaVersion.v80, InstFlags.None), + new(0xD4A00003, 0xFFE0001F, InstName.Dcps3, IsaVersion.v80, InstFlags.None), + new(0xD50320DF, 0xFFFFFFFF, InstName.Dgh, IsaVersion.v84, InstFlags.None), + new(0xD50330BF, 0xFFFFF0FF, InstName.Dmb, IsaVersion.v80, InstFlags.None), + new(0xD6BF03E0, 0xFFFFFFFF, InstName.Drps, IsaVersion.v80, InstFlags.None), + new(0xD503309F, 0xFFFFF0FF, InstName.DsbDsbMemory, IsaVersion.v80, InstFlags.None), + new(0xD503323F, 0xFFFFF3FF, InstName.DsbDsbNxs, IsaVersion.v87, InstFlags.None), + new(0x5E000400, 0xFFE0FC00, imm5Constraints, InstName.DupAdvsimdEltScalarFromElement, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E000400, 0xBFE0FC00, imm5Imm5qConstraints, InstName.DupAdvsimdEltVectorFromElement, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E000C00, 0xBFE0FC00, imm5Imm5qConstraints, InstName.DupAdvsimdGen, IsaVersion.v80, InstFlags.RdRnFpSimdFromGpr), + new(0x4A200000, 0x7F200000, sfimm6Constraints, InstName.Eon, IsaVersion.v80, InstFlags.RdRnRm), + new(0xCE000000, 0xFFE08000, InstName.Eor3Advsimd, IsaVersion.v82, InstFlags.RdRnRmRaFpSimd), + new(0x2E201C00, 0xBFE0FC00, InstName.EorAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x52000000, 0x7F800000, nimmsNimmsNimmsNimmsNimmsNimmsNimmsNimmsSfnConstraints, InstName.EorLogImm, IsaVersion.v80, InstFlags.RdSPRn), + new(0x4A000000, 0x7F200000, sfimm6Constraints, InstName.EorLogShift, IsaVersion.v80, InstFlags.RdRnRm), + new(0xD69F03E0, 0xFFFFFFFF, InstName.Eret, IsaVersion.v80, InstFlags.None), + new(0xD69F0BFF, 0xFFFFFBFF, InstName.Ereta, IsaVersion.v83, InstFlags.None), + new(0xD503221F, 0xFFFFFFFF, InstName.Esb, IsaVersion.v82, InstFlags.None), + new(0x13800000, 0x7FA00000, nsfNsfSfimmsConstraints, InstName.Extr, IsaVersion.v80, InstFlags.RdRnRm), + new(0x2E000000, 0xBFE08400, qimm4Constraints, InstName.ExtAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x7EC01400, 0xFFE0FC00, InstName.FabdAdvsimdSH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x7EA0D400, 0xFFA0FC00, InstName.FabdAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2EC01400, 0xBFE0FC00, InstName.FabdAdvsimdVH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x2EA0D400, 0xBFA0FC00, qszConstraints, InstName.FabdAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0EF8F800, 0xBFFFFC00, InstName.FabsAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0EA0F800, 0xBFBFFC00, qszConstraints, InstName.FabsAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E20C000, 0xFFBFFC00, InstName.FabsFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1EE0C000, 0xFFFFFC00, InstName.FabsFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x7E402C00, 0xFFE0FC00, euacEuacEuacConstraints, InstName.FacgeAdvsimdSH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x7E20EC00, 0xFFA0FC00, euacEuacEuacConstraints, InstName.FacgeAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E402C00, 0xBFE0FC00, euacEuacEuacConstraints, InstName.FacgeAdvsimdVH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x2E20EC00, 0xBFA0FC00, qszEuacEuacEuacConstraints, InstName.FacgeAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x7EC02C00, 0xFFE0FC00, euacEuacEuacConstraints, InstName.FacgtAdvsimdSH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x7EA0EC00, 0xFFA0FC00, euacEuacEuacConstraints, InstName.FacgtAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2EC02C00, 0xBFE0FC00, euacEuacEuacConstraints, InstName.FacgtAdvsimdVH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x2EA0EC00, 0xBFA0FC00, qszEuacEuacEuacConstraints, InstName.FacgtAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5E30D800, 0xFFBFFC00, szConstraints, InstName.FaddpAdvsimdPairHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x7E30D800, 0xFFBFFC00, InstName.FaddpAdvsimdPairSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E401400, 0xBFE0FC00, InstName.FaddpAdvsimdVecHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x2E20D400, 0xBFA0FC00, qszConstraints, InstName.FaddpAdvsimdVecSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E401400, 0xBFE0FC00, InstName.FaddAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x0E20D400, 0xBFA0FC00, qszConstraints, InstName.FaddAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1E202800, 0xFFA0FC00, InstName.FaddFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1EE02800, 0xFFE0FC00, InstName.FaddFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E80E400, 0xBFA0EC00, sizeQsizeConstraints, InstName.FcaddAdvsimdVec, IsaVersion.v83, InstFlags.RdRnRmFpSimd), + new(0x2E40E400, 0xBFE0EC00, sizeQsizeConstraints, InstName.FcaddAdvsimdVec, IsaVersion.v83, InstFlags.RdRnRmFpSimd), + new(0x1E200410, 0xFFA00C10, InstName.FccmpeFloat, IsaVersion.v80, InstFlags.RnRmNzcvSFpSimd), + new(0x1EE00410, 0xFFE00C10, InstName.FccmpeFloat, IsaVersion.v80, InstFlags.RnRmNzcvSFpSimd), + new(0x1E200400, 0xFFA00C10, InstName.FccmpFloat, IsaVersion.v80, InstFlags.RnRmNzcvSFpSimd), + new(0x1EE00400, 0xFFE00C10, InstName.FccmpFloat, IsaVersion.v80, InstFlags.RnRmNzcvSFpSimd), + new(0x5E402400, 0xFFE0FC00, euacEuacEuacConstraints, InstName.FcmeqAdvsimdRegSH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x5E20E400, 0xFFA0FC00, euacEuacEuacConstraints, InstName.FcmeqAdvsimdRegS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E402400, 0xBFE0FC00, euacEuacEuacConstraints, InstName.FcmeqAdvsimdRegVH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x0E20E400, 0xBFA0FC00, qszEuacEuacEuacConstraints, InstName.FcmeqAdvsimdRegV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5EF8D800, 0xFFFFFC00, InstName.FcmeqAdvsimdZeroSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x5EA0D800, 0xFFBFFC00, InstName.FcmeqAdvsimdZeroS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0EF8D800, 0xBFFFFC00, InstName.FcmeqAdvsimdZeroVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0EA0D800, 0xBFBFFC00, qszConstraints, InstName.FcmeqAdvsimdZeroV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x7E402400, 0xFFE0FC00, euacEuacEuacConstraints, InstName.FcmgeAdvsimdRegSH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x7E20E400, 0xFFA0FC00, euacEuacEuacConstraints, InstName.FcmgeAdvsimdRegS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E402400, 0xBFE0FC00, euacEuacEuacConstraints, InstName.FcmgeAdvsimdRegVH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x2E20E400, 0xBFA0FC00, qszEuacEuacEuacConstraints, InstName.FcmgeAdvsimdRegV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x7EF8C800, 0xFFFFFC00, InstName.FcmgeAdvsimdZeroSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x7EA0C800, 0xFFBFFC00, InstName.FcmgeAdvsimdZeroS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2EF8C800, 0xBFFFFC00, InstName.FcmgeAdvsimdZeroVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x2EA0C800, 0xBFBFFC00, qszConstraints, InstName.FcmgeAdvsimdZeroV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x7EC02400, 0xFFE0FC00, euacEuacEuacConstraints, InstName.FcmgtAdvsimdRegSH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x7EA0E400, 0xFFA0FC00, euacEuacEuacConstraints, InstName.FcmgtAdvsimdRegS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2EC02400, 0xBFE0FC00, euacEuacEuacConstraints, InstName.FcmgtAdvsimdRegVH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x2EA0E400, 0xBFA0FC00, qszEuacEuacEuacConstraints, InstName.FcmgtAdvsimdRegV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5EF8C800, 0xFFFFFC00, InstName.FcmgtAdvsimdZeroSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x5EA0C800, 0xFFBFFC00, InstName.FcmgtAdvsimdZeroS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0EF8C800, 0xBFFFFC00, InstName.FcmgtAdvsimdZeroVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0EA0C800, 0xBFBFFC00, qszConstraints, InstName.FcmgtAdvsimdZeroV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2F801000, 0xBF809400, sizeSizeSizelSizeqSizehqConstraints, InstName.FcmlaAdvsimdElt, IsaVersion.v83, InstFlags.RdReadRdRnRmFpSimd), + new(0x2F401000, 0xBFC09400, sizeSizeSizelSizeqSizehqConstraints, InstName.FcmlaAdvsimdElt, IsaVersion.v83, InstFlags.RdReadRdRnRmFpSimd), + new(0x2E80C400, 0xBFA0E400, sizeQsizeConstraints, InstName.FcmlaAdvsimdVec, IsaVersion.v83, InstFlags.RdReadRdRnRmFpSimd), + new(0x2E40C400, 0xBFE0E400, sizeQsizeConstraints, InstName.FcmlaAdvsimdVec, IsaVersion.v83, InstFlags.RdReadRdRnRmFpSimd), + new(0x7EF8D800, 0xFFFFFC00, InstName.FcmleAdvsimdSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x7EA0D800, 0xFFBFFC00, InstName.FcmleAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2EF8D800, 0xBFFFFC00, InstName.FcmleAdvsimdVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x2EA0D800, 0xBFBFFC00, qszConstraints, InstName.FcmleAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5EF8E800, 0xFFFFFC00, InstName.FcmltAdvsimdSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x5EA0E800, 0xFFBFFC00, InstName.FcmltAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0EF8E800, 0xBFFFFC00, InstName.FcmltAdvsimdVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0EA0E800, 0xBFBFFC00, qszConstraints, InstName.FcmltAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E202010, 0xFFA0FC17, InstName.FcmpeFloat, IsaVersion.v80, InstFlags.RnRmSFpSimd), + new(0x1EE02010, 0xFFE0FC17, InstName.FcmpeFloat, IsaVersion.v80, InstFlags.RnRmSFpSimd), + new(0x1E202000, 0xFFA0FC17, InstName.FcmpFloat, IsaVersion.v80, InstFlags.RnRmSFpSimd), + new(0x1EE02000, 0xFFE0FC17, InstName.FcmpFloat, IsaVersion.v80, InstFlags.RnRmSFpSimd), + new(0x1E200C00, 0xFFA00C00, InstName.FcselFloat, IsaVersion.v80, InstFlags.RdRnRmNzcvFpSimd), + new(0x1EE00C00, 0xFFE00C00, InstName.FcselFloat, IsaVersion.v80, InstFlags.RdRnRmNzcvFpSimd), + new(0x5E79C800, 0xFFFFFC00, InstName.FcvtasAdvsimdSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x5E21C800, 0xFFBFFC00, InstName.FcvtasAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E79C800, 0xBFFFFC00, InstName.FcvtasAdvsimdVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0E21C800, 0xBFBFFC00, qszConstraints, InstName.FcvtasAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E240000, 0x7FBFFC00, InstName.FcvtasFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1EE40000, 0x7FFFFC00, InstName.FcvtasFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x7E79C800, 0xFFFFFC00, InstName.FcvtauAdvsimdSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x7E21C800, 0xFFBFFC00, InstName.FcvtauAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E79C800, 0xBFFFFC00, InstName.FcvtauAdvsimdVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x2E21C800, 0xBFBFFC00, qszConstraints, InstName.FcvtauAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E250000, 0x7FBFFC00, InstName.FcvtauFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1EE50000, 0x7FFFFC00, InstName.FcvtauFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x0E217800, 0xBFBFFC00, InstName.FcvtlAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5E79B800, 0xFFFFFC00, InstName.FcvtmsAdvsimdSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x5E21B800, 0xFFBFFC00, InstName.FcvtmsAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E79B800, 0xBFFFFC00, InstName.FcvtmsAdvsimdVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0E21B800, 0xBFBFFC00, qszConstraints, InstName.FcvtmsAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E300000, 0x7FBFFC00, InstName.FcvtmsFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1EF00000, 0x7FFFFC00, InstName.FcvtmsFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x7E79B800, 0xFFFFFC00, InstName.FcvtmuAdvsimdSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x7E21B800, 0xFFBFFC00, InstName.FcvtmuAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E79B800, 0xBFFFFC00, InstName.FcvtmuAdvsimdVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x2E21B800, 0xBFBFFC00, qszConstraints, InstName.FcvtmuAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E310000, 0x7FBFFC00, InstName.FcvtmuFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1EF10000, 0x7FFFFC00, InstName.FcvtmuFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x5E79A800, 0xFFFFFC00, InstName.FcvtnsAdvsimdSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x5E21A800, 0xFFBFFC00, InstName.FcvtnsAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E79A800, 0xBFFFFC00, InstName.FcvtnsAdvsimdVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0E21A800, 0xBFBFFC00, qszConstraints, InstName.FcvtnsAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E200000, 0x7FBFFC00, InstName.FcvtnsFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1EE00000, 0x7FFFFC00, InstName.FcvtnsFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x7E79A800, 0xFFFFFC00, InstName.FcvtnuAdvsimdSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x7E21A800, 0xFFBFFC00, InstName.FcvtnuAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E79A800, 0xBFFFFC00, InstName.FcvtnuAdvsimdVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x2E21A800, 0xBFBFFC00, qszConstraints, InstName.FcvtnuAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E210000, 0x7FBFFC00, InstName.FcvtnuFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1EE10000, 0x7FFFFC00, InstName.FcvtnuFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x0E216800, 0xBFBFFC00, InstName.FcvtnAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0x5EF9A800, 0xFFFFFC00, InstName.FcvtpsAdvsimdSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x5EA1A800, 0xFFBFFC00, InstName.FcvtpsAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0EF9A800, 0xBFFFFC00, InstName.FcvtpsAdvsimdVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0EA1A800, 0xBFBFFC00, qszConstraints, InstName.FcvtpsAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E280000, 0x7FBFFC00, InstName.FcvtpsFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1EE80000, 0x7FFFFC00, InstName.FcvtpsFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x7EF9A800, 0xFFFFFC00, InstName.FcvtpuAdvsimdSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x7EA1A800, 0xFFBFFC00, InstName.FcvtpuAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2EF9A800, 0xBFFFFC00, InstName.FcvtpuAdvsimdVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x2EA1A800, 0xBFBFFC00, qszConstraints, InstName.FcvtpuAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E290000, 0x7FBFFC00, InstName.FcvtpuFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1EE90000, 0x7FFFFC00, InstName.FcvtpuFloat, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x7E216800, 0xFFBFFC00, szConstraints2, InstName.FcvtxnAdvsimdS, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0x2E216800, 0xBFBFFC00, szConstraints2, InstName.FcvtxnAdvsimdV, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0x5F40FC00, 0xFFC0FC00, immhConstraints, InstName.FcvtzsAdvsimdFixS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5F10FC00, 0xFFF0FC00, immhConstraints, InstName.FcvtzsAdvsimdFixS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5F20FC00, 0xFFE0FC00, immhConstraints, InstName.FcvtzsAdvsimdFixS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0F40FC00, 0xBFC0FC00, immhQimmhConstraints, InstName.FcvtzsAdvsimdFixV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0F10FC00, 0xBFF0FC00, immhQimmhConstraints, InstName.FcvtzsAdvsimdFixV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0F20FC00, 0xBFE0FC00, immhQimmhConstraints, InstName.FcvtzsAdvsimdFixV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5EF9B800, 0xFFFFFC00, InstName.FcvtzsAdvsimdIntSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x5EA1B800, 0xFFBFFC00, InstName.FcvtzsAdvsimdIntS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0EF9B800, 0xBFFFFC00, InstName.FcvtzsAdvsimdIntVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0EA1B800, 0xBFBFFC00, qszConstraints, InstName.FcvtzsAdvsimdIntV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E180000, 0x7FBF0000, sfscaleConstraints, InstName.FcvtzsFloatFix, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1ED80000, 0x7FFF0000, sfscaleConstraints, InstName.FcvtzsFloatFix, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1E380000, 0x7FBFFC00, InstName.FcvtzsFloatInt, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1EF80000, 0x7FFFFC00, InstName.FcvtzsFloatInt, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x7F40FC00, 0xFFC0FC00, immhConstraints, InstName.FcvtzuAdvsimdFixS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x7F10FC00, 0xFFF0FC00, immhConstraints, InstName.FcvtzuAdvsimdFixS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x7F20FC00, 0xFFE0FC00, immhConstraints, InstName.FcvtzuAdvsimdFixS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2F40FC00, 0xBFC0FC00, immhQimmhConstraints, InstName.FcvtzuAdvsimdFixV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2F10FC00, 0xBFF0FC00, immhQimmhConstraints, InstName.FcvtzuAdvsimdFixV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2F20FC00, 0xBFE0FC00, immhQimmhConstraints, InstName.FcvtzuAdvsimdFixV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x7EF9B800, 0xFFFFFC00, InstName.FcvtzuAdvsimdIntSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x7EA1B800, 0xFFBFFC00, InstName.FcvtzuAdvsimdIntS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2EF9B800, 0xBFFFFC00, InstName.FcvtzuAdvsimdIntVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x2EA1B800, 0xBFBFFC00, qszConstraints, InstName.FcvtzuAdvsimdIntV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E190000, 0x7FBF0000, sfscaleConstraints, InstName.FcvtzuFloatFix, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1ED90000, 0x7FFF0000, sfscaleConstraints, InstName.FcvtzuFloatFix, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1E390000, 0x7FBFFC00, InstName.FcvtzuFloatInt, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1EF90000, 0x7FFFFC00, InstName.FcvtzuFloatInt, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x1E224000, 0xFF3E7C00, ftypeopcFtypeopcFtypeopcFtypeopcFtypeOpcConstraints, InstName.FcvtFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E403C00, 0xBFE0FC00, InstName.FdivAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x2E20FC00, 0xBFA0FC00, qszConstraints, InstName.FdivAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1E201800, 0xFFA0FC00, InstName.FdivFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1EE01800, 0xFFE0FC00, InstName.FdivFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1E7E0000, 0xFFFFFC00, InstName.Fjcvtzs, IsaVersion.v83, InstFlags.RdRnSFpSimd), + new(0x1F000000, 0xFFA08000, InstName.FmaddFloat, IsaVersion.v80, InstFlags.RdRnRmRaFpSimd), + new(0x1FC00000, 0xFFE08000, InstName.FmaddFloat, IsaVersion.v80, InstFlags.RdRnRmRaFpSimd), + new(0x5E30C800, 0xFFBFFC00, szConstraints, InstName.FmaxnmpAdvsimdPairHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x7E30C800, 0xFFBFFC00, InstName.FmaxnmpAdvsimdPairSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E400400, 0xBFE0FC00, InstName.FmaxnmpAdvsimdVecHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x2E20C400, 0xBFA0FC00, qszConstraints, InstName.FmaxnmpAdvsimdVecSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E30C800, 0xBFFFFC00, InstName.FmaxnmvAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x6E30C800, 0xFFFFFC00, InstName.FmaxnmvAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E400400, 0xBFE0FC00, InstName.FmaxnmAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x0E20C400, 0xBFA0FC00, qszConstraints, InstName.FmaxnmAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1E206800, 0xFFA0FC00, InstName.FmaxnmFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1EE06800, 0xFFE0FC00, InstName.FmaxnmFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5E30F800, 0xFFBFFC00, szConstraints, InstName.FmaxpAdvsimdPairHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x7E30F800, 0xFFBFFC00, InstName.FmaxpAdvsimdPairSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E403400, 0xBFE0FC00, InstName.FmaxpAdvsimdVecHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x2E20F400, 0xBFA0FC00, qszConstraints, InstName.FmaxpAdvsimdVecSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E30F800, 0xBFFFFC00, InstName.FmaxvAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x6E30F800, 0xFFFFFC00, InstName.FmaxvAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E403400, 0xBFE0FC00, InstName.FmaxAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x0E20F400, 0xBFA0FC00, qszConstraints, InstName.FmaxAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1E204800, 0xFFA0FC00, InstName.FmaxFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1EE04800, 0xFFE0FC00, InstName.FmaxFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5EB0C800, 0xFFBFFC00, szConstraints, InstName.FminnmpAdvsimdPairHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x7EB0C800, 0xFFBFFC00, InstName.FminnmpAdvsimdPairSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2EC00400, 0xBFE0FC00, InstName.FminnmpAdvsimdVecHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x2EA0C400, 0xBFA0FC00, qszConstraints, InstName.FminnmpAdvsimdVecSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0EB0C800, 0xBFFFFC00, InstName.FminnmvAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x6EB0C800, 0xFFFFFC00, InstName.FminnmvAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0EC00400, 0xBFE0FC00, InstName.FminnmAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x0EA0C400, 0xBFA0FC00, qszConstraints, InstName.FminnmAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1E207800, 0xFFA0FC00, InstName.FminnmFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1EE07800, 0xFFE0FC00, InstName.FminnmFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5EB0F800, 0xFFBFFC00, szConstraints, InstName.FminpAdvsimdPairHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x7EB0F800, 0xFFBFFC00, InstName.FminpAdvsimdPairSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2EC03400, 0xBFE0FC00, InstName.FminpAdvsimdVecHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x2EA0F400, 0xBFA0FC00, qszConstraints, InstName.FminpAdvsimdVecSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0EB0F800, 0xBFFFFC00, InstName.FminvAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x6EB0F800, 0xFFFFFC00, InstName.FminvAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0EC03400, 0xBFE0FC00, InstName.FminAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x0EA0F400, 0xBFA0FC00, qszConstraints, InstName.FminAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1E205800, 0xFFA0FC00, InstName.FminFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1EE05800, 0xFFE0FC00, InstName.FminFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0F800000, 0xBFC0F400, szConstraints, InstName.FmlalAdvsimdEltFmlal, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x2F808000, 0xBFC0F400, szConstraints, InstName.FmlalAdvsimdEltFmlal2, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x0E20EC00, 0xBFE0FC00, szConstraints, InstName.FmlalAdvsimdVecFmlal, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x2E20CC00, 0xBFE0FC00, szConstraints, InstName.FmlalAdvsimdVecFmlal2, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x5F001000, 0xFFC0F400, InstName.FmlaAdvsimdElt2regScalarHalf, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x5F801000, 0xFF80F400, szlConstraints, InstName.FmlaAdvsimdElt2regScalarSingleAndDouble, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x0F001000, 0xBFC0F400, InstName.FmlaAdvsimdElt2regElementHalf, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x0F801000, 0xBF80F400, szlQszConstraints, InstName.FmlaAdvsimdElt2regElementSingleAndDouble, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x0E400C00, 0xBFE0FC00, InstName.FmlaAdvsimdVecHalf, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x0E20CC00, 0xBFA0FC00, qszConstraints, InstName.FmlaAdvsimdVecSingleAndDouble, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x0F804000, 0xBFC0F400, szConstraints, InstName.FmlslAdvsimdEltFmlsl, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x2F80C000, 0xBFC0F400, szConstraints, InstName.FmlslAdvsimdEltFmlsl2, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x0EA0EC00, 0xBFE0FC00, szConstraints, InstName.FmlslAdvsimdVecFmlsl, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x2EA0CC00, 0xBFE0FC00, szConstraints, InstName.FmlslAdvsimdVecFmlsl2, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x5F005000, 0xFFC0F400, InstName.FmlsAdvsimdElt2regScalarHalf, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x5F805000, 0xFF80F400, szlConstraints, InstName.FmlsAdvsimdElt2regScalarSingleAndDouble, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x0F005000, 0xBFC0F400, InstName.FmlsAdvsimdElt2regElementHalf, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x0F805000, 0xBF80F400, szlQszConstraints, InstName.FmlsAdvsimdElt2regElementSingleAndDouble, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x0EC00C00, 0xBFE0FC00, InstName.FmlsAdvsimdVecHalf, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x0EA0CC00, 0xBFA0FC00, qszConstraints, InstName.FmlsAdvsimdVecSingleAndDouble, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x0F00FC00, 0xBFF8FC00, InstName.FmovAdvsimdPerHalf, IsaVersion.v82, InstFlags.RdFpSimd), + new(0x0F00F400, 0x9FF8FC00, qConstraints, InstName.FmovAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdFpSimd), + new(0x1E204000, 0xFFBFFC00, InstName.FmovFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1EE04000, 0xFFFFFC00, InstName.FmovFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E260000, 0xFFFEFC00, sfftypermodeSfftypermodeConstraints, InstName.FmovFloatGen, IsaVersion.v80, InstFlags.RdRnSFpSimdFromToGpr), + new(0x9E660000, 0xFFFEFC00, sfftypermodeSfftypermodeConstraints, InstName.FmovFloatGen, IsaVersion.v80, InstFlags.RdRnSFpSimdFromToGpr), + new(0x9EAE0000, 0xFFFEFC00, sfftypermodeSfftypermodeConstraints, InstName.FmovFloatGen, IsaVersion.v80, InstFlags.RdRnSFpSimdFromToGpr), + new(0x1EE60000, 0x7FFEFC00, sfftypermodeSfftypermodeConstraints, InstName.FmovFloatGen, IsaVersion.v80, InstFlags.RdRnSFpSimdFromToGpr), + new(0x1E201000, 0xFFA01FE0, InstName.FmovFloatImm, IsaVersion.v80, InstFlags.RdFpSimd), + new(0x1EE01000, 0xFFE01FE0, InstName.FmovFloatImm, IsaVersion.v80, InstFlags.RdFpSimd), + new(0x1F008000, 0xFFA08000, InstName.FmsubFloat, IsaVersion.v80, InstFlags.RdRnRmRaFpSimd), + new(0x1FC08000, 0xFFE08000, InstName.FmsubFloat, IsaVersion.v80, InstFlags.RdRnRmRaFpSimd), + new(0x7F009000, 0xFFC0F400, InstName.FmulxAdvsimdElt2regScalarHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x7F809000, 0xFF80F400, szlConstraints, InstName.FmulxAdvsimdElt2regScalarSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2F009000, 0xBFC0F400, InstName.FmulxAdvsimdElt2regElementHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x2F809000, 0xBF80F400, szlQszConstraints, InstName.FmulxAdvsimdElt2regElementSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5E401C00, 0xFFE0FC00, InstName.FmulxAdvsimdVecSH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x5E20DC00, 0xFFA0FC00, InstName.FmulxAdvsimdVecS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E401C00, 0xBFE0FC00, InstName.FmulxAdvsimdVecVH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x0E20DC00, 0xBFA0FC00, qszConstraints, InstName.FmulxAdvsimdVecV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5F009000, 0xFFC0F400, InstName.FmulAdvsimdElt2regScalarHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x5F809000, 0xFF80F400, szlConstraints, InstName.FmulAdvsimdElt2regScalarSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0F009000, 0xBFC0F400, InstName.FmulAdvsimdElt2regElementHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x0F809000, 0xBF80F400, szlQszConstraints, InstName.FmulAdvsimdElt2regElementSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E401C00, 0xBFE0FC00, InstName.FmulAdvsimdVecHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x2E20DC00, 0xBFA0FC00, qszConstraints, InstName.FmulAdvsimdVecSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1E200800, 0xFFA0FC00, InstName.FmulFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1EE00800, 0xFFE0FC00, InstName.FmulFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2EF8F800, 0xBFFFFC00, InstName.FnegAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x2EA0F800, 0xBFBFFC00, qszConstraints, InstName.FnegAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E214000, 0xFFBFFC00, InstName.FnegFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1EE14000, 0xFFFFFC00, InstName.FnegFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1F200000, 0xFFA08000, InstName.FnmaddFloat, IsaVersion.v80, InstFlags.RdRnRmRaFpSimd), + new(0x1FE00000, 0xFFE08000, InstName.FnmaddFloat, IsaVersion.v80, InstFlags.RdRnRmRaFpSimd), + new(0x1F208000, 0xFFA08000, InstName.FnmsubFloat, IsaVersion.v80, InstFlags.RdRnRmRaFpSimd), + new(0x1FE08000, 0xFFE08000, InstName.FnmsubFloat, IsaVersion.v80, InstFlags.RdRnRmRaFpSimd), + new(0x1E208800, 0xFFA0FC00, InstName.FnmulFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1EE08800, 0xFFE0FC00, InstName.FnmulFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5EF9D800, 0xFFFFFC00, InstName.FrecpeAdvsimdSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x5EA1D800, 0xFFBFFC00, InstName.FrecpeAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0EF9D800, 0xBFFFFC00, InstName.FrecpeAdvsimdVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0EA1D800, 0xBFBFFC00, qszConstraints, InstName.FrecpeAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5E403C00, 0xFFE0FC00, InstName.FrecpsAdvsimdSH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x5E20FC00, 0xFFA0FC00, InstName.FrecpsAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E403C00, 0xBFE0FC00, InstName.FrecpsAdvsimdVH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x0E20FC00, 0xBFA0FC00, qszConstraints, InstName.FrecpsAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5EF9F800, 0xFFFFFC00, InstName.FrecpxAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x5EA1F800, 0xFFBFFC00, InstName.FrecpxAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E21E800, 0xBFBFFC00, qszConstraints, InstName.Frint32xAdvsimd, IsaVersion.v85, InstFlags.RdRnFpSimd), + new(0x1E28C000, 0xFFBFFC00, InstName.Frint32xFloat, IsaVersion.v85, InstFlags.RdRnFpSimd), + new(0x0E21E800, 0xBFBFFC00, qszConstraints, InstName.Frint32zAdvsimd, IsaVersion.v85, InstFlags.RdRnFpSimd), + new(0x1E284000, 0xFFBFFC00, InstName.Frint32zFloat, IsaVersion.v85, InstFlags.RdRnFpSimd), + new(0x2E21F800, 0xBFBFFC00, qszConstraints, InstName.Frint64xAdvsimd, IsaVersion.v85, InstFlags.RdRnFpSimd), + new(0x1E29C000, 0xFFBFFC00, InstName.Frint64xFloat, IsaVersion.v85, InstFlags.RdRnFpSimd), + new(0x0E21F800, 0xBFBFFC00, qszConstraints, InstName.Frint64zAdvsimd, IsaVersion.v85, InstFlags.RdRnFpSimd), + new(0x1E294000, 0xFFBFFC00, InstName.Frint64zFloat, IsaVersion.v85, InstFlags.RdRnFpSimd), + new(0x2E798800, 0xBFFFFC00, uo1o2Constraints, InstName.FrintaAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x2E218800, 0xBFBFFC00, qszUo1o2Constraints, InstName.FrintaAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E264000, 0xFFBFFC00, InstName.FrintaFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1EE64000, 0xFFFFFC00, InstName.FrintaFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2EF99800, 0xBFFFFC00, uo1o2Constraints, InstName.FrintiAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x2EA19800, 0xBFBFFC00, qszUo1o2Constraints, InstName.FrintiAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E27C000, 0xFFBFFC00, InstName.FrintiFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1EE7C000, 0xFFFFFC00, InstName.FrintiFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E799800, 0xBFFFFC00, uo1o2Constraints, InstName.FrintmAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0E219800, 0xBFBFFC00, qszUo1o2Constraints, InstName.FrintmAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E254000, 0xFFBFFC00, InstName.FrintmFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1EE54000, 0xFFFFFC00, InstName.FrintmFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E798800, 0xBFFFFC00, uo1o2Constraints, InstName.FrintnAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0E218800, 0xBFBFFC00, qszUo1o2Constraints, InstName.FrintnAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E244000, 0xFFBFFC00, InstName.FrintnFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1EE44000, 0xFFFFFC00, InstName.FrintnFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0EF98800, 0xBFFFFC00, uo1o2Constraints, InstName.FrintpAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0EA18800, 0xBFBFFC00, qszUo1o2Constraints, InstName.FrintpAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E24C000, 0xFFBFFC00, InstName.FrintpFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1EE4C000, 0xFFFFFC00, InstName.FrintpFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E799800, 0xBFFFFC00, uo1o2Constraints, InstName.FrintxAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x2E219800, 0xBFBFFC00, qszUo1o2Constraints, InstName.FrintxAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E274000, 0xFFBFFC00, InstName.FrintxFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1EE74000, 0xFFFFFC00, InstName.FrintxFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0EF99800, 0xBFFFFC00, uo1o2Constraints, InstName.FrintzAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0EA19800, 0xBFBFFC00, qszUo1o2Constraints, InstName.FrintzAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E25C000, 0xFFBFFC00, InstName.FrintzFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1EE5C000, 0xFFFFFC00, InstName.FrintzFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x7EF9D800, 0xFFFFFC00, InstName.FrsqrteAdvsimdSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x7EA1D800, 0xFFBFFC00, InstName.FrsqrteAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2EF9D800, 0xBFFFFC00, InstName.FrsqrteAdvsimdVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x2EA1D800, 0xBFBFFC00, qszConstraints, InstName.FrsqrteAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5EC03C00, 0xFFE0FC00, InstName.FrsqrtsAdvsimdSH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x5EA0FC00, 0xFFA0FC00, InstName.FrsqrtsAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0EC03C00, 0xBFE0FC00, InstName.FrsqrtsAdvsimdVH, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x0EA0FC00, 0xBFA0FC00, qszConstraints, InstName.FrsqrtsAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2EF9F800, 0xBFFFFC00, InstName.FsqrtAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x2EA1F800, 0xBFBFFC00, qszConstraints, InstName.FsqrtAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E21C000, 0xFFBFFC00, InstName.FsqrtFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1EE1C000, 0xFFFFFC00, InstName.FsqrtFloat, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0EC01400, 0xBFE0FC00, InstName.FsubAdvsimdHalf, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x0EA0D400, 0xBFA0FC00, qszConstraints, InstName.FsubAdvsimdSingleAndDouble, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1E203800, 0xFFA0FC00, InstName.FsubFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x1EE03800, 0xFFE0FC00, InstName.FsubFloat, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0xD503227F, 0xFFFFFFFF, InstName.Gcsb, IsaVersion.None, InstFlags.None), + new(0xD91F0C00, 0xFFFFFC00, InstName.Gcsstr, IsaVersion.None, InstFlags.RtReadRtRnSP), + new(0xD91F1C00, 0xFFFFFC00, InstName.Gcssttr, IsaVersion.None, InstFlags.RtReadRtRnSP), + new(0x9AC01400, 0xFFE0FC00, InstName.Gmi, IsaVersion.v85, InstFlags.None), + new(0xD503201F, 0xFFFFF01F, InstName.Hint, IsaVersion.v80, InstFlags.None), + new(0xD4400000, 0xFFE0001F, InstName.Hlt, IsaVersion.v80, InstFlags.None), + new(0xD4000002, 0xFFE0001F, InstName.Hvc, IsaVersion.v80, InstFlags.None), + new(0x6E000400, 0xFFE08400, imm5Constraints, InstName.InsAdvsimdElt, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0x4E001C00, 0xFFE0FC00, imm5Constraints, InstName.InsAdvsimdGen, IsaVersion.v80, InstFlags.RdReadRdRnFpSimdFromGpr), + new(0x9AC01000, 0xFFE0FC00, InstName.Irg, IsaVersion.v85, InstFlags.None), + new(0xD50330DF, 0xFFFFF0FF, InstName.Isb, IsaVersion.v80, InstFlags.None), + new(0x0D40C000, 0xBFFFF000, sConstraints, InstName.Ld1rAdvsimdAsNoPostIndex, IsaVersion.v80, InstFlags.RtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0DC0C000, 0xBFE0F000, sConstraints, InstName.Ld1rAdvsimdAsPostIndex, IsaVersion.v80, InstFlags.RtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0C402000, 0xBFFFF000, InstName.Ld1AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C406000, 0xBFFFF000, InstName.Ld1AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C407000, 0xBFFFF000, InstName.Ld1AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C40A000, 0xBFFFF000, InstName.Ld1AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C406000, 0xBFFFF000, InstName.Ld1AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C407000, 0xBFFFF000, InstName.Ld1AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C40A000, 0xBFFFF000, InstName.Ld1AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0CC02000, 0xBFE0F000, InstName.Ld1AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0CC06000, 0xBFE0F000, InstName.Ld1AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0CC07000, 0xBFE0F000, InstName.Ld1AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0CC0A000, 0xBFE0F000, InstName.Ld1AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0CC06000, 0xBFE0F000, InstName.Ld1AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0CC07000, 0xBFE0F000, InstName.Ld1AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0CC0A000, 0xBFE0F000, InstName.Ld1AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0D400000, 0xBFFF2000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.Ld1AdvsimdSnglAsNoPostIndex, IsaVersion.v80, InstFlags.RtReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0DC00000, 0xBFE02000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.Ld1AdvsimdSnglAsPostIndex, IsaVersion.v80, InstFlags.RtReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0D60C000, 0xBFFFF000, sConstraints, InstName.Ld2rAdvsimdAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0DE0C000, 0xBFE0F000, sConstraints, InstName.Ld2rAdvsimdAsPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0C408000, 0xBFFFF000, qsizeConstraints2, InstName.Ld2AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0CC08000, 0xBFE0F000, qsizeConstraints2, InstName.Ld2AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0D600000, 0xBFFF2000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.Ld2AdvsimdSnglAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0DE00000, 0xBFE02000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.Ld2AdvsimdSnglAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0D40E000, 0xBFFFF000, sConstraints, InstName.Ld3rAdvsimdAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0DC0E000, 0xBFE0F000, sConstraints, InstName.Ld3rAdvsimdAsPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0C404000, 0xBFFFF000, qsizeConstraints2, InstName.Ld3AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0CC04000, 0xBFE0F000, qsizeConstraints2, InstName.Ld3AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0D402000, 0xBFFF2000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.Ld3AdvsimdSnglAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0DC02000, 0xBFE02000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.Ld3AdvsimdSnglAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0D60E000, 0xBFFFF000, sConstraints, InstName.Ld4rAdvsimdAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0DE0E000, 0xBFE0F000, sConstraints, InstName.Ld4rAdvsimdAsPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0C400000, 0xBFFFF000, qsizeConstraints2, InstName.Ld4AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0CC00000, 0xBFE0F000, qsizeConstraints2, InstName.Ld4AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0D602000, 0xBFFF2000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.Ld4AdvsimdSnglAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0DE02000, 0xBFE02000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.Ld4AdvsimdSnglAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0xF83FD000, 0xFFFFFC00, rtRtConstraints, InstName.Ld64b, IsaVersion.v87, InstFlags.RtRnSP), + new(0xB8200000, 0xBF20FC00, InstName.Ldadd, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x38200000, 0xFF20FC00, InstName.Ldaddb, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x78200000, 0xFF20FC00, InstName.Ldaddh, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x0D418400, 0xBFFFFC00, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.Ldap1AdvsimdSngl, IsaVersion.v82, InstFlags.RtReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0xB8BFC000, 0xBFFFFC00, InstName.LdaprBaseRegister, IsaVersion.v83, InstFlags.RtRnSP, AddressForm.BaseRegister), + new(0x99C00800, 0xBFFFFC00, InstName.LdaprPostIndexed, IsaVersion.v82, InstFlags.RtRnSPMemWBack, AddressForm.PostIndexed), + new(0x38BFC000, 0xFFFFFC00, InstName.Ldaprb, IsaVersion.v83, InstFlags.RtRnSP), + new(0x78BFC000, 0xFFFFFC00, InstName.Ldaprh, IsaVersion.v83, InstFlags.RtRnSP), + new(0x19400000, 0xFFE00C00, InstName.Ldapurb, IsaVersion.v84, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x59400000, 0xFFE00C00, InstName.Ldapurh, IsaVersion.v84, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x19800000, 0xFFA00C00, InstName.Ldapursb, IsaVersion.v84, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x59800000, 0xFFA00C00, InstName.Ldapursh, IsaVersion.v84, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x99800000, 0xFFE00C00, InstName.Ldapursw, IsaVersion.v84, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x1D400800, 0x3F600C00, opc1sizeOpc1sizeOpc1sizeConstraints, InstName.LdapurFpsimd, IsaVersion.v82, InstFlags.RtRnSPFpSimd, AddressForm.BasePlusOffset), + new(0x99400000, 0xBFE00C00, InstName.LdapurGen, IsaVersion.v84, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x88DFFC00, 0xBFFFFC00, InstName.Ldar, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BaseRegister), + new(0x08DFFC00, 0xFFFFFC00, InstName.Ldarb, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BaseRegister), + new(0x48DFFC00, 0xFFFFFC00, InstName.Ldarh, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BaseRegister), + new(0x887F8000, 0xBFFF8000, InstName.Ldaxp, IsaVersion.v80, InstFlags.RtRt2RnSP, AddressForm.BaseRegister), + new(0x885FFC00, 0xBFFFFC00, InstName.Ldaxr, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BaseRegister), + new(0x085FFC00, 0xFFFFFC00, InstName.Ldaxrb, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BaseRegister), + new(0x485FFC00, 0xFFFFFC00, InstName.Ldaxrh, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BaseRegister), + new(0xB8201000, 0xBF20FC00, InstName.Ldclr, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x38201000, 0xFF20FC00, InstName.Ldclrb, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x78201000, 0xFF20FC00, InstName.Ldclrh, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x19201000, 0xFF20FC00, rtRt2Constraints, InstName.Ldclrp, IsaVersion.None, InstFlags.RtRt2RnSP), + new(0xB8202000, 0xBF20FC00, InstName.Ldeor, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x38202000, 0xFF20FC00, InstName.Ldeorb, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x78202000, 0xFF20FC00, InstName.Ldeorh, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0xD9600000, 0xFFE00C00, InstName.Ldg, IsaVersion.v85, InstFlags.None), + new(0xD9E00000, 0xFFFFFC00, InstName.Ldgm, IsaVersion.v85, InstFlags.None), + new(0x99400800, 0xBFE0EC00, InstName.Ldiapp, IsaVersion.v82, InstFlags.RtRt2RnSP), + new(0x88DF7C00, 0xBFFFFC00, InstName.Ldlar, IsaVersion.v81, InstFlags.RtRnSP, AddressForm.BaseRegister), + new(0x08DF7C00, 0xFFFFFC00, InstName.Ldlarb, IsaVersion.v81, InstFlags.RtRnSP, AddressForm.BaseRegister), + new(0x48DF7C00, 0xFFFFFC00, InstName.Ldlarh, IsaVersion.v81, InstFlags.RtRnSP, AddressForm.BaseRegister), + new(0x2C400000, 0x3FC00000, opcConstraints, InstName.LdnpFpsimd, IsaVersion.v80, InstFlags.RtRt2RnSPFpSimd, AddressForm.SignedScaled), + new(0x28400000, 0x7FC00000, opcConstraints2, InstName.LdnpGen, IsaVersion.v80, InstFlags.RtRt2RnSP, AddressForm.SignedScaled), + new(0x68C00000, 0xFFC00000, InstName.LdpswPostIndexed, IsaVersion.v80, InstFlags.RtRt2RnSPMemWBack, AddressForm.PostIndexed), + new(0x69C00000, 0xFFC00000, InstName.LdpswPreIndexed, IsaVersion.v80, InstFlags.RtRt2RnSPMemWBack, AddressForm.PreIndexed), + new(0x69400000, 0xFFC00000, InstName.LdpswSignedScaledOffset, IsaVersion.v80, InstFlags.RtRt2RnSP, AddressForm.SignedScaled), + new(0x2CC00000, 0x3FC00000, opcConstraints, InstName.LdpFpsimdPostIndexed, IsaVersion.v80, InstFlags.RtRt2RnSPFpSimdMemWBack, AddressForm.PostIndexed), + new(0x2DC00000, 0x3FC00000, opcConstraints, InstName.LdpFpsimdPreIndexed, IsaVersion.v80, InstFlags.RtRt2RnSPFpSimdMemWBack, AddressForm.PreIndexed), + new(0x2D400000, 0x3FC00000, opcConstraints, InstName.LdpFpsimdSignedScaledOffset, IsaVersion.v80, InstFlags.RtRt2RnSPFpSimd, AddressForm.SignedScaled), + new(0x28C00000, 0x7FC00000, opclOpcConstraints, InstName.LdpGenPostIndexed, IsaVersion.v80, InstFlags.RtRt2RnSPMemWBack, AddressForm.PostIndexed), + new(0x29C00000, 0x7FC00000, opclOpcConstraints, InstName.LdpGenPreIndexed, IsaVersion.v80, InstFlags.RtRt2RnSPMemWBack, AddressForm.PreIndexed), + new(0x29400000, 0x7FC00000, opclOpcConstraints, InstName.LdpGenSignedScaledOffset, IsaVersion.v80, InstFlags.RtRt2RnSP, AddressForm.SignedScaled), + new(0xF8200400, 0xFF200400, InstName.Ldra, IsaVersion.v83, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x38400400, 0xFFE00C00, InstName.LdrbImmPostIndexed, IsaVersion.v80, InstFlags.RtRnSPMemWBack, AddressForm.PostIndexed), + new(0x38400C00, 0xFFE00C00, InstName.LdrbImmPreIndexed, IsaVersion.v80, InstFlags.RtRnSPMemWBack, AddressForm.PreIndexed), + new(0x39400000, 0xFFC00000, InstName.LdrbImmUnsignedScaledOffset, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.UnsignedScaled), + new(0x38600800, 0xFFE00C00, optionConstraints, InstName.LdrbReg, IsaVersion.v80, InstFlags.RtRnSPRm, AddressForm.OffsetReg), + new(0x78400400, 0xFFE00C00, InstName.LdrhImmPostIndexed, IsaVersion.v80, InstFlags.RtRnSPMemWBack, AddressForm.PostIndexed), + new(0x78400C00, 0xFFE00C00, InstName.LdrhImmPreIndexed, IsaVersion.v80, InstFlags.RtRnSPMemWBack, AddressForm.PreIndexed), + new(0x79400000, 0xFFC00000, InstName.LdrhImmUnsignedScaledOffset, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.UnsignedScaled), + new(0x78600800, 0xFFE00C00, optionConstraints, InstName.LdrhReg, IsaVersion.v80, InstFlags.RtRnSPRm, AddressForm.OffsetReg), + new(0x38800400, 0xFFA00C00, InstName.LdrsbImmPostIndexed, IsaVersion.v80, InstFlags.RtRnSPMemWBack, AddressForm.PostIndexed), + new(0x38800C00, 0xFFA00C00, InstName.LdrsbImmPreIndexed, IsaVersion.v80, InstFlags.RtRnSPMemWBack, AddressForm.PreIndexed), + new(0x39800000, 0xFF800000, InstName.LdrsbImmUnsignedScaledOffset, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.UnsignedScaled), + new(0x38A00800, 0xFFA00C00, optionConstraints, InstName.LdrsbReg, IsaVersion.v80, InstFlags.RtRnSPRm, AddressForm.OffsetReg), + new(0x78800400, 0xFFA00C00, InstName.LdrshImmPostIndexed, IsaVersion.v80, InstFlags.RtRnSPMemWBack, AddressForm.PostIndexed), + new(0x78800C00, 0xFFA00C00, InstName.LdrshImmPreIndexed, IsaVersion.v80, InstFlags.RtRnSPMemWBack, AddressForm.PreIndexed), + new(0x79800000, 0xFF800000, InstName.LdrshImmUnsignedScaledOffset, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.UnsignedScaled), + new(0x78A00800, 0xFFA00C00, optionConstraints, InstName.LdrshReg, IsaVersion.v80, InstFlags.RtRnSPRm, AddressForm.OffsetReg), + new(0xB8800400, 0xFFE00C00, InstName.LdrswImmPostIndexed, IsaVersion.v80, InstFlags.RtRnSPMemWBack, AddressForm.PostIndexed), + new(0xB8800C00, 0xFFE00C00, InstName.LdrswImmPreIndexed, IsaVersion.v80, InstFlags.RtRnSPMemWBack, AddressForm.PreIndexed), + new(0xB9800000, 0xFFC00000, InstName.LdrswImmUnsignedScaledOffset, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.UnsignedScaled), + new(0x98000000, 0xFF000000, InstName.LdrswLit, IsaVersion.v80, InstFlags.Rt, AddressForm.Literal), + new(0xB8A00800, 0xFFE00C00, optionConstraints, InstName.LdrswReg, IsaVersion.v80, InstFlags.RtRnSPRm, AddressForm.OffsetReg), + new(0x3C400400, 0x3F600C00, opc1sizeOpc1sizeOpc1sizeConstraints, InstName.LdrImmFpsimdPostIndexed, IsaVersion.v80, InstFlags.RtRnSPFpSimdMemWBack, AddressForm.PostIndexed), + new(0x3C400C00, 0x3F600C00, opc1sizeOpc1sizeOpc1sizeConstraints, InstName.LdrImmFpsimdPreIndexed, IsaVersion.v80, InstFlags.RtRnSPFpSimdMemWBack, AddressForm.PreIndexed), + new(0x3D400000, 0x3F400000, opc1sizeOpc1sizeOpc1sizeConstraints, InstName.LdrImmFpsimdUnsignedScaledOffset, IsaVersion.v80, InstFlags.RtRnSPFpSimd, AddressForm.UnsignedScaled), + new(0xB8400400, 0xBFE00C00, InstName.LdrImmGenPostIndexed, IsaVersion.v80, InstFlags.RtRnSPMemWBack, AddressForm.PostIndexed), + new(0xB8400C00, 0xBFE00C00, InstName.LdrImmGenPreIndexed, IsaVersion.v80, InstFlags.RtRnSPMemWBack, AddressForm.PreIndexed), + new(0xB9400000, 0xBFC00000, InstName.LdrImmGenUnsignedScaledOffset, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.UnsignedScaled), + new(0x1C000000, 0x3F000000, opcConstraints, InstName.LdrLitFpsimd, IsaVersion.v80, InstFlags.RtFpSimd, AddressForm.Literal), + new(0x18000000, 0xBF000000, InstName.LdrLitGen, IsaVersion.v80, InstFlags.Rt, AddressForm.Literal), + new(0x3C600800, 0x3F600C00, opc1sizeOpc1sizeOpc1sizeOptionConstraints, InstName.LdrRegFpsimd, IsaVersion.v80, InstFlags.RtRnSPRmFpSimd, AddressForm.OffsetReg), + new(0xB8600800, 0xBFE00C00, optionConstraints, InstName.LdrRegGen, IsaVersion.v80, InstFlags.RtRnSPRm, AddressForm.OffsetReg), + new(0xB8203000, 0xBF20FC00, InstName.Ldset, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x38203000, 0xFF20FC00, InstName.Ldsetb, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x78203000, 0xFF20FC00, InstName.Ldseth, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x19203000, 0xFF20FC00, rtRt2Constraints, InstName.Ldsetp, IsaVersion.None, InstFlags.RtRt2RnSP), + new(0xB8204000, 0xBF20FC00, InstName.Ldsmax, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x38204000, 0xFF20FC00, InstName.Ldsmaxb, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x78204000, 0xFF20FC00, InstName.Ldsmaxh, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0xB8205000, 0xBF20FC00, InstName.Ldsmin, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x38205000, 0xFF20FC00, InstName.Ldsminb, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x78205000, 0xFF20FC00, InstName.Ldsminh, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0xB8400800, 0xBFE00C00, InstName.Ldtr, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x38400800, 0xFFE00C00, InstName.Ldtrb, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x78400800, 0xFFE00C00, InstName.Ldtrh, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x38800800, 0xFFA00C00, InstName.Ldtrsb, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x78800800, 0xFFA00C00, InstName.Ldtrsh, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0xB8800800, 0xFFE00C00, InstName.Ldtrsw, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0xB8206000, 0xBF20FC00, InstName.Ldumax, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x38206000, 0xFF20FC00, InstName.Ldumaxb, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x78206000, 0xFF20FC00, InstName.Ldumaxh, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0xB8207000, 0xBF20FC00, InstName.Ldumin, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x38207000, 0xFF20FC00, InstName.Lduminb, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x78207000, 0xFF20FC00, InstName.Lduminh, IsaVersion.v81, InstFlags.RtRnSPRs), + new(0x38400000, 0xFFE00C00, InstName.Ldurb, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x78400000, 0xFFE00C00, InstName.Ldurh, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x38800000, 0xFFA00C00, InstName.Ldursb, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x78800000, 0xFFA00C00, InstName.Ldursh, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0xB8800000, 0xFFE00C00, InstName.Ldursw, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x3C400000, 0x3F600C00, opc1sizeOpc1sizeOpc1sizeConstraints, InstName.LdurFpsimd, IsaVersion.v80, InstFlags.RtRnSPFpSimd, AddressForm.BasePlusOffset), + new(0xB8400000, 0xBFE00C00, InstName.LdurGen, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BasePlusOffset), + new(0x887F0000, 0xBFFF8000, InstName.Ldxp, IsaVersion.v80, InstFlags.RtRt2RnSP, AddressForm.BaseRegister), + new(0x885F7C00, 0xBFFFFC00, InstName.Ldxr, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BaseRegister), + new(0x085F7C00, 0xFFFFFC00, InstName.Ldxrb, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BaseRegister), + new(0x485F7C00, 0xFFFFFC00, InstName.Ldxrh, IsaVersion.v80, InstFlags.RtRnSP, AddressForm.BaseRegister), + new(0x1AC02000, 0x7FE0FC00, InstName.Lslv, IsaVersion.v80, InstFlags.RdRnRm), + new(0x1AC02400, 0x7FE0FC00, InstName.Lsrv, IsaVersion.v80, InstFlags.RdRnRm), + new(0x1B000000, 0x7FE08000, InstName.Madd, IsaVersion.v80, InstFlags.RdRnRmRa), + new(0x2F000000, 0xBF00F400, sizeSizeConstraints, InstName.MlaAdvsimdElt, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x0E209400, 0xBF20FC00, sizeConstraints, InstName.MlaAdvsimdVec, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x2F004000, 0xBF00F400, sizeSizeConstraints, InstName.MlsAdvsimdElt, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x2E209400, 0xBF20FC00, sizeConstraints, InstName.MlsAdvsimdVec, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x0F000400, 0x9FF80C00, cmodeopqConstraints, InstName.MoviAdvsimd, IsaVersion.v80, InstFlags.RdFpSimd), + new(0x72800000, 0x7F800000, sfhwConstraints, InstName.Movk, IsaVersion.v80, InstFlags.RdReadRd), + new(0x12800000, 0x7F800000, sfhwConstraints, InstName.Movn, IsaVersion.v80, InstFlags.Rd), + new(0x52800000, 0x7F800000, sfhwConstraints, InstName.Movz, IsaVersion.v80, InstFlags.Rd), + new(0xD5700000, 0xFFF00000, rtConstraints, InstName.Mrrs, IsaVersion.None, InstFlags.RtReadRt), + new(0xD5300000, 0xFFF00000, InstName.Mrs, IsaVersion.v80, InstFlags.Rt), + new(0xD5500000, 0xFFF00000, rtConstraints, InstName.Msrr, IsaVersion.None, InstFlags.RtReadRt), + new(0xD500401F, 0xFFF8F01F, InstName.MsrImm, IsaVersion.v80, InstFlags.None), + new(0xD5100000, 0xFFF00000, InstName.MsrReg, IsaVersion.v80, InstFlags.RtReadRt), + new(0x1B008000, 0x7FE08000, InstName.Msub, IsaVersion.v80, InstFlags.RdRnRmRa), + new(0x0F008000, 0xBF00F400, sizeSizeConstraints, InstName.MulAdvsimdElt, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E209C00, 0xBF20FC00, usizeUsizeUsizeSizeConstraints, InstName.MulAdvsimdVec, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2F000400, 0xBFF80C00, cmodeopqConstraints, InstName.MvniAdvsimd, IsaVersion.v80, InstFlags.RdFpSimd), + new(0x7EE0B800, 0xFFFFFC00, InstName.NegAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E20B800, 0xBF3FFC00, qsizeConstraints, InstName.NegAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0xD503201F, 0xFFFFFFFF, InstName.Nop, IsaVersion.v80, InstFlags.None), + new(0x2E205800, 0xBFFFFC00, InstName.NotAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0EE01C00, 0xBFE0FC00, InstName.OrnAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2A200000, 0x7F200000, sfimm6Constraints, InstName.OrnLogShift, IsaVersion.v80, InstFlags.RdRnRm), + new(0x0F001400, 0xBFF81C00, InstName.OrrAdvsimdImm, IsaVersion.v80, InstFlags.RdFpSimd), + new(0x0EA01C00, 0xBFE0FC00, InstName.OrrAdvsimdReg, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x32000000, 0x7F800000, nimmsNimmsNimmsNimmsNimmsNimmsNimmsNimmsSfnConstraints, InstName.OrrLogImm, IsaVersion.v80, InstFlags.RdSPRn), + new(0x2A000000, 0x7F200000, sfimm6Constraints, InstName.OrrLogShift, IsaVersion.v80, InstFlags.RdRnRm), + new(0xDAC10800, 0xFFFFDC00, InstName.Pacda, IsaVersion.v83, InstFlags.RdRnSP), + new(0xDAC10C00, 0xFFFFDC00, InstName.Pacdb, IsaVersion.v83, InstFlags.RdRnSP), + new(0x9AC03000, 0xFFE0FC00, InstName.Pacga, IsaVersion.v83, InstFlags.RdRnRm), + new(0xDAC10000, 0xFFFFDC00, InstName.PaciaGeneral, IsaVersion.v83, InstFlags.RdRnSP), + new(0xD503211F, 0xFFFFFDDF, InstName.PaciaSystem, IsaVersion.v83, InstFlags.None), + new(0xDAC10400, 0xFFFFDC00, InstName.PacibGeneral, IsaVersion.v83, InstFlags.RdRnSP), + new(0xD503215F, 0xFFFFFDDF, InstName.PacibSystem, IsaVersion.v83, InstFlags.None), + new(0x0E20E000, 0xBFE0FC00, sizeSizeConstraints2, InstName.PmullAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0EE0E000, 0xBFE0FC00, sizeSizeConstraints2, InstName.PmullAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E209C00, 0xBF20FC00, usizeUsizeUsizeSizeConstraints, InstName.PmulAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0xF9800000, 0xFFC00000, InstName.PrfmImm, IsaVersion.v80, InstFlags.RnSP, AddressForm.UnsignedScaled), + new(0xD8000000, 0xFF000000, InstName.PrfmLit, IsaVersion.v80, InstFlags.None, AddressForm.Literal), + new(0xF8A04800, 0xFFE04C00, rtConstraints2, InstName.PrfmReg, IsaVersion.v80, InstFlags.RnSPRm, AddressForm.OffsetReg), + new(0xF8800000, 0xFFE00C00, InstName.Prfum, IsaVersion.v80, InstFlags.RnSP, AddressForm.BasePlusOffset), + new(0xD503223F, 0xFFFFFFFF, InstName.Psb, IsaVersion.v82, InstFlags.None), + new(0x2E204000, 0xBF20FC00, sizeConstraints, InstName.RaddhnAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0xCE608C00, 0xFFE0FC00, InstName.Rax1Advsimd, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x2E605800, 0xBFFFFC00, InstName.RbitAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5AC00000, 0x7FFFFC00, InstName.RbitInt, IsaVersion.v80, InstFlags.RdRn), + new(0x19200800, 0xFF20FC00, InstName.Rcwcas, IsaVersion.v89, InstFlags.RtReadRtRnSPRsS), + new(0x19200C00, 0xFF20FC00, rsRtConstraints, InstName.Rcwcasp, IsaVersion.None, InstFlags.RtReadRtRnSPRsS), + new(0x38209000, 0xFF20FC00, InstName.Rcwclr, IsaVersion.v89, InstFlags.RtReadRtRnSPRsS), + new(0x19209000, 0xFF20FC00, rtRt2Constraints, InstName.Rcwclrp, IsaVersion.None, InstFlags.RtReadRtRt2RnSPS), + new(0x59200800, 0xFF20FC00, InstName.Rcwscas, IsaVersion.v89, InstFlags.RtReadRtRnSPRsS), + new(0x59200C00, 0xFF20FC00, rsRtConstraints, InstName.Rcwscasp, IsaVersion.None, InstFlags.RtReadRtRnSPRsS), + new(0x78209000, 0xFF20FC00, InstName.Rcwsclr, IsaVersion.v89, InstFlags.RtReadRtRnSPRsS), + new(0x59209000, 0xFF20FC00, rtRt2Constraints, InstName.Rcwsclrp, IsaVersion.None, InstFlags.RtReadRtRt2RnSPS), + new(0x3820B000, 0xFF20FC00, InstName.Rcwset, IsaVersion.v89, InstFlags.RtReadRtRnSPRsS), + new(0x1920B000, 0xFF20FC00, rtRt2Constraints, InstName.Rcwsetp, IsaVersion.None, InstFlags.RtReadRtRt2RnSPS), + new(0x7820B000, 0xFF20FC00, InstName.Rcwsset, IsaVersion.v89, InstFlags.RtReadRtRnSPRsS), + new(0x5920B000, 0xFF20FC00, rtRt2Constraints, InstName.Rcwssetp, IsaVersion.None, InstFlags.RtReadRtRt2RnSPS), + new(0x7820A000, 0xFF20FC00, InstName.Rcwsswp, IsaVersion.v89, InstFlags.RtReadRtRnSPRsS), + new(0x5920A000, 0xFF20FC00, rtRt2Constraints, InstName.Rcwsswpp, IsaVersion.None, InstFlags.RtReadRtRt2RnSPS), + new(0x3820A000, 0xFF20FC00, InstName.Rcwswp, IsaVersion.v89, InstFlags.RtReadRtRnSPRsS), + new(0x1920A000, 0xFF20FC00, rtRt2Constraints, InstName.Rcwswpp, IsaVersion.None, InstFlags.RtReadRtRt2RnSPS), + new(0xD65F0000, 0xFFFFFC1F, InstName.Ret, IsaVersion.v80, InstFlags.Rn), + new(0xD65F0BFF, 0xFFFFFBFF, InstName.Reta, IsaVersion.v83, InstFlags.None), + new(0x5AC00800, 0x7FFFF800, sfopcConstraints, InstName.Rev, IsaVersion.v80, InstFlags.RdRn), + new(0x0E201800, 0xBF3FFC00, sizeSizeSizeConstraints, InstName.Rev16Advsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5AC00400, 0x7FFFFC00, InstName.Rev16Int, IsaVersion.v80, InstFlags.RdRn), + new(0x2E200800, 0xBF3FFC00, sizeSizeConstraints3, InstName.Rev32Advsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0xDAC00800, 0xFFFFFC00, sfConstraints, InstName.Rev32Int, IsaVersion.v80, InstFlags.RdRn), + new(0x0E200800, 0xBF3FFC00, sizeConstraints, InstName.Rev64Advsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0xBA000400, 0xFFE07C10, InstName.Rmif, IsaVersion.v84, InstFlags.RnC), + new(0x1AC02C00, 0x7FE0FC00, InstName.Rorv, IsaVersion.v80, InstFlags.RdRnRm), + new(0xF8A04818, 0xFFE04C18, InstName.RprfmReg, IsaVersion.v80, InstFlags.RnSPRm, AddressForm.OffsetReg), + new(0x0F008C00, 0xBF80FC00, immhImmhConstraints, InstName.RshrnAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0x2E206000, 0xBF20FC00, sizeConstraints, InstName.RsubhnAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x0E205000, 0xBF20FC00, sizeConstraints, InstName.SabalAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E207C00, 0xBF20FC00, sizeConstraints, InstName.SabaAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E207000, 0xBF20FC00, sizeConstraints, InstName.SabdlAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E207400, 0xBF20FC00, sizeConstraints, InstName.SabdAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E206800, 0xBF3FFC00, sizeConstraints, InstName.SadalpAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0x0E202800, 0xBF3FFC00, sizeConstraints, InstName.SaddlpAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0x0E303800, 0xBF3FFC00, qsizeSizeConstraints, InstName.SaddlvAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E200000, 0xBF20FC00, sizeConstraints, InstName.SaddlAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E201000, 0xBF20FC00, sizeConstraints, InstName.SaddwAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0xD50330FF, 0xFFFFFFFF, InstName.Sb, IsaVersion.v85, InstFlags.None), + new(0x5A000000, 0x7FE0FC00, InstName.Sbc, IsaVersion.v80, InstFlags.RdRnRmC), + new(0x7A000000, 0x7FE0FC00, InstName.Sbcs, IsaVersion.v80, InstFlags.RdRnRmCS), + new(0x13000000, 0x7F800000, sfnSfnSfimmr5Sfimms5Constraints, InstName.Sbfm, IsaVersion.v80, InstFlags.RdRn), + new(0x5F40E400, 0xFFC0FC00, immhConstraints, InstName.ScvtfAdvsimdFixS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5F10E400, 0xFFF0FC00, immhConstraints, InstName.ScvtfAdvsimdFixS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5F20E400, 0xFFE0FC00, immhConstraints, InstName.ScvtfAdvsimdFixS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0F40E400, 0xBFC0FC00, immhQimmhConstraints, InstName.ScvtfAdvsimdFixV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0F10E400, 0xBFF0FC00, immhQimmhConstraints, InstName.ScvtfAdvsimdFixV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0F20E400, 0xBFE0FC00, immhQimmhConstraints, InstName.ScvtfAdvsimdFixV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5E79D800, 0xFFFFFC00, InstName.ScvtfAdvsimdIntSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x5E21D800, 0xFFBFFC00, InstName.ScvtfAdvsimdIntS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E79D800, 0xBFFFFC00, InstName.ScvtfAdvsimdIntVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x0E21D800, 0xBFBFFC00, qszConstraints, InstName.ScvtfAdvsimdIntV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E020000, 0x7FBF0000, sfscaleConstraints, InstName.ScvtfFloatFix, IsaVersion.v80, InstFlags.RdRnFpSimdFromGpr), + new(0x1EC20000, 0x7FFF0000, sfscaleConstraints, InstName.ScvtfFloatFix, IsaVersion.v80, InstFlags.RdRnFpSimdFromGpr), + new(0x1E220000, 0x7FBFFC00, InstName.ScvtfFloatInt, IsaVersion.v80, InstFlags.RdRnFpSimdFromGpr), + new(0x1EE20000, 0x7FFFFC00, InstName.ScvtfFloatInt, IsaVersion.v80, InstFlags.RdRnFpSimdFromGpr), + new(0x1AC00C00, 0x7FE0FC00, InstName.Sdiv, IsaVersion.v80, InstFlags.RdRnRm), + new(0x0F80E000, 0xBFC0F400, InstName.SdotAdvsimdElt, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x0E809400, 0xBFE0FC00, InstName.SdotAdvsimdVec, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x3A00080D, 0xFFFFBC1F, InstName.Setf, IsaVersion.v84, InstFlags.RnC), + new(0x1DC00400, 0x3FE03C00, InstName.Setgp, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1DC02400, 0x3FE03C00, InstName.Setgpn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1DC01400, 0x3FE03C00, InstName.Setgpt, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x1DC03400, 0x3FE03C00, InstName.Setgptn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x19C00400, 0x3FE03C00, InstName.Setp, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x19C02400, 0x3FE03C00, InstName.Setpn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x19C01400, 0x3FE03C00, InstName.Setpt, IsaVersion.v88, InstFlags.RdRnRsS), + new(0x19C03400, 0x3FE03C00, InstName.Setptn, IsaVersion.v88, InstFlags.RdRnRsS), + new(0xD503209F, 0xFFFFFFFF, InstName.Sev, IsaVersion.v80, InstFlags.None), + new(0xD50320BF, 0xFFFFFFFF, InstName.Sevl, IsaVersion.v80, InstFlags.None), + new(0x5E000000, 0xFFE0FC00, InstName.Sha1cAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5E280800, 0xFFFFFC00, InstName.Sha1hAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5E002000, 0xFFE0FC00, InstName.Sha1mAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5E001000, 0xFFE0FC00, InstName.Sha1pAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5E003000, 0xFFE0FC00, InstName.Sha1su0Advsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5E281800, 0xFFFFFC00, InstName.Sha1su1Advsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5E005000, 0xFFE0FC00, InstName.Sha256h2Advsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5E004000, 0xFFE0FC00, InstName.Sha256hAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5E282800, 0xFFFFFC00, InstName.Sha256su0Advsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5E006000, 0xFFE0FC00, InstName.Sha256su1Advsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0xCE608400, 0xFFE0FC00, InstName.Sha512h2Advsimd, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0xCE608000, 0xFFE0FC00, InstName.Sha512hAdvsimd, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0xCEC08000, 0xFFFFFC00, InstName.Sha512su0Advsimd, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0xCE608800, 0xFFE0FC00, InstName.Sha512su1Advsimd, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0x0E200400, 0xBF20FC00, sizeConstraints, InstName.ShaddAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E213800, 0xBF3FFC00, sizeConstraints, InstName.ShllAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5F405400, 0xFFC0FC00, immhConstraints, InstName.ShlAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0F005400, 0xBF80FC00, immhQimmhConstraints, InstName.ShlAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0F008400, 0xBF80FC00, immhImmhConstraints, InstName.ShrnAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0x0E202400, 0xBF20FC00, sizeConstraints, InstName.ShsubAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x7F405400, 0xFFC0FC00, immhConstraints, InstName.SliAdvsimdS, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0x2F005400, 0xBF80FC00, immhQimmhConstraints, InstName.SliAdvsimdV, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0xCE60C000, 0xFFE0FC00, InstName.Sm3partw1Advsimd, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0xCE60C400, 0xFFE0FC00, InstName.Sm3partw2Advsimd, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0xCE400000, 0xFFE08000, InstName.Sm3ss1Advsimd, IsaVersion.v82, InstFlags.RdRnRmRaFpSimd), + new(0xCE408000, 0xFFE0CC00, InstName.Sm3tt1aAdvsimd, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0xCE408400, 0xFFE0CC00, InstName.Sm3tt1bAdvsimd, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0xCE408800, 0xFFE0CC00, InstName.Sm3tt2aAdvsimd, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0xCE408C00, 0xFFE0CC00, InstName.Sm3tt2bAdvsimd, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0xCE60C800, 0xFFE0FC00, InstName.Sm4ekeyAdvsimd, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0xCEC08400, 0xFFFFFC00, InstName.Sm4eAdvsimd, IsaVersion.v82, InstFlags.RdReadRdRnFpSimd), + new(0x9B200000, 0xFFE08000, InstName.Smaddl, IsaVersion.v80, InstFlags.RdRnRmRa), + new(0x0E20A400, 0xBF20FC00, sizeConstraints, InstName.SmaxpAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E30A800, 0xBF3FFC00, qsizeSizeConstraints, InstName.SmaxvAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E206400, 0xBF20FC00, sizeConstraints, InstName.SmaxAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x11C00000, 0x7FFC0000, InstName.SmaxImm, IsaVersion.v89, InstFlags.RdRn), + new(0x1AC06000, 0x7FE0FC00, InstName.SmaxReg, IsaVersion.v89, InstFlags.RdRnRm), + new(0xD4000003, 0xFFE0001F, InstName.Smc, IsaVersion.v80, InstFlags.None), + new(0x0E20AC00, 0xBF20FC00, sizeConstraints, InstName.SminpAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E31A800, 0xBF3FFC00, qsizeSizeConstraints, InstName.SminvAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E206C00, 0xBF20FC00, sizeConstraints, InstName.SminAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x11C80000, 0x7FFC0000, InstName.SminImm, IsaVersion.v89, InstFlags.RdRn), + new(0x1AC06800, 0x7FE0FC00, InstName.SminReg, IsaVersion.v89, InstFlags.RdRnRm), + new(0x0F002000, 0xBF00F400, sizeSizeConstraints, InstName.SmlalAdvsimdElt, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E208000, 0xBF20FC00, sizeConstraints, InstName.SmlalAdvsimdVec, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0F006000, 0xBF00F400, sizeSizeConstraints, InstName.SmlslAdvsimdElt, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E20A000, 0xBF20FC00, sizeConstraints, InstName.SmlslAdvsimdVec, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x4E80A400, 0xFFE0FC00, InstName.SmmlaAdvsimdVec, IsaVersion.v86, InstFlags.RdRnRmFpSimd), + new(0x0E012C00, 0xBFE1FC00, InstName.SmovAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x0E022C00, 0xBFE3FC00, InstName.SmovAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x4E042C00, 0xFFE7FC00, InstName.SmovAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x9B208000, 0xFFE08000, InstName.Smsubl, IsaVersion.v80, InstFlags.RdRnRmRa), + new(0x9B407C00, 0xFFE0FC00, InstName.Smulh, IsaVersion.v80, InstFlags.RdRnRm), + new(0x0F00A000, 0xBF00F400, sizeSizeConstraints, InstName.SmullAdvsimdElt, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E20C000, 0xBF20FC00, sizeConstraints, InstName.SmullAdvsimdVec, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5E207800, 0xFF3FFC00, InstName.SqabsAdvsimdS, IsaVersion.v80, InstFlags.RdRnQcFpSimd), + new(0x0E207800, 0xBF3FFC00, qsizeConstraints, InstName.SqabsAdvsimdV, IsaVersion.v80, InstFlags.RdRnQcFpSimd), + new(0x5E200C00, 0xFF20FC00, InstName.SqaddAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x0E200C00, 0xBF20FC00, qsizeConstraints, InstName.SqaddAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x5F003000, 0xFF00F400, sizeSizeConstraints, InstName.SqdmlalAdvsimdElt2regScalar, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x0F003000, 0xBF00F400, sizeSizeConstraints, InstName.SqdmlalAdvsimdElt2regElement, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x5E209000, 0xFF20FC00, sizeSizeConstraints, InstName.SqdmlalAdvsimdVecS, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x0E209000, 0xBF20FC00, sizeSizeConstraints, InstName.SqdmlalAdvsimdVecV, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x5F007000, 0xFF00F400, sizeSizeConstraints, InstName.SqdmlslAdvsimdElt2regScalar, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x0F007000, 0xBF00F400, sizeSizeConstraints, InstName.SqdmlslAdvsimdElt2regElement, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x5E20B000, 0xFF20FC00, sizeSizeConstraints, InstName.SqdmlslAdvsimdVecS, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x0E20B000, 0xBF20FC00, sizeSizeConstraints, InstName.SqdmlslAdvsimdVecV, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x5F00C000, 0xFF00F400, sizeSizeConstraints, InstName.SqdmulhAdvsimdElt2regScalar, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0F00C000, 0xBF00F400, sizeSizeConstraints, InstName.SqdmulhAdvsimdElt2regElement, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5E20B400, 0xFF20FC00, sizeSizeConstraints4, InstName.SqdmulhAdvsimdVecS, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x0E20B400, 0xBF20FC00, sizeSizeConstraints4, InstName.SqdmulhAdvsimdVecV, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x5F00B000, 0xFF00F400, sizeSizeConstraints, InstName.SqdmullAdvsimdElt2regScalar, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x0F00B000, 0xBF00F400, sizeSizeConstraints, InstName.SqdmullAdvsimdElt2regElement, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x5E20D000, 0xFF20FC00, sizeSizeConstraints, InstName.SqdmullAdvsimdVecS, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x0E20D000, 0xBF20FC00, sizeSizeConstraints, InstName.SqdmullAdvsimdVecV, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x7E207800, 0xFF3FFC00, InstName.SqnegAdvsimdS, IsaVersion.v80, InstFlags.RdRnQcFpSimd), + new(0x2E207800, 0xBF3FFC00, qsizeConstraints, InstName.SqnegAdvsimdV, IsaVersion.v80, InstFlags.RdRnQcFpSimd), + new(0x7F00D000, 0xFF00F400, sizeSizeConstraints, InstName.SqrdmlahAdvsimdElt2regScalar, IsaVersion.v81, InstFlags.RdReadRdRnRmQcFpSimd), + new(0x2F00D000, 0xBF00F400, sizeSizeConstraints, InstName.SqrdmlahAdvsimdElt2regElement, IsaVersion.v81, InstFlags.RdReadRdRnRmQcFpSimd), + new(0x7E008400, 0xFF20FC00, sizeSizeConstraints4, InstName.SqrdmlahAdvsimdVecS, IsaVersion.v81, InstFlags.RdReadRdRnRmQcFpSimd), + new(0x2E008400, 0xBF20FC00, sizeSizeConstraints4, InstName.SqrdmlahAdvsimdVecV, IsaVersion.v81, InstFlags.RdReadRdRnRmQcFpSimd), + new(0x7F00F000, 0xFF00F400, sizeSizeConstraints, InstName.SqrdmlshAdvsimdElt2regScalar, IsaVersion.v81, InstFlags.RdReadRdRnRmQcFpSimd), + new(0x2F00F000, 0xBF00F400, sizeSizeConstraints, InstName.SqrdmlshAdvsimdElt2regElement, IsaVersion.v81, InstFlags.RdReadRdRnRmQcFpSimd), + new(0x7E008C00, 0xFF20FC00, sizeSizeConstraints4, InstName.SqrdmlshAdvsimdVecS, IsaVersion.v81, InstFlags.RdReadRdRnRmQcFpSimd), + new(0x2E008C00, 0xBF20FC00, sizeSizeConstraints4, InstName.SqrdmlshAdvsimdVecV, IsaVersion.v81, InstFlags.RdReadRdRnRmQcFpSimd), + new(0x5F00D000, 0xFF00F400, sizeSizeConstraints, InstName.SqrdmulhAdvsimdElt2regScalar, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x0F00D000, 0xBF00F400, sizeSizeConstraints, InstName.SqrdmulhAdvsimdElt2regElement, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x7E20B400, 0xFF20FC00, sizeSizeConstraints4, InstName.SqrdmulhAdvsimdVecS, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x2E20B400, 0xBF20FC00, sizeSizeConstraints4, InstName.SqrdmulhAdvsimdVecV, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x5E205C00, 0xFF20FC00, ssizeSsizeSsizeConstraints, InstName.SqrshlAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x0E205C00, 0xBF20FC00, qsizeConstraints, InstName.SqrshlAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x5F009C00, 0xFF80FC00, immhImmhConstraints, InstName.SqrshrnAdvsimdS, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x0F009C00, 0xBF80FC00, immhImmhConstraints, InstName.SqrshrnAdvsimdV, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x7F008C00, 0xFF80FC00, immhImmhConstraints, InstName.SqrshrunAdvsimdS, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x2F008C00, 0xBF80FC00, immhImmhConstraints, InstName.SqrshrunAdvsimdV, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x7F006400, 0xFF80FC00, immhOpuConstraints, InstName.SqshluAdvsimdS, IsaVersion.v80, InstFlags.RdRnQcFpSimd), + new(0x2F006400, 0xBF80FC00, immhQimmhOpuConstraints, InstName.SqshluAdvsimdV, IsaVersion.v80, InstFlags.RdRnQcFpSimd), + new(0x5F007400, 0xFF80FC00, immhOpuConstraints, InstName.SqshlAdvsimdImmS, IsaVersion.v80, InstFlags.RdRnQcFpSimd), + new(0x0F007400, 0xBF80FC00, immhQimmhOpuConstraints, InstName.SqshlAdvsimdImmV, IsaVersion.v80, InstFlags.RdRnQcFpSimd), + new(0x5E204C00, 0xFF20FC00, ssizeSsizeSsizeConstraints, InstName.SqshlAdvsimdRegS, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x0E204C00, 0xBF20FC00, qsizeConstraints, InstName.SqshlAdvsimdRegV, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x5F009400, 0xFF80FC00, immhImmhConstraints, InstName.SqshrnAdvsimdS, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x0F009400, 0xBF80FC00, immhImmhConstraints, InstName.SqshrnAdvsimdV, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x7F008400, 0xFF80FC00, immhImmhConstraints, InstName.SqshrunAdvsimdS, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x2F008400, 0xBF80FC00, immhImmhConstraints, InstName.SqshrunAdvsimdV, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x5E202C00, 0xFF20FC00, InstName.SqsubAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x0E202C00, 0xBF20FC00, qsizeConstraints, InstName.SqsubAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x5E214800, 0xFF3FFC00, sizeConstraints, InstName.SqxtnAdvsimdS, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x0E214800, 0xBF3FFC00, sizeConstraints, InstName.SqxtnAdvsimdV, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x7E212800, 0xFF3FFC00, sizeConstraints, InstName.SqxtunAdvsimdS, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x2E212800, 0xBF3FFC00, sizeConstraints, InstName.SqxtunAdvsimdV, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x0E201400, 0xBF20FC00, sizeConstraints, InstName.SrhaddAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x7F404400, 0xFFC0FC00, immhConstraints, InstName.SriAdvsimdS, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0x2F004400, 0xBF80FC00, immhQimmhConstraints, InstName.SriAdvsimdV, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0x5E205400, 0xFF20FC00, ssizeSsizeSsizeConstraints, InstName.SrshlAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E205400, 0xBF20FC00, qsizeConstraints, InstName.SrshlAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5F402400, 0xFFC0FC00, immhConstraints, InstName.SrshrAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0F002400, 0xBF80FC00, immhQimmhConstraints, InstName.SrshrAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5F403400, 0xFFC0FC00, immhConstraints, InstName.SrsraAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0F003400, 0xBF80FC00, immhQimmhConstraints, InstName.SrsraAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0F00A400, 0xBF80FC00, immhImmhConstraints, InstName.SshllAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5E204400, 0xFF20FC00, ssizeSsizeSsizeConstraints, InstName.SshlAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E204400, 0xBF20FC00, qsizeConstraints, InstName.SshlAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x5F400400, 0xFFC0FC00, immhConstraints, InstName.SshrAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0F000400, 0xBF80FC00, immhQimmhConstraints, InstName.SshrAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x5F401400, 0xFFC0FC00, immhConstraints, InstName.SsraAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0F001400, 0xBF80FC00, immhQimmhConstraints, InstName.SsraAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0E202000, 0xBF20FC00, sizeConstraints, InstName.SsublAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E203000, 0xBF20FC00, sizeConstraints, InstName.SsubwAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0C002000, 0xBFFFF000, InstName.St1AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C006000, 0xBFFFF000, InstName.St1AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C007000, 0xBFFFF000, InstName.St1AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C00A000, 0xBFFFF000, InstName.St1AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C006000, 0xBFFFF000, InstName.St1AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C007000, 0xBFFFF000, InstName.St1AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C00A000, 0xBFFFF000, InstName.St1AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C802000, 0xBFE0F000, InstName.St1AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0C806000, 0xBFE0F000, InstName.St1AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0C807000, 0xBFE0F000, InstName.St1AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0C80A000, 0xBFE0F000, InstName.St1AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0C806000, 0xBFE0F000, InstName.St1AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0C807000, 0xBFE0F000, InstName.St1AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0C80A000, 0xBFE0F000, InstName.St1AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0D000000, 0xBFFF2000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.St1AdvsimdSnglAsNoPostIndex, IsaVersion.v80, InstFlags.RtReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0D800000, 0xBFE02000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.St1AdvsimdSnglAsPostIndex, IsaVersion.v80, InstFlags.RtReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0xD9A00400, 0xFFE00C00, InstName.St2gPostIndexed, IsaVersion.v85, InstFlags.MemWBack, AddressForm.PostIndexed), + new(0xD9A00C00, 0xFFE00C00, InstName.St2gPreIndexed, IsaVersion.v85, InstFlags.MemWBack, AddressForm.PreIndexed), + new(0xD9A00800, 0xFFE00C00, InstName.St2gSignedScaledOffset, IsaVersion.v85, InstFlags.None, AddressForm.SignedScaled), + new(0x0C008000, 0xBFFFF000, qsizeConstraints2, InstName.St2AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C808000, 0xBFE0F000, qsizeConstraints2, InstName.St2AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0D200000, 0xBFFF2000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.St2AdvsimdSnglAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0DA00000, 0xBFE02000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.St2AdvsimdSnglAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0C004000, 0xBFFFF000, qsizeConstraints2, InstName.St3AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C804000, 0xBFE0F000, qsizeConstraints2, InstName.St3AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0D002000, 0xBFFF2000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.St3AdvsimdSnglAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0D802000, 0xBFE02000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.St3AdvsimdSnglAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0C000000, 0xBFFFF000, qsizeConstraints2, InstName.St4AdvsimdMultAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0C800000, 0xBFE0F000, qsizeConstraints2, InstName.St4AdvsimdMultAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0x0D202000, 0xBFFF2000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.St4AdvsimdSnglAsNoPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x0DA02000, 0xBFE02000, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.St4AdvsimdSnglAsPostIndex, IsaVersion.v80, InstFlags.RtSeqReadRtRnSPRmFpSimdMemWBack, AddressForm.StructPostIndexedReg), + new(0xF83F9000, 0xFFFFFC00, rtRtConstraints, InstName.St64b, IsaVersion.v87, InstFlags.RtReadRtRnSP), + new(0xF820B000, 0xFFE0FC00, rtRtConstraints, InstName.St64bv, IsaVersion.v87, InstFlags.RtReadRtRnSPRs), + new(0xF820A000, 0xFFE0FC00, rtRtConstraints, InstName.St64bv0, IsaVersion.v87, InstFlags.RtReadRtRnSPRs), + new(0xD9200400, 0xFFE00C00, InstName.StgPostIndexed, IsaVersion.v85, InstFlags.MemWBack, AddressForm.PostIndexed), + new(0xD9200C00, 0xFFE00C00, InstName.StgPreIndexed, IsaVersion.v85, InstFlags.MemWBack, AddressForm.PreIndexed), + new(0xD9200800, 0xFFE00C00, InstName.StgSignedScaledOffset, IsaVersion.v85, InstFlags.None, AddressForm.SignedScaled), + new(0xD9A00000, 0xFFFFFC00, InstName.Stgm, IsaVersion.v85, InstFlags.None), + new(0x68800000, 0xFFC00000, InstName.StgpPostIndexed, IsaVersion.v85, InstFlags.MemWBack, AddressForm.PostIndexed), + new(0x69800000, 0xFFC00000, InstName.StgpPreIndexed, IsaVersion.v85, InstFlags.MemWBack, AddressForm.PreIndexed), + new(0x69000000, 0xFFC00000, InstName.StgpSignedScaledOffset, IsaVersion.v85, InstFlags.None, AddressForm.SignedScaled), + new(0x99000800, 0xBFE0EC00, InstName.Stilp, IsaVersion.v82, InstFlags.RtReadRtRt2RnSP), + new(0x0D018400, 0xBFFFFC00, opcodesizeOpcodesizeOpcodesizesOpcodesizeConstraints, InstName.Stl1AdvsimdSngl, IsaVersion.v82, InstFlags.RtReadRtRnSPFpSimd, AddressForm.StructNoOffset), + new(0x889F7C00, 0xBFFFFC00, InstName.Stllr, IsaVersion.v81, InstFlags.RtReadRtRnSP, AddressForm.BaseRegister), + new(0x089F7C00, 0xFFFFFC00, InstName.Stllrb, IsaVersion.v81, InstFlags.RtReadRtRnSP, AddressForm.BaseRegister), + new(0x489F7C00, 0xFFFFFC00, InstName.Stllrh, IsaVersion.v81, InstFlags.RtReadRtRnSP, AddressForm.BaseRegister), + new(0x889FFC00, 0xBFFFFC00, InstName.StlrBaseRegister, IsaVersion.v80, InstFlags.RtReadRtRnSP, AddressForm.BaseRegister), + new(0x99800800, 0xBFFFFC00, InstName.StlrPreIndexed, IsaVersion.v82, InstFlags.RtReadRtRnSPMemWBack, AddressForm.PreIndexed), + new(0x089FFC00, 0xFFFFFC00, InstName.Stlrb, IsaVersion.v80, InstFlags.RtReadRtRnSP, AddressForm.BaseRegister), + new(0x489FFC00, 0xFFFFFC00, InstName.Stlrh, IsaVersion.v80, InstFlags.RtReadRtRnSP, AddressForm.BaseRegister), + new(0x19000000, 0xFFE00C00, InstName.Stlurb, IsaVersion.v84, InstFlags.RtReadRtRnSP, AddressForm.BasePlusOffset), + new(0x59000000, 0xFFE00C00, InstName.Stlurh, IsaVersion.v84, InstFlags.RtReadRtRnSP, AddressForm.BasePlusOffset), + new(0x1D000800, 0x3F600C00, opc1sizeOpc1sizeOpc1sizeConstraints, InstName.StlurFpsimd, IsaVersion.v82, InstFlags.RtReadRtRnSPFpSimd, AddressForm.BasePlusOffset), + new(0x99000000, 0xBFE00C00, InstName.StlurGen, IsaVersion.v84, InstFlags.RtReadRtRnSP, AddressForm.BasePlusOffset), + new(0x88208000, 0xBFE08000, InstName.Stlxp, IsaVersion.v80, InstFlags.RtReadRtRt2RnSPRs, AddressForm.BaseRegister), + new(0x8800FC00, 0xBFE0FC00, InstName.Stlxr, IsaVersion.v80, InstFlags.RtReadRtRnSPRs, AddressForm.BaseRegister), + new(0x0800FC00, 0xFFE0FC00, InstName.Stlxrb, IsaVersion.v80, InstFlags.RtReadRtRnSPRs, AddressForm.BaseRegister), + new(0x4800FC00, 0xFFE0FC00, InstName.Stlxrh, IsaVersion.v80, InstFlags.RtReadRtRnSPRs, AddressForm.BaseRegister), + new(0x2C000000, 0x3FC00000, opcConstraints, InstName.StnpFpsimd, IsaVersion.v80, InstFlags.RtReadRtRt2RnSPFpSimd, AddressForm.SignedScaled), + new(0x28000000, 0x7FC00000, opcConstraints2, InstName.StnpGen, IsaVersion.v80, InstFlags.RtReadRtRt2RnSP, AddressForm.SignedScaled), + new(0x2C800000, 0x3FC00000, opcConstraints, InstName.StpFpsimdPostIndexed, IsaVersion.v80, InstFlags.RtReadRtRt2RnSPFpSimdMemWBack, AddressForm.PostIndexed), + new(0x2D800000, 0x3FC00000, opcConstraints, InstName.StpFpsimdPreIndexed, IsaVersion.v80, InstFlags.RtReadRtRt2RnSPFpSimdMemWBack, AddressForm.PreIndexed), + new(0x2D000000, 0x3FC00000, opcConstraints, InstName.StpFpsimdSignedScaledOffset, IsaVersion.v80, InstFlags.RtReadRtRt2RnSPFpSimd, AddressForm.SignedScaled), + new(0x28800000, 0x7FC00000, opclOpcConstraints, InstName.StpGenPostIndexed, IsaVersion.v80, InstFlags.RtReadRtRt2RnSPMemWBack, AddressForm.PostIndexed), + new(0x29800000, 0x7FC00000, opclOpcConstraints, InstName.StpGenPreIndexed, IsaVersion.v80, InstFlags.RtReadRtRt2RnSPMemWBack, AddressForm.PreIndexed), + new(0x29000000, 0x7FC00000, opclOpcConstraints, InstName.StpGenSignedScaledOffset, IsaVersion.v80, InstFlags.RtReadRtRt2RnSP, AddressForm.SignedScaled), + new(0x38000400, 0xFFE00C00, InstName.StrbImmPostIndexed, IsaVersion.v80, InstFlags.RtReadRtRnSPMemWBack, AddressForm.PostIndexed), + new(0x38000C00, 0xFFE00C00, InstName.StrbImmPreIndexed, IsaVersion.v80, InstFlags.RtReadRtRnSPMemWBack, AddressForm.PreIndexed), + new(0x39000000, 0xFFC00000, InstName.StrbImmUnsignedScaledOffset, IsaVersion.v80, InstFlags.RtReadRtRnSP, AddressForm.UnsignedScaled), + new(0x38200800, 0xFFE00C00, optionConstraints, InstName.StrbReg, IsaVersion.v80, InstFlags.RtReadRtRnSPRm, AddressForm.OffsetReg), + new(0x78000400, 0xFFE00C00, InstName.StrhImmPostIndexed, IsaVersion.v80, InstFlags.RtReadRtRnSPMemWBack, AddressForm.PostIndexed), + new(0x78000C00, 0xFFE00C00, InstName.StrhImmPreIndexed, IsaVersion.v80, InstFlags.RtReadRtRnSPMemWBack, AddressForm.PreIndexed), + new(0x79000000, 0xFFC00000, InstName.StrhImmUnsignedScaledOffset, IsaVersion.v80, InstFlags.RtReadRtRnSP, AddressForm.UnsignedScaled), + new(0x78200800, 0xFFE00C00, optionConstraints, InstName.StrhReg, IsaVersion.v80, InstFlags.RtReadRtRnSPRm, AddressForm.OffsetReg), + new(0x3C000400, 0x3F600C00, opc1sizeOpc1sizeOpc1sizeConstraints, InstName.StrImmFpsimdPostIndexed, IsaVersion.v80, InstFlags.RtReadRtRnSPFpSimdMemWBack, AddressForm.PostIndexed), + new(0x3C000C00, 0x3F600C00, opc1sizeOpc1sizeOpc1sizeConstraints, InstName.StrImmFpsimdPreIndexed, IsaVersion.v80, InstFlags.RtReadRtRnSPFpSimdMemWBack, AddressForm.PreIndexed), + new(0x3D000000, 0x3F400000, opc1sizeOpc1sizeOpc1sizeConstraints, InstName.StrImmFpsimdUnsignedScaledOffset, IsaVersion.v80, InstFlags.RtReadRtRnSPFpSimd, AddressForm.UnsignedScaled), + new(0xB8000400, 0xBFE00C00, InstName.StrImmGenPostIndexed, IsaVersion.v80, InstFlags.RtReadRtRnSPMemWBack, AddressForm.PostIndexed), + new(0xB8000C00, 0xBFE00C00, InstName.StrImmGenPreIndexed, IsaVersion.v80, InstFlags.RtReadRtRnSPMemWBack, AddressForm.PreIndexed), + new(0xB9000000, 0xBFC00000, InstName.StrImmGenUnsignedScaledOffset, IsaVersion.v80, InstFlags.RtReadRtRnSP, AddressForm.UnsignedScaled), + new(0x3C200800, 0x3F600C00, opc1sizeOpc1sizeOpc1sizeOptionConstraints, InstName.StrRegFpsimd, IsaVersion.v80, InstFlags.RtReadRtRnSPRmFpSimd, AddressForm.OffsetReg), + new(0xB8200800, 0xBFE00C00, optionConstraints, InstName.StrRegGen, IsaVersion.v80, InstFlags.RtReadRtRnSPRm, AddressForm.OffsetReg), + new(0xB8000800, 0xBFE00C00, InstName.Sttr, IsaVersion.v80, InstFlags.RtReadRtRnSP, AddressForm.BasePlusOffset), + new(0x38000800, 0xFFE00C00, InstName.Sttrb, IsaVersion.v80, InstFlags.RtReadRtRnSP, AddressForm.BasePlusOffset), + new(0x78000800, 0xFFE00C00, InstName.Sttrh, IsaVersion.v80, InstFlags.RtReadRtRnSP, AddressForm.BasePlusOffset), + new(0x38000000, 0xFFE00C00, InstName.Sturb, IsaVersion.v80, InstFlags.RtReadRtRnSP, AddressForm.BasePlusOffset), + new(0x78000000, 0xFFE00C00, InstName.Sturh, IsaVersion.v80, InstFlags.RtReadRtRnSP, AddressForm.BasePlusOffset), + new(0x3C000000, 0x3F600C00, opc1sizeOpc1sizeOpc1sizeConstraints, InstName.SturFpsimd, IsaVersion.v80, InstFlags.RtReadRtRnSPFpSimd, AddressForm.BasePlusOffset), + new(0xB8000000, 0xBFE00C00, InstName.SturGen, IsaVersion.v80, InstFlags.RtReadRtRnSP, AddressForm.BasePlusOffset), + new(0x88200000, 0xBFE08000, InstName.Stxp, IsaVersion.v80, InstFlags.RtReadRtRt2RnSPRs, AddressForm.BaseRegister), + new(0x88007C00, 0xBFE0FC00, InstName.Stxr, IsaVersion.v80, InstFlags.RtReadRtRnSPRs, AddressForm.BaseRegister), + new(0x08007C00, 0xFFE0FC00, InstName.Stxrb, IsaVersion.v80, InstFlags.RtReadRtRnSPRs, AddressForm.BaseRegister), + new(0x48007C00, 0xFFE0FC00, InstName.Stxrh, IsaVersion.v80, InstFlags.RtReadRtRnSPRs, AddressForm.BaseRegister), + new(0xD9E00400, 0xFFE00C00, InstName.Stz2gPostIndexed, IsaVersion.v85, InstFlags.MemWBack, AddressForm.PostIndexed), + new(0xD9E00C00, 0xFFE00C00, InstName.Stz2gPreIndexed, IsaVersion.v85, InstFlags.MemWBack, AddressForm.PreIndexed), + new(0xD9E00800, 0xFFE00C00, InstName.Stz2gSignedScaledOffset, IsaVersion.v85, InstFlags.None, AddressForm.SignedScaled), + new(0xD9600400, 0xFFE00C00, InstName.StzgPostIndexed, IsaVersion.v85, InstFlags.MemWBack, AddressForm.PostIndexed), + new(0xD9600C00, 0xFFE00C00, InstName.StzgPreIndexed, IsaVersion.v85, InstFlags.MemWBack, AddressForm.PreIndexed), + new(0xD9600800, 0xFFE00C00, InstName.StzgSignedScaledOffset, IsaVersion.v85, InstFlags.None, AddressForm.SignedScaled), + new(0xD9200000, 0xFFFFFC00, InstName.Stzgm, IsaVersion.v85, InstFlags.None), + new(0xD1800000, 0xFFC0C000, InstName.Subg, IsaVersion.v85, InstFlags.None), + new(0x0E206000, 0xBF20FC00, sizeConstraints, InstName.SubhnAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnRmFpSimd), + new(0x9AC00000, 0xFFE0FC00, InstName.Subp, IsaVersion.v85, InstFlags.None), + new(0xBAC00000, 0xFFE0FC00, InstName.Subps, IsaVersion.v85, InstFlags.S), + new(0x6B200000, 0x7FE00000, opuOpuOpuConstraints, InstName.SubsAddsubExt, IsaVersion.v80, InstFlags.RdRnSPRmS), + new(0x71000000, 0x7F800000, InstName.SubsAddsubImm, IsaVersion.v80, InstFlags.RdRnSPS), + new(0x6B000000, 0x7F200000, shiftSfimm6Constraints, InstName.SubsAddsubShift, IsaVersion.v80, InstFlags.RdRnRmS), + new(0x4B200000, 0x7FE00000, opuOpuOpuConstraints, InstName.SubAddsubExt, IsaVersion.v80, InstFlags.RdSPRnSPRm), + new(0x51000000, 0x7F800000, InstName.SubAddsubImm, IsaVersion.v80, InstFlags.RdSPRnSP), + new(0x4B000000, 0x7F200000, shiftSfimm6Constraints, InstName.SubAddsubShift, IsaVersion.v80, InstFlags.RdRnRm), + new(0x7EE08400, 0xFFE0FC00, InstName.SubAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E208400, 0xBF20FC00, qsizeConstraints, InstName.SubAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0F00F000, 0xBFC0F400, InstName.SudotAdvsimdElt, IsaVersion.v86, InstFlags.RdReadRdRnRmFpSimd), + new(0x5E203800, 0xFF3FFC00, InstName.SuqaddAdvsimdS, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x0E203800, 0xBF3FFC00, qsizeConstraints, InstName.SuqaddAdvsimdV, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0xD4000001, 0xFFE0001F, InstName.Svc, IsaVersion.v80, InstFlags.None), + new(0xB8208000, 0xBF20FC00, InstName.Swp, IsaVersion.v81, InstFlags.RtReadRtRnSPRs), + new(0x38208000, 0xFF20FC00, InstName.Swpb, IsaVersion.v81, InstFlags.RtReadRtRnSPRs), + new(0x78208000, 0xFF20FC00, InstName.Swph, IsaVersion.v81, InstFlags.RtReadRtRnSPRs), + new(0x19208000, 0xFF20FC00, rtRt2Constraints, InstName.Swpp, IsaVersion.None, InstFlags.RtReadRtRt2RnSP), + new(0xD5080000, 0xFFF80000, InstName.Sys, IsaVersion.v80, InstFlags.RtReadRt), + new(0xD5280000, 0xFFF80000, InstName.Sysl, IsaVersion.v80, InstFlags.Rt), + new(0xD5480000, 0xFFF80000, InstName.Sysp, IsaVersion.None, InstFlags.RtReadRt), + new(0x0E000000, 0xBFE09C00, InstName.TblAdvsimd, IsaVersion.v80, InstFlags.RdRnSeqRmFpSimd), + new(0x37000000, 0x7F000000, InstName.Tbnz, IsaVersion.v80, InstFlags.RtReadRt), + new(0x0E001000, 0xBFE09C00, InstName.TbxAdvsimd, IsaVersion.v80, InstFlags.RdRnSeqRmFpSimd), + new(0x36000000, 0x7F000000, InstName.Tbz, IsaVersion.v80, InstFlags.RtReadRt), + new(0xD4600000, 0xFFE0001F, InstName.Tcancel, IsaVersion.None, InstFlags.None), + new(0xD503307F, 0xFFFFFFFF, InstName.Tcommit, IsaVersion.None, InstFlags.None), + new(0x0E002800, 0xBF20FC00, qsizeConstraints, InstName.Trn1Advsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E006800, 0xBF20FC00, qsizeConstraints, InstName.Trn2Advsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0xD503225F, 0xFFFFFFFF, InstName.Tsb, IsaVersion.v84, InstFlags.None), + new(0xD5233060, 0xFFFFFFE0, InstName.Tstart, IsaVersion.None, InstFlags.RtReadRt), + new(0xD5233160, 0xFFFFFFE0, InstName.Ttest, IsaVersion.None, InstFlags.RtReadRt), + new(0x2E205000, 0xBF20FC00, sizeConstraints, InstName.UabalAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E207C00, 0xBF20FC00, sizeConstraints, InstName.UabaAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E207000, 0xBF20FC00, sizeConstraints, InstName.UabdlAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E207400, 0xBF20FC00, sizeConstraints, InstName.UabdAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E206800, 0xBF3FFC00, sizeConstraints, InstName.UadalpAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0x2E202800, 0xBF3FFC00, sizeConstraints, InstName.UaddlpAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0x2E303800, 0xBF3FFC00, qsizeSizeConstraints, InstName.UaddlvAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E200000, 0xBF20FC00, sizeConstraints, InstName.UaddlAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E201000, 0xBF20FC00, sizeConstraints, InstName.UaddwAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x53000000, 0x7F800000, sfnSfnSfimmr5Sfimms5Constraints, InstName.Ubfm, IsaVersion.v80, InstFlags.RdRn), + new(0x7F40E400, 0xFFC0FC00, immhConstraints, InstName.UcvtfAdvsimdFixS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x7F10E400, 0xFFF0FC00, immhConstraints, InstName.UcvtfAdvsimdFixS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x7F20E400, 0xFFE0FC00, immhConstraints, InstName.UcvtfAdvsimdFixS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2F40E400, 0xBFC0FC00, immhQimmhConstraints, InstName.UcvtfAdvsimdFixV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2F10E400, 0xBFF0FC00, immhQimmhConstraints, InstName.UcvtfAdvsimdFixV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2F20E400, 0xBFE0FC00, immhQimmhConstraints, InstName.UcvtfAdvsimdFixV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x7E79D800, 0xFFFFFC00, InstName.UcvtfAdvsimdIntSH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x7E21D800, 0xFFBFFC00, InstName.UcvtfAdvsimdIntS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E79D800, 0xBFFFFC00, InstName.UcvtfAdvsimdIntVH, IsaVersion.v82, InstFlags.RdRnFpSimd), + new(0x2E21D800, 0xBFBFFC00, qszConstraints, InstName.UcvtfAdvsimdIntV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x1E030000, 0x7FBF0000, sfscaleConstraints, InstName.UcvtfFloatFix, IsaVersion.v80, InstFlags.RdRnFpSimdFromGpr), + new(0x1EC30000, 0x7FFF0000, sfscaleConstraints, InstName.UcvtfFloatFix, IsaVersion.v80, InstFlags.RdRnFpSimdFromGpr), + new(0x1E230000, 0x7FBFFC00, InstName.UcvtfFloatInt, IsaVersion.v80, InstFlags.RdRnFpSimdFromGpr), + new(0x1EE30000, 0x7FFFFC00, InstName.UcvtfFloatInt, IsaVersion.v80, InstFlags.RdRnFpSimdFromGpr), + new(0x00000000, 0xFFFF0000, InstName.UdfPermUndef, IsaVersion.v80, InstFlags.None), + new(0x1AC00800, 0x7FE0FC00, InstName.Udiv, IsaVersion.v80, InstFlags.RdRnRm), + new(0x2F80E000, 0xBFC0F400, InstName.UdotAdvsimdElt, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x2E809400, 0xBFE0FC00, InstName.UdotAdvsimdVec, IsaVersion.v82, InstFlags.RdReadRdRnRmFpSimd), + new(0x2E200400, 0xBF20FC00, sizeConstraints, InstName.UhaddAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E202400, 0xBF20FC00, sizeConstraints, InstName.UhsubAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x9BA00000, 0xFFE08000, InstName.Umaddl, IsaVersion.v80, InstFlags.RdRnRmRa), + new(0x2E20A400, 0xBF20FC00, sizeConstraints, InstName.UmaxpAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E30A800, 0xBF3FFC00, qsizeSizeConstraints, InstName.UmaxvAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E206400, 0xBF20FC00, sizeConstraints, InstName.UmaxAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x11C40000, 0x7FFC0000, InstName.UmaxImm, IsaVersion.v89, InstFlags.RdRn), + new(0x1AC06400, 0x7FE0FC00, InstName.UmaxReg, IsaVersion.v89, InstFlags.RdRnRm), + new(0x2E20AC00, 0xBF20FC00, sizeConstraints, InstName.UminpAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E31A800, 0xBF3FFC00, qsizeSizeConstraints, InstName.UminvAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E206C00, 0xBF20FC00, sizeConstraints, InstName.UminAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x11CC0000, 0x7FFC0000, InstName.UminImm, IsaVersion.v89, InstFlags.RdRn), + new(0x1AC06C00, 0x7FE0FC00, InstName.UminReg, IsaVersion.v89, InstFlags.RdRnRm), + new(0x2F002000, 0xBF00F400, sizeSizeConstraints, InstName.UmlalAdvsimdElt, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E208000, 0xBF20FC00, sizeConstraints, InstName.UmlalAdvsimdVec, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2F006000, 0xBF00F400, sizeSizeConstraints, InstName.UmlslAdvsimdElt, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E20A000, 0xBF20FC00, sizeConstraints, InstName.UmlslAdvsimdVec, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x6E80A400, 0xFFE0FC00, InstName.UmmlaAdvsimdVec, IsaVersion.v86, InstFlags.RdRnRmFpSimd), + new(0x0E013C00, 0xFFE1FC00, InstName.UmovAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x0E023C00, 0xFFE3FC00, InstName.UmovAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x0E043C00, 0xFFE7FC00, InstName.UmovAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x4E083C00, 0xFFEFFC00, InstName.UmovAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimdToGpr), + new(0x9BA08000, 0xFFE08000, InstName.Umsubl, IsaVersion.v80, InstFlags.RdRnRmRa), + new(0x9BC07C00, 0xFFE0FC00, InstName.Umulh, IsaVersion.v80, InstFlags.RdRnRm), + new(0x2F00A000, 0xBF00F400, sizeSizeConstraints, InstName.UmullAdvsimdElt, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E20C000, 0xBF20FC00, sizeConstraints, InstName.UmullAdvsimdVec, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x7E200C00, 0xFF20FC00, InstName.UqaddAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x2E200C00, 0xBF20FC00, qsizeConstraints, InstName.UqaddAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x7E205C00, 0xFF20FC00, ssizeSsizeSsizeConstraints, InstName.UqrshlAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x2E205C00, 0xBF20FC00, qsizeConstraints, InstName.UqrshlAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x7F009C00, 0xFF80FC00, immhImmhConstraints, InstName.UqrshrnAdvsimdS, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x2F009C00, 0xBF80FC00, immhImmhConstraints, InstName.UqrshrnAdvsimdV, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x7F007400, 0xFF80FC00, immhOpuConstraints, InstName.UqshlAdvsimdImmS, IsaVersion.v80, InstFlags.RdRnQcFpSimd), + new(0x2F007400, 0xBF80FC00, immhQimmhOpuConstraints, InstName.UqshlAdvsimdImmV, IsaVersion.v80, InstFlags.RdRnQcFpSimd), + new(0x7E204C00, 0xFF20FC00, ssizeSsizeSsizeConstraints, InstName.UqshlAdvsimdRegS, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x2E204C00, 0xBF20FC00, qsizeConstraints, InstName.UqshlAdvsimdRegV, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x7F009400, 0xFF80FC00, immhImmhConstraints, InstName.UqshrnAdvsimdS, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x2F009400, 0xBF80FC00, immhImmhConstraints, InstName.UqshrnAdvsimdV, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x7E202C00, 0xFF20FC00, InstName.UqsubAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x2E202C00, 0xBF20FC00, qsizeConstraints, InstName.UqsubAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmQcFpSimd), + new(0x7E214800, 0xFF3FFC00, sizeConstraints, InstName.UqxtnAdvsimdS, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x2E214800, 0xBF3FFC00, sizeConstraints, InstName.UqxtnAdvsimdV, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x0EA1C800, 0xBFBFFC00, szConstraints, InstName.UrecpeAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E201400, 0xBF20FC00, sizeConstraints, InstName.UrhaddAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x7E205400, 0xFF20FC00, ssizeSsizeSsizeConstraints, InstName.UrshlAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E205400, 0xBF20FC00, qsizeConstraints, InstName.UrshlAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x7F402400, 0xFFC0FC00, immhConstraints, InstName.UrshrAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2F002400, 0xBF80FC00, immhQimmhConstraints, InstName.UrshrAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2EA1C800, 0xBFBFFC00, szConstraints, InstName.UrsqrteAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x7F403400, 0xFFC0FC00, immhConstraints, InstName.UrsraAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2F003400, 0xBF80FC00, immhQimmhConstraints, InstName.UrsraAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x0F80F000, 0xBFC0F400, InstName.UsdotAdvsimdElt, IsaVersion.v86, InstFlags.RdReadRdRnRmFpSimd), + new(0x0E809C00, 0xBFE0FC00, InstName.UsdotAdvsimdVec, IsaVersion.v86, InstFlags.RdReadRdRnRmFpSimd), + new(0x2F00A400, 0xBF80FC00, immhImmhConstraints, InstName.UshllAdvsimd, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x7E204400, 0xFF20FC00, ssizeSsizeSsizeConstraints, InstName.UshlAdvsimdS, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E204400, 0xBF20FC00, qsizeConstraints, InstName.UshlAdvsimdV, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x7F400400, 0xFFC0FC00, immhConstraints, InstName.UshrAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2F000400, 0xBF80FC00, immhQimmhConstraints, InstName.UshrAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x4E80AC00, 0xFFE0FC00, InstName.UsmmlaAdvsimdVec, IsaVersion.v86, InstFlags.RdRnRmFpSimd), + new(0x7E203800, 0xFF3FFC00, InstName.UsqaddAdvsimdS, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x2E203800, 0xBF3FFC00, qsizeConstraints, InstName.UsqaddAdvsimdV, IsaVersion.v80, InstFlags.RdReadRdRnQcFpSimd), + new(0x7F401400, 0xFFC0FC00, immhConstraints, InstName.UsraAdvsimdS, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2F001400, 0xBF80FC00, immhQimmhConstraints, InstName.UsraAdvsimdV, IsaVersion.v80, InstFlags.RdRnFpSimd), + new(0x2E202000, 0xBF20FC00, sizeConstraints, InstName.UsublAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x2E203000, 0xBF20FC00, sizeConstraints, InstName.UsubwAdvsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E001800, 0xBF20FC00, qsizeConstraints, InstName.Uzp1Advsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E005800, 0xBF20FC00, qsizeConstraints, InstName.Uzp2Advsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0xD503205F, 0xFFFFFFFF, InstName.Wfe, IsaVersion.v80, InstFlags.None), + new(0xD5031000, 0xFFFFFFE0, InstName.Wfet, IsaVersion.v87, InstFlags.Rd), + new(0xD503207F, 0xFFFFFFFF, InstName.Wfi, IsaVersion.v80, InstFlags.None), + new(0xD5031020, 0xFFFFFFE0, InstName.Wfit, IsaVersion.v87, InstFlags.Rd), + new(0xD500403F, 0xFFFFFFFF, InstName.Xaflag, IsaVersion.v85, InstFlags.C), + new(0xCE800000, 0xFFE00000, InstName.XarAdvsimd, IsaVersion.v82, InstFlags.RdRnRmFpSimd), + new(0xDAC143E0, 0xFFFFFBE0, InstName.XpacGeneral, IsaVersion.v83, InstFlags.Rd), + new(0xD50320FF, 0xFFFFFFFF, InstName.XpacSystem, IsaVersion.v83, InstFlags.None), + new(0x0E212800, 0xBF3FFC00, sizeConstraints, InstName.XtnAdvsimd, IsaVersion.v80, InstFlags.RdReadRdRnFpSimd), + new(0xD503203F, 0xFFFFFFFF, InstName.Yield, IsaVersion.v80, InstFlags.None), + new(0x0E003800, 0xBF20FC00, qsizeConstraints, InstName.Zip1Advsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + new(0x0E007800, 0xBF20FC00, qsizeConstraints, InstName.Zip2Advsimd, IsaVersion.v80, InstFlags.RdRnRmFpSimd), + }; + + _table = new(insts); + } + + public static (InstName, InstFlags, AddressForm) GetInstNameAndFlags(uint encoding, IsaVersion version, IsaFeature features) + { + if (_table.TryFind(encoding, version, features, out InstInfo info)) + { + return (info.Name, info.Flags, info.AddressForm); + } + + return new(InstName.UdfPermUndef, InstFlags.None, AddressForm.None); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Cache/CacheEntry.cs b/src/Ryujinx.Cpu/LightningJit/Cache/CacheEntry.cs new file mode 100644 index 00000000..0249e24b --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Cache/CacheEntry.cs @@ -0,0 +1,22 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.Cpu.LightningJit.Cache +{ + readonly struct CacheEntry : IComparable + { + public int Offset { get; } + public int Size { get; } + + public CacheEntry(int offset, int size) + { + Offset = offset; + Size = size; + } + + public int CompareTo([AllowNull] CacheEntry other) + { + return Offset.CompareTo(other.Offset); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Cache/CacheMemoryAllocator.cs b/src/Ryujinx.Cpu/LightningJit/Cache/CacheMemoryAllocator.cs new file mode 100644 index 00000000..8ba3a6dc --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Cache/CacheMemoryAllocator.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.Cpu.LightningJit.Cache +{ + class CacheMemoryAllocator + { + private readonly struct MemoryBlock : IComparable + { + public int Offset { get; } + public int Size { get; } + + public MemoryBlock(int offset, int size) + { + Offset = offset; + Size = size; + } + + public int CompareTo([AllowNull] MemoryBlock other) + { + return Offset.CompareTo(other.Offset); + } + } + + private readonly List _blocks = new(); + + public CacheMemoryAllocator(int capacity) + { + _blocks.Add(new MemoryBlock(0, capacity)); + } + + public int Allocate(int size) + { + for (int i = 0; i < _blocks.Count; i++) + { + MemoryBlock block = _blocks[i]; + + if (block.Size > size) + { + _blocks[i] = new(block.Offset + size, block.Size - size); + return block.Offset; + } + else if (block.Size == size) + { + _blocks.RemoveAt(i); + return block.Offset; + } + } + + // We don't have enough free memory to perform the allocation. + return -1; + } + + public void ForceAllocation(int offset, int size) + { + int index = _blocks.BinarySearch(new(offset, size)); + + if (index < 0) + { + index = ~index; + } + + int endOffset = offset + size; + + MemoryBlock block = _blocks[index]; + + Debug.Assert(block.Offset <= offset && block.Offset + block.Size >= endOffset); + + if (offset > block.Offset && endOffset < block.Offset + block.Size) + { + _blocks[index] = new(block.Offset, offset - block.Offset); + _blocks.Insert(index + 1, new(endOffset, (block.Offset + block.Size) - endOffset)); + } + else if (offset > block.Offset) + { + _blocks[index] = new(block.Offset, offset - block.Offset); + } + else if (endOffset < block.Offset + block.Size) + { + _blocks[index] = new(endOffset, (block.Offset + block.Size) - endOffset); + } + else + { + _blocks.RemoveAt(index); + } + } + + public void Free(int offset, int size) + { + Insert(new MemoryBlock(offset, size)); + } + + private void Insert(MemoryBlock block) + { + int index = _blocks.BinarySearch(block); + + if (index < 0) + { + index = ~index; + } + + if (index < _blocks.Count) + { + MemoryBlock next = _blocks[index]; + + int endOffs = block.Offset + block.Size; + + if (next.Offset == endOffs) + { + block = new MemoryBlock(block.Offset, block.Size + next.Size); + _blocks.RemoveAt(index); + } + } + + if (index > 0) + { + MemoryBlock prev = _blocks[index - 1]; + + if (prev.Offset + prev.Size == block.Offset) + { + block = new MemoryBlock(block.Offset - prev.Size, block.Size + prev.Size); + _blocks.RemoveAt(--index); + } + } + + _blocks.Insert(index, block); + } + + public void Clear() + { + _blocks.Clear(); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs b/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs new file mode 100644 index 00000000..6f1191ca --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs @@ -0,0 +1,197 @@ +using ARMeilleure.Memory; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Cpu.LightningJit.Cache +{ + static partial class JitCache + { + private static readonly int _pageSize = (int)MemoryBlock.GetPageSize(); + private static readonly int _pageMask = _pageSize - 1; + + private const int CodeAlignment = 4; // Bytes. + private const int CacheSize = 2047 * 1024 * 1024; + + private static ReservedRegion _jitRegion; + private static JitCacheInvalidation _jitCacheInvalidator; + + private static CacheMemoryAllocator _cacheAllocator; + + private static readonly List _cacheEntries = new(); + + private static readonly object _lock = new(); + private static bool _initialized; + + [SupportedOSPlatform("windows")] + [LibraryImport("kernel32.dll", SetLastError = true)] + public static partial IntPtr FlushInstructionCache(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize); + + public static void Initialize(IJitMemoryAllocator allocator) + { + if (_initialized) + { + return; + } + + lock (_lock) + { + if (_initialized) + { + return; + } + + _jitRegion = new ReservedRegion(allocator, CacheSize); + + if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS()) + { + _jitCacheInvalidator = new JitCacheInvalidation(allocator); + } + + _cacheAllocator = new CacheMemoryAllocator(CacheSize); + + _initialized = true; + } + } + + public unsafe static IntPtr Map(ReadOnlySpan code) + { + lock (_lock) + { + Debug.Assert(_initialized); + + int funcOffset = Allocate(code.Length); + + IntPtr funcPtr = _jitRegion.Pointer + funcOffset; + + if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + unsafe + { + fixed (byte* codePtr = code) + { + JitSupportDarwin.Copy(funcPtr, (IntPtr)codePtr, (ulong)code.Length); + } + } + } + else + { + ReprotectAsWritable(funcOffset, code.Length); + code.CopyTo(new Span((void*)funcPtr, code.Length)); + ReprotectAsExecutable(funcOffset, code.Length); + + if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (UIntPtr)code.Length); + } + else + { + _jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length); + } + } + + Add(funcOffset, code.Length); + + return funcPtr; + } + } + + public static void Unmap(IntPtr pointer) + { + lock (_lock) + { + Debug.Assert(_initialized); + + int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64()); + + if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) + { + _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); + _cacheEntries.RemoveAt(entryIndex); + } + } + } + + private static void ReprotectAsWritable(int offset, int size) + { + int endOffs = offset + size; + + int regionStart = offset & ~_pageMask; + int regionEnd = (endOffs + _pageMask) & ~_pageMask; + + _jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart)); + } + + private static void ReprotectAsExecutable(int offset, int size) + { + int endOffs = offset + size; + + int regionStart = offset & ~_pageMask; + int regionEnd = (endOffs + _pageMask) & ~_pageMask; + + _jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart)); + } + + private static int Allocate(int codeSize) + { + codeSize = AlignCodeSize(codeSize); + + int allocOffset = _cacheAllocator.Allocate(codeSize); + + if (allocOffset < 0) + { + throw new OutOfMemoryException("JIT Cache exhausted."); + } + + _jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); + + return allocOffset; + } + + private static int AlignCodeSize(int codeSize) + { + return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1); + } + + private static void Add(int offset, int size) + { + CacheEntry entry = new(offset, size); + + int index = _cacheEntries.BinarySearch(entry); + + if (index < 0) + { + index = ~index; + } + + _cacheEntries.Insert(index, entry); + } + + public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex) + { + lock (_lock) + { + int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0)); + + if (index < 0) + { + index = ~index - 1; + } + + if (index >= 0) + { + entry = _cacheEntries[index]; + entryIndex = index; + return true; + } + } + + entry = default; + entryIndex = 0; + return false; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Cache/JitCacheInvalidation.cs b/src/Ryujinx.Cpu/LightningJit/Cache/JitCacheInvalidation.cs new file mode 100644 index 00000000..cd5f3ede --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Cache/JitCacheInvalidation.cs @@ -0,0 +1,79 @@ +using ARMeilleure.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.LightningJit.Cache +{ + class JitCacheInvalidation + { + private static readonly int[] _invalidationCode = new int[] + { + unchecked((int)0xd53b0022), // mrs x2, ctr_el0 + unchecked((int)0xd3504c44), // ubfx x4, x2, #16, #4 + unchecked((int)0x52800083), // mov w3, #0x4 + unchecked((int)0x12000c45), // and w5, w2, #0xf + unchecked((int)0x1ac42064), // lsl w4, w3, w4 + unchecked((int)0x51000482), // sub w2, w4, #0x1 + unchecked((int)0x8a220002), // bic x2, x0, x2 + unchecked((int)0x1ac52063), // lsl w3, w3, w5 + unchecked((int)0xeb01005f), // cmp x2, x1 + unchecked((int)0x93407c84), // sxtw x4, w4 + unchecked((int)0x540000a2), // b.cs 3c + unchecked((int)0xd50b7b22), // dc cvau, x2 + unchecked((int)0x8b040042), // add x2, x2, x4 + unchecked((int)0xeb02003f), // cmp x1, x2 + unchecked((int)0x54ffffa8), // b.hi 2c + unchecked((int)0xd5033b9f), // dsb ish + unchecked((int)0x51000462), // sub w2, w3, #0x1 + unchecked((int)0x93407c63), // sxtw x3, w3 + unchecked((int)0x8a220000), // bic x0, x0, x2 + unchecked((int)0xeb00003f), // cmp x1, x0 + unchecked((int)0x540000a9), // b.ls 64 + unchecked((int)0xd50b7520), // ic ivau, x0 + unchecked((int)0x8b030000), // add x0, x0, x3 + unchecked((int)0xeb00003f), // cmp x1, x0 + unchecked((int)0x54ffffa8), // b.hi 54 + unchecked((int)0xd5033b9f), // dsb ish + unchecked((int)0xd5033fdf), // isb + unchecked((int)0xd65f03c0), // ret + }; + + private delegate void InvalidateCache(ulong start, ulong end); + + private readonly InvalidateCache _invalidateCache; + private readonly ReservedRegion _invalidateCacheCodeRegion; + + private readonly bool _needsInvalidation; + + public JitCacheInvalidation(IJitMemoryAllocator allocator) + { + // On macOS and Windows, a different path is used to write to the JIT cache, which does the invalidation. + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + ulong size = (ulong)_invalidationCode.Length * sizeof(int); + ulong mask = (ulong)ReservedRegion.DefaultGranularity - 1; + + size = (size + mask) & ~mask; + + _invalidateCacheCodeRegion = new ReservedRegion(allocator, size); + _invalidateCacheCodeRegion.ExpandIfNeeded(size); + + Marshal.Copy(_invalidationCode, 0, _invalidateCacheCodeRegion.Pointer, _invalidationCode.Length); + + _invalidateCacheCodeRegion.Block.MapAsRx(0, size); + + _invalidateCache = Marshal.GetDelegateForFunctionPointer(_invalidateCacheCodeRegion.Pointer); + + _needsInvalidation = true; + } + } + + public void Invalidate(IntPtr basePointer, ulong size) + { + if (_needsInvalidation) + { + _invalidateCache((ulong)basePointer, (ulong)basePointer + size); + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Cache/JitSupportDarwin.cs b/src/Ryujinx.Cpu/LightningJit/Cache/JitSupportDarwin.cs new file mode 100644 index 00000000..06c81045 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Cache/JitSupportDarwin.cs @@ -0,0 +1,16 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Cpu.LightningJit.Cache +{ + [SupportedOSPlatform("macos")] + static partial class JitSupportDarwin + { + [LibraryImport("libarmeilleure-jitsupport", EntryPoint = "armeilleure_jit_memcpy")] + public static partial void Copy(IntPtr dst, IntPtr src, ulong n); + + [LibraryImport("libc", EntryPoint = "sys_icache_invalidate", SetLastError = true)] + public static partial void SysIcacheInvalidate(IntPtr start, IntPtr len); + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs b/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs new file mode 100644 index 00000000..a7107499 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs @@ -0,0 +1,340 @@ +using ARMeilleure.Memory; +using Ryujinx.Common; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.Cache +{ + class NoWxCache : IDisposable + { + private const int CodeAlignment = 4; // Bytes. + private const int SharedCacheSize = 2047 * 1024 * 1024; + private const int LocalCacheSize = 128 * 1024 * 1024; + + // How many calls to the same function we allow until we pad the shared cache to force the function to become available there + // and allow the guest to take the fast path. + private const int MinCallsForPad = 8; + + private class MemoryCache : IDisposable + { + private readonly ReservedRegion _region; + private readonly CacheMemoryAllocator _cacheAllocator; + + public CacheMemoryAllocator Allocator => _cacheAllocator; + public IntPtr Pointer => _region.Block.Pointer; + + public MemoryCache(IJitMemoryAllocator allocator, ulong size) + { + _region = new(allocator, size); + _cacheAllocator = new((int)size); + } + + public int Allocate(int codeSize) + { + codeSize = AlignCodeSize(codeSize); + + int allocOffset = _cacheAllocator.Allocate(codeSize); + + if (allocOffset < 0) + { + throw new OutOfMemoryException("JIT Cache exhausted."); + } + + _region.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); + + return allocOffset; + } + + public void Free(int offset, int size) + { + _cacheAllocator.Free(offset, size); + } + + public void ReprotectAsRw(int offset, int size) + { + Debug.Assert(offset >= 0 && (offset & (int)(MemoryBlock.GetPageSize() - 1)) == 0); + Debug.Assert(size > 0 && (size & (int)(MemoryBlock.GetPageSize() - 1)) == 0); + + _region.Block.MapAsRw((ulong)offset, (ulong)size); + } + + public void ReprotectAsRx(int offset, int size) + { + Debug.Assert(offset >= 0 && (offset & (int)(MemoryBlock.GetPageSize() - 1)) == 0); + Debug.Assert(size > 0 && (size & (int)(MemoryBlock.GetPageSize() - 1)) == 0); + + _region.Block.MapAsRx((ulong)offset, (ulong)size); + + if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS()) + { + JitSupportDarwin.SysIcacheInvalidate(_region.Block.Pointer + offset, size); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + private static int AlignCodeSize(int codeSize) + { + return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _region.Dispose(); + _cacheAllocator.Clear(); + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } + + private readonly IStackWalker _stackWalker; + private readonly Translator _translator; + private readonly MemoryCache _sharedCache; + private readonly MemoryCache _localCache; + private readonly PageAlignedRangeList _pendingMap; + private readonly object _lock; + + class ThreadLocalCacheEntry + { + public readonly int Offset; + public readonly int Size; + public readonly IntPtr FuncPtr; + private int _useCount; + + public ThreadLocalCacheEntry(int offset, int size, IntPtr funcPtr) + { + Offset = offset; + Size = size; + FuncPtr = funcPtr; + _useCount = 0; + } + + public int IncrementUseCount() + { + return ++_useCount; + } + } + + [ThreadStatic] + private static Dictionary _threadLocalCache; + + public NoWxCache(IJitMemoryAllocator allocator, IStackWalker stackWalker, Translator translator) + { + _stackWalker = stackWalker; + _translator = translator; + _sharedCache = new(allocator, SharedCacheSize); + _localCache = new(allocator, LocalCacheSize); + _pendingMap = new(_sharedCache.ReprotectAsRx, RegisterFunction); + _lock = new(); + } + + public unsafe IntPtr Map(IntPtr framePointer, ReadOnlySpan code, ulong guestAddress, ulong guestSize) + { + if (TryGetThreadLocalFunction(guestAddress, out IntPtr funcPtr)) + { + return funcPtr; + } + + lock (_lock) + { + if (!_pendingMap.Has(guestAddress) && !_translator.Functions.ContainsKey(guestAddress)) + { + int funcOffset = _sharedCache.Allocate(code.Length); + + funcPtr = _sharedCache.Pointer + funcOffset; + code.CopyTo(new Span((void*)funcPtr, code.Length)); + + TranslatedFunction function = new(funcPtr, guestSize); + + _pendingMap.Add(funcOffset, code.Length, guestAddress, function); + } + + ClearThreadLocalCache(framePointer); + + return AddThreadLocalFunction(code, guestAddress); + } + } + + public unsafe IntPtr MapPageAligned(ReadOnlySpan code) + { + lock (_lock) + { + // Ensure we will get an aligned offset from the allocator. + _pendingMap.Pad(_sharedCache.Allocator); + + int sizeAligned = BitUtils.AlignUp(code.Length, (int)MemoryBlock.GetPageSize()); + int funcOffset = _sharedCache.Allocate(sizeAligned); + + Debug.Assert((funcOffset & ((int)MemoryBlock.GetPageSize() - 1)) == 0); + + IntPtr funcPtr = _sharedCache.Pointer + funcOffset; + code.CopyTo(new Span((void*)funcPtr, code.Length)); + + _sharedCache.ReprotectAsRx(funcOffset, sizeAligned); + + return funcPtr; + } + } + + private bool TryGetThreadLocalFunction(ulong guestAddress, out IntPtr funcPtr) + { + if ((_threadLocalCache ??= new()).TryGetValue(guestAddress, out var entry)) + { + if (entry.IncrementUseCount() >= MinCallsForPad) + { + // Function is being called often, let's make it available in the shared cache so that the guest code + // can take the fast path and stop calling the emulator to get the function from the thread local cache. + // To do that we pad all "pending" function until they complete a page of memory, allowing us to reprotect them as RX. + + lock (_lock) + { + _pendingMap.Pad(_sharedCache.Allocator); + } + } + + funcPtr = entry.FuncPtr; + + return true; + } + + funcPtr = IntPtr.Zero; + + return false; + } + + private void ClearThreadLocalCache(IntPtr framePointer) + { + // Try to delete functions that are already on the shared cache + // and no longer being executed. + + if (_threadLocalCache == null) + { + return; + } + + IEnumerable callStack = _stackWalker.GetCallStack( + framePointer, + _localCache.Pointer, + LocalCacheSize, + _sharedCache.Pointer, + SharedCacheSize); + + List<(ulong, ThreadLocalCacheEntry)> toDelete = new(); + + foreach ((ulong address, ThreadLocalCacheEntry entry) in _threadLocalCache) + { + // We only want to delete if the function is already on the shared cache, + // otherwise we will keep translating the same function over and over again. + bool canDelete = !_pendingMap.Has(address); + if (!canDelete) + { + continue; + } + + // We can only delete if the function is not part of the current thread call stack, + // otherwise we will crash the program when the thread returns to it. + foreach (ulong funcAddress in callStack) + { + if (funcAddress >= (ulong)entry.FuncPtr && funcAddress < (ulong)entry.FuncPtr + (ulong)entry.Size) + { + canDelete = false; + break; + } + } + + if (canDelete) + { + toDelete.Add((address, entry)); + } + } + + int pageSize = (int)MemoryBlock.GetPageSize(); + + foreach ((ulong address, ThreadLocalCacheEntry entry) in toDelete) + { + _threadLocalCache.Remove(address); + + int sizeAligned = BitUtils.AlignUp(entry.Size, pageSize); + + _localCache.Free(entry.Offset, sizeAligned); + _localCache.ReprotectAsRw(entry.Offset, sizeAligned); + } + } + + public void ClearEntireThreadLocalCache() + { + // Thread is exiting, delete everything. + + if (_threadLocalCache == null) + { + return; + } + + int pageSize = (int)MemoryBlock.GetPageSize(); + + foreach ((_, ThreadLocalCacheEntry entry) in _threadLocalCache) + { + int sizeAligned = BitUtils.AlignUp(entry.Size, pageSize); + + _localCache.Free(entry.Offset, sizeAligned); + _localCache.ReprotectAsRw(entry.Offset, sizeAligned); + } + + _threadLocalCache.Clear(); + _threadLocalCache = null; + } + + private unsafe IntPtr AddThreadLocalFunction(ReadOnlySpan code, ulong guestAddress) + { + int alignedSize = BitUtils.AlignUp(code.Length, (int)MemoryBlock.GetPageSize()); + int funcOffset = _localCache.Allocate(alignedSize); + + Debug.Assert((funcOffset & (int)(MemoryBlock.GetPageSize() - 1)) == 0); + + IntPtr funcPtr = _localCache.Pointer + funcOffset; + code.CopyTo(new Span((void*)funcPtr, code.Length)); + + (_threadLocalCache ??= new()).Add(guestAddress, new(funcOffset, code.Length, funcPtr)); + + _localCache.ReprotectAsRx(funcOffset, alignedSize); + + return funcPtr; + } + + private void RegisterFunction(ulong address, TranslatedFunction func) + { + TranslatedFunction oldFunc = _translator.Functions.GetOrAdd(address, func.GuestSize, func); + + Debug.Assert(oldFunc == func); + + _translator.RegisterFunction(address, func); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _localCache.Dispose(); + _sharedCache.Dispose(); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Cache/PageAlignedRangeList.cs b/src/Ryujinx.Cpu/LightningJit/Cache/PageAlignedRangeList.cs new file mode 100644 index 00000000..b6b38671 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Cache/PageAlignedRangeList.cs @@ -0,0 +1,218 @@ +using Ryujinx.Common; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.Cpu.LightningJit.Cache +{ + class PageAlignedRangeList + { + private readonly struct Range : IComparable + { + public int Offset { get; } + public int Size { get; } + + public Range(int offset, int size) + { + Offset = offset; + Size = size; + } + + public int CompareTo([AllowNull] Range other) + { + return Offset.CompareTo(other.Offset); + } + } + + private readonly Action _alignedRangeAction; + private readonly Action _alignedFunctionAction; + private readonly List<(Range, ulong, TranslatedFunction)> _pendingFunctions; + private readonly List _ranges; + + public PageAlignedRangeList(Action alignedRangeAction, Action alignedFunctionAction) + { + _alignedRangeAction = alignedRangeAction; + _alignedFunctionAction = alignedFunctionAction; + _pendingFunctions = new(); + _ranges = new(); + } + + public bool Has(ulong address) + { + foreach ((_, ulong guestAddress, _) in _pendingFunctions) + { + if (guestAddress == address) + { + return true; + } + } + + return false; + } + + public void Add(int offset, int size, ulong address, TranslatedFunction function) + { + Range range = new(offset, size); + + Insert(range); + _pendingFunctions.Add((range, address, function)); + ProcessAlignedRanges(); + } + + public void Pad(CacheMemoryAllocator allocator) + { + int pageSize = (int)MemoryBlock.GetPageSize(); + + for (int index = 0; index < _ranges.Count; index++) + { + Range range = _ranges[index]; + + int endOffset = range.Offset + range.Size; + + int alignedStart = BitUtils.AlignDown(range.Offset, pageSize); + int alignedEnd = BitUtils.AlignUp(endOffset, pageSize); + int alignedSize = alignedEnd - alignedStart; + + if (alignedStart < range.Offset) + { + allocator.ForceAllocation(alignedStart, range.Offset - alignedStart); + } + + if (alignedEnd > endOffset) + { + allocator.ForceAllocation(endOffset, alignedEnd - endOffset); + } + + _alignedRangeAction(alignedStart, alignedSize); + _ranges.RemoveAt(index--); + ProcessPendingFunctions(index, alignedEnd); + } + } + + private void ProcessAlignedRanges() + { + int pageSize = (int)MemoryBlock.GetPageSize(); + + for (int index = 0; index < _ranges.Count; index++) + { + Range range = _ranges[index]; + + int alignedStart = BitUtils.AlignUp(range.Offset, pageSize); + int alignedEnd = BitUtils.AlignDown(range.Offset + range.Size, pageSize); + int alignedSize = alignedEnd - alignedStart; + + if (alignedSize <= 0) + { + continue; + } + + _alignedRangeAction(alignedStart, alignedSize); + SplitAt(ref index, alignedStart, alignedEnd); + ProcessPendingFunctions(index, alignedEnd); + } + } + + private void ProcessPendingFunctions(int rangeIndex, int alignedEnd) + { + if ((rangeIndex > 0 && rangeIndex == _ranges.Count) || + (rangeIndex >= 0 && rangeIndex < _ranges.Count && _ranges[rangeIndex].Offset >= alignedEnd)) + { + rangeIndex--; + } + + int alignedStart; + + if (rangeIndex >= 0) + { + alignedStart = _ranges[rangeIndex].Offset + _ranges[rangeIndex].Size; + } + else + { + alignedStart = 0; + } + + if (rangeIndex < _ranges.Count - 1) + { + alignedEnd = _ranges[rangeIndex + 1].Offset; + } + else + { + alignedEnd = int.MaxValue; + } + + for (int index = 0; index < _pendingFunctions.Count; index++) + { + (Range range, ulong address, TranslatedFunction function) = _pendingFunctions[index]; + + if (range.Offset >= alignedStart && range.Offset + range.Size <= alignedEnd) + { + _alignedFunctionAction(address, function); + _pendingFunctions.RemoveAt(index--); + } + } + } + + private void Insert(Range range) + { + int index = _ranges.BinarySearch(range); + + if (index < 0) + { + index = ~index; + } + + if (index < _ranges.Count) + { + Range next = _ranges[index]; + + int endOffs = range.Offset + range.Size; + + if (next.Offset == endOffs) + { + range = new Range(range.Offset, range.Size + next.Size); + _ranges.RemoveAt(index); + } + } + + if (index > 0) + { + Range prev = _ranges[index - 1]; + + if (prev.Offset + prev.Size == range.Offset) + { + range = new Range(range.Offset - prev.Size, range.Size + prev.Size); + _ranges.RemoveAt(--index); + } + } + + _ranges.Insert(index, range); + } + + private void SplitAt(ref int index, int alignedStart, int alignedEnd) + { + Range range = _ranges[index]; + + if (range.Offset < alignedStart) + { + _ranges[index++] = new(range.Offset, alignedStart - range.Offset); + + if (range.Offset + range.Size > alignedEnd) + { + _ranges.Insert(index, new(alignedEnd, (range.Offset + range.Size) - alignedEnd)); + } + } + else if (range.Offset + range.Size > alignedEnd) + { + _ranges[index] = new(alignedEnd, (range.Offset + range.Size) - alignedEnd); + } + else if (range.Offset == alignedStart && range.Offset + range.Size == alignedEnd) + { + Debug.Assert(range.Offset == alignedStart && range.Offset + range.Size == alignedEnd); + + _ranges.RemoveAt(index--); + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/AbiConstants.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/AbiConstants.cs new file mode 100644 index 00000000..24a646ca --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/AbiConstants.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64 +{ + static class AbiConstants + { + // Some of those register have specific roles and can't be used as general purpose registers. + // X18 - Reserved for platform specific usage. + // X29 - Frame pointer. + // X30 - Return address. + // X31 - Not an actual register, in some cases maps to SP, and in others to ZR. + public const uint ReservedRegsMask = (1u << 18) | (1u << 29) | (1u << 30) | (1u << 31); + + public const uint GprCalleeSavedRegsMask = 0x1ff80000; // X19 to X28 + public const uint FpSimdCalleeSavedRegsMask = 0xff00; // D8 to D15 + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmCondition.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmCondition.cs new file mode 100644 index 00000000..caa2e593 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmCondition.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64 +{ + enum ArmCondition + { + Eq = 0, + Ne = 1, + GeUn = 2, + LtUn = 3, + Mi = 4, + Pl = 5, + Vs = 6, + Vc = 7, + GtUn = 8, + LeUn = 9, + Ge = 10, + Lt = 11, + Gt = 12, + Le = 13, + Al = 14, + Nv = 15, + } + + static class ArmConditionExtensions + { + public static ArmCondition Invert(this ArmCondition condition) + { + return (ArmCondition)((int)condition ^ 1); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmExtensionType.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmExtensionType.cs new file mode 100644 index 00000000..abcf7678 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmExtensionType.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64 +{ + enum ArmExtensionType + { + Uxtb = 0, + Uxth = 1, + Uxtw = 2, + Uxtx = 3, + Sxtb = 4, + Sxth = 5, + Sxtw = 6, + Sxtx = 7, + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmShiftType.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmShiftType.cs new file mode 100644 index 00000000..348edd48 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/ArmShiftType.cs @@ -0,0 +1,11 @@ + +namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64 +{ + enum ArmShiftType + { + Lsl = 0, + Lsr = 1, + Asr = 2, + Ror = 3, + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/Assembler.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/Assembler.cs new file mode 100644 index 00000000..28539707 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/Assembler.cs @@ -0,0 +1,4777 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64 +{ + struct Assembler + { + private const uint SfFlag = 1u << 31; + + public const int SpRegister = 31; + public const int ZrRegister = 31; + + private readonly List _code; + + private class LabelState + { + public int BranchIndex; + public int TargetIndex; + public bool HasBranch; + public bool HasTarget; + } + + private readonly List _labels; + + public Assembler(CodeWriter writer) + { + _code = writer.GetList(); + _labels = new List(); + } + + public readonly Operand CreateLabel() + { + int labelIndex = _labels.Count; + _labels.Add(new LabelState()); + + return new Operand(OperandKind.Label, OperandType.None, (ulong)labelIndex); + } + + public readonly void MarkLabel(Operand label) + { + int targetIndex = _code.Count; + + var state = _labels[label.AsInt32()]; + + state.TargetIndex = targetIndex; + state.HasTarget = true; + + if (state.HasBranch) + { + int imm = (targetIndex - state.BranchIndex) * sizeof(uint); + uint code = _code[state.BranchIndex]; + + if ((code & 0xfc000000u) == 0x14000000u) + { + _code[state.BranchIndex] = code | EncodeSImm26_2(imm); + } + else + { + _code[state.BranchIndex] = code | (EncodeSImm19_2(imm) << 5); + } + } + } + + // Base + + public readonly void B(Operand label, ArmCondition condition = ArmCondition.Al) + { + int branchIndex = _code.Count; + + var state = _labels[label.AsInt32()]; + + state.BranchIndex = branchIndex; + state.HasBranch = true; + + int imm = 0; + + if (state.HasTarget) + { + imm = (state.TargetIndex - branchIndex) * sizeof(uint); + } + + if (condition == ArmCondition.Al) + { + B(imm); + } + else + { + B(condition, imm); + } + } + + public readonly void Cbz(Operand rt, Operand label) + { + int branchIndex = _code.Count; + + var state = _labels[label.AsInt32()]; + + state.BranchIndex = branchIndex; + state.HasBranch = true; + + int imm = 0; + + if (state.HasTarget) + { + imm = (state.TargetIndex - branchIndex) * sizeof(uint); + } + + Cbz(rt, imm); + } + + public readonly void Cbnz(Operand rt, Operand label) + { + int branchIndex = _code.Count; + + var state = _labels[label.AsInt32()]; + + state.BranchIndex = branchIndex; + state.HasBranch = true; + + int imm = 0; + + if (state.HasTarget) + { + imm = (state.TargetIndex - branchIndex) * sizeof(uint); + } + + Cbnz(rt, imm); + } + + public readonly void Adc(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x1a000000u, rd, rn, rm); + } + + public readonly void Adcs(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x3a000000u, rd, rn, rm); + } + + public readonly void Add(Operand rd, Operand rn, Operand rm, ArmExtensionType extensionType, int shiftAmount = 0) + { + WriteInstructionAuto(0x0b200000u, rd, rn, rm, extensionType, shiftAmount); + } + + public readonly void Add(Operand rd, Operand rn, Operand rm) + { + Add(rd, rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Add(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + Add(rd, rn, rm, shiftType, shiftAmount, false); + } + + public readonly void Add(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount, bool immForm) + { + WriteInstructionAuto(0x11000000u, 0x0b000000u, rd, rn, rm, shiftType, shiftAmount, immForm); + } + + public readonly void Adds(Operand rd, Operand rn, Operand rm) + { + Adds(rd, rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Adds(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + Adds(rd, rn, rm, shiftType, shiftAmount, false); + } + + public readonly void Adds(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount, bool immForm) + { + WriteInstructionAuto(0x31000000u, 0x2b000000u, rd, rn, rm, shiftType, shiftAmount, immForm); + } + + public readonly void And(Operand rd, Operand rn, Operand rm) + { + And(rd, rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void And(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + WriteInstructionBitwiseAuto(0x12000000u, 0x0a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public readonly void Ands(Operand rd, Operand rn, Operand rm) + { + Ands(rd, rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Ands(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + WriteInstructionBitwiseAuto(0x72000000u, 0x6a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public readonly void Asr(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Sbfm(rd, rn, shift, mask); + } + else + { + Asrv(rd, rn, rm); + } + } + + public readonly void Asrv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02800u, rd, rn, rm); + } + + public readonly void B(int imm) + { + WriteUInt32(0x14000000u | EncodeSImm26_2(imm)); + } + + public readonly void B(ArmCondition condition, int imm) + { + WriteUInt32(0x54000000u | (uint)condition | (EncodeSImm19_2(imm) << 5)); + } + + public readonly void Bfc(Operand rd, int lsb, int width) + { + Bfi(rd, new Operand(ZrRegister, RegisterType.Integer, OperandType.I32), lsb, width); + } + + public readonly void Bfi(Operand rd, Operand rn, int lsb, int width) + { + Bfm(rd, rn, -lsb & 31, width - 1); + } + + public readonly void Bfm(Operand rd, Operand rn, int immr, int imms) + { + WriteInstruction(0x33000000u | (EncodeUImm6(imms) << 10) | (EncodeUImm6(immr) << 16), rd, rn); + } + + public readonly void Bfxil(Operand rd, Operand rn, int lsb, int width) + { + Bfm(rd, rn, lsb, lsb + width - 1); + } + + public readonly void Bic(Operand rd, Operand rn, Operand rm) + { + Bic(rd, rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Bic(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + WriteInstructionAuto(0x0a200000u, rd, rn, rm, shiftType, shiftAmount); + } + + public readonly void Bics(Operand rd, Operand rn, Operand rm) + { + Bics(rd, rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Bics(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + WriteInstructionAuto(0x6a200000u, rd, rn, rm, shiftType, shiftAmount); + } + + public readonly void Blr(Operand rn) + { + WriteUInt32(0xd63f0000u | (EncodeReg(rn) << 5)); + } + + public readonly void Br(Operand rn) + { + WriteUInt32(0xd61f0000u | (EncodeReg(rn) << 5)); + } + + public readonly void Brk() + { + WriteUInt32(0xd4200000u); + } + + public readonly void Cbz(Operand rt, int imm) + { + WriteInstructionAuto(0x34000000u | (EncodeSImm19_2(imm) << 5), rt); + } + + public readonly void Cbnz(Operand rt, int imm) + { + WriteInstructionAuto(0x35000000u | (EncodeSImm19_2(imm) << 5), rt); + } + + public readonly void Crc32(Operand rd, Operand rn, Operand rm, uint sz) + { + Debug.Assert(sz <= 3); + WriteInstructionRm16(0x1ac04000u | (sz << 10), rd, rn, rm); + } + + public readonly void Crc32c(Operand rd, Operand rn, Operand rm, uint sz) + { + Debug.Assert(sz <= 3); + WriteInstructionRm16(0x1ac05000u | (sz << 10), rd, rn, rm); + } + + public readonly void Clrex(int crm = 15) + { + WriteUInt32(0xd503305fu | (EncodeUImm4(crm) << 8)); + } + + public readonly void Clz(Operand rd, Operand rn) + { + WriteInstructionAuto(0x5ac01000u, rd, rn); + } + + public readonly void Cmn(Operand rn, Operand rm) + { + Cmn(rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Cmn(Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + Adds(new Operand(ZrRegister, RegisterType.Integer, rn.Type), rn, rm, shiftType, shiftAmount); + } + + public readonly void Cmp(Operand rn, Operand rm) + { + Cmp(rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Cmp(Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + Subs(new Operand(ZrRegister, RegisterType.Integer, rn.Type), rn, rm, shiftType, shiftAmount); + } + + public readonly void Csdb() + { + WriteUInt32(0xd503229fu); + } + + public readonly void Csel(Operand rd, Operand rn, Operand rm, ArmCondition condition) + { + WriteInstructionBitwiseAuto(0x1a800000u | ((uint)condition << 12), rd, rn, rm); + } + + public readonly void Cset(Operand rd, ArmCondition condition) + { + var zr = new Operand(ZrRegister, RegisterType.Integer, rd.Type); + Csinc(rd, zr, zr, (ArmCondition)((int)condition ^ 1)); + } + + public readonly void Csinc(Operand rd, Operand rn, Operand rm, ArmCondition condition) + { + WriteInstructionBitwiseAuto(0x1a800400u | ((uint)condition << 12), rd, rn, rm); + } + + public readonly void Dmb(uint option) + { + WriteUInt32(0xd50330bfu | (option << 8)); + } + + public readonly void Dsb(uint option) + { + WriteUInt32(0xd503309fu | (option << 8)); + } + + public readonly void Eor(Operand rd, Operand rn, Operand rm) + { + Eor(rd, rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Eor(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + WriteInstructionBitwiseAuto(0x52000000u, 0x4a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public readonly void Eors(Operand rd, Operand rn, Operand rm) + { + Eors(rd, rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Eors(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + Eor(rd, rn, rm, shiftType, shiftAmount); + Tst(rd, rd); + } + + public readonly void Esb() + { + WriteUInt32(0xd503221fu); + } + + public readonly void Extr(Operand rd, Operand rn, Operand rm, int imms) + { + uint n = rd.Type == OperandType.I64 ? 1u << 22 : 0u; + WriteInstructionBitwiseAuto(0x13800000u | n | (EncodeUImm6(imms) << 10), rd, rn, rm); + } + + public readonly void Isb(uint option) + { + WriteUInt32(0xd50330dfu | (option << 8)); + } + + public readonly void Ldar(Operand rt, Operand rn) + { + WriteInstruction(0x88dffc00u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn); + } + + public readonly void Ldarb(Operand rt, Operand rn) + { + WriteInstruction(0x08dffc00u, rt, rn); + } + + public readonly void Ldarh(Operand rt, Operand rn) + { + WriteInstruction(0x48dffc00u, rt, rn); + } + + public readonly void Ldaxp(Operand rt, Operand rt2, Operand rn) + { + WriteInstruction(0x887f8000u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn, rt2); + } + + public readonly void Ldaxr(Operand rt, Operand rn) + { + WriteInstruction(0x885ffc00u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn); + } + + public readonly void Ldaxrb(Operand rt, Operand rn) + { + WriteInstruction(0x085ffc00u, rt, rn); + } + + public readonly void Ldaxrh(Operand rt, Operand rn) + { + WriteInstruction(0x485ffc00u, rt, rn); + } + + public readonly void LdpRiPost(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x28c00000u, 0x2cc00000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public readonly void LdpRiPre(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29c00000u, 0x2dc00000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public readonly void LdpRiUn(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29400000u, 0x2d400000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public readonly void LdrLit(Operand rt, int offset) + { + uint instruction = 0x18000000u | (EncodeSImm19_2(offset) << 5); + + if (rt.Type == OperandType.I64) + { + instruction |= 1u << 30; + } + + WriteInstruction(instruction, rt); + } + + public readonly void LdrRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8400400u, 0x3c400400u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void LdrRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8400c00u, 0x3c400c00u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void LdrRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb9400000u, 0x3d400000u, rt.Type) | (EncodeUImm12(imm, rt.Type) << 10); + WriteInstruction(instruction, rt, rn); + } + + public readonly void LdrRiUn(Operand rt, Operand rn, int imm, uint size) + { + uint instruction = GetLdrStrInstruction(0xb9400000u, 0x3d400000u, rt.Type, size) | (EncodeUImm12(imm, (int)size) << 10); + WriteInstruction(instruction, rt, rn); + } + + public readonly void LdrRr(Operand rt, Operand rn, Operand rm, ArmExtensionType extensionType, bool shift) + { + uint instruction = GetLdrStrInstruction(0xb8600800u, 0x3ce00800u, rt.Type); + WriteInstructionLdrStrAuto(instruction, rt, rn, rm, extensionType, shift); + } + + public readonly void LdrbRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38400400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void LdrbRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38400c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void LdrbRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x39400000u | (EncodeUImm12(imm, 0) << 10); + WriteInstruction(instruction, rt, rn); + } + + public readonly void LdrhRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78400400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void LdrhRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78400c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void LdrhRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x79400000u | (EncodeUImm12(imm, 1) << 10); + WriteInstruction(instruction, rt, rn); + } + + public readonly void LdrsbRiPost(Operand rt, Operand rn, int imm) + { + WriteInstruction(0x38800400u | (EncodeSImm9(imm) << 12), rt, rn); + } + + public readonly void LdrsbRiPre(Operand rt, Operand rn, int imm) + { + WriteInstruction(0x38800c00u | (EncodeSImm9(imm) << 12), rt, rn); + } + + public readonly void LdrsbRiUn(Operand rt, Operand rn, int imm) + { + WriteInstruction(0x39800000u | (EncodeUImm12(imm, 0) << 10), rt, rn); + } + + public readonly void LdrshRiPost(Operand rt, Operand rn, int imm) + { + WriteInstruction(0x78800400u | (EncodeSImm9(imm) << 12), rt, rn); + } + + public readonly void LdrshRiPre(Operand rt, Operand rn, int imm) + { + WriteInstruction(0x78800c00u | (EncodeSImm9(imm) << 12), rt, rn); + } + + public readonly void LdrshRiUn(Operand rt, Operand rn, int imm) + { + WriteInstruction(0x79800000u | (EncodeUImm12(imm, 1) << 10), rt, rn); + } + + public readonly void LdrswRiPost(Operand rt, Operand rn, int imm) + { + WriteInstruction(0xb8800400u | (EncodeSImm9(imm) << 12), rt, rn); + } + + public readonly void LdrswRiPre(Operand rt, Operand rn, int imm) + { + WriteInstruction(0xb8800c00u | (EncodeSImm9(imm) << 12), rt, rn); + } + + public readonly void LdrswRiUn(Operand rt, Operand rn, int imm) + { + WriteInstruction(0xb9800000u | (EncodeUImm12(imm, 2) << 10), rt, rn); + } + + public readonly void Ldur(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8400000u, 0x3c400000u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void Ldur(Operand rt, Operand rn, int imm, uint size) + { + uint instruction = GetLdrStrInstruction(0xb8400000u, 0x3c400000u, rt.Type, size) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void Ldurb(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38400000u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void Ldurh(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78400000u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void Ldursb(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38800000u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void Ldursh(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78800000u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void Ldursw(Operand rt, Operand rn, int imm) + { + uint instruction = 0xb8800000u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void Lsl(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Ubfm(rd, rn, -shift & mask, mask - shift); + } + else + { + Lslv(rd, rn, rm); + } + } + + public readonly void Lslv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02000u, rd, rn, rm); + } + + public readonly void Lsr(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Ubfm(rd, rn, shift, mask); + } + else + { + Lsrv(rd, rn, rm); + } + } + + public readonly void Lsrv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02400u, rd, rn, rm); + } + + public readonly void Madd(Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteInstructionAuto(0x1b000000u, rd, rn, rm, ra); + } + + public readonly void Msub(Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteInstructionAuto(0x1b008000u, rd, rn, rm, ra); + } + + public readonly void Mul(Operand rd, Operand rn, Operand rm) + { + Madd(rd, rn, rm, new Operand(ZrRegister, RegisterType.Integer, rd.Type)); + } + + public readonly void Mov(Operand rd, Operand rn) + { + Debug.Assert(rd.Type.IsInteger()); + Orr(rd, new Operand(ZrRegister, RegisterType.Integer, rd.Type), rn); + } + + public readonly void MovSp(Operand rd, Operand rn) + { + if (rd.GetRegister().Index == SpRegister || + rn.GetRegister().Index == SpRegister) + { + Add(rd, rn, new Operand(rd.Type, 0), ArmShiftType.Lsl, 0, immForm: true); + } + else + { + Mov(rd, rn); + } + } + + public readonly void Mov(Operand rd, ulong value) + { + if (value == 0) + { + Mov(rd, new Operand(ZrRegister, RegisterType.Integer, rd.Type)); + } + else if (CodeGenCommon.TryEncodeBitMask(rd.Type, value, out _, out _, out _)) + { + Orr(rd, new Operand(ZrRegister, RegisterType.Integer, rd.Type), new Operand(OperandKind.Constant, rd.Type, value)); + } + else if (value == ulong.MaxValue || (value == uint.MaxValue && rd.Type == OperandType.I32)) + { + Mvn(rd, new Operand(ZrRegister, RegisterType.Integer, rd.Type)); + } + else + { + int hw = 0; + bool first = true; + + while (value != 0) + { + int valueLow = (ushort)value; + if (valueLow != 0) + { + if (first) + { + Movz(rd, valueLow, hw); + first = false; + } + else + { + Movk(rd, valueLow, hw); + } + } + + hw++; + value >>= 16; + } + } + } + + public readonly void Mov(Operand rd, int imm) + { + Movz(rd, imm, 0); + } + + public readonly void Movz(Operand rd, int imm, int hw) + { + Debug.Assert((hw & (rd.Type == OperandType.I64 ? 3 : 1)) == hw); + WriteInstructionAuto(0x52800000u | (EncodeUImm16(imm) << 5) | ((uint)hw << 21), rd); + } + + public readonly void Movk(Operand rd, int imm, int hw) + { + Debug.Assert((hw & (rd.Type == OperandType.I64 ? 3 : 1)) == hw); + WriteInstructionAuto(0x72800000u | (EncodeUImm16(imm) << 5) | ((uint)hw << 21), rd); + } + + public readonly void Mrs(Operand rt, uint o0, uint op1, uint crn, uint crm, uint op2) + { + WriteSysRegInstruction(0xd5300000u, rt, o0, op1, crn, crm, op2); + } + + public readonly void MrsNzcv(Operand rt) + { + WriteInstruction(0xd53b4200u, rt); + } + + public readonly void MrsFpcr(Operand rt) + { + WriteInstruction(0xd53b4400u, rt); + } + + public readonly void MrsFpsr(Operand rt) + { + WriteInstruction(0xd53b4420u, rt); + } + + public readonly void MrsTpidrEl0(Operand rt) + { + WriteInstruction(0xd53bd040u, rt); + } + + public readonly void MrsTpidrroEl0(Operand rt) + { + WriteInstruction(0xd53bd060u, rt); + } + + public readonly void Msr(Operand rt, uint o0, uint op1, uint crn, uint crm, uint op2) + { + WriteSysRegInstruction(0xd5100000u, rt, o0, op1, crn, crm, op2); + } + + public readonly void MsrNzcv(Operand rt) + { + WriteInstruction(0xd51b4200u, rt); + } + + public readonly void MsrFpcr(Operand rt) + { + WriteInstruction(0xd51b4400, rt); + } + + public readonly void MsrFpsr(Operand rt) + { + WriteInstruction(0xd51b4420, rt); + } + + public readonly void Mvn(Operand rd, Operand rn, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Orn(rd, new Operand(ZrRegister, RegisterType.Integer, rd.Type), rn, shiftType, shiftAmount); + } + + public readonly void Neg(Operand rd, Operand rn, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Sub(rd, new Operand(ZrRegister, RegisterType.Integer, rd.Type), rn, shiftType, shiftAmount); + } + + public readonly void Negs(Operand rd, Operand rn, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Subs(rd, new Operand(ZrRegister, RegisterType.Integer, rd.Type), rn, shiftType, shiftAmount); + } + + public readonly void Orn(Operand rd, Operand rn, Operand rm) + { + Orn(rd, rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Orn(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + WriteInstructionBitwiseAuto(0x2a200000u, rd, rn, rm, shiftType, shiftAmount); + } + + public readonly void Orns(Operand rd, Operand rn, Operand rm) + { + Orns(rd, rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Orns(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + Orn(rd, rn, rm, shiftType, shiftAmount); + Tst(rd, rd); + } + + public readonly void Orr(Operand rd, Operand rn, Operand rm) + { + Orr(rd, rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Orr(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + WriteInstructionBitwiseAuto(0x32000000u, 0x2a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public readonly void Orrs(Operand rd, Operand rn, Operand rm) + { + Orrs(rd, rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Orrs(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + Orr(rd, rn, rm, shiftType, shiftAmount); + Tst(rd, rd); + } + + public readonly void PrfmI(Operand rn, int imm, uint type, uint target, uint policy) + { + Operand rt = new Operand((int)EncodeTypeTargetPolicy(type, target, policy), RegisterType.Integer, OperandType.I32); + WriteInstruction(0xf9800000u | (EncodeUImm12(imm, 3) << 10), rt, rn); + } + + public readonly void PrfmR(Operand rt, Operand rn) + { + WriteInstruction(0xf8a04800u, rt, rn); + } + + public readonly void Prfum(Operand rn, int imm, uint type, uint target, uint policy) + { + Operand rt = new Operand((int)EncodeTypeTargetPolicy(type, target, policy), RegisterType.Integer, OperandType.I32); + WriteInstruction(0xf8800000u | (EncodeSImm9(imm) << 12), rt, rn); + } + + public readonly void Rbit(Operand rd, Operand rn) + { + WriteInstructionAuto(0x5ac00000u, rd, rn); + } + + public readonly void Ret() + { + Ret(new Operand(30, RegisterType.Integer, OperandType.I64)); + } + + public readonly void Ret(Operand rn) + { + WriteUInt32(0xd65f0000u | (EncodeReg(rn) << 5)); + } + + public readonly void Rev(Operand rd, Operand rn) + { + uint opc0 = rd.Type == OperandType.I64 ? 1u << 10 : 0u; + WriteInstructionAuto(0x5ac00800u | opc0, rd, rn); + } + + public readonly void Rev16(Operand rd, Operand rn) + { + WriteInstructionAuto(0x5ac00400u, rd, rn); + } + + public readonly void Rev32(Operand rd, Operand rn) + { + WriteInstructionAuto(0xdac00800u, rd, rn); + } + + public readonly void Ror(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Extr(rd, rn, rn, shift); + } + else + { + Rorv(rd, rn, rm); + } + } + + public readonly void Rorv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02c00u, rd, rn, rm); + } + + public readonly void Sbc(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x5a000000u, rd, rn, rm); + } + + public readonly void Sbcs(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x7a000000u, rd, rn, rm); + } + + public readonly void Sbfm(Operand rd, Operand rn, int immr, int imms) + { + uint n = rd.Type == OperandType.I64 ? 1u << 22 : 0u; + WriteInstructionAuto(0x13000000u | n | (EncodeUImm6(imms) << 10) | (EncodeUImm6(immr) << 16), rd, rn); + } + + public readonly void Sbfx(Operand rd, Operand rn, int lsb, int width) + { + Sbfm(rd, rn, lsb, lsb + width - 1); + } + + public readonly void Sdiv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16Auto(0x1ac00c00u, rd, rn, rm); + } + + public readonly void Sev() + { + WriteUInt32(0xd503209fu); + } + + public readonly void Sevl() + { + WriteUInt32(0xd50320bfu); + } + + public readonly void Smaddl(Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteInstruction(0x9b200000u, rd, rn, rm, ra); + } + + public readonly void Smsubl(Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteInstruction(0x9b208000u, rd, rn, rm, ra); + } + + public readonly void Smulh(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x9b407c00u, rd, rn, rm); + } + + public readonly void Smull(Operand rd, Operand rn, Operand rm) + { + Smaddl(rd, rn, rm, new Operand(ZrRegister, RegisterType.Integer, rd.Type)); + } + + public readonly void Stlr(Operand rt, Operand rn) + { + WriteInstruction(0x889ffc00u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn); + } + + public readonly void Stlrb(Operand rt, Operand rn) + { + WriteInstruction(0x089ffc00u, rt, rn); + } + + public readonly void Stlrh(Operand rt, Operand rn) + { + WriteInstruction(0x489ffc00u, rt, rn); + } + + public readonly void Stlxp(Operand rs, Operand rt, Operand rt2, Operand rn) + { + WriteInstruction(0x88208000u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn, rs, rt2); + } + + public readonly void Stlxr(Operand rs, Operand rt, Operand rn) + { + WriteInstructionRm16(0x8800fc00u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn, rs); + } + + public readonly void Stlxrb(Operand rs, Operand rt, Operand rn) + { + WriteInstructionRm16(0x0800fc00u, rt, rn, rs); + } + + public readonly void Stlxrh(Operand rs, Operand rt, Operand rn) + { + WriteInstructionRm16(0x4800fc00u, rt, rn, rs); + } + + public readonly void StpRiPost(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x28800000u, 0x2c800000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public readonly void StpRiPre(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29800000u, 0x2d800000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public readonly void StpRiUn(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29000000u, 0x2d000000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public readonly void StrRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8000400u, 0x3c000400u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void StrRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8000c00u, 0x3c000c00u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void StrRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb9000000u, 0x3d000000u, rt.Type) | (EncodeUImm12(imm, rt.Type) << 10); + WriteInstruction(instruction, rt, rn); + } + + public readonly void StrRiUn(Operand rt, Operand rn, int imm, uint size) + { + uint instruction = GetLdrStrInstruction(0xb9000000u, 0x3d000000u, rt.Type, size) | (EncodeUImm12(imm, (int)size) << 10); + WriteInstruction(instruction, rt, rn); + } + + public readonly void StrRr(Operand rt, Operand rn, Operand rm, ArmExtensionType extensionType, bool shift) + { + uint instruction = GetLdrStrInstruction(0xb8200800u, 0x3ca00800u, rt.Type); + WriteInstructionLdrStrAuto(instruction, rt, rn, rm, extensionType, shift); + } + + public readonly void StrbRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38000400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void StrbRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38000c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void StrbRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x39000000u | (EncodeUImm12(imm, 0) << 10); + WriteInstruction(instruction, rt, rn); + } + + public readonly void StrhRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78000400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void StrhRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78000c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void StrhRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x79000000u | (EncodeUImm12(imm, 1) << 10); + WriteInstruction(instruction, rt, rn); + } + + public readonly void Stur(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8000000u, 0x3c000000u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void Stur(Operand rt, Operand rn, int imm, uint size) + { + uint instruction = GetLdrStrInstruction(0xb8000000u, 0x3c000000u, rt.Type, size) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void Sturb(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38000000u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void Sturh(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78000000u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public readonly void Sub(Operand rd, Operand rn, Operand rm, ArmExtensionType extensionType, int shiftAmount = 0) + { + WriteInstructionAuto(0x4b200000u, rd, rn, rm, extensionType, shiftAmount); + } + + public readonly void Sub(Operand rd, Operand rn, Operand rm) + { + Sub(rd, rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Sub(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + WriteInstructionAuto(0x51000000u, 0x4b000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public readonly void Subs(Operand rd, Operand rn, Operand rm) + { + Subs(rd, rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Subs(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + WriteInstructionAuto(0x71000000u, 0x6b000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public readonly void Sxtb(Operand rd, Operand rn) + { + Sbfm(rd, rn, 0, 7); + } + + public readonly void Sxth(Operand rd, Operand rn) + { + Sbfm(rd, rn, 0, 15); + } + + public readonly void Sxtw(Operand rd, Operand rn) + { + Sbfm(rd, rn, 0, 31); + } + + public readonly void Tsb() + { + WriteUInt32(0xd503225fu); + } + + public readonly void Tst(Operand rn, Operand rm) + { + Tst(rn, rm, ArmShiftType.Lsl, 0); + } + + public readonly void Tst(Operand rn, Operand rm, ArmShiftType shiftType, int shiftAmount) + { + Ands(new Operand(ZrRegister, RegisterType.Integer, rn.Type), rn, rm, shiftType, shiftAmount); + } + + public readonly void Ubfm(Operand rd, Operand rn, int immr, int imms) + { + uint n = rd.Type == OperandType.I64 ? 1u << 22 : 0u; + WriteInstructionAuto(0x53000000u | n | (EncodeUImm6(imms) << 10) | (EncodeUImm6(immr) << 16), rd, rn); + } + + public readonly void Ubfx(Operand rd, Operand rn, int lsb, int width) + { + Ubfm(rd, rn, lsb, lsb + width - 1); + } + + public readonly void Udiv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16Auto(0x1ac00800u, rd, rn, rm); + } + + public readonly void Umaddl(Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteInstructionAuto(0x9ba00000u, rd, rn, rm, ra); + } + + public readonly void Umulh(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x9bc07c00u, rd, rn, rm); + } + + public readonly void Umull(Operand rd, Operand rn, Operand rm) + { + Umaddl(rd, rn, rm, new Operand(ZrRegister, RegisterType.Integer, rd.Type)); + } + + public readonly void Uxtb(Operand rd, Operand rn) + { + Ubfm(rd, rn, 0, 7); + } + + public readonly void Uxth(Operand rd, Operand rn) + { + Ubfm(rd, rn, 0, 15); + } + + // FP & SIMD + + public readonly void AbsS(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x5e20b800u | (size << 22), rd, rn); + } + + public readonly void AbsV(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e20b800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Addhn(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e204000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void AddpPair(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x5e31b800u | (size << 22), rd, rn); + } + + public readonly void AddpVec(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e20bc00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Addv(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e31b800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void AddS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x5e208400u | (size << 22), rd, rn, rm); + } + + public readonly void AddV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e208400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Aesd(Operand rd, Operand rn) + { + WriteInstruction(0x4e285800u, rd, rn); + } + + public readonly void Aese(Operand rd, Operand rn) + { + WriteInstruction(0x4e284800u, rd, rn); + } + + public readonly void Aesimc(Operand rd, Operand rn) + { + WriteInstruction(0x4e287800u, rd, rn); + } + + public readonly void Aesmc(Operand rd, Operand rn) + { + WriteInstruction(0x4e286800u, rd, rn); + } + + public readonly void And(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0e201c00u | (q << 30), rd, rn, rm); + } + + public readonly void Bcax(Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteInstruction(0xce200000u, rd, rn, rm, ra); + } + + public readonly void Bfcvtn(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0ea16800u | (q << 30), rd, rn); + } + + public readonly void BfcvtFloat(Operand rd, Operand rn) + { + WriteInstruction(0x1e634000u, rd, rn); + } + + public readonly void BfdotElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint q) + { + WriteInstructionRm16(0x0f40f000u | (h << 11) | (m << 20) | (l << 21) | (q << 30), rd, rn, rm); + } + + public readonly void BfdotVec(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2e40fc00u | (q << 30), rd, rn, rm); + } + + public readonly void BfmlalElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint q) + { + WriteInstructionRm16(0x0fc0f000u | (h << 11) | (m << 20) | (l << 21) | (q << 30), rd, rn, rm); + } + + public readonly void BfmlalVec(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2ec0fc00u | (q << 30), rd, rn, rm); + } + + public readonly void Bfmmla(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x6e40ec00u, rd, rn, rm); + } + + public readonly void BicImm(Operand rd, uint h, uint g, uint f, uint e, uint d, uint c, uint b, uint a, uint q) + { + WriteInstruction(0x2f001400u | (h << 5) | (g << 6) | (f << 7) | (e << 8) | (d << 9) | (c << 16) | (b << 17) | (a << 18) | (q << 30), rd); + } + + public readonly void BicReg(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0e601c00u | (q << 30), rd, rn, rm); + } + + public readonly void Bif(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2ee01c00u | (q << 30), rd, rn, rm); + } + + public readonly void Bit(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2ea01c00u | (q << 30), rd, rn, rm); + } + + public readonly void Bsl(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2e601c00u | (q << 30), rd, rn, rm); + } + + public readonly void Cls(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e204800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Clz(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e204800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void CmeqRegS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x7e208c00u | (size << 22), rd, rn, rm); + } + + public readonly void CmeqRegV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e208c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void CmeqZeroS(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x5e209800u | (size << 22), rd, rn); + } + + public readonly void CmeqZeroV(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e209800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void CmgeRegS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x5e203c00u | (size << 22), rd, rn, rm); + } + + public readonly void CmgeRegV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e203c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void CmgeZeroS(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x7e208800u | (size << 22), rd, rn); + } + + public readonly void CmgeZeroV(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e208800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void CmgtRegS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x5e203400u | (size << 22), rd, rn, rm); + } + + public readonly void CmgtRegV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e203400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void CmgtZeroS(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x5e208800u | (size << 22), rd, rn); + } + + public readonly void CmgtZeroV(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e208800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void CmhiS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x7e203400u | (size << 22), rd, rn, rm); + } + + public readonly void CmhiV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e203400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void CmhsS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x7e203c00u | (size << 22), rd, rn, rm); + } + + public readonly void CmhsV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e203c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void CmleS(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x7e209800u | (size << 22), rd, rn); + } + + public readonly void CmleV(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e209800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void CmltS(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x5e20a800u | (size << 22), rd, rn); + } + + public readonly void CmltV(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e20a800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void CmtstS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x5e208c00u | (size << 22), rd, rn, rm); + } + + public readonly void CmtstV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e208c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Cnt(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e205800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void DupEltScalarFromElement(Operand rd, Operand rn, uint imm5) + { + WriteInstruction(0x5e000400u | (imm5 << 16), rd, rn); + } + + public readonly void DupEltVectorFromElement(Operand rd, Operand rn, uint imm5, uint q) + { + WriteInstruction(0x0e000400u | (imm5 << 16) | (q << 30), rd, rn); + } + + public readonly void DupGen(Operand rd, Operand rn, uint imm5, uint q) + { + WriteInstruction(0x0e000c00u | (imm5 << 16) | (q << 30), rd, rn); + } + + public readonly void Eor3(Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteInstruction(0xce000000u, rd, rn, rm, ra); + } + + public readonly void Eor(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2e201c00u | (q << 30), rd, rn, rm); + } + + public readonly void Ext(Operand rd, Operand rn, uint imm4, Operand rm, uint q) + { + WriteInstructionRm16(0x2e000000u | (imm4 << 11) | (q << 30), rd, rn, rm); + } + + public readonly void FabdSH(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x7ec01400u, rd, rn, rm); + } + + public readonly void FabdS(Operand rd, Operand rn, Operand rm, uint sz) + { + WriteInstructionRm16(0x7ea0d400u | (sz << 22), rd, rn, rm); + } + + public readonly void FabdVH(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2ec01400u | (q << 30), rd, rn, rm); + } + + public readonly void FabdV(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x2ea0d400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FabsHalf(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0ef8f800u | (q << 30), rd, rn); + } + + public readonly void FabsSingleAndDouble(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0ea0f800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FabsFloat(Operand rd, Operand rn, uint ftype) + { + WriteInstruction(0x1e20c000u | (ftype << 22), rd, rn); + } + + public readonly void FacgeSH(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x7e402c00u, rd, rn, rm); + } + + public readonly void FacgeS(Operand rd, Operand rn, Operand rm, uint sz) + { + WriteInstructionRm16(0x7e20ec00u | (sz << 22), rd, rn, rm); + } + + public readonly void FacgeVH(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2e402c00u | (q << 30), rd, rn, rm); + } + + public readonly void FacgeV(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x2e20ec00u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FacgtSH(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x7ec02c00u, rd, rn, rm); + } + + public readonly void FacgtS(Operand rd, Operand rn, Operand rm, uint sz) + { + WriteInstructionRm16(0x7ea0ec00u | (sz << 22), rd, rn, rm); + } + + public readonly void FacgtVH(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2ec02c00u | (q << 30), rd, rn, rm); + } + + public readonly void FacgtV(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x2ea0ec00u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FaddpPairHalf(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5e30d800u | (sz << 22), rd, rn); + } + + public readonly void FaddpPairSingleAndDouble(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7e30d800u | (sz << 22), rd, rn); + } + + public readonly void FaddpVecHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2e401400u | (q << 30), rd, rn, rm); + } + + public readonly void FaddpVecSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x2e20d400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FaddHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0e401400u | (q << 30), rd, rn, rm); + } + + public readonly void FaddSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x0e20d400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FaddFloat(Operand rd, Operand rn, Operand rm, uint ftype) + { + WriteInstructionRm16(0x1e202800u | (ftype << 22), rd, rn, rm); + } + + public readonly void FcaddVec(Operand rd, Operand rn, uint rot, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e00e400u | (rot << 12) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FccmpeFloat(uint nzcv, Operand rn, uint cond, Operand rm, uint ftype) + { + WriteInstructionRm16(0x1e200410u | (nzcv << 0) | (cond << 12) | (ftype << 22), rn, rm); + } + + public readonly void FccmpFloat(uint nzcv, Operand rn, uint cond, Operand rm, uint ftype) + { + WriteInstructionRm16(0x1e200400u | (nzcv << 0) | (cond << 12) | (ftype << 22), rn, rm); + } + + public readonly void FcmeqRegSH(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x5e402400u, rd, rn, rm); + } + + public readonly void FcmeqRegS(Operand rd, Operand rn, Operand rm, uint sz) + { + WriteInstructionRm16(0x5e20e400u | (sz << 22), rd, rn, rm); + } + + public readonly void FcmeqRegVH(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0e402400u | (q << 30), rd, rn, rm); + } + + public readonly void FcmeqRegV(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x0e20e400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FcmeqZeroSH(Operand rd, Operand rn) + { + WriteInstruction(0x5ef8d800u, rd, rn); + } + + public readonly void FcmeqZeroS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5ea0d800u | (sz << 22), rd, rn); + } + + public readonly void FcmeqZeroVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0ef8d800u | (q << 30), rd, rn); + } + + public readonly void FcmeqZeroV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0ea0d800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcmgeRegSH(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x7e402400u, rd, rn, rm); + } + + public readonly void FcmgeRegS(Operand rd, Operand rn, Operand rm, uint sz) + { + WriteInstructionRm16(0x7e20e400u | (sz << 22), rd, rn, rm); + } + + public readonly void FcmgeRegVH(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2e402400u | (q << 30), rd, rn, rm); + } + + public readonly void FcmgeRegV(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x2e20e400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FcmgeZeroSH(Operand rd, Operand rn) + { + WriteInstruction(0x7ef8c800u, rd, rn); + } + + public readonly void FcmgeZeroS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7ea0c800u | (sz << 22), rd, rn); + } + + public readonly void FcmgeZeroVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2ef8c800u | (q << 30), rd, rn); + } + + public readonly void FcmgeZeroV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2ea0c800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcmgtRegSH(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x7ec02400u, rd, rn, rm); + } + + public readonly void FcmgtRegS(Operand rd, Operand rn, Operand rm, uint sz) + { + WriteInstructionRm16(0x7ea0e400u | (sz << 22), rd, rn, rm); + } + + public readonly void FcmgtRegVH(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2ec02400u | (q << 30), rd, rn, rm); + } + + public readonly void FcmgtRegV(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x2ea0e400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FcmgtZeroSH(Operand rd, Operand rn) + { + WriteInstruction(0x5ef8c800u, rd, rn); + } + + public readonly void FcmgtZeroS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5ea0c800u | (sz << 22), rd, rn); + } + + public readonly void FcmgtZeroVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0ef8c800u | (q << 30), rd, rn); + } + + public readonly void FcmgtZeroV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0ea0c800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcmlaElt(Operand rd, Operand rn, uint h, uint rot, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x2f001000u | (h << 11) | (rot << 13) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FcmlaVec(Operand rd, Operand rn, uint rot, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e00c400u | (rot << 11) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FcmleSH(Operand rd, Operand rn) + { + WriteInstruction(0x7ef8d800u, rd, rn); + } + + public readonly void FcmleS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7ea0d800u | (sz << 22), rd, rn); + } + + public readonly void FcmleVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2ef8d800u | (q << 30), rd, rn); + } + + public readonly void FcmleV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2ea0d800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcmltSH(Operand rd, Operand rn) + { + WriteInstruction(0x5ef8e800u, rd, rn); + } + + public readonly void FcmltS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5ea0e800u | (sz << 22), rd, rn); + } + + public readonly void FcmltVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0ef8e800u | (q << 30), rd, rn); + } + + public readonly void FcmltV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0ea0e800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcmpeFloat(Operand rn, Operand rm, uint opc, uint ftype) + { + WriteInstructionRm16(0x1e202010u | (opc << 3) | (ftype << 22), rn, rm); + } + + public readonly void FcmpFloat(Operand rn, Operand rm, uint opc, uint ftype) + { + WriteInstructionRm16(0x1e202000u | (opc << 3) | (ftype << 22), rn, rm); + } + + public readonly void FcselFloat(Operand rd, Operand rn, uint cond, Operand rm, uint ftype) + { + WriteInstructionRm16(0x1e200c00u | (cond << 12) | (ftype << 22), rd, rn, rm); + } + + public readonly void FcvtasSH(Operand rd, Operand rn) + { + WriteInstruction(0x5e79c800u, rd, rn); + } + + public readonly void FcvtasS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5e21c800u | (sz << 22), rd, rn); + } + + public readonly void FcvtasVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0e79c800u | (q << 30), rd, rn); + } + + public readonly void FcvtasV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0e21c800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcvtasFloat(Operand rd, Operand rn, uint ftype, uint sf) + { + WriteInstruction(0x1e240000u | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void FcvtauSH(Operand rd, Operand rn) + { + WriteInstruction(0x7e79c800u, rd, rn); + } + + public readonly void FcvtauS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7e21c800u | (sz << 22), rd, rn); + } + + public readonly void FcvtauVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2e79c800u | (q << 30), rd, rn); + } + + public readonly void FcvtauV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2e21c800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcvtauFloat(Operand rd, Operand rn, uint ftype, uint sf) + { + WriteInstruction(0x1e250000u | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void Fcvtl(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0e217800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcvtmsSH(Operand rd, Operand rn) + { + WriteInstruction(0x5e79b800u, rd, rn); + } + + public readonly void FcvtmsS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5e21b800u | (sz << 22), rd, rn); + } + + public readonly void FcvtmsVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0e79b800u | (q << 30), rd, rn); + } + + public readonly void FcvtmsV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0e21b800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcvtmsFloat(Operand rd, Operand rn, uint ftype, uint sf) + { + WriteInstruction(0x1e300000u | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void FcvtmuSH(Operand rd, Operand rn) + { + WriteInstruction(0x7e79b800u, rd, rn); + } + + public readonly void FcvtmuS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7e21b800u | (sz << 22), rd, rn); + } + + public readonly void FcvtmuVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2e79b800u | (q << 30), rd, rn); + } + + public readonly void FcvtmuV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2e21b800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcvtmuFloat(Operand rd, Operand rn, uint ftype, uint sf) + { + WriteInstruction(0x1e310000u | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void FcvtnsSH(Operand rd, Operand rn) + { + WriteInstruction(0x5e79a800u, rd, rn); + } + + public readonly void FcvtnsS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5e21a800u | (sz << 22), rd, rn); + } + + public readonly void FcvtnsVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0e79a800u | (q << 30), rd, rn); + } + + public readonly void FcvtnsV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0e21a800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcvtnsFloat(Operand rd, Operand rn, uint ftype, uint sf) + { + WriteInstruction(0x1e200000u | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void FcvtnuSH(Operand rd, Operand rn) + { + WriteInstruction(0x7e79a800u, rd, rn); + } + + public readonly void FcvtnuS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7e21a800u | (sz << 22), rd, rn); + } + + public readonly void FcvtnuVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2e79a800u | (q << 30), rd, rn); + } + + public readonly void FcvtnuV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2e21a800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcvtnuFloat(Operand rd, Operand rn, uint ftype, uint sf) + { + WriteInstruction(0x1e210000u | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void Fcvtn(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0e216800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcvtpsSH(Operand rd, Operand rn) + { + WriteInstruction(0x5ef9a800u, rd, rn); + } + + public readonly void FcvtpsS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5ea1a800u | (sz << 22), rd, rn); + } + + public readonly void FcvtpsVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0ef9a800u | (q << 30), rd, rn); + } + + public readonly void FcvtpsV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0ea1a800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcvtpsFloat(Operand rd, Operand rn, uint ftype, uint sf) + { + WriteInstruction(0x1e280000u | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void FcvtpuSH(Operand rd, Operand rn) + { + WriteInstruction(0x7ef9a800u, rd, rn); + } + + public readonly void FcvtpuS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7ea1a800u | (sz << 22), rd, rn); + } + + public readonly void FcvtpuVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2ef9a800u | (q << 30), rd, rn); + } + + public readonly void FcvtpuV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2ea1a800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcvtpuFloat(Operand rd, Operand rn, uint ftype, uint sf) + { + WriteInstruction(0x1e290000u | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void FcvtxnS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7e216800u | (sz << 22), rd, rn); + } + + public readonly void FcvtxnV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2e216800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcvtzsFixS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x5f00fc00u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void FcvtzsFixV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x0f00fc00u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void FcvtzsIntSH(Operand rd, Operand rn) + { + WriteInstruction(0x5ef9b800u, rd, rn); + } + + public readonly void FcvtzsIntS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5ea1b800u | (sz << 22), rd, rn); + } + + public readonly void FcvtzsIntVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0ef9b800u | (q << 30), rd, rn); + } + + public readonly void FcvtzsIntV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0ea1b800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcvtzsFloatFix(Operand rd, Operand rn, uint scale, uint ftype, uint sf) + { + WriteInstruction(0x1e180000u | (scale << 10) | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void FcvtzsFloatInt(Operand rd, Operand rn, uint ftype, uint sf) + { + WriteInstruction(0x1e380000u | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void FcvtzuFixS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x7f00fc00u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void FcvtzuFixV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f00fc00u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void FcvtzuIntSH(Operand rd, Operand rn) + { + WriteInstruction(0x7ef9b800u, rd, rn); + } + + public readonly void FcvtzuIntS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7ea1b800u | (sz << 22), rd, rn); + } + + public readonly void FcvtzuIntVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2ef9b800u | (q << 30), rd, rn); + } + + public readonly void FcvtzuIntV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2ea1b800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FcvtzuFloatFix(Operand rd, Operand rn, uint scale, uint ftype, uint sf) + { + WriteInstruction(0x1e190000u | (scale << 10) | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void FcvtzuFloatInt(Operand rd, Operand rn, uint ftype, uint sf) + { + WriteInstruction(0x1e390000u | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void FcvtFloat(Operand rd, Operand rn, uint opc, uint ftype) + { + WriteInstruction(0x1e224000u | (opc << 15) | (ftype << 22), rd, rn); + } + + public readonly void FdivHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2e403c00u | (q << 30), rd, rn, rm); + } + + public readonly void FdivSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x2e20fc00u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FdivFloat(Operand rd, Operand rn, Operand rm, uint ftype) + { + WriteInstructionRm16(0x1e201800u | (ftype << 22), rd, rn, rm); + } + + public readonly void Fjcvtzs(Operand rd, Operand rn) + { + WriteInstruction(0x1e7e0000u, rd, rn); + } + + public readonly void FmaddFloat(Operand rd, Operand rn, Operand rm, Operand ra, uint ftype) + { + WriteInstruction(0x1f000000u | (ftype << 22), rd, rn, rm, ra); + } + + public readonly void FmaxnmpPairHalf(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5e30c800u | (sz << 22), rd, rn); + } + + public readonly void FmaxnmpPairSingleAndDouble(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7e30c800u | (sz << 22), rd, rn); + } + + public readonly void FmaxnmpVecHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2e400400u | (q << 30), rd, rn, rm); + } + + public readonly void FmaxnmpVecSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x2e20c400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FmaxnmvHalf(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0e30c800u | (q << 30), rd, rn); + } + + public readonly void FmaxnmvSingleAndDouble(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2e30c800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FmaxnmHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0e400400u | (q << 30), rd, rn, rm); + } + + public readonly void FmaxnmSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x0e20c400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FmaxnmFloat(Operand rd, Operand rn, Operand rm, uint ftype) + { + WriteInstructionRm16(0x1e206800u | (ftype << 22), rd, rn, rm); + } + + public readonly void FmaxpPairHalf(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5e30f800u | (sz << 22), rd, rn); + } + + public readonly void FmaxpPairSingleAndDouble(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7e30f800u | (sz << 22), rd, rn); + } + + public readonly void FmaxpVecHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2e403400u | (q << 30), rd, rn, rm); + } + + public readonly void FmaxpVecSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x2e20f400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FmaxvHalf(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0e30f800u | (q << 30), rd, rn); + } + + public readonly void FmaxvSingleAndDouble(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2e30f800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FmaxHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0e403400u | (q << 30), rd, rn, rm); + } + + public readonly void FmaxSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x0e20f400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FmaxFloat(Operand rd, Operand rn, Operand rm, uint ftype) + { + WriteInstructionRm16(0x1e204800u | (ftype << 22), rd, rn, rm); + } + + public readonly void FminnmpPairHalf(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5eb0c800u | (sz << 22), rd, rn); + } + + public readonly void FminnmpPairSingleAndDouble(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7eb0c800u | (sz << 22), rd, rn); + } + + public readonly void FminnmpVecHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2ec00400u | (q << 30), rd, rn, rm); + } + + public readonly void FminnmpVecSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x2ea0c400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FminnmvHalf(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0eb0c800u | (q << 30), rd, rn); + } + + public readonly void FminnmvSingleAndDouble(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2eb0c800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FminnmHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0ec00400u | (q << 30), rd, rn, rm); + } + + public readonly void FminnmSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x0ea0c400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FminnmFloat(Operand rd, Operand rn, Operand rm, uint ftype) + { + WriteInstructionRm16(0x1e207800u | (ftype << 22), rd, rn, rm); + } + + public readonly void FminpPairHalf(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5eb0f800u | (sz << 22), rd, rn); + } + + public readonly void FminpPairSingleAndDouble(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7eb0f800u | (sz << 22), rd, rn); + } + + public readonly void FminpVecHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2ec03400u | (q << 30), rd, rn, rm); + } + + public readonly void FminpVecSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x2ea0f400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FminvHalf(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0eb0f800u | (q << 30), rd, rn); + } + + public readonly void FminvSingleAndDouble(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2eb0f800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FminHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0ec03400u | (q << 30), rd, rn, rm); + } + + public readonly void FminSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x0ea0f400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FminFloat(Operand rd, Operand rn, Operand rm, uint ftype) + { + WriteInstructionRm16(0x1e205800u | (ftype << 22), rd, rn, rm); + } + + public readonly void FmlalEltFmlal(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint q) + { + WriteInstructionRm16(0x0f800000u | (h << 11) | (m << 20) | (l << 21) | (q << 30), rd, rn, rm); + } + + public readonly void FmlalEltFmlal2(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint q) + { + WriteInstructionRm16(0x2f808000u | (h << 11) | (m << 20) | (l << 21) | (q << 30), rd, rn, rm); + } + + public readonly void FmlalVecFmlal(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0e20ec00u | (q << 30), rd, rn, rm); + } + + public readonly void FmlalVecFmlal2(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2e20cc00u | (q << 30), rd, rn, rm); + } + + public readonly void FmlaElt2regScalarHalf(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l) + { + WriteInstructionRm16(0x5f001000u | (h << 11) | (m << 20) | (l << 21), rd, rn, rm); + } + + public readonly void FmlaElt2regScalarSingleAndDouble(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint sz) + { + WriteInstructionRm16(0x5f801000u | (h << 11) | (m << 20) | (l << 21) | (sz << 22), rd, rn, rm); + } + + public readonly void FmlaElt2regElementHalf(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint q) + { + WriteInstructionRm16(0x0f001000u | (h << 11) | (m << 20) | (l << 21) | (q << 30), rd, rn, rm); + } + + public readonly void FmlaElt2regElementSingleAndDouble(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint sz, uint q) + { + WriteInstructionRm16(0x0f801000u | (h << 11) | (m << 20) | (l << 21) | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FmlaVecHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0e400c00u | (q << 30), rd, rn, rm); + } + + public readonly void FmlaVecSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x0e20cc00u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FmlslEltFmlsl(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint q) + { + WriteInstructionRm16(0x0f804000u | (h << 11) | (m << 20) | (l << 21) | (q << 30), rd, rn, rm); + } + + public readonly void FmlslEltFmlsl2(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint q) + { + WriteInstructionRm16(0x2f80c000u | (h << 11) | (m << 20) | (l << 21) | (q << 30), rd, rn, rm); + } + + public readonly void FmlslVecFmlsl(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0ea0ec00u | (q << 30), rd, rn, rm); + } + + public readonly void FmlslVecFmlsl2(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2ea0cc00u | (q << 30), rd, rn, rm); + } + + public readonly void FmlsElt2regScalarHalf(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l) + { + WriteInstructionRm16(0x5f005000u | (h << 11) | (m << 20) | (l << 21), rd, rn, rm); + } + + public readonly void FmlsElt2regScalarSingleAndDouble(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint sz) + { + WriteInstructionRm16(0x5f805000u | (h << 11) | (m << 20) | (l << 21) | (sz << 22), rd, rn, rm); + } + + public readonly void FmlsElt2regElementHalf(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint q) + { + WriteInstructionRm16(0x0f005000u | (h << 11) | (m << 20) | (l << 21) | (q << 30), rd, rn, rm); + } + + public readonly void FmlsElt2regElementSingleAndDouble(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint sz, uint q) + { + WriteInstructionRm16(0x0f805000u | (h << 11) | (m << 20) | (l << 21) | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FmlsVecHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0ec00c00u | (q << 30), rd, rn, rm); + } + + public readonly void FmlsVecSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x0ea0cc00u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FmovPerHalf(Operand rd, uint h, uint g, uint f, uint e, uint d, uint c, uint b, uint a, uint q) + { + WriteInstruction(0x0f00fc00u | (h << 5) | (g << 6) | (f << 7) | (e << 8) | (d << 9) | (c << 16) | (b << 17) | (a << 18) | (q << 30), rd); + } + + public readonly void FmovSingleAndDouble(Operand rd, uint h, uint g, uint f, uint e, uint d, uint c, uint b, uint a, uint op, uint q) + { + WriteInstruction(0x0f00f400u | (h << 5) | (g << 6) | (f << 7) | (e << 8) | (d << 9) | (c << 16) | (b << 17) | (a << 18) | (op << 29) | (q << 30), rd); + } + + public readonly void FmovFloat(Operand rd, Operand rn, uint ftype) + { + WriteInstruction(0x1e204000u | (ftype << 22), rd, rn); + } + + public readonly void FmovFloatGen(Operand rd, Operand rn, uint ftype, uint sf, uint dir, uint top) + { + WriteInstruction(0x1e260000u | (dir << 16) | (top << 19) | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void FmovFloatImm(Operand rd, uint imm8, uint ftype) + { + WriteInstruction(0x1e201000u | (imm8 << 13) | (ftype << 22), rd); + } + + public readonly void FmsubFloat(Operand rd, Operand rn, Operand rm, Operand ra, uint ftype) + { + WriteInstruction(0x1f008000u | (ftype << 22), rd, rn, rm, ra); + } + + public readonly void FmulxElt2regScalarHalf(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l) + { + WriteInstructionRm16(0x7f009000u | (h << 11) | (m << 20) | (l << 21), rd, rn, rm); + } + + public readonly void FmulxElt2regScalarSingleAndDouble(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint sz) + { + WriteInstructionRm16(0x7f809000u | (h << 11) | (m << 20) | (l << 21) | (sz << 22), rd, rn, rm); + } + + public readonly void FmulxElt2regElementHalf(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint q) + { + WriteInstructionRm16(0x2f009000u | (h << 11) | (m << 20) | (l << 21) | (q << 30), rd, rn, rm); + } + + public readonly void FmulxElt2regElementSingleAndDouble(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint sz, uint q) + { + WriteInstructionRm16(0x2f809000u | (h << 11) | (m << 20) | (l << 21) | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FmulxVecSH(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x5e401c00u, rd, rn, rm); + } + + public readonly void FmulxVecS(Operand rd, Operand rn, Operand rm, uint sz) + { + WriteInstructionRm16(0x5e20dc00u | (sz << 22), rd, rn, rm); + } + + public readonly void FmulxVecVH(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0e401c00u | (q << 30), rd, rn, rm); + } + + public readonly void FmulxVecV(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x0e20dc00u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FmulElt2regScalarHalf(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l) + { + WriteInstructionRm16(0x5f009000u | (h << 11) | (m << 20) | (l << 21), rd, rn, rm); + } + + public readonly void FmulElt2regScalarSingleAndDouble(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint sz) + { + WriteInstructionRm16(0x5f809000u | (h << 11) | (m << 20) | (l << 21) | (sz << 22), rd, rn, rm); + } + + public readonly void FmulElt2regElementHalf(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint q) + { + WriteInstructionRm16(0x0f009000u | (h << 11) | (m << 20) | (l << 21) | (q << 30), rd, rn, rm); + } + + public readonly void FmulElt2regElementSingleAndDouble(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint sz, uint q) + { + WriteInstructionRm16(0x0f809000u | (h << 11) | (m << 20) | (l << 21) | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FmulVecHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x2e401c00u | (q << 30), rd, rn, rm); + } + + public readonly void FmulVecSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x2e20dc00u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FmulFloat(Operand rd, Operand rn, Operand rm, uint ftype) + { + WriteInstructionRm16(0x1e200800u | (ftype << 22), rd, rn, rm); + } + + public readonly void FnegHalf(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2ef8f800u | (q << 30), rd, rn); + } + + public readonly void FnegSingleAndDouble(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2ea0f800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FnegFloat(Operand rd, Operand rn, uint ftype) + { + WriteInstruction(0x1e214000u | (ftype << 22), rd, rn); + } + + public readonly void FnmaddFloat(Operand rd, Operand rn, Operand rm, Operand ra, uint ftype) + { + WriteInstruction(0x1f200000u | (ftype << 22), rd, rn, rm, ra); + } + + public readonly void FnmsubFloat(Operand rd, Operand rn, Operand rm, Operand ra, uint ftype) + { + WriteInstruction(0x1f208000u | (ftype << 22), rd, rn, rm, ra); + } + + public readonly void FnmulFloat(Operand rd, Operand rn, Operand rm, uint ftype) + { + WriteInstructionRm16(0x1e208800u | (ftype << 22), rd, rn, rm); + } + + public readonly void FrecpeSH(Operand rd, Operand rn) + { + WriteInstruction(0x5ef9d800u, rd, rn); + } + + public readonly void FrecpeS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5ea1d800u | (sz << 22), rd, rn); + } + + public readonly void FrecpeVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0ef9d800u | (q << 30), rd, rn); + } + + public readonly void FrecpeV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0ea1d800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FrecpsSH(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x5e403c00u, rd, rn, rm); + } + + public readonly void FrecpsS(Operand rd, Operand rn, Operand rm, uint sz) + { + WriteInstructionRm16(0x5e20fc00u | (sz << 22), rd, rn, rm); + } + + public readonly void FrecpsVH(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0e403c00u | (q << 30), rd, rn, rm); + } + + public readonly void FrecpsV(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x0e20fc00u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FrecpxHalf(Operand rd, Operand rn) + { + WriteInstruction(0x5ef9f800u, rd, rn); + } + + public readonly void FrecpxSingleAndDouble(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5ea1f800u | (sz << 22), rd, rn); + } + + public readonly void Frint32x(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2e21e800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void Frint32xFloat(Operand rd, Operand rn) + { + WriteInstruction(0x1e28c000u, rd, rn); + } + + public readonly void Frint32z(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0e21e800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void Frint32zFloat(Operand rd, Operand rn) + { + WriteInstruction(0x1e284000u, rd, rn); + } + + public readonly void Frint64x(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2e21f800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void Frint64xFloat(Operand rd, Operand rn) + { + WriteInstruction(0x1e29c000u, rd, rn); + } + + public readonly void Frint64z(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0e21f800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void Frint64zFloat(Operand rd, Operand rn) + { + WriteInstruction(0x1e294000u, rd, rn); + } + + public readonly void FrintaHalf(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2e798800u | (q << 30), rd, rn); + } + + public readonly void FrintaSingleAndDouble(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2e218800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FrintaFloat(Operand rd, Operand rn, uint ftype) + { + WriteInstruction(0x1e264000u | (ftype << 22), rd, rn); + } + + public readonly void FrintiHalf(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2ef99800u | (q << 30), rd, rn); + } + + public readonly void FrintiSingleAndDouble(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2ea19800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FrintiFloat(Operand rd, Operand rn, uint ftype) + { + WriteInstruction(0x1e27c000u | (ftype << 22), rd, rn); + } + + public readonly void FrintmHalf(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0e799800u | (q << 30), rd, rn); + } + + public readonly void FrintmSingleAndDouble(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0e219800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FrintmFloat(Operand rd, Operand rn, uint ftype) + { + WriteInstruction(0x1e254000u | (ftype << 22), rd, rn); + } + + public readonly void FrintnHalf(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0e798800u | (q << 30), rd, rn); + } + + public readonly void FrintnSingleAndDouble(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0e218800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FrintnFloat(Operand rd, Operand rn, uint ftype) + { + WriteInstruction(0x1e244000u | (ftype << 22), rd, rn); + } + + public readonly void FrintpHalf(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0ef98800u | (q << 30), rd, rn); + } + + public readonly void FrintpSingleAndDouble(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0ea18800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FrintpFloat(Operand rd, Operand rn, uint ftype) + { + WriteInstruction(0x1e24c000u | (ftype << 22), rd, rn); + } + + public readonly void FrintxHalf(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2e799800u | (q << 30), rd, rn); + } + + public readonly void FrintxSingleAndDouble(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2e219800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FrintxFloat(Operand rd, Operand rn, uint ftype) + { + WriteInstruction(0x1e274000u | (ftype << 22), rd, rn); + } + + public readonly void FrintzHalf(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0ef99800u | (q << 30), rd, rn); + } + + public readonly void FrintzSingleAndDouble(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0ea19800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FrintzFloat(Operand rd, Operand rn, uint ftype) + { + WriteInstruction(0x1e25c000u | (ftype << 22), rd, rn); + } + + public readonly void FrsqrteSH(Operand rd, Operand rn) + { + WriteInstruction(0x7ef9d800u, rd, rn); + } + + public readonly void FrsqrteS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7ea1d800u | (sz << 22), rd, rn); + } + + public readonly void FrsqrteVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2ef9d800u | (q << 30), rd, rn); + } + + public readonly void FrsqrteV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2ea1d800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FrsqrtsSH(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x5ec03c00u, rd, rn, rm); + } + + public readonly void FrsqrtsS(Operand rd, Operand rn, Operand rm, uint sz) + { + WriteInstructionRm16(0x5ea0fc00u | (sz << 22), rd, rn, rm); + } + + public readonly void FrsqrtsVH(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0ec03c00u | (q << 30), rd, rn, rm); + } + + public readonly void FrsqrtsV(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x0ea0fc00u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FsqrtHalf(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2ef9f800u | (q << 30), rd, rn); + } + + public readonly void FsqrtSingleAndDouble(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2ea1f800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void FsqrtFloat(Operand rd, Operand rn, uint ftype) + { + WriteInstruction(0x1e21c000u | (ftype << 22), rd, rn); + } + + public readonly void FsubHalf(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0ec01400u | (q << 30), rd, rn, rm); + } + + public readonly void FsubSingleAndDouble(Operand rd, Operand rn, Operand rm, uint sz, uint q) + { + WriteInstructionRm16(0x0ea0d400u | (sz << 22) | (q << 30), rd, rn, rm); + } + + public readonly void FsubFloat(Operand rd, Operand rn, Operand rm, uint ftype) + { + WriteInstructionRm16(0x1e203800u | (ftype << 22), rd, rn, rm); + } + + public readonly void InsElt(Operand rd, Operand rn, uint imm4, uint imm5) + { + WriteInstruction(0x6e000400u | (imm4 << 11) | (imm5 << 16), rd, rn); + } + + public readonly void InsGen(Operand rd, Operand rn, uint imm5) + { + WriteInstruction(0x4e001c00u | (imm5 << 16), rd, rn); + } + + public readonly void Ld1rAsNoPostIndex(Operand rt, Operand rn, uint size, uint q) + { + WriteInstruction(0x0d40c000u | (size << 10) | (q << 30), rt, rn); + } + + public readonly void Ld1rAsPostIndex(Operand rt, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0dc0c000u | (size << 10) | (q << 30), rt, rn, rm); + } + + public readonly void Ld1MultAsNoPostIndex(Operand rt, Operand rn, uint registersCount, uint size, uint q) + { + WriteInstruction(0x0c402000u | (size << 10) | (EncodeLdSt1MultOpcode(registersCount) << 12) | (q << 30), rt, rn); + } + + public readonly void Ld1MultAsPostIndex(Operand rt, Operand rn, Operand rm, uint registersCount, uint size, uint q) + { + WriteInstructionRm16(0x0cc02000u | (size << 10) | (EncodeLdSt1MultOpcode(registersCount) << 12) | (q << 30), rt, rn, rm); + } + + public readonly void Ld1SnglAsNoPostIndex(Operand rt, Operand rn, uint index, uint size) + { + WriteInstruction(EncodeIndexSizeSngl(0x0d400000u, index, size), rt, rn); + } + + public readonly void Ld1SnglAsPostIndex(Operand rt, Operand rn, Operand rm, uint index, uint size) + { + WriteInstructionRm16(EncodeIndexSizeSngl(0x0dc00000u, index, size), rt, rn, rm); + } + + public readonly void Ld2rAsNoPostIndex(Operand rt, Operand rn, uint size, uint q) + { + WriteInstruction(0x0d60c000u | (size << 10) | (q << 30), rt, rn); + } + + public readonly void Ld2rAsPostIndex(Operand rt, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0de0c000u | (size << 10) | (q << 30), rt, rn, rm); + } + + public readonly void Ld2MultAsNoPostIndex(Operand rt, Operand rn, uint size, uint q) + { + WriteInstruction(0x0c408000u | (size << 10) | (q << 30), rt, rn); + } + + public readonly void Ld2MultAsPostIndex(Operand rt, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0cc08000u | (size << 10) | (q << 30), rt, rn, rm); + } + + public readonly void Ld2SnglAsNoPostIndex(Operand rt, Operand rn, uint index, uint size) + { + WriteInstruction(EncodeIndexSizeSngl(0x0d600000u, index, size), rt, rn); + } + + public readonly void Ld2SnglAsPostIndex(Operand rt, Operand rn, Operand rm, uint index, uint size) + { + WriteInstructionRm16(EncodeIndexSizeSngl(0x0de00000u, index, size), rt, rn, rm); + } + + public readonly void Ld3rAsNoPostIndex(Operand rt, Operand rn, uint size, uint q) + { + WriteInstruction(0x0d40e000u | (size << 10) | (q << 30), rt, rn); + } + + public readonly void Ld3rAsPostIndex(Operand rt, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0dc0e000u | (size << 10) | (q << 30), rt, rn, rm); + } + + public readonly void Ld3MultAsNoPostIndex(Operand rt, Operand rn, uint size, uint q) + { + WriteInstruction(0x0c404000u | (size << 10) | (q << 30), rt, rn); + } + + public readonly void Ld3MultAsPostIndex(Operand rt, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0cc04000u | (size << 10) | (q << 30), rt, rn, rm); + } + + public readonly void Ld3SnglAsNoPostIndex(Operand rt, Operand rn, uint index, uint size) + { + WriteInstruction(EncodeIndexSizeSngl(0x0d402000u, index, size), rt, rn); + } + + public readonly void Ld3SnglAsPostIndex(Operand rt, Operand rn, Operand rm, uint index, uint size) + { + WriteInstructionRm16(EncodeIndexSizeSngl(0x0dc02000u, index, size), rt, rn, rm); + } + + public readonly void Ld4rAsNoPostIndex(Operand rt, Operand rn, uint size, uint q) + { + WriteInstruction(0x0d60e000u | (size << 10) | (q << 30), rt, rn); + } + + public readonly void Ld4rAsPostIndex(Operand rt, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0de0e000u | (size << 10) | (q << 30), rt, rn, rm); + } + + public readonly void Ld4MultAsNoPostIndex(Operand rt, Operand rn, uint size, uint q) + { + WriteInstruction(0x0c400000u | (size << 10) | (q << 30), rt, rn); + } + + public readonly void Ld4MultAsPostIndex(Operand rt, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0cc00000u | (size << 10) | (q << 30), rt, rn, rm); + } + + public readonly void Ld4SnglAsNoPostIndex(Operand rt, Operand rn, uint index, uint size) + { + WriteInstruction(EncodeIndexSizeSngl(0x0d602000u, index, size), rt, rn); + } + + public readonly void Ld4SnglAsPostIndex(Operand rt, Operand rn, Operand rm, uint index, uint size) + { + WriteInstructionRm16(EncodeIndexSizeSngl(0x0de02000u, index, size), rt, rn, rm); + } + + public readonly void Ldap1Sngl(Operand rt, Operand rn, uint q) + { + WriteInstruction(0x0d418400u | (q << 30), rt, rn); + } + + public readonly void Ldra(Operand rt, Operand rn, uint w, uint imm9, uint s, uint m) + { + WriteInstruction(0xf8200400u | (w << 11) | (imm9 << 12) | (s << 22) | (m << 23), rt, rn); + } + + public readonly void MlaElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x2f000000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void MlaVec(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e209400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void MlsElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x2f004000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void MlsVec(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e209400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Movi(Operand rd, uint h, uint g, uint f, uint e, uint d, uint cmode, uint c, uint b, uint a, uint op, uint q) + { + WriteInstruction(0x0f000400u | (h << 5) | (g << 6) | (f << 7) | (e << 8) | (d << 9) | (cmode << 12) | (c << 16) | (b << 17) | (a << 18) | (op << 29) | (q << 30), rd); + } + + public readonly void MulElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x0f008000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void MulVec(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e209c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Mvni(Operand rd, uint h, uint g, uint f, uint e, uint d, uint cmode, uint c, uint b, uint a, uint q) + { + WriteInstruction(0x2f000400u | (h << 5) | (g << 6) | (f << 7) | (e << 8) | (d << 9) | (cmode << 12) | (c << 16) | (b << 17) | (a << 18) | (q << 30), rd); + } + + public readonly void NegS(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x7e20b800u | (size << 22), rd, rn); + } + + public readonly void NegV(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e20b800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Not(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2e205800u | (q << 30), rd, rn); + } + + public readonly void Orn(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0ee01c00u | (q << 30), rd, rn, rm); + } + + public readonly void OrrImm(Operand rd, uint h, uint g, uint f, uint e, uint d, uint c, uint b, uint a, uint q) + { + WriteInstruction(0x0f001400u | (h << 5) | (g << 6) | (f << 7) | (e << 8) | (d << 9) | (c << 16) | (b << 17) | (a << 18) | (q << 30), rd); + } + + public readonly void OrrReg(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0ea01c00u | (q << 30), rd, rn, rm); + } + + public readonly void Pmull(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e20e000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Pmul(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e209c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Raddhn(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e204000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Rax1(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0xce608c00u, rd, rn, rm); + } + + public readonly void Rbit(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2e605800u | (q << 30), rd, rn); + } + + public readonly void Rev16(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e201800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Rev32(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e200800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Rev64(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e200800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Rshrn(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x0f008c00u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void Rsubhn(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e206000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Sabal(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e205000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Saba(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e207c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Sabdl(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e207000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Sabd(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e207400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Sadalp(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e206800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Saddlp(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e202800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Saddlv(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e303800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Saddl(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e200000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Saddw(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e201000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void ScvtfFixS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x5f00e400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void ScvtfFixV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x0f00e400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void ScvtfIntSH(Operand rd, Operand rn) + { + WriteInstruction(0x5e79d800u, rd, rn); + } + + public readonly void ScvtfIntS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x5e21d800u | (sz << 22), rd, rn); + } + + public readonly void ScvtfIntVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x0e79d800u | (q << 30), rd, rn); + } + + public readonly void ScvtfIntV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0e21d800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void ScvtfFloatFix(Operand rd, Operand rn, uint scale, uint ftype, uint sf) + { + WriteInstruction(0x1e020000u | (scale << 10) | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void ScvtfFloatInt(Operand rd, Operand rn, uint ftype, uint sf) + { + WriteInstruction(0x1e220000u | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void SdotElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x0f00e000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SdotVec(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e009400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Sha1c(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x5e000000u, rd, rn, rm); + } + + public readonly void Sha1h(Operand rd, Operand rn) + { + WriteInstruction(0x5e280800u, rd, rn); + } + + public readonly void Sha1m(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x5e002000u, rd, rn, rm); + } + + public readonly void Sha1p(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x5e001000u, rd, rn, rm); + } + + public readonly void Sha1su0(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x5e003000u, rd, rn, rm); + } + + public readonly void Sha1su1(Operand rd, Operand rn) + { + WriteInstruction(0x5e281800u, rd, rn); + } + + public readonly void Sha256h2(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x5e005000u, rd, rn, rm); + } + + public readonly void Sha256h(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x5e004000u, rd, rn, rm); + } + + public readonly void Sha256su0(Operand rd, Operand rn) + { + WriteInstruction(0x5e282800u, rd, rn); + } + + public readonly void Sha256su1(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x5e006000u, rd, rn, rm); + } + + public readonly void Sha512h2(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0xce608400u, rd, rn, rm); + } + + public readonly void Sha512h(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0xce608000u, rd, rn, rm); + } + + public readonly void Sha512su0(Operand rd, Operand rn) + { + WriteInstruction(0xcec08000u, rd, rn); + } + + public readonly void Sha512su1(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0xce608800u, rd, rn, rm); + } + + public readonly void Shadd(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e200400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Shll(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e213800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void ShlS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x5f005400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void ShlV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x0f005400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void Shrn(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x0f008400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void Shsub(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e202400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SliS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x7f005400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void SliV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f005400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void Sm3partw1(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0xce60c000u, rd, rn, rm); + } + + public readonly void Sm3partw2(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0xce60c400u, rd, rn, rm); + } + + public readonly void Sm3ss1(Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteInstruction(0xce400000u, rd, rn, rm, ra); + } + + public readonly void Sm3tt1a(Operand rd, Operand rn, uint imm2, Operand rm) + { + WriteInstructionRm16(0xce408000u | (imm2 << 12), rd, rn, rm); + } + + public readonly void Sm3tt1b(Operand rd, Operand rn, uint imm2, Operand rm) + { + WriteInstructionRm16(0xce408400u | (imm2 << 12), rd, rn, rm); + } + + public readonly void Sm3tt2a(Operand rd, Operand rn, uint imm2, Operand rm) + { + WriteInstructionRm16(0xce408800u | (imm2 << 12), rd, rn, rm); + } + + public readonly void Sm3tt2b(Operand rd, Operand rn, uint imm2, Operand rm) + { + WriteInstructionRm16(0xce408c00u | (imm2 << 12), rd, rn, rm); + } + + public readonly void Sm4ekey(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0xce60c800u, rd, rn, rm); + } + + public readonly void Sm4e(Operand rd, Operand rn) + { + WriteInstruction(0xcec08400u, rd, rn); + } + + public readonly void Smaxp(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e20a400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Smaxv(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e30a800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Smax(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e206400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Sminp(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e20ac00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Sminv(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e31a800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Smin(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e206c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SmlalElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x0f002000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SmlalVec(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e208000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SmlslElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x0f006000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SmlslVec(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e20a000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SmmlaVec(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x4e80a400u, rd, rn, rm); + } + + public readonly void Smov(Operand rd, Operand rn, int index, int size) + { + uint q = size == 2 ? 1u << 30 : 0u; + WriteInstruction(0x0e002c00u | (EncodeIndexSizeImm5(index, size) << 16) | q, rd, rn); + } + + public readonly void SmullElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x0f00a000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SmullVec(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e20c000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqabsS(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x5e207800u | (size << 22), rd, rn); + } + + public readonly void SqabsV(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e207800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void SqaddS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x5e200c00u | (size << 22), rd, rn, rm); + } + + public readonly void SqaddV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e200c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqdmlalElt2regScalar(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size) + { + WriteInstructionRm16(0x5f003000u | (h << 11) | (m << 20) | (l << 21) | (size << 22), rd, rn, rm); + } + + public readonly void SqdmlalElt2regElement(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x0f003000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqdmlalVecS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x5e209000u | (size << 22), rd, rn, rm); + } + + public readonly void SqdmlalVecV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e209000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqdmlslElt2regScalar(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size) + { + WriteInstructionRm16(0x5f007000u | (h << 11) | (m << 20) | (l << 21) | (size << 22), rd, rn, rm); + } + + public readonly void SqdmlslElt2regElement(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x0f007000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqdmlslVecS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x5e20b000u | (size << 22), rd, rn, rm); + } + + public readonly void SqdmlslVecV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e20b000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqdmulhElt2regScalar(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size) + { + WriteInstructionRm16(0x5f00c000u | (h << 11) | (m << 20) | (l << 21) | (size << 22), rd, rn, rm); + } + + public readonly void SqdmulhElt2regElement(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x0f00c000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqdmulhVecS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x5e20b400u | (size << 22), rd, rn, rm); + } + + public readonly void SqdmulhVecV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e20b400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqdmullElt2regScalar(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size) + { + WriteInstructionRm16(0x5f00b000u | (h << 11) | (m << 20) | (l << 21) | (size << 22), rd, rn, rm); + } + + public readonly void SqdmullElt2regElement(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x0f00b000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqdmullVecS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x5e20d000u | (size << 22), rd, rn, rm); + } + + public readonly void SqdmullVecV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e20d000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqnegS(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x7e207800u | (size << 22), rd, rn); + } + + public readonly void SqnegV(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e207800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void SqrdmlahElt2regScalar(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size) + { + WriteInstructionRm16(0x7f00d000u | (h << 11) | (m << 20) | (l << 21) | (size << 22), rd, rn, rm); + } + + public readonly void SqrdmlahElt2regElement(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x2f00d000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqrdmlahVecS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x7e008400u | (size << 22), rd, rn, rm); + } + + public readonly void SqrdmlahVecV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e008400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqrdmlshElt2regScalar(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size) + { + WriteInstructionRm16(0x7f00f000u | (h << 11) | (m << 20) | (l << 21) | (size << 22), rd, rn, rm); + } + + public readonly void SqrdmlshElt2regElement(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x2f00f000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqrdmlshVecS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x7e008c00u | (size << 22), rd, rn, rm); + } + + public readonly void SqrdmlshVecV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e008c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqrdmulhElt2regScalar(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size) + { + WriteInstructionRm16(0x5f00d000u | (h << 11) | (m << 20) | (l << 21) | (size << 22), rd, rn, rm); + } + + public readonly void SqrdmulhElt2regElement(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x0f00d000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqrdmulhVecS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x7e20b400u | (size << 22), rd, rn, rm); + } + + public readonly void SqrdmulhVecV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e20b400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqrshlS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x5e205c00u | (size << 22), rd, rn, rm); + } + + public readonly void SqrshlV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e205c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqrshrnS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x5f009c00u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void SqrshrnV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x0f009c00u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void SqrshrunS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x7f008c00u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void SqrshrunV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f008c00u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void SqshluS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x7f006400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void SqshluV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f006400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void SqshlImmS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x5f007400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void SqshlImmV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x0f007400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void SqshlRegS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x5e204c00u | (size << 22), rd, rn, rm); + } + + public readonly void SqshlRegV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e204c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqshrnS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x5f009400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void SqshrnV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x0f009400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void SqshrunS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x7f008400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void SqshrunV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f008400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void SqsubS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x5e202c00u | (size << 22), rd, rn, rm); + } + + public readonly void SqsubV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e202c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SqxtnS(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x5e214800u | (size << 22), rd, rn); + } + + public readonly void SqxtnV(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e214800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void SqxtunS(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x7e212800u | (size << 22), rd, rn); + } + + public readonly void SqxtunV(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e212800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Srhadd(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e201400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SriS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x7f004400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void SriV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f004400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void SrshlS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x5e205400u | (size << 22), rd, rn, rm); + } + + public readonly void SrshlV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e205400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SrshrS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x5f002400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void SrshrV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x0f002400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void SrsraS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x5f003400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void SrsraV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x0f003400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void Sshll(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x0f00a400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void SshlS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x5e204400u | (size << 22), rd, rn, rm); + } + + public readonly void SshlV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e204400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SshrS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x5f000400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void SshrV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x0f000400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void SsraS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x5f001400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void SsraV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x0f001400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void Ssubl(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e202000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Ssubw(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e203000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void St1MultAsNoPostIndex(Operand rt, Operand rn, uint registersCount, uint size, uint q) + { + WriteInstruction(0x0c002000u | (size << 10) | (EncodeLdSt1MultOpcode(registersCount) << 12) | (q << 30), rt, rn); + } + + public readonly void St1MultAsPostIndex(Operand rt, Operand rn, Operand rm, uint registersCount, uint size, uint q) + { + WriteInstructionRm16(0x0c802000u | (size << 10) | (EncodeLdSt1MultOpcode(registersCount) << 12) | (q << 30), rt, rn, rm); + } + + public readonly void St1SnglAsNoPostIndex(Operand rt, Operand rn, uint index, uint size) + { + WriteInstruction(EncodeIndexSizeSngl(0x0d000000u, index, size), rt, rn); + } + + public readonly void St1SnglAsPostIndex(Operand rt, Operand rn, Operand rm, uint index, uint size) + { + WriteInstructionRm16(EncodeIndexSizeSngl(0x0d800000u, index, size), rt, rn, rm); + } + + public readonly void St2MultAsNoPostIndex(Operand rt, Operand rn, uint size, uint q) + { + WriteInstruction(0x0c008000u | (size << 10) | (q << 30), rt, rn); + } + + public readonly void St2MultAsPostIndex(Operand rt, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0c808000u | (size << 10) | (q << 30), rt, rn, rm); + } + + public readonly void St2SnglAsNoPostIndex(Operand rt, Operand rn, uint index, uint size) + { + WriteInstruction(EncodeIndexSizeSngl(0x0d200000u, index, size), rt, rn); + } + + public readonly void St2SnglAsPostIndex(Operand rt, Operand rn, Operand rm, uint index, uint size) + { + WriteInstructionRm16(EncodeIndexSizeSngl(0x0da00000u, index, size), rt, rn, rm); + } + + public readonly void St3MultAsNoPostIndex(Operand rt, Operand rn, uint size, uint q) + { + WriteInstruction(0x0c004000u | (size << 10) | (q << 30), rt, rn); + } + + public readonly void St3MultAsPostIndex(Operand rt, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0c804000u | (size << 10) | (q << 30), rt, rn, rm); + } + + public readonly void St3SnglAsNoPostIndex(Operand rt, Operand rn, uint index, uint size) + { + WriteInstruction(EncodeIndexSizeSngl(0x0d002000u, index, size), rt, rn); + } + + public readonly void St3SnglAsPostIndex(Operand rt, Operand rn, Operand rm, uint index, uint size) + { + WriteInstructionRm16(EncodeIndexSizeSngl(0x0d802000u, index, size), rt, rn, rm); + } + + public readonly void St4MultAsNoPostIndex(Operand rt, Operand rn, uint size, uint q) + { + WriteInstruction(0x0c000000u | (size << 10) | (q << 30), rt, rn); + } + + public readonly void St4MultAsPostIndex(Operand rt, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0c800000u | (size << 10) | (q << 30), rt, rn, rm); + } + + public readonly void St4SnglAsNoPostIndex(Operand rt, Operand rn, uint index, uint size) + { + WriteInstruction(EncodeIndexSizeSngl(0x0d202000u, index, size), rt, rn); + } + + public readonly void St4SnglAsPostIndex(Operand rt, Operand rn, Operand rm, uint index, uint size) + { + WriteInstructionRm16(EncodeIndexSizeSngl(0x0da02000u, index, size), rt, rn, rm); + } + + public readonly void Stl1Sngl(Operand rt, Operand rn, uint q) + { + WriteInstruction(0x0d018400u | (q << 30), rt, rn); + } + + public readonly void Subhn(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e206000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SubS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x7e208400u | (size << 22), rd, rn, rm); + } + + public readonly void SubV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e208400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void SudotElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint q) + { + WriteInstructionRm16(0x0f00f000u | (h << 11) | (m << 20) | (l << 21) | (q << 30), rd, rn, rm); + } + + public readonly void SuqaddS(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x5e203800u | (size << 22), rd, rn); + } + + public readonly void SuqaddV(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e203800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Tbl(Operand rd, Operand rn, uint len, Operand rm, uint q) + { + WriteInstructionRm16(0x0e000000u | (len << 13) | (q << 30), rd, rn, rm); + } + + public readonly void Tbx(Operand rd, Operand rn, uint len, Operand rm, uint q) + { + WriteInstructionRm16(0x0e001000u | (len << 13) | (q << 30), rd, rn, rm); + } + + public readonly void Trn1(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e002800u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Trn2(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e006800u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Uabal(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e205000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Uaba(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e207c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Uabdl(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e207000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Uabd(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e207400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Uadalp(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e206800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Uaddlp(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e202800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Uaddlv(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e303800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Uaddl(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e200000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Uaddw(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e201000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UcvtfFixS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x7f00e400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void UcvtfFixV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f00e400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void UcvtfIntSH(Operand rd, Operand rn) + { + WriteInstruction(0x7e79d800u, rd, rn); + } + + public readonly void UcvtfIntS(Operand rd, Operand rn, uint sz) + { + WriteInstruction(0x7e21d800u | (sz << 22), rd, rn); + } + + public readonly void UcvtfIntVH(Operand rd, Operand rn, uint q) + { + WriteInstruction(0x2e79d800u | (q << 30), rd, rn); + } + + public readonly void UcvtfIntV(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2e21d800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void UcvtfFloatFix(Operand rd, Operand rn, uint scale, uint ftype, uint sf) + { + WriteInstruction(0x1e030000u | (scale << 10) | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void UcvtfFloatInt(Operand rd, Operand rn, uint ftype, uint sf) + { + WriteInstruction(0x1e230000u | (ftype << 22) | (sf << 31), rd, rn); + } + + public readonly void UdotElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x2f00e000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UdotVec(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e009400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Uhadd(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e200400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Uhsub(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e202400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Umaxp(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e20a400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Umaxv(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e30a800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Umax(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e206400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Uminp(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e20ac00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Uminv(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e31a800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Umin(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e206c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UmlalElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x2f002000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UmlalVec(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e208000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UmlslElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x2f006000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UmlslVec(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e20a000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UmmlaVec(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x6e80a400u, rd, rn, rm); + } + + public readonly void Umov(Operand rd, Operand rn, int index, int size) + { + uint q = size == 3 ? 1u << 30 : 0u; + WriteInstruction(0x0e003c00u | (EncodeIndexSizeImm5(index, size) << 16) | q, rd, rn); + } + + public readonly void UmullElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint size, uint q) + { + WriteInstructionRm16(0x2f00a000u | (h << 11) | (m << 20) | (l << 21) | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UmullVec(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e20c000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UqaddS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x7e200c00u | (size << 22), rd, rn, rm); + } + + public readonly void UqaddV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e200c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UqrshlS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x7e205c00u | (size << 22), rd, rn, rm); + } + + public readonly void UqrshlV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e205c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UqrshrnS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x7f009c00u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void UqrshrnV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f009c00u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void UqshlImmS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x7f007400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void UqshlImmV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f007400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void UqshlRegS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x7e204c00u | (size << 22), rd, rn, rm); + } + + public readonly void UqshlRegV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e204c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UqshrnS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x7f009400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void UqshrnV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f009400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void UqsubS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x7e202c00u | (size << 22), rd, rn, rm); + } + + public readonly void UqsubV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e202c00u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UqxtnS(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x7e214800u | (size << 22), rd, rn); + } + + public readonly void UqxtnV(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e214800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Urecpe(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x0ea1c800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void Urhadd(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e201400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UrshlS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x7e205400u | (size << 22), rd, rn, rm); + } + + public readonly void UrshlV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e205400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UrshrS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x7f002400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void UrshrV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f002400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void Ursqrte(Operand rd, Operand rn, uint sz, uint q) + { + WriteInstruction(0x2ea1c800u | (sz << 22) | (q << 30), rd, rn); + } + + public readonly void UrsraS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x7f003400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void UrsraV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f003400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void UsdotElt(Operand rd, Operand rn, uint h, Operand rm, uint m, uint l, uint q) + { + WriteInstructionRm16(0x0f80f000u | (h << 11) | (m << 20) | (l << 21) | (q << 30), rd, rn, rm); + } + + public readonly void UsdotVec(Operand rd, Operand rn, Operand rm, uint q) + { + WriteInstructionRm16(0x0e809c00u | (q << 30), rd, rn, rm); + } + + public readonly void Ushll(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f00a400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void UshlS(Operand rd, Operand rn, Operand rm, uint size) + { + WriteInstructionRm16(0x7e204400u | (size << 22), rd, rn, rm); + } + + public readonly void UshlV(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e204400u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void UshrS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x7f000400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void UshrV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f000400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void UsmmlaVec(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x4e80ac00u, rd, rn, rm); + } + + public readonly void UsqaddS(Operand rd, Operand rn, uint size) + { + WriteInstruction(0x7e203800u | (size << 22), rd, rn); + } + + public readonly void UsqaddV(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x2e203800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void UsraS(Operand rd, Operand rn, uint immb, uint immh) + { + WriteInstruction(0x7f001400u | (immb << 16) | (immh << 19), rd, rn); + } + + public readonly void UsraV(Operand rd, Operand rn, uint immb, uint immh, uint q) + { + WriteInstruction(0x2f001400u | (immb << 16) | (immh << 19) | (q << 30), rd, rn); + } + + public readonly void Usubl(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e202000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Usubw(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x2e203000u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Uzp1(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e001800u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Uzp2(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e005800u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Wfe() + { + WriteUInt32(0xd503205fu); + } + + public readonly void Wfi() + { + WriteUInt32(0xd503207fu); + } + + public readonly void Xar(Operand rd, Operand rn, uint imm6, Operand rm) + { + WriteInstructionRm16(0xce800000u | (imm6 << 10), rd, rn, rm); + } + + public readonly void Xtn(Operand rd, Operand rn, uint size, uint q) + { + WriteInstruction(0x0e212800u | (size << 22) | (q << 30), rd, rn); + } + + public readonly void Yield() + { + WriteUInt32(0xd503203fu); + } + + public readonly void Zip1(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e003800u | (size << 22) | (q << 30), rd, rn, rm); + } + + public readonly void Zip2(Operand rd, Operand rn, Operand rm, uint size, uint q) + { + WriteInstructionRm16(0x0e007800u | (size << 22) | (q << 30), rd, rn, rm); + } + + // Utility + + public readonly void WriteSysRegInstruction(uint inst, Operand rt, uint o0, uint op1, uint crn, uint crm, uint op2) + { + inst |= (op2 & 7) << 5; + inst |= (crm & 15) << 8; + inst |= (crn & 15) << 12; + inst |= (op1 & 7) << 16; + inst |= (o0 & 1) << 19; + + WriteInstruction(inst, rt); + } + + private readonly void WriteInstructionAuto( + uint instI, + uint instR, + Operand rd, + Operand rn, + Operand rm, + ArmShiftType shiftType = ArmShiftType.Lsl, + int shiftAmount = 0, + bool immForm = false) + { + if (rm.Kind == OperandKind.Constant && (rm.Value != 0 || immForm)) + { + Debug.Assert(shiftAmount == 0); + int imm = rm.AsInt32(); + Debug.Assert((uint)imm == rm.Value); + if (imm != 0 && (imm & 0xfff) == 0) + { + instI |= 1 << 22; // sh flag + imm >>= 12; + } + WriteInstructionAuto(instI | (EncodeUImm12(imm, 0) << 10), rd, rn); + } + else + { + Debug.Assert((uint)shiftType < 3); // ROR shift is a reserved encoding for ADD and SUB. + instR |= EncodeUImm6(shiftAmount) << 10; + instR |= (uint)shiftType << 22; + + WriteInstructionRm16Auto(instR, rd, rn, rm); + } + } + + private readonly void WriteInstructionAuto( + uint instR, + Operand rd, + Operand rn, + Operand rm, + ArmShiftType shiftType = ArmShiftType.Lsl, + int shiftAmount = 0) + { + instR |= EncodeUImm6(shiftAmount) << 10; + instR |= (uint)shiftType << 22; + + WriteInstructionRm16Auto(instR, rd, rn, rm); + } + + private readonly void WriteInstructionAuto( + uint instruction, + Operand rd, + Operand rn, + Operand rm, + ArmExtensionType extensionType, + int shiftAmount = 0) + { + Debug.Assert((uint)shiftAmount <= 4); + + instruction |= (uint)shiftAmount << 10; + instruction |= (uint)extensionType << 13; + + WriteInstructionRm16Auto(instruction, rd, rn, rm); + } + + private readonly void WriteInstructionBitwiseAuto( + uint instI, + uint instR, + Operand rd, + Operand rn, + Operand rm, + ArmShiftType shiftType = ArmShiftType.Lsl, + int shiftAmount = 0) + { + if (rm.Kind == OperandKind.Constant && rm.Value != 0) + { + Debug.Assert(shiftAmount == 0); + bool canEncode = CodeGenCommon.TryEncodeBitMask(rm, out int immN, out int immS, out int immR); + Debug.Assert(canEncode); + uint instruction = instI | ((uint)immS << 10) | ((uint)immR << 16) | ((uint)immN << 22); + + WriteInstructionAuto(instruction, rd, rn); + } + else + { + WriteInstructionBitwiseAuto(instR, rd, rn, rm, shiftType, shiftAmount); + } + } + + private readonly void WriteInstructionBitwiseAuto( + uint instruction, + Operand rd, + Operand rn, + Operand rm, + ArmShiftType shiftType = ArmShiftType.Lsl, + int shiftAmount = 0) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + instruction |= EncodeUImm6(shiftAmount) << 10; + instruction |= (uint)shiftType << 22; + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + private readonly void WriteInstructionLdrStrAuto( + uint instruction, + Operand rd, + Operand rn, + Operand rm, + ArmExtensionType extensionType, + bool shift) + { + if (shift) + { + instruction |= 1u << 12; + } + + instruction |= (uint)extensionType << 13; + + if (rd.Type == OperandType.I64) + { + instruction |= 1u << 30; + } + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + private readonly void WriteInstructionAuto(uint instruction, Operand rd) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstruction(instruction, rd); + } + + public readonly void WriteInstructionAuto(uint instruction, Operand rd, Operand rn) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstruction(instruction, rd, rn); + } + + private readonly void WriteInstructionAuto(uint instruction, Operand rd, Operand rn, Operand rm, Operand ra) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstruction(instruction, rd, rn, rm, ra); + } + + public readonly void WriteInstruction(uint instruction, Operand rd) + { + WriteUInt32(instruction | EncodeReg(rd)); + } + + public readonly void WriteInstruction(uint instruction, Operand rd, Operand rn) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5)); + } + + public readonly void WriteInstruction(uint instruction, Operand rd, Operand rn, Operand rm) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5) | (EncodeReg(rm) << 10)); + } + + public readonly void WriteInstruction(uint instruction, Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5) | (EncodeReg(ra) << 10) | (EncodeReg(rm) << 16)); + } + + private readonly void WriteInstructionRm16Auto(uint instruction, Operand rd, Operand rn, Operand rm) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + public readonly void WriteInstructionRm16(uint instruction, Operand rn, Operand rm) + { + WriteUInt32(instruction | (EncodeReg(rn) << 5) | (EncodeReg(rm) << 16)); + } + + public readonly void WriteInstructionRm16(uint instruction, Operand rd, Operand rn, Operand rm) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5) | (EncodeReg(rm) << 16)); + } + + private static uint GetLdpStpInstruction(uint intInst, uint vecInst, int imm, OperandType type) + { + uint instruction; + int scale; + + if (type.IsInteger()) + { + instruction = intInst; + + if (type == OperandType.I64) + { + instruction |= SfFlag; + scale = 3; + } + else + { + scale = 2; + } + } + else + { + int opc = type switch + { + OperandType.FP32 => 0, + OperandType.FP64 => 1, + _ => 2, + }; + + instruction = vecInst | ((uint)opc << 30); + scale = 2 + opc; + } + + instruction |= (EncodeSImm7(imm, scale) << 15); + + return instruction; + } + + private static uint GetLdrStrInstruction(uint intInst, uint vecInst, OperandType type) + { + uint instruction; + + if (type.IsInteger()) + { + instruction = intInst; + + if (type == OperandType.I64) + { + instruction |= 1 << 30; + } + } + else + { + instruction = vecInst; + + if (type == OperandType.V128) + { + instruction |= 1u << 23; + } + else + { + instruction |= type == OperandType.FP32 ? 2u << 30 : 3u << 30; + } + } + + return instruction; + } + + private static uint GetLdrStrInstruction(uint intInst, uint vecInst, OperandType type, uint size) + { + uint instruction; + + if (type.IsInteger()) + { + instruction = intInst; + + if (type == OperandType.I64) + { + instruction |= 1 << 30; + } + } + else + { + instruction = vecInst; + + if (type == OperandType.V128) + { + instruction |= 1u << 23; + } + else + { + instruction |= size << 30; + } + } + + return instruction; + } + + private static uint EncodeIndexSizeImm5(int index, int size) + { + Debug.Assert((uint)size < 4); + Debug.Assert((uint)index < (16u >> size), $"Invalid index {index} and size {size} combination."); + return ((uint)index << (size + 1)) | (1u << size); + } + + private static uint EncodeSImm7(int value, int scale) + { + uint imm = (uint)(value >> scale) & 0x7f; + Debug.Assert(((int)imm << 25) >> (25 - scale) == value, $"Failed to encode constant 0x{value:X} with scale {scale}."); + return imm; + } + + private static uint EncodeSImm9(int value) + { + uint imm = (uint)value & 0x1ff; + Debug.Assert(((int)imm << 23) >> 23 == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeSImm19_2(int value) + { + uint imm = (uint)(value >> 2) & 0x7ffff; + Debug.Assert(((int)imm << 13) >> 11 == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeSImm26_2(int value) + { + uint imm = (uint)(value >> 2) & 0x3ffffff; + Debug.Assert(((int)imm << 6) >> 4 == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeUImm4(int value) + { + uint imm = (uint)value & 0xf; + Debug.Assert((int)imm == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeUImm6(int value) + { + uint imm = (uint)value & 0x3f; + Debug.Assert((int)imm == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeUImm12(int value, OperandType type) + { + return EncodeUImm12(value, GetScaleForType(type)); + } + + private static uint EncodeUImm12(int value, int scale) + { + uint imm = (uint)(value >> scale) & 0xfff; + Debug.Assert((int)imm << scale == value, $"Failed to encode constant 0x{value:X} with scale {scale}."); + return imm; + } + + private static uint EncodeUImm16(int value) + { + uint imm = (uint)value & 0xffff; + Debug.Assert((int)imm == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeReg(Operand reg) + { + if (reg.Kind == OperandKind.Constant && reg.Value == 0) + { + return ZrRegister; + } + + uint regIndex = (uint)reg.GetRegister().Index; + Debug.Assert(reg.Kind == OperandKind.Register); + Debug.Assert(regIndex < 32); + return regIndex; + } + + private static uint EncodeTypeTargetPolicy(uint type, uint target, uint policy) + { + Debug.Assert(type <= 2); + Debug.Assert(target <= 2); + Debug.Assert(policy <= 1); + + return (type << 3) | (target << 1) | policy; + } + + private static uint EncodeIndexSizeSngl(uint inst, uint index, uint size) + { + uint opcode = Math.Min(2u, size) << 1; + + index <<= (int)size; + + if (size == 3) + { + index |= 1u; + } + + size = index & 3; + uint s = (index >> 2) & 1; + uint q = index >> 3; + + Debug.Assert(q <= 1); + + return inst | (size << 10) | (s << 12) | (opcode << 13) | (q << 30); + } + + private static uint EncodeLdSt1MultOpcode(uint registersCount) + { + return registersCount switch + { + 2 => 0b1010, + 3 => 0b0110, + 4 => 0b0010, + _ => 0b0111, + }; + } + + private static int GetScaleForType(OperandType type) + { + return type switch + { + OperandType.I32 => 2, + OperandType.I64 => 3, + OperandType.FP32 => 2, + OperandType.FP64 => 3, + OperandType.V128 => 4, + _ => throw new ArgumentException($"Invalid type {type}."), + }; + } + + private readonly void WriteUInt32(uint value) + { + _code.Add(value); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/CodeGenCommon.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/CodeGenCommon.cs new file mode 100644 index 00000000..db6a7159 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/CodeGenCommon.cs @@ -0,0 +1,67 @@ +using System.Numerics; + +namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64 +{ + static class CodeGenCommon + { + public static bool TryEncodeBitMask(Operand operand, out int immN, out int immS, out int immR) + { + return TryEncodeBitMask(operand.Type, operand.Value, out immN, out immS, out immR); + } + + public static bool TryEncodeBitMask(OperandType type, ulong value, out int immN, out int immS, out int immR) + { + if (type == OperandType.I32) + { + value &= uint.MaxValue; + value |= value << 32; + } + + return TryEncodeBitMask(value, out immN, out immS, out immR); + } + + public static bool TryEncodeBitMask(ulong value, out int immN, out int immS, out int immR) + { + // Some special values also can't be encoded: + // 0 can't be encoded because we need to subtract 1 from onesCount (which would became negative if 0). + // A value with all bits set can't be encoded because it is reserved according to the spec, because: + // Any value AND all ones will be equal itself, so it's effectively a no-op. + // Any value OR all ones will be equal all ones, so one can just use MOV. + // Any value XOR all ones will be equal its inverse, so one can just use MVN. + if (value == 0 || value == ulong.MaxValue) + { + immN = 0; + immS = 0; + immR = 0; + + return false; + } + + // Normalize value, rotating it such that the LSB is 1: Ensures we get a complete element that has not + // been cut-in-half across the word boundary. + int rotation = BitOperations.TrailingZeroCount(value & (value + 1)); + ulong rotatedValue = ulong.RotateRight(value, rotation); + + // Now that we have a complete element in the LSB with the LSB = 1, determine size and number of ones + // in element. + int elementSize = BitOperations.TrailingZeroCount(rotatedValue & (rotatedValue + 1)); + int onesInElement = BitOperations.TrailingZeroCount(~rotatedValue); + + // Check the value is repeating; also ensures element size is a power of two. + if (ulong.RotateRight(value, elementSize) != value) + { + immN = 0; + immS = 0; + immR = 0; + + return false; + } + + immN = (elementSize >> 6) & 1; + immS = (((~elementSize + 1) << 1) | (onesInElement - 1)) & 0x3f; + immR = (elementSize - rotation) & (elementSize - 1); + + return true; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/RegisterSaveRestore.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/RegisterSaveRestore.cs new file mode 100644 index 00000000..b4b8bb52 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/RegisterSaveRestore.cs @@ -0,0 +1,252 @@ +using System.Numerics; + +namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64 +{ + readonly struct RegisterSaveRestore + { + private const int FpRegister = 29; + private const int LrRegister = 30; + + public const int Encodable9BitsOffsetLimit = 0x100; + + private readonly uint _gprMask; + private readonly uint _fpSimdMask; + private readonly OperandType _fpSimdType; + private readonly int _reservedStackSize; + private readonly bool _hasCall; + + public RegisterSaveRestore( + uint gprMask, + uint fpSimdMask = 0, + OperandType fpSimdType = OperandType.FP64, + bool hasCall = false, + int reservedStackSize = 0) + { + _gprMask = gprMask; + _fpSimdMask = fpSimdMask; + _fpSimdType = fpSimdType; + _reservedStackSize = reservedStackSize; + _hasCall = hasCall; + } + + public int GetReservedStackOffset() + { + int gprCalleeSavedRegsCount = BitOperations.PopCount(_gprMask); + int fpSimdCalleeSavedRegsCount = BitOperations.PopCount(_fpSimdMask); + + return (_hasCall ? 16 : 0) + Align16(gprCalleeSavedRegsCount * 8 + fpSimdCalleeSavedRegsCount * _fpSimdType.GetSizeInBytes()); + } + + public void WritePrologue(ref Assembler asm) + { + uint gprMask = _gprMask; + uint fpSimdMask = _fpSimdMask; + + int gprCalleeSavedRegsCount = BitOperations.PopCount(gprMask); + int fpSimdCalleeSavedRegsCount = BitOperations.PopCount(fpSimdMask); + + int reservedStackSize = Align16(_reservedStackSize); + int calleeSaveRegionSize = Align16(gprCalleeSavedRegsCount * 8 + fpSimdCalleeSavedRegsCount * _fpSimdType.GetSizeInBytes()) + reservedStackSize; + int offset = 0; + + WritePrologueCalleeSavesPreIndexed(ref asm, ref gprMask, ref offset, calleeSaveRegionSize, OperandType.I64); + + if (_fpSimdType == OperandType.V128 && (gprCalleeSavedRegsCount & 1) != 0) + { + offset += 8; + } + + WritePrologueCalleeSavesPreIndexed(ref asm, ref fpSimdMask, ref offset, calleeSaveRegionSize, _fpSimdType); + + if (_hasCall) + { + Operand rsp = Register(Assembler.SpRegister); + + if (offset != 0 || calleeSaveRegionSize + 16 < Encodable9BitsOffsetLimit) + { + asm.StpRiPre(Register(FpRegister), Register(LrRegister), rsp, offset == 0 ? -(calleeSaveRegionSize + 16) : -16); + } + else + { + asm.Sub(rsp, rsp, new Operand(OperandKind.Constant, OperandType.I64, (ulong)calleeSaveRegionSize)); + asm.StpRiPre(Register(FpRegister), Register(LrRegister), rsp, -16); + } + + asm.MovSp(Register(FpRegister), rsp); + } + } + + private static void WritePrologueCalleeSavesPreIndexed( + ref Assembler asm, + ref uint mask, + ref int offset, + int calleeSaveRegionSize, + OperandType type) + { + if ((BitOperations.PopCount(mask) & 1) != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + + mask &= ~(1u << reg); + + if (offset != 0) + { + asm.StrRiUn(Register(reg, type), Register(Assembler.SpRegister), offset); + } + else if (calleeSaveRegionSize < Encodable9BitsOffsetLimit) + { + asm.StrRiPre(Register(reg, type), Register(Assembler.SpRegister), -calleeSaveRegionSize); + } + else + { + asm.Sub(Register(Assembler.SpRegister), Register(Assembler.SpRegister), new Operand(OperandType.I64, (ulong)calleeSaveRegionSize)); + asm.StrRiUn(Register(reg, type), Register(Assembler.SpRegister), 0); + } + + offset += type.GetSizeInBytes(); + } + + while (mask != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + + mask &= ~(1u << reg); + + int reg2 = BitOperations.TrailingZeroCount(mask); + + mask &= ~(1u << reg2); + + if (offset != 0) + { + asm.StpRiUn(Register(reg, type), Register(reg2, type), Register(Assembler.SpRegister), offset); + } + else if (calleeSaveRegionSize < Encodable9BitsOffsetLimit) + { + asm.StpRiPre(Register(reg, type), Register(reg2, type), Register(Assembler.SpRegister), -calleeSaveRegionSize); + } + else + { + asm.Sub(Register(Assembler.SpRegister), Register(Assembler.SpRegister), new Operand(OperandType.I64, (ulong)calleeSaveRegionSize)); + asm.StpRiUn(Register(reg, type), Register(reg2, type), Register(Assembler.SpRegister), 0); + } + + offset += type.GetSizeInBytes() * 2; + } + } + + public void WriteEpilogue(ref Assembler asm) + { + uint gprMask = _gprMask; + uint fpSimdMask = _fpSimdMask; + + int gprCalleeSavedRegsCount = BitOperations.PopCount(gprMask); + int fpSimdCalleeSavedRegsCount = BitOperations.PopCount(fpSimdMask); + + bool misalignedVector = _fpSimdType == OperandType.V128 && (gprCalleeSavedRegsCount & 1) != 0; + + int offset = gprCalleeSavedRegsCount * 8 + fpSimdCalleeSavedRegsCount * _fpSimdType.GetSizeInBytes(); + + if (misalignedVector) + { + offset += 8; + } + + int calleeSaveRegionSize = Align16(offset) + Align16(_reservedStackSize); + + if (_hasCall) + { + Operand rsp = Register(Assembler.SpRegister); + + if (offset != 0 || calleeSaveRegionSize + 16 < Encodable9BitsOffsetLimit) + { + asm.LdpRiPost(Register(FpRegister), Register(LrRegister), rsp, offset == 0 ? calleeSaveRegionSize + 16 : 16); + } + else + { + asm.LdpRiPost(Register(FpRegister), Register(LrRegister), rsp, 16); + asm.Add(rsp, rsp, new Operand(OperandKind.Constant, OperandType.I64, (ulong)calleeSaveRegionSize)); + } + } + + WriteEpilogueCalleeSavesPostIndexed(ref asm, ref fpSimdMask, ref offset, calleeSaveRegionSize, _fpSimdType); + + if (misalignedVector) + { + offset -= 8; + } + + WriteEpilogueCalleeSavesPostIndexed(ref asm, ref gprMask, ref offset, calleeSaveRegionSize, OperandType.I64); + } + + private static void WriteEpilogueCalleeSavesPostIndexed( + ref Assembler asm, + ref uint mask, + ref int offset, + int calleeSaveRegionSize, + OperandType type) + { + while (mask != 0) + { + int reg = HighestBitSet(mask); + + mask &= ~(1u << reg); + + if (mask != 0) + { + int reg2 = HighestBitSet(mask); + + mask &= ~(1u << reg2); + + offset -= type.GetSizeInBytes() * 2; + + if (offset != 0) + { + asm.LdpRiUn(Register(reg2, type), Register(reg, type), Register(Assembler.SpRegister), offset); + } + else if (calleeSaveRegionSize < Encodable9BitsOffsetLimit) + { + asm.LdpRiPost(Register(reg2, type), Register(reg, type), Register(Assembler.SpRegister), calleeSaveRegionSize); + } + else + { + asm.LdpRiUn(Register(reg2, type), Register(reg, type), Register(Assembler.SpRegister), 0); + asm.Add(Register(Assembler.SpRegister), Register(Assembler.SpRegister), new Operand(OperandType.I64, (ulong)calleeSaveRegionSize)); + } + } + else + { + offset -= type.GetSizeInBytes(); + + if (offset != 0) + { + asm.LdrRiUn(Register(reg, type), Register(Assembler.SpRegister), offset); + } + else if (calleeSaveRegionSize < Encodable9BitsOffsetLimit) + { + asm.LdrRiPost(Register(reg, type), Register(Assembler.SpRegister), calleeSaveRegionSize); + } + else + { + asm.LdrRiUn(Register(reg, type), Register(Assembler.SpRegister), 0); + asm.Add(Register(Assembler.SpRegister), Register(Assembler.SpRegister), new Operand(OperandType.I64, (ulong)calleeSaveRegionSize)); + } + } + } + } + + private static int HighestBitSet(uint value) + { + return 31 - BitOperations.LeadingZeroCount(value); + } + + private static Operand Register(int register, OperandType type = OperandType.I64) + { + return new Operand(register, RegisterType.Integer, type); + } + + private static int Align16(int value) + { + return (value + 0xf) & ~0xf; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/StackWalker.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/StackWalker.cs new file mode 100644 index 00000000..3b01e674 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/StackWalker.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64 +{ + class StackWalker : IStackWalker + { + public IEnumerable GetCallStack(IntPtr framePointer, IntPtr codeRegionStart, int codeRegionSize, IntPtr codeRegion2Start, int codeRegion2Size) + { + List functionPointers = new(); + + while (true) + { + IntPtr functionPointer = Marshal.ReadIntPtr(framePointer, IntPtr.Size); + + if ((functionPointer < codeRegionStart || functionPointer >= codeRegionStart + codeRegionSize) && + (functionPointer < codeRegion2Start || functionPointer >= codeRegion2Start + codeRegion2Size)) + { + break; + } + + functionPointers.Add((ulong)functionPointer - 4); + framePointer = Marshal.ReadIntPtr(framePointer); + } + + return functionPointers; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/TailMerger.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/TailMerger.cs new file mode 100644 index 00000000..e042af12 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/Arm64/TailMerger.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.CodeGen.Arm64 +{ + class TailMerger + { + private enum BranchType + { + Conditional, + Unconditional, + } + + private readonly List<(BranchType, int)> _branchPointers; + + public TailMerger() + { + _branchPointers = new(); + } + + public void AddConditionalReturn(CodeWriter writer, in Assembler asm, ArmCondition returnCondition) + { + _branchPointers.Add((BranchType.Conditional, writer.InstructionPointer)); + asm.B(returnCondition, 0); + } + + public void AddConditionalZeroReturn(CodeWriter writer, in Assembler asm, Operand value) + { + _branchPointers.Add((BranchType.Conditional, writer.InstructionPointer)); + asm.Cbz(value, 0); + } + + public void AddUnconditionalReturn(CodeWriter writer, in Assembler asm) + { + _branchPointers.Add((BranchType.Unconditional, writer.InstructionPointer)); + asm.B(0); + } + + public void WriteReturn(CodeWriter writer, Action writeEpilogue) + { + if (_branchPointers.Count == 0) + { + return; + } + + int targetIndex = writer.InstructionPointer; + int startIndex = _branchPointers.Count - 1; + + if (startIndex >= 0 && + _branchPointers[startIndex].Item1 == BranchType.Unconditional && + _branchPointers[startIndex].Item2 == targetIndex - 1) + { + // Remove the last branch if it is redundant. + writer.RemoveLastInstruction(); + startIndex--; + targetIndex--; + } + + Assembler asm = new(writer); + + writeEpilogue(); + asm.Ret(); + + for (int i = startIndex; i >= 0; i--) + { + (BranchType type, int branchIndex) = _branchPointers[i]; + + uint encoding = writer.ReadInstructionAt(branchIndex); + int delta = targetIndex - branchIndex; + + if (type == BranchType.Conditional) + { + uint branchMask = 0x7ffff; + int branchMax = (int)(branchMask + 1) / 2; + + if (delta >= -branchMax && delta < branchMax) + { + writer.WriteInstructionAt(branchIndex, (encoding & ~(branchMask << 5)) | (uint)((delta & branchMask) << 5)); + } + else + { + // If the branch target is too far away, we use a regular unconditional branch + // instruction instead which has a much higher range. + // We branch directly to the end of the function, where we put the conditional branch, + // and then branch back to the next instruction or return the branch target depending + // on the branch being taken or not. + + delta = writer.InstructionPointer - branchIndex; + + uint branchInst = 0x14000000u | ((uint)delta & 0x3ffffff); + Debug.Assert(ExtractSImm26Times4(branchInst) == delta * 4); + + writer.WriteInstructionAt(branchIndex, branchInst); + + int movedBranchIndex = writer.InstructionPointer; + + writer.WriteInstruction(0u); // Placeholder + asm.B((branchIndex + 1 - writer.InstructionPointer) * 4); + + delta = targetIndex - movedBranchIndex; + + writer.WriteInstructionAt(movedBranchIndex, (encoding & ~(branchMask << 5)) | (uint)((delta & branchMask) << 5)); + } + } + else + { + Debug.Assert(type == BranchType.Unconditional); + + writer.WriteInstructionAt(branchIndex, (encoding & ~0x3ffffffu) | (uint)(delta & 0x3ffffff)); + } + } + } + + private static int ExtractSImm26Times4(uint encoding) + { + return (int)(encoding << 6) >> 4; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/Operand.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/Operand.cs new file mode 100644 index 00000000..8bf34030 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/Operand.cs @@ -0,0 +1,38 @@ +using System.Diagnostics; + +namespace Ryujinx.Cpu.LightningJit.CodeGen +{ + readonly struct Operand + { + public readonly OperandKind Kind { get; } + public readonly OperandType Type { get; } + public readonly ulong Value { get; } + + public Operand(OperandKind kind, OperandType type, ulong value) + { + Kind = kind; + Type = type; + Value = value; + } + + public Operand(int index, RegisterType regType, OperandType type) : this(OperandKind.Register, type, (ulong)((int)regType << 24 | index)) + { + } + + public Operand(OperandType type, ulong value) : this(OperandKind.Constant, type, value) + { + } + + public readonly Register GetRegister() + { + Debug.Assert(Kind == OperandKind.Register); + + return new Register((int)Value & 0xffffff, (RegisterType)(Value >> 24)); + } + + public readonly int AsInt32() + { + return (int)Value; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/OperandKind.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/OperandKind.cs new file mode 100644 index 00000000..4f31763b --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/OperandKind.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Cpu.LightningJit.CodeGen +{ + enum OperandKind + { + None, + Constant, + Label, + Register, + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/OperandType.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/OperandType.cs new file mode 100644 index 00000000..6191f791 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/OperandType.cs @@ -0,0 +1,35 @@ +using System; + +namespace Ryujinx.Cpu.LightningJit.CodeGen +{ + enum OperandType + { + None, + I32, + I64, + FP32, + FP64, + V128, + } + + static class OperandTypeExtensions + { + public static bool IsInteger(this OperandType type) + { + return type == OperandType.I32 || type == OperandType.I64; + } + + public static int GetSizeInBytes(this OperandType type) + { + return type switch + { + OperandType.FP32 => 4, + OperandType.FP64 => 8, + OperandType.I32 => 4, + OperandType.I64 => 8, + OperandType.V128 => 16, + _ => throw new InvalidOperationException($"Invalid operand type \"{type}\"."), + }; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/Register.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/Register.cs new file mode 100644 index 00000000..8b96c916 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/Register.cs @@ -0,0 +1,42 @@ +using System; + +namespace Ryujinx.Cpu.LightningJit.CodeGen +{ + readonly struct Register : IEquatable + { + public int Index { get; } + + public RegisterType Type { get; } + + public Register(int index, RegisterType type) + { + Index = index; + Type = type; + } + + public override int GetHashCode() + { + return (ushort)Index | ((int)Type << 16); + } + + public static bool operator ==(Register x, Register y) + { + return x.Equals(y); + } + + public static bool operator !=(Register x, Register y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is Register reg && Equals(reg); + } + + public bool Equals(Register other) + { + return other.Index == Index && other.Type == Type; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeGen/RegisterType.cs b/src/Ryujinx.Cpu/LightningJit/CodeGen/RegisterType.cs new file mode 100644 index 00000000..c4a726bc --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeGen/RegisterType.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Cpu.LightningJit.CodeGen +{ + enum RegisterType + { + Integer, + Vector, + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CodeWriter.cs b/src/Ryujinx.Cpu/LightningJit/CodeWriter.cs new file mode 100644 index 00000000..ac4b22ff --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CodeWriter.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.LightningJit +{ + class CodeWriter + { + private readonly List _instructions; + + public int InstructionPointer => _instructions.Count; + + public CodeWriter() + { + _instructions = new(); + } + + public void WriteInstruction(uint instruction) + { + _instructions.Add(instruction); + } + + public void WriteInstructionAt(int index, uint instruction) + { + _instructions[index] = instruction; + } + + public void WriteInstructionsAt(int index, CodeWriter writer) + { + _instructions.InsertRange(index, writer._instructions); + } + + public uint ReadInstructionAt(int index) + { + return _instructions[index]; + } + + public List GetList() + { + return _instructions; + } + + public void RemoveLastInstruction() + { + if (_instructions.Count > 0) + { + _instructions.RemoveAt(_instructions.Count - 1); + } + } + + public ReadOnlySpan AsSpan() + { + return CollectionsMarshal.AsSpan(_instructions); + } + + public ReadOnlySpan AsByteSpan() + { + return MemoryMarshal.Cast(AsSpan()); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CompiledFunction.cs b/src/Ryujinx.Cpu/LightningJit/CompiledFunction.cs new file mode 100644 index 00000000..ed375f22 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CompiledFunction.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.Cpu.LightningJit +{ + readonly ref struct CompiledFunction + { + public readonly ReadOnlySpan Code; + public readonly int GuestCodeLength; + + public CompiledFunction(ReadOnlySpan code, int guestCodeLength) + { + Code = code; + GuestCodeLength = guestCodeLength; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CpuPreset.cs b/src/Ryujinx.Cpu/LightningJit/CpuPreset.cs new file mode 100644 index 00000000..e5af69b3 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CpuPreset.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Cpu.LightningJit +{ + public readonly struct CpuPreset + { + public readonly IsaVersion Version; + public readonly IsaFeature Features; + + public CpuPreset(IsaVersion version, IsaFeature features) + { + Version = version; + Features = features; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/CpuPresets.cs b/src/Ryujinx.Cpu/LightningJit/CpuPresets.cs new file mode 100644 index 00000000..7798fbfc --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/CpuPresets.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Cpu.LightningJit +{ + public static class CpuPresets + { + public static CpuPreset CortexA57 => new( + IsaVersion.v80, + IsaFeature.FeatAes | + IsaFeature.FeatCrc32 | + IsaFeature.FeatSha1 | + IsaFeature.FeatSha256 | + IsaFeature.FeatPmull); + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Graph/DataFlow.cs b/src/Ryujinx.Cpu/LightningJit/Graph/DataFlow.cs new file mode 100644 index 00000000..e5b369d6 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Graph/DataFlow.cs @@ -0,0 +1,171 @@ +namespace Ryujinx.Cpu.LightningJit.Graph +{ + static class DataFlow + { + public static (RegisterMask[], RegisterMask[]) GetGlobalUses(IBlockList blocks) + { + // Compute local register inputs and outputs used inside blocks. + RegisterMask[] localInputs = new RegisterMask[blocks.Count]; + RegisterMask[] localOutputs = new RegisterMask[blocks.Count]; + + for (int index = 0; index < blocks.Count; index++) + { + IBlock block = blocks[index]; + + RegisterUse use = block.ComputeUseMasks(); + + localInputs[block.Index] = use.Read; + localOutputs[block.Index] = use.Write; + } + + // Compute global register inputs and outputs used across blocks. + RegisterMask[] globalInputs = new RegisterMask[blocks.Count]; + RegisterMask[] globalOutputs = new RegisterMask[blocks.Count]; + + bool modified; + + // Compute register outputs. + do + { + modified = false; + + for (int index = 0; index < blocks.Count; index++) + { + IBlock block = blocks[index]; + + int firstPIndex = GetFirstPredecessorIndex(block); + if (firstPIndex >= 0) + { + IBlock predecessor = block.GetPredecessor(firstPIndex); + + RegisterMask outputs = globalOutputs[predecessor.Index]; + + for (int pIndex = firstPIndex + 1; pIndex < block.PredecessorsCount; pIndex++) + { + predecessor = block.GetPredecessor(pIndex); + + if (predecessor.EndsWithContextStore()) + { + // If a block ended with a context store, then we don't need to care + // about any of it's outputs, as they have already been saved to the context. + // Common outputs must be reset as doing a context stores indicates we will + // do a function call and wipe all register values. + + continue; + } + + outputs |= globalOutputs[predecessor.Index]; + } + + outputs |= localOutputs[block.Index]; + modified |= Exchange(globalOutputs, block.Index, globalOutputs[block.Index] | outputs); + } + else + { + modified |= Exchange(globalOutputs, block.Index, localOutputs[block.Index]); + } + } + } + while (modified); + + // Compute register inputs. + do + { + modified = false; + + for (int index = blocks.Count - 1; index >= 0; index--) + { + IBlock block = blocks[index]; + + RegisterMask cmnOutputs = RegisterMask.Zero; + RegisterMask allOutputs = RegisterMask.Zero; + + int firstPIndex = GetFirstPredecessorIndex(block); + if (firstPIndex == 0) + { + IBlock predecessor = block.GetPredecessor(0); + + // Assumes that block index 0 is the entry block. + cmnOutputs = block.Index != 0 ? globalOutputs[predecessor.Index] : RegisterMask.Zero; + allOutputs = globalOutputs[predecessor.Index]; + + for (int pIndex = 1; pIndex < block.PredecessorsCount; pIndex++) + { + predecessor = block.GetPredecessor(pIndex); + + if (!predecessor.EndsWithContextStore()) + { + RegisterMask outputs = globalOutputs[predecessor.Index]; + + cmnOutputs &= outputs; + allOutputs |= outputs; + } + else + { + cmnOutputs = RegisterMask.Zero; + } + } + } + else if (firstPIndex > 0) + { + IBlock predecessor = block.GetPredecessor(firstPIndex); + + allOutputs = globalOutputs[predecessor.Index]; + + for (int pIndex = firstPIndex + 1; pIndex < block.PredecessorsCount; pIndex++) + { + predecessor = block.GetPredecessor(pIndex); + + if (!predecessor.EndsWithContextStore()) + { + allOutputs |= globalOutputs[predecessor.Index]; + } + } + } + + RegisterMask inputs = localInputs[block.Index]; + + // If this block will load from context at the end, + // we don't need to care about what comes next. + if (!block.EndsWithContextLoad()) + { + for (int sIndex = 0; sIndex < block.SuccessorsCount; sIndex++) + { + inputs |= globalInputs[block.GetSuccessor(sIndex).Index] & ~localOutputs[block.Index]; + } + } + + inputs |= allOutputs & ~localOutputs[block.Index]; + inputs &= ~cmnOutputs; + + modified |= Exchange(globalInputs, block.Index, globalInputs[block.Index] | inputs); + } + } + while (modified); + + return (globalInputs, globalOutputs); + } + + private static bool Exchange(RegisterMask[] masks, int blkIndex, RegisterMask value) + { + ref RegisterMask curValue = ref masks[blkIndex]; + bool changed = curValue != value; + curValue = value; + + return changed; + } + + private static int GetFirstPredecessorIndex(IBlock block) + { + for (int pIndex = 0; pIndex < block.PredecessorsCount; pIndex++) + { + if (!block.GetPredecessor(pIndex).EndsWithContextStore()) + { + return pIndex; + } + } + + return -1; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Graph/IBlock.cs b/src/Ryujinx.Cpu/LightningJit/Graph/IBlock.cs new file mode 100644 index 00000000..2e5d7132 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Graph/IBlock.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Cpu.LightningJit.Graph +{ + interface IBlock + { + int Index { get; } + + int PredecessorsCount { get; } + int SuccessorsCount { get; } + + IBlock GetSuccessor(int index); + IBlock GetPredecessor(int index); + + RegisterUse ComputeUseMasks(); + bool EndsWithContextLoad(); + bool EndsWithContextStore(); + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Graph/IBlockList.cs b/src/Ryujinx.Cpu/LightningJit/Graph/IBlockList.cs new file mode 100644 index 00000000..aeec799c --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Graph/IBlockList.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Cpu.LightningJit.Graph +{ + interface IBlockList + { + int Count { get; } + + IBlock this[int index] { get; } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Graph/RegisterMask.cs b/src/Ryujinx.Cpu/LightningJit/Graph/RegisterMask.cs new file mode 100644 index 00000000..cde324d5 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Graph/RegisterMask.cs @@ -0,0 +1,60 @@ +using System; + +namespace Ryujinx.Cpu.LightningJit.Graph +{ + readonly struct RegisterMask : IEquatable + { + public readonly uint GprMask; + public readonly uint FpSimdMask; + public readonly uint PStateMask; + + public static RegisterMask Zero => new(0u, 0u, 0u); + + public RegisterMask(uint gprMask, uint fpSimdMask, uint pStateMask) + { + GprMask = gprMask; + FpSimdMask = fpSimdMask; + PStateMask = pStateMask; + } + + public static RegisterMask operator &(RegisterMask x, RegisterMask y) + { + return new(x.GprMask & y.GprMask, x.FpSimdMask & y.FpSimdMask, x.PStateMask & y.PStateMask); + } + + public static RegisterMask operator |(RegisterMask x, RegisterMask y) + { + return new(x.GprMask | y.GprMask, x.FpSimdMask | y.FpSimdMask, x.PStateMask | y.PStateMask); + } + + public static RegisterMask operator ~(RegisterMask x) + { + return new(~x.GprMask, ~x.FpSimdMask, ~x.PStateMask); + } + + public static bool operator ==(RegisterMask x, RegisterMask y) + { + return x.Equals(y); + } + + public static bool operator !=(RegisterMask x, RegisterMask y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is RegisterMask regMask && Equals(regMask); + } + + public bool Equals(RegisterMask other) + { + return GprMask == other.GprMask && FpSimdMask == other.FpSimdMask && PStateMask == other.PStateMask; + } + + public override int GetHashCode() + { + return HashCode.Combine(GprMask, FpSimdMask, PStateMask); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Graph/RegisterUse.cs b/src/Ryujinx.Cpu/LightningJit/Graph/RegisterUse.cs new file mode 100644 index 00000000..83ef4192 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Graph/RegisterUse.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Cpu.LightningJit.Graph +{ + readonly struct RegisterUse + { + public readonly RegisterMask Read; + public readonly RegisterMask Write; + + public RegisterUse(RegisterMask read, RegisterMask write) + { + Read = read; + Write = write; + } + + public RegisterUse( + uint gprReadMask, + uint gprWriteMask, + uint fpSimdReadMask, + uint fpSimdWriteMask, + uint pStateReadMask, + uint pStateWriteMask) : this(new(gprReadMask, fpSimdReadMask, pStateReadMask), new(gprWriteMask, fpSimdWriteMask, pStateWriteMask)) + { + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/IStackWalker.cs b/src/Ryujinx.Cpu/LightningJit/IStackWalker.cs new file mode 100644 index 00000000..2fddef65 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/IStackWalker.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Cpu.LightningJit +{ + interface IStackWalker + { + IEnumerable GetCallStack(IntPtr framePointer, IntPtr codeRegionStart, int codeRegionSize, IntPtr codeRegion2Start, int codeRegion2Size); + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/IsaFeature.cs b/src/Ryujinx.Cpu/LightningJit/IsaFeature.cs new file mode 100644 index 00000000..c1f799d2 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/IsaFeature.cs @@ -0,0 +1,65 @@ +using System; + +namespace Ryujinx.Cpu.LightningJit +{ + [Flags] + public enum IsaFeature : ulong + { + None = 0, + FeatAa32bf16 = 1UL << 0, + FeatAa32i8mm = 1UL << 1, + FeatAes = 1UL << 2, + FeatBf16 = 1UL << 3, + FeatBti = 1UL << 4, + FeatChk = 1UL << 5, + FeatClrbhb = 1UL << 6, + FeatCrc32 = 1UL << 7, + FeatCssc = 1UL << 8, + FeatD128 = 1UL << 9, + FeatDgh = 1UL << 10, + FeatDotprod = 1UL << 11, + FeatFcma = 1UL << 12, + FeatFhm = 1UL << 13, + FeatFlagm = 1UL << 14, + FeatFlagm2 = 1UL << 15, + FeatFp16 = 1UL << 16, + FeatFrintts = 1UL << 17, + FeatGcs = 1UL << 18, + FeatHbc = 1UL << 19, + FeatI8mm = 1UL << 20, + FeatJscvt = 1UL << 21, + FeatLor = 1UL << 22, + FeatLrcpc = 1UL << 23, + FeatLrcpc2 = 1UL << 24, + FeatLrcpc3 = 1UL << 25, + FeatLs64 = 1UL << 26, + FeatLs64Accdata = 1UL << 27, + FeatLs64V = 1UL << 28, + FeatLse = 1UL << 29, + FeatLse128 = 1UL << 30, + FeatMops = 1UL << 31, + FeatMte = 1UL << 32, + FeatMte2 = 1UL << 33, + FeatPan = 1UL << 34, + FeatPauth = 1UL << 35, + FeatPmull = 1UL << 36, + FeatRas = 1UL << 37, + FeatRdm = 1UL << 38, + FeatRprfm = 1UL << 39, + FeatSb = 1UL << 40, + FeatSha1 = 1UL << 41, + FeatSha256 = 1UL << 42, + FeatSha3 = 1UL << 43, + FeatSha512 = 1UL << 44, + FeatSm3 = 1UL << 45, + FeatSm4 = 1UL << 46, + FeatSpe = 1UL << 47, + FeatSysinstr128 = 1UL << 48, + FeatSysreg128 = 1UL << 49, + FeatThe = 1UL << 50, + FeatTme = 1UL << 51, + FeatTrf = 1UL << 52, + FeatWfxt = 1UL << 53, + FeatXs = 1UL << 54, + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/IsaVersion.cs b/src/Ryujinx.Cpu/LightningJit/IsaVersion.cs new file mode 100644 index 00000000..5923eede --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/IsaVersion.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Cpu.LightningJit +{ + public enum IsaVersion + { + None, + v80, + v81, + v82, + v83, + v84, + v85, + v86, + v87, + v88, + v89, + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs b/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs new file mode 100644 index 00000000..b63636e3 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/LightningJitCpuContext.cs @@ -0,0 +1,58 @@ +using ARMeilleure.Memory; +using Ryujinx.Cpu.Jit; +using Ryujinx.Cpu.LightningJit.State; + +namespace Ryujinx.Cpu.LightningJit +{ + class LightningJitCpuContext : ICpuContext + { + private readonly ITickSource _tickSource; + private readonly Translator _translator; + + public LightningJitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit) + { + _tickSource = tickSource; + _translator = new Translator(memory, for64Bit); + memory.UnmapEvent += UnmapHandler; + } + + private void UnmapHandler(ulong address, ulong size) + { + _translator.InvalidateJitCacheRegion(address, size); + } + + /// + public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks) + { + return new ExecutionContext(new JitMemoryAllocator(), _tickSource, exceptionCallbacks); + } + + /// + public void Execute(IExecutionContext context, ulong address) + { + _translator.Execute((ExecutionContext)context, address); + } + + /// + public void InvalidateCacheRegion(ulong address, ulong size) + { + _translator.InvalidateJitCacheRegion(address, size); + } + + /// + public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled) + { + return new DummyDiskCacheLoadState(); + } + + /// + public void PrepareCodeRange(ulong address, ulong size) + { + } + + public void Dispose() + { + _translator.Dispose(); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/LightningJitEngine.cs b/src/Ryujinx.Cpu/LightningJit/LightningJitEngine.cs new file mode 100644 index 00000000..c97ddc7c --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/LightningJitEngine.cs @@ -0,0 +1,20 @@ +using ARMeilleure.Memory; + +namespace Ryujinx.Cpu.LightningJit +{ + public class LightningJitEngine : ICpuEngine + { + private readonly ITickSource _tickSource; + + public LightningJitEngine(ITickSource tickSource) + { + _tickSource = tickSource; + } + + /// + public ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit) + { + return new LightningJitCpuContext(_tickSource, memoryManager, for64Bit); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/NativeContextOffsets.cs b/src/Ryujinx.Cpu/LightningJit/NativeContextOffsets.cs new file mode 100644 index 00000000..8a3a968d --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/NativeContextOffsets.cs @@ -0,0 +1,17 @@ +using Ryujinx.Cpu.LightningJit.State; + +namespace Ryujinx.Cpu.LightningJit +{ + class NativeContextOffsets + { + public static int GprBaseOffset => NativeContext.GetXOffset(); + public static int FpSimdBaseOffset => NativeContext.GetVOffset(); + public static int FlagsBaseOffset => NativeContext.GetFlagsOffset(); + public static int FpFlagsBaseOffset => NativeContext.GetFpFlagsOffset(); + public static int TpidrEl0Offset => NativeContext.GetTpidrEl0Offset(); + public static int TpidrroEl0Offset => NativeContext.GetTpidrroEl0Offset(); + public static int RunningOffset => NativeContext.GetRunningOffset(); + public static int CounterOffset => NativeContext.GetCounterOffset(); + public static int DispatchAddressOffset => NativeContext.GetDispatchAddressOffset(); + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/NativeInterface.cs b/src/Ryujinx.Cpu/LightningJit/NativeInterface.cs new file mode 100644 index 00000000..da3ad983 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/NativeInterface.cs @@ -0,0 +1,93 @@ +using ARMeilleure.Memory; +using Ryujinx.Cpu.LightningJit.State; +using System; + +namespace Ryujinx.Cpu.LightningJit +{ + static class NativeInterface + { + private const int DczSizeLog2 = 4; // Log2 size in words + private const int DczSizeInBytes = 4 << DczSizeLog2; + + private class ThreadContext + { + public ExecutionContext Context { get; } + public IMemoryManager Memory { get; } + public Translator Translator { get; } + + public ThreadContext(ExecutionContext context, IMemoryManager memory, Translator translator) + { + Context = context; + Memory = memory; + Translator = translator; + } + } + + [ThreadStatic] + private static ThreadContext Context; + + public static void RegisterThread(ExecutionContext context, IMemoryManager memory, Translator translator) + { + Context = new ThreadContext(context, memory, translator); + } + + public static void UnregisterThread() + { + Context = null; + } + + public static void Break(ulong address, int imm) + { + GetContext().OnBreak(address, imm); + } + + public static void SupervisorCall(ulong address, int imm) + { + GetContext().OnSupervisorCall(address, imm); + } + + public static void Undefined(ulong address, int opCode) + { + GetContext().OnUndefined(address, opCode); + } + + public static ulong GetCntfrqEl0() + { + return GetContext().CntfrqEl0; + } + + public static ulong GetCntpctEl0() + { + return GetContext().CntpctEl0; + } + + public static ulong GetFunctionAddress(IntPtr framePointer, ulong address) + { + return (ulong)Context.Translator.GetOrTranslatePointer(framePointer, address, GetContext().ExecutionMode); + } + + public static void InvalidateCacheLine(ulong address) + { + Context.Translator.InvalidateJitCacheRegion(address, DczSizeInBytes); + } + + public static bool CheckSynchronization() + { + ExecutionContext context = GetContext(); + + context.CheckInterrupt(); + + return context.Running; + } + + public static ExecutionContext GetContext() + { + return Context.Context; + } + + public static IMemoryManager GetMemoryManager() + { + return Context.Memory; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs b/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs new file mode 100644 index 00000000..facb9142 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/State/ExecutionContext.cs @@ -0,0 +1,153 @@ +using ARMeilleure.Memory; +using ARMeilleure.State; +using System; + +namespace Ryujinx.Cpu.LightningJit.State +{ + public class ExecutionContext : IExecutionContext + { + private const int MinCountForCheck = 4000; + + private readonly NativeContext _nativeContext; + + internal IntPtr NativeContextPtr => _nativeContext.BasePtr; + + private bool _interrupted; + private readonly ICounter _counter; + + public ulong Pc => _nativeContext.GetPc(); + + public ulong CntfrqEl0 => _counter.Frequency; + public ulong CntpctEl0 => _counter.Counter; + + public long TpidrEl0 + { + get => _nativeContext.GetTpidrEl0(); + set => _nativeContext.SetTpidrEl0(value); + } + + public long TpidrroEl0 + { + get => _nativeContext.GetTpidrroEl0(); + set => _nativeContext.SetTpidrroEl0(value); + } + + public uint Pstate + { + get => _nativeContext.GetPstate(); + set => _nativeContext.SetPstate(value); + } + + public uint Fpsr + { + get => _nativeContext.GetFPState((uint)FPSR.Mask); + set => _nativeContext.SetFPState(value, (uint)FPSR.Mask); + } + + public uint Fpcr + { + get => _nativeContext.GetFPState((uint)FPCR.Mask); + set => _nativeContext.SetFPState(value, (uint)FPCR.Mask); + } + + public bool IsAarch32 { get; set; } + + internal ExecutionMode ExecutionMode + { + get + { + if (IsAarch32) + { + return (Pstate & (1u << 5)) != 0 + ? ExecutionMode.Aarch32Thumb + : ExecutionMode.Aarch32Arm; + } + else + { + return ExecutionMode.Aarch64; + } + } + } + + public bool Running + { + get => _nativeContext.GetRunning(); + private set => _nativeContext.SetRunning(value); + } + + private readonly ExceptionCallbackNoArgs _interruptCallback; + private readonly ExceptionCallback _breakCallback; + private readonly ExceptionCallback _supervisorCallback; + private readonly ExceptionCallback _undefinedCallback; + + public ExecutionContext(IJitMemoryAllocator allocator, ICounter counter, ExceptionCallbacks exceptionCallbacks) + { + _nativeContext = new NativeContext(allocator); + _counter = counter; + _interruptCallback = exceptionCallbacks.InterruptCallback; + _breakCallback = exceptionCallbacks.BreakCallback; + _supervisorCallback = exceptionCallbacks.SupervisorCallback; + _undefinedCallback = exceptionCallbacks.UndefinedCallback; + + Running = true; + + _nativeContext.SetCounter(MinCountForCheck); + } + + public ulong GetX(int index) => _nativeContext.GetX(index); + public void SetX(int index, ulong value) => _nativeContext.SetX(index, value); + + public V128 GetV(int index) => _nativeContext.GetV(index); + public void SetV(int index, V128 value) => _nativeContext.SetV(index, value); + + internal void CheckInterrupt() + { + if (_interrupted) + { + _interrupted = false; + + _interruptCallback?.Invoke(this); + } + + _nativeContext.SetCounter(MinCountForCheck); + } + + public void RequestInterrupt() + { + _interrupted = true; + } + + internal void OnBreak(ulong address, int imm) + { + _breakCallback?.Invoke(this, address, imm); + } + + internal void OnSupervisorCall(ulong address, int imm) + { + _supervisorCallback?.Invoke(this, address, imm); + } + + internal void OnUndefined(ulong address, int opCode) + { + _undefinedCallback?.Invoke(this, address, opCode); + } + + public void StopRunning() + { + Running = false; + + _nativeContext.SetCounter(0); + } + + protected virtual void Dispose(bool disposing) + { + _nativeContext.Dispose(); + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/State/ExecutionMode.cs b/src/Ryujinx.Cpu/LightningJit/State/ExecutionMode.cs new file mode 100644 index 00000000..0602bc9a --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/State/ExecutionMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Cpu.LightningJit.State +{ + enum ExecutionMode + { + Aarch32Arm = 0, + Aarch32Thumb = 1, + Aarch64 = 2, + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/State/NativeContext.cs b/src/Ryujinx.Cpu/LightningJit/State/NativeContext.cs new file mode 100644 index 00000000..fdb8793d --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/State/NativeContext.cs @@ -0,0 +1,173 @@ +using ARMeilleure.Memory; +using ARMeilleure.State; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Cpu.LightningJit.State +{ + class NativeContext : IDisposable + { + private unsafe struct NativeCtxStorage + { + public fixed ulong X[32]; + public fixed ulong V[64]; + public uint Flags; + public uint FpFlags; + public long TpidrEl0; + public long TpidrroEl0; + public int Counter; + public uint HostFpFlags; + public ulong DispatchAddress; + public int Running; + } + + private static NativeCtxStorage _dummyStorage = new(); + + private readonly IJitMemoryBlock _block; + + public IntPtr BasePtr => _block.Pointer; + + public NativeContext(IJitMemoryAllocator allocator) + { + _block = allocator.Allocate((ulong)Unsafe.SizeOf()); + } + + public ulong GetPc() + { + // TODO: More precise tracking of PC value. + return GetStorage().DispatchAddress; + } + + public unsafe ulong GetX(int index) + { + if ((uint)index >= 32) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return GetStorage().X[index]; + } + + public unsafe void SetX(int index, ulong value) + { + if ((uint)index >= 32) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + GetStorage().X[index] = value; + } + + public unsafe V128 GetV(int index) + { + if ((uint)index >= 32) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return new V128(GetStorage().V[index * 2 + 0], GetStorage().V[index * 2 + 1]); + } + + public unsafe void SetV(int index, V128 value) + { + if ((uint)index >= 32) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + GetStorage().V[index * 2 + 0] = value.Extract(0); + GetStorage().V[index * 2 + 1] = value.Extract(1); + } + + public unsafe uint GetPstate() + { + return GetStorage().Flags; + } + + public unsafe void SetPstate(uint value) + { + GetStorage().Flags = value; + } + + public unsafe uint GetFPState(uint mask = uint.MaxValue) + { + return GetStorage().FpFlags & mask; + } + + public unsafe void SetFPState(uint value, uint mask = uint.MaxValue) + { + GetStorage().FpFlags = (value & mask) | (GetStorage().FpFlags & ~mask); + } + + 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; + + public bool GetRunning() => GetStorage().Running != 0; + public void SetRunning(bool value) => GetStorage().Running = value ? 1 : 0; + + public unsafe static int GetXOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.X[0]); + } + + public unsafe static int GetVOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.V[0]); + } + + public static int GetFlagsOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.Flags); + } + + public static int GetFpFlagsOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.FpFlags); + } + + 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); + } + + public static int GetHostFpFlagsOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.HostFpFlags); + } + + public static int GetDispatchAddressOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.DispatchAddress); + } + + public static int GetRunningOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.Running); + } + + private static int StorageOffset(ref NativeCtxStorage storage, ref T target) + { + return (int)Unsafe.ByteOffset(ref Unsafe.As(ref storage), ref target); + } + + private unsafe ref NativeCtxStorage GetStorage() => ref Unsafe.AsRef((void*)_block.Pointer); + + public void Dispose() => _block.Dispose(); + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Table/IInstInfo.cs b/src/Ryujinx.Cpu/LightningJit/Table/IInstInfo.cs new file mode 100644 index 00000000..a51fe37b --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Table/IInstInfo.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Cpu.LightningJit.Table +{ + interface IInstInfo + { + public uint Encoding { get; } + public uint EncodingMask { get; } + public IsaVersion Version { get; } + public IsaFeature Feature { get; } + + bool IsConstrained(uint encoding); + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Table/InstEncoding.cs b/src/Ryujinx.Cpu/LightningJit/Table/InstEncoding.cs new file mode 100644 index 00000000..32250b03 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Table/InstEncoding.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Cpu.LightningJit.Table +{ + readonly struct InstEncoding + { + public readonly uint Encoding; + public readonly uint EncodingMask; + + public InstEncoding(uint encoding, uint encodingMask) + { + Encoding = encoding; + EncodingMask = encodingMask; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Table/InstTableLevel.cs b/src/Ryujinx.Cpu/LightningJit/Table/InstTableLevel.cs new file mode 100644 index 00000000..6567efee --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Table/InstTableLevel.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using System.Numerics; + +namespace Ryujinx.Cpu.LightningJit.Table +{ + class InstTableLevel where T : IInstInfo + { + private readonly int _shift; + private readonly uint _mask; + private readonly InstTableLevel[] _childs; + private readonly List _insts; + + private InstTableLevel(List insts, uint baseMask) + { + uint commonEncodingMask = baseMask; + + foreach (T info in insts) + { + commonEncodingMask &= info.EncodingMask; + } + + if (commonEncodingMask != 0) + { + _shift = BitOperations.TrailingZeroCount(commonEncodingMask); + int bits = BitOperations.TrailingZeroCount(~(commonEncodingMask >> _shift)); + int count = 1 << bits; + _mask = uint.MaxValue >> (32 - bits); + + _childs = new InstTableLevel[count]; + + List[] splitList = new List[count]; + + for (int index = 0; index < insts.Count; index++) + { + int splitIndex = (int)((insts[index].Encoding >> _shift) & _mask); + + (splitList[splitIndex] ??= new()).Add(insts[index]); + } + + for (int index = 0; index < count; index++) + { + if (splitList[index] == null) + { + continue; + } + + _childs[index] = new InstTableLevel(splitList[index], baseMask & ~commonEncodingMask); + } + } + else + { + _insts = insts; + } + } + + public InstTableLevel(List insts) : this(insts, uint.MaxValue) + { + } + + public bool TryFind(uint encoding, IsaVersion version, IsaFeature features, out T value) + { + if (_childs != null) + { + int index = (int)((encoding >> _shift) & _mask); + + if (_childs[index] == null) + { + value = default; + + return false; + } + + return _childs[index].TryFind(encoding, version, features, out value); + } + else + { + foreach (T info in _insts) + { + if ((encoding & info.EncodingMask) == info.Encoding && + !info.IsConstrained(encoding) && + info.Version <= version && + (info.Feature & features) == info.Feature) + { + value = info; + + return true; + } + } + + value = default; + + return false; + } + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/TranslatedFunction.cs b/src/Ryujinx.Cpu/LightningJit/TranslatedFunction.cs new file mode 100644 index 00000000..a4e2c7b9 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/TranslatedFunction.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.Cpu.LightningJit +{ + class TranslatedFunction + { + public IntPtr FuncPointer { get; } + public ulong GuestSize { get; } + + public TranslatedFunction(IntPtr funcPointer, ulong guestSize) + { + FuncPointer = funcPointer; + GuestSize = guestSize; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/Translator.cs b/src/Ryujinx.Cpu/LightningJit/Translator.cs new file mode 100644 index 00000000..d6241025 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/Translator.cs @@ -0,0 +1,227 @@ +using ARMeilleure.Common; +using ARMeilleure.Memory; +using Ryujinx.Cpu.Jit; +using Ryujinx.Cpu.LightningJit.Cache; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using Ryujinx.Cpu.LightningJit.State; +using Ryujinx.Cpu.Signal; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Cpu.LightningJit +{ + class Translator : IDisposable + { + // Should be enabled on platforms that enforce W^X. + private static bool IsNoWxPlatform => false; + + private static readonly AddressTable.Level[] _levels64Bit = + new AddressTable.Level[] + { + new(31, 17), + new(23, 8), + new(15, 8), + new( 7, 8), + new( 2, 5), + }; + + private static readonly AddressTable.Level[] _levels32Bit = + new AddressTable.Level[] + { + new(23, 9), + new(15, 8), + new( 7, 8), + new( 1, 6), + }; + + private readonly ConcurrentQueue> _oldFuncs; + private readonly NoWxCache _noWxCache; + private bool _disposed; + + internal TranslatorCache Functions { get; } + internal AddressTable FunctionTable { get; } + internal TranslatorStubs Stubs { get; } + internal IMemoryManager Memory { get; } + + public Translator(IMemoryManager memory, bool for64Bits) + { + Memory = memory; + + _oldFuncs = new ConcurrentQueue>(); + + if (IsNoWxPlatform) + { + _noWxCache = new(new JitMemoryAllocator(), CreateStackWalker(), this); + } + else + { + JitCache.Initialize(new JitMemoryAllocator(forJit: true)); + } + + Functions = new TranslatorCache(); + FunctionTable = new AddressTable(for64Bits ? _levels64Bit : _levels32Bit); + Stubs = new TranslatorStubs(FunctionTable, _noWxCache); + + FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; + + if (memory.Type.IsHostMappedOrTracked()) + { + NativeSignalHandler.InitializeSignalHandler(); + } + } + + private static IStackWalker CreateStackWalker() + { + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + return new StackWalker(); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public void Execute(State.ExecutionContext context, ulong address) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + NativeInterface.RegisterThread(context, Memory, this); + + Stubs.DispatchLoop(context.NativeContextPtr, address); + + NativeInterface.UnregisterThread(); + _noWxCache?.ClearEntireThreadLocalCache(); + } + + internal IntPtr GetOrTranslatePointer(IntPtr framePointer, ulong address, ExecutionMode mode) + { + if (_noWxCache != null) + { + CompiledFunction func = Compile(address, mode); + + return _noWxCache.Map(framePointer, func.Code, address, (ulong)func.GuestCodeLength); + } + + return GetOrTranslate(address, mode).FuncPointer; + } + + private TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode) + { + if (!Functions.TryGetValue(address, out TranslatedFunction func)) + { + func = Translate(address, mode); + + TranslatedFunction oldFunc = Functions.GetOrAdd(address, func.GuestSize, func); + + if (oldFunc != func) + { + JitCache.Unmap(func.FuncPointer); + func = oldFunc; + } + + RegisterFunction(address, func); + } + + return func; + } + + internal void RegisterFunction(ulong guestAddress, TranslatedFunction func) + { + if (FunctionTable.IsValid(guestAddress)) + { + Volatile.Write(ref FunctionTable.GetValue(guestAddress), (ulong)func.FuncPointer); + } + } + + private TranslatedFunction Translate(ulong address, ExecutionMode mode) + { + CompiledFunction func = Compile(address, mode); + IntPtr funcPointer = JitCache.Map(func.Code); + + return new TranslatedFunction(funcPointer, (ulong)func.GuestCodeLength); + } + + private CompiledFunction Compile(ulong address, ExecutionMode mode) + { + return AarchCompiler.Compile(CpuPresets.CortexA57, Memory, address, FunctionTable, Stubs.DispatchStub, mode, RuntimeInformation.ProcessArchitecture); + } + + public void InvalidateJitCacheRegion(ulong address, ulong size) + { + ulong[] overlapAddresses = Array.Empty(); + + int overlapsCount = Functions.GetOverlaps(address, size, ref overlapAddresses); + + for (int index = 0; index < overlapsCount; index++) + { + ulong overlapAddress = overlapAddresses[index]; + + if (Functions.TryGetValue(overlapAddress, out TranslatedFunction overlap)) + { + Functions.Remove(overlapAddress); + Volatile.Write(ref FunctionTable.GetValue(overlapAddress), FunctionTable.Fill); + EnqueueForDeletion(overlapAddress, overlap); + } + } + + // TODO: Remove overlapping functions from the JitCache aswell. + // This should be done safely, with a mechanism to ensure the function is not being executed. + } + + private void EnqueueForDeletion(ulong guestAddress, TranslatedFunction func) + { + _oldFuncs.Enqueue(new(guestAddress, func)); + } + + private void ClearJitCache() + { + List functions = Functions.AsList(); + + foreach (var func in functions) + { + JitCache.Unmap(func.FuncPointer); + } + + Functions.Clear(); + + while (_oldFuncs.TryDequeue(out var kv)) + { + JitCache.Unmap(kv.Value.FuncPointer); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + if (_noWxCache != null) + { + _noWxCache.Dispose(); + } + else + { + ClearJitCache(); + } + + Stubs.Dispose(); + FunctionTable.Dispose(); + } + + _disposed = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/TranslatorCache.cs b/src/Ryujinx.Cpu/LightningJit/TranslatorCache.cs new file mode 100644 index 00000000..60e13de9 --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/TranslatorCache.cs @@ -0,0 +1,96 @@ +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Cpu.LightningJit +{ + internal class TranslatorCache + { + private readonly IntervalTree _tree; + private readonly ReaderWriterLockSlim _treeLock; + + public int Count => _tree.Count; + + public TranslatorCache() + { + _tree = new IntervalTree(); + _treeLock = new ReaderWriterLockSlim(); + } + + public bool TryAdd(ulong address, ulong size, T value) + { + return AddOrUpdate(address, size, value, null); + } + + public bool AddOrUpdate(ulong address, ulong size, T value, Func updateFactoryCallback) + { + _treeLock.EnterWriteLock(); + bool result = _tree.AddOrUpdate(address, address + size, value, updateFactoryCallback); + _treeLock.ExitWriteLock(); + + return result; + } + + public T GetOrAdd(ulong address, ulong size, T value) + { + _treeLock.EnterWriteLock(); + value = _tree.GetOrAdd(address, address + size, value); + _treeLock.ExitWriteLock(); + + return value; + } + + public bool Remove(ulong address) + { + _treeLock.EnterWriteLock(); + bool removed = _tree.Remove(address) != 0; + _treeLock.ExitWriteLock(); + + return removed; + } + + public void Clear() + { + _treeLock.EnterWriteLock(); + _tree.Clear(); + _treeLock.ExitWriteLock(); + } + + public bool ContainsKey(ulong address) + { + _treeLock.EnterReadLock(); + bool result = _tree.ContainsKey(address); + _treeLock.ExitReadLock(); + + return result; + } + + public bool TryGetValue(ulong address, out T value) + { + _treeLock.EnterReadLock(); + bool result = _tree.TryGet(address, out value); + _treeLock.ExitReadLock(); + + return result; + } + + public int GetOverlaps(ulong address, ulong size, ref ulong[] overlaps) + { + _treeLock.EnterReadLock(); + int count = _tree.Get(address, address + size, ref overlaps); + _treeLock.ExitReadLock(); + + return count; + } + + public List AsList() + { + _treeLock.EnterReadLock(); + List list = _tree.AsList(); + _treeLock.ExitReadLock(); + + return list; + } + } +} diff --git a/src/Ryujinx.Cpu/LightningJit/TranslatorStubs.cs b/src/Ryujinx.Cpu/LightningJit/TranslatorStubs.cs new file mode 100644 index 00000000..914712bb --- /dev/null +++ b/src/Ryujinx.Cpu/LightningJit/TranslatorStubs.cs @@ -0,0 +1,380 @@ +using ARMeilleure.Common; +using Ryujinx.Cpu.LightningJit.Cache; +using Ryujinx.Cpu.LightningJit.CodeGen; +using Ryujinx.Cpu.LightningJit.CodeGen.Arm64; +using Ryujinx.Cpu.LightningJit.State; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.LightningJit +{ + delegate void DispatcherFunction(IntPtr nativeContext, ulong startAddress); + + /// + /// Represents a stub manager. + /// + class TranslatorStubs : IDisposable + { + private delegate ulong GetFunctionAddressDelegate(IntPtr framePointer, ulong address); + + private readonly Lazy _slowDispatchStub; + + private bool _disposed; + + private readonly AddressTable _functionTable; + private readonly NoWxCache _noWxCache; + private readonly GetFunctionAddressDelegate _getFunctionAddressRef; + private readonly IntPtr _getFunctionAddress; + private readonly Lazy _dispatchStub; + private readonly Lazy _dispatchLoop; + + /// + /// Gets the dispatch stub. + /// + /// instance was disposed + public IntPtr DispatchStub + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _dispatchStub.Value; + } + } + + /// + /// Gets the slow dispatch stub. + /// + /// instance was disposed + public IntPtr SlowDispatchStub + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _slowDispatchStub.Value; + } + } + + /// + /// Gets the dispatch loop function. + /// + /// instance was disposed + public DispatcherFunction DispatchLoop + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _dispatchLoop.Value; + } + } + + /// + /// Initializes a new instance of the class with the specified + /// instance. + /// + /// Function table used to store pointers to the functions that the guest code will call + /// Cache used on platforms that enforce W^X, otherwise should be null + /// is null + public TranslatorStubs(AddressTable functionTable, NoWxCache noWxCache) + { + ArgumentNullException.ThrowIfNull(functionTable); + + _functionTable = functionTable; + _noWxCache = noWxCache; + _getFunctionAddressRef = NativeInterface.GetFunctionAddress; + _getFunctionAddress = Marshal.GetFunctionPointerForDelegate(_getFunctionAddressRef); + _slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true); + _dispatchStub = new(GenerateDispatchStub, isThreadSafe: true); + _dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true); + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resouces + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (_noWxCache == null) + { + if (_dispatchStub.IsValueCreated) + { + JitCache.Unmap(_dispatchStub.Value); + } + + if (_dispatchLoop.IsValueCreated) + { + JitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value)); + } + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~TranslatorStubs() + { + Dispose(false); + } + + /// + /// Generates a . + /// + /// Generated + private IntPtr GenerateDispatchStub() + { + List branchToFallbackOffsets = new(); + + CodeWriter writer = new(); + + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + Assembler asm = new(writer); + RegisterSaveRestore rsr = new((1u << 19) | (1u << 21) | (1u << 22), hasCall: true); + + rsr.WritePrologue(ref asm); + + Operand context = Register(19); + asm.Mov(context, Register(0)); + + // Load the target guest address from the native context. + Operand guestAddress = Register(16); + + asm.LdrRiUn(guestAddress, context, NativeContext.GetDispatchAddressOffset()); + + // Check if guest address is within range of the AddressTable. + asm.And(Register(17), guestAddress, Const(~_functionTable.Mask)); + + branchToFallbackOffsets.Add(writer.InstructionPointer); + + asm.Cbnz(Register(17), 0); + + Operand page = Register(17); + Operand index = Register(21); + Operand mask = Register(22); + + asm.Mov(page, (ulong)_functionTable.Base); + + for (int i = 0; i < _functionTable.Levels.Length; i++) + { + ref var level = ref _functionTable.Levels[i]; + + asm.Mov(mask, level.Mask >> level.Index); + asm.And(index, mask, guestAddress, ArmShiftType.Lsr, level.Index); + + if (i < _functionTable.Levels.Length - 1) + { + asm.LdrRr(page, page, index, ArmExtensionType.Uxtx, true); + + branchToFallbackOffsets.Add(writer.InstructionPointer); + + asm.Cbz(page, 0); + } + } + + asm.LdrRr(page, page, index, ArmExtensionType.Uxtx, true); + + rsr.WriteEpilogue(ref asm); + + asm.Br(page); + + foreach (int branchOffset in branchToFallbackOffsets) + { + uint branchInst = writer.ReadInstructionAt(branchOffset); + Debug.Assert(writer.InstructionPointer > branchOffset); + writer.WriteInstructionAt(branchOffset, branchInst | ((uint)(writer.InstructionPointer - branchOffset) << 5)); + } + + // Fallback. + asm.Mov(Register(0), Register(29)); + asm.Mov(Register(1), guestAddress); + asm.Mov(Register(16), (ulong)_getFunctionAddress); + asm.Blr(Register(16)); + asm.Mov(Register(16), Register(0)); + asm.Mov(Register(0), Register(19)); + + rsr.WriteEpilogue(ref asm); + + asm.Br(Register(16)); + } + else + { + throw new PlatformNotSupportedException(); + } + + return Map(writer.AsByteSpan()); + } + + /// + /// Generates a . + /// + /// Generated + private IntPtr GenerateSlowDispatchStub() + { + CodeWriter writer = new(); + + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + Assembler asm = new(writer); + RegisterSaveRestore rsr = new(1u << 19, hasCall: true); + + rsr.WritePrologue(ref asm); + + Operand context = Register(19); + asm.Mov(context, Register(0)); + + // Load the target guest address from the native context. + asm.Mov(Register(0), Register(29)); + asm.LdrRiUn(Register(1), context, NativeContext.GetDispatchAddressOffset()); + asm.Mov(Register(16), (ulong)_getFunctionAddress); + asm.Blr(Register(16)); + asm.Mov(Register(16), Register(0)); + asm.Mov(Register(0), Register(19)); + + rsr.WriteEpilogue(ref asm); + + asm.Br(Register(16)); + } + else + { + throw new PlatformNotSupportedException(); + } + + return Map(writer.AsByteSpan()); + } + + /// + /// Emits code that syncs FP state before executing guest code, or returns it to normal. + /// + /// Assembler + /// Pointer to the native context + /// First temporary register + /// Second temporary register + /// True if entering guest code, false otherwise + private static void EmitSyncFpContext(ref Assembler asm, Operand context, Operand tempRegister, Operand tempRegister2, bool enter) + { + if (enter) + { + EmitSwapFpFlags(ref asm, context, tempRegister, tempRegister2, NativeContext.GetFpFlagsOffset(), NativeContext.GetHostFpFlagsOffset()); + } + else + { + EmitSwapFpFlags(ref asm, context, tempRegister, tempRegister2, NativeContext.GetHostFpFlagsOffset(), NativeContext.GetFpFlagsOffset()); + } + } + + /// + /// Swaps the FPCR and FPSR values with values stored in the native context. + /// + /// Assembler + /// Pointer to the native context + /// First temporary register + /// Second temporary register + /// Offset of the new flags that will be loaded + /// Offset where the current flags should be saved + private static void EmitSwapFpFlags(ref Assembler asm, Operand context, Operand tempRegister, Operand tempRegister2, int loadOffset, int storeOffset) + { + asm.MrsFpcr(tempRegister); + asm.MrsFpsr(tempRegister2); + asm.Orr(tempRegister, tempRegister, tempRegister2); + + asm.StrRiUn(tempRegister, context, storeOffset); + + asm.LdrRiUn(tempRegister, context, loadOffset); + asm.MsrFpcr(tempRegister); + asm.MsrFpsr(tempRegister2); + } + + /// + /// Generates a function. + /// + /// function + private DispatcherFunction GenerateDispatchLoop() + { + CodeWriter writer = new(); + + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + Assembler asm = new(writer); + RegisterSaveRestore rsr = new(1u << 19, hasCall: true); + + rsr.WritePrologue(ref asm); + + Operand context = Register(19); + asm.Mov(context, Register(0)); + + EmitSyncFpContext(ref asm, context, Register(16, OperandType.I32), Register(17, OperandType.I32), true); + + // Load the target guest address from the native context. + Operand guestAddress = Register(16); + + asm.Mov(guestAddress, Register(1)); + + int loopStartIndex = writer.InstructionPointer; + + asm.StrRiUn(guestAddress, context, NativeContext.GetDispatchAddressOffset()); + asm.Mov(Register(0), context); + asm.Mov(Register(17), (ulong)DispatchStub); + asm.Blr(Register(17)); + asm.Mov(guestAddress, Register(0)); + asm.Cbz(guestAddress, 16); + asm.LdrRiUn(Register(17), context, NativeContext.GetRunningOffset()); + asm.Cbz(Register(17), 8); + asm.B((loopStartIndex - writer.InstructionPointer) * 4); + + EmitSyncFpContext(ref asm, context, Register(16, OperandType.I32), Register(17, OperandType.I32), false); + + rsr.WriteEpilogue(ref asm); + + asm.Ret(); + } + else + { + throw new PlatformNotSupportedException(); + } + + IntPtr pointer = Map(writer.AsByteSpan()); + + return Marshal.GetDelegateForFunctionPointer(pointer); + } + + private IntPtr Map(ReadOnlySpan code) + { + if (_noWxCache != null) + { + return _noWxCache.MapPageAligned(code); + } + else + { + return JitCache.Map(code); + } + } + + private static Operand Register(int register, OperandType type = OperandType.I64) + { + return new Operand(register, RegisterType.Integer, type); + } + + private static Operand Const(ulong value) + { + return new(OperandKind.Constant, OperandType.I64, value); + } + } +} diff --git a/src/Ryujinx.Cpu/LoadState.cs b/src/Ryujinx.Cpu/LoadState.cs new file mode 100644 index 00000000..ea8f2abf --- /dev/null +++ b/src/Ryujinx.Cpu/LoadState.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Cpu +{ + /// + /// Load state. + /// + public enum LoadState + { + Unloaded, + Loading, + Loaded, + } +} diff --git a/src/Ryujinx.Cpu/ManagedPageFlags.cs b/src/Ryujinx.Cpu/ManagedPageFlags.cs new file mode 100644 index 00000000..a839dae6 --- /dev/null +++ b/src/Ryujinx.Cpu/ManagedPageFlags.cs @@ -0,0 +1,389 @@ +using Ryujinx.Memory; +using Ryujinx.Memory.Tracking; +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.Cpu +{ + /// + /// A page bitmap that keeps track of mapped state and tracking protection + /// for managed memory accesses (not using host page protection). + /// + internal readonly struct ManagedPageFlags + { + public const int PageBits = 12; + public const int PageSize = 1 << PageBits; + public const int PageMask = PageSize - 1; + + private readonly ulong[] _pageBitmap; + + public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry. + public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set. + + private enum ManagedPtBits : ulong + { + Unmapped = 0, + Mapped, + WriteTracked, + ReadWriteTracked, + + MappedReplicated = 0x5555555555555555, + WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa, + ReadWriteTrackedReplicated = ulong.MaxValue, + } + + public ManagedPageFlags(int addressSpaceBits) + { + int bits = Math.Max(0, addressSpaceBits - (PageBits + PageToPteShift)); + _pageBitmap = new ulong[1 << bits]; + } + + /// + /// Computes the number of pages in a virtual address range. + /// + /// Virtual address of the range + /// Size of the range + /// The virtual address of the beginning of the first page + /// This function does not differentiate between allocated and unallocated pages. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetPagesCount(ulong va, ulong size, out ulong startVa) + { + // WARNING: Always check if ulong does not overflow during the operations. + startVa = va & ~(ulong)PageMask; + ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; + + return (int)(vaSpan / PageSize); + } + + /// + /// Checks if the page at a given CPU virtual address is mapped. + /// + /// Virtual address to check + /// True if the address is mapped, false otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool IsMapped(ulong va) + { + ulong page = va >> PageBits; + + int bit = (int)((page & 31) << 1); + + int pageIndex = (int)(page >> PageToPteShift); + ref ulong pageRef = ref _pageBitmap[pageIndex]; + + ulong pte = Volatile.Read(ref pageRef); + + return ((pte >> bit) & 3) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex) + { + startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1); + endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1)); + + pageIndex = (int)(pageStart >> PageToPteShift); + pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift); + } + + /// + /// Checks if a memory range is mapped. + /// + /// Virtual address of the range + /// Size of the range in bytes + /// True if the entire range is mapped, false otherwise + public readonly bool IsRangeMapped(ulong va, ulong size) + { + int pages = GetPagesCount(va, size, out _); + + if (pages == 1) + { + return IsMapped(va); + } + + ulong pageStart = va >> PageBits; + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + // Check if either bit in each 2 bit page entry is set. + // OR the block with itself shifted down by 1, and check the first bit of each entry. + + ulong mask = BlockMappedMask & startMask; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + ulong pte = Volatile.Read(ref pageRef); + + pte |= pte >> 1; + if ((pte & mask) != mask) + { + return false; + } + + mask = BlockMappedMask; + } + + return true; + } + + /// + /// Reprotect a region of virtual memory for tracking. + /// + /// Virtual address base + /// Size of the region to protect + /// Memory protection to set + public readonly void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + { + // Protection is inverted on software pages, since the default value is 0. + protection = (~protection) & MemoryPermission.ReadAndWrite; + + int pages = GetPagesCount(va, size, out va); + ulong pageStart = va >> PageBits; + + if (pages == 1) + { + ulong protTag = protection switch + { + MemoryPermission.None => (ulong)ManagedPtBits.Mapped, + MemoryPermission.Write => (ulong)ManagedPtBits.WriteTracked, + _ => (ulong)ManagedPtBits.ReadWriteTracked, + }; + + int bit = (int)((pageStart & 31) << 1); + + ulong tagMask = 3UL << bit; + ulong invTagMask = ~tagMask; + + ulong tag = protTag << bit; + + int pageIndex = (int)(pageStart >> PageToPteShift); + ref ulong pageRef = ref _pageBitmap[pageIndex]; + + ulong pte; + + do + { + pte = Volatile.Read(ref pageRef); + } + while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte); + } + else + { + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + ulong mask = startMask; + + ulong protTag = protection switch + { + MemoryPermission.None => (ulong)ManagedPtBits.MappedReplicated, + MemoryPermission.Write => (ulong)ManagedPtBits.WriteTrackedReplicated, + _ => (ulong)ManagedPtBits.ReadWriteTrackedReplicated, + }; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + + ulong pte; + ulong mappedMask; + + // Change the protection of all 2 bit entries that are mapped. + do + { + pte = Volatile.Read(ref pageRef); + + mappedMask = pte | (pte >> 1); + mappedMask |= (mappedMask & BlockMappedMask) << 1; + mappedMask &= mask; // Only update mapped pages within the given range. + } + while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte); + + mask = ulong.MaxValue; + } + } + } + + /// + /// Alerts the memory tracking that a given region has been read from or written to. + /// This should be called before read/write is performed. + /// + /// Memory tracking structure to call when pages are protected + /// Virtual address of the region + /// Size of the region + /// True if the region was written, false if read + /// Optional ID of the handles that should not be signalled + /// + /// This function also validates that the given range is both valid and mapped, and will throw if it is not. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void SignalMemoryTracking(MemoryTracking tracking, ulong va, ulong size, bool write, int? exemptId = null) + { + // Software table, used for managed memory tracking. + + int pages = GetPagesCount(va, size, out _); + ulong pageStart = va >> PageBits; + + if (pages == 1) + { + ulong tag = (ulong)(write ? ManagedPtBits.WriteTracked : ManagedPtBits.ReadWriteTracked); + + int bit = (int)((pageStart & 31) << 1); + + int pageIndex = (int)(pageStart >> PageToPteShift); + ref ulong pageRef = ref _pageBitmap[pageIndex]; + + ulong pte = Volatile.Read(ref pageRef); + ulong state = ((pte >> bit) & 3); + + if (state >= tag) + { + tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); + return; + } + else if (state == 0) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); + } + } + else + { + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + ulong mask = startMask; + + ulong anyTrackingTag = (ulong)ManagedPtBits.WriteTrackedReplicated; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + + ulong pte = Volatile.Read(ref pageRef); + ulong mappedMask = mask & BlockMappedMask; + + ulong mappedPte = pte | (pte >> 1); + if ((mappedPte & mappedMask) != mappedMask) + { + ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}"); + } + + pte &= mask; + if ((pte & anyTrackingTag) != 0) // Search for any tracking. + { + // Writes trigger any tracking. + // Only trigger tracking from reads if both bits are set on any page. + if (write || (pte & (pte >> 1) & BlockMappedMask) != 0) + { + tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId); + break; + } + } + + mask = ulong.MaxValue; + } + } + } + + /// + /// Adds the given address mapping to the page table. + /// + /// Virtual memory address + /// Size to be mapped + public readonly void AddMapping(ulong va, ulong size) + { + int pages = GetPagesCount(va, size, out _); + ulong pageStart = va >> PageBits; + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + ulong mask = startMask; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask &= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + + ulong pte; + ulong mappedMask; + + // Map all 2-bit entries that are unmapped. + do + { + pte = Volatile.Read(ref pageRef); + + mappedMask = pte | (pte >> 1); + mappedMask |= (mappedMask & BlockMappedMask) << 1; + mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged. + } + while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte); + + mask = ulong.MaxValue; + } + } + + /// + /// Removes the given address mapping from the page table. + /// + /// Virtual memory address + /// Size to be unmapped + public readonly void RemoveMapping(ulong va, ulong size) + { + int pages = GetPagesCount(va, size, out _); + ulong pageStart = va >> PageBits; + ulong pageEnd = pageStart + (ulong)pages; + + GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex); + + startMask = ~startMask; + endMask = ~endMask; + + ulong mask = startMask; + + while (pageIndex <= pageEndIndex) + { + if (pageIndex == pageEndIndex) + { + mask |= endMask; + } + + ref ulong pageRef = ref _pageBitmap[pageIndex++]; + ulong pte; + + do + { + pte = Volatile.Read(ref pageRef); + } + while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte); + + mask = 0; + } + } + + private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message); + } +} diff --git a/src/Ryujinx.Cpu/MemoryEhMeilleure.cs b/src/Ryujinx.Cpu/MemoryEhMeilleure.cs new file mode 100644 index 00000000..379ace94 --- /dev/null +++ b/src/Ryujinx.Cpu/MemoryEhMeilleure.cs @@ -0,0 +1,85 @@ +using Ryujinx.Common; +using Ryujinx.Cpu.Signal; +using Ryujinx.Memory; +using Ryujinx.Memory.Tracking; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu +{ + public class MemoryEhMeilleure : IDisposable + { + public delegate ulong TrackingEventDelegate(ulong address, ulong size, bool write); + + private readonly MemoryTracking _tracking; + private readonly TrackingEventDelegate _trackingEvent; + + private readonly ulong _pageSize; + + private readonly ulong _baseAddress; + private readonly ulong _mirrorAddress; + + public MemoryEhMeilleure(MemoryBlock addressSpace, MemoryBlock addressSpaceMirror, MemoryTracking tracking, TrackingEventDelegate trackingEvent = null) + { + _baseAddress = (ulong)addressSpace.Pointer; + + ulong endAddress = _baseAddress + addressSpace.Size; + + _tracking = tracking; + _trackingEvent = trackingEvent ?? VirtualMemoryEvent; + + _pageSize = MemoryBlock.GetPageSize(); + + bool added = NativeSignalHandler.AddTrackedRegion((nuint)_baseAddress, (nuint)endAddress, Marshal.GetFunctionPointerForDelegate(_trackingEvent)); + + if (!added) + { + throw new InvalidOperationException("Number of allowed tracked regions exceeded."); + } + + if (OperatingSystem.IsWindows() && addressSpaceMirror != null) + { + // Add a tracking event with no signal handler for the mirror on Windows. + // The native handler has its own code to check for the partial overlap race when regions are protected by accident, + // and when there is no signal handler present. + + _mirrorAddress = (ulong)addressSpaceMirror.Pointer; + ulong endAddressMirror = _mirrorAddress + addressSpace.Size; + + bool addedMirror = NativeSignalHandler.AddTrackedRegion((nuint)_mirrorAddress, (nuint)endAddressMirror, IntPtr.Zero); + + if (!addedMirror) + { + throw new InvalidOperationException("Number of allowed tracked regions exceeded."); + } + } + } + + private ulong VirtualMemoryEvent(ulong address, ulong size, bool write) + { + ulong pageSize = _pageSize; + ulong addressAligned = BitUtils.AlignDown(address, pageSize); + ulong endAddressAligned = BitUtils.AlignUp(address + size, pageSize); + ulong sizeAligned = endAddressAligned - addressAligned; + + if (_tracking.VirtualMemoryEvent(addressAligned, sizeAligned, write)) + { + return _baseAddress + address; + } + + return 0; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + NativeSignalHandler.RemoveTrackedRegion((nuint)_baseAddress); + + if (_mirrorAddress != 0) + { + NativeSignalHandler.RemoveTrackedRegion((nuint)_mirrorAddress); + } + } + } +} diff --git a/src/Ryujinx.Cpu/MemoryHelper.cs b/src/Ryujinx.Cpu/MemoryHelper.cs new file mode 100644 index 00000000..e3d08ad5 --- /dev/null +++ b/src/Ryujinx.Cpu/MemoryHelper.cs @@ -0,0 +1,61 @@ +using Microsoft.IO; +using Ryujinx.Common.Memory; +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Cpu +{ + public static class MemoryHelper + { + public static void FillWithZeros(IVirtualMemoryManager memory, ulong position, int size) + { + int size8 = size & ~(8 - 1); + + for (int offs = 0; offs < size8; offs += 8) + { + memory.Write(position + (ulong)offs, 0); + } + + for (int offs = size8; offs < (size - size8); offs++) + { + memory.Write(position + (ulong)offs, 0); + } + } + + public static T Read(IVirtualMemoryManager memory, ulong position) where T : unmanaged + { + return MemoryMarshal.Cast(memory.GetSpan(position, Unsafe.SizeOf()))[0]; + } + + public static ulong Write(IVirtualMemoryManager memory, ulong position, T value) where T : unmanaged + { + ReadOnlySpan data = MemoryMarshal.Cast(MemoryMarshal.CreateReadOnlySpan(ref value, 1)); + + memory.Write(position, data); + + return (ulong)data.Length; + } + + public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1) + { + using RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(); + + for (long offs = 0; offs < maxSize || maxSize == -1; offs++) + { + byte value = memory.Read(position + (ulong)offs); + + if (value == 0) + { + break; + } + + ms.WriteByte(value); + } + + return Encoding.ASCII.GetString(ms.GetReadOnlySequence()); + } + } +} diff --git a/src/Ryujinx.Cpu/PrivateMemoryAllocation.cs b/src/Ryujinx.Cpu/PrivateMemoryAllocation.cs new file mode 100644 index 00000000..61b3827c --- /dev/null +++ b/src/Ryujinx.Cpu/PrivateMemoryAllocation.cs @@ -0,0 +1,41 @@ +using Ryujinx.Memory; +using System; + +namespace Ryujinx.Cpu +{ + readonly struct PrivateMemoryAllocation : IDisposable + { + private readonly PrivateMemoryAllocator _owner; + private readonly PrivateMemoryAllocator.Block _block; + + public bool IsValid => _owner != null; + public MemoryBlock Memory => _block?.Memory; + public ulong Offset { get; } + public ulong Size { get; } + + public PrivateMemoryAllocation( + PrivateMemoryAllocator owner, + PrivateMemoryAllocator.Block block, + ulong offset, + ulong size) + { + _owner = owner; + _block = block; + Offset = offset; + Size = size; + } + + public (PrivateMemoryAllocation, PrivateMemoryAllocation) Split(ulong splitOffset) + { + PrivateMemoryAllocation left = new(_owner, _block, Offset, splitOffset); + PrivateMemoryAllocation right = new(_owner, _block, Offset + splitOffset, Size - splitOffset); + + return (left, right); + } + + public void Dispose() + { + _owner.Free(_block, Offset, Size); + } + } +} diff --git a/src/Ryujinx.Cpu/PrivateMemoryAllocator.cs b/src/Ryujinx.Cpu/PrivateMemoryAllocator.cs new file mode 100644 index 00000000..8db74f1e --- /dev/null +++ b/src/Ryujinx.Cpu/PrivateMemoryAllocator.cs @@ -0,0 +1,268 @@ +using Ryujinx.Common; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Cpu +{ + class PrivateMemoryAllocator : PrivateMemoryAllocatorImpl + { + public const ulong InvalidOffset = ulong.MaxValue; + + public class Block : IComparable + { + public MemoryBlock Memory { get; private set; } + public ulong Size { get; } + + private readonly struct Range : IComparable + { + public ulong Offset { get; } + public ulong Size { get; } + + public Range(ulong offset, ulong size) + { + Offset = offset; + Size = size; + } + + public int CompareTo(Range other) + { + return Offset.CompareTo(other.Offset); + } + } + + private readonly List _freeRanges; + + public Block(MemoryBlock memory, ulong size) + { + Memory = memory; + Size = size; + _freeRanges = new List + { + new Range(0, size), + }; + } + + public ulong Allocate(ulong size, ulong alignment) + { + for (int i = 0; i < _freeRanges.Count; i++) + { + var range = _freeRanges[i]; + + ulong alignedOffset = BitUtils.AlignUp(range.Offset, alignment); + ulong sizeDelta = alignedOffset - range.Offset; + ulong usableSize = range.Size - sizeDelta; + + if (sizeDelta < range.Size && usableSize >= size) + { + _freeRanges.RemoveAt(i); + + if (sizeDelta != 0) + { + InsertFreeRange(range.Offset, sizeDelta); + } + + ulong endOffset = range.Offset + range.Size; + ulong remainingSize = endOffset - (alignedOffset + size); + if (remainingSize != 0) + { + InsertFreeRange(endOffset - remainingSize, remainingSize); + } + + return alignedOffset; + } + } + + return InvalidOffset; + } + + public void Free(ulong offset, ulong size) + { + InsertFreeRangeComingled(offset, size); + } + + private void InsertFreeRange(ulong offset, ulong size) + { + var range = new Range(offset, size); + int index = _freeRanges.BinarySearch(range); + if (index < 0) + { + index = ~index; + } + + _freeRanges.Insert(index, range); + } + + private void InsertFreeRangeComingled(ulong offset, ulong size) + { + ulong endOffset = offset + size; + var range = new Range(offset, size); + int index = _freeRanges.BinarySearch(range); + if (index < 0) + { + index = ~index; + } + + if (index < _freeRanges.Count && _freeRanges[index].Offset == endOffset) + { + endOffset = _freeRanges[index].Offset + _freeRanges[index].Size; + _freeRanges.RemoveAt(index); + } + + if (index > 0 && _freeRanges[index - 1].Offset + _freeRanges[index - 1].Size == offset) + { + offset = _freeRanges[index - 1].Offset; + _freeRanges.RemoveAt(--index); + } + + range = new Range(offset, endOffset - offset); + + _freeRanges.Insert(index, range); + } + + public bool IsTotallyFree() + { + if (_freeRanges.Count == 1 && _freeRanges[0].Size == Size) + { + Debug.Assert(_freeRanges[0].Offset == 0); + return true; + } + + return false; + } + + public int CompareTo(Block other) + { + return Size.CompareTo(other.Size); + } + + public virtual void Destroy() + { + Memory.Dispose(); + } + } + + public PrivateMemoryAllocator(ulong blockAlignment, MemoryAllocationFlags allocationFlags) : base(blockAlignment, allocationFlags) + { + } + + public PrivateMemoryAllocation Allocate(ulong size, ulong alignment) + { + var allocation = Allocate(size, alignment, CreateBlock); + + return new PrivateMemoryAllocation(this, allocation.Block, allocation.Offset, allocation.Size); + } + + private Block CreateBlock(MemoryBlock memory, ulong size) + { + return new Block(memory, size); + } + } + + class PrivateMemoryAllocatorImpl : IDisposable where T : PrivateMemoryAllocator.Block + { + private const ulong InvalidOffset = ulong.MaxValue; + + public readonly struct Allocation + { + public T Block { get; } + public ulong Offset { get; } + public ulong Size { get; } + + public Allocation(T block, ulong offset, ulong size) + { + Block = block; + Offset = offset; + Size = size; + } + } + + private readonly List _blocks; + + private readonly ulong _blockAlignment; + private readonly MemoryAllocationFlags _allocationFlags; + + public PrivateMemoryAllocatorImpl(ulong blockAlignment, MemoryAllocationFlags allocationFlags) + { + _blocks = new List(); + _blockAlignment = blockAlignment; + _allocationFlags = allocationFlags; + } + + protected Allocation Allocate(ulong size, ulong alignment, Func createBlock) + { + // Ensure we have a sane alignment value. + if ((ulong)(int)alignment != alignment || (int)alignment <= 0) + { + throw new ArgumentOutOfRangeException(nameof(alignment), $"Invalid alignment 0x{alignment:X}."); + } + + for (int i = 0; i < _blocks.Count; i++) + { + var block = _blocks[i]; + + if (block.Size >= size) + { + ulong offset = block.Allocate(size, alignment); + if (offset != InvalidOffset) + { + return new Allocation(block, offset, size); + } + } + } + + ulong blockAlignedSize = BitUtils.AlignUp(size, _blockAlignment); + + var memory = new MemoryBlock(blockAlignedSize, _allocationFlags); + var newBlock = createBlock(memory, blockAlignedSize); + + InsertBlock(newBlock); + + ulong newBlockOffset = newBlock.Allocate(size, alignment); + Debug.Assert(newBlockOffset != InvalidOffset); + + return new Allocation(newBlock, newBlockOffset, size); + } + + public void Free(PrivateMemoryAllocator.Block block, ulong offset, ulong size) + { + block.Free(offset, size); + + if (block.IsTotallyFree()) + { + for (int i = 0; i < _blocks.Count; i++) + { + if (_blocks[i] == block) + { + _blocks.RemoveAt(i); + break; + } + } + + block.Destroy(); + } + } + + private void InsertBlock(T block) + { + int index = _blocks.BinarySearch(block); + if (index < 0) + { + index = ~index; + } + + _blocks.Insert(index, block); + } + + public void Dispose() + { + for (int i = 0; i < _blocks.Count; i++) + { + _blocks[i].Destroy(); + } + + _blocks.Clear(); + } + } +} diff --git a/src/Ryujinx.Cpu/Ryujinx.Cpu.csproj b/src/Ryujinx.Cpu/Ryujinx.Cpu.csproj new file mode 100644 index 00000000..5a6bf5c3 --- /dev/null +++ b/src/Ryujinx.Cpu/Ryujinx.Cpu.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + true + + + + + + + + diff --git a/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs b/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs new file mode 100644 index 00000000..93e60832 --- /dev/null +++ b/src/Ryujinx.Cpu/Signal/NativeSignalHandler.cs @@ -0,0 +1,184 @@ +using ARMeilleure.Signal; +using Ryujinx.Common; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.Signal +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SignalHandlerRange + { + public int IsActive; + public nuint RangeAddress; + public nuint RangeEndAddress; + public IntPtr ActionPointer; + } + + [InlineArray(NativeSignalHandlerGenerator.MaxTrackedRanges)] + struct SignalHandlerRangeArray + { + public SignalHandlerRange Range0; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SignalHandlerConfig + { + /// + /// The byte offset of the faulting address in the SigInfo or ExceptionRecord struct. + /// + public int StructAddressOffset; + + /// + /// The byte offset of the write flag in the SigInfo or ExceptionRecord struct. + /// + public int StructWriteOffset; + + /// + /// The sigaction handler that was registered before this one. (unix only) + /// + public nuint UnixOldSigaction; + + /// + /// The type of the previous sigaction. True for the 3 argument variant. (unix only) + /// + public int UnixOldSigaction3Arg; + + /// + /// Fixed size array of tracked ranges. + /// + public SignalHandlerRangeArray Ranges; + } + + static class NativeSignalHandler + { + private static readonly IntPtr _handlerConfig; + private static IntPtr _signalHandlerPtr; + + private static MemoryBlock _codeBlock; + + private static readonly object _lock = new(); + private static bool _initialized; + + static NativeSignalHandler() + { + _handlerConfig = Marshal.AllocHGlobal(Unsafe.SizeOf()); + ref SignalHandlerConfig config = ref GetConfigRef(); + + config = new SignalHandlerConfig(); + } + + public static void InitializeSignalHandler(Func customSignalHandlerFactory = null) + { + if (_initialized) + { + return; + } + + lock (_lock) + { + if (_initialized) + { + return; + } + + int rangeStructSize = Unsafe.SizeOf(); + + ref SignalHandlerConfig config = ref GetConfigRef(); + + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + _signalHandlerPtr = MapCode(NativeSignalHandlerGenerator.GenerateUnixSignalHandler(_handlerConfig, rangeStructSize)); + + if (customSignalHandlerFactory != null) + { + _signalHandlerPtr = customSignalHandlerFactory(UnixSignalHandlerRegistration.GetSegfaultExceptionHandler().sa_handler, _signalHandlerPtr); + } + + var old = UnixSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr); + + config.UnixOldSigaction = (nuint)(ulong)old.sa_handler; + config.UnixOldSigaction3Arg = old.sa_flags & 4; + } + else + { + config.StructAddressOffset = 40; // ExceptionInformation1 + config.StructWriteOffset = 32; // ExceptionInformation0 + + _signalHandlerPtr = MapCode(NativeSignalHandlerGenerator.GenerateWindowsSignalHandler(_handlerConfig, rangeStructSize)); + + if (customSignalHandlerFactory != null) + { + _signalHandlerPtr = customSignalHandlerFactory(IntPtr.Zero, _signalHandlerPtr); + } + + WindowsSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr); + } + + _initialized = true; + } + } + + private static IntPtr MapCode(ReadOnlySpan code) + { + Debug.Assert(_codeBlock == null); + + ulong codeSizeAligned = BitUtils.AlignUp((ulong)code.Length, MemoryBlock.GetPageSize()); + + _codeBlock = new MemoryBlock(codeSizeAligned); + _codeBlock.Write(0, code); + _codeBlock.Reprotect(0, codeSizeAligned, MemoryPermission.ReadAndExecute); + + return _codeBlock.Pointer; + } + + private static unsafe ref SignalHandlerConfig GetConfigRef() + { + return ref Unsafe.AsRef((void*)_handlerConfig); + } + + public static bool AddTrackedRegion(nuint address, nuint endAddress, IntPtr action) + { + Span ranges = GetConfigRef().Ranges; + + for (int i = 0; i < NativeSignalHandlerGenerator.MaxTrackedRanges; i++) + { + if (ranges[i].IsActive == 0) + { + ranges[i].RangeAddress = address; + ranges[i].RangeEndAddress = endAddress; + ranges[i].ActionPointer = action; + ranges[i].IsActive = 1; + + return true; + } + } + + return false; + } + + public static bool RemoveTrackedRegion(nuint address) + { + Span ranges = GetConfigRef().Ranges; + + for (int i = 0; i < NativeSignalHandlerGenerator.MaxTrackedRanges; i++) + { + if (ranges[i].IsActive == 1 && ranges[i].RangeAddress == address) + { + ranges[i].IsActive = 0; + + return true; + } + } + + return false; + } + + public static bool SupportsFaultAddressPatching() + { + return NativeSignalHandlerGenerator.SupportsFaultAddressPatchingForHost(); + } + } +} diff --git a/src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs b/src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs new file mode 100644 index 00000000..e88a6c0f --- /dev/null +++ b/src/Ryujinx.Cpu/Signal/UnixSignalHandlerRegistration.cs @@ -0,0 +1,83 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.Signal +{ + static partial class UnixSignalHandlerRegistration + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct SigSet + { + fixed long sa_mask[16]; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SigAction + { + public IntPtr sa_handler; + public SigSet sa_mask; + public int sa_flags; + public IntPtr sa_restorer; + } + + private const int SIGSEGV = 11; + private const int SIGBUS = 10; + private const int SA_SIGINFO = 0x00000004; + + [LibraryImport("libc", SetLastError = true)] + private static partial int sigaction(int signum, ref SigAction sigAction, out SigAction oldAction); + + [LibraryImport("libc", SetLastError = true)] + private static partial int sigaction(int signum, IntPtr sigAction, out SigAction oldAction); + + [LibraryImport("libc", SetLastError = true)] + private static partial int sigemptyset(ref SigSet set); + + public static SigAction GetSegfaultExceptionHandler() + { + int result = sigaction(SIGSEGV, IntPtr.Zero, out SigAction old); + + if (result != 0) + { + throw new InvalidOperationException($"Could not get SIGSEGV sigaction. Error: {result}"); + } + + return old; + } + + public static SigAction RegisterExceptionHandler(IntPtr action) + { + SigAction sig = new() + { + sa_handler = action, + sa_flags = SA_SIGINFO, + }; + + sigemptyset(ref sig.sa_mask); + + int result = sigaction(SIGSEGV, ref sig, out SigAction old); + + if (result != 0) + { + throw new InvalidOperationException($"Could not register SIGSEGV sigaction. Error: {result}"); + } + + if (OperatingSystem.IsMacOS()) + { + result = sigaction(SIGBUS, ref sig, out _); + + if (result != 0) + { + throw new InvalidOperationException($"Could not register SIGBUS sigaction. Error: {result}"); + } + } + + return old; + } + + public static bool RestoreExceptionHandler(SigAction oldAction) + { + return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0 && (!OperatingSystem.IsMacOS() || sigaction(SIGBUS, ref oldAction, out SigAction _) == 0); + } + } +} diff --git a/src/Ryujinx.Cpu/Signal/WindowsSignalHandlerRegistration.cs b/src/Ryujinx.Cpu/Signal/WindowsSignalHandlerRegistration.cs new file mode 100644 index 00000000..1fbce0f7 --- /dev/null +++ b/src/Ryujinx.Cpu/Signal/WindowsSignalHandlerRegistration.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Cpu.Signal +{ + static partial class WindowsSignalHandlerRegistration + { + [LibraryImport("kernel32.dll")] + private static partial IntPtr AddVectoredExceptionHandler(uint first, IntPtr handler); + + [LibraryImport("kernel32.dll")] + private static partial ulong RemoveVectoredExceptionHandler(IntPtr handle); + + public static IntPtr RegisterExceptionHandler(IntPtr action) + { + return AddVectoredExceptionHandler(1, action); + } + + public static bool RemoveExceptionHandler(IntPtr handle) + { + return RemoveVectoredExceptionHandler(handle) != 0; + } + } +} diff --git a/src/Ryujinx.Cpu/TickSource.cs b/src/Ryujinx.Cpu/TickSource.cs new file mode 100644 index 00000000..eee83fc6 --- /dev/null +++ b/src/Ryujinx.Cpu/TickSource.cs @@ -0,0 +1,45 @@ +using System; +using System.Diagnostics; + +namespace Ryujinx.Cpu +{ + public class TickSource : ITickSource + { + private static Stopwatch _tickCounter; + + private static double _hostTickFreq; + + /// + public ulong Frequency { get; } + + /// + public ulong Counter => (ulong)(ElapsedSeconds * Frequency); + + /// + public TimeSpan ElapsedTime => _tickCounter.Elapsed; + + /// + public double ElapsedSeconds => _tickCounter.ElapsedTicks * _hostTickFreq; + + public TickSource(ulong frequency) + { + Frequency = frequency; + _hostTickFreq = 1.0 / Stopwatch.Frequency; + + _tickCounter = new Stopwatch(); + _tickCounter.Start(); + } + + /// + public void Suspend() + { + _tickCounter.Stop(); + } + + /// + public void Resume() + { + _tickCounter.Start(); + } + } +} diff --git a/src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs b/src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs new file mode 100644 index 00000000..3c7b3380 --- /dev/null +++ b/src/Ryujinx.Cpu/VirtualMemoryManagerRefCountedBase.cs @@ -0,0 +1,32 @@ +using Ryujinx.Memory; +using System.Diagnostics; +using System.Threading; + +namespace Ryujinx.Cpu +{ + public abstract class VirtualMemoryManagerRefCountedBase : VirtualMemoryManagerBase, IRefCounted + { + private int _referenceCount; + + public void IncrementReferenceCount() + { + int newRefCount = Interlocked.Increment(ref _referenceCount); + + Debug.Assert(newRefCount >= 1); + } + + public void DecrementReferenceCount() + { + int newRefCount = Interlocked.Decrement(ref _referenceCount); + + Debug.Assert(newRefCount >= 0); + + if (newRefCount == 0) + { + Destroy(); + } + } + + protected abstract void Destroy(); + } +} diff --git a/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs b/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs new file mode 100644 index 00000000..cb1a7c3a --- /dev/null +++ b/src/Ryujinx.Graphics.Device/DeviceMemoryManager.cs @@ -0,0 +1,396 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Device +{ + /// + /// Device memory manager. + /// + public class DeviceMemoryManager : IWritableBlock + { + private const int PtLvl0Bits = 10; + private const int PtLvl1Bits = 10; + public const int PtPageBits = 12; + + private const ulong PtLvl0Size = 1UL << PtLvl0Bits; + private const ulong PtLvl1Size = 1UL << PtLvl1Bits; + public const ulong PageSize = 1UL << PtPageBits; + + private const ulong PtLvl0Mask = PtLvl0Size - 1; + private const ulong PtLvl1Mask = PtLvl1Size - 1; + public const ulong PageMask = PageSize - 1; + + private const int PtLvl0Bit = PtPageBits + PtLvl1Bits; + private const int PtLvl1Bit = PtPageBits; + private const int AddressSpaceBits = PtPageBits + PtLvl1Bits + PtLvl0Bits; + + public const ulong PteUnmapped = ulong.MaxValue; + + private readonly ulong[][] _pageTable; + + private readonly IVirtualMemoryManager _physical; + + /// + /// Creates a new instance of the GPU memory manager. + /// + /// Physical memory that this memory manager will map into + public DeviceMemoryManager(IVirtualMemoryManager physicalMemory) + { + _physical = physicalMemory; + _pageTable = new ulong[PtLvl0Size][]; + } + + /// + /// Reads data from GPU mapped memory. + /// + /// Type of the data + /// GPU virtual address where the data is located + /// The data at the specified memory location + public T Read(ulong va) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (IsContiguous(va, size)) + { + return _physical.Read(Translate(va)); + } + else + { + Span data = new byte[size]; + + ReadImpl(va, data); + + return MemoryMarshal.Cast(data)[0]; + } + } + + /// + /// Gets a read-only span of data from GPU mapped memory. + /// + /// GPU virtual address where the data is located + /// Size of the data + /// The span of the data at the specified memory location + public ReadOnlySpan GetSpan(ulong va, int size) + { + if (IsContiguous(va, size)) + { + return _physical.GetSpan(Translate(va), size); + } + else + { + Span data = new byte[size]; + + ReadImpl(va, data); + + return data; + } + } + + /// + /// Reads data from a possibly non-contiguous region of GPU mapped memory. + /// + /// GPU virtual address of the data + /// Span to write the read data into + private void ReadImpl(ulong va, Span data) + { + if (data.Length == 0) + { + return; + } + + int offset = 0, size; + + if ((va & PageMask) != 0) + { + ulong pa = Translate(va); + + size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask)); + + if (pa != PteUnmapped && _physical.IsMapped(pa)) + { + _physical.GetSpan(pa, size).CopyTo(data[..size]); + } + + offset += size; + } + + for (; offset < data.Length; offset += size) + { + ulong pa = Translate(va + (ulong)offset); + + size = Math.Min(data.Length - offset, (int)PageSize); + + if (pa != PteUnmapped && _physical.IsMapped(pa)) + { + _physical.GetSpan(pa, size).CopyTo(data.Slice(offset, size)); + } + } + } + + /// + /// Gets a writable region from GPU mapped memory. + /// + /// Start address of the range + /// Size in bytes to be range + /// A writable region with the data at the specified memory location + public WritableRegion GetWritableRegion(ulong va, int size) + { + if (IsContiguous(va, size)) + { + return _physical.GetWritableRegion(Translate(va), size, tracked: true); + } + else + { + MemoryOwner memoryOwner = MemoryOwner.Rent(size); + + ReadImpl(va, memoryOwner.Span); + + return new WritableRegion(this, va, memoryOwner, tracked: true); + } + } + + /// + /// Writes data to GPU mapped memory. + /// + /// Type of the data + /// GPU virtual address to write the value into + /// The value to be written + public void Write(ulong va, T value) where T : unmanaged + { + Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); + } + + /// + /// Writes data to GPU mapped memory. + /// + /// GPU virtual address to write the data into + /// The data to be written + public void Write(ulong va, ReadOnlySpan data) + { + if (IsContiguous(va, data.Length)) + { + _physical.Write(Translate(va), data); + } + else + { + int offset = 0, size; + + if ((va & PageMask) != 0) + { + ulong pa = Translate(va); + + size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask)); + + if (pa != PteUnmapped && _physical.IsMapped(pa)) + { + _physical.Write(pa, data[..size]); + } + + offset += size; + } + + for (; offset < data.Length; offset += size) + { + ulong pa = Translate(va + (ulong)offset); + + size = Math.Min(data.Length - offset, (int)PageSize); + + if (pa != PteUnmapped && _physical.IsMapped(pa)) + { + _physical.Write(pa, data.Slice(offset, size)); + } + } + } + } + + /// + /// Writes data to GPU mapped memory without write tracking. + /// + /// GPU virtual address to write the data into + /// The data to be written + public void WriteUntracked(ulong va, ReadOnlySpan data) + { + throw new NotSupportedException(); + } + + /// + /// Maps a given range of pages to the specified CPU virtual address. + /// + /// + /// All addresses and sizes must be page aligned. + /// + /// CPU virtual address to map into + /// GPU virtual address to be mapped + /// Kind of the resource located at the mapping + public void Map(ulong pa, ulong va, ulong size) + { + lock (_pageTable) + { + for (ulong offset = 0; offset < size; offset += PageSize) + { + SetPte(va + offset, PackPte(pa + offset)); + } + } + } + + /// + /// Unmaps a given range of pages at the specified GPU virtual memory region. + /// + /// GPU virtual address to unmap + /// Size in bytes of the region being unmapped + public void Unmap(ulong va, ulong size) + { + lock (_pageTable) + { + for (ulong offset = 0; offset < size; offset += PageSize) + { + SetPte(va + offset, PteUnmapped); + } + } + } + + /// + /// Checks if a region of GPU mapped memory is contiguous. + /// + /// GPU virtual address of the region + /// Size of the region + /// True if the region is contiguous, false otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsContiguous(ulong va, int size) + { + if (!ValidateAddress(va) || GetPte(va) == PteUnmapped) + { + return false; + } + + ulong endVa = (va + (ulong)size + PageMask) & ~PageMask; + + va &= ~PageMask; + + int pages = (int)((endVa - va) / PageSize); + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize) || GetPte(va + PageSize) == PteUnmapped) + { + return false; + } + + if (Translate(va) + PageSize != Translate(va + PageSize)) + { + return false; + } + + va += PageSize; + } + + return true; + } + + /// + /// Validates a GPU virtual address. + /// + /// Address to validate + /// True if the address is valid, false otherwise + private static bool ValidateAddress(ulong va) + { + return va < (1UL << AddressSpaceBits); + } + + /// + /// Checks if a given page is mapped. + /// + /// GPU virtual address of the page to check + /// True if the page is mapped, false otherwise + public bool IsMapped(ulong va) + { + return Translate(va) != PteUnmapped; + } + + /// + /// Translates a GPU virtual address to a CPU virtual address. + /// + /// GPU virtual address to be translated + /// CPU virtual address, or if unmapped + public ulong Translate(ulong va) + { + if (!ValidateAddress(va)) + { + return PteUnmapped; + } + + ulong pte = GetPte(va); + + if (pte == PteUnmapped) + { + return PteUnmapped; + } + + return UnpackPaFromPte(pte) + (va & PageMask); + } + + /// + /// Gets the Page Table entry for a given GPU virtual address. + /// + /// GPU virtual address + /// Page table entry (CPU virtual address) + private ulong GetPte(ulong va) + { + ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask; + ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask; + + if (_pageTable[l0] == null) + { + return PteUnmapped; + } + + return _pageTable[l0][l1]; + } + + /// + /// Sets a Page Table entry at a given GPU virtual address. + /// + /// GPU virtual address + /// Page table entry (CPU virtual address) + private void SetPte(ulong va, ulong pte) + { + ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask; + ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask; + + if (_pageTable[l0] == null) + { + _pageTable[l0] = new ulong[PtLvl1Size]; + + for (ulong index = 0; index < PtLvl1Size; index++) + { + _pageTable[l0][index] = PteUnmapped; + } + } + + _pageTable[l0][l1] = pte; + } + + /// + /// Creates a page table entry from a physical address and kind. + /// + /// Physical address + /// Page table entry + private static ulong PackPte(ulong pa) + { + return pa; + } + + /// + /// Unpacks physical address from a page table entry. + /// + /// Page table entry + /// Physical address + private static ulong UnpackPaFromPte(ulong pte) + { + return pte; + } + } +} diff --git a/src/Ryujinx.Graphics.Device/DeviceState.cs b/src/Ryujinx.Graphics.Device/DeviceState.cs new file mode 100644 index 00000000..54178a41 --- /dev/null +++ b/src/Ryujinx.Graphics.Device/DeviceState.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Device +{ + public class DeviceState<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TState> : IDeviceState where TState : unmanaged + { + private const int RegisterSize = sizeof(int); + + public TState State; + + private static uint Size => (uint)(Unsafe.SizeOf() + RegisterSize - 1) / RegisterSize; + + private readonly Func[] _readCallbacks; + private readonly Action[] _writeCallbacks; + + private readonly Dictionary _fieldNamesForDebug; + private readonly Action _debugLogCallback; + + public DeviceState(IReadOnlyDictionary callbacks = null, Action debugLogCallback = null) + { + _readCallbacks = new Func[Size]; + _writeCallbacks = new Action[Size]; + + if (debugLogCallback != null) + { + _fieldNamesForDebug = new Dictionary(); + _debugLogCallback = debugLogCallback; + } + + var fields = typeof(TState).GetFields(); + int offset = 0; + + for (int fieldIndex = 0; fieldIndex < fields.Length; fieldIndex++) + { + var field = fields[fieldIndex]; + + var currentFieldOffset = (int)Marshal.OffsetOf(field.Name); + var nextFieldOffset = fieldIndex + 1 == fields.Length ? Unsafe.SizeOf() : (int)Marshal.OffsetOf(fields[fieldIndex + 1].Name); + + int sizeOfField = nextFieldOffset - currentFieldOffset; + + for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4) + { + int index = (offset + i) / RegisterSize; + + if (callbacks != null && callbacks.TryGetValue(field.Name, out var cb)) + { + if (cb.Read != null) + { + _readCallbacks[index] = cb.Read; + } + + if (cb.Write != null) + { + _writeCallbacks[index] = cb.Write; + } + } + } + + if (debugLogCallback != null) + { + _fieldNamesForDebug.Add((uint)offset, field.Name); + } + + offset += sizeOfField; + } + + Debug.Assert(offset == Unsafe.SizeOf()); + } + + public int Read(int offset) + { + uint index = (uint)offset / RegisterSize; + + if (index < Size) + { + uint alignedOffset = index * RegisterSize; + + var readCallback = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_readCallbacks), (IntPtr)index); + if (readCallback != null) + { + return readCallback(); + } + else + { + return GetRefUnchecked(alignedOffset); + } + } + + return 0; + } + + public void Write(int offset, int data) + { + uint index = (uint)offset / RegisterSize; + + if (index < Size) + { + uint alignedOffset = index * RegisterSize; + DebugWrite(alignedOffset, data); + + GetRefIntAlignedUncheck(index) = data; + + Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_writeCallbacks), (IntPtr)index)?.Invoke(data); + } + } + + public void WriteWithRedundancyCheck(int offset, int data, out bool changed) + { + uint index = (uint)offset / RegisterSize; + + if (index < Size) + { + uint alignedOffset = index * RegisterSize; + DebugWrite(alignedOffset, data); + + ref var storage = ref GetRefIntAlignedUncheck(index); + changed = storage != data; + storage = data; + + Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_writeCallbacks), (IntPtr)index)?.Invoke(data); + } + else + { + changed = false; + } + } + + [Conditional("DEBUG")] + private void DebugWrite(uint alignedOffset, int data) + { + if (_fieldNamesForDebug != null && _fieldNamesForDebug.TryGetValue(alignedOffset, out string fieldName)) + { + _debugLogCallback($"{typeof(TState).Name}.{fieldName} = 0x{data:X}"); + } + } + + public ref T GetRef(int offset) where T : unmanaged + { + if ((uint)(offset + Unsafe.SizeOf()) > Unsafe.SizeOf()) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + return ref GetRefUnchecked((uint)offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ref T GetRefUnchecked(uint offset) where T : unmanaged + { + return ref Unsafe.As(ref Unsafe.AddByteOffset(ref State, (IntPtr)offset)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ref int GetRefIntAlignedUncheck(ulong index) + { + return ref Unsafe.Add(ref Unsafe.As(ref State), (IntPtr)index); + } + } +} diff --git a/src/Ryujinx.Graphics.Device/IDeviceState.cs b/src/Ryujinx.Graphics.Device/IDeviceState.cs new file mode 100644 index 00000000..dcd32cf0 --- /dev/null +++ b/src/Ryujinx.Graphics.Device/IDeviceState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Device +{ + public interface IDeviceState + { + int Read(int offset); + void Write(int offset, int data); + } +} diff --git a/src/Ryujinx.Graphics.Device/IDeviceStateWithContext.cs b/src/Ryujinx.Graphics.Device/IDeviceStateWithContext.cs new file mode 100644 index 00000000..17b2fa21 --- /dev/null +++ b/src/Ryujinx.Graphics.Device/IDeviceStateWithContext.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Device +{ + public interface IDeviceStateWithContext : IDeviceState + { + long CreateContext(); + void DestroyContext(long id); + void BindContext(long id); + } +} diff --git a/src/Ryujinx.Graphics.Device/ISynchronizationManager.cs b/src/Ryujinx.Graphics.Device/ISynchronizationManager.cs new file mode 100644 index 00000000..2a8d1d9b --- /dev/null +++ b/src/Ryujinx.Graphics.Device/ISynchronizationManager.cs @@ -0,0 +1,39 @@ +using Ryujinx.Common.Logging; +using System; +using System.Threading; + +namespace Ryujinx.Graphics.Device +{ + /// + /// Synchronization manager interface. + /// + public interface ISynchronizationManager + { + /// + /// Increment the value of a syncpoint with a given id. + /// + /// The id of the syncpoint + /// Thrown when id >= MaxHardwareSyncpoints + /// The incremented value of the syncpoint + uint IncrementSyncpoint(uint id); + + /// + /// Get the value of a syncpoint with a given id. + /// + /// The id of the syncpoint + /// Thrown when id >= MaxHardwareSyncpoints + /// The value of the syncpoint + uint GetSyncpointValue(uint id); + + /// + /// Wait on a syncpoint with a given id at a target threshold. + /// The callback will be called once the threshold is reached and will automatically be unregistered. + /// + /// The id of the syncpoint + /// The target threshold + /// The timeout + /// Thrown when id >= MaxHardwareSyncpoints + /// True if timed out + bool WaitOnSyncpoint(uint id, uint threshold, TimeSpan timeout); + } +} diff --git a/src/Ryujinx.Graphics.Device/RwCallback.cs b/src/Ryujinx.Graphics.Device/RwCallback.cs new file mode 100644 index 00000000..46428cdd --- /dev/null +++ b/src/Ryujinx.Graphics.Device/RwCallback.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.Graphics.Device +{ + public readonly struct RwCallback + { + public Action Write { get; } + public Func Read { get; } + + public RwCallback(Action write, Func read) + { + Write = write; + Read = read; + } + } +} diff --git a/src/Ryujinx.Graphics.Device/Ryujinx.Graphics.Device.csproj b/src/Ryujinx.Graphics.Device/Ryujinx.Graphics.Device.csproj new file mode 100644 index 00000000..973a9e26 --- /dev/null +++ b/src/Ryujinx.Graphics.Device/Ryujinx.Graphics.Device.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + + + + + + + diff --git a/src/Ryujinx.Graphics.GAL/AddressMode.cs b/src/Ryujinx.Graphics.GAL/AddressMode.cs new file mode 100644 index 00000000..bf7d0393 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/AddressMode.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum AddressMode + { + Repeat, + MirroredRepeat, + ClampToEdge, + ClampToBorder, + Clamp, + MirrorClampToEdge, + MirrorClampToBorder, + MirrorClamp, + } +} diff --git a/src/Ryujinx.Graphics.GAL/AdvancedBlendDescriptor.cs b/src/Ryujinx.Graphics.GAL/AdvancedBlendDescriptor.cs new file mode 100644 index 00000000..e91d9caa --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/AdvancedBlendDescriptor.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct AdvancedBlendDescriptor + { + public AdvancedBlendOp Op { get; } + public AdvancedBlendOverlap Overlap { get; } + public bool SrcPreMultiplied { get; } + + public AdvancedBlendDescriptor(AdvancedBlendOp op, AdvancedBlendOverlap overlap, bool srcPreMultiplied) + { + Op = op; + Overlap = overlap; + SrcPreMultiplied = srcPreMultiplied; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/AdvancedBlendOp.cs b/src/Ryujinx.Graphics.GAL/AdvancedBlendOp.cs new file mode 100644 index 00000000..2c7654f0 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/AdvancedBlendOp.cs @@ -0,0 +1,52 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum AdvancedBlendOp + { + Zero, + Src, + Dst, + SrcOver, + DstOver, + SrcIn, + DstIn, + SrcOut, + DstOut, + SrcAtop, + DstAtop, + Xor, + Plus, + PlusClamped, + PlusClampedAlpha, + PlusDarker, + Multiply, + Screen, + Overlay, + Darken, + Lighten, + ColorDodge, + ColorBurn, + HardLight, + SoftLight, + Difference, + Minus, + MinusClamped, + Exclusion, + Contrast, + Invert, + InvertRGB, + InvertOvg, + LinearDodge, + LinearBurn, + VividLight, + LinearLight, + PinLight, + HardMix, + Red, + Green, + Blue, + HslHue, + HslSaturation, + HslColor, + HslLuminosity, + } +} diff --git a/src/Ryujinx.Graphics.GAL/AdvancedBlendOverlap.cs b/src/Ryujinx.Graphics.GAL/AdvancedBlendOverlap.cs new file mode 100644 index 00000000..abd95704 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/AdvancedBlendOverlap.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum AdvancedBlendOverlap + { + Uncorrelated, + Disjoint, + Conjoint, + } +} diff --git a/src/Ryujinx.Graphics.GAL/AntiAliasing.cs b/src/Ryujinx.Graphics.GAL/AntiAliasing.cs new file mode 100644 index 00000000..04b52976 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/AntiAliasing.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum AntiAliasing + { + None, + Fxaa, + SmaaLow, + SmaaMedium, + SmaaHigh, + SmaaUltra, + } +} diff --git a/src/Ryujinx.Graphics.GAL/BlendDescriptor.cs b/src/Ryujinx.Graphics.GAL/BlendDescriptor.cs new file mode 100644 index 00000000..d9cf3e49 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/BlendDescriptor.cs @@ -0,0 +1,35 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct BlendDescriptor + { + public bool Enable { get; } + + public ColorF BlendConstant { get; } + public BlendOp ColorOp { get; } + public BlendFactor ColorSrcFactor { get; } + public BlendFactor ColorDstFactor { get; } + public BlendOp AlphaOp { get; } + public BlendFactor AlphaSrcFactor { get; } + public BlendFactor AlphaDstFactor { get; } + + public BlendDescriptor( + bool enable, + ColorF blendConstant, + BlendOp colorOp, + BlendFactor colorSrcFactor, + BlendFactor colorDstFactor, + BlendOp alphaOp, + BlendFactor alphaSrcFactor, + BlendFactor alphaDstFactor) + { + Enable = enable; + BlendConstant = blendConstant; + ColorOp = colorOp; + ColorSrcFactor = colorSrcFactor; + ColorDstFactor = colorDstFactor; + AlphaOp = alphaOp; + AlphaSrcFactor = alphaSrcFactor; + AlphaDstFactor = alphaDstFactor; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/BlendFactor.cs b/src/Ryujinx.Graphics.GAL/BlendFactor.cs new file mode 100644 index 00000000..1dba229d --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/BlendFactor.cs @@ -0,0 +1,62 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum BlendFactor + { + Zero = 1, + One, + SrcColor, + OneMinusSrcColor, + SrcAlpha, + OneMinusSrcAlpha, + DstAlpha, + OneMinusDstAlpha, + DstColor, + OneMinusDstColor, + SrcAlphaSaturate, + Src1Color = 0x10, + OneMinusSrc1Color, + Src1Alpha, + OneMinusSrc1Alpha, + ConstantColor = 0xc001, + OneMinusConstantColor, + ConstantAlpha, + OneMinusConstantAlpha, + + ZeroGl = 0x4000, + OneGl = 0x4001, + SrcColorGl = 0x4300, + OneMinusSrcColorGl = 0x4301, + SrcAlphaGl = 0x4302, + OneMinusSrcAlphaGl = 0x4303, + DstAlphaGl = 0x4304, + OneMinusDstAlphaGl = 0x4305, + DstColorGl = 0x4306, + OneMinusDstColorGl = 0x4307, + SrcAlphaSaturateGl = 0x4308, + Src1ColorGl = 0xc900, + OneMinusSrc1ColorGl = 0xc901, + Src1AlphaGl = 0xc902, + OneMinusSrc1AlphaGl = 0xc903, + } + + public static class BlendFactorExtensions + { + public static bool IsDualSource(this BlendFactor factor) + { + switch (factor) + { + case BlendFactor.Src1Color: + case BlendFactor.Src1ColorGl: + case BlendFactor.Src1Alpha: + case BlendFactor.Src1AlphaGl: + case BlendFactor.OneMinusSrc1Color: + case BlendFactor.OneMinusSrc1ColorGl: + case BlendFactor.OneMinusSrc1Alpha: + case BlendFactor.OneMinusSrc1AlphaGl: + return true; + default: + return false; + } + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/BlendOp.cs b/src/Ryujinx.Graphics.GAL/BlendOp.cs new file mode 100644 index 00000000..c3b67073 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/BlendOp.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum BlendOp + { + Add = 1, + Subtract, + ReverseSubtract, + Minimum, + Maximum, + + AddGl = 0x8006, + MinimumGl = 0x8007, + MaximumGl = 0x8008, + SubtractGl = 0x800a, + ReverseSubtractGl = 0x800b, + } +} diff --git a/src/Ryujinx.Graphics.GAL/BufferAccess.cs b/src/Ryujinx.Graphics.GAL/BufferAccess.cs new file mode 100644 index 00000000..1e7736f8 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/BufferAccess.cs @@ -0,0 +1,18 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + [Flags] + public enum BufferAccess + { + Default = 0, + HostMemory = 1, + DeviceMemory = 2, + DeviceMemoryMapped = 3, + + MemoryTypeMask = 0xf, + + Stream = 1 << 4, + SparseCompatible = 1 << 5, + } +} diff --git a/src/Ryujinx.Graphics.GAL/BufferAssignment.cs b/src/Ryujinx.Graphics.GAL/BufferAssignment.cs new file mode 100644 index 00000000..e302936e --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/BufferAssignment.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct BufferAssignment + { + public readonly int Binding; + public readonly BufferRange Range; + + public BufferAssignment(int binding, BufferRange range) + { + Binding = binding; + Range = range; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/BufferHandle.cs b/src/Ryujinx.Graphics.GAL/BufferHandle.cs new file mode 100644 index 00000000..7994e4ee --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/BufferHandle.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.GAL +{ + [StructLayout(LayoutKind.Sequential, Size = 8)] + public readonly record struct BufferHandle + { + private readonly ulong _value; + + public static BufferHandle Null => new(0); + + private BufferHandle(ulong value) => _value = value; + } +} diff --git a/src/Ryujinx.Graphics.GAL/BufferRange.cs b/src/Ryujinx.Graphics.GAL/BufferRange.cs new file mode 100644 index 00000000..fec82de2 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/BufferRange.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct BufferRange + { + private static readonly BufferRange _empty = new(BufferHandle.Null, 0, 0); + + public static BufferRange Empty => _empty; + + public BufferHandle Handle { get; } + + public int Offset { get; } + public int Size { get; } + public bool Write { get; } + + public BufferRange(BufferHandle handle, int offset, int size, bool write = false) + { + Handle = handle; + Offset = offset; + Size = size; + Write = write; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs new file mode 100644 index 00000000..a5c6eb5c --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs @@ -0,0 +1,198 @@ +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.GAL +{ + public readonly struct Capabilities + { + public readonly TargetApi Api; + public readonly string VendorName; + public readonly SystemMemoryType MemoryType; + + public readonly bool HasFrontFacingBug; + public readonly bool HasVectorIndexingBug; + public readonly bool NeedsFragmentOutputSpecialization; + public readonly bool ReduceShaderPrecision; + + public readonly bool SupportsAstcCompression; + public readonly bool SupportsBc123Compression; + public readonly bool SupportsBc45Compression; + public readonly bool SupportsBc67Compression; + public readonly bool SupportsEtc2Compression; + public readonly bool Supports3DTextureCompression; + public readonly bool SupportsBgraFormat; + public readonly bool SupportsR4G4Format; + public readonly bool SupportsR4G4B4A4Format; + public readonly bool SupportsScaledVertexFormats; + public readonly bool SupportsSnormBufferTextureFormat; + public readonly bool SupportsSparseBuffer; + public readonly bool Supports5BitComponentFormat; + public readonly bool SupportsBlendEquationAdvanced; + public readonly bool SupportsFragmentShaderInterlock; + public readonly bool SupportsFragmentShaderOrderingIntel; + public readonly bool SupportsGeometryShader; + public readonly bool SupportsGeometryShaderPassthrough; + public readonly bool SupportsTransformFeedback; + public readonly bool SupportsImageLoadFormatted; + public readonly bool SupportsLayerVertexTessellation; + public readonly bool SupportsMismatchingViewFormat; + public readonly bool SupportsCubemapView; + public readonly bool SupportsNonConstantTextureOffset; + public readonly bool SupportsQuads; + public readonly bool SupportsSeparateSampler; + public readonly bool SupportsShaderBallot; + public readonly bool SupportsShaderBarrierDivergence; + public readonly bool SupportsShaderFloat64; + public readonly bool SupportsTextureGatherOffsets; + public readonly bool SupportsTextureShadowLod; + public readonly bool SupportsVertexStoreAndAtomics; + public readonly bool SupportsViewportIndexVertexTessellation; + public readonly bool SupportsViewportMask; + public readonly bool SupportsViewportSwizzle; + public readonly bool SupportsIndirectParameters; + public readonly bool SupportsDepthClipControl; + + public readonly int UniformBufferSetIndex; + public readonly int StorageBufferSetIndex; + public readonly int TextureSetIndex; + public readonly int ImageSetIndex; + public readonly int ExtraSetBaseIndex; + public readonly int MaximumExtraSets; + + public readonly uint MaximumUniformBuffersPerStage; + public readonly uint MaximumStorageBuffersPerStage; + public readonly uint MaximumTexturesPerStage; + public readonly uint MaximumImagesPerStage; + + public readonly int MaximumComputeSharedMemorySize; + public readonly float MaximumSupportedAnisotropy; + public readonly int ShaderSubgroupSize; + public readonly int StorageBufferOffsetAlignment; + public readonly int TextureBufferOffsetAlignment; + + public readonly int GatherBiasPrecision; + + public Capabilities( + TargetApi api, + string vendorName, + SystemMemoryType memoryType, + bool hasFrontFacingBug, + bool hasVectorIndexingBug, + bool needsFragmentOutputSpecialization, + bool reduceShaderPrecision, + bool supportsAstcCompression, + bool supportsBc123Compression, + bool supportsBc45Compression, + bool supportsBc67Compression, + bool supportsEtc2Compression, + bool supports3DTextureCompression, + bool supportsBgraFormat, + bool supportsR4G4Format, + bool supportsR4G4B4A4Format, + bool supportsScaledVertexFormats, + bool supportsSnormBufferTextureFormat, + bool supports5BitComponentFormat, + bool supportsSparseBuffer, + bool supportsBlendEquationAdvanced, + bool supportsFragmentShaderInterlock, + bool supportsFragmentShaderOrderingIntel, + bool supportsGeometryShader, + bool supportsGeometryShaderPassthrough, + bool supportsTransformFeedback, + bool supportsImageLoadFormatted, + bool supportsLayerVertexTessellation, + bool supportsMismatchingViewFormat, + bool supportsCubemapView, + bool supportsNonConstantTextureOffset, + bool supportsQuads, + bool supportsSeparateSampler, + bool supportsShaderBallot, + bool supportsShaderBarrierDivergence, + bool supportsShaderFloat64, + bool supportsTextureGatherOffsets, + bool supportsTextureShadowLod, + bool supportsVertexStoreAndAtomics, + bool supportsViewportIndexVertexTessellation, + bool supportsViewportMask, + bool supportsViewportSwizzle, + bool supportsIndirectParameters, + bool supportsDepthClipControl, + int uniformBufferSetIndex, + int storageBufferSetIndex, + int textureSetIndex, + int imageSetIndex, + int extraSetBaseIndex, + int maximumExtraSets, + uint maximumUniformBuffersPerStage, + uint maximumStorageBuffersPerStage, + uint maximumTexturesPerStage, + uint maximumImagesPerStage, + int maximumComputeSharedMemorySize, + float maximumSupportedAnisotropy, + int shaderSubgroupSize, + int storageBufferOffsetAlignment, + int textureBufferOffsetAlignment, + int gatherBiasPrecision) + { + Api = api; + VendorName = vendorName; + MemoryType = memoryType; + HasFrontFacingBug = hasFrontFacingBug; + HasVectorIndexingBug = hasVectorIndexingBug; + NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization; + ReduceShaderPrecision = reduceShaderPrecision; + SupportsAstcCompression = supportsAstcCompression; + SupportsBc123Compression = supportsBc123Compression; + SupportsBc45Compression = supportsBc45Compression; + SupportsBc67Compression = supportsBc67Compression; + SupportsEtc2Compression = supportsEtc2Compression; + Supports3DTextureCompression = supports3DTextureCompression; + SupportsBgraFormat = supportsBgraFormat; + SupportsR4G4Format = supportsR4G4Format; + SupportsR4G4B4A4Format = supportsR4G4B4A4Format; + SupportsScaledVertexFormats = supportsScaledVertexFormats; + SupportsSnormBufferTextureFormat = supportsSnormBufferTextureFormat; + Supports5BitComponentFormat = supports5BitComponentFormat; + SupportsSparseBuffer = supportsSparseBuffer; + SupportsBlendEquationAdvanced = supportsBlendEquationAdvanced; + SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock; + SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel; + SupportsGeometryShader = supportsGeometryShader; + SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough; + SupportsTransformFeedback = supportsTransformFeedback; + SupportsImageLoadFormatted = supportsImageLoadFormatted; + SupportsLayerVertexTessellation = supportsLayerVertexTessellation; + SupportsMismatchingViewFormat = supportsMismatchingViewFormat; + SupportsCubemapView = supportsCubemapView; + SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset; + SupportsQuads = supportsQuads; + SupportsSeparateSampler = supportsSeparateSampler; + SupportsShaderBallot = supportsShaderBallot; + SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence; + SupportsShaderFloat64 = supportsShaderFloat64; + SupportsTextureGatherOffsets = supportsTextureGatherOffsets; + SupportsTextureShadowLod = supportsTextureShadowLod; + SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics; + SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation; + SupportsViewportMask = supportsViewportMask; + SupportsViewportSwizzle = supportsViewportSwizzle; + SupportsIndirectParameters = supportsIndirectParameters; + SupportsDepthClipControl = supportsDepthClipControl; + UniformBufferSetIndex = uniformBufferSetIndex; + StorageBufferSetIndex = storageBufferSetIndex; + TextureSetIndex = textureSetIndex; + ImageSetIndex = imageSetIndex; + ExtraSetBaseIndex = extraSetBaseIndex; + MaximumExtraSets = maximumExtraSets; + MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage; + MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage; + MaximumTexturesPerStage = maximumTexturesPerStage; + MaximumImagesPerStage = maximumImagesPerStage; + MaximumComputeSharedMemorySize = maximumComputeSharedMemorySize; + MaximumSupportedAnisotropy = maximumSupportedAnisotropy; + ShaderSubgroupSize = shaderSubgroupSize; + StorageBufferOffsetAlignment = storageBufferOffsetAlignment; + TextureBufferOffsetAlignment = textureBufferOffsetAlignment; + GatherBiasPrecision = gatherBiasPrecision; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/ColorF.cs b/src/Ryujinx.Graphics.GAL/ColorF.cs new file mode 100644 index 00000000..235f4229 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ColorF.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly record struct ColorF(float Red, float Green, float Blue, float Alpha); +} diff --git a/src/Ryujinx.Graphics.GAL/CompareMode.cs b/src/Ryujinx.Graphics.GAL/CompareMode.cs new file mode 100644 index 00000000..42ab14d6 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/CompareMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum CompareMode + { + None, + CompareRToTexture, + } +} diff --git a/src/Ryujinx.Graphics.GAL/CompareOp.cs b/src/Ryujinx.Graphics.GAL/CompareOp.cs new file mode 100644 index 00000000..8141ecca --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/CompareOp.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum CompareOp + { + Never = 1, + Less, + Equal, + LessOrEqual, + Greater, + NotEqual, + GreaterOrEqual, + Always, + + NeverGl = 0x200, + LessGl = 0x201, + EqualGl = 0x202, + LessOrEqualGl = 0x203, + GreaterGl = 0x204, + NotEqualGl = 0x205, + GreaterOrEqualGl = 0x206, + AlwaysGl = 0x207, + } +} diff --git a/src/Ryujinx.Graphics.GAL/CounterType.cs b/src/Ryujinx.Graphics.GAL/CounterType.cs new file mode 100644 index 00000000..58a4254b --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/CounterType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum CounterType + { + SamplesPassed, + PrimitivesGenerated, + TransformFeedbackPrimitivesWritten, + } +} diff --git a/src/Ryujinx.Graphics.GAL/DepthMode.cs b/src/Ryujinx.Graphics.GAL/DepthMode.cs new file mode 100644 index 00000000..52a3d9cf --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/DepthMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum DepthMode + { + MinusOneToOne, + ZeroToOne, + } +} diff --git a/src/Ryujinx.Graphics.GAL/DepthStencilMode.cs b/src/Ryujinx.Graphics.GAL/DepthStencilMode.cs new file mode 100644 index 00000000..1031355f --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/DepthStencilMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum DepthStencilMode + { + Depth, + Stencil, + } +} diff --git a/src/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs b/src/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs new file mode 100644 index 00000000..d0edb603 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct DepthTestDescriptor + { + public bool TestEnable { get; } + public bool WriteEnable { get; } + + public CompareOp Func { get; } + + public DepthTestDescriptor( + bool testEnable, + bool writeEnable, + CompareOp func) + { + TestEnable = testEnable; + WriteEnable = writeEnable; + Func = func; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/DeviceInfo.cs b/src/Ryujinx.Graphics.GAL/DeviceInfo.cs new file mode 100644 index 00000000..04258460 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/DeviceInfo.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct DeviceInfo + { + public readonly string Id; + public readonly string Vendor; + public readonly string Name; + public readonly bool IsDiscrete; + + public DeviceInfo(string id, string vendor, string name, bool isDiscrete) + { + Id = id; + Vendor = vendor; + Name = name; + IsDiscrete = isDiscrete; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Extents2D.cs b/src/Ryujinx.Graphics.GAL/Extents2D.cs new file mode 100644 index 00000000..96fbc0f7 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Extents2D.cs @@ -0,0 +1,31 @@ +using Ryujinx.Common; + +namespace Ryujinx.Graphics.GAL +{ + public readonly struct Extents2D + { + public int X1 { get; } + public int Y1 { get; } + public int X2 { get; } + public int Y2 { get; } + + public Extents2D(int x1, int y1, int x2, int y2) + { + X1 = x1; + Y1 = y1; + X2 = x2; + Y2 = y2; + } + + public Extents2D Reduce(int level) + { + int div = 1 << level; + + return new Extents2D( + X1 >> level, + Y1 >> level, + BitUtils.DivRoundUp(X2, div), + BitUtils.DivRoundUp(Y2, div)); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Extents2DF.cs b/src/Ryujinx.Graphics.GAL/Extents2DF.cs new file mode 100644 index 00000000..116e998e --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Extents2DF.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct Extents2DF + { + public float X1 { get; } + public float Y1 { get; } + public float X2 { get; } + public float Y2 { get; } + + public Extents2DF(float x1, float y1, float x2, float y2) + { + X1 = x1; + Y1 = y1; + X2 = x2; + Y2 = y2; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Face.cs b/src/Ryujinx.Graphics.GAL/Face.cs new file mode 100644 index 00000000..73e43cf2 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Face.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum Face + { + Front = 0x404, + Back = 0x405, + FrontAndBack = 0x408, + } +} diff --git a/src/Ryujinx.Graphics.GAL/Format.cs b/src/Ryujinx.Graphics.GAL/Format.cs new file mode 100644 index 00000000..17c42d2d --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Format.cs @@ -0,0 +1,746 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum Format + { + R8Unorm, + R8Snorm, + R8Uint, + R8Sint, + R16Float, + R16Unorm, + R16Snorm, + R16Uint, + R16Sint, + R32Float, + R32Uint, + R32Sint, + R8G8Unorm, + R8G8Snorm, + R8G8Uint, + R8G8Sint, + R16G16Float, + R16G16Unorm, + R16G16Snorm, + R16G16Uint, + R16G16Sint, + R32G32Float, + R32G32Uint, + R32G32Sint, + R8G8B8Unorm, + R8G8B8Snorm, + R8G8B8Uint, + R8G8B8Sint, + R16G16B16Float, + R16G16B16Unorm, + R16G16B16Snorm, + R16G16B16Uint, + R16G16B16Sint, + R32G32B32Float, + R32G32B32Uint, + R32G32B32Sint, + R8G8B8A8Unorm, + R8G8B8A8Snorm, + R8G8B8A8Uint, + R8G8B8A8Sint, + R16G16B16A16Float, + R16G16B16A16Unorm, + R16G16B16A16Snorm, + R16G16B16A16Uint, + R16G16B16A16Sint, + R32G32B32A32Float, + R32G32B32A32Uint, + R32G32B32A32Sint, + S8Uint, + D16Unorm, + S8UintD24Unorm, + D32Float, + D24UnormS8Uint, + D32FloatS8Uint, + R8G8B8A8Srgb, + R4G4Unorm, + R4G4B4A4Unorm, + R5G5B5X1Unorm, + R5G5B5A1Unorm, + R5G6B5Unorm, + R10G10B10A2Unorm, + R10G10B10A2Uint, + R11G11B10Float, + R9G9B9E5Float, + Bc1RgbaUnorm, + Bc2Unorm, + Bc3Unorm, + Bc1RgbaSrgb, + Bc2Srgb, + Bc3Srgb, + Bc4Unorm, + Bc4Snorm, + Bc5Unorm, + Bc5Snorm, + Bc7Unorm, + Bc7Srgb, + Bc6HSfloat, + Bc6HUfloat, + Etc2RgbUnorm, + Etc2RgbaUnorm, + Etc2RgbPtaUnorm, + Etc2RgbSrgb, + Etc2RgbaSrgb, + Etc2RgbPtaSrgb, + R8Uscaled, + R8Sscaled, + R16Uscaled, + R16Sscaled, + R32Uscaled, + R32Sscaled, + R8G8Uscaled, + R8G8Sscaled, + R16G16Uscaled, + R16G16Sscaled, + R32G32Uscaled, + R32G32Sscaled, + R8G8B8Uscaled, + R8G8B8Sscaled, + R16G16B16Uscaled, + R16G16B16Sscaled, + R32G32B32Uscaled, + R32G32B32Sscaled, + R8G8B8A8Uscaled, + R8G8B8A8Sscaled, + R16G16B16A16Uscaled, + R16G16B16A16Sscaled, + R32G32B32A32Uscaled, + R32G32B32A32Sscaled, + R10G10B10A2Snorm, + R10G10B10A2Sint, + R10G10B10A2Uscaled, + R10G10B10A2Sscaled, + Astc4x4Unorm, + Astc5x4Unorm, + Astc5x5Unorm, + Astc6x5Unorm, + Astc6x6Unorm, + Astc8x5Unorm, + Astc8x6Unorm, + Astc8x8Unorm, + Astc10x5Unorm, + Astc10x6Unorm, + Astc10x8Unorm, + Astc10x10Unorm, + Astc12x10Unorm, + Astc12x12Unorm, + Astc4x4Srgb, + Astc5x4Srgb, + Astc5x5Srgb, + Astc6x5Srgb, + Astc6x6Srgb, + Astc8x5Srgb, + Astc8x6Srgb, + Astc8x8Srgb, + Astc10x5Srgb, + Astc10x6Srgb, + Astc10x8Srgb, + Astc10x10Srgb, + Astc12x10Srgb, + Astc12x12Srgb, + B5G6R5Unorm, + B5G5R5A1Unorm, + A1B5G5R5Unorm, + B8G8R8A8Unorm, + B8G8R8A8Srgb, + B10G10R10A2Unorm, + X8UintD24Unorm, + } + + public static class FormatExtensions + { + /// + /// The largest scalar size for a buffer format. + /// + public const int MaxBufferFormatScalarSize = 4; + + /// + /// Gets the byte size for a single component of this format, or its packed size. + /// + /// Texture format + /// Byte size for a single component, or packed size + public static int GetScalarSize(this Format format) + { + switch (format) + { + case Format.R8Unorm: + case Format.R8Snorm: + case Format.R8Uint: + case Format.R8Sint: + case Format.R8G8Unorm: + case Format.R8G8Snorm: + case Format.R8G8Uint: + case Format.R8G8Sint: + case Format.R8G8B8Unorm: + case Format.R8G8B8Snorm: + case Format.R8G8B8Uint: + case Format.R8G8B8Sint: + case Format.R8G8B8A8Unorm: + case Format.R8G8B8A8Snorm: + case Format.R8G8B8A8Uint: + case Format.R8G8B8A8Sint: + case Format.R8G8B8A8Srgb: + case Format.R4G4Unorm: + case Format.R8Uscaled: + case Format.R8Sscaled: + case Format.R8G8Uscaled: + case Format.R8G8Sscaled: + case Format.R8G8B8Uscaled: + case Format.R8G8B8Sscaled: + case Format.R8G8B8A8Uscaled: + case Format.R8G8B8A8Sscaled: + case Format.B8G8R8A8Unorm: + case Format.B8G8R8A8Srgb: + return 1; + + case Format.R16Float: + case Format.R16Unorm: + case Format.R16Snorm: + case Format.R16Uint: + case Format.R16Sint: + case Format.R16G16Float: + case Format.R16G16Unorm: + case Format.R16G16Snorm: + case Format.R16G16Uint: + case Format.R16G16Sint: + case Format.R16G16B16Float: + case Format.R16G16B16Unorm: + case Format.R16G16B16Snorm: + case Format.R16G16B16Uint: + case Format.R16G16B16Sint: + case Format.R16G16B16A16Float: + case Format.R16G16B16A16Unorm: + case Format.R16G16B16A16Snorm: + case Format.R16G16B16A16Uint: + case Format.R16G16B16A16Sint: + case Format.R4G4B4A4Unorm: + case Format.R5G5B5X1Unorm: + case Format.R5G5B5A1Unorm: + case Format.R5G6B5Unorm: + case Format.R16Uscaled: + case Format.R16Sscaled: + case Format.R16G16Uscaled: + case Format.R16G16Sscaled: + case Format.R16G16B16Uscaled: + case Format.R16G16B16Sscaled: + case Format.R16G16B16A16Uscaled: + case Format.R16G16B16A16Sscaled: + case Format.B5G6R5Unorm: + case Format.B5G5R5A1Unorm: + case Format.A1B5G5R5Unorm: + return 2; + + case Format.R32Float: + case Format.R32Uint: + case Format.R32Sint: + case Format.R32G32Float: + case Format.R32G32Uint: + case Format.R32G32Sint: + case Format.R32G32B32Float: + case Format.R32G32B32Uint: + case Format.R32G32B32Sint: + case Format.R32G32B32A32Float: + case Format.R32G32B32A32Uint: + case Format.R32G32B32A32Sint: + case Format.R10G10B10A2Unorm: + case Format.R10G10B10A2Uint: + case Format.R11G11B10Float: + case Format.R9G9B9E5Float: + case Format.R32Uscaled: + case Format.R32Sscaled: + case Format.R32G32Uscaled: + case Format.R32G32Sscaled: + case Format.R32G32B32Uscaled: + case Format.R32G32B32Sscaled: + case Format.R32G32B32A32Uscaled: + case Format.R32G32B32A32Sscaled: + case Format.R10G10B10A2Snorm: + case Format.R10G10B10A2Sint: + case Format.R10G10B10A2Uscaled: + case Format.R10G10B10A2Sscaled: + case Format.B10G10R10A2Unorm: + return 4; + + case Format.S8Uint: + return 1; + case Format.D16Unorm: + return 2; + case Format.S8UintD24Unorm: + case Format.X8UintD24Unorm: + case Format.D32Float: + case Format.D24UnormS8Uint: + return 4; + case Format.D32FloatS8Uint: + return 8; + + case Format.Bc1RgbaUnorm: + case Format.Bc1RgbaSrgb: + return 8; + + case Format.Bc2Unorm: + case Format.Bc3Unorm: + case Format.Bc2Srgb: + case Format.Bc3Srgb: + case Format.Bc4Unorm: + case Format.Bc4Snorm: + case Format.Bc5Unorm: + case Format.Bc5Snorm: + case Format.Bc7Unorm: + case Format.Bc7Srgb: + case Format.Bc6HSfloat: + case Format.Bc6HUfloat: + return 16; + + case Format.Etc2RgbUnorm: + case Format.Etc2RgbPtaUnorm: + case Format.Etc2RgbSrgb: + case Format.Etc2RgbPtaSrgb: + return 8; + + case Format.Etc2RgbaUnorm: + case Format.Etc2RgbaSrgb: + return 16; + + case Format.Astc4x4Unorm: + case Format.Astc5x4Unorm: + case Format.Astc5x5Unorm: + case Format.Astc6x5Unorm: + case Format.Astc6x6Unorm: + case Format.Astc8x5Unorm: + case Format.Astc8x6Unorm: + case Format.Astc8x8Unorm: + case Format.Astc10x5Unorm: + case Format.Astc10x6Unorm: + case Format.Astc10x8Unorm: + case Format.Astc10x10Unorm: + case Format.Astc12x10Unorm: + case Format.Astc12x12Unorm: + case Format.Astc4x4Srgb: + case Format.Astc5x4Srgb: + case Format.Astc5x5Srgb: + case Format.Astc6x5Srgb: + case Format.Astc6x6Srgb: + case Format.Astc8x5Srgb: + case Format.Astc8x6Srgb: + case Format.Astc8x8Srgb: + case Format.Astc10x5Srgb: + case Format.Astc10x6Srgb: + case Format.Astc10x8Srgb: + case Format.Astc10x10Srgb: + case Format.Astc12x10Srgb: + case Format.Astc12x12Srgb: + return 16; + } + + return 1; + } + + /// + /// Checks if the texture format is a depth or depth-stencil format. + /// + /// Texture format + /// True if the format is a depth or depth-stencil format, false otherwise + public static bool HasDepth(this Format format) + { + switch (format) + { + case Format.D16Unorm: + case Format.D24UnormS8Uint: + case Format.S8UintD24Unorm: + case Format.X8UintD24Unorm: + case Format.D32Float: + case Format.D32FloatS8Uint: + return true; + } + + return false; + } + + /// + /// Checks if the texture format is a stencil or depth-stencil format. + /// + /// Texture format + /// True if the format is a stencil or depth-stencil format, false otherwise + public static bool HasStencil(this Format format) + { + switch (format) + { + case Format.D24UnormS8Uint: + case Format.S8UintD24Unorm: + case Format.D32FloatS8Uint: + case Format.S8Uint: + return true; + } + + return false; + } + + /// + /// Checks if the texture format is valid to use as image format. + /// + /// Texture format + /// True if the texture can be used as image, false otherwise + public static bool IsImageCompatible(this Format format) + { + switch (format) + { + case Format.R8Unorm: + case Format.R8Snorm: + case Format.R8Uint: + case Format.R8Sint: + case Format.R16Float: + case Format.R16Unorm: + case Format.R16Snorm: + case Format.R16Uint: + case Format.R16Sint: + case Format.R32Float: + case Format.R32Uint: + case Format.R32Sint: + case Format.R8G8Unorm: + case Format.R8G8Snorm: + case Format.R8G8Uint: + case Format.R8G8Sint: + case Format.R16G16Float: + case Format.R16G16Unorm: + case Format.R16G16Snorm: + case Format.R16G16Uint: + case Format.R16G16Sint: + case Format.R32G32Float: + case Format.R32G32Uint: + case Format.R32G32Sint: + case Format.R8G8B8A8Unorm: + case Format.R8G8B8A8Snorm: + case Format.R8G8B8A8Uint: + case Format.R8G8B8A8Sint: + case Format.R16G16B16A16Float: + case Format.R16G16B16A16Unorm: + case Format.R16G16B16A16Snorm: + case Format.R16G16B16A16Uint: + case Format.R16G16B16A16Sint: + case Format.R32G32B32A32Float: + case Format.R32G32B32A32Uint: + case Format.R32G32B32A32Sint: + case Format.R10G10B10A2Unorm: + case Format.R10G10B10A2Uint: + case Format.R11G11B10Float: + case Format.B8G8R8A8Unorm: + return true; + } + + return false; + } + + /// + /// Checks if the texture format is valid to use as render target color format. + /// + /// Texture format + /// True if the texture can be used as render target, false otherwise + public static bool IsRtColorCompatible(this Format format) + { + switch (format) + { + case Format.R32G32B32A32Float: + case Format.R32G32B32A32Sint: + case Format.R32G32B32A32Uint: + case Format.R16G16B16A16Unorm: + case Format.R16G16B16A16Snorm: + case Format.R16G16B16A16Sint: + case Format.R16G16B16A16Uint: + case Format.R16G16B16A16Float: + case Format.R32G32Float: + case Format.R32G32Sint: + case Format.R32G32Uint: + case Format.B8G8R8A8Unorm: + case Format.B8G8R8A8Srgb: + case Format.B10G10R10A2Unorm: + case Format.R10G10B10A2Unorm: + case Format.R10G10B10A2Uint: + case Format.R8G8B8A8Unorm: + case Format.R8G8B8A8Srgb: + case Format.R8G8B8A8Snorm: + case Format.R8G8B8A8Sint: + case Format.R8G8B8A8Uint: + case Format.R16G16Unorm: + case Format.R16G16Snorm: + case Format.R16G16Sint: + case Format.R16G16Uint: + case Format.R16G16Float: + case Format.R11G11B10Float: + case Format.R32Sint: + case Format.R32Uint: + case Format.R32Float: + case Format.B5G6R5Unorm: + case Format.B5G5R5A1Unorm: + case Format.R8G8Unorm: + case Format.R8G8Snorm: + case Format.R8G8Sint: + case Format.R8G8Uint: + case Format.R16Unorm: + case Format.R16Snorm: + case Format.R16Sint: + case Format.R16Uint: + case Format.R16Float: + case Format.R8Unorm: + case Format.R8Snorm: + case Format.R8Sint: + case Format.R8Uint: + return true; + } + + return false; + } + + /// + /// Checks if the texture format is 16 bit packed. + /// + /// Texture format + /// True if the texture format is 16 bit packed, false otherwise + public static bool Is16BitPacked(this Format format) + { + switch (format) + { + case Format.B5G6R5Unorm: + case Format.B5G5R5A1Unorm: + case Format.R5G5B5X1Unorm: + case Format.R5G5B5A1Unorm: + case Format.R5G6B5Unorm: + case Format.R4G4B4A4Unorm: + return true; + } + + return false; + } + + /// + /// Checks if the texture format is an ASTC format. + /// + /// Texture format + /// True if the texture format is an ASTC format, false otherwise + public static bool IsAstc(this Format format) + { + return format.IsAstcUnorm() || format.IsAstcSrgb(); + } + + /// + /// Checks if the texture format is an ASTC Unorm format. + /// + /// Texture format + /// True if the texture format is an ASTC Unorm format, false otherwise + public static bool IsAstcUnorm(this Format format) + { + switch (format) + { + case Format.Astc4x4Unorm: + case Format.Astc5x4Unorm: + case Format.Astc5x5Unorm: + case Format.Astc6x5Unorm: + case Format.Astc6x6Unorm: + case Format.Astc8x5Unorm: + case Format.Astc8x6Unorm: + case Format.Astc8x8Unorm: + case Format.Astc10x5Unorm: + case Format.Astc10x6Unorm: + case Format.Astc10x8Unorm: + case Format.Astc10x10Unorm: + case Format.Astc12x10Unorm: + case Format.Astc12x12Unorm: + return true; + } + + return false; + } + + /// + /// Checks if the texture format is an ASTC SRGB format. + /// + /// Texture format + /// True if the texture format is an ASTC SRGB format, false otherwise + public static bool IsAstcSrgb(this Format format) + { + switch (format) + { + case Format.Astc4x4Srgb: + case Format.Astc5x4Srgb: + case Format.Astc5x5Srgb: + case Format.Astc6x5Srgb: + case Format.Astc6x6Srgb: + case Format.Astc8x5Srgb: + case Format.Astc8x6Srgb: + case Format.Astc8x8Srgb: + case Format.Astc10x5Srgb: + case Format.Astc10x6Srgb: + case Format.Astc10x8Srgb: + case Format.Astc10x10Srgb: + case Format.Astc12x10Srgb: + case Format.Astc12x12Srgb: + return true; + } + + return false; + } + + /// + /// Checks if the texture format is an ETC2 format. + /// + /// Texture format + /// True if the texture format is an ETC2 format, false otherwise + public static bool IsEtc2(this Format format) + { + switch (format) + { + case Format.Etc2RgbaSrgb: + case Format.Etc2RgbaUnorm: + case Format.Etc2RgbPtaSrgb: + case Format.Etc2RgbPtaUnorm: + case Format.Etc2RgbSrgb: + case Format.Etc2RgbUnorm: + return true; + } + + return false; + } + + /// + /// Checks if the texture format is a BGR format. + /// + /// Texture format + /// True if the texture format is a BGR format, false otherwise + public static bool IsBgr(this Format format) + { + switch (format) + { + case Format.B5G6R5Unorm: + case Format.B5G5R5A1Unorm: + case Format.B8G8R8A8Unorm: + case Format.B8G8R8A8Srgb: + case Format.B10G10R10A2Unorm: + return true; + } + + return false; + } + + /// + /// Checks if the texture format is a depth, stencil or depth-stencil format. + /// + /// Texture format + /// True if the format is a depth, stencil or depth-stencil format, false otherwise + public static bool IsDepthOrStencil(this Format format) + { + switch (format) + { + case Format.D16Unorm: + case Format.D24UnormS8Uint: + case Format.S8UintD24Unorm: + case Format.X8UintD24Unorm: + case Format.D32Float: + case Format.D32FloatS8Uint: + case Format.S8Uint: + return true; + } + + return false; + } + + /// + /// Checks if the texture format is an unsigned integer color format. + /// + /// Texture format + /// True if the texture format is an unsigned integer color format, false otherwise + public static bool IsUint(this Format format) + { + switch (format) + { + case Format.R8Uint: + case Format.R16Uint: + case Format.R32Uint: + case Format.R8G8Uint: + case Format.R16G16Uint: + case Format.R32G32Uint: + case Format.R8G8B8Uint: + case Format.R16G16B16Uint: + case Format.R32G32B32Uint: + case Format.R8G8B8A8Uint: + case Format.R16G16B16A16Uint: + case Format.R32G32B32A32Uint: + case Format.R10G10B10A2Uint: + return true; + } + + return false; + } + + /// + /// Checks if the texture format is a signed integer color format. + /// + /// Texture format + /// True if the texture format is a signed integer color format, false otherwise + public static bool IsSint(this Format format) + { + switch (format) + { + case Format.R8Sint: + case Format.R16Sint: + case Format.R32Sint: + case Format.R8G8Sint: + case Format.R16G16Sint: + case Format.R32G32Sint: + case Format.R8G8B8Sint: + case Format.R16G16B16Sint: + case Format.R32G32B32Sint: + case Format.R8G8B8A8Sint: + case Format.R16G16B16A16Sint: + case Format.R32G32B32A32Sint: + case Format.R10G10B10A2Sint: + return true; + } + + return false; + } + + /// + /// Checks if the texture format is an integer color format. + /// + /// Texture format + /// True if the texture format is an integer color format, false otherwise + public static bool IsInteger(this Format format) + { + return format.IsUint() || format.IsSint(); + } + + /// + /// Checks if the texture format is a float or sRGB color format. + /// + /// + /// Does not include normalized, compressed or depth formats. + /// Float and sRGB formats do not participate in logical operations. + /// + /// Texture format + /// True if the format is a float or sRGB color format, false otherwise + public static bool IsFloatOrSrgb(this Format format) + { + switch (format) + { + case Format.R8G8B8A8Srgb: + case Format.B8G8R8A8Srgb: + case Format.R16Float: + case Format.R16G16Float: + case Format.R16G16B16Float: + case Format.R16G16B16A16Float: + case Format.R32Float: + case Format.R32G32Float: + case Format.R32G32B32Float: + case Format.R32G32B32A32Float: + case Format.R11G11B10Float: + case Format.R9G9B9E5Float: + return true; + } + + return false; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/FrontFace.cs b/src/Ryujinx.Graphics.GAL/FrontFace.cs new file mode 100644 index 00000000..5dade276 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/FrontFace.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum FrontFace + { + Clockwise = 0x900, + CounterClockwise = 0x901, + } +} diff --git a/src/Ryujinx.Graphics.GAL/HardwareInfo.cs b/src/Ryujinx.Graphics.GAL/HardwareInfo.cs new file mode 100644 index 00000000..c2546fa4 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/HardwareInfo.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct HardwareInfo + { + public string GpuVendor { get; } + public string GpuModel { get; } + public string GpuDriver { get; } + + public HardwareInfo(string gpuVendor, string gpuModel, string gpuDriver) + { + GpuVendor = gpuVendor; + GpuModel = gpuModel; + GpuDriver = gpuDriver; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/ICounterEvent.cs b/src/Ryujinx.Graphics.GAL/ICounterEvent.cs new file mode 100644 index 00000000..a410506b --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ICounterEvent.cs @@ -0,0 +1,13 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + public interface ICounterEvent : IDisposable + { + bool Invalid { get; set; } + + bool ReserveForHostAccess(); + + void Flush(); + } +} diff --git a/src/Ryujinx.Graphics.GAL/IImageArray.cs b/src/Ryujinx.Graphics.GAL/IImageArray.cs new file mode 100644 index 00000000..d119aa9f --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/IImageArray.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + public interface IImageArray : IDisposable + { + void SetFormats(int index, Format[] imageFormats); + void SetImages(int index, ITexture[] images); + } +} diff --git a/src/Ryujinx.Graphics.GAL/IPipeline.cs b/src/Ryujinx.Graphics.GAL/IPipeline.cs new file mode 100644 index 00000000..cbf1bc3a --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/IPipeline.cs @@ -0,0 +1,114 @@ +using Ryujinx.Graphics.Shader; +using System; + +namespace Ryujinx.Graphics.GAL +{ + public interface IPipeline + { + void Barrier(); + + void BeginTransformFeedback(PrimitiveTopology topology); + + void ClearBuffer(BufferHandle destination, int offset, int size, uint value); + + void ClearRenderTargetColor(int index, int layer, int layerCount, uint componentMask, ColorF color); + + void ClearRenderTargetDepthStencil( + int layer, + int layerCount, + float depthValue, + bool depthMask, + int stencilValue, + int stencilMask); + + void CommandBufferBarrier(); + + void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size); + + void DispatchCompute(int groupsX, int groupsY, int groupsZ); + + void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance); + void DrawIndexed( + int indexCount, + int instanceCount, + int firstIndex, + int firstVertex, + int firstInstance); + void DrawIndexedIndirect(BufferRange indirectBuffer); + void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride); + void DrawIndirect(BufferRange indirectBuffer); + void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride); + void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion); + + void EndTransformFeedback(); + + void SetAlphaTest(bool enable, float reference, CompareOp op); + + void SetBlendState(AdvancedBlendDescriptor blend); + void SetBlendState(int index, BlendDescriptor blend); + + void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp); + void SetDepthClamp(bool clamp); + void SetDepthMode(DepthMode mode); + void SetDepthTest(DepthTestDescriptor depthTest); + + void SetFaceCulling(bool enable, Face face); + + void SetFrontFace(FrontFace frontFace); + + void SetIndexBuffer(BufferRange buffer, IndexType type); + + void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat); + void SetImageArray(ShaderStage stage, int binding, IImageArray array); + void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array); + + void SetLineParameters(float width, bool smooth); + + void SetLogicOpState(bool enable, LogicalOp op); + + void SetMultisampleState(MultisampleDescriptor multisample); + + void SetPatchParameters(int vertices, ReadOnlySpan defaultOuterLevel, ReadOnlySpan defaultInnerLevel); + void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin); + + void SetPolygonMode(PolygonMode frontMode, PolygonMode backMode); + + void SetPrimitiveRestart(bool enable, int index); + + void SetPrimitiveTopology(PrimitiveTopology topology); + + void SetProgram(IProgram program); + + void SetRasterizerDiscard(bool discard); + + void SetRenderTargetColorMasks(ReadOnlySpan componentMask); + void SetRenderTargets(ITexture[] colors, ITexture depthStencil); + + void SetScissors(ReadOnlySpan> regions); + + void SetStencilTest(StencilTestDescriptor stencilTest); + + void SetStorageBuffers(ReadOnlySpan buffers); + + void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler); + void SetTextureArray(ShaderStage stage, int binding, ITextureArray array); + void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array); + + void SetTransformFeedbackBuffers(ReadOnlySpan buffers); + void SetUniformBuffers(ReadOnlySpan buffers); + + void SetUserClipDistance(int index, bool enableClip); + + void SetVertexAttribs(ReadOnlySpan vertexAttribs); + void SetVertexBuffers(ReadOnlySpan vertexBuffers); + + void SetViewports(ReadOnlySpan viewports); + + void TextureBarrier(); + void TextureBarrierTiled(); + + bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual); + bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual); + void EndHostConditionalRendering(); + } +} diff --git a/src/Ryujinx.Graphics.GAL/IProgram.cs b/src/Ryujinx.Graphics.GAL/IProgram.cs new file mode 100644 index 00000000..272a2f7d --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/IProgram.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + public interface IProgram : IDisposable + { + ProgramLinkStatus CheckProgramLink(bool blocking); + + byte[] GetBinary(); + } +} diff --git a/src/Ryujinx.Graphics.GAL/IRenderer.cs b/src/Ryujinx.Graphics.GAL/IRenderer.cs new file mode 100644 index 00000000..85d0bd72 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/IRenderer.cs @@ -0,0 +1,68 @@ +using Ryujinx.Common.Configuration; +using System; +using System.Threading; + +namespace Ryujinx.Graphics.GAL +{ + public interface IRenderer : IDisposable + { + event EventHandler ScreenCaptured; + + bool PreferThreading { get; } + + IPipeline Pipeline { get; } + + IWindow Window { get; } + + void BackgroundContextAction(Action action, bool alwaysBackground = false); + + BufferHandle CreateBuffer(int size, BufferAccess access = BufferAccess.Default); + BufferHandle CreateBuffer(nint pointer, int size); + BufferHandle CreateBufferSparse(ReadOnlySpan storageBuffers); + + IImageArray CreateImageArray(int size, bool isBuffer); + + IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info); + + ISampler CreateSampler(SamplerCreateInfo info); + ITexture CreateTexture(TextureCreateInfo info); + ITextureArray CreateTextureArray(int size, bool isBuffer); + + bool PrepareHostMapping(nint address, ulong size); + + void CreateSync(ulong id, bool strict); + + void DeleteBuffer(BufferHandle buffer); + + PinnedSpan GetBufferData(BufferHandle buffer, int offset, int size); + + Capabilities GetCapabilities(); + ulong GetCurrentSync(); + HardwareInfo GetHardwareInfo(); + + IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info); + + void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan data); + + void UpdateCounters(); + + void PreFrame(); + + ICounterEvent ReportCounter(CounterType type, EventHandler resultHandler, float divisor, bool hostReserved); + + void ResetCounter(CounterType type); + + void RunLoop(ThreadStart gpuLoop) + { + gpuLoop(); + } + + void WaitSync(ulong id); + + void Initialize(GraphicsDebugLevel logLevel); + + void SetInterruptAction(Action interruptAction); + + void Screenshot(); + } +} diff --git a/src/Ryujinx.Graphics.GAL/ISampler.cs b/src/Ryujinx.Graphics.GAL/ISampler.cs new file mode 100644 index 00000000..de70f663 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ISampler.cs @@ -0,0 +1,6 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + public interface ISampler : IDisposable { } +} diff --git a/src/Ryujinx.Graphics.GAL/ITexture.cs b/src/Ryujinx.Graphics.GAL/ITexture.cs new file mode 100644 index 00000000..2d9c6b79 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ITexture.cs @@ -0,0 +1,50 @@ +using System.Buffers; + +namespace Ryujinx.Graphics.GAL +{ + public interface ITexture + { + int Width { get; } + int Height { get; } + + void CopyTo(ITexture destination, int firstLayer, int firstLevel); + void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel); + void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter); + void CopyTo(BufferRange range, int layer, int level, int stride); + + ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel); + + PinnedSpan GetData(); + PinnedSpan GetData(int layer, int level); + + /// + /// Sets the texture data. The data passed as a will be disposed when + /// the operation completes. + /// + /// Texture data bytes + void SetData(IMemoryOwner data); + + /// + /// Sets the texture data. The data passed as a will be disposed when + /// the operation completes. + /// + /// Texture data bytes + /// Target layer + /// Target level + void SetData(IMemoryOwner data, int layer, int level); + + /// + /// Sets the texture data. The data passed as a will be disposed when + /// the operation completes. + /// + /// Texture data bytes + /// Target layer + /// Target level + /// Target sub-region of the texture to update + void SetData(IMemoryOwner data, int layer, int level, Rectangle region); + + void SetStorage(BufferRange buffer); + + void Release(); + } +} diff --git a/src/Ryujinx.Graphics.GAL/ITextureArray.cs b/src/Ryujinx.Graphics.GAL/ITextureArray.cs new file mode 100644 index 00000000..9ee79dac --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ITextureArray.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + public interface ITextureArray : IDisposable + { + void SetSamplers(int index, ISampler[] samplers); + void SetTextures(int index, ITexture[] textures); + } +} diff --git a/src/Ryujinx.Graphics.GAL/IWindow.cs b/src/Ryujinx.Graphics.GAL/IWindow.cs new file mode 100644 index 00000000..83418e70 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/IWindow.cs @@ -0,0 +1,18 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + public interface IWindow + { + void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback); + + void SetSize(int width, int height); + + void ChangeVSyncMode(bool vsyncEnabled); + + void SetAntiAliasing(AntiAliasing antialiasing); + void SetScalingFilter(ScalingFilter type); + void SetScalingFilterLevel(float level); + void SetColorSpacePassthrough(bool colorSpacePassThroughEnabled); + } +} diff --git a/src/Ryujinx.Graphics.GAL/ImageCrop.cs b/src/Ryujinx.Graphics.GAL/ImageCrop.cs new file mode 100644 index 00000000..511b74f6 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ImageCrop.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct ImageCrop + { + public int Left { get; } + public int Right { get; } + public int Top { get; } + public int Bottom { get; } + public bool FlipX { get; } + public bool FlipY { get; } + public bool IsStretched { get; } + public float AspectRatioX { get; } + public float AspectRatioY { get; } + + public ImageCrop( + int left, + int right, + int top, + int bottom, + bool flipX, + bool flipY, + bool isStretched, + float aspectRatioX, + float aspectRatioY) + { + Left = left; + Right = right; + Top = top; + Bottom = bottom; + FlipX = flipX; + FlipY = flipY; + IsStretched = isStretched; + AspectRatioX = aspectRatioX; + AspectRatioY = aspectRatioY; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/IndexType.cs b/src/Ryujinx.Graphics.GAL/IndexType.cs new file mode 100644 index 00000000..8bbefc83 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/IndexType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum IndexType + { + UByte, + UShort, + UInt, + } +} diff --git a/src/Ryujinx.Graphics.GAL/LogicalOp.cs b/src/Ryujinx.Graphics.GAL/LogicalOp.cs new file mode 100644 index 00000000..6c6a6189 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/LogicalOp.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum LogicalOp + { + Clear = 0x1500, + And = 0x1501, + AndReverse = 0x1502, + Copy = 0x1503, + AndInverted = 0x1504, + Noop = 0x1505, + Xor = 0x1506, + Or = 0x1507, + Nor = 0x1508, + Equiv = 0x1509, + Invert = 0x150A, + OrReverse = 0x150B, + CopyInverted = 0x150C, + OrInverted = 0x150D, + Nand = 0x150E, + Set = 0x150F, + } +} diff --git a/src/Ryujinx.Graphics.GAL/MagFilter.cs b/src/Ryujinx.Graphics.GAL/MagFilter.cs new file mode 100644 index 00000000..d5cd6c7c --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/MagFilter.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum MagFilter + { + Nearest = 1, + Linear, + } +} diff --git a/src/Ryujinx.Graphics.GAL/MinFilter.cs b/src/Ryujinx.Graphics.GAL/MinFilter.cs new file mode 100644 index 00000000..f089947b --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/MinFilter.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum MinFilter + { + Nearest = 1, + Linear, + NearestMipmapNearest, + LinearMipmapNearest, + NearestMipmapLinear, + LinearMipmapLinear, + } +} diff --git a/src/Ryujinx.Graphics.GAL/MultisampleDescriptor.cs b/src/Ryujinx.Graphics.GAL/MultisampleDescriptor.cs new file mode 100644 index 00000000..a6fb65aa --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/MultisampleDescriptor.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct MultisampleDescriptor + { + public bool AlphaToCoverageEnable { get; } + public bool AlphaToCoverageDitherEnable { get; } + public bool AlphaToOneEnable { get; } + + public MultisampleDescriptor( + bool alphaToCoverageEnable, + bool alphaToCoverageDitherEnable, + bool alphaToOneEnable) + { + AlphaToCoverageEnable = alphaToCoverageEnable; + AlphaToCoverageDitherEnable = alphaToCoverageDitherEnable; + AlphaToOneEnable = alphaToOneEnable; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/BufferMap.cs b/src/Ryujinx.Graphics.GAL/Multithreading/BufferMap.cs new file mode 100644 index 00000000..e8eec123 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/BufferMap.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.Graphics.GAL.Multithreading +{ + /// + /// Buffer handles given to the client are not the same as those provided by the backend, + /// as their handle is created at a later point on the queue. + /// The handle returned is a unique identifier that will map to the real buffer when it is available. + /// Note that any uses within the queue should be safe, but outside you must use MapBufferBlocking. + /// + class BufferMap + { + private ulong _bufferHandle = 0; + + private readonly Dictionary _bufferMap = new(); + private readonly HashSet _inFlight = new(); + private readonly AutoResetEvent _inFlightChanged = new(false); + + internal BufferHandle CreateBufferHandle() + { + ulong handle64 = Interlocked.Increment(ref _bufferHandle); + + BufferHandle threadedHandle = Unsafe.As(ref handle64); + + lock (_inFlight) + { + _inFlight.Add(threadedHandle); + } + + return threadedHandle; + } + + internal void AssignBuffer(BufferHandle threadedHandle, BufferHandle realHandle) + { + lock (_bufferMap) + { + _bufferMap[threadedHandle] = realHandle; + } + + lock (_inFlight) + { + _inFlight.Remove(threadedHandle); + } + + _inFlightChanged.Set(); + } + + internal void UnassignBuffer(BufferHandle threadedHandle) + { + lock (_bufferMap) + { + _bufferMap.Remove(threadedHandle); + } + } + + internal BufferHandle MapBuffer(BufferHandle handle) + { + // Maps a threaded buffer to a backend one. + // Threaded buffers are returned on creation as the buffer + // isn't actually created until the queue runs the command. + + lock (_bufferMap) + { + if (!_bufferMap.TryGetValue(handle, out BufferHandle result)) + { + result = BufferHandle.Null; + } + + return result; + } + } + + internal BufferHandle MapBufferBlocking(BufferHandle handle) + { + // Blocks until the handle is available. + + + lock (_bufferMap) + { + if (_bufferMap.TryGetValue(handle, out BufferHandle result)) + { + return result; + } + } + + bool signal = false; + + while (true) + { + lock (_inFlight) + { + if (!_inFlight.Contains(handle)) + { + break; + } + } + + _inFlightChanged.WaitOne(); + signal = true; + } + + if (signal) + { + // Signal other threads which might still be waiting. + _inFlightChanged.Set(); + } + + return MapBuffer(handle); + } + + internal BufferRange MapBufferRange(BufferRange range) + { + return new BufferRange(MapBuffer(range.Handle), range.Offset, range.Size, range.Write); + } + + internal Span MapBufferRanges(Span ranges) + { + // Rewrite the buffer ranges to point to the mapped handles. + + lock (_bufferMap) + { + for (int i = 0; i < ranges.Length; i++) + { + ref BufferRange range = ref ranges[i]; + + if (!_bufferMap.TryGetValue(range.Handle, out BufferHandle result)) + { + result = BufferHandle.Null; + } + + range = new BufferRange(result, range.Offset, range.Size, range.Write); + } + } + + return ranges; + } + + internal Span MapBufferRanges(Span ranges) + { + // Rewrite the buffer ranges to point to the mapped handles. + + lock (_bufferMap) + { + for (int i = 0; i < ranges.Length; i++) + { + ref BufferAssignment assignment = ref ranges[i]; + BufferRange range = assignment.Range; + + if (!_bufferMap.TryGetValue(range.Handle, out BufferHandle result)) + { + result = BufferHandle.Null; + } + + assignment = new BufferAssignment(ranges[i].Binding, new BufferRange(result, range.Offset, range.Size, range.Write)); + } + } + + return ranges; + } + + internal Span MapBufferRanges(Span ranges) + { + // Rewrite the buffer ranges to point to the mapped handles. + + lock (_bufferMap) + { + for (int i = 0; i < ranges.Length; i++) + { + BufferRange range = ranges[i].Buffer; + + if (!_bufferMap.TryGetValue(range.Handle, out BufferHandle result)) + { + result = BufferHandle.Null; + } + + range = new BufferRange(result, range.Offset, range.Size, range.Write); + + ranges[i] = new VertexBufferDescriptor(range, ranges[i].Stride, ranges[i].Divisor); + } + } + + return ranges; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs new file mode 100644 index 00000000..ef227d4a --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs @@ -0,0 +1,166 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer; +using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent; +using Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Program; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; +using Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Window; +using System; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.GAL.Multithreading +{ + static class CommandHelper + { + private delegate void CommandDelegate(Span memory, ThreadedRenderer threaded, IRenderer renderer); + + private static readonly int _totalCommands = (int)Enum.GetValues().Max() + 1; + private static readonly CommandDelegate[] _lookup = new CommandDelegate[_totalCommands]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ref T GetCommand(Span memory) + { + return ref Unsafe.As(ref MemoryMarshal.GetReference(memory)); + } + + public static int GetMaxCommandSize() + { + return InitLookup() + 1; // 1 byte reserved for command size. + } + + private static int InitLookup() + { + int maxCommandSize = 0; + + void Register(CommandType commandType) where T : unmanaged, IGALCommand, IGALCommand + { + maxCommandSize = Math.Max(maxCommandSize, Unsafe.SizeOf()); + _lookup[(int)commandType] = (memory, threaded, renderer) => T.Run(ref GetCommand(memory), threaded, renderer); + } + + Register(CommandType.Action); + Register(CommandType.CreateBufferAccess); + Register(CommandType.CreateBufferSparse); + Register(CommandType.CreateHostBuffer); + Register(CommandType.CreateImageArray); + Register(CommandType.CreateProgram); + Register(CommandType.CreateSampler); + Register(CommandType.CreateSync); + Register(CommandType.CreateTexture); + Register(CommandType.CreateTextureArray); + Register(CommandType.GetCapabilities); + Register(CommandType.PreFrame); + Register(CommandType.ReportCounter); + Register(CommandType.ResetCounter); + Register(CommandType.UpdateCounters); + + Register(CommandType.BufferDispose); + Register(CommandType.BufferGetData); + Register(CommandType.BufferSetData); + + Register(CommandType.CounterEventDispose); + Register(CommandType.CounterEventFlush); + + Register(CommandType.ImageArrayDispose); + Register(CommandType.ImageArraySetFormats); + Register(CommandType.ImageArraySetImages); + + Register(CommandType.ProgramDispose); + Register(CommandType.ProgramGetBinary); + Register(CommandType.ProgramCheckLink); + + Register(CommandType.SamplerDispose); + + Register(CommandType.TextureCopyTo); + Register(CommandType.TextureCopyToScaled); + Register(CommandType.TextureCopyToSlice); + Register(CommandType.TextureCopyToBuffer); + Register(CommandType.TextureCreateView); + Register(CommandType.TextureGetData); + Register(CommandType.TextureGetDataSlice); + Register(CommandType.TextureRelease); + Register(CommandType.TextureSetData); + Register(CommandType.TextureSetDataSlice); + Register(CommandType.TextureSetDataSliceRegion); + Register(CommandType.TextureSetStorage); + + Register(CommandType.TextureArrayDispose); + Register(CommandType.TextureArraySetSamplers); + Register(CommandType.TextureArraySetTextures); + + Register(CommandType.WindowPresent); + + Register(CommandType.Barrier); + Register(CommandType.BeginTransformFeedback); + Register(CommandType.ClearBuffer); + Register(CommandType.ClearRenderTargetColor); + Register(CommandType.ClearRenderTargetDepthStencil); + Register(CommandType.CommandBufferBarrier); + Register(CommandType.CopyBuffer); + Register(CommandType.DispatchCompute); + Register(CommandType.Draw); + Register(CommandType.DrawIndexed); + Register(CommandType.DrawIndexedIndirect); + Register(CommandType.DrawIndexedIndirectCount); + Register(CommandType.DrawIndirect); + Register(CommandType.DrawIndirectCount); + Register(CommandType.DrawTexture); + Register(CommandType.EndHostConditionalRendering); + Register(CommandType.EndTransformFeedback); + Register(CommandType.SetAlphaTest); + Register(CommandType.SetBlendStateAdvanced); + Register(CommandType.SetBlendState); + Register(CommandType.SetDepthBias); + Register(CommandType.SetDepthClamp); + Register(CommandType.SetDepthMode); + Register(CommandType.SetDepthTest); + Register(CommandType.SetFaceCulling); + Register(CommandType.SetFrontFace); + Register(CommandType.SetStorageBuffers); + Register(CommandType.SetTransformFeedbackBuffers); + Register(CommandType.SetUniformBuffers); + Register(CommandType.SetImage); + Register(CommandType.SetImageArray); + Register(CommandType.SetImageArraySeparate); + Register(CommandType.SetIndexBuffer); + Register(CommandType.SetLineParameters); + Register(CommandType.SetLogicOpState); + Register(CommandType.SetMultisampleState); + Register(CommandType.SetPatchParameters); + Register(CommandType.SetPointParameters); + Register(CommandType.SetPolygonMode); + Register(CommandType.SetPrimitiveRestart); + Register(CommandType.SetPrimitiveTopology); + Register(CommandType.SetProgram); + Register(CommandType.SetRasterizerDiscard); + Register(CommandType.SetRenderTargetColorMasks); + Register(CommandType.SetRenderTargets); + Register(CommandType.SetScissor); + Register(CommandType.SetStencilTest); + Register(CommandType.SetTextureAndSampler); + Register(CommandType.SetTextureArray); + Register(CommandType.SetTextureArraySeparate); + Register(CommandType.SetUserClipDistance); + Register(CommandType.SetVertexAttribs); + Register(CommandType.SetVertexBuffers); + Register(CommandType.SetViewports); + Register(CommandType.TextureBarrier); + Register(CommandType.TextureBarrierTiled); + Register(CommandType.TryHostConditionalRendering); + Register(CommandType.TryHostConditionalRenderingFlush); + + return maxCommandSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RunCommand(Span memory, ThreadedRenderer threaded, IRenderer renderer) + { + _lookup[memory[^1]](memory, threaded, renderer); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs new file mode 100644 index 00000000..cf3f5d6c --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs @@ -0,0 +1,117 @@ +namespace Ryujinx.Graphics.GAL.Multithreading +{ + enum CommandType : byte + { + Action, + CreateBufferAccess, + CreateBufferSparse, + CreateHostBuffer, + CreateImageArray, + CreateProgram, + CreateSampler, + CreateSync, + CreateTexture, + CreateTextureArray, + GetCapabilities, + Unused, + PreFrame, + ReportCounter, + ResetCounter, + UpdateCounters, + + BufferDispose, + BufferGetData, + BufferSetData, + + CounterEventDispose, + CounterEventFlush, + + ImageArrayDispose, + ImageArraySetFormats, + ImageArraySetImages, + + ProgramDispose, + ProgramGetBinary, + ProgramCheckLink, + + SamplerDispose, + + TextureCopyTo, + TextureCopyToBuffer, + TextureCopyToScaled, + TextureCopyToSlice, + TextureCreateView, + TextureGetData, + TextureGetDataSlice, + TextureRelease, + TextureSetData, + TextureSetDataSlice, + TextureSetDataSliceRegion, + TextureSetStorage, + + TextureArrayDispose, + TextureArraySetSamplers, + TextureArraySetTextures, + + WindowPresent, + + Barrier, + BeginTransformFeedback, + ClearBuffer, + ClearRenderTargetColor, + ClearRenderTargetDepthStencil, + CommandBufferBarrier, + CopyBuffer, + DispatchCompute, + Draw, + DrawIndexed, + DrawIndexedIndirect, + DrawIndexedIndirectCount, + DrawIndirect, + DrawIndirectCount, + DrawTexture, + EndHostConditionalRendering, + EndTransformFeedback, + SetAlphaTest, + SetBlendStateAdvanced, + SetBlendState, + SetDepthBias, + SetDepthClamp, + SetDepthMode, + SetDepthTest, + SetFaceCulling, + SetFrontFace, + SetStorageBuffers, + SetTransformFeedbackBuffers, + SetUniformBuffers, + SetImage, + SetImageArray, + SetImageArraySeparate, + SetIndexBuffer, + SetLineParameters, + SetLogicOpState, + SetMultisampleState, + SetPatchParameters, + SetPointParameters, + SetPolygonMode, + SetPrimitiveRestart, + SetPrimitiveTopology, + SetProgram, + SetRasterizerDiscard, + SetRenderTargetColorMasks, + SetRenderTargets, + SetScissor, + SetStencilTest, + SetTextureAndSampler, + SetTextureArray, + SetTextureArraySeparate, + SetUserClipDistance, + SetVertexAttribs, + SetVertexBuffers, + SetViewports, + TextureBarrier, + TextureBarrierTiled, + TryHostConditionalRendering, + TryHostConditionalRenderingFlush, + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/BarrierCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/BarrierCommand.cs new file mode 100644 index 00000000..fd80a127 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/BarrierCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct BarrierCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.Barrier; + + public static void Run(ref BarrierCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.Barrier(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/BeginTransformFeedbackCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/BeginTransformFeedbackCommand.cs new file mode 100644 index 00000000..7b2f7317 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/BeginTransformFeedbackCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct BeginTransformFeedbackCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.BeginTransformFeedback; + private PrimitiveTopology _topology; + + public void Set(PrimitiveTopology topology) + { + _topology = topology; + } + + public static void Run(ref BeginTransformFeedbackCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.BeginTransformFeedback(command._topology); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferDisposeCommand.cs new file mode 100644 index 00000000..aaf8e5b2 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferDisposeCommand.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer +{ + struct BufferDisposeCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.BufferDispose; + private BufferHandle _buffer; + + public void Set(BufferHandle buffer) + { + _buffer = buffer; + } + + public static void Run(ref BufferDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.DeleteBuffer(threaded.Buffers.MapBuffer(command._buffer)); + threaded.Buffers.UnassignBuffer(command._buffer); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferGetDataCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferGetDataCommand.cs new file mode 100644 index 00000000..b8f61df0 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferGetDataCommand.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer +{ + struct BufferGetDataCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.BufferGetData; + private BufferHandle _buffer; + private int _offset; + private int _size; + private TableRef>> _result; + + public void Set(BufferHandle buffer, int offset, int size, TableRef>> result) + { + _buffer = buffer; + _offset = offset; + _size = size; + _result = result; + } + + public static void Run(ref BufferGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + PinnedSpan result = renderer.GetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, command._size); + + command._result.Get(threaded).Result = result; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferSetDataCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferSetDataCommand.cs new file mode 100644 index 00000000..592699e4 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferSetDataCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer +{ + struct BufferSetDataCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.BufferSetData; + private BufferHandle _buffer; + private int _offset; + private SpanRef _data; + + public void Set(BufferHandle buffer, int offset, SpanRef data) + { + _buffer = buffer; + _offset = offset; + _data = data; + } + + public static void Run(ref BufferSetDataCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ReadOnlySpan data = command._data.Get(threaded); + renderer.SetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, data); + command._data.Dispose(threaded); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearBufferCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearBufferCommand.cs new file mode 100644 index 00000000..e94da74b --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearBufferCommand.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct ClearBufferCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ClearBuffer; + private BufferHandle _destination; + private int _offset; + private int _size; + private uint _value; + + public void Set(BufferHandle destination, int offset, int size, uint value) + { + _destination = destination; + _offset = offset; + _size = size; + _value = value; + } + + public static void Run(ref ClearBufferCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.ClearBuffer(threaded.Buffers.MapBuffer(command._destination), command._offset, command._size, command._value); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetColorCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetColorCommand.cs new file mode 100644 index 00000000..d4417f66 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetColorCommand.cs @@ -0,0 +1,26 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct ClearRenderTargetColorCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ClearRenderTargetColor; + private int _index; + private int _layer; + private int _layerCount; + private uint _componentMask; + private ColorF _color; + + public void Set(int index, int layer, int layerCount, uint componentMask, ColorF color) + { + _index = index; + _layer = layer; + _layerCount = layerCount; + _componentMask = componentMask; + _color = color; + } + + public static void Run(ref ClearRenderTargetColorCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.ClearRenderTargetColor(command._index, command._layer, command._layerCount, command._componentMask, command._color); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetDepthStencilCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetDepthStencilCommand.cs new file mode 100644 index 00000000..78f81025 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetDepthStencilCommand.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct ClearRenderTargetDepthStencilCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ClearRenderTargetDepthStencil; + private int _layer; + private int _layerCount; + private float _depthValue; + private bool _depthMask; + private int _stencilValue; + private int _stencilMask; + + public void Set(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask) + { + _layer = layer; + _layerCount = layerCount; + _depthValue = depthValue; + _depthMask = depthMask; + _stencilValue = stencilValue; + _stencilMask = stencilMask; + } + + public static void Run(ref ClearRenderTargetDepthStencilCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.ClearRenderTargetDepthStencil(command._layer, command._layerCount, command._depthValue, command._depthMask, command._stencilValue, command._stencilMask); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CommandBufferBarrierCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CommandBufferBarrierCommand.cs new file mode 100644 index 00000000..0403b33f --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CommandBufferBarrierCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct CommandBufferBarrierCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CommandBufferBarrier; + + public static void Run(ref CommandBufferBarrierCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.CommandBufferBarrier(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CopyBufferCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CopyBufferCommand.cs new file mode 100644 index 00000000..3eeaefb8 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CopyBufferCommand.cs @@ -0,0 +1,26 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct CopyBufferCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CopyBuffer; + private BufferHandle _source; + private BufferHandle _destination; + private int _srcOffset; + private int _dstOffset; + private int _size; + + public void Set(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size) + { + _source = source; + _destination = destination; + _srcOffset = srcOffset; + _dstOffset = dstOffset; + _size = size; + } + + public static void Run(ref CopyBufferCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.CopyBuffer(threaded.Buffers.MapBuffer(command._source), threaded.Buffers.MapBuffer(command._destination), command._srcOffset, command._dstOffset, command._size); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventDisposeCommand.cs new file mode 100644 index 00000000..1d5eabcf --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventDisposeCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent +{ + struct CounterEventDisposeCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CounterEventDispose; + private TableRef _event; + + public void Set(TableRef evt) + { + _event = evt; + } + + public static void Run(ref CounterEventDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._event.Get(threaded).Base.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventFlushCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventFlushCommand.cs new file mode 100644 index 00000000..27cde8a8 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventFlushCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent +{ + struct CounterEventFlushCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CounterEventFlush; + private TableRef _event; + + public void Set(TableRef evt) + { + _event = evt; + } + + public static void Run(ref CounterEventFlushCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._event.Get(threaded).Base.Flush(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DispatchComputeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DispatchComputeCommand.cs new file mode 100644 index 00000000..65028378 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DispatchComputeCommand.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct DispatchComputeCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.DispatchCompute; + private int _groupsX; + private int _groupsY; + private int _groupsZ; + + public void Set(int groupsX, int groupsY, int groupsZ) + { + _groupsX = groupsX; + _groupsY = groupsY; + _groupsZ = groupsZ; + } + + public static void Run(ref DispatchComputeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.DispatchCompute(command._groupsX, command._groupsY, command._groupsZ); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawCommand.cs new file mode 100644 index 00000000..8acf12ca --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawCommand.cs @@ -0,0 +1,26 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct DrawIndexedCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.DrawIndexed; + private int _indexCount; + private int _instanceCount; + private int _firstIndex; + private int _firstVertex; + private int _firstInstance; + + public void Set(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance) + { + _indexCount = indexCount; + _instanceCount = instanceCount; + _firstIndex = firstIndex; + _firstVertex = firstVertex; + _firstInstance = firstInstance; + } + + public static void Run(ref DrawIndexedCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.DrawIndexed(command._indexCount, command._instanceCount, command._firstIndex, command._firstVertex, command._firstInstance); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedCommand.cs new file mode 100644 index 00000000..8d43d456 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedCommand.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct DrawCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.Draw; + private int _vertexCount; + private int _instanceCount; + private int _firstVertex; + private int _firstInstance; + + public void Set(int vertexCount, int instanceCount, int firstVertex, int firstInstance) + { + _vertexCount = vertexCount; + _instanceCount = instanceCount; + _firstVertex = firstVertex; + _firstInstance = firstInstance; + } + + public static void Run(ref DrawCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.Draw(command._vertexCount, command._instanceCount, command._firstVertex, command._firstInstance); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCommand.cs new file mode 100644 index 00000000..e4156e2b --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct DrawIndexedIndirectCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.DrawIndexedIndirect; + private BufferRange _indirectBuffer; + + public void Set(BufferRange indirectBuffer) + { + _indirectBuffer = indirectBuffer; + } + + public static void Run(ref DrawIndexedIndirectCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.DrawIndexedIndirect(threaded.Buffers.MapBufferRange(command._indirectBuffer)); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCountCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCountCommand.cs new file mode 100644 index 00000000..2be81fbe --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCountCommand.cs @@ -0,0 +1,29 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct DrawIndexedIndirectCountCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.DrawIndexedIndirectCount; + private BufferRange _indirectBuffer; + private BufferRange _parameterBuffer; + private int _maxDrawCount; + private int _stride; + + public void Set(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + _indirectBuffer = indirectBuffer; + _parameterBuffer = parameterBuffer; + _maxDrawCount = maxDrawCount; + _stride = stride; + } + + public static void Run(ref DrawIndexedIndirectCountCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.DrawIndexedIndirectCount( + threaded.Buffers.MapBufferRange(command._indirectBuffer), + threaded.Buffers.MapBufferRange(command._parameterBuffer), + command._maxDrawCount, + command._stride + ); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCommand.cs new file mode 100644 index 00000000..e2e93d3c --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct DrawIndirectCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.DrawIndirect; + private BufferRange _indirectBuffer; + + public void Set(BufferRange indirectBuffer) + { + _indirectBuffer = indirectBuffer; + } + + public static void Run(ref DrawIndirectCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.DrawIndirect(threaded.Buffers.MapBufferRange(command._indirectBuffer)); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCountCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCountCommand.cs new file mode 100644 index 00000000..0247aa29 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCountCommand.cs @@ -0,0 +1,29 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct DrawIndirectCountCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.DrawIndirectCount; + private BufferRange _indirectBuffer; + private BufferRange _parameterBuffer; + private int _maxDrawCount; + private int _stride; + + public void Set(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + _indirectBuffer = indirectBuffer; + _parameterBuffer = parameterBuffer; + _maxDrawCount = maxDrawCount; + _stride = stride; + } + + public static void Run(ref DrawIndirectCountCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.DrawIndirectCount( + threaded.Buffers.MapBufferRange(command._indirectBuffer), + threaded.Buffers.MapBufferRange(command._parameterBuffer), + command._maxDrawCount, + command._stride + ); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawTextureCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawTextureCommand.cs new file mode 100644 index 00000000..1e9cfd83 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawTextureCommand.cs @@ -0,0 +1,31 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct DrawTextureCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.DrawTexture; + private TableRef _texture; + private TableRef _sampler; + private Extents2DF _srcRegion; + private Extents2DF _dstRegion; + + public void Set(TableRef texture, TableRef sampler, Extents2DF srcRegion, Extents2DF dstRegion) + { + _texture = texture; + _sampler = sampler; + _srcRegion = srcRegion; + _dstRegion = dstRegion; + } + + public static void Run(ref DrawTextureCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.DrawTexture( + command._texture.GetAs(threaded)?.Base, + command._sampler.GetAs(threaded)?.Base, + command._srcRegion, + command._dstRegion); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndHostConditionalRenderingCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndHostConditionalRenderingCommand.cs new file mode 100644 index 00000000..3f20c010 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndHostConditionalRenderingCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct EndHostConditionalRenderingCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.EndHostConditionalRendering; + + public static void Run(ref EndHostConditionalRenderingCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.EndHostConditionalRendering(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndTransformFeedbackCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndTransformFeedbackCommand.cs new file mode 100644 index 00000000..f5d5e5c2 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndTransformFeedbackCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct EndTransformFeedbackCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.EndTransformFeedback; + + public static void Run(ref EndTransformFeedbackCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.EndTransformFeedback(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/IGALCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/IGALCommand.cs new file mode 100644 index 00000000..ada6713b --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/IGALCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + interface IGALCommand + { + CommandType CommandType { get; } + } + + interface IGALCommand where T : IGALCommand + { + abstract static void Run(ref T command, ThreadedRenderer threaded, IRenderer renderer); + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArrayDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArrayDisposeCommand.cs new file mode 100644 index 00000000..ac2ac933 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArrayDisposeCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray +{ + struct ImageArrayDisposeCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ImageArrayDispose; + private TableRef _imageArray; + + public void Set(TableRef imageArray) + { + _imageArray = imageArray; + } + + public static void Run(ref ImageArrayDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._imageArray.Get(threaded).Base.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs new file mode 100644 index 00000000..8e3ba88a --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetFormatsCommand.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray +{ + struct ImageArraySetFormatsCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ImageArraySetFormats; + private TableRef _imageArray; + private int _index; + private TableRef _imageFormats; + + public void Set(TableRef imageArray, int index, TableRef imageFormats) + { + _imageArray = imageArray; + _index = index; + _imageFormats = imageFormats; + } + + public static void Run(ref ImageArraySetFormatsCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedImageArray imageArray = command._imageArray.Get(threaded); + imageArray.Base.SetFormats(command._index, command._imageFormats.Get(threaded)); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetImagesCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetImagesCommand.cs new file mode 100644 index 00000000..cc28d585 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArraySetImagesCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray +{ + struct ImageArraySetImagesCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ImageArraySetImages; + private TableRef _imageArray; + private int _index; + private TableRef _images; + + public void Set(TableRef imageArray, int index, TableRef images) + { + _imageArray = imageArray; + _index = index; + _images = images; + } + + public static void Run(ref ImageArraySetImagesCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedImageArray imageArray = command._imageArray.Get(threaded); + imageArray.Base.SetImages(command._index, command._images.Get(threaded).Select(texture => ((ThreadedTexture)texture)?.Base).ToArray()); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramCheckLinkCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramCheckLinkCommand.cs new file mode 100644 index 00000000..e07fad9d --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramCheckLinkCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program +{ + struct ProgramCheckLinkCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ProgramCheckLink; + private TableRef _program; + private bool _blocking; + private TableRef> _result; + + public void Set(TableRef program, bool blocking, TableRef> result) + { + _program = program; + _blocking = blocking; + _result = result; + } + + public static void Run(ref ProgramCheckLinkCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ProgramLinkStatus result = command._program.Get(threaded).Base.CheckProgramLink(command._blocking); + + command._result.Get(threaded).Result = result; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramDisposeCommand.cs new file mode 100644 index 00000000..ebe1428d --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramDisposeCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program +{ + struct ProgramDisposeCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ProgramDispose; + private TableRef _program; + + public void Set(TableRef program) + { + _program = program; + } + + public static void Run(ref ProgramDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._program.Get(threaded).Base.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramGetBinaryCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramGetBinaryCommand.cs new file mode 100644 index 00000000..9aa72d4b --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramGetBinaryCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program +{ + struct ProgramGetBinaryCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ProgramGetBinary; + private TableRef _program; + private TableRef> _result; + + public void Set(TableRef program, TableRef> result) + { + _program = program; + _result = result; + } + + public static void Run(ref ProgramGetBinaryCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + byte[] result = command._program.Get(threaded).Base.GetBinary(); + + command._result.Get(threaded).Result = result; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ActionCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ActionCommand.cs new file mode 100644 index 00000000..f0695f26 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ActionCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct ActionCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.Action; + private TableRef _action; + + public void Set(TableRef action) + { + _action = action; + } + + public static void Run(ref ActionCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._action.Get(threaded)(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferAccessCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferAccessCommand.cs new file mode 100644 index 00000000..3dbc0b06 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferAccessCommand.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateBufferAccessCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CreateBufferAccess; + private BufferHandle _threadedHandle; + private int _size; + private BufferAccess _access; + + public void Set(BufferHandle threadedHandle, int size, BufferAccess access) + { + _threadedHandle = threadedHandle; + _size = size; + _access = access; + } + + public static void Run(ref CreateBufferAccessCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, command._access)); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferSparseCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferSparseCommand.cs new file mode 100644 index 00000000..965529ad --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferSparseCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateBufferSparseCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CreateBufferSparse; + private BufferHandle _threadedHandle; + private SpanRef _buffers; + + public void Set(BufferHandle threadedHandle, SpanRef buffers) + { + _threadedHandle = threadedHandle; + _buffers = buffers; + } + + public static void Run(ref CreateBufferSparseCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + Span buffers = command._buffers.Get(threaded); + threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBufferSparse(threaded.Buffers.MapBufferRanges(buffers))); + command._buffers.Dispose(threaded); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateHostBufferCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateHostBufferCommand.cs new file mode 100644 index 00000000..0001680b --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateHostBufferCommand.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateHostBufferCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CreateHostBuffer; + private BufferHandle _threadedHandle; + private nint _pointer; + private int _size; + + public void Set(BufferHandle threadedHandle, nint pointer, int size) + { + _threadedHandle = threadedHandle; + _pointer = pointer; + _size = size; + } + + public static void Run(ref CreateHostBufferCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._pointer, command._size)); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateImageArrayCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateImageArrayCommand.cs new file mode 100644 index 00000000..1c3fb812 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateImageArrayCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateImageArrayCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CreateImageArray; + private TableRef _imageArray; + private int _size; + private bool _isBuffer; + + public void Set(TableRef imageArray, int size, bool isBuffer) + { + _imageArray = imageArray; + _size = size; + _isBuffer = isBuffer; + } + + public static void Run(ref CreateImageArrayCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._imageArray.Get(threaded).Base = renderer.CreateImageArray(command._size, command._isBuffer); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateProgramCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateProgramCommand.cs new file mode 100644 index 00000000..2388c79a --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateProgramCommand.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources.Programs; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateProgramCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CreateProgram; + private TableRef _request; + + public void Set(TableRef request) + { + _request = request; + } + + public static void Run(ref CreateProgramCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + IProgramRequest request = command._request.Get(threaded); + + if (request.Threaded.Base == null) + { + request.Threaded.Base = request.Create(renderer); + } + + threaded.Programs.ProcessQueue(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSamplerCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSamplerCommand.cs new file mode 100644 index 00000000..ae01bd0c --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSamplerCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateSamplerCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CreateSampler; + private TableRef _sampler; + private SamplerCreateInfo _info; + + public void Set(TableRef sampler, SamplerCreateInfo info) + { + _sampler = sampler; + _info = info; + } + + public static void Run(ref CreateSamplerCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._sampler.Get(threaded).Base = renderer.CreateSampler(command._info); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSyncCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSyncCommand.cs new file mode 100644 index 00000000..9e9caa98 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSyncCommand.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateSyncCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CreateSync; + private ulong _id; + private bool _strict; + + public void Set(ulong id, bool strict) + { + _id = id; + _strict = strict; + } + + public static void Run(ref CreateSyncCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.CreateSync(command._id, command._strict); + + threaded.Sync.AssignSync(command._id); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureArrayCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureArrayCommand.cs new file mode 100644 index 00000000..9bd891e6 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureArrayCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateTextureArrayCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CreateTextureArray; + private TableRef _textureArray; + private int _size; + private bool _isBuffer; + + public void Set(TableRef textureArray, int size, bool isBuffer) + { + _textureArray = textureArray; + _size = size; + _isBuffer = isBuffer; + } + + public static void Run(ref CreateTextureArrayCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._textureArray.Get(threaded).Base = renderer.CreateTextureArray(command._size, command._isBuffer); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureCommand.cs new file mode 100644 index 00000000..7caf5bc1 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateTextureCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CreateTexture; + private TableRef _texture; + private TextureCreateInfo _info; + + public void Set(TableRef texture, TextureCreateInfo info) + { + _texture = texture; + _info = info; + } + + public static void Run(ref CreateTextureCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._texture.Get(threaded).Base = renderer.CreateTexture(command._info); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/GetCapabilitiesCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/GetCapabilitiesCommand.cs new file mode 100644 index 00000000..d6e6db7d --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/GetCapabilitiesCommand.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct GetCapabilitiesCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.GetCapabilities; + private TableRef> _result; + + public void Set(TableRef> result) + { + _result = result; + } + + public static void Run(ref GetCapabilitiesCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._result.Get(threaded).Result = renderer.GetCapabilities(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs new file mode 100644 index 00000000..b9e2944b --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct PreFrameCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.PreFrame; + + public static void Run(ref PreFrameCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.PreFrame(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ReportCounterCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ReportCounterCommand.cs new file mode 100644 index 00000000..18b96f74 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ReportCounterCommand.cs @@ -0,0 +1,32 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct ReportCounterCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ReportCounter; + private TableRef _event; + private CounterType _type; + private TableRef> _resultHandler; + private float _divisor; + private bool _hostReserved; + + public void Set(TableRef evt, CounterType type, TableRef> resultHandler, float divisor, bool hostReserved) + { + _event = evt; + _type = type; + _resultHandler = resultHandler; + _divisor = divisor; + _hostReserved = hostReserved; + } + + public static void Run(ref ReportCounterCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedCounterEvent evt = command._event.Get(threaded); + + evt.Create(renderer, command._type, command._resultHandler.Get(threaded), command._divisor, command._hostReserved); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ResetCounterCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ResetCounterCommand.cs new file mode 100644 index 00000000..44dd674e --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ResetCounterCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct ResetCounterCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.ResetCounter; + private CounterType _type; + + public void Set(CounterType type) + { + _type = type; + } + + public static void Run(ref ResetCounterCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.ResetCounter(command._type); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/UpdateCountersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/UpdateCountersCommand.cs new file mode 100644 index 00000000..a6c877a4 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/UpdateCountersCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct UpdateCountersCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.UpdateCounters; + + public static void Run(ref UpdateCountersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.UpdateCounters(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Sampler/SamplerDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Sampler/SamplerDisposeCommand.cs new file mode 100644 index 00000000..2e4eab5f --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Sampler/SamplerDisposeCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler +{ + struct SamplerDisposeCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SamplerDispose; + private TableRef _sampler; + + public void Set(TableRef sampler) + { + _sampler = sampler; + } + + public static void Run(ref SamplerDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._sampler.Get(threaded).Base.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetAlphaTestCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetAlphaTestCommand.cs new file mode 100644 index 00000000..bee7b866 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetAlphaTestCommand.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetAlphaTestCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetAlphaTest; + private bool _enable; + private float _reference; + private CompareOp _op; + + public void Set(bool enable, float reference, CompareOp op) + { + _enable = enable; + _reference = reference; + _op = op; + } + + public static void Run(ref SetAlphaTestCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetAlphaTest(command._enable, command._reference, command._op); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateAdvancedCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateAdvancedCommand.cs new file mode 100644 index 00000000..8405a8ee --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateAdvancedCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetBlendStateAdvancedCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetBlendStateAdvanced; + private AdvancedBlendDescriptor _blend; + + public void Set(AdvancedBlendDescriptor blend) + { + _blend = blend; + } + + public static void Run(ref SetBlendStateAdvancedCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetBlendState(command._blend); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateCommand.cs new file mode 100644 index 00000000..6fc2c1a2 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetBlendStateCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetBlendState; + private int _index; + private BlendDescriptor _blend; + + public void Set(int index, BlendDescriptor blend) + { + _index = index; + _blend = blend; + } + + public static void Run(ref SetBlendStateCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetBlendState(command._index, command._blend); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthBiasCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthBiasCommand.cs new file mode 100644 index 00000000..0c46fbda --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthBiasCommand.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetDepthBiasCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetDepthBias; + private PolygonModeMask _enables; + private float _factor; + private float _units; + private float _clamp; + + public void Set(PolygonModeMask enables, float factor, float units, float clamp) + { + _enables = enables; + _factor = factor; + _units = units; + _clamp = clamp; + } + + public static void Run(ref SetDepthBiasCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetDepthBias(command._enables, command._factor, command._units, command._clamp); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthClampCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthClampCommand.cs new file mode 100644 index 00000000..ec34f473 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthClampCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetDepthClampCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetDepthClamp; + private bool _clamp; + + public void Set(bool clamp) + { + _clamp = clamp; + } + + public static void Run(ref SetDepthClampCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetDepthClamp(command._clamp); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthModeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthModeCommand.cs new file mode 100644 index 00000000..1f9b1fbc --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthModeCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetDepthModeCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetDepthMode; + private DepthMode _mode; + + public void Set(DepthMode mode) + { + _mode = mode; + } + + public static void Run(ref SetDepthModeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetDepthMode(command._mode); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthTestCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthTestCommand.cs new file mode 100644 index 00000000..8df54528 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthTestCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetDepthTestCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetDepthTest; + private DepthTestDescriptor _depthTest; + + public void Set(DepthTestDescriptor depthTest) + { + _depthTest = depthTest; + } + + public static void Run(ref SetDepthTestCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetDepthTest(command._depthTest); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs new file mode 100644 index 00000000..611168f8 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetFaceCullingCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetFaceCulling; + private bool _enable; + private Face _face; + + public void Set(bool enable, Face face) + { + _enable = enable; + _face = face; + } + + public static void Run(ref SetFaceCullingCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetFaceCulling(command._enable, command._face); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFrontFaceCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFrontFaceCommand.cs new file mode 100644 index 00000000..9c0b79e4 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFrontFaceCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetFrontFaceCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetFrontFace; + private FrontFace _frontFace; + + public void Set(FrontFace frontFace) + { + _frontFace = frontFace; + } + + public static void Run(ref SetFrontFaceCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetFrontFace(command._frontFace); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArrayCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArrayCommand.cs new file mode 100644 index 00000000..b8d3c7ac --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArrayCommand.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetImageArrayCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetImageArray; + private ShaderStage _stage; + private int _binding; + private TableRef _array; + + public void Set(ShaderStage stage, int binding, TableRef array) + { + _stage = stage; + _binding = binding; + _array = array; + } + + public static void Run(ref SetImageArrayCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetImageArray(command._stage, command._binding, command._array.GetAs(threaded)?.Base); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArraySeparateCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArraySeparateCommand.cs new file mode 100644 index 00000000..abeb58a0 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArraySeparateCommand.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetImageArraySeparateCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetImageArraySeparate; + private ShaderStage _stage; + private int _setIndex; + private TableRef _array; + + public void Set(ShaderStage stage, int setIndex, TableRef array) + { + _stage = stage; + _setIndex = setIndex; + _array = array; + } + + public static void Run(ref SetImageArraySeparateCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetImageArraySeparate(command._stage, command._setIndex, command._array.GetAs(threaded)?.Base); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs new file mode 100644 index 00000000..243480a8 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetImageCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetImage; + private ShaderStage _stage; + private int _binding; + private TableRef _texture; + private Format _imageFormat; + + public void Set(ShaderStage stage, int binding, TableRef texture, Format imageFormat) + { + _stage = stage; + _binding = binding; + _texture = texture; + _imageFormat = imageFormat; + } + + public static void Run(ref SetImageCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetImage(command._stage, command._binding, command._texture.GetAs(threaded)?.Base, command._imageFormat); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetIndexBufferCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetIndexBufferCommand.cs new file mode 100644 index 00000000..629906dc --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetIndexBufferCommand.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetIndexBufferCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetIndexBuffer; + private BufferRange _buffer; + private IndexType _type; + + public void Set(BufferRange buffer, IndexType type) + { + _buffer = buffer; + _type = type; + } + + public static void Run(ref SetIndexBufferCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + BufferRange range = threaded.Buffers.MapBufferRange(command._buffer); + renderer.Pipeline.SetIndexBuffer(range, command._type); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLineParametersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLineParametersCommand.cs new file mode 100644 index 00000000..d11d46bb --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLineParametersCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetLineParametersCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetLineParameters; + private float _width; + private bool _smooth; + + public void Set(float width, bool smooth) + { + _width = width; + _smooth = smooth; + } + + public static void Run(ref SetLineParametersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetLineParameters(command._width, command._smooth); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLogicOpStateCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLogicOpStateCommand.cs new file mode 100644 index 00000000..71f2e1d8 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLogicOpStateCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetLogicOpStateCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetLogicOpState; + private bool _enable; + private LogicalOp _op; + + public void Set(bool enable, LogicalOp op) + { + _enable = enable; + _op = op; + } + + public static void Run(ref SetLogicOpStateCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetLogicOpState(command._enable, command._op); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetMultisampleStateCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetMultisampleStateCommand.cs new file mode 100644 index 00000000..f30140c7 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetMultisampleStateCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetMultisampleStateCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetMultisampleState; + private MultisampleDescriptor _multisample; + + public void Set(MultisampleDescriptor multisample) + { + _multisample = multisample; + } + + public static void Run(ref SetMultisampleStateCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetMultisampleState(command._multisample); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPatchParametersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPatchParametersCommand.cs new file mode 100644 index 00000000..6dbe4b7b --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPatchParametersCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Common.Memory; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetPatchParametersCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetPatchParameters; + private int _vertices; + private Array4 _defaultOuterLevel; + private Array2 _defaultInnerLevel; + + public void Set(int vertices, ReadOnlySpan defaultOuterLevel, ReadOnlySpan defaultInnerLevel) + { + _vertices = vertices; + defaultOuterLevel.CopyTo(_defaultOuterLevel.AsSpan()); + defaultInnerLevel.CopyTo(_defaultInnerLevel.AsSpan()); + } + + public static void Run(ref SetPatchParametersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetPatchParameters(command._vertices, command._defaultOuterLevel.AsSpan(), command._defaultInnerLevel.AsSpan()); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPointParametersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPointParametersCommand.cs new file mode 100644 index 00000000..6faa2510 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPointParametersCommand.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetPointParametersCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetPointParameters; + private float _size; + private bool _isProgramPointSize; + private bool _enablePointSprite; + private Origin _origin; + + public void Set(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin) + { + _size = size; + _isProgramPointSize = isProgramPointSize; + _enablePointSprite = enablePointSprite; + _origin = origin; + } + + public static void Run(ref SetPointParametersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetPointParameters(command._size, command._isProgramPointSize, command._enablePointSprite, command._origin); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPolygonModeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPolygonModeCommand.cs new file mode 100644 index 00000000..caa362c0 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPolygonModeCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetPolygonModeCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetPolygonMode; + private PolygonMode _frontMode; + private PolygonMode _backMode; + + public void Set(PolygonMode frontMode, PolygonMode backMode) + { + _frontMode = frontMode; + _backMode = backMode; + } + + public static void Run(ref SetPolygonModeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetPolygonMode(command._frontMode, command._backMode); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveRestartCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveRestartCommand.cs new file mode 100644 index 00000000..9fd5b541 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveRestartCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetPrimitiveRestartCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetPrimitiveRestart; + private bool _enable; + private int _index; + + public void Set(bool enable, int index) + { + _enable = enable; + _index = index; + } + + public static void Run(ref SetPrimitiveRestartCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetPrimitiveRestart(command._enable, command._index); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveTopologyCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveTopologyCommand.cs new file mode 100644 index 00000000..c7cb2a2f --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveTopologyCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetPrimitiveTopologyCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetPrimitiveTopology; + private PrimitiveTopology _topology; + + public void Set(PrimitiveTopology topology) + { + _topology = topology; + } + + public static void Run(ref SetPrimitiveTopologyCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetPrimitiveTopology(command._topology); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetProgramCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetProgramCommand.cs new file mode 100644 index 00000000..7f1d845b --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetProgramCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetProgramCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetProgram; + private TableRef _program; + + public void Set(TableRef program) + { + _program = program; + } + + public static void Run(ref SetProgramCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedProgram program = command._program.GetAs(threaded); + + threaded.Programs.WaitForProgram(program); + + renderer.Pipeline.SetProgram(program.Base); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRasterizerDiscardCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRasterizerDiscardCommand.cs new file mode 100644 index 00000000..61f9246f --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRasterizerDiscardCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetRasterizerDiscardCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetRasterizerDiscard; + private bool _discard; + + public void Set(bool discard) + { + _discard = discard; + } + + public static void Run(ref SetRasterizerDiscardCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetRasterizerDiscard(command._discard); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetColorMasksCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetColorMasksCommand.cs new file mode 100644 index 00000000..785c3215 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetColorMasksCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetRenderTargetColorMasksCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetRenderTargetColorMasks; + private SpanRef _componentMask; + + public void Set(SpanRef componentMask) + { + _componentMask = componentMask; + } + + public static void Run(ref SetRenderTargetColorMasksCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ReadOnlySpan componentMask = command._componentMask.Get(threaded); + renderer.Pipeline.SetRenderTargetColorMasks(componentMask); + command._componentMask.Dispose(threaded); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs new file mode 100644 index 00000000..063cf1e0 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs @@ -0,0 +1,24 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetRenderTargetsCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetRenderTargets; + private TableRef _colors; + private TableRef _depthStencil; + + public void Set(TableRef colors, TableRef depthStencil) + { + _colors = colors; + _depthStencil = depthStencil; + } + + public static void Run(ref SetRenderTargetsCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetRenderTargets(command._colors.Get(threaded).Select(color => ((ThreadedTexture)color)?.Base).ToArray(), command._depthStencil.GetAs(threaded)?.Base); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorsCommand.cs new file mode 100644 index 00000000..15ac00de --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorsCommand.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetScissorsCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetScissor; + private SpanRef> _scissors; + + public void Set(SpanRef> scissors) + { + _scissors = scissors; + } + + public static void Run(ref SetScissorsCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetScissors(command._scissors.Get(threaded)); + + command._scissors.Dispose(threaded); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStencilTestCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStencilTestCommand.cs new file mode 100644 index 00000000..044241e6 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStencilTestCommand.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetStencilTestCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetStencilTest; + private StencilTestDescriptor _stencilTest; + + public void Set(StencilTestDescriptor stencilTest) + { + _stencilTest = stencilTest; + } + + public static void Run(ref SetStencilTestCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetStencilTest(command._stencilTest); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStorageBuffersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStorageBuffersCommand.cs new file mode 100644 index 00000000..63160557 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStorageBuffersCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetStorageBuffersCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetStorageBuffers; + private SpanRef _buffers; + + public void Set(SpanRef buffers) + { + _buffers = buffers; + } + + public static void Run(ref SetStorageBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + Span buffers = command._buffers.Get(threaded); + renderer.Pipeline.SetStorageBuffers(threaded.Buffers.MapBufferRanges(buffers)); + command._buffers.Dispose(threaded); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureAndSamplerCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureAndSamplerCommand.cs new file mode 100644 index 00000000..6d63db86 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureAndSamplerCommand.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetTextureAndSamplerCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetTextureAndSampler; + private ShaderStage _stage; + private int _binding; + private TableRef _texture; + private TableRef _sampler; + + public void Set(ShaderStage stage, int binding, TableRef texture, TableRef sampler) + { + _stage = stage; + _binding = binding; + _texture = texture; + _sampler = sampler; + } + + public static void Run(ref SetTextureAndSamplerCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetTextureAndSampler(command._stage, command._binding, command._texture.GetAs(threaded)?.Base, command._sampler.GetAs(threaded)?.Base); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArrayCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArrayCommand.cs new file mode 100644 index 00000000..45e28aa6 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArrayCommand.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetTextureArrayCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetTextureArray; + private ShaderStage _stage; + private int _binding; + private TableRef _array; + + public void Set(ShaderStage stage, int binding, TableRef array) + { + _stage = stage; + _binding = binding; + _array = array; + } + + public static void Run(ref SetTextureArrayCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetTextureArray(command._stage, command._binding, command._array.GetAs(threaded)?.Base); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArraySeparateCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArraySeparateCommand.cs new file mode 100644 index 00000000..b179f2e7 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArraySeparateCommand.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetTextureArraySeparateCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetTextureArraySeparate; + private ShaderStage _stage; + private int _setIndex; + private TableRef _array; + + public void Set(ShaderStage stage, int setIndex, TableRef array) + { + _stage = stage; + _setIndex = setIndex; + _array = array; + } + + public static void Run(ref SetTextureArraySeparateCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetTextureArraySeparate(command._stage, command._setIndex, command._array.GetAs(threaded)?.Base); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTransformFeedbackBuffersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTransformFeedbackBuffersCommand.cs new file mode 100644 index 00000000..35cb09f8 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTransformFeedbackBuffersCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetTransformFeedbackBuffersCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetTransformFeedbackBuffers; + private SpanRef _buffers; + + public void Set(SpanRef buffers) + { + _buffers = buffers; + } + + public static void Run(ref SetTransformFeedbackBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + Span buffers = command._buffers.Get(threaded); + renderer.Pipeline.SetTransformFeedbackBuffers(threaded.Buffers.MapBufferRanges(buffers)); + command._buffers.Dispose(threaded); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUniformBuffersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUniformBuffersCommand.cs new file mode 100644 index 00000000..ec3777c3 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUniformBuffersCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetUniformBuffersCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetUniformBuffers; + private SpanRef _buffers; + + public void Set(SpanRef buffers) + { + _buffers = buffers; + } + + public static void Run(ref SetUniformBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + Span buffers = command._buffers.Get(threaded); + renderer.Pipeline.SetUniformBuffers(threaded.Buffers.MapBufferRanges(buffers)); + command._buffers.Dispose(threaded); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUserClipDistanceCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUserClipDistanceCommand.cs new file mode 100644 index 00000000..fffa69e6 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUserClipDistanceCommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetUserClipDistanceCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetUserClipDistance; + private int _index; + private bool _enableClip; + + public void Set(int index, bool enableClip) + { + _index = index; + _enableClip = enableClip; + } + + public static void Run(ref SetUserClipDistanceCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.SetUserClipDistance(command._index, command._enableClip); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexAttribsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexAttribsCommand.cs new file mode 100644 index 00000000..a8a18204 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexAttribsCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetVertexAttribsCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetVertexAttribs; + private SpanRef _vertexAttribs; + + public void Set(SpanRef vertexAttribs) + { + _vertexAttribs = vertexAttribs; + } + + public static void Run(ref SetVertexAttribsCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ReadOnlySpan vertexAttribs = command._vertexAttribs.Get(threaded); + renderer.Pipeline.SetVertexAttribs(vertexAttribs); + command._vertexAttribs.Dispose(threaded); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexBuffersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexBuffersCommand.cs new file mode 100644 index 00000000..9138fe42 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexBuffersCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetVertexBuffersCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetVertexBuffers; + private SpanRef _vertexBuffers; + + public void Set(SpanRef vertexBuffers) + { + _vertexBuffers = vertexBuffers; + } + + public static void Run(ref SetVertexBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + Span vertexBuffers = command._vertexBuffers.Get(threaded); + renderer.Pipeline.SetVertexBuffers(threaded.Buffers.MapBufferRanges(vertexBuffers)); + command._vertexBuffers.Dispose(threaded); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs new file mode 100644 index 00000000..03eae09e --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct SetViewportsCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.SetViewports; + private SpanRef _viewports; + + public void Set(SpanRef viewports) + { + _viewports = viewports; + } + + public static void Run(ref SetViewportsCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ReadOnlySpan viewports = command._viewports.Get(threaded); + renderer.Pipeline.SetViewports(viewports); + command._viewports.Dispose(threaded); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToBufferCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToBufferCommand.cs new file mode 100644 index 00000000..d22cc9b7 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToBufferCommand.cs @@ -0,0 +1,29 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureCopyToBufferCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureCopyToBuffer; + private TableRef _texture; + private BufferRange _range; + private int _layer; + private int _level; + private int _stride; + + public void Set(TableRef texture, BufferRange range, int layer, int level, int stride) + { + _texture = texture; + _range = range; + _layer = layer; + _level = level; + _stride = stride; + } + + public static void Run(ref TextureCopyToBufferCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._texture.Get(threaded).Base.CopyTo(threaded.Buffers.MapBufferRange(command._range), command._layer, command._level, command._stride); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToCommand.cs new file mode 100644 index 00000000..80e0814f --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToCommand.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureCopyToCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureCopyTo; + private TableRef _texture; + private TableRef _destination; + private int _firstLayer; + private int _firstLevel; + + public void Set(TableRef texture, TableRef destination, int firstLayer, int firstLevel) + { + _texture = texture; + _destination = destination; + _firstLayer = firstLayer; + _firstLevel = firstLevel; + } + + public static void Run(ref TextureCopyToCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTexture source = command._texture.Get(threaded); + source.Base.CopyTo(command._destination.Get(threaded).Base, command._firstLayer, command._firstLevel); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToScaledCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToScaledCommand.cs new file mode 100644 index 00000000..8e879229 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToScaledCommand.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureCopyToScaledCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureCopyToScaled; + private TableRef _texture; + private TableRef _destination; + private Extents2D _srcRegion; + private Extents2D _dstRegion; + private bool _linearFilter; + + public void Set(TableRef texture, TableRef destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter) + { + _texture = texture; + _destination = destination; + _srcRegion = srcRegion; + _dstRegion = dstRegion; + _linearFilter = linearFilter; + } + + public static void Run(ref TextureCopyToScaledCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTexture source = command._texture.Get(threaded); + source.Base.CopyTo(command._destination.Get(threaded).Base, command._srcRegion, command._dstRegion, command._linearFilter); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToSliceCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToSliceCommand.cs new file mode 100644 index 00000000..52c56961 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToSliceCommand.cs @@ -0,0 +1,32 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureCopyToSliceCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureCopyToSlice; + private TableRef _texture; + private TableRef _destination; + private int _srcLayer; + private int _dstLayer; + private int _srcLevel; + private int _dstLevel; + + public void Set(TableRef texture, TableRef destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel) + { + _texture = texture; + _destination = destination; + _srcLayer = srcLayer; + _dstLayer = dstLayer; + _srcLevel = srcLevel; + _dstLevel = dstLevel; + } + + public static void Run(ref TextureCopyToSliceCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTexture source = command._texture.Get(threaded); + source.Base.CopyTo(command._destination.Get(threaded).Base, command._srcLayer, command._dstLayer, command._srcLevel, command._dstLevel); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCreateViewCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCreateViewCommand.cs new file mode 100644 index 00000000..04fbb584 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCreateViewCommand.cs @@ -0,0 +1,30 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureCreateViewCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureCreateView; + private TableRef _texture; + private TableRef _destination; + private TextureCreateInfo _info; + private int _firstLayer; + private int _firstLevel; + + public void Set(TableRef texture, TableRef destination, TextureCreateInfo info, int firstLayer, int firstLevel) + { + _texture = texture; + _destination = destination; + _info = info; + _firstLayer = firstLayer; + _firstLevel = firstLevel; + } + + public static void Run(ref TextureCreateViewCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTexture source = command._texture.Get(threaded); + command._destination.Get(threaded).Base = source.Base.CreateView(command._info, command._firstLayer, command._firstLevel); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataCommand.cs new file mode 100644 index 00000000..fa0f4f7a --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureGetDataCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureGetData; + private TableRef _texture; + private TableRef>> _result; + + public void Set(TableRef texture, TableRef>> result) + { + _texture = texture; + _result = result; + } + + public static void Run(ref TextureGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + PinnedSpan result = command._texture.Get(threaded).Base.GetData(); + + command._result.Get(threaded).Result = result; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataSliceCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataSliceCommand.cs new file mode 100644 index 00000000..1cb92d66 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataSliceCommand.cs @@ -0,0 +1,29 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureGetDataSliceCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureGetDataSlice; + private TableRef _texture; + private TableRef>> _result; + private int _layer; + private int _level; + + public void Set(TableRef texture, TableRef>> result, int layer, int level) + { + _texture = texture; + _result = result; + _layer = layer; + _level = level; + } + + public static void Run(ref TextureGetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + PinnedSpan result = command._texture.Get(threaded).Base.GetData(command._layer, command._level); + + command._result.Get(threaded).Result = result; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureReleaseCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureReleaseCommand.cs new file mode 100644 index 00000000..c9727f1a --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureReleaseCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureReleaseCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureRelease; + private TableRef _texture; + + public void Set(TableRef texture) + { + _texture = texture; + } + + public static void Run(ref TextureReleaseCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._texture.Get(threaded).Base.Release(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs new file mode 100644 index 00000000..3aba004d --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System.Buffers; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureSetDataCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureSetData; + private TableRef _texture; + private TableRef> _data; + + public void Set(TableRef texture, TableRef> data) + { + _texture = texture; + _data = data; + } + + public static void Run(ref TextureSetDataCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTexture texture = command._texture.Get(threaded); + texture.Base.SetData(command._data.Get(threaded)); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs new file mode 100644 index 00000000..7ad709a7 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs @@ -0,0 +1,29 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System.Buffers; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureSetDataSliceCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureSetDataSlice; + private TableRef _texture; + private TableRef> _data; + private int _layer; + private int _level; + + public void Set(TableRef texture, TableRef> data, int layer, int level) + { + _texture = texture; + _data = data; + _layer = layer; + _level = level; + } + + public static void Run(ref TextureSetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTexture texture = command._texture.Get(threaded); + texture.Base.SetData(command._data.Get(threaded), command._layer, command._level); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs new file mode 100644 index 00000000..c211931b --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs @@ -0,0 +1,31 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System.Buffers; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureSetDataSliceRegionCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureSetDataSliceRegion; + private TableRef _texture; + private TableRef> _data; + private int _layer; + private int _level; + private Rectangle _region; + + public void Set(TableRef texture, TableRef> data, int layer, int level, Rectangle region) + { + _texture = texture; + _data = data; + _layer = layer; + _level = level; + _region = region; + } + + public static void Run(ref TextureSetDataSliceRegionCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTexture texture = command._texture.Get(threaded); + texture.Base.SetData(command._data.Get(threaded), command._layer, command._level, command._region); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetStorageCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetStorageCommand.cs new file mode 100644 index 00000000..ed53d105 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetStorageCommand.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture +{ + struct TextureSetStorageCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureSetStorage; + private TableRef _texture; + private BufferRange _storage; + + public void Set(TableRef texture, BufferRange storage) + { + _texture = texture; + _storage = storage; + } + + public static void Run(ref TextureSetStorageCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._texture.Get(threaded).Base.SetStorage(threaded.Buffers.MapBufferRange(command._storage)); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArrayDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArrayDisposeCommand.cs new file mode 100644 index 00000000..fec1c48f --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArrayDisposeCommand.cs @@ -0,0 +1,21 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray +{ + struct TextureArrayDisposeCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureArrayDispose; + private TableRef _textureArray; + + public void Set(TableRef textureArray) + { + _textureArray = textureArray; + } + + public static void Run(ref TextureArrayDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + command._textureArray.Get(threaded).Base.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetSamplersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetSamplersCommand.cs new file mode 100644 index 00000000..204ee32d --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetSamplersCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray +{ + struct TextureArraySetSamplersCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureArraySetSamplers; + private TableRef _textureArray; + private int _index; + private TableRef _samplers; + + public void Set(TableRef textureArray, int index, TableRef samplers) + { + _textureArray = textureArray; + _index = index; + _samplers = samplers; + } + + public static void Run(ref TextureArraySetSamplersCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTextureArray textureArray = command._textureArray.Get(threaded); + textureArray.Base.SetSamplers(command._index, command._samplers.Get(threaded).Select(sampler => ((ThreadedSampler)sampler)?.Base).ToArray()); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetTexturesCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetTexturesCommand.cs new file mode 100644 index 00000000..cc94d1b6 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetTexturesCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray +{ + struct TextureArraySetTexturesCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureArraySetTextures; + private TableRef _textureArray; + private int _index; + private TableRef _textures; + + public void Set(TableRef textureArray, int index, TableRef textures) + { + _textureArray = textureArray; + _index = index; + _textures = textures; + } + + public static void Run(ref TextureArraySetTexturesCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + ThreadedTextureArray textureArray = command._textureArray.Get(threaded); + textureArray.Base.SetTextures(command._index, command._textures.Get(threaded).Select(texture => ((ThreadedTexture)texture)?.Base).ToArray()); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierCommand.cs new file mode 100644 index 00000000..5ad8adfa --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct TextureBarrierCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureBarrier; + + public static void Run(ref TextureBarrierCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.TextureBarrier(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierTiledCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierTiledCommand.cs new file mode 100644 index 00000000..423a7efb --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierTiledCommand.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct TextureBarrierTiledCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TextureBarrierTiled; + + public static void Run(ref TextureBarrierTiledCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.TextureBarrierTiled(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingCommand.cs new file mode 100644 index 00000000..21e3a0d1 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct TryHostConditionalRenderingCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TryHostConditionalRendering; + private TableRef _value; + private ulong _compare; + private bool _isEqual; + + public void Set(TableRef value, ulong compare, bool isEqual) + { + _value = value; + _compare = compare; + _isEqual = isEqual; + } + + public static void Run(ref TryHostConditionalRenderingCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.TryHostConditionalRendering(command._value.Get(threaded)?.Base, command._compare, command._isEqual); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingFlushCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingFlushCommand.cs new file mode 100644 index 00000000..73acef6b --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingFlushCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands +{ + struct TryHostConditionalRenderingFlushCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.TryHostConditionalRenderingFlush; + private TableRef _value; + private TableRef _compare; + private bool _isEqual; + + public void Set(TableRef value, TableRef compare, bool isEqual) + { + _value = value; + _compare = compare; + _isEqual = isEqual; + } + + public static void Run(ref TryHostConditionalRenderingFlushCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + renderer.Pipeline.TryHostConditionalRendering(command._value.Get(threaded)?.Base, command._compare.Get(threaded)?.Base, command._isEqual); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs new file mode 100644 index 00000000..4034bc3c --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Window +{ + struct WindowPresentCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.WindowPresent; + private TableRef _texture; + private ImageCrop _crop; + private TableRef _swapBuffersCallback; + + public void Set(TableRef texture, ImageCrop crop, TableRef swapBuffersCallback) + { + _texture = texture; + _crop = crop; + _swapBuffersCallback = swapBuffersCallback; + } + + public static void Run(ref WindowPresentCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + threaded.SignalFrame(); + renderer.Window.Present(command._texture.Get(threaded)?.Base, command._crop, command._swapBuffersCallback.Get(threaded)); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Model/CircularSpanPool.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Model/CircularSpanPool.cs new file mode 100644 index 00000000..ec7ab159 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Model/CircularSpanPool.cs @@ -0,0 +1,89 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Graphics.GAL.Multithreading.Model +{ + /// + /// A memory pool for passing through Span resources with one producer and consumer. + /// Data is copied on creation to part of the pool, then that region is reserved until it is disposed by the consumer. + /// Similar to the command queue, this pool assumes that data is created and disposed in the same order. + /// + class CircularSpanPool + { + private readonly ThreadedRenderer _renderer; + private readonly byte[] _pool; + private readonly int _size; + + private int _producerPtr; + private int _producerSkipPosition = -1; + private int _consumerPtr; + + public CircularSpanPool(ThreadedRenderer renderer, int size) + { + _renderer = renderer; + _size = size; + _pool = new byte[size]; + } + + public SpanRef Insert(ReadOnlySpan data) where T : unmanaged + { + int size = data.Length * Unsafe.SizeOf(); + + // Wrapping aware circular queue. + // If there's no space at the end of the pool for this span, we can't fragment it. + // So just loop back around to the start. Remember the last skipped position. + + bool wraparound = _producerPtr + size >= _size; + int index = wraparound ? 0 : _producerPtr; + + // _consumerPtr is from another thread, and we're taking it without a lock, so treat this as a snapshot in the past. + // We know that it will always be before or equal to the producer pointer, and it cannot pass it. + // This is enough to reason about if there is space in the queue for the data, even if we're checking against an outdated value. + + int consumer = _consumerPtr; + bool beforeConsumer = _producerPtr < consumer; + + if (size > _size - 1 || (wraparound && beforeConsumer) || ((index < consumer || wraparound) && index + size >= consumer)) + { + // Just get an array in the following situations: + // - The data is too large to fit in the pool. + // - A wraparound would happen but the consumer would be covered by it. + // - The producer would catch up to the consumer as a result. + + return new SpanRef(_renderer, data.ToArray()); + } + + data.CopyTo(MemoryMarshal.Cast(new Span(_pool).Slice(index, size))); + + if (wraparound) + { + _producerSkipPosition = _producerPtr; + } + + _producerPtr = index + size; + + return new SpanRef(data.Length); + } + + public Span Get(int length) where T : unmanaged + { + int size = length * Unsafe.SizeOf(); + + if (_consumerPtr == Interlocked.CompareExchange(ref _producerSkipPosition, -1, _consumerPtr)) + { + _consumerPtr = 0; + } + + return MemoryMarshal.Cast(new Span(_pool).Slice(_consumerPtr, size)); + } + + public void Dispose(int length) where T : unmanaged + { + int size = length * Unsafe.SizeOf(); + + _consumerPtr += size; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Model/ResultBox.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Model/ResultBox.cs new file mode 100644 index 00000000..9e136de9 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Model/ResultBox.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Model +{ + public class ResultBox + { + public T Result; + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Model/SpanRef.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Model/SpanRef.cs new file mode 100644 index 00000000..faa496f1 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Model/SpanRef.cs @@ -0,0 +1,39 @@ +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Model +{ + readonly struct SpanRef where T : unmanaged + { + private readonly int _packedLengthId; + + public SpanRef(ThreadedRenderer renderer, T[] data) + { + _packedLengthId = -(renderer.AddTableRef(data) + 1); + } + + public SpanRef(int length) + { + _packedLengthId = length; + } + + public Span Get(ThreadedRenderer renderer) + { + if (_packedLengthId >= 0) + { + return renderer.SpanPool.Get(_packedLengthId); + } + else + { + return new Span((T[])renderer.RemoveTableRef(-(_packedLengthId + 1))); + } + } + + public void Dispose(ThreadedRenderer renderer) + { + if (_packedLengthId > 0) + { + renderer.SpanPool.Dispose(_packedLengthId); + } + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Model/TableRef.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Model/TableRef.cs new file mode 100644 index 00000000..d3a5a14e --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Model/TableRef.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Model +{ + readonly struct TableRef + { + private readonly int _index; + + public TableRef(ThreadedRenderer renderer, T reference) + { + _index = renderer.AddTableRef(reference); + } + + public T Get(ThreadedRenderer renderer) + { + return (T)renderer.RemoveTableRef(_index); + } + + public T2 GetAs(ThreadedRenderer renderer) where T2 : T + { + return (T2)renderer.RemoveTableRef(_index); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ProgramQueue.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ProgramQueue.cs new file mode 100644 index 00000000..cda3518c --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ProgramQueue.cs @@ -0,0 +1,107 @@ +using Ryujinx.Graphics.GAL.Multithreading.Resources.Programs; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + /// + /// A structure handling multithreaded compilation for programs. + /// + class ProgramQueue + { + private const int MaxConcurrentCompilations = 8; + + private readonly IRenderer _renderer; + + private readonly Queue _toCompile; + private readonly List _inProgress; + + public ProgramQueue(IRenderer renderer) + { + _renderer = renderer; + + _toCompile = new Queue(); + _inProgress = new List(); + } + + public void Add(IProgramRequest request) + { + lock (_toCompile) + { + _toCompile.Enqueue(request); + } + } + + public void ProcessQueue() + { + for (int i = 0; i < _inProgress.Count; i++) + { + ThreadedProgram program = _inProgress[i]; + + ProgramLinkStatus status = program.Base.CheckProgramLink(false); + + if (status != ProgramLinkStatus.Incomplete) + { + program.Compiled = true; + _inProgress.RemoveAt(i--); + } + } + + int freeSpace = MaxConcurrentCompilations - _inProgress.Count; + + for (int i = 0; i < freeSpace; i++) + { + // Begin compilation of some programs in the compile queue. + IProgramRequest program; + + lock (_toCompile) + { + if (!_toCompile.TryDequeue(out program)) + { + break; + } + } + + if (program.Threaded.Base != null) + { + ProgramLinkStatus status = program.Threaded.Base.CheckProgramLink(false); + + if (status != ProgramLinkStatus.Incomplete) + { + // This program is already compiled. Keep going through the queue. + program.Threaded.Compiled = true; + i--; + continue; + } + } + else + { + program.Threaded.Base = program.Create(_renderer); + } + + _inProgress.Add(program.Threaded); + } + } + + /// + /// Process the queue until the given program has finished compiling. + /// This will begin compilation of other programs on the queue as well. + /// + /// The program to wait for + public void WaitForProgram(ThreadedProgram program) + { + Span spinWait = stackalloc SpinWait[1]; + + while (!program.Compiled) + { + ProcessQueue(); + + if (!program.Compiled) + { + spinWait[0].SpinOnce(-1); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/BinaryProgramRequest.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/BinaryProgramRequest.cs new file mode 100644 index 00000000..6d250682 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/BinaryProgramRequest.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs +{ + class BinaryProgramRequest : IProgramRequest + { + public ThreadedProgram Threaded { get; set; } + + private readonly byte[] _data; + private readonly bool _hasFragmentShader; + private ShaderInfo _info; + + public BinaryProgramRequest(ThreadedProgram program, byte[] data, bool hasFragmentShader, ShaderInfo info) + { + Threaded = program; + + _data = data; + _hasFragmentShader = hasFragmentShader; + _info = info; + } + + public IProgram Create(IRenderer renderer) + { + return renderer.LoadProgramBinary(_data, _hasFragmentShader, _info); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/IProgramRequest.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/IProgramRequest.cs new file mode 100644 index 00000000..51fef9f5 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/IProgramRequest.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs +{ + interface IProgramRequest + { + ThreadedProgram Threaded { get; set; } + IProgram Create(IRenderer renderer); + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs new file mode 100644 index 00000000..9852f5c9 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.GAL.Multithreading.Resources.Programs +{ + class SourceProgramRequest : IProgramRequest + { + public ThreadedProgram Threaded { get; set; } + + private readonly ShaderSource[] _shaders; + private ShaderInfo _info; + + public SourceProgramRequest(ThreadedProgram program, ShaderSource[] shaders, ShaderInfo info) + { + Threaded = program; + + _shaders = shaders; + _info = info; + } + + public IProgram Create(IRenderer renderer) + { + return renderer.CreateProgram(_shaders, _info); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedCounterEvent.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedCounterEvent.cs new file mode 100644 index 00000000..15db2b9f --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedCounterEvent.cs @@ -0,0 +1,80 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent; +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System.Threading; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + class ThreadedCounterEvent : ICounterEvent + { + private readonly ThreadedRenderer _renderer; + public ICounterEvent Base; + + public bool Invalid { get; set; } + + public CounterType Type { get; } + public bool ClearCounter { get; } + + private bool _reserved; + private int _createLock; + + public ThreadedCounterEvent(ThreadedRenderer renderer, CounterType type, bool clearCounter) + { + _renderer = renderer; + Type = type; + ClearCounter = clearCounter; + } + + private TableRef Ref(T reference) + { + return new TableRef(_renderer, reference); + } + + public void Dispose() + { + _renderer.New().Set(Ref(this)); + _renderer.QueueCommand(); + } + + public void Flush() + { + ThreadedHelpers.SpinUntilNonNull(ref Base); + + Base.Flush(); + } + + public bool ReserveForHostAccess() + { + if (Base != null) + { + return Base.ReserveForHostAccess(); + } + else + { + bool result = true; + + // A very light lock, as this case is uncommon. + ThreadedHelpers.SpinUntilExchange(ref _createLock, 1, 0); + + if (Base != null) + { + result = Base.ReserveForHostAccess(); + } + else + { + _reserved = true; + } + + Volatile.Write(ref _createLock, 0); + + return result; + } + } + + public void Create(IRenderer renderer, CounterType type, System.EventHandler eventHandler, float divisor, bool hostReserved) + { + ThreadedHelpers.SpinUntilExchange(ref _createLock, 1, 0); + Base = renderer.ReportCounter(type, eventHandler, divisor, hostReserved || _reserved); + Volatile.Write(ref _createLock, 0); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs new file mode 100644 index 00000000..19bc6f23 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs @@ -0,0 +1,42 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray; +using Ryujinx.Graphics.GAL.Multithreading.Model; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + /// + /// Threaded representation of a image array. + /// + class ThreadedImageArray : IImageArray + { + private readonly ThreadedRenderer _renderer; + public IImageArray Base; + + public ThreadedImageArray(ThreadedRenderer renderer) + { + _renderer = renderer; + } + + private TableRef Ref(T reference) + { + return new TableRef(_renderer, reference); + } + + public void Dispose() + { + _renderer.New().Set(Ref(this)); + _renderer.QueueCommand(); + } + + public void SetFormats(int index, Format[] imageFormats) + { + _renderer.New().Set(Ref(this), index, Ref(imageFormats)); + _renderer.QueueCommand(); + } + + public void SetImages(int index, ITexture[] images) + { + _renderer.New().Set(Ref(this), index, Ref(images)); + _renderer.QueueCommand(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedProgram.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedProgram.cs new file mode 100644 index 00000000..d5a22f66 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedProgram.cs @@ -0,0 +1,48 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.Program; +using Ryujinx.Graphics.GAL.Multithreading.Model; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + class ThreadedProgram : IProgram + { + private readonly ThreadedRenderer _renderer; + + public IProgram Base; + + internal bool Compiled; + + public ThreadedProgram(ThreadedRenderer renderer) + { + _renderer = renderer; + } + + private TableRef Ref(T reference) + { + return new TableRef(_renderer, reference); + } + + public void Dispose() + { + _renderer.New().Set(Ref(this)); + _renderer.QueueCommand(); + } + + public byte[] GetBinary() + { + ResultBox box = new(); + _renderer.New().Set(Ref(this), Ref(box)); + _renderer.InvokeCommand(); + + return box.Result; + } + + public ProgramLinkStatus CheckProgramLink(bool blocking) + { + ResultBox box = new(); + _renderer.New().Set(Ref(this), blocking, Ref(box)); + _renderer.InvokeCommand(); + + return box.Result; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedSampler.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedSampler.cs new file mode 100644 index 00000000..ae3d993a --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedSampler.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler; +using Ryujinx.Graphics.GAL.Multithreading.Model; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + class ThreadedSampler : ISampler + { + private readonly ThreadedRenderer _renderer; + public ISampler Base; + + public ThreadedSampler(ThreadedRenderer renderer) + { + _renderer = renderer; + } + + public void Dispose() + { + _renderer.New().Set(new TableRef(_renderer, this)); + _renderer.QueueCommand(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs new file mode 100644 index 00000000..80003b84 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs @@ -0,0 +1,146 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System.Buffers; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + /// + /// Threaded representation of a texture. + /// + class ThreadedTexture : ITexture + { + private readonly ThreadedRenderer _renderer; + private readonly TextureCreateInfo _info; + public ITexture Base; + + public int Width => _info.Width; + + public int Height => _info.Height; + + public ThreadedTexture(ThreadedRenderer renderer, TextureCreateInfo info) + { + _renderer = renderer; + _info = info; + } + + private TableRef Ref(T reference) + { + return new TableRef(_renderer, reference); + } + + public void CopyTo(ITexture destination, int firstLayer, int firstLevel) + { + _renderer.New().Set(Ref(this), Ref((ThreadedTexture)destination), firstLayer, firstLevel); + _renderer.QueueCommand(); + } + + public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel) + { + _renderer.New().Set(Ref(this), Ref((ThreadedTexture)destination), srcLayer, dstLayer, srcLevel, dstLevel); + _renderer.QueueCommand(); + } + + public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter) + { + ThreadedTexture dest = (ThreadedTexture)destination; + + if (_renderer.IsGpuThread()) + { + _renderer.New().Set(Ref(this), Ref(dest), srcRegion, dstRegion, linearFilter); + _renderer.QueueCommand(); + } + else + { + // Scaled copy can happen on another thread for a res scale flush. + ThreadedHelpers.SpinUntilNonNull(ref Base); + ThreadedHelpers.SpinUntilNonNull(ref dest.Base); + + Base.CopyTo(dest.Base, srcRegion, dstRegion, linearFilter); + } + } + + public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + ThreadedTexture newTex = new(_renderer, info); + _renderer.New().Set(Ref(this), Ref(newTex), info, firstLayer, firstLevel); + _renderer.QueueCommand(); + + return newTex; + } + + public PinnedSpan GetData() + { + if (_renderer.IsGpuThread()) + { + ResultBox> box = new(); + _renderer.New().Set(Ref(this), Ref(box)); + _renderer.InvokeCommand(); + + return box.Result; + } + else + { + ThreadedHelpers.SpinUntilNonNull(ref Base); + + return Base.GetData(); + } + } + + public PinnedSpan GetData(int layer, int level) + { + if (_renderer.IsGpuThread()) + { + ResultBox> box = new(); + _renderer.New().Set(Ref(this), Ref(box), layer, level); + _renderer.InvokeCommand(); + + return box.Result; + } + else + { + ThreadedHelpers.SpinUntilNonNull(ref Base); + + return Base.GetData(layer, level); + } + } + + public void CopyTo(BufferRange range, int layer, int level, int stride) + { + _renderer.New().Set(Ref(this), range, layer, level, stride); + _renderer.QueueCommand(); + } + + /// + public void SetData(IMemoryOwner data) + { + _renderer.New().Set(Ref(this), Ref(data)); + _renderer.QueueCommand(); + } + + /// + public void SetData(IMemoryOwner data, int layer, int level) + { + _renderer.New().Set(Ref(this), Ref(data), layer, level); + _renderer.QueueCommand(); + } + + /// + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) + { + _renderer.New().Set(Ref(this), Ref(data), layer, level, region); + _renderer.QueueCommand(); + } + + public void SetStorage(BufferRange buffer) + { + _renderer.New().Set(Ref(this), buffer); + _renderer.QueueCommand(); + } + + public void Release() + { + _renderer.New().Set(Ref(this)); + _renderer.QueueCommand(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs new file mode 100644 index 00000000..4334c704 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs @@ -0,0 +1,43 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray; +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading.Resources +{ + /// + /// Threaded representation of a texture and sampler array. + /// + class ThreadedTextureArray : ITextureArray + { + private readonly ThreadedRenderer _renderer; + public ITextureArray Base; + + public ThreadedTextureArray(ThreadedRenderer renderer) + { + _renderer = renderer; + } + + private TableRef Ref(T reference) + { + return new TableRef(_renderer, reference); + } + + public void Dispose() + { + _renderer.New().Set(Ref(this)); + _renderer.QueueCommand(); + } + + public void SetSamplers(int index, ISampler[] samplers) + { + _renderer.New().Set(Ref(this), index, Ref(samplers.ToArray())); + _renderer.QueueCommand(); + } + + public void SetTextures(int index, ITexture[] textures) + { + _renderer.New().Set(Ref(this), index, Ref(textures.ToArray())); + _renderer.QueueCommand(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/SyncMap.cs b/src/Ryujinx.Graphics.GAL/Multithreading/SyncMap.cs new file mode 100644 index 00000000..ecdff492 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/SyncMap.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.GAL.Multithreading +{ + class SyncMap : IDisposable + { + private readonly HashSet _inFlight = new(); + private readonly AutoResetEvent _inFlightChanged = new(false); + + internal void CreateSyncHandle(ulong id) + { + lock (_inFlight) + { + _inFlight.Add(id); + } + } + + internal void AssignSync(ulong id) + { + lock (_inFlight) + { + _inFlight.Remove(id); + } + + _inFlightChanged.Set(); + } + + internal void WaitSyncAvailability(ulong id) + { + // Blocks until the handle is available. + + bool signal = false; + + while (true) + { + lock (_inFlight) + { + if (!_inFlight.Contains(id)) + { + break; + } + } + + _inFlightChanged.WaitOne(); + signal = true; + } + + if (signal) + { + // Signal other threads which might still be waiting. + _inFlightChanged.Set(); + } + } + + public void Dispose() + { + _inFlightChanged.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedHelpers.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedHelpers.cs new file mode 100644 index 00000000..dcd0c5b1 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedHelpers.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading; + +namespace Ryujinx.Graphics.GAL.Multithreading +{ + static class ThreadedHelpers + { + public static void SpinUntilNonNull(ref T obj) where T : class + { + Span spinWait = stackalloc SpinWait[1]; + + while (obj == null) + { + spinWait[0].SpinOnce(-1); + } + } + + public static void SpinUntilExchange(ref int target, int value, int comparand) + { + Span spinWait = stackalloc SpinWait[1]; + + while (Interlocked.CompareExchange(ref target, value, comparand) != comparand) + { + spinWait[0].SpinOnce(-1); + } + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs new file mode 100644 index 00000000..edd79d8a --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs @@ -0,0 +1,390 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands; +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.Shader; +using System; +using System.Linq; + +namespace Ryujinx.Graphics.GAL.Multithreading +{ + public class ThreadedPipeline : IPipeline + { + private readonly ThreadedRenderer _renderer; + + public ThreadedPipeline(ThreadedRenderer renderer) + { + _renderer = renderer; + } + + private TableRef Ref(T reference) + { + return new TableRef(_renderer, reference); + } + + public void Barrier() + { + _renderer.New(); + _renderer.QueueCommand(); + } + + public void BeginTransformFeedback(PrimitiveTopology topology) + { + _renderer.New().Set(topology); + _renderer.QueueCommand(); + } + + public void ClearBuffer(BufferHandle destination, int offset, int size, uint value) + { + _renderer.New().Set(destination, offset, size, value); + _renderer.QueueCommand(); + } + + public void ClearRenderTargetColor(int index, int layer, int layerCount, uint componentMask, ColorF color) + { + _renderer.New().Set(index, layer, layerCount, componentMask, color); + _renderer.QueueCommand(); + } + + public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask) + { + _renderer.New().Set(layer, layerCount, depthValue, depthMask, stencilValue, stencilMask); + _renderer.QueueCommand(); + } + + public void CommandBufferBarrier() + { + _renderer.New(); + _renderer.QueueCommand(); + } + + public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size) + { + _renderer.New().Set(source, destination, srcOffset, dstOffset, size); + _renderer.QueueCommand(); + } + + public void DispatchCompute(int groupsX, int groupsY, int groupsZ) + { + _renderer.New().Set(groupsX, groupsY, groupsZ); + _renderer.QueueCommand(); + } + + public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance) + { + _renderer.New().Set(vertexCount, instanceCount, firstVertex, firstInstance); + _renderer.QueueCommand(); + } + + public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance) + { + _renderer.New().Set(indexCount, instanceCount, firstIndex, firstVertex, firstInstance); + _renderer.QueueCommand(); + } + + public void DrawIndexedIndirect(BufferRange indirectBuffer) + { + _renderer.New().Set(indirectBuffer); + _renderer.QueueCommand(); + } + + public void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + _renderer.New().Set(indirectBuffer, parameterBuffer, maxDrawCount, stride); + _renderer.QueueCommand(); + } + + public void DrawIndirect(BufferRange indirectBuffer) + { + _renderer.New().Set(indirectBuffer); + _renderer.QueueCommand(); + } + + public void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + _renderer.New().Set(indirectBuffer, parameterBuffer, maxDrawCount, stride); + _renderer.QueueCommand(); + } + + public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion) + { + _renderer.New().Set(Ref(texture), Ref(sampler), srcRegion, dstRegion); + _renderer.QueueCommand(); + } + + public void EndHostConditionalRendering() + { + _renderer.New(); + _renderer.QueueCommand(); + } + + public void EndTransformFeedback() + { + _renderer.New(); + _renderer.QueueCommand(); + } + + public void SetAlphaTest(bool enable, float reference, CompareOp op) + { + _renderer.New().Set(enable, reference, op); + _renderer.QueueCommand(); + } + + public void SetBlendState(AdvancedBlendDescriptor blend) + { + _renderer.New().Set(blend); + _renderer.QueueCommand(); + } + + public void SetBlendState(int index, BlendDescriptor blend) + { + _renderer.New().Set(index, blend); + _renderer.QueueCommand(); + } + + public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp) + { + _renderer.New().Set(enables, factor, units, clamp); + _renderer.QueueCommand(); + } + + public void SetDepthClamp(bool clamp) + { + _renderer.New().Set(clamp); + _renderer.QueueCommand(); + } + + public void SetDepthMode(DepthMode mode) + { + _renderer.New().Set(mode); + _renderer.QueueCommand(); + } + + public void SetDepthTest(DepthTestDescriptor depthTest) + { + _renderer.New().Set(depthTest); + _renderer.QueueCommand(); + } + + public void SetFaceCulling(bool enable, Face face) + { + _renderer.New().Set(enable, face); + _renderer.QueueCommand(); + } + + public void SetFrontFace(FrontFace frontFace) + { + _renderer.New().Set(frontFace); + _renderer.QueueCommand(); + } + + public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat) + { + _renderer.New().Set(stage, binding, Ref(texture), imageFormat); + _renderer.QueueCommand(); + } + + public void SetImageArray(ShaderStage stage, int binding, IImageArray array) + { + _renderer.New().Set(stage, binding, Ref(array)); + _renderer.QueueCommand(); + } + + public void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array) + { + _renderer.New().Set(stage, setIndex, Ref(array)); + _renderer.QueueCommand(); + } + + public void SetIndexBuffer(BufferRange buffer, IndexType type) + { + _renderer.New().Set(buffer, type); + _renderer.QueueCommand(); + } + + public void SetLineParameters(float width, bool smooth) + { + _renderer.New().Set(width, smooth); + _renderer.QueueCommand(); + } + + public void SetLogicOpState(bool enable, LogicalOp op) + { + _renderer.New().Set(enable, op); + _renderer.QueueCommand(); + } + + public void SetMultisampleState(MultisampleDescriptor multisample) + { + _renderer.New().Set(multisample); + _renderer.QueueCommand(); + } + + public void SetPatchParameters(int vertices, ReadOnlySpan defaultOuterLevel, ReadOnlySpan defaultInnerLevel) + { + _renderer.New().Set(vertices, defaultOuterLevel, defaultInnerLevel); + _renderer.QueueCommand(); + } + + public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin) + { + _renderer.New().Set(size, isProgramPointSize, enablePointSprite, origin); + _renderer.QueueCommand(); + } + + public void SetPolygonMode(PolygonMode frontMode, PolygonMode backMode) + { + _renderer.New().Set(frontMode, backMode); + _renderer.QueueCommand(); + } + + public void SetPrimitiveRestart(bool enable, int index) + { + _renderer.New().Set(enable, index); + _renderer.QueueCommand(); + } + + public void SetPrimitiveTopology(PrimitiveTopology topology) + { + _renderer.New().Set(topology); + _renderer.QueueCommand(); + } + + public void SetProgram(IProgram program) + { + _renderer.New().Set(Ref(program)); + _renderer.QueueCommand(); + } + + public void SetRasterizerDiscard(bool discard) + { + _renderer.New().Set(discard); + _renderer.QueueCommand(); + } + + public void SetRenderTargetColorMasks(ReadOnlySpan componentMask) + { + _renderer.New().Set(_renderer.CopySpan(componentMask)); + _renderer.QueueCommand(); + } + + public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) + { + _renderer.New().Set(Ref(colors.ToArray()), Ref(depthStencil)); + _renderer.QueueCommand(); + } + + public void SetScissors(ReadOnlySpan> scissors) + { + _renderer.New().Set(_renderer.CopySpan(scissors)); + _renderer.QueueCommand(); + } + + public void SetStencilTest(StencilTestDescriptor stencilTest) + { + _renderer.New().Set(stencilTest); + _renderer.QueueCommand(); + } + + public void SetStorageBuffers(ReadOnlySpan buffers) + { + _renderer.New().Set(_renderer.CopySpan(buffers)); + _renderer.QueueCommand(); + } + + public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler) + { + _renderer.New().Set(stage, binding, Ref(texture), Ref(sampler)); + _renderer.QueueCommand(); + } + + public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array) + { + _renderer.New().Set(stage, binding, Ref(array)); + _renderer.QueueCommand(); + } + + public void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array) + { + _renderer.New().Set(stage, setIndex, Ref(array)); + _renderer.QueueCommand(); + } + + public void SetTransformFeedbackBuffers(ReadOnlySpan buffers) + { + _renderer.New().Set(_renderer.CopySpan(buffers)); + _renderer.QueueCommand(); + } + + public void SetUniformBuffers(ReadOnlySpan buffers) + { + _renderer.New().Set(_renderer.CopySpan(buffers)); + _renderer.QueueCommand(); + } + + public void SetUserClipDistance(int index, bool enableClip) + { + _renderer.New().Set(index, enableClip); + _renderer.QueueCommand(); + } + + public void SetVertexAttribs(ReadOnlySpan vertexAttribs) + { + _renderer.New().Set(_renderer.CopySpan(vertexAttribs)); + _renderer.QueueCommand(); + } + + public void SetVertexBuffers(ReadOnlySpan vertexBuffers) + { + _renderer.New().Set(_renderer.CopySpan(vertexBuffers)); + _renderer.QueueCommand(); + } + + public void SetViewports(ReadOnlySpan viewports) + { + _renderer.New().Set(_renderer.CopySpan(viewports)); + _renderer.QueueCommand(); + } + + public void TextureBarrier() + { + _renderer.New(); + _renderer.QueueCommand(); + } + + public void TextureBarrierTiled() + { + _renderer.New(); + _renderer.QueueCommand(); + } + + public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual) + { + var evt = value as ThreadedCounterEvent; + if (evt != null) + { + if (compare == 0 && evt.Type == CounterType.SamplesPassed && evt.ClearCounter) + { + if (!evt.ReserveForHostAccess()) + { + return false; + } + + _renderer.New().Set(Ref(evt), compare, isEqual); + _renderer.QueueCommand(); + return true; + } + } + + _renderer.New().Set(Ref(evt), Ref(null), isEqual); + _renderer.QueueCommand(); + return false; + } + + public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual) + { + _renderer.New().Set(Ref(value as ThreadedCounterEvent), Ref(compare as ThreadedCounterEvent), isEqual); + _renderer.QueueCommand(); + return false; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs new file mode 100644 index 00000000..cc3d2e5c --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs @@ -0,0 +1,546 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Graphics.GAL.Multithreading.Commands; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer; +using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer; +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using Ryujinx.Graphics.GAL.Multithreading.Resources.Programs; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Graphics.GAL.Multithreading +{ + /// + /// The ThreadedRenderer is a layer that can be put in front of any Renderer backend to make + /// its processing happen on a separate thread, rather than intertwined with the GPU emulation. + /// A new thread is created to handle the GPU command processing, separate from the renderer thread. + /// Calls to the renderer, pipeline and resources are queued to happen on the renderer thread. + /// + public class ThreadedRenderer : IRenderer + { + private const int SpanPoolBytes = 4 * 1024 * 1024; + private const int MaxRefsPerCommand = 2; + private const int QueueCount = 10000; + + private readonly int _elementSize; + private readonly IRenderer _baseRenderer; + private Thread _gpuThread; + private Thread _backendThread; + private bool _running; + + private readonly AutoResetEvent _frameComplete = new(true); + + private readonly ManualResetEventSlim _galWorkAvailable; + private readonly CircularSpanPool _spanPool; + + private readonly ManualResetEventSlim _invokeRun; + private readonly AutoResetEvent _interruptRun; + + private bool _lastSampleCounterClear = true; + + private readonly byte[] _commandQueue; + private readonly object[] _refQueue; + + private int _consumerPtr; + private int _commandCount; + + private int _producerPtr; + private int _lastProducedPtr; + private int _invokePtr; + + private int _refProducerPtr; + private int _refConsumerPtr; + + private Action _interruptAction; + private readonly object _interruptLock = new(); + + public event EventHandler ScreenCaptured; + + internal BufferMap Buffers { get; } + internal SyncMap Sync { get; } + internal CircularSpanPool SpanPool { get; } + internal ProgramQueue Programs { get; } + + public IPipeline Pipeline { get; } + public IWindow Window { get; } + + public IRenderer BaseRenderer => _baseRenderer; + + public bool PreferThreading => _baseRenderer.PreferThreading; + + public ThreadedRenderer(IRenderer renderer) + { + _baseRenderer = renderer; + + renderer.ScreenCaptured += (sender, info) => ScreenCaptured?.Invoke(this, info); + renderer.SetInterruptAction(Interrupt); + + Pipeline = new ThreadedPipeline(this); + Window = new ThreadedWindow(this, renderer); + Buffers = new BufferMap(); + Sync = new SyncMap(); + Programs = new ProgramQueue(renderer); + + _galWorkAvailable = new ManualResetEventSlim(false); + _invokeRun = new ManualResetEventSlim(); + _interruptRun = new AutoResetEvent(false); + _spanPool = new CircularSpanPool(this, SpanPoolBytes); + SpanPool = _spanPool; + + _elementSize = BitUtils.AlignUp(CommandHelper.GetMaxCommandSize(), 4); + + _commandQueue = new byte[_elementSize * QueueCount]; + _refQueue = new object[MaxRefsPerCommand * QueueCount]; + } + + public void RunLoop(ThreadStart gpuLoop) + { + _running = true; + + _backendThread = Thread.CurrentThread; + + _gpuThread = new Thread(gpuLoop) + { + Name = "GPU.MainThread", + }; + + _gpuThread.Start(); + + RenderLoop(); + } + + public void RenderLoop() + { + // Power through the render queue until the Gpu thread work is done. + + while (_running) + { + _galWorkAvailable.Wait(); + _galWorkAvailable.Reset(); + + if (Volatile.Read(ref _interruptAction) != null) + { + _interruptAction(); + _interruptRun.Set(); + + Interlocked.Exchange(ref _interruptAction, null); + } + + // The other thread can only increase the command count. + // We can assume that if it is above 0, it will stay there or get higher. + + while (Volatile.Read(ref _commandCount) > 0 && Volatile.Read(ref _interruptAction) == null) + { + int commandPtr = _consumerPtr; + + Span command = new(_commandQueue, commandPtr * _elementSize, _elementSize); + + // Run the command. + + CommandHelper.RunCommand(command, this, _baseRenderer); + + if (Interlocked.CompareExchange(ref _invokePtr, -1, commandPtr) == commandPtr) + { + _invokeRun.Set(); + } + + _consumerPtr = (_consumerPtr + 1) % QueueCount; + + Interlocked.Decrement(ref _commandCount); + } + } + } + + internal SpanRef CopySpan(ReadOnlySpan data) where T : unmanaged + { + return _spanPool.Insert(data); + } + + private TableRef Ref(T reference) + { + return new TableRef(this, reference); + } + + internal ref T New() where T : struct + { + while (_producerPtr == (Volatile.Read(ref _consumerPtr) + QueueCount - 1) % QueueCount) + { + // If incrementing the producer pointer would overflow, we need to wait. + // _consumerPtr can only move forward, so there's no race to worry about here. + + Thread.Sleep(1); + } + + int taken = _producerPtr; + _lastProducedPtr = taken; + + _producerPtr = (_producerPtr + 1) % QueueCount; + + Span memory = new(_commandQueue, taken * _elementSize, _elementSize); + ref T result = ref Unsafe.As(ref MemoryMarshal.GetReference(memory)); + + memory[^1] = (byte)((IGALCommand)result).CommandType; + + return ref result; + } + + internal int AddTableRef(object obj) + { + // The reference table is sized so that it will never overflow, so long as the references are taken after the command is allocated. + + int index = _refProducerPtr; + + _refQueue[index] = obj; + + _refProducerPtr = (_refProducerPtr + 1) % _refQueue.Length; + + return index; + } + + internal object RemoveTableRef(int index) + { + Debug.Assert(index == _refConsumerPtr); + + object result = _refQueue[_refConsumerPtr]; + _refQueue[_refConsumerPtr] = null; + + _refConsumerPtr = (_refConsumerPtr + 1) % _refQueue.Length; + + return result; + } + + internal void QueueCommand() + { + int result = Interlocked.Increment(ref _commandCount); + + if (result == 1) + { + _galWorkAvailable.Set(); + } + } + + internal void InvokeCommand() + { + _invokeRun.Reset(); + _invokePtr = _lastProducedPtr; + + QueueCommand(); + + // Wait for the command to complete. + _invokeRun.Wait(); + } + + internal void WaitForFrame() + { + _frameComplete.WaitOne(); + } + + internal void SignalFrame() + { + _frameComplete.Set(); + } + + internal bool IsGpuThread() + { + return Thread.CurrentThread == _gpuThread; + } + + public void BackgroundContextAction(Action action, bool alwaysBackground = false) + { + if (IsGpuThread() && !alwaysBackground) + { + // The action must be performed on the render thread. + New().Set(Ref(action)); + InvokeCommand(); + } + else + { + _baseRenderer.BackgroundContextAction(action, true); + } + } + + public BufferHandle CreateBuffer(int size, BufferAccess access) + { + BufferHandle handle = Buffers.CreateBufferHandle(); + New().Set(handle, size, access); + QueueCommand(); + + return handle; + } + + public BufferHandle CreateBuffer(nint pointer, int size) + { + BufferHandle handle = Buffers.CreateBufferHandle(); + New().Set(handle, pointer, size); + QueueCommand(); + + return handle; + } + + public BufferHandle CreateBufferSparse(ReadOnlySpan storageBuffers) + { + BufferHandle handle = Buffers.CreateBufferHandle(); + New().Set(handle, CopySpan(storageBuffers)); + QueueCommand(); + + return handle; + } + + public IImageArray CreateImageArray(int size, bool isBuffer) + { + var imageArray = new ThreadedImageArray(this); + New().Set(Ref(imageArray), size, isBuffer); + QueueCommand(); + + return imageArray; + } + + public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info) + { + var program = new ThreadedProgram(this); + + SourceProgramRequest request = new(program, shaders, info); + + Programs.Add(request); + + New().Set(Ref((IProgramRequest)request)); + QueueCommand(); + + return program; + } + + public ISampler CreateSampler(SamplerCreateInfo info) + { + var sampler = new ThreadedSampler(this); + New().Set(Ref(sampler), info); + QueueCommand(); + + return sampler; + } + + public void CreateSync(ulong id, bool strict) + { + Sync.CreateSyncHandle(id); + New().Set(id, strict); + QueueCommand(); + } + + public ITexture CreateTexture(TextureCreateInfo info) + { + if (IsGpuThread()) + { + var texture = new ThreadedTexture(this, info); + New().Set(Ref(texture), info); + QueueCommand(); + + return texture; + } + else + { + var texture = new ThreadedTexture(this, info) + { + Base = _baseRenderer.CreateTexture(info), + }; + + return texture; + } + } + public ITextureArray CreateTextureArray(int size, bool isBuffer) + { + var textureArray = new ThreadedTextureArray(this); + New().Set(Ref(textureArray), size, isBuffer); + QueueCommand(); + + return textureArray; + } + + public void DeleteBuffer(BufferHandle buffer) + { + New().Set(buffer); + QueueCommand(); + } + + public PinnedSpan GetBufferData(BufferHandle buffer, int offset, int size) + { + if (IsGpuThread()) + { + ResultBox> box = new(); + New().Set(buffer, offset, size, Ref(box)); + InvokeCommand(); + + return box.Result; + } + else + { + return _baseRenderer.GetBufferData(Buffers.MapBufferBlocking(buffer), offset, size); + } + } + + public Capabilities GetCapabilities() + { + ResultBox box = new(); + New().Set(Ref(box)); + InvokeCommand(); + + return box.Result; + } + + public ulong GetCurrentSync() + { + return _baseRenderer.GetCurrentSync(); + } + + public HardwareInfo GetHardwareInfo() + { + return _baseRenderer.GetHardwareInfo(); + } + + /// + /// Initialize the base renderer. Must be called on the render thread. + /// + /// Log level to use + public void Initialize(GraphicsDebugLevel logLevel) + { + _baseRenderer.Initialize(logLevel); + } + + public IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info) + { + var program = new ThreadedProgram(this); + + BinaryProgramRequest request = new(program, programBinary, hasFragmentShader, info); + Programs.Add(request); + + New().Set(Ref((IProgramRequest)request)); + QueueCommand(); + + return program; + } + + public void PreFrame() + { + New(); + QueueCommand(); + } + + public ICounterEvent ReportCounter(CounterType type, EventHandler resultHandler, float divisor, bool hostReserved) + { + ThreadedCounterEvent evt = new(this, type, _lastSampleCounterClear); + New().Set(Ref(evt), type, Ref(resultHandler), divisor, hostReserved); + QueueCommand(); + + if (type == CounterType.SamplesPassed) + { + _lastSampleCounterClear = false; + } + + return evt; + } + + public void ResetCounter(CounterType type) + { + New().Set(type); + QueueCommand(); + _lastSampleCounterClear = true; + } + + public void Screenshot() + { + _baseRenderer.Screenshot(); + } + + public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan data) + { + New().Set(buffer, offset, CopySpan(data)); + QueueCommand(); + } + + public void UpdateCounters() + { + New(); + QueueCommand(); + } + + public void WaitSync(ulong id) + { + Sync.WaitSyncAvailability(id); + + _baseRenderer.WaitSync(id); + } + + private void Interrupt(Action action) + { + // Interrupt the backend thread from any external thread and invoke the given action. + + if (Thread.CurrentThread == _backendThread) + { + // If this is called from the backend thread, the action can run immediately. + action(); + } + else + { + lock (_interruptLock) + { + while (Interlocked.CompareExchange(ref _interruptAction, action, null) != null) + { + } + + _galWorkAvailable.Set(); + + _interruptRun.WaitOne(); + } + } + } + + public void SetInterruptAction(Action interruptAction) + { + // Threaded renderer ignores given interrupt action, as it provides its own to the child renderer. + } + + public bool PrepareHostMapping(nint address, ulong size) + { + return _baseRenderer.PrepareHostMapping(address, size); + } + + public void FlushThreadedCommands() + { + SpinWait wait = new(); + + while (Volatile.Read(ref _commandCount) > 0) + { + wait.SpinOnce(); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + // Dispose must happen from the render thread, after all commands have completed. + + // Stop the GPU thread. + _running = false; + _galWorkAvailable.Set(); + + if (_gpuThread != null && _gpuThread.IsAlive) + { + _gpuThread.Join(); + } + + // Dispose the renderer. + _baseRenderer.Dispose(); + + // Dispose events. + _frameComplete.Dispose(); + _galWorkAvailable.Dispose(); + _invokeRun.Dispose(); + _interruptRun.Dispose(); + + Sync.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs new file mode 100644 index 00000000..acda37ef --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs @@ -0,0 +1,44 @@ +using Ryujinx.Graphics.GAL.Multithreading.Commands.Window; +using Ryujinx.Graphics.GAL.Multithreading.Model; +using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading +{ + public class ThreadedWindow : IWindow + { + private readonly ThreadedRenderer _renderer; + private readonly IRenderer _impl; + + public ThreadedWindow(ThreadedRenderer renderer, IRenderer impl) + { + _renderer = renderer; + _impl = impl; + } + + public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback) + { + // If there's already a frame in the pipeline, wait for it to be presented first. + // This is a multithread rate limit - we can't be more than one frame behind the command queue. + + _renderer.WaitForFrame(); + _renderer.New().Set(new TableRef(_renderer, texture as ThreadedTexture), crop, new TableRef(_renderer, swapBuffersCallback)); + _renderer.QueueCommand(); + } + + public void SetSize(int width, int height) + { + _impl.Window.SetSize(width, height); + } + + public void ChangeVSyncMode(bool vsyncEnabled) { } + + public void SetAntiAliasing(AntiAliasing effect) { } + + public void SetScalingFilter(ScalingFilter type) { } + + public void SetScalingFilterLevel(float level) { } + + public void SetColorSpacePassthrough(bool colorSpacePassthroughEnabled) { } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Origin.cs b/src/Ryujinx.Graphics.GAL/Origin.cs new file mode 100644 index 00000000..9d336326 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Origin.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum Origin + { + UpperLeft, + LowerLeft, + } +} diff --git a/src/Ryujinx.Graphics.GAL/PinnedSpan.cs b/src/Ryujinx.Graphics.GAL/PinnedSpan.cs new file mode 100644 index 00000000..e2b33f44 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/PinnedSpan.cs @@ -0,0 +1,53 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.GAL +{ + public readonly unsafe struct PinnedSpan : IDisposable where T : unmanaged + { + private readonly void* _ptr; + private readonly int _size; + private readonly Action _disposeAction; + + /// + /// Creates a new PinnedSpan from an existing ReadOnlySpan. The span *must* be pinned in memory. + /// The data must be guaranteed to live until disposeAction is called. + /// + /// Existing span + /// Action to call on dispose + /// + /// If a dispose action is not provided, it is safe to assume the resource will be available until the next call. + /// + public static PinnedSpan UnsafeFromSpan(ReadOnlySpan span, Action disposeAction = null) + { + return new PinnedSpan(Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)), span.Length, disposeAction); + } + + /// + /// Creates a new PinnedSpan from an existing unsafe region. The data must be guaranteed to live until disposeAction is called. + /// + /// Pointer to the region + /// The total items of T the region contains + /// Action to call on dispose + /// + /// If a dispose action is not provided, it is safe to assume the resource will be available until the next call. + /// + public PinnedSpan(void* ptr, int size, Action disposeAction = null) + { + _ptr = ptr; + _size = size; + _disposeAction = disposeAction; + } + + public ReadOnlySpan Get() + { + return new ReadOnlySpan(_ptr, _size * Unsafe.SizeOf()); + } + + public void Dispose() + { + _disposeAction?.Invoke(); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/PolygonMode.cs b/src/Ryujinx.Graphics.GAL/PolygonMode.cs new file mode 100644 index 00000000..841a6f22 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/PolygonMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum PolygonMode + { + Point = 0x1b00, + Line = 0x1b01, + Fill = 0x1b02, + } +} diff --git a/src/Ryujinx.Graphics.GAL/PolygonModeMask.cs b/src/Ryujinx.Graphics.GAL/PolygonModeMask.cs new file mode 100644 index 00000000..c6ac4509 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/PolygonModeMask.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.Graphics.GAL +{ + [Flags] + public enum PolygonModeMask + { + Point = 1 << 0, + Line = 1 << 1, + Fill = 1 << 2, + } +} diff --git a/src/Ryujinx.Graphics.GAL/PrimitiveTopology.cs b/src/Ryujinx.Graphics.GAL/PrimitiveTopology.cs new file mode 100644 index 00000000..91e6a68a --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/PrimitiveTopology.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum PrimitiveTopology + { + Points, + Lines, + LineLoop, + LineStrip, + Triangles, + TriangleStrip, + TriangleFan, + Quads, + QuadStrip, + Polygon, + LinesAdjacency, + LineStripAdjacency, + TrianglesAdjacency, + TriangleStripAdjacency, + Patches, + } +} diff --git a/src/Ryujinx.Graphics.GAL/ProgramLinkStatus.cs b/src/Ryujinx.Graphics.GAL/ProgramLinkStatus.cs new file mode 100644 index 00000000..84f2729a --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ProgramLinkStatus.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum ProgramLinkStatus + { + Incomplete, + Success, + Failure, + } +} diff --git a/src/Ryujinx.Graphics.GAL/ProgramPipelineState.cs b/src/Ryujinx.Graphics.GAL/ProgramPipelineState.cs new file mode 100644 index 00000000..c16722af --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ProgramPipelineState.cs @@ -0,0 +1,80 @@ +using Ryujinx.Common.Memory; +using System; + +namespace Ryujinx.Graphics.GAL +{ + /// + /// Descriptor for a pipeline buffer binding. + /// + public readonly struct BufferPipelineDescriptor + { + public bool Enable { get; } + public int Stride { get; } + public int Divisor { get; } + + public BufferPipelineDescriptor(bool enable, int stride, int divisor) + { + Enable = enable; + Stride = stride; + Divisor = divisor; + } + } + + /// + /// State required for a program to compile shaders. + /// + public struct ProgramPipelineState + { + // Some state is considered always dynamic and should not be included: + // - Viewports/Scissors + // - Bias values (not enable) + + public int SamplesCount; + public Array8 AttachmentEnable; + public Array8 AttachmentFormats; + public bool DepthStencilEnable; + public Format DepthStencilFormat; + + public bool LogicOpEnable; + public LogicalOp LogicOp; + public Array8 BlendDescriptors; + public Array8 ColorWriteMask; + + public int VertexAttribCount; + public Array32 VertexAttribs; + + public int VertexBufferCount; + public Array32 VertexBuffers; + + // TODO: Min/max depth bounds. + public DepthTestDescriptor DepthTest; + public StencilTestDescriptor StencilTest; + public FrontFace FrontFace; + public Face CullMode; + public bool CullEnable; + + public PolygonModeMask BiasEnable; + + public float LineWidth; + // TODO: Polygon mode. + public bool DepthClampEnable; + public bool RasterizerDiscard; + public PrimitiveTopology Topology; + public bool PrimitiveRestartEnable; + public uint PatchControlPoints; + + public DepthMode DepthMode; + + public void SetVertexAttribs(ReadOnlySpan vertexAttribs) + { + VertexAttribCount = vertexAttribs.Length; + vertexAttribs.CopyTo(VertexAttribs.AsSpan()); + } + + public void SetLogicOpState(bool enable, LogicalOp op) + { + LogicOp = op; + LogicOpEnable = enable; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Rectangle.cs b/src/Ryujinx.Graphics.GAL/Rectangle.cs new file mode 100644 index 00000000..c8fa93d9 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Rectangle.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct Rectangle where T : unmanaged + { + public T X { get; } + public T Y { get; } + public T Width { get; } + public T Height { get; } + + public Rectangle(T x, T y, T width, T height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/ResourceLayout.cs b/src/Ryujinx.Graphics.GAL/ResourceLayout.cs new file mode 100644 index 00000000..b7464ee1 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ResourceLayout.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.ObjectModel; + +namespace Ryujinx.Graphics.GAL +{ + public enum ResourceType : byte + { + UniformBuffer, + StorageBuffer, + Texture, + Sampler, + TextureAndSampler, + Image, + BufferTexture, + BufferImage, + } + + [Flags] + public enum ResourceStages : byte + { + None = 0, + Compute = 1 << 0, + Vertex = 1 << 1, + TessellationControl = 1 << 2, + TessellationEvaluation = 1 << 3, + Geometry = 1 << 4, + Fragment = 1 << 5, + } + + public readonly struct ResourceDescriptor : IEquatable + { + public int Binding { get; } + public int Count { get; } + public ResourceType Type { get; } + public ResourceStages Stages { get; } + + public ResourceDescriptor(int binding, int count, ResourceType type, ResourceStages stages) + { + Binding = binding; + Count = count; + Type = type; + Stages = stages; + } + + public override int GetHashCode() + { + return HashCode.Combine(Binding, Count, Type, Stages); + } + + public override bool Equals(object obj) + { + return obj is ResourceDescriptor other && Equals(other); + } + + public bool Equals(ResourceDescriptor other) + { + return Binding == other.Binding && Count == other.Count && Type == other.Type && Stages == other.Stages; + } + + public static bool operator ==(ResourceDescriptor left, ResourceDescriptor right) + { + return left.Equals(right); + } + + public static bool operator !=(ResourceDescriptor left, ResourceDescriptor right) + { + return !(left == right); + } + } + + public readonly struct ResourceUsage : IEquatable + { + public int Binding { get; } + public int ArrayLength { get; } + public ResourceType Type { get; } + public ResourceStages Stages { get; } + public bool Write { get; } + + public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages, bool write) + { + Binding = binding; + ArrayLength = arrayLength; + Type = type; + Stages = stages; + Write = write; + } + + public override int GetHashCode() + { + return HashCode.Combine(Binding, ArrayLength, Type, Stages); + } + + public override bool Equals(object obj) + { + return obj is ResourceUsage other && Equals(other); + } + + public bool Equals(ResourceUsage other) + { + return Binding == other.Binding && ArrayLength == other.ArrayLength && Type == other.Type && Stages == other.Stages; + } + + public static bool operator ==(ResourceUsage left, ResourceUsage right) + { + return left.Equals(right); + } + + public static bool operator !=(ResourceUsage left, ResourceUsage right) + { + return !(left == right); + } + } + + public readonly struct ResourceDescriptorCollection + { + public ReadOnlyCollection Descriptors { get; } + + public ResourceDescriptorCollection(ReadOnlyCollection descriptors) + { + Descriptors = descriptors; + } + + public override int GetHashCode() + { + HashCode hasher = new(); + + if (Descriptors != null) + { + foreach (var descriptor in Descriptors) + { + hasher.Add(descriptor); + } + } + + return hasher.ToHashCode(); + } + + public override bool Equals(object obj) + { + return obj is ResourceDescriptorCollection other && Equals(other); + } + + public bool Equals(ResourceDescriptorCollection other) + { + if ((Descriptors == null) != (other.Descriptors == null)) + { + return false; + } + + if (Descriptors != null) + { + if (Descriptors.Count != other.Descriptors.Count) + { + return false; + } + + for (int index = 0; index < Descriptors.Count; index++) + { + if (!Descriptors[index].Equals(other.Descriptors[index])) + { + return false; + } + } + } + + return true; + } + + public static bool operator ==(ResourceDescriptorCollection left, ResourceDescriptorCollection right) + { + return left.Equals(right); + } + + public static bool operator !=(ResourceDescriptorCollection left, ResourceDescriptorCollection right) + { + return !(left == right); + } + } + + public readonly struct ResourceUsageCollection + { + public ReadOnlyCollection Usages { get; } + + public ResourceUsageCollection(ReadOnlyCollection usages) + { + Usages = usages; + } + } + + public readonly struct ResourceLayout + { + public ReadOnlyCollection Sets { get; } + public ReadOnlyCollection SetUsages { get; } + + public ResourceLayout( + ReadOnlyCollection sets, + ReadOnlyCollection setUsages) + { + Sets = sets; + SetUsages = setUsages; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj b/src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj new file mode 100644 index 00000000..d88b641a --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + + + + true + + + + true + + + + + + + + diff --git a/src/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs b/src/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs new file mode 100644 index 00000000..2374a066 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs @@ -0,0 +1,72 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct SamplerCreateInfo + { + public MinFilter MinFilter { get; } + public MagFilter MagFilter { get; } + + public bool SeamlessCubemap { get; } + + public AddressMode AddressU { get; } + public AddressMode AddressV { get; } + public AddressMode AddressP { get; } + + public CompareMode CompareMode { get; } + public CompareOp CompareOp { get; } + + public ColorF BorderColor { get; } + + public float MinLod { get; } + public float MaxLod { get; } + public float MipLodBias { get; } + public float MaxAnisotropy { get; } + + public SamplerCreateInfo( + MinFilter minFilter, + MagFilter magFilter, + bool seamlessCubemap, + AddressMode addressU, + AddressMode addressV, + AddressMode addressP, + CompareMode compareMode, + CompareOp compareOp, + ColorF borderColor, + float minLod, + float maxLod, + float mipLodBias, + float maxAnisotropy) + { + MinFilter = minFilter; + MagFilter = magFilter; + SeamlessCubemap = seamlessCubemap; + AddressU = addressU; + AddressV = addressV; + AddressP = addressP; + CompareMode = compareMode; + CompareOp = compareOp; + BorderColor = borderColor; + MinLod = minLod; + MaxLod = maxLod; + MipLodBias = mipLodBias; + MaxAnisotropy = maxAnisotropy; + } + + public static SamplerCreateInfo Create(MinFilter minFilter, MagFilter magFilter) + { + return new SamplerCreateInfo( + minFilter, + magFilter, + false, + AddressMode.ClampToEdge, + AddressMode.ClampToEdge, + AddressMode.ClampToEdge, + CompareMode.None, + CompareOp.Always, + new ColorF(0f, 0f, 0f, 0f), + 0f, + 0f, + 0f, + 1f); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs b/src/Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs new file mode 100644 index 00000000..31ad1fec --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct ScreenCaptureImageInfo + { + public ScreenCaptureImageInfo(int width, int height, bool isBgra, byte[] data, bool flipX, bool flipY) + { + Width = width; + Height = height; + IsBgra = isBgra; + Data = data; + FlipX = flipX; + FlipY = flipY; + } + + public int Width { get; } + public int Height { get; } + public byte[] Data { get; } + public bool IsBgra { get; } + public bool FlipX { get; } + public bool FlipY { get; } + } +} diff --git a/src/Ryujinx.Graphics.GAL/ShaderInfo.cs b/src/Ryujinx.Graphics.GAL/ShaderInfo.cs new file mode 100644 index 00000000..2fd3227d --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ShaderInfo.cs @@ -0,0 +1,26 @@ +namespace Ryujinx.Graphics.GAL +{ + public struct ShaderInfo + { + public int FragmentOutputMap { get; } + public ResourceLayout ResourceLayout { get; } + public ProgramPipelineState? State { get; } + public bool FromCache { get; set; } + + public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, ProgramPipelineState state, bool fromCache = false) + { + FragmentOutputMap = fragmentOutputMap; + ResourceLayout = resourceLayout; + State = state; + FromCache = fromCache; + } + + public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, bool fromCache = false) + { + FragmentOutputMap = fragmentOutputMap; + ResourceLayout = resourceLayout; + State = null; + FromCache = fromCache; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/ShaderSource.cs b/src/Ryujinx.Graphics.GAL/ShaderSource.cs new file mode 100644 index 00000000..bf678e60 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ShaderSource.cs @@ -0,0 +1,29 @@ +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.GAL +{ + public readonly struct ShaderSource + { + public string Code { get; } + public byte[] BinaryCode { get; } + public ShaderStage Stage { get; } + public TargetLanguage Language { get; } + + public ShaderSource(string code, byte[] binaryCode, ShaderStage stage, TargetLanguage language) + { + Code = code; + BinaryCode = binaryCode; + Stage = stage; + Language = language; + } + + public ShaderSource(string code, ShaderStage stage, TargetLanguage language) : this(code, null, stage, language) + { + } + + public ShaderSource(byte[] binaryCode, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, stage, language) + { + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/StencilOp.cs b/src/Ryujinx.Graphics.GAL/StencilOp.cs new file mode 100644 index 00000000..fa87f0b5 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/StencilOp.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum StencilOp + { + Keep = 1, + Zero, + Replace, + IncrementAndClamp, + DecrementAndClamp, + Invert, + IncrementAndWrap, + DecrementAndWrap, + + ZeroGl = 0x0, + InvertGl = 0x150a, + KeepGl = 0x1e00, + ReplaceGl = 0x1e01, + IncrementAndClampGl = 0x1e02, + DecrementAndClampGl = 0x1e03, + IncrementAndWrapGl = 0x8507, + DecrementAndWrapGl = 0x8508, + } +} diff --git a/src/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs b/src/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs new file mode 100644 index 00000000..4309aa95 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs @@ -0,0 +1,56 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct StencilTestDescriptor + { + public bool TestEnable { get; } + + public CompareOp FrontFunc { get; } + public StencilOp FrontSFail { get; } + public StencilOp FrontDpPass { get; } + public StencilOp FrontDpFail { get; } + public int FrontFuncRef { get; } + public int FrontFuncMask { get; } + public int FrontMask { get; } + public CompareOp BackFunc { get; } + public StencilOp BackSFail { get; } + public StencilOp BackDpPass { get; } + public StencilOp BackDpFail { get; } + public int BackFuncRef { get; } + public int BackFuncMask { get; } + public int BackMask { get; } + + public StencilTestDescriptor( + bool testEnable, + CompareOp frontFunc, + StencilOp frontSFail, + StencilOp frontDpPass, + StencilOp frontDpFail, + int frontFuncRef, + int frontFuncMask, + int frontMask, + CompareOp backFunc, + StencilOp backSFail, + StencilOp backDpPass, + StencilOp backDpFail, + int backFuncRef, + int backFuncMask, + int backMask) + { + TestEnable = testEnable; + FrontFunc = frontFunc; + FrontSFail = frontSFail; + FrontDpPass = frontDpPass; + FrontDpFail = frontDpFail; + FrontFuncRef = frontFuncRef; + FrontFuncMask = frontFuncMask; + FrontMask = frontMask; + BackFunc = backFunc; + BackSFail = backSFail; + BackDpPass = backDpPass; + BackDpFail = backDpFail; + BackFuncRef = backFuncRef; + BackFuncMask = backFuncMask; + BackMask = backMask; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/SwizzleComponent.cs b/src/Ryujinx.Graphics.GAL/SwizzleComponent.cs new file mode 100644 index 00000000..ee65e010 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/SwizzleComponent.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum SwizzleComponent + { + Zero, + One, + Red, + Green, + Blue, + Alpha, + } +} diff --git a/src/Ryujinx.Graphics.GAL/SystemMemoryType.cs b/src/Ryujinx.Graphics.GAL/SystemMemoryType.cs new file mode 100644 index 00000000..53292129 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/SystemMemoryType.cs @@ -0,0 +1,29 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum SystemMemoryType + { + /// + /// The backend manages the ownership of memory. This mode never supports host imported memory. + /// + BackendManaged, + + /// + /// Device memory has similar performance to host memory, usually because it's shared between CPU/GPU. + /// Use host memory whenever possible. + /// + UnifiedMemory, + + /// + /// GPU storage to host memory goes though a slow interconnect, but it would still be preferable to use it if the data is flushed back often. + /// Assumes constant buffer access to host memory is rather fast. + /// + DedicatedMemory, + + /// + /// GPU storage to host memory goes though a slow interconnect, that is very slow when doing access from storage. + /// When frequently accessed, copy buffers to host memory using DMA. + /// Assumes constant buffer access to host memory is rather fast. + /// + DedicatedMemorySlowStorage + } +} diff --git a/src/Ryujinx.Graphics.GAL/Target.cs b/src/Ryujinx.Graphics.GAL/Target.cs new file mode 100644 index 00000000..73497546 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Target.cs @@ -0,0 +1,34 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum Target + { + Texture1D, + Texture2D, + Texture3D, + Texture1DArray, + Texture2DArray, + Texture2DMultisample, + Texture2DMultisampleArray, + Cubemap, + CubemapArray, + TextureBuffer, + } + + public static class TargetExtensions + { + public static bool IsMultisample(this Target target) + { + return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray; + } + + public static bool HasDepthOrLayers(this Target target) + { + return target == Target.Texture3D || + target == Target.Texture1DArray || + target == Target.Texture2DArray || + target == Target.Texture2DMultisampleArray || + target == Target.Cubemap || + target == Target.CubemapArray; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs b/src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs new file mode 100644 index 00000000..79c84db0 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs @@ -0,0 +1,159 @@ +using Ryujinx.Common; +using System; + +namespace Ryujinx.Graphics.GAL +{ + public readonly struct TextureCreateInfo : IEquatable + { + public int Width { get; } + public int Height { get; } + public int Depth { get; } + public int Levels { get; } + public int Samples { get; } + public int BlockWidth { get; } + public int BlockHeight { get; } + public int BytesPerPixel { get; } + + public bool IsCompressed => (BlockWidth | BlockHeight) != 1; + + public Format Format { get; } + + public DepthStencilMode DepthStencilMode { get; } + + public Target Target { get; } + + public SwizzleComponent SwizzleR { get; } + public SwizzleComponent SwizzleG { get; } + public SwizzleComponent SwizzleB { get; } + public SwizzleComponent SwizzleA { get; } + + public TextureCreateInfo( + int width, + int height, + int depth, + int levels, + int samples, + int blockWidth, + int blockHeight, + int bytesPerPixel, + Format format, + DepthStencilMode depthStencilMode, + Target target, + SwizzleComponent swizzleR, + SwizzleComponent swizzleG, + SwizzleComponent swizzleB, + SwizzleComponent swizzleA) + { + Width = width; + Height = height; + Depth = depth; + Levels = levels; + Samples = samples; + BlockWidth = blockWidth; + BlockHeight = blockHeight; + BytesPerPixel = bytesPerPixel; + Format = format; + DepthStencilMode = depthStencilMode; + Target = target; + SwizzleR = swizzleR; + SwizzleG = swizzleG; + SwizzleB = swizzleB; + SwizzleA = swizzleA; + } + + public int GetMipSize(int level) + { + return GetMipStride(level) * GetLevelHeight(level) * GetLevelDepth(level); + } + + public int GetMipSize2D(int level) + { + return GetMipStride(level) * GetLevelHeight(level); + } + + public int GetMipStride(int level) + { + return BitUtils.AlignUp(GetLevelWidth(level) * BytesPerPixel, 4); + } + + private int GetLevelWidth(int level) + { + return BitUtils.DivRoundUp(GetLevelSize(Width, level), BlockWidth); + } + + private int GetLevelHeight(int level) + { + return BitUtils.DivRoundUp(GetLevelSize(Height, level), BlockHeight); + } + + private int GetLevelDepth(int level) + { + return Target == Target.Texture3D ? GetLevelSize(Depth, level) : GetLayers(); + } + + public int GetDepthOrLayers() + { + return Target == Target.Texture3D ? Depth : GetLayers(); + } + + public int GetLayers() + { + if (Target == Target.Texture2DArray || + Target == Target.Texture2DMultisampleArray || + Target == Target.CubemapArray) + { + return Depth; + } + else if (Target == Target.Cubemap) + { + return 6; + } + + return 1; + } + + private static int GetLevelSize(int size, int level) + { + return Math.Max(1, size >> level); + } + + public override int GetHashCode() + { + return HashCode.Combine(Width, Height); + } + + public bool Equals(TextureCreateInfo other) + { + return Width == other.Width && + Height == other.Height && + Depth == other.Depth && + Levels == other.Levels && + Samples == other.Samples && + BlockWidth == other.BlockWidth && + BlockHeight == other.BlockHeight && + BytesPerPixel == other.BytesPerPixel && + Format == other.Format && + DepthStencilMode == other.DepthStencilMode && + Target == other.Target && + SwizzleR == other.SwizzleR && + SwizzleG == other.SwizzleG && + SwizzleB == other.SwizzleB && + SwizzleA == other.SwizzleA; + } + + public override bool Equals(object obj) + { + return obj is TextureCreateInfo info && this.Equals(info); + } + + public static bool operator ==(TextureCreateInfo left, TextureCreateInfo right) + { + return left.Equals(right); + } + + public static bool operator !=(TextureCreateInfo left, TextureCreateInfo right) + { + return !(left == right); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs b/src/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs new file mode 100644 index 00000000..c058df2b --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Graphics.GAL +{ + public delegate void TextureReleaseCallback(object context); +} diff --git a/src/Ryujinx.Graphics.GAL/UpscaleType.cs b/src/Ryujinx.Graphics.GAL/UpscaleType.cs new file mode 100644 index 00000000..ca24199c --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/UpscaleType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.GAL +{ + public enum ScalingFilter + { + Bilinear, + Nearest, + Fsr, + } +} diff --git a/src/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs b/src/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs new file mode 100644 index 00000000..4f5ea6a6 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly record struct VertexAttribDescriptor(int BufferIndex, int Offset, bool IsZero, Format Format); +} diff --git a/src/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs b/src/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs new file mode 100644 index 00000000..8fd3c816 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct VertexBufferDescriptor + { + public BufferRange Buffer { get; } + + public int Stride { get; } + public int Divisor { get; } + + public VertexBufferDescriptor(BufferRange buffer, int stride, int divisor) + { + Buffer = buffer; + Stride = stride; + Divisor = divisor; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Viewport.cs b/src/Ryujinx.Graphics.GAL/Viewport.cs new file mode 100644 index 00000000..12d13b7c --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Viewport.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.Graphics.GAL +{ + public readonly struct Viewport + { + public Rectangle Region { get; } + + public ViewportSwizzle SwizzleX { get; } + public ViewportSwizzle SwizzleY { get; } + public ViewportSwizzle SwizzleZ { get; } + public ViewportSwizzle SwizzleW { get; } + + public float DepthNear { get; } + public float DepthFar { get; } + + public Viewport( + Rectangle region, + ViewportSwizzle swizzleX, + ViewportSwizzle swizzleY, + ViewportSwizzle swizzleZ, + ViewportSwizzle swizzleW, + float depthNear, + float depthFar) + { + Region = region; + SwizzleX = swizzleX; + SwizzleY = swizzleY; + SwizzleZ = swizzleZ; + SwizzleW = swizzleW; + DepthNear = depthNear; + DepthFar = depthFar; + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/ViewportSwizzle.cs b/src/Ryujinx.Graphics.GAL/ViewportSwizzle.cs new file mode 100644 index 00000000..9352c816 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/ViewportSwizzle.cs @@ -0,0 +1,19 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.Graphics.GAL +{ + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + public enum ViewportSwizzle + { + PositiveX = 0, + NegativeX = 1, + PositiveY = 2, + NegativeY = 3, + PositiveZ = 4, + NegativeZ = 5, + PositiveW = 6, + NegativeW = 7, + + NegativeFlag = 1, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/ClassId.cs b/src/Ryujinx.Graphics.Gpu/ClassId.cs new file mode 100644 index 00000000..7c1d1e46 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/ClassId.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Gpu +{ + /// + /// GPU engine class ID. + /// + public enum ClassId + { + Twod = 0x902d, + Threed = 0xb197, + Compute = 0xb1c0, + InlineToMemory = 0xa140, + Dma = 0xb0b5, + GPFifo = 0xb06f, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Constants.cs b/src/Ryujinx.Graphics.Gpu/Constants.cs new file mode 100644 index 00000000..23b9be5c --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Constants.cs @@ -0,0 +1,98 @@ +namespace Ryujinx.Graphics.Gpu +{ + /// + /// Common Maxwell GPU constants. + /// + static class Constants + { + /// + /// Maximum number of compute uniform buffers. + /// + /// + /// This does not reflect the hardware count, the API will emulate some constant buffers using + /// global memory to make up for the low amount of compute constant buffers supported by hardware (only 8). + /// + public const int TotalCpUniformBuffers = 17; // 8 hardware constant buffers + 9 emulated (14 available to the user). + + /// + /// Maximum number of compute storage buffers. + /// + /// + /// The maximum number of storage buffers is API limited, the hardware supports an unlimited amount. + /// + public const int TotalCpStorageBuffers = 16; + + /// + /// Maximum number of graphics uniform buffers. + /// + public const int TotalGpUniformBuffers = 18; + + /// + /// Maximum number of graphics storage buffers. + /// + /// + /// The maximum number of storage buffers is API limited, the hardware supports an unlimited amount. + /// + public const int TotalGpStorageBuffers = 16; + + /// + /// Maximum number of transform feedback buffers. + /// + public const int TotalTransformFeedbackBuffers = 4; + + /// + /// Maximum number of render target color buffers. + /// + public const int TotalRenderTargets = 8; + + /// + /// Number of shader stages. + /// + public const int ShaderStages = 5; + + /// + /// Maximum number of vertex attributes. + /// + public const int TotalVertexAttribs = 16; // FIXME: Should be 32, but OpenGL only supports 16. + + /// + /// Maximum number of vertex buffers. + /// + public const int TotalVertexBuffers = 16; + + /// + /// Maximum number of viewports. + /// + public const int TotalViewports = 16; + + /// + /// Maximum size of gl_ClipDistance array in shaders. + /// + public const int TotalClipDistances = 8; + + /// + /// Byte alignment for texture stride. + /// + public const int StrideAlignment = 32; + + /// + /// Byte alignment for block linear textures + /// + public const int GobAlignment = 64; + + /// + /// Number of the uniform buffer reserved by the driver to store the storage buffer base addresses. + /// + public const int DriverReservedUniformBuffer = 0; + + /// + /// Maximum size that an storage buffer is assumed to have when the correct size is unknown. + /// + public const ulong MaxUnknownStorageSize = 0x100000; + + /// + /// Size of a bindless texture handle as exposed by guest graphics APIs. + /// + public const int TextureHandleSizeInBytes = sizeof(ulong); + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs new file mode 100644 index 00000000..cd814472 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs @@ -0,0 +1,208 @@ +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; +using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Shader; +using Ryujinx.Graphics.Shader; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Compute +{ + /// + /// Represents a compute engine class. + /// + class ComputeClass : IDeviceState + { + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly ThreedClass _3dEngine; + private readonly DeviceState _state; + + private readonly InlineToMemoryClass _i2mClass; + + /// + /// Creates a new instance of the compute engine class. + /// + /// GPU context + /// GPU channel + /// 3D engine + public ComputeClass(GpuContext context, GpuChannel channel, ThreedClass threedEngine) + { + _context = context; + _channel = channel; + _3dEngine = threedEngine; + _state = new DeviceState(new Dictionary + { + { nameof(ComputeClassState.LaunchDma), new RwCallback(LaunchDma, null) }, + { nameof(ComputeClassState.LoadInlineData), new RwCallback(LoadInlineData, null) }, + { nameof(ComputeClassState.SendSignalingPcasB), new RwCallback(SendSignalingPcasB, null) }, + }); + + _i2mClass = new InlineToMemoryClass(context, channel, initializeState: false); + } + + /// + /// Reads data from the class registers. + /// + /// Register byte offset + /// Data at the specified offset + public int Read(int offset) => _state.Read(offset); + + /// + /// Writes data to the class registers. + /// + /// Register byte offset + /// Data to be written + public void Write(int offset, int data) => _state.Write(offset, data); + + /// + /// Launches the Inline-to-Memory DMA copy operation. + /// + /// Method call argument + private void LaunchDma(int argument) + { + _i2mClass.LaunchDma(ref Unsafe.As(ref _state.State), argument); + } + + /// + /// Pushes a block of data to the Inline-to-Memory engine. + /// + /// Data to push + public void LoadInlineData(ReadOnlySpan data) + { + _i2mClass.LoadInlineData(data); + } + + /// + /// Pushes a word of data to the Inline-to-Memory engine. + /// + /// Method call argument + private void LoadInlineData(int argument) + { + _i2mClass.LoadInlineData(argument); + } + + /// + /// Performs the compute dispatch operation. + /// + /// Method call argument + private void SendSignalingPcasB(int argument) + { + var memoryManager = _channel.MemoryManager; + + // Since we're going to change the state, make sure any pending instanced draws are done. + _3dEngine.PerformDeferredDraws(); + + // Make sure all pending uniform buffer data is written to memory. + _3dEngine.FlushUboDirty(); + + uint qmdAddress = _state.State.SendPcasA; + + var qmd = _channel.MemoryManager.Read((ulong)qmdAddress << 8); + + ulong shaderGpuVa = ((ulong)_state.State.SetProgramRegionAAddressUpper << 32) | _state.State.SetProgramRegionB; + + shaderGpuVa += (uint)qmd.ProgramOffset; + + int localMemorySize = qmd.ShaderLocalMemoryLowSize + qmd.ShaderLocalMemoryHighSize; + + int sharedMemorySize = Math.Min(qmd.SharedMemorySize, _context.Capabilities.MaximumComputeSharedMemorySize); + + for (int index = 0; index < Constants.TotalCpUniformBuffers; index++) + { + if (!qmd.ConstantBufferValid(index)) + { + continue; + } + + ulong gpuVa = (uint)qmd.ConstantBufferAddrLower(index) | (ulong)qmd.ConstantBufferAddrUpper(index) << 32; + ulong size = (ulong)qmd.ConstantBufferSize(index); + + _channel.BufferManager.SetComputeUniformBuffer(index, gpuVa, size); + } + + ulong samplerPoolGpuVa = ((ulong)_state.State.SetTexSamplerPoolAOffsetUpper << 32) | _state.State.SetTexSamplerPoolB; + ulong texturePoolGpuVa = ((ulong)_state.State.SetTexHeaderPoolAOffsetUpper << 32) | _state.State.SetTexHeaderPoolB; + + int samplerPoolMaximumId = _state.State.SetTexSamplerPoolCMaximumIndex; + + GpuChannelPoolState poolState = new( + texturePoolGpuVa, + _state.State.SetTexHeaderPoolCMaximumIndex, + _state.State.SetBindlessTextureConstantBufferSlotSelect); + + GpuChannelComputeState computeState = new( + qmd.CtaThreadDimension0, + qmd.CtaThreadDimension1, + qmd.CtaThreadDimension2, + localMemorySize, + sharedMemorySize, + _channel.BufferManager.HasUnalignedStorageBuffers); + + CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa); + + _context.Renderer.Pipeline.SetProgram(cs.HostProgram); + + _channel.TextureManager.SetComputeSamplerPool(samplerPoolGpuVa, _state.State.SetTexSamplerPoolCMaximumIndex, qmd.SamplerIndex); + _channel.TextureManager.SetComputeTexturePool(texturePoolGpuVa, _state.State.SetTexHeaderPoolCMaximumIndex); + _channel.TextureManager.SetComputeTextureBufferIndex(_state.State.SetBindlessTextureConstantBufferSlotSelect); + + ShaderProgramInfo info = cs.Shaders[0].Info; + + for (int index = 0; index < info.SBuffers.Count; index++) + { + BufferDescriptor sb = info.SBuffers[index]; + + ulong sbDescAddress = _channel.BufferManager.GetComputeUniformBufferAddress(sb.SbCbSlot); + sbDescAddress += (ulong)sb.SbCbOffset * 4; + + SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read(sbDescAddress); + + uint size; + if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer) + { + // Only trust the SbDescriptor size if it comes from slot 0. + size = (uint)sbDescriptor.Size; + } + else + { + // TODO: Use full mapped size and somehow speed up buffer sync. + size = (uint)_channel.MemoryManager.GetMappedSize(sbDescriptor.PackAddress(), Constants.MaxUnknownStorageSize); + } + + _channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), size, sb.Flags); + } + + if (_channel.BufferManager.HasUnalignedStorageBuffers != computeState.HasUnalignedStorageBuffer) + { + // Refetch the shader, as assumptions about storage buffer alignment have changed. + computeState = new GpuChannelComputeState( + qmd.CtaThreadDimension0, + qmd.CtaThreadDimension1, + qmd.CtaThreadDimension2, + localMemorySize, + sharedMemorySize, + _channel.BufferManager.HasUnalignedStorageBuffers); + + cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, samplerPoolMaximumId, poolState, computeState, shaderGpuVa); + + _context.Renderer.Pipeline.SetProgram(cs.HostProgram); + } + + _channel.BufferManager.SetComputeBufferBindings(cs.Bindings); + + _channel.TextureManager.SetComputeBindings(cs.Bindings); + + // Should never return false for mismatching spec state, since the shader was fetched above. + _channel.TextureManager.CommitComputeBindings(cs.SpecializationState); + + _channel.BufferManager.CommitComputeBindings(); + + _context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth); + + _3dEngine.ForceShaderUpdate(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClassState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClassState.cs new file mode 100644 index 00000000..18f79cc3 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClassState.cs @@ -0,0 +1,435 @@ +// This file was auto-generated from NVIDIA official Maxwell definitions. + +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; + +namespace Ryujinx.Graphics.Gpu.Engine.Compute +{ + /// + /// Notify type. + /// + enum NotifyType + { + WriteOnly = 0, + WriteThenAwaken = 1, + } + + /// + /// CWD control SM selection. + /// + enum SetCwdControlSmSelection + { + LoadBalanced = 0, + RoundRobin = 1, + } + + /// + /// Cache lines to invalidate. + /// + enum InvalidateCacheLines + { + All = 0, + One = 1, + } + + /// + /// GWC SCG type. + /// + enum SetGwcScgTypeScgType + { + GraphicsCompute0 = 0, + Compute1 = 1, + } + + /// + /// Render enable override mode. + /// + enum SetRenderEnableOverrideMode + { + UseRenderEnable = 0, + AlwaysRender = 1, + NeverRender = 2, + } + + /// + /// Semaphore report operation. + /// + enum SetReportSemaphoreDOperation + { + Release = 0, + Trap = 3, + } + + /// + /// Semaphore report structure size. + /// + enum SetReportSemaphoreDStructureSize + { + FourWords = 0, + OneWord = 1, + } + + /// + /// Semaphore report reduction operation. + /// + enum SetReportSemaphoreDReductionOp + { + RedAdd = 0, + RedMin = 1, + RedMax = 2, + RedInc = 3, + RedDec = 4, + RedAnd = 5, + RedOr = 6, + RedXor = 7, + } + + /// + /// Semaphore report reduction format. + /// + enum SetReportSemaphoreDReductionFormat + { + Unsigned32 = 0, + Signed32 = 1, + } + + /// + /// Compute class state. + /// + unsafe struct ComputeClassState + { +#pragma warning disable CS0649 // Field is never assigned to + public uint SetObject; + public readonly int SetObjectClassId => (int)(SetObject & 0xFFFF); + public readonly int SetObjectEngineId => (int)((SetObject >> 16) & 0x1F); + public fixed uint Reserved04[63]; + public uint NoOperation; + public uint SetNotifyA; + public readonly int SetNotifyAAddressUpper => (int)(SetNotifyA & 0xFF); + public uint SetNotifyB; + public uint Notify; + public readonly NotifyType NotifyType => (NotifyType)(Notify); + public uint WaitForIdle; + public fixed uint Reserved114[7]; + public uint SetGlobalRenderEnableA; + public readonly int SetGlobalRenderEnableAOffsetUpper => (int)(SetGlobalRenderEnableA & 0xFF); + public uint SetGlobalRenderEnableB; + public uint SetGlobalRenderEnableC; + public readonly int SetGlobalRenderEnableCMode => (int)(SetGlobalRenderEnableC & 0x7); + public uint SendGoIdle; + public uint PmTrigger; + public uint PmTriggerWfi; + public fixed uint Reserved148[2]; + public uint SetInstrumentationMethodHeader; + public uint SetInstrumentationMethodData; + public fixed uint Reserved158[10]; + public uint LineLengthIn; + public uint LineCount; + public uint OffsetOutUpper; + public readonly int OffsetOutUpperValue => (int)(OffsetOutUpper & 0xFF); + public uint OffsetOut; + public uint PitchOut; + public uint SetDstBlockSize; + public readonly SetDstBlockSizeWidth SetDstBlockSizeWidth => (SetDstBlockSizeWidth)(SetDstBlockSize & 0xF); + public readonly SetDstBlockSizeHeight SetDstBlockSizeHeight => (SetDstBlockSizeHeight)((SetDstBlockSize >> 4) & 0xF); + public readonly SetDstBlockSizeDepth SetDstBlockSizeDepth => (SetDstBlockSizeDepth)((SetDstBlockSize >> 8) & 0xF); + public uint SetDstWidth; + public uint SetDstHeight; + public uint SetDstDepth; + public uint SetDstLayer; + public uint SetDstOriginBytesX; + public readonly int SetDstOriginBytesXV => (int)(SetDstOriginBytesX & 0xFFFFF); + public uint SetDstOriginSamplesY; + public readonly int SetDstOriginSamplesYV => (int)(SetDstOriginSamplesY & 0xFFFF); + public uint LaunchDma; + public readonly LaunchDmaDstMemoryLayout LaunchDmaDstMemoryLayout => (LaunchDmaDstMemoryLayout)(LaunchDma & 0x1); + public readonly LaunchDmaCompletionType LaunchDmaCompletionType => (LaunchDmaCompletionType)((LaunchDma >> 4) & 0x3); + public readonly LaunchDmaInterruptType LaunchDmaInterruptType => (LaunchDmaInterruptType)((LaunchDma >> 8) & 0x3); + public readonly LaunchDmaSemaphoreStructSize LaunchDmaSemaphoreStructSize => (LaunchDmaSemaphoreStructSize)((LaunchDma >> 12) & 0x1); + public readonly bool LaunchDmaReductionEnable => (LaunchDma & 0x2) != 0; + public readonly LaunchDmaReductionOp LaunchDmaReductionOp => (LaunchDmaReductionOp)((LaunchDma >> 13) & 0x7); + public readonly LaunchDmaReductionFormat LaunchDmaReductionFormat => (LaunchDmaReductionFormat)((LaunchDma >> 2) & 0x3); + public readonly bool LaunchDmaSysmembarDisable => (LaunchDma & 0x40) != 0; + public uint LoadInlineData; + public fixed uint Reserved1B8[9]; + public uint SetI2mSemaphoreA; + public readonly int SetI2mSemaphoreAOffsetUpper => (int)(SetI2mSemaphoreA & 0xFF); + public uint SetI2mSemaphoreB; + public uint SetI2mSemaphoreC; + public fixed uint Reserved1E8[2]; + public uint SetI2mSpareNoop00; + public uint SetI2mSpareNoop01; + public uint SetI2mSpareNoop02; + public uint SetI2mSpareNoop03; + public uint SetValidSpanOverflowAreaA; + public readonly int SetValidSpanOverflowAreaAAddressUpper => (int)(SetValidSpanOverflowAreaA & 0xFF); + public uint SetValidSpanOverflowAreaB; + public uint SetValidSpanOverflowAreaC; + public uint SetCoalesceWaitingPeriodUnit; + public uint PerfmonTransfer; + public uint SetShaderSharedMemoryWindow; + public uint SetSelectMaxwellTextureHeaders; + public readonly bool SetSelectMaxwellTextureHeadersV => (SetSelectMaxwellTextureHeaders & 0x1) != 0; + public uint InvalidateShaderCaches; + public readonly bool InvalidateShaderCachesInstruction => (InvalidateShaderCaches & 0x1) != 0; + public readonly bool InvalidateShaderCachesData => (InvalidateShaderCaches & 0x10) != 0; + public readonly bool InvalidateShaderCachesConstant => (InvalidateShaderCaches & 0x1000) != 0; + public readonly bool InvalidateShaderCachesLocks => (InvalidateShaderCaches & 0x2) != 0; + public readonly bool InvalidateShaderCachesFlushData => (InvalidateShaderCaches & 0x4) != 0; + public uint SetReservedSwMethod00; + public uint SetReservedSwMethod01; + public uint SetReservedSwMethod02; + public uint SetReservedSwMethod03; + public uint SetReservedSwMethod04; + public uint SetReservedSwMethod05; + public uint SetReservedSwMethod06; + public uint SetReservedSwMethod07; + public uint SetCwdControl; + public readonly SetCwdControlSmSelection SetCwdControlSmSelection => (SetCwdControlSmSelection)(SetCwdControl & 0x1); + public uint InvalidateTextureHeaderCacheNoWfi; + public readonly InvalidateCacheLines InvalidateTextureHeaderCacheNoWfiLines => (InvalidateCacheLines)(InvalidateTextureHeaderCacheNoWfi & 0x1); + public readonly int InvalidateTextureHeaderCacheNoWfiTag => (int)((InvalidateTextureHeaderCacheNoWfi >> 4) & 0x3FFFFF); + public uint SetCwdRefCounter; + public readonly int SetCwdRefCounterSelect => (int)(SetCwdRefCounter & 0x3F); + public readonly int SetCwdRefCounterValue => (int)((SetCwdRefCounter >> 8) & 0xFFFF); + public uint SetReservedSwMethod08; + public uint SetReservedSwMethod09; + public uint SetReservedSwMethod10; + public uint SetReservedSwMethod11; + public uint SetReservedSwMethod12; + public uint SetReservedSwMethod13; + public uint SetReservedSwMethod14; + public uint SetReservedSwMethod15; + public uint SetGwcScgType; + public readonly SetGwcScgTypeScgType SetGwcScgTypeScgType => (SetGwcScgTypeScgType)(SetGwcScgType & 0x1); + public uint SetScgControl; + public readonly int SetScgControlCompute1MaxSmCount => (int)(SetScgControl & 0x1FF); + public uint InvalidateConstantBufferCacheA; + public readonly int InvalidateConstantBufferCacheAAddressUpper => (int)(InvalidateConstantBufferCacheA & 0xFF); + public uint InvalidateConstantBufferCacheB; + public uint InvalidateConstantBufferCacheC; + public readonly int InvalidateConstantBufferCacheCByteCount => (int)(InvalidateConstantBufferCacheC & 0x1FFFF); + public readonly bool InvalidateConstantBufferCacheCThruL2 => (InvalidateConstantBufferCacheC & 0x80000000) != 0; + public uint SetComputeClassVersion; + public readonly int SetComputeClassVersionCurrent => (int)(SetComputeClassVersion & 0xFFFF); + public readonly int SetComputeClassVersionOldestSupported => (int)((SetComputeClassVersion >> 16) & 0xFFFF); + public uint CheckComputeClassVersion; + public readonly int CheckComputeClassVersionCurrent => (int)(CheckComputeClassVersion & 0xFFFF); + public readonly int CheckComputeClassVersionOldestSupported => (int)((CheckComputeClassVersion >> 16) & 0xFFFF); + public uint SetQmdVersion; + public readonly int SetQmdVersionCurrent => (int)(SetQmdVersion & 0xFFFF); + public readonly int SetQmdVersionOldestSupported => (int)((SetQmdVersion >> 16) & 0xFFFF); + public uint SetWfiConfig; + public readonly bool SetWfiConfigEnableScgTypeWfi => (SetWfiConfig & 0x1) != 0; + public uint CheckQmdVersion; + public readonly int CheckQmdVersionCurrent => (int)(CheckQmdVersion & 0xFFFF); + public readonly int CheckQmdVersionOldestSupported => (int)((CheckQmdVersion >> 16) & 0xFFFF); + public uint WaitForIdleScgType; + public uint InvalidateSkedCaches; + public readonly bool InvalidateSkedCachesV => (InvalidateSkedCaches & 0x1) != 0; + public uint SetScgRenderEnableControl; + public readonly bool SetScgRenderEnableControlCompute1UsesRenderEnable => (SetScgRenderEnableControl & 0x1) != 0; + public fixed uint Reserved2A0[4]; + public uint SetCwdSlotCount; + public readonly int SetCwdSlotCountV => (int)(SetCwdSlotCount & 0xFF); + public uint SendPcasA; + public uint SendPcasB; + public readonly int SendPcasBFrom => (int)(SendPcasB & 0xFFFFFF); + public readonly int SendPcasBDelta => (int)((SendPcasB >> 24) & 0xFF); + public uint SendSignalingPcasB; + public readonly bool SendSignalingPcasBInvalidate => (SendSignalingPcasB & 0x1) != 0; + public readonly bool SendSignalingPcasBSchedule => (SendSignalingPcasB & 0x2) != 0; + public fixed uint Reserved2C0[9]; + public uint SetShaderLocalMemoryNonThrottledA; + public readonly int SetShaderLocalMemoryNonThrottledASizeUpper => (int)(SetShaderLocalMemoryNonThrottledA & 0xFF); + public uint SetShaderLocalMemoryNonThrottledB; + public uint SetShaderLocalMemoryNonThrottledC; + public readonly int SetShaderLocalMemoryNonThrottledCMaxSmCount => (int)(SetShaderLocalMemoryNonThrottledC & 0x1FF); + public uint SetShaderLocalMemoryThrottledA; + public readonly int SetShaderLocalMemoryThrottledASizeUpper => (int)(SetShaderLocalMemoryThrottledA & 0xFF); + public uint SetShaderLocalMemoryThrottledB; + public uint SetShaderLocalMemoryThrottledC; + public readonly int SetShaderLocalMemoryThrottledCMaxSmCount => (int)(SetShaderLocalMemoryThrottledC & 0x1FF); + public fixed uint Reserved2FC[5]; + public uint SetSpaVersion; + public readonly int SetSpaVersionMinor => (int)(SetSpaVersion & 0xFF); + public readonly int SetSpaVersionMajor => (int)((SetSpaVersion >> 8) & 0xFF); + public fixed uint Reserved314[123]; + public uint SetFalcon00; + public uint SetFalcon01; + public uint SetFalcon02; + public uint SetFalcon03; + public uint SetFalcon04; + public uint SetFalcon05; + public uint SetFalcon06; + public uint SetFalcon07; + public uint SetFalcon08; + public uint SetFalcon09; + public uint SetFalcon10; + public uint SetFalcon11; + public uint SetFalcon12; + public uint SetFalcon13; + public uint SetFalcon14; + public uint SetFalcon15; + public uint SetFalcon16; + public uint SetFalcon17; + public uint SetFalcon18; + public uint SetFalcon19; + public uint SetFalcon20; + public uint SetFalcon21; + public uint SetFalcon22; + public uint SetFalcon23; + public uint SetFalcon24; + public uint SetFalcon25; + public uint SetFalcon26; + public uint SetFalcon27; + public uint SetFalcon28; + public uint SetFalcon29; + public uint SetFalcon30; + public uint SetFalcon31; + public fixed uint Reserved580[127]; + public uint SetShaderLocalMemoryWindow; + public fixed uint Reserved780[4]; + public uint SetShaderLocalMemoryA; + public readonly int SetShaderLocalMemoryAAddressUpper => (int)(SetShaderLocalMemoryA & 0xFF); + public uint SetShaderLocalMemoryB; + public fixed uint Reserved798[383]; + public uint SetShaderCacheControl; + public readonly bool SetShaderCacheControlIcachePrefetchEnable => (SetShaderCacheControl & 0x1) != 0; + public fixed uint ReservedD98[19]; + public uint SetSmTimeoutInterval; + public readonly int SetSmTimeoutIntervalCounterBit => (int)(SetSmTimeoutInterval & 0x3F); + public fixed uint ReservedDE8[87]; + public uint SetSpareNoop12; + public uint SetSpareNoop13; + public uint SetSpareNoop14; + public uint SetSpareNoop15; + public fixed uint ReservedF54[59]; + public uint SetSpareNoop00; + public uint SetSpareNoop01; + public uint SetSpareNoop02; + public uint SetSpareNoop03; + public uint SetSpareNoop04; + public uint SetSpareNoop05; + public uint SetSpareNoop06; + public uint SetSpareNoop07; + public uint SetSpareNoop08; + public uint SetSpareNoop09; + public uint SetSpareNoop10; + public uint SetSpareNoop11; + public fixed uint Reserved1070[103]; + public uint InvalidateSamplerCacheAll; + public readonly bool InvalidateSamplerCacheAllV => (InvalidateSamplerCacheAll & 0x1) != 0; + public uint InvalidateTextureHeaderCacheAll; + public readonly bool InvalidateTextureHeaderCacheAllV => (InvalidateTextureHeaderCacheAll & 0x1) != 0; + public fixed uint Reserved1214[29]; + public uint InvalidateTextureDataCacheNoWfi; + public readonly InvalidateCacheLines InvalidateTextureDataCacheNoWfiLines => (InvalidateCacheLines)(InvalidateTextureDataCacheNoWfi & 0x1); + public readonly int InvalidateTextureDataCacheNoWfiTag => (int)((InvalidateTextureDataCacheNoWfi >> 4) & 0x3FFFFF); + public fixed uint Reserved128C[7]; + public uint ActivatePerfSettingsForComputeContext; + public readonly bool ActivatePerfSettingsForComputeContextAll => (ActivatePerfSettingsForComputeContext & 0x1) != 0; + public fixed uint Reserved12AC[33]; + public uint InvalidateSamplerCache; + public readonly InvalidateCacheLines InvalidateSamplerCacheLines => (InvalidateCacheLines)(InvalidateSamplerCache & 0x1); + public readonly int InvalidateSamplerCacheTag => (int)((InvalidateSamplerCache >> 4) & 0x3FFFFF); + public uint InvalidateTextureHeaderCache; + public readonly InvalidateCacheLines InvalidateTextureHeaderCacheLines => (InvalidateCacheLines)(InvalidateTextureHeaderCache & 0x1); + public readonly int InvalidateTextureHeaderCacheTag => (int)((InvalidateTextureHeaderCache >> 4) & 0x3FFFFF); + public uint InvalidateTextureDataCache; + public readonly InvalidateCacheLines InvalidateTextureDataCacheLines => (InvalidateCacheLines)(InvalidateTextureDataCache & 0x1); + public readonly int InvalidateTextureDataCacheTag => (int)((InvalidateTextureDataCache >> 4) & 0x3FFFFF); + public fixed uint Reserved133C[58]; + public uint InvalidateSamplerCacheNoWfi; + public readonly InvalidateCacheLines InvalidateSamplerCacheNoWfiLines => (InvalidateCacheLines)(InvalidateSamplerCacheNoWfi & 0x1); + public readonly int InvalidateSamplerCacheNoWfiTag => (int)((InvalidateSamplerCacheNoWfi >> 4) & 0x3FFFFF); + public fixed uint Reserved1428[64]; + public uint SetShaderExceptions; + public readonly bool SetShaderExceptionsEnable => (SetShaderExceptions & 0x1) != 0; + public fixed uint Reserved152C[9]; + public uint SetRenderEnableA; + public readonly int SetRenderEnableAOffsetUpper => (int)(SetRenderEnableA & 0xFF); + public uint SetRenderEnableB; + public uint SetRenderEnableC; + public readonly int SetRenderEnableCMode => (int)(SetRenderEnableC & 0x7); + public uint SetTexSamplerPoolA; + public readonly int SetTexSamplerPoolAOffsetUpper => (int)(SetTexSamplerPoolA & 0xFF); + public uint SetTexSamplerPoolB; + public uint SetTexSamplerPoolC; + public readonly int SetTexSamplerPoolCMaximumIndex => (int)(SetTexSamplerPoolC & 0xFFFFF); + public fixed uint Reserved1568[3]; + public uint SetTexHeaderPoolA; + public readonly int SetTexHeaderPoolAOffsetUpper => (int)(SetTexHeaderPoolA & 0xFF); + public uint SetTexHeaderPoolB; + public uint SetTexHeaderPoolC; + public readonly int SetTexHeaderPoolCMaximumIndex => (int)(SetTexHeaderPoolC & 0x3FFFFF); + public fixed uint Reserved1580[34]; + public uint SetProgramRegionA; + public readonly int SetProgramRegionAAddressUpper => (int)(SetProgramRegionA & 0xFF); + public uint SetProgramRegionB; + public fixed uint Reserved1610[34]; + public uint InvalidateShaderCachesNoWfi; + public readonly bool InvalidateShaderCachesNoWfiInstruction => (InvalidateShaderCachesNoWfi & 0x1) != 0; + public readonly bool InvalidateShaderCachesNoWfiGlobalData => (InvalidateShaderCachesNoWfi & 0x10) != 0; + public readonly bool InvalidateShaderCachesNoWfiConstant => (InvalidateShaderCachesNoWfi & 0x1000) != 0; + public fixed uint Reserved169C[170]; + public uint SetRenderEnableOverride; + public readonly SetRenderEnableOverrideMode SetRenderEnableOverrideMode => (SetRenderEnableOverrideMode)(SetRenderEnableOverride & 0x3); + public fixed uint Reserved1948[57]; + public uint PipeNop; + public uint SetSpare00; + public uint SetSpare01; + public uint SetSpare02; + public uint SetSpare03; + public fixed uint Reserved1A40[48]; + public uint SetReportSemaphoreA; + public readonly int SetReportSemaphoreAOffsetUpper => (int)(SetReportSemaphoreA & 0xFF); + public uint SetReportSemaphoreB; + public uint SetReportSemaphoreC; + public uint SetReportSemaphoreD; + public readonly SetReportSemaphoreDOperation SetReportSemaphoreDOperation => (SetReportSemaphoreDOperation)(SetReportSemaphoreD & 0x3); + public readonly bool SetReportSemaphoreDAwakenEnable => (SetReportSemaphoreD & 0x100000) != 0; + public readonly SetReportSemaphoreDStructureSize SetReportSemaphoreDStructureSize => (SetReportSemaphoreDStructureSize)((SetReportSemaphoreD >> 28) & 0x1); + public readonly bool SetReportSemaphoreDFlushDisable => (SetReportSemaphoreD & 0x4) != 0; + public readonly bool SetReportSemaphoreDReductionEnable => (SetReportSemaphoreD & 0x8) != 0; + public readonly SetReportSemaphoreDReductionOp SetReportSemaphoreDReductionOp => (SetReportSemaphoreDReductionOp)((SetReportSemaphoreD >> 9) & 0x7); + public readonly SetReportSemaphoreDReductionFormat SetReportSemaphoreDReductionFormat => (SetReportSemaphoreDReductionFormat)((SetReportSemaphoreD >> 17) & 0x3); + public fixed uint Reserved1B10[702]; + public uint SetBindlessTexture; + public readonly int SetBindlessTextureConstantBufferSlotSelect => (int)(SetBindlessTexture & 0x7); + public uint SetTrapHandler; + public fixed uint Reserved2610[843]; + public Array8 SetShaderPerformanceCounterValueUpper; + public Array8 SetShaderPerformanceCounterValue; + public Array8 SetShaderPerformanceCounterEvent; + public int SetShaderPerformanceCounterEventEvent(int i) => (int)((SetShaderPerformanceCounterEvent[i] >> 0) & 0xFF); + public Array8 SetShaderPerformanceCounterControlA; + public int SetShaderPerformanceCounterControlAEvent0(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 0) & 0x3); + public int SetShaderPerformanceCounterControlABitSelect0(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 2) & 0x7); + public int SetShaderPerformanceCounterControlAEvent1(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 5) & 0x3); + public int SetShaderPerformanceCounterControlABitSelect1(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 7) & 0x7); + public int SetShaderPerformanceCounterControlAEvent2(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 10) & 0x3); + public int SetShaderPerformanceCounterControlABitSelect2(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 12) & 0x7); + public int SetShaderPerformanceCounterControlAEvent3(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 15) & 0x3); + public int SetShaderPerformanceCounterControlABitSelect3(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 17) & 0x7); + public int SetShaderPerformanceCounterControlAEvent4(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 20) & 0x3); + public int SetShaderPerformanceCounterControlABitSelect4(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 22) & 0x7); + public int SetShaderPerformanceCounterControlAEvent5(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 25) & 0x3); + public int SetShaderPerformanceCounterControlABitSelect5(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 27) & 0x7); + public int SetShaderPerformanceCounterControlASpare(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 30) & 0x3); + public Array8 SetShaderPerformanceCounterControlB; + public bool SetShaderPerformanceCounterControlBEdge(int i) => (SetShaderPerformanceCounterControlB[i] & 0x1) != 0; + public int SetShaderPerformanceCounterControlBMode(int i) => (int)((SetShaderPerformanceCounterControlB[i] >> 1) & 0x3); + public bool SetShaderPerformanceCounterControlBWindowed(int i) => (SetShaderPerformanceCounterControlB[i] & 0x8) != 0; + public int SetShaderPerformanceCounterControlBFunc(int i) => (int)((SetShaderPerformanceCounterControlB[i] >> 4) & 0xFFFF); + public uint SetShaderPerformanceCounterTrapControl; + public readonly int SetShaderPerformanceCounterTrapControlMask => (int)(SetShaderPerformanceCounterTrapControl & 0xFF); + public uint StartShaderPerformanceCounter; + public readonly int StartShaderPerformanceCounterCounterMask => (int)(StartShaderPerformanceCounter & 0xFF); + public uint StopShaderPerformanceCounter; + public readonly int StopShaderPerformanceCounterCounterMask => (int)(StopShaderPerformanceCounter & 0xFF); + public fixed uint Reserved33E8[6]; + public Array256 SetMmeShadowScratch; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeQmd.cs b/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeQmd.cs new file mode 100644 index 00000000..4e750ff5 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeQmd.cs @@ -0,0 +1,275 @@ +using Ryujinx.Graphics.Gpu.Engine.Types; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Compute +{ + /// + /// Type of the dependent Queue Meta Data. + /// + enum DependentQmdType + { + Queue, + Grid, + } + + /// + /// Type of the release memory barrier. + /// + enum ReleaseMembarType + { + FeNone, + FeSysmembar, + } + + /// + /// Type of the CWD memory barrier. + /// + enum CwdMembarType + { + L1None, + L1Sysmembar, + L1Membar, + } + + /// + /// NaN behavior of 32-bits float operations on the shader. + /// + enum Fp32NanBehavior + { + Legacy, + Fp64Compatible, + } + + /// + /// NaN behavior of 32-bits float to integer conversion on the shader. + /// + enum Fp32F2iNanBehavior + { + PassZero, + PassIndefinite, + } + + /// + /// Limit of calls. + /// + enum ApiVisibleCallLimit + { + _32, + NoCheck, + } + + /// + /// Shared memory bank mapping mode. + /// + enum SharedMemoryBankMapping + { + FourBytesPerBank, + EightBytesPerBank, + } + + /// + /// Denormal behavior of 32-bits float narrowing instructions. + /// + enum Fp32NarrowInstruction + { + KeepDenorms, + FlushDenorms, + } + + /// + /// Configuration of the L1 cache. + /// + enum L1Configuration + { + DirectlyAddressableMemorySize16kb, + DirectlyAddressableMemorySize32kb, + DirectlyAddressableMemorySize48kb, + } + + /// + /// Reduction operation. + /// + enum ReductionOp + { + RedAdd, + RedMin, + RedMax, + RedInc, + RedDec, + RedAnd, + RedOr, + RedXor, + } + + /// + /// Reduction format. + /// + enum ReductionFormat + { + Unsigned32, + Signed32, + } + + /// + /// Size of a structure in words. + /// + enum StructureSize + { + FourWords, + OneWord, + } + + /// + /// Compute Queue Meta Data. + /// + unsafe struct ComputeQmd + { + private fixed int _words[64]; + + public readonly int OuterPut => BitRange(30, 0); + public readonly bool OuterOverflow => Bit(31); + public readonly int OuterGet => BitRange(62, 32); + public readonly bool OuterStickyOverflow => Bit(63); + public readonly int InnerGet => BitRange(94, 64); + public readonly bool InnerOverflow => Bit(95); + public readonly int InnerPut => BitRange(126, 96); + public readonly bool InnerStickyOverflow => Bit(127); + public readonly int QmdReservedAA => BitRange(159, 128); + public readonly int DependentQmdPointer => BitRange(191, 160); + public readonly int QmdGroupId => BitRange(197, 192); + public readonly bool SmGlobalCachingEnable => Bit(198); + public readonly bool RunCtaInOneSmPartition => Bit(199); + public readonly bool IsQueue => Bit(200); + public readonly bool AddToHeadOfQmdGroupLinkedList => Bit(201); + public readonly bool SemaphoreReleaseEnable0 => Bit(202); + public readonly bool SemaphoreReleaseEnable1 => Bit(203); + public readonly bool RequireSchedulingPcas => Bit(204); + public readonly bool DependentQmdScheduleEnable => Bit(205); + public readonly DependentQmdType DependentQmdType => (DependentQmdType)BitRange(206, 206); + public readonly bool DependentQmdFieldCopy => Bit(207); + public readonly int QmdReservedB => BitRange(223, 208); + public readonly int CircularQueueSize => BitRange(248, 224); + public readonly bool QmdReservedC => Bit(249); + public readonly bool InvalidateTextureHeaderCache => Bit(250); + public readonly bool InvalidateTextureSamplerCache => Bit(251); + public readonly bool InvalidateTextureDataCache => Bit(252); + public readonly bool InvalidateShaderDataCache => Bit(253); + public readonly bool InvalidateInstructionCache => Bit(254); + public readonly bool InvalidateShaderConstantCache => Bit(255); + public readonly int ProgramOffset => BitRange(287, 256); + public readonly int CircularQueueAddrLower => BitRange(319, 288); + public readonly int CircularQueueAddrUpper => BitRange(327, 320); + public readonly int QmdReservedD => BitRange(335, 328); + public readonly int CircularQueueEntrySize => BitRange(351, 336); + public readonly int CwdReferenceCountId => BitRange(357, 352); + public readonly int CwdReferenceCountDeltaMinusOne => BitRange(365, 358); + public readonly ReleaseMembarType ReleaseMembarType => (ReleaseMembarType)BitRange(366, 366); + public readonly bool CwdReferenceCountIncrEnable => Bit(367); + public readonly CwdMembarType CwdMembarType => (CwdMembarType)BitRange(369, 368); + public readonly bool SequentiallyRunCtas => Bit(370); + public readonly bool CwdReferenceCountDecrEnable => Bit(371); + public readonly bool Throttled => Bit(372); + public readonly Fp32NanBehavior Fp32NanBehavior => (Fp32NanBehavior)BitRange(376, 376); + public readonly Fp32F2iNanBehavior Fp32F2iNanBehavior => (Fp32F2iNanBehavior)BitRange(377, 377); + public readonly ApiVisibleCallLimit ApiVisibleCallLimit => (ApiVisibleCallLimit)BitRange(378, 378); + public readonly SharedMemoryBankMapping SharedMemoryBankMapping => (SharedMemoryBankMapping)BitRange(379, 379); + public readonly SamplerIndex SamplerIndex => (SamplerIndex)BitRange(382, 382); + public readonly Fp32NarrowInstruction Fp32NarrowInstruction => (Fp32NarrowInstruction)BitRange(383, 383); + public readonly int CtaRasterWidth => BitRange(415, 384); + public readonly int CtaRasterHeight => BitRange(431, 416); + public readonly int CtaRasterDepth => BitRange(447, 432); + public readonly int CtaRasterWidthResume => BitRange(479, 448); + public readonly int CtaRasterHeightResume => BitRange(495, 480); + public readonly int CtaRasterDepthResume => BitRange(511, 496); + public readonly int QueueEntriesPerCtaMinusOne => BitRange(518, 512); + public readonly int CoalesceWaitingPeriod => BitRange(529, 522); + public readonly int SharedMemorySize => BitRange(561, 544); + public readonly int QmdReservedG => BitRange(575, 562); + public readonly int QmdVersion => BitRange(579, 576); + public readonly int QmdMajorVersion => BitRange(583, 580); + public readonly int QmdReservedH => BitRange(591, 584); + public readonly int CtaThreadDimension0 => BitRange(607, 592); + public readonly int CtaThreadDimension1 => BitRange(623, 608); + public readonly int CtaThreadDimension2 => BitRange(639, 624); + public readonly bool ConstantBufferValid(int i) => Bit(640 + i * 1); + public readonly int QmdReservedI => BitRange(668, 648); + public readonly L1Configuration L1Configuration => (L1Configuration)BitRange(671, 669); + public readonly int SmDisableMaskLower => BitRange(703, 672); + public readonly int SmDisableMaskUpper => BitRange(735, 704); + public readonly int Release0AddressLower => BitRange(767, 736); + public readonly int Release0AddressUpper => BitRange(775, 768); + public readonly int QmdReservedJ => BitRange(783, 776); + public readonly ReductionOp Release0ReductionOp => (ReductionOp)BitRange(790, 788); + public readonly bool QmdReservedK => Bit(791); + public readonly ReductionFormat Release0ReductionFormat => (ReductionFormat)BitRange(793, 792); + public readonly bool Release0ReductionEnable => Bit(794); + public readonly StructureSize Release0StructureSize => (StructureSize)BitRange(799, 799); + public readonly int Release0Payload => BitRange(831, 800); + public readonly int Release1AddressLower => BitRange(863, 832); + public readonly int Release1AddressUpper => BitRange(871, 864); + public readonly int QmdReservedL => BitRange(879, 872); + public readonly ReductionOp Release1ReductionOp => (ReductionOp)BitRange(886, 884); + public readonly bool QmdReservedM => Bit(887); + public readonly ReductionFormat Release1ReductionFormat => (ReductionFormat)BitRange(889, 888); + public readonly bool Release1ReductionEnable => Bit(890); + public readonly StructureSize Release1StructureSize => (StructureSize)BitRange(895, 895); + public readonly int Release1Payload => BitRange(927, 896); + public readonly int ConstantBufferAddrLower(int i) => BitRange(959 + i * 64, 928 + i * 64); + public readonly int ConstantBufferAddrUpper(int i) => BitRange(967 + i * 64, 960 + i * 64); + public readonly int ConstantBufferReservedAddr(int i) => BitRange(973 + i * 64, 968 + i * 64); + public readonly bool ConstantBufferInvalidate(int i) => Bit(974 + i * 64); + public readonly int ConstantBufferSize(int i) => BitRange(991 + i * 64, 975 + i * 64); + public readonly int ShaderLocalMemoryLowSize => BitRange(1463, 1440); + public readonly int QmdReservedN => BitRange(1466, 1464); + public readonly int BarrierCount => BitRange(1471, 1467); + public readonly int ShaderLocalMemoryHighSize => BitRange(1495, 1472); + public readonly int RegisterCount => BitRange(1503, 1496); + public readonly int ShaderLocalMemoryCrsSize => BitRange(1527, 1504); + public readonly int SassVersion => BitRange(1535, 1528); + public readonly int HwOnlyInnerGet => BitRange(1566, 1536); + public readonly bool HwOnlyRequireSchedulingPcas => Bit(1567); + public readonly int HwOnlyInnerPut => BitRange(1598, 1568); + public readonly bool HwOnlyScgType => Bit(1599); + public readonly int HwOnlySpanListHeadIndex => BitRange(1629, 1600); + public readonly bool QmdReservedQ => Bit(1630); + public readonly bool HwOnlySpanListHeadIndexValid => Bit(1631); + public readonly int HwOnlySkedNextQmdPointer => BitRange(1663, 1632); + public readonly int QmdSpareE => BitRange(1695, 1664); + public readonly int QmdSpareF => BitRange(1727, 1696); + public readonly int QmdSpareG => BitRange(1759, 1728); + public readonly int QmdSpareH => BitRange(1791, 1760); + public readonly int QmdSpareI => BitRange(1823, 1792); + public readonly int QmdSpareJ => BitRange(1855, 1824); + public readonly int QmdSpareK => BitRange(1887, 1856); + public readonly int QmdSpareL => BitRange(1919, 1888); + public readonly int QmdSpareM => BitRange(1951, 1920); + public readonly int QmdSpareN => BitRange(1983, 1952); + public readonly int DebugIdUpper => BitRange(2015, 1984); + public readonly int DebugIdLower => BitRange(2047, 2016); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private readonly bool Bit(int bit) + { + if ((uint)bit >= 64 * 32) + { + throw new ArgumentOutOfRangeException(nameof(bit)); + } + + return (_words[bit >> 5] & (1 << (bit & 31))) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private readonly int BitRange(int upper, int lower) + { + if ((uint)lower >= 64 * 32) + { + throw new ArgumentOutOfRangeException(nameof(lower)); + } + + int mask = (int)(uint.MaxValue >> (32 - (upper - lower + 1))); + + return (_words[lower >> 5] >> (lower & 31)) & mask; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/ConditionalRenderEnabled.cs b/src/Ryujinx.Graphics.Gpu/Engine/ConditionalRenderEnabled.cs new file mode 100644 index 00000000..9edf8068 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/ConditionalRenderEnabled.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gpu.Engine +{ + /// + /// Conditional rendering enable. + /// + enum ConditionalRenderEnabled + { + False, + True, + Host, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/DeviceStateWithShadow.cs b/src/Ryujinx.Graphics.Gpu/Engine/DeviceStateWithShadow.cs new file mode 100644 index 00000000..a2e5b116 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/DeviceStateWithShadow.cs @@ -0,0 +1,101 @@ +using Ryujinx.Graphics.Device; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + /// + /// State interface with a shadow memory control register. + /// + interface IShadowState + { + /// + /// MME shadow ram control mode. + /// + SetMmeShadowRamControlMode SetMmeShadowRamControlMode { get; } + } + + /// + /// Represents a device's state, with a additional shadow state. + /// + /// Type of the state + class DeviceStateWithShadow<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TState> : IDeviceState where TState : unmanaged, IShadowState + { + private readonly DeviceState _state; + private readonly DeviceState _shadowState; + + /// + /// Current device state. + /// + public ref TState State => ref _state.State; + + /// + /// Current shadow state. + /// + public ref TState ShadowState => ref _shadowState.State; + + /// + /// Creates a new instance of the device state, with shadow state. + /// + /// Optional that will be called if a register specified by name is read or written + /// Optional callback to be used for debug log messages + public DeviceStateWithShadow(IReadOnlyDictionary callbacks = null, Action debugLogCallback = null) + { + _state = new DeviceState(callbacks, debugLogCallback); + _shadowState = new DeviceState(); + } + + /// + /// Reads a value from a register. + /// + /// Register offset in bytes + /// Value stored on the register + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Read(int offset) + { + return _state.Read(offset); + } + + /// + /// Writes a value to a register. + /// + /// Register offset in bytes + /// Value to be written + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(int offset, int value) + { + WriteWithRedundancyCheck(offset, value, out _); + } + + /// + /// Writes a value to a register, returning a value indicating if + /// is different from the current value on the register. + /// + /// Register offset in bytes + /// Value to be written + /// True if the value was changed, false otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteWithRedundancyCheck(int offset, int value, out bool changed) + { + var shadowRamControl = _state.State.SetMmeShadowRamControlMode; + if (shadowRamControl == SetMmeShadowRamControlMode.MethodPassthrough || offset < 0x200) + { + _state.WriteWithRedundancyCheck(offset, value, out changed); + } + else if (shadowRamControl == SetMmeShadowRamControlMode.MethodTrack || + shadowRamControl == SetMmeShadowRamControlMode.MethodTrackWithFilter) + { + _shadowState.Write(offset, value); + _state.WriteWithRedundancyCheck(offset, value, out changed); + } + else /* if (shadowRamControl == SetMmeShadowRamControlMode.MethodReplay) */ + { + Debug.Assert(shadowRamControl == SetMmeShadowRamControlMode.MethodReplay); + _state.WriteWithRedundancyCheck(offset, _shadowState.Read(offset), out changed); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs new file mode 100644 index 00000000..f2bfd8ea --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs @@ -0,0 +1,707 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Texture; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace Ryujinx.Graphics.Gpu.Engine.Dma +{ + /// + /// Represents a DMA copy engine class. + /// + class DmaClass : IDeviceState + { + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly ThreedClass _3dEngine; + private readonly DeviceState _state; + + /// + /// Copy flags passed on DMA launch. + /// + [Flags] + private enum CopyFlags + { + SrcLinear = 1 << 7, + DstLinear = 1 << 8, + MultiLineEnable = 1 << 9, + RemapEnable = 1 << 10, + } + + /// + /// Texture parameters for copy. + /// + private readonly struct TextureParams + { + /// + /// Copy region X coordinate. + /// + public readonly int RegionX; + + /// + /// Copy region Y coordinate. + /// + public readonly int RegionY; + + /// + /// Offset from the base pointer of the data in memory. + /// + public readonly int BaseOffset; + + /// + /// Bytes per pixel. + /// + public readonly int Bpp; + + /// + /// Whether the texture is linear. If false, the texture is block linear. + /// + public readonly bool Linear; + + /// + /// Pixel offset from XYZ coordinates calculator. + /// + public readonly OffsetCalculator Calculator; + + /// + /// Creates texture parameters. + /// + /// Copy region X coordinate + /// Copy region Y coordinate + /// Offset from the base pointer of the data in memory + /// Bytes per pixel + /// Whether the texture is linear. If false, the texture is block linear + /// Pixel offset from XYZ coordinates calculator + 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; + } + + /// + /// Creates a new instance of the DMA copy engine class. + /// + /// GPU context + /// GPU channel + /// 3D engine + public DmaClass(GpuContext context, GpuChannel channel, ThreedClass threedEngine) + { + _context = context; + _channel = channel; + _3dEngine = threedEngine; + _state = new DeviceState(new Dictionary + { + { nameof(DmaClassState.LaunchDma), new RwCallback(LaunchDma, null) }, + }); + } + + /// + /// Reads data from the class registers. + /// + /// Register byte offset + /// Data at the specified offset + public int Read(int offset) => _state.Read(offset); + + /// + /// Writes data to the class registers. + /// + /// Register byte offset + /// Data to be written + public void Write(int offset, int data) => _state.Write(offset, data); + + /// + /// Determine if a buffer-to-texture region covers the entirety of a texture. + /// + /// Texture to compare + /// True if the texture is linear, false if block linear + /// Texture bytes per pixel + /// Texture stride + /// Number of pixels to be copied + /// Number of lines to be copied + /// + private static bool IsTextureCopyComplete(DmaTexture tex, bool linear, int bpp, int stride, int xCount, int yCount) + { + if (linear) + { + // If the stride is negative, the texture has to be flipped, so + // the fast copy is not trivial, use the slow path. + if (stride <= 0) + { + return false; + } + + int alignWidth = Constants.StrideAlignment / bpp; + return stride / bpp == BitUtils.AlignUp(xCount, alignWidth); + } + else + { + int alignWidth = Constants.GobAlignment / bpp; + return tex.RegionX == 0 && + tex.RegionY == 0 && + tex.Width == BitUtils.AlignUp(xCount, alignWidth) && + tex.Height == yCount; + } + } + + /// + /// Releases a semaphore for a given LaunchDma method call. + /// + /// The LaunchDma call argument + private void ReleaseSemaphore(int argument) + { + LaunchDmaSemaphoreType type = (LaunchDmaSemaphoreType)((argument >> 3) & 0x3); + if (type != LaunchDmaSemaphoreType.None) + { + ulong address = ((ulong)_state.State.SetSemaphoreA << 32) | _state.State.SetSemaphoreB; + if (type == LaunchDmaSemaphoreType.ReleaseOneWordSemaphore) + { + _channel.MemoryManager.Write(address, _state.State.SetSemaphorePayload); + } + else /* if (type == LaunchDmaSemaphoreType.ReleaseFourWordSemaphore) */ + { + _channel.MemoryManager.Write(address + 8, _context.GetTimestamp()); + _channel.MemoryManager.Write(address, (ulong)_state.State.SetSemaphorePayload); + } + } + } + + /// + /// Performs a buffer to buffer, or buffer to texture copy. + /// + /// The LaunchDma call argument + private void DmaCopy(int argument) + { + var memoryManager = _channel.MemoryManager; + + CopyFlags copyFlags = (CopyFlags)argument; + + bool srcLinear = copyFlags.HasFlag(CopyFlags.SrcLinear); + bool dstLinear = copyFlags.HasFlag(CopyFlags.DstLinear); + bool copy2D = copyFlags.HasFlag(CopyFlags.MultiLineEnable); + bool remap = copyFlags.HasFlag(CopyFlags.RemapEnable); + + uint size = _state.State.LineLengthIn; + + if (size == 0) + { + return; + } + + ulong srcGpuVa = ((ulong)_state.State.OffsetInUpperUpper << 32) | _state.State.OffsetInLower; + ulong dstGpuVa = ((ulong)_state.State.OffsetOutUpperUpper << 32) | _state.State.OffsetOutLower; + + int xCount = (int)_state.State.LineLengthIn; + int yCount = (int)_state.State.LineCount; + + _channel.TextureManager.RefreshModifiedTextures(); + _3dEngine.CreatePendingSyncs(); + _3dEngine.FlushUboDirty(); + + if (copy2D) + { + // Buffer to texture copy. + int componentSize = (int)_state.State.SetRemapComponentsComponentSize + 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(ref _state.State.SetDstBlockSize); + var src = Unsafe.As(ref _state.State.SetSrcBlockSize); + + int srcRegionX = 0, srcRegionY = 0, dstRegionX = 0, dstRegionY = 0; + + if (!srcLinear) + { + srcRegionX = src.RegionX; + srcRegionY = src.RegionY; + } + + if (!dstLinear) + { + dstRegionX = dst.RegionX; + dstRegionY = dst.RegionY; + } + + int srcStride = (int)_state.State.PitchIn; + int dstStride = (int)_state.State.PitchOut; + + var srcCalculator = new OffsetCalculator( + src.Width, + src.Height, + srcStride, + srcLinear, + src.MemoryLayout.UnpackGobBlocksInY(), + src.MemoryLayout.UnpackGobBlocksInZ(), + srcBpp); + + var dstCalculator = new OffsetCalculator( + dst.Width, + dst.Height, + dstStride, + dstLinear, + dst.MemoryLayout.UnpackGobBlocksInY(), + dst.MemoryLayout.UnpackGobBlocksInZ(), + dstBpp); + + (int srcBaseOffset, int srcSize) = srcCalculator.GetRectangleRange(srcRegionX, srcRegionY, xCount, yCount); + (int dstBaseOffset, int dstSize) = dstCalculator.GetRectangleRange(dstRegionX, dstRegionY, xCount, yCount); + + if (srcLinear && srcStride < 0) + { + srcBaseOffset += srcStride * (yCount - 1); + } + + if (dstLinear && dstStride < 0) + { + dstBaseOffset += dstStride * (yCount - 1); + } + + // If remapping is disabled, we always copy the components directly, in order. + // If it's enabled, but the mapping is just XYZW, we also copy them in order. + bool isIdentityRemap = !remap || + (_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.SrcX && + (dstComponents < 2 || _state.State.SetRemapComponentsDstY == SetRemapComponentsDst.SrcY) && + (dstComponents < 3 || _state.State.SetRemapComponentsDstZ == SetRemapComponentsDst.SrcZ) && + (dstComponents < 4 || _state.State.SetRemapComponentsDstW == SetRemapComponentsDst.SrcW)); + + bool completeSource = IsTextureCopyComplete(src, srcLinear, srcBpp, srcStride, xCount, yCount); + bool completeDest = IsTextureCopyComplete(dst, dstLinear, dstBpp, dstStride, xCount, yCount); + + // Check if the source texture exists on the GPU, if it does, do a GPU side copy. + // Otherwise, we would need to flush the source texture which is costly. + // We don't expect the source to be linear in such cases, as linear source usually indicates buffer or CPU written data. + + if (completeSource && completeDest && !srcLinear && isIdentityRemap) + { + var source = memoryManager.Physical.TextureCache.FindTexture( + memoryManager, + srcGpuVa, + srcBpp, + srcStride, + src.Height, + xCount, + yCount, + srcLinear, + src.MemoryLayout.UnpackGobBlocksInY(), + src.MemoryLayout.UnpackGobBlocksInZ()); + + if (source != null && source.Height == yCount) + { + source.SynchronizeMemory(); + + var target = memoryManager.Physical.TextureCache.FindOrCreateTexture( + memoryManager, + source.Info.FormatInfo, + dstGpuVa, + xCount, + yCount, + dstStride, + dstLinear, + dst.MemoryLayout.UnpackGobBlocksInY(), + dst.MemoryLayout.UnpackGobBlocksInZ()); + + if (source.ScaleFactor != target.ScaleFactor) + { + target.PropagateScale(source); + } + + source.HostTexture.CopyTo(target.HostTexture, 0, 0); + target.SignalModified(); + return; + } + } + + ReadOnlySpan srcSpan = memoryManager.GetSpan(srcGpuVa + (ulong)srcBaseOffset, srcSize, true); + + // Try to set the texture data directly, + // but only if we are doing a complete copy, + // and not for block linear to linear copies, since those are typically accessed from the CPU. + + if (completeSource && completeDest && !(dstLinear && !srcLinear) && isIdentityRemap) + { + var target = memoryManager.Physical.TextureCache.FindTexture( + memoryManager, + dstGpuVa, + dstBpp, + dstStride, + dst.Height, + xCount, + yCount, + dstLinear, + dst.MemoryLayout.UnpackGobBlocksInY(), + dst.MemoryLayout.UnpackGobBlocksInZ()); + + if (target != null) + { + IMemoryOwner data; + if (srcLinear) + { + data = LayoutConverter.ConvertLinearStridedToLinear( + target.Info.Width, + target.Info.Height, + 1, + 1, + xCount * srcBpp, + srcStride, + target.Info.FormatInfo.BytesPerPixel, + srcSpan); + } + else + { + data = LayoutConverter.ConvertBlockLinearToLinear( + src.Width, + src.Height, + src.Depth, + 1, + 1, + 1, + 1, + 1, + srcBpp, + src.MemoryLayout.UnpackGobBlocksInY(), + src.MemoryLayout.UnpackGobBlocksInZ(), + 1, + new SizeInfo((int)target.Size), + srcSpan); + } + + target.SynchronizeMemory(); + target.SetData(data); + target.SignalModified(); + return; + } + else if (srcCalculator.LayoutMatches(dstCalculator)) + { + // No layout conversion has to be performed, just copy the data entirely. + memoryManager.Write(dstGpuVa + (ulong)dstBaseOffset, srcSpan); + return; + } + } + + // 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 dstSpan = memoryManager.GetSpan(dstGpuVa + (ulong)dstBaseOffset, dstSize).ToArray(); + + TextureParams srcParams = new(srcRegionX, srcRegionY, srcBaseOffset, srcBpp, srcLinear, srcCalculator); + TextureParams dstParams = new(dstRegionX, dstRegionY, dstBaseOffset, dstBpp, dstLinear, dstCalculator); + + if (isIdentityRemap) + { + // The order of the components doesn't change, so we can just copy directly + // (with layout conversion if necessary). + + switch (srcBpp) + { + case 1: + Copy(dstSpan, srcSpan, dstParams, srcParams); + break; + case 2: + Copy(dstSpan, srcSpan, dstParams, srcParams); + break; + case 4: + Copy(dstSpan, srcSpan, dstParams, srcParams); + break; + case 8: + Copy(dstSpan, srcSpan, dstParams, srcParams); + break; + case 12: + Copy(dstSpan, srcSpan, dstParams, srcParams); + break; + case 16: + Copy>(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(dstSpan, srcSpan, dstParams, srcParams); + break; + case 2: + CopyShuffle(dstSpan, srcSpan, dstParams, srcParams); + break; + case 3: + CopyShuffle(dstSpan, srcSpan, dstParams, srcParams); + break; + case 4: + CopyShuffle(dstSpan, srcSpan, dstParams, srcParams); + break; + default: + throw new NotSupportedException($"Unable to copy ${componentSize} component size."); + } + } + + memoryManager.Write(dstGpuVa + (ulong)dstBaseOffset, dstSpan); + } + else + { + if (remap && + _state.State.SetRemapComponentsDstX == SetRemapComponentsDst.ConstA && + _state.State.SetRemapComponentsDstY == SetRemapComponentsDst.ConstA && + _state.State.SetRemapComponentsDstZ == SetRemapComponentsDst.ConstA && + _state.State.SetRemapComponentsDstW == SetRemapComponentsDst.ConstA && + _state.State.SetRemapComponentsNumSrcComponents == SetRemapComponentsNumComponents.One && + _state.State.SetRemapComponentsNumDstComponents == SetRemapComponentsNumComponents.One && + _state.State.SetRemapComponentsComponentSize == SetRemapComponentsComponentSize.Four) + { + // Fast path for clears when remap is enabled. + memoryManager.Physical.BufferCache.ClearBuffer(memoryManager, dstGpuVa, size * 4, _state.State.SetRemapConstA); + } + else + { + // TODO: Implement remap functionality. + // Buffer to buffer copy. + + bool srcIsPitchKind = memoryManager.GetKind(srcGpuVa).IsPitch(); + bool dstIsPitchKind = memoryManager.GetKind(dstGpuVa).IsPitch(); + + if (!srcIsPitchKind && dstIsPitchKind) + { + CopyGobBlockLinearToLinear(memoryManager, srcGpuVa, dstGpuVa, size); + } + else if (srcIsPitchKind && !dstIsPitchKind) + { + CopyGobLinearToBlockLinear(memoryManager, srcGpuVa, dstGpuVa, size); + } + else + { + memoryManager.Physical.BufferCache.CopyBuffer(memoryManager, srcGpuVa, dstGpuVa, size); + } + } + } + } + + /// + /// Copies data from one texture to another, while performing layout conversion if necessary. + /// + /// Pixel type + /// Destination texture memory region + /// Source texture memory region + /// Destination texture parameters + /// Source texture parameters + private unsafe void Copy(Span dstSpan, ReadOnlySpan 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); + } + } + } + } + } + + /// + /// Sets texture pixel data to a constant value, while performing layout conversion if necessary. + /// + /// Pixel type + /// Destination texture memory region + /// Destination texture parameters + /// Constant pixel value to be set + private unsafe void Fill(Span 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; + } + } + } + } + + /// + /// Copies data from one texture to another, while performing layout conversion and component shuffling if necessary. + /// + /// Pixel type + /// Destination texture memory region + /// Source texture memory region + /// Destination texture parameters + /// Source texture parameters + private void CopyShuffle(Span dstSpan, ReadOnlySpan 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(dstSpan[(Unsafe.SizeOf() * i)..], srcSpan, dst, src); + break; + case SetRemapComponentsDst.SrcY: + Copy(dstSpan[(Unsafe.SizeOf() * i)..], srcSpan[Unsafe.SizeOf()..], dst, src); + break; + case SetRemapComponentsDst.SrcZ: + Copy(dstSpan[(Unsafe.SizeOf() * i)..], srcSpan[(Unsafe.SizeOf() * 2)..], dst, src); + break; + case SetRemapComponentsDst.SrcW: + Copy(dstSpan[(Unsafe.SizeOf() * i)..], srcSpan[(Unsafe.SizeOf() * 3)..], dst, src); + break; + case SetRemapComponentsDst.ConstA: + Fill(dstSpan[(Unsafe.SizeOf() * i)..], dst, Unsafe.As(ref _state.State.SetRemapConstA)); + break; + case SetRemapComponentsDst.ConstB: + Fill(dstSpan[(Unsafe.SizeOf() * i)..], dst, Unsafe.As(ref _state.State.SetRemapConstB)); + break; + } + } + } + + /// + /// Copies block linear data with block linear GOBs to a block linear destination with linear GOBs. + /// + /// GPU memory manager + /// Source GPU virtual address + /// Destination GPU virtual address + /// Size in bytes of the copy + private static void CopyGobBlockLinearToLinear(MemoryManager memoryManager, ulong srcGpuVa, ulong dstGpuVa, ulong size) + { + if (((srcGpuVa | dstGpuVa | size) & 0xf) == 0) + { + for (ulong offset = 0; offset < size; offset += 16) + { + Vector128 data = memoryManager.Read>(ConvertGobLinearToBlockLinearAddress(srcGpuVa + offset), true); + memoryManager.Write(dstGpuVa + offset, data); + } + } + else + { + for (ulong offset = 0; offset < size; offset++) + { + byte data = memoryManager.Read(ConvertGobLinearToBlockLinearAddress(srcGpuVa + offset), true); + memoryManager.Write(dstGpuVa + offset, data); + } + } + } + + /// + /// Copies block linear data with linear GOBs to a block linear destination with block linear GOBs. + /// + /// GPU memory manager + /// Source GPU virtual address + /// Destination GPU virtual address + /// Size in bytes of the copy + private static void CopyGobLinearToBlockLinear(MemoryManager memoryManager, ulong srcGpuVa, ulong dstGpuVa, ulong size) + { + if (((srcGpuVa | dstGpuVa | size) & 0xf) == 0) + { + for (ulong offset = 0; offset < size; offset += 16) + { + Vector128 data = memoryManager.Read>(srcGpuVa + offset, true); + memoryManager.Write(ConvertGobLinearToBlockLinearAddress(dstGpuVa + offset), data); + } + } + else + { + for (ulong offset = 0; offset < size; offset++) + { + byte data = memoryManager.Read(srcGpuVa + offset, true); + memoryManager.Write(ConvertGobLinearToBlockLinearAddress(dstGpuVa + offset), data); + } + } + } + + /// + /// Calculates the GOB block linear address from a linear address. + /// + /// Linear address + /// Block linear address + private static ulong ConvertGobLinearToBlockLinearAddress(ulong address) + { + // y2 y1 y0 x5 x4 x3 x2 x1 x0 -> x5 y2 y1 x4 y0 x3 x2 x1 x0 + return (address & ~0x1f0UL) | + ((address & 0x40) >> 2) | + ((address & 0x10) << 1) | + ((address & 0x180) >> 1) | + ((address & 0x20) << 3); + } + + /// + /// Performs a buffer to buffer, or buffer to texture copy, then optionally releases a semaphore. + /// + /// Method call argument + private void LaunchDma(int argument) + { + DmaCopy(argument); + ReleaseSemaphore(argument); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClassState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClassState.cs new file mode 100644 index 00000000..f258c007 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClassState.cs @@ -0,0 +1,271 @@ +// This file was auto-generated from NVIDIA official Maxwell definitions. + +namespace Ryujinx.Graphics.Gpu.Engine.Dma +{ + /// + /// Physical mode target. + /// + enum SetPhysModeTarget + { + LocalFb = 0, + CoherentSysmem = 1, + NoncoherentSysmem = 2, + } + + /// + /// DMA data transfer type. + /// + enum LaunchDmaDataTransferType + { + None = 0, + Pipelined = 1, + NonPipelined = 2, + } + + /// + /// DMA semaphore type. + /// + enum LaunchDmaSemaphoreType + { + None = 0, + ReleaseOneWordSemaphore = 1, + ReleaseFourWordSemaphore = 2, + } + + /// + /// DMA interrupt type. + /// + enum LaunchDmaInterruptType + { + None = 0, + Blocking = 1, + NonBlocking = 2, + } + + /// + /// DMA destination memory layout. + /// + enum LaunchDmaMemoryLayout + { + Blocklinear = 0, + Pitch = 1, + } + + /// + /// DMA type. + /// + enum LaunchDmaType + { + Virtual = 0, + Physical = 1, + } + + /// + /// DMA semaphore reduction operation. + /// + enum LaunchDmaSemaphoreReduction + { + Imin = 0, + Imax = 1, + Ixor = 2, + Iand = 3, + Ior = 4, + Iadd = 5, + Inc = 6, + Dec = 7, + Fadd = 10, + } + + /// + /// DMA semaphore reduction signedness. + /// + enum LaunchDmaSemaphoreReductionSign + { + Signed = 0, + Unsigned = 1, + } + + /// + /// DMA L2 cache bypass. + /// + enum LaunchDmaBypassL2 + { + UsePteSetting = 0, + ForceVolatile = 1, + } + + /// + /// DMA component remapping source component. + /// + enum SetRemapComponentsDst + { + SrcX = 0, + SrcY = 1, + SrcZ = 2, + SrcW = 3, + ConstA = 4, + ConstB = 5, + NoWrite = 6, + } + + /// + /// DMA component remapping component size. + /// + enum SetRemapComponentsComponentSize + { + One = 0, + Two = 1, + Three = 2, + Four = 3, + } + + /// + /// DMA component remapping number of components. + /// + enum SetRemapComponentsNumComponents + { + One = 0, + Two = 1, + Three = 2, + Four = 3, + } + + /// + /// Width in GOBs of the destination texture. + /// + enum SetBlockSizeWidth + { + QuarterGob = 14, + OneGob = 0, + } + + /// + /// Height in GOBs of the destination texture. + /// + enum SetBlockSizeHeight + { + OneGob = 0, + TwoGobs = 1, + FourGobs = 2, + EightGobs = 3, + SixteenGobs = 4, + ThirtytwoGobs = 5, + } + + /// + /// Depth in GOBs of the destination texture. + /// + enum SetBlockSizeDepth + { + OneGob = 0, + TwoGobs = 1, + FourGobs = 2, + EightGobs = 3, + SixteenGobs = 4, + ThirtytwoGobs = 5, + } + + /// + /// Height of a single GOB in lines. + /// + enum SetBlockSizeGobHeight + { + GobHeightTesla4 = 0, + GobHeightFermi8 = 1, + } + + /// + /// DMA copy class state. + /// + unsafe struct DmaClassState + { +#pragma warning disable CS0649 // Field is never assigned to + public fixed uint Reserved00[64]; + public uint Nop; + public fixed uint Reserved104[15]; + public uint PmTrigger; + public fixed uint Reserved144[63]; + public uint SetSemaphoreA; + public readonly int SetSemaphoreAUpper => (int)(SetSemaphoreA & 0xFF); + public uint SetSemaphoreB; + public uint SetSemaphorePayload; + public fixed uint Reserved24C[2]; + public uint SetRenderEnableA; + public readonly int SetRenderEnableAUpper => (int)(SetRenderEnableA & 0xFF); + public uint SetRenderEnableB; + public uint SetRenderEnableC; + public readonly int SetRenderEnableCMode => (int)(SetRenderEnableC & 0x7); + public uint SetSrcPhysMode; + public readonly SetPhysModeTarget SetSrcPhysModeTarget => (SetPhysModeTarget)(SetSrcPhysMode & 0x3); + public uint SetDstPhysMode; + public readonly SetPhysModeTarget SetDstPhysModeTarget => (SetPhysModeTarget)(SetDstPhysMode & 0x3); + public fixed uint Reserved268[38]; + public uint LaunchDma; + public readonly LaunchDmaDataTransferType LaunchDmaDataTransferType => (LaunchDmaDataTransferType)(LaunchDma & 0x3); + public readonly bool LaunchDmaFlushEnable => (LaunchDma & 0x4) != 0; + public readonly LaunchDmaSemaphoreType LaunchDmaSemaphoreType => (LaunchDmaSemaphoreType)((LaunchDma >> 3) & 0x3); + public readonly LaunchDmaInterruptType LaunchDmaInterruptType => (LaunchDmaInterruptType)((LaunchDma >> 5) & 0x3); + public readonly LaunchDmaMemoryLayout LaunchDmaSrcMemoryLayout => (LaunchDmaMemoryLayout)((LaunchDma >> 7) & 0x1); + public readonly LaunchDmaMemoryLayout LaunchDmaDstMemoryLayout => (LaunchDmaMemoryLayout)((LaunchDma >> 8) & 0x1); + public readonly bool LaunchDmaMultiLineEnable => (LaunchDma & 0x200) != 0; + public readonly bool LaunchDmaRemapEnable => (LaunchDma & 0x400) != 0; + public readonly bool LaunchDmaForceRmwdisable => (LaunchDma & 0x800) != 0; + public readonly LaunchDmaType LaunchDmaSrcType => (LaunchDmaType)((LaunchDma >> 12) & 0x1); + public readonly LaunchDmaType LaunchDmaDstType => (LaunchDmaType)((LaunchDma >> 13) & 0x1); + public readonly LaunchDmaSemaphoreReduction LaunchDmaSemaphoreReduction => (LaunchDmaSemaphoreReduction)((LaunchDma >> 14) & 0xF); + public readonly LaunchDmaSemaphoreReductionSign LaunchDmaSemaphoreReductionSign => (LaunchDmaSemaphoreReductionSign)((LaunchDma >> 18) & 0x1); + public readonly bool LaunchDmaSemaphoreReductionEnable => (LaunchDma & 0x80000) != 0; + public readonly LaunchDmaBypassL2 LaunchDmaBypassL2 => (LaunchDmaBypassL2)((LaunchDma >> 20) & 0x1); + public fixed uint Reserved304[63]; + public uint OffsetInUpper; + public readonly int OffsetInUpperUpper => (int)(OffsetInUpper & 0xFF); + public uint OffsetInLower; + public uint OffsetOutUpper; + public readonly int OffsetOutUpperUpper => (int)(OffsetOutUpper & 0xFF); + public uint OffsetOutLower; + public uint PitchIn; + public uint PitchOut; + public uint LineLengthIn; + public uint LineCount; + public fixed uint Reserved420[184]; + public uint SetRemapConstA; + public uint SetRemapConstB; + public uint SetRemapComponents; + public readonly SetRemapComponentsDst SetRemapComponentsDstX => (SetRemapComponentsDst)(SetRemapComponents & 0x7); + public readonly SetRemapComponentsDst SetRemapComponentsDstY => (SetRemapComponentsDst)((SetRemapComponents >> 4) & 0x7); + public readonly SetRemapComponentsDst SetRemapComponentsDstZ => (SetRemapComponentsDst)((SetRemapComponents >> 8) & 0x7); + public readonly SetRemapComponentsDst SetRemapComponentsDstW => (SetRemapComponentsDst)((SetRemapComponents >> 12) & 0x7); + public readonly SetRemapComponentsComponentSize SetRemapComponentsComponentSize => (SetRemapComponentsComponentSize)((SetRemapComponents >> 16) & 0x3); + public readonly SetRemapComponentsNumComponents SetRemapComponentsNumSrcComponents => (SetRemapComponentsNumComponents)((SetRemapComponents >> 20) & 0x3); + public readonly SetRemapComponentsNumComponents SetRemapComponentsNumDstComponents => (SetRemapComponentsNumComponents)((SetRemapComponents >> 24) & 0x3); + public uint SetDstBlockSize; + public readonly SetBlockSizeWidth SetDstBlockSizeWidth => (SetBlockSizeWidth)(SetDstBlockSize & 0xF); + public readonly SetBlockSizeHeight SetDstBlockSizeHeight => (SetBlockSizeHeight)((SetDstBlockSize >> 4) & 0xF); + public readonly SetBlockSizeDepth SetDstBlockSizeDepth => (SetBlockSizeDepth)((SetDstBlockSize >> 8) & 0xF); + public readonly SetBlockSizeGobHeight SetDstBlockSizeGobHeight => (SetBlockSizeGobHeight)((SetDstBlockSize >> 12) & 0xF); + public uint SetDstWidth; + public uint SetDstHeight; + public uint SetDstDepth; + public uint SetDstLayer; + public uint SetDstOrigin; + public readonly int SetDstOriginX => (int)(SetDstOrigin & 0xFFFF); + public readonly int SetDstOriginY => (int)((SetDstOrigin >> 16) & 0xFFFF); + public uint Reserved724; + public uint SetSrcBlockSize; + public readonly SetBlockSizeWidth SetSrcBlockSizeWidth => (SetBlockSizeWidth)(SetSrcBlockSize & 0xF); + public readonly SetBlockSizeHeight SetSrcBlockSizeHeight => (SetBlockSizeHeight)((SetSrcBlockSize >> 4) & 0xF); + public readonly SetBlockSizeDepth SetSrcBlockSizeDepth => (SetBlockSizeDepth)((SetSrcBlockSize >> 8) & 0xF); + public readonly SetBlockSizeGobHeight SetSrcBlockSizeGobHeight => (SetBlockSizeGobHeight)((SetSrcBlockSize >> 12) & 0xF); + public uint SetSrcWidth; + public uint SetSrcHeight; + public uint SetSrcDepth; + public uint SetSrcLayer; + public uint SetSrcOrigin; + public readonly int SetSrcOriginX => (int)(SetSrcOrigin & 0xFFFF); + public readonly int SetSrcOriginY => (int)((SetSrcOrigin >> 16) & 0xFFFF); + public fixed uint Reserved740[629]; + public uint PmTriggerEnd; + public fixed uint Reserved1118[2490]; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaTexture.cs b/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaTexture.cs new file mode 100644 index 00000000..36815871 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaTexture.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.Gpu.Engine.Types; + +namespace Ryujinx.Graphics.Gpu.Engine.Dma +{ + /// + /// Buffer to texture copy parameters. + /// + struct DmaTexture + { +#pragma warning disable CS0649 // Field is never assigned to + public MemoryLayout MemoryLayout; + public int Width; + public int Height; + public int Depth; + public int RegionZ; + public ushort RegionX; + public ushort RegionY; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/CompressedMethod.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/CompressedMethod.cs new file mode 100644 index 00000000..21eb5bfa --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/CompressedMethod.cs @@ -0,0 +1,41 @@ +// This file was auto-generated from NVIDIA official Maxwell definitions. + +namespace Ryujinx.Graphics.Gpu.Engine.GPFifo +{ + enum TertOp + { + Grp0IncMethod = 0, + Grp0SetSubDevMask = 1, + Grp0StoreSubDevMask = 2, + Grp0UseSubDevMask = 3, + Grp2NonIncMethod = Grp0IncMethod, + } + + enum SecOp + { + Grp0UseTert = 0, + IncMethod = 1, + Grp2UseTert = 2, + NonIncMethod = 3, + ImmdDataMethod = 4, + OneInc = 5, + Reserved6 = 6, + EndPbSegment = 7, + } + + struct CompressedMethod + { +#pragma warning disable CS0649 // Field is never assigned to + public uint Method; +#pragma warning restore CS0649 + public readonly int MethodAddressOld => (int)((Method >> 2) & 0x7FF); + public readonly int MethodAddress => (int)(Method & 0xFFF); + public readonly int SubdeviceMask => (int)((Method >> 4) & 0xFFF); + public readonly int MethodSubchannel => (int)((Method >> 13) & 0x7); + public readonly TertOp TertOp => (TertOp)((Method >> 16) & 0x3); + public readonly int MethodCountOld => (int)((Method >> 18) & 0x7FF); + public readonly int MethodCount => (int)((Method >> 16) & 0x1FFF); + public readonly int ImmdData => (int)((Method >> 16) & 0x1FFF); + public readonly SecOp SecOp => (SecOp)((Method >> 29) & 0x7); + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPEntry.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPEntry.cs new file mode 100644 index 00000000..d28f2fbb --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPEntry.cs @@ -0,0 +1,55 @@ +// This file was auto-generated from NVIDIA official Maxwell definitions. + +namespace Ryujinx.Graphics.Gpu.Engine.GPFifo +{ + enum Entry0Fetch + { + Unconditional = 0, + Conditional = 1, + } + + enum Entry1Priv + { + User = 0, + Kernel = 1, + } + + enum Entry1Level + { + Main = 0, + Subroutine = 1, + } + + enum Entry1Sync + { + Proceed = 0, + Wait = 1, + } + + enum Entry1Opcode + { + Nop = 0, + Illegal = 1, + Crc = 2, + PbCrc = 3, + } + + struct GPEntry + { +#pragma warning disable CS0649 // Field is never assigned to + public uint Entry0; +#pragma warning restore CS0649 + public readonly Entry0Fetch Entry0Fetch => (Entry0Fetch)(Entry0 & 0x1); + public readonly int Entry0Get => (int)((Entry0 >> 2) & 0x3FFFFFFF); + public readonly int Entry0Operand => (int)(Entry0); +#pragma warning disable CS0649 // Field is never assigned to + public uint Entry1; +#pragma warning restore CS0649 + public readonly int Entry1GetHi => (int)(Entry1 & 0xFF); + public readonly Entry1Priv Entry1Priv => (Entry1Priv)((Entry1 >> 8) & 0x1); + public readonly Entry1Level Entry1Level => (Entry1Level)((Entry1 >> 9) & 0x1); + public readonly int Entry1Length => (int)((Entry1 >> 10) & 0x1FFFFF); + public readonly Entry1Sync Entry1Sync => (Entry1Sync)((Entry1 >> 31) & 0x1); + public readonly Entry1Opcode Entry1Opcode => (Entry1Opcode)(Entry1 & 0xFF); + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs new file mode 100644 index 00000000..cedd824a --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs @@ -0,0 +1,251 @@ +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Gpu.Engine.MME; +using Ryujinx.Graphics.Gpu.Synchronization; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Engine.GPFifo +{ + /// + /// Represents a GPU General Purpose FIFO class. + /// + class GPFifoClass : IDeviceState + { + private readonly GpuContext _context; + private readonly GPFifoProcessor _parent; + private readonly DeviceState _state; + + private bool _createSyncPending; + + private const int MacrosCount = 0x80; + + // Note: The size of the macro memory is unknown, we just make + // a guess here and use 256kb as the size. Increase if needed. + private const int MacroCodeSize = 256 * 256; + + private readonly Macro[] _macros; + private readonly int[] _macroCode; + + /// + /// Creates a new instance of the GPU General Purpose FIFO class. + /// + /// GPU context + /// Parent GPU General Purpose FIFO processor + public GPFifoClass(GpuContext context, GPFifoProcessor parent) + { + _context = context; + _parent = parent; + _state = new DeviceState(new Dictionary + { + { nameof(GPFifoClassState.Semaphored), new RwCallback(Semaphored, null) }, + { nameof(GPFifoClassState.Syncpointb), new RwCallback(Syncpointb, null) }, + { nameof(GPFifoClassState.WaitForIdle), new RwCallback(WaitForIdle, null) }, + { nameof(GPFifoClassState.SetReference), new RwCallback(SetReference, null) }, + { nameof(GPFifoClassState.LoadMmeInstructionRam), new RwCallback(LoadMmeInstructionRam, null) }, + { nameof(GPFifoClassState.LoadMmeStartAddressRam), new RwCallback(LoadMmeStartAddressRam, null) }, + { nameof(GPFifoClassState.SetMmeShadowRamControl), new RwCallback(SetMmeShadowRamControl, null) }, + }); + + _macros = new Macro[MacrosCount]; + _macroCode = new int[MacroCodeSize]; + } + + /// + /// Create any syncs from WaitForIdle command that are currently pending. + /// + public void CreatePendingSyncs() + { + if (_createSyncPending) + { + _createSyncPending = false; + _context.CreateHostSyncIfNeeded(HostSyncFlags.None); + } + } + + /// + /// Reads data from the class registers. + /// + /// Register byte offset + /// Data at the specified offset + public int Read(int offset) => _state.Read(offset); + + /// + /// Writes data to the class registers. + /// + /// Register byte offset + /// Data to be written + public void Write(int offset, int data) => _state.Write(offset, data); + + /// + /// Writes a GPU counter to guest memory. + /// + /// Method call argument + public void Semaphored(int argument) + { + ulong address = ((ulong)_state.State.SemaphorebOffsetLower << 2) | + ((ulong)_state.State.SemaphoreaOffsetUpper << 32); + + int value = _state.State.SemaphorecPayload; + + SemaphoredOperation operation = _state.State.SemaphoredOperation; + + if (_state.State.SemaphoredReleaseSize == SemaphoredReleaseSize.SixteenBytes) + { + _parent.MemoryManager.Write(address + 4, 0); + _parent.MemoryManager.Write(address + 8, _context.GetTimestamp()); + } + + // TODO: Acquire operations (Wait), interrupts for invalid combinations. + if (operation == SemaphoredOperation.Release) + { + _parent.MemoryManager.Write(address, value); + } + else if (operation == SemaphoredOperation.Reduction) + { + bool signed = _state.State.SemaphoredFormat == SemaphoredFormat.Signed; + + int mem = _parent.MemoryManager.Read(address); + + switch (_state.State.SemaphoredReduction) + { + case SemaphoredReduction.Min: + value = signed ? Math.Min(mem, value) : (int)Math.Min((uint)mem, (uint)value); + break; + case SemaphoredReduction.Max: + value = signed ? Math.Max(mem, value) : (int)Math.Max((uint)mem, (uint)value); + break; + case SemaphoredReduction.Xor: + value ^= mem; + break; + case SemaphoredReduction.And: + value &= mem; + break; + case SemaphoredReduction.Or: + value |= mem; + break; + case SemaphoredReduction.Add: + value += mem; + break; + case SemaphoredReduction.Inc: + value = (uint)mem < (uint)value ? mem + 1 : 0; + break; + case SemaphoredReduction.Dec: + value = (uint)mem > 0 && (uint)mem <= (uint)value ? mem - 1 : value; + break; + } + + _parent.MemoryManager.Write(address, value); + } + } + + /// + /// Apply a fence operation on a syncpoint. + /// + /// Method call argument + public void Syncpointb(int argument) + { + SyncpointbOperation operation = _state.State.SyncpointbOperation; + + uint syncpointId = (uint)_state.State.SyncpointbSyncptIndex; + + if (operation == SyncpointbOperation.Wait) + { + uint threshold = (uint)_state.State.SyncpointaPayload; + + _context.Synchronization.WaitOnSyncpoint(syncpointId, threshold, Timeout.InfiniteTimeSpan); + } + else if (operation == SyncpointbOperation.Incr) + { + // "Unbind" render targets since a syncpoint increment might indicate future CPU access for the textures. + _parent.TextureManager.RefreshModifiedTextures(); + + _context.CreateHostSyncIfNeeded(HostSyncFlags.StrictSyncpoint); + _context.Synchronization.IncrementSyncpoint(syncpointId); + } + + _context.AdvanceSequence(); + } + + /// + /// Waits for the GPU to be idle. + /// + /// Method call argument + public void WaitForIdle(int argument) + { + _parent.PerformDeferredDraws(); + _context.Renderer.Pipeline.Barrier(); + + _createSyncPending = true; + } + + /// + /// Used as an indirect data barrier on NVN. When used, access to previously written data must be coherent. + /// + /// Method call argument + public void SetReference(int argument) + { + _context.Renderer.Pipeline.CommandBufferBarrier(); + + _context.CreateHostSyncIfNeeded(HostSyncFlags.Strict); + } + + /// + /// Sends macro code/data to the MME. + /// + /// Method call argument + public void LoadMmeInstructionRam(int argument) + { + _macroCode[_state.State.LoadMmeInstructionRamPointer++] = argument; + } + + /// + /// Binds a macro index to a position for the MME + /// + /// Method call argument + public void LoadMmeStartAddressRam(int argument) + { + _macros[_state.State.LoadMmeStartAddressRamPointer++] = new Macro(argument); + } + + /// + /// Changes the shadow RAM control. + /// + /// Method call argument + public void SetMmeShadowRamControl(int argument) + { + _parent.SetShadowRamControl(argument); + } + + /// + /// Pushes an argument to a macro. + /// + /// Index of the macro + /// GPU virtual address where the command word is located + /// Argument to be pushed to the macro + public void MmePushArgument(int index, ulong gpuVa, int argument) + { + _macros[index].PushArgument(gpuVa, argument); + } + + /// + /// Prepares a macro for execution. + /// + /// Index of the macro + /// Initial argument passed to the macro + public void MmeStart(int index, int argument) + { + _macros[index].StartExecution(_context, _parent, _macroCode, argument); + } + + /// + /// Executes a macro. + /// + /// Index of the macro + /// Current GPU state + public void CallMme(int index, IDeviceState state) + { + _macros[index].Execute(_macroCode, state); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClassState.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClassState.cs new file mode 100644 index 00000000..56bebc33 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClassState.cs @@ -0,0 +1,233 @@ +// This file was auto-generated from NVIDIA official Maxwell definitions. + +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Gpu.Engine.GPFifo +{ + /// + /// Semaphore operation. + /// + enum SemaphoredOperation + { + Acquire = 1, + Release = 2, + AcqGeq = 4, + AcqAnd = 8, + Reduction = 16, + } + + /// + /// Semaphore acquire switch enable. + /// + enum SemaphoredAcquireSwitch + { + Disabled = 0, + Enabled = 1, + } + + /// + /// Semaphore release interrupt wait enable. + /// + enum SemaphoredReleaseWfi + { + En = 0, + Dis = 1, + } + + /// + /// Semaphore release structure size. + /// + enum SemaphoredReleaseSize + { + SixteenBytes = 0, + FourBytes = 1, + } + + /// + /// Semaphore reduction operation. + /// + enum SemaphoredReduction + { + Min = 0, + Max = 1, + Xor = 2, + And = 3, + Or = 4, + Add = 5, + Inc = 6, + Dec = 7, + } + + /// + /// Semaphore format. + /// + enum SemaphoredFormat + { + Signed = 0, + Unsigned = 1, + } + + /// + /// Memory Translation Lookaside Buffer Page Directory Buffer invalidation. + /// + enum MemOpCTlbInvalidatePdb + { + One = 0, + All = 1, + } + + /// + /// Memory Translation Lookaside Buffer GPC invalidation enable. + /// + enum MemOpCTlbInvalidateGpc + { + Enable = 0, + Disable = 1, + } + + /// + /// Memory Translation Lookaside Buffer invalidation target. + /// + enum MemOpCTlbInvalidateTarget + { + VidMem = 0, + SysMemCoherent = 2, + SysMemNoncoherent = 3, + } + + /// + /// Memory operation. + /// + enum MemOpDOperation + { + Membar = 5, + MmuTlbInvalidate = 9, + L2PeermemInvalidate = 13, + L2SysmemInvalidate = 14, + L2CleanComptags = 15, + L2FlushDirty = 16, + } + + /// + /// Syncpoint operation. + /// + enum SyncpointbOperation + { + Wait = 0, + Incr = 1, + } + + /// + /// Syncpoint wait switch enable. + /// + enum SyncpointbWaitSwitch + { + Dis = 0, + En = 1, + } + + /// + /// Wait for interrupt scope. + /// + enum WfiScope + { + CurrentScgType = 0, + All = 1, + } + + /// + /// Yield operation. + /// + enum YieldOp + { + Nop = 0, + PbdmaTimeslice = 1, + RunlistTimeslice = 2, + Tsg = 3, + } + + /// + /// General Purpose FIFO class state. + /// + struct GPFifoClassState + { +#pragma warning disable CS0649 // Field is never assigned to + public uint SetObject; + public readonly int SetObjectNvclass => (int)(SetObject & 0xFFFF); + public readonly int SetObjectEngine => (int)((SetObject >> 16) & 0x1F); + public uint Illegal; + public readonly int IllegalHandle => (int)(Illegal); + public uint Nop; + public readonly int NopHandle => (int)(Nop); + public uint Reserved0C; + public uint Semaphorea; + public readonly int SemaphoreaOffsetUpper => (int)(Semaphorea & 0xFF); + public uint Semaphoreb; + public readonly int SemaphorebOffsetLower => (int)((Semaphoreb >> 2) & 0x3FFFFFFF); + public uint Semaphorec; + public readonly int SemaphorecPayload => (int)(Semaphorec); + public uint Semaphored; + public readonly SemaphoredOperation SemaphoredOperation => (SemaphoredOperation)(Semaphored & 0x1F); + public readonly SemaphoredAcquireSwitch SemaphoredAcquireSwitch => (SemaphoredAcquireSwitch)((Semaphored >> 12) & 0x1); + public readonly SemaphoredReleaseWfi SemaphoredReleaseWfi => (SemaphoredReleaseWfi)((Semaphored >> 20) & 0x1); + public readonly SemaphoredReleaseSize SemaphoredReleaseSize => (SemaphoredReleaseSize)((Semaphored >> 24) & 0x1); + public readonly SemaphoredReduction SemaphoredReduction => (SemaphoredReduction)((Semaphored >> 27) & 0xF); + public readonly SemaphoredFormat SemaphoredFormat => (SemaphoredFormat)((Semaphored >> 31) & 0x1); + public uint NonStallInterrupt; + public readonly int NonStallInterruptHandle => (int)(NonStallInterrupt); + public uint FbFlush; + public readonly int FbFlushHandle => (int)(FbFlush); + public uint Reserved28; + public uint Reserved2C; + public uint MemOpC; + public readonly int MemOpCOperandLow => (int)((MemOpC >> 2) & 0x3FFFFFFF); + public readonly MemOpCTlbInvalidatePdb MemOpCTlbInvalidatePdb => (MemOpCTlbInvalidatePdb)(MemOpC & 0x1); + public readonly MemOpCTlbInvalidateGpc MemOpCTlbInvalidateGpc => (MemOpCTlbInvalidateGpc)((MemOpC >> 1) & 0x1); + public readonly MemOpCTlbInvalidateTarget MemOpCTlbInvalidateTarget => (MemOpCTlbInvalidateTarget)((MemOpC >> 10) & 0x3); + public readonly int MemOpCTlbInvalidateAddrLo => (int)((MemOpC >> 12) & 0xFFFFF); + public uint MemOpD; + public readonly int MemOpDOperandHigh => (int)(MemOpD & 0xFF); + public readonly MemOpDOperation MemOpDOperation => (MemOpDOperation)((MemOpD >> 27) & 0x1F); + public readonly int MemOpDTlbInvalidateAddrHi => (int)(MemOpD & 0xFF); + public uint Reserved38; + public uint Reserved3C; + public uint Reserved40; + public uint Reserved44; + public uint Reserved48; + public uint Reserved4C; + public uint SetReference; + public readonly int SetReferenceCount => (int)(SetReference); + public uint Reserved54; + public uint Reserved58; + public uint Reserved5C; + public uint Reserved60; + public uint Reserved64; + public uint Reserved68; + public uint Reserved6C; + public uint Syncpointa; + public readonly int SyncpointaPayload => (int)(Syncpointa); + public uint Syncpointb; + public readonly SyncpointbOperation SyncpointbOperation => (SyncpointbOperation)(Syncpointb & 0x1); + public readonly SyncpointbWaitSwitch SyncpointbWaitSwitch => (SyncpointbWaitSwitch)((Syncpointb >> 4) & 0x1); + public readonly int SyncpointbSyncptIndex => (int)((Syncpointb >> 8) & 0xFFF); + public uint Wfi; + public readonly WfiScope WfiScope => (WfiScope)(Wfi & 0x1); + public uint CrcCheck; + public readonly int CrcCheckValue => (int)(CrcCheck); + public uint Yield; + public readonly YieldOp YieldOp => (YieldOp)(Yield & 0x3); + // TODO: Eventually move this to per-engine state. + public Array31 Reserved84; + public uint NoOperation; + public uint SetNotifyA; + public uint SetNotifyB; + public uint Notify; + public uint WaitForIdle; + public uint LoadMmeInstructionRamPointer; + public uint LoadMmeInstructionRam; + public uint LoadMmeStartAddressRamPointer; + public uint LoadMmeStartAddressRam; + public uint SetMmeShadowRamControl; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs new file mode 100644 index 00000000..7d0f026e --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs @@ -0,0 +1,261 @@ +using Ryujinx.Graphics.Gpu.Memory; +using System; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Engine.GPFifo +{ + /// + /// Represents a GPU General Purpose FIFO device. + /// + public sealed class GPFifoDevice : IDisposable + { + /// + /// Indicates if the command buffer has pre-fetch enabled. + /// + private enum CommandBufferType + { + Prefetch, + NoPrefetch, + } + + /// + /// Command buffer data. + /// + private struct CommandBuffer + { + /// + /// Processor used to process the command buffer. Contains channel state. + /// + public GPFifoProcessor Processor; + + /// + /// The type of the command buffer. + /// + public CommandBufferType Type; + + /// + /// Fetched data. + /// + public int[] Words; + + /// + /// The GPFIFO entry address (used in mode). + /// + public ulong EntryAddress; + + /// + /// The count of entries inside this GPFIFO entry. + /// + public uint EntryCount; + + /// + /// Get the entries for the command buffer from memory. + /// + /// The memory manager used to fetch the data + /// If true, flushes potential GPU written data before reading the command buffer + /// The fetched data + private readonly ReadOnlySpan GetWords(MemoryManager memoryManager, bool flush) + { + return MemoryMarshal.Cast(memoryManager.GetSpan(EntryAddress, (int)EntryCount * 4, flush)); + } + + /// + /// Prefetch the command buffer. + /// + /// The memory manager used to fetch the data + public void Prefetch(MemoryManager memoryManager) + { + Words = GetWords(memoryManager, true).ToArray(); + } + + /// + /// Fetch the command buffer. + /// + /// The memory manager used to fetch the data + /// If true, flushes potential GPU written data before reading the command buffer + /// The command buffer words + public readonly ReadOnlySpan Fetch(MemoryManager memoryManager, bool flush) + { + return Words ?? GetWords(memoryManager, flush); + } + } + + private readonly ConcurrentQueue _commandBufferQueue; + + private GPFifoProcessor _prevChannelProcessor; + + private readonly bool _ibEnable; + private readonly GpuContext _context; + private readonly AutoResetEvent _event; + + private bool _interrupt; + private int _flushSkips; + + /// + /// Creates a new instance of the GPU General Purpose FIFO device. + /// + /// GPU context that the GPFIFO belongs to + internal GPFifoDevice(GpuContext context) + { + _commandBufferQueue = new ConcurrentQueue(); + _ibEnable = true; + _context = context; + _event = new AutoResetEvent(false); + } + + /// + /// Signal the FIFO that there are new entries to process. + /// + public void SignalNewEntries() + { + _event.Set(); + } + + /// + /// Push a GPFIFO entry in the form of a prefetched command buffer. + /// It is intended to be used by nvservices to handle special cases. + /// + /// Processor used to process + /// The command buffer containing the prefetched commands + internal void PushHostCommandBuffer(GPFifoProcessor processor, int[] commandBuffer) + { + _commandBufferQueue.Enqueue(new CommandBuffer + { + Processor = processor, + Type = CommandBufferType.Prefetch, + Words = commandBuffer, + EntryAddress = ulong.MaxValue, + EntryCount = (uint)commandBuffer.Length, + }); + } + + /// + /// Create a CommandBuffer from a GPFIFO entry. + /// + /// Processor used to process the command buffer pointed to by + /// The GPFIFO entry + /// A new CommandBuffer based on the GPFIFO entry + private static CommandBuffer CreateCommandBuffer(GPFifoProcessor processor, GPEntry entry) + { + CommandBufferType type = CommandBufferType.Prefetch; + + if (entry.Entry1Sync == Entry1Sync.Wait) + { + type = CommandBufferType.NoPrefetch; + } + + ulong startAddress = ((ulong)entry.Entry0Get << 2) | ((ulong)entry.Entry1GetHi << 32); + + return new CommandBuffer + { + Processor = processor, + Type = type, + Words = null, + EntryAddress = startAddress, + EntryCount = (uint)entry.Entry1Length, + }; + } + + /// + /// Pushes GPFIFO entries. + /// + /// Processor used to process the command buffers pointed to by + /// GPFIFO entries + internal void PushEntries(GPFifoProcessor processor, ReadOnlySpan entries) + { + bool beforeBarrier = true; + + for (int index = 0; index < entries.Length; index++) + { + ulong entry = entries[index]; + + CommandBuffer commandBuffer = CreateCommandBuffer(processor, Unsafe.As(ref entry)); + + if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch) + { + commandBuffer.Prefetch(processor.MemoryManager); + } + + if (commandBuffer.Type == CommandBufferType.NoPrefetch) + { + beforeBarrier = false; + } + + _commandBufferQueue.Enqueue(commandBuffer); + } + } + + /// + /// Waits until commands are pushed to the FIFO. + /// + /// True if commands were received, false if wait timed out + public bool WaitForCommands() + { + return !_commandBufferQueue.IsEmpty || (_event.WaitOne(8) && !_commandBufferQueue.IsEmpty); + } + + /// + /// Processes commands pushed to the FIFO. + /// + public void DispatchCalls() + { + // Use this opportunity to also dispose any pending channels that were closed. + _context.RunDeferredActions(); + + // Process command buffers. + while (_ibEnable && !_interrupt && _commandBufferQueue.TryDequeue(out CommandBuffer entry)) + { + bool flushCommandBuffer = true; + + if (_flushSkips != 0) + { + _flushSkips--; + flushCommandBuffer = false; + } + + ReadOnlySpan words = entry.Fetch(entry.Processor.MemoryManager, flushCommandBuffer); + + // If we are changing the current channel, + // we need to force all the host state to be updated. + if (_prevChannelProcessor != entry.Processor) + { + _prevChannelProcessor = entry.Processor; + entry.Processor.ForceAllDirty(); + } + + entry.Processor.Process(entry.EntryAddress, words); + } + + _interrupt = false; + } + + /// + /// Sets the number of flushes that should be skipped for subsequent command buffers. + /// + /// + /// This can improve performance when command buffer data only needs to be consumed by the GPU. + /// + /// The amount of flushes that should be skipped + internal void SetFlushSkips(int count) + { + _flushSkips = count; + } + + /// + /// Interrupts command processing. This will break out of the DispatchCalls loop. + /// + public void Interrupt() + { + _interrupt = true; + _event.Set(); + } + + /// + /// Disposes of resources used for GPFifo command processing. + /// + public void Dispose() => _event.Dispose(); + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs new file mode 100644 index 00000000..984a9cff --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs @@ -0,0 +1,351 @@ +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Gpu.Engine.Compute; +using Ryujinx.Graphics.Gpu.Engine.Dma; +using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; +using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Gpu.Engine.Twod; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Memory; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Engine.GPFifo +{ + /// + /// Represents a GPU General Purpose FIFO command processor. + /// + class GPFifoProcessor : IDisposable + { + private const int MacrosCount = 0x80; + private const int MacroIndexMask = MacrosCount - 1; + + private const int LoadInlineDataMethodOffset = 0x6d; + private const int UniformBufferUpdateDataMethodOffset = 0x8e4; + + private readonly GpuChannel _channel; + + /// + /// Channel memory manager. + /// + public MemoryManager MemoryManager => _channel.MemoryManager; + + /// + /// Channel texture manager. + /// + public TextureManager TextureManager => _channel.TextureManager; + + /// + /// 3D Engine. + /// + public ThreedClass ThreedClass => _3dClass; + + /// + /// Internal GPFIFO state. + /// + private struct DmaState + { + public int Method; + public int SubChannel; + public int MethodCount; + public bool NonIncrementing; + public bool IncrementOnce; + } + + private DmaState _state; + + private readonly ThreedClass _3dClass; + private readonly ComputeClass _computeClass; + private readonly InlineToMemoryClass _i2mClass; + private readonly TwodClass _2dClass; + private readonly DmaClass _dmaClass; + + private readonly GPFifoClass _fifoClass; + + /// + /// Creates a new instance of the GPU General Purpose FIFO command processor. + /// + /// GPU context + /// Channel that the GPFIFO processor belongs to + public GPFifoProcessor(GpuContext context, GpuChannel channel) + { + _channel = channel; + + _fifoClass = new GPFifoClass(context, this); + _3dClass = new ThreedClass(context, channel, _fifoClass); + _computeClass = new ComputeClass(context, channel, _3dClass); + _i2mClass = new InlineToMemoryClass(context, channel); + _2dClass = new TwodClass(channel); + _dmaClass = new DmaClass(context, channel, _3dClass); + } + + /// + /// Processes a command buffer. + /// + /// Base GPU virtual address of the command buffer + /// Command buffer + public void Process(ulong baseGpuVa, ReadOnlySpan commandBuffer) + { + for (int index = 0; index < commandBuffer.Length; index++) + { + int command = commandBuffer[index]; + + ulong gpuVa = baseGpuVa + (ulong)index * 4; + + if (_state.MethodCount != 0) + { + if (TryFastI2mBufferUpdate(commandBuffer, ref index)) + { + continue; + } + + Send(gpuVa, _state.Method, command, _state.SubChannel, _state.MethodCount <= 1); + + if (!_state.NonIncrementing) + { + _state.Method++; + } + + if (_state.IncrementOnce) + { + _state.NonIncrementing = true; + } + + _state.MethodCount--; + } + else + { + CompressedMethod meth = Unsafe.As(ref command); + + if (TryFastUniformBufferUpdate(meth, commandBuffer, index)) + { + index += meth.MethodCount; + continue; + } + + switch (meth.SecOp) + { + case SecOp.IncMethod: + case SecOp.NonIncMethod: + case SecOp.OneInc: + _state.Method = meth.MethodAddress; + _state.SubChannel = meth.MethodSubchannel; + _state.MethodCount = meth.MethodCount; + _state.IncrementOnce = meth.SecOp == SecOp.OneInc; + _state.NonIncrementing = meth.SecOp == SecOp.NonIncMethod; + break; + case SecOp.ImmdDataMethod: + Send(gpuVa, meth.MethodAddress, meth.ImmdData, meth.MethodSubchannel, true); + break; + } + } + } + + _3dClass.FlushUboDirty(); + } + + /// + /// Tries to perform a fast Inline-to-Memory data update. + /// If successful, all data will be copied at once, and + /// command buffer entries will be consumed. + /// + /// Command buffer where the data is contained + /// Offset at where the data is located, auto-incremented on success + /// True if the fast copy was successful, false otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TryFastI2mBufferUpdate(ReadOnlySpan commandBuffer, ref int offset) + { + if (_state.Method == LoadInlineDataMethodOffset && _state.NonIncrementing && _state.SubChannel <= 2) + { + int availableCount = commandBuffer.Length - offset; + int consumeCount = Math.Min(_state.MethodCount, availableCount); + + var data = commandBuffer.Slice(offset, consumeCount); + + if (_state.SubChannel == 0) + { + _3dClass.LoadInlineData(data); + } + else if (_state.SubChannel == 1) + { + _computeClass.LoadInlineData(data); + } + else /* if (_state.SubChannel == 2) */ + { + _i2mClass.LoadInlineData(data); + } + + offset += consumeCount - 1; + _state.MethodCount -= consumeCount; + + return true; + } + + return false; + } + + /// + /// Tries to perform a fast constant buffer data update. + /// If successful, all data will be copied at once, and + 1 + /// command buffer entries will be consumed. + /// + /// Compressed method to be checked + /// Command buffer where is contained + /// Offset at where is located + /// True if the fast copy was successful, false otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TryFastUniformBufferUpdate(CompressedMethod meth, ReadOnlySpan commandBuffer, int offset) + { + int availableCount = commandBuffer.Length - offset; + + if (meth.MethodAddress == UniformBufferUpdateDataMethodOffset && + meth.MethodCount < availableCount && + meth.SecOp == SecOp.NonIncMethod) + { + _3dClass.ConstantBufferUpdate(commandBuffer.Slice(offset + 1, meth.MethodCount)); + + return true; + } + + return false; + } + + /// + /// Sends a uncompressed method for processing by the graphics pipeline. + /// + /// GPU virtual address where the command word is located + /// Method to be processed + private void Send(ulong gpuVa, int offset, int argument, int subChannel, bool isLastCall) + { + if (offset < 0x60) + { + _fifoClass.Write(offset * 4, argument); + } + else if (offset < 0xe00) + { + offset *= 4; + + switch (subChannel) + { + case 0: + _3dClass.Write(offset, argument); + break; + case 1: + _computeClass.Write(offset, argument); + break; + case 2: + _i2mClass.Write(offset, argument); + break; + case 3: + _2dClass.Write(offset, argument); + break; + case 4: + _dmaClass.Write(offset, argument); + break; + } + } + else + { + IDeviceState state = subChannel switch + { + 0 => _3dClass, + 3 => _2dClass, + _ => null, + }; + + if (state != null) + { + int macroIndex = (offset >> 1) & MacroIndexMask; + + if ((offset & 1) != 0) + { + _fifoClass.MmePushArgument(macroIndex, gpuVa, argument); + } + else + { + _fifoClass.MmeStart(macroIndex, argument); + } + + if (isLastCall) + { + _fifoClass.CallMme(macroIndex, state); + + _3dClass.PerformDeferredDraws(); + } + } + } + } + + /// + /// Writes data directly to the state of the specified class. + /// + /// ID of the class to write the data into + /// State offset in bytes + /// Value to be written + public void Write(ClassId classId, int offset, int value) + { + switch (classId) + { + case ClassId.Threed: + _3dClass.Write(offset, value); + break; + case ClassId.Compute: + _computeClass.Write(offset, value); + break; + case ClassId.InlineToMemory: + _i2mClass.Write(offset, value); + break; + case ClassId.Twod: + _2dClass.Write(offset, value); + break; + case ClassId.Dma: + _dmaClass.Write(offset, value); + break; + case ClassId.GPFifo: + _fifoClass.Write(offset, value); + break; + } + } + + /// + /// Sets the shadow ram control value of all sub-channels. + /// + /// New shadow ram control value + public void SetShadowRamControl(int control) + { + _3dClass.SetShadowRamControl(control); + } + + /// + /// Forces a full host state update by marking all state as modified, + /// and also requests all GPU resources in use to be rebound. + /// + public void ForceAllDirty() + { + _3dClass.ForceStateDirty(); + _channel.BufferManager.Rebind(); + _channel.TextureManager.Rebind(); + } + + /// + /// Perform any deferred draws. + /// + public void PerformDeferredDraws() + { + _3dClass.PerformDeferredDraws(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _3dClass.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs new file mode 100644 index 00000000..78099f74 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs @@ -0,0 +1,275 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Texture; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory +{ + /// + /// Represents a Inline-to-Memory engine class. + /// + class InlineToMemoryClass : IDeviceState + { + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly DeviceState _state; + + private bool _isLinear; + + private int _offset; + private int _size; + + private ulong _dstGpuVa; + private int _dstX; + private int _dstY; + private int _dstWidth; + private int _dstHeight; + private int _dstStride; + private int _dstGobBlocksInY; + private int _dstGobBlocksInZ; + private int _lineLengthIn; + private int _lineCount; + + private bool _finished; + + private int[] _buffer; + + /// + /// Creates a new instance of the Inline-to-Memory engine class. + /// + /// GPU context + /// GPU channel + /// Indicates if the internal state should be initialized. Set to false if part of another engine + public InlineToMemoryClass(GpuContext context, GpuChannel channel, bool initializeState) + { + _context = context; + _channel = channel; + + if (initializeState) + { + _state = new DeviceState(new Dictionary + { + { nameof(InlineToMemoryClassState.LaunchDma), new RwCallback(LaunchDma, null) }, + { nameof(InlineToMemoryClassState.LoadInlineData), new RwCallback(LoadInlineData, null) }, + }); + } + } + + /// + /// Creates a new instance of the inline-to-memory engine class. + /// + /// GPU context + /// GPU channel + public InlineToMemoryClass(GpuContext context, GpuChannel channel) : this(context, channel, true) + { + } + + /// + /// Reads data from the class registers. + /// + /// Register byte offset + /// Data at the specified offset + public int Read(int offset) => _state.Read(offset); + + /// + /// Writes data to the class registers. + /// + /// Register byte offset + /// Data to be written + public void Write(int offset, int data) => _state.Write(offset, data); + + /// + /// Launches Inline-to-Memory engine DMA copy. + /// + /// Method call argument + private void LaunchDma(int argument) + { + LaunchDma(ref _state.State, argument); + } + + /// + /// Launches Inline-to-Memory engine DMA copy. + /// + /// Current class state + /// Method call argument + public void LaunchDma(ref InlineToMemoryClassState state, int argument) + { + _isLinear = (argument & 1) != 0; + + _offset = 0; + _size = (int)(BitUtils.AlignUp(state.LineLengthIn, 4) * state.LineCount); + + int count = _size / 4; + + if (_buffer == null || _buffer.Length < count) + { + _buffer = new int[count]; + } + + ulong dstGpuVa = ((ulong)state.OffsetOutUpperValue << 32) | state.OffsetOut; + + _dstGpuVa = dstGpuVa; + _dstX = state.SetDstOriginBytesXV; + _dstY = state.SetDstOriginSamplesYV; + _dstWidth = (int)state.SetDstWidth; + _dstHeight = (int)state.SetDstHeight; + _dstStride = (int)state.PitchOut; + _dstGobBlocksInY = 1 << (int)state.SetDstBlockSizeHeight; + _dstGobBlocksInZ = 1 << (int)state.SetDstBlockSizeDepth; + _lineLengthIn = (int)state.LineLengthIn; + _lineCount = (int)state.LineCount; + + _finished = false; + } + + /// + /// Pushes a block of data to the Inline-to-Memory engine. + /// + /// Data to push + public void LoadInlineData(ReadOnlySpan data) + { + if (!_finished) + { + int copySize = Math.Min(data.Length, _buffer.Length - _offset); + data[..copySize].CopyTo(new Span(_buffer).Slice(_offset, copySize)); + + _offset += copySize; + + if (_offset * 4 >= _size) + { + FinishTransfer(); + } + } + } + + /// + /// Pushes a word of data to the Inline-to-Memory engine. + /// + /// Method call argument + public void LoadInlineData(int argument) + { + if (!_finished) + { + _buffer[_offset++] = argument; + + if (_offset * 4 >= _size) + { + FinishTransfer(); + } + } + } + + /// + /// Performs actual copy of the inline data after the transfer is finished. + /// + private void FinishTransfer() + { + var memoryManager = _channel.MemoryManager; + + var data = MemoryMarshal.Cast(_buffer)[.._size]; + + if (_isLinear && _lineCount == 1) + { + memoryManager.WriteTrackedResource(_dstGpuVa, data[.._lineLengthIn]); + _context.AdvanceSequence(); + } + else + { + // TODO: Verify if the destination X/Y and width/height are taken into account + // for linear texture transfers. If not, we can use the fast path for that aswell. + // Right now the copy code at the bottom assumes that it is used on both which might be incorrect. + if (!_isLinear) + { + var target = memoryManager.Physical.TextureCache.FindTexture( + memoryManager, + _dstGpuVa, + 1, + _dstStride, + _dstHeight, + _lineLengthIn, + _lineCount, + _isLinear, + _dstGobBlocksInY, + _dstGobBlocksInZ); + + if (target != null) + { + target.SynchronizeMemory(); + var dataCopy = MemoryOwner.RentCopy(data); + target.SetData(dataCopy, 0, 0, new GAL.Rectangle(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount)); + target.SignalModified(); + + return; + } + } + + var dstCalculator = new OffsetCalculator( + _dstWidth, + _dstHeight, + _dstStride, + _isLinear, + _dstGobBlocksInY, + 1); + + int srcOffset = 0; + + for (int y = _dstY; y < _dstY + _lineCount; y++) + { + int x1 = _dstX; + int x2 = _dstX + _lineLengthIn; + int x1Round = BitUtils.AlignUp(_dstX, 16); + int x2Trunc = BitUtils.AlignDown(x2, 16); + + int x = x1; + + if (x1Round <= x2) + { + for (; x < x1Round; x++, srcOffset++) + { + int dstOffset = dstCalculator.GetOffset(x, y); + + ulong dstAddress = _dstGpuVa + (uint)dstOffset; + + memoryManager.Write(dstAddress, data[srcOffset]); + } + } + + for (; x < x2Trunc; x += 16, srcOffset += 16) + { + int dstOffset = dstCalculator.GetOffset(x, y); + + ulong dstAddress = _dstGpuVa + (uint)dstOffset; + + memoryManager.Write(dstAddress, MemoryMarshal.Cast>(data.Slice(srcOffset, 16))[0]); + } + + for (; x < x2; x++, srcOffset++) + { + int dstOffset = dstCalculator.GetOffset(x, y); + + ulong dstAddress = _dstGpuVa + (uint)dstOffset; + + memoryManager.Write(dstAddress, data[srcOffset]); + } + + // All lines must be aligned to 4 bytes, as the data is pushed one word at a time. + // If our copy length is not a multiple of 4, then we need to skip the padding bytes here. + int misalignment = _lineLengthIn & 3; + + if (misalignment != 0) + { + srcOffset += 4 - misalignment; + } + } + + _context.AdvanceSequence(); + } + + _finished = true; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClassState.cs b/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClassState.cs new file mode 100644 index 00000000..1aff0441 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClassState.cs @@ -0,0 +1,183 @@ +// This file was auto-generated from NVIDIA official Maxwell definitions. + +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory +{ + /// + /// Notify type. + /// + enum NotifyType + { + WriteOnly = 0, + WriteThenAwaken = 1, + } + + /// + /// Width in GOBs of the destination texture. + /// + enum SetDstBlockSizeWidth + { + OneGob = 0, + } + + /// + /// Height in GOBs of the destination texture. + /// + enum SetDstBlockSizeHeight + { + OneGob = 0, + TwoGobs = 1, + FourGobs = 2, + EightGobs = 3, + SixteenGobs = 4, + ThirtytwoGobs = 5, + } + + /// + /// Depth in GOBs of the destination texture. + /// + enum SetDstBlockSizeDepth + { + OneGob = 0, + TwoGobs = 1, + FourGobs = 2, + EightGobs = 3, + SixteenGobs = 4, + ThirtytwoGobs = 5, + } + + /// + /// Memory layout of the destination texture. + /// + enum LaunchDmaDstMemoryLayout + { + Blocklinear = 0, + Pitch = 1, + } + + /// + /// DMA completion type. + /// + enum LaunchDmaCompletionType + { + FlushDisable = 0, + FlushOnly = 1, + ReleaseSemaphore = 2, + } + + /// + /// DMA interrupt type. + /// + enum LaunchDmaInterruptType + { + None = 0, + Interrupt = 1, + } + + /// + /// DMA semaphore structure size. + /// + enum LaunchDmaSemaphoreStructSize + { + FourWords = 0, + OneWord = 1, + } + + /// + /// DMA semaphore reduction operation. + /// + enum LaunchDmaReductionOp + { + RedAdd = 0, + RedMin = 1, + RedMax = 2, + RedInc = 3, + RedDec = 4, + RedAnd = 5, + RedOr = 6, + RedXor = 7, + } + + /// + /// DMA semaphore reduction format. + /// + enum LaunchDmaReductionFormat + { + Unsigned32 = 0, + Signed32 = 1, + } + + /// + /// Inline-to-Memory class state. + /// + unsafe struct InlineToMemoryClassState + { +#pragma warning disable CS0649 // Field is never assigned to + public uint SetObject; + public readonly int SetObjectClassId => (int)(SetObject & 0xFFFF); + public readonly int SetObjectEngineId => (int)((SetObject >> 16) & 0x1F); + public fixed uint Reserved04[63]; + public uint NoOperation; + public uint SetNotifyA; + public readonly int SetNotifyAAddressUpper => (int)(SetNotifyA & 0xFF); + public uint SetNotifyB; + public uint Notify; + public readonly NotifyType NotifyType => (NotifyType)(Notify); + public uint WaitForIdle; + public fixed uint Reserved114[7]; + public uint SetGlobalRenderEnableA; + public readonly int SetGlobalRenderEnableAOffsetUpper => (int)(SetGlobalRenderEnableA & 0xFF); + public uint SetGlobalRenderEnableB; + public uint SetGlobalRenderEnableC; + public readonly int SetGlobalRenderEnableCMode => (int)(SetGlobalRenderEnableC & 0x7); + public uint SendGoIdle; + public uint PmTrigger; + public uint PmTriggerWfi; + public fixed uint Reserved148[2]; + public uint SetInstrumentationMethodHeader; + public uint SetInstrumentationMethodData; + public fixed uint Reserved158[10]; + public uint LineLengthIn; + public uint LineCount; + public uint OffsetOutUpper; + public readonly int OffsetOutUpperValue => (int)(OffsetOutUpper & 0xFF); + public uint OffsetOut; + public uint PitchOut; + public uint SetDstBlockSize; + public readonly SetDstBlockSizeWidth SetDstBlockSizeWidth => (SetDstBlockSizeWidth)(SetDstBlockSize & 0xF); + public readonly SetDstBlockSizeHeight SetDstBlockSizeHeight => (SetDstBlockSizeHeight)((SetDstBlockSize >> 4) & 0xF); + public readonly SetDstBlockSizeDepth SetDstBlockSizeDepth => (SetDstBlockSizeDepth)((SetDstBlockSize >> 8) & 0xF); + public uint SetDstWidth; + public uint SetDstHeight; + public uint SetDstDepth; + public uint SetDstLayer; + public uint SetDstOriginBytesX; + public readonly int SetDstOriginBytesXV => (int)(SetDstOriginBytesX & 0xFFFFF); + public uint SetDstOriginSamplesY; + public readonly int SetDstOriginSamplesYV => (int)(SetDstOriginSamplesY & 0xFFFF); + public uint LaunchDma; + public readonly LaunchDmaDstMemoryLayout LaunchDmaDstMemoryLayout => (LaunchDmaDstMemoryLayout)(LaunchDma & 0x1); + public readonly LaunchDmaCompletionType LaunchDmaCompletionType => (LaunchDmaCompletionType)((LaunchDma >> 4) & 0x3); + public readonly LaunchDmaInterruptType LaunchDmaInterruptType => (LaunchDmaInterruptType)((LaunchDma >> 8) & 0x3); + public readonly LaunchDmaSemaphoreStructSize LaunchDmaSemaphoreStructSize => (LaunchDmaSemaphoreStructSize)((LaunchDma >> 12) & 0x1); + public readonly bool LaunchDmaReductionEnable => (LaunchDma & 0x2) != 0; + public readonly LaunchDmaReductionOp LaunchDmaReductionOp => (LaunchDmaReductionOp)((LaunchDma >> 13) & 0x7); + public readonly LaunchDmaReductionFormat LaunchDmaReductionFormat => (LaunchDmaReductionFormat)((LaunchDma >> 2) & 0x3); + public readonly bool LaunchDmaSysmembarDisable => (LaunchDma & 0x40) != 0; + public uint LoadInlineData; + public fixed uint Reserved1B8[9]; + public uint SetI2mSemaphoreA; + public readonly int SetI2mSemaphoreAOffsetUpper => (int)(SetI2mSemaphoreA & 0xFF); + public uint SetI2mSemaphoreB; + public uint SetI2mSemaphoreC; + public fixed uint Reserved1E8[2]; + public uint SetI2mSpareNoop00; + public uint SetI2mSpareNoop01; + public uint SetI2mSpareNoop02; + public uint SetI2mSpareNoop03; + public fixed uint Reserved200[3200]; + public Array256 SetMmeShadowScratch; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/AluOperation.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/AluOperation.cs new file mode 100644 index 00000000..45b24e4a --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/AluOperation.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Gpu.Engine.MME +{ + /// + /// GPU Macro Arithmetic and Logic unit operation. + /// + enum AluOperation + { + AluReg = 0, + AddImmediate = 1, + BitfieldReplace = 2, + BitfieldExtractLslImm = 3, + BitfieldExtractLslReg = 4, + ReadImmediate = 5, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/AluRegOperation.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/AluRegOperation.cs new file mode 100644 index 00000000..09e5c6f0 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/AluRegOperation.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.Gpu.Engine.MME +{ + /// + /// GPU Macro Arithmetic and Logic unit binary register-to-register operation. + /// + enum AluRegOperation + { + Add = 0, + AddWithCarry = 1, + Subtract = 2, + SubtractWithBorrow = 3, + BitwiseExclusiveOr = 8, + BitwiseOr = 9, + BitwiseAnd = 10, + BitwiseAndNot = 11, + BitwiseNotAnd = 12, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/AssignmentOperation.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/AssignmentOperation.cs new file mode 100644 index 00000000..3ad3446f --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/AssignmentOperation.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.Gpu.Engine.MME +{ + /// + /// GPU Macro assignment operation. + /// + enum AssignmentOperation + { + IgnoreAndFetch = 0, + Move = 1, + MoveAndSetMaddr = 2, + FetchAndSend = 3, + MoveAndSend = 4, + FetchAndSetMaddr = 5, + MoveAndSetMaddrThenFetchAndSend = 6, + MoveAndSetMaddrThenSendHigh = 7, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/IMacroEE.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/IMacroEE.cs new file mode 100644 index 00000000..d7b86252 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/IMacroEE.cs @@ -0,0 +1,52 @@ +using Ryujinx.Graphics.Device; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Engine.MME +{ + /// + /// FIFO word. + /// + readonly struct FifoWord + { + /// + /// GPU virtual address where the word is located in memory. + /// + public ulong GpuVa { get; } + + /// + /// Word value. + /// + public int Word { get; } + + /// + /// Creates a new FIFO word. + /// + /// GPU virtual address where the word is located in memory + /// Word value + public FifoWord(ulong gpuVa, int word) + { + GpuVa = gpuVa; + Word = word; + } + } + + /// + /// Macro Execution Engine interface. + /// + interface IMacroEE + { + /// + /// Arguments FIFO. + /// + Queue Fifo { get; } + + /// + /// Should execute the GPU Macro code being passed. + /// + /// Code to be executed + /// GPU state at the time of the call + /// First argument to be passed to the GPU Macro + void Execute(ReadOnlySpan code, IDeviceState state, int arg0); + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs new file mode 100644 index 00000000..0cf1f306 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs @@ -0,0 +1,101 @@ +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Gpu.Engine.GPFifo; +using System; + +namespace Ryujinx.Graphics.Gpu.Engine.MME +{ + /// + /// GPU macro program. + /// + struct Macro + { + /// + /// Word offset of the code on the code memory. + /// + public int Position { get; } + + private IMacroEE _executionEngine; + private bool _executionPending; + private int _argument; + private MacroHLEFunctionName _hleFunction; + + /// + /// Creates a new instance of the GPU cached macro program. + /// + /// Macro code start position + public Macro(int position) + { + Position = position; + + _executionEngine = null; + _executionPending = false; + _argument = 0; + _hleFunction = MacroHLEFunctionName.None; + } + + /// + /// Sets the first argument for the macro call. + /// + /// GPU context where the macro code is being executed + /// GPU GP FIFO command processor + /// Code to be executed + /// First argument + public void StartExecution(GpuContext context, GPFifoProcessor processor, ReadOnlySpan code, int argument) + { + _argument = argument; + + _executionPending = true; + + if (_executionEngine == null) + { + if (GraphicsConfig.EnableMacroHLE && MacroHLETable.TryGetMacroHLEFunction(code[Position..], context.Capabilities, out _hleFunction)) + { + _executionEngine = new MacroHLE(processor, _hleFunction); + } + else if (GraphicsConfig.EnableMacroJit) + { + _executionEngine = new MacroJit(); + } + else + { + _executionEngine = new MacroInterpreter(); + } + } + + // We don't consume the parameter buffer value, so we don't need to flush it. + // Doing so improves performance if the value was written by a GPU shader. + if (_hleFunction == MacroHLEFunctionName.DrawElementsIndirect) + { + context.GPFifo.SetFlushSkips(1); + } + else if (_hleFunction == MacroHLEFunctionName.MultiDrawElementsIndirectCount) + { + context.GPFifo.SetFlushSkips(2); + } + } + + /// + /// Starts executing the macro program code. + /// + /// Program code + /// Current GPU state + public void Execute(ReadOnlySpan code, IDeviceState state) + { + if (_executionPending) + { + _executionPending = false; + _executionEngine?.Execute(code[Position..], state, _argument); + } + } + + /// + /// Pushes an argument to the macro call argument FIFO. + /// + /// GPU virtual address where the command word is located + /// Argument to be pushed + public readonly void PushArgument(ulong gpuVa, int argument) + { + _executionEngine?.Fifo.Enqueue(new FifoWord(gpuVa, argument)); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs new file mode 100644 index 00000000..475d1ee4 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs @@ -0,0 +1,550 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.GPFifo; +using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Memory.Range; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Engine.MME +{ + /// + /// Macro High-level emulation. + /// + class MacroHLE : IMacroEE + { + private const int ColorLayerCountOffset = 0x818; + private const int ColorStructSize = 0x40; + private const int ZetaLayerCountOffset = 0x1230; + private const int UniformBufferBindVertexOffset = 0x2410; + private const int FirstVertexOffset = 0x1434; + + private const int IndirectIndexedDataEntrySize = 0x14; + + private const int LogicOpOffset = 0x19c4; + private const int ShaderIdScratchOffset = 0x3470; + private const int ShaderAddressScratchOffset = 0x3488; + private const int UpdateConstantBufferAddressesBase = 0x34a8; + private const int UpdateConstantBufferSizesBase = 0x34bc; + private const int UpdateConstantBufferAddressCbu = 0x3460; + + private readonly GPFifoProcessor _processor; + private readonly MacroHLEFunctionName _functionName; + + /// + /// Arguments FIFO. + /// + public Queue Fifo { get; } + + /// + /// Creates a new instance of the HLE macro handler. + /// + /// GPU GP FIFO command processor + /// Name of the HLE macro function to be called + public MacroHLE(GPFifoProcessor processor, MacroHLEFunctionName functionName) + { + _processor = processor; + _functionName = functionName; + + Fifo = new Queue(); + } + + /// + /// Executes a macro program until it exits. + /// + /// Code of the program to execute + /// GPU state at the time of the call + /// Optional argument passed to the program, 0 if not used + public void Execute(ReadOnlySpan code, IDeviceState state, int arg0) + { + switch (_functionName) + { + case MacroHLEFunctionName.BindShaderProgram: + BindShaderProgram(state, arg0); + break; + case MacroHLEFunctionName.ClearColor: + ClearColor(state, arg0); + break; + case MacroHLEFunctionName.ClearDepthStencil: + ClearDepthStencil(state, arg0); + break; + case MacroHLEFunctionName.DrawArraysInstanced: + DrawArraysInstanced(state, arg0); + break; + case MacroHLEFunctionName.DrawElements: + DrawElements(state, arg0); + break; + case MacroHLEFunctionName.DrawElementsInstanced: + DrawElementsInstanced(state, arg0); + break; + case MacroHLEFunctionName.DrawElementsIndirect: + DrawElementsIndirect(state, arg0); + break; + case MacroHLEFunctionName.MultiDrawElementsIndirectCount: + MultiDrawElementsIndirectCount(state, arg0); + break; + case MacroHLEFunctionName.UpdateBlendState: + UpdateBlendState(state, arg0); + break; + case MacroHLEFunctionName.UpdateColorMasks: + UpdateColorMasks(state, arg0); + break; + case MacroHLEFunctionName.UpdateUniformBufferState: + UpdateUniformBufferState(state, arg0); + break; + case MacroHLEFunctionName.UpdateUniformBufferStateCbu: + UpdateUniformBufferStateCbu(state, arg0); + break; + case MacroHLEFunctionName.UpdateUniformBufferStateCbuV2: + UpdateUniformBufferStateCbuV2(state, arg0); + break; + default: + throw new NotImplementedException(_functionName.ToString()); + } + + // It should be empty at this point, but clear it just to be safe. + Fifo.Clear(); + } + + /// + /// Binds a shader program with the index in arg0. + /// + /// GPU state at the time of the call + /// First argument of the call + private void BindShaderProgram(IDeviceState state, int arg0) + { + int scratchOffset = ShaderIdScratchOffset + arg0 * 4; + + int lastId = state.Read(scratchOffset); + int id = FetchParam().Word; + int offset = FetchParam().Word; + + if (lastId == id) + { + FetchParam(); + FetchParam(); + + return; + } + + _processor.ThreedClass.SetShaderOffset(arg0, (uint)offset); + + // Removes overflow on the method address into the increment portion. + // Present in the original macro. + int addrMask = unchecked((int)0xfffc0fff) << 2; + + state.Write(scratchOffset & addrMask, id); + state.Write((ShaderAddressScratchOffset + arg0 * 4) & addrMask, offset); + + int stage = FetchParam().Word; + uint cbAddress = (uint)FetchParam().Word; + + _processor.ThreedClass.UpdateUniformBufferState(65536, cbAddress >> 24, cbAddress << 8); + + int stageOffset = (stage & 0x7f) << 3; + + state.Write((UniformBufferBindVertexOffset + stageOffset * 4) & addrMask, 17); + } + + /// + /// Updates uniform buffer state for update or bind. + /// + /// GPU state at the time of the call + /// First argument of the call + private void UpdateUniformBufferState(IDeviceState state, int arg0) + { + uint address = (uint)state.Read(UpdateConstantBufferAddressesBase + arg0 * 4); + int size = state.Read(UpdateConstantBufferSizesBase + arg0 * 4); + + _processor.ThreedClass.UpdateUniformBufferState(size, address >> 24, address << 8); + } + + /// + /// Updates uniform buffer state for update. + /// + /// GPU state at the time of the call + /// First argument of the call + private void UpdateUniformBufferStateCbu(IDeviceState state, int arg0) + { + uint address = (uint)state.Read(UpdateConstantBufferAddressCbu); + + UniformBufferState ubState = new() + { + Address = new() + { + High = address >> 24, + Low = address << 8 + }, + Size = 24320, + Offset = arg0 << 2 + }; + + _processor.ThreedClass.UpdateUniformBufferState(ubState); + } + + /// + /// Updates uniform buffer state for update. + /// + /// GPU state at the time of the call + /// First argument of the call + private void UpdateUniformBufferStateCbuV2(IDeviceState state, int arg0) + { + uint address = (uint)state.Read(UpdateConstantBufferAddressCbu); + + UniformBufferState ubState = new() + { + Address = new() + { + High = address >> 24, + Low = address << 8 + }, + Size = 28672, + Offset = arg0 << 2 + }; + + _processor.ThreedClass.UpdateUniformBufferState(ubState); + } + + /// + /// Updates blend enable using the given argument. + /// + /// GPU state at the time of the call + /// First argument of the call + private void UpdateBlendState(IDeviceState state, int arg0) + { + state.Write(LogicOpOffset, 0); + + Array8 enable = new(); + + for (int i = 0; i < 8; i++) + { + enable[i] = new Boolean32((uint)(arg0 >> (i + 8)) & 1); + } + + _processor.ThreedClass.UpdateBlendEnable(ref enable); + } + + /// + /// Updates color masks using the given argument and three pushed arguments. + /// + /// GPU state at the time of the call + /// First argument of the call + private void UpdateColorMasks(IDeviceState state, int arg0) + { + Array8 masks = new(); + + int index = 0; + + for (int i = 0; i < 4; i++) + { + masks[index++] = new RtColorMask((uint)arg0 & 0x1fff); + masks[index++] = new RtColorMask(((uint)arg0 >> 16) & 0x1fff); + + if (i != 3) + { + arg0 = FetchParam().Word; + } + } + + _processor.ThreedClass.UpdateColorMasks(ref masks); + } + + /// + /// Clears one bound color target. + /// + /// GPU state at the time of the call + /// First argument of the call + private void ClearColor(IDeviceState state, int arg0) + { + int index = (arg0 >> 6) & 0xf; + int layerCount = state.Read(ColorLayerCountOffset + index * ColorStructSize); + + _processor.ThreedClass.Clear(arg0, layerCount); + } + + /// + /// Clears the current depth-stencil target. + /// + /// GPU state at the time of the call + /// First argument of the call + private void ClearDepthStencil(IDeviceState state, int arg0) + { + int layerCount = state.Read(ZetaLayerCountOffset); + + _processor.ThreedClass.Clear(arg0, layerCount); + } + + /// + /// Performs a draw. + /// + /// GPU state at the time of the call + /// First argument of the call + private void DrawArraysInstanced(IDeviceState state, int arg0) + { + var topology = (PrimitiveTopology)arg0; + + var count = FetchParam(); + var instanceCount = FetchParam(); + var firstVertex = FetchParam(); + var firstInstance = FetchParam(); + + if (ShouldSkipDraw(state, instanceCount.Word)) + { + return; + } + + _processor.ThreedClass.Draw( + topology, + count.Word, + instanceCount.Word, + 0, + firstVertex.Word, + firstInstance.Word, + indexed: false); + } + + /// + /// Performs a indexed draw. + /// + /// GPU state at the time of the call + /// First argument of the call + private void DrawElements(IDeviceState state, int arg0) + { + var topology = (PrimitiveTopology)arg0; + + var indexAddressHigh = FetchParam(); + var indexAddressLow = FetchParam(); + var indexType = FetchParam(); + var firstIndex = 0; + var indexCount = FetchParam(); + + _processor.ThreedClass.UpdateIndexBuffer( + (uint)indexAddressHigh.Word, + (uint)indexAddressLow.Word, + (IndexType)indexType.Word); + + _processor.ThreedClass.Draw( + topology, + indexCount.Word, + 1, + firstIndex, + state.Read(FirstVertexOffset), + 0, + indexed: true); + } + + /// + /// Performs a indexed draw. + /// + /// GPU state at the time of the call + /// First argument of the call + private void DrawElementsInstanced(IDeviceState state, int arg0) + { + var topology = (PrimitiveTopology)arg0; + + var count = FetchParam(); + var instanceCount = FetchParam(); + var firstIndex = FetchParam(); + var firstVertex = FetchParam(); + var firstInstance = FetchParam(); + + if (ShouldSkipDraw(state, instanceCount.Word)) + { + return; + } + + _processor.ThreedClass.Draw( + topology, + count.Word, + instanceCount.Word, + firstIndex.Word, + firstVertex.Word, + firstInstance.Word, + indexed: true); + } + + /// + /// Performs a indirect indexed draw, with parameters from a GPU buffer. + /// + /// GPU state at the time of the call + /// First argument of the call + private void DrawElementsIndirect(IDeviceState state, int arg0) + { + var topology = (PrimitiveTopology)arg0; + + var count = FetchParam(); + var instanceCount = FetchParam(); + var firstIndex = FetchParam(); + var firstVertex = FetchParam(); + var firstInstance = FetchParam(); + + ulong indirectBufferGpuVa = count.GpuVa; + + var bufferCache = _processor.MemoryManager.Physical.BufferCache; + + bool useBuffer = bufferCache.CheckModified(_processor.MemoryManager, indirectBufferGpuVa, IndirectIndexedDataEntrySize, out ulong indirectBufferAddress); + + if (useBuffer) + { + int indexCount = firstIndex.Word + count.Word; + + _processor.ThreedClass.DrawIndirect( + topology, + new MultiRange(indirectBufferAddress, IndirectIndexedDataEntrySize), + default, + 1, + IndirectIndexedDataEntrySize, + indexCount, + IndirectDrawType.DrawIndexedIndirect); + } + else + { + if (ShouldSkipDraw(state, instanceCount.Word)) + { + return; + } + + _processor.ThreedClass.Draw( + topology, + count.Word, + instanceCount.Word, + firstIndex.Word, + firstVertex.Word, + firstInstance.Word, + indexed: true); + } + } + + /// + /// Performs a indirect indexed multi-draw, with parameters from a GPU buffer. + /// + /// GPU state at the time of the call + /// First argument of the call + private void MultiDrawElementsIndirectCount(IDeviceState state, int arg0) + { + int arg1 = FetchParam().Word; + int arg2 = FetchParam().Word; + int arg3 = FetchParam().Word; + + int startDraw = arg0; + int endDraw = arg1; + var topology = (PrimitiveTopology)arg2; + int paddingWords = arg3; + int stride = paddingWords * 4 + 0x14; + + ulong parameterBufferGpuVa = FetchParam().GpuVa; + + int maxDrawCount = endDraw - startDraw; + + if (startDraw != 0) + { + int drawCount = _processor.MemoryManager.Read(parameterBufferGpuVa, tracked: true); + + // Calculate maximum draw count based on the previous draw count and current draw count. + if ((uint)drawCount <= (uint)startDraw) + { + // The start draw is past our total draw count, so all draws were already performed. + maxDrawCount = 0; + } + else + { + // Perform just the missing number of draws. + maxDrawCount = (int)Math.Min((uint)maxDrawCount, (uint)(drawCount - startDraw)); + } + } + + if (maxDrawCount == 0) + { + Fifo.Clear(); + return; + } + + ulong indirectBufferGpuVa = 0; + int indexCount = 0; + + for (int i = 0; i < maxDrawCount; i++) + { + var count = FetchParam(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + var instanceCount = FetchParam(); + var firstIndex = FetchParam(); + var firstVertex = FetchParam(); + var firstInstance = FetchParam(); +#pragma warning restore IDE0059 + + if (i == 0) + { + indirectBufferGpuVa = count.GpuVa; + } + + indexCount = Math.Max(indexCount, count.Word + firstIndex.Word); + + if (i != maxDrawCount - 1) + { + for (int j = 0; j < paddingWords; j++) + { + FetchParam(); + } + } + } + + var bufferCache = _processor.MemoryManager.Physical.BufferCache; + + ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride; + + MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize, BufferStage.Indirect); + MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4, BufferStage.Indirect); + + _processor.ThreedClass.DrawIndirect( + topology, + indirectBufferRange, + parameterBufferRange, + maxDrawCount, + stride, + indexCount, + Threed.IndirectDrawType.DrawIndexedIndirectCount); + } + + /// + /// Checks if the draw should be skipped, because the masked instance count is zero. + /// + /// Current GPU state + /// Draw instance count + /// True if the draw should be skipped, false otherwise + private static bool ShouldSkipDraw(IDeviceState state, int instanceCount) + { + return (Read(state, 0xd1b) & instanceCount) == 0; + } + + /// + /// Fetches a arguments from the arguments FIFO. + /// + /// The call argument, or a 0 value with null address if the FIFO is empty + private FifoWord FetchParam() + { + if (!Fifo.TryDequeue(out var value)) + { + Logger.Warning?.Print(LogClass.Gpu, "Macro attempted to fetch an inexistent argument."); + + return new FifoWord(0UL, 0); + } + + return value; + } + + /// + /// Reads data from a GPU register. + /// + /// Current GPU state + /// Register offset to read + /// GPU register value + private static int Read(IDeviceState state, int reg) + { + return state.Read(reg * 4); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLEFunctionName.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLEFunctionName.cs new file mode 100644 index 00000000..55514c73 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLEFunctionName.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Graphics.Gpu.Engine.MME +{ + /// + /// Name of the High-level implementation of a Macro function. + /// + enum MacroHLEFunctionName + { + None, + BindShaderProgram, + ClearColor, + ClearDepthStencil, + DrawArraysInstanced, + DrawElements, + DrawElementsInstanced, + DrawElementsIndirect, + MultiDrawElementsIndirectCount, + + UpdateBlendState, + UpdateColorMasks, + UpdateUniformBufferState, + UpdateUniformBufferStateCbu, + UpdateUniformBufferStateCbuV2 + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLETable.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLETable.cs new file mode 100644 index 00000000..43b70129 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLETable.cs @@ -0,0 +1,116 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine.MME +{ + /// + /// Table with information about High-level implementations of GPU Macro code. + /// + static class MacroHLETable + { + /// + /// Macroo High-level implementation table entry. + /// + readonly struct TableEntry + { + /// + /// Name of the Macro function. + /// + public MacroHLEFunctionName Name { get; } + + /// + /// Hash of the original binary Macro function code. + /// + public Hash128 Hash { get; } + + /// + /// Size (in bytes) of the original binary Macro function code. + /// + public int Length { get; } + + /// + /// Creates a new table entry. + /// + /// Name of the Macro function + /// Hash of the original binary Macro function code + /// Size (in bytes) of the original binary Macro function code + public TableEntry(MacroHLEFunctionName name, Hash128 hash, int length) + { + Name = name; + Hash = hash; + Length = length; + } + } + + private static readonly TableEntry[] _table = new TableEntry[] + { + new(MacroHLEFunctionName.BindShaderProgram, new Hash128(0x5d5efb912369f60b, 0x69131ed5019f08ef), 0x68), + new(MacroHLEFunctionName.ClearColor, new Hash128(0xA9FB28D1DC43645A, 0xB177E5D2EAE67FB0), 0x28), + new(MacroHLEFunctionName.ClearDepthStencil, new Hash128(0x1B96CB77D4879F4F, 0x8557032FE0C965FB), 0x24), + new(MacroHLEFunctionName.DrawArraysInstanced, new Hash128(0x197FB416269DBC26, 0x34288C01DDA82202), 0x48), + new(MacroHLEFunctionName.DrawElements, new Hash128(0x3D7F32AE6C2702A7, 0x9353C9F41C1A244D), 0x20), + new(MacroHLEFunctionName.DrawElementsInstanced, new Hash128(0x1A501FD3D54EC8E0, 0x6CF570CF79DA74D6), 0x5c), + new(MacroHLEFunctionName.DrawElementsIndirect, new Hash128(0x86A3E8E903AF8F45, 0xD35BBA07C23860A4), 0x7c), + new(MacroHLEFunctionName.MultiDrawElementsIndirectCount, new Hash128(0x890AF57ED3FB1C37, 0x35D0C95C61F5386F), 0x19C), + new(MacroHLEFunctionName.UpdateBlendState, new Hash128(0x40F6D4E7B08D7640, 0x82167BEEAECB959F), 0x28), + new(MacroHLEFunctionName.UpdateColorMasks, new Hash128(0x9EE32420B8441DFD, 0x6E7724759A57333E), 0x24), + new(MacroHLEFunctionName.UpdateUniformBufferState, new Hash128(0x8EE66706049CB0B0, 0x51C1CF906EC86F7C), 0x20), + new(MacroHLEFunctionName.UpdateUniformBufferStateCbu, new Hash128(0xA4592676A3E581A0, 0xA39E77FE19FE04AC), 0x18), + new(MacroHLEFunctionName.UpdateUniformBufferStateCbuV2, new Hash128(0x392FA750489983D4, 0x35BACE455155D2C3), 0x18) + }; + + /// + /// Checks if the host supports all features required by the HLE macro. + /// + /// Host capabilities + /// Name of the HLE macro to be checked + /// True if the host supports the HLE macro, false otherwise + private static bool IsMacroHLESupported(Capabilities caps, MacroHLEFunctionName name) + { + if (name == MacroHLEFunctionName.MultiDrawElementsIndirectCount) + { + return caps.SupportsIndirectParameters; + } + else if (name != MacroHLEFunctionName.None) + { + return true; + } + + return false; + } + + /// + /// Checks if there's a fast, High-level implementation of the specified Macro code available. + /// + /// Macro code to be checked + /// Renderer capabilities to check for this macro HLE support + /// Name of the function if a implementation is available and supported, otherwise + /// True if there is a implementation available and supported, false otherwise + public static bool TryGetMacroHLEFunction(ReadOnlySpan code, Capabilities caps, out MacroHLEFunctionName name) + { + var mc = MemoryMarshal.Cast(code); + + for (int i = 0; i < _table.Length; i++) + { + ref var entry = ref _table[i]; + + var hash = XXHash128.ComputeHash(mc[..entry.Length]); + if (hash == entry.Hash) + { + if (IsMacroHLESupported(caps, entry.Name)) + { + name = entry.Name; + return true; + } + + break; + } + } + + name = MacroHLEFunctionName.None; + return false; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroInterpreter.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroInterpreter.cs new file mode 100644 index 00000000..dd60688d --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroInterpreter.cs @@ -0,0 +1,405 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Device; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Engine.MME +{ + /// + /// Macro code interpreter. + /// + class MacroInterpreter : IMacroEE + { + /// + /// Arguments FIFO. + /// + public Queue Fifo { get; } + + private readonly int[] _gprs; + + private int _methAddr; + private int _methIncr; + + private bool _carry; + + private int _opCode; + private int _pipeOp; + + private bool _ignoreExitFlag; + + private int _pc; + + /// + /// Creates a new instance of the macro code interpreter. + /// + public MacroInterpreter() + { + Fifo = new Queue(); + + _gprs = new int[8]; + } + + /// + /// Executes a macro program until it exits. + /// + /// Code of the program to execute + /// Current GPU state + /// Optional argument passed to the program, 0 if not used + public void Execute(ReadOnlySpan code, IDeviceState state, int arg0) + { + Reset(); + + _gprs[1] = arg0; + + _pc = 0; + + FetchOpCode(code); + + while (Step(code, state)) + { + } + + // Due to the delay slot, we still need to execute + // one more instruction before we actually exit. + Step(code, state); + } + + /// + /// Resets the internal interpreter state. + /// Call each time you run a new program. + /// + private void Reset() + { + for (int index = 0; index < _gprs.Length; index++) + { + _gprs[index] = 0; + } + + _methAddr = 0; + _methIncr = 0; + + _carry = false; + } + + /// + /// Executes a single instruction of the program. + /// + /// Program code to execute + /// Current GPU state + /// True to continue execution, false if the program exited + private bool Step(ReadOnlySpan code, IDeviceState state) + { + int baseAddr = _pc - 1; + + FetchOpCode(code); + + if ((_opCode & 7) < 7) + { + // Operation produces a value. + AssignmentOperation asgOp = (AssignmentOperation)((_opCode >> 4) & 7); + + int result = GetAluResult(state); + + switch (asgOp) + { + // Fetch parameter and ignore result. + case AssignmentOperation.IgnoreAndFetch: + SetDstGpr(FetchParam()); + break; + // Move result. + case AssignmentOperation.Move: + SetDstGpr(result); + break; + // Move result and use as Method Address. + case AssignmentOperation.MoveAndSetMaddr: + SetDstGpr(result); + SetMethAddr(result); + break; + // Fetch parameter and send result. + case AssignmentOperation.FetchAndSend: + SetDstGpr(FetchParam()); + Send(state, result); + break; + // Move and send result. + case AssignmentOperation.MoveAndSend: + SetDstGpr(result); + Send(state, result); + break; + // Fetch parameter and use result as Method Address. + case AssignmentOperation.FetchAndSetMaddr: + SetDstGpr(FetchParam()); + SetMethAddr(result); + break; + // Move result and use as Method Address, then fetch and send parameter. + case AssignmentOperation.MoveAndSetMaddrThenFetchAndSend: + SetDstGpr(result); + SetMethAddr(result); + Send(state, FetchParam()); + break; + // Move result and use as Method Address, then send bits 17:12 of result. + case AssignmentOperation.MoveAndSetMaddrThenSendHigh: + SetDstGpr(result); + SetMethAddr(result); + Send(state, (result >> 12) & 0x3f); + break; + } + } + else + { + // Branch. + bool onNotZero = ((_opCode >> 4) & 1) != 0; + + bool taken = onNotZero + ? GetGprA() != 0 + : GetGprA() == 0; + + if (taken) + { + _pc = baseAddr + GetImm(); + + bool noDelays = (_opCode & 0x20) != 0; + + if (noDelays) + { + FetchOpCode(code); + } + else + { + // The delay slot instruction exit flag should be ignored. + _ignoreExitFlag = true; + } + + return true; + } + } + + bool exit = (_opCode & 0x80) != 0 && !_ignoreExitFlag; + + _ignoreExitFlag = false; + + return !exit; + } + + /// + /// Fetches a single operation code from the program code. + /// + /// Program code + private void FetchOpCode(ReadOnlySpan code) + { + _opCode = _pipeOp; + _pipeOp = code[_pc++]; + } + + /// + /// Gets the result of the current Arithmetic and Logic unit operation. + /// + /// Current GPU state + /// Operation result + private int GetAluResult(IDeviceState state) + { + AluOperation op = (AluOperation)(_opCode & 7); + + switch (op) + { + case AluOperation.AluReg: + return GetAluResult((AluRegOperation)((_opCode >> 17) & 0x1f), GetGprA(), GetGprB()); + + case AluOperation.AddImmediate: + return GetGprA() + GetImm(); + + case AluOperation.BitfieldReplace: + case AluOperation.BitfieldExtractLslImm: + case AluOperation.BitfieldExtractLslReg: + int bfSrcBit = (_opCode >> 17) & 0x1f; + int bfSize = (_opCode >> 22) & 0x1f; + int bfDstBit = (_opCode >> 27) & 0x1f; + + int bfMask = (1 << bfSize) - 1; + + int dst = GetGprA(); + int src = GetGprB(); + + switch (op) + { + case AluOperation.BitfieldReplace: + src = (int)((uint)src >> bfSrcBit) & bfMask; + + dst &= ~(bfMask << bfDstBit); + + dst |= src << bfDstBit; + + return dst; + + case AluOperation.BitfieldExtractLslImm: + src = (int)((uint)src >> dst) & bfMask; + + return src << bfDstBit; + + case AluOperation.BitfieldExtractLslReg: + src = (int)((uint)src >> bfSrcBit) & bfMask; + + return src << dst; + } + + break; + + case AluOperation.ReadImmediate: + return Read(state, GetGprA() + GetImm()); + } + + throw new InvalidOperationException($"Invalid operation \"{op}\" on instruction 0x{_opCode:X8}."); + } + + /// + /// Gets the result of an Arithmetic and Logic operation using registers. + /// + /// Arithmetic and Logic unit operation with registers + /// First operand value + /// Second operand value + /// Operation result + private int GetAluResult(AluRegOperation aluOp, int a, int b) + { + ulong result; + + switch (aluOp) + { + case AluRegOperation.Add: + result = (ulong)a + (ulong)b; + + _carry = result > 0xffffffff; + + return (int)result; + + case AluRegOperation.AddWithCarry: + result = (ulong)a + (ulong)b + (_carry ? 1UL : 0UL); + + _carry = result > 0xffffffff; + + return (int)result; + + case AluRegOperation.Subtract: + result = (ulong)a - (ulong)b; + + _carry = result < 0x100000000; + + return (int)result; + + case AluRegOperation.SubtractWithBorrow: + result = (ulong)a - (ulong)b - (_carry ? 0UL : 1UL); + + _carry = result < 0x100000000; + + return (int)result; + + case AluRegOperation.BitwiseExclusiveOr: + return a ^ b; + case AluRegOperation.BitwiseOr: + return a | b; + case AluRegOperation.BitwiseAnd: + return a & b; + case AluRegOperation.BitwiseAndNot: + return a & ~b; + case AluRegOperation.BitwiseNotAnd: + return ~(a & b); + } + + throw new InvalidOperationException($"Invalid operation \"{aluOp}\" on instruction 0x{_opCode:X8}."); + } + + /// + /// Extracts a 32-bits signed integer constant from the current operation code. + /// + /// The 32-bits immediate value encoded at the current operation code + private int GetImm() + { + // Note: The immediate is signed, the sign-extension is intended here. + return _opCode >> 14; + } + + /// + /// Sets the current method address, for method calls. + /// + /// Packed address and increment value + private void SetMethAddr(int value) + { + _methAddr = (value >> 0) & 0xfff; + _methIncr = (value >> 12) & 0x3f; + } + + /// + /// Sets the destination register value. + /// + /// Value to set (usually the operation result) + private void SetDstGpr(int value) + { + _gprs[(_opCode >> 8) & 7] = value; + } + + /// + /// Gets first operand value from the respective register. + /// + /// Operand value + private int GetGprA() + { + return GetGprValue((_opCode >> 11) & 7); + } + + /// + /// Gets second operand value from the respective register. + /// + /// Operand value + private int GetGprB() + { + return GetGprValue((_opCode >> 14) & 7); + } + + /// + /// Gets the value from a register, or 0 if the R0 register is specified. + /// + /// Index of the register + /// Register value + private int GetGprValue(int index) + { + return index != 0 ? _gprs[index] : 0; + } + + /// + /// Fetches a call argument from the call argument FIFO. + /// + /// The call argument, or 0 if the FIFO is empty + private int FetchParam() + { + if (!Fifo.TryDequeue(out var value)) + { + Logger.Warning?.Print(LogClass.Gpu, "Macro attempted to fetch an inexistent argument."); + + return 0; + } + + return value.Word; + } + + /// + /// Reads data from a GPU register. + /// + /// Current GPU state + /// Register offset to read + /// GPU register value + private static int Read(IDeviceState state, int reg) + { + return state.Read(reg * 4); + } + + /// + /// Performs a GPU method call. + /// + /// Current GPU state + /// Call argument + private void Send(IDeviceState state, int value) + { + state.Write(_methAddr * 4, value); + + _methAddr += _methIncr; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJit.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJit.cs new file mode 100644 index 00000000..0f26490a --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJit.cs @@ -0,0 +1,39 @@ +using Ryujinx.Graphics.Device; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Engine.MME +{ + /// + /// Represents a execution engine that uses a Just-in-Time compiler for fast execution. + /// + class MacroJit : IMacroEE + { + private readonly MacroJitContext _context = new(); + + /// + /// Arguments FIFO. + /// + public Queue Fifo => _context.Fifo; + + private MacroJitCompiler.MacroExecute _execute; + + /// + /// Executes a macro program until it exits. + /// + /// Code of the program to execute + /// Current GPU state + /// Optional argument passed to the program, 0 if not used + public void Execute(ReadOnlySpan code, IDeviceState state, int arg0) + { + if (_execute == null) + { + MacroJitCompiler compiler = new(); + + _execute = compiler.Compile(code); + } + + _execute(_context, state, arg0); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitCompiler.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitCompiler.cs new file mode 100644 index 00000000..d5229970 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitCompiler.cs @@ -0,0 +1,517 @@ +using Ryujinx.Graphics.Device; +using System; +using System.Collections.Generic; +using System.Reflection.Emit; + +namespace Ryujinx.Graphics.Gpu.Engine.MME +{ + /// + /// Represents a Macro Just-in-Time compiler. + /// R + class MacroJitCompiler + { + private readonly DynamicMethod _meth; + private readonly ILGenerator _ilGen; + private readonly LocalBuilder[] _gprs; + private readonly LocalBuilder _carry; + private readonly LocalBuilder _methAddr; + private readonly LocalBuilder _methIncr; + + /// + /// Creates a new instance of the Macro Just-in-Time compiler. + /// + public MacroJitCompiler() + { + _meth = new DynamicMethod("Macro", typeof(void), new Type[] { typeof(MacroJitContext), typeof(IDeviceState), typeof(int) }); + _ilGen = _meth.GetILGenerator(); + _gprs = new LocalBuilder[8]; + + for (int i = 1; i < 8; i++) + { + _gprs[i] = _ilGen.DeclareLocal(typeof(int)); + } + + _carry = _ilGen.DeclareLocal(typeof(int)); + _methAddr = _ilGen.DeclareLocal(typeof(int)); + _methIncr = _ilGen.DeclareLocal(typeof(int)); + + _ilGen.Emit(OpCodes.Ldarg_2); + _ilGen.Emit(OpCodes.Stloc, _gprs[1]); + } + + public delegate void MacroExecute(MacroJitContext context, IDeviceState state, int arg0); + + /// + /// Translates a new piece of GPU Macro code into host executable code. + /// + /// Code to be translated + /// Delegate of the host compiled code + public MacroExecute Compile(ReadOnlySpan code) + { + Dictionary labels = new(); + + int lastTarget = 0; + int i; + + // Collect all branch targets. + for (i = 0; i < code.Length; i++) + { + int opCode = code[i]; + + if ((opCode & 7) == 7) + { + int target = i + (opCode >> 14); + + if (!labels.ContainsKey(target)) + { + labels.Add(target, _ilGen.DefineLabel()); + } + + if (lastTarget < target) + { + lastTarget = target; + } + } + + bool exit = (opCode & 0x80) != 0; + + if (exit && i >= lastTarget) + { + break; + } + } + + // Code generation. + for (i = 0; i < code.Length; i++) + { + if (labels.TryGetValue(i, out Label label)) + { + _ilGen.MarkLabel(label); + } + + Emit(code, i, labels); + + int opCode = code[i]; + + bool exit = (opCode & 0x80) != 0; + + if (exit) + { + Emit(code, i + 1, labels); + _ilGen.Emit(OpCodes.Ret); + + if (i >= lastTarget) + { + break; + } + } + } + + if (i == code.Length) + { + _ilGen.Emit(OpCodes.Ret); + } + + return _meth.CreateDelegate(); + } + + /// + /// Emits IL equivalent to the Macro instruction at a given offset. + /// + /// GPU Macro code + /// Offset, in words, where the instruction is located + /// Labels for Macro branch targets, used by branch instructions + private void Emit(ReadOnlySpan code, int offset, Dictionary labels) + { + int opCode = code[offset]; + + if ((opCode & 7) < 7) + { + // Operation produces a value. + AssignmentOperation asgOp = (AssignmentOperation)((opCode >> 4) & 7); + + EmitAluOp(opCode); + + switch (asgOp) + { + // Fetch parameter and ignore result. + case AssignmentOperation.IgnoreAndFetch: + _ilGen.Emit(OpCodes.Pop); + EmitFetchParam(); + EmitStoreDstGpr(opCode); + break; + // Move result. + case AssignmentOperation.Move: + EmitStoreDstGpr(opCode); + break; + // Move result and use as Method Address. + case AssignmentOperation.MoveAndSetMaddr: + _ilGen.Emit(OpCodes.Dup); + EmitStoreDstGpr(opCode); + EmitStoreMethAddr(); + break; + // Fetch parameter and send result. + case AssignmentOperation.FetchAndSend: + EmitFetchParam(); + EmitStoreDstGpr(opCode); + EmitSend(); + break; + // Move and send result. + case AssignmentOperation.MoveAndSend: + _ilGen.Emit(OpCodes.Dup); + EmitStoreDstGpr(opCode); + EmitSend(); + break; + // Fetch parameter and use result as Method Address. + case AssignmentOperation.FetchAndSetMaddr: + EmitFetchParam(); + EmitStoreDstGpr(opCode); + EmitStoreMethAddr(); + break; + // Move result and use as Method Address, then fetch and send parameter. + case AssignmentOperation.MoveAndSetMaddrThenFetchAndSend: + _ilGen.Emit(OpCodes.Dup); + EmitStoreDstGpr(opCode); + EmitStoreMethAddr(); + EmitFetchParam(); + EmitSend(); + break; + // Move result and use as Method Address, then send bits 17:12 of result. + case AssignmentOperation.MoveAndSetMaddrThenSendHigh: + _ilGen.Emit(OpCodes.Dup); + _ilGen.Emit(OpCodes.Dup); + EmitStoreDstGpr(opCode); + EmitStoreMethAddr(); + _ilGen.Emit(OpCodes.Ldc_I4, 12); + _ilGen.Emit(OpCodes.Shr_Un); + _ilGen.Emit(OpCodes.Ldc_I4, 0x3f); + _ilGen.Emit(OpCodes.And); + EmitSend(); + break; + } + } + else + { + // Branch. + bool onNotZero = ((opCode >> 4) & 1) != 0; + + EmitLoadGprA(opCode); + + Label lblSkip = _ilGen.DefineLabel(); + + if (onNotZero) + { + _ilGen.Emit(OpCodes.Brfalse, lblSkip); + } + else + { + _ilGen.Emit(OpCodes.Brtrue, lblSkip); + } + + bool noDelays = (opCode & 0x20) != 0; + + if (!noDelays) + { + Emit(code, offset + 1, labels); + } + + int target = offset + (opCode >> 14); + + _ilGen.Emit(OpCodes.Br, labels[target]); + + _ilGen.MarkLabel(lblSkip); + } + } + + /// + /// Emits IL for a Arithmetic and Logic Unit instruction. + /// + /// Instruction to be translated + /// Throw when the instruction encoding is invalid + private void EmitAluOp(int opCode) + { + AluOperation op = (AluOperation)(opCode & 7); + + switch (op) + { + case AluOperation.AluReg: + EmitAluOp((AluRegOperation)((opCode >> 17) & 0x1f), opCode); + break; + + case AluOperation.AddImmediate: + EmitLoadGprA(opCode); + EmitLoadImm(opCode); + _ilGen.Emit(OpCodes.Add); + break; + + case AluOperation.BitfieldReplace: + case AluOperation.BitfieldExtractLslImm: + case AluOperation.BitfieldExtractLslReg: + int bfSrcBit = (opCode >> 17) & 0x1f; + int bfSize = (opCode >> 22) & 0x1f; + int bfDstBit = (opCode >> 27) & 0x1f; + + int bfMask = (1 << bfSize) - 1; + + switch (op) + { + case AluOperation.BitfieldReplace: + EmitLoadGprB(opCode); + _ilGen.Emit(OpCodes.Ldc_I4, bfSrcBit); + _ilGen.Emit(OpCodes.Shr_Un); + _ilGen.Emit(OpCodes.Ldc_I4, bfMask); + _ilGen.Emit(OpCodes.And); + _ilGen.Emit(OpCodes.Ldc_I4, bfDstBit); + _ilGen.Emit(OpCodes.Shl); + EmitLoadGprA(opCode); + _ilGen.Emit(OpCodes.Ldc_I4, ~(bfMask << bfDstBit)); + _ilGen.Emit(OpCodes.And); + _ilGen.Emit(OpCodes.Or); + break; + + case AluOperation.BitfieldExtractLslImm: + EmitLoadGprB(opCode); + EmitLoadGprA(opCode); + _ilGen.Emit(OpCodes.Shr_Un); + _ilGen.Emit(OpCodes.Ldc_I4, bfMask); + _ilGen.Emit(OpCodes.And); + _ilGen.Emit(OpCodes.Ldc_I4, bfDstBit); + _ilGen.Emit(OpCodes.Shl); + break; + + case AluOperation.BitfieldExtractLslReg: + EmitLoadGprB(opCode); + _ilGen.Emit(OpCodes.Ldc_I4, bfSrcBit); + _ilGen.Emit(OpCodes.Shr_Un); + _ilGen.Emit(OpCodes.Ldc_I4, bfMask); + _ilGen.Emit(OpCodes.And); + EmitLoadGprA(opCode); + _ilGen.Emit(OpCodes.Shl); + break; + } + break; + + case AluOperation.ReadImmediate: + _ilGen.Emit(OpCodes.Ldarg_1); + EmitLoadGprA(opCode); + EmitLoadImm(opCode); + _ilGen.Emit(OpCodes.Add); + _ilGen.Emit(OpCodes.Call, typeof(MacroJitContext).GetMethod(nameof(MacroJitContext.Read))); + break; + + default: + throw new InvalidOperationException($"Invalid operation \"{op}\" on instruction 0x{opCode:X8}."); + } + } + + /// + /// Emits IL for a binary Arithmetic and Logic Unit instruction. + /// + /// Arithmetic and Logic Unit instruction + /// Raw instruction + /// Throw when the instruction encoding is invalid + private void EmitAluOp(AluRegOperation aluOp, int opCode) + { + switch (aluOp) + { + case AluRegOperation.Add: + EmitLoadGprA(opCode); + _ilGen.Emit(OpCodes.Conv_U8); + EmitLoadGprB(opCode); + _ilGen.Emit(OpCodes.Conv_U8); + _ilGen.Emit(OpCodes.Add); + _ilGen.Emit(OpCodes.Dup); + _ilGen.Emit(OpCodes.Ldc_I8, 0xffffffffL); + _ilGen.Emit(OpCodes.Cgt_Un); + _ilGen.Emit(OpCodes.Stloc, _carry); + _ilGen.Emit(OpCodes.Conv_U4); + break; + case AluRegOperation.AddWithCarry: + EmitLoadGprA(opCode); + _ilGen.Emit(OpCodes.Conv_U8); + EmitLoadGprB(opCode); + _ilGen.Emit(OpCodes.Conv_U8); + _ilGen.Emit(OpCodes.Ldloc_S, _carry); + _ilGen.Emit(OpCodes.Conv_U8); + _ilGen.Emit(OpCodes.Add); + _ilGen.Emit(OpCodes.Add); + _ilGen.Emit(OpCodes.Dup); + _ilGen.Emit(OpCodes.Ldc_I8, 0xffffffffL); + _ilGen.Emit(OpCodes.Cgt_Un); + _ilGen.Emit(OpCodes.Stloc, _carry); + _ilGen.Emit(OpCodes.Conv_U4); + break; + case AluRegOperation.Subtract: + EmitLoadGprA(opCode); + _ilGen.Emit(OpCodes.Conv_U8); + EmitLoadGprB(opCode); + _ilGen.Emit(OpCodes.Conv_U8); + _ilGen.Emit(OpCodes.Sub); + _ilGen.Emit(OpCodes.Dup); + _ilGen.Emit(OpCodes.Ldc_I8, 0x100000000L); + _ilGen.Emit(OpCodes.Clt_Un); + _ilGen.Emit(OpCodes.Stloc, _carry); + _ilGen.Emit(OpCodes.Conv_U4); + break; + case AluRegOperation.SubtractWithBorrow: + EmitLoadGprA(opCode); + _ilGen.Emit(OpCodes.Conv_U8); + EmitLoadGprB(opCode); + _ilGen.Emit(OpCodes.Conv_U8); + _ilGen.Emit(OpCodes.Ldc_I4_1); + _ilGen.Emit(OpCodes.Ldloc_S, _carry); + _ilGen.Emit(OpCodes.Sub); + _ilGen.Emit(OpCodes.Conv_U8); + _ilGen.Emit(OpCodes.Sub); + _ilGen.Emit(OpCodes.Sub); + _ilGen.Emit(OpCodes.Dup); + _ilGen.Emit(OpCodes.Ldc_I8, 0x100000000L); + _ilGen.Emit(OpCodes.Clt_Un); + _ilGen.Emit(OpCodes.Stloc, _carry); + _ilGen.Emit(OpCodes.Conv_U4); + break; + case AluRegOperation.BitwiseExclusiveOr: + EmitLoadGprA(opCode); + EmitLoadGprB(opCode); + _ilGen.Emit(OpCodes.Xor); + break; + case AluRegOperation.BitwiseOr: + EmitLoadGprA(opCode); + EmitLoadGprB(opCode); + _ilGen.Emit(OpCodes.Or); + break; + case AluRegOperation.BitwiseAnd: + EmitLoadGprA(opCode); + EmitLoadGprB(opCode); + _ilGen.Emit(OpCodes.And); + break; + case AluRegOperation.BitwiseAndNot: + EmitLoadGprA(opCode); + EmitLoadGprB(opCode); + _ilGen.Emit(OpCodes.Not); + _ilGen.Emit(OpCodes.And); + break; + case AluRegOperation.BitwiseNotAnd: + EmitLoadGprA(opCode); + EmitLoadGprB(opCode); + _ilGen.Emit(OpCodes.And); + _ilGen.Emit(OpCodes.Not); + break; + default: + throw new InvalidOperationException($"Invalid operation \"{aluOp}\" on instruction 0x{opCode:X8}."); + } + } + + /// + /// Loads a immediate value on the IL evaluation stack. + /// + /// Instruction from where the immediate should be extracted + private void EmitLoadImm(int opCode) + { + // Note: The immediate is signed, the sign-extension is intended here. + _ilGen.Emit(OpCodes.Ldc_I4, opCode >> 14); + } + + /// + /// Loads a value from the General Purpose register specified as first operand on the IL evaluation stack. + /// + /// Instruction from where the register number should be extracted + private void EmitLoadGprA(int opCode) + { + EmitLoadGpr((opCode >> 11) & 7); + } + + /// + /// Loads a value from the General Purpose register specified as second operand on the IL evaluation stack. + /// + /// Instruction from where the register number should be extracted + private void EmitLoadGprB(int opCode) + { + EmitLoadGpr((opCode >> 14) & 7); + } + + /// + /// Loads a value a General Purpose register on the IL evaluation stack. + /// + /// + /// Register number 0 has a hardcoded value of 0. + /// + /// Register number + private void EmitLoadGpr(int index) + { + if (index == 0) + { + _ilGen.Emit(OpCodes.Ldc_I4_0); + } + else + { + _ilGen.Emit(OpCodes.Ldloc_S, _gprs[index]); + } + } + + /// + /// Emits a call to the method that fetches an argument from the arguments FIFO. + /// The argument is pushed into the IL evaluation stack. + /// + private void EmitFetchParam() + { + _ilGen.Emit(OpCodes.Ldarg_0); + _ilGen.Emit(OpCodes.Call, typeof(MacroJitContext).GetMethod(nameof(MacroJitContext.FetchParam))); + } + + /// + /// Stores the value on the top of the IL evaluation stack into a General Purpose register. + /// + /// + /// Register number 0 does not exist, reads are hardcoded to 0, and writes are simply discarded. + /// + /// Instruction from where the register number should be extracted + private void EmitStoreDstGpr(int opCode) + { + int index = (opCode >> 8) & 7; + + if (index == 0) + { + _ilGen.Emit(OpCodes.Pop); + } + else + { + _ilGen.Emit(OpCodes.Stloc_S, _gprs[index]); + } + } + + /// + /// Stores the value on the top of the IL evaluation stack as method address. + /// This will be used on subsequent send calls as the destination method address. + /// Additionally, the 6 bits starting at bit 12 will be used as increment value, + /// added to the method address after each sent value. + /// + private void EmitStoreMethAddr() + { + _ilGen.Emit(OpCodes.Dup); + _ilGen.Emit(OpCodes.Ldc_I4, 0xfff); + _ilGen.Emit(OpCodes.And); + _ilGen.Emit(OpCodes.Stloc_S, _methAddr); + _ilGen.Emit(OpCodes.Ldc_I4, 12); + _ilGen.Emit(OpCodes.Shr_Un); + _ilGen.Emit(OpCodes.Ldc_I4, 0x3f); + _ilGen.Emit(OpCodes.And); + _ilGen.Emit(OpCodes.Stloc_S, _methIncr); + } + + /// + /// Sends the value on the top of the IL evaluation stack to the GPU, + /// using the current method address. + /// + private void EmitSend() + { + _ilGen.Emit(OpCodes.Ldarg_1); + _ilGen.Emit(OpCodes.Ldloc_S, _methAddr); + _ilGen.Emit(OpCodes.Call, typeof(MacroJitContext).GetMethod(nameof(MacroJitContext.Send))); + _ilGen.Emit(OpCodes.Ldloc_S, _methAddr); + _ilGen.Emit(OpCodes.Ldloc_S, _methIncr); + _ilGen.Emit(OpCodes.Add); + _ilGen.Emit(OpCodes.Stloc_S, _methAddr); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitContext.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitContext.cs new file mode 100644 index 00000000..01bff8e8 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitContext.cs @@ -0,0 +1,55 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Device; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Engine.MME +{ + /// + /// Represents a Macro Just-in-Time compiler execution context. + /// + class MacroJitContext + { + /// + /// Arguments FIFO. + /// + public Queue Fifo { get; } = new(); + + /// + /// Fetches a arguments from the arguments FIFO. + /// + /// The call argument, or 0 if the FIFO is empty + public int FetchParam() + { + if (!Fifo.TryDequeue(out var value)) + { + Logger.Warning?.Print(LogClass.Gpu, "Macro attempted to fetch an inexistent argument."); + + return 0; + } + + return value.Word; + } + + /// + /// Reads data from a GPU register. + /// + /// Current GPU state + /// Register offset to read + /// GPU register value + public static int Read(IDeviceState state, int reg) + { + return state.Read(reg * 4); + } + + /// + /// Performs a GPU method call. + /// + /// Call argument + /// Current GPU state + /// Address, in words, of the method + public static void Send(int value, IDeviceState state, int methAddr) + { + state.Write(methAddr * 4, value); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MmeShadowScratch.cs b/src/Ryujinx.Graphics.Gpu/Engine/MmeShadowScratch.cs new file mode 100644 index 00000000..b4346add --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/MmeShadowScratch.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + /// + /// Represents temporary storage used by macros. + /// + [StructLayout(LayoutKind.Sequential, Size = 1024)] + struct MmeShadowScratch + { +#pragma warning disable CS0169 // The private field is never used + private uint _e0; +#pragma warning restore CS0169 + public ref uint this[int index] => ref AsSpan()[index]; + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 256); + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/SetMmeShadowRamControlMode.cs b/src/Ryujinx.Graphics.Gpu/Engine/SetMmeShadowRamControlMode.cs new file mode 100644 index 00000000..b9a5c74a --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/SetMmeShadowRamControlMode.cs @@ -0,0 +1,31 @@ +namespace Ryujinx.Graphics.Gpu.Engine +{ + /// + /// MME shadow RAM control mode. + /// + enum SetMmeShadowRamControlMode + { + MethodTrack = 0, + MethodTrackWithFilter = 1, + MethodPassthrough = 2, + MethodReplay = 3, + } + + static class SetMmeShadowRamControlModeExtensions + { + public static bool IsTrack(this SetMmeShadowRamControlMode mode) + { + return mode == SetMmeShadowRamControlMode.MethodTrack || mode == SetMmeShadowRamControlMode.MethodTrackWithFilter; + } + + public static bool IsPassthrough(this SetMmeShadowRamControlMode mode) + { + return mode == SetMmeShadowRamControlMode.MethodPassthrough; + } + + public static bool IsReplay(this SetMmeShadowRamControlMode mode) + { + return mode == SetMmeShadowRamControlMode.MethodReplay; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs b/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs new file mode 100644 index 00000000..7bff1c4b --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs @@ -0,0 +1,113 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + /// + /// Shader texture properties conversion methods. + /// + static class ShaderTexture + { + /// + /// Gets a texture target from a sampler type. + /// + /// Sampler type + /// Texture target value + public static Target GetTarget(SamplerType type) + { + type &= ~SamplerType.Shadow; + + switch (type) + { + case SamplerType.Texture1D: + return Target.Texture1D; + + case SamplerType.TextureBuffer: + return Target.TextureBuffer; + + case SamplerType.Texture1D | SamplerType.Array: + return Target.Texture1DArray; + + case SamplerType.Texture2D: + return Target.Texture2D; + + case SamplerType.Texture2D | SamplerType.Array: + return Target.Texture2DArray; + + case SamplerType.Texture2D | SamplerType.Multisample: + return Target.Texture2DMultisample; + + case SamplerType.Texture2D | SamplerType.Multisample | SamplerType.Array: + return Target.Texture2DMultisampleArray; + + case SamplerType.Texture3D: + return Target.Texture3D; + + case SamplerType.TextureCube: + return Target.Cubemap; + + case SamplerType.TextureCube | SamplerType.Array: + return Target.CubemapArray; + } + + Logger.Warning?.Print(LogClass.Gpu, $"Invalid sampler type \"{type}\"."); + + return Target.Texture2D; + } + + /// + /// Gets a texture format from a shader image format. + /// + /// Shader image format + /// Texture format + public static Format GetFormat(TextureFormat format) + { + return format switch + { +#pragma warning disable IDE0055 // Disable formatting + TextureFormat.R8Unorm => Format.R8Unorm, + TextureFormat.R8Snorm => Format.R8Snorm, + TextureFormat.R8Uint => Format.R8Uint, + TextureFormat.R8Sint => Format.R8Sint, + TextureFormat.R16Float => Format.R16Float, + TextureFormat.R16Unorm => Format.R16Unorm, + TextureFormat.R16Snorm => Format.R16Snorm, + TextureFormat.R16Uint => Format.R16Uint, + TextureFormat.R16Sint => Format.R16Sint, + TextureFormat.R32Float => Format.R32Float, + TextureFormat.R32Uint => Format.R32Uint, + TextureFormat.R32Sint => Format.R32Sint, + TextureFormat.R8G8Unorm => Format.R8G8Unorm, + TextureFormat.R8G8Snorm => Format.R8G8Snorm, + TextureFormat.R8G8Uint => Format.R8G8Uint, + TextureFormat.R8G8Sint => Format.R8G8Sint, + TextureFormat.R16G16Float => Format.R16G16Float, + TextureFormat.R16G16Unorm => Format.R16G16Unorm, + TextureFormat.R16G16Snorm => Format.R16G16Snorm, + TextureFormat.R16G16Uint => Format.R16G16Uint, + TextureFormat.R16G16Sint => Format.R16G16Sint, + TextureFormat.R32G32Float => Format.R32G32Float, + TextureFormat.R32G32Uint => Format.R32G32Uint, + TextureFormat.R32G32Sint => Format.R32G32Sint, + TextureFormat.R8G8B8A8Unorm => Format.R8G8B8A8Unorm, + TextureFormat.R8G8B8A8Snorm => Format.R8G8B8A8Snorm, + TextureFormat.R8G8B8A8Uint => Format.R8G8B8A8Uint, + TextureFormat.R8G8B8A8Sint => Format.R8G8B8A8Sint, + TextureFormat.R16G16B16A16Float => Format.R16G16B16A16Float, + TextureFormat.R16G16B16A16Unorm => Format.R16G16B16A16Unorm, + TextureFormat.R16G16B16A16Snorm => Format.R16G16B16A16Snorm, + TextureFormat.R16G16B16A16Uint => Format.R16G16B16A16Uint, + TextureFormat.R16G16B16A16Sint => Format.R16G16B16A16Sint, + TextureFormat.R32G32B32A32Float => Format.R32G32B32A32Float, + TextureFormat.R32G32B32A32Uint => Format.R32G32B32A32Uint, + TextureFormat.R32G32B32A32Sint => Format.R32G32B32A32Sint, + TextureFormat.R10G10B10A2Unorm => Format.R10G10B10A2Unorm, + TextureFormat.R10G10B10A2Uint => Format.R10G10B10A2Uint, + TextureFormat.R11G11B10Float => Format.R11G11B10Float, + _ => 0, +#pragma warning restore IDE0055 + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendFunctions.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendFunctions.cs new file mode 100644 index 00000000..0aca3907 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendFunctions.cs @@ -0,0 +1,4226 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender +{ + static class AdvancedBlendFunctions + { + public static readonly AdvancedBlendUcode[] Table = new AdvancedBlendUcode[] + { + new AdvancedBlendUcode(AdvancedBlendOp.PlusClamped, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedPlusClampedPremul), + new AdvancedBlendUcode(AdvancedBlendOp.PlusClampedAlpha, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedPlusClampedAlphaPremul), + new AdvancedBlendUcode(AdvancedBlendOp.PlusDarker, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedPlusDarkerPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedMultiplyPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedScreenPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedOverlayPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedDarkenPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedLightenPremul), + new AdvancedBlendUcode(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedColorDodgePremul), + new AdvancedBlendUcode(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedColorBurnPremul), + new AdvancedBlendUcode(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedHardLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedSoftLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedDifferencePremul), + new AdvancedBlendUcode(AdvancedBlendOp.Minus, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedMinusPremul), + new AdvancedBlendUcode(AdvancedBlendOp.MinusClamped, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedMinusClampedPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedExclusionPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Contrast, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedContrastPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Invert, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedInvertPremul), + new AdvancedBlendUcode(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedInvertRGBPremul), + new AdvancedBlendUcode(AdvancedBlendOp.InvertOvg, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedInvertOvgPremul), + new AdvancedBlendUcode(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedLinearDodgePremul), + new AdvancedBlendUcode(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedLinearBurnPremul), + new AdvancedBlendUcode(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedVividLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedLinearLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedPinLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedHardMixPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Red, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedRedPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Green, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedGreenPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Blue, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedBluePremul), + new AdvancedBlendUcode(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedHslHuePremul), + new AdvancedBlendUcode(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedHslSaturationPremul), + new AdvancedBlendUcode(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedHslColorPremul), + new AdvancedBlendUcode(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Uncorrelated, true, GenUncorrelatedHslLuminosityPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Src, AdvancedBlendOverlap.Disjoint, true, GenDisjointSrcPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Dst, AdvancedBlendOverlap.Disjoint, true, GenDisjointDstPremul), + new AdvancedBlendUcode(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Disjoint, true, GenDisjointSrcOverPremul), + new AdvancedBlendUcode(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Disjoint, true, GenDisjointDstOverPremul), + new AdvancedBlendUcode(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Disjoint, true, GenDisjointSrcInPremul), + new AdvancedBlendUcode(AdvancedBlendOp.DstIn, AdvancedBlendOverlap.Disjoint, true, GenDisjointDstInPremul), + new AdvancedBlendUcode(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Disjoint, true, GenDisjointSrcOutPremul), + new AdvancedBlendUcode(AdvancedBlendOp.DstOut, AdvancedBlendOverlap.Disjoint, true, GenDisjointDstOutPremul), + new AdvancedBlendUcode(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Disjoint, true, GenDisjointSrcAtopPremul), + new AdvancedBlendUcode(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Disjoint, true, GenDisjointDstAtopPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Disjoint, true, GenDisjointXorPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Plus, AdvancedBlendOverlap.Disjoint, true, GenDisjointPlusPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Disjoint, true, GenDisjointMultiplyPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Disjoint, true, GenDisjointScreenPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Disjoint, true, GenDisjointOverlayPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Disjoint, true, GenDisjointDarkenPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Disjoint, true, GenDisjointLightenPremul), + new AdvancedBlendUcode(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Disjoint, true, GenDisjointColorDodgePremul), + new AdvancedBlendUcode(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Disjoint, true, GenDisjointColorBurnPremul), + new AdvancedBlendUcode(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Disjoint, true, GenDisjointHardLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Disjoint, true, GenDisjointSoftLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Disjoint, true, GenDisjointDifferencePremul), + new AdvancedBlendUcode(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Disjoint, true, GenDisjointExclusionPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Invert, AdvancedBlendOverlap.Disjoint, true, GenDisjointInvertPremul), + new AdvancedBlendUcode(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Disjoint, true, GenDisjointInvertRGBPremul), + new AdvancedBlendUcode(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Disjoint, true, GenDisjointLinearDodgePremul), + new AdvancedBlendUcode(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Disjoint, true, GenDisjointLinearBurnPremul), + new AdvancedBlendUcode(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Disjoint, true, GenDisjointVividLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Disjoint, true, GenDisjointLinearLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Disjoint, true, GenDisjointPinLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Disjoint, true, GenDisjointHardMixPremul), + new AdvancedBlendUcode(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Disjoint, true, GenDisjointHslHuePremul), + new AdvancedBlendUcode(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Disjoint, true, GenDisjointHslSaturationPremul), + new AdvancedBlendUcode(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Disjoint, true, GenDisjointHslColorPremul), + new AdvancedBlendUcode(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Disjoint, true, GenDisjointHslLuminosityPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Src, AdvancedBlendOverlap.Conjoint, true, GenConjointSrcPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Dst, AdvancedBlendOverlap.Conjoint, true, GenConjointDstPremul), + new AdvancedBlendUcode(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Conjoint, true, GenConjointSrcOverPremul), + new AdvancedBlendUcode(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Conjoint, true, GenConjointDstOverPremul), + new AdvancedBlendUcode(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Conjoint, true, GenConjointSrcInPremul), + new AdvancedBlendUcode(AdvancedBlendOp.DstIn, AdvancedBlendOverlap.Conjoint, true, GenConjointDstInPremul), + new AdvancedBlendUcode(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Conjoint, true, GenConjointSrcOutPremul), + new AdvancedBlendUcode(AdvancedBlendOp.DstOut, AdvancedBlendOverlap.Conjoint, true, GenConjointDstOutPremul), + new AdvancedBlendUcode(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Conjoint, true, GenConjointSrcAtopPremul), + new AdvancedBlendUcode(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Conjoint, true, GenConjointDstAtopPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Conjoint, true, GenConjointXorPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Conjoint, true, GenConjointMultiplyPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Conjoint, true, GenConjointScreenPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Conjoint, true, GenConjointOverlayPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Conjoint, true, GenConjointDarkenPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Conjoint, true, GenConjointLightenPremul), + new AdvancedBlendUcode(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Conjoint, true, GenConjointColorDodgePremul), + new AdvancedBlendUcode(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Conjoint, true, GenConjointColorBurnPremul), + new AdvancedBlendUcode(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Conjoint, true, GenConjointHardLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Conjoint, true, GenConjointSoftLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Conjoint, true, GenConjointDifferencePremul), + new AdvancedBlendUcode(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Conjoint, true, GenConjointExclusionPremul), + new AdvancedBlendUcode(AdvancedBlendOp.Invert, AdvancedBlendOverlap.Conjoint, true, GenConjointInvertPremul), + new AdvancedBlendUcode(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Conjoint, true, GenConjointInvertRGBPremul), + new AdvancedBlendUcode(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Conjoint, true, GenConjointLinearDodgePremul), + new AdvancedBlendUcode(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Conjoint, true, GenConjointLinearBurnPremul), + new AdvancedBlendUcode(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Conjoint, true, GenConjointVividLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Conjoint, true, GenConjointLinearLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Conjoint, true, GenConjointPinLightPremul), + new AdvancedBlendUcode(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Conjoint, true, GenConjointHardMixPremul), + new AdvancedBlendUcode(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Conjoint, true, GenConjointHslHuePremul), + new AdvancedBlendUcode(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Conjoint, true, GenConjointHslSaturationPremul), + new AdvancedBlendUcode(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Conjoint, true, GenConjointHslColorPremul), + new AdvancedBlendUcode(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Conjoint, true, GenConjointHslLuminosityPremul), + new AdvancedBlendUcode(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedDstOver), + new AdvancedBlendUcode(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedSrcIn), + new AdvancedBlendUcode(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedSrcOut), + new AdvancedBlendUcode(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedSrcAtop), + new AdvancedBlendUcode(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedDstAtop), + new AdvancedBlendUcode(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedXor), + new AdvancedBlendUcode(AdvancedBlendOp.PlusClamped, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedPlusClamped), + new AdvancedBlendUcode(AdvancedBlendOp.PlusClampedAlpha, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedPlusClampedAlpha), + new AdvancedBlendUcode(AdvancedBlendOp.PlusDarker, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedPlusDarker), + new AdvancedBlendUcode(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedMultiply), + new AdvancedBlendUcode(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedScreen), + new AdvancedBlendUcode(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedOverlay), + new AdvancedBlendUcode(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedDarken), + new AdvancedBlendUcode(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedLighten), + new AdvancedBlendUcode(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedColorDodge), + new AdvancedBlendUcode(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedColorBurn), + new AdvancedBlendUcode(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedHardLight), + new AdvancedBlendUcode(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedSoftLight), + new AdvancedBlendUcode(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedDifference), + new AdvancedBlendUcode(AdvancedBlendOp.Minus, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedMinus), + new AdvancedBlendUcode(AdvancedBlendOp.MinusClamped, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedMinusClamped), + new AdvancedBlendUcode(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedExclusion), + new AdvancedBlendUcode(AdvancedBlendOp.Contrast, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedContrast), + new AdvancedBlendUcode(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedInvertRGB), + new AdvancedBlendUcode(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedLinearDodge), + new AdvancedBlendUcode(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedLinearBurn), + new AdvancedBlendUcode(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedVividLight), + new AdvancedBlendUcode(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedLinearLight), + new AdvancedBlendUcode(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedPinLight), + new AdvancedBlendUcode(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedHardMix), + new AdvancedBlendUcode(AdvancedBlendOp.Red, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedRed), + new AdvancedBlendUcode(AdvancedBlendOp.Green, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedGreen), + new AdvancedBlendUcode(AdvancedBlendOp.Blue, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedBlue), + new AdvancedBlendUcode(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedHslHue), + new AdvancedBlendUcode(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedHslSaturation), + new AdvancedBlendUcode(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedHslColor), + new AdvancedBlendUcode(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Uncorrelated, false, GenUncorrelatedHslLuminosity), + new AdvancedBlendUcode(AdvancedBlendOp.Src, AdvancedBlendOverlap.Disjoint, false, GenDisjointSrc), + new AdvancedBlendUcode(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Disjoint, false, GenDisjointSrcOver), + new AdvancedBlendUcode(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Disjoint, false, GenDisjointDstOver), + new AdvancedBlendUcode(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Disjoint, false, GenDisjointSrcIn), + new AdvancedBlendUcode(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Disjoint, false, GenDisjointSrcOut), + new AdvancedBlendUcode(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Disjoint, false, GenDisjointSrcAtop), + new AdvancedBlendUcode(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Disjoint, false, GenDisjointDstAtop), + new AdvancedBlendUcode(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Disjoint, false, GenDisjointXor), + new AdvancedBlendUcode(AdvancedBlendOp.Plus, AdvancedBlendOverlap.Disjoint, false, GenDisjointPlus), + new AdvancedBlendUcode(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Disjoint, false, GenDisjointMultiply), + new AdvancedBlendUcode(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Disjoint, false, GenDisjointScreen), + new AdvancedBlendUcode(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Disjoint, false, GenDisjointOverlay), + new AdvancedBlendUcode(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Disjoint, false, GenDisjointDarken), + new AdvancedBlendUcode(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Disjoint, false, GenDisjointLighten), + new AdvancedBlendUcode(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Disjoint, false, GenDisjointColorDodge), + new AdvancedBlendUcode(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Disjoint, false, GenDisjointColorBurn), + new AdvancedBlendUcode(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Disjoint, false, GenDisjointHardLight), + new AdvancedBlendUcode(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Disjoint, false, GenDisjointSoftLight), + new AdvancedBlendUcode(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Disjoint, false, GenDisjointDifference), + new AdvancedBlendUcode(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Disjoint, false, GenDisjointExclusion), + new AdvancedBlendUcode(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Disjoint, false, GenDisjointInvertRGB), + new AdvancedBlendUcode(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Disjoint, false, GenDisjointLinearDodge), + new AdvancedBlendUcode(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Disjoint, false, GenDisjointLinearBurn), + new AdvancedBlendUcode(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Disjoint, false, GenDisjointVividLight), + new AdvancedBlendUcode(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Disjoint, false, GenDisjointLinearLight), + new AdvancedBlendUcode(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Disjoint, false, GenDisjointPinLight), + new AdvancedBlendUcode(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Disjoint, false, GenDisjointHardMix), + new AdvancedBlendUcode(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Disjoint, false, GenDisjointHslHue), + new AdvancedBlendUcode(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Disjoint, false, GenDisjointHslSaturation), + new AdvancedBlendUcode(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Disjoint, false, GenDisjointHslColor), + new AdvancedBlendUcode(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Disjoint, false, GenDisjointHslLuminosity), + new AdvancedBlendUcode(AdvancedBlendOp.Src, AdvancedBlendOverlap.Conjoint, false, GenConjointSrc), + new AdvancedBlendUcode(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Conjoint, false, GenConjointSrcOver), + new AdvancedBlendUcode(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Conjoint, false, GenConjointDstOver), + new AdvancedBlendUcode(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Conjoint, false, GenConjointSrcIn), + new AdvancedBlendUcode(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Conjoint, false, GenConjointSrcOut), + new AdvancedBlendUcode(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Conjoint, false, GenConjointSrcAtop), + new AdvancedBlendUcode(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Conjoint, false, GenConjointDstAtop), + new AdvancedBlendUcode(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Conjoint, false, GenConjointXor), + new AdvancedBlendUcode(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Conjoint, false, GenConjointMultiply), + new AdvancedBlendUcode(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Conjoint, false, GenConjointScreen), + new AdvancedBlendUcode(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Conjoint, false, GenConjointOverlay), + new AdvancedBlendUcode(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Conjoint, false, GenConjointDarken), + new AdvancedBlendUcode(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Conjoint, false, GenConjointLighten), + new AdvancedBlendUcode(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Conjoint, false, GenConjointColorDodge), + new AdvancedBlendUcode(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Conjoint, false, GenConjointColorBurn), + new AdvancedBlendUcode(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Conjoint, false, GenConjointHardLight), + new AdvancedBlendUcode(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Conjoint, false, GenConjointSoftLight), + new AdvancedBlendUcode(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Conjoint, false, GenConjointDifference), + new AdvancedBlendUcode(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Conjoint, false, GenConjointExclusion), + new AdvancedBlendUcode(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Conjoint, false, GenConjointInvertRGB), + new AdvancedBlendUcode(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Conjoint, false, GenConjointLinearDodge), + new AdvancedBlendUcode(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Conjoint, false, GenConjointLinearBurn), + new AdvancedBlendUcode(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Conjoint, false, GenConjointVividLight), + new AdvancedBlendUcode(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Conjoint, false, GenConjointLinearLight), + new AdvancedBlendUcode(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Conjoint, false, GenConjointPinLight), + new AdvancedBlendUcode(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Conjoint, false, GenConjointHardMix), + new AdvancedBlendUcode(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Conjoint, false, GenConjointHslHue), + new AdvancedBlendUcode(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Conjoint, false, GenConjointHslSaturation), + new AdvancedBlendUcode(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Conjoint, false, GenConjointHslColor), + new AdvancedBlendUcode(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Conjoint, false, GenConjointHslLuminosity), + }; + + public static string GenTable() + { + // This can be used to generate the table on AdvancedBlendPreGenTable. + + StringBuilder sb = new(); + + sb.AppendLine($"private static Dictionary _entries = new()"); + sb.AppendLine("{"); + + foreach (var entry in Table) + { + Hash128 hash = XXHash128.ComputeHash(MemoryMarshal.Cast(entry.Code)); + + string[] constants = new string[entry.Constants != null ? entry.Constants.Length : 0]; + + for (int i = 0; i < constants.Length; i++) + { + RgbFloat rgb = entry.Constants[i]; + + constants[i] = string.Format(CultureInfo.InvariantCulture, "new " + nameof(RgbFloat) + "({0}f, {1}f, {2}f)", rgb.R, rgb.G, rgb.B); + } + + string constantList = constants.Length > 0 ? $"new[] {{ {string.Join(", ", constants)} }}" : $"Array.Empty<{nameof(RgbFloat)}>()"; + + static string EnumValue(string name, object value) + { + if (value.ToString() == "0") + { + return "0"; + } + + return $"{name}.{value}"; + } + + string alpha = $"new {nameof(FixedFunctionAlpha)}({EnumValue(nameof(BlendUcodeEnable), entry.Alpha.Enable)}, {EnumValue(nameof(BlendOp), entry.Alpha.AlphaOp)}, {EnumValue(nameof(BlendFactor), entry.Alpha.AlphaSrcFactor)}, {EnumValue(nameof(BlendFactor), entry.Alpha.AlphaDstFactor)})"; + + sb.AppendLine($" {{ new Hash128(0x{hash.Low:X16}, 0x{hash.High:X16}), new AdvancedBlendEntry({nameof(AdvancedBlendOp)}.{entry.Op}, {nameof(AdvancedBlendOverlap)}.{entry.Overlap}, {(entry.SrcPreMultiplied ? "true" : "false")}, {constantList}, {alpha}) }},"); + } + + sb.AppendLine("};"); + + return sb.ToString(); + } + + private static FixedFunctionAlpha GenUncorrelatedPlusClampedPremul(ref UcodeAssembler asm) + { + asm.Add(CC.T, Dest.PBR, OpBD.DstRGB, OpBD.SrcRGB); + asm.Min(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenUncorrelatedPlusClampedAlphaPremul(ref UcodeAssembler asm) + { + asm.Add(CC.T, Dest.Temp0, OpBD.DstRGB, OpBD.SrcRGB); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantOne); + asm.Min(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenUncorrelatedPlusDarkerPremul(ref UcodeAssembler asm) + { + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantOne); + asm.Add(CC.T, Dest.PBR, OpBD.PBR, OpBD.SrcRGB); + asm.Add(CC.T, Dest.PBR, OpBD.PBR, OpBD.DstRGB); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.SrcAAA); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.DstAAA); + asm.Max(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantZero); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenUncorrelatedMultiplyPremul(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.DstRGB); + asm.Mmadd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedScreenPremul(ref UcodeAssembler asm) + { + asm.Mmadd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.DstAAA, OpAC.DstRGB, OpBD.SrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.SrcRGB, OpBD.DstRGB); + asm.Mmadd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedOverlayPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.ConstantRGB); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.Temp2, OpBD.Temp1, OpAC.Temp2, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.ConstantOne, OpBD.Temp2); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedDarkenPremul(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.DstAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.DstRGB, OpBD.SrcAAA); + asm.Min(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR); + asm.Mmadd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedLightenPremul(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.DstAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.DstRGB, OpBD.SrcAAA); + asm.Max(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR); + asm.Mmadd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedColorDodgePremul(ref UcodeAssembler asm) + { + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.SrcRGB); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mul(CC.GT, Dest.PBR, OpAC.PBR, OpBD.SrcAAA); + asm.Mul(CC.GT, Dest.PBR, OpAC.PBR, OpBD.DstRGB); + asm.Min(CC.GT, Dest.PBR, OpAC.DstAAA, OpBD.PBR); + asm.Mul(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.SrcAAA); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.DstAAA); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.DstRGB, OpBD.ConstantZero); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Mmadd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedColorBurnPremul(ref UcodeAssembler asm) + { + asm.Mmsub(CC.T, Dest.Temp0, OpAC.DstAAA, OpBD.SrcAAA, OpAC.SrcAAA, OpBD.DstRGB); + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcRGB); + asm.Mul(CC.T, Dest.PBR, OpAC.Temp0, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.PBR); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.SrcAAA, OpBD.DstAAA, OpAC.SrcAAA, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcRGB, OpBD.ConstantZero); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.DstAAA, OpBD.DstRGB); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedHardLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.Temp2, OpBD.ConstantRGB); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.Temp2, OpBD.Temp1, OpAC.Temp2, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.ConstantOne, OpBD.Temp2); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedSoftLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(4, 0.25f, 0.25f, 0.25f); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantRGB); + asm.SetConstant(0, 0.2605f, 0.2605f, 0.2605f); + asm.Mul(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(1, -0.7817f, -0.7817f, -0.7817f); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(2, 0.3022f, 0.3022f, 0.3022f); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(3, 0.2192f, 0.2192f, 0.2192f); + asm.Add(CC.GT, Dest.Temp0, OpBD.PBR, OpBD.ConstantRGB); + asm.SetConstant(5, 16f, 16f, 16f); + asm.Mul(CC.LE, Dest.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(6, 12f, 12f, 12f); + asm.Mmsub(CC.LE, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(7, 3f, 3f, 3f); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Mmsub(CC.LE, Dest.Temp0, OpAC.Temp1, OpBD.ConstantOne, OpAC.Temp1, OpBD.Temp1); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedDifferencePremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.Temp0, OpBD.Temp2, OpBD.Temp1); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedMinusPremul(ref UcodeAssembler asm) + { + asm.Sub(CC.T, Dest.Temp0, OpBD.DstRGB, OpBD.SrcRGB); + return new FixedFunctionAlpha(BlendOp.ReverseSubtractGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedMinusClampedPremul(ref UcodeAssembler asm) + { + asm.Sub(CC.T, Dest.PBR, OpBD.DstRGB, OpBD.SrcRGB); + asm.Max(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Max(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantZero); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenUncorrelatedExclusionPremul(ref UcodeAssembler asm) + { + asm.Mmadd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.DstAAA, OpAC.DstRGB, OpBD.SrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.SrcRGB, OpBD.DstRGB); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.SrcRGB, OpBD.DstRGB); + asm.Mmadd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedContrastPremul(ref UcodeAssembler asm) + { + asm.SetConstant(0, 2f, 2f, 2f); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.DstRGB, OpBD.ConstantRGB, OpAC.DstAAA, OpBD.ConstantOne); + asm.Mmsub(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.SrcAAA, OpBD.ConstantOne); + asm.Mul(CC.T, Dest.PBR, OpAC.Temp0, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.PBR, OpBD.DstAAA); + asm.SetConstant(1, 0.5f, 0.5f, 0.5f); + asm.Mul(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantRGB); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedInvertPremul(ref UcodeAssembler asm) + { + asm.Mmsub(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA, OpAC.SrcAAA, OpBD.DstRGB); + asm.Madd(CC.T, Dest.Temp0, OpAC.DstRGB, OpBD.OneMinusSrcAAA, OpAC.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedInvertRGBPremul(ref UcodeAssembler asm) + { + asm.Mmsub(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.DstAAA, OpAC.SrcRGB, OpBD.DstRGB); + asm.Madd(CC.T, Dest.Temp0, OpAC.DstRGB, OpBD.OneMinusSrcAAA, OpAC.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedInvertOvgPremul(ref UcodeAssembler asm) + { + asm.Sub(CC.T, Dest.PBR, OpBD.ConstantOne, OpBD.DstRGB); + asm.Mmadd(CC.T, Dest.Temp0, OpAC.SrcAAA, OpBD.PBR, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedLinearDodgePremul(ref UcodeAssembler asm) + { + asm.Mmadd(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.DstAAA, OpAC.DstRGB, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR); + asm.Mmadd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedLinearBurnPremul(ref UcodeAssembler asm) + { + asm.Mmadd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.DstAAA, OpAC.DstRGB, OpBD.SrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.SrcAAA, OpBD.DstAAA); + asm.Max(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantZero); + asm.Mmadd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedVividLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.Temp2, OpBD.ConstantRGB); + asm.Sub(CC.GE, Dest.PBR, OpBD.ConstantOne, OpBD.Temp2); + asm.Add(CC.GE, Dest.PBR, OpBD.PBR, OpBD.PBR); + asm.Rcp(CC.GE, Dest.PBR, OpAC.PBR); + asm.Mul(CC.GE, Dest.PBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GE, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Add(CC.LT, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Rcp(CC.LT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.LT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne); + asm.Sub(CC.LT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.Temp2, OpBD.ConstantZero); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.Temp2, OpBD.ConstantOne); + asm.Mov(CC.GE, Dest.Temp0, OpBD.ConstantOne); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedLinearLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 2f, 2f, 2f); + asm.Madd(CC.T, Dest.PBR, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Min(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedPinLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Sub(CC.T, Dest.Temp0, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.ConstantZero); + asm.Add(CC.LE, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Min(CC.LE, Dest.Temp0, OpAC.PBR, OpBD.Temp1); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedHardMixPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Mul(CC.LT, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Mov(CC.GE, Dest.Temp0, OpBD.ConstantOne); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedRedPremul(ref UcodeAssembler asm) + { + asm.Mov(CC.T, Dest.Temp0, OpBD.DstRGB); + asm.Mov(CC.T, Dest.Temp0.R, OpBD.SrcRGB); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedGreenPremul(ref UcodeAssembler asm) + { + asm.Mov(CC.T, Dest.Temp0, OpBD.DstRGB); + asm.Mov(CC.T, Dest.Temp0.G, OpBD.SrcRGB); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedBluePremul(ref UcodeAssembler asm) + { + asm.Mov(CC.T, Dest.Temp0, OpBD.DstRGB); + asm.Mov(CC.T, Dest.Temp0.B, OpBD.SrcRGB); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedHslHuePremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.Temp0.GBR, OpAC.PBR, OpBD.Temp2); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp0); + asm.Rcp(CC.GT, Dest.Temp0, OpAC.Temp0); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.Temp2, OpAC.Temp0, OpBD.PBR); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.Temp2.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.Temp2); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp0, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedHslSaturationPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.PBR); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.T, Dest.Temp0.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp0); + asm.Rcp(CC.GT, Dest.Temp0, OpAC.Temp0); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.Temp1, OpAC.Temp0, OpBD.PBR); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.GT, Dest.Temp1.GBR, OpAC.PBR, OpBD.Temp2); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.Temp1); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp0, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedHslColorPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp2, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp2, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedHslLuminosityPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp2, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp2.BBB, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp1, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Add(CC.T, Dest.Temp1, OpBD.Temp1, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp2); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp1, OpBD.Temp2); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp2); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp1, OpBD.Temp2, OpAC.Temp2, OpBD.Temp2); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp2); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.SrcRGB, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenDisjointSrcPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl); + } + + private static FixedFunctionAlpha GenDisjointDstPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.DstAAA, OpAC.Temp1, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenDisjointSrcOverPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp2); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointDstOverPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp1); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointSrcInPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Sub(CC.T, Dest.Temp1.RToA, OpBD.DstAAA, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointDstInPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.DstAAA, OpAC.Temp1, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Sub(CC.T, Dest.Temp1.RToA, OpBD.DstAAA, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointSrcOutPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointDstOutPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointSrcAtopPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenDisjointDstAtopPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.DstAAA, OpAC.Temp1, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl); + } + + private static FixedFunctionAlpha GenDisjointXorPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + asm.Min(CC.T, Dest.Temp1, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Add(CC.T, Dest.Temp1.RToA, OpBD.Temp1, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointPlusPremul(ref UcodeAssembler asm) + { + asm.Add(CC.T, Dest.Temp0, OpBD.DstRGB, OpBD.SrcRGB); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenDisjointMultiplyPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointScreenPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.Temp2, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointOverlayPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.ConstantRGB); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.Temp2, OpBD.Temp1, OpAC.Temp2, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.ConstantOne, OpBD.Temp2); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointDarkenPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointLightenPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Max(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointColorDodgePremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.ConstantOne, OpBD.Temp2); + asm.Rcp(CC.GT, Dest.PBR, OpAC.Temp0); + asm.Mul(CC.GT, Dest.PBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.LE, Dest.Temp0, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.Temp1, OpBD.ConstantZero); + asm.Mov(CC.LE, Dest.Temp0, OpBD.ConstantZero); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointColorBurnPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.Temp2, OpBD.ConstantZero); + asm.Rcp(CC.GT, Dest.PBR, OpAC.Temp2); + asm.Mmsub(CC.GT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Max(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.ConstantOne, OpBD.Temp1); + asm.Mov(CC.LE, Dest.Temp0, OpBD.ConstantOne); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointHardLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.Temp2, OpBD.ConstantRGB); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.Temp2, OpBD.Temp1, OpAC.Temp2, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.ConstantOne, OpBD.Temp2); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointSoftLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(4, 0.25f, 0.25f, 0.25f); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantRGB); + asm.SetConstant(0, 0.2605f, 0.2605f, 0.2605f); + asm.Mul(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(1, -0.7817f, -0.7817f, -0.7817f); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(2, 0.3022f, 0.3022f, 0.3022f); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(3, 0.2192f, 0.2192f, 0.2192f); + asm.Add(CC.GT, Dest.Temp0, OpBD.PBR, OpBD.ConstantRGB); + asm.SetConstant(5, 16f, 16f, 16f); + asm.Mul(CC.LE, Dest.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(6, 12f, 12f, 12f); + asm.Mmsub(CC.LE, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(7, 3f, 3f, 3f); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Mmsub(CC.LE, Dest.Temp0, OpAC.Temp1, OpBD.ConstantOne, OpAC.Temp1, OpBD.Temp1); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointDifferencePremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.Temp0, OpBD.Temp2, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointExclusionPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.Temp2, OpBD.Temp1); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.Temp2, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointInvertPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp0, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenDisjointInvertRGBPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.ConstantOne, OpAC.Temp2, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp0, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenDisjointLinearDodgePremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Min(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointLinearBurnPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Max(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantZero); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointVividLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.Temp2, OpBD.ConstantRGB); + asm.Sub(CC.GE, Dest.PBR, OpBD.ConstantOne, OpBD.Temp2); + asm.Add(CC.GE, Dest.PBR, OpBD.PBR, OpBD.PBR); + asm.Rcp(CC.GE, Dest.PBR, OpAC.PBR); + asm.Mul(CC.GE, Dest.PBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GE, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Add(CC.LT, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Rcp(CC.LT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.LT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne); + asm.Sub(CC.LT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.Temp2, OpBD.ConstantZero); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.Temp2, OpBD.ConstantOne); + asm.Mov(CC.GE, Dest.Temp0, OpBD.ConstantOne); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointLinearLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 2f, 2f, 2f); + asm.Madd(CC.T, Dest.PBR, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Min(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointPinLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Sub(CC.T, Dest.Temp0, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.ConstantZero); + asm.Add(CC.LE, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Min(CC.LE, Dest.Temp0, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointHardMixPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Mul(CC.LT, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Mov(CC.GE, Dest.Temp0, OpBD.ConstantOne); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointHslHuePremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.Temp0.GBR, OpAC.PBR, OpBD.Temp2); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp0); + asm.Rcp(CC.GT, Dest.Temp0, OpAC.Temp0); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.Temp2, OpAC.Temp0, OpBD.PBR); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.Temp2.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.Temp2); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp0, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointHslSaturationPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.PBR); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.T, Dest.Temp0.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp0); + asm.Rcp(CC.GT, Dest.Temp0, OpAC.Temp0); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.Temp1, OpAC.Temp0, OpBD.PBR); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.GT, Dest.Temp1.GBR, OpAC.PBR, OpBD.Temp2); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.Temp1); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp0, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointHslColorPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp2, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp2, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointHslLuminosityPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp2, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp2.BBB, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp1, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Add(CC.T, Dest.Temp1, OpBD.Temp1, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp2); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp1, OpBD.Temp2); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp2); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp1, OpBD.Temp2, OpAC.Temp2, OpBD.Temp2); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp2); + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.Temp2, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenConjointSrcPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl); + } + + private static FixedFunctionAlpha GenConjointDstPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointSrcOverPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp2, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointDstOverPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp1, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointSrcInPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MinimumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointDstInPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MinimumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointSrcOutPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Max(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantZero); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenConjointDstOutPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Max(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantZero); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenConjointSrcAtopPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointDstAtopPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl); + } + + private static FixedFunctionAlpha GenConjointXorPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + asm.Sub(CC.T, Dest.Temp1.CC, OpBD.DstAAA, OpBD.SrcAAA); + asm.Sub(CC.LT, Dest.Temp1, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mov(CC.T, Dest.Temp1.RToA, OpBD.Temp1); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenConjointMultiplyPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointScreenPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.Temp2, OpBD.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointOverlayPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.ConstantRGB); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.Temp2, OpBD.Temp1, OpAC.Temp2, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.ConstantOne, OpBD.Temp2); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointDarkenPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointLightenPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Max(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointColorDodgePremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.ConstantOne, OpBD.Temp2); + asm.Rcp(CC.GT, Dest.PBR, OpAC.Temp0); + asm.Mul(CC.GT, Dest.PBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.LE, Dest.Temp0, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.Temp1, OpBD.ConstantZero); + asm.Mov(CC.LE, Dest.Temp0, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointColorBurnPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.Temp2, OpBD.ConstantZero); + asm.Rcp(CC.GT, Dest.PBR, OpAC.Temp2); + asm.Mmsub(CC.GT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Max(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.ConstantOne, OpBD.Temp1); + asm.Mov(CC.LE, Dest.Temp0, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointHardLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.Temp2, OpBD.ConstantRGB); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.Temp2, OpBD.Temp1, OpAC.Temp2, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.ConstantOne, OpBD.Temp2); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointSoftLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(4, 0.25f, 0.25f, 0.25f); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantRGB); + asm.SetConstant(0, 0.2605f, 0.2605f, 0.2605f); + asm.Mul(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(1, -0.7817f, -0.7817f, -0.7817f); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(2, 0.3022f, 0.3022f, 0.3022f); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(3, 0.2192f, 0.2192f, 0.2192f); + asm.Add(CC.GT, Dest.Temp0, OpBD.PBR, OpBD.ConstantRGB); + asm.SetConstant(5, 16f, 16f, 16f); + asm.Mul(CC.LE, Dest.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(6, 12f, 12f, 12f); + asm.Mmsub(CC.LE, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(7, 3f, 3f, 3f); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Mmsub(CC.LE, Dest.Temp0, OpAC.Temp1, OpBD.ConstantOne, OpAC.Temp1, OpBD.Temp1); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointDifferencePremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.Temp0, OpBD.Temp2, OpBD.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointExclusionPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.Temp2, OpBD.Temp1); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.Temp2, OpBD.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointInvertPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointInvertRGBPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.ConstantOne, OpAC.Temp2, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointLinearDodgePremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Min(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointLinearBurnPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Max(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointVividLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.Temp2, OpBD.ConstantRGB); + asm.Sub(CC.GE, Dest.PBR, OpBD.ConstantOne, OpBD.Temp2); + asm.Add(CC.GE, Dest.PBR, OpBD.PBR, OpBD.PBR); + asm.Rcp(CC.GE, Dest.PBR, OpAC.PBR); + asm.Mul(CC.GE, Dest.PBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GE, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Add(CC.LT, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Rcp(CC.LT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.LT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne); + asm.Sub(CC.LT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.Temp2, OpBD.ConstantZero); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.Temp2, OpBD.ConstantOne); + asm.Mov(CC.GE, Dest.Temp0, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointLinearLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 2f, 2f, 2f); + asm.Madd(CC.T, Dest.PBR, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Min(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointPinLightPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Sub(CC.T, Dest.Temp0, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.ConstantZero); + asm.Add(CC.LE, Dest.PBR, OpBD.Temp2, OpBD.Temp2); + asm.Min(CC.LE, Dest.Temp0, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointHardMixPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Mul(CC.LT, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Mov(CC.GE, Dest.Temp0, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointHslHuePremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.Temp0.GBR, OpAC.PBR, OpBD.Temp2); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp0); + asm.Rcp(CC.GT, Dest.Temp0, OpAC.Temp0); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.Temp2, OpAC.Temp0, OpBD.PBR); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.Temp2.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.Temp2); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp0, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointHslSaturationPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.PBR); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.T, Dest.Temp0.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp0); + asm.Rcp(CC.GT, Dest.Temp0, OpAC.Temp0); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.Temp1, OpAC.Temp0, OpBD.PBR); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.GT, Dest.Temp1.GBR, OpAC.PBR, OpBD.Temp2); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.Temp1); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp0, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointHslColorPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp2, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp2, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointHslLuminosityPremul(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp2, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp2.BBB, OpAC.Temp2, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp1, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Add(CC.T, Dest.Temp1, OpBD.Temp1, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp2); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp1, OpBD.Temp2); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp2); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp1, OpBD.Temp2, OpAC.Temp2, OpBD.Temp2); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp2); + asm.Rcp(CC.T, Dest.PBR, OpAC.SrcAAA); + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.PBR); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp2, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedDstOver(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.DstRGB, OpBD.SrcAAA); + asm.Mmadd(CC.T, Dest.PBR, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedSrcIn(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.PBR, OpBD.DstAAA); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.DstAlphaGl, BlendFactor.ZeroGl); + } + + private static FixedFunctionAlpha GenUncorrelatedSrcOut(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.PBR, OpBD.OneMinusDstAAA); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneMinusDstAlphaGl, BlendFactor.ZeroGl); + } + + private static FixedFunctionAlpha GenUncorrelatedSrcAtop(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.PBR, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.DstRGB, OpBD.OneMinusSrcAAA, OpAC.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedDstAtop(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.DstRGB, OpBD.SrcAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl); + } + + private static FixedFunctionAlpha GenUncorrelatedXor(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.PBR, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.DstRGB, OpBD.OneMinusSrcAAA, OpAC.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneMinusDstAlphaGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedPlusClamped(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Add(CC.T, Dest.PBR, OpBD.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenUncorrelatedPlusClampedAlpha(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantOne); + asm.Min(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenUncorrelatedPlusDarker(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantOne); + asm.Add(CC.T, Dest.PBR, OpBD.PBR, OpBD.Temp2); + asm.Add(CC.T, Dest.PBR, OpBD.PBR, OpBD.DstRGB); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.SrcAAA); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.DstAAA); + asm.Max(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantZero); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenUncorrelatedMultiply(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.PBR, OpBD.DstRGB); + asm.Mmadd(CC.T, Dest.PBR, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedScreen(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mmadd(CC.T, Dest.PBR, OpAC.PBR, OpBD.DstAAA, OpAC.DstRGB, OpBD.SrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.Temp2, OpBD.DstRGB); + asm.Mmadd(CC.T, Dest.PBR, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedOverlay(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.ConstantRGB); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.SrcRGB, OpBD.Temp1, OpAC.SrcRGB, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.ConstantOne, OpBD.SrcRGB); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedDarken(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.PBR, OpBD.DstAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.DstRGB, OpBD.SrcAAA); + asm.Min(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR); + asm.Mmadd(CC.T, Dest.PBR, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedLighten(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.PBR, OpBD.DstAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.DstRGB, OpBD.SrcAAA); + asm.Max(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR); + asm.Mmadd(CC.T, Dest.PBR, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedColorDodge(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.PBR); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mul(CC.GT, Dest.PBR, OpAC.PBR, OpBD.SrcAAA); + asm.Mul(CC.GT, Dest.PBR, OpAC.PBR, OpBD.DstRGB); + asm.Min(CC.GT, Dest.PBR, OpAC.DstAAA, OpBD.PBR); + asm.Mul(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.SrcAAA); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.DstAAA); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.DstRGB, OpBD.ConstantZero); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Mmadd(CC.T, Dest.PBR, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedColorBurn(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.DstAAA, OpBD.SrcAAA, OpAC.SrcAAA, OpBD.DstRGB); + asm.Rcp(CC.T, Dest.PBR, OpAC.Temp2); + asm.Mul(CC.T, Dest.PBR, OpAC.Temp0, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.PBR); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.SrcAAA, OpBD.DstAAA, OpAC.SrcAAA, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.Temp2, OpBD.ConstantZero); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.DstAAA, OpBD.DstRGB); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.T, Dest.PBR, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedHardLight(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.SrcRGB, OpBD.ConstantRGB); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.SrcRGB, OpBD.Temp1, OpAC.SrcRGB, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.ConstantOne, OpBD.SrcRGB); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedSoftLight(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(4, 0.25f, 0.25f, 0.25f); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantRGB); + asm.SetConstant(0, 0.2605f, 0.2605f, 0.2605f); + asm.Mul(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(1, -0.7817f, -0.7817f, -0.7817f); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(2, 0.3022f, 0.3022f, 0.3022f); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(3, 0.2192f, 0.2192f, 0.2192f); + asm.Add(CC.GT, Dest.Temp0, OpBD.PBR, OpBD.ConstantRGB); + asm.SetConstant(5, 16f, 16f, 16f); + asm.Mul(CC.LE, Dest.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(6, 12f, 12f, 12f); + asm.Mmsub(CC.LE, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(7, 3f, 3f, 3f); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Mmsub(CC.LE, Dest.Temp0, OpAC.Temp1, OpBD.ConstantOne, OpAC.Temp1, OpBD.Temp1); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedDifference(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.SrcRGB); + asm.Sub(CC.LT, Dest.Temp0, OpBD.SrcRGB, OpBD.Temp1); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedMinus(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Sub(CC.T, Dest.Temp0, OpBD.DstRGB, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.ReverseSubtractGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedMinusClamped(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Sub(CC.T, Dest.PBR, OpBD.DstRGB, OpBD.PBR); + asm.Max(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Max(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantZero); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenUncorrelatedExclusion(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mmadd(CC.T, Dest.PBR, OpAC.PBR, OpBD.DstAAA, OpAC.DstRGB, OpBD.SrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.Temp2, OpBD.DstRGB); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.Temp2, OpBD.DstRGB); + asm.Mmadd(CC.T, Dest.PBR, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedContrast(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.SetConstant(0, 2f, 2f, 2f); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.DstRGB, OpBD.ConstantRGB, OpAC.DstAAA, OpBD.ConstantOne); + asm.Mmsub(CC.T, Dest.PBR, OpAC.Temp2, OpBD.ConstantRGB, OpAC.SrcAAA, OpBD.ConstantOne); + asm.Mul(CC.T, Dest.PBR, OpAC.Temp0, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.PBR, OpBD.DstAAA); + asm.SetConstant(1, 0.5f, 0.5f, 0.5f); + asm.Mul(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantRGB); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedInvertRGB(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.DstAAA, OpAC.PBR, OpBD.DstRGB); + asm.Madd(CC.T, Dest.Temp0, OpAC.DstRGB, OpBD.OneMinusSrcAAA, OpAC.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedLinearDodge(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mmadd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.DstAAA, OpAC.DstRGB, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR); + asm.Mmadd(CC.T, Dest.PBR, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedLinearBurn(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mmadd(CC.T, Dest.PBR, OpAC.PBR, OpBD.DstAAA, OpAC.DstRGB, OpBD.SrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.SrcAAA, OpBD.DstAAA); + asm.Max(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantZero); + asm.Mmadd(CC.T, Dest.PBR, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.Temp0, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedVividLight(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcRGB, OpBD.ConstantRGB); + asm.Sub(CC.GE, Dest.PBR, OpBD.ConstantOne, OpBD.SrcRGB); + asm.Add(CC.GE, Dest.PBR, OpBD.PBR, OpBD.PBR); + asm.Rcp(CC.GE, Dest.PBR, OpAC.PBR); + asm.Mul(CC.GE, Dest.PBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GE, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Add(CC.LT, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Rcp(CC.LT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.LT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne); + asm.Sub(CC.LT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcRGB, OpBD.ConstantZero); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcRGB, OpBD.ConstantOne); + asm.Mov(CC.GE, Dest.Temp0, OpBD.ConstantOne); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedLinearLight(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 2f, 2f, 2f); + asm.Madd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Min(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedPinLight(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Sub(CC.T, Dest.Temp0, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.ConstantZero); + asm.Add(CC.LE, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Min(CC.LE, Dest.Temp0, OpAC.PBR, OpBD.Temp1); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedHardMix(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Mul(CC.LT, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Mov(CC.GE, Dest.Temp0, OpBD.ConstantOne); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.Temp2, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedRed(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mov(CC.T, Dest.Temp0, OpBD.DstRGB); + asm.Mov(CC.T, Dest.Temp0.R, OpBD.Temp2); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedGreen(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mov(CC.T, Dest.Temp0, OpBD.DstRGB); + asm.Mov(CC.T, Dest.Temp0.G, OpBD.Temp2); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedBlue(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.Temp2, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mov(CC.T, Dest.Temp0, OpBD.DstRGB); + asm.Mov(CC.T, Dest.Temp0.B, OpBD.Temp2); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenUncorrelatedHslHue(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Min(CC.T, Dest.Temp0.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp0); + asm.Rcp(CC.GT, Dest.Temp0, OpAC.Temp0); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.SrcRGB, OpAC.Temp0, OpBD.PBR); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.Temp2.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.Temp2); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp0, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.PBR, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedHslSaturation(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.PBR); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.T, Dest.Temp0.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp0); + asm.Rcp(CC.GT, Dest.Temp0, OpAC.Temp0); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.Temp1, OpAC.Temp0, OpBD.PBR); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Min(CC.GT, Dest.Temp1.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.Temp1); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp0, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.PBR, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedHslColor(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.SrcRGB, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.SrcRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.PBR, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenUncorrelatedHslLuminosity(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.SrcRGB, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp2.BBB, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp1, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Add(CC.T, Dest.Temp1, OpBD.Temp1, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp2); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp1, OpBD.Temp2); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp2); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp1, OpBD.Temp2, OpAC.Temp2, OpBD.Temp2); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp2); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Mmadd(CC.T, Dest.Temp1, OpAC.PBR, OpBD.OneMinusDstAAA, OpAC.DstRGB, OpBD.OneMinusSrcAAA); + asm.Mul(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.DstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl); + } + + private static FixedFunctionAlpha GenDisjointSrc(ref UcodeAssembler asm) + { + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl); + } + + private static FixedFunctionAlpha GenDisjointSrcOver(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.SrcRGB); + asm.Madd(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointDstOver(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp1); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointSrcIn(ref UcodeAssembler asm) + { + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Sub(CC.T, Dest.Temp1.RToA, OpBD.DstAAA, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointSrcOut(ref UcodeAssembler asm) + { + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointSrcAtop(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenDisjointDstAtop(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.DstAAA, OpAC.Temp1, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl); + } + + private static FixedFunctionAlpha GenDisjointXor(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + asm.Min(CC.T, Dest.Temp1, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Add(CC.T, Dest.Temp1.RToA, OpBD.Temp1, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointPlus(ref UcodeAssembler asm) + { + asm.Mul(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.SrcAAA); + asm.Add(CC.T, Dest.Temp0, OpBD.DstRGB, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenDisjointMultiply(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mul(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointScreen(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.PBR); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.SrcRGB, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointOverlay(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.ConstantRGB); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.SrcRGB, OpBD.Temp1, OpAC.SrcRGB, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.ConstantOne, OpBD.SrcRGB); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointDarken(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointLighten(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Max(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointColorDodge(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.ConstantOne, OpBD.SrcRGB); + asm.Rcp(CC.GT, Dest.PBR, OpAC.Temp0); + asm.Mul(CC.GT, Dest.PBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.LE, Dest.Temp0, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.Temp1, OpBD.ConstantZero); + asm.Mov(CC.LE, Dest.Temp0, OpBD.ConstantZero); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointColorBurn(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.SrcRGB, OpBD.ConstantZero); + asm.Rcp(CC.GT, Dest.PBR, OpAC.SrcRGB); + asm.Mmsub(CC.GT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Max(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.ConstantOne, OpBD.Temp1); + asm.Mov(CC.LE, Dest.Temp0, OpBD.ConstantOne); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointHardLight(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.SrcRGB, OpBD.ConstantRGB); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.SrcRGB, OpBD.Temp1, OpAC.SrcRGB, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.ConstantOne, OpBD.SrcRGB); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointSoftLight(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(4, 0.25f, 0.25f, 0.25f); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantRGB); + asm.SetConstant(0, 0.2605f, 0.2605f, 0.2605f); + asm.Mul(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(1, -0.7817f, -0.7817f, -0.7817f); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(2, 0.3022f, 0.3022f, 0.3022f); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(3, 0.2192f, 0.2192f, 0.2192f); + asm.Add(CC.GT, Dest.Temp0, OpBD.PBR, OpBD.ConstantRGB); + asm.SetConstant(5, 16f, 16f, 16f); + asm.Mul(CC.LE, Dest.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(6, 12f, 12f, 12f); + asm.Mmsub(CC.LE, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(7, 3f, 3f, 3f); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Mmsub(CC.LE, Dest.Temp0, OpAC.Temp1, OpBD.ConstantOne, OpAC.Temp1, OpBD.Temp1); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointDifference(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.SrcRGB); + asm.Sub(CC.LT, Dest.Temp0, OpBD.SrcRGB, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointExclusion(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.PBR); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.SrcRGB, OpBD.Temp1); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.SrcRGB, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointInvertRGB(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.ConstantOne, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.Temp0, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenDisjointLinearDodge(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointLinearBurn(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Max(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantZero); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointVividLight(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcRGB, OpBD.ConstantRGB); + asm.Sub(CC.GE, Dest.PBR, OpBD.ConstantOne, OpBD.SrcRGB); + asm.Add(CC.GE, Dest.PBR, OpBD.PBR, OpBD.PBR); + asm.Rcp(CC.GE, Dest.PBR, OpAC.PBR); + asm.Mul(CC.GE, Dest.PBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GE, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Add(CC.LT, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Rcp(CC.LT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.LT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne); + asm.Sub(CC.LT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcRGB, OpBD.ConstantZero); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcRGB, OpBD.ConstantOne); + asm.Mov(CC.GE, Dest.Temp0, OpBD.ConstantOne); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointLinearLight(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 2f, 2f, 2f); + asm.Madd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Min(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointPinLight(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Sub(CC.T, Dest.Temp0, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.ConstantZero); + asm.Add(CC.LE, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Min(CC.LE, Dest.Temp0, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointHardMix(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Mul(CC.LT, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Mov(CC.GE, Dest.Temp0, OpBD.ConstantOne); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointHslHue(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Min(CC.T, Dest.Temp0.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp0); + asm.Rcp(CC.GT, Dest.Temp0, OpAC.Temp0); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.SrcRGB, OpAC.Temp0, OpBD.PBR); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.Temp2.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.Temp2); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp0, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointHslSaturation(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.PBR); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.T, Dest.Temp0.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp0); + asm.Rcp(CC.GT, Dest.Temp0, OpAC.Temp0); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.Temp1, OpAC.Temp0, OpBD.PBR); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Min(CC.GT, Dest.Temp1.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.Temp1); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp0, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointHslColor(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.SrcRGB, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.SrcRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenDisjointHslLuminosity(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.SrcRGB, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp2.BBB, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp1, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Add(CC.T, Dest.Temp1, OpBD.Temp1, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp2); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp1, OpBD.Temp2); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp2); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp1, OpBD.Temp2, OpAC.Temp2, OpBD.Temp2); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp2); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.OneMinusSrcAAA); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.Temp1, OpAC.PBR, OpBD.Temp0); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.SrcAAA, OpBD.OneMinusDstAAA); + asm.Madd(CC.T, Dest.Temp0, OpAC.PBR, OpBD.SrcRGB, OpAC.Temp0); + asm.Add(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Min(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenConjointSrc(ref UcodeAssembler asm) + { + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Madd(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl); + } + + private static FixedFunctionAlpha GenConjointSrcOver(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.SrcRGB); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.SrcRGB, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointDstOver(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp1, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointSrcIn(ref UcodeAssembler asm) + { + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MinimumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointSrcOut(ref UcodeAssembler asm) + { + asm.Sub(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Mul(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Max(CC.T, Dest.Temp1.RToA, OpAC.PBR, OpBD.ConstantZero); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenConjointSrcAtop(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointDstAtop(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Madd(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl); + } + + private static FixedFunctionAlpha GenConjointXor(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.SrcAAA, OpBD.DstAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Mul(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + asm.Sub(CC.T, Dest.Temp1.CC, OpBD.DstAAA, OpBD.SrcAAA); + asm.Sub(CC.LT, Dest.Temp1, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mov(CC.T, Dest.Temp1.RToA, OpBD.Temp1); + asm.Mov(CC.T, Dest.Temp0, OpBD.Temp0); + return FixedFunctionAlpha.Disabled; + } + + private static FixedFunctionAlpha GenConjointMultiply(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mul(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointScreen(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.PBR); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.SrcRGB, OpBD.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointOverlay(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.ConstantRGB); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.SrcRGB, OpBD.Temp1, OpAC.SrcRGB, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.ConstantOne, OpBD.SrcRGB); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointDarken(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Min(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointLighten(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Max(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointColorDodge(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.ConstantOne, OpBD.SrcRGB); + asm.Rcp(CC.GT, Dest.PBR, OpAC.Temp0); + asm.Mul(CC.GT, Dest.PBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Mov(CC.LE, Dest.Temp0, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.Temp1, OpBD.ConstantZero); + asm.Mov(CC.LE, Dest.Temp0, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointColorBurn(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.SrcRGB, OpBD.ConstantZero); + asm.Rcp(CC.GT, Dest.PBR, OpAC.SrcRGB); + asm.Mmsub(CC.GT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Max(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.ConstantOne, OpBD.Temp1); + asm.Mov(CC.LE, Dest.Temp0, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointHardLight(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.SrcRGB, OpBD.ConstantRGB); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.SrcRGB, OpBD.Temp1, OpAC.SrcRGB, OpBD.Temp1); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.ConstantOne, OpBD.SrcRGB); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.GT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointSoftLight(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(4, 0.25f, 0.25f, 0.25f); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantRGB); + asm.SetConstant(0, 0.2605f, 0.2605f, 0.2605f); + asm.Mul(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(1, -0.7817f, -0.7817f, -0.7817f); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(2, 0.3022f, 0.3022f, 0.3022f); + asm.Mmadd(CC.GT, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(3, 0.2192f, 0.2192f, 0.2192f); + asm.Add(CC.GT, Dest.Temp0, OpBD.PBR, OpBD.ConstantRGB); + asm.SetConstant(5, 16f, 16f, 16f); + asm.Mul(CC.LE, Dest.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(6, 12f, 12f, 12f); + asm.Mmsub(CC.LE, Dest.PBR, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.SetConstant(7, 3f, 3f, 3f); + asm.Mmadd(CC.LE, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp1, OpBD.ConstantRGB); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Mmsub(CC.LE, Dest.Temp0, OpAC.Temp1, OpBD.ConstantOne, OpAC.Temp1, OpBD.Temp1); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointDifference(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.SrcRGB); + asm.Sub(CC.LT, Dest.Temp0, OpBD.SrcRGB, OpBD.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointExclusion(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.PBR); + asm.Mmsub(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.SrcRGB, OpBD.Temp1); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.SrcRGB, OpBD.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointInvertRGB(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mmsub(CC.T, Dest.Temp0, OpAC.SrcRGB, OpBD.ConstantOne, OpAC.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.PBR, OpAC.DstAAA, OpBD.SrcAAA); + asm.Mul(CC.T, Dest.Temp0, OpAC.Temp0, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Madd(CC.T, Dest.Temp0, OpAC.Temp1, OpBD.PBR, OpAC.Temp0); + return new FixedFunctionAlpha(BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointLinearDodge(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.PBR); + asm.Min(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointLinearBurn(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Max(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointVividLight(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.5f, 0.5f, 0.5f); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcRGB, OpBD.ConstantRGB); + asm.Sub(CC.GE, Dest.PBR, OpBD.ConstantOne, OpBD.SrcRGB); + asm.Add(CC.GE, Dest.PBR, OpBD.PBR, OpBD.PBR); + asm.Rcp(CC.GE, Dest.PBR, OpAC.PBR); + asm.Mul(CC.GE, Dest.PBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GE, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Add(CC.LT, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Rcp(CC.LT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.LT, Dest.PBR, OpAC.PBR, OpBD.ConstantOne); + asm.Sub(CC.LT, Dest.Temp0, OpBD.ConstantOne, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcRGB, OpBD.ConstantZero); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcRGB, OpBD.ConstantOne); + asm.Mov(CC.GE, Dest.Temp0, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointLinearLight(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 2f, 2f, 2f); + asm.Madd(CC.T, Dest.PBR, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Max(CC.T, Dest.PBR, OpAC.PBR, OpBD.ConstantZero); + asm.Min(CC.T, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointPinLight(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Sub(CC.T, Dest.Temp0, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.ConstantZero); + asm.Add(CC.LE, Dest.PBR, OpBD.SrcRGB, OpBD.SrcRGB); + asm.Min(CC.LE, Dest.Temp0, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointHardMix(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Add(CC.T, Dest.PBR, OpBD.SrcRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Mul(CC.LT, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Mov(CC.GE, Dest.Temp0, OpBD.ConstantOne); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointHslHue(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Min(CC.T, Dest.Temp0.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp0); + asm.Rcp(CC.GT, Dest.Temp0, OpAC.Temp0); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.SrcRGB, OpAC.Temp0, OpBD.PBR); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.Temp2.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.Temp2); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp0, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointHslSaturation(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.PBR); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.T, Dest.Temp0.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.T, Dest.Temp0.CC, OpBD.PBR, OpBD.Temp0); + asm.Rcp(CC.GT, Dest.Temp0, OpAC.Temp0); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.Temp1, OpAC.Temp0, OpBD.PBR); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Min(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Min(CC.GT, Dest.Temp1.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Mov(CC.GT, Dest.PBR.GBR, OpBD.SrcRGB); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Max(CC.GT, Dest.PBR.GBR, OpAC.PBR, OpBD.SrcRGB); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp0, OpBD.Temp1); + asm.Mul(CC.LE, Dest.Temp0, OpAC.SrcAAA, OpBD.ConstantZero); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp0, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp0, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointHslColor(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.PBR, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp1.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.SrcRGB, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Add(CC.T, Dest.Temp2, OpBD.SrcRGB, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp1); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp2, OpBD.Temp1); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp1); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp2); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp1, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp2, OpBD.Temp1, OpAC.Temp1, OpBD.Temp1); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp1); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + + private static FixedFunctionAlpha GenConjointHslLuminosity(ref UcodeAssembler asm) + { + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.SetConstant(0, 0.3f, 0.59f, 0.11f); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.SrcRGB, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.Temp2.BBB, OpAC.SrcRGB, OpBD.ConstantRGB, OpAC.PBR); + asm.Mul(CC.T, Dest.PBR.RRR, OpAC.Temp1, OpBD.ConstantRGB); + asm.Madd(CC.T, Dest.PBR.GGG, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Madd(CC.T, Dest.PBR.BBB, OpAC.Temp1, OpBD.ConstantRGB, OpAC.PBR); + asm.Sub(CC.T, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Add(CC.T, Dest.Temp1, OpBD.Temp1, OpBD.PBR); + asm.Mov(CC.T, Dest.Temp0, OpBD.PBR); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Max(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.PBR, OpBD.ConstantOne); + asm.Add(CC.GT, Dest.PBR, OpBD.PBR, OpBD.ConstantOne); + asm.Sub(CC.GT, Dest.PBR, OpBD.PBR, OpBD.Temp2); + asm.Rcp(CC.GT, Dest.PBR, OpAC.PBR); + asm.Mmsub(CC.GT, Dest.Temp0, OpAC.PBR, OpBD.ConstantOne, OpAC.PBR, OpBD.Temp2); + asm.Sub(CC.GT, Dest.PBR, OpBD.Temp1, OpBD.Temp2); + asm.Madd(CC.GT, Dest.Temp0, OpAC.Temp0, OpBD.PBR, OpAC.Temp2); + asm.Mov(CC.T, Dest.PBR.GBR, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR.GBR, OpAC.PBR, OpBD.Temp1); + asm.Min(CC.T, Dest.PBR.GBR.CC, OpAC.PBR, OpBD.Temp1); + asm.Sub(CC.LT, Dest.PBR, OpBD.Temp2, OpBD.PBR); + asm.Rcp(CC.LT, Dest.Temp0, OpAC.PBR); + asm.Mmsub(CC.LT, Dest.PBR, OpAC.Temp1, OpBD.Temp2, OpAC.Temp2, OpBD.Temp2); + asm.Madd(CC.LT, Dest.Temp0, OpAC.PBR, OpBD.Temp0, OpAC.Temp2); + asm.Rcp(CC.T, Dest.PBR, OpAC.DstAAA); + asm.Mul(CC.T, Dest.Temp1, OpAC.DstRGB, OpBD.PBR); + asm.Sub(CC.T, Dest.PBR.CC, OpBD.SrcAAA, OpBD.DstAAA); + asm.Mmadd(CC.GE, Dest.Temp0, OpAC.Temp0, OpBD.DstAAA, OpAC.SrcRGB, OpBD.PBR); + asm.Sub(CC.LT, Dest.PBR, OpBD.DstAAA, OpBD.SrcAAA); + asm.Mmadd(CC.LT, Dest.Temp0, OpAC.Temp0, OpBD.SrcAAA, OpAC.Temp1, OpBD.PBR); + return new FixedFunctionAlpha(BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendManager.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendManager.cs new file mode 100644 index 00000000..b336382d --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendManager.cs @@ -0,0 +1,115 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender +{ + /// + /// Advanced blend manager. + /// + class AdvancedBlendManager + { + private const int InstructionRamSize = 128; + private const int InstructionRamSizeMask = InstructionRamSize - 1; + + private readonly DeviceStateWithShadow _state; + + private readonly uint[] _code; + private int _ip; + + /// + /// Creates a new instance of the advanced blend manager. + /// + /// GPU state of the channel owning this manager + public AdvancedBlendManager(DeviceStateWithShadow state) + { + _state = state; + _code = new uint[InstructionRamSize]; + } + + /// + /// Sets the start offset of the blend microcode in memory. + /// + /// Method call argument + public void LoadBlendUcodeStart(int argument) + { + _ip = argument; + } + + /// + /// Pushes one word of blend microcode. + /// + /// Method call argument + public void LoadBlendUcodeInstruction(int argument) + { + _code[_ip++ & InstructionRamSizeMask] = (uint)argument; + } + + /// + /// Tries to identify the current advanced blend function being used, + /// given the current state and microcode that was uploaded. + /// + /// Advanced blend descriptor + /// True if the function was found, false otherwise + public bool TryGetAdvancedBlend(out AdvancedBlendDescriptor descriptor) + { + Span currentCode = new(_code); + byte codeLength = (byte)_state.State.BlendUcodeSize; + + if (currentCode.Length > codeLength) + { + currentCode = currentCode[..codeLength]; + } + + Hash128 hash = XXHash128.ComputeHash(MemoryMarshal.Cast(currentCode)); + + descriptor = default; + + if (!AdvancedBlendPreGenTable.Entries.TryGetValue(hash, out var entry)) + { + return false; + } + + if (entry.Constants != null) + { + bool constantsMatch = true; + + for (int i = 0; i < entry.Constants.Length; i++) + { + RgbFloat constant = entry.Constants[i]; + RgbHalf constant2 = _state.State.BlendUcodeConstants[i]; + + if ((Half)constant.R != constant2.UnpackR() || + (Half)constant.G != constant2.UnpackG() || + (Half)constant.B != constant2.UnpackB()) + { + constantsMatch = false; + break; + } + } + + if (!constantsMatch) + { + return false; + } + } + + if (entry.Alpha.Enable != _state.State.BlendUcodeEnable) + { + return false; + } + + if (entry.Alpha.Enable == BlendUcodeEnable.EnableRGBA && + (entry.Alpha.AlphaOp != _state.State.BlendStateCommon.AlphaOp || + entry.Alpha.AlphaSrcFactor != _state.State.BlendStateCommon.AlphaSrcFactor || + entry.Alpha.AlphaDstFactor != _state.State.BlendStateCommon.AlphaDstFactor)) + { + return false; + } + + descriptor = new AdvancedBlendDescriptor(entry.Op, entry.Overlap, entry.SrcPreMultiplied); + return true; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendPreGenTable.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendPreGenTable.cs new file mode 100644 index 00000000..b6cd9def --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendPreGenTable.cs @@ -0,0 +1,273 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender +{ + /// + /// Advanced blend function entry. + /// + readonly struct AdvancedBlendEntry + { + /// + /// Advanced blend operation. + /// + public AdvancedBlendOp Op { get; } + + /// + /// Advanced blend overlap mode. + /// + public AdvancedBlendOverlap Overlap { get; } + + /// + /// Whenever the source input is pre-multiplied. + /// + public bool SrcPreMultiplied { get; } + + /// + /// Constants used by the microcode. + /// + public RgbFloat[] Constants { get; } + + /// + /// Fixed function alpha state. + /// + public FixedFunctionAlpha Alpha { get; } + + /// + /// Creates a new advanced blend function entry. + /// + /// Advanced blend operation + /// Advanced blend overlap mode + /// Whenever the source input is pre-multiplied + /// Constants used by the microcode + /// Fixed function alpha state + public AdvancedBlendEntry( + AdvancedBlendOp op, + AdvancedBlendOverlap overlap, + bool srcPreMultiplied, + RgbFloat[] constants, + FixedFunctionAlpha alpha) + { + Op = op; + Overlap = overlap; + SrcPreMultiplied = srcPreMultiplied; + Constants = constants; + Alpha = alpha; + } + } + + /// + /// Pre-generated hash table with advanced blend functions used by the driver. + /// + static class AdvancedBlendPreGenTable + { + /// + /// Advanced blend functions dictionary. + /// + public static readonly IReadOnlyDictionary Entries = new Dictionary() + { + { new Hash128(0x19ECF57B83DE31F7, 0x5BAE759246F264C0), new AdvancedBlendEntry(AdvancedBlendOp.PlusClamped, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xDE1B14A356A1A9ED, 0x59D803593C607C1D), new AdvancedBlendEntry(AdvancedBlendOp.PlusClampedAlpha, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x1A3C3A6D32DEC368, 0xBCAE519EC6AAA045), new AdvancedBlendEntry(AdvancedBlendOp.PlusDarker, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x6FD380261A63B240, 0x17C3B335DBB9E3DB), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x1D39164823D3A2D1, 0xC45350959CE1C8FB), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x18DF09FF53B129FE, 0xC02EDA33C36019F6), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x5973E583271EBF06, 0x711497D75D1272E0), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x4759E0E5DA54D5E8, 0x1FDD57C0C38AFA1F), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x337684D43CCE97FA, 0x0139E30CC529E1C9), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0xDA59E85D8428992D, 0x1D3D7C64C9EF0132), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x9455B949298CE805, 0xE73D3301518BE98A), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0xBDD3B4DEDBE336AA, 0xBFA4DCD50D535DEE), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x22D4E970A028649A, 0x4F3FCB055FCED965), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0xA346A91311D72114, 0x151A27A3FB0A1904), new AdvancedBlendEntry(AdvancedBlendOp.Minus, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.ReverseSubtractGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x8A307241061FACD6, 0xA39D1826440B8EE7), new AdvancedBlendEntry(AdvancedBlendOp.MinusClamped, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xB3BE569485EFFFE0, 0x0BA4E269B3CFB165), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x36FCA3277DC11822, 0x2BC0F6CAC2029672), new AdvancedBlendEntry(AdvancedBlendOp.Contrast, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(2f, 2f, 2f), new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x4A6226AF2DE9BD7F, 0xEB890D7DA716F73A), new AdvancedBlendEntry(AdvancedBlendOp.Invert, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0xF364CAA94E160FEB, 0xBF364512C72A3797), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x6BF791AB4AC19C87, 0x6FA17A994EA0FCDE), new AdvancedBlendEntry(AdvancedBlendOp.InvertOvg, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x053C75A0AE0BB222, 0x03C791FEEB59754C), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x25762AB40B6CBDE9, 0x595E9A968AC4F01C), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0xC2D05E2DBE16955D, 0xB8659C7A3FCFA7CE), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x223F220B8F74CBFB, 0xD3DD19D7C39209A5), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0xD0DAE57A9F1FE78A, 0x353796BCFB8CE30B), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x601C8CBEC07FF8FF, 0xB8E22882360E8695), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x3A55B7B78C76A7A8, 0x206F503B2D9FFEAA), new AdvancedBlendEntry(AdvancedBlendOp.Red, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x80BC65C7831388E5, 0xC652457B2C766AEC), new AdvancedBlendEntry(AdvancedBlendOp.Green, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x3D3A912E5833EE13, 0x307895951349EE33), new AdvancedBlendEntry(AdvancedBlendOp.Blue, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x289105BE92E81803, 0xFD8F1F03D15C53B4), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x007AE3BD140764EB, 0x0EE05A0D2E80BBAE), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x77F7EE0DB3FDDB96, 0xDEA47C881306DB3E), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x66F4E9A7D73CA157, 0x1486058A177DB11C), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x593E9F331612D618, 0x9D217BEFA4EB919A), new AdvancedBlendEntry(AdvancedBlendOp.Src, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) }, + { new Hash128(0x0A5194C5E6891106, 0xDD8EC6586106557C), new AdvancedBlendEntry(AdvancedBlendOp.Dst, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x8D77173D5E06E916, 0x06AB190E7D10F4D4), new AdvancedBlendEntry(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x655B4EBC148981DA, 0x455999EF2B9BD28A), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x98F5437D5F518929, 0xBFF4A6E83183DB63), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x6ADDEFE3B9CEF2FD, 0xB6F6272AFECB1AAB), new AdvancedBlendEntry(AdvancedBlendOp.DstIn, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x80953F0953BF05B1, 0xD59ABFAA34F8196F), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xA401D9AA2A39C121, 0xFC0C8005C22AD7E3), new AdvancedBlendEntry(AdvancedBlendOp.DstOut, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x06274FB7CA9CDD22, 0x6CE8188B1A9AB6EF), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x0B079BE7F7F70817, 0xB72E7736CA51E321), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) }, + { new Hash128(0x66215C99403CEDDE, 0x900B733D62204C48), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x12DEF2AD900CAD6C, 0x58CF5CC3004910DF), new AdvancedBlendEntry(AdvancedBlendOp.Plus, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x272BA3A49F64DAE4, 0xAC70B96C00A99EAF), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x206C34AAA7D3F545, 0xDA4B30CACAA483A0), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x3D93494920D257BE, 0xDCC573BE1F5F4449), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x0D7417D80191107B, 0xEAF40547827E005F), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xEC1B03E8C883F9C9, 0x2D3CA044C58C01B4), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x58A19A0135D68B31, 0x82F35B97AED068E5), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x20489F9AB36CC0E3, 0x20499874219E35EE), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xBB176935E5EE05BF, 0x95B26D4D30EA7A14), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x5FF9393C908ACFED, 0x068B0BD875773ABF), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x03181F8711C9802C, 0x6B02C7C6B224FE7B), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x2EE2209021F6B977, 0xF3AFA1491B8B89FC), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xD8BA4DD2EDE4DC9E, 0x01006114977CF715), new AdvancedBlendEntry(AdvancedBlendOp.Invert, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0xD156B99835A2D8ED, 0x2D0BEE9E135EA7A7), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x20CE8C898ED4BE27, 0x1514900B6F5E8F66), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xCDE5F743820BA2D9, 0x917845FE2ECB083D), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xEB03DF4A0C1D14CD, 0xBAE2E831C6E8FFE4), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x1DC9E49AABC779AC, 0x4053A1441EB713D3), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xFBDEF776248F7B3E, 0xE05EEFD65AC47CB7), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x415A1A48E03AA6E7, 0x046D7EE33CA46B9A), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Disjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x59A6901EC9BB2041, 0x2F3E19CE5EEC3EBE), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x044B2B6E105221DA, 0x3089BBC033F994AF), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x374A5A24AA8E6CC5, 0x29930FAA6215FA2B), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x30CD0F7AF0CF26F9, 0x06CCA6744DE7DCF5), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x1A6C9A1F6FE494A5, 0xA0CFAF77617E54DD), new AdvancedBlendEntry(AdvancedBlendOp.Src, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) }, + { new Hash128(0x081AF6DAAB1C8717, 0xBFEDCE59AE3DC9AC), new AdvancedBlendEntry(AdvancedBlendOp.Dst, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x3518E44573AB68BA, 0xC96EE71AF9F8F546), new AdvancedBlendEntry(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0xF89E81FE8D73C96F, 0x4583A04577A0F21C), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0xDF4026421CB61119, 0x14115A1F5139AFC7), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MinimumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x91A20262C3E3A695, 0x0B3A102BFCDC6B1C), new AdvancedBlendEntry(AdvancedBlendOp.DstIn, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MinimumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x44F4C7CCFEB9EBFA, 0xF68394E6D56E5C2F), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xB89F17C7021E9760, 0x430357EE0F7188EF), new AdvancedBlendEntry(AdvancedBlendOp.DstOut, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xDA2D20EA4242B8A0, 0x0D1EC05B72E3838F), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x855DFEE1208D11B9, 0x77C6E3DDCFE30B85), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) }, + { new Hash128(0x9B3808439683FD58, 0x123DCBE4705AB25E), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xA42CF045C248A00A, 0x0C6C63C24EA0B0C1), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x320A83B6D00C8059, 0x796EDAB3EB7314BC), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x45253AC9ABFFC613, 0x8F92EA70195FB573), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x1A5D263B588274B6, 0x167D305F6C794179), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x709C1A837FE966AC, 0x75D8CE49E8A78EDB), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x8265C26F85E4145F, 0x932E6CCBF37CB600), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x3F252B3FEF983F27, 0x9370D7EEFEFA1A9E), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x66A334A4AEA41078, 0xCB52254E1E395231), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0xFDD05C53B25F0035, 0xB7E3ECEE166C222F), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x25D932A77FFED81A, 0xA50D797B0FCA94E8), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x4A953B6F5F7D341C, 0xDC05CFB50DDB5DC1), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x838CB660C4F41F6D, 0x9E7D958697543495), new AdvancedBlendEntry(AdvancedBlendOp.Invert, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x4DF6EC1348A8F797, 0xA128E0CD69DB5A64), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x178CDFAB9A015295, 0x2BF40EA72E596D57), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x338FC99050E56AFD, 0x2AF41CF82BE602BF), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x62E02ED60D1E978E, 0xBF726B3E68C11E4D), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0xFBAF92DD4C101502, 0x7AF2EDA6596B819D), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x0EF1241F65D4B50A, 0xE8D85DFA6AEDDB84), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x77FE024B5C9D4A18, 0xF19D48A932F6860F), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Conjoint, true, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x9C88CBFA2E09D857, 0x0A0361704CBEEE1D), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x5B94127FA190E640, 0x8D1FEFF837A91268), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0xB9C9105B7E063DDB, 0xF6A70E1D511B96FD), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0xF0751AAE332B3ED1, 0xC40146F5C83C2533), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x579EB12F595F75AD, 0x151BF0504703B81B), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0xF9CA152C03AC8C62, 0x1581336205E5CF47), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.DstAlphaGl, BlendFactor.ZeroGl)) }, + { new Hash128(0x98ACD8BB5E195D0F, 0x91F937672BE899F0), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneMinusDstAlphaGl, BlendFactor.ZeroGl)) }, + { new Hash128(0xBF97F10FC301F44C, 0x75721789F0D48548), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x1B982263B8B08A10, 0x3350C76E2E1B27DF), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) }, + { new Hash128(0xFF20AC79F64EDED8, 0xAF9025B2D97B9273), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneMinusDstAlphaGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x9FFD986600FB112F, 0x384FDDF4E060139A), new AdvancedBlendEntry(AdvancedBlendOp.PlusClamped, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x0425E40B5B8B3B52, 0x5880CBED7CAB631C), new AdvancedBlendEntry(AdvancedBlendOp.PlusClampedAlpha, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x16DAC8593F28623A, 0x233DBC82325B8AED), new AdvancedBlendEntry(AdvancedBlendOp.PlusDarker, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xB37E5F234B9F0948, 0xD5F957A2ECD98FD6), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0xCA0FDADD1D20DBE3, 0x1A5C15CCBF1AC538), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x1C48304D73A9DF3A, 0x891DB93FA36E3450), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x53200F2279B7FA39, 0x051C2462EBF6789C), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0xB88BFB80714DCD5C, 0xEBD6938D744E6A41), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0xE33DC2A25FC1A976, 0x08B3DBB1F3027D45), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0xCE97E71615370316, 0xE131AE49D3A4D62B), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0xE059FD265149B256, 0x94AF817AC348F61F), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x16D31333D477E231, 0x9A98AAC84F72CC62), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x47FC3B0776366D3C, 0xE96D9BD83B277874), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x7230401E3FEA1F3B, 0xF0D15F05D3D1E309), new AdvancedBlendEntry(AdvancedBlendOp.Minus, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.ReverseSubtractGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x188212F9303742F5, 0x100C51CB96E03591), new AdvancedBlendEntry(AdvancedBlendOp.MinusClamped, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x52B755D296B44DC5, 0x4003B87275625973), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0xD873ED973ADF7EAD, 0x73E68B57D92034E7), new AdvancedBlendEntry(AdvancedBlendOp.Contrast, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(2f, 2f, 2f), new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x471F9FA34B945ACB, 0x10524D1410B3C402), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x99F569454EA0EF32, 0x6FC70A8B3A07DC8B), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x5AD55F950067AC7E, 0x4BA60A4FBABDD0AC), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x03FF2C858C9C4C5B, 0xE95AE7F561FB60E9), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x6DC0E510C7BCF9D2, 0xAE805D7CECDCB5C1), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x44832332CED5C054, 0x2F8D5536C085B30A), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x4AB4D387618AC51F, 0x495B46E0555F4B32), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x99282B49405A01A8, 0xD6FA93F864F24A8E), new AdvancedBlendEntry(AdvancedBlendOp.Red, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x37B30C1064FBD23E, 0x5D068366F42317C2), new AdvancedBlendEntry(AdvancedBlendOp.Green, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x760FAE9D59E04BC2, 0xA40AD483EA01435E), new AdvancedBlendEntry(AdvancedBlendOp.Blue, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0xE786950FD9D1C6EF, 0xF9FDD5AF6451D239), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x052458BB4788B0CA, 0x8AC58FDCA1F45EF5), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x6AFC3837D1D31920, 0xB9D49C2FE49642C6), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0xAFC2911949317E01, 0xD5B63636F5CB3422), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) }, + { new Hash128(0x13B46DF507CC2C53, 0x86DE26517E6BF0A7), new AdvancedBlendEntry(AdvancedBlendOp.Src, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) }, + { new Hash128(0x5C372442474BE410, 0x79ECD3C0C496EF2E), new AdvancedBlendEntry(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x74AAB45DBF5336E9, 0x01BFC4E181DAD442), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x43239E282A36C85C, 0x36FB65560E46AD0F), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x1A3BA8A7583B8F7A, 0xE64E41D548033180), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x32BBB9859E9B565D, 0x3D5CE94FE55F18B5), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0xD947A0766AE3C0FC, 0x391E5D53E86F4ED6), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) }, + { new Hash128(0xBD9A7C08BDFD8CE6, 0x905407634901355E), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x8395475BCB0D7A8C, 0x48AF5DD501D44A70), new AdvancedBlendEntry(AdvancedBlendOp.Plus, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x80AAC23FEBD4A3E5, 0xEA8C70F0B4DE52DE), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x2F3AD1B0F1B3FD09, 0xC0EBC784BFAB8EA3), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x52B54032F2F70BFF, 0xC941D6FDED674765), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xCA7B86F72EC6A99B, 0x55868A131AFE359E), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x377919B60BD133CA, 0x0FD611627664EF40), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x9D4A0C5EE1153887, 0x7B869EBA218C589B), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x311F2A858545D123, 0xB4D09C802480AD62), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xCF78AA6A83AFA689, 0x9DC48B0C2182A3E1), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xC3018CD6F1CF62D1, 0x016E32DD9087B1BB), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x9CB62CE0E956EE29, 0x0FB67F503E60B3AD), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x3589A13C16EF3BFA, 0x15B29BFC91F3BDFB), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x3502CA5FB7529917, 0xFA51BFD0D1688071), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x62ADC25AD6D0A923, 0x76CB6D238276D3A3), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x09FDEB1116A9D52C, 0x85BB8627CD5C2733), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x0709FED1B65E18EB, 0x5BC3AA4D99EC19CF), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xB18D28AE5DE4C723, 0xE820AA2B75C9C02E), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x6743C51621497480, 0x4B164E40858834AE), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x63D1E181E34A2944, 0x1AE292C9D9F12819), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Disjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x079523298250BFF6, 0xC0C793510603CDB5), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x4C9D0A973C805EA6, 0xD1FF59AD5156B93C), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x1E914678F3057BCD, 0xD503AE389C12D229), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0x9FDBADE5556C5311, 0x03F0CBC798FC5C94), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xE39451534635403C, 0x606CC1CA1F452388), new AdvancedBlendEntry(AdvancedBlendOp.Src, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) }, + { new Hash128(0x1D39F0F0A1008AA6, 0xBFDF2B97E6C3F125), new AdvancedBlendEntry(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0xDB81BED30D5BDBEA, 0xAF0B2856EB93AD2C), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x83F69CCF1D0A79B6, 0x70D31332797430AC), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MinimumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x7B87F807AB7A8F5C, 0x1241A2A01FB31771), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xF557172E20D5272D, 0xC1961F8C7A5D2820), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0xA8476B3944DBBC9B, 0x84A2F6AF97B15FDF), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) }, + { new Hash128(0x3259602B55414DA3, 0x72AACCC00B5A9D10), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) }, + { new Hash128(0xC0CB8C10F36EDCD6, 0x8C2D088AD8191E1C), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x81806C451C6255EF, 0x5AA8AC9A08941A15), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0xE55A6537F4568198, 0xCA8735390B799B19), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x5C044BA14536DDA3, 0xBCE0123ED7D510EC), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x6788346C405BE130, 0x372A4BB199C01F9F), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x510EDC2A34E2856B, 0xE1727A407E294254), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x4B7BE01BD398C7A8, 0x5BFF79BC00672C18), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x213B43845540CFEC, 0xDA857411CF1CCFCE), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x765AFA6732E783F1, 0x8F1CABF1BC78A014), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0xA4A5DE1CC06F6CB1, 0xA0634A0011001709), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x81F32BD8816EA796, 0x697EE86683165170), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0xB870C209EAA5F092, 0xAF5FD923909CAA1F), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) }, + { new Hash128(0x3649A9F5C936FB83, 0xDD7C834897AA182A), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0xD72A2B1097A5995C, 0x3D41B2763A913654), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x551E212B9F6C454A, 0xB0DFA05BEB3C37FA), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x681B5A313B7416BF, 0xCB1CBAEEB4D81500), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x9343A18BD4B16777, 0xEDB4AC1C8972C3A4), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0xC960BF6D8519DE28, 0x78D8557FD405D119), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Conjoint, false, Array.Empty(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x65A7B01FDC73A46C, 0x297E096ED5CC4D8A), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0xD9C99BA4A6CDC13B, 0x3CFF0ACEDC2EE150), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x6BC00DA6EB922BD1, 0x5FD4C11F2A685234), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + { new Hash128(0x8652300E32D93050, 0x9460E7B449132371), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) }, + }; + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendUcode.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendUcode.cs new file mode 100644 index 00000000..d3e3abbc --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendUcode.cs @@ -0,0 +1,126 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender +{ + /// + /// Fixed function alpha state used for a advanced blend function. + /// + readonly struct FixedFunctionAlpha + { + /// + /// Fixed function alpha state with alpha blending disabled. + /// + public static FixedFunctionAlpha Disabled => new(BlendUcodeEnable.EnableRGBA, default, default, default); + + /// + /// Individual enable bits for the RGB and alpha components. + /// + public BlendUcodeEnable Enable { get; } + + /// + /// Alpha blend operation. + /// + public BlendOp AlphaOp { get; } + + /// + /// Value multiplied with the blend source operand. + /// + public BlendFactor AlphaSrcFactor { get; } + + /// + /// Value multiplied with the blend destination operand. + /// + public BlendFactor AlphaDstFactor { get; } + + /// + /// Creates a new blend fixed function alpha state. + /// + /// Individual enable bits for the RGB and alpha components + /// Alpha blend operation + /// Value multiplied with the blend source operand + /// Value multiplied with the blend destination operand + public FixedFunctionAlpha(BlendUcodeEnable enable, BlendOp alphaOp, BlendFactor alphaSrc, BlendFactor alphaDst) + { + Enable = enable; + AlphaOp = alphaOp; + AlphaSrcFactor = alphaSrc; + AlphaDstFactor = alphaDst; + } + + /// + /// Creates a new blend fixed function alpha state. + /// + /// Alpha blend operation + /// Value multiplied with the blend source operand + /// Value multiplied with the blend destination operand + public FixedFunctionAlpha(BlendOp alphaOp, BlendFactor alphaSrc, BlendFactor alphaDst) : this(BlendUcodeEnable.EnableRGB, alphaOp, alphaSrc, alphaDst) + { + } + } + + /// + /// Blend microcode assembly function delegate. + /// + /// Assembler + /// Fixed function alpha state for the microcode + delegate FixedFunctionAlpha GenUcodeFunc(ref UcodeAssembler asm); + + /// + /// Advanced blend microcode state. + /// + readonly struct AdvancedBlendUcode + { + /// + /// Advanced blend operation. + /// + public AdvancedBlendOp Op { get; } + + /// + /// Advanced blend overlap mode. + /// + public AdvancedBlendOverlap Overlap { get; } + + /// + /// Whenever the source input is pre-multiplied. + /// + public bool SrcPreMultiplied { get; } + + /// + /// Fixed function alpha state. + /// + public FixedFunctionAlpha Alpha { get; } + + /// + /// Microcode. + /// + public uint[] Code { get; } + + /// + /// Constants used by the microcode. + /// + public RgbFloat[] Constants { get; } + + /// + /// Creates a new advanced blend state. + /// + /// Advanced blend operation + /// Advanced blend overlap mode + /// Whenever the source input is pre-multiplied + /// Function that will generate the advanced blend microcode + public AdvancedBlendUcode( + AdvancedBlendOp op, + AdvancedBlendOverlap overlap, + bool srcPreMultiplied, + GenUcodeFunc genFunc) + { + Op = op; + Overlap = overlap; + SrcPreMultiplied = srcPreMultiplied; + + UcodeAssembler asm = new(); + Alpha = genFunc(ref asm); + Code = asm.GetCode(); + Constants = asm.GetConstants(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/UcodeAssembler.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/UcodeAssembler.cs new file mode 100644 index 00000000..8e020906 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/UcodeAssembler.cs @@ -0,0 +1,305 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender +{ + /// + /// Blend microcode instruction. + /// + enum Instruction + { + Mmadd = 0, + Mmsub = 1, + Min = 2, + Max = 3, + Rcp = 4, + Add = 5, + Sub = 6, + } + + /// + /// Blend microcode condition code. + /// + enum CC + { + F = 0, + T = 1, + EQ = 2, + NE = 3, + LT = 4, + LE = 5, + GT = 6, + GE = 7, + } + + /// + /// Blend microcode opend B or D value. + /// + enum OpBD + { + ConstantZero = 0x0, + ConstantOne = 0x1, + SrcRGB = 0x2, + SrcAAA = 0x3, + OneMinusSrcAAA = 0x4, + DstRGB = 0x5, + DstAAA = 0x6, + OneMinusDstAAA = 0x7, + Temp0 = 0x9, + Temp1 = 0xa, + Temp2 = 0xb, + PBR = 0xc, + ConstantRGB = 0xd, + } + + /// + /// Blend microcode operand A or C value. + /// + enum OpAC + { + SrcRGB = 0, + DstRGB = 1, + SrcAAA = 2, + DstAAA = 3, + Temp0 = 4, + Temp1 = 5, + Temp2 = 6, + PBR = 7, + } + + /// + /// Blend microcode destination operand. + /// + enum OpDst + { + Temp0 = 0, + Temp1 = 1, + Temp2 = 2, + PBR = 3, + } + + /// + /// Blend microcode input swizzle. + /// + enum Swizzle + { + RGB = 0, + GBR = 1, + RRR = 2, + GGG = 3, + BBB = 4, + RToA = 5, + } + + /// + /// Blend microcode output components. + /// + enum WriteMask + { + RGB = 0, + R = 1, + G = 2, + B = 3, + } + + /// + /// Floating-point RGB color values. + /// + readonly struct RgbFloat + { + /// + /// Red component value. + /// + public float R { get; } + + /// + /// Green component value. + /// + public float G { get; } + + /// + /// Blue component value. + /// + public float B { get; } + + /// + /// Creates a new floating-point RGB value. + /// + /// Red component value + /// Green component value + /// Blue component value + public RgbFloat(float r, float g, float b) + { + R = r; + G = g; + B = b; + } + } + + /// + /// Blend microcode destination operand, including swizzle, write mask and condition code update flag. + /// + readonly struct Dest + { + public static Dest Temp0 => new(OpDst.Temp0, Swizzle.RGB, WriteMask.RGB, false); + public static Dest Temp1 => new(OpDst.Temp1, Swizzle.RGB, WriteMask.RGB, false); + public static Dest Temp2 => new(OpDst.Temp2, Swizzle.RGB, WriteMask.RGB, false); + public static Dest PBR => new(OpDst.PBR, Swizzle.RGB, WriteMask.RGB, false); + + public Dest GBR => new(Dst, Swizzle.GBR, WriteMask, WriteCC); + public Dest RRR => new(Dst, Swizzle.RRR, WriteMask, WriteCC); + public Dest GGG => new(Dst, Swizzle.GGG, WriteMask, WriteCC); + public Dest BBB => new(Dst, Swizzle.BBB, WriteMask, WriteCC); + public Dest RToA => new(Dst, Swizzle.RToA, WriteMask, WriteCC); + + public Dest R => new(Dst, Swizzle, WriteMask.R, WriteCC); + public Dest G => new(Dst, Swizzle, WriteMask.G, WriteCC); + public Dest B => new(Dst, Swizzle, WriteMask.B, WriteCC); + + public Dest CC => new(Dst, Swizzle, WriteMask, true); + + public OpDst Dst { get; } + public Swizzle Swizzle { get; } + public WriteMask WriteMask { get; } + public bool WriteCC { get; } + + /// + /// Creates a new blend microcode destination operand. + /// + /// Operand + /// Swizzle + /// Write maks + /// Indicates if condition codes should be updated + public Dest(OpDst dst, Swizzle swizzle, WriteMask writeMask, bool writeCC) + { + Dst = dst; + Swizzle = swizzle; + WriteMask = writeMask; + WriteCC = writeCC; + } + } + + /// + /// Blend microcode operaiton. + /// + readonly struct UcodeOp + { + public readonly uint Word; + + /// + /// Creates a new blend microcode operation. + /// + /// Condition code that controls whenever the operation is executed or not + /// Instruction + /// Index on the constant table of the constant used by any constant operand + /// Destination operand + /// First input operand + /// Second input operand + /// Third input operand + /// Fourth input operand + public UcodeOp(CC cc, Instruction inst, int constIndex, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC, OpBD srcD) + { + Word = (uint)cc | + ((uint)inst << 3) | + ((uint)constIndex << 6) | + ((uint)srcA << 9) | + ((uint)srcB << 12) | + ((uint)srcC << 16) | + ((uint)srcD << 19) | + ((uint)dest.Swizzle << 23) | + ((uint)dest.WriteMask << 26) | + ((uint)dest.Dst << 28) | + (dest.WriteCC ? (1u << 31) : 0); + } + } + + /// + /// Blend microcode assembler. + /// + struct UcodeAssembler + { + private List _code; + private RgbFloat[] _constants; + private int _constantIndex; + + public void Mul(CC cc, Dest dest, OpAC srcA, OpBD srcB) + { + Assemble(cc, Instruction.Mmadd, dest, srcA, srcB, OpAC.SrcRGB, OpBD.ConstantZero); + } + + public void Madd(CC cc, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC) + { + Assemble(cc, Instruction.Mmadd, dest, srcA, srcB, srcC, OpBD.ConstantOne); + } + + public void Mmadd(CC cc, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC, OpBD srcD) + { + Assemble(cc, Instruction.Mmadd, dest, srcA, srcB, srcC, srcD); + } + + public void Mmsub(CC cc, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC, OpBD srcD) + { + Assemble(cc, Instruction.Mmsub, dest, srcA, srcB, srcC, srcD); + } + + public void Min(CC cc, Dest dest, OpAC srcA, OpBD srcB) + { + Assemble(cc, Instruction.Min, dest, srcA, srcB, OpAC.SrcRGB, OpBD.ConstantZero); + } + + public void Max(CC cc, Dest dest, OpAC srcA, OpBD srcB) + { + Assemble(cc, Instruction.Max, dest, srcA, srcB, OpAC.SrcRGB, OpBD.ConstantZero); + } + + public void Rcp(CC cc, Dest dest, OpAC srcA) + { + Assemble(cc, Instruction.Rcp, dest, srcA, OpBD.ConstantZero, OpAC.SrcRGB, OpBD.ConstantZero); + } + + public void Mov(CC cc, Dest dest, OpBD srcB) + { + Assemble(cc, Instruction.Add, dest, OpAC.SrcRGB, srcB, OpAC.SrcRGB, OpBD.ConstantZero); + } + + public void Add(CC cc, Dest dest, OpBD srcB, OpBD srcD) + { + Assemble(cc, Instruction.Add, dest, OpAC.SrcRGB, srcB, OpAC.SrcRGB, srcD); + } + + public void Sub(CC cc, Dest dest, OpBD srcB, OpBD srcD) + { + Assemble(cc, Instruction.Sub, dest, OpAC.SrcRGB, srcB, OpAC.SrcRGB, srcD); + } + + private void Assemble(CC cc, Instruction inst, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC, OpBD srcD) + { + (_code ??= new List()).Add(new UcodeOp(cc, inst, _constantIndex, dest, srcA, srcB, srcC, srcD).Word); + } + + public void SetConstant(int index, float r, float g, float b) + { + if (_constants == null) + { + _constants = new RgbFloat[index + 1]; + } + else if (_constants.Length <= index) + { + Array.Resize(ref _constants, index + 1); + } + + _constants[index] = new RgbFloat(r, g, b); + _constantIndex = index; + } + + public readonly uint[] GetCode() + { + return _code?.ToArray(); + } + + public readonly RgbFloat[] GetConstants() + { + return _constants; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VertexInfoBufferUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VertexInfoBufferUpdater.cs new file mode 100644 index 00000000..65f556fc --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VertexInfoBufferUpdater.cs @@ -0,0 +1,141 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Shader; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw +{ + /// + /// Vertex info buffer data updater. + /// + class VertexInfoBufferUpdater : BufferUpdater + { + private VertexInfoBuffer _data; + + /// + /// Creates a new instance of the vertex info buffer updater. + /// + /// Renderer that the vertex info buffer will be used with + public VertexInfoBufferUpdater(IRenderer renderer) : base(renderer) + { + } + + /// + /// Sets vertex data related counts. + /// + /// Number of vertices used on the draw + /// Number of draw instances + /// Index of the first vertex on the vertex buffer + /// Index of the first instanced vertex on the vertex buffer + public void SetVertexCounts(int vertexCount, int instanceCount, int firstVertex, int firstInstance) + { + if (_data.VertexCounts.X != vertexCount) + { + _data.VertexCounts.X = vertexCount; + MarkDirty(VertexInfoBuffer.VertexCountsOffset, sizeof(int)); + } + + if (_data.VertexCounts.Y != instanceCount) + { + _data.VertexCounts.Y = instanceCount; + MarkDirty(VertexInfoBuffer.VertexCountsOffset + sizeof(int), sizeof(int)); + } + + if (_data.VertexCounts.Z != firstVertex) + { + _data.VertexCounts.Z = firstVertex; + MarkDirty(VertexInfoBuffer.VertexCountsOffset + sizeof(int) * 2, sizeof(int)); + } + + if (_data.VertexCounts.W != firstInstance) + { + _data.VertexCounts.W = firstInstance; + MarkDirty(VertexInfoBuffer.VertexCountsOffset + sizeof(int) * 3, sizeof(int)); + } + } + + /// + /// Sets vertex data related counts. + /// + /// Number of primitives consumed by the geometry shader + public void SetGeometryCounts(int primitivesCount) + { + if (_data.GeometryCounts.X != primitivesCount) + { + _data.GeometryCounts.X = primitivesCount; + MarkDirty(VertexInfoBuffer.GeometryCountsOffset, sizeof(int)); + } + } + + /// + /// Sets a vertex stride and related data. + /// + /// Index of the vertex stride to be updated + /// Stride divided by the component or format size + /// Number of components that the format has + public void SetVertexStride(int index, int stride, int componentCount) + { + if (_data.VertexStrides[index].X != stride) + { + _data.VertexStrides[index].X = stride; + MarkDirty(VertexInfoBuffer.VertexStridesOffset + index * Unsafe.SizeOf>(), sizeof(int)); + } + + for (int c = 1; c < 4; c++) + { + int value = c < componentCount ? 1 : 0; + + ref int currentValue = ref GetElementRef(ref _data.VertexStrides[index], c); + + if (currentValue != value) + { + currentValue = value; + MarkDirty(VertexInfoBuffer.VertexStridesOffset + index * Unsafe.SizeOf>() + c * sizeof(int), sizeof(int)); + } + } + } + + /// + /// Sets a vertex offset and related data. + /// + /// Index of the vertex offset to be updated + /// Offset divided by the component or format size + /// If the draw is instanced, should have the vertex divisor value, otherwise should be zero + public void SetVertexOffset(int index, int offset, int divisor) + { + if (_data.VertexOffsets[index].X != offset) + { + _data.VertexOffsets[index].X = offset; + MarkDirty(VertexInfoBuffer.VertexOffsetsOffset + index * Unsafe.SizeOf>(), sizeof(int)); + } + + if (_data.VertexOffsets[index].Y != divisor) + { + _data.VertexOffsets[index].Y = divisor; + MarkDirty(VertexInfoBuffer.VertexOffsetsOffset + index * Unsafe.SizeOf>() + sizeof(int), sizeof(int)); + } + } + + /// + /// Sets the offset of the index buffer. + /// + /// Offset divided by the component size + public void SetIndexBufferOffset(int offset) + { + if (_data.GeometryCounts.W != offset) + { + _data.GeometryCounts.W = offset; + MarkDirty(VertexInfoBuffer.GeometryCountsOffset + sizeof(int) * 3, sizeof(int)); + } + } + + /// + /// Submits all pending buffer updates to the GPU. + /// + public void Commit() + { + Commit(MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref _data, 1))); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsCompute.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsCompute.cs new file mode 100644 index 00000000..cbbfd251 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsCompute.cs @@ -0,0 +1,96 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Shader; +using System; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw +{ + /// + /// Vertex, tessellation and geometry as compute shader draw manager. + /// + class VtgAsCompute : IDisposable + { + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly DeviceStateWithShadow _state; + private readonly VtgAsComputeContext _vacContext; + + /// + /// Creates a new instance of the vertex, tessellation and geometry as compute shader draw manager. + /// + /// GPU context + /// GPU channel + /// 3D engine state + public VtgAsCompute(GpuContext context, GpuChannel channel, DeviceStateWithShadow state) + { + _context = context; + _channel = channel; + _state = state; + _vacContext = new(context); + } + + /// + /// Emulates the pre-rasterization stages of a draw operation using a compute shader. + /// + /// 3D engine + /// Vertex shader converted to compute + /// Optional geometry shader converted to compute + /// Fragment shader with a vertex passthrough shader to feed the compute output into the fragment stage + /// Primitive topology of the draw + /// Index or vertex count of the draw + /// Instance count + /// First index on the index buffer, for indexed draws + /// First vertex on the vertex buffer + /// First instance + /// Whether the draw is indexed + public void DrawAsCompute( + ThreedClass engine, + ShaderAsCompute vertexAsCompute, + ShaderAsCompute geometryAsCompute, + IProgram vertexPassthroughProgram, + PrimitiveTopology topology, + int count, + int instanceCount, + int firstIndex, + int firstVertex, + int firstInstance, + bool indexed) + { + VtgAsComputeState state = new( + _context, + _channel, + _state, + _vacContext, + engine, + vertexAsCompute, + geometryAsCompute, + vertexPassthroughProgram, + topology, + count, + instanceCount, + firstIndex, + firstVertex, + firstInstance, + indexed); + + state.RunVertex(); + state.RunGeometry(); + state.RunFragment(); + + _vacContext.FreeBuffers(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _vacContext.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs new file mode 100644 index 00000000..6de50fb2 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs @@ -0,0 +1,651 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw +{ + /// + /// Vertex, tessellation and geometry as compute shader context. + /// + class VtgAsComputeContext : IDisposable + { + private const int DummyBufferSize = 16; + + private readonly GpuContext _context; + + /// + /// Cache of buffer textures used for vertex and index buffers. + /// + private class BufferTextureCache : IDisposable + { + private readonly Dictionary _cache; + + /// + /// Creates a new instance of the buffer texture cache. + /// + public BufferTextureCache() + { + _cache = new(); + } + + /// + /// Gets a cached or creates and caches a buffer texture with the specified format. + /// + /// Renderer where the texture will be used + /// Format of the buffer texture + /// Buffer texture + public ITexture Get(IRenderer renderer, Format format) + { + if (!_cache.TryGetValue(format, out ITexture bufferTexture)) + { + bufferTexture = renderer.CreateTexture(new TextureCreateInfo( + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + format, + DepthStencilMode.Depth, + Target.TextureBuffer, + SwizzleComponent.Red, + SwizzleComponent.Green, + SwizzleComponent.Blue, + SwizzleComponent.Alpha)); + + _cache.Add(format, bufferTexture); + } + + return bufferTexture; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (var texture in _cache.Values) + { + texture.Release(); + } + + _cache.Clear(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } + + /// + /// Buffer state. + /// + private struct Buffer + { + /// + /// Buffer handle. + /// + public BufferHandle Handle; + + /// + /// Current free buffer offset. + /// + public int Offset; + + /// + /// Total buffer size in bytes. + /// + public int Size; + } + + /// + /// Index buffer state. + /// + private readonly struct IndexBuffer + { + /// + /// Buffer handle. + /// + public BufferHandle Handle { get; } + + /// + /// Index count. + /// + public int Count { get; } + + /// + /// Size in bytes. + /// + public int Size { get; } + + /// + /// Creates a new index buffer state. + /// + /// Buffer handle + /// Index count + /// Size in bytes + public IndexBuffer(BufferHandle handle, int count, int size) + { + Handle = handle; + Count = count; + Size = size; + } + + /// + /// Creates a full range starting from the beggining of the buffer. + /// + /// Range + public readonly BufferRange ToRange() + { + return new BufferRange(Handle, 0, Size); + } + + /// + /// Creates a range starting from the beggining of the buffer, with the specified size. + /// + /// Size in bytes of the range + /// Range + public readonly BufferRange ToRange(int size) + { + return new BufferRange(Handle, 0, size); + } + } + + private readonly BufferTextureCache[] _bufferTextures; + private BufferHandle _dummyBuffer; + private Buffer _vertexDataBuffer; + private Buffer _geometryVertexDataBuffer; + private Buffer _geometryIndexDataBuffer; + private BufferHandle _sequentialIndexBuffer; + private int _sequentialIndexBufferCount; + + private readonly Dictionary _topologyRemapBuffers; + + /// + /// Vertex information buffer updater. + /// + public VertexInfoBufferUpdater VertexInfoBufferUpdater { get; } + + /// + /// Creates a new instance of the vertex, tessellation and geometry as compute shader context. + /// + /// + public VtgAsComputeContext(GpuContext context) + { + _context = context; + _bufferTextures = new BufferTextureCache[Constants.TotalVertexBuffers + 2]; + _topologyRemapBuffers = new(); + VertexInfoBufferUpdater = new(context.Renderer); + } + + /// + /// Gets the number of complete primitives that can be formed with a given vertex count, for a given topology. + /// + /// Topology + /// Vertex count + /// Total of complete primitives + public static int GetPrimitivesCount(PrimitiveTopology primitiveType, int count) + { + return primitiveType switch + { + PrimitiveTopology.Lines => count / 2, + PrimitiveTopology.LinesAdjacency => count / 4, + PrimitiveTopology.LineLoop => count > 1 ? count : 0, + PrimitiveTopology.LineStrip => Math.Max(count - 1, 0), + PrimitiveTopology.LineStripAdjacency => Math.Max(count - 3, 0), + PrimitiveTopology.Triangles => count / 3, + PrimitiveTopology.TrianglesAdjacency => count / 6, + PrimitiveTopology.TriangleStrip or + PrimitiveTopology.TriangleFan or + PrimitiveTopology.Polygon => Math.Max(count - 2, 0), + PrimitiveTopology.TriangleStripAdjacency => Math.Max(count - 2, 0) / 2, + PrimitiveTopology.Quads => (count / 4) * 2, // In triangles. + PrimitiveTopology.QuadStrip => Math.Max((count - 2) / 2, 0) * 2, // In triangles. + _ => count, + }; + } + + /// + /// Gets the total of vertices that a single primitive has, for the specified topology. + /// + /// Topology + /// Vertex count + private static int GetVerticesPerPrimitive(PrimitiveTopology primitiveType) + { + return primitiveType switch + { + PrimitiveTopology.Lines or + PrimitiveTopology.LineLoop or + PrimitiveTopology.LineStrip => 2, + PrimitiveTopology.LinesAdjacency or + PrimitiveTopology.LineStripAdjacency => 4, + PrimitiveTopology.Triangles or + PrimitiveTopology.TriangleStrip or + PrimitiveTopology.TriangleFan or + PrimitiveTopology.Polygon => 3, + PrimitiveTopology.TrianglesAdjacency or + PrimitiveTopology.TriangleStripAdjacency => 6, + PrimitiveTopology.Quads or + PrimitiveTopology.QuadStrip => 3, // 2 triangles. + _ => 1, + }; + } + + /// + /// Gets a cached or creates a new buffer that can be used to map linear indices to ones + /// of a specified topology, and build complete primitives. + /// + /// Topology + /// Number of input vertices that needs to be mapped using that buffer + /// Remap buffer range + public BufferRange GetOrCreateTopologyRemapBuffer(PrimitiveTopology topology, int count) + { + if (!_topologyRemapBuffers.TryGetValue(topology, out IndexBuffer buffer) || buffer.Count < count) + { + if (buffer.Handle != BufferHandle.Null) + { + _context.Renderer.DeleteBuffer(buffer.Handle); + } + + buffer = CreateTopologyRemapBuffer(topology, count); + _topologyRemapBuffers[topology] = buffer; + + return buffer.ToRange(); + } + + return buffer.ToRange(Math.Max(GetPrimitivesCount(topology, count) * GetVerticesPerPrimitive(topology), 1) * sizeof(uint)); + } + + /// + /// Creates a new topology remap buffer. + /// + /// Topology + /// Maximum of vertices that will be accessed + /// Remap buffer range + private IndexBuffer CreateTopologyRemapBuffer(PrimitiveTopology topology, int count) + { + // Size can't be zero as creating zero sized buffers is invalid. + Span data = new int[Math.Max(GetPrimitivesCount(topology, count) * GetVerticesPerPrimitive(topology), 1)]; + + switch (topology) + { + case PrimitiveTopology.Points: + case PrimitiveTopology.Lines: + case PrimitiveTopology.LinesAdjacency: + case PrimitiveTopology.Triangles: + case PrimitiveTopology.TrianglesAdjacency: + case PrimitiveTopology.Patches: + for (int index = 0; index < data.Length; index++) + { + data[index] = index; + } + break; + case PrimitiveTopology.LineLoop: + data[^1] = 0; + + for (int index = 0; index < ((data.Length - 1) & ~1); index += 2) + { + data[index] = index >> 1; + data[index + 1] = (index >> 1) + 1; + } + break; + case PrimitiveTopology.LineStrip: + for (int index = 0; index < ((data.Length - 1) & ~1); index += 2) + { + data[index] = index >> 1; + data[index + 1] = (index >> 1) + 1; + } + break; + case PrimitiveTopology.TriangleStrip: + int tsTrianglesCount = data.Length / 3; + int tsOutIndex = 3; + + if (tsTrianglesCount > 0) + { + data[0] = 0; + data[1] = 1; + data[2] = 2; + } + + for (int tri = 1; tri < tsTrianglesCount; tri++) + { + int baseIndex = tri * 3; + + if ((tri & 1) != 0) + { + data[baseIndex] = tsOutIndex - 1; + data[baseIndex + 1] = tsOutIndex - 2; + data[baseIndex + 2] = tsOutIndex++; + } + else + { + data[baseIndex] = tsOutIndex - 2; + data[baseIndex + 1] = tsOutIndex - 1; + data[baseIndex + 2] = tsOutIndex++; + } + } + break; + case PrimitiveTopology.TriangleFan: + case PrimitiveTopology.Polygon: + int tfTrianglesCount = data.Length / 3; + int tfOutIndex = 1; + + for (int index = 0; index < tfTrianglesCount * 3; index += 3) + { + data[index] = 0; + data[index + 1] = tfOutIndex; + data[index + 2] = ++tfOutIndex; + } + break; + case PrimitiveTopology.Quads: + int qQuadsCount = data.Length / 6; + + for (int quad = 0; quad < qQuadsCount; quad++) + { + int index = quad * 6; + int qIndex = quad * 4; + + data[index] = qIndex; + data[index + 1] = qIndex + 1; + data[index + 2] = qIndex + 2; + data[index + 3] = qIndex; + data[index + 4] = qIndex + 2; + data[index + 5] = qIndex + 3; + } + break; + case PrimitiveTopology.QuadStrip: + int qsQuadsCount = data.Length / 6; + + if (qsQuadsCount > 0) + { + data[0] = 0; + data[1] = 1; + data[2] = 2; + data[3] = 0; + data[4] = 2; + data[5] = 3; + } + + for (int quad = 1; quad < qsQuadsCount; quad++) + { + int index = quad * 6; + int qIndex = quad * 2; + + data[index] = qIndex + 1; + data[index + 1] = qIndex; + data[index + 2] = qIndex + 2; + data[index + 3] = qIndex + 1; + data[index + 4] = qIndex + 2; + data[index + 5] = qIndex + 3; + } + break; + case PrimitiveTopology.LineStripAdjacency: + for (int index = 0; index < ((data.Length - 3) & ~3); index += 4) + { + int lIndex = index >> 2; + + data[index] = lIndex; + data[index + 1] = lIndex + 1; + data[index + 2] = lIndex + 2; + data[index + 3] = lIndex + 3; + } + break; + case PrimitiveTopology.TriangleStripAdjacency: + int tsaTrianglesCount = data.Length / 6; + int tsaOutIndex = 6; + + if (tsaTrianglesCount > 0) + { + data[0] = 0; + data[1] = 1; + data[2] = 2; + data[3] = 3; + data[4] = 4; + data[5] = 5; + } + + for (int tri = 1; tri < tsaTrianglesCount; tri++) + { + int baseIndex = tri * 6; + + if ((tri & 1) != 0) + { + data[baseIndex] = tsaOutIndex - 2; + data[baseIndex + 1] = tsaOutIndex - 1; + data[baseIndex + 2] = tsaOutIndex - 4; + data[baseIndex + 3] = tsaOutIndex - 3; + data[baseIndex + 4] = tsaOutIndex++; + data[baseIndex + 5] = tsaOutIndex++; + } + else + { + data[baseIndex] = tsaOutIndex - 4; + data[baseIndex + 1] = tsaOutIndex - 3; + data[baseIndex + 2] = tsaOutIndex - 2; + data[baseIndex + 3] = tsaOutIndex - 1; + data[baseIndex + 4] = tsaOutIndex++; + data[baseIndex + 5] = tsaOutIndex++; + } + } + break; + } + + ReadOnlySpan dataBytes = MemoryMarshal.Cast(data); + + BufferHandle buffer = _context.Renderer.CreateBuffer(dataBytes.Length, BufferAccess.DeviceMemory); + _context.Renderer.SetBufferData(buffer, 0, dataBytes); + + return new IndexBuffer(buffer, count, dataBytes.Length); + } + + /// + /// Gets a buffer texture with a given format, for the given index. + /// + /// Index of the buffer texture + /// Format of the buffer texture + /// Buffer texture + public ITexture EnsureBufferTexture(int index, Format format) + { + return (_bufferTextures[index] ??= new()).Get(_context.Renderer, format); + } + + /// + /// Gets the offset and size of usable storage on the output vertex buffer. + /// + /// Size in bytes that will be used + /// Usable offset and size on the buffer + public (int, int) GetVertexDataBuffer(int size) + { + return EnsureBuffer(ref _vertexDataBuffer, size); + } + + /// + /// Gets the offset and size of usable storage on the output geometry shader vertex buffer. + /// + /// Size in bytes that will be used + /// Usable offset and size on the buffer + public (int, int) GetGeometryVertexDataBuffer(int size) + { + return EnsureBuffer(ref _geometryVertexDataBuffer, size); + } + + /// + /// Gets the offset and size of usable storage on the output geometry shader index buffer. + /// + /// Size in bytes that will be used + /// Usable offset and size on the buffer + public (int, int) GetGeometryIndexDataBuffer(int size) + { + return EnsureBuffer(ref _geometryIndexDataBuffer, size); + } + + /// + /// Gets a range of the output vertex buffer for binding. + /// + /// Offset of the range + /// Size of the range in bytes + /// Indicates if the buffer contents will be modified + /// Range + public BufferRange GetVertexDataBufferRange(int offset, int size, bool write) + { + return new BufferRange(_vertexDataBuffer.Handle, offset, size, write); + } + + /// + /// Gets a range of the output geometry shader vertex buffer for binding. + /// + /// Offset of the range + /// Size of the range in bytes + /// Indicates if the buffer contents will be modified + /// Range + public BufferRange GetGeometryVertexDataBufferRange(int offset, int size, bool write) + { + return new BufferRange(_geometryVertexDataBuffer.Handle, offset, size, write); + } + + /// + /// Gets a range of the output geometry shader index buffer for binding. + /// + /// Offset of the range + /// Size of the range in bytes + /// Indicates if the buffer contents will be modified + /// Range + public BufferRange GetGeometryIndexDataBufferRange(int offset, int size, bool write) + { + return new BufferRange(_geometryIndexDataBuffer.Handle, offset, size, write); + } + + /// + /// Gets the range for a dummy 16 bytes buffer, filled with zeros. + /// + /// Dummy buffer range + public BufferRange GetDummyBufferRange() + { + if (_dummyBuffer == BufferHandle.Null) + { + _dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize, BufferAccess.DeviceMemory); + _context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0); + } + + return new BufferRange(_dummyBuffer, 0, DummyBufferSize); + } + + /// + /// Gets the range for a sequential index buffer, with ever incrementing index values. + /// + /// Minimum number of indices that the buffer should have + /// Buffer handle + public BufferHandle GetSequentialIndexBuffer(int count) + { + if (_sequentialIndexBufferCount < count) + { + if (_sequentialIndexBuffer != BufferHandle.Null) + { + _context.Renderer.DeleteBuffer(_sequentialIndexBuffer); + } + + _sequentialIndexBuffer = _context.Renderer.CreateBuffer(count * sizeof(uint), BufferAccess.DeviceMemory); + _sequentialIndexBufferCount = count; + + Span data = new int[count]; + + for (int index = 0; index < count; index++) + { + data[index] = index; + } + + _context.Renderer.SetBufferData(_sequentialIndexBuffer, 0, MemoryMarshal.Cast(data)); + } + + return _sequentialIndexBuffer; + } + + /// + /// Ensure that a buffer exists, is large enough, and allocates a sub-region of the specified size inside the buffer. + /// + /// Buffer state + /// Required size in bytes + /// Allocated offset and size + private (int, int) EnsureBuffer(ref Buffer buffer, int size) + { + int newSize = buffer.Offset + size; + + if (buffer.Size < newSize) + { + if (buffer.Handle != BufferHandle.Null) + { + _context.Renderer.DeleteBuffer(buffer.Handle); + } + + buffer.Handle = _context.Renderer.CreateBuffer(newSize, BufferAccess.DeviceMemory); + buffer.Size = newSize; + } + + int offset = buffer.Offset; + + buffer.Offset = BitUtils.AlignUp(newSize, _context.Capabilities.StorageBufferOffsetAlignment); + + return (offset, size); + } + + /// + /// Frees all buffer sub-regions that were previously allocated. + /// + public void FreeBuffers() + { + _vertexDataBuffer.Offset = 0; + _geometryVertexDataBuffer.Offset = 0; + _geometryIndexDataBuffer.Offset = 0; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + for (int index = 0; index < _bufferTextures.Length; index++) + { + _bufferTextures[index]?.Dispose(); + _bufferTextures[index] = null; + } + + DestroyIfNotNull(ref _dummyBuffer); + DestroyIfNotNull(ref _vertexDataBuffer.Handle); + DestroyIfNotNull(ref _geometryVertexDataBuffer.Handle); + DestroyIfNotNull(ref _geometryIndexDataBuffer.Handle); + DestroyIfNotNull(ref _sequentialIndexBuffer); + + foreach (var indexBuffer in _topologyRemapBuffers.Values) + { + _context.Renderer.DeleteBuffer(indexBuffer.Handle); + } + + _topologyRemapBuffers.Clear(); + } + } + + /// + /// Deletes a buffer if the handle is valid (not null), then sets the handle to null. + /// + /// Buffer handle + private void DestroyIfNotNull(ref BufferHandle handle) + { + if (handle != BufferHandle.Null) + { + _context.Renderer.DeleteBuffer(handle); + handle = BufferHandle.Null; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs new file mode 100644 index 00000000..73682866 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs @@ -0,0 +1,536 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.Shader; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw +{ + /// + /// Vertex, tessellation and geometry as compute shader state. + /// + struct VtgAsComputeState + { + private const int ComputeLocalSize = 32; + + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly DeviceStateWithShadow _state; + private readonly VtgAsComputeContext _vacContext; + private readonly ThreedClass _engine; + private readonly ShaderAsCompute _vertexAsCompute; + private readonly ShaderAsCompute _geometryAsCompute; + private readonly IProgram _vertexPassthroughProgram; + private readonly PrimitiveTopology _topology; + private readonly int _count; + private readonly int _instanceCount; + private readonly int _firstIndex; + private readonly int _firstVertex; + private readonly int _firstInstance; + private readonly bool _indexed; + + private readonly int _vertexDataOffset; + private readonly int _vertexDataSize; + private readonly int _geometryVertexDataOffset; + private readonly int _geometryVertexDataSize; + private readonly int _geometryIndexDataOffset; + private readonly int _geometryIndexDataSize; + private readonly int _geometryIndexDataCount; + + /// + /// Creates a new vertex, tessellation and geometry as compute shader state. + /// + /// GPU context + /// GPU channel + /// 3D engine state + /// Vertex as compute context + /// 3D engine + /// Vertex shader converted to compute + /// Optional geometry shader converted to compute + /// Fragment shader with a vertex passthrough shader to feed the compute output into the fragment stage + /// Primitive topology of the draw + /// Index or vertex count of the draw + /// Instance count + /// First index on the index buffer, for indexed draws + /// First vertex on the vertex buffer + /// First instance + /// Whether the draw is indexed + public VtgAsComputeState( + GpuContext context, + GpuChannel channel, + DeviceStateWithShadow state, + VtgAsComputeContext vacContext, + ThreedClass engine, + ShaderAsCompute vertexAsCompute, + ShaderAsCompute geometryAsCompute, + IProgram vertexPassthroughProgram, + PrimitiveTopology topology, + int count, + int instanceCount, + int firstIndex, + int firstVertex, + int firstInstance, + bool indexed) + { + _context = context; + _channel = channel; + _state = state; + _vacContext = vacContext; + _engine = engine; + _vertexAsCompute = vertexAsCompute; + _geometryAsCompute = geometryAsCompute; + _vertexPassthroughProgram = vertexPassthroughProgram; + _topology = topology; + _count = count; + _instanceCount = instanceCount; + _firstIndex = firstIndex; + _firstVertex = firstVertex; + _firstInstance = firstInstance; + _indexed = indexed; + + int vertexDataSize = vertexAsCompute.Reservations.OutputSizeInBytesPerInvocation * count * instanceCount; + + (_vertexDataOffset, _vertexDataSize) = _vacContext.GetVertexDataBuffer(vertexDataSize); + + if (geometryAsCompute != null) + { + int totalPrimitivesCount = VtgAsComputeContext.GetPrimitivesCount(topology, count * instanceCount); + int maxCompleteStrips = GetMaxCompleteStrips(geometryAsCompute.Info.GeometryVerticesPerPrimitive, geometryAsCompute.Info.GeometryMaxOutputVertices); + int totalVerticesCount = totalPrimitivesCount * geometryAsCompute.Info.GeometryMaxOutputVertices * geometryAsCompute.Info.ThreadsPerInputPrimitive; + int geometryVbDataSize = totalVerticesCount * geometryAsCompute.Reservations.OutputSizeInBytesPerInvocation; + int geometryIbDataCount = totalVerticesCount + totalPrimitivesCount * maxCompleteStrips; + int geometryIbDataSize = geometryIbDataCount * sizeof(uint); + + (_geometryVertexDataOffset, _geometryVertexDataSize) = vacContext.GetGeometryVertexDataBuffer(geometryVbDataSize); + (_geometryIndexDataOffset, _geometryIndexDataSize) = vacContext.GetGeometryIndexDataBuffer(geometryIbDataSize); + + _geometryIndexDataCount = geometryIbDataCount; + } + } + + /// + /// Emulates the vertex stage using compute. + /// + public readonly void RunVertex() + { + _context.Renderer.Pipeline.SetProgram(_vertexAsCompute.HostProgram); + + int primitivesCount = VtgAsComputeContext.GetPrimitivesCount(_topology, _count); + + _vacContext.VertexInfoBufferUpdater.SetVertexCounts(_count, _instanceCount, _firstVertex, _firstInstance); + _vacContext.VertexInfoBufferUpdater.SetGeometryCounts(primitivesCount); + + for (int index = 0; index < Constants.TotalVertexAttribs; index++) + { + var vertexAttrib = _state.State.VertexAttribState[index]; + + if (!FormatTable.TryGetSingleComponentAttribFormat(vertexAttrib.UnpackFormat(), out Format format, out int componentsCount)) + { + Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}."); + + format = vertexAttrib.UnpackType() switch + { + VertexAttribType.Sint => Format.R32Sint, + VertexAttribType.Uint => Format.R32Uint, + _ => Format.R32Float + }; + + componentsCount = 4; + } + + if (vertexAttrib.UnpackIsConstant()) + { + _vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount); + _vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0); + SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format); + continue; + } + + int bufferIndex = vertexAttrib.UnpackBufferIndex(); + + GpuVa endAddress = _state.State.VertexBufferEndAddress[bufferIndex]; + var vertexBuffer = _state.State.VertexBufferState[bufferIndex]; + bool instanced = _state.State.VertexBufferInstanced[bufferIndex]; + + ulong address = vertexBuffer.Address.Pack(); + + if (!vertexBuffer.UnpackEnable() || !_channel.MemoryManager.IsMapped(address)) + { + _vacContext.VertexInfoBufferUpdater.SetVertexStride(index, 0, componentsCount); + _vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, 0, 0); + SetDummyBufferTexture(_vertexAsCompute.Reservations, index, format); + continue; + } + + int vbStride = vertexBuffer.UnpackStride(); + ulong vbSize = GetVertexBufferSize(address, endAddress.Pack(), vbStride, _indexed, instanced, _firstVertex, _count); + + ulong oldVbSize = vbSize; + + ulong attributeOffset = (ulong)vertexAttrib.UnpackOffset(); + int componentSize = format.GetScalarSize(); + + address += attributeOffset; + + ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1); + + vbSize = Align(vbSize - attributeOffset + misalign, componentSize); + + SetBufferTexture(_vertexAsCompute.Reservations, index, format, address - misalign, vbSize); + + _vacContext.VertexInfoBufferUpdater.SetVertexStride(index, vbStride / componentSize, componentsCount); + _vacContext.VertexInfoBufferUpdater.SetVertexOffset(index, (int)misalign / componentSize, instanced ? vertexBuffer.Divisor : 0); + } + + if (_indexed) + { + SetIndexBufferTexture(_vertexAsCompute.Reservations, _firstIndex, _count, out int ibOffset); + _vacContext.VertexInfoBufferUpdater.SetIndexBufferOffset(ibOffset); + } + else + { + SetSequentialIndexBufferTexture(_vertexAsCompute.Reservations, _count); + _vacContext.VertexInfoBufferUpdater.SetIndexBufferOffset(0); + } + + int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding; + BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize); + _context.Renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) }); + + int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding; + BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: true); + _context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) }); + + _vacContext.VertexInfoBufferUpdater.Commit(); + + _context.Renderer.Pipeline.DispatchCompute( + BitUtils.DivRoundUp(_count, ComputeLocalSize), + BitUtils.DivRoundUp(_instanceCount, ComputeLocalSize), + 1); + } + + /// + /// Emulates the geometry stage using compute, if it exists, otherwise does nothing. + /// + public readonly void RunGeometry() + { + if (_geometryAsCompute == null) + { + return; + } + + int primitivesCount = VtgAsComputeContext.GetPrimitivesCount(_topology, _count); + + _vacContext.VertexInfoBufferUpdater.SetVertexCounts(_count, _instanceCount, _firstVertex, _firstInstance); + _vacContext.VertexInfoBufferUpdater.SetGeometryCounts(primitivesCount); + _vacContext.VertexInfoBufferUpdater.Commit(); + + int vertexInfoBinding = _vertexAsCompute.Reservations.VertexInfoConstantBufferBinding; + BufferRange vertexInfoRange = new(_vacContext.VertexInfoBufferUpdater.Handle, 0, VertexInfoBuffer.RequiredSize); + _context.Renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(vertexInfoBinding, vertexInfoRange) }); + + int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding; + + // Wait until compute is done. + // TODO: Batch compute and draw operations to avoid pipeline stalls. + _context.Renderer.Pipeline.Barrier(); + _context.Renderer.Pipeline.SetProgram(_geometryAsCompute.HostProgram); + + SetTopologyRemapBufferTexture(_geometryAsCompute.Reservations, _topology, _count); + + int geometryVbBinding = _geometryAsCompute.Reservations.GeometryVertexOutputStorageBufferBinding; + int geometryIbBinding = _geometryAsCompute.Reservations.GeometryIndexOutputStorageBufferBinding; + + BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: false); + BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize, write: true); + BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize, write: true); + + _context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] + { + new BufferAssignment(vertexDataBinding, vertexDataRange), + new BufferAssignment(geometryVbBinding, vertexBuffer), + new BufferAssignment(geometryIbBinding, indexBuffer), + }); + + _context.Renderer.Pipeline.DispatchCompute( + BitUtils.DivRoundUp(primitivesCount, ComputeLocalSize), + BitUtils.DivRoundUp(_instanceCount, ComputeLocalSize), + _geometryAsCompute.Info.ThreadsPerInputPrimitive); + } + + /// + /// Performs a draw using the data produced on the vertex, tessellation and geometry stages, + /// if rasterizer discard is disabled. + /// + public readonly void RunFragment() + { + bool tfEnabled = _state.State.TfEnable; + + if (!_state.State.RasterizeEnable && (!tfEnabled || !_context.Capabilities.SupportsTransformFeedback)) + { + // No need to run fragment if rasterizer discard is enabled, + // and we are emulating transform feedback or transform feedback is disabled. + + // Note: We might skip geometry shader here, but right now, this is fine, + // because the only cases that triggers VTG to compute are geometry shader + // being not supported, or the vertex pipeline doing store operations. + // If the geometry shader does not do any store and rasterizer discard is enabled, the geometry shader can be skipped. + // If the geometry shader does have stores, it would have been converted to compute too if stores are not supported. + + return; + } + + int vertexDataBinding = _vertexAsCompute.Reservations.VertexOutputStorageBufferBinding; + + _context.Renderer.Pipeline.Barrier(); + + _vacContext.VertexInfoBufferUpdater.SetVertexCounts(_count, _instanceCount, _firstVertex, _firstInstance); + _vacContext.VertexInfoBufferUpdater.Commit(); + + if (_geometryAsCompute != null) + { + BufferRange vertexBuffer = _vacContext.GetGeometryVertexDataBufferRange(_geometryVertexDataOffset, _geometryVertexDataSize, write: false); + BufferRange indexBuffer = _vacContext.GetGeometryIndexDataBufferRange(_geometryIndexDataOffset, _geometryIndexDataSize, write: false); + + _context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram); + _context.Renderer.Pipeline.SetIndexBuffer(indexBuffer, IndexType.UInt); + _context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexBuffer) }); + + _context.Renderer.Pipeline.SetPrimitiveRestart(true, -1); + _context.Renderer.Pipeline.SetPrimitiveTopology(GetGeometryOutputTopology(_geometryAsCompute.Info.GeometryVerticesPerPrimitive)); + + _context.Renderer.Pipeline.DrawIndexed(_geometryIndexDataCount, 1, 0, 0, 0); + + _engine.ForceStateDirtyByIndex(StateUpdater.IndexBufferStateIndex); + _engine.ForceStateDirtyByIndex(StateUpdater.PrimitiveRestartStateIndex); + } + else + { + BufferRange vertexDataRange = _vacContext.GetVertexDataBufferRange(_vertexDataOffset, _vertexDataSize, write: false); + + _context.Renderer.Pipeline.SetProgram(_vertexPassthroughProgram); + _context.Renderer.Pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(vertexDataBinding, vertexDataRange) }); + _context.Renderer.Pipeline.Draw(_count, _instanceCount, 0, 0); + } + } + + /// + /// Gets a strip primitive topology from the vertices per primitive count. + /// + /// Vertices per primitive count + /// Primitive topology + private static PrimitiveTopology GetGeometryOutputTopology(int verticesPerPrimitive) + { + return verticesPerPrimitive switch + { + 3 => PrimitiveTopology.TriangleStrip, + 2 => PrimitiveTopology.LineStrip, + _ => PrimitiveTopology.Points, + }; + } + + /// + /// Gets the maximum number of complete primitive strips for a vertex count. + /// + /// Vertices per primitive count + /// Maximum geometry shader output vertices count + /// Maximum number of complete primitive strips + private static int GetMaxCompleteStrips(int verticesPerPrimitive, int maxOutputVertices) + { + return maxOutputVertices / verticesPerPrimitive; + } + + /// + /// Binds a dummy buffer as vertex buffer into a buffer texture. + /// + /// Shader resource binding reservations + /// Buffer texture index + /// Buffer texture format + private readonly void SetDummyBufferTexture(ResourceReservations reservations, int index, Format format) + { + ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format); + bufferTexture.SetStorage(_vacContext.GetDummyBufferRange()); + + _context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.GetVertexBufferTextureBinding(index), bufferTexture, null); + } + + /// + /// Binds a vertex buffer into a buffer texture. + /// + /// Shader resource binding reservations + /// Buffer texture index + /// Buffer texture format + /// Address of the vertex buffer + /// Size of the buffer in bytes + private readonly void SetBufferTexture(ResourceReservations reservations, int index, Format format, ulong address, ulong size) + { + var memoryManager = _channel.MemoryManager; + + BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size), BufferStage.VertexBuffer); + + ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format); + bufferTexture.SetStorage(range); + + _context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.GetVertexBufferTextureBinding(index), bufferTexture, null); + } + + /// + /// Binds the index buffer into a buffer texture. + /// + /// Shader resource binding reservations + /// First index of the index buffer + /// Index count + /// Offset that should be added when accessing the buffer texture on the shader + private readonly void SetIndexBufferTexture(ResourceReservations reservations, int firstIndex, int count, out int misalignedOffset) + { + ulong address = _state.State.IndexBufferState.Address.Pack(); + ulong indexOffset = (ulong)firstIndex; + ulong size = (ulong)count; + + int shift = 0; + Format format = Format.R8Uint; + + switch (_state.State.IndexBufferState.Type) + { + case IndexType.UShort: + shift = 1; + format = Format.R16Uint; + break; + case IndexType.UInt: + shift = 2; + format = Format.R32Uint; + break; + } + + indexOffset <<= shift; + size <<= shift; + + var memoryManager = _channel.MemoryManager; + + ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1); + BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange( + memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign), + BufferStage.IndexBuffer); + misalignedOffset = (int)misalign >> shift; + + SetIndexBufferTexture(reservations, range, format); + } + + /// + /// Sets the host buffer texture for the index buffer. + /// + /// Shader resource binding reservations + /// Index buffer range + /// Index buffer format + private readonly void SetIndexBufferTexture(ResourceReservations reservations, BufferRange range, Format format) + { + ITexture bufferTexture = _vacContext.EnsureBufferTexture(0, format); + bufferTexture.SetStorage(range); + + _context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.IndexBufferTextureBinding, bufferTexture, null); + } + + /// + /// Sets the host buffer texture for the topology remap buffer. + /// + /// Shader resource binding reservations + /// Input topology + /// Input vertex count + private readonly void SetTopologyRemapBufferTexture(ResourceReservations reservations, PrimitiveTopology topology, int count) + { + ITexture bufferTexture = _vacContext.EnsureBufferTexture(1, Format.R32Uint); + bufferTexture.SetStorage(_vacContext.GetOrCreateTopologyRemapBuffer(topology, count)); + + _context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.TopologyRemapBufferTextureBinding, bufferTexture, null); + } + + /// + /// Sets the host buffer texture to a generated sequential index buffer. + /// + /// Shader resource binding reservations + /// Vertex count + private readonly void SetSequentialIndexBufferTexture(ResourceReservations reservations, int count) + { + BufferHandle sequentialIndexBuffer = _vacContext.GetSequentialIndexBuffer(count); + + ITexture bufferTexture = _vacContext.EnsureBufferTexture(0, Format.R32Uint); + bufferTexture.SetStorage(new BufferRange(sequentialIndexBuffer, 0, count * sizeof(uint))); + + _context.Renderer.Pipeline.SetTextureAndSampler(ShaderStage.Compute, reservations.IndexBufferTextureBinding, bufferTexture, null); + } + + /// + /// Gets the size of a vertex buffer based on the current 3D engine state. + /// + /// Vertex buffer address + /// Vertex buffer end address (exclusive) + /// Vertex buffer stride + /// Whether the draw is indexed + /// Whether the draw is instanced + /// First vertex index + /// Vertex count + /// Size of the vertex buffer, in bytes + private readonly ulong GetVertexBufferSize(ulong vbAddress, ulong vbEndAddress, int vbStride, bool indexed, bool instanced, int firstVertex, int vertexCount) + { + IndexType indexType = _state.State.IndexBufferState.Type; + bool indexTypeSmall = indexType == IndexType.UByte || indexType == IndexType.UShort; + ulong vbSize = vbEndAddress - vbAddress + 1; + ulong size; + + if (indexed || vbStride == 0 || instanced) + { + // This size may be (much) larger than the real vertex buffer size. + // Avoid calculating it this way, unless we don't have any other option. + + size = vbSize; + + if (vbStride > 0 && indexTypeSmall && indexed && !instanced) + { + // If the index type is a small integer type, then we might be still able + // to reduce the vertex buffer size based on the maximum possible index value. + + ulong maxVertexBufferSize = indexType == IndexType.UByte ? 0x100UL : 0x10000UL; + + maxVertexBufferSize += _state.State.FirstVertex; + maxVertexBufferSize *= (uint)vbStride; + + size = Math.Min(size, maxVertexBufferSize); + } + } + else + { + // For non-indexed draws, we can guess the size from the vertex count + // and stride. + + int firstInstance = (int)_state.State.FirstInstance; + + size = Math.Min(vbSize, (ulong)((firstInstance + firstVertex + vertexCount) * vbStride)); + } + + return size; + } + + /// + /// Aligns a size to a given alignment value. + /// + /// Size + /// Alignment + /// Aligned size + private static ulong Align(ulong size, int alignment) + { + ulong align = (ulong)alignment; + + size += align - 1; + + size /= align; + size *= align; + + return size; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ConditionalRendering.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ConditionalRendering.cs new file mode 100644 index 00000000..01ef2f9a --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ConditionalRendering.cs @@ -0,0 +1,130 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Memory; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// + /// Helper methods used for conditional rendering. + /// + static class ConditionalRendering + { + /// + /// Checks if draws and clears should be performed, according + /// to currently set conditional rendering conditions. + /// + /// GPU context + /// Memory manager bound to the channel currently executing + /// Conditional rendering buffer address + /// Conditional rendering condition + /// True if rendering is enabled, false otherwise + public static ConditionalRenderEnabled GetRenderEnable(GpuContext context, MemoryManager memoryManager, GpuVa address, Condition condition) + { + switch (condition) + { + case Condition.Always: + return ConditionalRenderEnabled.True; + case Condition.Never: + return ConditionalRenderEnabled.False; + case Condition.ResultNonZero: + return CounterNonZero(context, memoryManager, address.Pack()); + case Condition.Equal: + return CounterCompare(context, memoryManager, address.Pack(), true); + case Condition.NotEqual: + return CounterCompare(context, memoryManager, address.Pack(), false); + } + + Logger.Warning?.Print(LogClass.Gpu, $"Invalid conditional render condition \"{condition}\"."); + + return ConditionalRenderEnabled.True; + } + + /// + /// Checks if the counter value at a given GPU memory address is non-zero. + /// + /// GPU context + /// Memory manager bound to the channel currently executing + /// GPU virtual address of the counter value + /// True if the value is not zero, false otherwise. Returns host if handling with host conditional rendering + private static ConditionalRenderEnabled CounterNonZero(GpuContext context, MemoryManager memoryManager, ulong gpuVa) + { + ICounterEvent evt = memoryManager.CounterCache.FindEvent(gpuVa); + + if (evt == null) + { + return ConditionalRenderEnabled.False; + } + + if (context.Renderer.Pipeline.TryHostConditionalRendering(evt, 0L, false)) + { + return ConditionalRenderEnabled.Host; + } + else + { + evt.Flush(); + return (memoryManager.Read(gpuVa, true) != 0) ? ConditionalRenderEnabled.True : ConditionalRenderEnabled.False; + } + } + + /// + /// Checks if the counter at a given GPU memory address passes a specified equality comparison. + /// + /// GPU context + /// Memory manager bound to the channel currently executing + /// GPU virtual address + /// True to check if the values are equal, false to check if they are not equal + /// True if the condition is met, false otherwise. Returns host if handling with host conditional rendering + private static ConditionalRenderEnabled CounterCompare(GpuContext context, MemoryManager memoryManager, ulong gpuVa, bool isEqual) + { + ICounterEvent evt = FindEvent(memoryManager.CounterCache, gpuVa); + ICounterEvent evt2 = FindEvent(memoryManager.CounterCache, gpuVa + 16); + + bool useHost; + + if (evt != null && evt2 == null) + { + useHost = context.Renderer.Pipeline.TryHostConditionalRendering(evt, memoryManager.Read(gpuVa + 16), isEqual); + } + else if (evt == null && evt2 != null) + { + useHost = context.Renderer.Pipeline.TryHostConditionalRendering(evt2, memoryManager.Read(gpuVa), isEqual); + } + else if (evt != null && evt2 != null) + { + useHost = context.Renderer.Pipeline.TryHostConditionalRendering(evt, evt2, isEqual); + } + else + { + useHost = false; + } + + if (useHost) + { + return ConditionalRenderEnabled.Host; + } + else + { + evt?.Flush(); + evt2?.Flush(); + + ulong x = memoryManager.Read(gpuVa, true); + ulong y = memoryManager.Read(gpuVa + 16, true); + + return (isEqual ? x == y : x != y) ? ConditionalRenderEnabled.True : ConditionalRenderEnabled.False; + } + } + + /// + /// Tries to find a counter that is supposed to be written at the specified address, + /// returning the related event. + /// + /// GPU counter cache to search on + /// GPU virtual address where the counter is supposed to be written + /// The counter event, or null if not present + private static ICounterEvent FindEvent(CounterCache counterCache, ulong gpuVa) + { + return counterCache.FindEvent(gpuVa); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs new file mode 100644 index 00000000..2095fcd7 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs @@ -0,0 +1,183 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// + /// Constant buffer updater. + /// + class ConstantBufferUpdater + { + private const int UniformDataCacheSize = 512; + + private readonly GpuChannel _channel; + private readonly DeviceStateWithShadow _state; + + // State associated with direct uniform buffer updates. + // This state is used to attempt to batch together consecutive updates. + private ulong _ubBeginCpuAddress = 0; + private ulong _ubFollowUpAddress = 0; + private ulong _ubByteCount = 0; + private int _ubIndex = 0; + private readonly int[] _ubData = new int[UniformDataCacheSize]; + + /// + /// Creates a new instance of the constant buffer updater. + /// + /// GPU channel + /// Channel state + public ConstantBufferUpdater(GpuChannel channel, DeviceStateWithShadow state) + { + _channel = channel; + _state = state; + } + + /// + /// Binds a uniform buffer for the vertex shader stage. + /// + /// Method call argument + public void BindVertex(int argument) + { + Bind(argument, ShaderType.Vertex); + } + + /// + /// Binds a uniform buffer for the tessellation control shader stage. + /// + /// Method call argument + public void BindTessControl(int argument) + { + Bind(argument, ShaderType.TessellationControl); + } + + /// + /// Binds a uniform buffer for the tessellation evaluation shader stage. + /// + /// Method call argument + public void BindTessEvaluation(int argument) + { + Bind(argument, ShaderType.TessellationEvaluation); + } + + /// + /// Binds a uniform buffer for the geometry shader stage. + /// + /// Method call argument + public void BindGeometry(int argument) + { + Bind(argument, ShaderType.Geometry); + } + + /// + /// Binds a uniform buffer for the fragment shader stage. + /// + /// Method call argument + public void BindFragment(int argument) + { + Bind(argument, ShaderType.Fragment); + } + + /// + /// Binds a uniform buffer for the specified shader stage. + /// + /// Method call argument + /// Shader stage that will access the uniform buffer + private void Bind(int argument, ShaderType type) + { + bool enable = (argument & 1) != 0; + + int index = (argument >> 4) & 0x1f; + + FlushUboDirty(); + + if (enable) + { + var uniformBuffer = _state.State.UniformBufferState; + + ulong address = uniformBuffer.Address.Pack(); + + _channel.BufferManager.SetGraphicsUniformBuffer((int)type, index, address, (uint)uniformBuffer.Size); + } + else + { + _channel.BufferManager.SetGraphicsUniformBuffer((int)type, index, 0, 0); + } + } + + /// + /// Flushes any queued UBO updates. + /// + public void FlushUboDirty() + { + if (_ubFollowUpAddress != 0) + { + var memoryManager = _channel.MemoryManager; + + Span data = MemoryMarshal.Cast(_ubData.AsSpan(0, (int)(_ubByteCount / 4))); + + if (memoryManager.Physical.WriteWithRedundancyCheck(_ubBeginCpuAddress, data)) + { + memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount); + } + + _ubFollowUpAddress = 0; + _ubIndex = 0; + } + } + + /// + /// Updates the uniform buffer data with inline data. + /// + /// New uniform buffer data word + public void Update(int argument) + { + var uniformBuffer = _state.State.UniformBufferState; + + ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset; + + if (_ubFollowUpAddress != address || _ubIndex == _ubData.Length) + { + FlushUboDirty(); + + _ubByteCount = 0; + _ubBeginCpuAddress = _channel.MemoryManager.Translate(address); + } + + _ubData[_ubIndex++] = argument; + + _ubFollowUpAddress = address + 4; + _ubByteCount += 4; + + _state.State.UniformBufferState.Offset += 4; + } + + /// + /// Updates the uniform buffer data with inline data. + /// + /// Data to be written to the uniform buffer + public void Update(ReadOnlySpan data) + { + var uniformBuffer = _state.State.UniformBufferState; + + ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset; + + ulong size = (ulong)data.Length * 4; + + if (_ubFollowUpAddress != address || _ubIndex + data.Length > _ubData.Length) + { + FlushUboDirty(); + + _ubByteCount = 0; + _ubBeginCpuAddress = _channel.MemoryManager.Translate(address); + } + + data.CopyTo(_ubData.AsSpan(_ubIndex)); + _ubIndex += data.Length; + + _ubFollowUpAddress = address + size; + _ubByteCount += size; + + _state.State.UniformBufferState.Offset += data.Length * 4; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs new file mode 100644 index 00000000..56ef64c6 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs @@ -0,0 +1,986 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Memory.Range; +using System; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// + /// Draw manager. + /// + class DrawManager : IDisposable + { + // Since we don't know the index buffer size for indirect draws, + // we must assume a minimum and maximum size and use that for buffer data update purposes. + private const int MinIndirectIndexCount = 0x10000; + private const int MaxIndirectIndexCount = 0x4000000; + + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly DeviceStateWithShadow _state; + private readonly DrawState _drawState; + private readonly SpecializationStateUpdater _currentSpecState; + private readonly VtgAsCompute _vtgAsCompute; + private bool _topologySet; + + private bool _instancedDrawPending; + private bool _instancedIndexed; + private bool _instancedIndexedInline; + + private int _instancedFirstIndex; + private int _instancedFirstVertex; + private int _instancedFirstInstance; + private int _instancedIndexCount; + private int _instancedDrawStateFirst; + private int _instancedDrawStateCount; + + private int _instanceIndex; + + private const int VertexBufferFirstMethodOffset = 0x35d; + private const int IndexBufferCountMethodOffset = 0x5f8; + + /// + /// Creates a new instance of the draw manager. + /// + /// GPU context + /// GPU channel + /// Channel state + /// Draw state + /// Specialization state updater + public DrawManager(GpuContext context, GpuChannel channel, DeviceStateWithShadow state, DrawState drawState, SpecializationStateUpdater spec) + { + _context = context; + _channel = channel; + _state = state; + _drawState = drawState; + _currentSpecState = spec; + _vtgAsCompute = new(context, channel, state); + } + + /// + /// Marks the entire state as dirty, forcing a full host state update before the next draw. + /// + public void ForceStateDirty() + { + _topologySet = false; + } + + /// + /// Pushes four 8-bit index buffer elements. + /// + /// Method call argument + public void VbElementU8(int argument) + { + _drawState.IbStreamer.VbElementU8(_context.Renderer, argument); + } + + /// + /// Pushes two 16-bit index buffer elements. + /// + /// Method call argument + public void VbElementU16(int argument) + { + _drawState.IbStreamer.VbElementU16(_context.Renderer, argument); + } + + /// + /// Pushes one 32-bit index buffer element. + /// + /// Method call argument + public void VbElementU32(int argument) + { + _drawState.IbStreamer.VbElementU32(_context.Renderer, argument); + } + + /// + /// Finishes the draw call. + /// This draws geometry on the bound buffers based on the current GPU state. + /// + /// 3D engine where this method is being called + /// Method call argument + public void DrawEnd(ThreedClass engine, int argument) + { + _drawState.DrawUsesEngineState = true; + + DrawEnd( + engine, + _state.State.IndexBufferState.First, + (int)_state.State.IndexBufferCount, + _state.State.VertexBufferDrawState.First, + _state.State.VertexBufferDrawState.Count); + } + + /// + /// Finishes the draw call. + /// This draws geometry on the bound buffers based on the current GPU state. + /// + /// 3D engine where this method is being called + /// Index of the first index buffer element used on the draw + /// Number of index buffer elements used on the draw + /// Index of the first vertex used on the draw + /// Number of vertices used on the draw + private void DrawEnd(ThreedClass engine, int firstIndex, int indexCount, int drawFirstVertex, int drawVertexCount) + { + ConditionalRenderEnabled renderEnable = ConditionalRendering.GetRenderEnable( + _context, + _channel.MemoryManager, + _state.State.RenderEnableAddress, + _state.State.RenderEnableCondition); + + if (renderEnable == ConditionalRenderEnabled.False || _instancedDrawPending) + { + if (renderEnable == ConditionalRenderEnabled.False) + { + PerformDeferredDraws(engine); + } + + _drawState.DrawIndexed = false; + + if (renderEnable == ConditionalRenderEnabled.Host) + { + _context.Renderer.Pipeline.EndHostConditionalRendering(); + } + + return; + } + + _drawState.FirstIndex = firstIndex; + _drawState.IndexCount = indexCount; + _drawState.DrawFirstVertex = drawFirstVertex; + _drawState.DrawVertexCount = drawVertexCount; + _currentSpecState.SetHasConstantBufferDrawParameters(false); + + engine.UpdateState(); + + bool instanced = _drawState.VsUsesInstanceId || _drawState.IsAnyVbInstanced; + + if (instanced) + { + _instancedDrawPending = true; + + int ibCount = _drawState.IbStreamer.InlineIndexCount; + + _instancedIndexed = _drawState.DrawIndexed; + _instancedIndexedInline = ibCount != 0; + + _instancedFirstIndex = firstIndex; + _instancedFirstVertex = (int)_state.State.FirstVertex; + _instancedFirstInstance = (int)_state.State.FirstInstance; + + _instancedIndexCount = ibCount != 0 ? ibCount : indexCount; + + _instancedDrawStateFirst = drawFirstVertex; + _instancedDrawStateCount = drawVertexCount; + + _drawState.DrawIndexed = false; + + if (renderEnable == ConditionalRenderEnabled.Host) + { + _context.Renderer.Pipeline.EndHostConditionalRendering(); + } + + return; + } + + int firstInstance = (int)_state.State.FirstInstance; + + int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer); + + if (inlineIndexCount != 0) + { + int firstVertex = (int)_state.State.FirstVertex; + + BufferRange br = new(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4); + + _channel.BufferManager.SetIndexBuffer(br, IndexType.UInt); + + DrawImpl(engine, inlineIndexCount, 1, firstIndex, firstVertex, firstInstance, indexed: true); + } + else if (_drawState.DrawIndexed) + { + int firstVertex = (int)_state.State.FirstVertex; + + DrawImpl(engine, indexCount, 1, firstIndex, firstVertex, firstInstance, indexed: true); + } + else + { + DrawImpl(engine, drawVertexCount, 1, 0, drawFirstVertex, firstInstance, indexed: false); + } + + _drawState.DrawIndexed = false; + + if (renderEnable == ConditionalRenderEnabled.Host) + { + _context.Renderer.Pipeline.EndHostConditionalRendering(); + } + } + + /// + /// Starts draw. + /// This sets primitive type and instanced draw parameters. + /// + /// 3D engine where this method is being called + /// Method call argument + public void DrawBegin(ThreedClass engine, int argument) + { + bool incrementInstance = (argument & (1 << 26)) != 0; + bool resetInstance = (argument & (1 << 27)) == 0; + + PrimitiveType type = (PrimitiveType)(argument & 0xffff); + DrawBegin(engine, incrementInstance, resetInstance, type); + } + + /// + /// Starts draw. + /// This sets primitive type and instanced draw parameters. + /// + /// 3D engine where this method is being called + /// Indicates if the current instance should be incremented + /// Indicates if the current instance should be set to zero + /// Primitive type + private void DrawBegin(ThreedClass engine, bool incrementInstance, bool resetInstance, PrimitiveType primitiveType) + { + if (incrementInstance) + { + _instanceIndex++; + } + else if (resetInstance) + { + PerformDeferredDraws(engine); + + _instanceIndex = 0; + } + + PrimitiveTopology topology; + + if (_state.State.PrimitiveTypeOverrideEnable) + { + PrimitiveTypeOverride typeOverride = _state.State.PrimitiveTypeOverride; + topology = typeOverride.Convert(); + } + else + { + topology = primitiveType.Convert(); + } + + UpdateTopology(topology); + } + + /// + /// Updates the current primitive topology if needed. + /// + /// New primitive topology + private void UpdateTopology(PrimitiveTopology topology) + { + if (_drawState.Topology != topology || !_topologySet) + { + _context.Renderer.Pipeline.SetPrimitiveTopology(topology); + _currentSpecState.SetTopology(topology); + _drawState.Topology = topology; + _topologySet = true; + } + } + + /// + /// Sets the index buffer count. + /// This also sets internal state that indicates that the next draw is an indexed draw. + /// + /// Method call argument + public void SetIndexBufferCount(int argument) + { + _drawState.DrawIndexed = true; + } + + // TODO: Verify if the index type is implied from the method that is called, + // or if it uses the state index type on hardware. + + /// + /// Performs a indexed draw with 8-bit index buffer elements. + /// + /// 3D engine where this method is being called + /// Method call argument + public void DrawIndexBuffer8BeginEndInstanceFirst(ThreedClass engine, int argument) + { + DrawIndexBufferBeginEndInstance(engine, argument, false); + } + + /// + /// Performs a indexed draw with 16-bit index buffer elements. + /// + /// 3D engine where this method is being called + /// Method call argument + public void DrawIndexBuffer16BeginEndInstanceFirst(ThreedClass engine, int argument) + { + DrawIndexBufferBeginEndInstance(engine, argument, false); + } + + /// + /// Performs a indexed draw with 32-bit index buffer elements. + /// + /// 3D engine where this method is being called + /// Method call argument + public void DrawIndexBuffer32BeginEndInstanceFirst(ThreedClass engine, int argument) + { + DrawIndexBufferBeginEndInstance(engine, argument, false); + } + + /// + /// Performs a indexed draw with 8-bit index buffer elements, + /// while also pre-incrementing the current instance value. + /// + /// 3D engine where this method is being called + /// Method call argument + public void DrawIndexBuffer8BeginEndInstanceSubsequent(ThreedClass engine, int argument) + { + DrawIndexBufferBeginEndInstance(engine, argument, true); + } + + /// + /// Performs a indexed draw with 16-bit index buffer elements, + /// while also pre-incrementing the current instance value. + /// + /// 3D engine where this method is being called + /// Method call argument + public void DrawIndexBuffer16BeginEndInstanceSubsequent(ThreedClass engine, int argument) + { + DrawIndexBufferBeginEndInstance(engine, argument, true); + } + + /// + /// Performs a indexed draw with 32-bit index buffer elements, + /// while also pre-incrementing the current instance value. + /// + /// 3D engine where this method is being called + /// Method call argument + public void DrawIndexBuffer32BeginEndInstanceSubsequent(ThreedClass engine, int argument) + { + DrawIndexBufferBeginEndInstance(engine, argument, true); + } + + /// + /// Performs a indexed draw with a low number of index buffer elements, + /// while optionally also pre-incrementing the current instance value. + /// + /// 3D engine where this method is being called + /// Method call argument + /// True to increment the current instance value, false otherwise + private void DrawIndexBufferBeginEndInstance(ThreedClass engine, int argument, bool instanced) + { + DrawBegin(engine, instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf)); + + int firstIndex = argument & 0xffff; + int indexCount = (argument >> 16) & 0xfff; + + bool oldDrawIndexed = _drawState.DrawIndexed; + + _drawState.DrawIndexed = true; + _drawState.DrawUsesEngineState = false; + engine.ForceStateDirty(IndexBufferCountMethodOffset * 4); + + DrawEnd(engine, firstIndex, indexCount, 0, 0); + + _drawState.DrawIndexed = oldDrawIndexed; + } + + /// + /// Performs a non-indexed draw with the specified topology, index and count. + /// + /// 3D engine where this method is being called + /// Method call argument + public void DrawVertexArrayBeginEndInstanceFirst(ThreedClass engine, int argument) + { + DrawVertexArrayBeginEndInstance(engine, argument, false); + } + + /// + /// Performs a non-indexed draw with the specified topology, index and count, + /// while incrementing the current instance. + /// + /// 3D engine where this method is being called + /// Method call argument + public void DrawVertexArrayBeginEndInstanceSubsequent(ThreedClass engine, int argument) + { + DrawVertexArrayBeginEndInstance(engine, argument, true); + } + + /// + /// Performs a indexed draw with a low number of index buffer elements, + /// while optionally also pre-incrementing the current instance value. + /// + /// 3D engine where this method is being called + /// Method call argument + /// True to increment the current instance value, false otherwise + private void DrawVertexArrayBeginEndInstance(ThreedClass engine, int argument, bool instanced) + { + DrawBegin(engine, instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf)); + + int firstVertex = argument & 0xffff; + int vertexCount = (argument >> 16) & 0xfff; + + bool oldDrawIndexed = _drawState.DrawIndexed; + + _drawState.DrawIndexed = false; + _drawState.DrawUsesEngineState = false; + engine.ForceStateDirty(VertexBufferFirstMethodOffset * 4); + + DrawEnd(engine, 0, 0, firstVertex, vertexCount); + + _drawState.DrawIndexed = oldDrawIndexed; + } + + /// + /// Performs a texture draw with a source texture and sampler ID, along with source + /// and destination coordinates and sizes. + /// + /// 3D engine where this method is being called + /// Method call argument + public void DrawTexture(ThreedClass engine, int argument) + { + static float FixedToFloat(int fixedValue) + { + return fixedValue * (1f / 4096); + } + + float dstX0 = FixedToFloat(_state.State.DrawTextureDstX); + float dstY0 = FixedToFloat(_state.State.DrawTextureDstY); + float dstWidth = FixedToFloat(_state.State.DrawTextureDstWidth); + float dstHeight = FixedToFloat(_state.State.DrawTextureDstHeight); + + // TODO: Confirm behaviour on hardware. + // When this is active, the origin appears to be on the bottom. + if (_state.State.YControl.HasFlag(YControl.NegateY)) + { + dstY0 -= dstHeight; + } + + float dstX1 = dstX0 + dstWidth; + float dstY1 = dstY0 + dstHeight; + + float srcX0 = FixedToFloat(_state.State.DrawTextureSrcX); + float srcY0 = FixedToFloat(_state.State.DrawTextureSrcY); + float srcX1 = ((float)_state.State.DrawTextureDuDx / (1UL << 32)) * dstWidth + srcX0; + float srcY1 = ((float)_state.State.DrawTextureDvDy / (1UL << 32)) * dstHeight + srcY0; + + engine.UpdateState(ulong.MaxValue & ~(1UL << StateUpdater.ShaderStateIndex)); + + _channel.TextureManager.UpdateRenderTargets(); + + int textureId = _state.State.DrawTextureTextureId; + int samplerId = _state.State.DrawTextureSamplerId; + + (var texture, var sampler) = _channel.TextureManager.GetGraphicsTextureAndSampler(textureId, samplerId); + + srcX0 *= texture.ScaleFactor; + srcY0 *= texture.ScaleFactor; + srcX1 *= texture.ScaleFactor; + srcY1 *= texture.ScaleFactor; + + float dstScale = _channel.TextureManager.RenderTargetScale; + + dstX0 *= dstScale; + dstY0 *= dstScale; + dstX1 *= dstScale; + dstY1 *= dstScale; + + _context.Renderer.Pipeline.DrawTexture( + texture?.HostTexture, + sampler?.GetHostSampler(texture), + new Extents2DF(srcX0, srcY0, srcX1, srcY1), + new Extents2DF(dstX0, dstY0, dstX1, dstY1)); + } + + /// + /// Performs a indexed or non-indexed draw. + /// + /// 3D engine where this method is being called + /// Primitive topology + /// Index count for indexed draws, vertex count for non-indexed draws + /// Instance count + /// First index on the index buffer for indexed draws, ignored for non-indexed draws + /// First vertex on the vertex buffer + /// First instance + /// True if the draw is indexed, false otherwise + public void Draw( + ThreedClass engine, + PrimitiveTopology topology, + int count, + int instanceCount, + int firstIndex, + int firstVertex, + int firstInstance, + bool indexed) + { + UpdateTopology(topology); + + ConditionalRenderEnabled renderEnable = ConditionalRendering.GetRenderEnable( + _context, + _channel.MemoryManager, + _state.State.RenderEnableAddress, + _state.State.RenderEnableCondition); + + if (renderEnable == ConditionalRenderEnabled.False) + { + _drawState.DrawIndexed = false; + return; + } + + if (indexed) + { + _drawState.FirstIndex = firstIndex; + _drawState.IndexCount = count; + _state.State.FirstVertex = (uint)firstVertex; + engine.ForceStateDirty(IndexBufferCountMethodOffset * 4); + } + else + { + _drawState.DrawFirstVertex = firstVertex; + _drawState.DrawVertexCount = count; + engine.ForceStateDirty(VertexBufferFirstMethodOffset * 4); + } + + _state.State.FirstInstance = (uint)firstInstance; + + _drawState.DrawIndexed = indexed; + _drawState.DrawUsesEngineState = true; + _currentSpecState.SetHasConstantBufferDrawParameters(true); + + engine.UpdateState(); + + DrawImpl(engine, count, instanceCount, firstIndex, firstVertex, firstInstance, indexed); + + if (indexed) + { + _state.State.FirstVertex = 0; + } + + _state.State.FirstInstance = 0; + + _drawState.DrawIndexed = false; + + if (renderEnable == ConditionalRenderEnabled.Host) + { + _context.Renderer.Pipeline.EndHostConditionalRendering(); + } + } + + /// + /// Performs a indexed or non-indexed draw. + /// + /// 3D engine where this method is being called + /// Index count for indexed draws, vertex count for non-indexed draws + /// Instance count + /// First index on the index buffer for indexed draws, ignored for non-indexed draws + /// First vertex on the vertex buffer + /// First instance + /// True if the draw is indexed, false otherwise + private void DrawImpl( + ThreedClass engine, + int count, + int instanceCount, + int firstIndex, + int firstVertex, + int firstInstance, + bool indexed) + { + if (instanceCount > 1) + { + _channel.BufferManager.SetInstancedDrawVertexCount(count); + } + + if (_drawState.VertexAsCompute != null) + { + _vtgAsCompute.DrawAsCompute( + engine, + _drawState.VertexAsCompute, + _drawState.GeometryAsCompute, + _drawState.VertexPassthrough, + _drawState.Topology, + count, + instanceCount, + firstIndex, + firstVertex, + firstInstance, + indexed); + + if (_drawState.GeometryAsCompute != null) + { + // Geometry draws need to change the topology, so we need to set it here again + // if we are going to do a regular draw. + // Would have been better to do that on the callee, but doing it here + // avoids having to pass the draw manager instance. + ForceStateDirty(); + } + } + else + { + if (indexed) + { + _context.Renderer.Pipeline.DrawIndexed(count, instanceCount, firstIndex, firstVertex, firstInstance); + } + else + { + _context.Renderer.Pipeline.Draw(count, instanceCount, firstVertex, firstInstance); + } + } + } + + /// + /// Performs a indirect draw, with parameters from a GPU buffer. + /// + /// 3D engine where this method is being called + /// Primitive topology + /// Memory range of the buffer with the draw parameters, such as count, first index, etc + /// Memory range of the buffer with the draw count + /// Maximum number of draws that can be made + /// Distance in bytes between each entry on the data pointed to by + /// Maximum number of indices that the draw can consume + /// Type of the indirect draw, which can be indexed or non-indexed, with or without a draw count + public void DrawIndirect( + ThreedClass engine, + PrimitiveTopology topology, + MultiRange indirectBufferRange, + MultiRange parameterBufferRange, + int maxDrawCount, + int stride, + int indexCount, + IndirectDrawType drawType) + { + UpdateTopology(topology); + + ConditionalRenderEnabled renderEnable = ConditionalRendering.GetRenderEnable( + _context, + _channel.MemoryManager, + _state.State.RenderEnableAddress, + _state.State.RenderEnableCondition); + + if (renderEnable == ConditionalRenderEnabled.False) + { + _drawState.DrawIndexed = false; + return; + } + + PhysicalMemory memory = _channel.MemoryManager.Physical; + + bool hasCount = (drawType & IndirectDrawType.Count) != 0; + bool indexed = (drawType & IndirectDrawType.Indexed) != 0; + + if (indexed) + { + indexCount = Math.Clamp(indexCount, MinIndirectIndexCount, MaxIndirectIndexCount); + _drawState.FirstIndex = 0; + _drawState.IndexCount = indexCount; + engine.ForceStateDirty(IndexBufferCountMethodOffset * 4); + } + + _drawState.DrawIndexed = indexed; + _drawState.DrawIndirect = true; + _drawState.DrawUsesEngineState = true; + _currentSpecState.SetHasConstantBufferDrawParameters(true); + + engine.UpdateState(); + + if (hasCount) + { + var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect); + var parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange, BufferStage.Indirect); + + if (indexed) + { + _context.Renderer.Pipeline.DrawIndexedIndirectCount(indirectBuffer, parameterBuffer, maxDrawCount, stride); + } + else + { + _context.Renderer.Pipeline.DrawIndirectCount(indirectBuffer, parameterBuffer, maxDrawCount, stride); + } + } + else + { + var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect); + + if (indexed) + { + _context.Renderer.Pipeline.DrawIndexedIndirect(indirectBuffer); + } + else + { + _context.Renderer.Pipeline.DrawIndirect(indirectBuffer); + } + } + + _drawState.DrawIndexed = false; + _drawState.DrawIndirect = false; + + if (renderEnable == ConditionalRenderEnabled.Host) + { + _context.Renderer.Pipeline.EndHostConditionalRendering(); + } + } + + /// + /// Perform any deferred draws. + /// This is used for instanced draws. + /// Since each instance is a separate draw, we defer the draw and accumulate the instance count. + /// Once we detect the last instanced draw, then we perform the host instanced draw, + /// with the accumulated instance count. + /// + /// 3D engine where this method is being called + public void PerformDeferredDraws(ThreedClass engine) + { + // Perform any pending instanced draw. + if (_instancedDrawPending) + { + _instancedDrawPending = false; + + int instanceCount = _instanceIndex + 1; + int firstInstance = _instancedFirstInstance; + bool indexedInline = _instancedIndexedInline; + + if (_instancedIndexed || indexedInline) + { + int indexCount = _instancedIndexCount; + + if (indexedInline) + { + int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer); + BufferRange br = new(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4); + + _channel.BufferManager.SetIndexBuffer(br, IndexType.UInt); + indexCount = inlineIndexCount; + } + + int firstIndex = _instancedFirstIndex; + int firstVertex = _instancedFirstVertex; + + DrawImpl(engine, indexCount, instanceCount, firstIndex, firstVertex, firstInstance, indexed: true); + } + else + { + int vertexCount = _instancedDrawStateCount; + int firstVertex = _instancedDrawStateFirst; + + DrawImpl(engine, vertexCount, instanceCount, 0, firstVertex, firstInstance, indexed: false); + } + } + } + + /// + /// Clears the current color and depth-stencil buffers. + /// Which buffers should be cleared can also be specified with the argument. + /// + /// 3D engine where this method is being called + /// Method call argument + public void Clear(ThreedClass engine, int argument) + { + Clear(engine, argument, 1); + } + + /// + /// Clears the current color and depth-stencil buffers. + /// Which buffers should be cleared can also specified with the arguments. + /// + /// 3D engine where this method is being called + /// Method call argument + /// For array and 3D textures, indicates how many layers should be cleared + public void Clear(ThreedClass engine, int argument, int layerCount) + { + ConditionalRenderEnabled renderEnable = ConditionalRendering.GetRenderEnable( + _context, + _channel.MemoryManager, + _state.State.RenderEnableAddress, + _state.State.RenderEnableCondition); + + if (renderEnable == ConditionalRenderEnabled.False) + { + return; + } + + bool clearDepth = (argument & 1) != 0; + bool clearStencil = (argument & 2) != 0; + uint componentMask = (uint)((argument >> 2) & 0xf); + int index = (argument >> 6) & 0xf; + int layer = (argument >> 10) & 0x3ff; + + RenderTargetUpdateFlags updateFlags = RenderTargetUpdateFlags.SingleColor; + + if (layer != 0 || layerCount > 1) + { + updateFlags |= RenderTargetUpdateFlags.Layered; + } + + bool clearDS = clearDepth || clearStencil; + + if (clearDS) + { + updateFlags |= RenderTargetUpdateFlags.UpdateDepthStencil; + } + + // If there is a mismatch on the host clip region and the one explicitly defined by the guest + // on the screen scissor state, then we need to force only one texture to be bound to avoid + // host clipping. + var screenScissorState = _state.State.ScreenScissorState; + + bool clearAffectedByStencilMask = (_state.State.ClearFlags & 1) != 0; + bool clearAffectedByScissor = (_state.State.ClearFlags & 0x100) != 0; + + if (clearDS || componentMask == 15) + { + // A full clear if scissor is disabled, or it matches the screen scissor state. + + bool fullClear = screenScissorState.X == 0 && screenScissorState.Y == 0; + + if (fullClear && clearAffectedByScissor && _state.State.ScissorState[0].Enable) + { + ref var scissorState = ref _state.State.ScissorState[0]; + + fullClear = scissorState.X1 == screenScissorState.X && + scissorState.Y1 == screenScissorState.Y && + scissorState.X2 >= screenScissorState.X + screenScissorState.Width && + scissorState.Y2 >= screenScissorState.Y + screenScissorState.Height; + } + + if (fullClear && clearDS) + { + // Must clear all aspects of the depth-stencil format. + + FormatInfo dsFormat = _state.State.RtDepthStencilState.Format.Convert(); + + bool hasDepth = dsFormat.Format.HasDepth(); + bool hasStencil = dsFormat.Format.HasStencil(); + + if (hasStencil && (!clearStencil || (clearAffectedByStencilMask && _state.State.StencilTestState.FrontMask != 0xff))) + { + fullClear = false; + } + else if (hasDepth && !clearDepth) + { + fullClear = false; + } + } + + if (fullClear) + { + updateFlags |= RenderTargetUpdateFlags.DiscardClip; + } + } + + engine.UpdateRenderTargetState(updateFlags, singleUse: componentMask != 0 ? index : -1); + + // Must happen after UpdateRenderTargetState to have up-to-date clip region values. + bool clipMismatch = (screenScissorState.X | screenScissorState.Y) != 0 || + screenScissorState.Width != _channel.TextureManager.ClipRegionWidth || + screenScissorState.Height != _channel.TextureManager.ClipRegionHeight; + + bool needsCustomScissor = !clearAffectedByScissor || clipMismatch; + + // Scissor and rasterizer discard also affect clears. + ulong updateMask = 1UL << StateUpdater.RasterizerStateIndex; + + if (!needsCustomScissor) + { + updateMask |= 1UL << StateUpdater.ScissorStateIndex; + } + + engine.UpdateState(updateMask); + + if (needsCustomScissor) + { + int scissorX = screenScissorState.X; + int scissorY = screenScissorState.Y; + int scissorW = screenScissorState.Width; + int scissorH = screenScissorState.Height; + + if (clearAffectedByScissor && _state.State.ScissorState[0].Enable) + { + ref var scissorState = ref _state.State.ScissorState[0]; + + scissorX = Math.Max(scissorX, scissorState.X1); + scissorY = Math.Max(scissorY, scissorState.Y1); + scissorW = Math.Min(scissorW, scissorState.X2 - scissorState.X1); + scissorH = Math.Min(scissorH, scissorState.Y2 - scissorState.Y1); + } + + float scale = _channel.TextureManager.RenderTargetScale; + if (scale != 1f) + { + scissorX = (int)(scissorX * scale); + scissorY = (int)(scissorY * scale); + scissorW = (int)MathF.Ceiling(scissorW * scale); + scissorH = (int)MathF.Ceiling(scissorH * scale); + } + + Span> scissors = stackalloc Rectangle[] + { + new Rectangle(scissorX, scissorY, scissorW, scissorH), + }; + + _context.Renderer.Pipeline.SetScissors(scissors); + } + + _channel.TextureManager.UpdateRenderTargets(); + + if (componentMask != 0) + { + var clearColor = _state.State.ClearColors; + + ColorF color = new(clearColor.Red, clearColor.Green, clearColor.Blue, clearColor.Alpha); + + _context.Renderer.Pipeline.ClearRenderTargetColor(index, layer, layerCount, componentMask, color); + } + + if (clearDepth || clearStencil) + { + float depthValue = _state.State.ClearDepthValue; + int stencilValue = (int)_state.State.ClearStencilValue; + + int stencilMask = 0; + + if (clearStencil) + { + stencilMask = clearAffectedByStencilMask ? _state.State.StencilTestState.FrontMask : 0xff; + } + + if (clipMismatch) + { + _channel.TextureManager.UpdateRenderTargetDepthStencil(); + } + + _context.Renderer.Pipeline.ClearRenderTargetDepthStencil( + layer, + layerCount, + depthValue, + clearDepth, + stencilValue, + stencilMask); + } + + if (needsCustomScissor) + { + engine.UpdateScissorState(); + } + + engine.UpdateRenderTargetState(RenderTargetUpdateFlags.UpdateAll); + + if (renderEnable == ConditionalRenderEnabled.Host) + { + _context.Renderer.Pipeline.EndHostConditionalRendering(); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _vtgAsCompute.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs new file mode 100644 index 00000000..03b5e3f3 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs @@ -0,0 +1,81 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Shader; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// + /// Draw state. + /// + class DrawState + { + /// + /// First index to be used for the draw on the index buffer. + /// + public int FirstIndex; + + /// + /// Number of indices to be used for the draw on the index buffer. + /// + public int IndexCount; + + /// + /// First vertex used on non-indexed draws. This value is stored somewhere else on indexed draws. + /// + public int DrawFirstVertex; + + /// + /// Vertex count used on non-indexed draws. Indexed draws have a index count instead. + /// + public int DrawVertexCount; + + /// + /// Indicates if the next draw will be a indexed draw. + /// + public bool DrawIndexed; + + /// + /// Indicates if the next draw will be a indirect draw. + /// + public bool DrawIndirect; + + /// + /// Indicates that the draw is using the draw parameters on the 3D engine state, rather than inline parameters submitted with the draw command. + /// + public bool DrawUsesEngineState; + + /// + /// Indicates if any of the currently used vertex shaders reads the instance ID. + /// + public bool VsUsesInstanceId; + + /// + /// Indicates if any of the currently used vertex buffers is instanced. + /// + public bool IsAnyVbInstanced; + + /// + /// Primitive topology for the next draw. + /// + public PrimitiveTopology Topology; + + /// + /// Index buffer data streamer for inline index buffer updates, such as those used in legacy OpenGL. + /// + public IbStreamer IbStreamer = new(); + + /// + /// If the vertex shader is emulated on compute, this should be set to the compute program, otherwise it should be null. + /// + public ShaderAsCompute VertexAsCompute; + + /// + /// If a geometry shader exists and is emulated on compute, this should be set to the compute program, otherwise it should be null. + /// + public ShaderAsCompute GeometryAsCompute; + + /// + /// If the vertex shader is emulated on compute, this should be set to the passthrough vertex program, otherwise it should be null. + /// + public IProgram VertexPassthrough; + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/IbStreamer.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/IbStreamer.cs new file mode 100644 index 00000000..739d3223 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/IbStreamer.cs @@ -0,0 +1,192 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// + /// Holds inline index buffer state. + /// The inline index buffer data is sent to the GPU through the command buffer. + /// + struct IbStreamer + { + private const int BufferCapacity = 256; // Must be a power of 2. + + private BufferHandle _inlineIndexBuffer; + private int _inlineIndexBufferSize; + private int _inlineIndexCount; + private uint[] _buffer; +#pragma warning disable IDE0051 // Remove unused private member + private readonly int _bufferOffset; +#pragma warning restore IDE0051 + + /// + /// Indicates if any index buffer data has been pushed. + /// + public readonly bool HasInlineIndexData => _inlineIndexCount != 0; + + /// + /// Total numbers of indices that have been pushed. + /// + public readonly int InlineIndexCount => _inlineIndexCount; + + /// + /// Gets the handle for the host buffer currently holding the inline index buffer data. + /// + /// Host buffer handle + public readonly BufferHandle GetInlineIndexBuffer() + { + return _inlineIndexBuffer; + } + + /// + /// Gets the number of elements on the current inline index buffer, + /// while also resetting it to zero for the next draw. + /// + /// Host renderer + /// Inline index buffer count + public int GetAndResetInlineIndexCount(IRenderer renderer) + { + UpdateRemaining(renderer); + int temp = _inlineIndexCount; + _inlineIndexCount = 0; + return temp; + } + + /// + /// Pushes four 8-bit index buffer elements. + /// + /// Host renderer + /// Method call argument + public void VbElementU8(IRenderer renderer, int argument) + { + byte i0 = (byte)argument; + byte i1 = (byte)(argument >> 8); + byte i2 = (byte)(argument >> 16); + byte i3 = (byte)(argument >> 24); + + int offset = _inlineIndexCount; + + PushData(renderer, offset, i0); + PushData(renderer, offset + 1, i1); + PushData(renderer, offset + 2, i2); + PushData(renderer, offset + 3, i3); + + _inlineIndexCount += 4; + } + + /// + /// Pushes two 16-bit index buffer elements. + /// + /// Host renderer + /// Method call argument + public void VbElementU16(IRenderer renderer, int argument) + { + ushort i0 = (ushort)argument; + ushort i1 = (ushort)(argument >> 16); + + int offset = _inlineIndexCount; + + PushData(renderer, offset, i0); + PushData(renderer, offset + 1, i1); + + _inlineIndexCount += 2; + } + + /// + /// Pushes one 32-bit index buffer element. + /// + /// Host renderer + /// Method call argument + public void VbElementU32(IRenderer renderer, int argument) + { + uint i0 = (uint)argument; + + int offset = _inlineIndexCount++; + + PushData(renderer, offset, i0); + } + + /// + /// Pushes a 32-bit value to the index buffer. + /// + /// Host renderer + /// Offset where the data should be written, in 32-bit words + /// Index value to be written + private void PushData(IRenderer renderer, int offset, uint value) + { + _buffer ??= new uint[BufferCapacity]; + + // We upload data in chunks. + // If we are at the start of a chunk, then the buffer might be full, + // in that case we need to submit any existing data before overwriting the buffer. + int subOffset = offset & (BufferCapacity - 1); + + if (subOffset == 0 && offset != 0) + { + int baseOffset = (offset - BufferCapacity) * sizeof(uint); + BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, BufferCapacity * sizeof(uint)); + renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast(_buffer)); + } + + _buffer[subOffset] = value; + } + + /// + /// Makes sure that any pending data is submitted to the GPU before the index buffer is used. + /// + /// Host renderer + private void UpdateRemaining(IRenderer renderer) + { + int offset = _inlineIndexCount; + if (offset == 0) + { + return; + } + + int count = offset & (BufferCapacity - 1); + if (count == 0) + { + count = BufferCapacity; + } + + int baseOffset = (offset - count) * sizeof(uint); + int length = count * sizeof(uint); + BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, length); + renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast(_buffer)[..length]); + } + + /// + /// Gets the handle of a buffer large enough to hold the data that will be written to . + /// + /// Host renderer + /// Offset where the data will be written + /// Number of bytes that will be written + /// Buffer handle + private BufferHandle GetInlineIndexBuffer(IRenderer renderer, int offset, int length) + { + // Calculate a reasonable size for the buffer that can fit all the data, + // and that also won't require frequent resizes if we need to push more data. + int size = BitUtils.AlignUp(offset + length + 0x10, 0x200); + + if (_inlineIndexBuffer == BufferHandle.Null) + { + _inlineIndexBuffer = renderer.CreateBuffer(size, BufferAccess.Stream); + _inlineIndexBufferSize = size; + } + else if (_inlineIndexBufferSize < size) + { + BufferHandle oldBuffer = _inlineIndexBuffer; + int oldSize = _inlineIndexBufferSize; + + _inlineIndexBuffer = renderer.CreateBuffer(size, BufferAccess.Stream); + _inlineIndexBufferSize = size; + + renderer.Pipeline.CopyBuffer(oldBuffer, _inlineIndexBuffer, 0, 0, oldSize); + renderer.DeleteBuffer(oldBuffer); + } + + return _inlineIndexBuffer; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/IndirectDrawType.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/IndirectDrawType.cs new file mode 100644 index 00000000..331b1976 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/IndirectDrawType.cs @@ -0,0 +1,41 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// + /// Indirect draw type, which can be indexed or non-indexed, with or without a draw count. + /// + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + enum IndirectDrawType + { + /// + /// Non-indexed draw without draw count. + /// + DrawIndirect = 0, + + /// + /// Indexed draw without draw count. + /// + DrawIndexedIndirect = Indexed, + + /// + /// Non-indexed draw with draw count. + /// + DrawIndirectCount = Count, + + /// + /// Indexed draw with draw count. + /// + DrawIndexedIndirectCount = Indexed | Count, + + /// + /// Indexed flag. + /// + Indexed = 1 << 0, + + /// + /// Draw count flag. + /// + Count = 1 << 1, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/RenderTargetUpdateFlags.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/RenderTargetUpdateFlags.cs new file mode 100644 index 00000000..58c7bdb4 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/RenderTargetUpdateFlags.cs @@ -0,0 +1,46 @@ +using System; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// + /// Flags indicating how the render targets should be updated. + /// + [Flags] + enum RenderTargetUpdateFlags + { + /// + /// No flags. + /// + None = 0, + + /// + /// Get render target index from the control register. + /// + UseControl = 1 << 0, + + /// + /// Indicates that all render targets are 2D array textures. + /// + Layered = 1 << 1, + + /// + /// Indicates that only a single color target will be used. + /// + SingleColor = 1 << 2, + + /// + /// Indicates that the depth-stencil target will be used. + /// + UpdateDepthStencil = 1 << 3, + + /// + /// Indicates that the data in the clip region can be discarded for the next use. + /// + DiscardClip = 1 << 4, + + /// + /// Default update flags for draw. + /// + UpdateAll = UseControl | UpdateDepthStencil, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs new file mode 100644 index 00000000..a9a96d54 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs @@ -0,0 +1,195 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// + /// Semaphore updater. + /// + class SemaphoreUpdater + { + /// + /// GPU semaphore operation. + /// + private enum SemaphoreOperation + { + Release = 0, + Acquire = 1, + Counter = 2, + } + + /// + /// Counter type for GPU counter reset. + /// + private enum ResetCounterType + { + SamplesPassed = 1, + ZcullStats = 2, + TransformFeedbackPrimitivesWritten = 0x10, + InputVertices = 0x12, + InputPrimitives = 0x13, + VertexShaderInvocations = 0x15, + TessControlShaderInvocations = 0x16, + TessEvaluationShaderInvocations = 0x17, + TessEvaluationShaderPrimitives = 0x18, + GeometryShaderInvocations = 0x1a, + GeometryShaderPrimitives = 0x1b, + ClipperInputPrimitives = 0x1c, + ClipperOutputPrimitives = 0x1d, + FragmentShaderInvocations = 0x1e, + PrimitivesGenerated = 0x1f, + } + + /// + /// Counter type for GPU counter reporting. + /// + private enum ReportCounterType + { + Payload = 0, + InputVertices = 1, + InputPrimitives = 3, + VertexShaderInvocations = 5, + GeometryShaderInvocations = 7, + GeometryShaderPrimitives = 9, + ZcullStats0 = 0xa, + TransformFeedbackPrimitivesWritten = 0xb, + ZcullStats1 = 0xc, + ZcullStats2 = 0xe, + ClipperInputPrimitives = 0xf, + ZcullStats3 = 0x10, + ClipperOutputPrimitives = 0x11, + PrimitivesGenerated = 0x12, + FragmentShaderInvocations = 0x13, + SamplesPassed = 0x15, + TransformFeedbackOffset = 0x1a, + TessControlShaderInvocations = 0x1b, + TessEvaluationShaderInvocations = 0x1d, + TessEvaluationShaderPrimitives = 0x1f, + } + + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly DeviceStateWithShadow _state; + + /// + /// Creates a new instance of the semaphore updater. + /// + /// GPU context + /// GPU channel + /// Channel state + public SemaphoreUpdater(GpuContext context, GpuChannel channel, DeviceStateWithShadow state) + { + _context = context; + _channel = channel; + _state = state; + } + + /// + /// Resets the value of an internal GPU counter back to zero. + /// + /// Method call argument + public void ResetCounter(int argument) + { + ResetCounterType type = (ResetCounterType)argument; + + switch (type) + { + case ResetCounterType.SamplesPassed: + _context.Renderer.ResetCounter(CounterType.SamplesPassed); + break; + case ResetCounterType.PrimitivesGenerated: + _context.Renderer.ResetCounter(CounterType.PrimitivesGenerated); + break; + case ResetCounterType.TransformFeedbackPrimitivesWritten: + _context.Renderer.ResetCounter(CounterType.TransformFeedbackPrimitivesWritten); + break; + } + } + + /// + /// Writes a GPU counter to guest memory. + /// + /// Method call argument + public void Report(int argument) + { + SemaphoreOperation op = (SemaphoreOperation)(argument & 3); + ReportCounterType type = (ReportCounterType)((argument >> 23) & 0x1f); + + switch (op) + { + case SemaphoreOperation.Release: + ReleaseSemaphore(); + break; + case SemaphoreOperation.Counter: + ReportCounter(type); + break; + } + } + + /// + /// Writes (or Releases) a GPU semaphore value to guest memory. + /// + private void ReleaseSemaphore() + { + _channel.MemoryManager.Write(_state.State.SemaphoreAddress.Pack(), _state.State.SemaphorePayload); + + _context.AdvanceSequence(); + } + + /// + /// Packed GPU counter data (including GPU timestamp) in memory. + /// + private struct CounterData + { + public ulong Counter; + public ulong Timestamp; + } + + /// + /// Writes a GPU counter to guest memory. + /// This also writes the current timestamp value. + /// + /// Counter to be written to memory + private void ReportCounter(ReportCounterType type) + { + ulong gpuVa = _state.State.SemaphoreAddress.Pack(); + + ulong ticks = _context.GetTimestamp(); + + ICounterEvent counter = null; + + void resultHandler(object evt, ulong result) + { + CounterData counterData = new() + { + Counter = result, + Timestamp = ticks, + }; + + if (counter?.Invalid != true) + { + _channel.MemoryManager.Write(gpuVa, counterData); + } + } + + switch (type) + { + case ReportCounterType.Payload: + resultHandler(null, (ulong)_state.State.SemaphorePayload); + break; + case ReportCounterType.SamplesPassed: + float scale = _channel.TextureManager.RenderTargetScale; + float divisor = scale * scale; + counter = _context.Renderer.ReportCounter(CounterType.SamplesPassed, resultHandler, divisor, false); + break; + case ReportCounterType.PrimitivesGenerated: + counter = _context.Renderer.ReportCounter(CounterType.PrimitivesGenerated, resultHandler, 1f, false); + break; + case ReportCounterType.TransformFeedbackPrimitivesWritten: + counter = _context.Renderer.ReportCounter(CounterType.TransformFeedbackPrimitivesWritten, resultHandler, 1f, false); + break; + } + + _channel.MemoryManager.CounterCache.AddOrUpdate(gpuVa, counter); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs new file mode 100644 index 00000000..dbd4efc8 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs @@ -0,0 +1,391 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Shader; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// + /// Maintains a "current" specialiation state, and provides a flag to check if it has changed meaningfully. + /// + internal class SpecializationStateUpdater + { + private readonly GpuContext _context; + private GpuChannelGraphicsState _graphics; + private GpuChannelPoolState _pool; + + private bool _usesDrawParameters; + private bool _usesTopology; + + private bool _changed; + + /// + /// Creates a new instance of the specialization state updater class. + /// + /// GPU context + public SpecializationStateUpdater(GpuContext context) + { + _context = context; + } + + /// + /// Signal that the specialization state has changed. + /// + private void Signal() + { + _changed = true; + } + + /// + /// Checks if the specialization state has changed since the last check. + /// + /// True if it has changed, false otherwise + public bool HasChanged() + { + if (_changed) + { + _changed = false; + return true; + } + else + { + return false; + } + } + + /// + /// Sets the active shader, clearing the dirty state and recording if certain specializations are noteworthy. + /// + /// The active shader + public void SetShader(CachedShaderProgram gs) + { + _usesDrawParameters = gs.Shaders[1]?.Info.UsesDrawParameters ?? false; + _usesTopology = gs.SpecializationState.IsPrimitiveTopologyQueried(); + + _changed = false; + } + + /// + /// Get the current graphics state. + /// + /// GPU graphics state + public ref GpuChannelGraphicsState GetGraphicsState() + { + return ref _graphics; + } + + /// + /// Get the current pool state. + /// + /// GPU pool state + public ref GpuChannelPoolState GetPoolState() + { + return ref _pool; + } + + /// + /// Early Z force enable. + /// + /// The new value + public void SetEarlyZForce(bool value) + { + _graphics.EarlyZForce = value; + + Signal(); + } + + /// + /// Primitive topology of current draw. + /// + /// The new value + public void SetTopology(PrimitiveTopology value) + { + if (value != _graphics.Topology) + { + _graphics.Topology = value; + + if (_usesTopology) + { + Signal(); + } + } + } + + /// + /// Tessellation mode. + /// + /// The new value + public void SetTessellationMode(TessMode value) + { + if (value.Packed != _graphics.TessellationMode.Packed) + { + _graphics.TessellationMode = value; + + Signal(); + } + } + + /// + /// Updates alpha-to-coverage state, and sets it as changed. + /// + /// Whether alpha-to-coverage is enabled + /// Whether alpha-to-coverage dithering is enabled + public void SetAlphaToCoverageEnable(bool enable, bool ditherEnable) + { + _graphics.AlphaToCoverageEnable = enable; + _graphics.AlphaToCoverageDitherEnable = ditherEnable; + + Signal(); + } + + /// + /// Indicates whether the viewport transform is disabled. + /// + /// The new value + public void SetViewportTransformDisable(bool value) + { + if (value != _graphics.ViewportTransformDisable) + { + _graphics.ViewportTransformDisable = value; + + Signal(); + } + } + + /// + /// Depth mode zero to one or minus one to one. + /// + /// The new value + public void SetDepthMode(bool value) + { + if (value != _graphics.DepthMode) + { + _graphics.DepthMode = value; + + Signal(); + } + } + + /// + /// Indicates if the point size is set on the shader or is fixed. + /// + /// The new value + public void SetProgramPointSizeEnable(bool value) + { + if (value != _graphics.ProgramPointSizeEnable) + { + _graphics.ProgramPointSizeEnable = value; + + Signal(); + } + } + + /// + /// Point size used if is provided false. + /// + /// The new value + public void SetPointSize(float value) + { + if (value != _graphics.PointSize) + { + _graphics.PointSize = value; + + Signal(); + } + } + + /// + /// Updates alpha test specialization state, and sets it as changed. + /// + /// Whether alpha test is enabled + /// The value to compare with the fragment output alpha + /// The comparison that decides if the fragment should be discarded + public void SetAlphaTest(bool enable, float reference, CompareOp op) + { + _graphics.AlphaTestEnable = enable; + _graphics.AlphaTestReference = reference; + _graphics.AlphaTestCompare = op; + + Signal(); + } + + /// + /// Updates the type of the vertex attributes consumed by the shader. + /// + /// The new state + public void SetAttributeTypes(ref Array32 state) + { + bool changed = false; + ref Array32 attributeTypes = ref _graphics.AttributeTypes; + bool mayConvertVtgToCompute = ShaderCache.MayConvertVtgToCompute(ref _context.Capabilities); + bool supportsScaledFormats = _context.Capabilities.SupportsScaledVertexFormats && !mayConvertVtgToCompute; + + for (int location = 0; location < state.Length; location++) + { + VertexAttribType type = state[location].UnpackType(); + VertexAttribSize size = state[location].UnpackSize(); + + AttributeType value; + + if (supportsScaledFormats) + { + value = type switch + { + VertexAttribType.Sint => AttributeType.Sint, + VertexAttribType.Uint => AttributeType.Uint, + _ => AttributeType.Float, + }; + } + else + { + value = type switch + { + VertexAttribType.Sint => AttributeType.Sint, + VertexAttribType.Uint => AttributeType.Uint, + VertexAttribType.Uscaled => AttributeType.Uscaled, + VertexAttribType.Sscaled => AttributeType.Sscaled, + _ => AttributeType.Float, + }; + } + + if (mayConvertVtgToCompute && (size == VertexAttribSize.Rgb10A2 || size == VertexAttribSize.Rg11B10)) + { + value |= AttributeType.Packed; + + if (type == VertexAttribType.Snorm || + type == VertexAttribType.Sint || + type == VertexAttribType.Sscaled) + { + value |= AttributeType.PackedRgb10A2Signed; + } + } + + if (attributeTypes[location] != value) + { + attributeTypes[location] = value; + changed = true; + } + } + + if (changed) + { + Signal(); + } + } + + /// + /// Updates the type of the outputs produced by the fragment shader based on the current render target state. + /// + /// The render target control register + /// The color attachment state + public void SetFragmentOutputTypes(RtControl rtControl, ref Array8 state) + { + bool changed = false; + int count = rtControl.UnpackCount(); + + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + int rtIndex = rtControl.UnpackPermutationIndex(index); + + var colorState = state[rtIndex]; + + if (index < count && StateUpdater.IsRtEnabled(colorState)) + { + Format format = colorState.Format.Convert().Format; + + AttributeType type = format.IsInteger() ? (format.IsSint() ? AttributeType.Sint : AttributeType.Uint) : AttributeType.Float; + + if (type != _graphics.FragmentOutputTypes[index]) + { + _graphics.FragmentOutputTypes[index] = type; + changed = true; + } + } + } + + if (changed && _context.Capabilities.NeedsFragmentOutputSpecialization) + { + Signal(); + } + } + + /// + /// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0. + /// + /// The new value + public void SetHasConstantBufferDrawParameters(bool value) + { + if (value != _graphics.HasConstantBufferDrawParameters) + { + _graphics.HasConstantBufferDrawParameters = value; + + if (_usesDrawParameters) + { + Signal(); + } + } + } + + /// + /// Indicates that any storage buffer use is unaligned. + /// + /// The new value + /// True if the unaligned state changed, false otherwise + public bool SetHasUnalignedStorageBuffer(bool value) + { + if (value != _graphics.HasUnalignedStorageBuffer) + { + _graphics.HasUnalignedStorageBuffer = value; + + Signal(); + + return true; + } + + return false; + } + + /// + /// Sets the GPU pool state. + /// + /// The new state + public void SetPoolState(GpuChannelPoolState state) + { + if (!state.Equals(_pool)) + { + _pool = state; + + Signal(); + } + } + + /// + /// Sets the dual-source blend enabled state. + /// + /// True if blending is enabled and using dual-source blend + public void SetDualSourceBlendEnabled(bool enabled) + { + if (enabled != _graphics.DualSourceBlendEnable) + { + _graphics.DualSourceBlendEnable = enabled; + + Signal(); + } + } + + /// + /// Sets the Y negate enabled state. + /// + /// True if Y negate of the fragment coordinates is enabled + public void SetYNegateEnabled(bool enabled) + { + if (enabled != _graphics.YNegateEnabled) + { + _graphics.YNegateEnabled = enabled; + + Signal(); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs new file mode 100644 index 00000000..effcb7bb --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs @@ -0,0 +1,180 @@ +using Ryujinx.Graphics.Device; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// + /// State update callback entry, with the callback function and associated field names. + /// + readonly struct StateUpdateCallbackEntry + { + /// + /// Callback function, to be called if the register was written as the state needs to be updated. + /// + public Action Callback { get; } + + /// + /// Name of the state fields (registers) associated with the callback function. + /// + public string[] FieldNames { get; } + + /// + /// Creates a new state update callback entry. + /// + /// Callback function, to be called if the register was written as the state needs to be updated + /// Name of the state fields (registers) associated with the callback function + public StateUpdateCallbackEntry(Action callback, params string[] fieldNames) + { + Callback = callback; + FieldNames = fieldNames; + } + } + + /// + /// GPU state update tracker. + /// + /// State type + class StateUpdateTracker<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TState> + { + private const int BlockSize = 0xe00; + private const int RegisterSize = sizeof(uint); + + private readonly byte[] _registerToGroupMapping; + private readonly Action[] _callbacks; + private ulong _dirtyMask; + + /// + /// Creates a new instance of the state update tracker. + /// + /// Update tracker callback entries + public StateUpdateTracker(StateUpdateCallbackEntry[] entries) + { + _registerToGroupMapping = new byte[BlockSize]; + _callbacks = new Action[entries.Length]; + + var fieldToDelegate = new Dictionary(); + + for (int entryIndex = 0; entryIndex < entries.Length; entryIndex++) + { + var entry = entries[entryIndex]; + + foreach (var fieldName in entry.FieldNames) + { + fieldToDelegate.Add(fieldName, entryIndex); + } + + _callbacks[entryIndex] = entry.Callback; + } + + var fields = typeof(TState).GetFields(); + int offset = 0; + + for (int fieldIndex = 0; fieldIndex < fields.Length; fieldIndex++) + { + var field = fields[fieldIndex]; + + var currentFieldOffset = (int)Marshal.OffsetOf(field.Name); + var nextFieldOffset = fieldIndex + 1 == fields.Length ? Unsafe.SizeOf() : (int)Marshal.OffsetOf(fields[fieldIndex + 1].Name); + + int sizeOfField = nextFieldOffset - currentFieldOffset; + + if (fieldToDelegate.TryGetValue(field.Name, out int entryIndex)) + { + for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4) + { + _registerToGroupMapping[(offset + i) / RegisterSize] = (byte)(entryIndex + 1); + } + } + + offset += sizeOfField; + } + + Debug.Assert(offset == Unsafe.SizeOf()); + } + + /// + /// Sets a register as modified. + /// + /// Register offset in bytes + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetDirty(int offset) + { + uint index = (uint)offset / RegisterSize; + + if (index < BlockSize) + { + int groupIndex = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_registerToGroupMapping), (IntPtr)index); + if (groupIndex != 0) + { + groupIndex--; + _dirtyMask |= 1UL << groupIndex; + } + } + } + + /// + /// Forces a register group as dirty, by index. + /// + /// Index of the group to be dirtied + public void ForceDirty(int groupIndex) + { + if ((uint)groupIndex >= _callbacks.Length) + { + throw new ArgumentOutOfRangeException(nameof(groupIndex)); + } + + _dirtyMask |= 1UL << groupIndex; + } + + /// + /// Forces all register groups as dirty, triggering a full update on the next call to . + /// + public void SetAllDirty() + { + Debug.Assert(_callbacks.Length <= sizeof(ulong) * 8); + _dirtyMask = ulong.MaxValue >> ((sizeof(ulong) * 8) - _callbacks.Length); + } + + /// + /// Check if the given register group is dirty without clearing it. + /// + /// Index of the group to check + /// True if dirty, false otherwise + public bool IsDirty(int groupIndex) + { + return (_dirtyMask & (1UL << groupIndex)) != 0; + } + + /// + /// Check all the groups specified by for modification, and update if modified. + /// + /// Mask, where each bit set corresponds to a group index that should be checked + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Update(ulong checkMask) + { + ulong mask = _dirtyMask & checkMask; + if (mask == 0) + { + return; + } + + do + { + int groupIndex = BitOperations.TrailingZeroCount(mask); + + _callbacks[groupIndex](); + + mask &= ~(1UL << groupIndex); + } + while (mask != 0); + + _dirtyMask &= ~checkMask; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs new file mode 100644 index 00000000..1dc77b52 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs @@ -0,0 +1,1617 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Threed.Blender; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Shader; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Texture; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// + /// GPU state updater. + /// + class StateUpdater + { + public const int ShaderStateIndex = 26; + public const int RtColorMaskIndex = 14; + public const int RasterizerStateIndex = 15; + public const int ScissorStateIndex = 16; + public const int VertexBufferStateIndex = 0; + public const int BlendStateIndex = 2; + public const int IndexBufferStateIndex = 23; + public const int PrimitiveRestartStateIndex = 12; + public const int RenderTargetStateIndex = 27; + + // Vertex buffers larger than this size will be clamped to the mapped size. + private const ulong VertexBufferSizeToMappedSizeThreshold = 256 * 1024 * 1024; // 256 MB + + private readonly GpuContext _context; + private readonly GpuChannel _channel; + private readonly DeviceStateWithShadow _state; + private readonly DrawState _drawState; + private readonly AdvancedBlendManager _blendManager; + + private readonly StateUpdateTracker _updateTracker; + + private readonly ShaderProgramInfo[] _currentProgramInfo; + private ShaderSpecializationState _shaderSpecState; + private readonly SpecializationStateUpdater _currentSpecState; + + private ProgramPipelineState _pipeline; + + private bool _fsReadsFragCoord; + private bool _vsUsesDrawParameters; + private bool _vtgWritesRtLayer; + private byte _vsClipDistancesWritten; + private uint _vbEnableMask; + + private bool _prevDrawIndexed; + private bool _prevDrawIndirect; + private bool _prevDrawUsesEngineState; + private IndexType _prevIndexType; + private uint _prevFirstVertex; + private bool _prevTfEnable; + + private uint _prevRtNoAlphaMask; + + /// + /// Creates a new instance of the state updater. + /// + /// GPU context + /// GPU channel + /// 3D engine state + /// Draw state + /// Advanced blend manager + /// Specialization state updater + public StateUpdater( + GpuContext context, + GpuChannel channel, + DeviceStateWithShadow state, + DrawState drawState, + AdvancedBlendManager blendManager, + SpecializationStateUpdater spec) + { + _context = context; + _channel = channel; + _state = state; + _drawState = drawState; + _blendManager = blendManager; + _currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages]; + _currentSpecState = spec; + + // ShaderState must be updated after other state updates, as specialization/pipeline state is used when fetching shaders. + // Render target state must appear after shader state as it depends on information from the currently bound shader. + // Rasterizer and scissor states are checked by render target clear, their indexes + // must be updated on the constants "RasterizerStateIndex" and "ScissorStateIndex" if modified. + // The vertex buffer state may be forced dirty when a indexed draw starts, the "VertexBufferStateIndex" + // constant must be updated if modified. + // The order of the other state updates doesn't matter. + _updateTracker = new StateUpdateTracker(new[] + { + new StateUpdateCallbackEntry(UpdateVertexBufferState, + nameof(ThreedClassState.VertexBufferDrawState), + nameof(ThreedClassState.VertexBufferInstanced), + nameof(ThreedClassState.VertexBufferState), + nameof(ThreedClassState.VertexBufferEndAddress)), + + // Must be done after vertex buffer updates. + new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)), + + new StateUpdateCallbackEntry(UpdateBlendState, + nameof(ThreedClassState.BlendUcodeEnable), + nameof(ThreedClassState.BlendUcodeSize), + nameof(ThreedClassState.BlendIndependent), + nameof(ThreedClassState.BlendConstant), + nameof(ThreedClassState.BlendStateCommon), + nameof(ThreedClassState.BlendEnableCommon), + nameof(ThreedClassState.BlendEnable), + nameof(ThreedClassState.BlendState)), + + new StateUpdateCallbackEntry(UpdateFaceState, nameof(ThreedClassState.FaceState)), + + new StateUpdateCallbackEntry(UpdateStencilTestState, + nameof(ThreedClassState.StencilBackMasks), + nameof(ThreedClassState.StencilTestState), + nameof(ThreedClassState.StencilBackTestState)), + + new StateUpdateCallbackEntry(UpdateDepthTestState, + nameof(ThreedClassState.DepthTestEnable), + nameof(ThreedClassState.DepthWriteEnable), + nameof(ThreedClassState.DepthTestFunc)), + + new StateUpdateCallbackEntry(UpdateTessellationState, + nameof(ThreedClassState.TessMode), + nameof(ThreedClassState.TessOuterLevel), + nameof(ThreedClassState.TessInnerLevel), + nameof(ThreedClassState.PatchVertices)), + + new StateUpdateCallbackEntry(UpdateViewportTransform, + nameof(ThreedClassState.DepthMode), + nameof(ThreedClassState.ViewportTransform), + nameof(ThreedClassState.ViewportExtents), + nameof(ThreedClassState.YControl), + nameof(ThreedClassState.ViewportTransformEnable)), + + new StateUpdateCallbackEntry(UpdateLogicOpState, nameof(ThreedClassState.LogicOpState)), + + new StateUpdateCallbackEntry(UpdateDepthClampState, nameof(ThreedClassState.ViewVolumeClipControl)), + + new StateUpdateCallbackEntry(UpdatePolygonMode, + nameof(ThreedClassState.PolygonModeFront), + nameof(ThreedClassState.PolygonModeBack)), + + new StateUpdateCallbackEntry(UpdateDepthBiasState, + nameof(ThreedClassState.DepthBiasState), + nameof(ThreedClassState.DepthBiasFactor), + nameof(ThreedClassState.DepthBiasUnits), + nameof(ThreedClassState.DepthBiasClamp)), + + new StateUpdateCallbackEntry(UpdatePrimitiveRestartState, nameof(ThreedClassState.PrimitiveRestartState)), + + new StateUpdateCallbackEntry(UpdateLineState, + nameof(ThreedClassState.LineWidthSmooth), + nameof(ThreedClassState.LineSmoothEnable)), + + new StateUpdateCallbackEntry(UpdateRtColorMask, + nameof(ThreedClassState.RtColorMaskShared), + nameof(ThreedClassState.RtColorMask)), + + new StateUpdateCallbackEntry(UpdateRasterizerState, nameof(ThreedClassState.RasterizeEnable)), + + new StateUpdateCallbackEntry(UpdateScissorState, + nameof(ThreedClassState.ScissorState), + nameof(ThreedClassState.ScreenScissorState)), + + new StateUpdateCallbackEntry(UpdateTfBufferState, nameof(ThreedClassState.TfBufferState)), + new StateUpdateCallbackEntry(UpdateUserClipState, nameof(ThreedClassState.ClipDistanceEnable)), + + new StateUpdateCallbackEntry(UpdateAlphaTestState, + nameof(ThreedClassState.AlphaTestEnable), + nameof(ThreedClassState.AlphaTestRef), + nameof(ThreedClassState.AlphaTestFunc)), + + new StateUpdateCallbackEntry(UpdateSamplerPoolState, + nameof(ThreedClassState.SamplerPoolState), + nameof(ThreedClassState.SamplerIndex)), + + new StateUpdateCallbackEntry(UpdateTexturePoolState, nameof(ThreedClassState.TexturePoolState)), + + new StateUpdateCallbackEntry(UpdatePointState, + nameof(ThreedClassState.PointSize), + nameof(ThreedClassState.VertexProgramPointSize), + nameof(ThreedClassState.PointSpriteEnable), + nameof(ThreedClassState.PointCoordReplace)), + + new StateUpdateCallbackEntry(UpdateIndexBufferState, + nameof(ThreedClassState.IndexBufferState), + nameof(ThreedClassState.IndexBufferCount)), + + new StateUpdateCallbackEntry(UpdateMultisampleState, + nameof(ThreedClassState.AlphaToCoverageDitherEnable), + nameof(ThreedClassState.MultisampleControl)), + + new StateUpdateCallbackEntry(UpdateEarlyZState, + nameof(ThreedClassState.EarlyZForce)), + + new StateUpdateCallbackEntry(UpdateShaderState, + nameof(ThreedClassState.ShaderBaseAddress), + nameof(ThreedClassState.ShaderState)), + + new StateUpdateCallbackEntry(UpdateRenderTargetState, + nameof(ThreedClassState.RtColorState), + nameof(ThreedClassState.RtDepthStencilState), + nameof(ThreedClassState.RtControl), + nameof(ThreedClassState.RtDepthStencilSize), + nameof(ThreedClassState.RtDepthStencilEnable)), + }); + } + + /// + /// Sets a register at a specific offset as dirty. + /// This must be called if the register value was modified. + /// + /// Register offset + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetDirty(int offset) + { + _updateTracker.SetDirty(offset); + } + + /// + /// Force all the guest state to be marked as dirty. + /// The next call to will update all the host state. + /// + public void SetAllDirty() + { + _updateTracker.SetAllDirty(); + } + + /// + /// Updates host state for any modified guest state, since the last time this function was called. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Update() + { + // The vertex buffer size is calculated using a different + // method when doing indexed draws, so we need to make sure + // to update the vertex buffers if we are doing a regular + // draw after a indexed one and vice-versa. + // Some draws also do not update the engine state, so it is possible for it + // to not be dirty even if the vertex counts or other state changed. We need to force it to be dirty in this case. + if (_drawState.DrawIndexed != _prevDrawIndexed || _drawState.DrawUsesEngineState != _prevDrawUsesEngineState) + { + _updateTracker.ForceDirty(VertexBufferStateIndex); + + // If PrimitiveRestartDrawArrays is false and this is a non-indexed draw, we need to ensure primitive restart is disabled. + // If PrimitiveRestartDrawArrays is false and this is a indexed draw, we need to ensure primitive restart enable matches GPU state. + // If PrimitiveRestartDrawArrays is true, then primitive restart enable should always match GPU state. + // That is because "PrimitiveRestartDrawArrays" is not configurable on the backend, it is always + // true on OpenGL and always false on Vulkan. + if (!_state.State.PrimitiveRestartDrawArrays && _state.State.PrimitiveRestartState.Enable) + { + _updateTracker.ForceDirty(PrimitiveRestartStateIndex); + } + + _prevDrawIndexed = _drawState.DrawIndexed; + _prevDrawUsesEngineState = _drawState.DrawUsesEngineState; + } + + // Some draw parameters are used to restrict the vertex buffer size, + // but they can't be used on indirect draws because their values are unknown in this case. + // When switching between indirect and non-indirect draw, we need to + // make sure the vertex buffer sizes are still correct. + if (_drawState.DrawIndirect != _prevDrawIndirect) + { + _updateTracker.ForceDirty(VertexBufferStateIndex); + + _prevDrawIndirect = _drawState.DrawIndirect; + } + + // In some cases, the index type is also used to guess the + // vertex buffer size, so we must update it if the type changed too. + if (_drawState.DrawIndexed && + (_prevIndexType != _state.State.IndexBufferState.Type || + _prevFirstVertex != _state.State.FirstVertex)) + { + _updateTracker.ForceDirty(VertexBufferStateIndex); + _prevIndexType = _state.State.IndexBufferState.Type; + _prevFirstVertex = _state.State.FirstVertex; + } + + bool tfEnable = _state.State.TfEnable && _context.Capabilities.SupportsTransformFeedback; + + if (!tfEnable && _prevTfEnable) + { + _context.Renderer.Pipeline.EndTransformFeedback(); + _prevTfEnable = false; + } + + if (_updateTracker.IsDirty(RenderTargetStateIndex)) + { + UpdateRenderTargetSpecialization(); + } + + _updateTracker.Update(ulong.MaxValue); + + // If any state that the shader depends on changed, + // then we may need to compile/bind a different version + // of the shader for the new state. + if (_shaderSpecState != null && _currentSpecState.HasChanged()) + { + if (!_shaderSpecState.MatchesGraphics( + _channel, + ref _currentSpecState.GetPoolState(), + ref _currentSpecState.GetGraphicsState(), + _drawState.VertexAsCompute != null, + _vsUsesDrawParameters, + checkTextures: false)) + { + // Shader must be reloaded. _vtgWritesRtLayer should not change. + UpdateShaderState(); + } + } + + CommitBindings(); + + if (tfEnable && !_prevTfEnable) + { + _context.Renderer.Pipeline.BeginTransformFeedback(_drawState.Topology); + _prevTfEnable = true; + } + } + + /// + /// Updates the host state for any modified guest state group with the respective bit set on . + /// + /// Mask, where each bit set corresponds to a group index that should be checked and updated + public void Update(ulong mask) + { + _updateTracker.Update(mask); + } + + /// + /// Ensures that the bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + private void CommitBindings() + { + UpdateStorageBuffers(); + + bool unalignedChanged = _currentSpecState.SetHasUnalignedStorageBuffer(_channel.BufferManager.HasUnalignedStorageBuffers); + + bool scaleMismatch; + do + { + if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState, out scaleMismatch) || unalignedChanged) + { + // Shader must be reloaded. _vtgWritesRtLayer should not change. + UpdateShaderState(); + } + + if (scaleMismatch) + { + // Binding textures changed scale of the bound render targets, correct the render target scale and rebind. + UpdateRenderTargetState(); + } + } + while (scaleMismatch); + + _channel.BufferManager.CommitGraphicsBindings(_drawState.DrawIndexed); + } + + /// + /// Updates storage buffer bindings. + /// + private void UpdateStorageBuffers() + { + for (int stage = 0; stage < Constants.ShaderStages; stage++) + { + ShaderProgramInfo info = _currentProgramInfo[stage]; + + if (info == null) + { + continue; + } + + for (int index = 0; index < info.SBuffers.Count; index++) + { + BufferDescriptor sb = info.SBuffers[index]; + + ulong sbDescAddress = _channel.BufferManager.GetGraphicsUniformBufferAddress(stage, sb.SbCbSlot); + sbDescAddress += (ulong)sb.SbCbOffset * 4; + + SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read(sbDescAddress); + + uint size; + if (sb.SbCbSlot == Constants.DriverReservedUniformBuffer) + { + // Only trust the SbDescriptor size if it comes from slot 0. + size = (uint)sbDescriptor.Size; + } + else + { + // TODO: Use full mapped size and somehow speed up buffer sync. + size = (uint)_channel.MemoryManager.GetMappedSize(sbDescriptor.PackAddress(), Constants.MaxUnknownStorageSize); + } + + _channel.BufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), size, sb.Flags); + } + } + } + + /// + /// Updates tessellation state based on the guest GPU state. + /// + private void UpdateTessellationState() + { + _pipeline.PatchControlPoints = (uint)_state.State.PatchVertices; + + _context.Renderer.Pipeline.SetPatchParameters( + _state.State.PatchVertices, + _state.State.TessOuterLevel.AsSpan(), + _state.State.TessInnerLevel.AsSpan()); + + _currentSpecState.SetTessellationMode(_state.State.TessMode); + } + + /// + /// Updates transform feedback buffer state based on the guest GPU state. + /// + private void UpdateTfBufferState() + { + for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++) + { + TfBufferState tfb = _state.State.TfBufferState[index]; + + if (!tfb.Enable) + { + _channel.BufferManager.SetTransformFeedbackBuffer(index, 0, 0); + + continue; + } + + _channel.BufferManager.SetTransformFeedbackBuffer(index, tfb.Address.Pack(), (uint)tfb.Size); + } + } + + /// + /// Updates Rasterizer primitive discard state based on guest gpu state. + /// + private void UpdateRasterizerState() + { + bool enable = _state.State.RasterizeEnable; + _pipeline.RasterizerDiscard = !enable; + _context.Renderer.Pipeline.SetRasterizerDiscard(!enable); + } + + /// + /// Updates render targets (color and depth-stencil buffers) based on current render target state. + /// + private void UpdateRenderTargetState() + { + UpdateRenderTargetState(RenderTargetUpdateFlags.UpdateAll); + } + + /// + /// Updates render targets (color and depth-stencil buffers) based on current render target state. + /// + /// Flags indicating which render targets should be updated and how + /// If this is not -1, it indicates that only the given indexed target will be used. + public void UpdateRenderTargetState(RenderTargetUpdateFlags updateFlags, int singleUse = -1) + { + var memoryManager = _channel.MemoryManager; + var rtControl = _state.State.RtControl; + + bool useControl = updateFlags.HasFlag(RenderTargetUpdateFlags.UseControl); + bool layered = updateFlags.HasFlag(RenderTargetUpdateFlags.Layered); + bool singleColor = updateFlags.HasFlag(RenderTargetUpdateFlags.SingleColor); + bool discard = updateFlags.HasFlag(RenderTargetUpdateFlags.DiscardClip); + + int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets; + + var msaaMode = _state.State.RtMsaaMode; + + int samplesInX = msaaMode.SamplesInX(); + int samplesInY = msaaMode.SamplesInY(); + + var scissor = _state.State.ScreenScissorState; + Size sizeHint = new((scissor.X + scissor.Width) * samplesInX, (scissor.Y + scissor.Height) * samplesInY, 1); + + int clipRegionWidth = int.MaxValue; + int clipRegionHeight = int.MaxValue; + + bool changedScale = false; + uint rtNoAlphaMask = 0; + + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + int rtIndex = useControl ? rtControl.UnpackPermutationIndex(index) : index; + + var colorState = _state.State.RtColorState[rtIndex]; + + if (index >= count || !IsRtEnabled(colorState) || (singleColor && index != singleUse)) + { + changedScale |= _channel.TextureManager.SetRenderTargetColor(index, null); + + continue; + } + + if (colorState.Format.NoAlpha()) + { + rtNoAlphaMask |= 1u << index; + } + + Image.Texture color = memoryManager.Physical.TextureCache.FindOrCreateTexture( + memoryManager, + colorState, + _vtgWritesRtLayer || layered, + discard, + samplesInX, + samplesInY, + sizeHint); + + changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color); + + if (color != null) + { + if (clipRegionWidth > color.Width / samplesInX) + { + clipRegionWidth = color.Width / samplesInX; + } + + if (clipRegionHeight > color.Height / samplesInY) + { + clipRegionHeight = color.Height / samplesInY; + } + + if (!_context.Capabilities.SupportsBgraFormat) + { + _context.SupportBufferUpdater.SetRenderTargetIsBgra(index, color.Format.IsBgr()); + } + } + } + + bool dsEnable = _state.State.RtDepthStencilEnable; + + Image.Texture depthStencil = null; + + if (dsEnable && updateFlags.HasFlag(RenderTargetUpdateFlags.UpdateDepthStencil)) + { + var dsState = _state.State.RtDepthStencilState; + var dsSize = _state.State.RtDepthStencilSize; + + depthStencil = memoryManager.Physical.TextureCache.FindOrCreateTexture( + memoryManager, + dsState, + dsSize, + _vtgWritesRtLayer || layered, + discard, + samplesInX, + samplesInY, + sizeHint); + + if (depthStencil != null) + { + if (clipRegionWidth > depthStencil.Width / samplesInX) + { + clipRegionWidth = depthStencil.Width / samplesInX; + } + + if (clipRegionHeight > depthStencil.Height / samplesInY) + { + clipRegionHeight = depthStencil.Height / samplesInY; + } + } + } + + changedScale |= _channel.TextureManager.SetRenderTargetDepthStencil(depthStencil); + + if (changedScale) + { + float oldScale = _channel.TextureManager.RenderTargetScale; + _channel.TextureManager.UpdateRenderTargetScale(singleUse); + + if (oldScale != _channel.TextureManager.RenderTargetScale) + { + _context.SupportBufferUpdater.SetRenderTargetScale(_channel.TextureManager.RenderTargetScale); + + UpdateViewportTransform(); + UpdateScissorState(); + } + } + + _channel.TextureManager.SetClipRegion(clipRegionWidth, clipRegionHeight); + + if (useControl && _prevRtNoAlphaMask != rtNoAlphaMask) + { + _prevRtNoAlphaMask = rtNoAlphaMask; + + UpdateBlendState(); + } + } + + /// + /// Updates specialization state based on render target state. + /// + public void UpdateRenderTargetSpecialization() + { + _currentSpecState.SetFragmentOutputTypes(_state.State.RtControl, ref _state.State.RtColorState); + } + + /// + /// Checks if a render target color buffer is used. + /// + /// Color buffer information + /// True if the specified buffer is enabled/used, false otherwise + internal static bool IsRtEnabled(RtColorState colorState) + { + // Colors are disabled by writing 0 to the format. + return colorState.Format != 0 && colorState.WidthOrStride != 0; + } + + /// + /// Updates host scissor test state based on current GPU state. + /// + public void UpdateScissorState() + { + const int MinX = 0; + const int MinY = 0; + const int MaxW = 0xffff; + const int MaxH = 0xffff; + + Span> regions = stackalloc Rectangle[Constants.TotalViewports]; + + for (int index = 0; index < Constants.TotalViewports; index++) + { + ScissorState scissor = _state.State.ScissorState[index]; + + bool enable = scissor.Enable && (scissor.X1 != MinX || + scissor.Y1 != MinY || + scissor.X2 != MaxW || + scissor.Y2 != MaxH); + + if (enable) + { + int x = scissor.X1; + int y = scissor.Y1; + int width = scissor.X2 - x; + int height = scissor.Y2 - y; + + if (_state.State.YControl.HasFlag(YControl.NegateY)) + { + ref var screenScissor = ref _state.State.ScreenScissorState; + y = screenScissor.Height - height - y; + + if (y < 0) + { + height += y; + y = 0; + } + } + + float scale = _channel.TextureManager.RenderTargetScale; + if (scale != 1f) + { + x = (int)(x * scale); + y = (int)(y * scale); + width = (int)MathF.Ceiling(width * scale); + height = (int)MathF.Ceiling(height * scale); + } + + regions[index] = new Rectangle(x, y, width, height); + } + else + { + regions[index] = new Rectangle(MinX, MinY, MaxW, MaxH); + } + } + + _context.Renderer.Pipeline.SetScissors(regions); + } + + /// + /// Updates host depth clamp state based on current GPU state. + /// + /// Current GPU state + private void UpdateDepthClampState() + { + ViewVolumeClipControl clip = _state.State.ViewVolumeClipControl; + bool clamp = (clip & ViewVolumeClipControl.DepthClampDisabled) == 0; + + _pipeline.DepthClampEnable = clamp; + _context.Renderer.Pipeline.SetDepthClamp(clamp); + } + + /// + /// Updates host alpha test state based on current GPU state. + /// + private void UpdateAlphaTestState() + { + _context.Renderer.Pipeline.SetAlphaTest( + _state.State.AlphaTestEnable, + _state.State.AlphaTestRef, + _state.State.AlphaTestFunc); + + _currentSpecState.SetAlphaTest( + _state.State.AlphaTestEnable, + _state.State.AlphaTestRef, + _state.State.AlphaTestFunc); + } + + /// + /// Updates host depth test state based on current GPU state. + /// + private void UpdateDepthTestState() + { + DepthTestDescriptor descriptor = new( + _state.State.DepthTestEnable, + _state.State.DepthWriteEnable, + _state.State.DepthTestFunc); + + _pipeline.DepthTest = descriptor; + _context.Renderer.Pipeline.SetDepthTest(descriptor); + } + + /// + /// Updates host viewport transform and clipping state based on current GPU state. + /// + private void UpdateViewportTransform() + { + var yControl = _state.State.YControl; + var face = _state.State.FaceState; + + bool disableTransform = _state.State.ViewportTransformEnable == 0; + bool yNegate = yControl.HasFlag(YControl.NegateY); + + UpdateFrontFace(yControl, face.FrontFace); + UpdateDepthMode(); + + Span viewports = stackalloc Viewport[Constants.TotalViewports]; + + for (int index = 0; index < Constants.TotalViewports; index++) + { + if (disableTransform) + { + ref var scissor = ref _state.State.ScreenScissorState; + + float rScale = _channel.TextureManager.RenderTargetScale; + var scissorRect = new Rectangle(0, 0, (scissor.X + scissor.Width) * rScale, (scissor.Y + scissor.Height) * rScale); + + viewports[index] = new Viewport(scissorRect, ViewportSwizzle.PositiveX, ViewportSwizzle.PositiveY, ViewportSwizzle.PositiveZ, ViewportSwizzle.PositiveW, 0, 1); + continue; + } + + ref var transform = ref _state.State.ViewportTransform[index]; + ref var extents = ref _state.State.ViewportExtents[index]; + + float scaleX = MathF.Abs(transform.ScaleX); + float scaleY = transform.ScaleY; + + if (yNegate) + { + scaleY = -scaleY; + } + + if (!_context.Capabilities.SupportsViewportSwizzle && transform.UnpackSwizzleY() == ViewportSwizzle.NegativeY) + { + scaleY = -scaleY; + } + + float x = transform.TranslateX - scaleX; + float y = transform.TranslateY - scaleY; + + float width = scaleX * 2; + float height = scaleY * 2; + + float scale = _channel.TextureManager.RenderTargetScale; + if (scale != 1f) + { + x *= scale; + y *= scale; + width *= scale; + height *= scale; + } + + Rectangle region = new(x, y, width, height); + + ViewportSwizzle swizzleX = transform.UnpackSwizzleX(); + ViewportSwizzle swizzleY = transform.UnpackSwizzleY(); + ViewportSwizzle swizzleZ = transform.UnpackSwizzleZ(); + ViewportSwizzle swizzleW = transform.UnpackSwizzleW(); + + float depthNear = extents.DepthNear; + float depthFar = extents.DepthFar; + + if (transform.ScaleZ < 0) + { + (depthFar, depthNear) = (depthNear, depthFar); + } + + viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar); + } + + _context.Renderer.Pipeline.SetDepthMode(GetDepthMode()); + _context.Renderer.Pipeline.SetViewports(viewports); + + _context.SupportBufferUpdater.SetViewportTransformDisable( + viewports[0].Region.Width, + viewports[0].Region.Height, + _channel.TextureManager.RenderTargetScale, + disableTransform); + + // Viewport size is only used on the shader when YNegate is enabled, + // and if the fragment shader accesses gl_FragCoord, + // so there's no need to update it in other cases. + if (yNegate && _fsReadsFragCoord) + { + UpdateSupportBufferViewportSize(); + } + + _currentSpecState.SetViewportTransformDisable(disableTransform); + _currentSpecState.SetDepthMode(GetDepthMode() == DepthMode.MinusOneToOne); + _currentSpecState.SetYNegateEnabled(yNegate); + } + + /// + /// Updates the depth mode (0 to 1 or -1 to 1) based on the current viewport and depth mode register state. + /// + private void UpdateDepthMode() + { + DepthMode mode = GetDepthMode(); + + _pipeline.DepthMode = mode; + + _context.Renderer.Pipeline.SetDepthMode(mode); + } + + /// + /// Updates polygon mode state based on current GPU state. + /// + private void UpdatePolygonMode() + { + _context.Renderer.Pipeline.SetPolygonMode(_state.State.PolygonModeFront, _state.State.PolygonModeBack); + } + + /// + /// Updates host depth bias (also called polygon offset) state based on current GPU state. + /// + private void UpdateDepthBiasState() + { + var depthBias = _state.State.DepthBiasState; + + float factor = _state.State.DepthBiasFactor; + float units = _state.State.DepthBiasUnits; + float clamp = _state.State.DepthBiasClamp; + + PolygonModeMask enables; + + enables = (depthBias.PointEnable ? PolygonModeMask.Point : 0); + enables |= (depthBias.LineEnable ? PolygonModeMask.Line : 0); + enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0); + + _pipeline.BiasEnable = enables; + _context.Renderer.Pipeline.SetDepthBias(enables, factor, units / 2f, clamp); + } + + /// + /// Updates host stencil test state based on current GPU state. + /// + private void UpdateStencilTestState() + { + var backMasks = _state.State.StencilBackMasks; + var test = _state.State.StencilTestState; + var backTest = _state.State.StencilBackTestState; + + CompareOp backFunc; + StencilOp backSFail; + StencilOp backDpPass; + StencilOp backDpFail; + int backFuncRef; + int backFuncMask; + int backMask; + + if (backTest.TwoSided) + { + backFunc = backTest.BackFunc; + backSFail = backTest.BackSFail; + backDpPass = backTest.BackDpPass; + backDpFail = backTest.BackDpFail; + backFuncRef = backMasks.FuncRef; + backFuncMask = backMasks.FuncMask; + backMask = backMasks.Mask; + } + else + { + backFunc = test.FrontFunc; + backSFail = test.FrontSFail; + backDpPass = test.FrontDpPass; + backDpFail = test.FrontDpFail; + backFuncRef = test.FrontFuncRef; + backFuncMask = test.FrontFuncMask; + backMask = test.FrontMask; + } + + StencilTestDescriptor descriptor = new( + test.Enable, + test.FrontFunc, + test.FrontSFail, + test.FrontDpPass, + test.FrontDpFail, + test.FrontFuncRef, + test.FrontFuncMask, + test.FrontMask, + backFunc, + backSFail, + backDpPass, + backDpFail, + backFuncRef, + backFuncMask, + backMask); + + _pipeline.StencilTest = descriptor; + _context.Renderer.Pipeline.SetStencilTest(descriptor); + } + + /// + /// Updates user-defined clipping based on the guest GPU state. + /// + private void UpdateUserClipState() + { + uint clipMask = _state.State.ClipDistanceEnable & _vsClipDistancesWritten; + + for (int i = 0; i < Constants.TotalClipDistances; ++i) + { + _context.Renderer.Pipeline.SetUserClipDistance(i, (clipMask & (1 << i)) != 0); + } + } + + /// + /// Updates current sampler pool address and size based on guest GPU state. + /// + private void UpdateSamplerPoolState() + { + var texturePool = _state.State.TexturePoolState; + var samplerPool = _state.State.SamplerPoolState; + + var samplerIndex = _state.State.SamplerIndex; + + int maximumId = samplerIndex == SamplerIndex.ViaHeaderIndex + ? texturePool.MaximumId + : samplerPool.MaximumId; + + _channel.TextureManager.SetGraphicsSamplerPool(samplerPool.Address.Pack(), maximumId, samplerIndex); + } + + /// + /// Updates current texture pool address and size based on guest GPU state. + /// + private void UpdateTexturePoolState() + { + var texturePool = _state.State.TexturePoolState; + + _channel.TextureManager.SetGraphicsTexturePool(texturePool.Address.Pack(), texturePool.MaximumId); + _channel.TextureManager.SetGraphicsTextureBufferIndex((int)_state.State.TextureBufferIndex); + + _currentSpecState.SetPoolState(GetPoolState()); + } + + /// + /// Updates host vertex attributes based on guest GPU state. + /// + private void UpdateVertexAttribState() + { + bool supportsScaledFormats = _context.Capabilities.SupportsScaledVertexFormats; + uint vbEnableMask = _vbEnableMask; + + Span vertexAttribs = stackalloc VertexAttribDescriptor[Constants.TotalVertexAttribs]; + + for (int index = 0; index < Constants.TotalVertexAttribs; index++) + { + var vertexAttrib = _state.State.VertexAttribState[index]; + + int bufferIndex = vertexAttrib.UnpackBufferIndex(); + + if ((vbEnableMask & (1u << bufferIndex)) == 0) + { + // Using a vertex buffer that doesn't exist is invalid, so let's use a dummy attribute for those cases. + vertexAttribs[index] = new VertexAttribDescriptor(0, 0, true, Format.R32G32B32A32Float); + continue; + } + + uint packedFormat = vertexAttrib.UnpackFormat(); + + if (!supportsScaledFormats) + { + packedFormat = vertexAttrib.UnpackType() switch + { + VertexAttribType.Uscaled => ((uint)VertexAttribType.Uint << 27) | (packedFormat & (0x3f << 21)), + VertexAttribType.Sscaled => ((uint)VertexAttribType.Sint << 27) | (packedFormat & (0x3f << 21)), + _ => packedFormat, + }; + } + + if (!FormatTable.TryGetAttribFormat(packedFormat, out Format format)) + { + Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}."); + + format = vertexAttrib.UnpackType() switch + { + VertexAttribType.Sint => Format.R32G32B32A32Sint, + VertexAttribType.Uint => Format.R32G32B32A32Uint, + _ => Format.R32G32B32A32Float, + }; + } + + vertexAttribs[index] = new VertexAttribDescriptor( + bufferIndex, + vertexAttrib.UnpackOffset(), + vertexAttrib.UnpackIsConstant(), + format); + } + + _pipeline.SetVertexAttribs(vertexAttribs); + _context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs); + _currentSpecState.SetAttributeTypes(ref _state.State.VertexAttribState); + } + + /// + /// Updates host line width based on guest GPU state. + /// + private void UpdateLineState() + { + float width = _state.State.LineWidthSmooth; + bool smooth = _state.State.LineSmoothEnable; + + _pipeline.LineWidth = width; + _context.Renderer.Pipeline.SetLineParameters(width, smooth); + } + + /// + /// Updates host point size based on guest GPU state. + /// + private void UpdatePointState() + { + float size = _state.State.PointSize; + bool isProgramPointSize = _state.State.VertexProgramPointSize; + bool enablePointSprite = _state.State.PointSpriteEnable; + + // TODO: Need to figure out a way to map PointCoordReplace enable bit. + Origin origin = (_state.State.PointCoordReplace & 4) == 0 ? Origin.LowerLeft : Origin.UpperLeft; + + _context.Renderer.Pipeline.SetPointParameters(size, isProgramPointSize, enablePointSprite, origin); + + _currentSpecState.SetProgramPointSizeEnable(isProgramPointSize); + _currentSpecState.SetPointSize(size); + } + + /// + /// Updates host primitive restart based on guest GPU state. + /// + private void UpdatePrimitiveRestartState() + { + PrimitiveRestartState primitiveRestart = _state.State.PrimitiveRestartState; + bool enable = primitiveRestart.Enable && (_drawState.DrawIndexed || _state.State.PrimitiveRestartDrawArrays); + + _pipeline.PrimitiveRestartEnable = enable; + _context.Renderer.Pipeline.SetPrimitiveRestart(enable, primitiveRestart.Index); + } + + /// + /// Updates host index buffer binding based on guest GPU state. + /// + private void UpdateIndexBufferState() + { + var indexBuffer = _state.State.IndexBufferState; + + if (_drawState.IndexCount == 0) + { + return; + } + + ulong gpuVa = indexBuffer.Address.Pack(); + + // Do not use the end address to calculate the size, because + // the result may be much larger than the real size of the index buffer. + ulong size = (ulong)(_drawState.FirstIndex + _drawState.IndexCount); + + switch (indexBuffer.Type) + { + case IndexType.UShort: + size *= 2; + break; + case IndexType.UInt: + size *= 4; + break; + } + + _channel.BufferManager.SetIndexBuffer(gpuVa, size, indexBuffer.Type); + } + + /// + /// Updates host vertex buffer bindings based on guest GPU state. + /// + private void UpdateVertexBufferState() + { + IndexType indexType = _state.State.IndexBufferState.Type; + bool indexTypeSmall = indexType == IndexType.UByte || indexType == IndexType.UShort; + + _drawState.IsAnyVbInstanced = false; + + bool drawIndexed = _drawState.DrawIndexed; + bool drawIndirect = _drawState.DrawIndirect; + int drawFirstVertex = _drawState.DrawFirstVertex; + int drawVertexCount = _drawState.DrawVertexCount; + uint vbEnableMask = 0; + + for (int index = 0; index < Constants.TotalVertexBuffers; index++) + { + var vertexBuffer = _state.State.VertexBufferState[index]; + + if (!vertexBuffer.UnpackEnable()) + { + _pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(false, 0, 0); + _channel.BufferManager.SetVertexBuffer(index, 0, 0, 0, 0); + + continue; + } + + GpuVa endAddress = _state.State.VertexBufferEndAddress[index]; + + ulong address = vertexBuffer.Address.Pack(); + + if (_channel.MemoryManager.IsMapped(address)) + { + vbEnableMask |= 1u << index; + } + + int stride = vertexBuffer.UnpackStride(); + + bool instanced = _state.State.VertexBufferInstanced[index]; + + int divisor = instanced ? vertexBuffer.Divisor : 0; + + _drawState.IsAnyVbInstanced |= divisor != 0; + + ulong vbSize = endAddress.Pack() - address + 1; + ulong size; + + if (_drawState.IbStreamer.HasInlineIndexData || drawIndexed || stride == 0 || instanced) + { + // This size may be (much) larger than the real vertex buffer size. + // Avoid calculating it this way, unless we don't have any other option. + + size = vbSize; + + if (stride > 0 && indexTypeSmall && drawIndexed && !drawIndirect && !instanced) + { + // If the index type is a small integer type, then we might be still able + // to reduce the vertex buffer size based on the maximum possible index value. + + ulong maxVertexBufferSize = indexType == IndexType.UByte ? 0x100UL : 0x10000UL; + + maxVertexBufferSize += _state.State.FirstVertex; + maxVertexBufferSize *= (uint)stride; + + size = Math.Min(size, maxVertexBufferSize); + } + else if (size > VertexBufferSizeToMappedSizeThreshold) + { + // Make sure we have a sane vertex buffer size, since in some cases applications + // might set the "end address" of the vertex buffer to the end of the GPU address space, + // which would result in a several GBs large buffer. + + size = _channel.MemoryManager.GetMappedSize(address, size); + } + } + else + { + // For non-indexed draws, we can guess the size from the vertex count + // and stride. + + int firstInstance = (int)_state.State.FirstInstance; + + size = Math.Min(vbSize, (ulong)((firstInstance + drawFirstVertex + drawVertexCount) * stride)); + } + + _pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(_channel.MemoryManager.IsMapped(address), stride, divisor); + _channel.BufferManager.SetVertexBuffer(index, address, size, stride, divisor); + } + + if (_vbEnableMask != vbEnableMask) + { + _vbEnableMask = vbEnableMask; + UpdateVertexAttribState(); + } + } + + /// + /// Updates host face culling and orientation based on guest GPU state. + /// + private void UpdateFaceState() + { + var yControl = _state.State.YControl; + var face = _state.State.FaceState; + + _pipeline.CullEnable = face.CullEnable; + _pipeline.CullMode = face.CullFace; + _context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace); + + UpdateFrontFace(yControl, face.FrontFace); + } + + /// + /// Updates the front face based on the current front face and the origin. + /// + /// Y control register value, where the origin is located + /// Front face + private void UpdateFrontFace(YControl yControl, FrontFace frontFace) + { + bool isUpperLeftOrigin = !yControl.HasFlag(YControl.TriangleRastFlip); + + if (isUpperLeftOrigin) + { + frontFace = frontFace == FrontFace.CounterClockwise ? FrontFace.Clockwise : FrontFace.CounterClockwise; + } + + _pipeline.FrontFace = frontFace; + _context.Renderer.Pipeline.SetFrontFace(frontFace); + } + + /// + /// Updates host render target color masks, based on guest GPU state. + /// This defines which color channels are written to each color buffer. + /// + private void UpdateRtColorMask() + { + bool rtColorMaskShared = _state.State.RtColorMaskShared; + + Span componentMasks = stackalloc uint[Constants.TotalRenderTargets]; + + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + var colorMask = _state.State.RtColorMask[rtColorMaskShared ? 0 : index]; + + uint componentMask; + + componentMask = (colorMask.UnpackRed() ? 1u : 0u); + componentMask |= (colorMask.UnpackGreen() ? 2u : 0u); + componentMask |= (colorMask.UnpackBlue() ? 4u : 0u); + componentMask |= (colorMask.UnpackAlpha() ? 8u : 0u); + + componentMasks[index] = componentMask; + _pipeline.ColorWriteMask[index] = componentMask; + } + + _context.Renderer.Pipeline.SetRenderTargetColorMasks(componentMasks); + } + + /// + /// Updates host render target color buffer blending state, based on guest state. + /// + private void UpdateBlendState() + { + if (_state.State.BlendUcodeEnable != BlendUcodeEnable.Disabled) + { + if (_context.Capabilities.SupportsBlendEquationAdvanced && _blendManager.TryGetAdvancedBlend(out var blendDescriptor)) + { + // Try to HLE it using advanced blend on the host if we can. + _context.Renderer.Pipeline.SetBlendState(blendDescriptor); + return; + } + else + { + // TODO: Blend emulation fallback. + } + } + + bool blendIndependent = _state.State.BlendIndependent; + ColorF blendConstant = _state.State.BlendConstant; + + bool dualSourceBlendEnabled = false; + + if (blendIndependent) + { + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + bool enable = _state.State.BlendEnable[index]; + var blend = _state.State.BlendState[index]; + + var descriptor = new BlendDescriptor( + enable, + blendConstant, + blend.ColorOp, + FilterBlendFactor(blend.ColorSrcFactor, index), + FilterBlendFactor(blend.ColorDstFactor, index), + blend.AlphaOp, + FilterBlendFactor(blend.AlphaSrcFactor, index), + FilterBlendFactor(blend.AlphaDstFactor, index)); + + if (enable && + (blend.ColorSrcFactor.IsDualSource() || + blend.ColorDstFactor.IsDualSource() || + blend.AlphaSrcFactor.IsDualSource() || + blend.AlphaDstFactor.IsDualSource())) + { + dualSourceBlendEnabled = true; + } + + _pipeline.BlendDescriptors[index] = descriptor; + _context.Renderer.Pipeline.SetBlendState(index, descriptor); + } + } + else + { + bool enable = _state.State.BlendEnable[0]; + var blend = _state.State.BlendStateCommon; + + var descriptor = new BlendDescriptor( + enable, + blendConstant, + blend.ColorOp, + FilterBlendFactor(blend.ColorSrcFactor, 0), + FilterBlendFactor(blend.ColorDstFactor, 0), + blend.AlphaOp, + FilterBlendFactor(blend.AlphaSrcFactor, 0), + FilterBlendFactor(blend.AlphaDstFactor, 0)); + + if (enable && + (blend.ColorSrcFactor.IsDualSource() || + blend.ColorDstFactor.IsDualSource() || + blend.AlphaSrcFactor.IsDualSource() || + blend.AlphaDstFactor.IsDualSource())) + { + dualSourceBlendEnabled = true; + } + + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + _pipeline.BlendDescriptors[index] = descriptor; + _context.Renderer.Pipeline.SetBlendState(index, descriptor); + } + } + + _currentSpecState.SetDualSourceBlendEnabled(dualSourceBlendEnabled); + } + + /// + /// Gets a blend factor for the color target currently. + /// This will return unless the target format has no alpha component, + /// in which case it will replace destination alpha factor with a constant factor of one or zero. + /// + /// Input factor + /// Color target index + /// New blend factor + private BlendFactor FilterBlendFactor(BlendFactor factor, int index) + { + // If any color target format without alpha is being used, we need to make sure that + // if blend is active, it will not use destination alpha as a factor. + // That is required because RGBX formats are emulated using host RGBA formats. + + if (_state.State.RtColorState[index].Format.NoAlpha()) + { + switch (factor) + { + case BlendFactor.DstAlpha: + case BlendFactor.DstAlphaGl: + factor = BlendFactor.One; + break; + case BlendFactor.OneMinusDstAlpha: + case BlendFactor.OneMinusDstAlphaGl: + factor = BlendFactor.Zero; + break; + } + } + + return factor; + } + + /// + /// Updates host logical operation state, based on guest state. + /// + private void UpdateLogicOpState() + { + LogicalOpState logicOpState = _state.State.LogicOpState; + + _pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp); + _context.Renderer.Pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp); + } + + /// + /// Updates multisample state, based on guest state. + /// + private void UpdateMultisampleState() + { + bool alphaToCoverageEnable = (_state.State.MultisampleControl & 1) != 0; + bool alphaToOneEnable = (_state.State.MultisampleControl & 0x10) != 0; + + _context.Renderer.Pipeline.SetMultisampleState(new MultisampleDescriptor( + alphaToCoverageEnable, + _state.State.AlphaToCoverageDitherEnable, + alphaToOneEnable)); + + _currentSpecState.SetAlphaToCoverageEnable(alphaToCoverageEnable, _state.State.AlphaToCoverageDitherEnable); + } + + /// + /// Updates the early z flag, based on guest state. + /// + private void UpdateEarlyZState() + { + _currentSpecState.SetEarlyZForce(_state.State.EarlyZForce); + } + + /// + /// Updates host shaders based on the guest GPU state. + /// + private void UpdateShaderState() + { + var shaderCache = _channel.MemoryManager.Physical.ShaderCache; + + _vtgWritesRtLayer = false; + + ShaderAddresses addresses = new(); + Span addressesSpan = addresses.AsSpan(); + + ulong baseAddress = _state.State.ShaderBaseAddress.Pack(); + + for (int index = 0; index < 6; index++) + { + var shader = _state.State.ShaderState[index]; + if (!shader.UnpackEnable() && index != 1) + { + continue; + } + + addressesSpan[index] = baseAddress + shader.Offset; + } + + int samplerPoolMaximumId = _state.State.SamplerIndex == SamplerIndex.ViaHeaderIndex + ? _state.State.TexturePoolState.MaximumId + : _state.State.SamplerPoolState.MaximumId; + + CachedShaderProgram gs = shaderCache.GetGraphicsShader( + ref _state.State, + ref _pipeline, + _channel, + samplerPoolMaximumId, + ref _currentSpecState.GetPoolState(), + ref _currentSpecState.GetGraphicsState(), + addresses); + + // Consume the modified flag for spec state so that it isn't checked again. + _currentSpecState.SetShader(gs); + + _shaderSpecState = gs.SpecializationState; + + byte oldVsClipDistancesWritten = _vsClipDistancesWritten; + + _drawState.VsUsesInstanceId = gs.Shaders[1]?.Info.UsesInstanceId ?? false; + _vsUsesDrawParameters = gs.Shaders[1]?.Info.UsesDrawParameters ?? false; + _vsClipDistancesWritten = gs.Shaders[1]?.Info.ClipDistancesWritten ?? 0; + + bool hasTransformFeedback = gs.SpecializationState.TransformFeedbackDescriptors != null; + if (hasTransformFeedback != _channel.BufferManager.HasTransformFeedbackOutputs) + { + if (!_context.Capabilities.SupportsTransformFeedback) + { + // If host does not support transform feedback, and the shader changed, + // we might need to update bindings as transform feedback emulation + // uses storage buffer bindings that might have been used for something + // else in a previous draw. + + _channel.BufferManager.ForceTransformFeedbackAndStorageBuffersDirty(); + } + + _channel.BufferManager.HasTransformFeedbackOutputs = hasTransformFeedback; + } + + if (oldVsClipDistancesWritten != _vsClipDistancesWritten) + { + UpdateUserClipState(); + } + + UpdateShaderBindings(gs.Bindings); + + for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++) + { + ShaderProgramInfo info = gs.Shaders[stageIndex + 1]?.Info; + + if (info?.UsesRtLayer == true) + { + _vtgWritesRtLayer = true; + } + + _currentProgramInfo[stageIndex] = info; + } + + if (gs.Shaders[5]?.Info.UsesFragCoord == true) + { + // Make sure we update the viewport size on the support buffer if it will be consumed on the new shader. + + if (!_fsReadsFragCoord && _state.State.YControl.HasFlag(YControl.NegateY)) + { + UpdateSupportBufferViewportSize(); + } + + _fsReadsFragCoord = true; + } + else + { + _fsReadsFragCoord = false; + } + + if (gs.VertexAsCompute != null) + { + _drawState.VertexAsCompute = gs.VertexAsCompute; + _drawState.GeometryAsCompute = gs.GeometryAsCompute; + _drawState.VertexPassthrough = gs.HostProgram; + } + else + { + _drawState.VertexAsCompute = null; + _drawState.GeometryAsCompute = null; + _drawState.VertexPassthrough = null; + } + + _context.Renderer.Pipeline.SetProgram(gs.HostProgram); + } + + /// + /// Updates the viewport size on the support buffer for fragment shader access. + /// + private void UpdateSupportBufferViewportSize() + { + ref var transform = ref _state.State.ViewportTransform[0]; + + float scaleX = MathF.Abs(transform.ScaleX); + float scaleY = transform.ScaleY; + + float width = scaleX * 2; + float height = scaleY * 2; + + _context.SupportBufferUpdater.SetViewportSize(width, MathF.Abs(height)); + } + + /// + /// Updates bindings consumed by the shader on the texture and buffer managers. + /// + /// Bindings for the active shader + private void UpdateShaderBindings(CachedShaderBindings bindings) + { + _channel.TextureManager.SetGraphicsBindings(bindings); + _channel.BufferManager.SetGraphicsBufferBindings(bindings); + } + + /// + /// Gets the current texture pool state. + /// + /// Texture pool state + private GpuChannelPoolState GetPoolState() + { + return new GpuChannelPoolState( + _state.State.TexturePoolState.Address.Pack(), + _state.State.TexturePoolState.MaximumId, + (int)_state.State.TextureBufferIndex); + } + + /// + /// Gets the depth mode that is currently being used (zero to one or minus one to one). + /// + /// Current depth mode + private DepthMode GetDepthMode() + { + ref var transform = ref _state.State.ViewportTransform[0]; + ref var extents = ref _state.State.ViewportExtents[0]; + + DepthMode depthMode; + + if (!float.IsInfinity(extents.DepthNear) && + !float.IsInfinity(extents.DepthFar) && + (extents.DepthFar - extents.DepthNear) != 0) + { + // Try to guess the depth mode being used on the high level API + // based on current transform. + // It is setup like so by said APIs: + // If depth mode is ZeroToOne: + // TranslateZ = Near + // ScaleZ = Far - Near + // If depth mode is MinusOneToOne: + // TranslateZ = (Near + Far) / 2 + // ScaleZ = (Far - Near) / 2 + // DepthNear/Far are sorted such as that Near is always less than Far. + depthMode = extents.DepthNear != transform.TranslateZ && + extents.DepthFar != transform.TranslateZ + ? DepthMode.MinusOneToOne + : DepthMode.ZeroToOne; + } + else + { + // If we can't guess from the viewport transform, then just use the depth mode register. + depthMode = (DepthMode)(_state.State.DepthMode & 1); + } + + return depthMode; + } + + /// + /// Forces the shaders to be rebound on the next draw. + /// + public void ForceShaderUpdate() + { + _updateTracker.ForceDirty(ShaderStateIndex); + } + + /// + /// Forces a register group as dirty, by index. + /// + /// Index of the group to be dirtied + public void ForceDirty(int groupIndex) + { + _updateTracker.ForceDirty(groupIndex); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs new file mode 100644 index 00000000..ab1d27a1 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs @@ -0,0 +1,850 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.GPFifo; +using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; +using Ryujinx.Graphics.Gpu.Engine.Threed.Blender; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Synchronization; +using Ryujinx.Memory.Range; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// + /// Represents a 3D engine class. + /// + class ThreedClass : IDeviceState, IDisposable + { + private readonly GpuContext _context; + private readonly GPFifoClass _fifoClass; + private readonly DeviceStateWithShadow _state; + + private readonly InlineToMemoryClass _i2mClass; + private readonly AdvancedBlendManager _blendManager; + private readonly DrawManager _drawManager; + private readonly SemaphoreUpdater _semaphoreUpdater; + private readonly ConstantBufferUpdater _cbUpdater; + private readonly StateUpdater _stateUpdater; + + private SetMmeShadowRamControlMode ShadowMode => _state.State.SetMmeShadowRamControlMode; + + /// + /// Creates a new instance of the 3D engine class. + /// + /// GPU context + /// GPU channel + public ThreedClass(GpuContext context, GpuChannel channel, GPFifoClass fifoClass) + { + _context = context; + _fifoClass = fifoClass; + _state = new DeviceStateWithShadow(new Dictionary + { + { nameof(ThreedClassState.LaunchDma), new RwCallback(LaunchDma, null) }, + { nameof(ThreedClassState.LoadInlineData), new RwCallback(LoadInlineData, null) }, + { nameof(ThreedClassState.SyncpointAction), new RwCallback(IncrementSyncpoint, null) }, + { nameof(ThreedClassState.InvalidateSamplerCacheNoWfi), new RwCallback(InvalidateSamplerCacheNoWfi, null) }, + { nameof(ThreedClassState.InvalidateTextureHeaderCacheNoWfi), new RwCallback(InvalidateTextureHeaderCacheNoWfi, null) }, + { nameof(ThreedClassState.TextureBarrier), new RwCallback(TextureBarrier, null) }, + { nameof(ThreedClassState.LoadBlendUcodeStart), new RwCallback(LoadBlendUcodeStart, null) }, + { nameof(ThreedClassState.LoadBlendUcodeInstruction), new RwCallback(LoadBlendUcodeInstruction, null) }, + { nameof(ThreedClassState.TextureBarrierTiled), new RwCallback(TextureBarrierTiled, null) }, + { nameof(ThreedClassState.DrawTextureSrcY), new RwCallback(DrawTexture, null) }, + { nameof(ThreedClassState.DrawVertexArrayBeginEndInstanceFirst), new RwCallback(DrawVertexArrayBeginEndInstanceFirst, null) }, + { nameof(ThreedClassState.DrawVertexArrayBeginEndInstanceSubsequent), new RwCallback(DrawVertexArrayBeginEndInstanceSubsequent, null) }, + { nameof(ThreedClassState.VbElementU8), new RwCallback(VbElementU8, null) }, + { nameof(ThreedClassState.VbElementU16), new RwCallback(VbElementU16, null) }, + { nameof(ThreedClassState.VbElementU32), new RwCallback(VbElementU32, null) }, + { nameof(ThreedClassState.ResetCounter), new RwCallback(ResetCounter, null) }, + { nameof(ThreedClassState.RenderEnableCondition), new RwCallback(null, Zero) }, + { nameof(ThreedClassState.DrawEnd), new RwCallback(DrawEnd, null) }, + { nameof(ThreedClassState.DrawBegin), new RwCallback(DrawBegin, null) }, + { nameof(ThreedClassState.DrawIndexBuffer32BeginEndInstanceFirst), new RwCallback(DrawIndexBuffer32BeginEndInstanceFirst, null) }, + { nameof(ThreedClassState.DrawIndexBuffer16BeginEndInstanceFirst), new RwCallback(DrawIndexBuffer16BeginEndInstanceFirst, null) }, + { nameof(ThreedClassState.DrawIndexBuffer8BeginEndInstanceFirst), new RwCallback(DrawIndexBuffer8BeginEndInstanceFirst, null) }, + { nameof(ThreedClassState.DrawIndexBuffer32BeginEndInstanceSubsequent), new RwCallback(DrawIndexBuffer32BeginEndInstanceSubsequent, null) }, + { nameof(ThreedClassState.DrawIndexBuffer16BeginEndInstanceSubsequent), new RwCallback(DrawIndexBuffer16BeginEndInstanceSubsequent, null) }, + { nameof(ThreedClassState.DrawIndexBuffer8BeginEndInstanceSubsequent), new RwCallback(DrawIndexBuffer8BeginEndInstanceSubsequent, null) }, + { nameof(ThreedClassState.IndexBufferCount), new RwCallback(SetIndexBufferCount, null) }, + { nameof(ThreedClassState.Clear), new RwCallback(Clear, null) }, + { nameof(ThreedClassState.SemaphoreControl), new RwCallback(Report, null) }, + { nameof(ThreedClassState.SetFalcon04), new RwCallback(SetFalcon04, null) }, + { nameof(ThreedClassState.UniformBufferUpdateData), new RwCallback(ConstantBufferUpdate, null) }, + { nameof(ThreedClassState.UniformBufferBindVertex), new RwCallback(ConstantBufferBindVertex, null) }, + { nameof(ThreedClassState.UniformBufferBindTessControl), new RwCallback(ConstantBufferBindTessControl, null) }, + { nameof(ThreedClassState.UniformBufferBindTessEvaluation), new RwCallback(ConstantBufferBindTessEvaluation, null) }, + { nameof(ThreedClassState.UniformBufferBindGeometry), new RwCallback(ConstantBufferBindGeometry, null) }, + { nameof(ThreedClassState.UniformBufferBindFragment), new RwCallback(ConstantBufferBindFragment, null) }, + }); + + _i2mClass = new InlineToMemoryClass(context, channel, initializeState: false); + + var spec = new SpecializationStateUpdater(context); + var drawState = new DrawState(); + + _drawManager = new DrawManager(context, channel, _state, drawState, spec); + _blendManager = new AdvancedBlendManager(_state); + _semaphoreUpdater = new SemaphoreUpdater(context, channel, _state); + _cbUpdater = new ConstantBufferUpdater(channel, _state); + _stateUpdater = new StateUpdater(context, channel, _state, drawState, _blendManager, spec); + + // This defaults to "always", even without any register write. + // Reads just return 0, regardless of what was set there. + _state.State.RenderEnableCondition = Condition.Always; + } + + /// + /// Reads data from the class registers. + /// + /// Register byte offset + /// Data at the specified offset + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Read(int offset) => _state.Read(offset); + + /// + /// Writes data to the class registers. + /// + /// Register byte offset + /// Data to be written + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(int offset, int data) + { + _state.WriteWithRedundancyCheck(offset, data, out bool valueChanged); + + if (valueChanged) + { + _stateUpdater.SetDirty(offset); + } + } + + /// + /// Sets the shadow ram control value of all sub-channels. + /// + /// New shadow ram control value + public void SetShadowRamControl(int control) + { + _state.State.SetMmeShadowRamControl = (uint)control; + } + + /// + /// Updates current host state for all registers modified since the last call to this method. + /// + public void UpdateState() + { + _fifoClass.CreatePendingSyncs(); + _cbUpdater.FlushUboDirty(); + _stateUpdater.Update(); + } + + /// + /// Updates current host state for all registers modified since the last call to this method. + /// + /// Mask where each bit set indicates that the respective state group index should be checked + public void UpdateState(ulong mask) + { + _stateUpdater.Update(mask); + } + + /// + /// Updates render targets (color and depth-stencil buffers) based on current render target state. + /// + /// Flags indicating which render targets should be updated and how + /// If this is not -1, it indicates that only the given indexed target will be used. + public void UpdateRenderTargetState(RenderTargetUpdateFlags updateFlags, int singleUse = -1) + { + _stateUpdater.UpdateRenderTargetState(updateFlags, singleUse); + } + + /// + /// Updates scissor based on current render target state. + /// + public void UpdateScissorState() + { + _stateUpdater.UpdateScissorState(); + } + + /// + /// Marks the entire state as dirty, forcing a full host state update before the next draw. + /// + public void ForceStateDirty() + { + _drawManager.ForceStateDirty(); + _stateUpdater.SetAllDirty(); + } + + /// + /// Marks the specified register offset as dirty, forcing the associated state to update on the next draw. + /// + /// Register offset + public void ForceStateDirty(int offset) + { + _stateUpdater.SetDirty(offset); + } + + /// + /// Marks the specified register range for a group index as dirty, forcing the associated state to update on the next draw. + /// + /// Index of the group to dirty + public void ForceStateDirtyByIndex(int groupIndex) + { + _stateUpdater.ForceDirty(groupIndex); + } + + /// + /// Forces the shaders to be rebound on the next draw. + /// + public void ForceShaderUpdate() + { + _stateUpdater.ForceShaderUpdate(); + } + + /// + /// Create any syncs from WaitForIdle command that are currently pending. + /// + public void CreatePendingSyncs() + { + _fifoClass.CreatePendingSyncs(); + } + + /// + /// Flushes any queued UBO updates. + /// + public void FlushUboDirty() + { + _cbUpdater.FlushUboDirty(); + } + + /// + /// Perform any deferred draws. + /// + public void PerformDeferredDraws() + { + _drawManager.PerformDeferredDraws(this); + } + + /// + /// Updates the currently bound constant buffer. + /// + /// Data to be written to the buffer + public void ConstantBufferUpdate(ReadOnlySpan data) + { + _cbUpdater.Update(data); + } + + /// + /// Test if two 32 byte structs are equal. + /// + /// Type of the 32-byte struct + /// First struct + /// Second struct + /// True if equal, false otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UnsafeEquals32Byte(ref T lhs, ref T rhs) where T : unmanaged + { + if (Vector256.IsHardwareAccelerated) + { + return Vector256.EqualsAll( + Unsafe.As>(ref lhs), + Unsafe.As>(ref rhs) + ); + } + else + { + ref var lhsVec = ref Unsafe.As>(ref lhs); + ref var rhsVec = ref Unsafe.As>(ref rhs); + + return Vector128.EqualsAll(lhsVec, rhsVec) && + Vector128.EqualsAll(Unsafe.Add(ref lhsVec, 1), Unsafe.Add(ref rhsVec, 1)); + } + } + + /// + /// Updates blend enable. Respects current shadow mode. + /// + /// Blend enable + public void UpdateBlendEnable(ref Array8 enable) + { + var shadow = ShadowMode; + ref var state = ref _state.State.BlendEnable; + + if (shadow.IsReplay()) + { + enable = _state.ShadowState.BlendEnable; + } + + if (!UnsafeEquals32Byte(ref enable, ref state)) + { + state = enable; + + _stateUpdater.ForceDirty(StateUpdater.BlendStateIndex); + } + + if (shadow.IsTrack()) + { + _state.ShadowState.BlendEnable = enable; + } + } + + /// + /// Updates color masks. Respects current shadow mode. + /// + /// Color masks + public void UpdateColorMasks(ref Array8 masks) + { + var shadow = ShadowMode; + ref var state = ref _state.State.RtColorMask; + + if (shadow.IsReplay()) + { + masks = _state.ShadowState.RtColorMask; + } + + if (!UnsafeEquals32Byte(ref masks, ref state)) + { + state = masks; + + _stateUpdater.ForceDirty(StateUpdater.RtColorMaskIndex); + } + + if (shadow.IsTrack()) + { + _state.ShadowState.RtColorMask = masks; + } + } + + /// + /// Updates index buffer state for an indexed draw. Respects current shadow mode. + /// + /// High part of the address + /// Low part of the address + /// Type of the binding + public void UpdateIndexBuffer(uint addrHigh, uint addrLow, IndexType type) + { + var shadow = ShadowMode; + ref var state = ref _state.State.IndexBufferState; + + if (shadow.IsReplay()) + { + ref var shadowState = ref _state.ShadowState.IndexBufferState; + addrHigh = shadowState.Address.High; + addrLow = shadowState.Address.Low; + type = shadowState.Type; + } + + if (state.Address.High != addrHigh || state.Address.Low != addrLow || state.Type != type) + { + state.Address.High = addrHigh; + state.Address.Low = addrLow; + state.Type = type; + + _stateUpdater.ForceDirty(StateUpdater.IndexBufferStateIndex); + } + + if (shadow.IsTrack()) + { + ref var shadowState = ref _state.ShadowState.IndexBufferState; + shadowState.Address.High = addrHigh; + shadowState.Address.Low = addrLow; + shadowState.Type = type; + } + } + + /// + /// Updates uniform buffer state for update or bind. Respects current shadow mode. + /// + /// Size of the binding + /// High part of the addrsss + /// Low part of the address + public void UpdateUniformBufferState(int size, uint addrHigh, uint addrLow) + { + var shadow = ShadowMode; + ref var state = ref _state.State.UniformBufferState; + + if (shadow.IsReplay()) + { + ref var shadowState = ref _state.ShadowState.UniformBufferState; + size = shadowState.Size; + addrHigh = shadowState.Address.High; + addrLow = shadowState.Address.Low; + } + + state.Size = size; + state.Address.High = addrHigh; + state.Address.Low = addrLow; + + if (shadow.IsTrack()) + { + ref var shadowState = ref _state.ShadowState.UniformBufferState; + shadowState.Size = size; + shadowState.Address.High = addrHigh; + shadowState.Address.Low = addrLow; + } + } + + /// + /// Updates a shader offset. Respects current shadow mode. + /// + /// Index of the shader to update + /// Offset to update with + public void SetShaderOffset(int index, uint offset) + { + var shadow = ShadowMode; + ref var shaderState = ref _state.State.ShaderState[index]; + + if (shadow.IsReplay()) + { + offset = _state.ShadowState.ShaderState[index].Offset; + } + + if (shaderState.Offset != offset) + { + shaderState.Offset = offset; + + _stateUpdater.ForceDirty(StateUpdater.ShaderStateIndex); + } + + if (shadow.IsTrack()) + { + _state.ShadowState.ShaderState[index].Offset = offset; + } + } + + /// + /// Updates uniform buffer state for update. Respects current shadow mode. + /// + /// Uniform buffer state + public void UpdateUniformBufferState(UniformBufferState ubState) + { + var shadow = ShadowMode; + ref var state = ref _state.State.UniformBufferState; + + if (shadow.IsReplay()) + { + ubState = _state.ShadowState.UniformBufferState; + } + + state = ubState; + + if (shadow.IsTrack()) + { + _state.ShadowState.UniformBufferState = ubState; + } + } + + /// + /// Launches the Inline-to-Memory DMA copy operation. + /// + /// Method call argument + private void LaunchDma(int argument) + { + _i2mClass.LaunchDma(ref Unsafe.As(ref _state.State), argument); + } + + /// + /// Pushes a block of data to the Inline-to-Memory engine. + /// + /// Data to push + public void LoadInlineData(ReadOnlySpan data) + { + _i2mClass.LoadInlineData(data); + } + + /// + /// Pushes a word of data to the Inline-to-Memory engine. + /// + /// Method call argument + private void LoadInlineData(int argument) + { + _i2mClass.LoadInlineData(argument); + } + + /// + /// Performs an incrementation on a syncpoint. + /// + /// Method call argument + public void IncrementSyncpoint(int argument) + { + uint syncpointId = (uint)argument & 0xFFFF; + + _context.AdvanceSequence(); + _context.CreateHostSyncIfNeeded(HostSyncFlags.StrictSyncpoint); + _context.Renderer.UpdateCounters(); // Poll the query counters, the game may want an updated result. + _context.Synchronization.IncrementSyncpoint(syncpointId); + } + + /// + /// Invalidates the cache with the sampler descriptors from the sampler pool. + /// + /// Method call argument (unused) + private void InvalidateSamplerCacheNoWfi(int argument) + { + _context.AdvanceSequence(); + } + + /// + /// Invalidates the cache with the texture descriptors from the texture pool. + /// + /// Method call argument (unused) + private void InvalidateTextureHeaderCacheNoWfi(int argument) + { + _context.AdvanceSequence(); + } + + /// + /// Issues a texture barrier. + /// This waits until previous texture writes from the GPU to finish, before + /// performing new operations with said textures. + /// + /// Method call argument (unused) + private void TextureBarrier(int argument) + { + _context.Renderer.Pipeline.TextureBarrier(); + } + + /// + /// Sets the start offset of the blend microcode in memory. + /// + /// Method call argument + private void LoadBlendUcodeStart(int argument) + { + _blendManager.LoadBlendUcodeStart(argument); + } + + /// + /// Pushes one word of blend microcode. + /// + /// Method call argument + private void LoadBlendUcodeInstruction(int argument) + { + _blendManager.LoadBlendUcodeInstruction(argument); + } + + /// + /// Issues a texture barrier. + /// This waits until previous texture writes from the GPU to finish, before + /// performing new operations with said textures. + /// This performs a per-tile wait, it is only valid if both the previous write + /// and current access has the same access patterns. + /// This may be faster than the regular barrier on tile-based rasterizers. + /// + /// Method call argument (unused) + private void TextureBarrierTiled(int argument) + { + _context.Renderer.Pipeline.TextureBarrierTiled(); + } + + /// + /// Draws a texture, without needing to specify shader programs. + /// + /// Method call argument + private void DrawTexture(int argument) + { + _drawManager.DrawTexture(this, argument); + } + + /// + /// Performs a non-indexed draw with the specified topology, index and count. + /// + /// Method call argument + private void DrawVertexArrayBeginEndInstanceFirst(int argument) + { + _drawManager.DrawVertexArrayBeginEndInstanceFirst(this, argument); + } + + /// + /// Performs a non-indexed draw with the specified topology, index and count, + /// while incrementing the current instance. + /// + /// Method call argument + private void DrawVertexArrayBeginEndInstanceSubsequent(int argument) + { + _drawManager.DrawVertexArrayBeginEndInstanceSubsequent(this, argument); + } + + /// + /// Pushes four 8-bit index buffer elements. + /// + /// Method call argument + private void VbElementU8(int argument) + { + _drawManager.VbElementU8(argument); + } + + /// + /// Pushes two 16-bit index buffer elements. + /// + /// Method call argument + private void VbElementU16(int argument) + { + _drawManager.VbElementU16(argument); + } + + /// + /// Pushes one 32-bit index buffer element. + /// + /// Method call argument + private void VbElementU32(int argument) + { + _drawManager.VbElementU32(argument); + } + + /// + /// Resets the value of an internal GPU counter back to zero. + /// + /// Method call argument + private void ResetCounter(int argument) + { + _semaphoreUpdater.ResetCounter(argument); + } + + /// + /// Finishes the draw call. + /// This draws geometry on the bound buffers based on the current GPU state. + /// + /// Method call argument + private void DrawEnd(int argument) + { + _drawManager.DrawEnd(this, argument); + } + + /// + /// Starts draw. + /// This sets primitive type and instanced draw parameters. + /// + /// Method call argument + private void DrawBegin(int argument) + { + _drawManager.DrawBegin(this, argument); + } + + /// + /// Sets the index buffer count. + /// This also sets internal state that indicates that the next draw is an indexed draw. + /// + /// Method call argument + private void SetIndexBufferCount(int argument) + { + _drawManager.SetIndexBufferCount(argument); + } + + /// + /// Performs a indexed draw with 8-bit index buffer elements. + /// + /// Method call argument + private void DrawIndexBuffer8BeginEndInstanceFirst(int argument) + { + _drawManager.DrawIndexBuffer8BeginEndInstanceFirst(this, argument); + } + + /// + /// Performs a indexed draw with 16-bit index buffer elements. + /// + /// Method call argument + private void DrawIndexBuffer16BeginEndInstanceFirst(int argument) + { + _drawManager.DrawIndexBuffer16BeginEndInstanceFirst(this, argument); + } + + /// + /// Performs a indexed draw with 32-bit index buffer elements. + /// + /// Method call argument + private void DrawIndexBuffer32BeginEndInstanceFirst(int argument) + { + _drawManager.DrawIndexBuffer32BeginEndInstanceFirst(this, argument); + } + + /// + /// Performs a indexed draw with 8-bit index buffer elements, + /// while also pre-incrementing the current instance value. + /// + /// Method call argument + private void DrawIndexBuffer8BeginEndInstanceSubsequent(int argument) + { + _drawManager.DrawIndexBuffer8BeginEndInstanceSubsequent(this, argument); + } + + /// + /// Performs a indexed draw with 16-bit index buffer elements, + /// while also pre-incrementing the current instance value. + /// + /// Method call argument + private void DrawIndexBuffer16BeginEndInstanceSubsequent(int argument) + { + _drawManager.DrawIndexBuffer16BeginEndInstanceSubsequent(this, argument); + } + + /// + /// Performs a indexed draw with 32-bit index buffer elements, + /// while also pre-incrementing the current instance value. + /// + /// Method call argument + private void DrawIndexBuffer32BeginEndInstanceSubsequent(int argument) + { + _drawManager.DrawIndexBuffer32BeginEndInstanceSubsequent(this, argument); + } + + /// + /// Clears the current color and depth-stencil buffers. + /// Which buffers should be cleared is also specified on the argument. + /// + /// Method call argument + private void Clear(int argument) + { + _drawManager.Clear(this, argument); + } + + /// + /// Writes a GPU counter to guest memory. + /// + /// Method call argument + private void Report(int argument) + { + _semaphoreUpdater.Report(argument); + } + + /// + /// Performs high-level emulation of Falcon microcode function number "4". + /// + /// Method call argument + private void SetFalcon04(int argument) + { + _state.State.SetMmeShadowScratch[0] = 1; + } + + /// + /// Updates the uniform buffer data with inline data. + /// + /// New uniform buffer data word + private void ConstantBufferUpdate(int argument) + { + _cbUpdater.Update(argument); + } + + /// + /// Binds a uniform buffer for the vertex shader stage. + /// + /// Method call argument + private void ConstantBufferBindVertex(int argument) + { + _cbUpdater.BindVertex(argument); + } + + /// + /// Binds a uniform buffer for the tessellation control shader stage. + /// + /// Method call argument + private void ConstantBufferBindTessControl(int argument) + { + _cbUpdater.BindTessControl(argument); + } + + /// + /// Binds a uniform buffer for the tessellation evaluation shader stage. + /// + /// Method call argument + private void ConstantBufferBindTessEvaluation(int argument) + { + _cbUpdater.BindTessEvaluation(argument); + } + + /// + /// Binds a uniform buffer for the geometry shader stage. + /// + /// Method call argument + private void ConstantBufferBindGeometry(int argument) + { + _cbUpdater.BindGeometry(argument); + } + + /// + /// Binds a uniform buffer for the fragment shader stage. + /// + /// Method call argument + private void ConstantBufferBindFragment(int argument) + { + _cbUpdater.BindFragment(argument); + } + + /// + /// Generic register read function that just returns 0. + /// + /// Zero + private static int Zero() + { + return 0; + } + + /// + /// Performs a indexed or non-indexed draw. + /// + /// Primitive topology + /// Index count for indexed draws, vertex count for non-indexed draws + /// Instance count + /// First index on the index buffer for indexed draws, ignored for non-indexed draws + /// First vertex on the vertex buffer + /// First instance + /// True if the draw is indexed, false otherwise + public void Draw( + PrimitiveTopology topology, + int count, + int instanceCount, + int firstIndex, + int firstVertex, + int firstInstance, + bool indexed) + { + _drawManager.Draw(this, topology, count, instanceCount, firstIndex, firstVertex, firstInstance, indexed); + } + + /// + /// Performs a indirect draw, with parameters from a GPU buffer. + /// + /// Primitive topology + /// Memory range of the buffer with the draw parameters, such as count, first index, etc + /// Memory range of the buffer with the draw count + /// Maximum number of draws that can be made + /// Distance in bytes between each entry on the data pointed to by + /// Maximum number of indices that the draw can consume + /// Type of the indirect draw, which can be indexed or non-indexed, with or without a draw count + public void DrawIndirect( + PrimitiveTopology topology, + MultiRange indirectBufferRange, + MultiRange parameterBufferRange, + int maxDrawCount, + int stride, + int indexCount, + IndirectDrawType drawType) + { + _drawManager.DrawIndirect(this, topology, indirectBufferRange, parameterBufferRange, maxDrawCount, stride, indexCount, drawType); + } + + /// + /// Clears the current color and depth-stencil buffers. + /// Which buffers should be cleared can also specified with the arguments. + /// + /// Method call argument + /// For array and 3D textures, indicates how many layers should be cleared + public void Clear(int argument, int layerCount) + { + _drawManager.Clear(this, argument, layerCount); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _drawManager.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs new file mode 100644 index 00000000..35051c6e --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs @@ -0,0 +1,1057 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Shader; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Engine.Threed +{ + /// + /// Shader stage name. + /// + enum ShaderType + { + Vertex, + TessellationControl, + TessellationEvaluation, + Geometry, + Fragment, + } + + /// + /// Tessellation mode. + /// + struct TessMode + { +#pragma warning disable CS0649 // Field is never assigned to + public uint Packed; +#pragma warning restore CS0649 + + /// + /// Unpacks the tessellation abstract patch type. + /// + /// Abtract patch type + public readonly TessPatchType UnpackPatchType() + { + return (TessPatchType)(Packed & 3); + } + + /// + /// Unpacks the spacing between tessellated vertices of the patch. + /// + /// Spacing between tessellated vertices + public readonly TessSpacing UnpackSpacing() + { + return (TessSpacing)((Packed >> 4) & 3); + } + + /// + /// Unpacks the primitive winding order. + /// + /// True if clockwise, false if counter-clockwise + public readonly bool UnpackCw() + { + return (Packed & (1 << 8)) != 0; + } + } + + /// + /// Transform feedback buffer state. + /// + struct TfBufferState + { +#pragma warning disable CS0649 // Field is never assigned to + public Boolean32 Enable; + public GpuVa Address; + public int Size; + public int Offset; + public uint Padding0; + public uint Padding1; + public uint Padding2; +#pragma warning restore CS0649 + } + + /// + /// Transform feedback state. + /// + struct TfState + { +#pragma warning disable CS0649 // Field is never assigned to + public int BufferIndex; + public int VaryingsCount; + public int Stride; + public uint Padding; +#pragma warning restore CS0649 + } + + /// + /// Render target color buffer state. + /// + struct RtColorState + { +#pragma warning disable CS0649 // Field is never assigned to + public GpuVa Address; + public int WidthOrStride; + public int Height; + public ColorFormat Format; + public MemoryLayout MemoryLayout; + public int Depth; + public int LayerSize; + public int BaseLayer; + public int Unknown0x24; + public int Padding0; + public int Padding1; + public int Padding2; + public int Padding3; + public int Padding4; + public int Padding5; +#pragma warning restore CS0649 + } + + /// + /// Viewport transform parameters, for viewport transformation. + /// + struct ViewportTransform + { +#pragma warning disable CS0649 // Field is never assigned to + public float ScaleX; + public float ScaleY; + public float ScaleZ; + public float TranslateX; + public float TranslateY; + public float TranslateZ; + public uint Swizzle; + public uint SubpixelPrecisionBias; +#pragma warning restore CS0649 + + /// + /// Unpacks viewport swizzle of the position X component. + /// + /// Swizzle enum value + public readonly ViewportSwizzle UnpackSwizzleX() + { + return (ViewportSwizzle)(Swizzle & 7); + } + + /// + /// Unpacks viewport swizzle of the position Y component. + /// + /// Swizzle enum value + public readonly ViewportSwizzle UnpackSwizzleY() + { + return (ViewportSwizzle)((Swizzle >> 4) & 7); + } + + /// + /// Unpacks viewport swizzle of the position Z component. + /// + /// Swizzle enum value + public readonly ViewportSwizzle UnpackSwizzleZ() + { + return (ViewportSwizzle)((Swizzle >> 8) & 7); + } + + /// + /// Unpacks viewport swizzle of the position W component. + /// + /// Swizzle enum value + public readonly ViewportSwizzle UnpackSwizzleW() + { + return (ViewportSwizzle)((Swizzle >> 12) & 7); + } + } + + /// + /// Viewport extents for viewport clipping, also includes depth range. + /// + struct ViewportExtents + { +#pragma warning disable CS0649 // Field is never assigned to + public ushort X; + public ushort Width; + public ushort Y; + public ushort Height; + public float DepthNear; + public float DepthFar; +#pragma warning restore CS0649 + } + + /// + /// Draw state for non-indexed draws. + /// + struct VertexBufferDrawState + { +#pragma warning disable CS0649 // Field is never assigned to + public int First; + public int Count; +#pragma warning restore CS0649 + } + + /// + /// Color buffer clear color. + /// + struct ClearColors + { +#pragma warning disable CS0649 // Field is never assigned to + public float Red; + public float Green; + public float Blue; + public float Alpha; +#pragma warning restore CS0649 + } + + /// + /// Depth bias (also called polygon offset) parameters. + /// + struct DepthBiasState + { +#pragma warning disable CS0649 // Field is never assigned to + public Boolean32 PointEnable; + public Boolean32 LineEnable; + public Boolean32 FillEnable; +#pragma warning restore CS0649 + } + + /// + /// Indicates whenever the blend microcode processes RGB and alpha components. + /// + enum BlendUcodeEnable + { + Disabled = 0, + EnableRGB = 1, + EnableAlpha = 2, + EnableRGBA = 3, + } + + /// + /// Scissor state. + /// + struct ScissorState + { +#pragma warning disable CS0649 // Field is never assigned to + public Boolean32 Enable; + public ushort X1; + public ushort X2; + public ushort Y1; + public ushort Y2; + public uint Padding; +#pragma warning restore CS0649 + } + + /// + /// Stencil test masks for back tests. + /// + struct StencilBackMasks + { +#pragma warning disable CS0649 // Field is never assigned to + public int FuncRef; + public int Mask; + public int FuncMask; +#pragma warning restore CS0649 + } + + /// + /// Render target depth-stencil buffer state. + /// + struct RtDepthStencilState + { +#pragma warning disable CS0649 // Field is never assigned to + public GpuVa Address; + public ZetaFormat Format; + public MemoryLayout MemoryLayout; + public int LayerSize; +#pragma warning restore CS0649 + } + + /// + /// Screen scissor state. + /// + struct ScreenScissorState + { +#pragma warning disable CS0649 // Field is never assigned to + public ushort X; + public ushort Width; + public ushort Y; + public ushort Height; +#pragma warning restore CS0649 + } + + /// + /// Vertex attribute vector and component size. + /// + enum VertexAttribSize + { + Size32x4 = 1, + Size32x3 = 2, + Size16x4 = 3, + Size32x2 = 4, + Size16x3 = 5, + Size8x4 = 0xa, + Size16x2 = 0xf, + Size32 = 0x12, + Size8x3 = 0x13, + Size8x2 = 0x18, + Size16 = 0x1b, + Size8 = 0x1d, + Rgb10A2 = 0x30, + Rg11B10 = 0x31, + } + + /// + /// Vertex attribute component type. + /// + enum VertexAttribType + { + Snorm = 1, + Unorm = 2, + Sint = 3, + Uint = 4, + Uscaled = 5, + Sscaled = 6, + Float = 7, + } + + /// + /// Vertex buffer attribute state. + /// + struct VertexAttribState + { +#pragma warning disable CS0649 // Field is never assigned to + public uint Attribute; +#pragma warning restore CS0649 + + /// + /// Unpacks the index of the vertex buffer this attribute belongs to. + /// + /// Vertex buffer index + public readonly int UnpackBufferIndex() + { + return (int)(Attribute & 0x1f); + } + + /// + /// Unpacks the attribute constant flag. + /// + /// True if the attribute is constant, false otherwise + public readonly bool UnpackIsConstant() + { + return (Attribute & 0x40) != 0; + } + + /// + /// Unpacks the offset, in bytes, of the attribute on the vertex buffer. + /// + /// Attribute offset in bytes + public readonly int UnpackOffset() + { + return (int)((Attribute >> 7) & 0x3fff); + } + + /// + /// Unpacks the Maxwell attribute format integer. + /// + /// Attribute format integer + public readonly uint UnpackFormat() + { + return Attribute & 0x3fe00000; + } + + /// + /// Unpacks the Maxwell attribute size. + /// + /// Attribute size + public readonly VertexAttribSize UnpackSize() + { + return (VertexAttribSize)((Attribute >> 21) & 0x3f); + } + + /// + /// Unpacks the Maxwell attribute component type. + /// + /// Attribute component type + public readonly VertexAttribType UnpackType() + { + return (VertexAttribType)((Attribute >> 27) & 7); + } + } + + /// + /// Render target draw buffers control. + /// + struct RtControl + { +#pragma warning disable CS0649 // Field is never assigned to + public uint Packed; +#pragma warning restore CS0649 + + /// + /// Unpacks the number of active draw buffers. + /// + /// Number of active draw buffers + public readonly int UnpackCount() + { + return (int)(Packed & 0xf); + } + + /// + /// Unpacks the color attachment index for a given draw buffer. + /// + /// Index of the draw buffer + /// Attachment index + public readonly int UnpackPermutationIndex(int index) + { + return (int)((Packed >> (4 + index * 3)) & 7); + } + } + + /// + /// 3D, 2D or 1D texture size. + /// + struct Size3D + { +#pragma warning disable CS0649 // Field is never assigned to + public int Width; + public int Height; + public ushort Depth; + public ushort Flags; + + public readonly bool UnpackIsLayered() + { + return (Flags & 1) == 0; + } +#pragma warning restore CS0649 + } + + /// + /// Stencil front test state and masks. + /// + struct StencilTestState + { +#pragma warning disable CS0649 // Field is never assigned to + public Boolean32 Enable; + public StencilOp FrontSFail; + public StencilOp FrontDpFail; + public StencilOp FrontDpPass; + public CompareOp FrontFunc; + public int FrontFuncRef; + public int FrontFuncMask; + public int FrontMask; +#pragma warning restore CS0649 + } + + /// + /// Screen Y control register. + /// + [Flags] + enum YControl + { + NegateY = 1 << 0, + TriangleRastFlip = 1 << 4, + } + + /// + /// RGB color components packed as 16-bit float values. + /// + struct RgbHalf + { +#pragma warning disable CS0649 // Field is never assigned to + public uint R; + public uint G; + public uint B; + public uint Padding; +#pragma warning restore CS0649 + + /// + /// Unpacks the red color component as a 16-bit float value. + /// + /// The component value + public readonly Half UnpackR() + { + ushort value = (ushort)R; + return Unsafe.As(ref value); + } + + /// + /// Unpacks the green color component as a 16-bit float value. + /// + /// The component value + public readonly Half UnpackG() + { + ushort value = (ushort)G; + return Unsafe.As(ref value); + } + + /// + /// Unpacks the blue color component as a 16-bit float value. + /// + /// The component value + public readonly Half UnpackB() + { + ushort value = (ushort)B; + return Unsafe.As(ref value); + } + } + + /// + /// Condition for conditional rendering. + /// + enum Condition + { + Never, + Always, + ResultNonZero, + Equal, + NotEqual, + } + + /// + /// Texture or sampler pool state. + /// + struct PoolState + { +#pragma warning disable CS0649 // Field is never assigned to + public GpuVa Address; + public int MaximumId; +#pragma warning restore CS0649 + } + + /// + /// Stencil back test state. + /// + struct StencilBackTestState + { +#pragma warning disable CS0649 // Field is never assigned to + public Boolean32 TwoSided; + public StencilOp BackSFail; + public StencilOp BackDpFail; + public StencilOp BackDpPass; + public CompareOp BackFunc; +#pragma warning restore CS0649 + } + + /// + /// Primitive restart state. + /// + struct PrimitiveRestartState + { +#pragma warning disable CS0649 // Field is never assigned to + public Boolean32 Enable; + public int Index; +#pragma warning restore CS0649 + } + + /// + /// GPU index buffer state. + /// This is used on indexed draws. + /// + struct IndexBufferState + { +#pragma warning disable CS0649 // Field is never assigned to + public GpuVa Address; + public GpuVa EndAddress; + public IndexType Type; + public int First; +#pragma warning restore CS0649 + } + + /// + /// Face culling and orientation parameters. + /// + struct FaceState + { +#pragma warning disable CS0649 // Field is never assigned to + public Boolean32 CullEnable; + public FrontFace FrontFace; + public Face CullFace; +#pragma warning restore CS0649 + } + + /// + /// View volume clip control. + /// + [Flags] + enum ViewVolumeClipControl + { + ForceDepthRangeZeroToOne = 1 << 0, + DepthClampDisabled = 1 << 11, + } + + /// + /// Logical operation state. + /// + struct LogicalOpState + { +#pragma warning disable CS0649 // Field is never assigned to + public Boolean32 Enable; + public LogicalOp LogicalOp; +#pragma warning restore CS0649 + } + + /// + /// Render target color buffer mask. + /// This defines which color channels are written to the color buffer. + /// + struct RtColorMask + { + public uint Packed; + + public RtColorMask(uint packed) + { + Packed = packed; + } + + /// + /// Unpacks red channel enable. + /// + /// True to write the new red channel color, false to keep the old value + public readonly bool UnpackRed() + { + return (Packed & 0x1) != 0; + } + + /// + /// Unpacks green channel enable. + /// + /// True to write the new green channel color, false to keep the old value + public readonly bool UnpackGreen() + { + return (Packed & 0x10) != 0; + } + + /// + /// Unpacks blue channel enable. + /// + /// True to write the new blue channel color, false to keep the old value + public readonly bool UnpackBlue() + { + return (Packed & 0x100) != 0; + } + + /// + /// Unpacks alpha channel enable. + /// + /// True to write the new alpha channel color, false to keep the old value + public readonly bool UnpackAlpha() + { + return (Packed & 0x1000) != 0; + } + } + + /// + /// Vertex buffer state. + /// + struct VertexBufferState + { +#pragma warning disable CS0649 // Field is never assigned to + public uint Control; + public GpuVa Address; + public int Divisor; +#pragma warning restore CS0649 + + /// + /// Vertex buffer stride, defined as the number of bytes occupied by each vertex in memory. + /// + /// Vertex buffer stride + public readonly int UnpackStride() + { + return (int)(Control & 0xfff); + } + + /// + /// Vertex buffer enable. + /// + /// True if the vertex buffer is enabled, false otherwise + public readonly bool UnpackEnable() + { + return (Control & (1 << 12)) != 0; + } + } + + /// + /// Color buffer blending parameters, shared by all color buffers. + /// + struct BlendStateCommon + { +#pragma warning disable CS0649 // Field is never assigned to + public Boolean32 SeparateAlpha; + public BlendOp ColorOp; + public BlendFactor ColorSrcFactor; + public BlendFactor ColorDstFactor; + public BlendOp AlphaOp; + public BlendFactor AlphaSrcFactor; + public uint Unknown0x1354; + public BlendFactor AlphaDstFactor; +#pragma warning restore CS0649 + } + + /// + /// Color buffer blending parameters. + /// + struct BlendState + { +#pragma warning disable CS0649 // Field is never assigned to + public Boolean32 SeparateAlpha; + public BlendOp ColorOp; + public BlendFactor ColorSrcFactor; + public BlendFactor ColorDstFactor; + public BlendOp AlphaOp; + public BlendFactor AlphaSrcFactor; + public BlendFactor AlphaDstFactor; + public uint Padding; +#pragma warning restore CS0649 + } + + /// + /// Graphics shader stage state. + /// + struct ShaderState + { +#pragma warning disable CS0649 // Field is never assigned to + public uint Control; + public uint Offset; + public uint Unknown0x8; + public int MaxRegisters; + public ShaderType Type; + public uint Unknown0x14; + public uint Unknown0x18; + public uint Unknown0x1c; + public uint Unknown0x20; + public uint Unknown0x24; + public uint Unknown0x28; + public uint Unknown0x2c; + public uint Unknown0x30; + public uint Unknown0x34; + public uint Unknown0x38; + public uint Unknown0x3c; +#pragma warning restore CS0649 + + /// + /// Unpacks shader enable information. + /// Must be ignored for vertex shaders, those are always enabled. + /// + /// True if the stage is enabled, false otherwise + public readonly bool UnpackEnable() + { + return (Control & 1) != 0; + } + } + + /// + /// Uniform buffer state for the uniform buffer currently being modified. + /// + struct UniformBufferState + { +#pragma warning disable CS0649 // Field is never assigned to + public int Size; + public GpuVa Address; + public int Offset; +#pragma warning restore CS0649 + } + + unsafe struct ThreedClassState : IShadowState + { +#pragma warning disable CS0649 // Field is never assigned to + public uint SetObject; + public readonly int SetObjectClassId => (int)(SetObject & 0xFFFF); + public readonly int SetObjectEngineId => (int)((SetObject >> 16) & 0x1F); + public fixed uint Reserved04[63]; + public uint NoOperation; + public uint SetNotifyA; + public readonly int SetNotifyAAddressUpper => (int)(SetNotifyA & 0xFF); + public uint SetNotifyB; + public uint Notify; + public readonly NotifyType NotifyType => (NotifyType)(Notify); + public uint WaitForIdle; + public uint LoadMmeInstructionRamPointer; + public uint LoadMmeInstructionRam; + public uint LoadMmeStartAddressRamPointer; + public uint LoadMmeStartAddressRam; + public uint SetMmeShadowRamControl; + public readonly SetMmeShadowRamControlMode SetMmeShadowRamControlMode => (SetMmeShadowRamControlMode)(SetMmeShadowRamControl & 0x3); + public fixed uint Reserved128[2]; + public uint SetGlobalRenderEnableA; + public readonly int SetGlobalRenderEnableAOffsetUpper => (int)(SetGlobalRenderEnableA & 0xFF); + public uint SetGlobalRenderEnableB; + public uint SetGlobalRenderEnableC; + public readonly int SetGlobalRenderEnableCMode => (int)(SetGlobalRenderEnableC & 0x7); + public uint SendGoIdle; + public uint PmTrigger; + public uint PmTriggerWfi; + public fixed uint Reserved148[2]; + public uint SetInstrumentationMethodHeader; + public uint SetInstrumentationMethodData; + public fixed uint Reserved158[10]; + public uint LineLengthIn; + public uint LineCount; + public uint OffsetOutUpper; + public readonly int OffsetOutUpperValue => (int)(OffsetOutUpper & 0xFF); + public uint OffsetOut; + public uint PitchOut; + public uint SetDstBlockSize; + public readonly SetDstBlockSizeWidth SetDstBlockSizeWidth => (SetDstBlockSizeWidth)(SetDstBlockSize & 0xF); + public readonly SetDstBlockSizeHeight SetDstBlockSizeHeight => (SetDstBlockSizeHeight)((SetDstBlockSize >> 4) & 0xF); + public readonly SetDstBlockSizeDepth SetDstBlockSizeDepth => (SetDstBlockSizeDepth)((SetDstBlockSize >> 8) & 0xF); + public uint SetDstWidth; + public uint SetDstHeight; + public uint SetDstDepth; + public uint SetDstLayer; + public uint SetDstOriginBytesX; + public readonly int SetDstOriginBytesXV => (int)(SetDstOriginBytesX & 0xFFFFF); + public uint SetDstOriginSamplesY; + public readonly int SetDstOriginSamplesYV => (int)(SetDstOriginSamplesY & 0xFFFF); + public uint LaunchDma; + public readonly LaunchDmaDstMemoryLayout LaunchDmaDstMemoryLayout => (LaunchDmaDstMemoryLayout)(LaunchDma & 0x1); + public readonly LaunchDmaCompletionType LaunchDmaCompletionType => (LaunchDmaCompletionType)((LaunchDma >> 4) & 0x3); + public readonly LaunchDmaInterruptType LaunchDmaInterruptType => (LaunchDmaInterruptType)((LaunchDma >> 8) & 0x3); + public readonly LaunchDmaSemaphoreStructSize LaunchDmaSemaphoreStructSize => (LaunchDmaSemaphoreStructSize)((LaunchDma >> 12) & 0x1); + public readonly bool LaunchDmaReductionEnable => (LaunchDma & 0x2) != 0; + public readonly LaunchDmaReductionOp LaunchDmaReductionOp => (LaunchDmaReductionOp)((LaunchDma >> 13) & 0x7); + public readonly LaunchDmaReductionFormat LaunchDmaReductionFormat => (LaunchDmaReductionFormat)((LaunchDma >> 2) & 0x3); + public readonly bool LaunchDmaSysmembarDisable => (LaunchDma & 0x40) != 0; + public uint LoadInlineData; + public fixed uint Reserved1B8[22]; + public Boolean32 EarlyZForce; + public fixed uint Reserved214[45]; + public uint SyncpointAction; + public fixed uint Reserved2CC[10]; + public uint BlendUcodeNormalizedDst; + public fixed uint Reserved2F8[10]; + public TessMode TessMode; + public Array4 TessOuterLevel; + public Array2 TessInnerLevel; + public fixed uint Reserved33C[16]; + public Boolean32 RasterizeEnable; + public Array4 TfBufferState; + public fixed uint Reserved400[192]; + public Array4 TfState; + public fixed uint Reserved740[1]; + public Boolean32 TfEnable; + public fixed uint Reserved748[46]; + public Array8 RtColorState; + public Array16 ViewportTransform; + public Array16 ViewportExtents; + public fixed uint ReservedD00[29]; + public VertexBufferDrawState VertexBufferDrawState; + public uint DepthMode; + public ClearColors ClearColors; + public float ClearDepthValue; + public fixed uint ReservedD94[3]; + public uint ClearStencilValue; + public fixed uint ReservedDA4[2]; + public PolygonMode PolygonModeFront; + public PolygonMode PolygonModeBack; + public Boolean32 PolygonSmoothEnable; + public fixed uint ReservedDB8[2]; + public DepthBiasState DepthBiasState; + public int PatchVertices; + public BlendUcodeEnable BlendUcodeEnable; + public uint BlendUcodeSize; + public fixed uint ReservedDD8[2]; + public uint TextureBarrier; + public uint WatchdogTimer; + public Boolean32 PrimitiveRestartDrawArrays; + public uint ReservedDEC; + public uint LoadBlendUcodeStart; + public uint LoadBlendUcodeInstruction; + public fixed uint ReservedDF8[2]; + public Array16 ScissorState; + public fixed uint ReservedF00[21]; + public StencilBackMasks StencilBackMasks; + public fixed uint ReservedF60[5]; + public uint InvalidateTextures; + public fixed uint ReservedF78[1]; + public uint TextureBarrierTiled; + public fixed uint ReservedF80[4]; + public Boolean32 RtColorMaskShared; + public fixed uint ReservedF94[19]; + public RtDepthStencilState RtDepthStencilState; + public ScreenScissorState ScreenScissorState; + public fixed uint ReservedFFC[33]; + public int DrawTextureDstX; + public int DrawTextureDstY; + public int DrawTextureDstWidth; + public int DrawTextureDstHeight; + public long DrawTextureDuDx; + public long DrawTextureDvDy; + public int DrawTextureSamplerId; + public int DrawTextureTextureId; + public int DrawTextureSrcX; + public int DrawTextureSrcY; + public fixed uint Reserved10B0[18]; + public uint ClearFlags; + public fixed uint Reserved10FC[25]; + public Array32 VertexAttribState; + public fixed uint Reserved11E0[13]; + public uint DrawVertexArrayBeginEndInstanceFirst; + public uint DrawVertexArrayBeginEndInstanceSubsequent; + public RtControl RtControl; + public fixed uint Reserved1220[2]; + public Size3D RtDepthStencilSize; + public SamplerIndex SamplerIndex; + public fixed uint Reserved1238[37]; + public Boolean32 DepthTestEnable; + public fixed uint Reserved12D0[4]; + public Boolean32 AlphaToCoverageDitherEnable; + public Boolean32 BlendIndependent; + public Boolean32 DepthWriteEnable; + public Boolean32 AlphaTestEnable; + public fixed uint Reserved12F0[5]; + public uint VbElementU8; + public uint Reserved1308; + public CompareOp DepthTestFunc; + public float AlphaTestRef; + public CompareOp AlphaTestFunc; + public uint Reserved1318; + public ColorF BlendConstant; + public fixed uint Reserved132C[4]; + public BlendStateCommon BlendStateCommon; + public Boolean32 BlendEnableCommon; + public Array8 BlendEnable; + public StencilTestState StencilTestState; + public fixed uint Reserved13A0[3]; + public YControl YControl; + public float LineWidthSmooth; + public float LineWidthAliased; + public fixed uint Reserved13B8[27]; + public uint InvalidateSamplerCacheNoWfi; + public uint InvalidateTextureHeaderCacheNoWfi; + public fixed uint Reserved142C[2]; + public uint FirstVertex; + public uint FirstInstance; + public fixed uint Reserved143C[17]; + public Array8 BlendUcodeConstants; + public fixed uint Reserved1500[4]; + public uint ClipDistanceEnable; + public uint Reserved1514; + public float PointSize; + public uint Reserved151C; + public Boolean32 PointSpriteEnable; + public fixed uint Reserved1524[3]; + public uint ResetCounter; + public Boolean32 MultisampleEnable; + public Boolean32 RtDepthStencilEnable; + public uint MultisampleControl; + public fixed uint Reserved1540[4]; + public GpuVa RenderEnableAddress; + public Condition RenderEnableCondition; + public PoolState SamplerPoolState; + public uint Reserved1568; + public float DepthBiasFactor; + public Boolean32 LineSmoothEnable; + public PoolState TexturePoolState; + public fixed uint Reserved1580[5]; + public StencilBackTestState StencilBackTestState; + public fixed uint Reserved15A8[5]; + public float DepthBiasUnits; + public fixed uint Reserved15C0[4]; + public TextureMsaaMode RtMsaaMode; + public fixed uint Reserved15D4[5]; + public uint VbElementU32; + public uint Reserved15EC; + public uint VbElementU16; + public fixed uint Reserved15F4[4]; + public uint PointCoordReplace; + public GpuVa ShaderBaseAddress; + public uint Reserved1610; + public uint DrawEnd; + public uint DrawBegin; + public fixed uint Reserved161C[10]; + public PrimitiveRestartState PrimitiveRestartState; + public fixed uint Reserved164C[95]; + public IndexBufferState IndexBufferState; + public uint IndexBufferCount; + public uint DrawIndexBuffer32BeginEndInstanceFirst; + public uint DrawIndexBuffer16BeginEndInstanceFirst; + public uint DrawIndexBuffer8BeginEndInstanceFirst; + public uint DrawIndexBuffer32BeginEndInstanceSubsequent; + public uint DrawIndexBuffer16BeginEndInstanceSubsequent; + public uint DrawIndexBuffer8BeginEndInstanceSubsequent; + public fixed uint Reserved17FC[32]; + public float DepthBiasClamp; + public Array16 VertexBufferInstanced; + public fixed uint Reserved18C0[20]; + public Boolean32 VertexProgramPointSize; + public uint Reserved1914; + public FaceState FaceState; + public fixed uint Reserved1924[2]; + public uint ViewportTransformEnable; + public fixed uint Reserved1930[3]; + public ViewVolumeClipControl ViewVolumeClipControl; + public fixed uint Reserved1940[2]; + public Boolean32 PrimitiveTypeOverrideEnable; + public fixed uint Reserved194C[9]; + public PrimitiveTypeOverride PrimitiveTypeOverride; + public fixed uint Reserved1974[20]; + public LogicalOpState LogicOpState; + public uint Reserved19CC; + public uint Clear; + public fixed uint Reserved19D4[11]; + public Array8 RtColorMask; + public fixed uint Reserved1A20[56]; + public GpuVa SemaphoreAddress; + public int SemaphorePayload; + public uint SemaphoreControl; + public fixed uint Reserved1B10[60]; + public Array16 VertexBufferState; + public fixed uint Reserved1D00[64]; + public Array8 BlendState; + public Array16 VertexBufferEndAddress; + public fixed uint Reserved1F80[32]; + public Array6 ShaderState; + public fixed uint Reserved2180[96]; + public uint SetFalcon00; + public uint SetFalcon01; + public uint SetFalcon02; + public uint SetFalcon03; + public uint SetFalcon04; + public uint SetFalcon05; + public uint SetFalcon06; + public uint SetFalcon07; + public uint SetFalcon08; + public uint SetFalcon09; + public uint SetFalcon10; + public uint SetFalcon11; + public uint SetFalcon12; + public uint SetFalcon13; + public uint SetFalcon14; + public uint SetFalcon15; + public uint SetFalcon16; + public uint SetFalcon17; + public uint SetFalcon18; + public uint SetFalcon19; + public uint SetFalcon20; + public uint SetFalcon21; + public uint SetFalcon22; + public uint SetFalcon23; + public uint SetFalcon24; + public uint SetFalcon25; + public uint SetFalcon26; + public uint SetFalcon27; + public uint SetFalcon28; + public uint SetFalcon29; + public uint SetFalcon30; + public uint SetFalcon31; + public UniformBufferState UniformBufferState; + public Array16 UniformBufferUpdateData; + public fixed uint Reserved23D0[16]; + public uint UniformBufferBindVertex; + public fixed uint Reserved2414[7]; + public uint UniformBufferBindTessControl; + public fixed uint Reserved2434[7]; + public uint UniformBufferBindTessEvaluation; + public fixed uint Reserved2454[7]; + public uint UniformBufferBindGeometry; + public fixed uint Reserved2474[7]; + public uint UniformBufferBindFragment; + public fixed uint Reserved2494[93]; + public uint TextureBufferIndex; + public fixed uint Reserved260C[125]; + public Array4> TfVaryingLocations; + public fixed uint Reserved2A00[640]; + public Array256 SetMmeShadowScratch; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs new file mode 100644 index 00000000..0dd9481d --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs @@ -0,0 +1,386 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Texture; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace Ryujinx.Graphics.Gpu.Engine.Twod +{ + /// + /// Represents a 2D engine class. + /// + class TwodClass : IDeviceState + { + private readonly GpuChannel _channel; + private readonly DeviceState _state; + + /// + /// Creates a new instance of the 2D engine class. + /// + /// The channel that will make use of the engine + public TwodClass(GpuChannel channel) + { + _channel = channel; + _state = new DeviceState(new Dictionary + { + { nameof(TwodClassState.PixelsFromMemorySrcY0Int), new RwCallback(PixelsFromMemorySrcY0Int, null) }, + }); + } + + /// + /// Reads data from the class registers. + /// + /// Register byte offset + /// Data at the specified offset + public int Read(int offset) => _state.Read(offset); + + /// + /// Writes data to the class registers. + /// + /// Register byte offset + /// Data to be written + public void Write(int offset, int data) => _state.Write(offset, data); + + /// + /// Determines if data is compatible between the source and destination texture. + /// The two textures must have the same size, layout, and bytes per pixel. + /// + /// Info for the first texture + /// Info for the second texture + /// Format of the first texture + /// Format of the second texture + /// True if the data is compatible, false otherwise + private static bool IsDataCompatible(TwodTexture lhs, TwodTexture rhs, FormatInfo lhsFormat, FormatInfo rhsFormat) + { + if (lhsFormat.BytesPerPixel != rhsFormat.BytesPerPixel || + lhs.Height != rhs.Height || + lhs.Depth != rhs.Depth || + lhs.LinearLayout != rhs.LinearLayout || + lhs.MemoryLayout.Packed != rhs.MemoryLayout.Packed) + { + return false; + } + + if (lhs.LinearLayout) + { + return lhs.Stride == rhs.Stride; + } + else + { + return lhs.Width == rhs.Width; + } + } + + /// + /// Determine if the given region covers the full texture, also considering width alignment. + /// + /// The texture to check + /// + /// Region start x + /// Region start y + /// Region end x + /// Region end y + /// True if the region covers the full texture, false otherwise + private static bool IsCopyRegionComplete(TwodTexture texture, FormatInfo formatInfo, int x1, int y1, int x2, int y2) + { + if (x1 != 0 || y1 != 0 || y2 != texture.Height) + { + return false; + } + + int width; + int widthAlignment; + + if (texture.LinearLayout) + { + widthAlignment = 1; + width = texture.Stride / formatInfo.BytesPerPixel; + } + else + { + widthAlignment = Constants.GobAlignment / formatInfo.BytesPerPixel; + width = texture.Width; + } + + return width == BitUtils.AlignUp(x2, widthAlignment); + } + + /// + /// Performs a full data copy between two textures, reading and writing guest memory directly. + /// The textures must have a matching layout, size, and bytes per pixel. + /// + /// The source texture + /// The destination texture + /// Copy width + /// Copy height + /// Bytes per pixel + private void UnscaledFullCopy(TwodTexture src, TwodTexture dst, int w, int h, int bpp) + { + var srcCalculator = new OffsetCalculator( + w, + h, + src.Stride, + src.LinearLayout, + src.MemoryLayout.UnpackGobBlocksInY(), + src.MemoryLayout.UnpackGobBlocksInZ(), + bpp); + + (int _, int srcSize) = srcCalculator.GetRectangleRange(0, 0, w, h); + + var memoryManager = _channel.MemoryManager; + + ulong srcGpuVa = src.Address.Pack(); + ulong dstGpuVa = dst.Address.Pack(); + + ReadOnlySpan srcSpan = memoryManager.GetSpan(srcGpuVa, srcSize, true); + + int width; + int height = src.Height; + if (src.LinearLayout) + { + width = src.Stride / bpp; + } + else + { + width = src.Width; + } + + // If the copy is not equal to the width and height of the texture, we will need to copy partially. + // It's worth noting that it has already been established that the src and dst are the same size. + + if (w == width && h == height) + { + memoryManager.Write(dstGpuVa, srcSpan); + } + else + { + using WritableRegion dstRegion = memoryManager.GetWritableRegion(dstGpuVa, srcSize, true); + Span dstSpan = dstRegion.Memory.Span; + + if (src.LinearLayout) + { + int stride = src.Stride; + int offset = 0; + int lineSize = width * bpp; + + for (int y = 0; y < height; y++) + { + srcSpan.Slice(offset, lineSize).CopyTo(dstSpan[offset..]); + + offset += stride; + } + } + else + { + // Copy with the block linear layout in mind. + // Recreate the offset calculate with bpp 1 for copy. + + int stride = w * bpp; + + srcCalculator = new OffsetCalculator( + stride, + h, + 0, + false, + src.MemoryLayout.UnpackGobBlocksInY(), + src.MemoryLayout.UnpackGobBlocksInZ(), + 1); + + int strideTrunc = BitUtils.AlignDown(stride, 16); + + ReadOnlySpan> srcVec = MemoryMarshal.Cast>(srcSpan); + Span> dstVec = MemoryMarshal.Cast>(dstSpan); + + for (int y = 0; y < h; y++) + { + int x = 0; + + srcCalculator.SetY(y); + + for (; x < strideTrunc; x += 16) + { + int offset = srcCalculator.GetOffset(x) >> 4; + + dstVec[offset] = srcVec[offset]; + } + + for (; x < stride; x++) + { + int offset = srcCalculator.GetOffset(x); + + dstSpan[offset] = srcSpan[offset]; + } + } + } + } + } + + /// + /// Performs the blit operation, triggered by the register write. + /// + /// Method call argument + private void PixelsFromMemorySrcY0Int(int argument) + { + var memoryManager = _channel.MemoryManager; + + var dstCopyTexture = Unsafe.As(ref _state.State.SetDstFormat); + var srcCopyTexture = Unsafe.As(ref _state.State.SetSrcFormat); + + long srcX = ((long)_state.State.SetPixelsFromMemorySrcX0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcX0Frac; + long srcY = ((long)_state.State.PixelsFromMemorySrcY0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcY0Frac; + + long duDx = ((long)_state.State.SetPixelsFromMemoryDuDxInt << 32) | (long)(ulong)_state.State.SetPixelsFromMemoryDuDxFrac; + long dvDy = ((long)_state.State.SetPixelsFromMemoryDvDyInt << 32) | (long)(ulong)_state.State.SetPixelsFromMemoryDvDyFrac; + + bool originCorner = _state.State.SetPixelsFromMemorySampleModeOrigin == SetPixelsFromMemorySampleModeOrigin.Corner; + + if (originCorner) + { + // If the origin is corner, it is assumed that the guest API + // is manually centering the origin by adding a offset to the + // source region X/Y coordinates. + // Here we attempt to remove such offset to ensure we have the correct region. + // The offset is calculated as FactorXY / 2.0, where FactorXY = SrcXY / DstXY, + // so we do the same here by dividing the fixed point value by 2, while + // throwing away the fractional part to avoid rounding errors. + srcX -= (duDx >> 33) << 32; + srcY -= (dvDy >> 33) << 32; + } + + int srcX1 = (int)(srcX >> 32); + int srcY1 = (int)(srcY >> 32); + + int srcX2 = srcX1 + (int)((duDx * _state.State.SetPixelsFromMemoryDstWidth + uint.MaxValue) >> 32); + int srcY2 = srcY1 + (int)((dvDy * _state.State.SetPixelsFromMemoryDstHeight + uint.MaxValue) >> 32); + + int dstX1 = (int)_state.State.SetPixelsFromMemoryDstX0; + int dstY1 = (int)_state.State.SetPixelsFromMemoryDstY0; + + int dstX2 = dstX1 + (int)_state.State.SetPixelsFromMemoryDstWidth; + int dstY2 = dstY1 + (int)_state.State.SetPixelsFromMemoryDstHeight; + + // The source and destination textures should at least be as big as the region being requested. + // The hints will only resize within alignment constraints, so out of bound copies won't resize in most cases. + var srcHint = new Size(srcX2, srcY2, 1); + var dstHint = new Size(dstX2, dstY2, 1); + + var srcCopyTextureFormat = srcCopyTexture.Format.Convert(); + + int srcWidthAligned = srcCopyTexture.Stride / srcCopyTextureFormat.BytesPerPixel; + + ulong offset = 0; + + // For an out of bounds copy, we must ensure that the copy wraps to the next line, + // so for a copy from a 64x64 texture, in the region [32, 96[, there are 32 pixels that are + // outside the bounds of the texture. We fill the destination with the first 32 pixels + // of the next line on the source texture. + // This can be done by simply adding an offset to the texture address, so that the initial + // gap is skipped and the copy is inside bounds again. + // This is required by the proprietary guest OpenGL driver. + if (srcCopyTexture.LinearLayout && srcCopyTexture.Width == srcX2 && srcX2 > srcWidthAligned && srcX1 > 0) + { + offset = (ulong)(srcX1 * srcCopyTextureFormat.BytesPerPixel); + srcCopyTexture.Width -= srcX1; + srcX2 -= srcX1; + srcX1 = 0; + } + + FormatInfo dstCopyTextureFormat = dstCopyTexture.Format.Convert(); + + bool canDirectCopy = GraphicsConfig.Fast2DCopy && + srcX2 == dstX2 && srcY2 == dstY2 && + IsDataCompatible(srcCopyTexture, dstCopyTexture, srcCopyTextureFormat, dstCopyTextureFormat) && + IsCopyRegionComplete(srcCopyTexture, srcCopyTextureFormat, srcX1, srcY1, srcX2, srcY2) && + IsCopyRegionComplete(dstCopyTexture, dstCopyTextureFormat, dstX1, dstY1, dstX2, dstY2); + + // We can only allow aliasing of color formats as depth if the source and destination textures + // are the same, as we can't blit between different depth formats. + bool srcDepthAlias = srcCopyTexture.Format == dstCopyTexture.Format; + + var srcTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture( + memoryManager, + srcCopyTexture, + offset, + srcCopyTextureFormat, + srcDepthAlias, + !canDirectCopy, + false, + srcHint); + + if (srcTexture == null) + { + if (canDirectCopy) + { + // Directly copy the data on CPU. + UnscaledFullCopy(srcCopyTexture, dstCopyTexture, srcX2, srcY2, srcCopyTextureFormat.BytesPerPixel); + } + + return; + } + + memoryManager.Physical.TextureCache.Lift(srcTexture); + + // When the source texture that was found has a depth format, + // we must enforce the target texture also has a depth format, + // as copies between depth and color formats are not allowed. + // For depth blit, the destination texture format should always match exactly. + + if (srcTexture.Format.IsDepthOrStencil()) + { + dstCopyTextureFormat = srcTexture.Info.FormatInfo; + } + else + { + dstCopyTextureFormat = dstCopyTexture.Format.Convert(); + } + + var dstTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture( + memoryManager, + dstCopyTexture, + 0, + dstCopyTextureFormat, + depthAlias: false, + shouldCreate: true, + srcTexture.ScaleMode == TextureScaleMode.Scaled, + dstHint); + + if (dstTexture == null) + { + return; + } + + if (srcTexture.Info.Samples > 1 || dstTexture.Info.Samples > 1) + { + srcTexture.PropagateScale(dstTexture); + } + + float scale = srcTexture.ScaleFactor; + float dstScale = dstTexture.ScaleFactor; + + Extents2D srcRegion = new( + (int)Math.Ceiling(scale * (srcX1 / srcTexture.Info.SamplesInX)), + (int)Math.Ceiling(scale * (srcY1 / srcTexture.Info.SamplesInY)), + (int)Math.Ceiling(scale * (srcX2 / srcTexture.Info.SamplesInX)), + (int)Math.Ceiling(scale * (srcY2 / srcTexture.Info.SamplesInY))); + + Extents2D dstRegion = new( + (int)Math.Ceiling(dstScale * (dstX1 / dstTexture.Info.SamplesInX)), + (int)Math.Ceiling(dstScale * (dstY1 / dstTexture.Info.SamplesInY)), + (int)Math.Ceiling(dstScale * (dstX2 / dstTexture.Info.SamplesInX)), + (int)Math.Ceiling(dstScale * (dstY2 / dstTexture.Info.SamplesInY))); + + bool linearFilter = _state.State.SetPixelsFromMemorySampleModeFilter == SetPixelsFromMemorySampleModeFilter.Bilinear; + + srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter); + + dstTexture.SignalModified(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClassState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClassState.cs new file mode 100644 index 00000000..2ea4709e --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClassState.cs @@ -0,0 +1,816 @@ +// This file was auto-generated from NVIDIA official Maxwell definitions. + +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Gpu.Engine.Twod +{ + /// + /// Notify type. + /// + enum NotifyType + { + WriteOnly = 0, + WriteThenAwaken = 1, + } + + /// + /// Format of the destination texture. + /// + enum SetDstFormatV + { + A8r8g8b8 = 207, + A8rl8gl8bl8 = 208, + A2r10g10b10 = 223, + A8b8g8r8 = 213, + A8bl8gl8rl8 = 214, + A2b10g10r10 = 209, + X8r8g8b8 = 230, + X8rl8gl8bl8 = 231, + X8b8g8r8 = 249, + X8bl8gl8rl8 = 250, + R5g6b5 = 232, + A1r5g5b5 = 233, + X1r5g5b5 = 248, + Y8 = 243, + Y16 = 238, + Y32 = 255, + Z1r5g5b5 = 251, + O1r5g5b5 = 252, + Z8r8g8b8 = 253, + O8r8g8b8 = 254, + Y18x8 = 28, + Rf16 = 242, + Rf32 = 229, + Rf32Gf32 = 203, + Rf16Gf16Bf16Af16 = 202, + Rf16Gf16Bf16X16 = 206, + Rf32Gf32Bf32Af32 = 192, + Rf32Gf32Bf32X32 = 195, + R16G16B16A16 = 198, + Rn16Gn16Bn16An16 = 199, + Bf10gf11rf11 = 224, + An8bn8gn8rn8 = 215, + Rf16Gf16 = 222, + R16G16 = 218, + Rn16Gn16 = 219, + G8r8 = 234, + Gn8rn8 = 235, + Rn16 = 239, + Rn8 = 244, + A8 = 247, + } + + /// + /// Memory layout of the destination texture. + /// + enum SetDstMemoryLayoutV + { + Blocklinear = 0, + Pitch = 1, + } + + /// + /// Height in GOBs of the destination texture. + /// + enum SetDstBlockSizeHeight + { + OneGob = 0, + TwoGobs = 1, + FourGobs = 2, + EightGobs = 3, + SixteenGobs = 4, + ThirtytwoGobs = 5, + } + + /// + /// Depth in GOBs of the destination texture. + /// + enum SetDstBlockSizeDepth + { + OneGob = 0, + TwoGobs = 1, + FourGobs = 2, + EightGobs = 3, + SixteenGobs = 4, + ThirtytwoGobs = 5, + } + + /// + /// Format of the source texture. + /// + enum SetSrcFormatV + { + A8r8g8b8 = 207, + A8rl8gl8bl8 = 208, + A2r10g10b10 = 223, + A8b8g8r8 = 213, + A8bl8gl8rl8 = 214, + A2b10g10r10 = 209, + X8r8g8b8 = 230, + X8rl8gl8bl8 = 231, + X8b8g8r8 = 249, + X8bl8gl8rl8 = 250, + R5g6b5 = 232, + A1r5g5b5 = 233, + X1r5g5b5 = 248, + Y8 = 243, + Ay8 = 29, + Y16 = 238, + Y32 = 255, + Z1r5g5b5 = 251, + O1r5g5b5 = 252, + Z8r8g8b8 = 253, + O8r8g8b8 = 254, + Y18x8 = 28, + Rf16 = 242, + Rf32 = 229, + Rf32Gf32 = 203, + Rf16Gf16Bf16Af16 = 202, + Rf16Gf16Bf16X16 = 206, + Rf32Gf32Bf32Af32 = 192, + Rf32Gf32Bf32X32 = 195, + R16G16B16A16 = 198, + Rn16Gn16Bn16An16 = 199, + Bf10gf11rf11 = 224, + An8bn8gn8rn8 = 215, + Rf16Gf16 = 222, + R16G16 = 218, + Rn16Gn16 = 219, + G8r8 = 234, + Gn8rn8 = 235, + Rn16 = 239, + Rn8 = 244, + A8 = 247, + } + + /// + /// Memory layout of the source texture. + /// + enum SetSrcMemoryLayoutV + { + Blocklinear = 0, + Pitch = 1, + } + + /// + /// Height in GOBs of the source texture. + /// + enum SetSrcBlockSizeHeight + { + OneGob = 0, + TwoGobs = 1, + FourGobs = 2, + EightGobs = 3, + SixteenGobs = 4, + ThirtytwoGobs = 5, + } + + /// + /// Depth in GOBs of the source texture. + /// + enum SetSrcBlockSizeDepth + { + OneGob = 0, + TwoGobs = 1, + FourGobs = 2, + EightGobs = 3, + SixteenGobs = 4, + ThirtytwoGobs = 5, + } + + /// + /// Texture data caches to invalidate. + /// + enum TwodInvalidateTextureDataCacheV + { + L1Only = 0, + L2Only = 1, + L1AndL2 = 2, + } + + /// + /// Sector promotion parameters. + /// + enum SetPixelsFromMemorySectorPromotionV + { + NoPromotion = 0, + PromoteTo2V = 1, + PromoteTo2H = 2, + PromoteTo4 = 3, + } + + /// + /// Number of processing clusters. + /// + enum SetNumProcessingClustersV + { + All = 0, + One = 1, + } + + /// + /// Color key format. + /// + enum SetColorKeyFormatV + { + A16r5g6b5 = 0, + A1r5g5b5 = 1, + A8r8g8b8 = 2, + A2r10g10b10 = 3, + Y8 = 4, + Y16 = 5, + Y32 = 6, + } + + /// + /// Color blit operation. + /// + enum SetOperationV + { + SrccopyAnd = 0, + RopAnd = 1, + BlendAnd = 2, + Srccopy = 3, + Rop = 4, + SrccopyPremult = 5, + BlendPremult = 6, + } + + /// + /// Texture pattern selection. + /// + enum SetPatternSelectV + { + Monochrome8x8 = 0, + Monochrome64x1 = 1, + Monochrome1x64 = 2, + Color = 3, + } + + /// + /// Render enable override mode. + /// + enum SetRenderEnableOverrideMode + { + UseRenderEnable = 0, + AlwaysRender = 1, + NeverRender = 2, + } + + /// + /// Pixels from memory horizontal direction. + /// + enum SetPixelsFromMemoryDirectionHorizontal + { + HwDecides = 0, + LeftToRight = 1, + RightToLeft = 2, + } + + /// + /// Pixels from memory vertical direction. + /// + enum SetPixelsFromMemoryDirectionVertical + { + HwDecides = 0, + TopToBottom = 1, + BottomToTop = 2, + } + + /// + /// Color format of the monochrome pattern. + /// + enum SetMonochromePatternColorFormatV + { + A8x8r5g6b5 = 0, + A1r5g5b5 = 1, + A8r8g8b8 = 2, + A8y8 = 3, + A8x8y16 = 4, + Y32 = 5, + ByteExpand = 6, + } + + /// + /// Format of the monochrome pattern. + /// + enum SetMonochromePatternFormatV + { + Cga6M1 = 0, + LeM1 = 1, + } + + /// + /// DMA semaphore reduction operation. + /// + enum MmeDmaReductionReductionOp + { + RedAdd = 0, + RedMin = 1, + RedMax = 2, + RedInc = 3, + RedDec = 4, + RedAnd = 5, + RedOr = 6, + RedXor = 7, + } + + /// + /// DMA semaphore reduction format. + /// + enum MmeDmaReductionReductionFormat + { + Unsigned = 0, + Signed = 1, + } + + /// + /// DMA semaphore reduction size. + /// + enum MmeDmaReductionReductionSize + { + FourBytes = 0, + EightBytes = 1, + } + + /// + /// Data FIFO size. + /// + enum SetMmeDataFifoConfigFifoSize + { + Size0kb = 0, + Size4kb = 1, + Size8kb = 2, + Size12kb = 3, + Size16kb = 4, + } + + /// + /// Render solid primitive mode. + /// + enum RenderSolidPrimModeV + { + Points = 0, + Lines = 1, + Polyline = 2, + Triangles = 3, + Rects = 4, + } + + /// + /// Render solid primitive color format. + /// + enum SetRenderSolidPrimColorFormatV + { + Rf32Gf32Bf32Af32 = 192, + Rf16Gf16Bf16Af16 = 202, + Rf32Gf32 = 203, + A8r8g8b8 = 207, + A2r10g10b10 = 223, + A8b8g8r8 = 213, + A2b10g10r10 = 209, + X8r8g8b8 = 230, + X8b8g8r8 = 249, + R5g6b5 = 232, + A1r5g5b5 = 233, + X1r5g5b5 = 248, + Y8 = 243, + Y16 = 238, + Y32 = 255, + Z1r5g5b5 = 251, + O1r5g5b5 = 252, + Z8r8g8b8 = 253, + O8r8g8b8 = 254, + } + + /// + /// Pixels from CPU data type. + /// + enum SetPixelsFromCpuDataTypeV + { + Color = 0, + Index = 1, + } + + /// + /// Pixels from CPU color format. + /// + enum SetPixelsFromCpuColorFormatV + { + A8r8g8b8 = 207, + A2r10g10b10 = 223, + A8b8g8r8 = 213, + A2b10g10r10 = 209, + X8r8g8b8 = 230, + X8b8g8r8 = 249, + R5g6b5 = 232, + A1r5g5b5 = 233, + X1r5g5b5 = 248, + Y8 = 243, + Y16 = 238, + Y32 = 255, + Z1r5g5b5 = 251, + O1r5g5b5 = 252, + Z8r8g8b8 = 253, + O8r8g8b8 = 254, + } + + /// + /// Pixels from CPU palette index format. + /// + enum SetPixelsFromCpuIndexFormatV + { + I1 = 0, + I4 = 1, + I8 = 2, + } + + /// + /// Pixels from CPU monochrome format. + /// + enum SetPixelsFromCpuMonoFormatV + { + Cga6M1 = 0, + LeM1 = 1, + } + + /// + /// Pixels from CPU wrap mode. + /// + enum SetPixelsFromCpuWrapV + { + WrapPixel = 0, + WrapByte = 1, + WrapDword = 2, + } + + /// + /// Pixels from CPU monochrome opacity. + /// + enum SetPixelsFromCpuMonoOpacityV + { + Transparent = 0, + Opaque = 1, + } + + /// + /// Pixels from memory block shape. + /// + enum SetPixelsFromMemoryBlockShapeV + { + Auto = 0, + Shape8x8 = 1, + Shape16x4 = 2, + } + + /// + /// Pixels from memory origin. + /// + enum SetPixelsFromMemorySampleModeOrigin + { + Center = 0, + Corner = 1, + } + + /// + /// Pixels from memory filter mode. + /// + enum SetPixelsFromMemorySampleModeFilter + { + Point = 0, + Bilinear = 1, + } + + /// + /// Render solid primitive point coordinates. + /// + struct RenderSolidPrimPoint + { +#pragma warning disable CS0649 // Field is never assigned to + public uint SetX; + public uint Y; +#pragma warning restore CS0649 + } + + /// + /// 2D class state. + /// + unsafe struct TwodClassState : IShadowState + { +#pragma warning disable CS0649 // Field is never assigned to + public uint SetObject; + public readonly int SetObjectClassId => (int)(SetObject & 0xFFFF); + public readonly int SetObjectEngineId => (int)((SetObject >> 16) & 0x1F); + public fixed uint Reserved04[63]; + public uint NoOperation; + public uint SetNotifyA; + public readonly int SetNotifyAAddressUpper => (int)(SetNotifyA & 0x1FFFFFF); + public uint SetNotifyB; + public uint Notify; + public readonly NotifyType NotifyType => (NotifyType)(Notify); + public uint WaitForIdle; + public uint LoadMmeInstructionRamPointer; + public uint LoadMmeInstructionRam; + public uint LoadMmeStartAddressRamPointer; + public uint LoadMmeStartAddressRam; + public uint SetMmeShadowRamControl; + public readonly SetMmeShadowRamControlMode SetMmeShadowRamControlMode => (SetMmeShadowRamControlMode)(SetMmeShadowRamControl & 0x3); + public fixed uint Reserved128[2]; + public uint SetGlobalRenderEnableA; + public readonly int SetGlobalRenderEnableAOffsetUpper => (int)(SetGlobalRenderEnableA & 0xFF); + public uint SetGlobalRenderEnableB; + public uint SetGlobalRenderEnableC; + public readonly int SetGlobalRenderEnableCMode => (int)(SetGlobalRenderEnableC & 0x7); + public uint SendGoIdle; + public uint PmTrigger; + public fixed uint Reserved144[3]; + public uint SetInstrumentationMethodHeader; + public uint SetInstrumentationMethodData; + public fixed uint Reserved158[37]; + public uint SetMmeSwitchState; + public readonly bool SetMmeSwitchStateValid => (SetMmeSwitchState & 0x1) != 0; + public readonly int SetMmeSwitchStateSaveMacro => (int)((SetMmeSwitchState >> 4) & 0xFF); + public readonly int SetMmeSwitchStateRestoreMacro => (int)((SetMmeSwitchState >> 12) & 0xFF); + public fixed uint Reserved1F0[4]; + public uint SetDstFormat; + public readonly SetDstFormatV SetDstFormatV => (SetDstFormatV)(SetDstFormat & 0xFF); + public uint SetDstMemoryLayout; + public readonly SetDstMemoryLayoutV SetDstMemoryLayoutV => (SetDstMemoryLayoutV)(SetDstMemoryLayout & 0x1); + public uint SetDstBlockSize; + public readonly SetDstBlockSizeHeight SetDstBlockSizeHeight => (SetDstBlockSizeHeight)((SetDstBlockSize >> 4) & 0x7); + public readonly SetDstBlockSizeDepth SetDstBlockSizeDepth => (SetDstBlockSizeDepth)((SetDstBlockSize >> 8) & 0x7); + public uint SetDstDepth; + public uint SetDstLayer; + public uint SetDstPitch; + public uint SetDstWidth; + public uint SetDstHeight; + public uint SetDstOffsetUpper; + public readonly int SetDstOffsetUpperV => (int)(SetDstOffsetUpper & 0xFF); + public uint SetDstOffsetLower; + public uint FlushAndInvalidateRopMiniCache; + public readonly bool FlushAndInvalidateRopMiniCacheV => (FlushAndInvalidateRopMiniCache & 0x1) != 0; + public uint SetSpareNoop06; + public uint SetSrcFormat; + public readonly SetSrcFormatV SetSrcFormatV => (SetSrcFormatV)(SetSrcFormat & 0xFF); + public uint SetSrcMemoryLayout; + public readonly SetSrcMemoryLayoutV SetSrcMemoryLayoutV => (SetSrcMemoryLayoutV)(SetSrcMemoryLayout & 0x1); + public uint SetSrcBlockSize; + public readonly SetSrcBlockSizeHeight SetSrcBlockSizeHeight => (SetSrcBlockSizeHeight)((SetSrcBlockSize >> 4) & 0x7); + public readonly SetSrcBlockSizeDepth SetSrcBlockSizeDepth => (SetSrcBlockSizeDepth)((SetSrcBlockSize >> 8) & 0x7); + public uint SetSrcDepth; + public uint TwodInvalidateTextureDataCache; + public readonly TwodInvalidateTextureDataCacheV TwodInvalidateTextureDataCacheV => (TwodInvalidateTextureDataCacheV)(TwodInvalidateTextureDataCache & 0x3); + public uint SetSrcPitch; + public uint SetSrcWidth; + public uint SetSrcHeight; + public uint SetSrcOffsetUpper; + public readonly int SetSrcOffsetUpperV => (int)(SetSrcOffsetUpper & 0xFF); + public uint SetSrcOffsetLower; + public uint SetPixelsFromMemorySectorPromotion; + public readonly SetPixelsFromMemorySectorPromotionV SetPixelsFromMemorySectorPromotionV => (SetPixelsFromMemorySectorPromotionV)(SetPixelsFromMemorySectorPromotion & 0x3); + public uint SetSpareNoop12; + public uint SetNumProcessingClusters; + public readonly SetNumProcessingClustersV SetNumProcessingClustersV => (SetNumProcessingClustersV)(SetNumProcessingClusters & 0x1); + public uint SetRenderEnableA; + public readonly int SetRenderEnableAOffsetUpper => (int)(SetRenderEnableA & 0xFF); + public uint SetRenderEnableB; + public uint SetRenderEnableC; + public readonly int SetRenderEnableCMode => (int)(SetRenderEnableC & 0x7); + public uint SetSpareNoop08; + public uint SetSpareNoop01; + public uint SetSpareNoop11; + public uint SetSpareNoop07; + public uint SetClipX0; + public uint SetClipY0; + public uint SetClipWidth; + public uint SetClipHeight; + public uint SetClipEnable; + public readonly bool SetClipEnableV => (SetClipEnable & 0x1) != 0; + public uint SetColorKeyFormat; + public readonly SetColorKeyFormatV SetColorKeyFormatV => (SetColorKeyFormatV)(SetColorKeyFormat & 0x7); + public uint SetColorKey; + public uint SetColorKeyEnable; + public readonly bool SetColorKeyEnableV => (SetColorKeyEnable & 0x1) != 0; + public uint SetRop; + public readonly int SetRopV => (int)(SetRop & 0xFF); + public uint SetBeta1; + public uint SetBeta4; + public readonly int SetBeta4B => (int)(SetBeta4 & 0xFF); + public readonly int SetBeta4G => (int)((SetBeta4 >> 8) & 0xFF); + public readonly int SetBeta4R => (int)((SetBeta4 >> 16) & 0xFF); + public readonly int SetBeta4A => (int)((SetBeta4 >> 24) & 0xFF); + public uint SetOperation; + public readonly SetOperationV SetOperationV => (SetOperationV)(SetOperation & 0x7); + public uint SetPatternOffset; + public readonly int SetPatternOffsetX => (int)(SetPatternOffset & 0x3F); + public readonly int SetPatternOffsetY => (int)((SetPatternOffset >> 8) & 0x3F); + public uint SetPatternSelect; + public readonly SetPatternSelectV SetPatternSelectV => (SetPatternSelectV)(SetPatternSelect & 0x3); + public uint SetDstColorRenderToZetaSurface; + public readonly bool SetDstColorRenderToZetaSurfaceV => (SetDstColorRenderToZetaSurface & 0x1) != 0; + public uint SetSpareNoop04; + public uint SetSpareNoop15; + public uint SetSpareNoop13; + public uint SetSpareNoop03; + public uint SetSpareNoop14; + public uint SetSpareNoop02; + public uint SetCompression; + public readonly bool SetCompressionEnable => (SetCompression & 0x1) != 0; + public uint SetSpareNoop09; + public uint SetRenderEnableOverride; + public readonly SetRenderEnableOverrideMode SetRenderEnableOverrideMode => (SetRenderEnableOverrideMode)(SetRenderEnableOverride & 0x3); + public uint SetPixelsFromMemoryDirection; + public readonly SetPixelsFromMemoryDirectionHorizontal SetPixelsFromMemoryDirectionHorizontal => (SetPixelsFromMemoryDirectionHorizontal)(SetPixelsFromMemoryDirection & 0x3); + public readonly SetPixelsFromMemoryDirectionVertical SetPixelsFromMemoryDirectionVertical => (SetPixelsFromMemoryDirectionVertical)((SetPixelsFromMemoryDirection >> 4) & 0x3); + public uint SetSpareNoop10; + public uint SetMonochromePatternColorFormat; + public readonly SetMonochromePatternColorFormatV SetMonochromePatternColorFormatV => (SetMonochromePatternColorFormatV)(SetMonochromePatternColorFormat & 0x7); + public uint SetMonochromePatternFormat; + public readonly SetMonochromePatternFormatV SetMonochromePatternFormatV => (SetMonochromePatternFormatV)(SetMonochromePatternFormat & 0x1); + public uint SetMonochromePatternColor0; + public uint SetMonochromePatternColor1; + public uint SetMonochromePattern0; + public uint SetMonochromePattern1; + public Array64 ColorPatternX8r8g8b8; + public int ColorPatternX8r8g8b8B0(int i) => (int)((ColorPatternX8r8g8b8[i] >> 0) & 0xFF); + public int ColorPatternX8r8g8b8G0(int i) => (int)((ColorPatternX8r8g8b8[i] >> 8) & 0xFF); + public int ColorPatternX8r8g8b8R0(int i) => (int)((ColorPatternX8r8g8b8[i] >> 16) & 0xFF); + public int ColorPatternX8r8g8b8Ignore0(int i) => (int)((ColorPatternX8r8g8b8[i] >> 24) & 0xFF); + public Array32 ColorPatternR5g6b5; + public int ColorPatternR5g6b5B0(int i) => (int)((ColorPatternR5g6b5[i] >> 0) & 0x1F); + public int ColorPatternR5g6b5G0(int i) => (int)((ColorPatternR5g6b5[i] >> 5) & 0x3F); + public int ColorPatternR5g6b5R0(int i) => (int)((ColorPatternR5g6b5[i] >> 11) & 0x1F); + public int ColorPatternR5g6b5B1(int i) => (int)((ColorPatternR5g6b5[i] >> 16) & 0x1F); + public int ColorPatternR5g6b5G1(int i) => (int)((ColorPatternR5g6b5[i] >> 21) & 0x3F); + public int ColorPatternR5g6b5R1(int i) => (int)((ColorPatternR5g6b5[i] >> 27) & 0x1F); + public Array32 ColorPatternX1r5g5b5; + public int ColorPatternX1r5g5b5B0(int i) => (int)((ColorPatternX1r5g5b5[i] >> 0) & 0x1F); + public int ColorPatternX1r5g5b5G0(int i) => (int)((ColorPatternX1r5g5b5[i] >> 5) & 0x1F); + public int ColorPatternX1r5g5b5R0(int i) => (int)((ColorPatternX1r5g5b5[i] >> 10) & 0x1F); + public bool ColorPatternX1r5g5b5Ignore0(int i) => (ColorPatternX1r5g5b5[i] & 0x8000) != 0; + public int ColorPatternX1r5g5b5B1(int i) => (int)((ColorPatternX1r5g5b5[i] >> 16) & 0x1F); + public int ColorPatternX1r5g5b5G1(int i) => (int)((ColorPatternX1r5g5b5[i] >> 21) & 0x1F); + public int ColorPatternX1r5g5b5R1(int i) => (int)((ColorPatternX1r5g5b5[i] >> 26) & 0x1F); + public bool ColorPatternX1r5g5b5Ignore1(int i) => (ColorPatternX1r5g5b5[i] & 0x80000000) != 0; + public Array16 ColorPatternY8; + public int ColorPatternY8Y0(int i) => (int)((ColorPatternY8[i] >> 0) & 0xFF); + public int ColorPatternY8Y1(int i) => (int)((ColorPatternY8[i] >> 8) & 0xFF); + public int ColorPatternY8Y2(int i) => (int)((ColorPatternY8[i] >> 16) & 0xFF); + public int ColorPatternY8Y3(int i) => (int)((ColorPatternY8[i] >> 24) & 0xFF); + public uint SetRenderSolidPrimColor0; + public uint SetRenderSolidPrimColor1; + public uint SetRenderSolidPrimColor2; + public uint SetRenderSolidPrimColor3; + public uint SetMmeMemAddressA; + public readonly int SetMmeMemAddressAUpper => (int)(SetMmeMemAddressA & 0x1FFFFFF); + public uint SetMmeMemAddressB; + public uint SetMmeDataRamAddress; + public uint MmeDmaRead; + public uint MmeDmaReadFifoed; + public uint MmeDmaWrite; + public uint MmeDmaReduction; + public readonly MmeDmaReductionReductionOp MmeDmaReductionReductionOp => (MmeDmaReductionReductionOp)(MmeDmaReduction & 0x7); + public readonly MmeDmaReductionReductionFormat MmeDmaReductionReductionFormat => (MmeDmaReductionReductionFormat)((MmeDmaReduction >> 4) & 0x3); + public readonly MmeDmaReductionReductionSize MmeDmaReductionReductionSize => (MmeDmaReductionReductionSize)((MmeDmaReduction >> 8) & 0x1); + public uint MmeDmaSysmembar; + public readonly bool MmeDmaSysmembarV => (MmeDmaSysmembar & 0x1) != 0; + public uint MmeDmaSync; + public uint SetMmeDataFifoConfig; + public readonly SetMmeDataFifoConfigFifoSize SetMmeDataFifoConfigFifoSize => (SetMmeDataFifoConfigFifoSize)(SetMmeDataFifoConfig & 0x7); + public fixed uint Reserved578[2]; + public uint RenderSolidPrimMode; + public readonly RenderSolidPrimModeV RenderSolidPrimModeV => (RenderSolidPrimModeV)(RenderSolidPrimMode & 0x7); + public uint SetRenderSolidPrimColorFormat; + public readonly SetRenderSolidPrimColorFormatV SetRenderSolidPrimColorFormatV => (SetRenderSolidPrimColorFormatV)(SetRenderSolidPrimColorFormat & 0xFF); + public uint SetRenderSolidPrimColor; + public uint SetRenderSolidLineTieBreakBits; + public readonly bool SetRenderSolidLineTieBreakBitsXmajXincYinc => (SetRenderSolidLineTieBreakBits & 0x1) != 0; + public readonly bool SetRenderSolidLineTieBreakBitsXmajXdecYinc => (SetRenderSolidLineTieBreakBits & 0x10) != 0; + public readonly bool SetRenderSolidLineTieBreakBitsYmajXincYinc => (SetRenderSolidLineTieBreakBits & 0x100) != 0; + public readonly bool SetRenderSolidLineTieBreakBitsYmajXdecYinc => (SetRenderSolidLineTieBreakBits & 0x1000) != 0; + public fixed uint Reserved590[20]; + public uint RenderSolidPrimPointXY; + public readonly int RenderSolidPrimPointXYX => (int)(RenderSolidPrimPointXY & 0xFFFF); + public readonly int RenderSolidPrimPointXYY => (int)((RenderSolidPrimPointXY >> 16) & 0xFFFF); + public fixed uint Reserved5E4[7]; + public Array64 RenderSolidPrimPoint; + public uint SetPixelsFromCpuDataType; + public readonly SetPixelsFromCpuDataTypeV SetPixelsFromCpuDataTypeV => (SetPixelsFromCpuDataTypeV)(SetPixelsFromCpuDataType & 0x1); + public uint SetPixelsFromCpuColorFormat; + public readonly SetPixelsFromCpuColorFormatV SetPixelsFromCpuColorFormatV => (SetPixelsFromCpuColorFormatV)(SetPixelsFromCpuColorFormat & 0xFF); + public uint SetPixelsFromCpuIndexFormat; + public readonly SetPixelsFromCpuIndexFormatV SetPixelsFromCpuIndexFormatV => (SetPixelsFromCpuIndexFormatV)(SetPixelsFromCpuIndexFormat & 0x3); + public uint SetPixelsFromCpuMonoFormat; + public readonly SetPixelsFromCpuMonoFormatV SetPixelsFromCpuMonoFormatV => (SetPixelsFromCpuMonoFormatV)(SetPixelsFromCpuMonoFormat & 0x1); + public uint SetPixelsFromCpuWrap; + public readonly SetPixelsFromCpuWrapV SetPixelsFromCpuWrapV => (SetPixelsFromCpuWrapV)(SetPixelsFromCpuWrap & 0x3); + public uint SetPixelsFromCpuColor0; + public uint SetPixelsFromCpuColor1; + public uint SetPixelsFromCpuMonoOpacity; + public readonly SetPixelsFromCpuMonoOpacityV SetPixelsFromCpuMonoOpacityV => (SetPixelsFromCpuMonoOpacityV)(SetPixelsFromCpuMonoOpacity & 0x1); + public fixed uint Reserved820[6]; + public uint SetPixelsFromCpuSrcWidth; + public uint SetPixelsFromCpuSrcHeight; + public uint SetPixelsFromCpuDxDuFrac; + public uint SetPixelsFromCpuDxDuInt; + public uint SetPixelsFromCpuDyDvFrac; + public uint SetPixelsFromCpuDyDvInt; + public uint SetPixelsFromCpuDstX0Frac; + public uint SetPixelsFromCpuDstX0Int; + public uint SetPixelsFromCpuDstY0Frac; + public uint SetPixelsFromCpuDstY0Int; + public uint PixelsFromCpuData; + public fixed uint Reserved864[3]; + public uint SetBigEndianControl; + public readonly bool SetBigEndianControlX32Swap1 => (SetBigEndianControl & 0x1) != 0; + public readonly bool SetBigEndianControlX32Swap4 => (SetBigEndianControl & 0x2) != 0; + public readonly bool SetBigEndianControlX32Swap8 => (SetBigEndianControl & 0x4) != 0; + public readonly bool SetBigEndianControlX32Swap16 => (SetBigEndianControl & 0x8) != 0; + public readonly bool SetBigEndianControlX16Swap1 => (SetBigEndianControl & 0x10) != 0; + public readonly bool SetBigEndianControlX16Swap4 => (SetBigEndianControl & 0x20) != 0; + public readonly bool SetBigEndianControlX16Swap8 => (SetBigEndianControl & 0x40) != 0; + public readonly bool SetBigEndianControlX16Swap16 => (SetBigEndianControl & 0x80) != 0; + public readonly bool SetBigEndianControlX8Swap1 => (SetBigEndianControl & 0x100) != 0; + public readonly bool SetBigEndianControlX8Swap4 => (SetBigEndianControl & 0x200) != 0; + public readonly bool SetBigEndianControlX8Swap8 => (SetBigEndianControl & 0x400) != 0; + public readonly bool SetBigEndianControlX8Swap16 => (SetBigEndianControl & 0x800) != 0; + public readonly bool SetBigEndianControlI1X8Cga6Swap1 => (SetBigEndianControl & 0x1000) != 0; + public readonly bool SetBigEndianControlI1X8Cga6Swap4 => (SetBigEndianControl & 0x2000) != 0; + public readonly bool SetBigEndianControlI1X8Cga6Swap8 => (SetBigEndianControl & 0x4000) != 0; + public readonly bool SetBigEndianControlI1X8Cga6Swap16 => (SetBigEndianControl & 0x8000) != 0; + public readonly bool SetBigEndianControlI1X8LeSwap1 => (SetBigEndianControl & 0x10000) != 0; + public readonly bool SetBigEndianControlI1X8LeSwap4 => (SetBigEndianControl & 0x20000) != 0; + public readonly bool SetBigEndianControlI1X8LeSwap8 => (SetBigEndianControl & 0x40000) != 0; + public readonly bool SetBigEndianControlI1X8LeSwap16 => (SetBigEndianControl & 0x80000) != 0; + public readonly bool SetBigEndianControlI4Swap1 => (SetBigEndianControl & 0x100000) != 0; + public readonly bool SetBigEndianControlI4Swap4 => (SetBigEndianControl & 0x200000) != 0; + public readonly bool SetBigEndianControlI4Swap8 => (SetBigEndianControl & 0x400000) != 0; + public readonly bool SetBigEndianControlI4Swap16 => (SetBigEndianControl & 0x800000) != 0; + public readonly bool SetBigEndianControlI8Swap1 => (SetBigEndianControl & 0x1000000) != 0; + public readonly bool SetBigEndianControlI8Swap4 => (SetBigEndianControl & 0x2000000) != 0; + public readonly bool SetBigEndianControlI8Swap8 => (SetBigEndianControl & 0x4000000) != 0; + public readonly bool SetBigEndianControlI8Swap16 => (SetBigEndianControl & 0x8000000) != 0; + public readonly bool SetBigEndianControlOverride => (SetBigEndianControl & 0x10000000) != 0; + public fixed uint Reserved874[3]; + public uint SetPixelsFromMemoryBlockShape; + public readonly SetPixelsFromMemoryBlockShapeV SetPixelsFromMemoryBlockShapeV => (SetPixelsFromMemoryBlockShapeV)(SetPixelsFromMemoryBlockShape & 0x7); + public uint SetPixelsFromMemoryCorralSize; + public readonly int SetPixelsFromMemoryCorralSizeV => (int)(SetPixelsFromMemoryCorralSize & 0x3FF); + public uint SetPixelsFromMemorySafeOverlap; + public readonly bool SetPixelsFromMemorySafeOverlapV => (SetPixelsFromMemorySafeOverlap & 0x1) != 0; + public uint SetPixelsFromMemorySampleMode; + public readonly SetPixelsFromMemorySampleModeOrigin SetPixelsFromMemorySampleModeOrigin => (SetPixelsFromMemorySampleModeOrigin)(SetPixelsFromMemorySampleMode & 0x1); + public readonly SetPixelsFromMemorySampleModeFilter SetPixelsFromMemorySampleModeFilter => (SetPixelsFromMemorySampleModeFilter)((SetPixelsFromMemorySampleMode >> 4) & 0x1); + public fixed uint Reserved890[8]; + public uint SetPixelsFromMemoryDstX0; + public uint SetPixelsFromMemoryDstY0; + public uint SetPixelsFromMemoryDstWidth; + public uint SetPixelsFromMemoryDstHeight; + public uint SetPixelsFromMemoryDuDxFrac; + public uint SetPixelsFromMemoryDuDxInt; + public uint SetPixelsFromMemoryDvDyFrac; + public uint SetPixelsFromMemoryDvDyInt; + public uint SetPixelsFromMemorySrcX0Frac; + public uint SetPixelsFromMemorySrcX0Int; + public uint SetPixelsFromMemorySrcY0Frac; + public uint PixelsFromMemorySrcY0Int; + public uint SetFalcon00; + public uint SetFalcon01; + public uint SetFalcon02; + public uint SetFalcon03; + public uint SetFalcon04; + public uint SetFalcon05; + public uint SetFalcon06; + public uint SetFalcon07; + public uint SetFalcon08; + public uint SetFalcon09; + public uint SetFalcon10; + public uint SetFalcon11; + public uint SetFalcon12; + public uint SetFalcon13; + public uint SetFalcon14; + public uint SetFalcon15; + public uint SetFalcon16; + public uint SetFalcon17; + public uint SetFalcon18; + public uint SetFalcon19; + public uint SetFalcon20; + public uint SetFalcon21; + public uint SetFalcon22; + public uint SetFalcon23; + public uint SetFalcon24; + public uint SetFalcon25; + public uint SetFalcon26; + public uint SetFalcon27; + public uint SetFalcon28; + public uint SetFalcon29; + public uint SetFalcon30; + public uint SetFalcon31; + public fixed uint Reserved960[291]; + public uint MmeDmaWriteMethodBarrier; + public readonly bool MmeDmaWriteMethodBarrierV => (MmeDmaWriteMethodBarrier & 0x1) != 0; + public fixed uint ReservedDF0[2436]; + public Array256 SetMmeShadowScratch; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodTexture.cs b/src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodTexture.cs new file mode 100644 index 00000000..8e8afb41 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodTexture.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Gpu.Engine.Types; + +namespace Ryujinx.Graphics.Gpu.Engine.Twod +{ + /// + /// Texture to texture (with optional resizing) copy parameters. + /// + struct TwodTexture + { +#pragma warning disable CS0649 // Field is never assigned to + public ColorFormat Format; + public Boolean32 LinearLayout; + public MemoryLayout MemoryLayout; + public int Depth; + public int Layer; + public int Stride; + public int Width; + public int Height; + public GpuVa Address; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs b/src/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs new file mode 100644 index 00000000..0db83da8 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// + /// Boolean value, stored as a 32-bits integer in memory. + /// + readonly struct Boolean32 + { + private readonly uint _value; + + public Boolean32(uint value) + { + _value = value; + } + + public static implicit operator bool(Boolean32 value) + { + return (value._value & 1) != 0; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs b/src/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs new file mode 100644 index 00000000..273438a6 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs @@ -0,0 +1,169 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; + +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// + /// Color texture format. + /// + enum ColorFormat + { + R32G32B32A32Float = 0xc0, + R32G32B32A32Sint = 0xc1, + R32G32B32A32Uint = 0xc2, + R32G32B32X32Float = 0xc3, + R32G32B32X32Sint = 0xc4, + R32G32B32X32Uint = 0xc5, + R16G16B16X16Unorm = 0xc6, + R16G16B16X16Snorm = 0xc7, + R16G16B16X16Sint = 0xc8, + R16G16B16X16Uint = 0xc9, + R16G16B16A16Float = 0xca, + R32G32Float = 0xcb, + R32G32Sint = 0xcc, + R32G32Uint = 0xcd, + R16G16B16X16Float = 0xce, + B8G8R8A8Unorm = 0xcf, + B8G8R8A8Srgb = 0xd0, + R10G10B10A2Unorm = 0xd1, + R10G10B10A2Uint = 0xd2, + R8G8B8A8Unorm = 0xd5, + R8G8B8A8Srgb = 0xd6, + R8G8B8X8Snorm = 0xd7, + R8G8B8X8Sint = 0xd8, + R8G8B8X8Uint = 0xd9, + R16G16Unorm = 0xda, + R16G16Snorm = 0xdb, + R16G16Sint = 0xdc, + R16G16Uint = 0xdd, + R16G16Float = 0xde, + B10G10R10A2Unorm = 0xdf, + R11G11B10Float = 0xe0, + R32Sint = 0xe3, + R32Uint = 0xe4, + R32Float = 0xe5, + B8G8R8X8Unorm = 0xe6, + B8G8R8X8Srgb = 0xe7, + B5G6R5Unorm = 0xe8, + B5G5R5A1Unorm = 0xe9, + R8G8Unorm = 0xea, + R8G8Snorm = 0xeb, + R8G8Sint = 0xec, + R8G8Uint = 0xed, + R16Unorm = 0xee, + R16Snorm = 0xef, + R16Sint = 0xf0, + R16Uint = 0xf1, + R16Float = 0xf2, + R8Unorm = 0xf3, + R8Snorm = 0xf4, + R8Sint = 0xf5, + R8Uint = 0xf6, + B5G5R5X1Unorm = 0xf8, + R8G8B8X8Unorm = 0xf9, + R8G8B8X8Srgb = 0xfa, + } + + static class ColorFormatConverter + { + /// + /// Converts the color texture format to a host compatible format. + /// + /// Color format + /// Host compatible format enum value + public static FormatInfo Convert(this ColorFormat format) + { + return format switch + { +#pragma warning disable IDE0055 // Disable formatting + ColorFormat.R32G32B32A32Float => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4), + ColorFormat.R32G32B32A32Sint => new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16, 4), + ColorFormat.R32G32B32A32Uint => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4), + ColorFormat.R32G32B32X32Float => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4), + ColorFormat.R32G32B32X32Sint => new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16, 4), + ColorFormat.R32G32B32X32Uint => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4), + ColorFormat.R16G16B16X16Unorm => new FormatInfo(Format.R16G16B16A16Unorm, 1, 1, 8, 4), + ColorFormat.R16G16B16X16Snorm => new FormatInfo(Format.R16G16B16A16Snorm, 1, 1, 8, 4), + ColorFormat.R16G16B16X16Sint => new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8, 4), + ColorFormat.R16G16B16X16Uint => new FormatInfo(Format.R16G16B16A16Uint, 1, 1, 8, 4), + ColorFormat.R16G16B16A16Float => new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4), + ColorFormat.R32G32Float => new FormatInfo(Format.R32G32Float, 1, 1, 8, 2), + ColorFormat.R32G32Sint => new FormatInfo(Format.R32G32Sint, 1, 1, 8, 2), + ColorFormat.R32G32Uint => new FormatInfo(Format.R32G32Uint, 1, 1, 8, 2), + ColorFormat.R16G16B16X16Float => new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4), + ColorFormat.B8G8R8A8Unorm => new FormatInfo(Format.B8G8R8A8Unorm, 1, 1, 4, 4), + ColorFormat.B8G8R8A8Srgb => new FormatInfo(Format.B8G8R8A8Srgb, 1, 1, 4, 4), + ColorFormat.R10G10B10A2Unorm => new FormatInfo(Format.R10G10B10A2Unorm, 1, 1, 4, 4), + ColorFormat.R10G10B10A2Uint => new FormatInfo(Format.R10G10B10A2Uint, 1, 1, 4, 4), + ColorFormat.R8G8B8A8Unorm => new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4), + ColorFormat.R8G8B8A8Srgb => new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4), + ColorFormat.R8G8B8X8Snorm => new FormatInfo(Format.R8G8B8A8Snorm, 1, 1, 4, 4), + ColorFormat.R8G8B8X8Sint => new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4, 4), + ColorFormat.R8G8B8X8Uint => new FormatInfo(Format.R8G8B8A8Uint, 1, 1, 4, 4), + ColorFormat.R16G16Unorm => new FormatInfo(Format.R16G16Unorm, 1, 1, 4, 2), + ColorFormat.R16G16Snorm => new FormatInfo(Format.R16G16Snorm, 1, 1, 4, 2), + ColorFormat.R16G16Sint => new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2), + ColorFormat.R16G16Uint => new FormatInfo(Format.R16G16Uint, 1, 1, 4, 2), + ColorFormat.R16G16Float => new FormatInfo(Format.R16G16Float, 1, 1, 4, 2), + ColorFormat.B10G10R10A2Unorm => new FormatInfo(Format.B10G10R10A2Unorm, 1, 1, 4, 4), + ColorFormat.R11G11B10Float => new FormatInfo(Format.R11G11B10Float, 1, 1, 4, 3), + ColorFormat.R32Sint => new FormatInfo(Format.R32Sint, 1, 1, 4, 1), + ColorFormat.R32Uint => new FormatInfo(Format.R32Uint, 1, 1, 4, 1), + ColorFormat.R32Float => new FormatInfo(Format.R32Float, 1, 1, 4, 1), + ColorFormat.B8G8R8X8Unorm => new FormatInfo(Format.B8G8R8A8Unorm, 1, 1, 4, 4), + ColorFormat.B8G8R8X8Srgb => new FormatInfo(Format.B8G8R8A8Srgb, 1, 1, 4, 4), + ColorFormat.B5G6R5Unorm => new FormatInfo(Format.B5G6R5Unorm, 1, 1, 2, 3), + ColorFormat.B5G5R5A1Unorm => new FormatInfo(Format.B5G5R5A1Unorm, 1, 1, 2, 4), + ColorFormat.R8G8Unorm => new FormatInfo(Format.R8G8Unorm, 1, 1, 2, 2), + ColorFormat.R8G8Snorm => new FormatInfo(Format.R8G8Snorm, 1, 1, 2, 2), + ColorFormat.R8G8Sint => new FormatInfo(Format.R8G8Sint, 1, 1, 2, 2), + ColorFormat.R8G8Uint => new FormatInfo(Format.R8G8Uint, 1, 1, 2, 2), + ColorFormat.R16Unorm => new FormatInfo(Format.R16Unorm, 1, 1, 2, 1), + ColorFormat.R16Snorm => new FormatInfo(Format.R16Snorm, 1, 1, 2, 1), + ColorFormat.R16Sint => new FormatInfo(Format.R16Sint, 1, 1, 2, 1), + ColorFormat.R16Uint => new FormatInfo(Format.R16Uint, 1, 1, 2, 1), + ColorFormat.R16Float => new FormatInfo(Format.R16Float, 1, 1, 2, 1), + ColorFormat.R8Unorm => new FormatInfo(Format.R8Unorm, 1, 1, 1, 1), + ColorFormat.R8Snorm => new FormatInfo(Format.R8Snorm, 1, 1, 1, 1), + ColorFormat.R8Sint => new FormatInfo(Format.R8Sint, 1, 1, 1, 1), + ColorFormat.R8Uint => new FormatInfo(Format.R8Uint, 1, 1, 1, 1), + ColorFormat.B5G5R5X1Unorm => new FormatInfo(Format.B5G5R5A1Unorm, 1, 1, 2, 4), + ColorFormat.R8G8B8X8Unorm => new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4), + ColorFormat.R8G8B8X8Srgb => new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4), + _ => FormatInfo.Default, +#pragma warning restore IDE0055 + }; + } + + /// + /// Checks if a format has an alpha component. + /// + /// Format to be checked + /// True if the format has no alpha component (RGBX), false if it does (RGBA) + public static bool NoAlpha(this ColorFormat format) + { + switch (format) + { + case ColorFormat.R32G32B32X32Float: + case ColorFormat.R32G32B32X32Sint: + case ColorFormat.R32G32B32X32Uint: + case ColorFormat.R16G16B16X16Unorm: + case ColorFormat.R16G16B16X16Snorm: + case ColorFormat.R16G16B16X16Sint: + case ColorFormat.R16G16B16X16Uint: + case ColorFormat.R16G16B16X16Float: + case ColorFormat.R8G8B8X8Snorm: + case ColorFormat.R8G8B8X8Sint: + case ColorFormat.R8G8B8X8Uint: + case ColorFormat.B8G8R8X8Unorm: + case ColorFormat.B8G8R8X8Srgb: + case ColorFormat.B5G5R5X1Unorm: + case ColorFormat.R8G8B8X8Unorm: + case ColorFormat.R8G8B8X8Srgb: + return true; + default: + return false; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Types/GpuVa.cs b/src/Ryujinx.Graphics.Gpu/Engine/Types/GpuVa.cs new file mode 100644 index 00000000..40459990 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Types/GpuVa.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// + /// Split GPU virtual address. + /// + struct GpuVa + { +#pragma warning disable CS0649 // Field is never assigned to + public uint High; + public uint Low; +#pragma warning restore CS0649 + + /// + /// Packs the split address into a 64-bits address value. + /// + /// The 64-bits address value + public readonly ulong Pack() + { + return Low | ((ulong)High << 32); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Types/MemoryLayout.cs b/src/Ryujinx.Graphics.Gpu/Engine/Types/MemoryLayout.cs new file mode 100644 index 00000000..9af5e610 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Types/MemoryLayout.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// + /// Memory layout parameters, for block linear textures. + /// + struct MemoryLayout + { +#pragma warning disable CS0649 // Field is never assigned to + public uint Packed; +#pragma warning restore CS0649 + + public readonly int UnpackGobBlocksInX() + { + return 1 << (int)(Packed & 0xf); + } + + public readonly int UnpackGobBlocksInY() + { + return 1 << (int)((Packed >> 4) & 0xf); + } + + public readonly int UnpackGobBlocksInZ() + { + return 1 << (int)((Packed >> 8) & 0xf); + } + + public readonly bool UnpackIsLinear() + { + return (Packed & 0x1000) != 0; + } + + public readonly bool UnpackIsTarget3D() + { + return (Packed & 0x10000) != 0; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Types/PrimitiveType.cs b/src/Ryujinx.Graphics.Gpu/Engine/Types/PrimitiveType.cs new file mode 100644 index 00000000..5abbc923 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Types/PrimitiveType.cs @@ -0,0 +1,103 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// + /// Draw primitive type. + /// + enum PrimitiveType + { + Points, + Lines, + LineLoop, + LineStrip, + Triangles, + TriangleStrip, + TriangleFan, + Quads, + QuadStrip, + Polygon, + LinesAdjacency, + LineStripAdjacency, + TrianglesAdjacency, + TriangleStripAdjacency, + Patches, + } + + /// + /// Alternative primitive type that might override . + /// + enum PrimitiveTypeOverride + { + Points = 1, + Lines = 2, + LineStrip = 3, + Triangles = 4, + TriangleStrip = 5, + TriangleFan = 0x1015, + LinesAdjacency = 10, + LineStripAdjacency = 11, + TrianglesAdjacency = 12, + TriangleStripAdjacency = 13, + Patches = 14, + } + + static class PrimitiveTypeConverter + { + /// + /// Converts the primitive type into something that can be used with the host API. + /// + /// The primitive type to convert + /// A host compatible enum value + public static PrimitiveTopology Convert(this PrimitiveType type) + { + return type switch + { +#pragma warning disable IDE0055 // Disable formatting + PrimitiveType.Points => PrimitiveTopology.Points, + PrimitiveType.Lines => PrimitiveTopology.Lines, + PrimitiveType.LineLoop => PrimitiveTopology.LineLoop, + PrimitiveType.LineStrip => PrimitiveTopology.LineStrip, + PrimitiveType.Triangles => PrimitiveTopology.Triangles, + PrimitiveType.TriangleStrip => PrimitiveTopology.TriangleStrip, + PrimitiveType.TriangleFan => PrimitiveTopology.TriangleFan, + PrimitiveType.Quads => PrimitiveTopology.Quads, + PrimitiveType.QuadStrip => PrimitiveTopology.QuadStrip, + PrimitiveType.Polygon => PrimitiveTopology.Polygon, + PrimitiveType.LinesAdjacency => PrimitiveTopology.LinesAdjacency, + PrimitiveType.LineStripAdjacency => PrimitiveTopology.LineStripAdjacency, + PrimitiveType.TrianglesAdjacency => PrimitiveTopology.TrianglesAdjacency, + PrimitiveType.TriangleStripAdjacency => PrimitiveTopology.TriangleStripAdjacency, + PrimitiveType.Patches => PrimitiveTopology.Patches, + _ => PrimitiveTopology.Triangles, +#pragma warning restore IDE0055 + }; + } + + /// + /// Converts the primitive type into something that can be used with the host API. + /// + /// The primitive type to convert + /// A host compatible enum value + public static PrimitiveTopology Convert(this PrimitiveTypeOverride type) + { + return type switch + { +#pragma warning disable IDE0055 // Disable formatting + PrimitiveTypeOverride.Points => PrimitiveTopology.Points, + PrimitiveTypeOverride.Lines => PrimitiveTopology.Lines, + PrimitiveTypeOverride.LineStrip => PrimitiveTopology.LineStrip, + PrimitiveTypeOverride.Triangles => PrimitiveTopology.Triangles, + PrimitiveTypeOverride.TriangleStrip => PrimitiveTopology.TriangleStrip, + PrimitiveTypeOverride.TriangleFan => PrimitiveTopology.TriangleFan, + PrimitiveTypeOverride.LinesAdjacency => PrimitiveTopology.LinesAdjacency, + PrimitiveTypeOverride.LineStripAdjacency => PrimitiveTopology.LineStripAdjacency, + PrimitiveTypeOverride.TrianglesAdjacency => PrimitiveTopology.TrianglesAdjacency, + PrimitiveTypeOverride.TriangleStripAdjacency => PrimitiveTopology.TriangleStripAdjacency, + PrimitiveTypeOverride.Patches => PrimitiveTopology.Patches, + _ => PrimitiveTopology.Triangles, +#pragma warning restore IDE0055 + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Types/SamplerIndex.cs b/src/Ryujinx.Graphics.Gpu/Engine/Types/SamplerIndex.cs new file mode 100644 index 00000000..22fe4a92 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Types/SamplerIndex.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// + /// Sampler pool indexing mode. + /// + enum SamplerIndex + { + Independently = 0, + ViaHeaderIndex = 1, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Types/SbDescriptor.cs b/src/Ryujinx.Graphics.Gpu/Engine/Types/SbDescriptor.cs new file mode 100644 index 00000000..e1cc8c01 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Types/SbDescriptor.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// + /// Storage buffer address and size information. + /// + struct SbDescriptor + { +#pragma warning disable CS0649 // Field is never assigned to + public uint AddressLow; + public uint AddressHigh; + public int Size; + public int Padding; +#pragma warning restore CS0649 + + public readonly ulong PackAddress() + { + return AddressLow | ((ulong)AddressHigh << 32); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Types/ZetaFormat.cs b/src/Ryujinx.Graphics.Gpu/Engine/Types/ZetaFormat.cs new file mode 100644 index 00000000..88fbe88f --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/Types/ZetaFormat.cs @@ -0,0 +1,44 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; + +namespace Ryujinx.Graphics.Gpu.Engine.Types +{ + /// + /// Depth-stencil texture format. + /// + enum ZetaFormat + { + Zf32 = 0xa, + Z16 = 0x13, + Z24S8 = 0x14, + X8Z24 = 0x15, + S8Z24 = 0x16, + S8Uint = 0x17, + Zf32X24S8 = 0x19, + } + + static class ZetaFormatConverter + { + /// + /// Converts the depth-stencil texture format to a host compatible format. + /// + /// Depth-stencil format + /// Host compatible format enum value + public static FormatInfo Convert(this ZetaFormat format) + { + return format switch + { +#pragma warning disable IDE0055 // Disable formatting + ZetaFormat.Zf32 => new FormatInfo(Format.D32Float, 1, 1, 4, 1), + ZetaFormat.Z16 => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1), + ZetaFormat.Z24S8 => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2), + ZetaFormat.X8Z24 => new FormatInfo(Format.X8UintD24Unorm, 1, 1, 4, 1), + ZetaFormat.S8Z24 => new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2), + ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1), + ZetaFormat.Zf32X24S8 => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2), + _ => FormatInfo.Default, +#pragma warning restore IDE0055 + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/GpuChannel.cs b/src/Ryujinx.Graphics.Gpu/GpuChannel.cs new file mode 100644 index 00000000..33618a15 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/GpuChannel.cs @@ -0,0 +1,150 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.GPFifo; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Memory; +using System; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu +{ + /// + /// Represents a GPU channel. + /// + public class GpuChannel : IDisposable + { + private readonly GpuContext _context; + private readonly GPFifoDevice _device; + private readonly GPFifoProcessor _processor; + private MemoryManager _memoryManager; + + /// + /// Channel buffer bindings manager. + /// + internal BufferManager BufferManager { get; } + + /// + /// Channel texture bindings manager. + /// + internal TextureManager TextureManager { get; } + + /// + /// Current channel memory manager. + /// + internal MemoryManager MemoryManager => _memoryManager; + + /// + /// Host hardware capabilities from the GPU context. + /// + internal ref Capabilities Capabilities => ref _context.Capabilities; + + /// + /// Creates a new instance of a GPU channel. + /// + /// GPU context that the channel belongs to + internal GpuChannel(GpuContext context) + { + _context = context; + _device = context.GPFifo; + _processor = new GPFifoProcessor(context, this); + BufferManager = new BufferManager(context, this); + TextureManager = new TextureManager(context, this); + } + + /// + /// Binds a memory manager to the channel. + /// All submitted and in-flight commands will use the specified memory manager for any memory operations. + /// + /// The new memory manager to be bound + public void BindMemory(MemoryManager memoryManager) + { + var oldMemoryManager = Interlocked.Exchange(ref _memoryManager, memoryManager ?? throw new ArgumentNullException(nameof(memoryManager))); + + memoryManager.Physical.IncrementReferenceCount(); + + if (oldMemoryManager != null) + { + oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind; + oldMemoryManager.Physical.DecrementReferenceCount(); + oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler; + } + + memoryManager.Physical.BufferCache.NotifyBuffersModified += BufferManager.Rebind; + memoryManager.MemoryUnmapped += MemoryUnmappedHandler; + + // Since the memory manager changed, make sure we will get pools from addresses of the new memory manager. + TextureManager.ReloadPools(); + memoryManager.Physical.BufferCache.QueuePrune(); + } + + /// + /// Memory mappings change event handler. + /// + /// Memory manager where the mappings changed + /// Information about the region that is being changed + private void MemoryUnmappedHandler(object sender, UnmapEventArgs e) + { + TextureManager.ReloadPools(); + + var memoryManager = Volatile.Read(ref _memoryManager); + memoryManager?.Physical.BufferCache.QueuePrune(); + } + + /// + /// Writes data directly to the state of the specified class. + /// + /// ID of the class to write the data into + /// State offset in bytes + /// Value to be written + public void Write(ClassId classId, int offset, uint value) + { + _processor.Write(classId, offset, (int)value); + } + + /// + /// Push a GPFIFO entry in the form of a prefetched command buffer. + /// It is intended to be used by nvservices to handle special cases. + /// + /// The command buffer containing the prefetched commands + public void PushHostCommandBuffer(int[] commandBuffer) + { + _device.PushHostCommandBuffer(_processor, commandBuffer); + } + + /// + /// Pushes GPFIFO entries. + /// + /// GPFIFO entries + public void PushEntries(ReadOnlySpan entries) + { + _device.PushEntries(_processor, entries); + } + + /// + /// Disposes the GPU channel. + /// It's an error to use the GPU channel after disposal. + /// + public void Dispose() + { + GC.SuppressFinalize(this); + _context.DeferredActions.Enqueue(Destroy); + } + + /// + /// Performs disposal of the host GPU resources used by this channel, that are not shared. + /// This must only be called from the render thread. + /// + private void Destroy() + { + _processor.Dispose(); + TextureManager.Dispose(); + + var oldMemoryManager = Interlocked.Exchange(ref _memoryManager, null); + if (oldMemoryManager != null) + { + oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind; + oldMemoryManager.Physical.DecrementReferenceCount(); + oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/GpuContext.cs b/src/Ryujinx.Graphics.Gpu/GpuContext.cs new file mode 100644 index 00000000..048d32fb --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/GpuContext.cs @@ -0,0 +1,456 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.GPFifo; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.Shader; +using Ryujinx.Graphics.Gpu.Synchronization; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu +{ + /// + /// GPU emulation context. + /// + public sealed class GpuContext : IDisposable + { + private const int NsToTicksFractionNumerator = 384; + private const int NsToTicksFractionDenominator = 625; + + /// + /// Event signaled when the host emulation context is ready to be used by the gpu context. + /// + public ManualResetEvent HostInitalized { get; } + + /// + /// Host renderer. + /// + public IRenderer Renderer { get; } + + /// + /// GPU General Purpose FIFO queue. + /// + public GPFifoDevice GPFifo { get; } + + /// + /// GPU synchronization manager. + /// + public SynchronizationManager Synchronization { get; } + + /// + /// Presentation window. + /// + public Window Window { get; } + + /// + /// Internal sequence number, used to avoid needless resource data updates + /// in the middle of a command buffer before synchronizations. + /// + internal int SequenceNumber { get; private set; } + + /// + /// Internal sync number, used to denote points at which host synchronization can be requested. + /// + internal ulong SyncNumber { get; private set; } + + /// + /// Actions to be performed when a CPU waiting syncpoint or barrier is triggered. + /// If there are more than 0 items when this happens, a host sync object will be generated for the given , + /// and the SyncNumber will be incremented. + /// + internal List SyncActions { get; } + + /// + /// Actions to be performed when a CPU waiting syncpoint is triggered. + /// If there are more than 0 items when this happens, a host sync object will be generated for the given , + /// and the SyncNumber will be incremented. + /// + internal List SyncpointActions { get; } + + /// + /// Buffer migrations that are currently in-flight. These are checked whenever sync is created to determine if buffer migration + /// copies have completed on the GPU, and their data can be freed. + /// + internal List BufferMigrations { get; } + + /// + /// Queue with deferred actions that must run on the render thread. + /// + internal Queue DeferredActions { get; } + + /// + /// Registry with physical memories that can be used with this GPU context, keyed by owner process ID. + /// + internal ConcurrentDictionary PhysicalMemoryRegistry { get; } + + /// + /// Support buffer updater. + /// + internal SupportBufferUpdater SupportBufferUpdater { get; } + + /// + /// Host hardware capabilities. + /// + internal Capabilities Capabilities; + + /// + /// Event for signalling shader cache loading progress. + /// + public event Action ShaderCacheStateChanged; + + private Thread _gpuThread; + private bool _pendingSync; + + private long _modifiedSequence; + private readonly ulong _firstTimestamp; + + private readonly ManualResetEvent _gpuReadyEvent; + + /// + /// Creates a new instance of the GPU emulation context. + /// + /// Host renderer + public GpuContext(IRenderer renderer) + { + Renderer = renderer; + + GPFifo = new GPFifoDevice(this); + + Synchronization = new SynchronizationManager(); + + Window = new Window(this); + + HostInitalized = new ManualResetEvent(false); + _gpuReadyEvent = new ManualResetEvent(false); + + SyncActions = new List(); + SyncpointActions = new List(); + BufferMigrations = new List(); + + DeferredActions = new Queue(); + + PhysicalMemoryRegistry = new ConcurrentDictionary(); + + SupportBufferUpdater = new SupportBufferUpdater(renderer); + + _firstTimestamp = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds); + } + + /// + /// Creates a new GPU channel. + /// + /// The GPU channel + public GpuChannel CreateChannel() + { + return new GpuChannel(this); + } + + /// + /// Creates a new GPU memory manager. + /// + /// ID of the process that owns the memory manager + /// The memory manager + /// Thrown when is invalid + public MemoryManager CreateMemoryManager(ulong pid) + { + if (!PhysicalMemoryRegistry.TryGetValue(pid, out var physicalMemory)) + { + throw new ArgumentException("The PID is invalid or the process was not registered", nameof(pid)); + } + + return new MemoryManager(physicalMemory); + } + + /// + /// Creates a new device memory manager. + /// + /// ID of the process that owns the memory manager + /// The memory manager + /// Thrown when is invalid + public DeviceMemoryManager CreateDeviceMemoryManager(ulong pid) + { + if (!PhysicalMemoryRegistry.TryGetValue(pid, out var physicalMemory)) + { + throw new ArgumentException("The PID is invalid or the process was not registered", nameof(pid)); + } + + return physicalMemory.CreateDeviceMemoryManager(); + } + + /// + /// Registers virtual memory used by a process for GPU memory access, caching and read/write tracking. + /// + /// ID of the process that owns + /// Virtual memory owned by the process + /// Thrown if was already registered + public void RegisterProcess(ulong pid, Cpu.IVirtualMemoryManagerTracked cpuMemory) + { + var physicalMemory = new PhysicalMemory(this, cpuMemory); + if (!PhysicalMemoryRegistry.TryAdd(pid, physicalMemory)) + { + throw new ArgumentException("The PID was already registered", nameof(pid)); + } + + physicalMemory.ShaderCache.ShaderCacheStateChanged += ShaderCacheStateUpdate; + } + + /// + /// Unregisters a process, indicating that its memory will no longer be used, and that caches can be freed. + /// + /// ID of the process + public void UnregisterProcess(ulong pid) + { + if (PhysicalMemoryRegistry.TryRemove(pid, out var physicalMemory)) + { + physicalMemory.ShaderCache.ShaderCacheStateChanged -= ShaderCacheStateUpdate; + physicalMemory.Dispose(); + } + } + + /// + /// Converts a nanoseconds timestamp value to Maxwell time ticks. + /// + /// + /// The frequency is 614400000 Hz. + /// + /// Timestamp in nanoseconds + /// Maxwell ticks + private static ulong ConvertNanosecondsToTicks(ulong nanoseconds) + { + // We need to divide first to avoid overflows. + // We fix up the result later by calculating the difference and adding + // that to the result. + ulong divided = nanoseconds / NsToTicksFractionDenominator; + + ulong rounded = divided * NsToTicksFractionDenominator; + + ulong errorBias = (nanoseconds - rounded) * NsToTicksFractionNumerator / NsToTicksFractionDenominator; + + return divided * NsToTicksFractionNumerator + errorBias; + } + + /// + /// Gets a sequence number for resource modification ordering. This increments on each call. + /// + /// A sequence number for resource modification ordering + internal long GetModifiedSequence() + { + return _modifiedSequence++; + } + + /// + /// Gets the value of the GPU timer. + /// + /// The current GPU timestamp + internal ulong GetTimestamp() + { + // Guest timestamp will start at 0, instead of host value. + ulong ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds) - _firstTimestamp; + + if (GraphicsConfig.FastGpuTime) + { + // Divide by some amount to report time as if operations were performed faster than they really are. + // This can prevent some games from switching to a lower resolution because rendering is too slow. + ticks /= 256; + } + + return ticks; + } + + /// + /// Shader cache state update handler. + /// + /// Current state of the shader cache load process + /// Number of the current shader being processed + /// Total number of shaders to process + private void ShaderCacheStateUpdate(ShaderCacheState state, int current, int total) + { + ShaderCacheStateChanged?.Invoke(state, current, total); + } + + /// + /// Initialize the GPU shader cache. + /// + public void InitializeShaderCache(CancellationToken cancellationToken) + { + HostInitalized.WaitOne(); + + foreach (var physicalMemory in PhysicalMemoryRegistry.Values) + { + physicalMemory.ShaderCache.Initialize(cancellationToken); + } + + _gpuReadyEvent.Set(); + } + + /// + /// Waits until the GPU is ready to receive commands. + /// + public void WaitUntilGpuReady() + { + _gpuReadyEvent.WaitOne(); + } + + /// + /// Sets the current thread as the main GPU thread. + /// + public void SetGpuThread() + { + _gpuThread = Thread.CurrentThread; + + Capabilities = Renderer.GetCapabilities(); + } + + /// + /// Checks if the current thread is the GPU thread. + /// + /// True if the thread is the GPU thread, false otherwise + public bool IsGpuThread() + { + return _gpuThread == Thread.CurrentThread; + } + + /// + /// Processes the queue of shaders that must save their binaries to the disk cache. + /// + public void ProcessShaderCacheQueue() + { + foreach (var physicalMemory in PhysicalMemoryRegistry.Values) + { + physicalMemory.ShaderCache.ProcessShaderCacheQueue(); + } + } + + /// + /// Advances internal sequence number. + /// This forces the update of any modified GPU resource. + /// + internal void AdvanceSequence() + { + SequenceNumber++; + } + + /// + /// Registers a buffer migration. These are checked to see if they can be disposed when the sync number increases, + /// and the migration copy has completed. + /// + /// The buffer migration + internal void RegisterBufferMigration(BufferMigration migration) + { + BufferMigrations.Add(migration); + _pendingSync = true; + } + + /// + /// Registers an action to be performed the next time a syncpoint is incremented. + /// This will also ensure a host sync object is created, and is incremented. + /// + /// The resource with action to be performed on sync object creation + /// True if the sync action should only run when syncpoints are incremented + internal void RegisterSyncAction(ISyncActionHandler action, bool syncpointOnly = false) + { + if (syncpointOnly) + { + SyncpointActions.Add(action); + } + else + { + SyncActions.Add(action); + _pendingSync = true; + } + } + + /// + /// Creates a host sync object if there are any pending sync actions. The actions will then be called. + /// If no actions are present, a host sync object is not created. + /// + /// Modifiers for how host sync should be created + internal void CreateHostSyncIfNeeded(HostSyncFlags flags) + { + bool syncpoint = flags.HasFlag(HostSyncFlags.Syncpoint); + bool strict = flags.HasFlag(HostSyncFlags.Strict); + bool force = flags.HasFlag(HostSyncFlags.Force); + + if (BufferMigrations.Count > 0) + { + ulong currentSyncNumber = Renderer.GetCurrentSync(); + + for (int i = 0; i < BufferMigrations.Count; i++) + { + BufferMigration migration = BufferMigrations[i]; + long diff = (long)(currentSyncNumber - migration.SyncNumber); + + if (diff >= 0) + { + migration.Dispose(); + BufferMigrations.RemoveAt(i--); + } + } + } + + if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0)) + { + foreach (var action in SyncActions) + { + action.SyncPreAction(syncpoint); + } + + foreach (var action in SyncpointActions) + { + action.SyncPreAction(syncpoint); + } + + Renderer.CreateSync(SyncNumber, strict); + + SyncNumber++; + + SyncActions.RemoveAll(action => action.SyncAction(syncpoint)); + SyncpointActions.RemoveAll(action => action.SyncAction(syncpoint)); + } + + _pendingSync = false; + } + + /// + /// Performs deferred actions. + /// This is useful for actions that must run on the render thread, such as resource disposal. + /// + internal void RunDeferredActions() + { + while (DeferredActions.TryDequeue(out Action action)) + { + action(); + } + } + + /// + /// Disposes all GPU resources currently cached. + /// It's an error to push any GPU commands after disposal. + /// Additionally, the GPU commands FIFO must be empty for disposal, + /// and processing of all commands must have finished. + /// + public void Dispose() + { + GPFifo.Dispose(); + HostInitalized.Dispose(); + _gpuReadyEvent.Dispose(); + + // Has to be disposed before processing deferred actions, as it will produce some. + foreach (var physicalMemory in PhysicalMemoryRegistry.Values) + { + physicalMemory.Dispose(); + } + + SupportBufferUpdater.Dispose(); + + PhysicalMemoryRegistry.Clear(); + + RunDeferredActions(); + + Renderer.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs b/src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs new file mode 100644 index 00000000..fbb7399c --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs @@ -0,0 +1,77 @@ +namespace Ryujinx.Graphics.Gpu +{ +#pragma warning disable CA2211 // Non-constant fields should not be visible + /// + /// General GPU and graphics configuration. + /// + public static class GraphicsConfig + { + /// + /// Resolution scale. + /// + public static float ResScale = 1f; + + /// + /// Max Anisotropy. Values range from 0 - 16. Set to -1 to let the game decide. + /// + public static float MaxAnisotropy = -1; + + /// + /// Base directory used to write shader code dumps. + /// Set to null to disable code dumping. + /// + public static string ShadersDumpPath; + + /// + /// Fast GPU time calculates the internal GPU time ticks as if the GPU was capable of + /// processing commands almost instantly, instead of using the host timer. + /// This can avoid lower resolution on some games when GPU performance is poor. + /// + public static bool FastGpuTime = true; + + /// + /// Enables or disables fast 2d engine texture copies entirely on CPU when possible. + /// Reduces stuttering and # of textures in games that copy textures around for streaming, + /// as textures will not need to be created for the copy, and the data does not need to be + /// flushed from GPU. + /// + public static bool Fast2DCopy = true; + + /// + /// Enables or disables the Just-in-Time compiler for GPU Macro code. + /// + public static bool EnableMacroJit = true; + + /// + /// Enables or disables high-level emulation of common GPU Macro code. + /// + public static bool EnableMacroHLE = true; + + /// + /// Title id of the current running game. + /// Used by the shader cache. + /// + public static string TitleId; + + /// + /// Enables or disables the shader cache. + /// + public static bool EnableShaderCache; + + /// + /// Enables or disables shader SPIR-V compilation. + /// + public static bool EnableSpirvCompilationOnVulkan = true; + + /// + /// Enables or disables recompression of compressed textures that are not natively supported by the host. + /// + public static bool EnableTextureRecompression = false; + + /// + /// Enables or disables color space passthrough, if available. + /// + public static bool EnableColorSpacePassthrough = false; + } +#pragma warning restore CA2211 +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs new file mode 100644 index 00000000..5e66a3b5 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs @@ -0,0 +1,297 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// An entry on the short duration texture cache. + /// + class ShortTextureCacheEntry + { + public bool IsAutoDelete; + public readonly TextureDescriptor Descriptor; + public readonly int InvalidatedSequence; + public readonly Texture Texture; + + /// + /// Create a new entry on the short duration texture cache. + /// + /// Last descriptor that referenced the texture + /// The texture + public ShortTextureCacheEntry(TextureDescriptor descriptor, Texture texture) + { + Descriptor = descriptor; + InvalidatedSequence = texture.InvalidatedSequence; + Texture = texture; + } + + /// + /// Create a new entry on the short duration texture cache from the auto delete cache. + /// + /// The texture + public ShortTextureCacheEntry(Texture texture) + { + IsAutoDelete = true; + InvalidatedSequence = texture.InvalidatedSequence; + Texture = texture; + } + } + + /// + /// A texture cache that automatically removes older textures that are not used for some time. + /// The cache works with a rotated list with a fixed size. When new textures are added, the + /// old ones at the bottom of the list are deleted. + /// + class AutoDeleteCache : IEnumerable + { + private const int MinCountForDeletion = 32; + private const int MaxCapacity = 2048; + private const ulong MaxTextureSizeCapacity = 1024 * 1024 * 1024; // MB; + + private readonly LinkedList _textures; + private ulong _totalSize; + + private HashSet _shortCacheBuilder; + private HashSet _shortCache; + + private readonly Dictionary _shortCacheLookup; + + /// + /// Creates a new instance of the automatic deletion cache. + /// + public AutoDeleteCache() + { + _textures = new LinkedList(); + + _shortCacheBuilder = new HashSet(); + _shortCache = new HashSet(); + + _shortCacheLookup = new Dictionary(); + } + + /// + /// Adds a new texture to the cache, even if the texture added is already on the cache. + /// + /// + /// Using this method is only recommended if you know that the texture is not yet on the cache, + /// otherwise it would store the same texture more than once. + /// + /// The texture to be added to the cache + public void Add(Texture texture) + { + _totalSize += texture.Size; + + texture.IncrementReferenceCount(); + texture.CacheNode = _textures.AddLast(texture); + + if (_textures.Count > MaxCapacity || + (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion)) + { + RemoveLeastUsedTexture(); + } + } + + /// + /// Adds a new texture to the cache, or just moves it to the top of the list if the + /// texture is already on the cache. + /// + /// + /// Moving the texture to the top of the list prevents it from being deleted, + /// as the textures on the bottom of the list are deleted when new ones are added. + /// + /// The texture to be added, or moved to the top + public void Lift(Texture texture) + { + if (texture.CacheNode != null) + { + if (texture.CacheNode != _textures.Last) + { + _textures.Remove(texture.CacheNode); + _textures.AddLast(texture.CacheNode); + } + + if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion) + { + RemoveLeastUsedTexture(); + } + } + else + { + Add(texture); + } + } + + /// + /// Removes the least used texture from the cache. + /// + private void RemoveLeastUsedTexture() + { + Texture oldestTexture = _textures.First.Value; + + _totalSize -= oldestTexture.Size; + + if (!oldestTexture.CheckModified(false)) + { + // The texture must be flushed if it falls out of the auto delete cache. + // Flushes out of the auto delete cache do not trigger write tracking, + // as it is expected that other overlapping textures exist that have more up-to-date contents. + + oldestTexture.Group.SynchronizeDependents(oldestTexture); + oldestTexture.FlushModified(false); + } + + _textures.RemoveFirst(); + + oldestTexture.DecrementReferenceCount(); + oldestTexture.CacheNode = null; + } + + /// + /// Removes a texture from the cache. + /// + /// The texture to be removed from the cache + /// True to remove the texture if it was on the cache + /// True if the texture was found and removed, false otherwise + public bool Remove(Texture texture, bool flush) + { + if (texture.CacheNode == null) + { + return false; + } + + // Remove our reference to this texture. + if (flush) + { + texture.FlushModified(false); + } + + _textures.Remove(texture.CacheNode); + + _totalSize -= texture.Size; + + texture.CacheNode = null; + + return texture.DecrementReferenceCount(); + } + + /// + /// Attempt to find a texture on the short duration cache. + /// + /// The texture descriptor + /// The texture if found, null otherwise + public Texture FindShortCache(in TextureDescriptor descriptor) + { + if (_shortCacheLookup.Count > 0 && _shortCacheLookup.TryGetValue(descriptor, out var entry)) + { + if (entry.InvalidatedSequence == entry.Texture.InvalidatedSequence) + { + return entry.Texture; + } + else + { + _shortCacheLookup.Remove(descriptor); + } + } + + return null; + } + + /// + /// Removes a texture from the short duration cache. + /// + /// Texture to remove from the short cache + public void RemoveShortCache(Texture texture) + { + bool removed = _shortCache.Remove(texture.ShortCacheEntry); + removed |= _shortCacheBuilder.Remove(texture.ShortCacheEntry); + + if (removed) + { + texture.DecrementReferenceCount(); + + if (!texture.ShortCacheEntry.IsAutoDelete) + { + _shortCacheLookup.Remove(texture.ShortCacheEntry.Descriptor); + } + + texture.ShortCacheEntry = null; + } + } + + /// + /// Adds a texture to the short duration cache. + /// It starts in the builder set, and it is moved into the deletion set on next process. + /// + /// Texture to add to the short cache + /// Last used texture descriptor + public void AddShortCache(Texture texture, ref TextureDescriptor descriptor) + { + var entry = new ShortTextureCacheEntry(descriptor, texture); + + _shortCacheBuilder.Add(entry); + _shortCacheLookup.Add(entry.Descriptor, entry); + + texture.ShortCacheEntry = entry; + + texture.IncrementReferenceCount(); + } + + /// + /// Adds a texture to the short duration cache without a descriptor. This typically keeps it alive for two ticks. + /// On expiry, it will be removed from the AutoDeleteCache. + /// + /// Texture to add to the short cache + public void AddShortCache(Texture texture) + { + if (texture.ShortCacheEntry != null) + { + var entry = new ShortTextureCacheEntry(texture); + + _shortCacheBuilder.Add(entry); + + texture.ShortCacheEntry = entry; + + texture.IncrementReferenceCount(); + } + } + + /// + /// Delete textures from the short duration cache. + /// Moves the builder set to be deleted on next process. + /// + public void ProcessShortCache() + { + HashSet toRemove = _shortCache; + + foreach (var entry in toRemove) + { + entry.Texture.DecrementReferenceCount(); + + if (entry.IsAutoDelete) + { + Remove(entry.Texture, false); + } + else + { + _shortCacheLookup.Remove(entry.Descriptor); + } + + entry.Texture.ShortCacheEntry = null; + } + + toRemove.Clear(); + _shortCache = _shortCacheBuilder; + _shortCacheBuilder = toRemove; + } + + public IEnumerator GetEnumerator() + { + return _textures.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _textures.GetEnumerator(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs b/src/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs new file mode 100644 index 00000000..8a9f37bb --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs @@ -0,0 +1,72 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Represents texture format information. + /// + readonly struct FormatInfo + { + /// + /// A default, generic RGBA8 texture format. + /// + public static FormatInfo Default { get; } = new(Format.R8G8B8A8Unorm, 1, 1, 4, 4); + + /// + /// The format of the texture data. + /// + public Format Format { get; } + + /// + /// The block width for compressed formats. + /// + /// + /// Must be 1 for non-compressed formats. + /// + public int BlockWidth { get; } + + /// + /// The block height for compressed formats. + /// + /// + /// Must be 1 for non-compressed formats. + /// + public int BlockHeight { get; } + + /// + /// The number of bytes occupied by a single pixel in memory of the texture data. + /// + public int BytesPerPixel { get; } + + /// + /// The maximum number of components this format has defined (in RGBA order). + /// + public int Components { get; } + + /// + /// Whenever or not the texture format is a compressed format. Determined from block size. + /// + public bool IsCompressed => (BlockWidth | BlockHeight) != 1; + + /// + /// Constructs the texture format info structure. + /// + /// The format of the texture data + /// The block width for compressed formats. Must be 1 for non-compressed formats + /// The block height for compressed formats. Must be 1 for non-compressed formats + /// The number of bytes occupied by a single pixel in memory of the texture data + public FormatInfo( + Format format, + int blockWidth, + int blockHeight, + int bytesPerPixel, + int components) + { + Format = format; + BlockWidth = blockWidth; + BlockHeight = blockHeight; + BytesPerPixel = bytesPerPixel; + Components = components; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs b/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs new file mode 100644 index 00000000..da9e5c3a --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs @@ -0,0 +1,715 @@ +using Ryujinx.Graphics.GAL; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Contains format tables, for texture and vertex attribute formats. + /// + static class FormatTable + { +#pragma warning disable IDE0055 // Disable formatting + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + private enum TextureFormat : uint + { + // Formats + R32G32B32A32 = 0x01, + R32G32B32 = 0x02, + R16G16B16A16 = 0x03, + R32G32 = 0x04, + R32B24G8 = 0x05, + X8B8G8R8 = 0x07, + A8B8G8R8 = 0x08, + A2B10G10R10 = 0x09, + R16G16 = 0x0c, + G8R24 = 0x0d, + G24R8 = 0x0e, + R32 = 0x0f, + A4B4G4R4 = 0x12, + A5B5G5R1 = 0x13, + A1B5G5R5 = 0x14, + B5G6R5 = 0x15, + B6G5R5 = 0x16, + G8R8 = 0x18, + R16 = 0x1b, + Y8Video = 0x1c, + R8 = 0x1d, + G4R4 = 0x1e, + R1 = 0x1f, + E5B9G9R9SharedExp = 0x20, + Bf10Gf11Rf11 = 0x21, + G8B8G8R8 = 0x22, + B8G8R8G8 = 0x23, + Bc1 = 0x24, + Bc2 = 0x25, + Bc3 = 0x26, + Bc4 = 0x27, + Bc5 = 0x28, + Bc6HSf16 = 0x10, + Bc6HUf16 = 0x11, + Bc7U = 0x17, + Etc2Rgb = 0x06, + Etc2RgbPta = 0x0a, + Etc2Rgba = 0x0b, + Eac = 0x19, + Eacx2 = 0x1a, + Z24S8 = 0x29, + X8Z24 = 0x2a, + S8Z24 = 0x2b, + X4V4Z24Cov4R4V = 0x2c, + X4V4Z24Cov8R8V = 0x2d, + V8Z24Cov4R12V = 0x2e, + Zf32 = 0x2f, + Zf32X24S8 = 0x30, + X8Z24X20V4S8Cov4R4V = 0x31, + X8Z24X20V4S8Cov8R8V = 0x32, + Zf32X20V4X8Cov4R4V = 0x33, + Zf32X20V4X8Cov8R8V = 0x34, + Zf32X20V4S8Cov4R4V = 0x35, + Zf32X20V4S8Cov8R8V = 0x36, + X8Z24X16V8S8Cov4R12V = 0x37, + Zf32X16V8X8Cov4R12V = 0x38, + Zf32X16V8S8Cov4R12V = 0x39, + Z16 = 0x3a, + V8Z24Cov8R24V = 0x3b, + X8Z24X16V8S8Cov8R24V = 0x3c, + Zf32X16V8X8Cov8R24V = 0x3d, + Zf32X16V8S8Cov8R24V = 0x3e, + Astc2D4x4 = 0x40, + Astc2D5x4 = 0x50, + Astc2D5x5 = 0x41, + Astc2D6x5 = 0x51, + Astc2D6x6 = 0x42, + Astc2D8x5 = 0x55, + Astc2D8x6 = 0x52, + Astc2D8x8 = 0x44, + Astc2D10x5 = 0x56, + Astc2D10x6 = 0x57, + Astc2D10x8 = 0x53, + Astc2D10x10 = 0x45, + Astc2D12x10 = 0x54, + Astc2D12x12 = 0x46, + + // Types + Snorm = 0x1, + Unorm = 0x2, + Sint = 0x3, + Uint = 0x4, + SnormForceFp16 = 0x5, + UnormForceFp16 = 0x6, + Float = 0x7, + + // Component Types + RSnorm = Snorm << 7, + GSnorm = Snorm << 10, + BSnorm = Snorm << 13, + ASnorm = Snorm << 16, + + RUnorm = Unorm << 7, + GUnorm = Unorm << 10, + BUnorm = Unorm << 13, + AUnorm = Unorm << 16, + + RSint = Sint << 7, + GSint = Sint << 10, + BSint = Sint << 13, + ASint = Sint << 16, + + RUint = Uint << 7, + GUint = Uint << 10, + BUint = Uint << 13, + AUint = Uint << 16, + + RSnormForceFp16 = SnormForceFp16 << 7, + GSnormForceFp16 = SnormForceFp16 << 10, + BSnormForceFp16 = SnormForceFp16 << 13, + ASnormForceFp16 = SnormForceFp16 << 16, + + RUnormForceFp16 = UnormForceFp16 << 7, + GUnormForceFp16 = UnormForceFp16 << 10, + BUnormForceFp16 = UnormForceFp16 << 13, + AUnormForceFp16 = UnormForceFp16 << 16, + + RFloat = Float << 7, + GFloat = Float << 10, + BFloat = Float << 13, + AFloat = Float << 16, + + Srgb = 0x1 << 19, // Custom encoding + + // Combinations + R8Unorm = R8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2491d + R8Snorm = R8 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x1249d + R8Uint = R8 | RUint | GUint | BUint | AUint, // 0x4921d + R8Sint = R8 | RSint | GSint | BSint | ASint, // 0x36d9d + R16Float = R16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff9b + R16Unorm = R16 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2491b + R16Snorm = R16 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x1249b + R16Uint = R16 | RUint | GUint | BUint | AUint, // 0x4921b + R16Sint = R16 | RSint | GSint | BSint | ASint, // 0x36d9b + R32Float = R32 | RFloat | GFloat | BFloat | AFloat, // 0x7ff8f + R32Uint = R32 | RUint | GUint | BUint | AUint, // 0x4920f + R32Sint = R32 | RSint | GSint | BSint | ASint, // 0x36d8f + G8R8Unorm = G8R8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24918 + G8R8Snorm = G8R8 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x12498 + G8R8Uint = G8R8 | RUint | GUint | BUint | AUint, // 0x49218 + G8R8Sint = G8R8 | RSint | GSint | BSint | ASint, // 0x36d98 + R16G16Float = R16G16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff8c + R16G16Unorm = R16G16 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2490c + R16G16Snorm = R16G16 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x1248c + R16G16Uint = R16G16 | RUint | GUint | BUint | AUint, // 0x4920c + R16G16Sint = R16G16 | RSint | GSint | BSint | ASint, // 0x36d8c + R32G32Float = R32G32 | RFloat | GFloat | BFloat | AFloat, // 0x7ff84 + R32G32Uint = R32G32 | RUint | GUint | BUint | AUint, // 0x49204 + R32G32Sint = R32G32 | RSint | GSint | BSint | ASint, // 0x36d84 + R32G32B32Float = R32G32B32 | RFloat | GFloat | BFloat | AFloat, // 0x7ff82 + R32G32B32Uint = R32G32B32 | RUint | GUint | BUint | AUint, // 0x49202 + R32G32B32Sint = R32G32B32 | RSint | GSint | BSint | ASint, // 0x36d82 + A8B8G8R8Unorm = A8B8G8R8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24908 + A8B8G8R8Snorm = A8B8G8R8 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x12488 + A8B8G8R8Uint = A8B8G8R8 | RUint | GUint | BUint | AUint, // 0x49208 + A8B8G8R8Sint = A8B8G8R8 | RSint | GSint | BSint | ASint, // 0x36d88 + R16G16B16A16Float = R16G16B16A16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff83 + R16G16B16A16Unorm = R16G16B16A16 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24903 + R16G16B16A16Snorm = R16G16B16A16 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x12483 + R16G16B16A16Uint = R16G16B16A16 | RUint | GUint | BUint | AUint, // 0x49203 + R16G16B16A16Sint = R16G16B16A16 | RSint | GSint | BSint | ASint, // 0x36d83 + R32G32B32A32Float = R32G32B32A32 | RFloat | GFloat | BFloat | AFloat, // 0x7ff81 + R32G32B32A32Uint = R32G32B32A32 | RUint | GUint | BUint | AUint, // 0x49201 + R32G32B32A32Sint = R32G32B32A32 | RSint | GSint | BSint | ASint, // 0x36d81 + Z16Unorm = Z16 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2493a + Z16RUnormGUintBUintAUint = Z16 | RUnorm | GUint | BUint | AUint, // 0x4913a + Zf32RFloatGUintBUintAUint = Zf32 | RFloat | GUint | BUint | AUint, // 0x493af + Zf32Float = Zf32 | RFloat | GFloat | BFloat | AFloat, // 0x7ffaf + G24R8RUintGUnormBUnormAUnorm = G24R8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a0e + Z24S8RUintGUnormBUnormAUnorm = Z24S8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a29 + Z24S8RUintGUnormBUintAUint = Z24S8 | RUint | GUnorm | BUint | AUint, // 0x48a29 + X8Z24RUnormGUintBUintAUint = X8Z24 | RUnorm | GUint | BUint | AUint, // 0x4912a + S8Z24RUnormGUintBUintAUint = S8Z24 | RUnorm | GUint | BUint | AUint, // 0x4912b + R32B24G8RFloatGUintBUnormAUnorm = R32B24G8 | RFloat | GUint | BUnorm | AUnorm, // 0x25385 + Zf32X24S8RFloatGUintBUnormAUnorm = Zf32X24S8 | RFloat | GUint | BUnorm | AUnorm, // 0x253b0 + A8B8G8R8UnormSrgb = A8B8G8R8 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4908 + G4R4Unorm = G4R4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2491e + A4B4G4R4Unorm = A4B4G4R4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24912 + A1B5G5R5Unorm = A1B5G5R5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24914 + B5G6R5Unorm = B5G6R5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24915 + A2B10G10R10Unorm = A2B10G10R10 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24909 + A2B10G10R10Uint = A2B10G10R10 | RUint | GUint | BUint | AUint, // 0x49209 + Bf10Gf11Rf11Float = Bf10Gf11Rf11 | RFloat | GFloat | BFloat | AFloat, // 0x7ffa1 + E5B9G9R9SharedExpFloat = E5B9G9R9SharedExp | RFloat | GFloat | BFloat | AFloat, // 0x7ffa0 + Bc1Unorm = Bc1 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24924 + Bc2Unorm = Bc2 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24925 + Bc3Unorm = Bc3 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24926 + Bc1UnormSrgb = Bc1 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4924 + Bc2UnormSrgb = Bc2 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4925 + Bc3UnormSrgb = Bc3 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4926 + Bc4Unorm = Bc4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24927 + Bc4Snorm = Bc4 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x124a7 + Bc5Unorm = Bc5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24928 + Bc5Snorm = Bc5 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x124a8 + Bc7UUnorm = Bc7U | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24917 + Bc7UUnormSrgb = Bc7U | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4917 + Bc6HSf16Float = Bc6HSf16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff90 + Bc6HUf16Float = Bc6HUf16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff91 + Etc2RgbUnorm = Etc2Rgb | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24906 + Etc2RgbPtaUnorm = Etc2RgbPta | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2490a + Etc2RgbaUnorm = Etc2Rgba | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2490b + Etc2RgbUnormSrgb = Etc2Rgb | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4906 + Etc2RgbPtaUnormSrgb = Etc2RgbPta | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa490a + Etc2RgbaUnormSrgb = Etc2Rgba | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa490b + Astc2D4x4Unorm = Astc2D4x4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24940 + Astc2D5x4Unorm = Astc2D5x4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24950 + Astc2D5x5Unorm = Astc2D5x5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24941 + Astc2D6x5Unorm = Astc2D6x5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24951 + Astc2D6x6Unorm = Astc2D6x6 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24942 + Astc2D8x5Unorm = Astc2D8x5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24955 + Astc2D8x6Unorm = Astc2D8x6 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24952 + Astc2D8x8Unorm = Astc2D8x8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24944 + Astc2D10x5Unorm = Astc2D10x5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24956 + Astc2D10x6Unorm = Astc2D10x6 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24957 + Astc2D10x8Unorm = Astc2D10x8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24953 + Astc2D10x10Unorm = Astc2D10x10 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24945 + Astc2D12x10Unorm = Astc2D12x10 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24954 + Astc2D12x12Unorm = Astc2D12x12 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24946 + Astc2D4x4UnormSrgb = Astc2D4x4 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4940 + Astc2D5x4UnormSrgb = Astc2D5x4 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4950 + Astc2D5x5UnormSrgb = Astc2D5x5 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4941 + Astc2D6x5UnormSrgb = Astc2D6x5 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4951 + Astc2D6x6UnormSrgb = Astc2D6x6 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4942 + Astc2D8x5UnormSrgb = Astc2D8x5 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4955 + Astc2D8x6UnormSrgb = Astc2D8x6 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4952 + Astc2D8x8UnormSrgb = Astc2D8x8 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4944 + Astc2D10x5UnormSrgb = Astc2D10x5 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4956 + Astc2D10x6UnormSrgb = Astc2D10x6 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4957 + Astc2D10x8UnormSrgb = Astc2D10x8 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4953 + Astc2D10x10UnormSrgb = Astc2D10x10 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4945 + Astc2D12x10UnormSrgb = Astc2D12x10 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4954 + Astc2D12x12UnormSrgb = Astc2D12x12 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4946 + A5B5G5R1Unorm = A5B5G5R1 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24913 + } + + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + private enum VertexAttributeFormat : uint + { + // Width + R32G32B32A32 = 0x01, + R32G32B32 = 0x02, + R16G16B16A16 = 0x03, + R32G32 = 0x04, + R16G16B16 = 0x05, + A8B8G8R8 = 0x2f, + R8G8B8A8 = 0x0a, + X8B8G8R8 = 0x33, + A2B10G10R10 = 0x30, + B10G11R11 = 0x31, + R16G16 = 0x0f, + R32 = 0x12, + R8G8B8 = 0x13, + G8R8 = 0x32, + R8G8 = 0x18, + R16 = 0x1b, + R8 = 0x1d, + A8 = 0x34, + + // Type + Snorm = 0x01, + Unorm = 0x02, + Sint = 0x03, + Uint = 0x04, + Uscaled = 0x05, + Sscaled = 0x06, + Float = 0x07, + + // Combinations + R8Unorm = (R8 << 21) | (Unorm << 27), // 0x13a00000 + R8Snorm = (R8 << 21) | (Snorm << 27), // 0x0ba00000 + R8Uint = (R8 << 21) | (Uint << 27), // 0x23a00000 + R8Sint = (R8 << 21) | (Sint << 27), // 0x1ba00000 + R16Float = (R16 << 21) | (Float << 27), // 0x3b600000 + R16Unorm = (R16 << 21) | (Unorm << 27), // 0x13600000 + R16Snorm = (R16 << 21) | (Snorm << 27), // 0x0b600000 + R16Uint = (R16 << 21) | (Uint << 27), // 0x23600000 + R16Sint = (R16 << 21) | (Sint << 27), // 0x1b600000 + R32Float = (R32 << 21) | (Float << 27), // 0x3a400000 + R32Uint = (R32 << 21) | (Uint << 27), // 0x22400000 + R32Sint = (R32 << 21) | (Sint << 27), // 0x1a400000 + R8G8Unorm = (R8G8 << 21) | (Unorm << 27), // 0x13000000 + R8G8Snorm = (R8G8 << 21) | (Snorm << 27), // 0x0b000000 + R8G8Uint = (R8G8 << 21) | (Uint << 27), // 0x23000000 + R8G8Sint = (R8G8 << 21) | (Sint << 27), // 0x1b000000 + R16G16Float = (R16G16 << 21) | (Float << 27), // 0x39e00000 + R16G16Unorm = (R16G16 << 21) | (Unorm << 27), // 0x11e00000 + R16G16Snorm = (R16G16 << 21) | (Snorm << 27), // 0x09e00000 + R16G16Uint = (R16G16 << 21) | (Uint << 27), // 0x21e00000 + R16G16Sint = (R16G16 << 21) | (Sint << 27), // 0x19e00000 + R32G32Float = (R32G32 << 21) | (Float << 27), // 0x38800000 + R32G32Uint = (R32G32 << 21) | (Uint << 27), // 0x20800000 + R32G32Sint = (R32G32 << 21) | (Sint << 27), // 0x18800000 + R8G8B8Unorm = (R8G8B8 << 21) | (Unorm << 27), // 0x12600000 + R8G8B8Snorm = (R8G8B8 << 21) | (Snorm << 27), // 0x0a600000 + R8G8B8Uint = (R8G8B8 << 21) | (Uint << 27), // 0x22600000 + R8G8B8Sint = (R8G8B8 << 21) | (Sint << 27), // 0x1a600000 + R16G16B16Float = (R16G16B16 << 21) | (Float << 27), // 0x38a00000 + R16G16B16Unorm = (R16G16B16 << 21) | (Unorm << 27), // 0x10a00000 + R16G16B16Snorm = (R16G16B16 << 21) | (Snorm << 27), // 0x08a00000 + R16G16B16Uint = (R16G16B16 << 21) | (Uint << 27), // 0x20a00000 + R16G16B16Sint = (R16G16B16 << 21) | (Sint << 27), // 0x18a00000 + R32G32B32Float = (R32G32B32 << 21) | (Float << 27), // 0x38400000 + R32G32B32Uint = (R32G32B32 << 21) | (Uint << 27), // 0x20400000 + R32G32B32Sint = (R32G32B32 << 21) | (Sint << 27), // 0x18400000 + R8G8B8A8Unorm = (R8G8B8A8 << 21) | (Unorm << 27), // 0x11400000 + R8G8B8A8Snorm = (R8G8B8A8 << 21) | (Snorm << 27), // 0x09400000 + R8G8B8A8Uint = (R8G8B8A8 << 21) | (Uint << 27), // 0x21400000 + R8G8B8A8Sint = (R8G8B8A8 << 21) | (Sint << 27), // 0x19400000 + R16G16B16A16Float = (R16G16B16A16 << 21) | (Float << 27), // 0x38600000 + R16G16B16A16Unorm = (R16G16B16A16 << 21) | (Unorm << 27), // 0x10600000 + R16G16B16A16Snorm = (R16G16B16A16 << 21) | (Snorm << 27), // 0x08600000 + R16G16B16A16Uint = (R16G16B16A16 << 21) | (Uint << 27), // 0x20600000 + R16G16B16A16Sint = (R16G16B16A16 << 21) | (Sint << 27), // 0x18600000 + R32G32B32A32Float = (R32G32B32A32 << 21) | (Float << 27), // 0x38200000 + R32G32B32A32Uint = (R32G32B32A32 << 21) | (Uint << 27), // 0x20200000 + R32G32B32A32Sint = (R32G32B32A32 << 21) | (Sint << 27), // 0x18200000 + A2B10G10R10Unorm = (A2B10G10R10 << 21) | (Unorm << 27), // 0x16000000 + A2B10G10R10Uint = (A2B10G10R10 << 21) | (Uint << 27), // 0x26000000 + B10G11R11Float = (B10G11R11 << 21) | (Float << 27), // 0x3e200000 + R8Uscaled = (R8 << 21) | (Uscaled << 27), // 0x2ba00000 + R8Sscaled = (R8 << 21) | (Sscaled << 27), // 0x33a00000 + R16Uscaled = (R16 << 21) | (Uscaled << 27), // 0x2b600000 + R16Sscaled = (R16 << 21) | (Sscaled << 27), // 0x33600000 + R32Uscaled = (R32 << 21) | (Uscaled << 27), // 0x2a400000 + R32Sscaled = (R32 << 21) | (Sscaled << 27), // 0x32400000 + R8G8Uscaled = (R8G8 << 21) | (Uscaled << 27), // 0x2b000000 + R8G8Sscaled = (R8G8 << 21) | (Sscaled << 27), // 0x33000000 + R16G16Uscaled = (R16G16 << 21) | (Uscaled << 27), // 0x29e00000 + R16G16Sscaled = (R16G16 << 21) | (Sscaled << 27), // 0x31e00000 + R32G32Uscaled = (R32G32 << 21) | (Uscaled << 27), // 0x28800000 + R32G32Sscaled = (R32G32 << 21) | (Sscaled << 27), // 0x30800000 + R8G8B8Uscaled = (R8G8B8 << 21) | (Uscaled << 27), // 0x2a600000 + R8G8B8Sscaled = (R8G8B8 << 21) | (Sscaled << 27), // 0x32600000 + R16G16B16Uscaled = (R16G16B16 << 21) | (Uscaled << 27), // 0x28a00000 + R16G16B16Sscaled = (R16G16B16 << 21) | (Sscaled << 27), // 0x30a00000 + R32G32B32Uscaled = (R32G32B32 << 21) | (Uscaled << 27), // 0x28400000 + R32G32B32Sscaled = (R32G32B32 << 21) | (Sscaled << 27), // 0x30400000 + R8G8B8A8Uscaled = (R8G8B8A8 << 21) | (Uscaled << 27), // 0x29400000 + R8G8B8A8Sscaled = (R8G8B8A8 << 21) | (Sscaled << 27), // 0x31400000 + R16G16B16A16Uscaled = (R16G16B16A16 << 21) | (Uscaled << 27), // 0x28600000 + R16G16B16A16Sscaled = (R16G16B16A16 << 21) | (Sscaled << 27), // 0x30600000 + R32G32B32A32Uscaled = (R32G32B32A32 << 21) | (Uscaled << 27), // 0x28200000 + R32G32B32A32Sscaled = (R32G32B32A32 << 21) | (Sscaled << 27), // 0x30200000 + A2B10G10R10Snorm = (A2B10G10R10 << 21) | (Snorm << 27), // 0x0e000000 + A2B10G10R10Sint = (A2B10G10R10 << 21) | (Sint << 27), // 0x1e000000 + A2B10G10R10Uscaled = (A2B10G10R10 << 21) | (Uscaled << 27), // 0x2e000000 + A2B10G10R10Sscaled = (A2B10G10R10 << 21) | (Sscaled << 27), // 0x36000000 + } + + private static readonly Dictionary _textureFormats = new() + { + { TextureFormat.R8Unorm, new FormatInfo(Format.R8Unorm, 1, 1, 1, 1) }, + { TextureFormat.R8Snorm, new FormatInfo(Format.R8Snorm, 1, 1, 1, 1) }, + { TextureFormat.R8Uint, new FormatInfo(Format.R8Uint, 1, 1, 1, 1) }, + { TextureFormat.R8Sint, new FormatInfo(Format.R8Sint, 1, 1, 1, 1) }, + { TextureFormat.R16Float, new FormatInfo(Format.R16Float, 1, 1, 2, 1) }, + { TextureFormat.R16Unorm, new FormatInfo(Format.R16Unorm, 1, 1, 2, 1) }, + { TextureFormat.R16Snorm, new FormatInfo(Format.R16Snorm, 1, 1, 2, 1) }, + { TextureFormat.R16Uint, new FormatInfo(Format.R16Uint, 1, 1, 2, 1) }, + { TextureFormat.R16Sint, new FormatInfo(Format.R16Sint, 1, 1, 2, 1) }, + { TextureFormat.R32Float, new FormatInfo(Format.R32Float, 1, 1, 4, 1) }, + { TextureFormat.R32Uint, new FormatInfo(Format.R32Uint, 1, 1, 4, 1) }, + { TextureFormat.R32Sint, new FormatInfo(Format.R32Sint, 1, 1, 4, 1) }, + { TextureFormat.G8R8Unorm, new FormatInfo(Format.R8G8Unorm, 1, 1, 2, 2) }, + { TextureFormat.G8R8Snorm, new FormatInfo(Format.R8G8Snorm, 1, 1, 2, 2) }, + { TextureFormat.G8R8Uint, new FormatInfo(Format.R8G8Uint, 1, 1, 2, 2) }, + { TextureFormat.G8R8Sint, new FormatInfo(Format.R8G8Sint, 1, 1, 2, 2) }, + { TextureFormat.R16G16Float, new FormatInfo(Format.R16G16Float, 1, 1, 4, 2) }, + { TextureFormat.R16G16Unorm, new FormatInfo(Format.R16G16Unorm, 1, 1, 4, 2) }, + { TextureFormat.R16G16Snorm, new FormatInfo(Format.R16G16Snorm, 1, 1, 4, 2) }, + { TextureFormat.R16G16Uint, new FormatInfo(Format.R16G16Uint, 1, 1, 4, 2) }, + { TextureFormat.R16G16Sint, new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2) }, + { TextureFormat.R32G32Float, new FormatInfo(Format.R32G32Float, 1, 1, 8, 2) }, + { TextureFormat.R32G32Uint, new FormatInfo(Format.R32G32Uint, 1, 1, 8, 2) }, + { TextureFormat.R32G32Sint, new FormatInfo(Format.R32G32Sint, 1, 1, 8, 2) }, + { TextureFormat.R32G32B32Float, new FormatInfo(Format.R32G32B32Float, 1, 1, 12, 3) }, + { TextureFormat.R32G32B32Uint, new FormatInfo(Format.R32G32B32Uint, 1, 1, 12, 3) }, + { TextureFormat.R32G32B32Sint, new FormatInfo(Format.R32G32B32Sint, 1, 1, 12, 3) }, + { TextureFormat.A8B8G8R8Unorm, new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4) }, + { TextureFormat.A8B8G8R8Snorm, new FormatInfo(Format.R8G8B8A8Snorm, 1, 1, 4, 4) }, + { TextureFormat.A8B8G8R8Uint, new FormatInfo(Format.R8G8B8A8Uint, 1, 1, 4, 4) }, + { TextureFormat.A8B8G8R8Sint, new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4, 4) }, + { TextureFormat.R16G16B16A16Float, new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4) }, + { TextureFormat.R16G16B16A16Unorm, new FormatInfo(Format.R16G16B16A16Unorm, 1, 1, 8, 4) }, + { TextureFormat.R16G16B16A16Snorm, new FormatInfo(Format.R16G16B16A16Snorm, 1, 1, 8, 4) }, + { TextureFormat.R16G16B16A16Uint, new FormatInfo(Format.R16G16B16A16Uint, 1, 1, 8, 4) }, + { TextureFormat.R16G16B16A16Sint, new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8, 4) }, + { TextureFormat.R32G32B32A32Float, new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4) }, + { TextureFormat.R32G32B32A32Uint, new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4) }, + { TextureFormat.R32G32B32A32Sint, new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16, 4) }, + { TextureFormat.Z16Unorm, new FormatInfo(Format.D16Unorm, 1, 1, 2, 1) }, + { TextureFormat.Z16RUnormGUintBUintAUint, new FormatInfo(Format.D16Unorm, 1, 1, 2, 1) }, + { TextureFormat.Zf32RFloatGUintBUintAUint, new FormatInfo(Format.D32Float, 1, 1, 4, 1) }, + { TextureFormat.Zf32Float, new FormatInfo(Format.D32Float, 1, 1, 4, 1) }, + { TextureFormat.G24R8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) }, + { TextureFormat.Z24S8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) }, + { TextureFormat.Z24S8RUintGUnormBUintAUint, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) }, + { TextureFormat.X8Z24RUnormGUintBUintAUint, new FormatInfo(Format.X8UintD24Unorm, 1, 1, 4, 2) }, + { TextureFormat.S8Z24RUnormGUintBUintAUint, new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2) }, + { TextureFormat.R32B24G8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) }, + { TextureFormat.Zf32X24S8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) }, + { TextureFormat.A8B8G8R8UnormSrgb, new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4) }, + { TextureFormat.G4R4Unorm, new FormatInfo(Format.R4G4Unorm, 1, 1, 1, 2) }, + { TextureFormat.A4B4G4R4Unorm, new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4) }, + { TextureFormat.A1B5G5R5Unorm, new FormatInfo(Format.R5G5B5A1Unorm, 1, 1, 2, 4) }, + { TextureFormat.B5G6R5Unorm, new FormatInfo(Format.R5G6B5Unorm, 1, 1, 2, 3) }, + { TextureFormat.A2B10G10R10Unorm, new FormatInfo(Format.R10G10B10A2Unorm, 1, 1, 4, 4) }, + { TextureFormat.A2B10G10R10Uint, new FormatInfo(Format.R10G10B10A2Uint, 1, 1, 4, 4) }, + { TextureFormat.Bf10Gf11Rf11Float, new FormatInfo(Format.R11G11B10Float, 1, 1, 4, 3) }, + { TextureFormat.E5B9G9R9SharedExpFloat, new FormatInfo(Format.R9G9B9E5Float, 1, 1, 4, 4) }, + { TextureFormat.Bc1Unorm, new FormatInfo(Format.Bc1RgbaUnorm, 4, 4, 8, 4) }, + { TextureFormat.Bc2Unorm, new FormatInfo(Format.Bc2Unorm, 4, 4, 16, 4) }, + { TextureFormat.Bc3Unorm, new FormatInfo(Format.Bc3Unorm, 4, 4, 16, 4) }, + { TextureFormat.Bc1UnormSrgb, new FormatInfo(Format.Bc1RgbaSrgb, 4, 4, 8, 4) }, + { TextureFormat.Bc2UnormSrgb, new FormatInfo(Format.Bc2Srgb, 4, 4, 16, 4) }, + { TextureFormat.Bc3UnormSrgb, new FormatInfo(Format.Bc3Srgb, 4, 4, 16, 4) }, + { TextureFormat.Bc4Unorm, new FormatInfo(Format.Bc4Unorm, 4, 4, 8, 1) }, + { TextureFormat.Bc4Snorm, new FormatInfo(Format.Bc4Snorm, 4, 4, 8, 1) }, + { TextureFormat.Bc5Unorm, new FormatInfo(Format.Bc5Unorm, 4, 4, 16, 2) }, + { TextureFormat.Bc5Snorm, new FormatInfo(Format.Bc5Snorm, 4, 4, 16, 2) }, + { TextureFormat.Bc7UUnorm, new FormatInfo(Format.Bc7Unorm, 4, 4, 16, 4) }, + { TextureFormat.Bc7UUnormSrgb, new FormatInfo(Format.Bc7Srgb, 4, 4, 16, 4) }, + { TextureFormat.Bc6HSf16Float, new FormatInfo(Format.Bc6HSfloat, 4, 4, 16, 4) }, + { TextureFormat.Bc6HUf16Float, new FormatInfo(Format.Bc6HUfloat, 4, 4, 16, 4) }, + { TextureFormat.Etc2RgbUnorm, new FormatInfo(Format.Etc2RgbUnorm, 4, 4, 8, 3) }, + { TextureFormat.Etc2RgbPtaUnorm, new FormatInfo(Format.Etc2RgbPtaUnorm, 4, 4, 8, 4) }, + { TextureFormat.Etc2RgbaUnorm, new FormatInfo(Format.Etc2RgbaUnorm, 4, 4, 16, 4) }, + { TextureFormat.Etc2RgbUnormSrgb, new FormatInfo(Format.Etc2RgbSrgb, 4, 4, 8, 3) }, + { TextureFormat.Etc2RgbPtaUnormSrgb, new FormatInfo(Format.Etc2RgbPtaSrgb, 4, 4, 8, 4) }, + { TextureFormat.Etc2RgbaUnormSrgb, new FormatInfo(Format.Etc2RgbaSrgb, 4, 4, 16, 4) }, + { TextureFormat.Astc2D4x4Unorm, new FormatInfo(Format.Astc4x4Unorm, 4, 4, 16, 4) }, + { TextureFormat.Astc2D5x4Unorm, new FormatInfo(Format.Astc5x4Unorm, 5, 4, 16, 4) }, + { TextureFormat.Astc2D5x5Unorm, new FormatInfo(Format.Astc5x5Unorm, 5, 5, 16, 4) }, + { TextureFormat.Astc2D6x5Unorm, new FormatInfo(Format.Astc6x5Unorm, 6, 5, 16, 4) }, + { TextureFormat.Astc2D6x6Unorm, new FormatInfo(Format.Astc6x6Unorm, 6, 6, 16, 4) }, + { TextureFormat.Astc2D8x5Unorm, new FormatInfo(Format.Astc8x5Unorm, 8, 5, 16, 4) }, + { TextureFormat.Astc2D8x6Unorm, new FormatInfo(Format.Astc8x6Unorm, 8, 6, 16, 4) }, + { TextureFormat.Astc2D8x8Unorm, new FormatInfo(Format.Astc8x8Unorm, 8, 8, 16, 4) }, + { TextureFormat.Astc2D10x5Unorm, new FormatInfo(Format.Astc10x5Unorm, 10, 5, 16, 4) }, + { TextureFormat.Astc2D10x6Unorm, new FormatInfo(Format.Astc10x6Unorm, 10, 6, 16, 4) }, + { TextureFormat.Astc2D10x8Unorm, new FormatInfo(Format.Astc10x8Unorm, 10, 8, 16, 4) }, + { TextureFormat.Astc2D10x10Unorm, new FormatInfo(Format.Astc10x10Unorm, 10, 10, 16, 4) }, + { TextureFormat.Astc2D12x10Unorm, new FormatInfo(Format.Astc12x10Unorm, 12, 10, 16, 4) }, + { TextureFormat.Astc2D12x12Unorm, new FormatInfo(Format.Astc12x12Unorm, 12, 12, 16, 4) }, + { TextureFormat.Astc2D4x4UnormSrgb, new FormatInfo(Format.Astc4x4Srgb, 4, 4, 16, 4) }, + { TextureFormat.Astc2D5x4UnormSrgb, new FormatInfo(Format.Astc5x4Srgb, 5, 4, 16, 4) }, + { TextureFormat.Astc2D5x5UnormSrgb, new FormatInfo(Format.Astc5x5Srgb, 5, 5, 16, 4) }, + { TextureFormat.Astc2D6x5UnormSrgb, new FormatInfo(Format.Astc6x5Srgb, 6, 5, 16, 4) }, + { TextureFormat.Astc2D6x6UnormSrgb, new FormatInfo(Format.Astc6x6Srgb, 6, 6, 16, 4) }, + { TextureFormat.Astc2D8x5UnormSrgb, new FormatInfo(Format.Astc8x5Srgb, 8, 5, 16, 4) }, + { TextureFormat.Astc2D8x6UnormSrgb, new FormatInfo(Format.Astc8x6Srgb, 8, 6, 16, 4) }, + { TextureFormat.Astc2D8x8UnormSrgb, new FormatInfo(Format.Astc8x8Srgb, 8, 8, 16, 4) }, + { TextureFormat.Astc2D10x5UnormSrgb, new FormatInfo(Format.Astc10x5Srgb, 10, 5, 16, 4) }, + { TextureFormat.Astc2D10x6UnormSrgb, new FormatInfo(Format.Astc10x6Srgb, 10, 6, 16, 4) }, + { TextureFormat.Astc2D10x8UnormSrgb, new FormatInfo(Format.Astc10x8Srgb, 10, 8, 16, 4) }, + { TextureFormat.Astc2D10x10UnormSrgb, new FormatInfo(Format.Astc10x10Srgb, 10, 10, 16, 4) }, + { TextureFormat.Astc2D12x10UnormSrgb, new FormatInfo(Format.Astc12x10Srgb, 12, 10, 16, 4) }, + { TextureFormat.Astc2D12x12UnormSrgb, new FormatInfo(Format.Astc12x12Srgb, 12, 12, 16, 4) }, + { TextureFormat.A5B5G5R1Unorm, new FormatInfo(Format.A1B5G5R5Unorm, 1, 1, 2, 4) }, + }; + + private static readonly Dictionary _attribFormats = new() + { + { VertexAttributeFormat.R8Unorm, Format.R8Unorm }, + { VertexAttributeFormat.R8Snorm, Format.R8Snorm }, + { VertexAttributeFormat.R8Uint, Format.R8Uint }, + { VertexAttributeFormat.R8Sint, Format.R8Sint }, + { VertexAttributeFormat.R16Float, Format.R16Float }, + { VertexAttributeFormat.R16Unorm, Format.R16Unorm }, + { VertexAttributeFormat.R16Snorm, Format.R16Snorm }, + { VertexAttributeFormat.R16Uint, Format.R16Uint }, + { VertexAttributeFormat.R16Sint, Format.R16Sint }, + { VertexAttributeFormat.R32Float, Format.R32Float }, + { VertexAttributeFormat.R32Uint, Format.R32Uint }, + { VertexAttributeFormat.R32Sint, Format.R32Sint }, + { VertexAttributeFormat.R8G8Unorm, Format.R8G8Unorm }, + { VertexAttributeFormat.R8G8Snorm, Format.R8G8Snorm }, + { VertexAttributeFormat.R8G8Uint, Format.R8G8Uint }, + { VertexAttributeFormat.R8G8Sint, Format.R8G8Sint }, + { VertexAttributeFormat.R16G16Float, Format.R16G16Float }, + { VertexAttributeFormat.R16G16Unorm, Format.R16G16Unorm }, + { VertexAttributeFormat.R16G16Snorm, Format.R16G16Snorm }, + { VertexAttributeFormat.R16G16Uint, Format.R16G16Uint }, + { VertexAttributeFormat.R16G16Sint, Format.R16G16Sint }, + { VertexAttributeFormat.R32G32Float, Format.R32G32Float }, + { VertexAttributeFormat.R32G32Uint, Format.R32G32Uint }, + { VertexAttributeFormat.R32G32Sint, Format.R32G32Sint }, + { VertexAttributeFormat.R8G8B8Unorm, Format.R8G8B8Unorm }, + { VertexAttributeFormat.R8G8B8Snorm, Format.R8G8B8Snorm }, + { VertexAttributeFormat.R8G8B8Uint, Format.R8G8B8Uint }, + { VertexAttributeFormat.R8G8B8Sint, Format.R8G8B8Sint }, + { VertexAttributeFormat.R16G16B16Float, Format.R16G16B16Float }, + { VertexAttributeFormat.R16G16B16Unorm, Format.R16G16B16Unorm }, + { VertexAttributeFormat.R16G16B16Snorm, Format.R16G16B16Snorm }, + { VertexAttributeFormat.R16G16B16Uint, Format.R16G16B16Uint }, + { VertexAttributeFormat.R16G16B16Sint, Format.R16G16B16Sint }, + { VertexAttributeFormat.R32G32B32Float, Format.R32G32B32Float }, + { VertexAttributeFormat.R32G32B32Uint, Format.R32G32B32Uint }, + { VertexAttributeFormat.R32G32B32Sint, Format.R32G32B32Sint }, + { VertexAttributeFormat.R8G8B8A8Unorm, Format.R8G8B8A8Unorm }, + { VertexAttributeFormat.R8G8B8A8Snorm, Format.R8G8B8A8Snorm }, + { VertexAttributeFormat.R8G8B8A8Uint, Format.R8G8B8A8Uint }, + { VertexAttributeFormat.R8G8B8A8Sint, Format.R8G8B8A8Sint }, + { VertexAttributeFormat.R16G16B16A16Float, Format.R16G16B16A16Float }, + { VertexAttributeFormat.R16G16B16A16Unorm, Format.R16G16B16A16Unorm }, + { VertexAttributeFormat.R16G16B16A16Snorm, Format.R16G16B16A16Snorm }, + { VertexAttributeFormat.R16G16B16A16Uint, Format.R16G16B16A16Uint }, + { VertexAttributeFormat.R16G16B16A16Sint, Format.R16G16B16A16Sint }, + { VertexAttributeFormat.R32G32B32A32Float, Format.R32G32B32A32Float }, + { VertexAttributeFormat.R32G32B32A32Uint, Format.R32G32B32A32Uint }, + { VertexAttributeFormat.R32G32B32A32Sint, Format.R32G32B32A32Sint }, + { VertexAttributeFormat.A2B10G10R10Unorm, Format.R10G10B10A2Unorm }, + { VertexAttributeFormat.A2B10G10R10Uint, Format.R10G10B10A2Uint }, + { VertexAttributeFormat.B10G11R11Float, Format.R11G11B10Float }, + { VertexAttributeFormat.R8Uscaled, Format.R8Uscaled }, + { VertexAttributeFormat.R8Sscaled, Format.R8Sscaled }, + { VertexAttributeFormat.R16Uscaled, Format.R16Uscaled }, + { VertexAttributeFormat.R16Sscaled, Format.R16Sscaled }, + { VertexAttributeFormat.R32Uscaled, Format.R32Uscaled }, + { VertexAttributeFormat.R32Sscaled, Format.R32Sscaled }, + { VertexAttributeFormat.R8G8Uscaled, Format.R8G8Uscaled }, + { VertexAttributeFormat.R8G8Sscaled, Format.R8G8Sscaled }, + { VertexAttributeFormat.R16G16Uscaled, Format.R16G16Uscaled }, + { VertexAttributeFormat.R16G16Sscaled, Format.R16G16Sscaled }, + { VertexAttributeFormat.R32G32Uscaled, Format.R32G32Uscaled }, + { VertexAttributeFormat.R32G32Sscaled, Format.R32G32Sscaled }, + { VertexAttributeFormat.R8G8B8Uscaled, Format.R8G8B8Uscaled }, + { VertexAttributeFormat.R8G8B8Sscaled, Format.R8G8B8Sscaled }, + { VertexAttributeFormat.R16G16B16Uscaled, Format.R16G16B16Uscaled }, + { VertexAttributeFormat.R16G16B16Sscaled, Format.R16G16B16Sscaled }, + { VertexAttributeFormat.R32G32B32Uscaled, Format.R32G32B32Uscaled }, + { VertexAttributeFormat.R32G32B32Sscaled, Format.R32G32B32Sscaled }, + { VertexAttributeFormat.R8G8B8A8Uscaled, Format.R8G8B8A8Uscaled }, + { VertexAttributeFormat.R8G8B8A8Sscaled, Format.R8G8B8A8Sscaled }, + { VertexAttributeFormat.R16G16B16A16Uscaled, Format.R16G16B16A16Uscaled }, + { VertexAttributeFormat.R16G16B16A16Sscaled, Format.R16G16B16A16Sscaled }, + { VertexAttributeFormat.R32G32B32A32Uscaled, Format.R32G32B32A32Uscaled }, + { VertexAttributeFormat.R32G32B32A32Sscaled, Format.R32G32B32A32Sscaled }, + { VertexAttributeFormat.A2B10G10R10Snorm, Format.R10G10B10A2Snorm }, + { VertexAttributeFormat.A2B10G10R10Sint, Format.R10G10B10A2Sint }, + { VertexAttributeFormat.A2B10G10R10Uscaled, Format.R10G10B10A2Uscaled }, + { VertexAttributeFormat.A2B10G10R10Sscaled, Format.R10G10B10A2Sscaled }, + }; +#pragma warning restore IDE0055 + + // Note: Some of those formats have been changed and requires conversion on the shader, + // as GPUs don't support them when used as buffer texture format. + private static readonly Dictionary _singleComponentAttribFormats = new() + { + { VertexAttributeFormat.R8Unorm, (Format.R8Unorm, 1) }, + { VertexAttributeFormat.R8Snorm, (Format.R8Snorm, 1) }, + { VertexAttributeFormat.R8Uint, (Format.R8Uint, 1) }, + { VertexAttributeFormat.R8Sint, (Format.R8Sint, 1) }, + { VertexAttributeFormat.R16Float, (Format.R16Float, 1) }, + { VertexAttributeFormat.R16Unorm, (Format.R16Unorm, 1) }, + { VertexAttributeFormat.R16Snorm, (Format.R16Snorm, 1) }, + { VertexAttributeFormat.R16Uint, (Format.R16Uint, 1) }, + { VertexAttributeFormat.R16Sint, (Format.R16Sint, 1) }, + { VertexAttributeFormat.R32Float, (Format.R32Float, 1) }, + { VertexAttributeFormat.R32Uint, (Format.R32Uint, 1) }, + { VertexAttributeFormat.R32Sint, (Format.R32Sint, 1) }, + { VertexAttributeFormat.R8G8Unorm, (Format.R8Unorm, 2) }, + { VertexAttributeFormat.R8G8Snorm, (Format.R8Snorm, 2) }, + { VertexAttributeFormat.R8G8Uint, (Format.R8Uint, 2) }, + { VertexAttributeFormat.R8G8Sint, (Format.R8Sint, 2) }, + { VertexAttributeFormat.R16G16Float, (Format.R16Float, 2) }, + { VertexAttributeFormat.R16G16Unorm, (Format.R16Unorm, 2) }, + { VertexAttributeFormat.R16G16Snorm, (Format.R16Snorm, 2) }, + { VertexAttributeFormat.R16G16Uint, (Format.R16Uint, 2) }, + { VertexAttributeFormat.R16G16Sint, (Format.R16Sint, 2) }, + { VertexAttributeFormat.R32G32Float, (Format.R32Float, 2) }, + { VertexAttributeFormat.R32G32Uint, (Format.R32Uint, 2) }, + { VertexAttributeFormat.R32G32Sint, (Format.R32Sint, 2) }, + { VertexAttributeFormat.R8G8B8Unorm, (Format.R8Unorm, 3) }, + { VertexAttributeFormat.R8G8B8Snorm, (Format.R8Snorm, 3) }, + { VertexAttributeFormat.R8G8B8Uint, (Format.R8Uint, 3) }, + { VertexAttributeFormat.R8G8B8Sint, (Format.R8Sint, 3) }, + { VertexAttributeFormat.R16G16B16Float, (Format.R16Float, 3) }, + { VertexAttributeFormat.R16G16B16Unorm, (Format.R16Unorm, 3) }, + { VertexAttributeFormat.R16G16B16Snorm, (Format.R16Snorm, 3) }, + { VertexAttributeFormat.R16G16B16Uint, (Format.R16Uint, 3) }, + { VertexAttributeFormat.R16G16B16Sint, (Format.R16Sint, 3) }, + { VertexAttributeFormat.R32G32B32Float, (Format.R32Float, 3) }, + { VertexAttributeFormat.R32G32B32Uint, (Format.R32Uint, 3) }, + { VertexAttributeFormat.R32G32B32Sint, (Format.R32Sint, 3) }, + { VertexAttributeFormat.R8G8B8A8Unorm, (Format.R8Unorm, 4) }, + { VertexAttributeFormat.R8G8B8A8Snorm, (Format.R8Snorm, 4) }, + { VertexAttributeFormat.R8G8B8A8Uint, (Format.R8Uint, 4) }, + { VertexAttributeFormat.R8G8B8A8Sint, (Format.R8Sint, 4) }, + { VertexAttributeFormat.R16G16B16A16Float, (Format.R16Float, 4) }, + { VertexAttributeFormat.R16G16B16A16Unorm, (Format.R16Unorm, 4) }, + { VertexAttributeFormat.R16G16B16A16Snorm, (Format.R16Snorm, 4) }, + { VertexAttributeFormat.R16G16B16A16Uint, (Format.R16Uint, 4) }, + { VertexAttributeFormat.R16G16B16A16Sint, (Format.R16Sint, 4) }, + { VertexAttributeFormat.R32G32B32A32Float, (Format.R32Float, 4) }, + { VertexAttributeFormat.R32G32B32A32Uint, (Format.R32Uint, 4) }, + { VertexAttributeFormat.R32G32B32A32Sint, (Format.R32Sint, 4) }, + { VertexAttributeFormat.A2B10G10R10Unorm, (Format.R10G10B10A2Unorm, 4) }, + { VertexAttributeFormat.A2B10G10R10Uint, (Format.R10G10B10A2Uint, 4) }, + { VertexAttributeFormat.B10G11R11Float, (Format.R11G11B10Float, 3) }, + { VertexAttributeFormat.R8Uscaled, (Format.R8Uint, 1) }, // Uscaled -> Uint + { VertexAttributeFormat.R8Sscaled, (Format.R8Sint, 1) }, // Sscaled -> Sint + { VertexAttributeFormat.R16Uscaled, (Format.R16Uint, 1) }, // Uscaled -> Uint + { VertexAttributeFormat.R16Sscaled, (Format.R16Sint, 1) }, // Sscaled -> Sint + { VertexAttributeFormat.R32Uscaled, (Format.R32Uint, 1) }, // Uscaled -> Uint + { VertexAttributeFormat.R32Sscaled, (Format.R32Sint, 1) }, // Sscaled -> Sint + { VertexAttributeFormat.R8G8Uscaled, (Format.R8Uint, 2) }, // Uscaled -> Uint + { VertexAttributeFormat.R8G8Sscaled, (Format.R8Sint, 2) }, // Sscaled -> Sint + { VertexAttributeFormat.R16G16Uscaled, (Format.R16Uint, 2) }, // Uscaled -> Uint + { VertexAttributeFormat.R16G16Sscaled, (Format.R16Sint, 2) }, // Sscaled -> Sint + { VertexAttributeFormat.R32G32Uscaled, (Format.R32Uint, 2) }, // Uscaled -> Uint + { VertexAttributeFormat.R32G32Sscaled, (Format.R32Sint, 2) }, // Sscaled -> Sint + { VertexAttributeFormat.R8G8B8Uscaled, (Format.R8Uint, 3) }, // Uscaled -> Uint + { VertexAttributeFormat.R8G8B8Sscaled, (Format.R8Sint, 3) }, // Sscaled -> Sint + { VertexAttributeFormat.R16G16B16Uscaled, (Format.R16Uint, 3) }, // Uscaled -> Uint + { VertexAttributeFormat.R16G16B16Sscaled, (Format.R16Sint, 3) }, // Sscaled -> Sint + { VertexAttributeFormat.R32G32B32Uscaled, (Format.R32Uint, 3) }, // Uscaled -> Uint + { VertexAttributeFormat.R32G32B32Sscaled, (Format.R32Sint , 3) }, // Sscaled -> Sint + { VertexAttributeFormat.R8G8B8A8Uscaled, (Format.R8Uint, 4) }, // Uscaled -> Uint + { VertexAttributeFormat.R8G8B8A8Sscaled, (Format.R8Sint, 4) }, // Sscaled -> Sint + { VertexAttributeFormat.R16G16B16A16Uscaled, (Format.R16Uint, 4) }, // Uscaled -> Uint + { VertexAttributeFormat.R16G16B16A16Sscaled, (Format.R16Sint, 4) }, // Sscaled -> Sint + { VertexAttributeFormat.R32G32B32A32Uscaled, (Format.R32Uint, 4) }, // Uscaled -> Uint + { VertexAttributeFormat.R32G32B32A32Sscaled, (Format.R32Sint, 4) }, // Sscaled -> Sint + { VertexAttributeFormat.A2B10G10R10Snorm, (Format.R10G10B10A2Uint, 4) }, // Snorm -> Uint + { VertexAttributeFormat.A2B10G10R10Sint, (Format.R10G10B10A2Uint, 4) }, // Sint -> Uint + { VertexAttributeFormat.A2B10G10R10Uscaled, (Format.R10G10B10A2Uint, 4) }, // Uscaled -> Uint + { VertexAttributeFormat.A2B10G10R10Sscaled, (Format.R10G10B10A2Sint, 4) } // Sscaled -> Sint + }; + + /// + /// Try getting the texture format from an encoded format integer from the Maxwell texture descriptor. + /// + /// The encoded format integer from the texture descriptor + /// Indicates if the format is a sRGB format + /// The output texture format + /// True if the format is valid, false otherwise + public static bool TryGetTextureFormat(uint encoded, bool isSrgb, out FormatInfo format) + { + bool isPacked = (encoded & 0x80000000u) != 0; + if (isPacked) + { + encoded &= ~0x80000000u; + } + + encoded |= isSrgb ? 1u << 19 : 0u; + + bool found = _textureFormats.TryGetValue((TextureFormat)encoded, out format); + + if (found && isPacked && !format.Format.IsDepthOrStencil()) + { + // If the packed flag is set, then the components of the pixel are tightly packed into the + // GPU registers on the shader. + // We can get the same behaviour by aliasing the texture as a format with the same amount of + // bytes per pixel, but only a single or the lowest possible number of components. + + format = format.BytesPerPixel switch + { + 1 => new FormatInfo(Format.R8Unorm, 1, 1, 1, 1), + 2 => new FormatInfo(Format.R16Unorm, 1, 1, 2, 1), + 4 => new FormatInfo(Format.R32Uint, 1, 1, 4, 1), + 8 => new FormatInfo(Format.R32G32Uint, 1, 1, 8, 2), + 16 => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4), + _ => format, + }; + } + + return found; + } + + /// + /// Try getting the vertex attribute format from an encoded format integer from Maxwell attribute registers. + /// + /// The encoded format integer from the attribute registers + /// The output vertex attribute format + /// True if the format is valid, false otherwise + public static bool TryGetAttribFormat(uint encoded, out Format format) + { + return _attribFormats.TryGetValue((VertexAttributeFormat)encoded, out format); + } + + /// + /// Try getting a single component vertex attribute format from an encoded format integer from Maxwell attribute registers. + /// + /// The encoded format integer from the attribute registers + /// The output single component vertex attribute format + /// Number of components that the format has + /// True if the format is valid, false otherwise + public static bool TryGetSingleComponentAttribFormat(uint encoded, out Format format, out int componentsCount) + { + bool result = _singleComponentAttribFormats.TryGetValue((VertexAttributeFormat)encoded, out var tuple); + + format = tuple.Item1; + componentsCount = tuple.Item2; + + return result; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/ITextureDescriptor.cs b/src/Ryujinx.Graphics.Gpu/Image/ITextureDescriptor.cs new file mode 100644 index 00000000..d287acc2 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/ITextureDescriptor.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + interface ITextureDescriptor + { + public uint UnpackFormat(); + public TextureTarget UnpackTextureTarget(); + public bool UnpackSrgb(); + public bool UnpackTextureCoordNormalized(); + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/Pool.cs b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs new file mode 100644 index 00000000..e12fedc7 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/Pool.cs @@ -0,0 +1,254 @@ +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Memory.Tracking; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Represents a pool of GPU resources, such as samplers or textures. + /// + /// Type of the GPU resource + /// Type of the descriptor + abstract class Pool : IDisposable where T2 : unmanaged + { + protected const int DescriptorSize = 0x20; + + protected GpuContext Context; + protected PhysicalMemory PhysicalMemory; + protected int SequenceNumber; + protected int ModifiedSequenceNumber; + + protected T1[] Items; + protected T2[] DescriptorCache; + + /// + /// The maximum ID value of resources on the pool (inclusive). + /// + /// + /// The maximum amount of resources on the pool is equal to this value plus one. + /// + public int MaximumId { get; } + + /// + /// The address of the pool in guest memory. + /// + public ulong Address { get; } + + /// + /// The size of the pool in bytes. + /// + public ulong Size { get; } + + private readonly MultiRegionHandle _memoryTracking; + private readonly Action _modifiedDelegate; + + private int _modifiedSequenceOffset; + private bool _modified; + + /// + /// Creates a new instance of the GPU resource pool. + /// + /// GPU context that the pool belongs to + /// Physical memory where the resource descriptors are mapped + /// Address of the pool in physical memory + /// Maximum index of an item on the pool (inclusive) + public Pool(GpuContext context, PhysicalMemory physicalMemory, ulong address, int maximumId) + { + Context = context; + PhysicalMemory = physicalMemory; + MaximumId = maximumId; + + int count = maximumId + 1; + + ulong size = (ulong)(uint)count * DescriptorSize; + + Items = new T1[count]; + DescriptorCache = new T2[count]; + + Address = address; + Size = size; + + _memoryTracking = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Pool, RegionFlags.None); + _memoryTracking.RegisterPreciseAction(address, size, PreciseAction); + _modifiedDelegate = RegionModified; + } + + /// + /// Gets the descriptor for a given ID. + /// + /// ID of the descriptor. This is effectively a zero-based index + /// The descriptor + public T2 GetDescriptor(int id) + { + return PhysicalMemory.Read(Address + (ulong)id * DescriptorSize); + } + + /// + /// Gets a reference to the descriptor for a given ID. + /// + /// ID of the descriptor. This is effectively a zero-based index + /// A reference to the descriptor + public ref readonly T2 GetDescriptorRef(int id) + { + return ref GetDescriptorRefAddress(Address + (ulong)id * DescriptorSize); + } + + /// + /// Gets a reference to the descriptor for a given address. + /// + /// Address of the descriptor + /// A reference to the descriptor + public ref readonly T2 GetDescriptorRefAddress(ulong address) + { + return ref MemoryMarshal.Cast(PhysicalMemory.GetSpan(address, DescriptorSize))[0]; + } + + /// + /// Gets the GPU resource with the given ID. + /// + /// ID of the resource. This is effectively a zero-based index + /// The GPU resource with the given ID + public abstract T1 Get(int id); + + /// + /// Gets the cached item with the given ID, or null if there is no cached item for the specified ID. + /// + /// ID of the item. This is effectively a zero-based index + /// The cached item with the given ID + public T1 GetCachedItem(int id) + { + if (!IsValidId(id)) + { + return default; + } + + return Items[id]; + } + + /// + /// Checks if a given ID is valid and inside the range of the pool. + /// + /// ID of the descriptor. This is effectively a zero-based index + /// True if the specified ID is valid, false otherwise + public bool IsValidId(int id) + { + return (uint)id <= MaximumId; + } + + /// + /// Synchronizes host memory with guest memory. + /// This causes invalidation of pool entries, + /// if a modification of entries by the CPU is detected. + /// + public void SynchronizeMemory() + { + _modified = false; + _memoryTracking.QueryModified(_modifiedDelegate); + + if (_modified) + { + UpdateModifiedSequence(); + } + } + + /// + /// Indicate that a region of the pool was modified, and must be loaded from memory. + /// + /// Start address of the modified region + /// Size of the modified region + private void RegionModified(ulong mAddress, ulong mSize) + { + _modified = true; + + if (mAddress < Address) + { + mAddress = Address; + } + + ulong maxSize = Address + Size - mAddress; + + if (mSize > maxSize) + { + mSize = maxSize; + } + + InvalidateRangeImpl(mAddress, mSize); + } + + /// + /// Updates the modified sequence number using the current sequence number and offset, + /// indicating that it has been modified. + /// + protected void UpdateModifiedSequence() + { + ModifiedSequenceNumber = SequenceNumber + _modifiedSequenceOffset; + } + + /// + /// An action to be performed when a precise memory access occurs to this resource. + /// Makes sure that the dirty flags are checked. + /// + /// Address of the memory action + /// Size in bytes + /// True if the access was a write, false otherwise + private bool PreciseAction(ulong address, ulong size, bool write) + { + if (write && Context.SequenceNumber == SequenceNumber) + { + if (ModifiedSequenceNumber == SequenceNumber + _modifiedSequenceOffset) + { + // The modified sequence number is offset when PreciseActions occur so that + // users checking it will see an increment and know the pool has changed since + // their last look, even though the main SequenceNumber has not been changed. + + _modifiedSequenceOffset++; + } + + // Force the pool to be checked again the next time it is used. + SequenceNumber--; + } + + return false; + } + + /// + /// Checks if the pool was modified by comparing the current with a cached one. + /// + /// Cached modified sequence number + /// True if the pool was modified, false otherwise + public bool WasModified(ref int sequenceNumber) + { + if (sequenceNumber != ModifiedSequenceNumber) + { + sequenceNumber = ModifiedSequenceNumber; + + return true; + } + + return false; + } + + protected abstract void InvalidateRangeImpl(ulong address, ulong size); + + protected abstract void Delete(T1 item); + + /// + /// Performs the disposal of all resources stored on the pool. + /// It's an error to try using the pool after disposal. + /// + public virtual void Dispose() + { + if (Items != null) + { + for (int index = 0; index < Items.Length; index++) + { + Delete(Items[index]); + } + + Items = null; + } + _memoryTracking.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs b/src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs new file mode 100644 index 00000000..50872ab6 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Resource pool interface. + /// + /// Resource pool type + interface IPool + { + /// + /// Start address of the pool in memory. + /// + ulong Address { get; } + + /// + /// Linked list node used on the texture pool cache. + /// + LinkedListNode CacheNode { get; set; } + + /// + /// Timestamp set on the last use of the pool by the cache. + /// + ulong CacheTimestamp { get; set; } + } + + /// + /// Pool cache. + /// This can keep multiple pools, and return the current one as needed. + /// + abstract class PoolCache : IDisposable where T : IPool, IDisposable + { + private const int MaxCapacity = 2; + private const ulong MinDeltaForRemoval = 20000; + + private readonly GpuContext _context; + private readonly LinkedList _pools; + private ulong _currentTimestamp; + + /// + /// Constructs a new instance of the pool. + /// + /// GPU context that the texture pool belongs to + public PoolCache(GpuContext context) + { + _context = context; + _pools = new LinkedList(); + } + + /// + /// Increments the internal timestamp of the cache that is used to decide when old resources will be deleted. + /// + public void Tick() + { + _currentTimestamp++; + } + + /// + /// Finds a cache texture pool, or creates a new one if not found. + /// + /// GPU channel that the texture pool cache belongs to + /// Start address of the texture pool + /// Maximum ID of the texture pool + /// Cache of texture array bindings + /// The found or newly created texture pool + public T FindOrCreate(GpuChannel channel, ulong address, int maximumId, TextureBindingsArrayCache bindingsArrayCache) + { + // Remove old entries from the cache, if possible. + while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval) + { + T oldestPool = _pools.First.Value; + + _pools.RemoveFirst(); + oldestPool.Dispose(); + oldestPool.CacheNode = null; + bindingsArrayCache.RemoveAllWithPool(oldestPool); + } + + T pool; + + // Try to find the pool on the cache. + for (LinkedListNode node = _pools.First; node != null; node = node.Next) + { + pool = node.Value; + + if (pool.Address == address) + { + if (pool.CacheNode != _pools.Last) + { + _pools.Remove(pool.CacheNode); + _pools.AddLast(pool.CacheNode); + } + + pool.CacheTimestamp = _currentTimestamp; + + return pool; + } + } + + // If not found, create a new one. + pool = CreatePool(_context, channel, address, maximumId); + + pool.CacheNode = _pools.AddLast(pool); + pool.CacheTimestamp = _currentTimestamp; + + return pool; + } + + /// + /// Creates a new instance of the pool. + /// + /// GPU context that the pool belongs to + /// GPU channel that the pool belongs to + /// Address of the pool in guest memory + /// Maximum ID of the pool (equal to maximum minus one) + protected abstract T CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId); + + public void Dispose() + { + foreach (T pool in _pools) + { + pool.Dispose(); + pool.CacheNode = null; + } + + _pools.Clear(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs b/src/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs new file mode 100644 index 00000000..01553e50 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Represents a filter used with texture minification linear filtering. + /// + /// + /// This feature is only supported on NVIDIA GPUs. + /// + enum ReductionFilter + { + Average, + Minimum, + Maximum, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/Sampler.cs b/src/Ryujinx.Graphics.Gpu/Image/Sampler.cs new file mode 100644 index 00000000..d6a3d975 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/Sampler.cs @@ -0,0 +1,115 @@ +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Cached sampler entry for sampler pools. + /// + class Sampler : IDisposable + { + /// + /// True if the sampler is disposed, false otherwise. + /// + public bool IsDisposed { get; private set; } + + /// + /// Host sampler object. + /// + private readonly ISampler _hostSampler; + + /// + /// Host sampler object, with anisotropy forced. + /// + private readonly ISampler _anisoSampler; + + /// + /// Creates a new instance of the cached sampler. + /// + /// The GPU context the sampler belongs to + /// The Maxwell sampler descriptor + public Sampler(GpuContext context, SamplerDescriptor descriptor) + { + MinFilter minFilter = descriptor.UnpackMinFilter(); + MagFilter magFilter = descriptor.UnpackMagFilter(); + + bool seamlessCubemap = descriptor.UnpackSeamlessCubemap(); + + AddressMode addressU = descriptor.UnpackAddressU(); + AddressMode addressV = descriptor.UnpackAddressV(); + AddressMode addressP = descriptor.UnpackAddressP(); + + CompareMode compareMode = descriptor.UnpackCompareMode(); + CompareOp compareOp = descriptor.UnpackCompareOp(); + + ColorF color = new( + descriptor.BorderColorR, + descriptor.BorderColorG, + descriptor.BorderColorB, + descriptor.BorderColorA); + + float minLod = descriptor.UnpackMinLod(); + float maxLod = descriptor.UnpackMaxLod(); + float mipLodBias = descriptor.UnpackMipLodBias(); + + float maxRequestedAnisotropy = descriptor.UnpackMaxAnisotropy(); + float maxSupportedAnisotropy = context.Capabilities.MaximumSupportedAnisotropy; + + _hostSampler = context.Renderer.CreateSampler(new SamplerCreateInfo( + minFilter, + magFilter, + seamlessCubemap, + addressU, + addressV, + addressP, + compareMode, + compareOp, + color, + minLod, + maxLod, + mipLodBias, + Math.Min(maxRequestedAnisotropy, maxSupportedAnisotropy))); + + if (GraphicsConfig.MaxAnisotropy >= 0 && GraphicsConfig.MaxAnisotropy <= 16 && (minFilter == MinFilter.LinearMipmapNearest || minFilter == MinFilter.LinearMipmapLinear)) + { + maxRequestedAnisotropy = GraphicsConfig.MaxAnisotropy; + + _anisoSampler = context.Renderer.CreateSampler(new SamplerCreateInfo( + minFilter, + magFilter, + seamlessCubemap, + addressU, + addressV, + addressP, + compareMode, + compareOp, + color, + minLod, + maxLod, + mipLodBias, + Math.Min(maxRequestedAnisotropy, maxSupportedAnisotropy))); + } + } + + /// + /// Gets a host sampler for the given texture. + /// + /// Texture to be sampled + /// A host sampler + public ISampler GetHostSampler(Texture texture) + { + return _anisoSampler != null && texture?.CanForceAnisotropy == true ? _anisoSampler : _hostSampler; + } + + /// + /// Disposes the host sampler object. + /// + public void Dispose() + { + IsDisposed = true; + + _hostSampler.Dispose(); + _anisoSampler?.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs b/src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs new file mode 100644 index 00000000..e04c31df --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs @@ -0,0 +1,266 @@ +using Ryujinx.Graphics.GAL; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Maxwell sampler descriptor structure. + /// This structure defines the sampler descriptor as it is packed on the GPU sampler pool region. + /// + struct SamplerDescriptor + { + private static readonly float[] _f5ToF32ConversionLut = new float[] + { + 0.0f, + 0.055555556f, + 0.1f, + 0.13636364f, + 0.16666667f, + 0.1923077f, + 0.21428572f, + 0.23333333f, + 0.25f, + 0.2777778f, + 0.3f, + 0.3181818f, + 0.33333334f, + 0.34615386f, + 0.35714287f, + 0.36666667f, + 0.375f, + 0.3888889f, + 0.4f, + 0.4090909f, + 0.41666666f, + 0.42307693f, + 0.42857143f, + 0.43333334f, + 0.4375f, + 0.44444445f, + 0.45f, + 0.45454547f, + 0.45833334f, + 0.46153846f, + 0.4642857f, + 0.46666667f, + }; + + private static readonly float[] _maxAnisotropyLut = new float[] + { + 1, 2, 4, 6, 8, 10, 12, 16, + }; + + private const float Frac8ToF32 = 1.0f / 256.0f; + +#pragma warning disable CS0649 // Field is never assigned to + public uint Word0; + public uint Word1; + public uint Word2; + public uint Word3; + public float BorderColorR; + public float BorderColorG; + public float BorderColorB; + public float BorderColorA; +#pragma warning restore CS0649 + + /// + /// Unpacks the texture wrap mode along the X axis. + /// + /// The texture wrap mode enum + public readonly AddressMode UnpackAddressU() + { + return (AddressMode)(Word0 & 7); + } + + // + /// Unpacks the texture wrap mode along the Y axis. + /// + /// The texture wrap mode enum + public readonly AddressMode UnpackAddressV() + { + return (AddressMode)((Word0 >> 3) & 7); + } + + // + /// Unpacks the texture wrap mode along the Z axis. + /// + /// The texture wrap mode enum + public readonly AddressMode UnpackAddressP() + { + return (AddressMode)((Word0 >> 6) & 7); + } + + /// + /// Unpacks the compare mode used for depth comparison on the shader, for + /// depth buffer texture. + /// This is only relevant for shaders with shadow samplers. + /// + /// The depth comparison mode enum + public readonly CompareMode UnpackCompareMode() + { + return (CompareMode)((Word0 >> 9) & 1); + } + + /// + /// Unpacks the compare operation used for depth comparison on the shader, for + /// depth buffer texture. + /// This is only relevant for shaders with shadow samplers. + /// + /// The depth comparison operation enum + public readonly CompareOp UnpackCompareOp() + { + return (CompareOp)(((Word0 >> 10) & 7) + 1); + } + + /// + /// Unpacks and converts the maximum anisotropy value used for texture anisotropic filtering. + /// + /// The maximum anisotropy + public readonly float UnpackMaxAnisotropy() + { + return _maxAnisotropyLut[(Word0 >> 20) & 7]; + } + + /// + /// Unpacks the texture magnification filter. + /// This defines the filtering used when the texture covers an area on the screen + /// that is larger than the texture size. + /// + /// The magnification filter + public readonly MagFilter UnpackMagFilter() + { + return (MagFilter)(Word1 & 3); + } + + /// + /// Unpacks the texture minification filter. + /// This defines the filtering used when the texture covers an area on the screen + /// that is smaller than the texture size. + /// + /// The minification filter + public readonly MinFilter UnpackMinFilter() + { + SamplerMinFilter minFilter = (SamplerMinFilter)((Word1 >> 4) & 3); + SamplerMipFilter mipFilter = (SamplerMipFilter)((Word1 >> 6) & 3); + + return ConvertFilter(minFilter, mipFilter); + } + + /// + /// Converts two minification and filter enum, to a single minification enum, + /// including mipmap filtering information, as expected from the host API. + /// + /// The minification filter + /// The mipmap level filter + /// The combined, host API compatible filter enum + private static MinFilter ConvertFilter(SamplerMinFilter minFilter, SamplerMipFilter mipFilter) + { + switch (mipFilter) + { + case SamplerMipFilter.None: + switch (minFilter) + { + case SamplerMinFilter.Nearest: + return MinFilter.Nearest; + case SamplerMinFilter.Linear: + return MinFilter.Linear; + } + break; + + case SamplerMipFilter.Nearest: + switch (minFilter) + { + case SamplerMinFilter.Nearest: + return MinFilter.NearestMipmapNearest; + case SamplerMinFilter.Linear: + return MinFilter.LinearMipmapNearest; + } + break; + + case SamplerMipFilter.Linear: + switch (minFilter) + { + case SamplerMinFilter.Nearest: + return MinFilter.NearestMipmapLinear; + case SamplerMinFilter.Linear: + return MinFilter.LinearMipmapLinear; + } + break; + } + + return MinFilter.Nearest; + } + + /// + /// Unpacks the seamless cubemap flag. + /// + /// The seamless cubemap flag + public readonly bool UnpackSeamlessCubemap() + { + return (Word1 & (1 << 9)) != 0; + } + + /// + /// Unpacks the reduction filter, used with texture minification linear filtering. + /// This describes how the final value will be computed from neighbouring pixels. + /// + /// The reduction filter + public readonly ReductionFilter UnpackReductionFilter() + { + return (ReductionFilter)((Word1 >> 10) & 3); + } + + /// + /// Unpacks the level-of-detail bias value. + /// This is a bias added to the level-of-detail value as computed by the GPU, used to select + /// which mipmap level to use from a given texture. + /// + /// The level-of-detail bias value + public readonly float UnpackMipLodBias() + { + int fixedValue = (int)(Word1 >> 12) & 0x1fff; + + fixedValue = (fixedValue << 19) >> 19; + + return fixedValue * Frac8ToF32; + } + + /// + /// Unpacks the level-of-detail snap value. + /// + /// The level-of-detail snap value + public readonly float UnpackLodSnap() + { + return _f5ToF32ConversionLut[(Word1 >> 26) & 0x1f]; + } + + /// + /// Unpacks the minimum level-of-detail value. + /// + /// The minimum level-of-detail value + public readonly float UnpackMinLod() + { + return (Word2 & 0xfff) * Frac8ToF32; + } + + /// + /// Unpacks the maximum level-of-detail value. + /// + /// The maximum level-of-detail value + public readonly float UnpackMaxLod() + { + return ((Word2 >> 12) & 0xfff) * Frac8ToF32; + } + + /// + /// Check if two descriptors are equal. + /// + /// The descriptor to compare against + /// True if they are equal, false otherwise + public bool Equals(ref SamplerDescriptor other) + { + return Unsafe.As>(ref this).Equals(Unsafe.As>(ref other)); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/SamplerMinFilter.cs b/src/Ryujinx.Graphics.Gpu/Image/SamplerMinFilter.cs new file mode 100644 index 00000000..d3009219 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/SamplerMinFilter.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Sampler texture minification filter. + /// + enum SamplerMinFilter + { + Nearest = 1, + Linear, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/SamplerMipFilter.cs b/src/Ryujinx.Graphics.Gpu/Image/SamplerMipFilter.cs new file mode 100644 index 00000000..b965f0c3 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/SamplerMipFilter.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Sampler texture mipmap level filter. + /// + enum SamplerMipFilter + { + None = 1, + Nearest, + Linear, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs b/src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs new file mode 100644 index 00000000..3efcad76 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs @@ -0,0 +1,162 @@ +using Ryujinx.Graphics.Gpu.Memory; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Sampler pool. + /// + class SamplerPool : Pool, IPool + { + private float _forcedAnisotropy; + + /// + /// Linked list node used on the sampler pool cache. + /// + public LinkedListNode CacheNode { get; set; } + + /// + /// Timestamp used by the sampler pool cache, updated on every use of this sampler pool. + /// + public ulong CacheTimestamp { get; set; } + + /// + /// Creates a new instance of the sampler pool. + /// + /// GPU context that the sampler pool belongs to + /// Physical memory where the sampler descriptors are mapped + /// Address of the sampler pool in guest memory + /// Maximum sampler ID of the sampler pool (equal to maximum samplers minus one) + public SamplerPool(GpuContext context, PhysicalMemory physicalMemory, ulong address, int maximumId) : base(context, physicalMemory, address, maximumId) + { + _forcedAnisotropy = GraphicsConfig.MaxAnisotropy; + } + + /// + /// Gets the sampler with the given ID. + /// + /// ID of the sampler. This is effectively a zero-based index + /// The sampler with the given ID + public override Sampler Get(int id) + { + if ((uint)id >= Items.Length) + { + return null; + } + + if (SequenceNumber != Context.SequenceNumber) + { + if (_forcedAnisotropy != GraphicsConfig.MaxAnisotropy) + { + _forcedAnisotropy = GraphicsConfig.MaxAnisotropy; + + for (int i = 0; i < Items.Length; i++) + { + if (Items[i] != null) + { + Items[i].Dispose(); + + Items[i] = null; + } + } + + UpdateModifiedSequence(); + } + + SequenceNumber = Context.SequenceNumber; + + SynchronizeMemory(); + } + + Sampler sampler = Items[id]; + + if (sampler == null) + { + SamplerDescriptor descriptor = GetDescriptor(id); + + sampler = new Sampler(Context, descriptor); + + Items[id] = sampler; + + DescriptorCache[id] = descriptor; + } + + return sampler; + } + + /// + /// Checks if the pool was modified, and returns the last sequence number where a modification was detected. + /// + /// A number that increments each time a modification is detected + public int CheckModified() + { + if (SequenceNumber != Context.SequenceNumber) + { + SequenceNumber = Context.SequenceNumber; + + if (_forcedAnisotropy != GraphicsConfig.MaxAnisotropy) + { + _forcedAnisotropy = GraphicsConfig.MaxAnisotropy; + + for (int i = 0; i < Items.Length; i++) + { + if (Items[i] != null) + { + Items[i].Dispose(); + + Items[i] = null; + } + } + + UpdateModifiedSequence(); + } + + SynchronizeMemory(); + } + + return ModifiedSequenceNumber; + } + + /// + /// Implementation of the sampler pool range invalidation. + /// + /// Start address of the range of the sampler pool + /// Size of the range being invalidated + protected override void InvalidateRangeImpl(ulong address, ulong size) + { + ulong endAddress = address + size; + + for (; address < endAddress; address += DescriptorSize) + { + int id = (int)((address - Address) / DescriptorSize); + + Sampler sampler = Items[id]; + + if (sampler != null) + { + SamplerDescriptor descriptor = GetDescriptor(id); + + // If the descriptors are the same, the sampler is still valid. + if (descriptor.Equals(ref DescriptorCache[id])) + { + continue; + } + + sampler.Dispose(); + + Items[id] = null; + } + } + } + + /// + /// Deletes a given sampler pool entry. + /// The host memory used by the sampler is released by the driver. + /// + /// The entry to be deleted + protected override void Delete(Sampler item) + { + item?.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/SamplerPoolCache.cs b/src/Ryujinx.Graphics.Gpu/Image/SamplerPoolCache.cs new file mode 100644 index 00000000..881c37af --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/SamplerPoolCache.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Sampler pool cache. + /// This can keep multiple sampler pools, and return the current one as needed. + /// It is useful for applications that uses multiple sampler pools. + /// + class SamplerPoolCache : PoolCache + { + /// + /// Constructs a new instance of the texture pool. + /// + /// GPU context that the texture pool belongs to + public SamplerPoolCache(GpuContext context) : base(context) + { + } + + /// + /// Creates a new instance of the sampler pool. + /// + /// GPU context that the sampler pool belongs to + /// GPU channel that the texture pool belongs to + /// Address of the sampler pool in guest memory + /// Maximum sampler ID of the sampler pool (equal to maximum samplers minus one) + protected override SamplerPool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId) + { + return new SamplerPool(context, channel.MemoryManager.Physical, address, maximumId); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs new file mode 100644 index 00000000..3b6c407c --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -0,0 +1,1771 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Texture; +using Ryujinx.Graphics.Texture.Astc; +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Numerics; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Represents a cached GPU texture. + /// + class Texture : IMultiRangeItem, IDisposable + { + // How many updates we need before switching to the byte-by-byte comparison + // modification check method. + // This method uses much more memory so we want to avoid it if possible. + private const int ByteComparisonSwitchThreshold = 4; + + // Tuning for blacklisting textures from scaling when their data is updated from CPU. + // Each write adds the weight, each GPU modification subtracts 1. + // Exceeding the threshold blacklists the texture. + private const int ScaledSetWeight = 10; + private const int ScaledSetThreshold = 30; + + private const int MinLevelsForForceAnisotropy = 5; + + private struct TexturePoolOwner + { + public TexturePool Pool; + public int ID; + public ulong GpuAddress; + } + + private GpuContext _context; + private PhysicalMemory _physicalMemory; + + private SizeInfo _sizeInfo; + + /// + /// Texture format. + /// + public Format Format => Info.FormatInfo.Format; + + /// + /// Texture target. + /// + public Target Target { get; private set; } + + /// + /// Texture width. + /// + public int Width { get; private set; } + + /// + /// Texture height. + /// + public int Height { get; private set; } + + /// + /// Texture information. + /// + public TextureInfo Info { get; private set; } + + /// + /// Set when anisotropic filtering can be forced on the given texture. + /// + public bool CanForceAnisotropy { get; private set; } + + /// + /// Host scale factor. + /// + public float ScaleFactor { get; private set; } + + /// + /// Upscaling mode. Informs if a texture is scaled, or is eligible for scaling. + /// + public TextureScaleMode ScaleMode { get; private set; } + + /// + /// Group that this texture belongs to. Manages read/write memory tracking. + /// + public TextureGroup Group { get; private set; } + + /// + /// Set when a texture's GPU VA has ever been partially or fully unmapped. + /// This indicates that the range must be fully checked when matching the texture. + /// + public bool ChangedMapping { get; private set; } + + /// + /// True if the data for this texture must always be flushed when an overlap appears. + /// This is useful if SetData is called directly on this texture, but the data is meant for a future texture. + /// + public bool AlwaysFlushOnOverlap { get; private set; } + + /// + /// Increments when the host texture is swapped, or when the texture is removed from all pools. + /// + public int InvalidatedSequence { get; private set; } + + private int _depth; + private int _layers; + public int FirstLayer { get; private set; } + public int FirstLevel { get; private set; } + + private bool _hasData; + private bool _dirty = true; + private int _updateCount; + private byte[] _currentData; + + private bool _modifiedStale = true; + + private ITexture _arrayViewTexture; + private Target _arrayViewTarget; + + private ITexture _flushHostTexture; + private ITexture _setHostTexture; + private int _scaledSetScore; + + private Texture _viewStorage; + + private List _views; + + /// + /// Host texture. + /// + public ITexture HostTexture { get; private set; } + + /// + /// Intrusive linked list node used on the auto deletion texture cache. + /// + public LinkedListNode CacheNode { get; set; } + + /// + /// Entry for this texture in the short duration cache, if present. + /// + public ShortTextureCacheEntry ShortCacheEntry { get; set; } + + /// + /// Whether this texture has ever been referenced by a pool. + /// + public bool HadPoolOwner { get; private set; } + + /// + /// Physical memory ranges where the texture data is located. + /// + public MultiRange Range { get; private set; } + + /// + /// Layer size in bytes. + /// + public int LayerSize => _sizeInfo.LayerSize; + + /// + /// Texture size in bytes. + /// + public ulong Size => (ulong)_sizeInfo.TotalSize; + + /// + /// Whether or not the texture belongs is a view. + /// + public bool IsView => _viewStorage != this; + + /// + /// Whether or not this texture has views. + /// + public bool HasViews => _views.Count > 0; + + private int _referenceCount; + private List _poolOwners; + + /// + /// Constructs a new instance of the cached GPU texture. + /// + /// GPU context that the texture belongs to + /// Physical memory where the texture is mapped + /// Texture information + /// Size information of the texture + /// Physical memory ranges where the texture data is located + /// The first layer of the texture, or 0 if the texture has no parent + /// The first mipmap level of the texture, or 0 if the texture has no parent + /// The floating point scale factor to initialize with + /// The scale mode to initialize with + private Texture( + GpuContext context, + PhysicalMemory physicalMemory, + TextureInfo info, + SizeInfo sizeInfo, + MultiRange range, + int firstLayer, + int firstLevel, + float scaleFactor, + TextureScaleMode scaleMode) + { + InitializeTexture(context, physicalMemory, info, sizeInfo, range); + + FirstLayer = firstLayer; + FirstLevel = firstLevel; + + ScaleFactor = scaleFactor; + ScaleMode = scaleMode; + + InitializeData(true); + } + + /// + /// Constructs a new instance of the cached GPU texture. + /// + /// GPU context that the texture belongs to + /// Physical memory where the texture is mapped + /// Texture information + /// Size information of the texture + /// Physical memory ranges where the texture data is located + /// The scale mode to initialize with. If scaled, the texture's data is loaded immediately and scaled up + public Texture( + GpuContext context, + PhysicalMemory physicalMemory, + TextureInfo info, + SizeInfo sizeInfo, + MultiRange range, + TextureScaleMode scaleMode) + { + ScaleFactor = 1f; // Texture is first loaded at scale 1x. + ScaleMode = scaleMode; + + InitializeTexture(context, physicalMemory, info, sizeInfo, range); + } + + /// + /// Common texture initialization method. + /// This sets the context, info and sizeInfo fields. + /// Other fields are initialized with their default values. + /// + /// GPU context that the texture belongs to + /// Physical memory where the texture is mapped + /// Texture information + /// Size information of the texture + /// Physical memory ranges where the texture data is located + private void InitializeTexture( + GpuContext context, + PhysicalMemory physicalMemory, + TextureInfo info, + SizeInfo sizeInfo, + MultiRange range) + { + _context = context; + _physicalMemory = physicalMemory; + _sizeInfo = sizeInfo; + Range = range; + + SetInfo(info); + + _viewStorage = this; + + _views = new List(); + _poolOwners = new List(); + } + + /// + /// Initializes the data for a texture. Can optionally initialize the texture with or without data. + /// If the texture is a view, it will initialize memory tracking to be non-dirty. + /// + /// True if the texture is a view, false otherwise + /// True if the texture is to be initialized with data + public void InitializeData(bool isView, bool withData = false) + { + withData |= Group != null && Group.FlushIncompatibleOverlapsIfNeeded(); + + if (withData) + { + Debug.Assert(!isView); + + TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor); + HostTexture = _context.Renderer.CreateTexture(createInfo); + + SynchronizeMemory(); // Load the data. + if (ScaleMode == TextureScaleMode.Scaled) + { + SetScale(GraphicsConfig.ResScale); // Scale the data up. + } + } + else + { + _hasData = true; + + if (!isView) + { + // Don't update this texture the next time we synchronize. + CheckModified(true); + + if (ScaleMode == TextureScaleMode.Scaled) + { + // Don't need to start at 1x as there is no data to scale, just go straight to the target scale. + ScaleFactor = GraphicsConfig.ResScale; + } + + TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, ScaleFactor); + HostTexture = _context.Renderer.CreateTexture(createInfo); + } + } + } + + /// + /// Initialize a new texture group with this texture as storage. + /// + /// True if the texture will have layer views + /// True if the texture will have mip views + /// Groups that overlap with this one but are incompatible + public void InitializeGroup(bool hasLayerViews, bool hasMipViews, List incompatibleOverlaps) + { + Group = new TextureGroup(_context, _physicalMemory, this, incompatibleOverlaps); + + Group.Initialize(ref _sizeInfo, hasLayerViews, hasMipViews); + } + + /// + /// Create a texture view from this texture. + /// A texture view is defined as a child texture, from a sub-range of their parent texture. + /// For example, the initial layer and mipmap level of the view can be defined, so the texture + /// will start at the given layer/level of the parent texture. + /// + /// Child texture information + /// Child texture size information + /// Physical memory ranges where the texture data is located + /// Start layer of the child texture on the parent texture + /// Start mipmap level of the child texture on the parent texture + /// The child texture + public Texture CreateView(TextureInfo info, SizeInfo sizeInfo, MultiRange range, int firstLayer, int firstLevel) + { + Texture texture = new( + _context, + _physicalMemory, + info, + sizeInfo, + range, + FirstLayer + firstLayer, + FirstLevel + firstLevel, + ScaleFactor, + ScaleMode); + + TextureCreateInfo createInfo = TextureCache.GetCreateInfo(info, _context.Capabilities, ScaleFactor); + texture.HostTexture = HostTexture.CreateView(createInfo, firstLayer, firstLevel); + + _viewStorage.AddView(texture); + + return texture; + } + + /// + /// Adds a child texture to this texture. + /// + /// The child texture + private void AddView(Texture texture) + { + IncrementReferenceCount(); + + _views.Add(texture); + + texture._viewStorage = this; + + Group.UpdateViews(_views, texture); + + if (texture.Group != null && texture.Group != Group) + { + if (texture.Group.Storage == texture) + { + // This texture's group is no longer used. + Group.Inherit(texture.Group); + + texture.Group.Dispose(); + } + } + + texture.Group = Group; + } + + /// + /// Removes a child texture from this texture. + /// + /// The child texture + private void RemoveView(Texture texture) + { + _views.Remove(texture); + + Group.RemoveView(_views, texture); + + texture._viewStorage = texture; + + DecrementReferenceCount(); + } + + /// + /// Replaces the texture's physical memory range. This forces tracking to regenerate. + /// + /// New physical memory range backing the texture + public void ReplaceRange(MultiRange range) + { + Range = range; + + Group.RangeChanged(); + } + + /// + /// Create a copy dependency to a texture that is view compatible with this one. + /// When either texture is modified, the texture data will be copied to the other to keep them in sync. + /// This is essentially an emulated view, useful for handling multiple view parents or format incompatibility. + /// This also forces a copy on creation, to or from the given texture to get them in sync immediately. + /// + /// The view compatible texture to create a dependency to + /// The base layer of the given texture relative to this one + /// The base level of the given texture relative to this one + /// True if this texture is first copied to the given one, false for the opposite direction + public void CreateCopyDependency(Texture contained, int layer, int level, bool copyTo) + { + if (contained.Group == Group) + { + return; + } + + Group.CreateCopyDependency(contained, FirstLayer + layer, FirstLevel + level, copyTo); + } + + /// + /// Registers when a texture has had its data set after being scaled, and + /// determines if it should be blacklisted from scaling to improve performance. + /// + /// True if setting data for a scaled texture is allowed, false if the texture has been blacklisted + private bool AllowScaledSetData() + { + _scaledSetScore += ScaledSetWeight; + + if (_scaledSetScore >= ScaledSetThreshold) + { + BlacklistScale(); + + return false; + } + + return true; + } + + /// + /// Blacklists this texture from being scaled. Resets its scale to 1 if needed. + /// + public void BlacklistScale() + { + ScaleMode = TextureScaleMode.Blacklisted; + SetScale(1f); + } + + /// + /// Propagates the scale between this texture and another to ensure they have the same scale. + /// If one texture is blacklisted from scaling, the other will become blacklisted too. + /// + /// The other texture + public void PropagateScale(Texture other) + { + if (other.ScaleMode == TextureScaleMode.Blacklisted || ScaleMode == TextureScaleMode.Blacklisted) + { + BlacklistScale(); + other.BlacklistScale(); + } + else + { + // Prefer the configured scale if present. If not, prefer the max. + float targetScale = GraphicsConfig.ResScale; + float sharedScale = (ScaleFactor == targetScale || other.ScaleFactor == targetScale) ? targetScale : Math.Max(ScaleFactor, other.ScaleFactor); + + SetScale(sharedScale); + other.SetScale(sharedScale); + } + } + + /// + /// Copy the host texture to a scaled one. If a texture is not provided, create it with the given scale. + /// + /// Scale factor + /// True if the data should be copied to the texture, false otherwise + /// Texture to use instead of creating one + /// A host texture containing a scaled version of this texture + private ITexture GetScaledHostTexture(float scale, bool copy, ITexture storage = null) + { + if (storage == null) + { + TextureCreateInfo createInfo = TextureCache.GetCreateInfo(Info, _context.Capabilities, scale); + storage = _context.Renderer.CreateTexture(createInfo); + } + + if (copy) + { + HostTexture.CopyTo(storage, new Extents2D(0, 0, HostTexture.Width, HostTexture.Height), new Extents2D(0, 0, storage.Width, storage.Height), true); + } + + return storage; + } + + /// + /// Sets the Scale Factor on this texture, and immediately recreates it at the correct size. + /// When a texture is resized, a scaled copy is performed from the old texture to the new one, to ensure no data is lost. + /// If scale is equivalent, this only propagates the blacklisted/scaled mode. + /// If called on a view, its storage is resized instead. + /// When resizing storage, all texture views are recreated. + /// + /// The new scale factor for this texture + public void SetScale(float scale) + { + bool unscaled = ScaleMode == TextureScaleMode.Blacklisted || (ScaleMode == TextureScaleMode.Undesired && scale == 1); + TextureScaleMode newScaleMode = unscaled ? ScaleMode : TextureScaleMode.Scaled; + + if (_viewStorage != this) + { + _viewStorage.ScaleMode = newScaleMode; + _viewStorage.SetScale(scale); + return; + } + + if (ScaleFactor != scale) + { + Logger.Debug?.Print(LogClass.Gpu, $"Rescaling {Info.Width}x{Info.Height} {Info.FormatInfo.Format} to ({ScaleFactor} to {scale}). "); + + ScaleFactor = scale; + + ITexture newStorage = GetScaledHostTexture(ScaleFactor, true); + + Logger.Debug?.Print(LogClass.Gpu, $" Copy performed: {HostTexture.Width}x{HostTexture.Height} to {newStorage.Width}x{newStorage.Height}"); + + ReplaceStorage(newStorage); + + // All views must be recreated against the new storage. + + foreach (var view in _views) + { + Logger.Debug?.Print(LogClass.Gpu, $" Recreating view {Info.Width}x{Info.Height} {Info.FormatInfo.Format}."); + view.ScaleFactor = scale; + + TextureCreateInfo viewCreateInfo = TextureCache.GetCreateInfo(view.Info, _context.Capabilities, scale); + ITexture newView = HostTexture.CreateView(viewCreateInfo, view.FirstLayer - FirstLayer, view.FirstLevel - FirstLevel); + + view.ReplaceStorage(newView); + view.ScaleMode = newScaleMode; + } + } + + if (ScaleMode != newScaleMode) + { + ScaleMode = newScaleMode; + + foreach (var view in _views) + { + view.ScaleMode = newScaleMode; + } + } + } + + /// + /// Checks if the memory for this texture was modified, and returns true if it was. + /// The modified flags are optionally consumed as a result. + /// + /// True to consume the dirty flags and reprotect, false to leave them as is + /// True if the texture was modified, false otherwise. + public bool CheckModified(bool consume) + { + return Group.CheckDirty(this, consume); + } + + /// + /// Discards all data for this texture. + /// This clears all dirty flags and pending copies from other textures. + /// It should be used if the texture data will be fully overwritten by the next use. + /// + public void DiscardData() + { + Group.DiscardData(this); + + _dirty = false; + } + + /// + /// Synchronizes guest and host memory. + /// This will overwrite the texture data with the texture data on the guest memory, if a CPU + /// modification is detected. + /// Be aware that this can cause texture data written by the GPU to be lost, this is just a + /// one way copy (from CPU owned to GPU owned memory). + /// + public void SynchronizeMemory() + { + if (Target == Target.TextureBuffer) + { + return; + } + + if (!_dirty) + { + return; + } + + _dirty = false; + + if (_hasData) + { + Group.SynchronizeMemory(this); + } + else + { + Group.CheckDirty(this, true); + SynchronizeFull(); + } + } + + /// + /// Signal that this texture is dirty, indicating that the texture group must be checked. + /// + public void SignalGroupDirty() + { + _dirty = true; + } + + /// + /// Signal that the modified state is dirty, indicating that the texture group should be notified when it changes. + /// + public void SignalModifiedDirty() + { + _modifiedStale = true; + } + + /// + /// Fully synchronizes guest and host memory. + /// This will replace the entire texture with the data present in guest memory. + /// + public void SynchronizeFull() + { + ReadOnlySpan data = _physicalMemory.GetSpan(Range); + + // If the host does not support ASTC compression, we need to do the decompression. + // The decompression is slow, so we want to avoid it as much as possible. + // This does a byte-by-byte check and skips the update if the data is equal in this case. + // This improves the speed on applications that overwrites ASTC data without changing anything. + if (Info.FormatInfo.Format.IsAstc() && !_context.Capabilities.SupportsAstcCompression) + { + if (_updateCount < ByteComparisonSwitchThreshold) + { + _updateCount++; + } + else + { + bool dataMatches = _currentData != null && data.SequenceEqual(_currentData); + if (dataMatches) + { + return; + } + + _currentData = data.ToArray(); + } + } + + IMemoryOwner result = ConvertToHostCompatibleFormat(data); + + if (ScaleFactor != 1f && AllowScaledSetData()) + { + // If needed, create a texture to load from 1x scale. + ITexture texture = _setHostTexture = GetScaledHostTexture(1f, false, _setHostTexture); + + texture.SetData(result); + + texture.CopyTo(HostTexture, new Extents2D(0, 0, texture.Width, texture.Height), new Extents2D(0, 0, HostTexture.Width, HostTexture.Height), true); + } + else + { + HostTexture.SetData(result); + } + + _hasData = true; + } + + /// + /// Uploads new texture data to the host GPU. + /// + /// New data + public void SetData(IMemoryOwner data) + { + BlacklistScale(); + + Group.CheckDirty(this, true); + + AlwaysFlushOnOverlap = true; + + HostTexture.SetData(data); + + _hasData = true; + } + + /// + /// Uploads new texture data to the host GPU for a specific layer/level. + /// + /// New data + /// Target layer + /// Target level + public void SetData(IMemoryOwner data, int layer, int level) + { + BlacklistScale(); + + HostTexture.SetData(data, layer, level); + + _currentData = null; + + _hasData = true; + } + + /// + /// Uploads new texture data to the host GPU for a specific layer/level and 2D sub-region. + /// + /// New data + /// Target layer + /// Target level + /// Target sub-region of the texture to update + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) + { + BlacklistScale(); + + HostTexture.SetData(data, layer, level, region); + + _currentData = null; + + _hasData = true; + } + + /// + /// Converts texture data to a format and layout that is supported by the host GPU. + /// + /// Data to be converted + /// Mip level to convert + /// True to convert a single slice + /// Converted data + public IMemoryOwner ConvertToHostCompatibleFormat(ReadOnlySpan data, int level = 0, bool single = false) + { + int width = Info.Width; + int height = Info.Height; + + int depth = _depth; + int layers = single ? 1 : _layers; + int levels = single ? 1 : (Info.Levels - level); + + width = Math.Max(width >> level, 1); + height = Math.Max(height >> level, 1); + depth = Math.Max(depth >> level, 1); + + int sliceDepth = single ? 1 : depth; + + IMemoryOwner linear; + + if (Info.IsLinear) + { + linear = LayoutConverter.ConvertLinearStridedToLinear( + width, + height, + Info.FormatInfo.BlockWidth, + Info.FormatInfo.BlockHeight, + Info.Stride, + Info.Stride, + Info.FormatInfo.BytesPerPixel, + data); + } + else + { + linear = LayoutConverter.ConvertBlockLinearToLinear( + width, + height, + depth, + sliceDepth, + levels, + layers, + Info.FormatInfo.BlockWidth, + Info.FormatInfo.BlockHeight, + Info.FormatInfo.BytesPerPixel, + Info.GobBlocksInY, + Info.GobBlocksInZ, + Info.GobBlocksInTileX, + _sizeInfo, + data); + } + + IMemoryOwner result = linear; + + // Handle compressed cases not supported by the host: + // - ASTC is usually not supported on desktop cards. + // - BC4/BC5 is not supported on 3D textures. + if (!_context.Capabilities.SupportsAstcCompression && Format.IsAstc()) + { + using (result) + { + if (!AstcDecoder.TryDecodeToRgba8P( + result.Memory, + Info.FormatInfo.BlockWidth, + Info.FormatInfo.BlockHeight, + width, + height, + sliceDepth, + levels, + layers, + out MemoryOwner decoded)) + { + string texInfo = $"{Info.Target} {Info.FormatInfo.Format} {Info.Width}x{Info.Height}x{Info.DepthOrLayers} levels {Info.Levels}"; + + Logger.Debug?.Print(LogClass.Gpu, $"Invalid ASTC texture at 0x{Info.GpuAddress:X} ({texInfo})."); + } + + if (GraphicsConfig.EnableTextureRecompression) + { + using (decoded) + { + return BCnEncoder.EncodeBC7(decoded.Memory, width, height, sliceDepth, levels, layers); + } + } + + return decoded; + } + } + else if (!_context.Capabilities.SupportsEtc2Compression && Format.IsEtc2()) + { + switch (Format) + { + case Format.Etc2RgbaSrgb: + case Format.Etc2RgbaUnorm: + using (result) + { + return ETC2Decoder.DecodeRgba(result.Memory.Span, width, height, sliceDepth, levels, layers); + } + case Format.Etc2RgbPtaSrgb: + case Format.Etc2RgbPtaUnorm: + using (result) + { + return ETC2Decoder.DecodePta(result.Memory.Span, width, height, sliceDepth, levels, layers); + } + case Format.Etc2RgbSrgb: + case Format.Etc2RgbUnorm: + using (result) + { + return ETC2Decoder.DecodeRgb(result.Memory.Span, width, height, sliceDepth, levels, layers); + } + } + } + else if (!TextureCompatibility.HostSupportsBcFormat(Format, Target, _context.Capabilities)) + { + switch (Format) + { + case Format.Bc1RgbaSrgb: + case Format.Bc1RgbaUnorm: + using (result) + { + return BCnDecoder.DecodeBC1(result.Memory.Span, width, height, sliceDepth, levels, layers); + } + case Format.Bc2Srgb: + case Format.Bc2Unorm: + using (result) + { + return BCnDecoder.DecodeBC2(result.Memory.Span, width, height, sliceDepth, levels, layers); + } + case Format.Bc3Srgb: + case Format.Bc3Unorm: + using (result) + { + return BCnDecoder.DecodeBC3(result.Memory.Span, width, height, sliceDepth, levels, layers); + } + case Format.Bc4Snorm: + case Format.Bc4Unorm: + using (result) + { + return BCnDecoder.DecodeBC4(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm); + } + case Format.Bc5Snorm: + case Format.Bc5Unorm: + using (result) + { + return BCnDecoder.DecodeBC5(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm); + } + case Format.Bc6HSfloat: + case Format.Bc6HUfloat: + using (result) + { + return BCnDecoder.DecodeBC6(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat); + } + case Format.Bc7Srgb: + case Format.Bc7Unorm: + using (result) + { + return BCnDecoder.DecodeBC7(result.Memory.Span, width, height, sliceDepth, levels, layers); + } + } + } + else if (!_context.Capabilities.SupportsR4G4Format && Format == Format.R4G4Unorm) + { + using (result) + { + var converted = PixelConverter.ConvertR4G4ToR4G4B4A4(result.Memory.Span, width); + + if (_context.Capabilities.SupportsR4G4B4A4Format) + { + return converted; + } + else + { + using (converted) + { + return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(converted.Memory.Span, width); + } + } + } + } + else if (Format == Format.R4G4B4A4Unorm) + { + if (!_context.Capabilities.SupportsR4G4B4A4Format) + { + using (result) + { + return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width); + } + } + } + else if (!_context.Capabilities.Supports5BitComponentFormat && Format.Is16BitPacked()) + { + switch (Format) + { + case Format.B5G6R5Unorm: + case Format.R5G6B5Unorm: + using (result) + { + return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Memory.Span, width); + } + case Format.B5G5R5A1Unorm: + case Format.R5G5B5X1Unorm: + case Format.R5G5B5A1Unorm: + using (result) + { + return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Memory.Span, width, Format == Format.R5G5B5X1Unorm); + } + case Format.A1B5G5R5Unorm: + using (result) + { + return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Memory.Span, width); + } + case Format.R4G4B4A4Unorm: + using (result) + { + return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width); + } + } + } + + return result; + } + + /// + /// Converts texture data from a format and layout that is supported by the host GPU, back into the intended format on the guest GPU. + /// + /// Optional output span to convert into + /// Data to be converted + /// Mip level to convert + /// True to convert a single slice + /// Converted data + public ReadOnlySpan ConvertFromHostCompatibleFormat(Span output, ReadOnlySpan data, int level = 0, bool single = false) + { + if (Target != Target.TextureBuffer) + { + int width = Info.Width; + int height = Info.Height; + + int depth = _depth; + int layers = single ? 1 : _layers; + int levels = single ? 1 : (Info.Levels - level); + + width = Math.Max(width >> level, 1); + height = Math.Max(height >> level, 1); + depth = Math.Max(depth >> level, 1); + + if (Info.IsLinear) + { + data = LayoutConverter.ConvertLinearToLinearStrided( + output, + Info.Width, + Info.Height, + Info.FormatInfo.BlockWidth, + Info.FormatInfo.BlockHeight, + Info.Stride, + Info.FormatInfo.BytesPerPixel, + data); + } + else + { + data = LayoutConverter.ConvertLinearToBlockLinear( + output, + width, + height, + depth, + single ? 1 : depth, + levels, + layers, + Info.FormatInfo.BlockWidth, + Info.FormatInfo.BlockHeight, + Info.FormatInfo.BytesPerPixel, + Info.GobBlocksInY, + Info.GobBlocksInZ, + Info.GobBlocksInTileX, + _sizeInfo, + data); + } + } + + return data; + } + + /// + /// Flushes the texture data. + /// This causes the texture data to be written back to guest memory. + /// If the texture was written by the GPU, this includes all modification made by the GPU + /// up to this point. + /// Be aware that this is an expensive operation, avoid calling it unless strictly needed. + /// This may cause data corruption if the memory is already being used for something else on the CPU side. + /// + /// Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either. + /// True if data was flushed, false otherwise + public bool FlushModified(bool tracked = true) + { + return TextureCompatibility.CanTextureFlush(Info, _context.Capabilities) && Group.FlushModified(this, tracked); + } + + /// + /// Flushes the texture data. + /// This causes the texture data to be written back to guest memory. + /// If the texture was written by the GPU, this includes all modification made by the GPU + /// up to this point. + /// Be aware that this is an expensive operation, avoid calling it unless strictly needed. + /// This may cause data corruption if the memory is already being used for something else on the CPU side. + /// + /// Whether or not the flush triggers write tracking. If it doesn't, the texture will not be blacklisted for scaling either. + public void Flush(bool tracked) + { + if (TextureCompatibility.CanTextureFlush(Info, _context.Capabilities)) + { + FlushTextureDataToGuest(tracked); + } + } + + /// + /// Gets a host texture to use for flushing the texture, at 1x resolution. + /// If the HostTexture is already at 1x resolution, it is returned directly. + /// + /// The host texture to flush + public ITexture GetFlushTexture() + { + ITexture texture = HostTexture; + if (ScaleFactor != 1f) + { + // If needed, create a texture to flush back to host at 1x scale. + texture = _flushHostTexture = GetScaledHostTexture(1f, true, _flushHostTexture); + } + + return texture; + } + + /// + /// Gets data from the host GPU, and flushes it all to guest memory. + /// + /// + /// This method should be used to retrieve data that was modified by the host GPU. + /// This is not cheap, avoid doing that unless strictly needed. + /// When possible, the data is written directly into guest memory, rather than copied. + /// + /// True if writing the texture data is tracked, false otherwise + /// The specific host texture to flush. Defaults to this texture + public void FlushTextureDataToGuest(bool tracked, ITexture texture = null) + { + using WritableRegion region = _physicalMemory.GetWritableRegion(Range, tracked); + + GetTextureDataFromGpu(region.Memory.Span, tracked, texture); + } + + /// + /// Gets data from the host GPU. + /// + /// + /// This method should be used to retrieve data that was modified by the host GPU. + /// This is not cheap, avoid doing that unless strictly needed. + /// + /// An output span to place the texture data into + /// True if the texture should be blacklisted, false otherwise + /// The specific host texture to flush. Defaults to this texture + private void GetTextureDataFromGpu(Span output, bool blacklist, ITexture texture = null) + { + PinnedSpan data; + + if (texture != null) + { + data = texture.GetData(); + } + else + { + if (blacklist) + { + BlacklistScale(); + data = HostTexture.GetData(); + } + else if (ScaleFactor != 1f) + { + float scale = ScaleFactor; + SetScale(1f); + data = HostTexture.GetData(); + SetScale(scale); + } + else + { + data = HostTexture.GetData(); + } + } + + ConvertFromHostCompatibleFormat(output, data.Get()); + + data.Dispose(); + } + + /// + /// Gets data from the host GPU for a single slice. + /// + /// + /// This method should be used to retrieve data that was modified by the host GPU. + /// This is not cheap, avoid doing that unless strictly needed. + /// + /// An output span to place the texture data into. If empty, one is generated + /// The layer of the texture to flush + /// The level of the texture to flush + /// True if the texture should be blacklisted, false otherwise + /// The specific host texture to flush. Defaults to this texture + public void GetTextureDataSliceFromGpu(Span output, int layer, int level, bool blacklist, ITexture texture = null) + { + PinnedSpan data; + + if (texture != null) + { + data = texture.GetData(layer, level); + } + else + { + if (blacklist) + { + BlacklistScale(); + data = HostTexture.GetData(layer, level); + } + else if (ScaleFactor != 1f) + { + float scale = ScaleFactor; + SetScale(1f); + data = HostTexture.GetData(layer, level); + SetScale(scale); + } + else + { + data = HostTexture.GetData(layer, level); + } + } + + ConvertFromHostCompatibleFormat(output, data.Get(), level, true); + + data.Dispose(); + } + + /// + /// This performs a strict comparison, used to check if this texture is equal to the one supplied. + /// + /// Texture information to compare against + /// Comparison flags + /// A value indicating how well this texture matches the given info + public TextureMatchQuality IsExactMatch(TextureInfo info, TextureSearchFlags flags) + { + bool forSampler = (flags & TextureSearchFlags.ForSampler) != 0; + + TextureMatchQuality matchQuality = TextureCompatibility.FormatMatches(Info, info, forSampler, (flags & TextureSearchFlags.DepthAlias) != 0); + + if (matchQuality == TextureMatchQuality.NoMatch) + { + return matchQuality; + } + + if (!TextureCompatibility.LayoutMatches(Info, info)) + { + return TextureMatchQuality.NoMatch; + } + + if (!TextureCompatibility.SizeMatches(Info, info, forSampler)) + { + return TextureMatchQuality.NoMatch; + } + + if ((flags & TextureSearchFlags.ForSampler) != 0) + { + if (!TextureCompatibility.SamplerParamsMatches(Info, info)) + { + return TextureMatchQuality.NoMatch; + } + } + + if ((flags & TextureSearchFlags.ForCopy) != 0) + { + bool msTargetCompatible = Info.Target == Target.Texture2DMultisample && info.Target == Target.Texture2D; + + if (!msTargetCompatible && !TextureCompatibility.TargetAndSamplesCompatible(Info, info)) + { + return TextureMatchQuality.NoMatch; + } + } + else if (!TextureCompatibility.TargetAndSamplesCompatible(Info, info)) + { + return TextureMatchQuality.NoMatch; + } + + return Info.Levels == info.Levels ? matchQuality : TextureMatchQuality.NoMatch; + } + + /// + /// Check if it's possible to create a view, with the given parameters, from this texture. + /// + /// Texture view information + /// Texture view physical memory ranges + /// Indicates if the texture sizes must be exactly equal, or width is allowed to differ + /// Layer size on the given texture + /// Host GPU capabilities + /// Texture view initial layer on this texture + /// Texture view first mipmap level on this texture + /// Texture search flags + /// The level of compatiblilty a view with the given parameters created from this texture has + public TextureViewCompatibility IsViewCompatible( + TextureInfo info, + MultiRange range, + bool exactSize, + int layerSize, + Capabilities caps, + out int firstLayer, + out int firstLevel, + TextureSearchFlags flags = TextureSearchFlags.None) + { + TextureViewCompatibility result = TextureViewCompatibility.Full; + + result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps, flags)); + if (result != TextureViewCompatibility.Incompatible) + { + result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info, ref caps)); + + bool bothMs = Info.Target.IsMultisample() && info.Target.IsMultisample(); + if (bothMs && (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY)) + { + result = TextureViewCompatibility.Incompatible; + } + + if (result == TextureViewCompatibility.Full && Info.FormatInfo.Format != info.FormatInfo.Format && !_context.Capabilities.SupportsMismatchingViewFormat) + { + // AMD and Intel have a bug where the view format is always ignored; + // they use the parent format instead. + // Create a copy dependency to avoid this issue. + + result = TextureViewCompatibility.CopyOnly; + } + } + + firstLayer = 0; + firstLevel = 0; + + if (result == TextureViewCompatibility.Incompatible) + { + return TextureViewCompatibility.Incompatible; + } + + int offset = Range.FindOffset(range); + + if (offset < 0 || !_sizeInfo.FindView(offset, out firstLayer, out firstLevel)) + { + return TextureViewCompatibility.LayoutIncompatible; + } + + if (!TextureCompatibility.ViewLayoutCompatible(Info, info, firstLevel)) + { + return TextureViewCompatibility.LayoutIncompatible; + } + + if (info.GetSlices() > 1 && LayerSize != layerSize) + { + return TextureViewCompatibility.LayoutIncompatible; + } + + result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSizeMatches(Info, info, exactSize, firstLevel)); + result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewSubImagesInBounds(Info, info, firstLayer, firstLevel)); + + return result; + } + + /// + /// Gets a texture of the specified target type from this texture. + /// This can be used to get an array texture from a non-array texture and vice-versa. + /// If this texture and the requested targets are equal, then this texture Host texture is returned directly. + /// + /// The desired target type + /// A view of this texture with the requested target, or null if the target is invalid for this texture + public ITexture GetTargetTexture(Target target) + { + if (target == Target) + { + return HostTexture; + } + + if (_arrayViewTexture == null && IsSameDimensionsTarget(target)) + { + FormatInfo formatInfo = TextureCompatibility.ToHostCompatibleFormat(Info, _context.Capabilities); + + TextureCreateInfo createInfo = new( + Info.Width, + Info.Height, + target == Target.CubemapArray ? 6 : 1, + Info.Levels, + Info.Samples, + formatInfo.BlockWidth, + formatInfo.BlockHeight, + formatInfo.BytesPerPixel, + formatInfo.Format, + Info.DepthStencilMode, + target, + Info.SwizzleR, + Info.SwizzleG, + Info.SwizzleB, + Info.SwizzleA); + + ITexture viewTexture = HostTexture.CreateView(createInfo, 0, 0); + + _arrayViewTexture = viewTexture; + _arrayViewTarget = target; + + return viewTexture; + } + else if (_arrayViewTarget == target) + { + return _arrayViewTexture; + } + + return null; + } + + /// + /// Determine if this texture can have anisotropic filtering forced. + /// Filtered textures that we might want to force anisotropy on should have a lot of mip levels. + /// + /// True if anisotropic filtering can be forced, false otherwise + private bool CanTextureForceAnisotropy() + { + if (!(Target == Target.Texture2D || Target == Target.Texture2DArray)) + { + return false; + } + + int maxSize = Math.Max(Info.Width, Info.Height); + int maxLevels = BitOperations.Log2((uint)maxSize) + 1; + + return Info.Levels >= Math.Min(MinLevelsForForceAnisotropy, maxLevels); + } + + /// + /// Check if this texture and the specified target have the same number of dimensions. + /// For the purposes of this comparison, 2D and 2D Multisample textures are not considered to have + /// the same number of dimensions. Same for Cubemap and 3D textures. + /// + /// The target to compare with + /// True if both targets have the same number of dimensions, false otherwise + private bool IsSameDimensionsTarget(Target target) + { + switch (Info.Target) + { + case Target.Texture1D: + case Target.Texture1DArray: + return target == Target.Texture1D || target == Target.Texture1DArray; + case Target.Texture2D: + case Target.Texture2DArray: + return target == Target.Texture2D || target == Target.Texture2DArray; + case Target.Cubemap: + case Target.CubemapArray: + return target == Target.Cubemap || target == Target.CubemapArray; + case Target.Texture2DMultisample: + case Target.Texture2DMultisampleArray: + return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray; + case Target.Texture3D: + return target == Target.Texture3D; + default: + return false; + } + } + + /// + /// Replaces view texture information. + /// This should only be used for child textures with a parent. + /// + /// The parent texture + /// The new view texture information + /// The new host texture + /// The first layer of the view + /// The first level of the view + public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture, int firstLayer, int firstLevel) + { + IncrementReferenceCount(); + parent._viewStorage.SynchronizeMemory(); + + // If this texture has views, they must be given to the new parent. + if (_views.Count > 0) + { + Texture[] viewCopy = _views.ToArray(); + + foreach (Texture view in viewCopy) + { + TextureCreateInfo createInfo = TextureCache.GetCreateInfo(view.Info, _context.Capabilities, ScaleFactor); + + ITexture newView = parent.HostTexture.CreateView(createInfo, view.FirstLayer + firstLayer, view.FirstLevel + firstLevel); + + view.ReplaceView(parent, view.Info, newView, view.FirstLayer + firstLayer, view.FirstLevel + firstLevel); + } + } + + ReplaceStorage(hostTexture); + + if (_viewStorage != this) + { + _viewStorage.RemoveView(this); + } + + FirstLayer = parent.FirstLayer + firstLayer; + FirstLevel = parent.FirstLevel + firstLevel; + parent._viewStorage.AddView(this); + + SetInfo(info); + DecrementReferenceCount(); + } + + /// + /// Sets the internal texture information structure. + /// + /// The new texture information + private void SetInfo(TextureInfo info) + { + Info = info; + Target = info.Target; + Width = info.Width; + Height = info.Height; + CanForceAnisotropy = CanTextureForceAnisotropy(); + + _depth = info.GetDepth(); + _layers = info.GetLayers(); + } + + /// + /// Signals that the texture has been modified. + /// + public void SignalModified() + { + _scaledSetScore = Math.Max(0, _scaledSetScore - 1); + + if (_modifiedStale || Group.HasCopyDependencies) + { + _modifiedStale = false; + Group.SignalModified(this); + } + + _physicalMemory.TextureCache.Lift(this); + } + + /// + /// Signals that a texture has been bound, or has been unbound. + /// During this time, lazy copies will not clear the dirty flag. + /// + /// True if the texture has been bound, false if it has been unbound + public void SignalModifying(bool bound) + { + if (bound) + { + _scaledSetScore = Math.Max(0, _scaledSetScore - 1); + } + + if (_modifiedStale || Group.HasCopyDependencies || Group.HasFlushBuffer) + { + _modifiedStale = false; + Group.SignalModifying(this, bound); + } + + _physicalMemory.TextureCache.Lift(this); + + if (bound) + { + IncrementReferenceCount(); + } + else + { + DecrementReferenceCount(); + } + } + + /// + /// Replaces the host texture, while disposing of the old one if needed. + /// + /// The new host texture + private void ReplaceStorage(ITexture hostTexture) + { + DisposeTextures(); + + HostTexture = hostTexture; + } + + /// + /// Determine if any of this texture's data overlaps with another. + /// + /// The texture to check against + /// The view compatibility of the two textures + /// True if any slice of the textures overlap, false otherwise + public bool DataOverlaps(Texture texture, TextureViewCompatibility compatibility) + { + if (compatibility == TextureViewCompatibility.LayoutIncompatible && Info.GobBlocksInZ > 1 && Info.GobBlocksInZ == texture.Info.GobBlocksInZ) + { + // Allow overlapping slices of layout compatible 3D textures with matching GobBlocksInZ, as they are interleaved. + return false; + } + + if (texture._sizeInfo.AllOffsets.Length == 1 && _sizeInfo.AllOffsets.Length == 1) + { + return Range.OverlapsWith(texture.Range); + } + + MultiRange otherRange = texture.Range; + + IEnumerable regions = _sizeInfo.AllRegions().Select((region) => Range.Slice((ulong)region.Offset, (ulong)region.Size)); + IEnumerable otherRegions = texture._sizeInfo.AllRegions().Select((region) => otherRange.Slice((ulong)region.Offset, (ulong)region.Size)); + + foreach (MultiRange region in regions) + { + foreach (MultiRange otherRegion in otherRegions) + { + if (region.OverlapsWith(otherRegion)) + { + return true; + } + } + } + + return false; + } + + /// + /// Increments the texture reference count. + /// + public void IncrementReferenceCount() + { + _referenceCount++; + } + + /// + /// Increments the reference count and records the given texture pool and ID as a pool owner. + /// + /// The texture pool this texture has been added to + /// The ID of the reference to this texture in the pool + /// GPU VA of the pool reference + public void IncrementReferenceCount(TexturePool pool, int id, ulong gpuVa) + { + HadPoolOwner = true; + + lock (_poolOwners) + { + _poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id, GpuAddress = gpuVa }); + } + + _referenceCount++; + + if (ShortCacheEntry != null) + { + _physicalMemory.TextureCache.RemoveShortCache(this); + } + } + + /// + /// Indicates that the texture has one reference left, and will delete on reference decrement. + /// + /// True if there is one reference remaining, false otherwise + public bool HasOneReference() + { + return _referenceCount == 1; + } + + /// + /// Decrements the texture reference count. + /// When the reference count hits zero, the texture may be deleted and can't be used anymore. + /// + /// True if the texture is now referenceless, false otherwise + public bool DecrementReferenceCount() + { + int newRefCount = --_referenceCount; + + if (newRefCount == 0) + { + if (_viewStorage != this) + { + _viewStorage.RemoveView(this); + } + + _physicalMemory.TextureCache.RemoveTextureFromCache(this); + } + + Debug.Assert(newRefCount >= 0); + + DeleteIfNotUsed(); + + return newRefCount <= 0; + } + + /// + /// Decrements the texture reference count, also removing an associated pool owner reference. + /// When the reference count hits zero, the texture may be deleted and can't be used anymore. + /// + /// The texture pool this texture is being removed from + /// The ID of the reference to this texture in the pool + /// True if the texture is now referenceless, false otherwise + public bool DecrementReferenceCount(TexturePool pool, int id = -1) + { + lock (_poolOwners) + { + int references = _poolOwners.RemoveAll(entry => entry.Pool == pool && entry.ID == id || id == -1); + + if (references == 0) + { + // This reference has already been removed. + return _referenceCount <= 0; + } + + Debug.Assert(references == 1); + } + + return DecrementReferenceCount(); + } + + /// + /// Forcibly remove this texture from all pools that reference it. + /// + /// Indicates if the removal is being done from another thread. + public void RemoveFromPools(bool deferred) + { + lock (_poolOwners) + { + foreach (var owner in _poolOwners) + { + owner.Pool.ForceRemove(this, owner.ID, deferred); + } + + _poolOwners.Clear(); + } + + if (ShortCacheEntry != null && !ShortCacheEntry.IsAutoDelete && _context.IsGpuThread()) + { + // If this is called from another thread (unmapped), the short cache will + // have to remove this texture on a future tick. + + _physicalMemory.TextureCache.RemoveShortCache(this); + } + + InvalidatedSequence++; + } + + /// + /// Queue updating texture mappings on the pool. Happens from another thread. + /// + public void UpdatePoolMappings() + { + ChangedMapping = true; + + lock (_poolOwners) + { + ulong address = 0; + + foreach (var owner in _poolOwners) + { + if (address == 0 || address == owner.GpuAddress) + { + address = owner.GpuAddress; + + owner.Pool.QueueUpdateMapping(this, owner.ID); + } + else + { + // If there is a different GPU VA mapping, prefer the first and delete the others. + owner.Pool.ForceRemove(this, owner.ID, true); + } + } + + _poolOwners.Clear(); + } + + InvalidatedSequence++; + } + + /// + /// Delete the texture if it is not used anymore. + /// The texture is considered unused when the reference count is zero, + /// and it has no child views. + /// + private void DeleteIfNotUsed() + { + // We can delete the texture as long it is not being used + // in any cache (the reference count is 0 in this case), and + // also all views that may be created from this texture were + // already deleted (views count is 0). + if (_referenceCount == 0 && _views.Count == 0) + { + Dispose(); + } + } + + /// + /// Performs texture disposal, deleting the texture. + /// + private void DisposeTextures() + { + InvalidatedSequence++; + + _currentData = null; + HostTexture.Release(); + + _arrayViewTexture?.Release(); + _arrayViewTexture = null; + + _flushHostTexture?.Release(); + _flushHostTexture = null; + + _setHostTexture?.Release(); + _setHostTexture = null; + } + + /// + /// Called when the memory for this texture has been unmapped. + /// Calls are from non-gpu threads. + /// + /// The range of memory being unmapped + public void Unmapped(MultiRange unmapRange) + { + ChangedMapping = true; + + if (Group.Storage == this) + { + Group.Unmapped(); + Group.ClearModified(unmapRange); + } + } + + /// + /// Performs texture disposal, deleting the texture. + /// + public void Dispose() + { + DisposeTextures(); + + if (Group.Storage == this) + { + Group.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs new file mode 100644 index 00000000..31abc21e --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs @@ -0,0 +1,104 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture binding information. + /// This is used for textures that needs to be accessed from shaders. + /// + readonly struct TextureBindingInfo + { + /// + /// Shader sampler target type. + /// + public Target Target { get; } + + /// + /// For images, indicates the format specified on the shader. + /// + public Format Format { get; } + + /// + /// Shader texture host set index. + /// + public int Set { get; } + + /// + /// Shader texture host binding point. + /// + public int Binding { get; } + + /// + /// For array of textures, this indicates the length of the array. A value of one indicates it is not an array. + /// + public int ArrayLength { get; } + + /// + /// Constant buffer slot with the texture handle. + /// + public int CbufSlot { get; } + + /// + /// Index of the texture handle on the constant buffer at slot . + /// + public int Handle { get; } + + /// + /// Flags from the texture descriptor that indicate how the texture is used. + /// + public TextureUsageFlags Flags { get; } + + /// + /// Indicates that the binding is for a sampler. + /// + public bool IsSamplerOnly { get; } + + /// + /// Constructs the texture binding information structure. + /// + /// The shader sampler target type + /// Format of the image as declared on the shader + /// Shader texture host set index + /// The shader texture binding point + /// For array of textures, this indicates the length of the array. A value of one indicates it is not an array + /// Constant buffer slot where the texture handle is located + /// The shader texture handle (read index into the texture constant buffer) + /// The texture's usage flags, indicating how it is used in the shader + public TextureBindingInfo(Target target, Format format, int set, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags) + { + Target = target; + Format = format; + Set = set; + Binding = binding; + ArrayLength = arrayLength; + CbufSlot = cbufSlot; + Handle = handle; + Flags = flags; + } + + /// + /// Constructs the texture binding information structure. + /// + /// The shader sampler target type + /// Shader texture host set index + /// The shader texture binding point + /// For array of textures, this indicates the length of the array. A value of one indicates it is not an array + /// Constant buffer slot where the texture handle is located + /// The shader texture handle (read index into the texture constant buffer) + /// The texture's usage flags, indicating how it is used in the shader + /// Indicates that the binding is for a sampler + public TextureBindingInfo( + Target target, + int set, + int binding, + int arrayLength, + int cbufSlot, + int handle, + TextureUsageFlags flags, + bool isSamplerOnly) : this(target, 0, set, binding, arrayLength, cbufSlot, handle, flags) + { + IsSamplerOnly = isSamplerOnly; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs new file mode 100644 index 00000000..8b9243b1 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs @@ -0,0 +1,1183 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Shader; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture bindings array cache. + /// + class TextureBindingsArrayCache + { + /// + /// Minimum timestamp delta until texture array can be removed from the cache. + /// + private const int MinDeltaForRemoval = 20000; + + private readonly GpuContext _context; + private readonly GpuChannel _channel; + + /// + /// Array cache entry key. + /// + private readonly struct CacheEntryFromPoolKey : IEquatable + { + /// + /// Whether the entry is for an image. + /// + public readonly bool IsImage; + + /// + /// Whether the entry is for a sampler. + /// + public readonly bool IsSampler; + + /// + /// Texture or image target type. + /// + public readonly Target Target; + + /// + /// Number of entries of the array. + /// + public readonly int ArrayLength; + + private readonly TexturePool _texturePool; + private readonly SamplerPool _samplerPool; + + /// + /// Creates a new array cache entry. + /// + /// Whether the entry is for an image + /// Binding information for the array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + public CacheEntryFromPoolKey(bool isImage, TextureBindingInfo bindingInfo, TexturePool texturePool, SamplerPool samplerPool) + { + IsImage = isImage; + IsSampler = bindingInfo.IsSamplerOnly; + Target = bindingInfo.Target; + ArrayLength = bindingInfo.ArrayLength; + + _texturePool = texturePool; + _samplerPool = samplerPool; + } + + /// + /// Checks if the pool matches the cached pool. + /// + /// Texture or sampler pool instance + /// True if the pool matches, false otherwise + public bool MatchesPool(IPool pool) + { + return _texturePool == pool || _samplerPool == pool; + } + + /// + /// Checks if the texture and sampler pools matches the cached pools. + /// + /// Texture pool instance + /// Sampler pool instance + /// True if the pools match, false otherwise + private bool MatchesPools(TexturePool texturePool, SamplerPool samplerPool) + { + return _texturePool == texturePool && _samplerPool == samplerPool; + } + + public bool Equals(CacheEntryFromPoolKey other) + { + return IsImage == other.IsImage && + IsSampler == other.IsSampler && + Target == other.Target && + ArrayLength == other.ArrayLength && + MatchesPools(other._texturePool, other._samplerPool); + } + + public override bool Equals(object obj) + { + return obj is CacheEntryFromBufferKey other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(_texturePool, _samplerPool, IsSampler); + } + } + + /// + /// Array cache entry key. + /// + private readonly struct CacheEntryFromBufferKey : IEquatable + { + /// + /// Whether the entry is for an image. + /// + public readonly bool IsImage; + + /// + /// Texture or image target type. + /// + public readonly Target Target; + + /// + /// Word offset of the first handle on the constant buffer. + /// + public readonly int HandleIndex; + + /// + /// Number of entries of the array. + /// + public readonly int ArrayLength; + + private readonly TexturePool _texturePool; + private readonly SamplerPool _samplerPool; + + private readonly BufferBounds _textureBufferBounds; + + /// + /// Creates a new array cache entry. + /// + /// Whether the entry is for an image + /// Binding information for the array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + /// Constant buffer bounds with the texture handles + public CacheEntryFromBufferKey( + bool isImage, + TextureBindingInfo bindingInfo, + TexturePool texturePool, + SamplerPool samplerPool, + ref BufferBounds textureBufferBounds) + { + IsImage = isImage; + Target = bindingInfo.Target; + HandleIndex = bindingInfo.Handle; + ArrayLength = bindingInfo.ArrayLength; + + _texturePool = texturePool; + _samplerPool = samplerPool; + + _textureBufferBounds = textureBufferBounds; + } + + /// + /// Checks if the texture and sampler pools matches the cached pools. + /// + /// Texture pool instance + /// Sampler pool instance + /// True if the pools match, false otherwise + private bool MatchesPools(TexturePool texturePool, SamplerPool samplerPool) + { + return _texturePool == texturePool && _samplerPool == samplerPool; + } + + /// + /// Checks if the cached constant buffer address and size matches. + /// + /// New buffer address and size + /// True if the address and size matches, false otherwise + private bool MatchesBufferBounds(BufferBounds textureBufferBounds) + { + return _textureBufferBounds.Equals(textureBufferBounds); + } + + public bool Equals(CacheEntryFromBufferKey other) + { + return IsImage == other.IsImage && + Target == other.Target && + HandleIndex == other.HandleIndex && + ArrayLength == other.ArrayLength && + MatchesPools(other._texturePool, other._samplerPool) && + MatchesBufferBounds(other._textureBufferBounds); + } + + public override bool Equals(object obj) + { + return obj is CacheEntryFromBufferKey other && Equals(other); + } + + public override int GetHashCode() + { + return _textureBufferBounds.Range.GetHashCode(); + } + } + + /// + /// Array cache entry from pool. + /// + private class CacheEntry + { + /// + /// All cached textures, along with their invalidated sequence number as value. + /// + public readonly Dictionary Textures; + + /// + /// Backend texture array if the entry is for a texture, otherwise null. + /// + public readonly ITextureArray TextureArray; + + /// + /// Backend image array if the entry is for an image, otherwise null. + /// + public readonly IImageArray ImageArray; + + /// + /// Texture pool where the array textures are located. + /// + protected readonly TexturePool TexturePool; + + /// + /// Sampler pool where the array samplers are located. + /// + protected readonly SamplerPool SamplerPool; + + private int _texturePoolSequence; + private int _samplerPoolSequence; + + /// + /// Creates a new array cache entry. + /// + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + private CacheEntry(TexturePool texturePool, SamplerPool samplerPool) + { + Textures = new Dictionary(); + + TexturePool = texturePool; + SamplerPool = samplerPool; + } + + /// + /// Creates a new array cache entry. + /// + /// Backend texture array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + public CacheEntry(ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : this(texturePool, samplerPool) + { + TextureArray = array; + } + + /// + /// Creates a new array cache entry. + /// + /// Backend image array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + public CacheEntry(IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : this(texturePool, samplerPool) + { + ImageArray = array; + } + + /// + /// Synchronizes memory for all textures in the array. + /// + /// Indicates if the texture may be modified by the access + /// Indicates if the texture should be blacklisted for scaling + public void SynchronizeMemory(bool isStore, bool blacklistScale) + { + foreach (Texture texture in Textures.Keys) + { + texture.SynchronizeMemory(); + + if (isStore) + { + texture.SignalModified(); + } + + if (blacklistScale && texture.ScaleMode != TextureScaleMode.Blacklisted) + { + // Scaling textures used on arrays is currently not supported. + + texture.BlacklistScale(); + } + } + } + + /// + /// Clears all cached texture instances. + /// + public virtual void Reset() + { + Textures.Clear(); + } + + /// + /// Checks if any texture has been deleted since the last call to this method. + /// + /// True if one or more textures have been deleted, false otherwise + public bool ValidateTextures() + { + foreach ((Texture texture, int invalidatedSequence) in Textures) + { + if (texture.InvalidatedSequence != invalidatedSequence) + { + return false; + } + } + + return true; + } + + /// + /// Checks if the cached texture or sampler pool has been modified since the last call to this method. + /// + /// True if any used entries of the pool might have been modified, false otherwise + public bool TexturePoolModified() + { + return TexturePool.WasModified(ref _texturePoolSequence); + } + + /// + /// Checks if the cached texture or sampler pool has been modified since the last call to this method. + /// + /// True if any used entries of the pool might have been modified, false otherwise + public bool SamplerPoolModified() + { + return SamplerPool != null && SamplerPool.WasModified(ref _samplerPoolSequence); + } + } + + /// + /// Array cache entry from constant buffer. + /// + private class CacheEntryFromBuffer : CacheEntry + { + /// + /// Key for this entry on the cache. + /// + public readonly CacheEntryFromBufferKey Key; + + /// + /// Linked list node used on the texture bindings array cache. + /// + public LinkedListNode CacheNode; + + /// + /// Timestamp set on the last use of the array by the cache. + /// + public int CacheTimestamp; + + /// + /// All pool texture IDs along with their textures. + /// + public readonly Dictionary TextureIds; + + /// + /// All pool sampler IDs along with their samplers. + /// + public readonly Dictionary SamplerIds; + + private int[] _cachedTextureBuffer; + private int[] _cachedSamplerBuffer; + + private int _lastSequenceNumber; + + /// + /// Creates a new array cache entry. + /// + /// Key for this entry on the cache + /// Backend texture array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + public CacheEntryFromBuffer(ref CacheEntryFromBufferKey key, ITextureArray array, TexturePool texturePool, SamplerPool samplerPool) : base(array, texturePool, samplerPool) + { + Key = key; + _lastSequenceNumber = -1; + TextureIds = new Dictionary(); + SamplerIds = new Dictionary(); + } + + /// + /// Creates a new array cache entry. + /// + /// Key for this entry on the cache + /// Backend image array + /// Texture pool where the array textures are located + /// Sampler pool where the array samplers are located + public CacheEntryFromBuffer(ref CacheEntryFromBufferKey key, IImageArray array, TexturePool texturePool, SamplerPool samplerPool) : base(array, texturePool, samplerPool) + { + Key = key; + _lastSequenceNumber = -1; + TextureIds = new Dictionary(); + SamplerIds = new Dictionary(); + } + + /// + public override void Reset() + { + base.Reset(); + TextureIds.Clear(); + SamplerIds.Clear(); + } + + /// + /// Updates the cached constant buffer data. + /// + /// Constant buffer data with the texture handles (and sampler handles, if they are combined) + /// Constant buffer data with the sampler handles + /// Whether and comes from different buffers + public void UpdateData(ReadOnlySpan cachedTextureBuffer, ReadOnlySpan cachedSamplerBuffer, bool separateSamplerBuffer) + { + _cachedTextureBuffer = cachedTextureBuffer.ToArray(); + _cachedSamplerBuffer = separateSamplerBuffer ? cachedSamplerBuffer.ToArray() : _cachedTextureBuffer; + } + + /// + /// Checks if the sequence number matches the one used on the last call to this method. + /// + /// Current sequence number + /// True if the sequence numbers match, false otherwise + public bool MatchesSequenceNumber(int currentSequenceNumber) + { + if (_lastSequenceNumber == currentSequenceNumber) + { + return true; + } + + _lastSequenceNumber = currentSequenceNumber; + + return false; + } + + /// + /// Checks if the buffer data matches the cached data. + /// + /// New texture buffer data + /// New sampler buffer data + /// Whether and comes from different buffers + /// Word offset of the sampler constant buffer handle that is used + /// True if the data matches, false otherwise + public bool MatchesBufferData( + ReadOnlySpan cachedTextureBuffer, + ReadOnlySpan cachedSamplerBuffer, + bool separateSamplerBuffer, + int samplerWordOffset) + { + if (_cachedTextureBuffer != null && cachedTextureBuffer.Length > _cachedTextureBuffer.Length) + { + cachedTextureBuffer = cachedTextureBuffer[.._cachedTextureBuffer.Length]; + } + + if (!_cachedTextureBuffer.AsSpan().SequenceEqual(cachedTextureBuffer)) + { + return false; + } + + if (separateSamplerBuffer) + { + if (_cachedSamplerBuffer == null || + _cachedSamplerBuffer.Length <= samplerWordOffset || + cachedSamplerBuffer.Length <= samplerWordOffset) + { + return false; + } + + int oldValue = _cachedSamplerBuffer[samplerWordOffset]; + int newValue = cachedSamplerBuffer[samplerWordOffset]; + + return oldValue == newValue; + } + + return true; + } + + /// + /// Checks if the cached texture or sampler pool has been modified since the last call to this method. + /// + /// True if any used entries of the pools might have been modified, false otherwise + public bool PoolsModified() + { + bool texturePoolModified = TexturePoolModified(); + bool samplerPoolModified = SamplerPoolModified(); + + // If both pools were not modified since the last check, we have nothing else to check. + if (!texturePoolModified && !samplerPoolModified) + { + return false; + } + + // If the pools were modified, let's check if any of the entries we care about changed. + + // Check if any of our cached textures changed on the pool. + foreach ((int textureId, (Texture texture, TextureDescriptor descriptor)) in TextureIds) + { + if (TexturePool.GetCachedItem(textureId) != texture || + (texture == null && TexturePool.IsValidId(textureId) && !TexturePool.GetDescriptorRef(textureId).Equals(descriptor))) + { + return true; + } + } + + // Check if any of our cached samplers changed on the pool. + if (SamplerPool != null) + { + foreach ((int samplerId, (Sampler sampler, SamplerDescriptor descriptor)) in SamplerIds) + { + if (SamplerPool.GetCachedItem(samplerId) != sampler || + (sampler == null && SamplerPool.IsValidId(samplerId) && !SamplerPool.GetDescriptorRef(samplerId).Equals(descriptor))) + { + return true; + } + } + } + + return false; + } + } + + private readonly Dictionary _cacheFromBuffer; + private readonly Dictionary _cacheFromPool; + private readonly LinkedList _lruCache; + + private int _currentTimestamp; + + /// + /// Creates a new instance of the texture bindings array cache. + /// + /// GPU context + /// GPU channel + public TextureBindingsArrayCache(GpuContext context, GpuChannel channel) + { + _context = context; + _channel = channel; + _cacheFromBuffer = new Dictionary(); + _cacheFromPool = new Dictionary(); + _lruCache = new LinkedList(); + } + + /// + /// Updates a texture array bindings and textures. + /// + /// Texture pool + /// Sampler pool + /// Shader stage where the array is used + /// Shader stage index where the array is used + /// Texture constant buffer index + /// Sampler handles source + /// Array binding information + public void UpdateTextureArray( + TexturePool texturePool, + SamplerPool samplerPool, + ShaderStage stage, + int stageIndex, + int textureBufferIndex, + SamplerIndex samplerIndex, + in TextureBindingInfo bindingInfo) + { + Update(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage: false, samplerIndex, bindingInfo); + } + + /// + /// Updates a image array bindings and textures. + /// + /// Texture pool + /// Shader stage where the array is used + /// Shader stage index where the array is used + /// Texture constant buffer index + /// Array binding information + public void UpdateImageArray(TexturePool texturePool, ShaderStage stage, int stageIndex, int textureBufferIndex, in TextureBindingInfo bindingInfo) + { + Update(texturePool, null, stage, stageIndex, textureBufferIndex, isImage: true, SamplerIndex.ViaHeaderIndex, bindingInfo); + } + + /// + /// Updates a texture or image array bindings and textures. + /// + /// Texture pool + /// Sampler pool + /// Shader stage where the array is used + /// Shader stage index where the array is used + /// Texture constant buffer index + /// Whether the array is a image or texture array + /// Sampler handles source + /// Array binding information + private void Update( + TexturePool texturePool, + SamplerPool samplerPool, + ShaderStage stage, + int stageIndex, + int textureBufferIndex, + bool isImage, + SamplerIndex samplerIndex, + in TextureBindingInfo bindingInfo) + { + if (IsDirectHandleType(bindingInfo.Handle)) + { + UpdateFromPool(texturePool, samplerPool, stage, isImage, bindingInfo); + } + else + { + UpdateFromBuffer(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage, samplerIndex, bindingInfo); + } + } + + /// + /// Updates a texture or image array bindings and textures from a texture or sampler pool. + /// + /// Texture pool + /// Sampler pool + /// Shader stage where the array is used + /// Whether the array is a image or texture array + /// Array binding information + private void UpdateFromPool(TexturePool texturePool, SamplerPool samplerPool, ShaderStage stage, bool isImage, in TextureBindingInfo bindingInfo) + { + CacheEntry entry = GetOrAddEntry(texturePool, samplerPool, bindingInfo, isImage, out bool isNewEntry); + + bool isSampler = bindingInfo.IsSamplerOnly; + bool poolModified = isSampler ? entry.SamplerPoolModified() : entry.TexturePoolModified(); + bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + bool resScaleUnsupported = bindingInfo.Flags.HasFlag(TextureUsageFlags.ResScaleUnsupported); + + if (!poolModified && !isNewEntry && entry.ValidateTextures()) + { + entry.SynchronizeMemory(isStore, resScaleUnsupported); + + if (isImage) + { + SetImageArray(stage, bindingInfo, entry.ImageArray); + } + else + { + SetTextureArray(stage, bindingInfo, entry.TextureArray); + } + + return; + } + + if (!isNewEntry) + { + entry.Reset(); + } + + int length = (isSampler ? samplerPool.MaximumId : texturePool.MaximumId) + 1; + length = Math.Min(length, bindingInfo.ArrayLength); + + Format[] formats = isImage ? new Format[bindingInfo.ArrayLength] : null; + ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength]; + ITexture[] textures = new ITexture[bindingInfo.ArrayLength]; + + for (int index = 0; index < length; index++) + { + Texture texture = null; + Sampler sampler = null; + + if (isSampler) + { + sampler = samplerPool?.Get(index); + } + else + { + ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(index, out texture); + + if (texture != null) + { + entry.Textures[texture] = texture.InvalidatedSequence; + + if (isStore) + { + texture.SignalModified(); + } + + if (resScaleUnsupported && texture.ScaleMode != TextureScaleMode.Blacklisted) + { + // Scaling textures used on arrays is currently not supported. + + texture.BlacklistScale(); + } + } + } + + ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); + ISampler hostSampler = sampler?.GetHostSampler(texture); + + Format format = bindingInfo.Format; + + if (hostTexture != null && texture.Target == Target.TextureBuffer) + { + // Ensure that the buffer texture is using the correct buffer as storage. + // Buffers are frequently re-created to accommodate larger data, so we need to re-bind + // to ensure we're not using a old buffer that was already deleted. + if (isImage) + { + if (format == 0 && texture != null) + { + format = texture.Format; + } + + _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format); + } + else + { + _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format); + } + } + else if (isImage) + { + if (format == 0 && texture != null) + { + format = texture.Format; + } + + formats[index] = format; + textures[index] = hostTexture; + } + else + { + samplers[index] = hostSampler; + textures[index] = hostTexture; + } + } + + if (isImage) + { + entry.ImageArray.SetFormats(0, formats); + entry.ImageArray.SetImages(0, textures); + + SetImageArray(stage, bindingInfo, entry.ImageArray); + } + else + { + entry.TextureArray.SetSamplers(0, samplers); + entry.TextureArray.SetTextures(0, textures); + + SetTextureArray(stage, bindingInfo, entry.TextureArray); + } + } + + /// + /// Updates a texture or image array bindings and textures from constant buffer handles. + /// + /// Texture pool + /// Sampler pool + /// Shader stage where the array is used + /// Shader stage index where the array is used + /// Texture constant buffer index + /// Whether the array is a image or texture array + /// Sampler handles source + /// Array binding information + private void UpdateFromBuffer( + TexturePool texturePool, + SamplerPool samplerPool, + ShaderStage stage, + int stageIndex, + int textureBufferIndex, + bool isImage, + SamplerIndex samplerIndex, + in TextureBindingInfo bindingInfo) + { + (textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex); + + bool separateSamplerBuffer = textureBufferIndex != samplerBufferIndex; + bool isCompute = stage == ShaderStage.Compute; + + ref BufferBounds textureBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex); + ref BufferBounds samplerBufferBounds = ref _channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex); + + CacheEntryFromBuffer entry = GetOrAddEntry( + texturePool, + samplerPool, + bindingInfo, + isImage, + ref textureBufferBounds, + out bool isNewEntry); + + bool poolsModified = entry.PoolsModified(); + bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + bool resScaleUnsupported = bindingInfo.Flags.HasFlag(TextureUsageFlags.ResScaleUnsupported); + + ReadOnlySpan cachedTextureBuffer; + ReadOnlySpan cachedSamplerBuffer; + + if (!poolsModified && !isNewEntry && entry.ValidateTextures()) + { + if (entry.MatchesSequenceNumber(_context.SequenceNumber)) + { + entry.SynchronizeMemory(isStore, resScaleUnsupported); + + if (isImage) + { + SetImageArray(stage, bindingInfo, entry.ImageArray); + } + else + { + SetTextureArray(stage, bindingInfo, entry.TextureArray); + } + + return; + } + + cachedTextureBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range)); + + if (separateSamplerBuffer) + { + cachedSamplerBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range)); + } + else + { + cachedSamplerBuffer = cachedTextureBuffer; + } + + (_, int samplerWordOffset, _) = TextureHandle.UnpackOffsets(bindingInfo.Handle); + + if (entry.MatchesBufferData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer, samplerWordOffset)) + { + entry.SynchronizeMemory(isStore, resScaleUnsupported); + + if (isImage) + { + SetImageArray(stage, bindingInfo, entry.ImageArray); + } + else + { + SetTextureArray(stage, bindingInfo, entry.TextureArray); + } + + return; + } + } + else + { + cachedTextureBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(textureBufferBounds.Range)); + + if (separateSamplerBuffer) + { + cachedSamplerBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(samplerBufferBounds.Range)); + } + else + { + cachedSamplerBuffer = cachedTextureBuffer; + } + } + + if (!isNewEntry) + { + entry.Reset(); + } + + entry.UpdateData(cachedTextureBuffer, cachedSamplerBuffer, separateSamplerBuffer); + + Format[] formats = isImage ? new Format[bindingInfo.ArrayLength] : null; + ISampler[] samplers = isImage ? null : new ISampler[bindingInfo.ArrayLength]; + ITexture[] textures = new ITexture[bindingInfo.ArrayLength]; + + for (int index = 0; index < bindingInfo.ArrayLength; index++) + { + int handleIndex = bindingInfo.Handle + index * (Constants.TextureHandleSizeInBytes / sizeof(int)); + int packedId = TextureHandle.ReadPackedId(handleIndex, cachedTextureBuffer, cachedSamplerBuffer); + int textureId = TextureHandle.UnpackTextureId(packedId); + int samplerId; + + if (samplerIndex == SamplerIndex.ViaHeaderIndex) + { + samplerId = textureId; + } + else + { + samplerId = TextureHandle.UnpackSamplerId(packedId); + } + + ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, out Texture texture); + + if (texture != null) + { + entry.Textures[texture] = texture.InvalidatedSequence; + + if (isStore) + { + texture.SignalModified(); + } + + if (resScaleUnsupported && texture.ScaleMode != TextureScaleMode.Blacklisted) + { + // Scaling textures used on arrays is currently not supported. + + texture.BlacklistScale(); + } + } + + entry.TextureIds[textureId] = (texture, descriptor); + + ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); + ISampler hostSampler = null; + + if (!isImage && bindingInfo.Target != Target.TextureBuffer) + { + Sampler sampler = samplerPool?.Get(samplerId); + + entry.SamplerIds[samplerId] = (sampler, samplerPool?.GetDescriptorRef(samplerId) ?? default); + + hostSampler = sampler?.GetHostSampler(texture); + } + + Format format = bindingInfo.Format; + + if (hostTexture != null && texture.Target == Target.TextureBuffer) + { + // Ensure that the buffer texture is using the correct buffer as storage. + // Buffers are frequently re-created to accommodate larger data, so we need to re-bind + // to ensure we're not using a old buffer that was already deleted. + if (isImage) + { + if (format == 0 && texture != null) + { + format = texture.Format; + } + + _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format); + } + else + { + _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format); + } + } + else if (isImage) + { + if (format == 0 && texture != null) + { + format = texture.Format; + } + + formats[index] = format; + textures[index] = hostTexture; + } + else + { + samplers[index] = hostSampler; + textures[index] = hostTexture; + } + } + + if (isImage) + { + entry.ImageArray.SetFormats(0, formats); + entry.ImageArray.SetImages(0, textures); + + SetImageArray(stage, bindingInfo, entry.ImageArray); + } + else + { + entry.TextureArray.SetSamplers(0, samplers); + entry.TextureArray.SetTextures(0, textures); + + SetTextureArray(stage, bindingInfo, entry.TextureArray); + } + } + + /// + /// Updates a texture array binding on the host. + /// + /// Shader stage where the array is used + /// Array binding information + /// Texture array + private void SetTextureArray(ShaderStage stage, in TextureBindingInfo bindingInfo, ITextureArray array) + { + if (bindingInfo.Set >= _context.Capabilities.ExtraSetBaseIndex && _context.Capabilities.MaximumExtraSets != 0) + { + _context.Renderer.Pipeline.SetTextureArraySeparate(stage, bindingInfo.Set, array); + } + else + { + _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, array); + } + } + + /// + /// Updates a image array binding on the host. + /// + /// Shader stage where the array is used + /// Array binding information + /// Image array + private void SetImageArray(ShaderStage stage, in TextureBindingInfo bindingInfo, IImageArray array) + { + if (bindingInfo.Set >= _context.Capabilities.ExtraSetBaseIndex && _context.Capabilities.MaximumExtraSets != 0) + { + _context.Renderer.Pipeline.SetImageArraySeparate(stage, bindingInfo.Set, array); + } + else + { + _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, array); + } + } + + /// + /// Gets a cached texture entry from pool, or creates a new one if not found. + /// + /// Texture pool + /// Sampler pool + /// Array binding information + /// Whether the array is a image or texture array + /// Whether a new entry was created, or an existing one was returned + /// Cache entry + private CacheEntry GetOrAddEntry( + TexturePool texturePool, + SamplerPool samplerPool, + in TextureBindingInfo bindingInfo, + bool isImage, + out bool isNew) + { + CacheEntryFromPoolKey key = new CacheEntryFromPoolKey(isImage, bindingInfo, texturePool, samplerPool); + + isNew = !_cacheFromPool.TryGetValue(key, out CacheEntry entry); + + if (isNew) + { + int arrayLength = bindingInfo.ArrayLength; + + if (isImage) + { + IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); + + _cacheFromPool.Add(key, entry = new CacheEntry(array, texturePool, samplerPool)); + } + else + { + ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); + + _cacheFromPool.Add(key, entry = new CacheEntry(array, texturePool, samplerPool)); + } + } + + return entry; + } + + /// + /// Gets a cached texture entry from constant buffer, or creates a new one if not found. + /// + /// Texture pool + /// Sampler pool + /// Array binding information + /// Whether the array is a image or texture array + /// Constant buffer bounds with the texture handles + /// Whether a new entry was created, or an existing one was returned + /// Cache entry + private CacheEntryFromBuffer GetOrAddEntry( + TexturePool texturePool, + SamplerPool samplerPool, + in TextureBindingInfo bindingInfo, + bool isImage, + ref BufferBounds textureBufferBounds, + out bool isNew) + { + CacheEntryFromBufferKey key = new CacheEntryFromBufferKey( + isImage, + bindingInfo, + texturePool, + samplerPool, + ref textureBufferBounds); + + isNew = !_cacheFromBuffer.TryGetValue(key, out CacheEntryFromBuffer entry); + + if (isNew) + { + int arrayLength = bindingInfo.ArrayLength; + + if (isImage) + { + IImageArray array = _context.Renderer.CreateImageArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); + + _cacheFromBuffer.Add(key, entry = new CacheEntryFromBuffer(ref key, array, texturePool, samplerPool)); + } + else + { + ITextureArray array = _context.Renderer.CreateTextureArray(arrayLength, bindingInfo.Target == Target.TextureBuffer); + + _cacheFromBuffer.Add(key, entry = new CacheEntryFromBuffer(ref key, array, texturePool, samplerPool)); + } + } + + if (entry.CacheNode != null) + { + _lruCache.Remove(entry.CacheNode); + _lruCache.AddLast(entry.CacheNode); + } + else + { + entry.CacheNode = _lruCache.AddLast(entry); + } + + entry.CacheTimestamp = ++_currentTimestamp; + + RemoveLeastUsedEntries(); + + return entry; + } + + /// + /// Remove entries from the cache that have not been used for some time. + /// + private void RemoveLeastUsedEntries() + { + LinkedListNode nextNode = _lruCache.First; + + while (nextNode != null && _currentTimestamp - nextNode.Value.CacheTimestamp >= MinDeltaForRemoval) + { + LinkedListNode toRemove = nextNode; + nextNode = nextNode.Next; + _cacheFromBuffer.Remove(toRemove.Value.Key); + _lruCache.Remove(toRemove); + + if (toRemove.Value.Key.IsImage) + { + toRemove.Value.ImageArray.Dispose(); + } + else + { + toRemove.Value.TextureArray.Dispose(); + } + } + } + + /// + /// Removes all cached texture arrays matching the specified texture pool. + /// + /// Texture pool + public void RemoveAllWithPool(IPool pool) + { + List keysToRemove = null; + + foreach ((CacheEntryFromPoolKey key, CacheEntry entry) in _cacheFromPool) + { + if (key.MatchesPool(pool)) + { + (keysToRemove ??= new()).Add(key); + + if (key.IsImage) + { + entry.ImageArray.Dispose(); + } + else + { + entry.TextureArray.Dispose(); + } + } + } + + if (keysToRemove != null) + { + foreach (CacheEntryFromPoolKey key in keysToRemove) + { + _cacheFromPool.Remove(key); + } + } + } + + /// + /// Checks if a handle indicates the binding should have all its textures sourced directly from a pool. + /// + /// Handle to check + /// True if the handle represents direct pool access, false otherwise + private static bool IsDirectHandleType(int handle) + { + (_, _, TextureHandleType type) = TextureHandle.UnpackOffsets(handle); + + return type == TextureHandleType.Direct; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs new file mode 100644 index 00000000..9f1f60d9 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -0,0 +1,873 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.Shader; +using Ryujinx.Graphics.Shader; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture bindings manager. + /// + class TextureBindingsManager + { + private const int InitialTextureStateSize = 32; + private const int InitialImageStateSize = 8; + + private readonly GpuContext _context; + + private readonly bool _isCompute; + + private ulong _texturePoolGpuVa; + private int _texturePoolMaximumId; + private TexturePool _texturePool; + private ulong _samplerPoolGpuVa; + private int _samplerPoolMaximumId; + private SamplerIndex _samplerIndex; + private SamplerPool _samplerPool; + + private readonly GpuChannel _channel; + private readonly TexturePoolCache _texturePoolCache; + private readonly SamplerPoolCache _samplerPoolCache; + + private readonly TextureBindingsArrayCache _bindingsArrayCache; + + private TexturePool _cachedTexturePool; + private SamplerPool _cachedSamplerPool; + + private TextureBindingInfo[][] _textureBindings; + private TextureBindingInfo[][] _imageBindings; + + private struct TextureState + { + public ITexture Texture; + public ISampler Sampler; + + public int TextureHandle; + public int SamplerHandle; + public Format ImageFormat; + public int InvalidatedSequence; + public Texture CachedTexture; + public Sampler CachedSampler; + } + + private TextureState[] _textureState; + private TextureState[] _imageState; + + private int[] _textureCounts; + + private int _texturePoolSequence; + private int _samplerPoolSequence; + + private int _textureBufferIndex; + + private int _lastFragmentTotal; + + /// + /// Constructs a new instance of the texture bindings manager. + /// + /// The GPU context that the texture bindings manager belongs to + /// The GPU channel that the texture bindings manager belongs to + /// Cache of texture array bindings + /// Texture pools cache used to get texture pools from + /// Sampler pools cache used to get sampler pools from + /// True if the bindings manager is used for the compute engine + public TextureBindingsManager( + GpuContext context, + GpuChannel channel, + TextureBindingsArrayCache bindingsArrayCache, + TexturePoolCache texturePoolCache, + SamplerPoolCache samplerPoolCache, + bool isCompute) + { + _context = context; + _channel = channel; + _texturePoolCache = texturePoolCache; + _samplerPoolCache = samplerPoolCache; + + _isCompute = isCompute; + + _bindingsArrayCache = bindingsArrayCache; + + int stages = isCompute ? 1 : Constants.ShaderStages; + + _textureBindings = new TextureBindingInfo[stages][]; + _imageBindings = new TextureBindingInfo[stages][]; + + _textureState = new TextureState[InitialTextureStateSize]; + _imageState = new TextureState[InitialImageStateSize]; + + for (int stage = 0; stage < stages; stage++) + { + _textureBindings[stage] = Array.Empty(); + _imageBindings[stage] = Array.Empty(); + } + + _textureCounts = Array.Empty(); + } + + /// + /// Sets the texture and image bindings. + /// + /// Bindings for the active shader + public void SetBindings(CachedShaderBindings bindings) + { + _textureBindings = bindings.TextureBindings; + _imageBindings = bindings.ImageBindings; + + _textureCounts = bindings.TextureCounts; + + SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding); + } + + /// + /// Sets the max binding indexes for textures and images. + /// + /// The maximum texture binding + /// The maximum image binding + public void SetMaxBindings(int maxTextureBinding, int maxImageBinding) + { + if (maxTextureBinding >= _textureState.Length) + { + Array.Resize(ref _textureState, maxTextureBinding + 1); + } + + if (maxImageBinding >= _imageState.Length) + { + Array.Resize(ref _imageState, maxImageBinding + 1); + } + } + + /// + /// Sets the textures constant buffer index. + /// The constant buffer specified holds the texture handles. + /// + /// Constant buffer index + public void SetTextureBufferIndex(int index) + { + _textureBufferIndex = index; + } + + /// + /// Sets the current texture sampler pool to be used. + /// + /// Start GPU virtual address of the pool + /// Maximum ID of the pool (total count minus one) + /// Type of the sampler pool indexing used for bound samplers + public void SetSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex) + { + _samplerPoolGpuVa = gpuVa; + _samplerPoolMaximumId = maximumId; + _samplerIndex = samplerIndex; + _samplerPool = null; + } + + /// + /// Sets the current texture pool to be used. + /// + /// Start GPU virtual address of the pool + /// Maximum ID of the pool (total count minus one) + public void SetTexturePool(ulong gpuVa, int maximumId) + { + _texturePoolGpuVa = gpuVa; + _texturePoolMaximumId = maximumId; + _texturePool = null; + } + + /// + /// Gets a texture and a sampler from their respective pools from a texture ID and a sampler ID. + /// + /// ID of the texture + /// ID of the sampler + public (Texture, Sampler) GetTextureAndSampler(int textureId, int samplerId) + { + (TexturePool texturePool, SamplerPool samplerPool) = GetPools(); + + return (texturePool.Get(textureId), samplerPool.Get(samplerId)); + } + + /// + /// Updates the texture scale for a given texture or image. + /// + /// Start GPU virtual address of the pool + /// The related texture usage flags + /// The texture/image binding index + /// The active shader stage + /// True if the given texture has become blacklisted, indicating that its host texture may have changed. + private bool UpdateScale(Texture texture, TextureUsageFlags usageFlags, int index, ShaderStage stage) + { + float result = 1f; + bool changed = false; + + if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 && texture != null) + { + if ((usageFlags & TextureUsageFlags.ResScaleUnsupported) != 0) + { + changed = texture.ScaleMode != TextureScaleMode.Blacklisted; + texture.BlacklistScale(); + } + else + { + switch (stage) + { + case ShaderStage.Fragment: + float scale = texture.ScaleFactor; + + if (scale != 1) + { + Texture activeTarget = _channel.TextureManager.GetAnyRenderTarget(); + + if (activeTarget != null && (activeTarget.Info.Width / (float)texture.Info.Width) == (activeTarget.Info.Height / (float)texture.Info.Height)) + { + // If the texture's size is a multiple of the sampler size, enable interpolation using gl_FragCoord. (helps "invent" new integer values between scaled pixels) + result = -scale; + break; + } + } + + result = scale; + break; + + case ShaderStage.Vertex: + int fragmentIndex = (int)ShaderStage.Fragment - 1; + index += _textureBindings[fragmentIndex].Length + _imageBindings[fragmentIndex].Length; + + result = texture.ScaleFactor; + break; + + case ShaderStage.Compute: + result = texture.ScaleFactor; + break; + } + } + } + + _context.SupportBufferUpdater.UpdateRenderScale(index, result); + + return changed; + } + + /// + /// Determines if the vertex stage requires a scale value. + /// + private bool VertexRequiresScale() + { + for (int i = 0; i < _textureBindings[0].Length; i++) + { + if ((_textureBindings[0][i].Flags & TextureUsageFlags.NeedsScaleValue) != 0) + { + return true; + } + } + + for (int i = 0; i < _imageBindings[0].Length; i++) + { + if ((_imageBindings[0][i].Flags & TextureUsageFlags.NeedsScaleValue) != 0) + { + return true; + } + } + + return false; + } + + /// + /// Uploads texture and image scales to the backend when they are used. + /// + private void CommitRenderScale() + { + // Stage 0 total: Compute or Vertex. + int total = _textureBindings[0].Length + _imageBindings[0].Length; + + int fragmentIndex = (int)ShaderStage.Fragment - 1; + int fragmentTotal = _isCompute ? 0 : (_textureBindings[fragmentIndex].Length + _imageBindings[fragmentIndex].Length); + + if (total != 0 && fragmentTotal != _lastFragmentTotal && VertexRequiresScale()) + { + // Must update scales in the support buffer if: + // - Vertex stage has bindings that require scale. + // - Fragment stage binding count has been updated since last render scale update. + + if (!_isCompute) + { + total += fragmentTotal; // Add the fragment bindings to the total. + } + + _lastFragmentTotal = fragmentTotal; + + _context.SupportBufferUpdater.UpdateRenderScaleFragmentCount(total, fragmentTotal); + } + } + + /// + /// Ensures that the bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + /// Specialization state for the bound shader + /// True if all bound textures match the current shader specialiation state, false otherwise + public bool CommitBindings(ShaderSpecializationState specState) + { + (TexturePool texturePool, SamplerPool samplerPool) = GetPools(); + + // Check if the texture pool has been modified since bindings were last committed. + // If it wasn't, then it's possible to avoid looking up textures again when the handle remains the same. + if (_cachedTexturePool != texturePool || _cachedSamplerPool != samplerPool) + { + Rebind(); + + _cachedTexturePool = texturePool; + _cachedSamplerPool = samplerPool; + } + + bool poolModified = false; + + if (texturePool != null) + { + int texturePoolSequence = texturePool.CheckModified(); + + if (_texturePoolSequence != texturePoolSequence) + { + poolModified = true; + _texturePoolSequence = texturePoolSequence; + } + } + + if (samplerPool != null) + { + int samplerPoolSequence = samplerPool.CheckModified(); + + if (_samplerPoolSequence != samplerPoolSequence) + { + poolModified = true; + _samplerPoolSequence = samplerPoolSequence; + } + } + + bool specStateMatches = true; + + if (_isCompute) + { + specStateMatches &= CommitTextureBindings(texturePool, samplerPool, ShaderStage.Compute, 0, poolModified, specState); + specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState); + } + else + { + for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++) + { + int stageIndex = (int)stage - 1; + + specStateMatches &= CommitTextureBindings(texturePool, samplerPool, stage, stageIndex, poolModified, specState); + specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState); + } + } + + CommitRenderScale(); + + return specStateMatches; + } + + /// + /// Fetch the constant buffers used for a texture to cache. + /// + /// Stage index of the constant buffer + /// The currently cached texture buffer index + /// The currently cached sampler buffer index + /// The currently cached texture buffer data + /// The currently cached sampler buffer data + /// The new texture buffer index + /// The new sampler buffer index + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateCachedBuffer( + int stageIndex, + scoped ref int cachedTextureBufferIndex, + scoped ref int cachedSamplerBufferIndex, + scoped ref ReadOnlySpan cachedTextureBuffer, + scoped ref ReadOnlySpan cachedSamplerBuffer, + int textureBufferIndex, + int samplerBufferIndex) + { + if (textureBufferIndex != cachedTextureBufferIndex) + { + ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex); + + cachedTextureBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(bounds.Range)); + cachedTextureBufferIndex = textureBufferIndex; + + if (samplerBufferIndex == textureBufferIndex) + { + cachedSamplerBuffer = cachedTextureBuffer; + cachedSamplerBufferIndex = samplerBufferIndex; + } + } + + if (samplerBufferIndex != cachedSamplerBufferIndex) + { + ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex); + + cachedSamplerBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(bounds.Range)); + cachedSamplerBufferIndex = samplerBufferIndex; + } + } + + /// + /// Ensures that the texture bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + /// The current texture pool + /// The current sampler pool + /// The shader stage using the textures to be bound + /// The stage number of the specified shader stageTrue if either the texture or sampler pool was modified, false otherwise + /// Specialization state for the bound shader + /// True if all bound textures match the current shader specialiation state, false otherwise + private bool CommitTextureBindings( + TexturePool texturePool, + SamplerPool samplerPool, + ShaderStage stage, + int stageIndex, + bool poolModified, + ShaderSpecializationState specState) + { + int textureCount = _textureBindings[stageIndex].Length; + if (textureCount == 0) + { + return true; + } + + if (texturePool == null) + { + Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses textures, but texture pool was not set."); + return true; + } + + bool specStateMatches = true; + + int cachedTextureBufferIndex = -1; + int cachedSamplerBufferIndex = -1; + ReadOnlySpan cachedTextureBuffer = Span.Empty; + ReadOnlySpan cachedSamplerBuffer = Span.Empty; + + for (int index = 0; index < textureCount; index++) + { + TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index]; + TextureUsageFlags usageFlags = bindingInfo.Flags; + + if (bindingInfo.ArrayLength > 1) + { + _bindingsArrayCache.UpdateTextureArray(texturePool, samplerPool, stage, stageIndex, _textureBufferIndex, _samplerIndex, bindingInfo); + + continue; + } + + (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); + + UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex); + + int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer); + int textureId = TextureHandle.UnpackTextureId(packedId); + int samplerId; + + if (_samplerIndex == SamplerIndex.ViaHeaderIndex) + { + samplerId = textureId; + } + else + { + samplerId = TextureHandle.UnpackSamplerId(packedId); + } + + ref TextureState state = ref _textureState[bindingInfo.Binding]; + + if (!poolModified && + state.TextureHandle == textureId && + state.SamplerHandle == samplerId && + state.CachedTexture != null && + state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence && + state.CachedSampler?.IsDisposed != true) + { + // The texture is already bound. + state.CachedTexture.SynchronizeMemory(); + + if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 && + UpdateScale(state.CachedTexture, usageFlags, index, stage)) + { + ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target); + + state.Texture = hostTextureRebind; + + _context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTextureRebind, state.Sampler); + } + + continue; + } + + state.TextureHandle = textureId; + state.SamplerHandle = samplerId; + + ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, out Texture texture); + + specStateMatches &= specState.MatchesTexture(stage, index, descriptor); + + Sampler sampler = samplerPool?.Get(samplerId); + + ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); + ISampler hostSampler = sampler?.GetHostSampler(texture); + + if (hostTexture != null && texture.Target == Target.TextureBuffer) + { + // Ensure that the buffer texture is using the correct buffer as storage. + // Buffers are frequently re-created to accommodate larger data, so we need to re-bind + // to ensure we're not using a old buffer that was already deleted. + _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, bindingInfo.Format, false); + + // Cache is not used for buffer texture, it must always rebind. + state.CachedTexture = null; + } + else + { + bool textureOrSamplerChanged = state.Texture != hostTexture || state.Sampler != hostSampler; + + if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 && + UpdateScale(texture, usageFlags, index, stage)) + { + hostTexture = texture?.GetTargetTexture(bindingInfo.Target); + textureOrSamplerChanged = true; + } + + if (textureOrSamplerChanged) + { + state.Texture = hostTexture; + state.Sampler = hostSampler; + + _context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTexture, hostSampler); + } + + state.CachedTexture = texture; + state.CachedSampler = sampler; + state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0; + } + } + + return specStateMatches; + } + + /// + /// Ensures that the image bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + /// The current texture pool + /// The shader stage using the textures to be bound + /// The stage number of the specified shader stage + /// True if either the texture or sampler pool was modified, false otherwise + /// Specialization state for the bound shader + /// True if all bound images match the current shader specialiation state, false otherwise + private bool CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState) + { + int imageCount = _imageBindings[stageIndex].Length; + if (imageCount == 0) + { + return true; + } + + if (pool == null) + { + Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses images, but texture pool was not set."); + return true; + } + + // Scales for images appear after the texture ones. + int baseScaleIndex = _textureCounts[stageIndex]; + + int cachedTextureBufferIndex = -1; + int cachedSamplerBufferIndex = -1; + ReadOnlySpan cachedTextureBuffer = Span.Empty; + ReadOnlySpan cachedSamplerBuffer = Span.Empty; + + bool specStateMatches = true; + + for (int index = 0; index < imageCount; index++) + { + TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index]; + TextureUsageFlags usageFlags = bindingInfo.Flags; + + if (bindingInfo.ArrayLength > 1) + { + _bindingsArrayCache.UpdateImageArray(pool, stage, stageIndex, _textureBufferIndex, bindingInfo); + + continue; + } + + int scaleIndex = baseScaleIndex + index; + + (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex); + + UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex); + + int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer); + int textureId = TextureHandle.UnpackTextureId(packedId); + + ref TextureState state = ref _imageState[bindingInfo.Binding]; + + bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + + if (!poolModified && + state.TextureHandle == textureId && + state.CachedTexture != null && + state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence) + { + Texture cachedTexture = state.CachedTexture; + + // The texture is already bound. + cachedTexture.SynchronizeMemory(); + + if (isStore) + { + cachedTexture.SignalModified(); + } + + Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format; + + if (state.ImageFormat != format || + ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 && + UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage))) + { + ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target); + + state.Texture = hostTextureRebind; + state.ImageFormat = format; + + _context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTextureRebind, format); + } + + continue; + } + + state.TextureHandle = textureId; + + ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture); + + specStateMatches &= specState.MatchesImage(stage, index, descriptor); + + ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); + + if (hostTexture != null && texture.Target == Target.TextureBuffer) + { + // Ensure that the buffer texture is using the correct buffer as storage. + // Buffers are frequently re-created to accommodate larger data, so we need to re-bind + // to ensure we're not using a old buffer that was already deleted. + + Format format = bindingInfo.Format; + + if (format == 0 && texture != null) + { + format = texture.Format; + } + + _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, format, true); + + // Cache is not used for buffer texture, it must always rebind. + state.CachedTexture = null; + } + else + { + if (isStore) + { + texture?.SignalModified(); + } + + if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 && + UpdateScale(texture, usageFlags, scaleIndex, stage)) + { + hostTexture = texture?.GetTargetTexture(bindingInfo.Target); + } + + if (state.Texture != hostTexture) + { + state.Texture = hostTexture; + + Format format = bindingInfo.Format; + + if (format == 0 && texture != null) + { + format = texture.Format; + } + + state.ImageFormat = format; + + _context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTexture, format); + } + + state.CachedTexture = texture; + state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0; + } + } + + return specStateMatches; + } + + /// + /// Gets the texture descriptor for a given texture handle. + /// + /// GPU virtual address of the texture pool + /// Index of the constant buffer with texture handles + /// Maximum ID of the texture pool + /// The stage number where the texture is bound + /// The texture handle + /// The texture handle's constant buffer slot + /// The texture descriptor for the specified texture + public TextureDescriptor GetTextureDescriptor( + ulong poolGpuVa, + int bufferIndex, + int maximumId, + int stageIndex, + int handle, + int cbufSlot) + { + (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex); + + int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex); + int textureId = TextureHandle.UnpackTextureId(packedId); + + ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa); + + TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache); + + TextureDescriptor descriptor; + + if (texturePool.IsValidId(textureId)) + { + descriptor = texturePool.GetDescriptor(textureId); + } + else + { + // If the ID is not valid, we just return a default descriptor with the most common state. + // Since this is used for shader specialization, doing so might avoid the need for recompilations. + descriptor = new TextureDescriptor(); + descriptor.Word4 |= (uint)TextureTarget.Texture2D << 23; + descriptor.Word5 |= 1u << 31; // Coords normalized. + } + + return descriptor; + } + + /// + /// Reads a packed texture and sampler ID (basically, the real texture handle) + /// from the texture constant buffer. + /// + /// The number of the shader stage where the texture is bound + /// A word offset of the handle on the buffer (the "fake" shader handle) + /// Index of the constant buffer holding the texture handles + /// Index of the constant buffer holding the sampler handles + /// The packed texture and sampler ID (the real texture handle) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex) + { + (int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset); + + ulong textureBufferAddress = _isCompute + ? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex) + : _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex); + + int handle = textureBufferAddress != MemoryManager.PteUnmapped + ? _channel.MemoryManager.Physical.Read(textureBufferAddress + (uint)textureWordOffset * 4) + : 0; + + // The "wordOffset" (which is really the immediate value used on texture instructions on the shader) + // is a 13-bit value. However, in order to also support separate samplers and textures (which uses + // bindless textures on the shader), we extend it with another value on the higher 16 bits with + // another offset for the sampler. + // The shader translator has code to detect separate texture and sampler uses with a bindless texture, + // turn that into a regular texture access and produce those special handles with values on the higher 16 bits. + if (handleType != TextureHandleType.CombinedSampler) + { + int samplerHandle; + + if (handleType != TextureHandleType.SeparateConstantSamplerHandle) + { + ulong samplerBufferAddress = _isCompute + ? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex) + : _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex); + + samplerHandle = samplerBufferAddress != MemoryManager.PteUnmapped + ? _channel.MemoryManager.Physical.Read(samplerBufferAddress + (uint)samplerWordOffset * 4) + : 0; + } + else + { + samplerHandle = samplerWordOffset; + } + + if (handleType == TextureHandleType.SeparateSamplerId || + handleType == TextureHandleType.SeparateConstantSamplerHandle) + { + samplerHandle <<= 20; + } + + handle |= samplerHandle; + } + + return handle; + } + + /// + /// Gets the texture and sampler pool for the GPU virtual address that are currently set. + /// + /// The texture and sampler pools + private (TexturePool, SamplerPool) GetPools() + { + MemoryManager memoryManager = _channel.MemoryManager; + + TexturePool texturePool = _texturePool; + SamplerPool samplerPool = _samplerPool; + + if (texturePool == null) + { + ulong poolAddress = memoryManager.Translate(_texturePoolGpuVa); + + if (poolAddress != MemoryManager.PteUnmapped) + { + texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId, _bindingsArrayCache); + _texturePool = texturePool; + } + } + + if (samplerPool == null) + { + ulong poolAddress = memoryManager.Translate(_samplerPoolGpuVa); + + if (poolAddress != MemoryManager.PteUnmapped) + { + samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId, _bindingsArrayCache); + _samplerPool = samplerPool; + } + } + + return (texturePool, samplerPool); + } + + /// + /// Forces the texture and sampler pools to be re-loaded from the cache on next use. + /// + /// + /// This should be called if the memory mappings change, to ensure the correct pools are being used. + /// + public void ReloadPools() + { + _samplerPool = null; + _texturePool = null; + } + + /// + /// Force all bound textures and images to be rebound the next time CommitBindings is called. + /// + public void Rebind() + { + Array.Clear(_textureState); + Array.Clear(_imageState); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs new file mode 100644 index 00000000..5a3319b0 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs @@ -0,0 +1,1454 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Gpu.Engine.Twod; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Texture; +using Ryujinx.Memory.Range; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture cache. + /// + class TextureCache : IDisposable + { + private readonly struct OverlapInfo + { + public TextureViewCompatibility Compatibility { get; } + public int FirstLayer { get; } + public int FirstLevel { get; } + + public OverlapInfo(TextureViewCompatibility compatibility, int firstLayer, int firstLevel) + { + Compatibility = compatibility; + FirstLayer = firstLayer; + FirstLevel = firstLevel; + } + } + + private const int OverlapsBufferInitialCapacity = 10; + private const int OverlapsBufferMaxCapacity = 10000; + + private readonly GpuContext _context; + private readonly PhysicalMemory _physicalMemory; + + private readonly MultiRangeList _textures; + private readonly HashSet _partiallyMappedTextures; + + private readonly ReaderWriterLockSlim _texturesLock; + + private Texture[] _textureOverlaps; + private OverlapInfo[] _overlapInfo; + + private readonly AutoDeleteCache _cache; + + /// + /// Constructs a new instance of the texture manager. + /// + /// The GPU context that the texture manager belongs to + /// Physical memory where the textures managed by this cache are mapped + public TextureCache(GpuContext context, PhysicalMemory physicalMemory) + { + _context = context; + _physicalMemory = physicalMemory; + + _textures = new MultiRangeList(); + _partiallyMappedTextures = new HashSet(); + + _texturesLock = new ReaderWriterLockSlim(); + + _textureOverlaps = new Texture[OverlapsBufferInitialCapacity]; + _overlapInfo = new OverlapInfo[OverlapsBufferInitialCapacity]; + + _cache = new AutoDeleteCache(); + } + + /// + /// Handles marking of textures written to a memory region being (partially) remapped. + /// + /// Sender object + /// Event arguments + public void MemoryUnmappedHandler(object sender, UnmapEventArgs e) + { + Texture[] overlaps = new Texture[10]; + int overlapCount; + + MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size); + + _texturesLock.EnterReadLock(); + + try + { + overlapCount = _textures.FindOverlaps(unmapped, ref overlaps); + } + finally + { + _texturesLock.ExitReadLock(); + } + + if (overlapCount > 0) + { + for (int i = 0; i < overlapCount; i++) + { + overlaps[i].Unmapped(unmapped); + } + } + + lock (_partiallyMappedTextures) + { + if (overlapCount > 0 || _partiallyMappedTextures.Count > 0) + { + e.AddRemapAction(() => + { + lock (_partiallyMappedTextures) + { + if (overlapCount > 0) + { + for (int i = 0; i < overlapCount; i++) + { + _partiallyMappedTextures.Add(overlaps[i]); + } + } + + // Any texture that has been unmapped at any point or is partially unmapped + // should update their pool references after the remap completes. + + foreach (var texture in _partiallyMappedTextures) + { + texture.UpdatePoolMappings(); + } + } + }); + } + } + } + + /// + /// Determines if a given texture is eligible for upscaling from its info. + /// + /// The texture info to check + /// True if the user of the texture would prefer it to be upscaled immediately + /// True if eligible + private static TextureScaleMode IsUpscaleCompatible(TextureInfo info, bool withUpscale) + { + if ((info.Target == Target.Texture2D || info.Target == Target.Texture2DArray || info.Target == Target.Texture2DMultisample) && !info.FormatInfo.IsCompressed) + { + return UpscaleSafeMode(info) ? (withUpscale ? TextureScaleMode.Scaled : TextureScaleMode.Eligible) : TextureScaleMode.Undesired; + } + + return TextureScaleMode.Blacklisted; + } + + /// + /// Determines if a given texture is "safe" for upscaling from its info. + /// Note that this is different from being compatible - this elilinates targets that would have detrimental effects when scaled. + /// + /// The texture info to check + /// True if safe + private static bool UpscaleSafeMode(TextureInfo info) + { + // While upscaling works for all targets defined by IsUpscaleCompatible, we additionally blacklist targets here that + // may have undesirable results (upscaling blur textures) or simply waste GPU resources (upscaling texture atlas). + + if (info.Levels > 3) + { + // Textures with more than 3 levels are likely to be game textures, rather than render textures. + // Small textures with full mips are likely to be removed by the next check. + return false; + } + + if (info.Width < 8 || info.Height < 8) + { + // Discount textures with small dimensions. + return false; + } + + int widthAlignment = (info.IsLinear ? Constants.StrideAlignment : Constants.GobAlignment) / info.FormatInfo.BytesPerPixel; + + if (!(info.FormatInfo.Format.IsDepthOrStencil() || info.FormatInfo.Components == 1)) + { + // Discount square textures that aren't depth-stencil like. (excludes game textures, cubemap faces, most 3D texture LUT, texture atlas) + // Detect if the texture is possibly square. Widths may be aligned, so to remove the uncertainty we align both the width and height. + + bool possiblySquare = BitUtils.AlignUp(info.Width, widthAlignment) == BitUtils.AlignUp(info.Height, widthAlignment); + + if (possiblySquare) + { + return false; + } + } + + if (info.Height < 360) + { + int aspectWidth = (int)MathF.Ceiling((info.Height / 9f) * 16f); + int aspectMaxWidth = BitUtils.AlignUp(aspectWidth, widthAlignment); + int aspectMinWidth = BitUtils.AlignDown(aspectWidth, widthAlignment); + + if (info.Width >= aspectMinWidth && info.Width <= aspectMaxWidth && info.Height < 360) + { + // Targets that are roughly 16:9 can only be rescaled if they're equal to or above 360p. (excludes blur and bloom textures) + return false; + } + } + + if (info.Width == info.Height * info.Height) + { + // Possibly used for a "3D texture" drawn onto a 2D surface. + // Some games do this to generate a tone mapping LUT without rendering into 3D texture slices. + + return false; + } + + return true; + } + + /// + /// Lifts the texture to the top of the AutoDeleteCache. This is primarily used to enforce that + /// data written to a target will be flushed to memory should the texture be deleted, but also + /// keeps rendered textures alive without a pool reference. + /// + /// Texture to lift + public void Lift(Texture texture) + { + _cache.Lift(texture); + } + + /// + /// Attempts to update a texture's physical memory range. + /// Returns false if there is an existing texture that matches with the updated range. + /// + /// Texture to update + /// New physical memory range + /// True if the mapping was updated, false otherwise + public bool UpdateMapping(Texture texture, MultiRange range) + { + // There cannot be an existing texture compatible with this mapping in the texture cache already. + int overlapCount; + + _texturesLock.EnterReadLock(); + + try + { + overlapCount = _textures.FindOverlaps(range, ref _textureOverlaps); + } + finally + { + _texturesLock.ExitReadLock(); + } + + for (int i = 0; i < overlapCount; i++) + { + var other = _textureOverlaps[i]; + + if (texture != other && + (texture.IsViewCompatible(other.Info, other.Range, true, other.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible || + other.IsViewCompatible(texture.Info, texture.Range, true, texture.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible)) + { + return false; + } + } + + _texturesLock.EnterWriteLock(); + + try + { + _textures.Remove(texture); + + texture.ReplaceRange(range); + + _textures.Add(texture); + } + finally + { + _texturesLock.ExitWriteLock(); + } + + return true; + } + + /// + /// Tries to find an existing texture, or create a new one if not found. + /// + /// GPU memory manager where the texture is mapped + /// Copy texture to find or create + /// Offset to be added to the physical texture address + /// Format information of the copy texture + /// Indicates if aliasing between color and depth format should be allowed + /// Indicates if a new texture should be created if none is found on the cache + /// Indicates if the texture should be scaled from the start + /// A hint indicating the minimum used size for the texture + /// The texture + public Texture FindOrCreateTexture( + MemoryManager memoryManager, + TwodTexture copyTexture, + ulong offset, + FormatInfo formatInfo, + bool depthAlias, + bool shouldCreate, + bool preferScaling, + Size sizeHint) + { + int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY(); + int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ(); + + int width; + + if (copyTexture.LinearLayout) + { + width = copyTexture.Stride / formatInfo.BytesPerPixel; + } + else + { + width = copyTexture.Width; + } + + TextureInfo info = new( + copyTexture.Address.Pack() + offset, + GetMinimumWidthInGob(width, sizeHint.Width, formatInfo.BytesPerPixel, copyTexture.LinearLayout), + copyTexture.Height, + copyTexture.Depth, + 1, + 1, + 1, + copyTexture.Stride, + copyTexture.LinearLayout, + gobBlocksInY, + gobBlocksInZ, + 1, + Target.Texture2D, + formatInfo); + + TextureSearchFlags flags = TextureSearchFlags.ForCopy; + + if (depthAlias) + { + flags |= TextureSearchFlags.DepthAlias; + } + + if (preferScaling) + { + flags |= TextureSearchFlags.WithUpscale; + } + + if (!shouldCreate) + { + flags |= TextureSearchFlags.NoCreate; + } + + Texture texture = FindOrCreateTexture(memoryManager, flags, info, 0, sizeHint: sizeHint); + + texture?.SynchronizeMemory(); + + return texture; + } + + /// + /// Tries to find an existing texture, or create a new one if not found. + /// + /// GPU memory manager where the texture is mapped + /// Format of the texture + /// GPU virtual address of the texture + /// Texture width in bytes + /// Texture height + /// Texture stride if linear, otherwise ignored + /// Indicates if the texture is linear or block linear + /// GOB blocks in Y for block linear textures + /// GOB blocks in Z for 3D block linear textures + /// The texture + public Texture FindOrCreateTexture( + MemoryManager memoryManager, + FormatInfo formatInfo, + ulong gpuAddress, + int xCount, + int yCount, + int stride, + bool isLinear, + int gobBlocksInY, + int gobBlocksInZ) + { + TextureInfo info = new( + gpuAddress, + xCount / formatInfo.BytesPerPixel, + yCount, + 1, + 1, + 1, + 1, + stride, + isLinear, + gobBlocksInY, + gobBlocksInZ, + 1, + Target.Texture2D, + formatInfo); + + Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.ForCopy, info, 0, sizeHint: new Size(xCount, yCount, 1)); + + texture?.SynchronizeMemory(); + + return texture; + } + + /// + /// Tries to find an existing texture, or create a new one if not found. + /// + /// GPU memory manager where the texture is mapped + /// Color buffer texture to find or create + /// Indicates if the texture might be accessed with a non-zero layer index + /// Indicates that the sizeHint region's data will be overwritten + /// Number of samples in the X direction, for MSAA + /// Number of samples in the Y direction, for MSAA + /// A hint indicating the minimum used size for the texture + /// The texture + public Texture FindOrCreateTexture( + MemoryManager memoryManager, + RtColorState colorState, + bool layered, + bool discard, + int samplesInX, + int samplesInY, + Size sizeHint) + { + bool isLinear = colorState.MemoryLayout.UnpackIsLinear(); + + int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY(); + int gobBlocksInZ = colorState.MemoryLayout.UnpackGobBlocksInZ(); + + Target target; + + if (colorState.MemoryLayout.UnpackIsTarget3D()) + { + target = Target.Texture3D; + } + else if ((samplesInX | samplesInY) != 1) + { + target = colorState.Depth > 1 && layered + ? Target.Texture2DMultisampleArray + : Target.Texture2DMultisample; + } + else + { + target = colorState.Depth > 1 && layered + ? Target.Texture2DArray + : Target.Texture2D; + } + + FormatInfo formatInfo = colorState.Format.Convert(); + + int width, stride; + + // For linear textures, the width value is actually the stride. + // We can easily get the width by dividing the stride by the bpp, + // since the stride is the total number of bytes occupied by a + // line. The stride should also meet alignment constraints however, + // so the width we get here is the aligned width. + if (isLinear) + { + width = colorState.WidthOrStride / formatInfo.BytesPerPixel; + stride = colorState.WidthOrStride; + } + else + { + width = colorState.WidthOrStride; + stride = 0; + } + + TextureInfo info = new( + colorState.Address.Pack(), + GetMinimumWidthInGob(width, sizeHint.Width, formatInfo.BytesPerPixel, isLinear), + colorState.Height, + colorState.Depth, + 1, + samplesInX, + samplesInY, + stride, + isLinear, + gobBlocksInY, + gobBlocksInZ, + 1, + target, + formatInfo); + + int layerSize = !isLinear ? colorState.LayerSize * 4 : 0; + + var flags = TextureSearchFlags.WithUpscale; + + if (discard) + { + flags |= TextureSearchFlags.DiscardData; + } + + Texture texture = FindOrCreateTexture(memoryManager, flags, info, layerSize, sizeHint: sizeHint); + + texture?.SynchronizeMemory(); + + return texture; + } + + /// + /// Tries to find an existing texture, or create a new one if not found. + /// + /// GPU memory manager where the texture is mapped + /// Depth-stencil buffer texture to find or create + /// Size of the depth-stencil texture + /// Indicates if the texture might be accessed with a non-zero layer index + /// Indicates that the sizeHint region's data will be overwritten + /// Number of samples in the X direction, for MSAA + /// Number of samples in the Y direction, for MSAA + /// A hint indicating the minimum used size for the texture + /// The texture + public Texture FindOrCreateTexture( + MemoryManager memoryManager, + RtDepthStencilState dsState, + Size3D size, + bool layered, + bool discard, + int samplesInX, + int samplesInY, + Size sizeHint) + { + int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY(); + int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ(); + + layered &= size.UnpackIsLayered(); + + Target target; + + if ((samplesInX | samplesInY) != 1) + { + target = size.Depth > 1 && layered + ? Target.Texture2DMultisampleArray + : Target.Texture2DMultisample; + } + else + { + target = size.Depth > 1 && layered + ? Target.Texture2DArray + : Target.Texture2D; + } + + FormatInfo formatInfo = dsState.Format.Convert(); + + TextureInfo info = new( + dsState.Address.Pack(), + GetMinimumWidthInGob(size.Width, sizeHint.Width, formatInfo.BytesPerPixel, false), + size.Height, + size.Depth, + 1, + samplesInX, + samplesInY, + 0, + false, + gobBlocksInY, + gobBlocksInZ, + 1, + target, + formatInfo); + + var flags = TextureSearchFlags.WithUpscale; + + if (discard) + { + flags |= TextureSearchFlags.DiscardData; + } + + Texture texture = FindOrCreateTexture(memoryManager, flags, info, dsState.LayerSize * 4, sizeHint: sizeHint); + + texture?.SynchronizeMemory(); + + return texture; + } + + /// + /// For block linear textures, gets the minimum width of the texture + /// that would still have the same number of GOBs per row as the original width. + /// + /// The possibly aligned texture width + /// The minimum width that the texture may have without losing data + /// Bytes per pixel of the texture format + /// True if the texture is linear, false for block linear + /// The minimum width of the texture with the same amount of GOBs per row + private static int GetMinimumWidthInGob(int width, int minimumWidth, int bytesPerPixel, bool isLinear) + { + if (isLinear || (uint)minimumWidth >= (uint)width) + { + return width; + } + + // Calculate the minimum possible that would not cause data loss + // and would be still within the same GOB (aligned size would be the same). + // This is useful for render and copy operations, where we don't know the + // exact width of the texture, but it doesn't matter, as long the texture is + // at least as large as the region being rendered or copied. + + int alignment = 64 / bytesPerPixel; + int widthAligned = BitUtils.AlignUp(width, alignment); + + return Math.Clamp(widthAligned - alignment + 1, minimumWidth, widthAligned); + } + + /// + /// Determines if texture data should be fully discarded + /// based on the size hint region and whether it is set to be discarded. + /// + /// Whether the size hint region should be discarded + /// The texture being discarded + /// A hint indicating the minimum used size for the texture + /// True if the data should be discarded, false otherwise + private static bool ShouldDiscard(bool discard, Texture texture, Size? sizeHint) + { + return discard && + texture.Info.DepthOrLayers == 1 && + sizeHint != null && + texture.Width <= sizeHint.Value.Width && + texture.Height <= sizeHint.Value.Height; + } + + /// + /// Discards texture data if requested and possible. + /// + /// Whether the size hint region should be discarded + /// The texture being discarded + /// A hint indicating the minimum used size for the texture + private static void DiscardIfNeeded(bool discard, Texture texture, Size? sizeHint) + { + if (ShouldDiscard(discard, texture, sizeHint)) + { + texture.DiscardData(); + } + } + + /// + /// Tries to find an existing texture, or create a new one if not found. + /// + /// GPU memory manager where the texture is mapped + /// The texture search flags, defines texture comparison rules + /// Texture information of the texture to be found or created + /// Size in bytes of a single texture layer + /// A hint indicating the minimum used size for the texture + /// Optional ranges of physical memory where the texture data is located + /// The texture + public Texture FindOrCreateTexture( + MemoryManager memoryManager, + TextureSearchFlags flags, + TextureInfo info, + int layerSize = 0, + Size? sizeHint = null, + MultiRange? range = null) + { + bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0; + bool discard = (flags & TextureSearchFlags.DiscardData) != 0; + + TextureScaleMode scaleMode = IsUpscaleCompatible(info, (flags & TextureSearchFlags.WithUpscale) != 0); + + ulong address; + + if (range != null) + { + address = range.Value.GetSubRange(0).Address; + } + else + { + address = memoryManager.Translate(info.GpuAddress); + + // If the start address is unmapped, let's try to find a page of memory that is mapped. + if (address == MemoryManager.PteUnmapped) + { + // Make sure that the dimensions are valid before calculating the texture size. + if (info.Width < 1 || info.Height < 1 || info.Levels < 1) + { + return null; + } + + if ((info.Target == Target.Texture3D || + info.Target == Target.Texture2DArray || + info.Target == Target.Texture2DMultisampleArray || + info.Target == Target.CubemapArray) && info.DepthOrLayers < 1) + { + return null; + } + + ulong dataSize = (ulong)info.CalculateSizeInfo(layerSize).TotalSize; + + address = memoryManager.TranslateFirstMapped(info.GpuAddress, dataSize); + } + + // If address is still invalid, the texture is fully unmapped, so it has no data, just return null. + if (address == MemoryManager.PteUnmapped) + { + return null; + } + } + + int sameAddressOverlapsCount; + + _texturesLock.EnterReadLock(); + + try + { + // Try to find a perfect texture match, with the same address and parameters. + sameAddressOverlapsCount = _textures.FindOverlaps(address, ref _textureOverlaps); + } + finally + { + _texturesLock.ExitReadLock(); + } + + Texture texture = null; + + long bestSequence = 0; + + for (int index = 0; index < sameAddressOverlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + + TextureMatchQuality matchQuality = overlap.IsExactMatch(info, flags); + + if (matchQuality != TextureMatchQuality.NoMatch) + { + // If the parameters match, we need to make sure the texture is mapped to the same memory regions. + if (range != null) + { + // If a range of memory was supplied, just check if the ranges match. + if (!overlap.Range.Equals(range.Value)) + { + continue; + } + } + else + { + // If no range was supplied, we can check if the GPU virtual address match. If they do, + // we know the textures are located at the same memory region. + // If they don't, it may still be mapped to the same physical region, so we + // do a more expensive check to tell if they are mapped into the same physical regions. + // If the GPU VA for the texture has ever been unmapped, then the range must be checked regardless. + if ((overlap.Info.GpuAddress != info.GpuAddress || overlap.ChangedMapping) && + !memoryManager.CompareRange(overlap.Range, info.GpuAddress)) + { + continue; + } + } + + if (texture == null || overlap.Group.ModifiedSequence - bestSequence > 0) + { + texture = overlap; + bestSequence = overlap.Group.ModifiedSequence; + } + } + } + + if (texture != null) + { + DiscardIfNeeded(discard, texture, sizeHint); + + texture.SynchronizeMemory(); + + return texture; + } + else if (flags.HasFlag(TextureSearchFlags.NoCreate)) + { + return null; + } + + // Calculate texture sizes, used to find all overlapping textures. + SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize); + + ulong size = (ulong)sizeInfo.TotalSize; + bool partiallyMapped = false; + + if (range == null) + { + range = memoryManager.GetPhysicalRegions(info.GpuAddress, size); + + for (int i = 0; i < range.Value.Count; i++) + { + if (range.Value.GetSubRange(i).Address == MemoryManager.PteUnmapped) + { + partiallyMapped = true; + break; + } + } + } + + // Find view compatible matches. + int overlapsCount = 0; + + if (info.Target != Target.TextureBuffer) + { + _texturesLock.EnterReadLock(); + + try + { + overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps); + } + finally + { + _texturesLock.ExitReadLock(); + } + } + + if (_overlapInfo.Length != _textureOverlaps.Length) + { + Array.Resize(ref _overlapInfo, _textureOverlaps.Length); + } + + // =============== Find Texture View of Existing Texture =============== + + int fullyCompatible = 0; + + // Evaluate compatibility of overlaps, add temporary references + int preferredOverlap = -1; + + for (int index = 0; index < overlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible( + info, + range.Value, + isSamplerTexture, + sizeInfo.LayerSize, + _context.Capabilities, + out int firstLayer, + out int firstLevel, + flags); + + if (overlapCompatibility >= TextureViewCompatibility.FormatAlias) + { + if (overlap.IsView) + { + overlapCompatibility = TextureViewCompatibility.CopyOnly; + } + else + { + fullyCompatible++; + + if (preferredOverlap == -1 || overlap.Group.ModifiedSequence - bestSequence > 0) + { + preferredOverlap = index; + bestSequence = overlap.Group.ModifiedSequence; + } + } + } + + _overlapInfo[index] = new OverlapInfo(overlapCompatibility, firstLayer, firstLevel); + overlap.IncrementReferenceCount(); + } + + // Search through the overlaps to find a compatible view and establish any copy dependencies. + + if (preferredOverlap != -1) + { + Texture overlap = _textureOverlaps[preferredOverlap]; + OverlapInfo oInfo = _overlapInfo[preferredOverlap]; + + bool aliased = oInfo.Compatibility == TextureViewCompatibility.FormatAlias; + + if (!isSamplerTexture) + { + // If this is not a sampler texture, the size might be different from the requested size, + // so we need to make sure the texture information has the correct size for this base texture, + // before creating the view. + + info = info.CreateInfoForLevelView(overlap, oInfo.FirstLevel, aliased); + } + else if (aliased) + { + // The format must be changed to match the parent. + info = info.CreateInfoWithFormat(overlap.Info.FormatInfo); + } + + texture = overlap.CreateView(info, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel); + texture.SynchronizeMemory(); + } + else + { + for (int index = 0; index < overlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + OverlapInfo oInfo = _overlapInfo[index]; + + if (oInfo.Compatibility == TextureViewCompatibility.CopyOnly && fullyCompatible == 0) + { + // Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead. + + texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode); + + // If the new texture is larger than the existing one, we need to fill the remaining space with CPU data, + // otherwise we only need the data that is copied from the existing texture, without loading the CPU data. + bool updateNewTexture = texture.Width > overlap.Width || texture.Height > overlap.Height; + + texture.InitializeGroup(true, true, new List()); + texture.InitializeData(false, updateNewTexture); + + overlap.SynchronizeMemory(); + overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true); + break; + } + } + } + + if (texture != null) + { + // This texture could be a view of multiple parent textures with different storages, even if it is a view. + // When a texture is created, make sure all possible dependencies to other textures are created as copies. + // (even if it could be fulfilled without a copy) + + for (int index = 0; index < overlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + OverlapInfo oInfo = _overlapInfo[index]; + + if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible) + { + if (!overlap.IsView && texture.DataOverlaps(overlap, oInfo.Compatibility)) + { + texture.Group.RegisterIncompatibleOverlap(new TextureIncompatibleOverlap(overlap.Group, oInfo.Compatibility), true); + } + } + else if (overlap.Group != texture.Group) + { + overlap.SynchronizeMemory(); + overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true); + } + } + + texture.SynchronizeMemory(); + } + + // =============== Create a New Texture =============== + + // No match, create a new texture. + if (texture == null) + { + texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode); + + // Step 1: Find textures that are view compatible with the new texture. + // Any textures that are incompatible will contain garbage data, so they should be removed where possible. + + int viewCompatible = 0; + fullyCompatible = 0; + bool setData = isSamplerTexture || overlapsCount == 0 || flags.HasFlag(TextureSearchFlags.ForCopy); + + bool hasLayerViews = false; + bool hasMipViews = false; + + var incompatibleOverlaps = new List(); + + for (int index = 0; index < overlapsCount; index++) + { + Texture overlap = _textureOverlaps[index]; + bool overlapInCache = overlap.CacheNode != null; + + TextureViewCompatibility compatibility = texture.IsViewCompatible( + overlap.Info, + overlap.Range, + exactSize: true, + overlap.LayerSize, + _context.Capabilities, + out int firstLayer, + out int firstLevel); + + if (overlap.IsView && compatibility == TextureViewCompatibility.Full) + { + compatibility = TextureViewCompatibility.CopyOnly; + } + + if (compatibility > TextureViewCompatibility.LayoutIncompatible) + { + _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); + _textureOverlaps[index] = _textureOverlaps[viewCompatible]; + _textureOverlaps[viewCompatible] = overlap; + + if (compatibility == TextureViewCompatibility.Full) + { + if (viewCompatible != fullyCompatible) + { + // Swap overlaps so that the fully compatible views have priority. + + _overlapInfo[viewCompatible] = _overlapInfo[fullyCompatible]; + _textureOverlaps[viewCompatible] = _textureOverlaps[fullyCompatible]; + + _overlapInfo[fullyCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); + _textureOverlaps[fullyCompatible] = overlap; + } + + fullyCompatible++; + } + + viewCompatible++; + + hasLayerViews |= overlap.Info.GetSlices() < texture.Info.GetSlices(); + hasMipViews |= overlap.Info.Levels < texture.Info.Levels; + } + else + { + bool dataOverlaps = texture.DataOverlaps(overlap, compatibility); + + if (!overlap.IsView && dataOverlaps && !incompatibleOverlaps.Exists(incompatible => incompatible.Group == overlap.Group)) + { + incompatibleOverlaps.Add(new TextureIncompatibleOverlap(overlap.Group, compatibility)); + } + + bool removeOverlap; + bool modified = overlap.CheckModified(false); + + if (overlapInCache || !setData) + { + if (!dataOverlaps) + { + // Allow textures to overlap if their data does not actually overlap. + // This typically happens when mip level subranges of a layered texture are used. (each texture fills the gaps of the others) + continue; + } + + // The overlap texture is going to contain garbage data after we draw, or is generally incompatible. + // The texture group will obtain copy dependencies for any subresources that are compatible between the two textures, + // but sometimes its data must be flushed regardless. + + // If the texture was modified since its last use, then that data is probably meant to go into this texture. + // If the data has been modified by the CPU, then it also shouldn't be flushed. + + bool flush = overlapInCache && !modified && overlap.AlwaysFlushOnOverlap; + + setData |= modified || flush; + + if (overlapInCache) + { + if (flush || overlap.HadPoolOwner || overlap.IsView) + { + _cache.Remove(overlap, flush); + } + else + { + // This texture has only ever been referenced in the AutoDeleteCache. + // Keep this texture alive with the short duration cache, as it may be used often but not sampled. + + _cache.AddShortCache(overlap); + } + } + + removeOverlap = modified; + } + else + { + // If an incompatible overlapping texture has been modified, then it's data is likely destined for this texture, + // and the overlapped texture will contain garbage. In this case, it should be removed to save memory. + removeOverlap = modified; + } + + if (removeOverlap && overlap.Info.Target != Target.TextureBuffer) + { + overlap.RemoveFromPools(false); + } + } + } + + texture.InitializeGroup(hasLayerViews, hasMipViews, incompatibleOverlaps); + + // We need to synchronize before copying the old view data to the texture, + // otherwise the copied data would be overwritten by a future synchronization. + texture.InitializeData(false, setData && !ShouldDiscard(discard, texture, sizeHint)); + + texture.Group.InitializeOverlaps(); + + for (int index = 0; index < viewCompatible; index++) + { + Texture overlap = _textureOverlaps[index]; + + OverlapInfo oInfo = _overlapInfo[index]; + + if (overlap.Group == texture.Group) + { + // If the texture group is equal, then this texture (or its parent) is already a view. + continue; + } + + // Note: If we allow different sizes for those overlaps, + // we need to make sure that the "info" has the correct size for the parent texture here. + // Since this is not allowed right now, we don't need to do it. + + TextureInfo overlapInfo = overlap.Info; + + if (texture.ScaleFactor != overlap.ScaleFactor) + { + // A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself. + // In that case, we prefer the higher scale only if our format is render-target-like, otherwise we scale the view down before copy. + + texture.PropagateScale(overlap); + } + + if (oInfo.Compatibility != TextureViewCompatibility.Full) + { + // Copy only compatibility, or target texture is already a view. + + overlap.SynchronizeMemory(); + texture.CreateCopyDependency(overlap, oInfo.FirstLayer, oInfo.FirstLevel, false); + } + else + { + TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor); + + ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel); + + overlap.SynchronizeMemory(); + + overlap.HostTexture.CopyTo(newView, 0, 0); + + overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel); + } + } + + texture.SynchronizeMemory(); + } + + // Sampler textures are managed by the texture pool, all other textures + // are managed by the auto delete cache. + if (!isSamplerTexture) + { + _cache.Add(texture); + } + + _texturesLock.EnterWriteLock(); + + try + { + _textures.Add(texture); + } + finally + { + _texturesLock.ExitWriteLock(); + } + + if (partiallyMapped) + { + lock (_partiallyMappedTextures) + { + _partiallyMappedTextures.Add(texture); + } + } + + ShrinkOverlapsBufferIfNeeded(); + + for (int i = 0; i < overlapsCount; i++) + { + _textureOverlaps[i].DecrementReferenceCount(); + } + + return texture; + } + + /// + /// Attempt to find a texture on the short duration cache. + /// + /// The texture descriptor + /// The texture if found, null otherwise + public Texture FindShortCache(in TextureDescriptor descriptor) + { + return _cache.FindShortCache(descriptor); + } + + /// + /// Tries to find an existing texture matching the given buffer copy destination. If none is found, returns null. + /// + /// GPU memory manager where the texture is mapped + /// GPU virtual address of the texture + /// Bytes per pixel + /// If is true, should have the texture stride, otherwise ignored + /// If is false, should have the texture height, otherwise ignored + /// Number of pixels to be copied per line + /// Number of lines to be copied + /// True if the texture has a linear layout, false otherwise + /// If is false, the amount of GOB blocks in the Y axis + /// If is false, the amount of GOB blocks in the Z axis + /// A matching texture, or null if there is no match + public Texture FindTexture( + MemoryManager memoryManager, + ulong gpuVa, + int bpp, + int stride, + int height, + int xCount, + int yCount, + bool linear, + int gobBlocksInY, + int gobBlocksInZ) + { + ulong address = memoryManager.Translate(gpuVa); + + if (address == MemoryManager.PteUnmapped) + { + return null; + } + + int addressMatches; + + _texturesLock.EnterReadLock(); + + try + { + addressMatches = _textures.FindOverlaps(address, ref _textureOverlaps); + } + finally + { + _texturesLock.ExitReadLock(); + } + + Texture textureMatch = null; + + for (int i = 0; i < addressMatches; i++) + { + Texture texture = _textureOverlaps[i]; + FormatInfo format = texture.Info.FormatInfo; + + if (texture.Info.DepthOrLayers > 1 || texture.Info.Levels > 1 || texture.Info.FormatInfo.IsCompressed) + { + // Don't support direct buffer copies to anything that isn't a single 2D image, uncompressed. + continue; + } + + bool match; + + if (linear) + { + // Size is not available for linear textures. Use the stride and end of the copy region instead. + + match = texture.Info.IsLinear && texture.Info.Stride == stride && yCount == texture.Info.Height; + } + else + { + // Bpp may be a mismatch between the target texture and the param. + // Due to the way linear strided and block layouts work, widths can be multiplied by Bpp for comparison. + // Note: tex.Width is the aligned texture size. Prefer param.XCount, as the destination should be a texture with that exact size. + + bool sizeMatch = xCount * bpp == texture.Info.Width * format.BytesPerPixel && height == texture.Info.Height; + bool formatMatch = !texture.Info.IsLinear && + texture.Info.GobBlocksInY == gobBlocksInY && + texture.Info.GobBlocksInZ == gobBlocksInZ; + + match = sizeMatch && formatMatch; + } + + if (match) + { + if (textureMatch == null) + { + textureMatch = texture; + } + else if (texture.Group != textureMatch.Group) + { + return null; // It's ambiguous which texture should match between multiple choices, so leave it up to the slow path. + } + } + } + + return textureMatch; + } + + /// + /// Resizes the temporary buffer used for range list intersection results, if it has grown too much. + /// + private void ShrinkOverlapsBufferIfNeeded() + { + if (_textureOverlaps.Length > OverlapsBufferMaxCapacity) + { + Array.Resize(ref _textureOverlaps, OverlapsBufferMaxCapacity); + } + } + + /// + /// Gets a texture creation information from texture information. + /// This can be used to create new host textures. + /// + /// Texture information + /// GPU capabilities + /// Texture scale factor, to be applied to the texture size + /// The texture creation information + public static TextureCreateInfo GetCreateInfo(TextureInfo info, Capabilities caps, float scale) + { + FormatInfo formatInfo = TextureCompatibility.ToHostCompatibleFormat(info, caps); + + if (info.Target == Target.TextureBuffer && !caps.SupportsSnormBufferTextureFormat) + { + // If the host does not support signed normalized formats, we use a signed integer format instead. + // The shader will need the appropriate conversion code to compensate. + switch (formatInfo.Format) + { + case Format.R8Snorm: + formatInfo = new FormatInfo(Format.R8Sint, 1, 1, 1, 1); + break; + case Format.R16Snorm: + formatInfo = new FormatInfo(Format.R16Sint, 1, 1, 2, 1); + break; + case Format.R8G8Snorm: + formatInfo = new FormatInfo(Format.R8G8Sint, 1, 1, 2, 2); + break; + case Format.R16G16Snorm: + formatInfo = new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2); + break; + case Format.R8G8B8A8Snorm: + formatInfo = new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4, 4); + break; + case Format.R16G16B16A16Snorm: + formatInfo = new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8, 4); + break; + } + } + + int width = info.Width / info.SamplesInX; + int height = info.Height / info.SamplesInY; + + int depth = info.GetDepth() * info.GetLayers(); + + if (scale != 1f) + { + width = (int)MathF.Ceiling(width * scale); + height = (int)MathF.Ceiling(height * scale); + } + + return new TextureCreateInfo( + width, + height, + depth, + info.Levels, + info.Samples, + formatInfo.BlockWidth, + formatInfo.BlockHeight, + formatInfo.BytesPerPixel, + formatInfo.Format, + info.DepthStencilMode, + info.Target, + info.SwizzleR, + info.SwizzleG, + info.SwizzleB, + info.SwizzleA); + } + + /// + /// Removes a texture from the cache. + /// + /// + /// This only removes the texture from the internal list, not from the auto-deletion cache. + /// It may still have live references after the removal. + /// + /// The texture to be removed + public void RemoveTextureFromCache(Texture texture) + { + _texturesLock.EnterWriteLock(); + + try + { + _textures.Remove(texture); + } + finally + { + _texturesLock.ExitWriteLock(); + } + + lock (_partiallyMappedTextures) + { + _partiallyMappedTextures.Remove(texture); + } + } + + /// + /// Queries a texture's memory range and marks it as partially mapped or not. + /// Partially mapped textures re-evaluate their memory range after each time GPU memory is mapped. + /// + /// GPU memory manager where the texture is mapped + /// The virtual address of the texture + /// The texture to be marked + /// The physical regions for the texture, found when evaluating whether the texture was partially mapped + public MultiRange UpdatePartiallyMapped(MemoryManager memoryManager, ulong address, Texture texture) + { + MultiRange range; + lock (_partiallyMappedTextures) + { + range = memoryManager.GetPhysicalRegions(address, texture.Size); + bool partiallyMapped = false; + + for (int i = 0; i < range.Count; i++) + { + if (range.GetSubRange(i).Address == MemoryManager.PteUnmapped) + { + partiallyMapped = true; + break; + } + } + + if (partiallyMapped) + { + _partiallyMappedTextures.Add(texture); + } + else + { + _partiallyMappedTextures.Remove(texture); + } + } + + return range; + } + + /// + /// Adds a texture to the short duration cache. This typically keeps it alive for two ticks. + /// + /// Texture to add to the short cache + /// Last used texture descriptor + public void AddShortCache(Texture texture, ref TextureDescriptor descriptor) + { + _cache.AddShortCache(texture, ref descriptor); + } + + /// + /// Adds a texture to the short duration cache without a descriptor. This typically keeps it alive for two ticks. + /// On expiry, it will be removed from the AutoDeleteCache. + /// + /// Texture to add to the short cache + public void AddShortCache(Texture texture) + { + _cache.AddShortCache(texture); + } + + /// + /// Removes a texture from the short duration cache. + /// + /// Texture to remove from the short cache + public void RemoveShortCache(Texture texture) + { + _cache.RemoveShortCache(texture); + } + + /// + /// Ticks periodic elements of the texture cache. + /// + public void Tick() + { + _cache.ProcessShortCache(); + } + + /// + /// Disposes all textures and samplers in the cache. + /// It's an error to use the texture cache after disposal. + /// + public void Dispose() + { + _texturesLock.EnterReadLock(); + + try + { + foreach (Texture texture in _textures) + { + texture.Dispose(); + } + } + finally + { + _texturesLock.ExitReadLock(); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs new file mode 100644 index 00000000..3cdeac9c --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs @@ -0,0 +1,876 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Texture; +using System; +using System.Diagnostics; +using System.Numerics; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture format compatibility checks. + /// + static class TextureCompatibility + { + private enum FormatClass + { + Unclassified, + Bc1Rgba, + Bc2, + Bc3, + Bc4, + Bc5, + Bc6, + Bc7, + Etc2Rgb, + Etc2Rgba, + Astc4x4, + Astc5x4, + Astc5x5, + Astc6x5, + Astc6x6, + Astc8x5, + Astc8x6, + Astc8x8, + Astc10x5, + Astc10x6, + Astc10x8, + Astc10x10, + Astc12x10, + Astc12x12, + } + + /// + /// Checks if a format is host incompatible. + /// + /// + /// Host incompatible formats can't be used directly, the texture data needs to be converted + /// to a compatible format first. + /// + /// Texture information + /// Host GPU capabilities + /// True if the format is incompatible, false otherwise + public static bool IsFormatHostIncompatible(TextureInfo info, Capabilities caps) + { + Format originalFormat = info.FormatInfo.Format; + return ToHostCompatibleFormat(info, caps).Format != originalFormat; + } + + /// + /// Converts a incompatible format to a host compatible format, or return the format directly + /// if it is already host compatible. + /// + /// + /// This can be used to convert a incompatible compressed format to the decompressor + /// output format. + /// + /// Texture information + /// Host GPU capabilities + /// A host compatible format + public static FormatInfo ToHostCompatibleFormat(TextureInfo info, Capabilities caps) + { + // The host API does not support those compressed formats. + // We assume software decompression will be done for those textures, + // and so we adjust the format here to match the decompressor output. + + if (!caps.SupportsAstcCompression) + { + if (info.FormatInfo.Format.IsAstcUnorm()) + { + return GraphicsConfig.EnableTextureRecompression + ? new FormatInfo(Format.Bc7Unorm, 4, 4, 16, 4) + : new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4); + } + else if (info.FormatInfo.Format.IsAstcSrgb()) + { + return GraphicsConfig.EnableTextureRecompression + ? new FormatInfo(Format.Bc7Srgb, 4, 4, 16, 4) + : new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4); + } + } + + if (!HostSupportsBcFormat(info.FormatInfo.Format, info.Target, caps)) + { + switch (info.FormatInfo.Format) + { + case Format.Bc1RgbaSrgb: + case Format.Bc2Srgb: + case Format.Bc3Srgb: + case Format.Bc7Srgb: + return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4); + case Format.Bc1RgbaUnorm: + case Format.Bc2Unorm: + case Format.Bc3Unorm: + case Format.Bc7Unorm: + return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4); + case Format.Bc4Unorm: + return new FormatInfo(Format.R8Unorm, 1, 1, 1, 1); + case Format.Bc4Snorm: + return new FormatInfo(Format.R8Snorm, 1, 1, 1, 1); + case Format.Bc5Unorm: + return new FormatInfo(Format.R8G8Unorm, 1, 1, 2, 2); + case Format.Bc5Snorm: + return new FormatInfo(Format.R8G8Snorm, 1, 1, 2, 2); + case Format.Bc6HSfloat: + case Format.Bc6HUfloat: + return new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4); + } + } + + if (!caps.SupportsEtc2Compression) + { + switch (info.FormatInfo.Format) + { + case Format.Etc2RgbaSrgb: + case Format.Etc2RgbPtaSrgb: + case Format.Etc2RgbSrgb: + return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4); + case Format.Etc2RgbaUnorm: + case Format.Etc2RgbPtaUnorm: + case Format.Etc2RgbUnorm: + return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4); + } + } + + if (!caps.SupportsR4G4Format && info.FormatInfo.Format == Format.R4G4Unorm) + { + if (caps.SupportsR4G4B4A4Format) + { + return new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4); + } + else + { + return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4); + } + } + + if (info.FormatInfo.Format == Format.R4G4B4A4Unorm) + { + if (!caps.SupportsR4G4B4A4Format) + { + return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4); + } + } + else if (!caps.Supports5BitComponentFormat && info.FormatInfo.Format.Is16BitPacked()) + { + return new FormatInfo(info.FormatInfo.Format.IsBgr() ? Format.B8G8R8A8Unorm : Format.R8G8B8A8Unorm, 1, 1, 4, 4); + } + + return info.FormatInfo; + } + + /// + /// Checks if the host API supports a given texture compression format of the BC family. + /// + /// BC format to be checked + /// Target usage of the texture + /// Host GPU Capabilities + /// True if the texture host supports the format with the given target usage, false otherwise + public static bool HostSupportsBcFormat(Format format, Target target, Capabilities caps) + { + bool not3DOr3DCompressionSupported = target != Target.Texture3D || caps.Supports3DTextureCompression; + + switch (format) + { + case Format.Bc1RgbaSrgb: + case Format.Bc1RgbaUnorm: + case Format.Bc2Srgb: + case Format.Bc2Unorm: + case Format.Bc3Srgb: + case Format.Bc3Unorm: + return caps.SupportsBc123Compression && not3DOr3DCompressionSupported; + case Format.Bc4Unorm: + case Format.Bc4Snorm: + case Format.Bc5Unorm: + case Format.Bc5Snorm: + return caps.SupportsBc45Compression && not3DOr3DCompressionSupported; + case Format.Bc6HSfloat: + case Format.Bc6HUfloat: + case Format.Bc7Srgb: + case Format.Bc7Unorm: + return caps.SupportsBc67Compression && not3DOr3DCompressionSupported; + } + + return true; + } + + /// + /// Determines whether a texture can flush its data back to guest memory. + /// + /// Texture information + /// Host GPU Capabilities + /// True if the texture can flush, false otherwise + public static bool CanTextureFlush(TextureInfo info, Capabilities caps) + { + if (IsFormatHostIncompatible(info, caps)) + { + return false; // Flushing this format is not supported, as it may have been converted to another host format. + } + + if (info.Target == Target.Texture2DMultisample || + info.Target == Target.Texture2DMultisampleArray) + { + return false; // Flushing multisample textures is not supported, the host does not allow getting their data. + } + + return true; + } + + /// + /// Checks if the texture format matches with the specified texture information. + /// + /// Texture information to compare + /// Texture information to compare with + /// Indicates that the texture will be used for shader sampling + /// Indicates if aliasing between color and depth format should be allowed + /// A value indicating how well the formats match + public static TextureMatchQuality FormatMatches(TextureInfo lhs, TextureInfo rhs, bool forSampler, bool depthAlias) + { + // D32F and R32F texture have the same representation internally, + // however the R32F format is used to sample from depth textures. + if (IsValidDepthAsColorAlias(lhs.FormatInfo.Format, rhs.FormatInfo.Format) && (forSampler || depthAlias)) + { + return TextureMatchQuality.FormatAlias; + } + + if (depthAlias) + { + // The 2D engine does not support depth-stencil formats, so it will instead + // use equivalent color formats. We must also consider them as compatible. + if (lhs.FormatInfo.Format == Format.S8Uint && rhs.FormatInfo.Format == Format.R8Unorm) + { + return TextureMatchQuality.FormatAlias; + } + else if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint || + lhs.FormatInfo.Format == Format.S8UintD24Unorm || + lhs.FormatInfo.Format == Format.X8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm) + { + return TextureMatchQuality.FormatAlias; + } + else if (lhs.FormatInfo.Format == Format.D32FloatS8Uint && rhs.FormatInfo.Format == Format.R32G32Float) + { + return TextureMatchQuality.FormatAlias; + } + } + + return lhs.FormatInfo.Format == rhs.FormatInfo.Format ? TextureMatchQuality.Perfect : TextureMatchQuality.NoMatch; + } + + /// + /// Checks if the texture layout specified matches with this texture layout. + /// The layout information is composed of the Stride for linear textures, or GOB block size + /// for block linear textures. + /// + /// Texture information to compare + /// Texture information to compare with + /// True if the layout matches, false otherwise + public static bool LayoutMatches(TextureInfo lhs, TextureInfo rhs) + { + if (lhs.IsLinear != rhs.IsLinear) + { + return false; + } + + // For linear textures, gob block sizes are ignored. + // For block linear textures, the stride is ignored. + if (rhs.IsLinear) + { + return lhs.Stride == rhs.Stride; + } + else + { + return lhs.GobBlocksInY == rhs.GobBlocksInY && + lhs.GobBlocksInZ == rhs.GobBlocksInZ; + } + } + + /// + /// Obtain the minimum compatibility level of two provided view compatibility results. + /// + /// The first compatibility level + /// The second compatibility level + /// The minimum compatibility level of two provided view compatibility results + public static TextureViewCompatibility PropagateViewCompatibility(TextureViewCompatibility first, TextureViewCompatibility second) + { + return (TextureViewCompatibility)Math.Min((int)first, (int)second); + } + + /// + /// Checks if the sizes of two texture levels are copy compatible. + /// + /// Texture information of the texture view + /// Texture information of the texture view to match against + /// Mipmap level of the texture view in relation to this texture + /// Mipmap level of the texture view in relation to the second texture + /// True if both levels are view compatible + public static bool CopySizeMatches(TextureInfo lhs, TextureInfo rhs, int lhsLevel, int rhsLevel) + { + Size size = GetAlignedSize(lhs, lhsLevel); + + Size otherSize = GetAlignedSize(rhs, rhsLevel); + + if (size.Width == otherSize.Width && size.Height == otherSize.Height) + { + return true; + } + else if (lhs.IsLinear && rhs.IsLinear) + { + // Copy between linear textures with matching stride. + int stride = BitUtils.AlignUp(Math.Max(1, lhs.Stride >> lhsLevel), Constants.StrideAlignment); + + return stride == rhs.Stride; + } + else + { + return false; + } + } + + /// + /// Checks if the sizes of two given textures are view compatible. + /// + /// Texture information of the texture view + /// Texture information of the texture view to match against + /// Indicates if the sizes must be exactly equal + /// Mipmap level of the texture view in relation to this texture + /// The view compatibility level of the view sizes + public static TextureViewCompatibility ViewSizeMatches(TextureInfo lhs, TextureInfo rhs, bool exact, int level) + { + Size lhsAlignedSize = GetAlignedSize(lhs, level); + Size rhsAlignedSize = GetAlignedSize(rhs); + + Size lhsSize = GetSizeInBlocks(lhs, level); + Size rhsSize = GetSizeInBlocks(rhs); + + bool alignedWidthMatches = lhsAlignedSize.Width == rhsAlignedSize.Width; + + if (lhs.FormatInfo.BytesPerPixel != rhs.FormatInfo.BytesPerPixel && IsIncompatibleFormatAliasingAllowed(lhs.FormatInfo, rhs.FormatInfo)) + { + // If the formats are incompatible, but the texture strides match, + // we might allow them to be copy compatible depending on the format. + // The strides are aligned because the format with higher bytes per pixel + // might need a bit of padding at the end due to one width not being a multiple of the other. + + Debug.Assert((1 << BitOperations.Log2((uint)lhs.FormatInfo.BytesPerPixel)) == lhs.FormatInfo.BytesPerPixel); + Debug.Assert((1 << BitOperations.Log2((uint)rhs.FormatInfo.BytesPerPixel)) == rhs.FormatInfo.BytesPerPixel); + + int alignment = Math.Max(lhs.FormatInfo.BytesPerPixel, rhs.FormatInfo.BytesPerPixel); + + int lhsStride = BitUtils.AlignUp(lhsSize.Width * lhs.FormatInfo.BytesPerPixel, alignment); + int rhsStride = BitUtils.AlignUp(rhsSize.Width * rhs.FormatInfo.BytesPerPixel, alignment); + + alignedWidthMatches = lhsStride == rhsStride; + } + + TextureViewCompatibility result = TextureViewCompatibility.Full; + + // For copies, we can copy a subset of the 3D texture slices, + // so the depth may be different in this case. + if (rhs.Target == Target.Texture3D && lhsSize.Depth != rhsSize.Depth) + { + result = TextureViewCompatibility.CopyOnly; + } + + // Some APIs align the width for copy and render target textures, + // so the width may not match in this case for different uses of the same texture. + // To account for this, we compare the aligned width here. + // We expect height to always match exactly, if the texture is the same. + if (alignedWidthMatches && lhsSize.Height == rhsSize.Height) + { + return (exact && lhsSize.Width != rhsSize.Width) || lhsSize.Width < rhsSize.Width + ? TextureViewCompatibility.CopyOnly + : result; + } + else if (lhs.IsLinear && rhs.IsLinear && lhsSize.Height == rhsSize.Height) + { + // Copy between linear textures with matching stride. + int stride = BitUtils.AlignUp(Math.Max(1, lhs.Stride >> level), Constants.StrideAlignment); + + return stride == rhs.Stride ? TextureViewCompatibility.CopyOnly : TextureViewCompatibility.LayoutIncompatible; + } + else if (lhs.Target.IsMultisample() != rhs.Target.IsMultisample() && alignedWidthMatches && lhsAlignedSize.Height == rhsAlignedSize.Height) + { + // Copy between multisample and non-multisample textures with mismatching size is allowed, + // as long aligned size matches. + + return TextureViewCompatibility.CopyOnly; + } + else + { + return TextureViewCompatibility.LayoutIncompatible; + } + } + + /// + /// Checks if the potential child texture fits within the level and layer bounds of the parent. + /// + /// Texture information for the parent + /// Texture information for the child + /// Base layer of the child texture + /// Base level of the child texture + /// Full compatiblity if the child's layer and level count fit within the parent, incompatible otherwise + public static TextureViewCompatibility ViewSubImagesInBounds(TextureInfo parent, TextureInfo child, int layer, int level) + { + if (level + child.Levels <= parent.Levels && + layer + child.GetSlices() <= parent.GetSlices()) + { + return TextureViewCompatibility.Full; + } + else + { + return TextureViewCompatibility.LayoutIncompatible; + } + } + + /// + /// Checks if the texture sizes of the supplied texture informations match. + /// + /// Texture information to compare + /// Texture information to compare with + /// Indicates if the size must be exactly equal between the textures, or if is allowed to be larger + /// True if the sizes matches, false otherwise + public static bool SizeMatches(TextureInfo lhs, TextureInfo rhs, bool exact) + { + if (lhs.GetLayers() != rhs.GetLayers()) + { + return false; + } + + Size lhsSize = GetSizeInBlocks(lhs); + Size rhsSize = GetSizeInBlocks(rhs); + + if (exact || lhs.IsLinear || rhs.IsLinear) + { + return lhsSize.Width == rhsSize.Width && + lhsSize.Height == rhsSize.Height && + lhsSize.Depth == rhsSize.Depth; + } + else + { + Size lhsAlignedSize = GetAlignedSize(lhs); + Size rhsAlignedSize = GetAlignedSize(rhs); + + return lhsAlignedSize.Width == rhsAlignedSize.Width && + lhsSize.Width >= rhsSize.Width && + lhsSize.Height == rhsSize.Height && + lhsSize.Depth == rhsSize.Depth; + } + } + + /// + /// Gets the aligned sizes for the given dimensions, using the specified texture information. + /// The alignment depends on the texture layout and format bytes per pixel. + /// + /// Texture information to calculate the aligned size from + /// The width to be aligned + /// The height to be aligned + /// The depth to be aligned + /// The aligned texture size + private static Size GetAlignedSize(TextureInfo info, int width, int height, int depth) + { + if (info.IsLinear) + { + return SizeCalculator.GetLinearAlignedSize( + width, + height, + info.FormatInfo.BlockWidth, + info.FormatInfo.BlockHeight, + info.FormatInfo.BytesPerPixel); + } + else + { + return SizeCalculator.GetBlockLinearAlignedSize( + width, + height, + depth, + info.FormatInfo.BlockWidth, + info.FormatInfo.BlockHeight, + info.FormatInfo.BytesPerPixel, + info.GobBlocksInY, + info.GobBlocksInZ, + info.GobBlocksInTileX); + } + } + + /// + /// Gets the aligned sizes of the specified texture information. + /// The alignment depends on the texture layout and format bytes per pixel. + /// + /// Texture information to calculate the aligned size from + /// Mipmap level for texture views + /// The aligned texture size + public static Size GetAlignedSize(TextureInfo info, int level = 0) + { + int width = Math.Max(1, info.Width >> level); + int height = Math.Max(1, info.Height >> level); + int depth = Math.Max(1, info.GetDepth() >> level); + + return GetAlignedSize(info, width, height, depth); + } + + /// + /// Gets the size in blocks for the given texture information. + /// For non-compressed formats, that's the same as the regular size. + /// + /// Texture information to calculate the aligned size from + /// Mipmap level for texture views + /// The texture size in blocks + public static Size GetSizeInBlocks(TextureInfo info, int level = 0) + { + int width = Math.Max(1, info.Width >> level); + int height = Math.Max(1, info.Height >> level); + int depth = Math.Max(1, info.GetDepth() >> level); + + return new Size( + BitUtils.DivRoundUp(width, info.FormatInfo.BlockWidth), + BitUtils.DivRoundUp(height, info.FormatInfo.BlockHeight), + depth); + } + + /// + /// Check if it's possible to create a view with the layout of the second texture information from the first. + /// The layout information is composed of the Stride for linear textures, or GOB block size + /// for block linear textures. + /// + /// Texture information of the texture view + /// Texture information of the texture view to compare against + /// Start level of the texture view, in relation with the first texture + /// True if the layout is compatible, false otherwise + public static bool ViewLayoutCompatible(TextureInfo lhs, TextureInfo rhs, int level) + { + if (lhs.IsLinear != rhs.IsLinear) + { + return false; + } + + // For linear textures, gob block sizes are ignored. + // For block linear textures, the stride is ignored. + if (rhs.IsLinear) + { + int stride = Math.Max(1, lhs.Stride >> level); + stride = BitUtils.AlignUp(stride, Constants.StrideAlignment); + + return stride == rhs.Stride; + } + else + { + int height = Math.Max(1, lhs.Height >> level); + int depth = Math.Max(1, lhs.GetDepth() >> level); + + (int gobBlocksInY, int gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes( + height, + depth, + lhs.FormatInfo.BlockHeight, + lhs.GobBlocksInY, + lhs.GobBlocksInZ, + level); + + return gobBlocksInY == rhs.GobBlocksInY && + gobBlocksInZ == rhs.GobBlocksInZ; + } + } + + /// + /// Check if it's possible to create a view with the layout of the second texture information from the first. + /// The layout information is composed of the Stride for linear textures, or GOB block size + /// for block linear textures. + /// + /// Texture information of the texture view + /// Texture information of the texture view to compare against + /// Start level of the texture view, in relation with the first texture + /// Start level of the texture view, in relation with the second texture + /// True if the layout is compatible, false otherwise + public static bool ViewLayoutCompatible(TextureInfo lhs, TextureInfo rhs, int lhsLevel, int rhsLevel) + { + if (lhs.IsLinear != rhs.IsLinear) + { + return false; + } + + // For linear textures, gob block sizes are ignored. + // For block linear textures, the stride is ignored. + if (rhs.IsLinear) + { + int lhsStride = Math.Max(1, lhs.Stride >> lhsLevel); + lhsStride = BitUtils.AlignUp(lhsStride, Constants.StrideAlignment); + + int rhsStride = Math.Max(1, rhs.Stride >> rhsLevel); + rhsStride = BitUtils.AlignUp(rhsStride, Constants.StrideAlignment); + + return lhsStride == rhsStride; + } + else + { + int lhsHeight = Math.Max(1, lhs.Height >> lhsLevel); + int lhsDepth = Math.Max(1, lhs.GetDepth() >> lhsLevel); + + (int lhsGobBlocksInY, int lhsGobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes( + lhsHeight, + lhsDepth, + lhs.FormatInfo.BlockHeight, + lhs.GobBlocksInY, + lhs.GobBlocksInZ, + lhsLevel); + + int rhsHeight = Math.Max(1, rhs.Height >> rhsLevel); + int rhsDepth = Math.Max(1, rhs.GetDepth() >> rhsLevel); + + (int rhsGobBlocksInY, int rhsGobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes( + rhsHeight, + rhsDepth, + rhs.FormatInfo.BlockHeight, + rhs.GobBlocksInY, + rhs.GobBlocksInZ, + rhsLevel); + + return lhsGobBlocksInY == rhsGobBlocksInY && + lhsGobBlocksInZ == rhsGobBlocksInZ; + } + } + + /// + /// Checks if the view format of the first texture format is compatible with the format of the second. + /// In general, the formats are considered compatible if the bytes per pixel values are equal, + /// but there are more complex rules for some formats, like compressed or depth-stencil formats. + /// This follows the host API copy compatibility rules. + /// + /// Texture information of the texture view + /// Texture information of the texture view + /// Host GPU capabilities + /// Texture search flags + /// The view compatibility level of the texture formats + public static TextureViewCompatibility ViewFormatCompatible(TextureInfo lhs, TextureInfo rhs, Capabilities caps, TextureSearchFlags flags) + { + FormatInfo lhsFormat = lhs.FormatInfo; + FormatInfo rhsFormat = rhs.FormatInfo; + + if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil()) + { + bool forSampler = flags.HasFlag(TextureSearchFlags.ForSampler); + bool depthAlias = flags.HasFlag(TextureSearchFlags.DepthAlias); + + TextureMatchQuality matchQuality = FormatMatches(lhs, rhs, forSampler, depthAlias); + + if (matchQuality == TextureMatchQuality.Perfect) + { + return TextureViewCompatibility.Full; + } + else if (matchQuality == TextureMatchQuality.FormatAlias) + { + return TextureViewCompatibility.FormatAlias; + } + else if (IsValidColorAsDepthAlias(lhsFormat.Format, rhsFormat.Format) || IsValidDepthAsColorAlias(lhsFormat.Format, rhsFormat.Format)) + { + return TextureViewCompatibility.CopyOnly; + } + else + { + return TextureViewCompatibility.Incompatible; + } + } + + if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps)) + { + return lhsFormat.Format == rhsFormat.Format ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible; + } + + if (lhsFormat.IsCompressed && rhsFormat.IsCompressed) + { + FormatClass lhsClass = GetFormatClass(lhsFormat.Format); + FormatClass rhsClass = GetFormatClass(rhsFormat.Format); + + return lhsClass == rhsClass ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible; + } + else if (lhsFormat.BytesPerPixel == rhsFormat.BytesPerPixel) + { + return lhs.FormatInfo.IsCompressed == rhs.FormatInfo.IsCompressed + ? TextureViewCompatibility.Full + : TextureViewCompatibility.CopyOnly; + } + else if (IsIncompatibleFormatAliasingAllowed(lhsFormat, rhsFormat)) + { + return TextureViewCompatibility.CopyOnly; + } + + return TextureViewCompatibility.Incompatible; + } + + /// + /// Checks if it's valid to alias a color format as a depth format. + /// + /// Source format to be checked + /// Target format to be checked + /// True if it's valid to alias the formats + private static bool IsValidColorAsDepthAlias(Format lhsFormat, Format rhsFormat) + { + return (lhsFormat == Format.R32Float && rhsFormat == Format.D32Float) || + (lhsFormat == Format.R16Unorm && rhsFormat == Format.D16Unorm); + } + + /// + /// Checks if it's valid to alias a depth format as a color format. + /// + /// Source format to be checked + /// Target format to be checked + /// True if it's valid to alias the formats + private static bool IsValidDepthAsColorAlias(Format lhsFormat, Format rhsFormat) + { + return (lhsFormat == Format.D32Float && rhsFormat == Format.R32Float) || + (lhsFormat == Format.D16Unorm && rhsFormat == Format.R16Unorm); + } + + /// + /// Checks if aliasing of two formats that would normally be considered incompatible be allowed, + /// using copy dependencies. + /// + /// Format information of the first textureFormat information of the second texture + /// True if aliasing should be allowed, false otherwise + private static bool IsIncompatibleFormatAliasingAllowed(FormatInfo lhsFormat, FormatInfo rhsFormat) + { + // Some games will try to alias textures with incompatible foramts, with different BPP (bytes per pixel). + // We allow that in some cases as long Width * BPP is equal on both textures. + // This is very conservative right now as we want to avoid copies as much as possible, + // so we only consider the formats we have seen being aliased. + + if (rhsFormat.BytesPerPixel < lhsFormat.BytesPerPixel) + { + (lhsFormat, rhsFormat) = (rhsFormat, lhsFormat); + } + + return (lhsFormat.Format == Format.R8G8B8A8Unorm && rhsFormat.Format == Format.R32G32B32A32Float) || + (lhsFormat.Format == Format.R8Unorm && rhsFormat.Format == Format.R8G8B8A8Unorm); + } + + /// + /// Check if the target of the first texture view information is compatible with the target of the second texture view information. + /// This follows the host API target compatibility rules. + /// + /// Texture information of the texture viewTexture information of the texture view + /// Host GPU capabilities + /// True if the targets are compatible, false otherwise + public static TextureViewCompatibility ViewTargetCompatible(TextureInfo lhs, TextureInfo rhs, ref Capabilities caps) + { + bool result = false; + switch (lhs.Target) + { + case Target.Texture1D: + case Target.Texture1DArray: + result = rhs.Target == Target.Texture1D || + rhs.Target == Target.Texture1DArray; + break; + + case Target.Texture2D: + result = rhs.Target == Target.Texture2D || + rhs.Target == Target.Texture2DArray; + break; + + case Target.Texture2DArray: + result = rhs.Target == Target.Texture2D || + rhs.Target == Target.Texture2DArray; + + if (rhs.Target == Target.Cubemap || rhs.Target == Target.CubemapArray) + { + return caps.SupportsCubemapView ? TextureViewCompatibility.Full : TextureViewCompatibility.CopyOnly; + } + break; + case Target.Cubemap: + case Target.CubemapArray: + result = rhs.Target == Target.Cubemap || + rhs.Target == Target.CubemapArray; + + if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray) + { + return caps.SupportsCubemapView ? TextureViewCompatibility.Full : TextureViewCompatibility.CopyOnly; + } + break; + case Target.Texture2DMultisample: + case Target.Texture2DMultisampleArray: + if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray) + { + return TextureViewCompatibility.CopyOnly; + } + + result = rhs.Target == Target.Texture2DMultisample || + rhs.Target == Target.Texture2DMultisampleArray; + break; + + case Target.Texture3D: + if (rhs.Target == Target.Texture2D) + { + return TextureViewCompatibility.CopyOnly; + } + + result = rhs.Target == Target.Texture3D; + break; + } + + return result ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible; + } + + /// + /// Checks if the texture shader sampling parameters of two texture informations match. + /// + /// Texture information to compare + /// Texture information to compare with + /// True if the texture shader sampling parameters matches, false otherwise + public static bool SamplerParamsMatches(TextureInfo lhs, TextureInfo rhs) + { + return lhs.DepthStencilMode == rhs.DepthStencilMode && + lhs.SwizzleR == rhs.SwizzleR && + lhs.SwizzleG == rhs.SwizzleG && + lhs.SwizzleB == rhs.SwizzleB && + lhs.SwizzleA == rhs.SwizzleA; + } + + /// + /// Check if the texture target and samples count (for multisampled textures) matches. + /// + /// Texture information to compare with + /// Texture information to compare with + /// True if the texture target and samples count matches, false otherwise + public static bool TargetAndSamplesCompatible(TextureInfo lhs, TextureInfo rhs) + { + return lhs.Target == rhs.Target && + lhs.SamplesInX == rhs.SamplesInX && + lhs.SamplesInY == rhs.SamplesInY; + } + + /// + /// Gets the texture format class, for compressed textures, or Unclassified otherwise. + /// + /// The format + /// Format class + private static FormatClass GetFormatClass(Format format) + { + return format switch + { + Format.Bc1RgbaSrgb or Format.Bc1RgbaUnorm => FormatClass.Bc1Rgba, + Format.Bc2Srgb or Format.Bc2Unorm => FormatClass.Bc2, + Format.Bc3Srgb or Format.Bc3Unorm => FormatClass.Bc3, + Format.Bc4Snorm or Format.Bc4Unorm => FormatClass.Bc4, + Format.Bc5Snorm or Format.Bc5Unorm => FormatClass.Bc5, + Format.Bc6HSfloat or Format.Bc6HUfloat => FormatClass.Bc6, + Format.Bc7Srgb or Format.Bc7Unorm => FormatClass.Bc7, + Format.Etc2RgbSrgb or Format.Etc2RgbUnorm => FormatClass.Etc2Rgb, + Format.Etc2RgbaSrgb or Format.Etc2RgbaUnorm => FormatClass.Etc2Rgba, + Format.Astc4x4Srgb or Format.Astc4x4Unorm => FormatClass.Astc4x4, + Format.Astc5x4Srgb or Format.Astc5x4Unorm => FormatClass.Astc5x4, + Format.Astc5x5Srgb or Format.Astc5x5Unorm => FormatClass.Astc5x5, + Format.Astc6x5Srgb or Format.Astc6x5Unorm => FormatClass.Astc6x5, + Format.Astc6x6Srgb or Format.Astc6x6Unorm => FormatClass.Astc6x6, + Format.Astc8x5Srgb or Format.Astc8x5Unorm => FormatClass.Astc8x5, + Format.Astc8x6Srgb or Format.Astc8x6Unorm => FormatClass.Astc8x6, + Format.Astc8x8Srgb or Format.Astc8x8Unorm => FormatClass.Astc8x8, + Format.Astc10x5Srgb or Format.Astc10x5Unorm => FormatClass.Astc10x5, + Format.Astc10x6Srgb or Format.Astc10x6Unorm => FormatClass.Astc10x6, + Format.Astc10x8Srgb or Format.Astc10x8Unorm => FormatClass.Astc10x8, + Format.Astc10x10Srgb or Format.Astc10x10Unorm => FormatClass.Astc10x10, + Format.Astc12x10Srgb or Format.Astc12x10Unorm => FormatClass.Astc12x10, + Format.Astc12x12Srgb or Format.Astc12x12Unorm => FormatClass.Astc12x12, + _ => FormatClass.Unclassified, + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs new file mode 100644 index 00000000..172d11a8 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs @@ -0,0 +1,40 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture swizzle color component. + /// + enum TextureComponent + { + Zero = 0, + Red = 2, + Green = 3, + Blue = 4, + Alpha = 5, + OneSI = 6, + OneF = 7, + } + + static class TextureComponentConverter + { + /// + /// Converts the texture swizzle color component enum to the respective Graphics Abstraction Layer enum. + /// + /// Texture swizzle color component + /// Converted enum + public static SwizzleComponent Convert(this TextureComponent component) + { + return component switch + { + TextureComponent.Zero => SwizzleComponent.Zero, + TextureComponent.Red => SwizzleComponent.Red, + TextureComponent.Green => SwizzleComponent.Green, + TextureComponent.Blue => SwizzleComponent.Blue, + TextureComponent.Alpha => SwizzleComponent.Alpha, + TextureComponent.OneSI or TextureComponent.OneF => SwizzleComponent.One, + _ => SwizzleComponent.Zero, + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs new file mode 100644 index 00000000..45a5a397 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// One side of a two-way dependency between one texture view and another. + /// Contains a reference to the handle owning the dependency, and the other dependency. + /// + class TextureDependency + { + /// + /// The handle that owns this dependency. + /// + public TextureGroupHandle Handle; + + /// + /// The other dependency linked to this one, which belongs to another handle. + /// + public TextureDependency Other; + + /// + /// Create a new texture dependency. + /// + /// The handle that owns the dependency + public TextureDependency(TextureGroupHandle handle) + { + Handle = handle; + } + + /// + /// Signal that the owner of this dependency has been modified, + /// meaning that the other dependency's handle must defer a copy from it. + /// + public void SignalModified() + { + Other.Handle.DeferCopy(Handle); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs new file mode 100644 index 00000000..c82a555e --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs @@ -0,0 +1,278 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Maxwell texture descriptor, as stored on the GPU texture pool memory region. + /// + struct TextureDescriptor : ITextureDescriptor, IEquatable + { +#pragma warning disable CS0649 // Field is never assigned to + public uint Word0; + public uint Word1; + public uint Word2; + public uint Word3; + public uint Word4; + public uint Word5; + public uint Word6; + public uint Word7; +#pragma warning restore CS0649 + + /// + /// Unpacks Maxwell texture format integer. + /// + /// The texture format integer + public readonly uint UnpackFormat() + { + return Word0 & 0x8007ffff; + } + + /// + /// Unpacks the swizzle component for the texture red color channel. + /// + /// The swizzle component + public readonly TextureComponent UnpackSwizzleR() + { + return (TextureComponent)((Word0 >> 19) & 7); + } + + /// + /// Unpacks the swizzle component for the texture green color channel. + /// + /// The swizzle component + public readonly TextureComponent UnpackSwizzleG() + { + return (TextureComponent)((Word0 >> 22) & 7); + } + + /// + /// Unpacks the swizzle component for the texture blue color channel. + /// + /// The swizzle component + public readonly TextureComponent UnpackSwizzleB() + { + return (TextureComponent)((Word0 >> 25) & 7); + } + + /// + /// Unpacks the swizzle component for the texture alpha color channel. + /// + /// The swizzle component + public readonly TextureComponent UnpackSwizzleA() + { + return (TextureComponent)((Word0 >> 28) & 7); + } + + /// + /// Unpacks the 40-bits texture GPU virtual address. + /// + /// The GPU virtual address + public readonly ulong UnpackAddress() + { + return Word1 | ((ulong)(Word2 & 0xffff) << 32); + } + + /// + /// Unpacks texture descriptor type for this texture descriptor. + /// This defines the texture layout, among other things. + /// + /// The texture descriptor type + public readonly TextureDescriptorType UnpackTextureDescriptorType() + { + return (TextureDescriptorType)((Word2 >> 21) & 7); + } + + /// + /// Unpacks the texture stride (bytes per line) for linear textures only. + /// Always 32-bytes aligned. + /// + /// The linear texture stride + public readonly int UnpackStride() + { + return (int)(Word3 & 0xffff) << 5; + } + + /// + /// Unpacks the GOB block size in X (width) for block linear textures. + /// Must be always 1, ignored by the GPU. + /// + /// THe GOB block X size + public readonly int UnpackGobBlocksInX() + { + return 1 << (int)(Word3 & 7); + } + + /// + /// Unpacks the GOB block size in Y (height) for block linear textures. + /// Must be always a power of 2, with a maximum value of 32. + /// + /// THe GOB block Y size + public readonly int UnpackGobBlocksInY() + { + return 1 << (int)((Word3 >> 3) & 7); + } + + /// + /// Unpacks the GOB block size in Z (depth) for block linear textures. + /// Must be always a power of 2, with a maximum value of 32. + /// Must be 1 for any texture target other than 3D textures. + /// + /// The GOB block Z size + public readonly int UnpackGobBlocksInZ() + { + return 1 << (int)((Word3 >> 6) & 7); + } + + /// + /// Number of GOB blocks per tile in the X direction. + /// This is only used for sparse textures, should be 1 otherwise. + /// + /// The number of GOB blocks per tile + public readonly int UnpackGobBlocksInTileX() + { + return 1 << (int)((Word3 >> 10) & 7); + } + + /// + /// Unpacks the number of mipmap levels of the texture. + /// + /// The number of mipmap levels + public readonly int UnpackLevels() + { + return (int)(Word3 >> 28) + 1; + } + + /// + /// Unpack the base level texture width size. + /// + /// The texture width + public readonly int UnpackWidth() + { + return (int)(Word4 & 0xffff) + 1; + } + + /// + /// Unpack the width of a buffer texture. + /// + /// The texture width + public readonly int UnpackBufferTextureWidth() + { + return (int)((Word4 & 0xffff) | (Word3 << 16)) + 1; + } + + /// + /// Unpacks the texture sRGB format flag. + /// + /// True if the texture is sRGB, false otherwise + public readonly bool UnpackSrgb() + { + return (Word4 & (1 << 22)) != 0; + } + + /// + /// Unpacks the texture target. + /// + /// The texture target + public readonly TextureTarget UnpackTextureTarget() + { + return (TextureTarget)((Word4 >> 23) & 0xf); + } + + /// + /// Unpack the base level texture height size, or array layers for 1D array textures. + /// Should be ignored for 1D or buffer textures. + /// + /// The texture height or layers count + public readonly int UnpackHeight() + { + return (int)(Word5 & 0xffff) + 1; + } + + /// + /// Unpack the base level texture depth size, number of array layers or cubemap faces. + /// The meaning of this value depends on the texture target. + /// + /// The texture depth, layer or faces count + public readonly int UnpackDepth() + { + return (int)((Word5 >> 16) & 0x3fff) + 1; + } + + /// + /// Unpacks the texture coordinates normalized flag. + /// When this is true, texture coordinates are expected to be in the [0, 1] range on the shader. + /// When this is false, texture coordinates are expected to be in the [0, W], [0, H] and [0, D] range. + /// It must be set to false (by the guest driver) for rectangle textures. + /// + /// The texture coordinates normalized flag + public readonly bool UnpackTextureCoordNormalized() + { + return (Word5 & (1 << 31)) != 0; + } + + /// + /// Unpacks the base mipmap level of the texture. + /// + /// The base mipmap level of the texture + public readonly int UnpackBaseLevel() + { + return (int)(Word7 & 0xf); + } + + /// + /// Unpacks the maximum mipmap level (inclusive) of the texture. + /// Usually equal to Levels minus 1. + /// + /// The maximum mipmap level (inclusive) of the texture + public readonly int UnpackMaxLevelInclusive() + { + return (int)((Word7 >> 4) & 0xf); + } + + /// + /// Unpacks the multisampled texture samples count in each direction. + /// Must be ignored for non-multisample textures. + /// + /// The multisample counts enum + public readonly TextureMsaaMode UnpackTextureMsaaMode() + { + return (TextureMsaaMode)((Word7 >> 8) & 0xf); + } + + /// + /// Check if two descriptors are equal. + /// + /// The descriptor to compare against + /// True if they are equal, false otherwise + public bool Equals(ref TextureDescriptor other) + { + return Unsafe.As>(ref this).Equals(Unsafe.As>(ref other)); + } + + /// + /// Check if two descriptors are equal. + /// + /// The descriptor to compare against + /// True if they are equal, false otherwise + public bool Equals(TextureDescriptor other) + { + return Equals(ref other); + } + + /// + /// Gets a hash code for this descriptor. + /// + /// The hash code for this descriptor. + public override int GetHashCode() + { + return Unsafe.As>(ref this).GetHashCode(); + } + + public override bool Equals(object obj) + { + return obj is TextureDescriptor descriptor && Equals(descriptor); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs new file mode 100644 index 00000000..ad0715c5 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// The texture descriptor type. + /// This specifies the texture memory layout. + /// The texture descriptor structure depends on the type. + /// + enum TextureDescriptorType + { + Buffer, + LinearColorKey, + Linear, + BlockLinear, + BlockLinearColorKey, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs new file mode 100644 index 00000000..06ca2c59 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -0,0 +1,1703 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Texture; +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using Ryujinx.Memory.Tracking; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// An overlapping texture group with a given view compatibility. + /// + readonly struct TextureIncompatibleOverlap + { + public readonly TextureGroup Group; + public readonly TextureViewCompatibility Compatibility; + + /// + /// Create a new texture incompatible overlap. + /// + /// The group that is incompatible + /// The view compatibility for the group + public TextureIncompatibleOverlap(TextureGroup group, TextureViewCompatibility compatibility) + { + Group = group; + Compatibility = compatibility; + } + } + + /// + /// A texture group represents a group of textures that belong to the same storage. + /// When views are created, this class will track memory accesses for them separately. + /// The group iteratively adds more granular tracking as views of different kinds are added. + /// Note that a texture group can be absorbed into another when it becomes a view parent. + /// + class TextureGroup : IDisposable + { + /// + /// Threshold of layers to force granular handles (and thus partial loading) on array/3D textures. + /// + private const int GranularLayerThreshold = 8; + + private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false); + + /// + /// The storage texture associated with this group. + /// + public Texture Storage { get; } + + /// + /// Indicates if the texture has copy dependencies. If true, then all modifications + /// must be signalled to the group, rather than skipping ones still to be flushed. + /// + public bool HasCopyDependencies { get; set; } + + /// + /// Indicates if the texture group has a pre-emptive flush buffer. + /// When one is present, the group must always be notified on unbind. + /// + public bool HasFlushBuffer => _flushBuffer != BufferHandle.Null; + + /// + /// Indicates if this texture has any incompatible overlaps alive. + /// + public bool HasIncompatibleOverlaps => _incompatibleOverlaps.Count > 0; + + /// + /// Number indicating the order this texture group was modified relative to others. + /// + public long ModifiedSequence { get; private set; } + + private readonly GpuContext _context; + private readonly PhysicalMemory _physicalMemory; + + private int[] _allOffsets; + private int[] _sliceSizes; + private readonly bool _is3D; + private readonly bool _isBuffer; + private bool _hasMipViews; + private bool _hasLayerViews; + private readonly int _layers; + private readonly int _levels; + + private MultiRange TextureRange => Storage.Range; + + /// + /// The views array from the storage texture. + /// + private Texture[] _views; + private TextureGroupHandle[] _handles; + private bool[] _loadNeeded; + + /// + /// Other texture groups that have incompatible overlaps with this one. + /// + private readonly List _incompatibleOverlaps; + private bool _incompatibleOverlapsDirty = true; + private readonly bool _flushIncompatibleOverlaps; + + private BufferHandle _flushBuffer; + private bool _flushBufferImported; + private bool _flushBufferInvalid; + + /// + /// Create a new texture group. + /// + /// GPU context that the texture group belongs to + /// Physical memory where the texture is mapped + /// The storage texture for this group + /// Groups that overlap with this one but are incompatible + public TextureGroup(GpuContext context, PhysicalMemory physicalMemory, Texture storage, List incompatibleOverlaps) + { + Storage = storage; + _context = context; + _physicalMemory = physicalMemory; + + _is3D = storage.Info.Target == Target.Texture3D; + _isBuffer = storage.Info.Target == Target.TextureBuffer; + _layers = storage.Info.GetSlices(); + _levels = storage.Info.Levels; + + _incompatibleOverlaps = incompatibleOverlaps; + _flushIncompatibleOverlaps = TextureCompatibility.IsFormatHostIncompatible(storage.Info, context.Capabilities); + } + + /// + /// Initialize a new texture group's dirty regions and offsets. + /// + /// Size info for the storage texture + /// True if the storage will have layer views + /// True if the storage will have mip views + public void Initialize(ref SizeInfo size, bool hasLayerViews, bool hasMipViews) + { + _allOffsets = size.AllOffsets; + _sliceSizes = size.SliceSizes; + + if (Storage.Target.HasDepthOrLayers() && Storage.Info.GetSlices() > GranularLayerThreshold) + { + _hasLayerViews = true; + _hasMipViews = true; + } + else + { + (_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews); + + // If the texture is partially mapped, fully subdivide handles immediately. + + MultiRange range = Storage.Range; + for (int i = 0; i < range.Count; i++) + { + if (range.GetSubRange(i).Address == MemoryManager.PteUnmapped) + { + _hasLayerViews = true; + _hasMipViews = true; + + break; + } + } + } + + RecalculateHandleRegions(); + } + + /// + /// Initialize all incompatible overlaps in the list, registering them with the other texture groups + /// and creating copy dependencies when partially compatible. + /// + public void InitializeOverlaps() + { + foreach (TextureIncompatibleOverlap overlap in _incompatibleOverlaps) + { + if (overlap.Compatibility == TextureViewCompatibility.LayoutIncompatible) + { + CreateCopyDependency(overlap.Group, false); + } + + overlap.Group._incompatibleOverlaps.Add(new TextureIncompatibleOverlap(this, overlap.Compatibility)); + overlap.Group._incompatibleOverlapsDirty = true; + } + + if (_incompatibleOverlaps.Count > 0) + { + SignalIncompatibleOverlapModified(); + } + } + + /// + /// Signal that the group is dirty to all views and the storage. + /// + private void SignalAllDirty() + { + Storage.SignalGroupDirty(); + if (_views != null) + { + foreach (Texture texture in _views) + { + texture.SignalGroupDirty(); + } + } + } + + /// + /// Signal that an incompatible overlap has been modified. + /// If this group must flush incompatible overlaps, the group is signalled as dirty too. + /// + private void SignalIncompatibleOverlapModified() + { + _incompatibleOverlapsDirty = true; + + if (_flushIncompatibleOverlaps) + { + SignalAllDirty(); + } + } + + + /// + /// Flushes incompatible overlaps if the storage format requires it, and they have been modified. + /// This allows unsupported host formats to accept data written to format aliased textures. + /// + /// True if data was flushed, false otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool FlushIncompatibleOverlapsIfNeeded() + { + if (_flushIncompatibleOverlaps && _incompatibleOverlapsDirty) + { + bool flushed = false; + + foreach (var overlap in _incompatibleOverlaps) + { + flushed |= overlap.Group.Storage.FlushModified(true); + } + + _incompatibleOverlapsDirty = false; + + return flushed; + } + else + { + return false; + } + } + + /// + /// Check and optionally consume the dirty flags for a given texture. + /// The state is shared between views of the same layers and levels. + /// + /// The texture being used + /// True to consume the dirty flags and reprotect, false to leave them as is + /// True if a flag was dirty, false otherwise + public bool CheckDirty(Texture texture, bool consume) + { + bool dirty = false; + + EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + { + for (int i = 0; i < regionCount; i++) + { + TextureGroupHandle group = _handles[baseHandle + i]; + + foreach (RegionHandle handle in group.Handles) + { + if (handle.Dirty) + { + if (consume) + { + handle.Reprotect(); + } + + dirty = true; + } + } + } + }); + + return dirty; + } + + /// + /// Discards all data for a given texture. + /// This clears all dirty flags and pending copies from other textures. + /// + /// The texture being discarded + public void DiscardData(Texture texture) + { + EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + { + for (int i = 0; i < regionCount; i++) + { + TextureGroupHandle group = _handles[baseHandle + i]; + + group.DiscardData(); + } + }); + } + + /// + /// Synchronize memory for a given texture. + /// If overlapping tracking handles are dirty, fully or partially synchronize the texture data. + /// + /// The texture being used + public void SynchronizeMemory(Texture texture) + { + FlushIncompatibleOverlapsIfNeeded(); + + EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + { + bool dirty = false; + bool anyModified = false; + bool anyNotDirty = false; + + for (int i = 0; i < regionCount; i++) + { + TextureGroupHandle group = _handles[baseHandle + i]; + + bool modified = group.Modified; + bool handleDirty = false; + bool handleUnmapped = false; + + foreach (RegionHandle handle in group.Handles) + { + if (handle.Dirty) + { + handle.Reprotect(); + handleDirty = true; + } + else + { + handleUnmapped |= handle.Unmapped; + } + } + + // If the modified flag is still present, prefer the data written from gpu. + // A write from CPU will do a flush before writing its data, which should unset this. + if (modified) + { + handleDirty = false; + } + + // Evaluate if any copy dependencies need to be fulfilled. A few rules: + // If the copy handle needs to be synchronized, prefer our own state. + // If we need to be synchronized and there is a copy present, prefer the copy. + + if (group.NeedsCopy && group.Copy(_context)) + { + anyModified |= true; // The copy target has been modified. + handleDirty = false; + } + else + { + anyModified |= modified; + dirty |= handleDirty; + } + + if (group.NeedsCopy) + { + // The texture we copied from is still being written to. Copy from it again the next time this texture is used. + texture.SignalGroupDirty(); + } + + bool loadNeeded = handleDirty && !handleUnmapped; + + anyNotDirty |= !loadNeeded; + _loadNeeded[baseHandle + i] = loadNeeded; + } + + if (dirty) + { + if (anyNotDirty || (_handles.Length > 1 && (anyModified || split))) + { + // Partial texture invalidation. Only update the layers/levels with dirty flags of the storage. + + SynchronizePartial(baseHandle, regionCount); + } + else + { + // Full texture invalidation. + + texture.SynchronizeFull(); + } + } + }); + } + + /// + /// Synchronize part of the storage texture, represented by a given range of handles. + /// Only handles marked by the _loadNeeded array will be synchronized. + /// + /// The base index of the range of handles + /// The number of handles to synchronize + private void SynchronizePartial(int baseHandle, int regionCount) + { + int spanEndIndex = -1; + int spanBase = 0; + ReadOnlySpan dataSpan = ReadOnlySpan.Empty; + + for (int i = 0; i < regionCount; i++) + { + if (_loadNeeded[baseHandle + i]) + { + var info = GetHandleInformation(baseHandle + i); + + // Ensure the data for this handle is loaded in the span. + if (spanEndIndex <= i - 1) + { + spanEndIndex = i; + + if (_is3D) + { + // Look ahead to see how many handles need to be loaded. + for (int j = i + 1; j < regionCount; j++) + { + if (_loadNeeded[baseHandle + j]) + { + spanEndIndex = j; + } + else + { + break; + } + } + } + + var endInfo = spanEndIndex == i ? info : GetHandleInformation(baseHandle + spanEndIndex); + + spanBase = _allOffsets[info.Index]; + int spanLast = _allOffsets[endInfo.Index + endInfo.Layers * endInfo.Levels - 1]; + int endOffset = Math.Min(spanLast + _sliceSizes[endInfo.BaseLevel + endInfo.Levels - 1], (int)Storage.Size); + int size = endOffset - spanBase; + + dataSpan = _physicalMemory.GetSpan(Storage.Range.Slice((ulong)spanBase, (ulong)size)); + } + + // Only one of these will be greater than 1, as partial sync is only called when there are sub-image views. + for (int layer = 0; layer < info.Layers; layer++) + { + for (int level = 0; level < info.Levels; level++) + { + int offsetIndex = GetOffsetIndex(info.BaseLayer + layer, info.BaseLevel + level); + int offset = _allOffsets[offsetIndex]; + + ReadOnlySpan data = dataSpan[(offset - spanBase)..]; + + IMemoryOwner result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true); + + Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level); + } + } + } + } + } + + /// + /// Synchronize dependent textures, if any of them have deferred a copy from the given texture. + /// + /// The texture to synchronize dependents of + public void SynchronizeDependents(Texture texture) + { + EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + { + for (int i = 0; i < regionCount; i++) + { + TextureGroupHandle group = _handles[baseHandle + i]; + + group.SynchronizeDependents(); + } + }); + } + + /// + /// Determines whether flushes in this texture group should be tracked. + /// Incompatible overlaps may need data from this texture to flush tracked for it to be visible to them. + /// + /// True if flushes should be tracked, false otherwise + private bool ShouldFlushTriggerTracking() + { + foreach (var overlap in _incompatibleOverlaps) + { + if (overlap.Group._flushIncompatibleOverlaps) + { + return true; + } + } + + return false; + } + + /// + /// Gets data from the host GPU, and flushes a slice to guest memory. + /// + /// + /// This method should be used to retrieve data that was modified by the host GPU. + /// This is not cheap, avoid doing that unless strictly needed. + /// When possible, the data is written directly into guest memory, rather than copied. + /// + /// True if writing the texture data is tracked, false otherwise + /// The index of the slice to flush + /// Whether the flushed texture data is up to date in the flush buffer + /// The specific host texture to flush. Defaults to the storage texture + private void FlushTextureDataSliceToGuest(bool tracked, int sliceIndex, bool inBuffer, ITexture texture = null) + { + (int layer, int level) = GetLayerLevelForView(sliceIndex); + + int offset = _allOffsets[sliceIndex]; + int endOffset = Math.Min(offset + _sliceSizes[level], (int)Storage.Size); + int size = endOffset - offset; + + using WritableRegion region = _physicalMemory.GetWritableRegion(Storage.Range.Slice((ulong)offset, (ulong)size), tracked); + + if (inBuffer) + { + using PinnedSpan data = _context.Renderer.GetBufferData(_flushBuffer, offset, size); + + Storage.ConvertFromHostCompatibleFormat(region.Memory.Span, data.Get(), level, true); + } + else + { + Storage.GetTextureDataSliceFromGpu(region.Memory.Span, layer, level, tracked, texture); + } + } + + /// + /// Gets and flushes a number of slices of the storage texture to guest memory. + /// + /// True if writing the texture data is tracked, false otherwise + /// The first slice to flush + /// The slice to finish flushing on (exclusive) + /// Whether the flushed texture data is up to date in the flush buffer + /// The specific host texture to flush. Defaults to the storage texture + private void FlushSliceRange(bool tracked, int sliceStart, int sliceEnd, bool inBuffer, ITexture texture = null) + { + for (int i = sliceStart; i < sliceEnd; i++) + { + FlushTextureDataSliceToGuest(tracked, i, inBuffer, texture); + } + } + + /// + /// Flush modified ranges for a given texture. + /// + /// The texture being used + /// True if the flush writes should be tracked, false otherwise + /// True if data was flushed, false otherwise + public bool FlushModified(Texture texture, bool tracked) + { + tracked = tracked || ShouldFlushTriggerTracking(); + bool flushed = false; + + EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + { + int startSlice = 0; + int endSlice = 0; + bool allModified = true; + + for (int i = 0; i < regionCount; i++) + { + TextureGroupHandle group = _handles[baseHandle + i]; + + if (group.Modified) + { + if (endSlice < group.BaseSlice) + { + if (endSlice > startSlice) + { + FlushSliceRange(tracked, startSlice, endSlice, false); + flushed = true; + } + + startSlice = group.BaseSlice; + } + + endSlice = group.BaseSlice + group.SliceCount; + + if (tracked) + { + group.Modified = false; + + foreach (Texture texture in group.Overlaps) + { + texture.SignalModifiedDirty(); + } + } + } + else + { + allModified = false; + } + } + + if (endSlice > startSlice) + { + if (allModified && !split) + { + texture.Flush(tracked); + } + else + { + FlushSliceRange(tracked, startSlice, endSlice, false); + } + + flushed = true; + } + }); + + Storage.SignalModifiedDirty(); + + return flushed; + } + + /// + /// Flush the texture data into a persistently mapped buffer. + /// If the buffer does not exist, this method will create it. + /// + /// Handle of the texture group to flush slices of + public void FlushIntoBuffer(TextureGroupHandle handle) + { + // Ensure that the buffer exists. + + if (_flushBufferInvalid && _flushBuffer != BufferHandle.Null) + { + _flushBufferInvalid = false; + _context.Renderer.DeleteBuffer(_flushBuffer); + _flushBuffer = BufferHandle.Null; + } + + if (_flushBuffer == BufferHandle.Null) + { + if (!TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities)) + { + return; + } + + bool canImport = Storage.Info.IsLinear && Storage.Info.Stride >= Storage.Info.Width * Storage.Info.FormatInfo.BytesPerPixel; + + var hostPointer = canImport ? _physicalMemory.GetHostPointer(Storage.Range) : 0; + + if (hostPointer != 0 && _context.Renderer.PrepareHostMapping(hostPointer, Storage.Size)) + { + _flushBuffer = _context.Renderer.CreateBuffer(hostPointer, (int)Storage.Size); + _flushBufferImported = true; + } + else + { + _flushBuffer = _context.Renderer.CreateBuffer((int)Storage.Size, BufferAccess.HostMemory); + _flushBufferImported = false; + } + + Storage.BlacklistScale(); + } + + int sliceStart = handle.BaseSlice; + int sliceEnd = sliceStart + handle.SliceCount; + + for (int i = sliceStart; i < sliceEnd; i++) + { + (int layer, int level) = GetLayerLevelForView(i); + + Storage.GetFlushTexture().CopyTo(new BufferRange(_flushBuffer, _allOffsets[i], _sliceSizes[level]), layer, level, _flushBufferImported ? Storage.Info.Stride : 0); + } + } + + /// + /// Clears competing modified flags for all incompatible ranges, if they have possibly been modified. + /// + /// The texture that has been modified + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ClearIncompatibleOverlaps(Texture texture) + { + if (_incompatibleOverlapsDirty) + { + foreach (TextureIncompatibleOverlap incompatible in _incompatibleOverlaps) + { + incompatible.Group.ClearModified(texture.Range, this); + + incompatible.Group.SignalIncompatibleOverlapModified(); + } + + _incompatibleOverlapsDirty = false; + } + } + + /// + /// Signal that a texture in the group has been modified by the GPU. + /// + /// The texture that has been modified + public void SignalModified(Texture texture) + { + ModifiedSequence = _context.GetModifiedSequence(); + + ClearIncompatibleOverlaps(texture); + + EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + { + for (int i = 0; i < regionCount; i++) + { + TextureGroupHandle group = _handles[baseHandle + i]; + + group.SignalModified(_context); + } + }); + } + + /// + /// Signal that a texture in the group is actively bound, or has been unbound by the GPU. + /// + /// The texture that has been modified + /// True if this texture is being bound, false if unbound + public void SignalModifying(Texture texture, bool bound) + { + ModifiedSequence = _context.GetModifiedSequence(); + + ClearIncompatibleOverlaps(texture); + + EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + { + for (int i = 0; i < regionCount; i++) + { + TextureGroupHandle group = _handles[baseHandle + i]; + + group.SignalModifying(bound, _context); + } + }); + } + + /// + /// Register a read/write action to flush for a texture group. + /// + /// The group to register an action for + public void RegisterAction(TextureGroupHandle group) + { + foreach (RegionHandle handle in group.Handles) + { + handle.RegisterAction((address, size) => FlushAction(group, address, size)); + } + } + + /// + /// Propagates the mip/layer view flags depending on the texture type. + /// When the most granular type of subresource has views, the other type of subresource must be segmented granularly too. + /// + /// True if the storage has layer views + /// True if the storage has mip views + /// The input values after propagation + private (bool HasLayerViews, bool HasMipViews) PropagateGranularity(bool hasLayerViews, bool hasMipViews) + { + if (_is3D) + { + hasMipViews |= hasLayerViews; + } + else + { + hasLayerViews |= hasMipViews; + } + + return (hasLayerViews, hasMipViews); + } + + /// + /// Evaluate the range of tracking handles which a view texture overlaps with. + /// + /// The texture to get handles for + /// + /// A function to be called with the base index of the range of handles for the given texture, and the number of handles it covers. + /// This can be called for multiple disjoint ranges, if required. + /// + private void EvaluateRelevantHandles(Texture texture, HandlesCallbackDelegate callback) + { + if (texture == Storage || !(_hasMipViews || _hasLayerViews)) + { + callback(0, _handles.Length); + + return; + } + + EvaluateRelevantHandles(texture.FirstLayer, texture.FirstLevel, texture.Info.GetSlices(), texture.Info.Levels, callback); + } + + /// + /// Evaluate the range of tracking handles which a view texture overlaps with, + /// using the view's position and slice/level counts. + /// + /// The first layer of the texture + /// The first level of the texture + /// The slice count of the texture + /// The level count of the texture + /// + /// A function to be called with the base index of the range of handles for the given texture, and the number of handles it covers. + /// This can be called for multiple disjoint ranges, if required. + /// + private void EvaluateRelevantHandles(int firstLayer, int firstLevel, int slices, int levels, HandlesCallbackDelegate callback) + { + int targetLayerHandles = _hasLayerViews ? slices : 1; + int targetLevelHandles = _hasMipViews ? levels : 1; + + if (_isBuffer) + { + return; + } + else if (_is3D) + { + // Future mip levels come after all layers of the last mip level. Each mipmap has less layers (depth) than the last. + + if (!_hasLayerViews) + { + // When there are no layer views, the mips are at a consistent offset. + + callback(firstLevel, targetLevelHandles); + } + else + { + (int levelIndex, int layerCount) = Get3DLevelRange(firstLevel); + + if (levels > 1 && slices < _layers) + { + // The given texture only covers some of the depth of multiple mips. (a "depth slice") + // Callback with each mip's range separately. + // Can assume that the group is fully subdivided (both slices and levels > 1 for storage) + + while (levels-- > 1) + { + callback(firstLayer + levelIndex, slices); + + levelIndex += layerCount; + layerCount = Math.Max(layerCount >> 1, 1); + slices = Math.Max(layerCount >> 1, 1); + } + } + else + { + int totalSize = Math.Min(layerCount, slices); + + while (levels-- > 1) + { + layerCount = Math.Max(layerCount >> 1, 1); + totalSize += layerCount; + } + + callback(firstLayer + levelIndex, totalSize); + } + } + } + else + { + // Future layers come after all mipmaps of the last. + int levelHandles = _hasMipViews ? _levels : 1; + + if (slices > 1 && levels < _levels) + { + // The given texture only covers some of the mipmaps of multiple slices. (a "mip slice") + // Callback with each layer's range separately. + // Can assume that the group is fully subdivided (both slices and levels > 1 for storage) + + for (int i = 0; i < slices; i++) + { + callback(firstLevel + (firstLayer + i) * levelHandles, targetLevelHandles, true); + } + } + else + { + callback(firstLevel + firstLayer * levelHandles, targetLevelHandles + (targetLayerHandles - 1) * levelHandles); + } + } + } + + /// + /// Get the range of offsets for a given mip level of a 3D texture. + /// + /// The level to return + /// Start index and count of offsets for the given level + private (int Index, int Count) Get3DLevelRange(int level) + { + int index = 0; + int count = _layers; // Depth. Halves with each mip level. + + while (level-- > 0) + { + index += count; + count = Math.Max(count >> 1, 1); + } + + return (index, count); + } + + /// + /// Get view information for a single tracking handle. + /// + /// The index of the handle + /// The layers and levels that the handle covers, and its index in the offsets array + private (int BaseLayer, int BaseLevel, int Levels, int Layers, int Index) GetHandleInformation(int handleIndex) + { + int baseLayer; + int baseLevel; + int levels = _hasMipViews ? 1 : _levels; + int layers = _hasLayerViews ? 1 : _layers; + int index; + + if (_is3D) + { + if (_hasLayerViews) + { + // NOTE: Will also have mip views, or only one level in storage. + + index = handleIndex; + baseLevel = 0; + + int levelLayers = _layers; + + while (handleIndex >= levelLayers) + { + handleIndex -= levelLayers; + baseLevel++; + levelLayers = Math.Max(levelLayers >> 1, 1); + } + + baseLayer = handleIndex; + } + else + { + baseLayer = 0; + baseLevel = handleIndex; + + (index, _) = Get3DLevelRange(baseLevel); + } + } + else + { + baseLevel = _hasMipViews ? handleIndex % _levels : 0; + baseLayer = _hasMipViews ? handleIndex / _levels : handleIndex; + index = baseLevel + baseLayer * _levels; + } + + return (baseLayer, baseLevel, levels, layers, index); + } + + /// + /// Gets the layer and level for a given view. + /// + /// The index of the view + /// The layer and level of the specified view + private (int BaseLayer, int BaseLevel) GetLayerLevelForView(int index) + { + if (_is3D) + { + int baseLevel = 0; + + int levelLayers = _layers; + + while (index >= levelLayers) + { + index -= levelLayers; + baseLevel++; + levelLayers = Math.Max(levelLayers >> 1, 1); + } + + return (index, baseLevel); + } + else + { + return (index / _levels, index % _levels); + } + } + + /// + /// Find the byte offset of a given texture relative to the storage. + /// + /// The texture to locate + /// The offset of the texture in bytes + public int FindOffset(Texture texture) + { + return _allOffsets[GetOffsetIndex(texture.FirstLayer, texture.FirstLevel)]; + } + + /// + /// Find the offset index of a given layer and level. + /// + /// The view layer + /// The view level + /// The offset index of the given layer and level + public int GetOffsetIndex(int layer, int level) + { + if (_is3D) + { + return layer + Get3DLevelRange(level).Index; + } + else + { + return level + layer * _levels; + } + } + + /// + /// Generate a CpuRegionHandle for a given address and size range in CPU VA. + /// + /// The start address of the tracked region + /// The size of the tracked region + /// A CpuRegionHandle covering the given range + private RegionHandle GenerateHandle(ulong address, ulong size) + { + return _physicalMemory.BeginTracking(address, size, ResourceKind.Texture); + } + + /// + /// Generate a TextureGroupHandle covering a specified range of views. + /// + /// The start view of the handle + /// The number of views to cover + /// A TextureGroupHandle covering the given views + private TextureGroupHandle GenerateHandles(int viewStart, int views) + { + int viewEnd = viewStart + views - 1; + (_, int lastLevel) = GetLayerLevelForView(viewEnd); + + int offset = _allOffsets[viewStart]; + int endOffset = _allOffsets[viewEnd] + _sliceSizes[lastLevel]; + int size = endOffset - offset; + + var result = new List(); + + for (int i = 0; i < TextureRange.Count; i++) + { + MemoryRange item = TextureRange.GetSubRange(i); + int subRangeSize = (int)item.Size; + + int sliceStart = Math.Clamp(offset, 0, subRangeSize); + int sliceEnd = Math.Clamp(endOffset, 0, subRangeSize); + + if (sliceStart != sliceEnd && item.Address != MemoryManager.PteUnmapped) + { + result.Add(GenerateHandle(item.Address + (ulong)sliceStart, (ulong)(sliceEnd - sliceStart))); + } + + offset -= subRangeSize; + endOffset -= subRangeSize; + + if (endOffset <= 0) + { + break; + } + } + + (int firstLayer, int firstLevel) = GetLayerLevelForView(viewStart); + + if (_hasLayerViews && _hasMipViews) + { + size = _sliceSizes[firstLevel]; + } + + offset = _allOffsets[viewStart]; + ulong maxSize = Storage.Size - (ulong)offset; + + var groupHandle = new TextureGroupHandle( + this, + offset, + Math.Min(maxSize, (ulong)size), + _views, + firstLayer, + firstLevel, + viewStart, + views, + result.ToArray()); + + return groupHandle; + } + + /// + /// Update the views in this texture group, rebuilding the memory tracking if required. + /// + /// The views list of the storage texture + /// The texture that has been added, if that is the only change, otherwise null + public void UpdateViews(List views, Texture texture) + { + // This is saved to calculate overlapping views for each handle. + _views = views.ToArray(); + + bool layerViews = _hasLayerViews; + bool mipViews = _hasMipViews; + bool regionsRebuilt = false; + + if (!(layerViews && mipViews)) + { + foreach (Texture view in views) + { + if (view.Info.GetSlices() < _layers) + { + layerViews = true; + } + + if (view.Info.Levels < _levels) + { + mipViews = true; + } + } + + (layerViews, mipViews) = PropagateGranularity(layerViews, mipViews); + + if (layerViews != _hasLayerViews || mipViews != _hasMipViews) + { + _hasLayerViews = layerViews; + _hasMipViews = mipViews; + + RecalculateHandleRegions(); + regionsRebuilt = true; + } + } + + if (!regionsRebuilt) + { + if (texture != null) + { + int offset = FindOffset(texture); + + foreach (TextureGroupHandle handle in _handles) + { + handle.AddOverlap(offset, texture); + } + } + else + { + // Must update the overlapping views on all handles, but only if they were not just recreated. + + foreach (TextureGroupHandle handle in _handles) + { + handle.RecalculateOverlaps(this, views); + } + } + } + + SignalAllDirty(); + } + + + /// + /// Removes a view from the group, removing it from all overlap lists. + /// + /// The views list of the storage texture + /// View to remove from the group + public void RemoveView(List views, Texture view) + { + // This is saved to calculate overlapping views for each handle. + _views = views.ToArray(); + + int offset = FindOffset(view); + + foreach (TextureGroupHandle handle in _handles) + { + handle.RemoveOverlap(offset, view); + } + } + + /// + /// Inherit handle state from an old set of handles, such as modified and dirty flags. + /// + /// The set of handles to inherit state from + /// The set of handles inheriting the state + /// The offset of the old handles in relation to the new ones + private void InheritHandles(TextureGroupHandle[] oldHandles, TextureGroupHandle[] handles, int relativeOffset) + { + foreach (var group in handles) + { + foreach (var handle in group.Handles) + { + bool dirty = false; + + foreach (var oldGroup in oldHandles) + { + if (group.OverlapsWith(oldGroup.Offset + relativeOffset, oldGroup.Size)) + { + foreach (var oldHandle in oldGroup.Handles) + { + if (handle.OverlapsWith(oldHandle.Address, oldHandle.Size)) + { + dirty |= oldHandle.Dirty; + } + } + + group.Inherit(oldGroup, group.Offset == oldGroup.Offset + relativeOffset); + } + } + + if (dirty && !handle.Dirty) + { + handle.Reprotect(true); + } + + if (group.Modified) + { + handle.RegisterAction((address, size) => FlushAction(group, address, size)); + } + } + } + + foreach (var oldGroup in oldHandles) + { + oldGroup.Modified = false; + } + } + + /// + /// Inherit state from another texture group. + /// + /// The texture group to inherit from + public void Inherit(TextureGroup other) + { + bool layerViews = _hasLayerViews || other._hasLayerViews; + bool mipViews = _hasMipViews || other._hasMipViews; + + if (layerViews != _hasLayerViews || mipViews != _hasMipViews) + { + _hasLayerViews = layerViews; + _hasMipViews = mipViews; + + RecalculateHandleRegions(); + } + + foreach (TextureIncompatibleOverlap incompatible in other._incompatibleOverlaps) + { + RegisterIncompatibleOverlap(incompatible, false); + + incompatible.Group._incompatibleOverlaps.RemoveAll(overlap => overlap.Group == other); + } + + int relativeOffset = Storage.Range.FindOffset(other.Storage.Range); + + InheritHandles(other._handles, _handles, relativeOffset); + } + + /// + /// Replace the current handles with the new handles. It is assumed that the new handles start dirty. + /// The dirty flags from the previous handles will be kept. + /// + /// The handles to replace the current handles with + /// True if the storage memory range changed since the last region handle generation + private void ReplaceHandles(TextureGroupHandle[] handles, bool rangeChanged) + { + if (_handles != null) + { + // When replacing handles, they should start as non-dirty. + + foreach (TextureGroupHandle groupHandle in handles) + { + if (rangeChanged) + { + // When the storage range changes, this becomes a little different. + // If a range does not match one in the original, treat it as modified. + // It has been newly mapped and its data must be synchronized. + + if (groupHandle.Handles.Length == 0) + { + continue; + } + + foreach (var oldGroup in _handles) + { + if (!groupHandle.OverlapsWith(oldGroup.Offset, oldGroup.Size)) + { + continue; + } + + foreach (RegionHandle handle in groupHandle.Handles) + { + bool hasMatch = false; + + foreach (var oldHandle in oldGroup.Handles) + { + if (oldHandle.RangeEquals(handle)) + { + hasMatch = true; + break; + } + } + + if (hasMatch) + { + handle.Reprotect(); + } + } + } + } + else + { + foreach (RegionHandle handle in groupHandle.Handles) + { + handle.Reprotect(); + } + } + } + + InheritHandles(_handles, handles, 0); + + foreach (var oldGroup in _handles) + { + foreach (var oldHandle in oldGroup.Handles) + { + oldHandle.Dispose(); + } + } + } + + _handles = handles; + _loadNeeded = new bool[_handles.Length]; + } + + /// + /// Recalculate handle regions for this texture group, and inherit existing state into the new handles. + /// + /// True if the storage memory range changed since the last region handle generation + private void RecalculateHandleRegions(bool rangeChanged = false) + { + TextureGroupHandle[] handles; + + if (_isBuffer) + { + handles = Array.Empty(); + } + else if (!(_hasMipViews || _hasLayerViews)) + { + // Single dirty region. + var cpuRegionHandles = new RegionHandle[TextureRange.Count]; + int count = 0; + + for (int i = 0; i < TextureRange.Count; i++) + { + var currentRange = TextureRange.GetSubRange(i); + if (currentRange.Address != MemoryManager.PteUnmapped) + { + cpuRegionHandles[count++] = GenerateHandle(currentRange.Address, currentRange.Size); + } + } + + if (count != TextureRange.Count) + { + Array.Resize(ref cpuRegionHandles, count); + } + + var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, 0, _allOffsets.Length, cpuRegionHandles); + + handles = new TextureGroupHandle[] { groupHandle }; + } + else + { + // Get views for the host texture. + // It's worth noting that either the texture has layer views or mip views when getting to this point, which simplifies the logic a little. + // Depending on if the texture is 3d, either the mip views imply that layer views are present (2d) or the other way around (3d). + // This is enforced by the way the texture matched as a view, so we don't need to check. + + int layerHandles = _hasLayerViews ? _layers : 1; + int levelHandles = _hasMipViews ? _levels : 1; + + int handleIndex = 0; + + if (_is3D) + { + var handlesList = new List(); + + for (int i = 0; i < levelHandles; i++) + { + for (int j = 0; j < layerHandles; j++) + { + (int viewStart, int views) = Get3DLevelRange(i); + viewStart += j; + views = _hasLayerViews ? 1 : views; // A layer view is also a mip view. + + handlesList.Add(GenerateHandles(viewStart, views)); + } + + layerHandles = Math.Max(1, layerHandles >> 1); + } + + handles = handlesList.ToArray(); + } + else + { + handles = new TextureGroupHandle[layerHandles * levelHandles]; + + for (int i = 0; i < layerHandles; i++) + { + for (int j = 0; j < levelHandles; j++) + { + int viewStart = j + i * _levels; + int views = _hasMipViews ? 1 : _levels; // A mip view is also a layer view. + + handles[handleIndex++] = GenerateHandles(viewStart, views); + } + } + } + } + + ReplaceHandles(handles, rangeChanged); + } + + /// + /// Regenerates handles when the storage range has been remapped. + /// This forces the regions to be fully subdivided. + /// + public void RangeChanged() + { + _hasLayerViews = true; + _hasMipViews = true; + + RecalculateHandleRegions(true); + + SignalAllDirty(); + } + + /// + /// Ensure that there is a handle for each potential texture view. Required for copy dependencies to work. + /// + private void EnsureFullSubdivision() + { + if (!(_hasLayerViews && _hasMipViews)) + { + _hasLayerViews = true; + _hasMipViews = true; + + RecalculateHandleRegions(); + } + } + + /// + /// Create a copy dependency between this texture group, and a texture at a given layer/level offset. + /// + /// The view compatible texture to create a dependency to + /// The base layer of the given texture relative to the storage + /// The base level of the given texture relative to the storage + /// True if this texture is first copied to the given one, false for the opposite direction + public void CreateCopyDependency(Texture other, int firstLayer, int firstLevel, bool copyTo) + { + TextureGroup otherGroup = other.Group; + + EnsureFullSubdivision(); + otherGroup.EnsureFullSubdivision(); + + // Get the location of each texture within its storage, so we can find the handles to apply the dependency to. + // This can consist of multiple disjoint regions, for example if this is a mip slice of an array texture. + + var targetRange = new List<(int BaseHandle, int RegionCount)>(); + var otherRange = new List<(int BaseHandle, int RegionCount)>(); + + EvaluateRelevantHandles(firstLayer, firstLevel, other.Info.GetSlices(), other.Info.Levels, (baseHandle, regionCount, split) => targetRange.Add((baseHandle, regionCount))); + otherGroup.EvaluateRelevantHandles(other, (baseHandle, regionCount, split) => otherRange.Add((baseHandle, regionCount))); + + int targetIndex = 0; + int otherIndex = 0; + (int Handle, int RegionCount) targetRegion = (0, 0); + (int Handle, int RegionCount) otherRegion = (0, 0); + + while (true) + { + if (targetRegion.RegionCount == 0) + { + if (targetIndex >= targetRange.Count) + { + break; + } + + targetRegion = targetRange[targetIndex++]; + } + + if (otherRegion.RegionCount == 0) + { + if (otherIndex >= otherRange.Count) + { + break; + } + + otherRegion = otherRange[otherIndex++]; + } + + TextureGroupHandle handle = _handles[targetRegion.Handle++]; + TextureGroupHandle otherHandle = other.Group._handles[otherRegion.Handle++]; + + targetRegion.RegionCount--; + otherRegion.RegionCount--; + + handle.CreateCopyDependency(otherHandle, copyTo); + + // If "copyTo" is true, this texture must copy to the other. + // Otherwise, it must copy to this texture. + + if (copyTo) + { + otherHandle.Copy(_context, handle); + } + else + { + handle.Copy(_context, otherHandle); + } + } + } + + /// + /// Creates a copy dependency to another texture group, where handles overlap. + /// Scans through all handles to find compatible patches in the other group. + /// + /// The texture group that overlaps this one + /// True if this texture is first copied to the given one, false for the opposite direction + public void CreateCopyDependency(TextureGroup other, bool copyTo) + { + for (int i = 0; i < _allOffsets.Length; i++) + { + (_, int level) = GetLayerLevelForView(i); + MultiRange handleRange = Storage.Range.Slice((ulong)_allOffsets[i], 1); + ulong handleBase = handleRange.GetSubRange(0).Address; + + for (int j = 0; j < other._handles.Length; j++) + { + (_, int otherLevel) = other.GetLayerLevelForView(j); + MultiRange otherHandleRange = other.Storage.Range.Slice((ulong)other._allOffsets[j], 1); + ulong otherHandleBase = otherHandleRange.GetSubRange(0).Address; + + if (handleBase == otherHandleBase) + { + // Check if the two sizes are compatible. + TextureInfo info = Storage.Info; + TextureInfo otherInfo = other.Storage.Info; + + if (TextureCompatibility.ViewLayoutCompatible(info, otherInfo, level, otherLevel) && + TextureCompatibility.CopySizeMatches(info, otherInfo, level, otherLevel)) + { + // These textures are copy compatible. Create the dependency. + + EnsureFullSubdivision(); + other.EnsureFullSubdivision(); + + TextureGroupHandle handle = _handles[i]; + TextureGroupHandle otherHandle = other._handles[j]; + + handle.CreateCopyDependency(otherHandle, copyTo); + + // If "copyTo" is true, this texture must copy to the other. + // Otherwise, it must copy to this texture. + + if (copyTo) + { + otherHandle.Copy(_context, handle); + } + else + { + handle.Copy(_context, otherHandle); + } + } + } + } + } + } + + /// + /// Registers another texture group as an incompatible overlap, if not already registered. + /// + /// The texture group to add to the incompatible overlaps list + /// True if the overlap should register copy dependencies + public void RegisterIncompatibleOverlap(TextureIncompatibleOverlap other, bool copy) + { + if (!_incompatibleOverlaps.Exists(overlap => overlap.Group == other.Group)) + { + if (copy && other.Compatibility == TextureViewCompatibility.LayoutIncompatible) + { + // Any of the group's views may share compatibility, even if the parents do not fully. + CreateCopyDependency(other.Group, false); + } + + _incompatibleOverlaps.Add(other); + other.Group._incompatibleOverlaps.Add(new TextureIncompatibleOverlap(this, other.Compatibility)); + } + + other.Group.SignalIncompatibleOverlapModified(); + SignalIncompatibleOverlapModified(); + } + + /// + /// Clear modified flags in the given range. + /// This will stop any GPU written data from flushing or copying to dependent textures. + /// + /// The range to clear modified flags in + /// Ignore handles that have a copy dependency to the specified group + public void ClearModified(MultiRange range, TextureGroup ignore = null) + { + TextureGroupHandle[] handles = _handles; + + foreach (TextureGroupHandle handle in handles) + { + // Handles list is not modified by another thread, only replaced, so this is thread safe. + // Remove modified flags from all overlapping handles, so that the textures don't flush to unmapped/remapped GPU memory. + + MultiRange subRange = Storage.Range.Slice((ulong)handle.Offset, (ulong)handle.Size); + + if (range.OverlapsWith(subRange)) + { + if ((ignore == null || !handle.HasDependencyTo(ignore)) && handle.Modified) + { + handle.Modified = false; + handle.DeferredCopy = null; + Storage.SignalModifiedDirty(); + + lock (handle.Overlaps) + { + foreach (Texture texture in handle.Overlaps) + { + texture.SignalModifiedDirty(); + } + } + } + } + } + + Storage.SignalModifiedDirty(); + + Texture[] views = _views; + + if (views != null) + { + foreach (Texture texture in views) + { + texture.SignalModifiedDirty(); + } + } + } + + /// + /// A flush has been requested on a tracked region. Flush texture data for the given handle. + /// + /// The handle this flush action is for + /// The address of the flushing memory access + /// The size of the flushing memory access + public void FlushAction(TextureGroupHandle handle, ulong address, ulong size) + { + // There is a small gap here where the action is removed but _actionRegistered is still 1. + // In this case it will skip registering the action, but here we are already handling it, + // so there shouldn't be any issue as it's the same handler for all actions. + + handle.ClearActionRegistered(); + + if (!handle.Modified) + { + return; + } + + bool isGpuThread = _context.IsGpuThread(); + + if (isGpuThread) + { + // No need to wait if we're on the GPU thread, we can just clear the modified flag immediately. + handle.Modified = false; + } + + _context.Renderer.BackgroundContextAction(() => + { + bool inBuffer = !isGpuThread && handle.Sync(_context); + + Storage.SignalModifiedDirty(); + + lock (handle.Overlaps) + { + foreach (Texture texture in handle.Overlaps) + { + texture.SignalModifiedDirty(); + } + } + + if (TextureCompatibility.CanTextureFlush(Storage.Info, _context.Capabilities) && !(inBuffer && _flushBufferImported)) + { + FlushSliceRange(false, handle.BaseSlice, handle.BaseSlice + handle.SliceCount, inBuffer, Storage.GetFlushTexture()); + } + }); + } + + /// + /// Called if any part of the storage texture is unmapped. + /// + public void Unmapped() + { + if (_flushBufferImported) + { + _flushBufferInvalid = true; + } + } + + /// + /// Dispose this texture group, disposing all related memory tracking handles. + /// + public void Dispose() + { + foreach (TextureGroupHandle group in _handles) + { + group.Dispose(); + } + + foreach (TextureIncompatibleOverlap incompatible in _incompatibleOverlaps) + { + incompatible.Group._incompatibleOverlaps.RemoveAll(overlap => overlap.Group == this); + } + + if (_flushBuffer != BufferHandle.Null) + { + _context.Renderer.DeleteBuffer(_flushBuffer); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs new file mode 100644 index 00000000..860922d5 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs @@ -0,0 +1,697 @@ +using Ryujinx.Graphics.Gpu.Synchronization; +using Ryujinx.Memory.Tracking; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// A tracking handle for a texture group, which represents a range of views in a storage texture. + /// Retains a list of overlapping texture views, a modified flag, and tracking for each + /// CPU VA range that the views cover. + /// Also tracks copy dependencies for the handle - references to other handles that must be kept + /// in sync with this one before use. + /// + class TextureGroupHandle : ISyncActionHandler, IDisposable + { + private const int FlushBalanceIncrement = 6; + private const int FlushBalanceWriteCost = 1; + private const int FlushBalanceThreshold = 7; + private const int FlushBalanceMax = 60; + private const int FlushBalanceMin = -10; + + private readonly TextureGroup _group; + private int _bindCount; + private readonly int _firstLevel; + private readonly int _firstLayer; + + // Sync state for texture flush. + + /// + /// The sync number last registered. + /// + private ulong _registeredSync; + private ulong _registeredBufferSync = ulong.MaxValue; + private ulong _registeredBufferGuestSync = ulong.MaxValue; + + /// + /// The sync number when the texture was last modified by GPU. + /// + private ulong _modifiedSync; + + /// + /// Whether a tracking action is currently registered or not. (0/1) + /// + private int _actionRegistered; + + /// + /// Whether a sync action is currently registered or not. + /// + private bool _syncActionRegistered; + + /// + /// Determines the balance of synced writes to flushes. + /// Used to determine if the texture should always write data to a persistent buffer for flush. + /// + private int _flushBalance; + + /// + /// The byte offset from the start of the storage of this handle. + /// + public int Offset { get; } + + /// + /// The size in bytes covered by this handle. + /// + public int Size { get; } + + /// + /// The base slice index for this handle. + /// + public int BaseSlice { get; } + + /// + /// The number of slices covered by this handle. + /// + public int SliceCount { get; } + + /// + /// The textures which this handle overlaps with. + /// + public List Overlaps { get; } + + /// + /// The CPU memory tracking handles that cover this handle. + /// + public RegionHandle[] Handles { get; } + + /// + /// True if a texture overlapping this handle has been modified. Is set false when the flush action is called. + /// + public bool Modified { get; set; } + + /// + /// Dependencies to handles from other texture groups. + /// + public List Dependencies { get; } + + /// + /// A flag indicating that a copy is required from one of the dependencies. + /// + public bool NeedsCopy => DeferredCopy != null; + + /// + /// A data copy that must be acknowledged the next time this handle is used. + /// + public TextureGroupHandle DeferredCopy { get; set; } + + /// + /// Create a new texture group handle, representing a range of views in a storage texture. + /// + /// The TextureGroup that the handle belongs to + /// The byte offset from the start of the storage of the handle + /// The size in bytes covered by the handle + /// All views of the storage texture, used to calculate overlaps + /// The first layer of this handle in the storage texture + /// The first level of this handle in the storage texture + /// The base slice index of this handle + /// The number of slices this handle covers + /// The memory tracking handles that cover this handle + public TextureGroupHandle(TextureGroup group, + int offset, + ulong size, + IEnumerable views, + int firstLayer, + int firstLevel, + int baseSlice, + int sliceCount, + RegionHandle[] handles) + { + _group = group; + _firstLayer = firstLayer; + _firstLevel = firstLevel; + + Offset = offset; + Size = (int)size; + Overlaps = new List(); + Dependencies = new List(); + + BaseSlice = baseSlice; + SliceCount = sliceCount; + + if (views != null) + { + RecalculateOverlaps(group, views); + } + + Handles = handles; + + if (group.Storage.Info.IsLinear) + { + // Linear textures are presumed to be used for readback initially. + _flushBalance = FlushBalanceThreshold + FlushBalanceIncrement; + } + + foreach (RegionHandle handle in handles) + { + handle.RegisterDirtyEvent(DirtyAction); + } + } + + /// + /// The action to perform when a memory tracking handle is flipped to dirty. + /// This notifies overlapping textures that the memory needs to be synchronized. + /// + private void DirtyAction() + { + // Notify all textures that belong to this handle. + + _group.Storage.SignalGroupDirty(); + + lock (Overlaps) + { + foreach (Texture overlap in Overlaps) + { + overlap.SignalGroupDirty(); + } + } + + DeferredCopy = null; + } + + /// + /// Discards all data for this handle. + /// This clears all dirty flags and pending copies from other handles. + /// + public void DiscardData() + { + DeferredCopy = null; + + foreach (RegionHandle handle in Handles) + { + if (handle.Dirty) + { + handle.Reprotect(); + } + } + } + + /// + /// Calculate a list of which views overlap this handle. + /// + /// The parent texture group, used to find a view's base CPU VA offset + /// The views to search for overlaps + public void RecalculateOverlaps(TextureGroup group, IEnumerable views) + { + // Overlaps can be accessed from the memory tracking signal handler, so access must be atomic. + lock (Overlaps) + { + int endOffset = Offset + Size; + + Overlaps.Clear(); + + foreach (Texture view in views) + { + int viewOffset = group.FindOffset(view); + if (viewOffset < endOffset && Offset < viewOffset + (int)view.Size) + { + Overlaps.Add(view); + } + } + } + } + + /// + /// Determine if the next sync will copy into the flush buffer. + /// + /// True if it will copy, false otherwise + private bool NextSyncCopies() + { + return _flushBalance - FlushBalanceWriteCost > FlushBalanceThreshold; + } + + /// + /// Alters the flush balance by the given value. Should increase significantly with each sync, decrease with each write. + /// A flush balance higher than the threshold will cause a texture to repeatedly copy to a flush buffer on each use. + /// + /// Value to add to the existing flush balance + /// True if the new balance is over the threshold, false otherwise + private bool ModifyFlushBalance(int modifier) + { + int result; + int existingBalance; + do + { + existingBalance = _flushBalance; + result = Math.Max(FlushBalanceMin, Math.Min(FlushBalanceMax, existingBalance + modifier)); + } + while (Interlocked.CompareExchange(ref _flushBalance, result, existingBalance) != existingBalance); + + return result > FlushBalanceThreshold; + } + + /// + /// Adds a single texture view as an overlap if its range overlaps. + /// + /// The offset of the view in the group + /// The texture to add as an overlap + public void AddOverlap(int offset, Texture view) + { + // Overlaps can be accessed from the memory tracking signal handler, so access must be atomic. + + if (OverlapsWith(offset, (int)view.Size)) + { + lock (Overlaps) + { + Overlaps.Add(view); + } + } + } + + /// + /// Removes a single texture view as an overlap if its range overlaps. + /// + /// The offset of the view in the group + /// The texture to add as an overlap + public void RemoveOverlap(int offset, Texture view) + { + // Overlaps can be accessed from the memory tracking signal handler, so access must be atomic. + + if (OverlapsWith(offset, (int)view.Size)) + { + lock (Overlaps) + { + Overlaps.Remove(view); + } + } + } + + /// + /// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle. + /// + /// The GPU context to register a sync action on + private void RegisterSync(GpuContext context) + { + if (!_syncActionRegistered) + { + _modifiedSync = context.SyncNumber; + context.RegisterSyncAction(this, true); + _syncActionRegistered = true; + } + + if (Interlocked.Exchange(ref _actionRegistered, 1) == 0) + { + _group.RegisterAction(this); + } + } + + /// + /// Signal that this handle has been modified to any existing dependencies, and set the modified flag. + /// + /// The GPU context to register a sync action on + public void SignalModified(GpuContext context) + { + Modified = true; + + // If this handle has any copy dependencies, notify the other handle that a copy needs to be performed. + + foreach (TextureDependency dependency in Dependencies) + { + dependency.SignalModified(); + } + + RegisterSync(context); + } + + /// + /// Signal that this handle has either started or ended being modified. + /// + /// True if this handle is being bound, false if unbound + /// The GPU context to register a sync action on + public void SignalModifying(bool bound, GpuContext context) + { + SignalModified(context); + + if (!bound && _syncActionRegistered && NextSyncCopies()) + { + // On unbind, textures that flush often should immediately create sync so their result can be obtained as soon as possible. + + context.CreateHostSyncIfNeeded(HostSyncFlags.Force); + } + + // Note: Bind count currently resets to 0 on inherit for safety, as the handle <-> view relationship can change. + _bindCount = Math.Max(0, _bindCount + (bound ? 1 : -1)); + } + + /// + /// Synchronize dependent textures, if any of them have deferred a copy from this texture. + /// + public void SynchronizeDependents() + { + foreach (TextureDependency dependency in Dependencies) + { + TextureGroupHandle otherHandle = dependency.Other.Handle; + + if (otherHandle.DeferredCopy == this) + { + otherHandle._group.Storage.SynchronizeMemory(); + } + } + } + + /// + /// Wait for the latest sync number that the texture handle was written to, + /// removing the modified flag if it was reached, or leaving it set if it has not yet been created. + /// + /// The GPU context used to wait for sync + /// True if the texture data can be read from the flush buffer + public bool Sync(GpuContext context) + { + // Currently assumes the calling thread is a guest thread. + + bool inBuffer = _registeredBufferGuestSync != ulong.MaxValue; + ulong sync = inBuffer ? _registeredBufferGuestSync : _registeredSync; + + long diff = (long)(context.SyncNumber - sync); + + ModifyFlushBalance(FlushBalanceIncrement); + + if (diff > 0) + { + context.Renderer.WaitSync(sync); + + if ((long)(_modifiedSync - sync) > 0) + { + // Flush the data in a previous state. Do not remove the modified flag - it will be removed to ignore following writes. + return inBuffer; + } + + Modified = false; + + return inBuffer; + } + + // If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag. + return false; + } + + /// + /// Clears the action registered variable, indicating that the tracking action should be + /// re-registered on the next modification. + /// + public void ClearActionRegistered() + { + Interlocked.Exchange(ref _actionRegistered, 0); + } + + /// + /// Action to perform before a sync number is registered after modification. + /// This action will copy the texture data to the flush buffer if this texture + /// flushes often enough, which is determined by the flush balance. + /// + /// + public void SyncPreAction(bool syncpoint) + { + if (syncpoint || NextSyncCopies()) + { + if (ModifyFlushBalance(0) && _registeredBufferSync != _modifiedSync) + { + _group.FlushIntoBuffer(this); + _registeredBufferSync = _modifiedSync; + } + } + } + + /// + /// Action to perform when a sync number is registered after modification. + /// This action will register a read tracking action on the memory tracking handle so that a flush from CPU can happen. + /// + /// + public bool SyncAction(bool syncpoint) + { + // The storage will need to signal modified again to update the sync number in future. + _group.Storage.SignalModifiedDirty(); + + bool lastInBuffer = _registeredBufferSync == _modifiedSync; + + if (!lastInBuffer) + { + _registeredBufferSync = ulong.MaxValue; + } + + lock (Overlaps) + { + foreach (Texture texture in Overlaps) + { + texture.SignalModifiedDirty(); + } + } + + // Register region tracking for CPU? (again) + + _registeredSync = _modifiedSync; + _syncActionRegistered = false; + + if (Interlocked.Exchange(ref _actionRegistered, 1) == 0) + { + _group.RegisterAction(this); + } + + if (syncpoint) + { + _registeredBufferGuestSync = _registeredBufferSync; + } + + // If the last modification is in the buffer, keep this sync action alive until it sees a syncpoint. + return syncpoint || !lastInBuffer; + } + + /// + /// Signal that a copy dependent texture has been modified, and must have its data copied to this one. + /// + /// The texture handle that must defer a copy to this one + public void DeferCopy(TextureGroupHandle copyFrom) + { + Modified = false; + DeferredCopy = copyFrom; + + _group.Storage.SignalGroupDirty(); + + foreach (Texture overlap in Overlaps) + { + overlap.SignalGroupDirty(); + } + } + + /// + /// Create a copy dependency between this handle, and another. + /// + /// The handle to create a copy dependency to + /// True if a copy should be deferred to all of the other handle's dependencies + public void CreateCopyDependency(TextureGroupHandle other, bool copyToOther = false) + { + // Does this dependency already exist? + foreach (TextureDependency existing in Dependencies) + { + if (existing.Other.Handle == other) + { + // Do not need to create it again. May need to set the dirty flag. + return; + } + } + + _group.HasCopyDependencies = true; + other._group.HasCopyDependencies = true; + + TextureDependency dependency = new(this); + TextureDependency otherDependency = new(other); + + dependency.Other = otherDependency; + otherDependency.Other = dependency; + + Dependencies.Add(dependency); + other.Dependencies.Add(otherDependency); + + // Recursively create dependency: + // All of this handle's dependencies must depend on the other. + foreach (TextureDependency existing in Dependencies.ToArray()) + { + if (existing != dependency && existing.Other.Handle != other) + { + existing.Other.Handle.CreateCopyDependency(other); + } + } + + // All of the other handle's dependencies must depend on this. + foreach (TextureDependency existing in other.Dependencies.ToArray()) + { + if (existing != otherDependency && existing.Other.Handle != this) + { + existing.Other.Handle.CreateCopyDependency(this); + + if (copyToOther && Modified) + { + existing.Other.Handle.DeferCopy(this); + } + } + } + } + + /// + /// Remove a dependency from this handle's dependency list. + /// + /// The dependency to remove + public void RemoveDependency(TextureDependency dependency) + { + Dependencies.Remove(dependency); + } + + /// + /// Check if any of this handle's memory tracking handles are dirty. + /// + /// True if at least one of the handles is dirty + private bool CheckDirty() + { + return Array.Exists(Handles, handle => handle.Dirty); + } + + /// + /// Perform a copy from the provided handle to this one, or perform a deferred copy if none is provided. + /// + /// GPU context to register sync for modified handles + /// The handle to copy from. If not provided, this method will copy from and clear the deferred copy instead + /// True if the copy was performed, false otherwise + public bool Copy(GpuContext context, TextureGroupHandle fromHandle = null) + { + bool result = false; + bool shouldCopy = false; + + if (fromHandle == null) + { + fromHandle = DeferredCopy; + + if (fromHandle != null) + { + // Only copy if the copy texture is still modified. + // DeferredCopy will be set to null if new data is written from CPU (see the DirtyAction method). + // It will also set as unmodified if a copy is deferred to it. + + shouldCopy = true; + + if (fromHandle._bindCount == 0) + { + // Repeat the copy in future if the bind count is greater than 0. + DeferredCopy = null; + } + } + } + else + { + // Copies happen directly when initializing a copy dependency. + // If dirty, do not copy. Its data no longer matters, and this handle should also be dirty. + // Also, only direct copy if the data in this handle is not already modified (can be set by copies from modified handles). + shouldCopy = !fromHandle.CheckDirty() && (fromHandle.Modified || !Modified); + } + + if (shouldCopy) + { + Texture from = fromHandle._group.Storage; + Texture to = _group.Storage; + + if (from.ScaleFactor != to.ScaleFactor) + { + to.PropagateScale(from); + } + + from.HostTexture.CopyTo( + to.HostTexture, + fromHandle._firstLayer, + _firstLayer, + fromHandle._firstLevel, + _firstLevel); + + if (fromHandle.Modified) + { + Modified = true; + + RegisterSync(context); + } + + result = true; + } + + return result; + } + + /// + /// Check if this handle has a dependency to a given texture group. + /// + /// The texture group to check for + /// True if there is a dependency, false otherwise + public bool HasDependencyTo(TextureGroup group) + { + foreach (TextureDependency dep in Dependencies) + { + if (dep.Other.Handle._group == group) + { + return true; + } + } + + return false; + } + + /// + /// Inherit modified flags and dependencies from another texture handle. + /// + /// The texture handle to inherit from + /// Whether the handle should inherit copy dependencies or not + public void Inherit(TextureGroupHandle old, bool withCopies) + { + Modified |= old.Modified; + + if (withCopies) + { + foreach (TextureDependency dependency in old.Dependencies.ToArray()) + { + CreateCopyDependency(dependency.Other.Handle); + + if (dependency.Other.Handle.DeferredCopy == old) + { + dependency.Other.Handle.DeferredCopy = this; + } + } + + DeferredCopy = old.DeferredCopy; + } + } + + /// + /// Check if this region overlaps with another. + /// + /// Base address + /// Size of the region + /// True if overlapping, false otherwise + public bool OverlapsWith(int offset, int size) + { + return Offset < offset + size && offset < Offset + Size; + } + + /// + /// Dispose this texture group handle, removing all its dependencies and disposing its memory tracking handles. + /// + public void Dispose() + { + foreach (RegionHandle handle in Handles) + { + handle.Dispose(); + } + + foreach (TextureDependency dependency in Dependencies.ToArray()) + { + dependency.Other.Handle.RemoveDependency(dependency.Other); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs new file mode 100644 index 00000000..94d2e0bf --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs @@ -0,0 +1,411 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Texture; +using System; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture information. + /// + readonly struct TextureInfo + { + /// + /// Address of the texture in GPU mapped memory. + /// + public ulong GpuAddress { get; } + + /// + /// The width of the texture. + /// + public int Width { get; } + + /// + /// The height of the texture, or layers count for 1D array textures. + /// + public int Height { get; } + + /// + /// The depth of the texture (for 3D textures), or layers count for array textures. + /// + public int DepthOrLayers { get; } + + /// + /// The number of mipmap levels of the texture. + /// + public int Levels { get; } + + /// + /// The number of samples in the X direction for multisampled textures. + /// + public int SamplesInX { get; } + + /// + /// The number of samples in the Y direction for multisampled textures. + /// + public int SamplesInY { get; } + + /// + /// The number of bytes per line for linear textures. + /// + public int Stride { get; } + + /// + /// Indicates whenever or not the texture is a linear texture. + /// + public bool IsLinear { get; } + + /// + /// GOB blocks in the Y direction, for block linear textures. + /// + public int GobBlocksInY { get; } + + /// + /// GOB blocks in the Z direction, for block linear textures. + /// + public int GobBlocksInZ { get; } + + /// + /// Number of GOB blocks per tile in the X direction, for block linear textures. + /// + public int GobBlocksInTileX { get; } + + /// + /// Total number of samples for multisampled textures. + /// + public int Samples => SamplesInX * SamplesInY; + + /// + /// Texture target type. + /// + public Target Target { get; } + + /// + /// Texture format information. + /// + public FormatInfo FormatInfo { get; } + + /// + /// Depth-stencil mode of the texture. This defines whenever the depth or stencil value is read from shaders, + /// for depth-stencil texture formats. + /// + public DepthStencilMode DepthStencilMode { get; } + + /// + /// Texture swizzle for the red color channel. + /// + public SwizzleComponent SwizzleR { get; } + + /// + /// Texture swizzle for the green color channel. + /// + public SwizzleComponent SwizzleG { get; } + + /// + /// Texture swizzle for the blue color channel. + /// + public SwizzleComponent SwizzleB { get; } + + /// + /// Texture swizzle for the alpha color channel. + /// + public SwizzleComponent SwizzleA { get; } + + /// + /// Constructs the texture information structure. + /// + /// The GPU address of the texture + /// The width of the texture + /// The height or the texture + /// The depth or layers count of the texture + /// The amount of mipmap levels of the texture + /// The number of samples in the X direction for multisample textures, should be 1 otherwise + /// The number of samples in the Y direction for multisample textures, should be 1 otherwise + /// The stride for linear textures + /// Whenever the texture is linear or block linear + /// Number of GOB blocks in the Y direction + /// Number of GOB blocks in the Z direction + /// Number of GOB blocks per tile in the X direction + /// Texture target type + /// Texture format information + /// Depth-stencil mode + /// Swizzle for the red color channel + /// Swizzle for the green color channel + /// Swizzle for the blue color channel + /// Swizzle for the alpha color channel + public TextureInfo( + ulong gpuAddress, + int width, + int height, + int depthOrLayers, + int levels, + int samplesInX, + int samplesInY, + int stride, + bool isLinear, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX, + Target target, + FormatInfo formatInfo, + DepthStencilMode depthStencilMode = DepthStencilMode.Depth, + SwizzleComponent swizzleR = SwizzleComponent.Red, + SwizzleComponent swizzleG = SwizzleComponent.Green, + SwizzleComponent swizzleB = SwizzleComponent.Blue, + SwizzleComponent swizzleA = SwizzleComponent.Alpha) + { + GpuAddress = gpuAddress; + Width = width; + Height = height; + DepthOrLayers = depthOrLayers; + Levels = levels; + SamplesInX = samplesInX; + SamplesInY = samplesInY; + Stride = stride; + IsLinear = isLinear; + GobBlocksInY = gobBlocksInY; + GobBlocksInZ = gobBlocksInZ; + GobBlocksInTileX = gobBlocksInTileX; + Target = target; + FormatInfo = formatInfo; + DepthStencilMode = depthStencilMode; + SwizzleR = swizzleR; + SwizzleG = swizzleG; + SwizzleB = swizzleB; + SwizzleA = swizzleA; + } + + /// + /// Gets the real texture depth. + /// Returns 1 for any target other than 3D textures. + /// + /// Texture depth + public int GetDepth() + { + return GetDepth(Target, DepthOrLayers); + } + + /// + /// Gets the real texture depth. + /// Returns 1 for any target other than 3D textures. + /// + /// Texture target + /// Texture depth if the texture is 3D, otherwise ignored + /// Texture depth + public static int GetDepth(Target target, int depthOrLayers) + { + return target == Target.Texture3D ? depthOrLayers : 1; + } + + /// + /// Gets the number of layers of the texture. + /// Returns 1 for non-array textures, 6 for cubemap textures, and layer faces for cubemap array textures. + /// + /// The number of texture layers + public int GetLayers() + { + return GetLayers(Target, DepthOrLayers); + } + + /// + /// Gets the number of layers of the texture. + /// Returns 1 for non-array textures, 6 for cubemap textures, and layer faces for cubemap array textures. + /// + /// Texture target + /// Texture layers if the is a array texture, ignored otherwise + /// The number of texture layers + public static int GetLayers(Target target, int depthOrLayers) + { + if (target == Target.Texture2DArray || target == Target.Texture2DMultisampleArray) + { + return depthOrLayers; + } + else if (target == Target.CubemapArray) + { + return depthOrLayers * 6; + } + else if (target == Target.Cubemap) + { + return 6; + } + else + { + return 1; + } + } + + /// + /// Gets the number of 2D slices of the texture. + /// Returns 6 for cubemap textures, layer faces for cubemap array textures, and DepthOrLayers for everything else. + /// + /// The number of texture slices + public int GetSlices() + { + if (Target == Target.Texture3D || Target == Target.Texture2DArray || Target == Target.Texture2DMultisampleArray) + { + return DepthOrLayers; + } + else if (Target == Target.CubemapArray) + { + return DepthOrLayers * 6; + } + else if (Target == Target.Cubemap) + { + return 6; + } + else + { + return 1; + } + } + + /// + /// Calculates the size information from the texture information. + /// + /// Optional size of each texture layer in bytes + /// Texture size information + public SizeInfo CalculateSizeInfo(int layerSize = 0) + { + if (Target == Target.TextureBuffer) + { + return new SizeInfo(Width * FormatInfo.BytesPerPixel); + } + else if (IsLinear) + { + return SizeCalculator.GetLinearTextureSize( + Stride, + Height, + FormatInfo.BlockHeight); + } + else + { + return SizeCalculator.GetBlockLinearTextureSize( + Width, + Height, + GetDepth(), + Levels, + GetLayers(), + FormatInfo.BlockWidth, + FormatInfo.BlockHeight, + FormatInfo.BytesPerPixel, + GobBlocksInY, + GobBlocksInZ, + GobBlocksInTileX, + layerSize); + } + } + + /// + /// Creates texture information for a given mipmap level of the specified parent texture and this information. + /// + /// The parent texture + /// The first level of the texture view + /// True if the parent format should be inherited + /// The adjusted texture information with the new size + public TextureInfo CreateInfoForLevelView(Texture parent, int firstLevel, bool parentFormat) + { + // When the texture is used as view of another texture, we must + // ensure that the sizes are valid, otherwise data uploads would fail + // (and the size wouldn't match the real size used on the host API). + // Given a parent texture from where the view is created, we have the + // following rules: + // - The view size must be equal to the parent size, divided by (2 ^ l), + // where l is the first mipmap level of the view. The division result must + // be rounded down, and the result must be clamped to 1. + // - If the parent format is compressed, and the view format isn't, the + // view size is calculated as above, but the width and height of the + // view must be also divided by the compressed format block width and height. + // - If the parent format is not compressed, and the view is, the view + // size is calculated as described on the first point, but the width and height + // of the view must be also multiplied by the block width and height. + int width = Math.Max(1, parent.Info.Width >> firstLevel); + int height = Math.Max(1, parent.Info.Height >> firstLevel); + + if (parent.Info.FormatInfo.IsCompressed && !FormatInfo.IsCompressed) + { + width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth); + height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight); + } + else if (!parent.Info.FormatInfo.IsCompressed && FormatInfo.IsCompressed) + { + width *= FormatInfo.BlockWidth; + height *= FormatInfo.BlockHeight; + } + + int depthOrLayers; + + if (Target == Target.Texture3D) + { + depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel); + } + else + { + depthOrLayers = DepthOrLayers; + } + + // 2D and 2D multisample textures are not considered compatible. + // This specific case is required for copies, where the source texture might be multisample. + // In this case, we inherit the parent texture multisample state. + Target target = Target; + int samplesInX = SamplesInX; + int samplesInY = SamplesInY; + + if (target == Target.Texture2D && parent.Target == Target.Texture2DMultisample) + { + target = Target.Texture2DMultisample; + samplesInX = parent.Info.SamplesInX; + samplesInY = parent.Info.SamplesInY; + } + + return new TextureInfo( + GpuAddress, + width, + height, + depthOrLayers, + Levels, + samplesInX, + samplesInY, + Stride, + IsLinear, + GobBlocksInY, + GobBlocksInZ, + GobBlocksInTileX, + target, + parentFormat ? parent.Info.FormatInfo : FormatInfo, + DepthStencilMode, + SwizzleR, + SwizzleG, + SwizzleB, + SwizzleA); + } + + /// + /// Creates texture information for a given format and this information. + /// + /// Format for the new texture info + /// New info with the specified format + public TextureInfo CreateInfoWithFormat(FormatInfo formatInfo) + { + return new TextureInfo( + GpuAddress, + Width, + Height, + DepthOrLayers, + Levels, + SamplesInX, + SamplesInY, + Stride, + IsLinear, + GobBlocksInY, + GobBlocksInZ, + GobBlocksInTileX, + Target, + formatInfo, + DepthStencilMode, + SwizzleR, + SwizzleG, + SwizzleB, + SwizzleA); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs new file mode 100644 index 00000000..db292146 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -0,0 +1,591 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Shader; +using System; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture manager. + /// + class TextureManager : IDisposable + { + private readonly GpuContext _context; + private readonly GpuChannel _channel; + + private readonly TextureBindingsManager _cpBindingsManager; + private readonly TextureBindingsManager _gpBindingsManager; + private readonly TextureBindingsArrayCache _bindingsArrayCache; + private readonly TexturePoolCache _texturePoolCache; + private readonly SamplerPoolCache _samplerPoolCache; + + private readonly Texture[] _rtColors; + private readonly ITexture[] _rtHostColors; + private readonly bool[] _rtColorsBound; + private Texture _rtDepthStencil; + private ITexture _rtHostDs; + private bool _rtDsBound; + + public int ClipRegionWidth { get; private set; } + public int ClipRegionHeight { get; private set; } + + /// + /// The scaling factor applied to all currently bound render targets. + /// + public float RenderTargetScale { get; private set; } = 1f; + + /// + /// Creates a new instance of the texture manager. + /// + /// GPU context that the texture manager belongs to + /// GPU channel that the texture manager belongs to + public TextureManager(GpuContext context, GpuChannel channel) + { + _context = context; + _channel = channel; + + TexturePoolCache texturePoolCache = new(context); + SamplerPoolCache samplerPoolCache = new(context); + + _bindingsArrayCache = new TextureBindingsArrayCache(context, channel); + _cpBindingsManager = new TextureBindingsManager(context, channel, _bindingsArrayCache, texturePoolCache, samplerPoolCache, isCompute: true); + _gpBindingsManager = new TextureBindingsManager(context, channel, _bindingsArrayCache, texturePoolCache, samplerPoolCache, isCompute: false); + _texturePoolCache = texturePoolCache; + _samplerPoolCache = samplerPoolCache; + + _rtColors = new Texture[Constants.TotalRenderTargets]; + _rtHostColors = new ITexture[Constants.TotalRenderTargets]; + _rtColorsBound = new bool[Constants.TotalRenderTargets]; + } + + /// + /// Sets the texture and image bindings for the compute pipeline. + /// + /// Bindings for the active shader + public void SetComputeBindings(CachedShaderBindings bindings) + { + _cpBindingsManager.SetBindings(bindings); + } + + /// + /// Sets the texture and image bindings for the graphics pipeline. + /// + /// Bindings for the active shader + public void SetGraphicsBindings(CachedShaderBindings bindings) + { + _gpBindingsManager.SetBindings(bindings); + } + + /// + /// Sets the texture constant buffer index on the compute pipeline. + /// + /// The texture constant buffer index + public void SetComputeTextureBufferIndex(int index) + { + _cpBindingsManager.SetTextureBufferIndex(index); + } + + /// + /// Sets the texture constant buffer index on the graphics pipeline. + /// + /// The texture constant buffer index + public void SetGraphicsTextureBufferIndex(int index) + { + _gpBindingsManager.SetTextureBufferIndex(index); + } + + /// + /// Sets the current sampler pool on the compute pipeline. + /// + /// The start GPU virtual address of the sampler pool + /// The maximum ID of the sampler pool + /// The indexing type of the sampler pool + public void SetComputeSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex) + { + _cpBindingsManager.SetSamplerPool(gpuVa, maximumId, samplerIndex); + } + + /// + /// Sets the current sampler pool on the graphics pipeline. + /// + /// The start GPU virtual address of the sampler pool + /// The maximum ID of the sampler pool + /// The indexing type of the sampler pool + public void SetGraphicsSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex) + { + _gpBindingsManager.SetSamplerPool(gpuVa, maximumId, samplerIndex); + } + + /// + /// Sets the current texture pool on the compute pipeline. + /// + /// The start GPU virtual address of the texture pool + /// The maximum ID of the texture pool + public void SetComputeTexturePool(ulong gpuVa, int maximumId) + { + _cpBindingsManager.SetTexturePool(gpuVa, maximumId); + } + + /// + /// Sets the current texture pool on the graphics pipeline. + /// + /// The start GPU virtual address of the texture pool + /// The maximum ID of the texture pool + public void SetGraphicsTexturePool(ulong gpuVa, int maximumId) + { + _gpBindingsManager.SetTexturePool(gpuVa, maximumId); + } + + /// + /// Check if a texture's scale must be updated to match the configured resolution scale. + /// + /// The texture to check + /// True if the scale needs updating, false if the scale is up to date + private static bool ScaleNeedsUpdated(Texture texture) + { + return texture != null && !(texture.ScaleMode == TextureScaleMode.Blacklisted || texture.ScaleMode == TextureScaleMode.Undesired) && texture.ScaleFactor != GraphicsConfig.ResScale; + } + + /// + /// Sets the render target color buffer. + /// + /// The index of the color buffer to set (up to 8) + /// The color buffer texture + /// True if render target scale must be updated. + public bool SetRenderTargetColor(int index, Texture color) + { + bool hasValue = color != null; + bool changesScale = (hasValue != (_rtColors[index] != null)) || (hasValue && RenderTargetScale != color.ScaleFactor); + + if (_rtColors[index] != color) + { + if (_rtColorsBound[index]) + { + _rtColors[index]?.SignalModifying(false); + } + else + { + _rtColorsBound[index] = true; + } + + if (color != null) + { + color.SynchronizeMemory(); + color.SignalModifying(true); + } + + _rtColors[index] = color; + } + + return changesScale || ScaleNeedsUpdated(color); + } + + /// + /// Sets the render target depth-stencil buffer. + /// + /// The depth-stencil buffer texture + /// True if render target scale must be updated. + public bool SetRenderTargetDepthStencil(Texture depthStencil) + { + bool hasValue = depthStencil != null; + bool changesScale = (hasValue != (_rtDepthStencil != null)) || (hasValue && RenderTargetScale != depthStencil.ScaleFactor); + + if (_rtDepthStencil != depthStencil) + { + if (_rtDsBound) + { + _rtDepthStencil?.SignalModifying(false); + } + else + { + _rtDsBound = true; + } + + if (depthStencil != null) + { + depthStencil.SynchronizeMemory(); + depthStencil.SignalModifying(true); + } + + _rtDepthStencil = depthStencil; + } + + return changesScale || ScaleNeedsUpdated(depthStencil); + } + + /// + /// Sets the host clip region, which should be the intersection of all render target texture sizes. + /// + /// Width of the clip region, defined as the minimum width across all bound textures + /// Height of the clip region, defined as the minimum height across all bound textures + public void SetClipRegion(int width, int height) + { + ClipRegionWidth = width; + ClipRegionHeight = height; + } + + /// + /// Gets the first available bound colour target, or the depth stencil target if not present. + /// + /// The first bound colour target, otherwise the depth stencil target + public Texture GetAnyRenderTarget() + { + return _rtColors[0] ?? _rtDepthStencil; + } + + /// + /// Updates the Render Target scale, given the currently bound render targets. + /// This will update scale to match the configured scale, scale textures that are eligible but not scaled, + /// and propagate blacklisted status from one texture to the ones bound with it. + /// + /// If this is not -1, it indicates that only the given indexed target will be used. + public void UpdateRenderTargetScale(int singleUse) + { + // Make sure all scales for render targets are at the highest they should be. Blacklisted targets should propagate their scale to the other targets. + bool mismatch = false; + bool blacklisted = false; + bool hasUpscaled = false; + bool hasUndesired = false; + float targetScale = GraphicsConfig.ResScale; + + void ConsiderTarget(Texture target) + { + if (target == null) + { + return; + } + + float scale = target.ScaleFactor; + + switch (target.ScaleMode) + { + case TextureScaleMode.Blacklisted: + mismatch |= scale != 1f; + blacklisted = true; + break; + case TextureScaleMode.Eligible: + mismatch = true; // We must make a decision. + break; + case TextureScaleMode.Undesired: + hasUndesired = true; + mismatch |= scale != 1f || hasUpscaled; // If another target is upscaled, scale this one up too. + break; + case TextureScaleMode.Scaled: + hasUpscaled = true; + mismatch |= hasUndesired || scale != targetScale; // If the target scale has changed, reset the scale for all targets. + break; + } + } + + if (singleUse != -1) + { + // If only one target is in use (by a clear, for example) the others do not need to be checked for mismatching scale. + ConsiderTarget(_rtColors[singleUse]); + } + else + { + foreach (Texture color in _rtColors) + { + ConsiderTarget(color); + } + } + + ConsiderTarget(_rtDepthStencil); + + mismatch |= blacklisted && hasUpscaled; + + if (blacklisted || (hasUndesired && !hasUpscaled)) + { + targetScale = 1f; + } + + if (mismatch) + { + if (blacklisted) + { + // Propagate the blacklisted state to the other textures. + foreach (Texture color in _rtColors) + { + color?.BlacklistScale(); + } + + _rtDepthStencil?.BlacklistScale(); + } + else + { + // Set the scale of the other textures. + foreach (Texture color in _rtColors) + { + color?.SetScale(targetScale); + } + + _rtDepthStencil?.SetScale(targetScale); + } + } + + RenderTargetScale = targetScale; + } + + /// + /// Gets a texture and a sampler from their respective pools from a texture ID and a sampler ID. + /// + /// ID of the texture + /// ID of the sampler + public (Texture, Sampler) GetGraphicsTextureAndSampler(int textureId, int samplerId) + { + return _gpBindingsManager.GetTextureAndSampler(textureId, samplerId); + } + + /// + /// Commits bindings on the compute pipeline. + /// + /// Specialization state for the bound shader + /// True if all bound textures match the current shader specialization state, false otherwise + public bool CommitComputeBindings(ShaderSpecializationState specState) + { + // Every time we switch between graphics and compute work, + // we must rebind everything. + // Since compute work happens less often, we always do that + // before and after the compute dispatch. + + _texturePoolCache.Tick(); + _samplerPoolCache.Tick(); + + _cpBindingsManager.Rebind(); + bool result = _cpBindingsManager.CommitBindings(specState); + _gpBindingsManager.Rebind(); + + return result; + } + + /// + /// Commits bindings on the graphics pipeline. + /// + /// Specialization state for the bound shader + /// True if there is a scale mismatch in the render targets, indicating they must be re-evaluated + /// True if all bound textures match the current shader specialization state, false otherwise + public bool CommitGraphicsBindings(ShaderSpecializationState specState, out bool scaleMismatch) + { + _texturePoolCache.Tick(); + _samplerPoolCache.Tick(); + + bool result = _gpBindingsManager.CommitBindings(specState); + + scaleMismatch = UpdateRenderTargets(); + + return result; + } + + /// + /// Returns a texture pool from the cache, with the given address and maximum id. + /// + /// GPU virtual address of the texture pool + /// Maximum ID of the texture pool + /// The texture pool + public TexturePool GetTexturePool(ulong poolGpuVa, int maximumId) + { + ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa); + + TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId, _bindingsArrayCache); + + return texturePool; + } + + /// + /// Gets a texture descriptor used on the compute pipeline. + /// + /// GPU virtual address of the texture pool + /// Index of the constant buffer with texture handles + /// Maximum ID of the texture pool + /// Shader "fake" handle of the texture + /// Shader constant buffer slot of the texture + /// The texture descriptor + public TextureDescriptor GetComputeTextureDescriptor(ulong poolGpuVa, int bufferIndex, int maximumId, int handle, int cbufSlot) + { + return _cpBindingsManager.GetTextureDescriptor(poolGpuVa, bufferIndex, maximumId, 0, handle, cbufSlot); + } + + /// + /// Gets a texture descriptor used on the graphics pipeline. + /// + /// GPU virtual address of the texture pool + /// Index of the constant buffer with texture handles + /// Maximum ID of the texture pool + /// Index of the shader stage where the texture is bound + /// Shader "fake" handle of the texture + /// Shader constant buffer slot of the texture + /// The texture descriptor + public TextureDescriptor GetGraphicsTextureDescriptor( + ulong poolGpuVa, + int bufferIndex, + int maximumId, + int stageIndex, + int handle, + int cbufSlot) + { + return _gpBindingsManager.GetTextureDescriptor(poolGpuVa, bufferIndex, maximumId, stageIndex, handle, cbufSlot); + } + + /// + /// Update host framebuffer attachments based on currently bound render target buffers. + /// + /// True if there is a scale mismatch in the render targets, indicating they must be re-evaluated + public bool UpdateRenderTargets() + { + bool anyChanged = false; + float expectedScale = RenderTargetScale; + bool scaleMismatch = false; + + Texture dsTexture = _rtDepthStencil; + ITexture hostDsTexture = null; + + if (dsTexture != null) + { + hostDsTexture = dsTexture.HostTexture; + + if (!_rtDsBound) + { + dsTexture.SignalModifying(true); + _rtDsBound = true; + } + } + + if (_rtHostDs != hostDsTexture) + { + _rtHostDs = hostDsTexture; + anyChanged = true; + + if (dsTexture != null && dsTexture.ScaleFactor != expectedScale) + { + scaleMismatch = true; + } + } + + for (int index = 0; index < _rtColors.Length; index++) + { + Texture texture = _rtColors[index]; + ITexture hostTexture = null; + + if (texture != null) + { + hostTexture = texture.HostTexture; + + if (!_rtColorsBound[index]) + { + texture.SignalModifying(true); + _rtColorsBound[index] = true; + } + } + + if (_rtHostColors[index] != hostTexture) + { + _rtHostColors[index] = hostTexture; + anyChanged = true; + + if (texture != null && texture.ScaleFactor != expectedScale) + { + scaleMismatch = true; + } + } + } + + if (anyChanged) + { + _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs); + } + + return scaleMismatch; + } + + /// + /// Update host framebuffer attachments based on currently bound render target buffers. + /// + /// + /// All color attachments will be unbound. + /// + public void UpdateRenderTargetDepthStencil() + { + new Span(_rtHostColors).Clear(); + _rtHostDs = _rtDepthStencil?.HostTexture; + + _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs); + } + + /// + /// Marks all currently bound render target textures as modified, and also makes them be set as modified again on next use. + /// + public void RefreshModifiedTextures() + { + Texture dsTexture = _rtDepthStencil; + + if (dsTexture != null && _rtDsBound) + { + dsTexture.SignalModifying(false); + _rtDsBound = false; + } + + for (int index = 0; index < _rtColors.Length; index++) + { + Texture texture = _rtColors[index]; + + if (texture != null && _rtColorsBound[index]) + { + texture.SignalModifying(false); + _rtColorsBound[index] = false; + } + } + } + + /// + /// Forces the texture and sampler pools to be re-loaded from the cache on next use. + /// + public void ReloadPools() + { + _cpBindingsManager.ReloadPools(); + _gpBindingsManager.ReloadPools(); + } + + /// + /// Forces all textures, samplers, images and render targets to be rebound the next time + /// CommitGraphicsBindings is called. + /// + public void Rebind() + { + _gpBindingsManager.Rebind(); + + for (int index = 0; index < _rtHostColors.Length; index++) + { + _rtHostColors[index] = null; + } + + _rtHostDs = null; + } + + /// + /// Disposes the texture manager. + /// It's an error to use the texture manager after disposal. + /// + public void Dispose() + { + // Textures are owned by the texture cache, so we shouldn't dispose the texture pool cache. + _samplerPoolCache.Dispose(); + + for (int i = 0; i < _rtColors.Length; i++) + { + if (_rtColorsBound[i]) + { + _rtColors[i]?.DecrementReferenceCount(); + } + + _rtColors[i] = null; + } + + if (_rtDsBound) + { + _rtDepthStencil?.DecrementReferenceCount(); + } + + _rtDepthStencil = null; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureMatchQuality.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureMatchQuality.cs new file mode 100644 index 00000000..b918911b --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureMatchQuality.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + enum TextureMatchQuality + { + NoMatch, + FormatAlias, + Perfect, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs new file mode 100644 index 00000000..43b83ae1 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs @@ -0,0 +1,68 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Multisampled texture samples count. + /// + enum TextureMsaaMode + { + Ms1x1 = 0, + Ms2x2 = 2, + Ms4x2 = 4, + Ms2x1 = 5, + Ms4x4 = 6, + } + + static class TextureMsaaModeConverter + { + /// + /// Returns the total number of samples from the MSAA mode. + /// + /// The MSAA mode + /// The total number of samples + public static int SamplesCount(this TextureMsaaMode msaaMode) + { + return msaaMode switch + { + TextureMsaaMode.Ms2x1 => 2, + TextureMsaaMode.Ms2x2 => 4, + TextureMsaaMode.Ms4x2 => 8, + TextureMsaaMode.Ms4x4 => 16, + _ => 1, + }; + } + + /// + /// Returns the number of samples in the X direction from the MSAA mode. + /// + /// The MSAA mode + /// The number of samples in the X direction + public static int SamplesInX(this TextureMsaaMode msaaMode) + { + return msaaMode switch + { + TextureMsaaMode.Ms2x1 => 2, + TextureMsaaMode.Ms2x2 => 2, + TextureMsaaMode.Ms4x2 => 4, + TextureMsaaMode.Ms4x4 => 4, + _ => 1, + }; + } + + /// + /// Returns the number of samples in the Y direction from the MSAA mode. + /// + /// The MSAA mode + /// The number of samples in the Y direction + public static int SamplesInY(this TextureMsaaMode msaaMode) + { + return msaaMode switch + { + TextureMsaaMode.Ms2x1 => 1, + TextureMsaaMode.Ms2x2 => 2, + TextureMsaaMode.Ms4x2 => 2, + TextureMsaaMode.Ms4x4 => 4, + _ => 1, + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs new file mode 100644 index 00000000..4ed0a93c --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -0,0 +1,642 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Texture; +using Ryujinx.Memory.Range; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Numerics; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture pool. + /// + class TexturePool : Pool, IPool + { + /// + /// A request to dereference a texture from a pool. + /// + private readonly struct DereferenceRequest + { + /// + /// Whether the dereference is due to a mapping change or not. + /// + public readonly bool IsRemapped; + + /// + /// The texture being dereferenced. + /// + public readonly Texture Texture; + + /// + /// The ID of the pool entry this reference belonged to. + /// + public readonly int ID; + + /// + /// Create a dereference request for a texture with a specific pool ID, and remapped flag. + /// + /// Whether the dereference is due to a mapping change or not + /// The texture being dereferenced + /// The ID of the pool entry, used to restore remapped textures + private DereferenceRequest(bool isRemapped, Texture texture, int id) + { + IsRemapped = isRemapped; + Texture = texture; + ID = id; + } + + /// + /// Create a dereference request for a texture removal. + /// + /// The texture being removed + /// A texture removal dereference request + public static DereferenceRequest Remove(Texture texture) + { + return new DereferenceRequest(false, texture, 0); + } + + /// + /// Create a dereference request for a texture remapping with a specific pool ID. + /// + /// The texture being remapped + /// The ID of the pool entry, used to restore remapped textures + /// A remap dereference request + public static DereferenceRequest Remap(Texture texture, int id) + { + return new DereferenceRequest(true, texture, id); + } + } + + private readonly GpuChannel _channel; + private readonly ConcurrentQueue _dereferenceQueue = new(); + private TextureDescriptor _defaultDescriptor; + + /// + /// Linked list node used on the texture pool cache. + /// + public LinkedListNode CacheNode { get; set; } + + /// + /// Timestamp used by the texture pool cache, updated on every use of this texture pool. + /// + public ulong CacheTimestamp { get; set; } + + /// + /// Creates a new instance of the texture pool. + /// + /// GPU context that the texture pool belongs to + /// GPU channel that the texture pool belongs to + /// Address of the texture pool in guest memory + /// Maximum texture ID of the texture pool (equal to maximum textures minus one) + public TexturePool(GpuContext context, GpuChannel channel, ulong address, int maximumId) : base(context, channel.MemoryManager.Physical, address, maximumId) + { + _channel = channel; + } + + /// + /// Gets the texture descripor and texture with the given ID with no bounds check or synchronization. + /// + /// ID of the texture. This is effectively a zero-based index + /// The texture with the given ID + /// The texture descriptor with the given ID + private ref readonly TextureDescriptor GetInternal(int id, out Texture texture) + { + texture = Items[id]; + + ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(id); + + if (texture == null) + { + texture = PhysicalMemory.TextureCache.FindShortCache(descriptor); + + if (texture == null) + { + TextureInfo info = GetInfo(descriptor, out int layerSize); + + // The dereference queue can put our texture back on the cache. + if ((texture = ProcessDereferenceQueue(id)) != null) + { + return ref descriptor; + } + + texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize); + + // If this happens, then the texture address is invalid, we can't add it to the cache. + if (texture == null) + { + return ref descriptor; + } + } + else + { + texture.SynchronizeMemory(); + } + + Items[id] = texture; + + texture.IncrementReferenceCount(this, id, descriptor.UnpackAddress()); + + DescriptorCache[id] = descriptor; + } + else + { + // On the path above (texture not yet in the pool), memory is automatically synchronized on texture creation. + texture.SynchronizeMemory(); + } + + return ref descriptor; + } + + /// + /// Gets the texture with the given ID. + /// + /// ID of the texture. This is effectively a zero-based index + /// The texture with the given ID + public override Texture Get(int id) + { + if ((uint)id >= Items.Length) + { + return null; + } + + if (SequenceNumber != Context.SequenceNumber) + { + SequenceNumber = Context.SequenceNumber; + + SynchronizeMemory(); + } + + GetInternal(id, out Texture texture); + + return texture; + } + + /// + /// Gets the texture descriptor and texture with the given ID. + /// + /// + /// This method assumes that the pool has been manually synchronized before doing binding. + /// + /// ID of the texture. This is effectively a zero-based index + /// The texture with the given ID + /// The texture descriptor with the given ID + public ref readonly TextureDescriptor GetForBinding(int id, out Texture texture) + { + if ((uint)id >= Items.Length) + { + texture = null; + return ref _defaultDescriptor; + } + + // When getting for binding, assume the pool has already been synchronized. + + return ref GetInternal(id, out texture); + } + + /// + /// Checks if the pool was modified, and returns the last sequence number where a modification was detected. + /// + /// A number that increments each time a modification is detected + public int CheckModified() + { + if (SequenceNumber != Context.SequenceNumber) + { + SequenceNumber = Context.SequenceNumber; + + SynchronizeMemory(); + } + + return ModifiedSequenceNumber; + } + + /// + /// Forcibly remove a texture from this pool's items. + /// If deferred, the dereference will be queued to occur on the render thread. + /// + /// The texture being removed + /// The ID of the texture in this pool + /// If true, queue the dereference to happen on the render thread, otherwise dereference immediately + public void ForceRemove(Texture texture, int id, bool deferred) + { + var previous = Interlocked.Exchange(ref Items[id], null); + + if (deferred) + { + if (previous != null) + { + _dereferenceQueue.Enqueue(DereferenceRequest.Remove(texture)); + } + } + else + { + texture.DecrementReferenceCount(); + } + } + + /// + /// Queues a request to update a texture's mapping. + /// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped. + /// + /// Texture with potential mapping change + /// ID in cache of texture with potential mapping change + public void QueueUpdateMapping(Texture texture, int id) + { + if (Interlocked.Exchange(ref Items[id], null) == texture) + { + _dereferenceQueue.Enqueue(DereferenceRequest.Remap(texture, id)); + } + } + + /// + /// Process the dereference queue, decrementing the reference count for each texture in it. + /// This is used to ensure that texture disposal happens on the render thread. + /// + /// The ID of the entry that triggered this method + /// Texture that matches the entry ID if it has been readded to the cache. + private Texture ProcessDereferenceQueue(int id = -1) + { + while (_dereferenceQueue.TryDequeue(out DereferenceRequest request)) + { + Texture texture = request.Texture; + + // Unmapped storage textures can swap their ranges. The texture must be storage with no views or dependencies. + // TODO: Would need to update ranges on views, or guarantee that ones where the range changes can be instantly deleted. + + if (request.IsRemapped && texture.Group.Storage == texture && !texture.HasViews && !texture.Group.HasCopyDependencies) + { + // Has the mapping for this texture changed? + ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(request.ID); + + ulong address = descriptor.UnpackAddress(); + + if (!descriptor.Equals(ref DescriptorCache[request.ID])) + { + // If the pool entry has already been replaced, just remove the texture. + + texture.DecrementReferenceCount(); + continue; + } + + MultiRange range = _channel.MemoryManager.Physical.TextureCache.UpdatePartiallyMapped(_channel.MemoryManager, address, texture); + + // If the texture is not mapped at all, delete its reference. + + if (range.Count == 1 && range.GetSubRange(0).Address == MemoryManager.PteUnmapped) + { + texture.DecrementReferenceCount(); + continue; + } + + Items[request.ID] = texture; + + // Create a new pool reference, as the last one was removed on unmap. + + texture.IncrementReferenceCount(this, request.ID, address); + texture.DecrementReferenceCount(); + + // Refetch the range. Changes since the last check could have been lost + // as the cache entry was not restored (required to queue mapping change). + + range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size); + + if (!range.Equals(texture.Range)) + { + // Part of the texture was mapped or unmapped. Replace the range and regenerate tracking handles. + if (!_channel.MemoryManager.Physical.TextureCache.UpdateMapping(texture, range)) + { + // Texture could not be remapped due to a collision, just delete it. + if (Interlocked.Exchange(ref Items[request.ID], null) != null) + { + // If this is null, a request was already queued to decrement reference. + texture.DecrementReferenceCount(this, request.ID); + } + continue; + } + } + + if (request.ID == id) + { + return texture; + } + } + else + { + texture.DecrementReferenceCount(); + } + } + + return null; + } + + /// + /// Implementation of the texture pool range invalidation. + /// + /// Start address of the range of the texture pool + /// Size of the range being invalidated + protected override void InvalidateRangeImpl(ulong address, ulong size) + { + ProcessDereferenceQueue(); + + ulong endAddress = address + size; + + for (; address < endAddress; address += DescriptorSize) + { + int id = (int)((address - Address) / DescriptorSize); + + Texture texture = Items[id]; + + if (texture != null) + { + ref TextureDescriptor cachedDescriptor = ref DescriptorCache[id]; + ref readonly TextureDescriptor descriptor = ref GetDescriptorRefAddress(address); + + // If the descriptors are the same, the texture is the same, + // we don't need to remove as it was not modified. Just continue. + if (descriptor.Equals(ref cachedDescriptor)) + { + continue; + } + + if (texture.HasOneReference()) + { + _channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor); + } + + if (Interlocked.Exchange(ref Items[id], null) != null) + { + texture.DecrementReferenceCount(this, id); + } + } + } + } + + /// + /// Gets texture information from a texture descriptor. + /// + /// The texture descriptor + /// Layer size for textures using a sub-range of mipmap levels, otherwise 0 + /// The texture information + private static TextureInfo GetInfo(in TextureDescriptor descriptor, out int layerSize) + { + int depthOrLayers = descriptor.UnpackDepth(); + int levels = descriptor.UnpackLevels(); + + TextureMsaaMode msaaMode = descriptor.UnpackTextureMsaaMode(); + + int samplesInX = msaaMode.SamplesInX(); + int samplesInY = msaaMode.SamplesInY(); + + int stride = descriptor.UnpackStride(); + + TextureDescriptorType descriptorType = descriptor.UnpackTextureDescriptorType(); + + bool isLinear = descriptorType == TextureDescriptorType.Linear; + + Target target = descriptor.UnpackTextureTarget().Convert((samplesInX | samplesInY) != 1); + + int width = target == Target.TextureBuffer ? descriptor.UnpackBufferTextureWidth() : descriptor.UnpackWidth(); + int height = descriptor.UnpackHeight(); + + if (target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray) + { + // This is divided back before the backend texture is created. + width *= samplesInX; + height *= samplesInY; + } + + // We use 2D targets for 1D textures as that makes texture cache + // management easier. We don't know the target for render target + // and copies, so those would normally use 2D targets, which are + // not compatible with 1D targets. By doing that we also allow those + // to match when looking for compatible textures on the cache. + if (target == Target.Texture1D) + { + target = Target.Texture2D; + height = 1; + } + else if (target == Target.Texture1DArray) + { + target = Target.Texture2DArray; + height = 1; + } + + uint format = descriptor.UnpackFormat(); + bool srgb = descriptor.UnpackSrgb(); + + ulong gpuVa = descriptor.UnpackAddress(); + + if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo)) + { + if (gpuVa != 0 && format != 0) + { + Logger.Error?.Print(LogClass.Gpu, $"Invalid texture format 0x{format:X} (sRGB: {srgb})."); + } + + formatInfo = FormatInfo.Default; + } + + int gobBlocksInY = descriptor.UnpackGobBlocksInY(); + int gobBlocksInZ = descriptor.UnpackGobBlocksInZ(); + + int gobBlocksInTileX = descriptor.UnpackGobBlocksInTileX(); + + layerSize = 0; + + int minLod = descriptor.UnpackBaseLevel(); + int maxLod = descriptor.UnpackMaxLevelInclusive(); + + // Linear textures don't support mipmaps, so we don't handle this case here. + if ((minLod != 0 || maxLod + 1 != levels) && target != Target.TextureBuffer && !isLinear) + { + int depth = TextureInfo.GetDepth(target, depthOrLayers); + int layers = TextureInfo.GetLayers(target, depthOrLayers); + + SizeInfo sizeInfo = SizeCalculator.GetBlockLinearTextureSize( + width, + height, + depth, + levels, + layers, + formatInfo.BlockWidth, + formatInfo.BlockHeight, + formatInfo.BytesPerPixel, + gobBlocksInY, + gobBlocksInZ, + gobBlocksInTileX); + + layerSize = sizeInfo.LayerSize; + + if (minLod != 0 && minLod < levels) + { + // If the base level is not zero, we additionally add the mip level offset + // to the address, this allows the texture manager to find the base level from the + // address if there is a overlapping texture on the cache that can contain the new texture. + gpuVa += (ulong)sizeInfo.GetMipOffset(minLod); + + width = Math.Max(1, width >> minLod); + height = Math.Max(1, height >> minLod); + + if (target == Target.Texture3D) + { + depthOrLayers = Math.Max(1, depthOrLayers >> minLod); + } + + (gobBlocksInY, gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(height, depth, formatInfo.BlockHeight, gobBlocksInY, gobBlocksInZ, minLod); + } + + levels = (maxLod - minLod) + 1; + } + + levels = ClampLevels(target, width, height, depthOrLayers, levels); + + SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert(); + SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert(); + SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert(); + SwizzleComponent swizzleA = descriptor.UnpackSwizzleA().Convert(); + + DepthStencilMode depthStencilMode = GetDepthStencilMode( + formatInfo.Format, + swizzleR, + swizzleG, + swizzleB, + swizzleA); + + if (formatInfo.Format.IsDepthOrStencil()) + { + swizzleR = SwizzleComponent.Red; + swizzleG = SwizzleComponent.Red; + swizzleB = SwizzleComponent.Red; + + if (depthStencilMode == DepthStencilMode.Depth) + { + swizzleA = SwizzleComponent.One; + } + else + { + swizzleA = SwizzleComponent.Red; + } + } + + return new TextureInfo( + gpuVa, + width, + height, + depthOrLayers, + levels, + samplesInX, + samplesInY, + stride, + isLinear, + gobBlocksInY, + gobBlocksInZ, + gobBlocksInTileX, + target, + formatInfo, + depthStencilMode, + swizzleR, + swizzleG, + swizzleB, + swizzleA); + } + + /// + /// Clamps the amount of mipmap levels to the maximum allowed for the given texture dimensions. + /// + /// Number of texture dimensions (1D, 2D, 3D, Cube, etc) + /// Width of the texture + /// Height of the texture, ignored for 1D textures + /// Depth of the texture for 3D textures, otherwise ignored + /// Original amount of mipmap levels + /// Clamped mipmap levels + private static int ClampLevels(Target target, int width, int height, int depthOrLayers, int levels) + { + int maxSize = width; + + if (target != Target.Texture1D && + target != Target.Texture1DArray) + { + maxSize = Math.Max(maxSize, height); + } + + if (target == Target.Texture3D) + { + maxSize = Math.Max(maxSize, depthOrLayers); + } + + int maxLevels = BitOperations.Log2((uint)maxSize) + 1; + return Math.Min(levels, maxLevels); + } + + /// + /// Gets the texture depth-stencil mode, based on the swizzle components of each color channel. + /// The depth-stencil mode is determined based on how the driver sets those parameters. + /// + /// The format of the texture + /// The texture swizzle components + /// The depth-stencil mode + private static DepthStencilMode GetDepthStencilMode(Format format, params SwizzleComponent[] components) + { + // R = Depth, G = Stencil. + // On 24-bits depth formats, this is inverted (Stencil is R etc). + // NVN setup: + // For depth, A is set to 1.0f, the other components are set to Depth. + // For stencil, all components are set to Stencil. + SwizzleComponent component = components[0]; + + for (int index = 1; index < 4 && !IsRG(component); index++) + { + component = components[index]; + } + + if (!IsRG(component)) + { + return DepthStencilMode.Depth; + } + + if (format == Format.D24UnormS8Uint) + { + return component == SwizzleComponent.Red + ? DepthStencilMode.Stencil + : DepthStencilMode.Depth; + } + else + { + return component == SwizzleComponent.Red + ? DepthStencilMode.Depth + : DepthStencilMode.Stencil; + } + } + + /// + /// Checks if the swizzle component is equal to the red or green channels. + /// + /// The swizzle component to check + /// True if the swizzle component is equal to the red or green, false otherwise + private static bool IsRG(SwizzleComponent component) + { + return component == SwizzleComponent.Red || + component == SwizzleComponent.Green; + } + + /// + /// Decrements the reference count of the texture. + /// This indicates that the texture pool is not using it anymore. + /// + /// The texture to be deleted + protected override void Delete(Texture item) + { + item?.DecrementReferenceCount(this); + } + + public override void Dispose() + { + ProcessDereferenceQueue(); + + base.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs new file mode 100644 index 00000000..5da2c439 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture pool cache. + /// This can keep multiple texture pools, and return the current one as needed. + /// It is useful for applications that uses multiple texture pools. + /// + class TexturePoolCache : PoolCache + { + /// + /// Constructs a new instance of the texture pool. + /// + /// GPU context that the texture pool belongs to + public TexturePoolCache(GpuContext context) : base(context) + { + } + + /// + /// Creates a new instance of the texture pool. + /// + /// GPU context that the texture pool belongs to + /// GPU channel that the texture pool belongs to + /// Address of the texture pool in guest memory + /// Maximum texture ID of the texture pool (equal to maximum textures minus one) + protected override TexturePool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId) + { + return new TexturePool(context, channel, address, maximumId); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs new file mode 100644 index 00000000..aa316a9f --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// The scale mode for a given texture. + /// Blacklisted textures cannot be scaled, Eligible textures have not been scaled yet, + /// and Scaled textures have been scaled already. + /// Undesired textures will stay at 1x until a situation where they must match a scaled texture. + /// + enum TextureScaleMode + { + Eligible = 0, + Scaled = 1, + Blacklisted = 2, + Undesired = 3, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs new file mode 100644 index 00000000..f651420a --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture search flags, defines texture information comparison rules. + /// + [Flags] + enum TextureSearchFlags + { + None = 0, + ForSampler = 1 << 1, + ForCopy = 1 << 2, + DepthAlias = 1 << 3, + WithUpscale = 1 << 4, + NoCreate = 1 << 5, + DiscardData = 1 << 6, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs new file mode 100644 index 00000000..b46b4204 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs @@ -0,0 +1,92 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// Texture target. + /// + enum TextureTarget : byte + { + Texture1D, + Texture2D, + Texture3D, + Cubemap, + Texture1DArray, + Texture2DArray, + TextureBuffer, + Texture2DRect, + CubemapArray, + } + + static class TextureTargetConverter + { + /// + /// Converts the texture target enum to a host compatible, Graphics Abstraction Layer enum. + /// + /// The target enum to convert + /// True if the texture is a multisampled texture + /// The host compatible texture target + public static Target Convert(this TextureTarget target, bool isMultisample) + { + if (isMultisample) + { + switch (target) + { + case TextureTarget.Texture2D: + return Target.Texture2DMultisample; + case TextureTarget.Texture2DArray: + return Target.Texture2DMultisampleArray; + } + } + else + { + switch (target) + { + case TextureTarget.Texture1D: + return Target.Texture1D; + case TextureTarget.Texture2D: + return Target.Texture2D; + case TextureTarget.Texture2DRect: + return Target.Texture2D; + case TextureTarget.Texture3D: + return Target.Texture3D; + case TextureTarget.Texture1DArray: + return Target.Texture1DArray; + case TextureTarget.Texture2DArray: + return Target.Texture2DArray; + case TextureTarget.Cubemap: + return Target.Cubemap; + case TextureTarget.CubemapArray: + return Target.CubemapArray; + case TextureTarget.TextureBuffer: + return Target.TextureBuffer; + } + } + + return Target.Texture1D; + } + + /// + /// Converts the texture target enum to a shader sampler type. + /// + /// The target enum to convert + /// The shader sampler type + public static SamplerType ConvertSamplerType(this TextureTarget target) + { + return target switch + { + TextureTarget.Texture1D => SamplerType.Texture1D, + TextureTarget.Texture2D => SamplerType.Texture2D, + TextureTarget.Texture3D => SamplerType.Texture3D, + TextureTarget.Cubemap => SamplerType.TextureCube, + TextureTarget.Texture1DArray => SamplerType.Texture1D | SamplerType.Array, + TextureTarget.Texture2DArray => SamplerType.Texture2D | SamplerType.Array, + TextureTarget.TextureBuffer => SamplerType.TextureBuffer, + TextureTarget.Texture2DRect => SamplerType.Texture2D, + TextureTarget.CubemapArray => SamplerType.TextureCube | SamplerType.Array, + _ => SamplerType.Texture2D, + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs new file mode 100644 index 00000000..22476c39 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Gpu.Image +{ + /// + /// The level of view compatibility one texture has to another. + /// Values are increasing in compatibility from 0 (incompatible). + /// + enum TextureViewCompatibility + { + Incompatible = 0, + LayoutIncompatible, + CopyOnly, + FormatAlias, + Full, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs new file mode 100644 index 00000000..e060e0b4 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -0,0 +1,1037 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Synchronization; +using Ryujinx.Memory.Range; +using Ryujinx.Memory.Tracking; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + delegate void BufferFlushAction(ulong address, ulong size, ulong syncNumber); + + /// + /// Buffer, used to store vertex and index data, uniform and storage buffers, and others. + /// + class Buffer : IRange, ISyncActionHandler, IDisposable + { + private const ulong GranularBufferThreshold = 4096; + + private readonly GpuContext _context; + private readonly PhysicalMemory _physicalMemory; + + /// + /// Host buffer handle. + /// + public BufferHandle Handle { get; private set; } + + /// + /// Start address of the buffer in guest memory. + /// + public ulong Address { get; } + + /// + /// Size of the buffer in bytes. + /// + public ulong Size { get; } + + /// + /// End address of the buffer in guest memory. + /// + public ulong EndAddress => Address + Size; + + /// + /// Increments when the buffer is (partially) unmapped or disposed. + /// + public int UnmappedSequence { get; private set; } + + /// + /// Indicates if the buffer can be used in a sparse buffer mapping. + /// + public bool SparseCompatible { get; } + + /// + /// Ranges of the buffer that have been modified on the GPU. + /// Ranges defined here cannot be updated from CPU until a CPU waiting sync point is reached. + /// Then, write tracking will signal, wait for GPU sync (generated at the syncpoint) and flush these regions. + /// + /// + /// This is null until at least one modification occurs. + /// + private BufferModifiedRangeList _modifiedRanges = null; + + /// + /// A structure that is used to flush buffer data back to a host mapped buffer for cached readback. + /// Only used if the buffer data is explicitly owned by device local memory. + /// + private BufferPreFlush _preFlush = null; + + /// + /// Usage tracking state that determines what type of backing the buffer should use. + /// + public BufferBackingState BackingState; + + private readonly MultiRegionHandle _memoryTrackingGranular; + private readonly RegionHandle _memoryTracking; + + private readonly RegionSignal _externalFlushDelegate; + private readonly Action _loadDelegate; + private readonly Action _modifiedDelegate; + + private HashSet _virtualDependencies; + private readonly ReaderWriterLockSlim _virtualDependenciesLock; + + private int _sequenceNumber; + + private readonly bool _useGranular; + private bool _syncActionRegistered; + + private int _referenceCount = 1; + + private ulong _dirtyStart = ulong.MaxValue; + private ulong _dirtyEnd = ulong.MaxValue; + + /// + /// Creates a new instance of the buffer. + /// + /// GPU context that the buffer belongs to + /// Physical memory where the buffer is mapped + /// Start address of the buffer + /// Size of the buffer in bytes + /// The type of usage that created the buffer + /// Indicates if the buffer can be used in a sparse buffer mapping + /// Buffers which this buffer contains, and will inherit tracking handles from + public Buffer( + GpuContext context, + PhysicalMemory physicalMemory, + ulong address, + ulong size, + BufferStage stage, + bool sparseCompatible, + IEnumerable baseBuffers = null) + { + _context = context; + _physicalMemory = physicalMemory; + Address = address; + Size = size; + SparseCompatible = sparseCompatible; + + BackingState = new BufferBackingState(_context, this, stage, baseBuffers); + + BufferAccess access = BackingState.SwitchAccess(this); + + Handle = context.Renderer.CreateBuffer((int)size, access); + + _useGranular = size > GranularBufferThreshold; + + IEnumerable baseHandles = null; + + if (baseBuffers != null) + { + baseHandles = baseBuffers.SelectMany(buffer => + { + if (buffer._useGranular) + { + return buffer._memoryTrackingGranular.GetHandles(); + } + else + { + return Enumerable.Repeat(buffer._memoryTracking, 1); + } + }); + } + + if (_useGranular) + { + _memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Buffer, RegionFlags.UnalignedAccess, baseHandles); + + _memoryTrackingGranular.RegisterPreciseAction(address, size, PreciseAction); + } + else + { + _memoryTracking = physicalMemory.BeginTracking(address, size, ResourceKind.Buffer, RegionFlags.UnalignedAccess); + + if (baseHandles != null) + { + _memoryTracking.Reprotect(false); + + foreach (IRegionHandle handle in baseHandles) + { + if (handle.Dirty) + { + _memoryTracking.Reprotect(true); + } + + handle.Dispose(); + } + } + + _memoryTracking.RegisterPreciseAction(PreciseAction); + } + + _externalFlushDelegate = new RegionSignal(ExternalFlush); + _loadDelegate = new Action(LoadRegion); + _modifiedDelegate = new Action(RegionModified); + + _virtualDependenciesLock = new ReaderWriterLockSlim(); + } + + /// + /// Recreates the backing buffer based on the desired access type + /// reported by the backing state struct. + /// + private void ChangeBacking() + { + BufferAccess access = BackingState.SwitchAccess(this); + + BufferHandle newHandle = _context.Renderer.CreateBuffer((int)Size, access); + + _context.Renderer.Pipeline.CopyBuffer(Handle, newHandle, 0, 0, (int)Size); + + _modifiedRanges?.SelfMigration(); + + // If swtiching from device local to host mapped, pre-flushing data no longer makes sense. + // This is set to null and disposed when the migration fully completes. + _preFlush = null; + + Handle = newHandle; + + _physicalMemory.BufferCache.BufferBackingChanged(this); + } + + /// + /// Gets a sub-range from the buffer, from a start address til a page boundary after the given size. + /// + /// + /// This can be used to bind and use sub-ranges of the buffer on the host API. + /// + /// Start address of the sub-range, must be greater than or equal to the buffer address + /// Size in bytes of the sub-range, must be less than or equal to the buffer size + /// Whether the buffer will be written to by this use + /// The buffer sub-range + public BufferRange GetRangeAligned(ulong address, ulong size, bool write) + { + ulong end = ((address + size + MemoryManager.PageMask) & ~MemoryManager.PageMask) - Address; + ulong offset = address - Address; + + return new BufferRange(Handle, (int)offset, (int)(end - offset), write); + } + + /// + /// Gets a sub-range from the buffer. + /// + /// + /// This can be used to bind and use sub-ranges of the buffer on the host API. + /// + /// Start address of the sub-range, must be greater than or equal to the buffer address + /// Size in bytes of the sub-range, must be less than or equal to the buffer size + /// Whether the buffer will be written to by this use + /// The buffer sub-range + public BufferRange GetRange(ulong address, ulong size, bool write) + { + int offset = (int)(address - Address); + + return new BufferRange(Handle, offset, (int)size, write); + } + + /// + /// Checks if a given range overlaps with the buffer. + /// + /// Start address of the range + /// Size in bytes of the range + /// True if the range overlaps, false otherwise + public bool OverlapsWith(ulong address, ulong size) + { + return Address < address + size && address < EndAddress; + } + + /// + /// Checks if a given range is fully contained in the buffer. + /// + /// Start address of the range + /// Size in bytes of the range + /// True if the range is contained, false otherwise + public bool FullyContains(ulong address, ulong size) + { + return address >= Address && address + size <= EndAddress; + } + + /// + /// Performs guest to host memory synchronization of the buffer data. + /// + /// + /// This causes the buffer data to be overwritten if a write was detected from the CPU, + /// since the last call to this method. + /// + /// Start address of the range to synchronize + /// Size in bytes of the range to synchronize + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SynchronizeMemory(ulong address, ulong size) + { + if (_useGranular) + { + _memoryTrackingGranular.QueryModified(address, size, _modifiedDelegate, _context.SequenceNumber); + } + else + { + if (_context.SequenceNumber != _sequenceNumber && _memoryTracking.DirtyOrVolatile()) + { + _memoryTracking.Reprotect(); + + if (_modifiedRanges != null) + { + _modifiedRanges.ExcludeModifiedRegions(Address, Size, _loadDelegate); + } + else + { + BackingState.RecordSet(); + _context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size)); + CopyToDependantVirtualBuffers(); + } + + _sequenceNumber = _context.SequenceNumber; + _dirtyStart = ulong.MaxValue; + } + } + + if (_dirtyStart != ulong.MaxValue) + { + ulong end = address + size; + + if (end > _dirtyStart && address < _dirtyEnd) + { + if (_modifiedRanges != null) + { + _modifiedRanges.ExcludeModifiedRegions(_dirtyStart, _dirtyEnd - _dirtyStart, _loadDelegate); + } + else + { + LoadRegion(_dirtyStart, _dirtyEnd - _dirtyStart); + } + + _dirtyStart = ulong.MaxValue; + } + } + } + + /// + /// Ensure that the modified range list exists. + /// + private void EnsureRangeList() + { + _modifiedRanges ??= new BufferModifiedRangeList(_context, this, Flush); + } + + /// + /// Checks if a backing change is deemed necessary from the given usage. + /// If it is, queues a backing change to happen on the next sync action. + /// + /// Buffer stage that can change backing type + private void TryQueueBackingChange(BufferStage stage) + { + if (BackingState.ShouldChangeBacking(stage)) + { + if (!_syncActionRegistered) + { + _context.RegisterSyncAction(this); + _syncActionRegistered = true; + } + } + } + + /// + /// Signal that the given region of the buffer has been modified. + /// + /// The start address of the modified region + /// The size of the modified region + /// Buffer stage that triggered the modification + public void SignalModified(ulong address, ulong size, BufferStage stage) + { + EnsureRangeList(); + + TryQueueBackingChange(stage); + + _modifiedRanges.SignalModified(address, size); + + if (!_syncActionRegistered) + { + _context.RegisterSyncAction(this); + _syncActionRegistered = true; + } + } + + /// + /// Indicate that mofifications in a given region of this buffer have been overwritten. + /// + /// The start address of the region + /// The size of the region + public void ClearModified(ulong address, ulong size) + { + _modifiedRanges?.Clear(address, size); + } + + /// + /// Action to be performed immediately before sync is created. + /// This will copy any buffer ranges designated for pre-flushing. + /// + /// True if the action is a guest syncpoint + public void SyncPreAction(bool syncpoint) + { + if (_referenceCount == 0) + { + return; + } + + if (BackingState.ShouldChangeBacking()) + { + ChangeBacking(); + } + + if (BackingState.IsDeviceLocal) + { + _preFlush ??= new BufferPreFlush(_context, this, FlushImpl); + + if (_preFlush.ShouldCopy) + { + _modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, (address, size) => + { + _preFlush.CopyModified(address, size); + }); + } + } + } + + /// + /// Action to be performed when a syncpoint is reached after modification. + /// This will register read/write tracking to flush the buffer from GPU when its memory is used. + /// + /// + public bool SyncAction(bool syncpoint) + { + _syncActionRegistered = false; + + if (_useGranular) + { + _modifiedRanges?.GetRanges(Address, Size, (address, size) => + { + _memoryTrackingGranular.RegisterAction(address, size, _externalFlushDelegate); + SynchronizeMemory(address, size); + }); + } + else + { + _memoryTracking.RegisterAction(_externalFlushDelegate); + SynchronizeMemory(Address, Size); + } + + return true; + } + + /// + /// Inherit modified and dirty ranges from another buffer. + /// + /// The buffer to inherit from + public void InheritModifiedRanges(Buffer from) + { + if (from._modifiedRanges != null && from._modifiedRanges.HasRanges) + { + if (from._syncActionRegistered && !_syncActionRegistered) + { + _context.RegisterSyncAction(this); + _syncActionRegistered = true; + } + + void registerRangeAction(ulong address, ulong size) + { + if (_useGranular) + { + _memoryTrackingGranular.RegisterAction(address, size, _externalFlushDelegate); + } + else + { + _memoryTracking.RegisterAction(_externalFlushDelegate); + } + } + + EnsureRangeList(); + + _modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction); + } + + if (from._dirtyStart != ulong.MaxValue) + { + ForceDirty(from._dirtyStart, from._dirtyEnd - from._dirtyStart); + } + } + + /// + /// Determine if a given region of the buffer has been modified, and must be flushed. + /// + /// The start address of the region + /// The size of the region + /// + public bool IsModified(ulong address, ulong size) + { + if (_modifiedRanges != null) + { + return _modifiedRanges.HasRange(address, size); + } + + return false; + } + + /// + /// Clear the dirty range that overlaps with the given region. + /// + /// Start address of the modified region + /// Size of the modified region + private void ClearDirty(ulong address, ulong size) + { + if (_dirtyStart != ulong.MaxValue) + { + ulong end = address + size; + + if (end > _dirtyStart && address < _dirtyEnd) + { + if (address <= _dirtyStart) + { + // Cut off the start. + + if (end < _dirtyEnd) + { + _dirtyStart = end; + } + else + { + _dirtyStart = ulong.MaxValue; + } + } + else if (end >= _dirtyEnd) + { + // Cut off the end. + + _dirtyEnd = address; + } + + // If fully contained, do nothing. + } + } + } + + /// + /// Indicate that a region of the buffer was modified, and must be loaded from memory. + /// + /// Start address of the modified region + /// Size of the modified region + private void RegionModified(ulong mAddress, ulong mSize) + { + if (mAddress < Address) + { + mAddress = Address; + } + + ulong maxSize = Address + Size - mAddress; + + if (mSize > maxSize) + { + mSize = maxSize; + } + + ClearDirty(mAddress, mSize); + + if (_modifiedRanges != null) + { + _modifiedRanges.ExcludeModifiedRegions(mAddress, mSize, _loadDelegate); + } + else + { + LoadRegion(mAddress, mSize); + } + } + + /// + /// Load a region of the buffer from memory. + /// + /// Start address of the modified region + /// Size of the modified region + private void LoadRegion(ulong mAddress, ulong mSize) + { + BackingState.RecordSet(); + + int offset = (int)(mAddress - Address); + + _context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize)); + + CopyToDependantVirtualBuffers(mAddress, mSize); + } + + /// + /// Force a region of the buffer to be dirty within the memory tracking. Avoids reprotection and nullifies sequence number check. + /// + /// Start address of the modified region + /// Size of the region to force dirty + private void ForceTrackingDirty(ulong mAddress, ulong mSize) + { + if (_useGranular) + { + _memoryTrackingGranular.ForceDirty(mAddress, mSize); + } + else + { + _memoryTracking.ForceDirty(); + _sequenceNumber--; + } + } + + /// + /// Force a region of the buffer to be dirty. Avoids reprotection and nullifies sequence number check. + /// + /// Start address of the modified region + /// Size of the region to force dirty + public void ForceDirty(ulong mAddress, ulong mSize) + { + _modifiedRanges?.Clear(mAddress, mSize); + + ulong end = mAddress + mSize; + + if (_dirtyStart == ulong.MaxValue) + { + _dirtyStart = mAddress; + _dirtyEnd = end; + } + else + { + // Is the new range more than a page away from the existing one? + + if ((long)(mAddress - _dirtyEnd) >= (long)MemoryManager.PageSize || + (long)(_dirtyStart - end) >= (long)MemoryManager.PageSize) + { + ForceTrackingDirty(mAddress, mSize); + } + else + { + _dirtyStart = Math.Min(_dirtyStart, mAddress); + _dirtyEnd = Math.Max(_dirtyEnd, end); + } + } + } + + /// + /// Performs copy of all the buffer data from one buffer to another. + /// + /// The destination buffer to copy the data into + /// The offset of the destination buffer to copy into + public void CopyTo(Buffer destination, int dstOffset) + { + CopyFromDependantVirtualBuffers(); + _context.Renderer.Pipeline.CopyBuffer(Handle, destination.Handle, 0, dstOffset, (int)Size); + } + + /// + /// Flushes a range of the buffer. + /// This writes the range data back into guest memory. + /// + /// Buffer handle to flush data from + /// Start address of the range + /// Size in bytes of the range + private void FlushImpl(BufferHandle handle, ulong address, ulong size) + { + int offset = (int)(address - Address); + + using PinnedSpan data = _context.Renderer.GetBufferData(handle, offset, (int)size); + + // TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers. + _physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size)); + } + + /// + /// Flushes a range of the buffer. + /// This writes the range data back into guest memory. + /// + /// Start address of the range + /// Size in bytes of the range + private void FlushImpl(ulong address, ulong size) + { + FlushImpl(Handle, address, size); + } + + /// + /// Flushes a range of the buffer from the most optimal source. + /// This writes the range data back into guest memory. + /// + /// Start address of the range + /// Size in bytes of the range + /// Sync number waited for before flushing the data + public void Flush(ulong address, ulong size, ulong syncNumber) + { + BackingState.RecordFlush(); + + BufferPreFlush preFlush = _preFlush; + + if (preFlush != null) + { + preFlush.FlushWithAction(address, size, syncNumber); + } + else + { + FlushImpl(address, size); + } + } + /// + /// Gets an action that disposes the backing buffer using its current handle. + /// Useful for deleting an old copy of the buffer after the handle changes. + /// + /// An action that flushes data from the specified range, using the buffer handle at the time the method is generated + public Action GetSnapshotDisposeAction() + { + BufferHandle handle = Handle; + BufferPreFlush preFlush = _preFlush; + + return () => + { + _context.Renderer.DeleteBuffer(handle); + preFlush?.Dispose(); + }; + } + + /// + /// Gets an action that flushes a range of the buffer using its current handle. + /// Useful for flushing data from old copies of the buffer after the handle changes. + /// + /// An action that flushes data from the specified range, using the buffer handle at the time the method is generated + public BufferFlushAction GetSnapshotFlushAction() + { + BufferHandle handle = Handle; + + return (ulong address, ulong size, ulong _) => + { + FlushImpl(handle, address, size); + }; + } + + /// + /// Align a given address and size region to page boundaries. + /// + /// The start address of the region + /// The size of the region + /// The page aligned address and size + private static (ulong address, ulong size) PageAlign(ulong address, ulong size) + { + ulong pageMask = MemoryManager.PageMask; + ulong rA = address & ~pageMask; + ulong rS = ((address + size + pageMask) & ~pageMask) - rA; + return (rA, rS); + } + + /// + /// Flush modified ranges of the buffer from another thread. + /// This will flush all modifications made before the active SyncNumber was set, and may block to wait for GPU sync. + /// + /// Address of the memory action + /// Size in bytes + public void ExternalFlush(ulong address, ulong size) + { + _context.Renderer.BackgroundContextAction(() => + { + var ranges = _modifiedRanges; + + if (ranges != null) + { + (address, size) = PageAlign(address, size); + ranges.WaitForAndFlushRanges(address, size); + } + }, true); + } + + /// + /// An action to be performed when a precise memory access occurs to this resource. + /// For buffers, this skips flush-on-write by punching holes directly into the modified range list. + /// + /// Address of the memory action + /// Size in bytes + /// True if the access was a write, false otherwise + private bool PreciseAction(ulong address, ulong size, bool write) + { + if (!write) + { + // We only want to skip flush-on-write. + return false; + } + + ulong maxAddress = Math.Max(address, Address); + ulong minEndAddress = Math.Min(address + size, Address + Size); + + if (maxAddress >= minEndAddress) + { + // Access doesn't overlap. + return false; + } + + ForceDirty(maxAddress, minEndAddress - maxAddress); + + return true; + } + + /// + /// Called when part of the memory for this buffer has been unmapped. + /// Calls are from non-GPU threads. + /// + /// Start address of the unmapped region + /// Size of the unmapped region + public void Unmapped(ulong address, ulong size) + { + BufferModifiedRangeList modifiedRanges = _modifiedRanges; + + modifiedRanges?.Clear(address, size); + + UnmappedSequence++; + } + + /// + /// Adds a virtual buffer dependency, indicating that a virtual buffer depends on data from this buffer. + /// + /// Dependant virtual buffer + public void AddVirtualDependency(MultiRangeBuffer virtualBuffer) + { + _virtualDependenciesLock.EnterWriteLock(); + + try + { + (_virtualDependencies ??= new()).Add(virtualBuffer); + } + finally + { + _virtualDependenciesLock.ExitWriteLock(); + } + } + + /// + /// Removes a virtual buffer dependency, indicating that a virtual buffer no longer depends on data from this buffer. + /// + /// Dependant virtual buffer + public void RemoveVirtualDependency(MultiRangeBuffer virtualBuffer) + { + _virtualDependenciesLock.EnterWriteLock(); + + try + { + if (_virtualDependencies != null) + { + _virtualDependencies.Remove(virtualBuffer); + + if (_virtualDependencies.Count == 0) + { + _virtualDependencies = null; + } + } + } + finally + { + _virtualDependenciesLock.ExitWriteLock(); + } + } + + /// + /// Copies the buffer data to all virtual buffers that depends on it. + /// + public void CopyToDependantVirtualBuffers() + { + CopyToDependantVirtualBuffers(Address, Size); + } + + /// + /// Copies the buffer data inside the specifide range to all virtual buffers that depends on it. + /// + /// Address of the range + /// Size of the range in bytes + public void CopyToDependantVirtualBuffers(ulong address, ulong size) + { + if (_virtualDependencies != null) + { + foreach (var virtualBuffer in _virtualDependencies) + { + CopyToDependantVirtualBuffer(virtualBuffer, address, size); + } + } + } + + /// + /// Copies all modified ranges from all virtual buffers back into this buffer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyFromDependantVirtualBuffers() + { + if (_virtualDependencies != null) + { + CopyFromDependantVirtualBuffersImpl(); + } + } + + /// + /// Copies all modified ranges from all virtual buffers back into this buffer. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private void CopyFromDependantVirtualBuffersImpl() + { + foreach (var virtualBuffer in _virtualDependencies.OrderBy(x => x.ModificationSequenceNumber)) + { + virtualBuffer.ConsumeModifiedRegion(this, (mAddress, mSize) => + { + // Get offset inside both this and the virtual buffer. + // Note that sometimes there is no right answer for the virtual offset, + // as the same physical range might be mapped multiple times inside a virtual buffer. + // We just assume it does not happen in practice as it can only be implemented correctly + // when the host has support for proper sparse mapping. + + ulong mEndAddress = mAddress + mSize; + mAddress = Math.Max(mAddress, Address); + mSize = Math.Min(mEndAddress, EndAddress) - mAddress; + + int physicalOffset = (int)(mAddress - Address); + int virtualOffset = virtualBuffer.Range.FindOffset(new(mAddress, mSize)); + + _context.Renderer.Pipeline.CopyBuffer(virtualBuffer.Handle, Handle, virtualOffset, physicalOffset, (int)mSize); + }); + } + } + + /// + /// Copies all overlapping modified ranges from all virtual buffers back into this buffer, and returns an updated span with the data. + /// + /// Span where the unmodified data will be taken from for the output + /// Address of the region to copy + /// Size of the region to copy in bytes + /// A span with , and the data for all modified ranges if any + private ReadOnlySpan CopyFromDependantVirtualBuffers(ReadOnlySpan dataSpan, ulong address, ulong size) + { + _virtualDependenciesLock.EnterReadLock(); + + try + { + if (_virtualDependencies != null) + { + byte[] storage = dataSpan.ToArray(); + + foreach (var virtualBuffer in _virtualDependencies.OrderBy(x => x.ModificationSequenceNumber)) + { + virtualBuffer.ConsumeModifiedRegion(address, size, (mAddress, mSize) => + { + // Get offset inside both this and the virtual buffer. + // Note that sometimes there is no right answer for the virtual offset, + // as the same physical range might be mapped multiple times inside a virtual buffer. + // We just assume it does not happen in practice as it can only be implemented correctly + // when the host has support for proper sparse mapping. + + ulong mEndAddress = mAddress + mSize; + mAddress = Math.Max(mAddress, address); + mSize = Math.Min(mEndAddress, address + size) - mAddress; + + int physicalOffset = (int)(mAddress - Address); + int virtualOffset = virtualBuffer.Range.FindOffset(new(mAddress, mSize)); + + _context.Renderer.Pipeline.CopyBuffer(virtualBuffer.Handle, Handle, virtualOffset, physicalOffset, (int)size); + virtualBuffer.GetData(storage.AsSpan().Slice((int)(mAddress - address), (int)mSize), virtualOffset, (int)mSize); + }); + } + + dataSpan = storage; + } + } + finally + { + _virtualDependenciesLock.ExitReadLock(); + } + + return dataSpan; + } + + /// + /// Copies the buffer data to the specified virtual buffer. + /// + /// Virtual buffer to copy the data into + public void CopyToDependantVirtualBuffer(MultiRangeBuffer virtualBuffer) + { + CopyToDependantVirtualBuffer(virtualBuffer, Address, Size); + } + + /// + /// Copies the buffer data inside the given range to the specified virtual buffer. + /// + /// Virtual buffer to copy the data into + /// Address of the range + /// Size of the range in bytes + public void CopyToDependantVirtualBuffer(MultiRangeBuffer virtualBuffer, ulong address, ulong size) + { + // Broadcast data to all ranges of the virtual buffer that are contained inside this buffer. + + ulong lastOffset = 0; + + while (virtualBuffer.TryGetPhysicalOffset(this, lastOffset, out ulong srcOffset, out ulong dstOffset, out ulong copySize)) + { + ulong innerOffset = address - Address; + ulong innerEndOffset = (address + size) - Address; + + lastOffset = dstOffset + copySize; + + // Clamp range to the specified range. + ulong copySrcOffset = Math.Max(srcOffset, innerOffset); + ulong copySrcEndOffset = Math.Min(innerEndOffset, srcOffset + copySize); + + if (copySrcEndOffset > copySrcOffset) + { + copySize = copySrcEndOffset - copySrcOffset; + dstOffset += copySrcOffset - srcOffset; + srcOffset = copySrcOffset; + + _context.Renderer.Pipeline.CopyBuffer(Handle, virtualBuffer.Handle, (int)srcOffset, (int)dstOffset, (int)copySize); + } + } + } + + /// + /// Increments the buffer reference count. + /// + public void IncrementReferenceCount() + { + _referenceCount++; + } + + /// + /// Decrements the buffer reference count. + /// + public void DecrementReferenceCount() + { + if (--_referenceCount == 0) + { + DisposeData(); + } + } + + /// + /// Disposes the host buffer's data, not its tracking handles. + /// + public void DisposeData() + { + _modifiedRanges?.Clear(); + + _context.Renderer.DeleteBuffer(Handle); + _preFlush?.Dispose(); + _preFlush = null; + + UnmappedSequence++; + } + + /// + /// Disposes the host buffer. + /// + public void Dispose() + { + _memoryTrackingGranular?.Dispose(); + _memoryTracking?.Dispose(); + + DecrementReferenceCount(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs new file mode 100644 index 00000000..3f65131e --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs @@ -0,0 +1,294 @@ +using Ryujinx.Graphics.GAL; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Type of backing memory. + /// In ascending order of priority when merging multiple buffer backing states. + /// + internal enum BufferBackingType + { + HostMemory, + DeviceMemory, + DeviceMemoryWithFlush + } + + /// + /// Keeps track of buffer usage to decide what memory heap that buffer memory is placed on. + /// Dedicated GPUs prefer certain types of resources to be device local, + /// and if we need data to be read back, we might prefer that they're in host memory. + /// + /// The measurements recorded here compare to a set of heruristics (thresholds and conditions) + /// that appear to produce good performance in most software. + /// + internal struct BufferBackingState + { + private const int DeviceLocalSizeThreshold = 256 * 1024; // 256kb + + private const int SetCountThreshold = 100; + private const int WriteCountThreshold = 50; + private const int FlushCountThreshold = 5; + private const int DeviceLocalForceExpiry = 100; + + public readonly bool IsDeviceLocal => _activeType != BufferBackingType.HostMemory; + + private readonly SystemMemoryType _systemMemoryType; + private BufferBackingType _activeType; + private BufferBackingType _desiredType; + + private bool _canSwap; + + private int _setCount; + private int _writeCount; + private int _flushCount; + private int _flushTemp; + private int _lastFlushWrite; + private int _deviceLocalForceCount; + + private readonly int _size; + + /// + /// Initialize the buffer backing state for a given parent buffer. + /// + /// GPU context + /// Parent buffer + /// Initial buffer stage + /// Buffers to inherit state from + public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, IEnumerable baseBuffers = null) + { + _size = (int)parent.Size; + _systemMemoryType = context.Capabilities.MemoryType; + + // Backend managed is always auto, unified memory is always host. + _desiredType = BufferBackingType.HostMemory; + _canSwap = _systemMemoryType != SystemMemoryType.BackendManaged && _systemMemoryType != SystemMemoryType.UnifiedMemory; + + if (_canSwap) + { + // Might want to start certain buffers as being device local, + // and the usage might also lock those buffers into being device local. + + BufferStage storageFlags = stage & BufferStage.StorageMask; + + if (parent.Size > DeviceLocalSizeThreshold && baseBuffers == null) + { + _desiredType = BufferBackingType.DeviceMemory; + } + + if (storageFlags != 0) + { + // Storage buffer bindings may require special treatment. + + var rawStage = stage & BufferStage.StageMask; + + if (rawStage == BufferStage.Fragment) + { + // Fragment read should start device local. + + _desiredType = BufferBackingType.DeviceMemory; + + if (storageFlags != BufferStage.StorageRead) + { + // Fragment write should stay device local until the use doesn't happen anymore. + + _deviceLocalForceCount = DeviceLocalForceExpiry; + } + } + + // TODO: Might be nice to force atomic access to be device local for any stage. + } + + if (baseBuffers != null) + { + foreach (Buffer buffer in baseBuffers) + { + CombineState(buffer.BackingState); + } + } + } + } + + /// + /// Combine buffer backing types, selecting the one with highest priority. + /// + /// First buffer backing type + /// Second buffer backing type + /// Combined buffer backing type + private static BufferBackingType CombineTypes(BufferBackingType left, BufferBackingType right) + { + return (BufferBackingType)Math.Max((int)left, (int)right); + } + + /// + /// Combine the state from the given buffer backing state with this one, + /// so that the state isn't lost when migrating buffers. + /// + /// Buffer state to combine into this state + private void CombineState(BufferBackingState oldState) + { + _setCount += oldState._setCount; + _writeCount += oldState._writeCount; + _flushCount += oldState._flushCount; + _flushTemp += oldState._flushTemp; + _lastFlushWrite = -1; + _deviceLocalForceCount = Math.Max(_deviceLocalForceCount, oldState._deviceLocalForceCount); + + _canSwap &= oldState._canSwap; + + _desiredType = CombineTypes(_desiredType, oldState._desiredType); + } + + /// + /// Get the buffer access for the desired backing type, and record that type as now being active. + /// + /// Parent buffer + /// Buffer access + public BufferAccess SwitchAccess(Buffer parent) + { + BufferAccess access = parent.SparseCompatible ? BufferAccess.SparseCompatible : BufferAccess.Default; + + bool isBackendManaged = _systemMemoryType == SystemMemoryType.BackendManaged; + + if (!isBackendManaged) + { + switch (_desiredType) + { + case BufferBackingType.HostMemory: + access |= BufferAccess.HostMemory; + break; + case BufferBackingType.DeviceMemory: + access |= BufferAccess.DeviceMemory; + break; + case BufferBackingType.DeviceMemoryWithFlush: + access |= BufferAccess.DeviceMemoryMapped; + break; + } + } + + _activeType = _desiredType; + + return access; + } + + /// + /// Record when data has been uploaded to the buffer. + /// + public void RecordSet() + { + _setCount++; + + ConsiderUseCounts(); + } + + /// + /// Record when data has been flushed from the buffer. + /// + public void RecordFlush() + { + if (_lastFlushWrite != _writeCount) + { + // If it's on the same page as the last flush, ignore it. + _lastFlushWrite = _writeCount; + _flushCount++; + } + } + + /// + /// Determine if the buffer backing should be changed. + /// + /// True if the desired backing type is different from the current type + public readonly bool ShouldChangeBacking() + { + return _desiredType != _activeType; + } + + /// + /// Determine if the buffer backing should be changed, considering a new use with the given buffer stage. + /// + /// Buffer stage for the use + /// True if the desired backing type is different from the current type + public bool ShouldChangeBacking(BufferStage stage) + { + if (!_canSwap) + { + return false; + } + + BufferStage storageFlags = stage & BufferStage.StorageMask; + + if (storageFlags != 0) + { + if (storageFlags != BufferStage.StorageRead) + { + // Storage write. + _writeCount++; + + var rawStage = stage & BufferStage.StageMask; + + if (rawStage == BufferStage.Fragment) + { + // Switch to device memory, swap back only if this use disappears. + + _desiredType = CombineTypes(_desiredType, BufferBackingType.DeviceMemory); + _deviceLocalForceCount = DeviceLocalForceExpiry; + + // TODO: Might be nice to force atomic access to be device local for any stage. + } + } + + ConsiderUseCounts(); + } + + return _desiredType != _activeType; + } + + /// + /// Evaluate the current counts to determine what the buffer's desired backing type is. + /// This method depends on heuristics devised by testing a variety of software. + /// + private void ConsiderUseCounts() + { + if (_canSwap) + { + if (_writeCount >= WriteCountThreshold || _setCount >= SetCountThreshold || _flushCount >= FlushCountThreshold) + { + if (_deviceLocalForceCount > 0 && --_deviceLocalForceCount != 0) + { + // Some buffer usage demanded that the buffer stay device local. + // The desired type was selected when this counter was set. + } + else if (_flushCount > 0 || _flushTemp-- > 0) + { + // Buffers that flush should ideally be mapped in host address space for easy copies. + // If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages). + // If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached. + _desiredType = _size > DeviceLocalSizeThreshold ? BufferBackingType.DeviceMemoryWithFlush : BufferBackingType.HostMemory; + } + else if (_writeCount >= WriteCountThreshold) + { + // Buffers that are written often should ideally be in the device local heap. (Storage buffers) + _desiredType = BufferBackingType.DeviceMemory; + } + else if (_setCount > SetCountThreshold) + { + // Buffers that have their data set often should ideally be host mapped. (Constant buffers) + _desiredType = BufferBackingType.HostMemory; + } + + // It's harder for a buffer that is flushed to revert to another type of mapping. + if (_flushCount > 0) + { + _flushTemp = 1000; + } + + _lastFlushWrite = -1; + _flushCount = 0; + _writeCount = 0; + _setCount = 0; + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs new file mode 100644 index 00000000..cf783ef2 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs @@ -0,0 +1,58 @@ +using Ryujinx.Graphics.Shader; +using Ryujinx.Memory.Range; +using System; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Memory range used for buffers. + /// + readonly struct BufferBounds : IEquatable + { + /// + /// Physical memory ranges where the buffer is mapped. + /// + public MultiRange Range { get; } + + /// + /// Buffer usage flags. + /// + public BufferUsageFlags Flags { get; } + + /// + /// Indicates that the backing memory for the buffer does not exist. + /// + public bool IsUnmapped => Range.IsUnmapped; + + /// + /// Creates a new buffer region. + /// + /// Physical memory ranges where the buffer is mapped + /// Buffer usage flags + public BufferBounds(MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None) + { + Range = range; + Flags = flags; + } + + public override bool Equals(object obj) + { + return obj is BufferBounds bounds && Equals(bounds); + } + + public bool Equals(BufferBounds bounds) + { + return Range == bounds.Range && Flags == bounds.Flags; + } + + public bool Equals(ref BufferBounds bounds) + { + return Range == bounds.Range && Flags == bounds.Flags; + } + + public override int GetHashCode() + { + return HashCode.Combine(Range, Flags); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs new file mode 100644 index 00000000..66d2cdb6 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs @@ -0,0 +1,1096 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Memory.Range; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Buffer cache. + /// + class BufferCache : IDisposable + { + /// + /// Initial size for the array holding overlaps. + /// + public const int OverlapsBufferInitialCapacity = 10; + + /// + /// Maximum size that an array holding overlaps may have after trimming. + /// + public const int OverlapsBufferMaxCapacity = 10000; + + private const ulong BufferAlignmentSize = 0x1000; + private const ulong BufferAlignmentMask = BufferAlignmentSize - 1; + + /// + /// Alignment required for sparse buffer mappings. + /// + public const ulong SparseBufferAlignmentSize = 0x10000; + + private const ulong MaxDynamicGrowthSize = 0x100000; + + private readonly GpuContext _context; + private readonly PhysicalMemory _physicalMemory; + + /// + /// Only modified from the GPU thread. Must lock for add/remove. + /// Must lock for any access from other threads. + /// + private readonly RangeList _buffers; + private readonly MultiRangeList _multiRangeBuffers; + + private Buffer[] _bufferOverlaps; + + private readonly Dictionary _dirtyCache; + private readonly Dictionary _modifiedCache; + private bool _pruneCaches; + private int _virtualModifiedSequenceNumber; + + public event Action NotifyBuffersModified; + + /// + /// Creates a new instance of the buffer manager. + /// + /// The GPU context that the buffer manager belongs to + /// Physical memory where the cached buffers are mapped + public BufferCache(GpuContext context, PhysicalMemory physicalMemory) + { + _context = context; + _physicalMemory = physicalMemory; + + _buffers = new RangeList(); + _multiRangeBuffers = new MultiRangeList(); + + _bufferOverlaps = new Buffer[OverlapsBufferInitialCapacity]; + + _dirtyCache = new Dictionary(); + + // There are a lot more entries on the modified cache, so it is separate from the one for ForceDirty. + _modifiedCache = new Dictionary(); + } + + /// + /// Handles removal of buffers written to a memory region being unmapped. + /// + /// Sender object + /// Event arguments + public void MemoryUnmappedHandler(object sender, UnmapEventArgs e) + { + Buffer[] overlaps = new Buffer[10]; + int overlapCount; + + MultiRange range = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size); + + for (int index = 0; index < range.Count; index++) + { + MemoryRange subRange = range.GetSubRange(index); + + lock (_buffers) + { + overlapCount = _buffers.FindOverlaps(subRange.Address, subRange.Size, ref overlaps); + } + + for (int i = 0; i < overlapCount; i++) + { + overlaps[i].Unmapped(subRange.Address, subRange.Size); + } + } + } + + /// + /// Performs address translation of the GPU virtual address, and creates a + /// new buffer, if needed, for the specified contiguous range. + /// + /// GPU memory manager where the buffer is mapped + /// Start GPU virtual address of the buffer + /// Size in bytes of the buffer + /// The type of usage that created the buffer + /// Contiguous physical range of the buffer, after address translation + public MultiRange TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage) + { + if (gpuVa == 0) + { + return new MultiRange(MemoryManager.PteUnmapped, size); + } + + ulong address = memoryManager.Translate(gpuVa); + + if (address != MemoryManager.PteUnmapped) + { + CreateBuffer(address, size, stage); + } + + return new MultiRange(address, size); + } + + /// + /// Performs address translation of the GPU virtual address, and creates + /// new physical and virtual buffers, if needed, for the specified range. + /// + /// GPU memory manager where the buffer is mapped + /// Start GPU virtual address of the buffer + /// Size in bytes of the buffer + /// The type of usage that created the buffer + /// Physical ranges of the buffer, after address translation + public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage) + { + if (gpuVa == 0) + { + return new MultiRange(MemoryManager.PteUnmapped, size); + } + + // Fast path not taken for non-contiguous ranges, + // since multi-range buffers are not coalesced, so a buffer that covers + // the entire cached range might not actually exist. + if (memoryManager.VirtualRangeCache.TryGetOrAddRange(gpuVa, size, out MultiRange range) && + range.Count == 1) + { + return range; + } + + CreateBuffer(range, stage); + + return range; + } + + /// + /// Performs address translation of the GPU virtual address, and creates + /// new physical buffers, if needed, for the specified range. + /// + /// GPU memory manager where the buffer is mapped + /// Start GPU virtual address of the buffer + /// Size in bytes of the buffer + /// The type of usage that created the buffer + /// Physical ranges of the buffer, after address translation + public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage) + { + if (gpuVa == 0) + { + return new MultiRange(MemoryManager.PteUnmapped, size); + } + + // Fast path not taken for non-contiguous ranges, + // since multi-range buffers are not coalesced, so a buffer that covers + // the entire cached range might not actually exist. + if (memoryManager.VirtualRangeCache.TryGetOrAddRange(gpuVa, size, out MultiRange range) && + range.Count == 1) + { + return range; + } + + for (int i = 0; i < range.Count; i++) + { + MemoryRange subRange = range.GetSubRange(i); + + if (subRange.Address != MemoryManager.PteUnmapped) + { + if (range.Count > 1) + { + CreateBuffer(subRange.Address, subRange.Size, stage, SparseBufferAlignmentSize); + } + else + { + CreateBuffer(subRange.Address, subRange.Size, stage); + } + } + } + + return range; + } + + /// + /// Creates a new buffer for the specified range, if it does not yet exist. + /// This can be used to ensure the existance of a buffer. + /// + /// Physical ranges of memory where the buffer data is located + /// The type of usage that created the buffer + public void CreateBuffer(MultiRange range, BufferStage stage) + { + if (range.Count > 1) + { + CreateMultiRangeBuffer(range, stage); + } + else + { + MemoryRange subRange = range.GetSubRange(0); + + if (subRange.Address != MemoryManager.PteUnmapped) + { + CreateBuffer(subRange.Address, subRange.Size, stage); + } + } + } + + /// + /// Creates a new buffer for the specified range, if it does not yet exist. + /// This can be used to ensure the existance of a buffer. + /// + /// Address of the buffer in memory + /// Size of the buffer in bytes + /// The type of usage that created the buffer + public void CreateBuffer(ulong address, ulong size, BufferStage stage) + { + ulong endAddress = address + size; + + ulong alignedAddress = address & ~BufferAlignmentMask; + ulong alignedEndAddress = (endAddress + BufferAlignmentMask) & ~BufferAlignmentMask; + + // The buffer must have the size of at least one page. + if (alignedEndAddress == alignedAddress) + { + alignedEndAddress += BufferAlignmentSize; + } + + CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, stage); + } + + /// + /// Creates a new buffer for the specified range, if it does not yet exist. + /// This can be used to ensure the existance of a buffer. + /// + /// Address of the buffer in memory + /// Size of the buffer in bytes + /// The type of usage that created the buffer + /// Alignment of the start address of the buffer in bytes + public void CreateBuffer(ulong address, ulong size, BufferStage stage, ulong alignment) + { + ulong alignmentMask = alignment - 1; + ulong pageAlignmentMask = BufferAlignmentMask; + ulong endAddress = address + size; + + ulong alignedAddress = address & ~alignmentMask; + ulong alignedEndAddress = (endAddress + pageAlignmentMask) & ~pageAlignmentMask; + + // The buffer must have the size of at least one page. + if (alignedEndAddress == alignedAddress) + { + alignedEndAddress += pageAlignmentMask; + } + + CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, stage, alignment); + } + + /// + /// Creates a buffer for a memory region composed of multiple physical ranges, + /// if it does not exist yet. + /// + /// Physical ranges of memory + /// The type of usage that created the buffer + private void CreateMultiRangeBuffer(MultiRange range, BufferStage stage) + { + // Ensure all non-contiguous buffer we might use are sparse aligned. + for (int i = 0; i < range.Count; i++) + { + MemoryRange subRange = range.GetSubRange(i); + + if (subRange.Address != MemoryManager.PteUnmapped) + { + CreateBuffer(subRange.Address, subRange.Size, stage, SparseBufferAlignmentSize); + } + } + + // Create sparse buffer. + MultiRangeBuffer[] overlaps = new MultiRangeBuffer[10]; + + int overlapCount = _multiRangeBuffers.FindOverlaps(range, ref overlaps); + + for (int index = 0; index < overlapCount; index++) + { + if (overlaps[index].Range.Contains(range)) + { + return; + } + } + + for (int index = 0; index < overlapCount; index++) + { + if (range.Contains(overlaps[index].Range)) + { + _multiRangeBuffers.Remove(overlaps[index]); + overlaps[index].Dispose(); + } + } + + MultiRangeBuffer multiRangeBuffer; + + MemoryRange[] alignedSubRanges = new MemoryRange[range.Count]; + + ulong alignmentMask = SparseBufferAlignmentSize - 1; + + if (_context.Capabilities.SupportsSparseBuffer) + { + BufferRange[] storages = new BufferRange[range.Count]; + + for (int i = 0; i < range.Count; i++) + { + MemoryRange subRange = range.GetSubRange(i); + + if (subRange.Address != MemoryManager.PteUnmapped) + { + ulong endAddress = subRange.Address + subRange.Size; + + ulong alignedAddress = subRange.Address & ~alignmentMask; + ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask; + ulong alignedSize = alignedEndAddress - alignedAddress; + + Buffer buffer = _buffers.FindFirstOverlap(alignedAddress, alignedSize); + BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false); + + alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize); + storages[i] = bufferRange; + } + else + { + ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask; + + alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize); + storages[i] = new BufferRange(BufferHandle.Null, 0, (int)alignedSize); + } + } + + multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges), storages); + } + else + { + for (int i = 0; i < range.Count; i++) + { + MemoryRange subRange = range.GetSubRange(i); + + if (subRange.Address != MemoryManager.PteUnmapped) + { + ulong endAddress = subRange.Address + subRange.Size; + + ulong alignedAddress = subRange.Address & ~alignmentMask; + ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask; + ulong alignedSize = alignedEndAddress - alignedAddress; + + alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize); + } + else + { + ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask; + + alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize); + } + } + + multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges)); + + UpdateVirtualBufferDependencies(multiRangeBuffer); + } + + _multiRangeBuffers.Add(multiRangeBuffer); + } + + /// + /// Adds two-way dependencies to all physical buffers contained within a given virtual buffer. + /// + /// Virtual buffer to have dependencies added + private void UpdateVirtualBufferDependencies(MultiRangeBuffer virtualBuffer) + { + virtualBuffer.ClearPhysicalDependencies(); + + ulong dstOffset = 0; + + HashSet physicalBuffers = new(); + + for (int i = 0; i < virtualBuffer.Range.Count; i++) + { + MemoryRange subRange = virtualBuffer.Range.GetSubRange(i); + + if (subRange.Address != MemoryManager.PteUnmapped) + { + Buffer buffer = _buffers.FindFirstOverlap(subRange.Address, subRange.Size); + + virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size); + physicalBuffers.Add(buffer); + } + + dstOffset += subRange.Size; + } + + foreach (var buffer in physicalBuffers) + { + buffer.CopyToDependantVirtualBuffer(virtualBuffer); + } + } + + /// + /// Performs address translation of the GPU virtual address, and attempts to force + /// the buffer in the region as dirty. + /// The buffer lookup for this function is cached in a dictionary for quick access, which + /// accelerates common UBO updates. + /// + /// GPU memory manager where the buffer is mapped + /// Start GPU virtual address of the buffer + /// Size in bytes of the buffer + public void ForceDirty(MemoryManager memoryManager, ulong gpuVa, ulong size) + { + if (_pruneCaches) + { + Prune(); + } + + if (!_dirtyCache.TryGetValue(gpuVa, out BufferCacheEntry result) || + result.EndGpuAddress < gpuVa + size || + result.UnmappedSequence != result.Buffer.UnmappedSequence) + { + MultiRange range = TranslateAndCreateBuffer(memoryManager, gpuVa, size, BufferStage.Internal); + ulong address = range.GetSubRange(0).Address; + result = new BufferCacheEntry(address, gpuVa, GetBuffer(address, size, BufferStage.Internal)); + + _dirtyCache[gpuVa] = result; + } + + result.Buffer.ForceDirty(result.Address, size); + } + + /// + /// Checks if the given buffer range has been GPU modifed. + /// + /// GPU memory manager where the buffer is mapped + /// Start GPU virtual address of the buffer + /// Size in bytes of the buffer + /// True if modified, false otherwise + public bool CheckModified(MemoryManager memoryManager, ulong gpuVa, ulong size, out ulong outAddr) + { + if (_pruneCaches) + { + Prune(); + } + + // Align the address to avoid creating too many entries on the quick lookup dictionary. + ulong mask = BufferAlignmentMask; + ulong alignedGpuVa = gpuVa & (~mask); + ulong alignedEndGpuVa = (gpuVa + size + mask) & (~mask); + + size = alignedEndGpuVa - alignedGpuVa; + + if (!_modifiedCache.TryGetValue(alignedGpuVa, out BufferCacheEntry result) || + result.EndGpuAddress < alignedEndGpuVa || + result.UnmappedSequence != result.Buffer.UnmappedSequence) + { + MultiRange range = TranslateAndCreateBuffer(memoryManager, alignedGpuVa, size, BufferStage.None); + ulong address = range.GetSubRange(0).Address; + result = new BufferCacheEntry(address, alignedGpuVa, GetBuffer(address, size, BufferStage.None)); + + _modifiedCache[alignedGpuVa] = result; + } + + outAddr = result.Address | (gpuVa & mask); + + return result.Buffer.IsModified(result.Address, size); + } + + /// + /// Creates a new buffer for the specified range, if needed. + /// If a buffer where this range can be fully contained already exists, + /// then the creation of a new buffer is not necessary. + /// + /// Address of the buffer in guest memory + /// Size in bytes of the buffer + /// The type of usage that created the buffer + private void CreateBufferAligned(ulong address, ulong size, BufferStage stage) + { + Buffer[] overlaps = _bufferOverlaps; + int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps); + + if (overlapsCount != 0) + { + // The buffer already exists. We can just return the existing buffer + // if the buffer we need is fully contained inside the overlapping buffer. + // Otherwise, we must delete the overlapping buffers and create a bigger buffer + // that fits all the data we need. We also need to copy the contents from the + // old buffer(s) to the new buffer. + + ulong endAddress = address + size; + Buffer overlap0 = overlaps[0]; + + if (overlap0.Address > address || overlap0.EndAddress < endAddress) + { + bool anySparseCompatible = false; + + // Check if the following conditions are met: + // - We have a single overlap. + // - The overlap starts at or before the requested range. That is, the overlap happens at the end. + // - The size delta between the new, merged buffer and the old one is of at most 2 pages. + // In this case, we attempt to extend the buffer further than the requested range, + // this can potentially avoid future resizes if the application keeps using overlapping + // sequential memory. + // Allowing for 2 pages (rather than just one) is necessary to catch cases where the + // range crosses a page, and after alignment, ends having a size of 2 pages. + if (overlapsCount == 1 && + address >= overlap0.Address && + endAddress - overlap0.EndAddress <= BufferAlignmentSize * 2) + { + // Try to grow the buffer by 1.5x of its current size. + // This improves performance in the cases where the buffer is resized often by small amounts. + ulong existingSize = overlap0.Size; + ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask; + + size = Math.Max(size, growthSize); + endAddress = address + size; + + overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps); + } + + for (int index = 0; index < overlapsCount; index++) + { + Buffer buffer = overlaps[index]; + + anySparseCompatible |= buffer.SparseCompatible; + + address = Math.Min(address, buffer.Address); + endAddress = Math.Max(endAddress, buffer.EndAddress); + + lock (_buffers) + { + _buffers.Remove(buffer); + } + } + + ulong newSize = endAddress - address; + + CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlaps, overlapsCount); + } + } + else + { + // No overlap, just create a new buffer. + Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false); + + lock (_buffers) + { + _buffers.Add(buffer); + } + } + + ShrinkOverlapsBufferIfNeeded(); + } + + /// + /// Creates a new buffer for the specified range, if needed. + /// If a buffer where this range can be fully contained already exists, + /// then the creation of a new buffer is not necessary. + /// + /// Address of the buffer in guest memory + /// Size in bytes of the buffer + /// The type of usage that created the buffer + /// Alignment of the start address of the buffer + private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment) + { + Buffer[] overlaps = _bufferOverlaps; + int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps); + bool sparseAligned = alignment >= SparseBufferAlignmentSize; + + if (overlapsCount != 0) + { + // If the buffer already exists, make sure if covers the entire range, + // and make sure it is properly aligned, otherwise sparse mapping may fail. + + ulong endAddress = address + size; + Buffer overlap0 = overlaps[0]; + + if (overlap0.Address > address || + overlap0.EndAddress < endAddress || + (overlap0.Address & (alignment - 1)) != 0 || + (!overlap0.SparseCompatible && sparseAligned)) + { + // We need to make sure the new buffer is properly aligned. + // However, after the range is aligned, it is possible that it + // overlaps more buffers, so try again after each extension + // and ensure we cover all overlaps. + + int oldOverlapsCount; + + do + { + for (int index = 0; index < overlapsCount; index++) + { + Buffer buffer = overlaps[index]; + + address = Math.Min(address, buffer.Address); + endAddress = Math.Max(endAddress, buffer.EndAddress); + } + + address &= ~(alignment - 1); + + oldOverlapsCount = overlapsCount; + overlapsCount = _buffers.FindOverlapsNonOverlapping(address, endAddress - address, ref overlaps); + } + while (oldOverlapsCount != overlapsCount); + + lock (_buffers) + { + for (int index = 0; index < overlapsCount; index++) + { + _buffers.Remove(overlaps[index]); + } + } + + ulong newSize = endAddress - address; + + CreateBufferAligned(address, newSize, stage, sparseAligned, overlaps, overlapsCount); + } + } + else + { + // No overlap, just create a new buffer. + Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseAligned); + + lock (_buffers) + { + _buffers.Add(buffer); + } + } + + ShrinkOverlapsBufferIfNeeded(); + } + + /// + /// Creates a new buffer for the specified range, if needed. + /// If a buffer where this range can be fully contained already exists, + /// then the creation of a new buffer is not necessary. + /// + /// Address of the buffer in guest memory + /// Size in bytes of the buffer + /// The type of usage that created the buffer + /// Indicates if the buffer can be used in a sparse buffer mapping + /// Buffers overlapping the range + /// Total of overlaps + private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, Buffer[] overlaps, int overlapsCount) + { + Buffer newBuffer = new Buffer(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps.Take(overlapsCount)); + + lock (_buffers) + { + _buffers.Add(newBuffer); + } + + for (int index = 0; index < overlapsCount; index++) + { + Buffer buffer = overlaps[index]; + + int dstOffset = (int)(buffer.Address - newBuffer.Address); + + buffer.CopyTo(newBuffer, dstOffset); + newBuffer.InheritModifiedRanges(buffer); + + buffer.DecrementReferenceCount(); + } + + newBuffer.SynchronizeMemory(address, size); + + // Existing buffers were modified, we need to rebind everything. + NotifyBuffersModified?.Invoke(); + + RecreateMultiRangeBuffers(address, size); + } + + /// + /// Recreates all the multi-range buffers that overlaps a given physical memory range. + /// + /// Start address of the range + /// Size of the range in bytes + private void RecreateMultiRangeBuffers(ulong address, ulong size) + { + if ((address & (SparseBufferAlignmentSize - 1)) != 0 || (size & (SparseBufferAlignmentSize - 1)) != 0) + { + return; + } + + MultiRangeBuffer[] overlaps = new MultiRangeBuffer[10]; + + int overlapCount = _multiRangeBuffers.FindOverlaps(address, size, ref overlaps); + + for (int index = 0; index < overlapCount; index++) + { + _multiRangeBuffers.Remove(overlaps[index]); + overlaps[index].Dispose(); + } + + for (int index = 0; index < overlapCount; index++) + { + CreateMultiRangeBuffer(overlaps[index].Range, BufferStage.None); + } + } + + /// + /// Resizes the temporary buffer used for range list intersection results, if it has grown too much. + /// + private void ShrinkOverlapsBufferIfNeeded() + { + if (_bufferOverlaps.Length > OverlapsBufferMaxCapacity) + { + Array.Resize(ref _bufferOverlaps, OverlapsBufferMaxCapacity); + } + } + + /// + /// Copy a buffer data from a given address to another. + /// + /// + /// This does a GPU side copy. + /// + /// GPU memory manager where the buffer is mapped + /// GPU virtual address of the copy source + /// GPU virtual address of the copy destination + /// Size in bytes of the copy + public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size) + { + MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size, BufferStage.Copy); + MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size, BufferStage.Copy); + + if (srcRange.Count == 1 && dstRange.Count == 1) + { + CopyBufferSingleRange(memoryManager, srcRange.GetSubRange(0).Address, dstRange.GetSubRange(0).Address, size); + } + else + { + ulong copiedSize = 0; + ulong srcOffset = 0; + ulong dstOffset = 0; + int srcRangeIndex = 0; + int dstRangeIndex = 0; + + while (copiedSize < size) + { + if (srcRange.GetSubRange(srcRangeIndex).Size == srcOffset) + { + srcRangeIndex++; + srcOffset = 0; + } + + if (dstRange.GetSubRange(dstRangeIndex).Size == dstOffset) + { + dstRangeIndex++; + dstOffset = 0; + } + + MemoryRange srcSubRange = srcRange.GetSubRange(srcRangeIndex); + MemoryRange dstSubRange = dstRange.GetSubRange(dstRangeIndex); + + ulong srcSize = srcSubRange.Size - srcOffset; + ulong dstSize = dstSubRange.Size - dstOffset; + ulong copySize = Math.Min(srcSize, dstSize); + + CopyBufferSingleRange(memoryManager, srcSubRange.Address + srcOffset, dstSubRange.Address + dstOffset, copySize); + + srcOffset += copySize; + dstOffset += copySize; + copiedSize += copySize; + } + } + } + + /// + /// Copy a buffer data from a given address to another. + /// + /// + /// This does a GPU side copy. + /// + /// GPU memory manager where the buffer is mapped + /// Physical address of the copy source + /// Physical address of the copy destination + /// Size in bytes of the copy + private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size) + { + Buffer srcBuffer = GetBuffer(srcAddress, size, BufferStage.Copy); + Buffer dstBuffer = GetBuffer(dstAddress, size, BufferStage.Copy); + + int srcOffset = (int)(srcAddress - srcBuffer.Address); + int dstOffset = (int)(dstAddress - dstBuffer.Address); + + _context.Renderer.Pipeline.CopyBuffer( + srcBuffer.Handle, + dstBuffer.Handle, + srcOffset, + dstOffset, + (int)size); + + if (srcBuffer.IsModified(srcAddress, size)) + { + dstBuffer.SignalModified(dstAddress, size, BufferStage.Copy); + } + else + { + // Optimization: If the data being copied is already in memory, then copy it directly instead of flushing from GPU. + + dstBuffer.ClearModified(dstAddress, size); + memoryManager.Physical.WriteTrackedResource(dstAddress, memoryManager.Physical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer); + } + + dstBuffer.CopyToDependantVirtualBuffers(dstAddress, size); + } + + /// + /// Clears a buffer at a given address with the specified value. + /// + /// + /// Both the address and size must be aligned to 4 bytes. + /// + /// GPU memory manager where the buffer is mapped + /// GPU virtual address of the region to clear + /// Number of bytes to clear + /// Value to be written into the buffer + public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, uint value) + { + MultiRange range = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, gpuVa, size, BufferStage.Copy); + + for (int index = 0; index < range.Count; index++) + { + MemoryRange subRange = range.GetSubRange(index); + Buffer buffer = GetBuffer(subRange.Address, subRange.Size, BufferStage.Copy); + + int offset = (int)(subRange.Address - buffer.Address); + + _context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)subRange.Size, value); + + memoryManager.Physical.FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer); + + buffer.CopyToDependantVirtualBuffers(subRange.Address, subRange.Size); + } + } + + /// + /// Gets a buffer sub-range starting at a given memory address, aligned to the next page boundary. + /// + /// Physical regions of memory where the buffer is mapped + /// Buffer stage that triggered the access + /// Whether the buffer will be written to by this use + /// The buffer sub-range starting at the given memory address + public BufferRange GetBufferRangeAligned(MultiRange range, BufferStage stage, bool write = false) + { + if (range.Count > 1) + { + return GetBuffer(range, stage, write).GetRange(range); + } + else + { + MemoryRange subRange = range.GetSubRange(0); + return GetBuffer(subRange.Address, subRange.Size, stage, write).GetRangeAligned(subRange.Address, subRange.Size, write); + } + } + + /// + /// Gets a buffer sub-range for a given memory range. + /// + /// Physical regions of memory where the buffer is mapped + /// Buffer stage that triggered the access + /// Whether the buffer will be written to by this use + /// The buffer sub-range for the given range + public BufferRange GetBufferRange(MultiRange range, BufferStage stage, bool write = false) + { + if (range.Count > 1) + { + return GetBuffer(range, stage, write).GetRange(range); + } + else + { + MemoryRange subRange = range.GetSubRange(0); + return GetBuffer(subRange.Address, subRange.Size, stage, write).GetRange(subRange.Address, subRange.Size, write); + } + } + + /// + /// Gets a buffer for a given memory range. + /// A buffer overlapping with the specified range is assumed to already exist on the cache. + /// + /// Physical regions of memory where the buffer is mapped + /// Buffer stage that triggered the access + /// Whether the buffer will be written to by this use + /// The buffer where the range is fully contained + private MultiRangeBuffer GetBuffer(MultiRange range, BufferStage stage, bool write = false) + { + for (int i = 0; i < range.Count; i++) + { + MemoryRange subRange = range.GetSubRange(i); + + Buffer subBuffer = _buffers.FindFirstOverlap(subRange.Address, subRange.Size); + + subBuffer.SynchronizeMemory(subRange.Address, subRange.Size); + + if (write) + { + subBuffer.SignalModified(subRange.Address, subRange.Size, stage); + } + } + + MultiRangeBuffer[] overlaps = new MultiRangeBuffer[10]; + + int overlapCount = _multiRangeBuffers.FindOverlaps(range, ref overlaps); + + MultiRangeBuffer buffer = null; + + for (int i = 0; i < overlapCount; i++) + { + if (overlaps[i].Range.Contains(range)) + { + buffer = overlaps[i]; + break; + } + } + + if (write && buffer != null && !_context.Capabilities.SupportsSparseBuffer) + { + buffer.AddModifiedRegion(range, ++_virtualModifiedSequenceNumber); + } + + return buffer; + } + + /// + /// Gets a buffer for a given memory range. + /// A buffer overlapping with the specified range is assumed to already exist on the cache. + /// + /// Start address of the memory range + /// Size in bytes of the memory range + /// Buffer stage that triggered the access + /// Whether the buffer will be written to by this use + /// The buffer where the range is fully contained + private Buffer GetBuffer(ulong address, ulong size, BufferStage stage, bool write = false) + { + Buffer buffer; + + if (size != 0) + { + buffer = _buffers.FindFirstOverlap(address, size); + + buffer.CopyFromDependantVirtualBuffers(); + buffer.SynchronizeMemory(address, size); + + if (write) + { + buffer.SignalModified(address, size, stage); + } + } + else + { + buffer = _buffers.FindFirstOverlap(address, 1); + } + + return buffer; + } + + /// + /// Performs guest to host memory synchronization of a given memory range. + /// + /// Physical regions of memory where the buffer is mapped + public void SynchronizeBufferRange(MultiRange range) + { + if (range.Count == 1) + { + MemoryRange subRange = range.GetSubRange(0); + SynchronizeBufferRange(subRange.Address, subRange.Size, copyBackVirtual: true); + } + else + { + for (int index = 0; index < range.Count; index++) + { + MemoryRange subRange = range.GetSubRange(index); + SynchronizeBufferRange(subRange.Address, subRange.Size, copyBackVirtual: false); + } + } + } + + /// + /// Performs guest to host memory synchronization of a given memory range. + /// + /// Start address of the memory range + /// Size in bytes of the memory range + /// Whether virtual buffers that uses this buffer as backing memory should have its data copied back if modified + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SynchronizeBufferRange(ulong address, ulong size, bool copyBackVirtual) + { + if (size != 0) + { + Buffer buffer = _buffers.FindFirstOverlap(address, size); + + if (copyBackVirtual) + { + buffer.CopyFromDependantVirtualBuffers(); + } + + buffer.SynchronizeMemory(address, size); + } + } + + /// + /// Signal that the given buffer's handle has changed, + /// forcing rebind and any overlapping multi-range buffers to be recreated. + /// + /// The buffer that has changed handle + public void BufferBackingChanged(Buffer buffer) + { + NotifyBuffersModified?.Invoke(); + + RecreateMultiRangeBuffers(buffer.Address, buffer.Size); + } + + /// + /// Prune any invalid entries from a quick access dictionary. + /// + /// Dictionary to prune + /// List used to track entries to delete + private static void Prune(Dictionary dictionary, ref List toDelete) + { + foreach (var entry in dictionary) + { + if (entry.Value.UnmappedSequence != entry.Value.Buffer.UnmappedSequence) + { + (toDelete ??= new()).Add(entry.Key); + } + } + + if (toDelete != null) + { + foreach (ulong entry in toDelete) + { + dictionary.Remove(entry); + } + } + } + + /// + /// Prune any invalid entries from the quick access dictionaries. + /// + private void Prune() + { + List toDelete = null; + + Prune(_dirtyCache, ref toDelete); + + toDelete?.Clear(); + + Prune(_modifiedCache, ref toDelete); + + _pruneCaches = false; + } + + /// + /// Queues a prune of invalid entries the next time a dictionary cache is accessed. + /// + public void QueuePrune() + { + _pruneCaches = true; + } + + /// + /// Disposes all buffers in the cache. + /// It's an error to use the buffer cache after disposal. + /// + public void Dispose() + { + lock (_buffers) + { + foreach (Buffer buffer in _buffers) + { + buffer.Dispose(); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferCacheEntry.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferCacheEntry.cs new file mode 100644 index 00000000..ef919c2b --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferCacheEntry.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// A cached entry for easily locating a buffer that is used often internally. + /// + class BufferCacheEntry + { + /// + /// The CPU VA of the buffer destination. + /// + public ulong Address; + + /// + /// The end GPU VA of the associated buffer, used to check if new data can fit. + /// + public ulong EndGpuAddress; + + /// + /// The buffer associated with this cache entry. + /// + public Buffer Buffer; + + /// + /// The UnmappedSequence of the buffer at the time of creation. + /// If this differs from the value currently in the buffer, then this cache entry is outdated. + /// + public int UnmappedSequence; + + /// + /// Create a new cache entry. + /// + /// The CPU VA of the buffer destination + /// The GPU VA of the buffer destination + /// The buffer object containing the target buffer + public BufferCacheEntry(ulong address, ulong gpuVa, Buffer buffer) + { + Address = address; + EndGpuAddress = gpuVa + (buffer.EndAddress - address); + Buffer = buffer; + UnmappedSequence = buffer.UnmappedSequence; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs new file mode 100644 index 00000000..26d9501c --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs @@ -0,0 +1,940 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Shader; +using Ryujinx.Graphics.Shader; +using Ryujinx.Memory.Range; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Buffer manager. + /// + class BufferManager + { + private readonly GpuContext _context; + private readonly GpuChannel _channel; + + private int _unalignedStorageBuffers; + public bool HasUnalignedStorageBuffers => _unalignedStorageBuffers > 0; + + public bool HasTransformFeedbackOutputs { get; set; } + + private IndexBuffer _indexBuffer; + private readonly VertexBuffer[] _vertexBuffers; + private readonly BufferBounds[] _transformFeedbackBuffers; + private readonly List _bufferTextures; + private readonly List> _bufferTextureArrays; + private readonly List> _bufferImageArrays; + private readonly BufferAssignment[] _ranges; + + /// + /// Holds shader stage buffer state and binding information. + /// + private class BuffersPerStage + { + /// + /// Shader buffer binding information. + /// + public BufferDescriptor[] Bindings { get; private set; } + + /// + /// Buffer regions. + /// + public BufferBounds[] Buffers { get; } + + /// + /// Flag indicating if this binding is unaligned. + /// + public bool[] Unaligned { get; } + + /// + /// Total amount of buffers used on the shader. + /// + public int Count { get; private set; } + + /// + /// Creates a new instance of the shader stage buffer information. + /// + /// Maximum amount of buffers that the shader stage can use + public BuffersPerStage(int count) + { + Bindings = new BufferDescriptor[count]; + Buffers = new BufferBounds[count]; + Unaligned = new bool[count]; + + Buffers.AsSpan().Fill(new BufferBounds(new MultiRange(MemoryManager.PteUnmapped, 0UL))); + } + + /// + /// Sets the region of a buffer at a given slot. + /// + /// Buffer slot + /// Physical memory regions where the buffer is mapped + /// Buffer usage flags + public void SetBounds(int index, MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None) + { + Buffers[index] = new BufferBounds(range, flags); + } + + /// + /// Sets shader buffer binding information. + /// + /// Buffer binding information + public void SetBindings(BufferDescriptor[] descriptors) + { + if (descriptors == null) + { + Count = 0; + return; + } + + if ((Count = descriptors.Length) != 0) + { + Bindings = descriptors; + } + } + } + + private readonly BuffersPerStage _cpStorageBuffers; + private readonly BuffersPerStage _cpUniformBuffers; + private readonly BuffersPerStage[] _gpStorageBuffers; + private readonly BuffersPerStage[] _gpUniformBuffers; + + private bool _gpStorageBuffersDirty; + private bool _gpUniformBuffersDirty; + + private bool _indexBufferDirty; + private bool _vertexBuffersDirty; + private uint _vertexBuffersEnableMask; + private bool _transformFeedbackBuffersDirty; + + private bool _rebind; + + /// + /// Creates a new instance of the buffer manager. + /// + /// GPU context that the buffer manager belongs to + /// GPU channel that the buffer manager belongs to + public BufferManager(GpuContext context, GpuChannel channel) + { + _context = context; + _channel = channel; + + _indexBuffer.Range = new MultiRange(MemoryManager.PteUnmapped, 0UL); + _vertexBuffers = new VertexBuffer[Constants.TotalVertexBuffers]; + + _transformFeedbackBuffers = new BufferBounds[Constants.TotalTransformFeedbackBuffers]; + + _cpStorageBuffers = new BuffersPerStage(Constants.TotalCpStorageBuffers); + _cpUniformBuffers = new BuffersPerStage(Constants.TotalCpUniformBuffers); + + _gpStorageBuffers = new BuffersPerStage[Constants.ShaderStages]; + _gpUniformBuffers = new BuffersPerStage[Constants.ShaderStages]; + + for (int index = 0; index < Constants.ShaderStages; index++) + { + _gpStorageBuffers[index] = new BuffersPerStage(Constants.TotalGpStorageBuffers); + _gpUniformBuffers[index] = new BuffersPerStage(Constants.TotalGpUniformBuffers); + } + + _bufferTextures = new List(); + _bufferTextureArrays = new List>(); + _bufferImageArrays = new List>(); + + _ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages]; + } + + /// + /// Sets the memory range with the index buffer data, to be used for subsequent draw calls. + /// + /// Start GPU virtual address of the index buffer + /// Size, in bytes, of the index buffer + /// Type of each index buffer element + public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type) + { + MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.IndexBuffer); + + _indexBuffer.Range = range; + _indexBuffer.Type = type; + + _indexBufferDirty = true; + } + + /// + /// Sets a new index buffer that overrides the one set on the call to . + /// + /// Buffer to be used as index buffer + /// Type of each index buffer element + public void SetIndexBuffer(BufferRange buffer, IndexType type) + { + _context.Renderer.Pipeline.SetIndexBuffer(buffer, type); + + _indexBufferDirty = true; + } + + /// + /// Sets the memory range with vertex buffer data, to be used for subsequent draw calls. + /// + /// Index of the vertex buffer (up to 16) + /// GPU virtual address of the buffer + /// Size in bytes of the buffer + /// Stride of the buffer, defined as the number of bytes of each vertex + /// Vertex divisor of the buffer, for instanced draws + public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor) + { + MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.VertexBuffer); + + _vertexBuffers[index].Range = range; + _vertexBuffers[index].Stride = stride; + _vertexBuffers[index].Divisor = divisor; + + _vertexBuffersDirty = true; + + if (!range.IsUnmapped) + { + _vertexBuffersEnableMask |= 1u << index; + } + else + { + _vertexBuffersEnableMask &= ~(1u << index); + } + } + + /// + /// Sets a transform feedback buffer on the graphics pipeline. + /// The output from the vertex transformation stages are written into the feedback buffer. + /// + /// Index of the transform feedback buffer + /// Start GPU virtual address of the buffer + /// Size in bytes of the transform feedback buffer + public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size) + { + MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStage.TransformFeedback); + + _transformFeedbackBuffers[index] = new BufferBounds(range); + _transformFeedbackBuffersDirty = true; + } + + /// + /// Records the alignment of a storage buffer. + /// Unaligned storage buffers disable some optimizations on the shader. + /// + /// The binding list to modify + /// Index of the storage buffer + /// Start GPU virtual address of the buffer + private void RecordStorageAlignment(BuffersPerStage buffers, int index, ulong gpuVa) + { + bool unaligned = (gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1)) != 0; + + if (unaligned || HasUnalignedStorageBuffers) + { + // Check if the alignment changed for this binding. + + ref bool currentUnaligned = ref buffers.Unaligned[index]; + + if (currentUnaligned != unaligned) + { + currentUnaligned = unaligned; + _unalignedStorageBuffers += unaligned ? 1 : -1; + } + } + } + + /// + /// Sets a storage buffer on the compute pipeline. + /// Storage buffers can be read and written to on shaders. + /// + /// Index of the storage buffer + /// Start GPU virtual address of the buffer + /// Size in bytes of the storage buffer + /// Buffer usage flags + public void SetComputeStorageBuffer(int index, ulong gpuVa, ulong size, BufferUsageFlags flags) + { + size += gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1); + + RecordStorageAlignment(_cpStorageBuffers, index, gpuVa); + + gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment); + + MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.ComputeStorage(flags)); + + _cpStorageBuffers.SetBounds(index, range, flags); + } + + /// + /// Sets a storage buffer on the graphics pipeline. + /// Storage buffers can be read and written to on shaders. + /// + /// Index of the shader stage + /// Index of the storage buffer + /// Start GPU virtual address of the buffer + /// Size in bytes of the storage buffer + /// Buffer usage flags + public void SetGraphicsStorageBuffer(int stage, int index, ulong gpuVa, ulong size, BufferUsageFlags flags) + { + size += gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1); + + BuffersPerStage buffers = _gpStorageBuffers[stage]; + + RecordStorageAlignment(buffers, index, gpuVa); + + gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment); + + MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.GraphicsStorage(stage, flags)); + + if (!buffers.Buffers[index].Range.Equals(range)) + { + _gpStorageBuffersDirty = true; + } + + buffers.SetBounds(index, range, flags); + } + + /// + /// Sets a uniform buffer on the compute pipeline. + /// Uniform buffers are read-only from shaders, and have a small capacity. + /// + /// Index of the uniform buffer + /// Start GPU virtual address of the buffer + /// Size in bytes of the storage buffer + public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size) + { + MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.Compute); + + _cpUniformBuffers.SetBounds(index, range); + } + + /// + /// Sets a uniform buffer on the graphics pipeline. + /// Uniform buffers are read-only from shaders, and have a small capacity. + /// + /// Index of the shader stage + /// Index of the uniform buffer + /// Start GPU virtual address of the buffer + /// Size in bytes of the storage buffer + public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size) + { + MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStageUtils.FromShaderStage(stage)); + + _gpUniformBuffers[stage].SetBounds(index, range); + _gpUniformBuffersDirty = true; + } + + /// + /// Sets the number of vertices per instance on a instanced draw. Used for transform feedback emulation. + /// + /// Vertex count per instance + public void SetInstancedDrawVertexCount(int vertexCount) + { + if (!_context.Capabilities.SupportsTransformFeedback && HasTransformFeedbackOutputs) + { + _context.SupportBufferUpdater.SetTfeVertexCount(vertexCount); + _context.SupportBufferUpdater.Commit(); + } + } + + /// + /// Forces transform feedback and storage buffers to be updated on the next draw. + /// + public void ForceTransformFeedbackAndStorageBuffersDirty() + { + _transformFeedbackBuffersDirty = true; + _gpStorageBuffersDirty = true; + } + + /// + /// Sets the binding points for the storage buffers bound on the compute pipeline. + /// + /// Bindings for the active shader + public void SetComputeBufferBindings(CachedShaderBindings bindings) + { + _cpStorageBuffers.SetBindings(bindings.StorageBufferBindings[0]); + _cpUniformBuffers.SetBindings(bindings.ConstantBufferBindings[0]); + } + + /// + /// Sets the binding points for the storage buffers bound on the graphics pipeline. + /// + /// Bindings for the active shader + public void SetGraphicsBufferBindings(CachedShaderBindings bindings) + { + for (int i = 0; i < Constants.ShaderStages; i++) + { + _gpStorageBuffers[i].SetBindings(bindings.StorageBufferBindings[i]); + _gpUniformBuffers[i].SetBindings(bindings.ConstantBufferBindings[i]); + } + + _gpStorageBuffersDirty = true; + _gpUniformBuffersDirty = true; + } + + /// + /// Gets a bit mask indicating which compute uniform buffers are currently bound. + /// + /// Mask where each bit set indicates a bound constant buffer + public uint GetComputeUniformBufferUseMask() + { + uint mask = 0; + + for (int i = 0; i < _cpUniformBuffers.Buffers.Length; i++) + { + if (!_cpUniformBuffers.Buffers[i].IsUnmapped) + { + mask |= 1u << i; + } + } + + return mask; + } + + /// + /// Gets a bit mask indicating which graphics uniform buffers are currently bound. + /// + /// Index of the shader stage + /// Mask where each bit set indicates a bound constant buffer + public uint GetGraphicsUniformBufferUseMask(int stage) + { + uint mask = 0; + + for (int i = 0; i < _gpUniformBuffers[stage].Buffers.Length; i++) + { + if (!_gpUniformBuffers[stage].Buffers[i].IsUnmapped) + { + mask |= 1u << i; + } + } + + return mask; + } + + /// + /// Gets the address of the compute uniform buffer currently bound at the given index. + /// + /// Index of the uniform buffer binding + /// The uniform buffer address, or an undefined value if the buffer is not currently bound + public ulong GetComputeUniformBufferAddress(int index) + { + return _cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Address; + } + + /// + /// Gets the size of the compute uniform buffer currently bound at the given index. + /// + /// Index of the uniform buffer binding + /// The uniform buffer size, or an undefined value if the buffer is not currently bound + public int GetComputeUniformBufferSize(int index) + { + return (int)_cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Size; + } + + /// + /// Gets the address of the graphics uniform buffer currently bound at the given index. + /// + /// Index of the shader stage + /// Index of the uniform buffer binding + /// The uniform buffer address, or an undefined value if the buffer is not currently bound + public ulong GetGraphicsUniformBufferAddress(int stage, int index) + { + return _gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Address; + } + + /// + /// Gets the size of the graphics uniform buffer currently bound at the given index. + /// + /// Index of the shader stage + /// Index of the uniform buffer binding + /// The uniform buffer size, or an undefined value if the buffer is not currently bound + public int GetGraphicsUniformBufferSize(int stage, int index) + { + return (int)_gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Size; + } + + /// + /// Gets the bounds of the uniform buffer currently bound at the given index. + /// + /// Indicates whenever the uniform is requested by the 3D or compute engine + /// Index of the shader stage, if the uniform is for the 3D engine + /// Index of the uniform buffer binding + /// The uniform buffer bounds, or an undefined value if the buffer is not currently bound + public ref BufferBounds GetUniformBufferBounds(bool isCompute, int stage, int index) + { + if (isCompute) + { + return ref _cpUniformBuffers.Buffers[index]; + } + else + { + return ref _gpUniformBuffers[stage].Buffers[index]; + } + } + + /// + /// Ensures that the compute engine bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + public void CommitComputeBindings() + { + var bufferCache = _channel.MemoryManager.Physical.BufferCache; + + BindBuffers(bufferCache, _cpStorageBuffers, isStorage: true); + BindBuffers(bufferCache, _cpUniformBuffers, isStorage: false); + + CommitBufferTextureBindings(bufferCache); + + // Force rebind after doing compute work. + Rebind(); + + _context.SupportBufferUpdater.Commit(); + } + + /// + /// Commit any queued buffer texture bindings. + /// + /// Buffer cache + private void CommitBufferTextureBindings(BufferCache bufferCache) + { + if (_bufferTextures.Count > 0) + { + foreach (var binding in _bufferTextures) + { + var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + var range = bufferCache.GetBufferRange(binding.Range, BufferStageUtils.TextureBuffer(binding.Stage, binding.BindingInfo.Flags), isStore); + binding.Texture.SetStorage(range); + + // The texture must be rebound to use the new storage if it was updated. + + if (binding.IsImage) + { + _context.Renderer.Pipeline.SetImage(binding.Stage, binding.BindingInfo.Binding, binding.Texture, binding.Format); + } + else + { + _context.Renderer.Pipeline.SetTextureAndSampler(binding.Stage, binding.BindingInfo.Binding, binding.Texture, null); + } + } + + _bufferTextures.Clear(); + } + + if (_bufferTextureArrays.Count > 0 || _bufferImageArrays.Count > 0) + { + ITexture[] textureArray = new ITexture[1]; + + foreach (var binding in _bufferTextureArrays) + { + var range = bufferCache.GetBufferRange(binding.Range, BufferStage.None); + binding.Texture.SetStorage(range); + + textureArray[0] = binding.Texture; + binding.Array.SetTextures(binding.Index, textureArray); + } + + foreach (var binding in _bufferImageArrays) + { + var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); + var range = bufferCache.GetBufferRange(binding.Range, BufferStage.None, isStore); + binding.Texture.SetStorage(range); + + textureArray[0] = binding.Texture; + binding.Array.SetImages(binding.Index, textureArray); + } + + _bufferTextureArrays.Clear(); + _bufferImageArrays.Clear(); + } + } + + /// + /// Ensures that the graphics engine bindings are visible to the host GPU. + /// Note: this actually performs the binding using the host graphics API. + /// + /// True if the index buffer is in use + public void CommitGraphicsBindings(bool indexed) + { + var bufferCache = _channel.MemoryManager.Physical.BufferCache; + + if (indexed) + { + if (_indexBufferDirty || _rebind) + { + _indexBufferDirty = false; + + if (!_indexBuffer.Range.IsUnmapped) + { + BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Range, BufferStage.IndexBuffer); + + _context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type); + } + } + else if (!_indexBuffer.Range.IsUnmapped) + { + bufferCache.SynchronizeBufferRange(_indexBuffer.Range); + } + } + else if (_rebind) + { + _indexBufferDirty = true; + } + + uint vbEnableMask = _vertexBuffersEnableMask; + + if (_vertexBuffersDirty || _rebind) + { + _vertexBuffersDirty = false; + + Span vertexBuffers = stackalloc VertexBufferDescriptor[Constants.TotalVertexBuffers]; + + for (int index = 0; (vbEnableMask >> index) != 0; index++) + { + VertexBuffer vb = _vertexBuffers[index]; + + if (vb.Range.IsUnmapped) + { + continue; + } + + BufferRange buffer = bufferCache.GetBufferRange(vb.Range, BufferStage.VertexBuffer); + + vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor); + } + + _context.Renderer.Pipeline.SetVertexBuffers(vertexBuffers); + } + else + { + for (int index = 0; (vbEnableMask >> index) != 0; index++) + { + VertexBuffer vb = _vertexBuffers[index]; + + if (vb.Range.IsUnmapped) + { + continue; + } + + bufferCache.SynchronizeBufferRange(vb.Range); + } + } + + if (_transformFeedbackBuffersDirty || _rebind) + { + _transformFeedbackBuffersDirty = false; + + if (_context.Capabilities.SupportsTransformFeedback) + { + Span tfbs = stackalloc BufferRange[Constants.TotalTransformFeedbackBuffers]; + + for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++) + { + BufferBounds tfb = _transformFeedbackBuffers[index]; + + if (tfb.IsUnmapped) + { + tfbs[index] = BufferRange.Empty; + continue; + } + + tfbs[index] = bufferCache.GetBufferRange(tfb.Range, BufferStage.TransformFeedback, write: true); + } + + _context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs); + } + else if (HasTransformFeedbackOutputs) + { + Span buffers = stackalloc BufferAssignment[Constants.TotalTransformFeedbackBuffers]; + + int alignment = _context.Capabilities.StorageBufferOffsetAlignment; + + for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++) + { + BufferBounds tfb = _transformFeedbackBuffers[index]; + + if (tfb.IsUnmapped) + { + buffers[index] = new BufferAssignment(index, BufferRange.Empty); + } + else + { + MultiRange range = tfb.Range; + ulong address0 = range.GetSubRange(0).Address; + ulong address = BitUtils.AlignDown(address0, (ulong)alignment); + + if (range.Count == 1) + { + range = new MultiRange(address, range.GetSubRange(0).Size + (address0 - address)); + } + else + { + MemoryRange[] subRanges = new MemoryRange[range.Count]; + + subRanges[0] = new MemoryRange(address, range.GetSubRange(0).Size + (address0 - address)); + + for (int i = 1; i < range.Count; i++) + { + subRanges[i] = range.GetSubRange(i); + } + + range = new MultiRange(subRanges); + } + + int tfeOffset = ((int)address0 & (alignment - 1)) / 4; + + _context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset); + + buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(range, BufferStage.TransformFeedback, write: true)); + } + } + + _context.Renderer.Pipeline.SetStorageBuffers(buffers); + } + } + else + { + for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++) + { + BufferBounds tfb = _transformFeedbackBuffers[index]; + + if (tfb.IsUnmapped) + { + continue; + } + + bufferCache.SynchronizeBufferRange(tfb.Range); + } + } + + if (_gpStorageBuffersDirty || _rebind) + { + _gpStorageBuffersDirty = false; + + BindBuffers(bufferCache, _gpStorageBuffers, isStorage: true); + } + else + { + UpdateBuffers(_gpStorageBuffers); + } + + if (_gpUniformBuffersDirty || _rebind) + { + _gpUniformBuffersDirty = false; + + BindBuffers(bufferCache, _gpUniformBuffers, isStorage: false); + } + else + { + UpdateBuffers(_gpUniformBuffers); + } + + CommitBufferTextureBindings(bufferCache); + + _rebind = false; + + _context.SupportBufferUpdater.Commit(); + } + + /// + /// Bind respective buffer bindings on the host API. + /// + /// Buffer cache holding the buffers for the specified ranges + /// Buffer memory ranges to bind + /// True to bind as storage buffer, false to bind as uniform buffer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void BindBuffers(BufferCache bufferCache, BuffersPerStage[] bindings, bool isStorage) + { + int rangesCount = 0; + + Span ranges = _ranges; + + for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++) + { + ref var buffers = ref bindings[(int)stage - 1]; + BufferStage bufferStage = BufferStageUtils.FromShaderStage(stage); + + for (int index = 0; index < buffers.Count; index++) + { + ref var bindingInfo = ref buffers.Bindings[index]; + + BufferBounds bounds = buffers.Buffers[bindingInfo.Slot]; + + if (!bounds.IsUnmapped) + { + var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write); + var range = isStorage + ? bufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite) + : bufferCache.GetBufferRange(bounds.Range, bufferStage); + + ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range); + } + } + } + + if (rangesCount != 0) + { + SetHostBuffers(ranges, rangesCount, isStorage); + } + } + + /// + /// Bind respective buffer bindings on the host API. + /// + /// Buffer cache holding the buffers for the specified ranges + /// Buffer memory ranges to bind + /// True to bind as storage buffer, false to bind as uniform buffer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void BindBuffers(BufferCache bufferCache, BuffersPerStage buffers, bool isStorage) + { + int rangesCount = 0; + + Span ranges = _ranges; + + for (int index = 0; index < buffers.Count; index++) + { + ref var bindingInfo = ref buffers.Bindings[index]; + + BufferBounds bounds = buffers.Buffers[bindingInfo.Slot]; + + if (!bounds.IsUnmapped) + { + var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write); + var range = isStorage + ? bufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite) + : bufferCache.GetBufferRange(bounds.Range, BufferStage.Compute); + + ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range); + } + } + + if (rangesCount != 0) + { + SetHostBuffers(ranges, rangesCount, isStorage); + } + } + + /// + /// Bind respective buffer bindings on the host API. + /// + /// Host buffers to bind, with their offsets and sizes + /// First binding point + /// Number of bindings + /// Indicates if the buffers are storage or uniform buffers + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetHostBuffers(ReadOnlySpan ranges, int count, bool isStorage) + { + if (isStorage) + { + _context.Renderer.Pipeline.SetStorageBuffers(ranges[..count]); + } + else + { + _context.Renderer.Pipeline.SetUniformBuffers(ranges[..count]); + } + } + + /// + /// Updates data for the already bound buffer bindings. + /// + /// Bindings to update + private void UpdateBuffers(BuffersPerStage[] bindings) + { + for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++) + { + ref var buffers = ref bindings[(int)stage - 1]; + + for (int index = 0; index < buffers.Count; index++) + { + ref var binding = ref buffers.Bindings[index]; + + BufferBounds bounds = buffers.Buffers[binding.Slot]; + + if (bounds.IsUnmapped) + { + continue; + } + + _channel.MemoryManager.Physical.BufferCache.SynchronizeBufferRange(bounds.Range); + } + } + } + + /// + /// Sets the buffer storage of a buffer texture. This will be bound when the buffer manager commits bindings. + /// + /// Shader stage accessing the texture + /// Buffer texture + /// Physical ranges of memory where the buffer texture data is located + /// Binding info for the buffer texture + /// Format of the buffer texture + /// Whether the binding is for an image or a sampler + public void SetBufferTextureStorage( + ShaderStage stage, + ITexture texture, + MultiRange range, + TextureBindingInfo bindingInfo, + Format format, + bool isImage) + { + _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags)); + + _bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage)); + } + + /// + /// Sets the buffer storage of a buffer texture array element. This will be bound when the buffer manager commits bindings. + /// + /// Shader stage accessing the texture + /// Texture array where the element will be inserted + /// Buffer texture + /// Physical ranges of memory where the buffer texture data is located + /// Binding info for the buffer texture + /// Index of the binding on the array + /// Format of the buffer texture + public void SetBufferTextureStorage( + ShaderStage stage, + ITextureArray array, + ITexture texture, + MultiRange range, + TextureBindingInfo bindingInfo, + int index, + Format format) + { + _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags)); + + _bufferTextureArrays.Add(new BufferTextureArrayBinding(array, texture, range, bindingInfo, index, format)); + } + + /// + /// Sets the buffer storage of a buffer image array element. This will be bound when the buffer manager commits bindings. + /// + /// Shader stage accessing the texture + /// Image array where the element will be inserted + /// Buffer texture + /// Physical ranges of memory where the buffer texture data is located + /// Binding info for the buffer texture + /// Index of the binding on the array + /// Format of the buffer texture + public void SetBufferTextureStorage( + ShaderStage stage, + IImageArray array, + ITexture texture, + MultiRange range, + TextureBindingInfo bindingInfo, + int index, + Format format) + { + _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags)); + + _bufferImageArrays.Add(new BufferTextureArrayBinding(array, texture, range, bindingInfo, index, format)); + } + + /// + /// Force all bound textures and images to be rebound the next time CommitBindings is called. + /// + public void Rebind() + { + _rebind = true; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs new file mode 100644 index 00000000..ce998531 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs @@ -0,0 +1,251 @@ +using System; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// A record of when buffer data was copied from multiple buffers to one migration target, + /// along with the SyncNumber when the migration will be complete. + /// Keeps the source buffers alive for data flushes until the migration is complete. + /// All spans cover the full range of the "destination" buffer. + /// + internal class BufferMigration : IDisposable + { + /// + /// Ranges from source buffers that were copied as part of this migration. + /// Ordered by increasing base address. + /// + public BufferMigrationSpan[] Spans { get; private set; } + + /// + /// The destination range list. This range list must be updated when flushing the source. + /// + public readonly BufferModifiedRangeList Destination; + + /// + /// The sync number that needs to be reached for this migration to be removed. This is set to the pending sync number on creation. + /// + public readonly ulong SyncNumber; + + /// + /// Number of active users there are traversing this migration's spans. + /// + private int _refCount; + + /// + /// Create a new buffer migration. + /// + /// Source spans for the migration + /// Destination buffer range list + /// Sync number where this migration will be complete + public BufferMigration(BufferMigrationSpan[] spans, BufferModifiedRangeList destination, ulong syncNumber) + { + Spans = spans; + Destination = destination; + SyncNumber = syncNumber; + } + + /// + /// Add a span to the migration. Allocates a new array with the target size, and replaces it. + /// + /// + /// The base address for the span is assumed to be higher than all other spans in the migration, + /// to keep the span array ordered. + /// + public void AddSpanToEnd(BufferMigrationSpan span) + { + BufferMigrationSpan[] oldSpans = Spans; + + BufferMigrationSpan[] newSpans = new BufferMigrationSpan[oldSpans.Length + 1]; + + oldSpans.CopyTo(newSpans, 0); + + newSpans[oldSpans.Length] = span; + + Spans = newSpans; + } + + /// + /// Performs the given range action, or one from a migration that overlaps and has not synced yet. + /// + /// The offset to pass to the action + /// The size to pass to the action + /// The sync number that has been reached + /// The action to perform + public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferFlushAction rangeAction) + { + long syncDiff = (long)(syncNumber - SyncNumber); + + if (syncDiff >= 0) + { + // The migration has completed. Run the parent action. + rangeAction(offset, size, syncNumber); + } + else + { + Interlocked.Increment(ref _refCount); + + ulong prevAddress = offset; + ulong endAddress = offset + size; + + foreach (BufferMigrationSpan span in Spans) + { + if (!span.Overlaps(offset, size)) + { + continue; + } + + if (span.Address > prevAddress) + { + // There's a gap between this span and the last (or the start address). Flush the range using the parent action. + + rangeAction(prevAddress, span.Address - prevAddress, syncNumber); + } + + span.RangeActionWithMigration(offset, size, syncNumber); + + prevAddress = span.Address + span.Size; + } + + if (endAddress > prevAddress) + { + // There's a gap at the end of the range with no migration. Flush the range using the parent action. + rangeAction(prevAddress, endAddress - prevAddress, syncNumber); + } + + Interlocked.Decrement(ref _refCount); + } + } + + /// + /// Dispose the buffer migration. This removes the reference from the destination range list, + /// and runs all the dispose buffers for the migration spans. (typically disposes the source buffer) + /// + public void Dispose() + { + while (Volatile.Read(ref _refCount) > 0) + { + // Coming into this method, the sync for the migration will be met, so nothing can increment the ref count. + // However, an existing traversal of the spans for data flush could still be in progress. + // Spin if this is ever the case, so they don't get disposed before the operation is complete. + } + + Destination.RemoveMigration(this); + + foreach (BufferMigrationSpan span in Spans) + { + span.Dispose(); + } + } + } + + /// + /// A record of when buffer data was copied from one buffer to another, for a specific range in a source buffer. + /// Keeps the source buffer alive for data flushes until the migration is complete. + /// + internal readonly struct BufferMigrationSpan : IDisposable + { + /// + /// The offset for the migrated region. + /// + public readonly ulong Address; + + /// + /// The size for the migrated region. + /// + public readonly ulong Size; + + /// + /// The action to perform when the migration isn't needed anymore. + /// + private readonly Action _disposeAction; + + /// + /// The source range action, to be called on overlap with an unreached sync number. + /// + private readonly BufferFlushAction _sourceRangeAction; + + /// + /// Optional migration for the source data. Can chain together if many migrations happen in a short time. + /// If this is null, then _sourceRangeAction will always provide up to date data. + /// + private readonly BufferMigration _source; + + /// + /// Creates a record for a buffer migration. + /// + /// The source buffer for this migration + /// The action to perform when the migration isn't needed anymore + /// The flush action for the source buffer + /// Pending migration for the source buffer + public BufferMigrationSpan( + Buffer buffer, + Action disposeAction, + BufferFlushAction sourceRangeAction, + BufferMigration source) + { + Address = buffer.Address; + Size = buffer.Size; + _disposeAction = disposeAction; + _sourceRangeAction = sourceRangeAction; + _source = source; + } + + /// + /// Creates a record for a buffer migration, using the default buffer dispose action. + /// + /// The source buffer for this migration + /// The flush action for the source buffer + /// Pending migration for the source buffer + public BufferMigrationSpan( + Buffer buffer, + BufferFlushAction sourceRangeAction, + BufferMigration source) : this(buffer, buffer.DecrementReferenceCount, sourceRangeAction, source) { } + + /// + /// Determine if the given range overlaps this migration, and has not been completed yet. + /// + /// Start offset + /// Range size + /// True if overlapping and in progress, false otherwise + public bool Overlaps(ulong offset, ulong size) + { + ulong end = offset + size; + ulong destEnd = Address + Size; + + return !(end <= Address || offset >= destEnd); + } + + /// + /// Perform the migration source's range action on the range provided, clamped to the bounds of the source buffer. + /// + /// Start offset + /// Range size + /// Current sync number + public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber) + { + ulong end = offset + size; + end = Math.Min(Address + Size, end); + offset = Math.Max(Address, offset); + + size = end - offset; + + if (_source != null) + { + _source.RangeActionWithMigration(offset, size, syncNumber, _sourceRangeAction); + } + else + { + _sourceRangeAction(offset, size, syncNumber); + } + } + + /// + /// Removes this migration span, potentially allowing for the source buffer to be disposed. + /// + public void Dispose() + { + _disposeAction(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs new file mode 100644 index 00000000..d330de63 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs @@ -0,0 +1,557 @@ +using Ryujinx.Common.Pools; +using Ryujinx.Memory.Range; +using System; +using System.Linq; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// A range within a buffer that has been modified by the GPU. + /// + class BufferModifiedRange : IRange + { + /// + /// Start address of the range in guest memory. + /// + public ulong Address { get; } + + /// + /// Size of the range in bytes. + /// + public ulong Size { get; } + + /// + /// End address of the range in guest memory. + /// + public ulong EndAddress => Address + Size; + + /// + /// The GPU sync number at the time of the last modification. + /// + public ulong SyncNumber { get; internal set; } + + /// + /// The range list that originally owned this range. + /// + public BufferModifiedRangeList Parent { get; internal set; } + + /// + /// Creates a new instance of a modified range. + /// + /// Start address of the range + /// Size of the range in bytes + /// The GPU sync number at the time of creation + /// The range list that owns this range + public BufferModifiedRange(ulong address, ulong size, ulong syncNumber, BufferModifiedRangeList parent) + { + Address = address; + Size = size; + SyncNumber = syncNumber; + Parent = parent; + } + + /// + /// Checks if a given range overlaps with the modified range. + /// + /// Start address of the range + /// Size in bytes of the range + /// True if the range overlaps, false otherwise + public bool OverlapsWith(ulong address, ulong size) + { + return Address < address + size && address < EndAddress; + } + } + + /// + /// A structure used to track GPU modified ranges within a buffer. + /// + class BufferModifiedRangeList : RangeList + { + private const int BackingInitialSize = 8; + + private readonly GpuContext _context; + private readonly Buffer _parent; + private readonly BufferFlushAction _flushAction; + + private BufferMigration _source; + private BufferModifiedRangeList _migrationTarget; + + private readonly object _lock = new(); + + /// + /// Whether the modified range list has any entries or not. + /// + public bool HasRanges + { + get + { + lock (_lock) + { + return Count > 0; + } + } + } + + /// + /// Creates a new instance of a modified range list. + /// + /// GPU context that the buffer range list belongs to + /// The parent buffer that owns this range list + /// The flush action for the parent buffer + public BufferModifiedRangeList(GpuContext context, Buffer parent, BufferFlushAction flushAction) : base(BackingInitialSize) + { + _context = context; + _parent = parent; + _flushAction = flushAction; + } + + /// + /// Given an input range, calls the given action with sub-ranges which exclude any of the modified regions. + /// + /// Start address of the query range + /// Size of the query range in bytes + /// Action to perform for each remaining sub-range of the input range + public void ExcludeModifiedRegions(ulong address, ulong size, Action action) + { + lock (_lock) + { + // Slices a given region using the modified regions in the list. Calls the action for the new slices. + ref var overlaps = ref ThreadStaticArray.Get(); + + int count = FindOverlapsNonOverlapping(address, size, ref overlaps); + + for (int i = 0; i < count; i++) + { + BufferModifiedRange overlap = overlaps[i]; + + if (overlap.Address > address) + { + // The start of the remaining region is uncovered by this overlap. Call the action for it. + action(address, overlap.Address - address); + } + + // Remaining region is after this overlap. + size -= overlap.EndAddress - address; + address = overlap.EndAddress; + } + + if ((long)size > 0) + { + // If there is any region left after removing the overlaps, signal it. + action(address, size); + } + } + } + + /// + /// Signal that a region of the buffer has been modified, and add the new region to the range list. + /// Any overlapping ranges will be (partially) removed. + /// + /// Start address of the modified region + /// Size of the modified region in bytes + public void SignalModified(ulong address, ulong size) + { + // Must lock, as this can affect flushes from the background thread. + lock (_lock) + { + // We may overlap with some existing modified regions. They must be cut into by the new entry. + ref var overlaps = ref ThreadStaticArray.Get(); + + int count = FindOverlapsNonOverlapping(address, size, ref overlaps); + + ulong endAddress = address + size; + ulong syncNumber = _context.SyncNumber; + + for (int i = 0; i < count; i++) + { + // The overlaps must be removed or split. + + BufferModifiedRange overlap = overlaps[i]; + + if (overlap.Address == address && overlap.Size == size) + { + // Region already exists. Just update the existing sync number. + overlap.SyncNumber = syncNumber; + overlap.Parent = this; + + return; + } + + Remove(overlap); + + if (overlap.Address < address && overlap.EndAddress > address) + { + // A split item must be created behind this overlap. + + Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent)); + } + + if (overlap.Address < endAddress && overlap.EndAddress > endAddress) + { + // A split item must be created after this overlap. + + Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent)); + } + } + + Add(new BufferModifiedRange(address, size, syncNumber, this)); + } + } + + /// + /// Gets modified ranges within the specified region, and then fires the given action for each range individually. + /// + /// Start address to query + /// Size to query + /// Sync number required for a range to be signalled + /// The action to call for each modified range + public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action rangeAction) + { + int count = 0; + + ref var overlaps = ref ThreadStaticArray.Get(); + + // Range list must be consistent for this operation. + lock (_lock) + { + count = FindOverlapsNonOverlapping(address, size, ref overlaps); + } + + for (int i = 0; i < count; i++) + { + BufferModifiedRange overlap = overlaps[i]; + + if (overlap.SyncNumber == syncNumber) + { + rangeAction(overlap.Address, overlap.Size); + } + } + } + + /// + /// Gets modified ranges within the specified region, and then fires the given action for each range individually. + /// + /// Start address to query + /// Size to query + /// The action to call for each modified range + public void GetRanges(ulong address, ulong size, Action rangeAction) + { + int count = 0; + + ref var overlaps = ref ThreadStaticArray.Get(); + + // Range list must be consistent for this operation. + lock (_lock) + { + count = FindOverlapsNonOverlapping(address, size, ref overlaps); + } + + for (int i = 0; i < count; i++) + { + BufferModifiedRange overlap = overlaps[i]; + rangeAction(overlap.Address, overlap.Size); + } + } + + /// + /// Queries if a range exists within the specified region. + /// + /// Start address to query + /// Size to query + /// True if a range exists in the specified region, false otherwise + public bool HasRange(ulong address, ulong size) + { + // Range list must be consistent for this operation. + lock (_lock) + { + return FindOverlapsNonOverlapping(address, size, ref ThreadStaticArray.Get()) > 0; + } + } + + /// + /// Performs the given range action, or one from a migration that overlaps and has not synced yet. + /// + /// The offset to pass to the action + /// The size to pass to the action + /// The sync number that has been reached + /// The action to perform + public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferFlushAction rangeAction) + { + if (_source != null) + { + _source.RangeActionWithMigration(offset, size, syncNumber, rangeAction); + } + else + { + rangeAction(offset, size, syncNumber); + } + } + + /// + /// Removes modified ranges ready by the sync number from the list, and flushes their buffer data within a given address range. + /// + /// Overlapping ranges to check + /// Number of overlapping ranges + /// The highest difference between an overlapping range's sync number and the current one + /// The current sync number + /// The start address of the flush range + /// The end address of the flush range + private void RemoveRangesAndFlush( + BufferModifiedRange[] overlaps, + int rangeCount, + long highestDiff, + ulong currentSync, + ulong address, + ulong endAddress) + { + lock (_lock) + { + if (_migrationTarget == null) + { + ulong waitSync = currentSync + (ulong)highestDiff; + + for (int i = 0; i < rangeCount; i++) + { + BufferModifiedRange overlap = overlaps[i]; + + long diff = (long)(overlap.SyncNumber - currentSync); + + if (diff <= highestDiff) + { + ulong clampAddress = Math.Max(address, overlap.Address); + ulong clampEnd = Math.Min(endAddress, overlap.EndAddress); + + ClearPart(overlap, clampAddress, clampEnd); + + RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction); + } + } + + return; + } + } + + // There is a migration target to call instead. This can't be changed after set so accessing it outside the lock is fine. + + _migrationTarget.RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress); + } + + /// + /// Gets modified ranges within the specified region, waits on ones from a previous sync number, + /// and then fires the flush action for each range individually. + /// + /// + /// This function assumes it is called from the background thread. + /// Modifications from the current sync number are ignored because the guest should not expect them to be available yet. + /// They will remain reserved, so that any data sync prioritizes the data in the GPU. + /// + /// Start address to query + /// Size to query + public void WaitForAndFlushRanges(ulong address, ulong size) + { + ulong endAddress = address + size; + ulong currentSync = _context.SyncNumber; + + int rangeCount = 0; + + ref var overlaps = ref ThreadStaticArray.Get(); + + // Range list must be consistent for this operation + lock (_lock) + { + if (_migrationTarget != null) + { + rangeCount = -1; + } + else + { + rangeCount = FindOverlapsNonOverlapping(address, size, ref overlaps); + } + } + + if (rangeCount == -1) + { + _migrationTarget.WaitForAndFlushRanges(address, size); + + return; + } + else if (rangeCount == 0) + { + return; + } + + // First, determine which syncpoint to wait on. + // This is the latest syncpoint that is not equal to the current sync. + + long highestDiff = long.MinValue; + + for (int i = 0; i < rangeCount; i++) + { + BufferModifiedRange overlap = overlaps[i]; + + long diff = (long)(overlap.SyncNumber - currentSync); + + if (diff < 0 && diff > highestDiff) + { + highestDiff = diff; + } + } + + if (highestDiff == long.MinValue) + { + return; + } + + // Wait for the syncpoint. + _context.Renderer.WaitSync(currentSync + (ulong)highestDiff); + + RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress); + } + + /// + /// Inherit ranges from another modified range list. + /// + /// + /// Assumes that ranges will be inherited in address ascending order. + /// + /// The range list to inherit from + /// The action to call for each modified range + public void InheritRanges(BufferModifiedRangeList ranges, Action registerRangeAction) + { + BufferModifiedRange[] inheritRanges; + + lock (ranges._lock) + { + inheritRanges = ranges.ToArray(); + + lock (_lock) + { + // Copy over the migration from the previous range list + + BufferMigration oldMigration = ranges._source; + + BufferMigrationSpan span = new BufferMigrationSpan(ranges._parent, ranges._flushAction, oldMigration); + ranges._parent.IncrementReferenceCount(); + + if (_source == null) + { + // Create a new migration. + _source = new BufferMigration(new BufferMigrationSpan[] { span }, this, _context.SyncNumber); + + _context.RegisterBufferMigration(_source); + } + else + { + // Extend the migration + _source.AddSpanToEnd(span); + } + + ranges._migrationTarget = this; + + foreach (BufferModifiedRange range in inheritRanges) + { + Add(range); + } + } + } + + ulong currentSync = _context.SyncNumber; + foreach (BufferModifiedRange range in inheritRanges) + { + if (range.SyncNumber != currentSync) + { + registerRangeAction(range.Address, range.Size); + } + } + } + + /// + /// Register a migration from previous buffer storage. This migration is from a snapshot of the buffer's + /// current handle to its handle in the future, and is assumed to be complete when the sync action completes. + /// When the migration completes, the handle is disposed. + /// + public void SelfMigration() + { + lock (_lock) + { + BufferMigrationSpan span = new(_parent, _parent.GetSnapshotDisposeAction(), _parent.GetSnapshotFlushAction(), _source); + BufferMigration migration = new(new BufferMigrationSpan[] { span }, this, _context.SyncNumber); + + // Migration target is used to redirect flush actions to the latest range list, + // so we don't need to set it here. (this range list is still the latest) + + _context.RegisterBufferMigration(migration); + + _source = migration; + } + } + + /// + /// Removes a source buffer migration, indicating its copy has completed. + /// + /// The migration to remove + public void RemoveMigration(BufferMigration migration) + { + lock (_lock) + { + if (_source == migration) + { + _source = null; + } + } + } + + private void ClearPart(BufferModifiedRange overlap, ulong address, ulong endAddress) + { + Remove(overlap); + + // If the overlap extends outside of the clear range, make sure those parts still exist. + + if (overlap.Address < address) + { + Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent)); + } + + if (overlap.EndAddress > endAddress) + { + Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent)); + } + } + + /// + /// Clear modified ranges within the specified area. + /// + /// Start address to clear + /// Size to clear + public void Clear(ulong address, ulong size) + { + lock (_lock) + { + // This function can be called from any thread, so it cannot use the arrays for background or foreground. + BufferModifiedRange[] toClear = new BufferModifiedRange[1]; + + int rangeCount = FindOverlapsNonOverlapping(address, size, ref toClear); + + ulong endAddress = address + size; + + for (int i = 0; i < rangeCount; i++) + { + BufferModifiedRange overlap = toClear[i]; + + ClearPart(overlap, address, endAddress); + } + } + } + + /// + /// Clear all modified ranges. + /// + public void Clear() + { + lock (_lock) + { + Count = 0; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferPreFlush.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferPreFlush.cs new file mode 100644 index 00000000..d58b9ea6 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferPreFlush.cs @@ -0,0 +1,295 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Manages flushing ranges from buffers in advance for easy access, if they are flushed often. + /// Typically, from device local memory to a host mapped target for cached access. + /// + internal class BufferPreFlush : IDisposable + { + private const ulong PageSize = MemoryManager.PageSize; + + /// + /// Threshold for the number of copies without a flush required to disable preflush on a page. + /// + private const int DeactivateCopyThreshold = 200; + + /// + /// Value that indicates whether a page has been flushed or copied before. + /// + private enum PreFlushState + { + None, + HasFlushed, + HasCopied + } + + /// + /// Flush state for each page of the buffer. + /// Controls whether data should be copied to the flush buffer, what sync is expected + /// and unflushed copy counting for stopping copies that are no longer needed. + /// + private struct PreFlushPage + { + public PreFlushState State; + public ulong FirstActivatedSync; + public ulong LastCopiedSync; + public int CopyCount; + } + + /// + /// True if there are ranges that should copy to the flush buffer, false otherwise. + /// + public bool ShouldCopy { get; private set; } + + private readonly GpuContext _context; + private readonly Buffer _buffer; + private readonly PreFlushPage[] _pages; + private readonly ulong _address; + private readonly ulong _size; + private readonly ulong _misalignment; + private readonly Action _flushAction; + + private BufferHandle _flushBuffer; + + public BufferPreFlush(GpuContext context, Buffer parent, Action flushAction) + { + _context = context; + _buffer = parent; + _address = parent.Address; + _size = parent.Size; + _pages = new PreFlushPage[BitUtils.DivRoundUp(_size, PageSize)]; + _misalignment = _address & (PageSize - 1); + + _flushAction = flushAction; + } + + /// + /// Ensure that the flush buffer exists. + /// + private void EnsureFlushBuffer() + { + if (_flushBuffer == BufferHandle.Null) + { + _flushBuffer = _context.Renderer.CreateBuffer((int)_size, BufferAccess.HostMemory); + } + } + + /// + /// Gets a page range from an address and size byte range. + /// + /// Range address + /// Range size + /// A page index and count + private (int index, int count) GetPageRange(ulong address, ulong size) + { + ulong offset = address - _address; + ulong endOffset = offset + size; + + int basePage = (int)(offset / PageSize); + int endPage = (int)((endOffset - 1) / PageSize); + + return (basePage, 1 + endPage - basePage); + } + + /// + /// Gets an offset and size range in the parent buffer from a page index and count. + /// + /// Range start page + /// Range page count + /// Offset and size range + private (int offset, int size) GetOffset(int startPage, int count) + { + int offset = (int)((ulong)startPage * PageSize - _misalignment); + int endOffset = (int)((ulong)(startPage + count) * PageSize - _misalignment); + + offset = Math.Max(0, offset); + endOffset = Math.Min((int)_size, endOffset); + + return (offset, endOffset - offset); + } + + /// + /// Copy a range of pages from the parent buffer into the flush buffer. + /// + /// Range start page + /// Range page count + private void CopyPageRange(int startPage, int count) + { + (int offset, int size) = GetOffset(startPage, count); + + EnsureFlushBuffer(); + + _context.Renderer.Pipeline.CopyBuffer(_buffer.Handle, _flushBuffer, offset, offset, size); + } + + /// + /// Copy a modified range into the flush buffer if it's marked as flushed. + /// Any pages the range overlaps are copied, and copies aren't repeated in the same sync number. + /// + /// Range address + /// Range size + public void CopyModified(ulong address, ulong size) + { + (int baseIndex, int count) = GetPageRange(address, size); + ulong syncNumber = _context.SyncNumber; + + int startPage = -1; + + for (int i = 0; i < count; i++) + { + int pageIndex = baseIndex + i; + ref PreFlushPage page = ref _pages[pageIndex]; + + if (page.State > PreFlushState.None) + { + // Perform the copy, and update the state of each page. + if (startPage == -1) + { + startPage = pageIndex; + } + + if (page.State != PreFlushState.HasCopied) + { + page.FirstActivatedSync = syncNumber; + page.State = PreFlushState.HasCopied; + } + else if (page.CopyCount++ >= DeactivateCopyThreshold) + { + page.CopyCount = 0; + page.State = PreFlushState.None; + } + + if (page.LastCopiedSync != syncNumber) + { + page.LastCopiedSync = syncNumber; + } + } + else if (startPage != -1) + { + CopyPageRange(startPage, pageIndex - startPage); + + startPage = -1; + } + } + + if (startPage != -1) + { + CopyPageRange(startPage, (baseIndex + count) - startPage); + } + } + + /// + /// Flush the given page range back into guest memory, optionally using data from the flush buffer. + /// The actual flushed range is an intersection of the page range and the address range. + /// + /// Address range start + /// Address range size + /// Page range start + /// Page range count + /// True if the data should come from the flush buffer + private void FlushPageRange(ulong address, ulong size, int startPage, int count, bool preFlush) + { + (int pageOffset, int pageSize) = GetOffset(startPage, count); + + int offset = (int)(address - _address); + int end = offset + (int)size; + + offset = Math.Max(offset, pageOffset); + end = Math.Min(end, pageOffset + pageSize); + + if (end >= offset) + { + BufferHandle handle = preFlush ? _flushBuffer : _buffer.Handle; + _flushAction(handle, _address + (ulong)offset, (ulong)(end - offset)); + } + } + + /// + /// Flush the given address range back into guest memory, optionally using data from the flush buffer. + /// When a copy has been performed on or before the waited sync number, the data can come from the flush buffer. + /// Otherwise, it flushes the parent buffer directly. + /// + /// Range address + /// Range size + /// Sync number that has been waited for + public void FlushWithAction(ulong address, ulong size, ulong syncNumber) + { + // Copy the parts of the range that have pre-flush copies that have been completed. + // Run the flush action for ranges that don't have pre-flush copies. + + // If a range doesn't have a pre-flush copy, consider adding one. + + (int baseIndex, int count) = GetPageRange(address, size); + + bool rangePreFlushed = false; + int startPage = -1; + + for (int i = 0; i < count; i++) + { + int pageIndex = baseIndex + i; + ref PreFlushPage page = ref _pages[pageIndex]; + + bool flushPage = false; + page.CopyCount = 0; + + if (page.State == PreFlushState.HasCopied) + { + if (syncNumber >= page.FirstActivatedSync) + { + // After the range is first activated, its data will always be copied to the preflush buffer on each sync. + flushPage = true; + } + } + else if (page.State == PreFlushState.None) + { + page.State = PreFlushState.HasFlushed; + ShouldCopy = true; + } + + if (flushPage) + { + if (!rangePreFlushed || startPage == -1) + { + if (startPage != -1) + { + FlushPageRange(address, size, startPage, pageIndex - startPage, false); + } + + rangePreFlushed = true; + startPage = pageIndex; + } + } + else if (rangePreFlushed || startPage == -1) + { + if (startPage != -1) + { + FlushPageRange(address, size, startPage, pageIndex - startPage, true); + } + + rangePreFlushed = false; + startPage = pageIndex; + } + } + + if (startPage != -1) + { + FlushPageRange(address, size, startPage, (baseIndex + count) - startPage, rangePreFlushed); + } + } + + /// + /// Dispose the flush buffer, if present. + /// + public void Dispose() + { + if (_flushBuffer != BufferHandle.Null) + { + _context.Renderer.DeleteBuffer(_flushBuffer); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferStage.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferStage.cs new file mode 100644 index 00000000..d56abda2 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferStage.cs @@ -0,0 +1,99 @@ +using Ryujinx.Graphics.Shader; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Pipeline stages that can modify buffer data, as well as flags indicating storage usage. + /// Must match ShaderStage for the shader stages, though anything after that can be in any order. + /// + internal enum BufferStage : byte + { + Compute, + Vertex, + TessellationControl, + TessellationEvaluation, + Geometry, + Fragment, + + Indirect, + VertexBuffer, + IndexBuffer, + Copy, + TransformFeedback, + Internal, + None, + + StageMask = 0x3f, + StorageMask = 0xc0, + + StorageRead = 0x40, + StorageWrite = 0x80, + +#pragma warning disable CA1069 // Enums values should not be duplicated + StorageAtomic = 0xc0 +#pragma warning restore CA1069 // Enums values should not be duplicated + } + + /// + /// Utility methods to convert shader stages and binding flags into buffer stages. + /// + internal static class BufferStageUtils + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BufferStage FromShaderStage(ShaderStage stage) + { + return (BufferStage)stage; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BufferStage FromShaderStage(int stageIndex) + { + return (BufferStage)(stageIndex + 1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BufferStage FromUsage(BufferUsageFlags flags) + { + if (flags.HasFlag(BufferUsageFlags.Write)) + { + return BufferStage.StorageWrite; + } + else + { + return BufferStage.StorageRead; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BufferStage FromUsage(TextureUsageFlags flags) + { + if (flags.HasFlag(TextureUsageFlags.ImageStore)) + { + return BufferStage.StorageWrite; + } + else + { + return BufferStage.StorageRead; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BufferStage TextureBuffer(ShaderStage shaderStage, TextureUsageFlags flags) + { + return FromShaderStage(shaderStage) | FromUsage(flags); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BufferStage GraphicsStorage(int stageIndex, BufferUsageFlags flags) + { + return FromShaderStage(stageIndex) | FromUsage(flags); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BufferStage ComputeStorage(BufferUsageFlags flags) + { + return BufferStage.Compute | FromUsage(flags); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs new file mode 100644 index 00000000..fa79e4f9 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureArrayBinding.cs @@ -0,0 +1,66 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Memory.Range; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// A buffer binding to apply to a buffer texture array element. + /// + readonly struct BufferTextureArrayBinding + { + /// + /// Backend texture or image array. + /// + public T Array { get; } + + /// + /// The buffer texture. + /// + public ITexture Texture { get; } + + /// + /// Physical ranges of memory where the buffer texture data is located. + /// + public MultiRange Range { get; } + + /// + /// The image or sampler binding info for the buffer texture. + /// + public TextureBindingInfo BindingInfo { get; } + + /// + /// Index of the binding on the array. + /// + public int Index { get; } + + /// + /// The image format for the binding. + /// + public Format Format { get; } + + /// + /// Create a new buffer texture binding. + /// + /// Buffer texture + /// Physical ranges of memory where the buffer texture data is located + /// Binding info + /// Index of the binding on the array + /// Binding format + public BufferTextureArrayBinding( + T array, + ITexture texture, + MultiRange range, + TextureBindingInfo bindingInfo, + int index, + Format format) + { + Array = array; + Texture = texture; + Range = range; + BindingInfo = bindingInfo; + Index = index; + Format = format; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs new file mode 100644 index 00000000..bf0beffa --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs @@ -0,0 +1,68 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Shader; +using Ryujinx.Memory.Range; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// A buffer binding to apply to a buffer texture. + /// + readonly struct BufferTextureBinding + { + /// + /// Shader stage accessing the texture. + /// + public ShaderStage Stage { get; } + + /// + /// The buffer texture. + /// + public ITexture Texture { get; } + + /// + /// Physical ranges of memory where the buffer texture data is located. + /// + public MultiRange Range { get; } + + /// + /// The image or sampler binding info for the buffer texture. + /// + public TextureBindingInfo BindingInfo { get; } + + /// + /// The image format for the binding. + /// + public Format Format { get; } + + /// + /// Whether the binding is for an image or a sampler. + /// + public bool IsImage { get; } + + /// + /// Create a new buffer texture binding. + /// + /// Shader stage accessing the texture + /// Buffer texture + /// Physical ranges of memory where the buffer texture data is located + /// Binding info + /// Binding format + /// Whether the binding is for an image or a sampler + public BufferTextureBinding( + ShaderStage stage, + ITexture texture, + MultiRange range, + TextureBindingInfo bindingInfo, + Format format, + bool isImage) + { + Stage = stage; + Texture = texture; + Range = range; + BindingInfo = bindingInfo; + Format = format; + IsImage = isImage; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferUpdater.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferUpdater.cs new file mode 100644 index 00000000..02090c04 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferUpdater.cs @@ -0,0 +1,123 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using System; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Buffer data updater. + /// + class BufferUpdater : IDisposable + { + private BufferHandle _handle; + + /// + /// Handle of the buffer. + /// + public BufferHandle Handle => _handle; + + private readonly IRenderer _renderer; + private int _startOffset = -1; + private int _endOffset = -1; + + /// + /// Creates a new instance of the buffer updater. + /// + /// Renderer that the buffer will be used with + public BufferUpdater(IRenderer renderer) + { + _renderer = renderer; + } + + /// + /// Mark a region of the buffer as modified and needing to be sent to the GPU. + /// + /// Start offset of the region in bytes + /// Size of the region in bytes + protected void MarkDirty(int startOffset, int byteSize) + { + int endOffset = startOffset + byteSize; + + if (_startOffset == -1) + { + _startOffset = startOffset; + _endOffset = endOffset; + } + else + { + if (startOffset < _startOffset) + { + _startOffset = startOffset; + } + + if (endOffset > _endOffset) + { + _endOffset = endOffset; + } + } + } + + /// + /// Submits all pending buffer updates to the GPU. + /// + /// All data that should be sent to the GPU. Only the modified regions will be updated + /// Optional binding to bind the buffer if a new buffer was created + protected void Commit(ReadOnlySpan data, int binding = -1) + { + if (_startOffset != -1) + { + if (_handle == BufferHandle.Null) + { + _handle = _renderer.CreateBuffer(data.Length, BufferAccess.Stream); + _renderer.Pipeline.ClearBuffer(_handle, 0, data.Length, 0); + + if (binding >= 0) + { + var range = new BufferRange(_handle, 0, data.Length); + _renderer.Pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, range) }); + } + }; + + _renderer.SetBufferData(_handle, _startOffset, data[_startOffset.._endOffset]); + + _startOffset = -1; + _endOffset = -1; + } + } + + /// + /// Gets a reference to a given element of a vector. + /// + /// Vector to get the element reference from + /// Element index + /// Reference to the specified element + protected static ref T GetElementRef(ref Vector4 vector, int elementIndex) + { + switch (elementIndex) + { + case 0: + return ref vector.X; + case 1: + return ref vector.Y; + case 2: + return ref vector.Z; + case 3: + return ref vector.W; + default: + throw new ArgumentOutOfRangeException(nameof(elementIndex)); + } + } + + /// + /// Destroys the buffer. + /// + public void Dispose() + { + if (_handle != BufferHandle.Null) + { + _renderer.DeleteBuffer(_handle); + _handle = BufferHandle.Null; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs new file mode 100644 index 00000000..24966df4 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs @@ -0,0 +1,192 @@ +using Ryujinx.Graphics.GAL; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Represents the GPU counter cache. + /// + class CounterCache + { + private readonly struct CounterEntry + { + public ulong Address { get; } + public ICounterEvent Event { get; } + + public CounterEntry(ulong address, ICounterEvent evt) + { + Address = address; + Event = evt; + } + } + + private readonly List _items; + + /// + /// Creates a new instance of the GPU counter cache. + /// + public CounterCache() + { + _items = new List(); + } + + /// + /// Adds a new counter to the counter cache, or updates a existing one. + /// + /// GPU virtual address where the counter will be written in memory + /// The new counter + public void AddOrUpdate(ulong gpuVa, ICounterEvent evt) + { + int index = BinarySearch(gpuVa); + + CounterEntry entry = new(gpuVa, evt); + + if (index < 0) + { + _items.Insert(~index, entry); + } + else + { + _items[index] = entry; + } + } + + /// + /// Handles removal of counters written to a memory region being unmapped. + /// + /// Sender object + /// Event arguments + public void MemoryUnmappedHandler(object sender, UnmapEventArgs e) => RemoveRange(e.Address, e.Size); + + private void RemoveRange(ulong gpuVa, ulong size) + { + int index = BinarySearch(gpuVa + size - 1); + + if (index < 0) + { + index = ~index; + } + + if (index >= _items.Count || !InRange(gpuVa, size, _items[index].Address)) + { + return; + } + + int count = 1; + + while (index > 0 && InRange(gpuVa, size, _items[index - 1].Address)) + { + index--; + count++; + } + + // Notify the removed counter events that their result should no longer be written out. + for (int i = 0; i < count; i++) + { + ICounterEvent evt = _items[index + i].Event; + if (evt != null) + { + evt.Invalid = true; + } + } + + _items.RemoveRange(index, count); + } + + /// + /// Checks whenever an address falls inside a given range. + /// + /// Range start address + /// Range size + /// Address being checked + /// True if the address falls inside the range, false otherwise + private static bool InRange(ulong startVa, ulong size, ulong gpuVa) + { + return gpuVa >= startVa && gpuVa < startVa + size; + } + + /// + /// Check if any counter value was written to the specified GPU virtual memory address. + /// + /// GPU virtual address + /// True if any counter value was written on the specified address, false otherwise + public bool Contains(ulong gpuVa) + { + return BinarySearch(gpuVa) >= 0; + } + + /// + /// Flush any counter value written to the specified GPU virtual memory address. + /// + /// GPU virtual address + /// True if any counter value was written on the specified address, false otherwise + public bool FindAndFlush(ulong gpuVa) + { + int index = BinarySearch(gpuVa); + if (index > 0) + { + _items[index].Event?.Flush(); + + return true; + } + else + { + return false; + } + } + + /// + /// Find any counter event that would write to the specified GPU virtual memory address. + /// + /// GPU virtual address + /// The counter event, or null if not present + public ICounterEvent FindEvent(ulong gpuVa) + { + int index = BinarySearch(gpuVa); + if (index > 0) + { + return _items[index].Event; + } + else + { + return null; + } + } + + /// + /// Performs binary search of an address on the list. + /// + /// Address to search + /// Index of the item, or complement of the index of the nearest item with lower value + private int BinarySearch(ulong address) + { + int left = 0; + int right = _items.Count - 1; + + while (left <= right) + { + int range = right - left; + + int middle = left + (range >> 1); + + CounterEntry item = _items[middle]; + + if (item.Address == address) + { + return middle; + } + + if (address < item.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return ~left; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs b/src/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs new file mode 100644 index 00000000..bdb7cf2c --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs @@ -0,0 +1,101 @@ +using Ryujinx.Memory.Tracking; +using System; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// A tracking handle for a region of GPU VA, represented by one or more tracking handles in CPU VA. + /// + class GpuRegionHandle : IRegionHandle + { + private readonly RegionHandle[] _cpuRegionHandles; + + public bool Dirty + { + get + { + foreach (var regionHandle in _cpuRegionHandles) + { + if (regionHandle.Dirty) + { + return true; + } + } + + return false; + } + } + + public ulong Address => throw new NotSupportedException(); + public ulong Size => throw new NotSupportedException(); + public ulong EndAddress => throw new NotSupportedException(); + + /// + /// Create a new GpuRegionHandle, made up of mulitple CpuRegionHandles. + /// + /// The CpuRegionHandles that make up this handle + public GpuRegionHandle(RegionHandle[] cpuRegionHandles) + { + _cpuRegionHandles = cpuRegionHandles; + } + + /// + /// Dispose the child handles. + /// + public void Dispose() + { + foreach (var regionHandle in _cpuRegionHandles) + { + regionHandle.Dispose(); + } + } + + /// + /// Register an action to perform when the tracked region is read or written. + /// The action is automatically removed after it runs. + /// + /// Action to call on read or write + public void RegisterAction(RegionSignal action) + { + foreach (var regionHandle in _cpuRegionHandles) + { + regionHandle.RegisterAction(action); + } + } + + /// + /// Register an action to perform when a precise access occurs (one with exact address and size). + /// If the action returns true, read/write tracking are skipped. + /// + /// Action to call on read or write + public void RegisterPreciseAction(PreciseRegionSignal action) + { + foreach (var regionHandle in _cpuRegionHandles) + { + regionHandle.RegisterPreciseAction(action); + } + } + + /// + /// Consume the dirty flag for the handles, and reprotect so it can be set on the next write. + /// + public void Reprotect(bool asDirty = false) + { + foreach (var regionHandle in _cpuRegionHandles) + { + regionHandle.Reprotect(asDirty); + } + } + + /// + /// Force the handles to be dirty, without reprotecting. + /// + public void ForceDirty() + { + foreach (var regionHandle in _cpuRegionHandles) + { + regionHandle.ForceDirty(); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs new file mode 100644 index 00000000..04114a95 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Memory.Range; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// GPU Index Buffer information. + /// + struct IndexBuffer + { + public MultiRange Range; + public IndexType Type; + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs new file mode 100644 index 00000000..59a940a4 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs @@ -0,0 +1,732 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// GPU memory manager. + /// + public class MemoryManager : IWritableBlock + { + private const int PtLvl0Bits = 14; + private const int PtLvl1Bits = 14; + public const int PtPageBits = 12; + + private const ulong PtLvl0Size = 1UL << PtLvl0Bits; + private const ulong PtLvl1Size = 1UL << PtLvl1Bits; + public const ulong PageSize = 1UL << PtPageBits; + + private const ulong PtLvl0Mask = PtLvl0Size - 1; + private const ulong PtLvl1Mask = PtLvl1Size - 1; + public const ulong PageMask = PageSize - 1; + + private const int PtLvl0Bit = PtPageBits + PtLvl1Bits; + private const int PtLvl1Bit = PtPageBits; + private const int AddressSpaceBits = PtPageBits + PtLvl1Bits + PtLvl0Bits; + + public const ulong PteUnmapped = ulong.MaxValue; + + private readonly ulong[][] _pageTable; + + public event EventHandler MemoryUnmapped; + + /// + /// Physical memory where the virtual memory is mapped into. + /// + internal PhysicalMemory Physical { get; } + + /// + /// Virtual range cache. + /// + internal VirtualRangeCache VirtualRangeCache { get; } + + /// + /// Cache of GPU counters. + /// + internal CounterCache CounterCache { get; } + + /// + /// Creates a new instance of the GPU memory manager. + /// + /// Physical memory that this memory manager will map into + internal MemoryManager(PhysicalMemory physicalMemory) + { + Physical = physicalMemory; + VirtualRangeCache = new VirtualRangeCache(this); + CounterCache = new CounterCache(); + _pageTable = new ulong[PtLvl0Size][]; + MemoryUnmapped += Physical.TextureCache.MemoryUnmappedHandler; + MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler; + MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler; + MemoryUnmapped += CounterCache.MemoryUnmappedHandler; + } + + /// + /// Reads data from GPU mapped memory. + /// + /// Type of the data + /// GPU virtual address where the data is located + /// True if read tracking is triggered on the memory region + /// The data at the specified memory location + public T Read(ulong va, bool tracked = false) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (IsContiguous(va, size)) + { + ulong address = Translate(va); + + if (tracked) + { + return Physical.ReadTracked(address); + } + else + { + return Physical.Read(address); + } + } + else + { + Span data = new byte[size]; + + ReadImpl(va, data, tracked); + + return MemoryMarshal.Cast(data)[0]; + } + } + + /// + /// Gets a read-only span of data from GPU mapped memory. + /// + /// GPU virtual address where the data is located + /// Size of the data + /// True if read tracking is triggered on the span + /// The span of the data at the specified memory location + public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + { + if (IsContiguous(va, size)) + { + return Physical.GetSpan(Translate(va), size, tracked); + } + else + { + Span data = new byte[size]; + + ReadImpl(va, data, tracked); + + return data; + } + } + + /// + /// Gets a read-only span of data from GPU mapped memory, up to the entire range specified, + /// or the last mapped page if the range is not fully mapped. + /// + /// GPU virtual address where the data is located + /// Size of the data + /// True if read tracking is triggered on the span + /// The span of the data at the specified memory location + public ReadOnlySpan GetSpanMapped(ulong va, int size, bool tracked = false) + { + bool isContiguous = true; + int mappedSize; + + if (ValidateAddress(va) && GetPte(va) != PteUnmapped && Physical.IsMapped(Translate(va))) + { + ulong endVa = va + (ulong)size; + ulong endVaAligned = (endVa + PageMask) & ~PageMask; + ulong currentVa = va & ~PageMask; + + int pages = (int)((endVaAligned - currentVa) / PageSize); + + for (int page = 0; page < pages - 1; page++) + { + ulong nextVa = currentVa + PageSize; + ulong nextPa = Translate(nextVa); + + if (!ValidateAddress(nextVa) || GetPte(nextVa) == PteUnmapped || !Physical.IsMapped(nextPa)) + { + break; + } + + if (Translate(currentVa) + PageSize != nextPa) + { + isContiguous = false; + } + + currentVa += PageSize; + } + + currentVa += PageSize; + + if (currentVa > endVa) + { + currentVa = endVa; + } + + mappedSize = (int)(currentVa - va); + } + else + { + return ReadOnlySpan.Empty; + } + + if (isContiguous) + { + return Physical.GetSpan(Translate(va), mappedSize, tracked); + } + else + { + Span data = new byte[mappedSize]; + + ReadImpl(va, data, tracked); + + return data; + } + } + + /// + /// Reads data from a possibly non-contiguous region of GPU mapped memory. + /// + /// GPU virtual address of the data + /// Span to write the read data into + /// True to enable write tracking on read, false otherwise + private void ReadImpl(ulong va, Span data, bool tracked) + { + if (data.Length == 0) + { + return; + } + + int offset = 0, size; + + if ((va & PageMask) != 0) + { + ulong pa = Translate(va); + + size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask)); + + Physical.GetSpan(pa, size, tracked).CopyTo(data[..size]); + + offset += size; + } + + for (; offset < data.Length; offset += size) + { + ulong pa = Translate(va + (ulong)offset); + + size = Math.Min(data.Length - offset, (int)PageSize); + + Physical.GetSpan(pa, size, tracked).CopyTo(data.Slice(offset, size)); + } + } + + /// + /// Gets a writable region from GPU mapped memory. + /// + /// Start address of the range + /// Size in bytes to be range + /// True if write tracking is triggered on the span + /// A writable region with the data at the specified memory location + public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + { + if (IsContiguous(va, size)) + { + return Physical.GetWritableRegion(Translate(va), size, tracked); + } + else + { + MemoryOwner memoryOwner = MemoryOwner.Rent(size); + + ReadImpl(va, memoryOwner.Span, tracked); + + return new WritableRegion(this, va, memoryOwner, tracked); + } + } + + /// + /// Writes data to GPU mapped memory. + /// + /// Type of the data + /// GPU virtual address to write the value into + /// The value to be written + public void Write(ulong va, T value) where T : unmanaged + { + Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); + } + + /// + /// Writes data to GPU mapped memory. + /// + /// GPU virtual address to write the data into + /// The data to be written + public void Write(ulong va, ReadOnlySpan data) + { + WriteImpl(va, data, Physical.Write); + } + + /// + /// Writes data to GPU mapped memory, destined for a tracked resource. + /// + /// GPU virtual address to write the data into + /// The data to be written + public void WriteTrackedResource(ulong va, ReadOnlySpan data) + { + WriteImpl(va, data, Physical.WriteTrackedResource); + } + + /// + /// Writes data to GPU mapped memory without write tracking. + /// + /// GPU virtual address to write the data into + /// The data to be written + public void WriteUntracked(ulong va, ReadOnlySpan data) + { + WriteImpl(va, data, Physical.WriteUntracked); + } + + private delegate void WriteCallback(ulong address, ReadOnlySpan data); + + /// + /// Writes data to possibly non-contiguous GPU mapped memory. + /// + /// GPU virtual address of the region to write into + /// Data to be written + /// Write callback + private void WriteImpl(ulong va, ReadOnlySpan data, WriteCallback writeCallback) + { + if (IsContiguous(va, data.Length)) + { + writeCallback(Translate(va), data); + } + else + { + int offset = 0, size; + + if ((va & PageMask) != 0) + { + ulong pa = Translate(va); + + size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask)); + + writeCallback(pa, data[..size]); + + offset += size; + } + + for (; offset < data.Length; offset += size) + { + ulong pa = Translate(va + (ulong)offset); + + size = Math.Min(data.Length - offset, (int)PageSize); + + writeCallback(pa, data.Slice(offset, size)); + } + } + } + + /// + /// Runs remap actions that are added to an unmap event. + /// These must run after the mapping completes. + /// + /// Event with remap actions + private static void RunRemapActions(UnmapEventArgs e) + { + if (e.RemapActions != null) + { + foreach (Action action in e.RemapActions) + { + action(); + } + } + } + + /// + /// Maps a given range of pages to the specified CPU virtual address. + /// + /// + /// All addresses and sizes must be page aligned. + /// + /// CPU virtual address to map into + /// GPU virtual address to be mapped + /// Size in bytes of the mapping + /// Kind of the resource located at the mapping + public void Map(ulong pa, ulong va, ulong size, PteKind kind) + { + lock (_pageTable) + { + UnmapEventArgs e = new(va, size); + MemoryUnmapped?.Invoke(this, e); + + for (ulong offset = 0; offset < size; offset += PageSize) + { + SetPte(va + offset, PackPte(pa + offset, kind)); + } + + RunRemapActions(e); + } + } + + /// + /// Unmaps a given range of pages at the specified GPU virtual memory region. + /// + /// GPU virtual address to unmap + /// Size in bytes of the region being unmapped + public void Unmap(ulong va, ulong size) + { + lock (_pageTable) + { + // Event handlers are not expected to be thread safe. + UnmapEventArgs e = new(va, size); + MemoryUnmapped?.Invoke(this, e); + + for (ulong offset = 0; offset < size; offset += PageSize) + { + SetPte(va + offset, PteUnmapped); + } + + RunRemapActions(e); + } + } + + /// + /// Checks if a region of GPU mapped memory is contiguous. + /// + /// GPU virtual address of the region + /// Size of the region + /// True if the region is contiguous, false otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsContiguous(ulong va, int size) + { + if (!ValidateAddress(va) || GetPte(va) == PteUnmapped) + { + return false; + } + + ulong endVa = (va + (ulong)size + PageMask) & ~PageMask; + + va &= ~PageMask; + + int pages = (int)((endVa - va) / PageSize); + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize) || GetPte(va + PageSize) == PteUnmapped) + { + return false; + } + + if (Translate(va) + PageSize != Translate(va + PageSize)) + { + return false; + } + + va += PageSize; + } + + return true; + } + + /// + /// Gets the physical regions that make up the given virtual address region. + /// + /// Virtual address of the range + /// Size of the range + /// Multi-range with the physical regions + public MultiRange GetPhysicalRegions(ulong va, ulong size) + { + if (IsContiguous(va, (int)size)) + { + return new MultiRange(Translate(va), size); + } + + ulong regionStart = Translate(va); + ulong regionSize = Math.Min(size, PageSize - (va & PageMask)); + + ulong endVa = va + size; + ulong endVaRounded = (endVa + PageMask) & ~PageMask; + + va &= ~PageMask; + + int pages = (int)((endVaRounded - va) / PageSize); + + var regions = new List(); + + for (int page = 0; page < pages - 1; page++) + { + ulong currPa = Translate(va); + ulong newPa = Translate(va + PageSize); + + if ((currPa != PteUnmapped || newPa != PteUnmapped) && currPa + PageSize != newPa) + { + regions.Add(new MemoryRange(regionStart, regionSize)); + regionStart = newPa; + regionSize = 0; + } + + va += PageSize; + regionSize += Math.Min(endVa - va, PageSize); + } + + if (regions.Count == 0) + { + return new MultiRange(regionStart, regionSize); + } + + regions.Add(new MemoryRange(regionStart, regionSize)); + + return new MultiRange(regions.ToArray()); + } + + /// + /// Checks if a given GPU virtual memory range is mapped to the same physical regions + /// as the specified physical memory multi-range. + /// + /// Physical memory multi-range + /// GPU virtual memory address + /// True if the virtual memory region is mapped into the specified physical one, false otherwise + public bool CompareRange(MultiRange range, ulong va) + { + va &= ~PageMask; + + for (int i = 0; i < range.Count; i++) + { + MemoryRange currentRange = range.GetSubRange(i); + + if (currentRange.Address != PteUnmapped) + { + ulong address = currentRange.Address & ~PageMask; + ulong endAddress = (currentRange.EndAddress + PageMask) & ~PageMask; + + while (address < endAddress) + { + if (Translate(va) != address) + { + return false; + } + + va += PageSize; + address += PageSize; + } + } + else + { + ulong endVa = va + (((currentRange.Size) + PageMask) & ~PageMask); + + while (va < endVa) + { + if (Translate(va) != PteUnmapped) + { + return false; + } + + va += PageSize; + } + } + } + + return true; + } + + /// + /// Validates a GPU virtual address. + /// + /// Address to validate + /// True if the address is valid, false otherwise + private static bool ValidateAddress(ulong va) + { + return va < (1UL << AddressSpaceBits); + } + + /// + /// Checks if a given page is mapped. + /// + /// GPU virtual address of the page to check + /// True if the page is mapped, false otherwise + public bool IsMapped(ulong va) + { + return Translate(va) != PteUnmapped; + } + + /// + /// Translates a GPU virtual address to a CPU virtual address. + /// + /// GPU virtual address to be translated + /// CPU virtual address, or if unmapped + public ulong Translate(ulong va) + { + if (!ValidateAddress(va)) + { + return PteUnmapped; + } + + ulong pte = GetPte(va); + + if (pte == PteUnmapped) + { + return PteUnmapped; + } + + return UnpackPaFromPte(pte) + (va & PageMask); + } + + /// + /// Translates a GPU virtual address to a CPU virtual address on the first mapped page of memory + /// on the specified region. + /// If no page is mapped on the specified region, is returned. + /// + /// GPU virtual address to be translated + /// Size of the range to be translated + /// CPU virtual address, or if unmapped + public ulong TranslateFirstMapped(ulong va, ulong size) + { + if (!ValidateAddress(va)) + { + return PteUnmapped; + } + + ulong endVa = va + size; + + ulong pte = GetPte(va); + + for (; va < endVa && pte == PteUnmapped; va += PageSize - (va & PageMask)) + { + pte = GetPte(va); + } + + if (pte == PteUnmapped) + { + return PteUnmapped; + } + + return UnpackPaFromPte(pte) + (va & PageMask); + } + + /// + /// Translates a GPU virtual address and returns the number of bytes that are mapped after it. + /// + /// GPU virtual address to be translated + /// Maximum size in bytes to scan + /// Number of bytes, 0 if unmapped + public ulong GetMappedSize(ulong va, ulong maxSize) + { + if (!ValidateAddress(va)) + { + return 0; + } + + ulong startVa = va; + ulong endVa = va + maxSize; + + ulong pte = GetPte(va); + + while (pte != PteUnmapped && va < endVa) + { + va += PageSize - (va & PageMask); + pte = GetPte(va); + } + + return Math.Min(maxSize, va - startVa); + } + + /// + /// Gets the kind of a given memory page. + /// This might indicate the type of resource that can be allocated on the page, and also texture tiling. + /// + /// GPU virtual address + /// Kind of the memory page + public PteKind GetKind(ulong va) + { + if (!ValidateAddress(va)) + { + return PteKind.Invalid; + } + + ulong pte = GetPte(va); + + if (pte == PteUnmapped) + { + return PteKind.Invalid; + } + + return UnpackKindFromPte(pte); + } + + /// + /// Gets the Page Table entry for a given GPU virtual address. + /// + /// GPU virtual address + /// Page table entry (CPU virtual address) + private ulong GetPte(ulong va) + { + ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask; + ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask; + + if (_pageTable[l0] == null) + { + return PteUnmapped; + } + + return _pageTable[l0][l1]; + } + + /// + /// Sets a Page Table entry at a given GPU virtual address. + /// + /// GPU virtual address + /// Page table entry (CPU virtual address) + private void SetPte(ulong va, ulong pte) + { + ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask; + ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask; + + if (_pageTable[l0] == null) + { + _pageTable[l0] = new ulong[PtLvl1Size]; + + for (ulong index = 0; index < PtLvl1Size; index++) + { + _pageTable[l0][index] = PteUnmapped; + } + } + + _pageTable[l0][l1] = pte; + } + + /// + /// Creates a page table entry from a physical address and kind. + /// + /// Physical address + /// Kind + /// Page table entry + private static ulong PackPte(ulong pa, PteKind kind) + { + return pa | ((ulong)kind << 56); + } + + /// + /// Unpacks kind from a page table entry. + /// + /// Page table entry + /// Kind + private static PteKind UnpackKindFromPte(ulong pte) + { + return (PteKind)(pte >> 56); + } + + /// + /// Unpacks physical address from a page table entry. + /// + /// Page table entry + /// Physical address + private static ulong UnpackPaFromPte(ulong pte) + { + return pte & 0xffffffffffffffUL; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/MultiRangeBuffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/MultiRangeBuffer.cs new file mode 100644 index 00000000..d92b0836 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/MultiRangeBuffer.cs @@ -0,0 +1,245 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Memory.Range; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Buffer, used to store vertex and index data, uniform and storage buffers, and others. + /// + class MultiRangeBuffer : IMultiRangeItem, IDisposable + { + private readonly GpuContext _context; + + /// + /// Host buffer handle. + /// + public BufferHandle Handle { get; } + + /// + /// Range of memory where the data is located. + /// + public MultiRange Range { get; } + + /// + /// Ever increasing counter value indicating when the buffer was modified relative to other buffers. + /// + public int ModificationSequenceNumber { get; private set; } + + /// + /// Physical buffer dependency entry. + /// + private readonly struct PhysicalDependency + { + /// + /// Physical buffer. + /// + public readonly Buffer PhysicalBuffer; + + /// + /// Offset of the range on the physical buffer. + /// + public readonly ulong PhysicalOffset; + + /// + /// Offset of the range on the virtual buffer. + /// + public readonly ulong VirtualOffset; + + /// + /// Size of the range. + /// + public readonly ulong Size; + + /// + /// Creates a new physical dependency. + /// + /// Physical buffer + /// Offset of the range on the physical buffer + /// Offset of the range on the virtual buffer + /// Size of the range + public PhysicalDependency(Buffer physicalBuffer, ulong physicalOffset, ulong virtualOffset, ulong size) + { + PhysicalBuffer = physicalBuffer; + PhysicalOffset = physicalOffset; + VirtualOffset = virtualOffset; + Size = size; + } + } + + private List _dependencies; + private BufferModifiedRangeList _modifiedRanges = null; + + /// + /// Creates a new instance of the buffer. + /// + /// GPU context that the buffer belongs to + /// Range of memory where the data is mapped + public MultiRangeBuffer(GpuContext context, MultiRange range) + { + _context = context; + Range = range; + Handle = context.Renderer.CreateBuffer((int)range.GetSize()); + } + + /// + /// Creates a new instance of the buffer. + /// + /// GPU context that the buffer belongs to + /// Range of memory where the data is mapped + /// Backing memory for the buffer + public MultiRangeBuffer(GpuContext context, MultiRange range, ReadOnlySpan storages) + { + _context = context; + Range = range; + Handle = context.Renderer.CreateBufferSparse(storages); + } + + /// + /// Gets a sub-range from the buffer. + /// + /// + /// This can be used to bind and use sub-ranges of the buffer on the host API. + /// + /// Range of memory where the data is mapped + /// The buffer sub-range + public BufferRange GetRange(MultiRange range) + { + int offset = Range.FindOffset(range); + + return new BufferRange(Handle, offset, (int)range.GetSize()); + } + + /// + /// Removes all physical buffer dependencies. + /// + public void ClearPhysicalDependencies() + { + _dependencies?.Clear(); + } + + /// + /// Adds a physical buffer dependency. + /// + /// Physical buffer to be added + /// Address inside the physical buffer where the virtual buffer range is located + /// Offset inside the virtual buffer where the physical range is located + /// Size of the range in bytes + public void AddPhysicalDependency(Buffer buffer, ulong rangeAddress, ulong dstOffset, ulong rangeSize) + { + (_dependencies ??= new()).Add(new(buffer, rangeAddress - buffer.Address, dstOffset, rangeSize)); + buffer.AddVirtualDependency(this); + } + + /// + /// Tries to get the physical range corresponding to the given physical buffer. + /// + /// Physical buffer + /// Minimum virtual offset that a range match can have + /// Physical offset of the match + /// Virtual offset of the match, always greater than or equal + /// Size of the range match + /// True if a match was found for the given parameters, false otherwise + public bool TryGetPhysicalOffset(Buffer buffer, ulong minimumVirtOffset, out ulong physicalOffset, out ulong virtualOffset, out ulong size) + { + physicalOffset = 0; + virtualOffset = 0; + size = 0; + + if (_dependencies != null) + { + foreach (var dependency in _dependencies) + { + if (dependency.PhysicalBuffer == buffer && dependency.VirtualOffset >= minimumVirtOffset) + { + physicalOffset = dependency.PhysicalOffset; + virtualOffset = dependency.VirtualOffset; + size = dependency.Size; + + return true; + } + } + } + + return false; + } + + /// + /// Adds a modified virtual memory range. + /// + /// + /// This is only required when the host does not support sparse buffers, otherwise only physical buffers need to track modification. + /// + /// Modified range + /// ModificationSequenceNumber + public void AddModifiedRegion(MultiRange range, int modifiedSequenceNumber) + { + _modifiedRanges ??= new(_context, null, null); + + for (int i = 0; i < range.Count; i++) + { + MemoryRange subRange = range.GetSubRange(i); + + _modifiedRanges.SignalModified(subRange.Address, subRange.Size); + } + + ModificationSequenceNumber = modifiedSequenceNumber; + } + + /// + /// Calls the specified for all modified ranges that overlaps with . + /// + /// Buffer to have its range checked + /// Action to perform for modified ranges + public void ConsumeModifiedRegion(Buffer buffer, Action rangeAction) + { + ConsumeModifiedRegion(buffer.Address, buffer.Size, rangeAction); + } + + /// + /// Calls the specified for all modified ranges that overlaps with and . + /// + /// Address of the region to consume + /// Size of the region to consume + /// Action to perform for modified ranges + public void ConsumeModifiedRegion(ulong address, ulong size, Action rangeAction) + { + if (_modifiedRanges != null) + { + _modifiedRanges.GetRanges(address, size, rangeAction); + _modifiedRanges.Clear(address, size); + } + } + + /// + /// Gets data from the specified region of the buffer, and places it on . + /// + /// Span to put the data into + /// Offset of the buffer to get the data from + /// Size of the data in bytes + public void GetData(Span output, int offset, int size) + { + using PinnedSpan data = _context.Renderer.GetBufferData(Handle, offset, size); + data.Get().CopyTo(output); + } + + /// + /// Disposes the host buffer. + /// + public void Dispose() + { + if (_dependencies != null) + { + foreach (var dependency in _dependencies) + { + dependency.PhysicalBuffer.RemoveVirtualDependency(this); + } + + _dependencies = null; + } + + _context.Renderer.DeleteBuffer(Handle); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/MultiRangeWritableBlock.cs b/src/Ryujinx.Graphics.Gpu/Memory/MultiRangeWritableBlock.cs new file mode 100644 index 00000000..9236886d --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/MultiRangeWritableBlock.cs @@ -0,0 +1,58 @@ +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using System; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// A writable block that targets a given MultiRange within a PhysicalMemory instance. + /// + internal class MultiRangeWritableBlock : IWritableBlock + { + private readonly MultiRange _range; + private readonly PhysicalMemory _physicalMemory; + + /// + /// Creates a new MultiRangeWritableBlock. + /// + /// The MultiRange to write to + /// The PhysicalMemory the given MultiRange addresses + public MultiRangeWritableBlock(MultiRange range, PhysicalMemory physicalMemory) + { + _range = range; + _physicalMemory = physicalMemory; + } + + /// + /// Write data to the MultiRange. + /// + /// Offset address + /// Data to write + /// Throw when a non-zero offset is given + public void Write(ulong va, ReadOnlySpan data) + { + if (va != 0) + { + throw new ArgumentException($"{nameof(va)} cannot be non-zero for {nameof(MultiRangeWritableBlock)}."); + } + + _physicalMemory.Write(_range, data); + } + + /// + /// Write data to the MultiRange, without tracking. + /// + /// Offset address + /// Data to write + /// Throw when a non-zero offset is given + public void WriteUntracked(ulong va, ReadOnlySpan data) + { + if (va != 0) + { + throw new ArgumentException($"{nameof(va)} cannot be non-zero for {nameof(MultiRangeWritableBlock)}."); + } + + _physicalMemory.WriteUntracked(_range, data); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs new file mode 100644 index 00000000..b22cc01b --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs @@ -0,0 +1,470 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Cpu; +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Shader; +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using Ryujinx.Memory.Tracking; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Represents physical memory, accessible from the GPU. + /// This is actually working CPU virtual addresses, of memory mapped on the application process. + /// + class PhysicalMemory : IDisposable + { + private readonly GpuContext _context; + private readonly IVirtualMemoryManagerTracked _cpuMemory; + private int _referenceCount; + + /// + /// In-memory shader cache. + /// + public ShaderCache ShaderCache { get; } + + /// + /// GPU buffer manager. + /// + public BufferCache BufferCache { get; } + + /// + /// GPU texture manager. + /// + public TextureCache TextureCache { get; } + + /// + /// Creates a new instance of the physical memory. + /// + /// GPU context that the physical memory belongs to + /// CPU memory manager of the application process + public PhysicalMemory(GpuContext context, IVirtualMemoryManagerTracked cpuMemory) + { + _context = context; + _cpuMemory = cpuMemory; + ShaderCache = new ShaderCache(context); + BufferCache = new BufferCache(context, this); + TextureCache = new TextureCache(context, this); + + if (cpuMemory is IRefCounted rc) + { + rc.IncrementReferenceCount(); + } + + _referenceCount = 1; + } + + /// + /// Increments the memory reference count. + /// + public void IncrementReferenceCount() + { + Interlocked.Increment(ref _referenceCount); + } + + /// + /// Decrements the memory reference count. + /// + public void DecrementReferenceCount() + { + if (Interlocked.Decrement(ref _referenceCount) == 0 && _cpuMemory is IRefCounted rc) + { + rc.DecrementReferenceCount(); + } + } + + /// + /// Creates a new device memory manager. + /// + /// The memory manager + public DeviceMemoryManager CreateDeviceMemoryManager() + { + return new DeviceMemoryManager(_cpuMemory); + } + + /// + /// Gets a host pointer for a given range of application memory. + /// If the memory region is not a single contiguous block, this method returns 0. + /// + /// + /// Getting a host pointer is unsafe. It should be considered invalid immediately if the GPU memory is unmapped. + /// + /// Ranges of physical memory where the target data is located + /// Pointer to the range of memory + public nint GetHostPointer(MultiRange range) + { + if (range.Count == 1) + { + var singleRange = range.GetSubRange(0); + if (singleRange.Address != MemoryManager.PteUnmapped) + { + var regions = _cpuMemory.GetHostRegions(singleRange.Address, singleRange.Size); + + if (regions != null && regions.Count() == 1) + { + return (nint)regions.First().Address; + } + } + } + + return 0; + } + + /// + /// Gets a span of data from the application process. + /// + /// Start address of the range + /// Size in bytes to be range + /// True if read tracking is triggered on the span + /// A read only span of the data at the specified memory location + public ReadOnlySpan GetSpan(ulong address, int size, bool tracked = false) + { + return _cpuMemory.GetSpan(address, size, tracked); + } + + /// + /// Gets a span of data from the application process. + /// + /// Ranges of physical memory where the data is located + /// True if read tracking is triggered on the span + /// A read only span of the data at the specified memory location + public ReadOnlySpan GetSpan(MultiRange range, bool tracked = false) + { + if (range.Count == 1) + { + var singleRange = range.GetSubRange(0); + if (singleRange.Address != MemoryManager.PteUnmapped) + { + return _cpuMemory.GetSpan(singleRange.Address, (int)singleRange.Size, tracked); + } + } + + Span data = new byte[range.GetSize()]; + + int offset = 0; + + for (int i = 0; i < range.Count; i++) + { + var currentRange = range.GetSubRange(i); + int size = (int)currentRange.Size; + if (currentRange.Address != MemoryManager.PteUnmapped) + { + _cpuMemory.GetSpan(currentRange.Address, size, tracked).CopyTo(data.Slice(offset, size)); + } + offset += size; + } + + return data; + } + + /// + /// Gets a writable region from the application process. + /// + /// Start address of the range + /// Size in bytes to be range + /// True if write tracking is triggered on the span + /// A writable region with the data at the specified memory location + public WritableRegion GetWritableRegion(ulong address, int size, bool tracked = false) + { + return _cpuMemory.GetWritableRegion(address, size, tracked); + } + + /// + /// Gets a writable region from GPU mapped memory. + /// + /// Range + /// True if write tracking is triggered on the span + /// A writable region with the data at the specified memory location + public WritableRegion GetWritableRegion(MultiRange range, bool tracked = false) + { + if (range.Count == 1) + { + MemoryRange subrange = range.GetSubRange(0); + + return GetWritableRegion(subrange.Address, (int)subrange.Size, tracked); + } + else + { + MemoryOwner memoryOwner = MemoryOwner.Rent(checked((int)range.GetSize())); + + Span memorySpan = memoryOwner.Span; + + int offset = 0; + for (int i = 0; i < range.Count; i++) + { + var currentRange = range.GetSubRange(i); + int size = (int)currentRange.Size; + if (currentRange.Address != MemoryManager.PteUnmapped) + { + GetSpan(currentRange.Address, size).CopyTo(memorySpan.Slice(offset, size)); + } + offset += size; + } + + return new WritableRegion(new MultiRangeWritableBlock(range, this), 0, memoryOwner, tracked); + } + } + + /// + /// Reads data from the application process. + /// + /// Type of the structure + /// Address to read from + /// The data at the specified memory location + public T Read(ulong address) where T : unmanaged + { + return _cpuMemory.Read(address); + } + + /// + /// Reads data from the application process, with write tracking. + /// + /// Type of the structure + /// Address to read from + /// The data at the specified memory location + public T ReadTracked(ulong address) where T : unmanaged + { + return _cpuMemory.ReadTracked(address); + } + + /// + /// Writes data to the application process, triggering a precise memory tracking event. + /// + /// Address to write into + /// Data to be written + public void WriteTrackedResource(ulong address, ReadOnlySpan data) + { + _cpuMemory.SignalMemoryTracking(address, (ulong)data.Length, true, precise: true); + _cpuMemory.WriteUntracked(address, data); + } + + /// + /// Writes data to the application process, triggering a precise memory tracking event. + /// + /// Address to write into + /// Data to be written + /// Kind of the resource being written, which will not be signalled as CPU modified + public void WriteTrackedResource(ulong address, ReadOnlySpan data, ResourceKind kind) + { + _cpuMemory.SignalMemoryTracking(address, (ulong)data.Length, true, precise: true, exemptId: (int)kind); + _cpuMemory.WriteUntracked(address, data); + } + + /// + /// Writes data to the application process. + /// + /// Address to write into + /// Data to be written + public void Write(ulong address, ReadOnlySpan data) + { + _cpuMemory.Write(address, data); + } + + /// + /// Writes data to the application process. + /// + /// Ranges of physical memory where the data is located + /// Data to be written + public void Write(MultiRange range, ReadOnlySpan data) + { + WriteImpl(range, data, _cpuMemory.Write); + } + + /// + /// Writes data to the application process, without any tracking. + /// + /// Address to write into + /// Data to be written + public void WriteUntracked(ulong address, ReadOnlySpan data) + { + _cpuMemory.WriteUntracked(address, data); + } + + /// + /// Writes data to the application process, without any tracking. + /// + /// Ranges of physical memory where the data is located + /// Data to be written + public void WriteUntracked(MultiRange range, ReadOnlySpan data) + { + WriteImpl(range, data, _cpuMemory.WriteUntracked); + } + + /// + /// Writes data to the application process, returning false if the data was not changed. + /// This triggers read memory tracking, as a redundancy check would be useless if the data is not up to date. + /// + /// The memory manager can return that memory has changed when it hasn't to avoid expensive data copies. + /// Address to write into + /// Data to be written + /// True if the data was changed, false otherwise + public bool WriteWithRedundancyCheck(ulong address, ReadOnlySpan data) + { + return _cpuMemory.WriteWithRedundancyCheck(address, data); + } + + private delegate void WriteCallback(ulong address, ReadOnlySpan data); + + /// + /// Writes data to the application process, using the supplied callback method. + /// + /// Ranges of physical memory where the data is located + /// Data to be written + /// Callback method that will perform the write + private static void WriteImpl(MultiRange range, ReadOnlySpan data, WriteCallback writeCallback) + { + if (range.Count == 1) + { + var singleRange = range.GetSubRange(0); + if (singleRange.Address != MemoryManager.PteUnmapped) + { + writeCallback(singleRange.Address, data); + } + } + else + { + int offset = 0; + + for (int i = 0; i < range.Count; i++) + { + var currentRange = range.GetSubRange(i); + int size = (int)currentRange.Size; + if (currentRange.Address != MemoryManager.PteUnmapped) + { + writeCallback(currentRange.Address, data.Slice(offset, size)); + } + offset += size; + } + } + } + + /// + /// Fills the specified memory region with a 32-bit integer value. + /// + /// CPU virtual address of the region + /// Size of the region + /// Value to fill the region with + /// Kind of the resource being filled, which will not be signalled as CPU modified + public void FillTrackedResource(ulong address, ulong size, uint value, ResourceKind kind) + { + _cpuMemory.SignalMemoryTracking(address, size, write: true, precise: true, (int)kind); + + using WritableRegion region = _cpuMemory.GetWritableRegion(address, (int)size); + + MemoryMarshal.Cast(region.Memory.Span).Fill(value); + } + + /// + /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with. + /// + /// CPU virtual address of the region + /// Size of the region + /// Kind of the resource being tracked + /// Region flags + /// The memory tracking handle + public RegionHandle BeginTracking(ulong address, ulong size, ResourceKind kind, RegionFlags flags = RegionFlags.None) + { + return _cpuMemory.BeginTracking(address, size, (int)kind, flags); + } + + /// + /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with. + /// + /// Ranges of physical memory where the data is located + /// Kind of the resource being tracked + /// The memory tracking handle + public GpuRegionHandle BeginTracking(MultiRange range, ResourceKind kind) + { + var cpuRegionHandles = new RegionHandle[range.Count]; + int count = 0; + + for (int i = 0; i < range.Count; i++) + { + var currentRange = range.GetSubRange(i); + if (currentRange.Address != MemoryManager.PteUnmapped) + { + cpuRegionHandles[count++] = _cpuMemory.BeginTracking(currentRange.Address, currentRange.Size, (int)kind); + } + } + + if (count != range.Count) + { + Array.Resize(ref cpuRegionHandles, count); + } + + return new GpuRegionHandle(cpuRegionHandles); + } + + /// + /// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with. + /// + /// CPU virtual address of the region + /// Size of the region + /// Kind of the resource being tracked + /// Region flags + /// Handles to inherit state from or reuse + /// Desired granularity of write tracking + /// The memory tracking handle + public MultiRegionHandle BeginGranularTracking( + ulong address, + ulong size, + ResourceKind kind, + RegionFlags flags = RegionFlags.None, + IEnumerable handles = null, + ulong granularity = 4096) + { + return _cpuMemory.BeginGranularTracking(address, size, handles, granularity, (int)kind, flags); + } + + /// + /// Obtains a smart memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with. + /// + /// CPU virtual address of the region + /// Size of the region + /// Kind of the resource being tracked + /// Desired granularity of write tracking + /// The memory tracking handle + public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ResourceKind kind, ulong granularity = 4096) + { + return _cpuMemory.BeginSmartGranularTracking(address, size, granularity, (int)kind); + } + + /// + /// Checks if a given memory page is mapped. + /// + /// CPU virtual address of the page + /// True if mapped, false otherwise + public bool IsMapped(ulong address) + { + return _cpuMemory.IsMapped(address); + } + + /// + /// Release our reference to the CPU memory manager. + /// + public void Dispose() + { + _context.DeferredActions.Enqueue(Destroy); + } + + /// + /// Performs disposal of the host GPU caches with resources mapped on this physical memory. + /// This must only be called from the render thread. + /// + private void Destroy() + { + ShaderCache.Dispose(); + BufferCache.Dispose(); + TextureCache.Dispose(); + + DecrementReferenceCount(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/PteKind.cs b/src/Ryujinx.Graphics.Gpu/Memory/PteKind.cs new file mode 100644 index 00000000..1585328f --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/PteKind.cs @@ -0,0 +1,268 @@ +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Kind of the resource at the given memory mapping. + /// + public enum PteKind : byte + { + Invalid = 0xff, + Pitch = 0x00, + Z16 = 0x01, + Z162C = 0x02, + Z16MS22C = 0x03, + Z16MS42C = 0x04, + Z16MS82C = 0x05, + Z16MS162C = 0x06, + Z162Z = 0x07, + Z16MS22Z = 0x08, + Z16MS42Z = 0x09, + Z16MS82Z = 0x0a, + Z16MS162Z = 0x0b, + Z162CZ = 0x36, + Z16MS22CZ = 0x37, + Z16MS42CZ = 0x38, + Z16MS82CZ = 0x39, + Z16MS162CZ = 0x5f, + Z164CZ = 0x0c, + Z16MS24CZ = 0x0d, + Z16MS44CZ = 0x0e, + Z16MS84CZ = 0x0f, + Z16MS164CZ = 0x10, + S8Z24 = 0x11, + S8Z241Z = 0x12, + S8Z24MS21Z = 0x13, + S8Z24MS41Z = 0x14, + S8Z24MS81Z = 0x15, + S8Z24MS161Z = 0x16, + S8Z242CZ = 0x17, + S8Z24MS22CZ = 0x18, + S8Z24MS42CZ = 0x19, + S8Z24MS82CZ = 0x1a, + S8Z24MS162CZ = 0x1b, + S8Z242CS = 0x1c, + S8Z24MS22CS = 0x1d, + S8Z24MS42CS = 0x1e, + S8Z24MS82CS = 0x1f, + S8Z24MS162CS = 0x20, + S8Z244CSZV = 0x21, + S8Z24MS24CSZV = 0x22, + S8Z24MS44CSZV = 0x23, + S8Z24MS84CSZV = 0x24, + S8Z24MS164CSZV = 0x25, + V8Z24MS4VC12 = 0x26, + V8Z24MS4VC4 = 0x27, + V8Z24MS8VC8 = 0x28, + V8Z24MS8VC24 = 0x29, + V8Z24MS4VC121ZV = 0x2e, + V8Z24MS4VC41ZV = 0x2f, + V8Z24MS8VC81ZV = 0x30, + V8Z24MS8VC241ZV = 0x31, + V8Z24MS4VC122CS = 0x32, + V8Z24MS4VC42CS = 0x33, + V8Z24MS8VC82CS = 0x34, + V8Z24MS8VC242CS = 0x35, + V8Z24MS4VC122CZV = 0x3a, + V8Z24MS4VC42CZV = 0x3b, + V8Z24MS8VC82CZV = 0x3c, + V8Z24MS8VC242CZV = 0x3d, + V8Z24MS4VC122ZV = 0x3e, + V8Z24MS4VC42ZV = 0x3f, + V8Z24MS8VC82ZV = 0x40, + V8Z24MS8VC242ZV = 0x41, + V8Z24MS4VC124CSZV = 0x42, + V8Z24MS4VC44CSZV = 0x43, + V8Z24MS8VC84CSZV = 0x44, + V8Z24MS8VC244CSZV = 0x45, + Z24S8 = 0x46, + Z24S81Z = 0x47, + Z24S8MS21Z = 0x48, + Z24S8MS41Z = 0x49, + Z24S8MS81Z = 0x4a, + Z24S8MS161Z = 0x4b, + Z24S82CS = 0x4c, + Z24S8MS22CS = 0x4d, + Z24S8MS42CS = 0x4e, + Z24S8MS82CS = 0x4f, + Z24S8MS162CS = 0x50, + Z24S82CZ = 0x51, + Z24S8MS22CZ = 0x52, + Z24S8MS42CZ = 0x53, + Z24S8MS82CZ = 0x54, + Z24S8MS162CZ = 0x55, + Z24S84CSZV = 0x56, + Z24S8MS24CSZV = 0x57, + Z24S8MS44CSZV = 0x58, + Z24S8MS84CSZV = 0x59, + Z24S8MS164CSZV = 0x5a, + Z24V8MS4VC12 = 0x5b, + Z24V8MS4VC4 = 0x5c, + Z24V8MS8VC8 = 0x5d, + Z24V8MS8VC24 = 0x5e, + YUVB8C12Y = 0x60, + YUVB8C22Y = 0x61, + YUVB10C12Y = 0x62, + YUVB10C22Y = 0x6b, + YUVB12C12Y = 0x6c, + YUVB12C22Y = 0x6d, + Z24V8MS4VC121ZV = 0x63, + Z24V8MS4VC41ZV = 0x64, + Z24V8MS8VC81ZV = 0x65, + Z24V8MS8VC241ZV = 0x66, + Z24V8MS4VC122CS = 0x67, + Z24V8MS4VC42CS = 0x68, + Z24V8MS8VC82CS = 0x69, + Z24V8MS8VC242CS = 0x6a, + Z24V8MS4VC122CZV = 0x6f, + Z24V8MS4VC42CZV = 0x70, + Z24V8MS8VC82CZV = 0x71, + Z24V8MS8VC242CZV = 0x72, + Z24V8MS4VC122ZV = 0x73, + Z24V8MS4VC42ZV = 0x74, + Z24V8MS8VC82ZV = 0x75, + Z24V8MS8VC242ZV = 0x76, + Z24V8MS4VC124CSZV = 0x77, + Z24V8MS4VC44CSZV = 0x78, + Z24V8MS8VC84CSZV = 0x79, + Z24V8MS8VC244CSZV = 0x7a, + ZF32 = 0x7b, + ZF321Z = 0x7c, + ZF32MS21Z = 0x7d, + ZF32MS41Z = 0x7e, + ZF32MS81Z = 0x7f, + ZF32MS161Z = 0x80, + ZF322CS = 0x81, + ZF32MS22CS = 0x82, + ZF32MS42CS = 0x83, + ZF32MS82CS = 0x84, + ZF32MS162CS = 0x85, + ZF322CZ = 0x86, + ZF32MS22CZ = 0x87, + ZF32MS42CZ = 0x88, + ZF32MS82CZ = 0x89, + ZF32MS162CZ = 0x8a, + X8Z24X16V8S8MS4VC12 = 0x8b, + X8Z24X16V8S8MS4VC4 = 0x8c, + X8Z24X16V8S8MS8VC8 = 0x8d, + X8Z24X16V8S8MS8VC24 = 0x8e, + X8Z24X16V8S8MS4VC121CS = 0x8f, + X8Z24X16V8S8MS4VC41CS = 0x90, + X8Z24X16V8S8MS8VC81CS = 0x91, + X8Z24X16V8S8MS8VC241CS = 0x92, + X8Z24X16V8S8MS4VC121ZV = 0x97, + X8Z24X16V8S8MS4VC41ZV = 0x98, + X8Z24X16V8S8MS8VC81ZV = 0x99, + X8Z24X16V8S8MS8VC241ZV = 0x9a, + X8Z24X16V8S8MS4VC121CZV = 0x9b, + X8Z24X16V8S8MS4VC41CZV = 0x9c, + X8Z24X16V8S8MS8VC81CZV = 0x9d, + X8Z24X16V8S8MS8VC241CZV = 0x9e, + X8Z24X16V8S8MS4VC122CS = 0x9f, + X8Z24X16V8S8MS4VC42CS = 0xa0, + X8Z24X16V8S8MS8VC82CS = 0xa1, + X8Z24X16V8S8MS8VC242CS = 0xa2, + X8Z24X16V8S8MS4VC122CSZV = 0xa3, + X8Z24X16V8S8MS4VC42CSZV = 0xa4, + X8Z24X16V8S8MS8VC82CSZV = 0xa5, + X8Z24X16V8S8MS8VC242CSZV = 0xa6, + ZF32X16V8S8MS4VC12 = 0xa7, + ZF32X16V8S8MS4VC4 = 0xa8, + ZF32X16V8S8MS8VC8 = 0xa9, + ZF32X16V8S8MS8VC24 = 0xaa, + ZF32X16V8S8MS4VC121CS = 0xab, + ZF32X16V8S8MS4VC41CS = 0xac, + ZF32X16V8S8MS8VC81CS = 0xad, + ZF32X16V8S8MS8VC241CS = 0xae, + ZF32X16V8S8MS4VC121ZV = 0xb3, + ZF32X16V8S8MS4VC41ZV = 0xb4, + ZF32X16V8S8MS8VC81ZV = 0xb5, + ZF32X16V8S8MS8VC241ZV = 0xb6, + ZF32X16V8S8MS4VC121CZV = 0xb7, + ZF32X16V8S8MS4VC41CZV = 0xb8, + ZF32X16V8S8MS8VC81CZV = 0xb9, + ZF32X16V8S8MS8VC241CZV = 0xba, + ZF32X16V8S8MS4VC122CS = 0xbb, + ZF32X16V8S8MS4VC42CS = 0xbc, + ZF32X16V8S8MS8VC82CS = 0xbd, + ZF32X16V8S8MS8VC242CS = 0xbe, + ZF32X16V8S8MS4VC122CSZV = 0xbf, + ZF32X16V8S8MS4VC42CSZV = 0xc0, + ZF32X16V8S8MS8VC82CSZV = 0xc1, + ZF32X16V8S8MS8VC242CSZV = 0xc2, + ZF32X24S8 = 0xc3, + ZF32X24S81CS = 0xc4, + ZF32X24S8MS21CS = 0xc5, + ZF32X24S8MS41CS = 0xc6, + ZF32X24S8MS81CS = 0xc7, + ZF32X24S8MS161CS = 0xc8, + ZF32X24S82CSZV = 0xce, + ZF32X24S8MS22CSZV = 0xcf, + ZF32X24S8MS42CSZV = 0xd0, + ZF32X24S8MS82CSZV = 0xd1, + ZF32X24S8MS162CSZV = 0xd2, + ZF32X24S82CS = 0xd3, + ZF32X24S8MS22CS = 0xd4, + ZF32X24S8MS42CS = 0xd5, + ZF32X24S8MS82CS = 0xd6, + ZF32X24S8MS162CS = 0xd7, + S8 = 0x2a, + S82S = 0x2b, + Generic16Bx2 = 0xfe, + C322C = 0xd8, + C322CBR = 0xd9, + C322CBA = 0xda, + C322CRA = 0xdb, + C322BRA = 0xdc, + C32MS22C = 0xdd, + C32MS22CBR = 0xde, + C32MS24CBRA = 0xcc, + C32MS42C = 0xdf, + C32MS42CBR = 0xe0, + C32MS42CBA = 0xe1, + C32MS42CRA = 0xe2, + C32MS42BRA = 0xe3, + C32MS44CBRA = 0x2c, + C32MS8MS162C = 0xe4, + C32MS8MS162CRA = 0xe5, + C642C = 0xe6, + C642CBR = 0xe7, + C642CBA = 0xe8, + C642CRA = 0xe9, + C642BRA = 0xea, + C64MS22C = 0xeb, + C64MS22CBR = 0xec, + C64MS24CBRA = 0xcd, + C64MS42C = 0xed, + C64MS42CBR = 0xee, + C64MS42CBA = 0xef, + C64MS42CRA = 0xf0, + C64MS42BRA = 0xf1, + C64MS44CBRA = 0x2d, + C64MS8MS162C = 0xf2, + C64MS8MS162CRA = 0xf3, + C1282C = 0xf4, + C1282CR = 0xf5, + C128MS22C = 0xf6, + C128MS22CR = 0xf7, + C128MS42C = 0xf8, + C128MS42CR = 0xf9, + C128MS8MS162C = 0xfa, + C128MS8MS162CR = 0xfb, + X8C24 = 0xfc, + PitchNoSwizzle = 0xfd, + SmSkedMessage = 0xca, + SmHostMessage = 0xcb, + } + + static class PteKindExtensions + { + /// + /// Checks if the kind is pitch. + /// + /// Kind to check + /// True if pitch, false otherwise + public static bool IsPitch(this PteKind kind) + { + return kind == PteKind.Pitch || kind == PteKind.PitchNoSwizzle; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/ResourceKind.cs b/src/Ryujinx.Graphics.Gpu/Memory/ResourceKind.cs new file mode 100644 index 00000000..5d2ada56 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/ResourceKind.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Kind of a GPU resource. + /// + enum ResourceKind + { + None, + Buffer, + Texture, + Pool, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs b/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs new file mode 100644 index 00000000..fc444f49 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/SupportBufferUpdater.cs @@ -0,0 +1,224 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Support buffer data updater. + /// + class SupportBufferUpdater : BufferUpdater + { + private SupportBuffer _data; + + /// + /// Creates a new instance of the support buffer updater. + /// + /// Renderer that the support buffer will be used with + public SupportBufferUpdater(IRenderer renderer) : base(renderer) + { + var defaultScale = new Vector4 { X = 1f, Y = 0f, Z = 0f, W = 0f }; + _data.RenderScale.AsSpan().Fill(defaultScale); + DirtyRenderScale(0, SupportBuffer.RenderScaleMaxCount); + } + + /// + /// Marks the fragment render scale count as being modified. + /// + private void DirtyFragmentRenderScaleCount() + { + MarkDirty(SupportBuffer.FragmentRenderScaleCountOffset, sizeof(int)); + } + + /// + /// Marks data of a given type as being modified. + /// + /// Type of the data + /// Base offset of the data in bytes + /// Index of the data, in elements + /// Number of elements + private void DirtyGenericField(int baseOffset, int offset, int count) where T : unmanaged + { + int elemSize = Unsafe.SizeOf(); + + MarkDirty(baseOffset + offset * elemSize, count * elemSize); + } + + /// + /// Marks render scales as being modified. + /// + /// Index of the first scale that was modified + /// Number of modified scales + private void DirtyRenderScale(int offset, int count) + { + DirtyGenericField>(SupportBuffer.GraphicsRenderScaleOffset, offset, count); + } + + /// + /// Marks render target BGRA format state as modified. + /// + /// Index of the first render target that had its BGRA format modified + /// Number of render targets + private void DirtyFragmentIsBgra(int offset, int count) + { + DirtyGenericField>(SupportBuffer.FragmentIsBgraOffset, offset, count); + } + + /// + /// Updates the inverse viewport vector. + /// + /// Inverse viewport vector + private void UpdateViewportInverse(Vector4 data) + { + _data.ViewportInverse = data; + + MarkDirty(SupportBuffer.ViewportInverseOffset, SupportBuffer.FieldSize); + } + + /// + /// Updates the viewport size vector. + /// + /// Viewport size vector + private void UpdateViewportSize(Vector4 data) + { + _data.ViewportSize = data; + + MarkDirty(SupportBuffer.ViewportSizeOffset, SupportBuffer.FieldSize); + } + + /// + /// Sets the scale of all output render targets (they should all have the same scale). + /// + /// Scale value + public void SetRenderTargetScale(float scale) + { + _data.RenderScale[0].X = scale; + DirtyRenderScale(0, 1); // Just the first element. + } + + /// + /// Updates the render scales for shader input textures or images. + /// + /// Index of the scale + /// Scale value + public void UpdateRenderScale(int index, float scale) + { + if (_data.RenderScale[1 + index].X != scale) + { + _data.RenderScale[1 + index].X = scale; + DirtyRenderScale(1 + index, 1); + } + } + + /// + /// Updates the render scales for shader input textures or images. + /// + /// Total number of scales across all stages + /// Total number of scales on the fragment shader stage + public void UpdateRenderScaleFragmentCount(int totalCount, int fragmentCount) + { + // Only update fragment count if there are scales after it for the vertex stage. + if (fragmentCount != totalCount && fragmentCount != _data.FragmentRenderScaleCount.X) + { + _data.FragmentRenderScaleCount.X = fragmentCount; + DirtyFragmentRenderScaleCount(); + } + } + + /// + /// Sets whether the format of a given render target is a BGRA format. + /// + /// Render target index + /// True if the format is BGRA< false otherwise + public void SetRenderTargetIsBgra(int index, bool isBgra) + { + bool isBgraChanged = _data.FragmentIsBgra[index].X != 0 != isBgra; + + if (isBgraChanged) + { + _data.FragmentIsBgra[index].X = isBgra ? 1 : 0; + DirtyFragmentIsBgra(index, 1); + } + } + + /// + /// Sets whether a viewport has transform disabled. + /// + /// Value used as viewport width + /// Value used as viewport height + /// Render target scale + /// True if transform is disabled, false otherwise + public void SetViewportTransformDisable(float viewportWidth, float viewportHeight, float scale, bool disableTransform) + { + float disableTransformF = disableTransform ? 1.0f : 0.0f; + if (_data.ViewportInverse.W != disableTransformF || disableTransform) + { + UpdateViewportInverse(new Vector4 + { + X = scale * 2f / viewportWidth, + Y = scale * 2f / viewportHeight, + Z = 1, + W = disableTransformF, + }); + } + } + + /// + /// Sets the viewport size, used to invert the fragment coordinates Y value. + /// + /// Value used as viewport width + /// Value used as viewport height + public void SetViewportSize(float viewportWidth, float viewportHeight) + { + if (_data.ViewportSize.X != viewportWidth || _data.ViewportSize.Y != viewportHeight) + { + UpdateViewportSize(new Vector4 + { + X = viewportWidth, + Y = viewportHeight, + Z = 1, + W = 0 + }); + } + } + + /// + /// Sets offset for the misaligned portion of a transform feedback buffer, and the buffer size, for transform feedback emulation. + /// + /// Index of the transform feedback buffer + /// Misaligned offset of the buffer + public void SetTfeOffset(int bufferIndex, int offset) + { + ref int currentOffset = ref GetElementRef(ref _data.TfeOffset, bufferIndex); + + if (currentOffset != offset) + { + currentOffset = offset; + MarkDirty(SupportBuffer.TfeOffsetOffset + bufferIndex * sizeof(int), sizeof(int)); + } + } + + /// + /// Sets the vertex count used for transform feedback emulation with instanced draws. + /// + /// Vertex count of the instanced draw + public void SetTfeVertexCount(int vertexCount) + { + if (_data.TfeVertexCount.X != vertexCount) + { + _data.TfeVertexCount.X = vertexCount; + MarkDirty(SupportBuffer.TfeVertexCountOffset, sizeof(int)); + } + } + + /// + /// Submits all pending buffer updates to the GPU. + /// + public void Commit() + { + Commit(MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref _data, 1)), SupportBuffer.Binding); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/UnmapEventArgs.cs b/src/Ryujinx.Graphics.Gpu/Memory/UnmapEventArgs.cs new file mode 100644 index 00000000..83fb1fce --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/UnmapEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + public class UnmapEventArgs + { + public ulong Address { get; } + public ulong Size { get; } + public List RemapActions { get; private set; } + + public UnmapEventArgs(ulong address, ulong size) + { + Address = address; + Size = size; + } + + public void AddRemapAction(Action action) + { + RemapActions ??= new List(); + RemapActions.Add(action); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs new file mode 100644 index 00000000..206e1b48 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs @@ -0,0 +1,14 @@ +using Ryujinx.Memory.Range; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// GPU Vertex Buffer information. + /// + struct VertexBuffer + { + public MultiRange Range; + public int Stride; + public int Divisor; + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs new file mode 100644 index 00000000..964507a2 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs @@ -0,0 +1,236 @@ +using Ryujinx.Memory.Range; +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Virtual range cache. + /// + class VirtualRangeCache + { + private readonly MemoryManager _memoryManager; + + /// + /// Represents a GPU virtual memory range. + /// + private readonly struct VirtualRange : IRange + { + /// + /// GPU virtual address where the range starts. + /// + public ulong Address { get; } + + /// + /// Size of the range in bytes. + /// + public ulong Size { get; } + + /// + /// GPU virtual address where the range ends. + /// + public ulong EndAddress => Address + Size; + + /// + /// Physical regions where the GPU virtual region is mapped. + /// + public MultiRange Range { get; } + + /// + /// Creates a new virtual memory range. + /// + /// GPU virtual address where the range starts + /// Size of the range in bytes + /// Physical regions where the GPU virtual region is mapped + public VirtualRange(ulong address, ulong size, MultiRange range) + { + Address = address; + Size = size; + Range = range; + } + + /// + /// Checks if a given range overlaps with the buffer. + /// + /// Start address of the range + /// Size in bytes of the range + /// True if the range overlaps, false otherwise + public bool OverlapsWith(ulong address, ulong size) + { + return Address < address + size && address < EndAddress; + } + } + + private readonly RangeList _virtualRanges; + private VirtualRange[] _virtualRangeOverlaps; + private readonly ConcurrentQueue _deferredUnmaps; + private int _hasDeferredUnmaps; + + /// + /// Creates a new instance of the virtual range cache. + /// + /// Memory manager that the virtual range cache belongs to + public VirtualRangeCache(MemoryManager memoryManager) + { + _memoryManager = memoryManager; + _virtualRanges = new RangeList(); + _virtualRangeOverlaps = new VirtualRange[BufferCache.OverlapsBufferInitialCapacity]; + _deferredUnmaps = new ConcurrentQueue(); + } + + /// + /// Handles removal of buffers written to a memory region being unmapped. + /// + /// Sender object + /// Event arguments + public void MemoryUnmappedHandler(object sender, UnmapEventArgs e) + { + void EnqueueUnmap() + { + _deferredUnmaps.Enqueue(new VirtualRange(e.Address, e.Size, default)); + + Interlocked.Exchange(ref _hasDeferredUnmaps, 1); + } + + e.AddRemapAction(EnqueueUnmap); + } + + /// + /// Tries to get a existing, cached physical range for the specified virtual region. + /// If no cached range is found, a new one is created and added. + /// + /// GPU virtual address to get the physical range from + /// Size in bytes of the region + /// Physical range for the specified GPU virtual region + /// True if the range already existed, false if a new one was created and added + public bool TryGetOrAddRange(ulong gpuVa, ulong size, out MultiRange range) + { + VirtualRange[] overlaps = _virtualRangeOverlaps; + int overlapsCount; + + if (Interlocked.Exchange(ref _hasDeferredUnmaps, 0) != 0) + { + while (_deferredUnmaps.TryDequeue(out VirtualRange unmappedRange)) + { + overlapsCount = _virtualRanges.FindOverlapsNonOverlapping(unmappedRange.Address, unmappedRange.Size, ref overlaps); + + for (int index = 0; index < overlapsCount; index++) + { + _virtualRanges.Remove(overlaps[index]); + } + } + } + + bool found = false; + + ulong originalVa = gpuVa; + + overlapsCount = _virtualRanges.FindOverlapsNonOverlapping(gpuVa, size, ref overlaps); + + if (overlapsCount != 0) + { + // The virtual range already exists. We just need to check if our range fits inside + // the existing one, and if not, we must extend the existing one. + + ulong endAddress = gpuVa + size; + VirtualRange overlap0 = overlaps[0]; + + if (overlap0.Address > gpuVa || overlap0.EndAddress < endAddress) + { + for (int index = 0; index < overlapsCount; index++) + { + VirtualRange virtualRange = overlaps[index]; + + gpuVa = Math.Min(gpuVa, virtualRange.Address); + endAddress = Math.Max(endAddress, virtualRange.EndAddress); + + _virtualRanges.Remove(virtualRange); + } + + ulong newSize = endAddress - gpuVa; + MultiRange newRange = _memoryManager.GetPhysicalRegions(gpuVa, newSize); + + _virtualRanges.Add(new(gpuVa, newSize, newRange)); + + range = newRange.Slice(originalVa - gpuVa, size); + } + else + { + found = overlap0.Range.Count == 1 || IsSparseAligned(overlap0.Range); + range = overlap0.Range.Slice(gpuVa - overlap0.Address, size); + } + } + else + { + // No overlap, just create a new virtual range. + range = _memoryManager.GetPhysicalRegions(gpuVa, size); + + VirtualRange virtualRange = new(gpuVa, size, range); + + _virtualRanges.Add(virtualRange); + } + + ShrinkOverlapsBufferIfNeeded(); + + // If the range is not properly aligned for sparse mapping, + // let's just force it to a single range. + // This might cause issues in some applications that uses sparse + // mappings. + if (!IsSparseAligned(range)) + { + range = new MultiRange(range.GetSubRange(0).Address, size); + } + + return found; + } + + /// + /// Checks if the physical memory ranges are valid for sparse mapping, + /// which requires all sub-ranges to be 64KB aligned. + /// + /// Range to check + /// True if the range is valid for sparse mapping, false otherwise + private static bool IsSparseAligned(MultiRange range) + { + if (range.Count == 1) + { + return (range.GetSubRange(0).Address & (BufferCache.SparseBufferAlignmentSize - 1)) == 0; + } + + for (int i = 0; i < range.Count; i++) + { + MemoryRange subRange = range.GetSubRange(i); + + // Check if address is aligned. The address of the first sub-range can + // be misaligned as it is at the start. + if (i > 0 && + subRange.Address != MemoryManager.PteUnmapped && + (subRange.Address & (BufferCache.SparseBufferAlignmentSize - 1)) != 0) + { + return false; + } + + // Check if the size is aligned. The size of the last sub-range can + // be misaligned as it is at the end. + if (i < range.Count - 1 && (subRange.Size & (BufferCache.SparseBufferAlignmentSize - 1)) != 0) + { + return false; + } + } + + return true; + } + + /// + /// Resizes the temporary buffer used for range list intersection results, if it has grown too much. + /// + private void ShrinkOverlapsBufferIfNeeded() + { + if (_virtualRangeOverlaps.Length > BufferCache.OverlapsBufferMaxCapacity) + { + Array.Resize(ref _virtualRangeOverlaps, BufferCache.OverlapsBufferMaxCapacity); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj b/src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj new file mode 100644 index 00000000..6f1cce6a --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + true + + + + + + + + + + + + diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs new file mode 100644 index 00000000..51be00b6 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs @@ -0,0 +1,117 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Shader; +using System; +using System.Linq; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// A collection of shader bindings ready for insertion into the buffer and texture managers. + /// + internal class CachedShaderBindings + { + public TextureBindingInfo[][] TextureBindings { get; } + public TextureBindingInfo[][] ImageBindings { get; } + public BufferDescriptor[][] ConstantBufferBindings { get; } + public BufferDescriptor[][] StorageBufferBindings { get; } + + public int[] TextureCounts { get; } + + public int MaxTextureBinding { get; } + public int MaxImageBinding { get; } + + /// + /// Create a new cached shader bindings collection. + /// + /// Whether the shader is for compute + /// The stages used by the shader + public CachedShaderBindings(bool isCompute, CachedShaderStage[] stages) + { + int stageCount = isCompute ? 1 : Constants.ShaderStages; + + TextureBindings = new TextureBindingInfo[stageCount][]; + ImageBindings = new TextureBindingInfo[stageCount][]; + ConstantBufferBindings = new BufferDescriptor[stageCount][]; + StorageBufferBindings = new BufferDescriptor[stageCount][]; + + TextureCounts = new int[stageCount]; + + int maxTextureBinding = -1; + int maxImageBinding = -1; + int offset = isCompute ? 0 : 1; + + for (int i = 0; i < stageCount; i++) + { + CachedShaderStage stage = stages[i + offset]; + + if (stage == null) + { + TextureBindings[i] = Array.Empty(); + ImageBindings[i] = Array.Empty(); + ConstantBufferBindings[i] = Array.Empty(); + StorageBufferBindings[i] = Array.Empty(); + + continue; + } + + TextureBindings[i] = stage.Info.Textures.Select(descriptor => + { + Target target = descriptor.Type != SamplerType.None ? ShaderTexture.GetTarget(descriptor.Type) : default; + + var result = new TextureBindingInfo( + target, + descriptor.Set, + descriptor.Binding, + descriptor.ArrayLength, + descriptor.CbufSlot, + descriptor.HandleIndex, + descriptor.Flags, + descriptor.Type == SamplerType.None); + + if (descriptor.ArrayLength <= 1) + { + if (descriptor.Binding > maxTextureBinding) + { + maxTextureBinding = descriptor.Binding; + } + + TextureCounts[i]++; + } + + return result; + }).ToArray(); + + ImageBindings[i] = stage.Info.Images.Select(descriptor => + { + Target target = ShaderTexture.GetTarget(descriptor.Type); + Format format = ShaderTexture.GetFormat(descriptor.Format); + + var result = new TextureBindingInfo( + target, + format, + descriptor.Set, + descriptor.Binding, + descriptor.ArrayLength, + descriptor.CbufSlot, + descriptor.HandleIndex, + descriptor.Flags); + + if (descriptor.ArrayLength <= 1 && descriptor.Binding > maxImageBinding) + { + maxImageBinding = descriptor.Binding; + } + + return result; + }).ToArray(); + + ConstantBufferBindings[i] = stage.Info.CBuffers.ToArray(); + StorageBufferBindings[i] = stage.Info.SBuffers.ToArray(); + } + + MaxTextureBinding = maxTextureBinding; + MaxImageBinding = maxImageBinding; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs new file mode 100644 index 00000000..4f56dcac --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs @@ -0,0 +1,79 @@ +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Represents a program composed of one or more shader stages (for graphics shaders), + /// or a single shader (for compute shaders). + /// + class CachedShaderProgram : IDisposable + { + /// + /// Host shader program object. + /// + public IProgram HostProgram { get; } + + /// + /// Optional vertex shader converted to compute. + /// + public ShaderAsCompute VertexAsCompute { get; } + + /// + /// Optional geometry shader converted to compute. + /// + public ShaderAsCompute GeometryAsCompute { get; } + + /// + /// GPU state used to create this version of the shader. + /// + public ShaderSpecializationState SpecializationState { get; } + + /// + /// Compiled shader for each shader stage. + /// + public CachedShaderStage[] Shaders { get; } + + /// + /// Cached shader bindings, ready for placing into the bindings manager. + /// + public CachedShaderBindings Bindings { get; } + + /// + /// Creates a new instance of the shader bundle. + /// + /// Host program with all the shader stages + /// GPU state used to create this version of the shader + /// Shaders + public CachedShaderProgram(IProgram hostProgram, ShaderSpecializationState specializationState, params CachedShaderStage[] shaders) + { + HostProgram = hostProgram; + SpecializationState = specializationState; + Shaders = shaders; + + SpecializationState.Prepare(shaders); + Bindings = new CachedShaderBindings(shaders.Length == 1, shaders); + } + + public CachedShaderProgram( + IProgram hostProgram, + ShaderAsCompute vertexAsCompute, + ShaderAsCompute geometryAsCompute, + ShaderSpecializationState specializationState, + CachedShaderStage[] shaders) : this(hostProgram, specializationState, shaders) + { + VertexAsCompute = vertexAsCompute; + GeometryAsCompute = geometryAsCompute; + } + + /// + /// Dispose of the host shader resources. + /// + public void Dispose() + { + HostProgram.Dispose(); + VertexAsCompute?.HostProgram.Dispose(); + GeometryAsCompute?.HostProgram.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderStage.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderStage.cs new file mode 100644 index 00000000..2381991d --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderStage.cs @@ -0,0 +1,38 @@ +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Cached shader code for a single shader stage. + /// + class CachedShaderStage + { + /// + /// Shader program information. + /// + public ShaderProgramInfo Info { get; } + + /// + /// Maxwell binary shader code. + /// + public byte[] Code { get; } + + /// + /// Constant buffer 1 data accessed by the shader. + /// + public byte[] Cb1Data { get; } + + /// + /// Creates a new instance of the shader code holder. + /// + /// Shader program information + /// Maxwell binary shader code + /// Constant buffer 1 data accessed by the shader + public CachedShaderStage(ShaderProgramInfo info, byte[] code, byte[] cb1Data) + { + Info = info; + Code = code; + Cb1Data = cb1Data; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ComputeShaderCacheHashTable.cs b/src/Ryujinx.Graphics.Gpu/Shader/ComputeShaderCacheHashTable.cs new file mode 100644 index 00000000..0119a6a3 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ComputeShaderCacheHashTable.cs @@ -0,0 +1,71 @@ +using Ryujinx.Graphics.Gpu.Shader.HashTable; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Compute shader cache hash table. + /// + class ComputeShaderCacheHashTable + { + private readonly PartitionedHashTable _cache; + private readonly List _shaderPrograms; + + /// + /// Creates a new compute shader cache hash table. + /// + public ComputeShaderCacheHashTable() + { + _cache = new PartitionedHashTable(); + _shaderPrograms = new List(); + } + + /// + /// Adds a program to the cache. + /// + /// Program to be added + public void Add(CachedShaderProgram program) + { + var specList = _cache.GetOrAdd(program.Shaders[0].Code, new ShaderSpecializationList()); + specList.Add(program); + _shaderPrograms.Add(program); + } + + /// + /// Tries to find a cached program. + /// + /// GPU channel + /// Texture pool state + /// Compute state + /// GPU virtual address of the compute shader + /// Cached host program for the given state, if found + /// Cached guest code, if any found + /// True if a cached host program was found, false otherwise + public bool TryFind( + GpuChannel channel, + GpuChannelPoolState poolState, + GpuChannelComputeState computeState, + ulong gpuVa, + out CachedShaderProgram program, + out byte[] cachedGuestCode) + { + program = null; + ShaderCodeAccessor codeAccessor = new(channel.MemoryManager, gpuVa); + bool hasSpecList = _cache.TryFindItem(codeAccessor, out var specList, out cachedGuestCode); + + return hasSpecList && specList.TryFindForCompute(channel, poolState, computeState, out program); + } + + /// + /// Gets all programs that have been added to the table. + /// + /// Programs added to the table + public IEnumerable GetPrograms() + { + foreach (var program in _shaderPrograms) + { + yield return program; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs new file mode 100644 index 00000000..e0f17ba9 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs @@ -0,0 +1,138 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using System; +using System.IO; + +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + /// + /// Represents a background disk cache writer. + /// + class BackgroundDiskCacheWriter : IDisposable + { + /// + /// Possible operation to do on the . + /// + private enum CacheFileOperation + { + /// + /// Operation to add a shader to the cache. + /// + AddShader, + } + + /// + /// Represents an operation to perform on the . + /// + private readonly struct CacheFileOperationTask + { + /// + /// The type of operation to perform. + /// + public readonly CacheFileOperation Type; + + /// + /// The data associated to this operation or null. + /// + public readonly object Data; + + public CacheFileOperationTask(CacheFileOperation type, object data) + { + Type = type; + Data = data; + } + } + + /// + /// Background shader cache write information. + /// + private readonly struct AddShaderData + { + /// + /// Cached shader program. + /// + public readonly CachedShaderProgram Program; + + /// + /// Binary host code. + /// + public readonly byte[] HostCode; + + /// + /// Creates a new background shader cache write information. + /// + /// Cached shader program + /// Binary host code + public AddShaderData(CachedShaderProgram program, byte[] hostCode) + { + Program = program; + HostCode = hostCode; + } + } + + private readonly GpuContext _context; + private readonly DiskCacheHostStorage _hostStorage; + private readonly AsyncWorkQueue _fileWriterWorkerQueue; + + /// + /// Creates a new background disk cache writer. + /// + /// GPU context + /// Disk cache host storage + public BackgroundDiskCacheWriter(GpuContext context, DiskCacheHostStorage hostStorage) + { + _context = context; + _hostStorage = hostStorage; + _fileWriterWorkerQueue = new AsyncWorkQueue(ProcessTask, "GPU.BackgroundDiskCacheWriter"); + } + + /// + /// Processes a shader cache background operation. + /// + /// Task to process + private void ProcessTask(CacheFileOperationTask task) + { + switch (task.Type) + { + case CacheFileOperation.AddShader: + AddShaderData data = (AddShaderData)task.Data; + try + { + _hostStorage.AddShader(_context, data.Program, data.HostCode); + } + catch (DiskCacheLoadException diskCacheLoadException) + { + Logger.Error?.Print(LogClass.Gpu, $"Error writing shader to disk cache. {diskCacheLoadException.Message}"); + } + catch (IOException ioException) + { + Logger.Error?.Print(LogClass.Gpu, $"Error writing shader to disk cache. {ioException.Message}"); + } + break; + } + } + + /// + /// Adds a shader program to be cached in the background. + /// + /// Shader program to cache + /// Host binary code of the program + public void AddShader(CachedShaderProgram program, byte[] hostCode) + { + _fileWriterWorkerQueue.Add(new CacheFileOperationTask(CacheFileOperation.AddShader, new AddShaderData(program, hostCode))); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _fileWriterWorkerQueue.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs new file mode 100644 index 00000000..3837092c --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs @@ -0,0 +1,247 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + /// + /// Binary data serializer. + /// + struct BinarySerializer + { + private readonly Stream _stream; + private Stream _activeStream; + + /// + /// Creates a new binary serializer. + /// + /// Stream to read from or write into + public BinarySerializer(Stream stream) + { + _stream = stream; + _activeStream = stream; + } + + /// + /// Reads data from the stream. + /// + /// Type of the data + /// Data read + public readonly void Read(ref T data) where T : unmanaged + { + Span buffer = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref data, 1)); + for (int offset = 0; offset < buffer.Length;) + { + offset += _activeStream.Read(buffer[offset..]); + } + } + + /// + /// Tries to read data from the stream. + /// + /// Type of the data + /// Data read + /// True if the read was successful, false otherwise + public readonly bool TryRead(ref T data) where T : unmanaged + { + // Length is unknown on compressed streams. + if (_activeStream == _stream) + { + int size = Unsafe.SizeOf(); + if (_activeStream.Length - _activeStream.Position < size) + { + return false; + } + } + + Read(ref data); + return true; + } + + /// + /// Reads data prefixed with a magic and size from the stream. + /// + /// Type of the data + /// Data read + /// Expected magic value, for validation + public readonly void ReadWithMagicAndSize(ref T data, uint magic) where T : unmanaged + { + uint actualMagic = 0; + int size = 0; + Read(ref actualMagic); + Read(ref size); + + if (actualMagic != magic) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedInvalidMagic); + } + + // Structs are expected to expand but not shrink between versions. + if (size > Unsafe.SizeOf()) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedInvalidLength); + } + + Span buffer = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref data, 1))[..size]; + for (int offset = 0; offset < buffer.Length;) + { + offset += _activeStream.Read(buffer[offset..]); + } + } + + /// + /// Writes data into the stream. + /// + /// Type of the data + /// Data to be written + public readonly void Write(ref T data) where T : unmanaged + { + Span buffer = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref data, 1)); + _activeStream.Write(buffer); + } + + /// + /// Writes data prefixed with a magic and size into the stream. + /// + /// Type of the data + /// Data to write + /// Magic value to write + public readonly void WriteWithMagicAndSize(ref T data, uint magic) where T : unmanaged + { + int size = Unsafe.SizeOf(); + Write(ref magic); + Write(ref size); + Span buffer = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref data, 1)); + _activeStream.Write(buffer); + } + + /// + /// Indicates that all data that will be read from the stream has been compressed. + /// + public void BeginCompression() + { + CompressionAlgorithm algorithm = CompressionAlgorithm.None; + Read(ref algorithm); + + switch (algorithm) + { + case CompressionAlgorithm.None: + break; + case CompressionAlgorithm.Deflate: + _activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true); + break; + case CompressionAlgorithm.Brotli: + _activeStream = new BrotliStream(_stream, CompressionMode.Decompress, true); + break; + default: + throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\""); + } + } + + /// + /// Indicates that all data that will be written into the stream should be compressed. + /// + /// Compression algorithm that should be used + public void BeginCompression(CompressionAlgorithm algorithm) + { + Write(ref algorithm); + + switch (algorithm) + { + case CompressionAlgorithm.None: + break; + case CompressionAlgorithm.Deflate: + _activeStream = new DeflateStream(_stream, CompressionLevel.Fastest, true); + break; + case CompressionAlgorithm.Brotli: + _activeStream = new BrotliStream(_stream, CompressionLevel.Fastest, true); + break; + default: + throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\""); + } + } + + /// + /// Indicates the end of a compressed chunck. + /// + /// + /// Any data written after this will not be compressed unless is called again. + /// Any data read after this will be assumed to be uncompressed unless is called again. + /// + public void EndCompression() + { + if (_activeStream != _stream) + { + _activeStream.Dispose(); + _activeStream = _stream; + } + } + + /// + /// Reads compressed data from the stream. + /// + /// + /// must have the exact length of the uncompressed data, + /// otherwise decompression will fail. + /// + /// Stream to read from + /// Buffer to write the uncompressed data into + public static void ReadCompressed(Stream stream, Span data) + { + CompressionAlgorithm algorithm = (CompressionAlgorithm)stream.ReadByte(); + + switch (algorithm) + { + case CompressionAlgorithm.None: + stream.ReadExactly(data); + break; + case CompressionAlgorithm.Deflate: + stream = new DeflateStream(stream, CompressionMode.Decompress, true); + for (int offset = 0; offset < data.Length;) + { + offset += stream.Read(data[offset..]); + } + stream.Dispose(); + break; + case CompressionAlgorithm.Brotli: + stream = new BrotliStream(stream, CompressionMode.Decompress, true); + for (int offset = 0; offset < data.Length;) + { + offset += stream.Read(data[offset..]); + } + stream.Dispose(); + break; + } + } + + /// + /// Compresses and writes the compressed data into the stream. + /// + /// Stream to write into + /// Data to compress + /// Compression algorithm to be used + public static void WriteCompressed(Stream stream, ReadOnlySpan data, CompressionAlgorithm algorithm) + { + stream.WriteByte((byte)algorithm); + + switch (algorithm) + { + case CompressionAlgorithm.None: + stream.Write(data); + break; + case CompressionAlgorithm.Deflate: + stream = new DeflateStream(stream, CompressionLevel.Fastest, true); + stream.Write(data); + stream.Dispose(); + break; + case CompressionAlgorithm.Brotli: + stream = new BrotliStream(stream, CompressionLevel.Fastest, true); + stream.Write(data); + stream.Dispose(); + break; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs new file mode 100644 index 00000000..86d3de07 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + /// + /// Algorithm used to compress the cache. + /// + enum CompressionAlgorithm : byte + { + /// + /// No compression, the data is stored as-is. + /// + None, + + /// + /// Deflate compression (RFC 1951). + /// + Deflate, + + /// + /// Brotli compression (RFC 7932). + /// + Brotli, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs new file mode 100644 index 00000000..cecfe9ac --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs @@ -0,0 +1,57 @@ +using Ryujinx.Common.Logging; +using System.IO; + +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + /// + /// Common disk cache utility methods. + /// + static class DiskCacheCommon + { + /// + /// Opens a file for read or write. + /// + /// Base path of the file (should not include the file name) + /// Name of the file + /// Indicates if the file will be read or written + /// File stream + public static FileStream OpenFile(string basePath, string fileName, bool writable) + { + string fullPath = Path.Combine(basePath, fileName); + + FileMode mode; + FileAccess access; + + if (writable) + { + mode = FileMode.OpenOrCreate; + access = FileAccess.ReadWrite; + } + else + { + mode = FileMode.Open; + access = FileAccess.Read; + } + + try + { + return new FileStream(fullPath, mode, access, FileShare.Read); + } + catch (IOException ioException) + { + Logger.Error?.Print(LogClass.Gpu, $"Could not access file \"{fullPath}\". {ioException.Message}"); + + throw new DiskCacheLoadException(DiskCacheLoadResult.NoAccess); + } + } + + /// + /// Gets the compression algorithm that should be used when writing the disk cache. + /// + /// Compression algorithm + public static CompressionAlgorithm GetCompressionAlgorithm() + { + return CompressionAlgorithm.Brotli; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs new file mode 100644 index 00000000..3c7664b7 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs @@ -0,0 +1,231 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + /// + /// Represents a GPU state and memory accessor. + /// + class DiskCacheGpuAccessor : GpuAccessorBase, IGpuAccessor + { + private readonly ReadOnlyMemory _data; + private readonly ReadOnlyMemory _cb1Data; + private readonly ShaderSpecializationState _oldSpecState; + private readonly ShaderSpecializationState _newSpecState; + private readonly int _stageIndex; + private readonly bool _isVulkan; + private readonly bool _hasGeometryShader; + private readonly bool _supportsQuads; + + /// + /// Creates a new instance of the cached GPU state accessor for shader translation. + /// + /// GPU context + /// The data of the shader + /// The constant buffer 1 data of the shader + /// Shader specialization state of the cached shader + /// Shader specialization state of the recompiled shader + /// Resource counts shared across all shader stages + /// Shader stage index + /// Indicates if a geometry shader is present + public DiskCacheGpuAccessor( + GpuContext context, + ReadOnlyMemory data, + ReadOnlyMemory cb1Data, + ShaderSpecializationState oldSpecState, + ShaderSpecializationState newSpecState, + ResourceCounts counts, + int stageIndex, + bool hasGeometryShader) : base(context, counts, stageIndex) + { + _data = data; + _cb1Data = cb1Data; + _oldSpecState = oldSpecState; + _newSpecState = newSpecState; + _stageIndex = stageIndex; + _isVulkan = context.Capabilities.Api == TargetApi.Vulkan; + _hasGeometryShader = hasGeometryShader; + _supportsQuads = context.Capabilities.SupportsQuads; + + if (stageIndex == (int)ShaderStage.Geometry - 1) + { + // Only geometry shaders require the primitive topology. + newSpecState.RecordPrimitiveTopology(); + } + } + + /// + public uint ConstantBuffer1Read(int offset) + { + if (offset + sizeof(uint) > _cb1Data.Length) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.InvalidCb1DataLength); + } + + return MemoryMarshal.Cast(_cb1Data.Span[offset..])[0]; + } + + /// + public void Log(string message) + { + Logger.Warning?.Print(LogClass.Gpu, $"Shader translator: {message}"); + } + + /// + public ReadOnlySpan GetCode(ulong address, int minimumSize) + { + return MemoryMarshal.Cast(_data.Span[(int)address..]); + } + + /// + public int QueryComputeLocalSizeX() => _oldSpecState.ComputeState.LocalSizeX; + + /// + public int QueryComputeLocalSizeY() => _oldSpecState.ComputeState.LocalSizeY; + + /// + public int QueryComputeLocalSizeZ() => _oldSpecState.ComputeState.LocalSizeZ; + + /// + public int QueryComputeLocalMemorySize() => _oldSpecState.ComputeState.LocalMemorySize; + + /// + public int QueryComputeSharedMemorySize() => _oldSpecState.ComputeState.SharedMemorySize; + + /// + public uint QueryConstantBufferUse() + { + _newSpecState.RecordConstantBufferUse(_stageIndex, _oldSpecState.ConstantBufferUse[_stageIndex]); + return _oldSpecState.ConstantBufferUse[_stageIndex]; + } + + /// + public GpuGraphicsState QueryGraphicsState() + { + return _oldSpecState.GraphicsState.CreateShaderGraphicsState( + !_isVulkan, + _supportsQuads, + _hasGeometryShader, + _isVulkan || _oldSpecState.GraphicsState.YNegateEnabled); + } + + /// + public bool QueryHasConstantBufferDrawParameters() + { + return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters; + } + + /// + /// Pool length is not available on the cache + public int QuerySamplerArrayLengthFromPool() + { + return QueryArrayLengthFromPool(isSampler: true); + } + + /// + public SamplerType QuerySamplerType(int handle, int cbufSlot) + { + _newSpecState.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); + return _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot).ConvertSamplerType(); + } + + /// + /// Constant buffer derived length is not available on the cache + public int QueryTextureArrayLengthFromBuffer(int slot) + { + if (!_oldSpecState.TextureArrayFromBufferRegistered(_stageIndex, 0, slot)) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureArrayLength); + } + + int arrayLength = _oldSpecState.GetTextureArrayFromBufferLength(_stageIndex, 0, slot); + _newSpecState.RegisterTextureArrayLengthFromBuffer(_stageIndex, 0, slot, arrayLength); + + return arrayLength; + } + + /// + /// Pool length is not available on the cache + public int QueryTextureArrayLengthFromPool() + { + return QueryArrayLengthFromPool(isSampler: false); + } + + /// + public TextureFormat QueryTextureFormat(int handle, int cbufSlot) + { + _newSpecState.RecordTextureFormat(_stageIndex, handle, cbufSlot); + (uint format, bool formatSrgb) = _oldSpecState.GetFormat(_stageIndex, handle, cbufSlot); + return ConvertToTextureFormat(format, formatSrgb); + } + + /// + public bool QueryTextureCoordNormalized(int handle, int cbufSlot) + { + _newSpecState.RecordTextureCoordNormalized(_stageIndex, handle, cbufSlot); + return _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot); + } + + /// + public bool QueryTransformFeedbackEnabled() + { + return _oldSpecState.TransformFeedbackDescriptors != null; + } + + /// + public ReadOnlySpan QueryTransformFeedbackVaryingLocations(int bufferIndex) + { + return _oldSpecState.TransformFeedbackDescriptors[bufferIndex].AsSpan(); + } + + /// + public int QueryTransformFeedbackStride(int bufferIndex) + { + return _oldSpecState.TransformFeedbackDescriptors[bufferIndex].Stride; + } + + /// + public bool QueryHasUnalignedStorageBuffer() + { + return _oldSpecState.GraphicsState.HasUnalignedStorageBuffer || _oldSpecState.ComputeState.HasUnalignedStorageBuffer; + } + + /// + /// Texture information is not available on the cache + public void RegisterTexture(int handle, int cbufSlot) + { + if (!_oldSpecState.TextureRegistered(_stageIndex, handle, cbufSlot)) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureDescriptor); + } + + (uint format, bool formatSrgb) = _oldSpecState.GetFormat(_stageIndex, handle, cbufSlot); + TextureTarget target = _oldSpecState.GetTextureTarget(_stageIndex, handle, cbufSlot); + bool coordNormalized = _oldSpecState.GetCoordNormalized(_stageIndex, handle, cbufSlot); + _newSpecState.RegisterTexture(_stageIndex, handle, cbufSlot, format, formatSrgb, target, coordNormalized); + } + + /// + /// Gets the cached texture or sampler pool capacity. + /// + /// True to get sampler pool length, false for texture pool length + /// Pool length + /// Pool length is not available on the cache + private int QueryArrayLengthFromPool(bool isSampler) + { + if (!_oldSpecState.TextureArrayFromPoolRegistered(isSampler)) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.MissingTextureArrayLength); + } + + int arrayLength = _oldSpecState.GetTextureArrayFromPoolLength(isSampler); + _newSpecState.RegisterTextureArrayLengthFromPool(isSampler, arrayLength); + + return arrayLength; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs new file mode 100644 index 00000000..08cd3bb0 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs @@ -0,0 +1,459 @@ +using Ryujinx.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + /// + /// On-disk shader cache storage for guest code. + /// + class DiskCacheGuestStorage + { + private const uint TocMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'G' << 24); + + private const ushort VersionMajor = 1; + private const ushort VersionMinor = 1; + private const uint VersionPacked = ((uint)VersionMajor << 16) | VersionMinor; + + private const string TocFileName = "guest.toc"; + private const string DataFileName = "guest.data"; + + private readonly string _basePath; + + /// + /// TOC (Table of contents) file header. + /// + private struct TocHeader + { + /// + /// Magic value, for validation and identification purposes. + /// + public uint Magic; + + /// + /// File format version. + /// + public uint Version; + + /// + /// Header padding. + /// + public uint Padding; + + /// + /// Number of modifications to the file, also the shaders count. + /// + public uint ModificationsCount; + + /// + /// Reserved space, to be used in the future. Write as zero. + /// + public ulong Reserved; + + /// + /// Reserved space, to be used in the future. Write as zero. + /// + public ulong Reserved2; + } + + /// + /// TOC (Table of contents) file entry. + /// + private struct TocEntry + { + /// + /// Offset of the data on the data file. + /// + public uint Offset; + + /// + /// Code size. + /// + public uint CodeSize; + + /// + /// Constant buffer 1 data size. + /// + public uint Cb1DataSize; + + /// + /// Hash of the code and constant buffer data. + /// + public uint Hash; + } + + /// + /// TOC (Table of contents) memory cache entry. + /// + private struct TocMemoryEntry + { + /// + /// Offset of the data on the data file. + /// + public uint Offset; + + /// + /// Code size. + /// + public uint CodeSize; + + /// + /// Constant buffer 1 data size. + /// + public uint Cb1DataSize; + + /// + /// Index of the shader on the cache. + /// + public readonly int Index; + + /// + /// Creates a new TOC memory entry. + /// + /// Offset of the data on the data file + /// Code size + /// Constant buffer 1 data size + /// Index of the shader on the cache + public TocMemoryEntry(uint offset, uint codeSize, uint cb1DataSize, int index) + { + Offset = offset; + CodeSize = codeSize; + Cb1DataSize = cb1DataSize; + Index = index; + } + } + + private Dictionary> _toc; + private uint _tocModificationsCount; + + private (byte[], byte[])[] _cache; + + /// + /// Creates a new disk cache guest storage. + /// + /// Base path of the disk shader cache + public DiskCacheGuestStorage(string basePath) + { + _basePath = basePath; + } + + /// + /// Checks if the TOC (table of contents) file for the guest cache exists. + /// + /// True if the file exists, false otherwise + public bool TocFileExists() + { + return File.Exists(Path.Combine(_basePath, TocFileName)); + } + + /// + /// Checks if the data file for the guest cache exists. + /// + /// True if the file exists, false otherwise + public bool DataFileExists() + { + return File.Exists(Path.Combine(_basePath, DataFileName)); + } + + /// + /// Opens the guest cache TOC (table of contents) file. + /// + /// File stream + public Stream OpenTocFileStream() + { + return DiskCacheCommon.OpenFile(_basePath, TocFileName, writable: false); + } + + /// + /// Opens the guest cache data file. + /// + /// File stream + public Stream OpenDataFileStream() + { + return DiskCacheCommon.OpenFile(_basePath, DataFileName, writable: false); + } + + /// + /// Clear all content from the guest cache files. + /// + public void ClearCache() + { + using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, TocFileName, writable: true); + using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, DataFileName, writable: true); + + tocFileStream.SetLength(0); + dataFileStream.SetLength(0); + } + + /// + /// Loads the guest cache from file or memory cache. + /// + /// Guest TOC file stream + /// Guest data file stream + /// Guest shader index + /// Guest code and constant buffer 1 data + public GuestCodeAndCbData LoadShader(Stream tocFileStream, Stream dataFileStream, int index) + { + if (_cache == null || index >= _cache.Length) + { + _cache = new (byte[], byte[])[Math.Max(index + 1, GetShadersCountFromLength(tocFileStream.Length))]; + } + + (byte[] guestCode, byte[] cb1Data) = _cache[index]; + + if (guestCode == null || cb1Data == null) + { + BinarySerializer tocReader = new(tocFileStream); + tocFileStream.Seek(Unsafe.SizeOf() + index * Unsafe.SizeOf(), SeekOrigin.Begin); + + TocEntry entry = new(); + tocReader.Read(ref entry); + + guestCode = new byte[entry.CodeSize]; + cb1Data = new byte[entry.Cb1DataSize]; + + if (entry.Offset >= (ulong)dataFileStream.Length) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric); + } + + dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin); + dataFileStream.ReadExactly(cb1Data); + BinarySerializer.ReadCompressed(dataFileStream, guestCode); + + _cache[index] = (guestCode, cb1Data); + } + + return new GuestCodeAndCbData(guestCode, cb1Data); + } + + /// + /// Clears guest code memory cache, forcing future loads to be from file. + /// + public void ClearMemoryCache() + { + _cache = null; + } + + /// + /// Calculates the guest shaders count from the TOC file length. + /// + /// TOC file length + /// Shaders count + private static int GetShadersCountFromLength(long length) + { + return (int)((length - Unsafe.SizeOf()) / Unsafe.SizeOf()); + } + + /// + /// Adds a guest shader to the cache. + /// + /// + /// If the shader is already on the cache, the existing index will be returned and nothing will be written. + /// + /// Guest code + /// Constant buffer 1 data accessed by the code + /// Index of the shader on the cache + public int AddShader(ReadOnlySpan data, ReadOnlySpan cb1Data) + { + using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, TocFileName, writable: true); + using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, DataFileName, writable: true); + + TocHeader header = new(); + + LoadOrCreateToc(tocFileStream, ref header); + + uint hash = CalcHash(data, cb1Data); + + if (_toc.TryGetValue(hash, out var list)) + { + foreach (var entry in list) + { + if (data.Length != entry.CodeSize || cb1Data.Length != entry.Cb1DataSize) + { + continue; + } + + dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin); + byte[] cachedCode = new byte[entry.CodeSize]; + byte[] cachedCb1Data = new byte[entry.Cb1DataSize]; + dataFileStream.ReadExactly(cachedCb1Data); + BinarySerializer.ReadCompressed(dataFileStream, cachedCode); + + if (data.SequenceEqual(cachedCode) && cb1Data.SequenceEqual(cachedCb1Data)) + { + return entry.Index; + } + } + } + + return WriteNewEntry(tocFileStream, dataFileStream, ref header, data, cb1Data, hash); + } + + /// + /// Loads the guest cache TOC file, or create a new one if not present. + /// + /// Guest TOC file stream + /// Set to the TOC file header + private void LoadOrCreateToc(Stream tocFileStream, ref TocHeader header) + { + BinarySerializer reader = new(tocFileStream); + + if (!reader.TryRead(ref header) || header.Magic != TocMagic || header.Version != VersionPacked) + { + CreateToc(tocFileStream, ref header); + } + + if (_toc == null || header.ModificationsCount != _tocModificationsCount) + { + if (!LoadTocEntries(tocFileStream, ref reader)) + { + CreateToc(tocFileStream, ref header); + } + + _tocModificationsCount = header.ModificationsCount; + } + } + + /// + /// Creates a new guest cache TOC file. + /// + /// Guest TOC file stream + /// Set to the TOC header + private static void CreateToc(Stream tocFileStream, ref TocHeader header) + { + BinarySerializer writer = new(tocFileStream); + + header.Magic = TocMagic; + header.Version = VersionPacked; + header.Padding = 0; + header.ModificationsCount = 0; + header.Reserved = 0; + header.Reserved2 = 0; + + if (tocFileStream.Length > 0) + { + tocFileStream.Seek(0, SeekOrigin.Begin); + tocFileStream.SetLength(0); + } + + writer.Write(ref header); + } + + /// + /// Reads all the entries on the guest TOC file. + /// + /// Guest TOC file stream + /// TOC file reader + /// True if the operation was successful, false otherwise + private bool LoadTocEntries(Stream tocFileStream, ref BinarySerializer reader) + { + _toc = new Dictionary>(); + + TocEntry entry = new(); + int index = 0; + + while (tocFileStream.Position < tocFileStream.Length) + { + if (!reader.TryRead(ref entry)) + { + return false; + } + + AddTocMemoryEntry(entry.Offset, entry.CodeSize, entry.Cb1DataSize, entry.Hash, index++); + } + + return true; + } + + /// + /// Writes a new guest code entry into the file. + /// + /// TOC file stream + /// Data file stream + /// TOC header, to be updated with the new count + /// Guest code + /// Constant buffer 1 data accessed by the guest code + /// Code and constant buffer data hash + /// Entry index + private int WriteNewEntry( + Stream tocFileStream, + Stream dataFileStream, + ref TocHeader header, + ReadOnlySpan data, + ReadOnlySpan cb1Data, + uint hash) + { + BinarySerializer tocWriter = new(tocFileStream); + + dataFileStream.Seek(0, SeekOrigin.End); + uint dataOffset = checked((uint)dataFileStream.Position); + uint codeSize = (uint)data.Length; + uint cb1DataSize = (uint)cb1Data.Length; + dataFileStream.Write(cb1Data); + BinarySerializer.WriteCompressed(dataFileStream, data, DiskCacheCommon.GetCompressionAlgorithm()); + + _tocModificationsCount = ++header.ModificationsCount; + tocFileStream.Seek(0, SeekOrigin.Begin); + tocWriter.Write(ref header); + + TocEntry entry = new() + { + Offset = dataOffset, + CodeSize = codeSize, + Cb1DataSize = cb1DataSize, + Hash = hash, + }; + + tocFileStream.Seek(0, SeekOrigin.End); + int index = (int)((tocFileStream.Position - Unsafe.SizeOf()) / Unsafe.SizeOf()); + + tocWriter.Write(ref entry); + + AddTocMemoryEntry(dataOffset, codeSize, cb1DataSize, hash, index); + + return index; + } + + /// + /// Adds an entry to the memory TOC cache. This can be used to avoid reading the TOC file all the time. + /// + /// Offset of the code and constant buffer data in the data file + /// Code size + /// Constant buffer 1 data size + /// Code and constant buffer data hash + /// Index of the data on the cache + private void AddTocMemoryEntry(uint dataOffset, uint codeSize, uint cb1DataSize, uint hash, int index) + { + if (!_toc.TryGetValue(hash, out var list)) + { + _toc.Add(hash, list = new List()); + } + + list.Add(new TocMemoryEntry(dataOffset, codeSize, cb1DataSize, index)); + } + + /// + /// Calculates the hash for a data pair. + /// + /// Data 1 + /// Data 2 + /// Hash of both data + private static uint CalcHash(ReadOnlySpan data, ReadOnlySpan data2) + { + return CalcHash(data2) * 23 ^ CalcHash(data); + } + + /// + /// Calculates the hash for data. + /// + /// Data to be hashed + /// Hash of the data + private static uint CalcHash(ReadOnlySpan data) + { + return (uint)XXHash128.ComputeHash(data).Low; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs new file mode 100644 index 00000000..c1f59201 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -0,0 +1,870 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.IO; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + /// + /// On-disk shader cache storage for host code. + /// + class DiskCacheHostStorage + { + private const uint TocsMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'S' << 24); + private const uint TochMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'H' << 24); + private const uint ShdiMagic = (byte)'S' | ((byte)'H' << 8) | ((byte)'D' << 16) | ((byte)'I' << 24); + private const uint BufdMagic = (byte)'B' | ((byte)'U' << 8) | ((byte)'F' << 16) | ((byte)'D' << 24); + private const uint TexdMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'D' << 24); + + private const ushort FileFormatVersionMajor = 1; + private const ushort FileFormatVersionMinor = 2; + private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; + private const uint CodeGenVersion = 7131; + + private const string SharedTocFileName = "shared.toc"; + private const string SharedDataFileName = "shared.data"; + + private readonly string _basePath; + + public bool CacheEnabled => !string.IsNullOrEmpty(_basePath); + + /// + /// TOC (Table of contents) file header. + /// + private struct TocHeader + { + /// + /// Magic value, for validation and identification. + /// + public uint Magic; + + /// + /// File format version. + /// + public uint FormatVersion; + + /// + /// Generated shader code version. + /// + public uint CodeGenVersion; + + /// + /// Header padding. + /// + public uint Padding; + + /// + /// Timestamp of when the file was first created. + /// + public ulong Timestamp; + + /// + /// Reserved space, to be used in the future. Write as zero. + /// + public ulong Reserved; + } + + /// + /// Offset and size pair. + /// + private struct OffsetAndSize + { + /// + /// Offset. + /// + public ulong Offset; + + /// + /// Size of uncompressed data. + /// + public uint UncompressedSize; + + /// + /// Size of compressed data. + /// + public uint CompressedSize; + } + + /// + /// Per-stage data entry. + /// + private struct DataEntryPerStage + { + /// + /// Index of the guest code on the guest code cache TOC file. + /// + public int GuestCodeIndex; + } + + /// + /// Per-program data entry. + /// + private struct DataEntry + { + /// + /// Bit mask where each bit set is a used shader stage. Should be zero for compute shaders. + /// + public uint StagesBitMask; + } + + /// + /// Per-stage shader information, returned by the translator. + /// + private struct DataShaderInfo + { + /// + /// Total constant buffers used. + /// + public ushort CBuffersCount; + + /// + /// Total storage buffers used. + /// + public ushort SBuffersCount; + + /// + /// Total textures used. + /// + public ushort TexturesCount; + + /// + /// Total images used. + /// + public ushort ImagesCount; + + /// + /// Shader stage. + /// + public ShaderStage Stage; + + /// + /// Number of vertices that each output primitive has on a geometry shader. + /// + public byte GeometryVerticesPerPrimitive; + + /// + /// Maximum number of vertices that a geometry shader may generate. + /// + public ushort GeometryMaxOutputVertices; + + /// + /// Number of invocations per primitive on tessellation or geometry shaders. + /// + public ushort ThreadsPerInputPrimitive; + + /// + /// Indicates if the fragment shader accesses the fragment coordinate built-in variable. + /// + public bool UsesFragCoord; + + /// + /// Indicates if the shader accesses the Instance ID built-in variable. + /// + public bool UsesInstanceId; + + /// + /// Indicates if the shader modifies the Layer built-in variable. + /// + public bool UsesRtLayer; + + /// + /// Bit mask with the clip distances written on the vertex stage. + /// + public byte ClipDistancesWritten; + + /// + /// Bit mask of the render target components written by the fragment stage. + /// + public int FragmentOutputMap; + + /// + /// Indicates if the vertex shader accesses draw parameters. + /// + public bool UsesDrawParameters; + } + + private readonly DiskCacheGuestStorage _guestStorage; + + /// + /// Creates a disk cache host storage. + /// + /// Base path of the shader cache + public DiskCacheHostStorage(string basePath) + { + _basePath = basePath; + _guestStorage = new DiskCacheGuestStorage(basePath); + + if (CacheEnabled) + { + Directory.CreateDirectory(basePath); + } + } + + /// + /// Gets the total of host programs on the cache. + /// + /// Host programs count + public int GetProgramCount() + { + string tocFilePath = Path.Combine(_basePath, SharedTocFileName); + + if (!File.Exists(tocFilePath)) + { + return 0; + } + + return Math.Max((int)((new FileInfo(tocFilePath).Length - Unsafe.SizeOf()) / sizeof(ulong)), 0); + } + + /// + /// Guest the name of the host program cache file, with extension. + /// + /// GPU context + /// Name of the file, without extension + private static string GetHostFileName(GpuContext context) + { + string apiName = context.Capabilities.Api.ToString().ToLowerInvariant(); + string vendorName = RemoveInvalidCharacters(context.Capabilities.VendorName.ToLowerInvariant()); + return $"{apiName}_{vendorName}"; + } + + /// + /// Removes invalid path characters and spaces from a file name. + /// + /// File name + /// Filtered file name + private static string RemoveInvalidCharacters(string fileName) + { + int indexOfSpace = fileName.IndexOf(' '); + if (indexOfSpace >= 0) + { + fileName = fileName[..indexOfSpace]; + } + + return string.Concat(fileName.Split(Path.GetInvalidFileNameChars(), StringSplitOptions.RemoveEmptyEntries)); + } + + /// + /// Gets the name of the TOC host file. + /// + /// GPU context + /// File name + private static string GetHostTocFileName(GpuContext context) + { + return GetHostFileName(context) + ".toc"; + } + + /// + /// Gets the name of the data host file. + /// + /// GPU context + /// File name + private static string GetHostDataFileName(GpuContext context) + { + return GetHostFileName(context) + ".data"; + } + + /// + /// Checks if a disk cache exists for the current application. + /// + /// True if a disk cache exists, false otherwise + public bool CacheExists() + { + string tocFilePath = Path.Combine(_basePath, SharedTocFileName); + string dataFilePath = Path.Combine(_basePath, SharedDataFileName); + + if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath) || !_guestStorage.TocFileExists() || !_guestStorage.DataFileExists()) + { + return false; + } + + return true; + } + + /// + /// Loads all shaders from the cache. + /// + /// GPU context + /// Parallel disk cache loader + public void LoadShaders(GpuContext context, ParallelDiskCacheLoader loader) + { + if (!CacheExists()) + { + return; + } + + Stream hostTocFileStream = null; + Stream hostDataFileStream = null; + + try + { + using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: false); + using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: false); + + using var guestTocFileStream = _guestStorage.OpenTocFileStream(); + using var guestDataFileStream = _guestStorage.OpenDataFileStream(); + + BinarySerializer tocReader = new(tocFileStream); + BinarySerializer dataReader = new(dataFileStream); + + TocHeader header = new(); + + if (!tocReader.TryRead(ref header) || header.Magic != TocsMagic) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric); + } + + if (header.FormatVersion != FileFormatVersionPacked) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.IncompatibleVersion); + } + + bool loadHostCache = header.CodeGenVersion == CodeGenVersion; + + int programIndex = 0; + + DataEntry entry = new(); + + while (tocFileStream.Position < tocFileStream.Length && loader.Active) + { + ulong dataOffset = 0; + tocReader.Read(ref dataOffset); + + if ((ulong)dataOffset >= (ulong)dataFileStream.Length) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric); + } + + dataFileStream.Seek((long)dataOffset, SeekOrigin.Begin); + + dataReader.BeginCompression(); + dataReader.Read(ref entry); + uint stagesBitMask = entry.StagesBitMask; + + if ((stagesBitMask & ~0x3fu) != 0) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric); + } + + bool isCompute = stagesBitMask == 0; + if (isCompute) + { + stagesBitMask = 1; + } + + GuestCodeAndCbData?[] guestShaders = new GuestCodeAndCbData?[isCompute ? 1 : Constants.ShaderStages + 1]; + + DataEntryPerStage stageEntry = new(); + + while (stagesBitMask != 0) + { + int stageIndex = BitOperations.TrailingZeroCount(stagesBitMask); + + dataReader.Read(ref stageEntry); + + guestShaders[stageIndex] = _guestStorage.LoadShader( + guestTocFileStream, + guestDataFileStream, + stageEntry.GuestCodeIndex); + + stagesBitMask &= ~(1u << stageIndex); + } + + ShaderSpecializationState specState = ShaderSpecializationState.Read(ref dataReader); + dataReader.EndCompression(); + + if (loadHostCache) + { + (byte[] hostCode, CachedShaderStage[] shaders) = ReadHostCode( + context, + ref hostTocFileStream, + ref hostDataFileStream, + guestShaders, + programIndex, + header.Timestamp); + + if (hostCode != null) + { + ShaderInfo shaderInfo = ShaderInfoBuilder.BuildForCache( + context, + shaders, + specState.PipelineState, + specState.TransformFeedbackDescriptors != null); + + IProgram hostProgram; + + if (context.Capabilities.Api == TargetApi.Vulkan) + { + ShaderSource[] shaderSources = ShaderBinarySerializer.Unpack(shaders, hostCode); + + hostProgram = context.Renderer.CreateProgram(shaderSources, shaderInfo); + } + else + { + bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null; + + hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, shaderInfo); + } + + CachedShaderProgram program = new(hostProgram, specState, shaders); + + loader.QueueHostProgram(program, hostCode, programIndex, isCompute); + } + else + { + loadHostCache = false; + } + } + + if (!loadHostCache) + { + loader.QueueGuestProgram(guestShaders, specState, programIndex, isCompute); + } + + loader.CheckCompilation(); + programIndex++; + } + } + finally + { + _guestStorage.ClearMemoryCache(); + + hostTocFileStream?.Dispose(); + hostDataFileStream?.Dispose(); + } + } + + /// + /// Reads the host code for a given shader, if existent. + /// + /// GPU context + /// Host TOC file stream, intialized if needed + /// Host data file stream, initialized if needed + /// Guest shader code for each active stage + /// Index of the program on the cache + /// Timestamp of the shared cache file. The host file must be newer than it + /// Host binary code, or null if not found + private (byte[], CachedShaderStage[]) ReadHostCode( + GpuContext context, + ref Stream tocFileStream, + ref Stream dataFileStream, + GuestCodeAndCbData?[] guestShaders, + int programIndex, + ulong expectedTimestamp) + { + if (tocFileStream == null && dataFileStream == null) + { + string tocFilePath = Path.Combine(_basePath, GetHostTocFileName(context)); + string dataFilePath = Path.Combine(_basePath, GetHostDataFileName(context)); + + if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath)) + { + return (null, null); + } + + tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: false); + dataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: false); + + BinarySerializer tempTocReader = new(tocFileStream); + + TocHeader header = new(); + + tempTocReader.Read(ref header); + + if (header.Timestamp < expectedTimestamp) + { + return (null, null); + } + } + + int offset = Unsafe.SizeOf() + programIndex * Unsafe.SizeOf(); + if (offset + Unsafe.SizeOf() > tocFileStream.Length) + { + return (null, null); + } + + if ((ulong)offset >= (ulong)dataFileStream.Length) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric); + } + + tocFileStream.Seek(offset, SeekOrigin.Begin); + + BinarySerializer tocReader = new(tocFileStream); + + OffsetAndSize offsetAndSize = new(); + tocReader.Read(ref offsetAndSize); + + if (offsetAndSize.Offset >= (ulong)dataFileStream.Length) + { + throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric); + } + + dataFileStream.Seek((long)offsetAndSize.Offset, SeekOrigin.Begin); + + byte[] hostCode = new byte[offsetAndSize.UncompressedSize]; + + BinarySerializer.ReadCompressed(dataFileStream, hostCode); + + CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length]; + BinarySerializer dataReader = new(dataFileStream); + + dataFileStream.Seek((long)(offsetAndSize.Offset + offsetAndSize.CompressedSize), SeekOrigin.Begin); + + dataReader.BeginCompression(); + + for (int index = 0; index < guestShaders.Length; index++) + { + if (!guestShaders[index].HasValue) + { + continue; + } + + GuestCodeAndCbData guestShader = guestShaders[index].Value; + ShaderProgramInfo info = index != 0 || guestShaders.Length == 1 ? ReadShaderProgramInfo(ref dataReader) : null; + + shaders[index] = new CachedShaderStage(info, guestShader.Code, guestShader.Cb1Data); + } + + dataReader.EndCompression(); + + return (hostCode, shaders); + } + + /// + /// Gets output streams for the disk cache, for faster batch writing. + /// + /// The GPU context, used to determine the host disk cache + /// A collection of disk cache output streams + public DiskCacheOutputStreams GetOutputStreams(GpuContext context) + { + var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true); + var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true); + + var hostTocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true); + var hostDataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true); + + return new DiskCacheOutputStreams(tocFileStream, dataFileStream, hostTocFileStream, hostDataFileStream); + } + + /// + /// Adds a shader to the cache. + /// + /// GPU context + /// Cached program + /// Optional host binary code + /// Output streams to use + public void AddShader(GpuContext context, CachedShaderProgram program, ReadOnlySpan hostCode, DiskCacheOutputStreams streams = null) + { + uint stagesBitMask = 0; + + for (int index = 0; index < program.Shaders.Length; index++) + { + var shader = program.Shaders[index]; + if (shader == null || (shader.Info != null && shader.Info.Stage == ShaderStage.Compute)) + { + continue; + } + + stagesBitMask |= 1u << index; + } + + var tocFileStream = streams != null ? streams.TocFileStream : DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true); + var dataFileStream = streams != null ? streams.DataFileStream : DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true); + + ulong timestamp = (ulong)DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds; + + if (tocFileStream.Length == 0) + { + TocHeader header = new(); + CreateToc(tocFileStream, ref header, TocsMagic, CodeGenVersion, timestamp); + } + + tocFileStream.Seek(0, SeekOrigin.End); + dataFileStream.Seek(0, SeekOrigin.End); + + BinarySerializer tocWriter = new(tocFileStream); + BinarySerializer dataWriter = new(dataFileStream); + + ulong dataOffset = (ulong)dataFileStream.Position; + tocWriter.Write(ref dataOffset); + + DataEntry entry = new() + { + StagesBitMask = stagesBitMask, + }; + + dataWriter.BeginCompression(DiskCacheCommon.GetCompressionAlgorithm()); + dataWriter.Write(ref entry); + + DataEntryPerStage stageEntry = new(); + + for (int index = 0; index < program.Shaders.Length; index++) + { + var shader = program.Shaders[index]; + if (shader == null) + { + continue; + } + + stageEntry.GuestCodeIndex = _guestStorage.AddShader(shader.Code, shader.Cb1Data); + + dataWriter.Write(ref stageEntry); + } + + program.SpecializationState.Write(ref dataWriter); + dataWriter.EndCompression(); + + if (streams == null) + { + tocFileStream.Dispose(); + dataFileStream.Dispose(); + } + + if (hostCode.IsEmpty) + { + return; + } + + WriteHostCode(context, hostCode, program.Shaders, streams, timestamp); + } + + /// + /// Clears all content from the guest cache files. + /// + public void ClearGuestCache() + { + _guestStorage.ClearCache(); + } + + /// + /// Clears all content from the shared cache files. + /// + /// GPU context + public void ClearSharedCache() + { + using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true); + using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true); + + tocFileStream.SetLength(0); + dataFileStream.SetLength(0); + } + + /// + /// Deletes all content from the host cache files. + /// + /// GPU context + public void ClearHostCache(GpuContext context) + { + using var tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true); + using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true); + + tocFileStream.SetLength(0); + dataFileStream.SetLength(0); + } + + /// + /// Writes the host binary code on the host cache. + /// + /// GPU context + /// Host binary code + /// Shader stages to be added to the host cache + /// Output streams to use + /// File creation timestamp + private void WriteHostCode( + GpuContext context, + ReadOnlySpan hostCode, + CachedShaderStage[] shaders, + DiskCacheOutputStreams streams, + ulong timestamp) + { + var tocFileStream = streams != null ? streams.HostTocFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true); + var dataFileStream = streams != null ? streams.HostDataFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true); + + if (tocFileStream.Length == 0) + { + TocHeader header = new(); + CreateToc(tocFileStream, ref header, TochMagic, 0, timestamp); + } + + tocFileStream.Seek(0, SeekOrigin.End); + dataFileStream.Seek(0, SeekOrigin.End); + + BinarySerializer tocWriter = new(tocFileStream); + BinarySerializer dataWriter = new(dataFileStream); + + OffsetAndSize offsetAndSize = new() + { + Offset = (ulong)dataFileStream.Position, + UncompressedSize = (uint)hostCode.Length, + }; + + long dataStartPosition = dataFileStream.Position; + + BinarySerializer.WriteCompressed(dataFileStream, hostCode, DiskCacheCommon.GetCompressionAlgorithm()); + + offsetAndSize.CompressedSize = (uint)(dataFileStream.Position - dataStartPosition); + + tocWriter.Write(ref offsetAndSize); + + dataWriter.BeginCompression(DiskCacheCommon.GetCompressionAlgorithm()); + + for (int index = 0; index < shaders.Length; index++) + { + if (shaders[index] != null) + { + WriteShaderProgramInfo(ref dataWriter, shaders[index].Info); + } + } + + dataWriter.EndCompression(); + + if (streams == null) + { + tocFileStream.Dispose(); + dataFileStream.Dispose(); + } + } + + /// + /// Creates a TOC file for the host or shared cache. + /// + /// TOC file stream + /// Set to the TOC file header + /// Magic value to be written + /// Shader codegen version, only valid for the host file + /// File creation timestamp + private static void CreateToc(Stream tocFileStream, ref TocHeader header, uint magic, uint codegenVersion, ulong timestamp) + { + BinarySerializer writer = new(tocFileStream); + + header.Magic = magic; + header.FormatVersion = FileFormatVersionPacked; + header.CodeGenVersion = codegenVersion; + header.Padding = 0; + header.Reserved = 0; + header.Timestamp = timestamp; + + if (tocFileStream.Length > 0) + { + tocFileStream.Seek(0, SeekOrigin.Begin); + tocFileStream.SetLength(0); + } + + writer.Write(ref header); + } + + /// + /// Reads the shader program info from the cache. + /// + /// Cache data reader + /// Shader program info + private static ShaderProgramInfo ReadShaderProgramInfo(ref BinarySerializer dataReader) + { + DataShaderInfo dataInfo = new(); + + dataReader.ReadWithMagicAndSize(ref dataInfo, ShdiMagic); + + BufferDescriptor[] cBuffers = new BufferDescriptor[dataInfo.CBuffersCount]; + BufferDescriptor[] sBuffers = new BufferDescriptor[dataInfo.SBuffersCount]; + TextureDescriptor[] textures = new TextureDescriptor[dataInfo.TexturesCount]; + TextureDescriptor[] images = new TextureDescriptor[dataInfo.ImagesCount]; + + for (int index = 0; index < dataInfo.CBuffersCount; index++) + { + dataReader.ReadWithMagicAndSize(ref cBuffers[index], BufdMagic); + } + + for (int index = 0; index < dataInfo.SBuffersCount; index++) + { + dataReader.ReadWithMagicAndSize(ref sBuffers[index], BufdMagic); + } + + for (int index = 0; index < dataInfo.TexturesCount; index++) + { + dataReader.ReadWithMagicAndSize(ref textures[index], TexdMagic); + } + + for (int index = 0; index < dataInfo.ImagesCount; index++) + { + dataReader.ReadWithMagicAndSize(ref images[index], TexdMagic); + } + + return new ShaderProgramInfo( + cBuffers, + sBuffers, + textures, + images, + dataInfo.Stage, + dataInfo.GeometryVerticesPerPrimitive, + dataInfo.GeometryMaxOutputVertices, + dataInfo.ThreadsPerInputPrimitive, + dataInfo.UsesFragCoord, + dataInfo.UsesInstanceId, + dataInfo.UsesDrawParameters, + dataInfo.UsesRtLayer, + dataInfo.ClipDistancesWritten, + dataInfo.FragmentOutputMap); + } + + /// + /// Writes the shader program info into the cache. + /// + /// Cache data writer + /// Program info + private static void WriteShaderProgramInfo(ref BinarySerializer dataWriter, ShaderProgramInfo info) + { + if (info == null) + { + return; + } + + DataShaderInfo dataInfo = new() + { + CBuffersCount = (ushort)info.CBuffers.Count, + SBuffersCount = (ushort)info.SBuffers.Count, + TexturesCount = (ushort)info.Textures.Count, + ImagesCount = (ushort)info.Images.Count, + Stage = info.Stage, + GeometryVerticesPerPrimitive = (byte)info.GeometryVerticesPerPrimitive, + GeometryMaxOutputVertices = (ushort)info.GeometryMaxOutputVertices, + ThreadsPerInputPrimitive = (ushort)info.ThreadsPerInputPrimitive, + UsesFragCoord = info.UsesFragCoord, + UsesInstanceId = info.UsesInstanceId, + UsesDrawParameters = info.UsesDrawParameters, + UsesRtLayer = info.UsesRtLayer, + ClipDistancesWritten = info.ClipDistancesWritten, + FragmentOutputMap = info.FragmentOutputMap, + }; + + dataWriter.WriteWithMagicAndSize(ref dataInfo, ShdiMagic); + + for (int index = 0; index < info.CBuffers.Count; index++) + { + var entry = info.CBuffers[index]; + dataWriter.WriteWithMagicAndSize(ref entry, BufdMagic); + } + + for (int index = 0; index < info.SBuffers.Count; index++) + { + var entry = info.SBuffers[index]; + dataWriter.WriteWithMagicAndSize(ref entry, BufdMagic); + } + + for (int index = 0; index < info.Textures.Count; index++) + { + var entry = info.Textures[index]; + dataWriter.WriteWithMagicAndSize(ref entry, TexdMagic); + } + + for (int index = 0; index < info.Images.Count; index++) + { + var entry = info.Images[index]; + dataWriter.WriteWithMagicAndSize(ref entry, TexdMagic); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadException.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadException.cs new file mode 100644 index 00000000..9320638c --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadException.cs @@ -0,0 +1,48 @@ +using System; + +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + /// + /// Disk cache load exception. + /// + class DiskCacheLoadException : Exception + { + /// + /// Result of the cache load operation. + /// + public DiskCacheLoadResult Result { get; } + + /// + /// Creates a new instance of the disk cache load exception. + /// + public DiskCacheLoadException() + { + } + + /// + /// Creates a new instance of the disk cache load exception. + /// + /// Exception message + public DiskCacheLoadException(string message) : base(message) + { + } + + /// + /// Creates a new instance of the disk cache load exception. + /// + /// Exception message + /// Inner exception + public DiskCacheLoadException(string message, Exception inner) : base(message, inner) + { + } + + /// + /// Creates a new instance of the disk cache load exception. + /// + /// Result code + public DiskCacheLoadException(DiskCacheLoadResult result) : base(result.GetMessage()) + { + Result = result; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs new file mode 100644 index 00000000..d5abb9e5 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs @@ -0,0 +1,78 @@ +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + /// + /// Result of a shader cache load operation. + /// + enum DiskCacheLoadResult + { + /// + /// No error. + /// + Success, + + /// + /// File can't be accessed. + /// + NoAccess, + + /// + /// The constant buffer 1 data length is too low for the translation of the guest shader. + /// + InvalidCb1DataLength, + + /// + /// The cache is missing the length of a texture array used by the shader. + /// + MissingTextureArrayLength, + + /// + /// The cache is missing the descriptor of a texture used by the shader. + /// + MissingTextureDescriptor, + + /// + /// File is corrupted. + /// + FileCorruptedGeneric, + + /// + /// File is corrupted, detected by magic value check. + /// + FileCorruptedInvalidMagic, + + /// + /// File is corrupted, detected by length check. + /// + FileCorruptedInvalidLength, + + /// + /// File might be valid, but is incompatible with the current emulator version. + /// + IncompatibleVersion, + } + + static class DiskCacheLoadResultExtensions + { + /// + /// Gets an error message from a result code. + /// + /// Result code + /// Error message + public static string GetMessage(this DiskCacheLoadResult result) + { + return result switch + { + DiskCacheLoadResult.Success => "No error.", + DiskCacheLoadResult.NoAccess => "Could not access the cache file.", + DiskCacheLoadResult.InvalidCb1DataLength => "Constant buffer 1 data length is too low.", + DiskCacheLoadResult.MissingTextureArrayLength => "Texture array length missing from the cache file.", + DiskCacheLoadResult.MissingTextureDescriptor => "Texture descriptor missing from the cache file.", + DiskCacheLoadResult.FileCorruptedGeneric => "The cache file is corrupted.", + DiskCacheLoadResult.FileCorruptedInvalidMagic => "Magic check failed, the cache file is corrupted.", + DiskCacheLoadResult.FileCorruptedInvalidLength => "Length check failed, the cache file is corrupted.", + DiskCacheLoadResult.IncompatibleVersion => "The version of the disk cache is not compatible with this version of the emulator.", + _ => "Unknown error.", + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheOutputStreams.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheOutputStreams.cs new file mode 100644 index 00000000..a7ad5357 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheOutputStreams.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; + +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + /// + /// Output streams for the disk shader cache. + /// + class DiskCacheOutputStreams : IDisposable + { + /// + /// Shared table of contents (TOC) file stream. + /// + public readonly FileStream TocFileStream; + + /// + /// Shared data file stream. + /// + public readonly FileStream DataFileStream; + + /// + /// Host table of contents (TOC) file stream. + /// + public readonly FileStream HostTocFileStream; + + /// + /// Host data file stream. + /// + public readonly FileStream HostDataFileStream; + + /// + /// Creates a new instance of a disk cache output stream container. + /// + /// Stream for the shared table of contents file + /// Stream for the shared data file + /// Stream for the host table of contents file + /// Stream for the host data file + public DiskCacheOutputStreams(FileStream tocFileStream, FileStream dataFileStream, FileStream hostTocFileStream, FileStream hostDataFileStream) + { + TocFileStream = tocFileStream; + DataFileStream = dataFileStream; + HostTocFileStream = hostTocFileStream; + HostDataFileStream = hostDataFileStream; + } + + /// + /// Disposes the output file streams. + /// + public void Dispose() + { + TocFileStream.Dispose(); + DataFileStream.Dispose(); + HostTocFileStream.Dispose(); + HostDataFileStream.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs new file mode 100644 index 00000000..f412c62e --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs @@ -0,0 +1,29 @@ +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + /// + /// Guest shader code and constant buffer data accessed by the shader. + /// + readonly struct GuestCodeAndCbData + { + /// + /// Maxwell binary shader code. + /// + public byte[] Code { get; } + + /// + /// Constant buffer 1 data accessed by the shader. + /// + public byte[] Cb1Data { get; } + + /// + /// Creates a new instance of the guest shader code and constant buffer data. + /// + /// Maxwell binary shader code + /// Constant buffer 1 data accessed by the shader + public GuestCodeAndCbData(byte[] code, byte[] cb1Data) + { + Code = code; + Cb1Data = cb1Data; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs new file mode 100644 index 00000000..20f96462 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs @@ -0,0 +1,737 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using static Ryujinx.Graphics.Gpu.Shader.ShaderCache; + +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + class ParallelDiskCacheLoader + { + private const int ThreadCount = 8; + + private readonly GpuContext _context; + private readonly ShaderCacheHashTable _graphicsCache; + private readonly ComputeShaderCacheHashTable _computeCache; + private readonly DiskCacheHostStorage _hostStorage; + private readonly CancellationToken _cancellationToken; + private readonly Action _stateChangeCallback; + + /// + /// Indicates if the cache should be loaded. + /// + public bool Active => !_cancellationToken.IsCancellationRequested; + + private bool _needsHostRegen; + + /// + /// Number of shaders that failed to compile from the cache. + /// + public int ErrorCount { get; private set; } + + /// + /// Program validation entry. + /// + private readonly struct ProgramEntry + { + /// + /// Cached shader program. + /// + public readonly CachedShaderProgram CachedProgram; + + /// + /// Optional binary code. If not null, it is used instead of the backend host binary. + /// + public readonly byte[] BinaryCode; + + /// + /// Program index. + /// + public readonly int ProgramIndex; + + /// + /// Indicates if the program is a compute shader. + /// + public readonly bool IsCompute; + + /// + /// Indicates if the program is a host binary shader. + /// + public readonly bool IsBinary; + + /// + /// Creates a new program validation entry. + /// + /// Cached shader program + /// Optional binary code. If not null, it is used instead of the backend host binary + /// Program index + /// Indicates if the program is a compute shader + /// Indicates if the program is a host binary shader + public ProgramEntry( + CachedShaderProgram cachedProgram, + byte[] binaryCode, + int programIndex, + bool isCompute, + bool isBinary) + { + CachedProgram = cachedProgram; + BinaryCode = binaryCode; + ProgramIndex = programIndex; + IsCompute = isCompute; + IsBinary = isBinary; + } + } + + /// + /// Translated shader compilation entry. + /// + private readonly struct ProgramCompilation + { + /// + /// Translated shader stages. + /// + public readonly ShaderProgram[] TranslatedStages; + + /// + /// Cached shaders. + /// + public readonly CachedShaderStage[] Shaders; + + /// + /// Specialization state. + /// + public readonly ShaderSpecializationState SpecializationState; + + /// + /// Program index. + /// + public readonly int ProgramIndex; + + /// + /// Indicates if the program is a compute shader. + /// + public readonly bool IsCompute; + + /// + /// Creates a new translated shader compilation entry. + /// + /// Translated shader stages + /// Cached shaders + /// Specialization state + /// Program index + /// Indicates if the program is a compute shader + public ProgramCompilation( + ShaderProgram[] translatedStages, + CachedShaderStage[] shaders, + ShaderSpecializationState specState, + int programIndex, + bool isCompute) + { + TranslatedStages = translatedStages; + Shaders = shaders; + SpecializationState = specState; + ProgramIndex = programIndex; + IsCompute = isCompute; + } + } + + /// + /// Program translation entry. + /// + private readonly struct AsyncProgramTranslation + { + /// + /// Guest code for each active stage. + /// + public readonly GuestCodeAndCbData?[] GuestShaders; + + /// + /// Specialization state. + /// + public readonly ShaderSpecializationState SpecializationState; + + /// + /// Program index. + /// + public readonly int ProgramIndex; + + /// + /// Indicates if the program is a compute shader. + /// + public readonly bool IsCompute; + + /// + /// Creates a new program translation entry. + /// + /// Guest code for each active stage + /// Specialization state + /// Program index + /// Indicates if the program is a compute shader + public AsyncProgramTranslation( + GuestCodeAndCbData?[] guestShaders, + ShaderSpecializationState specState, + int programIndex, + bool isCompute) + { + GuestShaders = guestShaders; + SpecializationState = specState; + ProgramIndex = programIndex; + IsCompute = isCompute; + } + } + + private readonly Queue _validationQueue; + private readonly ConcurrentQueue _compilationQueue; + private readonly BlockingCollection _asyncTranslationQueue; + private readonly SortedList _programList; + + private readonly int _backendParallelCompileThreads; + private int _compiledCount; + private int _totalCount; + + /// + /// Creates a new parallel disk cache loader. + /// + /// GPU context + /// Graphics shader cache + /// Compute shader cache + /// Disk cache host storage + /// Function to be called when there is a state change, reporting state, compiled and total shaders count + /// Cancellation token + public ParallelDiskCacheLoader(GpuContext context, + ShaderCacheHashTable graphicsCache, + ComputeShaderCacheHashTable computeCache, + DiskCacheHostStorage hostStorage, + Action stateChangeCallback, + CancellationToken cancellationToken) + { + _context = context; + _graphicsCache = graphicsCache; + _computeCache = computeCache; + _hostStorage = hostStorage; + _stateChangeCallback = stateChangeCallback; + _cancellationToken = cancellationToken; + _validationQueue = new Queue(); + _compilationQueue = new ConcurrentQueue(); + _asyncTranslationQueue = new BlockingCollection(ThreadCount); + _programList = new SortedList(); + _backendParallelCompileThreads = Math.Min(Environment.ProcessorCount, 8); // Must be kept in sync with the backend code. + } + + /// + /// Loads all shaders from the cache. + /// + public void LoadShaders() + { + Thread[] workThreads = new Thread[ThreadCount]; + + for (int index = 0; index < ThreadCount; index++) + { + workThreads[index] = new Thread(ProcessAsyncQueue) + { + Name = $"GPU.AsyncTranslationThread.{index}", + }; + } + + int programCount = _hostStorage.GetProgramCount(); + + _compiledCount = 0; + _totalCount = programCount; + + _stateChangeCallback(ShaderCacheState.Start, 0, programCount); + + Logger.Info?.Print(LogClass.Gpu, $"Loading {programCount} shaders from the cache..."); + + for (int index = 0; index < ThreadCount; index++) + { + workThreads[index].Start(_cancellationToken); + } + + try + { + _hostStorage.LoadShaders(_context, this); + } + catch (DiskCacheLoadException diskCacheLoadException) + { + Logger.Warning?.Print(LogClass.Gpu, $"Error loading the shader cache. {diskCacheLoadException.Message}"); + + // If we can't even access the file, then we also can't rebuild. + if (diskCacheLoadException.Result != DiskCacheLoadResult.NoAccess) + { + _needsHostRegen = true; + } + } + catch (InvalidDataException invalidDataException) + { + Logger.Warning?.Print(LogClass.Gpu, $"Error decompressing the shader cache file. {invalidDataException.Message}"); + _needsHostRegen = true; + } + catch (IOException ioException) + { + Logger.Warning?.Print(LogClass.Gpu, $"Error reading the shader cache file. {ioException.Message}"); + _needsHostRegen = true; + } + + _asyncTranslationQueue.CompleteAdding(); + + for (int index = 0; index < ThreadCount; index++) + { + workThreads[index].Join(); + } + + CheckCompilationBlocking(); + + if (_needsHostRegen && Active) + { + // Rebuild both shared and host cache files. + // Rebuilding shared is required because the shader information returned by the translator + // might have changed, and so we have to reconstruct the file with the new information. + try + { + _hostStorage.ClearSharedCache(); + _hostStorage.ClearHostCache(_context); + + if (_programList.Count != 0) + { + _stateChangeCallback(ShaderCacheState.Packaging, 0, _programList.Count); + + Logger.Info?.Print(LogClass.Gpu, $"Rebuilding {_programList.Count} shaders..."); + + using var streams = _hostStorage.GetOutputStreams(_context); + + int packagedShaders = 0; + foreach (var kv in _programList) + { + if (!Active) + { + break; + } + + (CachedShaderProgram program, byte[] binaryCode) = kv.Value; + + _hostStorage.AddShader(_context, program, binaryCode, streams); + + _stateChangeCallback(ShaderCacheState.Packaging, ++packagedShaders, _programList.Count); + } + + Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully."); + } + else + { + _hostStorage.ClearGuestCache(); + + Logger.Info?.Print(LogClass.Gpu, "Shader cache deleted due to corruption."); + } + } + catch (DiskCacheLoadException diskCacheLoadException) + { + Logger.Warning?.Print(LogClass.Gpu, $"Error deleting the shader cache. {diskCacheLoadException.Message}"); + } + catch (IOException ioException) + { + Logger.Warning?.Print(LogClass.Gpu, $"Error deleting the shader cache file. {ioException.Message}"); + } + } + + Logger.Info?.Print(LogClass.Gpu, "Shader cache loaded."); + + _stateChangeCallback(ShaderCacheState.Loaded, programCount, programCount); + } + + /// + /// Enqueues a host program for compilation. + /// + /// Cached program + /// Host binary code + /// Program index + /// Indicates if the program is a compute shader + public void QueueHostProgram(CachedShaderProgram cachedProgram, byte[] binaryCode, int programIndex, bool isCompute) + { + EnqueueForValidation(new ProgramEntry(cachedProgram, binaryCode, programIndex, isCompute, isBinary: true)); + } + + /// + /// Enqueues a guest program for compilation. + /// + /// Guest code for each active stage + /// Specialization state + /// Program index + /// Indicates if the program is a compute shader + public void QueueGuestProgram(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex, bool isCompute) + { + try + { + AsyncProgramTranslation asyncTranslation = new(guestShaders, specState, programIndex, isCompute); + _asyncTranslationQueue.Add(asyncTranslation, _cancellationToken); + } + catch (OperationCanceledException) + { + } + } + + /// + /// Check the state of programs that have already been compiled, + /// and add to the cache if the compilation was successful. + /// + public void CheckCompilation() + { + ProcessCompilationQueue(); + + // Process programs that already finished compiling. + // If not yet compiled, do nothing. This avoids blocking to wait for shader compilation. + while (_validationQueue.TryPeek(out ProgramEntry entry)) + { + ProgramLinkStatus result = entry.CachedProgram.HostProgram.CheckProgramLink(false); + + if (result != ProgramLinkStatus.Incomplete) + { + ProcessCompiledProgram(ref entry, result); + _validationQueue.Dequeue(); + } + else + { + break; + } + } + } + + /// + /// Waits until all programs finishes compiling, then adds the ones + /// with successful compilation to the cache. + /// + private void CheckCompilationBlocking() + { + ProcessCompilationQueue(); + + while (_validationQueue.TryDequeue(out ProgramEntry entry) && Active) + { + ProcessCompiledProgram(ref entry, entry.CachedProgram.HostProgram.CheckProgramLink(true), asyncCompile: false); + } + } + + /// + /// Process a compiled program result. + /// + /// Compiled program entry + /// Compilation result + /// For failed host compilations, indicates if a guest compilation should be done asynchronously + private void ProcessCompiledProgram(ref ProgramEntry entry, ProgramLinkStatus result, bool asyncCompile = true) + { + if (result == ProgramLinkStatus.Success) + { + // Compilation successful, add to memory cache. + if (entry.IsCompute) + { + _computeCache.Add(entry.CachedProgram); + } + else + { + _graphicsCache.Add(entry.CachedProgram); + } + + if (!entry.IsBinary) + { + _needsHostRegen = true; + } + + // Fetch the binary code from the backend if it isn't already present. + byte[] binaryCode = entry.BinaryCode ?? entry.CachedProgram.HostProgram.GetBinary(); + + _programList.Add(entry.ProgramIndex, (entry.CachedProgram, binaryCode)); + SignalCompiled(); + } + else if (entry.IsBinary) + { + // If this is a host binary and compilation failed, + // we still have a chance to recompile from the guest binary. + CachedShaderProgram program = entry.CachedProgram; + + GuestCodeAndCbData?[] guestShaders = new GuestCodeAndCbData?[program.Shaders.Length]; + + for (int index = 0; index < program.Shaders.Length; index++) + { + CachedShaderStage shader = program.Shaders[index]; + + if (shader != null) + { + guestShaders[index] = new GuestCodeAndCbData(shader.Code, shader.Cb1Data); + } + } + + if (asyncCompile) + { + QueueGuestProgram(guestShaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute); + } + else + { + RecompileFromGuestCode(guestShaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute); + ProcessCompilationQueue(); + } + } + else + { + // Failed to compile from both host and guest binary. + ErrorCount++; + SignalCompiled(); + } + } + + /// + /// Processes the queue of translated guest programs that should be compiled on the host. + /// + private void ProcessCompilationQueue() + { + while (_compilationQueue.TryDequeue(out ProgramCompilation compilation) && Active) + { + ShaderSource[] shaderSources = new ShaderSource[compilation.TranslatedStages.Length]; + + ShaderInfoBuilder shaderInfoBuilder = new(_context, compilation.SpecializationState.TransformFeedbackDescriptors != null); + + for (int index = 0; index < compilation.TranslatedStages.Length; index++) + { + ShaderProgram shader = compilation.TranslatedStages[index]; + shaderSources[index] = CreateShaderSource(shader); + shaderInfoBuilder.AddStageInfo(shader.Info); + } + + ShaderInfo shaderInfo = shaderInfoBuilder.Build(compilation.SpecializationState.PipelineState, fromCache: true); + IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, shaderInfo); + CachedShaderProgram program = new(hostProgram, compilation.SpecializationState, compilation.Shaders); + + // Vulkan's binary code is the SPIR-V used for compilation, so it is ready immediately. Other APIs get this after compilation. + byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(shaderSources) : null; + + EnqueueForValidation(new ProgramEntry(program, binaryCode, compilation.ProgramIndex, compilation.IsCompute, isBinary: false)); + } + } + + /// + /// Enqueues a program for validation, which will check if the program was compiled successfully. + /// + /// Program entry to be validated + private void EnqueueForValidation(ProgramEntry newEntry) + { + _validationQueue.Enqueue(newEntry); + + // Do not allow more than N shader compilation in-flight, where N is the maximum number of threads + // the driver will be using for parallel compilation. + // Submitting more seems to cause NVIDIA OpenGL driver to crash. + if (_validationQueue.Count >= _backendParallelCompileThreads && _validationQueue.TryDequeue(out ProgramEntry entry)) + { + ProcessCompiledProgram(ref entry, entry.CachedProgram.HostProgram.CheckProgramLink(true), asyncCompile: false); + } + } + + /// + /// Processses the queue of programs that should be translated from guest code. + /// + /// Cancellation token + private void ProcessAsyncQueue(object state) + { + CancellationToken ct = (CancellationToken)state; + + try + { + foreach (AsyncProgramTranslation asyncCompilation in _asyncTranslationQueue.GetConsumingEnumerable(ct)) + { + RecompileFromGuestCode( + asyncCompilation.GuestShaders, + asyncCompilation.SpecializationState, + asyncCompilation.ProgramIndex, + asyncCompilation.IsCompute); + } + } + catch (OperationCanceledException) + { + } + } + + /// + /// Recompiles a program from guest code. + /// + /// Guest code for each active stage + /// Specialization state + /// Program index + /// Indicates if the program is a compute shader + private void RecompileFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex, bool isCompute) + { + try + { + if (isCompute) + { + RecompileComputeFromGuestCode(guestShaders, specState, programIndex); + } + else + { + RecompileGraphicsFromGuestCode(guestShaders, specState, programIndex); + } + } + catch (Exception exception) + { + Logger.Error?.Print(LogClass.Gpu, $"Error translating guest shader. {exception.Message}"); + + ErrorCount++; + SignalCompiled(); + } + } + + /// + /// Recompiles a graphics program from guest code. + /// + /// Guest code for each active stage + /// Specialization state + /// Program index + private void RecompileGraphicsFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex) + { + ShaderSpecializationState newSpecState = new( + ref specState.GraphicsState, + specState.PipelineState, + specState.TransformFeedbackDescriptors); + + ResourceCounts counts = new(); + + DiskCacheGpuAccessor[] gpuAccessors = new DiskCacheGpuAccessor[Constants.ShaderStages]; + TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1]; + TranslatorContext nextStage = null; + + TargetApi api = _context.Capabilities.Api; + + bool hasCachedGs = guestShaders[4].HasValue; + + for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--) + { + if (guestShaders[stageIndex + 1].HasValue) + { + GuestCodeAndCbData shader = guestShaders[stageIndex + 1].Value; + + byte[] guestCode = shader.Code; + byte[] cb1Data = shader.Cb1Data; + + DiskCacheGpuAccessor gpuAccessor = new(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex, hasCachedGs); + TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, 0); + + if (nextStage != null) + { + currentStage.SetNextStage(nextStage); + } + + if (stageIndex == 0 && guestShaders[0].HasValue) + { + byte[] guestCodeA = guestShaders[0].Value.Code; + byte[] cb1DataA = guestShaders[0].Value.Cb1Data; + + DiskCacheGpuAccessor gpuAccessorA = new(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0, hasCachedGs); + translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0); + } + + gpuAccessors[stageIndex] = gpuAccessor; + translatorContexts[stageIndex + 1] = currentStage; + nextStage = currentStage; + } + } + + bool hasGeometryShader = translatorContexts[4] != null; + bool vertexHasStore = translatorContexts[1] != null && translatorContexts[1].HasStore; + bool geometryHasStore = hasGeometryShader && translatorContexts[4].HasStore; + bool vertexToCompute = ShouldConvertVertexToCompute(_context, vertexHasStore, geometryHasStore, hasGeometryShader); + + // We don't support caching shader stages that have been converted to compute currently, + // so just eliminate them if they exist in the cache. + if (vertexToCompute) + { + return; + } + + CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length]; + List translatedStages = new(); + + TranslatorContext previousStage = null; + + for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++) + { + TranslatorContext currentStage = translatorContexts[stageIndex + 1]; + + if (currentStage != null) + { + gpuAccessors[stageIndex].InitializeReservedCounts(specState.TransformFeedbackDescriptors != null, vertexToCompute); + + ShaderProgram program; + + byte[] guestCode = guestShaders[stageIndex + 1].Value.Code; + byte[] cb1Data = guestShaders[stageIndex + 1].Value.Cb1Data; + + if (stageIndex == 0 && guestShaders[0].HasValue) + { + program = currentStage.Translate(translatorContexts[0]); + + byte[] guestCodeA = guestShaders[0].Value.Code; + byte[] cb1DataA = guestShaders[0].Value.Cb1Data; + + shaders[0] = new CachedShaderStage(null, guestCodeA, cb1DataA); + shaders[1] = new CachedShaderStage(program.Info, guestCode, cb1Data); + } + else + { + program = currentStage.Translate(); + + shaders[stageIndex + 1] = new CachedShaderStage(program.Info, guestCode, cb1Data); + } + + if (program != null) + { + translatedStages.Add(program); + } + + previousStage = currentStage; + } + else if ( + previousStage != null && + previousStage.LayerOutputWritten && + stageIndex == 3 && + !_context.Capabilities.SupportsLayerVertexTessellation) + { + translatedStages.Add(previousStage.GenerateGeometryPassthrough()); + } + } + + _compilationQueue.Enqueue(new ProgramCompilation(translatedStages.ToArray(), shaders, newSpecState, programIndex, isCompute: false)); + } + + /// + /// Recompiles a compute program from guest code. + /// + /// Guest code for each active stage + /// Specialization state + /// Program index + private void RecompileComputeFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex) + { + GuestCodeAndCbData shader = guestShaders[0].Value; + ResourceCounts counts = new(); + ShaderSpecializationState newSpecState = new(ref specState.ComputeState); + DiskCacheGpuAccessor gpuAccessor = new(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0, false); + gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false); + + TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0); + + ShaderProgram program = translatorContext.Translate(); + + CachedShaderStage[] shaders = new[] { new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data) }; + + _compilationQueue.Enqueue(new ProgramCompilation(new[] { program }, shaders, newSpecState, programIndex, isCompute: true)); + } + + /// + /// Signals that compilation of a program has been finished successfully, + /// or that it failed and guest recompilation has also been attempted. + /// + private void SignalCompiled() + { + _stateChangeCallback(ShaderCacheState.Loading, ++_compiledCount, _totalCount); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs new file mode 100644 index 00000000..a18b5780 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs @@ -0,0 +1,50 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + static class ShaderBinarySerializer + { + public static byte[] Pack(ShaderSource[] sources) + { + using MemoryStream output = MemoryStreamManager.Shared.GetStream(); + + output.Write(sources.Length); + + foreach (ShaderSource source in sources) + { + output.Write((int)source.Stage); + output.Write(source.BinaryCode.Length); + output.Write(source.BinaryCode); + } + + return output.ToArray(); + } + + public static ShaderSource[] Unpack(CachedShaderStage[] stages, byte[] code) + { + using MemoryStream input = new(code); + using BinaryReader reader = new(input); + + List output = new(); + + int count = reader.ReadInt32(); + + for (int i = 0; i < count; i++) + { + ShaderStage stage = (ShaderStage)reader.ReadInt32(); + int binaryCodeLength = reader.ReadInt32(); + byte[] binaryCode = reader.ReadBytes(binaryCodeLength); + + output.Add(new ShaderSource(binaryCode, stage, TargetLanguage.Spirv)); + } + + return output.ToArray(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs new file mode 100644 index 00000000..1be75f24 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -0,0 +1,244 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Represents a GPU state and memory accessor. + /// + class GpuAccessor : GpuAccessorBase, IGpuAccessor + { + private readonly GpuChannel _channel; + private readonly GpuAccessorState _state; + private readonly int _stageIndex; + private readonly bool _compute; + private readonly bool _isVulkan; + private readonly bool _hasGeometryShader; + private readonly bool _supportsQuads; + + /// + /// Creates a new instance of the GPU state accessor for graphics shader translation. + /// + /// GPU context + /// GPU channel + /// Current GPU state + /// Graphics shader stage index (0 = Vertex, 4 = Fragment) + /// Indicates if a geometry shader is present + public GpuAccessor( + GpuContext context, + GpuChannel channel, + GpuAccessorState state, + int stageIndex, + bool hasGeometryShader) : base(context, state.ResourceCounts, stageIndex) + { + _channel = channel; + _state = state; + _stageIndex = stageIndex; + _isVulkan = context.Capabilities.Api == TargetApi.Vulkan; + _hasGeometryShader = hasGeometryShader; + _supportsQuads = context.Capabilities.SupportsQuads; + + if (stageIndex == (int)ShaderStage.Geometry - 1) + { + // Only geometry shaders require the primitive topology. + _state.SpecializationState.RecordPrimitiveTopology(); + } + } + + /// + /// Creates a new instance of the GPU state accessor for compute shader translation. + /// + /// GPU context + /// GPU channel + /// Current GPU state + public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0) + { + _channel = channel; + _state = state; + _compute = true; + } + + /// + public uint ConstantBuffer1Read(int offset) + { + ulong baseAddress = _compute + ? _channel.BufferManager.GetComputeUniformBufferAddress(1) + : _channel.BufferManager.GetGraphicsUniformBufferAddress(_stageIndex, 1); + + return _channel.MemoryManager.Physical.Read(baseAddress + (ulong)offset); + } + + /// + public void Log(string message) + { + Logger.Warning?.Print(LogClass.Gpu, $"Shader translator: {message}"); + } + + /// + public ReadOnlySpan GetCode(ulong address, int minimumSize) + { + int size = Math.Max(minimumSize, 0x1000 - (int)(address & 0xfff)); + + return MemoryMarshal.Cast(_channel.MemoryManager.GetSpan(address, size)); + } + + /// + public int QueryComputeLocalSizeX() => _state.ComputeState.LocalSizeX; + + /// + public int QueryComputeLocalSizeY() => _state.ComputeState.LocalSizeY; + + /// + public int QueryComputeLocalSizeZ() => _state.ComputeState.LocalSizeZ; + + /// + public int QueryComputeLocalMemorySize() => _state.ComputeState.LocalMemorySize; + + /// + public int QueryComputeSharedMemorySize() => _state.ComputeState.SharedMemorySize; + + /// + public uint QueryConstantBufferUse() + { + uint useMask = _compute + ? _channel.BufferManager.GetComputeUniformBufferUseMask() + : _channel.BufferManager.GetGraphicsUniformBufferUseMask(_stageIndex); + + _state.SpecializationState?.RecordConstantBufferUse(_stageIndex, useMask); + return useMask; + } + + /// + public GpuGraphicsState QueryGraphicsState() + { + return _state.GraphicsState.CreateShaderGraphicsState( + !_isVulkan, + _supportsQuads, + _hasGeometryShader, + _isVulkan || _state.GraphicsState.YNegateEnabled); + } + + /// + public bool QueryHasConstantBufferDrawParameters() + { + return _state.GraphicsState.HasConstantBufferDrawParameters; + } + + /// + public bool QueryHasUnalignedStorageBuffer() + { + return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer; + } + + /// + public int QuerySamplerArrayLengthFromPool() + { + int length = _state.SamplerPoolMaximumId + 1; + _state.SpecializationState?.RegisterTextureArrayLengthFromPool(isSampler: true, length); + + return length; + } + + /// + public SamplerType QuerySamplerType(int handle, int cbufSlot) + { + _state.SpecializationState?.RecordTextureSamplerType(_stageIndex, handle, cbufSlot); + return GetTextureDescriptor(handle, cbufSlot).UnpackTextureTarget().ConvertSamplerType(); + } + + /// + public int QueryTextureArrayLengthFromBuffer(int slot) + { + int size = _compute + ? _channel.BufferManager.GetComputeUniformBufferSize(slot) + : _channel.BufferManager.GetGraphicsUniformBufferSize(_stageIndex, slot); + + int arrayLength = size / Constants.TextureHandleSizeInBytes; + + _state.SpecializationState?.RegisterTextureArrayLengthFromBuffer(_stageIndex, 0, slot, arrayLength); + + return arrayLength; + } + + /// + public int QueryTextureArrayLengthFromPool() + { + int length = _state.PoolState.TexturePoolMaximumId + 1; + _state.SpecializationState?.RegisterTextureArrayLengthFromPool(isSampler: false, length); + + return length; + } + + //// + public TextureFormat QueryTextureFormat(int handle, int cbufSlot) + { + _state.SpecializationState?.RecordTextureFormat(_stageIndex, handle, cbufSlot); + var descriptor = GetTextureDescriptor(handle, cbufSlot); + return ConvertToTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb()); + } + + /// + public bool QueryTextureCoordNormalized(int handle, int cbufSlot) + { + _state.SpecializationState?.RecordTextureCoordNormalized(_stageIndex, handle, cbufSlot); + return GetTextureDescriptor(handle, cbufSlot).UnpackTextureCoordNormalized(); + } + + /// + /// Gets the texture descriptor for a given texture on the pool. + /// + /// Index of the texture (this is the word offset of the handle in the constant buffer) + /// Constant buffer slot for the texture handle + /// Texture descriptor + private Image.TextureDescriptor GetTextureDescriptor(int handle, int cbufSlot) + { + if (_compute) + { + return _channel.TextureManager.GetComputeTextureDescriptor( + _state.PoolState.TexturePoolGpuVa, + _state.PoolState.TextureBufferIndex, + _state.PoolState.TexturePoolMaximumId, + handle, + cbufSlot); + } + else + { + return _channel.TextureManager.GetGraphicsTextureDescriptor( + _state.PoolState.TexturePoolGpuVa, + _state.PoolState.TextureBufferIndex, + _state.PoolState.TexturePoolMaximumId, + _stageIndex, + handle, + cbufSlot); + } + } + + /// + public bool QueryTransformFeedbackEnabled() + { + return _state.TransformFeedbackDescriptors != null; + } + + /// + public ReadOnlySpan QueryTransformFeedbackVaryingLocations(int bufferIndex) + { + return _state.TransformFeedbackDescriptors[bufferIndex].AsSpan(); + } + + /// + public int QueryTransformFeedbackStride(int bufferIndex) + { + return _state.TransformFeedbackDescriptors[bufferIndex].Stride; + } + + /// + public void RegisterTexture(int handle, int cbufSlot) + { + _state.SpecializationState?.RegisterTexture(_stageIndex, handle, cbufSlot, GetTextureDescriptor(handle, cbufSlot)); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs new file mode 100644 index 00000000..d89eebab --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -0,0 +1,309 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// GPU accessor. + /// + class GpuAccessorBase + { + private readonly GpuContext _context; + private readonly ResourceCounts _resourceCounts; + private readonly int _stageIndex; + + private int _reservedConstantBuffers; + private int _reservedStorageBuffers; + private int _reservedTextures; + private int _reservedImages; + + private int _staticTexturesCount; + private int _staticImagesCount; + + /// + /// Creates a new GPU accessor. + /// + /// GPU context + /// Counter of GPU resources used by the shader + /// Index of the shader stage, 0 for compute + public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex) + { + _context = context; + _resourceCounts = resourceCounts; + _stageIndex = stageIndex; + } + + /// + /// Initializes counts for bindings that will be reserved for emulator use. + /// + /// Indicates if the current graphics shader is used with transform feedback enabled + /// Indicates that the vertex shader will be emulated on a compute shader + public void InitializeReservedCounts(bool tfEnabled, bool vertexAsCompute) + { + ResourceReservationCounts rrc = new(!_context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute); + + _reservedConstantBuffers = rrc.ReservedConstantBuffers; + _reservedStorageBuffers = rrc.ReservedStorageBuffers; + _reservedTextures = rrc.ReservedTextures; + _reservedImages = rrc.ReservedImages; + } + + public SetBindingPair CreateConstantBufferBinding(int index) + { + int binding; + + if (_context.Capabilities.Api == TargetApi.Vulkan) + { + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumUniformBuffersPerStage, "Uniform buffer"); + } + else + { + binding = _resourceCounts.UniformBuffersCount++; + } + + return new SetBindingPair(_context.Capabilities.UniformBufferSetIndex, binding + _reservedConstantBuffers); + } + + public SetBindingPair CreateImageBinding(int count, bool isBuffer) + { + int binding; + + if (_context.Capabilities.Api == TargetApi.Vulkan) + { + if (count == 1) + { + int index = _staticImagesCount++; + + if (isBuffer) + { + index += (int)_context.Capabilities.MaximumImagesPerStage; + } + + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image"); + } + else + { + binding = (int)GetDynamicBaseIndexDual(_context.Capabilities.MaximumImagesPerStage) + _resourceCounts.ImagesCount++; + } + } + else + { + binding = _resourceCounts.ImagesCount; + + _resourceCounts.ImagesCount += count; + } + + return new SetBindingPair(_context.Capabilities.ImageSetIndex, binding + _reservedImages); + } + + public SetBindingPair CreateStorageBufferBinding(int index) + { + int binding; + + if (_context.Capabilities.Api == TargetApi.Vulkan) + { + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer"); + } + else + { + binding = _resourceCounts.StorageBuffersCount++; + } + + return new SetBindingPair(_context.Capabilities.StorageBufferSetIndex, binding + _reservedStorageBuffers); + } + + public SetBindingPair CreateTextureBinding(int count, bool isBuffer) + { + int binding; + + if (_context.Capabilities.Api == TargetApi.Vulkan) + { + if (count == 1) + { + int index = _staticTexturesCount++; + + if (isBuffer) + { + index += (int)_context.Capabilities.MaximumTexturesPerStage; + } + + binding = GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture"); + } + else + { + binding = (int)GetDynamicBaseIndexDual(_context.Capabilities.MaximumTexturesPerStage) + _resourceCounts.TexturesCount++; + } + } + else + { + binding = _resourceCounts.TexturesCount; + + _resourceCounts.TexturesCount += count; + } + + return new SetBindingPair(_context.Capabilities.TextureSetIndex, binding + _reservedTextures); + } + + private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName) + { + if ((uint)index >= maxPerStage) + { + Logger.Error?.Print(LogClass.Gpu, $"{resourceName} index {index} exceeds per stage limit of {maxPerStage}."); + } + + return GetStageIndex(_stageIndex) * (int)maxPerStage + index; + } + + public static int GetStageIndex(int stageIndex) + { + // This is just a simple remapping to ensure that most frequently used shader stages + // have the lowest binding numbers. + // This is useful because if we need to run on a system with a low limit on the bindings, + // then we can still get most games working as the most common shaders will have low binding numbers. + return stageIndex switch + { + 4 => 1, // Fragment + 3 => 2, // Geometry + 1 => 3, // Tessellation control + 2 => 4, // Tessellation evaluation + _ => 0, // Vertex/Compute + }; + } + + private static uint GetDynamicBaseIndexDual(uint maxPerStage) + { + return GetDynamicBaseIndex(maxPerStage) * 2; + } + + private static uint GetDynamicBaseIndex(uint maxPerStage) + { + return maxPerStage * Constants.ShaderStages; + } + + public int CreateExtraSet() + { + if (_resourceCounts.SetsCount >= _context.Capabilities.MaximumExtraSets) + { + return -1; + } + + return _context.Capabilities.ExtraSetBaseIndex + _resourceCounts.SetsCount++; + } + + public int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision; + + public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision; + + public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug; + + public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug; + + public int QueryHostStorageBufferOffsetAlignment() => _context.Capabilities.StorageBufferOffsetAlignment; + + public int QueryHostSubgroupSize() => _context.Capabilities.ShaderSubgroupSize; + + public bool QueryHostSupportsBgraFormat() => _context.Capabilities.SupportsBgraFormat; + + public bool QueryHostSupportsFragmentShaderInterlock() => _context.Capabilities.SupportsFragmentShaderInterlock; + + public bool QueryHostSupportsFragmentShaderOrderingIntel() => _context.Capabilities.SupportsFragmentShaderOrderingIntel; + + public bool QueryHostSupportsGeometryShader() => _context.Capabilities.SupportsGeometryShader; + + public bool QueryHostSupportsGeometryShaderPassthrough() => _context.Capabilities.SupportsGeometryShaderPassthrough; + + public bool QueryHostSupportsImageLoadFormatted() => _context.Capabilities.SupportsImageLoadFormatted; + + public bool QueryHostSupportsLayerVertexTessellation() => _context.Capabilities.SupportsLayerVertexTessellation; + + public bool QueryHostSupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset; + + public bool QueryHostSupportsScaledVertexFormats() => _context.Capabilities.SupportsScaledVertexFormats; + + public bool QueryHostSupportsSeparateSampler() => _context.Capabilities.SupportsSeparateSampler; + + public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot; + + public bool QueryHostSupportsShaderBarrierDivergence() => _context.Capabilities.SupportsShaderBarrierDivergence; + + public bool QueryHostSupportsShaderFloat64() => _context.Capabilities.SupportsShaderFloat64; + + public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat; + + public bool QueryHostSupportsTextureGatherOffsets() => _context.Capabilities.SupportsTextureGatherOffsets; + + public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod; + + public bool QueryHostSupportsTransformFeedback() => _context.Capabilities.SupportsTransformFeedback; + + public bool QueryHostSupportsViewportIndexVertexTessellation() => _context.Capabilities.SupportsViewportIndexVertexTessellation; + + public bool QueryHostSupportsViewportMask() => _context.Capabilities.SupportsViewportMask; + + public bool QueryHostSupportsDepthClipControl() => _context.Capabilities.SupportsDepthClipControl; + + /// + /// Converts a packed Maxwell texture format to the shader translator texture format. + /// + /// Packed maxwell format + /// Indicates if the format is sRGB + /// Shader translator texture format + protected static TextureFormat ConvertToTextureFormat(uint format, bool formatSrgb) + { + if (!FormatTable.TryGetTextureFormat(format, formatSrgb, out FormatInfo formatInfo)) + { + return TextureFormat.Unknown; + } + + return formatInfo.Format switch + { +#pragma warning disable IDE0055 // Disable formatting + Format.R8Unorm => TextureFormat.R8Unorm, + Format.R8Snorm => TextureFormat.R8Snorm, + Format.R8Uint => TextureFormat.R8Uint, + Format.R8Sint => TextureFormat.R8Sint, + Format.R16Float => TextureFormat.R16Float, + Format.R16Unorm => TextureFormat.R16Unorm, + Format.R16Snorm => TextureFormat.R16Snorm, + Format.R16Uint => TextureFormat.R16Uint, + Format.R16Sint => TextureFormat.R16Sint, + Format.R32Float => TextureFormat.R32Float, + Format.R32Uint => TextureFormat.R32Uint, + Format.R32Sint => TextureFormat.R32Sint, + Format.R8G8Unorm => TextureFormat.R8G8Unorm, + Format.R8G8Snorm => TextureFormat.R8G8Snorm, + Format.R8G8Uint => TextureFormat.R8G8Uint, + Format.R8G8Sint => TextureFormat.R8G8Sint, + Format.R16G16Float => TextureFormat.R16G16Float, + Format.R16G16Unorm => TextureFormat.R16G16Unorm, + Format.R16G16Snorm => TextureFormat.R16G16Snorm, + Format.R16G16Uint => TextureFormat.R16G16Uint, + Format.R16G16Sint => TextureFormat.R16G16Sint, + Format.R32G32Float => TextureFormat.R32G32Float, + Format.R32G32Uint => TextureFormat.R32G32Uint, + Format.R32G32Sint => TextureFormat.R32G32Sint, + Format.R8G8B8A8Unorm => TextureFormat.R8G8B8A8Unorm, + Format.R8G8B8A8Snorm => TextureFormat.R8G8B8A8Snorm, + Format.R8G8B8A8Uint => TextureFormat.R8G8B8A8Uint, + Format.R8G8B8A8Sint => TextureFormat.R8G8B8A8Sint, + Format.R8G8B8A8Srgb => TextureFormat.R8G8B8A8Unorm, + Format.R16G16B16A16Float => TextureFormat.R16G16B16A16Float, + Format.R16G16B16A16Unorm => TextureFormat.R16G16B16A16Unorm, + Format.R16G16B16A16Snorm => TextureFormat.R16G16B16A16Snorm, + Format.R16G16B16A16Uint => TextureFormat.R16G16B16A16Uint, + Format.R16G16B16A16Sint => TextureFormat.R16G16B16A16Sint, + Format.R32G32B32A32Float => TextureFormat.R32G32B32A32Float, + Format.R32G32B32A32Uint => TextureFormat.R32G32B32A32Uint, + Format.R32G32B32A32Sint => TextureFormat.R32G32B32A32Sint, + Format.R10G10B10A2Unorm => TextureFormat.R10G10B10A2Unorm, + Format.R10G10B10A2Uint => TextureFormat.R10G10B10A2Uint, + Format.R11G11B10Float => TextureFormat.R11G11B10Float, + _ => TextureFormat.Unknown, +#pragma warning restore IDE0055 + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs new file mode 100644 index 00000000..808bf185 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs @@ -0,0 +1,69 @@ +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// State used by the . + /// + class GpuAccessorState + { + /// + /// Maximum ID that a sampler pool entry may have. + /// + public readonly int SamplerPoolMaximumId; + + /// + /// GPU texture pool state. + /// + public readonly GpuChannelPoolState PoolState; + + /// + /// GPU compute state, for compute shaders. + /// + public readonly GpuChannelComputeState ComputeState; + + /// + /// GPU graphics state, for vertex, tessellation, geometry and fragment shaders. + /// + public readonly GpuChannelGraphicsState GraphicsState; + + /// + /// Shader specialization state (shared by all stages). + /// + public readonly ShaderSpecializationState SpecializationState; + + /// + /// Transform feedback information, if the shader uses transform feedback. Otherwise, should be null. + /// + public readonly TransformFeedbackDescriptor[] TransformFeedbackDescriptors; + + /// + /// Shader resource counts (shared by all stages). + /// + public readonly ResourceCounts ResourceCounts; + + /// + /// Creates a new GPU accessor state. + /// + /// Maximum ID that a sampler pool entry may have + /// GPU texture pool state + /// GPU compute state, for compute shaders + /// GPU graphics state, for vertex, tessellation, geometry and fragment shaders + /// Shader specialization state (shared by all stages) + /// Transform feedback information, if the shader uses transform feedback. Otherwise, should be null + public GpuAccessorState( + int samplerPoolMaximumId, + GpuChannelPoolState poolState, + GpuChannelComputeState computeState, + GpuChannelGraphicsState graphicsState, + ShaderSpecializationState specializationState, + TransformFeedbackDescriptor[] transformFeedbackDescriptors = null) + { + SamplerPoolMaximumId = samplerPoolMaximumId; + PoolState = poolState; + GraphicsState = graphicsState; + ComputeState = computeState; + SpecializationState = specializationState; + TransformFeedbackDescriptors = transformFeedbackDescriptors; + ResourceCounts = new ResourceCounts(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelComputeState.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelComputeState.cs new file mode 100644 index 00000000..d8cdbc34 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelComputeState.cs @@ -0,0 +1,65 @@ +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// State used by the . + /// + readonly struct GpuChannelComputeState + { + // New fields should be added to the end of the struct to keep disk shader cache compatibility. + + /// + /// Local group size X of the compute shader. + /// + public readonly int LocalSizeX; + + /// + /// Local group size Y of the compute shader. + /// + public readonly int LocalSizeY; + + /// + /// Local group size Z of the compute shader. + /// + public readonly int LocalSizeZ; + + /// + /// Local memory size of the compute shader. + /// + public readonly int LocalMemorySize; + + /// + /// Shared memory size of the compute shader. + /// + public readonly int SharedMemorySize; + + /// + /// Indicates that any storage buffer use is unaligned. + /// + public readonly bool HasUnalignedStorageBuffer; + + /// + /// Creates a new GPU compute state. + /// + /// Local group size X of the compute shader + /// Local group size Y of the compute shader + /// Local group size Z of the compute shader + /// Local memory size of the compute shader + /// Shared memory size of the compute shader + /// Indicates that any storage buffer use is unaligned + public GpuChannelComputeState( + int localSizeX, + int localSizeY, + int localSizeZ, + int localMemorySize, + int sharedMemorySize, + bool hasUnalignedStorageBuffer) + { + LocalSizeX = localSizeX; + LocalSizeY = localSizeY; + LocalSizeZ = localSizeZ; + LocalMemorySize = localMemorySize; + SharedMemorySize = sharedMemorySize; + HasUnalignedStorageBuffer = hasUnalignedStorageBuffer; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs new file mode 100644 index 00000000..765bef7d --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs @@ -0,0 +1,190 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// State used by the . + /// + struct GpuChannelGraphicsState + { + // New fields should be added to the end of the struct to keep disk shader cache compatibility. + + /// + /// Early Z force enable. + /// + public bool EarlyZForce; + + /// + /// Primitive topology of current draw. + /// + public PrimitiveTopology Topology; + + /// + /// Tessellation mode. + /// + public TessMode TessellationMode; + + /// + /// Indicates whether alpha-to-coverage is enabled. + /// + public bool AlphaToCoverageEnable; + + /// + /// Indicates whether alpha-to-coverage dithering is enabled. + /// + public bool AlphaToCoverageDitherEnable; + + /// + /// Indicates whether the viewport transform is disabled. + /// + public bool ViewportTransformDisable; + + /// + /// Depth mode zero to one or minus one to one. + /// + public bool DepthMode; + + /// + /// Indicates if the point size is set on the shader or is fixed. + /// + public bool ProgramPointSizeEnable; + + /// + /// Point size used if is false. + /// + public float PointSize; + + /// + /// Indicates whether alpha test is enabled. + /// + public bool AlphaTestEnable; + + /// + /// When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded. + /// + public CompareOp AlphaTestCompare; + + /// + /// When alpha test is enabled, indicates the value to compare with the fragment output alpha. + /// + public float AlphaTestReference; + + /// + /// Type of the vertex attributes consumed by the shader. + /// + public Array32 AttributeTypes; + + /// + /// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0. + /// + public bool HasConstantBufferDrawParameters; + + /// + /// Indicates that any storage buffer use is unaligned. + /// + public bool HasUnalignedStorageBuffer; + + /// + /// Type of the fragment shader outputs. + /// + public Array8 FragmentOutputTypes; + + /// + /// Indicates whether dual source blend is enabled. + /// + public bool DualSourceBlendEnable; + + /// + /// Indicates whether Y negate of the fragment coordinates is enabled. + /// + public bool YNegateEnabled; + + /// + /// Creates a new graphics state from this state that can be used for shader generation. + /// + /// Indicates if the host API supports alpha test operations + /// Indicates if the host API supports quad primitives + /// Indicates if a geometry shader is used + /// If true, indicates that the fragment origin is the upper left corner of the viewport, otherwise it is the lower left corner + /// GPU graphics state that can be used for shader translation + public readonly GpuGraphicsState CreateShaderGraphicsState(bool hostSupportsAlphaTest, bool hostSupportsQuads, bool hasGeometryShader, bool originUpperLeft) + { + AlphaTestOp alphaTestOp; + + if (hostSupportsAlphaTest || !AlphaTestEnable) + { + alphaTestOp = AlphaTestOp.Always; + } + else + { + alphaTestOp = AlphaTestCompare switch + { + CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never, + CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less, + CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal, + CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual, + CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater, + CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual, + CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual, + _ => AlphaTestOp.Always, + }; + } + + bool isQuad = Topology == PrimitiveTopology.Quads || Topology == PrimitiveTopology.QuadStrip; + bool halvePrimitiveId = !hostSupportsQuads && !hasGeometryShader && isQuad; + + return new GpuGraphicsState( + EarlyZForce, + ConvertToInputTopology(Topology, TessellationMode), + TessellationMode.UnpackCw(), + TessellationMode.UnpackPatchType(), + TessellationMode.UnpackSpacing(), + AlphaToCoverageEnable, + AlphaToCoverageDitherEnable, + ViewportTransformDisable, + DepthMode, + ProgramPointSizeEnable, + PointSize, + alphaTestOp, + AlphaTestReference, + in AttributeTypes, + HasConstantBufferDrawParameters, + in FragmentOutputTypes, + DualSourceBlendEnable, + YNegateEnabled, + originUpperLeft, + halvePrimitiveId); + } + + /// + /// Converts the Maxwell primitive topology to the shader translator topology. + /// + /// Maxwell primitive topology + /// Maxwell tessellation mode + /// Shader translator topology + private static InputTopology ConvertToInputTopology(PrimitiveTopology topology, TessMode tessellationMode) + { + return topology switch + { + PrimitiveTopology.Points => InputTopology.Points, + PrimitiveTopology.Lines or + PrimitiveTopology.LineLoop or + PrimitiveTopology.LineStrip => InputTopology.Lines, + PrimitiveTopology.LinesAdjacency or + PrimitiveTopology.LineStripAdjacency => InputTopology.LinesAdjacency, + PrimitiveTopology.Triangles or + PrimitiveTopology.TriangleStrip or + PrimitiveTopology.TriangleFan => InputTopology.Triangles, + PrimitiveTopology.TrianglesAdjacency or + PrimitiveTopology.TriangleStripAdjacency => InputTopology.TrianglesAdjacency, + PrimitiveTopology.Patches => tessellationMode.UnpackPatchType() == TessPatchType.Isolines + ? InputTopology.Lines + : InputTopology.Triangles, + _ => InputTopology.Points, + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs new file mode 100644 index 00000000..a2ab9933 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs @@ -0,0 +1,60 @@ +using System; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// State used by the . + /// + readonly struct GpuChannelPoolState : IEquatable + { + /// + /// GPU virtual address of the texture pool. + /// + public readonly ulong TexturePoolGpuVa; + + /// + /// Maximum ID of the texture pool. + /// + public readonly int TexturePoolMaximumId; + + /// + /// Constant buffer slot where the texture handles are located. + /// + public readonly int TextureBufferIndex; + + /// + /// Creates a new GPU texture pool state. + /// + /// GPU virtual address of the texture pool + /// Maximum ID of the texture pool + /// Constant buffer slot where the texture handles are located + public GpuChannelPoolState(ulong texturePoolGpuVa, int texturePoolMaximumId, int textureBufferIndex) + { + TexturePoolGpuVa = texturePoolGpuVa; + TexturePoolMaximumId = texturePoolMaximumId; + TextureBufferIndex = textureBufferIndex; + } + + /// + /// Check if the pool states are equal. + /// + /// Pool state to compare with + /// True if they are equal, false otherwise + public bool Equals(GpuChannelPoolState other) + { + return TexturePoolGpuVa == other.TexturePoolGpuVa && + TexturePoolMaximumId == other.TexturePoolMaximumId && + TextureBufferIndex == other.TextureBufferIndex; + } + + public override bool Equals(object obj) + { + return obj is GpuChannelPoolState state && Equals(state); + } + + public override int GetHashCode() + { + return HashCode.Combine(TexturePoolGpuVa, TexturePoolMaximumId, TextureBufferIndex); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/HashTable/HashState.cs b/src/Ryujinx.Graphics.Gpu/Shader/HashTable/HashState.cs new file mode 100644 index 00000000..cb1cf5bb --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/HashTable/HashState.cs @@ -0,0 +1,113 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Shader.HashTable +{ + /// + /// State of a hash calculation. + /// + struct HashState + { + // This is using a slightly modified implementation of FastHash64. + // Reference: https://github.com/ztanml/fast-hash/blob/master/fasthash.c + private const ulong M = 0x880355f21e6d1965UL; + private ulong _hash; + private int _start; + + /// + /// One shot hash calculation for a given data. + /// + /// Data to be hashed + /// Hash of the given data + public static uint CalcHash(ReadOnlySpan data) + { + HashState state = new(); + + state.Initialize(); + state.Continue(data); + return state.Finalize(data); + } + + /// + /// Initializes the hash state. + /// + public void Initialize() + { + _hash = 23; + } + + /// + /// Calculates the hash of the given data. + /// + /// + /// The full data must be passed on . + /// If this is not the first time the method is called, then must start with the data passed on the last call. + /// If a smaller slice of the data was already hashed before, only the additional data will be hashed. + /// This can be used for additive hashing of data in chuncks. + /// + /// Data to be hashed + public void Continue(ReadOnlySpan data) + { + ulong h = _hash; + + ReadOnlySpan dataAsUlong = MemoryMarshal.Cast(data[_start..]); + + for (int i = 0; i < dataAsUlong.Length; i++) + { + ulong value = dataAsUlong[i]; + + h ^= Mix(value); + h *= M; + } + + _hash = h; + _start = data.Length & ~7; + } + + /// + /// Performs the hash finalization step, and returns the calculated hash. + /// + /// + /// The full data must be passed on . + /// must start with the data passed on the last call to . + /// No internal state is changed, so one can still continue hashing data with + /// after calling this method. + /// + /// Data to be hashed + /// Hash of all the data hashed with this + public readonly uint Finalize(ReadOnlySpan data) + { + ulong h = _hash; + + int remainder = data.Length & 7; + if (remainder != 0) + { + ulong v = 0; + + for (int i = data.Length - remainder; i < data.Length; i++) + { + v |= (ulong)data[i] << ((i - remainder) * 8); + } + + h ^= Mix(v); + h *= M; + } + + h = Mix(h); + return (uint)(h - (h >> 32)); + } + + /// + /// Hash mix function. + /// + /// Hash to mix + /// Mixed hash + private static ulong Mix(ulong h) + { + h ^= h >> 23; + h *= 0x2127599bf4325c37UL; + h ^= h >> 47; + return h; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/HashTable/IDataAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/HashTable/IDataAccessor.cs new file mode 100644 index 00000000..5aecd155 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/HashTable/IDataAccessor.cs @@ -0,0 +1,27 @@ +using System; + +namespace Ryujinx.Graphics.Gpu.Shader.HashTable +{ + /// + /// Data accessor, used by to access data of unknown length. + /// + /// + /// This will be used to access chuncks of data and try finding a match on the table. + /// This is necessary because the data size is assumed to be unknown, and so the + /// hash table must try to "guess" the size of the data based on the entries on the table. + /// + public interface IDataAccessor + { + /// + /// Gets a span of shader code at the specified offset, with at most the specified size. + /// + /// + /// This might return a span smaller than the requested if there's + /// no more code available. + /// + /// Offset in shader code + /// Size in bytes + /// Shader code span + ReadOnlySpan GetSpan(int offset, int length); + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionHashTable.cs b/src/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionHashTable.cs new file mode 100644 index 00000000..3971526e --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionHashTable.cs @@ -0,0 +1,451 @@ +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace Ryujinx.Graphics.Gpu.Shader.HashTable +{ + /// + /// Partitioned hash table. + /// + /// Hash table entry type + class PartitionHashTable + { + /// + /// Hash table entry. + /// + private struct Entry + { + /// + /// Hash bytes of . + /// + public readonly uint Hash; + + /// + /// If this entry is only a sub-region of , this indicates the size in bytes + /// of that region. Otherwise, it should be zero. + /// + public readonly int OwnSize; + + /// + /// Data used to compute the hash for this entry. + /// + /// + /// To avoid additional allocations, this might be a instance of the full entry data, + /// and only a sub-region of it might be actually used by this entry. Such sub-region + /// has its size indicated by in this case. + /// + public readonly byte[] Data; + + /// + /// Item associated with this entry. + /// + public T Item; + + /// + /// Indicates if the entry is partial, which means that this entry is only for a sub-region of the data. + /// + /// + /// Partial entries have no items associated with them. They just indicates that the data might be present on + /// the table, and one must keep looking for the full entry on other tables of larger data size. + /// + public readonly bool IsPartial => OwnSize != 0; + + /// + /// Creates a new partial hash table entry. + /// + /// Hash of the data + /// Full data + /// Size of the sub-region of data that belongs to this entry + public Entry(uint hash, byte[] ownerData, int ownSize) + { + Hash = hash; + OwnSize = ownSize; + Data = ownerData; + Item = default; + } + + /// + /// Creates a new full hash table entry. + /// + /// Hash of the data + /// Data + /// Item associated with this entry + public Entry(uint hash, byte[] data, T item) + { + Hash = hash; + OwnSize = 0; + Data = data; + Item = item; + } + + /// + /// Gets the data for this entry, either full or partial. + /// + /// Data sub-region + public readonly ReadOnlySpan GetData() + { + if (OwnSize != 0) + { + return new ReadOnlySpan(Data)[..OwnSize]; + } + + return Data; + } + } + + /// + /// Hash table bucket. + /// + private struct Bucket + { + /// + /// Inline entry, to avoid allocations for the common single entry case. + /// + public Entry InlineEntry; + + /// + /// List of additional entries for the not-so-common multiple entries case. + /// + public List MoreEntries; + } + + private Bucket[] _buckets; + private int _count; + + /// + /// Total amount of entries on the hash table. + /// + public int Count => _count; + + /// + /// Creates a new instance of the partitioned hash table. + /// + public PartitionHashTable() + { + _buckets = Array.Empty(); + } + + /// + /// Gets an item on the table, or adds a new one if not present. + /// + /// Data + /// Hash of the data + /// Item to be added if not found + /// Existing item if found, or if not found + public T GetOrAdd(byte[] data, uint dataHash, T item) + { + if (TryFindItem(dataHash, data, out T existingItem)) + { + return existingItem; + } + + Entry entry = new(dataHash, data, item); + + AddToBucket(dataHash, ref entry); + + return item; + } + + /// + /// Adds an item to the hash table. + /// + /// Data + /// Hash of the data + /// Item to be added + /// True if the item was added, false due to an item associated with the data already being on the table + public bool Add(byte[] data, uint dataHash, T item) + { + if (TryFindItem(dataHash, data, out _)) + { + return false; + } + + Entry entry = new(dataHash, data, item); + + AddToBucket(dataHash, ref entry); + + return true; + } + + /// + /// Adds a partial entry to the hash table. + /// + /// Full data + /// Size of the sub-region of used by the partial entry + /// True if added, false otherwise + public bool AddPartial(byte[] ownerData, int ownSize) + { + ReadOnlySpan data = new ReadOnlySpan(ownerData)[..ownSize]; + + return AddPartial(ownerData, HashState.CalcHash(data), ownSize); + } + + /// + /// Adds a partial entry to the hash table. + /// + /// Full data + /// Hash of the data sub-region + /// Size of the sub-region of used by the partial entry + /// True if added, false otherwise + public bool AddPartial(byte[] ownerData, uint dataHash, int ownSize) + { + ReadOnlySpan data = new ReadOnlySpan(ownerData)[..ownSize]; + + if (TryFindItem(dataHash, data, out _)) + { + return false; + } + + Entry entry = new(dataHash, ownerData, ownSize); + + AddToBucket(dataHash, ref entry); + + return true; + } + + /// + /// Adds entry with a given hash to the table. + /// + /// Hash of the entry + /// Entry + private void AddToBucket(uint dataHash, ref Entry entry) + { + int pow2Count = GetPow2Count(++_count); + if (pow2Count != _buckets.Length) + { + Rebuild(pow2Count); + } + + ref Bucket bucket = ref GetBucketForHash(dataHash); + + AddToBucket(ref bucket, ref entry); + } + + /// + /// Adds an entry to a bucket. + /// + /// Bucket to add the entry into + /// Entry to be added + private static void AddToBucket(ref Bucket bucket, ref Entry entry) + { + if (bucket.InlineEntry.Data == null) + { + bucket.InlineEntry = entry; + } + else + { + (bucket.MoreEntries ??= new List()).Add(entry); + } + } + + /// + /// Creates partial entries on a new hash table for all existing full entries. + /// + /// + /// This should be called every time a new hash table is created, and there are hash + /// tables with data sizes that are higher than that of the new table. + /// This will then fill the new hash table with "partial" entries of full entries + /// on the hash tables with higher size. + /// + /// New hash table + /// Size of the data on the new hash table + public void FillPartials(PartitionHashTable newTable, int newEntrySize) + { + for (int i = 0; i < _buckets.Length; i++) + { + ref Bucket bucket = ref _buckets[i]; + ref Entry inlineEntry = ref bucket.InlineEntry; + + if (inlineEntry.Data != null) + { + if (!inlineEntry.IsPartial) + { + newTable.AddPartial(inlineEntry.Data, newEntrySize); + } + + if (bucket.MoreEntries != null) + { + foreach (Entry entry in bucket.MoreEntries) + { + if (entry.IsPartial) + { + continue; + } + + newTable.AddPartial(entry.Data, newEntrySize); + } + } + } + } + } + + /// + /// Tries to find an item on the table. + /// + /// Hash of + /// Data to find + /// Item associated with the data + /// True if an item was found, false otherwise + private bool TryFindItem(uint dataHash, ReadOnlySpan data, out T item) + { + if (_count == 0) + { + item = default; + return false; + } + + ref Bucket bucket = ref GetBucketForHash(dataHash); + + if (bucket.InlineEntry.Data != null) + { + if (bucket.InlineEntry.Hash == dataHash && bucket.InlineEntry.GetData().SequenceEqual(data)) + { + item = bucket.InlineEntry.Item; + return true; + } + + if (bucket.MoreEntries != null) + { + foreach (Entry entry in bucket.MoreEntries) + { + if (entry.Hash == dataHash && entry.GetData().SequenceEqual(data)) + { + item = entry.Item; + return true; + } + } + } + } + + item = default; + return false; + } + + /// + /// Indicates the result of a hash table lookup. + /// + public enum SearchResult + { + /// + /// No entry was found, the search must continue on hash tables of lower size. + /// + NotFound, + + /// + /// A partial entry was found, the search must continue on hash tables of higher size. + /// + FoundPartial, + + /// + /// A full entry was found, the search was concluded and the item can be retrieved. + /// + FoundFull, + } + + /// + /// Tries to find an item on the table. + /// + /// Data accessor + /// Size of the hash table data + /// The item on the table, if found, otherwise unmodified + /// The data on the table, if found, otherwise unmodified + /// Table lookup result + public SearchResult TryFindItem(scoped ref SmartDataAccessor dataAccessor, int size, scoped ref T item, scoped ref byte[] data) + { + if (_count == 0) + { + return SearchResult.NotFound; + } + + ReadOnlySpan dataSpan = dataAccessor.GetSpanAndHash(size, out uint dataHash); + + if (dataSpan.Length != size) + { + return SearchResult.NotFound; + } + + ref Bucket bucket = ref GetBucketForHash(dataHash); + + if (bucket.InlineEntry.Data != null) + { + if (bucket.InlineEntry.Hash == dataHash && bucket.InlineEntry.GetData().SequenceEqual(dataSpan)) + { + item = bucket.InlineEntry.Item; + data = bucket.InlineEntry.Data; + return bucket.InlineEntry.IsPartial ? SearchResult.FoundPartial : SearchResult.FoundFull; + } + + if (bucket.MoreEntries != null) + { + foreach (Entry entry in bucket.MoreEntries) + { + if (entry.Hash == dataHash && entry.GetData().SequenceEqual(dataSpan)) + { + item = entry.Item; + data = entry.Data; + return entry.IsPartial ? SearchResult.FoundPartial : SearchResult.FoundFull; + } + } + } + } + + return SearchResult.NotFound; + } + + /// + /// Rebuilds the table for a new count. + /// + /// New power of two count of the table + private void Rebuild(int newPow2Count) + { + Bucket[] newBuckets = new Bucket[newPow2Count]; + + uint mask = (uint)newPow2Count - 1; + + for (int i = 0; i < _buckets.Length; i++) + { + ref Bucket bucket = ref _buckets[i]; + + if (bucket.InlineEntry.Data != null) + { + AddToBucket(ref newBuckets[(int)(bucket.InlineEntry.Hash & mask)], ref bucket.InlineEntry); + + if (bucket.MoreEntries != null) + { + foreach (Entry entry in bucket.MoreEntries) + { + Entry entryCopy = entry; + AddToBucket(ref newBuckets[(int)(entry.Hash & mask)], ref entryCopy); + } + } + } + } + + _buckets = newBuckets; + } + + /// + /// Gets the bucket for a given hash. + /// + /// Data hash + /// Bucket for the hash + private ref Bucket GetBucketForHash(uint hash) + { + int index = (int)(hash & (_buckets.Length - 1)); + + return ref _buckets[index]; + } + + /// + /// Gets a power of two count from a regular count. + /// + /// Count + /// Power of two count + private static int GetPow2Count(int count) + { + // This returns the nearest power of two that is lower than count. + // This was done to optimize memory usage rather than performance. + return 1 << BitOperations.Log2((uint)count); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionedHashTable.cs b/src/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionedHashTable.cs new file mode 100644 index 00000000..b4c18058 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionedHashTable.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Gpu.Shader.HashTable +{ + /// + /// Partitioned hash table. + /// + /// + public class PartitionedHashTable + { + /// + /// Entry for a given data size. + /// + private readonly struct SizeEntry + { + /// + /// Size for the data that will be stored on the hash table on this entry. + /// + public int Size { get; } + + /// + /// Number of entries on the hash table. + /// + public int TableCount => _table.Count; + + private readonly PartitionHashTable _table; + + /// + /// Creates an entry for a given size. + /// + /// Size of the data to be stored on this entry + public SizeEntry(int size) + { + Size = size; + _table = new PartitionHashTable(); + } + + /// + /// Gets an item for existing data, or adds a new one. + /// + /// Data associated with the item + /// Hash of + /// Item to be added + /// Existing item, or if not present + public T GetOrAdd(byte[] data, uint dataHash, T item) + { + Debug.Assert(data.Length == Size); + return _table.GetOrAdd(data, dataHash, item); + } + + /// + /// Adds a new item. + /// + /// Data associated with the item + /// Hash of + /// Item to be added + /// True if added, false otherwise + public bool Add(byte[] data, uint dataHash, T item) + { + Debug.Assert(data.Length == Size); + return _table.Add(data, dataHash, item); + } + + /// + /// Adds a partial entry. + /// + /// Full entry data + /// Hash of the sub-region of the data that belongs to this entry + /// True if added, false otherwise + public bool AddPartial(byte[] ownerData, uint dataHash) + { + return _table.AddPartial(ownerData, dataHash, Size); + } + + /// + /// Fills a new hash table with "partials" of existing full entries of higher size. + /// + /// Entry with the new hash table + public void FillPartials(SizeEntry newEntry) + { + Debug.Assert(newEntry.Size < Size); + _table.FillPartials(newEntry._table, newEntry.Size); + } + + /// + /// Tries to find an item on the hash table. + /// + /// Data accessor + /// The item on the table, if found, otherwise unmodified + /// The data on the table, if found, otherwise unmodified + /// Table lookup result + public PartitionHashTable.SearchResult TryFindItem(scoped ref SmartDataAccessor dataAccessor, scoped ref T item, scoped ref byte[] data) + { + return _table.TryFindItem(ref dataAccessor, Size, ref item, ref data); + } + } + + private readonly List _sizeTable; + + /// + /// Creates a new partitioned hash table. + /// + public PartitionedHashTable() + { + _sizeTable = new List(); + } + + /// + /// Adds a new item to the table. + /// + /// Data + /// Item associated with the data + public void Add(byte[] data, T item) + { + GetOrAdd(data, item); + } + + /// + /// Gets an existing item from the table, or adds a new one if not present. + /// + /// Data + /// Item associated with the data + /// Existing item, or if not present + public T GetOrAdd(byte[] data, T item) + { + SizeEntry sizeEntry; + + int index = BinarySearch(_sizeTable, data.Length); + if (index < _sizeTable.Count && _sizeTable[index].Size == data.Length) + { + sizeEntry = _sizeTable[index]; + } + else + { + if (index < _sizeTable.Count && _sizeTable[index].Size < data.Length) + { + index++; + } + + sizeEntry = new SizeEntry(data.Length); + + _sizeTable.Insert(index, sizeEntry); + + for (int i = index + 1; i < _sizeTable.Count; i++) + { + _sizeTable[i].FillPartials(sizeEntry); + } + } + + HashState hashState = new(); + hashState.Initialize(); + + for (int i = 0; i < index; i++) + { + ReadOnlySpan dataSlice = new ReadOnlySpan(data)[.._sizeTable[i].Size]; + hashState.Continue(dataSlice); + _sizeTable[i].AddPartial(data, hashState.Finalize(dataSlice)); + } + + hashState.Continue(data); + return sizeEntry.GetOrAdd(data, hashState.Finalize(data), item); + } + + /// + /// Performs binary search on a list of hash tables, each one with a fixed data size. + /// + /// List of hash tables + /// Size to search for + /// Index of the hash table with the given size, or nearest one otherwise + private static int BinarySearch(List entries, int size) + { + int left = 0; + int middle = 0; + int right = entries.Count - 1; + + while (left <= right) + { + middle = left + ((right - left) >> 1); + + SizeEntry entry = entries[middle]; + + if (size == entry.Size) + { + break; + } + + if (size < entry.Size) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return middle; + } + + /// + /// Tries to find an item on the table. + /// + /// Data accessor + /// Item, if found + /// Data, if found + /// True if the item was found on the table, false otherwise + public bool TryFindItem(IDataAccessor dataAccessor, out T item, out byte[] data) + { + SmartDataAccessor sda = new(dataAccessor); + + item = default; + data = null; + + int left = 0; + int right = _sizeTable.Count; + + while (left != right) + { + int index = left + ((right - left) >> 1); + + PartitionHashTable.SearchResult result = _sizeTable[index].TryFindItem(ref sda, ref item, ref data); + + if (result == PartitionHashTable.SearchResult.FoundFull) + { + return true; + } + + if (result == PartitionHashTable.SearchResult.NotFound) + { + right = index; + } + else /* if (result == PartitionHashTable.SearchResult.FoundPartial) */ + { + left = index + 1; + } + } + + data = null; + return false; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/HashTable/SmartDataAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/HashTable/SmartDataAccessor.cs new file mode 100644 index 00000000..d0eabba2 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/HashTable/SmartDataAccessor.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Shader.HashTable +{ + /// + /// Smart data accessor that can cache data and hashes to avoid reading and re-hashing the same memory regions. + /// + ref struct SmartDataAccessor + { + private readonly IDataAccessor _dataAccessor; + private ReadOnlySpan _data; + private readonly SortedList _cachedHashes; + + /// + /// Creates a new smart data accessor. + /// + /// Data accessor + public SmartDataAccessor(IDataAccessor dataAccessor) + { + _dataAccessor = dataAccessor; + _data = ReadOnlySpan.Empty; + _cachedHashes = new SortedList(); + } + + /// + /// Get a spans of a given size. + /// + /// + /// The actual length of the span returned depends on the + /// and might be less than requested. + /// + /// Size in bytes + /// Span with the requested size + public ReadOnlySpan GetSpan(int length) + { + if (_data.Length < length) + { + _data = _dataAccessor.GetSpan(0, length); + } + else if (_data.Length > length) + { + return _data[..length]; + } + + return _data; + } + + /// + /// Gets a span of the requested size, and a hash of its data. + /// + /// Length of the span + /// Hash of the span data + /// Span of data + public ReadOnlySpan GetSpanAndHash(int length, out uint hash) + { + ReadOnlySpan data = GetSpan(length); + hash = data.Length == length ? CalcHashCached(data) : 0; + return data; + } + + /// + /// Calculates the hash for a requested span. + /// This will try to use a cached hash if the data was already accessed before, to avoid re-hashing. + /// + /// Data to be hashed + /// Hash of the data + private readonly uint CalcHashCached(ReadOnlySpan data) + { + HashState state = default; + bool found = false; + + for (int i = _cachedHashes.Count - 1; i >= 0; i--) + { + int cachedHashSize = _cachedHashes.Keys[i]; + + if (cachedHashSize < data.Length) + { + state = _cachedHashes.Values[i]; + found = true; + break; + } + } + + if (!found) + { + state = new HashState(); + state.Initialize(); + } + + state.Continue(data); + _cachedHashes[data.Length & ~7] = state; + return state.Finalize(data); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs b/src/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs new file mode 100644 index 00000000..59ab378c --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Holds counts for the resources used by a shader. + /// + class ResourceCounts + { + /// + /// Total of uniform buffers used by the shaders. + /// + public int UniformBuffersCount; + + /// + /// Total of storage buffers used by the shaders. + /// + public int StorageBuffersCount; + + /// + /// Total of textures used by the shaders. + /// + public int TexturesCount; + + /// + /// Total of images used by the shaders. + /// + public int ImagesCount; + + /// + /// Total of extra sets used by the shaders. + /// + public int SetsCount; + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs new file mode 100644 index 00000000..32d92223 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs @@ -0,0 +1,64 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Shader code addresses in memory for each shader stage. + /// + struct ShaderAddresses : IEquatable + { +#pragma warning disable CS0649 // Field is never assigned to + public ulong VertexA; + public ulong VertexB; + public ulong TessControl; + public ulong TessEvaluation; + public ulong Geometry; + public ulong Fragment; +#pragma warning restore CS0649 + + /// + /// Check if the addresses are equal. + /// + /// Shader addresses structure to compare with + /// True if they are equal, false otherwise + public readonly override bool Equals(object other) + { + return other is ShaderAddresses addresses && Equals(addresses); + } + + /// + /// Check if the addresses are equal. + /// + /// Shader addresses structure to compare with + /// True if they are equal, false otherwise + public readonly bool Equals(ShaderAddresses other) + { + return VertexA == other.VertexA && + VertexB == other.VertexB && + TessControl == other.TessControl && + TessEvaluation == other.TessEvaluation && + Geometry == other.Geometry && + Fragment == other.Fragment; + } + + /// + /// Computes hash code from the addresses. + /// + /// Hash code + public readonly override int GetHashCode() + { + return HashCode.Combine(VertexA, VertexB, TessControl, TessEvaluation, Geometry, Fragment); + } + + /// + /// Gets a view of the structure as a span of addresses. + /// + /// Span of addresses + public Span AsSpan() + { + return MemoryMarshal.CreateSpan(ref VertexA, Unsafe.SizeOf() / sizeof(ulong)); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs new file mode 100644 index 00000000..71540a13 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderAsCompute.cs @@ -0,0 +1,20 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + class ShaderAsCompute + { + public IProgram HostProgram { get; } + public ShaderProgramInfo Info { get; } + public ResourceReservations Reservations { get; } + + public ShaderAsCompute(IProgram hostProgram, ShaderProgramInfo info, ResourceReservations reservations) + { + HostProgram = hostProgram; + Info = info; + Reservations = reservations; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs new file mode 100644 index 00000000..4fc66c4c --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -0,0 +1,858 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.Shader.DiskCache; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Memory cache of shader code. + /// + class ShaderCache : IDisposable + { + /// + /// Default flags used on the shader translation process. + /// + public const TranslationFlags DefaultFlags = TranslationFlags.DebugMode; + + private readonly struct TranslatedShader + { + public readonly CachedShaderStage Shader; + public readonly ShaderProgram Program; + + public TranslatedShader(CachedShaderStage shader, ShaderProgram program) + { + Shader = shader; + Program = program; + } + } + + private readonly struct TranslatedShaderVertexPair + { + public readonly CachedShaderStage VertexA; + public readonly CachedShaderStage VertexB; + public readonly ShaderProgram Program; + + public TranslatedShaderVertexPair(CachedShaderStage vertexA, CachedShaderStage vertexB, ShaderProgram program) + { + VertexA = vertexA; + VertexB = vertexB; + Program = program; + } + } + + private readonly GpuContext _context; + + private readonly ShaderDumper _dumper; + + private readonly Dictionary _cpPrograms; + private readonly Dictionary _gpPrograms; + + private readonly struct ProgramToSave + { + public readonly CachedShaderProgram CachedProgram; + public readonly IProgram HostProgram; + public readonly byte[] BinaryCode; + + public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram, byte[] binaryCode) + { + CachedProgram = cachedProgram; + HostProgram = hostProgram; + BinaryCode = binaryCode; + } + } + + private readonly Queue _programsToSaveQueue; + + private readonly ComputeShaderCacheHashTable _computeShaderCache; + private readonly ShaderCacheHashTable _graphicsShaderCache; + private readonly DiskCacheHostStorage _diskCacheHostStorage; + private readonly BackgroundDiskCacheWriter _cacheWriter; + + /// + /// Event for signalling shader cache loading progress. + /// + public event Action ShaderCacheStateChanged; + + /// + /// Creates a new instance of the shader cache. + /// + /// GPU context that the shader cache belongs to + public ShaderCache(GpuContext context) + { + _context = context; + + _dumper = new ShaderDumper(); + + _cpPrograms = new Dictionary(); + _gpPrograms = new Dictionary(); + + _programsToSaveQueue = new Queue(); + + string diskCacheTitleId = GetDiskCachePath(); + + _computeShaderCache = new ComputeShaderCacheHashTable(); + _graphicsShaderCache = new ShaderCacheHashTable(); + _diskCacheHostStorage = new DiskCacheHostStorage(diskCacheTitleId); + + if (_diskCacheHostStorage.CacheEnabled) + { + _cacheWriter = new BackgroundDiskCacheWriter(context, _diskCacheHostStorage); + } + } + + /// + /// Gets the path where the disk cache for the current application is stored. + /// + private static string GetDiskCachePath() + { + return GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null + ? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId, "cache", "shader") + : null; + } + + /// + /// Processes the queue of shaders that must save their binaries to the disk cache. + /// + public void ProcessShaderCacheQueue() + { + // Check to see if the binaries for previously compiled shaders are ready, and save them out. + + while (_programsToSaveQueue.TryPeek(out ProgramToSave programToSave)) + { + ProgramLinkStatus result = programToSave.HostProgram.CheckProgramLink(false); + + if (result != ProgramLinkStatus.Incomplete) + { + if (result == ProgramLinkStatus.Success) + { + _cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary()); + } + + _programsToSaveQueue.Dequeue(); + } + else + { + break; + } + } + } + + /// + /// Initialize the cache. + /// + /// Cancellation token to cancel the shader cache initialization process + internal void Initialize(CancellationToken cancellationToken) + { + if (_diskCacheHostStorage.CacheEnabled) + { + ParallelDiskCacheLoader loader = new( + _context, + _graphicsShaderCache, + _computeShaderCache, + _diskCacheHostStorage, + ShaderCacheStateUpdate, + cancellationToken); + + loader.LoadShaders(); + + int errorCount = loader.ErrorCount; + if (errorCount != 0) + { + Logger.Warning?.Print(LogClass.Gpu, $"Failed to load {errorCount} shaders from the disk cache."); + } + } + } + + /// + /// Shader cache state update handler. + /// + /// Current state of the shader cache load process + /// Number of the current shader being processed + /// Total number of shaders to process + private void ShaderCacheStateUpdate(ShaderCacheState state, int current, int total) + { + ShaderCacheStateChanged?.Invoke(state, current, total); + } + + /// + /// Gets a compute shader from the cache. + /// + /// + /// This automatically translates, compiles and adds the code to the cache if not present. + /// + /// GPU channel + /// Maximum ID that an entry in the sampler pool may have + /// Texture pool state + /// Compute engine state + /// GPU virtual address of the binary shader code + /// Compiled compute shader code + public CachedShaderProgram GetComputeShader( + GpuChannel channel, + int samplerPoolMaximumId, + GpuChannelPoolState poolState, + GpuChannelComputeState computeState, + ulong gpuVa) + { + if (_cpPrograms.TryGetValue(gpuVa, out var cpShader) && IsShaderEqual(channel, poolState, computeState, cpShader, gpuVa)) + { + return cpShader; + } + + if (_computeShaderCache.TryFind(channel, poolState, computeState, gpuVa, out cpShader, out byte[] cachedGuestCode)) + { + _cpPrograms[gpuVa] = cpShader; + return cpShader; + } + + ShaderSpecializationState specState = new(ref computeState); + GpuAccessorState gpuAccessorState = new(samplerPoolMaximumId, poolState, computeState, default, specState); + GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState); + gpuAccessor.InitializeReservedCounts(tfEnabled: false, vertexAsCompute: false); + + TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa); + TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode, asCompute: false); + + ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) }; + ShaderInfo info = ShaderInfoBuilder.BuildForCompute(_context, translatedShader.Program.Info); + IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info); + + cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader); + + _computeShaderCache.Add(cpShader); + EnqueueProgramToSave(cpShader, hostProgram, shaderSourcesArray); + _cpPrograms[gpuVa] = cpShader; + + return cpShader; + } + + /// + /// Updates the shader pipeline state based on the current GPU state. + /// + /// Current GPU 3D engine state + /// Shader pipeline state to be updated + /// Current graphics state + /// Current GPU channel + private static void UpdatePipelineInfo( + ref ThreedClassState state, + ref ProgramPipelineState pipeline, + GpuChannelGraphicsState graphicsState, + GpuChannel channel) + { + channel.TextureManager.UpdateRenderTargets(); + + var rtControl = state.RtControl; + var msaaMode = state.RtMsaaMode; + + pipeline.SamplesCount = msaaMode.SamplesInX() * msaaMode.SamplesInY(); + + int count = rtControl.UnpackCount(); + + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + int rtIndex = rtControl.UnpackPermutationIndex(index); + + var colorState = state.RtColorState[rtIndex]; + + if (index >= count || colorState.Format == 0 || colorState.WidthOrStride == 0) + { + pipeline.AttachmentEnable[index] = false; + pipeline.AttachmentFormats[index] = Format.R8G8B8A8Unorm; + } + else + { + pipeline.AttachmentEnable[index] = true; + pipeline.AttachmentFormats[index] = colorState.Format.Convert().Format; + } + } + + pipeline.DepthStencilEnable = state.RtDepthStencilEnable; + pipeline.DepthStencilFormat = pipeline.DepthStencilEnable ? state.RtDepthStencilState.Format.Convert().Format : Format.D24UnormS8Uint; + + pipeline.VertexBufferCount = Constants.TotalVertexBuffers; + pipeline.Topology = graphicsState.Topology; + } + + /// + /// Gets a graphics shader program from the shader cache. + /// This includes all the specified shader stages. + /// + /// + /// This automatically translates, compiles and adds the code to the cache if not present. + /// + /// GPU state + /// Pipeline state + /// GPU channel + /// Maximum ID that an entry in the sampler pool may have + /// Texture pool state + /// 3D engine state + /// Addresses of the shaders for each stage + /// Compiled graphics shader code + public CachedShaderProgram GetGraphicsShader( + ref ThreedClassState state, + ref ProgramPipelineState pipeline, + GpuChannel channel, + int samplerPoolMaximumId, + ref GpuChannelPoolState poolState, + ref GpuChannelGraphicsState graphicsState, + ShaderAddresses addresses) + { + if (_gpPrograms.TryGetValue(addresses, out var gpShaders) && IsShaderEqual(channel, ref poolState, ref graphicsState, gpShaders, addresses)) + { + return gpShaders; + } + + if (_graphicsShaderCache.TryFind(channel, ref poolState, ref graphicsState, addresses, out gpShaders, out var cachedGuestCode)) + { + _gpPrograms[addresses] = gpShaders; + return gpShaders; + } + + TransformFeedbackDescriptor[] transformFeedbackDescriptors = GetTransformFeedbackDescriptors(ref state); + + UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel); + + ShaderSpecializationState specState = new(ref graphicsState, ref pipeline, transformFeedbackDescriptors); + GpuAccessorState gpuAccessorState = new(samplerPoolMaximumId, poolState, default, graphicsState, specState, transformFeedbackDescriptors); + + ReadOnlySpan addressesSpan = addresses.AsSpan(); + + GpuAccessor[] gpuAccessors = new GpuAccessor[Constants.ShaderStages]; + TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1]; + TranslatorContext nextStage = null; + + TargetApi api = _context.Capabilities.Api; + + for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--) + { + ulong gpuVa = addressesSpan[stageIndex + 1]; + + if (gpuVa != 0) + { + GpuAccessor gpuAccessor = new(_context, channel, gpuAccessorState, stageIndex, addresses.Geometry != 0); + TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, gpuVa); + + if (nextStage != null) + { + currentStage.SetNextStage(nextStage); + } + + if (stageIndex == 0 && addresses.VertexA != 0) + { + translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA); + } + + gpuAccessors[stageIndex] = gpuAccessor; + translatorContexts[stageIndex + 1] = currentStage; + nextStage = currentStage; + } + } + + bool hasGeometryShader = translatorContexts[4] != null; + bool vertexHasStore = translatorContexts[1] != null && translatorContexts[1].HasStore; + bool geometryHasStore = hasGeometryShader && translatorContexts[4].HasStore; + bool vertexToCompute = ShouldConvertVertexToCompute(_context, vertexHasStore, geometryHasStore, hasGeometryShader); + bool geometryToCompute = ShouldConvertGeometryToCompute(_context, geometryHasStore); + + CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1]; + List shaderSources = new(); + + TranslatorContext previousStage = null; + ShaderInfoBuilder infoBuilder = new(_context, transformFeedbackDescriptors != null, vertexToCompute); + + if (geometryToCompute && translatorContexts[4] != null) + { + translatorContexts[4].SetVertexOutputMapForGeometryAsCompute(translatorContexts[1]); + } + + ShaderAsCompute vertexAsCompute = null; + ShaderAsCompute geometryAsCompute = null; + + for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++) + { + TranslatorContext currentStage = translatorContexts[stageIndex + 1]; + + if (currentStage != null) + { + gpuAccessors[stageIndex].InitializeReservedCounts(transformFeedbackDescriptors != null, vertexToCompute); + + ShaderProgram program; + + bool asCompute = (stageIndex == 0 && vertexToCompute) || (stageIndex == 3 && geometryToCompute); + + if (stageIndex == 0 && translatorContexts[0] != null) + { + TranslatedShaderVertexPair translatedShader = TranslateShader( + _dumper, + channel, + currentStage, + translatorContexts[0], + cachedGuestCode.VertexACode, + cachedGuestCode.VertexBCode, + asCompute); + + shaders[0] = translatedShader.VertexA; + shaders[1] = translatedShader.VertexB; + program = translatedShader.Program; + } + else + { + byte[] code = cachedGuestCode.GetByIndex(stageIndex); + + TranslatedShader translatedShader = TranslateShader(_dumper, channel, currentStage, code, asCompute); + + shaders[stageIndex + 1] = translatedShader.Shader; + program = translatedShader.Program; + } + + if (asCompute) + { + bool tfEnabled = transformFeedbackDescriptors != null; + + if (stageIndex == 0) + { + vertexAsCompute = CreateHostVertexAsComputeProgram(program, currentStage, tfEnabled); + + TranslatorContext lastInVertexPipeline = geometryToCompute ? translatorContexts[4] ?? currentStage : currentStage; + + program = lastInVertexPipeline.GenerateVertexPassthroughForCompute(); + } + else + { + geometryAsCompute = CreateHostVertexAsComputeProgram(program, currentStage, tfEnabled); + program = null; + } + } + + if (program != null) + { + shaderSources.Add(CreateShaderSource(program)); + infoBuilder.AddStageInfo(program.Info); + } + + previousStage = currentStage; + } + else if ( + previousStage != null && + previousStage.LayerOutputWritten && + stageIndex == 3 && + !_context.Capabilities.SupportsLayerVertexTessellation) + { + shaderSources.Add(CreateShaderSource(previousStage.GenerateGeometryPassthrough())); + } + } + + ShaderSource[] shaderSourcesArray = shaderSources.ToArray(); + + ShaderInfo info = infoBuilder.Build(pipeline); + + IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info); + + gpShaders = new(hostProgram, vertexAsCompute, geometryAsCompute, specState, shaders); + + _graphicsShaderCache.Add(gpShaders); + + // We don't currently support caching shaders that have been converted to compute. + if (vertexAsCompute == null) + { + EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray); + } + + _gpPrograms[addresses] = gpShaders; + + return gpShaders; + } + + /// + /// Checks if a vertex shader should be converted to a compute shader due to it making use of + /// features that are not supported on the host. + /// + /// GPU context of the shader + /// Whether the vertex shader has image or storage buffer store operations + /// Whether the geometry shader has image or storage buffer store operations, if one exists + /// Whether a geometry shader exists + /// True if the vertex shader should be converted to compute, false otherwise + public static bool ShouldConvertVertexToCompute(GpuContext context, bool vertexHasStore, bool geometryHasStore, bool hasGeometryShader) + { + // If the host does not support store operations on vertex, + // we need to emulate it on a compute shader. + if (!context.Capabilities.SupportsVertexStoreAndAtomics && vertexHasStore) + { + return true; + } + + // If any stage after the vertex stage is converted to compute, + // we need to convert vertex to compute too. + return hasGeometryShader && ShouldConvertGeometryToCompute(context, geometryHasStore); + } + + /// + /// Checks if a geometry shader should be converted to a compute shader due to it making use of + /// features that are not supported on the host. + /// + /// GPU context of the shader + /// Whether the geometry shader has image or storage buffer store operations, if one exists + /// True if the geometry shader should be converted to compute, false otherwise + public static bool ShouldConvertGeometryToCompute(GpuContext context, bool geometryHasStore) + { + return (!context.Capabilities.SupportsVertexStoreAndAtomics && geometryHasStore) || + !context.Capabilities.SupportsGeometryShader; + } + + /// + /// Checks if it might be necessary for any vertex, tessellation or geometry shader to be converted to compute, + /// based on the supported host features. + /// + /// Host capabilities + /// True if the possibility of a shader being converted to compute exists, false otherwise + public static bool MayConvertVtgToCompute(ref Capabilities capabilities) + { + return !capabilities.SupportsVertexStoreAndAtomics || !capabilities.SupportsGeometryShader; + } + + /// + /// Creates a compute shader from a vertex, tessellation or geometry shader that has been converted to compute. + /// + /// Shader program + /// Translation context of the shader + /// Whether transform feedback is enabled + /// Compute shader + private ShaderAsCompute CreateHostVertexAsComputeProgram(ShaderProgram program, TranslatorContext context, bool tfEnabled) + { + ShaderSource source = new(program.Code, program.BinaryCode, ShaderStage.Compute, program.Language); + ShaderInfo info = ShaderInfoBuilder.BuildForVertexAsCompute(_context, program.Info, tfEnabled); + + return new(_context.Renderer.CreateProgram(new[] { source }, info), program.Info, context.GetResourceReservations()); + } + + /// + /// Creates a shader source for use with the backend from a translated shader program. + /// + /// Translated shader program + /// Shader source + public static ShaderSource CreateShaderSource(ShaderProgram program) + { + return new ShaderSource(program.Code, program.BinaryCode, program.Info.Stage, program.Language); + } + + /// + /// Puts a program on the queue of programs to be saved on the disk cache. + /// + /// + /// This will not do anything if disk shader cache is disabled. + /// + /// Cached shader program + /// Host program + /// Source for each shader stage + private void EnqueueProgramToSave(CachedShaderProgram program, IProgram hostProgram, ShaderSource[] sources) + { + if (_diskCacheHostStorage.CacheEnabled) + { + byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(sources) : null; + ProgramToSave programToSave = new(program, hostProgram, binaryCode); + + _programsToSaveQueue.Enqueue(programToSave); + } + } + + /// + /// Gets transform feedback state from the current GPU state. + /// + /// Current GPU state + /// Four transform feedback descriptors for the enabled TFBs, or null if TFB is disabled + private static TransformFeedbackDescriptor[] GetTransformFeedbackDescriptors(ref ThreedClassState state) + { + bool tfEnable = state.TfEnable; + if (!tfEnable) + { + return null; + } + + TransformFeedbackDescriptor[] descs = new TransformFeedbackDescriptor[Constants.TotalTransformFeedbackBuffers]; + + for (int i = 0; i < Constants.TotalTransformFeedbackBuffers; i++) + { + var tf = state.TfState[i]; + + descs[i] = new TransformFeedbackDescriptor( + tf.BufferIndex, + tf.Stride, + tf.VaryingsCount, + ref state.TfVaryingLocations[i]); + } + + return descs; + } + + /// + /// Checks if compute shader code in memory is equal to the cached shader. + /// + /// GPU channel using the shader + /// GPU channel state to verify shader compatibility + /// GPU channel compute state to verify shader compatibility + /// Cached compute shader + /// GPU virtual address of the shader code in memory + /// True if the code is different, false otherwise + private static bool IsShaderEqual( + GpuChannel channel, + GpuChannelPoolState poolState, + GpuChannelComputeState computeState, + CachedShaderProgram cpShader, + ulong gpuVa) + { + if (IsShaderEqual(channel.MemoryManager, cpShader.Shaders[0], gpuVa)) + { + return cpShader.SpecializationState.MatchesCompute(channel, ref poolState, computeState, true); + } + + return false; + } + + /// + /// Checks if graphics shader code from all stages in memory are equal to the cached shaders. + /// + /// GPU channel using the shader + /// GPU channel state to verify shader compatibility + /// GPU channel graphics state to verify shader compatibility + /// Cached graphics shaders + /// GPU virtual addresses of all enabled shader stages + /// True if the code is different, false otherwise + private static bool IsShaderEqual( + GpuChannel channel, + ref GpuChannelPoolState poolState, + ref GpuChannelGraphicsState graphicsState, + CachedShaderProgram gpShaders, + ShaderAddresses addresses) + { + ReadOnlySpan addressesSpan = addresses.AsSpan(); + + for (int stageIndex = 0; stageIndex < gpShaders.Shaders.Length; stageIndex++) + { + CachedShaderStage shader = gpShaders.Shaders[stageIndex]; + + ulong gpuVa = addressesSpan[stageIndex]; + + if (!IsShaderEqual(channel.MemoryManager, shader, gpuVa)) + { + return false; + } + } + + bool vertexAsCompute = gpShaders.VertexAsCompute != null; + bool usesDrawParameters = gpShaders.Shaders[1]?.Info.UsesDrawParameters ?? false; + + return gpShaders.SpecializationState.MatchesGraphics( + channel, + ref poolState, + ref graphicsState, + vertexAsCompute, + usesDrawParameters, + checkTextures: true); + } + + /// + /// Checks if the code of the specified cached shader is different from the code in memory. + /// + /// Memory manager used to access the GPU memory where the shader is located + /// Cached shader to compare with + /// GPU virtual address of the binary shader code + /// True if the code is different, false otherwise + private static bool IsShaderEqual(MemoryManager memoryManager, CachedShaderStage shader, ulong gpuVa) + { + if (shader == null) + { + return true; + } + + ReadOnlySpan memoryCode = memoryManager.GetSpanMapped(gpuVa, shader.Code.Length); + + return memoryCode.SequenceEqual(shader.Code); + } + + /// + /// Decode the binary Maxwell shader code to a translator context. + /// + /// GPU state accessor + /// Graphics API that will be used with the shader + /// GPU virtual address of the binary shader code + /// The generated translator context + public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, TargetApi api, ulong gpuVa) + { + var options = CreateTranslationOptions(api, DefaultFlags | TranslationFlags.Compute); + return Translator.CreateContext(gpuVa, gpuAccessor, options); + } + + /// + /// Decode the binary Maxwell shader code to a translator context. + /// + /// + /// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader. + /// + /// GPU state accessor + /// Graphics API that will be used with the shader + /// Flags that controls shader translation + /// GPU virtual address of the shader code + /// The generated translator context + public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TargetApi api, TranslationFlags flags, ulong gpuVa) + { + var options = CreateTranslationOptions(api, flags); + return Translator.CreateContext(gpuVa, gpuAccessor, options); + } + + /// + /// Translates a previously generated translator context to something that the host API accepts. + /// + /// Optional shader code dumper + /// GPU channel using the shader + /// Translator context of the stage to be translated + /// Optional translator context of the shader that should be combined + /// Optional Maxwell binary code of the Vertex A shader, if present + /// Optional Maxwell binary code of the Vertex B or current stage shader, if present on cache + /// Indicates that the vertex shader should be converted to a compute shader + /// Compiled graphics shader code + private static TranslatedShaderVertexPair TranslateShader( + ShaderDumper dumper, + GpuChannel channel, + TranslatorContext currentStage, + TranslatorContext vertexA, + byte[] codeA, + byte[] codeB, + bool asCompute) + { + ulong cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(0, 1); + + var memoryManager = channel.MemoryManager; + + codeA ??= memoryManager.GetSpan(vertexA.Address, vertexA.Size).ToArray(); + codeB ??= memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray(); + byte[] cb1DataA = ReadArray(memoryManager, cb1DataAddress, vertexA.Cb1DataSize); + byte[] cb1DataB = ReadArray(memoryManager, cb1DataAddress, currentStage.Cb1DataSize); + + ShaderDumpPaths pathsA = default; + ShaderDumpPaths pathsB = default; + + if (dumper != null) + { + pathsA = dumper.Dump(codeA, compute: false); + pathsB = dumper.Dump(codeB, compute: false); + } + + ShaderProgram program = currentStage.Translate(vertexA, asCompute); + + pathsB.Prepend(program); + pathsA.Prepend(program); + + CachedShaderStage vertexAStage = new(null, codeA, cb1DataA); + CachedShaderStage vertexBStage = new(program.Info, codeB, cb1DataB); + + return new TranslatedShaderVertexPair(vertexAStage, vertexBStage, program); + } + + /// + /// Translates a previously generated translator context to something that the host API accepts. + /// + /// Optional shader code dumper + /// GPU channel using the shader + /// Translator context of the stage to be translated + /// Optional Maxwell binary code of the current stage shader, if present on cache + /// Indicates that the vertex shader should be converted to a compute shader + /// Compiled graphics shader code + private static TranslatedShader TranslateShader(ShaderDumper dumper, GpuChannel channel, TranslatorContext context, byte[] code, bool asCompute) + { + var memoryManager = channel.MemoryManager; + + ulong cb1DataAddress = context.Stage == ShaderStage.Compute + ? channel.BufferManager.GetComputeUniformBufferAddress(1) + : channel.BufferManager.GetGraphicsUniformBufferAddress(StageToStageIndex(context.Stage), 1); + + byte[] cb1Data = ReadArray(memoryManager, cb1DataAddress, context.Cb1DataSize); + code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray(); + + ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default; + ShaderProgram program = context.Translate(asCompute); + + paths.Prepend(program); + + return new TranslatedShader(new CachedShaderStage(program.Info, code, cb1Data), program); + } + + /// + /// Reads data from physical memory, returns an empty array if the memory is unmapped or size is 0. + /// + /// Memory manager with the physical memory to read from + /// Physical address of the region to read + /// Size in bytes of the data + /// An array with the data at the specified memory location + private static byte[] ReadArray(MemoryManager memoryManager, ulong address, int size) + { + if (address == MemoryManager.PteUnmapped || size == 0) + { + return Array.Empty(); + } + + return memoryManager.Physical.GetSpan(address, size).ToArray(); + } + + /// + /// Gets the index of a stage from a . + /// + /// Stage to get the index from + /// Stage index + private static int StageToStageIndex(ShaderStage stage) + { + return stage switch + { + ShaderStage.TessellationControl => 1, + ShaderStage.TessellationEvaluation => 2, + ShaderStage.Geometry => 3, + ShaderStage.Fragment => 4, + _ => 0, + }; + } + + /// + /// Creates shader translation options with the requested graphics API and flags. + /// The shader language is choosen based on the current configuration and graphics API. + /// + /// Target graphics API + /// Translation flags + /// Translation options + private static TranslationOptions CreateTranslationOptions(TargetApi api, TranslationFlags flags) + { + TargetLanguage lang = GraphicsConfig.EnableSpirvCompilationOnVulkan && api == TargetApi.Vulkan + ? TargetLanguage.Spirv + : TargetLanguage.Glsl; + + return new TranslationOptions(lang, api, flags); + } + + /// + /// Disposes the shader cache, deleting all the cached shaders. + /// It's an error to use the shader cache after disposal. + /// + public void Dispose() + { + foreach (CachedShaderProgram program in _graphicsShaderCache.GetPrograms()) + { + program.Dispose(); + } + + foreach (CachedShaderProgram program in _computeShaderCache.GetPrograms()) + { + program.Dispose(); + } + + _cacheWriter?.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs new file mode 100644 index 00000000..e65a1dec --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs @@ -0,0 +1,282 @@ +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.Shader.HashTable; +using Ryujinx.Graphics.Shader; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Holds already cached code for a guest shader. + /// + struct CachedGraphicsGuestCode + { + public byte[] VertexACode; + public byte[] VertexBCode; + public byte[] TessControlCode; + public byte[] TessEvaluationCode; + public byte[] GeometryCode; + public byte[] FragmentCode; + + /// + /// Gets the guest code of a shader stage by its index. + /// + /// Index of the shader stage + /// Guest code, or null if not present + public readonly byte[] GetByIndex(int stageIndex) + { + return stageIndex switch + { + 1 => TessControlCode, + 2 => TessEvaluationCode, + 3 => GeometryCode, + 4 => FragmentCode, + _ => VertexBCode, + }; + } + } + + /// + /// Graphics shader cache hash table. + /// + class ShaderCacheHashTable + { + /// + /// Shader ID cache. + /// + private struct IdCache + { + private PartitionedHashTable _cache; + private int _id; + + /// + /// Initializes the state. + /// + public void Initialize() + { + _cache = new PartitionedHashTable(); + _id = 0; + } + + /// + /// Adds guest code to the cache. + /// + /// + /// If the code was already cached, it will just return the existing ID. + /// + /// Code to add + /// Unique ID for the guest code + public int Add(byte[] code) + { + int id = ++_id; + int cachedId = _cache.GetOrAdd(code, id); + if (cachedId != id) + { + --_id; + } + + return cachedId; + } + + /// + /// Tries to find cached guest code. + /// + /// Code accessor used to read guest code to find a match on the hash table + /// ID of the guest code, if found + /// Cached guest code, if found + /// True if found, false otherwise + public readonly bool TryFind(IDataAccessor dataAccessor, out int id, out byte[] data) + { + return _cache.TryFindItem(dataAccessor, out id, out data); + } + } + + /// + /// Guest code IDs of the guest shaders that when combined forms a single host program. + /// + private struct IdTable : IEquatable + { + public int VertexAId; + public int VertexBId; + public int TessControlId; + public int TessEvaluationId; + public int GeometryId; + public int FragmentId; + + public readonly override bool Equals(object obj) + { + return obj is IdTable other && Equals(other); + } + + public readonly bool Equals(IdTable other) + { + return other.VertexAId == VertexAId && + other.VertexBId == VertexBId && + other.TessControlId == TessControlId && + other.TessEvaluationId == TessEvaluationId && + other.GeometryId == GeometryId && + other.FragmentId == FragmentId; + } + + public readonly override int GetHashCode() + { + return HashCode.Combine(VertexAId, VertexBId, TessControlId, TessEvaluationId, GeometryId, FragmentId); + } + } + + private IdCache _vertexACache; + private IdCache _vertexBCache; + private IdCache _tessControlCache; + private IdCache _tessEvaluationCache; + private IdCache _geometryCache; + private IdCache _fragmentCache; + + private readonly Dictionary _shaderPrograms; + + /// + /// Creates a new graphics shader cache hash table. + /// + public ShaderCacheHashTable() + { + _vertexACache.Initialize(); + _vertexBCache.Initialize(); + _tessControlCache.Initialize(); + _tessEvaluationCache.Initialize(); + _geometryCache.Initialize(); + _fragmentCache.Initialize(); + + _shaderPrograms = new Dictionary(); + } + + /// + /// Adds a program to the cache. + /// + /// Program to be added + public void Add(CachedShaderProgram program) + { + IdTable idTable = new(); + + foreach (var shader in program.Shaders) + { + if (shader == null) + { + continue; + } + + if (shader.Info != null) + { + switch (shader.Info.Stage) + { + case ShaderStage.Vertex: + idTable.VertexBId = _vertexBCache.Add(shader.Code); + break; + case ShaderStage.TessellationControl: + idTable.TessControlId = _tessControlCache.Add(shader.Code); + break; + case ShaderStage.TessellationEvaluation: + idTable.TessEvaluationId = _tessEvaluationCache.Add(shader.Code); + break; + case ShaderStage.Geometry: + idTable.GeometryId = _geometryCache.Add(shader.Code); + break; + case ShaderStage.Fragment: + idTable.FragmentId = _fragmentCache.Add(shader.Code); + break; + } + } + else + { + idTable.VertexAId = _vertexACache.Add(shader.Code); + } + } + + if (!_shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList)) + { + specList = new ShaderSpecializationList(); + _shaderPrograms.Add(idTable, specList); + } + + specList.Add(program); + } + + /// + /// Tries to find a cached program. + /// + /// + /// Even if false is returned, might still contain cached guest code. + /// This can be used to avoid additional allocations for guest code that was already cached. + /// + /// GPU channel + /// Texture pool state + /// Graphics state + /// Guest addresses of the shaders to find + /// Cached host program for the given state, if found + /// Cached guest code, if any found + /// True if a cached host program was found, false otherwise + public bool TryFind( + GpuChannel channel, + ref GpuChannelPoolState poolState, + ref GpuChannelGraphicsState graphicsState, + ShaderAddresses addresses, + out CachedShaderProgram program, + out CachedGraphicsGuestCode guestCode) + { + var memoryManager = channel.MemoryManager; + IdTable idTable = new(); + guestCode = new CachedGraphicsGuestCode(); + + program = null; + + bool found = TryGetId(_vertexACache, memoryManager, addresses.VertexA, out idTable.VertexAId, out guestCode.VertexACode); + found &= TryGetId(_vertexBCache, memoryManager, addresses.VertexB, out idTable.VertexBId, out guestCode.VertexBCode); + found &= TryGetId(_tessControlCache, memoryManager, addresses.TessControl, out idTable.TessControlId, out guestCode.TessControlCode); + found &= TryGetId(_tessEvaluationCache, memoryManager, addresses.TessEvaluation, out idTable.TessEvaluationId, out guestCode.TessEvaluationCode); + found &= TryGetId(_geometryCache, memoryManager, addresses.Geometry, out idTable.GeometryId, out guestCode.GeometryCode); + found &= TryGetId(_fragmentCache, memoryManager, addresses.Fragment, out idTable.FragmentId, out guestCode.FragmentCode); + + if (found && _shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList)) + { + return specList.TryFindForGraphics(channel, ref poolState, ref graphicsState, out program); + } + + return false; + } + + /// + /// Tries to get the ID of a single cached shader stage. + /// + /// ID cache of the stage + /// GPU memory manager + /// Base address of the shader + /// ID, if found + /// Cached guest code, if found + /// True if a cached shader is found, false otherwise + private static bool TryGetId(IdCache idCache, MemoryManager memoryManager, ulong baseAddress, out int id, out byte[] data) + { + if (baseAddress == 0) + { + id = 0; + data = null; + return true; + } + + ShaderCodeAccessor codeAccessor = new(memoryManager, baseAddress); + return idCache.TryFind(codeAccessor, out id, out data); + } + + /// + /// Gets all programs that have been added to the table. + /// + /// Programs added to the table + public IEnumerable GetPrograms() + { + foreach (var specList in _shaderPrograms.Values) + { + foreach (var program in specList) + { + yield return program; + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheState.cs new file mode 100644 index 00000000..075e3a61 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheState.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// Shader cache loading states + public enum ShaderCacheState + { + /// Shader cache started loading + Start, + /// Shader cache is loading + Loading, + /// Shader cache is written to disk + Packaging, + /// Shader cache finished loading + Loaded, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCodeAccessor.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCodeAccessor.cs new file mode 100644 index 00000000..240a4ce9 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCodeAccessor.cs @@ -0,0 +1,32 @@ +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.Shader.HashTable; +using System; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Shader code accessor. + /// + readonly struct ShaderCodeAccessor : IDataAccessor + { + private readonly MemoryManager _memoryManager; + private readonly ulong _baseAddress; + + /// + /// Creates a new shader code accessor. + /// + /// Memory manager used to access the shader code + /// Base address of the shader in memory + public ShaderCodeAccessor(MemoryManager memoryManager, ulong baseAddress) + { + _memoryManager = memoryManager; + _baseAddress = baseAddress; + } + + /// + public ReadOnlySpan GetSpan(int offset, int length) + { + return _memoryManager.GetSpanMapped(_baseAddress + (ulong)offset, length); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderDumpPaths.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderDumpPaths.cs new file mode 100644 index 00000000..d0765963 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderDumpPaths.cs @@ -0,0 +1,49 @@ +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Paths where shader code was dumped on disk. + /// + readonly struct ShaderDumpPaths + { + /// + /// Path where the full shader code with header was dumped, or null if not dumped. + /// + public string FullPath { get; } + + /// + /// Path where the shader code without header was dumped, or null if not dumped. + /// + public string CodePath { get; } + + /// + /// True if the shader was dumped, false otherwise. + /// + public bool HasPath => FullPath != null && CodePath != null; + + /// + /// Creates a new shader dumps path structure. + /// + /// Path where the full shader code with header was dumped, or null if not dumped + /// Path where the shader code without header was dumped, or null if not dumped + public ShaderDumpPaths(string fullPath, string codePath) + { + FullPath = fullPath; + CodePath = codePath; + } + + /// + /// Prepends the shader paths on the program source, as a comment. + /// + /// Program to prepend into + public void Prepend(ShaderProgram program) + { + if (HasPath) + { + program.Prepend("// " + CodePath); + program.Prepend("// " + FullPath); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderDumper.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderDumper.cs new file mode 100644 index 00000000..80d599e9 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderDumper.cs @@ -0,0 +1,129 @@ +using System.IO; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Shader dumper, writes binary shader code to disk. + /// + class ShaderDumper + { + private string _runtimeDir; + private string _dumpPath; + + /// + /// Current index of the shader dump binary file. + /// This is incremented after each save, in order to give unique names to the files. + /// + public int CurrentDumpIndex { get; private set; } + + /// + /// Creates a new instance of the shader dumper. + /// + public ShaderDumper() + { + CurrentDumpIndex = 1; + } + + /// + /// Dumps shader code to disk. + /// + /// Code to be dumped + /// True for compute shader code, false for graphics shader code + /// Paths where the shader code was dumped + public ShaderDumpPaths Dump(byte[] code, bool compute) + { + _dumpPath = GraphicsConfig.ShadersDumpPath; + + if (string.IsNullOrWhiteSpace(_dumpPath)) + { + return default; + } + + string fileName = "Shader" + CurrentDumpIndex.ToString("d4") + ".bin"; + + string fullPath = Path.Combine(FullDir(), fileName); + string codePath = Path.Combine(CodeDir(), fileName); + + CurrentDumpIndex++; + + using MemoryStream stream = new(code); + BinaryReader codeReader = new(stream); + + using FileStream fullFile = File.Create(fullPath); + using FileStream codeFile = File.Create(codePath); + BinaryWriter fullWriter = new(fullFile); + BinaryWriter codeWriter = new(codeFile); + + int headerSize = compute ? 0 : 0x50; + + fullWriter.Write(codeReader.ReadBytes(headerSize)); + + byte[] temp = codeReader.ReadBytes(code.Length - headerSize); + + fullWriter.Write(temp); + codeWriter.Write(temp); + + // Align to meet nvdisasm requirements. + while (codeFile.Length % 0x20 != 0) + { + codeWriter.Write(0); + } + + return new ShaderDumpPaths(fullPath, codePath); + } + + /// + /// Returns the output directory for shader code with header. + /// + /// Directory path + private string FullDir() + { + return CreateAndReturn(Path.Combine(DumpDir(), "Full")); + } + + /// + /// Returns the output directory for shader code without header. + /// + /// Directory path + private string CodeDir() + { + return CreateAndReturn(Path.Combine(DumpDir(), "Code")); + } + + /// + /// Returns the full output directory for the current shader dump. + /// + /// Directory path + private string DumpDir() + { + if (string.IsNullOrEmpty(_runtimeDir)) + { + int index = 1; + + do + { + _runtimeDir = Path.Combine(_dumpPath, "Dumps" + index.ToString("d2")); + + index++; + } + while (Directory.Exists(_runtimeDir)); + + Directory.CreateDirectory(_runtimeDir); + } + + return _runtimeDir; + } + + /// + /// Creates a new specified directory if needed. + /// + /// The directory to create + /// The same directory passed to the method + private static string CreateAndReturn(string dir) + { + Directory.CreateDirectory(dir); + + return dir; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs new file mode 100644 index 00000000..49823562 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs @@ -0,0 +1,434 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Shader info structure builder. + /// + class ShaderInfoBuilder + { + private const ResourceStages SupportBufferStages = + ResourceStages.Compute | + ResourceStages.Vertex | + ResourceStages.Fragment; + + private const ResourceStages VtgStages = + ResourceStages.Vertex | + ResourceStages.TessellationControl | + ResourceStages.TessellationEvaluation | + ResourceStages.Geometry; + + private readonly GpuContext _context; + + private int _fragmentOutputMap; + + private readonly int _reservedConstantBuffers; + private readonly int _reservedStorageBuffers; + private readonly int _reservedTextures; + private readonly int _reservedImages; + + private List[] _resourceDescriptors; + private List[] _resourceUsages; + + /// + /// Creates a new shader info builder. + /// + /// GPU context that owns the shaders that will be added to the builder + /// Indicates if the graphics shader is used with transform feedback enabled + /// Indicates that the vertex shader will be emulated on a compute shader + public ShaderInfoBuilder(GpuContext context, bool tfEnabled, bool vertexAsCompute = false) + { + _context = context; + + _fragmentOutputMap = -1; + + int uniformSetIndex = context.Capabilities.UniformBufferSetIndex; + int storageSetIndex = context.Capabilities.StorageBufferSetIndex; + int textureSetIndex = context.Capabilities.TextureSetIndex; + int imageSetIndex = context.Capabilities.ImageSetIndex; + + int totalSets = Math.Max(uniformSetIndex, storageSetIndex); + totalSets = Math.Max(totalSets, textureSetIndex); + totalSets = Math.Max(totalSets, imageSetIndex); + totalSets++; + + _resourceDescriptors = new List[totalSets]; + _resourceUsages = new List[totalSets]; + + for (int index = 0; index < totalSets; index++) + { + _resourceDescriptors[index] = new(); + _resourceUsages[index] = new(); + } + + AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, uniformSetIndex, 0, 1); + AddUsage(SupportBufferStages, ResourceType.UniformBuffer, uniformSetIndex, 0, 1); + + ResourceReservationCounts rrc = new(!context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute); + + _reservedConstantBuffers = rrc.ReservedConstantBuffers; + _reservedStorageBuffers = rrc.ReservedStorageBuffers; + _reservedTextures = rrc.ReservedTextures; + _reservedImages = rrc.ReservedImages; + + // TODO: Handle that better? Maybe we should only set the binding that are really needed on each shader. + ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages; + + PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, uniformSetIndex, 1, rrc.ReservedConstantBuffers - 1); + PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, storageSetIndex, 0, rrc.ReservedStorageBuffers, true); + PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, textureSetIndex, 0, rrc.ReservedTextures); + PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages, true); + } + + /// + /// Populates descriptors and usages for vertex as compute and transform feedback emulation reserved resources. + /// + /// Shader stages where the resources are used + /// Resource type + /// Resource set index where the resources are used + /// First binding number + /// Amount of bindings + /// True if the binding is written from the shader, false otherwise + private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count, bool write = false) + { + AddDescriptor(stages, type, setIndex, start, count); + AddUsage(stages, type, setIndex, start, count, write); + } + + /// + /// Adds information from a given shader stage. + /// + /// Shader stage information + /// True if the shader stage has been converted into a compute shader + public void AddStageInfo(ShaderProgramInfo info, bool vertexAsCompute = false) + { + if (info.Stage == ShaderStage.Fragment) + { + _fragmentOutputMap = info.FragmentOutputMap; + } + + int stageIndex = GpuAccessorBase.GetStageIndex(info.Stage switch + { + ShaderStage.TessellationControl => 1, + ShaderStage.TessellationEvaluation => 2, + ShaderStage.Geometry => 3, + ShaderStage.Fragment => 4, + _ => 0, + }); + + ResourceStages stages = vertexAsCompute ? ResourceStages.Compute : info.Stage switch + { + ShaderStage.Compute => ResourceStages.Compute, + ShaderStage.Vertex => ResourceStages.Vertex, + ShaderStage.TessellationControl => ResourceStages.TessellationControl, + ShaderStage.TessellationEvaluation => ResourceStages.TessellationEvaluation, + ShaderStage.Geometry => ResourceStages.Geometry, + ShaderStage.Fragment => ResourceStages.Fragment, + _ => ResourceStages.None, + }; + + int uniformsPerStage = (int)_context.Capabilities.MaximumUniformBuffersPerStage; + int storagesPerStage = (int)_context.Capabilities.MaximumStorageBuffersPerStage; + int texturesPerStage = (int)_context.Capabilities.MaximumTexturesPerStage; + int imagesPerStage = (int)_context.Capabilities.MaximumImagesPerStage; + + int uniformBinding = _reservedConstantBuffers + stageIndex * uniformsPerStage; + int storageBinding = _reservedStorageBuffers + stageIndex * storagesPerStage; + int textureBinding = _reservedTextures + stageIndex * texturesPerStage * 2; + int imageBinding = _reservedImages + stageIndex * imagesPerStage * 2; + + int uniformSetIndex = _context.Capabilities.UniformBufferSetIndex; + int storageSetIndex = _context.Capabilities.StorageBufferSetIndex; + int textureSetIndex = _context.Capabilities.TextureSetIndex; + int imageSetIndex = _context.Capabilities.ImageSetIndex; + + AddDescriptor(stages, ResourceType.UniformBuffer, uniformSetIndex, uniformBinding, uniformsPerStage); + AddDescriptor(stages, ResourceType.StorageBuffer, storageSetIndex, storageBinding, storagesPerStage); + AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, textureSetIndex, textureBinding, texturesPerStage); + AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, imageSetIndex, imageBinding, imagesPerStage); + + AddArrayDescriptors(info.Textures, stages, isImage: false); + AddArrayDescriptors(info.Images, stages, isImage: true); + + AddUsage(info.CBuffers, stages, isStorage: false); + AddUsage(info.SBuffers, stages, isStorage: true); + AddUsage(info.Textures, stages, isImage: false); + AddUsage(info.Images, stages, isImage: true); + } + + /// + /// Adds a resource descriptor to the list of descriptors. + /// + /// Shader stages where the resource is used + /// Type of the resource + /// Descriptor set number where the resource will be bound + /// Binding number where the resource will be bound + /// Number of resources bound at the binding location + private void AddDescriptor(ResourceStages stages, ResourceType type, int setIndex, int binding, int count) + { + for (int index = 0; index < count; index++) + { + _resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding + index, 1, type, stages)); + } + } + + /// + /// Adds two interleaved groups of resources to the list of descriptors. + /// + /// Shader stages where the resource is used + /// Type of the first interleaved resource + /// Type of the second interleaved resource + /// Descriptor set number where the resource will be bound + /// Binding number where the resource will be bound + /// Number of resources bound at the binding location + private void AddDualDescriptor(ResourceStages stages, ResourceType type, ResourceType type2, int setIndex, int binding, int count) + { + AddDescriptor(stages, type, setIndex, binding, count); + AddDescriptor(stages, type2, setIndex, binding + count, count); + } + + /// + /// Adds all array descriptors (those with an array length greater than one). + /// + /// Textures to be added + /// Stages where the textures are used + /// True for images, false for textures + private void AddArrayDescriptors(IEnumerable textures, ResourceStages stages, bool isImage) + { + foreach (TextureDescriptor texture in textures) + { + if (texture.ArrayLength > 1) + { + ResourceType type = GetTextureResourceType(texture, isImage); + + GetDescriptors(texture.Set).Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages)); + } + } + } + + /// + /// Adds buffer usage information to the list of usages. + /// + /// Shader stages where the resource is used + /// Type of the resource + /// Descriptor set number where the resource will be bound + /// Binding number where the resource will be bound + /// Number of resources bound at the binding location + /// True if the binding is written from the shader, false otherwise + private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count, bool write = false) + { + for (int index = 0; index < count; index++) + { + _resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages, write)); + } + } + + /// + /// Adds buffer usage information to the list of usages. + /// + /// Buffers to be added + /// Stages where the buffers are used + /// True for storage buffers, false for uniform buffers + private void AddUsage(IEnumerable buffers, ResourceStages stages, bool isStorage) + { + foreach (BufferDescriptor buffer in buffers) + { + GetUsages(buffer.Set).Add(new ResourceUsage( + buffer.Binding, + 1, + isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer, + stages, + buffer.Flags.HasFlag(BufferUsageFlags.Write))); + } + } + + /// + /// Adds texture usage information to the list of usages. + /// + /// Textures to be added + /// Stages where the textures are used + /// True for images, false for textures + private void AddUsage(IEnumerable textures, ResourceStages stages, bool isImage) + { + foreach (TextureDescriptor texture in textures) + { + ResourceType type = GetTextureResourceType(texture, isImage); + + GetUsages(texture.Set).Add(new ResourceUsage( + texture.Binding, + texture.ArrayLength, + type, + stages, + texture.Flags.HasFlag(TextureUsageFlags.ImageStore))); + } + } + + /// + /// Gets the list of resource descriptors for a given set index. A new list will be created if needed. + /// + /// Resource set index + /// List of resource descriptors + private List GetDescriptors(int setIndex) + { + if (_resourceDescriptors.Length <= setIndex) + { + int oldLength = _resourceDescriptors.Length; + Array.Resize(ref _resourceDescriptors, setIndex + 1); + + for (int index = oldLength; index <= setIndex; index++) + { + _resourceDescriptors[index] = new(); + } + } + + return _resourceDescriptors[setIndex]; + } + + /// + /// Gets the list of resource usages for a given set index. A new list will be created if needed. + /// + /// Resource set index + /// List of resource usages + private List GetUsages(int setIndex) + { + if (_resourceUsages.Length <= setIndex) + { + int oldLength = _resourceUsages.Length; + Array.Resize(ref _resourceUsages, setIndex + 1); + + for (int index = oldLength; index <= setIndex; index++) + { + _resourceUsages[index] = new(); + } + } + + return _resourceUsages[setIndex]; + } + + /// + /// Gets a resource type from a texture descriptor. + /// + /// Texture descriptor + /// Whether the texture is a image texture (writable) or not (sampled) + /// Resource type + private static ResourceType GetTextureResourceType(TextureDescriptor texture, bool isImage) + { + bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer; + + if (isBuffer) + { + return isImage ? ResourceType.BufferImage : ResourceType.BufferTexture; + } + else if (isImage) + { + return ResourceType.Image; + } + else if (texture.Type == SamplerType.None) + { + return ResourceType.Sampler; + } + else if (texture.Separate) + { + return ResourceType.Texture; + } + else + { + return ResourceType.TextureAndSampler; + } + } + + /// + /// Creates a new shader information structure from the added information. + /// + /// Optional pipeline state for background shader compilation + /// Indicates if the shader comes from a disk cache + /// Shader information + public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false) + { + int totalSets = _resourceDescriptors.Length; + + var descriptors = new ResourceDescriptorCollection[totalSets]; + var usages = new ResourceUsageCollection[totalSets]; + + for (int index = 0; index < totalSets; index++) + { + descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly()); + usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly()); + } + + ResourceLayout resourceLayout = new(descriptors.AsReadOnly(), usages.AsReadOnly()); + + if (pipeline.HasValue) + { + return new ShaderInfo(_fragmentOutputMap, resourceLayout, pipeline.Value, fromCache); + } + else + { + return new ShaderInfo(_fragmentOutputMap, resourceLayout, fromCache); + } + } + + /// + /// Builds shader information for shaders from the disk cache. + /// + /// GPU context that owns the shaders + /// Shaders from the disk cache + /// Optional pipeline for background compilation + /// Indicates if the graphics shader is used with transform feedback enabled + /// Shader information + public static ShaderInfo BuildForCache( + GpuContext context, + IEnumerable programs, + ProgramPipelineState? pipeline, + bool tfEnabled) + { + ShaderInfoBuilder builder = new(context, tfEnabled); + + foreach (CachedShaderStage program in programs) + { + if (program?.Info != null) + { + builder.AddStageInfo(program.Info); + } + } + + return builder.Build(pipeline, fromCache: true); + } + + /// + /// Builds shader information for a compute shader. + /// + /// GPU context that owns the shader + /// Compute shader information + /// True if the compute shader comes from a disk cache, false otherwise + /// Shader information + public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, bool fromCache = false) + { + ShaderInfoBuilder builder = new(context, tfEnabled: false, vertexAsCompute: false); + + builder.AddStageInfo(info); + + return builder.Build(null, fromCache); + } + + /// + /// Builds shader information for a vertex or geometry shader thas was converted to compute shader. + /// + /// GPU context that owns the shader + /// Compute shader information + /// Indicates if the graphics shader is used with transform feedback enabled + /// True if the compute shader comes from a disk cache, false otherwise + /// Shader information + public static ShaderInfo BuildForVertexAsCompute(GpuContext context, ShaderProgramInfo info, bool tfEnabled, bool fromCache = false) + { + ShaderInfoBuilder builder = new(context, tfEnabled, vertexAsCompute: true); + + builder.AddStageInfo(info, vertexAsCompute: true); + + return builder.Build(null, fromCache); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs new file mode 100644 index 00000000..3c2f0b9b --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs @@ -0,0 +1,91 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// List of cached shader programs that differs only by specialization state. + /// + class ShaderSpecializationList : IEnumerable + { + private readonly List _entries = new(); + + /// + /// Adds a program to the list. + /// + /// Program to be added + public void Add(CachedShaderProgram program) + { + _entries.Add(program); + } + + /// + /// Tries to find an existing 3D program on the cache. + /// + /// GPU channel + /// Texture pool state + /// Graphics state + /// Cached program, if found + /// True if a compatible program is found, false otherwise + public bool TryFindForGraphics( + GpuChannel channel, + ref GpuChannelPoolState poolState, + ref GpuChannelGraphicsState graphicsState, + out CachedShaderProgram program) + { + foreach (var entry in _entries) + { + bool vertexAsCompute = entry.VertexAsCompute != null; + bool usesDrawParameters = entry.Shaders[1]?.Info.UsesDrawParameters ?? false; + + if (entry.SpecializationState.MatchesGraphics( + channel, + ref poolState, + ref graphicsState, + vertexAsCompute, + usesDrawParameters, + checkTextures: true)) + { + program = entry; + return true; + } + } + + program = default; + return false; + } + + /// + /// Tries to find an existing compute program on the cache. + /// + /// GPU channel + /// Texture pool state + /// Compute state + /// Cached program, if found + /// True if a compatible program is found, false otherwise + public bool TryFindForCompute(GpuChannel channel, GpuChannelPoolState poolState, GpuChannelComputeState computeState, out CachedShaderProgram program) + { + foreach (var entry in _entries) + { + if (entry.SpecializationState.MatchesCompute(channel, ref poolState, computeState, true)) + { + program = entry; + return true; + } + } + + program = default; + return false; + } + + public IEnumerator GetEnumerator() + { + return _entries.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs new file mode 100644 index 00000000..98acb6f2 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -0,0 +1,1052 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.Shader.DiskCache; +using Ryujinx.Graphics.Shader; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + class ShaderSpecializationState + { + private const uint ComsMagic = (byte)'C' | ((byte)'O' << 8) | ((byte)'M' << 16) | ((byte)'S' << 24); + private const uint GfxsMagic = (byte)'G' | ((byte)'F' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24); + private const uint TfbdMagic = (byte)'T' | ((byte)'F' << 8) | ((byte)'B' << 16) | ((byte)'D' << 24); + private const uint TexkMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'K' << 24); + private const uint TexsMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24); + private const uint PgpsMagic = (byte)'P' | ((byte)'G' << 8) | ((byte)'P' << 16) | ((byte)'S' << 24); + + /// + /// Flags indicating GPU state that is used by the shader. + /// + [Flags] + private enum QueriedStateFlags + { + PrimitiveTopology = 1 << 1, + TransformFeedback = 1 << 3, + TextureArrayFromBuffer = 1 << 4, + TextureArrayFromPool = 1 << 5, + } + + private QueriedStateFlags _queriedState; + private bool _compute; + private byte _constantBufferUsePerStage; + + /// + /// Compute engine state. + /// + public GpuChannelComputeState ComputeState; + + /// + /// 3D engine state. + /// + public GpuChannelGraphicsState GraphicsState; + + /// + /// Contant buffers bound at the time the shader was compiled, per stage. + /// + public Array5 ConstantBufferUse; + + /// + /// Pipeline state captured at the time of shader use. + /// + public ProgramPipelineState? PipelineState; + + /// + /// Transform feedback buffers active at the time the shader was compiled. + /// + public TransformFeedbackDescriptor[] TransformFeedbackDescriptors; + + /// + /// Flags indicating texture state that is used by the shader. + /// + [Flags] + private enum QueriedTextureStateFlags + { + TextureFormat = 1 << 0, + SamplerType = 1 << 1, + CoordNormalized = 1 << 2, + } + + /// + /// Reference type wrapping a value. + /// + private class Box + { + /// + /// Wrapped value. + /// + public T Value; + } + + /// + /// State of a texture or image that is accessed by the shader. + /// + private struct TextureSpecializationState + { + // New fields should be added to the end of the struct to keep disk shader cache compatibility. + + /// + /// Flags indicating which state of the texture the shader depends on. + /// + public QueriedTextureStateFlags QueriedFlags; + + /// + /// Encoded texture format value. + /// + public uint Format; + + /// + /// True if the texture format is sRGB, false otherwise. + /// + public bool FormatSrgb; + + /// + /// Texture target. + /// + public TextureTarget TextureTarget; + + /// + /// Indicates if the coordinates used to sample the texture are normalized or not (0.0..1.0 or 0..Width/Height). + /// + public bool CoordNormalized; + } + + /// + /// Texture binding information, used to identify each texture accessed by the shader. + /// + private readonly record struct TextureKey + { + // New fields should be added to the end of the struct to keep disk shader cache compatibility. + + /// + /// Shader stage where the texture is used. + /// + public readonly int StageIndex; + + /// + /// Texture handle offset in words on the texture buffer. + /// + public readonly int Handle; + + /// + /// Constant buffer slot of the texture buffer (-1 to use the texture buffer index GPU register). + /// + public readonly int CbufSlot; + + /// + /// Creates a new texture key. + /// + /// Shader stage where the texture is used + /// Texture handle offset in words on the texture buffer + /// Constant buffer slot of the texture buffer (-1 to use the texture buffer index GPU register) + public TextureKey(int stageIndex, int handle, int cbufSlot) + { + StageIndex = stageIndex; + Handle = handle; + CbufSlot = cbufSlot; + } + } + + private readonly Dictionary> _textureSpecialization; + private readonly Dictionary _textureArrayFromBufferSpecialization; + private readonly Dictionary _textureArrayFromPoolSpecialization; + private KeyValuePair>[] _allTextures; + private Box[][] _textureByBinding; + private Box[][] _imageByBinding; + + /// + /// Creates a new instance of the shader specialization state. + /// + private ShaderSpecializationState() + { + _textureSpecialization = new Dictionary>(); + _textureArrayFromBufferSpecialization = new Dictionary(); + _textureArrayFromPoolSpecialization = new Dictionary(); + } + + /// + /// Creates a new instance of the shader specialization state. + /// + /// Current compute engine state + public ShaderSpecializationState(ref GpuChannelComputeState state) : this() + { + ComputeState = state; + _compute = true; + } + + /// + /// Creates a new instance of the shader specialization state. + /// + /// Current 3D engine state + /// Optional transform feedback buffers in use, if any + private ShaderSpecializationState(ref GpuChannelGraphicsState state, TransformFeedbackDescriptor[] descriptors) : this() + { + GraphicsState = state; + _compute = false; + + if (descriptors != null) + { + TransformFeedbackDescriptors = descriptors; + _queriedState |= QueriedStateFlags.TransformFeedback; + } + } + + /// + /// Prepare the shader specialization state for quick binding lookups. + /// + /// The shader stages + public void Prepare(CachedShaderStage[] stages) + { + _allTextures = _textureSpecialization.ToArray(); + + _textureByBinding = new Box[stages.Length][]; + _imageByBinding = new Box[stages.Length][]; + + for (int i = 0; i < stages.Length; i++) + { + CachedShaderStage stage = stages[i]; + if (stage?.Info != null) + { + var textures = stage.Info.Textures; + var images = stage.Info.Images; + + var texBindings = new Box[textures.Count]; + var imageBindings = new Box[images.Count]; + + int stageIndex = Math.Max(i - 1, 0); // Don't count VertexA for looking up spec state. No-Op for compute. + + for (int j = 0; j < textures.Count; j++) + { + var texture = textures[j]; + texBindings[j] = GetTextureSpecState(stageIndex, texture.HandleIndex, texture.CbufSlot); + } + + for (int j = 0; j < images.Count; j++) + { + var image = images[j]; + imageBindings[j] = GetTextureSpecState(stageIndex, image.HandleIndex, image.CbufSlot); + } + + _textureByBinding[i] = texBindings; + _imageByBinding[i] = imageBindings; + } + } + } + + /// + /// Creates a new instance of the shader specialization state. + /// + /// Current 3D engine state + /// Current program pipeline state + /// Optional transform feedback buffers in use, if any + public ShaderSpecializationState( + ref GpuChannelGraphicsState state, + ref ProgramPipelineState pipelineState, + TransformFeedbackDescriptor[] descriptors) : this(ref state, descriptors) + { + PipelineState = pipelineState; + } + + /// + /// Creates a new instance of the shader specialization state. + /// + /// Current 3D engine state + /// Current program pipeline state + /// Optional transform feedback buffers in use, if any + public ShaderSpecializationState( + ref GpuChannelGraphicsState state, + ProgramPipelineState? pipelineState, + TransformFeedbackDescriptor[] descriptors) : this(ref state, descriptors) + { + PipelineState = pipelineState; + } + + /// + /// Indicates that the shader accesses the primitive topology state. + /// + public void RecordPrimitiveTopology() + { + _queriedState |= QueriedStateFlags.PrimitiveTopology; + } + + /// + /// Indicates that the shader accesses the constant buffer use state. + /// + /// Shader stage index + /// Mask indicating the constant buffers bound at the time of the shader compilation + public void RecordConstantBufferUse(int stageIndex, uint useMask) + { + ConstantBufferUse[stageIndex] = useMask; + _constantBufferUsePerStage |= (byte)(1 << stageIndex); + } + + /// + /// Indicates that a given texture is accessed by the shader. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + /// Descriptor of the texture + public void RegisterTexture(int stageIndex, int handle, int cbufSlot, Image.TextureDescriptor descriptor) + { + Box state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot); + state.Value.Format = descriptor.UnpackFormat(); + state.Value.FormatSrgb = descriptor.UnpackSrgb(); + state.Value.TextureTarget = descriptor.UnpackTextureTarget(); + state.Value.CoordNormalized = descriptor.UnpackTextureCoordNormalized(); + } + + /// + /// Indicates that a given texture is accessed by the shader. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + /// Maxwell texture format value + /// Whenever the texture format is a sRGB format + /// Texture target type + /// Whenever the texture coordinates used on the shader are considered normalized + public void RegisterTexture( + int stageIndex, + int handle, + int cbufSlot, + uint format, + bool formatSrgb, + TextureTarget target, + bool coordNormalized) + { + Box state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot); + state.Value.Format = format; + state.Value.FormatSrgb = formatSrgb; + state.Value.TextureTarget = target; + state.Value.CoordNormalized = coordNormalized; + } + + /// + /// Registers the length of a texture array calculated from a constant buffer size. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + /// Number of elements in the texture array + public void RegisterTextureArrayLengthFromBuffer(int stageIndex, int handle, int cbufSlot, int length) + { + _textureArrayFromBufferSpecialization[new TextureKey(stageIndex, handle, cbufSlot)] = length; + _queriedState |= QueriedStateFlags.TextureArrayFromBuffer; + } + + /// + /// Registers the length of a texture array calculated from a texture or sampler pool capacity. + /// + /// True for sampler pool, false for texture pool + /// Number of elements in the texture array + public void RegisterTextureArrayLengthFromPool(bool isSampler, int length) + { + _textureArrayFromPoolSpecialization[isSampler] = length; + _queriedState |= QueriedStateFlags.TextureArrayFromPool; + } + + /// + /// Indicates that the format of a given texture was used during the shader translation process. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + public void RecordTextureFormat(int stageIndex, int handle, int cbufSlot) + { + Box state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot); + state.Value.QueriedFlags |= QueriedTextureStateFlags.TextureFormat; + } + + /// + /// Indicates that the target of a given texture was used during the shader translation process. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + public void RecordTextureSamplerType(int stageIndex, int handle, int cbufSlot) + { + Box state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot); + state.Value.QueriedFlags |= QueriedTextureStateFlags.SamplerType; + } + + /// + /// Indicates that the coordinate normalization state of a given texture was used during the shader translation process. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + public void RecordTextureCoordNormalized(int stageIndex, int handle, int cbufSlot) + { + Box state = GetOrCreateTextureSpecState(stageIndex, handle, cbufSlot); + state.Value.QueriedFlags |= QueriedTextureStateFlags.CoordNormalized; + } + + /// + /// Checks if primitive topology was queried by the shader. + /// + /// True if queried, false otherwise + public bool IsPrimitiveTopologyQueried() + { + return _queriedState.HasFlag(QueriedStateFlags.PrimitiveTopology); + } + + /// + /// Checks if a given texture was registered on this specialization state. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + public bool TextureRegistered(int stageIndex, int handle, int cbufSlot) + { + return GetTextureSpecState(stageIndex, handle, cbufSlot) != null; + } + + /// + /// Checks if a given texture array (from constant buffer) was registered on this specialization state. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + /// True if the length for the given buffer and stage exists, false otherwise + public bool TextureArrayFromBufferRegistered(int stageIndex, int handle, int cbufSlot) + { + return _textureArrayFromBufferSpecialization.ContainsKey(new TextureKey(stageIndex, handle, cbufSlot)); + } + + /// + /// Checks if a given texture array (from a sampler pool or texture pool) was registered on this specialization state. + /// + /// True for sampler pool, false for texture pool + /// True if the length for the given pool, false otherwise + public bool TextureArrayFromPoolRegistered(bool isSampler) + { + return _textureArrayFromPoolSpecialization.ContainsKey(isSampler); + } + + /// + /// Gets the recorded format of a given texture. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + /// Format and sRGB tuple + public (uint, bool) GetFormat(int stageIndex, int handle, int cbufSlot) + { + TextureSpecializationState state = GetTextureSpecState(stageIndex, handle, cbufSlot).Value; + return (state.Format, state.FormatSrgb); + } + + /// + /// Gets the recorded target of a given texture. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + /// Texture target + public TextureTarget GetTextureTarget(int stageIndex, int handle, int cbufSlot) + { + return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.TextureTarget; + } + + /// + /// Gets the recorded coordinate normalization state of a given texture. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + /// True if coordinates are normalized, false otherwise + public bool GetCoordNormalized(int stageIndex, int handle, int cbufSlot) + { + return GetTextureSpecState(stageIndex, handle, cbufSlot).Value.CoordNormalized; + } + + /// + /// Gets the recorded length of a given texture array (from constant buffer). + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + /// Texture array length + public int GetTextureArrayFromBufferLength(int stageIndex, int handle, int cbufSlot) + { + return _textureArrayFromBufferSpecialization[new TextureKey(stageIndex, handle, cbufSlot)]; + } + + /// + /// Gets the recorded length of a given texture array (from a sampler or texture pool). + /// + /// True to get the sampler pool length, false to get the texture pool length + /// Texture array length + public int GetTextureArrayFromPoolLength(bool isSampler) + { + return _textureArrayFromPoolSpecialization[isSampler]; + } + + /// + /// Gets texture specialization state for a given texture, or create a new one if not present. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + /// Texture specialization state + private Box GetOrCreateTextureSpecState(int stageIndex, int handle, int cbufSlot) + { + TextureKey key = new(stageIndex, handle, cbufSlot); + + if (!_textureSpecialization.TryGetValue(key, out Box state)) + { + _textureSpecialization.Add(key, state = new Box()); + } + + return state; + } + + /// + /// Gets texture specialization state for a given texture. + /// + /// Shader stage where the texture is used + /// Offset in words of the texture handle on the texture buffer + /// Slot of the texture buffer constant buffer + /// Texture specialization state + private Box GetTextureSpecState(int stageIndex, int handle, int cbufSlot) + { + TextureKey key = new(stageIndex, handle, cbufSlot); + + if (_textureSpecialization.TryGetValue(key, out Box state)) + { + return state; + } + + return null; + } + + /// + /// Checks if the recorded state matches the current GPU 3D engine state. + /// + /// GPU channel + /// Texture pool state + /// Graphics state + /// Indicates that the vertex shader has been converted into a compute shader + /// Indicates whether the vertex shader accesses draw parameters + /// Indicates whether texture descriptors should be checked + /// True if the state matches, false otherwise + public bool MatchesGraphics( + GpuChannel channel, + ref GpuChannelPoolState poolState, + ref GpuChannelGraphicsState graphicsState, + bool vertexAsCompute, + bool usesDrawParameters, + bool checkTextures) + { + if (graphicsState.ViewportTransformDisable != GraphicsState.ViewportTransformDisable) + { + return false; + } + + bool thisA2cDitherEnable = GraphicsState.AlphaToCoverageEnable && GraphicsState.AlphaToCoverageDitherEnable; + bool otherA2cDitherEnable = graphicsState.AlphaToCoverageEnable && graphicsState.AlphaToCoverageDitherEnable; + + if (otherA2cDitherEnable != thisA2cDitherEnable) + { + return false; + } + + if (graphicsState.DepthMode != GraphicsState.DepthMode) + { + return false; + } + + if (graphicsState.AlphaTestEnable != GraphicsState.AlphaTestEnable) + { + return false; + } + + if (graphicsState.AlphaTestEnable && + (graphicsState.AlphaTestCompare != GraphicsState.AlphaTestCompare || + graphicsState.AlphaTestReference != GraphicsState.AlphaTestReference)) + { + return false; + } + + if (ShaderCache.MayConvertVtgToCompute(ref channel.Capabilities) && !vertexAsCompute) + { + for (int index = 0; index < graphicsState.AttributeTypes.Length; index++) + { + AttributeType lType = FilterAttributeType(channel, graphicsState.AttributeTypes[index]); + AttributeType rType = FilterAttributeType(channel, GraphicsState.AttributeTypes[index]); + + if (lType != rType) + { + return false; + } + } + } + else + { + if (!graphicsState.AttributeTypes.AsSpan().SequenceEqual(GraphicsState.AttributeTypes.AsSpan())) + { + return false; + } + } + + if (usesDrawParameters && graphicsState.HasConstantBufferDrawParameters != GraphicsState.HasConstantBufferDrawParameters) + { + return false; + } + + if (graphicsState.HasUnalignedStorageBuffer != GraphicsState.HasUnalignedStorageBuffer) + { + return false; + } + + if (channel.Capabilities.NeedsFragmentOutputSpecialization && !graphicsState.FragmentOutputTypes.AsSpan().SequenceEqual(GraphicsState.FragmentOutputTypes.AsSpan())) + { + return false; + } + + if (graphicsState.DualSourceBlendEnable != GraphicsState.DualSourceBlendEnable) + { + return false; + } + + if (graphicsState.YNegateEnabled != GraphicsState.YNegateEnabled) + { + return false; + } + + return Matches(channel, ref poolState, checkTextures, isCompute: false); + } + + /// + /// Converts special vertex attribute groups to their generic equivalents, for comparison purposes. + /// + /// GPU channel + /// Vertex attribute type + /// Filtered attribute + private static AttributeType FilterAttributeType(GpuChannel channel, AttributeType type) + { + type &= ~(AttributeType.Packed | AttributeType.PackedRgb10A2Signed); + + if (channel.Capabilities.SupportsScaledVertexFormats && + (type == AttributeType.Sscaled || type == AttributeType.Uscaled)) + { + type = AttributeType.Float; + } + + return type; + } + + /// + /// Checks if the recorded state matches the current GPU compute engine state. + /// + /// GPU channel + /// Texture pool state + /// Compute state + /// Indicates whether texture descriptors should be checked + /// True if the state matches, false otherwise + public bool MatchesCompute(GpuChannel channel, ref GpuChannelPoolState poolState, GpuChannelComputeState computeState, bool checkTextures) + { + if (computeState.HasUnalignedStorageBuffer != ComputeState.HasUnalignedStorageBuffer) + { + return false; + } + + return Matches(channel, ref poolState, checkTextures, isCompute: true); + } + + /// + /// Fetch the constant buffers used for a texture to cache. + /// + /// GPU channel + /// Indicates whenever the check is requested by the 3D or compute engine + /// The currently cached texture buffer index + /// The currently cached sampler buffer index + /// The currently cached texture buffer data + /// The currently cached sampler buffer data + /// The currently cached stage + /// The new texture buffer index + /// The new sampler buffer index + /// Stage index of the constant buffer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void UpdateCachedBuffer( + GpuChannel channel, + bool isCompute, + scoped ref int cachedTextureBufferIndex, + scoped ref int cachedSamplerBufferIndex, + scoped ref ReadOnlySpan cachedTextureBuffer, + scoped ref ReadOnlySpan cachedSamplerBuffer, + scoped ref int cachedStageIndex, + int textureBufferIndex, + int samplerBufferIndex, + int stageIndex) + { + bool stageChange = stageIndex != cachedStageIndex; + + if (stageChange || textureBufferIndex != cachedTextureBufferIndex) + { + ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex); + + cachedTextureBuffer = MemoryMarshal.Cast(channel.MemoryManager.Physical.GetSpan(bounds.Range)); + cachedTextureBufferIndex = textureBufferIndex; + + if (samplerBufferIndex == textureBufferIndex) + { + cachedSamplerBuffer = cachedTextureBuffer; + cachedSamplerBufferIndex = samplerBufferIndex; + } + } + + if (stageChange || samplerBufferIndex != cachedSamplerBufferIndex) + { + ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex); + + cachedSamplerBuffer = MemoryMarshal.Cast(channel.MemoryManager.Physical.GetSpan(bounds.Range)); + cachedSamplerBufferIndex = samplerBufferIndex; + } + + cachedStageIndex = stageIndex; + } + + /// + /// Checks if the recorded state matches the current GPU state. + /// + /// GPU channel + /// Texture pool state + /// Indicates whether texture descriptors should be checked + /// Indicates whenever the check is requested by the 3D or compute engine + /// True if the state matches, false otherwise + private bool Matches(GpuChannel channel, ref GpuChannelPoolState poolState, bool checkTextures, bool isCompute) + { + int constantBufferUsePerStageMask = _constantBufferUsePerStage; + + while (constantBufferUsePerStageMask != 0) + { + int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask); + + uint useMask = isCompute + ? channel.BufferManager.GetComputeUniformBufferUseMask() + : channel.BufferManager.GetGraphicsUniformBufferUseMask(index); + + if (ConstantBufferUse[index] != useMask) + { + return false; + } + + constantBufferUsePerStageMask &= ~(1 << index); + } + + if (checkTextures) + { + TexturePool pool = channel.TextureManager.GetTexturePool(poolState.TexturePoolGpuVa, poolState.TexturePoolMaximumId); + + int cachedTextureBufferIndex = -1; + int cachedSamplerBufferIndex = -1; + int cachedStageIndex = -1; + ReadOnlySpan cachedTextureBuffer = Span.Empty; + ReadOnlySpan cachedSamplerBuffer = Span.Empty; + + foreach (var kv in _allTextures) + { + TextureKey textureKey = kv.Key; + + (int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(textureKey.CbufSlot, poolState.TextureBufferIndex); + + UpdateCachedBuffer(channel, + isCompute, + ref cachedTextureBufferIndex, + ref cachedSamplerBufferIndex, + ref cachedTextureBuffer, + ref cachedSamplerBuffer, + ref cachedStageIndex, + textureBufferIndex, + samplerBufferIndex, + textureKey.StageIndex); + + int packedId = TextureHandle.ReadPackedId(textureKey.Handle, cachedTextureBuffer, cachedSamplerBuffer); + int textureId = TextureHandle.UnpackTextureId(packedId); + + if (pool.IsValidId(textureId)) + { + ref readonly Image.TextureDescriptor descriptor = ref pool.GetDescriptorRef(textureId); + + if (!MatchesTexture(kv.Value, descriptor)) + { + return false; + } + } + } + } + + return true; + } + + /// + /// Checks if the recorded texture state matches the given texture descriptor. + /// + /// Texture specialization state + /// Texture descriptor + /// True if the state matches, false otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool MatchesTexture(Box specializationState, in Image.TextureDescriptor descriptor) + { + if (specializationState != null) + { + if (specializationState.Value.QueriedFlags.HasFlag(QueriedTextureStateFlags.CoordNormalized) && + specializationState.Value.CoordNormalized != descriptor.UnpackTextureCoordNormalized()) + { + return false; + } + } + + return true; + } + + /// + /// Checks if the recorded texture state for a given texture binding matches a texture descriptor. + /// + /// The shader stage + /// The texture index + /// Texture descriptor + /// True if the state matches, false otherwise + public bool MatchesTexture(ShaderStage stage, int index, in Image.TextureDescriptor descriptor) + { + Box specializationState = _textureByBinding[(int)stage][index]; + + return MatchesTexture(specializationState, descriptor); + } + + /// + /// Checks if the recorded texture state for a given image binding matches a texture descriptor. + /// + /// The shader stage + /// The texture index + /// Texture descriptor + /// True if the state matches, false otherwise + public bool MatchesImage(ShaderStage stage, int index, in Image.TextureDescriptor descriptor) + { + Box specializationState = _imageByBinding[(int)stage][index]; + + return MatchesTexture(specializationState, descriptor); + } + + /// + /// Populates pipeline state that doesn't exist in older caches with default values + /// based on specialization state. + /// + /// Pipeline state to prepare + private void PreparePipelineState(ref ProgramPipelineState pipelineState) + { + if (!_compute) + { + pipelineState.DepthMode = GraphicsState.DepthMode ? DepthMode.MinusOneToOne : DepthMode.ZeroToOne; + } + } + + /// + /// Reads shader specialization state that has been serialized. + /// + /// Data reader + /// Shader specialization state + public static ShaderSpecializationState Read(ref BinarySerializer dataReader) + { + ShaderSpecializationState specState = new(); + + dataReader.Read(ref specState._queriedState); + dataReader.Read(ref specState._compute); + + if (specState._compute) + { + dataReader.ReadWithMagicAndSize(ref specState.ComputeState, ComsMagic); + } + else + { + dataReader.ReadWithMagicAndSize(ref specState.GraphicsState, GfxsMagic); + } + + dataReader.Read(ref specState._constantBufferUsePerStage); + + int constantBufferUsePerStageMask = specState._constantBufferUsePerStage; + + while (constantBufferUsePerStageMask != 0) + { + int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask); + dataReader.Read(ref specState.ConstantBufferUse[index]); + constantBufferUsePerStageMask &= ~(1 << index); + } + + bool hasPipelineState = false; + + dataReader.Read(ref hasPipelineState); + + if (hasPipelineState) + { + ProgramPipelineState pipelineState = default; + dataReader.ReadWithMagicAndSize(ref pipelineState, PgpsMagic); + + specState.PreparePipelineState(ref pipelineState); + specState.PipelineState = pipelineState; + } + + if (specState._queriedState.HasFlag(QueriedStateFlags.TransformFeedback)) + { + ushort tfCount = 0; + dataReader.Read(ref tfCount); + specState.TransformFeedbackDescriptors = new TransformFeedbackDescriptor[tfCount]; + + for (int index = 0; index < tfCount; index++) + { + dataReader.ReadWithMagicAndSize(ref specState.TransformFeedbackDescriptors[index], TfbdMagic); + } + } + + ushort count = 0; + dataReader.Read(ref count); + + for (int index = 0; index < count; index++) + { + TextureKey textureKey = default; + Box textureState = new(); + + dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic); + dataReader.ReadWithMagicAndSize(ref textureState.Value, TexsMagic); + + specState._textureSpecialization[textureKey] = textureState; + } + + if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer)) + { + dataReader.Read(ref count); + + for (int index = 0; index < count; index++) + { + TextureKey textureKey = default; + int length = 0; + + dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic); + dataReader.Read(ref length); + + specState._textureArrayFromBufferSpecialization[textureKey] = length; + } + } + + if (specState._queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool)) + { + dataReader.Read(ref count); + + for (int index = 0; index < count; index++) + { + bool textureKey = default; + int length = 0; + + dataReader.ReadWithMagicAndSize(ref textureKey, TexkMagic); + dataReader.Read(ref length); + + specState._textureArrayFromPoolSpecialization[textureKey] = length; + } + } + + return specState; + } + + /// + /// Serializes the shader specialization state. + /// + /// Data writer + public void Write(ref BinarySerializer dataWriter) + { + dataWriter.Write(ref _queriedState); + dataWriter.Write(ref _compute); + + if (_compute) + { + dataWriter.WriteWithMagicAndSize(ref ComputeState, ComsMagic); + } + else + { + dataWriter.WriteWithMagicAndSize(ref GraphicsState, GfxsMagic); + } + + dataWriter.Write(ref _constantBufferUsePerStage); + + int constantBufferUsePerStageMask = _constantBufferUsePerStage; + + while (constantBufferUsePerStageMask != 0) + { + int index = BitOperations.TrailingZeroCount(constantBufferUsePerStageMask); + dataWriter.Write(ref ConstantBufferUse[index]); + constantBufferUsePerStageMask &= ~(1 << index); + } + + bool hasPipelineState = PipelineState.HasValue; + + dataWriter.Write(ref hasPipelineState); + + if (hasPipelineState) + { + ProgramPipelineState pipelineState = PipelineState.Value; + dataWriter.WriteWithMagicAndSize(ref pipelineState, PgpsMagic); + } + + if (_queriedState.HasFlag(QueriedStateFlags.TransformFeedback)) + { + ushort tfCount = (ushort)TransformFeedbackDescriptors.Length; + dataWriter.Write(ref tfCount); + + for (int index = 0; index < TransformFeedbackDescriptors.Length; index++) + { + dataWriter.WriteWithMagicAndSize(ref TransformFeedbackDescriptors[index], TfbdMagic); + } + } + + ushort count = (ushort)_textureSpecialization.Count; + dataWriter.Write(ref count); + + foreach (var kv in _textureSpecialization) + { + var textureKey = kv.Key; + var textureState = kv.Value; + + dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic); + dataWriter.WriteWithMagicAndSize(ref textureState.Value, TexsMagic); + } + + if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromBuffer)) + { + count = (ushort)_textureArrayFromBufferSpecialization.Count; + dataWriter.Write(ref count); + + foreach (var kv in _textureArrayFromBufferSpecialization) + { + var textureKey = kv.Key; + var length = kv.Value; + + dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic); + dataWriter.Write(ref length); + } + } + + if (_queriedState.HasFlag(QueriedStateFlags.TextureArrayFromPool)) + { + count = (ushort)_textureArrayFromPoolSpecialization.Count; + dataWriter.Write(ref count); + + foreach (var kv in _textureArrayFromPoolSpecialization) + { + var textureKey = kv.Key; + var length = kv.Value; + + dataWriter.WriteWithMagicAndSize(ref textureKey, TexkMagic); + dataWriter.Write(ref length); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/TransformFeedbackDescriptor.cs b/src/Ryujinx.Graphics.Gpu/Shader/TransformFeedbackDescriptor.cs new file mode 100644 index 00000000..1f245881 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Shader/TransformFeedbackDescriptor.cs @@ -0,0 +1,58 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Transform feedback descriptor. + /// + struct TransformFeedbackDescriptor + { + // New fields should be added to the end of the struct to keep disk shader cache compatibility. + + /// + /// Index of the transform feedback. + /// + public readonly int BufferIndex; + + /// + /// Amount of bytes consumed per vertex. + /// + public readonly int Stride; + + /// + /// Number of varyings written into the buffer. + /// + public readonly int VaryingCount; + + /// + /// Location of varyings to be written into the buffer. Each byte is one location. + /// + public Array32 VaryingLocations; // Making this readonly breaks AsSpan + + /// + /// Creates a new transform feedback descriptor. + /// + /// Index of the transform feedback + /// Amount of bytes consumed per vertex + /// Number of varyings written into the buffer. Indicates size in bytes of + /// Location of varyings to be written into the buffer. Each byte is one location + public TransformFeedbackDescriptor(int bufferIndex, int stride, int varyingCount, ref Array32 varyingLocations) + { + BufferIndex = bufferIndex; + Stride = stride; + VaryingCount = varyingCount; + VaryingLocations = varyingLocations; + } + + /// + /// Gets a span of the . + /// + /// Span of varying locations + public ReadOnlySpan AsSpan() + { + return MemoryMarshal.Cast(VaryingLocations.AsSpan())[..Math.Min(128, VaryingCount)]; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Synchronization/HostSyncFlags.cs b/src/Ryujinx.Graphics.Gpu/Synchronization/HostSyncFlags.cs new file mode 100644 index 00000000..810c2b4a --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Synchronization/HostSyncFlags.cs @@ -0,0 +1,30 @@ +using System; + +namespace Ryujinx.Graphics.Gpu.Synchronization +{ + /// + /// Modifier flags for creating host sync. + /// + [Flags] + internal enum HostSyncFlags + { + None = 0, + + /// + /// Present if host sync is being created by a syncpoint. + /// + Syncpoint = 1 << 0, + + /// + /// Present if the sync should signal as soon as possible. + /// + Strict = 1 << 1, + + /// + /// Present will force the sync to be created, even if no actions are eligible. + /// + Force = 1 << 2, + + StrictSyncpoint = Strict | Syncpoint, + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Synchronization/ISyncActionHandler.cs b/src/Ryujinx.Graphics.Gpu/Synchronization/ISyncActionHandler.cs new file mode 100644 index 00000000..d470d2f0 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Synchronization/ISyncActionHandler.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.Gpu.Synchronization +{ + /// + /// This interface indicates that a class can be registered for a sync action. + /// + interface ISyncActionHandler + { + /// + /// Action to be performed when some synchronizing action is reached after modification. + /// Generally used to register read/write tracking to flush resources from GPU when their memory is used. + /// + /// True if the action is a guest syncpoint + /// True if the action is to be removed, false otherwise + bool SyncAction(bool syncpoint); + + /// + /// Action to be performed immediately before sync is created. + /// + /// True if the action is a guest syncpoint + void SyncPreAction(bool syncpoint) { } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs b/src/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs new file mode 100644 index 00000000..1042a4db --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs @@ -0,0 +1,109 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Device; +using System; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Synchronization +{ + /// + /// GPU synchronization manager. + /// + public class SynchronizationManager : ISynchronizationManager + { + /// + /// The maximum number of syncpoints supported by the GM20B. + /// + public const int MaxHardwareSyncpoints = 192; + + /// + /// Array containing all hardware syncpoints. + /// + private readonly Syncpoint[] _syncpoints; + + public SynchronizationManager() + { + _syncpoints = new Syncpoint[MaxHardwareSyncpoints]; + + for (uint i = 0; i < _syncpoints.Length; i++) + { + _syncpoints[i] = new Syncpoint(i); + } + } + + /// + public uint IncrementSyncpoint(uint id) + { + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints); + + return _syncpoints[id].Increment(); + } + + /// + public uint GetSyncpointValue(uint id) + { + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints); + + return _syncpoints[id].Value; + } + + /// + /// Register a new callback on a syncpoint with a given id at a target threshold. + /// The callback will be called once the threshold is reached and will automatically be unregistered. + /// + /// The id of the syncpoint + /// The target threshold + /// The callback to call when the threshold is reached + /// Thrown when id >= MaxHardwareSyncpoints + /// The created SyncpointWaiterHandle object or null if already past threshold + public SyncpointWaiterHandle RegisterCallbackOnSyncpoint(uint id, uint threshold, Action callback) + { + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints); + + return _syncpoints[id].RegisterCallback(threshold, callback); + } + + /// + /// Unregister a callback on a given syncpoint. + /// + /// The id of the syncpoint + /// The waiter information to unregister + /// Thrown when id >= MaxHardwareSyncpoints + public void UnregisterCallback(uint id, SyncpointWaiterHandle waiterInformation) + { + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints); + + _syncpoints[id].UnregisterCallback(waiterInformation); + } + + /// + public bool WaitOnSyncpoint(uint id, uint threshold, TimeSpan timeout) + { + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints); + + // TODO: Remove this when GPU channel scheduling will be implemented. + if (timeout == Timeout.InfiniteTimeSpan) + { + timeout = TimeSpan.FromSeconds(1); + } + + using ManualResetEvent waitEvent = new(false); + var info = _syncpoints[id].RegisterCallback(threshold, (x) => waitEvent.Set()); + + if (info == null) + { + return false; + } + + bool signaled = waitEvent.WaitOne(timeout); + + if (!signaled && info != null) + { + Logger.Error?.Print(LogClass.Gpu, $"Wait on syncpoint {id} for threshold {threshold} took more than {timeout.TotalMilliseconds}ms, resuming execution..."); + + _syncpoints[id].UnregisterCallback(info); + } + + return !signaled; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Synchronization/Syncpoint.cs b/src/Ryujinx.Graphics.Gpu/Synchronization/Syncpoint.cs new file mode 100644 index 00000000..a1626c85 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Synchronization/Syncpoint.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Synchronization +{ + /// + /// Represents GPU hardware syncpoint. + /// + class Syncpoint + { + private int _storedValue; + + public readonly uint Id; + + /// + /// The value of the syncpoint. + /// + public uint Value => (uint)_storedValue; + + // TODO: switch to something handling concurrency? + private readonly List _waiters; + + public Syncpoint(uint id) + { + Id = id; + _waiters = new List(); + } + + /// + /// Register a new callback for a target threshold. + /// The callback will be called once the threshold is reached and will automatically be unregistered. + /// + /// The target threshold + /// The callback to call when the threshold is reached + /// The created SyncpointWaiterHandle object or null if already past threshold + public SyncpointWaiterHandle RegisterCallback(uint threshold, Action callback) + { + lock (_waiters) + { + if (Value >= threshold) + { + callback(null); + + return null; + } + else + { + SyncpointWaiterHandle waiterInformation = new() + { + Threshold = threshold, + Callback = callback, + }; + + _waiters.Add(waiterInformation); + + return waiterInformation; + } + } + } + + public void UnregisterCallback(SyncpointWaiterHandle waiterInformation) + { + lock (_waiters) + { + _waiters.Remove(waiterInformation); + } + } + + /// + /// Increment the syncpoint + /// + /// The incremented value of the syncpoint + public uint Increment() + { + uint currentValue = (uint)Interlocked.Increment(ref _storedValue); + + SyncpointWaiterHandle expired = null; + List expiredList = null; + + lock (_waiters) + { + _waiters.RemoveAll(item => + { + bool isPastThreshold = currentValue >= item.Threshold; + + if (isPastThreshold) + { + if (expired == null) + { + expired = item; + } + else + { + expiredList ??= new List(); + + expiredList.Add(item); + } + } + + return isPastThreshold; + }); + } + + // Call the callbacks as a separate step. + // As we don't know what the callback will be doing, + // and it could block execution for a indefinite amount of time, + // we can't call it inside the lock. + if (expired != null) + { + expired.Callback(expired); + + if (expiredList != null) + { + for (int i = 0; i < expiredList.Count; i++) + { + expiredList[i].Callback(expiredList[i]); + } + } + } + + return currentValue; + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Synchronization/SyncpointWaiterHandle.cs b/src/Ryujinx.Graphics.Gpu/Synchronization/SyncpointWaiterHandle.cs new file mode 100644 index 00000000..e75b1e89 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Synchronization/SyncpointWaiterHandle.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ryujinx.Graphics.Gpu.Synchronization +{ + public class SyncpointWaiterHandle + { + internal uint Threshold; + internal Action Callback; + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Window.cs b/src/Ryujinx.Graphics.Gpu/Window.cs new file mode 100644 index 00000000..3b236853 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Window.cs @@ -0,0 +1,270 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Texture; +using Ryujinx.Memory.Range; +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu +{ + /// + /// GPU image presentation window. + /// + public class Window + { + private readonly GpuContext _context; + + /// + /// Texture presented on the window. + /// + private readonly struct PresentationTexture + { + /// + /// Texture cache where the texture might be located. + /// + public TextureCache Cache { get; } + + /// + /// Texture information. + /// + public TextureInfo Info { get; } + + /// + /// Physical memory locations where the texture data is located. + /// + public MultiRange Range { get; } + + /// + /// Texture crop region. + /// + public ImageCrop Crop { get; } + + /// + /// Texture acquire callback. + /// + public Action AcquireCallback { get; } + + /// + /// Texture release callback. + /// + public Action ReleaseCallback { get; } + + /// + /// User defined object, passed to the various callbacks. + /// + public object UserObj { get; } + + /// + /// Creates a new instance of the presentation texture. + /// + /// Texture cache used to look for the texture to be presented + /// Information of the texture to be presented + /// Physical memory locations where the texture data is located + /// Texture crop region + /// Texture acquire callback + /// Texture release callback + /// User defined object passed to the release callback, can be used to identify the texture + public PresentationTexture( + TextureCache cache, + TextureInfo info, + MultiRange range, + ImageCrop crop, + Action acquireCallback, + Action releaseCallback, + object userObj) + { + Cache = cache; + Info = info; + Range = range; + Crop = crop; + AcquireCallback = acquireCallback; + ReleaseCallback = releaseCallback; + UserObj = userObj; + } + } + + private readonly ConcurrentQueue _frameQueue; + + private int _framesAvailable; + + public bool IsFrameAvailable => _framesAvailable != 0; + + /// + /// Creates a new instance of the GPU presentation window. + /// + /// GPU emulation context + public Window(GpuContext context) + { + _context = context; + + _frameQueue = new ConcurrentQueue(); + } + + /// + /// Enqueues a frame for presentation. + /// This method is thread safe and can be called from any thread. + /// When the texture is presented and not needed anymore, the release callback is called. + /// It's an error to modify the texture after calling this method, before the release callback is called. + /// + /// Process ID of the process that owns the texture pointed to by + /// CPU virtual address of the texture data + /// Texture width + /// Texture height + /// Texture stride for linear texture, should be zero otherwise + /// Indicates if the texture is linear, normally false + /// GOB blocks in the Y direction, for block linear textures + /// Texture format + /// Texture format bytes per pixel (must match the format) + /// Texture crop region + /// Texture acquire callback + /// Texture release callback + /// User defined object passed to the release callback + /// Thrown when is invalid + /// True if the frame was added to the queue, false otherwise + public bool EnqueueFrameThreadSafe( + ulong pid, + ulong address, + int width, + int height, + int stride, + bool isLinear, + int gobBlocksInY, + Format format, + int bytesPerPixel, + ImageCrop crop, + Action acquireCallback, + Action releaseCallback, + object userObj) + { + if (!_context.PhysicalMemoryRegistry.TryGetValue(pid, out var physicalMemory)) + { + return false; + } + + FormatInfo formatInfo = new(format, 1, 1, bytesPerPixel, 4); + + TextureInfo info = new( + 0UL, + width, + height, + 1, + 1, + 1, + 1, + stride, + isLinear, + gobBlocksInY, + 1, + 1, + Target.Texture2D, + formatInfo); + + int size = SizeCalculator.GetBlockLinearTextureSize( + width, + height, + 1, + 1, + 1, + 1, + 1, + bytesPerPixel, + gobBlocksInY, + 1, + 1).TotalSize; + + MultiRange range = new(address, (ulong)size); + + _frameQueue.Enqueue(new PresentationTexture( + physicalMemory.TextureCache, + info, + range, + crop, + acquireCallback, + releaseCallback, + userObj)); + + return true; + } + + /// + /// Presents a texture on the queue. + /// If the queue is empty, then no texture is presented. + /// + /// Callback method to call when a new texture should be presented on the screen + public void Present(Action swapBuffersCallback) + { + _context.AdvanceSequence(); + + if (_frameQueue.TryDequeue(out PresentationTexture pt)) + { + pt.AcquireCallback(_context, pt.UserObj); + + Image.Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, range: pt.Range); + + pt.Cache.Tick(); + + texture.SynchronizeMemory(); + + ImageCrop crop = new( + (int)(pt.Crop.Left * texture.ScaleFactor), + (int)MathF.Ceiling(pt.Crop.Right * texture.ScaleFactor), + (int)(pt.Crop.Top * texture.ScaleFactor), + (int)MathF.Ceiling(pt.Crop.Bottom * texture.ScaleFactor), + pt.Crop.FlipX, + pt.Crop.FlipY, + pt.Crop.IsStretched, + pt.Crop.AspectRatioX, + pt.Crop.AspectRatioY); + + if (texture.Info.Width > pt.Info.Width || texture.Info.Height > pt.Info.Height) + { + int top = crop.Top; + int bottom = crop.Bottom; + int left = crop.Left; + int right = crop.Right; + + if (top == 0 && bottom == 0) + { + bottom = Math.Min(texture.Info.Height, pt.Info.Height); + } + + if (left == 0 && right == 0) + { + right = Math.Min(texture.Info.Width, pt.Info.Width); + } + + crop = new ImageCrop(left, right, top, bottom, crop.FlipX, crop.FlipY, crop.IsStretched, crop.AspectRatioX, crop.AspectRatioY); + } + + _context.Renderer.Window.Present(texture.HostTexture, crop, swapBuffersCallback); + + pt.ReleaseCallback(pt.UserObj); + } + } + + /// + /// Indicate that a frame on the queue is ready to be acquired. + /// + public void SignalFrameReady() + { + Interlocked.Increment(ref _framesAvailable); + } + + /// + /// Determine if any frames are available, and decrement the available count if there are. + /// + /// True if a frame is available, false otherwise + public bool ConsumeFrameAvailable() + { + if (Interlocked.CompareExchange(ref _framesAvailable, 0, 0) != 0) + { + Interlocked.Decrement(ref _framesAvailable); + + return true; + } + + return false; + } + } +} diff --git a/src/Ryujinx.Graphics.Host1x/ClassId.cs b/src/Ryujinx.Graphics.Host1x/ClassId.cs new file mode 100644 index 00000000..8cba6b88 --- /dev/null +++ b/src/Ryujinx.Graphics.Host1x/ClassId.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Host1x +{ + public enum ClassId + { + Host1x = 0x1, + Mpeg = 0x20, + Nvenc = 0x21, + Vi = 0x30, + Isp = 0x32, + Ispb = 0x34, + Vii2c = 0x36, + Vic = 0x5d, + Gr3d = 0x60, + Gpu = 0x61, + Tsec = 0xe0, + Tsecb = 0xe1, + Nvjpg = 0xc0, + Nvdec = 0xf0, + } +} diff --git a/src/Ryujinx.Graphics.Host1x/Devices.cs b/src/Ryujinx.Graphics.Host1x/Devices.cs new file mode 100644 index 00000000..9de2b81a --- /dev/null +++ b/src/Ryujinx.Graphics.Host1x/Devices.cs @@ -0,0 +1,32 @@ +using Ryujinx.Graphics.Device; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Host1x +{ + class Devices : IDisposable + { + private readonly Dictionary _devices = new(); + + public void RegisterDevice(ClassId classId, IDeviceState device) + { + _devices[classId] = device; + } + + public IDeviceState GetDevice(ClassId classId) + { + return _devices.TryGetValue(classId, out IDeviceState device) ? device : null; + } + + public void Dispose() + { + foreach (var device in _devices.Values) + { + if (device is ThiDevice thi) + { + thi.Dispose(); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Host1x/Host1xClass.cs b/src/Ryujinx.Graphics.Host1x/Host1xClass.cs new file mode 100644 index 00000000..3ffc87ba --- /dev/null +++ b/src/Ryujinx.Graphics.Host1x/Host1xClass.cs @@ -0,0 +1,32 @@ +using Ryujinx.Graphics.Device; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.Host1x +{ + public class Host1xClass : IDeviceState + { + private readonly ISynchronizationManager _syncMgr; + private readonly DeviceState _state; + + public Host1xClass(ISynchronizationManager syncMgr) + { + _syncMgr = syncMgr; + _state = new DeviceState(new Dictionary + { + { nameof(Host1xClassRegisters.WaitSyncpt32), new RwCallback(WaitSyncpt32, null) }, + }); + } + + public int Read(int offset) => _state.Read(offset); + public void Write(int offset, int data) => _state.Write(offset, data); + + private void WaitSyncpt32(int data) + { + uint syncpointId = (uint)(data & 0xFF); + uint threshold = _state.State.LoadSyncptPayload32; + + _syncMgr.WaitOnSyncpoint(syncpointId, threshold, Timeout.InfiniteTimeSpan); + } + } +} diff --git a/src/Ryujinx.Graphics.Host1x/Host1xClassRegisters.cs b/src/Ryujinx.Graphics.Host1x/Host1xClassRegisters.cs new file mode 100644 index 00000000..28f5d24b --- /dev/null +++ b/src/Ryujinx.Graphics.Host1x/Host1xClassRegisters.cs @@ -0,0 +1,43 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Host1x +{ + struct Host1xClassRegisters + { +#pragma warning disable CS0649 // Field is never assigned to + public uint IncrSyncpt; + public uint IncrSyncptCntrl; + public uint IncrSyncptError; + public Array5 ReservedC; + public uint WaitSyncpt; + public uint WaitSyncptBase; + public uint WaitSyncptIncr; + public uint LoadSyncptBase; + public uint IncrSyncptBase; + public uint Clear; + public uint Wait; + public uint WaitWithIntr; + public uint DelayUsec; + public uint TickcountHi; + public uint TickcountLo; + public uint Tickctrl; + public Array23 Reserved50; + public uint Indctrl; + public uint Indoff2; + public uint Indoff; + public Array31 Inddata; + public uint Reserved134; + public uint LoadSyncptPayload32; + public uint Stallctrl; + public uint WaitSyncpt32; + public uint WaitSyncptBase32; + public uint LoadSyncptBase32; + public uint IncrSyncptBase32; + public uint StallcountHi; + public uint StallcountLo; + public uint Xrefctrl; + public uint ChannelXrefHi; + public uint ChannelXrefLo; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Host1x/Host1xDevice.cs b/src/Ryujinx.Graphics.Host1x/Host1xDevice.cs new file mode 100644 index 00000000..2db74ce5 --- /dev/null +++ b/src/Ryujinx.Graphics.Host1x/Host1xDevice.cs @@ -0,0 +1,173 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Device; +using System; +using System.Numerics; + +namespace Ryujinx.Graphics.Host1x +{ + public sealed class Host1xDevice : IDisposable + { + private readonly struct Command + { + public int[] Buffer { get; } + public long ContextId { get; } + + public Command(int[] buffer, long contextId) + { + Buffer = buffer; + ContextId = contextId; + } + } + + private readonly SyncptIncrManager _syncptIncrMgr; + private readonly AsyncWorkQueue _commandQueue; + + private readonly Devices _devices = new(); + + public Host1xClass Class { get; } + + private IDeviceState _device; + + private int _count; + private int _offset; + private int _mask; + private bool _incrementing; + + public Host1xDevice(ISynchronizationManager syncMgr) + { + _syncptIncrMgr = new SyncptIncrManager(syncMgr); + _commandQueue = new AsyncWorkQueue(Process, "Ryujinx.Host1xProcessor"); + + Class = new Host1xClass(syncMgr); + + _devices.RegisterDevice(ClassId.Host1x, Class); + } + + public void RegisterDevice(ClassId classId, IDeviceState device) + { + var thi = new ThiDevice(classId, device ?? throw new ArgumentNullException(nameof(device)), _syncptIncrMgr); + _devices.RegisterDevice(classId, thi); + } + + public long CreateContext() + { + if (_devices.GetDevice(ClassId.Nvdec) is IDeviceStateWithContext nvdec) + { + return nvdec.CreateContext(); + } + + return -1; + } + + public void DestroyContext(long id) + { + if (id == -1) + { + return; + } + + if (_devices.GetDevice(ClassId.Nvdec) is IDeviceStateWithContext nvdec) + { + nvdec.DestroyContext(id); + } + } + + private void SetNvdecContext(long id) + { + if (id == -1) + { + return; + } + + if (_devices.GetDevice(ClassId.Nvdec) is IDeviceStateWithContext nvdec) + { + nvdec.BindContext(id); + } + } + + public void Submit(ReadOnlySpan commandBuffer, long contextId) + { + _commandQueue.Add(new Command(commandBuffer.ToArray(), contextId)); + } + + private void Process(Command command) + { + SetNvdecContext(command.ContextId); + int[] commandBuffer = command.Buffer; + + for (int index = 0; index < commandBuffer.Length; index++) + { + Step(commandBuffer[index]); + } + } + + private void Step(int value) + { + if (_mask != 0) + { + int lbs = BitOperations.TrailingZeroCount(_mask); + + _mask &= ~(1 << lbs); + + DeviceWrite(_offset + lbs, value); + + return; + } + else if (_count != 0) + { + _count--; + + DeviceWrite(_offset, value); + + if (_incrementing) + { + _offset++; + } + + return; + } + + OpCode opCode = (OpCode)((value >> 28) & 0xf); + + switch (opCode) + { + case OpCode.SetClass: + _mask = value & 0x3f; + ClassId classId = (ClassId)((value >> 6) & 0x3ff); + _offset = (value >> 16) & 0xfff; + _device = _devices.GetDevice(classId); + break; + case OpCode.Incr: + case OpCode.NonIncr: + _count = value & 0xffff; + _offset = (value >> 16) & 0xfff; + _incrementing = opCode == OpCode.Incr; + break; + case OpCode.Mask: + _mask = value & 0xffff; + _offset = (value >> 16) & 0xfff; + break; + case OpCode.Imm: + int data = value & 0xfff; + _offset = (value >> 16) & 0xfff; + DeviceWrite(_offset, data); + break; + default: + Logger.Error?.Print(LogClass.Host1x, $"Unsupported opcode \"{opCode}\"."); + break; + } + } + + private void DeviceWrite(int offset, int data) + { + _device?.Write(offset * 4, data); + } + + public void Dispose() + { + _commandQueue.Dispose(); + _devices.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Host1x/OpCode.cs b/src/Ryujinx.Graphics.Host1x/OpCode.cs new file mode 100644 index 00000000..c2d2b59e --- /dev/null +++ b/src/Ryujinx.Graphics.Host1x/OpCode.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.Graphics.Host1x +{ + enum OpCode + { + SetClass, + Incr, + NonIncr, + Mask, + Imm, + Restart, + Gather, + SetStrmId, + SetAppId, + SetPyld, + IncrW, + NonIncrW, + GatherW, + RestartW, + Extend, + } +} diff --git a/src/Ryujinx.Graphics.Host1x/Ryujinx.Graphics.Host1x.csproj b/src/Ryujinx.Graphics.Host1x/Ryujinx.Graphics.Host1x.csproj new file mode 100644 index 00000000..d631d039 --- /dev/null +++ b/src/Ryujinx.Graphics.Host1x/Ryujinx.Graphics.Host1x.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + + + + + + + diff --git a/src/Ryujinx.Graphics.Host1x/SyncptIncrManager.cs b/src/Ryujinx.Graphics.Host1x/SyncptIncrManager.cs new file mode 100644 index 00000000..a5ee1198 --- /dev/null +++ b/src/Ryujinx.Graphics.Host1x/SyncptIncrManager.cs @@ -0,0 +1,99 @@ +using Ryujinx.Graphics.Device; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Host1x +{ + class SyncptIncrManager + { + private readonly ISynchronizationManager _syncMgr; + + private readonly struct SyncptIncr + { + public uint Id { get; } + public ClassId ClassId { get; } + public uint SyncptId { get; } + public bool Done { get; } + + public SyncptIncr(uint id, ClassId classId, uint syncptId, bool done = false) + { + Id = id; + ClassId = classId; + SyncptId = syncptId; + Done = done; + } + } + + private readonly List _incrs = new(); + + private uint _currentId; + + public SyncptIncrManager(ISynchronizationManager syncMgr) + { + _syncMgr = syncMgr; + } + + public void Increment(uint id) + { + lock (_incrs) + { + _incrs.Add(new SyncptIncr(0, 0, id, true)); + + IncrementAllDone(); + } + } + + public uint IncrementWhenDone(ClassId classId, uint id) + { + lock (_incrs) + { + uint handle = _currentId++; + + _incrs.Add(new SyncptIncr(handle, classId, id)); + + return handle; + } + } + + public void SignalDone(uint handle) + { + lock (_incrs) + { + // Set pending increment with the given handle to "done". + for (int i = 0; i < _incrs.Count; i++) + { + SyncptIncr incr = _incrs[i]; + + if (_incrs[i].Id == handle) + { + _incrs[i] = new SyncptIncr(incr.Id, incr.ClassId, incr.SyncptId, true); + + break; + } + } + + IncrementAllDone(); + } + } + + private void IncrementAllDone() + { + lock (_incrs) + { + // Increment all sequential pending increments that are already done. + int doneCount = 0; + + for (; doneCount < _incrs.Count; doneCount++) + { + if (!_incrs[doneCount].Done) + { + break; + } + + _syncMgr.IncrementSyncpoint(_incrs[doneCount].SyncptId); + } + + _incrs.RemoveRange(0, doneCount); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Host1x/ThiDevice.cs b/src/Ryujinx.Graphics.Host1x/ThiDevice.cs new file mode 100644 index 00000000..02871efb --- /dev/null +++ b/src/Ryujinx.Graphics.Host1x/ThiDevice.cs @@ -0,0 +1,137 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Device; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Host1x +{ + class ThiDevice : IDeviceStateWithContext, IDisposable + { + private readonly ClassId _classId; + private readonly IDeviceState _device; + + private readonly SyncptIncrManager _syncptIncrMgr; + + private long _currentContextId; + private long _previousContextId; + + private class CommandAction + { + public long ContextId { get; } + public int Data { get; } + + public CommandAction(long contextId, int data) + { + ContextId = contextId; + Data = data; + } + } + + private class MethodCallAction : CommandAction + { + public int Method { get; } + + public MethodCallAction(long contextId, int method, int data) : base(contextId, data) + { + Method = method; + } + } + + private class SyncptIncrAction : CommandAction + { + public SyncptIncrAction(long contextId, uint syncptIncrHandle) : base(contextId, (int)syncptIncrHandle) + { + } + } + + private readonly AsyncWorkQueue _commandQueue; + + private readonly DeviceState _state; + + public ThiDevice(ClassId classId, IDeviceState device, SyncptIncrManager syncptIncrMgr) + { + _classId = classId; + _device = device; + _syncptIncrMgr = syncptIncrMgr; + _commandQueue = new AsyncWorkQueue(Process, $"Ryujinx.{classId}Processor"); + _state = new DeviceState(new Dictionary + { + { nameof(ThiRegisters.IncrSyncpt), new RwCallback(IncrSyncpt, null) }, + { nameof(ThiRegisters.Method1), new RwCallback(Method1, null) }, + }); + + _previousContextId = -1; + } + + public long CreateContext() + { + if (_device is IDeviceStateWithContext deviceWithContext) + { + return deviceWithContext.CreateContext(); + } + + return -1; + } + + public void DestroyContext(long id) + { + if (_device is IDeviceStateWithContext deviceWithContext) + { + deviceWithContext.DestroyContext(id); + } + } + + public void BindContext(long id) + { + _currentContextId = id; + } + + public int Read(int offset) => _state.Read(offset); + public void Write(int offset, int data) => _state.Write(offset, data); + + private void IncrSyncpt(int data) + { + uint syncpointId = (uint)(data & 0xFF); + uint cond = (uint)((data >> 8) & 0xFF); // 0 = Immediate, 1 = Done + + if (cond == 0) + { + _syncptIncrMgr.Increment(syncpointId); + } + else + { + _commandQueue.Add(new SyncptIncrAction(_currentContextId, _syncptIncrMgr.IncrementWhenDone(_classId, syncpointId))); + } + } + + private void Method1(int data) + { + _commandQueue.Add(new MethodCallAction(_currentContextId, (int)_state.State.Method0 * sizeof(uint), data)); + } + + private void Process(CommandAction cmdAction) + { + long contextId = cmdAction.ContextId; + if (contextId != _previousContextId) + { + _previousContextId = contextId; + + if (_device is IDeviceStateWithContext deviceWithContext) + { + deviceWithContext.BindContext(contextId); + } + } + + if (cmdAction is SyncptIncrAction syncptIncrAction) + { + _syncptIncrMgr.SignalDone((uint)syncptIncrAction.Data); + } + else if (cmdAction is MethodCallAction methodCallAction) + { + _device.Write(methodCallAction.Method, methodCallAction.Data); + } + } + + public void Dispose() => _commandQueue.Dispose(); + } +} diff --git a/src/Ryujinx.Graphics.Host1x/ThiRegisters.cs b/src/Ryujinx.Graphics.Host1x/ThiRegisters.cs new file mode 100644 index 00000000..bb7534d8 --- /dev/null +++ b/src/Ryujinx.Graphics.Host1x/ThiRegisters.cs @@ -0,0 +1,24 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Host1x +{ + struct ThiRegisters + { +#pragma warning disable CS0649 // Field is never assigned to + public uint IncrSyncpt; + public uint Reserved4; + public uint IncrSyncptErr; + public uint CtxswIncrSyncpt; + public Array4 Reserved10; + public uint Ctxsw; + public uint Reserved24; + public uint ContSyncptEof; + public Array5 Reserved2C; + public uint Method0; + public uint Method1; + public Array12 Reserved48; + public uint IntStatus; + public uint IntMask; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs new file mode 100644 index 00000000..0767cc9d --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs @@ -0,0 +1,175 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Nvdec.FFmpeg.Native; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg +{ + unsafe class FFmpegContext : IDisposable + { + private unsafe delegate int AVCodec_decode(AVCodecContext* avctx, void* outdata, int* got_frame_ptr, AVPacket* avpkt); + + private readonly AVCodec_decode _decodeFrame; + private static readonly FFmpegApi.av_log_set_callback_callback _logFunc; + private readonly AVCodec* _codec; + private readonly AVPacket* _packet; + private readonly AVCodecContext* _context; + + public FFmpegContext(AVCodecID codecId) + { + _codec = FFmpegApi.avcodec_find_decoder(codecId); + if (_codec == null) + { + Logger.Error?.PrintMsg(LogClass.FFmpeg, $"Codec wasn't found. Make sure you have the {codecId} codec present in your FFmpeg installation."); + + return; + } + + _context = FFmpegApi.avcodec_alloc_context3(_codec); + if (_context == null) + { + Logger.Error?.PrintMsg(LogClass.FFmpeg, "Codec context couldn't be allocated."); + + return; + } + + if (FFmpegApi.avcodec_open2(_context, _codec, null) != 0) + { + Logger.Error?.PrintMsg(LogClass.FFmpeg, "Codec couldn't be opened."); + + return; + } + + _packet = FFmpegApi.av_packet_alloc(); + if (_packet == null) + { + Logger.Error?.PrintMsg(LogClass.FFmpeg, "Packet couldn't be allocated."); + + return; + } + + int avCodecRawVersion = FFmpegApi.avcodec_version(); + int avCodecMajorVersion = avCodecRawVersion >> 16; + int avCodecMinorVersion = (avCodecRawVersion >> 8) & 0xFF; + + // libavcodec 59.24 changed AvCodec to move its private API and also move the codec function to an union. + if (avCodecMajorVersion > 59 || (avCodecMajorVersion == 59 && avCodecMinorVersion > 24)) + { + _decodeFrame = Marshal.GetDelegateForFunctionPointer(((FFCodec*)_codec)->CodecCallback); + } + // libavcodec 59.x changed AvCodec private API layout. + else if (avCodecMajorVersion == 59) + { + _decodeFrame = Marshal.GetDelegateForFunctionPointer(((FFCodecLegacy*)_codec)->Decode); + } + // libavcodec 58.x and lower + else + { + _decodeFrame = Marshal.GetDelegateForFunctionPointer(((FFCodecLegacy*)_codec)->Decode); + } + } + + static FFmpegContext() + { + _logFunc = Log; + + // Redirect log output. + FFmpegApi.av_log_set_level(AVLog.MaxOffset); + FFmpegApi.av_log_set_callback(_logFunc); + } + + private static void Log(void* ptr, AVLog level, string format, byte* vl) + { + if (level > FFmpegApi.av_log_get_level()) + { + return; + } + + int lineSize = 1024; + byte* lineBuffer = stackalloc byte[lineSize]; + int printPrefix = 1; + + FFmpegApi.av_log_format_line(ptr, level, format, vl, lineBuffer, lineSize, &printPrefix); + + string line = Marshal.PtrToStringAnsi((IntPtr)lineBuffer).Trim(); + + switch (level) + { + case AVLog.Panic: + case AVLog.Fatal: + case AVLog.Error: + Logger.Error?.Print(LogClass.FFmpeg, line); + break; + case AVLog.Warning: + Logger.Warning?.Print(LogClass.FFmpeg, line); + break; + case AVLog.Info: + Logger.Info?.Print(LogClass.FFmpeg, line); + break; + case AVLog.Verbose: + case AVLog.Debug: + Logger.Debug?.Print(LogClass.FFmpeg, line); + break; + case AVLog.Trace: + Logger.Trace?.Print(LogClass.FFmpeg, line); + break; + } + } + + public int DecodeFrame(Surface output, ReadOnlySpan bitstream) + { + FFmpegApi.av_frame_unref(output.Frame); + + int result; + int gotFrame; + + fixed (byte* ptr = bitstream) + { + _packet->Data = ptr; + _packet->Size = bitstream.Length; + result = _decodeFrame(_context, output.Frame, &gotFrame, _packet); + } + + if (gotFrame == 0) + { + FFmpegApi.av_frame_unref(output.Frame); + + // If the frame was not delivered, it was probably delayed. + // Get the next delayed frame by passing a 0 length packet. + _packet->Data = null; + _packet->Size = 0; + result = _decodeFrame(_context, output.Frame, &gotFrame, _packet); + + // We need to set B frames to 0 as we already consumed all delayed frames. + // This prevents the decoder from trying to return a delayed frame next time. + _context->HasBFrames = 0; + } + + FFmpegApi.av_packet_unref(_packet); + + if (gotFrame == 0) + { + FFmpegApi.av_frame_unref(output.Frame); + + return -1; + } + + return result < 0 ? result : 0; + } + + public void Dispose() + { + fixed (AVPacket** ppPacket = &_packet) + { + FFmpegApi.av_packet_free(ppPacket); + } + + _ = FFmpegApi.avcodec_close(_context); + + fixed (AVCodecContext** ppContext = &_context) + { + FFmpegApi.avcodec_free_context(ppContext); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/Decoder.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/Decoder.cs new file mode 100644 index 00000000..14877dd5 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/Decoder.cs @@ -0,0 +1,56 @@ +using Ryujinx.Graphics.Nvdec.FFmpeg.Native; +using Ryujinx.Graphics.Video; +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264 +{ + public sealed class Decoder : IH264Decoder + { + public bool IsHardwareAccelerated => false; + + private const int WorkBufferSize = 0x200; + + private readonly byte[] _workBuffer = new byte[WorkBufferSize]; + + private FFmpegContext _context = new(AVCodecID.AV_CODEC_ID_H264); + + private int _oldOutputWidth; + private int _oldOutputHeight; + + public ISurface CreateSurface(int width, int height) + { + return new Surface(width, height); + } + + public bool Decode(ref H264PictureInfo pictureInfo, ISurface output, ReadOnlySpan bitstream) + { + Surface outSurf = (Surface)output; + + if (outSurf.RequestedWidth != _oldOutputWidth || + outSurf.RequestedHeight != _oldOutputHeight) + { + _context.Dispose(); + _context = new FFmpegContext(AVCodecID.AV_CODEC_ID_H264); + + _oldOutputWidth = outSurf.RequestedWidth; + _oldOutputHeight = outSurf.RequestedHeight; + } + + Span bs = Prepend(bitstream, SpsAndPpsReconstruction.Reconstruct(ref pictureInfo, _workBuffer)); + + return _context.DecodeFrame(outSurf, bs) == 0; + } + + private static byte[] Prepend(ReadOnlySpan data, ReadOnlySpan prep) + { + byte[] output = new byte[data.Length + prep.Length]; + + prep.CopyTo(output); + data.CopyTo(new Span(output)[prep.Length..]); + + return output; + } + + public void Dispose() => _context.Dispose(); + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/H264BitStreamWriter.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/H264BitStreamWriter.cs new file mode 100644 index 00000000..57ab9fb5 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/H264BitStreamWriter.cs @@ -0,0 +1,121 @@ +using System; +using System.Numerics; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264 +{ + struct H264BitStreamWriter + { + private const int BufferSize = 8; + + private readonly byte[] _workBuffer; + + private int _offset; + private int _buffer; + private int _bufferPos; + + public H264BitStreamWriter(byte[] workBuffer) + { + _workBuffer = workBuffer; + _offset = 0; + _buffer = 0; + _bufferPos = 0; + } + + public void WriteBit(bool value) + { + WriteBits(value ? 1 : 0, 1); + } + + public void WriteBits(int value, int valueSize) + { + int valuePos = 0; + + int remaining = valueSize; + + while (remaining > 0) + { + int copySize = remaining; + + int free = GetFreeBufferBits(); + + if (copySize > free) + { + copySize = free; + } + + int mask = (1 << copySize) - 1; + + int srcShift = (valueSize - valuePos) - copySize; + int dstShift = (BufferSize - _bufferPos) - copySize; + + _buffer |= ((value >> srcShift) & mask) << dstShift; + + valuePos += copySize; + _bufferPos += copySize; + remaining -= copySize; + } + } + + private int GetFreeBufferBits() + { + if (_bufferPos == BufferSize) + { + Flush(); + } + + return BufferSize - _bufferPos; + } + + public void Flush() + { + if (_bufferPos != 0) + { + _workBuffer[_offset++] = (byte)_buffer; + + _buffer = 0; + _bufferPos = 0; + } + } + + public void End() + { + WriteBit(true); + + Flush(); + } + + public readonly Span AsSpan() + { + return new Span(_workBuffer)[.._offset]; + } + + public void WriteU(uint value, int valueSize) => WriteBits((int)value, valueSize); + public void WriteSe(int value) => WriteExpGolombCodedInt(value); + public void WriteUe(uint value) => WriteExpGolombCodedUInt(value); + + private void WriteExpGolombCodedInt(int value) + { + int sign = value <= 0 ? 0 : 1; + + if (value < 0) + { + value = -value; + } + + value = (value << 1) - sign; + + WriteExpGolombCodedUInt((uint)value); + } + + private void WriteExpGolombCodedUInt(uint value) + { + int size = 32 - BitOperations.LeadingZeroCount(value + 1); + + WriteBits(1, size); + + value -= (1u << (size - 1)) - 1; + + WriteBits((int)value, size - 1); + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/SpsAndPpsReconstruction.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/SpsAndPpsReconstruction.cs new file mode 100644 index 00000000..6d012f89 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/SpsAndPpsReconstruction.cs @@ -0,0 +1,159 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Video; +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.H264 +{ + static class SpsAndPpsReconstruction + { + public static Span Reconstruct(ref H264PictureInfo pictureInfo, byte[] workBuffer) + { + H264BitStreamWriter writer = new(workBuffer); + + // Sequence Parameter Set. + writer.WriteU(1, 24); + writer.WriteU(0, 1); + writer.WriteU(3, 2); + writer.WriteU(7, 5); + writer.WriteU(100, 8); // Profile idc + writer.WriteU(0, 8); // Reserved + writer.WriteU(31, 8); // Level idc + writer.WriteUe(0); // Seq parameter set id + writer.WriteUe(pictureInfo.ChromaFormatIdc); + + if (pictureInfo.ChromaFormatIdc == 3) + { + writer.WriteBit(false); // Separate colour plane flag + } + + writer.WriteUe(0); // Bit depth luma minus 8 + writer.WriteUe(0); // Bit depth chroma minus 8 + writer.WriteBit(pictureInfo.QpprimeYZeroTransformBypassFlag); + writer.WriteBit(false); // Scaling matrix present flag + + writer.WriteUe(pictureInfo.Log2MaxFrameNumMinus4); + writer.WriteUe(pictureInfo.PicOrderCntType); + + if (pictureInfo.PicOrderCntType == 0) + { + writer.WriteUe(pictureInfo.Log2MaxPicOrderCntLsbMinus4); + } + else if (pictureInfo.PicOrderCntType == 1) + { + writer.WriteBit(pictureInfo.DeltaPicOrderAlwaysZeroFlag); + + writer.WriteSe(0); // Offset for non-ref pic + writer.WriteSe(0); // Offset for top to bottom field + writer.WriteUe(0); // Num ref frames in pic order cnt cycle + } + + writer.WriteUe(16); // Max num ref frames + writer.WriteBit(false); // Gaps in frame num value allowed flag + writer.WriteUe(pictureInfo.PicWidthInMbsMinus1); + writer.WriteUe(pictureInfo.PicHeightInMapUnitsMinus1); + writer.WriteBit(pictureInfo.FrameMbsOnlyFlag); + + if (!pictureInfo.FrameMbsOnlyFlag) + { + writer.WriteBit(pictureInfo.MbAdaptiveFrameFieldFlag); + } + + writer.WriteBit(pictureInfo.Direct8x8InferenceFlag); + writer.WriteBit(false); // Frame cropping flag + writer.WriteBit(false); // VUI parameter present flag + + writer.End(); + + // Picture Parameter Set. + writer.WriteU(1, 24); + writer.WriteU(0, 1); + writer.WriteU(3, 2); + writer.WriteU(8, 5); + + writer.WriteUe(0); // Pic parameter set id + writer.WriteUe(0); // Seq parameter set id + + writer.WriteBit(pictureInfo.EntropyCodingModeFlag); + writer.WriteBit(pictureInfo.PicOrderPresentFlag); + writer.WriteUe(0); // Num slice groups minus 1 + writer.WriteUe(pictureInfo.NumRefIdxL0ActiveMinus1); + writer.WriteUe(pictureInfo.NumRefIdxL1ActiveMinus1); + writer.WriteBit(pictureInfo.WeightedPredFlag); + writer.WriteU(pictureInfo.WeightedBipredIdc, 2); + writer.WriteSe(pictureInfo.PicInitQpMinus26); + writer.WriteSe(0); // Pic init qs minus 26 + writer.WriteSe(pictureInfo.ChromaQpIndexOffset); + writer.WriteBit(pictureInfo.DeblockingFilterControlPresentFlag); + writer.WriteBit(pictureInfo.ConstrainedIntraPredFlag); + writer.WriteBit(pictureInfo.RedundantPicCntPresentFlag); + writer.WriteBit(pictureInfo.Transform8x8ModeFlag); + + writer.WriteBit(pictureInfo.ScalingMatrixPresent); + + if (pictureInfo.ScalingMatrixPresent) + { + for (int index = 0; index < 6; index++) + { + writer.WriteBit(true); + + WriteScalingList(ref writer, pictureInfo.ScalingLists4x4[index]); + } + + if (pictureInfo.Transform8x8ModeFlag) + { + for (int index = 0; index < 2; index++) + { + writer.WriteBit(true); + + WriteScalingList(ref writer, pictureInfo.ScalingLists8x8[index]); + } + } + } + + writer.WriteSe(pictureInfo.SecondChromaQpIndexOffset); + + writer.End(); + + return writer.AsSpan(); + } + + // ZigZag LUTs from libavcodec. + private static ReadOnlySpan ZigZagDirect => new byte[] + { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + }; + + private static ReadOnlySpan ZigZagScan => new byte[] + { + 0 + 0 * 4, 1 + 0 * 4, 0 + 1 * 4, 0 + 2 * 4, + 1 + 1 * 4, 2 + 0 * 4, 3 + 0 * 4, 2 + 1 * 4, + 1 + 2 * 4, 0 + 3 * 4, 1 + 3 * 4, 2 + 2 * 4, + 3 + 1 * 4, 3 + 2 * 4, 2 + 3 * 4, 3 + 3 * 4, + }; + + private static void WriteScalingList(ref H264BitStreamWriter writer, IArray list) + { + ReadOnlySpan scan = list.Length == 16 ? ZigZagScan : ZigZagDirect; + + int lastScale = 8; + + for (int index = 0; index < list.Length; index++) + { + byte value = list[scan[index]]; + + int deltaScale = value - lastScale; + + writer.WriteSe(deltaScale); + + lastScale = value; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodec.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodec.cs new file mode 100644 index 00000000..0267000c --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodec.cs @@ -0,0 +1,26 @@ +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + struct AVCodec + { +#pragma warning disable CS0649 // Field is never assigned to + public unsafe byte* Name; + public unsafe byte* LongName; + public int Type; + public AVCodecID Id; + public int Capabilities; + public byte MaxLowRes; + public unsafe AVRational* SupportedFramerates; + public IntPtr PixFmts; + public IntPtr SupportedSamplerates; + public IntPtr SampleFmts; + // Deprecated + public unsafe ulong* ChannelLayouts; + public unsafe IntPtr PrivClass; + public IntPtr Profiles; + public unsafe byte* WrapperName; + public IntPtr ChLayouts; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodec501.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodec501.cs new file mode 100644 index 00000000..9084f402 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodec501.cs @@ -0,0 +1,25 @@ +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + struct AVCodec501 + { +#pragma warning disable CS0649 // Field is never assigned to + public unsafe byte* Name; + public unsafe byte* LongName; + public int Type; + public AVCodecID Id; + public int Capabilities; + public byte MaxLowRes; + public unsafe AVRational* SupportedFramerates; + public IntPtr PixFmts; + public IntPtr SupportedSamplerates; + public IntPtr SampleFmts; + // Deprecated + public unsafe ulong* ChannelLayouts; + public unsafe IntPtr PrivClass; + public IntPtr Profiles; + public unsafe byte* WrapperName; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecContext.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecContext.cs new file mode 100644 index 00000000..c743ab33 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecContext.cs @@ -0,0 +1,171 @@ +using Ryujinx.Common.Memory; +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + struct AVCodecContext + { +#pragma warning disable CS0649 // Field is never assigned to + public unsafe IntPtr AvClass; + public int LogLevelOffset; + public int CodecType; + public unsafe AVCodec* Codec; + public AVCodecID CodecId; + public uint CodecTag; + public IntPtr PrivData; + public IntPtr Internal; + public IntPtr Opaque; + public long BitRate; + public int BitRateTolerance; + public int GlobalQuality; + public int CompressionLevel; + public int Flags; + public int Flags2; + public IntPtr ExtraData; + public int ExtraDataSize; + public AVRational TimeBase; + public int TicksPerFrame; + public int Delay; + public int Width; + public int Height; + public int CodedWidth; + public int CodedHeight; + public int GopSize; + public int PixFmt; + public IntPtr DrawHorizBand; + public IntPtr GetFormat; + public int MaxBFrames; + public float BQuantFactor; + public float BQuantOffset; + public int HasBFrames; + public float IQuantFactor; + public float IQuantOffset; + public float LumiMasking; + public float TemporalCplxMasking; + public float SpatialCplxMasking; + public float PMasking; + public float DarkMasking; + public int SliceCount; + public IntPtr SliceOffset; + public AVRational SampleAspectRatio; + public int MeCmp; + public int MeSubCmp; + public int MbCmp; + public int IldctCmp; + public int DiaSize; + public int LastPredictorCount; + public int MePreCmp; + public int PreDiaSize; + public int MeSubpelQuality; + public int MeRange; + public int SliceFlags; + public int MbDecision; + public IntPtr IntraMatrix; + public IntPtr InterMatrix; + public int IntraDcPrecision; + public int SkipTop; + public int SkipBottom; + public int MbLmin; + public int MbLmax; + public int BidirRefine; + public int KeyintMin; + public int Refs; + public int Mv0Threshold; + public int ColorPrimaries; + public int ColorPrc; + public int Colorspace; + public int ColorRange; + public int ChromaSampleLocation; + public int Slices; + public int FieldOrder; + public int SampleRate; + public int Channels; + public int SampleFmt; + public int FrameSize; + public int FrameNumber; + public int BlockAlign; + public int CutOff; + public ulong ChannelLayout; + public ulong RequestChannelLayout; + public int AudioServiceType; + public int RequestSampleFmt; + public IntPtr GetBuffer2; + public float QCompress; + public float QBlur; + public int QMin; + public int QMax; + public int MaxQdiff; + public int RcBufferSize; + public int RcOverrideCount; + public IntPtr RcOverride; + public long RcMaxRate; + public long RcMinRate; + public float RcMax_available_vbv_use; + public float RcMin_vbv_overflow_use; + public int RcInitialBufferOccupancy; + public int Trellis; + public IntPtr StatsOut; + public IntPtr StatsIn; + public int WorkaroundBugs; + public int StrictStdCompliance; + public int ErrorConcealment; + public int Debug; + public int ErrRecognition; + public long ReorderedOpaque; + public IntPtr HwAccel; + public IntPtr HwAccelContext; + public Array8 Error; + public int DctAlgo; + public int IdctAlgo; + public int BitsPerCodedSample; + public int BitsPerRawSample; + public int LowRes; + public int ThreadCount; + public int ThreadType; + public int ActiveThreadType; + public int ThreadSafeCallbacks; + public IntPtr Execute; + public IntPtr Execute2; + public int NsseWeight; + public int Profile; + public int Level; + public int SkipLoopFilter; + public int SkipIdct; + public int SkipFrame; + public IntPtr SubtitleHeader; + public int SubtitleHeaderSize; + public int InitialPadding; + public AVRational Framerate; + public int SwPixFmt; + public AVRational PktTimebase; + public IntPtr CodecDescriptor; + public long PtsCorrectionNumFaultyPts; + public long PtsCorrectionNumFaultyDts; + public long PtsCorrectionLastPts; + public long PtsCorrectionLastDts; + public IntPtr SubCharenc; + public int SubCharencMode; + public int SkipAlpha; + public int SeekPreroll; + public int DebugMv; + public IntPtr ChromaIntraMatrix; + public IntPtr DumpSeparator; + public IntPtr CodecWhitelist; + public uint Properties; + public IntPtr CodedSideData; + public int NbCodedSideData; + public IntPtr HwFramesCtx; + public int SubTextFormat; + public int TrailingPadding; + public long MaxPixels; + public IntPtr HwDeviceCtx; + public int HwAccelFlags; + public int applyCropping; + public int ExtraHwFrames; + public int DiscardDamagedPercentage; + public long MaxSamples; + public int ExportSideData; + public IntPtr GetEncodeBuffer; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecID.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecID.cs new file mode 100644 index 00000000..0af07641 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecID.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + enum AVCodecID + { + AV_CODEC_ID_H264 = 27, + AV_CODEC_ID_VP8 = 139, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVFrame.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVFrame.cs new file mode 100644 index 00000000..a1eb7a09 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVFrame.cs @@ -0,0 +1,37 @@ +using Ryujinx.Common.Memory; +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + struct AVFrame + { +#pragma warning disable CS0649 // Field is never assigned to + public Array8 Data; + public Array8 LineSize; + public IntPtr ExtendedData; + public int Width; + public int Height; + public int NumSamples; + public int Format; + public int KeyFrame; + public int PictureType; + public AVRational SampleAspectRatio; + public long Pts; + public long PktDts; + public AVRational TimeBase; + public int CodedPictureNumber; + public int DisplayPictureNumber; + public int Quality; + public IntPtr Opaque; + public int RepeatPicture; + public int InterlacedFrame; + public int TopFieldFirst; + public int PaletteHasChanged; + public long ReorderedOpaque; + public int SampleRate; + public ulong ChannelLayout; +#pragma warning restore CS0649 + + // NOTE: There is more after, but the layout kind of changed a bit and we don't need more than this. This is safe as we only manipulate this behind a reference. + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVLog.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVLog.cs new file mode 100644 index 00000000..642e4897 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVLog.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + enum AVLog + { + Panic = 0, + Fatal = 8, + Error = 16, + Warning = 24, + Info = 32, + Verbose = 40, + Debug = 48, + Trace = 56, + MaxOffset = 64, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVPacket.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVPacket.cs new file mode 100644 index 00000000..8df4e26a --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVPacket.cs @@ -0,0 +1,24 @@ +using AVBufferRef = System.IntPtr; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + struct AVPacket + { +#pragma warning disable CS0649 // Field is never assigned to + public unsafe AVBufferRef* Buf; + public long Pts; + public long Dts; + public unsafe byte* Data; + public int Size; + public int StreamIndex; + public int Flags; + public AVBufferRef SizeData; + public int SizeDataElems; + public long Duration; + public long Position; + public AVBufferRef Opaque; + public unsafe AVBufferRef* OpaqueRef; + public AVRational TimeBase; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVRational.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVRational.cs new file mode 100644 index 00000000..15ed3278 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVRational.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + public struct AVRational + { + public int Numerator; + public int Denominator; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodec.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodec.cs new file mode 100644 index 00000000..ceb8a3b0 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodec.cs @@ -0,0 +1,21 @@ +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + struct FFCodec where T : struct + { +#pragma warning disable CS0649 // Field is never assigned to + public T Base; + public int CapsInternalOrCbType; + public int PrivDataSize; + public IntPtr UpdateThreadContext; + public IntPtr UpdateThreadContextForUser; + public IntPtr Defaults; + public IntPtr InitStaticData; + public IntPtr Init; + public IntPtr CodecCallback; +#pragma warning restore CS0649 + + // NOTE: There is more after, but the layout kind of changed a bit and we don't need more than this. This is safe as we only manipulate this behind a reference. + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodecLegacy.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodecLegacy.cs new file mode 100644 index 00000000..03eba311 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodecLegacy.cs @@ -0,0 +1,23 @@ +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + struct FFCodecLegacy where T : struct + { +#pragma warning disable CS0649 // Field is never assigned to + public T Base; + public uint CapsInternalOrCbType; + public int PrivDataSize; + public IntPtr UpdateThreadContext; + public IntPtr UpdateThreadContextForUser; + public IntPtr Defaults; + public IntPtr InitStaticData; + public IntPtr Init; + public IntPtr EncodeSub; + public IntPtr Encode2; + public IntPtr Decode; +#pragma warning restore CS0649 + + // NOTE: There is more after, but the layout kind of changed a bit and we don't need more than this. This is safe as we only manipulate this behind a reference. + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs new file mode 100644 index 00000000..5167ff9f --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native +{ + static partial class FFmpegApi + { + public const string AvCodecLibraryName = "avcodec"; + public const string AvUtilLibraryName = "avutil"; + + private static readonly Dictionary _librariesWhitelist = new() + { + { AvCodecLibraryName, (58, 59) }, + { AvUtilLibraryName, (56, 57) }, + }; + + private static string FormatLibraryNameForCurrentOs(string libraryName, int version) + { + if (OperatingSystem.IsWindows()) + { + return $"{libraryName}-{version}.dll"; + } + else if (OperatingSystem.IsLinux()) + { + return $"lib{libraryName}.so.{version}"; + } + else if (OperatingSystem.IsMacOS()) + { + return $"lib{libraryName}.{version}.dylib"; + } + else + { + throw new NotImplementedException($"Unsupported OS for FFmpeg: {RuntimeInformation.RuntimeIdentifier}"); + } + } + + + private static bool TryLoadWhitelistedLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, out IntPtr handle) + { + handle = IntPtr.Zero; + + if (_librariesWhitelist.TryGetValue(libraryName, out var value)) + { + (int minVersion, int maxVersion) = value; + + for (int version = maxVersion; version >= minVersion; version--) + { + if (NativeLibrary.TryLoad(FormatLibraryNameForCurrentOs(libraryName, version), assembly, searchPath, out handle)) + { + return true; + } + } + } + + return false; + } + + static FFmpegApi() + { + NativeLibrary.SetDllImportResolver(typeof(FFmpegApi).Assembly, (name, assembly, path) => + { + + if (name == AvUtilLibraryName && TryLoadWhitelistedLibrary(AvUtilLibraryName, assembly, path, out nint handle)) + { + return handle; + } + else if (name == AvCodecLibraryName && TryLoadWhitelistedLibrary(AvCodecLibraryName, assembly, path, out handle)) + { + return handle; + } + + return IntPtr.Zero; + }); + } + + public unsafe delegate void av_log_set_callback_callback(void* a0, AVLog level, [MarshalAs(UnmanagedType.LPUTF8Str)] string a2, byte* a3); + + [LibraryImport(AvUtilLibraryName)] + internal static unsafe partial AVFrame* av_frame_alloc(); + + [LibraryImport(AvUtilLibraryName)] + internal static unsafe partial void av_frame_unref(AVFrame* frame); + + [LibraryImport(AvUtilLibraryName)] + internal static unsafe partial void av_free(AVFrame* frame); + + [LibraryImport(AvUtilLibraryName)] + internal static unsafe partial void av_log_set_level(AVLog level); + + [LibraryImport(AvUtilLibraryName)] + internal static unsafe partial void av_log_set_callback(av_log_set_callback_callback callback); + + [LibraryImport(AvUtilLibraryName)] + internal static unsafe partial AVLog av_log_get_level(); + + [LibraryImport(AvUtilLibraryName)] + internal static unsafe partial void av_log_format_line(void* ptr, AVLog level, [MarshalAs(UnmanagedType.LPUTF8Str)] string fmt, byte* vl, byte* line, int lineSize, int* printPrefix); + + [LibraryImport(AvCodecLibraryName)] + internal static unsafe partial AVCodec* avcodec_find_decoder(AVCodecID id); + + [LibraryImport(AvCodecLibraryName)] + internal static unsafe partial AVCodecContext* avcodec_alloc_context3(AVCodec* codec); + + [LibraryImport(AvCodecLibraryName)] + internal static unsafe partial int avcodec_open2(AVCodecContext* avctx, AVCodec* codec, void** options); + + [LibraryImport(AvCodecLibraryName)] + internal static unsafe partial int avcodec_close(AVCodecContext* avctx); + + [LibraryImport(AvCodecLibraryName)] + internal static unsafe partial void avcodec_free_context(AVCodecContext** avctx); + + [LibraryImport(AvCodecLibraryName)] + internal static unsafe partial AVPacket* av_packet_alloc(); + + [LibraryImport(AvCodecLibraryName)] + internal static unsafe partial void av_packet_unref(AVPacket* pkt); + + [LibraryImport(AvCodecLibraryName)] + internal static unsafe partial void av_packet_free(AVPacket** pkt); + + [LibraryImport(AvCodecLibraryName)] + internal static unsafe partial int avcodec_version(); + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj new file mode 100644 index 00000000..d1a6358c --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + true + + + + + + + + diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs new file mode 100644 index 00000000..65fb7b4a --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs @@ -0,0 +1,41 @@ +using Ryujinx.Graphics.Nvdec.FFmpeg.Native; +using Ryujinx.Graphics.Video; +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg +{ + unsafe class Surface : ISurface + { + public AVFrame* Frame { get; } + + public int RequestedWidth { get; } + public int RequestedHeight { get; } + + public Plane YPlane => new((IntPtr)Frame->Data[0], Stride * Height); + public Plane UPlane => new((IntPtr)Frame->Data[1], UvStride * UvHeight); + public Plane VPlane => new((IntPtr)Frame->Data[2], UvStride * UvHeight); + + public FrameField Field => Frame->InterlacedFrame != 0 ? FrameField.Interlaced : FrameField.Progressive; + + public int Width => Frame->Width; + public int Height => Frame->Height; + public int Stride => Frame->LineSize[0]; + public int UvWidth => (Width + 1) >> 1; + public int UvHeight => (Height + 1) >> 1; + public int UvStride => Frame->LineSize[1]; + + public Surface(int width, int height) + { + RequestedWidth = width; + RequestedHeight = height; + + Frame = FFmpegApi.av_frame_alloc(); + } + + public void Dispose() + { + FFmpegApi.av_frame_unref(Frame); + FFmpegApi.av_free(Frame); + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs new file mode 100644 index 00000000..0733e385 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs @@ -0,0 +1,53 @@ +using Ryujinx.Graphics.Nvdec.FFmpeg.Native; +using Ryujinx.Graphics.Video; +using System; + +namespace Ryujinx.Graphics.Nvdec.FFmpeg.Vp8 +{ + public sealed class Decoder : IDecoder + { + public bool IsHardwareAccelerated => false; + + private readonly FFmpegContext _context = new(AVCodecID.AV_CODEC_ID_VP8); + + public ISurface CreateSurface(int width, int height) + { + return new Surface(width, height); + } + + public bool Decode(ref Vp8PictureInfo pictureInfo, ISurface output, ReadOnlySpan bitstream) + { + Surface outSurf = (Surface)output; + + int uncompHeaderSize = pictureInfo.KeyFrame ? 10 : 3; + + byte[] frame = new byte[bitstream.Length + uncompHeaderSize]; + + uint firstPartSizeShifted = pictureInfo.FirstPartSize << 5; + + frame[0] = (byte)(pictureInfo.KeyFrame ? 0 : 1); + frame[0] |= (byte)((pictureInfo.Version & 7) << 1); + frame[0] |= 1 << 4; + frame[0] |= (byte)firstPartSizeShifted; + frame[1] |= (byte)(firstPartSizeShifted >> 8); + frame[2] |= (byte)(firstPartSizeShifted >> 16); + + if (pictureInfo.KeyFrame) + { + frame[3] = 0x9d; + frame[4] = 0x01; + frame[5] = 0x2a; + frame[6] = (byte)pictureInfo.FrameWidth; + frame[7] = (byte)((pictureInfo.FrameWidth >> 8) & 0x3F); + frame[8] = (byte)pictureInfo.FrameHeight; + frame[9] = (byte)((pictureInfo.FrameHeight >> 8) & 0x3F); + } + + bitstream.CopyTo(new Span(frame)[uncompHeaderSize..]); + + return _context.DecodeFrame(outSurf, frame) == 0; + } + + public void Dispose() => _context.Dispose(); + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/BitDepth.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/BitDepth.cs new file mode 100644 index 00000000..a43c8358 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/BitDepth.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal enum BitDepth + { + Bits8 = 8, /**< 8 bits */ + Bits10 = 10, /**< 10 bits */ + Bits12 = 12, /**< 12 bits */ + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/CodecErr.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/CodecErr.cs new file mode 100644 index 00000000..3bd149df --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/CodecErr.cs @@ -0,0 +1,56 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal enum CodecErr + { + /*!\brief Operation completed without error */ + CodecOk, + + /*!\brief Unspecified error */ + CodecError, + + /*!\brief Memory operation failed */ + CodecMemError, + + /*!\brief ABI version mismatch */ + CodecAbiMismatch, + + /*!\brief Algorithm does not have required capability */ + CodecIncapable, + + /*!\brief The given bitstream is not supported. + * + * The bitstream was unable to be parsed at the highest level. The decoder + * is unable to proceed. This error \ref SHOULD be treated as fatal to the + * stream. */ + CodecUnsupBitstream, + + /*!\brief Encoded bitstream uses an unsupported feature + * + * The decoder does not implement a feature required by the encoder. This + * return code should only be used for features that prevent future + * pictures from being properly decoded. This error \ref MAY be treated as + * fatal to the stream or \ref MAY be treated as fatal to the current GOP. + */ + CodecUnsupFeature, + + /*!\brief The coded data for this stream is corrupt or incomplete + * + * There was a problem decoding the current frame. This return code + * should only be used for failures that prevent future pictures from + * being properly decoded. This error \ref MAY be treated as fatal to the + * stream or \ref MAY be treated as fatal to the current GOP. If decoding + * is continued for the current GOP, artifacts may be present. + */ + CodecCorruptFrame, + + /*!\brief An application-supplied parameter is not valid. + * + */ + CodecInvalidParam, + + /*!\brief An iterator reached the end of list. + * + */ + CodecListEnd, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Common/BitUtils.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Common/BitUtils.cs new file mode 100644 index 00000000..807f1293 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Common/BitUtils.cs @@ -0,0 +1,59 @@ +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Common +{ + internal static class BitUtils + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte ClipPixel(int val) + { + return (byte)((val > 255) ? 255 : (val < 0) ? 0 : val); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ClipPixelHighbd(int val, int bd) + { + return bd switch + { + 10 => (ushort)Math.Clamp(val, 0, 1023), + 12 => (ushort)Math.Clamp(val, 0, 4095), + _ => (ushort)Math.Clamp(val, 0, 255), + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int RoundPowerOfTwo(int value, int n) + { + return (value + (1 << (n - 1))) >> n; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long RoundPowerOfTwo(long value, int n) + { + return (value + (1L << (n - 1))) >> n; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int AlignPowerOfTwo(int value, int n) + { + return (value + ((1 << n) - 1)) & ~((1 << n) - 1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetMsb(uint n) + { + Debug.Assert(n != 0); + + return 31 ^ BitOperations.LeadingZeroCount(n); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetUnsignedBits(uint numValues) + { + return numValues > 0 ? GetMsb(numValues) + 1 : 0; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Common/MemoryAllocator.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Common/MemoryAllocator.cs new file mode 100644 index 00000000..c75cfeb0 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Common/MemoryAllocator.cs @@ -0,0 +1,94 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Common +{ + internal class MemoryAllocator : IDisposable + { + private const int PoolEntries = 10; + + private struct PoolItem + { + public IntPtr Pointer; + public int Length; + public bool InUse; + } + + private readonly PoolItem[] _pool = new PoolItem[PoolEntries]; + + public ArrayPtr Allocate(int length) where T : unmanaged + { + int lengthInBytes = Unsafe.SizeOf() * length; + + IntPtr ptr = IntPtr.Zero; + + for (int i = 0; i < PoolEntries; i++) + { + ref PoolItem item = ref _pool[i]; + + if (!item.InUse && item.Length == lengthInBytes) + { + item.InUse = true; + ptr = item.Pointer; + break; + } + } + + if (ptr == IntPtr.Zero) + { + ptr = Marshal.AllocHGlobal(lengthInBytes); + + for (int i = 0; i < PoolEntries; i++) + { + ref PoolItem item = ref _pool[i]; + + if (!item.InUse) + { + item.InUse = true; + if (item.Pointer != IntPtr.Zero) + { + Marshal.FreeHGlobal(item.Pointer); + } + item.Pointer = ptr; + item.Length = lengthInBytes; + break; + } + } + } + + return new ArrayPtr(ptr, length); + } + + public unsafe void Free(ArrayPtr arr) where T : unmanaged + { + IntPtr ptr = (IntPtr)arr.ToPointer(); + + for (int i = 0; i < PoolEntries; i++) + { + ref PoolItem item = ref _pool[i]; + + if (item.Pointer == ptr) + { + item.InUse = false; + break; + } + } + } + + public void Dispose() + { + for (int i = 0; i < PoolEntries; i++) + { + ref PoolItem item = ref _pool[i]; + + if (item.Pointer != IntPtr.Zero) + { + Marshal.FreeHGlobal(item.Pointer); + item.Pointer = IntPtr.Zero; + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Common/MemoryUtil.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Common/MemoryUtil.cs new file mode 100644 index 00000000..afdc6b0b --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Common/MemoryUtil.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Common +{ + internal static class MemoryUtil + { + public static unsafe void Copy(T* dest, T* source, int length) where T : unmanaged + { + new Span(source, length).CopyTo(new Span(dest, length)); + } + + public static void Copy(ref T dest, ref T source) where T : unmanaged + { + MemoryMarshal.CreateSpan(ref source, 1).CopyTo(MemoryMarshal.CreateSpan(ref dest, 1)); + } + + public static unsafe void Fill(T* ptr, T value, int length) where T : unmanaged + { + new Span(ptr, length).Fill(value); + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Constants.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Constants.cs new file mode 100644 index 00000000..17efb203 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Constants.cs @@ -0,0 +1,69 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal static class Constants + { + public const int Vp9InterpExtend = 4; + + public const int MaxMbPlane = 3; + + public const int None = -1; + public const int IntraFrame = 0; + public const int LastFrame = 1; + public const int GoldenFrame = 2; + public const int AltRefFrame = 3; + public const int MaxRefFrames = 4; + + public const int MiSizeLog2 = 3; + public const int MiBlockSizeLog2 = 6 - MiSizeLog2; // 64 = 2^6 + + public const int MiSize = 1 << MiSizeLog2; // pixels per mi-unit + public const int MiBlockSize = 1 << MiBlockSizeLog2; // mi-units per max block + public const int MiMask = MiBlockSize - 1; + + public const int PartitionPloffset = 4; // number of probability models per block size + + /* Segment Feature Masks */ + public const int MaxMvRefCandidates = 2; + + public const int CompInterContexts = 5; + public const int RefContexts = 5; + + public const int EightTap = 0; + public const int EightTapSmooth = 1; + public const int EightTapSharp = 2; + public const int SwitchableFilters = 3; /* Number of switchable filters */ + public const int Bilinear = 3; + public const int Switchable = 4; /* should be the last one */ + + // Frame + public const int RefsPerFrame = 3; + + public const int NumPingPongBuffers = 2; + + public const int Class0Bits = 1; /* bits at integer precision for class 0 */ + public const int Class0Size = 1 << Class0Bits; + + public const int MvInUseBits = 14; + public const int MvUpp = (1 << MvInUseBits) - 1; + public const int MvLow = -(1 << MvInUseBits); + + // Coefficient token alphabet + public const int ZeroToken = 0; // 0 Extra Bits 0+0 + public const int OneToken = 1; // 1 Extra Bits 0+1 + public const int TwoToken = 2; // 2 Extra Bits 0+1 + + public const int PivotNode = 2; + + public const int Cat1MinVal = 5; + public const int Cat2MinVal = 7; + public const int Cat3MinVal = 11; + public const int Cat4MinVal = 19; + public const int Cat5MinVal = 35; + public const int Cat6MinVal = 67; + + public const int EobModelToken = 3; + + public const int SegmentAbsData = 1; + public const int MaxSegments = 8; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/DecodeFrame.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/DecodeFrame.cs new file mode 100644 index 00000000..5f03e1af --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/DecodeFrame.cs @@ -0,0 +1,1387 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Nvdec.Vp9.Common; +using Ryujinx.Graphics.Nvdec.Vp9.Dsp; +using Ryujinx.Graphics.Nvdec.Vp9.Types; +using Ryujinx.Graphics.Video; +using System; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + static class DecodeFrame + { + private static bool ReadIsValid(ArrayPtr start, int len) + { + return len != 0 && len <= start.Length; + } + + private static void InverseTransformBlockInter(ref MacroBlockD xd, int plane, TxSize txSize, Span dst, int stride, int eob) + { + ref MacroBlockDPlane pd = ref xd.Plane[plane]; + ArrayPtr dqcoeff = pd.DqCoeff; + Debug.Assert(eob > 0); + if (xd.CurBuf.HighBd) + { + Span dst16 = MemoryMarshal.Cast(dst); + if (xd.Lossless) + { + Idct.HighbdIwht4x4Add(dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd); + } + else + { + switch (txSize) + { + case TxSize.Tx4x4: + Idct.HighbdIdct4x4Add(dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd); + break; + case TxSize.Tx8x8: + Idct.HighbdIdct8x8Add(dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd); + break; + case TxSize.Tx16x16: + Idct.HighbdIdct16x16Add(dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd); + break; + case TxSize.Tx32x32: + Idct.HighbdIdct32x32Add(dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd); + break; + default: + Debug.Assert(false, "Invalid transform size"); + break; + } + } + } + else + { + if (xd.Lossless) + { + Idct.Iwht4x4Add(dqcoeff.AsSpan(), dst, stride, eob); + } + else + { + switch (txSize) + { + case TxSize.Tx4x4: + Idct.Idct4x4Add(dqcoeff.AsSpan(), dst, stride, eob); + break; + case TxSize.Tx8x8: + Idct.Idct8x8Add(dqcoeff.AsSpan(), dst, stride, eob); + break; + case TxSize.Tx16x16: + Idct.Idct16x16Add(dqcoeff.AsSpan(), dst, stride, eob); + break; + case TxSize.Tx32x32: + Idct.Idct32x32Add(dqcoeff.AsSpan(), dst, stride, eob); + break; + default: + Debug.Assert(false, "Invalid transform size"); + return; + } + } + } + + if (eob == 1) + { + dqcoeff.AsSpan()[0] = 0; + } + else + { + if (txSize <= TxSize.Tx16x16 && eob <= 10) + { + dqcoeff.AsSpan()[..(4 * (4 << (int)txSize))].Clear(); + } + else if (txSize == TxSize.Tx32x32 && eob <= 34) + { + dqcoeff.AsSpan()[..256].Clear(); + } + else + { + dqcoeff.AsSpan()[..(16 << ((int)txSize << 1))].Clear(); + } + } + } + + private static void InverseTransformBlockIntra( + ref MacroBlockD xd, + int plane, + TxType txType, + TxSize txSize, + Span dst, + int stride, + int eob) + { + ref MacroBlockDPlane pd = ref xd.Plane[plane]; + ArrayPtr dqcoeff = pd.DqCoeff; + Debug.Assert(eob > 0); + if (xd.CurBuf.HighBd) + { + Span dst16 = MemoryMarshal.Cast(dst); + if (xd.Lossless) + { + Idct.HighbdIwht4x4Add(dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd); + } + else + { + switch (txSize) + { + case TxSize.Tx4x4: + Idct.HighbdIht4x4Add(txType, dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd); + break; + case TxSize.Tx8x8: + Idct.HighbdIht8x8Add(txType, dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd); + break; + case TxSize.Tx16x16: + Idct.HighbdIht16x16Add(txType, dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd); + break; + case TxSize.Tx32x32: + Idct.HighbdIdct32x32Add(dqcoeff.AsSpan(), dst16, stride, eob, xd.Bd); + break; + default: + Debug.Assert(false, "Invalid transform size"); + break; + } + } + } + else + { + if (xd.Lossless) + { + Idct.Iwht4x4Add(dqcoeff.AsSpan(), dst, stride, eob); + } + else + { + switch (txSize) + { + case TxSize.Tx4x4: + Idct.Iht4x4Add(txType, dqcoeff.AsSpan(), dst, stride, eob); + break; + case TxSize.Tx8x8: + Idct.Iht8x8Add(txType, dqcoeff.AsSpan(), dst, stride, eob); + break; + case TxSize.Tx16x16: + Idct.Iht16x16Add(txType, dqcoeff.AsSpan(), dst, stride, eob); + break; + case TxSize.Tx32x32: + Idct.Idct32x32Add(dqcoeff.AsSpan(), dst, stride, eob); + break; + default: + Debug.Assert(false, "Invalid transform size"); + return; + } + } + } + + if (eob == 1) + { + dqcoeff.AsSpan()[0] = 0; + } + else + { + if (txType == TxType.DctDct && txSize <= TxSize.Tx16x16 && eob <= 10) + { + dqcoeff.AsSpan()[..(4 * (4 << (int)txSize))].Clear(); + } + else if (txSize == TxSize.Tx32x32 && eob <= 34) + { + dqcoeff.AsSpan()[..256].Clear(); + } + else + { + dqcoeff.AsSpan()[..(16 << ((int)txSize << 1))].Clear(); + } + } + } + + private static unsafe void PredictAndReconstructIntraBlock( + ref TileWorkerData twd, + ref ModeInfo mi, + int plane, + int row, + int col, + TxSize txSize) + { + ref MacroBlockD xd = ref twd.Xd; + ref MacroBlockDPlane pd = ref xd.Plane[plane]; + PredictionMode mode = (plane == 0) ? mi.Mode : mi.UvMode; + int dstOffset = 4 * row * pd.Dst.Stride + 4 * col; + byte* dst = &pd.Dst.Buf.ToPointer()[dstOffset]; + Span dstSpan = pd.Dst.Buf.AsSpan()[dstOffset..]; + + if (mi.SbType < BlockSize.Block8x8) + { + if (plane == 0) + { + mode = xd.Mi[0].Value.Bmi[(row << 1) + col].Mode; + } + } + + ReconIntra.PredictIntraBlock(ref xd, pd.N4Wl, txSize, mode, dst, pd.Dst.Stride, dst, pd.Dst.Stride, col, row, plane); + + if (mi.Skip == 0) + { + TxType txType = + (plane != 0 || xd.Lossless) ? TxType.DctDct : ReconIntra.IntraModeToTxTypeLookup[(int)mode]; + var sc = (plane != 0 || xd.Lossless) + ? Luts.Vp9DefaultScanOrders[(int)txSize] + : Luts.Vp9ScanOrders[(int)txSize][(int)txType]; + int eob = Detokenize.DecodeBlockTokens(ref twd, plane, sc, col, row, txSize, mi.SegmentId); + if (eob > 0) + { + InverseTransformBlockIntra(ref xd, plane, txType, txSize, dstSpan, pd.Dst.Stride, eob); + } + } + } + + private static int ReconstructInterBlock( + ref TileWorkerData twd, + ref ModeInfo mi, + int plane, + int row, + int col, + TxSize txSize) + { + ref MacroBlockD xd = ref twd.Xd; + ref MacroBlockDPlane pd = ref xd.Plane[plane]; + var sc = Luts.Vp9DefaultScanOrders[(int)txSize]; + int eob = Detokenize.DecodeBlockTokens(ref twd, plane, sc, col, row, txSize, mi.SegmentId); + Span dst = pd.Dst.Buf.AsSpan()[(4 * row * pd.Dst.Stride + 4 * col)..]; + + if (eob > 0) + { + InverseTransformBlockInter(ref xd, plane, txSize, dst, pd.Dst.Stride, eob); + } + return eob; + } + + private static unsafe void BuildMcBorder( + byte* src, + int srcStride, + byte* dst, + int dstStride, + int x, + int y, + int bW, + int bH, + int w, + int h) + { + // Get a pointer to the start of the real data for this row. + byte* refRow = src - x - y * srcStride; + + if (y >= h) + { + refRow += (h - 1) * srcStride; + } + else if (y > 0) + { + refRow += y * srcStride; + } + + do + { + int right = 0, copy; + int left = x < 0 ? -x : 0; + + if (left > bW) + { + left = bW; + } + + if (x + bW > w) + { + right = x + bW - w; + } + + if (right > bW) + { + right = bW; + } + + copy = bW - left - right; + + if (left != 0) + { + MemoryUtil.Fill(dst, refRow[0], left); + } + + if (copy != 0) + { + MemoryUtil.Copy(dst + left, refRow + x + left, copy); + } + + if (right != 0) + { + MemoryUtil.Fill(dst + left + copy, refRow[w - 1], right); + } + + dst += dstStride; + ++y; + + if (y > 0 && y < h) + { + refRow += srcStride; + } + } while (--bH != 0); + } + + private static unsafe void HighBuildMcBorder( + byte* src8, + int srcStride, + ushort* dst, + int dstStride, + int x, + int y, + int bW, + int bH, + int w, + int h) + { + // Get a pointer to the start of the real data for this row. + ushort* src = (ushort*)src8; + ushort* refRow = src - x - y * srcStride; + + if (y >= h) + { + refRow += (h - 1) * srcStride; + } + else if (y > 0) + { + refRow += y * srcStride; + } + + do + { + int right = 0, copy; + int left = x < 0 ? -x : 0; + + if (left > bW) + { + left = bW; + } + + if (x + bW > w) + { + right = x + bW - w; + } + + if (right > bW) + { + right = bW; + } + + copy = bW - left - right; + + if (left != 0) + { + MemoryUtil.Fill(dst, refRow[0], left); + } + + if (copy != 0) + { + MemoryUtil.Copy(dst + left, refRow + x + left, copy); + } + + if (right != 0) + { + MemoryUtil.Fill(dst + left + copy, refRow[w - 1], right); + } + + dst += dstStride; + ++y; + + if (y > 0 && y < h) + { + refRow += srcStride; + } + } while (--bH != 0); + } + + [SkipLocalsInit] + private static unsafe void ExtendAndPredict( + byte* bufPtr1, + int preBufStride, + int x0, + int y0, + int bW, + int bH, + int frameWidth, + int frameHeight, + int borderOffset, + byte* dst, + int dstBufStride, + int subpelX, + int subpelY, + Array8[] kernel, + ref ScaleFactors sf, + ref MacroBlockD xd, + int w, + int h, + int refr, + int xs, + int ys) + { + ushort* mcBufHigh = stackalloc ushort[80 * 2 * 80 * 2]; + if (xd.CurBuf.HighBd) + { + HighBuildMcBorder(bufPtr1, preBufStride, mcBufHigh, bW, x0, y0, bW, bH, frameWidth, frameHeight); + ReconInter.HighbdInterPredictor( + mcBufHigh + borderOffset, + bW, + (ushort*)dst, + dstBufStride, + subpelX, + subpelY, + ref sf, + w, + h, + refr, + kernel, + xs, + ys, + xd.Bd); + } + else + { + BuildMcBorder(bufPtr1, preBufStride, (byte*)mcBufHigh, bW, x0, y0, bW, bH, frameWidth, frameHeight); + ReconInter.InterPredictor( + (byte*)mcBufHigh + borderOffset, + bW, + dst, + dstBufStride, + subpelX, + subpelY, + ref sf, + w, + h, + refr, + kernel, + xs, + ys); + } + } + + private static unsafe void DecBuildInterPredictors( + ref MacroBlockD xd, + int plane, + int bw, + int bh, + int x, + int y, + int w, + int h, + int miX, + int miY, + Array8[] kernel, + ref ScaleFactors sf, + ref Buf2D preBuf, + ref Buf2D dstBuf, + ref Mv mv, + ref Surface refFrameBuf, + bool isScaled, + int refr) + { + ref MacroBlockDPlane pd = ref xd.Plane[plane]; + byte* dst = dstBuf.Buf.ToPointer() + dstBuf.Stride * y + x; + Mv32 scaledMv; + int xs, ys, x0, y0, x0_16, y0_16, frameWidth, frameHeight, bufStride, subpelX, subpelY; + byte* refFrame; + byte* bufPtr; + + // Get reference frame pointer, width and height. + if (plane == 0) + { + frameWidth = refFrameBuf.Width; + frameHeight = refFrameBuf.Height; + refFrame = refFrameBuf.YBuffer.ToPointer(); + } + else + { + frameWidth = refFrameBuf.UvWidth; + frameHeight = refFrameBuf.UvHeight; + refFrame = plane == 1 ? refFrameBuf.UBuffer.ToPointer() : refFrameBuf.VBuffer.ToPointer(); + } + + if (isScaled) + { + Mv mvQ4 = ReconInter.ClampMvToUmvBorderSb(ref xd, ref mv, bw, bh, pd.SubsamplingX, pd.SubsamplingY); + // Co-ordinate of containing block to pixel precision. + int xStart = (-xd.MbToLeftEdge >> (3 + pd.SubsamplingX)); + int yStart = (-xd.MbToTopEdge >> (3 + pd.SubsamplingY)); + // Co-ordinate of the block to 1/16th pixel precision. + x0_16 = (xStart + x) << Filter.SubpelBits; + y0_16 = (yStart + y) << Filter.SubpelBits; + + // Co-ordinate of current block in reference frame + // to 1/16th pixel precision. + x0_16 = sf.ScaleValueX(x0_16); + y0_16 = sf.ScaleValueY(y0_16); + + // Map the top left corner of the block into the reference frame. + x0 = sf.ScaleValueX(xStart + x); + y0 = sf.ScaleValueY(yStart + y); + + // Scale the MV and incorporate the sub-pixel offset of the block + // in the reference frame. + scaledMv = sf.ScaleMv(ref mvQ4, miX + x, miY + y); + xs = sf.XStepQ4; + ys = sf.YStepQ4; + } + else + { + // Co-ordinate of containing block to pixel precision. + x0 = (-xd.MbToLeftEdge >> (3 + pd.SubsamplingX)) + x; + y0 = (-xd.MbToTopEdge >> (3 + pd.SubsamplingY)) + y; + + // Co-ordinate of the block to 1/16th pixel precision. + x0_16 = x0 << Filter.SubpelBits; + y0_16 = y0 << Filter.SubpelBits; + + scaledMv.Row = mv.Row * (1 << (1 - pd.SubsamplingY)); + scaledMv.Col = mv.Col * (1 << (1 - pd.SubsamplingX)); + xs = ys = 16; + } + subpelX = scaledMv.Col & Filter.SubpelMask; + subpelY = scaledMv.Row & Filter.SubpelMask; + + // Calculate the top left corner of the best matching block in the + // reference frame. + x0 += scaledMv.Col >> Filter.SubpelBits; + y0 += scaledMv.Row >> Filter.SubpelBits; + x0_16 += scaledMv.Col; + y0_16 += scaledMv.Row; + + // Get reference block pointer. + bufPtr = refFrame + y0 * preBuf.Stride + x0; + bufStride = preBuf.Stride; + + // Do border extension if there is motion or the + // width/height is not a multiple of 8 pixels. + if (isScaled || scaledMv.Col != 0 || scaledMv.Row != 0 || (frameWidth & 0x7) != 0 || (frameHeight & 0x7) != 0) + { + int y1 = ((y0_16 + (h - 1) * ys) >> Filter.SubpelBits) + 1; + + // Get reference block bottom right horizontal coordinate. + int x1 = ((x0_16 + (w - 1) * xs) >> Filter.SubpelBits) + 1; + int xPad = 0, yPad = 0; + + if (subpelX != 0 || (sf.XStepQ4 != Filter.SubpelShifts)) + { + x0 -= Constants.Vp9InterpExtend - 1; + x1 += Constants.Vp9InterpExtend; + xPad = 1; + } + + if (subpelY != 0 || (sf.YStepQ4 != Filter.SubpelShifts)) + { + y0 -= Constants.Vp9InterpExtend - 1; + y1 += Constants.Vp9InterpExtend; + yPad = 1; + } + + // Skip border extension if block is inside the frame. + if (x0 < 0 || x0 > frameWidth - 1 || x1 < 0 || x1 > frameWidth - 1 || + y0 < 0 || y0 > frameHeight - 1 || y1 < 0 || y1 > frameHeight - 1) + { + // Extend the border. + byte* bufPtr1 = refFrame + y0 * bufStride + x0; + int bW = x1 - x0 + 1; + int bH = y1 - y0 + 1; + int borderOffset = yPad * 3 * bW + xPad * 3; + + ExtendAndPredict( + bufPtr1, + bufStride, + x0, + y0, + bW, + bH, + frameWidth, + frameHeight, + borderOffset, + dst, + dstBuf.Stride, + subpelX, + subpelY, + kernel, + ref sf, + ref xd, + w, + h, + refr, + xs, + ys); + + return; + } + } + + if (xd.CurBuf.HighBd) + { + ReconInter.HighbdInterPredictor( + (ushort*)bufPtr, + bufStride, + (ushort*)dst, + dstBuf.Stride, + subpelX, + subpelY, + ref sf, + w, + h, + refr, + kernel, + xs, + ys, + xd.Bd); + } + else + { + ReconInter.InterPredictor( + bufPtr, + bufStride, + dst, + dstBuf.Stride, + subpelX, + subpelY, + ref sf, + w, + h, + refr, + kernel, + xs, + ys); + } + } + + private static void DecBuildInterPredictorsSb(ref Vp9Common cm, ref MacroBlockD xd, int miRow, int miCol) + { + int plane; + int miX = miCol * Constants.MiSize; + int miY = miRow * Constants.MiSize; + ref ModeInfo mi = ref xd.Mi[0].Value; + Array8[] kernel = Luts.Vp9FilterKernels[mi.InterpFilter]; + BlockSize sbType = mi.SbType; + int isCompound = mi.HasSecondRef() ? 1 : 0; + int refr; + bool isScaled; + + for (refr = 0; refr < 1 + isCompound; ++refr) + { + int frame = mi.RefFrame[refr]; + ref RefBuffer refBuf = ref cm.FrameRefs[frame - Constants.LastFrame]; + ref ScaleFactors sf = ref refBuf.Sf; + ref Surface refFrameBuf = ref refBuf.Buf; + + if (!sf.IsValidScale()) + { + xd.ErrorInfo.Value.InternalError(CodecErr.CodecUnsupBitstream, "Reference frame has invalid dimensions"); + } + + isScaled = sf.IsScaled(); + ReconInter.SetupPrePlanes(ref xd, refr, ref refFrameBuf, miRow, miCol, isScaled ? new Ptr(ref sf) : Ptr.Null); + xd.BlockRefs[refr] = new Ptr(ref refBuf); + + if (sbType < BlockSize.Block8x8) + { + for (plane = 0; plane < Constants.MaxMbPlane; ++plane) + { + ref MacroBlockDPlane pd = ref xd.Plane[plane]; + ref Buf2D dstBuf = ref pd.Dst; + int num4x4W = pd.N4W; + int num4x4H = pd.N4H; + int n4Wx4 = 4 * num4x4W; + int n4Hx4 = 4 * num4x4H; + ref Buf2D preBuf = ref pd.Pre[refr]; + int i = 0, x, y; + for (y = 0; y < num4x4H; ++y) + { + for (x = 0; x < num4x4W; ++x) + { + Mv mv = ReconInter.AverageSplitMvs(ref pd, ref mi, refr, i++); + DecBuildInterPredictors( + ref xd, + plane, + n4Wx4, + n4Hx4, + 4 * x, + 4 * y, + 4, + 4, + miX, + miY, + kernel, + ref sf, + ref preBuf, + ref dstBuf, + ref mv, + ref refFrameBuf, + isScaled, + refr); + } + } + } + } + else + { + Mv mv = mi.Mv[refr]; + for (plane = 0; plane < Constants.MaxMbPlane; ++plane) + { + ref MacroBlockDPlane pd = ref xd.Plane[plane]; + ref Buf2D dstBuf = ref pd.Dst; + int num4x4W = pd.N4W; + int num4x4H = pd.N4H; + int n4Wx4 = 4 * num4x4W; + int n4Hx4 = 4 * num4x4H; + ref Buf2D preBuf = ref pd.Pre[refr]; + DecBuildInterPredictors( + ref xd, + plane, + n4Wx4, + n4Hx4, + 0, + 0, + n4Wx4, + n4Hx4, + miX, + miY, + kernel, + ref sf, + ref preBuf, + ref dstBuf, + ref mv, + ref refFrameBuf, + isScaled, + refr); + } + } + } + } + + private static unsafe void DecResetSkipContext(ref MacroBlockD xd) + { + int i; + for (i = 0; i < Constants.MaxMbPlane; i++) + { + ref MacroBlockDPlane pd = ref xd.Plane[i]; + MemoryUtil.Fill(pd.AboveContext.ToPointer(), (sbyte)0, pd.N4W); + MemoryUtil.Fill(pd.LeftContext.ToPointer(), (sbyte)0, pd.N4H); + } + } + + private static void SetPlaneN4(ref MacroBlockD xd, int bw, int bh, int bwl, int bhl) + { + int i; + for (i = 0; i < Constants.MaxMbPlane; i++) + { + xd.Plane[i].N4W = (ushort)((bw << 1) >> xd.Plane[i].SubsamplingX); + xd.Plane[i].N4H = (ushort)((bh << 1) >> xd.Plane[i].SubsamplingY); + xd.Plane[i].N4Wl = (byte)(bwl - xd.Plane[i].SubsamplingX); + xd.Plane[i].N4Hl = (byte)(bhl - xd.Plane[i].SubsamplingY); + } + } + + private static ref ModeInfo SetOffsets( + ref Vp9Common cm, + ref MacroBlockD xd, + BlockSize bsize, + int miRow, + int miCol, + int bw, + int bh, + int xMis, + int yMis, + int bwl, + int bhl) + { + int offset = miRow * cm.MiStride + miCol; + int x, y; + ref TileInfo tile = ref xd.Tile; + + xd.Mi = cm.MiGridVisible.Slice(offset); + xd.Mi[0] = new Ptr(ref cm.Mi[offset]); + xd.Mi[0].Value.SbType = bsize; + for (y = 0; y < yMis; ++y) + { + for (x = y == 0 ? 1 : 0; x < xMis; ++x) + { + xd.Mi[y * cm.MiStride + x] = xd.Mi[0]; + } + } + + SetPlaneN4(ref xd, bw, bh, bwl, bhl); + + xd.SetSkipContext(miRow, miCol); + + // Distance of Mb to the various image edges. These are specified to 8th pel + // as they are always compared to values that are in 1/8th pel units + xd.SetMiRowCol(ref tile, miRow, bh, miCol, bw, cm.MiRows, cm.MiCols); + + ReconInter.SetupDstPlanes(ref xd.Plane, ref xd.CurBuf, miRow, miCol); + + return ref xd.Mi[0].Value; + } + + private static void DecodeBlock( + ref TileWorkerData twd, + ref Vp9Common cm, + int miRow, + int miCol, + BlockSize bsize, + int bwl, + int bhl) + { + bool less8x8 = bsize < BlockSize.Block8x8; + int bw = 1 << (bwl - 1); + int bh = 1 << (bhl - 1); + int xMis = Math.Min(bw, cm.MiCols - miCol); + int yMis = Math.Min(bh, cm.MiRows - miRow); + ref Reader r = ref twd.BitReader; + ref MacroBlockD xd = ref twd.Xd; + + ref ModeInfo mi = ref SetOffsets(ref cm, ref xd, bsize, miRow, miCol, bw, bh, xMis, yMis, bwl, bhl); + + if (bsize >= BlockSize.Block8x8 && (cm.SubsamplingX != 0 || cm.SubsamplingY != 0)) + { + BlockSize uvSubsize = Luts.SsSizeLookup[(int)bsize][cm.SubsamplingX][cm.SubsamplingY]; + if (uvSubsize == BlockSize.BlockInvalid) + { + xd.ErrorInfo.Value.InternalError(CodecErr.CodecCorruptFrame, "Invalid block size."); + } + } + + DecodeMv.ReadModeInfo(ref twd, ref cm, miRow, miCol, xMis, yMis); + + if (mi.Skip != 0) + { + DecResetSkipContext(ref xd); + } + + if (!mi.IsInterBlock()) + { + int plane; + for (plane = 0; plane < Constants.MaxMbPlane; ++plane) + { + ref MacroBlockDPlane pd = ref xd.Plane[plane]; + TxSize txSize = plane != 0 ? mi.GetUvTxSize(ref pd) : mi.TxSize; + int num4x4W = pd.N4W; + int num4x4H = pd.N4H; + int step = 1 << (int)txSize; + int row, col; + int maxBlocksWide = num4x4W + (xd.MbToRightEdge >= 0 ? 0 : xd.MbToRightEdge >> (5 + pd.SubsamplingX)); + int maxBlocksHigh = num4x4H + (xd.MbToBottomEdge >= 0 ? 0 : xd.MbToBottomEdge >> (5 + pd.SubsamplingY)); + + xd.MaxBlocksWide = (uint)(xd.MbToRightEdge >= 0 ? 0 : maxBlocksWide); + xd.MaxBlocksHigh = (uint)(xd.MbToBottomEdge >= 0 ? 0 : maxBlocksHigh); + + for (row = 0; row < maxBlocksHigh; row += step) + { + for (col = 0; col < maxBlocksWide; col += step) + { + PredictAndReconstructIntraBlock(ref twd, ref mi, plane, row, col, txSize); + } + } + } + } + else + { + // Prediction + DecBuildInterPredictorsSb(ref cm, ref xd, miRow, miCol); + + // Reconstruction + if (mi.Skip == 0) + { + int eobtotal = 0; + int plane; + + for (plane = 0; plane < Constants.MaxMbPlane; ++plane) + { + ref MacroBlockDPlane pd = ref xd.Plane[plane]; + TxSize txSize = plane != 0 ? mi.GetUvTxSize(ref pd) : mi.TxSize; + int num4x4W = pd.N4W; + int num4x4H = pd.N4H; + int step = 1 << (int)txSize; + int row, col; + int maxBlocksWide = num4x4W + (xd.MbToRightEdge >= 0 ? 0 : xd.MbToRightEdge >> (5 + pd.SubsamplingX)); + int maxBlocksHigh = num4x4H + (xd.MbToBottomEdge >= 0 ? 0 : xd.MbToBottomEdge >> (5 + pd.SubsamplingY)); + + xd.MaxBlocksWide = (uint)(xd.MbToRightEdge >= 0 ? 0 : maxBlocksWide); + xd.MaxBlocksHigh = (uint)(xd.MbToBottomEdge >= 0 ? 0 : maxBlocksHigh); + + for (row = 0; row < maxBlocksHigh; row += step) + { + for (col = 0; col < maxBlocksWide; col += step) + { + eobtotal += ReconstructInterBlock(ref twd, ref mi, plane, row, col, txSize); + } + } + } + + if (!less8x8 && eobtotal == 0) + { + mi.Skip = 1; // Skip loopfilter + } + } + } + + xd.Corrupted |= r.HasError(); + + if (cm.Lf.FilterLevel != 0) + { + LoopFilter.BuildMask(ref cm, ref mi, miRow, miCol, bw, bh); + } + } + + private static int DecPartitionPlaneContext(ref TileWorkerData twd, int miRow, int miCol, int bsl) + { + ref sbyte aboveCtx = ref twd.Xd.AboveSegContext[miCol]; + ref sbyte leftCtx = ref twd.Xd.LeftSegContext[miRow & Constants.MiMask]; + int above = (aboveCtx >> bsl) & 1, left = (leftCtx >> bsl) & 1; + + return (left * 2 + above) + bsl * Constants.PartitionPloffset; + } + + private static void DecUpdatePartitionContext( + ref TileWorkerData twd, + int miRow, + int miCol, + BlockSize subsize, + int bw) + { + Span aboveCtx = twd.Xd.AboveSegContext.Slice(miCol).AsSpan(); + Span leftCtx = MemoryMarshal.CreateSpan(ref twd.Xd.LeftSegContext[miRow & Constants.MiMask], 8 - (miRow & Constants.MiMask)); + + // Update the partition context at the end notes. Set partition bits + // of block sizes larger than the current one to be one, and partition + // bits of smaller block sizes to be zero. + aboveCtx[..bw].Fill(Luts.PartitionContextLookup[(int)subsize].Above); + leftCtx[..bw].Fill(Luts.PartitionContextLookup[(int)subsize].Left); + } + + private static PartitionType ReadPartition( + ref TileWorkerData twd, + int miRow, + int miCol, + int hasRows, + int hasCols, + int bsl) + { + int ctx = DecPartitionPlaneContext(ref twd, miRow, miCol, bsl); + ReadOnlySpan probs = MemoryMarshal.CreateReadOnlySpan(ref twd.Xd.PartitionProbs[ctx][0], 3); + PartitionType p; + ref Reader r = ref twd.BitReader; + + if (hasRows != 0 && hasCols != 0) + { + p = (PartitionType)r.ReadTree(Luts.Vp9PartitionTree, probs); + } + else if (hasRows == 0 && hasCols != 0) + { + p = r.Read(probs[1]) != 0 ? PartitionType.PartitionSplit : PartitionType.PartitionHorz; + } + else if (hasRows != 0 && hasCols == 0) + { + p = r.Read(probs[2]) != 0 ? PartitionType.PartitionSplit : PartitionType.PartitionVert; + } + else + { + p = PartitionType.PartitionSplit; + } + + if (!twd.Xd.Counts.IsNull) + { + ++twd.Xd.Counts.Value.Partition[ctx][(int)p]; + } + + return p; + } + + private static void DecodePartition( + ref TileWorkerData twd, + ref Vp9Common cm, + int miRow, + int miCol, + BlockSize bsize, + int n4x4L2) + { + int n8x8L2 = n4x4L2 - 1; + int num8x8Wh = 1 << n8x8L2; + int hbs = num8x8Wh >> 1; + PartitionType partition; + BlockSize subsize; + bool hasRows = (miRow + hbs) < cm.MiRows; + bool hasCols = (miCol + hbs) < cm.MiCols; + ref MacroBlockD xd = ref twd.Xd; + + if (miRow >= cm.MiRows || miCol >= cm.MiCols) + { + return; + } + + partition = ReadPartition(ref twd, miRow, miCol, hasRows ? 1 : 0, hasCols ? 1 : 0, n8x8L2); + subsize = Luts.SubsizeLookup[(int)partition][(int)bsize]; + if (hbs == 0) + { + // Calculate bmode block dimensions (log 2) + xd.BmodeBlocksWl = (byte)(1 >> ((partition & PartitionType.PartitionVert) != 0 ? 1 : 0)); + xd.BmodeBlocksHl = (byte)(1 >> ((partition & PartitionType.PartitionHorz) != 0 ? 1 : 0)); + DecodeBlock(ref twd, ref cm, miRow, miCol, subsize, 1, 1); + } + else + { + switch (partition) + { + case PartitionType.PartitionNone: + DecodeBlock(ref twd, ref cm, miRow, miCol, subsize, n4x4L2, n4x4L2); + break; + case PartitionType.PartitionHorz: + DecodeBlock(ref twd, ref cm, miRow, miCol, subsize, n4x4L2, n8x8L2); + if (hasRows) + { + DecodeBlock(ref twd, ref cm, miRow + hbs, miCol, subsize, n4x4L2, n8x8L2); + } + + break; + case PartitionType.PartitionVert: + DecodeBlock(ref twd, ref cm, miRow, miCol, subsize, n8x8L2, n4x4L2); + if (hasCols) + { + DecodeBlock(ref twd, ref cm, miRow, miCol + hbs, subsize, n8x8L2, n4x4L2); + } + + break; + case PartitionType.PartitionSplit: + DecodePartition(ref twd, ref cm, miRow, miCol, subsize, n8x8L2); + DecodePartition(ref twd, ref cm, miRow, miCol + hbs, subsize, n8x8L2); + DecodePartition(ref twd, ref cm, miRow + hbs, miCol, subsize, n8x8L2); + DecodePartition(ref twd, ref cm, miRow + hbs, miCol + hbs, subsize, n8x8L2); + break; + default: + Debug.Assert(false, "Invalid partition type"); + break; + } + } + + // Update partition context + if (bsize >= BlockSize.Block8x8 && (bsize == BlockSize.Block8x8 || partition != PartitionType.PartitionSplit)) + { + DecUpdatePartitionContext(ref twd, miRow, miCol, subsize, num8x8Wh); + } + } + + private static void SetupTokenDecoder( + ArrayPtr data, + int readSize, + ref InternalErrorInfo errorInfo, + ref Reader r) + { + // Validate the calculated partition length. If the buffer described by the + // partition can't be fully read then throw an error. + if (!ReadIsValid(data, readSize)) + { + errorInfo.InternalError(CodecErr.CodecCorruptFrame, "Truncated packet or corrupt tile length"); + } + + if (r.Init(data, readSize)) + { + errorInfo.InternalError(CodecErr.CodecMemError, "Failed to allocate bool decoder 1"); + } + } + + // Reads the next tile returning its size and adjusting '*data' accordingly + // based on 'isLast'. + private static void GetTileBuffer( + bool isLast, + ref InternalErrorInfo errorInfo, + ref ArrayPtr data, + ref TileBuffer buf) + { + int size; + + if (!isLast) + { + if (!ReadIsValid(data, 4)) + { + errorInfo.InternalError(CodecErr.CodecCorruptFrame, "Truncated packet or corrupt tile length"); + } + + size = BinaryPrimitives.ReadInt32BigEndian(data.AsSpan()); + data = data.Slice(4); + + if (size > data.Length) + { + errorInfo.InternalError(CodecErr.CodecCorruptFrame, "Truncated packet or corrupt tile size"); + } + } + else + { + size = data.Length; + } + + buf.Data = data; + buf.Size = size; + + data = data.Slice(size); + } + + private static void GetTileBuffers(ref Vp9Common cm, ArrayPtr data, int tileCols, ref Array64 tileBuffers) + { + int c; + + for (c = 0; c < tileCols; ++c) + { + bool isLast = c == tileCols - 1; + ref TileBuffer buf = ref tileBuffers[c]; + buf.Col = c; + GetTileBuffer(isLast, ref cm.Error, ref data, ref buf); + } + } + + private static void GetTileBuffers( + ref Vp9Common cm, + ArrayPtr data, + int tileCols, + int tileRows, + ref Array4> tileBuffers) + { + int r, c; + + for (r = 0; r < tileRows; ++r) + { + for (c = 0; c < tileCols; ++c) + { + bool isLast = (r == tileRows - 1) && (c == tileCols - 1); + ref TileBuffer buf = ref tileBuffers[r][c]; + GetTileBuffer(isLast, ref cm.Error, ref data, ref buf); + } + } + } + + public static unsafe ArrayPtr DecodeTiles(ref Vp9Common cm, ArrayPtr data) + { + int alignedCols = TileInfo.MiColsAlignedToSb(cm.MiCols); + int tileCols = 1 << cm.Log2TileCols; + int tileRows = 1 << cm.Log2TileRows; + Array4> tileBuffers = new(); + int tileRow, tileCol; + int miRow, miCol; + + Debug.Assert(tileRows <= 4); + Debug.Assert(tileCols <= (1 << 6)); + + // Note: this memset assumes above_context[0], [1] and [2] + // are allocated as part of the same buffer. + MemoryUtil.Fill(cm.AboveContext.ToPointer(), (sbyte)0, Constants.MaxMbPlane * 2 * alignedCols); + MemoryUtil.Fill(cm.AboveSegContext.ToPointer(), (sbyte)0, alignedCols); + + LoopFilter.ResetLfm(ref cm); + + GetTileBuffers(ref cm, data, tileCols, tileRows, ref tileBuffers); + // Load all tile information into tile_data. + for (tileRow = 0; tileRow < tileRows; ++tileRow) + { + for (tileCol = 0; tileCol < tileCols; ++tileCol) + { + ref TileBuffer buf = ref tileBuffers[tileRow][tileCol]; + ref TileWorkerData tileData = ref cm.TileWorkerData[tileCols * tileRow + tileCol]; + tileData.Xd = cm.Mb; + tileData.Xd.Corrupted = false; + tileData.Xd.Counts = cm.Counts; + tileData.Dqcoeff = new Array32>(); + tileData.Xd.Tile.Init(ref cm, tileRow, tileCol); + SetupTokenDecoder(buf.Data, buf.Size, ref cm.Error, ref tileData.BitReader); + cm.InitMacroBlockD(ref tileData.Xd, new ArrayPtr(ref tileData.Dqcoeff[0][0], 32 * 32)); + } + } + + for (tileRow = 0; tileRow < tileRows; ++tileRow) + { + TileInfo tile = new(); + tile.SetRow(ref cm, tileRow); + for (miRow = tile.MiRowStart; miRow < tile.MiRowEnd; miRow += Constants.MiBlockSize) + { + for (tileCol = 0; tileCol < tileCols; ++tileCol) + { + int col = tileCol; + ref TileWorkerData tileData = ref cm.TileWorkerData[tileCols * tileRow + col]; + tile.SetCol(ref cm, col); + tileData.Xd.LeftContext = new Array3>(); + tileData.Xd.LeftSegContext = new Array8(); + for (miCol = tile.MiColStart; miCol < tile.MiColEnd; miCol += Constants.MiBlockSize) + { + DecodePartition(ref tileData, ref cm, miRow, miCol, BlockSize.Block64x64, 4); + } + cm.Mb.Corrupted |= tileData.Xd.Corrupted; + if (cm.Mb.Corrupted) + { + cm.Error.InternalError(CodecErr.CodecCorruptFrame, "Failed to decode tile data"); + } + } + } + } + + // Get last tile data. + return cm.TileWorkerData[tileCols * tileRows - 1].BitReader.FindEnd(); + } + + private static bool DecodeTileCol(ref TileWorkerData tileData, ref Vp9Common cm, ref Array64 tileBuffers) + { + ref TileInfo tile = ref tileData.Xd.Tile; + int finalCol = (1 << cm.Log2TileCols) - 1; + ArrayPtr bitReaderEnd = ArrayPtr.Null; + + int n = tileData.BufStart; + + tileData.Xd.Corrupted = false; + + do + { + ref TileBuffer buf = ref tileBuffers[n]; + + Debug.Assert(cm.Log2TileRows == 0); + tileData.Dqcoeff = new Array32>(); + tile.Init(ref cm, 0, buf.Col); + SetupTokenDecoder(buf.Data, buf.Size, ref tileData.ErrorInfo, ref tileData.BitReader); + cm.InitMacroBlockD(ref tileData.Xd, new ArrayPtr(ref tileData.Dqcoeff[0][0], 32 * 32)); + tileData.Xd.ErrorInfo = new Ptr(ref tileData.ErrorInfo); + + for (int miRow = tile.MiRowStart; miRow < tile.MiRowEnd; miRow += Constants.MiBlockSize) + { + tileData.Xd.LeftContext = new Array3>(); + tileData.Xd.LeftSegContext = new Array8(); + for (int miCol = tile.MiColStart; miCol < tile.MiColEnd; miCol += Constants.MiBlockSize) + { + DecodePartition(ref tileData, ref cm, miRow, miCol, BlockSize.Block64x64, 4); + } + } + + if (buf.Col == finalCol) + { + bitReaderEnd = tileData.BitReader.FindEnd(); + } + } while (!tileData.Xd.Corrupted && ++n <= tileData.BufEnd); + + tileData.DataEnd = bitReaderEnd; + + return !tileData.Xd.Corrupted; + } + + public static ArrayPtr DecodeTilesMt(ref Vp9Common cm, ArrayPtr data, int maxThreads) + { + ArrayPtr bitReaderEnd = ArrayPtr.Null; + + int tileCols = 1 << cm.Log2TileCols; + int tileRows = 1 << cm.Log2TileRows; + int totalTiles = tileCols * tileRows; + int numWorkers = Math.Min(maxThreads, tileCols); + int n; + + Debug.Assert(tileCols <= (1 << 6)); + Debug.Assert(tileRows == 1); + + cm.AboveContext.AsSpan().Clear(); + cm.AboveSegContext.AsSpan().Clear(); + + for (n = 0; n < numWorkers; ++n) + { + ref TileWorkerData tileData = ref cm.TileWorkerData[n + totalTiles]; + + tileData.Xd = cm.Mb; + tileData.Xd.Counts = new Ptr(ref tileData.Counts); + tileData.Counts = new Vp9BackwardUpdates(); + } + + Array64 tileBuffers = new(); + + GetTileBuffers(ref cm, data, tileCols, ref tileBuffers); + + tileBuffers.AsSpan()[..tileCols].Sort(CompareTileBuffers); + + if (numWorkers == tileCols) + { + TileBuffer largest = tileBuffers[0]; + Span buffers = tileBuffers.AsSpan(); + buffers[1..].CopyTo(buffers[..(tileBuffers.Length - 1)]); + tileBuffers[tileCols - 1] = largest; + } + else + { + int start = 0, end = tileCols - 2; + TileBuffer tmp; + + // Interleave the tiles to distribute the load between threads, assuming a + // larger tile implies it is more difficult to decode. + while (start < end) + { + tmp = tileBuffers[start]; + tileBuffers[start] = tileBuffers[end]; + tileBuffers[end] = tmp; + start += 2; + end -= 2; + } + } + + int baseVal = tileCols / numWorkers; + int remain = tileCols % numWorkers; + int bufStart = 0; + + for (n = 0; n < numWorkers; ++n) + { + int count = baseVal + (remain + n) / numWorkers; + ref TileWorkerData tileData = ref cm.TileWorkerData[n + totalTiles]; + + tileData.BufStart = bufStart; + tileData.BufEnd = bufStart + count - 1; + tileData.DataEnd = data.Slice(data.Length); + bufStart += count; + } + + Ptr cmPtr = new(ref cm); + + Parallel.For(0, numWorkers, n => + { + ref TileWorkerData tileData = ref cmPtr.Value.TileWorkerData[n + totalTiles]; + + if (!DecodeTileCol(ref tileData, ref cmPtr.Value, ref tileBuffers)) + { + cmPtr.Value.Mb.Corrupted = true; + } + }); + + for (; n > 0; --n) + { + if (bitReaderEnd.IsNull) + { + ref TileWorkerData tileData = ref cm.TileWorkerData[n - 1 + totalTiles]; + bitReaderEnd = tileData.DataEnd; + } + } + + for (n = 0; n < numWorkers; ++n) + { + ref TileWorkerData tileData = ref cm.TileWorkerData[n + totalTiles]; + AccumulateFrameCounts(ref cm.Counts.Value, ref tileData.Counts); + } + + Debug.Assert(!bitReaderEnd.IsNull || cm.Mb.Corrupted); + + return bitReaderEnd; + } + + private static int CompareTileBuffers(TileBuffer bufA, TileBuffer bufB) + { + return (bufA.Size < bufB.Size ? 1 : 0) - (bufA.Size > bufB.Size ? 1 : 0); + } + + private static void AccumulateFrameCounts(ref Vp9BackwardUpdates accum, ref Vp9BackwardUpdates counts) + { + Span a = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref accum, 1)); + Span c = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref counts, 1)); + + for (int i = 0; i < a.Length; i++) + { + a[i] += c[i]; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/DecodeMv.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/DecodeMv.cs new file mode 100644 index 00000000..091490bf --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/DecodeMv.cs @@ -0,0 +1,1176 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Nvdec.Vp9.Dsp; +using Ryujinx.Graphics.Nvdec.Vp9.Types; +using Ryujinx.Graphics.Video; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal static class DecodeMv + { + private const int MvrefNeighbours = 8; + + private static PredictionMode ReadIntraMode(ref Reader r, ReadOnlySpan p) + { + return (PredictionMode)r.ReadTree(Luts.Vp9IntraModeTree, p); + } + + private static PredictionMode ReadIntraModeY(ref Vp9Common cm, ref MacroBlockD xd, ref Reader r, int sizeGroup) + { + PredictionMode yMode = ReadIntraMode(ref r, cm.Fc.Value.YModeProb[sizeGroup].AsSpan()); + if (!xd.Counts.IsNull) + { + ++xd.Counts.Value.YMode[sizeGroup][(int)yMode]; + } + + return yMode; + } + + private static PredictionMode ReadIntraModeUv(ref Vp9Common cm, ref MacroBlockD xd, ref Reader r, byte yMode) + { + PredictionMode uvMode = ReadIntraMode(ref r, cm.Fc.Value.UvModeProb[yMode].AsSpan()); + if (!xd.Counts.IsNull) + { + ++xd.Counts.Value.UvMode[yMode][(int)uvMode]; + } + + return uvMode; + } + + private static PredictionMode ReadInterMode(ref Vp9Common cm, ref MacroBlockD xd, ref Reader r, int ctx) + { + int mode = r.ReadTree(Luts.Vp9InterModeTree, cm.Fc.Value.InterModeProb[ctx].AsSpan()); + if (!xd.Counts.IsNull) + { + ++xd.Counts.Value.InterMode[ctx][mode]; + } + + return PredictionMode.NearestMv + mode; + } + + private static int ReadSegmentId(ref Reader r, ref Array7 segTreeProbs) + { + return r.ReadTree(Luts.Vp9SegmentTree, segTreeProbs.AsSpan()); + } + + private static ReadOnlySpan GetTxProbs(ref Vp9EntropyProbs fc, TxSize maxTxSize, int ctx) + { + switch (maxTxSize) + { + case TxSize.Tx8x8: + return fc.Tx8x8Prob[ctx].AsSpan(); + case TxSize.Tx16x16: + return fc.Tx16x16Prob[ctx].AsSpan(); + case TxSize.Tx32x32: + return fc.Tx32x32Prob[ctx].AsSpan(); + default: + Debug.Assert(false, "Invalid maxTxSize."); + + return ReadOnlySpan.Empty; + } + } + + private static Span GetTxCounts(ref Vp9BackwardUpdates counts, TxSize maxTxSize, int ctx) + { + switch (maxTxSize) + { + case TxSize.Tx8x8: + return counts.Tx8x8[ctx].AsSpan(); + case TxSize.Tx16x16: + return counts.Tx16x16[ctx].AsSpan(); + case TxSize.Tx32x32: + return counts.Tx32x32[ctx].AsSpan(); + default: + Debug.Assert(false, "Invalid maxTxSize."); + + return Span.Empty; + } + } + + private static TxSize ReadSelectedTxSize(ref Vp9Common cm, ref MacroBlockD xd, TxSize maxTxSize, ref Reader r) + { + int ctx = xd.GetTxSizeContext(); + ReadOnlySpan txProbs = GetTxProbs(ref cm.Fc.Value, maxTxSize, ctx); + TxSize txSize = (TxSize)r.Read(txProbs[0]); + if (txSize != TxSize.Tx4x4 && maxTxSize >= TxSize.Tx16x16) + { + txSize += r.Read(txProbs[1]); + if (txSize != TxSize.Tx8x8 && maxTxSize >= TxSize.Tx32x32) + { + txSize += r.Read(txProbs[2]); + } + } + + if (!xd.Counts.IsNull) + { + ++GetTxCounts(ref xd.Counts.Value, maxTxSize, ctx)[(int)txSize]; + } + + return txSize; + } + + private static TxSize ReadTxSize(ref Vp9Common cm, ref MacroBlockD xd, bool allowSelect, ref Reader r) + { + TxMode txMode = cm.TxMode; + BlockSize bsize = xd.Mi[0].Value.SbType; + TxSize maxTxSize = Luts.MaxTxSizeLookup[(int)bsize]; + if (allowSelect && txMode == TxMode.TxModeSelect && bsize >= BlockSize.Block8x8) + { + return ReadSelectedTxSize(ref cm, ref xd, maxTxSize, ref r); + } + + return (TxSize)Math.Min((int)maxTxSize, (int)Luts.TxModeToBiggestTxSize[(int)txMode]); + } + + private static int DecGetSegmentId(ref Vp9Common cm, ArrayPtr segmentIds, int miOffset, int xMis, int yMis) + { + int x, y, segmentId = int.MaxValue; + + for (y = 0; y < yMis; y++) + { + for (x = 0; x < xMis; x++) + { + segmentId = Math.Min(segmentId, segmentIds[miOffset + y * cm.MiCols + x]); + } + } + + Debug.Assert(segmentId >= 0 && segmentId < Constants.MaxSegments); + + return segmentId; + } + + private static void SetSegmentId(ref Vp9Common cm, int miOffset, int xMis, int yMis, int segmentId) + { + int x, y; + + Debug.Assert(segmentId >= 0 && segmentId < Constants.MaxSegments); + + for (y = 0; y < yMis; y++) + { + for (x = 0; x < xMis; x++) + { + cm.CurrentFrameSegMap[miOffset + y * cm.MiCols + x] = (byte)segmentId; + } + } + } + + private static void CopySegmentId( + ref Vp9Common cm, + ArrayPtr lastSegmentIds, + ArrayPtr currentSegmentIds, + int miOffset, + int xMis, + int yMis) + { + int x, y; + + for (y = 0; y < yMis; y++) + { + for (x = 0; x < xMis; x++) + { + currentSegmentIds[miOffset + y * cm.MiCols + x] = (byte)(!lastSegmentIds.IsNull ? lastSegmentIds[miOffset + y * cm.MiCols + x] : 0); + } + } + } + + private static int ReadIntraSegmentId(ref Vp9Common cm, int miOffset, int xMis, int yMis, ref Reader r) + { + ref Segmentation seg = ref cm.Seg; + int segmentId; + + if (!seg.Enabled) + { + return 0; // Default for disabled segmentation + } + + if (!seg.UpdateMap) + { + CopySegmentId(ref cm, cm.LastFrameSegMap, cm.CurrentFrameSegMap, miOffset, xMis, yMis); + + return 0; + } + + segmentId = ReadSegmentId(ref r, ref cm.Fc.Value.SegTreeProb); + SetSegmentId(ref cm, miOffset, xMis, yMis, segmentId); + + return segmentId; + } + + private static int ReadInterSegmentId( + ref Vp9Common cm, + ref MacroBlockD xd, + int miRow, + int miCol, + ref Reader r, + int xMis, + int yMis) + { + ref Segmentation seg = ref cm.Seg; + ref ModeInfo mi = ref xd.Mi[0].Value; + int predictedSegmentId, segmentId; + int miOffset = miRow * cm.MiCols + miCol; + + if (!seg.Enabled) + { + return 0; // Default for disabled segmentation + } + + predictedSegmentId = !cm.LastFrameSegMap.IsNull + ? DecGetSegmentId(ref cm, cm.LastFrameSegMap, miOffset, xMis, yMis) + : 0; + + if (!seg.UpdateMap) + { + CopySegmentId(ref cm, cm.LastFrameSegMap, cm.CurrentFrameSegMap, miOffset, xMis, yMis); + + return predictedSegmentId; + } + + if (seg.TemporalUpdate) + { + byte predProb = Segmentation.GetPredProbSegId(ref cm.Fc.Value.SegPredProb, ref xd); + mi.SegIdPredicted = (sbyte)r.Read(predProb); + segmentId = mi.SegIdPredicted != 0 ? predictedSegmentId : ReadSegmentId(ref r, ref cm.Fc.Value.SegTreeProb); + } + else + { + segmentId = ReadSegmentId(ref r, ref cm.Fc.Value.SegTreeProb); + } + SetSegmentId(ref cm, miOffset, xMis, yMis, segmentId); + + return segmentId; + } + + private static int ReadSkip(ref Vp9Common cm, ref MacroBlockD xd, int segmentId, ref Reader r) + { + if (cm.Seg.IsSegFeatureActive(segmentId, SegLvlFeatures.SegLvlSkip) != 0) + { + return 1; + } + + int ctx = xd.GetSkipContext(); + int skip = r.Read(cm.Fc.Value.SkipProb[ctx]); + if (!xd.Counts.IsNull) + { + ++xd.Counts.Value.Skip[ctx][skip]; + } + + return skip; + } + + private static int ReadMvComponent(ref Reader r, ref Vp9EntropyProbs fc, int mvcomp, bool usehp) + { + int mag, d, fr, hp; + bool sign = r.Read(fc.Sign[mvcomp]) != 0; + MvClassType mvClass = (MvClassType)r.ReadTree(Luts.Vp9MvClassTree, fc.Classes[mvcomp].AsSpan()); + bool class0 = mvClass == MvClassType.MvClass0; + + // Integer part + if (class0) + { + d = r.Read(fc.Class0[mvcomp][0]); + mag = 0; + } + else + { + int i; + int n = (int)mvClass + Constants.Class0Bits - 1; // Number of bits + + d = 0; + for (i = 0; i < n; ++i) + { + d |= r.Read(fc.Bits[mvcomp][i]) << i; + } + + mag = Constants.Class0Size << ((int)mvClass + 2); + } + + // Fractional part + fr = r.ReadTree(Luts.Vp9MvFPTree, class0 ? fc.Class0Fp[mvcomp][d].AsSpan() : fc.Fp[mvcomp].AsSpan()); + + // High precision part (if hp is not used, the default value of the hp is 1) + hp = usehp ? r.Read(class0 ? fc.Class0Hp[mvcomp] : fc.Hp[mvcomp]) : 1; + + // Result + mag += ((d << 3) | (fr << 1) | hp) + 1; + + return sign ? -mag : mag; + } + + private static void ReadMv( + ref Reader r, + ref Mv mv, + ref Mv refr, + ref Vp9EntropyProbs fc, + Ptr counts, + bool allowHP) + { + MvJointType jointType = (MvJointType)r.ReadTree(Luts.Vp9MvJointTree, fc.Joints.AsSpan()); + bool useHP = allowHP && refr.UseMvHp(); + Mv diff = new(); + + if (Mv.MvJointVertical(jointType)) + { + diff.Row = (short)ReadMvComponent(ref r, ref fc, 0, useHP); + } + + if (Mv.MvJointHorizontal(jointType)) + { + diff.Col = (short)ReadMvComponent(ref r, ref fc, 1, useHP); + } + + diff.IncMv(counts); + + mv.Row = (short)(refr.Row + diff.Row); + mv.Col = (short)(refr.Col + diff.Col); + } + + private static ReferenceMode ReadBlockReferenceMode(ref Vp9Common cm, ref MacroBlockD xd, ref Reader r) + { + if (cm.ReferenceMode == ReferenceMode.ReferenceModeSelect) + { + int ctx = PredCommon.GetReferenceModeContext(ref cm, ref xd); + ReferenceMode mode = (ReferenceMode)r.Read(cm.Fc.Value.CompInterProb[ctx]); + if (!xd.Counts.IsNull) + { + ++xd.Counts.Value.CompInter[ctx][(int)mode]; + } + + return mode; // SingleReference or CompoundReference + } + + return cm.ReferenceMode; + } + + // Read the referncence frame + private static void ReadRefFrames( + ref Vp9Common cm, + ref MacroBlockD xd, + ref Reader r, + int segmentId, + ref Array2 refFrame) + { + ref Vp9EntropyProbs fc = ref cm.Fc.Value; + + if (cm.Seg.IsSegFeatureActive(segmentId, SegLvlFeatures.SegLvlRefFrame) != 0) + { + refFrame[0] = (sbyte)cm.Seg.GetSegData(segmentId, SegLvlFeatures.SegLvlRefFrame); + refFrame[1] = Constants.None; + } + else + { + ReferenceMode mode = ReadBlockReferenceMode(ref cm, ref xd, ref r); + if (mode == ReferenceMode.CompoundReference) + { + int idx = cm.RefFrameSignBias[cm.CompFixedRef]; + int ctx = PredCommon.GetPredContextCompRefP(ref cm, ref xd); + int bit = r.Read(fc.CompRefProb[ctx]); + if (!xd.Counts.IsNull) + { + ++xd.Counts.Value.CompRef[ctx][bit]; + } + + refFrame[idx] = cm.CompFixedRef; + refFrame[idx == 0 ? 1 : 0] = cm.CompVarRef[bit]; + } + else if (mode == ReferenceMode.SingleReference) + { + int ctx0 = PredCommon.GetPredContextSingleRefP1(ref xd); + int bit0 = r.Read(fc.SingleRefProb[ctx0][0]); + if (!xd.Counts.IsNull) + { + ++xd.Counts.Value.SingleRef[ctx0][0][bit0]; + } + + if (bit0 != 0) + { + int ctx1 = PredCommon.GetPredContextSingleRefP2(ref xd); + int bit1 = r.Read(fc.SingleRefProb[ctx1][1]); + if (!xd.Counts.IsNull) + { + ++xd.Counts.Value.SingleRef[ctx1][1][bit1]; + } + + refFrame[0] = (sbyte)(bit1 != 0 ? Constants.AltRefFrame : Constants.GoldenFrame); + } + else + { + refFrame[0] = Constants.LastFrame; + } + + refFrame[1] = Constants.None; + } + else + { + Debug.Assert(false, "Invalid prediction mode."); + } + } + } + + private static byte ReadSwitchableInterpFilter(ref Vp9Common cm, ref MacroBlockD xd, ref Reader r) + { + int ctx = xd.GetPredContextSwitchableInterp(); + byte type = (byte)r.ReadTree(Luts.Vp9SwitchableInterpTree, cm.Fc.Value.SwitchableInterpProb[ctx].AsSpan()); + if (!xd.Counts.IsNull) + { + ++xd.Counts.Value.SwitchableInterp[ctx][type]; + } + + return type; + } + + private static void ReadIntraBlockModeInfo(ref Vp9Common cm, ref MacroBlockD xd, ref ModeInfo mi, ref Reader r) + { + BlockSize bsize = mi.SbType; + int i; + + switch (bsize) + { + case BlockSize.Block4x4: + for (i = 0; i < 4; ++i) + { + mi.Bmi[i].Mode = ReadIntraModeY(ref cm, ref xd, ref r, 0); + } + + mi.Mode = mi.Bmi[3].Mode; + break; + case BlockSize.Block4x8: + mi.Bmi[0].Mode = mi.Bmi[2].Mode = ReadIntraModeY(ref cm, ref xd, ref r, 0); + mi.Bmi[1].Mode = mi.Bmi[3].Mode = mi.Mode = ReadIntraModeY(ref cm, ref xd, ref r, 0); + break; + case BlockSize.Block8x4: + mi.Bmi[0].Mode = mi.Bmi[1].Mode = ReadIntraModeY(ref cm, ref xd, ref r, 0); + mi.Bmi[2].Mode = mi.Bmi[3].Mode = mi.Mode = ReadIntraModeY(ref cm, ref xd, ref r, 0); + break; + default: + mi.Mode = ReadIntraModeY(ref cm, ref xd, ref r, Luts.SizeGroupLookup[(int)bsize]); + break; + } + + mi.UvMode = ReadIntraModeUv(ref cm, ref xd, ref r, (byte)mi.Mode); + + // Initialize interp_filter here so we do not have to check for inter block + // modes in GetPredContextSwitchableInterp() + mi.InterpFilter = Constants.SwitchableFilters; + + mi.RefFrame[0] = Constants.IntraFrame; + mi.RefFrame[1] = Constants.None; + } + + private static bool IsMvValid(ref Mv mv) + { + return mv.Row > Constants.MvLow && + mv.Row < Constants.MvUpp && + mv.Col > Constants.MvLow && + mv.Col < Constants.MvUpp; + } + + private static void CopyMvPair(ref Array2 dst, ref Array2 src) + { + dst[0] = src[0]; + dst[1] = src[1]; + } + + private static void ZeroMvPair(ref Array2 dst) + { + dst[0] = new Mv(); + dst[1] = new Mv(); + } + + private static bool AssignMv( + ref Vp9Common cm, + ref MacroBlockD xd, + PredictionMode mode, + ref Array2 mv, + ref Array2 refMv, + ref Array2 nearNearestMv, + int isCompound, + bool allowHP, + ref Reader r) + { + int i; + bool ret = true; + + switch (mode) + { + case PredictionMode.NewMv: + { + for (i = 0; i < 1 + isCompound; ++i) + { + ReadMv(ref r, ref mv[i], ref refMv[i], ref cm.Fc.Value, xd.Counts, allowHP); + ret = ret && IsMvValid(ref mv[i]); + } + break; + } + case PredictionMode.NearMv: + case PredictionMode.NearestMv: + { + CopyMvPair(ref mv, ref nearNearestMv); + break; + } + case PredictionMode.ZeroMv: + { + ZeroMvPair(ref mv); + break; + } + default: + return false; + } + return ret; + } + + private static bool ReadIsInterBlock(ref Vp9Common cm, ref MacroBlockD xd, int segmentId, ref Reader r) + { + if (cm.Seg.IsSegFeatureActive(segmentId, SegLvlFeatures.SegLvlRefFrame) != 0) + { + return cm.Seg.GetSegData(segmentId, SegLvlFeatures.SegLvlRefFrame) != Constants.IntraFrame; + } + + int ctx = xd.GetIntraInterContext(); + bool isInter = r.Read(cm.Fc.Value.IntraInterProb[ctx]) != 0; + if (!xd.Counts.IsNull) + { + ++xd.Counts.Value.IntraInter[ctx][isInter ? 1 : 0]; + } + + return isInter; + } + + private static void DecFindBestRefMvs(bool allowHP, Span mvlist, ref Mv bestMv, int refmvCount) + { + int i; + + // Make sure all the candidates are properly clamped etc + for (i = 0; i < refmvCount; ++i) + { + mvlist[i].LowerMvPrecision(allowHP); + bestMv = mvlist[i]; + } + } + + private static bool AddMvRefListEb(Mv mv, ref int refMvCount, Span mvRefList, bool earlyBreak) + { + if (refMvCount != 0) + { + if (Unsafe.As(ref mv) != Unsafe.As(ref mvRefList[0])) + { + mvRefList[refMvCount] = mv; + refMvCount++; + + return true; + } + } + else + { + mvRefList[refMvCount++] = mv; + if (earlyBreak) + { + return true; + } + } + + return false; + } + + // Performs mv sign inversion if indicated by the reference frame combination. + private static Mv ScaleMv(ref ModeInfo mi, int refr, sbyte thisRefFrame, ref Array4 refSignBias) + { + Mv mv = mi.Mv[refr]; + if (refSignBias[mi.RefFrame[refr]] != refSignBias[thisRefFrame]) + { + mv.Row *= -1; + mv.Col *= -1; + } + return mv; + } + + private static bool IsDiffRefFrameAddMvEb( + ref ModeInfo mbmi, + sbyte refFrame, + ref Array4 refSignBias, + ref int refmvCount, + Span mvRefList, + bool earlyBreak) + { + if (mbmi.IsInterBlock()) + { + if (mbmi.RefFrame[0] != refFrame) + { + if (AddMvRefListEb(ScaleMv(ref mbmi, 0, refFrame, ref refSignBias), ref refmvCount, mvRefList, earlyBreak)) + { + return true; + } + } + if (mbmi.HasSecondRef() && mbmi.RefFrame[1] != refFrame && Unsafe.As(ref mbmi.Mv[1]) != Unsafe.As(ref mbmi.Mv[0])) + { + if (AddMvRefListEb(ScaleMv(ref mbmi, 1, refFrame, ref refSignBias), ref refmvCount, mvRefList, earlyBreak)) + { + return true; + } + } + + } + return false; + } + + // This function searches the neighborhood of a given MB/SB + // to try and find candidate reference vectors. + private static int DecFindMvRefs( + ref Vp9Common cm, + ref MacroBlockD xd, + PredictionMode mode, + sbyte refFrame, + Span mvRefSearch, + Span mvRefList, + int miRow, + int miCol, + int block, + int isSub8X8) + { + ref Array4 refSignBias = ref cm.RefFrameSignBias; + int i, refmvCount = 0; + bool differentRefFound = false; + Ptr prevFrameMvs = cm.UsePrevFrameMvs ? new Ptr(ref cm.PrevFrameMvs[miRow * cm.MiCols + miCol]) : Ptr.Null; + ref TileInfo tile = ref xd.Tile; + // If mode is nearestmv or newmv (uses nearestmv as a reference) then stop + // searching after the first mv is found. + bool earlyBreak = mode != PredictionMode.NearMv; + + // Blank the reference vector list + mvRefList[..Constants.MaxMvRefCandidates].Clear(); + + i = 0; + if (isSub8X8 != 0) + { + // If the size < 8x8 we get the mv from the bmi substructure for the + // nearest two blocks. + for (i = 0; i < 2; ++i) + { + ref Position mvRef = ref mvRefSearch[i]; + if (tile.IsInside(miCol, miRow, cm.MiRows, ref mvRef)) + { + ref ModeInfo candidateMi = ref xd.Mi[mvRef.Col + mvRef.Row * xd.MiStride].Value; + differentRefFound = true; + + if (candidateMi.RefFrame[0] == refFrame) + { + if (AddMvRefListEb(candidateMi.GetSubBlockMv(0, mvRef.Col, block), ref refmvCount, mvRefList, earlyBreak)) + { + goto Done; + } + } + else if (candidateMi.RefFrame[1] == refFrame) + { + if (AddMvRefListEb(candidateMi.GetSubBlockMv(1, mvRef.Col, block), ref refmvCount, mvRefList, earlyBreak)) + { + goto Done; + } + } + } + } + } + + // Check the rest of the neighbors in much the same way + // as before except we don't need to keep track of sub blocks or + // mode counts. + for (; i < MvrefNeighbours; ++i) + { + ref Position mvRef = ref mvRefSearch[i]; + if (tile.IsInside(miCol, miRow, cm.MiRows, ref mvRef)) + { + ref ModeInfo candidate = ref xd.Mi[mvRef.Col + mvRef.Row * xd.MiStride].Value; + differentRefFound = true; + + if (candidate.RefFrame[0] == refFrame) + { + if (AddMvRefListEb(candidate.Mv[0], ref refmvCount, mvRefList, earlyBreak)) + { + goto Done; + } + } + else if (candidate.RefFrame[1] == refFrame) + { + if (AddMvRefListEb(candidate.Mv[1], ref refmvCount, mvRefList, earlyBreak)) + { + goto Done; + } + } + } + } + + // Check the last frame's mode and mv info. + if (!prevFrameMvs.IsNull) + { + if (prevFrameMvs.Value.RefFrame[0] == refFrame) + { + if (AddMvRefListEb(prevFrameMvs.Value.Mv[0], ref refmvCount, mvRefList, earlyBreak)) + { + goto Done; + } + } + else if (prevFrameMvs.Value.RefFrame[1] == refFrame) + { + if (AddMvRefListEb(prevFrameMvs.Value.Mv[1], ref refmvCount, mvRefList, earlyBreak)) + { + goto Done; + } + } + } + + // Since we couldn't find 2 mvs from the same reference frame + // go back through the neighbors and find motion vectors from + // different reference frames. + if (differentRefFound) + { + for (i = 0; i < MvrefNeighbours; ++i) + { + ref Position mvRef = ref mvRefSearch[i]; + if (tile.IsInside(miCol, miRow, cm.MiRows, ref mvRef)) + { + ref ModeInfo candidate = ref xd.Mi[mvRef.Col + mvRef.Row * xd.MiStride].Value; + + // If the candidate is Intra we don't want to consider its mv. + if (IsDiffRefFrameAddMvEb(ref candidate, refFrame, ref refSignBias, ref refmvCount, mvRefList, earlyBreak)) + { + goto Done; + } + } + } + } + + // Since we still don't have a candidate we'll try the last frame. + if (!prevFrameMvs.IsNull) + { + if (prevFrameMvs.Value.RefFrame[0] != refFrame && prevFrameMvs.Value.RefFrame[0] > Constants.IntraFrame) + { + Mv mv = prevFrameMvs.Value.Mv[0]; + if (refSignBias[prevFrameMvs.Value.RefFrame[0]] != refSignBias[refFrame]) + { + mv.Row *= -1; + mv.Col *= -1; + } + if (AddMvRefListEb(mv, ref refmvCount, mvRefList, earlyBreak)) + { + goto Done; + } + } + + if (prevFrameMvs.Value.RefFrame[1] > Constants.IntraFrame && + prevFrameMvs.Value.RefFrame[1] != refFrame && + Unsafe.As(ref prevFrameMvs.Value.Mv[1]) != Unsafe.As(ref prevFrameMvs.Value.Mv[0])) + { + Mv mv = prevFrameMvs.Value.Mv[1]; + if (refSignBias[prevFrameMvs.Value.RefFrame[1]] != refSignBias[refFrame]) + { + mv.Row *= -1; + mv.Col *= -1; + } + if (AddMvRefListEb(mv, ref refmvCount, mvRefList, earlyBreak)) + { + goto Done; + } + } + } + + if (mode == PredictionMode.NearMv) + { + refmvCount = Constants.MaxMvRefCandidates; + } + else + { + // We only care about the nearestmv for the remaining modes + refmvCount = 1; + } + + Done: + // Clamp vectors + for (i = 0; i < refmvCount; ++i) + { + mvRefList[i].ClampMvRef(ref xd); + } + + return refmvCount; + } + + private static void AppendSub8x8MvsForIdx( + ref Vp9Common cm, + ref MacroBlockD xd, + Span mvRefSearch, + PredictionMode bMode, + int block, + int refr, + int miRow, + int miCol, + ref Mv bestSub8x8) + { + Span mvList = stackalloc Mv[Constants.MaxMvRefCandidates]; + ref ModeInfo mi = ref xd.Mi[0].Value; + ref Array4 bmi = ref mi.Bmi; + int n; + int refmvCount; + + Debug.Assert(Constants.MaxMvRefCandidates == 2); + + refmvCount = DecFindMvRefs(ref cm, ref xd, bMode, mi.RefFrame[refr], mvRefSearch, mvList, miRow, miCol, block, 1); + + switch (block) + { + case 0: + bestSub8x8 = mvList[refmvCount - 1]; + break; + case 1: + case 2: + if (bMode == PredictionMode.NearestMv) + { + bestSub8x8 = bmi[0].Mv[refr]; + } + else + { + bestSub8x8 = new Mv(); + for (n = 0; n < refmvCount; ++n) + { + if (Unsafe.As(ref bmi[0].Mv[refr]) != Unsafe.As(ref mvList[n])) + { + bestSub8x8 = mvList[n]; + break; + } + } + } + break; + case 3: + if (bMode == PredictionMode.NearestMv) + { + bestSub8x8 = bmi[2].Mv[refr]; + } + else + { + Span candidates = stackalloc Mv[2 + Constants.MaxMvRefCandidates]; + candidates[0] = bmi[1].Mv[refr]; + candidates[1] = bmi[0].Mv[refr]; + candidates[2] = mvList[0]; + candidates[3] = mvList[1]; + bestSub8x8 = new Mv(); + for (n = 0; n < 2 + Constants.MaxMvRefCandidates; ++n) + { + if (Unsafe.As(ref bmi[2].Mv[refr]) != Unsafe.As(ref candidates[n])) + { + bestSub8x8 = candidates[n]; + break; + } + } + } + break; + default: + Debug.Assert(false, "Invalid block index."); + break; + } + } + + private static byte GetModeContext(ref Vp9Common cm, ref MacroBlockD xd, Span mvRefSearch, int miRow, int miCol) + { + int i; + int contextCounter = 0; + ref TileInfo tile = ref xd.Tile; + + // Get mode count from nearest 2 blocks + for (i = 0; i < 2; ++i) + { + ref Position mvRef = ref mvRefSearch[i]; + if (tile.IsInside(miCol, miRow, cm.MiRows, ref mvRef)) + { + ref ModeInfo candidate = ref xd.Mi[mvRef.Col + mvRef.Row * xd.MiStride].Value; + // Keep counts for entropy encoding. + contextCounter += Luts.Mode2Counter[(int)candidate.Mode]; + } + } + + return (byte)Luts.CounterToContext[contextCounter]; + } + + private static void ReadInterBlockModeInfo( + ref Vp9Common cm, + ref MacroBlockD xd, + ref ModeInfo mi, + int miRow, + int miCol, + ref Reader r) + { + BlockSize bsize = mi.SbType; + bool allowHP = cm.AllowHighPrecisionMv; + Array2 bestRefMvs = new(); + int refr, isCompound; + byte interModeCtx; + Span mvRefSearch = Luts.MvRefBlocks[(int)bsize]; + + ReadRefFrames(ref cm, ref xd, ref r, mi.SegmentId, ref mi.RefFrame); + isCompound = mi.HasSecondRef() ? 1 : 0; + interModeCtx = GetModeContext(ref cm, ref xd, mvRefSearch, miRow, miCol); + + if (cm.Seg.IsSegFeatureActive(mi.SegmentId, SegLvlFeatures.SegLvlSkip) != 0) + { + mi.Mode = PredictionMode.ZeroMv; + if (bsize < BlockSize.Block8x8) + { + xd.ErrorInfo.Value.InternalError(CodecErr.CodecUnsupBitstream, "Invalid usage of segement feature on small blocks"); + + return; + } + } + else + { + if (bsize >= BlockSize.Block8x8) + { + mi.Mode = ReadInterMode(ref cm, ref xd, ref r, interModeCtx); + } + else + { + // Sub 8x8 blocks use the nearestmv as a ref_mv if the bMode is NewMv. + // Setting mode to NearestMv forces the search to stop after the nearestmv + // has been found. After bModes have been read, mode will be overwritten + // by the last bMode. + mi.Mode = PredictionMode.NearestMv; + } + + if (mi.Mode != PredictionMode.ZeroMv) + { + Span tmpMvs = stackalloc Mv[Constants.MaxMvRefCandidates]; + + for (refr = 0; refr < 1 + isCompound; ++refr) + { + sbyte frame = mi.RefFrame[refr]; + int refmvCount; + + refmvCount = DecFindMvRefs(ref cm, ref xd, mi.Mode, frame, mvRefSearch, tmpMvs, miRow, miCol, -1, 0); + + DecFindBestRefMvs(allowHP, tmpMvs, ref bestRefMvs[refr], refmvCount); + } + } + } + + mi.InterpFilter = (cm.InterpFilter == Constants.Switchable) ? ReadSwitchableInterpFilter(ref cm, ref xd, ref r) : cm.InterpFilter; + + if (bsize < BlockSize.Block8x8) + { + int num4X4W = 1 << xd.BmodeBlocksWl; + int num4X4H = 1 << xd.BmodeBlocksHl; + int idx, idy; + PredictionMode bMode = 0; + Array2 bestSub8x8 = new(); + const uint InvalidMv = 0x80008000; + // Initialize the 2nd element as even though it won't be used meaningfully + // if isCompound is false. + Unsafe.As(ref bestSub8x8[1]) = InvalidMv; + for (idy = 0; idy < 2; idy += num4X4H) + { + for (idx = 0; idx < 2; idx += num4X4W) + { + int j = idy * 2 + idx; + bMode = ReadInterMode(ref cm, ref xd, ref r, interModeCtx); + + if (bMode == PredictionMode.NearestMv || bMode == PredictionMode.NearMv) + { + for (refr = 0; refr < 1 + isCompound; ++refr) + { + AppendSub8x8MvsForIdx(ref cm, ref xd, mvRefSearch, bMode, j, refr, miRow, miCol, ref bestSub8x8[refr]); + } + } + + if (!AssignMv(ref cm, ref xd, bMode, ref mi.Bmi[j].Mv, ref bestRefMvs, ref bestSub8x8, isCompound, allowHP, ref r)) + { + xd.Corrupted |= true; + break; + } + + if (num4X4H == 2) + { + mi.Bmi[j + 2] = mi.Bmi[j]; + } + + if (num4X4W == 2) + { + mi.Bmi[j + 1] = mi.Bmi[j]; + } + } + } + + mi.Mode = bMode; + + CopyMvPair(ref mi.Mv, ref mi.Bmi[3].Mv); + } + else + { + xd.Corrupted |= !AssignMv(ref cm, ref xd, mi.Mode, ref mi.Mv, ref bestRefMvs, ref bestRefMvs, isCompound, allowHP, ref r); + } + } + + private static void ReadInterFrameModeInfo( + ref Vp9Common cm, + ref MacroBlockD xd, + int miRow, + int miCol, + ref Reader r, + int xMis, + int yMis) + { + ref ModeInfo mi = ref xd.Mi[0].Value; + bool interBlock; + + mi.SegmentId = (sbyte)ReadInterSegmentId(ref cm, ref xd, miRow, miCol, ref r, xMis, yMis); + mi.Skip = (sbyte)ReadSkip(ref cm, ref xd, mi.SegmentId, ref r); + interBlock = ReadIsInterBlock(ref cm, ref xd, mi.SegmentId, ref r); + mi.TxSize = ReadTxSize(ref cm, ref xd, mi.Skip == 0 || !interBlock, ref r); + + if (interBlock) + { + ReadInterBlockModeInfo(ref cm, ref xd, ref mi, miRow, miCol, ref r); + } + else + { + ReadIntraBlockModeInfo(ref cm, ref xd, ref mi, ref r); + } + } + + private static PredictionMode LeftBlockMode(Ptr curMi, Ptr leftMi, int b) + { + if (b == 0 || b == 2) + { + if (leftMi.IsNull || leftMi.Value.IsInterBlock()) + { + return PredictionMode.DcPred; + } + + return leftMi.Value.GetYMode(b + 1); + } + + Debug.Assert(b == 1 || b == 3); + + return curMi.Value.Bmi[b - 1].Mode; + } + + private static PredictionMode AboveBlockMode(Ptr curMi, Ptr aboveMi, int b) + { + if (b == 0 || b == 1) + { + if (aboveMi.IsNull || aboveMi.Value.IsInterBlock()) + { + return PredictionMode.DcPred; + } + + return aboveMi.Value.GetYMode(b + 2); + } + + Debug.Assert(b == 2 || b == 3); + + return curMi.Value.Bmi[b - 2].Mode; + } + + private static ReadOnlySpan GetYModeProbs( + ref Vp9EntropyProbs fc, + Ptr mi, + Ptr aboveMi, + Ptr leftMi, + int block) + { + PredictionMode above = AboveBlockMode(mi, aboveMi, block); + PredictionMode left = LeftBlockMode(mi, leftMi, block); + + return fc.KfYModeProb[(int)above][(int)left].AsSpan(); + } + + private static void ReadIntraFrameModeInfo( + ref Vp9Common cm, + ref MacroBlockD xd, + int miRow, + int miCol, + ref Reader r, + int xMis, + int yMis) + { + Ptr mi = xd.Mi[0]; + Ptr aboveMi = xd.AboveMi; + Ptr leftMi = xd.LeftMi; + BlockSize bsize = mi.Value.SbType; + int i; + int miOffset = miRow * cm.MiCols + miCol; + + mi.Value.SegmentId = (sbyte)ReadIntraSegmentId(ref cm, miOffset, xMis, yMis, ref r); + mi.Value.Skip = (sbyte)ReadSkip(ref cm, ref xd, mi.Value.SegmentId, ref r); + mi.Value.TxSize = ReadTxSize(ref cm, ref xd, true, ref r); + mi.Value.RefFrame[0] = Constants.IntraFrame; + mi.Value.RefFrame[1] = Constants.None; + + switch (bsize) + { + case BlockSize.Block4x4: + for (i = 0; i < 4; ++i) + { + mi.Value.Bmi[i].Mode = + ReadIntraMode(ref r, GetYModeProbs(ref cm.Fc.Value, mi, aboveMi, leftMi, i)); + } + + mi.Value.Mode = mi.Value.Bmi[3].Mode; + break; + case BlockSize.Block4x8: + mi.Value.Bmi[0].Mode = mi.Value.Bmi[2].Mode = + ReadIntraMode(ref r, GetYModeProbs(ref cm.Fc.Value, mi, aboveMi, leftMi, 0)); + mi.Value.Bmi[1].Mode = mi.Value.Bmi[3].Mode = mi.Value.Mode = + ReadIntraMode(ref r, GetYModeProbs(ref cm.Fc.Value, mi, aboveMi, leftMi, 1)); + break; + case BlockSize.Block8x4: + mi.Value.Bmi[0].Mode = mi.Value.Bmi[1].Mode = + ReadIntraMode(ref r, GetYModeProbs(ref cm.Fc.Value, mi, aboveMi, leftMi, 0)); + mi.Value.Bmi[2].Mode = mi.Value.Bmi[3].Mode = mi.Value.Mode = + ReadIntraMode(ref r, GetYModeProbs(ref cm.Fc.Value, mi, aboveMi, leftMi, 2)); + break; + default: + mi.Value.Mode = ReadIntraMode(ref r, GetYModeProbs(ref cm.Fc.Value, mi, aboveMi, leftMi, 0)); + break; + } + + mi.Value.UvMode = ReadIntraMode(ref r, cm.Fc.Value.KfUvModeProb[(int)mi.Value.Mode].AsSpan()); + } + + private static void CopyRefFramePair(ref Array2 dst, ref Array2 src) + { + dst[0] = src[0]; + dst[1] = src[1]; + } + + public static void ReadModeInfo( + ref TileWorkerData twd, + ref Vp9Common cm, + int miRow, + int miCol, + int xMis, + int yMis) + { + ref Reader r = ref twd.BitReader; + ref MacroBlockD xd = ref twd.Xd; + ref ModeInfo mi = ref xd.Mi[0].Value; + ArrayPtr frameMvs = cm.CurFrameMvs.Slice(miRow * cm.MiCols + miCol); + int w, h; + + if (cm.FrameIsIntraOnly()) + { + ReadIntraFrameModeInfo(ref cm, ref xd, miRow, miCol, ref r, xMis, yMis); + } + else + { + ReadInterFrameModeInfo(ref cm, ref xd, miRow, miCol, ref r, xMis, yMis); + + for (h = 0; h < yMis; ++h) + { + for (w = 0; w < xMis; ++w) + { + ref MvRef mv = ref frameMvs[w]; + CopyRefFramePair(ref mv.RefFrame, ref mi.RefFrame); + CopyMvPair(ref mv.Mv, ref mi.Mv); + } + frameMvs = frameMvs.Slice(cm.MiCols); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Decoder.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Decoder.cs new file mode 100644 index 00000000..57057d5f --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Decoder.cs @@ -0,0 +1,181 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Nvdec.Vp9.Common; +using Ryujinx.Graphics.Nvdec.Vp9.Types; +using Ryujinx.Graphics.Video; +using System; + +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + public sealed class Decoder : IVp9Decoder + { + public bool IsHardwareAccelerated => false; + + private readonly MemoryAllocator _allocator = new(); + + public ISurface CreateSurface(int width, int height) => new Surface(width, height); + + private static ReadOnlySpan LiteralToFilter => new byte[] + { + Constants.EightTapSmooth, + Constants.EightTap, + Constants.EightTapSharp, + Constants.Bilinear, + }; + + public unsafe bool Decode( + ref Vp9PictureInfo pictureInfo, + ISurface output, + ReadOnlySpan bitstream, + ReadOnlySpan mvsIn, + Span mvsOut) + { + Vp9Common cm = new() + { + FrameType = pictureInfo.IsKeyFrame ? FrameType.KeyFrame : FrameType.InterFrame, + IntraOnly = pictureInfo.IntraOnly, + + Width = output.Width, + Height = output.Height, + SubsamplingX = 1, + SubsamplingY = 1, + + UsePrevFrameMvs = pictureInfo.UsePrevInFindMvRefs, + + RefFrameSignBias = pictureInfo.RefFrameSignBias, + + BaseQindex = pictureInfo.BaseQIndex, + YDcDeltaQ = pictureInfo.YDcDeltaQ, + UvAcDeltaQ = pictureInfo.UvAcDeltaQ, + UvDcDeltaQ = pictureInfo.UvDcDeltaQ, + }; + + cm.Mb.Lossless = pictureInfo.Lossless; + cm.Mb.Bd = 8; + + cm.TxMode = (TxMode)pictureInfo.TransformMode; + + cm.AllowHighPrecisionMv = pictureInfo.AllowHighPrecisionMv; + + cm.InterpFilter = (byte)pictureInfo.InterpFilter; + + if (cm.InterpFilter != Constants.Switchable) + { + cm.InterpFilter = LiteralToFilter[cm.InterpFilter]; + } + + cm.ReferenceMode = (ReferenceMode)pictureInfo.ReferenceMode; + + cm.CompFixedRef = pictureInfo.CompFixedRef; + cm.CompVarRef = pictureInfo.CompVarRef; + + cm.Log2TileCols = pictureInfo.Log2TileCols; + cm.Log2TileRows = pictureInfo.Log2TileRows; + + cm.Seg.Enabled = pictureInfo.SegmentEnabled; + cm.Seg.UpdateMap = pictureInfo.SegmentMapUpdate; + cm.Seg.TemporalUpdate = pictureInfo.SegmentMapTemporalUpdate; + cm.Seg.AbsDelta = (byte)pictureInfo.SegmentAbsDelta; + cm.Seg.FeatureMask = pictureInfo.SegmentFeatureEnable; + cm.Seg.FeatureData = pictureInfo.SegmentFeatureData; + + cm.Lf.ModeRefDeltaEnabled = pictureInfo.ModeRefDeltaEnabled; + cm.Lf.RefDeltas = pictureInfo.RefDeltas; + cm.Lf.ModeDeltas = pictureInfo.ModeDeltas; + + cm.Fc = new Ptr(ref pictureInfo.Entropy); + cm.Counts = new Ptr(ref pictureInfo.BackwardUpdateCounts); + + cm.FrameRefs[0].Buf = (Surface)pictureInfo.LastReference; + cm.FrameRefs[1].Buf = (Surface)pictureInfo.GoldenReference; + cm.FrameRefs[2].Buf = (Surface)pictureInfo.AltReference; + cm.Mb.CurBuf = (Surface)output; + + cm.Mb.SetupBlockPlanes(1, 1); + + int tileCols = 1 << pictureInfo.Log2TileCols; + int tileRows = 1 << pictureInfo.Log2TileRows; + + // Video usually have only 4 columns, so more threads won't make a difference for those. + // Try to not take all CPU cores for video decoding. + int maxThreads = Math.Min(4, Environment.ProcessorCount / 2); + + cm.AllocTileWorkerData(_allocator, tileCols, tileRows, maxThreads); + cm.AllocContextBuffers(_allocator, output.Width, output.Height); + cm.InitContextBuffers(); + cm.SetupSegmentationDequant(); + cm.SetupScaleFactors(); + + SetMvs(ref cm, mvsIn); + + fixed (byte* dataPtr = bitstream) + { + try + { + if (maxThreads > 1 && tileRows == 1 && tileCols > 1) + { + DecodeFrame.DecodeTilesMt(ref cm, new ArrayPtr(dataPtr, bitstream.Length), maxThreads); + } + else + { + DecodeFrame.DecodeTiles(ref cm, new ArrayPtr(dataPtr, bitstream.Length)); + } + } + catch (InternalErrorException) + { + return false; + } + } + + GetMvs(ref cm, mvsOut); + + cm.FreeTileWorkerData(_allocator); + cm.FreeContextBuffers(_allocator); + + return true; + } + + private static void SetMvs(ref Vp9Common cm, ReadOnlySpan mvs) + { + if (mvs.Length > cm.PrevFrameMvs.Length) + { + throw new ArgumentException($"Size mismatch, expected: {cm.PrevFrameMvs.Length}, but got: {mvs.Length}."); + } + + for (int i = 0; i < mvs.Length; i++) + { + ref var mv = ref cm.PrevFrameMvs[i]; + + mv.Mv[0].Row = mvs[i].Mvs[0].Row; + mv.Mv[0].Col = mvs[i].Mvs[0].Col; + mv.Mv[1].Row = mvs[i].Mvs[1].Row; + mv.Mv[1].Col = mvs[i].Mvs[1].Col; + + mv.RefFrame[0] = (sbyte)mvs[i].RefFrames[0]; + mv.RefFrame[1] = (sbyte)mvs[i].RefFrames[1]; + } + } + + private static void GetMvs(ref Vp9Common cm, Span mvs) + { + if (mvs.Length > cm.CurFrameMvs.Length) + { + throw new ArgumentException($"Size mismatch, expected: {cm.CurFrameMvs.Length}, but got: {mvs.Length}."); + } + + for (int i = 0; i < mvs.Length; i++) + { + ref var mv = ref cm.CurFrameMvs[i]; + + mvs[i].Mvs[0].Row = mv.Mv[0].Row; + mvs[i].Mvs[0].Col = mv.Mv[0].Col; + mvs[i].Mvs[1].Row = mv.Mv[1].Row; + mvs[i].Mvs[1].Col = mv.Mv[1].Col; + + mvs[i].RefFrames[0] = mv.RefFrame[0]; + mvs[i].RefFrames[1] = mv.RefFrame[1]; + } + } + + public void Dispose() => _allocator.Dispose(); + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Detokenize.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Detokenize.cs new file mode 100644 index 00000000..c255f748 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Detokenize.cs @@ -0,0 +1,327 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Nvdec.Vp9.Dsp; +using Ryujinx.Graphics.Nvdec.Vp9.Types; +using Ryujinx.Graphics.Video; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using static Ryujinx.Graphics.Nvdec.Vp9.Dsp.InvTxfm; + +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal static class Detokenize + { + private const int EobContextNode = 0; + private const int ZeroContextNode = 1; + private const int OneContextNode = 2; + + private static int GetCoefContext(ReadOnlySpan neighbors, ReadOnlySpan tokenCache, int c) + { + const int MaxNeighbors = 2; + + return (1 + tokenCache[neighbors[MaxNeighbors * c + 0]] + tokenCache[neighbors[MaxNeighbors * c + 1]]) >> 1; + } + + private static int ReadCoeff( + ref Reader r, + ReadOnlySpan probs, + int n, + ref ulong value, + ref int count, + ref uint range) + { + int i, val = 0; + for (i = 0; i < n; ++i) + { + val = (val << 1) | r.ReadBool(probs[i], ref value, ref count, ref range); + } + + return val; + } + + private static int DecodeCoefs( + ref MacroBlockD xd, + PlaneType type, + Span dqcoeff, + TxSize txSize, + ref Array2 dq, + int ctx, + ReadOnlySpan scan, + ReadOnlySpan nb, + ref Reader r) + { + ref Vp9BackwardUpdates counts = ref xd.Counts.Value; + int maxEob = 16 << ((int)txSize << 1); + ref Vp9EntropyProbs fc = ref xd.Fc.Value; + int refr = xd.Mi[0].Value.IsInterBlock() ? 1 : 0; + int band, c = 0; + ref Array6>> coefProbs = ref fc.CoefProbs[(int)txSize][(int)type][refr]; + Span tokenCache = stackalloc byte[32 * 32]; + ReadOnlySpan bandTranslate = Luts.GetBandTranslate(txSize); + int dqShift = (txSize == TxSize.Tx32x32) ? 1 : 0; + int v; + short dqv = dq[0]; + ReadOnlySpan cat6Prob = (xd.Bd == 12) + ? Luts.Vp9Cat6ProbHigh12 + : (xd.Bd == 10) ? Luts.Vp9Cat6ProbHigh12[2..] : Luts.Vp9Cat6Prob; + int cat6Bits = (xd.Bd == 12) ? 18 : (xd.Bd == 10) ? 16 : 14; + // Keep value, range, and count as locals. The compiler produces better + // results with the locals than using r directly. + ulong value = r.Value; + uint range = r.Range; + int count = r.Count; + + while (c < maxEob) + { + int val = -1; + band = bandTranslate[0]; + bandTranslate = bandTranslate[1..]; + ref Array3 prob = ref coefProbs[band][ctx]; + if (!xd.Counts.IsNull) + { + ++counts.EobBranch[(int)txSize][(int)type][refr][band][ctx]; + } + + if (r.ReadBool(prob[EobContextNode], ref value, ref count, ref range) == 0) + { + if (!xd.Counts.IsNull) + { + ++counts.Coef[(int)txSize][(int)type][refr][band][ctx][Constants.EobModelToken]; + } + + break; + } + + while (r.ReadBool(prob[ZeroContextNode], ref value, ref count, ref range) == 0) + { + if (!xd.Counts.IsNull) + { + ++counts.Coef[(int)txSize][(int)type][refr][band][ctx][Constants.ZeroToken]; + } + + dqv = dq[1]; + tokenCache[scan[c]] = 0; + ++c; + if (c >= maxEob) + { + r.Value = value; + r.Range = range; + r.Count = count; + + return c; // Zero tokens at the end (no eob token) + } + ctx = GetCoefContext(nb, tokenCache, c); + band = bandTranslate[0]; + bandTranslate = bandTranslate[1..]; + prob = ref coefProbs[band][ctx]; + } + + if (r.ReadBool(prob[OneContextNode], ref value, ref count, ref range) != 0) + { + ReadOnlySpan p = Luts.Vp9Pareto8Full[prob[Constants.PivotNode] - 1]; + if (!xd.Counts.IsNull) + { + ++counts.Coef[(int)txSize][(int)type][refr][band][ctx][Constants.TwoToken]; + } + + if (r.ReadBool(p[0], ref value, ref count, ref range) != 0) + { + if (r.ReadBool(p[3], ref value, ref count, ref range) != 0) + { + tokenCache[scan[c]] = 5; + if (r.ReadBool(p[5], ref value, ref count, ref range) != 0) + { + if (r.ReadBool(p[7], ref value, ref count, ref range) != 0) + { + val = Constants.Cat6MinVal + ReadCoeff(ref r, cat6Prob, cat6Bits, ref value, ref count, ref range); + } + else + { + val = Constants.Cat5MinVal + ReadCoeff(ref r, Luts.Vp9Cat5Prob, 5, ref value, ref count, ref range); + } + } + else if (r.ReadBool(p[6], ref value, ref count, ref range) != 0) + { + val = Constants.Cat4MinVal + ReadCoeff(ref r, Luts.Vp9Cat4Prob, 4, ref value, ref count, ref range); + } + else + { + val = Constants.Cat3MinVal + ReadCoeff(ref r, Luts.Vp9Cat3Prob, 3, ref value, ref count, ref range); + } + } + else + { + tokenCache[scan[c]] = 4; + if (r.ReadBool(p[4], ref value, ref count, ref range) != 0) + { + val = Constants.Cat2MinVal + ReadCoeff(ref r, Luts.Vp9Cat2Prob, 2, ref value, ref count, ref range); + } + else + { + val = Constants.Cat1MinVal + ReadCoeff(ref r, Luts.Vp9Cat1Prob, 1, ref value, ref count, ref range); + } + } + // Val may use 18-bits + v = (int)(((long)val * dqv) >> dqShift); + } + else + { + if (r.ReadBool(p[1], ref value, ref count, ref range) != 0) + { + tokenCache[scan[c]] = 3; + v = ((3 + r.ReadBool(p[2], ref value, ref count, ref range)) * dqv) >> dqShift; + } + else + { + tokenCache[scan[c]] = 2; + v = (2 * dqv) >> dqShift; + } + } + } + else + { + if (!xd.Counts.IsNull) + { + ++counts.Coef[(int)txSize][(int)type][refr][band][ctx][Constants.OneToken]; + } + + tokenCache[scan[c]] = 1; + v = dqv >> dqShift; + } + dqcoeff[scan[c]] = (int)HighbdCheckRange(r.ReadBool(128, ref value, ref count, ref range) != 0 ? -v : v, xd.Bd); + ++c; + ctx = GetCoefContext(nb, tokenCache, c); + dqv = dq[1]; + } + + r.Value = value; + r.Range = range; + r.Count = count; + + return c; + } + + private static void GetCtxShift(ref MacroBlockD xd, ref int ctxShiftA, ref int ctxShiftL, int x, int y, uint txSizeInBlocks) + { + if (xd.MaxBlocksWide != 0) + { + if (txSizeInBlocks + x > xd.MaxBlocksWide) + { + ctxShiftA = (int)(txSizeInBlocks - (xd.MaxBlocksWide - x)) * 8; + } + } + if (xd.MaxBlocksHigh != 0) + { + if (txSizeInBlocks + y > xd.MaxBlocksHigh) + { + ctxShiftL = (int)(txSizeInBlocks - (xd.MaxBlocksHigh - y)) * 8; + } + } + } + + private static PlaneType GetPlaneType(int plane) + { + return (PlaneType)(plane > 0 ? 1 : 0); + } + + public static int DecodeBlockTokens( + ref TileWorkerData twd, + int plane, + Luts.ScanOrder sc, + int x, + int y, + TxSize txSize, + int segId) + { + ref Reader r = ref twd.BitReader; + ref MacroBlockD xd = ref twd.Xd; + ref MacroBlockDPlane pd = ref xd.Plane[plane]; + ref Array2 dequant = ref pd.SegDequant[segId]; + int eob; + Span a = pd.AboveContext.AsSpan()[x..]; + Span l = pd.LeftContext.AsSpan()[y..]; + int ctx; + int ctxShiftA = 0; + int ctxShiftL = 0; + + switch (txSize) + { + case TxSize.Tx4x4: + ctx = a[0] != 0 ? 1 : 0; + ctx += l[0] != 0 ? 1 : 0; + eob = DecodeCoefs( + ref xd, + GetPlaneType(plane), + pd.DqCoeff.AsSpan(), + txSize, + ref dequant, + ctx, + sc.Scan, + sc.Neighbors, + ref r); + a[0] = l[0] = (sbyte)(eob > 0 ? 1 : 0); + break; + case TxSize.Tx8x8: + GetCtxShift(ref xd, ref ctxShiftA, ref ctxShiftL, x, y, 1 << (int)TxSize.Tx8x8); + ctx = MemoryMarshal.Cast(a)[0] != 0 ? 1 : 0; + ctx += MemoryMarshal.Cast(l)[0] != 0 ? 1 : 0; + eob = DecodeCoefs( + ref xd, + GetPlaneType(plane), + pd.DqCoeff.AsSpan(), + txSize, + ref dequant, + ctx, + sc.Scan, + sc.Neighbors, + ref r); + MemoryMarshal.Cast(a)[0] = (ushort)((eob > 0 ? 0x0101 : 0) >> ctxShiftA); + MemoryMarshal.Cast(l)[0] = (ushort)((eob > 0 ? 0x0101 : 0) >> ctxShiftL); + break; + case TxSize.Tx16x16: + GetCtxShift(ref xd, ref ctxShiftA, ref ctxShiftL, x, y, 1 << (int)TxSize.Tx16x16); + ctx = MemoryMarshal.Cast(a)[0] != 0 ? 1 : 0; + ctx += MemoryMarshal.Cast(l)[0] != 0 ? 1 : 0; + eob = DecodeCoefs( + ref xd, + GetPlaneType(plane), + pd.DqCoeff.AsSpan(), + txSize, + ref dequant, + ctx, + sc.Scan, + sc.Neighbors, + ref r); + MemoryMarshal.Cast(a)[0] = (uint)((eob > 0 ? 0x01010101 : 0) >> ctxShiftA); + MemoryMarshal.Cast(l)[0] = (uint)((eob > 0 ? 0x01010101 : 0) >> ctxShiftL); + break; + case TxSize.Tx32x32: + GetCtxShift(ref xd, ref ctxShiftA, ref ctxShiftL, x, y, 1 << (int)TxSize.Tx32x32); + // NOTE: Casting to ulong here is safe because the default memory + // alignment is at least 8 bytes and the Tx32x32 is aligned on 8 byte + // boundaries. + ctx = MemoryMarshal.Cast(a)[0] != 0 ? 1 : 0; + ctx += MemoryMarshal.Cast(l)[0] != 0 ? 1 : 0; + eob = DecodeCoefs( + ref xd, + GetPlaneType(plane), + pd.DqCoeff.AsSpan(), + txSize, + ref dequant, + ctx, + sc.Scan, + sc.Neighbors, + ref r); + MemoryMarshal.Cast(a)[0] = (eob > 0 ? 0x0101010101010101UL : 0) >> ctxShiftA; + MemoryMarshal.Cast(l)[0] = (eob > 0 ? 0x0101010101010101UL : 0) >> ctxShiftL; + break; + default: + Debug.Assert(false, "Invalid transform size."); + eob = 0; + break; + } + + return eob; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Convolve.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Convolve.cs new file mode 100644 index 00000000..9e279dd2 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Convolve.cs @@ -0,0 +1,945 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Nvdec.Vp9.Common; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static Ryujinx.Graphics.Nvdec.Vp9.Dsp.Filter; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Dsp +{ + internal static class Convolve + { + private const bool UseIntrinsics = true; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 MultiplyAddAdjacent( + Vector128 vsrc0, + Vector128 vsrc1, + Vector128 vsrc2, + Vector128 vsrc3, + Vector128 vfilter, + Vector128 zero) + { + // < sumN, sumN, sumN, sumN > + Vector128 sum0 = Sse2.MultiplyAddAdjacent(vsrc0, vfilter); + Vector128 sum1 = Sse2.MultiplyAddAdjacent(vsrc1, vfilter); + Vector128 sum2 = Sse2.MultiplyAddAdjacent(vsrc2, vfilter); + Vector128 sum3 = Sse2.MultiplyAddAdjacent(vsrc3, vfilter); + + // < 0, 0, sumN, sumN > + sum0 = Ssse3.HorizontalAdd(sum0, zero); + sum1 = Ssse3.HorizontalAdd(sum1, zero); + sum2 = Ssse3.HorizontalAdd(sum2, zero); + sum3 = Ssse3.HorizontalAdd(sum3, zero); + + // < 0, 0, 0, sumN > + sum0 = Ssse3.HorizontalAdd(sum0, zero); + sum1 = Ssse3.HorizontalAdd(sum1, zero); + sum2 = Ssse3.HorizontalAdd(sum2, zero); + sum3 = Ssse3.HorizontalAdd(sum3, zero); + + // < 0, 0, sum1, sum0 > + Vector128 sum01 = Sse2.UnpackLow(sum0, sum1); + + // < 0, 0, sum3, sum2 > + Vector128 sum23 = Sse2.UnpackLow(sum2, sum3); + + // < sum3, sum2, sum1, sum0 > + return Sse.MoveLowToHigh(sum01.AsSingle(), sum23.AsSingle()).AsInt32(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 RoundShift(Vector128 value, Vector128 const64) + { + return Sse2.ShiftRightArithmetic(Sse2.Add(value, const64), FilterBits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 PackUnsignedSaturate(Vector128 value, Vector128 zero) + { + return Sse2.PackUnsignedSaturate(Sse41.PackUnsignedSaturate(value, zero).AsInt16(), zero.AsInt16()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe void ConvolveHorizSse41( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] xFilters, + int x0Q4, + int w, + int h) + { + Vector128 zero = Vector128.Zero; + Vector128 const64 = Vector128.Create(64); + + ulong x, y; + src -= SubpelTaps / 2 - 1; + + fixed (Array8* xFilter = xFilters) + { + Vector128 vfilter = Sse2.LoadVector128((short*)xFilter + (uint)(x0Q4 & SubpelMask) * 8); + + for (y = 0; y < (uint)h; ++y) + { + ulong srcOffset = (uint)x0Q4 >> SubpelBits; + for (x = 0; x < (uint)w; x += 4) + { + Vector128 vsrc0 = Sse41.ConvertToVector128Int16(&src[srcOffset + x]); + Vector128 vsrc1 = Sse41.ConvertToVector128Int16(&src[srcOffset + x + 1]); + Vector128 vsrc2 = Sse41.ConvertToVector128Int16(&src[srcOffset + x + 2]); + Vector128 vsrc3 = Sse41.ConvertToVector128Int16(&src[srcOffset + x + 3]); + + Vector128 sum0123 = MultiplyAddAdjacent(vsrc0, vsrc1, vsrc2, vsrc3, vfilter, zero); + + Sse.StoreScalar((float*)&dst[x], PackUnsignedSaturate(RoundShift(sum0123, const64), zero).AsSingle()); + } + src += srcStride; + dst += dstStride; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe void ConvolveHoriz( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] xFilters, + int x0Q4, + int xStepQ4, + int w, + int h) + { + if (Sse41.IsSupported && UseIntrinsics && xStepQ4 == 1 << SubpelBits) + { + ConvolveHorizSse41(src, srcStride, dst, dstStride, xFilters, x0Q4, w, h); + + return; + } + + int x, y; + src -= SubpelTaps / 2 - 1; + + for (y = 0; y < h; ++y) + { + int xQ4 = x0Q4; + for (x = 0; x < w; ++x) + { + byte* srcX = &src[xQ4 >> SubpelBits]; + ref Array8 xFilter = ref xFilters[xQ4 & SubpelMask]; + int k, sum = 0; + for (k = 0; k < SubpelTaps; ++k) + { + sum += srcX[k] * xFilter[k]; + } + + dst[x] = BitUtils.ClipPixel(BitUtils.RoundPowerOfTwo(sum, FilterBits)); + xQ4 += xStepQ4; + } + src += srcStride; + dst += dstStride; + } + } + + private static unsafe void ConvolveAvgHoriz( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] xFilters, + int x0Q4, + int xStepQ4, + int w, + int h) + { + int x, y; + src -= SubpelTaps / 2 - 1; + + for (y = 0; y < h; ++y) + { + int xQ4 = x0Q4; + for (x = 0; x < w; ++x) + { + byte* srcX = &src[xQ4 >> SubpelBits]; + ref Array8 xFilter = ref xFilters[xQ4 & SubpelMask]; + int k, sum = 0; + for (k = 0; k < SubpelTaps; ++k) + { + sum += srcX[k] * xFilter[k]; + } + + dst[x] = (byte)BitUtils.RoundPowerOfTwo(dst[x] + BitUtils.ClipPixel(BitUtils.RoundPowerOfTwo(sum, FilterBits)), 1); + xQ4 += xStepQ4; + } + src += srcStride; + dst += dstStride; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe void ConvolveVertAvx2( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] yFilters, + int y0Q4, + int w, + int h) + { + Vector128 zero = Vector128.Zero; + Vector128 const64 = Vector128.Create(64); + Vector256 indices = Vector256.Create( + 0, + srcStride, + srcStride * 2, + srcStride * 3, + srcStride * 4, + srcStride * 5, + srcStride * 6, + srcStride * 7); + + ulong x, y; + src -= srcStride * (SubpelTaps / 2 - 1); + + fixed (Array8* yFilter = yFilters) + { + Vector128 vfilter = Sse2.LoadVector128((short*)yFilter + (uint)(y0Q4 & SubpelMask) * 8); + + ulong srcBaseY = (uint)y0Q4 >> SubpelBits; + for (y = 0; y < (uint)h; ++y) + { + ulong srcOffset = (srcBaseY + y) * (uint)srcStride; + for (x = 0; x < (uint)w; x += 4) + { + Vector256 vsrc = Avx2.GatherVector256((uint*)&src[srcOffset + x], indices, 1).AsInt32(); + + Vector128 vsrcL = vsrc.GetLower(); + Vector128 vsrcH = vsrc.GetUpper(); + + Vector128 vsrcUnpck11 = Sse2.UnpackLow(vsrcL.AsByte(), vsrcH.AsByte()); + Vector128 vsrcUnpck12 = Sse2.UnpackHigh(vsrcL.AsByte(), vsrcH.AsByte()); + + Vector128 vsrcUnpck21 = Sse2.UnpackLow(vsrcUnpck11, vsrcUnpck12); + Vector128 vsrcUnpck22 = Sse2.UnpackHigh(vsrcUnpck11, vsrcUnpck12); + + Vector128 vsrc01 = Sse2.UnpackLow(vsrcUnpck21, vsrcUnpck22); + Vector128 vsrc23 = Sse2.UnpackHigh(vsrcUnpck21, vsrcUnpck22); + + Vector128 vsrc11 = Sse.MoveHighToLow(vsrc01.AsSingle(), vsrc01.AsSingle()).AsByte(); + Vector128 vsrc33 = Sse.MoveHighToLow(vsrc23.AsSingle(), vsrc23.AsSingle()).AsByte(); + + Vector128 vsrc0 = Sse41.ConvertToVector128Int16(vsrc01); + Vector128 vsrc1 = Sse41.ConvertToVector128Int16(vsrc11); + Vector128 vsrc2 = Sse41.ConvertToVector128Int16(vsrc23); + Vector128 vsrc3 = Sse41.ConvertToVector128Int16(vsrc33); + + Vector128 sum0123 = MultiplyAddAdjacent(vsrc0, vsrc1, vsrc2, vsrc3, vfilter, zero); + + Sse.StoreScalar((float*)&dst[x], PackUnsignedSaturate(RoundShift(sum0123, const64), zero).AsSingle()); + } + dst += dstStride; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe void ConvolveVert( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] yFilters, + int y0Q4, + int yStepQ4, + int w, + int h) + { + if (Avx2.IsSupported && UseIntrinsics && yStepQ4 == 1 << SubpelBits) + { + ConvolveVertAvx2(src, srcStride, dst, dstStride, yFilters, y0Q4, w, h); + + return; + } + + int x, y; + src -= srcStride * (SubpelTaps / 2 - 1); + + for (x = 0; x < w; ++x) + { + int yQ4 = y0Q4; + for (y = 0; y < h; ++y) + { + byte* srcY = &src[(yQ4 >> SubpelBits) * srcStride]; + ref Array8 yFilter = ref yFilters[yQ4 & SubpelMask]; + int k, sum = 0; + for (k = 0; k < SubpelTaps; ++k) + { + sum += srcY[k * srcStride] * yFilter[k]; + } + + dst[y * dstStride] = BitUtils.ClipPixel(BitUtils.RoundPowerOfTwo(sum, FilterBits)); + yQ4 += yStepQ4; + } + ++src; + ++dst; + } + } + + private static unsafe void ConvolveAvgVert( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] yFilters, + int y0Q4, + int yStepQ4, + int w, + int h) + { + int x, y; + src -= srcStride * (SubpelTaps / 2 - 1); + + for (x = 0; x < w; ++x) + { + int yQ4 = y0Q4; + for (y = 0; y < h; ++y) + { + byte* srcY = &src[(yQ4 >> SubpelBits) * srcStride]; + ref Array8 yFilter = ref yFilters[yQ4 & SubpelMask]; + int k, sum = 0; + for (k = 0; k < SubpelTaps; ++k) + { + sum += srcY[k * srcStride] * yFilter[k]; + } + + dst[y * dstStride] = (byte)BitUtils.RoundPowerOfTwo( + dst[y * dstStride] + BitUtils.ClipPixel(BitUtils.RoundPowerOfTwo(sum, FilterBits)), 1); + yQ4 += yStepQ4; + } + ++src; + ++dst; + } + } + + public static unsafe void Convolve8Horiz( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h) + { + ConvolveHoriz(src, srcStride, dst, dstStride, filter, x0Q4, xStepQ4, w, h); + } + + public static unsafe void Convolve8AvgHoriz( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h) + { + ConvolveAvgHoriz(src, srcStride, dst, dstStride, filter, x0Q4, xStepQ4, w, h); + } + + public static unsafe void Convolve8Vert( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h) + { + ConvolveVert(src, srcStride, dst, dstStride, filter, y0Q4, yStepQ4, w, h); + } + + public static unsafe void Convolve8AvgVert( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h) + { + ConvolveAvgVert(src, srcStride, dst, dstStride, filter, y0Q4, yStepQ4, w, h); + } + + [SkipLocalsInit] + public static unsafe void Convolve8( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h) + { + // Note: Fixed size intermediate buffer, temp, places limits on parameters. + // 2d filtering proceeds in 2 steps: + // (1) Interpolate horizontally into an intermediate buffer, temp. + // (2) Interpolate temp vertically to derive the sub-pixel result. + // Deriving the maximum number of rows in the temp buffer (135): + // --Smallest scaling factor is x1/2 ==> yStepQ4 = 32 (Normative). + // --Largest block size is 64x64 pixels. + // --64 rows in the downscaled frame span a distance of (64 - 1) * 32 in the + // original frame (in 1/16th pixel units). + // --Must round-up because block may be located at sub-pixel position. + // --Require an additional SubpelTaps rows for the 8-tap filter tails. + // --((64 - 1) * 32 + 15) >> 4 + 8 = 135. + // When calling in frame scaling function, the smallest scaling factor is x1/4 + // ==> yStepQ4 = 64. Since w and h are at most 16, the temp buffer is still + // big enough. + byte* temp = stackalloc byte[64 * 135]; + int intermediateHeight = (((h - 1) * yStepQ4 + y0Q4) >> SubpelBits) + SubpelTaps; + + Debug.Assert(w <= 64); + Debug.Assert(h <= 64); + Debug.Assert(yStepQ4 <= 32 || (yStepQ4 <= 64 && h <= 32)); + Debug.Assert(xStepQ4 <= 64); + + ConvolveHoriz(src - srcStride * (SubpelTaps / 2 - 1), srcStride, temp, 64, filter, x0Q4, xStepQ4, w, intermediateHeight); + ConvolveVert(temp + 64 * (SubpelTaps / 2 - 1), 64, dst, dstStride, filter, y0Q4, yStepQ4, w, h); + } + + public static unsafe void Convolve8Avg( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h) + { + // Fixed size intermediate buffer places limits on parameters. + byte* temp = stackalloc byte[64 * 64]; + Debug.Assert(w <= 64); + Debug.Assert(h <= 64); + + Convolve8(src, srcStride, temp, 64, filter, x0Q4, xStepQ4, y0Q4, yStepQ4, w, h); + ConvolveAvg(temp, 64, dst, dstStride, null, 0, 0, 0, 0, w, h); + } + + public static unsafe void ConvolveCopy( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h) + { + int r; + + for (r = h; r > 0; --r) + { + MemoryUtil.Copy(dst, src, w); + src += srcStride; + dst += dstStride; + } + } + + public static unsafe void ConvolveAvg( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h) + { + int x, y; + + for (y = 0; y < h; ++y) + { + for (x = 0; x < w; ++x) + { + dst[x] = (byte)BitUtils.RoundPowerOfTwo(dst[x] + src[x], 1); + } + + src += srcStride; + dst += dstStride; + } + } + + public static unsafe void ScaledHoriz( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h) + { + Convolve8Horiz(src, srcStride, dst, dstStride, filter, x0Q4, xStepQ4, y0Q4, yStepQ4, w, h); + } + + public static unsafe void ScaledVert( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h) + { + Convolve8Vert(src, srcStride, dst, dstStride, filter, x0Q4, xStepQ4, y0Q4, yStepQ4, w, h); + } + + public static unsafe void Scaled2D( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h) + { + Convolve8(src, srcStride, dst, dstStride, filter, x0Q4, xStepQ4, y0Q4, yStepQ4, w, h); + } + + public static unsafe void ScaledAvgHoriz( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h) + { + Convolve8AvgHoriz(src, srcStride, dst, dstStride, filter, x0Q4, xStepQ4, y0Q4, yStepQ4, w, h); + } + + public static unsafe void ScaledAvgVert( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h) + { + Convolve8AvgVert(src, srcStride, dst, dstStride, filter, x0Q4, xStepQ4, y0Q4, yStepQ4, w, h); + } + + public static unsafe void ScaledAvg2D( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h) + { + Convolve8Avg(src, srcStride, dst, dstStride, filter, x0Q4, xStepQ4, y0Q4, yStepQ4, w, h); + } + + private static unsafe void HighbdConvolveHoriz( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + Array8[] xFilters, + int x0Q4, + int xStepQ4, + int w, + int h, + int bd) + { + int x, y; + src -= SubpelTaps / 2 - 1; + + for (y = 0; y < h; ++y) + { + int xQ4 = x0Q4; + for (x = 0; x < w; ++x) + { + ushort* srcX = &src[xQ4 >> SubpelBits]; + ref Array8 xFilter = ref xFilters[xQ4 & SubpelMask]; + int k, sum = 0; + for (k = 0; k < SubpelTaps; ++k) + { + sum += srcX[k] * xFilter[k]; + } + + dst[x] = BitUtils.ClipPixelHighbd(BitUtils.RoundPowerOfTwo(sum, FilterBits), bd); + xQ4 += xStepQ4; + } + src += srcStride; + dst += dstStride; + } + } + + private static unsafe void HighbdConvolveAvgHoriz( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + Array8[] xFilters, + int x0Q4, + int xStepQ4, + int w, + int h, + int bd) + { + int x, y; + src -= SubpelTaps / 2 - 1; + + for (y = 0; y < h; ++y) + { + int xQ4 = x0Q4; + for (x = 0; x < w; ++x) + { + ushort* srcX = &src[xQ4 >> SubpelBits]; + ref Array8 xFilter = ref xFilters[xQ4 & SubpelMask]; + int k, sum = 0; + for (k = 0; k < SubpelTaps; ++k) + { + sum += srcX[k] * xFilter[k]; + } + + dst[x] = (ushort)BitUtils.RoundPowerOfTwo(dst[x] + BitUtils.ClipPixelHighbd(BitUtils.RoundPowerOfTwo(sum, FilterBits), bd), 1); + xQ4 += xStepQ4; + } + src += srcStride; + dst += dstStride; + } + } + + private static unsafe void HighbdConvolveVert( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + Array8[] yFilters, + int y0Q4, + int yStepQ4, + int w, + int h, + int bd) + { + int x, y; + src -= srcStride * (SubpelTaps / 2 - 1); + + for (x = 0; x < w; ++x) + { + int yQ4 = y0Q4; + for (y = 0; y < h; ++y) + { + ushort* srcY = &src[(yQ4 >> SubpelBits) * srcStride]; + ref Array8 yFilter = ref yFilters[yQ4 & SubpelMask]; + int k, sum = 0; + for (k = 0; k < SubpelTaps; ++k) + { + sum += srcY[k * srcStride] * yFilter[k]; + } + + dst[y * dstStride] = BitUtils.ClipPixelHighbd(BitUtils.RoundPowerOfTwo(sum, FilterBits), bd); + yQ4 += yStepQ4; + } + ++src; + ++dst; + } + } + + private static unsafe void HighConvolveAvgVert( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + Array8[] yFilters, + int y0Q4, + int yStepQ4, + int w, + int h, + int bd) + { + int x, y; + src -= srcStride * (SubpelTaps / 2 - 1); + + for (x = 0; x < w; ++x) + { + int yQ4 = y0Q4; + for (y = 0; y < h; ++y) + { + ushort* srcY = &src[(yQ4 >> SubpelBits) * srcStride]; + ref Array8 yFilter = ref yFilters[yQ4 & SubpelMask]; + int k, sum = 0; + for (k = 0; k < SubpelTaps; ++k) + { + sum += srcY[k * srcStride] * yFilter[k]; + } + + dst[y * dstStride] = (ushort)BitUtils.RoundPowerOfTwo( + dst[y * dstStride] + BitUtils.ClipPixelHighbd(BitUtils.RoundPowerOfTwo(sum, FilterBits), bd), 1); + yQ4 += yStepQ4; + } + ++src; + ++dst; + } + } + + private static unsafe void HighbdConvolve( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h, + int bd) + { + // Note: Fixed size intermediate buffer, temp, places limits on parameters. + // 2d filtering proceeds in 2 steps: + // (1) Interpolate horizontally into an intermediate buffer, temp. + // (2) Interpolate temp vertically to derive the sub-pixel result. + // Deriving the maximum number of rows in the temp buffer (135): + // --Smallest scaling factor is x1/2 ==> yStepQ4 = 32 (Normative). + // --Largest block size is 64x64 pixels. + // --64 rows in the downscaled frame span a distance of (64 - 1) * 32 in the + // original frame (in 1/16th pixel units). + // --Must round-up because block may be located at sub-pixel position. + // --Require an additional SubpelTaps rows for the 8-tap filter tails. + // --((64 - 1) * 32 + 15) >> 4 + 8 = 135. + ushort* temp = stackalloc ushort[64 * 135]; + int intermediateHeight = (((h - 1) * yStepQ4 + y0Q4) >> SubpelBits) + SubpelTaps; + + Debug.Assert(w <= 64); + Debug.Assert(h <= 64); + Debug.Assert(yStepQ4 <= 32); + Debug.Assert(xStepQ4 <= 32); + + HighbdConvolveHoriz(src - srcStride * (SubpelTaps / 2 - 1), srcStride, temp, 64, filter, x0Q4, xStepQ4, w, intermediateHeight, bd); + HighbdConvolveVert(temp + 64 * (SubpelTaps / 2 - 1), 64, dst, dstStride, filter, y0Q4, yStepQ4, w, h, bd); + } + + public static unsafe void HighbdConvolve8Horiz( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h, + int bd) + { + HighbdConvolveHoriz(src, srcStride, dst, dstStride, filter, x0Q4, xStepQ4, w, h, bd); + } + + public static unsafe void HighbdConvolve8AvgHoriz( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h, + int bd) + { + HighbdConvolveAvgHoriz(src, srcStride, dst, dstStride, filter, x0Q4, xStepQ4, w, h, bd); + } + + public static unsafe void HighbdConvolve8Vert( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h, + int bd) + { + HighbdConvolveVert(src, srcStride, dst, dstStride, filter, y0Q4, yStepQ4, w, h, bd); + } + + public static unsafe void HighbdConvolve8AvgVert( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h, + int bd) + { + HighConvolveAvgVert(src, srcStride, dst, dstStride, filter, y0Q4, yStepQ4, w, h, bd); + } + + public static unsafe void HighbdConvolve8( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h, + int bd) + { + HighbdConvolve(src, srcStride, dst, dstStride, filter, x0Q4, xStepQ4, y0Q4, yStepQ4, w, h, bd); + } + + public static unsafe void HighbdConvolve8Avg( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h, + int bd) + { + // Fixed size intermediate buffer places limits on parameters. + ushort* temp = stackalloc ushort[64 * 64]; + Debug.Assert(w <= 64); + Debug.Assert(h <= 64); + + HighbdConvolve8(src, srcStride, temp, 64, filter, x0Q4, xStepQ4, y0Q4, yStepQ4, w, h, bd); + HighbdConvolveAvg(temp, 64, dst, dstStride, null, 0, 0, 0, 0, w, h, bd); + } + + public static unsafe void HighbdConvolveCopy( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h, + int bd) + { + int r; + + for (r = h; r > 0; --r) + { + MemoryUtil.Copy(dst, src, w); + src += srcStride; + dst += dstStride; + } + } + + public static unsafe void HighbdConvolveAvg( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h, + int bd) + { + int x, y; + + for (y = 0; y < h; ++y) + { + for (x = 0; x < w; ++x) + { + dst[x] = (ushort)BitUtils.RoundPowerOfTwo(dst[x] + src[x], 1); + } + + src += srcStride; + dst += dstStride; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Filter.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Filter.cs new file mode 100644 index 00000000..a32221e0 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Filter.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Dsp +{ + internal static class Filter + { + public const int FilterBits = 7; + + public const int SubpelBits = 4; + public const int SubpelMask = (1 << SubpelBits) - 1; + public const int SubpelShifts = 1 << SubpelBits; + public const int SubpelTaps = 8; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/IntraPred.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/IntraPred.cs new file mode 100644 index 00000000..8a570ed5 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/IntraPred.cs @@ -0,0 +1,1379 @@ +using Ryujinx.Graphics.Nvdec.Vp9.Common; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Dsp +{ + internal static class IntraPred + { + private static unsafe ref byte Dst(byte* dst, int stride, int x, int y) + { + return ref dst[x + y * stride]; + } + + private static unsafe ref ushort Dst(ushort* dst, int stride, int x, int y) + { + return ref dst[x + y * stride]; + } + + private static byte Avg3(byte a, byte b, byte c) + { + return (byte)((a + 2 * b + c + 2) >> 2); + } + + private static ushort Avg3(ushort a, ushort b, ushort c) + { + return (ushort)((a + 2 * b + c + 2) >> 2); + } + + private static byte Avg2(byte a, byte b) + { + return (byte)((a + b + 1) >> 1); + } + + private static ushort Avg2(ushort a, ushort b) + { + return (ushort)((a + b + 1) >> 1); + } + + public static unsafe void D207Predictor8x8(byte* dst, int stride, byte* above, byte* left) + { + D207Predictor(dst, stride, 8, above, left); + } + + public static unsafe void D207Predictor16x16(byte* dst, int stride, byte* above, byte* left) + { + D207Predictor(dst, stride, 16, above, left); + } + + public static unsafe void D207Predictor32x32(byte* dst, int stride, byte* above, byte* left) + { + D207Predictor(dst, stride, 32, above, left); + } + + private static unsafe void D207Predictor(byte* dst, int stride, int bs, byte* above, byte* left) + { + int r, c; + // First column + for (r = 0; r < bs - 1; ++r) + { + dst[r * stride] = Avg2(left[r], left[r + 1]); + } + + dst[(bs - 1) * stride] = left[bs - 1]; + dst++; + + // Second column + for (r = 0; r < bs - 2; ++r) + { + dst[r * stride] = Avg3(left[r], left[r + 1], left[r + 2]); + } + + dst[(bs - 2) * stride] = Avg3(left[bs - 2], left[bs - 1], left[bs - 1]); + dst[(bs - 1) * stride] = left[bs - 1]; + dst++; + + // Rest of last row + for (c = 0; c < bs - 2; ++c) + { + dst[(bs - 1) * stride + c] = left[bs - 1]; + } + + for (r = bs - 2; r >= 0; --r) + { + for (c = 0; c < bs - 2; ++c) + { + dst[r * stride + c] = dst[(r + 1) * stride + c - 2]; + } + } + } + + public static unsafe void D63Predictor8x8(byte* dst, int stride, byte* above, byte* left) + { + D63Predictor(dst, stride, 8, above, left); + } + + public static unsafe void D63Predictor16x16(byte* dst, int stride, byte* above, byte* left) + { + D63Predictor(dst, stride, 16, above, left); + } + + public static unsafe void D63Predictor32x32(byte* dst, int stride, byte* above, byte* left) + { + D63Predictor(dst, stride, 32, above, left); + } + + private static unsafe void D63Predictor(byte* dst, int stride, int bs, byte* above, byte* left) + { + int r, c; + int size; + for (c = 0; c < bs; ++c) + { + dst[c] = Avg2(above[c], above[c + 1]); + dst[stride + c] = Avg3(above[c], above[c + 1], above[c + 2]); + } + for (r = 2, size = bs - 2; r < bs; r += 2, --size) + { + MemoryUtil.Copy(dst + (r + 0) * stride, dst + (r >> 1), size); + MemoryUtil.Fill(dst + (r + 0) * stride + size, above[bs - 1], bs - size); + MemoryUtil.Copy(dst + (r + 1) * stride, dst + stride + (r >> 1), size); + MemoryUtil.Fill(dst + (r + 1) * stride + size, above[bs - 1], bs - size); + } + } + + public static unsafe void D45Predictor8x8(byte* dst, int stride, byte* above, byte* left) + { + D45Predictor(dst, stride, 8, above, left); + } + + public static unsafe void D45Predictor16x16(byte* dst, int stride, byte* above, byte* left) + { + D45Predictor(dst, stride, 16, above, left); + } + + public static unsafe void D45Predictor32x32(byte* dst, int stride, byte* above, byte* left) + { + D45Predictor(dst, stride, 32, above, left); + } + + private static unsafe void D45Predictor(byte* dst, int stride, int bs, byte* above, byte* left) + { + byte aboveRight = above[bs - 1]; + byte* dstRow0 = dst; + int x, size; + + for (x = 0; x < bs - 1; ++x) + { + dst[x] = Avg3(above[x], above[x + 1], above[x + 2]); + } + dst[bs - 1] = aboveRight; + dst += stride; + for (x = 1, size = bs - 2; x < bs; ++x, --size) + { + MemoryUtil.Copy(dst, dstRow0 + x, size); + MemoryUtil.Fill(dst + size, aboveRight, x + 1); + dst += stride; + } + } + + public static unsafe void D117Predictor8x8(byte* dst, int stride, byte* above, byte* left) + { + D117Predictor(dst, stride, 8, above, left); + } + + public static unsafe void D117Predictor16x16(byte* dst, int stride, byte* above, byte* left) + { + D117Predictor(dst, stride, 16, above, left); + } + + public static unsafe void D117Predictor32x32(byte* dst, int stride, byte* above, byte* left) + { + D117Predictor(dst, stride, 32, above, left); + } + + private static unsafe void D117Predictor(byte* dst, int stride, int bs, byte* above, byte* left) + { + int r, c; + + // First row + for (c = 0; c < bs; c++) + { + dst[c] = Avg2(above[c - 1], above[c]); + } + + dst += stride; + + // Second row + dst[0] = Avg3(left[0], above[-1], above[0]); + for (c = 1; c < bs; c++) + { + dst[c] = Avg3(above[c - 2], above[c - 1], above[c]); + } + + dst += stride; + + // The rest of first col + dst[0] = Avg3(above[-1], left[0], left[1]); + for (r = 3; r < bs; ++r) + { + dst[(r - 2) * stride] = Avg3(left[r - 3], left[r - 2], left[r - 1]); + } + + // The rest of the block + for (r = 2; r < bs; ++r) + { + for (c = 1; c < bs; c++) + { + dst[c] = dst[-2 * stride + c - 1]; + } + + dst += stride; + } + } + + public static unsafe void D135Predictor8x8(byte* dst, int stride, byte* above, byte* left) + { + D135Predictor(dst, stride, 8, above, left); + } + + public static unsafe void D135Predictor16x16(byte* dst, int stride, byte* above, byte* left) + { + D135Predictor(dst, stride, 16, above, left); + } + + public static unsafe void D135Predictor32x32(byte* dst, int stride, byte* above, byte* left) + { + D135Predictor(dst, stride, 32, above, left); + } + + private static unsafe void D135Predictor(byte* dst, int stride, int bs, byte* above, byte* left) + { + int i; + byte* border = stackalloc byte[32 + 32 - 1]; // outer border from bottom-left to top-right + + // Dst(dst, stride, bs, bs - 2)[0], i.e., border starting at bottom-left + for (i = 0; i < bs - 2; ++i) + { + border[i] = Avg3(left[bs - 3 - i], left[bs - 2 - i], left[bs - 1 - i]); + } + border[bs - 2] = Avg3(above[-1], left[0], left[1]); + border[bs - 1] = Avg3(left[0], above[-1], above[0]); + border[bs - 0] = Avg3(above[-1], above[0], above[1]); + // dst[0][2, size), i.e., remaining top border ascending + for (i = 0; i < bs - 2; ++i) + { + border[bs + 1 + i] = Avg3(above[i], above[i + 1], above[i + 2]); + } + + for (i = 0; i < bs; ++i) + { + MemoryUtil.Copy(dst + i * stride, border + bs - 1 - i, bs); + } + } + + public static unsafe void D153Predictor8x8(byte* dst, int stride, byte* above, byte* left) + { + D153Predictor(dst, stride, 8, above, left); + } + + public static unsafe void D153Predictor16x16(byte* dst, int stride, byte* above, byte* left) + { + D153Predictor(dst, stride, 16, above, left); + } + + public static unsafe void D153Predictor32x32(byte* dst, int stride, byte* above, byte* left) + { + D153Predictor(dst, stride, 32, above, left); + } + + private static unsafe void D153Predictor(byte* dst, int stride, int bs, byte* above, byte* left) + { + int r, c; + dst[0] = Avg2(above[-1], left[0]); + for (r = 1; r < bs; r++) + { + dst[r * stride] = Avg2(left[r - 1], left[r]); + } + + dst++; + + dst[0] = Avg3(left[0], above[-1], above[0]); + dst[stride] = Avg3(above[-1], left[0], left[1]); + for (r = 2; r < bs; r++) + { + dst[r * stride] = Avg3(left[r - 2], left[r - 1], left[r]); + } + + dst++; + + for (c = 0; c < bs - 2; c++) + { + dst[c] = Avg3(above[c - 1], above[c], above[c + 1]); + } + + dst += stride; + + for (r = 1; r < bs; ++r) + { + for (c = 0; c < bs - 2; c++) + { + dst[c] = dst[-stride + c - 2]; + } + + dst += stride; + } + } + + public static unsafe void VPredictor4x4(byte* dst, int stride, byte* above, byte* left) + { + VPredictor(dst, stride, 4, above, left); + } + + public static unsafe void VPredictor8x8(byte* dst, int stride, byte* above, byte* left) + { + VPredictor(dst, stride, 8, above, left); + } + + public static unsafe void VPredictor16x16(byte* dst, int stride, byte* above, byte* left) + { + VPredictor(dst, stride, 16, above, left); + } + + public static unsafe void VPredictor32x32(byte* dst, int stride, byte* above, byte* left) + { + VPredictor(dst, stride, 32, above, left); + } + + private static unsafe void VPredictor(byte* dst, int stride, int bs, byte* above, byte* left) + { + int r; + + for (r = 0; r < bs; r++) + { + MemoryUtil.Copy(dst, above, bs); + dst += stride; + } + } + + public static unsafe void HPredictor4x4(byte* dst, int stride, byte* above, byte* left) + { + HPredictor(dst, stride, 4, above, left); + } + + public static unsafe void HPredictor8x8(byte* dst, int stride, byte* above, byte* left) + { + HPredictor(dst, stride, 8, above, left); + } + + public static unsafe void HPredictor16x16(byte* dst, int stride, byte* above, byte* left) + { + HPredictor(dst, stride, 16, above, left); + } + + public static unsafe void HPredictor32x32(byte* dst, int stride, byte* above, byte* left) + { + HPredictor(dst, stride, 32, above, left); + } + + private static unsafe void HPredictor(byte* dst, int stride, int bs, byte* above, byte* left) + { + int r; + + for (r = 0; r < bs; r++) + { + MemoryUtil.Fill(dst, left[r], bs); + dst += stride; + } + } + + public static unsafe void TMPredictor4x4(byte* dst, int stride, byte* above, byte* left) + { + TMPredictor(dst, stride, 4, above, left); + } + + public static unsafe void TMPredictor8x8(byte* dst, int stride, byte* above, byte* left) + { + TMPredictor(dst, stride, 8, above, left); + } + + public static unsafe void TMPredictor16x16(byte* dst, int stride, byte* above, byte* left) + { + TMPredictor(dst, stride, 16, above, left); + } + + public static unsafe void TMPredictor32x32(byte* dst, int stride, byte* above, byte* left) + { + TMPredictor(dst, stride, 32, above, left); + } + + private static unsafe void TMPredictor(byte* dst, int stride, int bs, byte* above, byte* left) + { + int r, c; + int yTopLeft = above[-1]; + + for (r = 0; r < bs; r++) + { + for (c = 0; c < bs; c++) + { + dst[c] = BitUtils.ClipPixel(left[r] + above[c] - yTopLeft); + } + + dst += stride; + } + } + + public static unsafe void Dc128Predictor4x4(byte* dst, int stride, byte* above, byte* left) + { + Dc128Predictor(dst, stride, 4, above, left); + } + + public static unsafe void Dc128Predictor8x8(byte* dst, int stride, byte* above, byte* left) + { + Dc128Predictor(dst, stride, 8, above, left); + } + + public static unsafe void Dc128Predictor16x16(byte* dst, int stride, byte* above, byte* left) + { + Dc128Predictor(dst, stride, 16, above, left); + } + + public static unsafe void Dc128Predictor32x32(byte* dst, int stride, byte* above, byte* left) + { + Dc128Predictor(dst, stride, 32, above, left); + } + + private static unsafe void Dc128Predictor(byte* dst, int stride, int bs, byte* above, byte* left) + { + int r; + + for (r = 0; r < bs; r++) + { + MemoryUtil.Fill(dst, (byte)128, bs); + dst += stride; + } + } + + public static unsafe void DcLeftPredictor4x4(byte* dst, int stride, byte* above, byte* left) + { + DcLeftPredictor(dst, stride, 4, above, left); + } + + public static unsafe void DcLeftPredictor8x8(byte* dst, int stride, byte* above, byte* left) + { + DcLeftPredictor(dst, stride, 8, above, left); + } + + public static unsafe void DcLeftPredictor16x16(byte* dst, int stride, byte* above, byte* left) + { + DcLeftPredictor(dst, stride, 16, above, left); + } + + public static unsafe void DcLeftPredictor32x32(byte* dst, int stride, byte* above, byte* left) + { + DcLeftPredictor(dst, stride, 32, above, left); + } + + private static unsafe void DcLeftPredictor(byte* dst, int stride, int bs, byte* above, byte* left) + { + int i, r, expectedDc, sum = 0; + + for (i = 0; i < bs; i++) + { + sum += left[i]; + } + + expectedDc = (sum + (bs >> 1)) / bs; + + for (r = 0; r < bs; r++) + { + MemoryUtil.Fill(dst, (byte)expectedDc, bs); + dst += stride; + } + } + + public static unsafe void DcTopPredictor4x4(byte* dst, int stride, byte* above, byte* left) + { + DcTopPredictor(dst, stride, 4, above, left); + } + + public static unsafe void DcTopPredictor8x8(byte* dst, int stride, byte* above, byte* left) + { + DcTopPredictor(dst, stride, 8, above, left); + } + + public static unsafe void DcTopPredictor16x16(byte* dst, int stride, byte* above, byte* left) + { + DcTopPredictor(dst, stride, 16, above, left); + } + + public static unsafe void DcTopPredictor32x32(byte* dst, int stride, byte* above, byte* left) + { + DcTopPredictor(dst, stride, 32, above, left); + } + + private static unsafe void DcTopPredictor(byte* dst, int stride, int bs, byte* above, byte* left) + { + int i, r, expectedDc, sum = 0; + + for (i = 0; i < bs; i++) + { + sum += above[i]; + } + + expectedDc = (sum + (bs >> 1)) / bs; + + for (r = 0; r < bs; r++) + { + MemoryUtil.Fill(dst, (byte)expectedDc, bs); + dst += stride; + } + } + + public static unsafe void DcPredictor4x4(byte* dst, int stride, byte* above, byte* left) + { + DcPredictor(dst, stride, 4, above, left); + } + + public static unsafe void DcPredictor8x8(byte* dst, int stride, byte* above, byte* left) + { + DcPredictor(dst, stride, 8, above, left); + } + + public static unsafe void DcPredictor16x16(byte* dst, int stride, byte* above, byte* left) + { + DcPredictor(dst, stride, 16, above, left); + } + + public static unsafe void DcPredictor32x32(byte* dst, int stride, byte* above, byte* left) + { + DcPredictor(dst, stride, 32, above, left); + } + + private static unsafe void DcPredictor(byte* dst, int stride, int bs, byte* above, byte* left) + { + int i, r, expectedDc, sum = 0; + int count = 2 * bs; + + for (i = 0; i < bs; i++) + { + sum += above[i]; + sum += left[i]; + } + + expectedDc = (sum + (count >> 1)) / count; + + for (r = 0; r < bs; r++) + { + MemoryUtil.Fill(dst, (byte)expectedDc, bs); + dst += stride; + } + } + + public static unsafe void HePredictor4x4(byte* dst, int stride, byte* above, byte* left) + { + byte h = above[-1]; + byte I = left[0]; + byte j = left[1]; + byte k = left[2]; + byte l = left[3]; + + MemoryUtil.Fill(dst + stride * 0, Avg3(h, I, j), 4); + MemoryUtil.Fill(dst + stride * 1, Avg3(I, j, k), 4); + MemoryUtil.Fill(dst + stride * 2, Avg3(j, k, l), 4); + MemoryUtil.Fill(dst + stride * 3, Avg3(k, l, l), 4); + } + + public static unsafe void VePredictor4x4(byte* dst, int stride, byte* above, byte* left) + { + byte h = above[-1]; + byte I = above[0]; + byte j = above[1]; + byte k = above[2]; + byte l = above[3]; + byte m = above[4]; + + dst[0] = Avg3(h, I, j); + dst[1] = Avg3(I, j, k); + dst[2] = Avg3(j, k, l); + dst[3] = Avg3(k, l, m); + MemoryUtil.Copy(dst + stride * 1, dst, 4); + MemoryUtil.Copy(dst + stride * 2, dst, 4); + MemoryUtil.Copy(dst + stride * 3, dst, 4); + } + + public static unsafe void D207Predictor4x4(byte* dst, int stride, byte* above, byte* left) + { + byte I = left[0]; + byte j = left[1]; + byte k = left[2]; + byte l = left[3]; + Dst(dst, stride, 0, 0) = Avg2(I, j); + Dst(dst, stride, 2, 0) = Dst(dst, stride, 0, 1) = Avg2(j, k); + Dst(dst, stride, 2, 1) = Dst(dst, stride, 0, 2) = Avg2(k, l); + Dst(dst, stride, 1, 0) = Avg3(I, j, k); + Dst(dst, stride, 3, 0) = Dst(dst, stride, 1, 1) = Avg3(j, k, l); + Dst(dst, stride, 3, 1) = Dst(dst, stride, 1, 2) = Avg3(k, l, l); + Dst(dst, stride, 3, 2) = Dst(dst, stride, 2, 2) = Dst(dst, stride, 0, 3) = Dst(dst, stride, 1, 3) = Dst(dst, stride, 2, 3) = Dst(dst, stride, 3, 3) = l; + } + + public static unsafe void D63Predictor4x4(byte* dst, int stride, byte* above, byte* left) + { + byte a = above[0]; + byte b = above[1]; + byte c = above[2]; + byte d = above[3]; + byte e = above[4]; + byte f = above[5]; + byte g = above[6]; + Dst(dst, stride, 0, 0) = Avg2(a, b); + Dst(dst, stride, 1, 0) = Dst(dst, stride, 0, 2) = Avg2(b, c); + Dst(dst, stride, 2, 0) = Dst(dst, stride, 1, 2) = Avg2(c, d); + Dst(dst, stride, 3, 0) = Dst(dst, stride, 2, 2) = Avg2(d, e); + Dst(dst, stride, 3, 2) = Avg2(e, f); // Differs from vp8 + + Dst(dst, stride, 0, 1) = Avg3(a, b, c); + Dst(dst, stride, 1, 1) = Dst(dst, stride, 0, 3) = Avg3(b, c, d); + Dst(dst, stride, 2, 1) = Dst(dst, stride, 1, 3) = Avg3(c, d, e); + Dst(dst, stride, 3, 1) = Dst(dst, stride, 2, 3) = Avg3(d, e, f); + Dst(dst, stride, 3, 3) = Avg3(e, f, g); // Differs from vp8 + } + + public static unsafe void D63ePredictor4x4(byte* dst, int stride, byte* above, byte* left) + { + byte a = above[0]; + byte b = above[1]; + byte c = above[2]; + byte d = above[3]; + byte e = above[4]; + byte f = above[5]; + byte g = above[6]; + byte h = above[7]; + Dst(dst, stride, 0, 0) = Avg2(a, b); + Dst(dst, stride, 1, 0) = Dst(dst, stride, 0, 2) = Avg2(b, c); + Dst(dst, stride, 2, 0) = Dst(dst, stride, 1, 2) = Avg2(c, d); + Dst(dst, stride, 3, 0) = Dst(dst, stride, 2, 2) = Avg2(d, e); + Dst(dst, stride, 3, 2) = Avg3(e, f, g); + + Dst(dst, stride, 0, 1) = Avg3(a, b, c); + Dst(dst, stride, 1, 1) = Dst(dst, stride, 0, 3) = Avg3(b, c, d); + Dst(dst, stride, 2, 1) = Dst(dst, stride, 1, 3) = Avg3(c, d, e); + Dst(dst, stride, 3, 1) = Dst(dst, stride, 2, 3) = Avg3(d, e, f); + Dst(dst, stride, 3, 3) = Avg3(f, g, h); + } + + public static unsafe void D45Predictor4x4(byte* dst, int stride, byte* above, byte* left) + { + byte a = above[0]; + byte b = above[1]; + byte c = above[2]; + byte d = above[3]; + byte e = above[4]; + byte f = above[5]; + byte g = above[6]; + byte h = above[7]; + Dst(dst, stride, 0, 0) = Avg3(a, b, c); + Dst(dst, stride, 1, 0) = Dst(dst, stride, 0, 1) = Avg3(b, c, d); + Dst(dst, stride, 2, 0) = Dst(dst, stride, 1, 1) = Dst(dst, stride, 0, 2) = Avg3(c, d, e); + Dst(dst, stride, 3, 0) = Dst(dst, stride, 2, 1) = Dst(dst, stride, 1, 2) = Dst(dst, stride, 0, 3) = Avg3(d, e, f); + Dst(dst, stride, 3, 1) = Dst(dst, stride, 2, 2) = Dst(dst, stride, 1, 3) = Avg3(e, f, g); + Dst(dst, stride, 3, 2) = Dst(dst, stride, 2, 3) = Avg3(f, g, h); + Dst(dst, stride, 3, 3) = h; // differs from vp8 + } + + public static unsafe void D45ePredictor4x4(byte* dst, int stride, byte* above, byte* left) + { + byte a = above[0]; + byte b = above[1]; + byte c = above[2]; + byte d = above[3]; + byte e = above[4]; + byte f = above[5]; + byte g = above[6]; + byte h = above[7]; + Dst(dst, stride, 0, 0) = Avg3(a, b, c); + Dst(dst, stride, 1, 0) = Dst(dst, stride, 0, 1) = Avg3(b, c, d); + Dst(dst, stride, 2, 0) = Dst(dst, stride, 1, 1) = Dst(dst, stride, 0, 2) = Avg3(c, d, e); + Dst(dst, stride, 3, 0) = Dst(dst, stride, 2, 1) = Dst(dst, stride, 1, 2) = Dst(dst, stride, 0, 3) = Avg3(d, e, f); + Dst(dst, stride, 3, 1) = Dst(dst, stride, 2, 2) = Dst(dst, stride, 1, 3) = Avg3(e, f, g); + Dst(dst, stride, 3, 2) = Dst(dst, stride, 2, 3) = Avg3(f, g, h); + Dst(dst, stride, 3, 3) = Avg3(g, h, h); + } + + public static unsafe void D117Predictor4x4(byte* dst, int stride, byte* above, byte* left) + { + byte I = left[0]; + byte j = left[1]; + byte k = left[2]; + byte x = above[-1]; + byte a = above[0]; + byte b = above[1]; + byte c = above[2]; + byte d = above[3]; + Dst(dst, stride, 0, 0) = Dst(dst, stride, 1, 2) = Avg2(x, a); + Dst(dst, stride, 1, 0) = Dst(dst, stride, 2, 2) = Avg2(a, b); + Dst(dst, stride, 2, 0) = Dst(dst, stride, 3, 2) = Avg2(b, c); + Dst(dst, stride, 3, 0) = Avg2(c, d); + + Dst(dst, stride, 0, 3) = Avg3(k, j, I); + Dst(dst, stride, 0, 2) = Avg3(j, I, x); + Dst(dst, stride, 0, 1) = Dst(dst, stride, 1, 3) = Avg3(I, x, a); + Dst(dst, stride, 1, 1) = Dst(dst, stride, 2, 3) = Avg3(x, a, b); + Dst(dst, stride, 2, 1) = Dst(dst, stride, 3, 3) = Avg3(a, b, c); + Dst(dst, stride, 3, 1) = Avg3(b, c, d); + } + + public static unsafe void D135Predictor4x4(byte* dst, int stride, byte* above, byte* left) + { + byte I = left[0]; + byte j = left[1]; + byte k = left[2]; + byte l = left[3]; + byte x = above[-1]; + byte a = above[0]; + byte b = above[1]; + byte c = above[2]; + byte d = above[3]; + Dst(dst, stride, 0, 3) = Avg3(j, k, l); + Dst(dst, stride, 1, 3) = Dst(dst, stride, 0, 2) = Avg3(I, j, k); + Dst(dst, stride, 2, 3) = Dst(dst, stride, 1, 2) = Dst(dst, stride, 0, 1) = Avg3(x, I, j); + Dst(dst, stride, 3, 3) = Dst(dst, stride, 2, 2) = Dst(dst, stride, 1, 1) = Dst(dst, stride, 0, 0) = Avg3(a, x, I); + Dst(dst, stride, 3, 2) = Dst(dst, stride, 2, 1) = Dst(dst, stride, 1, 0) = Avg3(b, a, x); + Dst(dst, stride, 3, 1) = Dst(dst, stride, 2, 0) = Avg3(c, b, a); + Dst(dst, stride, 3, 0) = Avg3(d, c, b); + } + + public static unsafe void D153Predictor4x4(byte* dst, int stride, byte* above, byte* left) + { + byte I = left[0]; + byte j = left[1]; + byte k = left[2]; + byte l = left[3]; + byte x = above[-1]; + byte a = above[0]; + byte b = above[1]; + byte c = above[2]; + Dst(dst, stride, 0, 0) = Dst(dst, stride, 2, 1) = Avg2(I, x); + Dst(dst, stride, 0, 1) = Dst(dst, stride, 2, 2) = Avg2(j, I); + Dst(dst, stride, 0, 2) = Dst(dst, stride, 2, 3) = Avg2(k, j); + Dst(dst, stride, 0, 3) = Avg2(l, k); + + Dst(dst, stride, 3, 0) = Avg3(a, b, c); + Dst(dst, stride, 2, 0) = Avg3(x, a, b); + Dst(dst, stride, 1, 0) = Dst(dst, stride, 3, 1) = Avg3(I, x, a); + Dst(dst, stride, 1, 1) = Dst(dst, stride, 3, 2) = Avg3(j, I, x); + Dst(dst, stride, 1, 2) = Dst(dst, stride, 3, 3) = Avg3(k, j, I); + Dst(dst, stride, 1, 3) = Avg3(l, k, j); + } + + public static unsafe void HighbdD207Predictor8x8(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD207Predictor(dst, stride, 8, above, left, bd); + } + + public static unsafe void HighbdD207Predictor16x16(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD207Predictor(dst, stride, 16, above, left, bd); + } + + public static unsafe void HighbdD207Predictor32x32(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD207Predictor(dst, stride, 32, above, left, bd); + } + + private static unsafe void HighbdD207Predictor(ushort* dst, int stride, int bs, ushort* above, ushort* left, int bd) + { + int r, c; + + // First column. + for (r = 0; r < bs - 1; ++r) + { + dst[r * stride] = Avg2(left[r], left[r + 1]); + } + dst[(bs - 1) * stride] = left[bs - 1]; + dst++; + + // Second column. + for (r = 0; r < bs - 2; ++r) + { + dst[r * stride] = Avg3(left[r], left[r + 1], left[r + 2]); + } + dst[(bs - 2) * stride] = Avg3(left[bs - 2], left[bs - 1], left[bs - 1]); + dst[(bs - 1) * stride] = left[bs - 1]; + dst++; + + // Rest of last row. + for (c = 0; c < bs - 2; ++c) + { + dst[(bs - 1) * stride + c] = left[bs - 1]; + } + + for (r = bs - 2; r >= 0; --r) + { + for (c = 0; c < bs - 2; ++c) + { + dst[r * stride + c] = dst[(r + 1) * stride + c - 2]; + } + } + } + + public static unsafe void HighbdD63Predictor8x8(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD63Predictor(dst, stride, 8, above, left, bd); + } + + public static unsafe void HighbdD63Predictor16x16(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD63Predictor(dst, stride, 16, above, left, bd); + } + + public static unsafe void HighbdD63Predictor32x32(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD63Predictor(dst, stride, 32, above, left, bd); + } + + private static unsafe void HighbdD63Predictor(ushort* dst, int stride, int bs, ushort* above, ushort* left, int bd) + { + int r, c; + int size; + for (c = 0; c < bs; ++c) + { + dst[c] = Avg2(above[c], above[c + 1]); + dst[stride + c] = Avg3(above[c], above[c + 1], above[c + 2]); + } + for (r = 2, size = bs - 2; r < bs; r += 2, --size) + { + MemoryUtil.Copy(dst + (r + 0) * stride, dst + (r >> 1), size); + MemoryUtil.Fill(dst + (r + 0) * stride + size, above[bs - 1], bs - size); + MemoryUtil.Copy(dst + (r + 1) * stride, dst + stride + (r >> 1), size); + MemoryUtil.Fill(dst + (r + 1) * stride + size, above[bs - 1], bs - size); + } + } + + public static unsafe void HighbdD45Predictor8x8(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD45Predictor(dst, stride, 8, above, left, bd); + } + + public static unsafe void HighbdD45Predictor16x16(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD45Predictor(dst, stride, 16, above, left, bd); + } + + public static unsafe void HighbdD45Predictor32x32(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD45Predictor(dst, stride, 32, above, left, bd); + } + + private static unsafe void HighbdD45Predictor(ushort* dst, int stride, int bs, ushort* above, ushort* left, int bd) + { + ushort aboveRight = above[bs - 1]; + ushort* dstRow0 = dst; + int x, size; + + for (x = 0; x < bs - 1; ++x) + { + dst[x] = Avg3(above[x], above[x + 1], above[x + 2]); + } + dst[bs - 1] = aboveRight; + dst += stride; + for (x = 1, size = bs - 2; x < bs; ++x, --size) + { + MemoryUtil.Copy(dst, dstRow0 + x, size); + MemoryUtil.Fill(dst + size, aboveRight, x + 1); + dst += stride; + } + } + + public static unsafe void HighbdD117Predictor8x8(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD117Predictor(dst, stride, 8, above, left, bd); + } + + public static unsafe void HighbdD117Predictor16x16(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD117Predictor(dst, stride, 16, above, left, bd); + } + + public static unsafe void HighbdD117Predictor32x32(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD117Predictor(dst, stride, 32, above, left, bd); + } + + private static unsafe void HighbdD117Predictor(ushort* dst, int stride, int bs, ushort* above, ushort* left, int bd) + { + int r, c; + + // First row + for (c = 0; c < bs; c++) + { + dst[c] = Avg2(above[c - 1], above[c]); + } + + dst += stride; + + // Second row + dst[0] = Avg3(left[0], above[-1], above[0]); + for (c = 1; c < bs; c++) + { + dst[c] = Avg3(above[c - 2], above[c - 1], above[c]); + } + + dst += stride; + + // The rest of first col + dst[0] = Avg3(above[-1], left[0], left[1]); + for (r = 3; r < bs; ++r) + { + dst[(r - 2) * stride] = Avg3(left[r - 3], left[r - 2], left[r - 1]); + } + + // The rest of the block + for (r = 2; r < bs; ++r) + { + for (c = 1; c < bs; c++) + { + dst[c] = dst[-2 * stride + c - 1]; + } + + dst += stride; + } + } + + public static unsafe void HighbdD135Predictor8x8(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD135Predictor(dst, stride, 8, above, left, bd); + } + + public static unsafe void HighbdD135Predictor16x16(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD135Predictor(dst, stride, 16, above, left, bd); + } + + public static unsafe void HighbdD135Predictor32x32(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD135Predictor(dst, stride, 32, above, left, bd); + } + + private static unsafe void HighbdD135Predictor(ushort* dst, int stride, int bs, ushort* above, ushort* left, int bd) + { + int i; + ushort* border = stackalloc ushort[32 + 32 - 1]; // Outer border from bottom-left to top-right + + // Dst(dst, stride, bs, bs - 2)[0], i.e., border starting at bottom-left + for (i = 0; i < bs - 2; ++i) + { + border[i] = Avg3(left[bs - 3 - i], left[bs - 2 - i], left[bs - 1 - i]); + } + border[bs - 2] = Avg3(above[-1], left[0], left[1]); + border[bs - 1] = Avg3(left[0], above[-1], above[0]); + border[bs - 0] = Avg3(above[-1], above[0], above[1]); + // dst[0][2, size), i.e., remaining top border ascending + for (i = 0; i < bs - 2; ++i) + { + border[bs + 1 + i] = Avg3(above[i], above[i + 1], above[i + 2]); + } + + for (i = 0; i < bs; ++i) + { + MemoryUtil.Copy(dst + i * stride, border + bs - 1 - i, bs); + } + } + + public static unsafe void HighbdD153Predictor8x8(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD153Predictor(dst, stride, 8, above, left, bd); + } + + public static unsafe void HighbdD153Predictor16x16(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD153Predictor(dst, stride, 16, above, left, bd); + } + + public static unsafe void HighbdD153Predictor32x32(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdD153Predictor(dst, stride, 32, above, left, bd); + } + + private static unsafe void HighbdD153Predictor(ushort* dst, int stride, int bs, ushort* above, ushort* left, int bd) + { + int r, c; + dst[0] = Avg2(above[-1], left[0]); + for (r = 1; r < bs; r++) + { + dst[r * stride] = Avg2(left[r - 1], left[r]); + } + + dst++; + + dst[0] = Avg3(left[0], above[-1], above[0]); + dst[stride] = Avg3(above[-1], left[0], left[1]); + for (r = 2; r < bs; r++) + { + dst[r * stride] = Avg3(left[r - 2], left[r - 1], left[r]); + } + + dst++; + + for (c = 0; c < bs - 2; c++) + { + dst[c] = Avg3(above[c - 1], above[c], above[c + 1]); + } + + dst += stride; + + for (r = 1; r < bs; ++r) + { + for (c = 0; c < bs - 2; c++) + { + dst[c] = dst[-stride + c - 2]; + } + + dst += stride; + } + } + + public static unsafe void HighbdVPredictor4x4(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdVPredictor(dst, stride, 4, above, left, bd); + } + + public static unsafe void HighbdVPredictor8x8(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdVPredictor(dst, stride, 8, above, left, bd); + } + + public static unsafe void HighbdVPredictor16x16(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdVPredictor(dst, stride, 16, above, left, bd); + } + + public static unsafe void HighbdVPredictor32x32(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdVPredictor(dst, stride, 32, above, left, bd); + } + + private static unsafe void HighbdVPredictor(ushort* dst, int stride, int bs, ushort* above, ushort* left, int bd) + { + int r; + for (r = 0; r < bs; r++) + { + MemoryUtil.Copy(dst, above, bs); + dst += stride; + } + } + + public static unsafe void HighbdHPredictor4x4(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdHPredictor(dst, stride, 4, above, left, bd); + } + + public static unsafe void HighbdHPredictor8x8(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdHPredictor(dst, stride, 8, above, left, bd); + } + + public static unsafe void HighbdHPredictor16x16(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdHPredictor(dst, stride, 16, above, left, bd); + } + + public static unsafe void HighbdHPredictor32x32(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdHPredictor(dst, stride, 32, above, left, bd); + } + + private static unsafe void HighbdHPredictor(ushort* dst, int stride, int bs, ushort* above, ushort* left, int bd) + { + int r; + for (r = 0; r < bs; r++) + { + MemoryUtil.Fill(dst, left[r], bs); + dst += stride; + } + } + + public static unsafe void HighbdTMPredictor4x4(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdTMPredictor(dst, stride, 4, above, left, bd); + } + + public static unsafe void HighbdTMPredictor8x8(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdTMPredictor(dst, stride, 8, above, left, bd); + } + + public static unsafe void HighbdTMPredictor16x16(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdTMPredictor(dst, stride, 16, above, left, bd); + } + + public static unsafe void HighbdTMPredictor32x32(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdTMPredictor(dst, stride, 32, above, left, bd); + } + + private static unsafe void HighbdTMPredictor(ushort* dst, int stride, int bs, ushort* above, ushort* left, int bd) + { + int r, c; + int yTopLeft = above[-1]; + + for (r = 0; r < bs; r++) + { + for (c = 0; c < bs; c++) + { + dst[c] = BitUtils.ClipPixelHighbd(left[r] + above[c] - yTopLeft, bd); + } + + dst += stride; + } + } + + public static unsafe void HighbdDc128Predictor4x4(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDc128Predictor(dst, stride, 4, above, left, bd); + } + + public static unsafe void HighbdDc128Predictor8x8(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDc128Predictor(dst, stride, 8, above, left, bd); + } + + public static unsafe void HighbdDc128Predictor16x16(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDc128Predictor(dst, stride, 16, above, left, bd); + } + + public static unsafe void HighbdDc128Predictor32x32(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDc128Predictor(dst, stride, 32, above, left, bd); + } + + private static unsafe void HighbdDc128Predictor(ushort* dst, int stride, int bs, ushort* above, ushort* left, int bd) + { + int r; + + for (r = 0; r < bs; r++) + { + MemoryUtil.Fill(dst, (ushort)(128 << (bd - 8)), bs); + dst += stride; + } + } + + public static unsafe void HighbdDcLeftPredictor4x4(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDcLeftPredictor(dst, stride, 4, above, left, bd); + } + + public static unsafe void HighbdDcLeftPredictor8x8(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDcLeftPredictor(dst, stride, 8, above, left, bd); + } + + public static unsafe void HighbdDcLeftPredictor16x16(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDcLeftPredictor(dst, stride, 16, above, left, bd); + } + + public static unsafe void HighbdDcLeftPredictor32x32(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDcLeftPredictor(dst, stride, 32, above, left, bd); + } + + private static unsafe void HighbdDcLeftPredictor(ushort* dst, int stride, int bs, ushort* above, ushort* left, int bd) + { + int i, r, expectedDc, sum = 0; + + for (i = 0; i < bs; i++) + { + sum += left[i]; + } + + expectedDc = (sum + (bs >> 1)) / bs; + + for (r = 0; r < bs; r++) + { + MemoryUtil.Fill(dst, (ushort)expectedDc, bs); + dst += stride; + } + } + + public static unsafe void HighbdDcTopPredictor4x4(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDcTopPredictor(dst, stride, 4, above, left, bd); + } + + public static unsafe void HighbdDcTopPredictor8x8(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDcTopPredictor(dst, stride, 8, above, left, bd); + } + + public static unsafe void HighbdDcTopPredictor16x16(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDcTopPredictor(dst, stride, 16, above, left, bd); + } + + public static unsafe void HighbdDcTopPredictor32x32(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDcTopPredictor(dst, stride, 32, above, left, bd); + } + + private static unsafe void HighbdDcTopPredictor(ushort* dst, int stride, int bs, ushort* above, ushort* left, int bd) + { + int i, r, expectedDc, sum = 0; + + for (i = 0; i < bs; i++) + { + sum += above[i]; + } + + expectedDc = (sum + (bs >> 1)) / bs; + + for (r = 0; r < bs; r++) + { + MemoryUtil.Fill(dst, (ushort)expectedDc, bs); + dst += stride; + } + } + + public static unsafe void HighbdDcPredictor4x4(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDcPredictor(dst, stride, 4, above, left, bd); + } + + public static unsafe void HighbdDcPredictor8x8(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDcPredictor(dst, stride, 8, above, left, bd); + } + + public static unsafe void HighbdDcPredictor16x16(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDcPredictor(dst, stride, 16, above, left, bd); + } + + public static unsafe void HighbdDcPredictor32x32(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + HighbdDcPredictor(dst, stride, 32, above, left, bd); + } + + private static unsafe void HighbdDcPredictor(ushort* dst, int stride, int bs, ushort* above, ushort* left, int bd) + { + int i, r, expectedDc, sum = 0; + int count = 2 * bs; + + for (i = 0; i < bs; i++) + { + sum += above[i]; + sum += left[i]; + } + + expectedDc = (sum + (count >> 1)) / count; + + for (r = 0; r < bs; r++) + { + MemoryUtil.Fill(dst, (ushort)expectedDc, bs); + dst += stride; + } + } + + public static unsafe void HighbdD207Predictor4x4(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + ushort I = left[0]; + ushort j = left[1]; + ushort k = left[2]; + ushort l = left[3]; + Dst(dst, stride, 0, 0) = Avg2(I, j); + Dst(dst, stride, 2, 0) = Dst(dst, stride, 0, 1) = Avg2(j, k); + Dst(dst, stride, 2, 1) = Dst(dst, stride, 0, 2) = Avg2(k, l); + Dst(dst, stride, 1, 0) = Avg3(I, j, k); + Dst(dst, stride, 3, 0) = Dst(dst, stride, 1, 1) = Avg3(j, k, l); + Dst(dst, stride, 3, 1) = Dst(dst, stride, 1, 2) = Avg3(k, l, l); + Dst(dst, stride, 3, 2) = Dst(dst, stride, 2, 2) = Dst(dst, stride, 0, 3) = Dst(dst, stride, 1, 3) = Dst(dst, stride, 2, 3) = Dst(dst, stride, 3, 3) = l; + } + + public static unsafe void HighbdD63Predictor4x4(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + ushort a = above[0]; + ushort b = above[1]; + ushort c = above[2]; + ushort d = above[3]; + ushort e = above[4]; + ushort f = above[5]; + ushort g = above[6]; + Dst(dst, stride, 0, 0) = Avg2(a, b); + Dst(dst, stride, 1, 0) = Dst(dst, stride, 0, 2) = Avg2(b, c); + Dst(dst, stride, 2, 0) = Dst(dst, stride, 1, 2) = Avg2(c, d); + Dst(dst, stride, 3, 0) = Dst(dst, stride, 2, 2) = Avg2(d, e); + Dst(dst, stride, 3, 2) = Avg2(e, f); // Differs from vp8 + + Dst(dst, stride, 0, 1) = Avg3(a, b, c); + Dst(dst, stride, 1, 1) = Dst(dst, stride, 0, 3) = Avg3(b, c, d); + Dst(dst, stride, 2, 1) = Dst(dst, stride, 1, 3) = Avg3(c, d, e); + Dst(dst, stride, 3, 1) = Dst(dst, stride, 2, 3) = Avg3(d, e, f); + Dst(dst, stride, 3, 3) = Avg3(e, f, g); // Differs from vp8 + } + + public static unsafe void HighbdD45Predictor4x4(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + ushort a = above[0]; + ushort b = above[1]; + ushort c = above[2]; + ushort d = above[3]; + ushort e = above[4]; + ushort f = above[5]; + ushort g = above[6]; + ushort h = above[7]; + Dst(dst, stride, 0, 0) = Avg3(a, b, c); + Dst(dst, stride, 1, 0) = Dst(dst, stride, 0, 1) = Avg3(b, c, d); + Dst(dst, stride, 2, 0) = Dst(dst, stride, 1, 1) = Dst(dst, stride, 0, 2) = Avg3(c, d, e); + Dst(dst, stride, 3, 0) = Dst(dst, stride, 2, 1) = Dst(dst, stride, 1, 2) = Dst(dst, stride, 0, 3) = Avg3(d, e, f); + Dst(dst, stride, 3, 1) = Dst(dst, stride, 2, 2) = Dst(dst, stride, 1, 3) = Avg3(e, f, g); + Dst(dst, stride, 3, 2) = Dst(dst, stride, 2, 3) = Avg3(f, g, h); + Dst(dst, stride, 3, 3) = h; // Differs from vp8 + } + + public static unsafe void HighbdD117Predictor4x4(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + ushort I = left[0]; + ushort j = left[1]; + ushort k = left[2]; + ushort x = above[-1]; + ushort a = above[0]; + ushort b = above[1]; + ushort c = above[2]; + ushort d = above[3]; + Dst(dst, stride, 0, 0) = Dst(dst, stride, 1, 2) = Avg2(x, a); + Dst(dst, stride, 1, 0) = Dst(dst, stride, 2, 2) = Avg2(a, b); + Dst(dst, stride, 2, 0) = Dst(dst, stride, 3, 2) = Avg2(b, c); + Dst(dst, stride, 3, 0) = Avg2(c, d); + + Dst(dst, stride, 0, 3) = Avg3(k, j, I); + Dst(dst, stride, 0, 2) = Avg3(j, I, x); + Dst(dst, stride, 0, 1) = Dst(dst, stride, 1, 3) = Avg3(I, x, a); + Dst(dst, stride, 1, 1) = Dst(dst, stride, 2, 3) = Avg3(x, a, b); + Dst(dst, stride, 2, 1) = Dst(dst, stride, 3, 3) = Avg3(a, b, c); + Dst(dst, stride, 3, 1) = Avg3(b, c, d); + } + + public static unsafe void HighbdD135Predictor4x4(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + ushort I = left[0]; + ushort j = left[1]; + ushort k = left[2]; + ushort l = left[3]; + ushort x = above[-1]; + ushort a = above[0]; + ushort b = above[1]; + ushort c = above[2]; + ushort d = above[3]; + Dst(dst, stride, 0, 3) = Avg3(j, k, l); + Dst(dst, stride, 1, 3) = Dst(dst, stride, 0, 2) = Avg3(I, j, k); + Dst(dst, stride, 2, 3) = Dst(dst, stride, 1, 2) = Dst(dst, stride, 0, 1) = Avg3(x, I, j); + Dst(dst, stride, 3, 3) = Dst(dst, stride, 2, 2) = Dst(dst, stride, 1, 1) = Dst(dst, stride, 0, 0) = Avg3(a, x, I); + Dst(dst, stride, 3, 2) = Dst(dst, stride, 2, 1) = Dst(dst, stride, 1, 0) = Avg3(b, a, x); + Dst(dst, stride, 3, 1) = Dst(dst, stride, 2, 0) = Avg3(c, b, a); + Dst(dst, stride, 3, 0) = Avg3(d, c, b); + } + + public static unsafe void HighbdD153Predictor4x4(ushort* dst, int stride, ushort* above, ushort* left, int bd) + { + ushort I = left[0]; + ushort j = left[1]; + ushort k = left[2]; + ushort l = left[3]; + ushort x = above[-1]; + ushort a = above[0]; + ushort b = above[1]; + ushort c = above[2]; + + Dst(dst, stride, 0, 0) = Dst(dst, stride, 2, 1) = Avg2(I, x); + Dst(dst, stride, 0, 1) = Dst(dst, stride, 2, 2) = Avg2(j, I); + Dst(dst, stride, 0, 2) = Dst(dst, stride, 2, 3) = Avg2(k, j); + Dst(dst, stride, 0, 3) = Avg2(l, k); + + Dst(dst, stride, 3, 0) = Avg3(a, b, c); + Dst(dst, stride, 2, 0) = Avg3(x, a, b); + Dst(dst, stride, 1, 0) = Dst(dst, stride, 3, 1) = Avg3(I, x, a); + Dst(dst, stride, 1, 1) = Dst(dst, stride, 3, 2) = Avg3(j, I, x); + Dst(dst, stride, 1, 2) = Dst(dst, stride, 3, 3) = Avg3(k, j, I); + Dst(dst, stride, 1, 3) = Avg3(l, k, j); + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/InvTxfm.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/InvTxfm.cs new file mode 100644 index 00000000..c4f45bfd --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/InvTxfm.cs @@ -0,0 +1,2935 @@ +using Ryujinx.Graphics.Nvdec.Vp9.Common; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using static Ryujinx.Graphics.Nvdec.Vp9.Dsp.TxfmCommon; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Dsp +{ + internal static class InvTxfm + { + // 12 signal input bits + 7 2D forward transform amplify bits + 5 1D inverse + // transform amplify bits + 1 bit for contingency in rounding and quantizing + private const int HighbdValidTxfmMagnitudeRange = (1 << 25); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int DetectInvalidHighbdInput(ReadOnlySpan input, int size) + { + int i; + for (i = 0; i < size; ++i) + { + if (Math.Abs(input[i]) >= HighbdValidTxfmMagnitudeRange) + { + return 1; + } + } + + return 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static long CheckRange(long input) + { + // For valid VP9 input streams, intermediate stage coefficients should always + // stay within the range of a signed 16 bit integer. Coefficients can go out + // of this range for invalid/corrupt VP9 streams. + Debug.Assert(short.MinValue <= input); + Debug.Assert(input <= short.MaxValue); + + return input; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long HighbdCheckRange(long input, int bd) + { + // For valid highbitdepth VP9 streams, intermediate stage coefficients will + // stay within the ranges: + // - 8 bit: signed 16 bit integer + // - 10 bit: signed 18 bit integer + // - 12 bit: signed 20 bit integer + int intMax = (1 << (7 + bd)) - 1; + int intMin = -intMax - 1; + Debug.Assert(intMin <= input); + Debug.Assert(input <= intMax); + + return input; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int WrapLow(long x) + { + return (short)CheckRange(x); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int HighbdWrapLow(long x, int bd) + { + return ((int)HighbdCheckRange(x, bd) << (24 - bd)) >> (24 - bd); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte ClipPixelAdd(byte dest, long trans) + { + trans = WrapLow(trans); + + return BitUtils.ClipPixel(dest + (int)trans); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort HighbdClipPixelAdd(ushort dest, long trans, int bd) + { + trans = HighbdWrapLow(trans, bd); + + return BitUtils.ClipPixelHighbd(dest + (int)trans, bd); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static long DctConstRoundShift(long input) + { + long rv = BitUtils.RoundPowerOfTwo(input, DctConstBits); + + return rv; + } + + [SkipLocalsInit] + public static void Iwht4x416Add(ReadOnlySpan input, Span dest, int stride) + { + /* 4-point reversible, orthonormal inverse Walsh-Hadamard in 3.5 adds, + 0.5 shifts per pixel. */ + int i; + Span output = stackalloc int[16]; + long a1, b1, c1, d1, e1; + ReadOnlySpan ip = input; + Span op = output; + + for (i = 0; i < 4; i++) + { + a1 = ip[0] >> UnitQuantShift; + c1 = ip[1] >> UnitQuantShift; + d1 = ip[2] >> UnitQuantShift; + b1 = ip[3] >> UnitQuantShift; + a1 += c1; + d1 -= b1; + e1 = (a1 - d1) >> 1; + b1 = e1 - b1; + c1 = e1 - c1; + a1 -= b1; + d1 += c1; + op[0] = WrapLow(a1); + op[1] = WrapLow(b1); + op[2] = WrapLow(c1); + op[3] = WrapLow(d1); + ip = ip[4..]; + op = op[4..]; + } + + Span ip2 = output; + for (i = 0; i < 4; i++) + { + a1 = ip2[4 * 0]; + c1 = ip2[4 * 1]; + d1 = ip2[4 * 2]; + b1 = ip2[4 * 3]; + a1 += c1; + d1 -= b1; + e1 = (a1 - d1) >> 1; + b1 = e1 - b1; + c1 = e1 - c1; + a1 -= b1; + d1 += c1; + dest[stride * 0] = ClipPixelAdd(dest[stride * 0], WrapLow(a1)); + dest[stride * 1] = ClipPixelAdd(dest[stride * 1], WrapLow(b1)); + dest[stride * 2] = ClipPixelAdd(dest[stride * 2], WrapLow(c1)); + dest[stride * 3] = ClipPixelAdd(dest[stride * 3], WrapLow(d1)); + + ip2 = ip2[1..]; + dest = dest[1..]; + } + } + + [SkipLocalsInit] + public static void Iwht4x41Add(ReadOnlySpan input, Span dest, int stride) + { + int i; + long a1, e1; + Span tmp = stackalloc int[4]; + ReadOnlySpan ip = input; + Span op = tmp; + + a1 = ip[0] >> UnitQuantShift; + e1 = a1 >> 1; + a1 -= e1; + op[0] = WrapLow(a1); + op[1] = op[2] = op[3] = WrapLow(e1); + + Span ip2 = tmp; + for (i = 0; i < 4; i++) + { + e1 = ip2[0] >> 1; + a1 = ip2[0] - e1; + dest[stride * 0] = ClipPixelAdd(dest[stride * 0], a1); + dest[stride * 1] = ClipPixelAdd(dest[stride * 1], e1); + dest[stride * 2] = ClipPixelAdd(dest[stride * 2], e1); + dest[stride * 3] = ClipPixelAdd(dest[stride * 3], e1); + ip2 = ip2[1..]; + dest = dest[1..]; + } + } + + public static void Iadst4(ReadOnlySpan input, Span output) + { + long s0, s1, s2, s3, s4, s5, s6, s7; + int x0 = input[0]; + int x1 = input[1]; + int x2 = input[2]; + int x3 = input[3]; + + if ((x0 | x1 | x2 | x3) == 0) + { + output[..4].Clear(); + + return; + } + + // 32-bit result is enough for the following multiplications. + s0 = SinPi1_9 * x0; + s1 = SinPi2_9 * x0; + s2 = SinPi3_9 * x1; + s3 = SinPi4_9 * x2; + s4 = SinPi1_9 * x2; + s5 = SinPi2_9 * x3; + s6 = SinPi4_9 * x3; + s7 = WrapLow(x0 - x2 + x3); + + s0 = s0 + s3 + s5; + s1 = s1 - s4 - s6; + s3 = s2; + s2 = SinPi3_9 * s7; + + // 1-D transform scaling factor is sqrt(2). + // The overall dynamic range is 14b (input) + 14b (multiplication scaling) + // + 1b (addition) = 29b. + // Hence the output bit depth is 15b. + output[0] = WrapLow(DctConstRoundShift(s0 + s3)); + output[1] = WrapLow(DctConstRoundShift(s1 + s3)); + output[2] = WrapLow(DctConstRoundShift(s2)); + output[3] = WrapLow(DctConstRoundShift(s0 + s1 - s3)); + } + + [SkipLocalsInit] + public static void Idct4(ReadOnlySpan input, Span output) + { + Span step = stackalloc short[4]; + long temp1, temp2; + + // stage 1 + temp1 = ((short)input[0] + (short)input[2]) * CosPi16_64; + temp2 = ((short)input[0] - (short)input[2]) * CosPi16_64; + step[0] = (short)WrapLow(DctConstRoundShift(temp1)); + step[1] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = (short)input[1] * CosPi24_64 - (short)input[3] * CosPi8_64; + temp2 = (short)input[1] * CosPi8_64 + (short)input[3] * CosPi24_64; + step[2] = (short)WrapLow(DctConstRoundShift(temp1)); + step[3] = (short)WrapLow(DctConstRoundShift(temp2)); + + // stage 2 + output[0] = WrapLow(step[0] + step[3]); + output[1] = WrapLow(step[1] + step[2]); + output[2] = WrapLow(step[1] - step[2]); + output[3] = WrapLow(step[0] - step[3]); + } + + [SkipLocalsInit] + public static void Idct4x416Add(ReadOnlySpan input, Span dest, int stride) + { + int i, j; + Span output = stackalloc int[4 * 4]; + Span outptr = output; + Span tempIn = stackalloc int[4]; + Span tempOut = stackalloc int[4]; + + // Rows + for (i = 0; i < 4; ++i) + { + Idct4(input, outptr); + input = input[4..]; + outptr = outptr[4..]; + } + + // Columns + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + tempIn[j] = output[j * 4 + i]; + } + + Idct4(tempIn, tempOut); + for (j = 0; j < 4; ++j) + { + dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 4)); + } + } + } + + public static void Idct4x41Add(ReadOnlySpan input, Span dest, int stride) + { + int i; + long a1; + int output = WrapLow(DctConstRoundShift((short)input[0] * CosPi16_64)); + + output = WrapLow(DctConstRoundShift(output * CosPi16_64)); + a1 = BitUtils.RoundPowerOfTwo(output, 4); + + for (i = 0; i < 4; i++) + { + dest[0] = ClipPixelAdd(dest[0], a1); + dest[1] = ClipPixelAdd(dest[1], a1); + dest[2] = ClipPixelAdd(dest[2], a1); + dest[3] = ClipPixelAdd(dest[3], a1); + dest = dest[stride..]; + } + } + + public static void Iadst8(ReadOnlySpan input, Span output) + { + int s0, s1, s2, s3, s4, s5, s6, s7; + long x0 = input[7]; + long x1 = input[0]; + long x2 = input[5]; + long x3 = input[2]; + long x4 = input[3]; + long x5 = input[4]; + long x6 = input[1]; + long x7 = input[6]; + + if ((x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7) == 0) + { + output[..8].Clear(); + + return; + } + + // stage 1 + s0 = (int)(CosPi2_64 * x0 + CosPi30_64 * x1); + s1 = (int)(CosPi30_64 * x0 - CosPi2_64 * x1); + s2 = (int)(CosPi10_64 * x2 + CosPi22_64 * x3); + s3 = (int)(CosPi22_64 * x2 - CosPi10_64 * x3); + s4 = (int)(CosPi18_64 * x4 + CosPi14_64 * x5); + s5 = (int)(CosPi14_64 * x4 - CosPi18_64 * x5); + s6 = (int)(CosPi26_64 * x6 + CosPi6_64 * x7); + s7 = (int)(CosPi6_64 * x6 - CosPi26_64 * x7); + + x0 = WrapLow(DctConstRoundShift(s0 + s4)); + x1 = WrapLow(DctConstRoundShift(s1 + s5)); + x2 = WrapLow(DctConstRoundShift(s2 + s6)); + x3 = WrapLow(DctConstRoundShift(s3 + s7)); + x4 = WrapLow(DctConstRoundShift(s0 - s4)); + x5 = WrapLow(DctConstRoundShift(s1 - s5)); + x6 = WrapLow(DctConstRoundShift(s2 - s6)); + x7 = WrapLow(DctConstRoundShift(s3 - s7)); + + // stage 2 + s0 = (int)x0; + s1 = (int)x1; + s2 = (int)x2; + s3 = (int)x3; + s4 = (int)(CosPi8_64 * x4 + CosPi24_64 * x5); + s5 = (int)(CosPi24_64 * x4 - CosPi8_64 * x5); + s6 = (int)(-CosPi24_64 * x6 + CosPi8_64 * x7); + s7 = (int)(CosPi8_64 * x6 + CosPi24_64 * x7); + + x0 = WrapLow(s0 + s2); + x1 = WrapLow(s1 + s3); + x2 = WrapLow(s0 - s2); + x3 = WrapLow(s1 - s3); + x4 = WrapLow(DctConstRoundShift(s4 + s6)); + x5 = WrapLow(DctConstRoundShift(s5 + s7)); + x6 = WrapLow(DctConstRoundShift(s4 - s6)); + x7 = WrapLow(DctConstRoundShift(s5 - s7)); + + // stage 3 + s2 = (int)(CosPi16_64 * (x2 + x3)); + s3 = (int)(CosPi16_64 * (x2 - x3)); + s6 = (int)(CosPi16_64 * (x6 + x7)); + s7 = (int)(CosPi16_64 * (x6 - x7)); + + x2 = WrapLow(DctConstRoundShift(s2)); + x3 = WrapLow(DctConstRoundShift(s3)); + x6 = WrapLow(DctConstRoundShift(s6)); + x7 = WrapLow(DctConstRoundShift(s7)); + + output[0] = WrapLow(x0); + output[1] = WrapLow(-x4); + output[2] = WrapLow(x6); + output[3] = WrapLow(-x2); + output[4] = WrapLow(x3); + output[5] = WrapLow(-x7); + output[6] = WrapLow(x5); + output[7] = WrapLow(-x1); + } + + [SkipLocalsInit] + public static void Idct8(ReadOnlySpan input, Span output) + { + Span step1 = stackalloc short[8]; + Span step2 = stackalloc short[8]; + long temp1, temp2; + + // stage 1 + step1[0] = (short)input[0]; + step1[2] = (short)input[4]; + step1[1] = (short)input[2]; + step1[3] = (short)input[6]; + temp1 = (short)input[1] * CosPi28_64 - (short)input[7] * CosPi4_64; + temp2 = (short)input[1] * CosPi4_64 + (short)input[7] * CosPi28_64; + step1[4] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[7] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = (short)input[5] * CosPi12_64 - (short)input[3] * CosPi20_64; + temp2 = (short)input[5] * CosPi20_64 + (short)input[3] * CosPi12_64; + step1[5] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[6] = (short)WrapLow(DctConstRoundShift(temp2)); + + // stage 2 + temp1 = (step1[0] + step1[2]) * CosPi16_64; + temp2 = (step1[0] - step1[2]) * CosPi16_64; + step2[0] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[1] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = step1[1] * CosPi24_64 - step1[3] * CosPi8_64; + temp2 = step1[1] * CosPi8_64 + step1[3] * CosPi24_64; + step2[2] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[3] = (short)WrapLow(DctConstRoundShift(temp2)); + step2[4] = (short)WrapLow(step1[4] + step1[5]); + step2[5] = (short)WrapLow(step1[4] - step1[5]); + step2[6] = (short)WrapLow(-step1[6] + step1[7]); + step2[7] = (short)WrapLow(step1[6] + step1[7]); + + // stage 3 + step1[0] = (short)WrapLow(step2[0] + step2[3]); + step1[1] = (short)WrapLow(step2[1] + step2[2]); + step1[2] = (short)WrapLow(step2[1] - step2[2]); + step1[3] = (short)WrapLow(step2[0] - step2[3]); + step1[4] = step2[4]; + temp1 = (step2[6] - step2[5]) * CosPi16_64; + temp2 = (step2[5] + step2[6]) * CosPi16_64; + step1[5] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[6] = (short)WrapLow(DctConstRoundShift(temp2)); + step1[7] = step2[7]; + + // stage 4 + output[0] = WrapLow(step1[0] + step1[7]); + output[1] = WrapLow(step1[1] + step1[6]); + output[2] = WrapLow(step1[2] + step1[5]); + output[3] = WrapLow(step1[3] + step1[4]); + output[4] = WrapLow(step1[3] - step1[4]); + output[5] = WrapLow(step1[2] - step1[5]); + output[6] = WrapLow(step1[1] - step1[6]); + output[7] = WrapLow(step1[0] - step1[7]); + } + + [SkipLocalsInit] + public static void Idct8x864Add(ReadOnlySpan input, Span dest, int stride) + { + int i, j; + Span output = stackalloc int[8 * 8]; + Span outptr = output; + Span tempIn = stackalloc int[8]; + Span tempOut = stackalloc int[8]; + + // First transform rows + for (i = 0; i < 8; ++i) + { + Idct8(input, outptr); + input = input[8..]; + outptr = outptr[8..]; + } + + // Then transform columns + for (i = 0; i < 8; ++i) + { + for (j = 0; j < 8; ++j) + { + tempIn[j] = output[j * 8 + i]; + } + + Idct8(tempIn, tempOut); + for (j = 0; j < 8; ++j) + { + dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], + BitUtils.RoundPowerOfTwo(tempOut[j], 5)); + } + } + } + + [SkipLocalsInit] + public static void Idct8x812Add(ReadOnlySpan input, Span dest, int stride) + { + int i, j; + Span output = stackalloc int[8 * 8]; + Span outptr = output; + Span tempIn = stackalloc int[8]; + Span tempOut = stackalloc int[8]; + + output.Clear(); + + // First transform rows + // Only first 4 row has non-zero coefs + for (i = 0; i < 4; ++i) + { + Idct8(input, outptr); + input = input[8..]; + outptr = outptr[8..]; + } + + // Then transform columns + for (i = 0; i < 8; ++i) + { + for (j = 0; j < 8; ++j) + { + tempIn[j] = output[j * 8 + i]; + } + + Idct8(tempIn, tempOut); + for (j = 0; j < 8; ++j) + { + dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 5)); + } + } + } + + public static void Idct8x81Add(ReadOnlySpan input, Span dest, int stride) + { + int i, j; + long a1; + int output = WrapLow(DctConstRoundShift((short)input[0] * CosPi16_64)); + + output = WrapLow(DctConstRoundShift(output * CosPi16_64)); + a1 = BitUtils.RoundPowerOfTwo(output, 5); + for (j = 0; j < 8; ++j) + { + for (i = 0; i < 8; ++i) + { + dest[i] = ClipPixelAdd(dest[i], a1); + } + + dest = dest[stride..]; + } + } + + public static void Iadst16(ReadOnlySpan input, Span output) + { + long s0, s1, s2, s3, s4, s5, s6, s7, s8; + long s9, s10, s11, s12, s13, s14, s15; + long x0 = input[15]; + long x1 = input[0]; + long x2 = input[13]; + long x3 = input[2]; + long x4 = input[11]; + long x5 = input[4]; + long x6 = input[9]; + long x7 = input[6]; + long x8 = input[7]; + long x9 = input[8]; + long x10 = input[5]; + long x11 = input[10]; + long x12 = input[3]; + long x13 = input[12]; + long x14 = input[1]; + long x15 = input[14]; + + if ((x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | x10 | x11 | x12 | x13 | x14 | x15) == 0) + { + output[..16].Clear(); + + return; + } + + // stage 1 + s0 = x0 * CosPi1_64 + x1 * CosPi31_64; + s1 = x0 * CosPi31_64 - x1 * CosPi1_64; + s2 = x2 * CosPi5_64 + x3 * CosPi27_64; + s3 = x2 * CosPi27_64 - x3 * CosPi5_64; + s4 = x4 * CosPi9_64 + x5 * CosPi23_64; + s5 = x4 * CosPi23_64 - x5 * CosPi9_64; + s6 = x6 * CosPi13_64 + x7 * CosPi19_64; + s7 = x6 * CosPi19_64 - x7 * CosPi13_64; + s8 = x8 * CosPi17_64 + x9 * CosPi15_64; + s9 = x8 * CosPi15_64 - x9 * CosPi17_64; + s10 = x10 * CosPi21_64 + x11 * CosPi11_64; + s11 = x10 * CosPi11_64 - x11 * CosPi21_64; + s12 = x12 * CosPi25_64 + x13 * CosPi7_64; + s13 = x12 * CosPi7_64 - x13 * CosPi25_64; + s14 = x14 * CosPi29_64 + x15 * CosPi3_64; + s15 = x14 * CosPi3_64 - x15 * CosPi29_64; + + x0 = WrapLow(DctConstRoundShift(s0 + s8)); + x1 = WrapLow(DctConstRoundShift(s1 + s9)); + x2 = WrapLow(DctConstRoundShift(s2 + s10)); + x3 = WrapLow(DctConstRoundShift(s3 + s11)); + x4 = WrapLow(DctConstRoundShift(s4 + s12)); + x5 = WrapLow(DctConstRoundShift(s5 + s13)); + x6 = WrapLow(DctConstRoundShift(s6 + s14)); + x7 = WrapLow(DctConstRoundShift(s7 + s15)); + x8 = WrapLow(DctConstRoundShift(s0 - s8)); + x9 = WrapLow(DctConstRoundShift(s1 - s9)); + x10 = WrapLow(DctConstRoundShift(s2 - s10)); + x11 = WrapLow(DctConstRoundShift(s3 - s11)); + x12 = WrapLow(DctConstRoundShift(s4 - s12)); + x13 = WrapLow(DctConstRoundShift(s5 - s13)); + x14 = WrapLow(DctConstRoundShift(s6 - s14)); + x15 = WrapLow(DctConstRoundShift(s7 - s15)); + + // stage 2 + s0 = x0; + s1 = x1; + s2 = x2; + s3 = x3; + s4 = x4; + s5 = x5; + s6 = x6; + s7 = x7; + s8 = x8 * CosPi4_64 + x9 * CosPi28_64; + s9 = x8 * CosPi28_64 - x9 * CosPi4_64; + s10 = x10 * CosPi20_64 + x11 * CosPi12_64; + s11 = x10 * CosPi12_64 - x11 * CosPi20_64; + s12 = -x12 * CosPi28_64 + x13 * CosPi4_64; + s13 = x12 * CosPi4_64 + x13 * CosPi28_64; + s14 = -x14 * CosPi12_64 + x15 * CosPi20_64; + s15 = x14 * CosPi20_64 + x15 * CosPi12_64; + + x0 = WrapLow(s0 + s4); + x1 = WrapLow(s1 + s5); + x2 = WrapLow(s2 + s6); + x3 = WrapLow(s3 + s7); + x4 = WrapLow(s0 - s4); + x5 = WrapLow(s1 - s5); + x6 = WrapLow(s2 - s6); + x7 = WrapLow(s3 - s7); + x8 = WrapLow(DctConstRoundShift(s8 + s12)); + x9 = WrapLow(DctConstRoundShift(s9 + s13)); + x10 = WrapLow(DctConstRoundShift(s10 + s14)); + x11 = WrapLow(DctConstRoundShift(s11 + s15)); + x12 = WrapLow(DctConstRoundShift(s8 - s12)); + x13 = WrapLow(DctConstRoundShift(s9 - s13)); + x14 = WrapLow(DctConstRoundShift(s10 - s14)); + x15 = WrapLow(DctConstRoundShift(s11 - s15)); + + // stage 3 + s0 = x0; + s1 = x1; + s2 = x2; + s3 = x3; + s4 = x4 * CosPi8_64 + x5 * CosPi24_64; + s5 = x4 * CosPi24_64 - x5 * CosPi8_64; + s6 = -x6 * CosPi24_64 + x7 * CosPi8_64; + s7 = x6 * CosPi8_64 + x7 * CosPi24_64; + s8 = x8; + s9 = x9; + s10 = x10; + s11 = x11; + s12 = x12 * CosPi8_64 + x13 * CosPi24_64; + s13 = x12 * CosPi24_64 - x13 * CosPi8_64; + s14 = -x14 * CosPi24_64 + x15 * CosPi8_64; + s15 = x14 * CosPi8_64 + x15 * CosPi24_64; + + x0 = WrapLow(s0 + s2); + x1 = WrapLow(s1 + s3); + x2 = WrapLow(s0 - s2); + x3 = WrapLow(s1 - s3); + x4 = WrapLow(DctConstRoundShift(s4 + s6)); + x5 = WrapLow(DctConstRoundShift(s5 + s7)); + x6 = WrapLow(DctConstRoundShift(s4 - s6)); + x7 = WrapLow(DctConstRoundShift(s5 - s7)); + x8 = WrapLow(s8 + s10); + x9 = WrapLow(s9 + s11); + x10 = WrapLow(s8 - s10); + x11 = WrapLow(s9 - s11); + x12 = WrapLow(DctConstRoundShift(s12 + s14)); + x13 = WrapLow(DctConstRoundShift(s13 + s15)); + x14 = WrapLow(DctConstRoundShift(s12 - s14)); + x15 = WrapLow(DctConstRoundShift(s13 - s15)); + + // stage 4 + s2 = (-CosPi16_64) * (x2 + x3); + s3 = CosPi16_64 * (x2 - x3); + s6 = CosPi16_64 * (x6 + x7); + s7 = CosPi16_64 * (-x6 + x7); + s10 = CosPi16_64 * (x10 + x11); + s11 = CosPi16_64 * (-x10 + x11); + s14 = (-CosPi16_64) * (x14 + x15); + s15 = CosPi16_64 * (x14 - x15); + + x2 = WrapLow(DctConstRoundShift(s2)); + x3 = WrapLow(DctConstRoundShift(s3)); + x6 = WrapLow(DctConstRoundShift(s6)); + x7 = WrapLow(DctConstRoundShift(s7)); + x10 = WrapLow(DctConstRoundShift(s10)); + x11 = WrapLow(DctConstRoundShift(s11)); + x14 = WrapLow(DctConstRoundShift(s14)); + x15 = WrapLow(DctConstRoundShift(s15)); + + output[0] = WrapLow(x0); + output[1] = WrapLow(-x8); + output[2] = WrapLow(x12); + output[3] = WrapLow(-x4); + output[4] = WrapLow(x6); + output[5] = WrapLow(x14); + output[6] = WrapLow(x10); + output[7] = WrapLow(x2); + output[8] = WrapLow(x3); + output[9] = WrapLow(x11); + output[10] = WrapLow(x15); + output[11] = WrapLow(x7); + output[12] = WrapLow(x5); + output[13] = WrapLow(-x13); + output[14] = WrapLow(x9); + output[15] = WrapLow(-x1); + } + + [SkipLocalsInit] + public static void Idct16(ReadOnlySpan input, Span output) + { + Span step1 = stackalloc short[16]; + Span step2 = stackalloc short[16]; + long temp1, temp2; + + // stage 1 + step1[0] = (short)input[0 / 2]; + step1[1] = (short)input[16 / 2]; + step1[2] = (short)input[8 / 2]; + step1[3] = (short)input[24 / 2]; + step1[4] = (short)input[4 / 2]; + step1[5] = (short)input[20 / 2]; + step1[6] = (short)input[12 / 2]; + step1[7] = (short)input[28 / 2]; + step1[8] = (short)input[2 / 2]; + step1[9] = (short)input[18 / 2]; + step1[10] = (short)input[10 / 2]; + step1[11] = (short)input[26 / 2]; + step1[12] = (short)input[6 / 2]; + step1[13] = (short)input[22 / 2]; + step1[14] = (short)input[14 / 2]; + step1[15] = (short)input[30 / 2]; + + // stage 2 + step2[0] = step1[0]; + step2[1] = step1[1]; + step2[2] = step1[2]; + step2[3] = step1[3]; + step2[4] = step1[4]; + step2[5] = step1[5]; + step2[6] = step1[6]; + step2[7] = step1[7]; + + temp1 = step1[8] * CosPi30_64 - step1[15] * CosPi2_64; + temp2 = step1[8] * CosPi2_64 + step1[15] * CosPi30_64; + step2[8] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[15] = (short)WrapLow(DctConstRoundShift(temp2)); + + temp1 = step1[9] * CosPi14_64 - step1[14] * CosPi18_64; + temp2 = step1[9] * CosPi18_64 + step1[14] * CosPi14_64; + step2[9] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[14] = (short)WrapLow(DctConstRoundShift(temp2)); + + temp1 = step1[10] * CosPi22_64 - step1[13] * CosPi10_64; + temp2 = step1[10] * CosPi10_64 + step1[13] * CosPi22_64; + step2[10] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[13] = (short)WrapLow(DctConstRoundShift(temp2)); + + temp1 = step1[11] * CosPi6_64 - step1[12] * CosPi26_64; + temp2 = step1[11] * CosPi26_64 + step1[12] * CosPi6_64; + step2[11] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[12] = (short)WrapLow(DctConstRoundShift(temp2)); + + // stage 3 + step1[0] = step2[0]; + step1[1] = step2[1]; + step1[2] = step2[2]; + step1[3] = step2[3]; + + temp1 = step2[4] * CosPi28_64 - step2[7] * CosPi4_64; + temp2 = step2[4] * CosPi4_64 + step2[7] * CosPi28_64; + step1[4] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[7] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = step2[5] * CosPi12_64 - step2[6] * CosPi20_64; + temp2 = step2[5] * CosPi20_64 + step2[6] * CosPi12_64; + step1[5] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[6] = (short)WrapLow(DctConstRoundShift(temp2)); + + step1[8] = (short)WrapLow(step2[8] + step2[9]); + step1[9] = (short)WrapLow(step2[8] - step2[9]); + step1[10] = (short)WrapLow(-step2[10] + step2[11]); + step1[11] = (short)WrapLow(step2[10] + step2[11]); + step1[12] = (short)WrapLow(step2[12] + step2[13]); + step1[13] = (short)WrapLow(step2[12] - step2[13]); + step1[14] = (short)WrapLow(-step2[14] + step2[15]); + step1[15] = (short)WrapLow(step2[14] + step2[15]); + + // stage 4 + temp1 = (step1[0] + step1[1]) * CosPi16_64; + temp2 = (step1[0] - step1[1]) * CosPi16_64; + step2[0] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[1] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = step1[2] * CosPi24_64 - step1[3] * CosPi8_64; + temp2 = step1[2] * CosPi8_64 + step1[3] * CosPi24_64; + step2[2] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[3] = (short)WrapLow(DctConstRoundShift(temp2)); + step2[4] = (short)WrapLow(step1[4] + step1[5]); + step2[5] = (short)WrapLow(step1[4] - step1[5]); + step2[6] = (short)WrapLow(-step1[6] + step1[7]); + step2[7] = (short)WrapLow(step1[6] + step1[7]); + + step2[8] = step1[8]; + step2[15] = step1[15]; + temp1 = -step1[9] * CosPi8_64 + step1[14] * CosPi24_64; + temp2 = step1[9] * CosPi24_64 + step1[14] * CosPi8_64; + step2[9] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[14] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = -step1[10] * CosPi24_64 - step1[13] * CosPi8_64; + temp2 = -step1[10] * CosPi8_64 + step1[13] * CosPi24_64; + step2[10] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[13] = (short)WrapLow(DctConstRoundShift(temp2)); + step2[11] = step1[11]; + step2[12] = step1[12]; + + // stage 5 + step1[0] = (short)WrapLow(step2[0] + step2[3]); + step1[1] = (short)WrapLow(step2[1] + step2[2]); + step1[2] = (short)WrapLow(step2[1] - step2[2]); + step1[3] = (short)WrapLow(step2[0] - step2[3]); + step1[4] = step2[4]; + temp1 = (step2[6] - step2[5]) * CosPi16_64; + temp2 = (step2[5] + step2[6]) * CosPi16_64; + step1[5] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[6] = (short)WrapLow(DctConstRoundShift(temp2)); + step1[7] = step2[7]; + + step1[8] = (short)WrapLow(step2[8] + step2[11]); + step1[9] = (short)WrapLow(step2[9] + step2[10]); + step1[10] = (short)WrapLow(step2[9] - step2[10]); + step1[11] = (short)WrapLow(step2[8] - step2[11]); + step1[12] = (short)WrapLow(-step2[12] + step2[15]); + step1[13] = (short)WrapLow(-step2[13] + step2[14]); + step1[14] = (short)WrapLow(step2[13] + step2[14]); + step1[15] = (short)WrapLow(step2[12] + step2[15]); + + // stage 6 + step2[0] = (short)WrapLow(step1[0] + step1[7]); + step2[1] = (short)WrapLow(step1[1] + step1[6]); + step2[2] = (short)WrapLow(step1[2] + step1[5]); + step2[3] = (short)WrapLow(step1[3] + step1[4]); + step2[4] = (short)WrapLow(step1[3] - step1[4]); + step2[5] = (short)WrapLow(step1[2] - step1[5]); + step2[6] = (short)WrapLow(step1[1] - step1[6]); + step2[7] = (short)WrapLow(step1[0] - step1[7]); + step2[8] = step1[8]; + step2[9] = step1[9]; + temp1 = (-step1[10] + step1[13]) * CosPi16_64; + temp2 = (step1[10] + step1[13]) * CosPi16_64; + step2[10] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[13] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = (-step1[11] + step1[12]) * CosPi16_64; + temp2 = (step1[11] + step1[12]) * CosPi16_64; + step2[11] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[12] = (short)WrapLow(DctConstRoundShift(temp2)); + step2[14] = step1[14]; + step2[15] = step1[15]; + + // stage 7 + output[0] = WrapLow(step2[0] + step2[15]); + output[1] = WrapLow(step2[1] + step2[14]); + output[2] = WrapLow(step2[2] + step2[13]); + output[3] = WrapLow(step2[3] + step2[12]); + output[4] = WrapLow(step2[4] + step2[11]); + output[5] = WrapLow(step2[5] + step2[10]); + output[6] = WrapLow(step2[6] + step2[9]); + output[7] = WrapLow(step2[7] + step2[8]); + output[8] = WrapLow(step2[7] - step2[8]); + output[9] = WrapLow(step2[6] - step2[9]); + output[10] = WrapLow(step2[5] - step2[10]); + output[11] = WrapLow(step2[4] - step2[11]); + output[12] = WrapLow(step2[3] - step2[12]); + output[13] = WrapLow(step2[2] - step2[13]); + output[14] = WrapLow(step2[1] - step2[14]); + output[15] = WrapLow(step2[0] - step2[15]); + } + + [SkipLocalsInit] + public static void Idct16x16256Add(ReadOnlySpan input, Span dest, int stride) + { + int i, j; + Span output = stackalloc int[16 * 16]; + Span outptr = output; + Span tempIn = stackalloc int[16]; + Span tempOut = stackalloc int[16]; + + // First transform rows + for (i = 0; i < 16; ++i) + { + Idct16(input, outptr); + input = input[16..]; + outptr = outptr[16..]; + } + + // Then transform columns + for (i = 0; i < 16; ++i) + { + for (j = 0; j < 16; ++j) + { + tempIn[j] = output[j * 16 + i]; + } + + Idct16(tempIn, tempOut); + for (j = 0; j < 16; ++j) + { + dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 6)); + } + } + } + + [SkipLocalsInit] + public static void Idct16x1638Add(ReadOnlySpan input, Span dest, int stride) + { + int i, j; + Span output = stackalloc int[16 * 16]; + Span outptr = output; + Span tempIn = stackalloc int[16]; + Span tempOut = stackalloc int[16]; + + output.Clear(); + + // First transform rows. Since all non-zero dct coefficients are in + // upper-left 8x8 area, we only need to calculate first 8 rows here. + for (i = 0; i < 8; ++i) + { + Idct16(input, outptr); + input = input[16..]; + outptr = outptr[16..]; + } + + // Then transform columns + for (i = 0; i < 16; ++i) + { + for (j = 0; j < 16; ++j) + { + tempIn[j] = output[j * 16 + i]; + } + + Idct16(tempIn, tempOut); + for (j = 0; j < 16; ++j) + { + dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 6)); + } + } + } + + [SkipLocalsInit] + public static void Idct16x1610Add(ReadOnlySpan input, Span dest, int stride) + { + int i, j; + Span output = stackalloc int[16 * 16]; + Span outptr = output; + Span tempIn = stackalloc int[16]; + Span tempOut = stackalloc int[16]; + + output.Clear(); + + // First transform rows. Since all non-zero dct coefficients are in + // upper-left 4x4 area, we only need to calculate first 4 rows here. + for (i = 0; i < 4; ++i) + { + Idct16(input, outptr); + input = input[16..]; + outptr = outptr[16..]; + } + + // Then transform columns + for (i = 0; i < 16; ++i) + { + for (j = 0; j < 16; ++j) + { + tempIn[j] = output[j * 16 + i]; + } + + Idct16(tempIn, tempOut); + for (j = 0; j < 16; ++j) + { + dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 6)); + } + } + } + + public static void Idct16x161Add(ReadOnlySpan input, Span dest, int stride) + { + int i, j; + long a1; + int output = WrapLow(DctConstRoundShift((short)input[0] * CosPi16_64)); + + output = WrapLow(DctConstRoundShift(output * CosPi16_64)); + a1 = BitUtils.RoundPowerOfTwo(output, 6); + for (j = 0; j < 16; ++j) + { + for (i = 0; i < 16; ++i) + { + dest[i] = ClipPixelAdd(dest[i], a1); + } + + dest = dest[stride..]; + } + } + + [SkipLocalsInit] + public static void Idct32(ReadOnlySpan input, Span output) + { + Span step1 = stackalloc short[32]; + Span step2 = stackalloc short[32]; + long temp1, temp2; + + // stage 1 + step1[0] = (short)input[0]; + step1[1] = (short)input[16]; + step1[2] = (short)input[8]; + step1[3] = (short)input[24]; + step1[4] = (short)input[4]; + step1[5] = (short)input[20]; + step1[6] = (short)input[12]; + step1[7] = (short)input[28]; + step1[8] = (short)input[2]; + step1[9] = (short)input[18]; + step1[10] = (short)input[10]; + step1[11] = (short)input[26]; + step1[12] = (short)input[6]; + step1[13] = (short)input[22]; + step1[14] = (short)input[14]; + step1[15] = (short)input[30]; + + temp1 = (short)input[1] * CosPi31_64 - (short)input[31] * CosPi1_64; + temp2 = (short)input[1] * CosPi1_64 + (short)input[31] * CosPi31_64; + step1[16] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[31] = (short)WrapLow(DctConstRoundShift(temp2)); + + temp1 = (short)input[17] * CosPi15_64 - (short)input[15] * CosPi17_64; + temp2 = (short)input[17] * CosPi17_64 + (short)input[15] * CosPi15_64; + step1[17] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[30] = (short)WrapLow(DctConstRoundShift(temp2)); + + temp1 = (short)input[9] * CosPi23_64 - (short)input[23] * CosPi9_64; + temp2 = (short)input[9] * CosPi9_64 + (short)input[23] * CosPi23_64; + step1[18] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[29] = (short)WrapLow(DctConstRoundShift(temp2)); + + temp1 = (short)input[25] * CosPi7_64 - (short)input[7] * CosPi25_64; + temp2 = (short)input[25] * CosPi25_64 + (short)input[7] * CosPi7_64; + step1[19] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[28] = (short)WrapLow(DctConstRoundShift(temp2)); + + temp1 = (short)input[5] * CosPi27_64 - (short)input[27] * CosPi5_64; + temp2 = (short)input[5] * CosPi5_64 + (short)input[27] * CosPi27_64; + step1[20] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[27] = (short)WrapLow(DctConstRoundShift(temp2)); + + temp1 = (short)input[21] * CosPi11_64 - (short)input[11] * CosPi21_64; + temp2 = (short)input[21] * CosPi21_64 + (short)input[11] * CosPi11_64; + step1[21] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[26] = (short)WrapLow(DctConstRoundShift(temp2)); + + temp1 = (short)input[13] * CosPi19_64 - (short)input[19] * CosPi13_64; + temp2 = (short)input[13] * CosPi13_64 + (short)input[19] * CosPi19_64; + step1[22] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[25] = (short)WrapLow(DctConstRoundShift(temp2)); + + temp1 = (short)input[29] * CosPi3_64 - (short)input[3] * CosPi29_64; + temp2 = (short)input[29] * CosPi29_64 + (short)input[3] * CosPi3_64; + step1[23] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[24] = (short)WrapLow(DctConstRoundShift(temp2)); + + // stage 2 + step2[0] = step1[0]; + step2[1] = step1[1]; + step2[2] = step1[2]; + step2[3] = step1[3]; + step2[4] = step1[4]; + step2[5] = step1[5]; + step2[6] = step1[6]; + step2[7] = step1[7]; + + temp1 = step1[8] * CosPi30_64 - step1[15] * CosPi2_64; + temp2 = step1[8] * CosPi2_64 + step1[15] * CosPi30_64; + step2[8] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[15] = (short)WrapLow(DctConstRoundShift(temp2)); + + temp1 = step1[9] * CosPi14_64 - step1[14] * CosPi18_64; + temp2 = step1[9] * CosPi18_64 + step1[14] * CosPi14_64; + step2[9] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[14] = (short)WrapLow(DctConstRoundShift(temp2)); + + temp1 = step1[10] * CosPi22_64 - step1[13] * CosPi10_64; + temp2 = step1[10] * CosPi10_64 + step1[13] * CosPi22_64; + step2[10] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[13] = (short)WrapLow(DctConstRoundShift(temp2)); + + temp1 = step1[11] * CosPi6_64 - step1[12] * CosPi26_64; + temp2 = step1[11] * CosPi26_64 + step1[12] * CosPi6_64; + step2[11] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[12] = (short)WrapLow(DctConstRoundShift(temp2)); + + step2[16] = (short)WrapLow(step1[16] + step1[17]); + step2[17] = (short)WrapLow(step1[16] - step1[17]); + step2[18] = (short)WrapLow(-step1[18] + step1[19]); + step2[19] = (short)WrapLow(step1[18] + step1[19]); + step2[20] = (short)WrapLow(step1[20] + step1[21]); + step2[21] = (short)WrapLow(step1[20] - step1[21]); + step2[22] = (short)WrapLow(-step1[22] + step1[23]); + step2[23] = (short)WrapLow(step1[22] + step1[23]); + step2[24] = (short)WrapLow(step1[24] + step1[25]); + step2[25] = (short)WrapLow(step1[24] - step1[25]); + step2[26] = (short)WrapLow(-step1[26] + step1[27]); + step2[27] = (short)WrapLow(step1[26] + step1[27]); + step2[28] = (short)WrapLow(step1[28] + step1[29]); + step2[29] = (short)WrapLow(step1[28] - step1[29]); + step2[30] = (short)WrapLow(-step1[30] + step1[31]); + step2[31] = (short)WrapLow(step1[30] + step1[31]); + + // stage 3 + step1[0] = step2[0]; + step1[1] = step2[1]; + step1[2] = step2[2]; + step1[3] = step2[3]; + + temp1 = step2[4] * CosPi28_64 - step2[7] * CosPi4_64; + temp2 = step2[4] * CosPi4_64 + step2[7] * CosPi28_64; + step1[4] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[7] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = step2[5] * CosPi12_64 - step2[6] * CosPi20_64; + temp2 = step2[5] * CosPi20_64 + step2[6] * CosPi12_64; + step1[5] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[6] = (short)WrapLow(DctConstRoundShift(temp2)); + + step1[8] = (short)WrapLow(step2[8] + step2[9]); + step1[9] = (short)WrapLow(step2[8] - step2[9]); + step1[10] = (short)WrapLow(-step2[10] + step2[11]); + step1[11] = (short)WrapLow(step2[10] + step2[11]); + step1[12] = (short)WrapLow(step2[12] + step2[13]); + step1[13] = (short)WrapLow(step2[12] - step2[13]); + step1[14] = (short)WrapLow(-step2[14] + step2[15]); + step1[15] = (short)WrapLow(step2[14] + step2[15]); + + step1[16] = step2[16]; + step1[31] = step2[31]; + temp1 = -step2[17] * CosPi4_64 + step2[30] * CosPi28_64; + temp2 = step2[17] * CosPi28_64 + step2[30] * CosPi4_64; + step1[17] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[30] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = -step2[18] * CosPi28_64 - step2[29] * CosPi4_64; + temp2 = -step2[18] * CosPi4_64 + step2[29] * CosPi28_64; + step1[18] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[29] = (short)WrapLow(DctConstRoundShift(temp2)); + step1[19] = step2[19]; + step1[20] = step2[20]; + temp1 = -step2[21] * CosPi20_64 + step2[26] * CosPi12_64; + temp2 = step2[21] * CosPi12_64 + step2[26] * CosPi20_64; + step1[21] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[26] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = -step2[22] * CosPi12_64 - step2[25] * CosPi20_64; + temp2 = -step2[22] * CosPi20_64 + step2[25] * CosPi12_64; + step1[22] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[25] = (short)WrapLow(DctConstRoundShift(temp2)); + step1[23] = step2[23]; + step1[24] = step2[24]; + step1[27] = step2[27]; + step1[28] = step2[28]; + + // stage 4 + temp1 = (step1[0] + step1[1]) * CosPi16_64; + temp2 = (step1[0] - step1[1]) * CosPi16_64; + step2[0] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[1] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = step1[2] * CosPi24_64 - step1[3] * CosPi8_64; + temp2 = step1[2] * CosPi8_64 + step1[3] * CosPi24_64; + step2[2] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[3] = (short)WrapLow(DctConstRoundShift(temp2)); + step2[4] = (short)WrapLow(step1[4] + step1[5]); + step2[5] = (short)WrapLow(step1[4] - step1[5]); + step2[6] = (short)WrapLow(-step1[6] + step1[7]); + step2[7] = (short)WrapLow(step1[6] + step1[7]); + + step2[8] = step1[8]; + step2[15] = step1[15]; + temp1 = -step1[9] * CosPi8_64 + step1[14] * CosPi24_64; + temp2 = step1[9] * CosPi24_64 + step1[14] * CosPi8_64; + step2[9] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[14] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = -step1[10] * CosPi24_64 - step1[13] * CosPi8_64; + temp2 = -step1[10] * CosPi8_64 + step1[13] * CosPi24_64; + step2[10] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[13] = (short)WrapLow(DctConstRoundShift(temp2)); + step2[11] = step1[11]; + step2[12] = step1[12]; + + step2[16] = (short)WrapLow(step1[16] + step1[19]); + step2[17] = (short)WrapLow(step1[17] + step1[18]); + step2[18] = (short)WrapLow(step1[17] - step1[18]); + step2[19] = (short)WrapLow(step1[16] - step1[19]); + step2[20] = (short)WrapLow(-step1[20] + step1[23]); + step2[21] = (short)WrapLow(-step1[21] + step1[22]); + step2[22] = (short)WrapLow(step1[21] + step1[22]); + step2[23] = (short)WrapLow(step1[20] + step1[23]); + + step2[24] = (short)WrapLow(step1[24] + step1[27]); + step2[25] = (short)WrapLow(step1[25] + step1[26]); + step2[26] = (short)WrapLow(step1[25] - step1[26]); + step2[27] = (short)WrapLow(step1[24] - step1[27]); + step2[28] = (short)WrapLow(-step1[28] + step1[31]); + step2[29] = (short)WrapLow(-step1[29] + step1[30]); + step2[30] = (short)WrapLow(step1[29] + step1[30]); + step2[31] = (short)WrapLow(step1[28] + step1[31]); + + // stage 5 + step1[0] = (short)WrapLow(step2[0] + step2[3]); + step1[1] = (short)WrapLow(step2[1] + step2[2]); + step1[2] = (short)WrapLow(step2[1] - step2[2]); + step1[3] = (short)WrapLow(step2[0] - step2[3]); + step1[4] = step2[4]; + temp1 = (step2[6] - step2[5]) * CosPi16_64; + temp2 = (step2[5] + step2[6]) * CosPi16_64; + step1[5] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[6] = (short)WrapLow(DctConstRoundShift(temp2)); + step1[7] = step2[7]; + + step1[8] = (short)WrapLow(step2[8] + step2[11]); + step1[9] = (short)WrapLow(step2[9] + step2[10]); + step1[10] = (short)WrapLow(step2[9] - step2[10]); + step1[11] = (short)WrapLow(step2[8] - step2[11]); + step1[12] = (short)WrapLow(-step2[12] + step2[15]); + step1[13] = (short)WrapLow(-step2[13] + step2[14]); + step1[14] = (short)WrapLow(step2[13] + step2[14]); + step1[15] = (short)WrapLow(step2[12] + step2[15]); + + step1[16] = step2[16]; + step1[17] = step2[17]; + temp1 = -step2[18] * CosPi8_64 + step2[29] * CosPi24_64; + temp2 = step2[18] * CosPi24_64 + step2[29] * CosPi8_64; + step1[18] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[29] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = -step2[19] * CosPi8_64 + step2[28] * CosPi24_64; + temp2 = step2[19] * CosPi24_64 + step2[28] * CosPi8_64; + step1[19] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[28] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = -step2[20] * CosPi24_64 - step2[27] * CosPi8_64; + temp2 = -step2[20] * CosPi8_64 + step2[27] * CosPi24_64; + step1[20] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[27] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = -step2[21] * CosPi24_64 - step2[26] * CosPi8_64; + temp2 = -step2[21] * CosPi8_64 + step2[26] * CosPi24_64; + step1[21] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[26] = (short)WrapLow(DctConstRoundShift(temp2)); + step1[22] = step2[22]; + step1[23] = step2[23]; + step1[24] = step2[24]; + step1[25] = step2[25]; + step1[30] = step2[30]; + step1[31] = step2[31]; + + // stage 6 + step2[0] = (short)WrapLow(step1[0] + step1[7]); + step2[1] = (short)WrapLow(step1[1] + step1[6]); + step2[2] = (short)WrapLow(step1[2] + step1[5]); + step2[3] = (short)WrapLow(step1[3] + step1[4]); + step2[4] = (short)WrapLow(step1[3] - step1[4]); + step2[5] = (short)WrapLow(step1[2] - step1[5]); + step2[6] = (short)WrapLow(step1[1] - step1[6]); + step2[7] = (short)WrapLow(step1[0] - step1[7]); + step2[8] = step1[8]; + step2[9] = step1[9]; + temp1 = (-step1[10] + step1[13]) * CosPi16_64; + temp2 = (step1[10] + step1[13]) * CosPi16_64; + step2[10] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[13] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = (-step1[11] + step1[12]) * CosPi16_64; + temp2 = (step1[11] + step1[12]) * CosPi16_64; + step2[11] = (short)WrapLow(DctConstRoundShift(temp1)); + step2[12] = (short)WrapLow(DctConstRoundShift(temp2)); + step2[14] = step1[14]; + step2[15] = step1[15]; + + step2[16] = (short)WrapLow(step1[16] + step1[23]); + step2[17] = (short)WrapLow(step1[17] + step1[22]); + step2[18] = (short)WrapLow(step1[18] + step1[21]); + step2[19] = (short)WrapLow(step1[19] + step1[20]); + step2[20] = (short)WrapLow(step1[19] - step1[20]); + step2[21] = (short)WrapLow(step1[18] - step1[21]); + step2[22] = (short)WrapLow(step1[17] - step1[22]); + step2[23] = (short)WrapLow(step1[16] - step1[23]); + + step2[24] = (short)WrapLow(-step1[24] + step1[31]); + step2[25] = (short)WrapLow(-step1[25] + step1[30]); + step2[26] = (short)WrapLow(-step1[26] + step1[29]); + step2[27] = (short)WrapLow(-step1[27] + step1[28]); + step2[28] = (short)WrapLow(step1[27] + step1[28]); + step2[29] = (short)WrapLow(step1[26] + step1[29]); + step2[30] = (short)WrapLow(step1[25] + step1[30]); + step2[31] = (short)WrapLow(step1[24] + step1[31]); + + // stage 7 + step1[0] = (short)WrapLow(step2[0] + step2[15]); + step1[1] = (short)WrapLow(step2[1] + step2[14]); + step1[2] = (short)WrapLow(step2[2] + step2[13]); + step1[3] = (short)WrapLow(step2[3] + step2[12]); + step1[4] = (short)WrapLow(step2[4] + step2[11]); + step1[5] = (short)WrapLow(step2[5] + step2[10]); + step1[6] = (short)WrapLow(step2[6] + step2[9]); + step1[7] = (short)WrapLow(step2[7] + step2[8]); + step1[8] = (short)WrapLow(step2[7] - step2[8]); + step1[9] = (short)WrapLow(step2[6] - step2[9]); + step1[10] = (short)WrapLow(step2[5] - step2[10]); + step1[11] = (short)WrapLow(step2[4] - step2[11]); + step1[12] = (short)WrapLow(step2[3] - step2[12]); + step1[13] = (short)WrapLow(step2[2] - step2[13]); + step1[14] = (short)WrapLow(step2[1] - step2[14]); + step1[15] = (short)WrapLow(step2[0] - step2[15]); + + step1[16] = step2[16]; + step1[17] = step2[17]; + step1[18] = step2[18]; + step1[19] = step2[19]; + temp1 = (-step2[20] + step2[27]) * CosPi16_64; + temp2 = (step2[20] + step2[27]) * CosPi16_64; + step1[20] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[27] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = (-step2[21] + step2[26]) * CosPi16_64; + temp2 = (step2[21] + step2[26]) * CosPi16_64; + step1[21] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[26] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = (-step2[22] + step2[25]) * CosPi16_64; + temp2 = (step2[22] + step2[25]) * CosPi16_64; + step1[22] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[25] = (short)WrapLow(DctConstRoundShift(temp2)); + temp1 = (-step2[23] + step2[24]) * CosPi16_64; + temp2 = (step2[23] + step2[24]) * CosPi16_64; + step1[23] = (short)WrapLow(DctConstRoundShift(temp1)); + step1[24] = (short)WrapLow(DctConstRoundShift(temp2)); + step1[28] = step2[28]; + step1[29] = step2[29]; + step1[30] = step2[30]; + step1[31] = step2[31]; + + // final stage + output[0] = WrapLow(step1[0] + step1[31]); + output[1] = WrapLow(step1[1] + step1[30]); + output[2] = WrapLow(step1[2] + step1[29]); + output[3] = WrapLow(step1[3] + step1[28]); + output[4] = WrapLow(step1[4] + step1[27]); + output[5] = WrapLow(step1[5] + step1[26]); + output[6] = WrapLow(step1[6] + step1[25]); + output[7] = WrapLow(step1[7] + step1[24]); + output[8] = WrapLow(step1[8] + step1[23]); + output[9] = WrapLow(step1[9] + step1[22]); + output[10] = WrapLow(step1[10] + step1[21]); + output[11] = WrapLow(step1[11] + step1[20]); + output[12] = WrapLow(step1[12] + step1[19]); + output[13] = WrapLow(step1[13] + step1[18]); + output[14] = WrapLow(step1[14] + step1[17]); + output[15] = WrapLow(step1[15] + step1[16]); + output[16] = WrapLow(step1[15] - step1[16]); + output[17] = WrapLow(step1[14] - step1[17]); + output[18] = WrapLow(step1[13] - step1[18]); + output[19] = WrapLow(step1[12] - step1[19]); + output[20] = WrapLow(step1[11] - step1[20]); + output[21] = WrapLow(step1[10] - step1[21]); + output[22] = WrapLow(step1[9] - step1[22]); + output[23] = WrapLow(step1[8] - step1[23]); + output[24] = WrapLow(step1[7] - step1[24]); + output[25] = WrapLow(step1[6] - step1[25]); + output[26] = WrapLow(step1[5] - step1[26]); + output[27] = WrapLow(step1[4] - step1[27]); + output[28] = WrapLow(step1[3] - step1[28]); + output[29] = WrapLow(step1[2] - step1[29]); + output[30] = WrapLow(step1[1] - step1[30]); + output[31] = WrapLow(step1[0] - step1[31]); + } + + [SkipLocalsInit] + public static void Idct32x321024Add(ReadOnlySpan input, Span dest, int stride) + { + int i, j; + Span output = stackalloc int[32 * 32]; + Span outptr = output; + Span tempIn = stackalloc int[32]; + Span tempOut = stackalloc int[32]; + + // Rows + for (i = 0; i < 32; ++i) + { + short zeroCoeff = 0; + for (j = 0; j < 32; ++j) + { + zeroCoeff |= (short)input[j]; + } + + if (zeroCoeff != 0) + { + Idct32(input, outptr); + } + else + { + outptr[..32].Clear(); + } + + input = input[32..]; + outptr = outptr[32..]; + } + + // Columns + for (i = 0; i < 32; ++i) + { + for (j = 0; j < 32; ++j) + { + tempIn[j] = output[j * 32 + i]; + } + + Idct32(tempIn, tempOut); + for (j = 0; j < 32; ++j) + { + dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 6)); + } + } + } + + [SkipLocalsInit] + public static void Idct32x32135Add(ReadOnlySpan input, Span dest, int stride) + { + int i, j; + Span output = stackalloc int[32 * 32]; + Span outptr = output; + Span tempIn = stackalloc int[32]; + Span tempOut = stackalloc int[32]; + + output.Clear(); + + // Rows + // Only upper-left 16x16 has non-zero coeff + for (i = 0; i < 16; ++i) + { + Idct32(input, outptr); + input = input[32..]; + outptr = outptr[32..]; + } + + // Columns + for (i = 0; i < 32; ++i) + { + for (j = 0; j < 32; ++j) + { + tempIn[j] = output[j * 32 + i]; + } + + Idct32(tempIn, tempOut); + for (j = 0; j < 32; ++j) + { + dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 6)); + } + } + } + + [SkipLocalsInit] + public static void Idct32x3234Add(ReadOnlySpan input, Span dest, int stride) + { + int i, j; + Span output = stackalloc int[32 * 32]; + Span outptr = output; + Span tempIn = stackalloc int[32]; + Span tempOut = stackalloc int[32]; + + output.Clear(); + + // Rows + // Only upper-left 8x8 has non-zero coeff + for (i = 0; i < 8; ++i) + { + Idct32(input, outptr); + input = input[32..]; + outptr = outptr[32..]; + } + + // Columns + for (i = 0; i < 32; ++i) + { + for (j = 0; j < 32; ++j) + { + tempIn[j] = output[j * 32 + i]; + } + + Idct32(tempIn, tempOut); + for (j = 0; j < 32; ++j) + { + dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 6)); + } + } + } + + public static void Idct32x321Add(ReadOnlySpan input, Span dest, int stride) + { + int i, j; + long a1; + int output = WrapLow(DctConstRoundShift((short)input[0] * CosPi16_64)); + + output = WrapLow(DctConstRoundShift(output * CosPi16_64)); + a1 = BitUtils.RoundPowerOfTwo(output, 6); + + for (j = 0; j < 32; ++j) + { + for (i = 0; i < 32; ++i) + { + dest[i] = ClipPixelAdd(dest[i], a1); + } + + dest = dest[stride..]; + } + } + + [SkipLocalsInit] + public static void HighbdIwht4x416Add(ReadOnlySpan input, Span dest, int stride, int bd) + { + /* 4-point reversible, orthonormal inverse Walsh-Hadamard in 3.5 adds, + 0.5 shifts per pixel. */ + int i; + Span output = stackalloc int[16]; + long a1, b1, c1, d1, e1; + ReadOnlySpan ip = input; + Span op = output; + + for (i = 0; i < 4; i++) + { + a1 = ip[0] >> UnitQuantShift; + c1 = ip[1] >> UnitQuantShift; + d1 = ip[2] >> UnitQuantShift; + b1 = ip[3] >> UnitQuantShift; + a1 += c1; + d1 -= b1; + e1 = (a1 - d1) >> 1; + b1 = e1 - b1; + c1 = e1 - c1; + a1 -= b1; + d1 += c1; + op[0] = HighbdWrapLow(a1, bd); + op[1] = HighbdWrapLow(b1, bd); + op[2] = HighbdWrapLow(c1, bd); + op[3] = HighbdWrapLow(d1, bd); + ip = ip[4..]; + op = op[4..]; + } + + ReadOnlySpan ip2 = output; + for (i = 0; i < 4; i++) + { + a1 = ip2[4 * 0]; + c1 = ip2[4 * 1]; + d1 = ip2[4 * 2]; + b1 = ip2[4 * 3]; + a1 += c1; + d1 -= b1; + e1 = (a1 - d1) >> 1; + b1 = e1 - b1; + c1 = e1 - c1; + a1 -= b1; + d1 += c1; + dest[stride * 0] = HighbdClipPixelAdd(dest[stride * 0], HighbdWrapLow(a1, bd), bd); + dest[stride * 1] = HighbdClipPixelAdd(dest[stride * 1], HighbdWrapLow(b1, bd), bd); + dest[stride * 2] = HighbdClipPixelAdd(dest[stride * 2], HighbdWrapLow(c1, bd), bd); + dest[stride * 3] = HighbdClipPixelAdd(dest[stride * 3], HighbdWrapLow(d1, bd), bd); + + ip2 = ip2[1..]; + dest = dest[1..]; + } + } + + [SkipLocalsInit] + public static void HighbdIwht4x41Add(ReadOnlySpan input, Span dest, int stride, int bd) + { + int i; + long a1, e1; + Span tmp = stackalloc int[4]; + ReadOnlySpan ip = input; + Span op = tmp; + + a1 = ip[0] >> UnitQuantShift; + e1 = a1 >> 1; + a1 -= e1; + op[0] = HighbdWrapLow(a1, bd); + op[1] = op[2] = op[3] = HighbdWrapLow(e1, bd); + + ReadOnlySpan ip2 = tmp; + for (i = 0; i < 4; i++) + { + e1 = ip2[0] >> 1; + a1 = ip2[0] - e1; + dest[stride * 0] = HighbdClipPixelAdd(dest[stride * 0], a1, bd); + dest[stride * 1] = HighbdClipPixelAdd(dest[stride * 1], e1, bd); + dest[stride * 2] = HighbdClipPixelAdd(dest[stride * 2], e1, bd); + dest[stride * 3] = HighbdClipPixelAdd(dest[stride * 3], e1, bd); + ip2 = ip2[1..]; + dest = dest[1..]; + } + } + + public static void HighbdIadst4(ReadOnlySpan input, Span output, int bd) + { + long s0, s1, s2, s3, s4, s5, s6, s7; + int x0 = input[0]; + int x1 = input[1]; + int x2 = input[2]; + int x3 = input[3]; + + if (DetectInvalidHighbdInput(input, 4) != 0) + { + Debug.Assert(false, "invalid highbd txfm input"); + output[..4].Clear(); + + return; + } + + if ((x0 | x1 | x2 | x3) == 0) + { + output[..4].Clear(); + + return; + } + + s0 = (long)SinPi1_9 * x0; + s1 = (long)SinPi2_9 * x0; + s2 = (long)SinPi3_9 * x1; + s3 = (long)SinPi4_9 * x2; + s4 = (long)SinPi1_9 * x2; + s5 = (long)SinPi2_9 * x3; + s6 = (long)SinPi4_9 * x3; + s7 = HighbdWrapLow(x0 - x2 + x3, bd); + + s0 = s0 + s3 + s5; + s1 = s1 - s4 - s6; + s3 = s2; + s2 = SinPi3_9 * s7; + + // 1-D transform scaling factor is sqrt(2). + // The overall dynamic range is 14b (input) + 14b (multiplication scaling) + // + 1b (addition) = 29b. + // Hence the output bit depth is 15b. + output[0] = HighbdWrapLow(DctConstRoundShift(s0 + s3), bd); + output[1] = HighbdWrapLow(DctConstRoundShift(s1 + s3), bd); + output[2] = HighbdWrapLow(DctConstRoundShift(s2), bd); + output[3] = HighbdWrapLow(DctConstRoundShift(s0 + s1 - s3), bd); + } + + [SkipLocalsInit] + public static void HighbdIdct4(ReadOnlySpan input, Span output, int bd) + { + Span step = stackalloc int[4]; + long temp1, temp2; + + if (DetectInvalidHighbdInput(input, 4) != 0) + { + Debug.Assert(false, "invalid highbd txfm input"); + output[..4].Clear(); + + return; + } + + // stage 1 + temp1 = (input[0] + input[2]) * (long)CosPi16_64; + temp2 = (input[0] - input[2]) * (long)CosPi16_64; + step[0] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step[1] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = input[1] * (long)CosPi24_64 - input[3] * (long)CosPi8_64; + temp2 = input[1] * (long)CosPi8_64 + input[3] * (long)CosPi24_64; + step[2] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step[3] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + // stage 2 + output[0] = HighbdWrapLow(step[0] + step[3], bd); + output[1] = HighbdWrapLow(step[1] + step[2], bd); + output[2] = HighbdWrapLow(step[1] - step[2], bd); + output[3] = HighbdWrapLow(step[0] - step[3], bd); + } + + [SkipLocalsInit] + public static void HighbdIdct4x416Add(ReadOnlySpan input, Span dest, int stride, int bd) + { + int i, j; + Span output = stackalloc int[4 * 4]; + Span outptr = output; + Span tempIn = stackalloc int[4]; + Span tempOut = stackalloc int[4]; + + // Rows + for (i = 0; i < 4; ++i) + { + HighbdIdct4(input, outptr, bd); + input = input[4..]; + outptr = outptr[4..]; + } + + // Columns + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + tempIn[j] = output[j * 4 + i]; + } + + HighbdIdct4(tempIn, tempOut, bd); + for (j = 0; j < 4; ++j) + { + dest[j * stride + i] = HighbdClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 4), bd); + } + } + } + + public static void HighbdIdct4x41Add(ReadOnlySpan input, Span dest, int stride, int bd) + { + int i; + long a1; + int output = HighbdWrapLow(DctConstRoundShift(input[0] * (long)CosPi16_64), bd); + + output = HighbdWrapLow(DctConstRoundShift(output * (long)CosPi16_64), bd); + a1 = BitUtils.RoundPowerOfTwo(output, 4); + + for (i = 0; i < 4; i++) + { + dest[0] = HighbdClipPixelAdd(dest[0], a1, bd); + dest[1] = HighbdClipPixelAdd(dest[1], a1, bd); + dest[2] = HighbdClipPixelAdd(dest[2], a1, bd); + dest[3] = HighbdClipPixelAdd(dest[3], a1, bd); + dest = dest[stride..]; + } + } + + public static void HighbdIadst8(ReadOnlySpan input, Span output, int bd) + { + long s0, s1, s2, s3, s4, s5, s6, s7; + int x0 = input[7]; + int x1 = input[0]; + int x2 = input[5]; + int x3 = input[2]; + int x4 = input[3]; + int x5 = input[4]; + int x6 = input[1]; + int x7 = input[6]; + + if (DetectInvalidHighbdInput(input, 8) != 0) + { + Debug.Assert(false, "invalid highbd txfm input"); + output[..8].Clear(); + + return; + } + + if ((x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7) == 0) + { + output[..8].Clear(); + + return; + } + + // stage 1 + s0 = (long)CosPi2_64 * x0 + (long)CosPi30_64 * x1; + s1 = (long)CosPi30_64 * x0 - (long)CosPi2_64 * x1; + s2 = (long)CosPi10_64 * x2 + (long)CosPi22_64 * x3; + s3 = (long)CosPi22_64 * x2 - (long)CosPi10_64 * x3; + s4 = (long)CosPi18_64 * x4 + (long)CosPi14_64 * x5; + s5 = (long)CosPi14_64 * x4 - (long)CosPi18_64 * x5; + s6 = (long)CosPi26_64 * x6 + (long)CosPi6_64 * x7; + s7 = (long)CosPi6_64 * x6 - (long)CosPi26_64 * x7; + + x0 = HighbdWrapLow(DctConstRoundShift(s0 + s4), bd); + x1 = HighbdWrapLow(DctConstRoundShift(s1 + s5), bd); + x2 = HighbdWrapLow(DctConstRoundShift(s2 + s6), bd); + x3 = HighbdWrapLow(DctConstRoundShift(s3 + s7), bd); + x4 = HighbdWrapLow(DctConstRoundShift(s0 - s4), bd); + x5 = HighbdWrapLow(DctConstRoundShift(s1 - s5), bd); + x6 = HighbdWrapLow(DctConstRoundShift(s2 - s6), bd); + x7 = HighbdWrapLow(DctConstRoundShift(s3 - s7), bd); + + // stage 2 + s0 = x0; + s1 = x1; + s2 = x2; + s3 = x3; + s4 = (long)CosPi8_64 * x4 + (long)CosPi24_64 * x5; + s5 = (long)CosPi24_64 * x4 - (long)CosPi8_64 * x5; + s6 = (long)(-CosPi24_64) * x6 + (long)CosPi8_64 * x7; + s7 = (long)CosPi8_64 * x6 + (long)CosPi24_64 * x7; + + x0 = HighbdWrapLow(s0 + s2, bd); + x1 = HighbdWrapLow(s1 + s3, bd); + x2 = HighbdWrapLow(s0 - s2, bd); + x3 = HighbdWrapLow(s1 - s3, bd); + x4 = HighbdWrapLow(DctConstRoundShift(s4 + s6), bd); + x5 = HighbdWrapLow(DctConstRoundShift(s5 + s7), bd); + x6 = HighbdWrapLow(DctConstRoundShift(s4 - s6), bd); + x7 = HighbdWrapLow(DctConstRoundShift(s5 - s7), bd); + + // stage 3 + s2 = (long)CosPi16_64 * (x2 + x3); + s3 = (long)CosPi16_64 * (x2 - x3); + s6 = (long)CosPi16_64 * (x6 + x7); + s7 = (long)CosPi16_64 * (x6 - x7); + + x2 = HighbdWrapLow(DctConstRoundShift(s2), bd); + x3 = HighbdWrapLow(DctConstRoundShift(s3), bd); + x6 = HighbdWrapLow(DctConstRoundShift(s6), bd); + x7 = HighbdWrapLow(DctConstRoundShift(s7), bd); + + output[0] = HighbdWrapLow(x0, bd); + output[1] = HighbdWrapLow(-x4, bd); + output[2] = HighbdWrapLow(x6, bd); + output[3] = HighbdWrapLow(-x2, bd); + output[4] = HighbdWrapLow(x3, bd); + output[5] = HighbdWrapLow(-x7, bd); + output[6] = HighbdWrapLow(x5, bd); + output[7] = HighbdWrapLow(-x1, bd); + } + + [SkipLocalsInit] + public static void HighbdIdct8(ReadOnlySpan input, Span output, int bd) + { + Span step1 = stackalloc int[8]; + Span step2 = stackalloc int[8]; + long temp1, temp2; + + if (DetectInvalidHighbdInput(input, 8) != 0) + { + Debug.Assert(false, "invalid highbd txfm input"); + output[..8].Clear(); + + return; + } + + // stage 1 + step1[0] = input[0]; + step1[2] = input[4]; + step1[1] = input[2]; + step1[3] = input[6]; + temp1 = input[1] * (long)CosPi28_64 - input[7] * (long)CosPi4_64; + temp2 = input[1] * (long)CosPi4_64 + input[7] * (long)CosPi28_64; + step1[4] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[7] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = input[5] * (long)CosPi12_64 - input[3] * (long)CosPi20_64; + temp2 = input[5] * (long)CosPi20_64 + input[3] * (long)CosPi12_64; + step1[5] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[6] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + // stage 2 & stage 3 - even half + HighbdIdct4(step1, step1, bd); + + // stage 2 - odd half + step2[4] = HighbdWrapLow(step1[4] + step1[5], bd); + step2[5] = HighbdWrapLow(step1[4] - step1[5], bd); + step2[6] = HighbdWrapLow(-step1[6] + step1[7], bd); + step2[7] = HighbdWrapLow(step1[6] + step1[7], bd); + + // stage 3 - odd half + step1[4] = step2[4]; + temp1 = (step2[6] - step2[5]) * (long)CosPi16_64; + temp2 = (step2[5] + step2[6]) * (long)CosPi16_64; + step1[5] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[6] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + step1[7] = step2[7]; + + // stage 4 + output[0] = HighbdWrapLow(step1[0] + step1[7], bd); + output[1] = HighbdWrapLow(step1[1] + step1[6], bd); + output[2] = HighbdWrapLow(step1[2] + step1[5], bd); + output[3] = HighbdWrapLow(step1[3] + step1[4], bd); + output[4] = HighbdWrapLow(step1[3] - step1[4], bd); + output[5] = HighbdWrapLow(step1[2] - step1[5], bd); + output[6] = HighbdWrapLow(step1[1] - step1[6], bd); + output[7] = HighbdWrapLow(step1[0] - step1[7], bd); + } + + [SkipLocalsInit] + public static void HighbdIdct8x864Add(ReadOnlySpan input, Span dest, int stride, int bd) + { + int i, j; + Span output = stackalloc int[8 * 8]; + Span outptr = output; + Span tempIn = stackalloc int[8]; + Span tempOut = stackalloc int[8]; + + // First transform rows + for (i = 0; i < 8; ++i) + { + HighbdIdct8(input, outptr, bd); + input = input[8..]; + outptr = outptr[8..]; + } + + // Then transform columns + for (i = 0; i < 8; ++i) + { + for (j = 0; j < 8; ++j) + { + tempIn[j] = output[j * 8 + i]; + } + + HighbdIdct8(tempIn, tempOut, bd); + for (j = 0; j < 8; ++j) + { + dest[j * stride + i] = HighbdClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 5), bd); + } + } + } + + [SkipLocalsInit] + public static void HighbdIdct8x812Add(ReadOnlySpan input, Span dest, int stride, int bd) + { + int i, j; + Span output = stackalloc int[8 * 8]; + Span outptr = output; + Span tempIn = stackalloc int[8]; + Span tempOut = stackalloc int[8]; + + output.Clear(); + + // First transform rows + // Only first 4 row has non-zero coefs + for (i = 0; i < 4; ++i) + { + HighbdIdct8(input, outptr, bd); + input = input[8..]; + outptr = outptr[8..]; + } + + // Then transform columns + for (i = 0; i < 8; ++i) + { + for (j = 0; j < 8; ++j) + { + tempIn[j] = output[j * 8 + i]; + } + + HighbdIdct8(tempIn, tempOut, bd); + for (j = 0; j < 8; ++j) + { + dest[j * stride + i] = HighbdClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 5), bd); + } + } + } + + public static void Vpx_Highbdidct8x8_1_add_c(ReadOnlySpan input, Span dest, int stride, int bd) + { + int i, j; + long a1; + int output = HighbdWrapLow(DctConstRoundShift(input[0] * (long)CosPi16_64), bd); + + output = HighbdWrapLow(DctConstRoundShift(output * (long)CosPi16_64), bd); + a1 = BitUtils.RoundPowerOfTwo(output, 5); + for (j = 0; j < 8; ++j) + { + for (i = 0; i < 8; ++i) + { + dest[i] = HighbdClipPixelAdd(dest[i], a1, bd); + } + + dest = dest[stride..]; + } + } + + public static void HighbdIadst16(ReadOnlySpan input, Span output, int bd) + { + long s0, s1, s2, s3, s4, s5, s6, s7, s8; + long s9, s10, s11, s12, s13, s14, s15; + int x0 = input[15]; + int x1 = input[0]; + int x2 = input[13]; + int x3 = input[2]; + int x4 = input[11]; + int x5 = input[4]; + int x6 = input[9]; + int x7 = input[6]; + int x8 = input[7]; + int x9 = input[8]; + int x10 = input[5]; + int x11 = input[10]; + int x12 = input[3]; + int x13 = input[12]; + int x14 = input[1]; + int x15 = input[14]; + + if (DetectInvalidHighbdInput(input, 16) != 0) + { + Debug.Assert(false, "invalid highbd txfm input"); + output[..16].Clear(); + + return; + } + + if ((x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | x10 | x11 | x12 | x13 | x14 | x15) == 0) + { + output[..16].Clear(); + + return; + } + + // stage 1 + s0 = x0 * (long)CosPi1_64 + x1 * (long)CosPi31_64; + s1 = x0 * (long)CosPi31_64 - x1 * (long)CosPi1_64; + s2 = x2 * (long)CosPi5_64 + x3 * (long)CosPi27_64; + s3 = x2 * (long)CosPi27_64 - x3 * (long)CosPi5_64; + s4 = x4 * (long)CosPi9_64 + x5 * (long)CosPi23_64; + s5 = x4 * (long)CosPi23_64 - x5 * (long)CosPi9_64; + s6 = x6 * (long)CosPi13_64 + x7 * (long)CosPi19_64; + s7 = x6 * (long)CosPi19_64 - x7 * (long)CosPi13_64; + s8 = x8 * (long)CosPi17_64 + x9 * (long)CosPi15_64; + s9 = x8 * (long)CosPi15_64 - x9 * (long)CosPi17_64; + s10 = x10 * (long)CosPi21_64 + x11 * (long)CosPi11_64; + s11 = x10 * (long)CosPi11_64 - x11 * (long)CosPi21_64; + s12 = x12 * (long)CosPi25_64 + x13 * (long)CosPi7_64; + s13 = x12 * (long)CosPi7_64 - x13 * (long)CosPi25_64; + s14 = x14 * (long)CosPi29_64 + x15 * (long)CosPi3_64; + s15 = x14 * (long)CosPi3_64 - x15 * (long)CosPi29_64; + + x0 = HighbdWrapLow(DctConstRoundShift(s0 + s8), bd); + x1 = HighbdWrapLow(DctConstRoundShift(s1 + s9), bd); + x2 = HighbdWrapLow(DctConstRoundShift(s2 + s10), bd); + x3 = HighbdWrapLow(DctConstRoundShift(s3 + s11), bd); + x4 = HighbdWrapLow(DctConstRoundShift(s4 + s12), bd); + x5 = HighbdWrapLow(DctConstRoundShift(s5 + s13), bd); + x6 = HighbdWrapLow(DctConstRoundShift(s6 + s14), bd); + x7 = HighbdWrapLow(DctConstRoundShift(s7 + s15), bd); + x8 = HighbdWrapLow(DctConstRoundShift(s0 - s8), bd); + x9 = HighbdWrapLow(DctConstRoundShift(s1 - s9), bd); + x10 = HighbdWrapLow(DctConstRoundShift(s2 - s10), bd); + x11 = HighbdWrapLow(DctConstRoundShift(s3 - s11), bd); + x12 = HighbdWrapLow(DctConstRoundShift(s4 - s12), bd); + x13 = HighbdWrapLow(DctConstRoundShift(s5 - s13), bd); + x14 = HighbdWrapLow(DctConstRoundShift(s6 - s14), bd); + x15 = HighbdWrapLow(DctConstRoundShift(s7 - s15), bd); + + // stage 2 + s0 = x0; + s1 = x1; + s2 = x2; + s3 = x3; + s4 = x4; + s5 = x5; + s6 = x6; + s7 = x7; + s8 = x8 * (long)CosPi4_64 + x9 * (long)CosPi28_64; + s9 = x8 * (long)CosPi28_64 - x9 * (long)CosPi4_64; + s10 = x10 * (long)CosPi20_64 + x11 * (long)CosPi12_64; + s11 = x10 * (long)CosPi12_64 - x11 * (long)CosPi20_64; + s12 = -x12 * (long)CosPi28_64 + x13 * (long)CosPi4_64; + s13 = x12 * (long)CosPi4_64 + x13 * (long)CosPi28_64; + s14 = -x14 * (long)CosPi12_64 + x15 * (long)CosPi20_64; + s15 = x14 * (long)CosPi20_64 + x15 * (long)CosPi12_64; + + x0 = HighbdWrapLow(s0 + s4, bd); + x1 = HighbdWrapLow(s1 + s5, bd); + x2 = HighbdWrapLow(s2 + s6, bd); + x3 = HighbdWrapLow(s3 + s7, bd); + x4 = HighbdWrapLow(s0 - s4, bd); + x5 = HighbdWrapLow(s1 - s5, bd); + x6 = HighbdWrapLow(s2 - s6, bd); + x7 = HighbdWrapLow(s3 - s7, bd); + x8 = HighbdWrapLow(DctConstRoundShift(s8 + s12), bd); + x9 = HighbdWrapLow(DctConstRoundShift(s9 + s13), bd); + x10 = HighbdWrapLow(DctConstRoundShift(s10 + s14), bd); + x11 = HighbdWrapLow(DctConstRoundShift(s11 + s15), bd); + x12 = HighbdWrapLow(DctConstRoundShift(s8 - s12), bd); + x13 = HighbdWrapLow(DctConstRoundShift(s9 - s13), bd); + x14 = HighbdWrapLow(DctConstRoundShift(s10 - s14), bd); + x15 = HighbdWrapLow(DctConstRoundShift(s11 - s15), bd); + + // stage 3 + s0 = x0; + s1 = x1; + s2 = x2; + s3 = x3; + s4 = x4 * (long)CosPi8_64 + x5 * (long)CosPi24_64; + s5 = x4 * (long)CosPi24_64 - x5 * (long)CosPi8_64; + s6 = -x6 * (long)CosPi24_64 + x7 * (long)CosPi8_64; + s7 = x6 * (long)CosPi8_64 + x7 * (long)CosPi24_64; + s8 = x8; + s9 = x9; + s10 = x10; + s11 = x11; + s12 = x12 * (long)CosPi8_64 + x13 * (long)CosPi24_64; + s13 = x12 * (long)CosPi24_64 - x13 * (long)CosPi8_64; + s14 = -x14 * (long)CosPi24_64 + x15 * (long)CosPi8_64; + s15 = x14 * (long)CosPi8_64 + x15 * (long)CosPi24_64; + + x0 = HighbdWrapLow(s0 + s2, bd); + x1 = HighbdWrapLow(s1 + s3, bd); + x2 = HighbdWrapLow(s0 - s2, bd); + x3 = HighbdWrapLow(s1 - s3, bd); + x4 = HighbdWrapLow(DctConstRoundShift(s4 + s6), bd); + x5 = HighbdWrapLow(DctConstRoundShift(s5 + s7), bd); + x6 = HighbdWrapLow(DctConstRoundShift(s4 - s6), bd); + x7 = HighbdWrapLow(DctConstRoundShift(s5 - s7), bd); + x8 = HighbdWrapLow(s8 + s10, bd); + x9 = HighbdWrapLow(s9 + s11, bd); + x10 = HighbdWrapLow(s8 - s10, bd); + x11 = HighbdWrapLow(s9 - s11, bd); + x12 = HighbdWrapLow(DctConstRoundShift(s12 + s14), bd); + x13 = HighbdWrapLow(DctConstRoundShift(s13 + s15), bd); + x14 = HighbdWrapLow(DctConstRoundShift(s12 - s14), bd); + x15 = HighbdWrapLow(DctConstRoundShift(s13 - s15), bd); + + // stage 4 + s2 = (long)(-CosPi16_64) * (x2 + x3); + s3 = (long)CosPi16_64 * (x2 - x3); + s6 = (long)CosPi16_64 * (x6 + x7); + s7 = (long)CosPi16_64 * (-x6 + x7); + s10 = (long)CosPi16_64 * (x10 + x11); + s11 = (long)CosPi16_64 * (-x10 + x11); + s14 = (long)(-CosPi16_64) * (x14 + x15); + s15 = (long)CosPi16_64 * (x14 - x15); + + x2 = HighbdWrapLow(DctConstRoundShift(s2), bd); + x3 = HighbdWrapLow(DctConstRoundShift(s3), bd); + x6 = HighbdWrapLow(DctConstRoundShift(s6), bd); + x7 = HighbdWrapLow(DctConstRoundShift(s7), bd); + x10 = HighbdWrapLow(DctConstRoundShift(s10), bd); + x11 = HighbdWrapLow(DctConstRoundShift(s11), bd); + x14 = HighbdWrapLow(DctConstRoundShift(s14), bd); + x15 = HighbdWrapLow(DctConstRoundShift(s15), bd); + + output[0] = HighbdWrapLow(x0, bd); + output[1] = HighbdWrapLow(-x8, bd); + output[2] = HighbdWrapLow(x12, bd); + output[3] = HighbdWrapLow(-x4, bd); + output[4] = HighbdWrapLow(x6, bd); + output[5] = HighbdWrapLow(x14, bd); + output[6] = HighbdWrapLow(x10, bd); + output[7] = HighbdWrapLow(x2, bd); + output[8] = HighbdWrapLow(x3, bd); + output[9] = HighbdWrapLow(x11, bd); + output[10] = HighbdWrapLow(x15, bd); + output[11] = HighbdWrapLow(x7, bd); + output[12] = HighbdWrapLow(x5, bd); + output[13] = HighbdWrapLow(-x13, bd); + output[14] = HighbdWrapLow(x9, bd); + output[15] = HighbdWrapLow(-x1, bd); + } + + [SkipLocalsInit] + public static void HighbdIdct16(ReadOnlySpan input, Span output, int bd) + { + Span step1 = stackalloc int[16]; + Span step2 = stackalloc int[16]; + long temp1, temp2; + + if (DetectInvalidHighbdInput(input, 16) != 0) + { + Debug.Assert(false, "invalid highbd txfm input"); + output[..16].Clear(); + + return; + } + + // stage 1 + step1[0] = input[0 / 2]; + step1[1] = input[16 / 2]; + step1[2] = input[8 / 2]; + step1[3] = input[24 / 2]; + step1[4] = input[4 / 2]; + step1[5] = input[20 / 2]; + step1[6] = input[12 / 2]; + step1[7] = input[28 / 2]; + step1[8] = input[2 / 2]; + step1[9] = input[18 / 2]; + step1[10] = input[10 / 2]; + step1[11] = input[26 / 2]; + step1[12] = input[6 / 2]; + step1[13] = input[22 / 2]; + step1[14] = input[14 / 2]; + step1[15] = input[30 / 2]; + + // stage 2 + step2[0] = step1[0]; + step2[1] = step1[1]; + step2[2] = step1[2]; + step2[3] = step1[3]; + step2[4] = step1[4]; + step2[5] = step1[5]; + step2[6] = step1[6]; + step2[7] = step1[7]; + + temp1 = step1[8] * (long)CosPi30_64 - step1[15] * (long)CosPi2_64; + temp2 = step1[8] * (long)CosPi2_64 + step1[15] * (long)CosPi30_64; + step2[8] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[15] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + temp1 = step1[9] * (long)CosPi14_64 - step1[14] * (long)CosPi18_64; + temp2 = step1[9] * (long)CosPi18_64 + step1[14] * (long)CosPi14_64; + step2[9] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[14] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + temp1 = step1[10] * (long)CosPi22_64 - step1[13] * (long)CosPi10_64; + temp2 = step1[10] * (long)CosPi10_64 + step1[13] * (long)CosPi22_64; + step2[10] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[13] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + temp1 = step1[11] * (long)CosPi6_64 - step1[12] * (long)CosPi26_64; + temp2 = step1[11] * (long)CosPi26_64 + step1[12] * (long)CosPi6_64; + step2[11] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[12] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + // stage 3 + step1[0] = step2[0]; + step1[1] = step2[1]; + step1[2] = step2[2]; + step1[3] = step2[3]; + + temp1 = step2[4] * (long)CosPi28_64 - step2[7] * (long)CosPi4_64; + temp2 = step2[4] * (long)CosPi4_64 + step2[7] * (long)CosPi28_64; + step1[4] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[7] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = step2[5] * (long)CosPi12_64 - step2[6] * (long)CosPi20_64; + temp2 = step2[5] * (long)CosPi20_64 + step2[6] * (long)CosPi12_64; + step1[5] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[6] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + step1[8] = HighbdWrapLow(step2[8] + step2[9], bd); + step1[9] = HighbdWrapLow(step2[8] - step2[9], bd); + step1[10] = HighbdWrapLow(-step2[10] + step2[11], bd); + step1[11] = HighbdWrapLow(step2[10] + step2[11], bd); + step1[12] = HighbdWrapLow(step2[12] + step2[13], bd); + step1[13] = HighbdWrapLow(step2[12] - step2[13], bd); + step1[14] = HighbdWrapLow(-step2[14] + step2[15], bd); + step1[15] = HighbdWrapLow(step2[14] + step2[15], bd); + + // stage 4 + temp1 = (step1[0] + step1[1]) * (long)CosPi16_64; + temp2 = (step1[0] - step1[1]) * (long)CosPi16_64; + step2[0] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[1] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = step1[2] * (long)CosPi24_64 - step1[3] * (long)CosPi8_64; + temp2 = step1[2] * (long)CosPi8_64 + step1[3] * (long)CosPi24_64; + step2[2] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[3] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + step2[4] = HighbdWrapLow(step1[4] + step1[5], bd); + step2[5] = HighbdWrapLow(step1[4] - step1[5], bd); + step2[6] = HighbdWrapLow(-step1[6] + step1[7], bd); + step2[7] = HighbdWrapLow(step1[6] + step1[7], bd); + + step2[8] = step1[8]; + step2[15] = step1[15]; + temp1 = -step1[9] * (long)CosPi8_64 + step1[14] * (long)CosPi24_64; + temp2 = step1[9] * (long)CosPi24_64 + step1[14] * (long)CosPi8_64; + step2[9] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[14] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = -step1[10] * (long)CosPi24_64 - step1[13] * (long)CosPi8_64; + temp2 = -step1[10] * (long)CosPi8_64 + step1[13] * (long)CosPi24_64; + step2[10] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[13] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + step2[11] = step1[11]; + step2[12] = step1[12]; + + // stage 5 + step1[0] = HighbdWrapLow(step2[0] + step2[3], bd); + step1[1] = HighbdWrapLow(step2[1] + step2[2], bd); + step1[2] = HighbdWrapLow(step2[1] - step2[2], bd); + step1[3] = HighbdWrapLow(step2[0] - step2[3], bd); + step1[4] = step2[4]; + temp1 = (step2[6] - step2[5]) * (long)CosPi16_64; + temp2 = (step2[5] + step2[6]) * (long)CosPi16_64; + step1[5] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[6] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + step1[7] = step2[7]; + + step1[8] = HighbdWrapLow(step2[8] + step2[11], bd); + step1[9] = HighbdWrapLow(step2[9] + step2[10], bd); + step1[10] = HighbdWrapLow(step2[9] - step2[10], bd); + step1[11] = HighbdWrapLow(step2[8] - step2[11], bd); + step1[12] = HighbdWrapLow(-step2[12] + step2[15], bd); + step1[13] = HighbdWrapLow(-step2[13] + step2[14], bd); + step1[14] = HighbdWrapLow(step2[13] + step2[14], bd); + step1[15] = HighbdWrapLow(step2[12] + step2[15], bd); + + // stage 6 + step2[0] = HighbdWrapLow(step1[0] + step1[7], bd); + step2[1] = HighbdWrapLow(step1[1] + step1[6], bd); + step2[2] = HighbdWrapLow(step1[2] + step1[5], bd); + step2[3] = HighbdWrapLow(step1[3] + step1[4], bd); + step2[4] = HighbdWrapLow(step1[3] - step1[4], bd); + step2[5] = HighbdWrapLow(step1[2] - step1[5], bd); + step2[6] = HighbdWrapLow(step1[1] - step1[6], bd); + step2[7] = HighbdWrapLow(step1[0] - step1[7], bd); + step2[8] = step1[8]; + step2[9] = step1[9]; + temp1 = (-step1[10] + step1[13]) * (long)CosPi16_64; + temp2 = (step1[10] + step1[13]) * (long)CosPi16_64; + step2[10] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[13] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = (-step1[11] + step1[12]) * (long)CosPi16_64; + temp2 = (step1[11] + step1[12]) * (long)CosPi16_64; + step2[11] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[12] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + step2[14] = step1[14]; + step2[15] = step1[15]; + + // stage 7 + output[0] = HighbdWrapLow(step2[0] + step2[15], bd); + output[1] = HighbdWrapLow(step2[1] + step2[14], bd); + output[2] = HighbdWrapLow(step2[2] + step2[13], bd); + output[3] = HighbdWrapLow(step2[3] + step2[12], bd); + output[4] = HighbdWrapLow(step2[4] + step2[11], bd); + output[5] = HighbdWrapLow(step2[5] + step2[10], bd); + output[6] = HighbdWrapLow(step2[6] + step2[9], bd); + output[7] = HighbdWrapLow(step2[7] + step2[8], bd); + output[8] = HighbdWrapLow(step2[7] - step2[8], bd); + output[9] = HighbdWrapLow(step2[6] - step2[9], bd); + output[10] = HighbdWrapLow(step2[5] - step2[10], bd); + output[11] = HighbdWrapLow(step2[4] - step2[11], bd); + output[12] = HighbdWrapLow(step2[3] - step2[12], bd); + output[13] = HighbdWrapLow(step2[2] - step2[13], bd); + output[14] = HighbdWrapLow(step2[1] - step2[14], bd); + output[15] = HighbdWrapLow(step2[0] - step2[15], bd); + } + + [SkipLocalsInit] + public static void HighbdIdct16x16256Add(ReadOnlySpan input, Span dest, int stride, int bd) + { + int i, j; + Span output = stackalloc int[16 * 16]; + Span outptr = output; + Span tempIn = stackalloc int[16]; + Span tempOut = stackalloc int[16]; + + // First transform rows + for (i = 0; i < 16; ++i) + { + HighbdIdct16(input, outptr, bd); + input = input[16..]; + outptr = outptr[16..]; + } + + // Then transform columns + for (i = 0; i < 16; ++i) + { + for (j = 0; j < 16; ++j) + { + tempIn[j] = output[j * 16 + i]; + } + + HighbdIdct16(tempIn, tempOut, bd); + for (j = 0; j < 16; ++j) + { + dest[j * stride + i] = HighbdClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 6), bd); + } + } + } + + [SkipLocalsInit] + public static void HighbdIdct16x1638Add(ReadOnlySpan input, Span dest, int stride, int bd) + { + int i, j; + Span output = stackalloc int[16 * 16]; + Span outptr = output; + Span tempIn = stackalloc int[16]; + Span tempOut = stackalloc int[16]; + + output.Clear(); + + // First transform rows. Since all non-zero dct coefficients are in + // upper-left 8x8 area, we only need to calculate first 8 rows here. + for (i = 0; i < 8; ++i) + { + HighbdIdct16(input, outptr, bd); + input = input[16..]; + outptr = outptr[16..]; + } + + // Then transform columns + for (i = 0; i < 16; ++i) + { + Span destT = dest; + for (j = 0; j < 16; ++j) + { + tempIn[j] = output[j * 16 + i]; + } + + HighbdIdct16(tempIn, tempOut, bd); + for (j = 0; j < 16; ++j) + { + destT[i] = HighbdClipPixelAdd(destT[i], BitUtils.RoundPowerOfTwo(tempOut[j], 6), bd); + destT = destT[stride..]; + } + } + } + + [SkipLocalsInit] + public static void HighbdIdct16x1610Add(ReadOnlySpan input, Span dest, int stride, int bd) + { + int i, j; + Span output = stackalloc int[16 * 16]; + Span outptr = output; + Span tempIn = stackalloc int[16]; + Span tempOut = stackalloc int[16]; + + output.Clear(); + + // First transform rows. Since all non-zero dct coefficients are in + // upper-left 4x4 area, we only need to calculate first 4 rows here. + for (i = 0; i < 4; ++i) + { + HighbdIdct16(input, outptr, bd); + input = input[16..]; + outptr = outptr[16..]; + } + + // Then transform columns + for (i = 0; i < 16; ++i) + { + for (j = 0; j < 16; ++j) + { + tempIn[j] = output[j * 16 + i]; + } + + HighbdIdct16(tempIn, tempOut, bd); + for (j = 0; j < 16; ++j) + { + dest[j * stride + i] = HighbdClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 6), bd); + } + } + } + + public static void HighbdIdct16x161Add(ReadOnlySpan input, Span dest, int stride, int bd) + { + int i, j; + long a1; + int output = HighbdWrapLow(DctConstRoundShift(input[0] * (long)CosPi16_64), bd); + + output = HighbdWrapLow(DctConstRoundShift(output * (long)CosPi16_64), bd); + a1 = BitUtils.RoundPowerOfTwo(output, 6); + for (j = 0; j < 16; ++j) + { + for (i = 0; i < 16; ++i) + { + dest[i] = HighbdClipPixelAdd(dest[i], a1, bd); + } + + dest = dest[stride..]; + } + } + + [SkipLocalsInit] + public static void HighbdIdct32(ReadOnlySpan input, Span output, int bd) + { + Span step1 = stackalloc int[32]; + Span step2 = stackalloc int[32]; + long temp1, temp2; + + if (DetectInvalidHighbdInput(input, 32) != 0) + { + Debug.Assert(false, "invalid highbd txfm input"); + output[..32].Clear(); + + return; + } + + // stage 1 + step1[0] = input[0]; + step1[1] = input[16]; + step1[2] = input[8]; + step1[3] = input[24]; + step1[4] = input[4]; + step1[5] = input[20]; + step1[6] = input[12]; + step1[7] = input[28]; + step1[8] = input[2]; + step1[9] = input[18]; + step1[10] = input[10]; + step1[11] = input[26]; + step1[12] = input[6]; + step1[13] = input[22]; + step1[14] = input[14]; + step1[15] = input[30]; + + temp1 = input[1] * (long)CosPi31_64 - input[31] * (long)CosPi1_64; + temp2 = input[1] * (long)CosPi1_64 + input[31] * (long)CosPi31_64; + step1[16] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[31] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + temp1 = input[17] * (long)CosPi15_64 - input[15] * (long)CosPi17_64; + temp2 = input[17] * (long)CosPi17_64 + input[15] * (long)CosPi15_64; + step1[17] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[30] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + temp1 = input[9] * (long)CosPi23_64 - input[23] * (long)CosPi9_64; + temp2 = input[9] * (long)CosPi9_64 + input[23] * (long)CosPi23_64; + step1[18] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[29] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + temp1 = input[25] * (long)CosPi7_64 - input[7] * (long)CosPi25_64; + temp2 = input[25] * (long)CosPi25_64 + input[7] * (long)CosPi7_64; + step1[19] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[28] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + temp1 = input[5] * (long)CosPi27_64 - input[27] * (long)CosPi5_64; + temp2 = input[5] * (long)CosPi5_64 + input[27] * (long)CosPi27_64; + step1[20] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[27] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + temp1 = input[21] * (long)CosPi11_64 - input[11] * (long)CosPi21_64; + temp2 = input[21] * (long)CosPi21_64 + input[11] * (long)CosPi11_64; + step1[21] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[26] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + temp1 = input[13] * (long)CosPi19_64 - input[19] * (long)CosPi13_64; + temp2 = input[13] * (long)CosPi13_64 + input[19] * (long)CosPi19_64; + step1[22] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[25] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + temp1 = input[29] * (long)CosPi3_64 - input[3] * (long)CosPi29_64; + temp2 = input[29] * (long)CosPi29_64 + input[3] * (long)CosPi3_64; + step1[23] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[24] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + // stage 2 + step2[0] = step1[0]; + step2[1] = step1[1]; + step2[2] = step1[2]; + step2[3] = step1[3]; + step2[4] = step1[4]; + step2[5] = step1[5]; + step2[6] = step1[6]; + step2[7] = step1[7]; + + temp1 = step1[8] * (long)CosPi30_64 - step1[15] * (long)CosPi2_64; + temp2 = step1[8] * (long)CosPi2_64 + step1[15] * (long)CosPi30_64; + step2[8] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[15] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + temp1 = step1[9] * (long)CosPi14_64 - step1[14] * (long)CosPi18_64; + temp2 = step1[9] * (long)CosPi18_64 + step1[14] * (long)CosPi14_64; + step2[9] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[14] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + temp1 = step1[10] * (long)CosPi22_64 - step1[13] * (long)CosPi10_64; + temp2 = step1[10] * (long)CosPi10_64 + step1[13] * (long)CosPi22_64; + step2[10] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[13] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + temp1 = step1[11] * (long)CosPi6_64 - step1[12] * (long)CosPi26_64; + temp2 = step1[11] * (long)CosPi26_64 + step1[12] * (long)CosPi6_64; + step2[11] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[12] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + step2[16] = HighbdWrapLow(step1[16] + step1[17], bd); + step2[17] = HighbdWrapLow(step1[16] - step1[17], bd); + step2[18] = HighbdWrapLow(-step1[18] + step1[19], bd); + step2[19] = HighbdWrapLow(step1[18] + step1[19], bd); + step2[20] = HighbdWrapLow(step1[20] + step1[21], bd); + step2[21] = HighbdWrapLow(step1[20] - step1[21], bd); + step2[22] = HighbdWrapLow(-step1[22] + step1[23], bd); + step2[23] = HighbdWrapLow(step1[22] + step1[23], bd); + step2[24] = HighbdWrapLow(step1[24] + step1[25], bd); + step2[25] = HighbdWrapLow(step1[24] - step1[25], bd); + step2[26] = HighbdWrapLow(-step1[26] + step1[27], bd); + step2[27] = HighbdWrapLow(step1[26] + step1[27], bd); + step2[28] = HighbdWrapLow(step1[28] + step1[29], bd); + step2[29] = HighbdWrapLow(step1[28] - step1[29], bd); + step2[30] = HighbdWrapLow(-step1[30] + step1[31], bd); + step2[31] = HighbdWrapLow(step1[30] + step1[31], bd); + + // stage 3 + step1[0] = step2[0]; + step1[1] = step2[1]; + step1[2] = step2[2]; + step1[3] = step2[3]; + + temp1 = step2[4] * (long)CosPi28_64 - step2[7] * (long)CosPi4_64; + temp2 = step2[4] * (long)CosPi4_64 + step2[7] * (long)CosPi28_64; + step1[4] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[7] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = step2[5] * (long)CosPi12_64 - step2[6] * (long)CosPi20_64; + temp2 = step2[5] * (long)CosPi20_64 + step2[6] * (long)CosPi12_64; + step1[5] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[6] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + + step1[8] = HighbdWrapLow(step2[8] + step2[9], bd); + step1[9] = HighbdWrapLow(step2[8] - step2[9], bd); + step1[10] = HighbdWrapLow(-step2[10] + step2[11], bd); + step1[11] = HighbdWrapLow(step2[10] + step2[11], bd); + step1[12] = HighbdWrapLow(step2[12] + step2[13], bd); + step1[13] = HighbdWrapLow(step2[12] - step2[13], bd); + step1[14] = HighbdWrapLow(-step2[14] + step2[15], bd); + step1[15] = HighbdWrapLow(step2[14] + step2[15], bd); + + step1[16] = step2[16]; + step1[31] = step2[31]; + temp1 = -step2[17] * (long)CosPi4_64 + step2[30] * (long)CosPi28_64; + temp2 = step2[17] * (long)CosPi28_64 + step2[30] * (long)CosPi4_64; + step1[17] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[30] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = -step2[18] * (long)CosPi28_64 - step2[29] * (long)CosPi4_64; + temp2 = -step2[18] * (long)CosPi4_64 + step2[29] * (long)CosPi28_64; + step1[18] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[29] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + step1[19] = step2[19]; + step1[20] = step2[20]; + temp1 = -step2[21] * (long)CosPi20_64 + step2[26] * (long)CosPi12_64; + temp2 = step2[21] * (long)CosPi12_64 + step2[26] * (long)CosPi20_64; + step1[21] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[26] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = -step2[22] * (long)CosPi12_64 - step2[25] * (long)CosPi20_64; + temp2 = -step2[22] * (long)CosPi20_64 + step2[25] * (long)CosPi12_64; + step1[22] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[25] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + step1[23] = step2[23]; + step1[24] = step2[24]; + step1[27] = step2[27]; + step1[28] = step2[28]; + + // stage 4 + temp1 = (step1[0] + step1[1]) * (long)CosPi16_64; + temp2 = (step1[0] - step1[1]) * (long)CosPi16_64; + step2[0] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[1] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = step1[2] * (long)CosPi24_64 - step1[3] * (long)CosPi8_64; + temp2 = step1[2] * (long)CosPi8_64 + step1[3] * (long)CosPi24_64; + step2[2] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[3] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + step2[4] = HighbdWrapLow(step1[4] + step1[5], bd); + step2[5] = HighbdWrapLow(step1[4] - step1[5], bd); + step2[6] = HighbdWrapLow(-step1[6] + step1[7], bd); + step2[7] = HighbdWrapLow(step1[6] + step1[7], bd); + + step2[8] = step1[8]; + step2[15] = step1[15]; + temp1 = -step1[9] * (long)CosPi8_64 + step1[14] * (long)CosPi24_64; + temp2 = step1[9] * (long)CosPi24_64 + step1[14] * (long)CosPi8_64; + step2[9] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[14] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = -step1[10] * (long)CosPi24_64 - step1[13] * (long)CosPi8_64; + temp2 = -step1[10] * (long)CosPi8_64 + step1[13] * (long)CosPi24_64; + step2[10] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[13] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + step2[11] = step1[11]; + step2[12] = step1[12]; + + step2[16] = HighbdWrapLow(step1[16] + step1[19], bd); + step2[17] = HighbdWrapLow(step1[17] + step1[18], bd); + step2[18] = HighbdWrapLow(step1[17] - step1[18], bd); + step2[19] = HighbdWrapLow(step1[16] - step1[19], bd); + step2[20] = HighbdWrapLow(-step1[20] + step1[23], bd); + step2[21] = HighbdWrapLow(-step1[21] + step1[22], bd); + step2[22] = HighbdWrapLow(step1[21] + step1[22], bd); + step2[23] = HighbdWrapLow(step1[20] + step1[23], bd); + + step2[24] = HighbdWrapLow(step1[24] + step1[27], bd); + step2[25] = HighbdWrapLow(step1[25] + step1[26], bd); + step2[26] = HighbdWrapLow(step1[25] - step1[26], bd); + step2[27] = HighbdWrapLow(step1[24] - step1[27], bd); + step2[28] = HighbdWrapLow(-step1[28] + step1[31], bd); + step2[29] = HighbdWrapLow(-step1[29] + step1[30], bd); + step2[30] = HighbdWrapLow(step1[29] + step1[30], bd); + step2[31] = HighbdWrapLow(step1[28] + step1[31], bd); + + // stage 5 + step1[0] = HighbdWrapLow(step2[0] + step2[3], bd); + step1[1] = HighbdWrapLow(step2[1] + step2[2], bd); + step1[2] = HighbdWrapLow(step2[1] - step2[2], bd); + step1[3] = HighbdWrapLow(step2[0] - step2[3], bd); + step1[4] = step2[4]; + temp1 = (step2[6] - step2[5]) * (long)CosPi16_64; + temp2 = (step2[5] + step2[6]) * (long)CosPi16_64; + step1[5] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[6] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + step1[7] = step2[7]; + + step1[8] = HighbdWrapLow(step2[8] + step2[11], bd); + step1[9] = HighbdWrapLow(step2[9] + step2[10], bd); + step1[10] = HighbdWrapLow(step2[9] - step2[10], bd); + step1[11] = HighbdWrapLow(step2[8] - step2[11], bd); + step1[12] = HighbdWrapLow(-step2[12] + step2[15], bd); + step1[13] = HighbdWrapLow(-step2[13] + step2[14], bd); + step1[14] = HighbdWrapLow(step2[13] + step2[14], bd); + step1[15] = HighbdWrapLow(step2[12] + step2[15], bd); + + step1[16] = step2[16]; + step1[17] = step2[17]; + temp1 = -step2[18] * (long)CosPi8_64 + step2[29] * (long)CosPi24_64; + temp2 = step2[18] * (long)CosPi24_64 + step2[29] * (long)CosPi8_64; + step1[18] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[29] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = -step2[19] * (long)CosPi8_64 + step2[28] * (long)CosPi24_64; + temp2 = step2[19] * (long)CosPi24_64 + step2[28] * (long)CosPi8_64; + step1[19] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[28] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = -step2[20] * (long)CosPi24_64 - step2[27] * (long)CosPi8_64; + temp2 = -step2[20] * (long)CosPi8_64 + step2[27] * (long)CosPi24_64; + step1[20] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[27] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = -step2[21] * (long)CosPi24_64 - step2[26] * (long)CosPi8_64; + temp2 = -step2[21] * (long)CosPi8_64 + step2[26] * (long)CosPi24_64; + step1[21] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[26] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + step1[22] = step2[22]; + step1[23] = step2[23]; + step1[24] = step2[24]; + step1[25] = step2[25]; + step1[30] = step2[30]; + step1[31] = step2[31]; + + // stage 6 + step2[0] = HighbdWrapLow(step1[0] + step1[7], bd); + step2[1] = HighbdWrapLow(step1[1] + step1[6], bd); + step2[2] = HighbdWrapLow(step1[2] + step1[5], bd); + step2[3] = HighbdWrapLow(step1[3] + step1[4], bd); + step2[4] = HighbdWrapLow(step1[3] - step1[4], bd); + step2[5] = HighbdWrapLow(step1[2] - step1[5], bd); + step2[6] = HighbdWrapLow(step1[1] - step1[6], bd); + step2[7] = HighbdWrapLow(step1[0] - step1[7], bd); + step2[8] = step1[8]; + step2[9] = step1[9]; + temp1 = (-step1[10] + step1[13]) * (long)CosPi16_64; + temp2 = (step1[10] + step1[13]) * (long)CosPi16_64; + step2[10] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[13] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = (-step1[11] + step1[12]) * (long)CosPi16_64; + temp2 = (step1[11] + step1[12]) * (long)CosPi16_64; + step2[11] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step2[12] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + step2[14] = step1[14]; + step2[15] = step1[15]; + + step2[16] = HighbdWrapLow(step1[16] + step1[23], bd); + step2[17] = HighbdWrapLow(step1[17] + step1[22], bd); + step2[18] = HighbdWrapLow(step1[18] + step1[21], bd); + step2[19] = HighbdWrapLow(step1[19] + step1[20], bd); + step2[20] = HighbdWrapLow(step1[19] - step1[20], bd); + step2[21] = HighbdWrapLow(step1[18] - step1[21], bd); + step2[22] = HighbdWrapLow(step1[17] - step1[22], bd); + step2[23] = HighbdWrapLow(step1[16] - step1[23], bd); + + step2[24] = HighbdWrapLow(-step1[24] + step1[31], bd); + step2[25] = HighbdWrapLow(-step1[25] + step1[30], bd); + step2[26] = HighbdWrapLow(-step1[26] + step1[29], bd); + step2[27] = HighbdWrapLow(-step1[27] + step1[28], bd); + step2[28] = HighbdWrapLow(step1[27] + step1[28], bd); + step2[29] = HighbdWrapLow(step1[26] + step1[29], bd); + step2[30] = HighbdWrapLow(step1[25] + step1[30], bd); + step2[31] = HighbdWrapLow(step1[24] + step1[31], bd); + + // stage 7 + step1[0] = HighbdWrapLow(step2[0] + step2[15], bd); + step1[1] = HighbdWrapLow(step2[1] + step2[14], bd); + step1[2] = HighbdWrapLow(step2[2] + step2[13], bd); + step1[3] = HighbdWrapLow(step2[3] + step2[12], bd); + step1[4] = HighbdWrapLow(step2[4] + step2[11], bd); + step1[5] = HighbdWrapLow(step2[5] + step2[10], bd); + step1[6] = HighbdWrapLow(step2[6] + step2[9], bd); + step1[7] = HighbdWrapLow(step2[7] + step2[8], bd); + step1[8] = HighbdWrapLow(step2[7] - step2[8], bd); + step1[9] = HighbdWrapLow(step2[6] - step2[9], bd); + step1[10] = HighbdWrapLow(step2[5] - step2[10], bd); + step1[11] = HighbdWrapLow(step2[4] - step2[11], bd); + step1[12] = HighbdWrapLow(step2[3] - step2[12], bd); + step1[13] = HighbdWrapLow(step2[2] - step2[13], bd); + step1[14] = HighbdWrapLow(step2[1] - step2[14], bd); + step1[15] = HighbdWrapLow(step2[0] - step2[15], bd); + + step1[16] = step2[16]; + step1[17] = step2[17]; + step1[18] = step2[18]; + step1[19] = step2[19]; + temp1 = (-step2[20] + step2[27]) * (long)CosPi16_64; + temp2 = (step2[20] + step2[27]) * (long)CosPi16_64; + step1[20] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[27] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = (-step2[21] + step2[26]) * (long)CosPi16_64; + temp2 = (step2[21] + step2[26]) * (long)CosPi16_64; + step1[21] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[26] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = (-step2[22] + step2[25]) * (long)CosPi16_64; + temp2 = (step2[22] + step2[25]) * (long)CosPi16_64; + step1[22] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[25] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + temp1 = (-step2[23] + step2[24]) * (long)CosPi16_64; + temp2 = (step2[23] + step2[24]) * (long)CosPi16_64; + step1[23] = HighbdWrapLow(DctConstRoundShift(temp1), bd); + step1[24] = HighbdWrapLow(DctConstRoundShift(temp2), bd); + step1[28] = step2[28]; + step1[29] = step2[29]; + step1[30] = step2[30]; + step1[31] = step2[31]; + + // final stage + output[0] = HighbdWrapLow(step1[0] + step1[31], bd); + output[1] = HighbdWrapLow(step1[1] + step1[30], bd); + output[2] = HighbdWrapLow(step1[2] + step1[29], bd); + output[3] = HighbdWrapLow(step1[3] + step1[28], bd); + output[4] = HighbdWrapLow(step1[4] + step1[27], bd); + output[5] = HighbdWrapLow(step1[5] + step1[26], bd); + output[6] = HighbdWrapLow(step1[6] + step1[25], bd); + output[7] = HighbdWrapLow(step1[7] + step1[24], bd); + output[8] = HighbdWrapLow(step1[8] + step1[23], bd); + output[9] = HighbdWrapLow(step1[9] + step1[22], bd); + output[10] = HighbdWrapLow(step1[10] + step1[21], bd); + output[11] = HighbdWrapLow(step1[11] + step1[20], bd); + output[12] = HighbdWrapLow(step1[12] + step1[19], bd); + output[13] = HighbdWrapLow(step1[13] + step1[18], bd); + output[14] = HighbdWrapLow(step1[14] + step1[17], bd); + output[15] = HighbdWrapLow(step1[15] + step1[16], bd); + output[16] = HighbdWrapLow(step1[15] - step1[16], bd); + output[17] = HighbdWrapLow(step1[14] - step1[17], bd); + output[18] = HighbdWrapLow(step1[13] - step1[18], bd); + output[19] = HighbdWrapLow(step1[12] - step1[19], bd); + output[20] = HighbdWrapLow(step1[11] - step1[20], bd); + output[21] = HighbdWrapLow(step1[10] - step1[21], bd); + output[22] = HighbdWrapLow(step1[9] - step1[22], bd); + output[23] = HighbdWrapLow(step1[8] - step1[23], bd); + output[24] = HighbdWrapLow(step1[7] - step1[24], bd); + output[25] = HighbdWrapLow(step1[6] - step1[25], bd); + output[26] = HighbdWrapLow(step1[5] - step1[26], bd); + output[27] = HighbdWrapLow(step1[4] - step1[27], bd); + output[28] = HighbdWrapLow(step1[3] - step1[28], bd); + output[29] = HighbdWrapLow(step1[2] - step1[29], bd); + output[30] = HighbdWrapLow(step1[1] - step1[30], bd); + output[31] = HighbdWrapLow(step1[0] - step1[31], bd); + } + + [SkipLocalsInit] + public static void HighbdIdct32x321024Add(ReadOnlySpan input, Span dest, int stride, int bd) + { + int i, j; + Span output = stackalloc int[32 * 32]; + Span outptr = output; + Span tempIn = stackalloc int[32]; + Span tempOut = stackalloc int[32]; + + // Rows + for (i = 0; i < 32; ++i) + { + int zeroCoeff = 0; + for (j = 0; j < 32; ++j) + { + zeroCoeff |= input[j]; + } + + if (zeroCoeff != 0) + { + HighbdIdct32(input, outptr, bd); + } + else + { + outptr[..32].Clear(); + } + + input = input[32..]; + outptr = outptr[32..]; + } + + // Columns + for (i = 0; i < 32; ++i) + { + for (j = 0; j < 32; ++j) + { + tempIn[j] = output[j * 32 + i]; + } + + HighbdIdct32(tempIn, tempOut, bd); + for (j = 0; j < 32; ++j) + { + dest[j * stride + i] = HighbdClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 6), bd); + } + } + } + + [SkipLocalsInit] + public static void HighbdIdct32x32135Add(ReadOnlySpan input, Span dest, int stride, int bd) + { + int i, j; + Span output = stackalloc int[32 * 32]; + Span outptr = output; + Span tempIn = stackalloc int[32]; + Span tempOut = stackalloc int[32]; + + output.Clear(); + + // Rows + // Only upper-left 16x16 has non-zero coeff + for (i = 0; i < 16; ++i) + { + HighbdIdct32(input, outptr, bd); + input = input[32..]; + outptr = outptr[32..]; + } + + // Columns + for (i = 0; i < 32; ++i) + { + Span destT = dest; + for (j = 0; j < 32; ++j) + { + tempIn[j] = output[j * 32 + i]; + } + + HighbdIdct32(tempIn, tempOut, bd); + for (j = 0; j < 32; ++j) + { + destT[i] = HighbdClipPixelAdd(destT[i], BitUtils.RoundPowerOfTwo(tempOut[j], 6), bd); + destT = destT[stride..]; + } + } + } + + [SkipLocalsInit] + public static void HighbdIdct32x3234Add(ReadOnlySpan input, Span dest, int stride, int bd) + { + int i, j; + Span output = stackalloc int[32 * 32]; + Span outptr = output; + Span tempIn = stackalloc int[32]; + Span tempOut = stackalloc int[32]; + + output.Clear(); + + // Rows + // Only upper-left 8x8 has non-zero coeff + for (i = 0; i < 8; ++i) + { + HighbdIdct32(input, outptr, bd); + input = input[32..]; + outptr = outptr[32..]; + } + + // Columns + for (i = 0; i < 32; ++i) + { + for (j = 0; j < 32; ++j) + { + tempIn[j] = output[j * 32 + i]; + } + + HighbdIdct32(tempIn, tempOut, bd); + for (j = 0; j < 32; ++j) + { + dest[j * stride + i] = HighbdClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 6), bd); + } + } + } + + public static void HighbdIdct32x321Add(ReadOnlySpan input, Span dest, int stride, int bd) + { + int i, j; + int a1; + int output = HighbdWrapLow(DctConstRoundShift(input[0] * (long)CosPi16_64), bd); + + output = HighbdWrapLow(DctConstRoundShift(output * (long)CosPi16_64), bd); + a1 = BitUtils.RoundPowerOfTwo(output, 6); + + for (j = 0; j < 32; ++j) + { + for (i = 0; i < 32; ++i) + { + dest[i] = HighbdClipPixelAdd(dest[i], a1, bd); + } + + dest = dest[stride..]; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Prob.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Prob.cs new file mode 100644 index 00000000..4d75b35d --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Prob.cs @@ -0,0 +1,75 @@ +using Ryujinx.Graphics.Nvdec.Vp9.Common; +using System; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Dsp +{ + internal static class Prob + { + public const int MaxProb = 255; + + private static byte GetProb(uint num, uint den) + { + Debug.Assert(den != 0); + { + int p = (int)(((ulong)num * 256 + (den >> 1)) / den); + // (p > 255) ? 255 : (p < 1) ? 1 : p; + int clippedProb = p | ((255 - p) >> 23) | (p == 0 ? 1 : 0); + + return (byte)clippedProb; + } + } + + /* This function assumes prob1 and prob2 are already within [1,255] range. */ + public static byte WeightedProb(int prob1, int prob2, int factor) + { + return (byte)BitUtils.RoundPowerOfTwo(prob1 * (256 - factor) + prob2 * factor, 8); + } + + // MODE_MV_MAX_UPDATE_FACTOR (128) * count / MODE_MV_COUNT_SAT; + private static readonly uint[] _countToUpdateFactor = { + 0, 6, 12, 19, 25, 32, 38, 44, 51, 57, 64, + 70, 76, 83, 89, 96, 102, 108, 115, 121, 128, + }; + + private const int ModeMvCountSat = 20; + + public static byte ModeMvMergeProbs(byte preProb, uint ct0, uint ct1) + { + uint den = ct0 + ct1; + if (den == 0) + { + return preProb; + } + else + { + uint count = Math.Min(den, ModeMvCountSat); + uint factor = _countToUpdateFactor[(int)count]; + byte prob = GetProb(ct0, den); + + return WeightedProb(preProb, prob, (int)factor); + } + } + + private static uint TreeMergeProbsImpl( + uint i, + sbyte[] tree, + ReadOnlySpan preProbs, + ReadOnlySpan counts, + Span probs) + { + int l = tree[i]; + uint leftCount = (l <= 0) ? counts[-l] : TreeMergeProbsImpl((uint)l, tree, preProbs, counts, probs); + int r = tree[i + 1]; + uint rightCount = (r <= 0) ? counts[-r] : TreeMergeProbsImpl((uint)r, tree, preProbs, counts, probs); + probs[(int)(i >> 1)] = ModeMvMergeProbs(preProbs[(int)(i >> 1)], leftCount, rightCount); + + return leftCount + rightCount; + } + + public static void TreeMergeProbs(sbyte[] tree, ReadOnlySpan preProbs, ReadOnlySpan counts, Span probs) + { + TreeMergeProbsImpl(0, tree, preProbs, counts, probs); + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Reader.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Reader.cs new file mode 100644 index 00000000..60a20b43 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Reader.cs @@ -0,0 +1,236 @@ +using Ryujinx.Common.Memory; +using System; +using System.Buffers.Binary; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Dsp +{ + internal struct Reader + { + private static readonly byte[] _norm = { + 0, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + private const int BdValueSize = sizeof(ulong) * 8; + + // This is meant to be a large, positive constant that can still be efficiently + // loaded as an immediate (on platforms like ARM, for example). + // Even relatively modest values like 100 would work fine. + private const int LotsOfBits = 0x40000000; + + public ulong Value; + public uint Range; + public int Count; + private ArrayPtr _buffer; + + public bool Init(ArrayPtr buffer, int size) + { + if (size != 0 && buffer.IsNull) + { + return true; + } + else + { + _buffer = new ArrayPtr(ref buffer[0], size); + Value = 0; + Count = -8; + Range = 255; + Fill(); + + return ReadBit() != 0; // Marker bit + } + } + + private void Fill() + { + ReadOnlySpan buffer = _buffer.AsSpan(); + ReadOnlySpan bufferStart = buffer; + ulong value = Value; + int count = Count; + ulong bytesLeft = (ulong)buffer.Length; + ulong bitsLeft = bytesLeft * 8; + int shift = BdValueSize - 8 - (count + 8); + + if (bitsLeft > BdValueSize) + { + int bits = (shift & unchecked((int)0xfffffff8)) + 8; + ulong nv; + ulong bigEndianValues = BinaryPrimitives.ReadUInt64BigEndian(buffer); + nv = bigEndianValues >> (BdValueSize - bits); + count += bits; + buffer = buffer[(bits >> 3)..]; + value = Value | (nv << (shift & 0x7)); + } + else + { + int bitsOver = shift + 8 - (int)bitsLeft; + int loopEnd = 0; + if (bitsOver >= 0) + { + count += LotsOfBits; + loopEnd = bitsOver; + } + + if (bitsOver < 0 || bitsLeft != 0) + { + while (shift >= loopEnd) + { + count += 8; + value |= (ulong)buffer[0] << shift; + buffer = buffer[1..]; + shift -= 8; + } + } + } + + // NOTE: Variable 'buffer' may not relate to '_buffer' after decryption, + // so we increase '_buffer' by the amount that 'buffer' moved, rather than + // assign 'buffer' to '_buffer'. + _buffer = _buffer.Slice(bufferStart.Length - buffer.Length); + Value = value; + Count = count; + } + + public readonly bool HasError() + { + // Check if we have reached the end of the buffer. + // + // Variable 'count' stores the number of bits in the 'value' buffer, minus + // 8. The top byte is part of the algorithm, and the remainder is buffered + // to be shifted into it. So if count == 8, the top 16 bits of 'value' are + // occupied, 8 for the algorithm and 8 in the buffer. + // + // When reading a byte from the user's buffer, count is filled with 8 and + // one byte is filled into the value buffer. When we reach the end of the + // data, count is additionally filled with LotsOfBits. So when + // count == LotsOfBits - 1, the user's data has been exhausted. + // + // 1 if we have tried to decode bits after the end of stream was encountered. + // 0 No error. + return Count > BdValueSize && Count < LotsOfBits; + } + + public int Read(int prob) + { + uint bit = 0; + ulong value; + ulong bigsplit; + int count; + uint range; + uint split = (Range * (uint)prob + (256 - (uint)prob)) >> 8; + + if (Count < 0) + { + Fill(); + } + + value = Value; + count = Count; + + bigsplit = (ulong)split << (BdValueSize - 8); + + range = split; + + if (value >= bigsplit) + { + range = Range - split; + value -= bigsplit; + bit = 1; + } + + { + int shift = _norm[range]; + range <<= shift; + value <<= shift; + count -= shift; + } + Value = value; + Count = count; + Range = range; + + return (int)bit; + } + + public int ReadBit() + { + return Read(128); // vpx_prob_half + } + + public int ReadLiteral(int bits) + { + int literal = 0, bit; + + for (bit = bits - 1; bit >= 0; bit--) + { + literal |= ReadBit() << bit; + } + + return literal; + } + + public int ReadTree(ReadOnlySpan tree, ReadOnlySpan probs) + { + sbyte i = 0; + + while ((i = tree[i + Read(probs[i >> 1])]) > 0) + { + } + + return -i; + } + + public int ReadBool(int prob, ref ulong value, ref int count, ref uint range) + { + uint split = (range * (uint)prob + (256 - (uint)prob)) >> 8; + ulong bigsplit = (ulong)split << (BdValueSize - 8); + + if (count < 0) + { + Value = value; + Count = count; + Fill(); + value = Value; + count = Count; + } + + if (value >= bigsplit) + { + range -= split; + value -= bigsplit; + { + int shift = _norm[range]; + range <<= shift; + value <<= shift; + count -= shift; + } + return 1; + } + range = split; + { + int shift = _norm[range]; + range <<= shift; + value <<= shift; + count -= shift; + } + return 0; + } + + public ArrayPtr FindEnd() + { + // Find the end of the coded buffer + while (Count > 8 && Count < BdValueSize) + { + Count -= 8; + _buffer = _buffer.Slice(-1); + } + return _buffer; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/TxfmCommon.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/TxfmCommon.cs new file mode 100644 index 00000000..209f1951 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/TxfmCommon.cs @@ -0,0 +1,54 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Dsp +{ + internal static class TxfmCommon + { + // Constants used by all idct/dct functions + public const int DctConstBits = 14; + public const int DctConstRounding = 1 << (DctConstBits - 1); + + public const int UnitQuantShift = 2; + public const int UnitQuantFactor = 1 << UnitQuantShift; + + // Constants: + // for (int i = 1; i < 32; ++i) + // Console.WriteLine("public const short CosPi{0}_64 = {1};", i, MathF.Round(16384 * MathF.Cos(i * MathF.PI / 64))); + // Note: sin(k * Pi / 64) = cos((32 - k) * Pi / 64) + public const short CosPi1_64 = 16364; + public const short CosPi2_64 = 16305; + public const short CosPi3_64 = 16207; + public const short CosPi4_64 = 16069; + public const short CosPi5_64 = 15893; + public const short CosPi6_64 = 15679; + public const short CosPi7_64 = 15426; + public const short CosPi8_64 = 15137; + public const short CosPi9_64 = 14811; + public const short CosPi10_64 = 14449; + public const short CosPi11_64 = 14053; + public const short CosPi12_64 = 13623; + public const short CosPi13_64 = 13160; + public const short CosPi14_64 = 12665; + public const short CosPi15_64 = 12140; + public const short CosPi16_64 = 11585; + public const short CosPi17_64 = 11003; + public const short CosPi18_64 = 10394; + public const short CosPi19_64 = 9760; + public const short CosPi20_64 = 9102; + public const short CosPi21_64 = 8423; + public const short CosPi22_64 = 7723; + public const short CosPi23_64 = 7005; + public const short CosPi24_64 = 6270; + public const short CosPi25_64 = 5520; + public const short CosPi26_64 = 4756; + public const short CosPi27_64 = 3981; + public const short CosPi28_64 = 3196; + public const short CosPi29_64 = 2404; + public const short CosPi30_64 = 1606; + public const short CosPi31_64 = 804; + + // 16384 * sqrt(2) * sin(kPi / 9) * 2 / 3 + public const short SinPi1_9 = 5283; + public const short SinPi2_9 = 9929; + public const short SinPi3_9 = 13377; + public const short SinPi4_9 = 15212; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Idct.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Idct.cs new file mode 100644 index 00000000..ebebf0ef --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Idct.cs @@ -0,0 +1,530 @@ +using Ryujinx.Graphics.Nvdec.Vp9.Common; +using Ryujinx.Graphics.Nvdec.Vp9.Types; +using System; +using static Ryujinx.Graphics.Nvdec.Vp9.Dsp.InvTxfm; + +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal static class Idct + { + private delegate void Transform1D(ReadOnlySpan input, Span output); + private delegate void HighbdTransform1D(ReadOnlySpan input, Span output, int bd); + + private struct Transform2D + { + public Transform1D Cols, Rows; // Vertical and horizontal + + public Transform2D(Transform1D cols, Transform1D rows) + { + Cols = cols; + Rows = rows; + } + } + + private struct HighbdTransform2D + { + public HighbdTransform1D Cols, Rows; // Vertical and horizontal + + public HighbdTransform2D(HighbdTransform1D cols, HighbdTransform1D rows) + { + Cols = cols; + Rows = rows; + } + } + + private static readonly Transform2D[] _iht4 = { + new(Idct4, Idct4), // DCT_DCT = 0 + new(Iadst4, Idct4), // ADST_DCT = 1 + new(Idct4, Iadst4), // DCT_ADST = 2 + new(Iadst4, Iadst4), // ADST_ADST = 3 + }; + + public static void Iht4x416Add(ReadOnlySpan input, Span dest, int stride, int txType) + { + int i, j; + Span output = stackalloc int[4 * 4]; + Span outptr = output; + Span tempIn = stackalloc int[4]; + Span tempOut = stackalloc int[4]; + + // Inverse transform row vectors + for (i = 0; i < 4; ++i) + { + _iht4[txType].Rows(input, outptr); + input = input[4..]; + outptr = outptr[4..]; + } + + // Inverse transform column vectors + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + tempIn[j] = output[j * 4 + i]; + } + + _iht4[txType].Cols(tempIn, tempOut); + for (j = 0; j < 4; ++j) + { + dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 4)); + } + } + } + + private static readonly Transform2D[] _iht8 = { + new(Idct8, Idct8), // DCT_DCT = 0 + new(Iadst8, Idct8), // ADST_DCT = 1 + new(Idct8, Iadst8), // DCT_ADST = 2 + new(Iadst8, Iadst8), // ADST_ADST = 3 + }; + + public static void Iht8x864Add(ReadOnlySpan input, Span dest, int stride, int txType) + { + int i, j; + Span output = stackalloc int[8 * 8]; + Span outptr = output; + Span tempIn = stackalloc int[8]; + Span tempOut = stackalloc int[8]; + Transform2D ht = _iht8[txType]; + + // Inverse transform row vectors + for (i = 0; i < 8; ++i) + { + ht.Rows(input, outptr); + input = input[8..]; + outptr = outptr[8..]; + } + + // Inverse transform column vectors + for (i = 0; i < 8; ++i) + { + for (j = 0; j < 8; ++j) + { + tempIn[j] = output[j * 8 + i]; + } + + ht.Cols(tempIn, tempOut); + for (j = 0; j < 8; ++j) + { + dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 5)); + } + } + } + + private static readonly Transform2D[] _iht16 = { + new(Idct16, Idct16), // DCT_DCT = 0 + new(Iadst16, Idct16), // ADST_DCT = 1 + new(Idct16, Iadst16), // DCT_ADST = 2 + new(Iadst16, Iadst16), // ADST_ADST = 3 + }; + + public static void Iht16x16256Add(ReadOnlySpan input, Span dest, int stride, int txType) + { + int i, j; + Span output = stackalloc int[16 * 16]; + Span outptr = output; + Span tempIn = stackalloc int[16]; + Span tempOut = stackalloc int[16]; + Transform2D ht = _iht16[txType]; + + // Rows + for (i = 0; i < 16; ++i) + { + ht.Rows(input, outptr); + input = input[16..]; + outptr = outptr[16..]; + } + + // Columns + for (i = 0; i < 16; ++i) + { + for (j = 0; j < 16; ++j) + { + tempIn[j] = output[j * 16 + i]; + } + + ht.Cols(tempIn, tempOut); + for (j = 0; j < 16; ++j) + { + dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 6)); + } + } + } + + // Idct + public static void Idct4x4Add(ReadOnlySpan input, Span dest, int stride, int eob) + { + if (eob > 1) + { + Idct4x416Add(input, dest, stride); + } + else + { + Idct4x41Add(input, dest, stride); + } + } + + public static void Iwht4x4Add(ReadOnlySpan input, Span dest, int stride, int eob) + { + if (eob > 1) + { + Iwht4x416Add(input, dest, stride); + } + else + { + Iwht4x41Add(input, dest, stride); + } + } + + public static void Idct8x8Add(ReadOnlySpan input, Span dest, int stride, int eob) + { + // If dc is 1, then input[0] is the reconstructed value, do not need + // dequantization. Also, when dc is 1, dc is counted in eobs, namely eobs >=1. + + // The calculation can be simplified if there are not many non-zero dct + // coefficients. Use eobs to decide what to do. + if (eob == 1) + { + // DC only DCT coefficient + Idct8x81Add(input, dest, stride); + } + else if (eob <= 12) + { + Idct8x812Add(input, dest, stride); + } + else + { + Idct8x864Add(input, dest, stride); + } + } + + public static void Idct16x16Add(ReadOnlySpan input, Span dest, int stride, int eob) + { + /* The calculation can be simplified if there are not many non-zero dct + * coefficients. Use eobs to separate different cases. */ + if (eob == 1) /* DC only DCT coefficient. */ + { + Idct16x161Add(input, dest, stride); + } + else if (eob <= 10) + { + Idct16x1610Add(input, dest, stride); + } + else if (eob <= 38) + { + Idct16x1638Add(input, dest, stride); + } + else + { + Idct16x16256Add(input, dest, stride); + } + } + + public static void Idct32x32Add(ReadOnlySpan input, Span dest, int stride, int eob) + { + if (eob == 1) + { + Idct32x321Add(input, dest, stride); + } + else if (eob <= 34) + { + // Non-zero coeff only in upper-left 8x8 + Idct32x3234Add(input, dest, stride); + } + else if (eob <= 135) + { + // Non-zero coeff only in upper-left 16x16 + Idct32x32135Add(input, dest, stride); + } + else + { + Idct32x321024Add(input, dest, stride); + } + } + + // Iht + public static void Iht4x4Add(TxType txType, ReadOnlySpan input, Span dest, int stride, int eob) + { + if (txType == TxType.DctDct) + { + Idct4x4Add(input, dest, stride, eob); + } + else + { + Iht4x416Add(input, dest, stride, (int)txType); + } + } + + public static void Iht8x8Add(TxType txType, ReadOnlySpan input, Span dest, int stride, int eob) + { + if (txType == TxType.DctDct) + { + Idct8x8Add(input, dest, stride, eob); + } + else + { + Iht8x864Add(input, dest, stride, (int)txType); + } + } + + public static void Iht16x16Add(TxType txType, ReadOnlySpan input, Span dest, + int stride, int eob) + { + if (txType == TxType.DctDct) + { + Idct16x16Add(input, dest, stride, eob); + } + else + { + Iht16x16256Add(input, dest, stride, (int)txType); + } + } + + private static readonly HighbdTransform2D[] _highbdIht4 = { + new(HighbdIdct4, HighbdIdct4), // DCT_DCT = 0 + new(HighbdIadst4, HighbdIdct4), // ADST_DCT = 1 + new(HighbdIdct4, HighbdIadst4), // DCT_ADST = 2 + new(HighbdIadst4, HighbdIadst4), // ADST_ADST = 3 + }; + + public static void HighbdIht4x416Add(ReadOnlySpan input, Span dest, int stride, int txType, int bd) + { + int i, j; + Span output = stackalloc int[4 * 4]; + Span outptr = output; + Span tempIn = stackalloc int[4]; + Span tempOut = stackalloc int[4]; + + // Inverse transform row vectors. + for (i = 0; i < 4; ++i) + { + _highbdIht4[txType].Rows(input, outptr, bd); + input = input[4..]; + outptr = outptr[4..]; + } + + // Inverse transform column vectors. + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + tempIn[j] = output[j * 4 + i]; + } + + _highbdIht4[txType].Cols(tempIn, tempOut, bd); + for (j = 0; j < 4; ++j) + { + dest[j * stride + i] = HighbdClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 4), bd); + } + } + } + + private static readonly HighbdTransform2D[] _highIht8 = { + new(HighbdIdct8, HighbdIdct8), // DCT_DCT = 0 + new(HighbdIadst8, HighbdIdct8), // ADST_DCT = 1 + new(HighbdIdct8, HighbdIadst8), // DCT_ADST = 2 + new(HighbdIadst8, HighbdIadst8), // ADST_ADST = 3 + }; + + public static void HighbdIht8x864Add(ReadOnlySpan input, Span dest, int stride, int txType, int bd) + { + int i, j; + Span output = stackalloc int[8 * 8]; + Span outptr = output; + Span tempIn = stackalloc int[8]; + Span tempOut = stackalloc int[8]; + HighbdTransform2D ht = _highIht8[txType]; + + // Inverse transform row vectors. + for (i = 0; i < 8; ++i) + { + ht.Rows(input, outptr, bd); + input = input[8..]; + outptr = output[8..]; + } + + // Inverse transform column vectors. + for (i = 0; i < 8; ++i) + { + for (j = 0; j < 8; ++j) + { + tempIn[j] = output[j * 8 + i]; + } + + ht.Cols(tempIn, tempOut, bd); + for (j = 0; j < 8; ++j) + { + dest[j * stride + i] = HighbdClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 5), bd); + } + } + } + + private static readonly HighbdTransform2D[] _highIht16 = { + new(HighbdIdct16, HighbdIdct16), // DCT_DCT = 0 + new(HighbdIadst16, HighbdIdct16), // ADST_DCT = 1 + new(HighbdIdct16, HighbdIadst16), // DCT_ADST = 2 + new(HighbdIadst16, HighbdIadst16), // ADST_ADST = 3 + }; + + public static void HighbdIht16x16256Add(ReadOnlySpan input, Span dest, int stride, int txType, int bd) + { + int i, j; + Span output = stackalloc int[16 * 16]; + Span outptr = output; + Span tempIn = stackalloc int[16]; + Span tempOut = stackalloc int[16]; + HighbdTransform2D ht = _highIht16[txType]; + + // Rows + for (i = 0; i < 16; ++i) + { + ht.Rows(input, outptr, bd); + input = input[16..]; + outptr = output[16..]; + } + + // Columns + for (i = 0; i < 16; ++i) + { + for (j = 0; j < 16; ++j) + { + tempIn[j] = output[j * 16 + i]; + } + + ht.Cols(tempIn, tempOut, bd); + for (j = 0; j < 16; ++j) + { + dest[j * stride + i] = HighbdClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 6), bd); + } + } + } + + // Idct + public static void HighbdIdct4x4Add(ReadOnlySpan input, Span dest, int stride, int eob, int bd) + { + if (eob > 1) + { + HighbdIdct4x416Add(input, dest, stride, bd); + } + else + { + HighbdIdct4x41Add(input, dest, stride, bd); + } + } + + public static void HighbdIwht4x4Add(ReadOnlySpan input, Span dest, int stride, int eob, int bd) + { + if (eob > 1) + { + HighbdIwht4x416Add(input, dest, stride, bd); + } + else + { + HighbdIwht4x41Add(input, dest, stride, bd); + } + } + + public static void HighbdIdct8x8Add(ReadOnlySpan input, Span dest, int stride, int eob, int bd) + { + // If dc is 1, then input[0] is the reconstructed value, do not need + // dequantization. Also, when dc is 1, dc is counted in eobs, namely eobs >=1. + + // The calculation can be simplified if there are not many non-zero dct + // coefficients. Use eobs to decide what to do. + // DC only DCT coefficient + if (eob == 1) + { + Vpx_Highbdidct8x8_1_add_c(input, dest, stride, bd); + } + else if (eob <= 12) + { + HighbdIdct8x812Add(input, dest, stride, bd); + } + else + { + HighbdIdct8x864Add(input, dest, stride, bd); + } + } + + public static void HighbdIdct16x16Add(ReadOnlySpan input, Span dest, int stride, int eob, int bd) + { + // The calculation can be simplified if there are not many non-zero dct + // coefficients. Use eobs to separate different cases. + // DC only DCT coefficient. + if (eob == 1) + { + HighbdIdct16x161Add(input, dest, stride, bd); + } + else if (eob <= 10) + { + HighbdIdct16x1610Add(input, dest, stride, bd); + } + else if (eob <= 38) + { + HighbdIdct16x1638Add(input, dest, stride, bd); + } + else + { + HighbdIdct16x16256Add(input, dest, stride, bd); + } + } + + public static void HighbdIdct32x32Add(ReadOnlySpan input, Span dest, int stride, int eob, int bd) + { + // Non-zero coeff only in upper-left 8x8 + if (eob == 1) + { + HighbdIdct32x321Add(input, dest, stride, bd); + } + else if (eob <= 34) + { + HighbdIdct32x3234Add(input, dest, stride, bd); + } + else if (eob <= 135) + { + HighbdIdct32x32135Add(input, dest, stride, bd); + } + else + { + HighbdIdct32x321024Add(input, dest, stride, bd); + } + } + + // Iht + public static void HighbdIht4x4Add(TxType txType, ReadOnlySpan input, Span dest, int stride, int eob, int bd) + { + if (txType == TxType.DctDct) + { + HighbdIdct4x4Add(input, dest, stride, eob, bd); + } + else + { + HighbdIht4x416Add(input, dest, stride, (int)txType, bd); + } + } + + public static void HighbdIht8x8Add(TxType txType, ReadOnlySpan input, Span dest, int stride, int eob, int bd) + { + if (txType == TxType.DctDct) + { + HighbdIdct8x8Add(input, dest, stride, eob, bd); + } + else + { + HighbdIht8x864Add(input, dest, stride, (int)txType, bd); + } + } + + public static void HighbdIht16x16Add(TxType txType, ReadOnlySpan input, Span dest, int stride, int eob, int bd) + { + if (txType == TxType.DctDct) + { + HighbdIdct16x16Add(input, dest, stride, eob, bd); + } + else + { + HighbdIht16x16256Add(input, dest, stride, (int)txType, bd); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/InternalErrorException.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/InternalErrorException.cs new file mode 100644 index 00000000..d4ba0d88 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/InternalErrorException.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + class InternalErrorException : Exception + { + public InternalErrorException(string message) : base(message) + { + } + + public InternalErrorException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/InternalErrorInfo.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/InternalErrorInfo.cs new file mode 100644 index 00000000..cfec81ed --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/InternalErrorInfo.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal struct InternalErrorInfo + { + public CodecErr ErrorCode; + + public void InternalError(CodecErr error, string message) + { + ErrorCode = error; + + throw new InternalErrorException(message); + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/LoopFilter.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/LoopFilter.cs new file mode 100644 index 00000000..9b2564d7 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/LoopFilter.cs @@ -0,0 +1,408 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Nvdec.Vp9.Common; +using Ryujinx.Graphics.Nvdec.Vp9.Types; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal static class LoopFilter + { + public const int MaxLoopFilter = 63; + + public const int MaxRefLfDeltas = 4; + public const int MaxModeLfDeltas = 2; + + // 64 bit masks for left transform size. Each 1 represents a position where + // we should apply a loop filter across the left border of an 8x8 block + // boundary. + // + // In the case of TX_16X16 -> ( in low order byte first we end up with + // a mask that looks like this + // + // 10101010 + // 10101010 + // 10101010 + // 10101010 + // 10101010 + // 10101010 + // 10101010 + // 10101010 + // + // A loopfilter should be applied to every other 8x8 horizontally. + private static readonly ulong[] _left64X64TxformMask = { + 0xffffffffffffffffUL, // TX_4X4 + 0xffffffffffffffffUL, // TX_8x8 + 0x5555555555555555UL, // TX_16x16 + 0x1111111111111111UL, // TX_32x32 + }; + + // 64 bit masks for above transform size. Each 1 represents a position where + // we should apply a loop filter across the top border of an 8x8 block + // boundary. + // + // In the case of TX_32x32 -> ( in low order byte first we end up with + // a mask that looks like this + // + // 11111111 + // 00000000 + // 00000000 + // 00000000 + // 11111111 + // 00000000 + // 00000000 + // 00000000 + // + // A loopfilter should be applied to every other 4 the row vertically. + private static readonly ulong[] _above64X64TxformMask = { + 0xffffffffffffffffUL, // TX_4X4 + 0xffffffffffffffffUL, // TX_8x8 + 0x00ff00ff00ff00ffUL, // TX_16x16 + 0x000000ff000000ffUL, // TX_32x32 + }; + + // 64 bit masks for prediction sizes (left). Each 1 represents a position + // where left border of an 8x8 block. These are aligned to the right most + // appropriate bit, and then shifted into place. + // + // In the case of TX_16x32 -> ( low order byte first ) we end up with + // a mask that looks like this : + // + // 10000000 + // 10000000 + // 10000000 + // 10000000 + // 00000000 + // 00000000 + // 00000000 + // 00000000 + private static readonly ulong[] _leftPredictionMask = { + 0x0000000000000001UL, // BLOCK_4X4, + 0x0000000000000001UL, // BLOCK_4X8, + 0x0000000000000001UL, // BLOCK_8X4, + 0x0000000000000001UL, // BLOCK_8X8, + 0x0000000000000101UL, // BLOCK_8X16, + 0x0000000000000001UL, // BLOCK_16X8, + 0x0000000000000101UL, // BLOCK_16X16, + 0x0000000001010101UL, // BLOCK_16X32, + 0x0000000000000101UL, // BLOCK_32X16, + 0x0000000001010101UL, // BLOCK_32X32, + 0x0101010101010101UL, // BLOCK_32X64, + 0x0000000001010101UL, // BLOCK_64X32, + 0x0101010101010101UL, // BLOCK_64X64 + }; + + // 64 bit mask to shift and set for each prediction size. + private static readonly ulong[] _abovePredictionMask = { + 0x0000000000000001UL, // BLOCK_4X4 + 0x0000000000000001UL, // BLOCK_4X8 + 0x0000000000000001UL, // BLOCK_8X4 + 0x0000000000000001UL, // BLOCK_8X8 + 0x0000000000000001UL, // BLOCK_8X16, + 0x0000000000000003UL, // BLOCK_16X8 + 0x0000000000000003UL, // BLOCK_16X16 + 0x0000000000000003UL, // BLOCK_16X32, + 0x000000000000000fUL, // BLOCK_32X16, + 0x000000000000000fUL, // BLOCK_32X32, + 0x000000000000000fUL, // BLOCK_32X64, + 0x00000000000000ffUL, // BLOCK_64X32, + 0x00000000000000ffUL, // BLOCK_64X64 + }; + + // 64 bit mask to shift and set for each prediction size. A bit is set for + // each 8x8 block that would be in the left most block of the given block + // size in the 64x64 block. + private static readonly ulong[] _sizeMask = { + 0x0000000000000001UL, // BLOCK_4X4 + 0x0000000000000001UL, // BLOCK_4X8 + 0x0000000000000001UL, // BLOCK_8X4 + 0x0000000000000001UL, // BLOCK_8X8 + 0x0000000000000101UL, // BLOCK_8X16, + 0x0000000000000003UL, // BLOCK_16X8 + 0x0000000000000303UL, // BLOCK_16X16 + 0x0000000003030303UL, // BLOCK_16X32, + 0x0000000000000f0fUL, // BLOCK_32X16, + 0x000000000f0f0f0fUL, // BLOCK_32X32, + 0x0f0f0f0f0f0f0f0fUL, // BLOCK_32X64, + 0x00000000ffffffffUL, // BLOCK_64X32, + 0xffffffffffffffffUL, // BLOCK_64X64 + }; + + // These are used for masking the left and above borders. +#pragma warning disable IDE0051 // Remove unused private member + private const ulong LeftBorder = 0x1111111111111111UL; + private const ulong AboveBorder = 0x000000ff000000ffUL; +#pragma warning restore IDE0051 + + // 16 bit masks for uv transform sizes. + private static readonly ushort[] _left64X64TxformMaskUv = { + 0xffff, // TX_4X4 + 0xffff, // TX_8x8 + 0x5555, // TX_16x16 + 0x1111, // TX_32x32 + }; + + private static readonly ushort[] _above64X64TxformMaskUv = { + 0xffff, // TX_4X4 + 0xffff, // TX_8x8 + 0x0f0f, // TX_16x16 + 0x000f, // TX_32x32 + }; + + // 16 bit left mask to shift and set for each uv prediction size. + private static readonly ushort[] _leftPredictionMaskUv = { + 0x0001, // BLOCK_4X4, + 0x0001, // BLOCK_4X8, + 0x0001, // BLOCK_8X4, + 0x0001, // BLOCK_8X8, + 0x0001, // BLOCK_8X16, + 0x0001, // BLOCK_16X8, + 0x0001, // BLOCK_16X16, + 0x0011, // BLOCK_16X32, + 0x0001, // BLOCK_32X16, + 0x0011, // BLOCK_32X32, + 0x1111, // BLOCK_32X64 + 0x0011, // BLOCK_64X32, + 0x1111, // BLOCK_64X64 + }; + + // 16 bit above mask to shift and set for uv each prediction size. + private static readonly ushort[] _abovePredictionMaskUv = { + 0x0001, // BLOCK_4X4 + 0x0001, // BLOCK_4X8 + 0x0001, // BLOCK_8X4 + 0x0001, // BLOCK_8X8 + 0x0001, // BLOCK_8X16, + 0x0001, // BLOCK_16X8 + 0x0001, // BLOCK_16X16 + 0x0001, // BLOCK_16X32, + 0x0003, // BLOCK_32X16, + 0x0003, // BLOCK_32X32, + 0x0003, // BLOCK_32X64, + 0x000f, // BLOCK_64X32, + 0x000f, // BLOCK_64X64 + }; + + // 64 bit mask to shift and set for each uv prediction size + private static readonly ushort[] _sizeMaskUv = { + 0x0001, // BLOCK_4X4 + 0x0001, // BLOCK_4X8 + 0x0001, // BLOCK_8X4 + 0x0001, // BLOCK_8X8 + 0x0001, // BLOCK_8X16, + 0x0001, // BLOCK_16X8 + 0x0001, // BLOCK_16X16 + 0x0011, // BLOCK_16X32, + 0x0003, // BLOCK_32X16, + 0x0033, // BLOCK_32X32, + 0x3333, // BLOCK_32X64, + 0x00ff, // BLOCK_64X32, + 0xffff, // BLOCK_64X64 + }; + +#pragma warning disable IDE0051 // Remove unused private member + private const ushort LeftBorderUv = 0x1111; + private const ushort AboveBorderUv = 0x000f; +#pragma warning restore IDE0051 + + private static readonly int[] _modeLfLut = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // INTRA_MODES + 1, 1, 0, 1, // INTER_MODES (ZEROMV == 0) + }; + + private static byte GetFilterLevel(ref LoopFilterInfoN lfiN, ref ModeInfo mi) + { + return lfiN.Lvl[mi.SegmentId][mi.RefFrame[0]][_modeLfLut[(int)mi.Mode]]; + } + + private static ref LoopFilterMask GetLfm(ref Types.LoopFilter lf, int miRow, int miCol) + { + return ref lf.Lfm[(miCol >> 3) + ((miRow >> 3) * lf.LfmStride)]; + } + + // 8x8 blocks in a superblock. A "1" represents the first block in a 16x16 + // or greater area. + private static readonly byte[][] _firstBlockIn16X16 = { + new byte[] { 1, 0, 1, 0, 1, 0, 1, 0 }, new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, + new byte[] { 1, 0, 1, 0, 1, 0, 1, 0 }, new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, + new byte[] { 1, 0, 1, 0, 1, 0, 1, 0 }, new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, + new byte[] { 1, 0, 1, 0, 1, 0, 1, 0 }, new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, + }; + + // This function sets up the bit masks for a block represented + // by miRow, miCol in a 64x64 region. + public static void BuildMask(ref Vp9Common cm, ref ModeInfo mi, int miRow, int miCol, int bw, int bh) + { + BlockSize blockSize = mi.SbType; + TxSize txSizeY = mi.TxSize; + ref LoopFilterInfoN lfiN = ref cm.LfInfo; + int filterLevel = GetFilterLevel(ref lfiN, ref mi); + TxSize txSizeUv = Luts.UvTxsizeLookup[(int)blockSize][(int)txSizeY][1][1]; + ref LoopFilterMask lfm = ref GetLfm(ref cm.Lf, miRow, miCol); + ref ulong leftY = ref lfm.LeftY[(int)txSizeY]; + ref ulong aboveY = ref lfm.AboveY[(int)txSizeY]; + ref ulong int4X4Y = ref lfm.Int4x4Y; + ref ushort leftUv = ref lfm.LeftUv[(int)txSizeUv]; + ref ushort aboveUv = ref lfm.AboveUv[(int)txSizeUv]; + ref ushort int4X4Uv = ref lfm.Int4x4Uv; + int rowInSb = (miRow & 7); + int colInSb = (miCol & 7); + int shiftY = colInSb + (rowInSb << 3); + int shiftUv = (colInSb >> 1) + ((rowInSb >> 1) << 2); + int buildUv = _firstBlockIn16X16[rowInSb][colInSb]; + + if (filterLevel == 0) + { + return; + } + + int index = shiftY; + int i; + for (i = 0; i < bh; i++) + { + MemoryMarshal.CreateSpan(ref lfm.LflY[index], 64 - index)[..bw].Fill((byte)filterLevel); + index += 8; + } + + // These set 1 in the current block size for the block size edges. + // For instance if the block size is 32x16, we'll set: + // above = 1111 + // 0000 + // and + // left = 1000 + // = 1000 + // NOTE : In this example the low bit is left most ( 1000 ) is stored as + // 1, not 8... + // + // U and V set things on a 16 bit scale. + // + aboveY |= _abovePredictionMask[(int)blockSize] << shiftY; + leftY |= _leftPredictionMask[(int)blockSize] << shiftY; + + if (buildUv != 0) + { + aboveUv |= (ushort)(_abovePredictionMaskUv[(int)blockSize] << shiftUv); + leftUv |= (ushort)(_leftPredictionMaskUv[(int)blockSize] << shiftUv); + } + + // If the block has no coefficients and is not intra we skip applying + // the loop filter on block edges. + if (mi.Skip != 0 && mi.IsInterBlock()) + { + return; + } + + // Add a mask for the transform size. The transform size mask is set to + // be correct for a 64x64 prediction block size. Mask to match the size of + // the block we are working on and then shift it into place. + aboveY |= (_sizeMask[(int)blockSize] & _above64X64TxformMask[(int)txSizeY]) << shiftY; + leftY |= (_sizeMask[(int)blockSize] & _left64X64TxformMask[(int)txSizeY]) << shiftY; + + if (buildUv != 0) + { + aboveUv |= (ushort)((_sizeMaskUv[(int)blockSize] & _above64X64TxformMaskUv[(int)txSizeUv]) << shiftUv); + leftUv |= (ushort)((_sizeMaskUv[(int)blockSize] & _left64X64TxformMaskUv[(int)txSizeUv]) << shiftUv); + } + + // Try to determine what to do with the internal 4x4 block boundaries. These + // differ from the 4x4 boundaries on the outside edge of an 8x8 in that the + // internal ones can be skipped and don't depend on the prediction block size. + if (txSizeY == TxSize.Tx4x4) + { + int4X4Y |= _sizeMask[(int)blockSize] << shiftY; + } + + if (buildUv != 0 && txSizeUv == TxSize.Tx4x4) + { + int4X4Uv |= (ushort)((_sizeMaskUv[(int)blockSize] & 0xffff) << shiftUv); + } + } + + public static unsafe void ResetLfm(ref Vp9Common cm) + { + if (cm.Lf.FilterLevel != 0) + { + MemoryUtil.Fill(cm.Lf.Lfm.ToPointer(), new LoopFilterMask(), ((cm.MiRows + (Constants.MiBlockSize - 1)) >> 3) * cm.Lf.LfmStride); + } + } + + private static void UpdateSharpness(ref LoopFilterInfoN lfi, int sharpnessLvl) + { + int lvl; + + // For each possible value for the loop filter fill out limits + for (lvl = 0; lvl <= MaxLoopFilter; lvl++) + { + // Set loop filter parameters that control sharpness. + int blockInsideLimit = lvl >> ((sharpnessLvl > 0 ? 1 : 0) + (sharpnessLvl > 4 ? 1 : 0)); + + if (sharpnessLvl > 0) + { + if (blockInsideLimit > (9 - sharpnessLvl)) + { + blockInsideLimit = (9 - sharpnessLvl); + } + } + + if (blockInsideLimit < 1) + { + blockInsideLimit = 1; + } + + lfi.Lfthr[lvl].Lim.AsSpan().Fill((byte)blockInsideLimit); + lfi.Lfthr[lvl].Mblim.AsSpan().Fill((byte)(2 * (lvl + 2) + blockInsideLimit)); + } + } + + public static void LoopFilterFrameInit(ref Vp9Common cm, int defaultFiltLvl) + { + int segId; + // nShift is the multiplier for lfDeltas + // the multiplier is 1 for when filterLvl is between 0 and 31; + // 2 when filterLvl is between 32 and 63 + int scale = 1 << (defaultFiltLvl >> 5); + ref LoopFilterInfoN lfi = ref cm.LfInfo; + ref Types.LoopFilter lf = ref cm.Lf; + ref Segmentation seg = ref cm.Seg; + + // Update limits if sharpness has changed + if (lf.LastSharpnessLevel != lf.SharpnessLevel) + { + UpdateSharpness(ref lfi, lf.SharpnessLevel); + lf.LastSharpnessLevel = lf.SharpnessLevel; + } + + for (segId = 0; segId < Constants.MaxSegments; segId++) + { + int lvlSeg = defaultFiltLvl; + if (seg.IsSegFeatureActive(segId, SegLvlFeatures.SegLvlAltLf) != 0) + { + int data = seg.GetSegData(segId, SegLvlFeatures.SegLvlAltLf); + lvlSeg = Math.Clamp(seg.AbsDelta == Constants.SegmentAbsData ? data : defaultFiltLvl + data, 0, MaxLoopFilter); + } + + if (!lf.ModeRefDeltaEnabled) + { + // We could get rid of this if we assume that deltas are set to + // zero when not in use; encoder always uses deltas + MemoryMarshal.Cast, byte>(lfi.Lvl[segId].AsSpan()).Fill((byte)lvlSeg); + } + else + { + int refr, mode; + int intraLvl = lvlSeg + lf.RefDeltas[Constants.IntraFrame] * scale; + lfi.Lvl[segId][Constants.IntraFrame][0] = (byte)Math.Clamp(intraLvl, 0, MaxLoopFilter); + + for (refr = Constants.LastFrame; refr < Constants.MaxRefFrames; ++refr) + { + for (mode = 0; mode < MaxModeLfDeltas; ++mode) + { + int interLvl = lvlSeg + lf.RefDeltas[refr] * scale + lf.ModeDeltas[mode] * scale; + lfi.Lvl[segId][refr][mode] = (byte)Math.Clamp(interLvl, 0, MaxLoopFilter); + } + } + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Luts.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Luts.cs new file mode 100644 index 00000000..5245b3f3 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Luts.cs @@ -0,0 +1,1581 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Nvdec.Vp9.Types; +using System; + +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal static class Luts + { + public static ReadOnlySpan SizeGroupLookup => new byte[] { 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3 }; + + public static readonly BlockSize[][] SubsizeLookup = { + new[] + { // PARTITION_NONE + BlockSize.Block4x4, BlockSize.Block4x8, BlockSize.Block8x4, BlockSize.Block8x8, BlockSize.Block8x16, BlockSize.Block16x8, + BlockSize.Block16x16, BlockSize.Block16x32, BlockSize.Block32x16, BlockSize.Block32x32, BlockSize.Block32x64, + BlockSize.Block64x32, BlockSize.Block64x64, + }, + new[] + { // PARTITION_HORZ + BlockSize.BlockInvalid, BlockSize.BlockInvalid, BlockSize.BlockInvalid, BlockSize.Block8x4, BlockSize.BlockInvalid, + BlockSize.BlockInvalid, BlockSize.Block16x8, BlockSize.BlockInvalid, BlockSize.BlockInvalid, BlockSize.Block32x16, + BlockSize.BlockInvalid, BlockSize.BlockInvalid, BlockSize.Block64x32, + }, + new[] + { // PARTITION_VERT + BlockSize.BlockInvalid, BlockSize.BlockInvalid, BlockSize.BlockInvalid, BlockSize.Block4x8, BlockSize.BlockInvalid, + BlockSize.BlockInvalid, BlockSize.Block8x16, BlockSize.BlockInvalid, BlockSize.BlockInvalid, BlockSize.Block16x32, + BlockSize.BlockInvalid, BlockSize.BlockInvalid, BlockSize.Block32x64, + }, + new[] + { // PARTITION_SPLIT + BlockSize.BlockInvalid, BlockSize.BlockInvalid, BlockSize.BlockInvalid, BlockSize.Block4x4, BlockSize.BlockInvalid, + BlockSize.BlockInvalid, BlockSize.Block8x8, BlockSize.BlockInvalid, BlockSize.BlockInvalid, BlockSize.Block16x16, + BlockSize.BlockInvalid, BlockSize.BlockInvalid, BlockSize.Block32x32, + }, + }; + + public static readonly TxSize[] MaxTxSizeLookup = { + TxSize.Tx4x4, TxSize.Tx4x4, TxSize.Tx4x4, TxSize.Tx8x8, TxSize.Tx8x8, TxSize.Tx8x8, TxSize.Tx16x16, + TxSize.Tx16x16, TxSize.Tx16x16, TxSize.Tx32x32, TxSize.Tx32x32, TxSize.Tx32x32, TxSize.Tx32x32, + }; + + public static readonly TxSize[] TxModeToBiggestTxSize = { + TxSize.Tx4x4, // ONLY_4X4 + TxSize.Tx8x8, // ALLOW_8X8 + TxSize.Tx16x16, // ALLOW_16X16 + TxSize.Tx32x32, // ALLOW_32X32 + TxSize.Tx32x32, // TX_MODE_SELECT + }; + + public static readonly BlockSize[][][] SsSizeLookup = { + // ss_x == 0 ss_x == 0 ss_x == 1 ss_x == 1 + // ss_y == 0 ss_y == 1 ss_y == 0 ss_y == 1 + new[] { new[] { BlockSize.Block4x4, BlockSize.BlockInvalid }, new[] { BlockSize.BlockInvalid, BlockSize.BlockInvalid } }, + new[] { new[] { BlockSize.Block4x8, BlockSize.Block4x4 }, new[] { BlockSize.BlockInvalid, BlockSize.BlockInvalid } }, + new[] { new[] { BlockSize.Block8x4, BlockSize.BlockInvalid }, new[] { BlockSize.Block4x4, BlockSize.BlockInvalid } }, + new[] { new[] { BlockSize.Block8x8, BlockSize.Block8x4 }, new[] { BlockSize.Block4x8, BlockSize.Block4x4 } }, + new[] { new[] { BlockSize.Block8x16, BlockSize.Block8x8 }, new[] { BlockSize.BlockInvalid, BlockSize.Block4x8 } }, + new[] { new[] { BlockSize.Block16x8, BlockSize.BlockInvalid }, new[] { BlockSize.Block8x8, BlockSize.Block8x4 } }, + new[] { new[] { BlockSize.Block16x16, BlockSize.Block16x8 }, new[] { BlockSize.Block8x16, BlockSize.Block8x8 } }, + new[] { new[] { BlockSize.Block16x32, BlockSize.Block16x16 }, new[] { BlockSize.BlockInvalid, BlockSize.Block8x16 } }, + new[] { new[] { BlockSize.Block32x16, BlockSize.BlockInvalid }, new[] { BlockSize.Block16x16, BlockSize.Block16x8 } }, + new[] { new[] { BlockSize.Block32x32, BlockSize.Block32x16 }, new[] { BlockSize.Block16x32, BlockSize.Block16x16 } }, + new[] { new[] { BlockSize.Block32x64, BlockSize.Block32x32 }, new[] { BlockSize.BlockInvalid, BlockSize.Block16x32 } }, + new[] { new[] { BlockSize.Block64x32, BlockSize.BlockInvalid }, new[] { BlockSize.Block32x32, BlockSize.Block32x16 } }, + new[] { new[] { BlockSize.Block64x64, BlockSize.Block64x32 }, new[] { BlockSize.Block32x64, BlockSize.Block32x32 } }, + }; + + public static readonly TxSize[][][][] UvTxsizeLookup = { + // ss_x == 0 ss_x == 0 ss_x == 1 ss_x == 1 + // ss_y == 0 ss_y == 1 ss_y == 0 ss_y == 1 + new[] + { + // BLOCK_4X4 + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + }, + new[] + { + // BLOCK_4X8 + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + }, + new[] + { + // BLOCK_8X4 + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + }, + new[] + { + // BLOCK_8X8 + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + }, + new[] + { + // BLOCK_8X16 + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx8x8 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx8x8 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx8x8 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + }, + new[] + { + // BLOCK_16X8 + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx4x4 }, new[] { TxSize.Tx8x8, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx4x4 }, new[] { TxSize.Tx8x8, TxSize.Tx8x8 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx4x4 }, new[] { TxSize.Tx8x8, TxSize.Tx8x8 } }, + }, + new[] + { + // BLOCK_16X16 + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx8x8 }, new[] { TxSize.Tx8x8, TxSize.Tx8x8 } }, + new[] { new[] { TxSize.Tx16x16, TxSize.Tx8x8 }, new[] { TxSize.Tx8x8, TxSize.Tx8x8 } }, + new[] { new[] { TxSize.Tx16x16, TxSize.Tx8x8 }, new[] { TxSize.Tx8x8, TxSize.Tx8x8 } }, + }, + new[] + { + // BLOCK_16X32 + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx8x8 }, new[] { TxSize.Tx8x8, TxSize.Tx8x8 } }, + new[] { new[] { TxSize.Tx16x16, TxSize.Tx16x16 }, new[] { TxSize.Tx8x8, TxSize.Tx8x8 } }, + new[] { new[] { TxSize.Tx16x16, TxSize.Tx16x16 }, new[] { TxSize.Tx8x8, TxSize.Tx8x8 } }, + }, + new[] + { + // BLOCK_32X16 + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx8x8 }, new[] { TxSize.Tx8x8, TxSize.Tx8x8 } }, + new[] { new[] { TxSize.Tx16x16, TxSize.Tx8x8 }, new[] { TxSize.Tx16x16, TxSize.Tx8x8 } }, + new[] { new[] { TxSize.Tx16x16, TxSize.Tx8x8 }, new[] { TxSize.Tx16x16, TxSize.Tx8x8 } }, + }, + new[] + { + // BLOCK_32X32 + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx8x8 }, new[] { TxSize.Tx8x8, TxSize.Tx8x8 } }, + new[] { new[] { TxSize.Tx16x16, TxSize.Tx16x16 }, new[] { TxSize.Tx16x16, TxSize.Tx16x16 } }, + new[] { new[] { TxSize.Tx32x32, TxSize.Tx16x16 }, new[] { TxSize.Tx16x16, TxSize.Tx16x16 } }, + }, + new[] + { + // BLOCK_32X64 + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx8x8 }, new[] { TxSize.Tx8x8, TxSize.Tx8x8 } }, + new[] { new[] { TxSize.Tx16x16, TxSize.Tx16x16 }, new[] { TxSize.Tx16x16, TxSize.Tx16x16 } }, + new[] { new[] { TxSize.Tx32x32, TxSize.Tx32x32 }, new[] { TxSize.Tx16x16, TxSize.Tx16x16 } }, + }, + new[] + { + // BLOCK_64X32 + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx8x8 }, new[] { TxSize.Tx8x8, TxSize.Tx8x8 } }, + new[] { new[] { TxSize.Tx16x16, TxSize.Tx16x16 }, new[] { TxSize.Tx16x16, TxSize.Tx16x16 } }, + new[] { new[] { TxSize.Tx32x32, TxSize.Tx16x16 }, new[] { TxSize.Tx32x32, TxSize.Tx16x16 } }, + }, + new[] + { + // BLOCK_64X64 + new[] { new[] { TxSize.Tx4x4, TxSize.Tx4x4 }, new[] { TxSize.Tx4x4, TxSize.Tx4x4 } }, + new[] { new[] { TxSize.Tx8x8, TxSize.Tx8x8 }, new[] { TxSize.Tx8x8, TxSize.Tx8x8 } }, + new[] { new[] { TxSize.Tx16x16, TxSize.Tx16x16 }, new[] { TxSize.Tx16x16, TxSize.Tx16x16 } }, + new[] { new[] { TxSize.Tx32x32, TxSize.Tx32x32 }, new[] { TxSize.Tx32x32, TxSize.Tx32x32 } }, + }, + }; + + public struct PartitionContextPair + { + public sbyte Above; + public sbyte Left; + + public PartitionContextPair(sbyte above, sbyte left) + { + Above = above; + Left = left; + } + } + + // Generates 4 bit field in which each bit set to 1 represents + // a blocksize partition 1111 means we split 64x64, 32x32, 16x16 + // and 8x8. 1000 means we just split the 64x64 to 32x32 + public static readonly PartitionContextPair[] PartitionContextLookup = { + new(15, 15), // 4X4 - {0b1111, 0b1111} + new(15, 14), // 4X8 - {0b1111, 0b1110} + new(14, 15), // 8X4 - {0b1110, 0b1111} + new(14, 14), // 8X8 - {0b1110, 0b1110} + new(14, 12), // 8X16 - {0b1110, 0b1100} + new(12, 14), // 16X8 - {0b1100, 0b1110} + new(12, 12), // 16X16 - {0b1100, 0b1100} + new(12, 8), // 16X32 - {0b1100, 0b1000} + new(8, 12), // 32X16 - {0b1000, 0b1100} + new(8, 8), // 32X32 - {0b1000, 0b1000} + new(8, 0), // 32X64 - {0b1000, 0b0000} + new(0, 8), // 64X32 - {0b0000, 0b1000} + new(0, 0), // 64X64 - {0b0000, 0b0000} + }; + + // Filter + + private static readonly Array8[] _bilinearFilters = { + NewArray8Short(0, 0, 0, 128, 0, 0, 0, 0), NewArray8Short(0, 0, 0, 120, 8, 0, 0, 0), + NewArray8Short(0, 0, 0, 112, 16, 0, 0, 0), NewArray8Short(0, 0, 0, 104, 24, 0, 0, 0), + NewArray8Short(0, 0, 0, 96, 32, 0, 0, 0), NewArray8Short(0, 0, 0, 88, 40, 0, 0, 0), + NewArray8Short(0, 0, 0, 80, 48, 0, 0, 0), NewArray8Short(0, 0, 0, 72, 56, 0, 0, 0), + NewArray8Short(0, 0, 0, 64, 64, 0, 0, 0), NewArray8Short(0, 0, 0, 56, 72, 0, 0, 0), + NewArray8Short(0, 0, 0, 48, 80, 0, 0, 0), NewArray8Short(0, 0, 0, 40, 88, 0, 0, 0), + NewArray8Short(0, 0, 0, 32, 96, 0, 0, 0), NewArray8Short(0, 0, 0, 24, 104, 0, 0, 0), + NewArray8Short(0, 0, 0, 16, 112, 0, 0, 0), NewArray8Short(0, 0, 0, 8, 120, 0, 0, 0), + }; + + // Lagrangian interpolation filter + private static readonly Array8[] _subPelFilters8 = { + NewArray8Short(0, 0, 0, 128, 0, 0, 0, 0), NewArray8Short(0, 1, -5, 126, 8, -3, 1, 0), + NewArray8Short(-1, 3, -10, 122, 18, -6, 2, 0), NewArray8Short(-1, 4, -13, 118, 27, -9, 3, -1), + NewArray8Short(-1, 4, -16, 112, 37, -11, 4, -1), NewArray8Short(-1, 5, -18, 105, 48, -14, 4, -1), + NewArray8Short(-1, 5, -19, 97, 58, -16, 5, -1), NewArray8Short(-1, 6, -19, 88, 68, -18, 5, -1), + NewArray8Short(-1, 6, -19, 78, 78, -19, 6, -1), NewArray8Short(-1, 5, -18, 68, 88, -19, 6, -1), + NewArray8Short(-1, 5, -16, 58, 97, -19, 5, -1), NewArray8Short(-1, 4, -14, 48, 105, -18, 5, -1), + NewArray8Short(-1, 4, -11, 37, 112, -16, 4, -1), NewArray8Short(-1, 3, -9, 27, 118, -13, 4, -1), + NewArray8Short(0, 2, -6, 18, 122, -10, 3, -1), NewArray8Short(0, 1, -3, 8, 126, -5, 1, 0), + }; + + // DCT based filter + private static readonly Array8[] _subPelFilters8S = { + NewArray8Short(0, 0, 0, 128, 0, 0, 0, 0), NewArray8Short(-1, 3, -7, 127, 8, -3, 1, 0), + NewArray8Short(-2, 5, -13, 125, 17, -6, 3, -1), NewArray8Short(-3, 7, -17, 121, 27, -10, 5, -2), + NewArray8Short(-4, 9, -20, 115, 37, -13, 6, -2), NewArray8Short(-4, 10, -23, 108, 48, -16, 8, -3), + NewArray8Short(-4, 10, -24, 100, 59, -19, 9, -3), NewArray8Short(-4, 11, -24, 90, 70, -21, 10, -4), + NewArray8Short(-4, 11, -23, 80, 80, -23, 11, -4), NewArray8Short(-4, 10, -21, 70, 90, -24, 11, -4), + NewArray8Short(-3, 9, -19, 59, 100, -24, 10, -4), NewArray8Short(-3, 8, -16, 48, 108, -23, 10, -4), + NewArray8Short(-2, 6, -13, 37, 115, -20, 9, -4), NewArray8Short(-2, 5, -10, 27, 121, -17, 7, -3), + NewArray8Short(-1, 3, -6, 17, 125, -13, 5, -2), NewArray8Short(0, 1, -3, 8, 127, -7, 3, -1), + }; + + // freqmultiplier = 0.5 + private static readonly Array8[] _subPelFilters8Lp = { + NewArray8Short(0, 0, 0, 128, 0, 0, 0, 0), NewArray8Short(-3, -1, 32, 64, 38, 1, -3, 0), + NewArray8Short(-2, -2, 29, 63, 41, 2, -3, 0), NewArray8Short(-2, -2, 26, 63, 43, 4, -4, 0), + NewArray8Short(-2, -3, 24, 62, 46, 5, -4, 0), NewArray8Short(-2, -3, 21, 60, 49, 7, -4, 0), + NewArray8Short(-1, -4, 18, 59, 51, 9, -4, 0), NewArray8Short(-1, -4, 16, 57, 53, 12, -4, -1), + NewArray8Short(-1, -4, 14, 55, 55, 14, -4, -1), NewArray8Short(-1, -4, 12, 53, 57, 16, -4, -1), + NewArray8Short(0, -4, 9, 51, 59, 18, -4, -1), NewArray8Short(0, -4, 7, 49, 60, 21, -3, -2), + NewArray8Short(0, -4, 5, 46, 62, 24, -3, -2), NewArray8Short(0, -4, 4, 43, 63, 26, -2, -2), + NewArray8Short(0, -3, 2, 41, 63, 29, -2, -2), NewArray8Short(0, -3, 1, 38, 64, 32, -1, -3), + }; + + private static Array8 NewArray8Short(short e0, short e1, short e2, short e3, short e4, short e5, short e6, short e7) + { + Array8 output = new(); + + output[0] = e0; + output[1] = e1; + output[2] = e2; + output[3] = e3; + output[4] = e4; + output[5] = e5; + output[6] = e6; + output[7] = e7; + + return output; + } + + public static readonly Array8[][] Vp9FilterKernels = { + _subPelFilters8, _subPelFilters8Lp, _subPelFilters8S, _bilinearFilters, + }; + + // Scan + + private static readonly short[] _defaultScan4X4 = { + 0, 4, 1, 5, 8, 2, 12, 9, 3, 6, 13, 10, 7, 14, 11, 15, + }; + + private static readonly short[] _colScan4X4 = { + 0, 4, 8, 1, 12, 5, 9, 2, 13, 6, 10, 3, 7, 14, 11, 15, + }; + + private static readonly short[] _rowScan4X4 = { + 0, 1, 4, 2, 5, 3, 6, 8, 9, 7, 12, 10, 13, 11, 14, 15, + }; + + private static readonly short[] _defaultScan8X8 = { + 0, 8, 1, 16, 9, 2, 17, 24, 10, 3, 18, 25, 32, 11, 4, 26, + 33, 19, 40, 12, 34, 27, 5, 41, 20, 48, 13, 35, 42, 28, 21, 6, + 49, 56, 36, 43, 29, 7, 14, 50, 57, 44, 22, 37, 15, 51, 58, 30, + 45, 23, 52, 59, 38, 31, 60, 53, 46, 39, 61, 54, 47, 62, 55, 63, + }; + + private static readonly short[] _colScan8X8 = { + 0, 8, 16, 1, 24, 9, 32, 17, 2, 40, 25, 10, 33, 18, 48, 3, + 26, 41, 11, 56, 19, 34, 4, 49, 27, 42, 12, 35, 20, 57, 50, 28, + 5, 43, 13, 36, 58, 51, 21, 44, 6, 29, 59, 37, 14, 52, 22, 7, + 45, 60, 30, 15, 38, 53, 23, 46, 31, 61, 39, 54, 47, 62, 55, 63, + }; + + private static readonly short[] _rowScan8X8 = { + 0, 1, 2, 8, 9, 3, 16, 10, 4, 17, 11, 24, 5, 18, 25, 12, + 19, 26, 32, 6, 13, 20, 33, 27, 7, 34, 40, 21, 28, 41, 14, 35, + 48, 42, 29, 36, 49, 22, 43, 15, 56, 37, 50, 44, 30, 57, 23, 51, + 58, 45, 38, 52, 31, 59, 53, 46, 60, 39, 61, 47, 54, 55, 62, 63, + }; + + private static readonly short[] _defaultScan16X16 = { + 0, 16, 1, 32, 17, 2, 48, 33, 18, 3, 64, 34, 49, 19, 65, + 80, 50, 4, 35, 66, 20, 81, 96, 51, 5, 36, 82, 97, 67, 112, + 21, 52, 98, 37, 83, 113, 6, 68, 128, 53, 22, 99, 114, 84, 7, + 129, 38, 69, 100, 115, 144, 130, 85, 54, 23, 8, 145, 39, 70, 116, + 101, 131, 160, 146, 55, 86, 24, 71, 132, 117, 161, 40, 9, 102, 147, + 176, 162, 87, 56, 25, 133, 118, 177, 148, 72, 103, 41, 163, 10, 192, + 178, 88, 57, 134, 149, 119, 26, 164, 73, 104, 193, 42, 179, 208, 11, + 135, 89, 165, 120, 150, 58, 194, 180, 27, 74, 209, 105, 151, 136, 43, + 90, 224, 166, 195, 181, 121, 210, 59, 12, 152, 106, 167, 196, 75, 137, + 225, 211, 240, 182, 122, 91, 28, 197, 13, 226, 168, 183, 153, 44, 212, + 138, 107, 241, 60, 29, 123, 198, 184, 227, 169, 242, 76, 213, 154, 45, + 92, 14, 199, 139, 61, 228, 214, 170, 185, 243, 108, 77, 155, 30, 15, + 200, 229, 124, 215, 244, 93, 46, 186, 171, 201, 109, 140, 230, 62, 216, + 245, 31, 125, 78, 156, 231, 47, 187, 202, 217, 94, 246, 141, 63, 232, + 172, 110, 247, 157, 79, 218, 203, 126, 233, 188, 248, 95, 173, 142, 219, + 111, 249, 234, 158, 127, 189, 204, 250, 235, 143, 174, 220, 205, 159, 251, + 190, 221, 175, 236, 237, 191, 206, 252, 222, 253, 207, 238, 223, 254, 239, + 255, + }; + + private static readonly short[] _colScan16X16 = { + 0, 16, 32, 48, 1, 64, 17, 80, 33, 96, 49, 2, 65, 112, 18, + 81, 34, 128, 50, 97, 3, 66, 144, 19, 113, 35, 82, 160, 98, 51, + 129, 4, 67, 176, 20, 114, 145, 83, 36, 99, 130, 52, 192, 5, 161, + 68, 115, 21, 146, 84, 208, 177, 37, 131, 100, 53, 162, 224, 69, 6, + 116, 193, 147, 85, 22, 240, 132, 38, 178, 101, 163, 54, 209, 117, 70, + 7, 148, 194, 86, 179, 225, 23, 133, 39, 164, 8, 102, 210, 241, 55, + 195, 118, 149, 71, 180, 24, 87, 226, 134, 165, 211, 40, 103, 56, 72, + 150, 196, 242, 119, 9, 181, 227, 88, 166, 25, 135, 41, 104, 212, 57, + 151, 197, 120, 73, 243, 182, 136, 167, 213, 89, 10, 228, 105, 152, 198, + 26, 42, 121, 183, 244, 168, 58, 137, 229, 74, 214, 90, 153, 199, 184, + 11, 106, 245, 27, 122, 230, 169, 43, 215, 59, 200, 138, 185, 246, 75, + 12, 91, 154, 216, 231, 107, 28, 44, 201, 123, 170, 60, 247, 232, 76, + 139, 13, 92, 217, 186, 248, 155, 108, 29, 124, 45, 202, 233, 171, 61, + 14, 77, 140, 15, 249, 93, 30, 187, 156, 218, 46, 109, 125, 62, 172, + 78, 203, 31, 141, 234, 94, 47, 188, 63, 157, 110, 250, 219, 79, 126, + 204, 173, 142, 95, 189, 111, 235, 158, 220, 251, 127, 174, 143, 205, 236, + 159, 190, 221, 252, 175, 206, 237, 191, 253, 222, 238, 207, 254, 223, 239, + 255, + }; + + private static readonly short[] _rowScan16X16 = { + 0, 1, 2, 16, 3, 17, 4, 18, 32, 5, 33, 19, 6, 34, 48, + 20, 49, 7, 35, 21, 50, 64, 8, 36, 65, 22, 51, 37, 80, 9, + 66, 52, 23, 38, 81, 67, 10, 53, 24, 82, 68, 96, 39, 11, 54, + 83, 97, 69, 25, 98, 84, 40, 112, 55, 12, 70, 99, 113, 85, 26, + 41, 56, 114, 100, 13, 71, 128, 86, 27, 115, 101, 129, 42, 57, 72, + 116, 14, 87, 130, 102, 144, 73, 131, 117, 28, 58, 15, 88, 43, 145, + 103, 132, 146, 118, 74, 160, 89, 133, 104, 29, 59, 147, 119, 44, 161, + 148, 90, 105, 134, 162, 120, 176, 75, 135, 149, 30, 60, 163, 177, 45, + 121, 91, 106, 164, 178, 150, 192, 136, 165, 179, 31, 151, 193, 76, 122, + 61, 137, 194, 107, 152, 180, 208, 46, 166, 167, 195, 92, 181, 138, 209, + 123, 153, 224, 196, 77, 168, 210, 182, 240, 108, 197, 62, 154, 225, 183, + 169, 211, 47, 139, 93, 184, 226, 212, 241, 198, 170, 124, 155, 199, 78, + 213, 185, 109, 227, 200, 63, 228, 242, 140, 214, 171, 186, 156, 229, 243, + 125, 94, 201, 244, 215, 216, 230, 141, 187, 202, 79, 172, 110, 157, 245, + 217, 231, 95, 246, 232, 126, 203, 247, 233, 173, 218, 142, 111, 158, 188, + 248, 127, 234, 219, 249, 189, 204, 143, 174, 159, 250, 235, 205, 220, 175, + 190, 251, 221, 191, 206, 236, 207, 237, 252, 222, 253, 223, 238, 239, 254, + 255, + }; + + private static readonly short[] _defaultScan32X32 = { + 0, 32, 1, 64, 33, 2, 96, 65, 34, 128, 3, 97, 66, + 160, 129, 35, 98, 4, 67, 130, 161, 192, 36, 99, 224, 5, + 162, 193, 68, 131, 37, 100, 225, 194, 256, 163, 69, 132, 6, + 226, 257, 288, 195, 101, 164, 38, 258, 7, 227, 289, 133, 320, + 70, 196, 165, 290, 259, 228, 39, 321, 102, 352, 8, 197, 71, + 134, 322, 291, 260, 353, 384, 229, 166, 103, 40, 354, 323, 292, + 135, 385, 198, 261, 72, 9, 416, 167, 386, 355, 230, 324, 104, + 293, 41, 417, 199, 136, 262, 387, 448, 325, 356, 10, 73, 418, + 231, 168, 449, 294, 388, 105, 419, 263, 42, 200, 357, 450, 137, + 480, 74, 326, 232, 11, 389, 169, 295, 420, 106, 451, 481, 358, + 264, 327, 201, 43, 138, 512, 482, 390, 296, 233, 170, 421, 75, + 452, 359, 12, 513, 265, 483, 328, 107, 202, 514, 544, 422, 391, + 453, 139, 44, 234, 484, 297, 360, 171, 76, 515, 545, 266, 329, + 454, 13, 423, 203, 108, 546, 485, 576, 298, 235, 140, 361, 330, + 172, 547, 45, 455, 267, 577, 486, 77, 204, 362, 608, 14, 299, + 578, 109, 236, 487, 609, 331, 141, 579, 46, 15, 173, 610, 363, + 78, 205, 16, 110, 237, 611, 142, 47, 174, 79, 206, 17, 111, + 238, 48, 143, 80, 175, 112, 207, 49, 18, 239, 81, 113, 19, + 50, 82, 114, 51, 83, 115, 640, 516, 392, 268, 144, 20, 672, + 641, 548, 517, 424, 393, 300, 269, 176, 145, 52, 21, 704, 673, + 642, 580, 549, 518, 456, 425, 394, 332, 301, 270, 208, 177, 146, + 84, 53, 22, 736, 705, 674, 643, 612, 581, 550, 519, 488, 457, + 426, 395, 364, 333, 302, 271, 240, 209, 178, 147, 116, 85, 54, + 23, 737, 706, 675, 613, 582, 551, 489, 458, 427, 365, 334, 303, + 241, 210, 179, 117, 86, 55, 738, 707, 614, 583, 490, 459, 366, + 335, 242, 211, 118, 87, 739, 615, 491, 367, 243, 119, 768, 644, + 520, 396, 272, 148, 24, 800, 769, 676, 645, 552, 521, 428, 397, + 304, 273, 180, 149, 56, 25, 832, 801, 770, 708, 677, 646, 584, + 553, 522, 460, 429, 398, 336, 305, 274, 212, 181, 150, 88, 57, + 26, 864, 833, 802, 771, 740, 709, 678, 647, 616, 585, 554, 523, + 492, 461, 430, 399, 368, 337, 306, 275, 244, 213, 182, 151, 120, + 89, 58, 27, 865, 834, 803, 741, 710, 679, 617, 586, 555, 493, + 462, 431, 369, 338, 307, 245, 214, 183, 121, 90, 59, 866, 835, + 742, 711, 618, 587, 494, 463, 370, 339, 246, 215, 122, 91, 867, + 743, 619, 495, 371, 247, 123, 896, 772, 648, 524, 400, 276, 152, + 28, 928, 897, 804, 773, 680, 649, 556, 525, 432, 401, 308, 277, + 184, 153, 60, 29, 960, 929, 898, 836, 805, 774, 712, 681, 650, + 588, 557, 526, 464, 433, 402, 340, 309, 278, 216, 185, 154, 92, + 61, 30, 992, 961, 930, 899, 868, 837, 806, 775, 744, 713, 682, + 651, 620, 589, 558, 527, 496, 465, 434, 403, 372, 341, 310, 279, + 248, 217, 186, 155, 124, 93, 62, 31, 993, 962, 931, 869, 838, + 807, 745, 714, 683, 621, 590, 559, 497, 466, 435, 373, 342, 311, + 249, 218, 187, 125, 94, 63, 994, 963, 870, 839, 746, 715, 622, + 591, 498, 467, 374, 343, 250, 219, 126, 95, 995, 871, 747, 623, + 499, 375, 251, 127, 900, 776, 652, 528, 404, 280, 156, 932, 901, + 808, 777, 684, 653, 560, 529, 436, 405, 312, 281, 188, 157, 964, + 933, 902, 840, 809, 778, 716, 685, 654, 592, 561, 530, 468, 437, + 406, 344, 313, 282, 220, 189, 158, 996, 965, 934, 903, 872, 841, + 810, 779, 748, 717, 686, 655, 624, 593, 562, 531, 500, 469, 438, + 407, 376, 345, 314, 283, 252, 221, 190, 159, 997, 966, 935, 873, + 842, 811, 749, 718, 687, 625, 594, 563, 501, 470, 439, 377, 346, + 315, 253, 222, 191, 998, 967, 874, 843, 750, 719, 626, 595, 502, + 471, 378, 347, 254, 223, 999, 875, 751, 627, 503, 379, 255, 904, + 780, 656, 532, 408, 284, 936, 905, 812, 781, 688, 657, 564, 533, + 440, 409, 316, 285, 968, 937, 906, 844, 813, 782, 720, 689, 658, + 596, 565, 534, 472, 441, 410, 348, 317, 286, 1000, 969, 938, 907, + 876, 845, 814, 783, 752, 721, 690, 659, 628, 597, 566, 535, 504, + 473, 442, 411, 380, 349, 318, 287, 1001, 970, 939, 877, 846, 815, + 753, 722, 691, 629, 598, 567, 505, 474, 443, 381, 350, 319, 1002, + 971, 878, 847, 754, 723, 630, 599, 506, 475, 382, 351, 1003, 879, + 755, 631, 507, 383, 908, 784, 660, 536, 412, 940, 909, 816, 785, + 692, 661, 568, 537, 444, 413, 972, 941, 910, 848, 817, 786, 724, + 693, 662, 600, 569, 538, 476, 445, 414, 1004, 973, 942, 911, 880, + 849, 818, 787, 756, 725, 694, 663, 632, 601, 570, 539, 508, 477, + 446, 415, 1005, 974, 943, 881, 850, 819, 757, 726, 695, 633, 602, + 571, 509, 478, 447, 1006, 975, 882, 851, 758, 727, 634, 603, 510, + 479, 1007, 883, 759, 635, 511, 912, 788, 664, 540, 944, 913, 820, + 789, 696, 665, 572, 541, 976, 945, 914, 852, 821, 790, 728, 697, + 666, 604, 573, 542, 1008, 977, 946, 915, 884, 853, 822, 791, 760, + 729, 698, 667, 636, 605, 574, 543, 1009, 978, 947, 885, 854, 823, + 761, 730, 699, 637, 606, 575, 1010, 979, 886, 855, 762, 731, 638, + 607, 1011, 887, 763, 639, 916, 792, 668, 948, 917, 824, 793, 700, + 669, 980, 949, 918, 856, 825, 794, 732, 701, 670, 1012, 981, 950, + 919, 888, 857, 826, 795, 764, 733, 702, 671, 1013, 982, 951, 889, + 858, 827, 765, 734, 703, 1014, 983, 890, 859, 766, 735, 1015, 891, + 767, 920, 796, 952, 921, 828, 797, 984, 953, 922, 860, 829, 798, + 1016, 985, 954, 923, 892, 861, 830, 799, 1017, 986, 955, 893, 862, + 831, 1018, 987, 894, 863, 1019, 895, 924, 956, 925, 988, 957, 926, + 1020, 989, 958, 927, 1021, 990, 959, 1022, 991, 1023, + }; + + // Neighborhood 2-tuples for various scans and blocksizes, + // in {top, left} order for each position in corresponding scan order. + private static readonly short[] _defaultScan4X4Neighbors = { + 0, 0, 0, 0, 0, 0, 1, 4, 4, 4, 1, 1, 8, 8, 5, 8, 2, + 2, 2, 5, 9, 12, 6, 9, 3, 6, 10, 13, 7, 10, 11, 14, 0, 0, + }; + + private static readonly short[] _colScan4X4Neighbors = { + 0, 0, 0, 0, 4, 4, 0, 0, 8, 8, 1, 1, 5, 5, 1, 1, 9, + 9, 2, 2, 6, 6, 2, 2, 3, 3, 10, 10, 7, 7, 11, 11, 0, 0, + }; + + private static readonly short[] _rowScan4X4Neighbors = { + 0, 0, 0, 0, 0, 0, 1, 1, 4, 4, 2, 2, 5, 5, 4, 4, 8, + 8, 6, 6, 8, 8, 9, 9, 12, 12, 10, 10, 13, 13, 14, 14, 0, 0, + }; + + private static readonly short[] _colScan8X8Neighbors = { + 0, 0, 0, 0, 8, 8, 0, 0, 16, 16, 1, 1, 24, 24, 9, 9, 1, 1, 32, + 32, 17, 17, 2, 2, 25, 25, 10, 10, 40, 40, 2, 2, 18, 18, 33, 33, 3, 3, + 48, 48, 11, 11, 26, 26, 3, 3, 41, 41, 19, 19, 34, 34, 4, 4, 27, 27, 12, + 12, 49, 49, 42, 42, 20, 20, 4, 4, 35, 35, 5, 5, 28, 28, 50, 50, 43, 43, + 13, 13, 36, 36, 5, 5, 21, 21, 51, 51, 29, 29, 6, 6, 44, 44, 14, 14, 6, + 6, 37, 37, 52, 52, 22, 22, 7, 7, 30, 30, 45, 45, 15, 15, 38, 38, 23, 23, + 53, 53, 31, 31, 46, 46, 39, 39, 54, 54, 47, 47, 55, 55, 0, 0, + }; + + private static readonly short[] _rowScan8X8Neighbors = { + 0, 0, 0, 0, 1, 1, 0, 0, 8, 8, 2, 2, 8, 8, 9, 9, 3, 3, 16, + 16, 10, 10, 16, 16, 4, 4, 17, 17, 24, 24, 11, 11, 18, 18, 25, 25, 24, 24, + 5, 5, 12, 12, 19, 19, 32, 32, 26, 26, 6, 6, 33, 33, 32, 32, 20, 20, 27, + 27, 40, 40, 13, 13, 34, 34, 40, 40, 41, 41, 28, 28, 35, 35, 48, 48, 21, 21, + 42, 42, 14, 14, 48, 48, 36, 36, 49, 49, 43, 43, 29, 29, 56, 56, 22, 22, 50, + 50, 57, 57, 44, 44, 37, 37, 51, 51, 30, 30, 58, 58, 52, 52, 45, 45, 59, 59, + 38, 38, 60, 60, 46, 46, 53, 53, 54, 54, 61, 61, 62, 62, 0, 0, + }; + + private static readonly short[] _defaultScan8X8Neighbors = { + 0, 0, 0, 0, 0, 0, 8, 8, 1, 8, 1, 1, 9, 16, 16, 16, 2, 9, 2, + 2, 10, 17, 17, 24, 24, 24, 3, 10, 3, 3, 18, 25, 25, 32, 11, 18, 32, 32, + 4, 11, 26, 33, 19, 26, 4, 4, 33, 40, 12, 19, 40, 40, 5, 12, 27, 34, 34, + 41, 20, 27, 13, 20, 5, 5, 41, 48, 48, 48, 28, 35, 35, 42, 21, 28, 6, 6, + 6, 13, 42, 49, 49, 56, 36, 43, 14, 21, 29, 36, 7, 14, 43, 50, 50, 57, 22, + 29, 37, 44, 15, 22, 44, 51, 51, 58, 30, 37, 23, 30, 52, 59, 45, 52, 38, 45, + 31, 38, 53, 60, 46, 53, 39, 46, 54, 61, 47, 54, 55, 62, 0, 0, + }; + + private static readonly short[] _colScan16X16Neighbors = { + 0, 0, 0, 0, 16, 16, 32, 32, 0, 0, 48, 48, 1, 1, 64, + 64, 17, 17, 80, 80, 33, 33, 1, 1, 49, 49, 96, 96, 2, 2, + 65, 65, 18, 18, 112, 112, 34, 34, 81, 81, 2, 2, 50, 50, 128, + 128, 3, 3, 97, 97, 19, 19, 66, 66, 144, 144, 82, 82, 35, 35, + 113, 113, 3, 3, 51, 51, 160, 160, 4, 4, 98, 98, 129, 129, 67, + 67, 20, 20, 83, 83, 114, 114, 36, 36, 176, 176, 4, 4, 145, 145, + 52, 52, 99, 99, 5, 5, 130, 130, 68, 68, 192, 192, 161, 161, 21, + 21, 115, 115, 84, 84, 37, 37, 146, 146, 208, 208, 53, 53, 5, 5, + 100, 100, 177, 177, 131, 131, 69, 69, 6, 6, 224, 224, 116, 116, 22, + 22, 162, 162, 85, 85, 147, 147, 38, 38, 193, 193, 101, 101, 54, 54, + 6, 6, 132, 132, 178, 178, 70, 70, 163, 163, 209, 209, 7, 7, 117, + 117, 23, 23, 148, 148, 7, 7, 86, 86, 194, 194, 225, 225, 39, 39, + 179, 179, 102, 102, 133, 133, 55, 55, 164, 164, 8, 8, 71, 71, 210, + 210, 118, 118, 149, 149, 195, 195, 24, 24, 87, 87, 40, 40, 56, 56, + 134, 134, 180, 180, 226, 226, 103, 103, 8, 8, 165, 165, 211, 211, 72, + 72, 150, 150, 9, 9, 119, 119, 25, 25, 88, 88, 196, 196, 41, 41, + 135, 135, 181, 181, 104, 104, 57, 57, 227, 227, 166, 166, 120, 120, 151, + 151, 197, 197, 73, 73, 9, 9, 212, 212, 89, 89, 136, 136, 182, 182, + 10, 10, 26, 26, 105, 105, 167, 167, 228, 228, 152, 152, 42, 42, 121, + 121, 213, 213, 58, 58, 198, 198, 74, 74, 137, 137, 183, 183, 168, 168, + 10, 10, 90, 90, 229, 229, 11, 11, 106, 106, 214, 214, 153, 153, 27, + 27, 199, 199, 43, 43, 184, 184, 122, 122, 169, 169, 230, 230, 59, 59, + 11, 11, 75, 75, 138, 138, 200, 200, 215, 215, 91, 91, 12, 12, 28, + 28, 185, 185, 107, 107, 154, 154, 44, 44, 231, 231, 216, 216, 60, 60, + 123, 123, 12, 12, 76, 76, 201, 201, 170, 170, 232, 232, 139, 139, 92, + 92, 13, 13, 108, 108, 29, 29, 186, 186, 217, 217, 155, 155, 45, 45, + 13, 13, 61, 61, 124, 124, 14, 14, 233, 233, 77, 77, 14, 14, 171, + 171, 140, 140, 202, 202, 30, 30, 93, 93, 109, 109, 46, 46, 156, 156, + 62, 62, 187, 187, 15, 15, 125, 125, 218, 218, 78, 78, 31, 31, 172, + 172, 47, 47, 141, 141, 94, 94, 234, 234, 203, 203, 63, 63, 110, 110, + 188, 188, 157, 157, 126, 126, 79, 79, 173, 173, 95, 95, 219, 219, 142, + 142, 204, 204, 235, 235, 111, 111, 158, 158, 127, 127, 189, 189, 220, 220, + 143, 143, 174, 174, 205, 205, 236, 236, 159, 159, 190, 190, 221, 221, 175, + 175, 237, 237, 206, 206, 222, 222, 191, 191, 238, 238, 207, 207, 223, 223, + 239, 239, 0, 0, + }; + + private static readonly short[] _rowScan16X16Neighbors = { + 0, 0, 0, 0, 1, 1, 0, 0, 2, 2, 16, 16, 3, 3, 17, + 17, 16, 16, 4, 4, 32, 32, 18, 18, 5, 5, 33, 33, 32, 32, + 19, 19, 48, 48, 6, 6, 34, 34, 20, 20, 49, 49, 48, 48, 7, + 7, 35, 35, 64, 64, 21, 21, 50, 50, 36, 36, 64, 64, 8, 8, + 65, 65, 51, 51, 22, 22, 37, 37, 80, 80, 66, 66, 9, 9, 52, + 52, 23, 23, 81, 81, 67, 67, 80, 80, 38, 38, 10, 10, 53, 53, + 82, 82, 96, 96, 68, 68, 24, 24, 97, 97, 83, 83, 39, 39, 96, + 96, 54, 54, 11, 11, 69, 69, 98, 98, 112, 112, 84, 84, 25, 25, + 40, 40, 55, 55, 113, 113, 99, 99, 12, 12, 70, 70, 112, 112, 85, + 85, 26, 26, 114, 114, 100, 100, 128, 128, 41, 41, 56, 56, 71, 71, + 115, 115, 13, 13, 86, 86, 129, 129, 101, 101, 128, 128, 72, 72, 130, + 130, 116, 116, 27, 27, 57, 57, 14, 14, 87, 87, 42, 42, 144, 144, + 102, 102, 131, 131, 145, 145, 117, 117, 73, 73, 144, 144, 88, 88, 132, + 132, 103, 103, 28, 28, 58, 58, 146, 146, 118, 118, 43, 43, 160, 160, + 147, 147, 89, 89, 104, 104, 133, 133, 161, 161, 119, 119, 160, 160, 74, + 74, 134, 134, 148, 148, 29, 29, 59, 59, 162, 162, 176, 176, 44, 44, + 120, 120, 90, 90, 105, 105, 163, 163, 177, 177, 149, 149, 176, 176, 135, + 135, 164, 164, 178, 178, 30, 30, 150, 150, 192, 192, 75, 75, 121, 121, + 60, 60, 136, 136, 193, 193, 106, 106, 151, 151, 179, 179, 192, 192, 45, + 45, 165, 165, 166, 166, 194, 194, 91, 91, 180, 180, 137, 137, 208, 208, + 122, 122, 152, 152, 208, 208, 195, 195, 76, 76, 167, 167, 209, 209, 181, + 181, 224, 224, 107, 107, 196, 196, 61, 61, 153, 153, 224, 224, 182, 182, + 168, 168, 210, 210, 46, 46, 138, 138, 92, 92, 183, 183, 225, 225, 211, + 211, 240, 240, 197, 197, 169, 169, 123, 123, 154, 154, 198, 198, 77, 77, + 212, 212, 184, 184, 108, 108, 226, 226, 199, 199, 62, 62, 227, 227, 241, + 241, 139, 139, 213, 213, 170, 170, 185, 185, 155, 155, 228, 228, 242, 242, + 124, 124, 93, 93, 200, 200, 243, 243, 214, 214, 215, 215, 229, 229, 140, + 140, 186, 186, 201, 201, 78, 78, 171, 171, 109, 109, 156, 156, 244, 244, + 216, 216, 230, 230, 94, 94, 245, 245, 231, 231, 125, 125, 202, 202, 246, + 246, 232, 232, 172, 172, 217, 217, 141, 141, 110, 110, 157, 157, 187, 187, + 247, 247, 126, 126, 233, 233, 218, 218, 248, 248, 188, 188, 203, 203, 142, + 142, 173, 173, 158, 158, 249, 249, 234, 234, 204, 204, 219, 219, 174, 174, + 189, 189, 250, 250, 220, 220, 190, 190, 205, 205, 235, 235, 206, 206, 236, + 236, 251, 251, 221, 221, 252, 252, 222, 222, 237, 237, 238, 238, 253, 253, + 254, 254, 0, 0, + }; + + private static readonly short[] _defaultScan16X16Neighbors = { + 0, 0, 0, 0, 0, 0, 16, 16, 1, 16, 1, 1, 32, 32, 17, + 32, 2, 17, 2, 2, 48, 48, 18, 33, 33, 48, 3, 18, 49, 64, + 64, 64, 34, 49, 3, 3, 19, 34, 50, 65, 4, 19, 65, 80, 80, + 80, 35, 50, 4, 4, 20, 35, 66, 81, 81, 96, 51, 66, 96, 96, + 5, 20, 36, 51, 82, 97, 21, 36, 67, 82, 97, 112, 5, 5, 52, + 67, 112, 112, 37, 52, 6, 21, 83, 98, 98, 113, 68, 83, 6, 6, + 113, 128, 22, 37, 53, 68, 84, 99, 99, 114, 128, 128, 114, 129, 69, + 84, 38, 53, 7, 22, 7, 7, 129, 144, 23, 38, 54, 69, 100, 115, + 85, 100, 115, 130, 144, 144, 130, 145, 39, 54, 70, 85, 8, 23, 55, + 70, 116, 131, 101, 116, 145, 160, 24, 39, 8, 8, 86, 101, 131, 146, + 160, 160, 146, 161, 71, 86, 40, 55, 9, 24, 117, 132, 102, 117, 161, + 176, 132, 147, 56, 71, 87, 102, 25, 40, 147, 162, 9, 9, 176, 176, + 162, 177, 72, 87, 41, 56, 118, 133, 133, 148, 103, 118, 10, 25, 148, + 163, 57, 72, 88, 103, 177, 192, 26, 41, 163, 178, 192, 192, 10, 10, + 119, 134, 73, 88, 149, 164, 104, 119, 134, 149, 42, 57, 178, 193, 164, + 179, 11, 26, 58, 73, 193, 208, 89, 104, 135, 150, 120, 135, 27, 42, + 74, 89, 208, 208, 150, 165, 179, 194, 165, 180, 105, 120, 194, 209, 43, + 58, 11, 11, 136, 151, 90, 105, 151, 166, 180, 195, 59, 74, 121, 136, + 209, 224, 195, 210, 224, 224, 166, 181, 106, 121, 75, 90, 12, 27, 181, + 196, 12, 12, 210, 225, 152, 167, 167, 182, 137, 152, 28, 43, 196, 211, + 122, 137, 91, 106, 225, 240, 44, 59, 13, 28, 107, 122, 182, 197, 168, + 183, 211, 226, 153, 168, 226, 241, 60, 75, 197, 212, 138, 153, 29, 44, + 76, 91, 13, 13, 183, 198, 123, 138, 45, 60, 212, 227, 198, 213, 154, + 169, 169, 184, 227, 242, 92, 107, 61, 76, 139, 154, 14, 29, 14, 14, + 184, 199, 213, 228, 108, 123, 199, 214, 228, 243, 77, 92, 30, 45, 170, + 185, 155, 170, 185, 200, 93, 108, 124, 139, 214, 229, 46, 61, 200, 215, + 229, 244, 15, 30, 109, 124, 62, 77, 140, 155, 215, 230, 31, 46, 171, + 186, 186, 201, 201, 216, 78, 93, 230, 245, 125, 140, 47, 62, 216, 231, + 156, 171, 94, 109, 231, 246, 141, 156, 63, 78, 202, 217, 187, 202, 110, + 125, 217, 232, 172, 187, 232, 247, 79, 94, 157, 172, 126, 141, 203, 218, + 95, 110, 233, 248, 218, 233, 142, 157, 111, 126, 173, 188, 188, 203, 234, + 249, 219, 234, 127, 142, 158, 173, 204, 219, 189, 204, 143, 158, 235, 250, + 174, 189, 205, 220, 159, 174, 220, 235, 221, 236, 175, 190, 190, 205, 236, + 251, 206, 221, 237, 252, 191, 206, 222, 237, 207, 222, 238, 253, 223, 238, + 239, 254, 0, 0, + }; + + private static readonly short[] _defaultScan32X32Neighbors = { + 0, 0, 0, 0, 0, 0, 32, 32, 1, 32, 1, 1, 64, 64, + 33, 64, 2, 33, 96, 96, 2, 2, 65, 96, 34, 65, 128, 128, + 97, 128, 3, 34, 66, 97, 3, 3, 35, 66, 98, 129, 129, 160, + 160, 160, 4, 35, 67, 98, 192, 192, 4, 4, 130, 161, 161, 192, + 36, 67, 99, 130, 5, 36, 68, 99, 193, 224, 162, 193, 224, 224, + 131, 162, 37, 68, 100, 131, 5, 5, 194, 225, 225, 256, 256, 256, + 163, 194, 69, 100, 132, 163, 6, 37, 226, 257, 6, 6, 195, 226, + 257, 288, 101, 132, 288, 288, 38, 69, 164, 195, 133, 164, 258, 289, + 227, 258, 196, 227, 7, 38, 289, 320, 70, 101, 320, 320, 7, 7, + 165, 196, 39, 70, 102, 133, 290, 321, 259, 290, 228, 259, 321, 352, + 352, 352, 197, 228, 134, 165, 71, 102, 8, 39, 322, 353, 291, 322, + 260, 291, 103, 134, 353, 384, 166, 197, 229, 260, 40, 71, 8, 8, + 384, 384, 135, 166, 354, 385, 323, 354, 198, 229, 292, 323, 72, 103, + 261, 292, 9, 40, 385, 416, 167, 198, 104, 135, 230, 261, 355, 386, + 416, 416, 293, 324, 324, 355, 9, 9, 41, 72, 386, 417, 199, 230, + 136, 167, 417, 448, 262, 293, 356, 387, 73, 104, 387, 418, 231, 262, + 10, 41, 168, 199, 325, 356, 418, 449, 105, 136, 448, 448, 42, 73, + 294, 325, 200, 231, 10, 10, 357, 388, 137, 168, 263, 294, 388, 419, + 74, 105, 419, 450, 449, 480, 326, 357, 232, 263, 295, 326, 169, 200, + 11, 42, 106, 137, 480, 480, 450, 481, 358, 389, 264, 295, 201, 232, + 138, 169, 389, 420, 43, 74, 420, 451, 327, 358, 11, 11, 481, 512, + 233, 264, 451, 482, 296, 327, 75, 106, 170, 201, 482, 513, 512, 512, + 390, 421, 359, 390, 421, 452, 107, 138, 12, 43, 202, 233, 452, 483, + 265, 296, 328, 359, 139, 170, 44, 75, 483, 514, 513, 544, 234, 265, + 297, 328, 422, 453, 12, 12, 391, 422, 171, 202, 76, 107, 514, 545, + 453, 484, 544, 544, 266, 297, 203, 234, 108, 139, 329, 360, 298, 329, + 140, 171, 515, 546, 13, 44, 423, 454, 235, 266, 545, 576, 454, 485, + 45, 76, 172, 203, 330, 361, 576, 576, 13, 13, 267, 298, 546, 577, + 77, 108, 204, 235, 455, 486, 577, 608, 299, 330, 109, 140, 547, 578, + 14, 45, 14, 14, 141, 172, 578, 609, 331, 362, 46, 77, 173, 204, + 15, 15, 78, 109, 205, 236, 579, 610, 110, 141, 15, 46, 142, 173, + 47, 78, 174, 205, 16, 16, 79, 110, 206, 237, 16, 47, 111, 142, + 48, 79, 143, 174, 80, 111, 175, 206, 17, 48, 17, 17, 207, 238, + 49, 80, 81, 112, 18, 18, 18, 49, 50, 81, 82, 113, 19, 50, + 51, 82, 83, 114, 608, 608, 484, 515, 360, 391, 236, 267, 112, 143, + 19, 19, 640, 640, 609, 640, 516, 547, 485, 516, 392, 423, 361, 392, + 268, 299, 237, 268, 144, 175, 113, 144, 20, 51, 20, 20, 672, 672, + 641, 672, 610, 641, 548, 579, 517, 548, 486, 517, 424, 455, 393, 424, + 362, 393, 300, 331, 269, 300, 238, 269, 176, 207, 145, 176, 114, 145, + 52, 83, 21, 52, 21, 21, 704, 704, 673, 704, 642, 673, 611, 642, + 580, 611, 549, 580, 518, 549, 487, 518, 456, 487, 425, 456, 394, 425, + 363, 394, 332, 363, 301, 332, 270, 301, 239, 270, 208, 239, 177, 208, + 146, 177, 115, 146, 84, 115, 53, 84, 22, 53, 22, 22, 705, 736, + 674, 705, 643, 674, 581, 612, 550, 581, 519, 550, 457, 488, 426, 457, + 395, 426, 333, 364, 302, 333, 271, 302, 209, 240, 178, 209, 147, 178, + 85, 116, 54, 85, 23, 54, 706, 737, 675, 706, 582, 613, 551, 582, + 458, 489, 427, 458, 334, 365, 303, 334, 210, 241, 179, 210, 86, 117, + 55, 86, 707, 738, 583, 614, 459, 490, 335, 366, 211, 242, 87, 118, + 736, 736, 612, 643, 488, 519, 364, 395, 240, 271, 116, 147, 23, 23, + 768, 768, 737, 768, 644, 675, 613, 644, 520, 551, 489, 520, 396, 427, + 365, 396, 272, 303, 241, 272, 148, 179, 117, 148, 24, 55, 24, 24, + 800, 800, 769, 800, 738, 769, 676, 707, 645, 676, 614, 645, 552, 583, + 521, 552, 490, 521, 428, 459, 397, 428, 366, 397, 304, 335, 273, 304, + 242, 273, 180, 211, 149, 180, 118, 149, 56, 87, 25, 56, 25, 25, + 832, 832, 801, 832, 770, 801, 739, 770, 708, 739, 677, 708, 646, 677, + 615, 646, 584, 615, 553, 584, 522, 553, 491, 522, 460, 491, 429, 460, + 398, 429, 367, 398, 336, 367, 305, 336, 274, 305, 243, 274, 212, 243, + 181, 212, 150, 181, 119, 150, 88, 119, 57, 88, 26, 57, 26, 26, + 833, 864, 802, 833, 771, 802, 709, 740, 678, 709, 647, 678, 585, 616, + 554, 585, 523, 554, 461, 492, 430, 461, 399, 430, 337, 368, 306, 337, + 275, 306, 213, 244, 182, 213, 151, 182, 89, 120, 58, 89, 27, 58, + 834, 865, 803, 834, 710, 741, 679, 710, 586, 617, 555, 586, 462, 493, + 431, 462, 338, 369, 307, 338, 214, 245, 183, 214, 90, 121, 59, 90, + 835, 866, 711, 742, 587, 618, 463, 494, 339, 370, 215, 246, 91, 122, + 864, 864, 740, 771, 616, 647, 492, 523, 368, 399, 244, 275, 120, 151, + 27, 27, 896, 896, 865, 896, 772, 803, 741, 772, 648, 679, 617, 648, + 524, 555, 493, 524, 400, 431, 369, 400, 276, 307, 245, 276, 152, 183, + 121, 152, 28, 59, 28, 28, 928, 928, 897, 928, 866, 897, 804, 835, + 773, 804, 742, 773, 680, 711, 649, 680, 618, 649, 556, 587, 525, 556, + 494, 525, 432, 463, 401, 432, 370, 401, 308, 339, 277, 308, 246, 277, + 184, 215, 153, 184, 122, 153, 60, 91, 29, 60, 29, 29, 960, 960, + 929, 960, 898, 929, 867, 898, 836, 867, 805, 836, 774, 805, 743, 774, + 712, 743, 681, 712, 650, 681, 619, 650, 588, 619, 557, 588, 526, 557, + 495, 526, 464, 495, 433, 464, 402, 433, 371, 402, 340, 371, 309, 340, + 278, 309, 247, 278, 216, 247, 185, 216, 154, 185, 123, 154, 92, 123, + 61, 92, 30, 61, 30, 30, 961, 992, 930, 961, 899, 930, 837, 868, + 806, 837, 775, 806, 713, 744, 682, 713, 651, 682, 589, 620, 558, 589, + 527, 558, 465, 496, 434, 465, 403, 434, 341, 372, 310, 341, 279, 310, + 217, 248, 186, 217, 155, 186, 93, 124, 62, 93, 31, 62, 962, 993, + 931, 962, 838, 869, 807, 838, 714, 745, 683, 714, 590, 621, 559, 590, + 466, 497, 435, 466, 342, 373, 311, 342, 218, 249, 187, 218, 94, 125, + 63, 94, 963, 994, 839, 870, 715, 746, 591, 622, 467, 498, 343, 374, + 219, 250, 95, 126, 868, 899, 744, 775, 620, 651, 496, 527, 372, 403, + 248, 279, 124, 155, 900, 931, 869, 900, 776, 807, 745, 776, 652, 683, + 621, 652, 528, 559, 497, 528, 404, 435, 373, 404, 280, 311, 249, 280, + 156, 187, 125, 156, 932, 963, 901, 932, 870, 901, 808, 839, 777, 808, + 746, 777, 684, 715, 653, 684, 622, 653, 560, 591, 529, 560, 498, 529, + 436, 467, 405, 436, 374, 405, 312, 343, 281, 312, 250, 281, 188, 219, + 157, 188, 126, 157, 964, 995, 933, 964, 902, 933, 871, 902, 840, 871, + 809, 840, 778, 809, 747, 778, 716, 747, 685, 716, 654, 685, 623, 654, + 592, 623, 561, 592, 530, 561, 499, 530, 468, 499, 437, 468, 406, 437, + 375, 406, 344, 375, 313, 344, 282, 313, 251, 282, 220, 251, 189, 220, + 158, 189, 127, 158, 965, 996, 934, 965, 903, 934, 841, 872, 810, 841, + 779, 810, 717, 748, 686, 717, 655, 686, 593, 624, 562, 593, 531, 562, + 469, 500, 438, 469, 407, 438, 345, 376, 314, 345, 283, 314, 221, 252, + 190, 221, 159, 190, 966, 997, 935, 966, 842, 873, 811, 842, 718, 749, + 687, 718, 594, 625, 563, 594, 470, 501, 439, 470, 346, 377, 315, 346, + 222, 253, 191, 222, 967, 998, 843, 874, 719, 750, 595, 626, 471, 502, + 347, 378, 223, 254, 872, 903, 748, 779, 624, 655, 500, 531, 376, 407, + 252, 283, 904, 935, 873, 904, 780, 811, 749, 780, 656, 687, 625, 656, + 532, 563, 501, 532, 408, 439, 377, 408, 284, 315, 253, 284, 936, 967, + 905, 936, 874, 905, 812, 843, 781, 812, 750, 781, 688, 719, 657, 688, + 626, 657, 564, 595, 533, 564, 502, 533, 440, 471, 409, 440, 378, 409, + 316, 347, 285, 316, 254, 285, 968, 999, 937, 968, 906, 937, 875, 906, + 844, 875, 813, 844, 782, 813, 751, 782, 720, 751, 689, 720, 658, 689, + 627, 658, 596, 627, 565, 596, 534, 565, 503, 534, 472, 503, 441, 472, + 410, 441, 379, 410, 348, 379, 317, 348, 286, 317, 255, 286, 969, 1000, + 938, 969, 907, 938, 845, 876, 814, 845, 783, 814, 721, 752, 690, 721, + 659, 690, 597, 628, 566, 597, 535, 566, 473, 504, 442, 473, 411, 442, + 349, 380, 318, 349, 287, 318, 970, 1001, 939, 970, 846, 877, 815, 846, + 722, 753, 691, 722, 598, 629, 567, 598, 474, 505, 443, 474, 350, 381, + 319, 350, 971, 1002, 847, 878, 723, 754, 599, 630, 475, 506, 351, 382, + 876, 907, 752, 783, 628, 659, 504, 535, 380, 411, 908, 939, 877, 908, + 784, 815, 753, 784, 660, 691, 629, 660, 536, 567, 505, 536, 412, 443, + 381, 412, 940, 971, 909, 940, 878, 909, 816, 847, 785, 816, 754, 785, + 692, 723, 661, 692, 630, 661, 568, 599, 537, 568, 506, 537, 444, 475, + 413, 444, 382, 413, 972, 1003, 941, 972, 910, 941, 879, 910, 848, 879, + 817, 848, 786, 817, 755, 786, 724, 755, 693, 724, 662, 693, 631, 662, + 600, 631, 569, 600, 538, 569, 507, 538, 476, 507, 445, 476, 414, 445, + 383, 414, 973, 1004, 942, 973, 911, 942, 849, 880, 818, 849, 787, 818, + 725, 756, 694, 725, 663, 694, 601, 632, 570, 601, 539, 570, 477, 508, + 446, 477, 415, 446, 974, 1005, 943, 974, 850, 881, 819, 850, 726, 757, + 695, 726, 602, 633, 571, 602, 478, 509, 447, 478, 975, 1006, 851, 882, + 727, 758, 603, 634, 479, 510, 880, 911, 756, 787, 632, 663, 508, 539, + 912, 943, 881, 912, 788, 819, 757, 788, 664, 695, 633, 664, 540, 571, + 509, 540, 944, 975, 913, 944, 882, 913, 820, 851, 789, 820, 758, 789, + 696, 727, 665, 696, 634, 665, 572, 603, 541, 572, 510, 541, 976, 1007, + 945, 976, 914, 945, 883, 914, 852, 883, 821, 852, 790, 821, 759, 790, + 728, 759, 697, 728, 666, 697, 635, 666, 604, 635, 573, 604, 542, 573, + 511, 542, 977, 1008, 946, 977, 915, 946, 853, 884, 822, 853, 791, 822, + 729, 760, 698, 729, 667, 698, 605, 636, 574, 605, 543, 574, 978, 1009, + 947, 978, 854, 885, 823, 854, 730, 761, 699, 730, 606, 637, 575, 606, + 979, 1010, 855, 886, 731, 762, 607, 638, 884, 915, 760, 791, 636, 667, + 916, 947, 885, 916, 792, 823, 761, 792, 668, 699, 637, 668, 948, 979, + 917, 948, 886, 917, 824, 855, 793, 824, 762, 793, 700, 731, 669, 700, + 638, 669, 980, 1011, 949, 980, 918, 949, 887, 918, 856, 887, 825, 856, + 794, 825, 763, 794, 732, 763, 701, 732, 670, 701, 639, 670, 981, 1012, + 950, 981, 919, 950, 857, 888, 826, 857, 795, 826, 733, 764, 702, 733, + 671, 702, 982, 1013, 951, 982, 858, 889, 827, 858, 734, 765, 703, 734, + 983, 1014, 859, 890, 735, 766, 888, 919, 764, 795, 920, 951, 889, 920, + 796, 827, 765, 796, 952, 983, 921, 952, 890, 921, 828, 859, 797, 828, + 766, 797, 984, 1015, 953, 984, 922, 953, 891, 922, 860, 891, 829, 860, + 798, 829, 767, 798, 985, 1016, 954, 985, 923, 954, 861, 892, 830, 861, + 799, 830, 986, 1017, 955, 986, 862, 893, 831, 862, 987, 1018, 863, 894, + 892, 923, 924, 955, 893, 924, 956, 987, 925, 956, 894, 925, 988, 1019, + 957, 988, 926, 957, 895, 926, 989, 1020, 958, 989, 927, 958, 990, 1021, + 959, 990, 991, 1022, 0, 0, + }; + + private static readonly short[] _vp9DefaultIscan4X4 = { + 0, 2, 5, 8, 1, 3, 9, 12, 4, 7, 11, 14, 6, 10, 13, 15, + }; + + private static readonly short[] _vp9ColIscan4X4 = { + 0, 3, 7, 11, 1, 5, 9, 12, 2, 6, 10, 14, 4, 8, 13, 15, + }; + + private static readonly short[] _vp9RowIscan4X4 = { + 0, 1, 3, 5, 2, 4, 6, 9, 7, 8, 11, 13, 10, 12, 14, 15, + }; + + private static readonly short[] _vp9ColIscan8X8 = { + 0, 3, 8, 15, 22, 32, 40, 47, 1, 5, 11, 18, 26, 34, 44, 51, + 2, 7, 13, 20, 28, 38, 46, 54, 4, 10, 16, 24, 31, 41, 50, 56, + 6, 12, 21, 27, 35, 43, 52, 58, 9, 17, 25, 33, 39, 48, 55, 60, + 14, 23, 30, 37, 45, 53, 59, 62, 19, 29, 36, 42, 49, 57, 61, 63, + }; + + private static readonly short[] _vp9RowIscan8X8 = { + 0, 1, 2, 5, 8, 12, 19, 24, 3, 4, 7, 10, 15, 20, 30, 39, + 6, 9, 13, 16, 21, 27, 37, 46, 11, 14, 17, 23, 28, 34, 44, 52, + 18, 22, 25, 31, 35, 41, 50, 57, 26, 29, 33, 38, 43, 49, 55, 59, + 32, 36, 42, 47, 51, 54, 60, 61, 40, 45, 48, 53, 56, 58, 62, 63, + }; + + private static readonly short[] _vp9DefaultIscan8X8 = { + 0, 2, 5, 9, 14, 22, 31, 37, 1, 4, 8, 13, 19, 26, 38, 44, + 3, 6, 10, 17, 24, 30, 42, 49, 7, 11, 15, 21, 29, 36, 47, 53, + 12, 16, 20, 27, 34, 43, 52, 57, 18, 23, 28, 35, 41, 48, 56, 60, + 25, 32, 39, 45, 50, 55, 59, 62, 33, 40, 46, 51, 54, 58, 61, 63, + }; + + private static readonly short[] _vp9ColIscan16X16 = { + 0, 4, 11, 20, 31, 43, 59, 75, 85, 109, 130, 150, 165, 181, 195, 198, + 1, 6, 14, 23, 34, 47, 64, 81, 95, 114, 135, 153, 171, 188, 201, 212, + 2, 8, 16, 25, 38, 52, 67, 83, 101, 116, 136, 157, 172, 190, 205, 216, + 3, 10, 18, 29, 41, 55, 71, 89, 103, 119, 141, 159, 176, 194, 208, 218, + 5, 12, 21, 32, 45, 58, 74, 93, 104, 123, 144, 164, 179, 196, 210, 223, + 7, 15, 26, 37, 49, 63, 78, 96, 112, 129, 146, 166, 182, 200, 215, 228, + 9, 19, 28, 39, 54, 69, 86, 102, 117, 132, 151, 170, 187, 206, 220, 230, + 13, 24, 35, 46, 60, 73, 91, 108, 122, 137, 154, 174, 189, 207, 224, 235, + 17, 30, 40, 53, 66, 82, 98, 115, 126, 142, 161, 180, 197, 213, 227, 237, + 22, 36, 48, 62, 76, 92, 105, 120, 133, 147, 167, 186, 203, 219, 232, 240, + 27, 44, 56, 70, 84, 99, 113, 127, 140, 156, 175, 193, 209, 226, 236, 244, + 33, 51, 68, 79, 94, 110, 125, 138, 149, 162, 184, 202, 217, 229, 241, 247, + 42, 61, 77, 90, 106, 121, 134, 148, 160, 173, 191, 211, 225, 238, 245, 251, + 50, 72, 87, 100, 118, 128, 145, 158, 168, 183, 204, 222, 233, 242, 249, 253, + 57, 80, 97, 111, 131, 143, 155, 169, 178, 192, 214, 231, 239, 246, 250, 254, + 65, 88, 107, 124, 139, 152, 163, 177, 185, 199, 221, 234, 243, 248, 252, 255, + }; + + private static readonly short[] _vp9RowIscan16X16 = { + 0, 1, 2, 4, 6, 9, 12, 17, 22, 29, 36, 43, 54, 64, 76, + 86, 3, 5, 7, 11, 15, 19, 25, 32, 38, 48, 59, 68, 84, 99, + 115, 130, 8, 10, 13, 18, 23, 27, 33, 42, 51, 60, 72, 88, 103, + 119, 142, 167, 14, 16, 20, 26, 31, 37, 44, 53, 61, 73, 85, 100, + 116, 135, 161, 185, 21, 24, 30, 35, 40, 47, 55, 65, 74, 81, 94, + 112, 133, 154, 179, 205, 28, 34, 39, 45, 50, 58, 67, 77, 87, 96, + 106, 121, 146, 169, 196, 212, 41, 46, 49, 56, 63, 70, 79, 90, 98, + 107, 122, 138, 159, 182, 207, 222, 52, 57, 62, 69, 75, 83, 93, 102, + 110, 120, 134, 150, 176, 195, 215, 226, 66, 71, 78, 82, 91, 97, 108, + 113, 127, 136, 148, 168, 188, 202, 221, 232, 80, 89, 92, 101, 105, 114, + 125, 131, 139, 151, 162, 177, 192, 208, 223, 234, 95, 104, 109, 117, 123, + 128, 143, 144, 155, 165, 175, 190, 206, 219, 233, 239, 111, 118, 124, 129, + 140, 147, 157, 164, 170, 181, 191, 203, 224, 230, 240, 243, 126, 132, 137, + 145, 153, 160, 174, 178, 184, 197, 204, 216, 231, 237, 244, 246, 141, 149, + 156, 166, 172, 180, 189, 199, 200, 210, 220, 228, 238, 242, 249, 251, 152, + 163, 171, 183, 186, 193, 201, 211, 214, 218, 227, 236, 245, 247, 252, 253, + 158, 173, 187, 194, 198, 209, 213, 217, 225, 229, 235, 241, 248, 250, 254, + 255, + }; + + private static readonly short[] _vp9DefaultIscan16X16 = { + 0, 2, 5, 9, 17, 24, 36, 44, 55, 72, 88, 104, 128, 143, 166, + 179, 1, 4, 8, 13, 20, 30, 40, 54, 66, 79, 96, 113, 141, 154, + 178, 196, 3, 7, 11, 18, 25, 33, 46, 57, 71, 86, 101, 119, 148, + 164, 186, 201, 6, 12, 16, 23, 31, 39, 53, 64, 78, 92, 110, 127, + 153, 169, 193, 208, 10, 14, 19, 28, 37, 47, 58, 67, 84, 98, 114, + 133, 161, 176, 198, 214, 15, 21, 26, 34, 43, 52, 65, 77, 91, 106, + 120, 140, 165, 185, 205, 221, 22, 27, 32, 41, 48, 60, 73, 85, 99, + 116, 130, 151, 175, 190, 211, 225, 29, 35, 42, 49, 59, 69, 81, 95, + 108, 125, 139, 155, 182, 197, 217, 229, 38, 45, 51, 61, 68, 80, 93, + 105, 118, 134, 150, 168, 191, 207, 223, 234, 50, 56, 63, 74, 83, 94, + 109, 117, 129, 147, 163, 177, 199, 213, 228, 238, 62, 70, 76, 87, 97, + 107, 122, 131, 145, 159, 172, 188, 210, 222, 235, 242, 75, 82, 90, 102, + 112, 124, 138, 146, 157, 173, 187, 202, 219, 230, 240, 245, 89, 100, 111, + 123, 132, 142, 156, 167, 180, 189, 203, 216, 231, 237, 246, 250, 103, 115, + 126, 136, 149, 162, 171, 183, 194, 204, 215, 224, 236, 241, 248, 252, 121, + 135, 144, 158, 170, 181, 192, 200, 209, 218, 227, 233, 243, 244, 251, 254, + 137, 152, 160, 174, 184, 195, 206, 212, 220, 226, 232, 239, 247, 249, 253, + 255, + }; + + private static readonly short[] _vp9DefaultIscan32X32 = { + 0, 2, 5, 10, 17, 25, 38, 47, 62, 83, 101, 121, 145, + 170, 193, 204, 210, 219, 229, 233, 245, 257, 275, 299, 342, 356, + 377, 405, 455, 471, 495, 527, 1, 4, 8, 15, 22, 30, 45, + 58, 74, 92, 112, 133, 158, 184, 203, 215, 222, 228, 234, 237, + 256, 274, 298, 317, 355, 376, 404, 426, 470, 494, 526, 551, 3, + 7, 12, 18, 28, 36, 52, 64, 82, 102, 118, 142, 164, 189, + 208, 217, 224, 231, 235, 238, 273, 297, 316, 329, 375, 403, 425, + 440, 493, 525, 550, 567, 6, 11, 16, 23, 31, 43, 60, 73, + 90, 109, 126, 150, 173, 196, 211, 220, 226, 232, 236, 239, 296, + 315, 328, 335, 402, 424, 439, 447, 524, 549, 566, 575, 9, 14, + 19, 29, 37, 50, 65, 78, 95, 116, 134, 157, 179, 201, 214, + 223, 244, 255, 272, 295, 341, 354, 374, 401, 454, 469, 492, 523, + 582, 596, 617, 645, 13, 20, 26, 35, 44, 54, 72, 85, 105, + 123, 140, 163, 182, 205, 216, 225, 254, 271, 294, 314, 353, 373, + 400, 423, 468, 491, 522, 548, 595, 616, 644, 666, 21, 27, 33, + 42, 53, 63, 80, 94, 113, 132, 151, 172, 190, 209, 218, 227, + 270, 293, 313, 327, 372, 399, 422, 438, 490, 521, 547, 565, 615, + 643, 665, 680, 24, 32, 39, 48, 57, 71, 88, 104, 120, 139, + 159, 178, 197, 212, 221, 230, 292, 312, 326, 334, 398, 421, 437, + 446, 520, 546, 564, 574, 642, 664, 679, 687, 34, 40, 46, 56, + 68, 81, 96, 111, 130, 147, 167, 186, 243, 253, 269, 291, 340, + 352, 371, 397, 453, 467, 489, 519, 581, 594, 614, 641, 693, 705, + 723, 747, 41, 49, 55, 67, 77, 91, 107, 124, 138, 161, 177, + 194, 252, 268, 290, 311, 351, 370, 396, 420, 466, 488, 518, 545, + 593, 613, 640, 663, 704, 722, 746, 765, 51, 59, 66, 76, 89, + 99, 119, 131, 149, 168, 181, 200, 267, 289, 310, 325, 369, 395, + 419, 436, 487, 517, 544, 563, 612, 639, 662, 678, 721, 745, 764, + 777, 61, 69, 75, 87, 100, 114, 129, 144, 162, 180, 191, 207, + 288, 309, 324, 333, 394, 418, 435, 445, 516, 543, 562, 573, 638, + 661, 677, 686, 744, 763, 776, 783, 70, 79, 86, 97, 108, 122, + 137, 155, 242, 251, 266, 287, 339, 350, 368, 393, 452, 465, 486, + 515, 580, 592, 611, 637, 692, 703, 720, 743, 788, 798, 813, 833, + 84, 93, 103, 110, 125, 141, 154, 171, 250, 265, 286, 308, 349, + 367, 392, 417, 464, 485, 514, 542, 591, 610, 636, 660, 702, 719, + 742, 762, 797, 812, 832, 848, 98, 106, 115, 127, 143, 156, 169, + 185, 264, 285, 307, 323, 366, 391, 416, 434, 484, 513, 541, 561, + 609, 635, 659, 676, 718, 741, 761, 775, 811, 831, 847, 858, 117, + 128, 136, 148, 160, 175, 188, 198, 284, 306, 322, 332, 390, 415, + 433, 444, 512, 540, 560, 572, 634, 658, 675, 685, 740, 760, 774, + 782, 830, 846, 857, 863, 135, 146, 152, 165, 241, 249, 263, 283, + 338, 348, 365, 389, 451, 463, 483, 511, 579, 590, 608, 633, 691, + 701, 717, 739, 787, 796, 810, 829, 867, 875, 887, 903, 153, 166, + 174, 183, 248, 262, 282, 305, 347, 364, 388, 414, 462, 482, 510, + 539, 589, 607, 632, 657, 700, 716, 738, 759, 795, 809, 828, 845, + 874, 886, 902, 915, 176, 187, 195, 202, 261, 281, 304, 321, 363, + 387, 413, 432, 481, 509, 538, 559, 606, 631, 656, 674, 715, 737, + 758, 773, 808, 827, 844, 856, 885, 901, 914, 923, 192, 199, 206, + 213, 280, 303, 320, 331, 386, 412, 431, 443, 508, 537, 558, 571, + 630, 655, 673, 684, 736, 757, 772, 781, 826, 843, 855, 862, 900, + 913, 922, 927, 240, 247, 260, 279, 337, 346, 362, 385, 450, 461, + 480, 507, 578, 588, 605, 629, 690, 699, 714, 735, 786, 794, 807, + 825, 866, 873, 884, 899, 930, 936, 945, 957, 246, 259, 278, 302, + 345, 361, 384, 411, 460, 479, 506, 536, 587, 604, 628, 654, 698, + 713, 734, 756, 793, 806, 824, 842, 872, 883, 898, 912, 935, 944, + 956, 966, 258, 277, 301, 319, 360, 383, 410, 430, 478, 505, 535, + 557, 603, 627, 653, 672, 712, 733, 755, 771, 805, 823, 841, 854, + 882, 897, 911, 921, 943, 955, 965, 972, 276, 300, 318, 330, 382, + 409, 429, 442, 504, 534, 556, 570, 626, 652, 671, 683, 732, 754, + 770, 780, 822, 840, 853, 861, 896, 910, 920, 926, 954, 964, 971, + 975, 336, 344, 359, 381, 449, 459, 477, 503, 577, 586, 602, 625, + 689, 697, 711, 731, 785, 792, 804, 821, 865, 871, 881, 895, 929, + 934, 942, 953, 977, 981, 987, 995, 343, 358, 380, 408, 458, 476, + 502, 533, 585, 601, 624, 651, 696, 710, 730, 753, 791, 803, 820, + 839, 870, 880, 894, 909, 933, 941, 952, 963, 980, 986, 994, 1001, + 357, 379, 407, 428, 475, 501, 532, 555, 600, 623, 650, 670, 709, + 729, 752, 769, 802, 819, 838, 852, 879, 893, 908, 919, 940, 951, + 962, 970, 985, 993, 1000, 1005, 378, 406, 427, 441, 500, 531, 554, + 569, 622, 649, 669, 682, 728, 751, 768, 779, 818, 837, 851, 860, + 892, 907, 918, 925, 950, 961, 969, 974, 992, 999, 1004, 1007, 448, + 457, 474, 499, 576, 584, 599, 621, 688, 695, 708, 727, 784, 790, + 801, 817, 864, 869, 878, 891, 928, 932, 939, 949, 976, 979, 984, + 991, 1008, 1010, 1013, 1017, 456, 473, 498, 530, 583, 598, 620, 648, + 694, 707, 726, 750, 789, 800, 816, 836, 868, 877, 890, 906, 931, + 938, 948, 960, 978, 983, 990, 998, 1009, 1012, 1016, 1020, 472, 497, + 529, 553, 597, 619, 647, 668, 706, 725, 749, 767, 799, 815, 835, + 850, 876, 889, 905, 917, 937, 947, 959, 968, 982, 989, 997, 1003, + 1011, 1015, 1019, 1022, 496, 528, 552, 568, 618, 646, 667, 681, 724, + 748, 766, 778, 814, 834, 849, 859, 888, 904, 916, 924, 946, 958, + 967, 973, 988, 996, 1002, 1006, 1014, 1018, 1021, 1023, + }; + + public class ScanOrder + { + public short[] Scan { get; } + public short[] IScan { get; } + public short[] Neighbors { get; } + + public ScanOrder(short[] scan, short[] iScan, short[] neighbors) + { + Scan = scan; + IScan = iScan; + Neighbors = neighbors; + } + } + + public static readonly ScanOrder[] Vp9DefaultScanOrders = { + new(_defaultScan4X4, _vp9DefaultIscan4X4, _defaultScan4X4Neighbors), + new(_defaultScan8X8, _vp9DefaultIscan8X8, _defaultScan8X8Neighbors), + new(_defaultScan16X16, _vp9DefaultIscan16X16, _defaultScan16X16Neighbors), + new(_defaultScan32X32, _vp9DefaultIscan32X32, _defaultScan32X32Neighbors), + }; + + public static readonly ScanOrder[][] Vp9ScanOrders = { + new ScanOrder[] + { // TX_4X4 + new(_defaultScan4X4, _vp9DefaultIscan4X4, _defaultScan4X4Neighbors), + new(_rowScan4X4, _vp9RowIscan4X4, _rowScan4X4Neighbors), + new(_colScan4X4, _vp9ColIscan4X4, _colScan4X4Neighbors), + new(_defaultScan4X4, _vp9DefaultIscan4X4, _defaultScan4X4Neighbors), + }, + new ScanOrder[] + { // TX_8X8 + new(_defaultScan8X8, _vp9DefaultIscan8X8, _defaultScan8X8Neighbors), + new(_rowScan8X8, _vp9RowIscan8X8, _rowScan8X8Neighbors), + new(_colScan8X8, _vp9ColIscan8X8, _colScan8X8Neighbors), + new(_defaultScan8X8, _vp9DefaultIscan8X8, _defaultScan8X8Neighbors), + }, + new ScanOrder[] + { // TX_16X16 + new(_defaultScan16X16, _vp9DefaultIscan16X16, _defaultScan16X16Neighbors), + new(_rowScan16X16, _vp9RowIscan16X16, _rowScan16X16Neighbors), + new(_colScan16X16, _vp9ColIscan16X16, _colScan16X16Neighbors), + new(_defaultScan16X16, _vp9DefaultIscan16X16, _defaultScan16X16Neighbors), + }, + new ScanOrder[] + { // TX_32X32 + new(_defaultScan32X32, _vp9DefaultIscan32X32, _defaultScan32X32Neighbors), + new(_defaultScan32X32, _vp9DefaultIscan32X32, _defaultScan32X32Neighbors), + new(_defaultScan32X32, _vp9DefaultIscan32X32, _defaultScan32X32Neighbors), + new(_defaultScan32X32, _vp9DefaultIscan32X32, _defaultScan32X32Neighbors), + }, + }; + + // Entropy MV + + public static readonly sbyte[] Vp9MvJointTree = { + -(sbyte)MvJointType.MvJointZero, 2, -(sbyte)MvJointType.MvJointHnzvz, 4, -(sbyte)MvJointType.MvJointHzvnz, -(sbyte)MvJointType.MvJointHnzvnz, + }; + + public static readonly sbyte[] Vp9MvClassTree = { + -(sbyte)MvClassType.MvClass0, + 2, + -(sbyte)MvClassType.MvClass1, + 4, + 6, + 8, + -(sbyte)MvClassType.MvClass2, + -(sbyte)MvClassType.MvClass3, + 10, + 12, + -(sbyte)MvClassType.MvClass4, + -(sbyte)MvClassType.MvClass5, + -(sbyte)MvClassType.MvClass6, + 14, + 16, + 18, + -(sbyte)MvClassType.MvClass7, + -(sbyte)MvClassType.MvClass8, + -(sbyte)MvClassType.MvClass9, + -(sbyte)MvClassType.MvClass10, + }; + + public static ReadOnlySpan Vp9MvFPTree => new sbyte[] { -0, 2, -1, 4, -2, -3 }; + + // Entropy + + public static ReadOnlySpan Vp9Cat1Prob => new byte[] { 159 }; + public static ReadOnlySpan Vp9Cat2Prob => new byte[] { 165, 145 }; + public static ReadOnlySpan Vp9Cat3Prob => new byte[] { 173, 148, 140 }; + public static ReadOnlySpan Vp9Cat4Prob => new byte[] { 176, 155, 140, 135 }; + public static ReadOnlySpan Vp9Cat5Prob => new byte[] { 180, 157, 141, 134, 130 }; + public static ReadOnlySpan Vp9Cat6Prob => new byte[] { 254, 254, 254, 252, 249, 243, 230, 196, 177, 153, 140, 133, 130, 129 }; + + public static ReadOnlySpan Vp9Cat6ProbHigh12 => new byte[] + { + 255, 255, 255, 255, 254, 254, 54, 252, 249, 243, 230, 196, 177, 153, 140, 133, 130, 129, + }; + + private static readonly byte[] _vp9CoefbandTrans8X8Plus = { + 0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, + // Beyond MAXBAND_INDEX+1 all values are filled as 5 + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + }; + + private static ReadOnlySpan Vp9CoefbandTrans4X4 => new byte[] + { + 0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, + }; + + public static ReadOnlySpan GetBandTranslate(TxSize txSize) + { + return txSize == TxSize.Tx4x4 ? Vp9CoefbandTrans4X4 : _vp9CoefbandTrans8X8Plus; + } + + public static readonly byte[][] Vp9Pareto8Full = { + new byte[] { 3, 86, 128, 6, 86, 23, 88, 29 }, + new byte[] { 6, 86, 128, 11, 87, 42, 91, 52 }, + new byte[] { 9, 86, 129, 17, 88, 61, 94, 76 }, + new byte[] { 12, 86, 129, 22, 88, 77, 97, 93 }, + new byte[] { 15, 87, 129, 28, 89, 93, 100, 110 }, + new byte[] { 17, 87, 129, 33, 90, 105, 103, 123 }, + new byte[] { 20, 88, 130, 38, 91, 118, 106, 136 }, + new byte[] { 23, 88, 130, 43, 91, 128, 108, 146 }, + new byte[] { 26, 89, 131, 48, 92, 139, 111, 156 }, + new byte[] { 28, 89, 131, 53, 93, 147, 114, 163 }, + new byte[] { 31, 90, 131, 58, 94, 156, 117, 171 }, + new byte[] { 34, 90, 131, 62, 94, 163, 119, 177 }, + new byte[] { 37, 90, 132, 66, 95, 171, 122, 184 }, + new byte[] { 39, 90, 132, 70, 96, 177, 124, 189 }, + new byte[] { 42, 91, 132, 75, 97, 183, 127, 194 }, + new byte[] { 44, 91, 132, 79, 97, 188, 129, 198 }, + new byte[] { 47, 92, 133, 83, 98, 193, 132, 202 }, + new byte[] { 49, 92, 133, 86, 99, 197, 134, 205 }, + new byte[] { 52, 93, 133, 90, 100, 201, 137, 208 }, + new byte[] { 54, 93, 133, 94, 100, 204, 139, 211 }, + new byte[] { 57, 94, 134, 98, 101, 208, 142, 214 }, + new byte[] { 59, 94, 134, 101, 102, 211, 144, 216 }, + new byte[] { 62, 94, 135, 105, 103, 214, 146, 218 }, + new byte[] { 64, 94, 135, 108, 103, 216, 148, 220 }, + new byte[] { 66, 95, 135, 111, 104, 219, 151, 222 }, + new byte[] { 68, 95, 135, 114, 105, 221, 153, 223 }, + new byte[] { 71, 96, 136, 117, 106, 224, 155, 225 }, + new byte[] { 73, 96, 136, 120, 106, 225, 157, 226 }, + new byte[] { 76, 97, 136, 123, 107, 227, 159, 228 }, + new byte[] { 78, 97, 136, 126, 108, 229, 160, 229 }, + new byte[] { 80, 98, 137, 129, 109, 231, 162, 231 }, + new byte[] { 82, 98, 137, 131, 109, 232, 164, 232 }, + new byte[] { 84, 98, 138, 134, 110, 234, 166, 233 }, + new byte[] { 86, 98, 138, 137, 111, 235, 168, 234 }, + new byte[] { 89, 99, 138, 140, 112, 236, 170, 235 }, + new byte[] { 91, 99, 138, 142, 112, 237, 171, 235 }, + new byte[] { 93, 100, 139, 145, 113, 238, 173, 236 }, + new byte[] { 95, 100, 139, 147, 114, 239, 174, 237 }, + new byte[] { 97, 101, 140, 149, 115, 240, 176, 238 }, + new byte[] { 99, 101, 140, 151, 115, 241, 177, 238 }, + new byte[] { 101, 102, 140, 154, 116, 242, 179, 239 }, + new byte[] { 103, 102, 140, 156, 117, 242, 180, 239 }, + new byte[] { 105, 103, 141, 158, 118, 243, 182, 240 }, + new byte[] { 107, 103, 141, 160, 118, 243, 183, 240 }, + new byte[] { 109, 104, 141, 162, 119, 244, 185, 241 }, + new byte[] { 111, 104, 141, 164, 119, 244, 186, 241 }, + new byte[] { 113, 104, 142, 166, 120, 245, 187, 242 }, + new byte[] { 114, 104, 142, 168, 121, 245, 188, 242 }, + new byte[] { 116, 105, 143, 170, 122, 246, 190, 243 }, + new byte[] { 118, 105, 143, 171, 122, 246, 191, 243 }, + new byte[] { 120, 106, 143, 173, 123, 247, 192, 244 }, + new byte[] { 121, 106, 143, 175, 124, 247, 193, 244 }, + new byte[] { 123, 107, 144, 177, 125, 248, 195, 244 }, + new byte[] { 125, 107, 144, 178, 125, 248, 196, 244 }, + new byte[] { 127, 108, 145, 180, 126, 249, 197, 245 }, + new byte[] { 128, 108, 145, 181, 127, 249, 198, 245 }, + new byte[] { 130, 109, 145, 183, 128, 249, 199, 245 }, + new byte[] { 132, 109, 145, 184, 128, 249, 200, 245 }, + new byte[] { 134, 110, 146, 186, 129, 250, 201, 246 }, + new byte[] { 135, 110, 146, 187, 130, 250, 202, 246 }, + new byte[] { 137, 111, 147, 189, 131, 251, 203, 246 }, + new byte[] { 138, 111, 147, 190, 131, 251, 204, 246 }, + new byte[] { 140, 112, 147, 192, 132, 251, 205, 247 }, + new byte[] { 141, 112, 147, 193, 132, 251, 206, 247 }, + new byte[] { 143, 113, 148, 194, 133, 251, 207, 247 }, + new byte[] { 144, 113, 148, 195, 134, 251, 207, 247 }, + new byte[] { 146, 114, 149, 197, 135, 252, 208, 248 }, + new byte[] { 147, 114, 149, 198, 135, 252, 209, 248 }, + new byte[] { 149, 115, 149, 199, 136, 252, 210, 248 }, + new byte[] { 150, 115, 149, 200, 137, 252, 210, 248 }, + new byte[] { 152, 115, 150, 201, 138, 252, 211, 248 }, + new byte[] { 153, 115, 150, 202, 138, 252, 212, 248 }, + new byte[] { 155, 116, 151, 204, 139, 253, 213, 249 }, + new byte[] { 156, 116, 151, 205, 139, 253, 213, 249 }, + new byte[] { 158, 117, 151, 206, 140, 253, 214, 249 }, + new byte[] { 159, 117, 151, 207, 141, 253, 215, 249 }, + new byte[] { 161, 118, 152, 208, 142, 253, 216, 249 }, + new byte[] { 162, 118, 152, 209, 142, 253, 216, 249 }, + new byte[] { 163, 119, 153, 210, 143, 253, 217, 249 }, + new byte[] { 164, 119, 153, 211, 143, 253, 217, 249 }, + new byte[] { 166, 120, 153, 212, 144, 254, 218, 250 }, + new byte[] { 167, 120, 153, 212, 145, 254, 219, 250 }, + new byte[] { 168, 121, 154, 213, 146, 254, 220, 250 }, + new byte[] { 169, 121, 154, 214, 146, 254, 220, 250 }, + new byte[] { 171, 122, 155, 215, 147, 254, 221, 250 }, + new byte[] { 172, 122, 155, 216, 147, 254, 221, 250 }, + new byte[] { 173, 123, 155, 217, 148, 254, 222, 250 }, + new byte[] { 174, 123, 155, 217, 149, 254, 222, 250 }, + new byte[] { 176, 124, 156, 218, 150, 254, 223, 250 }, + new byte[] { 177, 124, 156, 219, 150, 254, 223, 250 }, + new byte[] { 178, 125, 157, 220, 151, 254, 224, 251 }, + new byte[] { 179, 125, 157, 220, 151, 254, 224, 251 }, + new byte[] { 180, 126, 157, 221, 152, 254, 225, 251 }, + new byte[] { 181, 126, 157, 221, 152, 254, 225, 251 }, + new byte[] { 183, 127, 158, 222, 153, 254, 226, 251 }, + new byte[] { 184, 127, 158, 223, 154, 254, 226, 251 }, + new byte[] { 185, 128, 159, 224, 155, 255, 227, 251 }, + new byte[] { 186, 128, 159, 224, 155, 255, 227, 251 }, + new byte[] { 187, 129, 160, 225, 156, 255, 228, 251 }, + new byte[] { 188, 130, 160, 225, 156, 255, 228, 251 }, + new byte[] { 189, 131, 160, 226, 157, 255, 228, 251 }, + new byte[] { 190, 131, 160, 226, 158, 255, 228, 251 }, + new byte[] { 191, 132, 161, 227, 159, 255, 229, 251 }, + new byte[] { 192, 132, 161, 227, 159, 255, 229, 251 }, + new byte[] { 193, 133, 162, 228, 160, 255, 230, 252 }, + new byte[] { 194, 133, 162, 229, 160, 255, 230, 252 }, + new byte[] { 195, 134, 163, 230, 161, 255, 231, 252 }, + new byte[] { 196, 134, 163, 230, 161, 255, 231, 252 }, + new byte[] { 197, 135, 163, 231, 162, 255, 231, 252 }, + new byte[] { 198, 135, 163, 231, 162, 255, 231, 252 }, + new byte[] { 199, 136, 164, 232, 163, 255, 232, 252 }, + new byte[] { 200, 136, 164, 232, 164, 255, 232, 252 }, + new byte[] { 201, 137, 165, 233, 165, 255, 233, 252 }, + new byte[] { 201, 137, 165, 233, 165, 255, 233, 252 }, + new byte[] { 202, 138, 166, 233, 166, 255, 233, 252 }, + new byte[] { 203, 138, 166, 233, 166, 255, 233, 252 }, + new byte[] { 204, 139, 166, 234, 167, 255, 234, 252 }, + new byte[] { 205, 139, 166, 234, 167, 255, 234, 252 }, + new byte[] { 206, 140, 167, 235, 168, 255, 235, 252 }, + new byte[] { 206, 140, 167, 235, 168, 255, 235, 252 }, + new byte[] { 207, 141, 168, 236, 169, 255, 235, 252 }, + new byte[] { 208, 141, 168, 236, 170, 255, 235, 252 }, + new byte[] { 209, 142, 169, 237, 171, 255, 236, 252 }, + new byte[] { 209, 143, 169, 237, 171, 255, 236, 252 }, + new byte[] { 210, 144, 169, 237, 172, 255, 236, 252 }, + new byte[] { 211, 144, 169, 237, 172, 255, 236, 252 }, + new byte[] { 212, 145, 170, 238, 173, 255, 237, 252 }, + new byte[] { 213, 145, 170, 238, 173, 255, 237, 252 }, + new byte[] { 214, 146, 171, 239, 174, 255, 237, 253 }, + new byte[] { 214, 146, 171, 239, 174, 255, 237, 253 }, + new byte[] { 215, 147, 172, 240, 175, 255, 238, 253 }, + new byte[] { 215, 147, 172, 240, 175, 255, 238, 253 }, + new byte[] { 216, 148, 173, 240, 176, 255, 238, 253 }, + new byte[] { 217, 148, 173, 240, 176, 255, 238, 253 }, + new byte[] { 218, 149, 173, 241, 177, 255, 239, 253 }, + new byte[] { 218, 149, 173, 241, 178, 255, 239, 253 }, + new byte[] { 219, 150, 174, 241, 179, 255, 239, 253 }, + new byte[] { 219, 151, 174, 241, 179, 255, 239, 253 }, + new byte[] { 220, 152, 175, 242, 180, 255, 240, 253 }, + new byte[] { 221, 152, 175, 242, 180, 255, 240, 253 }, + new byte[] { 222, 153, 176, 242, 181, 255, 240, 253 }, + new byte[] { 222, 153, 176, 242, 181, 255, 240, 253 }, + new byte[] { 223, 154, 177, 243, 182, 255, 240, 253 }, + new byte[] { 223, 154, 177, 243, 182, 255, 240, 253 }, + new byte[] { 224, 155, 178, 244, 183, 255, 241, 253 }, + new byte[] { 224, 155, 178, 244, 183, 255, 241, 253 }, + new byte[] { 225, 156, 178, 244, 184, 255, 241, 253 }, + new byte[] { 225, 157, 178, 244, 184, 255, 241, 253 }, + new byte[] { 226, 158, 179, 244, 185, 255, 242, 253 }, + new byte[] { 227, 158, 179, 244, 185, 255, 242, 253 }, + new byte[] { 228, 159, 180, 245, 186, 255, 242, 253 }, + new byte[] { 228, 159, 180, 245, 186, 255, 242, 253 }, + new byte[] { 229, 160, 181, 245, 187, 255, 242, 253 }, + new byte[] { 229, 160, 181, 245, 187, 255, 242, 253 }, + new byte[] { 230, 161, 182, 246, 188, 255, 243, 253 }, + new byte[] { 230, 162, 182, 246, 188, 255, 243, 253 }, + new byte[] { 231, 163, 183, 246, 189, 255, 243, 253 }, + new byte[] { 231, 163, 183, 246, 189, 255, 243, 253 }, + new byte[] { 232, 164, 184, 247, 190, 255, 243, 253 }, + new byte[] { 232, 164, 184, 247, 190, 255, 243, 253 }, + new byte[] { 233, 165, 185, 247, 191, 255, 244, 253 }, + new byte[] { 233, 165, 185, 247, 191, 255, 244, 253 }, + new byte[] { 234, 166, 185, 247, 192, 255, 244, 253 }, + new byte[] { 234, 167, 185, 247, 192, 255, 244, 253 }, + new byte[] { 235, 168, 186, 248, 193, 255, 244, 253 }, + new byte[] { 235, 168, 186, 248, 193, 255, 244, 253 }, + new byte[] { 236, 169, 187, 248, 194, 255, 244, 253 }, + new byte[] { 236, 169, 187, 248, 194, 255, 244, 253 }, + new byte[] { 236, 170, 188, 248, 195, 255, 245, 253 }, + new byte[] { 236, 170, 188, 248, 195, 255, 245, 253 }, + new byte[] { 237, 171, 189, 249, 196, 255, 245, 254 }, + new byte[] { 237, 172, 189, 249, 196, 255, 245, 254 }, + new byte[] { 238, 173, 190, 249, 197, 255, 245, 254 }, + new byte[] { 238, 173, 190, 249, 197, 255, 245, 254 }, + new byte[] { 239, 174, 191, 249, 198, 255, 245, 254 }, + new byte[] { 239, 174, 191, 249, 198, 255, 245, 254 }, + new byte[] { 240, 175, 192, 249, 199, 255, 246, 254 }, + new byte[] { 240, 176, 192, 249, 199, 255, 246, 254 }, + new byte[] { 240, 177, 193, 250, 200, 255, 246, 254 }, + new byte[] { 240, 177, 193, 250, 200, 255, 246, 254 }, + new byte[] { 241, 178, 194, 250, 201, 255, 246, 254 }, + new byte[] { 241, 178, 194, 250, 201, 255, 246, 254 }, + new byte[] { 242, 179, 195, 250, 202, 255, 246, 254 }, + new byte[] { 242, 180, 195, 250, 202, 255, 246, 254 }, + new byte[] { 242, 181, 196, 250, 203, 255, 247, 254 }, + new byte[] { 242, 181, 196, 250, 203, 255, 247, 254 }, + new byte[] { 243, 182, 197, 251, 204, 255, 247, 254 }, + new byte[] { 243, 183, 197, 251, 204, 255, 247, 254 }, + new byte[] { 244, 184, 198, 251, 205, 255, 247, 254 }, + new byte[] { 244, 184, 198, 251, 205, 255, 247, 254 }, + new byte[] { 244, 185, 199, 251, 206, 255, 247, 254 }, + new byte[] { 244, 185, 199, 251, 206, 255, 247, 254 }, + new byte[] { 245, 186, 200, 251, 207, 255, 247, 254 }, + new byte[] { 245, 187, 200, 251, 207, 255, 247, 254 }, + new byte[] { 246, 188, 201, 252, 207, 255, 248, 254 }, + new byte[] { 246, 188, 201, 252, 207, 255, 248, 254 }, + new byte[] { 246, 189, 202, 252, 208, 255, 248, 254 }, + new byte[] { 246, 190, 202, 252, 208, 255, 248, 254 }, + new byte[] { 247, 191, 203, 252, 209, 255, 248, 254 }, + new byte[] { 247, 191, 203, 252, 209, 255, 248, 254 }, + new byte[] { 247, 192, 204, 252, 210, 255, 248, 254 }, + new byte[] { 247, 193, 204, 252, 210, 255, 248, 254 }, + new byte[] { 248, 194, 205, 252, 211, 255, 248, 254 }, + new byte[] { 248, 194, 205, 252, 211, 255, 248, 254 }, + new byte[] { 248, 195, 206, 252, 212, 255, 249, 254 }, + new byte[] { 248, 196, 206, 252, 212, 255, 249, 254 }, + new byte[] { 249, 197, 207, 253, 213, 255, 249, 254 }, + new byte[] { 249, 197, 207, 253, 213, 255, 249, 254 }, + new byte[] { 249, 198, 208, 253, 214, 255, 249, 254 }, + new byte[] { 249, 199, 209, 253, 214, 255, 249, 254 }, + new byte[] { 250, 200, 210, 253, 215, 255, 249, 254 }, + new byte[] { 250, 200, 210, 253, 215, 255, 249, 254 }, + new byte[] { 250, 201, 211, 253, 215, 255, 249, 254 }, + new byte[] { 250, 202, 211, 253, 215, 255, 249, 254 }, + new byte[] { 250, 203, 212, 253, 216, 255, 249, 254 }, + new byte[] { 250, 203, 212, 253, 216, 255, 249, 254 }, + new byte[] { 251, 204, 213, 253, 217, 255, 250, 254 }, + new byte[] { 251, 205, 213, 253, 217, 255, 250, 254 }, + new byte[] { 251, 206, 214, 254, 218, 255, 250, 254 }, + new byte[] { 251, 206, 215, 254, 218, 255, 250, 254 }, + new byte[] { 252, 207, 216, 254, 219, 255, 250, 254 }, + new byte[] { 252, 208, 216, 254, 219, 255, 250, 254 }, + new byte[] { 252, 209, 217, 254, 220, 255, 250, 254 }, + new byte[] { 252, 210, 217, 254, 220, 255, 250, 254 }, + new byte[] { 252, 211, 218, 254, 221, 255, 250, 254 }, + new byte[] { 252, 212, 218, 254, 221, 255, 250, 254 }, + new byte[] { 253, 213, 219, 254, 222, 255, 250, 254 }, + new byte[] { 253, 213, 220, 254, 222, 255, 250, 254 }, + new byte[] { 253, 214, 221, 254, 223, 255, 250, 254 }, + new byte[] { 253, 215, 221, 254, 223, 255, 250, 254 }, + new byte[] { 253, 216, 222, 254, 224, 255, 251, 254 }, + new byte[] { 253, 217, 223, 254, 224, 255, 251, 254 }, + new byte[] { 253, 218, 224, 254, 225, 255, 251, 254 }, + new byte[] { 253, 219, 224, 254, 225, 255, 251, 254 }, + new byte[] { 254, 220, 225, 254, 225, 255, 251, 254 }, + new byte[] { 254, 221, 226, 254, 225, 255, 251, 254 }, + new byte[] { 254, 222, 227, 255, 226, 255, 251, 254 }, + new byte[] { 254, 223, 227, 255, 226, 255, 251, 254 }, + new byte[] { 254, 224, 228, 255, 227, 255, 251, 254 }, + new byte[] { 254, 225, 229, 255, 227, 255, 251, 254 }, + new byte[] { 254, 226, 230, 255, 228, 255, 251, 254 }, + new byte[] { 254, 227, 230, 255, 229, 255, 251, 254 }, + new byte[] { 255, 228, 231, 255, 230, 255, 251, 254 }, + new byte[] { 255, 229, 232, 255, 230, 255, 251, 254 }, + new byte[] { 255, 230, 233, 255, 231, 255, 252, 254 }, + new byte[] { 255, 231, 234, 255, 231, 255, 252, 254 }, + new byte[] { 255, 232, 235, 255, 232, 255, 252, 254 }, + new byte[] { 255, 233, 236, 255, 232, 255, 252, 254 }, + new byte[] { 255, 235, 237, 255, 233, 255, 252, 254 }, + new byte[] { 255, 236, 238, 255, 234, 255, 252, 254 }, + new byte[] { 255, 238, 240, 255, 235, 255, 252, 255 }, + new byte[] { 255, 239, 241, 255, 235, 255, 252, 254 }, + new byte[] { 255, 241, 243, 255, 236, 255, 252, 254 }, + new byte[] { 255, 243, 245, 255, 237, 255, 252, 254 }, + new byte[] { 255, 246, 247, 255, 239, 255, 253, 255 }, + }; + + // Array indices are identical to previously-existing INTRAMODECONTEXTNODES. + public static readonly sbyte[] Vp9IntraModeTree = { + -(sbyte)PredictionMode.DcPred, 2, // 0 = DC_NODE + -(sbyte)PredictionMode.TmPred, 4, // 1 = TM_NODE + -(sbyte)PredictionMode.VPred, 6, // 2 = V_NODE + 8, 12, // 3 = COM_NODE + -(sbyte)PredictionMode.HPred, 10, // 4 = H_NODE + -(sbyte)PredictionMode.D135Pred, -(sbyte)PredictionMode.D117Pred, // 5 = D135_NODE + -(sbyte)PredictionMode.D45Pred, 14, // 6 = D45_NODE + -(sbyte)PredictionMode.D63Pred, 16, // 7 = D63_NODE + -(sbyte)PredictionMode.D153Pred, -(sbyte)PredictionMode.D207Pred, // 8 = D153_NODE + }; + + public static readonly sbyte[] Vp9InterModeTree = { + -((sbyte)PredictionMode.ZeroMv - (sbyte)PredictionMode. NearestMv), 2, + -((sbyte)PredictionMode.NearestMv - (sbyte)PredictionMode.NearestMv), 4, + -((sbyte)PredictionMode.NearMv - (sbyte)PredictionMode.NearestMv), + -((sbyte)PredictionMode.NewMv - (sbyte)PredictionMode.NearestMv), + }; + + public static readonly sbyte[] Vp9PartitionTree = { + -(sbyte)PartitionType.PartitionNone, 2, -(sbyte)PartitionType.PartitionHorz, 4, -(sbyte)PartitionType.PartitionVert, -(sbyte)PartitionType.PartitionSplit, + }; + + public static readonly sbyte[] Vp9SwitchableInterpTree = { + -Constants.EightTap, 2, -Constants.EightTapSmooth, -Constants.EightTapSharp, + }; + + public static readonly sbyte[] Vp9SegmentTree = { + 2, 4, 6, 8, 10, 12, 0, -1, -2, -3, -4, -5, -6, -7, + }; + + // MV Ref + + // This is used to figure out a context for the ref blocks. The code flattens + // an array that would have 3 possible counts (0, 1 & 2) for 3 choices by + // adding 9 for each intra block, 3 for each zero mv and 1 for each new + // motion vector. This single number is then converted into a context + // with a single lookup ( CounterToContext ). + public static readonly int[] Mode2Counter = { + 9, // DC_PRED + 9, // V_PRED + 9, // H_PRED + 9, // D45_PRED + 9, // D135_PRED + 9, // D117_PRED + 9, // D153_PRED + 9, // D207_PRED + 9, // D63_PRED + 9, // TM_PRED + 0, // NEARESTMV + 0, // NEARMV + 3, // ZEROMV + 1, // NEWMV + }; + + // There are 3^3 different combinations of 3 counts that can be either 0,1 or + // 2. However the actual count can never be greater than 2 so the highest + // counter we need is 18. 9 is an invalid counter that's never used. + public static readonly MotionVectorContext[] CounterToContext = { + MotionVectorContext.BothPredicted, // 0 + MotionVectorContext.NewPlusNonIntra, // 1 + MotionVectorContext.BothNew, // 2 + MotionVectorContext.ZeroPlusPredicted, // 3 + MotionVectorContext.NewPlusNonIntra, // 4 + MotionVectorContext.InvalidCase, // 5 + MotionVectorContext.BothZero, // 6 + MotionVectorContext.InvalidCase, // 7 + MotionVectorContext.InvalidCase, // 8 + MotionVectorContext.IntraPlusNonIntra, // 9 + MotionVectorContext.IntraPlusNonIntra, // 10 + MotionVectorContext.InvalidCase, // 11 + MotionVectorContext.IntraPlusNonIntra, // 12 + MotionVectorContext.InvalidCase, // 13 + MotionVectorContext.InvalidCase, // 14 + MotionVectorContext.InvalidCase, // 15 + MotionVectorContext.InvalidCase, // 16 + MotionVectorContext.InvalidCase, // 17 + MotionVectorContext.BothIntra, // 18 + }; + + public static readonly Position[][] MvRefBlocks = { + // 4X4 + new Position[] { + new(-1, 0), + new(0, -1), + new(-1, -1), + new(-2, 0), + new(0, -2), + new(-2, -1), + new(-1, -2), + new(-2, -2), + }, + // 4X8 + new Position[] { + new(-1, 0), + new(0, -1), + new(-1, -1), + new(-2, 0), + new(0, -2), + new(-2, -1), + new(-1, -2), + new(-2, -2), + }, + // 8X4 + new Position[] { + new(-1, 0), + new(0, -1), + new(-1, -1), + new(-2, 0), + new(0, -2), + new(-2, -1), + new(-1, -2), + new(-2, -2), + }, + // 8X8 + new Position[] { + new(-1, 0), + new(0, -1), + new(-1, -1), + new(-2, 0), + new(0, -2), + new(-2, -1), + new(-1, -2), + new(-2, -2), + }, + // 8X16 + new Position[] { + new(0, -1), + new(-1, 0), + new(1, -1), + new(-1, -1), + new(0, -2), + new(-2, 0), + new(-2, -1), + new(-1, -2), + }, + // 16X8 + new Position[] { + new(-1, 0), + new(0, -1), + new(-1, 1), + new(-1, -1), + new(-2, 0), + new(0, -2), + new(-1, -2), + new(-2, -1), + }, + // 16X16 + new Position[] { + new(-1, 0), + new(0, -1), + new(-1, 1), + new(1, -1), + new(-1, -1), + new(-3, 0), + new(0, -3), + new(-3, -3), + }, + // 16X32 + new Position[] { + new(0, -1), + new(-1, 0), + new(2, -1), + new(-1, -1), + new(-1, 1), + new(0, -3), + new(-3, 0), + new(-3, -3), + }, + // 32X16 + new Position[] { + new(-1, 0), + new(0, -1), + new(-1, 2), + new(-1, -1), + new(1, -1), + new(-3, 0), + new(0, -3), + new(-3, -3), + }, + // 32X32 + new Position[] { + new(-1, 1), + new(1, -1), + new(-1, 2), + new(2, -1), + new(-1, -1), + new(-3, 0), + new(0, -3), + new(-3, -3), + }, + // 32X64 + new Position[] { + new(0, -1), + new(-1, 0), + new(4, -1), + new(-1, 2), + new(-1, -1), + new(0, -3), + new(-3, 0), + new(2, -1), + }, + // 64X32 + new Position[] { + new(-1, 0), + new(0, -1), + new(-1, 4), + new(2, -1), + new(-1, -1), + new(-3, 0), + new(0, -3), + new(-1, 2), + }, + // 64X64 + new Position[] { + new(-1, 3), + new(3, -1), + new(-1, 4), + new(4, -1), + new(-1, -1), + new(-1, 0), + new(0, -1), + new(-1, 6), + }, + }; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/PredCommon.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/PredCommon.cs new file mode 100644 index 00000000..1f391f6e --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/PredCommon.cs @@ -0,0 +1,393 @@ +using Ryujinx.Graphics.Nvdec.Vp9.Types; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal static class PredCommon + { + public static int GetReferenceModeContext(ref Vp9Common cm, ref MacroBlockD xd) + { + int ctx; + // Note: + // The mode info data structure has a one element border above and to the + // left of the entries corresponding to real macroblocks. + // The prediction flags in these dummy entries are initialized to 0. + if (!xd.AboveMi.IsNull && !xd.LeftMi.IsNull) + { // both edges available + if (!xd.AboveMi.Value.HasSecondRef() && !xd.LeftMi.Value.HasSecondRef()) + { + // Neither edge uses comp pred (0/1) + ctx = (xd.AboveMi.Value.RefFrame[0] == cm.CompFixedRef ? 1 : 0) ^ + (xd.LeftMi.Value.RefFrame[0] == cm.CompFixedRef ? 1 : 0); + } + else if (!xd.AboveMi.Value.HasSecondRef()) + { + // One of two edges uses comp pred (2/3) + ctx = 2 + (xd.AboveMi.Value.RefFrame[0] == cm.CompFixedRef || !xd.AboveMi.Value.IsInterBlock() ? 1 : 0); + } + else if (!xd.LeftMi.Value.HasSecondRef()) + { + // One of two edges uses comp pred (2/3) + ctx = 2 + (xd.LeftMi.Value.RefFrame[0] == cm.CompFixedRef || !xd.LeftMi.Value.IsInterBlock() ? 1 : 0); + } + else // Both edges use comp pred (4) + { + ctx = 4; + } + } + else if (!xd.AboveMi.IsNull || !xd.LeftMi.IsNull) + { // One edge available + ref ModeInfo edgeMi = ref !xd.AboveMi.IsNull ? ref xd.AboveMi.Value : ref xd.LeftMi.Value; + + if (!edgeMi.HasSecondRef()) + { + // Edge does not use comp pred (0/1) + ctx = edgeMi.RefFrame[0] == cm.CompFixedRef ? 1 : 0; + } + else + { + // Edge uses comp pred (3) + ctx = 3; + } + } + else + { // No edges available (1) + ctx = 1; + } + Debug.Assert(ctx >= 0 && ctx < Constants.CompInterContexts); + + return ctx; + } + + // Returns a context number for the given MB prediction signal + public static int GetPredContextCompRefP(ref Vp9Common cm, ref MacroBlockD xd) + { + int predContext; + // Note: + // The mode info data structure has a one element border above and to the + // left of the entries corresponding to real macroblocks. + // The prediction flags in these dummy entries are initialized to 0. + int fixRefIdx = cm.RefFrameSignBias[cm.CompFixedRef]; + int varRefIdx = fixRefIdx == 0 ? 1 : 0; + + if (!xd.AboveMi.IsNull && !xd.LeftMi.IsNull) + { // Both edges available + bool aboveIntra = !xd.AboveMi.Value.IsInterBlock(); + bool leftIntra = !xd.LeftMi.Value.IsInterBlock(); + + if (aboveIntra && leftIntra) + { // Intra/Intra (2) + predContext = 2; + } + else if (aboveIntra || leftIntra) + { // Intra/Inter + ref ModeInfo edgeMi = ref aboveIntra ? ref xd.LeftMi.Value : ref xd.AboveMi.Value; + + if (!edgeMi.HasSecondRef()) // single pred (1/3) + { + predContext = 1 + 2 * (edgeMi.RefFrame[0] != cm.CompVarRef[1] ? 1 : 0); + } + else // Comp pred (1/3) + { + predContext = 1 + 2 * (edgeMi.RefFrame[varRefIdx] != cm.CompVarRef[1] ? 1 : 0); + } + } + else + { // Inter/Inter + bool lSg = !xd.LeftMi.Value.HasSecondRef(); + bool aSg = !xd.AboveMi.Value.HasSecondRef(); + sbyte vrfa = aSg ? xd.AboveMi.Value.RefFrame[0] : xd.AboveMi.Value.RefFrame[varRefIdx]; + sbyte vrfl = lSg ? xd.LeftMi.Value.RefFrame[0] : xd.LeftMi.Value.RefFrame[varRefIdx]; + + if (vrfa == vrfl && cm.CompVarRef[1] == vrfa) + { + predContext = 0; + } + else if (lSg && aSg) + { // Single/Single + if ((vrfa == cm.CompFixedRef && vrfl == cm.CompVarRef[0]) || + (vrfl == cm.CompFixedRef && vrfa == cm.CompVarRef[0])) + { + predContext = 4; + } + else if (vrfa == vrfl) + { + predContext = 3; + } + else + { + predContext = 1; + } + } + else if (lSg || aSg) + { // Single/Comp + sbyte vrfc = lSg ? vrfa : vrfl; + sbyte rfs = aSg ? vrfa : vrfl; + if (vrfc == cm.CompVarRef[1] && rfs != cm.CompVarRef[1]) + { + predContext = 1; + } + else if (rfs == cm.CompVarRef[1] && vrfc != cm.CompVarRef[1]) + { + predContext = 2; + } + else + { + predContext = 4; + } + } + else if (vrfa == vrfl) + { // Comp/Comp + predContext = 4; + } + else + { + predContext = 2; + } + } + } + else if (!xd.AboveMi.IsNull || !xd.LeftMi.IsNull) + { // One edge available + ref ModeInfo edgeMi = ref !xd.AboveMi.IsNull ? ref xd.AboveMi.Value : ref xd.LeftMi.Value; + + if (!edgeMi.IsInterBlock()) + { + predContext = 2; + } + else + { + if (edgeMi.HasSecondRef()) + { + predContext = 4 * (edgeMi.RefFrame[varRefIdx] != cm.CompVarRef[1] ? 1 : 0); + } + else + { + predContext = 3 * (edgeMi.RefFrame[0] != cm.CompVarRef[1] ? 1 : 0); + } + } + } + else + { // No edges available (2) + predContext = 2; + } + Debug.Assert(predContext >= 0 && predContext < Constants.RefContexts); + + return predContext; + } + + public static int GetPredContextSingleRefP1(ref MacroBlockD xd) + { + int predContext; + // Note: + // The mode info data structure has a one element border above and to the + // left of the entries corresponding to real macroblocks. + // The prediction flags in these dummy entries are initialized to 0. + if (!xd.AboveMi.IsNull && !xd.LeftMi.IsNull) + { // Both edges available + bool aboveIntra = !xd.AboveMi.Value.IsInterBlock(); + bool leftIntra = !xd.LeftMi.Value.IsInterBlock(); + + if (aboveIntra && leftIntra) + { // Intra/Intra + predContext = 2; + } + else if (aboveIntra || leftIntra) + { // Intra/Inter or Inter/Intra + ref ModeInfo edgeMi = ref aboveIntra ? ref xd.LeftMi.Value : ref xd.AboveMi.Value; + if (!edgeMi.HasSecondRef()) + { + predContext = 4 * (edgeMi.RefFrame[0] == Constants.LastFrame ? 1 : 0); + } + else + { + predContext = 1 + (edgeMi.RefFrame[0] == Constants.LastFrame || + edgeMi.RefFrame[1] == Constants.LastFrame ? 1 : 0); + } + } + else + { // Inter/Inter + bool aboveHasSecond = xd.AboveMi.Value.HasSecondRef(); + bool leftHasSecond = xd.LeftMi.Value.HasSecondRef(); + sbyte above0 = xd.AboveMi.Value.RefFrame[0]; + sbyte above1 = xd.AboveMi.Value.RefFrame[1]; + sbyte left0 = xd.LeftMi.Value.RefFrame[0]; + sbyte left1 = xd.LeftMi.Value.RefFrame[1]; + + if (aboveHasSecond && leftHasSecond) + { + predContext = 1 + (above0 == Constants.LastFrame || above1 == Constants.LastFrame || + left0 == Constants.LastFrame || left1 == Constants.LastFrame ? 1 : 0); + } + else if (aboveHasSecond || leftHasSecond) + { + sbyte rfs = !aboveHasSecond ? above0 : left0; + sbyte crf1 = aboveHasSecond ? above0 : left0; + sbyte crf2 = aboveHasSecond ? above1 : left1; + + if (rfs == Constants.LastFrame) + { + predContext = 3 + (crf1 == Constants.LastFrame || crf2 == Constants.LastFrame ? 1 : 0); + } + else + { + predContext = (crf1 == Constants.LastFrame || crf2 == Constants.LastFrame ? 1 : 0); + } + } + else + { + predContext = 2 * (above0 == Constants.LastFrame ? 1 : 0) + 2 * (left0 == Constants.LastFrame ? 1 : 0); + } + } + } + else if (!xd.AboveMi.IsNull || !xd.LeftMi.IsNull) + { // One edge available + ref ModeInfo edgeMi = ref !xd.AboveMi.IsNull ? ref xd.AboveMi.Value : ref xd.LeftMi.Value; + if (!edgeMi.IsInterBlock()) + { // Intra + predContext = 2; + } + else + { // Inter + if (!edgeMi.HasSecondRef()) + { + predContext = 4 * (edgeMi.RefFrame[0] == Constants.LastFrame ? 1 : 0); + } + else + { + predContext = 1 + (edgeMi.RefFrame[0] == Constants.LastFrame || + edgeMi.RefFrame[1] == Constants.LastFrame ? 1 : 0); + } + } + } + else + { // No edges available + predContext = 2; + } + Debug.Assert(predContext >= 0 && predContext < Constants.RefContexts); + + return predContext; + } + + public static int GetPredContextSingleRefP2(ref MacroBlockD xd) + { + int predContext; + + // Note: + // The mode info data structure has a one element border above and to the + // left of the entries corresponding to real macroblocks. + // The prediction flags in these dummy entries are initialized to 0. + if (!xd.AboveMi.IsNull && !xd.LeftMi.IsNull) + { // Both edges available + bool aboveIntra = !xd.AboveMi.Value.IsInterBlock(); + bool leftIntra = !xd.LeftMi.Value.IsInterBlock(); + + if (aboveIntra && leftIntra) + { // Intra/Intra + predContext = 2; + } + else if (aboveIntra || leftIntra) + { // Intra/Inter or Inter/Intra + ref ModeInfo edgeMi = ref aboveIntra ? ref xd.LeftMi.Value : ref xd.AboveMi.Value; + if (!edgeMi.HasSecondRef()) + { + if (edgeMi.RefFrame[0] == Constants.LastFrame) + { + predContext = 3; + } + else + { + predContext = 4 * (edgeMi.RefFrame[0] == Constants.GoldenFrame ? 1 : 0); + } + } + else + { + predContext = 1 + 2 * (edgeMi.RefFrame[0] == Constants.GoldenFrame || + edgeMi.RefFrame[1] == Constants.GoldenFrame ? 1 : 0); + } + } + else + { // Inter/Inter + bool aboveHasSecond = xd.AboveMi.Value.HasSecondRef(); + bool leftHasSecond = xd.LeftMi.Value.HasSecondRef(); + sbyte above0 = xd.AboveMi.Value.RefFrame[0]; + sbyte above1 = xd.AboveMi.Value.RefFrame[1]; + sbyte left0 = xd.LeftMi.Value.RefFrame[0]; + sbyte left1 = xd.LeftMi.Value.RefFrame[1]; + + if (aboveHasSecond && leftHasSecond) + { + if (above0 == left0 && above1 == left1) + { + predContext = 3 * (above0 == Constants.GoldenFrame || above1 == Constants.GoldenFrame || + left0 == Constants.GoldenFrame || left1 == Constants.GoldenFrame ? 1 : 0); + } + else + { + predContext = 2; + } + } + else if (aboveHasSecond || leftHasSecond) + { + sbyte rfs = !aboveHasSecond ? above0 : left0; + sbyte crf1 = aboveHasSecond ? above0 : left0; + sbyte crf2 = aboveHasSecond ? above1 : left1; + + if (rfs == Constants.GoldenFrame) + { + predContext = 3 + (crf1 == Constants.GoldenFrame || crf2 == Constants.GoldenFrame ? 1 : 0); + } + else if (rfs == Constants.AltRefFrame) + { + predContext = crf1 == Constants.GoldenFrame || crf2 == Constants.GoldenFrame ? 1 : 0; + } + else + { + predContext = 1 + 2 * (crf1 == Constants.GoldenFrame || crf2 == Constants.GoldenFrame ? 1 : 0); + } + } + else + { + if (above0 == Constants.LastFrame && left0 == Constants.LastFrame) + { + predContext = 3; + } + else if (above0 == Constants.LastFrame || left0 == Constants.LastFrame) + { + sbyte edge0 = (above0 == Constants.LastFrame) ? left0 : above0; + predContext = 4 * (edge0 == Constants.GoldenFrame ? 1 : 0); + } + else + { + predContext = 2 * (above0 == Constants.GoldenFrame ? 1 : 0) + 2 * (left0 == Constants.GoldenFrame ? 1 : 0); + } + } + } + } + else if (!xd.AboveMi.IsNull || !xd.LeftMi.IsNull) + { // One edge available + ref ModeInfo edgeMi = ref !xd.AboveMi.IsNull ? ref xd.AboveMi.Value : ref xd.LeftMi.Value; + + if (!edgeMi.IsInterBlock() || (edgeMi.RefFrame[0] == Constants.LastFrame && !edgeMi.HasSecondRef())) + { + predContext = 2; + } + else if (!edgeMi.HasSecondRef()) + { + predContext = 4 * (edgeMi.RefFrame[0] == Constants.GoldenFrame ? 1 : 0); + } + else + { + predContext = 3 * (edgeMi.RefFrame[0] == Constants.GoldenFrame || + edgeMi.RefFrame[1] == Constants.GoldenFrame ? 1 : 0); + } + } + else + { // No edges available (2) + predContext = 2; + } + Debug.Assert(predContext >= 0 && predContext < Constants.RefContexts); + + return predContext; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/QuantCommon.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/QuantCommon.cs new file mode 100644 index 00000000..8e8fafdb --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/QuantCommon.cs @@ -0,0 +1,204 @@ +using Ryujinx.Graphics.Nvdec.Vp9.Types; +using System; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal static class QuantCommon + { + public const int MinQ = 0; + public const int MaxQ = 255; + + private static readonly short[] _dcQlookup = { + 4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, + 19, 19, 20, 21, 22, 23, 24, 25, 26, 26, 27, 28, 29, 30, + 31, 32, 32, 33, 34, 35, 36, 37, 38, 38, 39, 40, 41, 42, + 43, 43, 44, 45, 46, 47, 48, 48, 49, 50, 51, 52, 53, 53, + 54, 55, 56, 57, 57, 58, 59, 60, 61, 62, 62, 63, 64, 65, + 66, 66, 67, 68, 69, 70, 70, 71, 72, 73, 74, 74, 75, 76, + 77, 78, 78, 79, 80, 81, 81, 82, 83, 84, 85, 85, 87, 88, + 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 108, 110, + 111, 113, 114, 116, 117, 118, 120, 121, 123, 125, 127, 129, 131, 134, + 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 161, 164, + 166, 169, 172, 174, 177, 180, 182, 185, 187, 190, 192, 195, 199, 202, + 205, 208, 211, 214, 217, 220, 223, 226, 230, 233, 237, 240, 243, 247, + 250, 253, 257, 261, 265, 269, 272, 276, 280, 284, 288, 292, 296, 300, + 304, 309, 313, 317, 322, 326, 330, 335, 340, 344, 349, 354, 359, 364, + 369, 374, 379, 384, 389, 395, 400, 406, 411, 417, 423, 429, 435, 441, + 447, 454, 461, 467, 475, 482, 489, 497, 505, 513, 522, 530, 539, 549, + 559, 569, 579, 590, 602, 614, 626, 640, 654, 668, 684, 700, 717, 736, + 755, 775, 796, 819, 843, 869, 896, 925, 955, 988, 1022, 1058, 1098, 1139, + 1184, 1232, 1282, 1336, + }; + + private static readonly short[] _dcQlookup10 = { + 4, 9, 10, 13, 15, 17, 20, 22, 25, 28, 31, 34, 37, + 40, 43, 47, 50, 53, 57, 60, 64, 68, 71, 75, 78, 82, + 86, 90, 93, 97, 101, 105, 109, 113, 116, 120, 124, 128, 132, + 136, 140, 143, 147, 151, 155, 159, 163, 166, 170, 174, 178, 182, + 185, 189, 193, 197, 200, 204, 208, 212, 215, 219, 223, 226, 230, + 233, 237, 241, 244, 248, 251, 255, 259, 262, 266, 269, 273, 276, + 280, 283, 287, 290, 293, 297, 300, 304, 307, 310, 314, 317, 321, + 324, 327, 331, 334, 337, 343, 350, 356, 362, 369, 375, 381, 387, + 394, 400, 406, 412, 418, 424, 430, 436, 442, 448, 454, 460, 466, + 472, 478, 484, 490, 499, 507, 516, 525, 533, 542, 550, 559, 567, + 576, 584, 592, 601, 609, 617, 625, 634, 644, 655, 666, 676, 687, + 698, 708, 718, 729, 739, 749, 759, 770, 782, 795, 807, 819, 831, + 844, 856, 868, 880, 891, 906, 920, 933, 947, 961, 975, 988, 1001, + 1015, 1030, 1045, 1061, 1076, 1090, 1105, 1120, 1137, 1153, 1170, 1186, 1202, + 1218, 1236, 1253, 1271, 1288, 1306, 1323, 1342, 1361, 1379, 1398, 1416, 1436, + 1456, 1476, 1496, 1516, 1537, 1559, 1580, 1601, 1624, 1647, 1670, 1692, 1717, + 1741, 1766, 1791, 1817, 1844, 1871, 1900, 1929, 1958, 1990, 2021, 2054, 2088, + 2123, 2159, 2197, 2236, 2276, 2319, 2363, 2410, 2458, 2508, 2561, 2616, 2675, + 2737, 2802, 2871, 2944, 3020, 3102, 3188, 3280, 3375, 3478, 3586, 3702, 3823, + 3953, 4089, 4236, 4394, 4559, 4737, 4929, 5130, 5347, + }; + + private static readonly short[] _dcQlookup12 = { + 4, 12, 18, 25, 33, 41, 50, 60, 70, 80, 91, + 103, 115, 127, 140, 153, 166, 180, 194, 208, 222, 237, + 251, 266, 281, 296, 312, 327, 343, 358, 374, 390, 405, + 421, 437, 453, 469, 484, 500, 516, 532, 548, 564, 580, + 596, 611, 627, 643, 659, 674, 690, 706, 721, 737, 752, + 768, 783, 798, 814, 829, 844, 859, 874, 889, 904, 919, + 934, 949, 964, 978, 993, 1008, 1022, 1037, 1051, 1065, 1080, + 1094, 1108, 1122, 1136, 1151, 1165, 1179, 1192, 1206, 1220, 1234, + 1248, 1261, 1275, 1288, 1302, 1315, 1329, 1342, 1368, 1393, 1419, + 1444, 1469, 1494, 1519, 1544, 1569, 1594, 1618, 1643, 1668, 1692, + 1717, 1741, 1765, 1789, 1814, 1838, 1862, 1885, 1909, 1933, 1957, + 1992, 2027, 2061, 2096, 2130, 2165, 2199, 2233, 2267, 2300, 2334, + 2367, 2400, 2434, 2467, 2499, 2532, 2575, 2618, 2661, 2704, 2746, + 2788, 2830, 2872, 2913, 2954, 2995, 3036, 3076, 3127, 3177, 3226, + 3275, 3324, 3373, 3421, 3469, 3517, 3565, 3621, 3677, 3733, 3788, + 3843, 3897, 3951, 4005, 4058, 4119, 4181, 4241, 4301, 4361, 4420, + 4479, 4546, 4612, 4677, 4742, 4807, 4871, 4942, 5013, 5083, 5153, + 5222, 5291, 5367, 5442, 5517, 5591, 5665, 5745, 5825, 5905, 5984, + 6063, 6149, 6234, 6319, 6404, 6495, 6587, 6678, 6769, 6867, 6966, + 7064, 7163, 7269, 7376, 7483, 7599, 7715, 7832, 7958, 8085, 8214, + 8352, 8492, 8635, 8788, 8945, 9104, 9275, 9450, 9639, 9832, 10031, + 10245, 10465, 10702, 10946, 11210, 11482, 11776, 12081, 12409, 12750, 13118, + 13501, 13913, 14343, 14807, 15290, 15812, 16356, 16943, 17575, 18237, 18949, + 19718, 20521, 21387, + }; + + private static readonly short[] _acQlookup = { + 4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, + 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 98, 99, 100, 101, 102, 104, 106, 108, 110, 112, 114, 116, 118, + 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, + 146, 148, 150, 152, 155, 158, 161, 164, 167, 170, 173, 176, 179, + 182, 185, 188, 191, 194, 197, 200, 203, 207, 211, 215, 219, 223, + 227, 231, 235, 239, 243, 247, 251, 255, 260, 265, 270, 275, 280, + 285, 290, 295, 300, 305, 311, 317, 323, 329, 335, 341, 347, 353, + 359, 366, 373, 380, 387, 394, 401, 408, 416, 424, 432, 440, 448, + 456, 465, 474, 483, 492, 501, 510, 520, 530, 540, 550, 560, 571, + 582, 593, 604, 615, 627, 639, 651, 663, 676, 689, 702, 715, 729, + 743, 757, 771, 786, 801, 816, 832, 848, 864, 881, 898, 915, 933, + 951, 969, 988, 1007, 1026, 1046, 1066, 1087, 1108, 1129, 1151, 1173, 1196, + 1219, 1243, 1267, 1292, 1317, 1343, 1369, 1396, 1423, 1451, 1479, 1508, 1537, + 1567, 1597, 1628, 1660, 1692, 1725, 1759, 1793, 1828, + }; + + private static readonly short[] _acQlookup10 = { + 4, 9, 11, 13, 16, 18, 21, 24, 27, 30, 33, 37, 40, + 44, 48, 51, 55, 59, 63, 67, 71, 75, 79, 83, 88, 92, + 96, 100, 105, 109, 114, 118, 122, 127, 131, 136, 140, 145, 149, + 154, 158, 163, 168, 172, 177, 181, 186, 190, 195, 199, 204, 208, + 213, 217, 222, 226, 231, 235, 240, 244, 249, 253, 258, 262, 267, + 271, 275, 280, 284, 289, 293, 297, 302, 306, 311, 315, 319, 324, + 328, 332, 337, 341, 345, 349, 354, 358, 362, 367, 371, 375, 379, + 384, 388, 392, 396, 401, 409, 417, 425, 433, 441, 449, 458, 466, + 474, 482, 490, 498, 506, 514, 523, 531, 539, 547, 555, 563, 571, + 579, 588, 596, 604, 616, 628, 640, 652, 664, 676, 688, 700, 713, + 725, 737, 749, 761, 773, 785, 797, 809, 825, 841, 857, 873, 889, + 905, 922, 938, 954, 970, 986, 1002, 1018, 1038, 1058, 1078, 1098, 1118, + 1138, 1158, 1178, 1198, 1218, 1242, 1266, 1290, 1314, 1338, 1362, 1386, 1411, + 1435, 1463, 1491, 1519, 1547, 1575, 1603, 1631, 1663, 1695, 1727, 1759, 1791, + 1823, 1859, 1895, 1931, 1967, 2003, 2039, 2079, 2119, 2159, 2199, 2239, 2283, + 2327, 2371, 2415, 2459, 2507, 2555, 2603, 2651, 2703, 2755, 2807, 2859, 2915, + 2971, 3027, 3083, 3143, 3203, 3263, 3327, 3391, 3455, 3523, 3591, 3659, 3731, + 3803, 3876, 3952, 4028, 4104, 4184, 4264, 4348, 4432, 4516, 4604, 4692, 4784, + 4876, 4972, 5068, 5168, 5268, 5372, 5476, 5584, 5692, 5804, 5916, 6032, 6148, + 6268, 6388, 6512, 6640, 6768, 6900, 7036, 7172, 7312, + }; + + private static readonly short[] _acQlookup12 = { + 4, 13, 19, 27, 35, 44, 54, 64, 75, 87, 99, + 112, 126, 139, 154, 168, 183, 199, 214, 230, 247, 263, + 280, 297, 314, 331, 349, 366, 384, 402, 420, 438, 456, + 475, 493, 511, 530, 548, 567, 586, 604, 623, 642, 660, + 679, 698, 716, 735, 753, 772, 791, 809, 828, 846, 865, + 884, 902, 920, 939, 957, 976, 994, 1012, 1030, 1049, 1067, + 1085, 1103, 1121, 1139, 1157, 1175, 1193, 1211, 1229, 1246, 1264, + 1282, 1299, 1317, 1335, 1352, 1370, 1387, 1405, 1422, 1440, 1457, + 1474, 1491, 1509, 1526, 1543, 1560, 1577, 1595, 1627, 1660, 1693, + 1725, 1758, 1791, 1824, 1856, 1889, 1922, 1954, 1987, 2020, 2052, + 2085, 2118, 2150, 2183, 2216, 2248, 2281, 2313, 2346, 2378, 2411, + 2459, 2508, 2556, 2605, 2653, 2701, 2750, 2798, 2847, 2895, 2943, + 2992, 3040, 3088, 3137, 3185, 3234, 3298, 3362, 3426, 3491, 3555, + 3619, 3684, 3748, 3812, 3876, 3941, 4005, 4069, 4149, 4230, 4310, + 4390, 4470, 4550, 4631, 4711, 4791, 4871, 4967, 5064, 5160, 5256, + 5352, 5448, 5544, 5641, 5737, 5849, 5961, 6073, 6185, 6297, 6410, + 6522, 6650, 6778, 6906, 7034, 7162, 7290, 7435, 7579, 7723, 7867, + 8011, 8155, 8315, 8475, 8635, 8795, 8956, 9132, 9308, 9484, 9660, + 9836, 10028, 10220, 10412, 10604, 10812, 11020, 11228, 11437, 11661, 11885, + 12109, 12333, 12573, 12813, 13053, 13309, 13565, 13821, 14093, 14365, 14637, + 14925, 15213, 15502, 15806, 16110, 16414, 16734, 17054, 17390, 17726, 18062, + 18414, 18766, 19134, 19502, 19886, 20270, 20670, 21070, 21486, 21902, 22334, + 22766, 23214, 23662, 24126, 24590, 25070, 25551, 26047, 26559, 27071, 27599, + 28143, 28687, 29247, + }; + + public static short DcQuant(int qindex, int delta, BitDepth bitDepth) + { + switch (bitDepth) + { + case BitDepth.Bits8: + return _dcQlookup[Math.Clamp(qindex + delta, 0, MaxQ)]; + case BitDepth.Bits10: + return _dcQlookup10[Math.Clamp(qindex + delta, 0, MaxQ)]; + case BitDepth.Bits12: + return _dcQlookup12[Math.Clamp(qindex + delta, 0, MaxQ)]; + default: + Debug.Assert(false, "bit_depth should be VPX_BITS_8, VPX_BITS_10 or VPX_BITS_12"); + + return -1; + } + } + + public static short AcQuant(int qindex, int delta, BitDepth bitDepth) + { + switch (bitDepth) + { + case BitDepth.Bits8: + return _acQlookup[Math.Clamp(qindex + delta, 0, MaxQ)]; + case BitDepth.Bits10: + return _acQlookup10[Math.Clamp(qindex + delta, 0, MaxQ)]; + case BitDepth.Bits12: + return _acQlookup12[Math.Clamp(qindex + delta, 0, MaxQ)]; + default: + Debug.Assert(false, "bit_depth should be VPX_BITS_8, VPX_BITS_10 or VPX_BITS_12"); + + return -1; + } + } + + public static int GetQIndex(ref Segmentation seg, int segmentId, int baseQIndex) + { + if (seg.IsSegFeatureActive(segmentId, SegLvlFeatures.SegLvlAltQ) != 0) + { + int data = seg.GetSegData(segmentId, SegLvlFeatures.SegLvlAltQ); + int segQIndex = seg.AbsDelta == Constants.SegmentAbsData ? data : baseQIndex + data; + + return Math.Clamp(segQIndex, 0, MaxQ); + } + + return baseQIndex; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/ReconInter.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/ReconInter.cs new file mode 100644 index 00000000..fdb68291 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/ReconInter.cs @@ -0,0 +1,243 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Nvdec.Vp9.Types; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using static Ryujinx.Graphics.Nvdec.Vp9.Dsp.Filter; + +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal static class ReconInter + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void InterPredictor( + byte* src, + int srcStride, + byte* dst, + int dstStride, + int subpelX, + int subpelY, + ref ScaleFactors sf, + int w, + int h, + int refr, + Array8[] kernel, + int xs, + int ys) + { + sf.InterPredict( + subpelX != 0 ? 1 : 0, + subpelY != 0 ? 1 : 0, + refr, + src, + srcStride, + dst, + dstStride, + subpelX, + subpelY, + w, + h, + kernel, + xs, + ys); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void HighbdInterPredictor( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + int subpelX, + int subpelY, + ref ScaleFactors sf, + int w, + int h, + int refr, + Array8[] kernel, + int xs, + int ys, + int bd) + { + sf.HighbdInterPredict( + subpelX != 0 ? 1 : 0, + subpelY != 0 ? 1 : 0, + refr, + src, + srcStride, + dst, + dstStride, + subpelX, + subpelY, + w, + h, + kernel, + xs, + ys, + bd); + } + + private static int RoundMvCompQ4(int value) + { + return (value < 0 ? value - 2 : value + 2) / 4; + } + + private static Mv MiMvPredQ4(ref ModeInfo mi, int idx) + { + return new Mv + { + Row = (short)RoundMvCompQ4( + mi.Bmi[0].Mv[idx].Row + mi.Bmi[1].Mv[idx].Row + + mi.Bmi[2].Mv[idx].Row + mi.Bmi[3].Mv[idx].Row), + Col = (short)RoundMvCompQ4( + mi.Bmi[0].Mv[idx].Col + mi.Bmi[1].Mv[idx].Col + + mi.Bmi[2].Mv[idx].Col + mi.Bmi[3].Mv[idx].Col), + }; + } + + private static int RoundMvCompQ2(int value) + { + return (value < 0 ? value - 1 : value + 1) / 2; + } + + private static Mv MiMvPredQ2(ref ModeInfo mi, int idx, int block0, int block1) + { + return new Mv + { + Row = (short)RoundMvCompQ2( + mi.Bmi[block0].Mv[idx].Row + + mi.Bmi[block1].Mv[idx].Row), + Col = (short)RoundMvCompQ2( + mi.Bmi[block0].Mv[idx].Col + + mi.Bmi[block1].Mv[idx].Col), + }; + } + + public static Mv ClampMvToUmvBorderSb(ref MacroBlockD xd, ref Mv srcMv, int bw, int bh, int ssX, int ssY) + { + // If the MV points so far into the UMV border that no visible pixels + // are used for reconstruction, the subpel part of the MV can be + // discarded and the MV limited to 16 pixels with equivalent results. + int spelLeft = (Constants.Vp9InterpExtend + bw) << SubpelBits; + int spelRight = spelLeft - SubpelShifts; + int spelTop = (Constants.Vp9InterpExtend + bh) << SubpelBits; + int spelBottom = spelTop - SubpelShifts; + Mv clampedMv = new() + { + Row = (short)(srcMv.Row * (1 << (1 - ssY))), + Col = (short)(srcMv.Col * (1 << (1 - ssX))), + }; + + Debug.Assert(ssX <= 1); + Debug.Assert(ssY <= 1); + + clampedMv.ClampMv( + xd.MbToLeftEdge * (1 << (1 - ssX)) - spelLeft, + xd.MbToRightEdge * (1 << (1 - ssX)) + spelRight, + xd.MbToTopEdge * (1 << (1 - ssY)) - spelTop, + xd.MbToBottomEdge * (1 << (1 - ssY)) + spelBottom); + + return clampedMv; + } + + public static Mv AverageSplitMvs(ref MacroBlockDPlane pd, ref ModeInfo mi, int refr, int block) + { + int ssIdx = ((pd.SubsamplingX > 0 ? 1 : 0) << 1) | (pd.SubsamplingY > 0 ? 1 : 0); + Mv res = new(); + switch (ssIdx) + { + case 0: + res = mi.Bmi[block].Mv[refr]; + break; + case 1: + res = MiMvPredQ2(ref mi, refr, block, block + 2); + break; + case 2: + res = MiMvPredQ2(ref mi, refr, block, block + 1); + break; + case 3: + res = MiMvPredQ4(ref mi, refr); + break; + default: + Debug.Assert(ssIdx <= 3 && ssIdx >= 0); + break; + } + return res; + } + + private static int ScaledBufferOffset(int xOffset, int yOffset, int stride, Ptr sf) + { + int x = !sf.IsNull ? sf.Value.ScaleValueX(xOffset) : xOffset; + int y = !sf.IsNull ? sf.Value.ScaleValueY(yOffset) : yOffset; + + return y * stride + x; + } + + private static void SetupPredPlanes( + ref Buf2D dst, + ArrayPtr src, + int stride, + int miRow, + int miCol, + Ptr scale, + int subsamplingX, + int subsamplingY) + { + int x = (Constants.MiSize * miCol) >> subsamplingX; + int y = (Constants.MiSize * miRow) >> subsamplingY; + dst.Buf = src.Slice(ScaledBufferOffset(x, y, stride, scale)); + dst.Stride = stride; + } + + public static void SetupDstPlanes( + ref Array3 planes, + ref Surface src, + int miRow, + int miCol) + { + Span> buffers = stackalloc ArrayPtr[Constants.MaxMbPlane]; + buffers[0] = src.YBuffer; + buffers[1] = src.UBuffer; + buffers[2] = src.VBuffer; + Span strides = stackalloc int[Constants.MaxMbPlane]; + strides[0] = src.Stride; + strides[1] = src.UvStride; + strides[2] = src.UvStride; + int i; + + for (i = 0; i < Constants.MaxMbPlane; ++i) + { + ref MacroBlockDPlane pd = ref planes[i]; + SetupPredPlanes(ref pd.Dst, buffers[i], strides[i], miRow, miCol, Ptr.Null, pd.SubsamplingX, pd.SubsamplingY); + } + } + + public static void SetupPrePlanes( + ref MacroBlockD xd, + int idx, + ref Surface src, + int miRow, + int miCol, + Ptr sf) + { + if (!src.YBuffer.IsNull && !src.UBuffer.IsNull && !src.VBuffer.IsNull) + { + Span> buffers = stackalloc ArrayPtr[Constants.MaxMbPlane]; + buffers[0] = src.YBuffer; + buffers[1] = src.UBuffer; + buffers[2] = src.VBuffer; + Span strides = stackalloc int[Constants.MaxMbPlane]; + strides[0] = src.Stride; + strides[1] = src.UvStride; + strides[2] = src.UvStride; + int i; + + for (i = 0; i < Constants.MaxMbPlane; ++i) + { + ref MacroBlockDPlane pd = ref xd.Plane[i]; + SetupPredPlanes(ref pd.Pre[idx], buffers[i], strides[i], miRow, miCol, sf, pd.SubsamplingX, pd.SubsamplingY); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/ReconIntra.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/ReconIntra.cs new file mode 100644 index 00000000..768c9acf --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/ReconIntra.cs @@ -0,0 +1,758 @@ +using Ryujinx.Graphics.Nvdec.Vp9.Common; +using Ryujinx.Graphics.Nvdec.Vp9.Types; +using System; +using static Ryujinx.Graphics.Nvdec.Vp9.Dsp.IntraPred; + +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal static class ReconIntra + { + public static readonly TxType[] IntraModeToTxTypeLookup = { + TxType.DctDct, // DC + TxType.AdstDct, // V + TxType.DctAdst, // H + TxType.DctDct, // D45 + TxType.AdstAdst, // D135 + TxType.AdstDct, // D117 + TxType.DctAdst, // D153 + TxType.DctAdst, // D207 + TxType.AdstDct, // D63 + TxType.AdstAdst, // TM + }; + + private const int NeedLeft = 1 << 1; + private const int NeedAbove = 1 << 2; + private const int NeedAboveRight = 1 << 3; + + private static ReadOnlySpan ExtendModes => new byte[] + { + NeedAbove | NeedLeft, // DC + NeedAbove, // V + NeedLeft, // H + NeedAboveRight, // D45 + NeedLeft | NeedAbove, // D135 + NeedLeft | NeedAbove, // D117 + NeedLeft | NeedAbove, // D153 + NeedLeft, // D207 + NeedAboveRight, // D63 + NeedLeft | NeedAbove, // TM + }; + + private unsafe delegate void IntraPredFn(byte* dst, int stride, byte* above, byte* left); + + private static readonly unsafe IntraPredFn[][] _pred = { + new IntraPredFn[] + { + null, + null, + null, + null, + }, + new IntraPredFn[] + { + VPredictor4x4, + VPredictor8x8, + VPredictor16x16, + VPredictor32x32, + }, + new IntraPredFn[] + { + HPredictor4x4, + HPredictor8x8, + HPredictor16x16, + HPredictor32x32, + }, + new IntraPredFn[] + { + D45Predictor4x4, + D45Predictor8x8, + D45Predictor16x16, + D45Predictor32x32, + }, + new IntraPredFn[] + { + D135Predictor4x4, + D135Predictor8x8, + D135Predictor16x16, + D135Predictor32x32, + }, + new IntraPredFn[] + { + D117Predictor4x4, + D117Predictor8x8, + D117Predictor16x16, + D117Predictor32x32, + }, + new IntraPredFn[] + { + D153Predictor4x4, + D153Predictor8x8, + D153Predictor16x16, + D153Predictor32x32, + }, + new IntraPredFn[] + { + D207Predictor4x4, + D207Predictor8x8, + D207Predictor16x16, + D207Predictor32x32, + }, + new IntraPredFn[] + { + D63Predictor4x4, + D63Predictor8x8, + D63Predictor16x16, + D63Predictor32x32, + }, + new IntraPredFn[] + { + TMPredictor4x4, + TMPredictor8x8, + TMPredictor16x16, + TMPredictor32x32, + }, + }; + + private static readonly unsafe IntraPredFn[][][] _dcPred = { + new[] + { + new IntraPredFn[] + { + Dc128Predictor4x4, + Dc128Predictor8x8, + Dc128Predictor16x16, + Dc128Predictor32x32, + }, + new IntraPredFn[] + { + DcTopPredictor4x4, + DcTopPredictor8x8, + DcTopPredictor16x16, + DcTopPredictor32x32, + }, + }, + new[] + { + new IntraPredFn[] + { + DcLeftPredictor4x4, + DcLeftPredictor8x8, + DcLeftPredictor16x16, + DcLeftPredictor32x32, + }, + new IntraPredFn[] + { + DcPredictor4x4, + DcPredictor8x8, + DcPredictor16x16, + DcPredictor32x32, + }, + }, + }; + + private unsafe delegate void IntraHighPredFn(ushort* dst, int stride, ushort* above, ushort* left, int bd); + + private static readonly unsafe IntraHighPredFn[][] _predHigh = { + new IntraHighPredFn[] + { + null, + null, + null, + null, + }, + new IntraHighPredFn[] + { + HighbdVPredictor4x4, + HighbdVPredictor8x8, + HighbdVPredictor16x16, + HighbdVPredictor32x32, + }, + new IntraHighPredFn[] + { + HighbdHPredictor4x4, + HighbdHPredictor8x8, + HighbdHPredictor16x16, + HighbdHPredictor32x32, + }, + new IntraHighPredFn[] + { + HighbdD45Predictor4x4, + HighbdD45Predictor8x8, + HighbdD45Predictor16x16, + HighbdD45Predictor32x32, + }, + new IntraHighPredFn[] + { + HighbdD135Predictor4x4, + HighbdD135Predictor8x8, + HighbdD135Predictor16x16, + HighbdD135Predictor32x32, + }, + new IntraHighPredFn[] + { + HighbdD117Predictor4x4, + HighbdD117Predictor8x8, + HighbdD117Predictor16x16, + HighbdD117Predictor32x32, + }, + new IntraHighPredFn[] + { + HighbdD153Predictor4x4, + HighbdD153Predictor8x8, + HighbdD153Predictor16x16, + HighbdD153Predictor32x32, + }, + new IntraHighPredFn[] + { + HighbdD207Predictor4x4, + HighbdD207Predictor8x8, + HighbdD207Predictor16x16, + HighbdD207Predictor32x32, + }, + new IntraHighPredFn[] + { + HighbdD63Predictor4x4, + HighbdD63Predictor8x8, + HighbdD63Predictor16x16, + HighbdD63Predictor32x32, + }, + new IntraHighPredFn[] + { + HighbdTMPredictor4x4, + HighbdTMPredictor8x8, + HighbdTMPredictor16x16, + HighbdTMPredictor32x32, + }, + }; + + private static readonly unsafe IntraHighPredFn[][][] _dcPredHigh = { + new[] + { + new IntraHighPredFn[] + { + HighbdDc128Predictor4x4, + HighbdDc128Predictor8x8, + HighbdDc128Predictor16x16, + HighbdDc128Predictor32x32, + }, + new IntraHighPredFn[] + { + HighbdDcTopPredictor4x4, + HighbdDcTopPredictor8x8, + HighbdDcTopPredictor16x16, + HighbdDcTopPredictor32x32, + }, + }, + new[] + { + new IntraHighPredFn[] + { + HighbdDcLeftPredictor4x4, + HighbdDcLeftPredictor8x8, + HighbdDcLeftPredictor16x16, + HighbdDcLeftPredictor32x32, + }, + new IntraHighPredFn[] + { + HighbdDcPredictor4x4, + HighbdDcPredictor8x8, + HighbdDcPredictor16x16, + HighbdDcPredictor32x32, + }, + }, + }; + + private static unsafe void BuildIntraPredictorsHigh( + ref MacroBlockD xd, + byte* ref8, + int refStride, + byte* dst8, + int dstStride, + PredictionMode mode, + TxSize txSize, + int upAvailable, + int leftAvailable, + int rightAvailable, + int x, + int y, + int plane) + { + int i; + ushort* dst = (ushort*)dst8; + ushort* refr = (ushort*)ref8; + ushort* leftCol = stackalloc ushort[32]; + ushort* aboveData = stackalloc ushort[64 + 16]; + ushort* aboveRow = aboveData + 16; + ushort* constAboveRow = aboveRow; + int bs = 4 << (int)txSize; + int frameWidth, frameHeight; + int x0, y0; + ref MacroBlockDPlane pd = ref xd.Plane[plane]; + int needLeft = ExtendModes[(int)mode] & NeedLeft; + int needAbove = ExtendModes[(int)mode] & NeedAbove; + int needAboveRight = ExtendModes[(int)mode] & NeedAboveRight; + int baseVal = 128 << (xd.Bd - 8); + // 127 127 127 .. 127 127 127 127 127 127 + // 129 A B .. Y Z + // 129 C D .. W X + // 129 E F .. U V + // 129 G H .. S T T T T T + // For 10 bit and 12 bit, 127 and 129 are replaced by base -1 and base + 1. + + // Get current frame pointer, width and height. + if (plane == 0) + { + frameWidth = xd.CurBuf.Width; + frameHeight = xd.CurBuf.Height; + } + else + { + frameWidth = xd.CurBuf.UvWidth; + frameHeight = xd.CurBuf.UvHeight; + } + + // Get block position in current frame. + x0 = (-xd.MbToLeftEdge >> (3 + pd.SubsamplingX)) + x; + y0 = (-xd.MbToTopEdge >> (3 + pd.SubsamplingY)) + y; + + // NEED_LEFT + if (needLeft != 0) + { + if (leftAvailable != 0) + { + if (xd.MbToBottomEdge < 0) + { + /* slower path if the block needs border extension */ + if (y0 + bs <= frameHeight) + { + for (i = 0; i < bs; ++i) + { + leftCol[i] = refr[i * refStride - 1]; + } + } + else + { + int extendBottom = frameHeight - y0; + for (i = 0; i < extendBottom; ++i) + { + leftCol[i] = refr[i * refStride - 1]; + } + + for (; i < bs; ++i) + { + leftCol[i] = refr[(extendBottom - 1) * refStride - 1]; + } + } + } + else + { + /* faster path if the block does not need extension */ + for (i = 0; i < bs; ++i) + { + leftCol[i] = refr[i * refStride - 1]; + } + } + } + else + { + MemoryUtil.Fill(leftCol, (ushort)(baseVal + 1), bs); + } + } + + // NEED_ABOVE + if (needAbove != 0) + { + if (upAvailable != 0) + { + ushort* aboveRef = refr - refStride; + if (xd.MbToRightEdge < 0) + { + /* slower path if the block needs border extension */ + if (x0 + bs <= frameWidth) + { + MemoryUtil.Copy(aboveRow, aboveRef, bs); + } + else if (x0 <= frameWidth) + { + int r = frameWidth - x0; + MemoryUtil.Copy(aboveRow, aboveRef, r); + MemoryUtil.Fill(aboveRow + r, aboveRow[r - 1], x0 + bs - frameWidth); + } + } + else + { + /* faster path if the block does not need extension */ + if (bs == 4 && rightAvailable != 0 && leftAvailable != 0) + { + constAboveRow = aboveRef; + } + else + { + MemoryUtil.Copy(aboveRow, aboveRef, bs); + } + } + aboveRow[-1] = leftAvailable != 0 ? aboveRef[-1] : (ushort)(baseVal + 1); + } + else + { + MemoryUtil.Fill(aboveRow, (ushort)(baseVal - 1), bs); + aboveRow[-1] = (ushort)(baseVal - 1); + } + } + + // NEED_ABOVERIGHT + if (needAboveRight != 0) + { + if (upAvailable != 0) + { + ushort* aboveRef = refr - refStride; + if (xd.MbToRightEdge < 0) + { + /* slower path if the block needs border extension */ + if (x0 + 2 * bs <= frameWidth) + { + if (rightAvailable != 0 && bs == 4) + { + MemoryUtil.Copy(aboveRow, aboveRef, 2 * bs); + } + else + { + MemoryUtil.Copy(aboveRow, aboveRef, bs); + MemoryUtil.Fill(aboveRow + bs, aboveRow[bs - 1], bs); + } + } + else if (x0 + bs <= frameWidth) + { + int r = frameWidth - x0; + if (rightAvailable != 0 && bs == 4) + { + MemoryUtil.Copy(aboveRow, aboveRef, r); + MemoryUtil.Fill(aboveRow + r, aboveRow[r - 1], x0 + 2 * bs - frameWidth); + } + else + { + MemoryUtil.Copy(aboveRow, aboveRef, bs); + MemoryUtil.Fill(aboveRow + bs, aboveRow[bs - 1], bs); + } + } + else if (x0 <= frameWidth) + { + int r = frameWidth - x0; + MemoryUtil.Copy(aboveRow, aboveRef, r); + MemoryUtil.Fill(aboveRow + r, aboveRow[r - 1], x0 + 2 * bs - frameWidth); + } + aboveRow[-1] = leftAvailable != 0 ? aboveRef[-1] : (ushort)(baseVal + 1); + } + else + { + /* faster path if the block does not need extension */ + if (bs == 4 && rightAvailable != 0 && leftAvailable != 0) + { + constAboveRow = aboveRef; + } + else + { + MemoryUtil.Copy(aboveRow, aboveRef, bs); + if (bs == 4 && rightAvailable != 0) + { + MemoryUtil.Copy(aboveRow + bs, aboveRef + bs, bs); + } + else + { + MemoryUtil.Fill(aboveRow + bs, aboveRow[bs - 1], bs); + } + + aboveRow[-1] = leftAvailable != 0 ? aboveRef[-1] : (ushort)(baseVal + 1); + } + } + } + else + { + MemoryUtil.Fill(aboveRow, (ushort)(baseVal - 1), bs * 2); + aboveRow[-1] = (ushort)(baseVal - 1); + } + } + + // Predict + if (mode == PredictionMode.DcPred) + { + _dcPredHigh[leftAvailable][upAvailable][(int)txSize](dst, dstStride, constAboveRow, leftCol, xd.Bd); + } + else + { + _predHigh[(int)mode][(int)txSize](dst, dstStride, constAboveRow, leftCol, xd.Bd); + } + } + + public static unsafe void BuildIntraPredictors( + ref MacroBlockD xd, + byte* refr, + int refStride, + byte* dst, + int dstStride, + PredictionMode mode, + TxSize txSize, + int upAvailable, + int leftAvailable, + int rightAvailable, + int x, + int y, + int plane) + { + int i; + byte* leftCol = stackalloc byte[32]; + byte* aboveData = stackalloc byte[64 + 16]; + byte* aboveRow = aboveData + 16; + byte* constAboveRow = aboveRow; + int bs = 4 << (int)txSize; + int frameWidth, frameHeight; + int x0, y0; + ref MacroBlockDPlane pd = ref xd.Plane[plane]; + + // 127 127 127 .. 127 127 127 127 127 127 + // 129 A B .. Y Z + // 129 C D .. W X + // 129 E F .. U V + // 129 G H .. S T T T T T + // .. + + // Get current frame pointer, width and height. + if (plane == 0) + { + frameWidth = xd.CurBuf.Width; + frameHeight = xd.CurBuf.Height; + } + else + { + frameWidth = xd.CurBuf.UvWidth; + frameHeight = xd.CurBuf.UvHeight; + } + + // Get block position in current frame. + x0 = (-xd.MbToLeftEdge >> (3 + pd.SubsamplingX)) + x; + y0 = (-xd.MbToTopEdge >> (3 + pd.SubsamplingY)) + y; + + // NEED_LEFT + if ((ExtendModes[(int)mode] & NeedLeft) != 0) + { + if (leftAvailable != 0) + { + if (xd.MbToBottomEdge < 0) + { + /* Slower path if the block needs border extension */ + if (y0 + bs <= frameHeight) + { + for (i = 0; i < bs; ++i) + { + leftCol[i] = refr[i * refStride - 1]; + } + } + else + { + int extendBottom = frameHeight - y0; + for (i = 0; i < extendBottom; ++i) + { + leftCol[i] = refr[i * refStride - 1]; + } + + for (; i < bs; ++i) + { + leftCol[i] = refr[(extendBottom - 1) * refStride - 1]; + } + } + } + else + { + /* Faster path if the block does not need extension */ + for (i = 0; i < bs; ++i) + { + leftCol[i] = refr[i * refStride - 1]; + } + } + } + else + { + MemoryUtil.Fill(leftCol, (byte)129, bs); + } + } + + // NEED_ABOVE + if ((ExtendModes[(int)mode] & NeedAbove) != 0) + { + if (upAvailable != 0) + { + byte* aboveRef = refr - refStride; + if (xd.MbToRightEdge < 0) + { + /* Slower path if the block needs border extension */ + if (x0 + bs <= frameWidth) + { + MemoryUtil.Copy(aboveRow, aboveRef, bs); + } + else if (x0 <= frameWidth) + { + int r = frameWidth - x0; + MemoryUtil.Copy(aboveRow, aboveRef, r); + MemoryUtil.Fill(aboveRow + r, aboveRow[r - 1], x0 + bs - frameWidth); + } + } + else + { + /* Faster path if the block does not need extension */ + if (bs == 4 && rightAvailable != 0 && leftAvailable != 0) + { + constAboveRow = aboveRef; + } + else + { + MemoryUtil.Copy(aboveRow, aboveRef, bs); + } + } + aboveRow[-1] = leftAvailable != 0 ? aboveRef[-1] : (byte)129; + } + else + { + MemoryUtil.Fill(aboveRow, (byte)127, bs); + aboveRow[-1] = 127; + } + } + + // NEED_ABOVERIGHT + if ((ExtendModes[(int)mode] & NeedAboveRight) != 0) + { + if (upAvailable != 0) + { + byte* aboveRef = refr - refStride; + if (xd.MbToRightEdge < 0) + { + /* Slower path if the block needs border extension */ + if (x0 + 2 * bs <= frameWidth) + { + if (rightAvailable != 0 && bs == 4) + { + MemoryUtil.Copy(aboveRow, aboveRef, 2 * bs); + } + else + { + MemoryUtil.Copy(aboveRow, aboveRef, bs); + MemoryUtil.Fill(aboveRow + bs, aboveRow[bs - 1], bs); + } + } + else if (x0 + bs <= frameWidth) + { + int r = frameWidth - x0; + if (rightAvailable != 0 && bs == 4) + { + MemoryUtil.Copy(aboveRow, aboveRef, r); + MemoryUtil.Fill(aboveRow + r, aboveRow[r - 1], x0 + 2 * bs - frameWidth); + } + else + { + MemoryUtil.Copy(aboveRow, aboveRef, bs); + MemoryUtil.Fill(aboveRow + bs, aboveRow[bs - 1], bs); + } + } + else if (x0 <= frameWidth) + { + int r = frameWidth - x0; + MemoryUtil.Copy(aboveRow, aboveRef, r); + MemoryUtil.Fill(aboveRow + r, aboveRow[r - 1], x0 + 2 * bs - frameWidth); + } + } + else + { + /* Faster path if the block does not need extension */ + if (bs == 4 && rightAvailable != 0 && leftAvailable != 0) + { + constAboveRow = aboveRef; + } + else + { + MemoryUtil.Copy(aboveRow, aboveRef, bs); + if (bs == 4 && rightAvailable != 0) + { + MemoryUtil.Copy(aboveRow + bs, aboveRef + bs, bs); + } + else + { + MemoryUtil.Fill(aboveRow + bs, aboveRow[bs - 1], bs); + } + } + } + aboveRow[-1] = leftAvailable != 0 ? aboveRef[-1] : (byte)129; + } + else + { + MemoryUtil.Fill(aboveRow, (byte)127, bs * 2); + aboveRow[-1] = 127; + } + } + + // Predict + if (mode == PredictionMode.DcPred) + { + _dcPred[leftAvailable][upAvailable][(int)txSize](dst, dstStride, constAboveRow, leftCol); + } + else + { + _pred[(int)mode][(int)txSize](dst, dstStride, constAboveRow, leftCol); + } + } + + public static unsafe void PredictIntraBlock( + ref MacroBlockD xd, + int bwlIn, + TxSize txSize, + PredictionMode mode, + byte* refr, + int refStride, + byte* dst, + int dstStride, + int aoff, + int loff, + int plane) + { + int bw = 1 << bwlIn; + int txw = 1 << (int)txSize; + int haveTop = loff != 0 || !xd.AboveMi.IsNull ? 1 : 0; + int haveLeft = aoff != 0 || !xd.LeftMi.IsNull ? 1 : 0; + int haveRight = (aoff + txw) < bw ? 1 : 0; + int x = aoff * 4; + int y = loff * 4; + + if (xd.CurBuf.HighBd) + { + BuildIntraPredictorsHigh( + ref xd, + refr, + refStride, + dst, + dstStride, + mode, + txSize, + haveTop, + haveLeft, + haveRight, + x, + y, + plane); + + return; + } + BuildIntraPredictors( + ref xd, + refr, + refStride, + dst, + dstStride, + mode, + txSize, + haveTop, + haveLeft, + haveRight, + x, + y, + plane); + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj b/src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj new file mode 100644 index 00000000..d1a6358c --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + true + + + + + + + + diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/TileBuffer.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/TileBuffer.cs new file mode 100644 index 00000000..b6adb95f --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/TileBuffer.cs @@ -0,0 +1,11 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal struct TileBuffer + { + public int Col; + public ArrayPtr Data; + public int Size; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/TileWorkerData.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/TileWorkerData.cs new file mode 100644 index 00000000..a9a7d566 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/TileWorkerData.cs @@ -0,0 +1,20 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Nvdec.Vp9.Dsp; +using Ryujinx.Graphics.Nvdec.Vp9.Types; +using Ryujinx.Graphics.Video; + +namespace Ryujinx.Graphics.Nvdec.Vp9 +{ + internal struct TileWorkerData + { + public ArrayPtr DataEnd; + public int BufStart; + public int BufEnd; + public Reader BitReader; + public Vp9BackwardUpdates Counts; + public MacroBlockD Xd; + /* dqcoeff are shared by all the planes. So planes must be decoded serially */ + public Array32> Dqcoeff; + public InternalErrorInfo ErrorInfo; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/BModeInfo.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/BModeInfo.cs new file mode 100644 index 00000000..4ff82820 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/BModeInfo.cs @@ -0,0 +1,10 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct BModeInfo + { + public PredictionMode Mode; + public Array2 Mv; // First, second inter predictor motion vectors + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/BlockSize.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/BlockSize.cs new file mode 100644 index 00000000..c7d4f7cc --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/BlockSize.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal enum BlockSize + { + Block4x4 = 0, + Block4x8 = 1, + Block8x4 = 2, + Block8x8 = 3, + Block8x16 = 4, + Block16x8 = 5, + Block16x16 = 6, + Block16x32 = 7, + Block32x16 = 8, + Block32x32 = 9, + Block32x64 = 10, + Block64x32 = 11, + Block64x64 = 12, + BlockSizes = 13, + BlockInvalid = BlockSizes, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Buf2D.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Buf2D.cs new file mode 100644 index 00000000..1313a2e1 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Buf2D.cs @@ -0,0 +1,10 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct Buf2D + { + public ArrayPtr Buf; + public int Stride; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/FrameType.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/FrameType.cs new file mode 100644 index 00000000..21f021c5 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/FrameType.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal enum FrameType + { + KeyFrame = 0, + InterFrame = 1, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilter.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilter.cs new file mode 100644 index 00000000..d2b29547 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilter.cs @@ -0,0 +1,27 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct LoopFilter + { + public int FilterLevel; + public int LastFiltLevel; + + public int SharpnessLevel; + public int LastSharpnessLevel; + + public bool ModeRefDeltaEnabled; + public bool ModeRefDeltaUpdate; + + // 0 = Intra, Last, GF, ARF + public Array4 RefDeltas; + public Array4 LastRefDeltas; + + // 0 = ZERO_MV, MV + public Array2 ModeDeltas; + public Array2 LastModeDeltas; + + public ArrayPtr Lfm; + public int LfmStride; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterInfoN.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterInfoN.cs new file mode 100644 index 00000000..be624307 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterInfoN.cs @@ -0,0 +1,10 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct LoopFilterInfoN + { + public Array64 Lfthr; + public Array8>> Lvl; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterMask.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterMask.cs new file mode 100644 index 00000000..879cbe15 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterMask.cs @@ -0,0 +1,24 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + // This structure holds bit masks for all 8x8 blocks in a 64x64 region. + // Each 1 bit represents a position in which we want to apply the loop filter. + // Left_ entries refer to whether we apply a filter on the border to the + // left of the block. Above_ entries refer to whether or not to apply a + // filter on the above border. Int_ entries refer to whether or not to + // apply borders on the 4x4 edges within the 8x8 block that each bit + // represents. + // Since each transform is accompanied by a potentially different type of + // loop filter there is a different entry in the array for each transform size. + internal struct LoopFilterMask + { + public Array4 LeftY; + public Array4 AboveY; + public ulong Int4x4Y; + public Array4 LeftUv; + public Array4 AboveUv; + public ushort Int4x4Uv; + public Array64 LflY; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterThresh.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterThresh.cs new file mode 100644 index 00000000..54621321 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterThresh.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + // Need to align this structure so when it is declared and + // passed it can be loaded into vector registers. + internal struct LoopFilterThresh + { +#pragma warning disable CS0649 // Field is never assigned to + public Array16 Mblim; + public Array16 Lim; + public Array16 HevThr; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MacroBlockD.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MacroBlockD.cs new file mode 100644 index 00000000..6305664d --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MacroBlockD.cs @@ -0,0 +1,182 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Video; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct MacroBlockD + { + public Array3 Plane; + public byte BmodeBlocksWl; + public byte BmodeBlocksHl; + + public Ptr Counts; + public TileInfo Tile; + + public int MiStride; + + // Grid of 8x8 cells is placed over the block. + // If some of them belong to the same mbtree-block + // they will just have same mi[i][j] value + public ArrayPtr> Mi; + public Ptr LeftMi; + public Ptr AboveMi; + + public uint MaxBlocksWide; + public uint MaxBlocksHigh; + + public ArrayPtr> PartitionProbs; + + /* Distance of MB away from frame edges */ + public int MbToLeftEdge; + public int MbToRightEdge; + public int MbToTopEdge; + public int MbToBottomEdge; + + public Ptr Fc; + + /* pointers to reference frames */ + public Array2> BlockRefs; + + /* pointer to current frame */ + public Surface CurBuf; + + public Array3> AboveContext; + public Array3> LeftContext; + + public ArrayPtr AboveSegContext; + public Array8 LeftSegContext; + + /* Bit depth: 8, 10, 12 */ + public int Bd; + + public bool Lossless; + public bool Corrupted; + + public Ptr ErrorInfo; + + public readonly int GetPredContextSegId() + { + sbyte aboveSip = !AboveMi.IsNull ? AboveMi.Value.SegIdPredicted : (sbyte)0; + sbyte leftSip = !LeftMi.IsNull ? LeftMi.Value.SegIdPredicted : (sbyte)0; + + return aboveSip + leftSip; + } + + public readonly int GetSkipContext() + { + int aboveSkip = !AboveMi.IsNull ? AboveMi.Value.Skip : 0; + int leftSkip = !LeftMi.IsNull ? LeftMi.Value.Skip : 0; + + return aboveSkip + leftSkip; + } + + public readonly int GetPredContextSwitchableInterp() + { + // Note: + // The mode info data structure has a one element border above and to the + // left of the entries corresponding to real macroblocks. + // The prediction flags in these dummy entries are initialized to 0. + int leftType = !LeftMi.IsNull ? LeftMi.Value.InterpFilter : Constants.SwitchableFilters; + int aboveType = !AboveMi.IsNull ? AboveMi.Value.InterpFilter : Constants.SwitchableFilters; + + if (leftType == aboveType) + { + return leftType; + } + else if (leftType == Constants.SwitchableFilters) + { + return aboveType; + } + else if (aboveType == Constants.SwitchableFilters) + { + return leftType; + } + else + { + return Constants.SwitchableFilters; + } + } + + // The mode info data structure has a one element border above and to the + // left of the entries corresponding to real macroblocks. + // The prediction flags in these dummy entries are initialized to 0. + // 0 - inter/inter, inter/--, --/inter, --/-- + // 1 - intra/inter, inter/intra + // 2 - intra/--, --/intra + // 3 - intra/intra + public readonly int GetIntraInterContext() + { + if (!AboveMi.IsNull && !LeftMi.IsNull) + { // Both edges available + bool aboveIntra = !AboveMi.Value.IsInterBlock(); + bool leftIntra = !LeftMi.Value.IsInterBlock(); + + return leftIntra && aboveIntra ? 3 : (leftIntra || aboveIntra ? 1 : 0); + } + + if (!AboveMi.IsNull || !LeftMi.IsNull) + { // One edge available + return 2 * (!(!AboveMi.IsNull ? AboveMi.Value : LeftMi.Value).IsInterBlock() ? 1 : 0); + } + return 0; + } + + // Returns a context number for the given MB prediction signal + // The mode info data structure has a one element border above and to the + // left of the entries corresponding to real blocks. + // The prediction flags in these dummy entries are initialized to 0. + public readonly int GetTxSizeContext() + { + int maxTxSize = (int)Luts.MaxTxSizeLookup[(int)Mi[0].Value.SbType]; + int aboveCtx = (!AboveMi.IsNull && AboveMi.Value.Skip == 0) ? (int)AboveMi.Value.TxSize : maxTxSize; + int leftCtx = (!LeftMi.IsNull && LeftMi.Value.Skip == 0) ? (int)LeftMi.Value.TxSize : maxTxSize; + if (LeftMi.IsNull) + { + leftCtx = aboveCtx; + } + + if (AboveMi.IsNull) + { + aboveCtx = leftCtx; + } + + return (aboveCtx + leftCtx) > maxTxSize ? 1 : 0; + } + + public void SetupBlockPlanes(int ssX, int ssY) + { + int i; + + for (i = 0; i < Constants.MaxMbPlane; i++) + { + Plane[i].SubsamplingX = i != 0 ? ssX : 0; + Plane[i].SubsamplingY = i != 0 ? ssY : 0; + } + } + + public void SetSkipContext(int miRow, int miCol) + { + int aboveIdx = miCol * 2; + int leftIdx = (miRow * 2) & 15; + int i; + for (i = 0; i < Constants.MaxMbPlane; ++i) + { + ref MacroBlockDPlane pd = ref Plane[i]; + pd.AboveContext = AboveContext[i].Slice(aboveIdx >> pd.SubsamplingX); + pd.LeftContext = new ArrayPtr(ref LeftContext[i][leftIdx >> pd.SubsamplingY], 16 - (leftIdx >> pd.SubsamplingY)); + } + } + + internal void SetMiRowCol(ref TileInfo tile, int miRow, int bh, int miCol, int bw, int miRows, int miCols) + { + MbToTopEdge = -((miRow * Constants.MiSize) * 8); + MbToBottomEdge = ((miRows - bh - miRow) * Constants.MiSize) * 8; + MbToLeftEdge = -((miCol * Constants.MiSize) * 8); + MbToRightEdge = ((miCols - bw - miCol) * Constants.MiSize) * 8; + + // Are edges available for intra prediction? + AboveMi = (miRow != 0) ? Mi[-MiStride] : Ptr.Null; + LeftMi = (miCol > tile.MiColStart) ? Mi[-1] : Ptr.Null; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MacroBlockDPlane.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MacroBlockDPlane.cs new file mode 100644 index 00000000..df21a8d4 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MacroBlockDPlane.cs @@ -0,0 +1,21 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct MacroBlockDPlane + { + public ArrayPtr DqCoeff; + public int SubsamplingX; + public int SubsamplingY; + public Buf2D Dst; + public Array2 Pre; + public ArrayPtr AboveContext; + public ArrayPtr LeftContext; + public Array8> SegDequant; + + // Number of 4x4s in current block + public ushort N4W, N4H; + // Log2 of N4W, N4H + public byte N4Wl, N4Hl; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/ModeInfo.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/ModeInfo.cs new file mode 100644 index 00000000..e97ed424 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/ModeInfo.cs @@ -0,0 +1,66 @@ +using Ryujinx.Common.Memory; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct ModeInfo + { + // Common for both Inter and Intra blocks + public BlockSize SbType; + public PredictionMode Mode; + public TxSize TxSize; + public sbyte Skip; + public sbyte SegmentId; + public sbyte SegIdPredicted; // Valid only when TemporalUpdate is enabled + + // Only for Intra blocks + public PredictionMode UvMode; + + // Only for Inter blocks + public byte InterpFilter; + + // if ref_frame[idx] is equal to AltRefFrame then + // MacroBlockD.BlockRef[idx] is an altref + public Array2 RefFrame; + + public Array2 Mv; + + public Array4 Bmi; + + public PredictionMode GetYMode(int block) + { + return SbType < BlockSize.Block8x8 ? Bmi[block].Mode : Mode; + } + + public readonly TxSize GetUvTxSize(ref MacroBlockDPlane pd) + { + Debug.Assert(SbType < BlockSize.Block8x8 || + Luts.SsSizeLookup[(int)SbType][pd.SubsamplingX][pd.SubsamplingY] != BlockSize.BlockInvalid); + + return Luts.UvTxsizeLookup[(int)SbType][(int)TxSize][pd.SubsamplingX][pd.SubsamplingY]; + } + + public bool IsInterBlock() + { + return RefFrame[0] > Constants.IntraFrame; + } + + public bool HasSecondRef() + { + return RefFrame[1] > Constants.IntraFrame; + } + + private static readonly int[][] _idxNColumnToSubblock = { + new[] { 1, 2 }, new[] { 1, 3 }, new[] { 3, 2 }, new[] { 3, 3 }, + }; + + // This function returns either the appropriate sub block or block's mv + // on whether the block_size < 8x8 and we have check_sub_blocks set. + public Mv GetSubBlockMv(int whichMv, int searchCol, int blockIdx) + { + return blockIdx >= 0 && SbType < BlockSize.Block8x8 + ? Bmi[_idxNColumnToSubblock[blockIdx][searchCol == 0 ? 1 : 0]].Mv[whichMv] + : Mv[whichMv]; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MotionVectorContext.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MotionVectorContext.cs new file mode 100644 index 00000000..bad5af28 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MotionVectorContext.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal enum MotionVectorContext + { + BothZero = 0, + ZeroPlusPredicted = 1, + BothPredicted = 2, + NewPlusNonIntra = 3, + BothNew = 4, + IntraPlusNonIntra = 5, + BothIntra = 6, + InvalidCase = 9, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Mv.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Mv.cs new file mode 100644 index 00000000..9ccb5150 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Mv.cs @@ -0,0 +1,187 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Video; +using System; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct Mv + { + public short Row; + public short Col; + + private static ReadOnlySpan LogInBase2 => new byte[] + { + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, + }; + + public readonly bool UseMvHp() + { + const int KMvRefThresh = 64; // Threshold for use of high-precision 1/8 mv + return Math.Abs(Row) < KMvRefThresh && Math.Abs(Col) < KMvRefThresh; + } + + public static bool MvJointVertical(MvJointType type) + { + return type == MvJointType.MvJointHzvnz || type == MvJointType.MvJointHnzvnz; + } + + public static bool MvJointHorizontal(MvJointType type) + { + return type == MvJointType.MvJointHnzvz || type == MvJointType.MvJointHnzvnz; + } + + private static int MvClassBase(MvClassType c) + { + return c != 0 ? Constants.Class0Size << ((int)c + 2) : 0; + } + + private static MvClassType GetMvClass(int z, Ptr offset) + { + MvClassType c = (z >= Constants.Class0Size * 4096) ? MvClassType.MvClass10 : (MvClassType)LogInBase2[z >> 3]; + if (!offset.IsNull) + { + offset.Value = z - MvClassBase(c); + } + + return c; + } + + private static void IncMvComponent(int v, ref Vp9BackwardUpdates counts, int comp, int incr, int usehp) + { + int s, z, c, o = 0, d, e, f; + Debug.Assert(v != 0); /* Should not be zero */ + s = v < 0 ? 1 : 0; + counts.Sign[comp][s] += (uint)incr; + z = (s != 0 ? -v : v) - 1; /* Magnitude - 1 */ + + c = (int)GetMvClass(z, new Ptr(ref o)); + counts.Classes[comp][c] += (uint)incr; + + d = (o >> 3); /* Int mv data */ + f = (o >> 1) & 3; /* Fractional pel mv data */ + e = (o & 1); /* High precision mv data */ + + if (c == (int)MvClassType.MvClass0) + { + counts.Class0[comp][d] += (uint)incr; + counts.Class0Fp[comp][d][f] += (uint)incr; + counts.Class0Hp[comp][e] += (uint)(usehp * incr); + } + else + { + int i; + int b = c + Constants.Class0Bits - 1; // Number of bits + for (i = 0; i < b; ++i) + { + counts.Bits[comp][i][((d >> i) & 1)] += (uint)incr; + } + + counts.Fp[comp][f] += (uint)incr; + counts.Hp[comp][e] += (uint)(usehp * incr); + } + } + + private readonly MvJointType GetMvJoint() + { + if (Row == 0) + { + return Col == 0 ? MvJointType.MvJointZero : MvJointType.MvJointHnzvz; + } + + return Col == 0 ? MvJointType.MvJointHzvnz : MvJointType.MvJointHnzvnz; + } + + internal readonly void IncMv(Ptr counts) + { + if (!counts.IsNull) + { + MvJointType j = GetMvJoint(); + ++counts.Value.Joints[(int)j]; + + if (MvJointVertical(j)) + { + IncMvComponent(Row, ref counts.Value, 0, 1, 1); + } + + if (MvJointHorizontal(j)) + { + IncMvComponent(Col, ref counts.Value, 1, 1, 1); + } + } + } + + public void ClampMv(int minCol, int maxCol, int minRow, int maxRow) + { + Col = (short)Math.Clamp(Col, minCol, maxCol); + Row = (short)Math.Clamp(Row, minRow, maxRow); + } + + private const int MvBorder = (16 << 3); // Allow 16 pels in 1/8th pel units + + public void ClampMvRef(ref MacroBlockD xd) + { + ClampMv( + xd.MbToLeftEdge - MvBorder, + xd.MbToRightEdge + MvBorder, + xd.MbToTopEdge - MvBorder, + xd.MbToBottomEdge + MvBorder); + } + + public void LowerMvPrecision(bool allowHP) + { + bool useHP = allowHP && UseMvHp(); + if (!useHP) + { + if ((Row & 1) != 0) + { + Row += (short)(Row > 0 ? -1 : 1); + } + + if ((Col & 1) != 0) + { + Col += (short)(Col > 0 ? -1 : 1); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Mv32.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Mv32.cs new file mode 100644 index 00000000..5231ca6a --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Mv32.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct Mv32 + { + public int Row; + public int Col; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvClassType.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvClassType.cs new file mode 100644 index 00000000..188eee0b --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvClassType.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal enum MvClassType + { + MvClass0 = 0, /* (0, 2] integer pel */ + MvClass1 = 1, /* (2, 4] integer pel */ + MvClass2 = 2, /* (4, 8] integer pel */ + MvClass3 = 3, /* (8, 16] integer pel */ + MvClass4 = 4, /* (16, 32] integer pel */ + MvClass5 = 5, /* (32, 64] integer pel */ + MvClass6 = 6, /* (64, 128] integer pel */ + MvClass7 = 7, /* (128, 256] integer pel */ + MvClass8 = 8, /* (256, 512] integer pel */ + MvClass9 = 9, /* (512, 1024] integer pel */ + MvClass10 = 10, /* (1024,2048] integer pel */ + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvJointType.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvJointType.cs new file mode 100644 index 00000000..8cccf90c --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvJointType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal enum MvJointType + { + MvJointZero = 0, /* Zero vector */ + MvJointHnzvz = 1, /* Vert zero, hor nonzero */ + MvJointHzvnz = 2, /* Hor zero, vert nonzero */ + MvJointHnzvnz = 3, /* Both components nonzero */ + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvRef.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvRef.cs new file mode 100644 index 00000000..1d32a092 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvRef.cs @@ -0,0 +1,10 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct MvRef + { + public Array2 Mv; + public Array2 RefFrame; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/PartitionType.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/PartitionType.cs new file mode 100644 index 00000000..c47557ab --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/PartitionType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal enum PartitionType + { + PartitionNone, + PartitionHorz, + PartitionVert, + PartitionSplit, + PartitionTypes, + PartitionInvalid = PartitionTypes, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/PlaneType.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/PlaneType.cs new file mode 100644 index 00000000..8b01cf3a --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/PlaneType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal enum PlaneType + { + Y = 0, + Uv = 1, + PlaneTypes, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Position.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Position.cs new file mode 100644 index 00000000..cee2fbe8 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Position.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct Position + { + public int Row; + public int Col; + + public Position(int row, int col) + { + Row = row; + Col = col; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/PredictionMode.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/PredictionMode.cs new file mode 100644 index 00000000..2d81b547 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/PredictionMode.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal enum PredictionMode + { + DcPred = 0, // Average of above and left pixels + VPred = 1, // Vertical + HPred = 2, // Horizontal + D45Pred = 3, // Directional 45 deg = round(arctan(1 / 1) * 180 / pi) + D135Pred = 4, // Directional 135 deg = 180 - 45 + D117Pred = 5, // Directional 117 deg = 180 - 63 + D153Pred = 6, // Directional 153 deg = 180 - 27 + D207Pred = 7, // Directional 207 deg = 180 + 27 + D63Pred = 8, // Directional 63 deg = round(arctan(2 / 1) * 180 / pi) + TmPred = 9, // True-motion + NearestMv = 10, + NearMv = 11, + ZeroMv = 12, + NewMv = 13, + MbModeCount = 14, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/RefBuffer.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/RefBuffer.cs new file mode 100644 index 00000000..0fbb8451 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/RefBuffer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct RefBuffer + { + public Surface Buf; + public ScaleFactors Sf; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/ReferenceMode.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/ReferenceMode.cs new file mode 100644 index 00000000..aff3cbb0 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/ReferenceMode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal enum ReferenceMode + { + SingleReference = 0, + CompoundReference = 1, + ReferenceModeSelect = 2, + ReferenceModes = 3, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/ScaleFactors.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/ScaleFactors.cs new file mode 100644 index 00000000..8e2ec424 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/ScaleFactors.cs @@ -0,0 +1,445 @@ +using Ryujinx.Common.Memory; +using System.Runtime.CompilerServices; +using static Ryujinx.Graphics.Nvdec.Vp9.Dsp.Convolve; +using static Ryujinx.Graphics.Nvdec.Vp9.Dsp.Filter; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct ScaleFactors + { + private const int RefScaleShift = 14; + private const int RefNoScale = (1 << RefScaleShift); + private const int RefInvalidScale = -1; + + private unsafe delegate void ConvolveFn( + byte* src, + int srcStride, + byte* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h); + + private unsafe delegate void HighbdConvolveFn( + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + Array8[] filter, + int x0Q4, + int xStepQ4, + int y0Q4, + int yStepQ4, + int w, + int h, + int bd); + + private static readonly unsafe ConvolveFn[][][] _predictX16Y16 = { + new[] + { + new ConvolveFn[] + { + ConvolveCopy, + ConvolveAvg, + }, + new ConvolveFn[] + { + Convolve8Vert, + Convolve8AvgVert, + }, + }, + new[] + { + new ConvolveFn[] + { + Convolve8Horiz, + Convolve8AvgHoriz, + }, + new ConvolveFn[] + { + Convolve8, + Convolve8Avg, + }, + }, + }; + + private static readonly unsafe ConvolveFn[][][] _predictX16 = { + new[] + { + new ConvolveFn[] + { + ScaledVert, + ScaledAvgVert, + }, + new ConvolveFn[] + { + ScaledVert, + ScaledAvgVert, + }, + }, + new[] + { + new ConvolveFn[] + { + Scaled2D, + ScaledAvg2D, + }, + new ConvolveFn[] + { + Scaled2D, + ScaledAvg2D, + }, + }, + }; + + private static readonly unsafe ConvolveFn[][][] _predictY16 = { + new[] + { + new ConvolveFn[] + { + ScaledHoriz, + ScaledAvgHoriz, + }, + new ConvolveFn[] + { + Scaled2D, + ScaledAvg2D, + }, + }, + new[] + { + new ConvolveFn[] + { + ScaledHoriz, + ScaledAvgHoriz, + }, + new ConvolveFn[] + { + Scaled2D, + ScaledAvg2D, + }, + }, + }; + + private static readonly unsafe ConvolveFn[][][] _predict = { + new[] + { + new ConvolveFn[] + { + Scaled2D, + ScaledAvg2D, + }, + new ConvolveFn[] + { + Scaled2D, + ScaledAvg2D, + }, + }, + new[] + { + new ConvolveFn[] + { + Scaled2D, + ScaledAvg2D, + }, + new ConvolveFn[] + { + Scaled2D, + ScaledAvg2D, + }, + }, + }; + + private static readonly unsafe HighbdConvolveFn[][][] _highbdPredictX16Y16 = { + new[] + { + new HighbdConvolveFn[] + { + HighbdConvolveCopy, + HighbdConvolveAvg, + }, + new HighbdConvolveFn[] + { + HighbdConvolve8Vert, + HighbdConvolve8AvgVert, + }, + }, + new[] + { + new HighbdConvolveFn[] + { + HighbdConvolve8Horiz, + HighbdConvolve8AvgHoriz, + }, + new HighbdConvolveFn[] + { + HighbdConvolve8, + HighbdConvolve8Avg, + }, + }, + }; + + private static readonly unsafe HighbdConvolveFn[][][] _highbdPredictX16 = { + new[] + { + new HighbdConvolveFn[] + { + HighbdConvolve8Vert, + HighbdConvolve8AvgVert, + }, + new HighbdConvolveFn[] + { + HighbdConvolve8Vert, + HighbdConvolve8AvgVert, + }, + }, + new[] + { + new HighbdConvolveFn[] + { + HighbdConvolve8, + HighbdConvolve8Avg, + }, + new HighbdConvolveFn[] + { + HighbdConvolve8, + HighbdConvolve8Avg, + }, + }, + }; + + private static readonly unsafe HighbdConvolveFn[][][] _highbdPredictY16 = { + new[] + { + new HighbdConvolveFn[] + { + HighbdConvolve8Horiz, + HighbdConvolve8AvgHoriz, + }, + new HighbdConvolveFn[] + { + HighbdConvolve8, + HighbdConvolve8Avg, + }, + }, + new[] + { + new HighbdConvolveFn[] + { + HighbdConvolve8Horiz, + HighbdConvolve8AvgHoriz, + }, + new HighbdConvolveFn[] + { + HighbdConvolve8, + HighbdConvolve8Avg, + }, + }, + }; + + private static readonly unsafe HighbdConvolveFn[][][] _highbdPredict = { + new[] + { + new HighbdConvolveFn[] + { + HighbdConvolve8, + HighbdConvolve8Avg, + }, + new HighbdConvolveFn[] + { + HighbdConvolve8, + HighbdConvolve8Avg, + }, + }, + new[] + { + new HighbdConvolveFn[] + { + HighbdConvolve8, + HighbdConvolve8Avg, + }, + new HighbdConvolveFn[] + { + HighbdConvolve8, + HighbdConvolve8Avg, + }, + }, + }; + + public int XScaleFP; // Horizontal fixed point scale factor + public int YScaleFP; // Vertical fixed point scale factor + public int XStepQ4; + public int YStepQ4; + + public readonly int ScaleValueX(int val) + { + return IsScaled() ? ScaledX(val) : val; + } + + public readonly int ScaleValueY(int val) + { + return IsScaled() ? ScaledY(val) : val; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly unsafe void InterPredict( + int horiz, + int vert, + int avg, + byte* src, + int srcStride, + byte* dst, + int dstStride, + int subpelX, + int subpelY, + int w, + int h, + Array8[] kernel, + int xs, + int ys) + { + if (XStepQ4 == 16) + { + if (YStepQ4 == 16) + { + // No scaling in either direction. + _predictX16Y16[horiz][vert][avg](src, srcStride, dst, dstStride, kernel, subpelX, xs, subpelY, ys, w, h); + } + else + { + // No scaling in x direction. Must always scale in the y direction. + _predictX16[horiz][vert][avg](src, srcStride, dst, dstStride, kernel, subpelX, xs, subpelY, ys, w, h); + } + } + else + { + if (YStepQ4 == 16) + { + // No scaling in the y direction. Must always scale in the x direction. + _predictY16[horiz][vert][avg](src, srcStride, dst, dstStride, kernel, subpelX, xs, subpelY, ys, w, h); + } + else + { + // Must always scale in both directions. + _predict[horiz][vert][avg](src, srcStride, dst, dstStride, kernel, subpelX, xs, subpelY, ys, w, h); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly unsafe void HighbdInterPredict( + int horiz, + int vert, + int avg, + ushort* src, + int srcStride, + ushort* dst, + int dstStride, + int subpelX, + int subpelY, + int w, + int h, + Array8[] kernel, + int xs, + int ys, + int bd) + { + if (XStepQ4 == 16) + { + if (YStepQ4 == 16) + { + // No scaling in either direction. + _highbdPredictX16Y16[horiz][vert][avg](src, srcStride, dst, dstStride, kernel, subpelX, xs, subpelY, ys, w, h, bd); + } + else + { + // No scaling in x direction. Must always scale in the y direction. + _highbdPredictX16[horiz][vert][avg](src, srcStride, dst, dstStride, kernel, subpelX, xs, subpelY, ys, w, h, bd); + } + } + else + { + if (YStepQ4 == 16) + { + // No scaling in the y direction. Must always scale in the x direction. + _highbdPredictY16[horiz][vert][avg](src, srcStride, dst, dstStride, kernel, subpelX, xs, subpelY, ys, w, h, bd); + } + else + { + // Must always scale in both directions. + _highbdPredict[horiz][vert][avg](src, srcStride, dst, dstStride, kernel, subpelX, xs, subpelY, ys, w, h, bd); + } + } + } + + private readonly int ScaledX(int val) + { + return (int)((long)val * XScaleFP >> RefScaleShift); + } + + private readonly int ScaledY(int val) + { + return (int)((long)val * YScaleFP >> RefScaleShift); + } + + private static int GetFixedPointScaleFactor(int otherSize, int thisSize) + { + // Calculate scaling factor once for each reference frame + // and use fixed point scaling factors in decoding and encoding routines. + // Hardware implementations can calculate scale factor in device driver + // and use multiplication and shifting on hardware instead of division. + return (otherSize << RefScaleShift) / thisSize; + } + + public Mv32 ScaleMv(ref Mv mv, int x, int y) + { + int xOffQ4 = ScaledX(x << SubpelBits) & SubpelMask; + int yOffQ4 = ScaledY(y << SubpelBits) & SubpelMask; + Mv32 res = new() + { + Row = ScaledY(mv.Row) + yOffQ4, + Col = ScaledX(mv.Col) + xOffQ4, + }; + + return res; + } + + public readonly bool IsValidScale() + { + return XScaleFP != RefInvalidScale && YScaleFP != RefInvalidScale; + } + + public readonly bool IsScaled() + { + return IsValidScale() && (XScaleFP != RefNoScale || YScaleFP != RefNoScale); + } + + public static bool ValidRefFrameSize(int refWidth, int refHeight, int thisWidth, int thisHeight) + { + return 2 * thisWidth >= refWidth && + 2 * thisHeight >= refHeight && + thisWidth <= 16 * refWidth && + thisHeight <= 16 * refHeight; + } + + public void SetupScaleFactorsForFrame(int otherW, int otherH, int thisW, int thisH) + { + if (!ValidRefFrameSize(otherW, otherH, thisW, thisH)) + { + XScaleFP = RefInvalidScale; + YScaleFP = RefInvalidScale; + + return; + } + + XScaleFP = GetFixedPointScaleFactor(otherW, thisW); + YScaleFP = GetFixedPointScaleFactor(otherH, thisH); + XStepQ4 = ScaledX(16); + YStepQ4 = ScaledY(16); + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/SegLvlFeatures.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/SegLvlFeatures.cs new file mode 100644 index 00000000..eb47abdf --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/SegLvlFeatures.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal enum SegLvlFeatures + { + SegLvlAltQ = 0, // Use alternate Quantizer .... + SegLvlAltLf = 1, // Use alternate loop filter value... + SegLvlRefFrame = 2, // Optional Segment reference frame + SegLvlSkip = 3, // Optional Segment (0,0) + skip mode + SegLvlMax = 4, // Number of features supported + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Segmentation.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Segmentation.cs new file mode 100644 index 00000000..f2415fc0 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Segmentation.cs @@ -0,0 +1,71 @@ +using Ryujinx.Common.Memory; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct Segmentation + { + private static readonly int[] _segFeatureDataSigned = { 1, 1, 0, 0 }; + private static readonly int[] _segFeatureDataMax = { QuantCommon.MaxQ, Vp9.LoopFilter.MaxLoopFilter, 3, 0 }; + + public bool Enabled; + public bool UpdateMap; + public byte UpdateData; + public byte AbsDelta; + public bool TemporalUpdate; + + public Array8> FeatureData; + public Array8 FeatureMask; + public int AqAvOffset; + + public static byte GetPredProbSegId(ref Array3 segPredProbs, ref MacroBlockD xd) + { + return segPredProbs[xd.GetPredContextSegId()]; + } + + public void ClearAllSegFeatures() + { + MemoryMarshal.CreateSpan(ref FeatureData[0][0], 8 * 4).Clear(); + MemoryMarshal.CreateSpan(ref FeatureMask[0], 8).Clear(); + AqAvOffset = 0; + } + + internal void EnableSegFeature(int segmentId, SegLvlFeatures featureId) + { + FeatureMask[segmentId] |= 1u << (int)featureId; + } + + internal static int FeatureDataMax(SegLvlFeatures featureId) + { + return _segFeatureDataMax[(int)featureId]; + } + + internal static int IsSegFeatureSigned(SegLvlFeatures featureId) + { + return _segFeatureDataSigned[(int)featureId]; + } + + internal void SetSegData(int segmentId, SegLvlFeatures featureId, int segData) + { + Debug.Assert(segData <= _segFeatureDataMax[(int)featureId]); + if (segData < 0) + { + Debug.Assert(_segFeatureDataSigned[(int)featureId] != 0); + Debug.Assert(-segData <= _segFeatureDataMax[(int)featureId]); + } + + FeatureData[segmentId][(int)featureId] = (short)segData; + } + + internal int IsSegFeatureActive(int segmentId, SegLvlFeatures featureId) + { + return Enabled && (FeatureMask[segmentId] & (1 << (int)featureId)) != 0 ? 1 : 0; + } + + internal short GetSegData(int segmentId, SegLvlFeatures featureId) + { + return FeatureData[segmentId][(int)featureId]; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Surface.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Surface.cs new file mode 100644 index 00000000..372b1d2b --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Surface.cs @@ -0,0 +1,84 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Video; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct Surface : ISurface + { + public ArrayPtr YBuffer; + public ArrayPtr UBuffer; + public ArrayPtr VBuffer; + + public readonly unsafe Plane YPlane => new((IntPtr)YBuffer.ToPointer(), YBuffer.Length); + public readonly unsafe Plane UPlane => new((IntPtr)UBuffer.ToPointer(), UBuffer.Length); + public readonly unsafe Plane VPlane => new((IntPtr)VBuffer.ToPointer(), VBuffer.Length); + + public readonly FrameField Field => FrameField.Progressive; + + public int Width { get; } + public int Height { get; } + public int AlignedWidth { get; } + public int AlignedHeight { get; } + public int Stride { get; } + public int UvWidth { get; } + public int UvHeight { get; } + public int UvAlignedWidth { get; } + public int UvAlignedHeight { get; } + public int UvStride { get; } + + public bool HighBd { get; } + + private readonly IntPtr _pointer; + + public Surface(int width, int height) + { + HighBd = false; + + const int Border = 32; + const int SsX = 1; + const int SsY = 1; + + int alignedWidth = (width + 7) & ~7; + int alignedHeight = (height + 7) & ~7; + int yStride = ((alignedWidth + 2 * Border) + 31) & ~31; + int yplaneSize = (alignedHeight + 2 * Border) * yStride; + int uvWidth = alignedWidth >> SsX; + int uvHeight = alignedHeight >> SsY; + int uvStride = yStride >> SsX; + int uvBorderW = Border >> SsX; + int uvBorderH = Border >> SsY; + int uvplaneSize = (uvHeight + 2 * uvBorderH) * uvStride; + + int frameSize = (HighBd ? 2 : 1) * (yplaneSize + 2 * uvplaneSize); + + IntPtr pointer = Marshal.AllocHGlobal(frameSize); + _pointer = pointer; + Width = width; + Height = height; + AlignedWidth = alignedWidth; + AlignedHeight = alignedHeight; + Stride = yStride; + UvWidth = (width + SsX) >> SsX; + UvHeight = (height + SsY) >> SsY; + UvAlignedWidth = uvWidth; + UvAlignedHeight = uvHeight; + UvStride = uvStride; + + ArrayPtr NewPlane(int start, int size, int planeBorder) + { + return new ArrayPtr(pointer + start + planeBorder, size - planeBorder); + } + + YBuffer = NewPlane(0, yplaneSize, (Border * yStride) + Border); + UBuffer = NewPlane(yplaneSize, uvplaneSize, (uvBorderH * uvStride) + uvBorderW); + VBuffer = NewPlane(yplaneSize + uvplaneSize, uvplaneSize, (uvBorderH * uvStride) + uvBorderW); + } + + public readonly void Dispose() + { + Marshal.FreeHGlobal(_pointer); + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/TileInfo.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/TileInfo.cs new file mode 100644 index 00000000..a3eea81a --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/TileInfo.cs @@ -0,0 +1,86 @@ +using Ryujinx.Graphics.Nvdec.Vp9.Common; +using System; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct TileInfo + { + private const int MinTileWidthB64 = 4; + private const int MaxTileWidthB64 = 64; + + public int MiRowStart, MiRowEnd; + public int MiColStart, MiColEnd; + + public static int MiColsAlignedToSb(int nMis) + { + return BitUtils.AlignPowerOfTwo(nMis, Constants.MiBlockSizeLog2); + } + + private static int GetTileOffset(int idx, int mis, int log2) + { + int sbCols = MiColsAlignedToSb(mis) >> Constants.MiBlockSizeLog2; + int offset = ((idx * sbCols) >> log2) << Constants.MiBlockSizeLog2; + + return Math.Min(offset, mis); + } + + public void SetRow(ref Vp9Common cm, int row) + { + MiRowStart = GetTileOffset(row, cm.MiRows, cm.Log2TileRows); + MiRowEnd = GetTileOffset(row + 1, cm.MiRows, cm.Log2TileRows); + } + + public void SetCol(ref Vp9Common cm, int col) + { + MiColStart = GetTileOffset(col, cm.MiCols, cm.Log2TileCols); + MiColEnd = GetTileOffset(col + 1, cm.MiCols, cm.Log2TileCols); + } + + public void Init(ref Vp9Common cm, int row, int col) + { + SetRow(ref cm, row); + SetCol(ref cm, col); + } + + // Checks that the given miRow, miCol and search point + // are inside the borders of the tile. + public readonly bool IsInside(int miCol, int miRow, int miRows, ref Position miPos) + { + return !(miRow + miPos.Row < 0 || + miCol + miPos.Col < MiColStart || + miRow + miPos.Row >= miRows || + miCol + miPos.Col >= MiColEnd); + } + + private static int GetMinLog2TileCols(int sb64Cols) + { + int minLog2 = 0; + while ((MaxTileWidthB64 << minLog2) < sb64Cols) + { + ++minLog2; + } + + return minLog2; + } + + private static int GetMaxLog2TileCols(int sb64Cols) + { + int maxLog2 = 1; + while ((sb64Cols >> maxLog2) >= MinTileWidthB64) + { + ++maxLog2; + } + + return maxLog2 - 1; + } + + public static void GetTileNBits(int miCols, ref int minLog2TileCols, ref int maxLog2TileCols) + { + int sb64Cols = MiColsAlignedToSb(miCols) >> Constants.MiBlockSizeLog2; + minLog2TileCols = GetMinLog2TileCols(sb64Cols); + maxLog2TileCols = GetMaxLog2TileCols(sb64Cols); + Debug.Assert(minLog2TileCols <= maxLog2TileCols); + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxMode.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxMode.cs new file mode 100644 index 00000000..b3a05ed9 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxMode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + public enum TxMode + { + Only4X4 = 0, // Only 4x4 transform used + Allow8X8 = 1, // Allow block transform size up to 8x8 + Allow16X16 = 2, // Allow block transform size up to 16x16 + Allow32X32 = 3, // Allow block transform size up to 32x32 + TxModeSelect = 4, // Transform specified for each block + TxModes = 5, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxSize.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxSize.cs new file mode 100644 index 00000000..0342087b --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxSize.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + public enum TxSize + { + Tx4x4 = 0, // 4x4 transform + Tx8x8 = 1, // 8x8 transform + Tx16x16 = 2, // 16x16 transform + Tx32x32 = 3, // 32x32 transform + TxSizes = 4, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxType.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxType.cs new file mode 100644 index 00000000..e7b37c92 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal enum TxType + { + DctDct = 0, // DCT in both horizontal and vertical + AdstDct = 1, // ADST in vertical, DCT in horizontal + DctAdst = 2, // DCT in vertical, ADST in horizontal + AdstAdst = 3, // ADST in both directions + TxTypes = 4, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Vp9Common.cs b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Vp9Common.cs new file mode 100644 index 00000000..2456e906 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Types/Vp9Common.cs @@ -0,0 +1,331 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Nvdec.Vp9.Common; +using Ryujinx.Graphics.Video; + +namespace Ryujinx.Graphics.Nvdec.Vp9.Types +{ + internal struct Vp9Common + { + public MacroBlockD Mb; + + public ArrayPtr TileWorkerData; + + public InternalErrorInfo Error; + + public int Width; + public int Height; + + public int SubsamplingX; + public int SubsamplingY; + + public ArrayPtr PrevFrameMvs; + public ArrayPtr CurFrameMvs; + + public Array3 FrameRefs; + + public FrameType FrameType; + + // Flag signaling that the frame is encoded using only Intra modes. + public bool IntraOnly; + + public bool AllowHighPrecisionMv; + + // MBs, MbRows/Cols is in 16-pixel units; MiRows/Cols is in + // ModeInfo (8-pixel) units. + public int MBs; + public int MbRows, MiRows; + public int MbCols, MiCols; + public int MiStride; + + /* Profile settings */ + public TxMode TxMode; + + public int BaseQindex; + public int YDcDeltaQ; + public int UvDcDeltaQ; + public int UvAcDeltaQ; + public Array8> YDequant; + public Array8> UvDequant; + + /* We allocate a ModeInfo struct for each macroblock, together with + an extra row on top and column on the left to simplify prediction. */ + public ArrayPtr Mip; /* Base of allocated array */ + public ArrayPtr Mi; /* Corresponds to upper left visible macroblock */ + + public ArrayPtr> MiGridBase; + public ArrayPtr> MiGridVisible; + + // Whether to use previous frame's motion vectors for prediction. + public bool UsePrevFrameMvs; + + // Persistent mb segment id map used in prediction. + public int SegMapIdx; + public int PrevSegMapIdx; + + public Array2> SegMapArray; + public ArrayPtr LastFrameSegMap; + public ArrayPtr CurrentFrameSegMap; + + public byte InterpFilter; + + public LoopFilterInfoN LfInfo; + + public Array4 RefFrameSignBias; /* Two state 0, 1 */ + + public LoopFilter Lf; + public Segmentation Seg; + + // Context probabilities for reference frame prediction + public sbyte CompFixedRef; + public Array2 CompVarRef; + public ReferenceMode ReferenceMode; + + public Ptr Fc; + public Ptr Counts; + + public int Log2TileCols, Log2TileRows; + + public ArrayPtr AboveSegContext; + public ArrayPtr AboveContext; + + public readonly bool FrameIsIntraOnly() + { + return FrameType == FrameType.KeyFrame || IntraOnly; + } + + public bool CompoundReferenceAllowed() + { + int i; + for (i = 1; i < Constants.RefsPerFrame; ++i) + { + if (RefFrameSignBias[i + 1] != RefFrameSignBias[1]) + { + return true; + } + } + + return false; + } + + private static int CalcMiSize(int len) + { + // Len is in mi units. + return len + Constants.MiBlockSize; + } + + public void SetMbMi(int width, int height) + { + int alignedWidth = BitUtils.AlignPowerOfTwo(width, Constants.MiSizeLog2); + int alignedHeight = BitUtils.AlignPowerOfTwo(height, Constants.MiSizeLog2); + + MiCols = alignedWidth >> Constants.MiSizeLog2; + MiRows = alignedHeight >> Constants.MiSizeLog2; + MiStride = CalcMiSize(MiCols); + + MbCols = (MiCols + 1) >> 1; + MbRows = (MiRows + 1) >> 1; + MBs = MbRows * MbCols; + } + + public void AllocTileWorkerData(MemoryAllocator allocator, int tileCols, int tileRows, int maxThreads) + { + TileWorkerData = allocator.Allocate(tileCols * tileRows + (maxThreads > 1 ? maxThreads : 0)); + } + + public readonly void FreeTileWorkerData(MemoryAllocator allocator) + { + allocator.Free(TileWorkerData); + } + + private void AllocSegMap(MemoryAllocator allocator, int segMapSize) + { + int i; + + for (i = 0; i < Constants.NumPingPongBuffers; ++i) + { + SegMapArray[i] = allocator.Allocate(segMapSize); + } + + // Init the index. + SegMapIdx = 0; + PrevSegMapIdx = 1; + + CurrentFrameSegMap = SegMapArray[SegMapIdx]; + LastFrameSegMap = SegMapArray[PrevSegMapIdx]; + } + + private void FreeSegMap(MemoryAllocator allocator) + { + int i; + + for (i = 0; i < Constants.NumPingPongBuffers; ++i) + { + allocator.Free(SegMapArray[i]); + SegMapArray[i] = ArrayPtr.Null; + } + + CurrentFrameSegMap = ArrayPtr.Null; + LastFrameSegMap = ArrayPtr.Null; + } + + private void DecAllocMi(MemoryAllocator allocator, int miSize) + { + Mip = allocator.Allocate(miSize); + MiGridBase = allocator.Allocate>(miSize); + } + + private void DecFreeMi(MemoryAllocator allocator) + { + allocator.Free(Mip); + Mip = ArrayPtr.Null; + allocator.Free(MiGridBase); + MiGridBase = ArrayPtr>.Null; + } + + public void FreeContextBuffers(MemoryAllocator allocator) + { + DecFreeMi(allocator); + FreeSegMap(allocator); + allocator.Free(AboveContext); + AboveContext = ArrayPtr.Null; + allocator.Free(AboveSegContext); + AboveSegContext = ArrayPtr.Null; + allocator.Free(Lf.Lfm); + Lf.Lfm = ArrayPtr.Null; + allocator.Free(CurFrameMvs); + CurFrameMvs = ArrayPtr.Null; + if (UsePrevFrameMvs) + { + allocator.Free(PrevFrameMvs); + PrevFrameMvs = ArrayPtr.Null; + } + } + + private void AllocLoopFilter(MemoryAllocator allocator) + { + // Each lfm holds bit masks for all the 8x8 blocks in a 64x64 region. The + // stride and rows are rounded up / truncated to a multiple of 8. + Lf.LfmStride = (MiCols + (Constants.MiBlockSize - 1)) >> 3; + Lf.Lfm = allocator.Allocate(((MiRows + (Constants.MiBlockSize - 1)) >> 3) * Lf.LfmStride); + } + + public void AllocContextBuffers(MemoryAllocator allocator, int width, int height) + { + SetMbMi(width, height); + int newMiSize = MiStride * CalcMiSize(MiRows); + if (newMiSize != 0) + { + DecAllocMi(allocator, newMiSize); + } + + if (MiRows * MiCols != 0) + { + // Create the segmentation map structure and set to 0. + AllocSegMap(allocator, MiRows * MiCols); + } + + if (MiCols != 0) + { + AboveContext = allocator.Allocate(2 * TileInfo.MiColsAlignedToSb(MiCols) * Constants.MaxMbPlane); + AboveSegContext = allocator.Allocate(TileInfo.MiColsAlignedToSb(MiCols)); + } + + AllocLoopFilter(allocator); + + CurFrameMvs = allocator.Allocate(MiRows * MiCols); + // Using the same size as the current frame is fine here, + // as this is never true when we have a resolution change. + if (UsePrevFrameMvs) + { + PrevFrameMvs = allocator.Allocate(MiRows * MiCols); + } + } + + private unsafe void DecSetupMi() + { + Mi = Mip.Slice(MiStride + 1); + MiGridVisible = MiGridBase.Slice(MiStride + 1); + MemoryUtil.Fill(MiGridBase.ToPointer(), Ptr.Null, MiStride * (MiRows + 1)); + } + + public unsafe void InitContextBuffers() + { + DecSetupMi(); + if (!LastFrameSegMap.IsNull) + { + MemoryUtil.Fill(LastFrameSegMap.ToPointer(), (byte)0, MiRows * MiCols); + } + } + + private readonly void SetPartitionProbs(ref MacroBlockD xd) + { + xd.PartitionProbs = FrameIsIntraOnly() + ? new ArrayPtr>(ref Fc.Value.KfPartitionProb[0], 16) + : new ArrayPtr>(ref Fc.Value.PartitionProb[0], 16); + } + + internal void InitMacroBlockD(ref MacroBlockD xd, ArrayPtr dqcoeff) + { + int i; + + for (i = 0; i < Constants.MaxMbPlane; ++i) + { + xd.Plane[i].DqCoeff = dqcoeff; + xd.AboveContext[i] = AboveContext.Slice(i * 2 * TileInfo.MiColsAlignedToSb(MiCols)); + + if (i == 0) + { + MemoryUtil.Copy(ref xd.Plane[i].SegDequant, ref YDequant); + } + else + { + MemoryUtil.Copy(ref xd.Plane[i].SegDequant, ref UvDequant); + } + xd.Fc = new Ptr(ref Fc.Value); + } + + xd.AboveSegContext = AboveSegContext; + xd.MiStride = MiStride; + xd.ErrorInfo = new Ptr(ref Error); + + SetPartitionProbs(ref xd); + } + + public void SetupSegmentationDequant() + { + const BitDepth BitDepth = BitDepth.Bits8; // TODO: Configurable + // Build y/uv dequant values based on segmentation. + if (Seg.Enabled) + { + int i; + for (i = 0; i < Constants.MaxSegments; ++i) + { + int qIndex = QuantCommon.GetQIndex(ref Seg, i, BaseQindex); + YDequant[i][0] = QuantCommon.DcQuant(qIndex, YDcDeltaQ, BitDepth); + YDequant[i][1] = QuantCommon.AcQuant(qIndex, 0, BitDepth); + UvDequant[i][0] = QuantCommon.DcQuant(qIndex, UvDcDeltaQ, BitDepth); + UvDequant[i][1] = QuantCommon.AcQuant(qIndex, UvAcDeltaQ, BitDepth); + } + } + else + { + int qIndex = BaseQindex; + // When segmentation is disabled, only the first value is used. The + // remaining are don't cares. + YDequant[0][0] = QuantCommon.DcQuant(qIndex, YDcDeltaQ, BitDepth); + YDequant[0][1] = QuantCommon.AcQuant(qIndex, 0, BitDepth); + UvDequant[0][0] = QuantCommon.DcQuant(qIndex, UvDcDeltaQ, BitDepth); + UvDequant[0][1] = QuantCommon.AcQuant(qIndex, UvAcDeltaQ, BitDepth); + } + } + + public void SetupScaleFactors() + { + for (int i = 0; i < Constants.RefsPerFrame; ++i) + { + ref RefBuffer refBuf = ref FrameRefs[i]; + refBuf.Sf.SetupScaleFactorsForFrame(refBuf.Buf.Width, refBuf.Buf.Height, Width, Height); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/ApplicationId.cs b/src/Ryujinx.Graphics.Nvdec/ApplicationId.cs new file mode 100644 index 00000000..5914e73c --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/ApplicationId.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Nvdec +{ + public enum ApplicationId + { + Mpeg = 1, + Vc1 = 2, + H264 = 3, + Mpeg4 = 4, + Vp8 = 5, + Hevc = 7, + Vp9 = 9, + HevcParser = 12, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/H264Decoder.cs b/src/Ryujinx.Graphics.Nvdec/H264Decoder.cs new file mode 100644 index 00000000..6058f72d --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/H264Decoder.cs @@ -0,0 +1,57 @@ +using Ryujinx.Graphics.Nvdec.FFmpeg.H264; +using Ryujinx.Graphics.Nvdec.Image; +using Ryujinx.Graphics.Nvdec.Types.H264; +using Ryujinx.Graphics.Video; +using System; + +namespace Ryujinx.Graphics.Nvdec +{ + static class H264Decoder + { + private const int MbSizeInPixels = 16; + + public static void Decode(NvdecDecoderContext context, ResourceManager rm, ref NvdecRegisters state) + { + PictureInfo pictureInfo = rm.MemoryManager.DeviceRead(state.SetDrvPicSetupOffset); + H264PictureInfo info = pictureInfo.Convert(); + + ReadOnlySpan bitstream = rm.MemoryManager.DeviceGetSpan(state.SetInBufBaseOffset, (int)pictureInfo.BitstreamSize); + + int width = (int)pictureInfo.PicWidthInMbs * MbSizeInPixels; + int height = (int)pictureInfo.PicHeightInMbs * MbSizeInPixels; + + int surfaceIndex = (int)pictureInfo.OutputSurfaceIndex; + + uint lumaOffset = state.SetPictureLumaOffset[surfaceIndex]; + uint chromaOffset = state.SetPictureChromaOffset[surfaceIndex]; + + Decoder decoder = context.GetH264Decoder(); + + ISurface outputSurface = rm.Cache.Get(decoder, 0, 0, width, height); + + if (decoder.Decode(ref info, outputSurface, bitstream)) + { + if (outputSurface.Field == FrameField.Progressive) + { + SurfaceWriter.Write( + rm.MemoryManager, + outputSurface, + lumaOffset + pictureInfo.LumaFrameOffset, + chromaOffset + pictureInfo.ChromaFrameOffset); + } + else + { + SurfaceWriter.WriteInterlaced( + rm.MemoryManager, + outputSurface, + lumaOffset + pictureInfo.LumaTopFieldOffset, + chromaOffset + pictureInfo.ChromaTopFieldOffset, + lumaOffset + pictureInfo.LumaBottomFieldOffset, + chromaOffset + pictureInfo.ChromaBottomFieldOffset); + } + } + + rm.Cache.Put(outputSurface); + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Image/SurfaceCache.cs b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceCache.cs new file mode 100644 index 00000000..7359b330 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceCache.cs @@ -0,0 +1,174 @@ +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Video; +using System; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Nvdec.Image +{ + class SurfaceCache + { + // Must be equal to at least the maximum number of surfaces + // that can be in use simultaneously (which is 17, since H264 + // can have up to 16 reference frames, and we need another one + // for the current frame). + // Realistically, most codecs won't ever use more than 4 simultaneously. + private const int MaxItems = 17; + + private struct CacheItem + { + public int ReferenceCount; + public uint LumaOffset; + public uint ChromaOffset; + public int Width; + public int Height; + public IDecoder Owner; + public ISurface Surface; + } + + private readonly CacheItem[] _pool = new CacheItem[MaxItems]; + + private readonly DeviceMemoryManager _mm; + + public SurfaceCache(DeviceMemoryManager mm) + { + _mm = mm; + } + + public ISurface Get(IDecoder decoder, uint lumaOffset, uint chromaOffset, int width, int height) + { + lock (_pool) + { + ISurface surface = null; + + // Try to find a compatible surface with same parameters, and same offsets. + for (int i = 0; i < MaxItems; i++) + { + ref CacheItem item = ref _pool[i]; + + if (item.LumaOffset == lumaOffset && + item.ChromaOffset == chromaOffset && + item.Owner == decoder && + item.Width == width && + item.Height == height) + { + item.ReferenceCount++; + surface = item.Surface; + MoveToFront(i); + break; + } + } + + // If we failed to find a perfect match, now ignore the offsets. + // Search backwards to replace the oldest compatible surface, + // this avoids thrashing frequently used surfaces. + // Now we need to ensure that the surface is not in use, as we'll change the data. + if (surface == null) + { + for (int i = MaxItems - 1; i >= 0; i--) + { + ref CacheItem item = ref _pool[i]; + + if (item.ReferenceCount == 0 && item.Owner == decoder && item.Width == width && item.Height == height) + { + item.ReferenceCount = 1; + item.LumaOffset = lumaOffset; + item.ChromaOffset = chromaOffset; + surface = item.Surface; + + if ((lumaOffset | chromaOffset) != 0) + { + SurfaceReader.Read(_mm, surface, lumaOffset, chromaOffset); + } + + MoveToFront(i); + break; + } + } + } + + // If everything else failed, we try to create a new surface, + // and insert it on the pool. We replace the oldest item on the + // pool to avoid thrashing frequently used surfaces. + // If even the oldest item is in use, that means that the entire pool + // is in use, in that case we throw as there's no place to insert + // the new surface. + if (surface == null) + { + if (_pool[MaxItems - 1].ReferenceCount == 0) + { + surface = decoder.CreateSurface(width, height); + + if ((lumaOffset | chromaOffset) != 0) + { + SurfaceReader.Read(_mm, surface, lumaOffset, chromaOffset); + } + + MoveToFront(MaxItems - 1); + ref CacheItem item = ref _pool[0]; + item.Surface?.Dispose(); + item.ReferenceCount = 1; + item.LumaOffset = lumaOffset; + item.ChromaOffset = chromaOffset; + item.Width = width; + item.Height = height; + item.Owner = decoder; + item.Surface = surface; + } + else + { + throw new InvalidOperationException("No free slot on the surface pool."); + } + } + + return surface; + } + } + + public void Put(ISurface surface) + { + lock (_pool) + { + for (int i = 0; i < MaxItems; i++) + { + ref CacheItem item = ref _pool[i]; + + if (item.Surface == surface) + { + item.ReferenceCount--; + Debug.Assert(item.ReferenceCount >= 0); + break; + } + } + } + } + + private void MoveToFront(int index) + { + // If index is 0 we don't need to do anything, + // as it's already on the front. + if (index != 0) + { + CacheItem temp = _pool[index]; + Array.Copy(_pool, 0, _pool, 1, index); + _pool[0] = temp; + } + } + + public void Trim() + { + lock (_pool) + { + for (int i = 0; i < MaxItems; i++) + { + ref CacheItem item = ref _pool[i]; + + if (item.ReferenceCount == 0) + { + item.Surface?.Dispose(); + item = default; + } + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Image/SurfaceCommon.cs b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceCommon.cs new file mode 100644 index 00000000..14bc113f --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceCommon.cs @@ -0,0 +1,26 @@ +using Ryujinx.Graphics.Texture; +using Ryujinx.Graphics.Video; +using System; + +namespace Ryujinx.Graphics.Nvdec.Image +{ + static class SurfaceCommon + { + public static int GetBlockLinearSize(int width, int height, int bytesPerPixel) + { + return SizeCalculator.GetBlockLinearTextureSize(width, height, 1, 1, 1, 1, 1, bytesPerPixel, 2, 1, 1).TotalSize; + } + + public static void Copy(ISurface src, ISurface dst) + { + src.YPlane.AsSpan().CopyTo(dst.YPlane.AsSpan()); + src.UPlane.AsSpan().CopyTo(dst.UPlane.AsSpan()); + src.VPlane.AsSpan().CopyTo(dst.VPlane.AsSpan()); + } + + public unsafe static Span AsSpan(this Plane plane) + { + return new Span((void*)plane.Pointer, plane.Length); + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Image/SurfaceReader.cs b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceReader.cs new file mode 100644 index 00000000..f510c128 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceReader.cs @@ -0,0 +1,133 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Texture; +using Ryujinx.Graphics.Video; +using System; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static Ryujinx.Graphics.Nvdec.Image.SurfaceCommon; + +namespace Ryujinx.Graphics.Nvdec.Image +{ + static class SurfaceReader + { + public static void Read(DeviceMemoryManager mm, ISurface surface, uint lumaOffset, uint chromaOffset) + { + int width = surface.Width; + int height = surface.Height; + int stride = surface.Stride; + + ReadOnlySpan luma = mm.DeviceGetSpan(lumaOffset, GetBlockLinearSize(width, height, 1)); + + ReadLuma(surface.YPlane.AsSpan(), luma, stride, width, height); + + int uvWidth = surface.UvWidth; + int uvHeight = surface.UvHeight; + int uvStride = surface.UvStride; + + ReadOnlySpan chroma = mm.DeviceGetSpan(chromaOffset, GetBlockLinearSize(uvWidth, uvHeight, 2)); + + ReadChroma(surface.UPlane.AsSpan(), surface.VPlane.AsSpan(), chroma, uvStride, uvWidth, uvHeight); + } + + private static void ReadLuma(Span dst, ReadOnlySpan src, int dstStride, int width, int height) + { + LayoutConverter.ConvertBlockLinearToLinear(dst, width, height, dstStride, 1, 2, src); + } + + private unsafe static void ReadChroma( + Span dstU, + Span dstV, + ReadOnlySpan src, + int dstStride, + int width, + int height) + { + OffsetCalculator calc = new(width, height, 0, false, 2, 2); + + if (Sse2.IsSupported) + { + int strideTrunc64 = BitUtils.AlignDown(width * 2, 64); + + int outStrideGap = dstStride - width; + + fixed (byte* dstUPtr = dstU, dstVPtr = dstV, dataPtr = src) + { + byte* uPtr = dstUPtr; + byte* vPtr = dstVPtr; + + for (int y = 0; y < height; y++) + { + calc.SetY(y); + + for (int x = 0; x < strideTrunc64; x += 64, uPtr += 32, vPtr += 32) + { + byte* offset = dataPtr + calc.GetOffsetWithLineOffset64(x); + byte* offset2 = offset + 0x20; + byte* offset3 = offset + 0x100; + byte* offset4 = offset + 0x120; + + Vector128 value = *(Vector128*)offset; + Vector128 value2 = *(Vector128*)offset2; + Vector128 value3 = *(Vector128*)offset3; + Vector128 value4 = *(Vector128*)offset4; + + Vector128 u00 = Sse2.UnpackLow(value, value2); + Vector128 v00 = Sse2.UnpackHigh(value, value2); + Vector128 u01 = Sse2.UnpackLow(value3, value4); + Vector128 v01 = Sse2.UnpackHigh(value3, value4); + + Vector128 u10 = Sse2.UnpackLow(u00, v00); + Vector128 v10 = Sse2.UnpackHigh(u00, v00); + Vector128 u11 = Sse2.UnpackLow(u01, v01); + Vector128 v11 = Sse2.UnpackHigh(u01, v01); + + Vector128 u20 = Sse2.UnpackLow(u10, v10); + Vector128 v20 = Sse2.UnpackHigh(u10, v10); + Vector128 u21 = Sse2.UnpackLow(u11, v11); + Vector128 v21 = Sse2.UnpackHigh(u11, v11); + + Vector128 u30 = Sse2.UnpackLow(u20, v20); + Vector128 v30 = Sse2.UnpackHigh(u20, v20); + Vector128 u31 = Sse2.UnpackLow(u21, v21); + Vector128 v31 = Sse2.UnpackHigh(u21, v21); + + *(Vector128*)uPtr = u30; + *(Vector128*)(uPtr + 16) = u31; + *(Vector128*)vPtr = v30; + *(Vector128*)(vPtr + 16) = v31; + } + + for (int x = strideTrunc64 / 2; x < width; x++, uPtr++, vPtr++) + { + byte* offset = dataPtr + calc.GetOffset(x); + + *uPtr = *offset; + *vPtr = *(offset + 1); + } + + uPtr += outStrideGap; + vPtr += outStrideGap; + } + } + } + else + { + for (int y = 0; y < height; y++) + { + int dstBaseOffset = y * dstStride; + + calc.SetY(y); + + for (int x = 0; x < width; x++) + { + int srcOffset = calc.GetOffset(x); + + dstU[dstBaseOffset + x] = src[srcOffset]; + dstV[dstBaseOffset + x] = src[srcOffset + 1]; + } + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Image/SurfaceWriter.cs b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceWriter.cs new file mode 100644 index 00000000..043be1f2 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Image/SurfaceWriter.cs @@ -0,0 +1,175 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Texture; +using Ryujinx.Graphics.Video; +using System; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static Ryujinx.Graphics.Nvdec.Image.SurfaceCommon; +using static Ryujinx.Graphics.Nvdec.MemoryExtensions; + +namespace Ryujinx.Graphics.Nvdec.Image +{ + static class SurfaceWriter + { + public static void Write(DeviceMemoryManager mm, ISurface surface, uint lumaOffset, uint chromaOffset) + { + int lumaSize = GetBlockLinearSize(surface.Width, surface.Height, 1); + + using var luma = mm.GetWritableRegion(ExtendOffset(lumaOffset), lumaSize); + + WriteLuma( + luma.Memory.Span, + surface.YPlane.AsSpan(), + surface.Stride, + surface.Width, + surface.Height); + + int chromaSize = GetBlockLinearSize(surface.UvWidth, surface.UvHeight, 2); + + using var chroma = mm.GetWritableRegion(ExtendOffset(chromaOffset), chromaSize); + + WriteChroma( + chroma.Memory.Span, + surface.UPlane.AsSpan(), + surface.VPlane.AsSpan(), + surface.UvStride, + surface.UvWidth, + surface.UvHeight); + } + + public static void WriteInterlaced( + DeviceMemoryManager mm, + ISurface surface, + uint lumaTopOffset, + uint chromaTopOffset, + uint lumaBottomOffset, + uint chromaBottomOffset) + { + int lumaSize = GetBlockLinearSize(surface.Width, surface.Height / 2, 1); + + using var lumaTop = mm.GetWritableRegion(ExtendOffset(lumaTopOffset), lumaSize); + using var lumaBottom = mm.GetWritableRegion(ExtendOffset(lumaBottomOffset), lumaSize); + + WriteLuma( + lumaTop.Memory.Span, + surface.YPlane.AsSpan(), + surface.Stride * 2, + surface.Width, + surface.Height / 2); + + WriteLuma( + lumaBottom.Memory.Span, + surface.YPlane.AsSpan()[surface.Stride..], + surface.Stride * 2, + surface.Width, + surface.Height / 2); + + int chromaSize = GetBlockLinearSize(surface.UvWidth, surface.UvHeight / 2, 2); + + using var chromaTop = mm.GetWritableRegion(ExtendOffset(chromaTopOffset), chromaSize); + using var chromaBottom = mm.GetWritableRegion(ExtendOffset(chromaBottomOffset), chromaSize); + + WriteChroma( + chromaTop.Memory.Span, + surface.UPlane.AsSpan(), + surface.VPlane.AsSpan(), + surface.UvStride * 2, + surface.UvWidth, + surface.UvHeight / 2); + + WriteChroma( + chromaBottom.Memory.Span, + surface.UPlane.AsSpan()[surface.UvStride..], + surface.VPlane.AsSpan()[surface.UvStride..], + surface.UvStride * 2, + surface.UvWidth, + surface.UvHeight / 2); + } + + private static void WriteLuma(Span dst, ReadOnlySpan src, int srcStride, int width, int height) + { + LayoutConverter.ConvertLinearToBlockLinear(dst, width, height, srcStride, 1, 2, src); + } + + private unsafe static void WriteChroma( + Span dst, + ReadOnlySpan srcU, + ReadOnlySpan srcV, + int srcStride, + int width, + int height) + { + OffsetCalculator calc = new(width, height, 0, false, 2, 2); + + if (Sse2.IsSupported) + { + int strideTrunc64 = BitUtils.AlignDown(width * 2, 64); + + int inStrideGap = srcStride - width; + + fixed (byte* outputPtr = dst, srcUPtr = srcU, srcVPtr = srcV) + { + byte* inUPtr = srcUPtr; + byte* inVPtr = srcVPtr; + + for (int y = 0; y < height; y++) + { + calc.SetY(y); + + for (int x = 0; x < strideTrunc64; x += 64, inUPtr += 32, inVPtr += 32) + { + byte* offset = outputPtr + calc.GetOffsetWithLineOffset64(x); + byte* offset2 = offset + 0x20; + byte* offset3 = offset + 0x100; + byte* offset4 = offset + 0x120; + + Vector128 value = *(Vector128*)inUPtr; + Vector128 value2 = *(Vector128*)inVPtr; + Vector128 value3 = *(Vector128*)(inUPtr + 16); + Vector128 value4 = *(Vector128*)(inVPtr + 16); + + Vector128 uv0 = Sse2.UnpackLow(value, value2); + Vector128 uv1 = Sse2.UnpackHigh(value, value2); + Vector128 uv2 = Sse2.UnpackLow(value3, value4); + Vector128 uv3 = Sse2.UnpackHigh(value3, value4); + + *(Vector128*)offset = uv0; + *(Vector128*)offset2 = uv1; + *(Vector128*)offset3 = uv2; + *(Vector128*)offset4 = uv3; + } + + for (int x = strideTrunc64 / 2; x < width; x++, inUPtr++, inVPtr++) + { + byte* offset = outputPtr + calc.GetOffset(x); + + *offset = *inUPtr; + *(offset + 1) = *inVPtr; + } + + inUPtr += inStrideGap; + inVPtr += inStrideGap; + } + } + } + else + { + for (int y = 0; y < height; y++) + { + int srcBaseOffset = y * srcStride; + + calc.SetY(y); + + for (int x = 0; x < width; x++) + { + int dstOffset = calc.GetOffset(x); + + dst[dstOffset + 0] = srcU[srcBaseOffset + x]; + dst[dstOffset + 1] = srcV[srcBaseOffset + x]; + } + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/MemoryExtensions.cs b/src/Ryujinx.Graphics.Nvdec/MemoryExtensions.cs new file mode 100644 index 00000000..1477ed91 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/MemoryExtensions.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.Device; +using System; + +namespace Ryujinx.Graphics.Nvdec +{ + static class MemoryExtensions + { + public static T DeviceRead(this DeviceMemoryManager gmm, uint offset) where T : unmanaged + { + return gmm.Read(ExtendOffset(offset)); + } + + public static ReadOnlySpan DeviceGetSpan(this DeviceMemoryManager gmm, uint offset, int size) + { + return gmm.GetSpan(ExtendOffset(offset), size); + } + + public static void DeviceWrite(this DeviceMemoryManager gmm, uint offset, ReadOnlySpan data) + { + gmm.Write(ExtendOffset(offset), data); + } + + public static ulong ExtendOffset(uint offset) + { + return (ulong)offset << 8; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/NvdecDecoderContext.cs b/src/Ryujinx.Graphics.Nvdec/NvdecDecoderContext.cs new file mode 100644 index 00000000..aaa734a8 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/NvdecDecoderContext.cs @@ -0,0 +1,29 @@ +using System; + +namespace Ryujinx.Graphics.Nvdec +{ + class NvdecDecoderContext : IDisposable + { + private FFmpeg.H264.Decoder _h264Decoder; + private FFmpeg.Vp8.Decoder _vp8Decoder; + + public FFmpeg.H264.Decoder GetH264Decoder() + { + return _h264Decoder ??= new FFmpeg.H264.Decoder(); + } + + public FFmpeg.Vp8.Decoder GetVp8Decoder() + { + return _vp8Decoder ??= new FFmpeg.Vp8.Decoder(); + } + + public void Dispose() + { + _h264Decoder?.Dispose(); + _h264Decoder = null; + + _vp8Decoder?.Dispose(); + _vp8Decoder = null; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/NvdecDevice.cs b/src/Ryujinx.Graphics.Nvdec/NvdecDevice.cs new file mode 100644 index 00000000..29e260d6 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/NvdecDevice.cs @@ -0,0 +1,82 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Nvdec.Image; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.Nvdec +{ + public class NvdecDevice : IDeviceStateWithContext + { + private readonly ResourceManager _rm; + private readonly DeviceState _state; + + private long _currentId; + private readonly ConcurrentDictionary _contexts; + private NvdecDecoderContext _currentContext; + + public NvdecDevice(DeviceMemoryManager mm) + { + _rm = new ResourceManager(mm, new SurfaceCache(mm)); + _state = new DeviceState(new Dictionary + { + { nameof(NvdecRegisters.Execute), new RwCallback(Execute, null) }, + }); + _contexts = new ConcurrentDictionary(); + } + + public long CreateContext() + { + long id = Interlocked.Increment(ref _currentId); + _contexts.TryAdd(id, new NvdecDecoderContext()); + + return id; + } + + public void DestroyContext(long id) + { + if (_contexts.TryRemove(id, out var context)) + { + context.Dispose(); + } + + _rm.Cache.Trim(); + } + + public void BindContext(long id) + { + if (_contexts.TryGetValue(id, out var context)) + { + _currentContext = context; + } + } + + public int Read(int offset) => _state.Read(offset); + public void Write(int offset, int data) => _state.Write(offset, data); + + private void Execute(int data) + { + Decode((ApplicationId)_state.State.SetApplicationId); + } + + private void Decode(ApplicationId applicationId) + { + switch (applicationId) + { + case ApplicationId.H264: + H264Decoder.Decode(_currentContext, _rm, ref _state.State); + break; + case ApplicationId.Vp8: + Vp8Decoder.Decode(_currentContext, _rm, ref _state.State); + break; + case ApplicationId.Vp9: + Vp9Decoder.Decode(_rm, ref _state.State); + break; + default: + Logger.Error?.Print(LogClass.Nvdec, $"Unsupported codec \"{applicationId}\"."); + break; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/NvdecRegisters.cs b/src/Ryujinx.Graphics.Nvdec/NvdecRegisters.cs new file mode 100644 index 00000000..bc325715 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/NvdecRegisters.cs @@ -0,0 +1,63 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Nvdec +{ + struct NvdecRegisters + { +#pragma warning disable CS0649 // Field is never assigned to + public Array64 Reserved0; + public uint Nop; + public Array63 Reserved104; + public uint SetApplicationId; + public uint SetWatchdogTimer; + public Array14 Reserved208; + public uint SemaphoreA; + public uint SemaphoreB; + public uint SemaphoreC; + public uint CtxSaveArea; + public Array44 Reserved254; + public uint Execute; + public uint SemaphoreD; + public Array62 Reserved308; + public uint SetControlParams; + public uint SetDrvPicSetupOffset; + public uint SetInBufBaseOffset; + public uint SetPictureIndex; + public uint SetSliceOffsetsBufOffset; // Also used by VC1 + public uint SetColocDataOffset; // Also used by VC1 + public uint SetHistoryOffset; // Used by VC1 + public uint SetDisplayBufSize; + public uint SetHistogramOffset; // Used by VC1 + public uint SetNvDecStatusOffset; + public uint SetDisplayBufLumaOffset; + public uint SetDisplayBufChromaOffset; + public Array17 SetPictureLumaOffset; + public Array17 SetPictureChromaOffset; + public uint SetPicScratchBufOffset; + public uint SetExternalMvBufferOffset; + public uint SetCryptoData0Offset; + public uint SetCryptoData1Offset; + public Array14 Unknown4C8; + public uint H264SetMbHistBufOffset; + public Array15 Unknown504; + public uint Vp8SetProbDataOffset; + public uint Vp8SetHeaderPartitionBufBaseOffset; + public Array14 Unknown548; + public uint HevcSetScalingListOffset; + public uint HevcSetTileSizesOffset; + public uint HevcSetFilterBufferOffset; + public uint HevcSetSaoBufferOffset; + public uint HevcSetSliceInfoBufferOffset; + public uint HevcSetSliceGroupIndex; + public Array10 Unknown598; + public uint Vp9SetProbTabBufOffset; + public uint Vp9SetCtxCounterBufOffset; + public uint Vp9SetSegmentReadBufOffset; + public uint Vp9SetSegmentWriteBufOffset; + public uint Vp9SetTileSizeBufOffset; + public uint Vp9SetColMvWriteBufOffset; + public uint Vp9SetColMvReadBufOffset; + public uint Vp9SetFilterBufferOffset; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/NvdecStatus.cs b/src/Ryujinx.Graphics.Nvdec/NvdecStatus.cs new file mode 100644 index 00000000..1d6b4a60 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/NvdecStatus.cs @@ -0,0 +1,16 @@ +using Ryujinx.Graphics.Nvdec.Types.Vp9; + +namespace Ryujinx.Graphics.Nvdec +{ + struct NvdecStatus + { +#pragma warning disable CS0649 // Field is never assigned to + public uint MbsCorrectlyDecoded; + public uint MbsInError; + public uint Reserved; + public uint ErrorStatus; + public FrameStats Stats; + public uint SliceHeaderErrorCode; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/ResourceManager.cs b/src/Ryujinx.Graphics.Nvdec/ResourceManager.cs new file mode 100644 index 00000000..da0ded91 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/ResourceManager.cs @@ -0,0 +1,17 @@ +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Nvdec.Image; + +namespace Ryujinx.Graphics.Nvdec +{ + readonly struct ResourceManager + { + public DeviceMemoryManager MemoryManager { get; } + public SurfaceCache Cache { get; } + + public ResourceManager(DeviceMemoryManager mm, SurfaceCache cache) + { + MemoryManager = mm; + Cache = cache; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj b/src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj new file mode 100644 index 00000000..6c00e9a7 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + true + + + + + + + + + + + + diff --git a/src/Ryujinx.Graphics.Nvdec/Types/H264/PictureInfo.cs b/src/Ryujinx.Graphics.Nvdec/Types/H264/PictureInfo.cs new file mode 100644 index 00000000..26ba2ea6 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Types/H264/PictureInfo.cs @@ -0,0 +1,123 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Video; + +namespace Ryujinx.Graphics.Nvdec.Types.H264 +{ + struct PictureInfo + { +#pragma warning disable IDE0051, CS0169, CS0649 // Remove unused private member + Array18 Unknown0; +#pragma warning restore IDE0051 + public uint BitstreamSize; + public uint NumSlices; + public uint Unknown50; + public uint Unknown54; + public uint Log2MaxPicOrderCntLsbMinus4; + public uint DeltaPicOrderAlwaysZeroFlag; + public uint FrameMbsOnlyFlag; + public uint PicWidthInMbs; + public uint PicHeightInMbs; + public uint BlockLayout; // Not supported on T210 + public uint EntropyCodingModeFlag; + public uint PicOrderPresentFlag; + public uint NumRefIdxL0ActiveMinus1; + public uint NumRefIdxL1ActiveMinus1; + public uint DeblockingFilterControlPresentFlag; + public uint RedundantPicCntPresentFlag; + public uint Transform8x8ModeFlag; + public uint LumaPitch; + public uint ChromaPitch; + public uint LumaTopFieldOffset; + public uint LumaBottomFieldOffset; + public uint LumaFrameOffset; + public uint ChromaTopFieldOffset; + public uint ChromaBottomFieldOffset; + public uint ChromaFrameOffset; + public uint HistBufferSize; + public ulong Flags; + public Array2 FieldOrderCnt; + public Array16 RefFrames; + public Array6> ScalingLists4x4; + public Array2> ScalingLists8x8; + public byte MvcextNumInterViewRefsL0; + public byte MvcextNumInterViewRefsL1; + public ushort Padding2A2; + public uint Unknown2A4; + public uint Unknown2A8; + public uint Unknown2AC; + public Array16 MvcextViewRefMasksL0; + public Array16 MvcextViewRefMasksL1; + public uint Flags2; + public Array10 Unknown2D4; +#pragma warning restore CS0169, CS0649 + + public readonly bool MbAdaptiveFrameFieldFlag => (Flags & (1 << 0)) != 0; + public readonly bool Direct8x8InferenceFlag => (Flags & (1 << 1)) != 0; + public readonly bool WeightedPredFlag => (Flags & (1 << 2)) != 0; + public readonly bool ConstrainedIntraPredFlag => (Flags & (1 << 3)) != 0; + public readonly bool IsReference => (Flags & (1 << 4)) != 0; + public readonly bool FieldPicFlag => (Flags & (1 << 5)) != 0; + public readonly bool BottomFieldFlag => (Flags & (1 << 6)) != 0; + public readonly uint Log2MaxFrameNumMinus4 => (uint)(Flags >> 8) & 0xf; + public readonly ushort ChromaFormatIdc => (ushort)((Flags >> 12) & 3); + public readonly uint PicOrderCntType => (uint)(Flags >> 14) & 3; + public readonly int PicInitQpMinus26 => ExtractSx(Flags, 16, 6); + public readonly int ChromaQpIndexOffset => ExtractSx(Flags, 22, 5); + public readonly int SecondChromaQpIndexOffset => ExtractSx(Flags, 27, 5); + public readonly uint WeightedBipredIdc => (uint)(Flags >> 32) & 3; + public readonly uint OutputSurfaceIndex => (uint)(Flags >> 34) & 0x7f; + public readonly uint ColIndex => (uint)(Flags >> 41) & 0x1f; + public readonly ushort FrameNum => (ushort)(Flags >> 46); + public readonly bool QpprimeYZeroTransformBypassFlag => (Flags2 & (1 << 1)) != 0; + + private static int ExtractSx(ulong packed, int lsb, int length) + { + return (int)((long)packed << (64 - (lsb + length)) >> (64 - length)); + } + + public H264PictureInfo Convert() + { + return new H264PictureInfo() + { + FieldOrderCnt = FieldOrderCnt, + IsReference = IsReference, + ChromaFormatIdc = ChromaFormatIdc, + FrameNum = FrameNum, + FieldPicFlag = FieldPicFlag, + BottomFieldFlag = BottomFieldFlag, + NumRefFrames = 0, + MbAdaptiveFrameFieldFlag = MbAdaptiveFrameFieldFlag, + ConstrainedIntraPredFlag = ConstrainedIntraPredFlag, + WeightedPredFlag = WeightedPredFlag, + WeightedBipredIdc = WeightedBipredIdc, + FrameMbsOnlyFlag = FrameMbsOnlyFlag != 0, + Transform8x8ModeFlag = Transform8x8ModeFlag != 0, + ChromaQpIndexOffset = ChromaQpIndexOffset, + SecondChromaQpIndexOffset = SecondChromaQpIndexOffset, + PicInitQpMinus26 = PicInitQpMinus26, + NumRefIdxL0ActiveMinus1 = NumRefIdxL0ActiveMinus1, + NumRefIdxL1ActiveMinus1 = NumRefIdxL1ActiveMinus1, + Log2MaxFrameNumMinus4 = Log2MaxFrameNumMinus4, + PicOrderCntType = PicOrderCntType, + Log2MaxPicOrderCntLsbMinus4 = Log2MaxPicOrderCntLsbMinus4, + DeltaPicOrderAlwaysZeroFlag = DeltaPicOrderAlwaysZeroFlag != 0, + Direct8x8InferenceFlag = Direct8x8InferenceFlag, + EntropyCodingModeFlag = EntropyCodingModeFlag != 0, + PicOrderPresentFlag = PicOrderPresentFlag != 0, + DeblockingFilterControlPresentFlag = DeblockingFilterControlPresentFlag != 0, + RedundantPicCntPresentFlag = RedundantPicCntPresentFlag != 0, + NumSliceGroupsMinus1 = 0, + SliceGroupMapType = 0, + SliceGroupChangeRateMinus1 = 0, + FmoAsoEnable = false, + ScalingMatrixPresent = true, + ScalingLists4x4 = ScalingLists4x4, + ScalingLists8x8 = ScalingLists8x8, + FrameType = 0, + PicWidthInMbsMinus1 = PicWidthInMbs - 1, + PicHeightInMapUnitsMinus1 = (PicHeightInMbs >> (FrameMbsOnlyFlag != 0 ? 0 : 1)) - 1, + QpprimeYZeroTransformBypassFlag = QpprimeYZeroTransformBypassFlag, + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Types/H264/ReferenceFrame.cs b/src/Ryujinx.Graphics.Nvdec/Types/H264/ReferenceFrame.cs new file mode 100644 index 00000000..c310abd6 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Types/H264/ReferenceFrame.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Nvdec.Types.H264 +{ + struct ReferenceFrame + { +#pragma warning disable CS0649 // Field is never assigned to + public uint Flags; + public Array2 FieldOrderCnt; + public uint FrameNum; +#pragma warning restore CS0649 + + public readonly uint OutputSurfaceIndex => (uint)Flags & 0x7f; + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Types/Vp8/PictureInfo.cs b/src/Ryujinx.Graphics.Nvdec/Types/Vp8/PictureInfo.cs new file mode 100644 index 00000000..a495cfc8 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Types/Vp8/PictureInfo.cs @@ -0,0 +1,75 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Video; + +namespace Ryujinx.Graphics.Nvdec.Types.Vp8 +{ + struct PictureInfo + { +#pragma warning disable CS0649 // Field is never assigned to + public Array13 Unknown0; + public uint GpTimerTimeoutValue; + public ushort FrameWidth; + public ushort FrameHeight; + public byte KeyFrame; // 1: key frame - 0: not + public byte Version; + public byte Flags0; + // TileFormat : 2 // 0: TBL; 1: KBL; + // GobHeight : 3 // Set GOB height, 0: GOB_2, 1: GOB_4, 2: GOB_8, 3: GOB_16, 4: GOB_32 (NVDEC3 onwards) + // ReserverdSurfaceFormat : 3 + public byte ErrorConcealOn; // 1: error conceal on - 0: off + public uint FirstPartSize; // the size of first partition (frame header and mb header partition) + public uint HistBufferSize; // in units of 256 + public uint VLDBufferSize; // in units of 1 + public Array2 FrameStride; // [y_c] + public uint LumaTopOffset; // offset of luma top field in units of 256 + public uint LumaBotOffset; // offset of luma bottom field in units of 256 + public uint LumaFrameOffset; // offset of luma frame in units of 256 + public uint ChromaTopOffset; // offset of chroma top field in units of 256 + public uint ChromaBotOffset; // offset of chroma bottom field in units of 256 + public uint ChromaFrameOffset; // offset of chroma frame in units of 256 + public uint Flags1; + // EnableTFOutput : 1; // =1, enable dbfdma to output the display surface; if disable, then the following configure on tf is useless. + // Remap for VC1 + // VC1MapYFlag : 1 + // MapYValue : 3 + // VC1MapUVFlag : 1 + // MapUVValue : 3 + // TF + // OutStride : 8 + // TilingFormat : 3; + // OutputStructure : 1 // 0:frame, 1:field + // Reserved0 : 11 + public Array2 OutputTop; // in units of 256 + public Array2 OutputBottom; // in units of 256 + // Histogram + public uint Flags2; + // EnableHistogram : 1 // enable histogram info collection + // HistogramStartX : 12 // start X of Histogram window + // HistogramStartY : 12 // start Y of Histogram window + // Reserved1 : 7 + // HistogramEndX : 12 // end X of Histogram window + // HistogramEndY : 12 // end y of Histogram window + // Reserved2 : 8 + // Decode picture buffer related + public sbyte CurrentOutputMemoryLayout; + public Array3 OutputMemoryLayout; // output NV12/NV24 setting. item 0:golden - 1: altref - 2: last + public byte SegmentationFeatureDataUpdate; + public Array3 Reserved3; + public uint ResultValue; // ucode return result + public Array8 PartitionOffset; + public Array3 Reserved4; +#pragma warning restore CS0649 + + public Vp8PictureInfo Convert() + { + return new Vp8PictureInfo() + { + KeyFrame = KeyFrame != 0, + FirstPartSize = FirstPartSize, + Version = Version, + FrameWidth = FrameWidth, + FrameHeight = FrameHeight, + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Types/Vp9/BackwardUpdates.cs b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/BackwardUpdates.cs new file mode 100644 index 00000000..20e81513 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/BackwardUpdates.cs @@ -0,0 +1,72 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Video; + +namespace Ryujinx.Graphics.Nvdec.Types.Vp9 +{ + struct BackwardUpdates + { + public Array7>> InterModeCounts; + public Array4> YModeCounts; + public Array10> UvModeCounts; + public Array16> PartitionCounts; + public Array4> SwitchableInterpsCount; + public Array4> IntraInterCount; + public Array5> CompInterCount; + public Array5>> SingleRefCount; + public Array5> CompRefCount; + public Array2> Tx32x32; + public Array2> Tx16x16; + public Array2> Tx8x8; + public Array3> MbSkipCount; + public Array4 Joints; + public Array2> Sign; + public Array2> Classes; + public Array2> Class0; + public Array2>> Bits; + public Array2>> Class0Fp; + public Array2> Fp; + public Array2> Class0Hp; + public Array2> Hp; + public Array4>>>>> CoefCounts; + public Array4>>>> EobCounts; + + public BackwardUpdates(ref Vp9BackwardUpdates counts) + { + InterModeCounts = new Array7>>(); + + for (int i = 0; i < 7; i++) + { + InterModeCounts[i][0][0] = counts.InterMode[i][2]; + InterModeCounts[i][0][1] = counts.InterMode[i][0] + counts.InterMode[i][1] + counts.InterMode[i][3]; + InterModeCounts[i][1][0] = counts.InterMode[i][0]; + InterModeCounts[i][1][1] = counts.InterMode[i][1] + counts.InterMode[i][3]; + InterModeCounts[i][2][0] = counts.InterMode[i][1]; + InterModeCounts[i][2][1] = counts.InterMode[i][3]; + } + + YModeCounts = counts.YMode; + UvModeCounts = counts.UvMode; + PartitionCounts = counts.Partition; + SwitchableInterpsCount = counts.SwitchableInterp; + IntraInterCount = counts.IntraInter; + CompInterCount = counts.CompInter; + SingleRefCount = counts.SingleRef; + CompRefCount = counts.CompRef; + Tx32x32 = counts.Tx32x32; + Tx16x16 = counts.Tx16x16; + Tx8x8 = counts.Tx8x8; + MbSkipCount = counts.Skip; + Joints = counts.Joints; + Sign = counts.Sign; + Classes = counts.Classes; + Class0 = counts.Class0; + Bits = counts.Bits; + Class0Fp = counts.Class0Fp; + Fp = counts.Fp; + Class0Hp = counts.Class0Hp; + Hp = counts.Hp; + CoefCounts = counts.Coef; + EobCounts = counts.EobBranch; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Types/Vp9/EntropyProbs.cs b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/EntropyProbs.cs new file mode 100644 index 00000000..82a09866 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/EntropyProbs.cs @@ -0,0 +1,141 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Video; + +namespace Ryujinx.Graphics.Nvdec.Types.Vp9 +{ + struct EntropyProbs + { +#pragma warning disable CS0649 // Field is never assigned to + public Array10>> KfYModeProbE0ToE7; + public Array10> KfYModeProbE8; + public Array3 Padding384; + public Array7 SegTreeProbs; + public Array3 SegPredProbs; + public Array15 Padding391; + public Array10> KfUvModeProbE0ToE7; + public Array10 KfUvModeProbE8; + public Array6 Padding3FA; + public Array7> InterModeProb; + public Array4 IntraInterProb; + public Array10> UvModeProbE0ToE7; + public Array2> Tx8x8Prob; + public Array2> Tx16x16Prob; + public Array2> Tx32x32Prob; + public Array4 YModeProbE8; + public Array4> YModeProbE0ToE7; + public Array16> KfPartitionProb; + public Array16> PartitionProb; + public Array10 UvModeProbE8; + public Array4> SwitchableInterpProb; + public Array5 CompInterProb; + public Array4 SkipProbs; + public Array3 Joints; + public Array2 Sign; + public Array2> Class0; + public Array2> Fp; + public Array2 Class0Hp; + public Array2 Hp; + public Array2> Classes; + public Array2>> Class0Fp; + public Array2> Bits; + public Array5> SingleRefProb; + public Array5 CompRefProb; + public Array17 Padding58F; + public Array4>>>>> CoefProbs; +#pragma warning restore CS0649 + + public void Convert(ref Vp9EntropyProbs fc) + { + for (int i = 0; i < 10; i++) + { + for (int j = 0; j < 10; j++) + { + for (int k = 0; k < 9; k++) + { + fc.KfYModeProb[i][j][k] = k < 8 ? KfYModeProbE0ToE7[i][j][k] : KfYModeProbE8[i][j]; + } + } + } + + fc.SegTreeProb = SegTreeProbs; + fc.SegPredProb = SegPredProbs; + + for (int i = 0; i < 7; i++) + { + for (int j = 0; j < 3; j++) + { + fc.InterModeProb[i][j] = InterModeProb[i][j]; + } + } + + fc.IntraInterProb = IntraInterProb; + + for (int i = 0; i < 10; i++) + { + for (int j = 0; j < 9; j++) + { + fc.KfUvModeProb[i][j] = j < 8 ? KfUvModeProbE0ToE7[i][j] : KfUvModeProbE8[i]; + fc.UvModeProb[i][j] = j < 8 ? UvModeProbE0ToE7[i][j] : UvModeProbE8[i]; + } + } + + fc.Tx8x8Prob = Tx8x8Prob; + fc.Tx16x16Prob = Tx16x16Prob; + fc.Tx32x32Prob = Tx32x32Prob; + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 9; j++) + { + fc.YModeProb[i][j] = j < 8 ? YModeProbE0ToE7[i][j] : YModeProbE8[i]; + } + } + + for (int i = 0; i < 16; i++) + { + for (int j = 0; j < 3; j++) + { + fc.KfPartitionProb[i][j] = KfPartitionProb[i][j]; + fc.PartitionProb[i][j] = PartitionProb[i][j]; + } + } + + fc.SwitchableInterpProb = SwitchableInterpProb; + fc.CompInterProb = CompInterProb; + fc.SkipProb[0] = SkipProbs[0]; + fc.SkipProb[1] = SkipProbs[1]; + fc.SkipProb[2] = SkipProbs[2]; + fc.Joints = Joints; + fc.Sign = Sign; + fc.Class0 = Class0; + fc.Fp = Fp; + fc.Class0Hp = Class0Hp; + fc.Hp = Hp; + fc.Classes = Classes; + fc.Class0Fp = Class0Fp; + fc.Bits = Bits; + fc.SingleRefProb = SingleRefProb; + fc.CompRefProb = CompRefProb; + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 2; j++) + { + for (int k = 0; k < 2; k++) + { + for (int l = 0; l < 6; l++) + { + for (int m = 0; m < 6; m++) + { + for (int n = 0; n < 3; n++) + { + fc.CoefProbs[i][j][k][l][m][n] = CoefProbs[i][j][k][l][m][n]; + } + } + } + } + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameFlags.cs b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameFlags.cs new file mode 100644 index 00000000..ac4cc486 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameFlags.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Nvdec.Types.Vp9 +{ + enum FrameFlags : uint + { + IsKeyFrame = 1 << 0, + LastFrameIsKeyFrame = 1 << 1, + FrameSizeChanged = 1 << 2, + ErrorResilientMode = 1 << 3, + LastShowFrame = 1 << 4, + IntraOnly = 1 << 5, + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameSize.cs b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameSize.cs new file mode 100644 index 00000000..d41c7da1 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameSize.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Nvdec.Types.Vp9 +{ + struct FrameSize + { +#pragma warning disable CS0649 // Field is never assigned to + public ushort Width; + public ushort Height; + public ushort LumaPitch; + public ushort ChromaPitch; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameStats.cs b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameStats.cs new file mode 100644 index 00000000..47f17e4e --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameStats.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.Graphics.Nvdec.Types.Vp9 +{ + struct FrameStats + { +#pragma warning disable CS0649 // Field is never assigned to + public uint Unknown0; + public uint Unknown4; + public uint Pass2CycleCount; + public uint ErrorStatus; + public uint FrameStatusIntraCnt; + public uint FrameStatusInterCnt; + public uint FrameStatusSkipCtuCount; + public uint FrameStatusFwdMvxCnt; + public uint FrameStatusFwdMvyCnt; + public uint FrameStatusBwdMvxCnt; + public uint FrameStatusBwdMvyCnt; + public uint ErrorCtbPos; + public uint ErrorSlicePos; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Types/Vp9/LoopFilter.cs b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/LoopFilter.cs new file mode 100644 index 00000000..139bd87b --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/LoopFilter.cs @@ -0,0 +1,13 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Nvdec.Types.Vp9 +{ + struct LoopFilter + { +#pragma warning disable CS0649 // Field is never assigned to + public byte ModeRefDeltaEnabled; + public Array4 RefDeltas; + public Array2 ModeDeltas; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Types/Vp9/PictureInfo.cs b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/PictureInfo.cs new file mode 100644 index 00000000..24c18b94 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/PictureInfo.cs @@ -0,0 +1,87 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Video; + +namespace Ryujinx.Graphics.Nvdec.Types.Vp9 +{ + struct PictureInfo + { +#pragma warning disable CS0649 // Field is never assigned to + public Array12 Unknown0; + public uint BitstreamSize; + public uint IsEncrypted; + public uint Unknown38; + public uint Reserved3C; + public uint BlockLayout; // Not supported on T210 + public uint WorkBufferSizeShr8; + public FrameSize LastFrameSize; + public FrameSize GoldenFrameSize; + public FrameSize AltFrameSize; + public FrameSize CurrentFrameSize; + public FrameFlags Flags; + public Array4 RefFrameSignBias; + public byte FirstLevel; + public byte SharpnessLevel; + public byte BaseQIndex; + public byte YDcDeltaQ; + public byte UvAcDeltaQ; + public byte UvDcDeltaQ; + public byte Lossless; + public byte TxMode; + public byte AllowHighPrecisionMv; + public byte InterpFilter; + public byte ReferenceMode; + public sbyte CompFixedRef; + public Array2 CompVarRef; + public byte Log2TileCols; + public byte Log2TileRows; + public Segmentation Seg; + public LoopFilter Lf; + public byte PaddingEB; + public uint WorkBufferSizeShr8New; // Not supported on T210 + public uint SurfaceParams; // Not supported on T210 + public uint UnknownF4; + public uint UnknownF8; + public uint UnknownFC; +#pragma warning restore CS0649 + + public readonly uint BitDepth => (SurfaceParams >> 1) & 0xf; + + public Vp9PictureInfo Convert() + { + return new Vp9PictureInfo() + { + IsKeyFrame = Flags.HasFlag(FrameFlags.IsKeyFrame), + IntraOnly = Flags.HasFlag(FrameFlags.IntraOnly), + UsePrevInFindMvRefs = + !Flags.HasFlag(FrameFlags.ErrorResilientMode) && + !Flags.HasFlag(FrameFlags.FrameSizeChanged) && + !Flags.HasFlag(FrameFlags.IntraOnly) && + Flags.HasFlag(FrameFlags.LastShowFrame) && + !Flags.HasFlag(FrameFlags.LastFrameIsKeyFrame), + RefFrameSignBias = RefFrameSignBias, + BaseQIndex = BaseQIndex, + YDcDeltaQ = YDcDeltaQ, + UvDcDeltaQ = UvDcDeltaQ, + UvAcDeltaQ = UvAcDeltaQ, + Lossless = Lossless != 0, + TransformMode = TxMode, + AllowHighPrecisionMv = AllowHighPrecisionMv != 0, + InterpFilter = InterpFilter, + ReferenceMode = ReferenceMode, + CompFixedRef = CompFixedRef, + CompVarRef = CompVarRef, + Log2TileCols = Log2TileCols, + Log2TileRows = Log2TileRows, + SegmentEnabled = Seg.Enabled != 0, + SegmentMapUpdate = Seg.UpdateMap != 0, + SegmentMapTemporalUpdate = Seg.TemporalUpdate != 0, + SegmentAbsDelta = Seg.AbsDelta, + SegmentFeatureEnable = Seg.FeatureMask, + SegmentFeatureData = Seg.FeatureData, + ModeRefDeltaEnabled = Lf.ModeRefDeltaEnabled != 0, + RefDeltas = Lf.RefDeltas, + ModeDeltas = Lf.ModeDeltas, + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Types/Vp9/Segmentation.cs b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/Segmentation.cs new file mode 100644 index 00000000..098becc2 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Types/Vp9/Segmentation.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Nvdec.Types.Vp9 +{ + struct Segmentation + { +#pragma warning disable CS0649 // Field is never assigned to + public byte Enabled; + public byte UpdateMap; + public byte TemporalUpdate; + public byte AbsDelta; + public Array8 FeatureMask; + public Array8> FeatureData; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Vp8Decoder.cs b/src/Ryujinx.Graphics.Nvdec/Vp8Decoder.cs new file mode 100644 index 00000000..3d2543c4 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Vp8Decoder.cs @@ -0,0 +1,33 @@ +using Ryujinx.Graphics.Nvdec.FFmpeg.Vp8; +using Ryujinx.Graphics.Nvdec.Image; +using Ryujinx.Graphics.Nvdec.Types.Vp8; +using Ryujinx.Graphics.Video; +using System; + +namespace Ryujinx.Graphics.Nvdec +{ + static class Vp8Decoder + { + public static void Decode(NvdecDecoderContext context, ResourceManager rm, ref NvdecRegisters state) + { + PictureInfo pictureInfo = rm.MemoryManager.DeviceRead(state.SetDrvPicSetupOffset); + ReadOnlySpan bitstream = rm.MemoryManager.DeviceGetSpan(state.SetInBufBaseOffset, (int)pictureInfo.VLDBufferSize); + + Decoder decoder = context.GetVp8Decoder(); + + ISurface outputSurface = rm.Cache.Get(decoder, 0, 0, pictureInfo.FrameWidth, pictureInfo.FrameHeight); + + Vp8PictureInfo info = pictureInfo.Convert(); + + uint lumaOffset = state.SetPictureLumaOffset[3]; + uint chromaOffset = state.SetPictureChromaOffset[3]; + + if (decoder.Decode(ref info, outputSurface, bitstream)) + { + SurfaceWriter.Write(rm.MemoryManager, outputSurface, lumaOffset, chromaOffset); + } + + rm.Cache.Put(outputSurface); + } + } +} diff --git a/src/Ryujinx.Graphics.Nvdec/Vp9Decoder.cs b/src/Ryujinx.Graphics.Nvdec/Vp9Decoder.cs new file mode 100644 index 00000000..5ed50864 --- /dev/null +++ b/src/Ryujinx.Graphics.Nvdec/Vp9Decoder.cs @@ -0,0 +1,90 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Nvdec.Image; +using Ryujinx.Graphics.Nvdec.Types.Vp9; +using Ryujinx.Graphics.Nvdec.Vp9; +using Ryujinx.Graphics.Video; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Graphics.Nvdec.MemoryExtensions; + +namespace Ryujinx.Graphics.Nvdec +{ + static class Vp9Decoder + { + private static readonly Decoder _decoder = new(); + + public unsafe static void Decode(ResourceManager rm, ref NvdecRegisters state) + { + PictureInfo pictureInfo = rm.MemoryManager.DeviceRead(state.SetDrvPicSetupOffset); + EntropyProbs entropy = rm.MemoryManager.DeviceRead(state.Vp9SetProbTabBufOffset); + + ISurface Rent(uint lumaOffset, uint chromaOffset, FrameSize size) + { + return rm.Cache.Get(_decoder, lumaOffset, chromaOffset, size.Width, size.Height); + } + + ISurface lastSurface = Rent(state.SetPictureLumaOffset[0], state.SetPictureChromaOffset[0], pictureInfo.LastFrameSize); + ISurface goldenSurface = Rent(state.SetPictureLumaOffset[1], state.SetPictureChromaOffset[1], pictureInfo.GoldenFrameSize); + ISurface altSurface = Rent(state.SetPictureLumaOffset[2], state.SetPictureChromaOffset[2], pictureInfo.AltFrameSize); + ISurface currentSurface = Rent(state.SetPictureLumaOffset[3], state.SetPictureChromaOffset[3], pictureInfo.CurrentFrameSize); + + Vp9PictureInfo info = pictureInfo.Convert(); + + info.LastReference = lastSurface; + info.GoldenReference = goldenSurface; + info.AltReference = altSurface; + + entropy.Convert(ref info.Entropy); + + ReadOnlySpan bitstream = rm.MemoryManager.DeviceGetSpan(state.SetInBufBaseOffset, (int)pictureInfo.BitstreamSize); + + ReadOnlySpan mvsIn = ReadOnlySpan.Empty; + + if (info.UsePrevInFindMvRefs) + { + mvsIn = GetMvsInput(rm.MemoryManager, pictureInfo.CurrentFrameSize, state.Vp9SetColMvReadBufOffset); + } + + int miCols = BitUtils.DivRoundUp(pictureInfo.CurrentFrameSize.Width, 8); + int miRows = BitUtils.DivRoundUp(pictureInfo.CurrentFrameSize.Height, 8); + + using var mvsRegion = rm.MemoryManager.GetWritableRegion(ExtendOffset(state.Vp9SetColMvWriteBufOffset), miRows * miCols * 16); + + Span mvsOut = MemoryMarshal.Cast(mvsRegion.Memory.Span); + + uint lumaOffset = state.SetPictureLumaOffset[3]; + uint chromaOffset = state.SetPictureChromaOffset[3]; + + if (_decoder.Decode(ref info, currentSurface, bitstream, mvsIn, mvsOut)) + { + SurfaceWriter.Write(rm.MemoryManager, currentSurface, lumaOffset, chromaOffset); + } + + WriteBackwardUpdates(rm.MemoryManager, state.Vp9SetCtxCounterBufOffset, ref info.BackwardUpdateCounts); + + rm.Cache.Put(lastSurface); + rm.Cache.Put(goldenSurface); + rm.Cache.Put(altSurface); + rm.Cache.Put(currentSurface); + } + + private static ReadOnlySpan GetMvsInput(DeviceMemoryManager mm, FrameSize size, uint offset) + { + int miCols = BitUtils.DivRoundUp(size.Width, 8); + int miRows = BitUtils.DivRoundUp(size.Height, 8); + + return MemoryMarshal.Cast(mm.DeviceGetSpan(offset, miRows * miCols * 16)); + } + + private static void WriteBackwardUpdates(DeviceMemoryManager mm, uint offset, ref Vp9BackwardUpdates counts) + { + using var backwardUpdatesRegion = mm.GetWritableRegion(ExtendOffset(offset), Unsafe.SizeOf()); + + ref var backwardUpdates = ref MemoryMarshal.Cast(backwardUpdatesRegion.Memory.Span)[0]; + + backwardUpdates = new BackwardUpdates(ref counts); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs b/src/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs new file mode 100644 index 00000000..f22e0df5 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs @@ -0,0 +1,93 @@ +using Ryujinx.Common; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.OpenGL +{ + unsafe class BackgroundContextWorker : IDisposable + { + [ThreadStatic] + public static bool InBackground; + private readonly Thread _thread; + private bool _running; + + private readonly AutoResetEvent _signal; + private readonly Queue _work; + private readonly ObjectPool _invokePool; + private readonly IOpenGLContext _backgroundContext; + + public BackgroundContextWorker(IOpenGLContext backgroundContext) + { + _backgroundContext = backgroundContext; + _running = true; + + _signal = new AutoResetEvent(false); + _work = new Queue(); + _invokePool = new ObjectPool(() => new ManualResetEventSlim(), 10); + + _thread = new Thread(Run); + _thread.Start(); + } + + public bool HasContext() => _backgroundContext.HasContext(); + + private void Run() + { + InBackground = true; + + _backgroundContext.MakeCurrent(); + + while (_running) + { + Action action; + + lock (_work) + { + _work.TryDequeue(out action); + } + + if (action != null) + { + action(); + } + else + { + _signal.WaitOne(); + } + } + + _backgroundContext.Dispose(); + } + + public void Invoke(Action action) + { + ManualResetEventSlim actionComplete = _invokePool.Allocate(); + + lock (_work) + { + _work.Enqueue(() => + { + action(); + actionComplete.Set(); + }); + } + + _signal.Set(); + + actionComplete.Wait(); + actionComplete.Reset(); + + _invokePool.Release(actionComplete); + } + + public void Dispose() + { + _running = false; + _signal.Set(); + + _thread.Join(); + _signal.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Buffer.cs b/src/Ryujinx.Graphics.OpenGL/Buffer.cs new file mode 100644 index 00000000..2a514310 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Buffer.cs @@ -0,0 +1,121 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + static class Buffer + { + public static void Clear(BufferHandle destination, int offset, int size, uint value) + { + GL.BindBuffer(BufferTarget.CopyWriteBuffer, destination.ToInt32()); + + unsafe + { + uint* valueArr = stackalloc uint[1]; + + valueArr[0] = value; + + GL.ClearBufferSubData( + BufferTarget.CopyWriteBuffer, + PixelInternalFormat.Rgba8ui, + (IntPtr)offset, + (IntPtr)size, + PixelFormat.RgbaInteger, + PixelType.UnsignedByte, + (IntPtr)valueArr); + } + } + + public static BufferHandle Create() + { + return Handle.FromInt32(GL.GenBuffer()); + } + + public static BufferHandle Create(int size) + { + int handle = GL.GenBuffer(); + + GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle); + GL.BufferData(BufferTarget.CopyWriteBuffer, size, IntPtr.Zero, BufferUsageHint.DynamicDraw); + + return Handle.FromInt32(handle); + } + + public static BufferHandle CreatePersistent(int size) + { + int handle = GL.GenBuffer(); + + GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle); + GL.BufferStorage(BufferTarget.CopyWriteBuffer, size, IntPtr.Zero, + BufferStorageFlags.MapPersistentBit | + BufferStorageFlags.MapCoherentBit | + BufferStorageFlags.ClientStorageBit | + BufferStorageFlags.MapReadBit); + + return Handle.FromInt32(handle); + } + + public static void Copy(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size) + { + GL.BindBuffer(BufferTarget.CopyReadBuffer, source.ToInt32()); + GL.BindBuffer(BufferTarget.CopyWriteBuffer, destination.ToInt32()); + + GL.CopyBufferSubData( + BufferTarget.CopyReadBuffer, + BufferTarget.CopyWriteBuffer, + (IntPtr)srcOffset, + (IntPtr)dstOffset, + (IntPtr)size); + } + + public static unsafe PinnedSpan GetData(OpenGLRenderer renderer, BufferHandle buffer, int offset, int size) + { + // Data in the persistent buffer and host array is guaranteed to be available + // until the next time the host thread requests data. + + if (renderer.PersistentBuffers.TryGet(buffer, out IntPtr ptr)) + { + return new PinnedSpan(IntPtr.Add(ptr, offset).ToPointer(), size); + } + else if (HwCapabilities.UsePersistentBufferForFlush) + { + return PinnedSpan.UnsafeFromSpan(renderer.PersistentBuffers.Default.GetBufferData(buffer, offset, size)); + } + else + { + IntPtr target = renderer.PersistentBuffers.Default.GetHostArray(size); + + GL.BindBuffer(BufferTarget.CopyReadBuffer, buffer.ToInt32()); + + GL.GetBufferSubData(BufferTarget.CopyReadBuffer, (IntPtr)offset, size, target); + + return new PinnedSpan(target.ToPointer(), size); + } + } + + public static void Resize(BufferHandle handle, int size) + { + GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle.ToInt32()); + GL.BufferData(BufferTarget.CopyWriteBuffer, size, IntPtr.Zero, BufferUsageHint.StreamCopy); + } + + public static void SetData(BufferHandle buffer, int offset, ReadOnlySpan data) + { + GL.BindBuffer(BufferTarget.CopyWriteBuffer, buffer.ToInt32()); + + unsafe + { + fixed (byte* ptr = data) + { + GL.BufferSubData(BufferTarget.CopyWriteBuffer, (IntPtr)offset, data.Length, (IntPtr)ptr); + } + } + } + + public static void Delete(BufferHandle buffer) + { + GL.DeleteBuffer(buffer.ToInt32()); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Constants.cs b/src/Ryujinx.Graphics.OpenGL/Constants.cs new file mode 100644 index 00000000..e38808b7 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Constants.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.OpenGL +{ + static class Constants + { + public const int MaxRenderTargets = 8; + public const int MaxViewports = 16; + public const int MaxVertexAttribs = 16; + public const int MaxVertexBuffers = 16; + public const int MaxTransformFeedbackBuffers = 4; + public const int MaxSubgroupSize = 64; + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Debugger.cs b/src/Ryujinx.Graphics.OpenGL/Debugger.cs new file mode 100644 index 00000000..7606bdbf --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Debugger.cs @@ -0,0 +1,109 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Graphics.OpenGL +{ + public static class Debugger + { + private static DebugProc _debugCallback; + + private static int _counter; + + public static void Initialize(GraphicsDebugLevel logLevel) + { + // Disable everything + GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DontCare, DebugSeverityControl.DontCare, 0, (int[])null, false); + + if (logLevel == GraphicsDebugLevel.None) + { + GL.Disable(EnableCap.DebugOutputSynchronous); + GL.DebugMessageCallback(null, IntPtr.Zero); + + return; + } + + GL.Enable(EnableCap.DebugOutputSynchronous); + + if (logLevel == GraphicsDebugLevel.Error) + { + GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DebugTypeError, DebugSeverityControl.DontCare, 0, (int[])null, true); + } + else if (logLevel == GraphicsDebugLevel.Slowdowns) + { + GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DebugTypeError, DebugSeverityControl.DontCare, 0, (int[])null, true); + GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DebugTypePerformance, DebugSeverityControl.DontCare, 0, (int[])null, true); + } + else + { + GL.DebugMessageControl(DebugSourceControl.DontCare, DebugTypeControl.DontCare, DebugSeverityControl.DontCare, 0, (int[])null, true); + } + + _counter = 0; + _debugCallback = GLDebugHandler; + + GL.DebugMessageCallback(_debugCallback, IntPtr.Zero); + + Logger.Warning?.Print(LogClass.Gpu, "OpenGL Debugging is enabled. Performance will be negatively impacted."); + } + + private static void GLDebugHandler( + DebugSource source, + DebugType type, + int id, + DebugSeverity severity, + int length, + IntPtr message, + IntPtr userParam) + { + string msg = Marshal.PtrToStringUTF8(message).Replace('\n', ' '); + + switch (type) + { + case DebugType.DebugTypeError: + Logger.Error?.Print(LogClass.Gpu, $"{severity}: {msg}\nCallStack={Environment.StackTrace}", "GLERROR"); + break; + case DebugType.DebugTypePerformance: + Logger.Warning?.Print(LogClass.Gpu, $"{severity}: {msg}", "GLPERF"); + break; + case DebugType.DebugTypePushGroup: + Logger.Info?.Print(LogClass.Gpu, $"{{ ({id}) {severity}: {msg}", "GLINFO"); + break; + case DebugType.DebugTypePopGroup: + Logger.Info?.Print(LogClass.Gpu, $"}} ({id}) {severity}: {msg}", "GLINFO"); + break; + default: + if (source == DebugSource.DebugSourceApplication) + { + Logger.Info?.Print(LogClass.Gpu, $"{type} {severity}: {msg}", "GLINFO"); + } + else + { + Logger.Debug?.Print(LogClass.Gpu, $"{type} {severity}: {msg}", "GLDEBUG"); + } + break; + } + } + + // Useful debug helpers + public static void PushGroup(string dbgMsg) + { + int counter = Interlocked.Increment(ref _counter); + + GL.PushDebugGroup(DebugSourceExternal.DebugSourceApplication, counter, dbgMsg.Length, dbgMsg); + } + + public static void PopGroup() + { + GL.PopDebugGroup(); + } + + public static void Print(string dbgMsg, DebugType type = DebugType.DebugTypeMarker, DebugSeverity severity = DebugSeverity.DebugSeverityNotification, int id = 999999) + { + GL.DebugMessageInsert(DebugSourceExternal.DebugSourceApplication, type, id, severity, dbgMsg.Length, dbgMsg); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/DrawTextureEmulation.cs b/src/Ryujinx.Graphics.OpenGL/DrawTextureEmulation.cs new file mode 100644 index 00000000..7a6af95e --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/DrawTextureEmulation.cs @@ -0,0 +1,134 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.OpenGL.Image; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class DrawTextureEmulation + { + private const string VertexShader = @"#version 430 core + +uniform float srcX0; +uniform float srcY0; +uniform float srcX1; +uniform float srcY1; + +layout (location = 0) out vec2 texcoord; + +void main() +{ + bool x1 = (gl_VertexID & 1) != 0; + bool y1 = (gl_VertexID & 2) != 0; + gl_Position = vec4(x1 ? 1 : -1, y1 ? -1 : 1, 0, 1); + texcoord = vec2(x1 ? srcX1 : srcX0, y1 ? srcY1 : srcY0); +}"; + + private const string FragmentShader = @"#version 430 core + +layout (location = 0) uniform sampler2D tex; + +layout (location = 0) in vec2 texcoord; +layout (location = 0) out vec4 colour; + +void main() +{ + colour = texture(tex, texcoord); +}"; + + private int _vsHandle; + private int _fsHandle; + private int _programHandle; + private int _uniformSrcX0Location; + private int _uniformSrcY0Location; + private int _uniformSrcX1Location; + private int _uniformSrcY1Location; + private bool _initialized; + + public void Draw( + TextureView texture, + Sampler sampler, + float x0, + float y0, + float x1, + float y1, + float s0, + float t0, + float s1, + float t1) + { + EnsureInitialized(); + + GL.UseProgram(_programHandle); + + texture.Bind(0); + sampler.Bind(0); + + if (x0 > x1) + { + (s1, s0) = (s0, s1); + } + + if (y0 > y1) + { + (t1, t0) = (t0, t1); + } + + GL.Uniform1(_uniformSrcX0Location, s0); + GL.Uniform1(_uniformSrcY0Location, t0); + GL.Uniform1(_uniformSrcX1Location, s1); + GL.Uniform1(_uniformSrcY1Location, t1); + + GL.ViewportIndexed(0, MathF.Min(x0, x1), MathF.Min(y0, y1), MathF.Abs(x1 - x0), MathF.Abs(y1 - y0)); + + GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4); + } + + private void EnsureInitialized() + { + if (_initialized) + { + return; + } + + _initialized = true; + + _vsHandle = GL.CreateShader(ShaderType.VertexShader); + _fsHandle = GL.CreateShader(ShaderType.FragmentShader); + + GL.ShaderSource(_vsHandle, VertexShader); + GL.ShaderSource(_fsHandle, FragmentShader); + + GL.CompileShader(_vsHandle); + GL.CompileShader(_fsHandle); + + _programHandle = GL.CreateProgram(); + + GL.AttachShader(_programHandle, _vsHandle); + GL.AttachShader(_programHandle, _fsHandle); + + GL.LinkProgram(_programHandle); + + GL.DetachShader(_programHandle, _vsHandle); + GL.DetachShader(_programHandle, _fsHandle); + + _uniformSrcX0Location = GL.GetUniformLocation(_programHandle, "srcX0"); + _uniformSrcY0Location = GL.GetUniformLocation(_programHandle, "srcY0"); + _uniformSrcX1Location = GL.GetUniformLocation(_programHandle, "srcX1"); + _uniformSrcY1Location = GL.GetUniformLocation(_programHandle, "srcY1"); + } + + public void Dispose() + { + if (!_initialized) + { + return; + } + + GL.DeleteShader(_vsHandle); + GL.DeleteShader(_fsHandle); + GL.DeleteProgram(_programHandle); + + _initialized = false; + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs b/src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs new file mode 100644 index 00000000..1a130beb --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs @@ -0,0 +1,177 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.OpenGL.Image; +using System; +using static Ryujinx.Graphics.OpenGL.Effects.ShaderHelper; + +namespace Ryujinx.Graphics.OpenGL.Effects +{ + internal class FsrScalingFilter : IScalingFilter + { + private readonly OpenGLRenderer _renderer; + private int _inputUniform; + private int _outputUniform; + private int _sharpeningUniform; + private int _srcX0Uniform; + private int _srcX1Uniform; + private int _srcY0Uniform; + private int _scalingShaderProgram; + private int _sharpeningShaderProgram; + private float _scale = 1; + private int _srcY1Uniform; + private int _dstX0Uniform; + private int _dstX1Uniform; + private int _dstY0Uniform; + private int _dstY1Uniform; + private int _scaleXUniform; + private int _scaleYUniform; + private TextureStorage _intermediaryTexture; + + public float Level + { + get => _scale; + set + { + _scale = MathF.Max(0.01f, value); + } + } + + public FsrScalingFilter(OpenGLRenderer renderer) + { + Initialize(); + + _renderer = renderer; + } + + public void Dispose() + { + if (_scalingShaderProgram != 0) + { + GL.DeleteProgram(_scalingShaderProgram); + GL.DeleteProgram(_sharpeningShaderProgram); + } + + _intermediaryTexture?.Dispose(); + } + + private void Initialize() + { + var scalingShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl"); + var sharpeningShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_sharpening.glsl"); + var fsrA = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_a.h"); + var fsr1 = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_fsr1.h"); + + scalingShader = scalingShader.Replace("#include \"ffx_a.h\"", fsrA); + scalingShader = scalingShader.Replace("#include \"ffx_fsr1.h\"", fsr1); + sharpeningShader = sharpeningShader.Replace("#include \"ffx_a.h\"", fsrA); + sharpeningShader = sharpeningShader.Replace("#include \"ffx_fsr1.h\"", fsr1); + + _scalingShaderProgram = CompileProgram(scalingShader, ShaderType.ComputeShader); + _sharpeningShaderProgram = CompileProgram(sharpeningShader, ShaderType.ComputeShader); + + _inputUniform = GL.GetUniformLocation(_scalingShaderProgram, "Source"); + _outputUniform = GL.GetUniformLocation(_scalingShaderProgram, "imgOutput"); + _sharpeningUniform = GL.GetUniformLocation(_sharpeningShaderProgram, "sharpening"); + + _srcX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX0"); + _srcX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX1"); + _srcY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY0"); + _srcY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY1"); + _dstX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX0"); + _dstX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX1"); + _dstY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY0"); + _dstY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY1"); + _scaleXUniform = GL.GetUniformLocation(_scalingShaderProgram, "scaleX"); + _scaleYUniform = GL.GetUniformLocation(_scalingShaderProgram, "scaleY"); + } + + public void Run( + TextureView view, + TextureView destinationTexture, + int width, + int height, + Extents2D source, + Extents2D destination) + { + if (_intermediaryTexture == null || _intermediaryTexture.Info.Width != width || _intermediaryTexture.Info.Height != height) + { + _intermediaryTexture?.Dispose(); + var originalInfo = view.Info; + var info = new TextureCreateInfo(width, + height, + originalInfo.Depth, + originalInfo.Levels, + originalInfo.Samples, + originalInfo.BlockWidth, + originalInfo.BlockHeight, + originalInfo.BytesPerPixel, + originalInfo.Format, + originalInfo.DepthStencilMode, + originalInfo.Target, + originalInfo.SwizzleR, + originalInfo.SwizzleG, + originalInfo.SwizzleB, + originalInfo.SwizzleA); + + _intermediaryTexture = new TextureStorage(_renderer, info); + _intermediaryTexture.CreateDefaultView(); + } + + var textureView = _intermediaryTexture.CreateView(_intermediaryTexture.Info, 0, 0) as TextureView; + + int previousProgram = GL.GetInteger(GetPName.CurrentProgram); + int previousUnit = GL.GetInteger(GetPName.ActiveTexture); + GL.ActiveTexture(TextureUnit.Texture0); + int previousTextureBinding = GL.GetInteger(GetPName.TextureBinding2D); + + GL.BindImageTexture(0, textureView.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8); + + int threadGroupWorkRegionDim = 16; + int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; + int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; + + // Scaling pass + float srcWidth = Math.Abs(source.X2 - source.X1); + float srcHeight = Math.Abs(source.Y2 - source.Y1); + float scaleX = srcWidth / view.Width; + float scaleY = srcHeight / view.Height; + GL.UseProgram(_scalingShaderProgram); + view.Bind(0); + GL.Uniform1(_inputUniform, 0); + GL.Uniform1(_outputUniform, 0); + GL.Uniform1(_srcX0Uniform, (float)source.X1); + GL.Uniform1(_srcX1Uniform, (float)source.X2); + GL.Uniform1(_srcY0Uniform, (float)source.Y1); + GL.Uniform1(_srcY1Uniform, (float)source.Y2); + GL.Uniform1(_dstX0Uniform, (float)destination.X1); + GL.Uniform1(_dstX1Uniform, (float)destination.X2); + GL.Uniform1(_dstY0Uniform, (float)destination.Y1); + GL.Uniform1(_dstY1Uniform, (float)destination.Y2); + GL.Uniform1(_scaleXUniform, scaleX); + GL.Uniform1(_scaleYUniform, scaleY); + GL.DispatchCompute(dispatchX, dispatchY, 1); + + GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit); + + // Sharpening Pass + GL.UseProgram(_sharpeningShaderProgram); + GL.BindImageTexture(0, destinationTexture.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8); + textureView.Bind(0); + GL.Uniform1(_inputUniform, 0); + GL.Uniform1(_outputUniform, 0); + GL.Uniform1(_sharpeningUniform, 1.5f - (Level * 0.01f * 1.5f)); + GL.DispatchCompute(dispatchX, dispatchY, 1); + + GL.UseProgram(previousProgram); + GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit); + + (_renderer.Pipeline as Pipeline).RestoreImages1And2(); + + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding); + + GL.ActiveTexture((TextureUnit)previousUnit); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/FxaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.OpenGL/Effects/FxaaPostProcessingEffect.cs new file mode 100644 index 00000000..4e92efe6 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/FxaaPostProcessingEffect.cs @@ -0,0 +1,81 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common; +using Ryujinx.Graphics.OpenGL.Image; + +namespace Ryujinx.Graphics.OpenGL.Effects +{ + internal class FxaaPostProcessingEffect : IPostProcessingEffect + { + private readonly OpenGLRenderer _renderer; + private int _resolutionUniform; + private int _inputUniform; + private int _outputUniform; + private int _shaderProgram; + private TextureStorage _textureStorage; + + public FxaaPostProcessingEffect(OpenGLRenderer renderer) + { + Initialize(); + + _renderer = renderer; + } + + public void Dispose() + { + if (_shaderProgram != 0) + { + GL.DeleteProgram(_shaderProgram); + _textureStorage?.Dispose(); + } + } + + private void Initialize() + { + _shaderProgram = ShaderHelper.CompileProgram(EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/fxaa.glsl"), ShaderType.ComputeShader); + + _resolutionUniform = GL.GetUniformLocation(_shaderProgram, "invResolution"); + _inputUniform = GL.GetUniformLocation(_shaderProgram, "inputTexture"); + _outputUniform = GL.GetUniformLocation(_shaderProgram, "imgOutput"); + } + + public TextureView Run(TextureView view, int width, int height) + { + if (_textureStorage == null || _textureStorage.Info.Width != view.Width || _textureStorage.Info.Height != view.Height) + { + _textureStorage?.Dispose(); + _textureStorage = new TextureStorage(_renderer, view.Info); + _textureStorage.CreateDefaultView(); + } + + var textureView = _textureStorage.CreateView(view.Info, 0, 0) as TextureView; + + int previousProgram = GL.GetInteger(GetPName.CurrentProgram); + int previousUnit = GL.GetInteger(GetPName.ActiveTexture); + GL.ActiveTexture(TextureUnit.Texture0); + int previousTextureBinding = GL.GetInteger(GetPName.TextureBinding2D); + + GL.BindImageTexture(0, textureView.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8); + GL.UseProgram(_shaderProgram); + + var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize); + var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize); + + view.Bind(0); + GL.Uniform1(_inputUniform, 0); + GL.Uniform1(_outputUniform, 0); + GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height); + GL.DispatchCompute(dispatchX, dispatchY, 1); + GL.UseProgram(previousProgram); + GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit); + + (_renderer.Pipeline as Pipeline).RestoreImages1And2(); + + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding); + + GL.ActiveTexture((TextureUnit)previousUnit); + + return textureView; + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/IPostProcessingEffect.cs b/src/Ryujinx.Graphics.OpenGL/Effects/IPostProcessingEffect.cs new file mode 100644 index 00000000..154e7492 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/IPostProcessingEffect.cs @@ -0,0 +1,11 @@ +using Ryujinx.Graphics.OpenGL.Image; +using System; + +namespace Ryujinx.Graphics.OpenGL.Effects +{ + internal interface IPostProcessingEffect : IDisposable + { + const int LocalGroupSize = 64; + TextureView Run(TextureView view, int width, int height); + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/IScalingFilter.cs b/src/Ryujinx.Graphics.OpenGL/Effects/IScalingFilter.cs new file mode 100644 index 00000000..b5c80878 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/IScalingFilter.cs @@ -0,0 +1,18 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.OpenGL.Image; +using System; + +namespace Ryujinx.Graphics.OpenGL.Effects +{ + internal interface IScalingFilter : IDisposable + { + float Level { get; set; } + void Run( + TextureView view, + TextureView destinationTexture, + int width, + int height, + Extents2D source, + Extents2D destination); + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs b/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs new file mode 100644 index 00000000..c25fe5b2 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs @@ -0,0 +1,39 @@ +using OpenTK.Graphics.OpenGL; + +namespace Ryujinx.Graphics.OpenGL.Effects +{ + internal static class ShaderHelper + { + public static int CompileProgram(string shaderCode, ShaderType shaderType) + { + var shader = GL.CreateShader(shaderType); + GL.ShaderSource(shader, shaderCode); + GL.CompileShader(shader); + + var program = GL.CreateProgram(); + GL.AttachShader(program, shader); + GL.LinkProgram(program); + + GL.DetachShader(program, shader); + GL.DeleteShader(shader); + + return program; + } + + public static int CompileProgram(string[] shaders, ShaderType shaderType) + { + var shader = GL.CreateShader(shaderType); + GL.ShaderSource(shader, shaders.Length, shaders, (int[])null); + GL.CompileShader(shader); + + var program = GL.CreateProgram(); + GL.AttachShader(program, shader); + GL.LinkProgram(program); + + GL.DetachShader(program, shader); + GL.DeleteShader(shader); + + return program; + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_a.h b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_a.h new file mode 100644 index 00000000..d04bff55 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_a.h @@ -0,0 +1,2656 @@ +//============================================================================================================================== +// +// [A] SHADER PORTABILITY 1.20210629 +// +//============================================================================================================================== +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +// MIT LICENSE +// =========== +// Copyright (c) 2014 Michal Drobot (for concepts used in "FLOAT APPROXIMATIONS"). +// ----------- +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// ----------- +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +// Software. +// ----------- +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +// ABOUT +// ===== +// Common central point for high-level shading language and C portability for various shader headers. +//------------------------------------------------------------------------------------------------------------------------------ +// DEFINES +// ======= +// A_CPU ..... Include the CPU related code. +// A_GPU ..... Include the GPU related code. +// A_GLSL .... Using GLSL. +// A_HLSL .... Using HLSL. +// A_HLSL_6_2 Using HLSL 6.2 with new 'uint16_t' and related types (requires '-enable-16bit-types'). +// A_NO_16_BIT_CAST Don't use instructions that are not availabe in SPIR-V (needed for running A_HLSL_6_2 on Vulkan) +// A_GCC ..... Using a GCC compatible compiler (else assume MSVC compatible compiler by default). +// ======= +// A_BYTE .... Support 8-bit integer. +// A_HALF .... Support 16-bit integer and floating point. +// A_LONG .... Support 64-bit integer. +// A_DUBL .... Support 64-bit floating point. +// ======= +// A_WAVE .... Support wave-wide operations. +//------------------------------------------------------------------------------------------------------------------------------ +// To get #include "ffx_a.h" working in GLSL use '#extension GL_GOOGLE_include_directive:require'. +//------------------------------------------------------------------------------------------------------------------------------ +// SIMPLIFIED TYPE SYSTEM +// ====================== +// - All ints will be unsigned with exception of when signed is required. +// - Type naming simplified and shortened "A<#components>", +// - H = 16-bit float (half) +// - F = 32-bit float (float) +// - D = 64-bit float (double) +// - P = 1-bit integer (predicate, not using bool because 'B' is used for byte) +// - B = 8-bit integer (byte) +// - W = 16-bit integer (word) +// - U = 32-bit integer (unsigned) +// - L = 64-bit integer (long) +// - Using "AS<#components>" for signed when required. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure 'ALerp*(a,b,m)' does 'b*m+(-a*m+a)' (2 ops). +//------------------------------------------------------------------------------------------------------------------------------ +// CHANGE LOG +// ========== +// 20200914 - Expanded wave ops and prx code. +// 20200713 - Added [ZOL] section, fixed serious bugs in sRGB and Rec.709 color conversion code, etc. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COMMON +//============================================================================================================================== +#define A_2PI 6.28318530718 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// CPU +// +// +//============================================================================================================================== +#ifdef A_CPU + // Supporting user defined overrides. + #ifndef A_RESTRICT + #define A_RESTRICT __restrict + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifndef A_STATIC + #define A_STATIC static + #endif +//------------------------------------------------------------------------------------------------------------------------------ + // Same types across CPU and GPU. + // Predicate uses 32-bit integer (C friendly bool). + typedef uint32_t AP1; + typedef float AF1; + typedef double AD1; + typedef uint8_t AB1; + typedef uint16_t AW1; + typedef uint32_t AU1; + typedef uint64_t AL1; + typedef int8_t ASB1; + typedef int16_t ASW1; + typedef int32_t ASU1; + typedef int64_t ASL1; +//------------------------------------------------------------------------------------------------------------------------------ + #define AD1_(a) ((AD1)(a)) + #define AF1_(a) ((AF1)(a)) + #define AL1_(a) ((AL1)(a)) + #define AU1_(a) ((AU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1_(a) ((ASL1)(a)) + #define ASU1_(a) ((ASU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AU1 AU1_AF1(AF1 a){union{AF1 f;AU1 u;}bits;bits.f=a;return bits.u;} +//------------------------------------------------------------------------------------------------------------------------------ + #define A_TRUE 1 + #define A_FALSE 0 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// CPU/GPU PORTING +// +//------------------------------------------------------------------------------------------------------------------------------ +// Get CPU and GPU to share all setup code, without duplicate code paths. +// This uses a lower-case prefix for special vector constructs. +// - In C restrict pointers are used. +// - In the shading language, in/inout/out arguments are used. +// This depends on the ability to access a vector value in both languages via array syntax (aka color[2]). +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD1 *A_RESTRICT + #define retAD3 AD1 *A_RESTRICT + #define retAD4 AD1 *A_RESTRICT + #define retAF2 AF1 *A_RESTRICT + #define retAF3 AF1 *A_RESTRICT + #define retAF4 AF1 *A_RESTRICT + #define retAL2 AL1 *A_RESTRICT + #define retAL3 AL1 *A_RESTRICT + #define retAL4 AL1 *A_RESTRICT + #define retAU2 AU1 *A_RESTRICT + #define retAU3 AU1 *A_RESTRICT + #define retAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 AD1 *A_RESTRICT + #define inAD3 AD1 *A_RESTRICT + #define inAD4 AD1 *A_RESTRICT + #define inAF2 AF1 *A_RESTRICT + #define inAF3 AF1 *A_RESTRICT + #define inAF4 AF1 *A_RESTRICT + #define inAL2 AL1 *A_RESTRICT + #define inAL3 AL1 *A_RESTRICT + #define inAL4 AL1 *A_RESTRICT + #define inAU2 AU1 *A_RESTRICT + #define inAU3 AU1 *A_RESTRICT + #define inAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 AD1 *A_RESTRICT + #define inoutAD3 AD1 *A_RESTRICT + #define inoutAD4 AD1 *A_RESTRICT + #define inoutAF2 AF1 *A_RESTRICT + #define inoutAF3 AF1 *A_RESTRICT + #define inoutAF4 AF1 *A_RESTRICT + #define inoutAL2 AL1 *A_RESTRICT + #define inoutAL3 AL1 *A_RESTRICT + #define inoutAL4 AL1 *A_RESTRICT + #define inoutAU2 AU1 *A_RESTRICT + #define inoutAU3 AU1 *A_RESTRICT + #define inoutAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 AD1 *A_RESTRICT + #define outAD3 AD1 *A_RESTRICT + #define outAD4 AD1 *A_RESTRICT + #define outAF2 AF1 *A_RESTRICT + #define outAF3 AF1 *A_RESTRICT + #define outAF4 AF1 *A_RESTRICT + #define outAL2 AL1 *A_RESTRICT + #define outAL3 AL1 *A_RESTRICT + #define outAL4 AL1 *A_RESTRICT + #define outAU2 AU1 *A_RESTRICT + #define outAU3 AU1 *A_RESTRICT + #define outAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD1 x[2] + #define varAD3(x) AD1 x[3] + #define varAD4(x) AD1 x[4] + #define varAF2(x) AF1 x[2] + #define varAF3(x) AF1 x[3] + #define varAF4(x) AF1 x[4] + #define varAL2(x) AL1 x[2] + #define varAL3(x) AL1 x[3] + #define varAL4(x) AL1 x[4] + #define varAU2(x) AU1 x[2] + #define varAU3(x) AU1 x[3] + #define varAU4(x) AU1 x[4] +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) {x,y} + #define initAD3(x,y,z) {x,y,z} + #define initAD4(x,y,z,w) {x,y,z,w} + #define initAF2(x,y) {x,y} + #define initAF3(x,y,z) {x,y,z} + #define initAF4(x,y,z,w) {x,y,z,w} + #define initAL2(x,y) {x,y} + #define initAL3(x,y,z) {x,y,z} + #define initAL4(x,y,z,w) {x,y,z,w} + #define initAU2(x,y) {x,y} + #define initAU3(x,y,z) {x,y,z} + #define initAU4(x,y,z,w) {x,y,z,w} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Replace transcendentals with manual versions. +//============================================================================================================================== + #ifdef A_GCC + A_STATIC AD1 AAbsD1(AD1 a){return __builtin_fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return __builtin_fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(__builtin_abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(__builtin_llabs(ASL1_(a)));} + #else + A_STATIC AD1 AAbsD1(AD1 a){return fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(labs((long)ASL1_(a)));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ACosD1(AD1 a){return __builtin_cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return __builtin_cosf(a);} + #else + A_STATIC AD1 ACosD1(AD1 a){return cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return cosf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ADotD2(inAD2 a,inAD2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AD1 ADotD3(inAD3 a,inAD3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AD1 ADotD4(inAD4 a,inAD4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} + A_STATIC AF1 ADotF2(inAF2 a,inAF2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AF1 ADotF3(inAF3 a,inAF3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AF1 ADotF4(inAF4 a,inAF4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AExp2D1(AD1 a){return __builtin_exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return __builtin_exp2f(a);} + #else + A_STATIC AD1 AExp2D1(AD1 a){return exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return exp2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AFloorD1(AD1 a){return __builtin_floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return __builtin_floorf(a);} + #else + A_STATIC AD1 AFloorD1(AD1 a){return floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return floorf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ALerpD1(AD1 a,AD1 b,AD1 c){return b*c+(-a*c+a);} + A_STATIC AF1 ALerpF1(AF1 a,AF1 b,AF1 c){return b*c+(-a*c+a);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ALog2D1(AD1 a){return __builtin_log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return __builtin_log2f(a);} + #else + A_STATIC AD1 ALog2D1(AD1 a){return log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return log2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMaxD1(AD1 a,AD1 b){return a>b?a:b;} + A_STATIC AF1 AMaxF1(AF1 a,AF1 b){return a>b?a:b;} + A_STATIC AL1 AMaxL1(AL1 a,AL1 b){return a>b?a:b;} + A_STATIC AU1 AMaxU1(AU1 a,AU1 b){return a>b?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + // These follow the convention that A integer types don't have signage, until they are operated on. + A_STATIC AL1 AMaxSL1(AL1 a,AL1 b){return (ASL1_(a)>ASL1_(b))?a:b;} + A_STATIC AU1 AMaxSU1(AU1 a,AU1 b){return (ASU1_(a)>ASU1_(b))?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMinD1(AD1 a,AD1 b){return a>ASL1_(b));} + A_STATIC AU1 AShrSU1(AU1 a,AU1 b){return AU1_(ASU1_(a)>>ASU1_(b));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASinD1(AD1 a){return __builtin_sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return __builtin_sinf(a);} + #else + A_STATIC AD1 ASinD1(AD1 a){return sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return sinf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASqrtD1(AD1 a){return __builtin_sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return __builtin_sqrtf(a);} + #else + A_STATIC AD1 ASqrtD1(AD1 a){return sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return sqrtf(a);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + A_STATIC AD1 AClampD1(AD1 x,AD1 n,AD1 m){return AMaxD1(n,AMinD1(x,m));} + A_STATIC AF1 AClampF1(AF1 x,AF1 n,AF1 m){return AMaxF1(n,AMinF1(x,m));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AFractD1(AD1 a){return a-AFloorD1(a);} + A_STATIC AF1 AFractF1(AF1 a){return a-AFloorF1(a);} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 APowD1(AD1 a,AD1 b){return AExp2D1(b*ALog2D1(a));} + A_STATIC AF1 APowF1(AF1 a,AF1 b){return AExp2F1(b*ALog2F1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ARsqD1(AD1 a){return ARcpD1(ASqrtD1(a));} + A_STATIC AF1 ARsqF1(AF1 a){return ARcpF1(ASqrtF1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ASatD1(AD1 a){return AMinD1(1.0,AMaxD1(0.0,a));} + A_STATIC AF1 ASatF1(AF1 a){return AMinF1(1.0f,AMaxF1(0.0f,a));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + A_STATIC retAD2 opAAbsD2(outAD2 d,inAD2 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);return d;} + A_STATIC retAD3 opAAbsD3(outAD3 d,inAD3 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);return d;} + A_STATIC retAD4 opAAbsD4(outAD4 d,inAD4 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);d[3]=AAbsD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAbsF2(outAF2 d,inAF2 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);return d;} + A_STATIC retAF3 opAAbsF3(outAF3 d,inAF3 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);return d;} + A_STATIC retAF4 opAAbsF4(outAF4 d,inAF4 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);d[3]=AAbsF1(a[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} + A_STATIC retAD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} + A_STATIC retAD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} + A_STATIC retAF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} + A_STATIC retAF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opACpyD2(outAD2 d,inAD2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAD3 opACpyD3(outAD3 d,inAD3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAD4 opACpyD4(outAD4 d,inAD4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opACpyF2(outAF2 d,inAF2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAF3 opACpyF3(outAF3 d,inAF3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAF4 opACpyF4(outAF4 d,inAF4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);return d;} + A_STATIC retAD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);return d;} + A_STATIC retAD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);d[3]=ALerpD1(a[3],b[3],c[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);return d;} + A_STATIC retAF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);return d;} + A_STATIC retAF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);d[3]=ALerpF1(a[3],b[3],c[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);return d;} + A_STATIC retAD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);return d;} + A_STATIC retAD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);d[3]=ALerpD1(a[3],b[3],c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);return d;} + A_STATIC retAF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);return d;} + A_STATIC retAF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);d[3]=ALerpF1(a[3],b[3],c);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);d[3]=AMaxD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);d[3]=AMaxF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);d[3]=AMinD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);d[3]=AMinF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opANegD2(outAD2 d,inAD2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAD3 opANegD3(outAD3 d,inAD3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAD4 opANegD4(outAD4 d,inAD4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opANegF2(outAF2 d,inAF2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAF3 opANegF3(outAF3 d,inAF3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAF4 opANegF4(outAF4 d,inAF4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opARcpD2(outAD2 d,inAD2 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);return d;} + A_STATIC retAD3 opARcpD3(outAD3 d,inAD3 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);return d;} + A_STATIC retAD4 opARcpD4(outAD4 d,inAD4 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);d[3]=ARcpD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opARcpF2(outAF2 d,inAF2 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);return d;} + A_STATIC retAF3 opARcpF3(outAF3 d,inAF3 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);return d;} + A_STATIC retAF4 opARcpF4(outAF4 d,inAF4 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);d[3]=ARcpF1(a[3]);return d;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF FLOAT PACKING +//============================================================================================================================== + // Convert float to half (in lower 16-bits of output). + // Same fast technique as documented here: ftp://ftp.fox-toolkit.org/pub/fasthalffloatconversion.pdf + // Supports denormals. + // Conversion rules are to make computations possibly "safer" on the GPU, + // -INF & -NaN -> -65504 + // +INF & +NaN -> +65504 + A_STATIC AU1 AU1_AH1_AF1(AF1 f){ + static AW1 base[512]={ + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0001,0x0002,0x0004,0x0008,0x0010,0x0020,0x0040,0x0080,0x0100, + 0x0200,0x0400,0x0800,0x0c00,0x1000,0x1400,0x1800,0x1c00,0x2000,0x2400,0x2800,0x2c00,0x3000,0x3400,0x3800,0x3c00, + 0x4000,0x4400,0x4800,0x4c00,0x5000,0x5400,0x5800,0x5c00,0x6000,0x6400,0x6800,0x6c00,0x7000,0x7400,0x7800,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8001,0x8002,0x8004,0x8008,0x8010,0x8020,0x8040,0x8080,0x8100, + 0x8200,0x8400,0x8800,0x8c00,0x9000,0x9400,0x9800,0x9c00,0xa000,0xa400,0xa800,0xac00,0xb000,0xb400,0xb800,0xbc00, + 0xc000,0xc400,0xc800,0xcc00,0xd000,0xd400,0xd800,0xdc00,0xe000,0xe400,0xe800,0xec00,0xf000,0xf400,0xf800,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff}; + static AB1 shift[512]={ + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18}; + union{AF1 f;AU1 u;}bits;bits.f=f;AU1 u=bits.u;AU1 i=u>>23;return (AU1)(base[i])+((u&0x7fffff)>>shift[i]);} +//------------------------------------------------------------------------------------------------------------------------------ + // Used to output packed constant. + A_STATIC AU1 AU1_AH2_AF2(inAF2 a){return AU1_AH1_AF1(a[0])+(AU1_AH1_AF1(a[1])<<16);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GLSL +// +// +//============================================================================================================================== +#if defined(A_GLSL) && defined(A_GPU) + #ifndef A_SKIP_EXT + #ifdef A_HALF + #extension GL_EXT_shader_16bit_storage:require + #extension GL_EXT_shader_explicit_arithmetic_types:require + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_LONG + #extension GL_ARB_gpu_shader_int64:require + #extension GL_NV_shader_atomic_int64:require + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_WAVE + #extension GL_KHR_shader_subgroup_arithmetic:require + #extension GL_KHR_shader_subgroup_ballot:require + #extension GL_KHR_shader_subgroup_quad:require + #extension GL_KHR_shader_subgroup_shuffle:require + #endif + #endif +//============================================================================================================================== + #define AP1 bool + #define AP2 bvec2 + #define AP3 bvec3 + #define AP4 bvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 vec2 + #define AF3 vec3 + #define AF4 vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uvec2 + #define AU3 uvec3 + #define AU4 uvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 ivec2 + #define ASU3 ivec3 + #define ASU4 ivec4 +//============================================================================================================================== + #define AF1_AU1(x) uintBitsToFloat(AU1(x)) + #define AF2_AU2(x) uintBitsToFloat(AU2(x)) + #define AF3_AU3(x) uintBitsToFloat(AU3(x)) + #define AF4_AU4(x) uintBitsToFloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) floatBitsToUint(AF1(x)) + #define AU2_AF2(x) floatBitsToUint(AF2(x)) + #define AU3_AF3(x) floatBitsToUint(AF3(x)) + #define AU4_AF4(x) floatBitsToUint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH1_AF1_x(AF1 a){return packHalf2x16(AF2(a,0.0));} + #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2_AF2 packHalf2x16 + #define AU1_AW2Unorm_AF2 packUnorm2x16 + #define AU1_AB4Unorm_AF4 packUnorm4x8 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF2_AH2_AU1 unpackHalf2x16 + #define AF2_AW2Unorm_AU1 unpackUnorm2x16 + #define AF4_AB4Unorm_AU1 unpackUnorm4x8 +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){return bitfieldExtract(src,ASU1(off),ASU1(bits));} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + // Proxy for V_BFI_B32 where the 'mask' is set as 'bits', 'mask=(1<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #define AB1 uint8_t + #define AB2 u8vec2 + #define AB3 u8vec3 + #define AB4 u8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASB1 int8_t + #define ASB2 i8vec2 + #define ASB3 i8vec3 + #define ASB4 i8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + AB1 AB1_x(AB1 a){return AB1(a);} + AB2 AB2_x(AB1 a){return AB2(a,a);} + AB3 AB3_x(AB1 a){return AB3(a,a,a);} + AB4 AB4_x(AB1 a){return AB4(a,a,a,a);} + #define AB1_(a) AB1_x(AB1(a)) + #define AB2_(a) AB2_x(AB1(a)) + #define AB3_(a) AB3_x(AB1(a)) + #define AB4_(a) AB4_x(AB1(a)) + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #define AH1 float16_t + #define AH2 f16vec2 + #define AH3 f16vec3 + #define AH4 f16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 u16vec2 + #define AW3 u16vec3 + #define AW4 u16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 i16vec2 + #define ASW3 i16vec3 + #define ASW4 i16vec4 +//============================================================================================================================== + #define AH2_AU1(x) unpackFloat2x16(AU1(x)) + AH4 AH4_AU2_x(AU2 x){return AH4(unpackFloat2x16(x.x),unpackFloat2x16(x.y));} + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) unpackUint2x16(AU1(x)) + #define AW4_AU2(x) unpackUint4x16(pack64(AU2(x))) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2(x) packFloat2x16(AH2(x)) + AU2 AU2_AH4_x(AH4 x){return AU2(packFloat2x16(x.xy),packFloat2x16(x.zw));} + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) packUint2x16(AW2(x)) + #define AU2_AW4(x) unpack32(packUint4x16(AW4(x))) +//============================================================================================================================== + #define AW1_AH1(x) halfBitsToUint16(AH1(x)) + #define AW2_AH2(x) halfBitsToUint16(AH2(x)) + #define AW3_AH3(x) halfBitsToUint16(AH3(x)) + #define AW4_AH4(x) halfBitsToUint16(AH4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AH1_AW1(x) uint16BitsToHalf(AW1(x)) + #define AH2_AW2(x) uint16BitsToHalf(AW2(x)) + #define AH3_AW3(x) uint16BitsToHalf(AW3(x)) + #define AH4_AW4(x) uint16BitsToHalf(AW4(x)) +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AClampH1(AH1 x,AH1 n,AH1 m){return clamp(x,n,m);} + AH2 AClampH2(AH2 x,AH2 n,AH2 m){return clamp(x,n,m);} + AH3 AClampH3(AH3 x,AH3 n,AH3 m){return clamp(x,n,m);} + AH4 AClampH4(AH4 x,AH4 n,AH4 m){return clamp(x,n,m);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFractH1(AH1 x){return fract(x);} + AH2 AFractH2(AH2 x){return fract(x);} + AH3 AFractH3(AH3 x){return fract(x);} + AH4 AFractH4(AH4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return mix(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return mix(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return mix(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of max3. + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of min3. + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return AH1_(1.0)/x;} + AH2 ARcpH2(AH2 x){return AH2_(1.0)/x;} + AH3 ARcpH3(AH3 x){return AH3_(1.0)/x;} + AH4 ARcpH4(AH4 x){return AH4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return AH1_(1.0)/sqrt(x);} + AH2 ARsqH2(AH2 x){return AH2_(1.0)/sqrt(x);} + AH3 ARsqH3(AH3 x){return AH3_(1.0)/sqrt(x);} + AH4 ARsqH4(AH4 x){return AH4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return clamp(x,AH1_(0.0),AH1_(1.0));} + AH2 ASatH2(AH2 x){return clamp(x,AH2_(0.0),AH2_(1.0));} + AH3 ASatH3(AH3 x){return clamp(x,AH3_(0.0),AH3_(1.0));} + AH4 ASatH4(AH4 x){return clamp(x,AH4_(0.0),AH4_(1.0));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #define AD1 double + #define AD2 dvec2 + #define AD3 dvec3 + #define AD4 dvec4 +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 x){return fract(x);} + AD2 AFractD2(AD2 x){return fract(x);} + AD3 AFractD3(AD3 x){return fract(x);} + AD4 AFractD4(AD4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return mix(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return mix(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return mix(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return AD1_(1.0)/x;} + AD2 ARcpD2(AD2 x){return AD2_(1.0)/x;} + AD3 ARcpD3(AD3 x){return AD3_(1.0)/x;} + AD4 ARcpD4(AD4 x){return AD4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return AD1_(1.0)/sqrt(x);} + AD2 ARsqD2(AD2 x){return AD2_(1.0)/sqrt(x);} + AD3 ARsqD3(AD3 x){return AD3_(1.0)/sqrt(x);} + AD4 ARsqD4(AD4 x){return AD4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return clamp(x,AD1_(0.0),AD1_(1.0));} + AD2 ASatD2(AD2 x){return clamp(x,AD2_(0.0),AD2_(1.0));} + AD3 ASatD3(AD3 x){return clamp(x,AD3_(0.0),AD3_(1.0));} + AD4 ASatD4(AD4 x){return clamp(x,AD4_(0.0),AD4_(1.0));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL LONG +//============================================================================================================================== + #ifdef A_LONG + #define AL1 uint64_t + #define AL2 u64vec2 + #define AL3 u64vec3 + #define AL4 u64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1 int64_t + #define ASL2 i64vec2 + #define ASL3 i64vec3 + #define ASL4 i64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AL1_AU2(x) packUint2x32(AU2(x)) + #define AU2_AL1(x) unpackUint2x32(AL1(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AL1_x(AL1 a){return AL1(a);} + AL2 AL2_x(AL1 a){return AL2(a,a);} + AL3 AL3_x(AL1 a){return AL3(a,a,a);} + AL4 AL4_x(AL1 a){return AL4(a,a,a,a);} + #define AL1_(a) AL1_x(AL1(a)) + #define AL2_(a) AL2_x(AL1(a)) + #define AL3_(a) AL3_x(AL1(a)) + #define AL4_(a) AL4_x(AL1(a)) +//============================================================================================================================== + AL1 AAbsSL1(AL1 a){return AL1(abs(ASL1(a)));} + AL2 AAbsSL2(AL2 a){return AL2(abs(ASL2(a)));} + AL3 AAbsSL3(AL3 a){return AL3(abs(ASL3(a)));} + AL4 AAbsSL4(AL4 a){return AL4(abs(ASL4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMaxSL1(AL1 a,AL1 b){return AL1(max(ASU1(a),ASU1(b)));} + AL2 AMaxSL2(AL2 a,AL2 b){return AL2(max(ASU2(a),ASU2(b)));} + AL3 AMaxSL3(AL3 a,AL3 b){return AL3(max(ASU3(a),ASU3(b)));} + AL4 AMaxSL4(AL4 a,AL4 b){return AL4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMinSL1(AL1 a,AL1 b){return AL1(min(ASU1(a),ASU1(b)));} + AL2 AMinSL2(AL2 a,AL2 b){return AL2(min(ASU2(a),ASU2(b)));} + AL3 AMinSL3(AL3 a,AL3 b){return AL3(min(ASU3(a),ASU3(b)));} + AL4 AMinSL4(AL4 a,AL4 b){return AL4(min(ASU4(a),ASU4(b)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// WAVE OPERATIONS +//============================================================================================================================== + #ifdef A_WAVE + // Where 'x' must be a compile time literal. + AF1 AWaveXorF1(AF1 v,AU1 x){return subgroupShuffleXor(v,x);} + AF2 AWaveXorF2(AF2 v,AU1 x){return subgroupShuffleXor(v,x);} + AF3 AWaveXorF3(AF3 v,AU1 x){return subgroupShuffleXor(v,x);} + AF4 AWaveXorF4(AF4 v,AU1 x){return subgroupShuffleXor(v,x);} + AU1 AWaveXorU1(AU1 v,AU1 x){return subgroupShuffleXor(v,x);} + AU2 AWaveXorU2(AU2 v,AU1 x){return subgroupShuffleXor(v,x);} + AU3 AWaveXorU3(AU3 v,AU1 x){return subgroupShuffleXor(v,x);} + AU4 AWaveXorU4(AU4 v,AU1 x){return subgroupShuffleXor(v,x);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(subgroupShuffleXor(AU1_AH2(v),x));} + AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(subgroupShuffleXor(AU2_AH4(v),x));} + AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(subgroupShuffleXor(AU1_AW2(v),x));} + AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU2(subgroupShuffleXor(AU2_AW4(v),x));} + #endif + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// HLSL +// +// +//============================================================================================================================== +#if defined(A_HLSL) && defined(A_GPU) + #ifdef A_HLSL_6_2 + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float32_t + #define AF2 float32_t2 + #define AF3 float32_t3 + #define AF4 float32_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint32_t + #define AU2 uint32_t2 + #define AU3 uint32_t3 + #define AU4 uint32_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int32_t + #define ASU2 int32_t2 + #define ASU3 int32_t3 + #define ASU4 int32_t4 + #else + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 float2 + #define AF3 float3 + #define AF4 float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uint2 + #define AU3 uint3 + #define AU4 uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 int2 + #define ASU3 int3 + #define ASU4 int4 + #endif +//============================================================================================================================== + #define AF1_AU1(x) asfloat(AU1(x)) + #define AF2_AU2(x) asfloat(AU2(x)) + #define AF3_AU3(x) asfloat(AU3(x)) + #define AF4_AU4(x) asfloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) asuint(AF1(x)) + #define AU2_AF2(x) asuint(AF2(x)) + #define AU3_AF3(x) asuint(AF3(x)) + #define AU4_AF4(x) asuint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH1_AF1_x(AF1 a){return f32tof16(a);} + #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_AF2_x(AF2 a){return f32tof16(a.x)|(f32tof16(a.y)<<16);} + #define AU1_AH2_AF2(a) AU1_AH2_AF2_x(AF2(a)) + #define AU1_AB4Unorm_AF4(x) D3DCOLORtoUBYTE4(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AF2 AF2_AH2_AU1_x(AU1 x){return AF2(f16tof32(x&0xFFFF),f16tof32(x>>16));} + #define AF2_AH2_AU1(x) AF2_AH2_AU1_x(AU1(x)) +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){AU1 mask=(1u<>off)&mask;} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + AU1 ABfiM(AU1 src,AU1 ins,AU1 bits){AU1 mask=(1u<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #ifdef A_HLSL_6_2 + #define AH1 float16_t + #define AH2 float16_t2 + #define AH3 float16_t3 + #define AH4 float16_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 uint16_t2 + #define AW3 uint16_t3 + #define AW4 uint16_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 int16_t2 + #define ASW3 int16_t3 + #define ASW4 int16_t4 + #else + #define AH1 min16float + #define AH2 min16float2 + #define AH3 min16float3 + #define AH4 min16float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 min16uint + #define AW2 min16uint2 + #define AW3 min16uint3 + #define AW4 min16uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 min16int + #define ASW2 min16int2 + #define ASW3 min16int3 + #define ASW4 min16int4 + #endif +//============================================================================================================================== + // Need to use manual unpack to get optimal execution (don't use packed types in buffers directly). + // Unpack requires this pattern: https://gpuopen.com/first-steps-implementing-fp16/ + AH2 AH2_AU1_x(AU1 x){AF2 t=f16tof32(AU2(x&0xFFFF,x>>16));return AH2(t);} + AH4 AH4_AU2_x(AU2 x){return AH4(AH2_AU1_x(x.x),AH2_AU1_x(x.y));} + AW2 AW2_AU1_x(AU1 x){AU2 t=AU2(x&0xFFFF,x>>16);return AW2(t);} + AW4 AW4_AU2_x(AU2 x){return AW4(AW2_AU1_x(x.x),AW2_AU1_x(x.y));} + #define AH2_AU1(x) AH2_AU1_x(AU1(x)) + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) AW2_AU1_x(AU1(x)) + #define AW4_AU2(x) AW4_AU2_x(AU2(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_x(AH2 x){return f32tof16(x.x)+(f32tof16(x.y)<<16);} + AU2 AU2_AH4_x(AH4 x){return AU2(AU1_AH2_x(x.xy),AU1_AH2_x(x.zw));} + AU1 AU1_AW2_x(AW2 x){return AU1(x.x)+(AU1(x.y)<<16);} + AU2 AU2_AW4_x(AW4 x){return AU2(AU1_AW2_x(x.xy),AU1_AW2_x(x.zw));} + #define AU1_AH2(x) AU1_AH2_x(AH2(x)) + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) AU1_AW2_x(AW2(x)) + #define AU2_AW4(x) AU2_AW4_x(AW4(x)) +//============================================================================================================================== + #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) + #define AW1_AH1(x) asuint16(x) + #define AW2_AH2(x) asuint16(x) + #define AW3_AH3(x) asuint16(x) + #define AW4_AH4(x) asuint16(x) + #else + #define AW1_AH1(a) AW1(f32tof16(AF1(a))) + #define AW2_AH2(a) AW2(AW1_AH1((a).x),AW1_AH1((a).y)) + #define AW3_AH3(a) AW3(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z)) + #define AW4_AH4(a) AW4(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z),AW1_AH1((a).w)) + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) + #define AH1_AW1(x) asfloat16(x) + #define AH2_AW2(x) asfloat16(x) + #define AH3_AW3(x) asfloat16(x) + #define AH4_AW4(x) asfloat16(x) + #else + #define AH1_AW1(a) AH1(f16tof32(AU1(a))) + #define AH2_AW2(a) AH2(AH1_AW1((a).x),AH1_AW1((a).y)) + #define AH3_AW3(a) AH3(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z)) + #define AH4_AW4(a) AH4(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z),AH1_AW1((a).w)) + #endif +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AClampH1(AH1 x,AH1 n,AH1 m){return max(n,min(x,m));} + AH2 AClampH2(AH2 x,AH2 n,AH2 m){return max(n,min(x,m));} + AH3 AClampH3(AH3 x,AH3 n,AH3 m){return max(n,min(x,m));} + AH4 AClampH4(AH4 x,AH4 n,AH4 m){return max(n,min(x,m));} +//------------------------------------------------------------------------------------------------------------------------------ + // V_FRACT_F16 (note DX frac() is different). + AH1 AFractH1(AH1 x){return x-floor(x);} + AH2 AFractH2(AH2 x){return x-floor(x);} + AH3 AFractH3(AH3 x){return x-floor(x);} + AH4 AFractH4(AH4 x){return x-floor(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return lerp(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return lerp(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return lerp(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return rcp(x);} + AH2 ARcpH2(AH2 x){return rcp(x);} + AH3 ARcpH3(AH3 x){return rcp(x);} + AH4 ARcpH4(AH4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return rsqrt(x);} + AH2 ARsqH2(AH2 x){return rsqrt(x);} + AH3 ARsqH3(AH3 x){return rsqrt(x);} + AH4 ARsqH4(AH4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return saturate(x);} + AH2 ASatH2(AH2 x){return saturate(x);} + AH3 ASatH3(AH3 x){return saturate(x);} + AH4 ASatH4(AH4 x){return saturate(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #ifdef A_HLSL_6_2 + #define AD1 float64_t + #define AD2 float64_t2 + #define AD3 float64_t3 + #define AD4 float64_t4 + #else + #define AD1 double + #define AD2 double2 + #define AD3 double3 + #define AD4 double4 + #endif +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 a){return a-floor(a);} + AD2 AFractD2(AD2 a){return a-floor(a);} + AD3 AFractD3(AD3 a){return a-floor(a);} + AD4 AFractD4(AD4 a){return a-floor(a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return lerp(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return lerp(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return lerp(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return rcp(x);} + AD2 ARcpD2(AD2 x){return rcp(x);} + AD3 ARcpD3(AD3 x){return rcp(x);} + AD4 ARcpD4(AD4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return rsqrt(x);} + AD2 ARsqD2(AD2 x){return rsqrt(x);} + AD3 ARsqD3(AD3 x){return rsqrt(x);} + AD4 ARsqD4(AD4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return saturate(x);} + AD2 ASatD2(AD2 x){return saturate(x);} + AD3 ASatD3(AD3 x){return saturate(x);} + AD4 ASatD4(AD4 x){return saturate(x);} + #endif +//============================================================================================================================== +// HLSL WAVE +//============================================================================================================================== + #ifdef A_WAVE + // Where 'x' must be a compile time literal. + AF1 AWaveXorF1(AF1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF2 AWaveXorF2(AF2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF3 AWaveXorF3(AF3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF4 AWaveXorF4(AF4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU1 AWaveXorU1(AU1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU2 AWaveXorU1(AU2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU3 AWaveXorU1(AU3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU4 AWaveXorU1(AU4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(WaveReadLaneAt(AU1_AH2(v),WaveGetLaneIndex()^x));} + AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(WaveReadLaneAt(AU2_AH4(v),WaveGetLaneIndex()^x));} + AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(WaveReadLaneAt(AU1_AW2(v),WaveGetLaneIndex()^x));} + AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU1(WaveReadLaneAt(AU1_AW4(v),WaveGetLaneIndex()^x));} + #endif + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU COMMON +// +// +//============================================================================================================================== +#ifdef A_GPU + // Negative and positive infinity. + #define A_INFP_F AF1_AU1(0x7f800000u) + #define A_INFN_F AF1_AU1(0xff800000u) +//------------------------------------------------------------------------------------------------------------------------------ + // Copy sign from 's' to positive 'd'. + AF1 ACpySgnF1(AF1 d,AF1 s){return AF1_AU1(AU1_AF1(d)|(AU1_AF1(s)&AU1_(0x80000000u)));} + AF2 ACpySgnF2(AF2 d,AF2 s){return AF2_AU2(AU2_AF2(d)|(AU2_AF2(s)&AU2_(0x80000000u)));} + AF3 ACpySgnF3(AF3 d,AF3 s){return AF3_AU3(AU3_AF3(d)|(AU3_AF3(s)&AU3_(0x80000000u)));} + AF4 ACpySgnF4(AF4 d,AF4 s){return AF4_AU4(AU4_AF4(d)|(AU4_AF4(s)&AU4_(0x80000000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Single operation to return (useful to create a mask to use in lerp for branch free logic), + // m=NaN := 0 + // m>=0 := 0 + // m<0 := 1 + // Uses the following useful floating point logic, + // saturate(+a*(-INF)==-INF) := 0 + // saturate( 0*(-INF)== NaN) := 0 + // saturate(-a*(-INF)==+INF) := 1 + AF1 ASignedF1(AF1 m){return ASatF1(m*AF1_(A_INFN_F));} + AF2 ASignedF2(AF2 m){return ASatF2(m*AF2_(A_INFN_F));} + AF3 ASignedF3(AF3 m){return ASatF3(m*AF3_(A_INFN_F));} + AF4 ASignedF4(AF4 m){return ASatF4(m*AF4_(A_INFN_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AGtZeroF1(AF1 m){return ASatF1(m*AF1_(A_INFP_F));} + AF2 AGtZeroF2(AF2 m){return ASatF2(m*AF2_(A_INFP_F));} + AF3 AGtZeroF3(AF3 m){return ASatF3(m*AF3_(A_INFP_F));} + AF4 AGtZeroF4(AF4 m){return ASatF4(m*AF4_(A_INFP_F));} +//============================================================================================================================== + #ifdef A_HALF + #ifdef A_HLSL_6_2 + #define A_INFP_H AH1_AW1((uint16_t)0x7c00u) + #define A_INFN_H AH1_AW1((uint16_t)0xfc00u) + #else + #define A_INFP_H AH1_AW1(0x7c00u) + #define A_INFN_H AH1_AW1(0xfc00u) + #endif + +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ACpySgnH1(AH1 d,AH1 s){return AH1_AW1(AW1_AH1(d)|(AW1_AH1(s)&AW1_(0x8000u)));} + AH2 ACpySgnH2(AH2 d,AH2 s){return AH2_AW2(AW2_AH2(d)|(AW2_AH2(s)&AW2_(0x8000u)));} + AH3 ACpySgnH3(AH3 d,AH3 s){return AH3_AW3(AW3_AH3(d)|(AW3_AH3(s)&AW3_(0x8000u)));} + AH4 ACpySgnH4(AH4 d,AH4 s){return AH4_AW4(AW4_AH4(d)|(AW4_AH4(s)&AW4_(0x8000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASignedH1(AH1 m){return ASatH1(m*AH1_(A_INFN_H));} + AH2 ASignedH2(AH2 m){return ASatH2(m*AH2_(A_INFN_H));} + AH3 ASignedH3(AH3 m){return ASatH3(m*AH3_(A_INFN_H));} + AH4 ASignedH4(AH4 m){return ASatH4(m*AH4_(A_INFN_H));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AGtZeroH1(AH1 m){return ASatH1(m*AH1_(A_INFP_H));} + AH2 AGtZeroH2(AH2 m){return ASatH2(m*AH2_(A_INFP_H));} + AH3 AGtZeroH3(AH3 m){return ASatH3(m*AH3_(A_INFP_H));} + AH4 AGtZeroH4(AH4 m){return ASatH4(m*AH4_(A_INFP_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [FIS] FLOAT INTEGER SORTABLE +//------------------------------------------------------------------------------------------------------------------------------ +// Float to integer sortable. +// - If sign bit=0, flip the sign bit (positives). +// - If sign bit=1, flip all bits (negatives). +// Integer sortable to float. +// - If sign bit=1, flip the sign bit (positives). +// - If sign bit=0, flip all bits (negatives). +// Has nice side effects. +// - Larger integers are more positive values. +// - Float zero is mapped to center of integers (so clear to integer zero is a nice default for atomic max usage). +// Burns 3 ops for conversion {shift,or,xor}. +//============================================================================================================================== + AU1 AFisToU1(AU1 x){return x^(( AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} + AU1 AFisFromU1(AU1 x){return x^((~AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} +//------------------------------------------------------------------------------------------------------------------------------ + // Just adjust high 16-bit value (useful when upper part of 32-bit word is a 16-bit float value). + AU1 AFisToHiU1(AU1 x){return x^(( AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} + AU1 AFisFromHiU1(AU1 x){return x^((~AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AW1 AFisToW1(AW1 x){return x^(( AShrSW1(x,AW1_(15)))|AW1_(0x8000));} + AW1 AFisFromW1(AW1 x){return x^((~AShrSW1(x,AW1_(15)))|AW1_(0x8000));} +//------------------------------------------------------------------------------------------------------------------------------ + AW2 AFisToW2(AW2 x){return x^(( AShrSW2(x,AW2_(15)))|AW2_(0x8000));} + AW2 AFisFromW2(AW2 x){return x^((~AShrSW2(x,AW2_(15)))|AW2_(0x8000));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [PERM] V_PERM_B32 +//------------------------------------------------------------------------------------------------------------------------------ +// Support for V_PERM_B32 started in the 3rd generation of GCN. +//------------------------------------------------------------------------------------------------------------------------------ +// yyyyxxxx - The 'i' input. +// 76543210 +// ======== +// HGFEDCBA - Naming on permutation. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure compiler optimizes this. +//============================================================================================================================== + #ifdef A_HALF + AU1 APerm0E0A(AU2 i){return((i.x )&0xffu)|((i.y<<16)&0xff0000u);} + AU1 APerm0F0B(AU2 i){return((i.x>> 8)&0xffu)|((i.y<< 8)&0xff0000u);} + AU1 APerm0G0C(AU2 i){return((i.x>>16)&0xffu)|((i.y )&0xff0000u);} + AU1 APerm0H0D(AU2 i){return((i.x>>24)&0xffu)|((i.y>> 8)&0xff0000u);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 APermHGFA(AU2 i){return((i.x )&0x000000ffu)|(i.y&0xffffff00u);} + AU1 APermHGFC(AU2 i){return((i.x>>16)&0x000000ffu)|(i.y&0xffffff00u);} + AU1 APermHGAE(AU2 i){return((i.x<< 8)&0x0000ff00u)|(i.y&0xffff00ffu);} + AU1 APermHGCE(AU2 i){return((i.x>> 8)&0x0000ff00u)|(i.y&0xffff00ffu);} + AU1 APermHAFE(AU2 i){return((i.x<<16)&0x00ff0000u)|(i.y&0xff00ffffu);} + AU1 APermHCFE(AU2 i){return((i.x )&0x00ff0000u)|(i.y&0xff00ffffu);} + AU1 APermAGFE(AU2 i){return((i.x<<24)&0xff000000u)|(i.y&0x00ffffffu);} + AU1 APermCGFE(AU2 i){return((i.x<< 8)&0xff000000u)|(i.y&0x00ffffffu);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 APermGCEA(AU2 i){return((i.x)&0x00ff00ffu)|((i.y<<8)&0xff00ff00u);} + AU1 APermGECA(AU2 i){return(((i.x)&0xffu)|((i.x>>8)&0xff00u)|((i.y<<16)&0xff0000u)|((i.y<<8)&0xff000000u));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [BUC] BYTE UNSIGNED CONVERSION +//------------------------------------------------------------------------------------------------------------------------------ +// Designed to use the optimal conversion, enables the scaling to possibly be factored into other computation. +// Works on a range of {0 to A_BUC_<32,16>}, for <32-bit, and 16-bit> respectively. +//------------------------------------------------------------------------------------------------------------------------------ +// OPCODE NOTES +// ============ +// GCN does not do UNORM or SNORM for bytes in opcodes. +// - V_CVT_F32_UBYTE{0,1,2,3} - Unsigned byte to float. +// - V_CVT_PKACC_U8_F32 - Float to unsigned byte (does bit-field insert into 32-bit integer). +// V_PERM_B32 does byte packing with ability to zero fill bytes as well. +// - Can pull out byte values from two sources, and zero fill upper 8-bits of packed hi and lo. +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U1() - Designed for V_CVT_F32_UBYTE* and V_CVT_PKACCUM_U8_F32 ops. +// ==== ===== +// 0 : 0 +// 1 : 1 +// ... +// 255 : 255 +// : 256 (just outside the encoding range) +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. +// ==== ===== +// 0 : 0 +// 1 : 1/512 +// 2 : 1/256 +// ... +// 64 : 1/8 +// 128 : 1/4 +// 255 : 255/512 +// : 1/2 (just outside the encoding range) +//------------------------------------------------------------------------------------------------------------------------------ +// OPTIMAL IMPLEMENTATIONS ON AMD ARCHITECTURES +// ============================================ +// r=ABuc0FromU1(i) +// V_CVT_F32_UBYTE0 r,i +// -------------------------------------------- +// r=ABuc0ToU1(d,i) +// V_CVT_PKACCUM_U8_F32 r,i,0,d +// -------------------------------------------- +// d=ABuc0FromU2(i) +// Where 'k0' is an SGPR with 0x0E0A +// Where 'k1' is an SGPR with {32768.0} packed into the lower 16-bits +// V_PERM_B32 d,i.x,i.y,k0 +// V_PK_FMA_F16 d,d,k1.x,0 +// -------------------------------------------- +// r=ABuc0ToU2(d,i) +// Where 'k0' is an SGPR with {1.0/32768.0} packed into the lower 16-bits +// Where 'k1' is an SGPR with 0x???? +// Where 'k2' is an SGPR with 0x???? +// V_PK_FMA_F16 i,i,k0.x,0 +// V_PERM_B32 r.x,i,i,k1 +// V_PERM_B32 r.y,i,i,k2 +//============================================================================================================================== + // Peak range for 32-bit and 16-bit operations. + #define A_BUC_32 (255.0) + #define A_BUC_16 (255.0/512.0) +//============================================================================================================================== + #if 1 + // Designed to be one V_CVT_PKACCUM_U8_F32. + // The extra min is required to pattern match to V_CVT_PKACCUM_U8_F32. + AU1 ABuc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i),255u) )&(0x000000ffu));} + AU1 ABuc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i),255u)<< 8)&(0x0000ff00u));} + AU1 ABuc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i),255u)<<16)&(0x00ff0000u));} + AU1 ABuc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i),255u)<<24)&(0xff000000u));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed to be one V_CVT_F32_UBYTE*. + AF1 ABuc0FromU1(AU1 i){return AF1((i )&255u);} + AF1 ABuc1FromU1(AU1 i){return AF1((i>> 8)&255u);} + AF1 ABuc2FromU1(AU1 i){return AF1((i>>16)&255u);} + AF1 ABuc3FromU1(AU1 i){return AF1((i>>24)&255u);} + #endif +//============================================================================================================================== + #ifdef A_HALF + // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. + AW2 ABuc01ToW2(AH2 x,AH2 y){x*=AH2_(1.0/32768.0);y*=AH2_(1.0/32768.0); + return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed for 3 ops to do SOA to AOS and conversion. + AU2 ABuc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABuc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABuc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABuc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed for 2 ops to do both AOS to SOA, and conversion. + AH2 ABuc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0);} + AH2 ABuc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0);} + AH2 ABuc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0);} + AH2 ABuc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [BSC] BYTE SIGNED CONVERSION +//------------------------------------------------------------------------------------------------------------------------------ +// Similar to [BUC]. +// Works on a range of {-/+ A_BSC_<32,16>}, for <32-bit, and 16-bit> respectively. +//------------------------------------------------------------------------------------------------------------------------------ +// ENCODING (without zero-based encoding) +// ======== +// 0 = unused (can be used to mean something else) +// 1 = lowest value +// 128 = exact zero center (zero based encoding +// 255 = highest value +//------------------------------------------------------------------------------------------------------------------------------ +// Zero-based [Zb] flips the MSB bit of the byte (making 128 "exact zero" actually zero). +// This is useful if there is a desire for cleared values to decode as zero. +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABsc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. +// ==== ===== +// 0 : -127/512 (unused) +// 1 : -126/512 +// 2 : -125/512 +// ... +// 128 : 0 +// ... +// 255 : 127/512 +// : 1/4 (just outside the encoding range) +//============================================================================================================================== + // Peak range for 32-bit and 16-bit operations. + #define A_BSC_32 (127.0) + #define A_BSC_16 (127.0/512.0) +//============================================================================================================================== + #if 1 + AU1 ABsc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i+128.0),255u) )&(0x000000ffu));} + AU1 ABsc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i+128.0),255u)<< 8)&(0x0000ff00u));} + AU1 ABsc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i+128.0),255u)<<16)&(0x00ff0000u));} + AU1 ABsc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i+128.0),255u)<<24)&(0xff000000u));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABsc0ToZbU1(AU1 d,AF1 i){return ((d&0xffffff00u)|((min(AU1(trunc(i)+128.0),255u) )&(0x000000ffu)))^0x00000080u;} + AU1 ABsc1ToZbU1(AU1 d,AF1 i){return ((d&0xffff00ffu)|((min(AU1(trunc(i)+128.0),255u)<< 8)&(0x0000ff00u)))^0x00008000u;} + AU1 ABsc2ToZbU1(AU1 d,AF1 i){return ((d&0xff00ffffu)|((min(AU1(trunc(i)+128.0),255u)<<16)&(0x00ff0000u)))^0x00800000u;} + AU1 ABsc3ToZbU1(AU1 d,AF1 i){return ((d&0x00ffffffu)|((min(AU1(trunc(i)+128.0),255u)<<24)&(0xff000000u)))^0x80000000u;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ABsc0FromU1(AU1 i){return AF1((i )&255u)-128.0;} + AF1 ABsc1FromU1(AU1 i){return AF1((i>> 8)&255u)-128.0;} + AF1 ABsc2FromU1(AU1 i){return AF1((i>>16)&255u)-128.0;} + AF1 ABsc3FromU1(AU1 i){return AF1((i>>24)&255u)-128.0;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ABsc0FromZbU1(AU1 i){return AF1(((i )&255u)^0x80u)-128.0;} + AF1 ABsc1FromZbU1(AU1 i){return AF1(((i>> 8)&255u)^0x80u)-128.0;} + AF1 ABsc2FromZbU1(AU1 i){return AF1(((i>>16)&255u)^0x80u)-128.0;} + AF1 ABsc3FromZbU1(AU1 i){return AF1(((i>>24)&255u)^0x80u)-128.0;} + #endif +//============================================================================================================================== + #ifdef A_HALF + // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. + AW2 ABsc01ToW2(AH2 x,AH2 y){x=x*AH2_(1.0/32768.0)+AH2_(0.25/32768.0);y=y*AH2_(1.0/32768.0)+AH2_(0.25/32768.0); + return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} +//------------------------------------------------------------------------------------------------------------------------------ + AU2 ABsc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABsc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABsc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABsc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU2 ABsc0ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABsc1ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABsc2ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABsc3ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 ABsc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0)-AH2_(0.25);} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 ABsc0FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc1FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc2FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc3FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These support only positive inputs. +// Did not see value yet in specialization for range. +// Using quick testing, ended up mostly getting the same "best" approximation for various ranges. +// With hardware that can co-execute transcendentals, the value in approximations could be less than expected. +// However from a latency perspective, if execution of a transcendental is 4 clk, with no packed support, -> 8 clk total. +// And co-execution would require a compiler interleaving a lot of independent work for packed usage. +//------------------------------------------------------------------------------------------------------------------------------ +// The one Newton Raphson iteration form of rsq() was skipped (requires 6 ops total). +// Same with sqrt(), as this could be x*rsq() (7 ops). +//============================================================================================================================== + #ifdef A_HALF + // Minimize squared error across full positive range, 2 ops. + // The 0x1de2 based approximation maps {0 to 1} input maps to < 1 output. + AH1 APrxLoSqrtH1(AH1 a){return AH1_AW1((AW1_AH1(a)>>AW1_(1))+AW1_(0x1de2));} + AH2 APrxLoSqrtH2(AH2 a){return AH2_AW2((AW2_AH2(a)>>AW2_(1))+AW2_(0x1de2));} + AH3 APrxLoSqrtH3(AH3 a){return AH3_AW3((AW3_AH3(a)>>AW3_(1))+AW3_(0x1de2));} + AH4 APrxLoSqrtH4(AH4 a){return AH4_AW4((AW4_AH4(a)>>AW4_(1))+AW4_(0x1de2));} +//------------------------------------------------------------------------------------------------------------------------------ + // Lower precision estimation, 1 op. + // Minimize squared error across {smallest normal to 16384.0}. + AH1 APrxLoRcpH1(AH1 a){return AH1_AW1(AW1_(0x7784)-AW1_AH1(a));} + AH2 APrxLoRcpH2(AH2 a){return AH2_AW2(AW2_(0x7784)-AW2_AH2(a));} + AH3 APrxLoRcpH3(AH3 a){return AH3_AW3(AW3_(0x7784)-AW3_AH3(a));} + AH4 APrxLoRcpH4(AH4 a){return AH4_AW4(AW4_(0x7784)-AW4_AH4(a));} +//------------------------------------------------------------------------------------------------------------------------------ + // Medium precision estimation, one Newton Raphson iteration, 3 ops. + AH1 APrxMedRcpH1(AH1 a){AH1 b=AH1_AW1(AW1_(0x778d)-AW1_AH1(a));return b*(-b*a+AH1_(2.0));} + AH2 APrxMedRcpH2(AH2 a){AH2 b=AH2_AW2(AW2_(0x778d)-AW2_AH2(a));return b*(-b*a+AH2_(2.0));} + AH3 APrxMedRcpH3(AH3 a){AH3 b=AH3_AW3(AW3_(0x778d)-AW3_AH3(a));return b*(-b*a+AH3_(2.0));} + AH4 APrxMedRcpH4(AH4 a){AH4 b=AH4_AW4(AW4_(0x778d)-AW4_AH4(a));return b*(-b*a+AH4_(2.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // Minimize squared error across {smallest normal to 16384.0}, 2 ops. + AH1 APrxLoRsqH1(AH1 a){return AH1_AW1(AW1_(0x59a3)-(AW1_AH1(a)>>AW1_(1)));} + AH2 APrxLoRsqH2(AH2 a){return AH2_AW2(AW2_(0x59a3)-(AW2_AH2(a)>>AW2_(1)));} + AH3 APrxLoRsqH3(AH3 a){return AH3_AW3(AW3_(0x59a3)-(AW3_AH3(a)>>AW3_(1)));} + AH4 APrxLoRsqH4(AH4 a){return AH4_AW4(AW4_(0x59a3)-(AW4_AH4(a)>>AW4_(1)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// FLOAT APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// Michal Drobot has an excellent presentation on these: "Low Level Optimizations For GCN", +// - Idea dates back to SGI, then to Quake 3, etc. +// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf +// - sqrt(x)=rsqrt(x)*x +// - rcp(x)=rsqrt(x)*rsqrt(x) for positive x +// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h +//------------------------------------------------------------------------------------------------------------------------------ +// These below are from perhaps less complete searching for optimal. +// Used FP16 normal range for testing with +4096 32-bit step size for sampling error. +// So these match up well with the half approximations. +//============================================================================================================================== + AF1 APrxLoSqrtF1(AF1 a){return AF1_AU1((AU1_AF1(a)>>AU1_(1))+AU1_(0x1fbc4639));} + AF1 APrxLoRcpF1(AF1 a){return AF1_AU1(AU1_(0x7ef07ebb)-AU1_AF1(a));} + AF1 APrxMedRcpF1(AF1 a){AF1 b=AF1_AU1(AU1_(0x7ef19fff)-AU1_AF1(a));return b*(-b*a+AF1_(2.0));} + AF1 APrxLoRsqF1(AF1 a){return AF1_AU1(AU1_(0x5f347d74)-(AU1_AF1(a)>>AU1_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 APrxLoSqrtF2(AF2 a){return AF2_AU2((AU2_AF2(a)>>AU2_(1))+AU2_(0x1fbc4639));} + AF2 APrxLoRcpF2(AF2 a){return AF2_AU2(AU2_(0x7ef07ebb)-AU2_AF2(a));} + AF2 APrxMedRcpF2(AF2 a){AF2 b=AF2_AU2(AU2_(0x7ef19fff)-AU2_AF2(a));return b*(-b*a+AF2_(2.0));} + AF2 APrxLoRsqF2(AF2 a){return AF2_AU2(AU2_(0x5f347d74)-(AU2_AF2(a)>>AU2_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF3 APrxLoSqrtF3(AF3 a){return AF3_AU3((AU3_AF3(a)>>AU3_(1))+AU3_(0x1fbc4639));} + AF3 APrxLoRcpF3(AF3 a){return AF3_AU3(AU3_(0x7ef07ebb)-AU3_AF3(a));} + AF3 APrxMedRcpF3(AF3 a){AF3 b=AF3_AU3(AU3_(0x7ef19fff)-AU3_AF3(a));return b*(-b*a+AF3_(2.0));} + AF3 APrxLoRsqF3(AF3 a){return AF3_AU3(AU3_(0x5f347d74)-(AU3_AF3(a)>>AU3_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF4 APrxLoSqrtF4(AF4 a){return AF4_AU4((AU4_AF4(a)>>AU4_(1))+AU4_(0x1fbc4639));} + AF4 APrxLoRcpF4(AF4 a){return AF4_AU4(AU4_(0x7ef07ebb)-AU4_AF4(a));} + AF4 APrxMedRcpF4(AF4 a){AF4 b=AF4_AU4(AU4_(0x7ef19fff)-AU4_AF4(a));return b*(-b*a+AF4_(2.0));} + AF4 APrxLoRsqF4(AF4 a){return AF4_AU4(AU4_(0x5f347d74)-(AU4_AF4(a)>>AU4_(1)));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PQ APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// PQ is very close to x^(1/8). The functions below Use the fast float approximation method to do +// PQ<~>Gamma2 (4th power and fast 4th root) and PQ<~>Linear (8th power and fast 8th root). Maximum error is ~0.2%. +//============================================================================================================================== +// Helpers + AF1 Quart(AF1 a) { a = a * a; return a * a;} + AF1 Oct(AF1 a) { a = a * a; a = a * a; return a * a; } + AF2 Quart(AF2 a) { a = a * a; return a * a; } + AF2 Oct(AF2 a) { a = a * a; a = a * a; return a * a; } + AF3 Quart(AF3 a) { a = a * a; return a * a; } + AF3 Oct(AF3 a) { a = a * a; a = a * a; return a * a; } + AF4 Quart(AF4 a) { a = a * a; return a * a; } + AF4 Oct(AF4 a) { a = a * a; a = a * a; return a * a; } + //------------------------------------------------------------------------------------------------------------------------------ + AF1 APrxPQToGamma2(AF1 a) { return Quart(a); } + AF1 APrxPQToLinear(AF1 a) { return Oct(a); } + AF1 APrxLoGamma2ToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); } + AF1 APrxMedGamma2ToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); AF1 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF1 APrxHighGamma2ToPQ(AF1 a) { return sqrt(sqrt(a)); } + AF1 APrxLoLinearToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); } + AF1 APrxMedLinearToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); AF1 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF1 APrxHighLinearToPQ(AF1 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF2 APrxPQToGamma2(AF2 a) { return Quart(a); } + AF2 APrxPQToLinear(AF2 a) { return Oct(a); } + AF2 APrxLoGamma2ToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); } + AF2 APrxMedGamma2ToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); AF2 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF2 APrxHighGamma2ToPQ(AF2 a) { return sqrt(sqrt(a)); } + AF2 APrxLoLinearToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); } + AF2 APrxMedLinearToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); AF2 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF2 APrxHighLinearToPQ(AF2 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF3 APrxPQToGamma2(AF3 a) { return Quart(a); } + AF3 APrxPQToLinear(AF3 a) { return Oct(a); } + AF3 APrxLoGamma2ToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); } + AF3 APrxMedGamma2ToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); AF3 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF3 APrxHighGamma2ToPQ(AF3 a) { return sqrt(sqrt(a)); } + AF3 APrxLoLinearToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); } + AF3 APrxMedLinearToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); AF3 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF3 APrxHighLinearToPQ(AF3 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF4 APrxPQToGamma2(AF4 a) { return Quart(a); } + AF4 APrxPQToLinear(AF4 a) { return Oct(a); } + AF4 APrxLoGamma2ToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); } + AF4 APrxMedGamma2ToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); AF4 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF4 APrxHighGamma2ToPQ(AF4 a) { return sqrt(sqrt(a)); } + AF4 APrxLoLinearToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); } + AF4 APrxMedLinearToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); AF4 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF4 APrxHighLinearToPQ(AF4 a) { return sqrt(sqrt(sqrt(a))); } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PARABOLIC SIN & COS +//------------------------------------------------------------------------------------------------------------------------------ +// Approximate answers to transcendental questions. +//------------------------------------------------------------------------------------------------------------------------------ +//============================================================================================================================== + #if 1 + // Valid input range is {-1 to 1} representing {0 to 2 pi}. + // Output range is {-1/4 to 1/4} representing {-1 to 1}. + AF1 APSinF1(AF1 x){return x*abs(x)-x;} // MAD. + AF2 APSinF2(AF2 x){return x*abs(x)-x;} + AF1 APCosF1(AF1 x){x=AFractF1(x*AF1_(0.5)+AF1_(0.75));x=x*AF1_(2.0)-AF1_(1.0);return APSinF1(x);} // 3x MAD, FRACT + AF2 APCosF2(AF2 x){x=AFractF2(x*AF2_(0.5)+AF2_(0.75));x=x*AF2_(2.0)-AF2_(1.0);return APSinF2(x);} + AF2 APSinCosF1(AF1 x){AF1 y=AFractF1(x*AF1_(0.5)+AF1_(0.75));y=y*AF1_(2.0)-AF1_(1.0);return APSinF2(AF2(x,y));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + // For a packed {sin,cos} pair, + // - Native takes 16 clocks and 4 issue slots (no packed transcendentals). + // - Parabolic takes 8 clocks and 8 issue slots (only fract is non-packed). + AH1 APSinH1(AH1 x){return x*abs(x)-x;} + AH2 APSinH2(AH2 x){return x*abs(x)-x;} // AND,FMA + AH1 APCosH1(AH1 x){x=AFractH1(x*AH1_(0.5)+AH1_(0.75));x=x*AH1_(2.0)-AH1_(1.0);return APSinH1(x);} + AH2 APCosH2(AH2 x){x=AFractH2(x*AH2_(0.5)+AH2_(0.75));x=x*AH2_(2.0)-AH2_(1.0);return APSinH2(x);} // 3x FMA, 2xFRACT, AND + AH2 APSinCosH1(AH1 x){AH1 y=AFractH1(x*AH1_(0.5)+AH1_(0.75));y=y*AH1_(2.0)-AH1_(1.0);return APSinH2(AH2(x,y));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [ZOL] ZERO ONE LOGIC +//------------------------------------------------------------------------------------------------------------------------------ +// Conditional free logic designed for easy 16-bit packing, and backwards porting to 32-bit. +//------------------------------------------------------------------------------------------------------------------------------ +// 0 := false +// 1 := true +//------------------------------------------------------------------------------------------------------------------------------ +// AndNot(x,y) -> !(x&y) .... One op. +// AndOr(x,y,z) -> (x&y)|z ... One op. +// GtZero(x) -> x>0.0 ..... One op. +// Sel(x,y,z) -> x?y:z ..... Two ops, has no precision loss. +// Signed(x) -> x<0.0 ..... One op. +// ZeroPass(x,y) -> x?0:y ..... Two ops, 'y' is a pass through safe for aliasing as integer. +//------------------------------------------------------------------------------------------------------------------------------ +// OPTIMIZATION NOTES +// ================== +// - On Vega to use 2 constants in a packed op, pass in as one AW2 or one AH2 'k.xy' and use as 'k.xx' and 'k.yy'. +// For example 'a.xy*k.xx+k.yy'. +//============================================================================================================================== + #if 1 + AU1 AZolAndU1(AU1 x,AU1 y){return min(x,y);} + AU2 AZolAndU2(AU2 x,AU2 y){return min(x,y);} + AU3 AZolAndU3(AU3 x,AU3 y){return min(x,y);} + AU4 AZolAndU4(AU4 x,AU4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AZolNotU1(AU1 x){return x^AU1_(1);} + AU2 AZolNotU2(AU2 x){return x^AU2_(1);} + AU3 AZolNotU3(AU3 x){return x^AU3_(1);} + AU4 AZolNotU4(AU4 x){return x^AU4_(1);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AZolOrU1(AU1 x,AU1 y){return max(x,y);} + AU2 AZolOrU2(AU2 x,AU2 y){return max(x,y);} + AU3 AZolOrU3(AU3 x,AU3 y){return max(x,y);} + AU4 AZolOrU4(AU4 x,AU4 y){return max(x,y);} +//============================================================================================================================== + AU1 AZolF1ToU1(AF1 x){return AU1(x);} + AU2 AZolF2ToU2(AF2 x){return AU2(x);} + AU3 AZolF3ToU3(AF3 x){return AU3(x);} + AU4 AZolF4ToU4(AF4 x){return AU4(x);} +//------------------------------------------------------------------------------------------------------------------------------ + // 2 ops, denormals don't work in 32-bit on PC (and if they are enabled, OMOD is disabled). + AU1 AZolNotF1ToU1(AF1 x){return AU1(AF1_(1.0)-x);} + AU2 AZolNotF2ToU2(AF2 x){return AU2(AF2_(1.0)-x);} + AU3 AZolNotF3ToU3(AF3 x){return AU3(AF3_(1.0)-x);} + AU4 AZolNotF4ToU4(AF4 x){return AU4(AF4_(1.0)-x);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolU1ToF1(AU1 x){return AF1(x);} + AF2 AZolU2ToF2(AU2 x){return AF2(x);} + AF3 AZolU3ToF3(AU3 x){return AF3(x);} + AF4 AZolU4ToF4(AU4 x){return AF4(x);} +//============================================================================================================================== + AF1 AZolAndF1(AF1 x,AF1 y){return min(x,y);} + AF2 AZolAndF2(AF2 x,AF2 y){return min(x,y);} + AF3 AZolAndF3(AF3 x,AF3 y){return min(x,y);} + AF4 AZolAndF4(AF4 x,AF4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ASolAndNotF1(AF1 x,AF1 y){return (-x)*y+AF1_(1.0);} + AF2 ASolAndNotF2(AF2 x,AF2 y){return (-x)*y+AF2_(1.0);} + AF3 ASolAndNotF3(AF3 x,AF3 y){return (-x)*y+AF3_(1.0);} + AF4 ASolAndNotF4(AF4 x,AF4 y){return (-x)*y+AF4_(1.0);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolAndOrF1(AF1 x,AF1 y,AF1 z){return ASatF1(x*y+z);} + AF2 AZolAndOrF2(AF2 x,AF2 y,AF2 z){return ASatF2(x*y+z);} + AF3 AZolAndOrF3(AF3 x,AF3 y,AF3 z){return ASatF3(x*y+z);} + AF4 AZolAndOrF4(AF4 x,AF4 y,AF4 z){return ASatF4(x*y+z);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolGtZeroF1(AF1 x){return ASatF1(x*AF1_(A_INFP_F));} + AF2 AZolGtZeroF2(AF2 x){return ASatF2(x*AF2_(A_INFP_F));} + AF3 AZolGtZeroF3(AF3 x){return ASatF3(x*AF3_(A_INFP_F));} + AF4 AZolGtZeroF4(AF4 x){return ASatF4(x*AF4_(A_INFP_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolNotF1(AF1 x){return AF1_(1.0)-x;} + AF2 AZolNotF2(AF2 x){return AF2_(1.0)-x;} + AF3 AZolNotF3(AF3 x){return AF3_(1.0)-x;} + AF4 AZolNotF4(AF4 x){return AF4_(1.0)-x;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolOrF1(AF1 x,AF1 y){return max(x,y);} + AF2 AZolOrF2(AF2 x,AF2 y){return max(x,y);} + AF3 AZolOrF3(AF3 x,AF3 y){return max(x,y);} + AF4 AZolOrF4(AF4 x,AF4 y){return max(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolSelF1(AF1 x,AF1 y,AF1 z){AF1 r=(-x)*z+z;return x*y+r;} + AF2 AZolSelF2(AF2 x,AF2 y,AF2 z){AF2 r=(-x)*z+z;return x*y+r;} + AF3 AZolSelF3(AF3 x,AF3 y,AF3 z){AF3 r=(-x)*z+z;return x*y+r;} + AF4 AZolSelF4(AF4 x,AF4 y,AF4 z){AF4 r=(-x)*z+z;return x*y+r;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolSignedF1(AF1 x){return ASatF1(x*AF1_(A_INFN_F));} + AF2 AZolSignedF2(AF2 x){return ASatF2(x*AF2_(A_INFN_F));} + AF3 AZolSignedF3(AF3 x){return ASatF3(x*AF3_(A_INFN_F));} + AF4 AZolSignedF4(AF4 x){return ASatF4(x*AF4_(A_INFN_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolZeroPassF1(AF1 x,AF1 y){return AF1_AU1((AU1_AF1(x)!=AU1_(0))?AU1_(0):AU1_AF1(y));} + AF2 AZolZeroPassF2(AF2 x,AF2 y){return AF2_AU2((AU2_AF2(x)!=AU2_(0))?AU2_(0):AU2_AF2(y));} + AF3 AZolZeroPassF3(AF3 x,AF3 y){return AF3_AU3((AU3_AF3(x)!=AU3_(0))?AU3_(0):AU3_AF3(y));} + AF4 AZolZeroPassF4(AF4 x,AF4 y){return AF4_AU4((AU4_AF4(x)!=AU4_(0))?AU4_(0):AU4_AF4(y));} + #endif +//============================================================================================================================== + #ifdef A_HALF + AW1 AZolAndW1(AW1 x,AW1 y){return min(x,y);} + AW2 AZolAndW2(AW2 x,AW2 y){return min(x,y);} + AW3 AZolAndW3(AW3 x,AW3 y){return min(x,y);} + AW4 AZolAndW4(AW4 x,AW4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AZolNotW1(AW1 x){return x^AW1_(1);} + AW2 AZolNotW2(AW2 x){return x^AW2_(1);} + AW3 AZolNotW3(AW3 x){return x^AW3_(1);} + AW4 AZolNotW4(AW4 x){return x^AW4_(1);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AZolOrW1(AW1 x,AW1 y){return max(x,y);} + AW2 AZolOrW2(AW2 x,AW2 y){return max(x,y);} + AW3 AZolOrW3(AW3 x,AW3 y){return max(x,y);} + AW4 AZolOrW4(AW4 x,AW4 y){return max(x,y);} +//============================================================================================================================== + // Uses denormal trick. + AW1 AZolH1ToW1(AH1 x){return AW1_AH1(x*AH1_AW1(AW1_(1)));} + AW2 AZolH2ToW2(AH2 x){return AW2_AH2(x*AH2_AW2(AW2_(1)));} + AW3 AZolH3ToW3(AH3 x){return AW3_AH3(x*AH3_AW3(AW3_(1)));} + AW4 AZolH4ToW4(AH4 x){return AW4_AH4(x*AH4_AW4(AW4_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + // AMD arch lacks a packed conversion opcode. + AH1 AZolW1ToH1(AW1 x){return AH1_AW1(x*AW1_AH1(AH1_(1.0)));} + AH2 AZolW2ToH2(AW2 x){return AH2_AW2(x*AW2_AH2(AH2_(1.0)));} + AH3 AZolW1ToH3(AW3 x){return AH3_AW3(x*AW3_AH3(AH3_(1.0)));} + AH4 AZolW2ToH4(AW4 x){return AH4_AW4(x*AW4_AH4(AH4_(1.0)));} +//============================================================================================================================== + AH1 AZolAndH1(AH1 x,AH1 y){return min(x,y);} + AH2 AZolAndH2(AH2 x,AH2 y){return min(x,y);} + AH3 AZolAndH3(AH3 x,AH3 y){return min(x,y);} + AH4 AZolAndH4(AH4 x,AH4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASolAndNotH1(AH1 x,AH1 y){return (-x)*y+AH1_(1.0);} + AH2 ASolAndNotH2(AH2 x,AH2 y){return (-x)*y+AH2_(1.0);} + AH3 ASolAndNotH3(AH3 x,AH3 y){return (-x)*y+AH3_(1.0);} + AH4 ASolAndNotH4(AH4 x,AH4 y){return (-x)*y+AH4_(1.0);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolAndOrH1(AH1 x,AH1 y,AH1 z){return ASatH1(x*y+z);} + AH2 AZolAndOrH2(AH2 x,AH2 y,AH2 z){return ASatH2(x*y+z);} + AH3 AZolAndOrH3(AH3 x,AH3 y,AH3 z){return ASatH3(x*y+z);} + AH4 AZolAndOrH4(AH4 x,AH4 y,AH4 z){return ASatH4(x*y+z);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolGtZeroH1(AH1 x){return ASatH1(x*AH1_(A_INFP_H));} + AH2 AZolGtZeroH2(AH2 x){return ASatH2(x*AH2_(A_INFP_H));} + AH3 AZolGtZeroH3(AH3 x){return ASatH3(x*AH3_(A_INFP_H));} + AH4 AZolGtZeroH4(AH4 x){return ASatH4(x*AH4_(A_INFP_H));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolNotH1(AH1 x){return AH1_(1.0)-x;} + AH2 AZolNotH2(AH2 x){return AH2_(1.0)-x;} + AH3 AZolNotH3(AH3 x){return AH3_(1.0)-x;} + AH4 AZolNotH4(AH4 x){return AH4_(1.0)-x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolOrH1(AH1 x,AH1 y){return max(x,y);} + AH2 AZolOrH2(AH2 x,AH2 y){return max(x,y);} + AH3 AZolOrH3(AH3 x,AH3 y){return max(x,y);} + AH4 AZolOrH4(AH4 x,AH4 y){return max(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolSelH1(AH1 x,AH1 y,AH1 z){AH1 r=(-x)*z+z;return x*y+r;} + AH2 AZolSelH2(AH2 x,AH2 y,AH2 z){AH2 r=(-x)*z+z;return x*y+r;} + AH3 AZolSelH3(AH3 x,AH3 y,AH3 z){AH3 r=(-x)*z+z;return x*y+r;} + AH4 AZolSelH4(AH4 x,AH4 y,AH4 z){AH4 r=(-x)*z+z;return x*y+r;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolSignedH1(AH1 x){return ASatH1(x*AH1_(A_INFN_H));} + AH2 AZolSignedH2(AH2 x){return ASatH2(x*AH2_(A_INFN_H));} + AH3 AZolSignedH3(AH3 x){return ASatH3(x*AH3_(A_INFN_H));} + AH4 AZolSignedH4(AH4 x){return ASatH4(x*AH4_(A_INFN_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COLOR CONVERSIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These are all linear to/from some other space (where 'linear' has been shortened out of the function name). +// So 'ToGamma' is 'LinearToGamma', and 'FromGamma' is 'LinearFromGamma'. +// These are branch free implementations. +// The AToSrgbF1() function is useful for stores for compute shaders for GPUs without hardware linear->sRGB store conversion. +//------------------------------------------------------------------------------------------------------------------------------ +// TRANSFER FUNCTIONS +// ================== +// 709 ..... Rec709 used for some HDTVs +// Gamma ... Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native +// Pq ...... PQ native for HDR10 +// Srgb .... The sRGB output, typical of PC displays, useful for 10-bit output, or storing to 8-bit UNORM without SRGB type +// Two ..... Gamma 2.0, fastest conversion (useful for intermediate pass approximations) +// Three ... Gamma 3.0, less fast, but good for HDR. +//------------------------------------------------------------------------------------------------------------------------------ +// KEEPING TO SPEC +// =============== +// Both Rec.709 and sRGB have a linear segment which as spec'ed would intersect the curved segment 2 times. +// (a.) For 8-bit sRGB, steps {0 to 10.3} are in the linear region (4% of the encoding range). +// (b.) For 8-bit 709, steps {0 to 20.7} are in the linear region (8% of the encoding range). +// Also there is a slight step in the transition regions. +// Precision of the coefficients in the spec being the likely cause. +// Main usage case of the sRGB code is to do the linear->sRGB converstion in a compute shader before store. +// This is to work around lack of hardware (typically only ROP does the conversion for free). +// To "correct" the linear segment, would be to introduce error, because hardware decode of sRGB->linear is fixed (and free). +// So this header keeps with the spec. +// For linear->sRGB transforms, the linear segment in some respects reduces error, because rounding in that region is linear. +// Rounding in the curved region in hardware (and fast software code) introduces error due to rounding in non-linear. +//------------------------------------------------------------------------------------------------------------------------------ +// FOR PQ +// ====== +// Both input and output is {0.0-1.0}, and where output 1.0 represents 10000.0 cd/m^2. +// All constants are only specified to FP32 precision. +// External PQ source reference, +// - https://github.com/ampas/aces-dev/blob/master/transforms/ctl/utilities/ACESlib.Utilities_Color.a1.0.1.ctl +//------------------------------------------------------------------------------------------------------------------------------ +// PACKED VERSIONS +// =============== +// These are the A*H2() functions. +// There is no PQ functions as FP16 seemed to not have enough precision for the conversion. +// The remaining functions are "good enough" for 8-bit, and maybe 10-bit if not concerned about a few 1-bit errors. +// Precision is lowest in the 709 conversion, higher in sRGB, higher still in Two and Gamma (when using 2.2 at least). +//------------------------------------------------------------------------------------------------------------------------------ +// NOTES +// ===== +// Could be faster for PQ conversions to be in ALU or a texture lookup depending on usage case. +//============================================================================================================================== + #if 1 + AF1 ATo709F1(AF1 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AF2 ATo709F2(AF2 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AF3 ATo709F3(AF3 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + // Note 'rcpX' is '1/x', where the 'x' is what would be used in AFromGamma(). + AF1 AToGammaF1(AF1 c,AF1 rcpX){return pow(c,AF1_(rcpX));} + AF2 AToGammaF2(AF2 c,AF1 rcpX){return pow(c,AF2_(rcpX));} + AF3 AToGammaF3(AF3 c,AF1 rcpX){return pow(c,AF3_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToPqF1(AF1 x){AF1 p=pow(x,AF1_(0.159302)); + return pow((AF1_(0.835938)+AF1_(18.8516)*p)/(AF1_(1.0)+AF1_(18.6875)*p),AF1_(78.8438));} + AF2 AToPqF1(AF2 x){AF2 p=pow(x,AF2_(0.159302)); + return pow((AF2_(0.835938)+AF2_(18.8516)*p)/(AF2_(1.0)+AF2_(18.6875)*p),AF2_(78.8438));} + AF3 AToPqF1(AF3 x){AF3 p=pow(x,AF3_(0.159302)); + return pow((AF3_(0.835938)+AF3_(18.8516)*p)/(AF3_(1.0)+AF3_(18.6875)*p),AF3_(78.8438));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToSrgbF1(AF1 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AF2 AToSrgbF2(AF2 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AF3 AToSrgbF3(AF3 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToTwoF1(AF1 c){return sqrt(c);} + AF2 AToTwoF2(AF2 c){return sqrt(c);} + AF3 AToTwoF3(AF3 c){return sqrt(c);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToThreeF1(AF1 c){return pow(c,AF1_(1.0/3.0));} + AF2 AToThreeF2(AF2 c){return pow(c,AF2_(1.0/3.0));} + AF3 AToThreeF3(AF3 c){return pow(c,AF3_(1.0/3.0));} + #endif +//============================================================================================================================== + #if 1 + // Unfortunately median won't work here. + AF1 AFrom709F1(AF1 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AF2 AFrom709F2(AF2 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AF3 AFrom709F3(AF3 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromGammaF1(AF1 c,AF1 x){return pow(c,AF1_(x));} + AF2 AFromGammaF2(AF2 c,AF1 x){return pow(c,AF2_(x));} + AF3 AFromGammaF3(AF3 c,AF1 x){return pow(c,AF3_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromPqF1(AF1 x){AF1 p=pow(x,AF1_(0.0126833)); + return pow(ASatF1(p-AF1_(0.835938))/(AF1_(18.8516)-AF1_(18.6875)*p),AF1_(6.27739));} + AF2 AFromPqF1(AF2 x){AF2 p=pow(x,AF2_(0.0126833)); + return pow(ASatF2(p-AF2_(0.835938))/(AF2_(18.8516)-AF2_(18.6875)*p),AF2_(6.27739));} + AF3 AFromPqF1(AF3 x){AF3 p=pow(x,AF3_(0.0126833)); + return pow(ASatF3(p-AF3_(0.835938))/(AF3_(18.8516)-AF3_(18.6875)*p),AF3_(6.27739));} +//------------------------------------------------------------------------------------------------------------------------------ + // Unfortunately median won't work here. + AF1 AFromSrgbF1(AF1 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AF2 AFromSrgbF2(AF2 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AF3 AFromSrgbF3(AF3 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromTwoF1(AF1 c){return c*c;} + AF2 AFromTwoF2(AF2 c){return c*c;} + AF3 AFromTwoF3(AF3 c){return c*c;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromThreeF1(AF1 c){return c*c*c;} + AF2 AFromThreeF2(AF2 c){return c*c*c;} + AF3 AFromThreeF3(AF3 c){return c*c*c;} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH1 ATo709H1(AH1 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AH2 ATo709H2(AH2 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AH3 ATo709H3(AH3 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToGammaH1(AH1 c,AH1 rcpX){return pow(c,AH1_(rcpX));} + AH2 AToGammaH2(AH2 c,AH1 rcpX){return pow(c,AH2_(rcpX));} + AH3 AToGammaH3(AH3 c,AH1 rcpX){return pow(c,AH3_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToSrgbH1(AH1 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AH2 AToSrgbH2(AH2 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AH3 AToSrgbH3(AH3 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToTwoH1(AH1 c){return sqrt(c);} + AH2 AToTwoH2(AH2 c){return sqrt(c);} + AH3 AToTwoH3(AH3 c){return sqrt(c);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToThreeF1(AH1 c){return pow(c,AH1_(1.0/3.0));} + AH2 AToThreeF2(AH2 c){return pow(c,AH2_(1.0/3.0));} + AH3 AToThreeF3(AH3 c){return pow(c,AH3_(1.0/3.0));} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH1 AFrom709H1(AH1 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AH2 AFrom709H2(AH2 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AH3 AFrom709H3(AH3 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromGammaH1(AH1 c,AH1 x){return pow(c,AH1_(x));} + AH2 AFromGammaH2(AH2 c,AH1 x){return pow(c,AH2_(x));} + AH3 AFromGammaH3(AH3 c,AH1 x){return pow(c,AH3_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AHromSrgbF1(AH1 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AH2 AHromSrgbF2(AH2 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AH3 AHromSrgbF3(AH3 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromTwoH1(AH1 c){return c*c;} + AH2 AFromTwoH2(AH2 c){return c*c;} + AH3 AFromTwoH3(AH3 c){return c*c;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromThreeH1(AH1 c){return c*c*c;} + AH2 AFromThreeH2(AH2 c){return c*c*c;} + AH3 AFromThreeH3(AH3 c){return c*c*c;} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CS REMAP +//============================================================================================================================== + // Simple remap 64x1 to 8x8 with rotated 2x2 pixel quads in quad linear. + // 543210 + // ====== + // ..xxx. + // yy...y + AU2 ARmp8x8(AU1 a){return AU2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} +//============================================================================================================================== + // More complex remap 64x1 to 8x8 which is necessary for 2D wave reductions. + // 543210 + // ====== + // .xx..x + // y..yy. + // Details, + // LANE TO 8x8 MAPPING + // =================== + // 00 01 08 09 10 11 18 19 + // 02 03 0a 0b 12 13 1a 1b + // 04 05 0c 0d 14 15 1c 1d + // 06 07 0e 0f 16 17 1e 1f + // 20 21 28 29 30 31 38 39 + // 22 23 2a 2b 32 33 3a 3b + // 24 25 2c 2d 34 35 3c 3d + // 26 27 2e 2f 36 37 3e 3f + AU2 ARmpRed8x8(AU1 a){return AU2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} +//============================================================================================================================== + #ifdef A_HALF + AW2 ARmp8x8H(AU1 a){return AW2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} + AW2 ARmpRed8x8H(AU1 a){return AW2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} + #endif +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// REFERENCE +// +//------------------------------------------------------------------------------------------------------------------------------ +// IEEE FLOAT RULES +// ================ +// - saturate(NaN)=0, saturate(-INF)=0, saturate(+INF)=1 +// - {+/-}0 * {+/-}INF = NaN +// - -INF + (+INF) = NaN +// - {+/-}0 / {+/-}0 = NaN +// - {+/-}INF / {+/-}INF = NaN +// - a<(-0) := sqrt(a) = NaN (a=-0.0 won't NaN) +// - 0 == -0 +// - 4/0 = +INF +// - 4/-0 = -INF +// - 4+INF = +INF +// - 4-INF = -INF +// - 4*(+INF) = +INF +// - 4*(-INF) = -INF +// - -4*(+INF) = -INF +// - sqrt(+INF) = +INF +//------------------------------------------------------------------------------------------------------------------------------ +// FP16 ENCODING +// ============= +// fedcba9876543210 +// ---------------- +// ......mmmmmmmmmm 10-bit mantissa (encodes 11-bit 0.5 to 1.0 except for denormals) +// .eeeee.......... 5-bit exponent +// .00000.......... denormals +// .00001.......... -14 exponent +// .11110.......... 15 exponent +// .111110000000000 infinity +// .11111nnnnnnnnnn NaN with n!=0 +// s............... sign +//------------------------------------------------------------------------------------------------------------------------------ +// FP16/INT16 ALIASING DENORMAL +// ============================ +// 11-bit unsigned integers alias with half float denormal/normal values, +// 1 = 2^(-24) = 1/16777216 ....................... first denormal value +// 2 = 2^(-23) +// ... +// 1023 = 2^(-14)*(1-2^(-10)) = 2^(-14)*(1-1/1024) ... last denormal value +// 1024 = 2^(-14) = 1/16384 .......................... first normal value that still maps to integers +// 2047 .............................................. last normal value that still maps to integers +// Scaling limits, +// 2^15 = 32768 ...................................... largest power of 2 scaling +// Largest pow2 conversion mapping is at *32768, +// 1 : 2^(-9) = 1/512 +// 2 : 1/256 +// 4 : 1/128 +// 8 : 1/64 +// 16 : 1/32 +// 32 : 1/16 +// 64 : 1/8 +// 128 : 1/4 +// 256 : 1/2 +// 512 : 1 +// 1024 : 2 +// 2047 : a little less than 4 +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU/CPU PORTABILITY +// +// +//------------------------------------------------------------------------------------------------------------------------------ +// This is the GPU implementation. +// See the CPU implementation for docs. +//============================================================================================================================== +#ifdef A_GPU + #define A_TRUE true + #define A_FALSE false + #define A_STATIC +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD2 + #define retAD3 AD3 + #define retAD4 AD4 + #define retAF2 AF2 + #define retAF3 AF3 + #define retAF4 AF4 + #define retAL2 AL2 + #define retAL3 AL3 + #define retAL4 AL4 + #define retAU2 AU2 + #define retAU3 AU3 + #define retAU4 AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 in AD2 + #define inAD3 in AD3 + #define inAD4 in AD4 + #define inAF2 in AF2 + #define inAF3 in AF3 + #define inAF4 in AF4 + #define inAL2 in AL2 + #define inAL3 in AL3 + #define inAL4 in AL4 + #define inAU2 in AU2 + #define inAU3 in AU3 + #define inAU4 in AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 inout AD2 + #define inoutAD3 inout AD3 + #define inoutAD4 inout AD4 + #define inoutAF2 inout AF2 + #define inoutAF3 inout AF3 + #define inoutAF4 inout AF4 + #define inoutAL2 inout AL2 + #define inoutAL3 inout AL3 + #define inoutAL4 inout AL4 + #define inoutAU2 inout AU2 + #define inoutAU3 inout AU3 + #define inoutAU4 inout AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 out AD2 + #define outAD3 out AD3 + #define outAD4 out AD4 + #define outAF2 out AF2 + #define outAF3 out AF3 + #define outAF4 out AF4 + #define outAL2 out AL2 + #define outAL3 out AL3 + #define outAL4 out AL4 + #define outAU2 out AU2 + #define outAU3 out AU3 + #define outAU4 out AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD2 x + #define varAD3(x) AD3 x + #define varAD4(x) AD4 x + #define varAF2(x) AF2 x + #define varAF3(x) AF3 x + #define varAF4(x) AF4 x + #define varAL2(x) AL2 x + #define varAL3(x) AL3 x + #define varAL4(x) AL4 x + #define varAU2(x) AU2 x + #define varAU3(x) AU3 x + #define varAU4(x) AU4 x +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) AD2(x,y) + #define initAD3(x,y,z) AD3(x,y,z) + #define initAD4(x,y,z,w) AD4(x,y,z,w) + #define initAF2(x,y) AF2(x,y) + #define initAF3(x,y,z) AF3(x,y,z) + #define initAF4(x,y,z,w) AF4(x,y,z,w) + #define initAL2(x,y) AL2(x,y) + #define initAL3(x,y,z) AL3(x,y,z) + #define initAL4(x,y,z,w) AL4(x,y,z,w) + #define initAU2(x,y) AU2(x,y) + #define initAU3(x,y,z) AU3(x,y,z) + #define initAU4(x,y,z,w) AU4(x,y,z,w) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//============================================================================================================================== + #define AAbsD1(a) abs(AD1(a)) + #define AAbsF1(a) abs(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ACosD1(a) cos(AD1(a)) + #define ACosF1(a) cos(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ADotD2(a,b) dot(AD2(a),AD2(b)) + #define ADotD3(a,b) dot(AD3(a),AD3(b)) + #define ADotD4(a,b) dot(AD4(a),AD4(b)) + #define ADotF2(a,b) dot(AF2(a),AF2(b)) + #define ADotF3(a,b) dot(AF3(a),AF3(b)) + #define ADotF4(a,b) dot(AF4(a),AF4(b)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AExp2D1(a) exp2(AD1(a)) + #define AExp2F1(a) exp2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AFloorD1(a) floor(AD1(a)) + #define AFloorF1(a) floor(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ALog2D1(a) log2(AD1(a)) + #define ALog2F1(a) log2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMaxD1(a,b) max(a,b) + #define AMaxF1(a,b) max(a,b) + #define AMaxL1(a,b) max(a,b) + #define AMaxU1(a,b) max(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMinD1(a,b) min(a,b) + #define AMinF1(a,b) min(a,b) + #define AMinL1(a,b) min(a,b) + #define AMinU1(a,b) min(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASinD1(a) sin(AD1(a)) + #define ASinF1(a) sin(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASqrtD1(a) sqrt(AD1(a)) + #define ASqrtF1(a) sqrt(AF1(a)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + #define APowD1(a,b) pow(AD1(a),AF1(b)) + #define APowF1(a,b) pow(AF1(a),AF1(b)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + #ifdef A_DUBL + AD2 opAAbsD2(outAD2 d,inAD2 a){d=abs(a);return d;} + AD3 opAAbsD3(outAD3 d,inAD3 a){d=abs(a);return d;} + AD4 opAAbsD4(outAD4 d,inAD4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d=a+b;return d;} + AD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d=a+b;return d;} + AD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d=a+AD2_(b);return d;} + AD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d=a+AD3_(b);return d;} + AD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d=a+AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opACpyD2(outAD2 d,inAD2 a){d=a;return d;} + AD3 opACpyD3(outAD3 d,inAD3 a){d=a;return d;} + AD4 opACpyD4(outAD4 d,inAD4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d=ALerpD2(a,b,c);return d;} + AD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d=ALerpD3(a,b,c);return d;} + AD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d=ALerpD4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d=ALerpD2(a,b,AD2_(c));return d;} + AD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d=ALerpD3(a,b,AD3_(c));return d;} + AD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d=ALerpD4(a,b,AD4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d=max(a,b);return d;} + AD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d=max(a,b);return d;} + AD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d=min(a,b);return d;} + AD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d=min(a,b);return d;} + AD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d=a*b;return d;} + AD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d=a*b;return d;} + AD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d=a*AD2_(b);return d;} + AD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d=a*AD3_(b);return d;} + AD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d=a*AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opANegD2(outAD2 d,inAD2 a){d=-a;return d;} + AD3 opANegD3(outAD3 d,inAD3 a){d=-a;return d;} + AD4 opANegD4(outAD4 d,inAD4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opARcpD2(outAD2 d,inAD2 a){d=ARcpD2(a);return d;} + AD3 opARcpD3(outAD3 d,inAD3 a){d=ARcpD3(a);return d;} + AD4 opARcpD4(outAD4 d,inAD4 a){d=ARcpD4(a);return d;} + #endif +//============================================================================================================================== + AF2 opAAbsF2(outAF2 d,inAF2 a){d=abs(a);return d;} + AF3 opAAbsF3(outAF3 d,inAF3 a){d=abs(a);return d;} + AF4 opAAbsF4(outAF4 d,inAF4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d=a+b;return d;} + AF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d=a+b;return d;} + AF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d=a+AF2_(b);return d;} + AF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d=a+AF3_(b);return d;} + AF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d=a+AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opACpyF2(outAF2 d,inAF2 a){d=a;return d;} + AF3 opACpyF3(outAF3 d,inAF3 a){d=a;return d;} + AF4 opACpyF4(outAF4 d,inAF4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d=ALerpF2(a,b,c);return d;} + AF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d=ALerpF3(a,b,c);return d;} + AF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d=ALerpF4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d=ALerpF2(a,b,AF2_(c));return d;} + AF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d=ALerpF3(a,b,AF3_(c));return d;} + AF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d=ALerpF4(a,b,AF4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d=max(a,b);return d;} + AF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d=max(a,b);return d;} + AF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d=min(a,b);return d;} + AF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d=min(a,b);return d;} + AF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d=a*b;return d;} + AF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d=a*b;return d;} + AF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d=a*AF2_(b);return d;} + AF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d=a*AF3_(b);return d;} + AF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d=a*AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opANegF2(outAF2 d,inAF2 a){d=-a;return d;} + AF3 opANegF3(outAF3 d,inAF3 a){d=-a;return d;} + AF4 opANegF4(outAF4 d,inAF4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opARcpF2(outAF2 d,inAF2 a){d=ARcpF2(a);return d;} + AF3 opARcpF3(outAF3 d,inAF3 a){d=ARcpF3(a);return d;} + AF4 opARcpF4(outAF4 d,inAF4 a){d=ARcpF4(a);return d;} +#endif diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_fsr1.h b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_fsr1.h new file mode 100644 index 00000000..4e0b3d54 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_fsr1.h @@ -0,0 +1,1199 @@ +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// AMD FidelityFX SUPER RESOLUTION [FSR 1] ::: SPATIAL SCALING & EXTRAS - v1.20210629 +// +// +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// ABOUT +// ===== +// FSR is a collection of algorithms relating to generating a higher resolution image. +// This specific header focuses on single-image non-temporal image scaling, and related tools. +// +// The core functions are EASU and RCAS: +// [EASU] Edge Adaptive Spatial Upsampling ....... 1x to 4x area range spatial scaling, clamped adaptive elliptical filter. +// [RCAS] Robust Contrast Adaptive Sharpening .... A non-scaling variation on CAS. +// RCAS needs to be applied after EASU as a separate pass. +// +// Optional utility functions are: +// [LFGA] Linear Film Grain Applicator ........... Tool to apply film grain after scaling. +// [SRTM] Simple Reversible Tone-Mapper .......... Linear HDR {0 to FP16_MAX} to {0 to 1} and back. +// [TEPD] Temporal Energy Preserving Dither ...... Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. +// See each individual sub-section for inline documentation. +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// FUNCTION PERMUTATIONS +// ===================== +// *F() ..... Single item computation with 32-bit. +// *H() ..... Single item computation with 16-bit, with packing (aka two 16-bit ops in parallel) when possible. +// *Hx2() ... Processing two items in parallel with 16-bit, easier packing. +// Not all interfaces in this file have a *Hx2() form. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [EASU] EDGE ADAPTIVE SPATIAL UPSAMPLING +// +//------------------------------------------------------------------------------------------------------------------------------ +// EASU provides a high quality spatial-only scaling at relatively low cost. +// Meaning EASU is appropiate for laptops and other low-end GPUs. +// Quality from 1x to 4x area scaling is good. +//------------------------------------------------------------------------------------------------------------------------------ +// The scalar uses a modified fast approximation to the standard lanczos(size=2) kernel. +// EASU runs in a single pass, so it applies a directionally and anisotropically adaptive radial lanczos. +// This is also kept as simple as possible to have minimum runtime. +//------------------------------------------------------------------------------------------------------------------------------ +// The lanzcos filter has negative lobes, so by itself it will introduce ringing. +// To remove all ringing, the algorithm uses the nearest 2x2 input texels as a neighborhood, +// and limits output to the minimum and maximum of that neighborhood. +//------------------------------------------------------------------------------------------------------------------------------ +// Input image requirements: +// +// Color needs to be encoded as 3 channel[red, green, blue](e.g.XYZ not supported) +// Each channel needs to be in the range[0, 1] +// Any color primaries are supported +// Display / tonemapping curve needs to be as if presenting to sRGB display or similar(e.g.Gamma 2.0) +// There should be no banding in the input +// There should be no high amplitude noise in the input +// There should be no noise in the input that is not at input pixel granularity +// For performance purposes, use 32bpp formats +//------------------------------------------------------------------------------------------------------------------------------ +// Best to apply EASU at the end of the frame after tonemapping +// but before film grain or composite of the UI. +//------------------------------------------------------------------------------------------------------------------------------ +// Example of including this header for D3D HLSL : +// +// #define A_GPU 1 +// #define A_HLSL 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of including this header for Vulkan GLSL : +// +// #define A_GPU 1 +// #define A_GLSL 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of including this header for Vulkan HLSL : +// +// #define A_GPU 1 +// #define A_HLSL 1 +// #define A_HLSL_6_2 1 +// #define A_NO_16_BIT_CAST 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of declaring the required input callbacks for GLSL : +// The callbacks need to gather4 for each color channel using the specified texture coordinate 'p'. +// EASU uses gather4 to reduce position computation logic and for free Arrays of Structures to Structures of Arrays conversion. +// +// AH4 FsrEasuRH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,0));} +// AH4 FsrEasuGH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,1));} +// AH4 FsrEasuBH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,2));} +// ... +// The FsrEasuCon function needs to be called from the CPU or GPU to set up constants. +// The difference in viewport and input image size is there to support Dynamic Resolution Scaling. +// To use FsrEasuCon() on the CPU, define A_CPU before including ffx_a and ffx_fsr1. +// Including a GPU example here, the 'con0' through 'con3' values would be stored out to a constant buffer. +// AU4 con0,con1,con2,con3; +// FsrEasuCon(con0,con1,con2,con3, +// 1920.0,1080.0, // Viewport size (top left aligned) in the input image which is to be scaled. +// 3840.0,2160.0, // The size of the input image. +// 2560.0,1440.0); // The output resolution. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CONSTANT SETUP +//============================================================================================================================== +// Call to setup required constant values (works on CPU or GPU). +A_STATIC void FsrEasuCon( +outAU4 con0, +outAU4 con1, +outAU4 con2, +outAU4 con3, +// This the rendered image resolution being upscaled +AF1 inputViewportInPixelsX, +AF1 inputViewportInPixelsY, +// This is the resolution of the resource containing the input image (useful for dynamic resolution) +AF1 inputSizeInPixelsX, +AF1 inputSizeInPixelsY, +// This is the display resolution which the input image gets upscaled to +AF1 outputSizeInPixelsX, +AF1 outputSizeInPixelsY){ + // Output integer position to a pixel position in viewport. + con0[0]=AU1_AF1(inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)); + con0[1]=AU1_AF1(inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)); + con0[2]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)-AF1_(0.5)); + con0[3]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)-AF1_(0.5)); + // Viewport pixel position to normalized image space. + // This is used to get upper-left of 'F' tap. + con1[0]=AU1_AF1(ARcpF1(inputSizeInPixelsX)); + con1[1]=AU1_AF1(ARcpF1(inputSizeInPixelsY)); + // Centers of gather4, first offset from upper-left of 'F'. + // +---+---+ + // | | | + // +--(0)--+ + // | b | c | + // +---F---+---+---+ + // | e | f | g | h | + // +--(1)--+--(2)--+ + // | i | j | k | l | + // +---+---+---+---+ + // | n | o | + // +--(3)--+ + // | | | + // +---+---+ + con1[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); + con1[3]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsY)); + // These are from (0) instead of 'F'. + con2[0]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsX)); + con2[1]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); + con2[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); + con2[3]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); + con3[0]=AU1_AF1(AF1_( 0.0)*ARcpF1(inputSizeInPixelsX)); + con3[1]=AU1_AF1(AF1_( 4.0)*ARcpF1(inputSizeInPixelsY)); + con3[2]=con3[3]=0;} + +//If the an offset into the input image resource +A_STATIC void FsrEasuConOffset( + outAU4 con0, + outAU4 con1, + outAU4 con2, + outAU4 con3, + // This the rendered image resolution being upscaled + AF1 inputViewportInPixelsX, + AF1 inputViewportInPixelsY, + // This is the resolution of the resource containing the input image (useful for dynamic resolution) + AF1 inputSizeInPixelsX, + AF1 inputSizeInPixelsY, + // This is the display resolution which the input image gets upscaled to + AF1 outputSizeInPixelsX, + AF1 outputSizeInPixelsY, + // This is the input image offset into the resource containing it (useful for dynamic resolution) + AF1 inputOffsetInPixelsX, + AF1 inputOffsetInPixelsY) { + FsrEasuCon(con0, con1, con2, con3, inputViewportInPixelsX, inputViewportInPixelsY, inputSizeInPixelsX, inputSizeInPixelsY, outputSizeInPixelsX, outputSizeInPixelsY); + con0[2] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsX * ARcpF1(outputSizeInPixelsX) - AF1_(0.5) + inputOffsetInPixelsX); + con0[3] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsY * ARcpF1(outputSizeInPixelsY) - AF1_(0.5) + inputOffsetInPixelsY); +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 32-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(FSR_EASU_F) + // Input callback prototypes, need to be implemented by calling shader + AF4 FsrEasuRF(AF2 p); + AF4 FsrEasuGF(AF2 p); + AF4 FsrEasuBF(AF2 p); +//------------------------------------------------------------------------------------------------------------------------------ + // Filtering for a given tap for the scalar. + void FsrEasuTapF( + inout AF3 aC, // Accumulated color, with negative lobe. + inout AF1 aW, // Accumulated weight. + AF2 off, // Pixel offset from resolve position to tap. + AF2 dir, // Gradient direction. + AF2 len, // Length. + AF1 lob, // Negative lobe strength. + AF1 clp, // Clipping point. + AF3 c){ // Tap color. + // Rotate offset by direction. + AF2 v; + v.x=(off.x*( dir.x))+(off.y*dir.y); + v.y=(off.x*(-dir.y))+(off.y*dir.x); + // Anisotropy. + v*=len; + // Compute distance^2. + AF1 d2=v.x*v.x+v.y*v.y; + // Limit to the window as at corner, 2 taps can easily be outside. + d2=min(d2,clp); + // Approximation of lancos2 without sin() or rcp(), or sqrt() to get x. + // (25/16 * (2/5 * x^2 - 1)^2 - (25/16 - 1)) * (1/4 * x^2 - 1)^2 + // |_______________________________________| |_______________| + // base window + // The general form of the 'base' is, + // (a*(b*x^2-1)^2-(a-1)) + // Where 'a=1/(2*b-b^2)' and 'b' moves around the negative lobe. + AF1 wB=AF1_(2.0/5.0)*d2+AF1_(-1.0); + AF1 wA=lob*d2+AF1_(-1.0); + wB*=wB; + wA*=wA; + wB=AF1_(25.0/16.0)*wB+AF1_(-(25.0/16.0-1.0)); + AF1 w=wB*wA; + // Do weighted average. + aC+=c*w;aW+=w;} +//------------------------------------------------------------------------------------------------------------------------------ + // Accumulate direction and length. + void FsrEasuSetF( + inout AF2 dir, + inout AF1 len, + AF2 pp, + AP1 biS,AP1 biT,AP1 biU,AP1 biV, + AF1 lA,AF1 lB,AF1 lC,AF1 lD,AF1 lE){ + // Compute bilinear weight, branches factor out as predicates are compiler time immediates. + // s t + // u v + AF1 w = AF1_(0.0); + if(biS)w=(AF1_(1.0)-pp.x)*(AF1_(1.0)-pp.y); + if(biT)w= pp.x *(AF1_(1.0)-pp.y); + if(biU)w=(AF1_(1.0)-pp.x)* pp.y ; + if(biV)w= pp.x * pp.y ; + // Direction is the '+' diff. + // a + // b c d + // e + // Then takes magnitude from abs average of both sides of 'c'. + // Length converts gradient reversal to 0, smoothly to non-reversal at 1, shaped, then adding horz and vert terms. + AF1 dc=lD-lC; + AF1 cb=lC-lB; + AF1 lenX=max(abs(dc),abs(cb)); + lenX=APrxLoRcpF1(lenX); + AF1 dirX=lD-lB; + dir.x+=dirX*w; + lenX=ASatF1(abs(dirX)*lenX); + lenX*=lenX; + len+=lenX*w; + // Repeat for the y axis. + AF1 ec=lE-lC; + AF1 ca=lC-lA; + AF1 lenY=max(abs(ec),abs(ca)); + lenY=APrxLoRcpF1(lenY); + AF1 dirY=lE-lA; + dir.y+=dirY*w; + lenY=ASatF1(abs(dirY)*lenY); + lenY*=lenY; + len+=lenY*w;} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrEasuF( + out AF3 pix, + AU2 ip, // Integer pixel position in output. + AU4 con0, // Constants generated by FsrEasuCon(). + AU4 con1, + AU4 con2, + AU4 con3){ +//------------------------------------------------------------------------------------------------------------------------------ + // Get position of 'f'. + AF2 pp=AF2(ip)*AF2_AU2(con0.xy)+AF2_AU2(con0.zw); + AF2 fp=floor(pp); + pp-=fp; +//------------------------------------------------------------------------------------------------------------------------------ + // 12-tap kernel. + // b c + // e f g h + // i j k l + // n o + // Gather 4 ordering. + // a b + // r g + // For packed FP16, need either {rg} or {ab} so using the following setup for gather in all versions, + // a b <- unused (z) + // r g + // a b a b + // r g r g + // a b + // r g <- unused (z) + // Allowing dead-code removal to remove the 'z's. + AF2 p0=fp*AF2_AU2(con1.xy)+AF2_AU2(con1.zw); + // These are from p0 to avoid pulling two constants on pre-Navi hardware. + AF2 p1=p0+AF2_AU2(con2.xy); + AF2 p2=p0+AF2_AU2(con2.zw); + AF2 p3=p0+AF2_AU2(con3.xy); + AF4 bczzR=FsrEasuRF(p0); + AF4 bczzG=FsrEasuGF(p0); + AF4 bczzB=FsrEasuBF(p0); + AF4 ijfeR=FsrEasuRF(p1); + AF4 ijfeG=FsrEasuGF(p1); + AF4 ijfeB=FsrEasuBF(p1); + AF4 klhgR=FsrEasuRF(p2); + AF4 klhgG=FsrEasuGF(p2); + AF4 klhgB=FsrEasuBF(p2); + AF4 zzonR=FsrEasuRF(p3); + AF4 zzonG=FsrEasuGF(p3); + AF4 zzonB=FsrEasuBF(p3); +//------------------------------------------------------------------------------------------------------------------------------ + // Simplest multi-channel approximate luma possible (luma times 2, in 2 FMA/MAD). + AF4 bczzL=bczzB*AF4_(0.5)+(bczzR*AF4_(0.5)+bczzG); + AF4 ijfeL=ijfeB*AF4_(0.5)+(ijfeR*AF4_(0.5)+ijfeG); + AF4 klhgL=klhgB*AF4_(0.5)+(klhgR*AF4_(0.5)+klhgG); + AF4 zzonL=zzonB*AF4_(0.5)+(zzonR*AF4_(0.5)+zzonG); + // Rename. + AF1 bL=bczzL.x; + AF1 cL=bczzL.y; + AF1 iL=ijfeL.x; + AF1 jL=ijfeL.y; + AF1 fL=ijfeL.z; + AF1 eL=ijfeL.w; + AF1 kL=klhgL.x; + AF1 lL=klhgL.y; + AF1 hL=klhgL.z; + AF1 gL=klhgL.w; + AF1 oL=zzonL.z; + AF1 nL=zzonL.w; + // Accumulate for bilinear interpolation. + AF2 dir=AF2_(0.0); + AF1 len=AF1_(0.0); + FsrEasuSetF(dir,len,pp,true, false,false,false,bL,eL,fL,gL,jL); + FsrEasuSetF(dir,len,pp,false,true ,false,false,cL,fL,gL,hL,kL); + FsrEasuSetF(dir,len,pp,false,false,true ,false,fL,iL,jL,kL,nL); + FsrEasuSetF(dir,len,pp,false,false,false,true ,gL,jL,kL,lL,oL); +//------------------------------------------------------------------------------------------------------------------------------ + // Normalize with approximation, and cleanup close to zero. + AF2 dir2=dir*dir; + AF1 dirR=dir2.x+dir2.y; + AP1 zro=dirR w = -m/(n+e+w+s) +// 1 == (w*(n+e+w+s)+m)/(4*w+1) -> w = (1-m)/(n+e+w+s-4*1) +// Then chooses the 'w' which results in no clipping, limits 'w', and multiplies by the 'sharp' amount. +// This solution above has issues with MSAA input as the steps along the gradient cause edge detection issues. +// So RCAS uses 4x the maximum and 4x the minimum (depending on equation)in place of the individual taps. +// As well as switching from 'm' to either the minimum or maximum (depending on side), to help in energy conservation. +// This stabilizes RCAS. +// RCAS does a simple highpass which is normalized against the local contrast then shaped, +// 0.25 +// 0.25 -1 0.25 +// 0.25 +// This is used as a noise detection filter, to reduce the effect of RCAS on grain, and focus on real edges. +// +// GLSL example for the required callbacks : +// +// AH4 FsrRcasLoadH(ASW2 p){return AH4(imageLoad(imgSrc,ASU2(p)));} +// void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b) +// { +// //do any simple input color conversions here or leave empty if none needed +// } +// +// FsrRcasCon need to be called from the CPU or GPU to set up constants. +// Including a GPU example here, the 'con' value would be stored out to a constant buffer. +// +// AU4 con; +// FsrRcasCon(con, +// 0.0); // The scale is {0.0 := maximum sharpness, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. +// --------------- +// RCAS sharpening supports a CAS-like pass-through alpha via, +// #define FSR_RCAS_PASSTHROUGH_ALPHA 1 +// RCAS also supports a define to enable a more expensive path to avoid some sharpening of noise. +// Would suggest it is better to apply film grain after RCAS sharpening (and after scaling) instead of using this define, +// #define FSR_RCAS_DENOISE 1 +//============================================================================================================================== +// This is set at the limit of providing unnatural results for sharpening. +#define FSR_RCAS_LIMIT (0.25-(1.0/16.0)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CONSTANT SETUP +//============================================================================================================================== +// Call to setup required constant values (works on CPU or GPU). +A_STATIC void FsrRcasCon( +outAU4 con, +// The scale is {0.0 := maximum, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. +AF1 sharpness){ + // Transform from stops to linear value. + sharpness=AExp2F1(-sharpness); + varAF2(hSharp)=initAF2(sharpness,sharpness); + con[0]=AU1_AF1(sharpness); + con[1]=AU1_AH2_AF2(hSharp); + con[2]=0; + con[3]=0;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 32-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(FSR_RCAS_F) + // Input callback prototypes that need to be implemented by calling shader + AF4 FsrRcasLoadF(ASU2 p); + void FsrRcasInputF(inout AF1 r,inout AF1 g,inout AF1 b); +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasF( + out AF1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. + out AF1 pixG, + out AF1 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AF1 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // Algorithm uses minimal 3x3 pixel neighborhood. + // b + // d e f + // h + ASU2 sp=ASU2(ip); + AF3 b=FsrRcasLoadF(sp+ASU2( 0,-1)).rgb; + AF3 d=FsrRcasLoadF(sp+ASU2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AF4 ee=FsrRcasLoadF(sp); + AF3 e=ee.rgb;pixA=ee.a; + #else + AF3 e=FsrRcasLoadF(sp).rgb; + #endif + AF3 f=FsrRcasLoadF(sp+ASU2( 1, 0)).rgb; + AF3 h=FsrRcasLoadF(sp+ASU2( 0, 1)).rgb; + // Rename (32-bit) or regroup (16-bit). + AF1 bR=b.r; + AF1 bG=b.g; + AF1 bB=b.b; + AF1 dR=d.r; + AF1 dG=d.g; + AF1 dB=d.b; + AF1 eR=e.r; + AF1 eG=e.g; + AF1 eB=e.b; + AF1 fR=f.r; + AF1 fG=f.g; + AF1 fB=f.b; + AF1 hR=h.r; + AF1 hG=h.g; + AF1 hB=h.b; + // Run optional input transform. + FsrRcasInputF(bR,bG,bB); + FsrRcasInputF(dR,dG,dB); + FsrRcasInputF(eR,eG,eB); + FsrRcasInputF(fR,fG,fB); + FsrRcasInputF(hR,hG,hB); + // Luma times 2. + AF1 bL=bB*AF1_(0.5)+(bR*AF1_(0.5)+bG); + AF1 dL=dB*AF1_(0.5)+(dR*AF1_(0.5)+dG); + AF1 eL=eB*AF1_(0.5)+(eR*AF1_(0.5)+eG); + AF1 fL=fB*AF1_(0.5)+(fR*AF1_(0.5)+fG); + AF1 hL=hB*AF1_(0.5)+(hR*AF1_(0.5)+hG); + // Noise detection. + AF1 nz=AF1_(0.25)*bL+AF1_(0.25)*dL+AF1_(0.25)*fL+AF1_(0.25)*hL-eL; + nz=ASatF1(abs(nz)*APrxMedRcpF1(AMax3F1(AMax3F1(bL,dL,eL),fL,hL)-AMin3F1(AMin3F1(bL,dL,eL),fL,hL))); + nz=AF1_(-0.5)*nz+AF1_(1.0); + // Min and max of ring. + AF1 mn4R=min(AMin3F1(bR,dR,fR),hR); + AF1 mn4G=min(AMin3F1(bG,dG,fG),hG); + AF1 mn4B=min(AMin3F1(bB,dB,fB),hB); + AF1 mx4R=max(AMax3F1(bR,dR,fR),hR); + AF1 mx4G=max(AMax3F1(bG,dG,fG),hG); + AF1 mx4B=max(AMax3F1(bB,dB,fB),hB); + // Immediate constants for peak range. + AF2 peakC=AF2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AF1 hitMinR=min(mn4R,eR)*ARcpF1(AF1_(4.0)*mx4R); + AF1 hitMinG=min(mn4G,eG)*ARcpF1(AF1_(4.0)*mx4G); + AF1 hitMinB=min(mn4B,eB)*ARcpF1(AF1_(4.0)*mx4B); + AF1 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpF1(AF1_(4.0)*mn4R+peakC.y); + AF1 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpF1(AF1_(4.0)*mn4G+peakC.y); + AF1 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpF1(AF1_(4.0)*mn4B+peakC.y); + AF1 lobeR=max(-hitMinR,hitMaxR); + AF1 lobeG=max(-hitMinG,hitMaxG); + AF1 lobeB=max(-hitMinB,hitMaxB); + AF1 lobe=max(AF1_(-FSR_RCAS_LIMIT),min(AMax3F1(lobeR,lobeG,lobeB),AF1_(0.0)))*AF1_AU1(con.x); + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AF1 rcpL=APrxMedRcpF1(AF1_(4.0)*lobe+AF1_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL; + return;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 16-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_H) + // Input callback prototypes that need to be implemented by calling shader + AH4 FsrRcasLoadH(ASW2 p); + void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b); +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasH( + out AH1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. + out AH1 pixG, + out AH1 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AH1 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // Sharpening algorithm uses minimal 3x3 pixel neighborhood. + // b + // d e f + // h + ASW2 sp=ASW2(ip); + AH3 b=FsrRcasLoadH(sp+ASW2( 0,-1)).rgb; + AH3 d=FsrRcasLoadH(sp+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee=FsrRcasLoadH(sp); + AH3 e=ee.rgb;pixA=ee.a; + #else + AH3 e=FsrRcasLoadH(sp).rgb; + #endif + AH3 f=FsrRcasLoadH(sp+ASW2( 1, 0)).rgb; + AH3 h=FsrRcasLoadH(sp+ASW2( 0, 1)).rgb; + // Rename (32-bit) or regroup (16-bit). + AH1 bR=b.r; + AH1 bG=b.g; + AH1 bB=b.b; + AH1 dR=d.r; + AH1 dG=d.g; + AH1 dB=d.b; + AH1 eR=e.r; + AH1 eG=e.g; + AH1 eB=e.b; + AH1 fR=f.r; + AH1 fG=f.g; + AH1 fB=f.b; + AH1 hR=h.r; + AH1 hG=h.g; + AH1 hB=h.b; + // Run optional input transform. + FsrRcasInputH(bR,bG,bB); + FsrRcasInputH(dR,dG,dB); + FsrRcasInputH(eR,eG,eB); + FsrRcasInputH(fR,fG,fB); + FsrRcasInputH(hR,hG,hB); + // Luma times 2. + AH1 bL=bB*AH1_(0.5)+(bR*AH1_(0.5)+bG); + AH1 dL=dB*AH1_(0.5)+(dR*AH1_(0.5)+dG); + AH1 eL=eB*AH1_(0.5)+(eR*AH1_(0.5)+eG); + AH1 fL=fB*AH1_(0.5)+(fR*AH1_(0.5)+fG); + AH1 hL=hB*AH1_(0.5)+(hR*AH1_(0.5)+hG); + // Noise detection. + AH1 nz=AH1_(0.25)*bL+AH1_(0.25)*dL+AH1_(0.25)*fL+AH1_(0.25)*hL-eL; + nz=ASatH1(abs(nz)*APrxMedRcpH1(AMax3H1(AMax3H1(bL,dL,eL),fL,hL)-AMin3H1(AMin3H1(bL,dL,eL),fL,hL))); + nz=AH1_(-0.5)*nz+AH1_(1.0); + // Min and max of ring. + AH1 mn4R=min(AMin3H1(bR,dR,fR),hR); + AH1 mn4G=min(AMin3H1(bG,dG,fG),hG); + AH1 mn4B=min(AMin3H1(bB,dB,fB),hB); + AH1 mx4R=max(AMax3H1(bR,dR,fR),hR); + AH1 mx4G=max(AMax3H1(bG,dG,fG),hG); + AH1 mx4B=max(AMax3H1(bB,dB,fB),hB); + // Immediate constants for peak range. + AH2 peakC=AH2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AH1 hitMinR=min(mn4R,eR)*ARcpH1(AH1_(4.0)*mx4R); + AH1 hitMinG=min(mn4G,eG)*ARcpH1(AH1_(4.0)*mx4G); + AH1 hitMinB=min(mn4B,eB)*ARcpH1(AH1_(4.0)*mx4B); + AH1 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpH1(AH1_(4.0)*mn4R+peakC.y); + AH1 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpH1(AH1_(4.0)*mn4G+peakC.y); + AH1 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpH1(AH1_(4.0)*mn4B+peakC.y); + AH1 lobeR=max(-hitMinR,hitMaxR); + AH1 lobeG=max(-hitMinG,hitMaxG); + AH1 lobeB=max(-hitMinB,hitMaxB); + AH1 lobe=max(AH1_(-FSR_RCAS_LIMIT),min(AMax3H1(lobeR,lobeG,lobeB),AH1_(0.0)))*AH2_AU1(con.y).x; + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AH1 rcpL=APrxMedRcpH1(AH1_(4.0)*lobe+AH1_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PACKED 16-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_HX2) + // Input callback prototypes that need to be implemented by the calling shader + AH4 FsrRcasLoadHx2(ASW2 p); + void FsrRcasInputHx2(inout AH2 r,inout AH2 g,inout AH2 b); +//------------------------------------------------------------------------------------------------------------------------------ + // Can be used to convert from packed Structures of Arrays to Arrays of Structures for store. + void FsrRcasDepackHx2(out AH4 pix0,out AH4 pix1,AH2 pixR,AH2 pixG,AH2 pixB){ + #ifdef A_HLSL + // Invoke a slower path for DX only, since it won't allow uninitialized values. + pix0.a=pix1.a=0.0; + #endif + pix0.rgb=AH3(pixR.x,pixG.x,pixB.x); + pix1.rgb=AH3(pixR.y,pixG.y,pixB.y);} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasHx2( + // Output values are for 2 8x8 tiles in a 16x8 region. + // pix.x = left 8x8 tile + // pix.y = right 8x8 tile + // This enables later processing to easily be packed as well. + out AH2 pixR, + out AH2 pixG, + out AH2 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AH2 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // No scaling algorithm uses minimal 3x3 pixel neighborhood. + ASW2 sp0=ASW2(ip); + AH3 b0=FsrRcasLoadHx2(sp0+ASW2( 0,-1)).rgb; + AH3 d0=FsrRcasLoadHx2(sp0+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee0=FsrRcasLoadHx2(sp0); + AH3 e0=ee0.rgb;pixA.r=ee0.a; + #else + AH3 e0=FsrRcasLoadHx2(sp0).rgb; + #endif + AH3 f0=FsrRcasLoadHx2(sp0+ASW2( 1, 0)).rgb; + AH3 h0=FsrRcasLoadHx2(sp0+ASW2( 0, 1)).rgb; + ASW2 sp1=sp0+ASW2(8,0); + AH3 b1=FsrRcasLoadHx2(sp1+ASW2( 0,-1)).rgb; + AH3 d1=FsrRcasLoadHx2(sp1+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee1=FsrRcasLoadHx2(sp1); + AH3 e1=ee1.rgb;pixA.g=ee1.a; + #else + AH3 e1=FsrRcasLoadHx2(sp1).rgb; + #endif + AH3 f1=FsrRcasLoadHx2(sp1+ASW2( 1, 0)).rgb; + AH3 h1=FsrRcasLoadHx2(sp1+ASW2( 0, 1)).rgb; + // Arrays of Structures to Structures of Arrays conversion. + AH2 bR=AH2(b0.r,b1.r); + AH2 bG=AH2(b0.g,b1.g); + AH2 bB=AH2(b0.b,b1.b); + AH2 dR=AH2(d0.r,d1.r); + AH2 dG=AH2(d0.g,d1.g); + AH2 dB=AH2(d0.b,d1.b); + AH2 eR=AH2(e0.r,e1.r); + AH2 eG=AH2(e0.g,e1.g); + AH2 eB=AH2(e0.b,e1.b); + AH2 fR=AH2(f0.r,f1.r); + AH2 fG=AH2(f0.g,f1.g); + AH2 fB=AH2(f0.b,f1.b); + AH2 hR=AH2(h0.r,h1.r); + AH2 hG=AH2(h0.g,h1.g); + AH2 hB=AH2(h0.b,h1.b); + // Run optional input transform. + FsrRcasInputHx2(bR,bG,bB); + FsrRcasInputHx2(dR,dG,dB); + FsrRcasInputHx2(eR,eG,eB); + FsrRcasInputHx2(fR,fG,fB); + FsrRcasInputHx2(hR,hG,hB); + // Luma times 2. + AH2 bL=bB*AH2_(0.5)+(bR*AH2_(0.5)+bG); + AH2 dL=dB*AH2_(0.5)+(dR*AH2_(0.5)+dG); + AH2 eL=eB*AH2_(0.5)+(eR*AH2_(0.5)+eG); + AH2 fL=fB*AH2_(0.5)+(fR*AH2_(0.5)+fG); + AH2 hL=hB*AH2_(0.5)+(hR*AH2_(0.5)+hG); + // Noise detection. + AH2 nz=AH2_(0.25)*bL+AH2_(0.25)*dL+AH2_(0.25)*fL+AH2_(0.25)*hL-eL; + nz=ASatH2(abs(nz)*APrxMedRcpH2(AMax3H2(AMax3H2(bL,dL,eL),fL,hL)-AMin3H2(AMin3H2(bL,dL,eL),fL,hL))); + nz=AH2_(-0.5)*nz+AH2_(1.0); + // Min and max of ring. + AH2 mn4R=min(AMin3H2(bR,dR,fR),hR); + AH2 mn4G=min(AMin3H2(bG,dG,fG),hG); + AH2 mn4B=min(AMin3H2(bB,dB,fB),hB); + AH2 mx4R=max(AMax3H2(bR,dR,fR),hR); + AH2 mx4G=max(AMax3H2(bG,dG,fG),hG); + AH2 mx4B=max(AMax3H2(bB,dB,fB),hB); + // Immediate constants for peak range. + AH2 peakC=AH2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AH2 hitMinR=min(mn4R,eR)*ARcpH2(AH2_(4.0)*mx4R); + AH2 hitMinG=min(mn4G,eG)*ARcpH2(AH2_(4.0)*mx4G); + AH2 hitMinB=min(mn4B,eB)*ARcpH2(AH2_(4.0)*mx4B); + AH2 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpH2(AH2_(4.0)*mn4R+peakC.y); + AH2 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpH2(AH2_(4.0)*mn4G+peakC.y); + AH2 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpH2(AH2_(4.0)*mn4B+peakC.y); + AH2 lobeR=max(-hitMinR,hitMaxR); + AH2 lobeG=max(-hitMinG,hitMaxG); + AH2 lobeB=max(-hitMinB,hitMaxB); + AH2 lobe=max(AH2_(-FSR_RCAS_LIMIT),min(AMax3H2(lobeR,lobeG,lobeB),AH2_(0.0)))*AH2_(AH2_AU1(con.y).x); + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AH2 rcpL=APrxMedRcpH2(AH2_(4.0)*lobe+AH2_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [LFGA] LINEAR FILM GRAIN APPLICATOR +// +//------------------------------------------------------------------------------------------------------------------------------ +// Adding output-resolution film grain after scaling is a good way to mask both rendering and scaling artifacts. +// Suggest using tiled blue noise as film grain input, with peak noise frequency set for a specific look and feel. +// The 'Lfga*()' functions provide a convenient way to introduce grain. +// These functions limit grain based on distance to signal limits. +// This is done so that the grain is temporally energy preserving, and thus won't modify image tonality. +// Grain application should be done in a linear colorspace. +// The grain should be temporally changing, but have a temporal sum per pixel that adds to zero (non-biased). +//------------------------------------------------------------------------------------------------------------------------------ +// Usage, +// FsrLfga*( +// color, // In/out linear colorspace color {0 to 1} ranged. +// grain, // Per pixel grain texture value {-0.5 to 0.5} ranged, input is 3-channel to support colored grain. +// amount); // Amount of grain (0 to 1} ranged. +//------------------------------------------------------------------------------------------------------------------------------ +// Example if grain texture is monochrome: 'FsrLfgaF(color,AF3_(grain),amount)' +//============================================================================================================================== +#if defined(A_GPU) + // Maximum grain is the minimum distance to the signal limit. + void FsrLfgaF(inout AF3 c,AF3 t,AF1 a){c+=(t*AF3_(a))*min(AF3_(1.0)-c,c);} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + // Half precision version (slower). + void FsrLfgaH(inout AH3 c,AH3 t,AH1 a){c+=(t*AH3_(a))*min(AH3_(1.0)-c,c);} +//------------------------------------------------------------------------------------------------------------------------------ + // Packed half precision version (faster). + void FsrLfgaHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 tR,AH2 tG,AH2 tB,AH1 a){ + cR+=(tR*AH2_(a))*min(AH2_(1.0)-cR,cR);cG+=(tG*AH2_(a))*min(AH2_(1.0)-cG,cG);cB+=(tB*AH2_(a))*min(AH2_(1.0)-cB,cB);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [SRTM] SIMPLE REVERSIBLE TONE-MAPPER +// +//------------------------------------------------------------------------------------------------------------------------------ +// This provides a way to take linear HDR color {0 to FP16_MAX} and convert it into a temporary {0 to 1} ranged post-tonemapped linear. +// The tonemapper preserves RGB ratio, which helps maintain HDR color bleed during filtering. +//------------------------------------------------------------------------------------------------------------------------------ +// Reversible tonemapper usage, +// FsrSrtm*(color); // {0 to FP16_MAX} converted to {0 to 1}. +// FsrSrtmInv*(color); // {0 to 1} converted into {0 to 32768, output peak safe for FP16}. +//============================================================================================================================== +#if defined(A_GPU) + void FsrSrtmF(inout AF3 c){c*=AF3_(ARcpF1(AMax3F1(c.r,c.g,c.b)+AF1_(1.0)));} + // The extra max solves the c=1.0 case (which is a /0). + void FsrSrtmInvF(inout AF3 c){c*=AF3_(ARcpF1(max(AF1_(1.0/32768.0),AF1_(1.0)-AMax3F1(c.r,c.g,c.b))));} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + void FsrSrtmH(inout AH3 c){c*=AH3_(ARcpH1(AMax3H1(c.r,c.g,c.b)+AH1_(1.0)));} + void FsrSrtmInvH(inout AH3 c){c*=AH3_(ARcpH1(max(AH1_(1.0/32768.0),AH1_(1.0)-AMax3H1(c.r,c.g,c.b))));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrSrtmHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ + AH2 rcp=ARcpH2(AMax3H2(cR,cG,cB)+AH2_(1.0));cR*=rcp;cG*=rcp;cB*=rcp;} + void FsrSrtmInvHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ + AH2 rcp=ARcpH2(max(AH2_(1.0/32768.0),AH2_(1.0)-AMax3H2(cR,cG,cB)));cR*=rcp;cG*=rcp;cB*=rcp;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [TEPD] TEMPORAL ENERGY PRESERVING DITHER +// +//------------------------------------------------------------------------------------------------------------------------------ +// Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. +// Gamma 2.0 is used so that the conversion back to linear is just to square the color. +// The conversion comes in 8-bit and 10-bit modes, designed for output to 8-bit UNORM or 10:10:10:2 respectively. +// Given good non-biased temporal blue noise as dither input, +// the output dither will temporally conserve energy. +// This is done by choosing the linear nearest step point instead of perceptual nearest. +// See code below for details. +//------------------------------------------------------------------------------------------------------------------------------ +// DX SPEC RULES FOR FLOAT->UNORM 8-BIT CONVERSION +// =============================================== +// - Output is 'uint(floor(saturate(n)*255.0+0.5))'. +// - Thus rounding is to nearest. +// - NaN gets converted to zero. +// - INF is clamped to {0.0 to 1.0}. +//============================================================================================================================== +#if defined(A_GPU) + // Hand tuned integer position to dither value, with more values than simple checkerboard. + // Only 32-bit has enough precision for this compddation. + // Output is {0 to <1}. + AF1 FsrTepdDitF(AU2 p,AU1 f){ + AF1 x=AF1_(p.x+f); + AF1 y=AF1_(p.y); + // The 1.61803 golden ratio. + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + // Number designed to provide a good visual pattern. + AF1 b=AF1_(1.0/3.69); + x=x*a+(y*b); + return AFractF1(x);} +//------------------------------------------------------------------------------------------------------------------------------ + // This version is 8-bit gamma 2.0. + // The 'c' input is {0 to 1}. + // Output is {0 to 1} ready for image store. + void FsrTepdC8F(inout AF3 c,AF1 dit){ + AF3 n=sqrt(c); + n=floor(n*AF3_(255.0))*AF3_(1.0/255.0); + AF3 a=n*n; + AF3 b=n+AF3_(1.0/255.0);b=b*b; + // Ratio of 'a' to 'b' required to produce 'c'. + // APrxLoRcpF1() won't work here (at least for very high dynamic ranges). + // APrxMedRcpF1() is an IADD,FMA,MUL. + AF3 r=(c-b)*APrxMedRcpF3(a-b); + // Use the ratio as a cutoff to choose 'a' or 'b'. + // AGtZeroF1() is a MUL. + c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // This version is 10-bit gamma 2.0. + // The 'c' input is {0 to 1}. + // Output is {0 to 1} ready for image store. + void FsrTepdC10F(inout AF3 c,AF1 dit){ + AF3 n=sqrt(c); + n=floor(n*AF3_(1023.0))*AF3_(1.0/1023.0); + AF3 a=n*n; + AF3 b=n+AF3_(1.0/1023.0);b=b*b; + AF3 r=(c-b)*APrxMedRcpF3(a-b); + c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/1023.0));} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + AH1 FsrTepdDitH(AU2 p,AU1 f){ + AF1 x=AF1_(p.x+f); + AF1 y=AF1_(p.y); + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + AF1 b=AF1_(1.0/3.69); + x=x*a+(y*b); + return AH1(AFractF1(x));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC8H(inout AH3 c,AH1 dit){ + AH3 n=sqrt(c); + n=floor(n*AH3_(255.0))*AH3_(1.0/255.0); + AH3 a=n*n; + AH3 b=n+AH3_(1.0/255.0);b=b*b; + AH3 r=(c-b)*APrxMedRcpH3(a-b); + c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC10H(inout AH3 c,AH1 dit){ + AH3 n=sqrt(c); + n=floor(n*AH3_(1023.0))*AH3_(1.0/1023.0); + AH3 a=n*n; + AH3 b=n+AH3_(1.0/1023.0);b=b*b; + AH3 r=(c-b)*APrxMedRcpH3(a-b); + c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/1023.0));} +//============================================================================================================================== + // This computes dither for positions 'p' and 'p+{8,0}'. + AH2 FsrTepdDitHx2(AU2 p,AU1 f){ + AF2 x; + x.x=AF1_(p.x+f); + x.y=x.x+AF1_(8.0); + AF1 y=AF1_(p.y); + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + AF1 b=AF1_(1.0/3.69); + x=x*AF2_(a)+AF2_(y*b); + return AH2(AFractF2(x));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC8Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ + AH2 nR=sqrt(cR); + AH2 nG=sqrt(cG); + AH2 nB=sqrt(cB); + nR=floor(nR*AH2_(255.0))*AH2_(1.0/255.0); + nG=floor(nG*AH2_(255.0))*AH2_(1.0/255.0); + nB=floor(nB*AH2_(255.0))*AH2_(1.0/255.0); + AH2 aR=nR*nR; + AH2 aG=nG*nG; + AH2 aB=nB*nB; + AH2 bR=nR+AH2_(1.0/255.0);bR=bR*bR; + AH2 bG=nG+AH2_(1.0/255.0);bG=bG*bG; + AH2 bB=nB+AH2_(1.0/255.0);bB=bB*bB; + AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); + AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); + AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); + cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/255.0)); + cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/255.0)); + cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC10Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ + AH2 nR=sqrt(cR); + AH2 nG=sqrt(cG); + AH2 nB=sqrt(cB); + nR=floor(nR*AH2_(1023.0))*AH2_(1.0/1023.0); + nG=floor(nG*AH2_(1023.0))*AH2_(1.0/1023.0); + nB=floor(nB*AH2_(1023.0))*AH2_(1.0/1023.0); + AH2 aR=nR*nR; + AH2 aG=nG*nG; + AH2 aB=nB*nB; + AH2 bR=nR+AH2_(1.0/1023.0);bR=bR*bR; + AH2 bG=nG+AH2_(1.0/1023.0);bG=bG*bG; + AH2 bB=nB+AH2_(1.0/1023.0);bB=bB*bB; + AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); + AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); + AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); + cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/1023.0)); + cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/1023.0)); + cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/1023.0));} +#endif diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl new file mode 100644 index 00000000..8e8755db --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl @@ -0,0 +1,88 @@ +#version 430 core +precision mediump float; +layout (local_size_x = 64) in; +layout(rgba8, binding = 0, location=0) uniform image2D imgOutput; +layout( location=1 ) uniform sampler2D Source; +layout( location=2 ) uniform float srcX0; +layout( location=3 ) uniform float srcX1; +layout( location=4 ) uniform float srcY0; +layout( location=5 ) uniform float srcY1; +layout( location=6 ) uniform float dstX0; +layout( location=7 ) uniform float dstX1; +layout( location=8 ) uniform float dstY0; +layout( location=9 ) uniform float dstY1; +layout( location=10 ) uniform float scaleX; +layout( location=11 ) uniform float scaleY; + +#define A_GPU 1 +#define A_GLSL 1 +#include "ffx_a.h" + +#define FSR_EASU_F 1 +AU4 con0, con1, con2, con3; +float srcW, srcH, dstW, dstH; +vec2 bLeft, tRight; + +AF2 translate(AF2 pos) { + return AF2(pos.x * scaleX, pos.y * scaleY); +} + +void setBounds(vec2 bottomLeft, vec2 topRight) { + bLeft = bottomLeft; + tRight = topRight; +} + +AF2 translateDest(AF2 pos) { + AF2 translatedPos = AF2(pos.x, pos.y); + translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x; + translatedPos.y = dstY0 > dstY1 ? dstY0 + dstY1 - translatedPos.y - 1: translatedPos.y; + return translatedPos; +} + +AF4 FsrEasuRF(AF2 p) { AF4 res = textureGather(Source, translate(p), 0); return res; } +AF4 FsrEasuGF(AF2 p) { AF4 res = textureGather(Source, translate(p), 1); return res; } +AF4 FsrEasuBF(AF2 p) { AF4 res = textureGather(Source, translate(p), 2); return res; } + +#include "ffx_fsr1.h" + +float insideBox(vec2 v) { + vec2 s = step(bLeft, v) - step(tRight, v); + return s.x * s.y; +} + +void CurrFilter(AU2 pos) +{ + if((insideBox(vec2(pos.x, pos.y))) == 0) { + imageStore(imgOutput, ASU2(pos.x, pos.y), AF4(0,0,0,1)); + return; + } + AF3 c; + FsrEasuF(c, AU2(pos.x - bLeft.x, pos.y - bLeft.y), con0, con1, con2, con3); + imageStore(imgOutput, ASU2(translateDest(pos)), AF4(c, 1)); +} + +void main() { + srcW = abs(srcX1 - srcX0); + srcH = abs(srcY1 - srcY0); + dstW = abs(dstX1 - dstX0); + dstH = abs(dstY1 - dstY0); + + AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u); + + setBounds(vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1), + vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0)); + + // Upscaling + FsrEasuCon(con0, con1, con2, con3, + srcW, srcH, // Viewport size (top left aligned) in the input image which is to be scaled. + srcW, srcH, // The size of the input image. + dstW, dstH); // The output resolution. + + CurrFilter(gxy); + gxy.x += 8u; + CurrFilter(gxy); + gxy.y += 8u; + CurrFilter(gxy); + gxy.x -= 8u; + CurrFilter(gxy); +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_sharpening.glsl b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_sharpening.glsl new file mode 100644 index 00000000..d3b98729 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_sharpening.glsl @@ -0,0 +1,37 @@ +#version 430 core +precision mediump float; +layout (local_size_x = 64) in; +layout(rgba8, binding = 0, location=0) uniform image2D imgOutput; +layout( location=1 ) uniform sampler2D source; +layout( location=2 ) uniform float sharpening; + +#define A_GPU 1 +#define A_GLSL 1 +#include "ffx_a.h" + +#define FSR_RCAS_F 1 +AU4 con0; + +AF4 FsrRcasLoadF(ASU2 p) { return AF4(texelFetch(source, p, 0)); } +void FsrRcasInputF(inout AF1 r, inout AF1 g, inout AF1 b) {} + +#include "ffx_fsr1.h" + +void CurrFilter(AU2 pos) +{ + AF3 c; + FsrRcasF(c.r, c.g, c.b, pos, con0); + imageStore(imgOutput, ASU2(pos), AF4(c, 1)); +} + +void main() { + FsrRcasCon(con0, sharpening); + AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u); + CurrFilter(gxy); + gxy.x += 8u; + CurrFilter(gxy); + gxy.y += 8u; + CurrFilter(gxy); + gxy.x -= 8u; + CurrFilter(gxy); +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fxaa.glsl b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fxaa.glsl new file mode 100644 index 00000000..8bdcbca6 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fxaa.glsl @@ -0,0 +1,1174 @@ +/*============================================================================ + + + NVIDIA FXAA 3.11 by TIMOTHY LOTTES + + +------------------------------------------------------------------------------ +COPYRIGHT (C) 2010, 2011 NVIDIA CORPORATION. ALL RIGHTS RESERVED. +------------------------------------------------------------------------------ +TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED +*AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA +OR ITS SUPPLIERS BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR +CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR +LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, +OR ANY OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE +THIS SOFTWARE, EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + +------------------------------------------------------------------------------ + INTEGRATION CHECKLIST +------------------------------------------------------------------------------ +(1.) +In the shader source, setup defines for the desired configuration. +When providing multiple shaders (for different presets), +simply setup the defines differently in multiple files. +Example, + + #define FXAA_PC 1 + #define FXAA_HLSL_5 1 + #define FXAA_QUALITY__PRESET 12 + +Or, + + #define FXAA_360 1 + +Or, + + #define FXAA_PS3 1 + +Etc. + +(2.) +Then include this file, + + #include "Fxaa3_11.h" + +(3.) +Then call the FXAA pixel shader from within your desired shader. +Look at the FXAA Quality FxaaPixelShader() for docs on inputs. +As for FXAA 3.11 all inputs for all shaders are the same +to enable easy porting between platforms. + + return FxaaPixelShader(...); + +(4.) +Insure pass prior to FXAA outputs RGBL (see next section). +Or use, + + #define FXAA_GREEN_AS_LUMA 1 + +(5.) +Setup engine to provide the following constants +which are used in the FxaaPixelShader() inputs, + + FxaaFloat2 fxaaQualityRcpFrame, + FxaaFloat4 fxaaConsoleRcpFrameOpt, + FxaaFloat4 fxaaConsoleRcpFrameOpt2, + FxaaFloat4 fxaaConsole360RcpFrameOpt2, + FxaaFloat fxaaQualitySubpix, + FxaaFloat fxaaQualityEdgeThreshold, + FxaaFloat fxaaQualityEdgeThresholdMin, + FxaaFloat fxaaConsoleEdgeSharpness, + FxaaFloat fxaaConsoleEdgeThreshold, + FxaaFloat fxaaConsoleEdgeThresholdMin, + FxaaFloat4 fxaaConsole360ConstDir + +Look at the FXAA Quality FxaaPixelShader() for docs on inputs. + +(6.) +Have FXAA vertex shader run as a full screen triangle, +and output "pos" and "fxaaConsolePosPos" +such that inputs in the pixel shader provide, + + // {xy} = center of pixel + FxaaFloat2 pos, + + // {xy__} = upper left of pixel + // {__zw} = lower right of pixel + FxaaFloat4 fxaaConsolePosPos, + +(7.) +Insure the texture sampler(s) used by FXAA are set to bilinear filtering. + + +------------------------------------------------------------------------------ + INTEGRATION - RGBL AND COLORSPACE +------------------------------------------------------------------------------ +FXAA3 requires RGBL as input unless the following is set, + + #define FXAA_GREEN_AS_LUMA 1 + +In which case the engine uses green in place of luma, +and requires RGB input is in a non-linear colorspace. + +RGB should be LDR (low dynamic range). +Specifically do FXAA after tonemapping. + +RGB data as returned by a texture fetch can be non-linear, +or linear when FXAA_GREEN_AS_LUMA is not set. +Note an "sRGB format" texture counts as linear, +because the result of a texture fetch is linear data. +Regular "RGBA8" textures in the sRGB colorspace are non-linear. + +If FXAA_GREEN_AS_LUMA is not set, +luma must be stored in the alpha channel prior to running FXAA. +This luma should be in a perceptual space (could be gamma 2.0). +Example pass before FXAA where output is gamma 2.0 encoded, + + color.rgb = ToneMap(color.rgb); // linear color output + color.rgb = sqrt(color.rgb); // gamma 2.0 color output + return color; + +To use FXAA, + + color.rgb = ToneMap(color.rgb); // linear color output + color.rgb = sqrt(color.rgb); // gamma 2.0 color output + color.a = dot(color.rgb, FxaaFloat3(0.299, 0.587, 0.114)); // compute luma + return color; + +Another example where output is linear encoded, +say for instance writing to an sRGB formated render target, +where the render target does the conversion back to sRGB after blending, + + color.rgb = ToneMap(color.rgb); // linear color output + return color; + +To use FXAA, + + color.rgb = ToneMap(color.rgb); // linear color output + color.a = sqrt(dot(color.rgb, FxaaFloat3(0.299, 0.587, 0.114))); // compute luma + return color; + +Getting luma correct is required for the algorithm to work correctly. + + +------------------------------------------------------------------------------ + BEING LINEARLY CORRECT? +------------------------------------------------------------------------------ +Applying FXAA to a framebuffer with linear RGB color will look worse. +This is very counter intuitive, but happends to be true in this case. +The reason is because dithering artifacts will be more visiable +in a linear colorspace. + + +------------------------------------------------------------------------------ + COMPLEX INTEGRATION +------------------------------------------------------------------------------ +Q. What if the engine is blending into RGB before wanting to run FXAA? + +A. In the last opaque pass prior to FXAA, + have the pass write out luma into alpha. + Then blend into RGB only. + FXAA should be able to run ok + assuming the blending pass did not any add aliasing. + This should be the common case for particles and common blending passes. + +A. Or use FXAA_GREEN_AS_LUMA. + +============================================================================*/ + +#version 430 core + +layout(local_size_x = 16, local_size_y = 16) in; +layout(rgba8, binding = 0) uniform image2D imgOutput; + +uniform sampler2D inputTexture; +layout(location=0) uniform vec2 invResolution; + +#define FXAA_QUALITY__PRESET 12 +#define FXAA_GREEN_AS_LUMA 1 +#define FXAA_PC 1 +#define FXAA_GLSL_130 1 + + +/*============================================================================ + + INTEGRATION KNOBS + +/*==========================================================================*/ +#ifndef FXAA_PC + // + // FXAA Quality + // The high quality PC algorithm. + // + #define FXAA_PC 0 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_GLSL_120 + #define FXAA_GLSL_120 0 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_GLSL_130 + #define FXAA_GLSL_130 0 +#endif +/*==========================================================================*/ +#ifndef FXAA_GREEN_AS_LUMA + // + // For those using non-linear color, + // and either not able to get luma in alpha, or not wanting to, + // this enables FXAA to run using green as a proxy for luma. + // So with this enabled, no need to pack luma in alpha. + // + // This will turn off AA on anything which lacks some amount of green. + // Pure red and blue or combination of only R and B, will get no AA. + // + // Might want to lower the settings for both, + // fxaaConsoleEdgeThresholdMin + // fxaaQualityEdgeThresholdMin + // In order to insure AA does not get turned off on colors + // which contain a minor amount of green. + // + // 1 = On. + // 0 = Off. + // + #define FXAA_GREEN_AS_LUMA 0 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_EARLY_EXIT + // + // Controls algorithm's early exit path. + // On PS3 turning this ON adds 2 cycles to the shader. + // On 360 turning this OFF adds 10ths of a millisecond to the shader. + // Turning this off on console will result in a more blurry image. + // So this defaults to on. + // + // 1 = On. + // 0 = Off. + // + #define FXAA_EARLY_EXIT 1 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_DISCARD + // + // Only valid for PC OpenGL currently. + // Probably will not work when FXAA_GREEN_AS_LUMA = 1. + // + // 1 = Use discard on pixels which don't need AA. + // For APIs which enable concurrent TEX+ROP from same surface. + // 0 = Return unchanged color on pixels which don't need AA. + // + #define FXAA_DISCARD 0 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_FAST_PIXEL_OFFSET + // + // Used for GLSL 120 only. + // + // 1 = GL API supports fast pixel offsets + // 0 = do not use fast pixel offsets + // + #ifdef GL_EXT_gpu_shader4 + #define FXAA_FAST_PIXEL_OFFSET 1 + #endif + #ifdef GL_NV_gpu_shader5 + #define FXAA_FAST_PIXEL_OFFSET 1 + #endif + #ifdef GL_ARB_gpu_shader5 + #define FXAA_FAST_PIXEL_OFFSET 1 + #endif + #ifndef FXAA_FAST_PIXEL_OFFSET + #define FXAA_FAST_PIXEL_OFFSET 0 + #endif +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_GATHER4_ALPHA + // + // 1 = API supports gather4 on alpha channel. + // 0 = API does not support gather4 on alpha channel. + // + #if (FXAA_HLSL_5 == 1) + #define FXAA_GATHER4_ALPHA 1 + #endif + #ifdef GL_ARB_gpu_shader5 + #define FXAA_GATHER4_ALPHA 1 + #endif + #ifdef GL_NV_gpu_shader5 + #define FXAA_GATHER4_ALPHA 1 + #endif + #ifndef FXAA_GATHER4_ALPHA + #define FXAA_GATHER4_ALPHA 0 + #endif +#endif + +/*============================================================================ + FXAA QUALITY - TUNING KNOBS +------------------------------------------------------------------------------ +NOTE the other tuning knobs are now in the shader function inputs! +============================================================================*/ +#ifndef FXAA_QUALITY__PRESET + // + // Choose the quality preset. + // This needs to be compiled into the shader as it effects code. + // Best option to include multiple presets is to + // in each shader define the preset, then include this file. + // + // OPTIONS + // ----------------------------------------------------------------------- + // 10 to 15 - default medium dither (10=fastest, 15=highest quality) + // 20 to 29 - less dither, more expensive (20=fastest, 29=highest quality) + // 39 - no dither, very expensive + // + // NOTES + // ----------------------------------------------------------------------- + // 12 = slightly faster then FXAA 3.9 and higher edge quality (default) + // 13 = about same speed as FXAA 3.9 and better than 12 + // 23 = closest to FXAA 3.9 visually and performance wise + // _ = the lowest digit is directly related to performance + // _ = the highest digit is directly related to style + // + #define FXAA_QUALITY__PRESET 12 +#endif + + +/*============================================================================ + + FXAA QUALITY - PRESETS + +============================================================================*/ + +/*============================================================================ + FXAA QUALITY - MEDIUM DITHER PRESETS +============================================================================*/ +#if (FXAA_QUALITY__PRESET == 10) + #define FXAA_QUALITY__PS 3 + #define FXAA_QUALITY__P0 1.5 + #define FXAA_QUALITY__P1 3.0 + #define FXAA_QUALITY__P2 12.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 11) + #define FXAA_QUALITY__PS 4 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.5 + #define FXAA_QUALITY__P2 3.0 + #define FXAA_QUALITY__P3 12.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 12) + #define FXAA_QUALITY__PS 5 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.5 + #define FXAA_QUALITY__P2 2.0 + #define FXAA_QUALITY__P3 4.0 + #define FXAA_QUALITY__P4 12.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 13) + #define FXAA_QUALITY__PS 6 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.5 + #define FXAA_QUALITY__P2 2.0 + #define FXAA_QUALITY__P3 2.0 + #define FXAA_QUALITY__P4 4.0 + #define FXAA_QUALITY__P5 12.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 14) + #define FXAA_QUALITY__PS 7 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.5 + #define FXAA_QUALITY__P2 2.0 + #define FXAA_QUALITY__P3 2.0 + #define FXAA_QUALITY__P4 2.0 + #define FXAA_QUALITY__P5 4.0 + #define FXAA_QUALITY__P6 12.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 15) + #define FXAA_QUALITY__PS 8 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.5 + #define FXAA_QUALITY__P2 2.0 + #define FXAA_QUALITY__P3 2.0 + #define FXAA_QUALITY__P4 2.0 + #define FXAA_QUALITY__P5 2.0 + #define FXAA_QUALITY__P6 4.0 + #define FXAA_QUALITY__P7 12.0 +#endif + +/*============================================================================ + FXAA QUALITY - LOW DITHER PRESETS +============================================================================*/ +#if (FXAA_QUALITY__PRESET == 20) + #define FXAA_QUALITY__PS 3 + #define FXAA_QUALITY__P0 1.5 + #define FXAA_QUALITY__P1 2.0 + #define FXAA_QUALITY__P2 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 21) + #define FXAA_QUALITY__PS 4 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.5 + #define FXAA_QUALITY__P2 2.0 + #define FXAA_QUALITY__P3 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 22) + #define FXAA_QUALITY__PS 5 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.5 + #define FXAA_QUALITY__P2 2.0 + #define FXAA_QUALITY__P3 2.0 + #define FXAA_QUALITY__P4 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 23) + #define FXAA_QUALITY__PS 6 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.5 + #define FXAA_QUALITY__P2 2.0 + #define FXAA_QUALITY__P3 2.0 + #define FXAA_QUALITY__P4 2.0 + #define FXAA_QUALITY__P5 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 24) + #define FXAA_QUALITY__PS 7 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.5 + #define FXAA_QUALITY__P2 2.0 + #define FXAA_QUALITY__P3 2.0 + #define FXAA_QUALITY__P4 2.0 + #define FXAA_QUALITY__P5 3.0 + #define FXAA_QUALITY__P6 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 25) + #define FXAA_QUALITY__PS 8 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.5 + #define FXAA_QUALITY__P2 2.0 + #define FXAA_QUALITY__P3 2.0 + #define FXAA_QUALITY__P4 2.0 + #define FXAA_QUALITY__P5 2.0 + #define FXAA_QUALITY__P6 4.0 + #define FXAA_QUALITY__P7 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 26) + #define FXAA_QUALITY__PS 9 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.5 + #define FXAA_QUALITY__P2 2.0 + #define FXAA_QUALITY__P3 2.0 + #define FXAA_QUALITY__P4 2.0 + #define FXAA_QUALITY__P5 2.0 + #define FXAA_QUALITY__P6 2.0 + #define FXAA_QUALITY__P7 4.0 + #define FXAA_QUALITY__P8 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 27) + #define FXAA_QUALITY__PS 10 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.5 + #define FXAA_QUALITY__P2 2.0 + #define FXAA_QUALITY__P3 2.0 + #define FXAA_QUALITY__P4 2.0 + #define FXAA_QUALITY__P5 2.0 + #define FXAA_QUALITY__P6 2.0 + #define FXAA_QUALITY__P7 2.0 + #define FXAA_QUALITY__P8 4.0 + #define FXAA_QUALITY__P9 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 28) + #define FXAA_QUALITY__PS 11 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.5 + #define FXAA_QUALITY__P2 2.0 + #define FXAA_QUALITY__P3 2.0 + #define FXAA_QUALITY__P4 2.0 + #define FXAA_QUALITY__P5 2.0 + #define FXAA_QUALITY__P6 2.0 + #define FXAA_QUALITY__P7 2.0 + #define FXAA_QUALITY__P8 2.0 + #define FXAA_QUALITY__P9 4.0 + #define FXAA_QUALITY__P10 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 29) + #define FXAA_QUALITY__PS 12 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.5 + #define FXAA_QUALITY__P2 2.0 + #define FXAA_QUALITY__P3 2.0 + #define FXAA_QUALITY__P4 2.0 + #define FXAA_QUALITY__P5 2.0 + #define FXAA_QUALITY__P6 2.0 + #define FXAA_QUALITY__P7 2.0 + #define FXAA_QUALITY__P8 2.0 + #define FXAA_QUALITY__P9 2.0 + #define FXAA_QUALITY__P10 4.0 + #define FXAA_QUALITY__P11 8.0 +#endif + +/*============================================================================ + FXAA QUALITY - EXTREME QUALITY +============================================================================*/ +#if (FXAA_QUALITY__PRESET == 39) + #define FXAA_QUALITY__PS 12 + #define FXAA_QUALITY__P0 1.0 + #define FXAA_QUALITY__P1 1.0 + #define FXAA_QUALITY__P2 1.0 + #define FXAA_QUALITY__P3 1.0 + #define FXAA_QUALITY__P4 1.0 + #define FXAA_QUALITY__P5 1.5 + #define FXAA_QUALITY__P6 2.0 + #define FXAA_QUALITY__P7 2.0 + #define FXAA_QUALITY__P8 2.0 + #define FXAA_QUALITY__P9 2.0 + #define FXAA_QUALITY__P10 4.0 + #define FXAA_QUALITY__P11 8.0 +#endif + + + +/*============================================================================ + + API PORTING + +============================================================================*/ +#if (FXAA_GLSL_120 == 1) || (FXAA_GLSL_130 == 1) + #define FxaaBool bool + #define FxaaDiscard discard + #define FxaaFloat float + #define FxaaFloat2 vec2 + #define FxaaFloat3 vec3 + #define FxaaFloat4 vec4 + #define FxaaHalf float + #define FxaaHalf2 vec2 + #define FxaaHalf3 vec3 + #define FxaaHalf4 vec4 + #define FxaaInt2 ivec2 + #define FxaaSat(x) clamp(x, 0.0, 1.0) + #define FxaaTex sampler2D +#else + #define FxaaBool bool + #define FxaaDiscard clip(-1) + #define FxaaFloat float + #define FxaaFloat2 float2 + #define FxaaFloat3 float3 + #define FxaaFloat4 float4 + #define FxaaHalf half + #define FxaaHalf2 half2 + #define FxaaHalf3 half3 + #define FxaaHalf4 half4 + #define FxaaSat(x) saturate(x) +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_GLSL_120 == 1) + // Requires, + // #version 120 + // And at least, + // #extension GL_EXT_gpu_shader4 : enable + // (or set FXAA_FAST_PIXEL_OFFSET 1 to work like DX9) + #define FxaaTexTop(t, p) texture2DLod(t, p, 0.0) + #if (FXAA_FAST_PIXEL_OFFSET == 1) + #define FxaaTexOff(t, p, o, r) texture2DLodOffset(t, p, 0.0, o) + #else + #define FxaaTexOff(t, p, o, r) texture2DLod(t, p + (o * r), 0.0) + #endif + #if (FXAA_GATHER4_ALPHA == 1) + // use #extension GL_ARB_gpu_shader5 : enable + #define FxaaTexAlpha4(t, p) textureGather(t, p, 3) + #define FxaaTexOffAlpha4(t, p, o) textureGatherOffset(t, p, o, 3) + #define FxaaTexGreen4(t, p) textureGather(t, p, 1) + #define FxaaTexOffGreen4(t, p, o) textureGatherOffset(t, p, o, 1) + #endif +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_GLSL_130 == 1) + // Requires "#version 130" or better + #define FxaaTexTop(t, p) textureLod(t, p, 0.0) + #define FxaaTexOff(t, p, o, r) textureLodOffset(t, p, 0.0, o) + #if (FXAA_GATHER4_ALPHA == 1) + // use #extension GL_ARB_gpu_shader5 : enable + #define FxaaTexAlpha4(t, p) textureGather(t, p, 3) + #define FxaaTexOffAlpha4(t, p, o) textureGatherOffset(t, p, o, 3) + #define FxaaTexGreen4(t, p) textureGather(t, p, 1) + #define FxaaTexOffGreen4(t, p, o) textureGatherOffset(t, p, o, 1) + #endif +#endif + + +/*============================================================================ + GREEN AS LUMA OPTION SUPPORT FUNCTION +============================================================================*/ +#if (FXAA_GREEN_AS_LUMA == 0) + FxaaFloat FxaaLuma(FxaaFloat4 rgba) { return rgba.w; } +#else + FxaaFloat FxaaLuma(FxaaFloat4 rgba) { return rgba.y; } +#endif + + + + +/*============================================================================ + + FXAA3 QUALITY - PC + +============================================================================*/ +#if (FXAA_PC == 1) +/*--------------------------------------------------------------------------*/ +FxaaFloat4 FxaaPixelShader( + // + // Use noperspective interpolation here (turn off perspective interpolation). + // {xy} = center of pixel + FxaaFloat2 pos, + // + // Used only for FXAA Console, and not used on the 360 version. + // Use noperspective interpolation here (turn off perspective interpolation). + // {xy__} = upper left of pixel + // {__zw} = lower right of pixel + FxaaFloat4 fxaaConsolePosPos, + // + // Input color texture. + // {rgb_} = color in linear or perceptual color space + // if (FXAA_GREEN_AS_LUMA == 0) + // {___a} = luma in perceptual color space (not linear) + FxaaTex tex, + // + // Only used on the optimized 360 version of FXAA Console. + // For everything but 360, just use the same input here as for "tex". + // For 360, same texture, just alias with a 2nd sampler. + // This sampler needs to have an exponent bias of -1. + FxaaTex fxaaConsole360TexExpBiasNegOne, + // + // Only used on the optimized 360 version of FXAA Console. + // For everything but 360, just use the same input here as for "tex". + // For 360, same texture, just alias with a 3nd sampler. + // This sampler needs to have an exponent bias of -2. + FxaaTex fxaaConsole360TexExpBiasNegTwo, + // + // Only used on FXAA Quality. + // This must be from a constant/uniform. + // {x_} = 1.0/screenWidthInPixels + // {_y} = 1.0/screenHeightInPixels + FxaaFloat2 fxaaQualityRcpFrame, + // + // Only used on FXAA Console. + // This must be from a constant/uniform. + // This effects sub-pixel AA quality and inversely sharpness. + // Where N ranges between, + // N = 0.50 (default) + // N = 0.33 (sharper) + // {x___} = -N/screenWidthInPixels + // {_y__} = -N/screenHeightInPixels + // {__z_} = N/screenWidthInPixels + // {___w} = N/screenHeightInPixels + FxaaFloat4 fxaaConsoleRcpFrameOpt, + // + // Only used on FXAA Console. + // Not used on 360, but used on PS3 and PC. + // This must be from a constant/uniform. + // {x___} = -2.0/screenWidthInPixels + // {_y__} = -2.0/screenHeightInPixels + // {__z_} = 2.0/screenWidthInPixels + // {___w} = 2.0/screenHeightInPixels + FxaaFloat4 fxaaConsoleRcpFrameOpt2, + // + // Only used on FXAA Console. + // Only used on 360 in place of fxaaConsoleRcpFrameOpt2. + // This must be from a constant/uniform. + // {x___} = 8.0/screenWidthInPixels + // {_y__} = 8.0/screenHeightInPixels + // {__z_} = -4.0/screenWidthInPixels + // {___w} = -4.0/screenHeightInPixels + FxaaFloat4 fxaaConsole360RcpFrameOpt2, + // + // Only used on FXAA Quality. + // This used to be the FXAA_QUALITY__SUBPIX define. + // It is here now to allow easier tuning. + // Choose the amount of sub-pixel aliasing removal. + // This can effect sharpness. + // 1.00 - upper limit (softer) + // 0.75 - default amount of filtering + // 0.50 - lower limit (sharper, less sub-pixel aliasing removal) + // 0.25 - almost off + // 0.00 - completely off + FxaaFloat fxaaQualitySubpix, + // + // Only used on FXAA Quality. + // This used to be the FXAA_QUALITY__EDGE_THRESHOLD define. + // It is here now to allow easier tuning. + // The minimum amount of local contrast required to apply algorithm. + // 0.333 - too little (faster) + // 0.250 - low quality + // 0.166 - default + // 0.125 - high quality + // 0.063 - overkill (slower) + FxaaFloat fxaaQualityEdgeThreshold, + // + // Only used on FXAA Quality. + // This used to be the FXAA_QUALITY__EDGE_THRESHOLD_MIN define. + // It is here now to allow easier tuning. + // Trims the algorithm from processing darks. + // 0.0833 - upper limit (default, the start of visible unfiltered edges) + // 0.0625 - high quality (faster) + // 0.0312 - visible limit (slower) + // Special notes when using FXAA_GREEN_AS_LUMA, + // Likely want to set this to zero. + // As colors that are mostly not-green + // will appear very dark in the green channel! + // Tune by looking at mostly non-green content, + // then start at zero and increase until aliasing is a problem. + FxaaFloat fxaaQualityEdgeThresholdMin, + // + // Only used on FXAA Console. + // This used to be the FXAA_CONSOLE__EDGE_SHARPNESS define. + // It is here now to allow easier tuning. + // This does not effect PS3, as this needs to be compiled in. + // Use FXAA_CONSOLE__PS3_EDGE_SHARPNESS for PS3. + // Due to the PS3 being ALU bound, + // there are only three safe values here: 2 and 4 and 8. + // These options use the shaders ability to a free *|/ by 2|4|8. + // For all other platforms can be a non-power of two. + // 8.0 is sharper (default!!!) + // 4.0 is softer + // 2.0 is really soft (good only for vector graphics inputs) + FxaaFloat fxaaConsoleEdgeSharpness, + // + // Only used on FXAA Console. + // This used to be the FXAA_CONSOLE__EDGE_THRESHOLD define. + // It is here now to allow easier tuning. + // This does not effect PS3, as this needs to be compiled in. + // Use FXAA_CONSOLE__PS3_EDGE_THRESHOLD for PS3. + // Due to the PS3 being ALU bound, + // there are only two safe values here: 1/4 and 1/8. + // These options use the shaders ability to a free *|/ by 2|4|8. + // The console setting has a different mapping than the quality setting. + // Other platforms can use other values. + // 0.125 leaves less aliasing, but is softer (default!!!) + // 0.25 leaves more aliasing, and is sharper + FxaaFloat fxaaConsoleEdgeThreshold, + // + // Only used on FXAA Console. + // This used to be the FXAA_CONSOLE__EDGE_THRESHOLD_MIN define. + // It is here now to allow easier tuning. + // Trims the algorithm from processing darks. + // The console setting has a different mapping than the quality setting. + // This only applies when FXAA_EARLY_EXIT is 1. + // This does not apply to PS3, + // PS3 was simplified to avoid more shader instructions. + // 0.06 - faster but more aliasing in darks + // 0.05 - default + // 0.04 - slower and less aliasing in darks + // Special notes when using FXAA_GREEN_AS_LUMA, + // Likely want to set this to zero. + // As colors that are mostly not-green + // will appear very dark in the green channel! + // Tune by looking at mostly non-green content, + // then start at zero and increase until aliasing is a problem. + FxaaFloat fxaaConsoleEdgeThresholdMin, + // + // Extra constants for 360 FXAA Console only. + // Use zeros or anything else for other platforms. + // These must be in physical constant registers and NOT immedates. + // Immedates will result in compiler un-optimizing. + // {xyzw} = float4(1.0, -1.0, 0.25, -0.25) + FxaaFloat4 fxaaConsole360ConstDir +) { +/*--------------------------------------------------------------------------*/ + FxaaFloat2 posM; + posM.x = pos.x; + posM.y = pos.y; + #if (FXAA_GATHER4_ALPHA == 1) + #if (FXAA_DISCARD == 0) + FxaaFloat4 rgbyM = FxaaTexTop(tex, posM); + #if (FXAA_GREEN_AS_LUMA == 0) + #define lumaM rgbyM.w + #else + #define lumaM rgbyM.y + #endif + #endif + #if (FXAA_GREEN_AS_LUMA == 0) + FxaaFloat4 luma4A = FxaaTexAlpha4(tex, posM); + FxaaFloat4 luma4B = FxaaTexOffAlpha4(tex, posM, FxaaInt2(-1, -1)); + #else + FxaaFloat4 luma4A = FxaaTexGreen4(tex, posM); + FxaaFloat4 luma4B = FxaaTexOffGreen4(tex, posM, FxaaInt2(-1, -1)); + #endif + #if (FXAA_DISCARD == 1) + #define lumaM luma4A.w + #endif + #define lumaE luma4A.z + #define lumaS luma4A.x + #define lumaSE luma4A.y + #define lumaNW luma4B.w + #define lumaN luma4B.z + #define lumaW luma4B.x + #else + FxaaFloat4 rgbyM = FxaaTexTop(tex, posM); + #if (FXAA_GREEN_AS_LUMA == 0) + #define lumaM rgbyM.w + #else + #define lumaM rgbyM.y + #endif + FxaaFloat lumaS = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 0, 1), fxaaQualityRcpFrame.xy)); + FxaaFloat lumaE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 1, 0), fxaaQualityRcpFrame.xy)); + FxaaFloat lumaN = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 0,-1), fxaaQualityRcpFrame.xy)); + FxaaFloat lumaW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1, 0), fxaaQualityRcpFrame.xy)); + #endif +/*--------------------------------------------------------------------------*/ + FxaaFloat maxSM = max(lumaS, lumaM); + FxaaFloat minSM = min(lumaS, lumaM); + FxaaFloat maxESM = max(lumaE, maxSM); + FxaaFloat minESM = min(lumaE, minSM); + FxaaFloat maxWN = max(lumaN, lumaW); + FxaaFloat minWN = min(lumaN, lumaW); + FxaaFloat rangeMax = max(maxWN, maxESM); + FxaaFloat rangeMin = min(minWN, minESM); + FxaaFloat rangeMaxScaled = rangeMax * fxaaQualityEdgeThreshold; + FxaaFloat range = rangeMax - rangeMin; + FxaaFloat rangeMaxClamped = max(fxaaQualityEdgeThresholdMin, rangeMaxScaled); + FxaaBool earlyExit = range < rangeMaxClamped; +/*--------------------------------------------------------------------------*/ + if(earlyExit) + #if (FXAA_DISCARD == 1) + FxaaDiscard; + #else + return rgbyM; + #endif +/*--------------------------------------------------------------------------*/ + #if (FXAA_GATHER4_ALPHA == 0) + FxaaFloat lumaNW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1,-1), fxaaQualityRcpFrame.xy)); + FxaaFloat lumaSE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 1, 1), fxaaQualityRcpFrame.xy)); + FxaaFloat lumaNE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 1,-1), fxaaQualityRcpFrame.xy)); + FxaaFloat lumaSW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1, 1), fxaaQualityRcpFrame.xy)); + #else + FxaaFloat lumaNE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(1, -1), fxaaQualityRcpFrame.xy)); + FxaaFloat lumaSW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1, 1), fxaaQualityRcpFrame.xy)); + #endif +/*--------------------------------------------------------------------------*/ + FxaaFloat lumaNS = lumaN + lumaS; + FxaaFloat lumaWE = lumaW + lumaE; + FxaaFloat subpixRcpRange = 1.0/range; + FxaaFloat subpixNSWE = lumaNS + lumaWE; + FxaaFloat edgeHorz1 = (-2.0 * lumaM) + lumaNS; + FxaaFloat edgeVert1 = (-2.0 * lumaM) + lumaWE; +/*--------------------------------------------------------------------------*/ + FxaaFloat lumaNESE = lumaNE + lumaSE; + FxaaFloat lumaNWNE = lumaNW + lumaNE; + FxaaFloat edgeHorz2 = (-2.0 * lumaE) + lumaNESE; + FxaaFloat edgeVert2 = (-2.0 * lumaN) + lumaNWNE; +/*--------------------------------------------------------------------------*/ + FxaaFloat lumaNWSW = lumaNW + lumaSW; + FxaaFloat lumaSWSE = lumaSW + lumaSE; + FxaaFloat edgeHorz4 = (abs(edgeHorz1) * 2.0) + abs(edgeHorz2); + FxaaFloat edgeVert4 = (abs(edgeVert1) * 2.0) + abs(edgeVert2); + FxaaFloat edgeHorz3 = (-2.0 * lumaW) + lumaNWSW; + FxaaFloat edgeVert3 = (-2.0 * lumaS) + lumaSWSE; + FxaaFloat edgeHorz = abs(edgeHorz3) + edgeHorz4; + FxaaFloat edgeVert = abs(edgeVert3) + edgeVert4; +/*--------------------------------------------------------------------------*/ + FxaaFloat subpixNWSWNESE = lumaNWSW + lumaNESE; + FxaaFloat lengthSign = fxaaQualityRcpFrame.x; + FxaaBool horzSpan = edgeHorz >= edgeVert; + FxaaFloat subpixA = subpixNSWE * 2.0 + subpixNWSWNESE; +/*--------------------------------------------------------------------------*/ + if(!horzSpan) lumaN = lumaW; + if(!horzSpan) lumaS = lumaE; + if(horzSpan) lengthSign = fxaaQualityRcpFrame.y; + FxaaFloat subpixB = (subpixA * (1.0/12.0)) - lumaM; +/*--------------------------------------------------------------------------*/ + FxaaFloat gradientN = lumaN - lumaM; + FxaaFloat gradientS = lumaS - lumaM; + FxaaFloat lumaNN = lumaN + lumaM; + FxaaFloat lumaSS = lumaS + lumaM; + FxaaBool pairN = abs(gradientN) >= abs(gradientS); + FxaaFloat gradient = max(abs(gradientN), abs(gradientS)); + if(pairN) lengthSign = -lengthSign; + FxaaFloat subpixC = FxaaSat(abs(subpixB) * subpixRcpRange); +/*--------------------------------------------------------------------------*/ + FxaaFloat2 posB; + posB.x = posM.x; + posB.y = posM.y; + FxaaFloat2 offNP; + offNP.x = (!horzSpan) ? 0.0 : fxaaQualityRcpFrame.x; + offNP.y = ( horzSpan) ? 0.0 : fxaaQualityRcpFrame.y; + if(!horzSpan) posB.x += lengthSign * 0.5; + if( horzSpan) posB.y += lengthSign * 0.5; +/*--------------------------------------------------------------------------*/ + FxaaFloat2 posN; + posN.x = posB.x - offNP.x * FXAA_QUALITY__P0; + posN.y = posB.y - offNP.y * FXAA_QUALITY__P0; + FxaaFloat2 posP; + posP.x = posB.x + offNP.x * FXAA_QUALITY__P0; + posP.y = posB.y + offNP.y * FXAA_QUALITY__P0; + FxaaFloat subpixD = ((-2.0)*subpixC) + 3.0; + FxaaFloat lumaEndN = FxaaLuma(FxaaTexTop(tex, posN)); + FxaaFloat subpixE = subpixC * subpixC; + FxaaFloat lumaEndP = FxaaLuma(FxaaTexTop(tex, posP)); +/*--------------------------------------------------------------------------*/ + if(!pairN) lumaNN = lumaSS; + FxaaFloat gradientScaled = gradient * 1.0/4.0; + FxaaFloat lumaMM = lumaM - lumaNN * 0.5; + FxaaFloat subpixF = subpixD * subpixE; + FxaaBool lumaMLTZero = lumaMM < 0.0; +/*--------------------------------------------------------------------------*/ + lumaEndN -= lumaNN * 0.5; + lumaEndP -= lumaNN * 0.5; + FxaaBool doneN = abs(lumaEndN) >= gradientScaled; + FxaaBool doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P1; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P1; + FxaaBool doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P1; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P1; +/*--------------------------------------------------------------------------*/ + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P2; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P2; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P2; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P2; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY__PS > 3) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P3; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P3; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P3; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P3; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY__PS > 4) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P4; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P4; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P4; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P4; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY__PS > 5) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P5; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P5; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P5; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P5; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY__PS > 6) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P6; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P6; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P6; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P6; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY__PS > 7) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P7; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P7; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P7; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P7; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY__PS > 8) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P8; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P8; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P8; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P8; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY__PS > 9) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P9; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P9; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P9; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P9; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY__PS > 10) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P10; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P10; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P10; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P10; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY__PS > 11) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P11; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P11; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P11; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P11; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY__PS > 12) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY__P12; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY__P12; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY__P12; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY__P12; +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } +/*--------------------------------------------------------------------------*/ + FxaaFloat dstN = posM.x - posN.x; + FxaaFloat dstP = posP.x - posM.x; + if(!horzSpan) dstN = posM.y - posN.y; + if(!horzSpan) dstP = posP.y - posM.y; +/*--------------------------------------------------------------------------*/ + FxaaBool goodSpanN = (lumaEndN < 0.0) != lumaMLTZero; + FxaaFloat spanLength = (dstP + dstN); + FxaaBool goodSpanP = (lumaEndP < 0.0) != lumaMLTZero; + FxaaFloat spanLengthRcp = 1.0/spanLength; +/*--------------------------------------------------------------------------*/ + FxaaBool directionN = dstN < dstP; + FxaaFloat dst = min(dstN, dstP); + FxaaBool goodSpan = directionN ? goodSpanN : goodSpanP; + FxaaFloat subpixG = subpixF * subpixF; + FxaaFloat pixelOffset = (dst * (-spanLengthRcp)) + 0.5; + FxaaFloat subpixH = subpixG * fxaaQualitySubpix; +/*--------------------------------------------------------------------------*/ + FxaaFloat pixelOffsetGood = goodSpan ? pixelOffset : 0.0; + FxaaFloat pixelOffsetSubpix = max(pixelOffsetGood, subpixH); + if(!horzSpan) posM.x += pixelOffsetSubpix * lengthSign; + if( horzSpan) posM.y += pixelOffsetSubpix * lengthSign; + #if (FXAA_DISCARD == 1) + return FxaaTexTop(tex, posM); + #else + return FxaaFloat4(FxaaTexTop(tex, posM).xyz, lumaM); + #endif +} +/*==========================================================================*/ +#endif + +vec4 mainImage(vec2 fragCoord) +{ + vec2 rcpFrame = 1./invResolution.xy; + vec2 uv2 = fragCoord.xy / invResolution.xy; + + float fxaaQualitySubpix = 0.75; // [0..1], default 0.75 + float fxaaQualityEdgeThreshold = 0.166; // [0.125..0.33], default 0.166 + float fxaaQualityEdgeThresholdMin = 0.02;//0.0625; // ? + vec4 dummy4 = vec4(0.0,0.0,0.0,0.0); + float dummy1 = 0.0; + + vec4 col = FxaaPixelShader(uv2, dummy4, + inputTexture, inputTexture, inputTexture, + rcpFrame, dummy4, dummy4, dummy4, + fxaaQualitySubpix, fxaaQualityEdgeThreshold, + fxaaQualityEdgeThresholdMin, + dummy1, dummy1, dummy1, dummy4); + + vec4 fragColor = vec4( col.xyz, 1. ); + + return fragColor; +} + +void main() +{ + ivec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4); + for(int i = 0; i < 4; i++) + { + for(int j = 0; j < 4; j++) + { + ivec2 texelCoord = ivec2(loc.x + i, loc.y + j); + vec4 outColor = mainImage(texelCoord + vec2(0.5)); + imageStore(imgOutput, texelCoord, outColor); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa.hlsl b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa.hlsl new file mode 100644 index 00000000..2201f78c --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa.hlsl @@ -0,0 +1,1361 @@ +/** + * Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com) + * Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) + * Copyright (C) 2013 Belen Masia (bmasia@unizar.es) + * Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com) + * Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. As clarification, there + * is no requirement that the copyright notice and permission be included in + * binary distributions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +/** + * _______ ___ ___ ___ ___ + * / || \/ | / \ / \ + * | (---- | \ / | / ^ \ / ^ \ + * \ \ | |\/| | / /_\ \ / /_\ \ + * ----) | | | | | / _____ \ / _____ \ + * |_______/ |__| |__| /__/ \__\ /__/ \__\ + * + * E N H A N C E D + * S U B P I X E L M O R P H O L O G I C A L A N T I A L I A S I N G + * + * http://www.iryoku.com/smaa/ + * + * Hi, welcome aboard! + * + * Here you'll find instructions to get the shader up and running as fast as + * possible. + * + * IMPORTANTE NOTICE: when updating, remember to update both this file and the + * precomputed textures! They may change from version to version. + * + * The shader has three passes, chained together as follows: + * + * |input|------------------� + * v | + * [ SMAA*EdgeDetection ] | + * v | + * |edgesTex| | + * v | + * [ SMAABlendingWeightCalculation ] | + * v | + * |blendTex| | + * v | + * [ SMAANeighborhoodBlending ] <------� + * v + * |output| + * + * Note that each [pass] has its own vertex and pixel shader. Remember to use + * oversized triangles instead of quads to avoid overshading along the + * diagonal. + * + * You've three edge detection methods to choose from: luma, color or depth. + * They represent different quality/performance and anti-aliasing/sharpness + * tradeoffs, so our recommendation is for you to choose the one that best + * suits your particular scenario: + * + * - Depth edge detection is usually the fastest but it may miss some edges. + * + * - Luma edge detection is usually more expensive than depth edge detection, + * but catches visible edges that depth edge detection can miss. + * + * - Color edge detection is usually the most expensive one but catches + * chroma-only edges. + * + * For quickstarters: just use luma edge detection. + * + * The general advice is to not rush the integration process and ensure each + * step is done correctly (don't try to integrate SMAA T2x with predicated edge + * detection from the start!). Ok then, let's go! + * + * 1. The first step is to create two RGBA temporal render targets for holding + * |edgesTex| and |blendTex|. + * + * In DX10 or DX11, you can use a RG render target for the edges texture. + * In the case of NVIDIA GPUs, using RG render targets seems to actually be + * slower. + * + * On the Xbox 360, you can use the same render target for resolving both + * |edgesTex| and |blendTex|, as they aren't needed simultaneously. + * + * 2. Both temporal render targets |edgesTex| and |blendTex| must be cleared + * each frame. Do not forget to clear the alpha channel! + * + * 3. The next step is loading the two supporting precalculated textures, + * 'areaTex' and 'searchTex'. You'll find them in the 'Textures' folder as + * C++ headers, and also as regular DDS files. They'll be needed for the + * 'SMAABlendingWeightCalculation' pass. + * + * If you use the C++ headers, be sure to load them in the format specified + * inside of them. + * + * You can also compress 'areaTex' and 'searchTex' using BC5 and BC4 + * respectively, if you have that option in your content processor pipeline. + * When compressing then, you get a non-perceptible quality decrease, and a + * marginal performance increase. + * + * 4. All samplers must be set to linear filtering and clamp. + * + * After you get the technique working, remember that 64-bit inputs have + * half-rate linear filtering on GCN. + * + * If SMAA is applied to 64-bit color buffers, switching to point filtering + * when accesing them will increase the performance. Search for + * 'SMAASamplePoint' to see which textures may benefit from point + * filtering, and where (which is basically the color input in the edge + * detection and resolve passes). + * + * 5. All texture reads and buffer writes must be non-sRGB, with the exception + * of the input read and the output write in + * 'SMAANeighborhoodBlending' (and only in this pass!). If sRGB reads in + * this last pass are not possible, the technique will work anyway, but + * will perform antialiasing in gamma space. + * + * IMPORTANT: for best results the input read for the color/luma edge + * detection should *NOT* be sRGB. + * + * 6. Before including SMAA.h you'll have to setup the render target metrics, + * the target and any optional configuration defines. Optionally you can + * use a preset. + * + * You have the following targets available: + * SMAA_HLSL_3 + * SMAA_HLSL_4 + * SMAA_HLSL_4_1 + * SMAA_GLSL_3 * + * SMAA_GLSL_4 * + * + * * (See SMAA_INCLUDE_VS and SMAA_INCLUDE_PS below). + * + * And four presets: + * SMAA_PRESET_LOW (%60 of the quality) + * SMAA_PRESET_MEDIUM (%80 of the quality) + * SMAA_PRESET_HIGH (%95 of the quality) + * SMAA_PRESET_ULTRA (%99 of the quality) + * + * For example: + * #define SMAA_RT_METRICS float4(1.0 / 1280.0, 1.0 / 720.0, 1280.0, 720.0) + * #define SMAA_HLSL_4 + * #define SMAA_PRESET_HIGH + * #include "SMAA.h" + * + * Note that SMAA_RT_METRICS doesn't need to be a macro, it can be a + * uniform variable. The code is designed to minimize the impact of not + * using a constant value, but it is still better to hardcode it. + * + * Depending on how you encoded 'areaTex' and 'searchTex', you may have to + * add (and customize) the following defines before including SMAA.h: + * #define SMAA_AREATEX_SELECT(sample) sample.rg + * #define SMAA_SEARCHTEX_SELECT(sample) sample.r + * + * If your engine is already using porting macros, you can define + * SMAA_CUSTOM_SL, and define the porting functions by yourself. + * + * 7. Then, you'll have to setup the passes as indicated in the scheme above. + * You can take a look into SMAA.fx, to see how we did it for our demo. + * Checkout the function wrappers, you may want to copy-paste them! + * + * 8. It's recommended to validate the produced |edgesTex| and |blendTex|. + * You can use a screenshot from your engine to compare the |edgesTex| + * and |blendTex| produced inside of the engine with the results obtained + * with the reference demo. + * + * 9. After you get the last pass to work, it's time to optimize. You'll have + * to initialize a stencil buffer in the first pass (discard is already in + * the code), then mask execution by using it the second pass. The last + * pass should be executed in all pixels. + * + * + * After this point you can choose to enable predicated thresholding, + * temporal supersampling and motion blur integration: + * + * a) If you want to use predicated thresholding, take a look into + * SMAA_PREDICATION; you'll need to pass an extra texture in the edge + * detection pass. + * + * b) If you want to enable temporal supersampling (SMAA T2x): + * + * 1. The first step is to render using subpixel jitters. I won't go into + * detail, but it's as simple as moving each vertex position in the + * vertex shader, you can check how we do it in our DX10 demo. + * + * 2. Then, you must setup the temporal resolve. You may want to take a look + * into SMAAResolve for resolving 2x modes. After you get it working, you'll + * probably see ghosting everywhere. But fear not, you can enable the + * CryENGINE temporal reprojection by setting the SMAA_REPROJECTION macro. + * Check out SMAA_DECODE_VELOCITY if your velocity buffer is encoded. + * + * 3. The next step is to apply SMAA to each subpixel jittered frame, just as + * done for 1x. + * + * 4. At this point you should already have something usable, but for best + * results the proper area textures must be set depending on current jitter. + * For this, the parameter 'subsampleIndices' of + * 'SMAABlendingWeightCalculationPS' must be set as follows, for our T2x + * mode: + * + * @SUBSAMPLE_INDICES + * + * | S# | Camera Jitter | subsampleIndices | + * +----+------------------+---------------------+ + * | 0 | ( 0.25, -0.25) | float4(1, 1, 1, 0) | + * | 1 | (-0.25, 0.25) | float4(2, 2, 2, 0) | + * + * These jitter positions assume a bottom-to-top y axis. S# stands for the + * sample number. + * + * More information about temporal supersampling here: + * http://iryoku.com/aacourse/downloads/13-Anti-Aliasing-Methods-in-CryENGINE-3.pdf + * + * c) If you want to enable spatial multisampling (SMAA S2x): + * + * 1. The scene must be rendered using MSAA 2x. The MSAA 2x buffer must be + * created with: + * - DX10: see below (*) + * - DX10.1: D3D10_STANDARD_MULTISAMPLE_PATTERN or + * - DX11: D3D11_STANDARD_MULTISAMPLE_PATTERN + * + * This allows to ensure that the subsample order matches the table in + * @SUBSAMPLE_INDICES. + * + * (*) In the case of DX10, we refer the reader to: + * - SMAA::detectMSAAOrder and + * - SMAA::msaaReorder + * + * These functions allow to match the standard multisample patterns by + * detecting the subsample order for a specific GPU, and reordering + * them appropriately. + * + * 2. A shader must be run to output each subsample into a separate buffer + * (DX10 is required). You can use SMAASeparate for this purpose, or just do + * it in an existing pass (for example, in the tone mapping pass, which has + * the advantage of feeding tone mapped subsamples to SMAA, which will yield + * better results). + * + * 3. The full SMAA 1x pipeline must be run for each separated buffer, storing + * the results in the final buffer. The second run should alpha blend with + * the existing final buffer using a blending factor of 0.5. + * 'subsampleIndices' must be adjusted as in the SMAA T2x case (see point + * b). + * + * d) If you want to enable temporal supersampling on top of SMAA S2x + * (which actually is SMAA 4x): + * + * 1. SMAA 4x consists on temporally jittering SMAA S2x, so the first step is + * to calculate SMAA S2x for current frame. In this case, 'subsampleIndices' + * must be set as follows: + * + * | F# | S# | Camera Jitter | Net Jitter | subsampleIndices | + * +----+----+--------------------+-------------------+----------------------+ + * | 0 | 0 | ( 0.125, 0.125) | ( 0.375, -0.125) | float4(5, 3, 1, 3) | + * | 0 | 1 | ( 0.125, 0.125) | (-0.125, 0.375) | float4(4, 6, 2, 3) | + * +----+----+--------------------+-------------------+----------------------+ + * | 1 | 2 | (-0.125, -0.125) | ( 0.125, -0.375) | float4(3, 5, 1, 4) | + * | 1 | 3 | (-0.125, -0.125) | (-0.375, 0.125) | float4(6, 4, 2, 4) | + * + * These jitter positions assume a bottom-to-top y axis. F# stands for the + * frame number. S# stands for the sample number. + * + * 2. After calculating SMAA S2x for current frame (with the new subsample + * indices), previous frame must be reprojected as in SMAA T2x mode (see + * point b). + * + * e) If motion blur is used, you may want to do the edge detection pass + * together with motion blur. This has two advantages: + * + * 1. Pixels under heavy motion can be omitted from the edge detection process. + * For these pixels we can just store "no edge", as motion blur will take + * care of them. + * 2. The center pixel tap is reused. + * + * Note that in this case depth testing should be used instead of stenciling, + * as we have to write all the pixels in the motion blur pass. + * + * That's it! + */ + +//----------------------------------------------------------------------------- +// SMAA Presets + +/** + * Note that if you use one of these presets, the following configuration + * macros will be ignored if set in the "Configurable Defines" section. + */ + +#if defined(SMAA_PRESET_LOW) +#define SMAA_THRESHOLD 0.15 +#define SMAA_MAX_SEARCH_STEPS 4 +#define SMAA_DISABLE_DIAG_DETECTION +#define SMAA_DISABLE_CORNER_DETECTION +#elif defined(SMAA_PRESET_MEDIUM) +#define SMAA_THRESHOLD 0.1 +#define SMAA_MAX_SEARCH_STEPS 8 +#define SMAA_DISABLE_DIAG_DETECTION +#define SMAA_DISABLE_CORNER_DETECTION +#elif defined(SMAA_PRESET_HIGH) +#define SMAA_THRESHOLD 0.1 +#define SMAA_MAX_SEARCH_STEPS 16 +#define SMAA_MAX_SEARCH_STEPS_DIAG 8 +#define SMAA_CORNER_ROUNDING 25 +#elif defined(SMAA_PRESET_ULTRA) +#define SMAA_THRESHOLD 0.05 +#define SMAA_MAX_SEARCH_STEPS 32 +#define SMAA_MAX_SEARCH_STEPS_DIAG 16 +#define SMAA_CORNER_ROUNDING 25 +#endif + +//----------------------------------------------------------------------------- +// Configurable Defines + +/** + * SMAA_THRESHOLD specifies the threshold or sensitivity to edges. + * Lowering this value you will be able to detect more edges at the expense of + * performance. + * + * Range: [0, 0.5] + * 0.1 is a reasonable value, and allows to catch most visible edges. + * 0.05 is a rather overkill value, that allows to catch 'em all. + * + * If temporal supersampling is used, 0.2 could be a reasonable value, as low + * contrast edges are properly filtered by just 2x. + */ +#ifndef SMAA_THRESHOLD +#define SMAA_THRESHOLD 0.1 +#endif + +/** + * SMAA_DEPTH_THRESHOLD specifies the threshold for depth edge detection. + * + * Range: depends on the depth range of the scene. + */ +#ifndef SMAA_DEPTH_THRESHOLD +#define SMAA_DEPTH_THRESHOLD (0.1 * SMAA_THRESHOLD) +#endif + +/** + * SMAA_MAX_SEARCH_STEPS specifies the maximum steps performed in the + * horizontal/vertical pattern searches, at each side of the pixel. + * + * In number of pixels, it's actually the double. So the maximum line length + * perfectly handled by, for example 16, is 64 (by perfectly, we meant that + * longer lines won't look as good, but still antialiased). + * + * Range: [0, 112] + */ +#ifndef SMAA_MAX_SEARCH_STEPS +#define SMAA_MAX_SEARCH_STEPS 16 +#endif + +/** + * SMAA_MAX_SEARCH_STEPS_DIAG specifies the maximum steps performed in the + * diagonal pattern searches, at each side of the pixel. In this case we jump + * one pixel at time, instead of two. + * + * Range: [0, 20] + * + * On high-end machines it is cheap (between a 0.8x and 0.9x slower for 16 + * steps), but it can have a significant impact on older machines. + * + * Define SMAA_DISABLE_DIAG_DETECTION to disable diagonal processing. + */ +#ifndef SMAA_MAX_SEARCH_STEPS_DIAG +#define SMAA_MAX_SEARCH_STEPS_DIAG 8 +#endif + +/** + * SMAA_CORNER_ROUNDING specifies how much sharp corners will be rounded. + * + * Range: [0, 100] + * + * Define SMAA_DISABLE_CORNER_DETECTION to disable corner processing. + */ +#ifndef SMAA_CORNER_ROUNDING +#define SMAA_CORNER_ROUNDING 25 +#endif + +/** + * If there is an neighbor edge that has SMAA_LOCAL_CONTRAST_FACTOR times + * bigger contrast than current edge, current edge will be discarded. + * + * This allows to eliminate spurious crossing edges, and is based on the fact + * that, if there is too much contrast in a direction, that will hide + * perceptually contrast in the other neighbors. + */ +#ifndef SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR +#define SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR 2.0 +#endif + +/** + * Predicated thresholding allows to better preserve texture details and to + * improve performance, by decreasing the number of detected edges using an + * additional buffer like the light accumulation buffer, object ids or even the + * depth buffer (the depth buffer usage may be limited to indoor or short range + * scenes). + * + * It locally decreases the luma or color threshold if an edge is found in an + * additional buffer (so the global threshold can be higher). + * + * This method was developed by Playstation EDGE MLAA team, and used in + * Killzone 3, by using the light accumulation buffer. More information here: + * http://iryoku.com/aacourse/downloads/06-MLAA-on-PS3.pptx + */ +#ifndef SMAA_PREDICATION +#define SMAA_PREDICATION 0 +#endif + +/** + * Threshold to be used in the additional predication buffer. + * + * Range: depends on the input, so you'll have to find the magic number that + * works for you. + */ +#ifndef SMAA_PREDICATION_THRESHOLD +#define SMAA_PREDICATION_THRESHOLD 0.01 +#endif + +/** + * How much to scale the global threshold used for luma or color edge + * detection when using predication. + * + * Range: [1, 5] + */ +#ifndef SMAA_PREDICATION_SCALE +#define SMAA_PREDICATION_SCALE 2.0 +#endif + +/** + * How much to locally decrease the threshold. + * + * Range: [0, 1] + */ +#ifndef SMAA_PREDICATION_STRENGTH +#define SMAA_PREDICATION_STRENGTH 0.4 +#endif + +/** + * Temporal reprojection allows to remove ghosting artifacts when using + * temporal supersampling. We use the CryEngine 3 method which also introduces + * velocity weighting. This feature is of extreme importance for totally + * removing ghosting. More information here: + * http://iryoku.com/aacourse/downloads/13-Anti-Aliasing-Methods-in-CryENGINE-3.pdf + * + * Note that you'll need to setup a velocity buffer for enabling reprojection. + * For static geometry, saving the previous depth buffer is a viable + * alternative. + */ +#ifndef SMAA_REPROJECTION +#define SMAA_REPROJECTION 0 +#endif + +/** + * SMAA_REPROJECTION_WEIGHT_SCALE controls the velocity weighting. It allows to + * remove ghosting trails behind the moving object, which are not removed by + * just using reprojection. Using low values will exhibit ghosting, while using + * high values will disable temporal supersampling under motion. + * + * Behind the scenes, velocity weighting removes temporal supersampling when + * the velocity of the subsamples differs (meaning they are different objects). + * + * Range: [0, 80] + */ +#ifndef SMAA_REPROJECTION_WEIGHT_SCALE +#define SMAA_REPROJECTION_WEIGHT_SCALE 30.0 +#endif + +/** + * On some compilers, discard cannot be used in vertex shaders. Thus, they need + * to be compiled separately. + */ +#ifndef SMAA_INCLUDE_VS +#define SMAA_INCLUDE_VS 1 +#endif +#ifndef SMAA_INCLUDE_PS +#define SMAA_INCLUDE_PS 1 +#endif + +//----------------------------------------------------------------------------- +// Texture Access Defines + +#ifndef SMAA_AREATEX_SELECT +#if defined(SMAA_HLSL_3) +#define SMAA_AREATEX_SELECT(sample) sample.ra +#else +#define SMAA_AREATEX_SELECT(sample) sample.rg +#endif +#endif + +#ifndef SMAA_SEARCHTEX_SELECT +#define SMAA_SEARCHTEX_SELECT(sample) sample.r +#endif + +#ifndef SMAA_DECODE_VELOCITY +#define SMAA_DECODE_VELOCITY(sample) sample.rg +#endif + +//----------------------------------------------------------------------------- +// Non-Configurable Defines + +#define SMAA_AREATEX_MAX_DISTANCE 16 +#define SMAA_AREATEX_MAX_DISTANCE_DIAG 20 +#define SMAA_AREATEX_PIXEL_SIZE (1.0 / float2(160.0, 560.0)) +#define SMAA_AREATEX_SUBTEX_SIZE (1.0 / 7.0) +#define SMAA_SEARCHTEX_SIZE float2(66.0, 33.0) +#define SMAA_SEARCHTEX_PACKED_SIZE float2(64.0, 16.0) +#define SMAA_CORNER_ROUNDING_NORM (float(SMAA_CORNER_ROUNDING) / 100.0) + +//----------------------------------------------------------------------------- +// Porting Functions + +#if defined(SMAA_HLSL_3) +#define SMAATexture2D(tex) sampler2D tex +#define SMAATexturePass2D(tex) tex +#define SMAASampleLevelZero(tex, coord) tex2Dlod(tex, float4(coord, 0.0, 0.0)) +#define SMAASampleLevelZeroPoint(tex, coord) tex2Dlod(tex, float4(coord, 0.0, 0.0)) +#define SMAASampleLevelZeroOffset(tex, coord, offset) tex2Dlod(tex, float4(coord + offset * SMAA_RT_METRICS.xy, 0.0, 0.0)) +#define SMAASample(tex, coord) tex2D(tex, coord) +#define SMAASamplePoint(tex, coord) tex2D(tex, coord) +#define SMAASampleOffset(tex, coord, offset) tex2D(tex, coord + offset * SMAA_RT_METRICS.xy) +#define SMAA_FLATTEN [flatten] +#define SMAA_BRANCH [branch] +#endif +#if defined(SMAA_HLSL_4) || defined(SMAA_HLSL_4_1) +SamplerState LinearSampler { Filter = MIN_MAG_LINEAR_MIP_POINT; AddressU = Clamp; AddressV = Clamp; }; +SamplerState PointSampler { Filter = MIN_MAG_MIP_POINT; AddressU = Clamp; AddressV = Clamp; }; +#define SMAATexture2D(tex) Texture2D tex +#define SMAATexturePass2D(tex) tex +#define SMAASampleLevelZero(tex, coord) tex.SampleLevel(LinearSampler, coord, 0) +#define SMAASampleLevelZeroPoint(tex, coord) tex.SampleLevel(PointSampler, coord, 0) +#define SMAASampleLevelZeroOffset(tex, coord, offset) tex.SampleLevel(LinearSampler, coord, 0, offset) +#define SMAASample(tex, coord) tex.Sample(LinearSampler, coord) +#define SMAASamplePoint(tex, coord) tex.Sample(PointSampler, coord) +#define SMAASampleOffset(tex, coord, offset) tex.Sample(LinearSampler, coord, offset) +#define SMAA_FLATTEN [flatten] +#define SMAA_BRANCH [branch] +#define SMAATexture2DMS2(tex) Texture2DMS tex +#define SMAALoad(tex, pos, sample) tex.Load(pos, sample) +#if defined(SMAA_HLSL_4_1) +#define SMAAGather(tex, coord) tex.Gather(LinearSampler, coord, 0) +#endif +#endif +#if defined(SMAA_GLSL_3) || defined(SMAA_GLSL_4) +#define SMAATexture2D(tex) sampler2D tex +#define SMAATexturePass2D(tex) tex +#define SMAASampleLevelZero(tex, coord) textureLod(tex, coord, 0.0) +#define SMAASampleLevelZeroPoint(tex, coord) textureLod(tex, coord, 0.0) +#define SMAASampleLevelZeroOffset(tex, coord, offset) textureLodOffset(tex, coord, 0.0, offset) +#define SMAASample(tex, coord) texture(tex, coord) +#define SMAASamplePoint(tex, coord) texture(tex, coord) +#define SMAASampleOffset(tex, coord, offset) texture(tex, coord, offset) +#define SMAA_FLATTEN +#define SMAA_BRANCH +#define lerp(a, b, t) mix(a, b, t) +#define saturate(a) clamp(a, 0.0, 1.0) +#if defined(SMAA_GLSL_4) +#define mad(a, b, c) fma(a, b, c) +#define SMAAGather(tex, coord) textureGather(tex, coord) +#else +#define mad(a, b, c) (a * b + c) +#endif +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 +#define int2 ivec2 +#define int3 ivec3 +#define int4 ivec4 +#define bool2 bvec2 +#define bool3 bvec3 +#define bool4 bvec4 +#endif + +#if !defined(SMAA_HLSL_3) && !defined(SMAA_HLSL_4) && !defined(SMAA_HLSL_4_1) && !defined(SMAA_GLSL_3) && !defined(SMAA_GLSL_4) && !defined(SMAA_CUSTOM_SL) +#error you must define the shading language: SMAA_HLSL_*, SMAA_GLSL_* or SMAA_CUSTOM_SL +#endif + +//----------------------------------------------------------------------------- +// Misc functions + +/** + * Gathers current pixel, and the top-left neighbors. + */ +float3 SMAAGatherNeighbours(float2 texcoord, + float4 offset[3], + SMAATexture2D(tex)) { + #ifdef SMAAGather + return SMAAGather(tex, texcoord + SMAA_RT_METRICS.xy * float2(-0.5, -0.5)).grb; + #else + float P = SMAASamplePoint(tex, texcoord).r; + float Pleft = SMAASamplePoint(tex, offset[0].xy).r; + float Ptop = SMAASamplePoint(tex, offset[0].zw).r; + return float3(P, Pleft, Ptop); + #endif +} + +/** + * Adjusts the threshold by means of predication. + */ +float2 SMAACalculatePredicatedThreshold(float2 texcoord, + float4 offset[3], + SMAATexture2D(predicationTex)) { + float3 neighbours = SMAAGatherNeighbours(texcoord, offset, SMAATexturePass2D(predicationTex)); + float2 delta = abs(neighbours.xx - neighbours.yz); + float2 edges = step(SMAA_PREDICATION_THRESHOLD, delta); + return SMAA_PREDICATION_SCALE * SMAA_THRESHOLD * (1.0 - SMAA_PREDICATION_STRENGTH * edges); +} + +/** + * Conditional move: + */ +void SMAAMovc(bool2 cond, inout float2 variable, float2 value) { + SMAA_FLATTEN if (cond.x) variable.x = value.x; + SMAA_FLATTEN if (cond.y) variable.y = value.y; +} + +void SMAAMovc(bool4 cond, inout float4 variable, float4 value) { + SMAAMovc(cond.xy, variable.xy, value.xy); + SMAAMovc(cond.zw, variable.zw, value.zw); +} + + +#if SMAA_INCLUDE_VS +//----------------------------------------------------------------------------- +// Vertex Shaders + +/** + * Edge Detection Vertex Shader + */ +void SMAAEdgeDetectionVS(float2 texcoord, + out float4 offset[3]) { + offset[0] = mad(SMAA_RT_METRICS.xyxy, float4(-1.0, 0.0, 0.0, -1.0), texcoord.xyxy); + offset[1] = mad(SMAA_RT_METRICS.xyxy, float4( 1.0, 0.0, 0.0, 1.0), texcoord.xyxy); + offset[2] = mad(SMAA_RT_METRICS.xyxy, float4(-2.0, 0.0, 0.0, -2.0), texcoord.xyxy); +} + +/** + * Blend Weight Calculation Vertex Shader + */ +void SMAABlendingWeightCalculationVS(float2 texcoord, + out float2 pixcoord, + out float4 offset[3]) { + pixcoord = texcoord * SMAA_RT_METRICS.zw; + + // We will use these offsets for the searches later on (see @PSEUDO_GATHER4): + offset[0] = mad(SMAA_RT_METRICS.xyxy, float4(-0.25, -0.125, 1.25, -0.125), texcoord.xyxy); + offset[1] = mad(SMAA_RT_METRICS.xyxy, float4(-0.125, -0.25, -0.125, 1.25), texcoord.xyxy); + + // And these for the searches, they indicate the ends of the loops: + offset[2] = mad(SMAA_RT_METRICS.xxyy, + float4(-2.0, 2.0, -2.0, 2.0) * float(SMAA_MAX_SEARCH_STEPS), + float4(offset[0].xz, offset[1].yw)); +} + +/** + * Neighborhood Blending Vertex Shader + */ +void SMAANeighborhoodBlendingVS(float2 texcoord, + out float4 offset) { + offset = mad(SMAA_RT_METRICS.xyxy, float4( 1.0, 0.0, 0.0, 1.0), texcoord.xyxy); +} +#endif // SMAA_INCLUDE_VS + +#if SMAA_INCLUDE_PS +//----------------------------------------------------------------------------- +// Edge Detection Pixel Shaders (First Pass) + +/** + * Luma Edge Detection + * + * IMPORTANT NOTICE: luma edge detection requires gamma-corrected colors, and + * thus 'colorTex' should be a non-sRGB texture. + */ +float2 SMAALumaEdgeDetectionPS(float2 texcoord, + float4 offset[3], + SMAATexture2D(colorTex) + #if SMAA_PREDICATION + , SMAATexture2D(predicationTex) + #endif + ) { + // Calculate the threshold: + #if SMAA_PREDICATION + float2 threshold = SMAACalculatePredicatedThreshold(texcoord, offset, SMAATexturePass2D(predicationTex)); + #else + float2 threshold = float2(SMAA_THRESHOLD, SMAA_THRESHOLD); + #endif + + // Calculate lumas: + float3 weights = float3(0.2126, 0.7152, 0.0722); + float L = dot(SMAASamplePoint(colorTex, texcoord).rgb, weights); + + float Lleft = dot(SMAASamplePoint(colorTex, offset[0].xy).rgb, weights); + float Ltop = dot(SMAASamplePoint(colorTex, offset[0].zw).rgb, weights); + + // We do the usual threshold: + float4 delta; + delta.xy = abs(L - float2(Lleft, Ltop)); + float2 edges = step(threshold, delta.xy); + + // Then discard if there is no edge: + if (dot(edges, float2(1.0, 1.0)) == 0.0) + return float2(-2.0, -2.0); + + // Calculate right and bottom deltas: + float Lright = dot(SMAASamplePoint(colorTex, offset[1].xy).rgb, weights); + float Lbottom = dot(SMAASamplePoint(colorTex, offset[1].zw).rgb, weights); + delta.zw = abs(L - float2(Lright, Lbottom)); + + // Calculate the maximum delta in the direct neighborhood: + float2 maxDelta = max(delta.xy, delta.zw); + + // Calculate left-left and top-top deltas: + float Lleftleft = dot(SMAASamplePoint(colorTex, offset[2].xy).rgb, weights); + float Ltoptop = dot(SMAASamplePoint(colorTex, offset[2].zw).rgb, weights); + delta.zw = abs(float2(Lleft, Ltop) - float2(Lleftleft, Ltoptop)); + + // Calculate the final maximum delta: + maxDelta = max(maxDelta.xy, delta.zw); + float finalDelta = max(maxDelta.x, maxDelta.y); + + // Local contrast adaptation: + edges.xy *= step(finalDelta, SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR * delta.xy); + + return edges; +} + +/** + * Color Edge Detection + * + * IMPORTANT NOTICE: color edge detection requires gamma-corrected colors, and + * thus 'colorTex' should be a non-sRGB texture. + */ +float2 SMAAColorEdgeDetectionPS(float2 texcoord, + float4 offset[3], + SMAATexture2D(colorTex) + #if SMAA_PREDICATION + , SMAATexture2D(predicationTex) + #endif + ) { + // Calculate the threshold: + #if SMAA_PREDICATION + float2 threshold = SMAACalculatePredicatedThreshold(texcoord, offset, predicationTex); + #else + float2 threshold = float2(SMAA_THRESHOLD, SMAA_THRESHOLD); + #endif + + // Calculate color deltas: + float4 delta; + float3 C = SMAASamplePoint(colorTex, texcoord).rgb; + + float3 Cleft = SMAASamplePoint(colorTex, offset[0].xy).rgb; + float3 t = abs(C - Cleft); + delta.x = max(max(t.r, t.g), t.b); + + float3 Ctop = SMAASamplePoint(colorTex, offset[0].zw).rgb; + t = abs(C - Ctop); + delta.y = max(max(t.r, t.g), t.b); + + // We do the usual threshold: + float2 edges = step(threshold, delta.xy); + + // Then discard if there is no edge: + if (dot(edges, float2(1.0, 1.0)) == 0.0) + return float2(-2.0, -2.0); + + // Calculate right and bottom deltas: + float3 Cright = SMAASamplePoint(colorTex, offset[1].xy).rgb; + t = abs(C - Cright); + delta.z = max(max(t.r, t.g), t.b); + + float3 Cbottom = SMAASamplePoint(colorTex, offset[1].zw).rgb; + t = abs(C - Cbottom); + delta.w = max(max(t.r, t.g), t.b); + + // Calculate the maximum delta in the direct neighborhood: + float2 maxDelta = max(delta.xy, delta.zw); + + // Calculate left-left and top-top deltas: + float3 Cleftleft = SMAASamplePoint(colorTex, offset[2].xy).rgb; + t = abs(C - Cleftleft); + delta.z = max(max(t.r, t.g), t.b); + + float3 Ctoptop = SMAASamplePoint(colorTex, offset[2].zw).rgb; + t = abs(C - Ctoptop); + delta.w = max(max(t.r, t.g), t.b); + + // Calculate the final maximum delta: + maxDelta = max(maxDelta.xy, delta.zw); + float finalDelta = max(maxDelta.x, maxDelta.y); + + // Local contrast adaptation: + edges.xy *= step(finalDelta, SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR * delta.xy); + + return edges; +} + +/** + * Depth Edge Detection + */ +float2 SMAADepthEdgeDetectionPS(float2 texcoord, + float4 offset[3], + SMAATexture2D(depthTex)) { + float3 neighbours = SMAAGatherNeighbours(texcoord, offset, SMAATexturePass2D(depthTex)); + float2 delta = abs(neighbours.xx - float2(neighbours.y, neighbours.z)); + float2 edges = step(SMAA_DEPTH_THRESHOLD, delta); + + if (dot(edges, float2(1.0, 1.0)) == 0.0) + return float2(-2.0, -2.0); + + return edges; +} + +//----------------------------------------------------------------------------- +// Diagonal Search Functions + +#if !defined(SMAA_DISABLE_DIAG_DETECTION) + +/** + * Allows to decode two binary values from a bilinear-filtered access. + */ +float2 SMAADecodeDiagBilinearAccess(float2 e) { + // Bilinear access for fetching 'e' have a 0.25 offset, and we are + // interested in the R and G edges: + // + // +---G---+-------+ + // | x o R x | + // +-------+-------+ + // + // Then, if one of these edge is enabled: + // Red: (0.75 * X + 0.25 * 1) => 0.25 or 1.0 + // Green: (0.75 * 1 + 0.25 * X) => 0.75 or 1.0 + // + // This function will unpack the values (mad + mul + round): + // wolframalpha.com: round(x * abs(5 * x - 5 * 0.75)) plot 0 to 1 + e.r = e.r * abs(5.0 * e.r - 5.0 * 0.75); + return round(e); +} + +float4 SMAADecodeDiagBilinearAccess(float4 e) { + e.rb = e.rb * abs(5.0 * e.rb - 5.0 * 0.75); + return round(e); +} + +/** + * These functions allows to perform diagonal pattern searches. + */ +float2 SMAASearchDiag1(SMAATexture2D(edgesTex), float2 texcoord, float2 dir, out float2 e) { + float4 coord = float4(texcoord, -1.0, 1.0); + float3 t = float3(SMAA_RT_METRICS.xy, 1.0); + while (coord.z < float(SMAA_MAX_SEARCH_STEPS_DIAG - 1) && + coord.w > 0.9) { + coord.xyz = mad(t, float3(dir, 1.0), coord.xyz); + e = SMAASampleLevelZero(edgesTex, coord.xy).rg; + coord.w = dot(e, float2(0.5, 0.5)); + } + return coord.zw; +} + +float2 SMAASearchDiag2(SMAATexture2D(edgesTex), float2 texcoord, float2 dir, out float2 e) { + float4 coord = float4(texcoord, -1.0, 1.0); + coord.x += 0.25 * SMAA_RT_METRICS.x; // See @SearchDiag2Optimization + float3 t = float3(SMAA_RT_METRICS.xy, 1.0); + while (coord.z < float(SMAA_MAX_SEARCH_STEPS_DIAG - 1) && + coord.w > 0.9) { + coord.xyz = mad(t, float3(dir, 1.0), coord.xyz); + + // @SearchDiag2Optimization + // Fetch both edges at once using bilinear filtering: + e = SMAASampleLevelZero(edgesTex, coord.xy).rg; + e = SMAADecodeDiagBilinearAccess(e); + + // Non-optimized version: + // e.g = SMAASampleLevelZero(edgesTex, coord.xy).g; + // e.r = SMAASampleLevelZeroOffset(edgesTex, coord.xy, int2(1, 0)).r; + + coord.w = dot(e, float2(0.5, 0.5)); + } + return coord.zw; +} + +/** + * Similar to SMAAArea, this calculates the area corresponding to a certain + * diagonal distance and crossing edges 'e'. + */ +float2 SMAAAreaDiag(SMAATexture2D(areaTex), float2 dist, float2 e, float offset) { + float2 texcoord = mad(float2(SMAA_AREATEX_MAX_DISTANCE_DIAG, SMAA_AREATEX_MAX_DISTANCE_DIAG), e, dist); + + // We do a scale and bias for mapping to texel space: + texcoord = mad(SMAA_AREATEX_PIXEL_SIZE, texcoord, 0.5 * SMAA_AREATEX_PIXEL_SIZE); + + // Diagonal areas are on the second half of the texture: + texcoord.x += 0.5; + + // Move to proper place, according to the subpixel offset: + texcoord.y += SMAA_AREATEX_SUBTEX_SIZE * offset; + + // Do it! + return SMAA_AREATEX_SELECT(SMAASampleLevelZero(areaTex, texcoord)); +} + +/** + * This searches for diagonal patterns and returns the corresponding weights. + */ +float2 SMAACalculateDiagWeights(SMAATexture2D(edgesTex), SMAATexture2D(areaTex), float2 texcoord, float2 e, float4 subsampleIndices) { + float2 weights = float2(0.0, 0.0); + + // Search for the line ends: + float4 d; + float2 end; + if (e.r > 0.0) { + d.xz = SMAASearchDiag1(SMAATexturePass2D(edgesTex), texcoord, float2(-1.0, 1.0), end); + d.x += float(end.y > 0.9); + } else + d.xz = float2(0.0, 0.0); + d.yw = SMAASearchDiag1(SMAATexturePass2D(edgesTex), texcoord, float2(1.0, -1.0), end); + + SMAA_BRANCH + if (d.x + d.y > 2.0) { // d.x + d.y + 1 > 3 + // Fetch the crossing edges: + float4 coords = mad(float4(-d.x + 0.25, d.x, d.y, -d.y - 0.25), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + float4 c; + c.xy = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).rg; + c.zw = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).rg; + c.yxwz = SMAADecodeDiagBilinearAccess(c.xyzw); + + // Non-optimized version: + // float4 coords = mad(float4(-d.x, d.x, d.y, -d.y), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + // float4 c; + // c.x = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).g; + // c.y = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2( 0, 0)).r; + // c.z = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).g; + // c.w = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, -1)).r; + + // Merge crossing edges at each side into a single value: + float2 cc = mad(float2(2.0, 2.0), c.xz, c.yw); + + // Remove the crossing edge if we didn't found the end of the line: + SMAAMovc(bool2(step(0.9, d.zw)), cc, float2(0.0, 0.0)); + + // Fetch the areas for this line: + weights += SMAAAreaDiag(SMAATexturePass2D(areaTex), d.xy, cc, subsampleIndices.z); + } + + // Search for the line ends: + d.xz = SMAASearchDiag2(SMAATexturePass2D(edgesTex), texcoord, float2(-1.0, -1.0), end); + if (SMAASampleLevelZeroOffset(edgesTex, texcoord, int2(1, 0)).r > 0.0) { + d.yw = SMAASearchDiag2(SMAATexturePass2D(edgesTex), texcoord, float2(1.0, 1.0), end); + d.y += float(end.y > 0.9); + } else + d.yw = float2(0.0, 0.0); + + SMAA_BRANCH + if (d.x + d.y > 2.0) { // d.x + d.y + 1 > 3 + // Fetch the crossing edges: + float4 coords = mad(float4(-d.x, -d.x, d.y, d.y), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + float4 c; + c.x = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).g; + c.y = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2( 0, -1)).r; + c.zw = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).gr; + float2 cc = mad(float2(2.0, 2.0), c.xz, c.yw); + + // Remove the crossing edge if we didn't found the end of the line: + SMAAMovc(bool2(step(0.9, d.zw)), cc, float2(0.0, 0.0)); + + // Fetch the areas for this line: + weights += SMAAAreaDiag(SMAATexturePass2D(areaTex), d.xy, cc, subsampleIndices.w).gr; + } + + return weights; +} +#endif + +//----------------------------------------------------------------------------- +// Horizontal/Vertical Search Functions + +/** + * This allows to determine how much length should we add in the last step + * of the searches. It takes the bilinearly interpolated edge (see + * @PSEUDO_GATHER4), and adds 0, 1 or 2, depending on which edges and + * crossing edges are active. + */ +float SMAASearchLength(SMAATexture2D(searchTex), float2 e, float offset) { + // The texture is flipped vertically, with left and right cases taking half + // of the space horizontally: + float2 scale = SMAA_SEARCHTEX_SIZE * float2(0.5, -1.0); + float2 bias = SMAA_SEARCHTEX_SIZE * float2(offset, 1.0); + + // Scale and bias to access texel centers: + scale += float2(-1.0, 1.0); + bias += float2( 0.5, -0.5); + + // Convert from pixel coordinates to texcoords: + // (We use SMAA_SEARCHTEX_PACKED_SIZE because the texture is cropped) + scale *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; + bias *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; + + // Lookup the search texture: + return SMAA_SEARCHTEX_SELECT(SMAASampleLevelZero(searchTex, mad(scale, e, bias))); +} + +/** + * Horizontal/vertical search functions for the 2nd pass. + */ +float SMAASearchXLeft(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + /** + * @PSEUDO_GATHER4 + * This texcoord has been offset by (-0.25, -0.125) in the vertex shader to + * sample between edge, thus fetching four edges in a row. + * Sampling with different offsets in each direction allows to disambiguate + * which edges are active from the four fetched ones. + */ + float2 e = float2(0.0, 1.0); + while (texcoord.x > end && + e.g > 0.8281 && // Is there some edge not activated? + e.r == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(-float2(2.0, 0.0), SMAA_RT_METRICS.xy, texcoord); + } + + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.0), 3.25); + return mad(SMAA_RT_METRICS.x, offset, texcoord.x); + + // Non-optimized version: + // We correct the previous (-0.25, -0.125) offset we applied: + // texcoord.x += 0.25 * SMAA_RT_METRICS.x; + + // The searches are bias by 1, so adjust the coords accordingly: + // texcoord.x += SMAA_RT_METRICS.x; + + // Disambiguate the length added by the last step: + // texcoord.x += 2.0 * SMAA_RT_METRICS.x; // Undo last step + // texcoord.x -= SMAA_RT_METRICS.x * (255.0 / 127.0) * SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.0); + // return mad(SMAA_RT_METRICS.x, offset, texcoord.x); +} + +float SMAASearchXRight(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(0.0, 1.0); + while (texcoord.x < end && + e.g > 0.8281 && // Is there some edge not activated? + e.r == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(float2(2.0, 0.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.5), 3.25); + return mad(-SMAA_RT_METRICS.x, offset, texcoord.x); +} + +float SMAASearchYUp(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(1.0, 0.0); + while (texcoord.y > end && + e.r > 0.8281 && // Is there some edge not activated? + e.g == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(-float2(0.0, 2.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e.gr, 0.0), 3.25); + return mad(SMAA_RT_METRICS.y, offset, texcoord.y); +} + +float SMAASearchYDown(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(1.0, 0.0); + while (texcoord.y < end && + e.r > 0.8281 && // Is there some edge not activated? + e.g == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(float2(0.0, 2.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e.gr, 0.5), 3.25); + return mad(-SMAA_RT_METRICS.y, offset, texcoord.y); +} + +/** + * Ok, we have the distance and both crossing edges. So, what are the areas + * at each side of current edge? + */ +float2 SMAAArea(SMAATexture2D(areaTex), float2 dist, float e1, float e2, float offset) { + // Rounding prevents precision errors of bilinear filtering: + float2 texcoord = mad(float2(SMAA_AREATEX_MAX_DISTANCE, SMAA_AREATEX_MAX_DISTANCE), round(4.0 * float2(e1, e2)), dist); + + // We do a scale and bias for mapping to texel space: + texcoord = mad(SMAA_AREATEX_PIXEL_SIZE, texcoord, 0.5 * SMAA_AREATEX_PIXEL_SIZE); + + // Move to proper place, according to the subpixel offset: + texcoord.y = mad(SMAA_AREATEX_SUBTEX_SIZE, offset, texcoord.y); + + // Do it! + return SMAA_AREATEX_SELECT(SMAASampleLevelZero(areaTex, texcoord)); +} + +//----------------------------------------------------------------------------- +// Corner Detection Functions + +void SMAADetectHorizontalCornerPattern(SMAATexture2D(edgesTex), inout float2 weights, float4 texcoord, float2 d) { + #if !defined(SMAA_DISABLE_CORNER_DETECTION) + float2 leftRight = step(d.xy, d.yx); + float2 rounding = (1.0 - SMAA_CORNER_ROUNDING_NORM) * leftRight; + + rounding /= leftRight.x + leftRight.y; // Reduce blending for pixels in the center of a line. + + float2 factor = float2(1.0, 1.0); + factor.x -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(0, 1)).r; + factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(1, 1)).r; + factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(0, -2)).r; + factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(1, -2)).r; + + weights *= saturate(factor); + #endif +} + +void SMAADetectVerticalCornerPattern(SMAATexture2D(edgesTex), inout float2 weights, float4 texcoord, float2 d) { + #if !defined(SMAA_DISABLE_CORNER_DETECTION) + float2 leftRight = step(d.xy, d.yx); + float2 rounding = (1.0 - SMAA_CORNER_ROUNDING_NORM) * leftRight; + + rounding /= leftRight.x + leftRight.y; + + float2 factor = float2(1.0, 1.0); + factor.x -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2( 1, 0)).g; + factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2( 1, 1)).g; + factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(-2, 0)).g; + factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(-2, 1)).g; + + weights *= saturate(factor); + #endif +} + +//----------------------------------------------------------------------------- +// Blending Weight Calculation Pixel Shader (Second Pass) + +float4 SMAABlendingWeightCalculationPS(float2 texcoord, + float2 pixcoord, + float4 offset[3], + SMAATexture2D(edgesTex), + SMAATexture2D(areaTex), + SMAATexture2D(searchTex), + float4 subsampleIndices) { // Just pass zero for SMAA 1x, see @SUBSAMPLE_INDICES. + float4 weights = float4(0.0, 0.0, 0.0, 0.0); + + float2 e = SMAASample(edgesTex, texcoord).rg; + + SMAA_BRANCH + if (e.g > 0.0) { // Edge at north + #if !defined(SMAA_DISABLE_DIAG_DETECTION) + // Diagonals have both north and west edges, so searching for them in + // one of the boundaries is enough. + weights.rg = SMAACalculateDiagWeights(SMAATexturePass2D(edgesTex), SMAATexturePass2D(areaTex), texcoord, e, subsampleIndices); + + // We give priority to diagonals, so if we find a diagonal we skip + // horizontal/vertical processing. + SMAA_BRANCH + if (weights.r == -weights.g) { // weights.r + weights.g == 0.0 + #endif + + float2 d; + + // Find the distance to the left: + float3 coords; + coords.x = SMAASearchXLeft(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[0].xy, offset[2].x); + coords.y = offset[1].y; // offset[1].y = texcoord.y - 0.25 * SMAA_RT_METRICS.y (@CROSSING_OFFSET) + d.x = coords.x; + + // Now fetch the left crossing edges, two at a time using bilinear + // filtering. Sampling at -0.25 (see @CROSSING_OFFSET) enables to + // discern what value each edge has: + float e1 = SMAASampleLevelZero(edgesTex, coords.xy).r; + + // Find the distance to the right: + coords.z = SMAASearchXRight(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[0].zw, offset[2].y); + d.y = coords.z; + + // We want the distances to be in pixel units (doing this here allow to + // better interleave arithmetic and memory accesses): + d = abs(round(mad(SMAA_RT_METRICS.zz, d, -pixcoord.xx))); + + // SMAAArea below needs a sqrt, as the areas texture is compressed + // quadratically: + float2 sqrt_d = sqrt(d); + + // Fetch the right crossing edges: + float e2 = SMAASampleLevelZeroOffset(edgesTex, coords.zy, int2(1, 0)).r; + + // Ok, we know how this pattern looks like, now it is time for getting + // the actual area: + weights.rg = SMAAArea(SMAATexturePass2D(areaTex), sqrt_d, e1, e2, subsampleIndices.y); + + // Fix corners: + coords.y = texcoord.y; + SMAADetectHorizontalCornerPattern(SMAATexturePass2D(edgesTex), weights.rg, coords.xyzy, d); + + #if !defined(SMAA_DISABLE_DIAG_DETECTION) + } else + e.r = 0.0; // Skip vertical processing. + #endif + } + + SMAA_BRANCH + if (e.r > 0.0) { // Edge at west + float2 d; + + // Find the distance to the top: + float3 coords; + coords.y = SMAASearchYUp(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[1].xy, offset[2].z); + coords.x = offset[0].x; // offset[1].x = texcoord.x - 0.25 * SMAA_RT_METRICS.x; + d.x = coords.y; + + // Fetch the top crossing edges: + float e1 = SMAASampleLevelZero(edgesTex, coords.xy).g; + + // Find the distance to the bottom: + coords.z = SMAASearchYDown(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[1].zw, offset[2].w); + d.y = coords.z; + + // We want the distances to be in pixel units: + d = abs(round(mad(SMAA_RT_METRICS.ww, d, -pixcoord.yy))); + + // SMAAArea below needs a sqrt, as the areas texture is compressed + // quadratically: + float2 sqrt_d = sqrt(d); + + // Fetch the bottom crossing edges: + float e2 = SMAASampleLevelZeroOffset(edgesTex, coords.xz, int2(0, 1)).g; + + // Get the area for this direction: + weights.ba = SMAAArea(SMAATexturePass2D(areaTex), sqrt_d, e1, e2, subsampleIndices.x); + + // Fix corners: + coords.x = texcoord.x; + SMAADetectVerticalCornerPattern(SMAATexturePass2D(edgesTex), weights.ba, coords.xyxz, d); + } + + return weights; +} + +//----------------------------------------------------------------------------- +// Neighborhood Blending Pixel Shader (Third Pass) + +float4 SMAANeighborhoodBlendingPS(float2 texcoord, + float4 offset, + SMAATexture2D(colorTex), + SMAATexture2D(blendTex) + #if SMAA_REPROJECTION + , SMAATexture2D(velocityTex) + #endif + ) { + // Fetch the blending weights for current pixel: + float4 a; + a.x = SMAASample(blendTex, offset.xy).a; // Right + a.y = SMAASample(blendTex, offset.zw).g; // Top + a.wz = SMAASample(blendTex, texcoord).xz; // Bottom / Left + + // Is there any blending weight with a value greater than 0.0? + SMAA_BRANCH + if (dot(a, float4(1.0, 1.0, 1.0, 1.0)) < 1e-5) { + float4 color = SMAASampleLevelZero(colorTex, texcoord); + + #if SMAA_REPROJECTION + float2 velocity = SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, texcoord)); + + // Pack velocity into the alpha channel: + color.a = sqrt(5.0 * length(velocity)); + #endif + + return color; + } else { + bool h = max(a.x, a.z) > max(a.y, a.w); // max(horizontal) > max(vertical) + + // Calculate the blending offsets: + float4 blendingOffset = float4(0.0, a.y, 0.0, a.w); + float2 blendingWeight = a.yw; + SMAAMovc(bool4(h, h, h, h), blendingOffset, float4(a.x, 0.0, a.z, 0.0)); + SMAAMovc(bool2(h, h), blendingWeight, a.xz); + blendingWeight /= dot(blendingWeight, float2(1.0, 1.0)); + + // Calculate the texture coordinates: + float4 blendingCoord = mad(blendingOffset, float4(SMAA_RT_METRICS.xy, -SMAA_RT_METRICS.xy), texcoord.xyxy); + + // We exploit bilinear filtering to mix current pixel with the chosen + // neighbor: + float4 color = blendingWeight.x * SMAASampleLevelZero(colorTex, blendingCoord.xy); + color += blendingWeight.y * SMAASampleLevelZero(colorTex, blendingCoord.zw); + + #if SMAA_REPROJECTION + // Antialias velocity for proper reprojection in a later stage: + float2 velocity = blendingWeight.x * SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, blendingCoord.xy)); + velocity += blendingWeight.y * SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, blendingCoord.zw)); + + // Pack velocity into the alpha channel: + color.a = sqrt(5.0 * length(velocity)); + #endif + + return color; + } +} + +//----------------------------------------------------------------------------- +// Temporal Resolve Pixel Shader (Optional Pass) + +float4 SMAAResolvePS(float2 texcoord, + SMAATexture2D(currentColorTex), + SMAATexture2D(previousColorTex) + #if SMAA_REPROJECTION + , SMAATexture2D(velocityTex) + #endif + ) { + #if SMAA_REPROJECTION + // Velocity is assumed to be calculated for motion blur, so we need to + // inverse it for reprojection: + float2 velocity = -SMAA_DECODE_VELOCITY(SMAASamplePoint(velocityTex, texcoord).rg); + + // Fetch current pixel: + float4 current = SMAASamplePoint(currentColorTex, texcoord); + + // Reproject current coordinates and fetch previous pixel: + float4 previous = SMAASamplePoint(previousColorTex, texcoord + velocity); + + // Attenuate the previous pixel if the velocity is different: + float delta = abs(current.a * current.a - previous.a * previous.a) / 5.0; + float weight = 0.5 * saturate(1.0 - sqrt(delta) * SMAA_REPROJECTION_WEIGHT_SCALE); + + // Blend the pixels according to the calculated weight: + return lerp(current, previous, weight); + #else + // Just blend the pixels: + float4 current = SMAASamplePoint(currentColorTex, texcoord); + float4 previous = SMAASamplePoint(previousColorTex, texcoord); + return lerp(current, previous, 0.5); + #endif +} + +//----------------------------------------------------------------------------- +// Separate Multisamples Pixel Shader (Optional Pass) + +#ifdef SMAALoad +void SMAASeparatePS(float4 position, + float2 texcoord, + out float4 target0, + out float4 target1, + SMAATexture2DMS2(colorTexMS)) { + int2 pos = int2(position.xy); + target0 = SMAALoad(colorTexMS, pos, 0); + target1 = SMAALoad(colorTexMS, pos, 1); +} +#endif + +//----------------------------------------------------------------------------- +#endif // SMAA_INCLUDE_PS diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_blend.glsl b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_blend.glsl new file mode 100644 index 00000000..c875ce12 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_blend.glsl @@ -0,0 +1,26 @@ +layout(rgba8, binding = 0) uniform image2D imgOutput; + +uniform sampler2D inputTexture; +layout( location=0 ) uniform vec2 invResolution; +uniform sampler2D samplerArea; +uniform sampler2D samplerSearch; + +void main() { + ivec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4); + for(int i = 0; i < 4; i++) + { + for(int j = 0; j < 4; j++) + { + ivec2 texelCoord = ivec2(loc.x + i, loc.y + j); + vec2 coord = (texelCoord + vec2(0.5)) / invResolution; + vec2 pixCoord; + vec4 offset[3]; + + SMAABlendingWeightCalculationVS(coord, pixCoord, offset); + + vec4 oColor = SMAABlendingWeightCalculationPS(coord, pixCoord, offset, inputTexture, samplerArea, samplerSearch, ivec4(0)); + + imageStore(imgOutput, texelCoord, oColor); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_edge.glsl b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_edge.glsl new file mode 100644 index 00000000..fd5d9715 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_edge.glsl @@ -0,0 +1,24 @@ +layout(rgba8, binding = 0) uniform image2D imgOutput; + +uniform sampler2D inputTexture; +layout( location=0 ) uniform vec2 invResolution; + +void main() +{ + vec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4); + for(int i = 0; i < 4; i++) + { + for(int j = 0; j < 4; j++) + { + ivec2 texelCoord = ivec2(loc.x + i, loc.y + j); + vec2 coord = (texelCoord + vec2(0.5)) / invResolution; + vec4 offset[3]; + SMAAEdgeDetectionVS(coord, offset); + vec2 oColor = SMAAColorEdgeDetectionPS(coord, offset, inputTexture); + if (oColor != float2(-2.0, -2.0)) + { + imageStore(imgOutput, texelCoord, vec4(oColor, 0.0, 1.0)); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_neighbour.glsl b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_neighbour.glsl new file mode 100644 index 00000000..2e9432ae --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_neighbour.glsl @@ -0,0 +1,26 @@ +layout(rgba8, binding = 0) uniform image2D imgOutput; + +uniform sampler2D inputTexture; +layout( location=0 ) uniform vec2 invResolution; +uniform sampler2D samplerBlend; + +void main() { + vec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4); + for(int i = 0; i < 4; i++) + { + for(int j = 0; j < 4; j++) + { + ivec2 texelCoord = ivec2(loc.x + i, loc.y + j); + vec2 coord = (texelCoord + vec2(0.5)) / invResolution; + vec2 pixCoord; + vec4 offset; + + SMAANeighborhoodBlendingVS(coord, offset); + + vec4 oColor = SMAANeighborhoodBlendingPS(coord, offset, inputTexture, samplerBlend); + + imageStore(imgOutput, texelCoord, oColor); + } + } + +} diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs new file mode 100644 index 00000000..a6c5e4ac --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs @@ -0,0 +1,262 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.OpenGL.Image; +using System; + +namespace Ryujinx.Graphics.OpenGL.Effects.Smaa +{ + internal partial class SmaaPostProcessingEffect : IPostProcessingEffect + { + public const int AreaWidth = 160; + public const int AreaHeight = 560; + public const int SearchWidth = 64; + public const int SearchHeight = 16; + + private readonly OpenGLRenderer _renderer; + private TextureStorage _outputTexture; + private TextureStorage _searchTexture; + private TextureStorage _areaTexture; + private int[] _edgeShaderPrograms; + private int[] _blendShaderPrograms; + private int[] _neighbourShaderPrograms; + private TextureStorage _edgeOutputTexture; + private TextureStorage _blendOutputTexture; + private readonly string[] _qualities; + private int _inputUniform; + private int _outputUniform; + private int _samplerAreaUniform; + private int _samplerSearchUniform; + private int _samplerBlendUniform; + private int _resolutionUniform; + private int _quality = 1; + + public int Quality + { + get => _quality; + set + { + _quality = Math.Clamp(value, 0, _qualities.Length - 1); + } + } + public SmaaPostProcessingEffect(OpenGLRenderer renderer, int quality) + { + _renderer = renderer; + + _edgeShaderPrograms = Array.Empty(); + _blendShaderPrograms = Array.Empty(); + _neighbourShaderPrograms = Array.Empty(); + + _qualities = new string[] { "SMAA_PRESET_LOW", "SMAA_PRESET_MEDIUM", "SMAA_PRESET_HIGH", "SMAA_PRESET_ULTRA" }; + + Quality = quality; + + Initialize(); + } + + public void Dispose() + { + _searchTexture?.Dispose(); + _areaTexture?.Dispose(); + _outputTexture?.Dispose(); + _edgeOutputTexture?.Dispose(); + _blendOutputTexture?.Dispose(); + + DeleteShaders(); + } + + private void DeleteShaders() + { + for (int i = 0; i < _edgeShaderPrograms.Length; i++) + { + GL.DeleteProgram(_edgeShaderPrograms[i]); + GL.DeleteProgram(_blendShaderPrograms[i]); + GL.DeleteProgram(_neighbourShaderPrograms[i]); + } + } + + private unsafe void RecreateShaders(int width, int height) + { + string baseShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa.hlsl"); + var pixelSizeDefine = $"#define SMAA_RT_METRICS float4(1.0 / {width}.0, 1.0 / {height}.0, {width}, {height}) \n"; + + _edgeShaderPrograms = new int[_qualities.Length]; + _blendShaderPrograms = new int[_qualities.Length]; + _neighbourShaderPrograms = new int[_qualities.Length]; + + for (int i = 0; i < +_edgeShaderPrograms.Length; i++) + { + var presets = $"#version 430 core \n#define {_qualities[i]} 1 \n{pixelSizeDefine}#define SMAA_GLSL_4 1 \nlayout (local_size_x = 16, local_size_y = 16) in;\n{baseShader}"; + + var edgeShaderData = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_edge.glsl"); + var blendShaderData = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_blend.glsl"); + var neighbourShaderData = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_neighbour.glsl"); + + var shaders = new string[] { presets, edgeShaderData }; + var edgeProgram = ShaderHelper.CompileProgram(shaders, ShaderType.ComputeShader); + + shaders[1] = blendShaderData; + var blendProgram = ShaderHelper.CompileProgram(shaders, ShaderType.ComputeShader); + + shaders[1] = neighbourShaderData; + var neighbourProgram = ShaderHelper.CompileProgram(shaders, ShaderType.ComputeShader); + + _edgeShaderPrograms[i] = edgeProgram; + _blendShaderPrograms[i] = blendProgram; + _neighbourShaderPrograms[i] = neighbourProgram; + } + + _inputUniform = GL.GetUniformLocation(_edgeShaderPrograms[0], "inputTexture"); + _outputUniform = GL.GetUniformLocation(_edgeShaderPrograms[0], "imgOutput"); + _samplerAreaUniform = GL.GetUniformLocation(_blendShaderPrograms[0], "samplerArea"); + _samplerSearchUniform = GL.GetUniformLocation(_blendShaderPrograms[0], "samplerSearch"); + _samplerBlendUniform = GL.GetUniformLocation(_neighbourShaderPrograms[0], "samplerBlend"); + _resolutionUniform = GL.GetUniformLocation(_edgeShaderPrograms[0], "invResolution"); + } + + private void Initialize() + { + var areaInfo = new TextureCreateInfo(AreaWidth, + AreaHeight, + 1, + 1, + 1, + 1, + 1, + 1, + Format.R8G8Unorm, + DepthStencilMode.Depth, + Target.Texture2D, + SwizzleComponent.Red, + SwizzleComponent.Green, + SwizzleComponent.Blue, + SwizzleComponent.Alpha); + + var searchInfo = new TextureCreateInfo(SearchWidth, + SearchHeight, + 1, + 1, + 1, + 1, + 1, + 1, + Format.R8Unorm, + DepthStencilMode.Depth, + Target.Texture2D, + SwizzleComponent.Red, + SwizzleComponent.Green, + SwizzleComponent.Blue, + SwizzleComponent.Alpha); + + _areaTexture = new TextureStorage(_renderer, areaInfo); + _searchTexture = new TextureStorage(_renderer, searchInfo); + + var areaTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin"); + var searchTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin"); + + var areaView = _areaTexture.CreateDefaultView(); + var searchView = _searchTexture.CreateDefaultView(); + + areaView.SetData(areaTexture); + searchView.SetData(searchTexture); + } + + public TextureView Run(TextureView view, int width, int height) + { + if (_outputTexture == null || _outputTexture.Info.Width != view.Width || _outputTexture.Info.Height != view.Height) + { + _outputTexture?.Dispose(); + _outputTexture = new TextureStorage(_renderer, view.Info); + _outputTexture.CreateDefaultView(); + _edgeOutputTexture = new TextureStorage(_renderer, view.Info); + _edgeOutputTexture.CreateDefaultView(); + _blendOutputTexture = new TextureStorage(_renderer, view.Info); + _blendOutputTexture.CreateDefaultView(); + + DeleteShaders(); + + RecreateShaders(view.Width, view.Height); + } + + var textureView = _outputTexture.CreateView(view.Info, 0, 0) as TextureView; + var edgeOutput = _edgeOutputTexture.DefaultView as TextureView; + var blendOutput = _blendOutputTexture.DefaultView as TextureView; + var areaTexture = _areaTexture.DefaultView as TextureView; + var searchTexture = _searchTexture.DefaultView as TextureView; + + var previousFramebuffer = GL.GetInteger(GetPName.FramebufferBinding); + int previousUnit = GL.GetInteger(GetPName.ActiveTexture); + GL.ActiveTexture(TextureUnit.Texture0); + int previousTextureBinding0 = GL.GetInteger(GetPName.TextureBinding2D); + GL.ActiveTexture(TextureUnit.Texture1); + int previousTextureBinding1 = GL.GetInteger(GetPName.TextureBinding2D); + GL.ActiveTexture(TextureUnit.Texture2); + int previousTextureBinding2 = GL.GetInteger(GetPName.TextureBinding2D); + + var framebuffer = new Framebuffer(); + framebuffer.Bind(); + framebuffer.AttachColor(0, edgeOutput); + GL.Clear(ClearBufferMask.ColorBufferBit); + GL.ClearColor(0, 0, 0, 0); + framebuffer.AttachColor(0, blendOutput); + GL.Clear(ClearBufferMask.ColorBufferBit); + GL.ClearColor(0, 0, 0, 0); + + GL.BindFramebuffer(FramebufferTarget.Framebuffer, previousFramebuffer); + + framebuffer.Dispose(); + + var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize); + var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize); + + int previousProgram = GL.GetInteger(GetPName.CurrentProgram); + GL.BindImageTexture(0, edgeOutput.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8); + GL.UseProgram(_edgeShaderPrograms[Quality]); + view.Bind(0); + GL.Uniform1(_inputUniform, 0); + GL.Uniform1(_outputUniform, 0); + GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height); + GL.DispatchCompute(dispatchX, dispatchY, 1); + GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit); + + GL.BindImageTexture(0, blendOutput.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8); + GL.UseProgram(_blendShaderPrograms[Quality]); + edgeOutput.Bind(0); + areaTexture.Bind(1); + searchTexture.Bind(2); + GL.Uniform1(_inputUniform, 0); + GL.Uniform1(_outputUniform, 0); + GL.Uniform1(_samplerAreaUniform, 1); + GL.Uniform1(_samplerSearchUniform, 2); + GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height); + GL.DispatchCompute(dispatchX, dispatchY, 1); + GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit); + + GL.BindImageTexture(0, textureView.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8); + GL.UseProgram(_neighbourShaderPrograms[Quality]); + view.Bind(0); + blendOutput.Bind(1); + GL.Uniform1(_inputUniform, 0); + GL.Uniform1(_outputUniform, 0); + GL.Uniform1(_samplerBlendUniform, 1); + GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height); + GL.DispatchCompute(dispatchX, dispatchY, 1); + GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit); + + (_renderer.Pipeline as Pipeline).RestoreImages1And2(); + + GL.UseProgram(previousProgram); + + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding0); + GL.ActiveTexture(TextureUnit.Texture1); + GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding1); + GL.ActiveTexture(TextureUnit.Texture2); + GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding2); + + GL.ActiveTexture((TextureUnit)previousUnit); + + return textureView; + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin b/src/Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin new file mode 100644 index 0000000000000000000000000000000000000000..f4a7a1b417766c12bbac4e4bdc56796f18538bd6 GIT binary patch literal 179200 zcmdSChkqN_mHs{GL?MVqfW3FHfnW!V2!g%$UL;B)B~rcj*s?5HvMgIJaxaPFIB^oE z$4Q**W;dH`Nwy@L&2IMd`(NJY+?hck0nA9^T7EGfTOP1u^T`PkOdDsFqO$-AyZ^Es}aGe~|W&IcazaIQlpzkNAFWxdq_ zZ=fzv-`U`A>}>Kk`J1u%XzTF#TlkGzGW9t-GduG36t$POm$z58RkeBAYTCTmYTN3t z)weZZlY8AbP3LzV1VNSLr(%BKF%Or`?}%~6Ig;6**IU$6+U*Wh1gZkn0Z*W&)7x3w zS?jOstoPUZ8~hF2hITS>z|oT$$n4DN$nPlHQ@p3N-3^iz?Un6S?bYqojOVWLJIO6_ z6`Ud_Ac-B1c{s=0F49>gq;@57+A)$kl-Zxtm)}#^UEE#TRn}Dwl9hp~&T69B z37Xsy^@4uPJZSHAb*F)0wm;8b&{5b?yr*PO>7KG6Nx9dRXg6bs|#_uY3i95|5;ufGlALm9KNt9w1zW*@~m(LG@VN<hCOjw|L;Ia4^H_&;Ce9_w(~{4R5+lv}EUP~ftD(Ku_G5eYg*V8??@g~Sm2 zjwv@_!OKj6Qy@4RMv$TjRG8YmtNd_H?f`l*gIpVII3JPN7@t~bFY$Yf!ykg*Y2{JX zhGswcK*PLoHZiC{c)}sFW5y8S>^LIGe)X!BS0KD#h#)+npu%0`cLmox!EGRhjB{OF z6IagVaLID-im!5yb+~MPR}ff_DG#X+X!ns1G|rpC2-=}RiY8!4ZBZOnkp(Y7fwOv2 z;JA4-X_#3t#S%mlXYa1^yTF~|Ho1M=3>3JBtK~|$Ec9jcA(h4ciTFLn;Sa&@tm3%x zuxeeiMhXPMInzwylwidph$UbM72=OMq1b{4+^1P#3Y;+z!7(CeB|pe}!v0<5cM*N) zqp;uwZj9?j6shKl5JzlWLTqm09^&^Hhd&g*6Yzj!!F$OE!iGW641(5i>zHlKK1!Z2 z8T?Kvj*th01uy9qpulP4q$!M`jS-a1FB?7MgXp20M>8*DyMpa%*mi}R<&MLJT|gX; zwqFDlRq#6new(o11F+x~ z){kK}j7U5Q4e|uR?=*Ta6hYQBt5Dzq`hgThreMPqi3NgkuPZcLff1@^j1o*E%I-&R z`3QO}ClEExfa5yoCF1Tk`u-T)#J7dSLpnj3!F!co~ zjtB%9zvGHcdl5r;1yU3dy&xkf_qt*`@ozffLObpXN)o?sX&D>;$I<+~*G9USkeQ+y{!6V9)bx{7->?^UnPd zC)oK*#Q0y7ve$Bf2kZM!41tUGCJgzu_?Iak15-jq}jI*LV-Dm4c z95PNM%;*+0%bLCFHT412x^hE#NV%!pQXEknRUDH?kGmxIy4zZ8$K6FWdME+(^v2QO zTR_YWAOG9Af7=PR{~CEXdX3x8FSXy(nK|GXGfx{A5|(v)wQHIK>UH&o>X7O%NFKq) z(>%V{rUAox>E=3qe+vd8N+h`n`=gv7{H-YCe-Xz^?XTNY9LVf-4cbPNCK9KOv-VF?TUwL$BzBs*OudZaux>;yKe^Y9-fE0cInnDY zg$kn&;Dz?bIDz- zIvgWl?EF&uo7yWoiaK+;(|cV5jNq7gB5}${mOQ6lNLbJDz@I43_pr#Q1L zts2=iE8X3d3qz>8qWL|e?i^Ja0V~i!<03Pj^c*Q#$BVabE{mpHjJ*EEqKsFSZ z(w{tNCxW9%|oUY=UzgeBXFp)biNm!I70 zN;E%#@rylxCpm&ed}1HYB}naWZmVf`cNBK!c4c;_^}2}QFi#M6Oe!Qxh7Qjt_N%9L z14e&Rv#r)ym0Ffr1P$gEWtOJ9Q!Amun&dhg7|Q1-@46EFIL7ZW2Jnbayu+pT``WyS zAjST?Kz3IKY&eV{c|sz{Ecq-Ri_dC@3|)zBmIgkZiagab(aYSTXTiQ_pKAV8Pv{j--~PMv5Y2!)b^k z0>SJe`TXQvSF!<5_yfeR1`(tTeHgOfZ1RCUf(=s~8R7|^=Z-24s1`Mox?z2)$K?E#pD-TD%MTi7v@5DLJjB>!4=_%E<|%sM!CHKlJxHQQNl(k<$L(>s%1u69 zmu5&kp_)-HDwh>|6>Exvigm>X9z1S>+7XcL$Qd%uYL_wOPgmYj98-i}nW)mksPmG4 z|C9bePx46Ow0=&zqS?nD2C+v$N<0Ws&?BVd3ZCX^L35(cV(X8aGL@&N7u#wQn~fa_ zJ-R{7h-N}Pt)5lQE0>hZ%2g%N+^^^;>v4`GPU;ufmG|Q@RQQ$Y+WeKDG#{z!cV+fF zM$MDPISkulz!?ud@JLOyp*&2F)Ue_45hxy4@HQ)h-)e%-R+5=nwg@{ncG~a5Z9{Vpm?qAZ_@SH~9xwxUSUS|D^W^@_JK;f{%}g zA^-S@Q4DfCqLj_=pr$La##xk=RhZ&-c&znF&89Y^-_WJ+(e>*FwIjq)GpX+I(v=78 zW9-4zT-bvvy7Ic}knjyYX+Bcd?=0%c>~{^>&<^(aXo0n4Rf~s5f+C;+fheX?sE;ZD?^1C?52l*{FT{* zWwL|ESlK4k^4;0rS=yD;mo|_*BD7=HFdu9QJ%Zbh2f_zb2UW899Z`)ZbeL-$?z93B zOfPX&BztVN7J(s@7%+50iyifyrQJEbX@l&_?9pG?l~;pT-knzd-<_q?-Id>)*_Sfp z071)ydCD|noI^_%NeyJpyb?9C`5jZv=z2`eR*$nZy&xwiKcgho?W}Ut*y=2eNzLXK zQ@g1H6gwK|%Fuq)D=+Kt0F_-?VtO8zogIIApcFZ&>4Q?|@2m(Ec4zmd54eULqd|hR zMyL=B;Sc$iXl3)mgC@;@p*^YIUYSywkq?5I#i?bk3TL&=%NY8=u+7xbNLTL4?qgRT zv5Mave`T5s!CzTw`XASBFvOc4D8)~5`k=)5{Z)bD?mXxqbir;i}){3HPo-Z&SCZ>9yBEcOwAT=az$!MCL>ssR+dr@6~dBjbxDo5cj;)N zD|hAfvMb}ei@5R}=63N{ruN5~p2uNsYP(7mdwQT0KgsEX66g0<2THs0yGaKrgUN`* z!FIro$*T)3;Yo?y#DAmm+p8Ye^%&cd8f+e?JGEFKm|hAyu5?!0Vac|7;)g2-@_U3U z4@X>ihSxtB^6if~J&!|oAM1Y=idfSFrT9rsACx%1za~)DRoI;i9i$Do29rbWfG316 zoF}gihO+sc#)GCQZNI^v*lekFRJqF1iZb(abMrEb(%}iA!s=wQCb;PY~s2$?1a<=l6Tz$4d|ib4Y>xDIn+w5u8k^#9xJ061HA$riH9fSq9n@NFq(1amE3=n3C z1mOu8zpBo%F5$}XzF}9!dI*7FWNYcSi0OG8n?4XR|8Lv=ohOKRXXpIBZO8BUK;(X8 z&ZtKAg5s_MQebZe)&PMzvuV|O_~v1ufcDkVg6bN*|0mc zD6=p~Fa!K>3@>v_t%48 zIXqw?d2*(})WH!*yY?vZ(kRbTs%7`HB z%IL+A_Jdc(y^wI_u%?Fn7&g5x>IA*8{b5H%`!VX_q3M4#KP1Zf;?Vh#wZ(%7QVKgl z3<>IhdBG8Ts2!2q;^+?#niTsmRbgB=sP8s*Fa=T+sdTwn9N`IOceI3G8F#{>0@;;G z{XD^FZKn5v)B9pf&CO~KXTsGfL|FhXykY2hIGJem?E*z4pC{59L0CT z^hXbx_A3|EliFd#U_U)*N~%K?sdnN)6N@8-+3e-&f>uZ-z`;1_%SublJC zT?e!!H-FO?n*R#Z`{GW}lbrq+|8QCWD{+3g{AA~E`l9n+Nlnm;Ct&Q8JY3=fa`RBh z&foL}=f9E~pOe6c@k5FA#S$lwt3TQKo4)A$R}$lM63qv^`0IbAj+e_%cK)U>I{%f# z_?%SxrPlXK9WR%k?EFn%bp9)e@j0pXORevfI$kb6+4-Bk==@g_<8xB&4;~(CeX+y| z5I>Q6?c3te(UM}Ez03s_yA!1GsxH9S!7U@mA~nW z&VLm(f#2hf|AhtscZ;9w{7t)?|0@2UPXdZDfcLoLf5;m}>x(5`^7Z!+`I~k(|5f}J z(D=yz(fA)fz!?AXa`{E(Z$dvhcK)VQirvnC6|V`zFXjT?#~uHZ&VVQP{_UaiH|>7@ ztJnl3#^+)m$nSZ~!;$}w6v(SE^zY96#aHnqpGF47EhL0N7F5bYDP#c@ag>*M>Iw7JH_;#=3?BgPr}IeVx6XJ^mhlH#VvS zBGdwzxmGz_JJitE)YIJM3-|&powNnZ6VTViwl}T~&GrrT^mX-gbp-;Qfll`F^REtn zaEtM0hu+TSG@)M}{-+i0@xB1L`un=`bpF25xr&ME(VF4f!MgtXzJ}h$-o~D$ZrYl= z*cR~RR~6QkHB>fvn!UbSUtLQBwnm|j3SUWuucdOXc*QeaH{LkXG}t`g>+|)t^tSY1 z>u&9C?P6P?l{QHtUgUFnj9L0`a4V1X1<2+13&l(N^VUt*zKq43+5D-(@shE!5%+NU zP~~9NKy|;T&(l}a>+SL873P-}RhD_my_L1q^`3^B25)0+V_j2yQ+;y-w#Md0UlX>A zX&VJAW%HF&o(b<*?MU5F{b0jD<3MA7Qy;e8<{oU_zHXl+zb38{<0g;%`Zjk5YoU~n ze7xM}AHU_kro64YXgX%yNZyySoW77bn=_p^nLklDRy0~NTsl-XeI?6c?41xJxVCRpr$co~oK^Z?%_E zv#PCk*ok+W8?QoL9?S3W}wt0z2THKR2nBFDM`c!?Oz4!zyVH6jyMChl1u z>Gf0YTigvK!Yg^CqdD2}_usgma$n`{t8Qp7>(3aEnGahJ+Sig-T+69MaVB#*dn%ha z;#H7eR9I3}3WDYCit?(8>WbEBSevLlx$WTawmGYLM{b&T#xYjIrly8RjhOD<#He4X!-p9$o&%E)LV)dRL^KG zC!95$Fl{9rvL3XpIaZy^uBFt4w7K+|jOi@!%g=`b3rmUxg6@h6o?$i5(BrM~dc9Zd zXHt%49LheBx36Fo1Q$x@%4T?mL=g-}JtN>K*{C*-EZB`G;*ggyJMxUb;K+iXLKJD` zG9q`$`c1C={)ziL?)y;SYs%+U*ELsk=k%wH#}W@GZSV|}SDec!i>dQzj9)=PVPSDm zDI@51S3rf8#IUlus=8WG;uZT@*YWhttc{!ld3!-{33dz>y2BWfJ$ob@LHruHDkv}= zUQqgmW7`Qo=YGO{#Jzw?z~lHDQn&4u{#EY%+kbNZfGd5>z0KWM-c~&gg6H+8NrmP^ zV0gf`W?yxHp$jQ>@?pV+1x1C$#k>aHZrCwY7-9&DS8V5;CsL0@48e~qm{(wkAXHdZQ69z+{E|nU6qt#ebBUZ*@|ds>{F3___g(H??m0FMy#px) zAFc?HTz>z>{S!Rk54dk|?{KdwUQpg7g4zoSXJE(2OJ780;VtAvfb|kWAVZ_Ls=z_ZRM8xWDCo46T2adlNQ%7X+Uog1WP0$D~4H zXc76rg5d+14TlkAcFY)FwOvR)<2sRcB>iwEq6oa;zWmkv)k65e;>8j{h0tNxpBnu~ zo?injxE$FOvth$lL}J%f_#H}9le;7+!EIZFPMjiNOE8D`TZUdgtYz<)c!#6 zqVkUNhMEXo)SXQ@tv_izX4*0xHV65U1ydA(4HuQ*g&rX^$VU?p0sj zhX?`-<`qabOmQTfAXK>H58nO;>5n1^S#SkzBl6Mo6nMczc?5~yPjPScby)Con2m54 ziOu^E&`Z%Bl52U6Zcm$FqpcvtZ|#_NCT_EMe-K+Q^14Uc zzn%BC-+e}%3!bwc-vC`<$2EF%Nl8$(7ak(&C*<<`AMQU8fq#u?^#ktP__p3f6nPmo z{5%L==Lu@h!xIMiQ6FZf0)qsvS})npA%ZXqW(s6cM2sW(tA)5T#)jr5e{7wr8tH3zg{_ViF{r(;bVdQaoBhHZzMpaN6U4uB$ zRa(Bz5kd55^em?~eWC`js$Td+v)uc)|KW(=KfnY26nEU8a3A8X@h$WOUxF9Bt$a2} zP~=AuB&a|>ibN2+#`8l2IhhK6=tq+cQxriQ;Uh76geyhC=33#`3K1d1zh7+QAhcJ4 z);VMB51t_U51%4#%;MI?oS-zrZ{{E~JYB=XID*m-;Hi#)U?v50qMVJtq7J&BQTVS8}VXF{Li8CcQenDx(5hd8RwFEUPT56k7>4@j$76SM1%crZg{|CDYB0 zFUc-uTTymVPEk&wutl^v{BJL3Dyf0`TG3K|{4d)6h!gDmDOXN+CvSl80tlAkYr+795_dRu_L)4cu%V=)uq3}Izc9Z*peT?enqjZc zxfc|t)T;?o#=*p%B)_%I=Ce0B8j|aswN9_ACdHFdomz!WikrCqn040LZ|_LE-F=8Sad-jIzv9Y$e#XwOEh4ixkkxW36*TiIL-fJNIup!S-J%9=?r7 z$cqRHIh<2%PSBZP$;2mfq_@P+j{imOk8*D6K27w|tx;!c2Z4Lg+zIiyKG}u_Q@xSo> z!Gogz!1H+rwsi_N+>M}`!=#98Em5d;6QyD{^Ae0!U#XlnQeg}QN7rAHPO;2Ki!2uNQS#_xXs1-_1Pit9KNO%hs1M%fi&sF_0XnNeg$9W;o1r2f5V zJ!w4*1uj`HGINf1G-@jr3@jsqj#;K#2% zMvT3QoRRyOg;s)1nlf2q(V6u8_`H-r@i*TFVB!Q@NJf8ockKEeIyJt*)9Yse%!7 zs;%14_`(}#_f^>N8dTbk%hn+p=W{I$jo#X7Pi0kkMOk@BgSg=*9kLhG;h(vGgpz5T z;w|nz;`!4ccoq|dwxGiOs#Wz8uS1QqgJ8VzKiV(x;`w|Rqr$Ipμw<5_SoZr#d3FkR_X{|a|ppP>)nXr zKe~z%eto!bJvg^EHrCd8m;%cy+!*2%tQdA2vSiruf8p-!R~V`N7L@!tyx=VmL`_i? zt3AS0xCU=X45he<`!8G1!UN(dvUM+Pcn*CT(jX(qROp8{Y=pANQSnxU&xME#IZ+Z&ZK8PSZA)*PXki4M;KaTNxtnokM^8-YPcLjYNh7FG(j?}}$ zWkWL*VSdEg{T7~*JPY4{Tqrf%i;K55HPzR9z121311n&|Zg;60k9_dr2?`AV0)Btb zp0#`o1>(LJ-_}h=5Op!hj(HU-rMQXviQh?B@FD8~roef`5vqebf*#=j*)gxehk2AE ziTeNI13bT4C=idgEAWVso>7Pdal0?P3je~!??1;k!H@nuz<2R{kl-0+$D7DSEb@{1 z_pm?iXNX2S@QC+z$G2S4(FWWi8i6|><8g6LJ37W3oxf5&>`H2(Zm zj9NT^$9(~JJ~x;KFQPwliWBuE_3src@gono$rQMcDR7>}kqPDpQEVOr<9+vEhJCHg zzD9~5wG=}{1@c}nq(KlQel&hhPy4=yUd-1KMc#xBzl1pQ9D3$t#TOBcrMQXviQif9 zI|d8JJT543FM5K!7o5U99Yqo%_+?-e?{5Gx)1cH|yg5O2(I|UC&7L3_5R#G6@FnPfc!FYfBmtkM4&xZ(tDWS}Q z5kt_AVK3ebx-oTy`ZD16C-h)`3lB)Wp-%+`z6Be88D5YGUS}E<^(FQ173)RxU{1pW z@)jIYAlWbx9A_H*GBAqwHxR$3`Uc*DDTeSqQ0&PB`_shl4|oFnYxcbPd+5b{13r+R zV!jH3FM!}pMo@~IxIe^?x8QZ_ekgDiHoSoQIX;dM!FYfBmto)Qu*ZLZW&Rud+n>1q z8k z-@#bTk6_ck07q)KNK!7pJX@D~75$i-=v}`9hTlT0`5`F&9Qx+Z_xJ3(xu$~ z>li1yf&07Hai8*aoR9xjegbv>Lg0AKa990?;;Y#AefW-_gYB=GMt{dNN(v<(@~>ZZ zd|Y3~jCH@^xb}kTY2|J7h9BU*;sdn#n`rZ=j3TxA=d$_bx*Cmx>Se_idehIKZ~ZF1 zllO7Hj}fsyh1z}uTA$&3*NnF{uP8~Op9p?~^!e+c-bG9QvIFeNnM__WAJU)HTm->e zu;W)?$?t;Uhpf%tMeE3-W!rB`U1E=RO0{2c90aeUUvnQSd`DpT36%XkM)8{IIqiMb z8;bX#cOv*BX5-{Dc!uOHNsC|hS9kts%7S&jaVz1p<`M|rgdM*G)x0S%{03S_%8|=2 zH?1bgpD?OkRBm84=Oqxl4Lg2K_)bWNWX+#)*Aj2(?x|l>z6~4x2CncucuC&ADSi<{ zKH^kiWy>4b&-+la{?FkrY5Nu1e8yw&5udWiM?N-k@2@2BzlmoacVQzR;aeqdwfzh| zx@)i=ii7;#@N3gw&{a5`K4V`sZy1j0&N70}D(@&>M4$Kp&PqD`8uN&9`Q@coTiXl+ zniG$P>H^Rg((eh9wh2@Y6pJ3w{6v^8S|hkNn@rM;T&BFAY|h9zi^M6%@(ZDZ2d- ziu(!L!<8#-d3dIH5#Q08r_6uMWdk`ADf9NdN$bWV`coiynGw9DxXV=d8Z7x8{M~Z- zhEc66xHGqR^L_x2DGK$*Ts%?Sz;*86J9-CuKW3E4(@MSG!e2IyNl8ty&hOk|ys@;I}HDxXI*xzF1C zmTZ2x85NEObBCc{H=$lot|<4hbqz%$tApk<0lq+2{{#OyzFLJpqzEMgWA?Y+;uE&rW}$H)5jBb@0t z=qY2JzY&j-^qC;IfU{r2H$=TWzDMy1Jb7n%U;b#uv}?h(H|d~h({L=|Oc=pCiWd|w z!MnUFn_pf=d2)SHo3U3ns+m=q7#MMaD8Kvw z%IDYrDllCw%K2$^Y^nycC)@Gdd;=|c8Vp~?H^hH`ot3?X!&#H5bB-13n)#4%3tsSS zkl?fA31P?gW%J9;bUSM;t)?#hpmq`p+zT5%f;OFJ8hjS&xC=XenfMuRCEV4%q<)1d z@IBaYC_0P&?Xzty&cIXuUaT-UD8%;XI5e3QlA<#?G&z`oHV73ShS$A{Co^~PcT2s( z*Z(RoD`?00X?9#bXH3HE$#y*TU4vRLp=V3|GK#SNs@|fZobj|-=OV8_<^|7cFKDj7 zimxl5#oXMxihBy#{x7r4>9zVyov`3BC~#T1UvUUt@U%ei1_<7PIG}^ScL*NOk1$w)4Fp( zf;W`6;0fjO%grit)!3TMdknq05zQ28U9Q50H}S2V!BaYlB+nq0-Qlh!T{qs;-_gCm zEcgxOTZ(rPhd*R-g#5~<=uJ>8+4*up3C(!=KZSV|hdIn&^C7qtb1CXXbAk&5X-e-X zRJe|7O4o1Qx&C+C`Dt}*6&|D;6QIsY5FCalJjl-d6=>Y==_wt^8_k?bolRbb0uLk} zh7BK2I1@qercyq?(iD%aF{zy_ST_y@E-BYw!+fpZ5JB)W-PGUKy}&G3P#|oWyx=zx zJw<{$`B9S1LKsyiW(mv@KXLx4O=q&`QdBvZ;n)s4p218E@RfK)TmQT5{62gog_tX+ zOoGhm+(YOuQT5`}uw=idyR5%pBzuA^*iH&uH*La(PwIGrSD6*xcqo3=_J*WZQven` z3I#5pA44{L1on0o7DpPqPWm%F3x0Q*1;3_zL-{uPF%*Zt9zigA^KF>CTMg6gE(u0I_{@S*r6*I9ju9mZ~0@EB9z3T$|TdBIcg>zANGfgd8sJq^X+2ciO*4S&S$ zlfMlsj{Qo)Nca!ERqIl-YxQ)c6hMtDN0SM2q_!ue@_a3jQCh}q;O z1!kOP;H#>|W&5D-1#T(ri1TFf8OJ&MdHV(XMcYN&CAM9*UABg{E7o5sKh!?Zzht;; zylr|e@mAuqiO-sEV0#AJ^(20KI_c>oFP=gk!*jKBn4fVG+a+w`d9GMP+cm4aEnfSn zk`o$2D;VlvvPA&uP?`0{q=aNf&<){8z2dF^70%D+S15(PZJbsM8D=9O*D-=)+-Si{ z_QCYc)T1fKU8kIyi^NRMq?q0%O{qy=e zhTDecjJL4eG~F~k%eH)bH=drY;hFdmJlj2qC%k9yocA2#c^=QqX(O)k=v|X|{)QE% z;q!NMp%IHF(P*&foa%IFPLGbC9WW2;Jy1`L7IEc5-SxP1FV|l- zQ?Qb=KWii7aQYFVm~zr}5+qLxB#9>R{11$=WA>!ttIBs&Z>V1*irN=-_jGp??k0ew z{j>JsOl()xXtK|*2HnM``C zmM18!KZ*6fA$=FZbgi6D1shI99I4<5R!)@67p&y1WgpBs1cqB!4S6*6c*+T^OA9CY zXN+g^tIt1FypLGx2x~Gd5KyY8qf$Vjxg5((z#bZ20M)JS#)a-9@NBVvA z%|23mRq-BU_`2#f^(!Jp?LF;1U0!-svdL((G6pZpyZ_;bV&n!WKc_jRc7eV(E64HX!w zUWFF(GTp9PhtJlDxqHKy!!nK87)!8Zo*`NDA=vZguGud#%l% zAS!|*8qY$7i%{Vz<_M83AKX2D6614H?U!2LD|LKlL$7zJdW;lUI#;|HPB3K2P$ED7 z>L2k`>{qbipI~g{Q`{YV3!d=nOoe30#4tCjB&`w_ObYD8N`wJ{;0&S(sgNw0819<> zB*y2Y+Ap=fSL*oA#@^aN&q(D&`7~^Jo(SecgS;mUv*iE5T$F#s{EB~o0_mPRMDXLF z3f}|6yzJt%3YRyz(cWV3KpgHxUuFdJ&?XT}<`7L55l@KWuJPl?Ph*bHNwr^UeXrE< zosB(p17yJ!6YiNZ*l_V;;WmQIlK%>R|BgP)@9<=n?s+~#91%6h2=Xda`nmvSThcyC}n*S_KPkaQL7=vf&=Yks-l~r{4~z^ zoK*Xz*7r&s?{Dg=?;{UbJ;p1LdNNFdB0<(CWc>aJ4@eeF3gm70C%7jL5hOd#1HZD= zN>>dmm{(vYY?$H*PjCX!nCy5L^%r-1POAO<_(AOTy;8^fo4Xo%$$~ww;7am=WWx~z zSuFWq%m)1v`p{&-{~#!kZ1~692MqkmvXB;71nx_ksd{CJ_9dV8!3!a&rnZOTn+2 zS#UFaV7tvPc)@UjyT*?nKdr$~YSi&LsrE~)@0B_p{1Abe2P6xI0@07*domFOi62Ga zKcEluJ3)cuJ3d2ChT;fM@Y@2v@)USLX2DP(`Y~j~A+$3?($TE+cD{GD>^ ze-v-jjMeuyq9_8@_28>|w1mp6P#Q9}b(o#S`JS@6%0^Fy_HNq&!6z~eE?hsftwQdm(^?XIb;t*Z0XhgJ0u z>w5V3x*iC~ca$e}2TY6BndFI-(e&ZWp`3xd{`|he-lE=;p3VG;26#D0eV# zprF67uc$ZqWctCJm4f-=>C%bvv5L{E;p!pJV9h{HzqhZpudWvxV=2k+F$;Lm@XceC z50S4ws!CB>>@GoN6sk#4Db)24>rsFs7ID@$AUuBusb!y0o!1`MA2RMYuUeMubICKV z>D0-z@r<#|(d?1zk=$X&nbgg!{dp?|3&pdg(`A$8;}v6-BcM3!A&&iCY_$k$lKf}@ z^f3x}Q0MzG%7@73Csw5>1HlS+h#~5Fu!&8XNqa5J zwgtzWa~2e*(k9Z!Ge+&_P`+X#dkq8^i{?sZ7{iH*@ybz99PtoE#!-@A_yj)55AXay zWct9J`=#EIo4*sO>Tl^=TW}mFcO~bV+2WsGiB56iE?5%RykTVQjHF&XISd* zM+fS$3V4L3Z#`1^5c&F}x-wK31zBKOE_Sgd1<#NuQk5PrZjyhEo4F4#N%uAgUecV^ zozNdK95Nj=6T?;8l6{esXuse@;#ex6lCu|SP8W+7ilIW>`b-f+bmWO5lqkt>=L9~< zL&85m{36o_hVPaBS#JJfr1BK2Qcym7KAXK*lOn{h1~)0c!Jxu7Ftzn1#VtngobFV@ zQNyM&#BjyFi1GoaP`=`DCKX@U3xZ2Ui^cOyg&~F$m1B}Eh&F*w@}STMqNNWE?UnxN zVfh8CQt$+M6^1JGRO2S)ceo{?2_+BU1#j{MwWkw~>9>rVrj5h{(BVGl@M7{Al&?VX zfGi{|4ibbNGZh9IO13~8K#rWiCwZ9o0si*+k;{j`e(5HH|9|M;(2a%o6B6_C8J$B_ zDL}B4a!gQt9!iS4s>_=5+B3|Kw~U8P>tMLgb}9L6 z%1Kr{U;{S1mPZd=mI`UuXCBdnEP0x2S!`#_fB6CAM=szIKKy{)7ThUUe=La4pi!>@?Yg%RNO{#ZIR&VgyV=NP@(OT5>}3R(nH3AsB&?~?e~dqYFZwZI@ACX&r4J1IV2mFh zQh(bN$f{C=2ttJz=Hd&5P|Cayp+F>gQlQKQBX~u_6Fj9qZo7>1?PtQuSFi#q6i2X$ z1ktC7@n?tL4h_IRQUMR*_ZaB|A5wqJ1MaL!5hCaYKPnXRD~u!XiP}(P05<$QmeY|2 z89{i$(^ld~_tfwm1ujbv;;y6K4VN zL+LLVgM&6KRs;wV1V1ViLKC&Vk0~;wz}I2J_Yg;(RbCeeB9?&P1;#H_zJf2lz(?ZM z{1x7c(dhUKfTb4jMx_>d)1^}ugJ8@B#HgMUcLDH2^CJu96&P8S0tBf-6cXY55&XnN zZQp=CAb3@CQG1@}hX}$7&`=RBA&Q8R7zA-w!Y|;6w*Xjj0dI&^w4cg3 zn-VjdV=f>rqUtGe768k>|78IL_ABrUQy@|-vKJ%B`28~?2tU>GTUZeD9-_!A=*gg5 z68bWvK@bGLilVcvbr1;BFoiFHwkUkn8j#4^TDEEGZ({1HUC9N!5og%(3qSqMtkl{-FvE{{npZ=D@nRFTl2<})wEKDz?C>C`AaM@n#dEIT*>sU4=rUIgLN<@-R zfF&fXvW=a@EDCMNT`t!Eta#(s1xd3?k_}}*ZJ5CV!MsWEsXE3UrAh>M- zad3KJj0M1zbFNL(MeR-1eZ^ZyjPWsgz2C?96D1$uliBd;s(zQ;m9VOmPHXi$x%><- zsozydr@#7RWNX=PkqdNpj{imOk8*S?vPBhy}#K<-Z(C_e5C$?3u{e zXFYDXq`jefLHQcSOemcVr3m=}C{h9fApw@F)i|YIS4gF``n6ns#=Dv~lp)Q>N`Lil z;cX569KJv|+W24i{@_8;e+Z2~+hD^wi;8M+g)JZsn_fsZykh~dcQj`)dBc2Ce_8X4 z>UrgT1)tc0l8EuiFodKrDUFGP+GW+2B7Rz{&*kzn-PXRMdPgCZ{_6M9Ta6xtxBxi% z_+My$j1!1&i{sB&KFpbw27}hBO2q=f-~wXWFHA3_sz*B(0M`!Z&!+6NY#GlaT-IDy z-B#XHh{>BMiP*QHL|RDy`X z3v^?R|B3r|92ofqzdTh~KFlR*^d_yH5fl~>6F;i6z?T1Vs5!E20dU=5(Ny}fecgP_ za8`Fk^R()g@&%|+Ox{E(F<1hubg#v47}YGQHWcyFT76G8zgr3SG}7s>ejC}^h~M}H zy0ORqgay12$L#oJ+wx)9aDrLmP*Qy=wtzTd`7ejEQQH;(*Y}r=PjZF(V9-HKQM+|XAxmNT2O zVn3L)Wjv)nue%BjB83?2_`X6+-UNmjRd!!ukA6(Es6L=Ps)&`=>Kk(TnOXWPHPT-x zKTzIA{Pk;4yJ6jy_P9peG1dg+SH{$aMUxfGMyGm@0eb= zZ2@p&uX`kKI&Co-OP&*t8crvi*Iv~;rM>}A_ySZ&DSqC<2*7(t%hY5J7>1$1cxkP^ zDx2R8!yVm=npdH~c@Hzai!k=9DHpk7lRLVCn#X|3LU z2!6rzSBN5fk~}`*QzAey{greAy6rr_;NP2iDu)UuvSw449c$JN^Oo_X{)|o}h{QQc zB*20SA7qw0>n&}jUj3++rL|ISDWavddQ&#PXN=DwebasQYfOP3zzar8fAxzm7{^0j zuBp3fuxKm?7VKPw0uPyv!V8|&T@*BU3!aed_&%kzvLdaOVNf>-1+uhOh$Gw6TFFi{ zCZ@mAu=H1mBGJ=d{p_I|^aUT#)LlJLGMYP;G3SB;EgR-d({b4FS=}W@@Ve?I%KO5O zk(SA8^O=KbtyKGA!#mSjy)0LMNZ)iz|2#ZkF#Q#?;qB?KwkHt#g6T+KuDRROUpkyO znK_fXl)P#?kaQ?<%XmV6I^i4$UPC0gj%a*a`8+M_wKXN}fd!A~rZn@2A_viv*`C(w z1^N7tz6t5Cm<1#K6`}~)Fr`rpC2#r>;_;XK)!gOjD;p{p&z??OaIVzCW3PS~y%;1`QzNYvY&blv)pgnYSo$lLzUjVNNPmSW!rO2t{nhuLFdoUv`vTsc z^1h-{^bIi!o+-AUUHr*FC{N6 zFCAM3+cNVq^Rj}Q#cR)ZvHcmr_p|fFEhkUho-iKC%hwgU^NWKOfTxOHxZLE0%6+liizK<*abIQ_50H z(@N5c)3Id~(Uw`rZ;}mo!XIcXt1K#`>gQBJKBD%ysJw^dheGqP=2hjCdQjVw;5W3H ze2GowhNOB+t<`I*v3cy(j;iD;%Q4HWrQh0tGV~3O+Tk7@>V zJ^Fy5!`NnOHTldpNA;2+ULYlikr9}v)6<|^W2tVzj9GEqaM=?>-u%Q`YuDK zVUMXD99t57mUEUvmSxMN1qI}-owjyc3m7&!>K%2USmX4#h$BcoVLXzT_jzk8tKAhu zaC-&$uYp=$sQD|_osOt}ULc>}wnFoWCI^(O z$_4eTdQvl{8PV|+yDb+j$1UrKB6Ex&3dlo+9X7t`G%1m%So?(WNM63BuEFE2tfuPS z0>P*iLGU|iISd6ZS!OKb&>)pt4OsoQ zJpw~gA}A(d5ZH?Zr!AP}*d}c4n2aKBMxRSZxgeX_Cw6{D3cxJRJBY0pxl*H@U)PhA zL>XfnV_c!7p`ix#&nr;>oUeCT%qrFf>rNx4U@MT%FG`_#%&$A7K%sf%vT7bWoYG8a z#w?dCrz~5RgO*j;@H7Yt1>~(LARo?E_#5$n`#4$vvjpL3fi*3q6Ota)a;RDC; z&q(oW#lzkvupPsRZt3jrcwA*ixfU6uL)-_!I72G&o3kh z)U%wObcGr`53U!k(2V-=O|^AYyOt`~Qq4=A;I``Lg@sbw#Qj*m5TnpMheGpE;Tq4- za@leg9`LZ`fMqXicn*CT(x6a49xC)hhhQiIuwn{XA(FU?f4|5GGoN0@?8n>$vsp<~ z2a4HeJd7i?QtCAb?UitPFpP2jumpjWIW`-AMJb*KSA!#cSFC1< z=|s_uYf$}eRR5*Qp-h4Bf@N+jZ^NrNSV0~=Ln&_JezyK2PN8|&F;gMqcM=wSNKoKB z;>aW;7%U)9DhzLb_}^ZRR(psKtrfe6RO)^%N8>OU6V*0YS{AIi2rVoRKRoKse)zpg zXb+i+k(oHeCV2iG2?CW!Vr*yE$OA>8{y8j|DX;=@I7CqNgixW#N9tej`&Z0;i&JPG zcD$+B0KfByAjd45Oo6LV;M@*^-PW$iM(dG4I0dcEV|3h5B4#*|g;BFO#Vx^7x&LptaTksmA=3an(+yCVtmp0HSoo48+C{}H#) zJlV133izFd2i!soAsm z21BAI30fkCQZJg{QU^=S#^?3;aFp|t1;2(kyunSN+f)fB>%cr1g%%90T(EvT#Srp= zV(n?silM<$EY=eFNd3#!f5<2_4}KRAfluHrP=9g$^z0-FX9)u{&(B?#ra)P69nprUt=SAK#C!} z0;3Qtko$@q;x0l$Bc7X zh2|N*v$zL21`9sK6o@Pdf)|{^Jsrhj5Daf?=w~f_y#;2JLj0(GUqd;ft0whE8{|l@p{U&Cy+(F@pO_W?1f)#t=Ie}&H<3Sa*lJU>4_DQ1E|q5e75kLN9zSDk@h({B{K_f*bdu@+s|C^w$j6jMq%p64_?HCT>aB#LaR|-WHo5O{Yb~~7xT7X zMLDN)C>u3{!GLCT{j!zmm{BkBBK^_)t7z+gh4cRs5$gL;_&e|sH!x#m3#EG|Q9h-e zCZ^V`6mDc6$vBaEGUc@Etn-}nT=Mzk3&|H87ae@zk4q^2fz5@o9*B~MU~R|YwN4>+ zo&`3pTuI9G(Io+*rDzUrhWgJJ@s0ozrX{1gG=I}|pcz&_g|FZK_g!&<7MNmeEJKR@L zHfjTv!IEe)p16BMp%w03#T&|3RQJ{QGz+%vm2gXc6Wg-}Y`en` zaQFm>KY)jSSJ1~{*zgGANIh=#vL*0AL=x+NB@zTSj#ti=tQ4%}t!E#~+RQkTel+cP z8mW=CQ?4`6BqN#bP42LvtOxx23K$+lpM$4(OyJ0qJdNnf3a!AN-T}kc7{wPg#1SNQ zM3PuOw_E(A#^1NW&5 zu;jf0LsDXpBFn!KEVKd@zNetV0Ed8lwjU`W<{h^NT%M?@4_ z;XXiVMxNn=5XD{NCow)J)qbh%^CcmI6iKUtstKtQmz?RIltBKZ)@qkx=_3X4X);sCp?gIFgt=_R%u$btKLCH6@;=L=;6*ng^N((D()S~AHpLB@87l+ zT7e4RQ@o>Kg;rqA4|a{8#Q2<4`=!?RN*xb=gWl0t z!$MgP5S$TINR~_t_p|lqQ46gIDijK>>>59b@j1!%WAqB;ePgcgl{~(uaiDg@Gf_EJ z&TNw9JJ>uH1sgasppR8Ara^F5hxg7Ac)vL3KtR#X8|gfDI+ zl=a{Vaxx38>>|Io<8xB&ms;N|b$n}}nfQ?hBn##hDB3XYiUmKoO8ih5N+|0gD6kVY z99Gr?BgT^gKgmKXyUH)l_?%SxrPlXK9p4)8Q3NIr$XhV4K)xqK8eE~0g!#k|Wj#SvSp0&VC=XmF^f6(9W@ALQid;H!0F17{yv~|)pc&&J-s@of=M?H!r zKh>e2Dipqsmf*(M33=gK&rqPdGtfy#`TZTSHpvE5agxs{@(3u5zzR$RnmU_0oBg!;#7*DnY|m~hY%6UmZ>wsnZmVhYw$--Pwbf&5 zz}CpNrnWiHNaH|rudk=2yQQnOt2NNtiA~Iw9Qrr@Uyr?mxP4Gcek$e%u3@MjPdOg> zC70i8n$t;ZuKCQ#+_8e;;=$5>cV9(cWp7olr^nM>1HpL%wVkz{b^1mH^pG(N@`Ah-UPH&EZ-(BIhC z)YH_>ZzvHIVkwDb4g#vjRG~+Fy~aI*P*e8EUw~ZweaPKbo!1>P9k8t07o0PxQ|S{~ z2JRo1@j$lIF|sWkH2`&H2qd zLq@TMr?^x!S2kTSQ8fmFBi^C90b*FkD>0m+WV4nqd$%7ZSbzyJkMMd64?QpAac&Fc z>3oEjTz=og6u4)V7f}*r69h3;1qD%Nozp2`m_Cs?o`te9tUQmt+tiWNZfi|$VFa7A zmF27Ikc>j*#n(CbPM8aX?K@>z;wW2VOV}=-}f?>u)<`^g%x=ns_ zyA^ARoGmG&K}Il#ESVTK6*U(#pUC*Rrz(O3Yes5=D$$RgRGWw@^it_KP%#e_D= z`9j;z_cjs)-Npj?WrU&fM{ojUy`j1b{BNP&L+JN!WBSKE#SP^p^%?E4ghNEo%*yiE zXOnq`Oo;cFbEdeB$=@?)ZHf zvkj)W0o-ZSz=mBk6#p=^ImB-U<`;kh%Q>1`OVe;=zutjey~JJTPC|kG2;_MW=|<)9<0m-X zQCx=t&+-HfAegw1ibJ7H*(?}N!7nALE13nUzX{XO*rk$oUNxvlRG-;dmZ~1=jxws%eg1C?<#}Xsl=aEQW z+yk87ec$t*d(OL<_F@Kp2N0dD_&$%Y6yHMFLJ$$k@~K$(eS}=Ri1X?x27>i?lQ-Ri zA(=PGm6mBm|F%H9-o6pLa|j0U^jdrbLZ`N{fY{(&Or= zlm}22WL^S0b{cjhNeZ*zCl+`bzuk?55)Gk%jm%{5OwNv=@cS6Kc!eOQ^GM)!8tEl2 zCYYrm%AVhM3Dx_6JN2sN4ib3X@s#tj>tfDXgJ2#6kwW-Q!cT1QNL&J=rO5<`841y2 z;ioK!jDdy(b|mbGA%*7gzcUQ{2N9f|Xz&CQ*pC86P{AUU&}m8^r$Mp6SVlDXG7>10 z@ewAIb=(4v`Q=&L-yf1*?i)zDXz#w;Wu&>L(o0X0nw(aR62Yp|m&R5_kh0zKR_=pCBkBVS=B`VbmDN zJSJ8W9|h+L>im=ifuNd4$L&ZeLAvjP;3PAYJ@i2+9*XxvXEjAA>jrghZ`$)F{Pt=k zb~Ju1Q3<=$T{@8Z2{TNIFv&5*a!2AD$afHu9NF{xV_XNs*1x5_W_d|TAUez_h$YTB z6W8JsUihtJ1~}#$^D7OGOJKA#m0(n14fq8BFGhF zZUX_4eLTTUq&;uK?+%RxEK15pF&Bpef;@G>8O6L20zWr*p`ChXKsq*dOrpW{Hb z&u`*j{B6dH_X!htow9hh83S*i!;FGri4z13elh~0!6Jb&lNKG0kAk`kQrd&@FTqcr zN9z)Z9hslY#Jg`{g{qYq@*4WM8H&=&J*pSS_`i?AzIuuCCj8#Tp?+NE%s$+Rx{>8E z?#I;pC&=o;79nWGD7SDIN703i{An8;`3~9h`wYkWud|Bqj?!R}z?-(~N{6u{SYllU z6+dP$ir;FnAxNOiV?>9O2eCkPs&U{d#sunV0qT#ckjAGcyGcQWqngK9Mk zcVZ%rPl3ZqmN#&p_=JymI+ozrYFVY0oZPM4ttk2~6nRHe ztYmG$xPQVonYYf%k-J_$;E-}$s2FZ;?5-LJK>-h#)dgz`*5t4CkNL;_`Bi zBlW7Ke~b0h4F+RHv?Hw~wJ_;bNA4uYy4-asea>fQ(o7Erk%BQs^^MAQ?1iVk{+I52 zN&}03`3m}U6>;tWjy;EfrW&No_=HJqgFM~cz;r}G$74+nnqG&6my{Or9VWX5Yled( zMWX?|ABvM4D>aDmu%%gT3s;4HT~`8Y~&0}FrsDkIYK+6it(GX$M3^jQA{VHnfaNcpfA39OSv?rK@yvGKNY z^&NVcYRCEp>xZhMp;*a?E>%c_$b)dyk2uYbv~$j#dEU{^&;O=CFRP}5Y^Qqfl4 z5$a@5v!|rDq>r89{!~g!|F%66ZR}`iXs_+8>?!LF_H!o&*%jqJ6mTE76GC&;pU8iZ z=(iUMs9`kkK#zps!u7v}=MxvE|KSf9k?v}jFb5;dn)5Z6gbo&is(~0V2+7lltZ#{# zl3MfX*;w9D#oI|~F)-9JR5w&PTox@IDIOu?#b`j+DWSMlILg0yV_(;9SdY54=T78D zi~53{<*ijsHTBe_u5GMts%)ufEpH1!GSsy+vD7oW!(;XRjqS~iZFL>hofX|>y`jER zp(ygKB)UM6Ow(%#v#&$~T6oszSF`8T^}p2fX&OlW)7NNm zS4DSuPpCI&P>gd-?rY*_x9VRlqkS9v7O(#$pHEzr{s&pVdPlp>>cK&DnBZZ+xa~GK zf>w%*MdbW@^!@65XeS@egN+SkCSAhNaOY6dP#A(0(X!zn7P5G>NHVXC1V&``p(w)f zJFHPWWj*fL>)Mh#=^G_=Uw=tYu%oQ4qPeoMx}h#oR~xRauW4ACSn8R5;Z5Pyk%5M; z##SnVN80MzYlLA}c~4nSP$)7Qh%~0}HAA^1LZE+&-{SSZ`SVLOVEp-OYEJtK8hR3A zu5SYfL2QZeDUN2s;DrLw82p++)Urczq^xBcNQ;c+Ceub~ry zO)aExL<*}rDurQpSx*RxFkJfiR}vMJ@SH2_SLxROjPnMlCH@yaA27l_$Lhgh%y|@% zu0#esc880Rut3%qK19P`V4wH|Q_Xdl6+K%5DG;c}FdXd}ZX0SG4iD7|!I&Z_QaEBT zEF39-;b+{1JKAN-N&7*^F4txj3D){Y3Wfp$q?_v!hGngl&6N#ROA|{yb0EAeye={t zSykWD(9zgRnw13BBnSp|DTEdRhBscj--0XwEidOO

mcNCv}CI)%wgy9@(bQ za#?1S@cle38tWVGM1vdHAFE|$sZwMx8XAV6NTJA~Jb{h=XXN~Ko@Fjsp0FKZ6pRzh zTkVe`h5e$*ieYy2JnIjJcQOKwM@Axp=x|qKyH2nLf+B@5tWQY#QCYNN-Z&!FKJQ-fY7&uVhD+DF$ zQ47gfTf<0UjjqY&`6nO9`k%pPj@z*d9hRz&Qc=pU)(1afgnSbTy~#ZB6n1($?})-! zQb@!`28O$%ZNtq-U?f^QRAUf~g~XN^QW*O_tEcZODZglW!a8f;@7V3y7AMF^h!hS6 z`UCyN+4I{S-WpyX9*e|~zynF#3TeOmbS;M>zG5~%AiE9@1b#fl($ ztfWx(FW*q*-Y`#$W5BA7ejUF#e5N0k(2%kNNl4;Ex&cp|bI30#E^%P80; zGRSF{<&K+)h>WC#gFhm#315~9a#%kAIieZAs0+=&LaFpEI~bsbJ2 zD0*z{U%te%&SjB6>x}(?V_FEhH$jm33?t!)tlwvK912fkfv1!PkLnVL4yO{7k?`~G z|8m=pjSgagyU^ekoH!53qQLIm|8Q>K84it;g&~7zOt@cjyFVM#!8nJ9?h= zhr)ZYz+0HZOvEK{puUfp45Q$@7Nf_@?ce8p{fOe%9qovT28#q@MU)OJORSHBjD&x} z{-wCLzsgMfI17m55LDLq z^X~t0+lSvsKNh4z*I3c4ex8b9+kT!CZ?NY0I<3CX)3+)B$^6@BS=yfU z-LSk0L%rOve4>2TXX+jrcTu=L?60lqf#lce@i$eU|B#)_ ztob!J`qwyjT93j|FEVO)kA&vewb`PJuA8=(EN^h;L;gyMP4#Ji2ub74 z$$fj+UmHuO^NzXZ>{qNeEVnE#B8k%5cNE7D>90=m)2#WqyQ_jddE>6__5;@AmN|TK zu4Aunb9aoE-^GXFLv1!V=e_ED&i119HOreY{2=MGzKI5Wn|mf&@UXwuSMCTL^d8SS z>v+=kwDqR-mgNo>_Dig|3PtJb`}FOj?D-9Zs#^ksxf8Cfj=k0y%SjksV#a*~tNj8x zeOGaOQ=2V6?SIn!wBtG39qV0-A))#mGxS}g|6zYk)^07C&Oe-cGUtr*qW!Avx?*_S zBJ!eBd|OFlw)*QHE{#NjUHQ@6N!J#~9{WM-QBsc2u|IQ_SV!vj)VSN@kyloL_c@2Gj zgIQnJ{GvroH5DyIeg07hPC0hj_t}o1$&@ODp_JMaeZB?1Q$=&WOB4-%27_X*S;tb!1;YX~bZ^J*}ZDnJ=idFjqEREP7<7g||%gV5BgXFxNO+IgS1K!Y%)BbDd z*oR2`;`X}>W*EEQQ#RFT-}r3#f4q4^&6d#aqJ8;?@{Z-6a-VUXb3W;~iWFYg8CqYk z+(DbOmcP{no#o+hd24Y`!BE~B&t%SKitSC?_alWzVR#x{zQBk$TYaMROu_lQOP;4F zc@06k5PStG{F3F3B!=QTwvE~POX$FR%F5`vEj<-lmgW-sUS#?%jD@&ZyssvaFynU| zSE^a==Zk1ry7q118rGzrWh@gLXN)Wc=dWqwt?TQi%6F7Z2lo4qet*on*0T zk-%#dY<^Y|d{OjRN#UC?6#FGFJ9~x)(f((l_$4(L`m*YaXgEGZjRDIzK2%qcqq|Ue z2b(AQug{ju_&pE32)-G!1pTLNf1Orc4dc^Tz!%l0%M8zG{S$4xeO+X8)wa-{qJ8M_ zjQ51+l>4;ntn-5NvMz-%yooMn&o95Xw5h75zO1dJCm;n!#}vVB6rI^?KVTOrJZ?E< znXNe(2k;`2Pk)JP3|ses zHr6rOu(^6m+0K&bLK6D;5M(42Jw^(% z<~QzNRn!q`tgekzAc4ib=y24##vq6_-h&iUz8ZcfOBn(4FX$484kr;*wnX$ewbMbo zE5wz150Y>SN|!afho?qxf=f-B_9d*VMay?X zu$LdVbB9jAP+Gq!{8~0d(BSeN!9B%$1Ef)76g-}LQpQ0=@B(^##gQ#P&$w@}urt_H zSyxxt7-}u)3iKBYi5Qysk1YSFJJJN#qqz3+TYL38pUa-RI^Dnx{i43`x!S+X=AYjY>#GPun#TX(GT zWaw1!nZh}2h>}3hHPK;3@D`Tj1=}60@q+HEU&W2P4NYB(U?2!G0!qgF7F;fJa~$FX zEoaA&lU?a9Px+hSADJBgyQux047+{YHr(_J`0RG(Q{%|^Zax}*?UPL#!)S2D_Rt=Y zz`%Z)$zVs~GRT~I&UH`xdP~|uja7BERSjjWNMJWQJm`;NN7i}_g2L}a=v2w+LNr)x zh%SLjhi}SkFitR~vx#5)jP*^7MkB&cMnG}uk@)FitH*86#{;dI4v!**X0P<|zbWk> z1k^@^t_`Q6Tsz*A;)FlI2W~{h$NI)Qupp7mHB*(?5F}7p5oR*8dB?pc^l=b^+4J*8 z3;IerLQR$7T1h&lB(PWMa11+Q5LEoiPU$0{vLTFto~NYby0Rk%!KM12hpv!f)PxvIpaW+mt=W1Hp2g@6rvZGvdFNKK?hUeYroA%wpOY8uB$;4sM4+ zuot1+)-v6+zwtoBA$1&X;HWo_BlR2+{dmuJqv&FAuKaZ66P2f`PFA0&IZ<=G=6J1s z9IG>ql5mVY$?fc|O|!$cKYW1WpgInPjpK0mu%vX$>d4f6sRyX|iT~tg2E0LrraH!X zufyeGtd~FpvzPAwmp=YCsr{WO>nI~cJx*B!F?f`Velrrjp?h1$uC_fbdz<$)?-!Dd zdh$t$u1Y?>X?fLk(|gtbWZ-$G737v6G$h-S`VlbL@qx9NE#E4QK8@5vKG0laovhdl(k05RY7^D;I+1 zX^Dj2((%8vZ^XCD{aJ+sstgMjAWALACT+5BbN9B+o$b5Z_K><$9SM@j{JyKbXM4%{ zocn3-72l=&iv{Nk=Zel2pDj64db;#<@QKi=(5W&xJcGg3`o@m>ei*I}kFz_z0ghAn zs%=9mb(TW&!SI9by#M3|hV)fPpo-wPc$`ilX!g?mBMtw_H1WTw_otf?q8uaaw6QK! z3c-nijlEkSxP$b&$YJZW&M=9jE|K`}THdg|=y)#YhUc31O5P>^#rz8e=L6?-j>Tt6 z;0Q@itf;H1rJ=FCz9%w(SH@^~Efgn_#*K>O7A2G09~eKg^*J+sX7Rmd*N+cw=-=G4 zwR>CFc0G2lZFed|MRH%WI6m6=?BG+DMZHgIkWzo#rK+BKQ=VEYD52~-l=XPMv+0%XzPcJh)D#+|L-g0 z5PRMFitV=Jmh-0ThDf0>^j<;|VJP}s;9lz|IH|0ox;3H*cGP#{6*LfMI0{42=ZOc$ z&t!ehtp8^5y=K>s4NnfPmux>W3d+%$AQ&GJbvgV~c4NQBOzACFm0nf^pLIPWQV7E< zafVO&F8JMJzM;b2U}r^pZA+xN2^s7_k9+ZJSrr*layX(S@qx*o$@-jG|IOli&8{Dd zPN2aX`{O#S2x3c;4>cl6{)11k^zT~Uu)b=0$u2SoL6Jhy=c*5}OnZx-KccD>>!8Vo_D!$Q!oCG!;9e~5R=H`$vZejgpa zs|Y^td`|RONukd0ve!M47b_qrDb!WbUeg**5bSK|MhbOJ9>S(PFn$s}m|=a+tp8^5 zy=K>s#jqfQ>xAFD4yTTUisFy(MfnzU%@2{lH__o&Y%d~%ae}%O%4nEN_Mwsd!J@v> z?(&YR_F4!wP|8|lQ1n=rLKzJo7(YFJddc-Uv;LdK_nKX=_zkUFg$8pdJFJg`sUu;> zkMN9={0ASiyCo9%Dx;u5Q0cKqA)_JLheq>Na3&JiQQcPC8i^BZQv~BuXrwhawIgQV zU#*{JSf4Yy-dMj{D!$k3dUE|qrcvF7B#(j%M#3NAr}syUfS)J{e9Q8sd4i7T^pVhY zov{#ps|f@_gM(e=9hK;CT}v`SU5|So96wqA%dkFY)_=43UbE}rH#{i>#fGHnaLP#d zXYl(jBj8urR}~3-10BAL4Binf7J@PoG8W>755EFJTT6S&kU-I4?1&+Q=y9jg;|Hd{ z!Y{-6oLT?P;(N`m7k*e^G+5b?B?N{3&uz7!{jHHP3bF8HnS z5zM8+tCa+b4r51}8`U`2rU-UENcofX(#!F)Te!6JcgT1AInu_Y6HR_7OkpJ;HmE`jLqB7zT4{tmO1wB-7nS^xF*tEJ<6 z&8~-^Sm0z0Hl9WdJEBHH;rGW_kZ+*DA2W*)3Dk91C-?$dEHX&;ur-G56rQ>_eu214OuffSPnK(t^_Zb%Cn@WQ} zWDI=A@+La0kAg3v#X3Rwt@VxMs}Ya{2}%O7BIvN#k$HmIu#@+geSfp{IkW4{;(N`m zho5eNl>{maB4ePwqL3tk{{>&}&+w=H7IT6@PY9&S)ViOzwpZx-)nZg;-_2S#4J!zoPQco6oudSlm+=zY48Vldl?eQ zC@7X#5hVNCSRTQwY6R><0=r}kL;@28Wj@{1^uYKnvw+7k%ZF&$*8k|aI=FXuTWoV| zaCm5VC^{UC#yF(!qsB2Z%AwtI z?DKCe-B`K4b}};2INm(oI^I6sG1fWO&CxT~%hAWty0v?K-{io=AXQeR&WcfEMX#dN;IYi|AzAZVTQXcTR8NRz`%ot-V|t>!!+pd3!vn(u(N)n^vB4N(Mkw8o z_GQbH&e^=_z|NAbWmA=#YBtoZkF0B$Y@BGGXqjl8XcL;_o#Rc@9oxDo#?rrGKow&d zn&6-u3oKPRme^QK4K1epmRZ1KndL*YZ0moF7mNg>RixReC&;j6sBNg7ki)KMcT`9U zOQG2x?PoZC-ge$~%zH3@x^QR7_RyA!sjAJ@o9i}(H%2zpuWww}G}#Qx#{F%(I=6Li z>Dk=3seeQNdML7tp(aw3$t+FzEwg~fGRuc#Eq{7hl;KLGka85mjYuLChgDe=!v7UX z!Z>@GV!vv6+CG zDGOvU)Jd)znl$CN%mN z#c?+0WbRDf!Tf!J>7qR)JA>QHww7`fr@K3Lkn(wJ_f+pDq)?}rEQwGw z<(KThXZq&FZ%9oaxcI!;U$V{Lxh`*Q0Vx7YAxK#i62+*pD8mNBmLbZb#GuGS$_LuZ zmK(Mwoo66;H1Cjqf5E;&VYn-ZB$jWfP!t;uHSbg9E4Hho*}5JZ43$3X+HCgymHWjq z3V19`A87h{3*L}5KQBcAB$;!NRDnXUMi@rPzXZcZ#zfKQmf=<=iC-bX>@Dll_KOfa zm3u7ji0@$j{=nYC>Eb;lyMsGJN)j6mHIe*T6%Wt}Dk)Sln4lHp6EuS{Rk-^WIzEDD8<3en+Uv@8a};c5skkOU zpiG19s^h%tjQdpXaqo=pkpDozzQEohVYnxVBsLtD@(c0u6-K(>AVoby3Ww_`w3T4UGS{a(n!JhxUbkP? z33^U=kLDfrAIKL?jx&Vc0k%i=@)cr7^uw?wIz9e1?Lm46KGVyLH!QP!NY?TfFN+c& z!KWfPEHWqrl@yXNfjuFP29_DV#ltb_wvfPUj!VvSB7+b-mN$bQAHbf-XgFPexba|8 z`HCch-CGjYBpZH9Iq(^PO#V-Hf9u5`=pM_5+!Mbszi6fX|m(Dlmnj$IsLTZ zUUi@4L+*i}D(jIyYDi!(suL7jq6iKN!7$4VQps4#43MH6D{|Fw(RnsO@TgC$37RbY z#DXN1uh8vCT#t=8jVXjo4t%B;C%eDhSNV|a`NhkktX2}JM#11Pso2qCouF<_YT@@C zEC_`{n8iq$1}PMB!F4uAWbh<1D0+-F5q_$GPjc}9!;a_-l{GO%klBIH^zwQ4xBDs| zau57eSr5@*T>_O3&l6N5A$m+A4M`O9J{tV0lECZe@MY)u6oRupTwLKN7KDR&wC?aQ zKME$K&=i2F17GGAmYF{AUiqoQMluE}36xRLkU`y+R4abpU`qxK)?QK)C^{^5WL^f3 zdym#15%*THARP1MD;5&mW*Pu&7QmYspH|ey?n#CK;(~5GG&2MMXFY#mVVN}A6gEPG zbqN$Zyoey_Vx&-rqzrvSyK8xYG4KXDd{x;IgCK=M;{3#dhy>0%+$7r(q>%1s4gfX_ z;5FgbY_XR>aLEATuuaS8V3<8WDeFNha?xPMK%=w~iBgkfFj@saDHQSv8zgU1DC9-G zPzXAFg;2UV*BLbqp2|HDQSzr-5Mv%4*WzS5BK$H30GkHzW(G+3ux1J1L9;{vG46#@ zKV``P;B24Y^s*?5AEn78CGTpI%PI*JOKfOyh2r;ZEQl%;!YoFYz^Cmp3SJ-(hcXvf zl2bZAV+2f*z+D|l1mnG5Isn)-fHyNg6+SGLDV7KzR_=vDaESomd*Nr4^)MumnRJ4n zl*toYO!_6gP{;?2fO@GP3WZp&D;>Us9f=c+^J5i8Ka4VPN(Oh%7lTXm-a=$Tx&U3% z0Nz9kX@7`^4-3Jx0mO0lLLrzo0J!{R$A^}$v$30;0Lw^HHJ|?KM->-r*`&4lVYZ19 zeD7JZNq?1^04rU9u4w>oT5O3w#>0m-j}?Mx0*K@8g))Yu4FGPO^9o2tW9H5)L{+Bj@cR>pa zeocfAYdLlZE(#z{xEE^V+)on#+`hZ?c-}?Vb^8m}S4bWr39$53N|KN&DFIe@V{lF0 zPS;WUIqOrFXR-L{(^_Sn=qY&7d(-)n?G5X@mQ3ldzPF&e8nH2{0l=mKyo*{$`9o6p zu!OcP3Ls8$FVqN{Srh==wYBm<;Dq;l&Qp$OY|j&LO9~d|zIp;IN!KAsPrj~wI#5;J z6By6k?mFN&Zk@ASwxmyM^-;F+S3c*z>VD4gqU|;7n@aZQQ*9-uzxr-UXZ2rF19TU! z|D~Q!(?If{Qo@I&{>*{^V&R)`FVsjhu^<4rcXRFTlEe8YbI;{mc0O&t3BwmjNJaV! zMbSur_3>~h(h?lZpUBEh%i^?FZ)eM|@^s)y@6$QYIqul*T3@%m1;hFD zEy=Q<+*#x2wn4Tjcb&hQlrscf*Il2Qq2jFvVj`eu04)}_M^5ZV0h7zn%3%W*8H9*I`6yU zx#7A6LDFAY->|$5LrKkMB&m@!LZ2?t?fC%R^y`1~=a*=}`16ABVeE+A4Z%bJ@dEck zu_Gw~zys?Ww^r^hIaqKMf@gCsx~@2`Id0fUfMva7eHn(L&rlpGYOASf3HAj>eUqLk z*DmKi#}WH+>l4;wJ^k3#s{$900s(UGr4U&d}bX!}-VZPUW6) zUvOPP3ZJ$=6K6DipK13f-upS7KgGt|>s-N=?-^~ut+ z1sC(Kc%II=>4c#DCG_|;E9tM|42>i|sohQfO%KqO^_PqRyvdg={yBO5G#);TCANEz zK@~tO=NGsaN)@w20Px^s>!!Nx6?;nd2WI@o;{-1`uOfxlZ92o-l8%M6Oyv#Xs@7m{ z;gElgS0^~_*iRB|(pp(hqstd8mq?6~WuP_bo6Z!T_h0fp<-YDR2uj+VFQLiv3_n=h z;bcwi6Ek9-8Rb*f3SSvusbvzQV)#M|~$Ccv=X$F2|+thK&SRYxDa`TdKnK zm2JVEqE-1LzA;5`n~Sto4$@j-PmT-2toa=;BY8=|1>Ys_Rdo0n2tJP%3qh6s3MnLo z7!1u`4`lsM$}gvc4{IKaQxROky-@0JsQ}=i@y-p6o9nh!><&&B?GGIBAIsAT&S6W0 zpp1oRGHIDg+A8Y9)y-ucC4GS*|7hNrcRez=%|%)(^jMd|6WQ`p>6^|5&ikK40!4>O zf0ZDp(qAEkl0xhue+`d!Z)hgazG@c|xVI1;7J|7VgJ*LtxGpI@W-KHvQ*lRmV{L6i zMO(1DcpxzBC#{ttxYf1Oxd%Nat(ATDUig)sDLhB&4Us_6VbWhIf}|c|Mx)YSSZ4*?H1oS;4uUKuAWwL92S8L6voEN=~VVMR!5g&moI;3f!)E!pGPe=q!k zr&aoz!=d$OyQlYNsxNg$MIz{33%o!81AM zDDQiD!be)B($N@SAAg&_s%i>g^S}1d0yN`i_x)C{EBl=ei(itw_sME{W-?nn=skg%uGU9?e_J zY*3d$2;LJv5&)9G?!4cSK+$0mQ8Jf#-thuj3_;Tle8F#sj`dA+Y)}cD(BQH?G6s@< zNMW7 zIj^6caO>wL$#G{K>qwyVaKBcUP~_LYCT}ffV2tUUen?OBKCJV|kv&)Kf^FE}7_6!e zMM|5B+6p@hx(j;p`}_m`RsKQWkZ(9|I4|mrdBd^#I*d?JZ^1zRpnu30^~Lf=@+z9+*{Zm7$_LbAIcvl z*C#AFMtq}rqsxjd`dzRet&df*cPr)NDSW;s-~tYTv1=%7-|Z(l(v?%m2{MJ7L!aYTHn#w&|KSA*-=Ib`jXz_zM}rZ z0S44T4oGrD%@ctAE+A$2jM3&;U9=Ma0V!5X(QX(PNa1^-C>$vuuLx#GZ@sK1-O!HO zx4R~CqyD}?XK`z=sjR*-TwPmRT~`^YXb_H}mJlh}#v=Xo?TwAib*HD+7_#i)dqzqqT zv?JDt|F@J}En_4sjTS40g(8PYVuE7U{8XWN%W>;o#}?P5XEblf-yi5H>JWyZ#`1>B zNL5`;U2P316jcHF`mTmnMX;{5roE~|7?yPfyLE~seZ?8R`a^MQv@6yUtH*n}YB)|1 z?`9a{<5##^*Ji~qYksQGyf$ZL3~v{vqczwXLYqm8N&)%ss1%T|@0cejQaI1BD=1~+J4xQU zjWkXB;*|7$=zZ1p+DjTqj=O~vs6zRG;yC{fGk#OJ1#M7m?@ix62?D7yO{VD`iEm)s zDY>7cy(8_hrf5A;9My4xVoOS+B86g6@bnWoya#@&&^!`&)V9yD!?ihQooCEDnm6oU zRUn!iX9&M|0eP&5Dj<&xHjpBrPDx>cq4ZyBBMjg`wh4;6=!M=>A$3s0lVqk<3j1i{tbs9(lHG+7vul2;Xw532(5jDo6wyg?8t z6ir5#_5NGP1lvk_-8C@U3`L=|NA-d|rt5Sp-=~Gih8ui~x9@gr(+TFvNEr1GB8C0%TU0>a zkill1U@cNuliYuCOlgMDAW|?!;?9jw#KY-rihb|B3$AkzBdq|cX z(?(Z~_QWV3j|PhbQoI`-u3AK}G;4l~3eBU#6q;u~y_w}5A-Ebnj>2zI0eN&dl^}Y& zu>W2ppoY=B13i*H;=Lgnd+7}JJ##ChkVpq+c zI-_MVnotkJZsFGpKhfaEAyqzJbU1~e%&N2Jmr`h6Ng##h83lJbx4Je#Q06mhyd#QV zynsBj7+nH;gdl}~4H-m_7xzCGU8`iQYM}~9zlO)`$M`org9L5`7d0jvIUZ{OIjw_o z`{K(N{AF?c@1pjC$#t8UjN^xDIpw3PPyiWcM!5q8vuPpuLNqBl@QW9a$A-j6Z!8ih zI^3C%!6oaT=P(eJQ00U59!UZFB}$f^=W#$DC)!Z&e7ncx$fbW~FYyoBhxy)sJxz}P zO=>?wfTR}%Ex0v8YMTbZbgtleJ-Q4_#XHzC^WAm@R*fC^XRZxVntB#TcUtG zPsE*AVx`4P_ur-YWY!`6Yf{$!HX3%4DfK!=!Dhhb+dM9($FlVG`WeH&YH|E;Qu|W& z{bknW=6Lwt&L}v*;9HFm@oKi7{VfNZ4mBQbJlt@^IHc@H{fu!$W;kYhV)dIM=4Cx7 z5$KyOnhnmD&sK6&%~sFWsH1jPKk8=n!!&X1tp1Kn{dY;~E8YL^9o4^cs()jc=0=7l zzb)74%yhk+oA<9;9RC|{Uo6y;2?7VVb?@q!Zrj_kzxhD(fu{7uKZukZtZVIVjMk4u zGMDw(r@d#n;kxLZ^PdhpQFN;0Wa)|EiO}(~AdQG(tF-Fmw&e4OyTLGCyGy%oGLw8 zdNO!2bTV|JjN`%Z1CH+hXY_9lf!c)*4`W9nOfgK?|4rk6rTq*E0;3xSrh0`S5v1*V z5)36OZJ(0KgSt$vF6ymnZ)k3B>}eQ);b>%SBwbmL?Zkn-XL-r?oa5=7E1pZ{BjY2dq)DK9m zDHtg0tZi#-Zfopn=&N5)~oTuDZaxZzG%)8*b;5#Q2l{B7x zVEjzh=gj(V7T;@j{mA6dhE<#TMTdppeDMl3A|@no@0fq6xVNmcx;@e&1UnkKk-~vE z!%-NDJ|m0dFQL$i_NL{oB6!nz9Vxu(Q4G;$T@s=A!1$T0&zbe#EWX$5`jN@u^@Eb_ zXNw{j9|_};;7KDQWhn~#gWVM!wQUg|Pa%jNtFj*TLrM-Q>miaz{tZ=VMWfJ)^)@ni z(|JQlq0Uf7M3Kbv4~(D5`kYz+&Ek8_t{<6*t{d7oKh@?0UsdG#DLbCZiwoBcZ`@4`nF|28;VbNMKESSSQ%j&Pdpe6zZBRk1jDS zQD{Yb!}2QW-Qxt66zZC+7(Ot53krBFT%R-Rzgc{*+4UpiG0F8P{N{BybtF^_CrIi% zRMcPEQ{GjD4u@MCL|uRbt-M*Qj$>vLxPH;eBzyPhO}!cVs$$)n(ckx-ST2&^jZ3nGD) z=&&TTPY_gk+#_Y8;FnTp1s#6bAgD&dxD-A>exLC4w&ePpS^v%Ad(Ezg-{i0m6dR&O z!MGjPrO=pF@0#?kk)-0H!SS*lZDL0nnnjB#N&!L9<6ihB6%Uoiui5p&j~UFo1~1fMWlK6m{@^!Ms5CfU)&m`0L=b){ zg;tP2DVC%Pttf&rpSkIL=7I9dus&zje_8*_6yIxhJ^XYFJg>pV(}-b5pioLT?%_0y%}d(Ey-;%C^9#X1Z@;it+{ z6j7EUsK!9CB9!&e39@S|<6xWOmr`g&%3LTN)(O6VEk*_(D8CHrb7uWF)}K?#`zFQr znq3b+-2#gaZ;IQH#X7uAm8Hm6WhqMZ!ZM73l=X<~uso3{f{NdQLMzN;UMDtRMnTGi z*c8DB$}hwEoLT?p*Po^Qa-zI%BEHw`dc{w-z{V^vZif?3BTA1IKYx4#OqM{=VHpKw zE(1Yri9#!J9ZnE@VEkNWJr^^g2nKkjYIcwJ;O_xJjh#TtDkK0jjP-E@;F zQv;9sxr(^cza z54pYW`Sq`wXz)i^k@a|V{T{xAUpY1;fHOzVy%YR5?Vq&&!Ir~II2Mc`%D;Cn;a7pD zK`}0kUV=qz_eS8i+OM_0W7Fo#xF7AoF(`Czmp}UN7Jijza0wFVLx-LBM)0@VZ?vCj zKi0m11fIrqrVCO2y?RyS#Z!p}2XSUAK!@G;M)2RYf6@Lw?XTIikwB8e$`}8k54wAh zH`w48LIMl1B3?#8NA?rqf7kw1`=$0j+4lMaT;R@2L=1k@4}I9@gCi*XWCScj@k9cB zxG>)X!GG8OP5Vbiz`q~`_B(Mm^dVfg%sp2hTo=C3mge~du_1+6kvv907nV4ybp_$~ zf3^Ru{T16@UqJ$&#GiZv4wyLdf1%y_oqf2@Pewq|U|j;eY81?h;D2bp)&7||^nXW# zzd@kFO{~ZsB8wzY!PI&1ch<>ee2|N7gwW3_4q>(M7*!!@J$Q{r7M+v#n!Kj$1#U|L zx?j#`RH{!&`JAJcu+8R1u7iva@($xY#=A_~e+W)nAy^v!_Chh0WUC4UX=Tu_UDUTM z_}$fpEPj{O%j~C=`y>=oD1C~9VYOzrFeVf*J_yD6_Xx{J#I>$h#h;siKp)o%F3Z>F$>^?lmEMl4mFVc@uvBr2T|vw;Ak3ovqaA zhoQq`%~hXNz^GUJYwhnTNOYTbOt8jP4YFK$$hIhoUhdhW^m3p-#+@%g&yWwSMlJvA z>woFaoAmED+W*02`+00`mlm;>JBpmX9Jkx)L7Q{=EdRf1zu@WaE6faMh&LWkH>(i5 zPFXLs-rt&;$yfS3PurOr%Uu`Q5bY^JB$C(v7N0lyHuCiGV;qhz5x&`_HCU_crOtpW zPcgK6EV(i}{GSkhTZOPru~IF85#@ZmEce{wVui_f`JJ4$GdC6~^v4;>(?|09-@@}I zUrN@menQE=yV?_Ehv?E8tu?kXq%hy*g`vwM>ob3^{W(_TRqZk|xRnsaK0;?|5&gnP zOnu`hY$%UgNnt(=gQZzvS~K{nOX4{wq(vuOnXvl$B^^ z&Rk^=If|VHE`5FBZ*U9v7SG4Gu+=k!$*x0>dk7*7o3#GWp0+V^=b*<@Q77H{-~4%# zE{J^nf~U9d6ZUhP0Ly8f-8!{qOT=1jFL#tW<@xxZ(BQvB0>4U_>`g-8Bt&cjq0|FN z;UoJJ+vh@$dL*wEj?^wlWcOCipF0|I^Q#bYWh;B=GEIbal}Sn2N(tSIH?u91#oh8fQkp}>#yO9+YlEw%n9=S^Po@+IppZ!^N3X9a7D=Z#LS z6@qg9H_UND>(zfY4Q*~;xB6wzjW(=rfYz@eEkD+yg%nT<6~wi z*IB{Zk9NvTuhsG&nv9VD$SnIOtg(I%#_#i<=O`4T6MV$^NnJ(Vf-GbG?>AVGzr#ZO z06On7OSwWalxZwP6oSI$yBv6uLNU-}&X56Qd!N;%11RfK3OyDtr#{?b| zcue3ifyV?M6L?JEF@eVf9us&>;4y*61RfK3OyG+mu!_i*AF+D=E4&>4g_X>IV?F)1 ztZC}Ue`r(oyLkBgoE6UBEI9igtbP86>DT@ayBS|4Wr(zquKlByPgyzr6?-81XB(fZ zw{G6P=@pclX({l#df3(eGCnFlhT=~-`wPYSpA~7dum4r(wZiauq%d9kHg`3v^l!2^@?-W$ z{vKZhxl4N6au*+smzCX|Q|wH9#17b>(v!cYFMo&E_s=!0)ang6y6NNdNYQuD_5X?_ z{VhHH8GZeQY4`p!R~^KYW$jY!7uqd7w0;{Otk2kE`fGMFrEUEV{*!6@56c@Y?B(o6 zlV3v8zCpCh57`a-D@ft{63BH|*}K?}y@VIgN9=NcpWX1kZeA8PncmfCUyt~y&6 zzEMxGFZMdSkl#X9eu%XErD^ZP^Zu(U{@0qm{W7hDNye z|GS`lER^QR z_YrxLw?C)YS?;K@HCS7b!Kmr?&m8}&+7}DV_@G&H_>gM6#s_#_ zk(#hGV!s~sm(EY}Z}=^_E+nu39j>s~#tE8rPxn8~5dSOfOMHn>bJ+MNh|hfowrd>U zQz7`Mzm~f`w*umOmvsx5yFRx<;(PC#;d0mKR!n^F{SsW}`rHbN@4Y{Q%UqvZQSrU^ zLvWevb1N*q_udIEbA4{b#rNJT!DX(`t-$!+dn35a^|=)p-+M0vW%Xbg>vJnKzBhY< z%UqwEkH1`D@x9p+T;}@ReEj8#i|@^j;4;_e^!Upa8sD1@!T9?9{jSdmzZD$cYfe!3 zEn|Id#mD!W5nSf_-0yvS?=lN`EVF!w+1-8k&o8rp$1=-@Jp6pjzTq+pcr3Ggh}pM$ z_|Gr1fX6b+hdlgz%)a3=3wSKEe2Ce%d-%^Uvw(;G_;TOnLmqxUX5TQ`fzRx{Y0k^^ zaT(=9(zN)9{VCOf@1DQBF@3zx@*(&9c@NzMiyipx=_?Aq)bxS(borzI=5!8x_s}ch zw=jL+JzW0izcsxBU)KE+e)0f)pXEcc{+x&QjY~Q3W%-4dN*|cz<&XZ`mUiIFs&59r z#pwgHy8O|9Qzi$#EP9tIeP9-sKl*RU?7(N*zj=P?(g&Je{^-BK)PXN^7j=I3T|Ojp z;}64GvjAYT0A7^N!kEn+zT}xPF8LJuxS8qW`1h@+2#t^Bp4@605R@`QkiAt1^_3o|D_G!&CE}Q z4{L6Zv(y@a;GzIx9$To6F4+zU08XMbe| z054wuTM)o&#!rP0iv$+gtE`O>ObH;4yBDt90N`}%e<=aH=KKgB);x|JpR?3fV`)ND zlLCn2?u8oFMpkA3aQgMXqyXN`{0#S_Tr1Uj0**3Ut)*E7O2-3;nhYnZR!ir!#fH>h^s1ZT95(9vjUjK^+@S5=x4JCY7%e5ha`OZ>C z!HA^=8B_tp3HL&o#jL~t;7sd(Du6c|eky!eLjt|7LPyA6ZLL=X2_QD~o63VLGXOaA z`k!eDT7CaWg%4{UyW5@PcNRO!Z8g>gODi%MO>!^PsJOK90)Usj{%7pJp}`i93kfVh zhbwHgIzizZ5C4_=p(`%{IJ0^_KKq!!V*-x}JSOm%z+(cB2|On7n80HKj|n^`@R-13 z0*?tiCh(ZRV*(E`0T(+HA+`>yIKn{N$8LL-E8zBc?74{^mck=s`u%&5y)w2Y^OkCVQG!4R&MF$?n6odH*B|&DiE?b?m)a8evBiAbLEMT+Dynk(IZUsDKdpB+S zX%hrmG@jASdl02e(bU~+G-qS&LUTSju(ce=}CNXufPO> z(!(4a^2BDU^i@Bj=++-n_~$Ffw&nY zwZ9@01lkz6bI@bNOXTAh6oL9a1z~Pem}466M+jOY_G+w2X=ZwQ`uxT5zbWm@2=Vun zI$BW)0>$>Z&|~9n+1>uojm3Uhv9LCUegMT`b{wCGea zm}p=3ulX5wXoV#RwBi!#6n=($e1-(Rj1JFId}dS;Y_Qd4%WrY~Z@his_iM`K{S8+B zdz6iOhtfUQD4#OJjA@E80*~-ZdPs@T-%?Id52zd8S_bzKp*334zg|Kx1D3Zt&sTM zjEFLxT;}@Riiz*d2r0wKWvvJnIzBetXCH}a~^|=)q-@62?G=H97f4Q&qIeGS_yzk13?@fa$ z?Vsk?U+!ytPWY+vzAG=jH!ZG3f7I7s?t6W1MaTCpf|ceE`ufW+*+cqotN{KID_Cz( xTKj5zb!khQD^mWn!sB~W`kMBaWft&Q@$tQBp(Xusnd@`L{x9WyS7dze{|`Wm#zg=C literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin b/src/Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin new file mode 100644 index 0000000000000000000000000000000000000000..db5bf73f7d5a0b5e436d336849c90bfbc24d76dc GIT binary patch literal 1024 zcmezOkD ShaderType.ComputeShader, + ShaderStage.Vertex => ShaderType.VertexShader, + ShaderStage.TessellationControl => ShaderType.TessControlShader, + ShaderStage.TessellationEvaluation => ShaderType.TessEvaluationShader, + ShaderStage.Geometry => ShaderType.GeometryShader, + ShaderStage.Fragment => ShaderType.FragmentShader, + _ => ShaderType.VertexShader, + }; + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/FormatInfo.cs b/src/Ryujinx.Graphics.OpenGL/FormatInfo.cs new file mode 100644 index 00000000..c1a97e81 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/FormatInfo.cs @@ -0,0 +1,45 @@ +using OpenTK.Graphics.OpenGL; + +namespace Ryujinx.Graphics.OpenGL +{ + readonly struct FormatInfo + { + public int Components { get; } + public bool Normalized { get; } + public bool Scaled { get; } + + public PixelInternalFormat PixelInternalFormat { get; } + public PixelFormat PixelFormat { get; } + public PixelType PixelType { get; } + + public bool IsCompressed { get; } + + public FormatInfo( + int components, + bool normalized, + bool scaled, + All pixelInternalFormat, + PixelFormat pixelFormat, + PixelType pixelType) + { + Components = components; + Normalized = normalized; + Scaled = scaled; + PixelInternalFormat = (PixelInternalFormat)pixelInternalFormat; + PixelFormat = pixelFormat; + PixelType = pixelType; + IsCompressed = false; + } + + public FormatInfo(int components, bool normalized, bool scaled, All pixelFormat) + { + Components = components; + Normalized = normalized; + Scaled = scaled; + PixelInternalFormat = 0; + PixelFormat = (PixelFormat)pixelFormat; + PixelType = 0; + IsCompressed = true; + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/FormatTable.cs b/src/Ryujinx.Graphics.OpenGL/FormatTable.cs new file mode 100644 index 00000000..4cf4dc76 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/FormatTable.cs @@ -0,0 +1,241 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + readonly struct FormatTable + { + private static readonly FormatInfo[] _table; + private static readonly SizedInternalFormat[] _tableImage; + + static FormatTable() + { + int tableSize = Enum.GetNames().Length; + + _table = new FormatInfo[tableSize]; + _tableImage = new SizedInternalFormat[tableSize]; + +#pragma warning disable IDE0055 // Disable formatting + Add(Format.R8Unorm, new FormatInfo(1, true, false, All.R8, PixelFormat.Red, PixelType.UnsignedByte)); + Add(Format.R8Snorm, new FormatInfo(1, true, false, All.R8Snorm, PixelFormat.Red, PixelType.Byte)); + Add(Format.R8Uint, new FormatInfo(1, false, false, All.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte)); + Add(Format.R8Sint, new FormatInfo(1, false, false, All.R8i, PixelFormat.RedInteger, PixelType.Byte)); + Add(Format.R16Float, new FormatInfo(1, false, false, All.R16f, PixelFormat.Red, PixelType.HalfFloat)); + Add(Format.R16Unorm, new FormatInfo(1, true, false, All.R16, PixelFormat.Red, PixelType.UnsignedShort)); + Add(Format.R16Snorm, new FormatInfo(1, true, false, All.R16Snorm, PixelFormat.Red, PixelType.Short)); + Add(Format.R16Uint, new FormatInfo(1, false, false, All.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort)); + Add(Format.R16Sint, new FormatInfo(1, false, false, All.R16i, PixelFormat.RedInteger, PixelType.Short)); + Add(Format.R32Float, new FormatInfo(1, false, false, All.R32f, PixelFormat.Red, PixelType.Float)); + Add(Format.R32Uint, new FormatInfo(1, false, false, All.R32ui, PixelFormat.RedInteger, PixelType.UnsignedInt)); + Add(Format.R32Sint, new FormatInfo(1, false, false, All.R32i, PixelFormat.RedInteger, PixelType.Int)); + Add(Format.R8G8Unorm, new FormatInfo(2, true, false, All.Rg8, PixelFormat.Rg, PixelType.UnsignedByte)); + Add(Format.R8G8Snorm, new FormatInfo(2, true, false, All.Rg8Snorm, PixelFormat.Rg, PixelType.Byte)); + Add(Format.R8G8Uint, new FormatInfo(2, false, false, All.Rg8ui, PixelFormat.RgInteger, PixelType.UnsignedByte)); + Add(Format.R8G8Sint, new FormatInfo(2, false, false, All.Rg8i, PixelFormat.RgInteger, PixelType.Byte)); + Add(Format.R16G16Float, new FormatInfo(2, false, false, All.Rg16f, PixelFormat.Rg, PixelType.HalfFloat)); + Add(Format.R16G16Unorm, new FormatInfo(2, true, false, All.Rg16, PixelFormat.Rg, PixelType.UnsignedShort)); + Add(Format.R16G16Snorm, new FormatInfo(2, true, false, All.Rg16Snorm, PixelFormat.Rg, PixelType.Short)); + Add(Format.R16G16Uint, new FormatInfo(2, false, false, All.Rg16ui, PixelFormat.RgInteger, PixelType.UnsignedShort)); + Add(Format.R16G16Sint, new FormatInfo(2, false, false, All.Rg16i, PixelFormat.RgInteger, PixelType.Short)); + Add(Format.R32G32Float, new FormatInfo(2, false, false, All.Rg32f, PixelFormat.Rg, PixelType.Float)); + Add(Format.R32G32Uint, new FormatInfo(2, false, false, All.Rg32ui, PixelFormat.RgInteger, PixelType.UnsignedInt)); + Add(Format.R32G32Sint, new FormatInfo(2, false, false, All.Rg32i, PixelFormat.RgInteger, PixelType.Int)); + Add(Format.R8G8B8Unorm, new FormatInfo(3, true, false, All.Rgb8, PixelFormat.Rgb, PixelType.UnsignedByte)); + Add(Format.R8G8B8Snorm, new FormatInfo(3, true, false, All.Rgb8Snorm, PixelFormat.Rgb, PixelType.Byte)); + Add(Format.R8G8B8Uint, new FormatInfo(3, false, false, All.Rgb8ui, PixelFormat.RgbInteger, PixelType.UnsignedByte)); + Add(Format.R8G8B8Sint, new FormatInfo(3, false, false, All.Rgb8i, PixelFormat.RgbInteger, PixelType.Byte)); + Add(Format.R16G16B16Float, new FormatInfo(3, false, false, All.Rgb16f, PixelFormat.Rgb, PixelType.HalfFloat)); + Add(Format.R16G16B16Unorm, new FormatInfo(3, true, false, All.Rgb16, PixelFormat.Rgb, PixelType.UnsignedShort)); + Add(Format.R16G16B16Snorm, new FormatInfo(3, true, false, All.Rgb16Snorm, PixelFormat.Rgb, PixelType.Short)); + Add(Format.R16G16B16Uint, new FormatInfo(3, false, false, All.Rgb16ui, PixelFormat.RgbInteger, PixelType.UnsignedShort)); + Add(Format.R16G16B16Sint, new FormatInfo(3, false, false, All.Rgb16i, PixelFormat.RgbInteger, PixelType.Short)); + Add(Format.R32G32B32Float, new FormatInfo(3, false, false, All.Rgb32f, PixelFormat.Rgb, PixelType.Float)); + Add(Format.R32G32B32Uint, new FormatInfo(3, false, false, All.Rgb32ui, PixelFormat.RgbInteger, PixelType.UnsignedInt)); + Add(Format.R32G32B32Sint, new FormatInfo(3, false, false, All.Rgb32i, PixelFormat.RgbInteger, PixelType.Int)); + Add(Format.R8G8B8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte)); + Add(Format.R8G8B8A8Snorm, new FormatInfo(4, true, false, All.Rgba8Snorm, PixelFormat.Rgba, PixelType.Byte)); + Add(Format.R8G8B8A8Uint, new FormatInfo(4, false, false, All.Rgba8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte)); + Add(Format.R8G8B8A8Sint, new FormatInfo(4, false, false, All.Rgba8i, PixelFormat.RgbaInteger, PixelType.Byte)); + Add(Format.R16G16B16A16Float, new FormatInfo(4, false, false, All.Rgba16f, PixelFormat.Rgba, PixelType.HalfFloat)); + Add(Format.R16G16B16A16Unorm, new FormatInfo(4, true, false, All.Rgba16, PixelFormat.Rgba, PixelType.UnsignedShort)); + Add(Format.R16G16B16A16Snorm, new FormatInfo(4, true, false, All.Rgba16Snorm, PixelFormat.Rgba, PixelType.Short)); + Add(Format.R16G16B16A16Uint, new FormatInfo(4, false, false, All.Rgba16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort)); + Add(Format.R16G16B16A16Sint, new FormatInfo(4, false, false, All.Rgba16i, PixelFormat.RgbaInteger, PixelType.Short)); + Add(Format.R32G32B32A32Float, new FormatInfo(4, false, false, All.Rgba32f, PixelFormat.Rgba, PixelType.Float)); + Add(Format.R32G32B32A32Uint, new FormatInfo(4, false, false, All.Rgba32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt)); + Add(Format.R32G32B32A32Sint, new FormatInfo(4, false, false, All.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int)); + Add(Format.S8Uint, new FormatInfo(1, false, false, All.StencilIndex8, PixelFormat.StencilIndex, PixelType.UnsignedByte)); + Add(Format.D16Unorm, new FormatInfo(1, false, false, All.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort)); + Add(Format.S8UintD24Unorm, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248)); + Add(Format.X8UintD24Unorm, new FormatInfo(1, false, false, All.DepthComponent24, PixelFormat.DepthComponent, PixelType.UnsignedInt)); + Add(Format.D32Float, new FormatInfo(1, false, false, All.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float)); + Add(Format.D24UnormS8Uint, new FormatInfo(1, false, false, All.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248)); + Add(Format.D32FloatS8Uint, new FormatInfo(1, false, false, All.Depth32fStencil8, PixelFormat.DepthStencil, PixelType.Float32UnsignedInt248Rev)); + Add(Format.R8G8B8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte)); + Add(Format.R4G4B4A4Unorm, new FormatInfo(4, true, false, All.Rgba4, PixelFormat.Rgba, PixelType.UnsignedShort4444Reversed)); + Add(Format.R5G5B5X1Unorm, new FormatInfo(4, true, false, All.Rgb5, PixelFormat.Rgb, PixelType.UnsignedShort1555Reversed)); + Add(Format.R5G5B5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed)); + Add(Format.R5G6B5Unorm, new FormatInfo(3, true, false, All.Rgb565, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed)); + Add(Format.R10G10B10A2Unorm, new FormatInfo(4, true, false, All.Rgb10A2, PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed)); + Add(Format.R10G10B10A2Uint, new FormatInfo(4, false, false, All.Rgb10A2ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed)); + Add(Format.R11G11B10Float, new FormatInfo(3, false, false, All.R11fG11fB10f, PixelFormat.Rgb, PixelType.UnsignedInt10F11F11FRev)); + Add(Format.R9G9B9E5Float, new FormatInfo(3, false, false, All.Rgb9E5, PixelFormat.Rgb, PixelType.UnsignedInt5999Rev)); + Add(Format.Bc1RgbaUnorm, new FormatInfo(4, true, false, All.CompressedRgbaS3tcDxt1Ext)); + Add(Format.Bc2Unorm, new FormatInfo(4, true, false, All.CompressedRgbaS3tcDxt3Ext)); + Add(Format.Bc3Unorm, new FormatInfo(4, true, false, All.CompressedRgbaS3tcDxt5Ext)); + Add(Format.Bc1RgbaSrgb, new FormatInfo(4, true, false, All.CompressedSrgbAlphaS3tcDxt1Ext)); + Add(Format.Bc2Srgb, new FormatInfo(4, false, false, All.CompressedSrgbAlphaS3tcDxt3Ext)); + Add(Format.Bc3Srgb, new FormatInfo(4, false, false, All.CompressedSrgbAlphaS3tcDxt5Ext)); + Add(Format.Bc4Unorm, new FormatInfo(1, true, false, All.CompressedRedRgtc1)); + Add(Format.Bc4Snorm, new FormatInfo(1, true, false, All.CompressedSignedRedRgtc1)); + Add(Format.Bc5Unorm, new FormatInfo(2, true, false, All.CompressedRgRgtc2)); + Add(Format.Bc5Snorm, new FormatInfo(2, true, false, All.CompressedSignedRgRgtc2)); + Add(Format.Bc7Unorm, new FormatInfo(4, true, false, All.CompressedRgbaBptcUnorm)); + Add(Format.Bc7Srgb, new FormatInfo(4, false, false, All.CompressedSrgbAlphaBptcUnorm)); + Add(Format.Bc6HSfloat, new FormatInfo(4, false, false, All.CompressedRgbBptcSignedFloat)); + Add(Format.Bc6HUfloat, new FormatInfo(4, false, false, All.CompressedRgbBptcUnsignedFloat)); + Add(Format.Etc2RgbUnorm, new FormatInfo(4, false, false, All.CompressedRgb8Etc2)); + Add(Format.Etc2RgbaUnorm, new FormatInfo(4, false, false, All.CompressedRgba8Etc2Eac)); + Add(Format.Etc2RgbPtaUnorm, new FormatInfo(4, false, false, All.CompressedRgb8PunchthroughAlpha1Etc2)); + Add(Format.Etc2RgbSrgb, new FormatInfo(4, false, false, All.CompressedSrgb8Etc2)); + Add(Format.Etc2RgbaSrgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Etc2Eac)); + Add(Format.Etc2RgbPtaSrgb, new FormatInfo(4, false, false, All.CompressedSrgb8PunchthroughAlpha1Etc2)); + Add(Format.R8Uscaled, new FormatInfo(1, false, true, All.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte)); + Add(Format.R8Sscaled, new FormatInfo(1, false, true, All.R8i, PixelFormat.RedInteger, PixelType.Byte)); + Add(Format.R16Uscaled, new FormatInfo(1, false, true, All.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort)); + Add(Format.R16Sscaled, new FormatInfo(1, false, true, All.R16i, PixelFormat.RedInteger, PixelType.Short)); + Add(Format.R32Uscaled, new FormatInfo(1, false, true, All.R32ui, PixelFormat.RedInteger, PixelType.UnsignedInt)); + Add(Format.R32Sscaled, new FormatInfo(1, false, true, All.R32i, PixelFormat.RedInteger, PixelType.Int)); + Add(Format.R8G8Uscaled, new FormatInfo(2, false, true, All.Rg8ui, PixelFormat.RgInteger, PixelType.UnsignedByte)); + Add(Format.R8G8Sscaled, new FormatInfo(2, false, true, All.Rg8i, PixelFormat.RgInteger, PixelType.Byte)); + Add(Format.R16G16Uscaled, new FormatInfo(2, false, true, All.Rg16ui, PixelFormat.RgInteger, PixelType.UnsignedShort)); + Add(Format.R16G16Sscaled, new FormatInfo(2, false, true, All.Rg16i, PixelFormat.RgInteger, PixelType.Short)); + Add(Format.R32G32Uscaled, new FormatInfo(2, false, true, All.Rg32ui, PixelFormat.RgInteger, PixelType.UnsignedInt)); + Add(Format.R32G32Sscaled, new FormatInfo(2, false, true, All.Rg32i, PixelFormat.RgInteger, PixelType.Int)); + Add(Format.R8G8B8Uscaled, new FormatInfo(3, false, true, All.Rgb8ui, PixelFormat.RgbInteger, PixelType.UnsignedByte)); + Add(Format.R8G8B8Sscaled, new FormatInfo(3, false, true, All.Rgb8i, PixelFormat.RgbInteger, PixelType.Byte)); + Add(Format.R16G16B16Uscaled, new FormatInfo(3, false, true, All.Rgb16ui, PixelFormat.RgbInteger, PixelType.UnsignedShort)); + Add(Format.R16G16B16Sscaled, new FormatInfo(3, false, true, All.Rgb16i, PixelFormat.RgbInteger, PixelType.Short)); + Add(Format.R32G32B32Uscaled, new FormatInfo(3, false, true, All.Rgb32ui, PixelFormat.RgbInteger, PixelType.UnsignedInt)); + Add(Format.R32G32B32Sscaled, new FormatInfo(3, false, true, All.Rgb32i, PixelFormat.RgbInteger, PixelType.Int)); + Add(Format.R8G8B8A8Uscaled, new FormatInfo(4, false, true, All.Rgba8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte)); + Add(Format.R8G8B8A8Sscaled, new FormatInfo(4, false, true, All.Rgba8i, PixelFormat.RgbaInteger, PixelType.Byte)); + Add(Format.R16G16B16A16Uscaled, new FormatInfo(4, false, true, All.Rgba16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort)); + Add(Format.R16G16B16A16Sscaled, new FormatInfo(4, false, true, All.Rgba16i, PixelFormat.RgbaInteger, PixelType.Short)); + Add(Format.R32G32B32A32Uscaled, new FormatInfo(4, false, true, All.Rgba32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt)); + Add(Format.R32G32B32A32Sscaled, new FormatInfo(4, false, true, All.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int)); + Add(Format.R10G10B10A2Snorm, new FormatInfo(4, true, false, All.Rgb10A2, PixelFormat.Rgba, (PixelType)All.Int2101010Rev)); + Add(Format.R10G10B10A2Sint, new FormatInfo(4, false, false, All.Rgb10A2, PixelFormat.RgbaInteger, (PixelType)All.Int2101010Rev)); + Add(Format.R10G10B10A2Uscaled, new FormatInfo(4, false, true, All.Rgb10A2ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed)); + Add(Format.R10G10B10A2Sscaled, new FormatInfo(4, false, true, All.Rgb10A2, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed)); + Add(Format.Astc4x4Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc4X4Khr)); + Add(Format.Astc5x4Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc5X4Khr)); + Add(Format.Astc5x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc5X5Khr)); + Add(Format.Astc6x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc6X5Khr)); + Add(Format.Astc6x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc6X6Khr)); + Add(Format.Astc8x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X5Khr)); + Add(Format.Astc8x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X6Khr)); + Add(Format.Astc8x8Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc8X8Khr)); + Add(Format.Astc10x5Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X5Khr)); + Add(Format.Astc10x6Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X6Khr)); + Add(Format.Astc10x8Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X8Khr)); + Add(Format.Astc10x10Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc10X10Khr)); + Add(Format.Astc12x10Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc12X10Khr)); + Add(Format.Astc12x12Unorm, new FormatInfo(4, true, false, All.CompressedRgbaAstc12X12Khr)); + Add(Format.Astc4x4Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc4X4Khr)); + Add(Format.Astc5x4Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc5X4Khr)); + Add(Format.Astc5x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc5X5Khr)); + Add(Format.Astc6x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc6X5Khr)); + Add(Format.Astc6x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc6X6Khr)); + Add(Format.Astc8x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X5Khr)); + Add(Format.Astc8x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X6Khr)); + Add(Format.Astc8x8Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc8X8Khr)); + Add(Format.Astc10x5Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X5Khr)); + Add(Format.Astc10x6Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X6Khr)); + Add(Format.Astc10x8Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X8Khr)); + Add(Format.Astc10x10Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc10X10Khr)); + Add(Format.Astc12x10Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc12X10Khr)); + Add(Format.Astc12x12Srgb, new FormatInfo(4, false, false, All.CompressedSrgb8Alpha8Astc12X12Khr)); + Add(Format.B5G6R5Unorm, new FormatInfo(3, true, false, All.Rgb565, PixelFormat.Rgb, PixelType.UnsignedShort565Reversed)); + Add(Format.B5G5R5A1Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort1555Reversed)); + Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort5551)); + Add(Format.B8G8R8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte)); + Add(Format.B8G8R8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte)); + Add(Format.B10G10R10A2Unorm, new FormatInfo(4, false, false, All.Rgb10A2, PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed)); + + Add(Format.R8Unorm, SizedInternalFormat.R8); + Add(Format.R8Uint, SizedInternalFormat.R8ui); + Add(Format.R8Sint, SizedInternalFormat.R8i); + Add(Format.R16Float, SizedInternalFormat.R16f); + Add(Format.R16Unorm, SizedInternalFormat.R16); + Add(Format.R16Snorm, (SizedInternalFormat)All.R16Snorm); + Add(Format.R16Uint, SizedInternalFormat.R16ui); + Add(Format.R16Sint, SizedInternalFormat.R16i); + Add(Format.R32Float, SizedInternalFormat.R32f); + Add(Format.R32Uint, SizedInternalFormat.R32ui); + Add(Format.R32Sint, SizedInternalFormat.R32i); + Add(Format.R8G8Unorm, SizedInternalFormat.Rg8); + Add(Format.R8G8Snorm, (SizedInternalFormat)All.Rg8Snorm); + Add(Format.R8G8Uint, SizedInternalFormat.Rg8ui); + Add(Format.R8G8Sint, SizedInternalFormat.Rg8i); + Add(Format.R16G16Float, SizedInternalFormat.Rg16f); + Add(Format.R16G16Unorm, SizedInternalFormat.Rg16); + Add(Format.R16G16Snorm, (SizedInternalFormat)All.Rg16Snorm); + Add(Format.R16G16Uint, SizedInternalFormat.Rg16ui); + Add(Format.R16G16Sint, SizedInternalFormat.Rg16i); + Add(Format.R32G32Float, SizedInternalFormat.Rg32f); + Add(Format.R32G32Uint, SizedInternalFormat.Rg32ui); + Add(Format.R32G32Sint, SizedInternalFormat.Rg32i); + Add(Format.R8G8B8A8Unorm, SizedInternalFormat.Rgba8); + Add(Format.R8G8B8A8Snorm, (SizedInternalFormat)All.Rgba8Snorm); + Add(Format.R8G8B8A8Uint, SizedInternalFormat.Rgba8ui); + Add(Format.R8G8B8A8Sint, SizedInternalFormat.Rgba8i); + Add(Format.R16G16B16A16Float, SizedInternalFormat.Rgba16f); + Add(Format.R16G16B16A16Unorm, SizedInternalFormat.Rgba16); + Add(Format.R16G16B16A16Snorm, (SizedInternalFormat)All.Rgba16Snorm); + Add(Format.R16G16B16A16Uint, SizedInternalFormat.Rgba16ui); + Add(Format.R16G16B16A16Sint, SizedInternalFormat.Rgba16i); + Add(Format.R32G32B32A32Float, SizedInternalFormat.Rgba32f); + Add(Format.R32G32B32A32Uint, SizedInternalFormat.Rgba32ui); + Add(Format.R32G32B32A32Sint, SizedInternalFormat.Rgba32i); + Add(Format.R8G8B8A8Srgb, SizedInternalFormat.Rgba8); + Add(Format.R10G10B10A2Unorm, (SizedInternalFormat)All.Rgb10A2); + Add(Format.R10G10B10A2Uint, (SizedInternalFormat)All.Rgb10A2ui); + Add(Format.R11G11B10Float, (SizedInternalFormat)All.R11fG11fB10f); +#pragma warning restore IDE0055 + } + + private static void Add(Format format, FormatInfo info) + { + _table[(int)format] = info; + } + + private static void Add(Format format, SizedInternalFormat sif) + { + _tableImage[(int)format] = sif; + } + + public static FormatInfo GetFormatInfo(Format format) + { + return _table[(int)format]; + } + + public static SizedInternalFormat GetImageFormat(Format format) + { + return _tableImage[(int)format]; + } + + public static bool IsPackedDepthStencil(Format format) + { + return format == Format.D24UnormS8Uint || + format == Format.D32FloatS8Uint || + format == Format.S8UintD24Unorm; + } + + public static bool IsDepthOnly(Format format) + { + return format == Format.D16Unorm || format == Format.D32Float || format == Format.X8UintD24Unorm; + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Framebuffer.cs b/src/Ryujinx.Graphics.OpenGL/Framebuffer.cs new file mode 100644 index 00000000..394b8bc7 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Framebuffer.cs @@ -0,0 +1,235 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.OpenGL.Image; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.OpenGL +{ + class Framebuffer : IDisposable + { + public int Handle { get; private set; } + private int _clearFbHandle; + private bool _clearFbInitialized; + + private FramebufferAttachment _lastDsAttachment; + + private readonly TextureView[] _colors; + private TextureView _depthStencil; + + private int _colorsCount; + private bool _dualSourceBlend; + + public Framebuffer() + { + Handle = GL.GenFramebuffer(); + _clearFbHandle = GL.GenFramebuffer(); + + _colors = new TextureView[8]; + } + + public int Bind() + { + GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle); + return Handle; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AttachColor(int index, TextureView color) + { + if (_colors[index] == color) + { + return; + } + + FramebufferAttachment attachment = FramebufferAttachment.ColorAttachment0 + index; + + GL.FramebufferTexture(FramebufferTarget.Framebuffer, attachment, color?.Handle ?? 0, 0); + + _colors[index] = color; + } + + public void AttachDepthStencil(TextureView depthStencil) + { + // Detach the last depth/stencil buffer if there is any. + if (_lastDsAttachment != 0) + { + GL.FramebufferTexture(FramebufferTarget.Framebuffer, _lastDsAttachment, 0, 0); + } + + if (depthStencil != null) + { + FramebufferAttachment attachment = GetAttachment(depthStencil.Format); + + GL.FramebufferTexture( + FramebufferTarget.Framebuffer, + attachment, + depthStencil.Handle, + 0); + + _lastDsAttachment = attachment; + } + else + { + _lastDsAttachment = 0; + } + + _depthStencil = depthStencil; + } + + public void SetDualSourceBlend(bool enable) + { + bool oldEnable = _dualSourceBlend; + + _dualSourceBlend = enable; + + // When dual source blend is used, + // we can only have one draw buffer. + if (enable) + { + GL.DrawBuffer(DrawBufferMode.ColorAttachment0); + } + else if (oldEnable) + { + SetDrawBuffersImpl(_colorsCount); + } + } + + public void SetDrawBuffers(int colorsCount) + { + if (_colorsCount != colorsCount && !_dualSourceBlend) + { + SetDrawBuffersImpl(colorsCount); + } + + _colorsCount = colorsCount; + } + + private static void SetDrawBuffersImpl(int colorsCount) + { + DrawBuffersEnum[] drawBuffers = new DrawBuffersEnum[colorsCount]; + + for (int index = 0; index < colorsCount; index++) + { + drawBuffers[index] = DrawBuffersEnum.ColorAttachment0 + index; + } + + GL.DrawBuffers(colorsCount, drawBuffers); + } + + private static FramebufferAttachment GetAttachment(Format format) + { + if (FormatTable.IsPackedDepthStencil(format)) + { + return FramebufferAttachment.DepthStencilAttachment; + } + else if (FormatTable.IsDepthOnly(format)) + { + return FramebufferAttachment.DepthAttachment; + } + else + { + return FramebufferAttachment.StencilAttachment; + } + } + + public int GetColorLayerCount(int index) + { + return _colors[index]?.Info.GetDepthOrLayers() ?? 0; + } + + public int GetDepthStencilLayerCount() + { + return _depthStencil?.Info.GetDepthOrLayers() ?? 0; + } + + public void AttachColorLayerForClear(int index, int layer) + { + TextureView color = _colors[index]; + + if (!IsLayered(color)) + { + return; + } + + BindClearFb(); + GL.FramebufferTextureLayer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0 + index, color.Handle, 0, layer); + } + + public void DetachColorLayerForClear(int index) + { + TextureView color = _colors[index]; + + if (!IsLayered(color)) + { + return; + } + + GL.FramebufferTexture(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0 + index, 0, 0); + Bind(); + } + + public void AttachDepthStencilLayerForClear(int layer) + { + TextureView depthStencil = _depthStencil; + + if (!IsLayered(depthStencil)) + { + return; + } + + BindClearFb(); + GL.FramebufferTextureLayer(FramebufferTarget.Framebuffer, GetAttachment(depthStencil.Format), depthStencil.Handle, 0, layer); + } + + public void DetachDepthStencilLayerForClear() + { + TextureView depthStencil = _depthStencil; + + if (!IsLayered(depthStencil)) + { + return; + } + + GL.FramebufferTexture(FramebufferTarget.Framebuffer, GetAttachment(depthStencil.Format), 0, 0); + Bind(); + } + + private void BindClearFb() + { + GL.BindFramebuffer(FramebufferTarget.Framebuffer, _clearFbHandle); + + if (!_clearFbInitialized) + { + SetDrawBuffersImpl(Constants.MaxRenderTargets); + _clearFbInitialized = true; + } + } + + private static bool IsLayered(TextureView view) + { + return view != null && + view.Target != Target.Texture1D && + view.Target != Target.Texture2D && + view.Target != Target.Texture2DMultisample && + view.Target != Target.TextureBuffer; + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteFramebuffer(Handle); + + Handle = 0; + } + + if (_clearFbHandle != 0) + { + GL.DeleteFramebuffer(_clearFbHandle); + + _clearFbHandle = 0; + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Handle.cs b/src/Ryujinx.Graphics.OpenGL/Handle.cs new file mode 100644 index 00000000..b63e8f94 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Handle.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.GAL; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.OpenGL +{ + static class Handle + { + public static T FromInt32(int handle) where T : unmanaged + { + Debug.Assert(Unsafe.SizeOf() == sizeof(ulong)); + + ulong handle64 = (uint)handle; + + return Unsafe.As(ref handle64); + } + + public static int ToInt32(this BufferHandle handle) + { + return (int)Unsafe.As(ref handle); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Helper/GLXHelper.cs b/src/Ryujinx.Graphics.OpenGL/Helper/GLXHelper.cs new file mode 100644 index 00000000..ce2b20f7 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Helper/GLXHelper.cs @@ -0,0 +1,36 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Graphics.OpenGL.Helper +{ + [SupportedOSPlatform("linux")] + internal static partial class GLXHelper + { + private const string LibraryName = "glx.dll"; + + static GLXHelper() + { + NativeLibrary.SetDllImportResolver(typeof(GLXHelper).Assembly, (name, assembly, path) => + { + if (name != LibraryName) + { + return IntPtr.Zero; + } + + if (!NativeLibrary.TryLoad("libGL.so.1", assembly, path, out IntPtr result)) + { + if (!NativeLibrary.TryLoad("libGL.so", assembly, path, out result)) + { + return IntPtr.Zero; + } + } + + return result; + }); + } + + [LibraryImport(LibraryName, EntryPoint = "glXGetCurrentContext")] + public static partial IntPtr GetCurrentContext(); + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Helper/WGLHelper.cs b/src/Ryujinx.Graphics.OpenGL/Helper/WGLHelper.cs new file mode 100644 index 00000000..be12ff99 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Helper/WGLHelper.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Graphics.OpenGL.Helper +{ + [SupportedOSPlatform("windows")] + internal static partial class WGLHelper + { + private const string LibraryName = "OPENGL32.DLL"; + + [LibraryImport(LibraryName, EntryPoint = "wglGetCurrentContext")] + public static partial IntPtr GetCurrentContext(); + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs b/src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs new file mode 100644 index 00000000..cf0b0645 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs @@ -0,0 +1,143 @@ +using OpenTK.Graphics.OpenGL; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + static class HwCapabilities + { + private static readonly Lazy _supportsAlphaToCoverageDitherControl = new(() => HasExtension("GL_NV_alpha_to_coverage_dither_control")); + private static readonly Lazy _supportsAstcCompression = new(() => HasExtension("GL_KHR_texture_compression_astc_ldr")); + private static readonly Lazy _supportsBlendEquationAdvanced = new(() => HasExtension("GL_NV_blend_equation_advanced")); + private static readonly Lazy _supportsDrawTexture = new(() => HasExtension("GL_NV_draw_texture")); + private static readonly Lazy _supportsFragmentShaderInterlock = new(() => HasExtension("GL_ARB_fragment_shader_interlock")); + private static readonly Lazy _supportsFragmentShaderOrdering = new(() => HasExtension("GL_INTEL_fragment_shader_ordering")); + private static readonly Lazy _supportsGeometryShaderPassthrough = new(() => HasExtension("GL_NV_geometry_shader_passthrough")); + private static readonly Lazy _supportsImageLoadFormatted = new(() => HasExtension("GL_EXT_shader_image_load_formatted")); + private static readonly Lazy _supportsIndirectParameters = new(() => HasExtension("GL_ARB_indirect_parameters")); + private static readonly Lazy _supportsParallelShaderCompile = new(() => HasExtension("GL_ARB_parallel_shader_compile")); + private static readonly Lazy _supportsPolygonOffsetClamp = new(() => HasExtension("GL_EXT_polygon_offset_clamp")); + private static readonly Lazy _supportsQuads = new(SupportsQuadsCheck); + private static readonly Lazy _supportsSeamlessCubemapPerTexture = new(() => HasExtension("GL_ARB_seamless_cubemap_per_texture")); + private static readonly Lazy _supportsShaderBallot = new(() => HasExtension("GL_ARB_shader_ballot")); + private static readonly Lazy _supportsShaderViewportLayerArray = new(() => HasExtension("GL_ARB_shader_viewport_layer_array")); + private static readonly Lazy _supportsViewportArray2 = new(() => HasExtension("GL_NV_viewport_array2")); + private static readonly Lazy _supportsTextureCompressionBptc = new(() => HasExtension("GL_EXT_texture_compression_bptc")); + private static readonly Lazy _supportsTextureCompressionRgtc = new(() => HasExtension("GL_EXT_texture_compression_rgtc")); + private static readonly Lazy _supportsTextureCompressionS3tc = new(() => HasExtension("GL_EXT_texture_compression_s3tc")); + private static readonly Lazy _supportsTextureShadowLod = new(() => HasExtension("GL_EXT_texture_shadow_lod")); + private static readonly Lazy _supportsViewportSwizzle = new(() => HasExtension("GL_NV_viewport_swizzle")); + + private static readonly Lazy _maximumComputeSharedMemorySize = new(() => GetLimit(All.MaxComputeSharedMemorySize)); + private static readonly Lazy _storageBufferOffsetAlignment = new(() => GetLimit(All.ShaderStorageBufferOffsetAlignment)); + private static readonly Lazy _textureBufferOffsetAlignment = new(() => GetLimit(All.TextureBufferOffsetAlignment)); + + public enum GpuVendor + { + Unknown, + AmdWindows, + AmdUnix, + IntelWindows, + IntelUnix, + Nvidia, + } + + private static readonly Lazy _gpuVendor = new(GetGpuVendor); + + private static bool IsIntel => _gpuVendor.Value == GpuVendor.IntelWindows || _gpuVendor.Value == GpuVendor.IntelUnix; + + public static GpuVendor Vendor => _gpuVendor.Value; + + private static readonly Lazy _maxSupportedAnisotropy = new(GL.GetFloat((GetPName)All.MaxTextureMaxAnisotropy)); + + public static bool UsePersistentBufferForFlush => _gpuVendor.Value == GpuVendor.AmdWindows || _gpuVendor.Value == GpuVendor.Nvidia; + + public static bool SupportsAlphaToCoverageDitherControl => _supportsAlphaToCoverageDitherControl.Value; + public static bool SupportsAstcCompression => _supportsAstcCompression.Value; + public static bool SupportsBlendEquationAdvanced => _supportsBlendEquationAdvanced.Value; + public static bool SupportsDrawTexture => _supportsDrawTexture.Value; + public static bool SupportsFragmentShaderInterlock => _supportsFragmentShaderInterlock.Value; + public static bool SupportsFragmentShaderOrdering => _supportsFragmentShaderOrdering.Value; + public static bool SupportsGeometryShaderPassthrough => _supportsGeometryShaderPassthrough.Value; + public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value; + public static bool SupportsIndirectParameters => _supportsIndirectParameters.Value; + public static bool SupportsParallelShaderCompile => _supportsParallelShaderCompile.Value; + public static bool SupportsPolygonOffsetClamp => _supportsPolygonOffsetClamp.Value; + public static bool SupportsQuads => _supportsQuads.Value; + 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; + public static bool SupportsTextureShadowLod => _supportsTextureShadowLod.Value; + public static bool SupportsViewportSwizzle => _supportsViewportSwizzle.Value; + + public static bool SupportsMismatchingViewFormat => _gpuVendor.Value != GpuVendor.AmdWindows && _gpuVendor.Value != GpuVendor.IntelWindows; + public static bool SupportsNonConstantTextureOffset => _gpuVendor.Value == GpuVendor.Nvidia; + public static bool RequiresSyncFlush => _gpuVendor.Value == GpuVendor.AmdWindows || IsIntel; + + public static int MaximumComputeSharedMemorySize => _maximumComputeSharedMemorySize.Value; + public static int StorageBufferOffsetAlignment => _storageBufferOffsetAlignment.Value; + public static int TextureBufferOffsetAlignment => _textureBufferOffsetAlignment.Value; + + public static float MaximumSupportedAnisotropy => _maxSupportedAnisotropy.Value; + + private static bool HasExtension(string name) + { + int numExtensions = GL.GetInteger(GetPName.NumExtensions); + + for (int extension = 0; extension < numExtensions; extension++) + { + if (GL.GetString(StringNameIndexed.Extensions, extension) == name) + { + return true; + } + } + + return false; + } + + private static int GetLimit(All name) + { + return GL.GetInteger((GetPName)name); + } + + private static GpuVendor GetGpuVendor() + { + string vendor = GL.GetString(StringName.Vendor).ToLowerInvariant(); + + if (vendor == "nvidia corporation") + { + return GpuVendor.Nvidia; + } + else if (vendor == "intel") + { + string renderer = GL.GetString(StringName.Renderer).ToLowerInvariant(); + + return renderer.Contains("mesa") ? GpuVendor.IntelUnix : GpuVendor.IntelWindows; + } + else if (vendor == "ati technologies inc." || vendor == "advanced micro devices, inc.") + { + return GpuVendor.AmdWindows; + } + else if (vendor == "amd" || vendor == "x.org") + { + return GpuVendor.AmdUnix; + } + else + { + return GpuVendor.Unknown; + } + } + + private static bool SupportsQuadsCheck() + { + GL.GetError(); // Clear any existing error. + GL.Begin(PrimitiveType.Quads); + GL.End(); + + return GL.GetError() == ErrorCode.NoError; + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs b/src/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs new file mode 100644 index 00000000..525418d7 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs @@ -0,0 +1,12 @@ +using Ryujinx.Graphics.OpenGL.Helper; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + public interface IOpenGLContext : IDisposable + { + void MakeCurrent(); + + bool HasContext(); + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs b/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs new file mode 100644 index 00000000..490c0c58 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs @@ -0,0 +1,152 @@ +using Ryujinx.Common.Memory; +using System; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + static class FormatConverter + { + public unsafe static MemoryOwner ConvertS8D24ToD24S8(ReadOnlySpan data) + { + MemoryOwner outputMemory = MemoryOwner.Rent(data.Length); + + Span output = outputMemory.Span; + + int start = 0; + + if (Avx2.IsSupported) + { + var mask = Vector256.Create( + (byte)3, (byte)0, (byte)1, (byte)2, + (byte)7, (byte)4, (byte)5, (byte)6, + (byte)11, (byte)8, (byte)9, (byte)10, + (byte)15, (byte)12, (byte)13, (byte)14, + (byte)19, (byte)16, (byte)17, (byte)18, + (byte)23, (byte)20, (byte)21, (byte)22, + (byte)27, (byte)24, (byte)25, (byte)26, + (byte)31, (byte)28, (byte)29, (byte)30); + + int sizeAligned = data.Length & ~31; + + fixed (byte* pInput = data, pOutput = output) + { + for (uint i = 0; i < sizeAligned; i += 32) + { + var dataVec = Avx.LoadVector256(pInput + i); + + dataVec = Avx2.Shuffle(dataVec, mask); + + Avx.Store(pOutput + i, dataVec); + } + } + + start = sizeAligned; + } + else if (Ssse3.IsSupported) + { + var mask = Vector128.Create( + (byte)3, (byte)0, (byte)1, (byte)2, + (byte)7, (byte)4, (byte)5, (byte)6, + (byte)11, (byte)8, (byte)9, (byte)10, + (byte)15, (byte)12, (byte)13, (byte)14); + + int sizeAligned = data.Length & ~15; + + fixed (byte* pInput = data, pOutput = output) + { + for (uint i = 0; i < sizeAligned; i += 16) + { + var dataVec = Sse2.LoadVector128(pInput + i); + + dataVec = Ssse3.Shuffle(dataVec, mask); + + Sse2.Store(pOutput + i, dataVec); + } + } + + start = sizeAligned; + } + + var outSpan = MemoryMarshal.Cast(output); + var dataSpan = MemoryMarshal.Cast(data); + for (int i = start / sizeof(uint); i < dataSpan.Length; i++) + { + outSpan[i] = BitOperations.RotateLeft(dataSpan[i], 8); + } + + return outputMemory; + } + + public unsafe static byte[] ConvertD24S8ToS8D24(ReadOnlySpan data) + { + byte[] output = new byte[data.Length]; + + int start = 0; + + if (Avx2.IsSupported) + { + var mask = Vector256.Create( + (byte)1, (byte)2, (byte)3, (byte)0, + (byte)5, (byte)6, (byte)7, (byte)4, + (byte)9, (byte)10, (byte)11, (byte)8, + (byte)13, (byte)14, (byte)15, (byte)12, + (byte)17, (byte)18, (byte)19, (byte)16, + (byte)21, (byte)22, (byte)23, (byte)20, + (byte)25, (byte)26, (byte)27, (byte)24, + (byte)29, (byte)30, (byte)31, (byte)28); + + int sizeAligned = data.Length & ~31; + + fixed (byte* pInput = data, pOutput = output) + { + for (uint i = 0; i < sizeAligned; i += 32) + { + var dataVec = Avx.LoadVector256(pInput + i); + + dataVec = Avx2.Shuffle(dataVec, mask); + + Avx.Store(pOutput + i, dataVec); + } + } + + start = sizeAligned; + } + else if (Ssse3.IsSupported) + { + var mask = Vector128.Create( + (byte)1, (byte)2, (byte)3, (byte)0, + (byte)5, (byte)6, (byte)7, (byte)4, + (byte)9, (byte)10, (byte)11, (byte)8, + (byte)13, (byte)14, (byte)15, (byte)12); + + int sizeAligned = data.Length & ~15; + + fixed (byte* pInput = data, pOutput = output) + { + for (uint i = 0; i < sizeAligned; i += 16) + { + var dataVec = Sse2.LoadVector128(pInput + i); + + dataVec = Ssse3.Shuffle(dataVec, mask); + + Sse2.Store(pOutput + i, dataVec); + } + } + + start = sizeAligned; + } + + var outSpan = MemoryMarshal.Cast(output); + var dataSpan = MemoryMarshal.Cast(data); + for (int i = start / sizeof(uint); i < dataSpan.Length; i++) + { + outSpan[i] = BitOperations.RotateRight(dataSpan[i], 8); + } + + return output; + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/ITextureInfo.cs b/src/Ryujinx.Graphics.OpenGL/Image/ITextureInfo.cs new file mode 100644 index 00000000..fecde6dd --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/ITextureInfo.cs @@ -0,0 +1,14 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + interface ITextureInfo + { + ITextureInfo Storage { get; } + int Handle { get; } + int FirstLayer => 0; + int FirstLevel => 0; + + TextureCreateInfo Info { get; } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs b/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs new file mode 100644 index 00000000..6198823d --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs @@ -0,0 +1,71 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + class ImageArray : IImageArray + { + private record struct TextureRef + { + public int Handle; + public Format Format; + } + + private readonly TextureRef[] _images; + + public ImageArray(int size) + { + _images = new TextureRef[size]; + } + + public void SetFormats(int index, GAL.Format[] imageFormats) + { + for (int i = 0; i < imageFormats.Length; i++) + { + _images[index + i].Format = imageFormats[i]; + } + } + + public void SetImages(int index, ITexture[] images) + { + for (int i = 0; i < images.Length; i++) + { + ITexture image = images[i]; + + if (image is TextureBase imageBase) + { + _images[index + i].Handle = imageBase.Handle; + } + else + { + _images[index + i].Handle = 0; + } + } + } + + public void Bind(int baseBinding) + { + for (int i = 0; i < _images.Length; i++) + { + if (_images[i].Handle == 0) + { + GL.BindImageTexture(baseBinding + i, 0, 0, true, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8); + } + else + { + SizedInternalFormat format = FormatTable.GetImageFormat(_images[i].Format); + + if (format != 0) + { + GL.BindImageTexture(baseBinding + i, _images[i].Handle, 0, true, 0, TextureAccess.ReadWrite, format); + } + } + } + } + + public void Dispose() + { + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs b/src/Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs new file mode 100644 index 00000000..64ee73fb --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs @@ -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 _entries; + + public IntermediatePool(OpenGLRenderer renderer) + { + _renderer = renderer; + _entries = new List(); + } + + 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)); + } + + public void Dispose() + { + foreach (TextureView entry in _entries) + { + entry.Dispose(); + } + + _entries.Clear(); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/Sampler.cs b/src/Ryujinx.Graphics.OpenGL/Image/Sampler.cs new file mode 100644 index 00000000..6cf04829 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/Sampler.cs @@ -0,0 +1,64 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + class Sampler : ISampler + { + public int Handle { get; private set; } + + public Sampler(SamplerCreateInfo info) + { + Handle = GL.GenSampler(); + + GL.SamplerParameter(Handle, SamplerParameterName.TextureMinFilter, (int)info.MinFilter.Convert()); + GL.SamplerParameter(Handle, SamplerParameterName.TextureMagFilter, (int)info.MagFilter.Convert()); + + if (HwCapabilities.SupportsSeamlessCubemapPerTexture) + { + GL.SamplerParameter(Handle, (SamplerParameterName)ArbSeamlessCubemapPerTexture.TextureCubeMapSeamless, info.SeamlessCubemap ? 1 : 0); + } + + GL.SamplerParameter(Handle, SamplerParameterName.TextureWrapS, (int)info.AddressU.Convert()); + GL.SamplerParameter(Handle, SamplerParameterName.TextureWrapT, (int)info.AddressV.Convert()); + GL.SamplerParameter(Handle, SamplerParameterName.TextureWrapR, (int)info.AddressP.Convert()); + + GL.SamplerParameter(Handle, SamplerParameterName.TextureCompareMode, (int)info.CompareMode.Convert()); + GL.SamplerParameter(Handle, SamplerParameterName.TextureCompareFunc, (int)info.CompareOp.Convert()); + + unsafe + { + float* borderColor = stackalloc float[4] + { + info.BorderColor.Red, + info.BorderColor.Green, + info.BorderColor.Blue, + info.BorderColor.Alpha, + }; + + GL.SamplerParameter(Handle, SamplerParameterName.TextureBorderColor, borderColor); + } + + GL.SamplerParameter(Handle, SamplerParameterName.TextureMinLod, info.MinLod); + GL.SamplerParameter(Handle, SamplerParameterName.TextureMaxLod, info.MaxLod); + GL.SamplerParameter(Handle, SamplerParameterName.TextureLodBias, info.MipLodBias); + + GL.SamplerParameter(Handle, SamplerParameterName.TextureMaxAnisotropyExt, info.MaxAnisotropy); + } + + public void Bind(int unit) + { + GL.BindSampler(unit, Handle); + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteSampler(Handle); + + Handle = 0; + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs new file mode 100644 index 00000000..41ac058c --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs @@ -0,0 +1,56 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + class TextureArray : ITextureArray + { + private record struct TextureRef + { + public TextureBase Texture; + public Sampler Sampler; + } + + private readonly TextureRef[] _textureRefs; + + public TextureArray(int size) + { + _textureRefs = new TextureRef[size]; + } + + public void SetSamplers(int index, ISampler[] samplers) + { + for (int i = 0; i < samplers.Length; i++) + { + _textureRefs[index + i].Sampler = samplers[i] as Sampler; + } + } + + public void SetTextures(int index, ITexture[] textures) + { + for (int i = 0; i < textures.Length; i++) + { + _textureRefs[index + i].Texture = textures[i] as TextureBase; + } + } + + public void Bind(int baseBinding) + { + for (int i = 0; i < _textureRefs.Length; i++) + { + if (_textureRefs[i].Texture != null) + { + _textureRefs[i].Texture.Bind(baseBinding + i); + _textureRefs[i].Sampler?.Bind(baseBinding + i); + } + else + { + TextureBase.ClearBinding(baseBinding + i); + } + } + } + + public void Dispose() + { + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureBase.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureBase.cs new file mode 100644 index 00000000..df845383 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureBase.cs @@ -0,0 +1,42 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + class TextureBase + { + public int Handle { get; protected set; } + + public TextureCreateInfo Info { get; } + + public int Width => Info.Width; + public int Height => Info.Height; + + public Target Target => Info.Target; + public Format Format => Info.Format; + + public TextureBase(TextureCreateInfo info) + { + Info = info; + + Handle = GL.GenTexture(); + } + + public void Bind(int unit) + { + Bind(Target.Convert(), unit); + } + + protected void Bind(TextureTarget target, int unit) + { + GL.ActiveTexture(TextureUnit.Texture0 + unit); + GL.BindTexture(target, Handle); + } + + public static void ClearBinding(int unit) + { + GL.ActiveTexture(TextureUnit.Texture0 + unit); + GL.BindTextureUnit(unit, 0); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs new file mode 100644 index 00000000..a8196541 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs @@ -0,0 +1,118 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; +using System.Buffers; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + class TextureBuffer : TextureBase, ITexture + { + private readonly OpenGLRenderer _renderer; + private int _bufferOffset; + private int _bufferSize; + private int _bufferCount; + + private BufferHandle _buffer; + + public TextureBuffer(OpenGLRenderer renderer, TextureCreateInfo info) : base(info) + { + _renderer = renderer; + } + + public void CopyTo(ITexture destination, int firstLayer, int firstLevel) + { + throw new NotSupportedException(); + } + + public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel) + { + throw new NotSupportedException(); + } + + public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter) + { + throw new NotSupportedException(); + } + + public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + throw new NotSupportedException(); + } + + public PinnedSpan GetData() + { + return Buffer.GetData(_renderer, _buffer, _bufferOffset, _bufferSize); + } + + public PinnedSpan GetData(int layer, int level) + { + return GetData(); + } + + public void CopyTo(BufferRange range, int layer, int level, int stride) + { + throw new NotImplementedException(); + } + + /// + public void SetData(IMemoryOwner data) + { + var dataSpan = data.Memory.Span; + + Buffer.SetData(_buffer, _bufferOffset, dataSpan[..Math.Min(dataSpan.Length, _bufferSize)]); + + data.Dispose(); + } + + /// + public void SetData(IMemoryOwner data, int layer, int level) + { + throw new NotSupportedException(); + } + + /// + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) + { + throw new NotSupportedException(); + } + + public void SetStorage(BufferRange buffer) + { + if (_buffer != BufferHandle.Null && + _buffer == buffer.Handle && + buffer.Offset == _bufferOffset && + buffer.Size == _bufferSize && + _renderer.BufferCount == _bufferCount) + { + // Only rebind the buffer when more have been created. + return; + } + + _buffer = buffer.Handle; + _bufferOffset = buffer.Offset; + _bufferSize = buffer.Size; + _bufferCount = _renderer.BufferCount; + + Bind(0); + + SizedInternalFormat format = (SizedInternalFormat)FormatTable.GetFormatInfo(Info.Format).PixelInternalFormat; + + GL.TexBufferRange(TextureBufferTarget.TextureBuffer, format, _buffer.ToInt32(), (IntPtr)buffer.Offset, buffer.Size); + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteTexture(Handle); + + Handle = 0; + } + } + + public void Release() + { + Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs new file mode 100644 index 00000000..89bd5e4f --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs @@ -0,0 +1,517 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + class TextureCopy : IDisposable + { + private readonly OpenGLRenderer _renderer; + + private int _srcFramebuffer; + private int _dstFramebuffer; + + private int _copyPboHandle; + private int _copyPboSize; + + public IntermediatePool IntermediatePool { get; } + + public TextureCopy(OpenGLRenderer renderer) + { + _renderer = renderer; + IntermediatePool = new IntermediatePool(renderer); + } + + public void Copy( + TextureView src, + TextureView dst, + Extents2D srcRegion, + Extents2D dstRegion, + bool linearFilter, + int srcLayer = 0, + int dstLayer = 0, + int srcLevel = 0, + int dstLevel = 0) + { + int levels = Math.Min(src.Info.Levels - srcLevel, dst.Info.Levels - dstLevel); + int layers = Math.Min(src.Info.GetLayers() - srcLayer, dst.Info.GetLayers() - dstLayer); + + Copy(src, dst, srcRegion, dstRegion, linearFilter, srcLayer, dstLayer, srcLevel, dstLevel, layers, levels); + } + + public void Copy( + TextureView src, + TextureView dst, + Extents2D srcRegion, + Extents2D dstRegion, + bool linearFilter, + int srcLayer, + int dstLayer, + int srcLevel, + int dstLevel, + int layers, + int levels) + { + TextureView srcConverted = src.Format.IsBgr() != dst.Format.IsBgr() ? BgraSwap(src) : src; + + (int oldDrawFramebufferHandle, int oldReadFramebufferHandle) = ((Pipeline)_renderer.Pipeline).GetBoundFramebuffers(); + + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetSrcFramebufferLazy()); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetDstFramebufferLazy()); + + if (srcLevel != 0) + { + srcRegion = srcRegion.Reduce(srcLevel); + } + + if (dstLevel != 0) + { + dstRegion = dstRegion.Reduce(dstLevel); + } + + for (int level = 0; level < levels; level++) + { + for (int layer = 0; layer < layers; layer++) + { + if ((srcLayer | dstLayer) != 0 || layers > 1) + { + Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, srcLevel + level, srcLayer + layer); + Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, dstLevel + level, dstLayer + layer); + } + else + { + Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, srcLevel + level); + Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, dstLevel + level); + } + + ClearBufferMask mask = GetMask(src.Format); + + if ((mask & (ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit)) != 0 || src.Format.IsInteger()) + { + linearFilter = false; + } + + BlitFramebufferFilter filter = linearFilter + ? BlitFramebufferFilter.Linear + : BlitFramebufferFilter.Nearest; + + GL.ReadBuffer(ReadBufferMode.ColorAttachment0); + GL.DrawBuffer(DrawBufferMode.ColorAttachment0); + + GL.Disable(EnableCap.RasterizerDiscard); + GL.Disable(IndexedEnableCap.ScissorTest, 0); + + GL.BlitFramebuffer( + srcRegion.X1, + srcRegion.Y1, + srcRegion.X2, + srcRegion.Y2, + dstRegion.X1, + dstRegion.Y1, + dstRegion.X2, + dstRegion.Y2, + mask, + filter); + } + + if (level < levels - 1) + { + srcRegion = srcRegion.Reduce(1); + dstRegion = dstRegion.Reduce(1); + } + } + + Attach(FramebufferTarget.ReadFramebuffer, src.Format, 0); + Attach(FramebufferTarget.DrawFramebuffer, dst.Format, 0); + + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle); + + ((Pipeline)_renderer.Pipeline).RestoreScissor0Enable(); + ((Pipeline)_renderer.Pipeline).RestoreRasterizerDiscard(); + + if (srcConverted != src) + { + srcConverted.Dispose(); + } + } + + public void CopyUnscaled( + ITextureInfo src, + ITextureInfo dst, + int srcLayer, + int dstLayer, + int srcLevel, + int dstLevel) + { + TextureCreateInfo srcInfo = src.Info; + TextureCreateInfo dstInfo = dst.Info; + + int srcDepth = srcInfo.GetDepthOrLayers(); + int srcLevels = srcInfo.Levels; + + int dstDepth = dstInfo.GetDepthOrLayers(); + int dstLevels = dstInfo.Levels; + + if (dstInfo.Target == Target.Texture3D) + { + dstDepth = Math.Max(1, dstDepth >> dstLevel); + } + + int depth = Math.Min(srcDepth, dstDepth); + int levels = Math.Min(srcLevels, dstLevels); + + CopyUnscaled(src, dst, srcLayer, dstLayer, srcLevel, dstLevel, depth, levels); + } + + public void CopyUnscaled( + ITextureInfo src, + ITextureInfo dst, + int srcLayer, + int dstLayer, + int srcLevel, + int dstLevel, + int depth, + int levels) + { + TextureCreateInfo srcInfo = src.Info; + TextureCreateInfo dstInfo = dst.Info; + + int srcHandle = src.Handle; + int dstHandle = dst.Handle; + + int srcWidth = srcInfo.Width; + int srcHeight = srcInfo.Height; + + int dstWidth = dstInfo.Width; + int dstHeight = dstInfo.Height; + + srcWidth = Math.Max(1, srcWidth >> srcLevel); + srcHeight = Math.Max(1, srcHeight >> srcLevel); + + dstWidth = Math.Max(1, dstWidth >> dstLevel); + dstHeight = Math.Max(1, dstHeight >> dstLevel); + + int blockWidth = 1; + int blockHeight = 1; + bool sizeInBlocks = false; + + // When copying from a compressed to a non-compressed format, + // the non-compressed texture will have the size of the texture + // in blocks (not in texels), so we must adjust that size to + // match the size in texels of the compressed texture. + if (!srcInfo.IsCompressed && dstInfo.IsCompressed) + { + srcWidth *= dstInfo.BlockWidth; + srcHeight *= dstInfo.BlockHeight; + blockWidth = dstInfo.BlockWidth; + blockHeight = dstInfo.BlockHeight; + + sizeInBlocks = true; + } + else if (srcInfo.IsCompressed && !dstInfo.IsCompressed) + { + dstWidth *= srcInfo.BlockWidth; + dstHeight *= srcInfo.BlockHeight; + blockWidth = srcInfo.BlockWidth; + blockHeight = srcInfo.BlockHeight; + } + + int width = Math.Min(srcWidth, dstWidth); + int height = Math.Min(srcHeight, dstHeight); + + for (int level = 0; level < levels; level++) + { + // Stop copy if we are already out of the levels range. + if (level >= srcInfo.Levels || dstLevel + level >= dstInfo.Levels) + { + break; + } + + if ((width % blockWidth != 0 || height % blockHeight != 0) && src is TextureView srcView && dst is TextureView dstView) + { + PboCopy(srcView, dstView, srcLayer, dstLayer, srcLevel + level, dstLevel + level, width, height); + } + else + { + int copyWidth = sizeInBlocks ? BitUtils.DivRoundUp(width, blockWidth) : width; + int copyHeight = sizeInBlocks ? BitUtils.DivRoundUp(height, blockHeight) : height; + + if (HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows) + { + GL.CopyImageSubData( + src.Storage.Handle, + src.Storage.Info.Target.ConvertToImageTarget(), + src.FirstLevel + srcLevel + level, + 0, + 0, + src.FirstLayer + srcLayer, + dst.Storage.Handle, + dst.Storage.Info.Target.ConvertToImageTarget(), + dst.FirstLevel + dstLevel + level, + 0, + 0, + dst.FirstLayer + dstLayer, + copyWidth, + copyHeight, + depth); + } + else + { + GL.CopyImageSubData( + srcHandle, + srcInfo.Target.ConvertToImageTarget(), + srcLevel + level, + 0, + 0, + srcLayer, + dstHandle, + dstInfo.Target.ConvertToImageTarget(), + dstLevel + level, + 0, + 0, + dstLayer, + copyWidth, + copyHeight, + depth); + } + } + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + + if (srcInfo.Target == Target.Texture3D) + { + depth = Math.Max(1, depth >> 1); + } + } + } + + private static FramebufferAttachment AttachmentForFormat(Format format) + { + if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint) + { + return FramebufferAttachment.DepthStencilAttachment; + } + else if (FormatTable.IsDepthOnly(format)) + { + return FramebufferAttachment.DepthAttachment; + } + else if (format == Format.S8Uint) + { + return FramebufferAttachment.StencilAttachment; + } + else + { + return FramebufferAttachment.ColorAttachment0; + } + } + + private static void Attach(FramebufferTarget target, Format format, int handle, int level = 0) + { + FramebufferAttachment attachment = AttachmentForFormat(format); + + GL.FramebufferTexture(target, attachment, handle, level); + } + + private static void Attach(FramebufferTarget target, Format format, int handle, int level, int layer) + { + FramebufferAttachment attachment = AttachmentForFormat(format); + + GL.FramebufferTextureLayer(target, attachment, handle, level, layer); + } + + private static ClearBufferMask GetMask(Format format) + { + if (FormatTable.IsPackedDepthStencil(format)) + { + return ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit; + } + else if (FormatTable.IsDepthOnly(format)) + { + return ClearBufferMask.DepthBufferBit; + } + else if (format == Format.S8Uint) + { + return ClearBufferMask.StencilBufferBit; + } + else + { + return ClearBufferMask.ColorBufferBit; + } + } + + public TextureView BgraSwap(TextureView from) + { + TextureView to = (TextureView)_renderer.CreateTexture(from.Info); + + EnsurePbo(from); + + GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyPboHandle); + + from.WriteToPbo(0, forceBgra: true); + + GL.BindBuffer(BufferTarget.PixelPackBuffer, 0); + GL.BindBuffer(BufferTarget.PixelUnpackBuffer, _copyPboHandle); + + to.ReadFromPbo(0, _copyPboSize); + + GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0); + + return to; + } + + public void PboCopy(TextureView from, TextureView to, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int width, int height) + { + int dstWidth = width; + int dstHeight = height; + + // The size of the source texture. + int unpackWidth = from.Width; + int unpackHeight = from.Height; + + if (from.Info.IsCompressed != to.Info.IsCompressed) + { + if (from.Info.IsCompressed) + { + // Dest size is in pixels, but should be in blocks + dstWidth = BitUtils.DivRoundUp(width, from.Info.BlockWidth); + dstHeight = BitUtils.DivRoundUp(height, from.Info.BlockHeight); + + // When copying from a compressed texture, the source size must be taken in blocks for unpacking to the uncompressed block texture. + unpackWidth = BitUtils.DivRoundUp(from.Info.Width, from.Info.BlockWidth); + unpackHeight = BitUtils.DivRoundUp(from.Info.Height, from.Info.BlockHeight); + } + else + { + // When copying to a compressed texture, the source size must be scaled by the block width for unpacking on the compressed target. + unpackWidth = from.Info.Width * to.Info.BlockWidth; + unpackHeight = from.Info.Height * to.Info.BlockHeight; + } + } + + EnsurePbo(from); + + GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyPboHandle); + + // The source texture is written out in full, then the destination is taken as a slice from the data using unpack params. + // The offset points to the base at which the requested layer is at. + + int offset = from.WriteToPbo2D(0, srcLayer, srcLevel); + + // If the destination size is not an exact match for the source unpack parameters, we need to set them to slice the data correctly. + + bool slice = (unpackWidth != dstWidth || unpackHeight != dstHeight); + + if (slice) + { + // Set unpack parameters to take a slice of width/height: + GL.PixelStore(PixelStoreParameter.UnpackRowLength, unpackWidth); + GL.PixelStore(PixelStoreParameter.UnpackImageHeight, unpackHeight); + + if (to.Info.IsCompressed) + { + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockWidth, to.Info.BlockWidth); + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockHeight, to.Info.BlockHeight); + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockDepth, 1); + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockSize, to.Info.BytesPerPixel); + } + } + + GL.BindBuffer(BufferTarget.PixelPackBuffer, 0); + GL.BindBuffer(BufferTarget.PixelUnpackBuffer, _copyPboHandle); + + to.ReadFromPbo2D(offset, dstLayer, dstLevel, dstWidth, dstHeight); + + if (slice) + { + // Reset unpack parameters + GL.PixelStore(PixelStoreParameter.UnpackRowLength, 0); + GL.PixelStore(PixelStoreParameter.UnpackImageHeight, 0); + + if (to.Info.IsCompressed) + { + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockWidth, 0); + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockHeight, 0); + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockDepth, 0); + GL.PixelStore(PixelStoreParameter.UnpackCompressedBlockSize, 0); + } + } + + GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0); + } + + private void EnsurePbo(TextureView view) + { + int requiredSize = 0; + + for (int level = 0; level < view.Info.Levels; level++) + { + requiredSize += view.Info.GetMipSize(level); + } + + if (_copyPboSize < requiredSize && _copyPboHandle != 0) + { + GL.DeleteBuffer(_copyPboHandle); + + _copyPboHandle = 0; + } + + if (_copyPboHandle == 0) + { + _copyPboHandle = GL.GenBuffer(); + _copyPboSize = requiredSize; + + GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyPboHandle); + GL.BufferData(BufferTarget.PixelPackBuffer, requiredSize, IntPtr.Zero, BufferUsageHint.DynamicCopy); + } + } + + private int GetSrcFramebufferLazy() + { + if (_srcFramebuffer == 0) + { + _srcFramebuffer = GL.GenFramebuffer(); + } + + return _srcFramebuffer; + } + + private int GetDstFramebufferLazy() + { + if (_dstFramebuffer == 0) + { + _dstFramebuffer = GL.GenFramebuffer(); + } + + return _dstFramebuffer; + } + + public void Dispose() + { + if (_srcFramebuffer != 0) + { + GL.DeleteFramebuffer(_srcFramebuffer); + + _srcFramebuffer = 0; + } + + if (_dstFramebuffer != 0) + { + GL.DeleteFramebuffer(_dstFramebuffer); + + _dstFramebuffer = 0; + } + + if (_copyPboHandle != 0) + { + GL.DeleteBuffer(_copyPboHandle); + + _copyPboHandle = 0; + } + + IntermediatePool.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureCopyIncompatible.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureCopyIncompatible.cs new file mode 100644 index 00000000..082e6ccf --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureCopyIncompatible.cs @@ -0,0 +1,248 @@ +using OpenTK.Graphics.OpenGL; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + class TextureCopyIncompatible + { + private const string ComputeShaderShortening = @"#version 450 core + +layout (binding = 0, $SRC_FORMAT$) uniform uimage2D src; +layout (binding = 1, $DST_FORMAT$) uniform uimage2D dst; + +layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in; + +void main() +{ + uvec2 coords = gl_GlobalInvocationID.xy; + ivec2 imageSz = imageSize(src); + + if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y) + { + return; + } + + uint coordsShifted = coords.x << $RATIO_LOG2$; + + uvec2 dstCoords0 = uvec2(coordsShifted, coords.y); + uvec2 dstCoords1 = uvec2(coordsShifted + 1, coords.y); + uvec2 dstCoords2 = uvec2(coordsShifted + 2, coords.y); + uvec2 dstCoords3 = uvec2(coordsShifted + 3, coords.y); + + uvec4 rgba = imageLoad(src, ivec2(coords)); + + imageStore(dst, ivec2(dstCoords0), rgba.rrrr); + imageStore(dst, ivec2(dstCoords1), rgba.gggg); + imageStore(dst, ivec2(dstCoords2), rgba.bbbb); + imageStore(dst, ivec2(dstCoords3), rgba.aaaa); +}"; + + private const string ComputeShaderWidening = @"#version 450 core + +layout (binding = 0, $SRC_FORMAT$) uniform uimage2D src; +layout (binding = 1, $DST_FORMAT$) uniform uimage2D dst; + +layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in; + +void main() +{ + uvec2 coords = gl_GlobalInvocationID.xy; + ivec2 imageSz = imageSize(dst); + + if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y) + { + return; + } + + uvec2 srcCoords = uvec2(coords.x << $RATIO_LOG2$, coords.y); + + uint r = imageLoad(src, ivec2(srcCoords) + ivec2(0, 0)).r; + uint g = imageLoad(src, ivec2(srcCoords) + ivec2(1, 0)).r; + uint b = imageLoad(src, ivec2(srcCoords) + ivec2(2, 0)).r; + uint a = imageLoad(src, ivec2(srcCoords) + ivec2(3, 0)).r; + + imageStore(dst, ivec2(coords), uvec4(r, g, b, a)); +}"; + + private readonly OpenGLRenderer _renderer; + private readonly Dictionary _shorteningProgramHandles; + private readonly Dictionary _wideningProgramHandles; + + public TextureCopyIncompatible(OpenGLRenderer renderer) + { + _renderer = renderer; + _shorteningProgramHandles = new Dictionary(); + _wideningProgramHandles = new Dictionary(); + } + + public void CopyIncompatibleFormats(ITextureInfo src, ITextureInfo dst, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int depth, int levels) + { + int srcBpp = src.Info.BytesPerPixel; + int dstBpp = dst.Info.BytesPerPixel; + + // Calculate ideal component size, given our constraints: + // - Component size must not exceed bytes per pixel of source and destination image formats. + // - Maximum component size is 4 (R32). + int componentSize = Math.Min(Math.Min(srcBpp, dstBpp), 4); + + int srcComponentsCount = srcBpp / componentSize; + int dstComponentsCount = dstBpp / componentSize; + + var srcFormat = GetFormat(componentSize, srcComponentsCount); + var dstFormat = GetFormat(componentSize, dstComponentsCount); + + GL.UseProgram(srcBpp < dstBpp + ? GetWideningShader(componentSize, srcComponentsCount, dstComponentsCount) + : GetShorteningShader(componentSize, srcComponentsCount, dstComponentsCount)); + + for (int l = 0; l < levels; l++) + { + int srcWidth = Math.Max(1, src.Info.Width >> l); + int srcHeight = Math.Max(1, src.Info.Height >> l); + + int dstWidth = Math.Max(1, dst.Info.Width >> l); + int dstHeight = Math.Max(1, dst.Info.Height >> l); + + int width = Math.Min(srcWidth, dstWidth); + int height = Math.Min(srcHeight, dstHeight); + + for (int z = 0; z < depth; z++) + { + GL.BindImageTexture(0, src.Handle, srcLevel + l, false, srcLayer + z, TextureAccess.ReadOnly, srcFormat); + GL.BindImageTexture(1, dst.Handle, dstLevel + l, false, dstLayer + z, TextureAccess.WriteOnly, dstFormat); + + GL.DispatchCompute((width + 31) / 32, (height + 31) / 32, 1); + } + } + + Pipeline pipeline = (Pipeline)_renderer.Pipeline; + + pipeline.RestoreProgram(); + pipeline.RestoreImages1And2(); + } + + private static SizedInternalFormat GetFormat(int componentSize, int componentsCount) + { + if (componentSize == 1) + { + return componentsCount switch + { + 1 => SizedInternalFormat.R8ui, + 2 => SizedInternalFormat.Rg8ui, + 4 => SizedInternalFormat.Rgba8ui, + _ => throw new ArgumentException($"Invalid components count {componentsCount}."), + }; + } + else if (componentSize == 2) + { + return componentsCount switch + { + 1 => SizedInternalFormat.R16ui, + 2 => SizedInternalFormat.Rg16ui, + 4 => SizedInternalFormat.Rgba16ui, + _ => throw new ArgumentException($"Invalid components count {componentsCount}."), + }; + } + else if (componentSize == 4) + { + return componentsCount switch + { + 1 => SizedInternalFormat.R32ui, + 2 => SizedInternalFormat.Rg32ui, + 4 => SizedInternalFormat.Rgba32ui, + _ => throw new ArgumentException($"Invalid components count {componentsCount}."), + }; + } + else + { + throw new ArgumentException($"Invalid component size {componentSize}."); + } + } + + private int GetShorteningShader(int componentSize, int srcComponentsCount, int dstComponentsCount) + { + return GetShader(ComputeShaderShortening, _shorteningProgramHandles, componentSize, srcComponentsCount, dstComponentsCount); + } + + private int GetWideningShader(int componentSize, int srcComponentsCount, int dstComponentsCount) + { + return GetShader(ComputeShaderWidening, _wideningProgramHandles, componentSize, srcComponentsCount, dstComponentsCount); + } + + private static int GetShader( + string code, + Dictionary programHandles, + int componentSize, + int srcComponentsCount, + int dstComponentsCount) + { + int componentSizeLog2 = BitOperations.Log2((uint)componentSize); + + int srcIndex = componentSizeLog2 + BitOperations.Log2((uint)srcComponentsCount) * 3; + int dstIndex = componentSizeLog2 + BitOperations.Log2((uint)dstComponentsCount) * 3; + + int key = srcIndex | (dstIndex << 8); + + if (!programHandles.TryGetValue(key, out int programHandle)) + { + int csHandle = GL.CreateShader(ShaderType.ComputeShader); + + string[] formatTable = new[] { "r8ui", "r16ui", "r32ui", "rg8ui", "rg16ui", "rg32ui", "rgba8ui", "rgba16ui", "rgba32ui" }; + + string srcFormat = formatTable[srcIndex]; + string dstFormat = formatTable[dstIndex]; + + int srcBpp = srcComponentsCount * componentSize; + int dstBpp = dstComponentsCount * componentSize; + + int ratio = srcBpp < dstBpp ? dstBpp / srcBpp : srcBpp / dstBpp; + int ratioLog2 = BitOperations.Log2((uint)ratio); + + GL.ShaderSource(csHandle, code + .Replace("$SRC_FORMAT$", srcFormat) + .Replace("$DST_FORMAT$", dstFormat) + .Replace("$RATIO_LOG2$", ratioLog2.ToString(CultureInfo.InvariantCulture))); + + GL.CompileShader(csHandle); + + programHandle = GL.CreateProgram(); + + GL.AttachShader(programHandle, csHandle); + GL.LinkProgram(programHandle); + GL.DetachShader(programHandle, csHandle); + GL.DeleteShader(csHandle); + + GL.GetProgram(programHandle, GetProgramParameterName.LinkStatus, out int status); + + if (status == 0) + { + throw new Exception(GL.GetProgramInfoLog(programHandle)); + } + + programHandles.Add(key, programHandle); + } + + return programHandle; + } + + public void Dispose() + { + foreach (int handle in _shorteningProgramHandles.Values) + { + GL.DeleteProgram(handle); + } + + _shorteningProgramHandles.Clear(); + + foreach (int handle in _wideningProgramHandles.Values) + { + GL.DeleteProgram(handle); + } + + _wideningProgramHandles.Clear(); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs new file mode 100644 index 00000000..0fa6453d --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs @@ -0,0 +1,276 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; +using System.Numerics; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + class TextureCopyMS + { + private const string ComputeShaderMSToNonMS = @"#version 450 core + +layout (binding = 0, $FORMAT$) uniform uimage2DMS imgIn; +layout (binding = 1, $FORMAT$) uniform uimage2D imgOut; + +layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in; + +void main() +{ + uvec2 coords = gl_GlobalInvocationID.xy; + ivec2 imageSz = imageSize(imgOut); + if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y) + { + return; + } + int inSamples = imageSamples(imgIn); + int samplesInXLog2 = 0; + int samplesInYLog2 = 0; + switch (inSamples) + { + case 2: + samplesInXLog2 = 1; + break; + case 4: + samplesInXLog2 = 1; + samplesInYLog2 = 1; + break; + case 8: + samplesInXLog2 = 2; + samplesInYLog2 = 1; + break; + case 16: + samplesInXLog2 = 2; + samplesInYLog2 = 2; + break; + } + int samplesInX = 1 << samplesInXLog2; + int samplesInY = 1 << samplesInYLog2; + int sampleIdx = (int(coords.x) & (samplesInX - 1)) | ((int(coords.y) & (samplesInY - 1)) << samplesInXLog2); + uvec4 value = imageLoad(imgIn, ivec2(int(coords.x) >> samplesInXLog2, int(coords.y) >> samplesInYLog2), sampleIdx); + imageStore(imgOut, ivec2(coords), value); +}"; + + private const string ComputeShaderNonMSToMS = @"#version 450 core + +layout (binding = 0, $FORMAT$) uniform uimage2D imgIn; +layout (binding = 1, $FORMAT$) uniform uimage2DMS imgOut; + +layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in; + +void main() +{ + uvec2 coords = gl_GlobalInvocationID.xy; + ivec2 imageSz = imageSize(imgIn); + if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y) + { + return; + } + int outSamples = imageSamples(imgOut); + int samplesInXLog2 = 0; + int samplesInYLog2 = 0; + switch (outSamples) + { + case 2: + samplesInXLog2 = 1; + break; + case 4: + samplesInXLog2 = 1; + samplesInYLog2 = 1; + break; + case 8: + samplesInXLog2 = 2; + samplesInYLog2 = 1; + break; + case 16: + samplesInXLog2 = 2; + samplesInYLog2 = 2; + break; + } + int samplesInX = 1 << samplesInXLog2; + int samplesInY = 1 << samplesInYLog2; + int sampleIdx = (int(coords.x) & (samplesInX - 1)) | ((int(coords.y) & (samplesInY - 1)) << samplesInXLog2); + uvec4 value = imageLoad(imgIn, ivec2(coords)); + imageStore(imgOut, ivec2(int(coords.x) >> samplesInXLog2, int(coords.y) >> samplesInYLog2), sampleIdx, value); +}"; + + private readonly OpenGLRenderer _renderer; + private readonly int[] _msToNonMSProgramHandles; + private readonly int[] _nonMSToMSProgramHandles; + + public TextureCopyMS(OpenGLRenderer renderer) + { + _renderer = renderer; + _msToNonMSProgramHandles = new int[5]; + _nonMSToMSProgramHandles = new int[5]; + } + + public void CopyMSToNonMS(ITextureInfo src, ITextureInfo dst, int srcLayer, int dstLayer, int depth) + { + TextureCreateInfo srcInfo = src.Info; + TextureCreateInfo dstInfo = dst.Info; + + int srcHandle = CreateViewIfNeeded(src); + int dstHandle = CreateViewIfNeeded(dst); + + int dstWidth = dstInfo.Width; + int dstHeight = dstInfo.Height; + + GL.UseProgram(GetMSToNonMSShader(srcInfo.BytesPerPixel)); + + for (int z = 0; z < depth; z++) + { + GL.BindImageTexture(0, srcHandle, 0, false, srcLayer + z, TextureAccess.ReadOnly, GetFormat(srcInfo.BytesPerPixel)); + GL.BindImageTexture(1, dstHandle, 0, false, dstLayer + z, TextureAccess.WriteOnly, GetFormat(dstInfo.BytesPerPixel)); + + GL.DispatchCompute((dstWidth + 31) / 32, (dstHeight + 31) / 32, 1); + } + + Pipeline pipeline = (Pipeline)_renderer.Pipeline; + + pipeline.RestoreProgram(); + pipeline.RestoreImages1And2(); + + DestroyViewIfNeeded(src, srcHandle); + DestroyViewIfNeeded(dst, dstHandle); + } + + public void CopyNonMSToMS(ITextureInfo src, ITextureInfo dst, int srcLayer, int dstLayer, int depth) + { + TextureCreateInfo srcInfo = src.Info; + TextureCreateInfo dstInfo = dst.Info; + + int srcHandle = CreateViewIfNeeded(src); + int dstHandle = CreateViewIfNeeded(dst); + + int srcWidth = srcInfo.Width; + int srcHeight = srcInfo.Height; + + GL.UseProgram(GetNonMSToMSShader(srcInfo.BytesPerPixel)); + + for (int z = 0; z < depth; z++) + { + GL.BindImageTexture(0, srcHandle, 0, false, srcLayer + z, TextureAccess.ReadOnly, GetFormat(srcInfo.BytesPerPixel)); + GL.BindImageTexture(1, dstHandle, 0, false, dstLayer + z, TextureAccess.WriteOnly, GetFormat(dstInfo.BytesPerPixel)); + + GL.DispatchCompute((srcWidth + 31) / 32, (srcHeight + 31) / 32, 1); + } + + Pipeline pipeline = (Pipeline)_renderer.Pipeline; + + pipeline.RestoreProgram(); + pipeline.RestoreImages1And2(); + + DestroyViewIfNeeded(src, srcHandle); + DestroyViewIfNeeded(dst, dstHandle); + } + + private static SizedInternalFormat GetFormat(int bytesPerPixel) + { + return bytesPerPixel switch + { + 1 => SizedInternalFormat.R8ui, + 2 => SizedInternalFormat.R16ui, + 4 => SizedInternalFormat.R32ui, + 8 => SizedInternalFormat.Rg32ui, + 16 => SizedInternalFormat.Rgba32ui, + _ => throw new ArgumentException($"Invalid bytes per pixel {bytesPerPixel}."), + }; + } + + private static int CreateViewIfNeeded(ITextureInfo texture) + { + // Binding sRGB textures as images doesn't work on NVIDIA, + // we need to create and bind a RGBA view for it to work. + if (texture.Info.Format == Format.R8G8B8A8Srgb) + { + int handle = GL.GenTexture(); + + GL.TextureView( + handle, + texture.Info.Target.Convert(), + texture.Storage.Handle, + PixelInternalFormat.Rgba8, + texture.FirstLevel, + 1, + texture.FirstLayer, + texture.Info.GetLayers()); + + return handle; + } + + return texture.Handle; + } + + private static void DestroyViewIfNeeded(ITextureInfo info, int handle) + { + if (info.Handle != handle) + { + GL.DeleteTexture(handle); + } + } + + private int GetMSToNonMSShader(int bytesPerPixel) + { + return GetShader(ComputeShaderMSToNonMS, _msToNonMSProgramHandles, bytesPerPixel); + } + + private int GetNonMSToMSShader(int bytesPerPixel) + { + return GetShader(ComputeShaderNonMSToMS, _nonMSToMSProgramHandles, bytesPerPixel); + } + + private static int GetShader(string code, int[] programHandles, int bytesPerPixel) + { + int index = BitOperations.Log2((uint)bytesPerPixel); + + if (programHandles[index] == 0) + { + int csHandle = GL.CreateShader(ShaderType.ComputeShader); + + string format = new[] { "r8ui", "r16ui", "r32ui", "rg32ui", "rgba32ui" }[index]; + + GL.ShaderSource(csHandle, code.Replace("$FORMAT$", format)); + GL.CompileShader(csHandle); + + int programHandle = GL.CreateProgram(); + + GL.AttachShader(programHandle, csHandle); + GL.LinkProgram(programHandle); + GL.DetachShader(programHandle, csHandle); + GL.DeleteShader(csHandle); + + GL.GetProgram(programHandle, GetProgramParameterName.LinkStatus, out int status); + + if (status == 0) + { + throw new Exception(GL.GetProgramInfoLog(programHandle)); + } + + programHandles[index] = programHandle; + } + + return programHandles[index]; + } + + public void Dispose() + { + for (int i = 0; i < _msToNonMSProgramHandles.Length; i++) + { + if (_msToNonMSProgramHandles[i] != 0) + { + GL.DeleteProgram(_msToNonMSProgramHandles[i]); + _msToNonMSProgramHandles[i] = 0; + } + } + + for (int i = 0; i < _nonMSToMSProgramHandles.Length; i++) + { + if (_nonMSToMSProgramHandles[i] != 0) + { + GL.DeleteProgram(_nonMSToMSProgramHandles[i]); + _nonMSToMSProgramHandles[i] = 0; + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs new file mode 100644 index 00000000..0ebafb04 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs @@ -0,0 +1,210 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + class TextureStorage : ITextureInfo + { + public ITextureInfo Storage => this; + public int Handle { get; private set; } + + public TextureCreateInfo Info { get; } + + private readonly OpenGLRenderer _renderer; + + private int _viewsCount; + + internal ITexture DefaultView { get; private set; } + + public TextureStorage(OpenGLRenderer renderer, TextureCreateInfo info) + { + _renderer = renderer; + Info = info; + + Handle = GL.GenTexture(); + + CreateImmutableStorage(); + } + + private void CreateImmutableStorage() + { + TextureTarget target = Info.Target.Convert(); + + GL.ActiveTexture(TextureUnit.Texture0); + + GL.BindTexture(target, Handle); + + FormatInfo format = FormatTable.GetFormatInfo(Info.Format); + + SizedInternalFormat internalFormat; + + if (format.IsCompressed) + { + internalFormat = (SizedInternalFormat)format.PixelFormat; + } + else + { + internalFormat = (SizedInternalFormat)format.PixelInternalFormat; + } + + int levels = Info.Levels; + + switch (Info.Target) + { + case Target.Texture1D: + GL.TexStorage1D( + TextureTarget1d.Texture1D, + levels, + internalFormat, + Info.Width); + break; + + case Target.Texture1DArray: + GL.TexStorage2D( + TextureTarget2d.Texture1DArray, + levels, + internalFormat, + Info.Width, + Info.Height); + break; + + case Target.Texture2D: + GL.TexStorage2D( + TextureTarget2d.Texture2D, + levels, + internalFormat, + Info.Width, + Info.Height); + break; + + case Target.Texture2DArray: + GL.TexStorage3D( + TextureTarget3d.Texture2DArray, + levels, + internalFormat, + Info.Width, + Info.Height, + Info.Depth); + break; + + case Target.Texture2DMultisample: + GL.TexStorage2DMultisample( + TextureTargetMultisample2d.Texture2DMultisample, + Info.Samples, + internalFormat, + Info.Width, + Info.Height, + true); + break; + + case Target.Texture2DMultisampleArray: + GL.TexStorage3DMultisample( + TextureTargetMultisample3d.Texture2DMultisampleArray, + Info.Samples, + internalFormat, + Info.Width, + Info.Height, + Info.Depth, + true); + break; + + case Target.Texture3D: + GL.TexStorage3D( + TextureTarget3d.Texture3D, + levels, + internalFormat, + Info.Width, + Info.Height, + Info.Depth); + break; + + case Target.Cubemap: + GL.TexStorage2D( + TextureTarget2d.TextureCubeMap, + levels, + internalFormat, + Info.Width, + Info.Height); + break; + + case Target.CubemapArray: + GL.TexStorage3D( + (TextureTarget3d)All.TextureCubeMapArray, + levels, + internalFormat, + Info.Width, + Info.Height, + Info.Depth); + break; + + default: + Logger.Debug?.Print(LogClass.Gpu, $"Invalid or unsupported texture target: {target}."); + break; + } + } + + public ITexture CreateDefaultView() + { + DefaultView = CreateView(Info, 0, 0); + + return DefaultView; + } + + public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + IncrementViewsCount(); + + return new TextureView(_renderer, this, info, firstLayer, firstLevel); + } + + private void IncrementViewsCount() + { + _viewsCount++; + } + + public void DecrementViewsCount() + { + // If we don't have any views, then the storage is now useless, delete it. + if (--_viewsCount == 0) + { + if (DefaultView == null) + { + Dispose(); + } + else + { + // If the default view still exists, we can put it into the resource pool. + Release(); + } + } + } + + ///

+ /// Release the TextureStorage to the resource pool without disposing its handle. + /// + public void Release() + { + _viewsCount = 1; // When we are used again, we will have the default view. + + _renderer.ResourcePool.AddTexture((TextureView)DefaultView); + } + + public void DeleteDefault() + { + DefaultView = null; + } + + public void Dispose() + { + DefaultView = null; + + if (Handle != 0) + { + GL.DeleteTexture(Handle); + + Handle = 0; + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs new file mode 100644 index 00000000..946eb755 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs @@ -0,0 +1,919 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using System; +using System.Buffers; +using System.Diagnostics; + +namespace Ryujinx.Graphics.OpenGL.Image +{ + class TextureView : TextureBase, ITexture, ITextureInfo + { + private readonly OpenGLRenderer _renderer; + + private readonly TextureStorage _parent; + + public ITextureInfo Storage => _parent; + + public int FirstLayer { get; private set; } + public int FirstLevel { get; private set; } + + public TextureView( + OpenGLRenderer renderer, + TextureStorage parent, + TextureCreateInfo info, + int firstLayer, + int firstLevel) : base(info) + { + _renderer = renderer; + _parent = parent; + + FirstLayer = firstLayer; + FirstLevel = firstLevel; + + CreateView(); + } + + private void CreateView() + { + TextureTarget target = Target.Convert(); + + FormatInfo format = FormatTable.GetFormatInfo(Info.Format); + + PixelInternalFormat pixelInternalFormat; + + if (format.IsCompressed) + { + pixelInternalFormat = (PixelInternalFormat)format.PixelFormat; + } + else + { + pixelInternalFormat = format.PixelInternalFormat; + } + + int levels = Info.Levels; + + GL.TextureView( + Handle, + target, + _parent.Handle, + pixelInternalFormat, + FirstLevel, + levels, + FirstLayer, + Info.GetLayers()); + + GL.ActiveTexture(TextureUnit.Texture0); + + GL.BindTexture(target, Handle); + + int[] swizzleRgba = new int[] + { + (int)Info.SwizzleR.Convert(), + (int)Info.SwizzleG.Convert(), + (int)Info.SwizzleB.Convert(), + (int)Info.SwizzleA.Convert(), + }; + + if (Info.Format == Format.A1B5G5R5Unorm) + { + int temp = swizzleRgba[0]; + int temp2 = swizzleRgba[1]; + swizzleRgba[0] = swizzleRgba[3]; + swizzleRgba[1] = swizzleRgba[2]; + swizzleRgba[2] = temp2; + swizzleRgba[3] = temp; + } + else if (Info.Format.IsBgr()) + { + // Swap B <-> R for BGRA formats, as OpenGL has no support for them + // and we need to manually swap the components on read/write on the GPU. + (swizzleRgba[2], swizzleRgba[0]) = (swizzleRgba[0], swizzleRgba[2]); + } + + GL.TexParameter(target, TextureParameterName.TextureSwizzleRgba, swizzleRgba); + + int maxLevel = levels - 1; + + if (maxLevel < 0) + { + maxLevel = 0; + } + + GL.TexParameter(target, TextureParameterName.TextureMaxLevel, maxLevel); + GL.TexParameter(target, TextureParameterName.DepthStencilTextureMode, (int)Info.DepthStencilMode.Convert()); + } + + public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + firstLayer += FirstLayer; + firstLevel += FirstLevel; + + return _parent.CreateView(info, firstLayer, firstLevel); + } + + public void CopyTo(ITexture destination, int firstLayer, int firstLevel) + { + TextureView destinationView = (TextureView)destination; + + 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 (dstIsMultisample && !srcIsMultisample) + { + int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer); + _renderer.TextureCopyMS.CopyNonMSToMS(this, destinationView, 0, firstLayer, layers); + } + else if (destinationView.Info.BytesPerPixel != Info.BytesPerPixel) + { + int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer); + int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel); + _renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, 0, firstLayer, 0, firstLevel, layers, levels); + } + else if (destinationView.Format.IsDepthOrStencil() != Format.IsDepthOrStencil()) + { + int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer); + int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel); + + for (int level = 0; level < levels; level++) + { + int srcWidth = Math.Max(1, Width >> level); + int srcHeight = Math.Max(1, Height >> level); + + int dstWidth = Math.Max(1, destinationView.Width >> (firstLevel + level)); + int dstHeight = Math.Max(1, destinationView.Height >> (firstLevel + level)); + + int minWidth = Math.Min(srcWidth, dstWidth); + int minHeight = Math.Min(srcHeight, dstHeight); + + for (int layer = 0; layer < layers; layer++) + { + _renderer.TextureCopy.PboCopy(this, destinationView, 0, firstLayer + layer, 0, firstLevel + level, minWidth, minHeight); + } + } + } + else + { + _renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel); + } + } + + public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel) + { + TextureView destinationView = (TextureView)destination; + + 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 (dstIsMultisample && !srcIsMultisample) + { + _renderer.TextureCopyMS.CopyNonMSToMS(this, destinationView, srcLayer, dstLayer, 1); + } + else if (destinationView.Info.BytesPerPixel != Info.BytesPerPixel) + { + _renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1); + } + else if (destinationView.Format.IsDepthOrStencil() != Format.IsDepthOrStencil()) + { + int minWidth = Math.Min(Width, destinationView.Width); + int minHeight = Math.Min(Height, destinationView.Height); + + _renderer.TextureCopy.PboCopy(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, minWidth, minHeight); + } + else + { + _renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1); + } + } + + 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(0, 0, Width, Height); + Extents2D dstRegion = new(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); + } + + public unsafe PinnedSpan GetData() + { + int size = 0; + int levels = Info.Levels; + + for (int level = 0; level < levels; level++) + { + size += Info.GetMipSize(level); + } + + ReadOnlySpan data; + + if (HwCapabilities.UsePersistentBufferForFlush) + { + data = _renderer.PersistentBuffers.Default.GetTextureData(this, size); + } + else + { + IntPtr target = _renderer.PersistentBuffers.Default.GetHostArray(size); + + WriteTo(target); + + data = new ReadOnlySpan(target.ToPointer(), size); + } + + if (Format == Format.S8UintD24Unorm) + { + data = FormatConverter.ConvertD24S8ToS8D24(data); + } + + return PinnedSpan.UnsafeFromSpan(data); + } + + public unsafe PinnedSpan GetData(int layer, int level) + { + int size = Info.GetMipSize(level); + + if (HwCapabilities.UsePersistentBufferForFlush) + { + return PinnedSpan.UnsafeFromSpan(_renderer.PersistentBuffers.Default.GetTextureData(this, size, layer, level)); + } + else + { + IntPtr target = _renderer.PersistentBuffers.Default.GetHostArray(size); + + int offset = WriteTo2D(target, layer, level); + + return new PinnedSpan((byte*)target.ToPointer() + offset, size); + } + } + + public void CopyTo(BufferRange range, int layer, int level, int stride) + { + if (stride != 0 && stride != BitUtils.AlignUp(Info.Width * Info.BytesPerPixel, 4)) + { + throw new NotSupportedException("Stride conversion for texture copy to buffer not supported."); + } + + GL.BindBuffer(BufferTarget.PixelPackBuffer, range.Handle.ToInt32()); + + FormatInfo format = FormatTable.GetFormatInfo(Info.Format); + if (format.PixelFormat == PixelFormat.DepthStencil) + { + throw new InvalidOperationException("DepthStencil copy to buffer is not supported for layer/level > 0."); + } + + int offset = WriteToPbo2D(range.Offset, layer, level); + + Debug.Assert(offset == 0); + + GL.BindBuffer(BufferTarget.PixelPackBuffer, 0); + } + + public void WriteToPbo(int offset, bool forceBgra) + { + WriteTo(IntPtr.Zero + offset, forceBgra); + } + + public int WriteToPbo2D(int offset, int layer, int level) + { + return WriteTo2D(IntPtr.Zero + offset, layer, level); + } + + private int WriteTo2D(IntPtr data, int layer, int level) + { + TextureTarget target = Target.Convert(); + + Bind(target, 0); + + FormatInfo format = FormatTable.GetFormatInfo(Info.Format); + + PixelFormat pixelFormat = format.PixelFormat; + PixelType pixelType = format.PixelType; + + if (target == TextureTarget.TextureCubeMap || target == TextureTarget.TextureCubeMapArray) + { + target = TextureTarget.TextureCubeMapPositiveX + (layer % 6); + } + + int mipSize = Info.GetMipSize2D(level); + + if (format.IsCompressed) + { + GL.GetCompressedTextureSubImage(Handle, level, 0, 0, layer, Math.Max(1, Info.Width >> level), Math.Max(1, Info.Height >> level), 1, mipSize, data); + } + else if (format.PixelFormat != PixelFormat.DepthStencil) + { + GL.GetTextureSubImage(Handle, level, 0, 0, layer, Math.Max(1, Info.Width >> level), Math.Max(1, Info.Height >> level), 1, pixelFormat, pixelType, mipSize, data); + } + else + { + GL.GetTexImage(target, level, pixelFormat, pixelType, data); + + // The GL function returns all layers. Must return the offset of the layer we're interested in. + return target switch + { + TextureTarget.TextureCubeMapArray => (layer / 6) * mipSize, + TextureTarget.Texture1DArray => layer * mipSize, + TextureTarget.Texture2DArray => layer * mipSize, + _ => 0, + }; + } + + return 0; + } + + private void WriteTo(IntPtr data, bool forceBgra = false) + { + TextureTarget target = Target.Convert(); + + Bind(target, 0); + + FormatInfo format = FormatTable.GetFormatInfo(Info.Format); + + PixelFormat pixelFormat = format.PixelFormat; + PixelType pixelType = format.PixelType; + + if (forceBgra) + { + if (pixelType == PixelType.UnsignedShort565) + { + pixelType = PixelType.UnsignedShort565Reversed; + } + else if (pixelType == PixelType.UnsignedShort565Reversed) + { + pixelType = PixelType.UnsignedShort565; + } + else + { + pixelFormat = PixelFormat.Bgra; + } + } + + int faces = 1; + + if (target == TextureTarget.TextureCubeMap) + { + target = TextureTarget.TextureCubeMapPositiveX; + + faces = 6; + } + + int levels = Info.Levels; + + for (int level = 0; level < levels; level++) + { + for (int face = 0; face < faces; face++) + { + int faceOffset = face * Info.GetMipSize2D(level); + + if (format.IsCompressed) + { + GL.GetCompressedTexImage(target + face, level, data + faceOffset); + } + else + { + GL.GetTexImage(target + face, level, pixelFormat, pixelType, data + faceOffset); + } + } + + data += Info.GetMipSize(level); + } + } + + public void SetData(IMemoryOwner data) + { + using (data = EnsureDataFormat(data)) + { + unsafe + { + var dataSpan = data.Memory.Span; + fixed (byte* ptr = dataSpan) + { + ReadFrom((IntPtr)ptr, dataSpan.Length); + } + } + } + } + + public void SetData(IMemoryOwner data, int layer, int level) + { + using (data = EnsureDataFormat(data)) + { + unsafe + { + fixed (byte* ptr = data.Memory.Span) + { + int width = Math.Max(Info.Width >> level, 1); + int height = Math.Max(Info.Height >> level, 1); + + ReadFrom2D((IntPtr)ptr, layer, level, 0, 0, width, height); + } + } + } + } + + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) + { + using (data = EnsureDataFormat(data)) + { + int wInBlocks = BitUtils.DivRoundUp(region.Width, Info.BlockWidth); + int hInBlocks = BitUtils.DivRoundUp(region.Height, Info.BlockHeight); + + unsafe + { + fixed (byte* ptr = data.Memory.Span) + { + ReadFrom2D( + (IntPtr)ptr, + layer, + level, + region.X, + region.Y, + region.Width, + region.Height, + BitUtils.AlignUp(wInBlocks * Info.BytesPerPixel, 4) * hInBlocks); + } + } + } + } + + public void ReadFromPbo(int offset, int size) + { + ReadFrom(IntPtr.Zero + offset, size); + } + + public void ReadFromPbo2D(int offset, int layer, int level, int width, int height) + { + ReadFrom2D(IntPtr.Zero + offset, layer, level, 0, 0, width, height); + } + + private void ReadFrom2D(IntPtr data, int layer, int level, int x, int y, int width, int height) + { + int mipSize = Info.GetMipSize2D(level); + + ReadFrom2D(data, layer, level, x, y, width, height, mipSize); + } + + private IMemoryOwner EnsureDataFormat(IMemoryOwner data) + { + if (Format == Format.S8UintD24Unorm) + { + using (data) + { + return FormatConverter.ConvertS8D24ToD24S8(data.Memory.Span); + } + } + + return data; + } + + private void ReadFrom2D(IntPtr data, int layer, int level, int x, int y, int width, int height, int mipSize) + { + TextureTarget target = Target.Convert(); + + Bind(target, 0); + + FormatInfo format = FormatTable.GetFormatInfo(Info.Format); + + switch (Target) + { + case Target.Texture1D: + if (format.IsCompressed) + { + GL.CompressedTexSubImage1D( + target, + level, + x, + width, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage1D( + target, + level, + x, + width, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Texture1DArray: + if (format.IsCompressed) + { + GL.CompressedTexSubImage2D( + target, + level, + x, + layer, + width, + 1, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage2D( + target, + level, + x, + layer, + width, + 1, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Texture2D: + if (format.IsCompressed) + { + GL.CompressedTexSubImage2D( + target, + level, + x, + y, + width, + height, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage2D( + target, + level, + x, + y, + width, + height, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Texture2DArray: + case Target.Texture3D: + case Target.CubemapArray: + if (format.IsCompressed) + { + GL.CompressedTexSubImage3D( + target, + level, + x, + y, + layer, + width, + height, + 1, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage3D( + target, + level, + x, + y, + layer, + width, + height, + 1, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Cubemap: + if (format.IsCompressed) + { + GL.CompressedTexSubImage2D( + TextureTarget.TextureCubeMapPositiveX + layer, + level, + x, + y, + width, + height, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage2D( + TextureTarget.TextureCubeMapPositiveX + layer, + level, + x, + y, + width, + height, + format.PixelFormat, + format.PixelType, + data); + } + break; + } + } + + private void ReadFrom(IntPtr data, int size) + { + TextureTarget target = Target.Convert(); + int baseLevel = 0; + + // glTexSubImage on cubemap views is broken on Intel, we have to use the storage instead. + if (Target == Target.Cubemap && HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows) + { + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(target, Storage.Handle); + baseLevel = FirstLevel; + } + else + { + Bind(target, 0); + } + + FormatInfo format = FormatTable.GetFormatInfo(Info.Format); + + int width = Info.Width; + int height = Info.Height; + int depth = Info.Depth; + int levels = Info.Levels; + + int offset = 0; + + for (int level = 0; level < levels; level++) + { + int mipSize = Info.GetMipSize(level); + + int endOffset = offset + mipSize; + + if ((uint)endOffset > (uint)size) + { + return; + } + + switch (Target) + { + case Target.Texture1D: + if (format.IsCompressed) + { + GL.CompressedTexSubImage1D( + target, + level, + 0, + width, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage1D( + target, + level, + 0, + width, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Texture1DArray: + case Target.Texture2D: + if (format.IsCompressed) + { + GL.CompressedTexSubImage2D( + target, + level, + 0, + 0, + width, + height, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage2D( + target, + level, + 0, + 0, + width, + height, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Texture2DArray: + case Target.Texture3D: + case Target.CubemapArray: + if (format.IsCompressed) + { + GL.CompressedTexSubImage3D( + target, + level, + 0, + 0, + 0, + width, + height, + depth, + format.PixelFormat, + mipSize, + data); + } + else + { + GL.TexSubImage3D( + target, + level, + 0, + 0, + 0, + width, + height, + depth, + format.PixelFormat, + format.PixelType, + data); + } + break; + + case Target.Cubemap: + int faceOffset = 0; + + for (int face = 0; face < 6; face++, faceOffset += mipSize / 6) + { + if (format.IsCompressed) + { + GL.CompressedTexSubImage2D( + TextureTarget.TextureCubeMapPositiveX + face, + baseLevel + level, + 0, + 0, + width, + height, + format.PixelFormat, + mipSize / 6, + data + faceOffset); + } + else + { + GL.TexSubImage2D( + TextureTarget.TextureCubeMapPositiveX + face, + baseLevel + level, + 0, + 0, + width, + height, + format.PixelFormat, + format.PixelType, + data + faceOffset); + } + } + break; + } + + data += mipSize; + offset += mipSize; + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + + if (Target == Target.Texture3D) + { + depth = Math.Max(1, depth >> 1); + } + } + } + + public void SetStorage(BufferRange buffer) + { + throw new NotSupportedException(); + } + + private void DisposeHandles() + { + if (Handle != 0) + { + GL.DeleteTexture(Handle); + + Handle = 0; + } + } + + /// + /// Release the view without necessarily disposing the parent if we are the default view. + /// This allows it to be added to the resource pool and reused later. + /// + public void Release() + { + bool hadHandle = Handle != 0; + + if (_parent.DefaultView != this) + { + DisposeHandles(); + } + + if (hadHandle) + { + _parent.DecrementViewsCount(); + } + } + + public void Dispose() + { + if (_parent.DefaultView == this) + { + // Remove the default view (us), so that the texture cannot be released to the cache. + _parent.DeleteDefault(); + } + + Release(); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs new file mode 100644 index 00000000..ba9cd45c --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -0,0 +1,335 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.OpenGL.Image; +using Ryujinx.Graphics.OpenGL.Queries; +using Ryujinx.Graphics.Shader.Translation; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + public sealed class OpenGLRenderer : IRenderer + { + private readonly Pipeline _pipeline; + + public IPipeline Pipeline => _pipeline; + + private readonly Counters _counters; + + private readonly Window _window; + + public IWindow Window => _window; + + private readonly TextureCopy _textureCopy; + private readonly TextureCopy _backgroundTextureCopy; + internal TextureCopy TextureCopy => BackgroundContextWorker.InBackground ? _backgroundTextureCopy : _textureCopy; + internal TextureCopyIncompatible TextureCopyIncompatible { get; } + internal TextureCopyMS TextureCopyMS { get; } + + private readonly Sync _sync; + + public event EventHandler ScreenCaptured; + + internal PersistentBuffers PersistentBuffers { get; } + + internal ResourcePool ResourcePool { get; } + + internal int BufferCount { get; private set; } + + public string GpuVendor { get; private set; } + public string GpuRenderer { get; private set; } + public string GpuVersion { get; private set; } + + public bool PreferThreading => true; + + public OpenGLRenderer() + { + _pipeline = new Pipeline(); + _counters = new Counters(); + _window = new Window(this); + _textureCopy = new TextureCopy(this); + _backgroundTextureCopy = new TextureCopy(this); + TextureCopyIncompatible = new TextureCopyIncompatible(this); + TextureCopyMS = new TextureCopyMS(this); + _sync = new Sync(); + PersistentBuffers = new PersistentBuffers(); + ResourcePool = new ResourcePool(); + } + + public BufferHandle CreateBuffer(int size, GAL.BufferAccess access) + { + BufferCount++; + + var memType = access & GAL.BufferAccess.MemoryTypeMask; + + if (memType == GAL.BufferAccess.HostMemory) + { + BufferHandle handle = Buffer.CreatePersistent(size); + + PersistentBuffers.Map(handle, size); + + return handle; + } + else + { + return Buffer.Create(size); + } + } + + public BufferHandle CreateBuffer(nint pointer, int size) + { + throw new NotSupportedException(); + } + + public BufferHandle CreateBufferSparse(ReadOnlySpan storageBuffers) + { + throw new NotSupportedException(); + } + + public IImageArray CreateImageArray(int size, bool isBuffer) + { + return new ImageArray(size); + } + + public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info) + { + return new Program(shaders, info.FragmentOutputMap); + } + + public ISampler CreateSampler(SamplerCreateInfo info) + { + return new Sampler(info); + } + + public ITexture CreateTexture(TextureCreateInfo info) + { + if (info.Target == Target.TextureBuffer) + { + return new TextureBuffer(this, info); + } + else + { + return ResourcePool.GetTextureOrNull(info) ?? new TextureStorage(this, info).CreateDefaultView(); + } + } + + public ITextureArray CreateTextureArray(int size, bool isBuffer) + { + return new TextureArray(size); + } + + public void DeleteBuffer(BufferHandle buffer) + { + PersistentBuffers.Unmap(buffer); + + Buffer.Delete(buffer); + } + + public HardwareInfo GetHardwareInfo() + { + return new HardwareInfo(GpuVendor, GpuRenderer, GpuVendor); // OpenGL does not provide a driver name, vendor name is closest analogue. + } + + public PinnedSpan GetBufferData(BufferHandle buffer, int offset, int size) + { + return Buffer.GetData(this, buffer, offset, size); + } + + public Capabilities GetCapabilities() + { + bool intelWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows; + bool intelUnix = HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelUnix; + bool amdWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows; + + return new Capabilities( + api: TargetApi.OpenGL, + vendorName: GpuVendor, + memoryType: SystemMemoryType.BackendManaged, + hasFrontFacingBug: intelWindows, + hasVectorIndexingBug: amdWindows, + needsFragmentOutputSpecialization: false, + reduceShaderPrecision: false, + supportsAstcCompression: HwCapabilities.SupportsAstcCompression, + supportsBc123Compression: HwCapabilities.SupportsTextureCompressionS3tc, + supportsBc45Compression: HwCapabilities.SupportsTextureCompressionRgtc, + supportsBc67Compression: true, // Should check BPTC extension, but for some reason NVIDIA is not exposing the extension. + supportsEtc2Compression: true, + supports3DTextureCompression: false, + supportsBgraFormat: false, + supportsR4G4Format: false, + supportsR4G4B4A4Format: true, + supportsScaledVertexFormats: true, + supportsSnormBufferTextureFormat: false, + supports5BitComponentFormat: true, + supportsSparseBuffer: false, + supportsBlendEquationAdvanced: HwCapabilities.SupportsBlendEquationAdvanced, + supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock, + supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering, + supportsGeometryShader: true, + supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough, + supportsTransformFeedback: true, + supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted, + supportsLayerVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray, + supportsMismatchingViewFormat: HwCapabilities.SupportsMismatchingViewFormat, + supportsCubemapView: true, + supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset, + supportsQuads: HwCapabilities.SupportsQuads, + supportsSeparateSampler: false, + supportsShaderBallot: HwCapabilities.SupportsShaderBallot, + supportsShaderBarrierDivergence: !(intelWindows || intelUnix), + supportsShaderFloat64: true, + supportsTextureGatherOffsets: true, + supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod, + supportsVertexStoreAndAtomics: true, + supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray, + supportsViewportMask: HwCapabilities.SupportsViewportArray2, + supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle, + supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters, + supportsDepthClipControl: true, + uniformBufferSetIndex: 0, + storageBufferSetIndex: 1, + textureSetIndex: 2, + imageSetIndex: 3, + extraSetBaseIndex: 0, + maximumExtraSets: 0, + maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver? + maximumStorageBuffersPerStage: 16, + maximumTexturesPerStage: 32, + maximumImagesPerStage: 8, + maximumComputeSharedMemorySize: HwCapabilities.MaximumComputeSharedMemorySize, + maximumSupportedAnisotropy: HwCapabilities.MaximumSupportedAnisotropy, + shaderSubgroupSize: Constants.MaxSubgroupSize, + storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment, + textureBufferOffsetAlignment: HwCapabilities.TextureBufferOffsetAlignment, + gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0); // Precision is 8 for these vendors on Vulkan. + } + + public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan data) + { + Buffer.SetData(buffer, offset, data); + } + + public void UpdateCounters() + { + _counters.Update(); + } + + public void PreFrame() + { + _sync.Cleanup(); + ResourcePool.Tick(); + } + + public ICounterEvent ReportCounter(CounterType type, EventHandler resultHandler, float divisor, bool hostReserved) + { + return _counters.QueueReport(type, resultHandler, divisor, _pipeline.DrawCount, hostReserved); + } + + public void Initialize(GraphicsDebugLevel glLogLevel) + { + Debugger.Initialize(glLogLevel); + + PrintGpuInformation(); + + if (HwCapabilities.SupportsParallelShaderCompile) + { + GL.Arb.MaxShaderCompilerThreads(Math.Min(Environment.ProcessorCount, 8)); + } + + _counters.Initialize(); + + // This is required to disable [0, 1] clamping for SNorm outputs on compatibility profiles. + // This call is expected to fail if we're running with a core profile, + // as this clamp target was deprecated, but that's fine as a core profile + // should already have the desired behaviour were outputs are not clamped. + GL.ClampColor(ClampColorTarget.ClampFragmentColor, ClampColorMode.False); + } + + private void PrintGpuInformation() + { + GpuVendor = GL.GetString(StringName.Vendor); + GpuRenderer = GL.GetString(StringName.Renderer); + GpuVersion = GL.GetString(StringName.Version); + + Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})"); + } + + public void ResetCounter(CounterType type) + { + _counters.QueueReset(type); + } + + public void BackgroundContextAction(Action action, bool alwaysBackground = false) + { + // alwaysBackground is ignored, since we cannot switch from the current context. + + if (_window.BackgroundContext.HasContext()) + { + action(); // We have a context already - use that (assuming it is the main one). + } + else + { + _window.BackgroundContext.Invoke(action); + } + } + + public void InitializeBackgroundContext(IOpenGLContext baseContext) + { + _window.InitializeBackgroundContext(baseContext); + } + + public void Dispose() + { + _textureCopy.Dispose(); + _backgroundTextureCopy.Dispose(); + TextureCopyMS.Dispose(); + PersistentBuffers.Dispose(); + ResourcePool.Dispose(); + _pipeline.Dispose(); + _window.Dispose(); + _counters.Dispose(); + _sync.Dispose(); + } + + public IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info) + { + return new Program(programBinary, hasFragmentShader, info.FragmentOutputMap); + } + + public void CreateSync(ulong id, bool strict) + { + _sync.Create(id); + } + + public void WaitSync(ulong id) + { + _sync.Wait(id); + } + + public ulong GetCurrentSync() + { + return _sync.GetCurrent(); + } + + public void SetInterruptAction(Action interruptAction) + { + // Currently no need for an interrupt action. + } + + public void Screenshot() + { + _window.ScreenCaptureRequested = true; + } + + public void OnScreenCaptured(ScreenCaptureImageInfo bitmap) + { + ScreenCaptured?.Invoke(this, bitmap); + } + + public bool PrepareHostMapping(nint address, ulong size) + { + return false; + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs b/src/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs new file mode 100644 index 00000000..ebfe3ad6 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs @@ -0,0 +1,163 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.OpenGL.Image; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.OpenGL +{ + class PersistentBuffers : IDisposable + { + private readonly PersistentBuffer _main = new(); + private readonly PersistentBuffer _background = new(); + + private readonly Dictionary _maps = new(); + + public PersistentBuffer Default => BackgroundContextWorker.InBackground ? _background : _main; + + public void Dispose() + { + _main?.Dispose(); + _background?.Dispose(); + } + + public void Map(BufferHandle handle, int size) + { + GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle.ToInt32()); + IntPtr ptr = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, IntPtr.Zero, size, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit); + + _maps[handle] = ptr; + } + + public void Unmap(BufferHandle handle) + { + if (_maps.ContainsKey(handle)) + { + GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle.ToInt32()); + GL.UnmapBuffer(BufferTarget.CopyWriteBuffer); + + _maps.Remove(handle); + } + } + + public bool TryGet(BufferHandle handle, out IntPtr ptr) + { + return _maps.TryGetValue(handle, out ptr); + } + } + + class PersistentBuffer : IDisposable + { + private IntPtr _bufferMap; + private int _copyBufferHandle; + private int _copyBufferSize; + + private byte[] _data; + private IntPtr _dataMap; + + private void EnsureBuffer(int requiredSize) + { + if (_copyBufferSize < requiredSize && _copyBufferHandle != 0) + { + GL.DeleteBuffer(_copyBufferHandle); + + _copyBufferHandle = 0; + } + + if (_copyBufferHandle == 0) + { + _copyBufferHandle = GL.GenBuffer(); + _copyBufferSize = requiredSize; + + GL.BindBuffer(BufferTarget.CopyWriteBuffer, _copyBufferHandle); + GL.BufferStorage(BufferTarget.CopyWriteBuffer, requiredSize, IntPtr.Zero, BufferStorageFlags.MapReadBit | BufferStorageFlags.MapPersistentBit); + + _bufferMap = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, IntPtr.Zero, requiredSize, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit); + } + } + + public unsafe IntPtr GetHostArray(int requiredSize) + { + if (_data == null || _data.Length < requiredSize) + { + _data = GC.AllocateUninitializedArray(requiredSize, true); + + _dataMap = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(_data)); + } + + return _dataMap; + } + + private static void Sync() + { + GL.MemoryBarrier(MemoryBarrierFlags.ClientMappedBufferBarrierBit); + + IntPtr sync = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None); + WaitSyncStatus syncResult = GL.ClientWaitSync(sync, ClientWaitSyncFlags.SyncFlushCommandsBit, 1000000000); + + if (syncResult == WaitSyncStatus.TimeoutExpired) + { + Logger.Error?.PrintMsg(LogClass.Gpu, $"Failed to sync persistent buffer state within 1000ms. Continuing..."); + } + + GL.DeleteSync(sync); + } + + public unsafe ReadOnlySpan GetTextureData(TextureView view, int size) + { + EnsureBuffer(size); + + GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyBufferHandle); + + view.WriteToPbo(0, false); + + GL.BindBuffer(BufferTarget.PixelPackBuffer, 0); + + Sync(); + + return new ReadOnlySpan(_bufferMap.ToPointer(), size); + } + + public unsafe ReadOnlySpan GetTextureData(TextureView view, int size, int layer, int level) + { + EnsureBuffer(size); + + GL.BindBuffer(BufferTarget.PixelPackBuffer, _copyBufferHandle); + + int offset = view.WriteToPbo2D(0, layer, level); + + GL.BindBuffer(BufferTarget.PixelPackBuffer, 0); + + Sync(); + + return new ReadOnlySpan(_bufferMap.ToPointer(), size)[offset..]; + } + + public unsafe ReadOnlySpan GetBufferData(BufferHandle buffer, int offset, int size) + { + EnsureBuffer(size); + + GL.BindBuffer(BufferTarget.CopyReadBuffer, buffer.ToInt32()); + GL.BindBuffer(BufferTarget.CopyWriteBuffer, _copyBufferHandle); + + GL.CopyBufferSubData(BufferTarget.CopyReadBuffer, BufferTarget.CopyWriteBuffer, (IntPtr)offset, IntPtr.Zero, size); + + GL.BindBuffer(BufferTarget.CopyWriteBuffer, 0); + + Sync(); + + return new ReadOnlySpan(_bufferMap.ToPointer(), size); + } + + public void Dispose() + { + if (_copyBufferHandle != 0) + { + GL.DeleteBuffer(_copyBufferHandle); + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs new file mode 100644 index 00000000..54f6b3f7 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -0,0 +1,1706 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.OpenGL.Image; +using Ryujinx.Graphics.OpenGL.Queries; +using Ryujinx.Graphics.Shader; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class Pipeline : IPipeline, IDisposable + { + private const int SavedImages = 2; + + private readonly DrawTextureEmulation _drawTexture; + + internal ulong DrawCount { get; private set; } + + private Program _program; + + private bool _rasterizerDiscard; + + private VertexArray _vertexArray; + private Framebuffer _framebuffer; + + private IntPtr _indexBaseOffset; + + private DrawElementsType _elementsType; + + private PrimitiveType _primitiveType; + + private int _stencilFrontMask; + private bool _depthMask; + private bool _depthTestEnable; + private bool _stencilTestEnable; + private bool _cullEnable; + + private float[] _viewportArray = Array.Empty(); + private double[] _depthRangeArray = Array.Empty(); + + private int _boundDrawFramebuffer; + private int _boundReadFramebuffer; + + private CounterQueueEvent _activeConditionalRender; + + private readonly Vector4[] _fpIsBgra = new Vector4[SupportBuffer.FragmentIsBgraCount]; + + private readonly (TextureBase, Format)[] _images; + private TextureBase _unit0Texture; + private Sampler _unit0Sampler; + + private FrontFaceDirection _frontFace; + private ClipOrigin _clipOrigin; + private ClipDepthMode _clipDepthMode; + + private uint _fragmentOutputMap; + private uint _componentMasks; + private uint _currentComponentMasks; + private bool _advancedBlendEnable; + + private uint _scissorEnables; + + private bool _tfEnabled; + private TransformFeedbackPrimitiveType _tfTopology; + + private readonly BufferHandle[] _tfbs; + private readonly BufferRange[] _tfbTargets; + + private ColorF _blendConstant; + + internal Pipeline() + { + _drawTexture = new DrawTextureEmulation(); + _rasterizerDiscard = false; + _clipOrigin = ClipOrigin.LowerLeft; + _clipDepthMode = ClipDepthMode.NegativeOneToOne; + + _fragmentOutputMap = uint.MaxValue; + _componentMasks = uint.MaxValue; + + _images = new (TextureBase, Format)[SavedImages]; + + _tfbs = new BufferHandle[Constants.MaxTransformFeedbackBuffers]; + _tfbTargets = new BufferRange[Constants.MaxTransformFeedbackBuffers]; + } + + public void Barrier() + { + GL.MemoryBarrier(MemoryBarrierFlags.AllBarrierBits); + } + + public void BeginTransformFeedback(PrimitiveTopology topology) + { + GL.BeginTransformFeedback(_tfTopology = topology.ConvertToTfType()); + _tfEnabled = true; + } + + public void ClearBuffer(BufferHandle destination, int offset, int size, uint value) + { + Buffer.Clear(destination, offset, size, value); + } + + public void ClearRenderTargetColor(int index, int layer, int layerCount, uint componentMask, ColorF color) + { + EnsureFramebuffer(); + + GL.ColorMask( + index, + (componentMask & 1) != 0, + (componentMask & 2) != 0, + (componentMask & 4) != 0, + (componentMask & 8) != 0); + + float[] colors = new float[] { color.Red, color.Green, color.Blue, color.Alpha }; + + if (layer != 0 || layerCount != _framebuffer.GetColorLayerCount(index)) + { + for (int l = layer; l < layer + layerCount; l++) + { + _framebuffer.AttachColorLayerForClear(index, l); + + GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors); + } + + _framebuffer.DetachColorLayerForClear(index); + } + else + { + GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Color, index, colors); + } + + RestoreComponentMask(index); + } + + public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask) + { + EnsureFramebuffer(); + + bool stencilMaskChanged = + stencilMask != 0 && + stencilMask != _stencilFrontMask; + + bool depthMaskChanged = depthMask && depthMask != _depthMask; + + if (stencilMaskChanged) + { + GL.StencilMaskSeparate(StencilFace.Front, stencilMask); + } + + if (depthMaskChanged) + { + GL.DepthMask(depthMask); + } + + if (layer != 0 || layerCount != _framebuffer.GetDepthStencilLayerCount()) + { + for (int l = layer; l < layer + layerCount; l++) + { + _framebuffer.AttachDepthStencilLayerForClear(l); + + ClearDepthStencil(depthValue, depthMask, stencilValue, stencilMask); + } + + _framebuffer.DetachDepthStencilLayerForClear(); + } + else + { + ClearDepthStencil(depthValue, depthMask, stencilValue, stencilMask); + } + + if (stencilMaskChanged) + { + GL.StencilMaskSeparate(StencilFace.Front, _stencilFrontMask); + } + + if (depthMaskChanged) + { + GL.DepthMask(_depthMask); + } + } + + private static void ClearDepthStencil(float depthValue, bool depthMask, int stencilValue, int stencilMask) + { + if (depthMask && stencilMask != 0) + { + GL.ClearBuffer(ClearBufferCombined.DepthStencil, 0, depthValue, stencilValue); + } + else if (depthMask) + { + GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Depth, 0, ref depthValue); + } + else if (stencilMask != 0) + { + GL.ClearBuffer(OpenTK.Graphics.OpenGL.ClearBuffer.Stencil, 0, ref stencilValue); + } + } + + public void CommandBufferBarrier() + { + GL.MemoryBarrier(MemoryBarrierFlags.CommandBarrierBit); + } + + public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size) + { + Buffer.Copy(source, destination, srcOffset, dstOffset, size); + } + + public void DispatchCompute(int groupsX, int groupsY, int groupsZ) + { + if (!_program.IsLinked) + { + Logger.Debug?.Print(LogClass.Gpu, "Dispatch error, shader not linked."); + return; + } + + PrepareForDispatch(); + + GL.DispatchCompute(groupsX, groupsY, groupsZ); + } + + public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance) + { + if (!_program.IsLinked) + { + Logger.Debug?.Print(LogClass.Gpu, "Draw error, shader not linked."); + return; + } + + PreDraw(vertexCount); + + if (_primitiveType == PrimitiveType.Quads && !HwCapabilities.SupportsQuads) + { + DrawQuadsImpl(vertexCount, instanceCount, firstVertex, firstInstance); + } + else if (_primitiveType == PrimitiveType.QuadStrip && !HwCapabilities.SupportsQuads) + { + DrawQuadStripImpl(vertexCount, instanceCount, firstVertex, firstInstance); + } + else + { + DrawImpl(vertexCount, instanceCount, firstVertex, firstInstance); + } + + PostDraw(); + } + + private static void DrawQuadsImpl( + int vertexCount, + int instanceCount, + int firstVertex, + int firstInstance) + { + // TODO: Instanced rendering. + int quadsCount = vertexCount / 4; + + int[] firsts = new int[quadsCount]; + int[] counts = new int[quadsCount]; + + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + firsts[quadIndex] = firstVertex + quadIndex * 4; + counts[quadIndex] = 4; + } + + GL.MultiDrawArrays( + PrimitiveType.TriangleFan, + firsts, + counts, + quadsCount); + } + + private static void DrawQuadStripImpl( + int vertexCount, + int instanceCount, + int firstVertex, + int firstInstance) + { + int quadsCount = (vertexCount - 2) / 2; + + if (firstInstance != 0 || instanceCount != 1) + { + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + GL.DrawArraysInstancedBaseInstance(PrimitiveType.TriangleFan, firstVertex + quadIndex * 2, 4, instanceCount, firstInstance); + } + } + else + { + int[] firsts = new int[quadsCount]; + int[] counts = new int[quadsCount]; + + firsts[0] = firstVertex; + counts[0] = 4; + + for (int quadIndex = 1; quadIndex < quadsCount; quadIndex++) + { + firsts[quadIndex] = firstVertex + quadIndex * 2; + counts[quadIndex] = 4; + } + + GL.MultiDrawArrays( + PrimitiveType.TriangleFan, + firsts, + counts, + quadsCount); + } + } + + private void DrawImpl( + int vertexCount, + int instanceCount, + int firstVertex, + int firstInstance) + { + if (firstInstance == 0 && instanceCount == 1) + { + GL.DrawArrays(_primitiveType, firstVertex, vertexCount); + } + else if (firstInstance == 0) + { + GL.DrawArraysInstanced(_primitiveType, firstVertex, vertexCount, instanceCount); + } + else + { + GL.DrawArraysInstancedBaseInstance( + _primitiveType, + firstVertex, + vertexCount, + instanceCount, + firstInstance); + } + } + + public void DrawIndexed( + int indexCount, + int instanceCount, + int firstIndex, + int firstVertex, + int firstInstance) + { + if (!_program.IsLinked) + { + Logger.Debug?.Print(LogClass.Gpu, "Draw error, shader not linked."); + return; + } + + PreDrawVbUnbounded(); + + int indexElemSize = 1; + + switch (_elementsType) + { + case DrawElementsType.UnsignedShort: + indexElemSize = 2; + break; + case DrawElementsType.UnsignedInt: + indexElemSize = 4; + break; + } + + IntPtr indexBaseOffset = _indexBaseOffset + firstIndex * indexElemSize; + + if (_primitiveType == PrimitiveType.Quads && !HwCapabilities.SupportsQuads) + { + DrawQuadsIndexedImpl( + indexCount, + instanceCount, + indexBaseOffset, + indexElemSize, + firstVertex, + firstInstance); + } + else if (_primitiveType == PrimitiveType.QuadStrip && !HwCapabilities.SupportsQuads) + { + DrawQuadStripIndexedImpl( + indexCount, + instanceCount, + indexBaseOffset, + indexElemSize, + firstVertex, + firstInstance); + } + else + { + DrawIndexedImpl( + indexCount, + instanceCount, + indexBaseOffset, + firstVertex, + firstInstance); + } + + PostDraw(); + } + + private void DrawQuadsIndexedImpl( + int indexCount, + int instanceCount, + IntPtr indexBaseOffset, + int indexElemSize, + int firstVertex, + int firstInstance) + { + int quadsCount = indexCount / 4; + + if (firstInstance != 0 || instanceCount != 1) + { + if (firstVertex != 0 && firstInstance != 0) + { + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + GL.DrawElementsInstancedBaseVertexBaseInstance( + PrimitiveType.TriangleFan, + 4, + _elementsType, + indexBaseOffset + quadIndex * 4 * indexElemSize, + instanceCount, + firstVertex, + firstInstance); + } + } + else if (firstInstance != 0) + { + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + GL.DrawElementsInstancedBaseInstance( + PrimitiveType.TriangleFan, + 4, + _elementsType, + indexBaseOffset + quadIndex * 4 * indexElemSize, + instanceCount, + firstInstance); + } + } + else + { + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + GL.DrawElementsInstanced( + PrimitiveType.TriangleFan, + 4, + _elementsType, + indexBaseOffset + quadIndex * 4 * indexElemSize, + instanceCount); + } + } + } + else + { + IntPtr[] indices = new IntPtr[quadsCount]; + + int[] counts = new int[quadsCount]; + + int[] baseVertices = new int[quadsCount]; + + for (int quadIndex = 0; quadIndex < quadsCount; quadIndex++) + { + indices[quadIndex] = indexBaseOffset + quadIndex * 4 * indexElemSize; + + counts[quadIndex] = 4; + + baseVertices[quadIndex] = firstVertex; + } + + GL.MultiDrawElementsBaseVertex( + PrimitiveType.TriangleFan, + counts, + _elementsType, + indices, + quadsCount, + baseVertices); + } + } + + private void DrawQuadStripIndexedImpl( + int indexCount, + int instanceCount, + IntPtr indexBaseOffset, + int indexElemSize, + int firstVertex, + int firstInstance) + { + // TODO: Instanced rendering. + int quadsCount = (indexCount - 2) / 2; + + IntPtr[] indices = new IntPtr[quadsCount]; + + int[] counts = new int[quadsCount]; + + int[] baseVertices = new int[quadsCount]; + + indices[0] = indexBaseOffset; + + counts[0] = 4; + + baseVertices[0] = firstVertex; + + for (int quadIndex = 1; quadIndex < quadsCount; quadIndex++) + { + indices[quadIndex] = indexBaseOffset + quadIndex * 2 * indexElemSize; + + counts[quadIndex] = 4; + + baseVertices[quadIndex] = firstVertex; + } + + GL.MultiDrawElementsBaseVertex( + PrimitiveType.TriangleFan, + counts, + _elementsType, + indices, + quadsCount, + baseVertices); + } + + private void DrawIndexedImpl( + int indexCount, + int instanceCount, + IntPtr indexBaseOffset, + int firstVertex, + int firstInstance) + { + if (firstInstance == 0 && firstVertex == 0 && instanceCount == 1) + { + GL.DrawElements(_primitiveType, indexCount, _elementsType, indexBaseOffset); + } + else if (firstInstance == 0 && instanceCount == 1) + { + GL.DrawElementsBaseVertex( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + firstVertex); + } + else if (firstInstance == 0 && firstVertex == 0) + { + GL.DrawElementsInstanced( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount); + } + else if (firstInstance == 0) + { + GL.DrawElementsInstancedBaseVertex( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount, + firstVertex); + } + else if (firstVertex == 0) + { + GL.DrawElementsInstancedBaseInstance( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount, + firstInstance); + } + else + { + GL.DrawElementsInstancedBaseVertexBaseInstance( + _primitiveType, + indexCount, + _elementsType, + indexBaseOffset, + instanceCount, + firstVertex, + firstInstance); + } + } + + public void DrawIndexedIndirect(BufferRange indirectBuffer) + { + if (!_program.IsLinked) + { + Logger.Debug?.Print(LogClass.Gpu, "Draw error, shader not linked."); + return; + } + + PreDrawVbUnbounded(); + + _vertexArray.SetRangeOfIndexBuffer(); + + GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle.ToInt32()); + + GL.DrawElementsIndirect(_primitiveType, _elementsType, (IntPtr)indirectBuffer.Offset); + + _vertexArray.RestoreIndexBuffer(); + + PostDraw(); + } + + public void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + if (!_program.IsLinked) + { + Logger.Debug?.Print(LogClass.Gpu, "Draw error, shader not linked."); + return; + } + + PreDrawVbUnbounded(); + + _vertexArray.SetRangeOfIndexBuffer(); + + GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle.ToInt32()); + GL.BindBuffer((BufferTarget)All.ParameterBuffer, parameterBuffer.Handle.ToInt32()); + + GL.MultiDrawElementsIndirectCount( + _primitiveType, + (All)_elementsType, + (IntPtr)indirectBuffer.Offset, + (IntPtr)parameterBuffer.Offset, + maxDrawCount, + stride); + + _vertexArray.RestoreIndexBuffer(); + + PostDraw(); + } + + public void DrawIndirect(BufferRange indirectBuffer) + { + if (!_program.IsLinked) + { + Logger.Debug?.Print(LogClass.Gpu, "Draw error, shader not linked."); + return; + } + + PreDrawVbUnbounded(); + + GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle.ToInt32()); + + GL.DrawArraysIndirect(_primitiveType, (IntPtr)indirectBuffer.Offset); + + PostDraw(); + } + + public void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + if (!_program.IsLinked) + { + Logger.Debug?.Print(LogClass.Gpu, "Draw error, shader not linked."); + return; + } + + PreDrawVbUnbounded(); + + GL.BindBuffer((BufferTarget)All.DrawIndirectBuffer, indirectBuffer.Handle.ToInt32()); + GL.BindBuffer((BufferTarget)All.ParameterBuffer, parameterBuffer.Handle.ToInt32()); + + GL.MultiDrawArraysIndirectCount( + _primitiveType, + (IntPtr)indirectBuffer.Offset, + (IntPtr)parameterBuffer.Offset, + maxDrawCount, + stride); + + PostDraw(); + } + + public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion) + { + if (texture is TextureView view && sampler is Sampler samp) + { + if (HwCapabilities.SupportsDrawTexture) + { + GL.NV.DrawTexture( + view.Handle, + samp.Handle, + dstRegion.X1, + dstRegion.Y1, + dstRegion.X2, + dstRegion.Y2, + 0, + srcRegion.X1 / view.Width, + srcRegion.Y1 / view.Height, + srcRegion.X2 / view.Width, + srcRegion.Y2 / view.Height); + } + else + { + static void Disable(EnableCap cap, bool enabled) + { + if (enabled) + { + GL.Disable(cap); + } + } + + static void Enable(EnableCap cap, bool enabled) + { + if (enabled) + { + GL.Enable(cap); + } + } + + Disable(EnableCap.CullFace, _cullEnable); + Disable(EnableCap.StencilTest, _stencilTestEnable); + Disable(EnableCap.DepthTest, _depthTestEnable); + + if (_depthMask) + { + GL.DepthMask(false); + } + + if (_tfEnabled) + { + GL.EndTransformFeedback(); + } + + GL.ClipControl(ClipOrigin.UpperLeft, ClipDepthMode.NegativeOneToOne); + + _drawTexture.Draw( + view, + samp, + dstRegion.X1, + dstRegion.Y1, + dstRegion.X2, + dstRegion.Y2, + srcRegion.X1 / view.Width, + srcRegion.Y1 / view.Height, + srcRegion.X2 / view.Width, + srcRegion.Y2 / view.Height); + + _program?.Bind(); + _unit0Sampler?.Bind(0); + + RestoreViewport0(); + + Enable(EnableCap.CullFace, _cullEnable); + Enable(EnableCap.StencilTest, _stencilTestEnable); + Enable(EnableCap.DepthTest, _depthTestEnable); + + if (_depthMask) + { + GL.DepthMask(true); + } + + if (_tfEnabled) + { + GL.BeginTransformFeedback(_tfTopology); + } + + RestoreClipControl(); + } + } + } + + public void EndTransformFeedback() + { + GL.EndTransformFeedback(); + _tfEnabled = false; + } + + public void SetAlphaTest(bool enable, float reference, CompareOp op) + { + if (!enable) + { + GL.Disable(EnableCap.AlphaTest); + return; + } + + GL.AlphaFunc((AlphaFunction)op.Convert(), reference); + GL.Enable(EnableCap.AlphaTest); + } + + public void SetBlendState(AdvancedBlendDescriptor blend) + { + if (HwCapabilities.SupportsBlendEquationAdvanced) + { + GL.BlendEquation((BlendEquationMode)blend.Op.Convert()); + GL.NV.BlendParameter(NvBlendEquationAdvanced.BlendOverlapNv, (int)blend.Overlap.Convert()); + GL.NV.BlendParameter(NvBlendEquationAdvanced.BlendPremultipliedSrcNv, blend.SrcPreMultiplied ? 1 : 0); + GL.Enable(EnableCap.Blend); + _advancedBlendEnable = true; + } + } + + public void SetBlendState(int index, BlendDescriptor blend) + { + if (_advancedBlendEnable) + { + GL.Disable(EnableCap.Blend); + _advancedBlendEnable = false; + } + + if (!blend.Enable) + { + GL.Disable(IndexedEnableCap.Blend, index); + return; + } + + GL.BlendEquationSeparate( + index, + blend.ColorOp.Convert(), + blend.AlphaOp.Convert()); + + GL.BlendFuncSeparate( + index, + (BlendingFactorSrc)blend.ColorSrcFactor.Convert(), + (BlendingFactorDest)blend.ColorDstFactor.Convert(), + (BlendingFactorSrc)blend.AlphaSrcFactor.Convert(), + (BlendingFactorDest)blend.AlphaDstFactor.Convert()); + + EnsureFramebuffer(); + + _framebuffer.SetDualSourceBlend( + blend.ColorSrcFactor.IsDualSource() || + blend.ColorDstFactor.IsDualSource() || + blend.AlphaSrcFactor.IsDualSource() || + blend.AlphaDstFactor.IsDualSource()); + + if (_blendConstant != blend.BlendConstant) + { + _blendConstant = blend.BlendConstant; + + GL.BlendColor( + blend.BlendConstant.Red, + blend.BlendConstant.Green, + blend.BlendConstant.Blue, + blend.BlendConstant.Alpha); + } + + GL.Enable(IndexedEnableCap.Blend, index); + } + + public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp) + { + if ((enables & PolygonModeMask.Point) != 0) + { + GL.Enable(EnableCap.PolygonOffsetPoint); + } + else + { + GL.Disable(EnableCap.PolygonOffsetPoint); + } + + if ((enables & PolygonModeMask.Line) != 0) + { + GL.Enable(EnableCap.PolygonOffsetLine); + } + else + { + GL.Disable(EnableCap.PolygonOffsetLine); + } + + if ((enables & PolygonModeMask.Fill) != 0) + { + GL.Enable(EnableCap.PolygonOffsetFill); + } + else + { + GL.Disable(EnableCap.PolygonOffsetFill); + } + + if (enables == 0) + { + return; + } + + if (HwCapabilities.SupportsPolygonOffsetClamp) + { + GL.PolygonOffsetClamp(factor, units, clamp); + } + else + { + GL.PolygonOffset(factor, units); + } + } + + public void SetDepthClamp(bool clamp) + { + if (!clamp) + { + GL.Disable(EnableCap.DepthClamp); + return; + } + + GL.Enable(EnableCap.DepthClamp); + } + + public void SetDepthMode(DepthMode mode) + { + ClipDepthMode depthMode = mode.Convert(); + + if (_clipDepthMode != depthMode) + { + _clipDepthMode = depthMode; + + GL.ClipControl(_clipOrigin, depthMode); + } + } + + public void SetDepthTest(DepthTestDescriptor depthTest) + { + if (depthTest.TestEnable) + { + GL.Enable(EnableCap.DepthTest); + GL.DepthFunc((DepthFunction)depthTest.Func.Convert()); + } + else + { + GL.Disable(EnableCap.DepthTest); + } + + GL.DepthMask(depthTest.WriteEnable); + _depthMask = depthTest.WriteEnable; + _depthTestEnable = depthTest.TestEnable; + } + + public void SetFaceCulling(bool enable, Face face) + { + _cullEnable = enable; + + if (!enable) + { + GL.Disable(EnableCap.CullFace); + return; + } + + GL.CullFace(face.Convert()); + + GL.Enable(EnableCap.CullFace); + } + + public void SetFrontFace(FrontFace frontFace) + { + SetFrontFace(_frontFace = frontFace.Convert()); + } + + public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat) + { + if ((uint)binding < SavedImages) + { + _images[binding] = (texture as TextureBase, imageFormat); + } + + if (texture == null) + { + GL.BindImageTexture(binding, 0, 0, true, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8); + return; + } + + TextureBase texBase = (TextureBase)texture; + + SizedInternalFormat format = FormatTable.GetImageFormat(imageFormat); + + if (format != 0) + { + GL.BindImageTexture(binding, texBase.Handle, 0, true, 0, TextureAccess.ReadWrite, format); + } + } + + public void SetImageArray(ShaderStage stage, int binding, IImageArray array) + { + (array as ImageArray).Bind(binding); + } + + public void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array) + { + throw new NotSupportedException("OpenGL does not support descriptor sets."); + } + + public void SetIndexBuffer(BufferRange buffer, IndexType type) + { + _elementsType = type.Convert(); + + _indexBaseOffset = (IntPtr)buffer.Offset; + + EnsureVertexArray(); + + _vertexArray.SetIndexBuffer(buffer); + } + + public void SetLogicOpState(bool enable, LogicalOp op) + { + if (enable) + { + GL.Enable(EnableCap.ColorLogicOp); + + GL.LogicOp((LogicOp)op.Convert()); + } + else + { + GL.Disable(EnableCap.ColorLogicOp); + } + } + + public void SetMultisampleState(MultisampleDescriptor multisample) + { + if (multisample.AlphaToCoverageEnable) + { + GL.Enable(EnableCap.SampleAlphaToCoverage); + + if (multisample.AlphaToOneEnable) + { + GL.Enable(EnableCap.SampleAlphaToOne); + } + else + { + GL.Disable(EnableCap.SampleAlphaToOne); + } + + if (HwCapabilities.SupportsAlphaToCoverageDitherControl) + { + GL.NV.AlphaToCoverageDitherControl(multisample.AlphaToCoverageDitherEnable + ? NvAlphaToCoverageDitherControl.AlphaToCoverageDitherEnableNv + : NvAlphaToCoverageDitherControl.AlphaToCoverageDitherDisableNv); + } + } + else + { + GL.Disable(EnableCap.SampleAlphaToCoverage); + } + } + + public void SetLineParameters(float width, bool smooth) + { + if (smooth) + { + GL.Enable(EnableCap.LineSmooth); + } + else + { + GL.Disable(EnableCap.LineSmooth); + } + + GL.LineWidth(width); + } + + public unsafe void SetPatchParameters(int vertices, ReadOnlySpan defaultOuterLevel, ReadOnlySpan defaultInnerLevel) + { + GL.PatchParameter(PatchParameterInt.PatchVertices, vertices); + + fixed (float* pOuterLevel = defaultOuterLevel) + { + GL.PatchParameter(PatchParameterFloat.PatchDefaultOuterLevel, pOuterLevel); + } + + fixed (float* pInnerLevel = defaultInnerLevel) + { + GL.PatchParameter(PatchParameterFloat.PatchDefaultInnerLevel, pInnerLevel); + } + } + + public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin) + { + // GL_POINT_SPRITE was deprecated in core profile 3.2+ and causes GL_INVALID_ENUM when set. + // As we don't know if the current context is core or compat, it's safer to keep this code. + if (enablePointSprite) + { + GL.Enable(EnableCap.PointSprite); + } + else + { + GL.Disable(EnableCap.PointSprite); + } + + if (isProgramPointSize) + { + GL.Enable(EnableCap.ProgramPointSize); + } + else + { + GL.Disable(EnableCap.ProgramPointSize); + } + + GL.PointParameter(origin == Origin.LowerLeft + ? PointSpriteCoordOriginParameter.LowerLeft + : PointSpriteCoordOriginParameter.UpperLeft); + + // Games seem to set point size to 0 which generates a GL_INVALID_VALUE + // From the spec, GL_INVALID_VALUE is generated if size is less than or equal to 0. + GL.PointSize(Math.Max(float.Epsilon, size)); + } + + public void SetPolygonMode(GAL.PolygonMode frontMode, GAL.PolygonMode backMode) + { + if (frontMode == backMode) + { + GL.PolygonMode(MaterialFace.FrontAndBack, frontMode.Convert()); + } + else + { + GL.PolygonMode(MaterialFace.Front, frontMode.Convert()); + GL.PolygonMode(MaterialFace.Back, backMode.Convert()); + } + } + + public void SetPrimitiveRestart(bool enable, int index) + { + if (!enable) + { + GL.Disable(EnableCap.PrimitiveRestart); + return; + } + + GL.PrimitiveRestartIndex(index); + + GL.Enable(EnableCap.PrimitiveRestart); + } + + public void SetPrimitiveTopology(PrimitiveTopology topology) + { + _primitiveType = topology.Convert(); + } + + public void SetProgram(IProgram program) + { + Program prg = (Program)program; + + if (_tfEnabled) + { + GL.EndTransformFeedback(); + prg.Bind(); + GL.BeginTransformFeedback(_tfTopology); + } + else + { + prg.Bind(); + } + + if (_fragmentOutputMap != (uint)prg.FragmentOutputMap) + { + _fragmentOutputMap = (uint)prg.FragmentOutputMap; + + for (int index = 0; index < Constants.MaxRenderTargets; index++) + { + RestoreComponentMask(index, force: false); + } + } + + _program = prg; + } + + public void SetRasterizerDiscard(bool discard) + { + if (discard) + { + GL.Enable(EnableCap.RasterizerDiscard); + } + else + { + GL.Disable(EnableCap.RasterizerDiscard); + } + + _rasterizerDiscard = discard; + } + + public void SetRenderTargetColorMasks(ReadOnlySpan componentMasks) + { + _componentMasks = 0; + + for (int index = 0; index < componentMasks.Length; index++) + { + _componentMasks |= componentMasks[index] << (index * 4); + + RestoreComponentMask(index, force: false); + } + } + + public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) + { + EnsureFramebuffer(); + + for (int index = 0; index < colors.Length; index++) + { + TextureView color = (TextureView)colors[index]; + + _framebuffer.AttachColor(index, color); + + if (color != null) + { + int isBgra = color.Format.IsBgr() ? 1 : 0; + + if (_fpIsBgra[index].X != isBgra) + { + _fpIsBgra[index].X = isBgra; + + RestoreComponentMask(index); + } + } + } + + TextureView depthStencilView = (TextureView)depthStencil; + + _framebuffer.AttachDepthStencil(depthStencilView); + _framebuffer.SetDrawBuffers(colors.Length); + } + + public void SetScissors(ReadOnlySpan> regions) + { + int count = Math.Min(regions.Length, Constants.MaxViewports); + + Span v = stackalloc int[count * 4]; + + for (int index = 0; index < count; index++) + { + int vIndex = index * 4; + + var region = regions[index]; + + bool enabled = (region.X | region.Y) != 0 || region.Width != 0xffff || region.Height != 0xffff; + uint mask = 1u << index; + + if (enabled) + { + v[vIndex] = region.X; + v[vIndex + 1] = region.Y; + v[vIndex + 2] = region.Width; + v[vIndex + 3] = region.Height; + + if ((_scissorEnables & mask) == 0) + { + _scissorEnables |= mask; + GL.Enable(IndexedEnableCap.ScissorTest, index); + } + } + else + { + if ((_scissorEnables & mask) != 0) + { + _scissorEnables &= ~mask; + GL.Disable(IndexedEnableCap.ScissorTest, index); + } + } + } + + GL.ScissorArray(0, count, ref v[0]); + } + + public void SetStencilTest(StencilTestDescriptor stencilTest) + { + _stencilTestEnable = stencilTest.TestEnable; + + if (!stencilTest.TestEnable) + { + GL.Disable(EnableCap.StencilTest); + return; + } + + GL.StencilOpSeparate( + StencilFace.Front, + stencilTest.FrontSFail.Convert(), + stencilTest.FrontDpFail.Convert(), + stencilTest.FrontDpPass.Convert()); + + GL.StencilFuncSeparate( + StencilFace.Front, + (StencilFunction)stencilTest.FrontFunc.Convert(), + stencilTest.FrontFuncRef, + stencilTest.FrontFuncMask); + + GL.StencilMaskSeparate(StencilFace.Front, stencilTest.FrontMask); + + GL.StencilOpSeparate( + StencilFace.Back, + stencilTest.BackSFail.Convert(), + stencilTest.BackDpFail.Convert(), + stencilTest.BackDpPass.Convert()); + + GL.StencilFuncSeparate( + StencilFace.Back, + (StencilFunction)stencilTest.BackFunc.Convert(), + stencilTest.BackFuncRef, + stencilTest.BackFuncMask); + + GL.StencilMaskSeparate(StencilFace.Back, stencilTest.BackMask); + + GL.Enable(EnableCap.StencilTest); + + _stencilFrontMask = stencilTest.FrontMask; + } + + public void SetStorageBuffers(ReadOnlySpan buffers) + { + SetBuffers(buffers, isStorage: true); + } + + public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler) + { + if (texture != null) + { + if (binding == 0) + { + _unit0Texture = (TextureBase)texture; + } + else + { + ((TextureBase)texture).Bind(binding); + } + } + else + { + TextureBase.ClearBinding(binding); + } + + Sampler glSampler = (Sampler)sampler; + + glSampler?.Bind(binding); + + if (binding == 0) + { + _unit0Sampler = glSampler; + } + } + + public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array) + { + (array as TextureArray).Bind(binding); + } + + public void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array) + { + throw new NotSupportedException("OpenGL does not support descriptor sets."); + } + + public void SetTransformFeedbackBuffers(ReadOnlySpan buffers) + { + if (_tfEnabled) + { + GL.EndTransformFeedback(); + } + + int count = Math.Min(buffers.Length, Constants.MaxTransformFeedbackBuffers); + + for (int i = 0; i < count; i++) + { + BufferRange buffer = buffers[i]; + _tfbTargets[i] = buffer; + + if (buffer.Handle == BufferHandle.Null) + { + GL.BindBufferBase(BufferRangeTarget.TransformFeedbackBuffer, i, 0); + continue; + } + + if (_tfbs[i] == BufferHandle.Null) + { + _tfbs[i] = Buffer.Create(); + } + + Buffer.Resize(_tfbs[i], buffer.Size); + Buffer.Copy(buffer.Handle, _tfbs[i], buffer.Offset, 0, buffer.Size); + GL.BindBufferBase(BufferRangeTarget.TransformFeedbackBuffer, i, _tfbs[i].ToInt32()); + } + + if (_tfEnabled) + { + GL.BeginTransformFeedback(_tfTopology); + } + } + + public void SetUniformBuffers(ReadOnlySpan buffers) + { + SetBuffers(buffers, isStorage: false); + } + + public void SetUserClipDistance(int index, bool enableClip) + { + if (!enableClip) + { + GL.Disable(EnableCap.ClipDistance0 + index); + return; + } + + GL.Enable(EnableCap.ClipDistance0 + index); + } + + public void SetVertexAttribs(ReadOnlySpan vertexAttribs) + { + EnsureVertexArray(); + + _vertexArray.SetVertexAttributes(vertexAttribs); + } + + public void SetVertexBuffers(ReadOnlySpan vertexBuffers) + { + EnsureVertexArray(); + + _vertexArray.SetVertexBuffers(vertexBuffers); + } + + public void SetViewports(ReadOnlySpan viewports) + { + Array.Resize(ref _viewportArray, viewports.Length * 4); + Array.Resize(ref _depthRangeArray, viewports.Length * 2); + + float[] viewportArray = _viewportArray; + double[] depthRangeArray = _depthRangeArray; + + for (int index = 0; index < viewports.Length; index++) + { + int viewportElemIndex = index * 4; + + Viewport viewport = viewports[index]; + + viewportArray[viewportElemIndex + 0] = viewport.Region.X; + viewportArray[viewportElemIndex + 1] = viewport.Region.Y + (viewport.Region.Height < 0 ? viewport.Region.Height : 0); + viewportArray[viewportElemIndex + 2] = viewport.Region.Width; + viewportArray[viewportElemIndex + 3] = MathF.Abs(viewport.Region.Height); + + if (HwCapabilities.SupportsViewportSwizzle) + { + GL.NV.ViewportSwizzle( + index, + viewport.SwizzleX.Convert(), + viewport.SwizzleY.Convert(), + viewport.SwizzleZ.Convert(), + viewport.SwizzleW.Convert()); + } + + depthRangeArray[index * 2 + 0] = viewport.DepthNear; + depthRangeArray[index * 2 + 1] = viewport.DepthFar; + } + + bool flipY = viewports.Length != 0 && viewports[0].Region.Height < 0; + + SetOrigin(flipY ? ClipOrigin.UpperLeft : ClipOrigin.LowerLeft); + + GL.ViewportArray(0, viewports.Length, viewportArray); + GL.DepthRangeArray(0, viewports.Length, depthRangeArray); + } + + public void TextureBarrier() + { + GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit); + } + + public void TextureBarrierTiled() + { + GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit); + } + + private static void SetBuffers(ReadOnlySpan buffers, bool isStorage) + { + BufferRangeTarget target = isStorage ? BufferRangeTarget.ShaderStorageBuffer : BufferRangeTarget.UniformBuffer; + + for (int index = 0; index < buffers.Length; index++) + { + BufferAssignment assignment = buffers[index]; + BufferRange buffer = assignment.Range; + + if (buffer.Handle == BufferHandle.Null) + { + GL.BindBufferRange(target, assignment.Binding, 0, IntPtr.Zero, 0); + continue; + } + + GL.BindBufferRange(target, assignment.Binding, buffer.Handle.ToInt32(), (IntPtr)buffer.Offset, buffer.Size); + } + } + + private void SetOrigin(ClipOrigin origin) + { + if (_clipOrigin != origin) + { + _clipOrigin = origin; + + GL.ClipControl(origin, _clipDepthMode); + + SetFrontFace(_frontFace); + } + } + + private void SetFrontFace(FrontFaceDirection frontFace) + { + // Changing clip origin will also change the front face to compensate + // for the flipped viewport, we flip it again here to compensate as + // this effect is undesirable for us. + if (_clipOrigin == ClipOrigin.UpperLeft) + { + frontFace = frontFace == FrontFaceDirection.Ccw ? FrontFaceDirection.Cw : FrontFaceDirection.Ccw; + } + + GL.FrontFace(frontFace); + } + + private void EnsureVertexArray() + { + if (_vertexArray == null) + { + _vertexArray = new VertexArray(); + + _vertexArray.Bind(); + } + } + + private void EnsureFramebuffer() + { + if (_framebuffer == null) + { + _framebuffer = new Framebuffer(); + + int boundHandle = _framebuffer.Bind(); + _boundDrawFramebuffer = _boundReadFramebuffer = boundHandle; + + GL.Enable(EnableCap.FramebufferSrgb); + } + } + + internal (int drawHandle, int readHandle) GetBoundFramebuffers() + { + if (BackgroundContextWorker.InBackground) + { + return (0, 0); + } + + return (_boundDrawFramebuffer, _boundReadFramebuffer); + } + + private void PrepareForDispatch() + { + _unit0Texture?.Bind(0); + } + + private void PreDraw(int vertexCount) + { + _vertexArray.PreDraw(vertexCount); + PreDraw(); + } + + private void PreDrawVbUnbounded() + { + _vertexArray.PreDrawVbUnbounded(); + PreDraw(); + } + + private void PreDraw() + { + DrawCount++; + + _unit0Texture?.Bind(0); + } + + private void PostDraw() + { + if (_tfEnabled) + { + for (int i = 0; i < Constants.MaxTransformFeedbackBuffers; i++) + { + if (_tfbTargets[i].Handle != BufferHandle.Null) + { + Buffer.Copy(_tfbs[i], _tfbTargets[i].Handle, 0, _tfbTargets[i].Offset, _tfbTargets[i].Size); + } + } + } + } + + public void RestoreComponentMask(int index, bool force = true) + { + // If the bound render target is bgra, swap the red and blue masks. + uint redMask = _fpIsBgra[index].X == 0 ? 1u : 4u; + uint blueMask = _fpIsBgra[index].X == 0 ? 4u : 1u; + + int shift = index * 4; + uint componentMask = _componentMasks & _fragmentOutputMap; + uint checkMask = 0xfu << shift; + uint componentMaskAtIndex = componentMask & checkMask; + + if (!force && componentMaskAtIndex == (_currentComponentMasks & checkMask)) + { + return; + } + + componentMask >>= shift; + componentMask &= 0xfu; + + GL.ColorMask( + index, + (componentMask & redMask) != 0, + (componentMask & 2u) != 0, + (componentMask & blueMask) != 0, + (componentMask & 8u) != 0); + + _currentComponentMasks &= ~checkMask; + _currentComponentMasks |= componentMaskAtIndex; + } + + public void RestoreClipControl() + { + GL.ClipControl(_clipOrigin, _clipDepthMode); + } + + public void RestoreScissor0Enable() + { + if ((_scissorEnables & 1u) != 0) + { + GL.Enable(IndexedEnableCap.ScissorTest, 0); + } + } + + public void RestoreRasterizerDiscard() + { + if (_rasterizerDiscard) + { + GL.Enable(EnableCap.RasterizerDiscard); + } + } + + public void RestoreViewport0() + { + if (_viewportArray.Length > 0) + { + GL.ViewportArray(0, 1, _viewportArray); + } + } + + public void RestoreProgram() + { + _program?.Bind(); + } + + public void RestoreImages1And2() + { + for (int i = 0; i < SavedImages; i++) + { + (TextureBase texBase, Format imageFormat) = _images[i]; + + if (texBase != null) + { + SizedInternalFormat format = FormatTable.GetImageFormat(imageFormat); + + if (format != 0) + { + GL.BindImageTexture(i, texBase.Handle, 0, true, 0, TextureAccess.ReadWrite, format); + continue; + } + } + + GL.BindImageTexture(i, 0, 0, true, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8); + } + } + + public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual) + { + // Compare an event and a constant value. + if (value is CounterQueueEvent evt) + { + // Easy host conditional rendering when the check matches what GL can do: + // - Event is of type samples passed. + // - Result is not a combination of multiple queries. + // - Comparing against 0. + // - Event has not already been flushed. + + if (compare == 0 && evt.Type == QueryTarget.SamplesPassed && evt.ClearCounter) + { + if (!value.ReserveForHostAccess()) + { + // If the event has been flushed, then just use the values on the CPU. + // The query object may already be repurposed for another draw (eg. begin + end). + return false; + } + + GL.BeginConditionalRender(evt.Query, isEqual ? ConditionalRenderType.QueryNoWaitInverted : ConditionalRenderType.QueryNoWait); + _activeConditionalRender = evt; + + return true; + } + } + + // The GPU will flush the queries to CPU and evaluate the condition there instead. + + GL.Flush(); // The thread will be stalled manually flushing the counter, so flush GL commands now. + return false; + } + + public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual) + { + GL.Flush(); // The GPU thread will be stalled manually flushing the counter, so flush GL commands now. + return false; // We don't currently have a way to compare two counters for conditional rendering. + } + + public void EndHostConditionalRendering() + { + GL.EndConditionalRender(); + + _activeConditionalRender?.ReleaseHostAccess(); + _activeConditionalRender = null; + } + + public void Dispose() + { + for (int i = 0; i < Constants.MaxTransformFeedbackBuffers; i++) + { + if (_tfbs[i] != BufferHandle.Null) + { + Buffer.Delete(_tfbs[i]); + _tfbs[i] = BufferHandle.Null; + } + } + + _activeConditionalRender?.ReleaseHostAccess(); + _framebuffer?.Dispose(); + _vertexArray?.Dispose(); + _drawTexture.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Program.cs b/src/Ryujinx.Graphics.OpenGL/Program.cs new file mode 100644 index 00000000..19de06f8 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Program.cs @@ -0,0 +1,176 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Buffers.Binary; + +namespace Ryujinx.Graphics.OpenGL +{ + class Program : IProgram + { + private const int MaxShaderLogLength = 2048; + + public int Handle { get; private set; } + + public bool IsLinked + { + get + { + if (_status == ProgramLinkStatus.Incomplete) + { + CheckProgramLink(true); + } + + return _status == ProgramLinkStatus.Success; + } + } + + private ProgramLinkStatus _status = ProgramLinkStatus.Incomplete; + private int[] _shaderHandles; + + public int FragmentOutputMap { get; } + + public Program(ShaderSource[] shaders, int fragmentOutputMap) + { + Handle = GL.CreateProgram(); + + GL.ProgramParameter(Handle, ProgramParameterName.ProgramBinaryRetrievableHint, 1); + + _shaderHandles = new int[shaders.Length]; + bool hasFragmentShader = false; + + for (int index = 0; index < shaders.Length; index++) + { + ShaderSource shader = shaders[index]; + + if (shader.Stage == ShaderStage.Fragment) + { + hasFragmentShader = true; + } + + int shaderHandle = GL.CreateShader(shader.Stage.Convert()); + + switch (shader.Language) + { + case TargetLanguage.Glsl: + GL.ShaderSource(shaderHandle, shader.Code); + GL.CompileShader(shaderHandle); + break; + case TargetLanguage.Spirv: + GL.ShaderBinary(1, ref shaderHandle, (BinaryFormat)All.ShaderBinaryFormatSpirVArb, shader.BinaryCode, shader.BinaryCode.Length); + GL.SpecializeShader(shaderHandle, "main", 0, (int[])null, (int[])null); + break; + } + + GL.AttachShader(Handle, shaderHandle); + + _shaderHandles[index] = shaderHandle; + } + + GL.LinkProgram(Handle); + + FragmentOutputMap = hasFragmentShader ? fragmentOutputMap : 0; + } + + public Program(ReadOnlySpan code, bool hasFragmentShader, int fragmentOutputMap) + { + Handle = GL.CreateProgram(); + + if (code.Length >= 4) + { + BinaryFormat binaryFormat = (BinaryFormat)BinaryPrimitives.ReadInt32LittleEndian(code.Slice(code.Length - 4, 4)); + + unsafe + { + fixed (byte* ptr = code) + { + GL.ProgramBinary(Handle, binaryFormat, (IntPtr)ptr, code.Length - 4); + } + } + } + + FragmentOutputMap = hasFragmentShader ? fragmentOutputMap : 0; + } + + public void Bind() + { + GL.UseProgram(Handle); + } + + public ProgramLinkStatus CheckProgramLink(bool blocking) + { + if (!blocking && HwCapabilities.SupportsParallelShaderCompile) + { + GL.GetProgram(Handle, (GetProgramParameterName)ArbParallelShaderCompile.CompletionStatusArb, out int completed); + + if (completed == 0) + { + return ProgramLinkStatus.Incomplete; + } + } + + GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out int status); + DeleteShaders(); + + if (status == 0) + { + _status = ProgramLinkStatus.Failure; + + string log = GL.GetProgramInfoLog(Handle); + + if (log.Length > MaxShaderLogLength) + { + log = log[..MaxShaderLogLength] + "..."; + } + + Logger.Warning?.Print(LogClass.Gpu, $"Shader linking failed: \n{log}"); + } + else + { + _status = ProgramLinkStatus.Success; + } + + return _status; + } + + public byte[] GetBinary() + { + GL.GetProgram(Handle, (GetProgramParameterName)All.ProgramBinaryLength, out int size); + + byte[] data = new byte[size + 4]; + + GL.GetProgramBinary(Handle, size, out _, out BinaryFormat binFormat, data); + + BinaryPrimitives.WriteInt32LittleEndian(data.AsSpan(size, 4), (int)binFormat); + + return data; + } + + private void DeleteShaders() + { + if (_shaderHandles != null) + { + foreach (int shaderHandle in _shaderHandles) + { + GL.DetachShader(Handle, shaderHandle); + GL.DeleteShader(shaderHandle); + } + + _shaderHandles = null; + } + } + + public void Dispose() + { + if (Handle != 0) + { + DeleteShaders(); + GL.DeleteProgram(Handle); + + Handle = 0; + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Queries/BufferedQuery.cs b/src/Ryujinx.Graphics.OpenGL/Queries/BufferedQuery.cs new file mode 100644 index 00000000..0a85970d --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Queries/BufferedQuery.cs @@ -0,0 +1,120 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Graphics.OpenGL.Queries +{ + class BufferedQuery : IDisposable + { + private const int MaxQueryRetries = 5000; + private const long DefaultValue = -1; + private const ulong HighMask = 0xFFFFFFFF00000000; + + public int Query { get; } + + private readonly int _buffer; + private readonly IntPtr _bufferMap; + private readonly QueryTarget _type; + + public BufferedQuery(QueryTarget type) + { + _buffer = GL.GenBuffer(); + Query = GL.GenQuery(); + _type = type; + + GL.BindBuffer(BufferTarget.QueryBuffer, _buffer); + + unsafe + { + long defaultValue = DefaultValue; + GL.BufferStorage(BufferTarget.QueryBuffer, sizeof(long), (IntPtr)(&defaultValue), BufferStorageFlags.MapReadBit | BufferStorageFlags.MapWriteBit | BufferStorageFlags.MapPersistentBit); + } + _bufferMap = GL.MapBufferRange(BufferTarget.QueryBuffer, IntPtr.Zero, sizeof(long), BufferAccessMask.MapReadBit | BufferAccessMask.MapWriteBit | BufferAccessMask.MapPersistentBit); + } + + public void Reset() + { + GL.EndQuery(_type); + GL.BeginQuery(_type, Query); + } + + public void Begin() + { + GL.BeginQuery(_type, Query); + } + + public unsafe void End(bool withResult) + { + GL.EndQuery(_type); + + if (withResult) + { + GL.BindBuffer(BufferTarget.QueryBuffer, _buffer); + + Marshal.WriteInt64(_bufferMap, -1L); + GL.GetQueryObject(Query, GetQueryObjectParam.QueryResult, (long*)0); + GL.MemoryBarrier(MemoryBarrierFlags.QueryBufferBarrierBit | MemoryBarrierFlags.ClientMappedBufferBarrierBit); + } + else + { + // Dummy result, just return 0. + Marshal.WriteInt64(_bufferMap, 0L); + } + } + + private static bool WaitingForValue(long data) + { + return data == DefaultValue || + ((ulong)data & HighMask) == (unchecked((ulong)DefaultValue) & HighMask); + } + + public bool TryGetResult(out long result) + { + result = Marshal.ReadInt64(_bufferMap); + + return !WaitingForValue(result); + } + + public long AwaitResult(AutoResetEvent wakeSignal = null) + { + long data = DefaultValue; + + if (wakeSignal == null) + { + while (WaitingForValue(data)) + { + data = Marshal.ReadInt64(_bufferMap); + } + } + else + { + int iterations = 0; + while (WaitingForValue(data) && iterations++ < MaxQueryRetries) + { + data = Marshal.ReadInt64(_bufferMap); + if (WaitingForValue(data)) + { + wakeSignal.WaitOne(1); + } + } + + if (iterations >= MaxQueryRetries) + { + Logger.Error?.Print(LogClass.Gpu, $"Error: Query result timed out. Took more than {MaxQueryRetries} tries."); + } + } + + return data; + } + + public void Dispose() + { + GL.BindBuffer(BufferTarget.QueryBuffer, _buffer); + GL.UnmapBuffer(BufferTarget.QueryBuffer); + GL.DeleteBuffer(_buffer); + GL.DeleteQuery(Query); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Queries/CounterQueue.cs b/src/Ryujinx.Graphics.OpenGL/Queries/CounterQueue.cs new file mode 100644 index 00000000..345a99ff --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Queries/CounterQueue.cs @@ -0,0 +1,224 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.OpenGL.Queries +{ + class CounterQueue : IDisposable + { + private const int QueryPoolInitialSize = 100; + + public CounterType Type { get; } + public bool Disposed { get; private set; } + + private readonly Queue _events = new(); + private CounterQueueEvent _current; + + private ulong _accumulatedCounter; + private int _waiterCount; + + private readonly object _lock = new(); + + private readonly Queue _queryPool; + private readonly AutoResetEvent _queuedEvent = new(false); + private readonly AutoResetEvent _wakeSignal = new(false); + private readonly AutoResetEvent _eventConsumed = new(false); + + private readonly Thread _consumerThread; + + internal CounterQueue(CounterType type) + { + Type = type; + + QueryTarget glType = GetTarget(Type); + + _queryPool = new Queue(QueryPoolInitialSize); + for (int i = 0; i < QueryPoolInitialSize; i++) + { + _queryPool.Enqueue(new BufferedQuery(glType)); + } + + _current = new CounterQueueEvent(this, glType, 0); + + _consumerThread = new Thread(EventConsumer); + _consumerThread.Start(); + } + + private void EventConsumer() + { + while (!Disposed) + { + CounterQueueEvent evt = null; + lock (_lock) + { + if (_events.Count > 0) + { + evt = _events.Dequeue(); + } + } + + if (evt == null) + { + _queuedEvent.WaitOne(); // No more events to go through, wait for more. + } + else + { + // Spin-wait rather than sleeping if there are any waiters, by passing null instead of the wake signal. + evt.TryConsume(ref _accumulatedCounter, true, _waiterCount == 0 ? _wakeSignal : null); + } + + if (_waiterCount > 0) + { + _eventConsumed.Set(); + } + } + } + + internal BufferedQuery GetQueryObject() + { + // Creating/disposing query objects on a context we're sharing with will cause issues. + // So instead, make a lot of query objects on the main thread and reuse them. + + lock (_lock) + { + if (_queryPool.Count > 0) + { + BufferedQuery result = _queryPool.Dequeue(); + return result; + } + else + { + return new BufferedQuery(GetTarget(Type)); + } + } + } + + internal void ReturnQueryObject(BufferedQuery query) + { + lock (_lock) + { + _queryPool.Enqueue(query); + } + } + + public CounterQueueEvent QueueReport(EventHandler resultHandler, float divisor, ulong lastDrawIndex, bool hostReserved) + { + CounterQueueEvent result; + ulong draws = lastDrawIndex - _current.DrawIndex; + + lock (_lock) + { + // A query's result only matters if more than one draw was performed during it. + // Otherwise, dummy it out and return 0 immediately. + + if (hostReserved) + { + // This counter event is guaranteed to be available for host conditional rendering. + _current.ReserveForHostAccess(); + } + + _current.Complete(draws > 0, divisor); + _events.Enqueue(_current); + + _current.OnResult += resultHandler; + + result = _current; + + _current = new CounterQueueEvent(this, GetTarget(Type), lastDrawIndex); + } + + _queuedEvent.Set(); + + return result; + } + + public void QueueReset() + { + lock (_lock) + { + _current.Clear(); + } + } + + private static QueryTarget GetTarget(CounterType type) + { + return type switch + { + CounterType.SamplesPassed => QueryTarget.SamplesPassed, + CounterType.PrimitivesGenerated => QueryTarget.PrimitivesGenerated, + CounterType.TransformFeedbackPrimitivesWritten => QueryTarget.TransformFeedbackPrimitivesWritten, + _ => QueryTarget.SamplesPassed, + }; + } + + public void Flush(bool blocking) + { + if (!blocking) + { + // Just wake the consumer thread - it will update the queries. + _wakeSignal.Set(); + return; + } + + lock (_lock) + { + // Tell the queue to process all events. + while (_events.Count > 0) + { + CounterQueueEvent flush = _events.Peek(); + if (!flush.TryConsume(ref _accumulatedCounter, true)) + { + return; // If not blocking, then return when we encounter an event that is not ready yet. + } + _events.Dequeue(); + } + } + } + + public void FlushTo(CounterQueueEvent evt) + { + // Flush the counter queue on the main thread. + + Interlocked.Increment(ref _waiterCount); + + _wakeSignal.Set(); + + while (!evt.Disposed) + { + _eventConsumed.WaitOne(1); + } + + Interlocked.Decrement(ref _waiterCount); + } + + public void Dispose() + { + lock (_lock) + { + while (_events.Count > 0) + { + CounterQueueEvent evt = _events.Dequeue(); + + evt.Dispose(); + } + + Disposed = true; + } + + _queuedEvent.Set(); + + _consumerThread.Join(); + + foreach (BufferedQuery query in _queryPool) + { + query.Dispose(); + } + + _queuedEvent.Dispose(); + _wakeSignal.Dispose(); + _eventConsumed.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Queries/CounterQueueEvent.cs b/src/Ryujinx.Graphics.OpenGL/Queries/CounterQueueEvent.cs new file mode 100644 index 00000000..32b75c61 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Queries/CounterQueueEvent.cs @@ -0,0 +1,163 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; +using System.Threading; + +namespace Ryujinx.Graphics.OpenGL.Queries +{ + class CounterQueueEvent : ICounterEvent + { + public event EventHandler OnResult; + + public QueryTarget Type { get; } + public bool ClearCounter { get; private set; } + public int Query => _counter.Query; + + public bool Disposed { get; private set; } + public bool Invalid { get; set; } + + public ulong DrawIndex { get; } + + private readonly CounterQueue _queue; + private readonly BufferedQuery _counter; + + private bool _hostAccessReserved = false; + private int _refCount = 1; // Starts with a reference from the counter queue. + + private readonly object _lock = new(); + private ulong _result = ulong.MaxValue; + private double _divisor = 1f; + + public CounterQueueEvent(CounterQueue queue, QueryTarget type, ulong drawIndex) + { + _queue = queue; + + _counter = queue.GetQueryObject(); + Type = type; + + DrawIndex = drawIndex; + + _counter.Begin(); + } + + internal void Clear() + { + _counter.Reset(); + ClearCounter = true; + } + + internal void Complete(bool withResult, double divisor) + { + _counter.End(withResult); + + _divisor = divisor; + } + + internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null) + { + lock (_lock) + { + if (Disposed) + { + return true; + } + + if (ClearCounter || Type == QueryTarget.Timestamp) + { + result = 0; + } + + long queryResult; + + if (block) + { + queryResult = _counter.AwaitResult(wakeSignal); + } + else + { + if (!_counter.TryGetResult(out queryResult)) + { + return false; + } + } + + result += _divisor == 1 ? (ulong)queryResult : (ulong)Math.Ceiling(queryResult / _divisor); + + _result = result; + + OnResult?.Invoke(this, result); + + Dispose(); // Return the our resources to the pool. + + return true; + } + } + + public void Flush() + { + if (Disposed) + { + return; + } + + // Tell the queue to process all events up to this one. + _queue.FlushTo(this); + } + + public void DecrementRefCount() + { + if (Interlocked.Decrement(ref _refCount) == 0) + { + DisposeInternal(); + } + } + + public bool ReserveForHostAccess() + { + if (_hostAccessReserved) + { + return true; + } + + if (IsValueAvailable()) + { + return false; + } + + if (Interlocked.Increment(ref _refCount) == 1) + { + Interlocked.Decrement(ref _refCount); + + return false; + } + + _hostAccessReserved = true; + + return true; + } + + public void ReleaseHostAccess() + { + _hostAccessReserved = false; + + DecrementRefCount(); + } + + private void DisposeInternal() + { + _queue.ReturnQueryObject(_counter); + } + + private bool IsValueAvailable() + { + return _result != ulong.MaxValue || _counter.TryGetResult(out _); + } + + public void Dispose() + { + Disposed = true; + + DecrementRefCount(); + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Queries/Counters.cs b/src/Ryujinx.Graphics.OpenGL/Queries/Counters.cs new file mode 100644 index 00000000..1530c9d2 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Queries/Counters.cs @@ -0,0 +1,57 @@ +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.OpenGL.Queries +{ + class Counters : IDisposable + { + private readonly CounterQueue[] _counterQueues; + + public Counters() + { + int count = Enum.GetNames().Length; + + _counterQueues = new CounterQueue[count]; + } + + public void Initialize() + { + for (int index = 0; index < _counterQueues.Length; index++) + { + CounterType type = (CounterType)index; + _counterQueues[index] = new CounterQueue(type); + } + } + + public CounterQueueEvent QueueReport(CounterType type, EventHandler resultHandler, float divisor, ulong lastDrawIndex, bool hostReserved) + { + return _counterQueues[(int)type].QueueReport(resultHandler, divisor, lastDrawIndex, hostReserved); + } + + public void QueueReset(CounterType type) + { + _counterQueues[(int)type].QueueReset(); + } + + public void Update() + { + foreach (var queue in _counterQueues) + { + queue.Flush(false); + } + } + + public void Flush(CounterType type) + { + _counterQueues[(int)type].Flush(true); + } + + public void Dispose() + { + foreach (var queue in _counterQueues) + { + queue.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/ResourcePool.cs b/src/Ryujinx.Graphics.OpenGL/ResourcePool.cs new file mode 100644 index 00000000..6385f57b --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/ResourcePool.cs @@ -0,0 +1,114 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.OpenGL.Image; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.OpenGL +{ + class DisposedTexture + { + public TextureCreateInfo Info; + public TextureView View; + public int RemainingFrames; + } + + /// + /// A structure for pooling resources that can be reused without recreation, such as textures. + /// + class ResourcePool : IDisposable + { + private const int DisposedLiveFrames = 2; + + private readonly object _lock = new(); + private readonly Dictionary> _textures = new(); + + /// + /// Add a texture that is not being used anymore to the resource pool to be used later. + /// Both the texture's view and storage should be completely unused. + /// + /// The texture's view + public void AddTexture(TextureView view) + { + lock (_lock) + { + if (!_textures.TryGetValue(view.Info, out List list)) + { + list = new List(); + _textures.Add(view.Info, list); + } + + list.Add(new DisposedTexture() + { + Info = view.Info, + View = view, + RemainingFrames = DisposedLiveFrames, + }); + } + } + + /// + /// Attempt to obtain a texture from the resource cache with the desired parameters. + /// + /// The creation info for the desired texture + /// A TextureView with the description specified, or null if one was not found. + public TextureView GetTextureOrNull(TextureCreateInfo info) + { + lock (_lock) + { + if (!_textures.TryGetValue(info, out List list)) + { + return null; + } + + foreach (DisposedTexture texture in list) + { + list.Remove(texture); + return texture.View; + } + + return null; + } + } + + /// + /// Update the pool, removing any resources that have expired. + /// + public void Tick() + { + lock (_lock) + { + foreach (List list in _textures.Values) + { + for (int i = 0; i < list.Count; i++) + { + DisposedTexture tex = list[i]; + + if (--tex.RemainingFrames < 0) + { + tex.View.Dispose(); + list.RemoveAt(i--); + } + } + } + } + } + + /// + /// Disposes the resource pool. + /// + public void Dispose() + { + lock (_lock) + { + foreach (List list in _textures.Values) + { + foreach (DisposedTexture texture in list) + { + texture.View.Dispose(); + } + } + _textures.Clear(); + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj b/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj new file mode 100644 index 00000000..3d64da99 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.Graphics.OpenGL/Sync.cs b/src/Ryujinx.Graphics.OpenGL/Sync.cs new file mode 100644 index 00000000..eba1638a --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Sync.cs @@ -0,0 +1,172 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Logging; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.OpenGL +{ + class Sync : IDisposable + { + private class SyncHandle + { + public ulong ID; + public IntPtr Handle; + } + + private ulong _firstHandle = 0; + private static ClientWaitSyncFlags SyncFlags => HwCapabilities.RequiresSyncFlush ? ClientWaitSyncFlags.None : ClientWaitSyncFlags.SyncFlushCommandsBit; + + private readonly List _handles = new(); + + public void Create(ulong id) + { + SyncHandle handle = new() + { + ID = id, + Handle = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None), + }; + + + if (HwCapabilities.RequiresSyncFlush) + { + // Force commands to flush up to the syncpoint. + GL.ClientWaitSync(handle.Handle, ClientWaitSyncFlags.SyncFlushCommandsBit, 0); + } + + lock (_handles) + { + _handles.Add(handle); + } + } + + public ulong GetCurrent() + { + lock (_handles) + { + ulong lastHandle = _firstHandle; + + foreach (SyncHandle handle in _handles) + { + lock (handle) + { + if (handle.Handle == IntPtr.Zero) + { + continue; + } + + if (handle.ID > lastHandle) + { + WaitSyncStatus syncResult = GL.ClientWaitSync(handle.Handle, SyncFlags, 0); + + if (syncResult == WaitSyncStatus.AlreadySignaled) + { + lastHandle = handle.ID; + } + } + } + } + + return lastHandle; + } + } + + public void Wait(ulong id) + { + SyncHandle result = null; + + lock (_handles) + { + if ((long)(_firstHandle - id) > 0) + { + return; // The handle has already been signalled or deleted. + } + + foreach (SyncHandle handle in _handles) + { + if (handle.ID == id) + { + result = handle; + break; + } + } + } + + if (result != null) + { + lock (result) + { + if (result.Handle == IntPtr.Zero) + { + return; + } + + WaitSyncStatus syncResult = GL.ClientWaitSync(result.Handle, SyncFlags, 1000000000); + + if (syncResult == WaitSyncStatus.TimeoutExpired) + { + Logger.Error?.PrintMsg(LogClass.Gpu, $"GL Sync Object {result.ID} failed to signal within 1000ms. Continuing..."); + } + } + } + } + + public void Cleanup() + { + // Iterate through handles and remove any that have already been signalled. + + while (true) + { + SyncHandle first = null; + lock (_handles) + { + first = _handles.FirstOrDefault(); + } + + if (first == null) + { + break; + } + + WaitSyncStatus syncResult = GL.ClientWaitSync(first.Handle, SyncFlags, 0); + + if (syncResult == WaitSyncStatus.AlreadySignaled) + { + // Delete the sync object. + lock (_handles) + { + lock (first) + { + _firstHandle = first.ID + 1; + _handles.RemoveAt(0); + GL.DeleteSync(first.Handle); + first.Handle = IntPtr.Zero; + } + } + } + else + { + // This sync handle and any following have not been reached yet. + break; + } + } + } + + public void Dispose() + { + lock (_handles) + { + foreach (SyncHandle handle in _handles) + { + lock (handle) + { + GL.DeleteSync(handle.Handle); + handle.Handle = IntPtr.Zero; + } + } + + _handles.Clear(); + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/VertexArray.cs b/src/Ryujinx.Graphics.OpenGL/VertexArray.cs new file mode 100644 index 00000000..32211e78 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/VertexArray.cs @@ -0,0 +1,280 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.OpenGL +{ + class VertexArray : IDisposable + { + public int Handle { get; private set; } + + private readonly VertexAttribDescriptor[] _vertexAttribs; + private readonly VertexBufferDescriptor[] _vertexBuffers; + + private int _minVertexCount; + + private uint _vertexAttribsInUse; + private uint _vertexBuffersInUse; + private uint _vertexBuffersLimited; + + private BufferRange _indexBuffer; + private readonly BufferHandle _tempIndexBuffer; + private BufferHandle _tempVertexBuffer; + private int _tempVertexBufferSize; + + public VertexArray() + { + Handle = GL.GenVertexArray(); + + _vertexAttribs = new VertexAttribDescriptor[Constants.MaxVertexAttribs]; + _vertexBuffers = new VertexBufferDescriptor[Constants.MaxVertexBuffers]; + + _tempIndexBuffer = Buffer.Create(); + } + + public void Bind() + { + GL.BindVertexArray(Handle); + } + + public void SetVertexBuffers(ReadOnlySpan vertexBuffers) + { + int minVertexCount = int.MaxValue; + + int bindingIndex; + for (bindingIndex = 0; bindingIndex < vertexBuffers.Length; bindingIndex++) + { + VertexBufferDescriptor vb = vertexBuffers[bindingIndex]; + + if (vb.Buffer.Handle != BufferHandle.Null) + { + int vertexCount = vb.Stride <= 0 ? 0 : vb.Buffer.Size / vb.Stride; + if (minVertexCount > vertexCount) + { + minVertexCount = vertexCount; + } + + GL.BindVertexBuffer(bindingIndex, vb.Buffer.Handle.ToInt32(), (IntPtr)vb.Buffer.Offset, vb.Stride); + GL.VertexBindingDivisor(bindingIndex, vb.Divisor); + _vertexBuffersInUse |= 1u << bindingIndex; + } + else + { + if ((_vertexBuffersInUse & (1u << bindingIndex)) != 0) + { + GL.BindVertexBuffer(bindingIndex, 0, IntPtr.Zero, 0); + _vertexBuffersInUse &= ~(1u << bindingIndex); + } + } + + _vertexBuffers[bindingIndex] = vb; + } + + _minVertexCount = minVertexCount; + } + + public void SetVertexAttributes(ReadOnlySpan vertexAttribs) + { + int index = 0; + + for (; index < vertexAttribs.Length; index++) + { + VertexAttribDescriptor attrib = vertexAttribs[index]; + + if (attrib.Equals(_vertexAttribs[index])) + { + continue; + } + + FormatInfo fmtInfo = FormatTable.GetFormatInfo(attrib.Format); + + if (attrib.IsZero) + { + // Disabling the attribute causes the shader to read a constant value. + // We currently set the constant to (0, 0, 0, 0). + DisableVertexAttrib(index); + } + else + { + EnableVertexAttrib(index); + } + + int offset = attrib.Offset; + int size = fmtInfo.Components; + + bool isFloat = fmtInfo.PixelType == PixelType.Float || + fmtInfo.PixelType == PixelType.HalfFloat; + + if (isFloat || fmtInfo.Normalized || fmtInfo.Scaled) + { + VertexAttribType type = (VertexAttribType)fmtInfo.PixelType; + + GL.VertexAttribFormat(index, size, type, fmtInfo.Normalized, offset); + } + else + { + VertexAttribIntegerType type = (VertexAttribIntegerType)fmtInfo.PixelType; + + GL.VertexAttribIFormat(index, size, type, offset); + } + + GL.VertexAttribBinding(index, attrib.BufferIndex); + + _vertexAttribs[index] = attrib; + } + + for (; index < Constants.MaxVertexAttribs; index++) + { + DisableVertexAttrib(index); + } + } + + public void SetIndexBuffer(BufferRange range) + { + _indexBuffer = range; + GL.BindBuffer(BufferTarget.ElementArrayBuffer, range.Handle.ToInt32()); + } + + public void SetRangeOfIndexBuffer() + { + Buffer.Resize(_tempIndexBuffer, _indexBuffer.Size); + Buffer.Copy(_indexBuffer.Handle, _tempIndexBuffer, _indexBuffer.Offset, 0, _indexBuffer.Size); + GL.BindBuffer(BufferTarget.ElementArrayBuffer, _tempIndexBuffer.ToInt32()); + } + + public void RestoreIndexBuffer() + { + GL.BindBuffer(BufferTarget.ElementArrayBuffer, _indexBuffer.Handle.ToInt32()); + } + + public void PreDraw(int vertexCount) + { + LimitVertexBuffers(vertexCount); + } + + public void PreDrawVbUnbounded() + { + UnlimitVertexBuffers(); + } + + public void LimitVertexBuffers(int vertexCount) + { + // Is it possible for the draw to fetch outside the bounds of any vertex buffer currently bound? + + if (vertexCount <= _minVertexCount) + { + return; + } + + // If the draw can fetch out of bounds, let's ensure that it will only fetch zeros rather than memory garbage. + + int currentTempVbOffset = 0; + uint buffersInUse = _vertexBuffersInUse; + + while (buffersInUse != 0) + { + int vbIndex = BitOperations.TrailingZeroCount(buffersInUse); + + ref var vb = ref _vertexBuffers[vbIndex]; + + int requiredSize = vertexCount * vb.Stride; + + if (vb.Buffer.Size < requiredSize) + { + BufferHandle tempVertexBuffer = EnsureTempVertexBufferSize(currentTempVbOffset + requiredSize); + + Buffer.Copy(vb.Buffer.Handle, tempVertexBuffer, vb.Buffer.Offset, currentTempVbOffset, vb.Buffer.Size); + Buffer.Clear(tempVertexBuffer, currentTempVbOffset + vb.Buffer.Size, requiredSize - vb.Buffer.Size, 0); + + GL.BindVertexBuffer(vbIndex, tempVertexBuffer.ToInt32(), (IntPtr)currentTempVbOffset, vb.Stride); + + currentTempVbOffset += requiredSize; + _vertexBuffersLimited |= 1u << vbIndex; + } + + buffersInUse &= ~(1u << vbIndex); + } + } + + private BufferHandle EnsureTempVertexBufferSize(int size) + { + BufferHandle tempVertexBuffer = _tempVertexBuffer; + + if (_tempVertexBufferSize < size) + { + _tempVertexBufferSize = size; + + if (tempVertexBuffer == BufferHandle.Null) + { + tempVertexBuffer = Buffer.Create(size); + _tempVertexBuffer = tempVertexBuffer; + return tempVertexBuffer; + } + + Buffer.Resize(_tempVertexBuffer, size); + } + + return tempVertexBuffer; + } + + public void UnlimitVertexBuffers() + { + uint buffersLimited = _vertexBuffersLimited; + + if (buffersLimited == 0) + { + return; + } + + while (buffersLimited != 0) + { + int vbIndex = BitOperations.TrailingZeroCount(buffersLimited); + + ref var vb = ref _vertexBuffers[vbIndex]; + + GL.BindVertexBuffer(vbIndex, vb.Buffer.Handle.ToInt32(), (IntPtr)vb.Buffer.Offset, vb.Stride); + + buffersLimited &= ~(1u << vbIndex); + } + + _vertexBuffersLimited = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnableVertexAttrib(int index) + { + uint mask = 1u << index; + + if ((_vertexAttribsInUse & mask) == 0) + { + _vertexAttribsInUse |= mask; + GL.EnableVertexAttribArray(index); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DisableVertexAttrib(int index) + { + uint mask = 1u << index; + + if ((_vertexAttribsInUse & mask) != 0) + { + _vertexAttribsInUse &= ~mask; + GL.DisableVertexAttribArray(index); + GL.VertexAttrib4(index, 0f, 0f, 0f, 1f); + } + } + + public void Dispose() + { + if (Handle != 0) + { + GL.DeleteVertexArray(Handle); + + Handle = 0; + } + } + } +} diff --git a/src/Ryujinx.Graphics.OpenGL/Window.cs b/src/Ryujinx.Graphics.OpenGL/Window.cs new file mode 100644 index 00000000..6bcfefa4 --- /dev/null +++ b/src/Ryujinx.Graphics.OpenGL/Window.cs @@ -0,0 +1,413 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.OpenGL.Effects; +using Ryujinx.Graphics.OpenGL.Effects.Smaa; +using Ryujinx.Graphics.OpenGL.Image; +using System; + +namespace Ryujinx.Graphics.OpenGL +{ + class Window : IWindow, IDisposable + { + private readonly OpenGLRenderer _renderer; + + private bool _initialized; + + private int _width; + private int _height; + private bool _updateSize; + private int _copyFramebufferHandle; + private IPostProcessingEffect _antiAliasing; + private IScalingFilter _scalingFilter; + private bool _isLinear; + private AntiAliasing _currentAntiAliasing; + private bool _updateEffect; + private ScalingFilter _currentScalingFilter; + private float _scalingFilterLevel; + private bool _updateScalingFilter; + private bool _isBgra; + private TextureView _upscaledTexture; + + internal BackgroundContextWorker BackgroundContext { get; private set; } + + internal bool ScreenCaptureRequested { get; set; } + + public Window(OpenGLRenderer renderer) + { + _renderer = renderer; + } + + public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback) + { + GL.Disable(EnableCap.FramebufferSrgb); + + (int oldDrawFramebufferHandle, int oldReadFramebufferHandle) = ((Pipeline)_renderer.Pipeline).GetBoundFramebuffers(); + + CopyTextureToFrameBufferRGB(0, GetCopyFramebufferHandleLazy(), (TextureView)texture, crop, swapBuffersCallback); + + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle); + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle); + + GL.Enable(EnableCap.FramebufferSrgb); + + // Restore unpack alignment to 4, as performance overlays such as RTSS may change this to load their resources. + GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4); + } + + public void ChangeVSyncMode(bool vsyncEnabled) { } + + public void SetSize(int width, int height) + { + _width = width; + _height = height; + + _updateSize = true; + } + + private void CopyTextureToFrameBufferRGB(int drawFramebuffer, int readFramebuffer, TextureView view, ImageCrop crop, Action swapBuffersCallback) + { + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer); + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer); + + TextureView viewConverted = view.Format.IsBgr() ? _renderer.TextureCopy.BgraSwap(view) : view; + + UpdateEffect(); + + if (_antiAliasing != null) + { + var oldView = viewConverted; + + viewConverted = _antiAliasing.Run(viewConverted, _width, _height); + + if (viewConverted.Format.IsBgr()) + { + var swappedView = _renderer.TextureCopy.BgraSwap(viewConverted); + + viewConverted?.Dispose(); + + viewConverted = swappedView; + } + + if (viewConverted != oldView && oldView != view) + { + oldView.Dispose(); + } + } + + GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer); + GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer); + + GL.FramebufferTexture( + FramebufferTarget.ReadFramebuffer, + FramebufferAttachment.ColorAttachment0, + viewConverted.Handle, + 0); + + GL.ReadBuffer(ReadBufferMode.ColorAttachment0); + + GL.Disable(EnableCap.RasterizerDiscard); + GL.Disable(IndexedEnableCap.ScissorTest, 0); + + GL.Clear(ClearBufferMask.ColorBufferBit); + + int srcX0, srcX1, srcY0, srcY1; + + if (crop.Left == 0 && crop.Right == 0) + { + srcX0 = 0; + srcX1 = viewConverted.Width; + } + else + { + srcX0 = crop.Left; + srcX1 = crop.Right; + } + + if (crop.Top == 0 && crop.Bottom == 0) + { + srcY0 = 0; + srcY1 = viewConverted.Height; + } + else + { + srcY0 = crop.Top; + srcY1 = crop.Bottom; + } + + float ratioX = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _height * crop.AspectRatioX / (_width * crop.AspectRatioY)); + float ratioY = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _width * crop.AspectRatioY / (_height * crop.AspectRatioX)); + + int dstWidth = (int)(_width * ratioX); + int dstHeight = (int)(_height * ratioY); + + int dstPaddingX = (_width - dstWidth) / 2; + int dstPaddingY = (_height - dstHeight) / 2; + + int dstX0 = crop.FlipX ? _width - dstPaddingX : dstPaddingX; + int dstX1 = crop.FlipX ? dstPaddingX : _width - dstPaddingX; + + int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY; + int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY; + + if (ScreenCaptureRequested) + { + CaptureFrame(srcX0, srcY0, srcX1, srcY1, view.Format.IsBgr(), crop.FlipX, crop.FlipY); + + ScreenCaptureRequested = false; + } + + if (_scalingFilter != null) + { + if (viewConverted.Format.IsBgr() && !_isBgra) + { + RecreateUpscalingTexture(true); + } + + _scalingFilter.Run( + viewConverted, + _upscaledTexture, + _width, + _height, + new Extents2D( + srcX0, + srcY0, + srcX1, + srcY1), + new Extents2D( + dstX0, + dstY0, + dstX1, + dstY1) + ); + + srcX0 = dstX0; + srcY0 = dstY0; + srcX1 = dstX1; + srcY1 = dstY1; + + GL.FramebufferTexture( + FramebufferTarget.ReadFramebuffer, + FramebufferAttachment.ColorAttachment0, + _upscaledTexture.Handle, + 0); + } + + GL.BlitFramebuffer( + srcX0, + srcY0, + srcX1, + srcY1, + dstX0, + dstY0, + dstX1, + dstY1, + ClearBufferMask.ColorBufferBit, + _isLinear ? BlitFramebufferFilter.Linear : BlitFramebufferFilter.Nearest); + + // Remove Alpha channel + GL.ColorMask(false, false, false, true); + GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f); + GL.Clear(ClearBufferMask.ColorBufferBit); + + for (int i = 0; i < Constants.MaxRenderTargets; i++) + { + ((Pipeline)_renderer.Pipeline).RestoreComponentMask(i); + } + + // 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(); + + ((Pipeline)_renderer.Pipeline).RestoreClipControl(); + ((Pipeline)_renderer.Pipeline).RestoreScissor0Enable(); + ((Pipeline)_renderer.Pipeline).RestoreRasterizerDiscard(); + ((Pipeline)_renderer.Pipeline).RestoreViewport0(); + + if (viewConverted != view) + { + viewConverted.Dispose(); + } + } + + private int GetCopyFramebufferHandleLazy() + { + int handle = _copyFramebufferHandle; + + if (handle == 0) + { + handle = GL.GenFramebuffer(); + + _copyFramebufferHandle = handle; + } + + return handle; + } + + public void InitializeBackgroundContext(IOpenGLContext baseContext) + { + BackgroundContext = new BackgroundContextWorker(baseContext); + _initialized = true; + } + + public void CaptureFrame(int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY) + { + long size = Math.Abs(4 * width * height); + byte[] bitmap = new byte[size]; + + GL.ReadPixels(x, y, width, height, isBgra ? PixelFormat.Bgra : PixelFormat.Rgba, PixelType.UnsignedByte, bitmap); + + _renderer.OnScreenCaptured(new ScreenCaptureImageInfo(width, height, isBgra, bitmap, flipX, flipY)); + } + + public void Dispose() + { + if (!_initialized) + { + return; + } + + BackgroundContext.Dispose(); + + if (_copyFramebufferHandle != 0) + { + GL.DeleteFramebuffer(_copyFramebufferHandle); + + _copyFramebufferHandle = 0; + } + + _antiAliasing?.Dispose(); + _scalingFilter?.Dispose(); + _upscaledTexture?.Dispose(); + } + + public void SetAntiAliasing(AntiAliasing effect) + { + if (_currentAntiAliasing == effect && _antiAliasing != null) + { + return; + } + + _currentAntiAliasing = effect; + + _updateEffect = true; + } + + public void SetScalingFilter(ScalingFilter type) + { + if (_currentScalingFilter == type && _antiAliasing != null) + { + return; + } + + _currentScalingFilter = type; + + _updateScalingFilter = true; + } + + public void SetColorSpacePassthrough(bool colorSpacePassthroughEnabled) { } + + private void UpdateEffect() + { + if (_updateEffect) + { + _updateEffect = false; + + switch (_currentAntiAliasing) + { + case AntiAliasing.Fxaa: + _antiAliasing?.Dispose(); + _antiAliasing = new FxaaPostProcessingEffect(_renderer); + break; + case AntiAliasing.None: + _antiAliasing?.Dispose(); + _antiAliasing = null; + break; + case AntiAliasing.SmaaLow: + case AntiAliasing.SmaaMedium: + case AntiAliasing.SmaaHigh: + case AntiAliasing.SmaaUltra: + var quality = _currentAntiAliasing - AntiAliasing.SmaaLow; + if (_antiAliasing is SmaaPostProcessingEffect smaa) + { + smaa.Quality = quality; + } + else + { + _antiAliasing?.Dispose(); + _antiAliasing = new SmaaPostProcessingEffect(_renderer, quality); + } + break; + } + } + + if (_updateSize && !_updateScalingFilter) + { + RecreateUpscalingTexture(); + } + + _updateSize = false; + + if (_updateScalingFilter) + { + _updateScalingFilter = false; + + switch (_currentScalingFilter) + { + case ScalingFilter.Bilinear: + case ScalingFilter.Nearest: + _scalingFilter?.Dispose(); + _scalingFilter = null; + _isLinear = _currentScalingFilter == ScalingFilter.Bilinear; + _upscaledTexture?.Dispose(); + _upscaledTexture = null; + break; + case ScalingFilter.Fsr: + if (_scalingFilter is not FsrScalingFilter) + { + _scalingFilter?.Dispose(); + _scalingFilter = new FsrScalingFilter(_renderer); + } + _isLinear = false; + _scalingFilter.Level = _scalingFilterLevel; + + RecreateUpscalingTexture(); + break; + } + } + } + + private void RecreateUpscalingTexture(bool forceBgra = false) + { + _upscaledTexture?.Dispose(); + + var info = new TextureCreateInfo( + _width, + _height, + 1, + 1, + 1, + 1, + 1, + 1, + Format.R8G8B8A8Unorm, + DepthStencilMode.Depth, + Target.Texture2D, + forceBgra ? SwizzleComponent.Blue : SwizzleComponent.Red, + SwizzleComponent.Green, + forceBgra ? SwizzleComponent.Red : SwizzleComponent.Blue, + SwizzleComponent.Alpha); + + _isBgra = forceBgra; + _upscaledTexture = _renderer.CreateTexture(info) as TextureView; + } + + public void SetScalingFilterLevel(float level) + { + _scalingFilterLevel = level; + _updateScalingFilter = true; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/AlphaTestOp.cs b/src/Ryujinx.Graphics.Shader/AlphaTestOp.cs new file mode 100644 index 00000000..13958ea4 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/AlphaTestOp.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum AlphaTestOp + { + Never = 1, + Less, + Equal, + LessOrEqual, + Greater, + NotEqual, + GreaterOrEqual, + Always, + } +} diff --git a/src/Ryujinx.Graphics.Shader/AttributeType.cs b/src/Ryujinx.Graphics.Shader/AttributeType.cs new file mode 100644 index 00000000..d2d146ec --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/AttributeType.cs @@ -0,0 +1,46 @@ +using Ryujinx.Graphics.Shader.Translation; +using System; + +namespace Ryujinx.Graphics.Shader +{ + public enum AttributeType : byte + { + // Generic types. + Float, + Sint, + Uint, + Sscaled, + Uscaled, + + Packed = 1 << 6, + PackedRgb10A2Signed = 1 << 7, + AnyPacked = Packed | PackedRgb10A2Signed, + } + + static class AttributeTypeExtensions + { + public static AggregateType ToAggregateType(this AttributeType type) + { + return (type & ~AttributeType.AnyPacked) switch + { + AttributeType.Float => AggregateType.FP32, + AttributeType.Sint => AggregateType.S32, + AttributeType.Uint => AggregateType.U32, + _ => throw new ArgumentException($"Invalid attribute type \"{type}\"."), + }; + } + + public static AggregateType ToAggregateType(this AttributeType type, bool supportsScaledFormats) + { + return (type & ~AttributeType.AnyPacked) switch + { + AttributeType.Float => AggregateType.FP32, + AttributeType.Sint => AggregateType.S32, + AttributeType.Uint => AggregateType.U32, + AttributeType.Sscaled => supportsScaledFormats ? AggregateType.FP32 : AggregateType.S32, + AttributeType.Uscaled => supportsScaledFormats ? AggregateType.FP32 : AggregateType.U32, + _ => throw new ArgumentException($"Invalid attribute type \"{type}\"."), + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/BufferDescriptor.cs b/src/Ryujinx.Graphics.Shader/BufferDescriptor.cs new file mode 100644 index 00000000..11d4e3c1 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/BufferDescriptor.cs @@ -0,0 +1,34 @@ +namespace Ryujinx.Graphics.Shader +{ + public readonly struct BufferDescriptor + { + // New fields should be added to the end of the struct to keep disk shader cache compatibility. + + public readonly int Set; + public readonly int Binding; + public readonly byte Slot; + public readonly byte SbCbSlot; + public readonly ushort SbCbOffset; + public readonly BufferUsageFlags Flags; + + public BufferDescriptor(int set, int binding, int slot) + { + Set = set; + Binding = binding; + Slot = (byte)slot; + SbCbSlot = 0; + SbCbOffset = 0; + Flags = BufferUsageFlags.None; + } + + public BufferDescriptor(int set, int binding, int slot, int sbCbSlot, int sbCbOffset, BufferUsageFlags flags) + { + Set = set; + Binding = binding; + Slot = (byte)slot; + SbCbSlot = (byte)sbCbSlot; + SbCbOffset = (ushort)sbCbOffset; + Flags = flags; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/BufferUsageFlags.cs b/src/Ryujinx.Graphics.Shader/BufferUsageFlags.cs new file mode 100644 index 00000000..10b98483 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/BufferUsageFlags.cs @@ -0,0 +1,18 @@ +using System; + +namespace Ryujinx.Graphics.Shader +{ + /// + /// Flags that indicate how a buffer will be used in a shader. + /// + [Flags] + public enum BufferUsageFlags : byte + { + None = 0, + + /// + /// Buffer is written to. + /// + Write = 1 << 0, + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/CodeGenParameters.cs b/src/Ryujinx.Graphics.Shader/CodeGen/CodeGenParameters.cs new file mode 100644 index 00000000..f692c428 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/CodeGenParameters.cs @@ -0,0 +1,31 @@ +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader.CodeGen +{ + readonly struct CodeGenParameters + { + public readonly AttributeUsage AttributeUsage; + public readonly ShaderDefinitions Definitions; + public readonly ShaderProperties Properties; + public readonly HostCapabilities HostCapabilities; + public readonly ILogger Logger; + public readonly TargetApi TargetApi; + + public CodeGenParameters( + AttributeUsage attributeUsage, + ShaderDefinitions definitions, + ShaderProperties properties, + HostCapabilities hostCapabilities, + ILogger logger, + TargetApi targetApi) + { + AttributeUsage = attributeUsage; + Definitions = definitions; + Properties = properties; + HostCapabilities = hostCapabilities; + Logger = logger; + TargetApi = targetApi; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs new file mode 100644 index 00000000..cd9c7128 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs @@ -0,0 +1,105 @@ +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System.Text; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + class CodeGenContext + { + public const string Tab = " "; + + public StructuredFunction CurrentFunction { get; set; } + + public StructuredProgramInfo Info { get; } + + public AttributeUsage AttributeUsage { get; } + public ShaderDefinitions Definitions { get; } + public ShaderProperties Properties { get; } + public HostCapabilities HostCapabilities { get; } + public ILogger Logger { get; } + public TargetApi TargetApi { get; } + + public OperandManager OperandManager { get; } + + private readonly StringBuilder _sb; + + private int _level; + + private string _indentation; + + public CodeGenContext(StructuredProgramInfo info, CodeGenParameters parameters) + { + Info = info; + AttributeUsage = parameters.AttributeUsage; + Definitions = parameters.Definitions; + Properties = parameters.Properties; + HostCapabilities = parameters.HostCapabilities; + Logger = parameters.Logger; + TargetApi = parameters.TargetApi; + + OperandManager = new OperandManager(); + + _sb = new StringBuilder(); + } + + public void AppendLine() + { + _sb.AppendLine(); + } + + public void AppendLine(string str) + { + _sb.AppendLine(_indentation + str); + } + + public string GetCode() + { + return _sb.ToString(); + } + + public void EnterScope() + { + AppendLine("{"); + + _level++; + + UpdateIndentation(); + } + + public void LeaveScope(string suffix = "") + { + if (_level == 0) + { + return; + } + + _level--; + + UpdateIndentation(); + + AppendLine("}" + suffix); + } + + public StructuredFunction GetFunction(int id) + { + return Info.Functions[id]; + } + + private void UpdateIndentation() + { + _indentation = GetIndentation(_level); + } + + private static string GetIndentation(int level) + { + StringBuilder indentationBuilder = new(); + + for (int index = 0; index < level; index++) + { + indentationBuilder.Append(Tab); + } + + return indentationBuilder.ToString(); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs new file mode 100644 index 00000000..eb6c689b --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -0,0 +1,663 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class Declarations + { + public static void Declare(CodeGenContext context, StructuredProgramInfo info) + { + context.AppendLine(context.TargetApi == TargetApi.Vulkan ? "#version 460 core" : "#version 450 core"); + context.AppendLine("#extension GL_ARB_gpu_shader_int64 : enable"); + + if (context.HostCapabilities.SupportsShaderBallot) + { + context.AppendLine("#extension GL_ARB_shader_ballot : enable"); + } + else + { + context.AppendLine("#extension GL_KHR_shader_subgroup_basic : enable"); + context.AppendLine("#extension GL_KHR_shader_subgroup_ballot : enable"); + context.AppendLine("#extension GL_KHR_shader_subgroup_shuffle : enable"); + } + + context.AppendLine("#extension GL_ARB_shader_group_vote : enable"); + context.AppendLine("#extension GL_EXT_shader_image_load_formatted : enable"); + context.AppendLine("#extension GL_EXT_texture_shadow_lod : enable"); + + if (context.Definitions.Stage == ShaderStage.Compute) + { + context.AppendLine("#extension GL_ARB_compute_shader : enable"); + } + else if (context.Definitions.Stage == ShaderStage.Fragment) + { + if (context.HostCapabilities.SupportsFragmentShaderInterlock) + { + context.AppendLine("#extension GL_ARB_fragment_shader_interlock : enable"); + } + else if (context.HostCapabilities.SupportsFragmentShaderOrderingIntel) + { + context.AppendLine("#extension GL_INTEL_fragment_shader_ordering : enable"); + } + } + else + { + if (context.Definitions.Stage == ShaderStage.Vertex) + { + context.AppendLine("#extension GL_ARB_shader_draw_parameters : enable"); + } + + context.AppendLine("#extension GL_ARB_shader_viewport_layer_array : enable"); + } + + if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough) + { + context.AppendLine("#extension GL_NV_geometry_shader_passthrough : enable"); + } + + if (context.HostCapabilities.SupportsViewportMask) + { + context.AppendLine("#extension GL_NV_viewport_array2 : enable"); + } + + context.AppendLine("#pragma optionNV(fastmath off)"); + context.AppendLine(); + + context.AppendLine($"const int {DefaultNames.UndefinedName} = 0;"); + context.AppendLine(); + + DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values); + DeclareStorageBuffers(context, context.Properties.StorageBuffers.Values); + DeclareMemories(context, context.Properties.LocalMemories.Values, isShared: false); + DeclareMemories(context, context.Properties.SharedMemories.Values, isShared: true); + DeclareSamplers(context, context.Properties.Textures.Values); + DeclareImages(context, context.Properties.Images.Values); + + if (context.Definitions.Stage != ShaderStage.Compute) + { + if (context.Definitions.Stage == ShaderStage.Geometry) + { + string inPrimitive = context.Definitions.InputTopology.ToGlslString(); + + context.AppendLine($"layout (invocations = {context.Definitions.ThreadsPerInputPrimitive}, {inPrimitive}) in;"); + + if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough) + { + context.AppendLine($"layout (passthrough) in gl_PerVertex"); + context.EnterScope(); + context.AppendLine("vec4 gl_Position;"); + context.AppendLine("float gl_PointSize;"); + context.AppendLine("float gl_ClipDistance[];"); + context.LeaveScope(";"); + } + else + { + string outPrimitive = context.Definitions.OutputTopology.ToGlslString(); + int maxOutputVertices = context.Definitions.MaxOutputVertices; + + context.AppendLine($"layout ({outPrimitive}, max_vertices = {maxOutputVertices}) out;"); + } + + context.AppendLine(); + } + else if (context.Definitions.Stage == ShaderStage.TessellationControl) + { + int threadsPerInputPrimitive = context.Definitions.ThreadsPerInputPrimitive; + + context.AppendLine($"layout (vertices = {threadsPerInputPrimitive}) out;"); + context.AppendLine(); + } + else if (context.Definitions.Stage == ShaderStage.TessellationEvaluation) + { + bool tessCw = context.Definitions.TessCw; + + if (context.TargetApi == TargetApi.Vulkan) + { + // We invert the front face on Vulkan backend, so we need to do that here aswell. + tessCw = !tessCw; + } + + string patchType = context.Definitions.TessPatchType.ToGlsl(); + string spacing = context.Definitions.TessSpacing.ToGlsl(); + string windingOrder = tessCw ? "cw" : "ccw"; + + context.AppendLine($"layout ({patchType}, {spacing}, {windingOrder}) in;"); + context.AppendLine(); + } + + static bool IsUserDefined(IoDefinition ioDefinition, StorageKind storageKind) + { + return ioDefinition.StorageKind == storageKind && ioDefinition.IoVariable == IoVariable.UserDefined; + } + + static bool IsUserDefinedOutput(ShaderStage stage, IoDefinition ioDefinition) + { + IoVariable ioVariable = stage == ShaderStage.Fragment ? IoVariable.FragmentOutputColor : IoVariable.UserDefined; + return ioDefinition.StorageKind == StorageKind.Output && ioDefinition.IoVariable == ioVariable; + } + + DeclareInputAttributes(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.Input))); + DeclareOutputAttributes(context, info.IoDefinitions.Where(x => IsUserDefinedOutput(context.Definitions.Stage, x))); + DeclareInputAttributesPerPatch(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.InputPerPatch))); + DeclareOutputAttributesPerPatch(context, info.IoDefinitions.Where(x => IsUserDefined(x, StorageKind.OutputPerPatch))); + + if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline) + { + var tfOutput = context.Definitions.GetTransformFeedbackOutput(AttributeConsts.PositionX); + if (tfOutput.Valid) + { + context.AppendLine($"layout (xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}) out gl_PerVertex"); + context.EnterScope(); + context.AppendLine("vec4 gl_Position;"); + context.LeaveScope(context.Definitions.Stage == ShaderStage.TessellationControl ? " gl_out[];" : ";"); + } + } + } + else + { + string localSizeX = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeX); + string localSizeY = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeY); + string localSizeZ = NumberFormatter.FormatInt(context.Definitions.ComputeLocalSizeZ); + + context.AppendLine( + "layout (" + + $"local_size_x = {localSizeX}, " + + $"local_size_y = {localSizeY}, " + + $"local_size_z = {localSizeZ}) in;"); + context.AppendLine(); + } + + if (context.Definitions.Stage == ShaderStage.Fragment) + { + if (context.Definitions.EarlyZForce) + { + context.AppendLine("layout (early_fragment_tests) in;"); + context.AppendLine(); + } + + if (context.Definitions.OriginUpperLeft) + { + context.AppendLine("layout (origin_upper_left) in vec4 gl_FragCoord;"); + context.AppendLine(); + } + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighS32) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl"); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighU32) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl"); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.SwizzleAdd) != 0) + { + AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl"); + } + } + + public static void DeclareLocals(CodeGenContext context, StructuredFunction function) + { + foreach (AstOperand decl in function.Locals) + { + string name = context.OperandManager.DeclareLocal(decl); + + context.AppendLine(GetVarTypeName(context, decl.VarType) + " " + name + ";"); + } + } + + public static string GetVarTypeName(CodeGenContext context, AggregateType type, bool precise = true) + { + if (context.HostCapabilities.ReducedPrecision) + { + precise = false; + } + + return type switch + { + AggregateType.Void => "void", + AggregateType.Bool => "bool", + AggregateType.FP32 => precise ? "precise float" : "float", + AggregateType.FP64 => "double", + AggregateType.S32 => "int", + AggregateType.U32 => "uint", + AggregateType.Vector2 | AggregateType.Bool => "bvec2", + AggregateType.Vector2 | AggregateType.FP32 => precise ? "precise vec2" : "vec2", + AggregateType.Vector2 | AggregateType.FP64 => "dvec2", + AggregateType.Vector2 | AggregateType.S32 => "ivec2", + AggregateType.Vector2 | AggregateType.U32 => "uvec2", + AggregateType.Vector3 | AggregateType.Bool => "bvec3", + AggregateType.Vector3 | AggregateType.FP32 => precise ? "precise vec3" : "vec3", + AggregateType.Vector3 | AggregateType.FP64 => "dvec3", + AggregateType.Vector3 | AggregateType.S32 => "ivec3", + AggregateType.Vector3 | AggregateType.U32 => "uvec3", + AggregateType.Vector4 | AggregateType.Bool => "bvec4", + AggregateType.Vector4 | AggregateType.FP32 => precise ? "precise vec4" : "vec4", + AggregateType.Vector4 | AggregateType.FP64 => "dvec4", + AggregateType.Vector4 | AggregateType.S32 => "ivec4", + AggregateType.Vector4 | AggregateType.U32 => "uvec4", + _ => throw new ArgumentException($"Invalid variable type \"{type}\"."), + }; + } + + private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable buffers) + { + DeclareBuffers(context, buffers, "uniform"); + } + + private static void DeclareStorageBuffers(CodeGenContext context, IEnumerable buffers) + { + DeclareBuffers(context, buffers, "buffer"); + } + + private static void DeclareBuffers(CodeGenContext context, IEnumerable buffers, string declType) + { + foreach (BufferDefinition buffer in buffers) + { + string layout = buffer.Layout switch + { + BufferLayout.Std140 => "std140", + _ => "std430", + }; + + string set = string.Empty; + + if (context.TargetApi == TargetApi.Vulkan) + { + set = $"set = {buffer.Set}, "; + } + + context.AppendLine($"layout ({set}binding = {buffer.Binding}, {layout}) {declType} _{buffer.Name}"); + context.EnterScope(); + + foreach (StructureField field in buffer.Type.Fields) + { + if (field.Type.HasFlag(AggregateType.Array)) + { + string typeName = GetVarTypeName(context, field.Type & ~AggregateType.Array); + + if (field.ArrayLength > 0) + { + string arraySize = field.ArrayLength.ToString(CultureInfo.InvariantCulture); + + context.AppendLine($"{typeName} {field.Name}[{arraySize}];"); + } + else + { + context.AppendLine($"{typeName} {field.Name}[];"); + } + } + else + { + string typeName = GetVarTypeName(context, field.Type); + + context.AppendLine($"{typeName} {field.Name};"); + } + } + + context.LeaveScope($" {buffer.Name};"); + context.AppendLine(); + } + } + + private static void DeclareMemories(CodeGenContext context, IEnumerable memories, bool isShared) + { + string prefix = isShared ? "shared " : string.Empty; + + foreach (MemoryDefinition memory in memories) + { + string typeName = GetVarTypeName(context, memory.Type & ~AggregateType.Array); + + if (memory.Type.HasFlag(AggregateType.Array)) + { + if (memory.ArrayLength > 0) + { + string arraySize = memory.ArrayLength.ToString(CultureInfo.InvariantCulture); + + context.AppendLine($"{prefix}{typeName} {memory.Name}[{arraySize}];"); + } + else + { + context.AppendLine($"{prefix}{typeName} {memory.Name}[];"); + } + } + else + { + context.AppendLine($"{prefix}{typeName} {memory.Name};"); + } + } + } + + private static void DeclareSamplers(CodeGenContext context, IEnumerable definitions) + { + foreach (var definition in definitions) + { + string arrayDecl = string.Empty; + + if (definition.ArrayLength > 1) + { + arrayDecl = $"[{NumberFormatter.FormatInt(definition.ArrayLength)}]"; + } + else if (definition.ArrayLength == 0) + { + arrayDecl = "[]"; + } + + string samplerTypeName = definition.Separate ? definition.Type.ToGlslTextureType() : definition.Type.ToGlslSamplerType(); + + string layout = string.Empty; + + if (context.TargetApi == TargetApi.Vulkan) + { + layout = $", set = {definition.Set}"; + } + + context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {samplerTypeName} {definition.Name}{arrayDecl};"); + } + } + + private static void DeclareImages(CodeGenContext context, IEnumerable definitions) + { + foreach (var definition in definitions) + { + string arrayDecl = string.Empty; + + if (definition.ArrayLength > 1) + { + arrayDecl = $"[{NumberFormatter.FormatInt(definition.ArrayLength)}]"; + } + else if (definition.ArrayLength == 0) + { + arrayDecl = "[]"; + } + + string imageTypeName = definition.Type.ToGlslImageType(definition.Format.GetComponentType()); + + if (definition.Flags.HasFlag(TextureUsageFlags.ImageCoherent)) + { + imageTypeName = "coherent " + imageTypeName; + } + + string layout = definition.Format.ToGlslFormat(); + + if (!string.IsNullOrEmpty(layout)) + { + layout = ", " + layout; + } + + if (context.TargetApi == TargetApi.Vulkan) + { + layout = $", set = {definition.Set}{layout}"; + } + + context.AppendLine($"layout (binding = {definition.Binding}{layout}) uniform {imageTypeName} {definition.Name}{arrayDecl};"); + } + } + + private static void DeclareInputAttributes(CodeGenContext context, IEnumerable inputs) + { + if (context.Definitions.IaIndexing) + { + string suffix = context.Definitions.Stage == ShaderStage.Geometry ? "[]" : string.Empty; + + context.AppendLine($"layout (location = 0) in vec4 {DefaultNames.IAttributePrefix}{suffix}[{Constants.MaxAttributes}];"); + context.AppendLine(); + } + else + { + foreach (var ioDefinition in inputs.OrderBy(x => x.Location)) + { + DeclareInputAttribute(context, ioDefinition.Location, ioDefinition.Component); + } + + if (inputs.Any()) + { + context.AppendLine(); + } + } + } + + private static void DeclareInputAttributesPerPatch(CodeGenContext context, IEnumerable inputs) + { + foreach (var ioDefinition in inputs.OrderBy(x => x.Location)) + { + DeclareInputAttributePerPatch(context, ioDefinition.Location); + } + + if (inputs.Any()) + { + context.AppendLine(); + } + } + + private static void DeclareInputAttribute(CodeGenContext context, int location, int component) + { + string suffix = IsArrayAttributeGlsl(context.Definitions.Stage, isOutAttr: false) ? "[]" : string.Empty; + string iq = string.Empty; + + if (context.Definitions.Stage == ShaderStage.Fragment) + { + iq = context.Definitions.ImapTypes[location].GetFirstUsedType() switch + { + PixelImap.Constant => "flat ", + PixelImap.ScreenLinear => "noperspective ", + _ => string.Empty, + }; + } + + string name = $"{DefaultNames.IAttributePrefix}{location}"; + + if (context.Definitions.TransformFeedbackEnabled && context.Definitions.Stage == ShaderStage.Fragment) + { + bool hasComponent = context.Definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput: false); + + if (hasComponent) + { + char swzMask = "xyzw"[component]; + + context.AppendLine($"layout (location = {location}, component = {component}) {iq}in float {name}_{swzMask}{suffix};"); + } + else + { + int components = context.Definitions.GetTransformFeedbackOutputComponents(location, 0); + + string type = components switch + { + 2 => "vec2", + 3 => "vec3", + 4 => "vec4", + _ => "float", + }; + + context.AppendLine($"layout (location = {location}) in {type} {name};"); + } + } + else + { + bool passthrough = (context.AttributeUsage.PassthroughAttributes & (1 << location)) != 0; + string pass = passthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough ? "passthrough, " : string.Empty; + string type = GetVarTypeName(context, context.Definitions.GetUserDefinedType(location, isOutput: false), false); + + context.AppendLine($"layout ({pass}location = {location}) {iq}in {type} {name}{suffix};"); + } + } + + private static void DeclareInputAttributePerPatch(CodeGenContext context, int patchLocation) + { + int location = context.AttributeUsage.GetPerPatchAttributeLocation(patchLocation); + string name = $"{DefaultNames.PerPatchAttributePrefix}{patchLocation}"; + + context.AppendLine($"layout (location = {location}) patch in vec4 {name};"); + } + + private static void DeclareOutputAttributes(CodeGenContext context, IEnumerable outputs) + { + if (context.Definitions.OaIndexing) + { + context.AppendLine($"layout (location = 0) out vec4 {DefaultNames.OAttributePrefix}[{Constants.MaxAttributes}];"); + context.AppendLine(); + } + else + { + outputs = outputs.OrderBy(x => x.Location); + + if (context.Definitions.Stage == ShaderStage.Fragment && context.Definitions.DualSourceBlend) + { + IoDefinition firstOutput = outputs.ElementAtOrDefault(0); + IoDefinition secondOutput = outputs.ElementAtOrDefault(1); + + if (firstOutput.Location + 1 == secondOutput.Location) + { + DeclareOutputDualSourceBlendAttribute(context, firstOutput.Location); + outputs = outputs.Skip(2); + } + } + + foreach (var ioDefinition in outputs) + { + DeclareOutputAttribute(context, ioDefinition.Location, ioDefinition.Component); + } + + if (outputs.Any()) + { + context.AppendLine(); + } + } + } + + private static void DeclareOutputAttribute(CodeGenContext context, int location, int component) + { + string suffix = IsArrayAttributeGlsl(context.Definitions.Stage, isOutAttr: true) ? "[]" : string.Empty; + string name = $"{DefaultNames.OAttributePrefix}{location}{suffix}"; + + if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline) + { + bool hasComponent = context.Definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput: true); + + if (hasComponent) + { + char swzMask = "xyzw"[component]; + + string xfb = string.Empty; + + var tfOutput = context.Definitions.GetTransformFeedbackOutput(location, component); + if (tfOutput.Valid) + { + xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}"; + } + + context.AppendLine($"layout (location = {location}, component = {component}{xfb}) out float {name}_{swzMask};"); + } + else + { + int components = context.Definitions.GetTransformFeedbackOutputComponents(location, 0); + + string type = components switch + { + 2 => "vec2", + 3 => "vec3", + 4 => "vec4", + _ => "float", + }; + + string xfb = string.Empty; + + var tfOutput = context.Definitions.GetTransformFeedbackOutput(location, 0); + if (tfOutput.Valid) + { + xfb = $", xfb_buffer = {tfOutput.Buffer}, xfb_offset = {tfOutput.Offset}, xfb_stride = {tfOutput.Stride}"; + } + + context.AppendLine($"layout (location = {location}{xfb}) out {type} {name};"); + } + } + else + { + string type = context.Definitions.Stage != ShaderStage.Fragment ? "vec4" : + GetVarTypeName(context, context.Definitions.GetFragmentOutputColorType(location), false); + + if (context.HostCapabilities.ReducedPrecision && context.Definitions.Stage == ShaderStage.Vertex && location == 0) + { + context.AppendLine($"layout (location = {location}) invariant out {type} {name};"); + } + else + { + context.AppendLine($"layout (location = {location}) out {type} {name};"); + } + } + } + + private static void DeclareOutputDualSourceBlendAttribute(CodeGenContext context, int location) + { + string name = $"{DefaultNames.OAttributePrefix}{location}"; + string name2 = $"{DefaultNames.OAttributePrefix}{(location + 1)}"; + + context.AppendLine($"layout (location = {location}, index = 0) out vec4 {name};"); + context.AppendLine($"layout (location = {location}, index = 1) out vec4 {name2};"); + } + + private static void DeclareOutputAttributesPerPatch(CodeGenContext context, IEnumerable outputs) + { + foreach (var ioDefinition in outputs) + { + DeclareOutputAttributePerPatch(context, ioDefinition.Location); + } + + if (outputs.Any()) + { + context.AppendLine(); + } + } + + private static void DeclareOutputAttributePerPatch(CodeGenContext context, int patchLocation) + { + int location = context.AttributeUsage.GetPerPatchAttributeLocation(patchLocation); + string name = $"{DefaultNames.PerPatchAttributePrefix}{patchLocation}"; + + context.AppendLine($"layout (location = {location}) patch out vec4 {name};"); + } + + 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 AppendHelperFunction(CodeGenContext context, string filename) + { + string code = EmbeddedResources.ReadAllText(filename); + + code = code.Replace("\t", CodeGenContext.Tab); + + if (context.HostCapabilities.SupportsShaderBallot) + { + code = code.Replace("$SUBGROUP_INVOCATION$", "gl_SubGroupInvocationARB"); + code = code.Replace("$SUBGROUP_BROADCAST$", "readInvocationARB"); + } + else + { + code = code.Replace("$SUBGROUP_INVOCATION$", "gl_SubgroupInvocationID"); + code = code.Replace("$SUBGROUP_BROADCAST$", "subgroupBroadcast"); + } + + context.AppendLine(code); + context.AppendLine(); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs new file mode 100644 index 00000000..54bf9aeb --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class DefaultNames + { + public const string LocalNamePrefix = "temp"; + + public const string PerPatchAttributePrefix = "patch_attr_"; + public const string IAttributePrefix = "in_attr"; + public const string OAttributePrefix = "out_attr"; + + public const string ArgumentNamePrefix = "a"; + + public const string UndefinedName = "undef"; + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs new file mode 100644 index 00000000..469c4f0a --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs @@ -0,0 +1,177 @@ +using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.TypeConversion; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class GlslGenerator + { + private const string MainFunctionName = "main"; + + public static string Generate(StructuredProgramInfo info, CodeGenParameters parameters) + { + CodeGenContext context = new(info, parameters); + + Declarations.Declare(context, info); + + if (info.Functions.Count != 0) + { + for (int i = 1; i < info.Functions.Count; i++) + { + context.AppendLine($"{GetFunctionSignature(context, info.Functions[i])};"); + } + + context.AppendLine(); + + for (int i = 1; i < info.Functions.Count; i++) + { + PrintFunction(context, info.Functions[i]); + + context.AppendLine(); + } + } + + PrintFunction(context, info.Functions[0], MainFunctionName); + + return context.GetCode(); + } + + private static void PrintFunction(CodeGenContext context, StructuredFunction function, string funcName = null) + { + context.CurrentFunction = function; + + context.AppendLine(GetFunctionSignature(context, function, funcName)); + context.EnterScope(); + + Declarations.DeclareLocals(context, function); + + PrintBlock(context, function.MainBlock, funcName == MainFunctionName); + + context.LeaveScope(); + } + + private static string GetFunctionSignature(CodeGenContext context, StructuredFunction function, string funcName = null) + { + string[] args = new string[function.InArguments.Length + function.OutArguments.Length]; + + for (int i = 0; i < function.InArguments.Length; i++) + { + args[i] = $"{Declarations.GetVarTypeName(context, function.InArguments[i])} {OperandManager.GetArgumentName(i)}"; + } + + for (int i = 0; i < function.OutArguments.Length; i++) + { + int j = i + function.InArguments.Length; + + args[j] = $"out {Declarations.GetVarTypeName(context, function.OutArguments[i])} {OperandManager.GetArgumentName(j)}"; + } + + return $"{Declarations.GetVarTypeName(context, function.ReturnType)} {funcName ?? function.Name}({string.Join(", ", args)})"; + } + + private static void PrintBlock(CodeGenContext context, AstBlock block, bool isMainFunction) + { + AstBlockVisitor visitor = new(block); + + visitor.BlockEntered += (sender, e) => + { + switch (e.Block.Type) + { + case AstBlockType.DoWhile: + context.AppendLine("do"); + break; + + case AstBlockType.Else: + context.AppendLine("else"); + break; + + case AstBlockType.ElseIf: + context.AppendLine($"else if ({GetCondExpr(context, e.Block.Condition)})"); + break; + + case AstBlockType.If: + context.AppendLine($"if ({GetCondExpr(context, e.Block.Condition)})"); + break; + + default: + throw new InvalidOperationException($"Found unexpected block type \"{e.Block.Type}\"."); + } + + context.EnterScope(); + }; + + visitor.BlockLeft += (sender, e) => + { + context.LeaveScope(); + + if (e.Block.Type == AstBlockType.DoWhile) + { + context.AppendLine($"while ({GetCondExpr(context, e.Block.Condition)});"); + } + }; + + bool supportsBarrierDivergence = context.HostCapabilities.SupportsShaderBarrierDivergence; + bool mayHaveReturned = false; + + foreach (IAstNode node in visitor.Visit()) + { + if (node is AstOperation operation) + { + if (!supportsBarrierDivergence) + { + if (operation.Inst == IntermediateRepresentation.Instruction.Barrier) + { + // Barrier on divergent control flow paths may cause the GPU to hang, + // so skip emitting the barrier for those cases. + if (visitor.Block.Type != AstBlockType.Main || mayHaveReturned || !isMainFunction) + { + context.Logger.Log("Shader has barrier on potentially divergent block, the barrier will be removed."); + + continue; + } + } + else if (operation.Inst == IntermediateRepresentation.Instruction.Return) + { + mayHaveReturned = true; + } + } + + string expr = InstGen.GetExpression(context, operation); + + if (expr != null) + { + context.AppendLine(expr + ";"); + } + } + else if (node is AstAssignment assignment) + { + AggregateType dstType = OperandManager.GetNodeDestType(context, assignment.Destination); + AggregateType srcType = OperandManager.GetNodeDestType(context, assignment.Source); + + string dest = InstGen.GetExpression(context, assignment.Destination); + string src = ReinterpretCast(context, assignment.Source, srcType, dstType); + + context.AppendLine(dest + " = " + src + ";"); + } + else if (node is AstComment comment) + { + context.AppendLine("// " + comment.Comment); + } + else + { + throw new InvalidOperationException($"Found unexpected node type \"{node?.GetType().Name ?? "null"}\"."); + } + } + } + + private static string GetCondExpr(CodeGenContext context, IAstNode cond) + { + AggregateType srcType = OperandManager.GetNodeDestType(context, cond); + + return ReinterpretCast(context, cond, srcType, AggregateType.Bool); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs new file mode 100644 index 00000000..0b80ac2b --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class HelperFunctionNames + { + public static string MultiplyHighS32 = "Helper_MultiplyHighS32"; + public static string MultiplyHighU32 = "Helper_MultiplyHighU32"; + + public static string SwizzleAdd = "Helper_SwizzleAdd"; + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl new file mode 100644 index 00000000..caad6f56 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl @@ -0,0 +1,7 @@ +int Helper_MultiplyHighS32(int x, int y) +{ + int msb; + int lsb; + imulExtended(x, y, msb, lsb); + return msb; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl new file mode 100644 index 00000000..617a925f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl @@ -0,0 +1,7 @@ +uint Helper_MultiplyHighU32(uint x, uint y) +{ + uint msb; + uint lsb; + umulExtended(x, y, msb, lsb); + return msb; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl new file mode 100644 index 00000000..057cb6ca --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl @@ -0,0 +1,7 @@ +float Helper_SwizzleAdd(float x, float y, int mask) +{ + vec4 xLut = vec4(1.0, -1.0, 1.0, 0.0); + vec4 yLut = vec4(1.0, 1.0, -1.0, 1.0); + int lutIdx = (mask >> (int($SUBGROUP_INVOCATION$ & 3u) * 2)) & 3; + return x * xLut[lutIdx] + y * yLut[lutIdx]; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs new file mode 100644 index 00000000..9e7f64b0 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs @@ -0,0 +1,208 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Text; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenBallot; +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenCall; +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenFSI; +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenMemory; +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenPacking; +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenShuffle; +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenVector; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGen + { + public static string GetExpression(CodeGenContext context, IAstNode node) + { + if (node is AstOperation operation) + { + return GetExpression(context, operation); + } + else if (node is AstOperand operand) + { + return context.OperandManager.GetExpression(context, operand); + } + + throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\"."); + } + + public static string Negate(CodeGenContext context, AstOperation operation, InstInfo info) + { + IAstNode src = operation.GetSource(0); + + AggregateType type = GetSrcVarType(operation.Inst, 0); + + string srcExpr = GetSourceExpr(context, src, type); + string zero; + + if (type == AggregateType.FP64) + { + zero = "0.0"; + } + else + { + NumberFormatter.TryFormat(0, type, out zero); + } + + // Starting in the 496.13 NVIDIA driver, there's an issue with assigning variables to negated expressions. + // (-expr) does not work, but (0.0 - expr) does. This should be removed once the issue is resolved. + + return $"{zero} - {Enclose(srcExpr, src, operation.Inst, info, false)}"; + } + + private static string GetExpression(CodeGenContext context, AstOperation operation) + { + Instruction inst = operation.Inst; + + InstInfo info = GetInstructionInfo(inst); + + if ((info.Type & InstType.Call) != 0) + { + bool atomic = (info.Type & InstType.Atomic) != 0; + + int arity = (int)(info.Type & InstType.ArityMask); + + StringBuilder builder = new(); + + if (atomic && (operation.StorageKind == StorageKind.StorageBuffer || operation.StorageKind == StorageKind.SharedMemory)) + { + builder.Append(GenerateLoadOrStore(context, operation, isStore: false)); + + AggregateType dstType = operation.Inst == Instruction.AtomicMaxS32 || operation.Inst == Instruction.AtomicMinS32 + ? AggregateType.S32 + : AggregateType.U32; + + for (int argIndex = operation.SourcesCount - arity + 2; argIndex < operation.SourcesCount; argIndex++) + { + builder.Append($", {GetSourceExpr(context, operation.GetSource(argIndex), dstType)}"); + } + } + else + { + for (int argIndex = 0; argIndex < arity; argIndex++) + { + if (argIndex != 0) + { + builder.Append(", "); + } + + AggregateType dstType = GetSrcVarType(inst, argIndex); + + builder.Append(GetSourceExpr(context, operation.GetSource(argIndex), dstType)); + } + } + + return $"{info.OpName}({builder})"; + } + else if ((info.Type & InstType.Op) != 0) + { + string op = info.OpName; + + // Return may optionally have a return value (and in this case it is unary). + if (inst == Instruction.Return && operation.SourcesCount != 0) + { + return $"{op} {GetSourceExpr(context, operation.GetSource(0), context.CurrentFunction.ReturnType)}"; + } + + int arity = (int)(info.Type & InstType.ArityMask); + + string[] expr = new string[arity]; + + for (int index = 0; index < arity; index++) + { + IAstNode src = operation.GetSource(index); + + string srcExpr = GetSourceExpr(context, src, GetSrcVarType(inst, index)); + + bool isLhs = arity == 2 && index == 0; + + expr[index] = Enclose(srcExpr, src, inst, info, isLhs); + } + + switch (arity) + { + case 0: + return op; + + case 1: + return op + expr[0]; + + case 2: + return $"{expr[0]} {op} {expr[1]}"; + + case 3: + return $"{expr[0]} {op[0]} {expr[1]} {op[1]} {expr[2]}"; + } + } + else if ((info.Type & InstType.Special) != 0) + { + switch (inst & Instruction.Mask) + { + case Instruction.Ballot: + return Ballot(context, operation); + + case Instruction.Call: + return Call(context, operation); + + case Instruction.FSIBegin: + return FSIBegin(context); + + case Instruction.FSIEnd: + return FSIEnd(context); + + case Instruction.ImageLoad: + case Instruction.ImageStore: + case Instruction.ImageAtomic: + return ImageLoadOrStore(context, operation); + + case Instruction.Load: + return Load(context, operation); + + case Instruction.Lod: + return Lod(context, operation); + + case Instruction.Negate: + return Negate(context, operation, info); + + case Instruction.PackDouble2x32: + return PackDouble2x32(context, operation); + + case Instruction.PackHalf2x16: + return PackHalf2x16(context, operation); + + case Instruction.Shuffle: + return Shuffle(context, operation); + + case Instruction.Store: + return Store(context, operation); + + case Instruction.TextureSample: + return TextureSample(context, operation); + + case Instruction.TextureQuerySamples: + return TextureQuerySamples(context, operation); + + case Instruction.TextureQuerySize: + return TextureQuerySize(context, operation); + + case Instruction.UnpackDouble2x32: + return UnpackDouble2x32(context, operation); + + case Instruction.UnpackHalf2x16: + return UnpackHalf2x16(context, operation); + + case Instruction.VectorExtract: + return VectorExtract(context, operation); + } + } + + throw new InvalidOperationException($"Unexpected instruction type \"{info.Type}\"."); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs new file mode 100644 index 00000000..000d7f79 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenBallot + { + public static string Ballot(CodeGenContext context, AstOperation operation) + { + AggregateType dstType = GetSrcVarType(operation.Inst, 0); + + string arg = GetSourceExpr(context, operation.GetSource(0), dstType); + char component = "xyzw"[operation.Index]; + + if (context.HostCapabilities.SupportsShaderBallot) + { + return $"unpackUint2x32(ballotARB({arg})).{component}"; + } + else + { + return $"subgroupBallot({arg}).{component}"; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenCall.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenCall.cs new file mode 100644 index 00000000..d5448856 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenCall.cs @@ -0,0 +1,29 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System.Diagnostics; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenCall + { + public static string Call(CodeGenContext context, AstOperation operation) + { + AstOperand funcId = (AstOperand)operation.GetSource(0); + + Debug.Assert(funcId.Type == OperandType.Constant); + + var function = context.GetFunction(funcId.Value); + + string[] args = new string[operation.SourcesCount - 1]; + + for (int i = 0; i < args.Length; i++) + { + args[i] = GetSourceExpr(context, operation.GetSource(i + 1), function.GetArgumentType(i)); + } + + return $"{function.Name}({string.Join(", ", args)})"; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenFSI.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenFSI.cs new file mode 100644 index 00000000..1697aa47 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenFSI.cs @@ -0,0 +1,29 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenFSI + { + public static string FSIBegin(CodeGenContext context) + { + if (context.HostCapabilities.SupportsFragmentShaderInterlock) + { + return "beginInvocationInterlockARB()"; + } + else if (context.HostCapabilities.SupportsFragmentShaderOrderingIntel) + { + return "beginFragmentShaderOrderingINTEL()"; + } + + return null; + } + + public static string FSIEnd(CodeGenContext context) + { + if (context.HostCapabilities.SupportsFragmentShaderInterlock) + { + return "endInvocationInterlockARB()"; + } + + return null; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs new file mode 100644 index 00000000..4b28f387 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs @@ -0,0 +1,222 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.TypeConversion; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenHelper + { + private static readonly InstInfo[] _infoTable; + + static InstGenHelper() + { + _infoTable = new InstInfo[(int)Instruction.Count]; + +#pragma warning disable IDE0055 // Disable formatting + Add(Instruction.AtomicAdd, InstType.AtomicBinary, "atomicAdd"); + Add(Instruction.AtomicAnd, InstType.AtomicBinary, "atomicAnd"); + Add(Instruction.AtomicCompareAndSwap, InstType.AtomicTernary, "atomicCompSwap"); + Add(Instruction.AtomicMaxU32, InstType.AtomicBinary, "atomicMax"); + Add(Instruction.AtomicMinU32, InstType.AtomicBinary, "atomicMin"); + Add(Instruction.AtomicOr, InstType.AtomicBinary, "atomicOr"); + Add(Instruction.AtomicSwap, InstType.AtomicBinary, "atomicExchange"); + Add(Instruction.AtomicXor, InstType.AtomicBinary, "atomicXor"); + Add(Instruction.Absolute, InstType.CallUnary, "abs"); + Add(Instruction.Add, InstType.OpBinaryCom, "+", 2); + Add(Instruction.Ballot, InstType.Special); + Add(Instruction.Barrier, InstType.CallNullary, "barrier"); + Add(Instruction.BitCount, InstType.CallUnary, "bitCount"); + Add(Instruction.BitfieldExtractS32, InstType.CallTernary, "bitfieldExtract"); + Add(Instruction.BitfieldExtractU32, InstType.CallTernary, "bitfieldExtract"); + Add(Instruction.BitfieldInsert, InstType.CallQuaternary, "bitfieldInsert"); + Add(Instruction.BitfieldReverse, InstType.CallUnary, "bitfieldReverse"); + Add(Instruction.BitwiseAnd, InstType.OpBinaryCom, "&", 6); + Add(Instruction.BitwiseExclusiveOr, InstType.OpBinaryCom, "^", 7); + Add(Instruction.BitwiseNot, InstType.OpUnary, "~", 0); + Add(Instruction.BitwiseOr, InstType.OpBinaryCom, "|", 8); + Add(Instruction.Call, InstType.Special); + Add(Instruction.Ceiling, InstType.CallUnary, "ceil"); + Add(Instruction.Clamp, InstType.CallTernary, "clamp"); + Add(Instruction.ClampU32, InstType.CallTernary, "clamp"); + Add(Instruction.CompareEqual, InstType.OpBinaryCom, "==", 5); + Add(Instruction.CompareGreater, InstType.OpBinary, ">", 4); + Add(Instruction.CompareGreaterOrEqual, InstType.OpBinary, ">=", 4); + Add(Instruction.CompareGreaterOrEqualU32, InstType.OpBinary, ">=", 4); + Add(Instruction.CompareGreaterU32, InstType.OpBinary, ">", 4); + Add(Instruction.CompareLess, InstType.OpBinary, "<", 4); + Add(Instruction.CompareLessOrEqual, InstType.OpBinary, "<=", 4); + Add(Instruction.CompareLessOrEqualU32, InstType.OpBinary, "<=", 4); + Add(Instruction.CompareLessU32, InstType.OpBinary, "<", 4); + Add(Instruction.CompareNotEqual, InstType.OpBinaryCom, "!=", 5); + Add(Instruction.ConditionalSelect, InstType.OpTernary, "?:", 12); + Add(Instruction.ConvertFP32ToFP64, InstType.CallUnary, "double"); + Add(Instruction.ConvertFP64ToFP32, InstType.CallUnary, "float"); + Add(Instruction.ConvertFP32ToS32, InstType.CallUnary, "int"); + Add(Instruction.ConvertFP32ToU32, InstType.CallUnary, "uint"); + Add(Instruction.ConvertFP64ToS32, InstType.CallUnary, "int"); + Add(Instruction.ConvertFP64ToU32, InstType.CallUnary, "uint"); + Add(Instruction.ConvertS32ToFP32, InstType.CallUnary, "float"); + Add(Instruction.ConvertS32ToFP64, InstType.CallUnary, "double"); + Add(Instruction.ConvertU32ToFP32, InstType.CallUnary, "float"); + Add(Instruction.ConvertU32ToFP64, InstType.CallUnary, "double"); + Add(Instruction.Cosine, InstType.CallUnary, "cos"); + Add(Instruction.Ddx, InstType.CallUnary, "dFdx"); + Add(Instruction.Ddy, InstType.CallUnary, "dFdy"); + Add(Instruction.Discard, InstType.OpNullary, "discard"); + Add(Instruction.Divide, InstType.OpBinary, "/", 1); + Add(Instruction.EmitVertex, InstType.CallNullary, "EmitVertex"); + Add(Instruction.EndPrimitive, InstType.CallNullary, "EndPrimitive"); + Add(Instruction.ExponentB2, InstType.CallUnary, "exp2"); + Add(Instruction.FSIBegin, InstType.Special); + Add(Instruction.FSIEnd, InstType.Special); + Add(Instruction.FindLSB, InstType.CallUnary, "findLSB"); + Add(Instruction.FindMSBS32, InstType.CallUnary, "findMSB"); + Add(Instruction.FindMSBU32, InstType.CallUnary, "findMSB"); + Add(Instruction.Floor, InstType.CallUnary, "floor"); + Add(Instruction.FusedMultiplyAdd, InstType.CallTernary, "fma"); + Add(Instruction.GroupMemoryBarrier, InstType.CallNullary, "groupMemoryBarrier"); + Add(Instruction.ImageLoad, InstType.Special); + Add(Instruction.ImageStore, InstType.Special); + Add(Instruction.ImageAtomic, InstType.Special); + Add(Instruction.IsNan, InstType.CallUnary, "isnan"); + Add(Instruction.Load, InstType.Special); + Add(Instruction.Lod, InstType.Special); + Add(Instruction.LogarithmB2, InstType.CallUnary, "log2"); + Add(Instruction.LogicalAnd, InstType.OpBinaryCom, "&&", 9); + Add(Instruction.LogicalExclusiveOr, InstType.OpBinaryCom, "^^", 10); + Add(Instruction.LogicalNot, InstType.OpUnary, "!", 0); + Add(Instruction.LogicalOr, InstType.OpBinaryCom, "||", 11); + Add(Instruction.LoopBreak, InstType.OpNullary, "break"); + Add(Instruction.LoopContinue, InstType.OpNullary, "continue"); + Add(Instruction.PackDouble2x32, InstType.Special); + Add(Instruction.PackHalf2x16, InstType.Special); + Add(Instruction.Maximum, InstType.CallBinary, "max"); + Add(Instruction.MaximumU32, InstType.CallBinary, "max"); + Add(Instruction.MemoryBarrier, InstType.CallNullary, "memoryBarrier"); + Add(Instruction.Minimum, InstType.CallBinary, "min"); + Add(Instruction.MinimumU32, InstType.CallBinary, "min"); + Add(Instruction.Modulo, InstType.CallBinary, "mod"); + Add(Instruction.Multiply, InstType.OpBinaryCom, "*", 1); + Add(Instruction.MultiplyHighS32, InstType.CallBinary, HelperFunctionNames.MultiplyHighS32); + Add(Instruction.MultiplyHighU32, InstType.CallBinary, HelperFunctionNames.MultiplyHighU32); + Add(Instruction.Negate, InstType.Special); + Add(Instruction.ReciprocalSquareRoot, InstType.CallUnary, "inversesqrt"); + Add(Instruction.Return, InstType.OpNullary, "return"); + Add(Instruction.Round, InstType.CallUnary, "roundEven"); + Add(Instruction.ShiftLeft, InstType.OpBinary, "<<", 3); + Add(Instruction.ShiftRightS32, InstType.OpBinary, ">>", 3); + Add(Instruction.ShiftRightU32, InstType.OpBinary, ">>", 3); + Add(Instruction.Shuffle, InstType.Special); + Add(Instruction.ShuffleDown, InstType.CallBinary, "subgroupShuffleDown"); + Add(Instruction.ShuffleUp, InstType.CallBinary, "subgroupShuffleUp"); + Add(Instruction.ShuffleXor, InstType.CallBinary, "subgroupShuffleXor"); + Add(Instruction.Sine, InstType.CallUnary, "sin"); + Add(Instruction.SquareRoot, InstType.CallUnary, "sqrt"); + Add(Instruction.Store, InstType.Special); + Add(Instruction.Subtract, InstType.OpBinary, "-", 2); + Add(Instruction.SwizzleAdd, InstType.CallTernary, HelperFunctionNames.SwizzleAdd); + Add(Instruction.TextureSample, InstType.Special); + Add(Instruction.TextureQuerySamples, InstType.Special); + Add(Instruction.TextureQuerySize, InstType.Special); + Add(Instruction.Truncate, InstType.CallUnary, "trunc"); + Add(Instruction.UnpackDouble2x32, InstType.Special); + Add(Instruction.UnpackHalf2x16, InstType.Special); + Add(Instruction.VectorExtract, InstType.Special); + Add(Instruction.VoteAll, InstType.CallUnary, "allInvocationsARB"); + Add(Instruction.VoteAllEqual, InstType.CallUnary, "allInvocationsEqualARB"); + Add(Instruction.VoteAny, InstType.CallUnary, "anyInvocationARB"); +#pragma warning restore IDE0055 + } + + private static void Add(Instruction inst, InstType flags, string opName = null, int precedence = 0) + { + _infoTable[(int)inst] = new InstInfo(flags, opName, precedence); + } + + public static InstInfo GetInstructionInfo(Instruction inst) + { + return _infoTable[(int)(inst & Instruction.Mask)]; + } + + public static string GetSourceExpr(CodeGenContext context, IAstNode node, AggregateType dstType) + { + return ReinterpretCast(context, node, OperandManager.GetNodeDestType(context, node), dstType); + } + + public static string Enclose(string expr, IAstNode node, Instruction pInst, bool isLhs) + { + InstInfo pInfo = GetInstructionInfo(pInst); + + return Enclose(expr, node, pInst, pInfo, isLhs); + } + + public static string Enclose(string expr, IAstNode node, Instruction pInst, InstInfo pInfo, bool isLhs = false) + { + if (NeedsParenthesis(node, pInst, pInfo, isLhs)) + { + expr = "(" + expr + ")"; + } + + return expr; + } + + public static bool NeedsParenthesis(IAstNode node, Instruction pInst, InstInfo pInfo, bool isLhs) + { + // If the node isn't a operation, then it can only be a operand, + // and those never needs to be surrounded in parenthesis. + if (node is not AstOperation operation) + { + // This is sort of a special case, if this is a negative constant, + // and it is consumed by a unary operation, we need to put on the parenthesis, + // as in GLSL a sequence like --2 or ~-1 is not valid. + if (IsNegativeConst(node) && pInfo.Type == InstType.OpUnary) + { + return true; + } + + return false; + } + + if ((pInfo.Type & (InstType.Call | InstType.Special)) != 0) + { + return false; + } + + InstInfo info = _infoTable[(int)(operation.Inst & Instruction.Mask)]; + + if ((info.Type & (InstType.Call | InstType.Special)) != 0) + { + return false; + } + + if (info.Precedence < pInfo.Precedence) + { + return false; + } + + if (info.Precedence == pInfo.Precedence && isLhs) + { + return false; + } + + if (pInst == operation.Inst && info.Type == InstType.OpBinaryCom) + { + return false; + } + + return true; + } + + private static bool IsNegativeConst(IAstNode node) + { + if (node is not AstOperand operand) + { + return false; + } + + return operand.Type == OperandType.Constant && operand.Value < 0; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs new file mode 100644 index 00000000..4308b08f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs @@ -0,0 +1,700 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Text; +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenMemory + { + public static string ImageLoadOrStore(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + + var texCallBuilder = new StringBuilder(); + + if (texOp.Inst == Instruction.ImageAtomic) + { + texCallBuilder.Append((texOp.Flags & TextureFlags.AtomicMask) switch + { +#pragma warning disable IDE0055 // Disable formatting + TextureFlags.Add => "imageAtomicAdd", + TextureFlags.Minimum => "imageAtomicMin", + TextureFlags.Maximum => "imageAtomicMax", + TextureFlags.Increment => "imageAtomicAdd", // TODO: Clamp value. + TextureFlags.Decrement => "imageAtomicAdd", // TODO: Clamp value. + TextureFlags.BitwiseAnd => "imageAtomicAnd", + TextureFlags.BitwiseOr => "imageAtomicOr", + TextureFlags.BitwiseXor => "imageAtomicXor", + TextureFlags.Swap => "imageAtomicExchange", + TextureFlags.CAS => "imageAtomicCompSwap", + _ => "imageAtomicAdd", +#pragma warning restore IDE0055 + }); + } + else + { + texCallBuilder.Append(texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore"); + } + + int srcIndex = 0; + + string Src(AggregateType type) + { + return GetSourceExpr(context, texOp.GetSource(srcIndex++), type); + } + + string imageName = GetImageName(context, texOp, ref srcIndex); + + texCallBuilder.Append('('); + texCallBuilder.Append(imageName); + + int coordsCount = texOp.Type.GetDimensions(); + + int pCount = coordsCount + (isArray ? 1 : 0); + + void Append(string str) + { + texCallBuilder.Append(", "); + texCallBuilder.Append(str); + } + + if (pCount > 1) + { + string[] elems = new string[pCount]; + + for (int index = 0; index < pCount; index++) + { + elems[index] = Src(AggregateType.S32); + } + + Append($"ivec{pCount}({string.Join(", ", elems)})"); + } + else + { + Append(Src(AggregateType.S32)); + } + + if (texOp.Inst == Instruction.ImageStore) + { + AggregateType type = texOp.Format.GetComponentType(); + + string[] cElems = new string[4]; + + for (int index = 0; index < 4; index++) + { + if (srcIndex < texOp.SourcesCount) + { + cElems[index] = Src(type); + } + else + { + cElems[index] = type switch + { + AggregateType.S32 => NumberFormatter.FormatInt(0), + AggregateType.U32 => NumberFormatter.FormatUint(0), + _ => NumberFormatter.FormatFloat(0), + }; + } + } + + string prefix = type switch + { + AggregateType.S32 => "i", + AggregateType.U32 => "u", + _ => string.Empty, + }; + + Append($"{prefix}vec4({string.Join(", ", cElems)})"); + } + + if (texOp.Inst == Instruction.ImageAtomic) + { + AggregateType type = texOp.Format.GetComponentType(); + + if ((texOp.Flags & TextureFlags.AtomicMask) == TextureFlags.CAS) + { + Append(Src(type)); // Compare value. + } + + string value = (texOp.Flags & TextureFlags.AtomicMask) switch + { + TextureFlags.Increment => NumberFormatter.FormatInt(1, type), // TODO: Clamp value + TextureFlags.Decrement => NumberFormatter.FormatInt(-1, type), // TODO: Clamp value + _ => Src(type), + }; + + Append(value); + + texCallBuilder.Append(')'); + + if (type != AggregateType.S32) + { + texCallBuilder + .Insert(0, "int(") + .Append(')'); + } + } + else + { + texCallBuilder.Append(')'); + + if (texOp.Inst == Instruction.ImageLoad) + { + texCallBuilder.Append(GetMaskMultiDest(texOp.Index)); + } + } + + return texCallBuilder.ToString(); + } + + public static string Load(CodeGenContext context, AstOperation operation) + { + return GenerateLoadOrStore(context, operation, isStore: false); + } + + public static string Lod(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + int coordsCount = texOp.Type.GetDimensions(); + int coordsIndex = 0; + + string samplerName = GetSamplerName(context, texOp, ref coordsIndex); + + string coordsExpr; + + if (coordsCount > 1) + { + string[] elems = new string[coordsCount]; + + for (int index = 0; index < coordsCount; index++) + { + elems[index] = GetSourceExpr(context, texOp.GetSource(coordsIndex + index), AggregateType.FP32); + } + + coordsExpr = "vec" + coordsCount + "(" + string.Join(", ", elems) + ")"; + } + else + { + coordsExpr = GetSourceExpr(context, texOp.GetSource(coordsIndex), AggregateType.FP32); + } + + return $"textureQueryLod({samplerName}, {coordsExpr}){GetMask(texOp.Index)}"; + } + + public static string Store(CodeGenContext context, AstOperation operation) + { + return GenerateLoadOrStore(context, operation, isStore: true); + } + + public static string TextureSample(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; + bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0; + bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; + bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0; + bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; + bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; + + SamplerType type = texOp.Type & SamplerType.Mask; + + bool is2D = type == SamplerType.Texture2D; + bool isCube = type == SamplerType.TextureCube; + + // 2D Array and Cube shadow samplers with LOD level or bias requires an extension. + // If the extension is not supported, just remove the LOD parameter. + if (isArray && isShadow && (is2D || isCube) && !context.HostCapabilities.SupportsTextureShadowLod) + { + hasLodBias = false; + hasLodLevel = false; + } + + // Cube shadow samplers with LOD level requires an extension. + // If the extension is not supported, just remove the LOD level parameter. + if (isShadow && isCube && !context.HostCapabilities.SupportsTextureShadowLod) + { + hasLodLevel = false; + } + + string texCall = intCoords ? "texelFetch" : "texture"; + + if (isGather) + { + texCall += "Gather"; + } + else if (hasDerivatives) + { + texCall += "Grad"; + } + else if (hasLodLevel && !intCoords) + { + texCall += "Lod"; + } + + if (hasOffset) + { + texCall += "Offset"; + } + else if (hasOffsets) + { + texCall += "Offsets"; + } + + int srcIndex = 0; + + string Src(AggregateType type) + { + return GetSourceExpr(context, texOp.GetSource(srcIndex++), type); + } + + string samplerName = GetSamplerName(context, texOp, ref srcIndex); + + texCall += "(" + samplerName; + + int coordsCount = texOp.Type.GetDimensions(); + + int pCount = coordsCount; + + int arrayIndexElem = -1; + + if (isArray) + { + arrayIndexElem = pCount++; + } + + // The sampler 1D shadow overload expects a + // dummy value on the middle of the vector, who knows why... + bool hasDummy1DShadowElem = texOp.Type == (SamplerType.Texture1D | SamplerType.Shadow); + + if (hasDummy1DShadowElem) + { + pCount++; + } + + if (isShadow && !isGather) + { + pCount++; + } + + // On textureGather*, the comparison value is + // always specified as an extra argument. + bool hasExtraCompareArg = isShadow && isGather; + + if (pCount == 5) + { + pCount = 4; + + hasExtraCompareArg = true; + } + + void Append(string str) + { + texCall += ", " + str; + } + + AggregateType coordType = intCoords ? AggregateType.S32 : AggregateType.FP32; + + string AssemblePVector(int count) + { + if (count > 1) + { + string[] elems = new string[count]; + + for (int index = 0; index < count; index++) + { + if (arrayIndexElem == index) + { + elems[index] = Src(AggregateType.S32); + + if (!intCoords) + { + elems[index] = "float(" + elems[index] + ")"; + } + } + else if (index == 1 && hasDummy1DShadowElem) + { + elems[index] = NumberFormatter.FormatFloat(0); + } + else + { + elems[index] = Src(coordType); + } + } + + string prefix = intCoords ? "i" : string.Empty; + + return prefix + "vec" + count + "(" + string.Join(", ", elems) + ")"; + } + else + { + return Src(coordType); + } + } + + Append(AssemblePVector(pCount)); + + string AssembleDerivativesVector(int count) + { + if (count > 1) + { + string[] elems = new string[count]; + + for (int index = 0; index < count; index++) + { + elems[index] = Src(AggregateType.FP32); + } + + return "vec" + count + "(" + string.Join(", ", elems) + ")"; + } + else + { + return Src(AggregateType.FP32); + } + } + + if (hasExtraCompareArg) + { + Append(Src(AggregateType.FP32)); + } + + if (hasDerivatives) + { + Append(AssembleDerivativesVector(coordsCount)); // dPdx + Append(AssembleDerivativesVector(coordsCount)); // dPdy + } + + if (isMultisample) + { + Append(Src(AggregateType.S32)); + } + else if (hasLodLevel) + { + Append(Src(coordType)); + } + + string AssembleOffsetVector(int count) + { + if (count > 1) + { + string[] elems = new string[count]; + + for (int index = 0; index < count; index++) + { + elems[index] = Src(AggregateType.S32); + } + + return "ivec" + count + "(" + string.Join(", ", elems) + ")"; + } + else + { + return Src(AggregateType.S32); + } + } + + if (hasOffset) + { + Append(AssembleOffsetVector(coordsCount)); + } + else if (hasOffsets) + { + texCall += $", ivec{coordsCount}[4]("; + + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ", "; + texCall += AssembleOffsetVector(coordsCount) + ")"; + } + + if (hasLodBias) + { + Append(Src(AggregateType.FP32)); + } + + // textureGather* optional extra component index, + // not needed for shadow samplers. + if (isGather && !isShadow) + { + Append(Src(AggregateType.S32)); + } + + bool colorIsVector = isGather || !isShadow; + + texCall += ")" + (colorIsVector ? GetMaskMultiDest(texOp.Index) : ""); + + return texCall; + } + + public static string TextureQuerySamples(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + int srcIndex = 0; + + string samplerName = GetSamplerName(context, texOp, ref srcIndex); + + return $"textureSamples({samplerName})"; + } + + public static string TextureQuerySize(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + int srcIndex = 0; + + string samplerName = GetSamplerName(context, texOp, ref srcIndex); + + if (texOp.Index == 3) + { + return $"textureQueryLevels({samplerName})"; + } + else + { + context.Properties.Textures.TryGetValue(texOp.GetTextureSetAndBinding(), out TextureDefinition definition); + bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer; + string texCall; + + if (hasLod) + { + IAstNode lod = operation.GetSource(srcIndex); + string lodExpr = GetSourceExpr(context, lod, GetSrcVarType(operation.Inst, srcIndex)); + + texCall = $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}"; + } + else + { + texCall = $"textureSize({samplerName}){GetMask(texOp.Index)}"; + } + + return texCall; + } + } + + public static string GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore) + { + StorageKind storageKind = operation.StorageKind; + + string varName; + AggregateType varType; + int srcIndex = 0; + bool isStoreOrAtomic = operation.Inst == Instruction.Store || operation.Inst.IsAtomic(); + int inputsCount = isStoreOrAtomic ? operation.SourcesCount - 1 : operation.SourcesCount; + + if (operation.Inst == Instruction.AtomicCompareAndSwap) + { + inputsCount--; + } + + switch (storageKind) + { + case StorageKind.ConstantBuffer: + case StorageKind.StorageBuffer: + if (operation.GetSource(srcIndex++) is not AstOperand bindingIndex || bindingIndex.Type != OperandType.Constant) + { + throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand."); + } + + int binding = bindingIndex.Value; + BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer + ? context.Properties.ConstantBuffers[binding] + : context.Properties.StorageBuffers[binding]; + + if (operation.GetSource(srcIndex++) is not AstOperand fieldIndex || fieldIndex.Type != OperandType.Constant) + { + throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand."); + } + + StructureField field = buffer.Type.Fields[fieldIndex.Value]; + varName = $"{buffer.Name}.{field.Name}"; + varType = field.Type; + break; + + case StorageKind.LocalMemory: + case StorageKind.SharedMemory: + if (operation.GetSource(srcIndex++) is not AstOperand { Type: OperandType.Constant } bindingId) + { + throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand."); + } + + MemoryDefinition memory = storageKind == StorageKind.LocalMemory + ? context.Properties.LocalMemories[bindingId.Value] + : context.Properties.SharedMemories[bindingId.Value]; + + varName = memory.Name; + varType = memory.Type; + break; + + case StorageKind.Input: + case StorageKind.InputPerPatch: + case StorageKind.Output: + case StorageKind.OutputPerPatch: + if (operation.GetSource(srcIndex++) is not 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.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput)) + { + if (operation.GetSource(srcIndex++) is not 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.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput)) + { + component = elemIndex.Value; + srcIndex++; + } + } + + (varName, varType) = IoMap.GetGlslVariable( + context.Definitions, + context.HostCapabilities, + ioVariable, + location, + component, + isOutput, + isPerPatch); + + if (IoMap.IsPerVertexBuiltIn(context.Definitions.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 = GetSourceExpr(context, operation.GetSource(srcIndex++), AggregateType.S32); + varName = $"gl_in[{expr}].{varName}"; + } + else if (storageKind == StorageKind.Output) + { + string expr = GetSourceExpr(context, operation.GetSource(srcIndex++), AggregateType.S32); + varName = $"gl_out[{expr}].{varName}"; + } + } + break; + + default: + throw new InvalidOperationException($"Invalid storage kind {storageKind}."); + } + + int firstSrcIndex = srcIndex; + + for (; srcIndex < inputsCount; srcIndex++) + { + IAstNode src = operation.GetSource(srcIndex); + + if ((varType & AggregateType.ElementCountMask) != 0 && + srcIndex == inputsCount - 1 && + src is AstOperand elementIndex && + elementIndex.Type == OperandType.Constant) + { + varName += "." + "xyzw"[elementIndex.Value & 3]; + } + else if (srcIndex == firstSrcIndex && context.Definitions.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 += $"[{GetSourceExpr(context, src, AggregateType.S32)}]"; + } + } + + if (isStore) + { + varType &= AggregateType.ElementTypeMask; + varName = $"{varName} = {GetSourceExpr(context, operation.GetSource(srcIndex), varType)}"; + } + + return varName; + } + + private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex) + { + TextureDefinition textureDefinition = context.Properties.Textures[texOp.GetTextureSetAndBinding()]; + string name = textureDefinition.Name; + + if (textureDefinition.ArrayLength != 1) + { + name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]"; + } + + if (texOp.IsSeparate) + { + TextureDefinition samplerDefinition = context.Properties.Textures[texOp.GetSamplerSetAndBinding()]; + string samplerName = samplerDefinition.Name; + + if (samplerDefinition.ArrayLength != 1) + { + samplerName = $"{samplerName}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]"; + } + + name = $"{texOp.Type.ToGlslSamplerType()}({name}, {samplerName})"; + } + + return name; + } + + private static string GetImageName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex) + { + TextureDefinition definition = context.Properties.Images[texOp.GetTextureSetAndBinding()]; + string name = definition.Name; + + if (definition.ArrayLength != 1) + { + name = $"{name}[{GetSourceExpr(context, texOp.GetSource(srcIndex++), AggregateType.S32)}]"; + } + + return name; + } + + private static string GetMask(int index) + { + return $".{"rgba".AsSpan(index, 1)}"; + } + + private static string GetMaskMultiDest(int mask) + { + StringBuilder swizzleBuilder = new(); + swizzleBuilder.Append('.'); + + for (int i = 0; i < 4; i++) + { + if ((mask & (1 << i)) != 0) + { + swizzleBuilder.Append("xyzw"[i]); + } + } + + return swizzleBuilder.ToString(); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs new file mode 100644 index 00000000..4469785d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs @@ -0,0 +1,56 @@ +using Ryujinx.Graphics.Shader.StructuredIr; +using System; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenPacking + { + public static string PackDouble2x32(CodeGenContext context, AstOperation operation) + { + IAstNode src0 = operation.GetSource(0); + IAstNode src1 = operation.GetSource(1); + + string src0Expr = GetSourceExpr(context, src0, GetSrcVarType(operation.Inst, 0)); + string src1Expr = GetSourceExpr(context, src1, GetSrcVarType(operation.Inst, 1)); + + return $"packDouble2x32(uvec2({src0Expr}, {src1Expr}))"; + } + + public static string PackHalf2x16(CodeGenContext context, AstOperation operation) + { + IAstNode src0 = operation.GetSource(0); + IAstNode src1 = operation.GetSource(1); + + string src0Expr = GetSourceExpr(context, src0, GetSrcVarType(operation.Inst, 0)); + string src1Expr = GetSourceExpr(context, src1, GetSrcVarType(operation.Inst, 1)); + + return $"packHalf2x16(vec2({src0Expr}, {src1Expr}))"; + } + + public static string UnpackDouble2x32(CodeGenContext context, AstOperation operation) + { + IAstNode src = operation.GetSource(0); + + string srcExpr = GetSourceExpr(context, src, GetSrcVarType(operation.Inst, 0)); + + return $"unpackDouble2x32({srcExpr}){GetMask(operation.Index)}"; + } + + public static string UnpackHalf2x16(CodeGenContext context, AstOperation operation) + { + IAstNode src = operation.GetSource(0); + + string srcExpr = GetSourceExpr(context, src, GetSrcVarType(operation.Inst, 0)); + + return $"unpackHalf2x16({srcExpr}){GetMask(operation.Index)}"; + } + + private static string GetMask(int index) + { + return $".{"xy".AsSpan(index, 1)}"; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs new file mode 100644 index 00000000..b72b94d9 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenShuffle.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenShuffle + { + public static string Shuffle(CodeGenContext context, AstOperation operation) + { + string value = GetSourceExpr(context, operation.GetSource(0), AggregateType.FP32); + string index = GetSourceExpr(context, operation.GetSource(1), AggregateType.U32); + + if (context.HostCapabilities.SupportsShaderBallot) + { + return $"readInvocationARB({value}, {index})"; + } + else + { + return $"subgroupShuffle({value}, {index})"; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenVector.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenVector.cs new file mode 100644 index 00000000..a300c775 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenVector.cs @@ -0,0 +1,32 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; + +using static Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions.InstGenHelper; +using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + static class InstGenVector + { + public static string VectorExtract(CodeGenContext context, AstOperation operation) + { + IAstNode vector = operation.GetSource(0); + IAstNode index = operation.GetSource(1); + + string vectorExpr = GetSourceExpr(context, vector, OperandManager.GetNodeDestType(context, vector)); + + if (index is AstOperand indexOperand && indexOperand.Type == OperandType.Constant) + { + char elem = "xyzw"[indexOperand.Value]; + + return $"{vectorExpr}.{elem}"; + } + else + { + string indexExpr = GetSourceExpr(context, index, GetSrcVarType(operation.Inst, 1)); + + return $"{vectorExpr}[{indexExpr}]"; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstInfo.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstInfo.cs new file mode 100644 index 00000000..a784e2bb --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstInfo.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + readonly struct InstInfo + { + public InstType Type { get; } + + public string OpName { get; } + + public int Precedence { get; } + + public InstInfo(InstType type, string opName, int precedence) + { + Type = type; + OpName = opName; + Precedence = precedence; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstType.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstType.cs new file mode 100644 index 00000000..56985ae0 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstType.cs @@ -0,0 +1,35 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions +{ + [Flags] + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + enum InstType + { + OpNullary = Op | 0, + OpUnary = Op | 1, + OpBinary = Op | 2, + OpBinaryCom = Op | 2 | Commutative, + OpTernary = Op | 3, + + CallNullary = Call | 0, + CallUnary = Call | 1, + CallBinary = Call | 2, + CallTernary = Call | 3, + CallQuaternary = Call | 4, + + // The atomic instructions have one extra operand, + // for the storage slot and offset pair. + AtomicBinary = Call | Atomic | 3, + AtomicTernary = Call | Atomic | 4, + + Commutative = 1 << 8, + Op = 1 << 9, + Call = 1 << 10, + Atomic = 1 << 11, + Special = 1 << 12, + + ArityMask = 0xff, + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/IoMap.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/IoMap.cs new file mode 100644 index 00000000..caa6ef64 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/IoMap.cs @@ -0,0 +1,144 @@ +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( + ShaderDefinitions definitions, + HostCapabilities hostCapabilities, + 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(definitions, location), + IoVariable.FragmentOutputDepth => ("gl_FragDepth", AggregateType.FP32), + 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.GlobalId => ("gl_GlobalInvocationID", AggregateType.Vector3 | AggregateType.U32), + 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(definitions.Stage, isOutput), + IoVariable.SubgroupEqMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Eq"), + IoVariable.SubgroupGeMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Ge"), + IoVariable.SubgroupGtMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Gt"), + IoVariable.SubgroupLaneId => GetSubgroupInvocationIdVariableName(hostCapabilities.SupportsShaderBallot), + IoVariable.SubgroupLeMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Le"), + IoVariable.SubgroupLtMask => GetSubgroupMaskVariableName(hostCapabilities.SupportsShaderBallot, "Lt"), + 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(definitions, 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(ShaderDefinitions definitions, int location) + { + if (location < 0) + { + return (DefaultNames.OAttributePrefix, definitions.GetFragmentOutputColorType(0)); + } + + string name = DefaultNames.OAttributePrefix + location.ToString(CultureInfo.InvariantCulture); + + return (name, definitions.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(bool supportsShaderBallot, string cc) + { + return supportsShaderBallot + ? ($"unpackUint2x32(gl_SubGroup{cc}MaskARB)", AggregateType.Vector2 | AggregateType.U32) + : ($"gl_Subgroup{cc}Mask", AggregateType.Vector4 | AggregateType.U32); + } + + private static (string, AggregateType) GetSubgroupInvocationIdVariableName(bool supportsShaderBallot) + { + return supportsShaderBallot + ? ("gl_SubGroupInvocationARB", AggregateType.U32) + : ("gl_SubgroupInvocationID", AggregateType.U32); + } + + private static (string, AggregateType) GetUserDefinedVariableName(ShaderDefinitions definitions, int location, int component, bool isOutput, bool isPerPatch) + { + string name = isPerPatch + ? DefaultNames.PerPatchAttributePrefix + : (isOutput ? DefaultNames.OAttributePrefix : DefaultNames.IAttributePrefix); + + if (location < 0) + { + return (name, definitions.GetUserDefinedType(0, isOutput)); + } + + name += location.ToString(CultureInfo.InvariantCulture); + + if (definitions.HasPerLocationInputOrOutputComponent(IoVariable.UserDefined, location, component, isOutput)) + { + name += "_" + "xyzw"[component & 3]; + } + + return (name, definitions.GetUserDefinedType(location, isOutput)); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/NumberFormatter.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/NumberFormatter.cs new file mode 100644 index 00000000..28e44c90 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/NumberFormatter.cs @@ -0,0 +1,104 @@ +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Globalization; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class NumberFormatter + { + private const int MaxDecimal = 256; + + public static bool TryFormat(int value, AggregateType dstType, out string formatted) + { + if (dstType == AggregateType.FP32) + { + return TryFormatFloat(BitConverter.Int32BitsToSingle(value), out formatted); + } + else if (dstType == AggregateType.S32) + { + formatted = FormatInt(value); + } + else if (dstType == AggregateType.U32) + { + formatted = FormatUint((uint)value); + } + else if (dstType == AggregateType.Bool) + { + formatted = value != 0 ? "true" : "false"; + } + else + { + throw new ArgumentException($"Invalid variable type \"{dstType}\"."); + } + + return true; + } + + public static string FormatFloat(float value) + { + if (!TryFormatFloat(value, out string formatted)) + { + throw new ArgumentException("Failed to convert float value to string."); + } + + return formatted; + } + + public static bool TryFormatFloat(float value, out string formatted) + { + if (float.IsNaN(value) || float.IsInfinity(value)) + { + formatted = null; + + return false; + } + + formatted = value.ToString("G9", CultureInfo.InvariantCulture); + + if (!(formatted.Contains('.') || + formatted.Contains('e') || + formatted.Contains('E'))) + { + formatted += ".0"; + } + + return true; + } + + public static string FormatInt(int value, AggregateType dstType) + { + if (dstType == AggregateType.S32) + { + return FormatInt(value); + } + else if (dstType == AggregateType.U32) + { + return FormatUint((uint)value); + } + else + { + throw new ArgumentException($"Invalid variable type \"{dstType}\"."); + } + } + + public static string FormatInt(int value) + { + if (value <= MaxDecimal && value >= -MaxDecimal) + { + return value.ToString(CultureInfo.InvariantCulture); + } + + return "0x" + value.ToString("X", CultureInfo.InvariantCulture); + } + + public static string FormatUint(uint value) + { + if (value <= MaxDecimal && value >= 0) + { + return value.ToString(CultureInfo.InvariantCulture) + "u"; + } + + return "0x" + value.ToString("X", CultureInfo.InvariantCulture) + "u"; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs new file mode 100644 index 00000000..a350b089 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs @@ -0,0 +1,178 @@ +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 static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + class OperandManager + { + private readonly Dictionary _locals; + + public OperandManager() + { + _locals = new Dictionary(); + } + + public string DeclareLocal(AstOperand operand) + { + string name = $"{DefaultNames.LocalNamePrefix}_{_locals.Count}"; + + _locals.Add(operand, name); + + return name; + } + + public string GetExpression(CodeGenContext context, AstOperand operand) + { + return operand.Type switch + { + OperandType.Argument => GetArgumentName(operand.Value), + OperandType.Constant => NumberFormatter.FormatInt(operand.Value), + OperandType.LocalVariable => _locals[operand], + OperandType.Undefined => DefaultNames.UndefinedName, + _ => throw new ArgumentException($"Invalid operand type \"{operand.Type}\"."), + }; + } + + public static string GetArgumentName(int argIndex) + { + return $"{DefaultNames.ArgumentNamePrefix}{argIndex}"; + } + + 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.Load || operation.Inst.IsAtomic()) + { + switch (operation.StorageKind) + { + case StorageKind.ConstantBuffer: + case StorageKind.StorageBuffer: + if (operation.GetSource(0) is not AstOperand bindingIndex || bindingIndex.Type != OperandType.Constant) + { + throw new InvalidOperationException($"First input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand."); + } + + if (operation.GetSource(1) is not AstOperand fieldIndex || fieldIndex.Type != OperandType.Constant) + { + throw new InvalidOperationException($"Second input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand."); + } + + BufferDefinition buffer = operation.StorageKind == StorageKind.ConstantBuffer + ? context.Properties.ConstantBuffers[bindingIndex.Value] + : context.Properties.StorageBuffers[bindingIndex.Value]; + StructureField field = buffer.Type.Fields[fieldIndex.Value]; + + return field.Type & AggregateType.ElementTypeMask; + + case StorageKind.LocalMemory: + case StorageKind.SharedMemory: + if (operation.GetSource(0) is not AstOperand { Type: OperandType.Constant } bindingId) + { + throw new InvalidOperationException($"First input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand."); + } + + MemoryDefinition memory = operation.StorageKind == StorageKind.LocalMemory + ? context.Properties.LocalMemories[bindingId.Value] + : context.Properties.SharedMemories[bindingId.Value]; + + return memory.Type & AggregateType.ElementTypeMask; + + case StorageKind.Input: + case StorageKind.InputPerPatch: + case StorageKind.Output: + case StorageKind.OutputPerPatch: + if (operation.GetSource(0) is not AstOperand varId || varId.Type != OperandType.Constant) + { + throw new InvalidOperationException($"First input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand."); + } + + 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.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput)) + { + if (operation.GetSource(1) is not 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.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput)) + { + component = elemIndex.Value; + } + } + + (_, AggregateType varType) = IoMap.GetGlslVariable( + context.Definitions, + context.HostCapabilities, + ioVariable, + location, + component, + isOutput, + isPerPatch); + + return varType & AggregateType.ElementTypeMask; + } + } + else if (operation.Inst == Instruction.Call) + { + AstOperand funcId = (AstOperand)operation.GetSource(0); + + Debug.Assert(funcId.Type == OperandType.Constant); + + return context.GetFunction(funcId.Value).ReturnType; + } + else if (operation.Inst == Instruction.VectorExtract) + { + return GetNodeDestType(context, operation.GetSource(0)) & ~AggregateType.ElementCountMask; + } + else if (operation is AstTextureOperation texOp) + { + if (texOp.Inst.IsImage()) + { + return texOp.GetVectorType(texOp.Format.GetComponentType()); + } + else if (texOp.Inst == Instruction.TextureSample) + { + return texOp.GetVectorType(GetDestVarType(operation.Inst)); + } + } + + return GetDestVarType(operation.Inst); + } + else if (node is AstOperand operand) + { + if (operand.Type == OperandType.Argument) + { + int argIndex = operand.Value; + + return context.CurrentFunction.GetArgumentType(argIndex); + } + + return OperandInfo.GetVarType(operand); + } + else + { + throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\"."); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/TypeConversion.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/TypeConversion.cs new file mode 100644 index 00000000..3d7d0d0c --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/TypeConversion.cs @@ -0,0 +1,93 @@ +using Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System; + +namespace Ryujinx.Graphics.Shader.CodeGen.Glsl +{ + static class TypeConversion + { + public static string ReinterpretCast( + CodeGenContext context, + IAstNode node, + AggregateType srcType, + AggregateType dstType) + { + if (node is AstOperand operand && operand.Type == OperandType.Constant) + { + if (NumberFormatter.TryFormat(operand.Value, dstType, out string formatted)) + { + return formatted; + } + } + + string expr = InstGen.GetExpression(context, node); + + return ReinterpretCast(expr, node, srcType, dstType); + } + + private static string ReinterpretCast(string expr, IAstNode node, AggregateType srcType, AggregateType dstType) + { + if (srcType == dstType) + { + return expr; + } + + if (srcType == AggregateType.FP32) + { + switch (dstType) + { + case AggregateType.Bool: + return $"(floatBitsToInt({expr}) != 0)"; + case AggregateType.S32: + return $"floatBitsToInt({expr})"; + case AggregateType.U32: + return $"floatBitsToUint({expr})"; + } + } + else if (dstType == AggregateType.FP32) + { + switch (srcType) + { + case AggregateType.Bool: + return $"intBitsToFloat({ReinterpretBoolToInt(expr, node, AggregateType.S32)})"; + case AggregateType.S32: + return $"intBitsToFloat({expr})"; + case AggregateType.U32: + return $"uintBitsToFloat({expr})"; + } + } + else if (srcType == AggregateType.Bool) + { + return ReinterpretBoolToInt(expr, node, dstType); + } + else if (dstType == AggregateType.Bool) + { + expr = InstGenHelper.Enclose(expr, node, Instruction.CompareNotEqual, isLhs: true); + + return $"({expr} != 0)"; + } + else if (dstType == AggregateType.S32) + { + return $"int({expr})"; + } + else if (dstType == AggregateType.U32) + { + return $"uint({expr})"; + } + + throw new ArgumentException($"Invalid reinterpret cast from \"{srcType}\" to \"{dstType}\"."); + } + + private static string ReinterpretBoolToInt(string expr, IAstNode node, AggregateType dstType) + { + string trueExpr = NumberFormatter.FormatInt(IrConsts.True, dstType); + string falseExpr = NumberFormatter.FormatInt(IrConsts.False, dstType); + + expr = InstGenHelper.Enclose(expr, node, Instruction.ConditionalSelect, isLhs: false); + + return $"({expr} ? {trueExpr} : {falseExpr})"; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs new file mode 100644 index 00000000..cc7977f8 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs @@ -0,0 +1,373 @@ +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 static Spv.Specification; +using Instruction = Spv.Generator.Instruction; + +namespace Ryujinx.Graphics.Shader.CodeGen.Spirv +{ + using IrOperandType = IntermediateRepresentation.OperandType; + + partial class CodeGenContext : Module + { + private const uint SpirvVersionMajor = 1; + private const uint SpirvVersionMinor = 3; + private const uint SpirvVersionRevision = 0; + private const uint SpirvVersionPacked = (SpirvVersionMajor << 16) | (SpirvVersionMinor << 8) | SpirvVersionRevision; + + public StructuredProgramInfo Info { get; } + + public AttributeUsage AttributeUsage { get; } + public ShaderDefinitions Definitions { get; } + public ShaderProperties Properties { get; } + public HostCapabilities HostCapabilities { get; } + public ILogger Logger { get; } + public TargetApi TargetApi { get; } + + public Dictionary ConstantBuffers { get; } = new(); + public Dictionary StorageBuffers { get; } = new(); + + public Dictionary LocalMemories { get; } = new(); + public Dictionary SharedMemories { get; } = new(); + + public Dictionary SamplersTypes { get; } = new(); + public Dictionary Samplers { get; } = new(); + public Dictionary Images { get; } = new(); + + public Dictionary Inputs { get; } = new(); + public Dictionary Outputs { get; } = new(); + public Dictionary InputsPerPatch { get; } = new(); + public Dictionary OutputsPerPatch { get; } = new(); + + public StructuredFunction CurrentFunction { get; set; } + private readonly Dictionary _locals = new(); + private readonly Dictionary _funcArgs = new(); + private readonly Dictionary _functions = new(); + + private class BlockState + { + private int _entryCount; + private readonly List _labels = new(); + + public Instruction GetNextLabel(CodeGenContext context) + { + return GetLabel(context, _entryCount); + } + + public Instruction GetNextLabelAutoIncrement(CodeGenContext context) + { + return GetLabel(context, _entryCount++); + } + + public Instruction GetLabel(CodeGenContext context, int index) + { + while (index >= _labels.Count) + { + _labels.Add(context.Label()); + } + + return _labels[index]; + } + } + + private readonly Dictionary _labels = new(); + + public Dictionary LoopTargets { get; set; } + + public AstBlock CurrentBlock { get; private set; } + + public SpirvDelegates Delegates { get; } + + public bool IsMainFunction { get; private set; } + public bool MayHaveReturned { get; set; } + + public CodeGenContext( + StructuredProgramInfo info, + CodeGenParameters parameters, + GeneratorPool instPool, + GeneratorPool integerPool) : base(SpirvVersionPacked, instPool, integerPool) + { + Info = info; + AttributeUsage = parameters.AttributeUsage; + Definitions = parameters.Definitions; + Properties = parameters.Properties; + HostCapabilities = parameters.HostCapabilities; + Logger = parameters.Logger; + TargetApi = parameters.TargetApi; + + Delegates = new SpirvDelegates(this); + } + + public void StartFunction(bool isMainFunction) + { + IsMainFunction = isMainFunction; + MayHaveReturned = false; + _locals.Clear(); + _funcArgs.Clear(); + } + + public void EnterBlock(AstBlock block) + { + CurrentBlock = block; + AddLabel(GetBlockStateLazy(block).GetNextLabelAutoIncrement(this)); + } + + public Instruction GetFirstLabel(AstBlock block) + { + return GetBlockStateLazy(block).GetLabel(this, 0); + } + + public Instruction GetNextLabel(AstBlock block) + { + return GetBlockStateLazy(block).GetNextLabel(this); + } + + private BlockState GetBlockStateLazy(AstBlock block) + { + if (!_labels.TryGetValue(block, out var blockState)) + { + blockState = new BlockState(); + + _labels.Add(block, blockState); + } + + return blockState; + } + + public Instruction NewBlock() + { + var label = Label(); + Branch(label); + AddLabel(label); + return label; + } + + public Instruction[] GetMainInterface() + { + var mainInterface = new List(); + + mainInterface.AddRange(Inputs.Values); + mainInterface.AddRange(Outputs.Values); + mainInterface.AddRange(InputsPerPatch.Values); + mainInterface.AddRange(OutputsPerPatch.Values); + + return mainInterface.ToArray(); + } + + public void DeclareLocal(AstOperand local, Instruction spvLocal) + { + _locals.Add(local, spvLocal); + } + + public void DeclareArgument(int argIndex, Instruction spvLocal) + { + _funcArgs.Add(argIndex, spvLocal); + } + + public void DeclareFunction(int funcIndex, StructuredFunction function, Instruction spvFunc) + { + _functions.Add(funcIndex, (function, spvFunc)); + } + + public Instruction GetFP32(IAstNode node) + { + return Get(AggregateType.FP32, node); + } + + public Instruction GetFP64(IAstNode node) + { + return Get(AggregateType.FP64, node); + } + + public Instruction GetS32(IAstNode node) + { + return Get(AggregateType.S32, node); + } + + public Instruction GetU32(IAstNode node) + { + return Get(AggregateType.U32, node); + } + + public Instruction Get(AggregateType type, IAstNode node) + { + if (node is AstOperation operation) + { + var opResult = Instructions.Generate(this, operation); + return BitcastIfNeeded(type, opResult.Type, opResult.Value); + } + else if (node is AstOperand operand) + { + return operand.Type switch + { + IrOperandType.Argument => GetArgument(type, operand), + IrOperandType.Constant => GetConstant(type, operand), + IrOperandType.LocalVariable => GetLocal(type, operand), + IrOperandType.Undefined => GetUndefined(type), + _ => throw new ArgumentException($"Invalid operand type \"{operand.Type}\"."), + }; + } + + throw new NotImplementedException(node.GetType().Name); + } + + public Instruction GetWithType(IAstNode node, out AggregateType type) + { + if (node is AstOperation operation) + { + var opResult = Instructions.Generate(this, operation); + type = opResult.Type; + return opResult.Value; + } + else if (node is AstOperand operand) + { + switch (operand.Type) + { + case IrOperandType.LocalVariable: + type = operand.VarType; + return GetLocal(type, operand); + default: + throw new ArgumentException($"Invalid operand type \"{operand.Type}\"."); + } + } + + throw new NotImplementedException(node.GetType().Name); + } + + private Instruction GetUndefined(AggregateType type) + { + return type switch + { + AggregateType.Bool => ConstantFalse(TypeBool()), + AggregateType.FP32 => Constant(TypeFP32(), 0f), + AggregateType.FP64 => Constant(TypeFP64(), 0d), + _ => Constant(GetType(type), 0), + }; + } + + public Instruction GetConstant(AggregateType type, AstOperand operand) + { + return type switch + { + AggregateType.Bool => operand.Value != 0 ? ConstantTrue(TypeBool()) : ConstantFalse(TypeBool()), + AggregateType.FP32 => Constant(TypeFP32(), BitConverter.Int32BitsToSingle(operand.Value)), + AggregateType.FP64 => Constant(TypeFP64(), (double)BitConverter.Int32BitsToSingle(operand.Value)), + AggregateType.S32 => Constant(TypeS32(), operand.Value), + AggregateType.U32 => Constant(TypeU32(), (uint)operand.Value), + _ => throw new ArgumentException($"Invalid type \"{type}\"."), + }; + } + + public Instruction GetLocalPointer(AstOperand local) + { + return _locals[local]; + } + + public Instruction GetArgumentPointer(AstOperand funcArg) + { + return _funcArgs[funcArg.Value]; + } + + public Instruction GetLocal(AggregateType dstType, AstOperand local) + { + var srcType = local.VarType; + return BitcastIfNeeded(dstType, srcType, Load(GetType(srcType), GetLocalPointer(local))); + } + + public Instruction GetArgument(AggregateType dstType, AstOperand funcArg) + { + var srcType = funcArg.VarType; + return BitcastIfNeeded(dstType, srcType, Load(GetType(srcType), GetArgumentPointer(funcArg))); + } + + public (StructuredFunction, Instruction) GetFunction(int funcIndex) + { + return _functions[funcIndex]; + } + + public Instruction GetType(AggregateType type, int length = 1) + { + if ((type & AggregateType.Array) != 0) + { + if (length > 0) + { + return TypeArray(GetType(type & ~AggregateType.Array), Constant(TypeU32(), length)); + } + else + { + return TypeRuntimeArray(GetType(type & ~AggregateType.Array)); + } + } + else if ((type & AggregateType.ElementCountMask) != 0) + { + int vectorLength = (type & AggregateType.ElementCountMask) switch + { + AggregateType.Vector2 => 2, + AggregateType.Vector3 => 3, + AggregateType.Vector4 => 4, + _ => 1, + }; + + return TypeVector(GetType(type & ~AggregateType.ElementCountMask), vectorLength); + } + + return type switch + { + AggregateType.Void => TypeVoid(), + AggregateType.Bool => TypeBool(), + AggregateType.FP32 => TypeFP32(), + AggregateType.FP64 => TypeFP64(), + AggregateType.S32 => TypeS32(), + AggregateType.U32 => TypeU32(), + _ => throw new ArgumentException($"Invalid attribute type \"{type}\"."), + }; + } + + public Instruction BitcastIfNeeded(AggregateType dstType, AggregateType srcType, Instruction value) + { + if (dstType == srcType) + { + return value; + } + + if (dstType == AggregateType.Bool) + { + return INotEqual(TypeBool(), BitcastIfNeeded(AggregateType.S32, srcType, value), Constant(TypeS32(), 0)); + } + else if (srcType == AggregateType.Bool) + { + var intTrue = Constant(TypeS32(), IrConsts.True); + var intFalse = Constant(TypeS32(), IrConsts.False); + + return BitcastIfNeeded(dstType, AggregateType.S32, Select(TypeS32(), value, intTrue, intFalse)); + } + else + { + return Bitcast(GetType(dstType, 1), value); + } + } + + public Instruction TypeS32() + { + return TypeInt(32, true); + } + + public Instruction TypeU32() + { + return TypeInt(32, false); + } + + public Instruction TypeFP32() + { + return TypeFloat(32); + } + + public Instruction TypeFP64() + { + return TypeFloat(64); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs new file mode 100644 index 00000000..55d35bf0 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs @@ -0,0 +1,659 @@ +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 static Spv.Specification; +using SpvInstruction = Spv.Generator.Instruction; + +namespace Ryujinx.Graphics.Shader.CodeGen.Spirv +{ + static class Declarations + { + public static void DeclareParameters(CodeGenContext context, StructuredFunction function) + { + DeclareParameters(context, function.InArguments, 0); + DeclareParameters(context, function.OutArguments, function.InArguments.Length); + } + + private static void DeclareParameters(CodeGenContext context, IEnumerable argTypes, int argIndex) + { + foreach (var argType in argTypes) + { + var argPointerType = context.TypePointer(StorageClass.Function, context.GetType(argType)); + var spvArg = context.FunctionParameter(argPointerType); + + context.DeclareArgument(argIndex++, spvArg); + } + } + + public static void DeclareLocals(CodeGenContext context, StructuredFunction function) + { + foreach (AstOperand local in function.Locals) + { + var localPointerType = context.TypePointer(StorageClass.Function, context.GetType(local.VarType)); + var spvLocal = context.Variable(localPointerType, StorageClass.Function); + + context.AddLocalVariable(spvLocal); + context.DeclareLocal(local, spvLocal); + } + } + + public static void DeclareAll(CodeGenContext context, StructuredProgramInfo info) + { + DeclareConstantBuffers(context, context.Properties.ConstantBuffers.Values); + DeclareStorageBuffers(context, context.Properties.StorageBuffers.Values); + DeclareMemories(context, context.Properties.LocalMemories, context.LocalMemories, StorageClass.Private); + DeclareMemories(context, context.Properties.SharedMemories, context.SharedMemories, StorageClass.Workgroup); + DeclareSamplers(context, context.Properties.Textures.Values); + DeclareImages(context, context.Properties.Images.Values); + DeclareInputsAndOutputs(context, info); + } + + private static void DeclareMemories( + CodeGenContext context, + IReadOnlyDictionary memories, + Dictionary dict, + StorageClass storage) + { + foreach ((int id, MemoryDefinition memory) in memories) + { + var pointerType = context.TypePointer(storage, context.GetType(memory.Type, memory.ArrayLength)); + var variable = context.Variable(pointerType, storage); + + context.AddGlobalVariable(variable); + + dict.Add(id, variable); + } + } + + private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable buffers) + { + DeclareBuffers(context, buffers, isBuffer: false); + } + + private static void DeclareStorageBuffers(CodeGenContext context, IEnumerable buffers) + { + DeclareBuffers(context, buffers, isBuffer: true); + } + + private static void DeclareBuffers(CodeGenContext context, IEnumerable buffers, bool isBuffer) + { + HashSet decoratedTypes = new(); + + foreach (BufferDefinition buffer in buffers) + { + int setIndex = context.TargetApi == TargetApi.Vulkan ? buffer.Set : 0; + int alignment = buffer.Layout == BufferLayout.Std140 ? 16 : 4; + int alignmentMask = alignment - 1; + int offset = 0; + + SpvInstruction[] structFieldTypes = new SpvInstruction[buffer.Type.Fields.Length]; + int[] structFieldOffsets = new int[buffer.Type.Fields.Length]; + + for (int fieldIndex = 0; fieldIndex < buffer.Type.Fields.Length; fieldIndex++) + { + StructureField field = buffer.Type.Fields[fieldIndex]; + int fieldSize = (field.Type.GetSizeInBytes() + alignmentMask) & ~alignmentMask; + + structFieldTypes[fieldIndex] = context.GetType(field.Type, field.ArrayLength); + structFieldOffsets[fieldIndex] = offset; + + if (field.Type.HasFlag(AggregateType.Array)) + { + // We can't decorate the type more than once. + if (decoratedTypes.Add(structFieldTypes[fieldIndex])) + { + context.Decorate(structFieldTypes[fieldIndex], Decoration.ArrayStride, (LiteralInteger)fieldSize); + } + + // Zero lengths are assumed to be a "runtime array" (which does not have a explicit length + // specified on the shader, and instead assumes the bound buffer length). + // It is only valid as the last struct element. + + Debug.Assert(field.ArrayLength > 0 || fieldIndex == buffer.Type.Fields.Length - 1); + + offset += fieldSize * field.ArrayLength; + } + else + { + offset += fieldSize; + } + } + + var structType = context.TypeStruct(false, structFieldTypes); + + if (decoratedTypes.Add(structType)) + { + context.Decorate(structType, isBuffer ? Decoration.BufferBlock : Decoration.Block); + + for (int fieldIndex = 0; fieldIndex < structFieldOffsets.Length; fieldIndex++) + { + context.MemberDecorate(structType, fieldIndex, Decoration.Offset, (LiteralInteger)structFieldOffsets[fieldIndex]); + } + } + + var pointerType = context.TypePointer(StorageClass.Uniform, structType); + var variable = context.Variable(pointerType, StorageClass.Uniform); + + context.Name(variable, buffer.Name); + context.Decorate(variable, Decoration.DescriptorSet, (LiteralInteger)setIndex); + context.Decorate(variable, Decoration.Binding, (LiteralInteger)buffer.Binding); + context.AddGlobalVariable(variable); + + if (isBuffer) + { + context.StorageBuffers.Add(buffer.Binding, variable); + } + else + { + context.ConstantBuffers.Add(buffer.Binding, variable); + } + } + } + + private static void DeclareSamplers(CodeGenContext context, IEnumerable samplers) + { + foreach (var sampler in samplers) + { + int setIndex = context.TargetApi == TargetApi.Vulkan ? sampler.Set : 0; + + SpvInstruction imageType; + SpvInstruction sampledImageType; + + if (sampler.Type != SamplerType.None) + { + var dim = (sampler.Type & SamplerType.Mask) switch + { + SamplerType.Texture1D => Dim.Dim1D, + SamplerType.Texture2D => Dim.Dim2D, + SamplerType.Texture3D => Dim.Dim3D, + SamplerType.TextureCube => Dim.Cube, + SamplerType.TextureBuffer => Dim.Buffer, + _ => throw new InvalidOperationException($"Invalid sampler type \"{sampler.Type & SamplerType.Mask}\"."), + }; + + imageType = context.TypeImage( + context.TypeFP32(), + dim, + sampler.Type.HasFlag(SamplerType.Shadow), + sampler.Type.HasFlag(SamplerType.Array), + sampler.Type.HasFlag(SamplerType.Multisample), + 1, + ImageFormat.Unknown); + + sampledImageType = context.TypeSampledImage(imageType); + } + else + { + imageType = sampledImageType = context.TypeSampler(); + } + + var sampledOrSeparateImageType = sampler.Separate ? imageType : sampledImageType; + var sampledImagePointerType = context.TypePointer(StorageClass.UniformConstant, sampledOrSeparateImageType); + var sampledImageArrayPointerType = sampledImagePointerType; + + if (sampler.ArrayLength == 0) + { + var sampledImageArrayType = context.TypeRuntimeArray(sampledOrSeparateImageType); + sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType); + } + else if (sampler.ArrayLength != 1) + { + var sampledImageArrayType = context.TypeArray(sampledOrSeparateImageType, context.Constant(context.TypeU32(), sampler.ArrayLength)); + sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType); + } + + var sampledImageVariable = context.Variable(sampledImageArrayPointerType, StorageClass.UniformConstant); + + context.Samplers.Add(new(sampler.Set, sampler.Binding), new SamplerDeclaration( + imageType, + sampledImageType, + sampledImagePointerType, + sampledImageVariable, + sampler.ArrayLength != 1)); + context.SamplersTypes.Add(new(sampler.Set, sampler.Binding), sampler.Type); + + context.Name(sampledImageVariable, sampler.Name); + context.Decorate(sampledImageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex); + context.Decorate(sampledImageVariable, Decoration.Binding, (LiteralInteger)sampler.Binding); + context.AddGlobalVariable(sampledImageVariable); + } + } + + private static void DeclareImages(CodeGenContext context, IEnumerable images) + { + foreach (var image in images) + { + int setIndex = context.TargetApi == TargetApi.Vulkan ? image.Set : 0; + + var dim = GetDim(image.Type); + + var imageType = context.TypeImage( + context.GetType(image.Format.GetComponentType()), + dim, + image.Type.HasFlag(SamplerType.Shadow), + image.Type.HasFlag(SamplerType.Array), + image.Type.HasFlag(SamplerType.Multisample), + AccessQualifier.ReadWrite, + GetImageFormat(image.Format)); + + var imagePointerType = context.TypePointer(StorageClass.UniformConstant, imageType); + var imageArrayPointerType = imagePointerType; + + if (image.ArrayLength == 0) + { + var imageArrayType = context.TypeRuntimeArray(imageType); + imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType); + } + else if (image.ArrayLength != 1) + { + var imageArrayType = context.TypeArray(imageType, context.Constant(context.TypeU32(), image.ArrayLength)); + imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType); + } + + var imageVariable = context.Variable(imageArrayPointerType, StorageClass.UniformConstant); + + context.Images.Add(new(image.Set, image.Binding), new ImageDeclaration(imageType, imagePointerType, imageVariable, image.ArrayLength != 1)); + + context.Name(imageVariable, image.Name); + context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex); + context.Decorate(imageVariable, Decoration.Binding, (LiteralInteger)image.Binding); + + if (image.Flags.HasFlag(TextureUsageFlags.ImageCoherent)) + { + context.Decorate(imageVariable, Decoration.Coherent); + } + + context.AddGlobalVariable(imageVariable); + } + } + + private static Dim GetDim(SamplerType type) + { + return (type & SamplerType.Mask) switch + { + SamplerType.Texture1D => Dim.Dim1D, + SamplerType.Texture2D => Dim.Dim2D, + SamplerType.Texture3D => Dim.Dim3D, + SamplerType.TextureCube => Dim.Cube, + SamplerType.TextureBuffer => Dim.Buffer, + _ => throw new ArgumentException($"Invalid sampler type \"{type & SamplerType.Mask}\"."), + }; + } + + private static ImageFormat GetImageFormat(TextureFormat format) + { + return format switch + { + TextureFormat.Unknown => ImageFormat.Unknown, + TextureFormat.R8Unorm => ImageFormat.R8, + TextureFormat.R8Snorm => ImageFormat.R8Snorm, + TextureFormat.R8Uint => ImageFormat.R8ui, + TextureFormat.R8Sint => ImageFormat.R8i, + TextureFormat.R16Float => ImageFormat.R16f, + TextureFormat.R16Unorm => ImageFormat.R16, + TextureFormat.R16Snorm => ImageFormat.R16Snorm, + TextureFormat.R16Uint => ImageFormat.R16ui, + TextureFormat.R16Sint => ImageFormat.R16i, + TextureFormat.R32Float => ImageFormat.R32f, + TextureFormat.R32Uint => ImageFormat.R32ui, + TextureFormat.R32Sint => ImageFormat.R32i, + TextureFormat.R8G8Unorm => ImageFormat.Rg8, + TextureFormat.R8G8Snorm => ImageFormat.Rg8Snorm, + TextureFormat.R8G8Uint => ImageFormat.Rg8ui, + TextureFormat.R8G8Sint => ImageFormat.Rg8i, + TextureFormat.R16G16Float => ImageFormat.Rg16f, + TextureFormat.R16G16Unorm => ImageFormat.Rg16, + TextureFormat.R16G16Snorm => ImageFormat.Rg16Snorm, + TextureFormat.R16G16Uint => ImageFormat.Rg16ui, + TextureFormat.R16G16Sint => ImageFormat.Rg16i, + TextureFormat.R32G32Float => ImageFormat.Rg32f, + TextureFormat.R32G32Uint => ImageFormat.Rg32ui, + TextureFormat.R32G32Sint => ImageFormat.Rg32i, + TextureFormat.R8G8B8A8Unorm => ImageFormat.Rgba8, + TextureFormat.R8G8B8A8Snorm => ImageFormat.Rgba8Snorm, + TextureFormat.R8G8B8A8Uint => ImageFormat.Rgba8ui, + TextureFormat.R8G8B8A8Sint => ImageFormat.Rgba8i, + TextureFormat.R16G16B16A16Float => ImageFormat.Rgba16f, + TextureFormat.R16G16B16A16Unorm => ImageFormat.Rgba16, + TextureFormat.R16G16B16A16Snorm => ImageFormat.Rgba16Snorm, + TextureFormat.R16G16B16A16Uint => ImageFormat.Rgba16ui, + TextureFormat.R16G16B16A16Sint => ImageFormat.Rgba16i, + TextureFormat.R32G32B32A32Float => ImageFormat.Rgba32f, + TextureFormat.R32G32B32A32Uint => ImageFormat.Rgba32ui, + TextureFormat.R32G32B32A32Sint => ImageFormat.Rgba32i, + TextureFormat.R10G10B10A2Unorm => ImageFormat.Rgb10A2, + TextureFormat.R10G10B10A2Uint => ImageFormat.Rgb10a2ui, + TextureFormat.R11G11B10Float => ImageFormat.R11fG11fB10f, + _ => throw new ArgumentException($"Invalid texture format \"{format}\"."), + }; + } + + private static void DeclareInputsAndOutputs(CodeGenContext context, StructuredProgramInfo info) + { + int firstLocation = int.MaxValue; + + if (context.Definitions.Stage == ShaderStage.Fragment && context.Definitions.DualSourceBlend) + { + foreach (var ioDefinition in info.IoDefinitions) + { + if (ioDefinition.IoVariable == IoVariable.FragmentOutputColor && ioDefinition.Location < firstLocation) + { + firstLocation = ioDefinition.Location; + } + } + } + + foreach (var ioDefinition in info.IoDefinitions) + { + PixelImap iq = PixelImap.Unused; + + if (context.Definitions.Stage == ShaderStage.Fragment) + { + var ioVariable = ioDefinition.IoVariable; + if (ioVariable == IoVariable.UserDefined) + { + iq = context.Definitions.ImapTypes[ioDefinition.Location].GetFirstUsedType(); + } + else + { + (_, AggregateType varType) = IoMap.GetSpirvBuiltIn(ioVariable); + AggregateType elemType = varType & AggregateType.ElementTypeMask; + + if (elemType is AggregateType.S32 or AggregateType.U32) + { + iq = PixelImap.Constant; + } + } + } + else if (IoMap.IsPerVertexBuiltIn(ioDefinition.IoVariable)) + { + continue; + } + + bool isOutput = ioDefinition.StorageKind.IsOutput(); + bool isPerPatch = ioDefinition.StorageKind.IsPerPatch(); + + DeclareInputOrOutput(context, ioDefinition, isOutput, isPerPatch, iq, firstLocation); + } + + DeclarePerVertexBlock(context); + } + + private static void DeclarePerVertexBlock(CodeGenContext context) + { + if (context.Definitions.Stage.IsVtg()) + { + if (context.Definitions.Stage != ShaderStage.Vertex) + { + var perVertexInputStructType = CreatePerVertexStructType(context); + int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.Definitions.InputTopology.ToInputVertices() : 32; + var perVertexInputArrayType = context.TypeArray(perVertexInputStructType, context.Constant(context.TypeU32(), arraySize)); + var perVertexInputPointerType = context.TypePointer(StorageClass.Input, perVertexInputArrayType); + var perVertexInputVariable = context.Variable(perVertexInputPointerType, StorageClass.Input); + + context.Name(perVertexInputVariable, "gl_in"); + + context.AddGlobalVariable(perVertexInputVariable); + context.Inputs.Add(new IoDefinition(StorageKind.Input, IoVariable.Position), perVertexInputVariable); + + if (context.Definitions.Stage == ShaderStage.Geometry && + context.Definitions.GpPassthrough && + context.HostCapabilities.SupportsGeometryShaderPassthrough) + { + context.MemberDecorate(perVertexInputStructType, 0, Decoration.PassthroughNV); + context.MemberDecorate(perVertexInputStructType, 1, Decoration.PassthroughNV); + context.MemberDecorate(perVertexInputStructType, 2, Decoration.PassthroughNV); + context.MemberDecorate(perVertexInputStructType, 3, Decoration.PassthroughNV); + } + } + + var perVertexOutputStructType = CreatePerVertexStructType(context); + + void DecorateTfo(IoVariable ioVariable, int fieldIndex) + { + if (context.Definitions.TryGetTransformFeedbackOutput(ioVariable, 0, 0, out var transformFeedbackOutput)) + { + context.MemberDecorate(perVertexOutputStructType, fieldIndex, Decoration.XfbBuffer, (LiteralInteger)transformFeedbackOutput.Buffer); + context.MemberDecorate(perVertexOutputStructType, fieldIndex, Decoration.XfbStride, (LiteralInteger)transformFeedbackOutput.Stride); + context.MemberDecorate(perVertexOutputStructType, fieldIndex, Decoration.Offset, (LiteralInteger)transformFeedbackOutput.Offset); + } + } + + DecorateTfo(IoVariable.Position, 0); + DecorateTfo(IoVariable.PointSize, 1); + DecorateTfo(IoVariable.ClipDistance, 2); + + SpvInstruction perVertexOutputArrayType; + + if (context.Definitions.Stage == ShaderStage.TessellationControl) + { + int arraySize = context.Definitions.ThreadsPerInputPrimitive; + perVertexOutputArrayType = context.TypeArray(perVertexOutputStructType, context.Constant(context.TypeU32(), arraySize)); + } + else + { + perVertexOutputArrayType = perVertexOutputStructType; + } + + var perVertexOutputPointerType = context.TypePointer(StorageClass.Output, perVertexOutputArrayType); + var perVertexOutputVariable = context.Variable(perVertexOutputPointerType, StorageClass.Output); + + context.AddGlobalVariable(perVertexOutputVariable); + context.Outputs.Add(new IoDefinition(StorageKind.Output, IoVariable.Position), perVertexOutputVariable); + } + } + + private static SpvInstruction CreatePerVertexStructType(CodeGenContext context) + { + var vec4FloatType = context.TypeVector(context.TypeFP32(), 4); + var floatType = context.TypeFP32(); + var array8FloatType = context.TypeArray(context.TypeFP32(), context.Constant(context.TypeU32(), 8)); + var array1FloatType = context.TypeArray(context.TypeFP32(), context.Constant(context.TypeU32(), 1)); + + var perVertexStructType = context.TypeStruct(true, vec4FloatType, floatType, array8FloatType, array1FloatType); + + context.Name(perVertexStructType, "gl_PerVertex"); + + context.MemberName(perVertexStructType, 0, "gl_Position"); + context.MemberName(perVertexStructType, 1, "gl_PointSize"); + context.MemberName(perVertexStructType, 2, "gl_ClipDistance"); + context.MemberName(perVertexStructType, 3, "gl_CullDistance"); + + context.Decorate(perVertexStructType, Decoration.Block); + + if (context.HostCapabilities.ReducedPrecision) + { + context.MemberDecorate(perVertexStructType, 0, Decoration.Invariant); + } + + context.MemberDecorate(perVertexStructType, 0, Decoration.BuiltIn, (LiteralInteger)BuiltIn.Position); + context.MemberDecorate(perVertexStructType, 1, Decoration.BuiltIn, (LiteralInteger)BuiltIn.PointSize); + context.MemberDecorate(perVertexStructType, 2, Decoration.BuiltIn, (LiteralInteger)BuiltIn.ClipDistance); + context.MemberDecorate(perVertexStructType, 3, Decoration.BuiltIn, (LiteralInteger)BuiltIn.CullDistance); + + return perVertexStructType; + } + + private static void DeclareInputOrOutput( + CodeGenContext context, + IoDefinition ioDefinition, + bool isOutput, + bool isPerPatch, + PixelImap iq = PixelImap.Unused, + int firstLocation = 0) + { + IoVariable ioVariable = ioDefinition.IoVariable; + var storageClass = isOutput ? StorageClass.Output : StorageClass.Input; + + bool isBuiltIn; + BuiltIn builtIn = default; + AggregateType varType; + + if (ioVariable == IoVariable.UserDefined) + { + varType = context.Definitions.GetUserDefinedType(ioDefinition.Location, isOutput); + isBuiltIn = false; + } + else if (ioVariable == IoVariable.FragmentOutputColor) + { + varType = context.Definitions.GetFragmentOutputColorType(ioDefinition.Location); + isBuiltIn = false; + } + else + { + (builtIn, varType) = IoMap.GetSpirvBuiltIn(ioVariable); + isBuiltIn = true; + + if (varType == AggregateType.Invalid) + { + throw new InvalidOperationException($"Unknown variable {ioVariable}."); + } + } + + bool hasComponent = context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, ioDefinition.Location, ioDefinition.Component, isOutput); + + if (hasComponent) + { + varType &= AggregateType.ElementTypeMask; + } + else if (ioVariable == IoVariable.UserDefined && context.Definitions.HasTransformFeedbackOutputs(isOutput)) + { + varType &= AggregateType.ElementTypeMask; + varType |= context.Definitions.GetTransformFeedbackOutputComponents(ioDefinition.Location, ioDefinition.Component) switch + { + 2 => AggregateType.Vector2, + 3 => AggregateType.Vector3, + 4 => AggregateType.Vector4, + _ => AggregateType.Invalid, + }; + } + + var spvType = context.GetType(varType, IoMap.GetSpirvBuiltInArrayLength(ioVariable)); + bool builtInPassthrough = false; + + if (!isPerPatch && IoMap.IsPerVertex(ioVariable, context.Definitions.Stage, isOutput)) + { + int arraySize = context.Definitions.Stage == ShaderStage.Geometry ? context.Definitions.InputTopology.ToInputVertices() : 32; + spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), arraySize)); + + if (context.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough) + { + builtInPassthrough = true; + } + } + + if (context.Definitions.Stage == ShaderStage.TessellationControl && isOutput && !isPerPatch) + { + spvType = context.TypeArray(spvType, context.Constant(context.TypeU32(), context.Definitions.ThreadsPerInputPrimitive)); + } + + var spvPointerType = context.TypePointer(storageClass, spvType); + var spvVar = context.Variable(spvPointerType, storageClass); + + if (builtInPassthrough) + { + context.Decorate(spvVar, Decoration.PassthroughNV); + } + + if (isBuiltIn) + { + if (isPerPatch) + { + context.Decorate(spvVar, Decoration.Patch); + } + + if (context.HostCapabilities.ReducedPrecision && ioVariable == IoVariable.Position) + { + context.Decorate(spvVar, Decoration.Invariant); + } + + context.Decorate(spvVar, Decoration.BuiltIn, (LiteralInteger)builtIn); + } + else if (isPerPatch) + { + context.Decorate(spvVar, Decoration.Patch); + + if (ioVariable == IoVariable.UserDefined) + { + int location = context.AttributeUsage.GetPerPatchAttributeLocation(ioDefinition.Location); + + context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location); + } + } + else if (ioVariable == IoVariable.UserDefined) + { + context.Decorate(spvVar, Decoration.Location, (LiteralInteger)ioDefinition.Location); + + if (hasComponent) + { + context.Decorate(spvVar, Decoration.Component, (LiteralInteger)ioDefinition.Component); + } + + if (!isOutput && + !isPerPatch && + (context.AttributeUsage.PassthroughAttributes & (1 << ioDefinition.Location)) != 0 && + context.HostCapabilities.SupportsGeometryShaderPassthrough) + { + context.Decorate(spvVar, Decoration.PassthroughNV); + } + } + else if (ioVariable == IoVariable.FragmentOutputColor) + { + int location = ioDefinition.Location; + + if (context.Definitions.Stage == ShaderStage.Fragment && context.Definitions.DualSourceBlend) + { + int index = location - firstLocation; + + if ((uint)index < 2) + { + context.Decorate(spvVar, Decoration.Location, (LiteralInteger)firstLocation); + context.Decorate(spvVar, Decoration.Index, (LiteralInteger)index); + } + else + { + context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location); + } + } + else + { + context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location); + } + } + + if (!isOutput) + { + switch (iq) + { + case PixelImap.Constant: + context.Decorate(spvVar, Decoration.Flat); + break; + case PixelImap.ScreenLinear: + context.Decorate(spvVar, Decoration.NoPerspective); + break; + } + } + else if (context.Definitions.TryGetTransformFeedbackOutput( + ioVariable, + ioDefinition.Location, + ioDefinition.Component, + out var transformFeedbackOutput)) + { + 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); + + var dict = isPerPatch + ? (isOutput ? context.OutputsPerPatch : context.InputsPerPatch) + : (isOutput ? context.Outputs : context.Inputs); + dict.Add(ioDefinition, spvVar); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/EnumConversion.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/EnumConversion.cs new file mode 100644 index 00000000..d444588e --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/EnumConversion.cs @@ -0,0 +1,22 @@ +using System; +using static Spv.Specification; + +namespace Ryujinx.Graphics.Shader.CodeGen.Spirv +{ + static class EnumConversion + { + public static ExecutionModel Convert(this ShaderStage stage) + { + return stage switch + { + ShaderStage.Compute => ExecutionModel.GLCompute, + ShaderStage.Vertex => ExecutionModel.Vertex, + ShaderStage.TessellationControl => ExecutionModel.TessellationControl, + ShaderStage.TessellationEvaluation => ExecutionModel.TessellationEvaluation, + ShaderStage.Geometry => ExecutionModel.Geometry, + ShaderStage.Fragment => ExecutionModel.Fragment, + _ => throw new ArgumentException($"Invalid shader stage \"{stage}\"."), + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ImageDeclaration.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ImageDeclaration.cs new file mode 100644 index 00000000..1e0aee73 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ImageDeclaration.cs @@ -0,0 +1,20 @@ +using Spv.Generator; + +namespace Ryujinx.Graphics.Shader.CodeGen.Spirv +{ + readonly struct ImageDeclaration + { + public readonly Instruction ImageType; + public readonly Instruction ImagePointerType; + public readonly Instruction Image; + public readonly bool IsIndexed; + + public ImageDeclaration(Instruction imageType, Instruction imagePointerType, Instruction image, bool isIndexed) + { + ImageType = imageType; + ImagePointerType = imagePointerType; + Image = image; + IsIndexed = isIndexed; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs new file mode 100644 index 00000000..6206985d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs @@ -0,0 +1,2121 @@ +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 Spv.Specification; + +namespace Ryujinx.Graphics.Shader.CodeGen.Spirv +{ + using SpvInstruction = Spv.Generator.Instruction; + using SpvLiteralInteger = Spv.Generator.LiteralInteger; + + static class Instructions + { + private const MemorySemanticsMask DefaultMemorySemantics = + MemorySemanticsMask.ImageMemory | + MemorySemanticsMask.AtomicCounterMemory | + MemorySemanticsMask.WorkgroupMemory | + MemorySemanticsMask.UniformMemory | + MemorySemanticsMask.AcquireRelease; + + private static readonly Func[] _instTable; + + static Instructions() + { + _instTable = new Func[(int)Instruction.Count]; + +#pragma warning disable IDE0055 // Disable formatting + Add(Instruction.Absolute, GenerateAbsolute); + Add(Instruction.Add, GenerateAdd); + Add(Instruction.AtomicAdd, GenerateAtomicAdd); + Add(Instruction.AtomicAnd, GenerateAtomicAnd); + Add(Instruction.AtomicCompareAndSwap, GenerateAtomicCompareAndSwap); + Add(Instruction.AtomicMinS32, GenerateAtomicMinS32); + Add(Instruction.AtomicMinU32, GenerateAtomicMinU32); + Add(Instruction.AtomicMaxS32, GenerateAtomicMaxS32); + Add(Instruction.AtomicMaxU32, GenerateAtomicMaxU32); + Add(Instruction.AtomicOr, GenerateAtomicOr); + Add(Instruction.AtomicSwap, GenerateAtomicSwap); + Add(Instruction.AtomicXor, GenerateAtomicXor); + Add(Instruction.Ballot, GenerateBallot); + Add(Instruction.Barrier, GenerateBarrier); + Add(Instruction.BitCount, GenerateBitCount); + Add(Instruction.BitfieldExtractS32, GenerateBitfieldExtractS32); + Add(Instruction.BitfieldExtractU32, GenerateBitfieldExtractU32); + Add(Instruction.BitfieldInsert, GenerateBitfieldInsert); + Add(Instruction.BitfieldReverse, GenerateBitfieldReverse); + Add(Instruction.BitwiseAnd, GenerateBitwiseAnd); + Add(Instruction.BitwiseExclusiveOr, GenerateBitwiseExclusiveOr); + Add(Instruction.BitwiseNot, GenerateBitwiseNot); + Add(Instruction.BitwiseOr, GenerateBitwiseOr); + Add(Instruction.Call, GenerateCall); + Add(Instruction.Ceiling, GenerateCeiling); + Add(Instruction.Clamp, GenerateClamp); + Add(Instruction.ClampU32, GenerateClampU32); + Add(Instruction.Comment, GenerateComment); + Add(Instruction.CompareEqual, GenerateCompareEqual); + Add(Instruction.CompareGreater, GenerateCompareGreater); + Add(Instruction.CompareGreaterOrEqual, GenerateCompareGreaterOrEqual); + Add(Instruction.CompareGreaterOrEqualU32, GenerateCompareGreaterOrEqualU32); + Add(Instruction.CompareGreaterU32, GenerateCompareGreaterU32); + Add(Instruction.CompareLess, GenerateCompareLess); + Add(Instruction.CompareLessOrEqual, GenerateCompareLessOrEqual); + Add(Instruction.CompareLessOrEqualU32, GenerateCompareLessOrEqualU32); + Add(Instruction.CompareLessU32, GenerateCompareLessU32); + Add(Instruction.CompareNotEqual, GenerateCompareNotEqual); + Add(Instruction.ConditionalSelect, GenerateConditionalSelect); + Add(Instruction.ConvertFP32ToFP64, GenerateConvertFP32ToFP64); + Add(Instruction.ConvertFP32ToS32, GenerateConvertFP32ToS32); + Add(Instruction.ConvertFP32ToU32, GenerateConvertFP32ToU32); + Add(Instruction.ConvertFP64ToFP32, GenerateConvertFP64ToFP32); + Add(Instruction.ConvertFP64ToS32, GenerateConvertFP64ToS32); + Add(Instruction.ConvertFP64ToU32, GenerateConvertFP64ToU32); + Add(Instruction.ConvertS32ToFP32, GenerateConvertS32ToFP32); + Add(Instruction.ConvertS32ToFP64, GenerateConvertS32ToFP64); + Add(Instruction.ConvertU32ToFP32, GenerateConvertU32ToFP32); + Add(Instruction.ConvertU32ToFP64, GenerateConvertU32ToFP64); + Add(Instruction.Cosine, GenerateCosine); + Add(Instruction.Ddx, GenerateDdx); + Add(Instruction.Ddy, GenerateDdy); + Add(Instruction.Discard, GenerateDiscard); + Add(Instruction.Divide, GenerateDivide); + Add(Instruction.EmitVertex, GenerateEmitVertex); + Add(Instruction.EndPrimitive, GenerateEndPrimitive); + Add(Instruction.ExponentB2, GenerateExponentB2); + Add(Instruction.FSIBegin, GenerateFSIBegin); + Add(Instruction.FSIEnd, GenerateFSIEnd); + Add(Instruction.FindLSB, GenerateFindLSB); + Add(Instruction.FindMSBS32, GenerateFindMSBS32); + Add(Instruction.FindMSBU32, GenerateFindMSBU32); + Add(Instruction.Floor, GenerateFloor); + Add(Instruction.FusedMultiplyAdd, GenerateFusedMultiplyAdd); + Add(Instruction.GroupMemoryBarrier, GenerateGroupMemoryBarrier); + Add(Instruction.ImageAtomic, GenerateImageAtomic); + Add(Instruction.ImageLoad, GenerateImageLoad); + Add(Instruction.ImageStore, GenerateImageStore); + Add(Instruction.IsNan, GenerateIsNan); + Add(Instruction.Load, GenerateLoad); + Add(Instruction.Lod, GenerateLod); + Add(Instruction.LogarithmB2, GenerateLogarithmB2); + Add(Instruction.LogicalAnd, GenerateLogicalAnd); + Add(Instruction.LogicalExclusiveOr, GenerateLogicalExclusiveOr); + Add(Instruction.LogicalNot, GenerateLogicalNot); + Add(Instruction.LogicalOr, GenerateLogicalOr); + Add(Instruction.LoopBreak, GenerateLoopBreak); + Add(Instruction.LoopContinue, GenerateLoopContinue); + Add(Instruction.Maximum, GenerateMaximum); + Add(Instruction.MaximumU32, GenerateMaximumU32); + Add(Instruction.MemoryBarrier, GenerateMemoryBarrier); + Add(Instruction.Minimum, GenerateMinimum); + Add(Instruction.MinimumU32, GenerateMinimumU32); + Add(Instruction.Modulo, GenerateModulo); + Add(Instruction.Multiply, GenerateMultiply); + Add(Instruction.MultiplyHighS32, GenerateMultiplyHighS32); + Add(Instruction.MultiplyHighU32, GenerateMultiplyHighU32); + Add(Instruction.Negate, GenerateNegate); + Add(Instruction.PackDouble2x32, GeneratePackDouble2x32); + Add(Instruction.PackHalf2x16, GeneratePackHalf2x16); + Add(Instruction.ReciprocalSquareRoot, GenerateReciprocalSquareRoot); + Add(Instruction.Return, GenerateReturn); + Add(Instruction.Round, GenerateRound); + Add(Instruction.ShiftLeft, GenerateShiftLeft); + Add(Instruction.ShiftRightS32, GenerateShiftRightS32); + Add(Instruction.ShiftRightU32, GenerateShiftRightU32); + Add(Instruction.Shuffle, GenerateShuffle); + Add(Instruction.ShuffleDown, GenerateShuffleDown); + Add(Instruction.ShuffleUp, GenerateShuffleUp); + Add(Instruction.ShuffleXor, GenerateShuffleXor); + Add(Instruction.Sine, GenerateSine); + Add(Instruction.SquareRoot, GenerateSquareRoot); + Add(Instruction.Store, GenerateStore); + Add(Instruction.Subtract, GenerateSubtract); + Add(Instruction.SwizzleAdd, GenerateSwizzleAdd); + Add(Instruction.TextureSample, GenerateTextureSample); + Add(Instruction.TextureQuerySamples, GenerateTextureQuerySamples); + Add(Instruction.TextureQuerySize, GenerateTextureQuerySize); + Add(Instruction.Truncate, GenerateTruncate); + Add(Instruction.UnpackDouble2x32, GenerateUnpackDouble2x32); + Add(Instruction.UnpackHalf2x16, GenerateUnpackHalf2x16); + Add(Instruction.VectorExtract, GenerateVectorExtract); + Add(Instruction.VoteAll, GenerateVoteAll); + Add(Instruction.VoteAllEqual, GenerateVoteAllEqual); + Add(Instruction.VoteAny, GenerateVoteAny); +#pragma warning restore IDE0055 + } + + private static void Add(Instruction inst, Func handler) + { + _instTable[(int)(inst & Instruction.Mask)] = handler; + } + + public static OperationResult Generate(CodeGenContext context, AstOperation operation) + { + var handler = _instTable[(int)(operation.Inst & Instruction.Mask)]; + if (handler != null) + { + return handler(context, operation); + } + else + { + throw new NotImplementedException(operation.Inst.ToString()); + } + } + + private static OperationResult GenerateAbsolute(CodeGenContext context, AstOperation operation) + { + return GenerateUnary(context, operation, context.Delegates.GlslFAbs, context.Delegates.GlslSAbs); + } + + private static OperationResult GenerateAdd(CodeGenContext context, AstOperation operation) + { + return GenerateBinary(context, operation, context.Delegates.FAdd, context.Delegates.IAdd); + } + + private static OperationResult GenerateAtomicAdd(CodeGenContext context, AstOperation operation) + { + return GenerateAtomicMemoryBinary(context, operation, context.Delegates.AtomicIAdd); + } + + private static OperationResult GenerateAtomicAnd(CodeGenContext context, AstOperation operation) + { + return GenerateAtomicMemoryBinary(context, operation, context.Delegates.AtomicAnd); + } + + private static OperationResult GenerateAtomicCompareAndSwap(CodeGenContext context, AstOperation operation) + { + return GenerateAtomicMemoryCas(context, operation); + } + + private static OperationResult GenerateAtomicMinS32(CodeGenContext context, AstOperation operation) + { + return GenerateAtomicMemoryBinary(context, operation, context.Delegates.AtomicSMin); + } + + private static OperationResult GenerateAtomicMinU32(CodeGenContext context, AstOperation operation) + { + return GenerateAtomicMemoryBinary(context, operation, context.Delegates.AtomicUMin); + } + + private static OperationResult GenerateAtomicMaxS32(CodeGenContext context, AstOperation operation) + { + return GenerateAtomicMemoryBinary(context, operation, context.Delegates.AtomicSMax); + } + + private static OperationResult GenerateAtomicMaxU32(CodeGenContext context, AstOperation operation) + { + return GenerateAtomicMemoryBinary(context, operation, context.Delegates.AtomicUMax); + } + + private static OperationResult GenerateAtomicOr(CodeGenContext context, AstOperation operation) + { + return GenerateAtomicMemoryBinary(context, operation, context.Delegates.AtomicOr); + } + + private static OperationResult GenerateAtomicSwap(CodeGenContext context, AstOperation operation) + { + return GenerateAtomicMemoryBinary(context, operation, context.Delegates.AtomicExchange); + } + + private static OperationResult GenerateAtomicXor(CodeGenContext context, AstOperation operation) + { + return GenerateAtomicMemoryBinary(context, operation, context.Delegates.AtomicXor); + } + + private static OperationResult GenerateBallot(CodeGenContext context, AstOperation operation) + { + var source = operation.GetSource(0); + + var uvec4Type = context.TypeVector(context.TypeU32(), 4); + var execution = context.Constant(context.TypeU32(), Scope.Subgroup); + + var maskVector = context.GroupNonUniformBallot(uvec4Type, execution, context.Get(AggregateType.Bool, source)); + var mask = context.CompositeExtract(context.TypeU32(), maskVector, (SpvLiteralInteger)operation.Index); + + return new OperationResult(AggregateType.U32, mask); + } + + private static OperationResult GenerateBarrier(CodeGenContext context, AstOperation operation) + { + // Barrier on divergent control flow paths may cause the GPU to hang, + // so skip emitting the barrier for those cases. + if (!context.HostCapabilities.SupportsShaderBarrierDivergence && + (context.CurrentBlock.Type != AstBlockType.Main || context.MayHaveReturned || !context.IsMainFunction)) + { + context.Logger.Log("Shader has barrier on potentially divergent block, the barrier will be removed."); + + return OperationResult.Invalid; + } + + context.ControlBarrier( + context.Constant(context.TypeU32(), Scope.Workgroup), + context.Constant(context.TypeU32(), Scope.Workgroup), + context.Constant(context.TypeU32(), MemorySemanticsMask.WorkgroupMemory | MemorySemanticsMask.AcquireRelease)); + + return OperationResult.Invalid; + } + + private static OperationResult GenerateBitCount(CodeGenContext context, AstOperation operation) + { + return GenerateUnaryS32(context, operation, context.Delegates.BitCount); + } + + private static OperationResult GenerateBitfieldExtractS32(CodeGenContext context, AstOperation operation) + { + return GenerateBitfieldExtractS32(context, operation, context.Delegates.BitFieldSExtract); + } + + private static OperationResult GenerateBitfieldExtractU32(CodeGenContext context, AstOperation operation) + { + return GenerateTernaryU32(context, operation, context.Delegates.BitFieldUExtract); + } + + private static OperationResult GenerateBitfieldInsert(CodeGenContext context, AstOperation operation) + { + return GenerateBitfieldInsert(context, operation, context.Delegates.BitFieldInsert); + } + + private static OperationResult GenerateBitfieldReverse(CodeGenContext context, AstOperation operation) + { + return GenerateUnaryS32(context, operation, context.Delegates.BitReverse); + } + + private static OperationResult GenerateBitwiseAnd(CodeGenContext context, AstOperation operation) + { + return GenerateBinaryS32(context, operation, context.Delegates.BitwiseAnd); + } + + private static OperationResult GenerateBitwiseExclusiveOr(CodeGenContext context, AstOperation operation) + { + return GenerateBinaryS32(context, operation, context.Delegates.BitwiseXor); + } + + private static OperationResult GenerateBitwiseNot(CodeGenContext context, AstOperation operation) + { + return GenerateUnaryS32(context, operation, context.Delegates.Not); + } + + private static OperationResult GenerateBitwiseOr(CodeGenContext context, AstOperation operation) + { + return GenerateBinaryS32(context, operation, context.Delegates.BitwiseOr); + } + + private static OperationResult GenerateCall(CodeGenContext context, AstOperation operation) + { + AstOperand funcId = (AstOperand)operation.GetSource(0); + + Debug.Assert(funcId.Type == OperandType.Constant); + + var (function, spvFunc) = context.GetFunction(funcId.Value); + + var args = new SpvInstruction[operation.SourcesCount - 1]; + + for (int i = 0; i < args.Length; i++) + { + var operand = operation.GetSource(i + 1); + + AstOperand local = (AstOperand)operand; + Debug.Assert(local.Type == OperandType.LocalVariable); + args[i] = context.GetLocalPointer(local); + } + + var retType = function.ReturnType; + var result = context.FunctionCall(context.GetType(retType), spvFunc, args); + return new OperationResult(retType, result); + } + + private static OperationResult GenerateCeiling(CodeGenContext context, AstOperation operation) + { + return GenerateUnary(context, operation, context.Delegates.GlslCeil, null); + } + + private static OperationResult GenerateClamp(CodeGenContext context, AstOperation operation) + { + return GenerateTernary(context, operation, context.Delegates.GlslFClamp, context.Delegates.GlslSClamp); + } + + private static OperationResult GenerateClampU32(CodeGenContext context, AstOperation operation) + { + return GenerateTernaryU32(context, operation, context.Delegates.GlslUClamp); + } + + private static OperationResult GenerateComment(CodeGenContext context, AstOperation operation) + { + return OperationResult.Invalid; + } + + private static OperationResult GenerateCompareEqual(CodeGenContext context, AstOperation operation) + { + return GenerateCompare(context, operation, context.Delegates.FOrdEqual, context.Delegates.IEqual); + } + + private static OperationResult GenerateCompareGreater(CodeGenContext context, AstOperation operation) + { + return GenerateCompare(context, operation, context.Delegates.FOrdGreaterThan, context.Delegates.SGreaterThan); + } + + private static OperationResult GenerateCompareGreaterOrEqual(CodeGenContext context, AstOperation operation) + { + return GenerateCompare(context, operation, context.Delegates.FOrdGreaterThanEqual, context.Delegates.SGreaterThanEqual); + } + + private static OperationResult GenerateCompareGreaterOrEqualU32(CodeGenContext context, AstOperation operation) + { + return GenerateCompareU32(context, operation, context.Delegates.UGreaterThanEqual); + } + + private static OperationResult GenerateCompareGreaterU32(CodeGenContext context, AstOperation operation) + { + return GenerateCompareU32(context, operation, context.Delegates.UGreaterThan); + } + + private static OperationResult GenerateCompareLess(CodeGenContext context, AstOperation operation) + { + return GenerateCompare(context, operation, context.Delegates.FOrdLessThan, context.Delegates.SLessThan); + } + + private static OperationResult GenerateCompareLessOrEqual(CodeGenContext context, AstOperation operation) + { + return GenerateCompare(context, operation, context.Delegates.FOrdLessThanEqual, context.Delegates.SLessThanEqual); + } + + private static OperationResult GenerateCompareLessOrEqualU32(CodeGenContext context, AstOperation operation) + { + return GenerateCompareU32(context, operation, context.Delegates.ULessThanEqual); + } + + private static OperationResult GenerateCompareLessU32(CodeGenContext context, AstOperation operation) + { + return GenerateCompareU32(context, operation, context.Delegates.ULessThan); + } + + private static OperationResult GenerateCompareNotEqual(CodeGenContext context, AstOperation operation) + { + return GenerateCompare(context, operation, context.Delegates.FOrdNotEqual, context.Delegates.INotEqual); + } + + private static OperationResult GenerateConditionalSelect(CodeGenContext context, AstOperation operation) + { + var src1 = operation.GetSource(0); + var src2 = operation.GetSource(1); + var src3 = operation.GetSource(2); + + var cond = context.Get(AggregateType.Bool, src1); + + if (operation.Inst.HasFlag(Instruction.FP64)) + { + return new OperationResult(AggregateType.FP64, context.Select(context.TypeFP64(), cond, context.GetFP64(src2), context.GetFP64(src3))); + } + else if (operation.Inst.HasFlag(Instruction.FP32)) + { + return new OperationResult(AggregateType.FP32, context.Select(context.TypeFP32(), cond, context.GetFP32(src2), context.GetFP32(src3))); + } + else + { + return new OperationResult(AggregateType.S32, context.Select(context.TypeS32(), cond, context.GetS32(src2), context.GetS32(src3))); + } + } + + private static OperationResult GenerateConvertFP32ToFP64(CodeGenContext context, AstOperation operation) + { + var source = operation.GetSource(0); + + return new OperationResult(AggregateType.FP64, context.FConvert(context.TypeFP64(), context.GetFP32(source))); + } + + private static OperationResult GenerateConvertFP32ToS32(CodeGenContext context, AstOperation operation) + { + var source = operation.GetSource(0); + + return new OperationResult(AggregateType.S32, context.ConvertFToS(context.TypeS32(), context.GetFP32(source))); + } + + private static OperationResult GenerateConvertFP32ToU32(CodeGenContext context, AstOperation operation) + { + var source = operation.GetSource(0); + + return new OperationResult(AggregateType.U32, context.ConvertFToU(context.TypeU32(), context.GetFP32(source))); + } + + private static OperationResult GenerateConvertFP64ToFP32(CodeGenContext context, AstOperation operation) + { + var source = operation.GetSource(0); + + return new OperationResult(AggregateType.FP32, context.FConvert(context.TypeFP32(), context.GetFP64(source))); + } + + private static OperationResult GenerateConvertFP64ToS32(CodeGenContext context, AstOperation operation) + { + var source = operation.GetSource(0); + + return new OperationResult(AggregateType.S32, context.ConvertFToS(context.TypeS32(), context.GetFP64(source))); + } + + private static OperationResult GenerateConvertFP64ToU32(CodeGenContext context, AstOperation operation) + { + var source = operation.GetSource(0); + + return new OperationResult(AggregateType.U32, context.ConvertFToU(context.TypeU32(), context.GetFP64(source))); + } + + private static OperationResult GenerateConvertS32ToFP32(CodeGenContext context, AstOperation operation) + { + var source = operation.GetSource(0); + + return new OperationResult(AggregateType.FP32, context.ConvertSToF(context.TypeFP32(), context.GetS32(source))); + } + + private static OperationResult GenerateConvertS32ToFP64(CodeGenContext context, AstOperation operation) + { + var source = operation.GetSource(0); + + return new OperationResult(AggregateType.FP64, context.ConvertSToF(context.TypeFP64(), context.GetS32(source))); + } + + private static OperationResult GenerateConvertU32ToFP32(CodeGenContext context, AstOperation operation) + { + var source = operation.GetSource(0); + + return new OperationResult(AggregateType.FP32, context.ConvertUToF(context.TypeFP32(), context.GetU32(source))); + } + + private static OperationResult GenerateConvertU32ToFP64(CodeGenContext context, AstOperation operation) + { + var source = operation.GetSource(0); + + return new OperationResult(AggregateType.FP64, context.ConvertUToF(context.TypeFP64(), context.GetU32(source))); + } + + private static OperationResult GenerateCosine(CodeGenContext context, AstOperation operation) + { + return GenerateUnary(context, operation, context.Delegates.GlslCos, null); + } + + private static OperationResult GenerateDdx(CodeGenContext context, AstOperation operation) + { + return GenerateUnaryFP32(context, operation, context.Delegates.DPdx); + } + + private static OperationResult GenerateDdy(CodeGenContext context, AstOperation operation) + { + return GenerateUnaryFP32(context, operation, context.Delegates.DPdy); + } + + private static OperationResult GenerateDiscard(CodeGenContext context, AstOperation operation) + { + context.Kill(); + return OperationResult.Invalid; + } + + private static OperationResult GenerateDivide(CodeGenContext context, AstOperation operation) + { + return GenerateBinary(context, operation, context.Delegates.FDiv, context.Delegates.SDiv); + } + + private static OperationResult GenerateEmitVertex(CodeGenContext context, AstOperation operation) + { + context.EmitVertex(); + + return OperationResult.Invalid; + } + + private static OperationResult GenerateEndPrimitive(CodeGenContext context, AstOperation operation) + { + context.EndPrimitive(); + + return OperationResult.Invalid; + } + + private static OperationResult GenerateExponentB2(CodeGenContext context, AstOperation operation) + { + return GenerateUnary(context, operation, context.Delegates.GlslExp2, null); + } + + private static OperationResult GenerateFSIBegin(CodeGenContext context, AstOperation operation) + { + if (context.HostCapabilities.SupportsFragmentShaderInterlock) + { + context.BeginInvocationInterlockEXT(); + } + + return OperationResult.Invalid; + } + + private static OperationResult GenerateFSIEnd(CodeGenContext context, AstOperation operation) + { + if (context.HostCapabilities.SupportsFragmentShaderInterlock) + { + context.EndInvocationInterlockEXT(); + } + + return OperationResult.Invalid; + } + + private static OperationResult GenerateFindLSB(CodeGenContext context, AstOperation operation) + { + var source = context.GetU32(operation.GetSource(0)); + return new OperationResult(AggregateType.U32, context.GlslFindILsb(context.TypeU32(), source)); + } + + private static OperationResult GenerateFindMSBS32(CodeGenContext context, AstOperation operation) + { + var source = context.GetS32(operation.GetSource(0)); + return new OperationResult(AggregateType.U32, context.GlslFindSMsb(context.TypeU32(), source)); + } + + private static OperationResult GenerateFindMSBU32(CodeGenContext context, AstOperation operation) + { + var source = context.GetU32(operation.GetSource(0)); + return new OperationResult(AggregateType.U32, context.GlslFindUMsb(context.TypeU32(), source)); + } + + private static OperationResult GenerateFloor(CodeGenContext context, AstOperation operation) + { + return GenerateUnary(context, operation, context.Delegates.GlslFloor, null); + } + + private static OperationResult GenerateFusedMultiplyAdd(CodeGenContext context, AstOperation operation) + { + return GenerateTernary(context, operation, context.Delegates.GlslFma, null); + } + + private static OperationResult GenerateGroupMemoryBarrier(CodeGenContext context, AstOperation operation) + { + context.MemoryBarrier(context.Constant(context.TypeU32(), Scope.Workgroup), context.Constant(context.TypeU32(), DefaultMemorySemantics)); + return OperationResult.Invalid; + } + + private static OperationResult GenerateImageAtomic(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + var componentType = texOp.Format.GetComponentType(); + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + + int srcIndex = 0; + + SpvInstruction Src(AggregateType type) + { + return context.Get(type, texOp.GetSource(srcIndex++)); + } + + ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()]; + SpvInstruction image = declaration.Image; + + SpvInstruction resultType = context.GetType(componentType); + SpvInstruction imagePointerType = context.TypePointer(StorageClass.Image, resultType); + + if (declaration.IsIndexed) + { + SpvInstruction textureIndex = Src(AggregateType.S32); + + image = context.AccessChain(imagePointerType, image, textureIndex); + } + + int coordsCount = texOp.Type.GetDimensions(); + + int pCount = coordsCount + (isArray ? 1 : 0); + + SpvInstruction pCoords; + + if (pCount > 1) + { + SpvInstruction[] elems = new SpvInstruction[pCount]; + + for (int i = 0; i < pCount; i++) + { + elems[i] = Src(AggregateType.S32); + } + + var vectorType = context.TypeVector(context.TypeS32(), pCount); + pCoords = context.CompositeConstruct(vectorType, elems); + } + else + { + pCoords = Src(AggregateType.S32); + } + + SpvInstruction value = Src(componentType); + + var pointer = context.ImageTexelPointer(imagePointerType, image, pCoords, context.Constant(context.TypeU32(), 0)); + var one = context.Constant(context.TypeU32(), 1); + var zero = context.Constant(context.TypeU32(), 0); + + var result = (texOp.Flags & TextureFlags.AtomicMask) switch + { + TextureFlags.Add => context.AtomicIAdd(resultType, pointer, one, zero, value), + TextureFlags.Minimum => componentType == AggregateType.S32 + ? context.AtomicSMin(resultType, pointer, one, zero, value) + : context.AtomicUMin(resultType, pointer, one, zero, value), + TextureFlags.Maximum => componentType == AggregateType.S32 + ? context.AtomicSMax(resultType, pointer, one, zero, value) + : context.AtomicUMax(resultType, pointer, one, zero, value), + TextureFlags.Increment => context.AtomicIIncrement(resultType, pointer, one, zero), + TextureFlags.Decrement => context.AtomicIDecrement(resultType, pointer, one, zero), + TextureFlags.BitwiseAnd => context.AtomicAnd(resultType, pointer, one, zero, value), + TextureFlags.BitwiseOr => context.AtomicOr(resultType, pointer, one, zero, value), + TextureFlags.BitwiseXor => context.AtomicXor(resultType, pointer, one, zero, value), + TextureFlags.Swap => context.AtomicExchange(resultType, pointer, one, zero, value), + TextureFlags.CAS => context.AtomicCompareExchange(resultType, pointer, one, zero, zero, Src(componentType), value), + _ => context.AtomicIAdd(resultType, pointer, one, zero, value), + }; + + return new OperationResult(componentType, result); + } + + private static OperationResult GenerateImageLoad(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + var componentType = texOp.Format.GetComponentType(); + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + + int srcIndex = 0; + + SpvInstruction Src(AggregateType type) + { + return context.Get(type, texOp.GetSource(srcIndex++)); + } + + ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()]; + SpvInstruction image = declaration.Image; + + if (declaration.IsIndexed) + { + SpvInstruction textureIndex = Src(AggregateType.S32); + + image = context.AccessChain(declaration.ImagePointerType, image, textureIndex); + } + + image = context.Load(declaration.ImageType, image); + + int coordsCount = texOp.Type.GetDimensions(); + + int pCount = coordsCount + (isArray ? 1 : 0); + + SpvInstruction pCoords; + + if (pCount > 1) + { + SpvInstruction[] elems = new SpvInstruction[pCount]; + + for (int i = 0; i < pCount; i++) + { + elems[i] = Src(AggregateType.S32); + } + + var vectorType = context.TypeVector(context.TypeS32(), pCount); + pCoords = context.CompositeConstruct(vectorType, elems); + } + else + { + pCoords = Src(AggregateType.S32); + } + + var imageComponentType = context.GetType(componentType); + var swizzledResultType = texOp.GetVectorType(componentType); + + var texel = context.ImageRead(context.TypeVector(imageComponentType, 4), image, pCoords, ImageOperandsMask.MaskNone); + var result = GetSwizzledResult(context, texel, swizzledResultType, texOp.Index); + + return new OperationResult(componentType, result); + } + + private static OperationResult GenerateImageStore(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + + int srcIndex = 0; + + SpvInstruction Src(AggregateType type) + { + return context.Get(type, texOp.GetSource(srcIndex++)); + } + + ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()]; + SpvInstruction image = declaration.Image; + + if (declaration.IsIndexed) + { + SpvInstruction textureIndex = Src(AggregateType.S32); + + image = context.AccessChain(declaration.ImagePointerType, image, textureIndex); + } + + image = context.Load(declaration.ImageType, image); + + int coordsCount = texOp.Type.GetDimensions(); + + int pCount = coordsCount + (isArray ? 1 : 0); + + SpvInstruction pCoords; + + if (pCount > 1) + { + SpvInstruction[] elems = new SpvInstruction[pCount]; + + for (int i = 0; i < pCount; i++) + { + elems[i] = Src(AggregateType.S32); + } + + var vectorType = context.TypeVector(context.TypeS32(), pCount); + pCoords = context.CompositeConstruct(vectorType, elems); + } + else + { + pCoords = Src(AggregateType.S32); + } + + var componentType = texOp.Format.GetComponentType(); + + const int ComponentsCount = 4; + + SpvInstruction[] cElems = new SpvInstruction[ComponentsCount]; + + for (int i = 0; i < ComponentsCount; i++) + { + if (srcIndex < texOp.SourcesCount) + { + cElems[i] = Src(componentType); + } + else + { + cElems[i] = componentType switch + { + AggregateType.S32 => context.Constant(context.TypeS32(), 0), + AggregateType.U32 => context.Constant(context.TypeU32(), 0u), + _ => context.Constant(context.TypeFP32(), 0f), + }; + } + } + + var texel = context.CompositeConstruct(context.TypeVector(context.GetType(componentType), ComponentsCount), cElems); + + context.ImageWrite(image, pCoords, texel, ImageOperandsMask.MaskNone); + + return OperationResult.Invalid; + } + + private static OperationResult GenerateIsNan(CodeGenContext context, AstOperation operation) + { + var source = operation.GetSource(0); + + SpvInstruction result; + + if (operation.Inst.HasFlag(Instruction.FP64)) + { + result = context.IsNan(context.TypeBool(), context.GetFP64(source)); + } + else + { + result = context.IsNan(context.TypeBool(), context.GetFP32(source)); + } + + return new OperationResult(AggregateType.Bool, result); + } + + private static OperationResult GenerateLoad(CodeGenContext context, AstOperation operation) + { + return GenerateLoadOrStore(context, operation, isStore: false); + } + + private static OperationResult GenerateLod(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + int srcIndex = 0; + + SpvInstruction Src(AggregateType type) + { + return context.Get(type, texOp.GetSource(srcIndex++)); + } + + SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()]; + SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex); + + int pCount = texOp.Type.GetDimensions(); + + SpvInstruction pCoords; + + if (pCount > 1) + { + SpvInstruction[] elems = new SpvInstruction[pCount]; + + for (int i = 0; i < pCount; i++) + { + elems[i] = Src(AggregateType.FP32); + } + + var vectorType = context.TypeVector(context.TypeFP32(), pCount); + pCoords = context.CompositeConstruct(vectorType, elems); + } + else + { + pCoords = Src(AggregateType.FP32); + } + + var resultType = context.TypeVector(context.TypeFP32(), 2); + var packed = context.ImageQueryLod(resultType, image, pCoords); + var result = context.CompositeExtract(context.TypeFP32(), packed, (SpvLiteralInteger)texOp.Index); + + return new OperationResult(AggregateType.FP32, result); + } + + private static OperationResult GenerateLogarithmB2(CodeGenContext context, AstOperation operation) + { + return GenerateUnary(context, operation, context.Delegates.GlslLog2, null); + } + + private static OperationResult GenerateLogicalAnd(CodeGenContext context, AstOperation operation) + { + return GenerateBinaryBool(context, operation, context.Delegates.LogicalAnd); + } + + private static OperationResult GenerateLogicalExclusiveOr(CodeGenContext context, AstOperation operation) + { + return GenerateBinaryBool(context, operation, context.Delegates.LogicalNotEqual); + } + + private static OperationResult GenerateLogicalNot(CodeGenContext context, AstOperation operation) + { + return GenerateUnaryBool(context, operation, context.Delegates.LogicalNot); + } + + private static OperationResult GenerateLogicalOr(CodeGenContext context, AstOperation operation) + { + return GenerateBinaryBool(context, operation, context.Delegates.LogicalOr); + } + + private static OperationResult GenerateLoopBreak(CodeGenContext context, AstOperation operation) + { + AstBlock loopBlock = context.CurrentBlock; + while (loopBlock.Type != AstBlockType.DoWhile) + { + loopBlock = loopBlock.Parent; + } + + context.Branch(context.GetNextLabel(loopBlock.Parent)); + + return OperationResult.Invalid; + } + + private static OperationResult GenerateLoopContinue(CodeGenContext context, AstOperation operation) + { + AstBlock loopBlock = context.CurrentBlock; + while (loopBlock.Type != AstBlockType.DoWhile) + { + loopBlock = loopBlock.Parent; + } + + (_, SpvInstruction continueTarget) = context.LoopTargets[loopBlock]; + + context.Branch(continueTarget); + + return OperationResult.Invalid; + } + + private static OperationResult GenerateMaximum(CodeGenContext context, AstOperation operation) + { + return GenerateBinary(context, operation, context.Delegates.GlslFMax, context.Delegates.GlslSMax); + } + + private static OperationResult GenerateMaximumU32(CodeGenContext context, AstOperation operation) + { + return GenerateBinaryU32(context, operation, context.Delegates.GlslUMax); + } + + private static OperationResult GenerateMemoryBarrier(CodeGenContext context, AstOperation operation) + { + context.MemoryBarrier(context.Constant(context.TypeU32(), Scope.Device), context.Constant(context.TypeU32(), DefaultMemorySemantics)); + return OperationResult.Invalid; + } + + private static OperationResult GenerateMinimum(CodeGenContext context, AstOperation operation) + { + return GenerateBinary(context, operation, context.Delegates.GlslFMin, context.Delegates.GlslSMin); + } + + private static OperationResult GenerateMinimumU32(CodeGenContext context, AstOperation operation) + { + return GenerateBinaryU32(context, operation, context.Delegates.GlslUMin); + } + + private static OperationResult GenerateModulo(CodeGenContext context, AstOperation operation) + { + return GenerateBinary(context, operation, context.Delegates.FMod, null); + } + + private static OperationResult GenerateMultiply(CodeGenContext context, AstOperation operation) + { + return GenerateBinary(context, operation, context.Delegates.FMul, context.Delegates.IMul); + } + + private static OperationResult GenerateMultiplyHighS32(CodeGenContext context, AstOperation operation) + { + var src1 = operation.GetSource(0); + var src2 = operation.GetSource(1); + + var resultType = context.TypeStruct(false, context.TypeS32(), context.TypeS32()); + var result = context.SMulExtended(resultType, context.GetS32(src1), context.GetS32(src2)); + result = context.CompositeExtract(context.TypeS32(), result, 1); + + return new OperationResult(AggregateType.S32, result); + } + + private static OperationResult GenerateMultiplyHighU32(CodeGenContext context, AstOperation operation) + { + var src1 = operation.GetSource(0); + var src2 = operation.GetSource(1); + + var resultType = context.TypeStruct(false, context.TypeU32(), context.TypeU32()); + var result = context.UMulExtended(resultType, context.GetU32(src1), context.GetU32(src2)); + result = context.CompositeExtract(context.TypeU32(), result, 1); + + return new OperationResult(AggregateType.U32, result); + } + + private static OperationResult GenerateNegate(CodeGenContext context, AstOperation operation) + { + return GenerateUnary(context, operation, context.Delegates.FNegate, context.Delegates.SNegate); + } + + private static OperationResult GeneratePackDouble2x32(CodeGenContext context, AstOperation operation) + { + var value0 = context.GetU32(operation.GetSource(0)); + var value1 = context.GetU32(operation.GetSource(1)); + var vector = context.CompositeConstruct(context.TypeVector(context.TypeU32(), 2), value0, value1); + var result = context.GlslPackDouble2x32(context.TypeFP64(), vector); + + return new OperationResult(AggregateType.FP64, result); + } + + private static OperationResult GeneratePackHalf2x16(CodeGenContext context, AstOperation operation) + { + var value0 = context.GetFP32(operation.GetSource(0)); + var value1 = context.GetFP32(operation.GetSource(1)); + var vector = context.CompositeConstruct(context.TypeVector(context.TypeFP32(), 2), value0, value1); + var result = context.GlslPackHalf2x16(context.TypeU32(), vector); + + return new OperationResult(AggregateType.U32, result); + } + + private static OperationResult GenerateReciprocalSquareRoot(CodeGenContext context, AstOperation operation) + { + return GenerateUnary(context, operation, context.Delegates.GlslInverseSqrt, null); + } + + private static OperationResult GenerateReturn(CodeGenContext context, AstOperation operation) + { + context.MayHaveReturned = true; + + if (operation.SourcesCount != 0) + { + context.ReturnValue(context.Get(context.CurrentFunction.ReturnType, operation.GetSource(0))); + } + else + { + context.Return(); + } + + return OperationResult.Invalid; + } + + private static OperationResult GenerateRound(CodeGenContext context, AstOperation operation) + { + return GenerateUnary(context, operation, context.Delegates.GlslRoundEven, null); + } + + private static OperationResult GenerateShiftLeft(CodeGenContext context, AstOperation operation) + { + return GenerateBinaryS32(context, operation, context.Delegates.ShiftLeftLogical); + } + + private static OperationResult GenerateShiftRightS32(CodeGenContext context, AstOperation operation) + { + return GenerateBinaryS32(context, operation, context.Delegates.ShiftRightArithmetic); + } + + private static OperationResult GenerateShiftRightU32(CodeGenContext context, AstOperation operation) + { + return GenerateBinaryS32(context, operation, context.Delegates.ShiftRightLogical); + } + + private static OperationResult GenerateShuffle(CodeGenContext context, AstOperation operation) + { + var value = context.GetFP32(operation.GetSource(0)); + var index = context.GetU32(operation.GetSource(1)); + + var result = context.GroupNonUniformShuffle(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), value, index); + + return new OperationResult(AggregateType.FP32, result); + } + + private static OperationResult GenerateShuffleDown(CodeGenContext context, AstOperation operation) + { + var value = context.GetFP32(operation.GetSource(0)); + var index = context.GetU32(operation.GetSource(1)); + + var result = context.GroupNonUniformShuffleDown(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), value, index); + + return new OperationResult(AggregateType.FP32, result); + } + + private static OperationResult GenerateShuffleUp(CodeGenContext context, AstOperation operation) + { + var value = context.GetFP32(operation.GetSource(0)); + var index = context.GetU32(operation.GetSource(1)); + + var result = context.GroupNonUniformShuffleUp(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), value, index); + + return new OperationResult(AggregateType.FP32, result); + } + + private static OperationResult GenerateShuffleXor(CodeGenContext context, AstOperation operation) + { + var value = context.GetFP32(operation.GetSource(0)); + var index = context.GetU32(operation.GetSource(1)); + + var result = context.GroupNonUniformShuffleXor(context.TypeFP32(), context.Constant(context.TypeU32(), (int)Scope.Subgroup), value, index); + + return new OperationResult(AggregateType.FP32, result); + } + + private static OperationResult GenerateSine(CodeGenContext context, AstOperation operation) + { + return GenerateUnary(context, operation, context.Delegates.GlslSin, null); + } + + private static OperationResult GenerateSquareRoot(CodeGenContext context, AstOperation operation) + { + return GenerateUnary(context, operation, context.Delegates.GlslSqrt, null); + } + + private static OperationResult GenerateStore(CodeGenContext context, AstOperation operation) + { + return GenerateLoadOrStore(context, operation, isStore: true); + } + + private static OperationResult GenerateSubtract(CodeGenContext context, AstOperation operation) + { + return GenerateBinary(context, operation, context.Delegates.FSub, context.Delegates.ISub); + } + + private static OperationResult GenerateSwizzleAdd(CodeGenContext context, AstOperation operation) + { + var x = context.Get(AggregateType.FP32, operation.GetSource(0)); + var y = context.Get(AggregateType.FP32, operation.GetSource(1)); + var mask = context.Get(AggregateType.U32, operation.GetSource(2)); + + var v4float = context.TypeVector(context.TypeFP32(), 4); + var one = context.Constant(context.TypeFP32(), 1.0f); + var minusOne = context.Constant(context.TypeFP32(), -1.0f); + var zero = context.Constant(context.TypeFP32(), 0.0f); + var xLut = context.ConstantComposite(v4float, one, minusOne, one, zero); + var yLut = context.ConstantComposite(v4float, one, one, minusOne, one); + + var three = context.Constant(context.TypeU32(), 3); + + 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); + lutIdx = context.BitwiseAnd(context.TypeU32(), lutIdx, three); + + var xLutValue = context.VectorExtractDynamic(context.TypeFP32(), xLut, lutIdx); + var yLutValue = context.VectorExtractDynamic(context.TypeFP32(), yLut, lutIdx); + + var xResult = context.FMul(context.TypeFP32(), x, xLutValue); + var yResult = context.FMul(context.TypeFP32(), y, yLutValue); + var result = context.FAdd(context.TypeFP32(), xResult, yResult); + + return new OperationResult(AggregateType.FP32, result); + } + + private static OperationResult GenerateTextureSample(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; + bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0; + bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; + bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0; + bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; + bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; + + int srcIndex = 0; + + SpvInstruction Src(AggregateType type) + { + return context.Get(type, texOp.GetSource(srcIndex++)); + } + + SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()]; + SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex); + + int coordsCount = texOp.Type.GetDimensions(); + + int pCount = coordsCount; + + int arrayIndexElem = -1; + + if (isArray) + { + arrayIndexElem = pCount++; + } + + AggregateType coordType = intCoords ? AggregateType.S32 : AggregateType.FP32; + + SpvInstruction AssemblePVector(int count) + { + if (count > 1) + { + SpvInstruction[] elems = new SpvInstruction[count]; + + for (int index = 0; index < count; index++) + { + if (arrayIndexElem == index) + { + elems[index] = Src(AggregateType.S32); + + if (!intCoords) + { + elems[index] = context.ConvertSToF(context.TypeFP32(), elems[index]); + } + } + else + { + elems[index] = Src(coordType); + } + } + + var vectorType = context.TypeVector(intCoords ? context.TypeS32() : context.TypeFP32(), count); + return context.CompositeConstruct(vectorType, elems); + } + else + { + return Src(coordType); + } + } + + SpvInstruction pCoords = AssemblePVector(pCount); + + SpvInstruction AssembleDerivativesVector(int count) + { + if (count > 1) + { + SpvInstruction[] elems = new SpvInstruction[count]; + + for (int index = 0; index < count; index++) + { + elems[index] = Src(AggregateType.FP32); + } + + var vectorType = context.TypeVector(context.TypeFP32(), count); + return context.CompositeConstruct(vectorType, elems); + } + else + { + return Src(AggregateType.FP32); + } + } + + SpvInstruction dRef = null; + + if (isShadow) + { + dRef = Src(AggregateType.FP32); + } + + SpvInstruction[] derivatives = null; + + if (hasDerivatives) + { + derivatives = new[] + { + AssembleDerivativesVector(coordsCount), // dPdx + AssembleDerivativesVector(coordsCount), // dPdy + }; + } + + SpvInstruction sample = null; + SpvInstruction lod = null; + + if (isMultisample) + { + sample = Src(AggregateType.S32); + } + else if (hasLodLevel) + { + lod = Src(coordType); + } + + SpvInstruction AssembleOffsetVector(int count) + { + if (count > 1) + { + SpvInstruction[] elems = new SpvInstruction[count]; + + for (int index = 0; index < count; index++) + { + elems[index] = Src(AggregateType.S32); + } + + var vectorType = context.TypeVector(context.TypeS32(), count); + + return context.ConstantComposite(vectorType, elems); + } + else + { + return Src(AggregateType.S32); + } + } + + SpvInstruction[] offsets = null; + + if (hasOffset) + { + offsets = new[] { AssembleOffsetVector(coordsCount) }; + } + else if (hasOffsets) + { + offsets = new[] + { + AssembleOffsetVector(coordsCount), + AssembleOffsetVector(coordsCount), + AssembleOffsetVector(coordsCount), + AssembleOffsetVector(coordsCount), + }; + } + + SpvInstruction lodBias = null; + + if (hasLodBias) + { + lodBias = Src(AggregateType.FP32); + } + + if (!isGather && !intCoords && !isMultisample && !hasLodLevel && !hasDerivatives && context.Definitions.Stage != ShaderStage.Fragment) + { + // Implicit LOD is only valid on fragment. + // Use the LOD bias as explicit LOD if available. + + lod = lodBias ?? context.Constant(context.TypeFP32(), 0f); + + lodBias = null; + hasLodBias = false; + hasLodLevel = true; + } + + SpvInstruction compIdx = null; + + // textureGather* optional extra component index, + // not needed for shadow samplers. + if (isGather && !isShadow) + { + compIdx = Src(AggregateType.S32); + } + + var operandsList = new List(); + var operandsMask = ImageOperandsMask.MaskNone; + + if (hasLodBias) + { + operandsMask |= ImageOperandsMask.Bias; + operandsList.Add(lodBias); + } + + if (!isMultisample && hasLodLevel) + { + operandsMask |= ImageOperandsMask.Lod; + operandsList.Add(lod); + } + + if (hasDerivatives) + { + operandsMask |= ImageOperandsMask.Grad; + operandsList.Add(derivatives[0]); + operandsList.Add(derivatives[1]); + } + + if (hasOffset) + { + operandsMask |= ImageOperandsMask.ConstOffset; + operandsList.Add(offsets[0]); + } + else if (hasOffsets) + { + operandsMask |= ImageOperandsMask.ConstOffsets; + SpvInstruction arrayv2 = context.TypeArray(context.TypeVector(context.TypeS32(), 2), context.Constant(context.TypeU32(), 4)); + operandsList.Add(context.ConstantComposite(arrayv2, offsets[0], offsets[1], offsets[2], offsets[3])); + } + + if (isMultisample) + { + operandsMask |= ImageOperandsMask.Sample; + operandsList.Add(sample); + } + + bool colorIsVector = isGather || !isShadow; + + var resultType = colorIsVector ? context.TypeVector(context.TypeFP32(), 4) : context.TypeFP32(); + + if (intCoords) + { + image = context.Image(declaration.ImageType, image); + } + + var operands = operandsList.ToArray(); + + SpvInstruction result; + + if (intCoords) + { + result = context.ImageFetch(resultType, image, pCoords, operandsMask, operands); + } + else if (isGather) + { + if (isShadow) + { + result = context.ImageDrefGather(resultType, image, pCoords, dRef, operandsMask, operands); + } + else + { + result = context.ImageGather(resultType, image, pCoords, compIdx, operandsMask, operands); + } + } + else if (isShadow) + { + if (hasLodLevel) + { + result = context.ImageSampleDrefExplicitLod(resultType, image, pCoords, dRef, operandsMask, operands); + } + else + { + result = context.ImageSampleDrefImplicitLod(resultType, image, pCoords, dRef, operandsMask, operands); + } + } + else if (hasDerivatives || hasLodLevel) + { + result = context.ImageSampleExplicitLod(resultType, image, pCoords, operandsMask, operands); + } + else + { + result = context.ImageSampleImplicitLod(resultType, image, pCoords, operandsMask, operands); + } + + var swizzledResultType = AggregateType.FP32; + + if (colorIsVector) + { + swizzledResultType = texOp.GetVectorType(swizzledResultType); + + result = GetSwizzledResult(context, result, swizzledResultType, texOp.Index); + } + + return new OperationResult(swizzledResultType, result); + } + + private static OperationResult GenerateTextureQuerySamples(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + int srcIndex = 0; + + SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()]; + SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex); + + image = context.Image(declaration.ImageType, image); + + SpvInstruction result = context.ImageQuerySamples(context.TypeS32(), image); + + return new OperationResult(AggregateType.S32, result); + } + + private static OperationResult GenerateTextureQuerySize(CodeGenContext context, AstOperation operation) + { + AstTextureOperation texOp = (AstTextureOperation)operation; + + int srcIndex = 0; + + SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()]; + SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex); + + image = context.Image(declaration.ImageType, image); + + if (texOp.Index == 3) + { + return new OperationResult(AggregateType.S32, context.ImageQueryLevels(context.TypeS32(), image)); + } + else + { + var type = context.SamplersTypes[texOp.GetTextureSetAndBinding()]; + bool hasLod = !type.HasFlag(SamplerType.Multisample) && type != SamplerType.TextureBuffer; + + int dimensions = (type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : type.GetDimensions(); + + if (type.HasFlag(SamplerType.Array)) + { + dimensions++; + } + + var resultType = dimensions == 1 ? context.TypeS32() : context.TypeVector(context.TypeS32(), dimensions); + + SpvInstruction result; + + if (hasLod) + { + var lod = context.GetS32(operation.GetSource(srcIndex)); + result = context.ImageQuerySizeLod(resultType, image, lod); + } + else + { + result = context.ImageQuerySize(resultType, image); + } + + if (dimensions != 1) + { + result = context.CompositeExtract(context.TypeS32(), result, (SpvLiteralInteger)texOp.Index); + } + + return new OperationResult(AggregateType.S32, result); + } + } + + private static OperationResult GenerateTruncate(CodeGenContext context, AstOperation operation) + { + return GenerateUnary(context, operation, context.Delegates.GlslTrunc, null); + } + + private static OperationResult GenerateUnpackDouble2x32(CodeGenContext context, AstOperation operation) + { + var value = context.GetFP64(operation.GetSource(0)); + var vector = context.GlslUnpackDouble2x32(context.TypeVector(context.TypeU32(), 2), value); + var result = context.CompositeExtract(context.TypeU32(), vector, operation.Index); + + return new OperationResult(AggregateType.U32, result); + } + + private static OperationResult GenerateUnpackHalf2x16(CodeGenContext context, AstOperation operation) + { + var value = context.GetU32(operation.GetSource(0)); + var vector = context.GlslUnpackHalf2x16(context.TypeVector(context.TypeFP32(), 2), value); + var result = context.CompositeExtract(context.TypeFP32(), vector, operation.Index); + + return new OperationResult(AggregateType.FP32, result); + } + + private static OperationResult GenerateVectorExtract(CodeGenContext context, AstOperation operation) + { + var vector = context.GetWithType(operation.GetSource(0), out AggregateType vectorType); + var scalarType = vectorType & ~AggregateType.ElementCountMask; + var resultType = context.GetType(scalarType); + SpvInstruction result; + + if (operation.GetSource(1) is AstOperand indexOperand && indexOperand.Type == OperandType.Constant) + { + result = context.CompositeExtract(resultType, vector, (SpvLiteralInteger)indexOperand.Value); + } + else + { + var index = context.Get(AggregateType.S32, operation.GetSource(1)); + result = context.VectorExtractDynamic(resultType, vector, index); + } + + return new OperationResult(scalarType, result); + } + + private static OperationResult GenerateVoteAll(CodeGenContext context, AstOperation operation) + { + var execution = context.Constant(context.TypeU32(), Scope.Subgroup); + var result = context.GroupNonUniformAll(context.TypeBool(), execution, context.Get(AggregateType.Bool, operation.GetSource(0))); + return new OperationResult(AggregateType.Bool, result); + } + + private static OperationResult GenerateVoteAllEqual(CodeGenContext context, AstOperation operation) + { + var execution = context.Constant(context.TypeU32(), Scope.Subgroup); + var result = context.GroupNonUniformAllEqual(context.TypeBool(), execution, context.Get(AggregateType.Bool, operation.GetSource(0))); + return new OperationResult(AggregateType.Bool, result); + } + + private static OperationResult GenerateVoteAny(CodeGenContext context, AstOperation operation) + { + var execution = context.Constant(context.TypeU32(), Scope.Subgroup); + var result = context.GroupNonUniformAny(context.TypeBool(), execution, context.Get(AggregateType.Bool, operation.GetSource(0))); + return new OperationResult(AggregateType.Bool, result); + } + + private static OperationResult GenerateCompare( + CodeGenContext context, + AstOperation operation, + Func emitF, + Func emitI) + { + var src1 = operation.GetSource(0); + var src2 = operation.GetSource(1); + + SpvInstruction result; + + if (operation.Inst.HasFlag(Instruction.FP64)) + { + result = emitF(context.TypeBool(), context.GetFP64(src1), context.GetFP64(src2)); + } + else if (operation.Inst.HasFlag(Instruction.FP32)) + { + result = emitF(context.TypeBool(), context.GetFP32(src1), context.GetFP32(src2)); + } + else + { + result = emitI(context.TypeBool(), context.GetS32(src1), context.GetS32(src2)); + } + + return new OperationResult(AggregateType.Bool, result); + } + + private static OperationResult GenerateCompareU32( + CodeGenContext context, + AstOperation operation, + Func emitU) + { + var src1 = operation.GetSource(0); + var src2 = operation.GetSource(1); + + var result = emitU(context.TypeBool(), context.GetU32(src1), context.GetU32(src2)); + + return new OperationResult(AggregateType.Bool, result); + } + + private static OperationResult GenerateAtomicMemoryBinary( + CodeGenContext context, + AstOperation operation, + Func emitU) + { + SpvInstruction elemPointer = GetStoragePointer(context, operation, out AggregateType varType); + + var value = context.Get(varType, operation.GetSource(operation.SourcesCount - 1)); + + var one = context.Constant(context.TypeU32(), 1); + var zero = context.Constant(context.TypeU32(), 0); + + return new OperationResult(varType, emitU(context.GetType(varType), elemPointer, one, zero, value)); + } + + private static OperationResult GenerateAtomicMemoryCas(CodeGenContext context, AstOperation operation) + { + SpvInstruction elemPointer = GetStoragePointer(context, operation, out AggregateType varType); + + var value0 = context.Get(varType, operation.GetSource(operation.SourcesCount - 2)); + var value1 = context.Get(varType, operation.GetSource(operation.SourcesCount - 1)); + + var one = context.Constant(context.TypeU32(), 1); + var zero = context.Constant(context.TypeU32(), 0); + + return new OperationResult(varType, context.AtomicCompareExchange(context.GetType(varType), elemPointer, one, zero, zero, value1, value0)); + } + + private static OperationResult GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore) + { + SpvInstruction pointer = GetStoragePointer(context, operation, out AggregateType varType); + + if (isStore) + { + context.Store(pointer, context.Get(varType, operation.GetSource(operation.SourcesCount - 1))); + return OperationResult.Invalid; + } + else + { + var result = context.Load(context.GetType(varType), pointer); + return new OperationResult(varType, result); + } + } + + private static SpvInstruction GetStoragePointer(CodeGenContext context, AstOperation operation, out AggregateType varType) + { + StorageKind storageKind = operation.StorageKind; + + StorageClass storageClass; + SpvInstruction baseObj; + int srcIndex = 0; + IoVariable? perVertexBuiltIn = null; + + switch (storageKind) + { + case StorageKind.ConstantBuffer: + case StorageKind.StorageBuffer: + if (operation.GetSource(srcIndex++) is not AstOperand bindingIndex || bindingIndex.Type != OperandType.Constant) + { + throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand."); + } + + if (operation.GetSource(srcIndex) is not AstOperand fieldIndex || fieldIndex.Type != OperandType.Constant) + { + throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand."); + } + + BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer + ? context.Properties.ConstantBuffers[bindingIndex.Value] + : context.Properties.StorageBuffers[bindingIndex.Value]; + StructureField field = buffer.Type.Fields[fieldIndex.Value]; + + storageClass = StorageClass.Uniform; + varType = field.Type & AggregateType.ElementTypeMask; + baseObj = storageKind == StorageKind.ConstantBuffer + ? context.ConstantBuffers[bindingIndex.Value] + : context.StorageBuffers[bindingIndex.Value]; + break; + + case StorageKind.LocalMemory: + case StorageKind.SharedMemory: + if (operation.GetSource(srcIndex++) is not AstOperand { Type: OperandType.Constant } bindingId) + { + throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand."); + } + + if (storageKind == StorageKind.LocalMemory) + { + storageClass = StorageClass.Private; + varType = context.Properties.LocalMemories[bindingId.Value].Type & AggregateType.ElementTypeMask; + baseObj = context.LocalMemories[bindingId.Value]; + } + else + { + storageClass = StorageClass.Workgroup; + varType = context.Properties.SharedMemories[bindingId.Value].Type & AggregateType.ElementTypeMask; + baseObj = context.SharedMemories[bindingId.Value]; + } + break; + + case StorageKind.Input: + case StorageKind.InputPerPatch: + case StorageKind.Output: + case StorageKind.OutputPerPatch: + if (operation.GetSource(srcIndex++) is not 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.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput)) + { + if (operation.GetSource(srcIndex++) is not 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.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, elemIndex.Value, isOutput)) + { + component = elemIndex.Value; + srcIndex++; + } + } + + if (ioVariable == IoVariable.UserDefined) + { + varType = context.Definitions.GetUserDefinedType(location, isOutput); + } + else if (ioVariable == IoVariable.FragmentOutputColor) + { + varType = context.Definitions.GetFragmentOutputColorType(location); + } + else + { + (_, varType) = IoMap.GetSpirvBuiltIn(ioVariable); + + if (IoMap.IsPerVertexBuiltIn(ioVariable)) + { + perVertexBuiltIn = ioVariable; + ioVariable = IoVariable.Position; + } + } + + varType &= AggregateType.ElementTypeMask; + + 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); + + baseObj = dict[ioDefinition]; + break; + + default: + throw new InvalidOperationException($"Invalid storage kind {storageKind}."); + } + + bool isStoreOrAtomic = operation.Inst == Instruction.Store || operation.Inst.IsAtomic(); + int inputsCount = (isStoreOrAtomic ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex; + + if (perVertexBuiltIn.HasValue) + { + int fieldIndex = IoMap.GetPerVertexStructFieldIndex(perVertexBuiltIn.Value); + + var indexes = new SpvInstruction[inputsCount + 1]; + int index = 0; + + if (IoMap.IsPerVertexArrayBuiltIn(storageKind, context.Definitions.Stage)) + { + indexes[index++] = context.Get(AggregateType.S32, operation.GetSource(srcIndex++)); + indexes[index++] = context.Constant(context.TypeS32(), fieldIndex); + } + else + { + indexes[index++] = context.Constant(context.TypeS32(), fieldIndex); + } + + for (; index < inputsCount + 1; srcIndex++, index++) + { + indexes[index] = context.Get(AggregateType.S32, operation.GetSource(srcIndex)); + } + + return context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, indexes); + } + + if (operation.Inst == Instruction.AtomicCompareAndSwap) + { + inputsCount--; + } + + SpvInstruction e0, e1, e2; + SpvInstruction pointer; + + switch (inputsCount) + { + case 0: + pointer = baseObj; + break; + case 1: + e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++)); + pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0); + break; + case 2: + e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++)); + e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++)); + pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1); + break; + case 3: + e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++)); + e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++)); + e2 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++)); + pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1, e2); + break; + default: + var indexes = new SpvInstruction[inputsCount]; + int index = 0; + + for (; index < inputsCount; srcIndex++, index++) + { + indexes[index] = context.Get(AggregateType.S32, operation.GetSource(srcIndex)); + } + + pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, indexes); + break; + } + + return pointer; + } + + 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 SpvInstruction GetSwizzledResult(CodeGenContext context, SpvInstruction vector, AggregateType swizzledResultType, int mask) + { + if ((swizzledResultType & AggregateType.ElementCountMask) != 0) + { + SpvLiteralInteger[] components = new SpvLiteralInteger[BitOperations.PopCount((uint)mask)]; + + int componentIndex = 0; + + for (int i = 0; i < 4; i++) + { + if ((mask & (1 << i)) != 0) + { + components[componentIndex++] = i; + } + } + + return context.VectorShuffle(context.GetType(swizzledResultType), vector, vector, components); + } + else + { + int componentIndex = (int)BitOperations.TrailingZeroCount(mask); + + return context.CompositeExtract(context.GetType(swizzledResultType), vector, (SpvLiteralInteger)componentIndex); + } + } + + private static SpvInstruction GenerateSampledImageLoad(CodeGenContext context, AstTextureOperation texOp, SamplerDeclaration declaration, ref int srcIndex) + { + SpvInstruction image = declaration.Image; + + if (declaration.IsIndexed) + { + SpvInstruction textureIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++)); + + image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); + } + + if (texOp.IsSeparate) + { + image = context.Load(declaration.ImageType, image); + + SamplerDeclaration samplerDeclaration = context.Samplers[texOp.GetSamplerSetAndBinding()]; + + SpvInstruction sampler = samplerDeclaration.Image; + + if (samplerDeclaration.IsIndexed) + { + SpvInstruction samplerIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++)); + + sampler = context.AccessChain(samplerDeclaration.SampledImagePointerType, sampler, samplerIndex); + } + + sampler = context.Load(samplerDeclaration.ImageType, sampler); + image = context.SampledImage(declaration.SampledImageType, image, sampler); + } + else + { + image = context.Load(declaration.SampledImageType, image); + } + + return image; + } + + private static OperationResult GenerateUnary( + CodeGenContext context, + AstOperation operation, + Func emitF, + Func emitI) + { + var source = operation.GetSource(0); + + if (operation.Inst.HasFlag(Instruction.FP64)) + { + return new OperationResult(AggregateType.FP64, emitF(context.TypeFP64(), context.GetFP64(source))); + } + else if (operation.Inst.HasFlag(Instruction.FP32)) + { + return new OperationResult(AggregateType.FP32, emitF(context.TypeFP32(), context.GetFP32(source))); + } + else + { + return new OperationResult(AggregateType.S32, emitI(context.TypeS32(), context.GetS32(source))); + } + } + + private static OperationResult GenerateUnaryBool( + CodeGenContext context, + AstOperation operation, + Func emitB) + { + var source = operation.GetSource(0); + return new OperationResult(AggregateType.Bool, emitB(context.TypeBool(), context.Get(AggregateType.Bool, source))); + } + + private static OperationResult GenerateUnaryFP32( + CodeGenContext context, + AstOperation operation, + Func emit) + { + var source = operation.GetSource(0); + return new OperationResult(AggregateType.FP32, emit(context.TypeFP32(), context.GetFP32(source))); + } + + private static OperationResult GenerateUnaryS32( + CodeGenContext context, + AstOperation operation, + Func emitS) + { + var source = operation.GetSource(0); + return new OperationResult(AggregateType.S32, emitS(context.TypeS32(), context.GetS32(source))); + } + + private static OperationResult GenerateBinary( + CodeGenContext context, + AstOperation operation, + Func emitF, + Func emitI) + { + var src1 = operation.GetSource(0); + var src2 = operation.GetSource(1); + + if (operation.Inst.HasFlag(Instruction.FP64)) + { + var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2)); + + if (!context.HostCapabilities.ReducedPrecision || operation.ForcePrecise) + { + context.Decorate(result, Decoration.NoContraction); + } + + return new OperationResult(AggregateType.FP64, result); + } + else if (operation.Inst.HasFlag(Instruction.FP32)) + { + var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2)); + + if (!context.HostCapabilities.ReducedPrecision || operation.ForcePrecise) + { + context.Decorate(result, Decoration.NoContraction); + } + + return new OperationResult(AggregateType.FP32, result); + } + else + { + return new OperationResult(AggregateType.S32, emitI(context.TypeS32(), context.GetS32(src1), context.GetS32(src2))); + } + } + + private static OperationResult GenerateBinaryBool( + CodeGenContext context, + AstOperation operation, + Func emitB) + { + var src1 = operation.GetSource(0); + var src2 = operation.GetSource(1); + + return new OperationResult(AggregateType.Bool, emitB(context.TypeBool(), context.Get(AggregateType.Bool, src1), context.Get(AggregateType.Bool, src2))); + } + + private static OperationResult GenerateBinaryS32( + CodeGenContext context, + AstOperation operation, + Func emitS) + { + var src1 = operation.GetSource(0); + var src2 = operation.GetSource(1); + + return new OperationResult(AggregateType.S32, emitS(context.TypeS32(), context.GetS32(src1), context.GetS32(src2))); + } + + private static OperationResult GenerateBinaryU32( + CodeGenContext context, + AstOperation operation, + Func emitU) + { + var src1 = operation.GetSource(0); + var src2 = operation.GetSource(1); + + return new OperationResult(AggregateType.U32, emitU(context.TypeU32(), context.GetU32(src1), context.GetU32(src2))); + } + + private static OperationResult GenerateTernary( + CodeGenContext context, + AstOperation operation, + Func emitF, + Func emitI) + { + var src1 = operation.GetSource(0); + var src2 = operation.GetSource(1); + var src3 = operation.GetSource(2); + + if (operation.Inst.HasFlag(Instruction.FP64)) + { + var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3)); + + if (!context.HostCapabilities.ReducedPrecision || operation.ForcePrecise) + { + context.Decorate(result, Decoration.NoContraction); + } + + return new OperationResult(AggregateType.FP64, result); + } + else if (operation.Inst.HasFlag(Instruction.FP32)) + { + var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3)); + + if (!context.HostCapabilities.ReducedPrecision || operation.ForcePrecise) + { + context.Decorate(result, Decoration.NoContraction); + } + + return new OperationResult(AggregateType.FP32, result); + } + else + { + return new OperationResult(AggregateType.S32, emitI(context.TypeS32(), context.GetS32(src1), context.GetS32(src2), context.GetS32(src3))); + } + } + + private static OperationResult GenerateTernaryU32( + CodeGenContext context, + AstOperation operation, + Func emitU) + { + var src1 = operation.GetSource(0); + var src2 = operation.GetSource(1); + var src3 = operation.GetSource(2); + + return new OperationResult(AggregateType.U32, emitU( + context.TypeU32(), + context.GetU32(src1), + context.GetU32(src2), + context.GetU32(src3))); + } + + private static OperationResult GenerateBitfieldExtractS32( + CodeGenContext context, + AstOperation operation, + Func emitS) + { + var src1 = operation.GetSource(0); + var src2 = operation.GetSource(1); + var src3 = operation.GetSource(2); + + return new OperationResult(AggregateType.S32, emitS( + context.TypeS32(), + context.GetS32(src1), + context.GetU32(src2), + context.GetU32(src3))); + } + + private static OperationResult GenerateBitfieldInsert( + CodeGenContext context, + AstOperation operation, + Func emitS) + { + var src1 = operation.GetSource(0); + var src2 = operation.GetSource(1); + var src3 = operation.GetSource(2); + var src4 = operation.GetSource(3); + + return new OperationResult(AggregateType.U32, emitS( + context.TypeU32(), + context.GetU32(src1), + context.GetU32(src2), + context.GetU32(src3), + context.GetU32(src4))); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/IoMap.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/IoMap.cs new file mode 100644 index 00000000..7b4e14ff --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/IoMap.cs @@ -0,0 +1,124 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; +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.GlobalId => (BuiltIn.GlobalInvocationId, AggregateType.Vector3 | AggregateType.U32), + 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 is ShaderStage.TessellationControl or ShaderStage.TessellationEvaluation or ShaderStage.Geometry; + } + + return false; + } + + public static bool IsPerVertexBuiltIn(IoVariable ioVariable) + { + switch (ioVariable) + { + case IoVariable.Position: + case IoVariable.PointSize: + case IoVariable.ClipDistance: + return true; + } + + return false; + } + + public static bool IsPerVertexArrayBuiltIn(StorageKind storageKind, ShaderStage stage) + { + if (storageKind == StorageKind.Output) + { + return stage == ShaderStage.TessellationControl; + } + else + { + return stage == ShaderStage.TessellationControl || + stage == ShaderStage.TessellationEvaluation || + stage == ShaderStage.Geometry; + } + } + + public static int GetPerVertexStructFieldIndex(IoVariable ioVariable) + { + return ioVariable switch + { + IoVariable.Position => 0, + IoVariable.PointSize => 1, + IoVariable.ClipDistance => 2, + _ => throw new ArgumentException($"Invalid built-in variable {ioVariable}.") + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/OperationResult.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/OperationResult.cs new file mode 100644 index 00000000..5be634c7 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/OperationResult.cs @@ -0,0 +1,19 @@ +using Ryujinx.Graphics.Shader.Translation; +using Spv.Generator; + +namespace Ryujinx.Graphics.Shader.CodeGen.Spirv +{ + readonly struct OperationResult + { + public static OperationResult Invalid => new(AggregateType.Invalid, null); + + public AggregateType Type { get; } + public Instruction Value { get; } + + public OperationResult(AggregateType type, Instruction value) + { + Type = type; + Value = value; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SamplerDeclaration.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SamplerDeclaration.cs new file mode 100644 index 00000000..9e0ecd79 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SamplerDeclaration.cs @@ -0,0 +1,27 @@ +using Spv.Generator; + +namespace Ryujinx.Graphics.Shader.CodeGen.Spirv +{ + readonly struct SamplerDeclaration + { + public readonly Instruction ImageType; + public readonly Instruction SampledImageType; + public readonly Instruction SampledImagePointerType; + public readonly Instruction Image; + public readonly bool IsIndexed; + + public SamplerDeclaration( + Instruction imageType, + Instruction sampledImageType, + Instruction sampledImagePointerType, + Instruction image, + bool isIndexed) + { + ImageType = imageType; + SampledImageType = sampledImageType; + SampledImagePointerType = sampledImagePointerType; + Image = image; + IsIndexed = isIndexed; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvDelegates.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvDelegates.cs new file mode 100644 index 00000000..3716d76d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvDelegates.cs @@ -0,0 +1,228 @@ +using FuncBinaryInstruction = System.Func; +using FuncQuaternaryInstruction = System.Func; +using FuncTernaryInstruction = System.Func; +using FuncUnaryInstruction = System.Func; + +namespace Ryujinx.Graphics.Shader.CodeGen.Spirv +{ + /// + /// Delegate cache for SPIR-V instruction generators. Avoids delegate allocation when passing generators as arguments. + /// + internal readonly struct SpirvDelegates + { + // Unary + public readonly FuncUnaryInstruction GlslFAbs; + public readonly FuncUnaryInstruction GlslSAbs; + public readonly FuncUnaryInstruction GlslCeil; + public readonly FuncUnaryInstruction GlslCos; + public readonly FuncUnaryInstruction GlslExp2; + public readonly FuncUnaryInstruction GlslFloor; + public readonly FuncUnaryInstruction GlslLog2; + public readonly FuncUnaryInstruction FNegate; + public readonly FuncUnaryInstruction SNegate; + public readonly FuncUnaryInstruction GlslInverseSqrt; + public readonly FuncUnaryInstruction GlslRoundEven; + public readonly FuncUnaryInstruction GlslSin; + public readonly FuncUnaryInstruction GlslSqrt; + public readonly FuncUnaryInstruction GlslTrunc; + + // UnaryBool + public readonly FuncUnaryInstruction LogicalNot; + + // UnaryFP32 + public readonly FuncUnaryInstruction DPdx; + public readonly FuncUnaryInstruction DPdy; + + // UnaryS32 + public readonly FuncUnaryInstruction BitCount; + public readonly FuncUnaryInstruction BitReverse; + public readonly FuncUnaryInstruction Not; + + // Compare + public readonly FuncBinaryInstruction FOrdEqual; + public readonly FuncBinaryInstruction IEqual; + public readonly FuncBinaryInstruction FOrdGreaterThan; + public readonly FuncBinaryInstruction SGreaterThan; + public readonly FuncBinaryInstruction FOrdGreaterThanEqual; + public readonly FuncBinaryInstruction SGreaterThanEqual; + public readonly FuncBinaryInstruction FOrdLessThan; + public readonly FuncBinaryInstruction SLessThan; + public readonly FuncBinaryInstruction FOrdLessThanEqual; + public readonly FuncBinaryInstruction SLessThanEqual; + public readonly FuncBinaryInstruction FOrdNotEqual; + public readonly FuncBinaryInstruction INotEqual; + + // CompareU32 + public readonly FuncBinaryInstruction UGreaterThanEqual; + public readonly FuncBinaryInstruction UGreaterThan; + public readonly FuncBinaryInstruction ULessThanEqual; + public readonly FuncBinaryInstruction ULessThan; + + // Binary + public readonly FuncBinaryInstruction FAdd; + public readonly FuncBinaryInstruction IAdd; + public readonly FuncBinaryInstruction FDiv; + public readonly FuncBinaryInstruction SDiv; + public readonly FuncBinaryInstruction GlslFMax; + public readonly FuncBinaryInstruction GlslSMax; + public readonly FuncBinaryInstruction GlslFMin; + public readonly FuncBinaryInstruction GlslSMin; + public readonly FuncBinaryInstruction FMod; + public readonly FuncBinaryInstruction FMul; + public readonly FuncBinaryInstruction IMul; + public readonly FuncBinaryInstruction FSub; + public readonly FuncBinaryInstruction ISub; + + // BinaryBool + public readonly FuncBinaryInstruction LogicalAnd; + public readonly FuncBinaryInstruction LogicalNotEqual; + public readonly FuncBinaryInstruction LogicalOr; + + // BinaryS32 + public readonly FuncBinaryInstruction BitwiseAnd; + public readonly FuncBinaryInstruction BitwiseXor; + public readonly FuncBinaryInstruction BitwiseOr; + public readonly FuncBinaryInstruction ShiftLeftLogical; + public readonly FuncBinaryInstruction ShiftRightArithmetic; + public readonly FuncBinaryInstruction ShiftRightLogical; + + // BinaryU32 + public readonly FuncBinaryInstruction GlslUMax; + public readonly FuncBinaryInstruction GlslUMin; + + // AtomicMemoryBinary + public readonly FuncQuaternaryInstruction AtomicIAdd; + public readonly FuncQuaternaryInstruction AtomicAnd; + public readonly FuncQuaternaryInstruction AtomicSMin; + public readonly FuncQuaternaryInstruction AtomicUMin; + public readonly FuncQuaternaryInstruction AtomicSMax; + public readonly FuncQuaternaryInstruction AtomicUMax; + public readonly FuncQuaternaryInstruction AtomicOr; + public readonly FuncQuaternaryInstruction AtomicExchange; + public readonly FuncQuaternaryInstruction AtomicXor; + + // Ternary + public readonly FuncTernaryInstruction GlslFClamp; + public readonly FuncTernaryInstruction GlslSClamp; + public readonly FuncTernaryInstruction GlslFma; + + // TernaryS32 + public readonly FuncTernaryInstruction BitFieldSExtract; + public readonly FuncTernaryInstruction BitFieldUExtract; + + // TernaryU32 + public readonly FuncTernaryInstruction GlslUClamp; + + // QuaternaryS32 + public readonly FuncQuaternaryInstruction BitFieldInsert; + + public SpirvDelegates(CodeGenContext context) + { + // Unary + GlslFAbs = context.GlslFAbs; + GlslSAbs = context.GlslSAbs; + GlslCeil = context.GlslCeil; + GlslCos = context.GlslCos; + GlslExp2 = context.GlslExp2; + GlslFloor = context.GlslFloor; + GlslLog2 = context.GlslLog2; + FNegate = context.FNegate; + SNegate = context.SNegate; + GlslInverseSqrt = context.GlslInverseSqrt; + GlslRoundEven = context.GlslRoundEven; + GlslSin = context.GlslSin; + GlslSqrt = context.GlslSqrt; + GlslTrunc = context.GlslTrunc; + + // UnaryBool + LogicalNot = context.LogicalNot; + + // UnaryFP32 + DPdx = context.DPdx; + DPdy = context.DPdy; + + // UnaryS32 + BitCount = context.BitCount; + BitReverse = context.BitReverse; + Not = context.Not; + + // Compare + FOrdEqual = context.FOrdEqual; + IEqual = context.IEqual; + FOrdGreaterThan = context.FOrdGreaterThan; + SGreaterThan = context.SGreaterThan; + FOrdGreaterThanEqual = context.FOrdGreaterThanEqual; + SGreaterThanEqual = context.SGreaterThanEqual; + FOrdLessThan = context.FOrdLessThan; + SLessThan = context.SLessThan; + FOrdLessThanEqual = context.FOrdLessThanEqual; + SLessThanEqual = context.SLessThanEqual; + FOrdNotEqual = context.FOrdNotEqual; + INotEqual = context.INotEqual; + + // CompareU32 + UGreaterThanEqual = context.UGreaterThanEqual; + UGreaterThan = context.UGreaterThan; + ULessThanEqual = context.ULessThanEqual; + ULessThan = context.ULessThan; + + // Binary + FAdd = context.FAdd; + IAdd = context.IAdd; + FDiv = context.FDiv; + SDiv = context.SDiv; + GlslFMax = context.GlslFMax; + GlslSMax = context.GlslSMax; + GlslFMin = context.GlslFMin; + GlslSMin = context.GlslSMin; + FMod = context.FMod; + FMul = context.FMul; + IMul = context.IMul; + FSub = context.FSub; + ISub = context.ISub; + + // BinaryBool + LogicalAnd = context.LogicalAnd; + LogicalNotEqual = context.LogicalNotEqual; + LogicalOr = context.LogicalOr; + + // BinaryS32 + BitwiseAnd = context.BitwiseAnd; + BitwiseXor = context.BitwiseXor; + BitwiseOr = context.BitwiseOr; + ShiftLeftLogical = context.ShiftLeftLogical; + ShiftRightArithmetic = context.ShiftRightArithmetic; + ShiftRightLogical = context.ShiftRightLogical; + + // BinaryU32 + GlslUMax = context.GlslUMax; + GlslUMin = context.GlslUMin; + + // AtomicMemoryBinary + AtomicIAdd = context.AtomicIAdd; + AtomicAnd = context.AtomicAnd; + AtomicSMin = context.AtomicSMin; + AtomicUMin = context.AtomicUMin; + AtomicSMax = context.AtomicSMax; + AtomicUMax = context.AtomicUMax; + AtomicOr = context.AtomicOr; + AtomicExchange = context.AtomicExchange; + AtomicXor = context.AtomicXor; + + // Ternary + GlslFClamp = context.GlslFClamp; + GlslSClamp = context.GlslSClamp; + GlslFma = context.GlslFma; + + // TernaryS32 + BitFieldSExtract = context.BitFieldSExtract; + BitFieldUExtract = context.BitFieldUExtract; + + // TernaryU32 + GlslUClamp = context.GlslUClamp; + + // QuaternaryS32 + BitFieldInsert = context.BitFieldInsert; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs new file mode 100644 index 00000000..b259dde2 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs @@ -0,0 +1,425 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using static Spv.Specification; + +namespace Ryujinx.Graphics.Shader.CodeGen.Spirv +{ + using SpvInstruction = Spv.Generator.Instruction; + using SpvInstructionPool = Spv.Generator.GeneratorPool; + using SpvLiteralInteger = Spv.Generator.LiteralInteger; + using SpvLiteralIntegerPool = Spv.Generator.GeneratorPool; + + static class SpirvGenerator + { + // Resource pools for Spirv generation. Note: Increase count when more threads are being used. + private const int GeneratorPoolCount = 1; + private static readonly ObjectPool _instructionPool; + private static readonly ObjectPool _integerPool; + private static readonly object _poolLock; + + static SpirvGenerator() + { + _instructionPool = new(() => new SpvInstructionPool(), GeneratorPoolCount); + _integerPool = new(() => new SpvLiteralIntegerPool(), GeneratorPoolCount); + _poolLock = new object(); + } + + private const HelperFunctionsMask NeedsInvocationIdMask = HelperFunctionsMask.SwizzleAdd; + + public static byte[] Generate(StructuredProgramInfo info, CodeGenParameters parameters) + { + SpvInstructionPool instPool; + SpvLiteralIntegerPool integerPool; + + lock (_poolLock) + { + instPool = _instructionPool.Allocate(); + integerPool = _integerPool.Allocate(); + } + + CodeGenContext context = new(info, parameters, instPool, integerPool); + + context.AddCapability(Capability.Shader); + + context.SetMemoryModel(AddressingModel.Logical, MemoryModel.GLSL450); + + context.AddCapability(Capability.GroupNonUniformBallot); + context.AddCapability(Capability.GroupNonUniformShuffle); + context.AddCapability(Capability.GroupNonUniformVote); + context.AddCapability(Capability.ImageBuffer); + context.AddCapability(Capability.ImageGatherExtended); + context.AddCapability(Capability.ImageQuery); + context.AddCapability(Capability.SampledBuffer); + + if (parameters.HostCapabilities.SupportsShaderFloat64) + { + context.AddCapability(Capability.Float64); + } + + if (parameters.Definitions.TransformFeedbackEnabled && parameters.Definitions.LastInVertexPipeline) + { + context.AddCapability(Capability.TransformFeedback); + } + + if (parameters.Definitions.Stage == ShaderStage.Fragment) + { + if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)) || + context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.PrimitiveId))) + { + context.AddCapability(Capability.Geometry); + } + + if (context.HostCapabilities.SupportsFragmentShaderInterlock) + { + context.AddCapability(Capability.FragmentShaderPixelInterlockEXT); + context.AddExtension("SPV_EXT_fragment_shader_interlock"); + } + } + else if (parameters.Definitions.Stage == ShaderStage.Geometry) + { + context.AddCapability(Capability.Geometry); + + if (parameters.Definitions.GpPassthrough && context.HostCapabilities.SupportsGeometryShaderPassthrough) + { + context.AddExtension("SPV_NV_geometry_shader_passthrough"); + context.AddCapability(Capability.GeometryShaderPassthroughNV); + } + } + else if (parameters.Definitions.Stage == ShaderStage.TessellationControl || + parameters.Definitions.Stage == ShaderStage.TessellationEvaluation) + { + context.AddCapability(Capability.Tessellation); + } + else if (parameters.Definitions.Stage == ShaderStage.Vertex) + { + context.AddCapability(Capability.DrawParameters); + } + + if (context.Definitions.Stage != ShaderStage.Fragment && + context.Definitions.Stage != ShaderStage.Geometry && + context.Definitions.Stage != ShaderStage.Compute && + (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.Layer)) || + context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.ViewportIndex)))) + { + context.AddExtension("SPV_EXT_shader_viewport_index_layer"); + context.AddCapability(Capability.ShaderViewportIndexLayerEXT); + } + + 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) + { + 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]; + var retType = context.GetType(function.ReturnType); + + var funcArgs = new SpvInstruction[function.InArguments.Length + function.OutArguments.Length]; + + for (int argIndex = 0; argIndex < funcArgs.Length; argIndex++) + { + var argType = context.GetType(function.GetArgumentType(argIndex)); + var argPointerType = context.TypePointer(StorageClass.Function, argType); + funcArgs[argIndex] = argPointerType; + } + + var funcType = context.TypeFunction(retType, false, funcArgs); + var spvFunc = context.Function(retType, FunctionControlMask.MaskNone, funcType); + + context.DeclareFunction(funcIndex, function, spvFunc); + } + + for (int funcIndex = 0; funcIndex < info.Functions.Count; funcIndex++) + { + Generate(context, info, funcIndex); + } + + byte[] result = context.Generate(); + + lock (_poolLock) + { + _instructionPool.Release(instPool); + _integerPool.Release(integerPool); + } + + return result; + } + + private static void Generate(CodeGenContext context, StructuredProgramInfo info, int funcIndex) + { + var (function, spvFunc) = context.GetFunction(funcIndex); + + context.CurrentFunction = function; + context.AddFunction(spvFunc); + context.StartFunction(isMainFunction: funcIndex == 0); + + Declarations.DeclareParameters(context, function); + + context.EnterBlock(function.MainBlock); + + Declarations.DeclareLocals(context, function); + + Generate(context, function.MainBlock); + + // Functions must always end with a return. + if (function.MainBlock.Last is not AstOperation operation || + (operation.Inst != Instruction.Return && operation.Inst != Instruction.Discard)) + { + context.Return(); + } + + context.FunctionEnd(); + + if (funcIndex == 0) + { + context.AddEntryPoint(context.Definitions.Stage.Convert(), spvFunc, "main", context.GetMainInterface()); + + if (context.Definitions.Stage == ShaderStage.TessellationControl) + { + context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)context.Definitions.ThreadsPerInputPrimitive); + } + else if (context.Definitions.Stage == ShaderStage.TessellationEvaluation) + { + switch (context.Definitions.TessPatchType) + { + case TessPatchType.Isolines: + context.AddExecutionMode(spvFunc, ExecutionMode.Isolines); + break; + case TessPatchType.Triangles: + context.AddExecutionMode(spvFunc, ExecutionMode.Triangles); + break; + case TessPatchType.Quads: + context.AddExecutionMode(spvFunc, ExecutionMode.Quads); + break; + } + + switch (context.Definitions.TessSpacing) + { + case TessSpacing.EqualSpacing: + context.AddExecutionMode(spvFunc, ExecutionMode.SpacingEqual); + break; + case TessSpacing.FractionalEventSpacing: + context.AddExecutionMode(spvFunc, ExecutionMode.SpacingFractionalEven); + break; + case TessSpacing.FractionalOddSpacing: + context.AddExecutionMode(spvFunc, ExecutionMode.SpacingFractionalOdd); + break; + } + + bool tessCw = context.Definitions.TessCw; + + if (context.TargetApi == TargetApi.Vulkan) + { + // We invert the front face on Vulkan backend, so we need to do that here as well. + tessCw = !tessCw; + } + + if (tessCw) + { + context.AddExecutionMode(spvFunc, ExecutionMode.VertexOrderCw); + } + else + { + context.AddExecutionMode(spvFunc, ExecutionMode.VertexOrderCcw); + } + } + else if (context.Definitions.Stage == ShaderStage.Geometry) + { + context.AddExecutionMode(spvFunc, context.Definitions.InputTopology switch + { + InputTopology.Points => ExecutionMode.InputPoints, + InputTopology.Lines => ExecutionMode.InputLines, + InputTopology.LinesAdjacency => ExecutionMode.InputLinesAdjacency, + InputTopology.Triangles => ExecutionMode.Triangles, + InputTopology.TrianglesAdjacency => ExecutionMode.InputTrianglesAdjacency, + _ => throw new InvalidOperationException($"Invalid input topology \"{context.Definitions.InputTopology}\"."), + }); + + context.AddExecutionMode(spvFunc, ExecutionMode.Invocations, (SpvLiteralInteger)context.Definitions.ThreadsPerInputPrimitive); + + context.AddExecutionMode(spvFunc, context.Definitions.OutputTopology switch + { + OutputTopology.PointList => ExecutionMode.OutputPoints, + OutputTopology.LineStrip => ExecutionMode.OutputLineStrip, + OutputTopology.TriangleStrip => ExecutionMode.OutputTriangleStrip, + _ => throw new InvalidOperationException($"Invalid output topology \"{context.Definitions.OutputTopology}\"."), + }); + + context.AddExecutionMode(spvFunc, ExecutionMode.OutputVertices, (SpvLiteralInteger)context.Definitions.MaxOutputVertices); + } + else if (context.Definitions.Stage == ShaderStage.Fragment) + { + context.AddExecutionMode(spvFunc, context.Definitions.OriginUpperLeft + ? ExecutionMode.OriginUpperLeft + : ExecutionMode.OriginLowerLeft); + + if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Output, IoVariable.FragmentOutputDepth))) + { + context.AddExecutionMode(spvFunc, ExecutionMode.DepthReplacing); + } + + if (context.Definitions.EarlyZForce) + { + context.AddExecutionMode(spvFunc, ExecutionMode.EarlyFragmentTests); + } + + if ((info.HelperFunctionsMask & HelperFunctionsMask.FSI) != 0 && + context.HostCapabilities.SupportsFragmentShaderInterlock) + { + context.AddExecutionMode(spvFunc, ExecutionMode.PixelInterlockOrderedEXT); + } + } + else if (context.Definitions.Stage == ShaderStage.Compute) + { + var localSizeX = (SpvLiteralInteger)context.Definitions.ComputeLocalSizeX; + var localSizeY = (SpvLiteralInteger)context.Definitions.ComputeLocalSizeY; + var localSizeZ = (SpvLiteralInteger)context.Definitions.ComputeLocalSizeZ; + + context.AddExecutionMode( + spvFunc, + ExecutionMode.LocalSize, + localSizeX, + localSizeY, + localSizeZ); + } + + if (context.Definitions.TransformFeedbackEnabled && context.Definitions.LastInVertexPipeline) + { + context.AddExecutionMode(spvFunc, ExecutionMode.Xfb); + } + } + } + + private static void Generate(CodeGenContext context, AstBlock block) + { + AstBlockVisitor visitor = new(block); + + var loopTargets = new Dictionary(); + + context.LoopTargets = loopTargets; + + visitor.BlockEntered += (sender, e) => + { + AstBlock mergeBlock = e.Block.Parent; + + if (e.Block.Type == AstBlockType.If) + { + AstBlock ifTrueBlock = e.Block; + AstBlock ifFalseBlock; + + if (AstHelper.Next(e.Block) is AstBlock nextBlock && nextBlock.Type == AstBlockType.Else) + { + ifFalseBlock = nextBlock; + } + else + { + ifFalseBlock = mergeBlock; + } + + var condition = context.Get(AggregateType.Bool, e.Block.Condition); + + context.SelectionMerge(context.GetNextLabel(mergeBlock), SelectionControlMask.MaskNone); + context.BranchConditional(condition, context.GetNextLabel(ifTrueBlock), context.GetNextLabel(ifFalseBlock)); + } + else if (e.Block.Type == AstBlockType.DoWhile) + { + var continueTarget = context.Label(); + + loopTargets.Add(e.Block, (context.NewBlock(), continueTarget)); + + context.LoopMerge(context.GetNextLabel(mergeBlock), continueTarget, LoopControlMask.MaskNone); + context.Branch(context.GetFirstLabel(e.Block)); + } + + context.EnterBlock(e.Block); + }; + + visitor.BlockLeft += (sender, e) => + { + if (e.Block.Parent != null) + { + if (e.Block.Type == AstBlockType.DoWhile) + { + // This is a loop, we need to jump back to the loop header + // if the condition is true. + AstBlock mergeBlock = e.Block.Parent; + + var (loopTarget, continueTarget) = loopTargets[e.Block]; + + context.Branch(continueTarget); + context.AddLabel(continueTarget); + + var condition = context.Get(AggregateType.Bool, e.Block.Condition); + + context.BranchConditional(condition, loopTarget, context.GetNextLabel(mergeBlock)); + } + else + { + // We only need a branch if the last instruction didn't + // already cause the program to exit or jump elsewhere. + bool lastIsCf = e.Block.Last is AstOperation lastOp && + (lastOp.Inst == Instruction.Discard || + lastOp.Inst == Instruction.LoopBreak || + lastOp.Inst == Instruction.LoopContinue || + lastOp.Inst == Instruction.Return); + + if (!lastIsCf) + { + context.Branch(context.GetNextLabel(e.Block.Parent)); + } + } + + bool hasElse = AstHelper.Next(e.Block) is AstBlock nextBlock && + (nextBlock.Type == AstBlockType.Else || + nextBlock.Type == AstBlockType.ElseIf); + + // Re-enter the parent block. + if (e.Block.Parent != null && !hasElse) + { + context.EnterBlock(e.Block.Parent); + } + } + }; + + foreach (IAstNode node in visitor.Visit()) + { + if (node is AstAssignment assignment) + { + var dest = (AstOperand)assignment.Destination; + + if (dest.Type == OperandType.LocalVariable) + { + var source = context.Get(dest.VarType, assignment.Source); + context.Store(context.GetLocalPointer(dest), source); + } + else if (dest.Type == OperandType.Argument) + { + var source = context.Get(dest.VarType, assignment.Source); + context.Store(context.GetArgumentPointer(dest), source); + } + else + { + throw new NotImplementedException(dest.Type.ToString()); + } + } + else if (node is AstOperation operation) + { + Instructions.Generate(context, operation); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Constants.cs b/src/Ryujinx.Graphics.Shader/Constants.cs new file mode 100644 index 00000000..6317369f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Constants.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Shader +{ + static class Constants + { + public const int ConstantBufferSize = 0x10000; // In bytes + + public const int MaxAttributes = 16; + public const int AllAttributesMask = (int)(uint.MaxValue >> (32 - MaxAttributes)); + + public const int NvnBaseVertexByteOffset = 0x640; + public const int NvnBaseInstanceByteOffset = 0x644; + public const int NvnDrawIndexByteOffset = 0x648; + } +} diff --git a/src/Ryujinx.Graphics.Shader/Decoders/Block.cs b/src/Ryujinx.Graphics.Shader/Decoders/Block.cs new file mode 100644 index 00000000..1a694898 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Decoders/Block.cs @@ -0,0 +1,168 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class PushOpInfo + { + public InstOp Op { get; } + public Dictionary Consumers; + + public PushOpInfo(InstOp op) + { + Op = op; + Consumers = new Dictionary(); + } + } + + readonly struct SyncTarget + { + public PushOpInfo PushOpInfo { get; } + public int PushOpId { get; } + + public SyncTarget(PushOpInfo pushOpInfo, int pushOpId) + { + PushOpInfo = pushOpInfo; + PushOpId = pushOpId; + } + } + + class Block + { + public ulong Address { get; set; } + public ulong EndAddress { get; set; } + + public List Predecessors { get; } + public List Successors { get; } + + public List OpCodes { get; } + public List PushOpCodes { get; } + public Dictionary SyncTargets { get; } + + public Block(ulong address) + { + Address = address; + + Predecessors = new List(); + Successors = new List(); + + OpCodes = new List(); + PushOpCodes = new List(); + SyncTargets = new Dictionary(); + } + + public void Split(Block rightBlock) + { + int splitIndex = BinarySearch(OpCodes, rightBlock.Address); + + if (OpCodes[splitIndex].Address < rightBlock.Address) + { + splitIndex++; + } + + int splitCount = OpCodes.Count - splitIndex; + if (splitCount <= 0) + { + throw new ArgumentException("Can't split at right block address."); + } + + rightBlock.EndAddress = EndAddress; + rightBlock.Successors.AddRange(Successors); + rightBlock.Predecessors.Add(this); + + EndAddress = rightBlock.Address; + + Successors.Clear(); + Successors.Add(rightBlock); + + // Move ops. + rightBlock.OpCodes.AddRange(OpCodes.GetRange(splitIndex, splitCount)); + + OpCodes.RemoveRange(splitIndex, splitCount); + + // Update push consumers that points to this block. + foreach (SyncTarget syncTarget in SyncTargets.Values) + { + PushOpInfo pushOpInfo = syncTarget.PushOpInfo; + + Operand local = pushOpInfo.Consumers[this]; + pushOpInfo.Consumers.Remove(this); + pushOpInfo.Consumers.Add(rightBlock, local); + } + + foreach ((ulong key, SyncTarget value) in SyncTargets) + { + rightBlock.SyncTargets.Add(key, value); + } + + SyncTargets.Clear(); + + // Move push ops. + for (int i = 0; i < PushOpCodes.Count; i++) + { + if (PushOpCodes[i].Op.Address >= rightBlock.Address) + { + int count = PushOpCodes.Count - i; + rightBlock.PushOpCodes.AddRange(PushOpCodes.Skip(i)); + PushOpCodes.RemoveRange(i, count); + break; + } + } + } + + private static int BinarySearch(List opCodes, ulong address) + { + int left = 0; + int middle = 0; + int right = opCodes.Count - 1; + + while (left <= right) + { + int size = right - left; + + middle = left + (size >> 1); + + InstOp opCode = opCodes[middle]; + + if (address == opCode.Address) + { + break; + } + + if (address < opCode.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return middle; + } + + public InstOp GetLastOp() + { + if (OpCodes.Count != 0) + { + return OpCodes[^1]; + } + + return default; + } + + public bool HasNext() + { + InstOp lastOp = GetLastOp(); + return OpCodes.Count != 0 && !Decoder.IsUnconditionalBranch(ref lastOp); + } + + public void AddPushOp(InstOp op) + { + PushOpCodes.Add(new PushOpInfo(op)); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Decoders/DecodedFunction.cs b/src/Ryujinx.Graphics.Shader/Decoders/DecodedFunction.cs new file mode 100644 index 00000000..49cd3a30 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Decoders/DecodedFunction.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + class DecodedFunction + { + private readonly HashSet _callers; + + public bool IsCompilerGenerated => Type != FunctionType.User; + public FunctionType Type { get; set; } + public int Id { get; set; } + + public ulong Address { get; } + public Block[] Blocks { get; private set; } + + public DecodedFunction(ulong address) + { + Address = address; + _callers = new HashSet(); + Type = FunctionType.User; + Id = -1; + } + + public void SetBlocks(Block[] blocks) + { + if (Blocks != null) + { + throw new InvalidOperationException("Blocks have already been set."); + } + + Blocks = blocks; + } + + public void AddCaller(DecodedFunction caller) + { + _callers.Add(caller); + } + + public void RemoveCaller(DecodedFunction caller) + { + if (_callers.Remove(caller) && _callers.Count == 0) + { + Type = FunctionType.Unused; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Decoders/DecodedProgram.cs b/src/Ryujinx.Graphics.Shader/Decoders/DecodedProgram.cs new file mode 100644 index 00000000..fdf3eacc --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Decoders/DecodedProgram.cs @@ -0,0 +1,78 @@ +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + readonly struct DecodedProgram : IEnumerable + { + public DecodedFunction MainFunction { get; } + private readonly IReadOnlyDictionary _functions; + private readonly List _functionsWithId; + public int FunctionsWithIdCount => _functionsWithId.Count; + + public AttributeUsage AttributeUsage { get; } + public FeatureFlags UsedFeatures { get; } + public byte ClipDistancesWritten { get; } + public int Cb1DataSize { get; } + + public DecodedProgram( + DecodedFunction mainFunction, + IReadOnlyDictionary functions, + AttributeUsage attributeUsage, + FeatureFlags usedFeatures, + byte clipDistancesWritten, + int cb1DataSize) + { + MainFunction = mainFunction; + _functions = functions; + _functionsWithId = new(); + AttributeUsage = attributeUsage; + UsedFeatures = usedFeatures; + ClipDistancesWritten = clipDistancesWritten; + Cb1DataSize = cb1DataSize; + } + + public DecodedFunction GetFunctionByAddress(ulong address) + { + if (_functions.TryGetValue(address, out DecodedFunction function)) + { + return function; + } + + return null; + } + + public DecodedFunction GetFunctionById(int id) + { + if ((uint)id >= (uint)_functionsWithId.Count) + { + throw new ArgumentOutOfRangeException(nameof(id)); + } + + return _functionsWithId[id]; + } + + public void AddFunctionAndSetId(DecodedFunction function) + { + function.Id = _functionsWithId.Count; + _functionsWithId.Add(function); + } + + public IoUsage GetIoUsage() + { + return new IoUsage(UsedFeatures, ClipDistancesWritten, AttributeUsage.UsedOutputAttributes); + } + + public IEnumerator GetEnumerator() + { + return _functions.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs b/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs new file mode 100644 index 00000000..1211e561 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs @@ -0,0 +1,905 @@ +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class Decoder + { + private class Context + { + public AttributeUsage AttributeUsage { get; } + public FeatureFlags UsedFeatures { get; private set; } + public byte ClipDistancesWritten { get; private set; } + public int Cb1DataSize { get; private set; } + + private readonly IGpuAccessor _gpuAccessor; + + public Context(IGpuAccessor gpuAccessor) + { + _gpuAccessor = gpuAccessor; + AttributeUsage = new(gpuAccessor); + } + + public uint ConstantBuffer1Read(int offset) + { + if (Cb1DataSize < offset + 4) + { + Cb1DataSize = offset + 4; + } + + return _gpuAccessor.ConstantBuffer1Read(offset); + } + + public void SetUsedFeature(FeatureFlags flags) + { + UsedFeatures |= flags; + } + + public void SetClipDistanceWritten(int index) + { + ClipDistancesWritten |= (byte)(1 << index); + } + } + + public static DecodedProgram Decode(ShaderDefinitions definitions, IGpuAccessor gpuAccessor, ulong startAddress) + { + Context context = new(gpuAccessor); + Queue functionsQueue = new(); + Dictionary functionsVisited = new(); + + DecodedFunction EnqueueFunction(ulong address) + { + if (!functionsVisited.TryGetValue(address, out DecodedFunction function)) + { + functionsVisited.Add(address, function = new DecodedFunction(address)); + functionsQueue.Enqueue(function); + } + + return function; + } + + DecodedFunction mainFunction = EnqueueFunction(0); + + while (functionsQueue.TryDequeue(out DecodedFunction currentFunction)) + { + List blocks = new(); + Queue workQueue = new(); + Dictionary visited = new(); + + Block GetBlock(ulong blkAddress) + { + if (!visited.TryGetValue(blkAddress, out Block block)) + { + block = new Block(blkAddress); + + workQueue.Enqueue(block); + visited.Add(blkAddress, block); + } + + return block; + } + + GetBlock(currentFunction.Address); + + bool hasNewTarget; + + do + { + while (workQueue.TryDequeue(out Block currBlock)) + { + // Check if the current block is inside another block. + if (BinarySearch(blocks, currBlock.Address, out int nBlkIndex)) + { + Block nBlock = blocks[nBlkIndex]; + + if (nBlock.Address == currBlock.Address) + { + throw new InvalidOperationException("Found duplicate block address on the list."); + } + + nBlock.Split(currBlock); + blocks.Insert(nBlkIndex + 1, currBlock); + + continue; + } + + // If we have a block after the current one, set the limit address. + ulong limitAddress = ulong.MaxValue; + + if (nBlkIndex != blocks.Count) + { + Block nBlock = blocks[nBlkIndex]; + + int nextIndex = nBlkIndex + 1; + + if (nBlock.Address < currBlock.Address && nextIndex < blocks.Count) + { + limitAddress = blocks[nextIndex].Address; + } + else if (nBlock.Address > currBlock.Address) + { + limitAddress = blocks[nBlkIndex].Address; + } + } + + FillBlock(definitions, gpuAccessor, context, currBlock, limitAddress, startAddress); + + if (currBlock.OpCodes.Count != 0) + { + // We should have blocks for all possible branch targets, + // including those from PBK/PCNT/SSY instructions. + foreach (PushOpInfo pushOp in currBlock.PushOpCodes) + { + GetBlock(pushOp.Op.GetAbsoluteAddress()); + } + + // Set child blocks. "Branch" is the block the branch instruction + // points to (when taken), "Next" is the block at the next address, + // executed when the branch is not taken. For Unconditional Branches + // or end of program, Next is null. + InstOp lastOp = currBlock.GetLastOp(); + + if (lastOp.Name == InstName.Cal) + { + EnqueueFunction(lastOp.GetAbsoluteAddress()).AddCaller(currentFunction); + } + else if (lastOp.Name == InstName.Bra) + { + Block succBlock = GetBlock(lastOp.GetAbsoluteAddress()); + currBlock.Successors.Add(succBlock); + succBlock.Predecessors.Add(currBlock); + } + + if (!IsUnconditionalBranch(ref lastOp)) + { + Block succBlock = GetBlock(currBlock.EndAddress); + currBlock.Successors.Insert(0, succBlock); + succBlock.Predecessors.Add(currBlock); + } + } + + // Insert the new block on the list (sorted by address). + if (blocks.Count != 0) + { + Block nBlock = blocks[nBlkIndex]; + + blocks.Insert(nBlkIndex + (nBlock.Address < currBlock.Address ? 1 : 0), currBlock); + } + else + { + blocks.Add(currBlock); + } + } + + // Propagate SSY/PBK addresses into their uses (SYNC/BRK). + foreach (Block block in blocks.Where(x => x.PushOpCodes.Count != 0)) + { + for (int pushOpIndex = 0; pushOpIndex < block.PushOpCodes.Count; pushOpIndex++) + { + PropagatePushOp(visited, block, pushOpIndex); + } + } + + // Try to find targets for BRX (indirect branch) instructions. + hasNewTarget = FindBrxTargets(context, blocks, GetBlock); + + // If we discovered new branch targets from the BRX instruction, + // we need another round of decoding to decode the new blocks. + // Additionally, we may have more SSY/PBK targets to propagate, + // and new BRX instructions. + } + while (hasNewTarget); + + currentFunction.SetBlocks(blocks.ToArray()); + } + + return new DecodedProgram( + mainFunction, + functionsVisited, + context.AttributeUsage, + context.UsedFeatures, + context.ClipDistancesWritten, + context.Cb1DataSize); + } + + private static bool BinarySearch(List blocks, ulong address, out int index) + { + index = 0; + + int left = 0; + int right = blocks.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Block block = blocks[middle]; + + index = middle; + + if (address >= block.Address && address < block.EndAddress) + { + return true; + } + + if (address < block.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return false; + } + + private static void FillBlock( + ShaderDefinitions definitions, + IGpuAccessor gpuAccessor, + Context context, + Block block, + ulong limitAddress, + ulong startAddress) + { + ulong address = block.Address; + int bufferOffset = 0; + ReadOnlySpan buffer = ReadOnlySpan.Empty; + + InstOp op = default; + + do + { + if (address + 7 >= limitAddress) + { + break; + } + + // Ignore scheduling instructions, which are written every 32 bytes. + if ((address & 0x1f) == 0) + { + address += 8; + bufferOffset++; + continue; + } + + if (bufferOffset >= buffer.Length) + { + buffer = gpuAccessor.GetCode(startAddress + address, 8); + bufferOffset = 0; + } + + ulong opCode = buffer[bufferOffset++]; + + op = InstTable.GetOp(address, opCode); + + if (op.Props.HasFlag(InstProps.TexB)) + { + context.SetUsedFeature(FeatureFlags.Bindless); + } + + switch (op.Name) + { + case InstName.Ald: + case InstName.Ast: + case InstName.Ipa: + SetUserAttributeUses(definitions, context, op.Name, opCode); + break; + case InstName.Pbk: + case InstName.Pcnt: + case InstName.Ssy: + block.AddPushOp(op); + break; + case InstName.Shfl: + context.SetUsedFeature(FeatureFlags.Shuffle); + break; + case InstName.Ldl: + case InstName.Stl: + context.SetUsedFeature(FeatureFlags.LocalMemory); + break; + case InstName.Atoms: + case InstName.AtomsCas: + case InstName.Lds: + case InstName.Sts: + context.SetUsedFeature(FeatureFlags.SharedMemory); + break; + case InstName.Atom: + case InstName.AtomCas: + case InstName.Red: + case InstName.Stg: + case InstName.Suatom: + case InstName.SuatomB: + case InstName.SuatomB2: + case InstName.SuatomCas: + case InstName.SuatomCasB: + case InstName.Sured: + case InstName.SuredB: + case InstName.Sust: + case InstName.SustB: + case InstName.SustD: + case InstName.SustDB: + context.SetUsedFeature(FeatureFlags.Store); + break; + } + + block.OpCodes.Add(op); + + address += 8; + } + while (!op.Props.HasFlag(InstProps.Bra)); + + block.EndAddress = address; + } + + private static void SetUserAttributeUses(ShaderDefinitions definitions, Context context, InstName name, ulong opCode) + { + int offset; + int count = 1; + bool isStore = false; + bool indexed; + bool perPatch = false; + + if (name == InstName.Ast) + { + InstAst opAst = new(opCode); + count = (int)opAst.AlSize + 1; + offset = opAst.Imm11; + indexed = opAst.Phys; + perPatch = opAst.P; + isStore = true; + } + else if (name == InstName.Ald) + { + InstAld opAld = new(opCode); + count = (int)opAld.AlSize + 1; + offset = opAld.Imm11; + indexed = opAld.Phys; + perPatch = opAld.P; + isStore = opAld.O; + } + else /* if (name == InstName.Ipa) */ + { + InstIpa opIpa = new(opCode); + offset = opIpa.Imm10; + indexed = opIpa.Idx; + } + + if (indexed) + { + if (isStore) + { + context.AttributeUsage.SetAllOutputUserAttributes(); + definitions.EnableOutputIndexing(); + } + else + { + context.AttributeUsage.SetAllInputUserAttributes(); + definitions.EnableInputIndexing(); + } + } + else + { + for (int elemIndex = 0; elemIndex < count; elemIndex++) + { + int attr = offset + elemIndex * 4; + + if (perPatch) + { + if (attr >= AttributeConsts.UserAttributePerPatchBase && attr < AttributeConsts.UserAttributePerPatchEnd) + { + int userAttr = attr - AttributeConsts.UserAttributePerPatchBase; + int index = userAttr / 16; + + if (isStore) + { + context.AttributeUsage.SetOutputUserAttributePerPatch(index); + } + else + { + context.AttributeUsage.SetInputUserAttributePerPatch(index); + } + } + } + else if (attr >= AttributeConsts.UserAttributeBase && attr < AttributeConsts.UserAttributeEnd) + { + int userAttr = attr - AttributeConsts.UserAttributeBase; + int index = userAttr / 16; + + if (isStore) + { + context.AttributeUsage.SetOutputUserAttribute(index); + } + else + { + context.AttributeUsage.SetInputUserAttribute(index, (userAttr >> 2) & 3); + } + } + + if (!isStore && + (attr == AttributeConsts.FogCoord || + (attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0) || + (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd))) + { + context.SetUsedFeature(FeatureFlags.FixedFuncAttr); + } + else + { + if (isStore) + { + switch (attr) + { + case AttributeConsts.Layer: + if (definitions.Stage != ShaderStage.Compute && definitions.Stage != ShaderStage.Fragment) + { + context.SetUsedFeature(FeatureFlags.RtLayer); + } + break; + case AttributeConsts.ViewportIndex: + if (definitions.Stage != ShaderStage.Fragment) + { + context.SetUsedFeature(FeatureFlags.ViewportIndex); + } + break; + case AttributeConsts.ClipDistance0: + case AttributeConsts.ClipDistance1: + case AttributeConsts.ClipDistance2: + case AttributeConsts.ClipDistance3: + case AttributeConsts.ClipDistance4: + case AttributeConsts.ClipDistance5: + case AttributeConsts.ClipDistance6: + case AttributeConsts.ClipDistance7: + if (definitions.Stage.IsVtg()) + { + context.SetClipDistanceWritten((attr - AttributeConsts.ClipDistance0) / 4); + } + break; + case AttributeConsts.ViewportMask: + if (definitions.Stage != ShaderStage.Fragment) + { + context.SetUsedFeature(FeatureFlags.ViewportMask); + } + break; + } + } + else + { + switch (attr) + { + case AttributeConsts.PositionX: + case AttributeConsts.PositionY: + if (definitions.Stage == ShaderStage.Fragment) + { + context.SetUsedFeature(FeatureFlags.FragCoordXY); + } + break; + case AttributeConsts.InstanceId: + if (definitions.Stage == ShaderStage.Vertex) + { + context.SetUsedFeature(FeatureFlags.InstanceId); + } + break; + } + } + } + } + } + } + + public static bool IsUnconditionalBranch(ref InstOp op) + { + return IsUnconditional(ref op) && op.Props.HasFlag(InstProps.Bra); + } + + private static bool IsUnconditional(ref InstOp op) + { + InstConditional condOp = new(op.RawOpCode); + + if ((op.Name == InstName.Bra || op.Name == InstName.Exit) && condOp.Ccc != Ccc.T) + { + return false; + } + + return condOp.Pred == RegisterConsts.PredicateTrueIndex && !condOp.PredInv; + } + + private static bool FindBrxTargets(Context context, IEnumerable blocks, Func getBlock) + { + bool hasNewTarget = false; + + foreach (Block block in blocks) + { + InstOp lastOp = block.GetLastOp(); + bool hasNext = block.HasNext(); + + if (lastOp.Name == InstName.Brx && block.Successors.Count == (hasNext ? 1 : 0)) + { + HashSet visited = new(); + + InstBrx opBrx = new(lastOp.RawOpCode); + ulong baseOffset = lastOp.GetAbsoluteAddress(); + + // An indirect branch could go anywhere, + // try to get the possible target offsets from the constant buffer. + (int cbBaseOffset, int cbOffsetsCount) = FindBrxTargetRange(block, opBrx.SrcA); + + if (cbOffsetsCount != 0) + { + hasNewTarget = true; + } + + for (int i = 0; i < cbOffsetsCount; i++) + { + uint targetOffset = context.ConstantBuffer1Read(cbBaseOffset + i * 4); + ulong targetAddress = baseOffset + targetOffset; + + if (visited.Add(targetAddress)) + { + Block target = getBlock(targetAddress); + target.Predecessors.Add(block); + block.Successors.Add(target); + } + } + } + } + + return hasNewTarget; + } + + private static (int, int) FindBrxTargetRange(Block block, int brxReg) + { + // Try to match the following pattern: + // + // IMNMX.U32 Rx, Rx, UpperBound, PT + // SHL Rx, Rx, 0x2 + // LDC Rx, c[0x1][Rx+BaseOffset] + // + // Here, Rx is an arbitrary register, "UpperBound" and "BaseOffset" are constants. + // The above pattern is assumed to be generated by the compiler before BRX, + // as the instruction is usually used to implement jump tables for switch statement optimizations. + // On a successful match, "BaseOffset" is the offset in bytes where the jump offsets are + // located on the constant buffer, and "UpperBound" is the total number of offsets for the BRX, minus 1. + + HashSet visited = new(); + + var ldcLocation = FindFirstRegWrite(visited, new BlockLocation(block, block.OpCodes.Count - 1), brxReg); + if (ldcLocation.Block == null || ldcLocation.Block.OpCodes[ldcLocation.Index].Name != InstName.Ldc) + { + return (0, 0); + } + + GetOp(ldcLocation, out var opLdc); + + if (opLdc.CbufSlot != 1 || opLdc.AddressMode != 0) + { + return (0, 0); + } + + var shlLocation = FindFirstRegWrite(visited, ldcLocation, opLdc.SrcA); + if (shlLocation.Block == null || !shlLocation.IsImmInst(InstName.Shl)) + { + return (0, 0); + } + + GetOp(shlLocation, out var opShl); + + if (opShl.Imm20 != 2) + { + return (0, 0); + } + + var imnmxLocation = FindFirstRegWrite(visited, shlLocation, opShl.SrcA); + if (imnmxLocation.Block == null || !imnmxLocation.IsImmInst(InstName.Imnmx)) + { + return (0, 0); + } + + GetOp(imnmxLocation, out var opImnmx); + + if (opImnmx.Signed || opImnmx.SrcPred != RegisterConsts.PredicateTrueIndex || opImnmx.SrcPredInv) + { + return (0, 0); + } + + return (opLdc.CbufOffset, opImnmx.Imm20 + 1); + } + + private static void GetOp(BlockLocation location, out T op) where T : unmanaged + { + ulong rawOp = location.Block.OpCodes[location.Index].RawOpCode; + op = Unsafe.As(ref rawOp); + } + + private readonly struct BlockLocation + { + public Block Block { get; } + public int Index { get; } + + public BlockLocation(Block block, int index) + { + Block = block; + Index = index; + } + + public bool IsImmInst(InstName name) + { + InstOp op = Block.OpCodes[Index]; + return op.Name == name && op.Props.HasFlag(InstProps.Ib); + } + } + + private static BlockLocation FindFirstRegWrite(HashSet visited, BlockLocation location, int regIndex) + { + Queue toVisit = new(); + toVisit.Enqueue(location); + visited.Add(location.Block); + + while (toVisit.TryDequeue(out var currentLocation)) + { + Block block = currentLocation.Block; + for (int i = currentLocation.Index - 1; i >= 0; i--) + { + if (WritesToRegister(block.OpCodes[i], regIndex)) + { + return new BlockLocation(block, i); + } + } + + foreach (Block predecessor in block.Predecessors) + { + if (visited.Add(predecessor)) + { + toVisit.Enqueue(new BlockLocation(predecessor, predecessor.OpCodes.Count)); + } + } + } + + return new BlockLocation(null, 0); + } + + private static bool WritesToRegister(InstOp op, int regIndex) + { + // Predicate instruction only ever writes to predicate, so we shouldn't check those. + if ((op.Props & (InstProps.Rd | InstProps.Rd2)) == 0) + { + return false; + } + + if (op.Props.HasFlag(InstProps.Rd2) && (byte)(op.RawOpCode >> 28) == regIndex) + { + return true; + } + + return (byte)op.RawOpCode == regIndex; + } + + private enum MergeType + { + Brk, + Cont, + Sync, + } + + private readonly struct PathBlockState + { + public Block Block { get; } + + private enum RestoreType + { + None, + PopPushOp, + PushBranchOp, + } + + private readonly RestoreType _restoreType; + + private readonly ulong _restoreValue; + private readonly MergeType _restoreMergeType; + + public bool ReturningFromVisit => _restoreType != RestoreType.None; + + public PathBlockState(Block block) + { + Block = block; + _restoreType = RestoreType.None; + _restoreValue = 0; + _restoreMergeType = default; + } + + public PathBlockState(int oldStackSize) + { + Block = null; + _restoreType = RestoreType.PopPushOp; + _restoreValue = (ulong)oldStackSize; + _restoreMergeType = default; + } + + public PathBlockState(ulong syncAddress, MergeType mergeType) + { + Block = null; + _restoreType = RestoreType.PushBranchOp; + _restoreValue = syncAddress; + _restoreMergeType = mergeType; + } + + public void RestoreStackState(Stack<(ulong, MergeType)> branchStack) + { + if (_restoreType == RestoreType.PushBranchOp) + { + branchStack.Push((_restoreValue, _restoreMergeType)); + } + else if (_restoreType == RestoreType.PopPushOp) + { + while (branchStack.Count > (uint)_restoreValue) + { + branchStack.Pop(); + } + } + } + } + + private static void PropagatePushOp(Dictionary blocks, Block currBlock, int pushOpIndex) + { + PushOpInfo pushOpInfo = currBlock.PushOpCodes[pushOpIndex]; + InstOp pushOp = pushOpInfo.Op; + + Block target = blocks[pushOp.GetAbsoluteAddress()]; + + Stack workQueue = new(); + HashSet visited = new(); + Stack<(ulong, MergeType)> branchStack = new(); + + void Push(PathBlockState pbs) + { + // When block is null, this means we are pushing a restore operation. + // Restore operations are used to undo the work done inside a block + // when we return from it, for example it pops addresses pushed by + // SSY/PBK instructions inside the block, and pushes addresses poped + // by SYNC/BRK. + // For blocks, if it's already visited, we just ignore to avoid going + // around in circles and getting stuck here. + if (pbs.Block == null || !visited.Contains(pbs.Block)) + { + workQueue.Push(pbs); + } + } + + Push(new PathBlockState(currBlock)); + + while (workQueue.TryPop(out PathBlockState pbs)) + { + if (pbs.ReturningFromVisit) + { + pbs.RestoreStackState(branchStack); + + continue; + } + + Block current = pbs.Block; + + // If the block was already processed, we just ignore it, otherwise + // we would push the same child blocks of an already processed block, + // and go around in circles until memory is exhausted. + if (!visited.Add(current)) + { + continue; + } + + int pushOpsCount = current.PushOpCodes.Count; + if (pushOpsCount != 0) + { + Push(new PathBlockState(branchStack.Count)); + + for (int index = pushOpIndex; index < pushOpsCount; index++) + { + InstOp currentPushOp = current.PushOpCodes[index].Op; + MergeType pushMergeType = GetMergeTypeFromPush(currentPushOp.Name); + branchStack.Push((currentPushOp.GetAbsoluteAddress(), pushMergeType)); + } + } + + pushOpIndex = 0; + + bool hasNext = current.HasNext(); + if (hasNext) + { + Push(new PathBlockState(current.Successors[0])); + } + + InstOp lastOp = current.GetLastOp(); + if (IsPopBranch(lastOp.Name)) + { + MergeType popMergeType = GetMergeTypeFromPop(lastOp.Name); + + bool found = true; + ulong targetAddress = 0UL; + MergeType mergeType; + + do + { + if (branchStack.Count == 0) + { + found = false; + break; + } + + (targetAddress, mergeType) = branchStack.Pop(); + + // Push the target address (this will be used to push the address + // back into the PBK/PCNT/SSY stack when we return from that block), + Push(new PathBlockState(targetAddress, mergeType)); + } + while (mergeType != popMergeType); + + // Make sure we found the correct address, + // the push and pop instruction types must match, so: + // - BRK can only consume addresses pushed by PBK. + // - CONT can only consume addresses pushed by PCNT. + // - SYNC can only consume addresses pushed by SSY. + if (found) + { + if (branchStack.Count == 0) + { + // If the entire stack was consumed, then the current pop instruction + // just consumed the address from our push instruction. + if (current.SyncTargets.TryAdd(pushOp.Address, new SyncTarget(pushOpInfo, current.SyncTargets.Count))) + { + pushOpInfo.Consumers.Add(current, Local()); + target.Predecessors.Add(current); + current.Successors.Add(target); + } + } + else + { + // Push the block itself into the work queue for processing. + Push(new PathBlockState(blocks[targetAddress])); + } + } + } + else + { + // By adding them in descending order (sorted by address), we process the blocks + // in order (of ascending address), since we work with a LIFO. + foreach (Block possibleTarget in current.Successors.OrderByDescending(x => x.Address)) + { + if (!hasNext || possibleTarget != current.Successors[0]) + { + Push(new PathBlockState(possibleTarget)); + } + } + } + } + } + + public static bool IsPopBranch(InstName name) + { + return name == InstName.Brk || name == InstName.Cont || name == InstName.Sync; + } + + private static MergeType GetMergeTypeFromPush(InstName name) + { + return name switch + { + InstName.Pbk => MergeType.Brk, + InstName.Pcnt => MergeType.Cont, + _ => MergeType.Sync, + }; + } + + private static MergeType GetMergeTypeFromPop(InstName name) + { + return name switch + { + InstName.Brk => MergeType.Brk, + InstName.Cont => MergeType.Cont, + _ => MergeType.Sync, + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Decoders/FunctionType.cs b/src/Ryujinx.Graphics.Shader/Decoders/FunctionType.cs new file mode 100644 index 00000000..6ea6a82a --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Decoders/FunctionType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum FunctionType : byte + { + User, + Unused, + BuiltInFSIBegin, + BuiltInFSIEnd + } +} diff --git a/src/Ryujinx.Graphics.Shader/Decoders/InstDecoders.cs b/src/Ryujinx.Graphics.Shader/Decoders/InstDecoders.cs new file mode 100644 index 00000000..8bf5671a --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Decoders/InstDecoders.cs @@ -0,0 +1,5394 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum AlSize + { + _32 = 0, + _64 = 1, + _96 = 2, + _128 = 3, + } + + enum AtomSize + { + U32 = 0, + S32 = 1, + U64 = 2, + F32FtzRn = 3, + F16x2FtzRn = 4, + S64 = 5, + } + + enum AtomOp + { + Add = 0, + Min = 1, + Max = 2, + Inc = 3, + Dec = 4, + And = 5, + Or = 6, + Xor = 7, + Exch = 8, + Safeadd = 10, + } + + enum AtomsSize + { + U32 = 0, + S32 = 1, + U64 = 2, + S64 = 3, + } + + enum BarMode + { + Bar = 0, + Result = 1, + Warp = 2, + } + + enum BarOp + { + Sync = 0, + Arv = 1, + Red = 2, + Scan = 3, + SyncAll = 4, + } + + enum BarRedOp + { + Popc = 0, + And = 1, + Or = 2, + } + + enum Bpt + { + DrainIllegal = 0, + Cal = 1, + Pause = 2, + Trap = 3, + Int = 4, + Drain = 5, + } + + enum Ccc + { + F = 0, + Lt = 1, + Eq = 2, + Le = 3, + Gt = 4, + Ne = 5, + Ge = 6, + Num = 7, + Nan = 8, + Ltu = 9, + Equ = 10, + Leu = 11, + Gtu = 12, + Neu = 13, + Geu = 14, + T = 15, + Off = 16, + Lo = 17, + Sff = 18, + Ls = 19, + Hi = 20, + Sft = 21, + Hs = 22, + Oft = 23, + CsmTa = 24, + CsmTr = 25, + CsmMx = 26, + FcsmTa = 27, + FcsmTr = 28, + FcsmMx = 29, + Rle = 30, + Rgt = 31, + } + + enum CacheType + { + U = 1, + C = 2, + I = 3, + Crs = 4, + } + + enum CctlOp + { + Pf1 = 1, + Pf1_5 = 2, + Pf2 = 3, + Wb = 4, + Iv = 5, + Ivall = 6, + Rs = 7, + Rslb = 9, + } + + enum CctltOp + { + Ivth = 1, + } + + enum BoolOp + { + And = 0, + Or = 1, + Xor = 2, + } + + enum SReg + { + LaneId = 0, + Clock = 1, + VirtCfg = 2, + VirtId = 3, + Pm0 = 4, + Pm1 = 5, + Pm2 = 6, + Pm3 = 7, + Pm4 = 8, + Pm5 = 9, + Pm6 = 10, + Pm7 = 11, + OrderingTicket = 15, + PrimType = 16, + InvocationId = 17, + YDirection = 18, + ThreadKill = 19, + ShaderType = 20, + DirectCbeWriteAddressLow = 21, + DirectCbeWriteAddressHigh = 22, + DirectCbeWriteEnabled = 23, + MachineId0 = 24, + MachineId1 = 25, + MachineId2 = 26, + MachineId3 = 27, + Affinity = 28, + InvocationInfo = 29, + WScaleFactorXY = 30, + WScaleFactorZ = 31, + TId = 32, + TIdX = 33, + TIdY = 34, + TIdZ = 35, + CtaParam = 36, + CtaIdX = 37, + CtaIdY = 38, + CtaIdZ = 39, + Ntid = 40, + CirQueueIncrMinusOne = 41, + Nlatc = 42, + Swinlo = 48, + Swinsz = 49, + Smemsz = 50, + Smembanks = 51, + LWinLo = 52, + LWinSz = 53, + LMemLoSz = 54, + LMemHiOff = 55, + EqMask = 56, + LtMask = 57, + LeMask = 58, + GtMask = 59, + GeMask = 60, + RegAlloc = 61, + CtxAddr = 62, + GlobalErrorStatus = 64, + WarpErrorStatus = 66, + WarpErrorStatusClear = 67, + PmHi0 = 72, + PmHi1 = 73, + PmHi2 = 74, + PmHi3 = 75, + PmHi4 = 76, + PmHi5 = 77, + PmHi6 = 78, + PmHi7 = 79, + ClockLo = 80, + ClockHi = 81, + GlobalTimerLo = 82, + GlobalTimerHi = 83, + HwTaskId = 96, + CircularQueueEntryIndex = 97, + CircularQueueEntryAddressLow = 98, + CircularQueueEntryAddressHigh = 99, + } + + enum RoundMode + { + Rn = 0, + Rm = 1, + Rp = 2, + Rz = 3, + } + + enum FComp + { + F = 0, + Lt = 1, + Eq = 2, + Le = 3, + Gt = 4, + Ne = 5, + Ge = 6, + Num = 7, + Nan = 8, + Ltu = 9, + Equ = 10, + Leu = 11, + Gtu = 12, + Neu = 13, + Geu = 14, + T = 15, + } + + enum IntegerRound + { + Pass = 1, + Round = 4, + Floor = 5, + Ceil = 6, + Trunc = 7, + } + + enum IDstFmt + { + U16 = 1, + U32 = 2, + U64 = 3, + S16 = 5, + S32 = 6, + S64 = 7, + } + + enum ISrcFmt + { + U8 = 0, + U16 = 1, + U32 = 2, + U64 = 3, + S8 = 4, + S16 = 5, + S32 = 6, + S64 = 7, + } + + enum ISrcDstFmt + { + U8 = 0, + U16 = 1, + U32 = 2, + S8 = 4, + S16 = 5, + S32 = 6, + } + + enum RoundMode2 + { + Round = 0, + Floor = 1, + Ceil = 2, + Trunc = 3, + } + + enum ChkModeF + { + Divide = 0, + } + + enum Fmz + { + Ftz = 1, + Fmz = 2, + } + + enum MultiplyScale + { + NoScale = 0, + D2 = 1, + D4 = 2, + D8 = 3, + M8 = 4, + M4 = 5, + M2 = 6, + } + + enum OFmt + { + F16 = 0, + F32 = 1, + MrgH0 = 2, + MrgH1 = 3, + } + + enum HalfSwizzle + { + F16 = 0, + F32 = 1, + H0H0 = 2, + H1H1 = 3, + } + + enum ByteSel + { + B0 = 0, + B1 = 1, + B2 = 2, + B3 = 3, + } + + enum DstFmt + { + F16 = 1, + F32 = 2, + F64 = 3, + } + + enum AvgMode + { + NoNeg = 0, + NegB = 1, + NegA = 2, + PlusOne = 3, + } + + enum Lrs + { + None = 0, + RightShift = 1, + LeftShift = 2, + } + + enum HalfSelect + { + B32 = 0, + H0 = 1, + H1 = 2, + } + + enum IComp + { + F = 0, + Lt = 1, + Eq = 2, + Le = 3, + Gt = 4, + Ne = 5, + Ge = 6, + T = 7, + } + + enum XMode + { + Xlo = 1, + Xmed = 2, + Xhi = 3, + } + + enum IpaOp + { + Pass = 0, + Multiply = 1, + Constant = 2, + Sc = 3, + } + + enum IBase + { + Patch = 1, + Prim = 2, + Attr = 3, + } + + enum CacheOpLd + { + Ca = 0, + Cg = 1, + Ci = 2, + Cv = 3, + } + + enum CacheOpSt + { + Wb = 0, + Cg = 1, + Ci = 2, + Wt = 3, + } + + enum LsSize + { + U8 = 0, + S8 = 1, + U16 = 2, + S16 = 3, + B32 = 4, + B64 = 5, + B128 = 6, + UB128 = 7, + } + + enum LsSize2 + { + U8 = 0, + S8 = 1, + U16 = 2, + S16 = 3, + B32 = 4, + B64 = 5, + B128 = 6, + } + + enum AddressMode + { + Il = 1, + Is = 2, + Isl = 3, + } + + enum CacheOp2 + { + Lu = 1, + Ci = 2, + Cv = 3, + } + + enum PredicateOp + { + F = 0, + T = 1, + Z = 2, + Nz = 3, + } + + enum LogicOp + { + And = 0, + Or = 1, + Xor = 2, + PassB = 3, + } + + enum Membar + { + Cta = 0, + Gl = 1, + Sys = 2, + Vc = 3, + } + + enum Ivall + { + Ivalld = 1, + Ivallt = 2, + Ivalltd = 3, + } + + enum MufuOp + { + Cos = 0, + Sin = 1, + Ex2 = 2, + Lg2 = 3, + Rcp = 4, + Rsq = 5, + Rcp64h = 6, + Rsq64h = 7, + Sqrt = 8, + } + + enum OutType + { + Emit = 1, + Cut = 2, + EmitThenCut = 3, + } + + enum PixMode + { + Covmask = 1, + Covered = 2, + Offset = 3, + CentroidOffset = 4, + MyIndex = 5, + } + + enum PMode + { + F4e = 1, + B4e = 2, + Rc8 = 3, + Ecl = 4, + Ecr = 5, + Rc16 = 6, + } + + enum RedOp + { + Add = 0, + Min = 1, + Max = 2, + Inc = 3, + Dec = 4, + And = 5, + Or = 6, + Xor = 7, + } + + enum XModeShf + { + Hi = 1, + X = 2, + Xhi = 3, + } + + enum MaxShift + { + U64 = 2, + S64 = 3, + } + + enum ShflMode + { + Idx = 0, + Up = 1, + Down = 2, + Bfly = 3, + } + + enum Clamp + { + Ign = 0, + Trap = 2, + } + + enum SuatomSize + { + U32 = 0, + S32 = 1, + U64 = 2, + F32FtzRn = 3, + F16x2FtzRn = 4, + S64 = 5, + Sd32 = 6, + Sd64 = 7, + } + + enum SuDim + { + _1d = 0, + _1dBuffer = 1, + _1dArray = 2, + _2d = 3, + _2dArray = 4, + _3d = 5, + } + + enum SuatomOp + { + Add = 0, + Min = 1, + Max = 2, + Inc = 3, + Dec = 4, + And = 5, + Or = 6, + Xor = 7, + Exch = 8, + } + + enum SuSize + { + U8 = 0, + S8 = 1, + U16 = 2, + S16 = 3, + B32 = 4, + B64 = 5, + B128 = 6, + UB128 = 7, + } + + enum SuRgba + { + R = 1, + G = 2, + Rg = 3, + B = 4, + Rb = 5, + Gb = 6, + Rgb = 7, + A = 8, + Ra = 9, + Ga = 10, + Rga = 11, + Ba = 12, + Rba = 13, + Gba = 14, + Rgba = 15, + } + + enum Lod + { + Lz = 1, + Lb = 2, + Ll = 3, + Lba = 6, + Lla = 7, + } + + enum TexDim + { + _1d = 0, + Array1d = 1, + _2d = 2, + Array2d = 3, + _3d = 4, + Array3d = 5, + Cube = 6, + ArrayCube = 7, + } + + enum TexsTarget + { + Texture1DLodZero = 0, + Texture2D = 1, + Texture2DLodZero = 2, + Texture2DLodLevel = 3, + Texture2DDepthCompare = 4, + Texture2DLodLevelDepthCompare = 5, + Texture2DLodZeroDepthCompare = 6, + Texture2DArray = 7, + Texture2DArrayLodZero = 8, + Texture2DArrayLodZeroDepthCompare = 9, + Texture3D = 10, + Texture3DLodZero = 11, + TextureCube = 12, + TextureCubeLodLevel = 13, + } + + enum TldsTarget + { + Texture1DLodZero = 0x0, + Texture1DLodLevel = 0x1, + Texture2DLodZero = 0x2, + Texture2DLodZeroOffset = 0x4, + Texture2DLodLevel = 0x5, + Texture2DLodZeroMultisample = 0x6, + Texture3DLodZero = 0x7, + Texture2DArrayLodZero = 0x8, + Texture2DLodLevelOffset = 0xc, + } + + enum TexComp + { + R = 0, + G = 1, + B = 2, + A = 3, + } + + enum TexOffset + { + None = 0, + Aoffi = 1, + Ptp = 2, + } + + enum TexQuery + { + TexHeaderDimension = 1, + TexHeaderTextureType = 2, + TexHeaderSamplerPos = 5, + TexSamplerFilter = 16, + TexSamplerLod = 18, + TexSamplerWrap = 20, + TexSamplerBorderColor = 22, + } + + [Flags] + enum VectorSelect + { + U8B0 = 0, + U8B1 = 1, + U8B2 = 2, + U8B3 = 3, + U16H0 = 4, + U16H1 = 5, + U32 = 6, + S8B0 = 8, + S8B1 = 9, + S8B2 = 10, + S8B3 = 11, + S16H0 = 12, + S16H1 = 13, + S32 = 14, + } + + enum VideoOp + { + Mrg16h = 0, + Mrg16l = 1, + Mrg8b0 = 2, + Mrg8b2 = 3, + Acc = 4, + Min = 5, + Max = 6, + } + + enum VideoRed + { + Acc = 1, + } + + enum LaneMask4 + { + Z = 1, + W = 2, + Zw = 3, + X = 4, + Xz = 5, + Xw = 6, + Xzw = 7, + Y = 8, + Yz = 9, + Yw = 10, + Yzw = 11, + Xy = 12, + Xyz = 13, + Xyw = 14, + Xyzw = 15, + } + + enum ASelect4 + { + _0000 = 0, + _1111 = 1, + _2222 = 2, + _3333 = 3, + _3210 = 4, + _5432 = 6, + _6543 = 7, + _3201 = 8, + _3012 = 9, + _0213 = 10, + _3120 = 11, + _1230 = 12, + _2310 = 13, + } + + enum BSelect4 + { + _4444 = 0, + _5555 = 1, + _6666 = 2, + _7777 = 3, + _7654 = 4, + _5432 = 6, + _4321 = 7, + _4567 = 8, + _6745 = 9, + _5476 = 10, + } + + enum VideoScale + { + Shr7 = 1, + Shr15 = 2, + } + + enum VoteMode + { + All = 0, + Any = 1, + Eq = 2, + } + + enum XmadCop + { + Cfull = 0, + Clo = 1, + Chi = 2, + Csfu = 3, + Cbcc = 4, + } + + enum XmadCop2 + { + Cfull = 0, + Clo = 1, + Chi = 2, + Csfu = 3, + } + + enum ImadspASelect + { + U32 = 0, + S32 = 1, + U24 = 2, + S24 = 3, + U16h0 = 4, + S16h0 = 5, + U16h1 = 6, + S16h1 = 7, + } + + enum ImadspBSelect + { + U24 = 0, + S24 = 1, + U16h0 = 2, + S16h0 = 3, + } + + readonly struct InstConditional + { + private readonly ulong _opcode; + public InstConditional(ulong opcode) => _opcode = opcode; + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Ccc Ccc => (Ccc)(_opcode & 0x1F); + } + + readonly struct InstAl2p + { + private readonly ulong _opcode; + public InstAl2p(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public AlSize AlSize => (AlSize)((_opcode >> 47) & 0x3); + public bool Aio => (_opcode & 0x100000000) != 0; + public int Imm11 => (int)((_opcode >> 20) & 0x7FF); + public int DestPred => (int)((_opcode >> 44) & 0x7); + } + + readonly struct InstAld + { + private readonly ulong _opcode; + public InstAld(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm11 => (int)((_opcode >> 20) & 0x7FF); + public bool P => (_opcode & 0x80000000) != 0; + public bool O => (_opcode & 0x100000000) != 0; + public AlSize AlSize => (AlSize)((_opcode >> 47) & 0x3); + public bool Phys => !P && Imm11 == 0 && SrcA != RegisterConsts.RegisterZeroIndex; + } + + readonly struct InstAst + { + private readonly ulong _opcode; + public InstAst(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)(_opcode & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm11 => (int)((_opcode >> 20) & 0x7FF); + public bool P => (_opcode & 0x80000000) != 0; + public AlSize AlSize => (AlSize)((_opcode >> 47) & 0x3); + public bool Phys => !P && Imm11 == 0 && SrcA != RegisterConsts.RegisterZeroIndex; + } + + readonly struct InstAtom + { + private readonly ulong _opcode; + public InstAtom(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm20 => (int)((_opcode >> 28) & 0xFFFFF); + public AtomSize Size => (AtomSize)((_opcode >> 49) & 0x7); + public AtomOp Op => (AtomOp)((_opcode >> 52) & 0xF); + public bool E => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstAtomCas + { + private readonly ulong _opcode; + public InstAtomCas(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int BcRz => (int)((_opcode >> 50) & 0x3); + public bool E => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstAtoms + { + private readonly ulong _opcode; + public InstAtoms(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm22 => (int)((_opcode >> 30) & 0x3FFFFF); + public AtomsSize AtomsSize => (AtomsSize)((_opcode >> 28) & 0x3); + public AtomOp AtomOp => (AtomOp)((_opcode >> 52) & 0xF); + } + + readonly struct InstAtomsCas + { + private readonly ulong _opcode; + public InstAtomsCas(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int AtomsBcRz => (int)((_opcode >> 28) & 0x3); + } + + readonly struct InstB2r + { + private readonly ulong _opcode; + public InstB2r(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int DestPred => (int)((_opcode >> 45) & 0x7); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public BarMode Mode => (BarMode)((_opcode >> 32) & 0x3); + } + + readonly struct InstBar + { + private readonly ulong _opcode; + public InstBar(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm12 => (int)((_opcode >> 20) & 0xFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public BarOp BarOp => (BarOp)((_opcode >> 32) & 0x7); + public BarRedOp BarRedOp => (BarRedOp)((_opcode >> 35) & 0x3); + public bool AFixBar => (_opcode & 0x100000000000) != 0; + public bool BFixBar => (_opcode & 0x80000000000) != 0; + } + + readonly struct InstBfeR + { + private readonly ulong _opcode; + public InstBfeR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Signed => (_opcode & 0x1000000000000) != 0; + public bool Brev => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstBfeI + { + private readonly ulong _opcode; + public InstBfeI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Signed => (_opcode & 0x1000000000000) != 0; + public bool Brev => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstBfeC + { + private readonly ulong _opcode; + public InstBfeC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Signed => (_opcode & 0x1000000000000) != 0; + public bool Brev => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstBfiR + { + private readonly ulong _opcode; + public InstBfiR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + } + + readonly struct InstBfiI + { + private readonly ulong _opcode; + public InstBfiI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + } + + readonly struct InstBfiC + { + private readonly ulong _opcode; + public InstBfiC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + } + + readonly struct InstBfiRc + { + private readonly ulong _opcode; + public InstBfiRc(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + } + + readonly struct InstBpt + { + private readonly ulong _opcode; + public InstBpt(ulong opcode) => _opcode = opcode; + public int Imm20 => (int)((_opcode >> 20) & 0xFFFFF); + public Bpt Bpt => (Bpt)((_opcode >> 6) & 0x7); + } + + readonly struct InstBra + { + private readonly ulong _opcode; + public InstBra(ulong opcode) => _opcode = opcode; + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Ccc Ccc => (Ccc)(_opcode & 0x1F); + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + public bool Ca => (_opcode & 0x20) != 0; + public bool Lmt => (_opcode & 0x40) != 0; + public bool U => (_opcode & 0x80) != 0; + } + + readonly struct InstBrk + { + private readonly ulong _opcode; + public InstBrk(ulong opcode) => _opcode = opcode; + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Ccc Ccc => (Ccc)(_opcode & 0x1F); + } + + readonly struct InstBrx + { + private readonly ulong _opcode; + public InstBrx(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Ccc Ccc => (Ccc)(_opcode & 0x1F); + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + public bool Ca => (_opcode & 0x20) != 0; + public bool Lmt => (_opcode & 0x40) != 0; + } + + readonly struct InstCal + { + private readonly ulong _opcode; + public InstCal(ulong opcode) => _opcode = opcode; + public bool Ca => (_opcode & 0x20) != 0; + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + public bool Inc => (_opcode & 0x40) != 0; + } + + readonly struct InstCctl + { + private readonly ulong _opcode; + public InstCctl(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm30 => (int)((_opcode >> 22) & 0x3FFFFFFF); + public bool E => (_opcode & 0x10000000000000) != 0; + public CacheType Cache => (CacheType)((_opcode >> 4) & 0x7); + public CctlOp CctlOp => (CctlOp)(_opcode & 0xF); + } + + readonly struct InstCctll + { + private readonly ulong _opcode; + public InstCctll(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm22 => (int)((_opcode >> 22) & 0x3FFFFF); + public int Cache => (int)((_opcode >> 4) & 0x3); + public CctlOp CctlOp => (CctlOp)(_opcode & 0xF); + } + + readonly struct InstCctlt + { + private readonly ulong _opcode; + public InstCctlt(ulong opcode) => _opcode = opcode; + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int TsIdx13 => (int)((_opcode >> 36) & 0x1FFF); + public CctltOp CctltOp => (CctltOp)(_opcode & 0x3); + } + + readonly struct InstCctltR + { + private readonly ulong _opcode; + public InstCctltR(ulong opcode) => _opcode = opcode; + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public CctltOp CctltOp => (CctltOp)(_opcode & 0x3); + } + + readonly struct InstCont + { + private readonly ulong _opcode; + public InstCont(ulong opcode) => _opcode = opcode; + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Ccc Ccc => (Ccc)(_opcode & 0x1F); + } + + readonly struct InstCset + { + private readonly ulong _opcode; + public InstCset(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public Ccc Ccc => (Ccc)((_opcode >> 8) & 0x1F); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public bool BVal => (_opcode & 0x100000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + } + + readonly struct InstCsetp + { + private readonly ulong _opcode; + public InstCsetp(ulong opcode) => _opcode = opcode; + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public Ccc Ccc => (Ccc)((_opcode >> 8) & 0x1F); + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int DestPredInv => (int)(_opcode & 0x7); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + } + + readonly struct InstCs2r + { + private readonly ulong _opcode; + public InstCs2r(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public SReg SReg => (SReg)((_opcode >> 20) & 0xFF); + } + + readonly struct InstDaddR + { + private readonly ulong _opcode; + public InstDaddR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + } + + readonly struct InstDaddI + { + private readonly ulong _opcode; + public InstDaddI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + } + + readonly struct InstDaddC + { + private readonly ulong _opcode; + public InstDaddC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + } + + readonly struct InstDepbar + { + private readonly ulong _opcode; + public InstDepbar(ulong opcode) => _opcode = opcode; + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool Le => (_opcode & 0x20000000) != 0; + public int Sbid => (int)((_opcode >> 26) & 0x7); + public int PendCnt => (int)((_opcode >> 20) & 0x3F); + public int Imm6 => (int)(_opcode & 0x3F); + } + + readonly struct InstDfmaR + { + private readonly ulong _opcode; + public InstDfmaR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 50) & 0x3); + public bool NegC => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstDfmaI + { + private readonly ulong _opcode; + public InstDfmaI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 50) & 0x3); + public bool NegC => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstDfmaC + { + private readonly ulong _opcode; + public InstDfmaC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 50) & 0x3); + public bool NegC => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstDfmaRc + { + private readonly ulong _opcode; + public InstDfmaRc(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 50) & 0x3); + public bool NegC => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstDmnmxR + { + private readonly ulong _opcode; + public InstDmnmxR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstDmnmxI + { + private readonly ulong _opcode; + public InstDmnmxI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstDmnmxC + { + private readonly ulong _opcode; + public InstDmnmxC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstDmulR + { + private readonly ulong _opcode; + public InstDmulR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + public bool NegA => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstDmulI + { + private readonly ulong _opcode; + public InstDmulI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + public bool NegA => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstDmulC + { + private readonly ulong _opcode; + public InstDmulC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + public bool NegA => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstDsetR + { + private readonly ulong _opcode; + public InstDsetR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsA => (_opcode & 0x40000000000000) != 0; + public bool NegB => (_opcode & 0x20000000000000) != 0; + public bool BVal => (_opcode & 0x10000000000000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool AbsB => (_opcode & 0x100000000000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstDsetI + { + private readonly ulong _opcode; + public InstDsetI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsA => (_opcode & 0x40000000000000) != 0; + public bool NegB => (_opcode & 0x20000000000000) != 0; + public bool BVal => (_opcode & 0x10000000000000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool AbsB => (_opcode & 0x100000000000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstDsetC + { + private readonly ulong _opcode; + public InstDsetC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsA => (_opcode & 0x40000000000000) != 0; + public bool NegB => (_opcode & 0x20000000000000) != 0; + public bool BVal => (_opcode & 0x10000000000000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool AbsB => (_opcode & 0x100000000000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstDsetpR + { + private readonly ulong _opcode; + public InstDsetpR(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool AbsB => (_opcode & 0x100000000000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool AbsA => (_opcode & 0x80) != 0; + public bool NegB => (_opcode & 0x40) != 0; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int DestPredInv => (int)(_opcode & 0x7); + } + + readonly struct InstDsetpI + { + private readonly ulong _opcode; + public InstDsetpI(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool AbsB => (_opcode & 0x100000000000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool AbsA => (_opcode & 0x80) != 0; + public bool NegB => (_opcode & 0x40) != 0; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int DestPredInv => (int)(_opcode & 0x7); + } + + readonly struct InstDsetpC + { + private readonly ulong _opcode; + public InstDsetpC(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool AbsB => (_opcode & 0x100000000000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool AbsA => (_opcode & 0x80) != 0; + public bool NegB => (_opcode & 0x40) != 0; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int DestPredInv => (int)(_opcode & 0x7); + } + + readonly struct InstExit + { + private readonly ulong _opcode; + public InstExit(ulong opcode) => _opcode = opcode; + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Ccc Ccc => (Ccc)(_opcode & 0x1F); + public bool KeepRefCnt => (_opcode & 0x20) != 0; + } + + readonly struct InstF2fR + { + private readonly ulong _opcode; + public InstF2fR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool Ftz => (_opcode & 0x100000000000) != 0; + public DstFmt DstFmt => (DstFmt)((_opcode >> 8) & 0x3); + public DstFmt SrcFmt => (DstFmt)((_opcode >> 10) & 0x3); + public IntegerRound RoundMode => (IntegerRound)((int)((_opcode >> 40) & 0x4) | (int)((_opcode >> 39) & 0x3)); + public bool Sh => (_opcode & 0x20000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstF2fI + { + private readonly ulong _opcode; + public InstF2fI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool Ftz => (_opcode & 0x100000000000) != 0; + public DstFmt DstFmt => (DstFmt)((_opcode >> 8) & 0x3); + public DstFmt SrcFmt => (DstFmt)((_opcode >> 10) & 0x3); + public IntegerRound RoundMode => (IntegerRound)((int)((_opcode >> 40) & 0x4) | (int)((_opcode >> 39) & 0x3)); + public bool Sh => (_opcode & 0x20000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstF2fC + { + private readonly ulong _opcode; + public InstF2fC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool Ftz => (_opcode & 0x100000000000) != 0; + public DstFmt DstFmt => (DstFmt)((_opcode >> 8) & 0x3); + public DstFmt SrcFmt => (DstFmt)((_opcode >> 10) & 0x3); + public IntegerRound RoundMode => (IntegerRound)((int)((_opcode >> 40) & 0x4) | (int)((_opcode >> 39) & 0x3)); + public bool Sh => (_opcode & 0x20000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstF2iR + { + private readonly ulong _opcode; + public InstF2iR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool Ftz => (_opcode & 0x100000000000) != 0; + public bool Sh => (_opcode & 0x20000000000) != 0; + public IDstFmt IDstFmt => (IDstFmt)((int)((_opcode >> 10) & 0x4) | (int)((_opcode >> 8) & 0x3)); + public DstFmt SrcFmt => (DstFmt)((_opcode >> 10) & 0x3); + public RoundMode2 RoundMode => (RoundMode2)((_opcode >> 39) & 0x3); + } + + readonly struct InstF2iI + { + private readonly ulong _opcode; + public InstF2iI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool Ftz => (_opcode & 0x100000000000) != 0; + public bool Sh => (_opcode & 0x20000000000) != 0; + public IDstFmt IDstFmt => (IDstFmt)((int)((_opcode >> 10) & 0x4) | (int)((_opcode >> 8) & 0x3)); + public DstFmt SrcFmt => (DstFmt)((_opcode >> 10) & 0x3); + public RoundMode2 RoundMode => (RoundMode2)((_opcode >> 39) & 0x3); + } + + readonly struct InstF2iC + { + private readonly ulong _opcode; + public InstF2iC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool Ftz => (_opcode & 0x100000000000) != 0; + public bool Sh => (_opcode & 0x20000000000) != 0; + public IDstFmt IDstFmt => (IDstFmt)((int)((_opcode >> 10) & 0x4) | (int)((_opcode >> 8) & 0x3)); + public DstFmt SrcFmt => (DstFmt)((_opcode >> 10) & 0x3); + public RoundMode2 RoundMode => (RoundMode2)((_opcode >> 39) & 0x3); + } + + readonly struct InstFaddR + { + private readonly ulong _opcode; + public InstFaddR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool Ftz => (_opcode & 0x100000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + } + + readonly struct InstFaddI + { + private readonly ulong _opcode; + public InstFaddI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool Ftz => (_opcode & 0x100000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + } + + readonly struct InstFaddC + { + private readonly ulong _opcode; + public InstFaddC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool Ftz => (_opcode & 0x100000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + } + + readonly struct InstFadd32i + { + private readonly ulong _opcode; + public InstFadd32i(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x10000000000000) != 0; + public int Imm32 => (int)(_opcode >> 20); + public bool AbsB => (_opcode & 0x200000000000000) != 0; + public bool NegA => (_opcode & 0x100000000000000) != 0; + public bool Ftz => (_opcode & 0x80000000000000) != 0; + public bool AbsA => (_opcode & 0x40000000000000) != 0; + public bool NegB => (_opcode & 0x20000000000000) != 0; + } + + readonly struct InstFchkR + { + private readonly ulong _opcode; + public InstFchkR(ulong opcode) => _opcode = opcode; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public ChkModeF ChkModeF => (ChkModeF)((_opcode >> 39) & 0x3F); + } + + readonly struct InstFchkI + { + private readonly ulong _opcode; + public InstFchkI(ulong opcode) => _opcode = opcode; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public ChkModeF ChkModeF => (ChkModeF)((_opcode >> 39) & 0x3F); + } + + readonly struct InstFchkC + { + private readonly ulong _opcode; + public InstFchkC(ulong opcode) => _opcode = opcode; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public ChkModeF ChkModeF => (ChkModeF)((_opcode >> 39) & 0x3F); + } + + readonly struct InstFcmpR + { + private readonly ulong _opcode; + public InstFcmpR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public bool Ftz => (_opcode & 0x800000000000) != 0; + } + + readonly struct InstFcmpI + { + private readonly ulong _opcode; + public InstFcmpI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public bool Ftz => (_opcode & 0x800000000000) != 0; + } + + readonly struct InstFcmpC + { + private readonly ulong _opcode; + public InstFcmpC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public bool Ftz => (_opcode & 0x800000000000) != 0; + } + + readonly struct InstFcmpRc + { + private readonly ulong _opcode; + public InstFcmpRc(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public bool Ftz => (_opcode & 0x800000000000) != 0; + } + + readonly struct InstFfmaR + { + private readonly ulong _opcode; + public InstFfmaR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool NegC => (_opcode & 0x2000000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 51) & 0x3); + public Fmz Fmz => (Fmz)((_opcode >> 53) & 0x3); + } + + readonly struct InstFfmaI + { + private readonly ulong _opcode; + public InstFfmaI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool NegC => (_opcode & 0x2000000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 51) & 0x3); + public Fmz Fmz => (Fmz)((_opcode >> 53) & 0x3); + } + + readonly struct InstFfmaC + { + private readonly ulong _opcode; + public InstFfmaC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool NegC => (_opcode & 0x2000000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 51) & 0x3); + public Fmz Fmz => (Fmz)((_opcode >> 53) & 0x3); + } + + readonly struct InstFfmaRc + { + private readonly ulong _opcode; + public InstFfmaRc(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool NegC => (_opcode & 0x2000000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 51) & 0x3); + public Fmz Fmz => (Fmz)((_opcode >> 53) & 0x3); + } + + readonly struct InstFfma32i + { + private readonly ulong _opcode; + public InstFfma32i(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm32 => (int)(_opcode >> 20); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegC => (_opcode & 0x200000000000000) != 0; + public bool NegA => (_opcode & 0x100000000000000) != 0; + public bool Sat => (_opcode & 0x80000000000000) != 0; + public bool WriteCC => (_opcode & 0x10000000000000) != 0; + public Fmz Fmz => (Fmz)((_opcode >> 53) & 0x3); + } + + readonly struct InstFloR + { + private readonly ulong _opcode; + public InstFloR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Signed => (_opcode & 0x1000000000000) != 0; + public bool Sh => (_opcode & 0x20000000000) != 0; + public bool NegB => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstFloI + { + private readonly ulong _opcode; + public InstFloI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Signed => (_opcode & 0x1000000000000) != 0; + public bool Sh => (_opcode & 0x20000000000) != 0; + public bool NegB => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstFloC + { + private readonly ulong _opcode; + public InstFloC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Signed => (_opcode & 0x1000000000000) != 0; + public bool Sh => (_opcode & 0x20000000000) != 0; + public bool NegB => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstFmnmxR + { + private readonly ulong _opcode; + public InstFmnmxR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool Ftz => (_opcode & 0x100000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstFmnmxI + { + private readonly ulong _opcode; + public InstFmnmxI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool Ftz => (_opcode & 0x100000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstFmnmxC + { + private readonly ulong _opcode; + public InstFmnmxC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool Ftz => (_opcode & 0x100000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstFmulR + { + private readonly ulong _opcode; + public InstFmulR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + public Fmz Fmz => (Fmz)((_opcode >> 44) & 0x3); + public MultiplyScale Scale => (MultiplyScale)((_opcode >> 41) & 0x7); + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstFmulI + { + private readonly ulong _opcode; + public InstFmulI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + public Fmz Fmz => (Fmz)((_opcode >> 44) & 0x3); + public MultiplyScale Scale => (MultiplyScale)((_opcode >> 41) & 0x7); + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstFmulC + { + private readonly ulong _opcode; + public InstFmulC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + public Fmz Fmz => (Fmz)((_opcode >> 44) & 0x3); + public MultiplyScale Scale => (MultiplyScale)((_opcode >> 41) & 0x7); + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstFmul32i + { + private readonly ulong _opcode; + public InstFmul32i(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm32 => (int)(_opcode >> 20); + public bool Sat => (_opcode & 0x80000000000000) != 0; + public Fmz Fmz => (Fmz)((_opcode >> 53) & 0x3); + public bool WriteCC => (_opcode & 0x10000000000000) != 0; + } + + readonly struct InstFsetR + { + private readonly ulong _opcode; + public InstFsetR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool NegB => (_opcode & 0x20000000000000) != 0; + public bool AbsA => (_opcode & 0x40000000000000) != 0; + public bool AbsB => (_opcode & 0x100000000000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool Ftz => (_opcode & 0x80000000000000) != 0; + public bool BVal => (_opcode & 0x10000000000000) != 0; + } + + readonly struct InstFsetC + { + private readonly ulong _opcode; + public InstFsetC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool NegB => (_opcode & 0x20000000000000) != 0; + public bool AbsA => (_opcode & 0x40000000000000) != 0; + public bool AbsB => (_opcode & 0x100000000000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool Ftz => (_opcode & 0x80000000000000) != 0; + public bool BVal => (_opcode & 0x10000000000000) != 0; + } + + readonly struct InstFsetI + { + private readonly ulong _opcode; + public InstFsetI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool NegB => (_opcode & 0x20000000000000) != 0; + public bool AbsA => (_opcode & 0x40000000000000) != 0; + public bool AbsB => (_opcode & 0x100000000000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool Ftz => (_opcode & 0x80000000000000) != 0; + public bool BVal => (_opcode & 0x10000000000000) != 0; + } + + readonly struct InstFsetpR + { + private readonly ulong _opcode; + public InstFsetpR(ulong opcode) => _opcode = opcode; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int DestPredInv => (int)(_opcode & 0x7); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool NegB => (_opcode & 0x40) != 0; + public bool AbsA => (_opcode & 0x80) != 0; + public bool AbsB => (_opcode & 0x100000000000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool Ftz => (_opcode & 0x800000000000) != 0; + } + + readonly struct InstFsetpI + { + private readonly ulong _opcode; + public InstFsetpI(ulong opcode) => _opcode = opcode; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int DestPredInv => (int)(_opcode & 0x7); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool NegB => (_opcode & 0x40) != 0; + public bool AbsA => (_opcode & 0x80) != 0; + public bool AbsB => (_opcode & 0x100000000000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool Ftz => (_opcode & 0x800000000000) != 0; + } + + readonly struct InstFsetpC + { + private readonly ulong _opcode; + public InstFsetpC(ulong opcode) => _opcode = opcode; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int DestPredInv => (int)(_opcode & 0x7); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool NegB => (_opcode & 0x40) != 0; + public bool AbsA => (_opcode & 0x80) != 0; + public bool AbsB => (_opcode & 0x100000000000) != 0; + public FComp FComp => (FComp)((_opcode >> 48) & 0xF); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool Ftz => (_opcode & 0x800000000000) != 0; + } + + readonly struct InstFswzadd + { + private readonly ulong _opcode; + public InstFswzadd(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Ftz => (_opcode & 0x100000000000) != 0; + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + public bool Ndv => (_opcode & 0x4000000000) != 0; + public int PnWord => (int)((_opcode >> 28) & 0xFF); + } + + readonly struct InstGetcrsptr + { + private readonly ulong _opcode; + public InstGetcrsptr(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + } + + readonly struct InstGetlmembase + { + private readonly ulong _opcode; + public InstGetlmembase(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + } + + readonly struct InstHadd2R + { + private readonly ulong _opcode; + public InstHadd2R(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public OFmt OFmt => (OFmt)((_opcode >> 49) & 0x3); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public HalfSwizzle BSwizzle => (HalfSwizzle)((_opcode >> 28) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool NegB => (_opcode & 0x80000000) != 0; + public bool AbsA => (_opcode & 0x100000000000) != 0; + public bool AbsB => (_opcode & 0x40000000) != 0; + public bool Sat => (_opcode & 0x100000000) != 0; + public bool Ftz => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstHadd2I + { + private readonly ulong _opcode; + public InstHadd2I(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int BimmH0 => (int)((_opcode >> 20) & 0x3FF); + public int BimmH1 => (int)((_opcode >> 47) & 0x200) | (int)((_opcode >> 30) & 0x1FF); + public OFmt OFmt => (OFmt)((_opcode >> 49) & 0x3); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool AbsA => (_opcode & 0x100000000000) != 0; + public bool Sat => (_opcode & 0x10000000000000) != 0; + public bool Ftz => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstHadd2C + { + private readonly ulong _opcode; + public InstHadd2C(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public OFmt OFmt => (OFmt)((_opcode >> 49) & 0x3); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool NegB => (_opcode & 0x100000000000000) != 0; + public bool AbsA => (_opcode & 0x100000000000) != 0; + public bool AbsB => (_opcode & 0x40000000000000) != 0; + public bool Sat => (_opcode & 0x10000000000000) != 0; + public bool Ftz => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstHadd232i + { + private readonly ulong _opcode; + public InstHadd232i(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm => (int)(_opcode >> 20); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 53) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x100000000000000) != 0; + public bool Sat => (_opcode & 0x10000000000000) != 0; + public bool Ftz => (_opcode & 0x80000000000000) != 0; + } + + readonly struct InstHfma2R + { + private readonly ulong _opcode; + public InstHfma2R(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public OFmt OFmt => (OFmt)((_opcode >> 49) & 0x3); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public HalfSwizzle BSwizzle => (HalfSwizzle)((_opcode >> 28) & 0x3); + public HalfSwizzle CSwizzle => (HalfSwizzle)((_opcode >> 35) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x80000000) != 0; + public bool NegC => (_opcode & 0x40000000) != 0; + public bool Sat => (_opcode & 0x100000000) != 0; + public Fmz Fmz => (Fmz)((_opcode >> 37) & 0x3); + } + + readonly struct InstHfma2I + { + private readonly ulong _opcode; + public InstHfma2I(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int BimmH0 => (int)((_opcode >> 20) & 0x3FF); + public int BimmH1 => (int)((_opcode >> 47) & 0x200) | (int)((_opcode >> 30) & 0x1FF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public OFmt OFmt => (OFmt)((_opcode >> 49) & 0x3); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public HalfSwizzle CSwizzle => (HalfSwizzle)((_opcode >> 53) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegC => (_opcode & 0x8000000000000) != 0; + public bool Sat => (_opcode & 0x10000000000000) != 0; + public Fmz Fmz => (Fmz)((_opcode >> 57) & 0x3); + } + + readonly struct InstHfma2C + { + private readonly ulong _opcode; + public InstHfma2C(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public OFmt OFmt => (OFmt)((_opcode >> 49) & 0x3); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public HalfSwizzle CSwizzle => (HalfSwizzle)((_opcode >> 53) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x100000000000000) != 0; + public bool NegC => (_opcode & 0x8000000000000) != 0; + public bool Sat => (_opcode & 0x10000000000000) != 0; + public Fmz Fmz => (Fmz)((_opcode >> 57) & 0x3); + } + + readonly struct InstHfma2Rc + { + private readonly ulong _opcode; + public InstHfma2Rc(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public OFmt OFmt => (OFmt)((_opcode >> 49) & 0x3); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public HalfSwizzle CSwizzle => (HalfSwizzle)((_opcode >> 53) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x100000000000000) != 0; + public bool NegC => (_opcode & 0x8000000000000) != 0; + public bool Sat => (_opcode & 0x10000000000000) != 0; + public Fmz Fmz => (Fmz)((_opcode >> 57) & 0x3); + } + + readonly struct InstHfma232i + { + private readonly ulong _opcode; + public InstHfma232i(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm => (int)(_opcode >> 20); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegC => (_opcode & 0x8000000000000) != 0; + public Fmz Fmz => (Fmz)((_opcode >> 57) & 0x3); + } + + readonly struct InstHmul2R + { + private readonly ulong _opcode; + public InstHmul2R(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public OFmt OFmt => (OFmt)((_opcode >> 49) & 0x3); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public HalfSwizzle BSwizzle => (HalfSwizzle)((_opcode >> 28) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x80000000) != 0; + public bool AbsA => (_opcode & 0x100000000000) != 0; + public bool AbsB => (_opcode & 0x40000000) != 0; + public bool Sat => (_opcode & 0x100000000) != 0; + public Fmz Fmz => (Fmz)((_opcode >> 39) & 0x3); + } + + readonly struct InstHmul2I + { + private readonly ulong _opcode; + public InstHmul2I(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int BimmH0 => (int)((_opcode >> 20) & 0x3FF); + public int BimmH1 => (int)((_opcode >> 47) & 0x200) | (int)((_opcode >> 30) & 0x1FF); + public OFmt OFmt => (OFmt)((_opcode >> 49) & 0x3); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool AbsA => (_opcode & 0x100000000000) != 0; + public bool Sat => (_opcode & 0x10000000000000) != 0; + public Fmz Fmz => (Fmz)((_opcode >> 39) & 0x3); + } + + readonly struct InstHmul2C + { + private readonly ulong _opcode; + public InstHmul2C(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public OFmt OFmt => (OFmt)((_opcode >> 49) & 0x3); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool AbsA => (_opcode & 0x100000000000) != 0; + public bool AbsB => (_opcode & 0x40000000000000) != 0; + public bool Sat => (_opcode & 0x10000000000000) != 0; + public Fmz Fmz => (Fmz)((_opcode >> 39) & 0x3); + } + + readonly struct InstHmul232i + { + private readonly ulong _opcode; + public InstHmul232i(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm32 => (int)(_opcode >> 20); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 53) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool Sat => (_opcode & 0x10000000000000) != 0; + public Fmz Fmz => (Fmz)((_opcode >> 55) & 0x3); + } + + readonly struct InstHset2R + { + private readonly ulong _opcode; + public InstHset2R(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public HalfSwizzle BSwizzle => (HalfSwizzle)((_opcode >> 28) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool AbsA => (_opcode & 0x100000000000) != 0; + public bool NegB => (_opcode & 0x80000000) != 0; + public bool AbsB => (_opcode & 0x40000000) != 0; + public bool Bval => (_opcode & 0x2000000000000) != 0; + public FComp Cmp => (FComp)((_opcode >> 35) & 0xF); + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public bool Ftz => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstHset2I + { + private readonly ulong _opcode; + public InstHset2I(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int BimmH0 => (int)((_opcode >> 20) & 0x3FF); + public int BimmH1 => (int)((_opcode >> 47) & 0x200) | (int)((_opcode >> 30) & 0x1FF); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool AbsA => (_opcode & 0x100000000000) != 0; + public bool Bval => (_opcode & 0x20000000000000) != 0; + public FComp Cmp => (FComp)((_opcode >> 49) & 0xF); + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public bool Ftz => (_opcode & 0x40000000000000) != 0; + } + + readonly struct InstHset2C + { + private readonly ulong _opcode; + public InstHset2C(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool AbsA => (_opcode & 0x100000000000) != 0; + public bool NegB => (_opcode & 0x100000000000000) != 0; + public bool Bval => (_opcode & 0x20000000000000) != 0; + public FComp Cmp => (FComp)((_opcode >> 49) & 0xF); + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public bool Ftz => (_opcode & 0x40000000000000) != 0; + } + + readonly struct InstHsetp2R + { + private readonly ulong _opcode; + public InstHsetp2R(ulong opcode) => _opcode = opcode; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int DestPredInv => (int)(_opcode & 0x7); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool NegB => (_opcode & 0x80000000) != 0; + public bool AbsA => (_opcode & 0x100000000000) != 0; + public bool AbsB => (_opcode & 0x40000000) != 0; + public FComp FComp2 => (FComp)((_opcode >> 35) & 0xF); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool Ftz => (_opcode & 0x40) != 0; + public bool HAnd => (_opcode & 0x2000000000000) != 0; + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + public HalfSwizzle BSwizzle => (HalfSwizzle)((_opcode >> 28) & 0x3); + } + + readonly struct InstHsetp2I + { + private readonly ulong _opcode; + public InstHsetp2I(ulong opcode) => _opcode = opcode; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int DestPredInv => (int)(_opcode & 0x7); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int BimmH0 => (int)((_opcode >> 20) & 0x3FF); + public int BimmH1 => (int)((_opcode >> 47) & 0x200) | (int)((_opcode >> 30) & 0x1FF); + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool AbsA => (_opcode & 0x100000000000) != 0; + public FComp FComp => (FComp)((_opcode >> 49) & 0xF); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool Ftz => (_opcode & 0x40) != 0; + public bool HAnd => (_opcode & 0x20000000000000) != 0; + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + } + + readonly struct InstHsetp2C + { + private readonly ulong _opcode; + public InstHsetp2C(ulong opcode) => _opcode = opcode; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int DestPredInv => (int)(_opcode & 0x7); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegA => (_opcode & 0x80000000000) != 0; + public bool NegB => (_opcode & 0x100000000000000) != 0; + public bool AbsA => (_opcode & 0x100000000000) != 0; + public bool AbsB => (_opcode & 0x40000000000000) != 0; + public FComp FComp => (FComp)((_opcode >> 49) & 0xF); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool Ftz => (_opcode & 0x40) != 0; + public bool HAnd => (_opcode & 0x20000000000000) != 0; + public HalfSwizzle ASwizzle => (HalfSwizzle)((_opcode >> 47) & 0x3); + } + + readonly struct InstI2fR + { + private readonly ulong _opcode; + public InstI2fR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public ByteSel ByteSel => (ByteSel)((_opcode >> 41) & 0x3); + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + public ISrcFmt ISrcFmt => (ISrcFmt)((int)((_opcode >> 11) & 0x4) | (int)((_opcode >> 10) & 0x3)); + public DstFmt DstFmt => (DstFmt)((_opcode >> 8) & 0x3); + } + + readonly struct InstI2fI + { + private readonly ulong _opcode; + public InstI2fI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public ByteSel ByteSel => (ByteSel)((_opcode >> 41) & 0x3); + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + public ISrcFmt ISrcFmt => (ISrcFmt)((int)((_opcode >> 11) & 0x4) | (int)((_opcode >> 10) & 0x3)); + public DstFmt DstFmt => (DstFmt)((_opcode >> 8) & 0x3); + } + + readonly struct InstI2fC + { + private readonly ulong _opcode; + public InstI2fC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public ByteSel ByteSel => (ByteSel)((_opcode >> 41) & 0x3); + public RoundMode RoundMode => (RoundMode)((_opcode >> 39) & 0x3); + public ISrcFmt ISrcFmt => (ISrcFmt)((int)((_opcode >> 11) & 0x4) | (int)((_opcode >> 10) & 0x3)); + public DstFmt DstFmt => (DstFmt)((_opcode >> 8) & 0x3); + } + + readonly struct InstI2iR + { + private readonly ulong _opcode; + public InstI2iR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public ByteSel ByteSel => (ByteSel)((_opcode >> 41) & 0x3); + public ISrcDstFmt IDstFmt => (ISrcDstFmt)((int)((_opcode >> 10) & 0x4) | (int)((_opcode >> 8) & 0x3)); + public ISrcDstFmt ISrcFmt => (ISrcDstFmt)((int)((_opcode >> 11) & 0x4) | (int)((_opcode >> 10) & 0x3)); + } + + readonly struct InstI2iI + { + private readonly ulong _opcode; + public InstI2iI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public ByteSel ByteSel => (ByteSel)((_opcode >> 41) & 0x3); + public ISrcDstFmt IDstFmt => (ISrcDstFmt)((int)((_opcode >> 10) & 0x4) | (int)((_opcode >> 8) & 0x3)); + public ISrcDstFmt ISrcFmt => (ISrcDstFmt)((int)((_opcode >> 11) & 0x4) | (int)((_opcode >> 10) & 0x3)); + } + + readonly struct InstI2iC + { + private readonly ulong _opcode; + public InstI2iC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public ByteSel ByteSel => (ByteSel)((_opcode >> 41) & 0x3); + public ISrcDstFmt IDstFmt => (ISrcDstFmt)((int)((_opcode >> 10) & 0x4) | (int)((_opcode >> 8) & 0x3)); + public ISrcDstFmt ISrcFmt => (ISrcDstFmt)((int)((_opcode >> 11) & 0x4) | (int)((_opcode >> 10) & 0x3)); + } + + readonly struct InstIaddR + { + private readonly ulong _opcode; + public InstIaddR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + public AvgMode AvgMode => (AvgMode)((_opcode >> 48) & 0x3); + public bool X => (_opcode & 0x80000000000) != 0; + } + + readonly struct InstIaddI + { + private readonly ulong _opcode; + public InstIaddI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + public AvgMode AvgMode => (AvgMode)((_opcode >> 48) & 0x3); + public bool X => (_opcode & 0x80000000000) != 0; + } + + readonly struct InstIaddC + { + private readonly ulong _opcode; + public InstIaddC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + public AvgMode AvgMode => (AvgMode)((_opcode >> 48) & 0x3); + public bool X => (_opcode & 0x80000000000) != 0; + } + + readonly struct InstIadd32i + { + private readonly ulong _opcode; + public InstIadd32i(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm32 => (int)(_opcode >> 20); + public AvgMode AvgMode => (AvgMode)((_opcode >> 55) & 0x3); + public bool Sat => (_opcode & 0x40000000000000) != 0; + public bool WriteCC => (_opcode & 0x10000000000000) != 0; + public bool X => (_opcode & 0x20000000000000) != 0; + } + + readonly struct InstIadd3R + { + private readonly ulong _opcode; + public InstIadd3R(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool NegA => (_opcode & 0x8000000000000) != 0; + public bool NegB => (_opcode & 0x4000000000000) != 0; + public bool NegC => (_opcode & 0x2000000000000) != 0; + public bool X => (_opcode & 0x1000000000000) != 0; + public Lrs Lrs => (Lrs)((_opcode >> 37) & 0x3); + public HalfSelect Apart => (HalfSelect)((_opcode >> 35) & 0x3); + public HalfSelect Bpart => (HalfSelect)((_opcode >> 33) & 0x3); + public HalfSelect Cpart => (HalfSelect)((_opcode >> 31) & 0x3); + } + + readonly struct InstIadd3I + { + private readonly ulong _opcode; + public InstIadd3I(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool NegA => (_opcode & 0x8000000000000) != 0; + public bool NegB => (_opcode & 0x4000000000000) != 0; + public bool NegC => (_opcode & 0x2000000000000) != 0; + public bool X => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstIadd3C + { + private readonly ulong _opcode; + public InstIadd3C(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool NegA => (_opcode & 0x8000000000000) != 0; + public bool NegB => (_opcode & 0x4000000000000) != 0; + public bool NegC => (_opcode & 0x2000000000000) != 0; + public bool X => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstIcmpR + { + private readonly ulong _opcode; + public InstIcmpR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public IComp IComp => (IComp)((_opcode >> 49) & 0x7); + public bool Signed => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstIcmpI + { + private readonly ulong _opcode; + public InstIcmpI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public IComp IComp => (IComp)((_opcode >> 49) & 0x7); + public bool Signed => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstIcmpC + { + private readonly ulong _opcode; + public InstIcmpC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public IComp IComp => (IComp)((_opcode >> 49) & 0x7); + public bool Signed => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstIcmpRc + { + private readonly ulong _opcode; + public InstIcmpRc(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public IComp IComp => (IComp)((_opcode >> 49) & 0x7); + public bool Signed => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstIde + { + private readonly ulong _opcode; + public InstIde(ulong opcode) => _opcode = opcode; + public int Imm16 => (int)((_opcode >> 20) & 0xFFFF); + public bool Di => (_opcode & 0x20) != 0; + } + + readonly struct InstIdpR + { + private readonly ulong _opcode; + public InstIdpR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool IsHi => (_opcode & 0x4000000000000) != 0; + public bool SrcASign => (_opcode & 0x2000000000000) != 0; + public bool IsDp => (_opcode & 0x1000000000000) != 0; + public bool SrcBSign => (_opcode & 0x800000000000) != 0; + } + + readonly struct InstIdpC + { + private readonly ulong _opcode; + public InstIdpC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool IsHi => (_opcode & 0x4000000000000) != 0; + public bool SrcASign => (_opcode & 0x2000000000000) != 0; + public bool IsDp => (_opcode & 0x1000000000000) != 0; + public bool SrcBSign => (_opcode & 0x800000000000) != 0; + } + + readonly struct InstImadR + { + private readonly ulong _opcode; + public InstImadR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Hilo => (_opcode & 0x40000000000000) != 0; + public bool BSigned => (_opcode & 0x20000000000000) != 0; + public AvgMode AvgMode => (AvgMode)((_opcode >> 51) & 0x3); + public bool Sat => (_opcode & 0x4000000000000) != 0; + public bool X => (_opcode & 0x2000000000000) != 0; + public bool ASigned => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstImadI + { + private readonly ulong _opcode; + public InstImadI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Hilo => (_opcode & 0x40000000000000) != 0; + public bool BSigned => (_opcode & 0x20000000000000) != 0; + public AvgMode AvgMode => (AvgMode)((_opcode >> 51) & 0x3); + public bool Sat => (_opcode & 0x4000000000000) != 0; + public bool X => (_opcode & 0x2000000000000) != 0; + public bool ASigned => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstImadC + { + private readonly ulong _opcode; + public InstImadC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Hilo => (_opcode & 0x40000000000000) != 0; + public bool BSigned => (_opcode & 0x20000000000000) != 0; + public AvgMode AvgMode => (AvgMode)((_opcode >> 51) & 0x3); + public bool Sat => (_opcode & 0x4000000000000) != 0; + public bool X => (_opcode & 0x2000000000000) != 0; + public bool ASigned => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstImadRc + { + private readonly ulong _opcode; + public InstImadRc(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Hilo => (_opcode & 0x40000000000000) != 0; + public bool BSigned => (_opcode & 0x20000000000000) != 0; + public AvgMode AvgMode => (AvgMode)((_opcode >> 51) & 0x3); + public bool Sat => (_opcode & 0x4000000000000) != 0; + public bool X => (_opcode & 0x2000000000000) != 0; + public bool ASigned => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstImad32i + { + private readonly ulong _opcode; + public InstImad32i(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm32 => (int)(_opcode >> 20); + public bool BSigned => (_opcode & 0x200000000000000) != 0; + public AvgMode AvgMode => (AvgMode)((_opcode >> 55) & 0x3); + public bool ASigned => (_opcode & 0x40000000000000) != 0; + public bool WriteCC => (_opcode & 0x10000000000000) != 0; + public bool Hilo => (_opcode & 0x20000000000000) != 0; + } + + readonly struct InstImadspR + { + private readonly ulong _opcode; + public InstImadspR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public ImadspASelect ASelect => (ImadspASelect)((_opcode >> 48) & 0x7); + public ImadspBSelect BSelect => (ImadspBSelect)((_opcode >> 53) & 0x3); + public ImadspASelect CSelect => (ImadspASelect)((int)((_opcode >> 50) & 0x6) | (int)((_opcode >> 48) & 0x1)); + } + + readonly struct InstImadspI + { + private readonly ulong _opcode; + public InstImadspI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public ImadspASelect ASelect => (ImadspASelect)((_opcode >> 48) & 0x7); + public ImadspBSelect BSelect => (ImadspBSelect)((_opcode >> 53) & 0x3); + public ImadspASelect CSelect => (ImadspASelect)((int)((_opcode >> 50) & 0x6) | (int)((_opcode >> 48) & 0x1)); + } + + readonly struct InstImadspC + { + private readonly ulong _opcode; + public InstImadspC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public ImadspASelect ASelect => (ImadspASelect)((_opcode >> 48) & 0x7); + public ImadspBSelect BSelect => (ImadspBSelect)((_opcode >> 53) & 0x3); + public ImadspASelect CSelect => (ImadspASelect)((int)((_opcode >> 50) & 0x6) | (int)((_opcode >> 48) & 0x1)); + } + + readonly struct InstImadspRc + { + private readonly ulong _opcode; + public InstImadspRc(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public ImadspASelect ASelect => (ImadspASelect)((_opcode >> 48) & 0x7); + public ImadspBSelect BSelect => (ImadspBSelect)((_opcode >> 53) & 0x3); + public ImadspASelect CSelect => (ImadspASelect)((int)((_opcode >> 50) & 0x6) | (int)((_opcode >> 48) & 0x1)); + } + + readonly struct InstImnmxR + { + private readonly ulong _opcode; + public InstImnmxR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Signed => (_opcode & 0x1000000000000) != 0; + public XMode XMode => (XMode)((_opcode >> 43) & 0x3); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstImnmxI + { + private readonly ulong _opcode; + public InstImnmxI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Signed => (_opcode & 0x1000000000000) != 0; + public XMode XMode => (XMode)((_opcode >> 43) & 0x3); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstImnmxC + { + private readonly ulong _opcode; + public InstImnmxC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Signed => (_opcode & 0x1000000000000) != 0; + public XMode XMode => (XMode)((_opcode >> 43) & 0x3); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstImulR + { + private readonly ulong _opcode; + public InstImulR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool ASigned => (_opcode & 0x10000000000) != 0; + public bool BSigned => (_opcode & 0x20000000000) != 0; + public bool Hilo => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstImulI + { + private readonly ulong _opcode; + public InstImulI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool ASigned => (_opcode & 0x10000000000) != 0; + public bool BSigned => (_opcode & 0x20000000000) != 0; + public bool Hilo => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstImulC + { + private readonly ulong _opcode; + public InstImulC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool ASigned => (_opcode & 0x10000000000) != 0; + public bool BSigned => (_opcode & 0x20000000000) != 0; + public bool Hilo => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstImul32i + { + private readonly ulong _opcode; + public InstImul32i(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm32 => (int)(_opcode >> 20); + public bool ASigned => (_opcode & 0x40000000000000) != 0; + public bool BSigned => (_opcode & 0x80000000000000) != 0; + public bool Hilo => (_opcode & 0x20000000000000) != 0; + public bool WriteCC => (_opcode & 0x10000000000000) != 0; + } + + readonly struct InstIpa + { + private readonly ulong _opcode; + public InstIpa(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public IpaOp IpaOp => (IpaOp)((_opcode >> 54) & 0x3); + public int Msi => (int)((_opcode >> 52) & 0x3); + public bool Sat => (_opcode & 0x8000000000000) != 0; + public bool Idx => (_opcode & 0x4000000000) != 0; + public int Imm10 => (int)((_opcode >> 28) & 0x3FF); + public int SrcPred => (int)((_opcode >> 47) & 0x7); + public bool SrcPredInv => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstIsberd + { + private readonly ulong _opcode; + public InstIsberd(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public AlSize AlSize => (AlSize)((_opcode >> 47) & 0x3); + public IBase IBase => (IBase)((_opcode >> 33) & 0x3); + public bool O => (_opcode & 0x100000000) != 0; + public bool P => (_opcode & 0x80000000) != 0; + } + + readonly struct InstIscaddR + { + private readonly ulong _opcode; + public InstIscaddR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public int Imm5 => (int)((_opcode >> 39) & 0x1F); + public AvgMode AvgMode => (AvgMode)((_opcode >> 48) & 0x3); + } + + readonly struct InstIscaddI + { + private readonly ulong _opcode; + public InstIscaddI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public int Imm5 => (int)((_opcode >> 39) & 0x1F); + public AvgMode AvgMode => (AvgMode)((_opcode >> 48) & 0x3); + } + + readonly struct InstIscaddC + { + private readonly ulong _opcode; + public InstIscaddC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public int Imm5 => (int)((_opcode >> 39) & 0x1F); + public AvgMode AvgMode => (AvgMode)((_opcode >> 48) & 0x3); + } + + readonly struct InstIscadd32i + { + private readonly ulong _opcode; + public InstIscadd32i(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm32 => (int)(_opcode >> 20); + public bool WriteCC => (_opcode & 0x10000000000000) != 0; + public int Imm5 => (int)((_opcode >> 53) & 0x1F); + } + + readonly struct InstIsetR + { + private readonly ulong _opcode; + public InstIsetR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public IComp IComp => (IComp)((_opcode >> 49) & 0x7); + public bool Signed => (_opcode & 0x1000000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public bool BVal => (_opcode & 0x100000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool X => (_opcode & 0x80000000000) != 0; + } + + readonly struct InstIsetI + { + private readonly ulong _opcode; + public InstIsetI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public IComp IComp => (IComp)((_opcode >> 49) & 0x7); + public bool Signed => (_opcode & 0x1000000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public bool BVal => (_opcode & 0x100000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool X => (_opcode & 0x80000000000) != 0; + } + + readonly struct InstIsetC + { + private readonly ulong _opcode; + public InstIsetC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public IComp IComp => (IComp)((_opcode >> 49) & 0x7); + public bool Signed => (_opcode & 0x1000000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public bool BVal => (_opcode & 0x100000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool X => (_opcode & 0x80000000000) != 0; + } + + readonly struct InstIsetpR + { + private readonly ulong _opcode; + public InstIsetpR(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public IComp IComp => (IComp)((_opcode >> 49) & 0x7); + public bool Signed => (_opcode & 0x1000000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool X => (_opcode & 0x80000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int DestPredInv => (int)(_opcode & 0x7); + } + + readonly struct InstIsetpI + { + private readonly ulong _opcode; + public InstIsetpI(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public IComp IComp => (IComp)((_opcode >> 49) & 0x7); + public bool Signed => (_opcode & 0x1000000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool X => (_opcode & 0x80000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int DestPredInv => (int)(_opcode & 0x7); + } + + readonly struct InstIsetpC + { + private readonly ulong _opcode; + public InstIsetpC(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public IComp IComp => (IComp)((_opcode >> 49) & 0x7); + public bool Signed => (_opcode & 0x1000000000000) != 0; + public BoolOp Bop => (BoolOp)((_opcode >> 45) & 0x3); + public bool X => (_opcode & 0x80000000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int DestPredInv => (int)(_opcode & 0x7); + } + + readonly struct InstJcal + { + private readonly ulong _opcode; + public InstJcal(ulong opcode) => _opcode = opcode; + public int Imm32 => (int)(_opcode >> 20); + public bool Ca => (_opcode & 0x20) != 0; + public bool Inc => (_opcode & 0x40) != 0; + } + + readonly struct InstJmp + { + private readonly ulong _opcode; + public InstJmp(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Ccc Ccc => (Ccc)(_opcode & 0x1F); + public bool Ca => (_opcode & 0x20) != 0; + public int Imm32 => (int)(_opcode >> 20); + public bool Lmt => (_opcode & 0x40) != 0; + public bool U => (_opcode & 0x80) != 0; + } + + readonly struct InstJmx + { + private readonly ulong _opcode; + public InstJmx(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Ccc Ccc => (Ccc)(_opcode & 0x1F); + public bool Ca => (_opcode & 0x20) != 0; + public int Imm32 => (int)(_opcode >> 20); + public bool Lmt => (_opcode & 0x40) != 0; + } + + readonly struct InstKil + { + private readonly ulong _opcode; + public InstKil(ulong opcode) => _opcode = opcode; + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Ccc Ccc => (Ccc)(_opcode & 0x1F); + } + + readonly struct InstLd + { + private readonly ulong _opcode; + public InstLd(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int SrcPred => (int)((_opcode >> 58) & 0x7); + public CacheOpLd CacheOp => (CacheOpLd)((_opcode >> 56) & 0x3); + public LsSize LsSize => (LsSize)((_opcode >> 53) & 0x7); + public bool E => (_opcode & 0x10000000000000) != 0; + public int Imm32 => (int)(_opcode >> 20); + } + + readonly struct InstLdc + { + private readonly ulong _opcode; + public InstLdc(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public LsSize2 LsSize => (LsSize2)((_opcode >> 48) & 0x7); + public AddressMode AddressMode => (AddressMode)((_opcode >> 44) & 0x3); + public int CbufSlot => (int)((_opcode >> 36) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0xFFFF); + } + + readonly struct InstLdg + { + private readonly ulong _opcode; + public InstLdg(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public LsSize LsSize => (LsSize)((_opcode >> 48) & 0x7); + public CacheOpLd CacheOp => (CacheOpLd)((_opcode >> 46) & 0x3); + public bool E => (_opcode & 0x200000000000) != 0; + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + } + + readonly struct InstLdl + { + private readonly ulong _opcode; + public InstLdl(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public LsSize2 LsSize => (LsSize2)((_opcode >> 48) & 0x7); + public CacheOp2 CacheOp => (CacheOp2)((_opcode >> 44) & 0x3); + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + } + + readonly struct InstLds + { + private readonly ulong _opcode; + public InstLds(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public LsSize2 LsSize => (LsSize2)((_opcode >> 48) & 0x7); + public bool U => (_opcode & 0x100000000000) != 0; + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + } + + readonly struct InstLeaR + { + private readonly ulong _opcode; + public InstLeaR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool X => (_opcode & 0x400000000000) != 0; + public bool NegA => (_opcode & 0x200000000000) != 0; + public int ImmU5 => (int)((_opcode >> 39) & 0x1F); + public int DestPred => (int)((_opcode >> 48) & 0x7); + } + + readonly struct InstLeaI + { + private readonly ulong _opcode; + public InstLeaI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool X => (_opcode & 0x400000000000) != 0; + public bool NegA => (_opcode & 0x200000000000) != 0; + public int ImmU5 => (int)((_opcode >> 39) & 0x1F); + public int DestPred => (int)((_opcode >> 48) & 0x7); + } + + readonly struct InstLeaC + { + private readonly ulong _opcode; + public InstLeaC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool X => (_opcode & 0x400000000000) != 0; + public bool NegA => (_opcode & 0x200000000000) != 0; + public int ImmU5 => (int)((_opcode >> 39) & 0x1F); + public int DestPred => (int)((_opcode >> 48) & 0x7); + } + + readonly struct InstLeaHiR + { + private readonly ulong _opcode; + public InstLeaHiR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool X => (_opcode & 0x4000000000) != 0; + public bool NegA => (_opcode & 0x2000000000) != 0; + public int ImmU5 => (int)((_opcode >> 28) & 0x1F); + public int DestPred => (int)((_opcode >> 48) & 0x7); + } + + readonly struct InstLeaHiC + { + private readonly ulong _opcode; + public InstLeaHiC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool X => (_opcode & 0x200000000000000) != 0; + public bool NegA => (_opcode & 0x100000000000000) != 0; + public int ImmU5 => (int)((_opcode >> 51) & 0x1F); + public int DestPred => (int)((_opcode >> 48) & 0x7); + } + + readonly struct InstLepc + { +#pragma warning disable IDE0052 // Remove unread private member + private readonly ulong _opcode; +#pragma warning restore IDE0052 + public InstLepc(ulong opcode) => _opcode = opcode; + } + + readonly struct InstLongjmp + { + private readonly ulong _opcode; + public InstLongjmp(ulong opcode) => _opcode = opcode; + public Ccc Ccc => (Ccc)(_opcode & 0x1F); + } + + readonly struct InstLopR + { + private readonly ulong _opcode; + public InstLopR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public int DestPred => (int)((_opcode >> 48) & 0x7); + public PredicateOp PredicateOp => (PredicateOp)((_opcode >> 44) & 0x3); + public bool X => (_opcode & 0x80000000000) != 0; + public LogicOp Lop => (LogicOp)((_opcode >> 41) & 0x3); + public bool NegA => (_opcode & 0x8000000000) != 0; + public bool NegB => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstLopI + { + private readonly ulong _opcode; + public InstLopI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public int DestPred => (int)((_opcode >> 48) & 0x7); + public PredicateOp PredicateOp => (PredicateOp)((_opcode >> 44) & 0x3); + public bool X => (_opcode & 0x80000000000) != 0; + public LogicOp LogicOp => (LogicOp)((_opcode >> 41) & 0x3); + public bool NegA => (_opcode & 0x8000000000) != 0; + public bool NegB => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstLopC + { + private readonly ulong _opcode; + public InstLopC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public int DestPred => (int)((_opcode >> 48) & 0x7); + public PredicateOp PredicateOp => (PredicateOp)((_opcode >> 44) & 0x3); + public bool X => (_opcode & 0x80000000000) != 0; + public LogicOp LogicOp => (LogicOp)((_opcode >> 41) & 0x3); + public bool NegA => (_opcode & 0x8000000000) != 0; + public bool NegB => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstLop3R + { + private readonly ulong _opcode; + public InstLop3R(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public int DestPred => (int)((_opcode >> 48) & 0x7); + public PredicateOp PredicateOp => (PredicateOp)((_opcode >> 36) & 0x3); + public bool X => (_opcode & 0x4000000000) != 0; + public int Imm => (int)((_opcode >> 28) & 0xFF); + } + + readonly struct InstLop3I + { + private readonly ulong _opcode; + public InstLop3I(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool X => (_opcode & 0x200000000000000) != 0; + public int Imm => (int)((_opcode >> 48) & 0xFF); + } + + readonly struct InstLop3C + { + private readonly ulong _opcode; + public InstLop3C(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool X => (_opcode & 0x100000000000000) != 0; + public int Imm => (int)((_opcode >> 48) & 0xFF); + } + + readonly struct InstLop32i + { + private readonly ulong _opcode; + public InstLop32i(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x10000000000000) != 0; + public int Imm32 => (int)(_opcode >> 20); + public bool X => (_opcode & 0x200000000000000) != 0; + public LogicOp LogicOp => (LogicOp)((_opcode >> 53) & 0x3); + public bool NegA => (_opcode & 0x80000000000000) != 0; + public bool NegB => (_opcode & 0x100000000000000) != 0; + } + + readonly struct InstMembar + { + private readonly ulong _opcode; + public InstMembar(ulong opcode) => _opcode = opcode; + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Membar Membar => (Membar)((_opcode >> 8) & 0x3); + public Ivall Ivall => (Ivall)(_opcode & 0x3); + } + + readonly struct InstMovR + { + private readonly ulong _opcode; + public InstMovR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int QuadMask => (int)((_opcode >> 39) & 0xF); + } + + readonly struct InstMovI + { + private readonly ulong _opcode; + public InstMovI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int QuadMask => (int)((_opcode >> 39) & 0xF); + } + + readonly struct InstMovC + { + private readonly ulong _opcode; + public InstMovC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int QuadMask => (int)((_opcode >> 39) & 0xF); + } + + readonly struct InstMov32i + { + private readonly ulong _opcode; + public InstMov32i(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int Imm32 => (int)(_opcode >> 20); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int QuadMask => (int)((_opcode >> 12) & 0xF); + } + + readonly struct InstMufu + { + private readonly ulong _opcode; + public InstMufu(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public MufuOp MufuOp => (MufuOp)((_opcode >> 20) & 0xF); + public bool AbsA => (_opcode & 0x400000000000) != 0; + public bool NegA => (_opcode & 0x1000000000000) != 0; + public bool Sat => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstNop + { + private readonly ulong _opcode; + public InstNop(ulong opcode) => _opcode = opcode; + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm16 => (int)((_opcode >> 20) & 0xFFFF); + public bool Trig => (_opcode & 0x2000) != 0; + public Ccc Ccc => (Ccc)((_opcode >> 8) & 0x1F); + } + + readonly struct InstOutR + { + private readonly ulong _opcode; + public InstOutR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public OutType OutType => (OutType)((_opcode >> 39) & 0x3); + } + + readonly struct InstOutI + { + private readonly ulong _opcode; + public InstOutI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public OutType OutType => (OutType)((_opcode >> 39) & 0x3); + } + + readonly struct InstOutC + { + private readonly ulong _opcode; + public InstOutC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public OutType OutType => (OutType)((_opcode >> 39) & 0x3); + } + + readonly struct InstP2rR + { + private readonly ulong _opcode; + public InstP2rR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public ByteSel ByteSel => (ByteSel)((_opcode >> 41) & 0x3); + public bool Ccpr => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstP2rI + { + private readonly ulong _opcode; + public InstP2rI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public ByteSel ByteSel => (ByteSel)((_opcode >> 41) & 0x3); + public bool Ccpr => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstP2rC + { + private readonly ulong _opcode; + public InstP2rC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public ByteSel ByteSel => (ByteSel)((_opcode >> 41) & 0x3); + public bool Ccpr => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstPbk + { + private readonly ulong _opcode; + public InstPbk(ulong opcode) => _opcode = opcode; + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + public bool Ca => (_opcode & 0x20) != 0; + } + + readonly struct InstPcnt + { + private readonly ulong _opcode; + public InstPcnt(ulong opcode) => _opcode = opcode; + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + public bool Ca => (_opcode & 0x20) != 0; + } + + readonly struct InstPexit + { + private readonly ulong _opcode; + public InstPexit(ulong opcode) => _opcode = opcode; + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + } + + readonly struct InstPixld + { + private readonly ulong _opcode; + public InstPixld(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int DestPred => (int)((_opcode >> 45) & 0x7); + public PixMode PixMode => (PixMode)((_opcode >> 31) & 0x7); + public int Imm8 => (int)((_opcode >> 20) & 0xFF); + } + + readonly struct InstPlongjmp + { + private readonly ulong _opcode; + public InstPlongjmp(ulong opcode) => _opcode = opcode; + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + public bool Ca => (_opcode & 0x20) != 0; + } + + readonly struct InstPopcR + { + private readonly ulong _opcode; + public InstPopcR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegB => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstPopcI + { + private readonly ulong _opcode; + public InstPopcI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegB => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstPopcC + { + private readonly ulong _opcode; + public InstPopcC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool NegB => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstPret + { + private readonly ulong _opcode; + public InstPret(ulong opcode) => _opcode = opcode; + public bool Ca => (_opcode & 0x20) != 0; + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + public bool Inc => (_opcode & 0x40) != 0; + } + + readonly struct InstPrmtR + { + private readonly ulong _opcode; + public InstPrmtR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public PMode PMode => (PMode)((_opcode >> 48) & 0xF); + } + + readonly struct InstPrmtI + { + private readonly ulong _opcode; + public InstPrmtI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public PMode PMode => (PMode)((_opcode >> 48) & 0xF); + } + + readonly struct InstPrmtC + { + private readonly ulong _opcode; + public InstPrmtC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public PMode PMode => (PMode)((_opcode >> 48) & 0xF); + } + + readonly struct InstPrmtRc + { + private readonly ulong _opcode; + public InstPrmtRc(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public PMode PMode => (PMode)((_opcode >> 48) & 0xF); + } + + readonly struct InstPset + { + private readonly ulong _opcode; + public InstPset(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public int Src2Pred => (int)((_opcode >> 12) & 0x7); + public bool Src2PredInv => (_opcode & 0x8000) != 0; + public int Src1Pred => (int)((_opcode >> 29) & 0x7); + public bool Src1PredInv => (_opcode & 0x100000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public BoolOp BoolOpAB => (BoolOp)((_opcode >> 24) & 0x3); + public BoolOp BoolOpC => (BoolOp)((_opcode >> 45) & 0x3); + public bool BVal => (_opcode & 0x100000000000) != 0; + } + + readonly struct InstPsetp + { + private readonly ulong _opcode; + public InstPsetp(ulong opcode) => _opcode = opcode; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int DestPredInv => (int)(_opcode & 0x7); + public int Src2Pred => (int)((_opcode >> 12) & 0x7); + public bool Src2PredInv => (_opcode & 0x8000) != 0; + public int Src1Pred => (int)((_opcode >> 29) & 0x7); + public bool Src1PredInv => (_opcode & 0x100000000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public BoolOp BoolOpAB => (BoolOp)((_opcode >> 24) & 0x3); + public BoolOp BoolOpC => (BoolOp)((_opcode >> 45) & 0x3); + } + + readonly struct InstR2b + { + private readonly ulong _opcode; + public InstR2b(ulong opcode) => _opcode = opcode; + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public BarMode Mode => (BarMode)((_opcode >> 32) & 0x3); + public int Name => (int)((_opcode >> 28) & 0xF); + } + + readonly struct InstR2pR + { + private readonly ulong _opcode; + public InstR2pR(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public ByteSel ByteSel => (ByteSel)((_opcode >> 41) & 0x3); + public bool Ccpr => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstR2pI + { + private readonly ulong _opcode; + public InstR2pI(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public ByteSel ByteSel => (ByteSel)((_opcode >> 41) & 0x3); + public bool Ccpr => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstR2pC + { + private readonly ulong _opcode; + public InstR2pC(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public ByteSel ByteSel => (ByteSel)((_opcode >> 41) & 0x3); + public bool Ccpr => (_opcode & 0x10000000000) != 0; + } + + readonly struct InstRam + { +#pragma warning disable IDE0052 // Remove unread private member + private readonly ulong _opcode; +#pragma warning restore IDE0052 + public InstRam(ulong opcode) => _opcode = opcode; + } + + readonly struct InstRed + { + private readonly ulong _opcode; + public InstRed(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)(_opcode & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm20 => (int)((_opcode >> 28) & 0xFFFFF); + public AtomSize RedSize => (AtomSize)((_opcode >> 20) & 0x7); + public RedOp RedOp => (RedOp)((_opcode >> 23) & 0x7); + public bool E => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstRet + { + private readonly ulong _opcode; + public InstRet(ulong opcode) => _opcode = opcode; + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Ccc Ccc => (Ccc)(_opcode & 0x1F); + } + + readonly struct InstRroR + { + private readonly ulong _opcode; + public InstRroR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool RroOp => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstRroI + { + private readonly ulong _opcode; + public InstRroI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool RroOp => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstRroC + { + private readonly ulong _opcode; + public InstRroC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool AbsB => (_opcode & 0x2000000000000) != 0; + public bool NegB => (_opcode & 0x200000000000) != 0; + public bool RroOp => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstRtt + { +#pragma warning disable IDE0052 // Remove unread private member + private readonly ulong _opcode; +#pragma warning restore IDE0052 + public InstRtt(ulong opcode) => _opcode = opcode; + } + + readonly struct InstS2r + { + private readonly ulong _opcode; + public InstS2r(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public SReg SReg => (SReg)((_opcode >> 20) & 0xFF); + } + + readonly struct InstSam + { +#pragma warning disable IDE0052 // Remove unread private member + private readonly ulong _opcode; +#pragma warning restore IDE0052 + public InstSam(ulong opcode) => _opcode = opcode; + } + + readonly struct InstSelR + { + private readonly ulong _opcode; + public InstSelR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstSelI + { + private readonly ulong _opcode; + public InstSelI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstSelC + { + private readonly ulong _opcode; + public InstSelC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + } + + readonly struct InstSetcrsptr + { + private readonly ulong _opcode; + public InstSetcrsptr(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + } + + readonly struct InstSetlmembase + { + private readonly ulong _opcode; + public InstSetlmembase(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + } + + readonly struct InstShfLR + { + private readonly ulong _opcode; + public InstShfLR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool M => (_opcode & 0x4000000000000) != 0; + public XModeShf XModeShf => (XModeShf)((_opcode >> 48) & 0x3); + public MaxShift MaxShift => (MaxShift)((_opcode >> 37) & 0x3); + } + + readonly struct InstShfRR + { + private readonly ulong _opcode; + public InstShfRR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool M => (_opcode & 0x4000000000000) != 0; + public XModeShf XModeShf => (XModeShf)((_opcode >> 48) & 0x3); + public MaxShift MaxShift => (MaxShift)((_opcode >> 37) & 0x3); + } + + readonly struct InstShfLI + { + private readonly ulong _opcode; + public InstShfLI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool M => (_opcode & 0x4000000000000) != 0; + public XModeShf XModeShf => (XModeShf)((_opcode >> 48) & 0x3); + public MaxShift MaxShift => (MaxShift)((_opcode >> 37) & 0x3); + public int Imm6 => (int)((_opcode >> 20) & 0x3F); + } + + readonly struct InstShfRI + { + private readonly ulong _opcode; + public InstShfRI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool M => (_opcode & 0x4000000000000) != 0; + public XModeShf XModeShf => (XModeShf)((_opcode >> 48) & 0x3); + public MaxShift MaxShift => (MaxShift)((_opcode >> 37) & 0x3); + public int Imm6 => (int)((_opcode >> 20) & 0x3F); + } + + readonly struct InstShfl + { + private readonly ulong _opcode; + public InstShfl(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int SrcBImm => (int)((_opcode >> 20) & 0x1F); + public int SrcCImm => (int)((_opcode >> 34) & 0x1FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public ShflMode ShflMode => (ShflMode)((_opcode >> 30) & 0x3); + public bool CFixShfl => (_opcode & 0x20000000) != 0; + public bool BFixShfl => (_opcode & 0x10000000) != 0; + public int DestPred => (int)((_opcode >> 48) & 0x7); + } + + readonly struct InstShlR + { + private readonly ulong _opcode; + public InstShlR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool X => (_opcode & 0x80000000000) != 0; + public bool M => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstShlI + { + private readonly ulong _opcode; + public InstShlI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool X => (_opcode & 0x80000000000) != 0; + public bool M => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstShlC + { + private readonly ulong _opcode; + public InstShlC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool X => (_opcode & 0x80000000000) != 0; + public bool M => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstShrR + { + private readonly ulong _opcode; + public InstShrR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Signed => (_opcode & 0x1000000000000) != 0; + public XMode XMode => (XMode)((_opcode >> 43) & 0x3); + public bool Brev => (_opcode & 0x10000000000) != 0; + public bool M => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstShrI + { + private readonly ulong _opcode; + public InstShrI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Imm20 => (int)((_opcode >> 37) & 0x80000) | (int)((_opcode >> 20) & 0x7FFFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Signed => (_opcode & 0x1000000000000) != 0; + public XMode XMode => (XMode)((_opcode >> 43) & 0x3); + public bool Brev => (_opcode & 0x10000000000) != 0; + public bool M => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstShrC + { + private readonly ulong _opcode; + public InstShrC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Signed => (_opcode & 0x1000000000000) != 0; + public XMode XMode => (XMode)((_opcode >> 43) & 0x3); + public bool Brev => (_opcode & 0x10000000000) != 0; + public bool M => (_opcode & 0x8000000000) != 0; + } + + readonly struct InstSsy + { + private readonly ulong _opcode; + public InstSsy(ulong opcode) => _opcode = opcode; + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + public bool Ca => (_opcode & 0x20) != 0; + } + + readonly struct InstSt + { + private readonly ulong _opcode; + public InstSt(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int SrcPred => (int)((_opcode >> 58) & 0x7); + public CacheOpSt CacheOp => (CacheOpSt)((_opcode >> 56) & 0x3); + public LsSize LsSize => (LsSize)((_opcode >> 53) & 0x7); + public bool E => (_opcode & 0x10000000000000) != 0; + public int Imm32 => (int)(_opcode >> 20); + } + + readonly struct InstStg + { + private readonly ulong _opcode; + public InstStg(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public LsSize2 LsSize => (LsSize2)((_opcode >> 48) & 0x7); + public CacheOpSt CacheOp => (CacheOpSt)((_opcode >> 46) & 0x3); + public bool E => (_opcode & 0x200000000000) != 0; + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + } + + readonly struct InstStl + { + private readonly ulong _opcode; + public InstStl(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public LsSize2 LsSize => (LsSize2)((_opcode >> 48) & 0x7); + public CacheOpSt CacheOp => (CacheOpSt)((_opcode >> 44) & 0x3); + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + } + + readonly struct InstStp + { + private readonly ulong _opcode; + public InstStp(ulong opcode) => _opcode = opcode; + public bool Wait => (_opcode & 0x80000000) != 0; + public int Imm8 => (int)((_opcode >> 20) & 0xFF); + } + + readonly struct InstSts + { + private readonly ulong _opcode; + public InstSts(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public LsSize2 LsSize => (LsSize2)((_opcode >> 48) & 0x7); + public int Imm24 => (int)((_opcode >> 20) & 0xFFFFFF); + } + + readonly struct InstSuatomB + { + private readonly ulong _opcode; + public InstSuatomB(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public SuatomSize Size => (SuatomSize)((_opcode >> 36) & 0x7); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public SuatomOp Op => (SuatomOp)((_opcode >> 29) & 0xF); + public bool Ba => (_opcode & 0x10000000) != 0; + } + + readonly struct InstSuatom + { + private readonly ulong _opcode; + public InstSuatom(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public SuatomSize Size => (SuatomSize)((_opcode >> 51) & 0x7); + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public SuatomOp Op => (SuatomOp)((_opcode >> 29) & 0xF); + public bool Ba => (_opcode & 0x10000000) != 0; + } + + readonly struct InstSuatomB2 + { + private readonly ulong _opcode; + public InstSuatomB2(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int DestPred => (int)((_opcode >> 51) & 0x7); + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public SuatomSize Size => (SuatomSize)((_opcode >> 36) & 0x7); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public SuatomOp Op => (SuatomOp)((_opcode >> 29) & 0xF); + public bool Ba => (_opcode & 0x10000000) != 0; + } + + readonly struct InstSuatomCasB + { + private readonly ulong _opcode; + public InstSuatomCasB(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public SuatomSize Size => (SuatomSize)((_opcode >> 36) & 0x7); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public int DestPred => (int)((_opcode >> 30) & 0x7); + public bool Ba => (_opcode & 0x10000000) != 0; + } + + readonly struct InstSuatomCas + { + private readonly ulong _opcode; + public InstSuatomCas(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public SuatomSize Size => (SuatomSize)((_opcode >> 51) & 0x7); + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public int DestPred => (int)((_opcode >> 30) & 0x7); + public bool Ba => (_opcode & 0x10000000) != 0; + } + + readonly struct InstSuldDB + { + private readonly ulong _opcode; + public InstSuldDB(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public int DestPred2 => (int)((_opcode >> 30) & 0x7); + public CacheOpLd CacheOp => (CacheOpLd)((_opcode >> 24) & 0x3); + public bool Ba => (_opcode & 0x800000) != 0; + public SuSize Size => (SuSize)((_opcode >> 20) & 0x7); + } + + readonly struct InstSuldD + { + private readonly ulong _opcode; + public InstSuldD(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public int DestPred2 => (int)((_opcode >> 30) & 0x7); + public CacheOpLd CacheOp => (CacheOpLd)((_opcode >> 24) & 0x3); + public bool Ba => (_opcode & 0x800000) != 0; + public SuSize Size => (SuSize)((_opcode >> 20) & 0x7); + } + + readonly struct InstSuldB + { + private readonly ulong _opcode; + public InstSuldB(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public int DestPred2 => (int)((_opcode >> 30) & 0x7); + public CacheOpLd CacheOp => (CacheOpLd)((_opcode >> 24) & 0x3); + public SuRgba Rgba => (SuRgba)((_opcode >> 20) & 0xF); + } + + readonly struct InstSuld + { + private readonly ulong _opcode; + public InstSuld(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public int DestPred2 => (int)((_opcode >> 30) & 0x7); + public CacheOpLd CacheOp => (CacheOpLd)((_opcode >> 24) & 0x3); + public SuRgba Rgba => (SuRgba)((_opcode >> 20) & 0xF); + } + + readonly struct InstSuredB + { + private readonly ulong _opcode; + public InstSuredB(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public RedOp Op => (RedOp)((_opcode >> 24) & 0x7); + public bool Ba => (_opcode & 0x800000) != 0; + public SuatomSize Size => (SuatomSize)((_opcode >> 20) & 0x7); + } + + readonly struct InstSured + { + private readonly ulong _opcode; + public InstSured(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public RedOp Op => (RedOp)((_opcode >> 24) & 0x7); + public bool Ba => (_opcode & 0x800000) != 0; + public SuatomSize Size => (SuatomSize)((_opcode >> 20) & 0x7); + } + + readonly struct InstSustDB + { + private readonly ulong _opcode; + public InstSustDB(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public CacheOpSt CacheOp => (CacheOpSt)((_opcode >> 24) & 0x3); + public bool Ba => (_opcode & 0x800000) != 0; + public SuSize Size => (SuSize)((_opcode >> 20) & 0x7); + } + + readonly struct InstSustD + { + private readonly ulong _opcode; + public InstSustD(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public CacheOpSt CacheOp => (CacheOpSt)((_opcode >> 24) & 0x3); + public bool Ba => (_opcode & 0x800000) != 0; + public SuSize Size => (SuSize)((_opcode >> 20) & 0x7); + } + + readonly struct InstSustB + { + private readonly ulong _opcode; + public InstSustB(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public CacheOpSt CacheOp => (CacheOpSt)((_opcode >> 24) & 0x3); + public SuRgba Rgba => (SuRgba)((_opcode >> 20) & 0xF); + } + + readonly struct InstSust + { + private readonly ulong _opcode; + public InstSust(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Clamp Clamp => (Clamp)((_opcode >> 49) & 0x3); + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public SuDim Dim => (SuDim)((_opcode >> 33) & 0x7); + public CacheOpSt CacheOp => (CacheOpSt)((_opcode >> 24) & 0x3); + public SuRgba Rgba => (SuRgba)((_opcode >> 20) & 0xF); + } + + readonly struct InstSync + { + private readonly ulong _opcode; + public InstSync(ulong opcode) => _opcode = opcode; + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public Ccc Ccc => (Ccc)(_opcode & 0x1F); + } + + readonly struct InstTex + { + private readonly ulong _opcode; + public InstTex(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool Lc => (_opcode & 0x400000000000000) != 0; + public int DestPred => (int)((_opcode >> 51) & 0x7); + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public Lod Lod => (Lod)((_opcode >> 55) & 0x7); + public bool Aoffi => (_opcode & 0x40000000000000) != 0; + public bool Dc => (_opcode & 0x4000000000000) != 0; + public bool Ndv => (_opcode & 0x800000000) != 0; + public TexDim Dim => (TexDim)((_opcode >> 28) & 0x7); + public int WMask => (int)((_opcode >> 31) & 0xF); + public bool Nodep => (_opcode & 0x2000000000000) != 0; + } + + readonly struct InstTexB + { + private readonly ulong _opcode; + public InstTexB(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool Lcb => (_opcode & 0x10000000000) != 0; + public int DestPred => (int)((_opcode >> 51) & 0x7); + public Lod Lodb => (Lod)((_opcode >> 37) & 0x7); + public bool Aoffib => (_opcode & 0x1000000000) != 0; + public bool Dc => (_opcode & 0x4000000000000) != 0; + public bool Ndv => (_opcode & 0x800000000) != 0; + public TexDim Dim => (TexDim)((_opcode >> 28) & 0x7); + public int WMask => (int)((_opcode >> 31) & 0xF); + public bool Nodep => (_opcode & 0x2000000000000) != 0; + } + + readonly struct InstTexs + { + private readonly ulong _opcode; + public InstTexs(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public TexsTarget Target => (TexsTarget)((_opcode >> 53) & 0xF); + public int WMask => (int)((_opcode >> 50) & 0x7); + public bool Nodep => (_opcode & 0x2000000000000) != 0; + public int Dest2 => (int)((_opcode >> 28) & 0xFF); + } + + readonly struct InstTld + { + private readonly ulong _opcode; + public InstTld(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public int WMask => (int)((_opcode >> 31) & 0xF); + public bool Lod => (_opcode & 0x80000000000000) != 0; + public bool Toff => (_opcode & 0x800000000) != 0; + public bool Ms => (_opcode & 0x4000000000000) != 0; + public bool Cl => (_opcode & 0x40000000000000) != 0; + public bool Nodep => (_opcode & 0x2000000000000) != 0; + public int DestPred => (int)((_opcode >> 51) & 0x7); + public TexDim Dim => (TexDim)((_opcode >> 28) & 0x7); + } + + readonly struct InstTldB + { + private readonly ulong _opcode; + public InstTldB(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int WMask => (int)((_opcode >> 31) & 0xF); + public bool Lod => (_opcode & 0x80000000000000) != 0; + public bool Toff => (_opcode & 0x800000000) != 0; + public bool Ms => (_opcode & 0x4000000000000) != 0; + public bool Cl => (_opcode & 0x40000000000000) != 0; + public bool Nodep => (_opcode & 0x2000000000000) != 0; + public int DestPred => (int)((_opcode >> 51) & 0x7); + public TexDim Dim => (TexDim)((_opcode >> 28) & 0x7); + } + + readonly struct InstTlds + { + private readonly ulong _opcode; + public InstTlds(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public TldsTarget Target => (TldsTarget)((_opcode >> 53) & 0xF); + public int WMask => (int)((_opcode >> 50) & 0x7); + public bool Nodep => (_opcode & 0x2000000000000) != 0; + public int Dest2 => (int)((_opcode >> 28) & 0xFF); + } + + readonly struct InstTld4 + { + private readonly ulong _opcode; + public InstTld4(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool Lc => (_opcode & 0x400000000000000) != 0; + public int DestPred => (int)((_opcode >> 51) & 0x7); + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public TexComp TexComp => (TexComp)((_opcode >> 56) & 0x3); + public TexOffset Toff => (TexOffset)((_opcode >> 54) & 0x3); + public bool Dc => (_opcode & 0x4000000000000) != 0; + public bool Ndv => (_opcode & 0x800000000) != 0; + public TexDim Dim => (TexDim)((_opcode >> 28) & 0x7); + public int WMask => (int)((_opcode >> 31) & 0xF); + public bool Nodep => (_opcode & 0x2000000000000) != 0; + } + + readonly struct InstTld4B + { + private readonly ulong _opcode; + public InstTld4B(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool Lc => (_opcode & 0x10000000000) != 0; + public int DestPred => (int)((_opcode >> 51) & 0x7); + public TexComp TexComp => (TexComp)((_opcode >> 38) & 0x3); + public TexOffset Toff => (TexOffset)((_opcode >> 36) & 0x3); + public bool Dc => (_opcode & 0x4000000000000) != 0; + public bool Ndv => (_opcode & 0x800000000) != 0; + public TexDim Dim => (TexDim)((_opcode >> 28) & 0x7); + public int WMask => (int)((_opcode >> 31) & 0xF); + public bool Nodep => (_opcode & 0x2000000000000) != 0; + } + + readonly struct InstTld4s + { + private readonly ulong _opcode; + public InstTld4s(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public TexComp TexComp => (TexComp)((_opcode >> 52) & 0x3); + public bool Aoffi => (_opcode & 0x8000000000000) != 0; + public bool Dc => (_opcode & 0x4000000000000) != 0; + public bool Nodep => (_opcode & 0x2000000000000) != 0; + public int Dest2 => (int)((_opcode >> 28) & 0xFF); + } + + readonly struct InstTmml + { + private readonly ulong _opcode; + public InstTmml(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool Nodep => (_opcode & 0x2000000000000) != 0; + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public bool Ndv => (_opcode & 0x800000000) != 0; + public int WMask => (int)((_opcode >> 31) & 0xF); + public TexDim Dim => (TexDim)((_opcode >> 28) & 0x7); + } + + readonly struct InstTmmlB + { + private readonly ulong _opcode; + public InstTmmlB(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool Nodep => (_opcode & 0x2000000000000) != 0; + public bool Ndv => (_opcode & 0x800000000) != 0; + public int WMask => (int)((_opcode >> 31) & 0xF); + public TexDim Dim => (TexDim)((_opcode >> 28) & 0x7); + } + + readonly struct InstTxa + { + private readonly ulong _opcode; + public InstTxa(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool Nodep => (_opcode & 0x2000000000000) != 0; + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public bool Ndv => (_opcode & 0x800000000) != 0; + public int WMask => (int)((_opcode >> 31) & 0xF); + } + + readonly struct InstTxd + { + private readonly ulong _opcode; + public InstTxd(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int DestPred => (int)((_opcode >> 51) & 0x7); + public bool Lc => (_opcode & 0x4000000000000) != 0; + public bool Nodep => (_opcode & 0x2000000000000) != 0; + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public bool Toff => (_opcode & 0x800000000) != 0; + public int WMask => (int)((_opcode >> 31) & 0xF); + public TexDim Dim => (TexDim)((_opcode >> 28) & 0x7); + } + + readonly struct InstTxdB + { + private readonly ulong _opcode; + public InstTxdB(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int DestPred => (int)((_opcode >> 51) & 0x7); + public bool Lc => (_opcode & 0x4000000000000) != 0; + public bool Nodep => (_opcode & 0x2000000000000) != 0; + public bool Toff => (_opcode & 0x800000000) != 0; + public int WMask => (int)((_opcode >> 31) & 0xF); + public TexDim Dim => (TexDim)((_opcode >> 28) & 0x7); + } + + readonly struct InstTxq + { + private readonly ulong _opcode; + public InstTxq(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool Nodep => (_opcode & 0x2000000000000) != 0; + public int TidB => (int)((_opcode >> 36) & 0x1FFF); + public int WMask => (int)((_opcode >> 31) & 0xF); + public TexQuery TexQuery => (TexQuery)((_opcode >> 22) & 0x3F); + } + + readonly struct InstTxqB + { + private readonly ulong _opcode; + public InstTxqB(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool Nodep => (_opcode & 0x2000000000000) != 0; + public int WMask => (int)((_opcode >> 31) & 0xF); + public TexQuery TexQuery => (TexQuery)((_opcode >> 22) & 0x3F); + } + + readonly struct InstVabsdiff + { + private readonly ulong _opcode; + public InstVabsdiff(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool DFormat => (_opcode & 0x40000000000000) != 0; + public VectorSelect ASelect => (VectorSelect)((int)((_opcode >> 45) & 0x8) | (int)((_opcode >> 36) & 0x7)); + public VectorSelect BSelect => (VectorSelect)((int)((_opcode >> 46) & 0x8) | (int)((_opcode >> 28) & 0x7)); + public bool Sat => (_opcode & 0x80000000000000) != 0; + public VideoOp VideoOp => (VideoOp)((_opcode >> 51) & 0x7); + public bool BVideo => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstVabsdiff4 + { + private readonly ulong _opcode; + public InstVabsdiff4(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public VideoRed VRed => (VideoRed)((_opcode >> 53) & 0x3); + public LaneMask4 LaneMask4 => (LaneMask4)((int)((_opcode >> 49) & 0xC) | (int)((_opcode >> 36) & 0x3)); + public bool Sat => (_opcode & 0x4000000000000) != 0; + public bool SrcBFmt => (_opcode & 0x2000000000000) != 0; + public bool SrcAFmt => (_opcode & 0x1000000000000) != 0; + public bool DFormat => (_opcode & 0x4000000000) != 0; + public ASelect4 Asel4 => (ASelect4)((_opcode >> 32) & 0xF); + public BSelect4 Bsel4 => (BSelect4)((_opcode >> 28) & 0xF); + } + + readonly struct InstVadd + { + private readonly ulong _opcode; + public InstVadd(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm16 => (int)((_opcode >> 20) & 0xFFFF); + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public AvgMode AvgMode => (AvgMode)((_opcode >> 56) & 0x3); + public bool DFormat => (_opcode & 0x40000000000000) != 0; + public VectorSelect ASelect => (VectorSelect)((int)((_opcode >> 45) & 0x8) | (int)((_opcode >> 36) & 0x7)); + public VectorSelect BSelect => (VectorSelect)((int)((_opcode >> 46) & 0x8) | (int)((_opcode >> 28) & 0x7)); + public bool Sat => (_opcode & 0x80000000000000) != 0; + public VideoOp VideoOp => (VideoOp)((_opcode >> 51) & 0x7); + public bool BVideo => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstVmad + { + private readonly ulong _opcode; + public InstVmad(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm16 => (int)((_opcode >> 20) & 0xFFFF); + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public VectorSelect ASelect => (VectorSelect)((int)((_opcode >> 45) & 0x8) | (int)((_opcode >> 36) & 0x7)); + public VectorSelect BSelect => (VectorSelect)((int)((_opcode >> 46) & 0x8) | (int)((_opcode >> 28) & 0x7)); + public bool Sat => (_opcode & 0x80000000000000) != 0; + public AvgMode AvgMode => (AvgMode)((_opcode >> 53) & 0x3); + public VideoScale VideoScale => (VideoScale)((_opcode >> 51) & 0x3); + public bool BVideo => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstVmnmx + { + private readonly ulong _opcode; + public InstVmnmx(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm16 => (int)((_opcode >> 20) & 0xFFFF); + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool DFormat => (_opcode & 0x40000000000000) != 0; + public VectorSelect ASelect => (VectorSelect)((int)((_opcode >> 45) & 0x8) | (int)((_opcode >> 36) & 0x7)); + public VectorSelect BSelect => (VectorSelect)((int)((_opcode >> 46) & 0x8) | (int)((_opcode >> 28) & 0x7)); + public bool Sat => (_opcode & 0x80000000000000) != 0; + public VideoOp VideoOp => (VideoOp)((_opcode >> 51) & 0x7); + public bool Mn => (_opcode & 0x100000000000000) != 0; + public bool BVideo => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstVote + { + private readonly ulong _opcode; + public InstVote(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public VoteMode VoteMode => (VoteMode)((_opcode >> 48) & 0x3); + public int VpDest => (int)((_opcode >> 45) & 0x7); + } + + readonly struct InstVotevtg + { + private readonly ulong _opcode; + public InstVotevtg(ulong opcode) => _opcode = opcode; + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public VoteMode VoteMode => (VoteMode)((_opcode >> 48) & 0x3); + public int Imm28 => (int)((_opcode >> 20) & 0xFFFFFFF); + } + + readonly struct InstVset + { + private readonly ulong _opcode; + public InstVset(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public IComp VComp => (IComp)((_opcode >> 54) & 0x7); + public VectorSelect ASelect => (VectorSelect)((int)((_opcode >> 45) & 0x8) | (int)((_opcode >> 36) & 0x7)); + public VectorSelect BSelect => (VectorSelect)((int)((_opcode >> 46) & 0x8) | (int)((_opcode >> 28) & 0x7)); + public VideoOp VideoOp => (VideoOp)((_opcode >> 51) & 0x7); + public bool BVideo => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstVsetp + { + private readonly ulong _opcode; + public InstVsetp(ulong opcode) => _opcode = opcode; + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public int Imm16 => (int)((_opcode >> 20) & 0xFFFF); + public VectorSelect ASelect => (VectorSelect)((int)((_opcode >> 45) & 0x8) | (int)((_opcode >> 36) & 0x7)); + public VectorSelect BSelect => (VectorSelect)((int)((_opcode >> 46) & 0x8) | (int)((_opcode >> 28) & 0x7)); + public IComp VComp => (IComp)((int)((_opcode >> 45) & 0x4) | (int)((_opcode >> 43) & 0x3)); + public BoolOp BoolOp => (BoolOp)((_opcode >> 45) & 0x3); + public int SrcPred => (int)((_opcode >> 39) & 0x7); + public bool SrcPredInv => (_opcode & 0x40000000000) != 0; + public int DestPred => (int)((_opcode >> 3) & 0x7); + public int DestPredInv => (int)(_opcode & 0x7); + public bool BVideo => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstVshl + { + private readonly ulong _opcode; + public InstVshl(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Mv => (_opcode & 0x2000000000000) != 0; + public bool DFormat => (_opcode & 0x40000000000000) != 0; + public VectorSelect ASelect => (VectorSelect)((int)((_opcode >> 45) & 0x8) | (int)((_opcode >> 36) & 0x7)); + public VectorSelect BSelect => (VectorSelect)((_opcode >> 28) & 0x7); + public bool Sat => (_opcode & 0x80000000000000) != 0; + public VideoOp VideoOp => (VideoOp)((_opcode >> 51) & 0x7); + public bool BVideo => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstVshr + { + private readonly ulong _opcode; + public InstVshr(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Mv => (_opcode & 0x2000000000000) != 0; + public bool DFormat => (_opcode & 0x40000000000000) != 0; + public VectorSelect ASelect => (VectorSelect)((int)((_opcode >> 45) & 0x8) | (int)((_opcode >> 36) & 0x7)); + public VectorSelect BSelect => (VectorSelect)((_opcode >> 28) & 0x7); + public bool Sat => (_opcode & 0x80000000000000) != 0; + public VideoOp VideoOp => (VideoOp)((_opcode >> 51) & 0x7); + public bool BVideo => (_opcode & 0x4000000000000) != 0; + } + + readonly struct InstXmadR + { + private readonly ulong _opcode; + public InstXmadR(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcB => (int)((_opcode >> 20) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool HiloA => (_opcode & 0x20000000000000) != 0; + public XmadCop XmadCop => (XmadCop)((_opcode >> 50) & 0x7); + public bool BSigned => (_opcode & 0x2000000000000) != 0; + public bool ASigned => (_opcode & 0x1000000000000) != 0; + public bool X => (_opcode & 0x4000000000) != 0; + public bool Mrg => (_opcode & 0x2000000000) != 0; + public bool Psl => (_opcode & 0x1000000000) != 0; + public bool HiloB => (_opcode & 0x800000000) != 0; + } + + readonly struct InstXmadI + { + private readonly ulong _opcode; + public InstXmadI(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public int Imm16 => (int)((_opcode >> 20) & 0xFFFF); + public bool HiloA => (_opcode & 0x20000000000000) != 0; + public XmadCop XmadCop => (XmadCop)((_opcode >> 50) & 0x7); + public bool BSigned => (_opcode & 0x2000000000000) != 0; + public bool ASigned => (_opcode & 0x1000000000000) != 0; + public bool X => (_opcode & 0x4000000000) != 0; + public bool Mrg => (_opcode & 0x2000000000) != 0; + public bool Psl => (_opcode & 0x1000000000) != 0; + } + + readonly struct InstXmadC + { + private readonly ulong _opcode; + public InstXmadC(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool Mrg => (_opcode & 0x100000000000000) != 0; + public bool Psl => (_opcode & 0x80000000000000) != 0; + public bool X => (_opcode & 0x40000000000000) != 0; + public bool HiloA => (_opcode & 0x20000000000000) != 0; + public bool HiloB => (_opcode & 0x10000000000000) != 0; + public XmadCop2 XmadCop => (XmadCop2)((_opcode >> 50) & 0x3); + public bool BSigned => (_opcode & 0x2000000000000) != 0; + public bool ASigned => (_opcode & 0x1000000000000) != 0; + } + + readonly struct InstXmadRc + { + private readonly ulong _opcode; + public InstXmadRc(ulong opcode) => _opcode = opcode; + public int Dest => (int)(_opcode & 0xFF); + public int SrcA => (int)((_opcode >> 8) & 0xFF); + public int SrcC => (int)((_opcode >> 39) & 0xFF); + public int CbufSlot => (int)((_opcode >> 34) & 0x1F); + public int CbufOffset => (int)((_opcode >> 20) & 0x3FFF); + public int Pred => (int)((_opcode >> 16) & 0x7); + public bool PredInv => (_opcode & 0x80000) != 0; + public bool WriteCC => (_opcode & 0x800000000000) != 0; + public bool X => (_opcode & 0x40000000000000) != 0; + public bool HiloA => (_opcode & 0x20000000000000) != 0; + public bool HiloB => (_opcode & 0x10000000000000) != 0; + public XmadCop2 XmadCop => (XmadCop2)((_opcode >> 50) & 0x3); + public bool BSigned => (_opcode & 0x2000000000000) != 0; + public bool ASigned => (_opcode & 0x1000000000000) != 0; + } +} diff --git a/src/Ryujinx.Graphics.Shader/Decoders/InstName.cs b/src/Ryujinx.Graphics.Shader/Decoders/InstName.cs new file mode 100644 index 00000000..04ad9391 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Decoders/InstName.cs @@ -0,0 +1,188 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum InstName : byte + { + Invalid = 0, + + Al2p, + Ald, + Ast, + Atom, + AtomCas, + Atoms, + AtomsCas, + B2r, + Bar, + Bfe, + Bfi, + Bpt, + Bra, + Brk, + Brx, + Cal, + Cctl, + Cctll, + Cctlt, + Cont, + Cset, + Csetp, + Cs2r, + Dadd, + Depbar, + Dfma, + Dmnmx, + Dmul, + Dset, + Dsetp, + Exit, + F2f, + F2i, + Fadd, + Fadd32i, + Fchk, + Fcmp, + Ffma, + Ffma32i, + Flo, + Fmnmx, + Fmul, + Fmul32i, + Fset, + Fsetp, + Fswzadd, + Getcrsptr, + Getlmembase, + Hadd2, + Hadd232i, + Hfma2, + Hmul2, + Hmul232i, + Hset2, + Hsetp2, + I2f, + I2i, + Iadd, + Iadd32i, + Iadd3, + Icmp, + Ide, + Idp, + Imad, + Imad32i, + Imadsp, + Imnmx, + Imul, + Imul32i, + Ipa, + Isberd, + Iscadd, + Iscadd32i, + Iset, + Isetp, + Jcal, + Jmp, + Jmx, + Kil, + Ld, + Ldc, + Ldg, + Ldl, + Lds, + Lea, + LeaHi, + Lepc, + Longjmp, + Lop, + Lop3, + Lop32i, + Membar, + Mov, + Mov32i, + Mufu, + Nop, + Out, + P2r, + Pbk, + Pcnt, + Pexit, + Pixld, + Plongjmp, + Popc, + Pret, + Prmt, + Pset, + Psetp, + R2b, + R2p, + Ram, + Red, + Ret, + Rro, + Rtt, + S2r, + Sam, + Sel, + Setcrsptr, + Setlmembase, + Shf, + Shf_2, + Shf_3, + Shf_4, + Shfl, + Shl, + Shr, + Ssy, + St, + Stg, + Stl, + Stp, + Sts, + SuatomB, + Suatom, + SuatomB2, + SuatomCasB, + SuatomCas, + SuldDB, + SuldD, + SuldB, + Suld, + SuredB, + Sured, + SustDB, + SustD, + SustB, + Sust, + Sync, + Tex, + TexB, + Texs, + TexsF16, + Tld, + TldB, + Tlds, + TldsF16, + Tld4, + Tld4B, + Tld4s, + Tld4sF16, + Tmml, + TmmlB, + Txa, + Txd, + TxdB, + Txq, + TxqB, + Vabsdiff, + Vabsdiff4, + Vadd, + Vmad, + Vmnmx, + Vote, + Votevtg, + Vset, + Vsetp, + Vshl, + Vshr, + Xmad, + } +} diff --git a/src/Ryujinx.Graphics.Shader/Decoders/InstOp.cs b/src/Ryujinx.Graphics.Shader/Decoders/InstOp.cs new file mode 100644 index 00000000..045257dc --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Decoders/InstOp.cs @@ -0,0 +1,27 @@ +using Ryujinx.Graphics.Shader.Instructions; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + readonly struct InstOp + { + public readonly ulong Address; + public readonly ulong RawOpCode; + public readonly InstEmitter Emitter; + public readonly InstProps Props; + public readonly InstName Name; + + public InstOp(ulong address, ulong rawOpCode, InstName name, InstEmitter emitter, InstProps props) + { + Address = address; + RawOpCode = rawOpCode; + Name = name; + Emitter = emitter; + Props = props; + } + + public ulong GetAbsoluteAddress() + { + return (ulong)((long)Address + (((int)(RawOpCode >> 20) << 8) >> 8) + 8); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Decoders/InstProps.cs b/src/Ryujinx.Graphics.Shader/Decoders/InstProps.cs new file mode 100644 index 00000000..14cdcd06 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Decoders/InstProps.cs @@ -0,0 +1,31 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + [Flags] + enum InstProps : ushort + { + None = 0, + Rd = 1 << 0, + Rd2 = 1 << 1, + Ra = 1 << 2, + Rb = 1 << 3, + Rb2 = 1 << 4, + Ib = 1 << 5, + Rc = 1 << 6, + + Pd = 1 << 7, + LPd = 2 << 7, + SPd = 3 << 7, + TPd = 4 << 7, + VPd = 5 << 7, + PdMask = 7 << 7, + + Pdn = 1 << 10, + Ps = 1 << 11, + Tex = 1 << 12, + TexB = 1 << 13, + Bra = 1 << 14, + NoPred = 1 << 15, + } +} diff --git a/src/Ryujinx.Graphics.Shader/Decoders/InstTable.cs b/src/Ryujinx.Graphics.Shader/Decoders/InstTable.cs new file mode 100644 index 00000000..35367b8d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Decoders/InstTable.cs @@ -0,0 +1,392 @@ +using Ryujinx.Graphics.Shader.Instructions; +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class InstTable + { + private const int EncodingBits = 14; + + private readonly struct TableEntry + { + public InstName Name { get; } + public InstEmitter Emitter { get; } + public InstProps Props { get; } + + public int XBits { get; } + + public TableEntry(InstName name, InstEmitter emitter, InstProps props, int xBits) + { + Name = name; + Emitter = emitter; + Props = props; + XBits = xBits; + } + } + + private static readonly TableEntry[] _opCodes; + + static InstTable() + { + _opCodes = new TableEntry[1 << EncodingBits]; + + #region Instructions +#pragma warning disable IDE0055 // Disable formatting + Add("1110111110100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Al2p, InstEmit.Al2p, InstProps.Rd | InstProps.Ra); + Add("1110111111011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ald, InstEmit.Ald, InstProps.Rd | InstProps.Ra); + Add("1110111111110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ast, InstEmit.Ast, InstProps.Ra | InstProps.Rb2 | InstProps.Rc); + Add("11101101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Atom, InstEmit.Atom, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("111011101111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.AtomCas, InstEmit.AtomCas, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("11101100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Atoms, InstEmit.Atoms, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("111011100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.AtomsCas, InstEmit.AtomsCas, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("1111000010111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.B2r, InstEmit.B2r, InstProps.Rd | InstProps.Ra | InstProps.VPd); + Add("1111000010101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bar, InstEmit.Bar, InstProps.Ra | InstProps.Ps); + Add("0101110000000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bfe, InstEmit.BfeR, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("0011100x00000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bfe, InstEmit.BfeI, InstProps.Rd | InstProps.Ra | InstProps.Ib); + Add("0100110000000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bfe, InstEmit.BfeC, InstProps.Rd | InstProps.Ra); + Add("0101101111110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bfi, InstEmit.BfiR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0011011x11110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bfi, InstEmit.BfiI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Rc); + Add("0100101111110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bfi, InstEmit.BfiC, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("0101001111110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bfi, InstEmit.BfiRc, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("111000111010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bpt, InstEmit.Bpt, InstProps.NoPred); + Add("111000100100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bra, InstEmit.Bra, InstProps.Bra); + Add("111000110100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Brk, InstEmit.Brk, InstProps.Bra); + Add("111000100101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Brx, InstEmit.Brx, InstProps.Ra | InstProps.Bra); + Add("111000100110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cal, InstEmit.Cal, InstProps.Bra | InstProps.NoPred); + Add("11101111011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cctl, InstEmit.Cctl, InstProps.Ra); + Add("1110111110000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cctll, InstEmit.Cctll, InstProps.Ra); + Add("1110101111110xx0000000000000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cctlt, InstEmit.Cctlt); + Add("1110101111101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cctlt, InstEmit.Cctlt, InstProps.Rc); + Add("111000110101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cont, InstEmit.Cont, InstProps.Bra); + Add("0101000010011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cset, InstEmit.Cset, InstProps.Rd | InstProps.Ps); + Add("0101000010100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Csetp, InstEmit.Csetp, InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("0101000011001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cs2r, InstEmit.Cs2r, InstProps.Rd); + Add("0101110001110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dadd, InstEmit.DaddR, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("0011100x01110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dadd, InstEmit.DaddI, InstProps.Rd | InstProps.Ra | InstProps.Ib); + Add("0100110001110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dadd, InstEmit.DaddC, InstProps.Rd | InstProps.Ra); + Add("1111000011110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Depbar, InstEmit.Depbar); + Add("010110110111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dfma, InstEmit.DfmaR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0011011x0111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dfma, InstEmit.DfmaI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Rc); + Add("010010110111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dfma, InstEmit.DfmaC, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("010100110111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dfma, InstEmit.DfmaRc, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("0101110001010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dmnmx, InstEmit.DmnmxR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Ps); + Add("0011100x01010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dmnmx, InstEmit.DmnmxI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Ps); + Add("0100110001010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dmnmx, InstEmit.DmnmxC, InstProps.Rd | InstProps.Ra | InstProps.Ps); + Add("0101110010000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dmul, InstEmit.DmulR, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("0011100x10000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dmul, InstEmit.DmulI, InstProps.Rd | InstProps.Ra | InstProps.Ib); + Add("0100110010000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dmul, InstEmit.DmulC, InstProps.Rd | InstProps.Ra); + Add("010110010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dset, InstEmit.DsetR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Ps); + Add("0011001x0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dset, InstEmit.DsetI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Ps); + Add("010010010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dset, InstEmit.DsetC, InstProps.Rd | InstProps.Ra | InstProps.Ps); + Add("010110111000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dsetp, InstEmit.DsetpR, InstProps.Ra | InstProps.Rb | InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("0011011x1000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dsetp, InstEmit.DsetpI, InstProps.Ra | InstProps.Ib | InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("010010111000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Dsetp, InstEmit.DsetpC, InstProps.Ra | InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("111000110000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Exit, InstEmit.Exit, InstProps.Bra); + Add("0101110010101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.F2f, InstEmit.F2fR, InstProps.Rd | InstProps.Rb); + Add("0011100x10101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.F2f, InstEmit.F2fI, InstProps.Rd | InstProps.Ib); + Add("0100110010101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.F2f, InstEmit.F2fC, InstProps.Rd); + Add("0101110010110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.F2i, InstEmit.F2iR, InstProps.Rd | InstProps.Rb); + Add("0011100x10110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.F2i, InstEmit.F2iI, InstProps.Rd | InstProps.Ib); + Add("0100110010110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.F2i, InstEmit.F2iC, InstProps.Rd); + Add("0101110001011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fadd, InstEmit.FaddR, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("0011100x01011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fadd, InstEmit.FaddI, InstProps.Rd | InstProps.Ra | InstProps.Ib); + Add("0100110001011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fadd, InstEmit.FaddC, InstProps.Rd | InstProps.Ra); + Add("000010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fadd32i, InstEmit.Fadd32i, InstProps.Rd | InstProps.Ra); + Add("0101110010001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fchk, InstEmit.FchkR, InstProps.Ra | InstProps.Rb | InstProps.Pd); + Add("0011100x10001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fchk, InstEmit.FchkI, InstProps.Ra | InstProps.Ib | InstProps.Pd); + Add("0100110010001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fchk, InstEmit.FchkC, InstProps.Ra | InstProps.Pd); + Add("010110111010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fcmp, InstEmit.FcmpR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0011011x1010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fcmp, InstEmit.FcmpI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Rc); + Add("010010111010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fcmp, InstEmit.FcmpC, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("010100111010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fcmp, InstEmit.FcmpRc, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("010110011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ffma, InstEmit.FfmaR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0011001x1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ffma, InstEmit.FfmaI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Rc); + Add("010010011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ffma, InstEmit.FfmaC, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("010100011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ffma, InstEmit.FfmaRc, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("000011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ffma32i, InstEmit.Ffma32i, InstProps.Rd | InstProps.Ra); + Add("0101110000110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Flo, InstEmit.FloR, InstProps.Rd | InstProps.Rb); + Add("0011100x00110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Flo, InstEmit.FloI, InstProps.Rd | InstProps.Ib); + Add("0100110000110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Flo, InstEmit.FloC, InstProps.Rd); + Add("0101110001100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fmnmx, InstEmit.FmnmxR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Ps); + Add("0011100x01100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fmnmx, InstEmit.FmnmxI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Ps); + Add("0100110001100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fmnmx, InstEmit.FmnmxC, InstProps.Rd | InstProps.Ra | InstProps.Ps); + Add("0101110001101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fmul, InstEmit.FmulR, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("0011100x01101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fmul, InstEmit.FmulI, InstProps.Rd | InstProps.Ra | InstProps.Ib); + Add("0100110001101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fmul, InstEmit.FmulC, InstProps.Rd | InstProps.Ra); + Add("00011110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fmul32i, InstEmit.Fmul32i, InstProps.Rd | InstProps.Ra); + Add("01011000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fset, InstEmit.FsetR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Ps); + Add("0011000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fset, InstEmit.FsetI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Ps); + Add("01001000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fset, InstEmit.FsetC, InstProps.Rd | InstProps.Ra | InstProps.Ps); + Add("010110111011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fsetp, InstEmit.FsetpR, InstProps.Ra | InstProps.Rb | InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("0011011x1011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fsetp, InstEmit.FsetpI, InstProps.Ra | InstProps.Ib | InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("010010111011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fsetp, InstEmit.FsetpC, InstProps.Ra | InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("0101000011111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Fswzadd, InstEmit.Fswzadd, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("111000101100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Getcrsptr, InstEmit.Getcrsptr, InstProps.Rd | InstProps.NoPred); + Add("111000101101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Getlmembase, InstEmit.Getlmembase, InstProps.Rd | InstProps.NoPred); + Add("0101110100010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hadd2, InstEmit.Hadd2R, InstProps.Rd | InstProps.Ra); + Add("0111101x0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hadd2, InstEmit.Hadd2I, InstProps.Rd | InstProps.Ra | InstProps.Ib); + Add("0111101x1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hadd2, InstEmit.Hadd2C, InstProps.Rd | InstProps.Ra); + Add("0010110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hadd232i, InstEmit.Hadd232i, InstProps.Rd | InstProps.Ra); + Add("0101110100000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hfma2, InstEmit.Hfma2R, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("01110xxx0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hfma2, InstEmit.Hfma2I, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Rc); + Add("01110xxx1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hfma2, InstEmit.Hfma2C, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("01100xxx1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hfma2, InstEmit.Hfma2Rc, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("0010100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hfma2, InstEmit.Hfma232i, InstProps.Rd | InstProps.Ra); + Add("0101110100001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hmul2, InstEmit.Hmul2R, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("0111100x0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hmul2, InstEmit.Hmul2I, InstProps.Rd | InstProps.Ra | InstProps.Ib); + Add("0111100x1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hmul2, InstEmit.Hmul2C, InstProps.Rd | InstProps.Ra); + Add("0010101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hmul232i, InstEmit.Hmul232i, InstProps.Rd | InstProps.Ra); + Add("0101110100011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hset2, InstEmit.Hset2R, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Ps); + Add("0111110x0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hset2, InstEmit.Hset2I, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Ps); + Add("0111110x1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hset2, InstEmit.Hset2C, InstProps.Rd | InstProps.Ra | InstProps.Ps); + Add("0101110100100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hsetp2, InstEmit.Hsetp2R, InstProps.Ra | InstProps.Rb | InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("0111111x0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hsetp2, InstEmit.Hsetp2I, InstProps.Ra | InstProps.Ib | InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("0111111x1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Hsetp2, InstEmit.Hsetp2C, InstProps.Ra | InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("0101110010111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.I2f, InstEmit.I2fR, InstProps.Rd | InstProps.Rb); + Add("0011100x10111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.I2f, InstEmit.I2fI, InstProps.Rd | InstProps.Ib); + Add("0100110010111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.I2f, InstEmit.I2fC, InstProps.Rd); + Add("0101110011100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.I2i, InstEmit.I2iR, InstProps.Rd | InstProps.Rb); + Add("0011100x11100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.I2i, InstEmit.I2iI, InstProps.Rd | InstProps.Ib); + Add("0100110011100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.I2i, InstEmit.I2iC, InstProps.Rd); + Add("0101110000010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Iadd, InstEmit.IaddR, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("0011100x00010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Iadd, InstEmit.IaddI, InstProps.Rd | InstProps.Ra | InstProps.Ib); + Add("0100110000010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Iadd, InstEmit.IaddC, InstProps.Rd | InstProps.Ra); + Add("0001110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Iadd32i, InstEmit.Iadd32i, InstProps.Rd | InstProps.Ra); + Add("010111001100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Iadd3, InstEmit.Iadd3R, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0011100x1100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Iadd3, InstEmit.Iadd3I, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Rc); + Add("010011001100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Iadd3, InstEmit.Iadd3C, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("010110110100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Icmp, InstEmit.IcmpR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0011011x0100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Icmp, InstEmit.IcmpI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Rc); + Add("010010110100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Icmp, InstEmit.IcmpC, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("010100110100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Icmp, InstEmit.IcmpRc, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("111000111001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ide, InstEmit.Ide, InstProps.NoPred); + Add("0101001111111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Idp, InstEmit.IdpR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0101001111011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Idp, InstEmit.IdpC, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("010110100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imad, InstEmit.ImadR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0011010x0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imad, InstEmit.ImadI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Rc); + Add("010010100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imad, InstEmit.ImadC, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("010100100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imad, InstEmit.ImadRc, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("000100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imad32i, InstEmit.Imad32i, InstProps.Rd | InstProps.Ra); + Add("010110101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imadsp, InstEmit.ImadspR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0011010x1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imadsp, InstEmit.ImadspI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Rc); + Add("010010101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imadsp, InstEmit.ImadspC, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("010100101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imadsp, InstEmit.ImadspRc, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("0101110000100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imnmx, InstEmit.ImnmxR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Ps); + Add("0011100x00100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imnmx, InstEmit.ImnmxI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Ps); + Add("0100110000100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imnmx, InstEmit.ImnmxC, InstProps.Rd | InstProps.Ra | InstProps.Ps); + Add("0101110000111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imul, InstEmit.ImulR, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("0011100x00111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imul, InstEmit.ImulI, InstProps.Rd | InstProps.Ra | InstProps.Ib); + Add("0100110000111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imul, InstEmit.ImulC, InstProps.Rd | InstProps.Ra); + Add("00011111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Imul32i, InstEmit.Imul32i, InstProps.Rd | InstProps.Ra); + Add("11100000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ipa, InstEmit.Ipa, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("1110111111010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Isberd, InstEmit.Isberd, InstProps.Rd | InstProps.Ra); + Add("0101110000011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Iscadd, InstEmit.IscaddR, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("0011100x00011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Iscadd, InstEmit.IscaddI, InstProps.Rd | InstProps.Ra | InstProps.Ib); + Add("0100110000011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Iscadd, InstEmit.IscaddC, InstProps.Rd | InstProps.Ra); + Add("000101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Iscadd32i, InstEmit.Iscadd32i, InstProps.Rd | InstProps.Ra); + Add("010110110101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Iset, InstEmit.IsetR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Ps); + Add("0011011x0101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Iset, InstEmit.IsetI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Ps); + Add("010010110101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Iset, InstEmit.IsetC, InstProps.Rd | InstProps.Ra | InstProps.Ps); + Add("010110110110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Isetp, InstEmit.IsetpR, InstProps.Ra | InstProps.Rb | InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("0011011x0110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Isetp, InstEmit.IsetpI, InstProps.Ra | InstProps.Ib | InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("010010110110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Isetp, InstEmit.IsetpC, InstProps.Ra | InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("111000100010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Jcal, InstEmit.Jcal, InstProps.Bra); + Add("111000100001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Jmp, InstEmit.Jmp, InstProps.Ra | InstProps.Bra); + Add("111000100000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Jmx, InstEmit.Jmx, InstProps.Ra | InstProps.Bra); + Add("111000110011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Kil, InstEmit.Kil, InstProps.Bra); + Add("100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ld, InstEmit.Ld, InstProps.Rd | InstProps.Ra); + Add("1110111110010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldc, InstEmit.Ldc, InstProps.Rd | InstProps.Ra); + Add("1110111011010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldg, InstEmit.Ldg, InstProps.Rd | InstProps.Ra); + Add("1110111101000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldl, InstEmit.Ldl, InstProps.Rd | InstProps.Ra); + Add("1110111101001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Lds, InstEmit.Lds, InstProps.Rd | InstProps.Ra); + Add("0101101111010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Lea, InstEmit.LeaR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.LPd); + Add("0011011x11010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Lea, InstEmit.LeaI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.LPd); + Add("0100101111010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Lea, InstEmit.LeaC, InstProps.Rd | InstProps.Ra | InstProps.LPd); + Add("0101101111011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.LeaHi, InstEmit.LeaHiR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc | InstProps.LPd); + Add("000110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.LeaHi, InstEmit.LeaHiC, InstProps.Rd | InstProps.Ra | InstProps.Rc | InstProps.LPd); + Add("0101000011010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Lepc, InstEmit.Lepc); + Add("111000110001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Longjmp, InstEmit.Longjmp, InstProps.Bra); + Add("0101110001000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Lop, InstEmit.LopR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.LPd); + Add("0011100x01000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Lop, InstEmit.LopI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.LPd); + Add("0100110001000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Lop, InstEmit.LopC, InstProps.Rd | InstProps.Ra | InstProps.LPd); + Add("0101101111100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Lop3, InstEmit.Lop3R, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc | InstProps.LPd); + Add("001111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Lop3, InstEmit.Lop3I, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Rc); + Add("0000001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Lop3, InstEmit.Lop3C, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("000001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Lop32i, InstEmit.Lop32i, InstProps.Rd | InstProps.Ra); + Add("1110111110011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Membar, InstEmit.Membar); + Add("0101110010011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Mov, InstEmit.MovR, InstProps.Rd | InstProps.Ra); + Add("0011100x10011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Mov, InstEmit.MovI, InstProps.Rd | InstProps.Ib); + Add("0100110010011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Mov, InstEmit.MovC, InstProps.Rd); + Add("000000010000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Mov32i, InstEmit.Mov32i, InstProps.Rd); + Add("0101000010000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Mufu, InstEmit.Mufu, InstProps.Rd | InstProps.Ra); + Add("0101000010110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Nop, InstEmit.Nop); + Add("1111101111100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Out, InstEmit.OutR, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("1111011x11100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Out, InstEmit.OutI, InstProps.Rd | InstProps.Ra | InstProps.Ib); + Add("1110101111100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Out, InstEmit.OutC, InstProps.Rd | InstProps.Ra); + Add("0101110011101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.P2r, InstEmit.P2rR, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("0011100x11101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.P2r, InstEmit.P2rI, InstProps.Rd | InstProps.Ra | InstProps.Ib); + Add("0100110011101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.P2r, InstEmit.P2rC, InstProps.Rd | InstProps.Ra); + Add("111000101010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Pbk, InstEmit.Pbk, InstProps.NoPred); + Add("111000101011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Pcnt, InstEmit.Pcnt, InstProps.NoPred); + Add("111000100011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Pexit, InstEmit.Pexit); + Add("1110111111101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Pixld, InstEmit.Pixld, InstProps.Rd | InstProps.Ra | InstProps.VPd); + Add("111000101000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Plongjmp, InstEmit.Plongjmp, InstProps.Bra | InstProps.NoPred); + Add("0101110000001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Popc, InstEmit.PopcR, InstProps.Rd | InstProps.Rb); + Add("0011100x00001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Popc, InstEmit.PopcI, InstProps.Rd | InstProps.Ib); + Add("0100110000001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Popc, InstEmit.PopcC, InstProps.Rd); + Add("111000100111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Pret, InstEmit.Pret, InstProps.NoPred); + Add("010110111100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Prmt, InstEmit.PrmtR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0011011x1100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Prmt, InstEmit.PrmtI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Rc); + Add("010010111100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Prmt, InstEmit.PrmtC, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("010100111100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Prmt, InstEmit.PrmtRc, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("0101000010001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Pset, InstEmit.Pset, InstProps.Rd | InstProps.Ps); + Add("0101000010010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Psetp, InstEmit.Psetp, InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("1111000011000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.R2b, InstEmit.R2b, InstProps.Rb); + Add("0101110011110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.R2p, InstEmit.R2pR, InstProps.Ra | InstProps.Rb); + Add("0011100x11110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.R2p, InstEmit.R2pI, InstProps.Ra | InstProps.Ib); + Add("0100110011110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.R2p, InstEmit.R2pC, InstProps.Ra); + Add("111000111000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ram, InstEmit.Ram, InstProps.NoPred); + Add("1110101111111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Red, InstEmit.Red, InstProps.Ra | InstProps.Rb2); + Add("111000110010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ret, InstEmit.Ret, InstProps.Bra); + Add("0101110010010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Rro, InstEmit.RroR, InstProps.Rd | InstProps.Rb); + Add("0011100x10010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Rro, InstEmit.RroI, InstProps.Rd | InstProps.Ib); + Add("0100110010010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Rro, InstEmit.RroC, InstProps.Rd); + Add("111000110110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Rtt, InstEmit.Rtt, InstProps.NoPred); + Add("1111000011001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.S2r, InstEmit.S2r, InstProps.Rd); + Add("111000110111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Sam, InstEmit.Sam, InstProps.NoPred); + Add("0101110010100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Sel, InstEmit.SelR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Ps); + Add("0011100x10100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Sel, InstEmit.SelI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Ps); + Add("0100110010100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Sel, InstEmit.SelC, InstProps.Rd | InstProps.Ra | InstProps.Ps); + Add("111000101110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Setcrsptr, InstEmit.Setcrsptr, InstProps.Ra | InstProps.NoPred); + Add("111000101111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Setlmembase, InstEmit.Setlmembase, InstProps.Ra | InstProps.NoPred); + Add("0101101111111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Shf, InstEmit.ShfLR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0101110011111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Shf, InstEmit.ShfRR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0011011x11111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Shf, InstEmit.ShfLI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Rc); + Add("0011100x11111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Shf, InstEmit.ShfRI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Rc); + Add("1110111100010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Shfl, InstEmit.Shfl, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc | InstProps.LPd); + Add("0101110001001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Shl, InstEmit.ShlR, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("0011100x01001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Shl, InstEmit.ShlI, InstProps.Rd | InstProps.Ra | InstProps.Ib); + Add("0100110001001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Shl, InstEmit.ShlC, InstProps.Rd | InstProps.Ra); + Add("0101110000101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Shr, InstEmit.ShrR, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("0011100x00101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Shr, InstEmit.ShrI, InstProps.Rd | InstProps.Ra | InstProps.Ib); + Add("0100110000101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Shr, InstEmit.ShrC, InstProps.Rd | InstProps.Ra); + Add("111000101001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ssy, InstEmit.Ssy, InstProps.NoPred); + Add("101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.St, InstEmit.St, InstProps.Rd | InstProps.Ra); + Add("1110111011011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Stg, InstEmit.Stg, InstProps.Rd | InstProps.Ra); + Add("1110111101010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Stl, InstEmit.Stl, InstProps.Rd | InstProps.Ra); + Add("1110111010100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Stp, InstEmit.Stp, InstProps.NoPred); + Add("1110111101011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Sts, InstEmit.Sts, InstProps.Rd | InstProps.Ra); + Add("1110101001110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuatomB, InstEmit.SuatomB, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("11101010x0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Suatom, InstEmit.Suatom, InstProps.Rd | InstProps.Ra | InstProps.Rb); + Add("1110101110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuatomB2, InstEmit.SuatomB2, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("1110101011010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuatomCasB, InstEmit.SuatomCasB, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc | InstProps.SPd); + Add("1110101x1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuatomCas, InstEmit.SuatomCas, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.SPd); + Add("1110101100010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuldDB, InstEmit.SuldDB, InstProps.Rd | InstProps.Ra | InstProps.Rc | InstProps.SPd | InstProps.TexB); + Add("1110101100011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuldD, InstEmit.SuldD, InstProps.Rd | InstProps.Ra | InstProps.SPd | InstProps.Tex); + Add("1110101100000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuldB, InstEmit.SuldB, InstProps.Rd | InstProps.Ra | InstProps.Rc | InstProps.SPd | InstProps.TexB); + Add("1110101100001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Suld, InstEmit.Suld, InstProps.Rd | InstProps.Ra | InstProps.SPd | InstProps.Tex); + Add("1110101101010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SuredB, InstEmit.SuredB, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("1110101101011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Sured, InstEmit.Sured, InstProps.Rd | InstProps.Ra); + Add("1110101100110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SustDB, InstEmit.SustDB, InstProps.Rd | InstProps.Ra | InstProps.Rc | InstProps.TexB); + Add("1110101100111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SustD, InstEmit.SustD, InstProps.Rd | InstProps.Ra | InstProps.Tex); + Add("1110101100100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.SustB, InstEmit.SustB, InstProps.Rd | InstProps.Ra | InstProps.Rc | InstProps.TexB); + Add("1110101100101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Sust, InstEmit.Sust, InstProps.Rd | InstProps.Ra | InstProps.Tex); + Add("1111000011111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Sync, InstEmit.Sync, InstProps.Bra); + Add("11000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tex, InstEmit.Tex, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.TPd | InstProps.Tex); + Add("1101111010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.TexB, InstEmit.TexB, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.TPd | InstProps.TexB); + Add("1101100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Texs, InstEmit.Texs, InstProps.Rd | InstProps.Rd2 | InstProps.Ra | InstProps.Rb | InstProps.Tex); + Add("1101000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.TexsF16, InstEmit.TexsF16, InstProps.Rd | InstProps.Rd2 | InstProps.Ra | InstProps.Rb | InstProps.Tex); + Add("11011100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tld, InstEmit.Tld, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.TPd | InstProps.Tex); + Add("11011101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.TldB, InstEmit.TldB, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.TPd | InstProps.TexB); + Add("1101101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tlds, InstEmit.Tlds, InstProps.Rd | InstProps.Rd2 | InstProps.Ra | InstProps.Rb | InstProps.Tex); + Add("1101001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.TldsF16, InstEmit.TldsF16, InstProps.Rd | InstProps.Rd2 | InstProps.Ra | InstProps.Rb | InstProps.Tex); + Add("110010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tld4, InstEmit.Tld4, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.TPd | InstProps.Tex); + Add("1101111011xxxxxxxxxxxxx0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tld4B, InstEmit.Tld4B, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.TPd | InstProps.TexB); + Add("1101111100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tld4s, InstEmit.Tld4s, InstProps.Rd | InstProps.Rd2 | InstProps.Ra | InstProps.Rb | InstProps.Tex); + Add("1101111110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tld4sF16, InstEmit.Tld4sF16, InstProps.Rd | InstProps.Rd2 | InstProps.Ra | InstProps.Rb | InstProps.Tex); + Add("1101111101011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tmml, InstEmit.Tmml, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Tex); + Add("1101111101100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.TmmlB, InstEmit.TmmlB, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.TexB); + Add("1101111101000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Txa, InstEmit.Txa, InstProps.Rd | InstProps.Ra | InstProps.Tex); + Add("110111100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Txd, InstEmit.Txd, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.TPd | InstProps.Tex); + Add("1101111001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.TxdB, InstEmit.TxdB, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.TPd | InstProps.TexB); + Add("1101111101001xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Txq, InstEmit.Txq, InstProps.Rd | InstProps.Ra | InstProps.Tex); + Add("1101111101010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.TxqB, InstEmit.TxqB, InstProps.Rd | InstProps.Ra | InstProps.TexB); + Add("01010100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Vabsdiff, InstEmit.Vabsdiff, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("010100000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Vabsdiff4, InstEmit.Vabsdiff4, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("001000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Vadd, InstEmit.Vadd, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("01011111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Vmad, InstEmit.Vmad, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0011101xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Vmnmx, InstEmit.Vmnmx, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0101000011011xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Vote, InstEmit.Vote, InstProps.Rd | InstProps.VPd | InstProps.Ps); + Add("0101000011100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Votevtg, InstEmit.Votevtg); + Add("0100000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Vset, InstEmit.Vset, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0101000011110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Vsetp, InstEmit.Vsetp, InstProps.Ra | InstProps.Rb | InstProps.Pd | InstProps.Pdn | InstProps.Ps); + Add("01010111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Vshl, InstEmit.Vshl, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("01010110xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Vshr, InstEmit.Vshr, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0101101100xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Xmad, InstEmit.XmadR, InstProps.Rd | InstProps.Ra | InstProps.Rb | InstProps.Rc); + Add("0011011x00xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Xmad, InstEmit.XmadI, InstProps.Rd | InstProps.Ra | InstProps.Ib | InstProps.Rc); + Add("0100111xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Xmad, InstEmit.XmadC, InstProps.Rd | InstProps.Ra | InstProps.Rc); + Add("010100010xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Xmad, InstEmit.XmadRc, InstProps.Rd | InstProps.Ra | InstProps.Rc); +#pragma warning restore IDE0055 + #endregion + } + + private static void Add(string encoding, InstName name, InstEmitter emitter, InstProps props = InstProps.None) + { + ReadOnlySpan encodingPart = encoding.AsSpan(0, EncodingBits); + + int bit = encodingPart.Length - 1; + int value = 0; + int xMask = 0; + int xBits = 0; + + int[] xPos = new int[encodingPart.Length]; + + for (int index = 0; index < encodingPart.Length; index++, bit--) + { + char chr = encodingPart[index]; + + if (chr == '1') + { + value |= 1 << bit; + } + else if (chr == 'x') + { + xMask |= 1 << bit; + + xPos[xBits++] = bit; + } + } + + xMask = ~xMask; + + TableEntry entry = new(name, emitter, props, xBits); + + for (int index = 0; index < (1 << xBits); index++) + { + value &= xMask; + + for (int x = 0; x < xBits; x++) + { + value |= ((index >> x) & 1) << xPos[x]; + } + + if (_opCodes[value].Emitter == null || _opCodes[value].XBits > xBits) + { + _opCodes[value] = entry; + } + } + } + + public static InstOp GetOp(ulong address, ulong opCode) + { + ref TableEntry entry = ref _opCodes[opCode >> (64 - EncodingBits)]; + + if (entry.Emitter != null) + { + return new InstOp(address, opCode, entry.Name, entry.Emitter, entry.Props); + } + + return new InstOp(address, opCode, InstName.Invalid, null, InstProps.None); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Decoders/Register.cs b/src/Ryujinx.Graphics.Shader/Decoders/Register.cs new file mode 100644 index 00000000..2e6d6199 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Decoders/Register.cs @@ -0,0 +1,36 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Decoders +{ + readonly struct Register : IEquatable + { + public int Index { get; } + + public RegisterType Type { get; } + + public bool IsRZ => Type == RegisterType.Gpr && Index == RegisterConsts.RegisterZeroIndex; + public bool IsPT => Type == RegisterType.Predicate && Index == RegisterConsts.PredicateTrueIndex; + + public Register(int index, RegisterType type) + { + Index = index; + Type = type; + } + + public override int GetHashCode() + { + return (ushort)Index | ((ushort)Type << 16); + } + + public override bool Equals(object obj) + { + return obj is Register reg && Equals(reg); + } + + public bool Equals(Register other) + { + return other.Index == Index && + other.Type == Type; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Decoders/RegisterConsts.cs b/src/Ryujinx.Graphics.Shader/Decoders/RegisterConsts.cs new file mode 100644 index 00000000..416fba96 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Decoders/RegisterConsts.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + static class RegisterConsts + { + public const int GprsCount = 255; + public const int PredsCount = 7; + public const int FlagsCount = 4; + public const int TotalCount = GprsCount + PredsCount + FlagsCount; + + public const int RegisterZeroIndex = GprsCount; + public const int PredicateTrueIndex = PredsCount; + } +} diff --git a/src/Ryujinx.Graphics.Shader/Decoders/RegisterType.cs b/src/Ryujinx.Graphics.Shader/Decoders/RegisterType.cs new file mode 100644 index 00000000..c870464f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Decoders/RegisterType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Decoders +{ + enum RegisterType + { + Flag, + Gpr, + Predicate, + } +} diff --git a/src/Ryujinx.Graphics.Shader/GpuGraphicsState.cs b/src/Ryujinx.Graphics.Shader/GpuGraphicsState.cs new file mode 100644 index 00000000..38684002 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/GpuGraphicsState.cs @@ -0,0 +1,177 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Shader +{ + /// + /// GPU graphics state that the shader depends on. + /// + public readonly struct GpuGraphicsState + { + /// + /// Early Z force enable. + /// + public readonly bool EarlyZForce; + + /// + /// Primitive topology of current draw. + /// + public readonly InputTopology Topology; + + /// + /// Tessellation winding order. + /// + public readonly bool TessCw; + + /// + /// Tessellation patch type. + /// + public readonly TessPatchType TessPatchType; + + /// + /// Tessellation spacing. + /// + public readonly TessSpacing TessSpacing; + + /// + /// Indicates whether alpha-to-coverage is enabled. + /// + public readonly bool AlphaToCoverageEnable; + + /// + /// Indicates whether alpha-to-coverage dithering is enabled. + /// + public readonly bool AlphaToCoverageDitherEnable; + + /// + /// Indicates whether the viewport transform is disabled. + /// + public readonly bool ViewportTransformDisable; + + /// + /// Depth mode zero to one or minus one to one. + /// + public readonly bool DepthMode; + + /// + /// Indicates if the point size is set on the shader or is fixed. + /// + public readonly bool ProgramPointSizeEnable; + + /// + /// Point size used if is false. + /// + public readonly float PointSize; + + /// + /// When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded. + /// + public readonly AlphaTestOp AlphaTestCompare; + + /// + /// When alpha test is enabled, indicates the value to compare with the fragment output alpha. + /// + public readonly float AlphaTestReference; + + /// + /// Type of the vertex attributes consumed by the shader. + /// + public readonly Array32 AttributeTypes; + + /// + /// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0. + /// + public readonly bool HasConstantBufferDrawParameters; + + /// + /// Type of the fragment shader outputs. + /// + public readonly Array8 FragmentOutputTypes; + + /// + /// Indicates whether dual source blend is enabled. + /// + public readonly bool DualSourceBlendEnable; + + /// + /// Indicates if negation of the viewport Y axis is enabled. + /// + public readonly bool YNegateEnabled; + + /// + /// If true, indicates that the fragment origin is the upper left corner of the viewport, otherwise it is the lower left corner. + /// + public readonly bool OriginUpperLeft; + + /// + /// Indicates that the primitive ID values on the shader should be halved due to quad to triangles conversion. + /// + public readonly bool HalvePrimitiveId; + + /// + /// Creates a new GPU graphics state. + /// + /// Early Z force enable + /// Primitive topology + /// Tessellation winding order (clockwise or counter-clockwise) + /// Tessellation patch type + /// Tessellation spacing + /// Indicates whether alpha-to-coverage is enabled + /// Indicates whether alpha-to-coverage dithering is enabled + /// Indicates whether the viewport transform is disabled + /// Depth mode zero to one or minus one to one + /// Indicates if the point size is set on the shader or is fixed + /// Point size if not set from shader + /// When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded + /// When alpha test is enabled, indicates the value to compare with the fragment output alpha + /// Type of the vertex attributes consumed by the shader + /// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0 + /// Type of the fragment shader outputs + /// Indicates whether dual source blend is enabled + /// Indicates if negation of the viewport Y axis is enabled + /// If true, indicates that the fragment origin is the upper left corner of the viewport, otherwise it is the lower left corner + /// Indicates that the primitive ID values on the shader should be halved due to quad to triangles conversion + public GpuGraphicsState( + bool earlyZForce, + InputTopology topology, + bool tessCw, + TessPatchType tessPatchType, + TessSpacing tessSpacing, + bool alphaToCoverageEnable, + bool alphaToCoverageDitherEnable, + bool viewportTransformDisable, + bool depthMode, + bool programPointSizeEnable, + float pointSize, + AlphaTestOp alphaTestCompare, + float alphaTestReference, + in Array32 attributeTypes, + bool hasConstantBufferDrawParameters, + in Array8 fragmentOutputTypes, + bool dualSourceBlendEnable, + bool yNegateEnabled, + bool originUpperLeft, + bool halvePrimitiveId) + { + EarlyZForce = earlyZForce; + Topology = topology; + TessCw = tessCw; + TessPatchType = tessPatchType; + TessSpacing = tessSpacing; + AlphaToCoverageEnable = alphaToCoverageEnable; + AlphaToCoverageDitherEnable = alphaToCoverageDitherEnable; + ViewportTransformDisable = viewportTransformDisable; + DepthMode = depthMode; + ProgramPointSizeEnable = programPointSizeEnable; + PointSize = pointSize; + AlphaTestCompare = alphaTestCompare; + AlphaTestReference = alphaTestReference; + AttributeTypes = attributeTypes; + HasConstantBufferDrawParameters = hasConstantBufferDrawParameters; + FragmentOutputTypes = fragmentOutputTypes; + DualSourceBlendEnable = dualSourceBlendEnable; + YNegateEnabled = yNegateEnabled; + OriginUpperLeft = originUpperLeft; + HalvePrimitiveId = halvePrimitiveId; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs new file mode 100644 index 00000000..4e6d6edf --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -0,0 +1,498 @@ +using Ryujinx.Graphics.Shader.CodeGen; +using System; + +namespace Ryujinx.Graphics.Shader +{ + /// + /// GPU state access interface. + /// + public interface IGpuAccessor : ILogger + { + /// + /// Reads data from the constant buffer 1. + /// + /// Offset in bytes to read from + /// Value at the given offset + uint ConstantBuffer1Read(int offset) + { + return 0; + } + + /// + /// Gets a span of the specified memory location, containing shader code. + /// + /// GPU virtual address of the data + /// Minimum size that the returned span may have + /// Span of the memory location + ReadOnlySpan GetCode(ulong address, int minimumSize); + + /// + /// Gets the binding number of a constant buffer. + /// + /// Constant buffer index + /// Binding number + SetBindingPair CreateConstantBufferBinding(int index); + + /// + /// Gets the binding number of an image. + /// + /// For array of images, the number of elements of the array, otherwise it should be 1 + /// Indicates if the image is a buffer image + /// Binding number + SetBindingPair CreateImageBinding(int count, bool isBuffer); + + /// + /// Gets the binding number of a storage buffer. + /// + /// Storage buffer index + /// Binding number + SetBindingPair CreateStorageBufferBinding(int index); + + /// + /// Gets the binding number of a texture. + /// + /// For array of textures, the number of elements of the array, otherwise it should be 1 + /// Indicates if the texture is a buffer texture + /// Binding number + SetBindingPair CreateTextureBinding(int count, bool isBuffer); + + /// + /// Gets the set index for an additional set, or -1 if there's no extra set available. + /// + /// Extra set index, or -1 if not available + int CreateExtraSet() + { + return -1; + } + + /// + /// Queries Local Size X for compute shaders. + /// + /// Local Size X + int QueryComputeLocalSizeX() + { + return 1; + } + + /// + /// Queries Local Size Y for compute shaders. + /// + /// Local Size Y + int QueryComputeLocalSizeY() + { + return 1; + } + + /// + /// Queries Local Size Z for compute shaders. + /// + /// Local Size Z + int QueryComputeLocalSizeZ() + { + return 1; + } + + /// + /// Queries Local Memory size in bytes for compute shaders. + /// + /// Local Memory size in bytes + int QueryComputeLocalMemorySize() + { + return 0x1000; + } + + /// + /// Queries Shared Memory size in bytes for compute shaders. + /// + /// Shared Memory size in bytes + int QueryComputeSharedMemorySize() + { + return 0xc000; + } + + /// + /// Queries Constant Buffer usage information. + /// + /// A mask where each bit set indicates a bound constant buffer + uint QueryConstantBufferUse() + { + return 0; + } + + /// + /// Queries specialized GPU graphics state that the shader depends on. + /// + /// GPU graphics state + GpuGraphicsState QueryGraphicsState() + { + return new GpuGraphicsState( + false, + InputTopology.Points, + false, + TessPatchType.Triangles, + TessSpacing.EqualSpacing, + false, + false, + false, + false, + false, + 1f, + AlphaTestOp.Always, + 0f, + default, + true, + default, + false, + false, + false, + false); + } + + /// + /// Queries whenever the current draw has written the base vertex and base instance into Constant Buffer 0. + /// + /// True if the shader translator can assume that the constant buffer contains the base IDs, false otherwise + bool QueryHasConstantBufferDrawParameters() + { + return false; + } + + /// + /// Queries whenever the current draw uses unaligned storage buffer addresses. + /// + /// True if any storage buffer address is not aligned to 16 bytes, false otherwise + bool QueryHasUnalignedStorageBuffer() + { + return false; + } + + /// + /// Queries host's gather operation precision bits for biasing their coordinates. Zero means no bias. + /// + /// Bits of gather operation precision to use for coordinate bias + int QueryHostGatherBiasPrecision() + { + return 0; + } + + /// + /// Queries host about whether to reduce precision to improve performance. + /// + /// True if precision is limited to vertex position, false otherwise + bool QueryHostReducedPrecision() + { + return false; + } + + /// + /// Queries host about the presence of the FrontFacing built-in variable bug. + /// + /// True if the bug is present on the host device used, false otherwise + bool QueryHostHasFrontFacingBug() + { + return false; + } + + /// + /// Queries host about the presence of the vector indexing bug. + /// + /// True if the bug is present on the host device used, false otherwise + bool QueryHostHasVectorIndexingBug() + { + return false; + } + + /// + /// Queries host storage buffer alignment required. + /// + /// Host storage buffer alignment in bytes + int QueryHostStorageBufferOffsetAlignment() + { + return 16; + } + + /// + /// Queries host shader subgroup size. + /// + /// Host shader subgroup size in invocations + int QueryHostSubgroupSize() + { + return 32; + } + + /// + /// Queries host support for texture formats with BGRA component order (such as BGRA8). + /// + /// True if BGRA formats are supported, false otherwise + bool QueryHostSupportsBgraFormat() + { + return true; + } + + /// + /// Queries host support for fragment shader ordering critical sections on the shader code. + /// + /// True if fragment shader interlock is supported, false otherwise + bool QueryHostSupportsFragmentShaderInterlock() + { + return true; + } + + /// + /// Queries host support for fragment shader ordering scoped critical sections on the shader code. + /// + /// True if fragment shader ordering is supported, false otherwise + bool QueryHostSupportsFragmentShaderOrderingIntel() + { + return false; + } + + /// + /// Queries host GPU geometry shader support. + /// + /// True if the GPU and driver supports geometry shaders, false otherwise + bool QueryHostSupportsGeometryShader() + { + return true; + } + + /// + /// Queries host GPU geometry shader passthrough support. + /// + /// True if the GPU and driver supports geometry shader passthrough, false otherwise + bool QueryHostSupportsGeometryShaderPassthrough() + { + return true; + } + + /// + /// Queries host support for readable images without a explicit format declaration on the shader. + /// + /// True if formatted image load is supported, false otherwise + bool QueryHostSupportsImageLoadFormatted() + { + return true; + } + + /// + /// Queries host support for writes to the layer from vertex or tessellation shader stages. + /// + /// True if writes to the layer from vertex or tessellation are supported, false otherwise + bool QueryHostSupportsLayerVertexTessellation() + { + return true; + } + + /// + /// Queries host GPU non-constant texture offset support. + /// + /// True if the GPU and driver supports non-constant texture offsets, false otherwise + bool QueryHostSupportsNonConstantTextureOffset() + { + return true; + } + + /// + /// Queries host support scaled vertex formats, where a integer value is converted to floating-point. + /// + /// True if the host support scaled vertex formats, false otherwise + bool QueryHostSupportsScaledVertexFormats() + { + return true; + } + + /// + /// Queries host API support for separate textures and samplers. + /// + /// True if the API supports samplers and textures to be combined on the shader, false otherwise + bool QueryHostSupportsSeparateSampler() + { + return true; + } + + /// + /// Queries host GPU shader ballot support. + /// + /// True if the GPU and driver supports shader ballot, false otherwise + bool QueryHostSupportsShaderBallot() + { + return true; + } + + /// + /// Queries host GPU shader support for barrier instructions on divergent control flow paths. + /// + /// True if the GPU supports barriers on divergent control flow paths, false otherwise + bool QueryHostSupportsShaderBarrierDivergence() + { + return true; + } + + /// + /// Queries host GPU support for 64-bit floating point (double precision) operations on the shader. + /// + /// True if the GPU and driver supports double operations, false otherwise + bool QueryHostSupportsShaderFloat64() + { + return true; + } + + /// + /// Queries host GPU support for signed normalized buffer texture formats. + /// + /// True if the GPU and driver supports the formats, false otherwise + bool QueryHostSupportsSnormBufferTextureFormat() + { + return true; + } + + /// + /// Queries host GPU texture gather with multiple offsets support. + /// + /// True if the GPU and driver supports texture gather offsets, false otherwise + bool QueryHostSupportsTextureGatherOffsets() + { + return true; + } + + /// + /// Queries host GPU texture shadow LOD support. + /// + /// True if the GPU and driver supports texture shadow LOD, false otherwise + bool QueryHostSupportsTextureShadowLod() + { + return true; + } + + /// + /// Queries host GPU transform feedback support. + /// + /// True if the GPU and driver supports transform feedback, false otherwise + bool QueryHostSupportsTransformFeedback() + { + return true; + } + + /// + /// Queries host support for writes to the viewport index from vertex or tessellation shader stages. + /// + /// True if writes to the viewport index from vertex or tessellation are supported, false otherwise + bool QueryHostSupportsViewportIndexVertexTessellation() + { + return true; + } + + /// + /// Queries host GPU shader viewport mask output support. + /// + /// True if the GPU and driver supports shader viewport mask output, false otherwise + bool QueryHostSupportsViewportMask() + { + return true; + } + + /// + /// Queries whether the host supports depth clip control. + /// + /// True if the GPU and driver supports depth clip control, false otherwise + bool QueryHostSupportsDepthClipControl() + { + return true; + } + + /// + /// Gets the maximum number of samplers that the bound texture pool may have. + /// + /// Maximum amount of samplers that the pool may have + int QuerySamplerArrayLengthFromPool(); + + /// + /// Queries sampler type information. + /// + /// Texture handle + /// Constant buffer slot for the texture handle + /// The sampler type value for the given handle + SamplerType QuerySamplerType(int handle, int cbufSlot = -1) + { + return SamplerType.Texture2D; + } + + /// + /// Gets the size in bytes of a bound constant buffer for the current shader stage. + /// + /// The number of the constant buffer to get the size from + /// Size in bytes + int QueryTextureArrayLengthFromBuffer(int slot); + + /// + /// Gets the maximum number of textures that the bound texture pool may have. + /// + /// Maximum amount of textures that the pool may have + int QueryTextureArrayLengthFromPool(); + + /// + /// Queries texture coordinate normalization information. + /// + /// Texture handle + /// Constant buffer slot for the texture handle + /// True if the coordinates are normalized, false otherwise + bool QueryTextureCoordNormalized(int handle, int cbufSlot = -1) + { + return true; + } + + /// + /// Queries texture format information, for shaders using image load or store. + /// + /// + /// This only returns non-compressed color formats. + /// If the format of the texture is a compressed, depth or unsupported format, then a default value is returned. + /// + /// Texture handle + /// Constant buffer slot for the texture handle + /// Color format of the non-compressed texture + TextureFormat QueryTextureFormat(int handle, int cbufSlot = -1) + { + return TextureFormat.R8G8B8A8Unorm; + } + + /// + /// Queries transform feedback enable state. + /// + /// True if the shader uses transform feedback, false otherwise + bool QueryTransformFeedbackEnabled() + { + return false; + } + + /// + /// Queries the varying locations that should be written to the transform feedback buffer. + /// + /// Index of the transform feedback buffer + /// Varying locations for the specified buffer + ReadOnlySpan QueryTransformFeedbackVaryingLocations(int bufferIndex) + { + return ReadOnlySpan.Empty; + } + + /// + /// Queries the stride (in bytes) of the per vertex data written into the transform feedback buffer. + /// + /// Index of the transform feedback buffer + /// Stride for the specified buffer + int QueryTransformFeedbackStride(int bufferIndex) + { + return 0; + } + + /// + /// Registers a texture used by the shader. + /// + /// Texture handle word offset + /// Constant buffer slot where the texture handle is located + void RegisterTexture(int handle, int cbufSlot) + { + // Only useful when recording information for a disk shader cache. + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/ILogger.cs b/src/Ryujinx.Graphics.Shader/ILogger.cs new file mode 100644 index 00000000..c43067f5 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/ILogger.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Graphics.Shader.CodeGen +{ + /// + /// Shader code generation logging interface. + /// + public interface ILogger + { + /// + /// Prints a log message. + /// + /// Message to print + void Log(string message) + { + // No default log output. + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/InputTopology.cs b/src/Ryujinx.Graphics.Shader/InputTopology.cs new file mode 100644 index 00000000..9438263d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/InputTopology.cs @@ -0,0 +1,53 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum InputTopology : byte + { + Points, + Lines, + LinesAdjacency, + Triangles, + TrianglesAdjacency, + } + + static class InputTopologyExtensions + { + public static string ToGlslString(this InputTopology topology) + { + return topology switch + { + InputTopology.Points => "points", + InputTopology.Lines => "lines", + InputTopology.LinesAdjacency => "lines_adjacency", + InputTopology.Triangles => "triangles", + InputTopology.TrianglesAdjacency => "triangles_adjacency", + _ => "points", + }; + } + + public static int ToInputVertices(this InputTopology topology) + { + return topology switch + { + InputTopology.Points => 1, + InputTopology.Lines => 2, + InputTopology.LinesAdjacency => 4, + InputTopology.Triangles => 3, + InputTopology.TrianglesAdjacency => 6, + _ => 1, + }; + } + + public static int ToInputVerticesNoAdjacency(this InputTopology topology) + { + return topology switch + { + InputTopology.Points => 1, + InputTopology.Lines or + InputTopology.LinesAdjacency => 2, + InputTopology.Triangles or + InputTopology.TrianglesAdjacency => 3, + _ => 1, + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/AttributeMap.cs b/src/Ryujinx.Graphics.Shader/Instructions/AttributeMap.cs new file mode 100644 index 00000000..54705aca --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/AttributeMap.cs @@ -0,0 +1,350 @@ +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 readonly 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 _attributes; + private static readonly IReadOnlyDictionary _attributesPerPatch; + + static AttributeMap() + { + _attributes = CreateMap(); + _attributesPerPatch = CreatePerPatchMap(); + } + + private static IReadOnlyDictionary CreateMap() + { + var map = new Dictionary(); + + 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 CreatePerPatchMap() + { + var map = new Dictionary(); + + 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 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.TranslatorContext.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.TranslatorContext.Definitions.Stage) & validUseMask) == StagesMask.None) + { + context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not valid for stage {context.TranslatorContext.Definitions.Stage}."); + return Const(0); + } + + if (!IsSupportedByHost(context.TranslatorContext.GpuAccessor, context.TranslatorContext.Definitions.Stage, entry.IoVariable)) + { + context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not supported by the host for stage {context.TranslatorContext.Definitions.Stage}."); + return Const(0); + } + + if (HasInvocationId(context.TranslatorContext.Definitions.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.TranslatorContext.Definitions.Stage, in entry); + AggregateType type = GetType(context.TranslatorContext.Definitions, isOutput, innerIndex, in entry); + int elementCount = GetElementCount(type); + + bool isArray = type.HasFlag(AggregateType.Array); + bool hasArrayIndex = isArray || context.TranslatorContext.Definitions.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.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} is not valid."); + return; + } + + if (((StagesMask)(1 << (int)context.TranslatorContext.Definitions.Stage) & entry.OutputMask) == StagesMask.None) + { + context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not valid for stage {context.TranslatorContext.Definitions.Stage}."); + return; + } + + if (!IsSupportedByHost(context.TranslatorContext.GpuAccessor, context.TranslatorContext.Definitions.Stage, entry.IoVariable)) + { + context.TranslatorContext.GpuAccessor.Log($"Attribute offset 0x{offset:X} ({entry.IoVariable}) is not supported by the host for stage {context.TranslatorContext.Definitions.Stage}."); + return; + } + + Operand invocationId = null; + + if (HasInvocationId(context.TranslatorContext.Definitions.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.TranslatorContext.Definitions.Stage, in entry); + AggregateType type = GetType(context.TranslatorContext.Definitions, isOutput: true, innerIndex, in entry); + int elementCount = GetElementCount(type); + + bool isArray = type.HasFlag(AggregateType.Array); + bool hasArrayIndex = isArray || context.TranslatorContext.Definitions.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(ShaderDefinitions definitions, int offset, out int location) + { + location = 0; + + if (!_attributes.TryGetValue(offset, out AttributeEntry entry)) + { + return IoVariable.Invalid; + } + + if (((StagesMask)(1 << (int)definitions.Stage) & entry.OutputMask) == StagesMask.None) + { + return IoVariable.Invalid; + } + + if (definitions.HasPerLocationInputOrOutput(entry.IoVariable, isOutput: true)) + { + location = (offset - entry.BaseOffset) / 16; + } + + return GetIoVariable(definitions.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(ShaderDefinitions definitions, bool isOutput, int innerIndex, in AttributeEntry entry) + { + AggregateType type = entry.Type; + + if (entry.IoVariable == IoVariable.UserDefined) + { + type = definitions.GetUserDefinedType(innerIndex / 4, isOutput); + } + else if (entry.IoVariable == IoVariable.FragmentOutputColor) + { + type = definitions.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, + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmit.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmit.cs new file mode 100644 index 00000000..0c2f90b7 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmit.cs @@ -0,0 +1,358 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void AtomCas(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction AtomCas is not implemented."); + } + + public static void AtomsCas(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction AtomsCas is not implemented."); + } + + public static void B2r(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction B2r is not implemented."); + } + + public static void Bpt(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Bpt is not implemented."); + } + + public static void Cctl(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Cctl is not implemented."); + } + + public static void Cctll(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Cctll is not implemented."); + } + + public static void Cctlt(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Cctlt is not implemented."); + } + + public static void Cs2r(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Cs2r is not implemented."); + } + + public static void FchkR(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction FchkR is not implemented."); + } + + public static void FchkI(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction FchkI is not implemented."); + } + + public static void FchkC(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction FchkC is not implemented."); + } + + public static void Getcrsptr(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Getcrsptr is not implemented."); + } + + public static void Getlmembase(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Getlmembase is not implemented."); + } + + public static void Ide(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Ide is not implemented."); + } + + public static void IdpR(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction IdpR is not implemented."); + } + + public static void IdpC(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction IdpC is not implemented."); + } + + public static void ImadspR(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction ImadspR is not implemented."); + } + + public static void ImadspI(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction ImadspI is not implemented."); + } + + public static void ImadspC(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction ImadspC is not implemented."); + } + + public static void ImadspRc(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction ImadspRc is not implemented."); + } + + public static void Jcal(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Jcal is not implemented."); + } + + public static void Jmp(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Jmp is not implemented."); + } + + public static void Jmx(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Jmx is not implemented."); + } + + public static void Ld(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Ld is not implemented."); + } + + public static void Lepc(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Lepc is not implemented."); + } + + public static void Longjmp(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Longjmp is not implemented."); + } + + public static void Pexit(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Pexit is not implemented."); + } + + public static void Pixld(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Pixld is not implemented."); + } + + public static void Plongjmp(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Plongjmp is not implemented."); + } + + public static void Pret(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Pret is not implemented."); + } + + public static void PrmtR(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtR is not implemented."); + } + + public static void PrmtI(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtI is not implemented."); + } + + public static void PrmtC(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtC is not implemented."); + } + + public static void PrmtRc(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction PrmtRc is not implemented."); + } + + public static void R2b(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction R2b is not implemented."); + } + + public static void Ram(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Ram is not implemented."); + } + + public static void Rtt(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Rtt is not implemented."); + } + + public static void Sam(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Sam is not implemented."); + } + + public static void Setcrsptr(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Setcrsptr is not implemented."); + } + + public static void Setlmembase(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Setlmembase is not implemented."); + } + + public static void St(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction St is not implemented."); + } + + public static void Stp(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Stp is not implemented."); + } + + public static void Txa(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Txa is not implemented."); + } + + public static void Vabsdiff(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Vabsdiff is not implemented."); + } + + public static void Vabsdiff4(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Vabsdiff4 is not implemented."); + } + + public static void Vadd(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Vadd is not implemented."); + } + + public static void Votevtg(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Votevtg is not implemented."); + } + + public static void Vset(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Vset is not implemented."); + } + + public static void Vshl(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Vshl is not implemented."); + } + + public static void Vshr(EmitterContext context) + { + context.GetOp(); + + context.TranslatorContext.GpuAccessor.Log("Shader instruction Vshr is not implemented."); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs new file mode 100644 index 00000000..4370560d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs @@ -0,0 +1,159 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static class InstEmitAluHelper + { + public static long GetIntMin(IDstFmt type) + { + return type switch + { + IDstFmt.U16 => ushort.MinValue, + IDstFmt.S16 => short.MinValue, + IDstFmt.U32 => uint.MinValue, + IDstFmt.S32 => int.MinValue, + _ => throw new ArgumentException($"The type \"{type}\" is not a supported integer type."), + }; + } + + public static long GetIntMax(IDstFmt type) + { + return type switch + { + IDstFmt.U16 => ushort.MaxValue, + IDstFmt.S16 => short.MaxValue, + IDstFmt.U32 => uint.MaxValue, + IDstFmt.S32 => int.MaxValue, + _ => throw new ArgumentException($"The type \"{type}\" is not a supported integer type."), + }; + } + + public static long GetIntMin(ISrcDstFmt type) + { + return type switch + { + ISrcDstFmt.U8 => byte.MinValue, + ISrcDstFmt.S8 => sbyte.MinValue, + ISrcDstFmt.U16 => ushort.MinValue, + ISrcDstFmt.S16 => short.MinValue, + ISrcDstFmt.U32 => uint.MinValue, + ISrcDstFmt.S32 => int.MinValue, + _ => throw new ArgumentException($"The type \"{type}\" is not a supported integer type."), + }; + } + + public static long GetIntMax(ISrcDstFmt type) + { + return type switch + { + ISrcDstFmt.U8 => byte.MaxValue, + ISrcDstFmt.S8 => sbyte.MaxValue, + ISrcDstFmt.U16 => ushort.MaxValue, + ISrcDstFmt.S16 => short.MaxValue, + ISrcDstFmt.U32 => uint.MaxValue, + ISrcDstFmt.S32 => int.MaxValue, + _ => throw new ArgumentException($"The type \"{type}\" is not a supported integer type."), + }; + } + + public static Operand GetPredLogicalOp(EmitterContext context, BoolOp logicOp, Operand input, Operand pred) + { + return logicOp switch + { + BoolOp.And => context.BitwiseAnd(input, pred), + BoolOp.Or => context.BitwiseOr(input, pred), + BoolOp.Xor => context.BitwiseExclusiveOr(input, pred), + _ => input, + }; + } + + public static Operand Extend(EmitterContext context, Operand src, VectorSelect type) + { + return type switch + { + VectorSelect.U8B0 => ZeroExtendTo32(context, context.ShiftRightU32(src, Const(0)), 8), + VectorSelect.U8B1 => ZeroExtendTo32(context, context.ShiftRightU32(src, Const(8)), 8), + VectorSelect.U8B2 => ZeroExtendTo32(context, context.ShiftRightU32(src, Const(16)), 8), + VectorSelect.U8B3 => ZeroExtendTo32(context, context.ShiftRightU32(src, Const(24)), 8), + VectorSelect.U16H0 => ZeroExtendTo32(context, context.ShiftRightU32(src, Const(0)), 16), + VectorSelect.U16H1 => ZeroExtendTo32(context, context.ShiftRightU32(src, Const(16)), 16), + VectorSelect.S8B0 => SignExtendTo32(context, context.ShiftRightU32(src, Const(0)), 8), + VectorSelect.S8B1 => SignExtendTo32(context, context.ShiftRightU32(src, Const(8)), 8), + VectorSelect.S8B2 => SignExtendTo32(context, context.ShiftRightU32(src, Const(16)), 8), + VectorSelect.S8B3 => SignExtendTo32(context, context.ShiftRightU32(src, Const(24)), 8), + VectorSelect.S16H0 => SignExtendTo32(context, context.ShiftRightU32(src, Const(0)), 16), + VectorSelect.S16H1 => SignExtendTo32(context, context.ShiftRightU32(src, Const(16)), 16), + _ => src, + }; + } + + public static void SetZnFlags(EmitterContext context, Operand dest, bool setCC, bool extended = false) + { + if (!setCC) + { + return; + } + + if (extended) + { + // When the operation is extended, it means we are doing + // the operation on a long word with any number of bits, + // so we need to AND the zero flag from result with the + // previous result when extended is specified, to ensure + // we have ZF set only if all words are zero, and not just + // the last one. + Operand oldZF = GetZF(); + + Operand res = context.BitwiseAnd(context.ICompareEqual(dest, Const(0)), oldZF); + + context.Copy(GetZF(), res); + } + else + { + context.Copy(GetZF(), context.ICompareEqual(dest, Const(0))); + } + + context.Copy(GetNF(), context.ICompareLess(dest, Const(0))); + } + + public static void SetFPZnFlags(EmitterContext context, Operand dest, bool setCC, Instruction fpType = Instruction.FP32) + { + if (setCC) + { + Operand zero = ConstF(0); + + if (fpType == Instruction.FP64) + { + zero = context.FP32ConvertToFP64(zero); + } + + context.Copy(GetZF(), context.FPCompareEqual(dest, zero, fpType)); + context.Copy(GetNF(), context.FPCompareLess(dest, zero, fpType)); + } + } + + public static (Operand, Operand) NegateLong(EmitterContext context, Operand low, Operand high) + { + low = context.BitwiseNot(low); + high = context.BitwiseNot(high); + low = AddWithCarry(context, low, Const(1), out Operand carryOut); + high = context.IAdd(high, carryOut); + return (low, high); + } + + public static Operand AddWithCarry(EmitterContext context, Operand lhs, Operand rhs, out Operand carryOut) + { + Operand result = context.IAdd(lhs, rhs); + + // C = Rd < Rn + carryOut = context.INegate(context.ICompareLessUnsigned(result, lhs)); + + return result; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs new file mode 100644 index 00000000..c704156b --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs @@ -0,0 +1,409 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Al2p(EmitterContext context) + { + InstAl2p op = context.GetOp(); + + context.Copy(GetDest(op.Dest), context.IAdd(GetSrcReg(context, op.SrcA), Const(op.Imm11))); + } + + public static void Ald(EmitterContext context) + { + InstAld op = context.GetOp(); + + // Some of those attributes are per invocation, + // so we should ignore any primitive vertex indexing for those. + bool hasPrimitiveVertex = AttributeMap.HasPrimitiveVertex(context.TranslatorContext.Definitions.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++) + { + Register rd = new(op.Dest + index, RegisterType.Gpr); + + if (rd.IsRZ) + { + break; + } + + if (op.Phys) + { + 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)); + + StorageKind storageKind = op.O ? StorageKind.Output : StorageKind.Input; + + context.Copy(Register(rd), context.Load(storageKind, IoVariable.UserDefined, primVertex, vecIndex, elemIndex)); + } + else if (op.SrcB == RegisterConsts.RegisterZeroIndex || op.P) + { + int offset = FixedFuncToUserAttribute(context.TranslatorContext, op.Imm11 + index * 4, op.O); + bool isOutput = op.O && CanLoadOutput(offset); + + if (!op.P && !isOutput && TryConvertIdToIndexForVulkan(context, offset, out Operand value)) + { + context.Copy(Register(rd), value); + } + else + { + value = AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, op.P); + + if ((!context.TranslatorContext.Definitions.SupportsScaledVertexFormats || context.VertexAsCompute) && + context.TranslatorContext.Stage == ShaderStage.Vertex && + !op.O && + offset >= 0x80 && + offset < 0x280) + { + // The host does not support scaled vertex formats, + // the emulator should use a integer format, and + // we compensate here inserting the conversion to float. + + AttributeType type = context.TranslatorContext.Definitions.GetAttributeType((offset - 0x80) >> 4); + + if (type == AttributeType.Sscaled) + { + value = context.IConvertS32ToFP32(value); + } + else if (type == AttributeType.Uscaled) + { + value = context.IConvertU32ToFP32(value); + } + } + else if (offset == AttributeConsts.PrimitiveId && context.TranslatorContext.Definitions.HalvePrimitiveId) + { + value = context.ShiftRightS32(value, Const(1)); + } + + context.Copy(Register(rd), value); + } + } + else + { + int offset = FixedFuncToUserAttribute(context.TranslatorContext, op.Imm11 + index * 4, op.O); + bool isOutput = op.O && CanLoadOutput(offset); + + context.Copy(Register(rd), AttributeMap.GenerateAttributeLoad(context, primVertex, offset, isOutput, false)); + } + } + } + + public static void Ast(EmitterContext context) + { + InstAst op = context.GetOp(); + + for (int index = 0; index < (int)op.AlSize + 1; index++) + { + if (op.SrcB + index > RegisterConsts.RegisterZeroIndex) + { + break; + } + + Register rd = new(op.SrcB + index, RegisterType.Gpr); + + if (op.Phys) + { + 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.TranslatorContext.Definitions.Stage, isOutput: true) + ? context.Load(StorageKind.Input, IoVariable.InvocationId) + : null; + + context.Store(StorageKind.Output, IoVariable.UserDefined, invocationId, vecIndex, elemIndex, Register(rd)); + } + else + { + // TODO: Support indirect stores using Ra. + + int offset = op.Imm11 + index * 4; + + if (!context.TranslatorContext.AttributeUsage.IsUsedOutputAttribute(offset)) + { + return; + } + + offset = FixedFuncToUserAttribute(context.TranslatorContext, offset, isOutput: true); + AttributeMap.GenerateAttributeStore(context, offset, op.P, Register(rd)); + } + } + } + + public static void Ipa(EmitterContext context) + { + InstIpa op = context.GetOp(); + + Operand res; + + bool isFixedFunc = false; + + if (op.Idx) + { + 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.Load(StorageKind.Input, IoVariable.UserDefined, null, vecIndex, elemIndex); + res = context.FPMultiply(res, context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(3))); + } + else + { + isFixedFunc = TryFixedFuncToUserAttributeIpa(context, op.Imm10, out res); + + if (op.Imm10 >= AttributeConsts.UserAttributeBase && op.Imm10 < AttributeConsts.UserAttributeEnd) + { + int index = (op.Imm10 - AttributeConsts.UserAttributeBase) >> 4; + + if (context.TranslatorContext.Definitions.ImapTypes[index].GetFirstUsedType() == PixelImap.Perspective) + { + 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.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.RenderScale), Const(0))); + + if (op.Imm10 == AttributeConsts.PositionY && context.TranslatorContext.Options.TargetApi != TargetApi.OpenGL) + { + // If YNegate is enabled, we need to flip the fragment coordinates vertically, unless + // the API supports changing the origin (only OpenGL does). + if (context.TranslatorContext.Definitions.YNegateEnabled) + { + Operand viewportHeight = context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.ViewportSize), Const(1)); + + res = context.FPSubtract(viewportHeight, res); + } + } + } + else if (op.Imm10 == AttributeConsts.PrimitiveId && context.TranslatorContext.Definitions.HalvePrimitiveId) + { + // If quads are used, but the host does not support them, they need to be converted to triangles. + // Since each quad becomes 2 triangles, we need to compensate here and divide primitive ID by 2. + res = context.ShiftRightS32(res, Const(1)); + } + else if (op.Imm10 == AttributeConsts.FrontFacing && context.TranslatorContext.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.FP32ConvertToS32(context.ConditionalSelect(res, ConstF(1f), ConstF(0f)))), Const(0)); + } + } + + if (op.IpaOp == IpaOp.Multiply && !isFixedFunc) + { + Operand srcB = GetSrcReg(context, op.SrcB); + + res = context.FPMultiply(res, srcB); + } + + res = context.FPSaturate(res, op.Sat); + + context.Copy(GetDest(op.Dest), res); + } + + public static void Isberd(EmitterContext context) + { + InstIsberd op = context.GetOp(); + + // This instruction performs a load from ISBE (Internal Stage Buffer Entry) memory. + // Here, we just propagate the offset, as the result from this instruction is usually + // used with ALD to perform vertex load on geometry or tessellation shaders. + // The offset is calculated as (PrimitiveIndex * VerticesPerPrimitive) + VertexIndex. + // Since we hardcode PrimitiveIndex to zero, then the offset will be just VertexIndex. + context.Copy(GetDest(op.Dest), GetSrcReg(context, op.SrcA)); + } + + public static void OutR(EmitterContext context) + { + InstOutR op = context.GetOp(); + + EmitOut(context, op.OutType.HasFlag(OutType.Emit), op.OutType.HasFlag(OutType.Cut)); + } + + public static void OutI(EmitterContext context) + { + InstOutI op = context.GetOp(); + + EmitOut(context, op.OutType.HasFlag(OutType.Emit), op.OutType.HasFlag(OutType.Cut)); + } + + public static void OutC(EmitterContext context) + { + InstOutC op = context.GetOp(); + + EmitOut(context, op.OutType.HasFlag(OutType.Emit), op.OutType.HasFlag(OutType.Cut)); + } + + private static void EmitOut(EmitterContext context, bool emit, bool cut) + { + if (!(emit || cut)) + { + context.TranslatorContext.GpuAccessor.Log("Invalid OUT encoding."); + } + + if (emit) + { + if (context.TranslatorContext.Definitions.LastInVertexPipeline) + { + context.PrepareForVertexReturn(out var tempXLocal, out var tempYLocal, out var tempZLocal); + + context.EmitVertex(); + + // Restore output position value before transformation. + + if (tempXLocal != null) + { + context.Copy(context.Load(StorageKind.Input, IoVariable.Position, null, Const(0)), tempXLocal); + } + + if (tempYLocal != null) + { + context.Copy(context.Load(StorageKind.Input, IoVariable.Position, null, Const(1)), tempYLocal); + } + + if (tempZLocal != null) + { + context.Copy(context.Load(StorageKind.Input, IoVariable.Position, null, Const(2)), tempZLocal); + } + } + else + { + context.EmitVertex(); + } + } + + if (cut) + { + context.EndPrimitive(); + } + } + + 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; + } + + private static bool TryFixedFuncToUserAttributeIpa(EmitterContext context, int attr, out Operand selectedAttr) + { + if (attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.BackColorDiffuseR) + { + // TODO: If two sided rendering is enabled, then this should return + // FrontColor if the fragment is front facing, and back color otherwise. + selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.TranslatorContext, 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.TranslatorContext, attr, isOutput: false)); + return true; + } + else if (attr >= AttributeConsts.BackColorDiffuseR && attr < AttributeConsts.ClipDistance0) + { + selectedAttr = ConstF(((attr >> 2) & 3) == 3 ? 1f : 0f); + return true; + } + else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd) + { + selectedAttr = GenerateIpaLoad(context, FixedFuncToUserAttribute(context.TranslatorContext, attr, isOutput: false)); + return true; + } + + 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(TranslatorContext translatorContext, int attr, bool isOutput) + { + bool supportsLayerFromVertexOrTess = translatorContext.GpuAccessor.QueryHostSupportsLayerVertexTessellation(); + int fixedStartAttr = supportsLayerFromVertexOrTess ? 0 : 1; + + if (attr == AttributeConsts.Layer && translatorContext.Definitions.Stage != ShaderStage.Geometry && !supportsLayerFromVertexOrTess) + { + attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.Layer, 0, isOutput); + translatorContext.SetLayerOutputAttribute(attr); + } + else if (attr == AttributeConsts.FogCoord) + { + attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.FogCoord, fixedStartAttr, isOutput); + } + else if (attr >= AttributeConsts.FrontColorDiffuseR && attr < AttributeConsts.ClipDistance0) + { + attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.FrontColorDiffuseR, fixedStartAttr + 1, isOutput); + } + else if (attr >= AttributeConsts.TexCoordBase && attr < AttributeConsts.TexCoordEnd) + { + attr = FixedFuncToUserAttribute(translatorContext, attr, AttributeConsts.TexCoordBase, fixedStartAttr + 5, isOutput); + } + + return attr; + } + + private static int FixedFuncToUserAttribute(TranslatorContext translatorContext, int attr, int baseAttr, int baseIndex, bool isOutput) + { + int index = (attr - baseAttr) >> 4; + int userAttrIndex = translatorContext.AttributeUsage.GetFreeUserAttribute(isOutput, baseIndex + index); + + if ((uint)userAttrIndex < Constants.MaxAttributes) + { + attr = AttributeConsts.UserAttributeBase + userAttrIndex * 16 + (attr & 0xf); + } + else + { + translatorContext.GpuAccessor.Log($"No enough user attributes for fixed attribute offset 0x{attr:X}."); + } + + return attr; + } + + private static bool TryConvertIdToIndexForVulkan(EmitterContext context, int attr, out Operand value) + { + if (context.TranslatorContext.Options.TargetApi == TargetApi.Vulkan) + { + if (attr == AttributeConsts.InstanceId) + { + value = context.ISubtract( + context.Load(StorageKind.Input, IoVariable.InstanceIndex), + context.Load(StorageKind.Input, IoVariable.BaseInstance)); + return true; + } + else if (attr == AttributeConsts.VertexId) + { + value = context.Load(StorageKind.Input, IoVariable.VertexIndex); + return true; + } + } + + value = null; + return false; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitBarrier.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitBarrier.cs new file mode 100644 index 00000000..8061aec2 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitBarrier.cs @@ -0,0 +1,46 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Bar(EmitterContext context) + { + InstBar op = context.GetOp(); + + // TODO: Support other modes. + if (op.BarOp == BarOp.Sync) + { + context.Barrier(); + } + else + { + context.TranslatorContext.GpuAccessor.Log($"Invalid barrier mode: {op.BarOp}."); + } + } + + public static void Depbar(EmitterContext context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + InstDepbar op = context.GetOp(); +#pragma warning restore IDE0059 + + // No operation. + } + + public static void Membar(EmitterContext context) + { + InstMembar op = context.GetOp(); + + if (op.Membar == Decoders.Membar.Cta) + { + context.GroupMemoryBarrier(); + } + else + { + context.MemoryBarrier(); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitBitfield.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitBitfield.cs new file mode 100644 index 00000000..3a841969 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitBitfield.cs @@ -0,0 +1,194 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void BfeR(EmitterContext context) + { + InstBfeR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + + EmitBfe(context, srcA, srcB, op.Dest, op.Brev, op.Signed); + } + + public static void BfeI(EmitterContext context) + { + InstBfeI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + + EmitBfe(context, srcA, srcB, op.Dest, op.Brev, op.Signed); + } + + public static void BfeC(EmitterContext context) + { + InstBfeC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitBfe(context, srcA, srcB, op.Dest, op.Brev, op.Signed); + } + + public static void BfiR(EmitterContext context) + { + InstBfiR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + var srcC = GetSrcReg(context, op.SrcC); + + EmitBfi(context, srcA, srcB, srcC, op.Dest); + } + + public static void BfiI(EmitterContext context) + { + InstBfiI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + var srcC = GetSrcReg(context, op.SrcC); + + EmitBfi(context, srcA, srcB, srcC, op.Dest); + } + + public static void BfiC(EmitterContext context) + { + InstBfiC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + var srcC = GetSrcReg(context, op.SrcC); + + EmitBfi(context, srcA, srcB, srcC, op.Dest); + } + + public static void BfiRc(EmitterContext context) + { + InstBfiRc op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcC); + var srcC = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitBfi(context, srcA, srcB, srcC, op.Dest); + } + + public static void FloR(EmitterContext context) + { + InstFloR op = context.GetOp(); + + EmitFlo(context, GetSrcReg(context, op.SrcB), op.Dest, op.NegB, op.Sh, op.Signed); + } + + public static void FloI(EmitterContext context) + { + InstFloI op = context.GetOp(); + + EmitFlo(context, GetSrcImm(context, Imm20ToSInt(op.Imm20)), op.Dest, op.NegB, op.Sh, op.Signed); + } + + public static void FloC(EmitterContext context) + { + InstFloC op = context.GetOp(); + + EmitFlo(context, GetSrcCbuf(context, op.CbufSlot, op.CbufOffset), op.Dest, op.NegB, op.Sh, op.Signed); + } + + public static void PopcR(EmitterContext context) + { + InstPopcR op = context.GetOp(); + + EmitPopc(context, GetSrcReg(context, op.SrcB), op.Dest, op.NegB); + } + + public static void PopcI(EmitterContext context) + { + InstPopcI op = context.GetOp(); + + EmitPopc(context, GetSrcImm(context, Imm20ToSInt(op.Imm20)), op.Dest, op.NegB); + } + + public static void PopcC(EmitterContext context) + { + InstPopcC op = context.GetOp(); + + EmitPopc(context, GetSrcCbuf(context, op.CbufSlot, op.CbufOffset), op.Dest, op.NegB); + } + + private static void EmitBfe( + EmitterContext context, + Operand srcA, + Operand srcB, + int rd, + bool bitReverse, + bool isSigned) + { + if (bitReverse) + { + srcA = context.BitfieldReverse(srcA); + } + + Operand position = context.BitwiseAnd(srcB, Const(0xff)); + + Operand size = context.BitfieldExtractU32(srcB, Const(8), Const(8)); + + Operand res = isSigned + ? context.BitfieldExtractS32(srcA, position, size) + : context.BitfieldExtractU32(srcA, position, size); + + context.Copy(GetDest(rd), res); + + // TODO: CC, X, corner cases. + } + + private static void EmitBfi(EmitterContext context, Operand srcA, Operand srcB, Operand srcC, int rd) + { + Operand position = context.BitwiseAnd(srcB, Const(0xff)); + + Operand size = context.BitfieldExtractU32(srcB, Const(8), Const(8)); + + Operand res = context.BitfieldInsert(srcC, srcA, position, size); + + context.Copy(GetDest(rd), res); + } + + private static void EmitFlo(EmitterContext context, Operand src, int rd, bool invert, bool sh, bool isSigned) + { + Operand srcB = context.BitwiseNot(src, invert); + + Operand res; + + if (sh) + { + res = context.FindLSB(context.BitfieldReverse(srcB)); + } + else + { + res = isSigned + ? context.FindMSBS32(srcB) + : context.FindMSBU32(srcB); + } + + context.Copy(GetDest(rd), res); + } + + private static void EmitPopc(EmitterContext context, Operand src, int rd, bool invert) + { + Operand srcB = context.BitwiseNot(src, invert); + + Operand res = context.BitCount(srcB); + + context.Copy(GetDest(rd), res); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitConditionCode.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitConditionCode.cs new file mode 100644 index 00000000..b5580a37 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitConditionCode.cs @@ -0,0 +1,86 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Cset(EmitterContext context) + { + InstCset op = context.GetOp(); + + Operand res = GetCondition(context, op.Ccc); + Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + res = GetPredLogicalOp(context, op.Bop, res, srcPred); + + Operand dest = GetDest(op.Dest); + + if (op.BVal) + { + context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0))); + } + else + { + context.Copy(dest, res); + } + + // TODO: CC. + } + + public static void Csetp(EmitterContext context) + { + InstCsetp op = context.GetOp(); + + Operand p0Res = GetCondition(context, op.Ccc); + Operand p1Res = context.BitwiseNot(p0Res); + Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + p0Res = GetPredLogicalOp(context, op.Bop, p0Res, srcPred); + p1Res = GetPredLogicalOp(context, op.Bop, p1Res, srcPred); + + context.Copy(Register(op.DestPred, RegisterType.Predicate), p0Res); + context.Copy(Register(op.DestPredInv, RegisterType.Predicate), p1Res); + + // TODO: CC. + } + + private static Operand GetCondition(EmitterContext context, Ccc cond, int defaultCond = IrConsts.True) + { + return cond switch + { + Ccc.F => Const(IrConsts.False), + Ccc.Lt => context.BitwiseExclusiveOr(context.BitwiseAnd(GetNF(), context.BitwiseNot(GetZF())), GetVF()), + Ccc.Eq => context.BitwiseAnd(context.BitwiseNot(GetNF()), GetZF()), + Ccc.Le => context.BitwiseExclusiveOr(GetNF(), context.BitwiseOr(GetZF(), GetVF())), + Ccc.Gt => context.BitwiseNot(context.BitwiseOr(context.BitwiseExclusiveOr(GetNF(), GetVF()), GetZF())), + Ccc.Ne => context.BitwiseNot(GetZF()), + Ccc.Ge => context.BitwiseNot(context.BitwiseExclusiveOr(GetNF(), GetVF())), + Ccc.Num => context.BitwiseNot(context.BitwiseAnd(GetNF(), GetZF())), + Ccc.Nan => context.BitwiseAnd(GetNF(), GetZF()), + Ccc.Ltu => context.BitwiseExclusiveOr(GetNF(), GetVF()), + Ccc.Equ => GetZF(), + Ccc.Leu => context.BitwiseOr(context.BitwiseExclusiveOr(GetNF(), GetVF()), GetZF()), + Ccc.Gtu => context.BitwiseExclusiveOr(context.BitwiseNot(GetNF()), context.BitwiseOr(GetVF(), GetZF())), + Ccc.Neu => context.BitwiseOr(GetNF(), context.BitwiseNot(GetZF())), + Ccc.Geu => context.BitwiseExclusiveOr(context.BitwiseOr(context.BitwiseNot(GetNF()), GetZF()), GetVF()), + Ccc.T => Const(IrConsts.True), + Ccc.Off => context.BitwiseNot(GetVF()), + Ccc.Lo => context.BitwiseNot(GetCF()), + Ccc.Sff => context.BitwiseNot(GetNF()), + Ccc.Ls => context.BitwiseOr(GetZF(), context.BitwiseNot(GetCF())), + Ccc.Hi => context.BitwiseAnd(GetCF(), context.BitwiseNot(GetZF())), + Ccc.Sft => GetNF(), + Ccc.Hs => GetCF(), + Ccc.Oft => GetVF(), + Ccc.Rle => context.BitwiseOr(GetNF(), GetZF()), + Ccc.Rgt => context.BitwiseNot(context.BitwiseOr(GetNF(), GetZF())), + _ => Const(defaultCond), + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs new file mode 100644 index 00000000..e7e0fba9 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs @@ -0,0 +1,424 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void F2fR(EmitterContext context) + { + InstF2fR op = context.GetOp(); + + var src = UnpackReg(context, op.SrcFmt, op.Sh, op.SrcB); + + EmitF2F(context, op.SrcFmt, op.DstFmt, op.RoundMode, src, op.Dest, op.AbsB, op.NegB, op.Sat); + } + + public static void F2fI(EmitterContext context) + { + InstF2fI op = context.GetOp(); + + var src = UnpackImm(context, op.SrcFmt, op.Sh, Imm20ToFloat(op.Imm20)); + + EmitF2F(context, op.SrcFmt, op.DstFmt, op.RoundMode, src, op.Dest, op.AbsB, op.NegB, op.Sat); + } + + public static void F2fC(EmitterContext context) + { + InstF2fC op = context.GetOp(); + + var src = UnpackCbuf(context, op.SrcFmt, op.Sh, op.CbufSlot, op.CbufOffset); + + EmitF2F(context, op.SrcFmt, op.DstFmt, op.RoundMode, src, op.Dest, op.AbsB, op.NegB, op.Sat); + } + + public static void F2iR(EmitterContext context) + { + InstF2iR op = context.GetOp(); + + var src = UnpackReg(context, op.SrcFmt, op.Sh, op.SrcB); + + EmitF2I(context, op.SrcFmt, op.IDstFmt, op.RoundMode, src, op.Dest, op.AbsB, op.NegB); + } + + public static void F2iI(EmitterContext context) + { + InstF2iI op = context.GetOp(); + + var src = UnpackImm(context, op.SrcFmt, op.Sh, Imm20ToFloat(op.Imm20)); + + EmitF2I(context, op.SrcFmt, op.IDstFmt, op.RoundMode, src, op.Dest, op.AbsB, op.NegB); + } + + public static void F2iC(EmitterContext context) + { + InstF2iC op = context.GetOp(); + + var src = UnpackCbuf(context, op.SrcFmt, op.Sh, op.CbufSlot, op.CbufOffset); + + EmitF2I(context, op.SrcFmt, op.IDstFmt, op.RoundMode, src, op.Dest, op.AbsB, op.NegB); + } + + public static void I2fR(EmitterContext context) + { + InstI2fR op = context.GetOp(); + + var src = GetSrcReg(context, op.SrcB); + + EmitI2F(context, op.ISrcFmt, op.DstFmt, src, op.ByteSel, op.Dest, op.AbsB, op.NegB); + } + + public static void I2fI(EmitterContext context) + { + InstI2fI op = context.GetOp(); + + var src = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + + EmitI2F(context, op.ISrcFmt, op.DstFmt, src, op.ByteSel, op.Dest, op.AbsB, op.NegB); + } + + public static void I2fC(EmitterContext context) + { + InstI2fC op = context.GetOp(); + + var src = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitI2F(context, op.ISrcFmt, op.DstFmt, src, op.ByteSel, op.Dest, op.AbsB, op.NegB); + } + + public static void I2iR(EmitterContext context) + { + InstI2iR op = context.GetOp(); + + var src = GetSrcReg(context, op.SrcB); + + EmitI2I(context, op.ISrcFmt, op.IDstFmt, src, op.ByteSel, op.Dest, op.AbsB, op.NegB, op.Sat, op.WriteCC); + } + + public static void I2iI(EmitterContext context) + { + InstI2iI op = context.GetOp(); + + var src = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + + EmitI2I(context, op.ISrcFmt, op.IDstFmt, src, op.ByteSel, op.Dest, op.AbsB, op.NegB, op.Sat, op.WriteCC); + } + + public static void I2iC(EmitterContext context) + { + InstI2iC op = context.GetOp(); + + var src = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitI2I(context, op.ISrcFmt, op.IDstFmt, src, op.ByteSel, op.Dest, op.AbsB, op.NegB, op.Sat, op.WriteCC); + } + + private static void EmitF2F( + EmitterContext context, + DstFmt srcType, + DstFmt dstType, + IntegerRound roundingMode, + Operand src, + int rd, + bool absolute, + bool negate, + bool saturate) + { + Operand srcB = context.FPAbsNeg(src, absolute, negate, srcType.ToInstFPType()); + + if (srcType == dstType) + { + srcB = roundingMode switch + { + IntegerRound.Round => context.FPRound(srcB, srcType.ToInstFPType()), + IntegerRound.Floor => context.FPFloor(srcB, srcType.ToInstFPType()), + IntegerRound.Ceil => context.FPCeiling(srcB, srcType.ToInstFPType()), + IntegerRound.Trunc => context.FPTruncate(srcB, srcType.ToInstFPType()), + _ => srcB, + }; + } + + // We don't need to handle conversions between FP16 <-> FP32 + // since we do FP16 operations as FP32 directly. + // FP16 <-> FP64 conversions are invalid. + if (srcType == DstFmt.F32 && dstType == DstFmt.F64) + { + srcB = context.FP32ConvertToFP64(srcB); + } + else if (srcType == DstFmt.F64 && dstType == DstFmt.F32) + { + srcB = context.FP64ConvertToFP32(srcB); + } + + srcB = context.FPSaturate(srcB, saturate, dstType.ToInstFPType()); + + WriteFP(context, dstType, srcB, rd); + + // TODO: CC. + } + + private static void EmitF2I( + EmitterContext context, + DstFmt srcType, + IDstFmt dstType, + RoundMode2 roundingMode, + Operand src, + int rd, + bool absolute, + bool negate) + { + if (dstType == IDstFmt.U64) + { + context.TranslatorContext.GpuAccessor.Log("Unimplemented 64-bits F2I."); + } + + Instruction fpType = srcType.ToInstFPType(); + + bool isSignedInt = dstType == IDstFmt.S16 || dstType == IDstFmt.S32 || dstType == IDstFmt.S64; + bool isSmallInt = dstType == IDstFmt.U16 || dstType == IDstFmt.S16; + + Operand srcB = context.FPAbsNeg(src, absolute, negate, fpType); + + srcB = roundingMode switch + { + RoundMode2.Round => context.FPRound(srcB, fpType), + RoundMode2.Floor => context.FPFloor(srcB, fpType), + RoundMode2.Ceil => context.FPCeiling(srcB, fpType), + RoundMode2.Trunc => context.FPTruncate(srcB, fpType), + _ => srcB, + }; + + if (!isSignedInt) + { + // Negative float to uint cast is undefined, so we clamp the value before conversion. + Operand c0 = srcType == DstFmt.F64 ? context.PackDouble2x32(0.0) : ConstF(0); + + srcB = context.FPMaximum(srcB, c0, fpType); + } + + if (srcType == DstFmt.F64) + { + srcB = isSignedInt + ? context.FP64ConvertToS32(srcB) + : context.FP64ConvertToU32(srcB); + } + else + { + srcB = isSignedInt + ? context.FP32ConvertToS32(srcB) + : context.FP32ConvertToU32(srcB); + } + + if (isSmallInt) + { + int min = (int)GetIntMin(dstType); + int max = (int)GetIntMax(dstType); + + srcB = isSignedInt + ? context.IClampS32(srcB, Const(min), Const(max)) + : context.IClampU32(srcB, Const(min), Const(max)); + } + + Operand dest = GetDest(rd); + + context.Copy(dest, srcB); + + // TODO: CC. + } + + private static void EmitI2F( + EmitterContext context, + ISrcFmt srcType, + DstFmt dstType, + Operand src, + ByteSel byteSelection, + int rd, + bool absolute, + bool negate) + { + bool isSignedInt = + srcType == ISrcFmt.S8 || + srcType == ISrcFmt.S16 || + srcType == ISrcFmt.S32 || + srcType == ISrcFmt.S64; + bool isSmallInt = + srcType == ISrcFmt.U16 || + srcType == ISrcFmt.S16 || + srcType == ISrcFmt.U8 || + srcType == ISrcFmt.S8; + + // TODO: Handle S/U64. + + Operand srcB = context.IAbsNeg(src, absolute, negate); + + if (isSmallInt) + { + int size = srcType == ISrcFmt.U16 || srcType == ISrcFmt.S16 ? 16 : 8; + + srcB = isSignedInt + ? context.BitfieldExtractS32(srcB, Const((int)byteSelection * 8), Const(size)) + : context.BitfieldExtractU32(srcB, Const((int)byteSelection * 8), Const(size)); + } + + if (dstType == DstFmt.F64) + { + srcB = isSignedInt + ? context.IConvertS32ToFP64(srcB) + : context.IConvertU32ToFP64(srcB); + } + else + { + srcB = isSignedInt + ? context.IConvertS32ToFP32(srcB) + : context.IConvertU32ToFP32(srcB); + } + + WriteFP(context, dstType, srcB, rd); + + // TODO: CC. + } + + private static void EmitI2I( + EmitterContext context, + ISrcDstFmt srcType, + ISrcDstFmt dstType, + Operand src, + ByteSel byteSelection, + int rd, + bool absolute, + bool negate, + bool saturate, + bool writeCC) + { + if ((srcType & ~ISrcDstFmt.S8) > ISrcDstFmt.U32 || (dstType & ~ISrcDstFmt.S8) > ISrcDstFmt.U32) + { + context.TranslatorContext.GpuAccessor.Log("Invalid I2I encoding."); + return; + } + + bool srcIsSignedInt = + srcType == ISrcDstFmt.S8 || + srcType == ISrcDstFmt.S16 || + srcType == ISrcDstFmt.S32; + bool dstIsSignedInt = + dstType == ISrcDstFmt.S8 || + dstType == ISrcDstFmt.S16 || + dstType == ISrcDstFmt.S32; + bool srcIsSmallInt = + srcType == ISrcDstFmt.U16 || + srcType == ISrcDstFmt.S16 || + srcType == ISrcDstFmt.U8 || + srcType == ISrcDstFmt.S8; + + if (srcIsSmallInt) + { + int size = srcType == ISrcDstFmt.U16 || srcType == ISrcDstFmt.S16 ? 16 : 8; + + src = srcIsSignedInt + ? context.BitfieldExtractS32(src, Const((int)byteSelection * 8), Const(size)) + : context.BitfieldExtractU32(src, Const((int)byteSelection * 8), Const(size)); + } + + src = context.IAbsNeg(src, absolute, negate); + + if (saturate) + { + int min = (int)GetIntMin(dstType); + int max = (int)GetIntMax(dstType); + + src = dstIsSignedInt + ? context.IClampS32(src, Const(min), Const(max)) + : context.IClampU32(src, Const(min), Const(max)); + } + + context.Copy(GetDest(rd), src); + + SetZnFlags(context, src, writeCC); + } + + private static Operand UnpackReg(EmitterContext context, DstFmt floatType, bool h, int reg) + { + if (floatType == DstFmt.F32) + { + return GetSrcReg(context, reg); + } + else if (floatType == DstFmt.F16) + { + return GetHalfUnpacked(context, GetSrcReg(context, reg), HalfSwizzle.F16)[h ? 1 : 0]; + } + else if (floatType == DstFmt.F64) + { + return GetSrcReg(context, reg, isFP64: true); + } + + throw new ArgumentException($"Invalid floating point type \"{floatType}\"."); + } + + private static Operand UnpackCbuf(EmitterContext context, DstFmt floatType, bool h, int cbufSlot, int cbufOffset) + { + if (floatType == DstFmt.F32) + { + return GetSrcCbuf(context, cbufSlot, cbufOffset); + } + else if (floatType == DstFmt.F16) + { + return GetHalfUnpacked(context, GetSrcCbuf(context, cbufSlot, cbufOffset), HalfSwizzle.F16)[h ? 1 : 0]; + } + else if (floatType == DstFmt.F64) + { + return GetSrcCbuf(context, cbufSlot, cbufOffset, isFP64: true); + } + + throw new ArgumentException($"Invalid floating point type \"{floatType}\"."); + } + + private static Operand UnpackImm(EmitterContext context, DstFmt floatType, bool h, int imm) + { + if (floatType == DstFmt.F32) + { + return GetSrcImm(context, imm); + } + else if (floatType == DstFmt.F16) + { + return GetHalfUnpacked(context, GetSrcImm(context, imm), HalfSwizzle.F16)[h ? 1 : 0]; + } + else if (floatType == DstFmt.F64) + { + return GetSrcImm(context, imm, isFP64: true); + } + + throw new ArgumentException($"Invalid floating point type \"{floatType}\"."); + } + + private static void WriteFP(EmitterContext context, DstFmt type, Operand srcB, int rd) + { + Operand dest = GetDest(rd); + + if (type == DstFmt.F32) + { + context.Copy(dest, srcB); + } + else if (type == DstFmt.F16) + { + context.Copy(dest, context.PackHalf2x16(srcB, ConstF(0))); + } + else /* if (type == FPType.FP64) */ + { + Operand dest2 = GetDest2(rd); + + context.Copy(dest, context.UnpackDouble2x32Low(srcB)); + context.Copy(dest2, context.UnpackDouble2x32High(srcB)); + } + } + + private static Instruction ToInstFPType(this DstFmt type) + { + return type == DstFmt.F64 ? Instruction.FP64 : Instruction.FP32; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatArithmetic.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatArithmetic.cs new file mode 100644 index 00000000..04dbd20e --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatArithmetic.cs @@ -0,0 +1,531 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void DaddR(EmitterContext context) + { + InstDaddR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcReg(context, op.SrcB, isFP64: true); + + EmitFadd(context, Instruction.FP64, srcA, srcB, op.Dest, op.NegA, op.NegB, op.AbsA, op.AbsB, false, op.WriteCC); + } + + public static void DaddI(EmitterContext context) + { + InstDaddI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20), isFP64: true); + + EmitFadd(context, Instruction.FP64, srcA, srcB, op.Dest, op.NegA, op.NegB, op.AbsA, op.AbsB, false, op.WriteCC); + } + + public static void DaddC(EmitterContext context) + { + InstDaddC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset, isFP64: true); + + EmitFadd(context, Instruction.FP64, srcA, srcB, op.Dest, op.NegA, op.NegB, op.AbsA, op.AbsB, false, op.WriteCC); + } + + public static void DfmaR(EmitterContext context) + { + InstDfmaR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcReg(context, op.SrcB, isFP64: true); + var srcC = GetSrcReg(context, op.SrcC, isFP64: true); + + EmitFfma(context, Instruction.FP64, srcA, srcB, srcC, op.Dest, op.NegA, op.NegC, false, op.WriteCC); + } + + public static void DfmaI(EmitterContext context) + { + InstDfmaI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20), isFP64: true); + var srcC = GetSrcReg(context, op.SrcC, isFP64: true); + + EmitFfma(context, Instruction.FP64, srcA, srcB, srcC, op.Dest, op.NegA, op.NegC, false, op.WriteCC); + } + + public static void DfmaC(EmitterContext context) + { + InstDfmaC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset, isFP64: true); + var srcC = GetSrcReg(context, op.SrcC, isFP64: true); + + EmitFfma(context, Instruction.FP64, srcA, srcB, srcC, op.Dest, op.NegA, op.NegC, false, op.WriteCC); + } + + public static void DfmaRc(EmitterContext context) + { + InstDfmaRc op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcReg(context, op.SrcC, isFP64: true); + var srcC = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset, isFP64: true); + + EmitFfma(context, Instruction.FP64, srcA, srcB, srcC, op.Dest, op.NegA, op.NegC, false, op.WriteCC); + } + + public static void DmulR(EmitterContext context) + { + InstDmulR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcReg(context, op.SrcB, isFP64: true); + + EmitFmul(context, Instruction.FP64, MultiplyScale.NoScale, srcA, srcB, op.Dest, op.NegA, false, op.WriteCC); + } + + public static void DmulI(EmitterContext context) + { + InstDmulI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20), isFP64: true); + + EmitFmul(context, Instruction.FP64, MultiplyScale.NoScale, srcA, srcB, op.Dest, op.NegA, false, op.WriteCC); + } + + public static void DmulC(EmitterContext context) + { + InstDmulC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset, isFP64: true); + + EmitFmul(context, Instruction.FP64, MultiplyScale.NoScale, srcA, srcB, op.Dest, op.NegA, false, op.WriteCC); + } + + public static void FaddR(EmitterContext context) + { + InstFaddR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + + EmitFadd(context, Instruction.FP32, srcA, srcB, op.Dest, op.NegA, op.NegB, op.AbsA, op.AbsB, op.Sat, op.WriteCC); + } + + public static void FaddI(EmitterContext context) + { + InstFaddI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20)); + + EmitFadd(context, Instruction.FP32, srcA, srcB, op.Dest, op.NegA, op.NegB, op.AbsA, op.AbsB, op.Sat, op.WriteCC); + } + + public static void FaddC(EmitterContext context) + { + InstFaddC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitFadd(context, Instruction.FP32, srcA, srcB, op.Dest, op.NegA, op.NegB, op.AbsA, op.AbsB, op.Sat, op.WriteCC); + } + + public static void Fadd32i(EmitterContext context) + { + InstFadd32i op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, op.Imm32); + + EmitFadd(context, Instruction.FP32, srcA, srcB, op.Dest, op.NegA, op.NegB, op.AbsA, op.AbsB, false, op.WriteCC); + } + + public static void FfmaR(EmitterContext context) + { + InstFfmaR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + var srcC = GetSrcReg(context, op.SrcC); + + EmitFfma(context, Instruction.FP32, srcA, srcB, srcC, op.Dest, op.NegA, op.NegC, op.Sat, op.WriteCC); + } + + public static void FfmaI(EmitterContext context) + { + InstFfmaI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20)); + var srcC = GetSrcReg(context, op.SrcC); + + EmitFfma(context, Instruction.FP32, srcA, srcB, srcC, op.Dest, op.NegA, op.NegC, op.Sat, op.WriteCC); + } + + public static void FfmaC(EmitterContext context) + { + InstFfmaC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + var srcC = GetSrcReg(context, op.SrcC); + + EmitFfma(context, Instruction.FP32, srcA, srcB, srcC, op.Dest, op.NegA, op.NegC, op.Sat, op.WriteCC); + } + + public static void FfmaRc(EmitterContext context) + { + InstFfmaRc op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcC); + var srcC = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitFfma(context, Instruction.FP32, srcA, srcB, srcC, op.Dest, op.NegA, op.NegC, op.Sat, op.WriteCC); + } + + public static void Ffma32i(EmitterContext context) + { + InstFfma32i op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, op.Imm32); + var srcC = GetSrcReg(context, op.Dest); + + EmitFfma(context, Instruction.FP32, srcA, srcB, srcC, op.Dest, op.NegA, op.NegC, op.Sat, op.WriteCC); + } + + public static void FmulR(EmitterContext context) + { + InstFmulR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + + EmitFmul(context, Instruction.FP32, op.Scale, srcA, srcB, op.Dest, op.NegA, op.Sat, op.WriteCC); + } + + public static void FmulI(EmitterContext context) + { + InstFmulI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20)); + + EmitFmul(context, Instruction.FP32, op.Scale, srcA, srcB, op.Dest, op.NegA, op.Sat, op.WriteCC); + } + + public static void FmulC(EmitterContext context) + { + InstFmulC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitFmul(context, Instruction.FP32, op.Scale, srcA, srcB, op.Dest, op.NegA, op.Sat, op.WriteCC); + } + + public static void Fmul32i(EmitterContext context) + { + InstFmul32i op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, op.Imm32); + + EmitFmul(context, Instruction.FP32, MultiplyScale.NoScale, srcA, srcB, op.Dest, false, op.Sat, op.WriteCC); + } + + public static void Hadd2R(EmitterContext context) + { + InstHadd2R op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, op.NegA, op.AbsA); + var srcB = GetHalfSrc(context, op.BSwizzle, op.SrcB, op.NegB, op.AbsB); + + EmitHadd2Hmul2(context, op.OFmt, srcA, srcB, isAdd: true, op.Dest, op.Sat); + } + + public static void Hadd2I(EmitterContext context) + { + InstHadd2I op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, op.NegA, op.AbsA); + var srcB = GetHalfSrc(context, op.BimmH0, op.BimmH1); + + EmitHadd2Hmul2(context, op.OFmt, srcA, srcB, isAdd: true, op.Dest, op.Sat); + } + + public static void Hadd2C(EmitterContext context) + { + InstHadd2C op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, op.NegA, op.AbsA); + var srcB = GetHalfSrc(context, HalfSwizzle.F32, op.CbufSlot, op.CbufOffset, op.NegB, op.AbsB); + + EmitHadd2Hmul2(context, op.OFmt, srcA, srcB, isAdd: true, op.Dest, op.Sat); + } + + public static void Hadd232i(EmitterContext context) + { + InstHadd232i op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, op.NegA, false); + var srcB = GetHalfSrc(context, op.Imm); + + EmitHadd2Hmul2(context, OFmt.F16, srcA, srcB, isAdd: true, op.Dest, op.Sat); + } + + public static void Hfma2R(EmitterContext context) + { + InstHfma2R op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, false, false); + var srcB = GetHalfSrc(context, op.BSwizzle, op.SrcB, op.NegA, false); + var srcC = GetHalfSrc(context, op.CSwizzle, op.SrcC, op.NegC, false); + + EmitHfma2(context, op.OFmt, srcA, srcB, srcC, op.Dest, op.Sat); + } + + public static void Hfma2I(EmitterContext context) + { + InstHfma2I op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, false, false); + var srcB = GetHalfSrc(context, op.BimmH0, op.BimmH1); + var srcC = GetHalfSrc(context, op.CSwizzle, op.SrcC, op.NegC, false); + + EmitHfma2(context, op.OFmt, srcA, srcB, srcC, op.Dest, op.Sat); + } + + public static void Hfma2C(EmitterContext context) + { + InstHfma2C op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, false, false); + var srcB = GetHalfSrc(context, HalfSwizzle.F32, op.CbufSlot, op.CbufOffset, op.NegA, false); + var srcC = GetHalfSrc(context, op.CSwizzle, op.SrcC, op.NegC, false); + + EmitHfma2(context, op.OFmt, srcA, srcB, srcC, op.Dest, op.Sat); + } + + public static void Hfma2Rc(EmitterContext context) + { + InstHfma2Rc op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, false, false); + var srcB = GetHalfSrc(context, op.CSwizzle, op.SrcC, op.NegA, false); + var srcC = GetHalfSrc(context, HalfSwizzle.F32, op.CbufSlot, op.CbufOffset, op.NegC, false); + + EmitHfma2(context, op.OFmt, srcA, srcB, srcC, op.Dest, op.Sat); + } + + public static void Hfma232i(EmitterContext context) + { + InstHfma232i op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, false, false); + var srcB = GetHalfSrc(context, op.Imm); + var srcC = GetHalfSrc(context, HalfSwizzle.F16, op.Dest, op.NegC, false); + + EmitHfma2(context, OFmt.F16, srcA, srcB, srcC, op.Dest, saturate: false); + } + + public static void Hmul2R(EmitterContext context) + { + InstHmul2R op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, false, op.AbsA); + var srcB = GetHalfSrc(context, op.BSwizzle, op.SrcB, op.NegA, op.AbsB); + + EmitHadd2Hmul2(context, op.OFmt, srcA, srcB, isAdd: false, op.Dest, op.Sat); + } + + public static void Hmul2I(EmitterContext context) + { + InstHmul2I op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, op.NegA, op.AbsA); + var srcB = GetHalfSrc(context, op.BimmH0, op.BimmH1); + + EmitHadd2Hmul2(context, op.OFmt, srcA, srcB, isAdd: false, op.Dest, op.Sat); + } + + public static void Hmul2C(EmitterContext context) + { + InstHmul2C op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, false, op.AbsA); + var srcB = GetHalfSrc(context, HalfSwizzle.F32, op.CbufSlot, op.CbufOffset, op.NegA, op.AbsB); + + EmitHadd2Hmul2(context, op.OFmt, srcA, srcB, isAdd: false, op.Dest, op.Sat); + } + + public static void Hmul232i(EmitterContext context) + { + InstHmul232i op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, false, false); + var srcB = GetHalfSrc(context, op.Imm32); + + EmitHadd2Hmul2(context, OFmt.F16, srcA, srcB, isAdd: false, op.Dest, op.Sat); + } + + private static void EmitFadd( + EmitterContext context, + Instruction fpType, + Operand srcA, + Operand srcB, + int rd, + bool negateA, + bool negateB, + bool absoluteA, + bool absoluteB, + bool saturate, + bool writeCC) + { + bool isFP64 = fpType == Instruction.FP64; + + srcA = context.FPAbsNeg(srcA, absoluteA, negateA, fpType); + srcB = context.FPAbsNeg(srcB, absoluteB, negateB, fpType); + + Operand res = context.FPSaturate(context.FPAdd(srcA, srcB, fpType), saturate, fpType); + + SetDest(context, res, rd, isFP64); + + SetFPZnFlags(context, res, writeCC, fpType); + } + + private static void EmitFfma( + EmitterContext context, + Instruction fpType, + Operand srcA, + Operand srcB, + Operand srcC, + int rd, + bool negateB, + bool negateC, + bool saturate, + bool writeCC) + { + bool isFP64 = fpType == Instruction.FP64; + + srcB = context.FPNegate(srcB, negateB, fpType); + srcC = context.FPNegate(srcC, negateC, fpType); + + Operand res = context.FPSaturate(context.FPFusedMultiplyAdd(srcA, srcB, srcC, fpType), saturate, fpType); + + SetDest(context, res, rd, isFP64); + + SetFPZnFlags(context, res, writeCC, fpType); + } + + private static void EmitFmul( + EmitterContext context, + Instruction fpType, + MultiplyScale scale, + Operand srcA, + Operand srcB, + int rd, + bool negateB, + bool saturate, + bool writeCC) + { + bool isFP64 = fpType == Instruction.FP64; + + srcB = context.FPNegate(srcB, negateB, fpType); + + if (scale != MultiplyScale.NoScale) + { + Operand scaleConst = scale switch + { + MultiplyScale.D2 => ConstF(0.5f), + MultiplyScale.D4 => ConstF(0.25f), + MultiplyScale.D8 => ConstF(0.125f), + MultiplyScale.M2 => ConstF(2f), + MultiplyScale.M4 => ConstF(4f), + MultiplyScale.M8 => ConstF(8f), + _ => ConstF(1f), // Invalid, behave as if it had no scale. + }; + + if (scaleConst.AsFloat() == 1f) + { + context.TranslatorContext.GpuAccessor.Log($"Invalid FP multiply scale \"{scale}\"."); + } + + if (isFP64) + { + scaleConst = context.FP32ConvertToFP64(scaleConst); + } + + srcA = context.FPMultiply(srcA, scaleConst, fpType); + } + + Operand res = context.FPSaturate(context.FPMultiply(srcA, srcB, fpType), saturate, fpType); + + SetDest(context, res, rd, isFP64); + + SetFPZnFlags(context, res, writeCC, fpType); + } + + private static void EmitHadd2Hmul2( + EmitterContext context, + OFmt swizzle, + Operand[] srcA, + Operand[] srcB, + bool isAdd, + int rd, + bool saturate) + { + Operand[] res = new Operand[2]; + + for (int index = 0; index < res.Length; index++) + { + if (isAdd) + { + res[index] = context.FPAdd(srcA[index], srcB[index]); + } + else + { + res[index] = context.FPMultiply(srcA[index], srcB[index]); + } + + res[index] = context.FPSaturate(res[index], saturate); + } + + context.Copy(GetDest(rd), GetHalfPacked(context, swizzle, res, rd)); + } + + public static void EmitHfma2( + EmitterContext context, + OFmt swizzle, + Operand[] srcA, + Operand[] srcB, + Operand[] srcC, + int rd, + bool saturate) + { + Operand[] res = new Operand[2]; + + for (int index = 0; index < res.Length; index++) + { + res[index] = context.FPFusedMultiplyAdd(srcA[index], srcB[index], srcC[index]); + res[index] = context.FPSaturate(res[index], saturate); + } + + context.Copy(GetDest(rd), GetHalfPacked(context, swizzle, res, rd)); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatComparison.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatComparison.cs new file mode 100644 index 00000000..59ad7a5d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatComparison.cs @@ -0,0 +1,570 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void DsetR(EmitterContext context) + { + InstDsetR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcReg(context, op.SrcB, isFP64: true); + + EmitFset( + context, + op.FComp, + op.Bop, + srcA, + srcB, + op.SrcPred, + op.SrcPredInv, + op.Dest, + op.AbsA, + op.AbsB, + op.NegA, + op.NegB, + op.BVal, + op.WriteCC, + isFP64: true); + } + + public static void DsetI(EmitterContext context) + { + InstDsetI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20), isFP64: true); + + EmitFset( + context, + op.FComp, + op.Bop, + srcA, + srcB, + op.SrcPred, + op.SrcPredInv, + op.Dest, + op.AbsA, + op.AbsB, + op.NegA, + op.NegB, + op.BVal, + op.WriteCC, + isFP64: true); + } + + public static void DsetC(EmitterContext context) + { + InstDsetC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset, isFP64: true); + + EmitFset( + context, + op.FComp, + op.Bop, + srcA, + srcB, + op.SrcPred, + op.SrcPredInv, + op.Dest, + op.AbsA, + op.AbsB, + op.NegA, + op.NegB, + op.BVal, + op.WriteCC, + isFP64: true); + } + + public static void DsetpR(EmitterContext context) + { + InstDsetpR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcReg(context, op.SrcB, isFP64: true); + + EmitFsetp( + context, + op.FComp, + op.Bop, + srcA, + srcB, + op.SrcPred, + op.SrcPredInv, + op.DestPred, + op.DestPredInv, + op.AbsA, + op.AbsB, + op.NegA, + op.NegB, + writeCC: false, + isFP64: true); + } + + public static void DsetpI(EmitterContext context) + { + InstDsetpI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20), isFP64: true); + + EmitFsetp( + context, + op.FComp, + op.Bop, + srcA, + srcB, + op.SrcPred, + op.SrcPredInv, + op.DestPred, + op.DestPredInv, + op.AbsA, + op.AbsB, + op.NegA, + op.NegB, + writeCC: false, + isFP64: true); + } + + public static void DsetpC(EmitterContext context) + { + InstDsetpC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset, isFP64: true); + + EmitFsetp( + context, + op.FComp, + op.Bop, + srcA, + srcB, + op.SrcPred, + op.SrcPredInv, + op.DestPred, + op.DestPredInv, + op.AbsA, + op.AbsB, + op.NegA, + op.NegB, + writeCC: false, + isFP64: true); + } + + public static void FcmpR(EmitterContext context) + { + InstFcmpR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + var srcC = GetSrcReg(context, op.SrcC); + + EmitFcmp(context, op.FComp, srcA, srcB, srcC, op.Dest); + } + + public static void FcmpI(EmitterContext context) + { + InstFcmpI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20)); + var srcC = GetSrcReg(context, op.SrcC); + + EmitFcmp(context, op.FComp, srcA, srcB, srcC, op.Dest); + } + + public static void FcmpC(EmitterContext context) + { + InstFcmpC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + var srcC = GetSrcReg(context, op.SrcC); + + EmitFcmp(context, op.FComp, srcA, srcB, srcC, op.Dest); + } + + public static void FcmpRc(EmitterContext context) + { + InstFcmpRc op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcC); + var srcC = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitFcmp(context, op.FComp, srcA, srcB, srcC, op.Dest); + } + + public static void FsetR(EmitterContext context) + { + InstFsetR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + + EmitFset(context, op.FComp, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.Dest, op.AbsA, op.AbsB, op.NegA, op.NegB, op.BVal, op.WriteCC); + } + + public static void FsetC(EmitterContext context) + { + InstFsetC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitFset(context, op.FComp, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.Dest, op.AbsA, op.AbsB, op.NegA, op.NegB, op.BVal, op.WriteCC); + } + + public static void FsetI(EmitterContext context) + { + InstFsetI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20)); + + EmitFset(context, op.FComp, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.Dest, op.AbsA, op.AbsB, op.NegA, op.NegB, op.BVal, op.WriteCC); + } + + public static void FsetpR(EmitterContext context) + { + InstFsetpR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + + EmitFsetp( + context, + op.FComp, + op.Bop, + srcA, + srcB, + op.SrcPred, + op.SrcPredInv, + op.DestPred, + op.DestPredInv, + op.AbsA, + op.AbsB, + op.NegA, + op.NegB, + op.WriteCC); + } + + public static void FsetpI(EmitterContext context) + { + InstFsetpI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20)); + + EmitFsetp( + context, + op.FComp, + op.Bop, + srcA, + srcB, + op.SrcPred, + op.SrcPredInv, + op.DestPred, + op.DestPredInv, + op.AbsA, + op.AbsB, + op.NegA, + op.NegB, + op.WriteCC); + } + + public static void FsetpC(EmitterContext context) + { + InstFsetpC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitFsetp( + context, + op.FComp, + op.Bop, + srcA, + srcB, + op.SrcPred, + op.SrcPredInv, + op.DestPred, + op.DestPredInv, + op.AbsA, + op.AbsB, + op.NegA, + op.NegB, + op.WriteCC); + } + + public static void Hset2R(EmitterContext context) + { + InstHset2R op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, op.NegA, op.AbsA); + var srcB = GetHalfSrc(context, op.BSwizzle, op.SrcB, op.NegB, op.AbsB); + + EmitHset2(context, op.Cmp, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.Dest, op.Bval); + } + + public static void Hset2I(EmitterContext context) + { + InstHset2I op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, op.NegA, op.AbsA); + var srcB = GetHalfSrc(context, op.BimmH0, op.BimmH1); + + EmitHset2(context, op.Cmp, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.Dest, op.Bval); + } + + public static void Hset2C(EmitterContext context) + { + InstHset2C op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, op.NegA, op.AbsA); + var srcB = GetHalfSrc(context, HalfSwizzle.F32, op.CbufSlot, op.CbufOffset, op.NegB, false); + + EmitHset2(context, op.Cmp, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.Dest, op.Bval); + } + + public static void Hsetp2R(EmitterContext context) + { + InstHsetp2R op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, op.NegA, op.AbsA); + var srcB = GetHalfSrc(context, op.BSwizzle, op.SrcB, op.NegB, op.AbsB); + + EmitHsetp2(context, op.FComp2, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.DestPred, op.DestPredInv, op.HAnd); + } + + public static void Hsetp2I(EmitterContext context) + { + InstHsetp2I op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, op.NegA, op.AbsA); + var srcB = GetHalfSrc(context, op.BimmH0, op.BimmH1); + + EmitHsetp2(context, op.FComp, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.DestPred, op.DestPredInv, op.HAnd); + } + + public static void Hsetp2C(EmitterContext context) + { + InstHsetp2C op = context.GetOp(); + + var srcA = GetHalfSrc(context, op.ASwizzle, op.SrcA, op.NegA, op.AbsA); + var srcB = GetHalfSrc(context, HalfSwizzle.F32, op.CbufSlot, op.CbufOffset, op.NegB, op.AbsB); + + EmitHsetp2(context, op.FComp, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.DestPred, op.DestPredInv, op.HAnd); + } + + private static void EmitFcmp(EmitterContext context, FComp cmpOp, Operand srcA, Operand srcB, Operand srcC, int rd) + { + Operand cmpRes = GetFPComparison(context, cmpOp, srcC, ConstF(0)); + + Operand res = context.ConditionalSelect(cmpRes, srcA, srcB); + + context.Copy(GetDest(rd), res); + } + + private static void EmitFset( + EmitterContext context, + FComp cmpOp, + BoolOp logicOp, + Operand srcA, + Operand srcB, + int srcPred, + bool srcPredInv, + int rd, + bool absoluteA, + bool absoluteB, + bool negateA, + bool negateB, + bool boolFloat, + bool writeCC, + bool isFP64 = false) + { + Instruction fpType = isFP64 ? Instruction.FP64 : Instruction.FP32; + + srcA = context.FPAbsNeg(srcA, absoluteA, negateA, fpType); + srcB = context.FPAbsNeg(srcB, absoluteB, negateB, fpType); + + Operand res = GetFPComparison(context, cmpOp, srcA, srcB, fpType); + Operand pred = GetPredicate(context, srcPred, srcPredInv); + + res = GetPredLogicalOp(context, logicOp, res, pred); + + Operand dest = GetDest(rd); + + if (boolFloat) + { + res = context.ConditionalSelect(res, ConstF(1), Const(0)); + + context.Copy(dest, res); + + SetFPZnFlags(context, res, writeCC); + } + else + { + context.Copy(dest, res); + + SetZnFlags(context, res, writeCC, extended: false); + } + } + + private static void EmitFsetp( + EmitterContext context, + FComp cmpOp, + BoolOp logicOp, + Operand srcA, + Operand srcB, + int srcPred, + bool srcPredInv, + int destPred, + int destPredInv, + bool absoluteA, + bool absoluteB, + bool negateA, + bool negateB, + bool writeCC, + bool isFP64 = false) + { + Instruction fpType = isFP64 ? Instruction.FP64 : Instruction.FP32; + + srcA = context.FPAbsNeg(srcA, absoluteA, negateA, fpType); + srcB = context.FPAbsNeg(srcB, absoluteB, negateB, fpType); + + Operand p0Res = GetFPComparison(context, cmpOp, srcA, srcB, fpType); + Operand p1Res = context.BitwiseNot(p0Res); + Operand pred = GetPredicate(context, srcPred, srcPredInv); + + p0Res = GetPredLogicalOp(context, logicOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, logicOp, p1Res, pred); + + context.Copy(Register(destPred, RegisterType.Predicate), p0Res); + context.Copy(Register(destPredInv, RegisterType.Predicate), p1Res); + } + + private static void EmitHset2( + EmitterContext context, + FComp cmpOp, + BoolOp logicOp, + Operand[] srcA, + Operand[] srcB, + int srcPred, + bool srcPredInv, + int rd, + bool boolFloat) + { + Operand[] res = new Operand[2]; + + res[0] = GetFPComparison(context, cmpOp, srcA[0], srcB[0]); + res[1] = GetFPComparison(context, cmpOp, srcA[1], srcB[1]); + + Operand pred = GetPredicate(context, srcPred, srcPredInv); + + res[0] = GetPredLogicalOp(context, logicOp, res[0], pred); + res[1] = GetPredLogicalOp(context, logicOp, res[1], pred); + + if (boolFloat) + { + res[0] = context.ConditionalSelect(res[0], ConstF(1), Const(0)); + res[1] = context.ConditionalSelect(res[1], ConstF(1), Const(0)); + + context.Copy(GetDest(rd), context.PackHalf2x16(res[0], res[1])); + } + else + { + Operand low = context.BitwiseAnd(res[0], Const(0xffff)); + Operand high = context.ShiftLeft(res[1], Const(16)); + + Operand packed = context.BitwiseOr(low, high); + + context.Copy(GetDest(rd), packed); + } + } + + private static void EmitHsetp2( + EmitterContext context, + FComp cmpOp, + BoolOp logicOp, + Operand[] srcA, + Operand[] srcB, + int srcPred, + bool srcPredInv, + int destPred, + int destPredInv, + bool hAnd) + { + Operand p0Res = GetFPComparison(context, cmpOp, srcA[0], srcB[0]); + Operand p1Res = GetFPComparison(context, cmpOp, srcA[1], srcB[1]); + + if (hAnd) + { + p0Res = context.BitwiseAnd(p0Res, p1Res); + p1Res = context.BitwiseNot(p0Res); + } + + Operand pred = GetPredicate(context, srcPred, srcPredInv); + + p0Res = GetPredLogicalOp(context, logicOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, logicOp, p1Res, pred); + + context.Copy(Register(destPred, RegisterType.Predicate), p0Res); + context.Copy(Register(destPredInv, RegisterType.Predicate), p1Res); + } + + private static Operand GetFPComparison(EmitterContext context, FComp cond, Operand srcA, Operand srcB, Instruction fpType = Instruction.FP32) + { + Operand res; + + if (cond == FComp.T) + { + res = Const(IrConsts.True); + } + else if (cond == FComp.F) + { + res = Const(IrConsts.False); + } + else if (cond == FComp.Nan || cond == FComp.Num) + { + res = context.BitwiseOr(context.IsNan(srcA, fpType), context.IsNan(srcB, fpType)); + + if (cond == FComp.Num) + { + res = context.BitwiseNot(res); + } + } + else + { + var inst = (cond & ~FComp.Nan) switch + { + FComp.Lt => Instruction.CompareLess, + FComp.Eq => Instruction.CompareEqual, + FComp.Le => Instruction.CompareLessOrEqual, + FComp.Gt => Instruction.CompareGreater, + FComp.Ne => Instruction.CompareNotEqual, + FComp.Ge => Instruction.CompareGreaterOrEqual, + _ => throw new ArgumentException($"Unexpected condition \"{cond}\"."), + }; + res = context.Add(inst | fpType, Local(), srcA, srcB); + + if ((cond & FComp.Nan) != 0) + { + res = context.BitwiseOr(res, context.IsNan(srcA, fpType)); + res = context.BitwiseOr(res, context.IsNan(srcB, fpType)); + } + } + + return res; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatMinMax.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatMinMax.cs new file mode 100644 index 00000000..5757e4fb --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatMinMax.cs @@ -0,0 +1,106 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void DmnmxR(EmitterContext context) + { + InstDmnmxR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcReg(context, op.SrcB, isFP64: true); + var srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + EmitFmnmx(context, srcA, srcB, srcPred, op.Dest, op.AbsA, op.AbsB, op.NegA, op.NegB, op.WriteCC, isFP64: true); + } + + public static void DmnmxI(EmitterContext context) + { + InstDmnmxI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20), isFP64: true); + var srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + EmitFmnmx(context, srcA, srcB, srcPred, op.Dest, op.AbsA, op.AbsB, op.NegA, op.NegB, op.WriteCC, isFP64: true); + } + + public static void DmnmxC(EmitterContext context) + { + InstDmnmxC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA, isFP64: true); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset, isFP64: true); + var srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + EmitFmnmx(context, srcA, srcB, srcPred, op.Dest, op.AbsA, op.AbsB, op.NegA, op.NegB, op.WriteCC, isFP64: true); + } + + public static void FmnmxR(EmitterContext context) + { + InstFmnmxR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + var srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + EmitFmnmx(context, srcA, srcB, srcPred, op.Dest, op.AbsA, op.AbsB, op.NegA, op.NegB, op.WriteCC); + } + + public static void FmnmxI(EmitterContext context) + { + InstFmnmxI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToFloat(op.Imm20)); + var srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + EmitFmnmx(context, srcA, srcB, srcPred, op.Dest, op.AbsA, op.AbsB, op.NegA, op.NegB, op.WriteCC); + } + + public static void FmnmxC(EmitterContext context) + { + InstFmnmxC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + var srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + EmitFmnmx(context, srcA, srcB, srcPred, op.Dest, op.AbsA, op.AbsB, op.NegA, op.NegB, op.WriteCC); + } + + private static void EmitFmnmx( + EmitterContext context, + Operand srcA, + Operand srcB, + Operand srcPred, + int rd, + bool absoluteA, + bool absoluteB, + bool negateA, + bool negateB, + bool writeCC, + bool isFP64 = false) + { + Instruction fpType = isFP64 ? Instruction.FP64 : Instruction.FP32; + + srcA = context.FPAbsNeg(srcA, absoluteA, negateA, fpType); + srcB = context.FPAbsNeg(srcB, absoluteB, negateB, fpType); + + Operand resMin = context.FPMinimum(srcA, srcB, fpType); + Operand resMax = context.FPMaximum(srcA, srcB, fpType); + + Operand res = context.ConditionalSelect(srcPred, resMin, resMax); + + SetDest(context, res, rd, isFP64); + + SetFPZnFlags(context, res, writeCC, fpType); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitFlowControl.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitFlowControl.cs new file mode 100644 index 00000000..803aaa62 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitFlowControl.cs @@ -0,0 +1,328 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; +using System.Linq; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Bra(EmitterContext context) + { + context.GetOp(); + + EmitBranch(context, context.CurrBlock.Successors[^1].Address); + } + + public static void Brk(EmitterContext context) + { + context.GetOp(); + + EmitBrkContSync(context); + } + + public static void Brx(EmitterContext context) + { + InstBrx op = context.GetOp(); + InstOp currOp = context.CurrOp; + int startIndex = context.CurrBlock.HasNext() ? 1 : 0; + + if (context.CurrBlock.Successors.Count <= startIndex) + { + context.TranslatorContext.GpuAccessor.Log($"Failed to find targets for BRX instruction at 0x{currOp.Address:X}."); + return; + } + + int offset = (int)currOp.GetAbsoluteAddress(); + + Operand address = context.IAdd(Register(op.SrcA, RegisterType.Gpr), Const(offset)); + + var targets = context.CurrBlock.Successors.Skip(startIndex); + + bool allTargetsSinglePred = true; + int total = context.CurrBlock.Successors.Count - startIndex; + int count = 0; + + foreach (var target in targets.OrderBy(x => x.Address)) + { + if (++count < total && (target.Predecessors.Count > 1 || target.Address <= context.CurrBlock.Address)) + { + allTargetsSinglePred = false; + break; + } + } + + if (allTargetsSinglePred) + { + // Chain blocks, each target block will check if the BRX target address + // matches its own address, if not, it jumps to the next target which will do the same check, + // until it reaches the last possible target, which executed unconditionally. + // We can only do this if the BRX block is the only predecessor of all target blocks. + // Additionally, this is not supported for blocks located before the current block, + // since it will be too late to insert a label, but this is something that can be improved + // in the future if necessary. + + var sortedTargets = targets.OrderBy(x => x.Address); + + Block currentTarget = null; + ulong firstTargetAddress = 0; + + foreach (Block nextTarget in sortedTargets) + { + if (currentTarget != null) + { + if (currentTarget.Address != nextTarget.Address) + { + context.SetBrxTarget(currentTarget.Address, address, (int)currentTarget.Address, nextTarget.Address); + } + } + else + { + firstTargetAddress = nextTarget.Address; + } + + currentTarget = nextTarget; + } + + context.Branch(context.GetLabel(firstTargetAddress)); + } + else + { + // Emit the branches sequentially. + // This generates slightly worse code, but should work for all cases. + + var sortedTargets = targets.OrderByDescending(x => x.Address); + ulong lastTargetAddress = ulong.MaxValue; + + count = 0; + + foreach (Block target in sortedTargets) + { + Operand label = context.GetLabel(target.Address); + + if (++count < total) + { + if (target.Address != lastTargetAddress) + { + context.BranchIfTrue(label, context.ICompareEqual(address, Const((int)target.Address))); + } + + lastTargetAddress = target.Address; + } + else + { + context.Branch(label); + } + } + } + } + + public static void Cal(EmitterContext context) + { + context.GetOp(); + + DecodedFunction function = context.Program.GetFunctionByAddress(context.CurrOp.GetAbsoluteAddress()); + + if (function.IsCompilerGenerated) + { + switch (function.Type) + { + case FunctionType.BuiltInFSIBegin: + context.FSIBegin(); + break; + case FunctionType.BuiltInFSIEnd: + context.FSIEnd(); + break; + } + } + else + { + context.Call(function.Id, false); + } + } + + public static void Cont(EmitterContext context) + { + context.GetOp(); + + EmitBrkContSync(context); + } + + public static void Exit(EmitterContext context) + { + InstExit op = context.GetOp(); + + if (context.IsNonMain) + { + context.TranslatorContext.GpuAccessor.Log("Invalid exit on non-main function."); + return; + } + + if (op.Ccc == Ccc.T) + { + if (context.PrepareForReturn()) + { + context.Return(); + } + } + else + { + Operand cond = GetCondition(context, op.Ccc, IrConsts.False); + + // If the condition is always false, we don't need to do anything. + if (cond.Type != OperandType.Constant || cond.Value != IrConsts.False) + { + Operand lblSkip = Label(); + context.BranchIfFalse(lblSkip, cond); + + if (context.PrepareForReturn()) + { + context.Return(); + } + + context.MarkLabel(lblSkip); + } + } + } + + public static void Kil(EmitterContext context) + { + context.GetOp(); + + context.Discard(); + } + + public static void Pbk(EmitterContext context) + { + context.GetOp(); + + EmitPbkPcntSsy(context); + } + + public static void Pcnt(EmitterContext context) + { + context.GetOp(); + + EmitPbkPcntSsy(context); + } + + public static void Ret(EmitterContext context) + { + context.GetOp(); + + if (context.IsNonMain) + { + context.Return(); + } + else + { + context.TranslatorContext.GpuAccessor.Log("Invalid return on main function."); + } + } + + public static void Ssy(EmitterContext context) + { + context.GetOp(); + + EmitPbkPcntSsy(context); + } + + public static void Sync(EmitterContext context) + { + context.GetOp(); + + EmitBrkContSync(context); + } + + private static void EmitPbkPcntSsy(EmitterContext context) + { + var consumers = context.CurrBlock.PushOpCodes.First(x => x.Op.Address == context.CurrOp.Address).Consumers; + + foreach (KeyValuePair kv in consumers) + { + Block consumerBlock = kv.Key; + Operand local = kv.Value; + + int id = consumerBlock.SyncTargets[context.CurrOp.Address].PushOpId; + + context.Copy(local, Const(id)); + } + } + + private static void EmitBrkContSync(EmitterContext context) + { + var targets = context.CurrBlock.SyncTargets; + + if (targets.Count == 1) + { + // If we have only one target, then the SSY/PBK is basically + // a branch, we can produce better codegen for this case. + EmitBranch(context, targets.Values.First().PushOpInfo.Op.GetAbsoluteAddress()); + } + else + { + // TODO: Support CC here as well (condition). + foreach (SyncTarget target in targets.Values) + { + PushOpInfo pushOpInfo = target.PushOpInfo; + + Operand label = context.GetLabel(pushOpInfo.Op.GetAbsoluteAddress()); + Operand local = pushOpInfo.Consumers[context.CurrBlock]; + + context.BranchIfTrue(label, context.ICompareEqual(local, Const(target.PushOpId))); + } + } + } + + private static void EmitBranch(EmitterContext context, ulong address) + { + InstOp op = context.CurrOp; + InstConditional opCond = new(op.RawOpCode); + + // If we're branching to the next instruction, then the branch + // is useless and we can ignore it. + if (address == op.Address + 8) + { + return; + } + + Operand label = context.GetLabel(address); + + Operand pred = Register(opCond.Pred, RegisterType.Predicate); + + if (opCond.Ccc != Ccc.T) + { + Operand cond = GetCondition(context, opCond.Ccc); + + if (opCond.Pred == RegisterConsts.PredicateTrueIndex) + { + pred = cond; + } + else if (opCond.PredInv) + { + pred = context.BitwiseAnd(context.BitwiseNot(pred), cond); + } + else + { + pred = context.BitwiseAnd(pred, cond); + } + + context.BranchIfTrue(label, pred); + } + else if (opCond.Pred == RegisterConsts.PredicateTrueIndex) + { + context.Branch(label); + } + else if (opCond.PredInv) + { + context.BranchIfFalse(label, pred); + } + else + { + context.BranchIfTrue(label, pred); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs new file mode 100644 index 00000000..8638fb8f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs @@ -0,0 +1,260 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Runtime.CompilerServices; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static class InstEmitHelper + { + public static Operand GetZF() + { + return Register(0, RegisterType.Flag); + } + + public static Operand GetNF() + { + return Register(1, RegisterType.Flag); + } + + public static Operand GetCF() + { + return Register(2, RegisterType.Flag); + } + + public static Operand GetVF() + { + return Register(3, RegisterType.Flag); + } + + public static Operand GetDest(int rd) + { + return Register(rd, RegisterType.Gpr); + } + + public static Operand GetDest2(int rd) + { + return Register(rd | 1, RegisterType.Gpr); + } + + public static Operand GetSrcCbuf(EmitterContext context, int cbufSlot, int cbufOffset, bool isFP64 = false) + { + if (isFP64) + { + return context.PackDouble2x32( + Cbuf(cbufSlot, cbufOffset), + Cbuf(cbufSlot, cbufOffset + 1)); + } + else + { + return Cbuf(cbufSlot, cbufOffset); + } + } + + public static Operand GetSrcImm(EmitterContext context, int imm, bool isFP64 = false) + { + if (isFP64) + { + return context.PackDouble2x32(Const(0), Const(imm)); + } + else + { + return Const(imm); + } + } + + public static Operand GetSrcReg(EmitterContext context, int reg, bool isFP64 = false) + { + if (isFP64) + { + return context.PackDouble2x32(Register(reg, RegisterType.Gpr), Register(reg | 1, RegisterType.Gpr)); + } + else + { + return Register(reg, RegisterType.Gpr); + } + } + + public static Operand[] GetHalfSrc( + EmitterContext context, + HalfSwizzle swizzle, + int ra, + bool negate, + bool absolute) + { + Operand[] operands = GetHalfUnpacked(context, GetSrcReg(context, ra), swizzle); + + return FPAbsNeg(context, operands, absolute, negate); + } + + public static Operand[] GetHalfSrc( + EmitterContext context, + HalfSwizzle swizzle, + int cbufSlot, + int cbufOffset, + bool negate, + bool absolute) + { + Operand[] operands = GetHalfUnpacked(context, GetSrcCbuf(context, cbufSlot, cbufOffset), swizzle); + + return FPAbsNeg(context, operands, absolute, negate); + } + + public static Operand[] GetHalfSrc(EmitterContext context, int immH0, int immH1) + { + ushort low = (ushort)(immH0 << 6); + ushort high = (ushort)(immH1 << 6); + + return new Operand[] + { + ConstF((float)Unsafe.As(ref low)), + ConstF((float)Unsafe.As(ref high)), + }; + } + + public static Operand[] GetHalfSrc(EmitterContext context, int imm32) + { + ushort low = (ushort)imm32; + ushort high = (ushort)(imm32 >> 16); + + return new Operand[] + { + ConstF((float)Unsafe.As(ref low)), + ConstF((float)Unsafe.As(ref high)), + }; + } + + public static Operand[] FPAbsNeg(EmitterContext context, Operand[] operands, bool abs, bool neg) + { + for (int index = 0; index < operands.Length; index++) + { + operands[index] = context.FPAbsNeg(operands[index], abs, neg); + } + + return operands; + } + + public static Operand[] GetHalfUnpacked(EmitterContext context, Operand src, HalfSwizzle swizzle) + { + return swizzle switch + { + HalfSwizzle.F16 => new Operand[] + { + context.UnpackHalf2x16Low (src), + context.UnpackHalf2x16High(src), + }, + HalfSwizzle.F32 => new Operand[] { src, src }, + HalfSwizzle.H0H0 => new Operand[] + { + context.UnpackHalf2x16Low(src), + context.UnpackHalf2x16Low(src), + }, + HalfSwizzle.H1H1 => new Operand[] + { + context.UnpackHalf2x16High(src), + context.UnpackHalf2x16High(src), + }, + _ => throw new ArgumentException($"Invalid swizzle \"{swizzle}\"."), + }; + } + + public static Operand GetHalfPacked(EmitterContext context, OFmt swizzle, Operand[] results, int rd) + { + switch (swizzle) + { + case OFmt.F16: + return context.PackHalf2x16(results[0], results[1]); + + case OFmt.F32: + return results[0]; + + case OFmt.MrgH0: + { + Operand h1 = GetHalfDest(context, rd, isHigh: true); + + return context.PackHalf2x16(results[0], h1); + } + + case OFmt.MrgH1: + { + Operand h0 = GetHalfDest(context, rd, isHigh: false); + + return context.PackHalf2x16(h0, results[1]); + } + } + + throw new ArgumentException($"Invalid swizzle \"{swizzle}\"."); + } + + public static Operand GetHalfDest(EmitterContext context, int rd, bool isHigh) + { + if (isHigh) + { + return context.UnpackHalf2x16High(GetDest(rd)); + } + else + { + return context.UnpackHalf2x16Low(GetDest(rd)); + } + } + + public static Operand GetPredicate(EmitterContext context, int pred, bool not) + { + Operand local = Register(pred, RegisterType.Predicate); + + if (not) + { + local = context.BitwiseNot(local); + } + + return local; + } + + public static void SetDest(EmitterContext context, Operand value, int rd, bool isFP64) + { + if (isFP64) + { + context.Copy(GetDest(rd), context.UnpackDouble2x32Low(value)); + context.Copy(GetDest2(rd), context.UnpackDouble2x32High(value)); + } + else + { + context.Copy(GetDest(rd), value); + } + } + + public static int Imm16ToSInt(int imm16) + { + return (short)imm16; + } + + public static int Imm20ToFloat(int imm20) + { + return imm20 << 12; + } + + public static int Imm20ToSInt(int imm20) + { + return (imm20 << 12) >> 12; + } + + public static int Imm24ToSInt(int imm24) + { + return (imm24 << 8) >> 8; + } + + public static Operand SignExtendTo32(EmitterContext context, Operand src, int srcBits) + { + return context.BitfieldExtractS32(src, Const(0), Const(srcBits)); + } + + public static Operand ZeroExtendTo32(EmitterContext context, Operand src, int srcBits) + { + int mask = (int)(uint.MaxValue >> (32 - srcBits)); + + return context.BitwiseAnd(src, Const(mask)); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerArithmetic.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerArithmetic.cs new file mode 100644 index 00000000..99922f7a --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerArithmetic.cs @@ -0,0 +1,701 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void IaddR(EmitterContext context) + { + InstIaddR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + + EmitIadd(context, srcA, srcB, op.Dest, op.AvgMode, op.X, op.WriteCC); + } + + public static void IaddI(EmitterContext context) + { + InstIaddI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + + EmitIadd(context, srcA, srcB, op.Dest, op.AvgMode, op.X, op.WriteCC); + } + + public static void IaddC(EmitterContext context) + { + InstIaddC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitIadd(context, srcA, srcB, op.Dest, op.AvgMode, op.X, op.WriteCC); + } + + public static void Iadd32i(EmitterContext context) + { + InstIadd32i op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, op.Imm32); + + EmitIadd(context, srcA, srcB, op.Dest, op.AvgMode, op.X, op.WriteCC); + } + + public static void Iadd3R(EmitterContext context) + { + InstIadd3R op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + var srcC = GetSrcReg(context, op.SrcC); + + EmitIadd3(context, op.Lrs, srcA, srcB, srcC, op.Apart, op.Bpart, op.Cpart, op.Dest, op.NegA, op.NegB, op.NegC); + } + + public static void Iadd3I(EmitterContext context) + { + InstIadd3I op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + var srcC = GetSrcReg(context, op.SrcC); + + EmitIadd3(context, Lrs.None, srcA, srcB, srcC, HalfSelect.B32, HalfSelect.B32, HalfSelect.B32, op.Dest, op.NegA, op.NegB, op.NegC); + } + + public static void Iadd3C(EmitterContext context) + { + InstIadd3C op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + var srcC = GetSrcReg(context, op.SrcC); + + EmitIadd3(context, Lrs.None, srcA, srcB, srcC, HalfSelect.B32, HalfSelect.B32, HalfSelect.B32, op.Dest, op.NegA, op.NegB, op.NegC); + } + + public static void ImadR(EmitterContext context) + { + InstImadR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + var srcC = GetSrcReg(context, op.SrcC); + + EmitImad(context, srcA, srcB, srcC, op.Dest, op.AvgMode, op.ASigned, op.BSigned, op.Hilo); + } + + public static void ImadI(EmitterContext context) + { + InstImadI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + var srcC = GetSrcReg(context, op.SrcC); + + EmitImad(context, srcA, srcB, srcC, op.Dest, op.AvgMode, op.ASigned, op.BSigned, op.Hilo); + } + + public static void ImadC(EmitterContext context) + { + InstImadC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + var srcC = GetSrcReg(context, op.SrcC); + + EmitImad(context, srcA, srcB, srcC, op.Dest, op.AvgMode, op.ASigned, op.BSigned, op.Hilo); + } + + public static void ImadRc(EmitterContext context) + { + InstImadRc op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcC); + var srcC = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitImad(context, srcA, srcB, srcC, op.Dest, op.AvgMode, op.ASigned, op.BSigned, op.Hilo); + } + + public static void Imad32i(EmitterContext context) + { + InstImad32i op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, op.Imm32); + var srcC = GetSrcReg(context, op.Dest); + + EmitImad(context, srcA, srcB, srcC, op.Dest, op.AvgMode, op.ASigned, op.BSigned, op.Hilo); + } + + public static void ImulR(EmitterContext context) + { + InstImulR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + + EmitImad(context, srcA, srcB, Const(0), op.Dest, AvgMode.NoNeg, op.ASigned, op.BSigned, op.Hilo); + } + + public static void ImulI(EmitterContext context) + { + InstImulI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + + EmitImad(context, srcA, srcB, Const(0), op.Dest, AvgMode.NoNeg, op.ASigned, op.BSigned, op.Hilo); + } + + public static void ImulC(EmitterContext context) + { + InstImulC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitImad(context, srcA, srcB, Const(0), op.Dest, AvgMode.NoNeg, op.ASigned, op.BSigned, op.Hilo); + } + + public static void Imul32i(EmitterContext context) + { + InstImul32i op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, op.Imm32); + + EmitImad(context, srcA, srcB, Const(0), op.Dest, AvgMode.NoNeg, op.ASigned, op.BSigned, op.Hilo); + } + + public static void IscaddR(EmitterContext context) + { + InstIscaddR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + + EmitIscadd(context, srcA, srcB, op.Dest, op.Imm5, op.AvgMode, op.WriteCC); + } + + public static void IscaddI(EmitterContext context) + { + InstIscaddI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + + EmitIscadd(context, srcA, srcB, op.Dest, op.Imm5, op.AvgMode, op.WriteCC); + } + + public static void IscaddC(EmitterContext context) + { + InstIscaddC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitIscadd(context, srcA, srcB, op.Dest, op.Imm5, op.AvgMode, op.WriteCC); + } + + public static void Iscadd32i(EmitterContext context) + { + InstIscadd32i op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, op.Imm32); + + EmitIscadd(context, srcA, srcB, op.Dest, op.Imm5, AvgMode.NoNeg, op.WriteCC); + } + + public static void LeaR(EmitterContext context) + { + InstLeaR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + + EmitLea(context, srcA, srcB, op.Dest, op.NegA, op.ImmU5); + } + + public static void LeaI(EmitterContext context) + { + InstLeaI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + + EmitLea(context, srcA, srcB, op.Dest, op.NegA, op.ImmU5); + } + + public static void LeaC(EmitterContext context) + { + InstLeaC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitLea(context, srcA, srcB, op.Dest, op.NegA, op.ImmU5); + } + + public static void LeaHiR(EmitterContext context) + { + InstLeaHiR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + var srcC = GetSrcReg(context, op.SrcC); + + EmitLeaHi(context, srcA, srcB, srcC, op.Dest, op.NegA, op.ImmU5); + } + + public static void LeaHiC(EmitterContext context) + { + InstLeaHiC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + var srcC = GetSrcReg(context, op.SrcC); + + EmitLeaHi(context, srcA, srcB, srcC, op.Dest, op.NegA, op.ImmU5); + } + + public static void XmadR(EmitterContext context) + { + InstXmadR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + var srcC = GetSrcReg(context, op.SrcC); + + EmitXmad(context, op.XmadCop, srcA, srcB, srcC, op.Dest, op.ASigned, op.BSigned, op.HiloA, op.HiloB, op.Psl, op.Mrg, op.X, op.WriteCC); + } + + public static void XmadI(EmitterContext context) + { + InstXmadI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, op.Imm16); + var srcC = GetSrcReg(context, op.SrcC); + + EmitXmad(context, op.XmadCop, srcA, srcB, srcC, op.Dest, op.ASigned, op.BSigned, op.HiloA, false, op.Psl, op.Mrg, op.X, op.WriteCC); + } + + public static void XmadC(EmitterContext context) + { + InstXmadC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + var srcC = GetSrcReg(context, op.SrcC); + + EmitXmad(context, op.XmadCop, srcA, srcB, srcC, op.Dest, op.ASigned, op.BSigned, op.HiloA, op.HiloB, op.Psl, op.Mrg, op.X, op.WriteCC); + } + + public static void XmadRc(EmitterContext context) + { + InstXmadRc op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcC); + var srcC = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitXmad(context, op.XmadCop, srcA, srcB, srcC, op.Dest, op.ASigned, op.BSigned, op.HiloA, op.HiloB, false, false, op.X, op.WriteCC); + } + + private static void EmitIadd( + EmitterContext context, + Operand srcA, + Operand srcB, + int rd, + AvgMode avgMode, + bool extended, + bool writeCC) + { + srcA = context.INegate(srcA, avgMode == AvgMode.NegA); + srcB = context.INegate(srcB, avgMode == AvgMode.NegB); + + Operand res = context.IAdd(srcA, srcB); + + if (extended) + { + res = context.IAdd(res, context.BitwiseAnd(GetCF(), Const(1))); + } + + SetIaddFlags(context, res, srcA, srcB, writeCC, extended); + + // TODO: SAT. + + context.Copy(GetDest(rd), res); + } + + private static void EmitIadd3( + EmitterContext context, + Lrs mode, + Operand srcA, + Operand srcB, + Operand srcC, + HalfSelect partA, + HalfSelect partB, + HalfSelect partC, + int rd, + bool negateA, + bool negateB, + bool negateC) + { + Operand Extend(Operand src, HalfSelect part) + { + if (part == HalfSelect.B32) + { + return src; + } + + if (part == HalfSelect.H0) + { + return context.BitwiseAnd(src, Const(0xffff)); + } + else if (part == HalfSelect.H1) + { + return context.ShiftRightU32(src, Const(16)); + } + else + { + context.TranslatorContext.GpuAccessor.Log($"Iadd3 has invalid component selection {part}."); + } + + return src; + } + + srcA = context.INegate(Extend(srcA, partA), negateA); + srcB = context.INegate(Extend(srcB, partB), negateB); + srcC = context.INegate(Extend(srcC, partC), negateC); + + Operand res = context.IAdd(srcA, srcB); + + if (mode != Lrs.None) + { + if (mode == Lrs.LeftShift) + { + res = context.ShiftLeft(res, Const(16)); + } + else if (mode == Lrs.RightShift) + { + res = context.ShiftRightU32(res, Const(16)); + } + else + { + // TODO: Warning. + } + } + + res = context.IAdd(res, srcC); + + context.Copy(GetDest(rd), res); + + // TODO: CC, X, corner cases. + } + + private static void EmitImad( + EmitterContext context, + Operand srcA, + Operand srcB, + Operand srcC, + int rd, + AvgMode avgMode, + bool signedA, + bool signedB, + bool high) + { + srcB = context.INegate(srcB, avgMode == AvgMode.NegA); + srcC = context.INegate(srcC, avgMode == AvgMode.NegB); + + Operand res; + + if (high) + { + if (signedA && signedB) + { + res = context.MultiplyHighS32(srcA, srcB); + } + else + { + res = context.MultiplyHighU32(srcA, srcB); + + if (signedA) + { + res = context.IAdd(res, context.IMultiply(srcB, context.ShiftRightS32(srcA, Const(31)))); + } + else if (signedB) + { + res = context.IAdd(res, context.IMultiply(srcA, context.ShiftRightS32(srcB, Const(31)))); + } + } + } + else + { + res = context.IMultiply(srcA, srcB); + } + + if (srcC.Type != OperandType.Constant || srcC.Value != 0) + { + res = context.IAdd(res, srcC); + } + + // TODO: CC, X, SAT, and more? + + context.Copy(GetDest(rd), res); + } + + private static void EmitIscadd( + EmitterContext context, + Operand srcA, + Operand srcB, + int rd, + int shift, + AvgMode avgMode, + bool writeCC) + { + srcA = context.ShiftLeft(srcA, Const(shift)); + + srcA = context.INegate(srcA, avgMode == AvgMode.NegA); + srcB = context.INegate(srcB, avgMode == AvgMode.NegB); + + Operand res = context.IAdd(srcA, srcB); + + SetIaddFlags(context, res, srcA, srcB, writeCC, false); + + context.Copy(GetDest(rd), res); + } + + public static void EmitLea(EmitterContext context, Operand srcA, Operand srcB, int rd, bool negateA, int shift) + { + srcA = context.ShiftLeft(srcA, Const(shift)); + srcA = context.INegate(srcA, negateA); + + Operand res = context.IAdd(srcA, srcB); + + context.Copy(GetDest(rd), res); + + // TODO: CC, X. + } + + private static void EmitLeaHi( + EmitterContext context, + Operand srcA, + Operand srcB, + Operand srcC, + int rd, + bool negateA, + int shift) + { + Operand aLow = context.ShiftLeft(srcA, Const(shift)); + Operand aHigh = shift == 0 ? Const(0) : context.ShiftRightU32(srcA, Const(32 - shift)); + aHigh = context.BitwiseOr(aHigh, context.ShiftLeft(srcC, Const(shift))); + + if (negateA) + { + // Perform 64-bit negation by doing bitwise not of the value, + // then adding 1 and carrying over from low to high. + aLow = context.BitwiseNot(aLow); + aHigh = context.BitwiseNot(aHigh); + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + aLow = AddWithCarry(context, aLow, Const(1), out Operand aLowCOut); +#pragma warning restore IDE0059 + aHigh = context.IAdd(aHigh, aLowCOut); + } + + Operand res = context.IAdd(aHigh, srcB); + + context.Copy(GetDest(rd), res); + + // TODO: CC, X. + } + + public static void EmitXmad( + EmitterContext context, + XmadCop2 mode, + Operand srcA, + Operand srcB, + Operand srcC, + int rd, + bool signedA, + bool signedB, + bool highA, + bool highB, + bool productShiftLeft, + bool merge, + bool extended, + bool writeCC) + { + XmadCop modeConv; + switch (mode) + { + case XmadCop2.Cfull: + modeConv = XmadCop.Cfull; + break; + case XmadCop2.Clo: + modeConv = XmadCop.Clo; + break; + case XmadCop2.Chi: + modeConv = XmadCop.Chi; + break; + case XmadCop2.Csfu: + modeConv = XmadCop.Csfu; + break; + default: + context.TranslatorContext.GpuAccessor.Log($"Invalid XMAD mode \"{mode}\"."); + return; + } + + EmitXmad(context, modeConv, srcA, srcB, srcC, rd, signedA, signedB, highA, highB, productShiftLeft, merge, extended, writeCC); + } + + public static void EmitXmad( + EmitterContext context, + XmadCop mode, + Operand srcA, + Operand srcB, + Operand srcC, + int rd, + bool signedA, + bool signedB, + bool highA, + bool highB, + bool productShiftLeft, + bool merge, + bool extended, + bool writeCC) + { + var srcBUnmodified = srcB; + + Operand Extend16To32(Operand src, bool high, bool signed) + { + if (signed && high) + { + return context.ShiftRightS32(src, Const(16)); + } + else if (signed) + { + return context.BitfieldExtractS32(src, Const(0), Const(16)); + } + else if (high) + { + return context.ShiftRightU32(src, Const(16)); + } + else + { + return context.BitwiseAnd(src, Const(0xffff)); + } + } + + srcA = Extend16To32(srcA, highA, signedA); + srcB = Extend16To32(srcB, highB, signedB); + + Operand res = context.IMultiply(srcA, srcB); + + if (productShiftLeft) + { + res = context.ShiftLeft(res, Const(16)); + } + + switch (mode) + { + case XmadCop.Cfull: + break; + + case XmadCop.Clo: + srcC = Extend16To32(srcC, high: false, signed: false); + break; + case XmadCop.Chi: + srcC = Extend16To32(srcC, high: true, signed: false); + break; + + case XmadCop.Cbcc: + srcC = context.IAdd(srcC, context.ShiftLeft(srcBUnmodified, Const(16))); + break; + + case XmadCop.Csfu: + Operand signAdjustA = context.ShiftLeft(context.ShiftRightU32(srcA, Const(31)), Const(16)); + Operand signAdjustB = context.ShiftLeft(context.ShiftRightU32(srcB, Const(31)), Const(16)); + + srcC = context.ISubtract(srcC, context.IAdd(signAdjustA, signAdjustB)); + break; + + default: + context.TranslatorContext.GpuAccessor.Log($"Invalid XMAD mode \"{mode}\"."); + return; + } + + Operand product = res; + + if (extended) + { + // Add with carry. + res = context.IAdd(res, context.BitwiseAnd(GetCF(), Const(1))); + } + else + { + // Add (no carry in). + res = context.IAdd(res, srcC); + } + + SetIaddFlags(context, res, product, srcC, writeCC, extended); + + if (merge) + { + res = context.BitwiseAnd(res, Const(0xffff)); + res = context.BitwiseOr(res, context.ShiftLeft(srcBUnmodified, Const(16))); + } + + context.Copy(GetDest(rd), res); + } + + private static void SetIaddFlags(EmitterContext context, Operand res, Operand srcA, Operand srcB, bool setCC, bool extended) + { + if (!setCC) + { + return; + } + + if (extended) + { + // C = (d == a && CIn) || d < a + Operand tempC0 = context.ICompareEqual(res, srcA); + Operand tempC1 = context.ICompareLessUnsigned(res, srcA); + + tempC0 = context.BitwiseAnd(tempC0, GetCF()); + + context.Copy(GetCF(), context.BitwiseOr(tempC0, tempC1)); + } + else + { + // C = d < a + context.Copy(GetCF(), context.ICompareLessUnsigned(res, srcA)); + } + + // V = (d ^ a) & ~(a ^ b) < 0 + Operand tempV0 = context.BitwiseExclusiveOr(res, srcA); + Operand tempV1 = context.BitwiseExclusiveOr(srcA, srcB); + + tempV1 = context.BitwiseNot(tempV1); + + Operand tempV = context.BitwiseAnd(tempV0, tempV1); + + context.Copy(GetVF(), context.ICompareLess(tempV, Const(0))); + + SetZnFlags(context, res, setCC: true, extended: extended); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerComparison.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerComparison.cs new file mode 100644 index 00000000..18d4e3d1 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerComparison.cs @@ -0,0 +1,319 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void IcmpR(EmitterContext context) + { + InstIcmpR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + var srcC = GetSrcReg(context, op.SrcC); + + EmitIcmp(context, op.IComp, srcA, srcB, srcC, op.Dest, op.Signed); + } + + public static void IcmpI(EmitterContext context) + { + InstIcmpI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + var srcC = GetSrcReg(context, op.SrcC); + + EmitIcmp(context, op.IComp, srcA, srcB, srcC, op.Dest, op.Signed); + } + + public static void IcmpC(EmitterContext context) + { + InstIcmpC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + var srcC = GetSrcReg(context, op.SrcC); + + EmitIcmp(context, op.IComp, srcA, srcB, srcC, op.Dest, op.Signed); + } + + public static void IcmpRc(EmitterContext context) + { + InstIcmpRc op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcC); + var srcC = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitIcmp(context, op.IComp, srcA, srcB, srcC, op.Dest, op.Signed); + } + + public static void IsetR(EmitterContext context) + { + InstIsetR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + + EmitIset(context, op.IComp, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.Dest, op.BVal, op.Signed, op.X, op.WriteCC); + } + + public static void IsetI(EmitterContext context) + { + InstIsetI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + + EmitIset(context, op.IComp, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.Dest, op.BVal, op.Signed, op.X, op.WriteCC); + } + + public static void IsetC(EmitterContext context) + { + InstIsetC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitIset(context, op.IComp, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.Dest, op.BVal, op.Signed, op.X, op.WriteCC); + } + + public static void IsetpR(EmitterContext context) + { + InstIsetpR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + + EmitIsetp(context, op.IComp, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.DestPred, op.DestPredInv, op.Signed, op.X); + } + + public static void IsetpI(EmitterContext context) + { + InstIsetpI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + + EmitIsetp(context, op.IComp, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.DestPred, op.DestPredInv, op.Signed, op.X); + } + + public static void IsetpC(EmitterContext context) + { + InstIsetpC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitIsetp(context, op.IComp, op.Bop, srcA, srcB, op.SrcPred, op.SrcPredInv, op.DestPred, op.DestPredInv, op.Signed, op.X); + } + + private static void EmitIcmp( + EmitterContext context, + IComp cmpOp, + Operand srcA, + Operand srcB, + Operand srcC, + int rd, + bool isSigned) + { + Operand cmpRes = GetIntComparison(context, cmpOp, srcC, Const(0), isSigned); + + Operand res = context.ConditionalSelect(cmpRes, srcA, srcB); + + context.Copy(GetDest(rd), res); + } + + private static void EmitIset( + EmitterContext context, + IComp cmpOp, + BoolOp logicOp, + Operand srcA, + Operand srcB, + int srcPred, + bool srcPredInv, + int rd, + bool boolFloat, + bool isSigned, + bool extended, + bool writeCC) + { + Operand res = GetIntComparison(context, cmpOp, srcA, srcB, isSigned, extended); + Operand pred = GetPredicate(context, srcPred, srcPredInv); + + res = GetPredLogicalOp(context, logicOp, res, pred); + + Operand dest = GetDest(rd); + + if (boolFloat) + { + res = context.ConditionalSelect(res, ConstF(1), Const(0)); + + context.Copy(dest, res); + + SetFPZnFlags(context, res, writeCC); + } + else + { + context.Copy(dest, res); + + SetZnFlags(context, res, writeCC, extended); + } + } + + private static void EmitIsetp( + EmitterContext context, + IComp cmpOp, + BoolOp logicOp, + Operand srcA, + Operand srcB, + int srcPred, + bool srcPredInv, + int destPred, + int destPredInv, + bool isSigned, + bool extended) + { + Operand p0Res = GetIntComparison(context, cmpOp, srcA, srcB, isSigned, extended); + Operand p1Res = context.BitwiseNot(p0Res); + Operand pred = GetPredicate(context, srcPred, srcPredInv); + + p0Res = GetPredLogicalOp(context, logicOp, p0Res, pred); + p1Res = GetPredLogicalOp(context, logicOp, p1Res, pred); + + context.Copy(Register(destPred, RegisterType.Predicate), p0Res); + context.Copy(Register(destPredInv, RegisterType.Predicate), p1Res); + } + + private static Operand GetIntComparison( + EmitterContext context, + IComp cond, + Operand srcA, + Operand srcB, + bool isSigned, + bool extended) + { + return extended + ? GetIntComparisonExtended(context, cond, srcA, srcB, isSigned) + : GetIntComparison(context, cond, srcA, srcB, isSigned); + } + + private static Operand GetIntComparisonExtended(EmitterContext context, IComp cond, Operand srcA, Operand srcB, bool isSigned) + { + Operand res; + + if (cond == IComp.T) + { + res = Const(IrConsts.True); + } + else if (cond == IComp.F) + { + res = Const(IrConsts.False); + } + else + { + res = context.ISubtract(srcA, srcB); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + res = context.IAdd(res, context.BitwiseNot(GetCF())); +#pragma warning restore IDE0059 + + switch (cond) + { + case IComp.Eq: // r = xh == yh && xl == yl + res = context.BitwiseAnd(context.ICompareEqual(srcA, srcB), GetZF()); + break; + case IComp.Lt: // r = xh < yh || (xh == yh && xl < yl) + Operand notC = context.BitwiseNot(GetCF()); + Operand prevLt = context.BitwiseAnd(context.ICompareEqual(srcA, srcB), notC); + res = isSigned + ? context.BitwiseOr(context.ICompareLess(srcA, srcB), prevLt) + : context.BitwiseOr(context.ICompareLessUnsigned(srcA, srcB), prevLt); + break; + case IComp.Le: // r = xh < yh || (xh == yh && xl <= yl) + Operand zOrNotC = context.BitwiseOr(GetZF(), context.BitwiseNot(GetCF())); + Operand prevLe = context.BitwiseAnd(context.ICompareEqual(srcA, srcB), zOrNotC); + res = isSigned + ? context.BitwiseOr(context.ICompareLess(srcA, srcB), prevLe) + : context.BitwiseOr(context.ICompareLessUnsigned(srcA, srcB), prevLe); + break; + case IComp.Gt: // r = xh > yh || (xh == yh && xl > yl) + Operand notZAndC = context.BitwiseAnd(context.BitwiseNot(GetZF()), GetCF()); + Operand prevGt = context.BitwiseAnd(context.ICompareEqual(srcA, srcB), notZAndC); + res = isSigned + ? context.BitwiseOr(context.ICompareGreater(srcA, srcB), prevGt) + : context.BitwiseOr(context.ICompareGreaterUnsigned(srcA, srcB), prevGt); + break; + case IComp.Ge: // r = xh > yh || (xh == yh && xl >= yl) + Operand prevGe = context.BitwiseAnd(context.ICompareEqual(srcA, srcB), GetCF()); + res = isSigned + ? context.BitwiseOr(context.ICompareGreater(srcA, srcB), prevGe) + : context.BitwiseOr(context.ICompareGreaterUnsigned(srcA, srcB), prevGe); + break; + case IComp.Ne: // r = xh != yh || xl != yl + res = context.BitwiseOr(context.ICompareNotEqual(srcA, srcB), context.BitwiseNot(GetZF())); + break; + default: + throw new ArgumentException($"Unexpected condition \"{cond}\"."); + } + } + + return res; + } + + private static Operand GetIntComparison(EmitterContext context, IComp cond, Operand srcA, Operand srcB, bool isSigned) + { + Operand res; + + if (cond == IComp.T) + { + res = Const(IrConsts.True); + } + else if (cond == IComp.F) + { + res = Const(IrConsts.False); + } + else + { + var inst = cond switch + { + IComp.Lt => Instruction.CompareLessU32, + IComp.Eq => Instruction.CompareEqual, + IComp.Le => Instruction.CompareLessOrEqualU32, + IComp.Gt => Instruction.CompareGreaterU32, + IComp.Ne => Instruction.CompareNotEqual, + IComp.Ge => Instruction.CompareGreaterOrEqualU32, + _ => throw new InvalidOperationException($"Unexpected condition \"{cond}\"."), + }; + + if (isSigned) + { + switch (cond) + { + case IComp.Lt: + inst = Instruction.CompareLess; + break; + case IComp.Le: + inst = Instruction.CompareLessOrEqual; + break; + case IComp.Gt: + inst = Instruction.CompareGreater; + break; + case IComp.Ge: + inst = Instruction.CompareGreaterOrEqual; + break; + } + } + + res = context.Add(inst, Local(), srcA, srcB); + } + + return res; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerLogical.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerLogical.cs new file mode 100644 index 00000000..5993c93d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerLogical.cs @@ -0,0 +1,166 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + private const int PT = RegisterConsts.PredicateTrueIndex; + + public static void LopR(EmitterContext context) + { + InstLopR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + + EmitLop(context, op.Lop, op.PredicateOp, srcA, srcB, op.Dest, op.DestPred, op.NegA, op.NegB, op.X, op.WriteCC); + } + + public static void LopI(EmitterContext context) + { + InstLopI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + + EmitLop(context, op.LogicOp, op.PredicateOp, srcA, srcB, op.Dest, op.DestPred, op.NegA, op.NegB, op.X, op.WriteCC); + } + + public static void LopC(EmitterContext context) + { + InstLopC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitLop(context, op.LogicOp, op.PredicateOp, srcA, srcB, op.Dest, op.DestPred, op.NegA, op.NegB, op.X, op.WriteCC); + } + + public static void Lop32i(EmitterContext context) + { + InstLop32i op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, op.Imm32); + + EmitLop(context, op.LogicOp, PredicateOp.F, srcA, srcB, op.Dest, PT, op.NegA, op.NegB, op.X, op.WriteCC); + } + + public static void Lop3R(EmitterContext context) + { + InstLop3R op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + var srcC = GetSrcReg(context, op.SrcC); + + EmitLop3(context, op.Imm, op.PredicateOp, srcA, srcB, srcC, op.Dest, op.DestPred, op.X, op.WriteCC); + } + + public static void Lop3I(EmitterContext context) + { + InstLop3I op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + var srcC = GetSrcReg(context, op.SrcC); + + EmitLop3(context, op.Imm, PredicateOp.F, srcA, srcB, srcC, op.Dest, PT, false, op.WriteCC); + } + + public static void Lop3C(EmitterContext context) + { + InstLop3C op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + var srcC = GetSrcReg(context, op.SrcC); + + EmitLop3(context, op.Imm, PredicateOp.F, srcA, srcB, srcC, op.Dest, PT, false, op.WriteCC); + } + + private static void EmitLop( + EmitterContext context, + LogicOp logicOp, + PredicateOp predOp, + Operand srcA, + Operand srcB, + int rd, + int destPred, + bool invertA, + bool invertB, + bool extended, + bool writeCC) + { + srcA = context.BitwiseNot(srcA, invertA); + srcB = context.BitwiseNot(srcB, invertB); + + Operand res = logicOp switch + { + LogicOp.And => context.BitwiseAnd(srcA, srcB), + LogicOp.Or => context.BitwiseOr(srcA, srcB), + LogicOp.Xor => context.BitwiseExclusiveOr(srcA, srcB), + _ => srcB, + }; + + EmitLopPredWrite(context, res, predOp, destPred); + + context.Copy(GetDest(rd), res); + + SetZnFlags(context, res, writeCC, extended); + } + + private static void EmitLop3( + EmitterContext context, + int truthTable, + PredicateOp predOp, + Operand srcA, + Operand srcB, + Operand srcC, + int rd, + int destPred, + bool extended, + bool writeCC) + { + Operand res = Lop3Expression.GetFromTruthTable(context, srcA, srcB, srcC, truthTable); + + EmitLopPredWrite(context, res, predOp, destPred); + + context.Copy(GetDest(rd), res); + + SetZnFlags(context, res, writeCC, extended); + } + + private static void EmitLopPredWrite(EmitterContext context, Operand result, PredicateOp predOp, int pred) + { + if (pred != RegisterConsts.PredicateTrueIndex) + { + Operand pRes; + + if (predOp == PredicateOp.F) + { + pRes = Const(IrConsts.False); + } + else if (predOp == PredicateOp.T) + { + pRes = Const(IrConsts.True); + } + else if (predOp == PredicateOp.Z) + { + pRes = context.ICompareEqual(result, Const(0)); + } + else /* if (predOp == Pop.Nz) */ + { + pRes = context.ICompareNotEqual(result, Const(0)); + } + + context.Copy(Register(pred, RegisterType.Predicate), pRes); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerMinMax.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerMinMax.cs new file mode 100644 index 00000000..739e9441 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerMinMax.cs @@ -0,0 +1,71 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void ImnmxR(EmitterContext context) + { + InstImnmxR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + var srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + EmitImnmx(context, srcA, srcB, srcPred, op.Dest, op.Signed, op.WriteCC); + } + + public static void ImnmxI(EmitterContext context) + { + InstImnmxI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + var srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + EmitImnmx(context, srcA, srcB, srcPred, op.Dest, op.Signed, op.WriteCC); + } + + public static void ImnmxC(EmitterContext context) + { + InstImnmxC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + var srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + EmitImnmx(context, srcA, srcB, srcPred, op.Dest, op.Signed, op.WriteCC); + } + + private static void EmitImnmx( + EmitterContext context, + Operand srcA, + Operand srcB, + Operand srcPred, + int rd, + bool isSignedInt, + bool writeCC) + { + Operand resMin = isSignedInt + ? context.IMinimumS32(srcA, srcB) + : context.IMinimumU32(srcA, srcB); + + Operand resMax = isSignedInt + ? context.IMaximumS32(srcA, srcB) + : context.IMaximumU32(srcA, srcB); + + Operand res = context.ConditionalSelect(srcPred, resMin, resMax); + + context.Copy(GetDest(rd), res); + + SetZnFlags(context, res, writeCC); + + // TODO: X flags. + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs new file mode 100644 index 00000000..40129252 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs @@ -0,0 +1,584 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System.Numerics; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Atom(EmitterContext context) + { + InstAtom op = context.GetOp(); + + int sOffset = (op.Imm20 << 12) >> 12; + + (Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, new Register(op.SrcA, RegisterType.Gpr), op.E, sOffset); + + Operand value = GetSrcReg(context, op.SrcB); + + Operand res = EmitAtomicOp(context, StorageKind.GlobalMemory, op.Op, op.Size, addrLow, addrHigh, value); + + context.Copy(GetDest(op.Dest), res); + } + + public static void Atoms(EmitterContext context) + { + if (context.TranslatorContext.Definitions.Stage != ShaderStage.Compute) + { + context.TranslatorContext.GpuAccessor.Log($"Atoms instruction is not valid on \"{context.TranslatorContext.Definitions.Stage}\" stage."); + return; + } + + InstAtoms op = context.GetOp(); + + Operand offset = context.ShiftRightU32(GetSrcReg(context, op.SrcA), Const(2)); + + int sOffset = (op.Imm22 << 10) >> 10; + + offset = context.IAdd(offset, Const(sOffset)); + + Operand value = GetSrcReg(context, op.SrcB); + + AtomSize size = op.AtomsSize switch + { + AtomsSize.S32 => AtomSize.S32, + AtomsSize.U64 => AtomSize.U64, + AtomsSize.S64 => AtomSize.S64, + _ => AtomSize.U32, + }; + + Operand id = Const(context.ResourceManager.SharedMemoryId); + Operand res = EmitAtomicOp(context, StorageKind.SharedMemory, op.AtomOp, size, id, offset, value); + + context.Copy(GetDest(op.Dest), res); + } + + public static void Ldc(EmitterContext context) + { + InstLdc op = context.GetOp(); + + if (op.LsSize > LsSize2.B64) + { + context.TranslatorContext.GpuAccessor.Log($"Invalid LDC size: {op.LsSize}."); + return; + } + + bool isSmallInt = op.LsSize < LsSize2.B32; + + int count = op.LsSize == LsSize2.B64 ? 2 : 1; + + Operand slot = Const(op.CbufSlot); + Operand srcA = GetSrcReg(context, op.SrcA); + + if (op.AddressMode == AddressMode.Is || op.AddressMode == AddressMode.Isl) + { + slot = context.IAdd(slot, context.BitfieldExtractU32(srcA, Const(16), Const(16))); + srcA = context.BitwiseAnd(srcA, Const(0xffff)); + } + + Operand addr = context.IAdd(srcA, Const(Imm16ToSInt(op.CbufOffset))); + Operand wordOffset = context.ShiftRightU32(addr, Const(2)); + + for (int index = 0; index < count; index++) + { + Register dest = new(op.Dest + index, RegisterType.Gpr); + + if (dest.IsRZ) + { + break; + } + + Operand offset = context.IAdd(wordOffset, Const(index)); + Operand value = EmitLoadConstant(context, slot, offset); + + if (isSmallInt) + { + value = ExtractSmallInt(context, (LsSize)op.LsSize, GetBitOffset(context, addr), value); + } + + context.Copy(Register(dest), value); + } + } + + public static void Ldg(EmitterContext context) + { + InstLdg op = context.GetOp(); + + EmitLdg(context, op.LsSize, op.SrcA, op.Dest, Imm24ToSInt(op.Imm24), op.E); + } + + public static void Ldl(EmitterContext context) + { + InstLdl op = context.GetOp(); + + EmitLoad(context, StorageKind.LocalMemory, op.LsSize, GetSrcReg(context, op.SrcA), op.Dest, Imm24ToSInt(op.Imm24)); + } + + public static void Lds(EmitterContext context) + { + if (context.TranslatorContext.Definitions.Stage != ShaderStage.Compute) + { + context.TranslatorContext.GpuAccessor.Log($"Lds instruction is not valid on \"{context.TranslatorContext.Definitions.Stage}\" stage."); + return; + } + + InstLds op = context.GetOp(); + + EmitLoad(context, StorageKind.SharedMemory, op.LsSize, GetSrcReg(context, op.SrcA), op.Dest, Imm24ToSInt(op.Imm24)); + } + + public static void Red(EmitterContext context) + { + InstRed op = context.GetOp(); + + (Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, new Register(op.SrcA, RegisterType.Gpr), op.E, op.Imm20); + + EmitAtomicOp(context, StorageKind.GlobalMemory, (AtomOp)op.RedOp, op.RedSize, addrLow, addrHigh, GetDest(op.SrcB)); + } + + public static void Stg(EmitterContext context) + { + InstStg op = context.GetOp(); + + EmitStg(context, op.LsSize, op.SrcA, op.Dest, Imm24ToSInt(op.Imm24), op.E); + } + + public static void Stl(EmitterContext context) + { + InstStl op = context.GetOp(); + + EmitStore(context, StorageKind.LocalMemory, op.LsSize, GetSrcReg(context, op.SrcA), op.Dest, Imm24ToSInt(op.Imm24)); + } + + public static void Sts(EmitterContext context) + { + if (context.TranslatorContext.Definitions.Stage != ShaderStage.Compute) + { + context.TranslatorContext.GpuAccessor.Log($"Sts instruction is not valid on \"{context.TranslatorContext.Definitions.Stage}\" stage."); + return; + } + + InstSts op = context.GetOp(); + + EmitStore(context, StorageKind.SharedMemory, op.LsSize, GetSrcReg(context, op.SrcA), op.Dest, Imm24ToSInt(op.Imm24)); + } + + private static Operand EmitLoadConstant(EmitterContext context, Operand slot, Operand offset) + { + Operand vecIndex = context.ShiftRightU32(offset, Const(2)); + Operand elemIndex = context.BitwiseAnd(offset, Const(3)); + + if (slot.Type == OperandType.Constant) + { + int binding = context.ResourceManager.GetConstantBufferBinding(slot.Value); + return context.Load(StorageKind.ConstantBuffer, binding, Const(0), vecIndex, elemIndex); + } + else + { + Operand value = Const(0); + + uint cbUseMask = context.TranslatorContext.GpuAccessor.QueryConstantBufferUse(); + + while (cbUseMask != 0) + { + int cbIndex = BitOperations.TrailingZeroCount(cbUseMask); + int binding = context.ResourceManager.GetConstantBufferBinding(cbIndex); + + Operand isCurrent = context.ICompareEqual(slot, Const(cbIndex)); + Operand currentValue = context.Load(StorageKind.ConstantBuffer, binding, Const(0), vecIndex, elemIndex); + + value = context.ConditionalSelect(isCurrent, currentValue, value); + + cbUseMask &= ~(1u << cbIndex); + } + + return value; + } + } + + private static Operand EmitAtomicOp( + EmitterContext context, + StorageKind storageKind, + AtomOp op, + AtomSize type, + Operand e0, + Operand e1, + Operand value) + { + Operand res = Const(0); + + switch (op) + { + case AtomOp.Add: + if (type == AtomSize.S32 || type == AtomSize.U32) + { + res = context.AtomicAdd(storageKind, e0, e1, value); + } + else + { + context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}."); + } + break; + case AtomOp.And: + if (type == AtomSize.S32 || type == AtomSize.U32) + { + res = context.AtomicAnd(storageKind, e0, e1, value); + } + else + { + context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}."); + } + break; + case AtomOp.Xor: + if (type == AtomSize.S32 || type == AtomSize.U32) + { + res = context.AtomicXor(storageKind, e0, e1, value); + } + else + { + context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}."); + } + break; + case AtomOp.Or: + if (type == AtomSize.S32 || type == AtomSize.U32) + { + res = context.AtomicOr(storageKind, e0, e1, value); + } + else + { + context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}."); + } + break; + case AtomOp.Max: + if (type == AtomSize.S32) + { + res = context.AtomicMaxS32(storageKind, e0, e1, value); + } + else if (type == AtomSize.U32) + { + res = context.AtomicMaxU32(storageKind, e0, e1, value); + } + else + { + context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}."); + } + break; + case AtomOp.Min: + if (type == AtomSize.S32) + { + res = context.AtomicMinS32(storageKind, e0, e1, value); + } + else if (type == AtomSize.U32) + { + res = context.AtomicMinU32(storageKind, e0, e1, value); + } + else + { + context.TranslatorContext.GpuAccessor.Log($"Invalid reduction type: {type}."); + } + break; + } + + return res; + } + + private static void EmitLoad( + EmitterContext context, + StorageKind storageKind, + LsSize2 size, + Operand srcA, + int rd, + int offset) + { + if (size > LsSize2.B128) + { + context.TranslatorContext.GpuAccessor.Log($"Invalid load size: {size}."); + return; + } + + int id = storageKind == StorageKind.LocalMemory + ? context.ResourceManager.LocalMemoryId + : context.ResourceManager.SharedMemoryId; + bool isSmallInt = size < LsSize2.B32; + + int count = size switch + { + LsSize2.B64 => 2, + LsSize2.B128 => 4, + _ => 1, + }; + + Operand baseOffset = context.Copy(srcA); + + for (int index = 0; index < count; index++) + { + Register dest = new(rd + index, RegisterType.Gpr); + + if (dest.IsRZ) + { + break; + } + + Operand byteOffset = context.IAdd(baseOffset, Const(offset + index * 4)); + Operand wordOffset = context.ShiftRightU32(byteOffset, Const(2)); // Word offset = byte offset / 4 (one word = 4 bytes). + Operand bitOffset = GetBitOffset(context, byteOffset); + Operand value = context.Load(storageKind, id, wordOffset); + + if (isSmallInt) + { + value = ExtractSmallInt(context, (LsSize)size, bitOffset, value); + } + + context.Copy(Register(dest), value); + } + } + + private static void EmitLdg( + EmitterContext context, + LsSize size, + int ra, + int rd, + int offset, + bool extended) + { + int count = GetVectorCount(size); + StorageKind storageKind = GetStorageKind(size); + + (_, Operand addrHigh) = Get40BitsAddress(context, new Register(ra, RegisterType.Gpr), extended, offset); + + Operand srcA = context.Copy(new Operand(new Register(ra, RegisterType.Gpr))); + + for (int index = 0; index < count; index++) + { + Register dest = new(rd + index, RegisterType.Gpr); + + if (dest.IsRZ) + { + break; + } + + Operand value = context.Load(storageKind, context.IAdd(srcA, Const(offset + index * 4)), addrHigh); + + context.Copy(Register(dest), value); + } + } + + private static void EmitStore( + EmitterContext context, + StorageKind storageKind, + LsSize2 size, + Operand srcA, + int rd, + int offset) + { + if (size > LsSize2.B128) + { + context.TranslatorContext.GpuAccessor.Log($"Invalid store size: {size}."); + return; + } + + int id = storageKind == StorageKind.LocalMemory + ? context.ResourceManager.LocalMemoryId + : context.ResourceManager.SharedMemoryId; + bool isSmallInt = size < LsSize2.B32; + + int count = size switch + { + LsSize2.B64 => 2, + LsSize2.B128 => 4, + _ => 1, + }; + + Operand baseOffset = context.Copy(srcA); + + for (int index = 0; index < count; index++) + { + bool isRz = rd + index >= RegisterConsts.RegisterZeroIndex; + + Operand value = Register(isRz ? rd : rd + index, RegisterType.Gpr); + Operand byteOffset = context.IAdd(baseOffset, Const(offset + index * 4)); + Operand wordOffset = context.ShiftRightU32(byteOffset, Const(2)); + Operand bitOffset = GetBitOffset(context, byteOffset); + + if (isSmallInt && storageKind == StorageKind.LocalMemory) + { + Operand word = context.Load(storageKind, id, wordOffset); + + value = InsertSmallInt(context, (LsSize)size, bitOffset, word, value); + } + + if (storageKind == StorageKind.LocalMemory) + { + context.Store(storageKind, id, wordOffset, value); + } + else if (storageKind == StorageKind.SharedMemory) + { + switch (size) + { + case LsSize2.U8: + case LsSize2.S8: + context.Store(StorageKind.SharedMemory8, id, byteOffset, value); + break; + case LsSize2.U16: + case LsSize2.S16: + context.Store(StorageKind.SharedMemory16, id, byteOffset, value); + break; + default: + context.Store(storageKind, id, wordOffset, value); + break; + } + } + } + } + + private static void EmitStg( + EmitterContext context, + LsSize2 size, + int ra, + int rd, + int offset, + bool extended) + { + if (size > LsSize2.B128) + { + context.TranslatorContext.GpuAccessor.Log($"Invalid store size: {size}."); + return; + } + + int count = GetVectorCount((LsSize)size); + StorageKind storageKind = GetStorageKind((LsSize)size); + + (_, Operand addrHigh) = Get40BitsAddress(context, new Register(ra, RegisterType.Gpr), extended, offset); + + Operand srcA = context.Copy(new Operand(new Register(ra, RegisterType.Gpr))); + + for (int index = 0; index < count; index++) + { + bool isRz = rd + index >= RegisterConsts.RegisterZeroIndex; + + Operand value = Register(isRz ? rd : rd + index, RegisterType.Gpr); + + Operand addrLowOffset = context.IAdd(srcA, Const(offset + index * 4)); + + context.Store(storageKind, addrLowOffset, addrHigh, value); + } + } + + private static StorageKind GetStorageKind(LsSize size) + { + return size switch + { + LsSize.U8 => StorageKind.GlobalMemoryU8, + LsSize.S8 => StorageKind.GlobalMemoryS8, + LsSize.U16 => StorageKind.GlobalMemoryU16, + LsSize.S16 => StorageKind.GlobalMemoryS16, + _ => StorageKind.GlobalMemory, + }; + } + + private static int GetVectorCount(LsSize size) + { + return size switch + { + LsSize.B64 => 2, + LsSize.B128 or LsSize.UB128 => 4, + _ => 1, + }; + } + + private static (Operand, Operand) Get40BitsAddress( + EmitterContext context, + Register ra, + bool extended, + int offset) + { + Operand addrLow = Register(ra); + Operand addrHigh; + + if (extended && !ra.IsRZ) + { + addrHigh = Register(ra.Index + 1, RegisterType.Gpr); + } + else + { + addrHigh = Const(0); + } + + Operand offs = Const(offset); + + addrLow = context.IAdd(addrLow, offs); + + if (extended) + { + Operand carry = context.ICompareLessUnsigned(addrLow, offs); + + addrHigh = context.IAdd(addrHigh, context.ConditionalSelect(carry, Const(1), Const(0))); + } + + return (addrLow, addrHigh); + } + + private static Operand GetBitOffset(EmitterContext context, Operand baseOffset) + { + // Note: bit offset = (baseOffset & 0b11) * 8. + // Addresses should be always aligned to the integer type, + // so we don't need to take unaligned addresses into account. + return context.ShiftLeft(context.BitwiseAnd(baseOffset, Const(3)), Const(3)); + } + + private static Operand ExtractSmallInt( + EmitterContext context, + LsSize size, + Operand bitOffset, + Operand value) + { + value = context.ShiftRightU32(value, bitOffset); + + switch (size) + { + case LsSize.U8: + value = ZeroExtendTo32(context, value, 8); + break; + case LsSize.U16: + value = ZeroExtendTo32(context, value, 16); + break; + case LsSize.S8: + value = SignExtendTo32(context, value, 8); + break; + case LsSize.S16: + value = SignExtendTo32(context, value, 16); + break; + } + + return value; + } + + private static Operand InsertSmallInt( + EmitterContext context, + LsSize size, + Operand bitOffset, + Operand word, + Operand value) + { + switch (size) + { + case LsSize.U8: + case LsSize.S8: + value = context.BitwiseAnd(value, Const(0xff)); + value = context.BitfieldInsert(word, value, bitOffset, Const(8)); + break; + + case LsSize.U16: + case LsSize.S16: + value = context.BitwiseAnd(value, Const(0xffff)); + value = context.BitfieldInsert(word, value, bitOffset, Const(16)); + break; + } + + return value; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs new file mode 100644 index 00000000..944039d6 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs @@ -0,0 +1,277 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void MovR(EmitterContext context) + { + InstMovR op = context.GetOp(); + + context.Copy(GetDest(op.Dest), GetSrcReg(context, op.SrcA)); + } + + public static void MovI(EmitterContext context) + { + InstMovI op = context.GetOp(); + + context.Copy(GetDest(op.Dest), GetSrcImm(context, op.Imm20)); + } + + public static void MovC(EmitterContext context) + { + InstMovC op = context.GetOp(); + + context.Copy(GetDest(op.Dest), GetSrcCbuf(context, op.CbufSlot, op.CbufOffset)); + } + + public static void Mov32i(EmitterContext context) + { + InstMov32i op = context.GetOp(); + + context.Copy(GetDest(op.Dest), GetSrcImm(context, op.Imm32)); + } + + public static void R2pR(EmitterContext context) + { + InstR2pR op = context.GetOp(); + + Operand value = GetSrcReg(context, op.SrcA); + Operand mask = GetSrcReg(context, op.SrcB); + + EmitR2p(context, value, mask, op.ByteSel, op.Ccpr); + } + + public static void R2pI(EmitterContext context) + { + InstR2pI op = context.GetOp(); + + Operand value = GetSrcReg(context, op.SrcA); + Operand mask = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + + EmitR2p(context, value, mask, op.ByteSel, op.Ccpr); + } + + public static void R2pC(EmitterContext context) + { + InstR2pC op = context.GetOp(); + + Operand value = GetSrcReg(context, op.SrcA); + Operand mask = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitR2p(context, value, mask, op.ByteSel, op.Ccpr); + } + + public static void S2r(EmitterContext context) + { + InstS2r op = context.GetOp(); + + Operand src; + + switch (op.SReg) + { + case SReg.LaneId: + src = EmitLoadSubgroupLaneId(context); + break; + + case SReg.InvocationId: + src = context.Load(StorageKind.Input, IoVariable.InvocationId); + break; + + case SReg.YDirection: + src = ConstF(1); // TODO: Use value from Y direction GPU register. + break; + + case SReg.ThreadKill: + src = context.TranslatorContext.Definitions.Stage == ShaderStage.Fragment ? context.Load(StorageKind.Input, IoVariable.ThreadKill) : Const(0); + break; + + case SReg.InvocationInfo: + if (context.TranslatorContext.Definitions.Stage != ShaderStage.Compute && context.TranslatorContext.Definitions.Stage != ShaderStage.Fragment) + { + // Note: Lowest 8-bits seems to contain some primitive index, + // but it seems to be NVIDIA implementation specific as it's only used + // to calculate ISBE offsets, so we can just keep it as zero. + + if (context.TranslatorContext.Definitions.Stage == ShaderStage.TessellationControl || + context.TranslatorContext.Definitions.Stage == ShaderStage.TessellationEvaluation) + { + src = context.ShiftLeft(context.Load(StorageKind.Input, IoVariable.PatchVertices), Const(16)); + } + else + { + src = Const(context.TranslatorContext.Definitions.InputTopology.ToInputVertices() << 16); + } + } + else + { + src = Const(0); + } + break; + + case SReg.TId: + 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)); + + src = context.BitwiseOr(tidX, context.BitwiseOr(tidY, tidZ)); + break; + + case SReg.TIdX: + src = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(0)); + break; + case SReg.TIdY: + src = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(1)); + break; + case SReg.TIdZ: + src = context.Load(StorageKind.Input, IoVariable.ThreadId, null, Const(2)); + break; + + case SReg.CtaIdX: + src = context.Load(StorageKind.Input, IoVariable.CtaId, null, Const(0)); + break; + case SReg.CtaIdY: + src = context.Load(StorageKind.Input, IoVariable.CtaId, null, Const(1)); + break; + case SReg.CtaIdZ: + src = context.Load(StorageKind.Input, IoVariable.CtaId, null, Const(2)); + break; + + case SReg.EqMask: + src = EmitLoadSubgroupMask(context, IoVariable.SubgroupEqMask); + break; + case SReg.LtMask: + src = EmitLoadSubgroupMask(context, IoVariable.SubgroupLtMask); + break; + case SReg.LeMask: + src = EmitLoadSubgroupMask(context, IoVariable.SubgroupLeMask); + break; + case SReg.GtMask: + src = EmitLoadSubgroupMask(context, IoVariable.SubgroupGtMask); + break; + case SReg.GeMask: + src = EmitLoadSubgroupMask(context, IoVariable.SubgroupGeMask); + break; + + default: + src = Const(0); + break; + } + + context.Copy(GetDest(op.Dest), src); + } + + private static Operand EmitLoadSubgroupLaneId(EmitterContext context) + { + if (context.TranslatorContext.GpuAccessor.QueryHostSubgroupSize() <= 32) + { + return context.Load(StorageKind.Input, IoVariable.SubgroupLaneId); + } + + return context.BitwiseAnd(context.Load(StorageKind.Input, IoVariable.SubgroupLaneId), Const(0x1f)); + } + + private static Operand EmitLoadSubgroupMask(EmitterContext context, IoVariable ioVariable) + { + int subgroupSize = context.TranslatorContext.GpuAccessor.QueryHostSubgroupSize(); + + if (subgroupSize <= 32) + { + return context.Load(StorageKind.Input, ioVariable, null, Const(0)); + } + else if (subgroupSize == 64) + { + Operand laneId = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId); + Operand low = context.Load(StorageKind.Input, ioVariable, null, Const(0)); + Operand high = context.Load(StorageKind.Input, ioVariable, null, Const(1)); + + return context.ConditionalSelect(context.BitwiseAnd(laneId, Const(32)), high, low); + } + else + { + Operand laneId = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId); + Operand element = context.ShiftRightU32(laneId, Const(5)); + + Operand res = context.Load(StorageKind.Input, ioVariable, null, Const(0)); + res = context.ConditionalSelect( + context.ICompareEqual(element, Const(1)), + context.Load(StorageKind.Input, ioVariable, null, Const(1)), res); + res = context.ConditionalSelect( + context.ICompareEqual(element, Const(2)), + context.Load(StorageKind.Input, ioVariable, null, Const(2)), res); + res = context.ConditionalSelect( + context.ICompareEqual(element, Const(3)), + context.Load(StorageKind.Input, ioVariable, null, Const(3)), res); + + return res; + } + } + + public static void SelR(EmitterContext context) + { + InstSelR op = context.GetOp(); + + Operand srcA = GetSrcReg(context, op.SrcA); + Operand srcB = GetSrcReg(context, op.SrcB); + Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + EmitSel(context, srcA, srcB, srcPred, op.Dest); + } + + public static void SelI(EmitterContext context) + { + InstSelI op = context.GetOp(); + + Operand srcA = GetSrcReg(context, op.SrcA); + Operand srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + EmitSel(context, srcA, srcB, srcPred, op.Dest); + } + + public static void SelC(EmitterContext context) + { + InstSelC op = context.GetOp(); + + Operand srcA = GetSrcReg(context, op.SrcA); + Operand srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + EmitSel(context, srcA, srcB, srcPred, op.Dest); + } + + private static void EmitR2p(EmitterContext context, Operand value, Operand mask, ByteSel byteSel, bool ccpr) + { + Operand Test(Operand value, int bit) + { + return context.ICompareNotEqual(context.BitwiseAnd(value, Const(1 << bit)), Const(0)); + } + + int count = ccpr ? RegisterConsts.FlagsCount : RegisterConsts.PredsCount; + RegisterType type = ccpr ? RegisterType.Flag : RegisterType.Predicate; + int shift = (int)byteSel * 8; + + for (int bit = 0; bit < count; bit++) + { + Operand flag = Register(bit, type); + Operand res = context.ConditionalSelect(Test(mask, bit), Test(value, bit + shift), flag); + context.Copy(flag, res); + } + } + + private static void EmitSel(EmitterContext context, Operand srcA, Operand srcB, Operand srcPred, int rd) + { + Operand res = context.ConditionalSelect(srcPred, srcA, srcB); + + context.Copy(GetDest(rd), res); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMultifunction.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMultifunction.cs new file mode 100644 index 00000000..5c079378 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitMultifunction.cs @@ -0,0 +1,97 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void RroR(EmitterContext context) + { + InstRroR op = context.GetOp(); + + EmitRro(context, GetSrcReg(context, op.SrcB), op.Dest, op.AbsB, op.NegB); + } + + public static void RroI(EmitterContext context) + { + InstRroI op = context.GetOp(); + + EmitRro(context, GetSrcImm(context, Imm20ToFloat(op.Imm20)), op.Dest, op.AbsB, op.NegB); + } + + public static void RroC(EmitterContext context) + { + InstRroC op = context.GetOp(); + + EmitRro(context, GetSrcCbuf(context, op.CbufSlot, op.CbufOffset), op.Dest, op.AbsB, op.NegB); + } + + public static void Mufu(EmitterContext context) + { + InstMufu op = context.GetOp(); + + Operand res = context.FPAbsNeg(GetSrcReg(context, op.SrcA), op.AbsA, op.NegA); + + switch (op.MufuOp) + { + case MufuOp.Cos: + res = context.FPCosine(res); + break; + + case MufuOp.Sin: + res = context.FPSine(res); + break; + + case MufuOp.Ex2: + res = context.FPExponentB2(res); + break; + + case MufuOp.Lg2: + res = context.FPLogarithmB2(res); + break; + + case MufuOp.Rcp: + res = context.FPReciprocal(res); + break; + + case MufuOp.Rsq: + res = context.FPReciprocalSquareRoot(res); + break; + + case MufuOp.Rcp64h: + res = context.PackDouble2x32(OperandHelper.Const(0), res); + res = context.UnpackDouble2x32High(context.FPReciprocal(res, Instruction.FP64)); + break; + + case MufuOp.Rsq64h: + res = context.PackDouble2x32(OperandHelper.Const(0), res); + res = context.UnpackDouble2x32High(context.FPReciprocalSquareRoot(res, Instruction.FP64)); + break; + + case MufuOp.Sqrt: + res = context.FPSquareRoot(res); + break; + + default: + context.TranslatorContext.GpuAccessor.Log($"Invalid MUFU operation \"{op.MufuOp}\"."); + break; + } + + context.Copy(GetDest(op.Dest), context.FPSaturate(res, op.Sat)); + } + + private static void EmitRro(EmitterContext context, Operand srcB, int rd, bool absB, bool negB) + { + // This is the range reduction operator, + // we translate it as a simple move, as it + // should be always followed by a matching + // MUFU instruction. + srcB = context.FPAbsNeg(srcB, absB, negB); + + context.Copy(GetDest(rd), srcB); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitNop.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitNop.cs new file mode 100644 index 00000000..28ee927d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitNop.cs @@ -0,0 +1,15 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Nop(EmitterContext context) + { + context.GetOp(); + + // No operation. + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs new file mode 100644 index 00000000..1d865125 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs @@ -0,0 +1,116 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Pset(EmitterContext context) + { + InstPset op = context.GetOp(); + + Operand srcA = context.BitwiseNot(Register(op.Src2Pred, RegisterType.Predicate), op.Src2PredInv); + Operand srcB = context.BitwiseNot(Register(op.Src1Pred, RegisterType.Predicate), op.Src1PredInv); + Operand srcC = context.BitwiseNot(Register(op.SrcPred, RegisterType.Predicate), op.SrcPredInv); + + Operand res = GetPredLogicalOp(context, op.BoolOpAB, srcA, srcB); + res = GetPredLogicalOp(context, op.BoolOpC, res, srcC); + + Operand dest = GetDest(op.Dest); + + if (op.BVal) + { + context.Copy(dest, context.ConditionalSelect(res, ConstF(1), ConstF(0))); + } + else + { + context.Copy(dest, res); + } + } + + public static void Psetp(EmitterContext context) + { + InstPsetp op = context.GetOp(); + + Operand srcA = context.BitwiseNot(Register(op.Src2Pred, RegisterType.Predicate), op.Src2PredInv); + Operand srcB = context.BitwiseNot(Register(op.Src1Pred, RegisterType.Predicate), op.Src1PredInv); + + Operand p0Res = GetPredLogicalOp(context, op.BoolOpAB, srcA, srcB); + Operand p1Res = context.BitwiseNot(p0Res); + Operand srcPred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + p0Res = GetPredLogicalOp(context, op.BoolOpC, p0Res, srcPred); + p1Res = GetPredLogicalOp(context, op.BoolOpC, p1Res, srcPred); + + context.Copy(Register(op.DestPred, RegisterType.Predicate), p0Res); + context.Copy(Register(op.DestPredInv, RegisterType.Predicate), p1Res); + } + + public static void P2rC(EmitterContext context) + { + InstP2rC op = context.GetOp(); + + Operand srcA = GetSrcReg(context, op.SrcA); + Operand dest = GetSrcReg(context, op.Dest); + Operand mask = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitP2r(context, srcA, dest, mask, op.ByteSel, op.Ccpr); + } + + public static void P2rI(EmitterContext context) + { + InstP2rI op = context.GetOp(); + + Operand srcA = GetSrcReg(context, op.SrcA); + Operand dest = GetSrcReg(context, op.Dest); + Operand mask = GetSrcImm(context, op.Imm20); + + EmitP2r(context, srcA, dest, mask, op.ByteSel, op.Ccpr); + } + + public static void P2rR(EmitterContext context) + { + InstP2rR op = context.GetOp(); + + Operand srcA = GetSrcReg(context, op.SrcA); + Operand dest = GetSrcReg(context, op.Dest); + Operand mask = GetSrcReg(context, op.SrcB); + + EmitP2r(context, srcA, dest, mask, op.ByteSel, op.Ccpr); + } + + private static void EmitP2r( + EmitterContext context, + Operand srcA, + Operand dest, + Operand mask, + ByteSel byteSel, + bool ccpr) + { + int count = ccpr ? RegisterConsts.FlagsCount : RegisterConsts.PredsCount; + int shift = (int)byteSel * 8; + mask = context.BitwiseAnd(mask, Const(0xff)); + + Operand insert = Const(0); + for (int i = 0; i < count; i++) + { + Operand condition = ccpr + ? Register(i, RegisterType.Flag) + : Register(i, RegisterType.Predicate); + + Operand bit = context.ConditionalSelect(condition, Const(1 << (i + shift)), Const(0)); + insert = context.BitwiseOr(insert, bit); + } + + Operand maskShifted = context.ShiftLeft(mask, Const(shift)); + Operand masked = context.BitwiseAnd(srcA, context.BitwiseNot(maskShifted)); + Operand res = context.BitwiseOr(masked, context.BitwiseAnd(insert, maskShifted)); + + context.Copy(dest, res); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitShift.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitShift.cs new file mode 100644 index 00000000..ee0dac15 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitShift.cs @@ -0,0 +1,249 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void ShfLR(EmitterContext context) + { + InstShfLR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + var srcC = GetSrcReg(context, op.SrcC); + + EmitShf(context, op.MaxShift, srcA, srcB, srcC, op.Dest, op.M, left: true, op.WriteCC); + } + + public static void ShfRR(EmitterContext context) + { + InstShfRR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + var srcC = GetSrcReg(context, op.SrcC); + + EmitShf(context, op.MaxShift, srcA, srcB, srcC, op.Dest, op.M, left: false, op.WriteCC); + } + + public static void ShfLI(EmitterContext context) + { + InstShfLI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = Const(op.Imm6); + var srcC = GetSrcReg(context, op.SrcC); + + EmitShf(context, op.MaxShift, srcA, srcB, srcC, op.Dest, op.M, left: true, op.WriteCC); + } + + public static void ShfRI(EmitterContext context) + { + InstShfRI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = Const(op.Imm6); + var srcC = GetSrcReg(context, op.SrcC); + + EmitShf(context, op.MaxShift, srcA, srcB, srcC, op.Dest, op.M, left: false, op.WriteCC); + } + + public static void ShlR(EmitterContext context) + { + InstShlR op = context.GetOp(); + + EmitShl(context, GetSrcReg(context, op.SrcA), GetSrcReg(context, op.SrcB), op.Dest, op.M); + } + + public static void ShlI(EmitterContext context) + { + InstShlI op = context.GetOp(); + + EmitShl(context, GetSrcReg(context, op.SrcA), GetSrcImm(context, Imm20ToSInt(op.Imm20)), op.Dest, op.M); + } + + public static void ShlC(EmitterContext context) + { + InstShlC op = context.GetOp(); + + EmitShl(context, GetSrcReg(context, op.SrcA), GetSrcCbuf(context, op.CbufSlot, op.CbufOffset), op.Dest, op.M); + } + + public static void ShrR(EmitterContext context) + { + InstShrR op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcReg(context, op.SrcB); + + EmitShr(context, srcA, srcB, op.Dest, op.M, op.Brev, op.Signed); + } + + public static void ShrI(EmitterContext context) + { + InstShrI op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcImm(context, Imm20ToSInt(op.Imm20)); + + EmitShr(context, srcA, srcB, op.Dest, op.M, op.Brev, op.Signed); + } + + public static void ShrC(EmitterContext context) + { + InstShrC op = context.GetOp(); + + var srcA = GetSrcReg(context, op.SrcA); + var srcB = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset); + + EmitShr(context, srcA, srcB, op.Dest, op.M, op.Brev, op.Signed); + } + + private static void EmitShf( + EmitterContext context, + MaxShift maxShift, + Operand srcA, + Operand srcB, + Operand srcC, + int rd, + bool mask, + bool left, + bool writeCC) + { + bool isLongShift = maxShift == MaxShift.U64 || maxShift == MaxShift.S64; + bool signedShift = maxShift == MaxShift.S64; + int maxShiftConst = isLongShift ? 64 : 32; + + if (mask) + { + srcB = context.BitwiseAnd(srcB, Const(maxShiftConst - 1)); + } + + Operand res; + + if (left) + { + // res = (C << B) | (A >> (32 - B)) + res = context.ShiftLeft(srcC, srcB); + res = context.BitwiseOr(res, context.ShiftRightU32(srcA, context.ISubtract(Const(32), srcB))); + + if (isLongShift) + { + // res = B >= 32 ? A << (B - 32) : res + Operand lowerShift = context.ShiftLeft(srcA, context.ISubtract(srcB, Const(32))); + + Operand shiftGreaterThan31 = context.ICompareGreaterOrEqualUnsigned(srcB, Const(32)); + res = context.ConditionalSelect(shiftGreaterThan31, lowerShift, res); + } + } + else + { + // res = (A >> B) | (C << (32 - B)) + res = context.ShiftRightU32(srcA, srcB); + res = context.BitwiseOr(res, context.ShiftLeft(srcC, context.ISubtract(Const(32), srcB))); + + if (isLongShift) + { + // res = B >= 32 ? C >> (B - 32) : res + Operand upperShift = signedShift + ? context.ShiftRightS32(srcC, context.ISubtract(srcB, Const(32))) + : context.ShiftRightU32(srcC, context.ISubtract(srcB, Const(32))); + + Operand shiftGreaterThan31 = context.ICompareGreaterOrEqualUnsigned(srcB, Const(32)); + res = context.ConditionalSelect(shiftGreaterThan31, upperShift, res); + } + } + + if (!mask) + { + // Clamped shift value. + Operand isLessThanMax = context.ICompareLessUnsigned(srcB, Const(maxShiftConst)); + + res = context.ConditionalSelect(isLessThanMax, res, Const(0)); + } + + context.Copy(GetDest(rd), res); + + if (writeCC) + { + InstEmitAluHelper.SetZnFlags(context, res, writeCC); + } + + // TODO: X. + } + + private static void EmitShl(EmitterContext context, Operand srcA, Operand srcB, int rd, bool mask) + { + if (mask) + { + srcB = context.BitwiseAnd(srcB, Const(0x1f)); + } + + Operand res = context.ShiftLeft(srcA, srcB); + + if (!mask) + { + // Clamped shift value. + Operand isLessThan32 = context.ICompareLessUnsigned(srcB, Const(32)); + + res = context.ConditionalSelect(isLessThan32, res, Const(0)); + } + + // TODO: X, CC. + + context.Copy(GetDest(rd), res); + } + + private static void EmitShr( + EmitterContext context, + Operand srcA, + Operand srcB, + int rd, + bool mask, + bool bitReverse, + bool isSigned) + { + if (bitReverse) + { + srcA = context.BitfieldReverse(srcA); + } + + if (mask) + { + srcB = context.BitwiseAnd(srcB, Const(0x1f)); + } + + Operand res = isSigned + ? context.ShiftRightS32(srcA, srcB) + : context.ShiftRightU32(srcA, srcB); + + if (!mask) + { + // Clamped shift value. + Operand resShiftBy32; + + if (isSigned) + { + resShiftBy32 = context.ShiftRightS32(srcA, Const(31)); + } + else + { + resShiftBy32 = Const(0); + } + + Operand isLessThan32 = context.ICompareLessUnsigned(srcB, Const(32)); + + res = context.ConditionalSelect(isLessThan32, res, resShiftBy32); + } + + // TODO: X, CC. + + context.Copy(GetDest(rd), res); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs new file mode 100644 index 00000000..383e82c6 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs @@ -0,0 +1,799 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.Numerics; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void SuatomB(EmitterContext context) + { + InstSuatomB op = context.GetOp(); + + EmitSuatom( + context, + op.Dim, + op.Op, + op.Size, + 0, + op.SrcA, + op.SrcB, + op.SrcC, + op.Dest, + op.Ba, + isBindless: true, + compareAndSwap: false); + } + + public static void Suatom(EmitterContext context) + { + InstSuatom op = context.GetOp(); + + EmitSuatom( + context, + op.Dim, + op.Op, + op.Size, + op.TidB, + op.SrcA, + op.SrcB, + 0, + op.Dest, + op.Ba, + isBindless: false, + compareAndSwap: false); + } + + public static void SuatomB2(EmitterContext context) + { + InstSuatomB2 op = context.GetOp(); + + EmitSuatom( + context, + op.Dim, + op.Op, + op.Size, + 0, + op.SrcA, + op.SrcB, + op.SrcC, + op.Dest, + op.Ba, + isBindless: true, + compareAndSwap: false); + } + + public static void SuatomCasB(EmitterContext context) + { + InstSuatomCasB op = context.GetOp(); + + EmitSuatom( + context, + op.Dim, + 0, + op.Size, + 0, + op.SrcA, + op.SrcB, + op.SrcC, + op.Dest, + op.Ba, + isBindless: true, + compareAndSwap: true); + } + + public static void SuatomCas(EmitterContext context) + { + InstSuatomCas op = context.GetOp(); + + EmitSuatom( + context, + op.Dim, + 0, + op.Size, + op.TidB, + op.SrcA, + op.SrcB, + 0, + op.Dest, + op.Ba, + isBindless: false, + compareAndSwap: true); + } + + public static void SuldDB(EmitterContext context) + { + InstSuldDB op = context.GetOp(); + + EmitSuld(context, op.CacheOp, op.Dim, op.Size, 0, 0, op.SrcA, op.Dest, op.SrcC, useComponents: false, op.Ba, isBindless: true); + } + + public static void SuldD(EmitterContext context) + { + InstSuldD op = context.GetOp(); + + EmitSuld(context, op.CacheOp, op.Dim, op.Size, op.TidB, 0, op.SrcA, op.Dest, 0, useComponents: false, op.Ba, isBindless: false); + } + + public static void SuldB(EmitterContext context) + { + InstSuldB op = context.GetOp(); + + EmitSuld(context, op.CacheOp, op.Dim, 0, 0, op.Rgba, op.SrcA, op.Dest, op.SrcC, useComponents: true, false, isBindless: true); + } + + public static void Suld(EmitterContext context) + { + InstSuld op = context.GetOp(); + + EmitSuld(context, op.CacheOp, op.Dim, 0, op.TidB, op.Rgba, op.SrcA, op.Dest, 0, useComponents: true, false, isBindless: false); + } + + public static void SuredB(EmitterContext context) + { + InstSuredB op = context.GetOp(); + + EmitSured(context, op.Dim, op.Op, op.Size, 0, op.SrcA, op.Dest, op.SrcC, op.Ba, isBindless: true); + } + + public static void Sured(EmitterContext context) + { + InstSured op = context.GetOp(); + + EmitSured(context, op.Dim, op.Op, op.Size, op.TidB, op.SrcA, op.Dest, 0, op.Ba, isBindless: false); + } + + public static void SustDB(EmitterContext context) + { + InstSustDB op = context.GetOp(); + + EmitSust(context, op.CacheOp, op.Dim, op.Size, 0, 0, op.SrcA, op.Dest, op.SrcC, useComponents: false, op.Ba, isBindless: true); + } + + public static void SustD(EmitterContext context) + { + InstSustD op = context.GetOp(); + + EmitSust(context, op.CacheOp, op.Dim, op.Size, op.TidB, 0, op.SrcA, op.Dest, 0, useComponents: false, op.Ba, isBindless: false); + } + + public static void SustB(EmitterContext context) + { + InstSustB op = context.GetOp(); + + EmitSust(context, op.CacheOp, op.Dim, 0, 0, op.Rgba, op.SrcA, op.Dest, op.SrcC, useComponents: true, false, isBindless: true); + } + + public static void Sust(EmitterContext context) + { + InstSust op = context.GetOp(); + + EmitSust(context, op.CacheOp, op.Dim, 0, op.TidB, op.Rgba, op.SrcA, op.Dest, 0, useComponents: true, false, isBindless: false); + } + + private static void EmitSuatom( + EmitterContext context, + SuDim dimensions, + SuatomOp atomicOp, + SuatomSize size, + int imm, + int srcA, + int srcB, + int srcC, + int dest, + bool byteAddress, + bool isBindless, + bool compareAndSwap) + { + SamplerType type = ConvertSamplerType(dimensions); + + if (type == SamplerType.None) + { + context.TranslatorContext.GpuAccessor.Log("Invalid image atomic sampler type."); + return; + } + + Operand Ra() + { + if (srcA > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcA++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (srcB > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcB++, RegisterType.Gpr)); + } + + Operand d = Register(dest, RegisterType.Gpr); + + List sourcesList = new(); + + if (isBindless) + { + sourcesList.Add(context.Copy(GetSrcReg(context, srcC))); + } + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + if (Sample1DAs2D && (type & SamplerType.Mask) == SamplerType.Texture1D) + { + sourcesList.Add(Const(0)); + + type &= ~SamplerType.Mask; + type |= SamplerType.Texture2D; + } + + if (type.HasFlag(SamplerType.Array)) + { + sourcesList.Add(Ra()); + + type |= SamplerType.Array; + } + + if (byteAddress) + { + int xIndex = isBindless ? 1 : 0; + + sourcesList[xIndex] = context.ShiftRightS32(sourcesList[xIndex], Const(GetComponentSizeInBytesLog2(size))); + } + + // TODO: FP and 64-bit formats. + TextureFormat format = size == SuatomSize.Sd32 || size == SuatomSize.Sd64 + ? (isBindless ? TextureFormat.Unknown : ShaderProperties.GetTextureFormatAtomic(context.TranslatorContext.GpuAccessor, imm)) + : GetTextureFormat(size); + + if (compareAndSwap) + { + sourcesList.Add(Rb()); + } + + sourcesList.Add(Rb()); + + Operand[] sources = sourcesList.ToArray(); + + TextureFlags flags = compareAndSwap ? TextureFlags.CAS : GetAtomicOpFlags(atomicOp); + + if (isBindless) + { + flags |= TextureFlags.Bindless; + } + + SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding( + Instruction.ImageAtomic, + type, + format, + flags, + TextureOperation.DefaultCbufSlot, + imm); + + Operand res = context.ImageAtomic(type, format, flags, setAndBinding, sources); + + context.Copy(d, res); + } + + private static void EmitSuld( + EmitterContext context, + CacheOpLd cacheOp, + SuDim dimensions, + SuSize size, + int imm, + SuRgba componentMask, + int srcA, + int srcB, + int srcC, + bool useComponents, + bool byteAddress, + bool isBindless) + { + if (srcB == RegisterConsts.RegisterZeroIndex) + { + return; + } + + SamplerType type = ConvertSamplerType(dimensions); + + if (type == SamplerType.None) + { + context.TranslatorContext.GpuAccessor.Log("Invalid image store sampler type."); + return; + } + + Operand Ra() + { + if (srcA > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcA++, RegisterType.Gpr)); + } + + List sourcesList = new(); + + if (isBindless) + { + sourcesList.Add(context.Copy(Register(srcC, RegisterType.Gpr))); + } + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + if (Sample1DAs2D && (type & SamplerType.Mask) == SamplerType.Texture1D) + { + sourcesList.Add(Const(0)); + + type &= ~SamplerType.Mask; + type |= SamplerType.Texture2D; + } + + if (type.HasFlag(SamplerType.Array)) + { + sourcesList.Add(Ra()); + } + + Operand[] sources = sourcesList.ToArray(); + + int handle = imm; + + TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None; + + if (cacheOp == CacheOpLd.Cg) + { + flags |= TextureFlags.Coherent; + } + + if (useComponents) + { + Operand[] dests = new Operand[BitOperations.PopCount((uint)componentMask)]; + + int outputIndex = 0; + + for (int i = 0; i < dests.Length; i++) + { + if (srcB + i >= RegisterConsts.RegisterZeroIndex) + { + break; + } + + dests[outputIndex++] = Register(srcB + i, RegisterType.Gpr); + } + + if (outputIndex != dests.Length) + { + Array.Resize(ref dests, outputIndex); + } + + TextureFormat format = isBindless ? TextureFormat.Unknown : ShaderProperties.GetTextureFormat(context.TranslatorContext.GpuAccessor, handle); + + SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding( + Instruction.ImageLoad, + type, + format, + flags, + TextureOperation.DefaultCbufSlot, + handle); + + context.ImageLoad(type, format, flags, setAndBinding, (int)componentMask, dests, sources); + } + else + { + if (byteAddress) + { + int xIndex = isBindless ? 1 : 0; + + sources[xIndex] = context.ShiftRightS32(sources[xIndex], Const(GetComponentSizeInBytesLog2(size))); + } + + int components = GetComponents(size); + int compMask = (1 << components) - 1; + + Operand[] dests = new Operand[components]; + + int outputIndex = 0; + + for (int i = 0; i < dests.Length; i++) + { + if (srcB + i >= RegisterConsts.RegisterZeroIndex) + { + break; + } + + dests[outputIndex++] = Register(srcB + i, RegisterType.Gpr); + } + + if (outputIndex != dests.Length) + { + Array.Resize(ref dests, outputIndex); + } + + TextureFormat format = GetTextureFormat(size); + + SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding( + Instruction.ImageLoad, + type, + format, + flags, + TextureOperation.DefaultCbufSlot, + handle); + + context.ImageLoad(type, format, flags, setAndBinding, compMask, dests, sources); + + switch (size) + { + case SuSize.U8: + context.Copy(dests[0], ZeroExtendTo32(context, dests[0], 8)); + break; + case SuSize.U16: + context.Copy(dests[0], ZeroExtendTo32(context, dests[0], 16)); + break; + case SuSize.S8: + context.Copy(dests[0], SignExtendTo32(context, dests[0], 8)); + break; + case SuSize.S16: + context.Copy(dests[0], SignExtendTo32(context, dests[0], 16)); + break; + } + } + } + + private static void EmitSured( + EmitterContext context, + SuDim dimensions, + RedOp atomicOp, + SuatomSize size, + int imm, + int srcA, + int srcB, + int srcC, + bool byteAddress, + bool isBindless) + { + SamplerType type = ConvertSamplerType(dimensions); + + if (type == SamplerType.None) + { + context.TranslatorContext.GpuAccessor.Log("Invalid image reduction sampler type."); + return; + } + + Operand Ra() + { + if (srcA > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcA++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (srcB > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcB++, RegisterType.Gpr)); + } + + List sourcesList = new(); + + if (isBindless) + { + sourcesList.Add(context.Copy(GetSrcReg(context, srcC))); + } + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + if (Sample1DAs2D && (type & SamplerType.Mask) == SamplerType.Texture1D) + { + sourcesList.Add(Const(0)); + + type &= ~SamplerType.Mask; + type |= SamplerType.Texture2D; + } + + if (type.HasFlag(SamplerType.Array)) + { + sourcesList.Add(Ra()); + + type |= SamplerType.Array; + } + + if (byteAddress) + { + int xIndex = isBindless ? 1 : 0; + + sourcesList[xIndex] = context.ShiftRightS32(sourcesList[xIndex], Const(GetComponentSizeInBytesLog2(size))); + } + + // TODO: FP and 64-bit formats. + TextureFormat format = size == SuatomSize.Sd32 || size == SuatomSize.Sd64 + ? (isBindless ? TextureFormat.Unknown : ShaderProperties.GetTextureFormatAtomic(context.TranslatorContext.GpuAccessor, imm)) + : GetTextureFormat(size); + + sourcesList.Add(Rb()); + + Operand[] sources = sourcesList.ToArray(); + + TextureFlags flags = GetAtomicOpFlags((SuatomOp)atomicOp); + + if (isBindless) + { + flags |= TextureFlags.Bindless; + } + + SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding( + Instruction.ImageAtomic, + type, + format, + flags, + TextureOperation.DefaultCbufSlot, + imm); + + context.ImageAtomic(type, format, flags, setAndBinding, sources); + } + + private static void EmitSust( + EmitterContext context, + CacheOpSt cacheOp, + SuDim dimensions, + SuSize size, + int imm, + SuRgba componentMask, + int srcA, + int srcB, + int srcC, + bool useComponents, + bool byteAddress, + bool isBindless) + { + SamplerType type = ConvertSamplerType(dimensions); + + if (type == SamplerType.None) + { + context.TranslatorContext.GpuAccessor.Log("Invalid image store sampler type."); + return; + } + + Operand Ra() + { + if (srcA > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcA++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (srcB > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcB++, RegisterType.Gpr)); + } + + List sourcesList = new(); + + if (isBindless) + { + sourcesList.Add(context.Copy(Register(srcC, RegisterType.Gpr))); + } + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + if (Sample1DAs2D && (type & SamplerType.Mask) == SamplerType.Texture1D) + { + sourcesList.Add(Const(0)); + + type &= ~SamplerType.Mask; + type |= SamplerType.Texture2D; + } + + if (type.HasFlag(SamplerType.Array)) + { + sourcesList.Add(Ra()); + } + + TextureFormat format = TextureFormat.Unknown; + + if (useComponents) + { + for (int compMask = (int)componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + sourcesList.Add(Rb()); + } + } + + if (!isBindless) + { + format = ShaderProperties.GetTextureFormat(context.TranslatorContext.GpuAccessor, imm); + } + } + else + { + if (byteAddress) + { + int xIndex = isBindless ? 1 : 0; + + sourcesList[xIndex] = context.ShiftRightS32(sourcesList[xIndex], Const(GetComponentSizeInBytesLog2(size))); + } + + int components = GetComponents(size); + + for (int compIndex = 0; compIndex < components; compIndex++) + { + sourcesList.Add(Rb()); + } + + format = GetTextureFormat(size); + } + + Operand[] sources = sourcesList.ToArray(); + + int handle = imm; + + TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None; + + if (cacheOp == CacheOpSt.Cg) + { + flags |= TextureFlags.Coherent; + } + + SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding( + Instruction.ImageStore, + type, + format, + flags, + TextureOperation.DefaultCbufSlot, + handle); + + context.ImageStore(type, format, flags, setAndBinding, sources); + } + + private static int GetComponentSizeInBytesLog2(SuatomSize size) + { + return size switch + { + SuatomSize.U32 => 2, + SuatomSize.S32 => 2, + SuatomSize.U64 => 3, + SuatomSize.F32FtzRn => 2, + SuatomSize.F16x2FtzRn => 2, + SuatomSize.S64 => 3, + SuatomSize.Sd32 => 2, + SuatomSize.Sd64 => 3, + _ => 2, + }; + } + + private static TextureFormat GetTextureFormat(SuatomSize size) + { + return size switch + { + SuatomSize.U32 => TextureFormat.R32Uint, + SuatomSize.S32 => TextureFormat.R32Sint, + SuatomSize.U64 => TextureFormat.R32G32Uint, + SuatomSize.F32FtzRn => TextureFormat.R32Float, + SuatomSize.F16x2FtzRn => TextureFormat.R16G16Float, + SuatomSize.S64 => TextureFormat.R32G32Uint, + SuatomSize.Sd32 => TextureFormat.R32Uint, + SuatomSize.Sd64 => TextureFormat.R32G32Uint, + _ => TextureFormat.R32Uint, + }; + } + + private static TextureFlags GetAtomicOpFlags(SuatomOp op) + { + return op switch + { + SuatomOp.Add => TextureFlags.Add, + SuatomOp.Min => TextureFlags.Minimum, + SuatomOp.Max => TextureFlags.Maximum, + SuatomOp.Inc => TextureFlags.Increment, + SuatomOp.Dec => TextureFlags.Decrement, + SuatomOp.And => TextureFlags.BitwiseAnd, + SuatomOp.Or => TextureFlags.BitwiseOr, + SuatomOp.Xor => TextureFlags.BitwiseXor, + SuatomOp.Exch => TextureFlags.Swap, + _ => TextureFlags.Add, + }; + } + + private static int GetComponents(SuSize size) + { + return size switch + { + SuSize.B64 => 2, + SuSize.B128 => 4, + SuSize.UB128 => 4, + _ => 1, + }; + } + + private static int GetComponentSizeInBytesLog2(SuSize size) + { + return size switch + { + SuSize.U8 => 0, + SuSize.S8 => 0, + SuSize.U16 => 1, + SuSize.S16 => 1, + SuSize.B32 => 2, + SuSize.B64 => 3, + SuSize.B128 => 4, + SuSize.UB128 => 4, + _ => 2, + }; + } + + private static TextureFormat GetTextureFormat(SuSize size) + { + return size switch + { + SuSize.U8 => TextureFormat.R8Uint, + SuSize.S8 => TextureFormat.R8Sint, + SuSize.U16 => TextureFormat.R16Uint, + SuSize.S16 => TextureFormat.R16Sint, + SuSize.B32 => TextureFormat.R32Uint, + SuSize.B64 => TextureFormat.R32G32Uint, + SuSize.B128 => TextureFormat.R32G32B32A32Uint, + SuSize.UB128 => TextureFormat.R32G32B32A32Uint, + _ => TextureFormat.R32Uint, + }; + } + + private static SamplerType ConvertSamplerType(SuDim target) + { + return target switch + { + SuDim._1d => SamplerType.Texture1D, + SuDim._1dBuffer => SamplerType.TextureBuffer, + SuDim._1dArray => SamplerType.Texture1D | SamplerType.Array, + SuDim._2d => SamplerType.Texture2D, + SuDim._2dArray => SamplerType.Texture2D | SamplerType.Array, + SuDim._3d => SamplerType.Texture3D, + _ => SamplerType.None, + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs new file mode 100644 index 00000000..2076262d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs @@ -0,0 +1,1331 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.Numerics; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + private static readonly int[][] _maskLut = new int[][] + { + new int[] { 0b0001, 0b0010, 0b0100, 0b1000, 0b0011, 0b1001, 0b1010, 0b1100 }, + new int[] { 0b0111, 0b1011, 0b1101, 0b1110, 0b1111, 0b0000, 0b0000, 0b0000 }, + }; + + public const bool Sample1DAs2D = true; + + private enum TexsType + { + Texs, + Tlds, + Tld4s, + } + + public static void Tex(EmitterContext context) + { + InstTex op = context.GetOp(); + + EmitTex(context, TextureFlags.None, op.Dim, op.Lod, op.TidB, op.WMask, op.SrcA, op.SrcB, op.Dest, false, op.Dc, op.Aoffi); + } + + public static void TexB(EmitterContext context) + { + InstTexB op = context.GetOp(); + + EmitTex(context, TextureFlags.Bindless, op.Dim, op.Lodb, 0, op.WMask, op.SrcA, op.SrcB, op.Dest, false, op.Dc, op.Aoffib); + } + + public static void Texs(EmitterContext context) + { + InstTexs op = context.GetOp(); + + EmitTexs(context, TexsType.Texs, op.TidB, op.WMask, op.SrcA, op.SrcB, op.Dest, op.Dest2, isF16: false); + } + + public static void TexsF16(EmitterContext context) + { + InstTexs op = context.GetOp(); + + EmitTexs(context, TexsType.Texs, op.TidB, op.WMask, op.SrcA, op.SrcB, op.Dest, op.Dest2, isF16: true); + } + + public static void Tld(EmitterContext context) + { + InstTld op = context.GetOp(); + + var lod = op.Lod ? Lod.Ll : Lod.Lz; + + EmitTex(context, TextureFlags.IntCoords, op.Dim, lod, op.TidB, op.WMask, op.SrcA, op.SrcB, op.Dest, op.Ms, false, op.Toff); + } + + public static void TldB(EmitterContext context) + { + InstTldB op = context.GetOp(); + + var flags = TextureFlags.IntCoords | TextureFlags.Bindless; + var lod = op.Lod ? Lod.Ll : Lod.Lz; + + EmitTex(context, flags, op.Dim, lod, 0, op.WMask, op.SrcA, op.SrcB, op.Dest, op.Ms, false, op.Toff); + } + + public static void Tlds(EmitterContext context) + { + InstTlds op = context.GetOp(); + + EmitTexs(context, TexsType.Tlds, op.TidB, op.WMask, op.SrcA, op.SrcB, op.Dest, op.Dest2, isF16: false); + } + + public static void TldsF16(EmitterContext context) + { + InstTlds op = context.GetOp(); + + EmitTexs(context, TexsType.Tlds, op.TidB, op.WMask, op.SrcA, op.SrcB, op.Dest, op.Dest2, isF16: true); + } + + public static void Tld4(EmitterContext context) + { + InstTld4 op = context.GetOp(); + + EmitTld4(context, op.Dim, op.TexComp, op.TidB, op.WMask, op.SrcA, op.SrcB, op.Dest, op.Toff, op.Dc, isBindless: false); + } + + public static void Tld4B(EmitterContext context) + { + InstTld4B op = context.GetOp(); + + EmitTld4(context, op.Dim, op.TexComp, 0, op.WMask, op.SrcA, op.SrcB, op.Dest, op.Toff, op.Dc, isBindless: true); + } + + public static void Tld4s(EmitterContext context) + { + InstTld4s op = context.GetOp(); + + EmitTexs(context, TexsType.Tld4s, op.TidB, 4, op.SrcA, op.SrcB, op.Dest, op.Dest2, isF16: false); + } + + public static void Tld4sF16(EmitterContext context) + { + InstTld4s op = context.GetOp(); + + EmitTexs(context, TexsType.Tld4s, op.TidB, 4, op.SrcA, op.SrcB, op.Dest, op.Dest2, isF16: true); + } + + public static void Tmml(EmitterContext context) + { + InstTmml op = context.GetOp(); + + EmitTmml(context, op.Dim, op.TidB, op.WMask, op.SrcA, op.SrcB, op.Dest, isBindless: false); + } + + public static void TmmlB(EmitterContext context) + { + InstTmmlB op = context.GetOp(); + + EmitTmml(context, op.Dim, 0, op.WMask, op.SrcA, op.SrcB, op.Dest, isBindless: true); + } + + public static void Txd(EmitterContext context) + { + InstTxd op = context.GetOp(); + + EmitTxd(context, op.Dim, op.TidB, op.WMask, op.SrcA, op.SrcB, op.Dest, op.Toff, isBindless: false); + } + + public static void TxdB(EmitterContext context) + { + InstTxdB op = context.GetOp(); + + EmitTxd(context, op.Dim, 0, op.WMask, op.SrcA, op.SrcB, op.Dest, op.Toff, isBindless: true); + } + + public static void Txq(EmitterContext context) + { + InstTxq op = context.GetOp(); + + EmitTxq(context, op.TexQuery, op.TidB, op.WMask, op.SrcA, op.Dest, isBindless: false); + } + + public static void TxqB(EmitterContext context) + { + InstTxqB op = context.GetOp(); + + EmitTxq(context, op.TexQuery, 0, op.WMask, op.SrcA, op.Dest, isBindless: true); + } + + private static void EmitTex( + EmitterContext context, + TextureFlags flags, + TexDim dimensions, + Lod lodMode, + int imm, + int componentMask, + int raIndex, + int rbIndex, + int rdIndex, + bool isMultisample, + bool hasDepthCompare, + bool hasOffset) + { + if (rdIndex == RegisterConsts.RegisterZeroIndex) + { + return; + } + + Operand Ra() + { + if (raIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(raIndex++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (rbIndex > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(rbIndex++, RegisterType.Gpr)); + } + + SamplerType type = ConvertSamplerType(dimensions); + + bool isArray = type.HasFlag(SamplerType.Array); + bool isBindless = flags.HasFlag(TextureFlags.Bindless); + + Operand arrayIndex = isArray ? Ra() : null; + + List sourcesList = new(); + + if (isBindless) + { + sourcesList.Add(Rb()); + } + + bool hasLod = lodMode > Lod.Lz; + + if (type == SamplerType.Texture1D && (flags & ~TextureFlags.Bindless) == TextureFlags.IntCoords && !( + hasLod || + hasDepthCompare || + hasOffset || + isArray || + isMultisample)) + { + // For bindless, we don't have any way to know the texture type, + // so we assume it's texture buffer when the sampler type is 1D, since that's more common. + bool isTypeBuffer = isBindless || context.TranslatorContext.GpuAccessor.QuerySamplerType(imm) == SamplerType.TextureBuffer; + if (isTypeBuffer) + { + type = SamplerType.TextureBuffer; + } + } + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + bool is1DTo2D = false; + + if (Sample1DAs2D && (type & SamplerType.Mask) == SamplerType.Texture1D) + { + sourcesList.Add(ConstF(0)); + + type = SamplerType.Texture2D | (type & SamplerType.Array); + is1DTo2D = true; + } + + if (isArray) + { + sourcesList.Add(arrayIndex); + } + + Operand lodValue = hasLod ? Rb() : ConstF(0); + + Operand packedOffs = hasOffset ? Rb() : null; + + if (hasDepthCompare) + { + sourcesList.Add(Rb()); + + type |= SamplerType.Shadow; + } + + if ((lodMode == Lod.Lz || + lodMode == Lod.Ll || + lodMode == Lod.Lla) && !isMultisample && type != SamplerType.TextureBuffer) + { + sourcesList.Add(lodValue); + + flags |= TextureFlags.LodLevel; + } + + if (hasOffset) + { + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(context.BitfieldExtractS32(packedOffs, Const(index * 4), Const(4))); + } + + if (is1DTo2D) + { + sourcesList.Add(Const(0)); + } + + flags |= TextureFlags.Offset; + } + + if (lodMode == Lod.Lb || lodMode == Lod.Lba) + { + sourcesList.Add(lodValue); + + flags |= TextureFlags.LodBias; + } + + if (isMultisample) + { + sourcesList.Add(Rb()); + + type |= SamplerType.Multisample; + } + + Operand[] sources = sourcesList.ToArray(); + Operand[] dests = new Operand[BitOperations.PopCount((uint)componentMask)]; + + int outputIndex = 0; + + for (int i = 0; i < dests.Length; i++) + { + if (rdIndex + i >= RegisterConsts.RegisterZeroIndex) + { + break; + } + + dests[outputIndex++] = Register(rdIndex + i, RegisterType.Gpr); + } + + if (outputIndex != dests.Length) + { + Array.Resize(ref dests, outputIndex); + } + + int handle = !isBindless ? imm : 0; + + EmitTextureSample(context, type, flags, handle, componentMask, dests, sources); + } + + private static void EmitTexs( + EmitterContext context, + TexsType texsType, + int imm, + int writeMask, + int srcA, + int srcB, + int dest, + int dest2, + bool isF16) + { + if (dest == RegisterConsts.RegisterZeroIndex && dest2 == RegisterConsts.RegisterZeroIndex) + { + return; + } + + List sourcesList = new(); + + Operand Ra() + { + if (srcA > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcA++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (srcB > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcB++, RegisterType.Gpr)); + } + + void AddTextureOffset(int coordsCount, int stride, int size) + { + Operand packedOffs = Rb(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(context.BitfieldExtractS32(packedOffs, Const(index * stride), Const(size))); + } + } + + SamplerType type; + TextureFlags flags; + + if (texsType == TexsType.Texs) + { + var texsOp = context.GetOp(); + + type = ConvertSamplerType(texsOp.Target); + + if (type == SamplerType.None) + { + context.TranslatorContext.GpuAccessor.Log("Invalid texture sampler type."); + return; + } + + flags = ConvertTextureFlags(texsOp.Target); + + // We don't need to handle 1D -> Buffer conversions here as + // only texture sample with integer coordinates can ever use buffer targets. + + if ((type & SamplerType.Array) != 0) + { + Operand arrayIndex = Ra(); + + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + + sourcesList.Add(arrayIndex); + + if ((type & SamplerType.Shadow) != 0) + { + sourcesList.Add(Rb()); + } + + if ((flags & TextureFlags.LodLevel) != 0) + { + sourcesList.Add(ConstF(0)); + } + } + else + { + switch (texsOp.Target) + { + case TexsTarget.Texture1DLodZero: + sourcesList.Add(Ra()); + + if (Sample1DAs2D) + { + sourcesList.Add(ConstF(0)); + + type &= ~SamplerType.Mask; + type |= SamplerType.Texture2D; + } + + sourcesList.Add(ConstF(0)); + break; + + case TexsTarget.Texture2D: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case TexsTarget.Texture2DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(ConstF(0)); + break; + + case TexsTarget.Texture2DLodLevel: + case TexsTarget.Texture2DDepthCompare: + case TexsTarget.Texture3D: + case TexsTarget.TextureCube: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case TexsTarget.Texture2DLodZeroDepthCompare: + case TexsTarget.Texture3DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(ConstF(0)); + break; + + case TexsTarget.Texture2DLodLevelDepthCompare: + case TexsTarget.TextureCubeLodLevel: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(Rb()); + break; + } + } + } + else if (texsType == TexsType.Tlds) + { + var tldsOp = context.GetOp(); + + type = ConvertSamplerType(tldsOp.Target); + + if (type == SamplerType.None) + { + context.TranslatorContext.GpuAccessor.Log("Invalid texel fetch sampler type."); + return; + } + + flags = ConvertTextureFlags(tldsOp.Target) | TextureFlags.IntCoords; + + if (tldsOp.Target == TldsTarget.Texture1DLodZero && + context.TranslatorContext.GpuAccessor.QuerySamplerType(tldsOp.TidB) == SamplerType.TextureBuffer) + { + type = SamplerType.TextureBuffer; + flags &= ~TextureFlags.LodLevel; + } + + switch (tldsOp.Target) + { + case TldsTarget.Texture1DLodZero: + sourcesList.Add(Ra()); + + if (type != SamplerType.TextureBuffer) + { + if (Sample1DAs2D) + { + sourcesList.Add(ConstF(0)); + + type &= ~SamplerType.Mask; + type |= SamplerType.Texture2D; + } + + sourcesList.Add(ConstF(0)); + } + break; + + case TldsTarget.Texture1DLodLevel: + sourcesList.Add(Ra()); + + if (Sample1DAs2D) + { + sourcesList.Add(ConstF(0)); + + type &= ~SamplerType.Mask; + type |= SamplerType.Texture2D; + } + + sourcesList.Add(Rb()); + break; + + case TldsTarget.Texture2DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(Const(0)); + break; + + case TldsTarget.Texture2DLodZeroOffset: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Const(0)); + break; + + case TldsTarget.Texture2DLodZeroMultisample: + case TldsTarget.Texture2DLodLevel: + case TldsTarget.Texture2DLodLevelOffset: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + break; + + case TldsTarget.Texture3DLodZero: + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + sourcesList.Add(Const(0)); + break; + + case TldsTarget.Texture2DArrayLodZero: + sourcesList.Add(Rb()); + sourcesList.Add(Rb()); + sourcesList.Add(Ra()); + sourcesList.Add(Const(0)); + break; + } + + if ((flags & TextureFlags.Offset) != 0) + { + AddTextureOffset(type.GetDimensions(), 4, 4); + } + } + else if (texsType == TexsType.Tld4s) + { + var tld4sOp = context.GetOp(); + + if (!(tld4sOp.Dc || tld4sOp.Aoffi)) + { + sourcesList.Add(Ra()); + sourcesList.Add(Rb()); + } + else + { + sourcesList.Add(Ra()); + sourcesList.Add(Ra()); + } + + type = SamplerType.Texture2D; + flags = TextureFlags.Gather; + + int depthCompareIndex = sourcesList.Count; + + if (tld4sOp.Aoffi) + { + AddTextureOffset(type.GetDimensions(), 8, 6); + + flags |= TextureFlags.Offset; + } + + if (tld4sOp.Dc) + { + sourcesList.Insert(depthCompareIndex, Rb()); + + type |= SamplerType.Shadow; + } + else + { + sourcesList.Add(Const((int)tld4sOp.TexComp)); + } + } + else + { + throw new ArgumentException($"Invalid TEXS type \"{texsType}\"."); + } + + Operand[] sources = sourcesList.ToArray(); + + Operand[] rd0 = new Operand[2] { ConstF(0), ConstF(0) }; + Operand[] rd1 = new Operand[2] { ConstF(0), ConstF(0) }; + + int handle = imm; + int componentMask = _maskLut[dest2 == RegisterConsts.RegisterZeroIndex ? 0 : 1][writeMask]; + + int componentsCount = BitOperations.PopCount((uint)componentMask); + + Operand[] dests = new Operand[componentsCount]; + + int outputIndex = 0; + + for (int i = 0; i < componentsCount; i++) + { + int high = i >> 1; + int low = i & 1; + + if (isF16) + { + dests[outputIndex++] = high != 0 + ? (rd1[low] = Local()) + : (rd0[low] = Local()); + } + else + { + int rdIndex = high != 0 ? dest2 : dest; + + if (rdIndex < RegisterConsts.RegisterZeroIndex) + { + rdIndex += low; + } + + dests[outputIndex++] = Register(rdIndex, RegisterType.Gpr); + } + } + + if (outputIndex != dests.Length) + { + Array.Resize(ref dests, outputIndex); + } + + EmitTextureSample(context, type, flags, handle, componentMask, dests, sources); + + if (isF16) + { + context.Copy(Register(dest, RegisterType.Gpr), context.PackHalf2x16(rd0[0], rd0[1])); + context.Copy(Register(dest2, RegisterType.Gpr), context.PackHalf2x16(rd1[0], rd1[1])); + } + } + + private static void EmitTld4( + EmitterContext context, + TexDim dimensions, + TexComp component, + int imm, + int componentMask, + int srcA, + int srcB, + int dest, + TexOffset offset, + bool hasDepthCompare, + bool isBindless) + { + if (dest == RegisterConsts.RegisterZeroIndex) + { + return; + } + + Operand Ra() + { + if (srcA > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcA++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (srcB > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcB++, RegisterType.Gpr)); + } + + bool isArray = + dimensions == TexDim.Array1d || + dimensions == TexDim.Array2d || + dimensions == TexDim.Array3d || + dimensions == TexDim.ArrayCube; + + Operand arrayIndex = isArray ? Ra() : null; + + List sourcesList = new(); + + SamplerType type = ConvertSamplerType(dimensions); + TextureFlags flags = TextureFlags.Gather; + + if (isBindless) + { + sourcesList.Add(Rb()); + + flags |= TextureFlags.Bindless; + } + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + bool is1DTo2D = Sample1DAs2D && (type & SamplerType.Mask) == SamplerType.Texture1D; + + if (is1DTo2D) + { + sourcesList.Add(ConstF(0)); + + type = SamplerType.Texture2D | (type & SamplerType.Array); + } + + if (isArray) + { + sourcesList.Add(arrayIndex); + } + + Operand[] packedOffs = new Operand[2]; + + bool hasAnyOffset = offset == TexOffset.Aoffi || offset == TexOffset.Ptp; + + packedOffs[0] = hasAnyOffset ? Rb() : null; + packedOffs[1] = offset == TexOffset.Ptp ? Rb() : null; + + if (hasDepthCompare) + { + sourcesList.Add(Rb()); + + type |= SamplerType.Shadow; + } + + if (hasAnyOffset) + { + int offsetTexelsCount = offset == TexOffset.Ptp ? 4 : 1; + + for (int index = 0; index < coordsCount * offsetTexelsCount; index++) + { + Operand packed = packedOffs[(index >> 2) & 1]; + + sourcesList.Add(context.BitfieldExtractS32(packed, Const((index & 3) * 8), Const(6))); + } + + if (is1DTo2D) + { + for (int index = 0; index < offsetTexelsCount; index++) + { + sourcesList.Add(Const(0)); + } + } + + flags |= offset == TexOffset.Ptp ? TextureFlags.Offsets : TextureFlags.Offset; + } + + if (!hasDepthCompare) + { + sourcesList.Add(Const((int)component)); + } + + Operand[] sources = sourcesList.ToArray(); + Operand[] dests = new Operand[BitOperations.PopCount((uint)componentMask)]; + + int outputIndex = 0; + + for (int i = 0; i < dests.Length; i++) + { + if (dest + i >= RegisterConsts.RegisterZeroIndex) + { + break; + } + + dests[outputIndex++] = Register(dest + i, RegisterType.Gpr); + } + + if (outputIndex != dests.Length) + { + Array.Resize(ref dests, outputIndex); + } + + EmitTextureSample(context, type, flags, imm, componentMask, dests, sources); + } + + private static void EmitTmml( + EmitterContext context, + TexDim dimensions, + int imm, + int componentMask, + int srcA, + int srcB, + int dest, + bool isBindless) + { + if (dest == RegisterConsts.RegisterZeroIndex) + { + return; + } + + Operand Ra() + { + if (srcA > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcA++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (srcB > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcB++, RegisterType.Gpr)); + } + + TextureFlags flags = TextureFlags.None; + + List sourcesList = new(); + + if (isBindless) + { + sourcesList.Add(Rb()); + + flags |= TextureFlags.Bindless; + } + + SamplerType type = ConvertSamplerType(dimensions); + + int coordsCount = type.GetDimensions(); + + bool isArray = + dimensions == TexDim.Array1d || + dimensions == TexDim.Array2d || + dimensions == TexDim.Array3d || + dimensions == TexDim.ArrayCube; + + Operand arrayIndex = isArray ? Ra() : null; + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + if (Sample1DAs2D && (type & SamplerType.Mask) == SamplerType.Texture1D) + { + sourcesList.Add(ConstF(0)); + + type = SamplerType.Texture2D | (type & SamplerType.Array); + } + + if (isArray) + { + sourcesList.Add(arrayIndex); + } + + Operand[] sources = sourcesList.ToArray(); + + Operand GetDest() + { + if (dest >= RegisterConsts.RegisterZeroIndex) + { + return null; + } + + return Register(dest++, RegisterType.Gpr); + } + + SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding( + Instruction.Lod, + type, + TextureFormat.Unknown, + flags, + TextureOperation.DefaultCbufSlot, + imm); + + for (int compMask = componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand d = GetDest(); + + if (d == null) + { + break; + } + + // Components z and w aren't standard, we return 0 in this case and add a comment. + if (compIndex >= 2) + { + context.Add(new CommentNode("Unsupported component z or w found")); + context.Copy(d, Const(0)); + } + else + { + // The instruction component order is the inverse of GLSL's. + Operand res = context.Lod(type, flags, setAndBinding, compIndex ^ 1, sources); + + res = context.FPMultiply(res, ConstF(256.0f)); + + Operand fixedPointValue = context.FP32ConvertToS32(res); + + context.Copy(d, fixedPointValue); + } + } + } + } + + private static void EmitTxd( + EmitterContext context, + TexDim dimensions, + int imm, + int componentMask, + int srcA, + int srcB, + int dest, + bool hasOffset, + bool isBindless) + { + if (dest == RegisterConsts.RegisterZeroIndex) + { + return; + } + + Operand Ra() + { + if (srcA > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcA++, RegisterType.Gpr)); + } + + Operand Rb() + { + if (srcB > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcB++, RegisterType.Gpr)); + } + + TextureFlags flags = TextureFlags.Derivatives; + + List sourcesList = new(); + + if (isBindless) + { + sourcesList.Add(Ra()); + + flags |= TextureFlags.Bindless; + } + + SamplerType type = ConvertSamplerType(dimensions); + + int coordsCount = type.GetDimensions(); + + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(Ra()); + } + + bool is1DTo2D = Sample1DAs2D && (type & SamplerType.Mask) == SamplerType.Texture1D; + + if (is1DTo2D) + { + sourcesList.Add(ConstF(0)); + + type = SamplerType.Texture2D | (type & SamplerType.Array); + } + + Operand packedParams = Ra(); + + bool isArray = + dimensions == TexDim.Array1d || + dimensions == TexDim.Array2d || + dimensions == TexDim.Array3d || + dimensions == TexDim.ArrayCube; + + if (isArray) + { + sourcesList.Add(context.BitwiseAnd(packedParams, Const(0xffff))); + } + + // Derivatives (X and Y). + for (int dIndex = 0; dIndex < 2 * coordsCount; dIndex++) + { + sourcesList.Add(Rb()); + + if (is1DTo2D) + { + sourcesList.Add(ConstF(0)); + } + } + + if (hasOffset) + { + for (int index = 0; index < coordsCount; index++) + { + sourcesList.Add(context.BitfieldExtractS32(packedParams, Const(16 + index * 4), Const(4))); + } + + if (is1DTo2D) + { + sourcesList.Add(Const(0)); + } + + flags |= TextureFlags.Offset; + } + + Operand[] sources = sourcesList.ToArray(); + Operand[] dests = new Operand[BitOperations.PopCount((uint)componentMask)]; + + int outputIndex = 0; + + for (int i = 0; i < dests.Length; i++) + { + if (dest + i >= RegisterConsts.RegisterZeroIndex) + { + break; + } + + dests[outputIndex++] = Register(dest + i, RegisterType.Gpr); + } + + if (outputIndex != dests.Length) + { + Array.Resize(ref dests, outputIndex); + } + + EmitTextureSample(context, type, flags, imm, componentMask, dests, sources); + } + + private static void EmitTxq( + EmitterContext context, + TexQuery query, + int imm, + int componentMask, + int srcA, + int dest, + bool isBindless) + { + if (dest == RegisterConsts.RegisterZeroIndex) + { + return; + } + + Operand Ra() + { + if (srcA > RegisterConsts.RegisterZeroIndex) + { + return Const(0); + } + + return context.Copy(Register(srcA++, RegisterType.Gpr)); + } + + List sourcesList = new(); + + if (isBindless) + { + sourcesList.Add(Ra()); + } + + sourcesList.Add(Ra()); + + Operand[] sources = sourcesList.ToArray(); + + Operand GetDest() + { + if (dest >= RegisterConsts.RegisterZeroIndex) + { + return null; + } + + return Register(dest++, RegisterType.Gpr); + } + + SamplerType type; + + if (isBindless) + { + if (query == TexQuery.TexHeaderTextureType) + { + type = SamplerType.Texture2D | SamplerType.Multisample; + } + else + { + type = (componentMask & 4) != 0 ? SamplerType.Texture3D : SamplerType.Texture2D; + } + } + else + { + type = context.TranslatorContext.GpuAccessor.QuerySamplerType(imm); + } + + TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None; + SetBindingPair setAndBinding; + + switch (query) + { + case TexQuery.TexHeaderDimension: + setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding( + Instruction.TextureQuerySize, + type, + TextureFormat.Unknown, + flags, + TextureOperation.DefaultCbufSlot, + imm); + + for (int compMask = componentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++) + { + if ((compMask & 1) != 0) + { + Operand d = GetDest(); + + if (d == null) + { + break; + } + + context.Copy(d, context.TextureQuerySize(type, flags, setAndBinding, compIndex, sources)); + } + } + break; + + case TexQuery.TexHeaderTextureType: + setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding( + Instruction.TextureQuerySamples, + type, + TextureFormat.Unknown, + flags, + TextureOperation.DefaultCbufSlot, + imm); + + if ((componentMask & 4) != 0) + { + // Skip first 2 components if necessary. + if ((componentMask & 1) != 0) + { + GetDest(); + } + + if ((componentMask & 2) != 0) + { + GetDest(); + } + + Operand d = GetDest(); + + if (d != null) + { + context.Copy(d, context.TextureQuerySamples(type, flags, setAndBinding, sources)); + } + } + break; + + default: + context.TranslatorContext.GpuAccessor.Log($"Invalid or unsupported query type \"{query}\"."); + break; + } + } + + private static void EmitTextureSample( + EmitterContext context, + SamplerType type, + TextureFlags flags, + int handle, + int componentMask, + Operand[] dests, + Operand[] sources) + { + SetBindingPair setAndBinding = flags.HasFlag(TextureFlags.Bindless) ? default : context.ResourceManager.GetTextureOrImageBinding( + Instruction.TextureSample, + type, + TextureFormat.Unknown, + flags, + TextureOperation.DefaultCbufSlot, + handle); + + context.TextureSample(type, flags, setAndBinding, componentMask, dests, sources); + } + + private static SamplerType ConvertSamplerType(TexDim dimensions) + { + return dimensions switch + { + TexDim._1d => SamplerType.Texture1D, + TexDim.Array1d => SamplerType.Texture1D | SamplerType.Array, + TexDim._2d => SamplerType.Texture2D, + TexDim.Array2d => SamplerType.Texture2D | SamplerType.Array, + TexDim._3d => SamplerType.Texture3D, + TexDim.Array3d => SamplerType.Texture3D | SamplerType.Array, + TexDim.Cube => SamplerType.TextureCube, + TexDim.ArrayCube => SamplerType.TextureCube | SamplerType.Array, + _ => throw new ArgumentException($"Invalid texture dimensions \"{dimensions}\"."), + }; + } + + private static SamplerType ConvertSamplerType(TexsTarget type) + { + switch (type) + { + case TexsTarget.Texture1DLodZero: + return SamplerType.Texture1D; + + case TexsTarget.Texture2D: + case TexsTarget.Texture2DLodZero: + case TexsTarget.Texture2DLodLevel: + return SamplerType.Texture2D; + + case TexsTarget.Texture2DDepthCompare: + case TexsTarget.Texture2DLodLevelDepthCompare: + case TexsTarget.Texture2DLodZeroDepthCompare: + return SamplerType.Texture2D | SamplerType.Shadow; + + case TexsTarget.Texture2DArray: + case TexsTarget.Texture2DArrayLodZero: + return SamplerType.Texture2D | SamplerType.Array; + + case TexsTarget.Texture2DArrayLodZeroDepthCompare: + return SamplerType.Texture2D | SamplerType.Array | SamplerType.Shadow; + + case TexsTarget.Texture3D: + case TexsTarget.Texture3DLodZero: + return SamplerType.Texture3D; + + case TexsTarget.TextureCube: + case TexsTarget.TextureCubeLodLevel: + return SamplerType.TextureCube; + } + + return SamplerType.None; + } + + private static SamplerType ConvertSamplerType(TldsTarget type) + { + switch (type) + { + case TldsTarget.Texture1DLodZero: + case TldsTarget.Texture1DLodLevel: + return SamplerType.Texture1D; + + case TldsTarget.Texture2DLodZero: + case TldsTarget.Texture2DLodZeroOffset: + case TldsTarget.Texture2DLodLevel: + case TldsTarget.Texture2DLodLevelOffset: + return SamplerType.Texture2D; + + case TldsTarget.Texture2DLodZeroMultisample: + return SamplerType.Texture2D | SamplerType.Multisample; + + case TldsTarget.Texture3DLodZero: + return SamplerType.Texture3D; + + case TldsTarget.Texture2DArrayLodZero: + return SamplerType.Texture2D | SamplerType.Array; + } + + return SamplerType.None; + } + + private static TextureFlags ConvertTextureFlags(TexsTarget type) + { + switch (type) + { + case TexsTarget.Texture1DLodZero: + case TexsTarget.Texture2DLodZero: + case TexsTarget.Texture2DLodLevel: + case TexsTarget.Texture2DLodLevelDepthCompare: + case TexsTarget.Texture2DLodZeroDepthCompare: + case TexsTarget.Texture2DArrayLodZero: + case TexsTarget.Texture2DArrayLodZeroDepthCompare: + case TexsTarget.Texture3DLodZero: + case TexsTarget.TextureCubeLodLevel: + return TextureFlags.LodLevel; + + case TexsTarget.Texture2D: + case TexsTarget.Texture2DDepthCompare: + case TexsTarget.Texture2DArray: + case TexsTarget.Texture3D: + case TexsTarget.TextureCube: + return TextureFlags.None; + } + + return TextureFlags.None; + } + + private static TextureFlags ConvertTextureFlags(TldsTarget type) + { + switch (type) + { + case TldsTarget.Texture1DLodZero: + case TldsTarget.Texture1DLodLevel: + case TldsTarget.Texture2DLodZero: + case TldsTarget.Texture2DLodLevel: + case TldsTarget.Texture2DLodZeroMultisample: + case TldsTarget.Texture3DLodZero: + case TldsTarget.Texture2DArrayLodZero: + return TextureFlags.LodLevel; + + case TldsTarget.Texture2DLodZeroOffset: + case TldsTarget.Texture2DLodLevelOffset: + return TextureFlags.LodLevel | TextureFlags.Offset; + } + + return TextureFlags.None; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoArithmetic.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoArithmetic.cs new file mode 100644 index 00000000..a0e9fb38 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoArithmetic.cs @@ -0,0 +1,117 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Vmad(EmitterContext context) + { + InstVmad op = context.GetOp(); + + bool aSigned = (op.ASelect & VectorSelect.S8B0) != 0; + bool bSigned = (op.BSelect & VectorSelect.S8B0) != 0; + + Operand srcA = InstEmitAluHelper.Extend(context, GetSrcReg(context, op.SrcA), op.ASelect); + Operand srcC = context.INegate(GetSrcReg(context, op.SrcC), op.AvgMode == AvgMode.NegB); + Operand srcB; + + if (op.BVideo) + { + srcB = InstEmitAluHelper.Extend(context, GetSrcReg(context, op.SrcB), op.BSelect); + } + else + { + int imm = op.Imm16; + + if (bSigned) + { + imm = (imm << 16) >> 16; + } + + srcB = Const(imm); + } + + Operand productLow = context.IMultiply(srcA, srcB); + Operand productHigh; + + if (aSigned == bSigned) + { + productHigh = aSigned + ? context.MultiplyHighS32(srcA, srcB) + : context.MultiplyHighU32(srcA, srcB); + } + else + { + Operand temp = aSigned + ? context.IMultiply(srcB, context.ShiftRightS32(srcA, Const(31))) + : context.IMultiply(srcA, context.ShiftRightS32(srcB, Const(31))); + + productHigh = context.IAdd(temp, context.MultiplyHighU32(srcA, srcB)); + } + + if (op.AvgMode == AvgMode.NegA) + { + (productLow, productHigh) = InstEmitAluHelper.NegateLong(context, productLow, productHigh); + } + + Operand resLow = InstEmitAluHelper.AddWithCarry(context, productLow, srcC, out Operand sumCarry); + Operand resHigh = context.IAdd(productHigh, sumCarry); + + if (op.AvgMode == AvgMode.PlusOne) + { + resLow = InstEmitAluHelper.AddWithCarry(context, resLow, Const(1), out Operand poCarry); + resHigh = context.IAdd(resHigh, poCarry); + } + + bool resSigned = op.ASelect == VectorSelect.S32 || + op.BSelect == VectorSelect.S32 || + op.AvgMode == AvgMode.NegB || + op.AvgMode == AvgMode.NegA; + + int shift = op.VideoScale switch + { + VideoScale.Shr7 => 7, + VideoScale.Shr15 => 15, + _ => 0, + }; + + if (shift != 0) + { + // Low = (Low >> Shift) | (High << (32 - Shift)) + // High >>= Shift + resLow = context.ShiftRightU32(resLow, Const(shift)); + resLow = context.BitwiseOr(resLow, context.ShiftLeft(resHigh, Const(32 - shift))); + resHigh = resSigned + ? context.ShiftRightS32(resHigh, Const(shift)) + : context.ShiftRightU32(resHigh, Const(shift)); + } + + Operand res = resLow; + + if (op.Sat) + { + Operand sign = context.ShiftRightS32(resHigh, Const(31)); + + if (resSigned) + { + Operand overflow = context.ICompareNotEqual(resHigh, context.ShiftRightS32(resLow, Const(31))); + Operand clampValue = context.ConditionalSelect(sign, Const(int.MinValue), Const(int.MaxValue)); + res = context.ConditionalSelect(overflow, clampValue, resLow); + } + else + { + Operand overflow = context.ICompareNotEqual(resHigh, Const(0)); + res = context.ConditionalSelect(overflow, context.BitwiseNot(sign), resLow); + } + } + + context.Copy(GetDest(op.Dest), res); + + // TODO: CC. + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoMinMax.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoMinMax.cs new file mode 100644 index 00000000..d52c972b --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoMinMax.cs @@ -0,0 +1,183 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Vmnmx(EmitterContext context) + { + InstVmnmx op = context.GetOp(); + + Operand srcA = InstEmitAluHelper.Extend(context, GetSrcReg(context, op.SrcA), op.ASelect); + Operand srcC = GetSrcReg(context, op.SrcC); + Operand srcB; + + if (op.BVideo) + { + srcB = InstEmitAluHelper.Extend(context, GetSrcReg(context, op.SrcB), op.BSelect); + } + else + { + int imm = op.Imm16; + + if ((op.BSelect & VectorSelect.S8B0) != 0) + { + imm = (imm << 16) >> 16; + } + + srcB = Const(imm); + } + + Operand res; + + bool resSigned; + + if ((op.ASelect & VectorSelect.S8B0) != (op.BSelect & VectorSelect.S8B0)) + { + // Signedness is different, but for max, result will always fit a U32, + // since one of the inputs can't be negative, and the result is the one + // with highest value. For min, it will always fit on a S32, since + // one of the input can't be greater than INT_MAX and we want the lowest value. + resSigned = !op.Mn; + + res = op.Mn ? context.IMaximumU32(srcA, srcB) : context.IMinimumS32(srcA, srcB); + + if ((op.ASelect & VectorSelect.S8B0) != 0) + { + Operand isBGtIntMax = context.ICompareLess(srcB, Const(0)); + + res = context.ConditionalSelect(isBGtIntMax, srcB, res); + } + else + { + Operand isAGtIntMax = context.ICompareLess(srcA, Const(0)); + + res = context.ConditionalSelect(isAGtIntMax, srcA, res); + } + } + else + { + // Ra and Rb have the same signedness, so doesn't matter which one we test. + resSigned = (op.ASelect & VectorSelect.S8B0) != 0; + + if (op.Mn) + { + res = resSigned + ? context.IMaximumS32(srcA, srcB) + : context.IMaximumU32(srcA, srcB); + } + else + { + res = resSigned + ? context.IMinimumS32(srcA, srcB) + : context.IMinimumU32(srcA, srcB); + } + } + + if (op.Sat) + { + if (op.DFormat && !resSigned) + { + res = context.IMinimumU32(res, Const(int.MaxValue)); + } + else if (!op.DFormat && resSigned) + { + res = context.IMaximumS32(res, Const(0)); + } + } + + switch (op.VideoOp) + { + case VideoOp.Acc: + res = context.IAdd(res, srcC); + break; + case VideoOp.Max: + res = op.DFormat ? context.IMaximumS32(res, srcC) : context.IMaximumU32(res, srcC); + break; + case VideoOp.Min: + res = op.DFormat ? context.IMinimumS32(res, srcC) : context.IMinimumU32(res, srcC); + break; + case VideoOp.Mrg16h: + res = context.BitfieldInsert(srcC, res, Const(16), Const(16)); + break; + case VideoOp.Mrg16l: + res = context.BitfieldInsert(srcC, res, Const(0), Const(16)); + break; + case VideoOp.Mrg8b0: + res = context.BitfieldInsert(srcC, res, Const(0), Const(8)); + break; + case VideoOp.Mrg8b2: + res = context.BitfieldInsert(srcC, res, Const(16), Const(8)); + break; + } + + context.Copy(GetDest(op.Dest), res); + } + + public static void Vsetp(EmitterContext context) + { + InstVsetp op = context.GetOp(); + + Operand srcA = InstEmitAluHelper.Extend(context, GetSrcReg(context, op.SrcA), op.ASelect); + Operand srcB; + + if (op.BVideo) + { + srcB = InstEmitAluHelper.Extend(context, GetSrcReg(context, op.SrcB), op.BSelect); + } + else + { + int imm = op.Imm16; + + if ((op.BSelect & VectorSelect.S8B0) != 0) + { + imm = (imm << 16) >> 16; + } + + srcB = Const(imm); + } + + Operand p0Res; + + bool signedA = (op.ASelect & VectorSelect.S8B0) != 0; + bool signedB = (op.BSelect & VectorSelect.S8B0) != 0; + + if (signedA != signedB) + { + bool a32 = (op.ASelect & ~VectorSelect.S8B0) == VectorSelect.U32; + bool b32 = (op.BSelect & ~VectorSelect.S8B0) == VectorSelect.U32; + + if (!a32 && !b32) + { + // Both values are extended small integer and can always fit in a S32, just do a signed comparison. + p0Res = GetIntComparison(context, op.VComp, srcA, srcB, isSigned: true, extended: false); + } + else + { + // TODO: Mismatching sign case. + p0Res = Const(0); + } + } + else + { + // Sign matches, just do a regular comparison. + p0Res = GetIntComparison(context, op.VComp, srcA, srcB, signedA, extended: false); + } + + Operand p1Res = context.BitwiseNot(p0Res); + + Operand pred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + + p0Res = InstEmitAluHelper.GetPredLogicalOp(context, op.BoolOp, p0Res, pred); + p1Res = InstEmitAluHelper.GetPredLogicalOp(context, op.BoolOp, p1Res, pred); + + context.Copy(Register(op.DestPred, RegisterType.Predicate), p0Res); + context.Copy(Register(op.DestPredInv, RegisterType.Predicate), p1Res); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitWarp.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitWarp.cs new file mode 100644 index 00000000..73eea5c3 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitWarp.cs @@ -0,0 +1,144 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static partial class InstEmit + { + public static void Fswzadd(EmitterContext context) + { + InstFswzadd op = context.GetOp(); + + Operand srcA = GetSrcReg(context, op.SrcA); + Operand srcB = GetSrcReg(context, op.SrcB); + Operand dest = GetDest(op.Dest); + + context.Copy(dest, context.FPSwizzleAdd(srcA, srcB, op.PnWord)); + + InstEmitAluHelper.SetFPZnFlags(context, dest, op.WriteCC); + } + + public static void Shfl(EmitterContext context) + { + InstShfl op = context.GetOp(); + + Operand pred = Register(op.DestPred, RegisterType.Predicate); + + Operand srcA = GetSrcReg(context, op.SrcA); + + Operand srcB = op.BFixShfl ? Const(op.SrcBImm) : GetSrcReg(context, op.SrcB); + Operand srcC = op.CFixShfl ? Const(op.SrcCImm) : GetSrcReg(context, op.SrcC); + + (Operand res, Operand valid) = op.ShflMode switch + { + ShflMode.Idx => context.Shuffle(srcA, srcB, srcC), + ShflMode.Up => context.ShuffleUp(srcA, srcB, srcC), + ShflMode.Down => context.ShuffleDown(srcA, srcB, srcC), + ShflMode.Bfly => context.ShuffleXor(srcA, srcB, srcC), + _ => (null, null), + }; + + context.Copy(GetDest(op.Dest), res); + context.Copy(pred, valid); + } + + public static void Vote(EmitterContext context) + { + InstVote op = context.GetOp(); + + Operand pred = GetPredicate(context, op.SrcPred, op.SrcPredInv); + Operand res = EmitVote(context, op.VoteMode, pred); + + if (res != null) + { + context.Copy(Register(op.VpDest, RegisterType.Predicate), res); + } + else + { + context.TranslatorContext.GpuAccessor.Log($"Invalid vote operation: {op.VoteMode}."); + } + + if (op.Dest != RegisterConsts.RegisterZeroIndex) + { + context.Copy(GetDest(op.Dest), EmitBallot(context, pred)); + } + } + + private static Operand EmitVote(EmitterContext context, VoteMode voteMode, Operand pred) + { + int subgroupSize = context.TranslatorContext.GpuAccessor.QueryHostSubgroupSize(); + + if (subgroupSize <= 32) + { + return voteMode switch + { + VoteMode.All => context.VoteAll(pred), + VoteMode.Any => context.VoteAny(pred), + VoteMode.Eq => context.VoteAllEqual(pred), + _ => null, + }; + } + + // Emulate vote with ballot masks. + // We do that when the GPU thread count is not 32, + // since the shader code assumes it is 32. + // allInvocations => ballot(pred) == ballot(true), + // anyInvocation => ballot(pred) != 0, + // allInvocationsEqual => ballot(pred) == balot(true) || ballot(pred) == 0 + Operand ballotMask = EmitBallot(context, pred); + + Operand AllTrue() => context.ICompareEqual(ballotMask, EmitBallot(context, Const(IrConsts.True))); + + return voteMode switch + { + VoteMode.All => AllTrue(), + VoteMode.Any => context.ICompareNotEqual(ballotMask, Const(0)), + VoteMode.Eq => context.BitwiseOr(AllTrue(), context.ICompareEqual(ballotMask, Const(0))), + _ => null, + }; + } + + private static Operand EmitBallot(EmitterContext context, Operand pred) + { + int subgroupSize = context.TranslatorContext.GpuAccessor.QueryHostSubgroupSize(); + + if (subgroupSize <= 32) + { + return context.Ballot(pred, 0); + } + else if (subgroupSize == 64) + { + // TODO: Add support for vector destination and do that with a single operation. + + Operand laneId = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId); + Operand low = context.Ballot(pred, 0); + Operand high = context.Ballot(pred, 1); + + return context.ConditionalSelect(context.BitwiseAnd(laneId, Const(32)), high, low); + } + else + { + // TODO: Add support for vector destination and do that with a single operation. + + Operand laneId = context.Load(StorageKind.Input, IoVariable.SubgroupLaneId); + Operand element = context.ShiftRightU32(laneId, Const(5)); + + Operand res = context.Ballot(pred, 0); + res = context.ConditionalSelect( + context.ICompareEqual(element, Const(1)), + context.Ballot(pred, 1), res); + res = context.ConditionalSelect( + context.ICompareEqual(element, Const(2)), + context.Ballot(pred, 2), res); + res = context.ConditionalSelect( + context.ICompareEqual(element, Const(3)), + context.Ballot(pred, 3), res); + + return res; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs new file mode 100644 index 00000000..e1cef26d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs @@ -0,0 +1,6 @@ +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + delegate void InstEmitter(EmitterContext context); +} diff --git a/src/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs b/src/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs new file mode 100644 index 00000000..6846ea8d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs @@ -0,0 +1,142 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Instructions +{ + static class Lop3Expression + { + private enum TruthTable : byte + { + False = 0x00, // false + True = 0xff, // true + In = 0xf0, // a + And2 = 0xc0, // a & b + Or2 = 0xfc, // a | b + Xor2 = 0x3c, // a ^ b + And3 = 0x80, // a & b & c + Or3 = 0xfe, // a | b | c + XorAnd = 0x60, // a & (b ^ c) + XorOr = 0xf6, // a | (b ^ c) + OrAnd = 0xe0, // a & (b | c) + AndOr = 0xf8, // a | (b & c) + Onehot = 0x16, // (a & !b & !c) | (!a & b & !c) | (!a & !b & c) - Only one value is true. + Majority = 0xe8, // Popcount(a, b, c) >= 2 + Gamble = 0x81, // (a & b & c) | (!a & !b & !c) - All on or all off + InverseGamble = 0x7e, // Inverse of Gamble + Dot = 0x1a, // a ^ (c | (a & b)) + Mux = 0xca, // a ? b : c + AndXor = 0x78, // a ^ (b & c) + OrXor = 0x1e, // a ^ (b | c) + Xor3 = 0x96, // a ^ b ^ c + } + + public static Operand GetFromTruthTable(EmitterContext context, Operand srcA, Operand srcB, Operand srcC, int imm) + { + for (int i = 0; i < 0x40; i++) + { + TruthTable currImm = (TruthTable)imm; + + Operand x = srcA; + Operand y = srcB; + Operand z = srcC; + + if ((i & 0x01) != 0) + { + (x, y) = (y, x); + currImm = PermuteTable(currImm, 7, 6, 3, 2, 5, 4, 1, 0); + } + + if ((i & 0x02) != 0) + { + (x, z) = (z, x); + currImm = PermuteTable(currImm, 7, 3, 5, 1, 6, 2, 4, 0); + } + + if ((i & 0x04) != 0) + { + (y, z) = (z, y); + currImm = PermuteTable(currImm, 7, 5, 6, 4, 3, 1, 2, 0); + } + + if ((i & 0x08) != 0) + { + x = context.BitwiseNot(x); + currImm = PermuteTable(currImm, 3, 2, 1, 0, 7, 6, 5, 4); + } + + if ((i & 0x10) != 0) + { + y = context.BitwiseNot(y); + currImm = PermuteTable(currImm, 5, 4, 7, 6, 1, 0, 3, 2); + } + + if ((i & 0x20) != 0) + { + z = context.BitwiseNot(z); + currImm = PermuteTable(currImm, 6, 7, 4, 5, 2, 3, 0, 1); + } + + Operand result = GetExpr(currImm, context, x, y, z); + if (result != null) + { + return result; + } + + Operand notResult = GetExpr((TruthTable)((~(int)currImm) & 0xff), context, x, y, z); + if (notResult != null) + { + return context.BitwiseNot(notResult); + } + } + + return null; + } + + private static Operand GetExpr(TruthTable imm, EmitterContext context, Operand x, Operand y, Operand z) + { + return imm switch + { +#pragma warning disable IDE0055 // Disable formatting + TruthTable.False => Const(0), + TruthTable.True => Const(-1), + TruthTable.In => x, + TruthTable.And2 => context.BitwiseAnd(x, y), + TruthTable.Or2 => context.BitwiseOr(x, y), + TruthTable.Xor2 => context.BitwiseExclusiveOr(x, y), + TruthTable.And3 => context.BitwiseAnd(x, context.BitwiseAnd(y, z)), + TruthTable.Or3 => context.BitwiseOr(x, context.BitwiseOr(y, z)), + TruthTable.XorAnd => context.BitwiseAnd(x, context.BitwiseExclusiveOr(y, z)), + TruthTable.XorOr => context.BitwiseOr(x, context.BitwiseExclusiveOr(y, z)), + TruthTable.OrAnd => context.BitwiseAnd(x, context.BitwiseOr(y, z)), + TruthTable.AndOr => context.BitwiseOr(x, context.BitwiseAnd(y, z)), + TruthTable.Onehot => context.BitwiseExclusiveOr(context.BitwiseOr(x, y), context.BitwiseOr(z, context.BitwiseAnd(x, y))), + TruthTable.Majority => context.BitwiseAnd(context.BitwiseOr(x, y), context.BitwiseOr(z, context.BitwiseAnd(x, y))), + TruthTable.InverseGamble => context.BitwiseOr(context.BitwiseExclusiveOr(x, y), context.BitwiseExclusiveOr(x, z)), + TruthTable.Dot => context.BitwiseAnd(context.BitwiseExclusiveOr(x, z), context.BitwiseOr(context.BitwiseNot(y), z)), + TruthTable.Mux => context.BitwiseOr(context.BitwiseAnd(x, y), context.BitwiseAnd(context.BitwiseNot(x), z)), + TruthTable.AndXor => context.BitwiseExclusiveOr(x, context.BitwiseAnd(y, z)), + TruthTable.OrXor => context.BitwiseExclusiveOr(x, context.BitwiseOr(y, z)), + TruthTable.Xor3 => context.BitwiseExclusiveOr(x, context.BitwiseExclusiveOr(y, z)), + _ => null, +#pragma warning restore IDE0055 + }; + } + + private static TruthTable PermuteTable(TruthTable imm, int bit7, int bit6, int bit5, int bit4, int bit3, int bit2, int bit1, int bit0) + { + int result = 0; + + result |= (((int)imm >> 0) & 1) << bit0; + result |= (((int)imm >> 1) & 1) << bit1; + result |= (((int)imm >> 2) & 1) << bit2; + result |= (((int)imm >> 3) & 1) << bit3; + result |= (((int)imm >> 4) & 1) << bit4; + result |= (((int)imm >> 5) & 1) << bit5; + result |= (((int)imm >> 6) & 1) << bit6; + result |= (((int)imm >> 7) & 1) << bit7; + + return (TruthTable)result; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs new file mode 100644 index 00000000..637e120e --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class BasicBlock + { + public int Index { get; set; } + + public LinkedList Operations { get; } + + private BasicBlock _next; + private BasicBlock _branch; + + public BasicBlock Next + { + get => _next; + set => _next = AddSuccessor(_next, value); + } + + public BasicBlock Branch + { + get => _branch; + set => _branch = AddSuccessor(_branch, value); + } + + public bool HasBranch => _branch != null; + public bool Reachable => Index == 0 || Predecessors.Count != 0; + + public List Predecessors { get; } + + public HashSet DominanceFrontiers { get; } + + public BasicBlock ImmediateDominator { get; set; } + + public BasicBlock() + { + Operations = new LinkedList(); + + Predecessors = new List(); + + DominanceFrontiers = new HashSet(); + } + + public BasicBlock(int index) : this() + { + Index = index; + } + + private BasicBlock AddSuccessor(BasicBlock oldBlock, BasicBlock newBlock) + { + oldBlock?.Predecessors.Remove(this); + newBlock?.Predecessors.Add(this); + + return newBlock; + } + + public INode GetLastOp() + { + return Operations.Last?.Value; + } + + public void Append(INode node) + { + INode lastOp = GetLastOp(); + + if (lastOp is Operation operation && IsControlFlowInst(operation.Inst)) + { + Operations.AddBefore(Operations.Last, node); + } + else + { + Operations.AddLast(node); + } + } + + private static bool IsControlFlowInst(Instruction inst) + { + switch (inst) + { + case Instruction.Branch: + case Instruction.BranchIfFalse: + case Instruction.BranchIfTrue: + case Instruction.Discard: + case Instruction.Return: + return true; + default: + return false; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs new file mode 100644 index 00000000..1d33a9b0 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class CommentNode : Operation + { + public string Comment { get; } + + public CommentNode(string comment) : base(Instruction.Comment, null) + { + Comment = comment; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Function.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Function.cs new file mode 100644 index 00000000..a5f3e0a8 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Function.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class Function + { + public BasicBlock[] Blocks { get; } + + public string Name { get; } + + public bool ReturnsValue { get; } + + public int InArgumentsCount { get; } + public int OutArgumentsCount { get; } + + public Function(BasicBlock[] blocks, string name, bool returnsValue, int inArgumentsCount, int outArgumentsCount) + { + Blocks = blocks; + Name = name; + ReturnsValue = returnsValue; + InArgumentsCount = inArgumentsCount; + OutArgumentsCount = outArgumentsCount; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs new file mode 100644 index 00000000..d5eae00b --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + interface INode + { + Operand Dest { get; set; } + + int DestsCount { get; } + int SourcesCount { get; } + + Operand GetDest(int index); + Operand GetSource(int index); + + void SetSource(int index, Operand operand); + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs new file mode 100644 index 00000000..273a38a5 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs @@ -0,0 +1,197 @@ +using System; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + [Flags] + enum Instruction + { + Absolute = 1, + Add, + AtomicAdd, + AtomicAnd, + AtomicCompareAndSwap, + AtomicMinS32, + AtomicMinU32, + AtomicMaxS32, + AtomicMaxU32, + AtomicOr, + AtomicSwap, + AtomicXor, + Ballot, + Barrier, + BitCount, + BitfieldExtractS32, + BitfieldExtractU32, + BitfieldInsert, + BitfieldReverse, + BitwiseAnd, + BitwiseExclusiveOr, + BitwiseNot, + BitwiseOr, + Branch, + BranchIfFalse, + BranchIfTrue, + Call, + Ceiling, + Clamp, + ClampU32, + Comment, + CompareEqual, + CompareGreater, + CompareGreaterOrEqual, + CompareGreaterOrEqualU32, + CompareGreaterU32, + CompareLess, + CompareLessOrEqual, + CompareLessOrEqualU32, + CompareLessU32, + CompareNotEqual, + ConditionalSelect, + ConvertFP32ToFP64, + ConvertFP64ToFP32, + ConvertFP32ToS32, + ConvertFP32ToU32, + ConvertFP64ToS32, + ConvertFP64ToU32, + ConvertS32ToFP32, + ConvertS32ToFP64, + ConvertU32ToFP32, + ConvertU32ToFP64, + Copy, + Cosine, + Ddx, + Ddy, + Discard, + Divide, + EmitVertex, + EndPrimitive, + ExponentB2, + FSIBegin, + FSIEnd, + FindLSB, + FindMSBS32, + FindMSBU32, + Floor, + FusedMultiplyAdd, + GroupMemoryBarrier, + ImageLoad, + ImageStore, + ImageAtomic, + IsNan, + Load, + Lod, + LogarithmB2, + LogicalAnd, + LogicalExclusiveOr, + LogicalNot, + LogicalOr, + LoopBreak, + LoopContinue, + MarkLabel, + Maximum, + MaximumU32, + MemoryBarrier, + Minimum, + MinimumU32, + Modulo, + Multiply, + MultiplyHighS32, + MultiplyHighU32, + Negate, + PackDouble2x32, + PackHalf2x16, + ReciprocalSquareRoot, + Return, + Round, + ShiftLeft, + ShiftRightS32, + ShiftRightU32, + Shuffle, + ShuffleDown, + ShuffleUp, + ShuffleXor, + Sine, + SquareRoot, + Store, + Subtract, + SwizzleAdd, + TextureSample, + TextureQuerySamples, + TextureQuerySize, + Truncate, + UnpackDouble2x32, + UnpackHalf2x16, + VectorExtract, + VoteAll, + VoteAllEqual, + VoteAny, + + Count, + + FP32 = 1 << 16, + FP64 = 1 << 17, + + Mask = 0xffff, + } + + static class InstructionExtensions + { + public static bool IsAtomic(this Instruction inst) + { + switch (inst & Instruction.Mask) + { + case Instruction.AtomicAdd: + case Instruction.AtomicAnd: + case Instruction.AtomicCompareAndSwap: + case Instruction.AtomicMaxS32: + case Instruction.AtomicMaxU32: + case Instruction.AtomicMinS32: + case Instruction.AtomicMinU32: + case Instruction.AtomicOr: + case Instruction.AtomicSwap: + case Instruction.AtomicXor: + return true; + } + + return false; + } + + public static bool IsComparison(this Instruction inst) + { + switch (inst & Instruction.Mask) + { + case Instruction.CompareEqual: + case Instruction.CompareGreater: + case Instruction.CompareGreaterOrEqual: + case Instruction.CompareGreaterOrEqualU32: + case Instruction.CompareGreaterU32: + case Instruction.CompareLess: + case Instruction.CompareLessOrEqual: + case Instruction.CompareLessOrEqualU32: + case Instruction.CompareLessU32: + case Instruction.CompareNotEqual: + return true; + } + + return false; + } + + public static bool IsTextureQuery(this Instruction inst) + { + inst &= Instruction.Mask; + return inst == Instruction.Lod || inst == Instruction.TextureQuerySamples || inst == Instruction.TextureQuerySize; + } + + public static bool IsImage(this Instruction inst) + { + inst &= Instruction.Mask; + return inst == Instruction.ImageAtomic || inst == Instruction.ImageLoad || inst == Instruction.ImageStore; + } + + public static bool IsImageStore(this Instruction inst) + { + inst &= Instruction.Mask; + return inst == Instruction.ImageAtomic || inst == Instruction.ImageStore; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IoVariable.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IoVariable.cs new file mode 100644 index 00000000..21e20863 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IoVariable.cs @@ -0,0 +1,49 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + enum IoVariable + { + Invalid, + + BackColorDiffuse, + BackColorSpecular, + BaseInstance, + BaseVertex, + ClipDistance, + CtaId, + DrawIndex, + FogCoord, + FragmentCoord, + FragmentOutputColor, + FragmentOutputDepth, + FrontColorDiffuse, + FrontColorSpecular, + FrontFacing, + GlobalId, + InstanceId, + InstanceIndex, + InvocationId, + Layer, + PatchVertices, + PointCoord, + PointSize, + Position, + PrimitiveId, + SubgroupEqMask, + SubgroupGeMask, + SubgroupGtMask, + SubgroupLaneId, + SubgroupLeMask, + SubgroupLtMask, + TessellationCoord, + TessellationLevelInner, + TessellationLevelOuter, + TextureCoord, + ThreadId, + ThreadKill, + UserDefined, + VertexId, + VertexIndex, + ViewportIndex, + ViewportMask, + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs new file mode 100644 index 00000000..cc9d6cc2 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + static class IrConsts + { + public const int False = 0; + public const int True = -1; + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs new file mode 100644 index 00000000..6648457f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs @@ -0,0 +1,79 @@ +using Ryujinx.Graphics.Shader.Decoders; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class Operand + { + private const int CbufSlotBits = 5; + private const int CbufSlotLsb = 32 - CbufSlotBits; + private const int CbufSlotMask = (1 << CbufSlotBits) - 1; + + public OperandType Type { get; } + + public int Value { get; } + + public INode AsgOp { get; set; } + + public HashSet UseOps { get; } + + private Operand() + { + UseOps = new HashSet(); + } + + public Operand(OperandType type) : this() + { + Type = type; + } + + public Operand(OperandType type, int value) : this() + { + Type = type; + Value = value; + } + + public Operand(Register reg) : this() + { + Type = OperandType.Register; + Value = PackRegInfo(reg.Index, reg.Type); + } + + public Operand(int slot, int offset) : this() + { + Type = OperandType.ConstantBuffer; + Value = PackCbufInfo(slot, offset); + } + + private static int PackCbufInfo(int slot, int offset) + { + return (slot << CbufSlotLsb) | offset; + } + + private static int PackRegInfo(int index, RegisterType type) + { + return ((int)type << 24) | index; + } + + public int GetCbufSlot() + { + return (Value >> CbufSlotLsb) & CbufSlotMask; + } + + public int GetCbufOffset() + { + return Value & ~(CbufSlotMask << CbufSlotLsb); + } + + public Register GetRegister() + { + return new Register(Value & 0xffffff, (RegisterType)(Value >> 24)); + } + + public float AsFloat() + { + return BitConverter.Int32BitsToSingle(Value); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs new file mode 100644 index 00000000..f8831355 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs @@ -0,0 +1,62 @@ +using Ryujinx.Graphics.Shader.Decoders; +using System; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + static class OperandHelper + { + public static Operand Argument(int value) + { + return new Operand(OperandType.Argument, value); + } + + public static Operand Cbuf(int slot, int offset) + { + return new Operand(slot, offset); + } + + public static Operand Const(int value) + { + return new Operand(OperandType.Constant, value); + } + + public static Operand ConstF(float value) + { + return new Operand(OperandType.Constant, BitConverter.SingleToInt32Bits(value)); + } + + public static Operand Label() + { + return new Operand(OperandType.Label); + } + + public static Operand Local() + { + return new Operand(OperandType.LocalVariable); + } + + public static Operand Register(int index, RegisterType type) + { + return Register(new Register(index, type)); + } + + public static Operand Register(Register reg) + { + if (reg.IsRZ) + { + return Const(0); + } + else if (reg.IsPT) + { + return Const(IrConsts.True); + } + + return new Operand(reg); + } + + public static Operand Undef() + { + return new Operand(OperandType.Undefined); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs new file mode 100644 index 00000000..7dbd9d25 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + enum OperandType + { + Argument, + Constant, + ConstantBuffer, + Label, + LocalVariable, + Register, + Undefined, + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs new file mode 100644 index 00000000..713e8a4f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs @@ -0,0 +1,294 @@ +using System; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class Operation : INode + { + public Instruction Inst { get; private set; } + public StorageKind StorageKind { get; } + + public bool ForcePrecise { get; set; } + + private Operand[] _dests; + + public Operand Dest + { + get + { + return _dests.Length != 0 ? _dests[0] : null; + } + set + { + if (value != null) + { + if (value.Type == OperandType.LocalVariable) + { + value.AsgOp = this; + } + + _dests = new[] { value }; + } + else + { + _dests = Array.Empty(); + } + } + } + + public int DestsCount => _dests.Length; + + private Operand[] _sources; + + public int SourcesCount => _sources.Length; + + public int Index { get; } + + private Operation(Operand[] sources) + { + // The array may be modified externally, so we store a copy. + _sources = (Operand[])sources.Clone(); + + for (int index = 0; index < _sources.Length; index++) + { + Operand source = _sources[index]; + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + } + } + + public Operation(Instruction inst, int index, Operand[] dests, Operand[] sources) : this(sources) + { + Inst = inst; + Index = index; + + if (dests != null) + { + // The array may be modified externally, so we store a copy. + _dests = (Operand[])dests.Clone(); + + for (int dstIndex = 0; dstIndex < dests.Length; dstIndex++) + { + Operand dest = dests[dstIndex]; + + if (dest != null && dest.Type == OperandType.LocalVariable) + { + dest.AsgOp = this; + } + } + } + else + { + _dests = Array.Empty(); + } + } + + public Operation(Instruction inst, Operand dest, params Operand[] sources) : this(sources) + { + Inst = inst; + + if (dest != null) + { + dest.AsgOp = this; + + _dests = new[] { dest }; + } + else + { + _dests = Array.Empty(); + } + } + + 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(); + } + } + + public Operation(Instruction inst, int index, Operand dest, params Operand[] sources) : this(inst, dest, sources) + { + Index = index; + } + + public void AppendDests(Operand[] operands) + { + int startIndex = _dests.Length; + + Array.Resize(ref _dests, startIndex + operands.Length); + + for (int index = 0; index < operands.Length; index++) + { + Operand dest = operands[index]; + + if (dest != null && dest.Type == OperandType.LocalVariable) + { + Debug.Assert(dest.AsgOp == null); + dest.AsgOp = this; + } + + _dests[startIndex + index] = dest; + } + } + + public void AppendSources(Operand[] operands) + { + int startIndex = _sources.Length; + + Array.Resize(ref _sources, startIndex + operands.Length); + + for (int index = 0; index < operands.Length; index++) + { + Operand source = operands[index]; + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources[startIndex + index] = source; + } + } + + public Operand GetDest(int index) + { + return _dests[index]; + } + + public Operand GetSource(int index) + { + return _sources[index]; + } + + public void SetDest(int index, Operand dest) + { + Operand oldDest = _dests[index]; + + if (oldDest != null && oldDest.Type == OperandType.LocalVariable) + { + oldDest.AsgOp = null; + } + + if (dest != null && dest.Type == OperandType.LocalVariable) + { + dest.AsgOp = this; + } + + _dests[index] = dest; + } + + public void SetSource(int index, Operand source) + { + Operand oldSrc = _sources[index]; + + if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable) + { + oldSrc.UseOps.Remove(this); + } + + if (source != null && source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources[index] = source; + } + + public void InsertSource(int index, Operand source) + { + Operand[] newSources = new Operand[_sources.Length + 1]; + + Array.Copy(_sources, 0, newSources, 0, index); + Array.Copy(_sources, index, newSources, index + 1, _sources.Length - index); + + newSources[index] = source; + + if (source != null && source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources = newSources; + } + + protected void RemoveSource(int index) + { + SetSource(index, null); + + Operand[] newSources = new Operand[_sources.Length - 1]; + + Array.Copy(_sources, 0, newSources, 0, index); + Array.Copy(_sources, index + 1, newSources, index, _sources.Length - (index + 1)); + + _sources = newSources; + } + + public void TurnIntoCopy(Operand source) + { + TurnInto(Instruction.Copy, source); + } + + public void TurnInto(Instruction newInst, Operand source) + { + Inst = newInst; + + foreach (Operand oldSrc in _sources) + { + if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable) + { + oldSrc.UseOps.Remove(this); + } + } + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources = new Operand[] { source }; + } + + public void TurnDoubleIntoFloat() + { + if ((Inst & ~Instruction.Mask) == Instruction.FP64) + { + Inst = (Inst & Instruction.Mask) | Instruction.FP32; + } + else + { + switch (Inst) + { + case Instruction.ConvertFP32ToFP64: + case Instruction.ConvertFP64ToFP32: + Inst = Instruction.Copy; + break; + case Instruction.ConvertFP64ToS32: + Inst = Instruction.ConvertFP32ToS32; + break; + case Instruction.ConvertFP64ToU32: + Inst = Instruction.ConvertFP32ToU32; + break; + case Instruction.ConvertS32ToFP64: + Inst = Instruction.ConvertS32ToFP32; + break; + case Instruction.ConvertU32ToFP64: + Inst = Instruction.ConvertU32ToFP32; + break; + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs new file mode 100644 index 00000000..f4c4fef4 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class PhiNode : INode + { + private Operand _dest; + + public Operand Dest + { + get => _dest; + set => _dest = AssignDest(value); + } + + public int DestsCount => _dest != null ? 1 : 0; + + private readonly HashSet _blocks; + + private class PhiSource + { + public BasicBlock Block { get; } + public Operand Operand { get; set; } + + public PhiSource(BasicBlock block, Operand operand) + { + Block = block; + Operand = operand; + } + } + + private readonly List _sources; + + public int SourcesCount => _sources.Count; + + public PhiNode(Operand dest) + { + _blocks = new HashSet(); + + _sources = new List(); + + dest.AsgOp = this; + + Dest = dest; + } + + private Operand AssignDest(Operand dest) + { + if (dest != null && dest.Type == OperandType.LocalVariable) + { + dest.AsgOp = this; + } + + return dest; + } + + public void AddSource(BasicBlock block, Operand operand) + { + if (_blocks.Add(block)) + { + if (operand.Type == OperandType.LocalVariable) + { + operand.UseOps.Add(this); + } + + _sources.Add(new PhiSource(block, operand)); + } + } + + public Operand GetDest(int index) + { + ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0); + + return _dest; + } + + public Operand GetSource(int index) + { + return _sources[index].Operand; + } + + public BasicBlock GetBlock(int index) + { + return _sources[index].Block; + } + + public void SetSource(int index, Operand source) + { + Operand oldSrc = _sources[index].Operand; + + if (oldSrc != null && oldSrc.Type == OperandType.LocalVariable) + { + oldSrc.UseOps.Remove(this); + } + + if (source.Type == OperandType.LocalVariable) + { + source.UseOps.Add(this); + } + + _sources[index].Operand = source; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/StorageKind.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/StorageKind.cs new file mode 100644 index 00000000..669c1281 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/StorageKind.cs @@ -0,0 +1,45 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + enum StorageKind + { + None, + Input, + InputPerPatch, + Output, + OutputPerPatch, + ConstantBuffer, + StorageBuffer, + LocalMemory, + SharedMemory, + SharedMemory8, // TODO: Remove this and store type as a field on the Operation class itself. + SharedMemory16, // TODO: Remove this and store type as a field on the Operation class itself. + GlobalMemory, + GlobalMemoryS8, // TODO: Remove this and store type as a field on the Operation class itself. + GlobalMemoryS16, // TODO: Remove this and store type as a field on the Operation class itself. + GlobalMemoryU8, // TODO: Remove this and store type as a field on the Operation class itself. + GlobalMemoryU16, // TODO: Remove this and store type as a field on the Operation class itself. + } + + 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; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs new file mode 100644 index 00000000..51ff09cf --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs @@ -0,0 +1,34 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + [Flags] + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + enum TextureFlags + { + None = 0, + Bindless = 1 << 0, + Gather = 1 << 1, + Derivatives = 1 << 2, + IntCoords = 1 << 3, + LodBias = 1 << 4, + LodLevel = 1 << 5, + Offset = 1 << 6, + Offsets = 1 << 7, + Coherent = 1 << 8, + + AtomicMask = 15 << 16, + + Add = 0 << 16, + Minimum = 1 << 16, + Maximum = 2 << 16, + Increment = 3 << 16, + Decrement = 4 << 16, + BitwiseAnd = 5 << 16, + BitwiseOr = 6 << 16, + BitwiseXor = 7 << 16, + Swap = 8 << 16, + CAS = 9 << 16, + } +} diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs new file mode 100644 index 00000000..7eee8f2e --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs @@ -0,0 +1,69 @@ +namespace Ryujinx.Graphics.Shader.IntermediateRepresentation +{ + class TextureOperation : Operation + { + public const int DefaultCbufSlot = -1; + + public SamplerType Type { get; set; } + public TextureFormat Format { get; set; } + public TextureFlags Flags { get; private set; } + + public int Set { get; private set; } + public int Binding { get; private set; } + public int SamplerSet { get; private set; } + public int SamplerBinding { get; private set; } + + public TextureOperation( + Instruction inst, + SamplerType type, + TextureFormat format, + TextureFlags flags, + int set, + int binding, + int compIndex, + Operand[] dests, + Operand[] sources) : base(inst, compIndex, dests, sources) + { + Type = type; + Format = format; + Flags = flags; + Set = set; + Binding = binding; + SamplerSet = -1; + SamplerBinding = -1; + } + + public void TurnIntoArray(SetBindingPair setAndBinding) + { + Flags &= ~TextureFlags.Bindless; + Set = setAndBinding.SetIndex; + Binding = setAndBinding.Binding; + } + + public void TurnIntoArray(SetBindingPair textureSetAndBinding, SetBindingPair samplerSetAndBinding) + { + TurnIntoArray(textureSetAndBinding); + + SamplerSet = samplerSetAndBinding.SetIndex; + SamplerBinding = samplerSetAndBinding.Binding; + } + + public void SetBinding(SetBindingPair setAndBinding) + { + if ((Flags & TextureFlags.Bindless) != 0) + { + Flags &= ~TextureFlags.Bindless; + + RemoveSource(0); + } + + Set = setAndBinding.SetIndex; + Binding = setAndBinding.Binding; + } + + public void SetLodLevelFlag() + { + Flags |= TextureFlags.LodLevel; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/OutputTopology.cs b/src/Ryujinx.Graphics.Shader/OutputTopology.cs new file mode 100644 index 00000000..dc4b304a --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/OutputTopology.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.Shader +{ + enum OutputTopology + { + PointList = 1, + LineStrip = 6, + TriangleStrip = 7, + } + + static class OutputTopologyExtensions + { + public static string ToGlslString(this OutputTopology topology) + { + return topology switch + { + OutputTopology.LineStrip => "line_strip", + OutputTopology.PointList => "points", + OutputTopology.TriangleStrip => "triangle_strip", + _ => "points", + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs b/src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs new file mode 100644 index 00000000..c0bae8ea --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/ResourceReservationCounts.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader +{ + public readonly struct ResourceReservationCounts + { + public readonly int ReservedConstantBuffers { get; } + public readonly int ReservedStorageBuffers { get; } + public readonly int ReservedTextures { get; } + public readonly int ReservedImages { get; } + + public ResourceReservationCounts(bool isTransformFeedbackEmulated, bool vertexAsCompute) + { + ResourceReservations reservations = new(isTransformFeedbackEmulated, vertexAsCompute); + + ReservedConstantBuffers = reservations.ReservedConstantBuffers; + ReservedStorageBuffers = reservations.ReservedStorageBuffers; + ReservedTextures = reservations.ReservedTextures; + ReservedImages = reservations.ReservedImages; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj b/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj new file mode 100644 index 00000000..8ccf5348 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + + + + + + + + + + + + + + diff --git a/src/Ryujinx.Graphics.Shader/SamplerType.cs b/src/Ryujinx.Graphics.Shader/SamplerType.cs new file mode 100644 index 00000000..a693495f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/SamplerType.cs @@ -0,0 +1,159 @@ +using Ryujinx.Graphics.Shader.Translation; +using System; + +namespace Ryujinx.Graphics.Shader +{ + [Flags] + public enum SamplerType + { + None = 0, + Texture1D, + TextureBuffer, + Texture2D, + Texture3D, + TextureCube, + + Mask = 0xff, + + Array = 1 << 8, + Multisample = 1 << 9, + Shadow = 1 << 10, + } + + static class SamplerTypeExtensions + { + public static int GetDimensions(this SamplerType type) + { + return (type & SamplerType.Mask) switch + { + SamplerType.Texture1D => 1, + SamplerType.TextureBuffer => 1, + SamplerType.Texture2D => 2, + SamplerType.Texture3D => 3, + SamplerType.TextureCube => 3, + _ => throw new ArgumentException($"Invalid sampler type \"{type}\"."), + }; + } + + public static string ToShortSamplerType(this SamplerType type) + { + string typeName = (type & SamplerType.Mask) switch + { + SamplerType.Texture1D => "1d", + SamplerType.TextureBuffer => "b", + SamplerType.Texture2D => "2d", + SamplerType.Texture3D => "3d", + SamplerType.TextureCube => "cube", + _ => throw new ArgumentException($"Invalid sampler type \"{type}\"."), + }; + + if ((type & SamplerType.Multisample) != 0) + { + typeName += "ms"; + } + + if ((type & SamplerType.Array) != 0) + { + typeName += "a"; + } + + if ((type & SamplerType.Shadow) != 0) + { + typeName += "s"; + } + + return typeName; + } + + public static string ToGlslSamplerType(this SamplerType type) + { + string typeName = (type & SamplerType.Mask) switch + { + SamplerType.None => "sampler", + SamplerType.Texture1D => "sampler1D", + SamplerType.TextureBuffer => "samplerBuffer", + SamplerType.Texture2D => "sampler2D", + SamplerType.Texture3D => "sampler3D", + SamplerType.TextureCube => "samplerCube", + _ => throw new ArgumentException($"Invalid sampler type \"{type}\"."), + }; + + if ((type & SamplerType.Multisample) != 0) + { + typeName += "MS"; + } + + if ((type & SamplerType.Array) != 0) + { + typeName += "Array"; + } + + if ((type & SamplerType.Shadow) != 0) + { + typeName += "Shadow"; + } + + return typeName; + } + + public static string ToGlslTextureType(this SamplerType type) + { + string typeName = (type & SamplerType.Mask) switch + { + SamplerType.Texture1D => "texture1D", + SamplerType.TextureBuffer => "textureBuffer", + SamplerType.Texture2D => "texture2D", + SamplerType.Texture3D => "texture3D", + SamplerType.TextureCube => "textureCube", + _ => throw new ArgumentException($"Invalid texture type \"{type}\"."), + }; + + if ((type & SamplerType.Multisample) != 0) + { + typeName += "MS"; + } + + if ((type & SamplerType.Array) != 0) + { + typeName += "Array"; + } + + return typeName; + } + + public static string ToGlslImageType(this SamplerType type, AggregateType componentType) + { + string typeName = (type & SamplerType.Mask) switch + { + SamplerType.Texture1D => "image1D", + SamplerType.TextureBuffer => "imageBuffer", + SamplerType.Texture2D => "image2D", + SamplerType.Texture3D => "image3D", + SamplerType.TextureCube => "imageCube", + _ => throw new ArgumentException($"Invalid sampler type \"{type}\"."), + }; + + if ((type & SamplerType.Multisample) != 0) + { + typeName += "MS"; + } + + if ((type & SamplerType.Array) != 0) + { + typeName += "Array"; + } + + switch (componentType) + { + case AggregateType.U32: + typeName = 'u' + typeName; + break; + case AggregateType.S32: + typeName = 'i' + typeName; + break; + } + + return typeName; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/SetBindingPair.cs b/src/Ryujinx.Graphics.Shader/SetBindingPair.cs new file mode 100644 index 00000000..1e8a4f9c --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/SetBindingPair.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Graphics.Shader +{ + public readonly record struct SetBindingPair(int SetIndex, int Binding); +} diff --git a/src/Ryujinx.Graphics.Shader/ShaderProgram.cs b/src/Ryujinx.Graphics.Shader/ShaderProgram.cs new file mode 100644 index 00000000..9e62491b --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/ShaderProgram.cs @@ -0,0 +1,35 @@ +using Ryujinx.Graphics.Shader.Translation; +using System; + +namespace Ryujinx.Graphics.Shader +{ + public class ShaderProgram + { + public ShaderProgramInfo Info { get; } + public TargetLanguage Language { get; } + + public string Code { get; private set; } + public byte[] BinaryCode { get; } + + private ShaderProgram(ShaderProgramInfo info, TargetLanguage language) + { + Info = info; + Language = language; + } + + public ShaderProgram(ShaderProgramInfo info, TargetLanguage language, string code) : this(info, language) + { + Code = code; + } + + public ShaderProgram(ShaderProgramInfo info, TargetLanguage language, byte[] binaryCode) : this(info, language) + { + BinaryCode = binaryCode; + } + + public void Prepend(string line) + { + Code = line + Environment.NewLine + Code; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs b/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs new file mode 100644 index 00000000..22823ac3 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.ObjectModel; + +namespace Ryujinx.Graphics.Shader +{ + public class ShaderProgramInfo + { + public ReadOnlyCollection CBuffers { get; } + public ReadOnlyCollection SBuffers { get; } + public ReadOnlyCollection Textures { get; } + public ReadOnlyCollection Images { get; } + + public ShaderStage Stage { get; } + public int GeometryVerticesPerPrimitive { get; } + public int GeometryMaxOutputVertices { get; } + public int ThreadsPerInputPrimitive { get; } + public bool UsesFragCoord { get; } + public bool UsesInstanceId { get; } + public bool UsesDrawParameters { get; } + public bool UsesRtLayer { get; } + public byte ClipDistancesWritten { get; } + public int FragmentOutputMap { get; } + + public ShaderProgramInfo( + BufferDescriptor[] cBuffers, + BufferDescriptor[] sBuffers, + TextureDescriptor[] textures, + TextureDescriptor[] images, + ShaderStage stage, + int geometryVerticesPerPrimitive, + int geometryMaxOutputVertices, + int threadsPerInputPrimitive, + bool usesFragCoord, + bool usesInstanceId, + bool usesDrawParameters, + bool usesRtLayer, + byte clipDistancesWritten, + int fragmentOutputMap) + { + CBuffers = Array.AsReadOnly(cBuffers); + SBuffers = Array.AsReadOnly(sBuffers); + Textures = Array.AsReadOnly(textures); + Images = Array.AsReadOnly(images); + + Stage = stage; + GeometryVerticesPerPrimitive = geometryVerticesPerPrimitive; + GeometryMaxOutputVertices = geometryMaxOutputVertices; + ThreadsPerInputPrimitive = threadsPerInputPrimitive; + UsesFragCoord = usesFragCoord; + UsesInstanceId = usesInstanceId; + UsesDrawParameters = usesDrawParameters; + UsesRtLayer = usesRtLayer; + ClipDistancesWritten = clipDistancesWritten; + FragmentOutputMap = fragmentOutputMap; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/ShaderStage.cs b/src/Ryujinx.Graphics.Shader/ShaderStage.cs new file mode 100644 index 00000000..2522b4fc --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/ShaderStage.cs @@ -0,0 +1,40 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum ShaderStage : byte + { + Compute, + Vertex, + TessellationControl, + TessellationEvaluation, + Geometry, + Fragment, + + Count, + } + + public static class ShaderStageExtensions + { + /// + /// Checks if the shader stage supports render scale. + /// + /// Shader stage + /// True if the shader stage supports render scale, false otherwise + public static bool SupportsRenderScale(this ShaderStage stage) + { + return stage == ShaderStage.Vertex || stage == ShaderStage.Fragment || stage == ShaderStage.Compute; + } + + /// + /// Checks if the shader stage is vertex, tessellation or geometry. + /// + /// Shader stage + /// True if the shader stage is vertex, tessellation or geometry, false otherwise + public static bool IsVtg(this ShaderStage stage) + { + return stage == ShaderStage.Vertex || + stage == ShaderStage.TessellationControl || + stage == ShaderStage.TessellationEvaluation || + stage == ShaderStage.Geometry; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs new file mode 100644 index 00000000..efda774c --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs @@ -0,0 +1,35 @@ +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstAssignment : AstNode + { + public IAstNode Destination { get; } + + private IAstNode _source; + + public IAstNode Source + { + get + { + return _source; + } + set + { + RemoveUse(_source, this); + + AddUse(value, this); + + _source = value; + } + } + + public AstAssignment(IAstNode destination, IAstNode source) + { + Destination = destination; + Source = source; + + AddDef(destination, this); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs new file mode 100644 index 00000000..826dbff8 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs @@ -0,0 +1,117 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstBlock : AstNode, IEnumerable + { + public AstBlockType Type { get; private set; } + + private IAstNode _condition; + + public IAstNode Condition + { + get + { + return _condition; + } + set + { + RemoveUse(_condition, this); + + AddUse(value, this); + + _condition = value; + } + } + + private readonly LinkedList _nodes; + + public IAstNode First => _nodes.First?.Value; + public IAstNode Last => _nodes.Last?.Value; + + public int Count => _nodes.Count; + + public AstBlock(AstBlockType type, IAstNode condition = null) + { + Type = type; + Condition = condition; + + _nodes = new LinkedList(); + } + + public void Add(IAstNode node) + { + Add(node, _nodes.AddLast(node)); + } + + public void AddFirst(IAstNode node) + { + Add(node, _nodes.AddFirst(node)); + } + + public void AddBefore(IAstNode next, IAstNode node) + { + Add(node, _nodes.AddBefore(next.LLNode, node)); + } + + public void AddAfter(IAstNode prev, IAstNode node) + { + Add(node, _nodes.AddAfter(prev.LLNode, node)); + } + + private void Add(IAstNode node, LinkedListNode newNode) + { + if (node.Parent != null) + { + throw new ArgumentException("Node already belongs to a block."); + } + + node.Parent = this; + node.LLNode = newNode; + } + + public void Remove(IAstNode node) + { + _nodes.Remove(node.LLNode); + + node.Parent = null; + node.LLNode = null; + } + + public void AndCondition(IAstNode cond) + { + Condition = new AstOperation(Instruction.LogicalAnd, Condition, cond); + } + + public void OrCondition(IAstNode cond) + { + Condition = new AstOperation(Instruction.LogicalOr, Condition, cond); + } + public void TurnIntoIf(IAstNode cond) + { + Condition = cond; + + Type = AstBlockType.If; + } + + public void TurnIntoElseIf() + { + Type = AstBlockType.ElseIf; + } + + public IEnumerator GetEnumerator() + { + return _nodes.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs new file mode 100644 index 00000000..a7dcc72a --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + enum AstBlockType + { + DoWhile, + If, + Else, + ElseIf, + Main, + While, + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs new file mode 100644 index 00000000..16efeff7 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstBlockVisitor + { + public AstBlock Block { get; private set; } + + public class BlockVisitationEventArgs : EventArgs + { + public AstBlock Block { get; } + + public BlockVisitationEventArgs(AstBlock block) + { + Block = block; + } + } + + public event EventHandler BlockEntered; + public event EventHandler BlockLeft; + + public AstBlockVisitor(AstBlock mainBlock) + { + Block = mainBlock; + } + + public IEnumerable Visit() + { + IAstNode node = Block.First; + + while (node != null) + { + // We reached a child block, visit the nodes inside. + while (node is AstBlock childBlock) + { + Block = childBlock; + + node = childBlock.First; + + BlockEntered?.Invoke(this, new BlockVisitationEventArgs(Block)); + } + + // Node may be null, if the block is empty. + if (node != null) + { + IAstNode next = Next(node); + + yield return node; + + node = next; + } + + // We reached the end of the list, go up on tree to the parent blocks. + while (node == null && Block.Type != AstBlockType.Main) + { + BlockLeft?.Invoke(this, new BlockVisitationEventArgs(Block)); + + node = Next(Block); + + Block = Block.Parent; + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs new file mode 100644 index 00000000..1c82e646 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstComment : AstNode + { + public string Comment { get; } + + public AstComment(string comment) + { + Comment = comment; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs new file mode 100644 index 00000000..06d13c90 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs @@ -0,0 +1,75 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class AstHelper + { + public static void AddUse(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Uses.Add(parent); + } + } + + public static void AddDef(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Defs.Add(parent); + } + } + + public static void RemoveUse(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Uses.Remove(parent); + } + } + + public static void RemoveDef(IAstNode node, IAstNode parent) + { + if (node is AstOperand operand && operand.Type == OperandType.LocalVariable) + { + operand.Defs.Remove(parent); + } + } + + public static AstAssignment Assign(IAstNode destination, IAstNode source) + { + return new AstAssignment(destination, source); + } + + public static AstOperand Const(int value) + { + return new AstOperand(OperandType.Constant, value); + } + + public static AstOperand Local(AggregateType type) + { + AstOperand local = new(OperandType.LocalVariable) + { + VarType = type, + }; + + return local; + } + + public static IAstNode InverseCond(IAstNode cond) + { + return new AstOperation(Instruction.LogicalNot, cond); + } + + public static IAstNode Next(IAstNode node) + { + return node.LLNode.Next?.Value; + } + + public static IAstNode Previous(IAstNode node) + { + return node.LLNode.Previous?.Value; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs new file mode 100644 index 00000000..0b824617 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstNode : IAstNode + { + public AstBlock Parent { get; set; } + + public LinkedListNode LLNode { get; set; } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs new file mode 100644 index 00000000..b64b96b8 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs @@ -0,0 +1,38 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstOperand : AstNode + { + public HashSet Defs { get; } + public HashSet Uses { get; } + + public OperandType Type { get; } + + public AggregateType VarType { get; set; } + + public int Value { get; } + + private AstOperand() + { + Defs = new HashSet(); + Uses = new HashSet(); + + VarType = AggregateType.S32; + } + + public AstOperand(Operand operand) : this() + { + Type = operand.Type; + Value = operand.Value; + } + + public AstOperand(OperandType type, int value = 0) : this() + { + Type = type; + Value = value; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs new file mode 100644 index 00000000..46555a85 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs @@ -0,0 +1,94 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System.Numerics; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstOperation : AstNode + { + public Instruction Inst { get; } + public StorageKind StorageKind { get; } + public bool ForcePrecise { get; } + + public int Index { get; } + + private readonly IAstNode[] _sources; + + public int SourcesCount => _sources.Length; + + public AstOperation(Instruction inst, StorageKind storageKind, bool forcePrecise, IAstNode[] sources, int sourcesCount) + { + Inst = inst; + StorageKind = storageKind; + ForcePrecise = forcePrecise; + _sources = sources; + + for (int index = 0; index < sources.Length; index++) + { + if (index < sourcesCount) + { + AddUse(sources[index], this); + } + else + { + AddDef(sources[index], this); + } + } + + Index = 0; + } + + public AstOperation( + Instruction inst, + StorageKind storageKind, + bool forcePrecise, + int index, + IAstNode[] sources, + int sourcesCount) : this(inst, storageKind, forcePrecise, sources, sourcesCount) + { + Index = index; + } + + public AstOperation(Instruction inst, params IAstNode[] sources) : this(inst, StorageKind.None, false, sources, sources.Length) + { + } + + public IAstNode GetSource(int index) + { + return _sources[index]; + } + + public void SetSource(int index, IAstNode source) + { + RemoveUse(_sources[index], this); + + AddUse(source, this); + + _sources[index] = source; + } + + public AggregateType GetVectorType(AggregateType scalarType) + { + int componentsCount = BitOperations.PopCount((uint)Index); + + AggregateType type = scalarType; + + switch (componentsCount) + { + case 2: + type |= AggregateType.Vector2; + break; + case 3: + type |= AggregateType.Vector3; + break; + case 4: + type |= AggregateType.Vector4; + break; + } + + return type; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs new file mode 100644 index 00000000..5d46ab49 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs @@ -0,0 +1,155 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; +using System.Linq; + +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class AstOptimizer + { + public static void Optimize(StructuredProgramContext context) + { + AstBlock mainBlock = context.CurrentFunction.MainBlock; + + // When debug mode is enabled, we disable expression propagation + // (this makes comparison with the disassembly easier). + if (!context.DebugMode) + { + AstBlockVisitor visitor = new(mainBlock); + + foreach (IAstNode node in visitor.Visit()) + { + if (node is AstAssignment assignment && assignment.Destination is AstOperand propVar) + { + bool isWorthPropagating = propVar.Uses.Count == 1 || IsWorthPropagating(assignment.Source); + + if (propVar.Defs.Count == 1 && isWorthPropagating) + { + PropagateExpression(propVar, assignment.Source); + } + + if (propVar.Type == OperandType.LocalVariable && propVar.Uses.Count == 0) + { + visitor.Block.Remove(assignment); + + context.CurrentFunction.Locals.Remove(propVar); + } + } + } + } + + RemoveEmptyBlocks(mainBlock); + } + + private static bool IsWorthPropagating(IAstNode source) + { + if (source is not AstOperation srcOp) + { + return false; + } + + if (!InstructionInfo.IsUnary(srcOp.Inst)) + { + return false; + } + + return srcOp.GetSource(0) is AstOperand || srcOp.Inst == Instruction.Copy; + } + + private static void PropagateExpression(AstOperand propVar, IAstNode source) + { + IAstNode[] uses = propVar.Uses.ToArray(); + + foreach (IAstNode useNode in uses) + { + if (useNode is AstBlock useBlock) + { + useBlock.Condition = source; + } + else if (useNode is AstOperation useOperation) + { + for (int srcIndex = 0; srcIndex < useOperation.SourcesCount; srcIndex++) + { + if (useOperation.GetSource(srcIndex) == propVar) + { + useOperation.SetSource(srcIndex, source); + } + } + } + else if (useNode is AstAssignment useAssignment) + { + useAssignment.Source = source; + } + } + } + + private static void RemoveEmptyBlocks(AstBlock mainBlock) + { + Queue pending = new(); + + pending.Enqueue(mainBlock); + + while (pending.TryDequeue(out AstBlock block)) + { + foreach (IAstNode node in block) + { + if (node is AstBlock childBlock) + { + pending.Enqueue(childBlock); + } + } + + AstBlock parent = block.Parent; + + if (parent == null) + { + continue; + } + + AstBlock nextBlock = Next(block) as AstBlock; + + bool hasElse = nextBlock != null && nextBlock.Type == AstBlockType.Else; + + bool isIf = block.Type == AstBlockType.If; + + if (block.Count == 0) + { + if (isIf) + { + if (hasElse) + { + nextBlock.TurnIntoIf(InverseCond(block.Condition)); + } + + parent.Remove(block); + } + else if (block.Type == AstBlockType.Else) + { + parent.Remove(block); + } + } + else if (isIf && parent.Type == AstBlockType.Else && parent.Count == (hasElse ? 2 : 1)) + { + AstBlock parentOfParent = parent.Parent; + + parent.Remove(block); + + parentOfParent.AddAfter(parent, block); + + if (hasElse) + { + parent.Remove(nextBlock); + + parentOfParent.AddAfter(block, nextBlock); + } + + parentOfParent.Remove(parent); + + block.TurnIntoElseIf(); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs new file mode 100644 index 00000000..867cae85 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs @@ -0,0 +1,49 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class AstTextureOperation : AstOperation + { + public SamplerType Type { get; } + public TextureFormat Format { get; } + public TextureFlags Flags { get; } + + public int Set { get; } + public int Binding { get; } + public int SamplerSet { get; } + public int SamplerBinding { get; } + + public bool IsSeparate => SamplerBinding >= 0; + + public AstTextureOperation( + Instruction inst, + SamplerType type, + TextureFormat format, + TextureFlags flags, + int set, + int binding, + int samplerSet, + int samplerBinding, + int index, + params IAstNode[] sources) : base(inst, StorageKind.None, false, index, sources, sources.Length) + { + Type = type; + Format = format; + Flags = flags; + Set = set; + Binding = binding; + SamplerSet = samplerSet; + SamplerBinding = samplerBinding; + } + + public SetBindingPair GetTextureSetAndBinding() + { + return new SetBindingPair(Set, Binding); + } + + public SetBindingPair GetSamplerSetAndBinding() + { + return new SetBindingPair(SamplerSet, SamplerBinding); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/BufferDefinition.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/BufferDefinition.cs new file mode 100644 index 00000000..e2759480 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/BufferDefinition.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + readonly struct BufferDefinition + { + public BufferLayout Layout { get; } + public int Set { get; } + public int Binding { get; } + public string Name { get; } + public StructureType Type { get; } + + public BufferDefinition(BufferLayout layout, int set, int binding, string name, StructureType type) + { + Layout = layout; + Set = set; + Binding = binding; + Name = name; + Type = type; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/BufferLayout.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/BufferLayout.cs new file mode 100644 index 00000000..1c25ed34 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/BufferLayout.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + enum BufferLayout + { + Std140, + Std430, + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs new file mode 100644 index 00000000..3ca1266f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs @@ -0,0 +1,458 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class GotoElimination + { + // This is a modified version of the algorithm presented on the paper + // "Taming Control Flow: A Structured Approach to Eliminating Goto Statements". + public static void Eliminate(GotoStatement[] gotos) + { + for (int index = gotos.Length - 1; index >= 0; index--) + { + GotoStatement stmt = gotos[index]; + + AstBlock gBlock = ParentBlock(stmt.Goto); + AstBlock lBlock = ParentBlock(stmt.Label); + + int gLevel = Level(gBlock); + int lLevel = Level(lBlock); + + if (IndirectlyRelated(gBlock, lBlock, gLevel, lLevel)) + { + AstBlock drBlock = gBlock; + + int drLevel = gLevel; + + do + { + drBlock = drBlock.Parent; + + drLevel--; + } + while (!DirectlyRelated(drBlock, lBlock, drLevel, lLevel)); + + MoveOutward(stmt, gLevel, drLevel); + + gBlock = drBlock; + gLevel = drLevel; + + if (Previous(stmt.Goto) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else) + { + // It's possible that the label was enclosed inside an else block, + // in this case we need to update the block and level. + // We also need to set the IsLoop for the case when the label is + // now before the goto, due to the newly introduced else block. + lBlock = ParentBlock(stmt.Label); + + lLevel = Level(lBlock); + + if (!IndirectlyRelated(elseBlock, lBlock, gLevel + 1, lLevel)) + { + stmt.IsLoop = true; + } + } + } + + if (DirectlyRelated(gBlock, lBlock, gLevel, lLevel)) + { + if (gLevel > lLevel) + { + MoveOutward(stmt, gLevel, lLevel); + } + else + { + if (stmt.IsLoop) + { + Lift(stmt); + } + + MoveInward(stmt); + } + } + + gBlock = ParentBlock(stmt.Goto); + + if (stmt.IsLoop) + { + EncloseDoWhile(stmt, gBlock, stmt.Label); + } + else + { + Enclose(gBlock, AstBlockType.If, stmt.Condition, Next(stmt.Goto), stmt.Label); + } + + gBlock.Remove(stmt.Goto); + } + } + + private static bool IndirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rlevel) + { + return !(lBlock == rBlock || DirectlyRelated(lBlock, rBlock, lLevel, rlevel)); + } + + private static bool DirectlyRelated(AstBlock lBlock, AstBlock rBlock, int lLevel, int rLevel) + { + // If the levels are equal, they can be either siblings or indirectly related. + if (lLevel == rLevel) + { + return false; + } + + IAstNode block; + IAstNode other; + + int blockLvl, otherLvl; + + if (lLevel > rLevel) + { + block = lBlock; + blockLvl = lLevel; + other = rBlock; + otherLvl = rLevel; + } + else /* if (rLevel > lLevel) */ + { + block = rBlock; + blockLvl = rLevel; + other = lBlock; + otherLvl = lLevel; + } + + while (blockLvl >= otherLvl) + { + if (block == other) + { + return true; + } + + block = block.Parent; + + blockLvl--; + } + + return false; + } + + private static void Lift(GotoStatement stmt) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label)); + + AstBlock loopFirstStmt = path[^1]; + + if (loopFirstStmt.Type == AstBlockType.Else) + { + loopFirstStmt = Previous(loopFirstStmt) as AstBlock; + + if (loopFirstStmt == null || loopFirstStmt.Type != AstBlockType.If) + { + throw new InvalidOperationException("Found an else without a matching if."); + } + } + + AstBlock newBlock = EncloseDoWhile(stmt, block, loopFirstStmt); + + block.Remove(stmt.Goto); + + newBlock.AddFirst(stmt.Goto); + + stmt.IsLoop = false; + } + + private static void MoveOutward(GotoStatement stmt, int gLevel, int lLevel) + { + AstBlock origin = ParentBlock(stmt.Goto); + + AstBlock block = origin; + + // Check if a loop is enclosing the goto, and the block that is + // directly related to the label is above the loop block. + // In that case, we need to introduce a break to get out of the loop. + AstBlock loopBlock = origin; + + int loopLevel = gLevel; + + while (loopLevel > lLevel) + { + AstBlock child = loopBlock; + + loopBlock = loopBlock.Parent; + + loopLevel--; + + if (child.Type == AstBlockType.DoWhile) + { + EncloseSingleInst(stmt, Instruction.LoopBreak); + + block.Remove(stmt.Goto); + + loopBlock.AddAfter(child, stmt.Goto); + + block = loopBlock; + gLevel = loopLevel; + } + } + + // Insert ifs to skip the parts that shouldn't be executed due to the goto. + bool tryInsertElse = stmt.IsUnconditional && origin.Type == AstBlockType.If; + + while (gLevel > lLevel) + { + Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto)); + + block.Remove(stmt.Goto); + + AstBlock child = block; + + // We can't move the goto in the middle of a if and a else block, in + // this case we need to move it after the else. + // IsLoop may need to be updated if the label is inside the else, as + // introducing a loop is the only way to ensure the else will be executed. + if (Next(child) is AstBlock elseBlock && elseBlock.Type == AstBlockType.Else) + { + child = elseBlock; + } + + block = block.Parent; + + block.AddAfter(child, stmt.Goto); + + gLevel--; + + if (tryInsertElse && child == origin) + { + AstBlock lBlock = ParentBlock(stmt.Label); + + IAstNode last = block == lBlock && !stmt.IsLoop ? stmt.Label : null; + + AstBlock newBlock = Enclose(block, AstBlockType.Else, null, Next(stmt.Goto), last); + + if (newBlock != null) + { + block.Remove(stmt.Goto); + + block.AddAfter(newBlock, stmt.Goto); + } + } + } + } + + private static void MoveInward(GotoStatement stmt) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock[] path = BackwardsPath(block, ParentBlock(stmt.Label)); + + for (int index = path.Length - 1; index >= 0; index--) + { + AstBlock child = path[index]; + AstBlock last = child; + + if (child.Type == AstBlockType.If) + { + // Modify the if condition to allow it to be entered by the goto. + if (!ContainsCondComb(child.Condition, Instruction.LogicalOr, stmt.Condition)) + { + child.OrCondition(stmt.Condition); + } + } + else if (child.Type == AstBlockType.Else) + { + // Modify the matching if condition to force the else to be entered by the goto. + if (Previous(child) is not AstBlock ifBlock || ifBlock.Type != AstBlockType.If) + { + throw new InvalidOperationException("Found an else without a matching if."); + } + + IAstNode cond = InverseCond(stmt.Condition); + + if (!ContainsCondComb(ifBlock.Condition, Instruction.LogicalAnd, cond)) + { + ifBlock.AndCondition(cond); + } + + last = ifBlock; + } + + Enclose(block, AstBlockType.If, stmt.Condition, Next(stmt.Goto), last); + + block.Remove(stmt.Goto); + + child.AddFirst(stmt.Goto); + + block = child; + } + } + + private static bool ContainsCondComb(IAstNode node, Instruction inst, IAstNode newCond) + { + while (node is AstOperation operation && operation.SourcesCount == 2) + { + if (operation.Inst == inst && IsSameCond(operation.GetSource(1), newCond)) + { + return true; + } + + node = operation.GetSource(0); + } + + return false; + } + + private static AstBlock EncloseDoWhile(GotoStatement stmt, AstBlock block, IAstNode first) + { + if (block.Type == AstBlockType.DoWhile && first == block.First) + { + // We only need to insert the continue if we're not at the end of the loop, + // or if our condition is different from the loop condition. + if (Next(stmt.Goto) != null || block.Condition != stmt.Condition) + { + EncloseSingleInst(stmt, Instruction.LoopContinue); + } + + // Modify the do-while condition to allow it to continue. + if (!ContainsCondComb(block.Condition, Instruction.LogicalOr, stmt.Condition)) + { + block.OrCondition(stmt.Condition); + } + + return block; + } + + return Enclose(block, AstBlockType.DoWhile, stmt.Condition, first, stmt.Goto); + } + + private static void EncloseSingleInst(GotoStatement stmt, Instruction inst) + { + AstBlock block = ParentBlock(stmt.Goto); + + AstBlock newBlock = new(AstBlockType.If, stmt.Condition); + + block.AddAfter(stmt.Goto, newBlock); + + newBlock.AddFirst(new AstOperation(inst)); + } + + private static AstBlock Enclose( + AstBlock block, + AstBlockType type, + IAstNode cond, + IAstNode first, + IAstNode last = null) + { + if (first == last) + { + return null; + } + + if (type == AstBlockType.If) + { + cond = InverseCond(cond); + } + + // Do a quick check, if we are enclosing a single block, + // and the block type/condition matches the one we're going + // to create, then we don't need a new block, we can just + // return the old one. + bool hasSingleNode = Next(first) == last; + + if (hasSingleNode && BlockMatches(first, type, cond)) + { + return first as AstBlock; + } + + AstBlock newBlock = new(type, cond); + + block.AddBefore(first, newBlock); + + while (first != last) + { + IAstNode next = Next(first); + + block.Remove(first); + + newBlock.Add(first); + + first = next; + } + + return newBlock; + } + + private static bool BlockMatches(IAstNode node, AstBlockType type, IAstNode cond) + { + if (node is not AstBlock block) + { + return false; + } + + return block.Type == type && IsSameCond(block.Condition, cond); + } + + private static bool IsSameCond(IAstNode lCond, IAstNode rCond) + { + if (lCond is AstOperation lCondOp && lCondOp.Inst == Instruction.LogicalNot) + { + if (rCond is not AstOperation rCondOp || rCondOp.Inst != lCondOp.Inst) + { + return false; + } + + lCond = lCondOp.GetSource(0); + rCond = rCondOp.GetSource(0); + } + + return lCond == rCond; + } + + private static AstBlock ParentBlock(IAstNode node) + { + if (node is AstBlock block) + { + return block.Parent; + } + + while (node is not AstBlock) + { + node = node.Parent; + } + + return node as AstBlock; + } + + private static AstBlock[] BackwardsPath(AstBlock top, AstBlock bottom) + { + AstBlock block = bottom; + + List path = new(); + + while (block != top) + { + path.Add(block); + + block = block.Parent; + } + + return path.ToArray(); + } + + private static int Level(IAstNode node) + { + int level = 0; + + while (node != null) + { + level++; + + node = node.Parent; + } + + return level; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs new file mode 100644 index 00000000..4607a16c --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs @@ -0,0 +1,23 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class GotoStatement + { + public AstOperation Goto { get; } + public AstAssignment Label { get; } + + public IAstNode Condition => Label.Destination; + + public bool IsLoop { get; set; } + + public bool IsUnconditional => Goto.Inst == Instruction.Branch; + + public GotoStatement(AstOperation branch, AstAssignment label, bool isLoop) + { + Goto = branch; + Label = label; + IsLoop = isLoop; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs new file mode 100644 index 00000000..2a3d65e7 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs @@ -0,0 +1,13 @@ +using System; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + [Flags] + enum HelperFunctionsMask + { + MultiplyHighS32 = 1 << 2, + MultiplyHighU32 = 1 << 3, + SwizzleAdd = 1 << 10, + FSI = 1 << 11, + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs new file mode 100644 index 00000000..248d8d69 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + interface IAstNode + { + AstBlock Parent { get; set; } + + LinkedListNode LLNode { get; set; } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs new file mode 100644 index 00000000..72d0e989 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs @@ -0,0 +1,208 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class InstructionInfo + { + private readonly struct InstInfo + { + public AggregateType DestType { get; } + + public AggregateType[] SrcTypes { get; } + + public InstInfo(AggregateType destType, params AggregateType[] srcTypes) + { + DestType = destType; + SrcTypes = srcTypes; + } + } + + private static readonly InstInfo[] _infoTbl; + + static InstructionInfo() + { + _infoTbl = new InstInfo[(int)Instruction.Count]; + +#pragma warning disable IDE0055 // Disable formatting + // Inst Destination type Source 1 type Source 2 type Source 3 type Source 4 type + Add(Instruction.AtomicAdd, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32); + Add(Instruction.AtomicAnd, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32); + Add(Instruction.AtomicCompareAndSwap, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32, AggregateType.U32); + Add(Instruction.AtomicMaxS32, AggregateType.S32, AggregateType.S32, AggregateType.S32, AggregateType.S32); + Add(Instruction.AtomicMaxU32, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32); + Add(Instruction.AtomicMinS32, AggregateType.S32, AggregateType.S32, AggregateType.S32, AggregateType.S32); + Add(Instruction.AtomicMinU32, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32); + Add(Instruction.AtomicOr, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32); + Add(Instruction.AtomicSwap, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32); + Add(Instruction.AtomicXor, AggregateType.U32, AggregateType.S32, AggregateType.S32, AggregateType.U32); + Add(Instruction.Absolute, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.Add, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.Ballot, AggregateType.U32, AggregateType.Bool); + Add(Instruction.BitCount, AggregateType.S32, AggregateType.S32); + Add(Instruction.BitfieldExtractS32, AggregateType.S32, AggregateType.S32, AggregateType.S32, AggregateType.S32); + Add(Instruction.BitfieldExtractU32, AggregateType.U32, AggregateType.U32, AggregateType.S32, AggregateType.S32); + Add(Instruction.BitfieldInsert, AggregateType.S32, AggregateType.S32, AggregateType.S32, AggregateType.S32, AggregateType.S32); + Add(Instruction.BitfieldReverse, AggregateType.S32, AggregateType.S32); + Add(Instruction.BitwiseAnd, AggregateType.S32, AggregateType.S32, AggregateType.S32); + Add(Instruction.BitwiseExclusiveOr, AggregateType.S32, AggregateType.S32, AggregateType.S32); + Add(Instruction.BitwiseNot, AggregateType.S32, AggregateType.S32); + Add(Instruction.BitwiseOr, AggregateType.S32, AggregateType.S32, AggregateType.S32); + Add(Instruction.BranchIfTrue, AggregateType.Void, AggregateType.Bool); + Add(Instruction.BranchIfFalse, AggregateType.Void, AggregateType.Bool); + Add(Instruction.Call, AggregateType.Scalar); + Add(Instruction.Ceiling, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.Clamp, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.ClampU32, AggregateType.U32, AggregateType.U32, AggregateType.U32, AggregateType.U32); + Add(Instruction.CompareEqual, AggregateType.Bool, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.CompareGreater, AggregateType.Bool, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.CompareGreaterOrEqual, AggregateType.Bool, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.CompareGreaterOrEqualU32, AggregateType.Bool, AggregateType.U32, AggregateType.U32); + Add(Instruction.CompareGreaterU32, AggregateType.Bool, AggregateType.U32, AggregateType.U32); + Add(Instruction.CompareLess, AggregateType.Bool, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.CompareLessOrEqual, AggregateType.Bool, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.CompareLessOrEqualU32, AggregateType.Bool, AggregateType.U32, AggregateType.U32); + Add(Instruction.CompareLessU32, AggregateType.Bool, AggregateType.U32, AggregateType.U32); + Add(Instruction.CompareNotEqual, AggregateType.Bool, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.ConditionalSelect, AggregateType.Scalar, AggregateType.Bool, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.ConvertFP32ToFP64, AggregateType.FP64, AggregateType.FP32); + Add(Instruction.ConvertFP64ToFP32, AggregateType.FP32, AggregateType.FP64); + Add(Instruction.ConvertFP32ToS32, AggregateType.S32, AggregateType.FP32); + Add(Instruction.ConvertFP32ToU32, AggregateType.U32, AggregateType.FP32); + Add(Instruction.ConvertFP64ToS32, AggregateType.S32, AggregateType.FP64); + Add(Instruction.ConvertFP64ToU32, AggregateType.U32, AggregateType.FP64); + Add(Instruction.ConvertS32ToFP32, AggregateType.FP32, AggregateType.S32); + Add(Instruction.ConvertS32ToFP64, AggregateType.FP64, AggregateType.S32); + Add(Instruction.ConvertU32ToFP32, AggregateType.FP32, AggregateType.U32); + Add(Instruction.ConvertU32ToFP64, AggregateType.FP64, AggregateType.U32); + Add(Instruction.Cosine, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.Ddx, AggregateType.FP32, AggregateType.FP32); + Add(Instruction.Ddy, AggregateType.FP32, AggregateType.FP32); + Add(Instruction.Divide, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.ExponentB2, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.FindLSB, AggregateType.S32, AggregateType.S32); + Add(Instruction.FindMSBS32, AggregateType.S32, AggregateType.S32); + Add(Instruction.FindMSBU32, AggregateType.S32, AggregateType.U32); + Add(Instruction.Floor, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.FusedMultiplyAdd, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.ImageLoad, AggregateType.FP32); + Add(Instruction.ImageStore, AggregateType.Void); + Add(Instruction.ImageAtomic, AggregateType.S32); + Add(Instruction.IsNan, AggregateType.Bool, AggregateType.Scalar); + Add(Instruction.Load, AggregateType.FP32); + Add(Instruction.Lod, AggregateType.FP32); + Add(Instruction.LogarithmB2, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.LogicalAnd, AggregateType.Bool, AggregateType.Bool, AggregateType.Bool); + Add(Instruction.LogicalExclusiveOr, AggregateType.Bool, AggregateType.Bool, AggregateType.Bool); + Add(Instruction.LogicalNot, AggregateType.Bool, AggregateType.Bool); + Add(Instruction.LogicalOr, AggregateType.Bool, AggregateType.Bool, AggregateType.Bool); + Add(Instruction.Maximum, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.MaximumU32, AggregateType.U32, AggregateType.U32, AggregateType.U32); + Add(Instruction.Minimum, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.MinimumU32, AggregateType.U32, AggregateType.U32, AggregateType.U32); + Add(Instruction.Modulo, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.Multiply, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.MultiplyHighS32, AggregateType.S32, AggregateType.S32, AggregateType.S32); + Add(Instruction.MultiplyHighU32, AggregateType.U32, AggregateType.U32, AggregateType.U32); + Add(Instruction.Negate, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.PackDouble2x32, AggregateType.FP64, AggregateType.U32, AggregateType.U32); + Add(Instruction.PackHalf2x16, AggregateType.U32, AggregateType.FP32, AggregateType.FP32); + Add(Instruction.ReciprocalSquareRoot, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.Return, AggregateType.Void, AggregateType.U32); + Add(Instruction.Round, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.ShiftLeft, AggregateType.S32, AggregateType.S32, AggregateType.S32); + Add(Instruction.ShiftRightS32, AggregateType.S32, AggregateType.S32, AggregateType.S32); + Add(Instruction.ShiftRightU32, AggregateType.U32, AggregateType.U32, AggregateType.S32); + Add(Instruction.Shuffle, AggregateType.FP32, AggregateType.FP32, AggregateType.U32); + Add(Instruction.ShuffleDown, AggregateType.FP32, AggregateType.FP32, AggregateType.U32); + Add(Instruction.ShuffleUp, AggregateType.FP32, AggregateType.FP32, AggregateType.U32); + Add(Instruction.ShuffleXor, AggregateType.FP32, AggregateType.FP32, AggregateType.U32); + Add(Instruction.Sine, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.SquareRoot, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.Store, AggregateType.Void); + Add(Instruction.Subtract, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.SwizzleAdd, AggregateType.FP32, AggregateType.FP32, AggregateType.FP32, AggregateType.S32); + Add(Instruction.TextureSample, AggregateType.FP32); + Add(Instruction.TextureQuerySamples, AggregateType.S32, AggregateType.S32); + Add(Instruction.TextureQuerySize, AggregateType.S32, AggregateType.S32, AggregateType.S32); + Add(Instruction.Truncate, AggregateType.Scalar, AggregateType.Scalar); + Add(Instruction.UnpackDouble2x32, AggregateType.U32, AggregateType.FP64); + Add(Instruction.UnpackHalf2x16, AggregateType.FP32, AggregateType.U32); + Add(Instruction.VectorExtract, AggregateType.Scalar, AggregateType.Vector4, AggregateType.S32); + Add(Instruction.VoteAll, AggregateType.Bool, AggregateType.Bool); + Add(Instruction.VoteAllEqual, AggregateType.Bool, AggregateType.Bool); + Add(Instruction.VoteAny, AggregateType.Bool, AggregateType.Bool); +#pragma warning restore IDE0055 + } + + private static void Add(Instruction inst, AggregateType destType, params AggregateType[] srcTypes) + { + _infoTbl[(int)inst] = new InstInfo(destType, srcTypes); + } + + public static AggregateType GetDestVarType(Instruction inst) + { + return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].DestType, inst); + } + + public static AggregateType GetSrcVarType(Instruction inst, int index) + { + // TODO: Return correct type depending on source index, + // that can improve the decompiler output. + if (inst == Instruction.ImageLoad || + inst == Instruction.ImageStore || + inst == Instruction.ImageAtomic || + inst == Instruction.Lod || + inst == Instruction.TextureSample) + { + return AggregateType.FP32; + } + else if (inst == Instruction.Call || inst == Instruction.Load || inst == Instruction.Store || inst.IsAtomic()) + { + return AggregateType.S32; + } + + return GetFinalVarType(_infoTbl[(int)(inst & Instruction.Mask)].SrcTypes[index], inst); + } + + private static AggregateType GetFinalVarType(AggregateType type, Instruction inst) + { + if (type == AggregateType.Scalar) + { + if ((inst & Instruction.FP32) != 0) + { + return AggregateType.FP32; + } + else if ((inst & Instruction.FP64) != 0) + { + return AggregateType.FP64; + } + else + { + return AggregateType.S32; + } + } + else if (type == AggregateType.Void) + { + throw new ArgumentException($"Invalid operand for instruction \"{inst}\"."); + } + + return type; + } + + public static bool IsUnary(Instruction inst) + { + if (inst == Instruction.Copy) + { + return true; + } + else if (inst == Instruction.TextureSample) + { + return false; + } + + return _infoTbl[(int)(inst & Instruction.Mask)].SrcTypes.Length == 1; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/IoDefinition.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/IoDefinition.cs new file mode 100644 index 00000000..0a0681fa --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/IoDefinition.cs @@ -0,0 +1,44 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + readonly struct IoDefinition : IEquatable + { + 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}"; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/MemoryDefinition.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/MemoryDefinition.cs new file mode 100644 index 00000000..3ea69fde --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/MemoryDefinition.cs @@ -0,0 +1,18 @@ +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + readonly struct MemoryDefinition + { + public string Name { get; } + public AggregateType Type { get; } + public int ArrayLength { get; } + + public MemoryDefinition(string name, AggregateType type, int arrayLength = 1) + { + Name = name; + Type = type; + ArrayLength = arrayLength; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs new file mode 100644 index 00000000..638a5298 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs @@ -0,0 +1,32 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class OperandInfo + { + public static AggregateType GetVarType(AstOperand operand) + { + if (operand.Type == OperandType.LocalVariable) + { + return operand.VarType; + } + else + { + return GetVarType(operand.Type); + } + } + + public static AggregateType GetVarType(OperandType type) + { + return type switch + { + OperandType.Argument => AggregateType.S32, + OperandType.Constant => AggregateType.S32, + OperandType.Undefined => AggregateType.S32, + _ => throw new ArgumentException($"Invalid operand type \"{type}\"."), + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs new file mode 100644 index 00000000..90f1f2f6 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs @@ -0,0 +1,49 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class PhiFunctions + { + public static void Remove(BasicBlock[] blocks) + { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + LinkedListNode node = block.Operations.First; + + while (node != null) + { + LinkedListNode nextNode = node.Next; + + if (node.Value is not PhiNode phi) + { + node = nextNode; + + continue; + } + + Operand temp = OperandHelper.Local(); + + for (int index = 0; index < phi.SourcesCount; index++) + { + Operand src = phi.GetSource(index); + BasicBlock srcBlock = phi.GetBlock(index); + + Operation copyOp = new(Instruction.Copy, temp, src); + + srcBlock.Append(copyOp); + } + + Operation copyOp2 = new(Instruction.Copy, phi.Dest, temp); + + nextNode = block.Operations.AddAfter(node, copyOp2).Next; + block.Operations.Remove(node); + + node = nextNode; + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs new file mode 100644 index 00000000..53ed6bfc --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs @@ -0,0 +1,110 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class ShaderProperties + { + private readonly Dictionary _constantBuffers; + private readonly Dictionary _storageBuffers; + private readonly Dictionary _textures; + private readonly Dictionary _images; + private readonly Dictionary _localMemories; + private readonly Dictionary _sharedMemories; + + public IReadOnlyDictionary ConstantBuffers => _constantBuffers; + public IReadOnlyDictionary StorageBuffers => _storageBuffers; + public IReadOnlyDictionary Textures => _textures; + public IReadOnlyDictionary Images => _images; + public IReadOnlyDictionary LocalMemories => _localMemories; + public IReadOnlyDictionary SharedMemories => _sharedMemories; + + public ShaderProperties() + { + _constantBuffers = new Dictionary(); + _storageBuffers = new Dictionary(); + _textures = new Dictionary(); + _images = new Dictionary(); + _localMemories = new Dictionary(); + _sharedMemories = new Dictionary(); + } + + public void AddOrUpdateConstantBuffer(BufferDefinition definition) + { + _constantBuffers[definition.Binding] = definition; + } + + public void AddOrUpdateStorageBuffer(BufferDefinition definition) + { + _storageBuffers[definition.Binding] = definition; + } + + public void AddOrUpdateTexture(TextureDefinition definition) + { + _textures[new(definition.Set, definition.Binding)] = definition; + } + + public void AddOrUpdateImage(TextureDefinition definition) + { + _images[new(definition.Set, definition.Binding)] = definition; + } + + public int AddLocalMemory(MemoryDefinition definition) + { + int id = _localMemories.Count; + _localMemories.Add(id, definition); + + return id; + } + + public int AddSharedMemory(MemoryDefinition definition) + { + int id = _sharedMemories.Count; + _sharedMemories.Add(id, definition); + + return id; + } + + public static TextureFormat GetTextureFormat(IGpuAccessor gpuAccessor, int handle, int cbufSlot = -1) + { + // When the formatted load extension is supported, we don't need to + // specify a format, we can just declare it without a format and the GPU will handle it. + if (gpuAccessor.QueryHostSupportsImageLoadFormatted()) + { + return TextureFormat.Unknown; + } + + var format = gpuAccessor.QueryTextureFormat(handle, cbufSlot); + + if (format == TextureFormat.Unknown) + { + gpuAccessor.Log($"Unknown format for texture {handle}."); + + format = TextureFormat.R8G8B8A8Unorm; + } + + return format; + } + + private static bool FormatSupportsAtomic(TextureFormat format) + { + return format == TextureFormat.R32Sint || format == TextureFormat.R32Uint; + } + + public static TextureFormat GetTextureFormatAtomic(IGpuAccessor gpuAccessor, int handle, int cbufSlot = -1) + { + // Atomic image instructions do not support GL_EXT_shader_image_load_formatted, + // and must have a type specified. Default to R32Sint if not available. + + var format = gpuAccessor.QueryTextureFormat(handle, cbufSlot); + + if (!FormatSupportsAtomic(format)) + { + gpuAccessor.Log($"Unsupported format for texture {handle}: {format}."); + + format = TextureFormat.R32Sint; + } + + return format; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructureType.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructureType.cs new file mode 100644 index 00000000..fdf824f5 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructureType.cs @@ -0,0 +1,28 @@ +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + readonly struct StructureField + { + public AggregateType Type { get; } + public string Name { get; } + public int ArrayLength { get; } + + public StructureField(AggregateType type, string name, int arrayLength = 1) + { + Type = type; + Name = name; + ArrayLength = arrayLength; + } + } + + class StructureType + { + public StructureField[] Fields { get; } + + public StructureType(StructureField[] fields) + { + Fields = fields; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredFunction.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredFunction.cs new file mode 100644 index 00000000..aa5e1386 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredFunction.cs @@ -0,0 +1,42 @@ +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class StructuredFunction + { + public AstBlock MainBlock { get; } + + public string Name { get; } + + public AggregateType ReturnType { get; } + + public AggregateType[] InArguments { get; } + public AggregateType[] OutArguments { get; } + + public HashSet Locals { get; } + + public StructuredFunction( + AstBlock mainBlock, + string name, + AggregateType returnType, + AggregateType[] inArguments, + AggregateType[] outArguments) + { + MainBlock = mainBlock; + Name = name; + ReturnType = returnType; + InArguments = inArguments; + OutArguments = outArguments; + + Locals = new HashSet(); + } + + public AggregateType GetArgumentType(int index) + { + return index >= InArguments.Length + ? OutArguments[index - InArguments.Length] + : InArguments[index]; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs new file mode 100644 index 00000000..88053658 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs @@ -0,0 +1,466 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + static class StructuredProgram + { + // TODO: Eventually it should be possible to specify the parameter types for the function instead of using S32 for everything. + private const AggregateType FuncParameterType = AggregateType.S32; + + public static StructuredProgramInfo MakeStructuredProgram( + IReadOnlyList functions, + AttributeUsage attributeUsage, + ShaderDefinitions definitions, + ResourceManager resourceManager, + TargetLanguage targetLanguage, + bool debugMode) + { + StructuredProgramContext context = new(attributeUsage, definitions, resourceManager, debugMode); + + for (int funcIndex = 0; funcIndex < functions.Count; funcIndex++) + { + Function function = functions[funcIndex]; + + BasicBlock[] blocks = function.Blocks; + + AggregateType returnType = function.ReturnsValue ? FuncParameterType : AggregateType.Void; + + AggregateType[] inArguments = new AggregateType[function.InArgumentsCount]; + AggregateType[] outArguments = new AggregateType[function.OutArgumentsCount]; + + for (int i = 0; i < inArguments.Length; i++) + { + inArguments[i] = FuncParameterType; + } + + for (int i = 0; i < outArguments.Length; i++) + { + outArguments[i] = FuncParameterType; + } + + context.EnterFunction(blocks.Length, function.Name, returnType, inArguments, outArguments); + + PhiFunctions.Remove(blocks); + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + context.EnterBlock(block); + + for (LinkedListNode opNode = block.Operations.First; opNode != null; opNode = opNode.Next) + { + Operation operation = (Operation)opNode.Value; + + if (IsBranchInst(operation.Inst)) + { + context.LeaveBlock(block, operation); + } + else + { + AddOperation(context, operation, targetLanguage, functions); + } + } + } + + GotoElimination.Eliminate(context.GetGotos()); + + AstOptimizer.Optimize(context); + + context.LeaveFunction(); + } + + return context.Info; + } + + private static void AddOperation(StructuredProgramContext context, Operation operation, TargetLanguage targetLanguage, IReadOnlyList functions) + { + Instruction inst = operation.Inst; + StorageKind storageKind = operation.StorageKind; + + if (inst == Instruction.Load || inst == Instruction.Store) + { + if (storageKind.IsInputOrOutput()) + { + IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value; + bool isOutput = storageKind.IsOutput(); + int location = 0; + int component = 0; + + if (context.Definitions.HasPerLocationInputOrOutput(ioVariable, isOutput)) + { + location = operation.GetSource(1).Value; + + if (operation.SourcesCount > 2 && + operation.GetSource(2).Type == OperandType.Constant && + context.Definitions.HasPerLocationInputOrOutputComponent(ioVariable, location, operation.GetSource(2).Value, isOutput)) + { + component = operation.GetSource(2).Value; + } + } + + context.Info.IoDefinitions.Add(new IoDefinition(storageKind, ioVariable, location, component)); + } + else if (storageKind == StorageKind.ConstantBuffer && operation.GetSource(0).Type == OperandType.Constant) + { + context.ResourceManager.SetUsedConstantBufferBinding(operation.GetSource(0).Value); + } + } + + bool vectorDest = IsVectorDestInst(inst); + + int sourcesCount = operation.SourcesCount; + int outDestsCount = operation.DestsCount != 0 && !vectorDest ? operation.DestsCount - 1 : 0; + + IAstNode[] sources = new IAstNode[sourcesCount + outDestsCount]; + + if (inst == Instruction.Call && targetLanguage == TargetLanguage.Spirv) + { + // SPIR-V requires that all function parameters are copied to a local variable before the call + // (or at least that's what the Khronos compiler does). + + // First one is the function index. + Operand funcIndexOperand = operation.GetSource(0); + Debug.Assert(funcIndexOperand.Type == OperandType.Constant); + int funcIndex = funcIndexOperand.Value; + + sources[0] = new AstOperand(OperandType.Constant, funcIndex); + + int inArgsCount = functions[funcIndex].InArgumentsCount; + + // Remaining ones are parameters, copy them to a temp local variable. + for (int index = 1; index < operation.SourcesCount; index++) + { + IAstNode source = context.GetOperandOrCbLoad(operation.GetSource(index)); + + if (index - 1 < inArgsCount) + { + AstOperand argTemp = context.NewTemp(FuncParameterType); + context.AddNode(new AstAssignment(argTemp, source)); + sources[index] = argTemp; + } + else + { + sources[index] = source; + } + } + } + else + { + for (int index = 0; index < operation.SourcesCount; index++) + { + sources[index] = context.GetOperandOrCbLoad(operation.GetSource(index)); + } + } + + for (int index = 0; index < outDestsCount; index++) + { + AstOperand oper = context.GetOperand(operation.GetDest(1 + index)); + + oper.VarType = InstructionInfo.GetSrcVarType(inst, sourcesCount + index); + + sources[sourcesCount + index] = oper; + } + + AstTextureOperation GetAstTextureOperation(TextureOperation texOp) + { + return new AstTextureOperation( + inst, + texOp.Type, + texOp.Format, + texOp.Flags, + texOp.Set, + texOp.Binding, + texOp.SamplerSet, + texOp.SamplerBinding, + texOp.Index, + sources); + } + + int componentsCount = BitOperations.PopCount((uint)operation.Index); + + if (vectorDest && componentsCount > 1) + { + AggregateType destType = InstructionInfo.GetDestVarType(inst); + + IAstNode source; + + if (operation is TextureOperation texOp) + { + if (texOp.Inst == Instruction.ImageLoad) + { + destType = texOp.Format.GetComponentType(); + } + + source = GetAstTextureOperation(texOp); + } + else + { + source = new AstOperation( + inst, + operation.StorageKind, + operation.ForcePrecise, + operation.Index, + sources, + operation.SourcesCount); + } + + AggregateType destElemType = destType; + + switch (componentsCount) + { + case 2: + destType |= AggregateType.Vector2; + break; + case 3: + destType |= AggregateType.Vector3; + break; + case 4: + destType |= AggregateType.Vector4; + break; + } + + AstOperand destVec = context.NewTemp(destType); + + context.AddNode(new AstAssignment(destVec, source)); + + for (int i = 0; i < operation.DestsCount; i++) + { + AstOperand dest = context.GetOperand(operation.GetDest(i)); + AstOperand index = new(OperandType.Constant, i); + + dest.VarType = destElemType; + + context.AddNode(new AstAssignment(dest, new AstOperation(Instruction.VectorExtract, StorageKind.None, false, new[] { destVec, index }, 2))); + } + } + else if (operation.Dest != null) + { + 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 + // a bitwise operation with the value, as it is likely to be used as + // a bool in the end. + if (IsBitwiseInst(inst) && AreAllSourceTypesEqual(sources, AggregateType.Bool)) + { + inst = GetLogicalFromBitwiseInst(inst); + } + + bool isCondSel = inst == Instruction.ConditionalSelect; + bool isCopy = inst == Instruction.Copy; + + if (isCondSel || isCopy) + { + AggregateType type = GetVarTypeFromUses(operation.Dest); + + if (isCondSel && type == AggregateType.FP32) + { + inst |= Instruction.FP32; + } + + dest.VarType = type; + } + else + { + dest.VarType = InstructionInfo.GetDestVarType(inst); + } + + IAstNode source; + + if (operation is TextureOperation texOp) + { + if (texOp.Inst == Instruction.ImageLoad) + { + dest.VarType = texOp.Format.GetComponentType(); + } + + source = GetAstTextureOperation(texOp); + } + else if (!isCopy) + { + source = new AstOperation( + inst, + operation.StorageKind, + operation.ForcePrecise, + operation.Index, + sources, + operation.SourcesCount); + } + else + { + source = sources[0]; + } + + context.AddNode(new AstAssignment(dest, source)); + } + else if (operation.Inst == Instruction.Comment) + { + context.AddNode(new AstComment(((CommentNode)operation).Comment)); + } + else if (operation is TextureOperation texOp) + { + AstTextureOperation astTexOp = GetAstTextureOperation(texOp); + + context.AddNode(astTexOp); + } + else + { + context.AddNode(new AstOperation( + inst, + operation.StorageKind, + operation.ForcePrecise, + operation.Index, + sources, + operation.SourcesCount)); + } + + // Those instructions needs to be emulated by using helper functions, + // because they are NVIDIA specific. Those flags helps the backend to + // decide which helper functions are needed on the final generated code. + switch (operation.Inst) + { + case Instruction.MultiplyHighS32: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.MultiplyHighS32; + break; + case Instruction.MultiplyHighU32: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.MultiplyHighU32; + break; + case Instruction.SwizzleAdd: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.SwizzleAdd; + break; + case Instruction.FSIBegin: + case Instruction.FSIEnd: + context.Info.HelperFunctionsMask |= HelperFunctionsMask.FSI; + break; + } + } + + private static AggregateType GetVarTypeFromUses(Operand dest) + { + HashSet visited = new(); + + Queue pending = new(); + + bool Enqueue(Operand operand) + { + if (visited.Add(operand)) + { + pending.Enqueue(operand); + + return true; + } + + return false; + } + + Enqueue(dest); + + while (pending.TryDequeue(out Operand operand)) + { + foreach (INode useNode in operand.UseOps) + { + if (useNode is not Operation operation) + { + continue; + } + + if (operation.Inst == Instruction.Copy) + { + if (operation.Dest.Type == OperandType.LocalVariable) + { + if (Enqueue(operation.Dest)) + { + break; + } + } + else + { + return OperandInfo.GetVarType(operation.Dest.Type); + } + } + else + { + for (int index = 0; index < operation.SourcesCount; index++) + { + if (operation.GetSource(index) == operand) + { + return InstructionInfo.GetSrcVarType(operation.Inst, index); + } + } + } + } + } + + return AggregateType.S32; + } + + private static bool AreAllSourceTypesEqual(IAstNode[] sources, AggregateType type) + { + foreach (IAstNode node in sources) + { + if (node is not AstOperand operand) + { + return false; + } + + if (operand.VarType != type) + { + return false; + } + } + + return true; + } + + private static bool IsVectorDestInst(Instruction inst) + { + return inst switch + { + Instruction.ImageLoad or + Instruction.TextureSample => true, + _ => false, + }; + } + + private static bool IsBranchInst(Instruction inst) + { + return inst switch + { + Instruction.Branch or + Instruction.BranchIfFalse or + Instruction.BranchIfTrue => true, + _ => false, + }; + } + + private static bool IsBitwiseInst(Instruction inst) + { + return inst switch + { + Instruction.BitwiseAnd or + Instruction.BitwiseExclusiveOr or + Instruction.BitwiseNot or + Instruction.BitwiseOr => true, + _ => false, + }; + } + + private static Instruction GetLogicalFromBitwiseInst(Instruction inst) + { + return inst switch + { + Instruction.BitwiseAnd => Instruction.LogicalAnd, + Instruction.BitwiseExclusiveOr => Instruction.LogicalExclusiveOr, + Instruction.BitwiseNot => Instruction.LogicalNot, + Instruction.BitwiseOr => Instruction.LogicalOr, + _ => throw new ArgumentException($"Unexpected instruction \"{inst}\"."), + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs new file mode 100644 index 00000000..045662a1 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs @@ -0,0 +1,354 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using static Ryujinx.Graphics.Shader.StructuredIr.AstHelper; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class StructuredProgramContext + { + private HashSet _loopTails; + + private Stack<(AstBlock Block, int CurrEndIndex, int LoopEndIndex)> _blockStack; + + private Dictionary _localsMap; + + private Dictionary _gotoTempAsgs; + + private List _gotos; + + private AstBlock _currBlock; + + private int _currEndIndex; + private int _loopEndIndex; + + public StructuredFunction CurrentFunction { get; private set; } + + public StructuredProgramInfo Info { get; } + + public ShaderDefinitions Definitions { get; } + public ResourceManager ResourceManager { get; } + public bool DebugMode { get; } + + public StructuredProgramContext( + AttributeUsage attributeUsage, + ShaderDefinitions definitions, + ResourceManager resourceManager, + bool debugMode) + { + Info = new StructuredProgramInfo(); + + Definitions = definitions; + ResourceManager = resourceManager; + DebugMode = debugMode; + + if (definitions.GpPassthrough) + { + int passthroughAttributes = attributeUsage.PassthroughAttributes; + while (passthroughAttributes != 0) + { + int index = BitOperations.TrailingZeroCount(passthroughAttributes); + + Info.IoDefinitions.Add(new IoDefinition(StorageKind.Input, IoVariable.UserDefined, index)); + + passthroughAttributes &= ~(1 << index); + } + + 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)); + } + } + + public void EnterFunction( + int blocksCount, + string name, + AggregateType returnType, + AggregateType[] inArguments, + AggregateType[] outArguments) + { + _loopTails = new HashSet(); + + _blockStack = new Stack<(AstBlock, int, int)>(); + + _localsMap = new Dictionary(); + + _gotoTempAsgs = new Dictionary(); + + _gotos = new List(); + + _currBlock = new AstBlock(AstBlockType.Main); + + _currEndIndex = blocksCount; + _loopEndIndex = blocksCount; + + CurrentFunction = new StructuredFunction(_currBlock, name, returnType, inArguments, outArguments); + } + + public void LeaveFunction() + { + Info.Functions.Add(CurrentFunction); + } + + public void EnterBlock(BasicBlock block) + { + while (_currEndIndex == block.Index) + { + (_currBlock, _currEndIndex, _loopEndIndex) = _blockStack.Pop(); + } + + if (_gotoTempAsgs.TryGetValue(block.Index, out AstAssignment gotoTempAsg)) + { + AddGotoTempReset(block, gotoTempAsg); + } + + LookForDoWhileStatements(block); + } + + public void LeaveBlock(BasicBlock block, Operation branchOp) + { + LookForIfStatements(block, branchOp); + } + + private void LookForDoWhileStatements(BasicBlock block) + { + // Check if we have any predecessor whose index is greater than the + // current block, this indicates a loop. + bool done = false; + + foreach (BasicBlock predecessor in block.Predecessors.OrderByDescending(x => x.Index)) + { + // If not a loop, break. + if (predecessor.Index < block.Index) + { + break; + } + + // Check if we can create a do-while loop here (only possible if the loop end + // falls inside the current scope), if not add a goto instead. + if (predecessor.Index < _currEndIndex && !done) + { + // Create do-while loop block. We must avoid inserting a goto at the end + // of the loop later, when the tail block is processed. So we add the predecessor + // to a list of loop tails to prevent it from being processed later. + Operation branchOp = (Operation)predecessor.GetLastOp(); + + NewBlock(AstBlockType.DoWhile, branchOp, predecessor.Index + 1); + + _loopTails.Add(predecessor); + + done = true; + } + else + { + // Failed to create loop. Since this block is the loop head, we reset the + // goto condition variable here. The variable is always reset on the jump + // target, and this block is the jump target for some loop. + AddGotoTempReset(block, GetGotoTempAsg(block.Index)); + + break; + } + } + } + + private void LookForIfStatements(BasicBlock block, Operation branchOp) + { + if (block.Branch == null) + { + return; + } + + // We can only enclose the "if" when the branch lands before + // the end of the current block. If the current enclosing block + // is not a loop, then we can also do so if the branch lands + // right at the end of the current block. When it is a loop, + // this is not valid as the loop condition would be evaluated, + // and it could erroneously jump back to the start of the loop. + bool inRange = + block.Branch.Index < _currEndIndex || + (block.Branch.Index == _currEndIndex && block.Branch.Index < _loopEndIndex); + + bool isLoop = block.Branch.Index <= block.Index; + + if (inRange && !isLoop) + { + NewBlock(AstBlockType.If, branchOp, block.Branch.Index); + } + else if (!_loopTails.Contains(block)) + { + AstAssignment gotoTempAsg = GetGotoTempAsg(block.Branch.Index); + + // We use DoWhile type here, as the condition should be true for + // unconditional branches, or it should jump if the condition is true otherwise. + IAstNode cond = GetBranchCond(AstBlockType.DoWhile, branchOp); + + AddNode(Assign(gotoTempAsg.Destination, cond)); + + AstOperation branch = new(branchOp.Inst); + + AddNode(branch); + + GotoStatement gotoStmt = new(branch, gotoTempAsg, isLoop); + + _gotos.Add(gotoStmt); + } + } + + private AstAssignment GetGotoTempAsg(int index) + { + if (_gotoTempAsgs.TryGetValue(index, out AstAssignment gotoTempAsg)) + { + return gotoTempAsg; + } + + AstOperand gotoTemp = NewTemp(AggregateType.Bool); + + gotoTempAsg = Assign(gotoTemp, Const(IrConsts.False)); + + _gotoTempAsgs.Add(index, gotoTempAsg); + + return gotoTempAsg; + } + + private void AddGotoTempReset(BasicBlock block, AstAssignment gotoTempAsg) + { + // If it was already added, we don't need to add it again. + if (gotoTempAsg.Parent != null) + { + return; + } + + AddNode(gotoTempAsg); + + // For block 0, we don't need to add the extra "reset" at the beginning, + // because it is already the first node to be executed on the shader, + // so it is reset to false by the "local" assignment anyway. + if (block.Index != 0) + { + CurrentFunction.MainBlock.AddFirst(Assign(gotoTempAsg.Destination, Const(IrConsts.False))); + } + } + + private void NewBlock(AstBlockType type, Operation branchOp, int endIndex) + { + NewBlock(type, GetBranchCond(type, branchOp), endIndex); + } + + private void NewBlock(AstBlockType type, IAstNode cond, int endIndex) + { + AstBlock childBlock = new(type, cond); + + AddNode(childBlock); + + _blockStack.Push((_currBlock, _currEndIndex, _loopEndIndex)); + + _currBlock = childBlock; + _currEndIndex = endIndex; + + if (type == AstBlockType.DoWhile) + { + _loopEndIndex = endIndex; + } + } + + private IAstNode GetBranchCond(AstBlockType type, Operation branchOp) + { + IAstNode cond; + + if (branchOp.Inst == Instruction.Branch) + { + // If the branch is not conditional, the condition is a constant. + // For if it's false (always jump over, if block never executed). + // For loops it's always true (always loop). + cond = Const(type == AstBlockType.If ? IrConsts.False : IrConsts.True); + } + else + { + cond = GetOperand(branchOp.GetSource(0)); + + Instruction invInst = type == AstBlockType.If + ? Instruction.BranchIfTrue + : Instruction.BranchIfFalse; + + if (branchOp.Inst == invInst) + { + cond = new AstOperation(Instruction.LogicalNot, cond); + } + } + + return cond; + } + + public void AddNode(IAstNode node) + { + _currBlock.Add(node); + } + + public GotoStatement[] GetGotos() + { + return _gotos.ToArray(); + } + + public AstOperand NewTemp(AggregateType type) + { + AstOperand newTemp = Local(type); + + CurrentFunction.Locals.Add(newTemp); + + return newTemp; + } + + public IAstNode GetOperandOrCbLoad(Operand operand) + { + if (operand.Type == OperandType.ConstantBuffer) + { + int cbufSlot = operand.GetCbufSlot(); + int cbufOffset = operand.GetCbufOffset(); + + int binding = ResourceManager.GetConstantBufferBinding(cbufSlot); + int vecIndex = cbufOffset >> 2; + int elemIndex = cbufOffset & 3; + + ResourceManager.SetUsedConstantBufferBinding(binding); + + IAstNode[] sources = new IAstNode[] + { + new AstOperand(OperandType.Constant, binding), + new AstOperand(OperandType.Constant, 0), + new AstOperand(OperandType.Constant, vecIndex), + new AstOperand(OperandType.Constant, elemIndex), + }; + + return new AstOperation(Instruction.Load, StorageKind.ConstantBuffer, false, sources, sources.Length); + } + + return GetOperand(operand); + } + + public AstOperand GetOperand(Operand operand) + { + if (operand == null) + { + return null; + } + + if (operand.Type != OperandType.LocalVariable) + { + return new AstOperand(operand); + } + + if (!_localsMap.TryGetValue(operand, out AstOperand astOperand)) + { + astOperand = new AstOperand(operand); + + _localsMap.Add(operand, astOperand); + + CurrentFunction.Locals.Add(astOperand); + } + + return astOperand; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs new file mode 100644 index 00000000..ded2f2a8 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.StructuredIr +{ + class StructuredProgramInfo + { + public List Functions { get; } + + public HashSet IoDefinitions { get; } + + public HelperFunctionsMask HelperFunctionsMask { get; set; } + + public StructuredProgramInfo() + { + Functions = new List(); + + IoDefinitions = new HashSet(); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs new file mode 100644 index 00000000..1021dff0 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/StructuredIr/TextureDefinition.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.Graphics.Shader +{ + readonly struct TextureDefinition + { + public int Set { get; } + public int Binding { get; } + public int ArrayLength { get; } + public bool Separate { get; } + public string Name { get; } + public SamplerType Type { get; } + public TextureFormat Format { get; } + public TextureUsageFlags Flags { get; } + + public TextureDefinition( + int set, + int binding, + int arrayLength, + bool separate, + string name, + SamplerType type, + TextureFormat format, + TextureUsageFlags flags) + { + Set = set; + Binding = binding; + ArrayLength = arrayLength; + Separate = separate; + Name = name; + Type = type; + Format = format; + Flags = flags; + } + + public TextureDefinition(int set, int binding, string name, SamplerType type) : this(set, binding, 1, false, name, type, TextureFormat.Unknown, TextureUsageFlags.None) + { + } + + public TextureDefinition SetFlag(TextureUsageFlags flag) + { + return new TextureDefinition(Set, Binding, ArrayLength, Separate, Name, Type, Format, Flags | flag); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/SupportBuffer.cs b/src/Ryujinx.Graphics.Shader/SupportBuffer.cs new file mode 100644 index 00000000..d4d3cbf8 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/SupportBuffer.cs @@ -0,0 +1,100 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Shader +{ + public struct Vector4 + { + public T X; + public T Y; + public T Z; + public T W; + } + + enum SupportBufferField + { + // Must match the order of the fields on the struct. + FragmentAlphaTest, + FragmentIsBgra, + ViewportInverse, + ViewportSize, + FragmentRenderScaleCount, + RenderScale, + TfeOffset, + TfeVertexCount, + } + + public struct SupportBuffer + { + public const int Binding = 0; + + public static readonly int FieldSize; + public static readonly int RequiredSize; + + public static readonly int FragmentAlphaTestOffset; + public static readonly int FragmentIsBgraOffset; + public static readonly int ViewportInverseOffset; + public static readonly int ViewportSizeOffset; + public static readonly int FragmentRenderScaleCountOffset; + public static readonly int GraphicsRenderScaleOffset; + public static readonly int ComputeRenderScaleOffset; + public static readonly int TfeOffsetOffset; + public static readonly int TfeVertexCountOffset; + + public const int FragmentIsBgraCount = 8; + // One for the render target, 64 for the textures, and 8 for the images. + public const int RenderScaleMaxCount = 1 + 64 + 8; + + private static int OffsetOf(ref SupportBuffer storage, ref T target) + { + return (int)Unsafe.ByteOffset(ref Unsafe.As(ref storage), ref target); + } + + static SupportBuffer() + { + FieldSize = Unsafe.SizeOf>(); + RequiredSize = Unsafe.SizeOf(); + + SupportBuffer instance = new(); + + FragmentAlphaTestOffset = OffsetOf(ref instance, ref instance.FragmentAlphaTest); + FragmentIsBgraOffset = OffsetOf(ref instance, ref instance.FragmentIsBgra); + ViewportInverseOffset = OffsetOf(ref instance, ref instance.ViewportInverse); + ViewportSizeOffset = OffsetOf(ref instance, ref instance.ViewportSize); + FragmentRenderScaleCountOffset = OffsetOf(ref instance, ref instance.FragmentRenderScaleCount); + GraphicsRenderScaleOffset = OffsetOf(ref instance, ref instance.RenderScale); + ComputeRenderScaleOffset = GraphicsRenderScaleOffset + FieldSize; + TfeOffsetOffset = OffsetOf(ref instance, ref instance.TfeOffset); + TfeVertexCountOffset = OffsetOf(ref instance, ref instance.TfeVertexCount); + } + + internal static StructureType GetStructureType() + { + return new StructureType(new[] + { + new StructureField(AggregateType.U32, "alpha_test"), + new StructureField(AggregateType.Array | AggregateType.U32, "is_bgra", FragmentIsBgraCount), + new StructureField(AggregateType.Vector4 | AggregateType.FP32, "viewport_inverse"), + new StructureField(AggregateType.Vector4 | AggregateType.FP32, "viewport_size"), + new StructureField(AggregateType.S32, "frag_scale_count"), + new StructureField(AggregateType.Array | AggregateType.FP32, "render_scale", RenderScaleMaxCount), + new StructureField(AggregateType.Vector4 | AggregateType.S32, "tfe_offset"), + new StructureField(AggregateType.S32, "tfe_vertex_count"), + }); + } + + public Vector4 FragmentAlphaTest; + public Array8> FragmentIsBgra; + public Vector4 ViewportInverse; + public Vector4 ViewportSize; + public Vector4 FragmentRenderScaleCount; + + // Render scale max count: 1 + 64 + 8. First scale is fragment output scale, others are textures/image inputs. + public Array73> RenderScale; + + public Vector4 TfeOffset; + public Vector4 TfeVertexCount; + } +} diff --git a/src/Ryujinx.Graphics.Shader/TessPatchType.cs b/src/Ryujinx.Graphics.Shader/TessPatchType.cs new file mode 100644 index 00000000..76be22fd --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/TessPatchType.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum TessPatchType + { + Isolines = 0, + Triangles = 1, + Quads = 2, + } + + static class TessPatchTypeExtensions + { + public static string ToGlsl(this TessPatchType type) + { + return type switch + { + TessPatchType.Isolines => "isolines", + TessPatchType.Quads => "quads", + _ => "triangles", + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/TessSpacing.cs b/src/Ryujinx.Graphics.Shader/TessSpacing.cs new file mode 100644 index 00000000..6035366c --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/TessSpacing.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Graphics.Shader +{ + public enum TessSpacing + { + EqualSpacing = 0, + FractionalEventSpacing = 1, + FractionalOddSpacing = 2, + } + + static class TessSpacingExtensions + { + public static string ToGlsl(this TessSpacing spacing) + { + return spacing switch + { + TessSpacing.FractionalEventSpacing => "fractional_even_spacing", + TessSpacing.FractionalOddSpacing => "fractional_odd_spacing", + _ => "equal_spacing", + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs b/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs new file mode 100644 index 00000000..1e387407 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.Graphics.Shader +{ + public readonly struct TextureDescriptor + { + // New fields should be added to the end of the struct to keep disk shader cache compatibility. + + public readonly int Set; + public readonly int Binding; + + public readonly SamplerType Type; + public readonly TextureFormat Format; + + public readonly int CbufSlot; + public readonly int HandleIndex; + public readonly int ArrayLength; + + public readonly bool Separate; + + public readonly TextureUsageFlags Flags; + + public TextureDescriptor( + int set, + int binding, + SamplerType type, + TextureFormat format, + int cbufSlot, + int handleIndex, + int arrayLength, + bool separate, + TextureUsageFlags flags) + { + Set = set; + Binding = binding; + Type = type; + Format = format; + CbufSlot = cbufSlot; + HandleIndex = handleIndex; + ArrayLength = arrayLength; + Separate = separate; + Flags = flags; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/TextureFormat.cs b/src/Ryujinx.Graphics.Shader/TextureFormat.cs new file mode 100644 index 00000000..619598c7 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/TextureFormat.cs @@ -0,0 +1,130 @@ +using Ryujinx.Graphics.Shader.Translation; + +namespace Ryujinx.Graphics.Shader +{ + public enum TextureFormat + { + Unknown, + R8Unorm, + R8Snorm, + R8Uint, + R8Sint, + R16Float, + R16Unorm, + R16Snorm, + R16Uint, + R16Sint, + R32Float, + R32Uint, + R32Sint, + R8G8Unorm, + R8G8Snorm, + R8G8Uint, + R8G8Sint, + R16G16Float, + R16G16Unorm, + R16G16Snorm, + R16G16Uint, + R16G16Sint, + R32G32Float, + R32G32Uint, + R32G32Sint, + R8G8B8A8Unorm, + R8G8B8A8Snorm, + R8G8B8A8Uint, + R8G8B8A8Sint, + R16G16B16A16Float, + R16G16B16A16Unorm, + R16G16B16A16Snorm, + R16G16B16A16Uint, + R16G16B16A16Sint, + R32G32B32A32Float, + R32G32B32A32Uint, + R32G32B32A32Sint, + R10G10B10A2Unorm, + R10G10B10A2Uint, + R11G11B10Float, + } + + static class TextureFormatExtensions + { + public static string ToGlslFormat(this TextureFormat format) + { + return format switch + { +#pragma warning disable IDE0055 // Disable formatting + TextureFormat.R8Unorm => "r8", + TextureFormat.R8Snorm => "r8_snorm", + TextureFormat.R8Uint => "r8ui", + TextureFormat.R8Sint => "r8i", + TextureFormat.R16Float => "r16f", + TextureFormat.R16Unorm => "r16", + TextureFormat.R16Snorm => "r16_snorm", + TextureFormat.R16Uint => "r16ui", + TextureFormat.R16Sint => "r16i", + TextureFormat.R32Float => "r32f", + TextureFormat.R32Uint => "r32ui", + TextureFormat.R32Sint => "r32i", + TextureFormat.R8G8Unorm => "rg8", + TextureFormat.R8G8Snorm => "rg8_snorm", + TextureFormat.R8G8Uint => "rg8ui", + TextureFormat.R8G8Sint => "rg8i", + TextureFormat.R16G16Float => "rg16f", + TextureFormat.R16G16Unorm => "rg16", + TextureFormat.R16G16Snorm => "rg16_snorm", + TextureFormat.R16G16Uint => "rg16ui", + TextureFormat.R16G16Sint => "rg16i", + TextureFormat.R32G32Float => "rg32f", + TextureFormat.R32G32Uint => "rg32ui", + TextureFormat.R32G32Sint => "rg32i", + TextureFormat.R8G8B8A8Unorm => "rgba8", + TextureFormat.R8G8B8A8Snorm => "rgba8_snorm", + TextureFormat.R8G8B8A8Uint => "rgba8ui", + TextureFormat.R8G8B8A8Sint => "rgba8i", + TextureFormat.R16G16B16A16Float => "rgba16f", + TextureFormat.R16G16B16A16Unorm => "rgba16", + TextureFormat.R16G16B16A16Snorm => "rgba16_snorm", + TextureFormat.R16G16B16A16Uint => "rgba16ui", + TextureFormat.R16G16B16A16Sint => "rgba16i", + TextureFormat.R32G32B32A32Float => "rgba32f", + TextureFormat.R32G32B32A32Uint => "rgba32ui", + TextureFormat.R32G32B32A32Sint => "rgba32i", + TextureFormat.R10G10B10A2Unorm => "rgb10_a2", + TextureFormat.R10G10B10A2Uint => "rgb10_a2ui", + TextureFormat.R11G11B10Float => "r11f_g11f_b10f", + _ => string.Empty, +#pragma warning restore IDE0055 + }; + } + + public static AggregateType GetComponentType(this TextureFormat format) + { + switch (format) + { + case TextureFormat.R8Uint: + case TextureFormat.R16Uint: + case TextureFormat.R32Uint: + case TextureFormat.R8G8Uint: + case TextureFormat.R16G16Uint: + case TextureFormat.R32G32Uint: + case TextureFormat.R8G8B8A8Uint: + case TextureFormat.R16G16B16A16Uint: + case TextureFormat.R32G32B32A32Uint: + case TextureFormat.R10G10B10A2Uint: + return AggregateType.U32; + case TextureFormat.R8Sint: + case TextureFormat.R16Sint: + case TextureFormat.R32Sint: + case TextureFormat.R8G8Sint: + case TextureFormat.R16G16Sint: + case TextureFormat.R32G32Sint: + case TextureFormat.R8G8B8A8Sint: + case TextureFormat.R16G16B16A16Sint: + case TextureFormat.R32G32B32A32Sint: + return AggregateType.S32; + } + + return AggregateType.FP32; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/TextureHandle.cs b/src/Ryujinx.Graphics.Shader/TextureHandle.cs new file mode 100644 index 00000000..3aaceac4 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/TextureHandle.cs @@ -0,0 +1,125 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Shader +{ + public enum TextureHandleType + { + CombinedSampler = 0, // Must be 0. + SeparateSamplerHandle = 1, + SeparateSamplerId = 2, + SeparateConstantSamplerHandle = 3, + Direct = 4, + } + + public static class TextureHandle + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int PackSlots(int cbufSlot0, int cbufSlot1) + { + return cbufSlot0 | ((cbufSlot1 + 1) << 16); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static (int, int) UnpackSlots(int slots, int defaultTextureBufferIndex) + { + int textureBufferIndex; + int samplerBufferIndex; + + if (slots < 0) + { + textureBufferIndex = defaultTextureBufferIndex; + samplerBufferIndex = textureBufferIndex; + } + else + { + uint high = (uint)slots >> 16; + + textureBufferIndex = (ushort)slots; + samplerBufferIndex = high != 0 ? (int)high - 1 : textureBufferIndex; + } + + return (textureBufferIndex, samplerBufferIndex); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int PackOffsets(int cbufOffset0, int cbufOffset1, TextureHandleType type) + { + return cbufOffset0 | (cbufOffset1 << 14) | ((int)type << 28); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static (int, int, TextureHandleType) UnpackOffsets(int handle) + { + return (handle & 0x3fff, (handle >> 14) & 0x3fff, (TextureHandleType)((uint)handle >> 28)); + } + + /// + /// Unpacks the texture ID from the real texture handle. + /// + /// The real texture handle + /// The texture ID + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int UnpackTextureId(int packedId) + { + return packedId & 0xfffff; + } + + /// + /// Unpacks the sampler ID from the real texture handle. + /// + /// The real texture handle + /// The sampler ID + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int UnpackSamplerId(int packedId) + { + return (packedId >> 20) & 0xfff; + } + + /// + /// Reads a packed texture and sampler ID (basically, the real texture handle) + /// from a given texture/sampler constant buffer. + /// + /// A word offset of the handle on the buffer (the "fake" shader handle) + /// The constant buffer to fetch texture IDs from + /// The constant buffer to fetch sampler IDs from + /// The packed texture and sampler ID (the real texture handle) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReadPackedId(int wordOffset, ReadOnlySpan cachedTextureBuffer, ReadOnlySpan cachedSamplerBuffer) + { + (int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = UnpackOffsets(wordOffset); + + int handle = textureWordOffset < cachedTextureBuffer.Length ? cachedTextureBuffer[textureWordOffset] : 0; + + // The "wordOffset" (which is really the immediate value used on texture instructions on the shader) + // is a 13-bit value. However, in order to also support separate samplers and textures (which uses + // bindless textures on the shader), we extend it with another value on the higher 16 bits with + // another offset for the sampler. + // The shader translator has code to detect separate texture and sampler uses with a bindless texture, + // turn that into a regular texture access and produce those special handles with values on the higher 16 bits. + if (handleType != TextureHandleType.CombinedSampler) + { + int samplerHandle; + + if (handleType != TextureHandleType.SeparateConstantSamplerHandle) + { + samplerHandle = samplerWordOffset < cachedSamplerBuffer.Length ? cachedSamplerBuffer[samplerWordOffset] : 0; + } + else + { + samplerHandle = samplerWordOffset; + } + + if (handleType == TextureHandleType.SeparateSamplerId || + handleType == TextureHandleType.SeparateConstantSamplerHandle) + { + samplerHandle <<= 20; + } + + handle |= samplerHandle; + } + + return handle; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/TextureUsageFlags.cs b/src/Ryujinx.Graphics.Shader/TextureUsageFlags.cs new file mode 100644 index 00000000..306435fd --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/TextureUsageFlags.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ryujinx.Graphics.Shader +{ + /// + /// Flags that indicate how a texture will be used in a shader. + /// + [Flags] + public enum TextureUsageFlags + { + None = 0, + + // Integer sampled textures must be noted for resolution scaling. + ResScaleUnsupported = 1 << 0, + NeedsScaleValue = 1 << 1, + ImageStore = 1 << 2, + ImageCoherent = 1 << 3, + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/AggregateType.cs b/src/Ryujinx.Graphics.Shader/Translation/AggregateType.cs new file mode 100644 index 00000000..108fcb94 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/AggregateType.cs @@ -0,0 +1,61 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.Graphics.Shader.Translation +{ + [Flags] + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + enum AggregateType + { + Invalid, + Void, + Bool, + FP32, + FP64, + S32, + U32, + + ElementTypeMask = 0xff, + + ElementCountShift = 8, + ElementCountMask = 3 << ElementCountShift, + + Scalar = 0 << ElementCountShift, + Vector2 = 1 << ElementCountShift, + Vector3 = 2 << ElementCountShift, + Vector4 = 3 << ElementCountShift, + + Array = 1 << 10, + } + + static class AggregateTypeExtensions + { + public static int GetSizeInBytes(this AggregateType type) + { + int elementSize = (type & AggregateType.ElementTypeMask) switch + { + AggregateType.Bool or + AggregateType.FP32 or + AggregateType.S32 or + AggregateType.U32 => 4, + AggregateType.FP64 => 8, + _ => 0, + }; + + switch (type & AggregateType.ElementCountMask) + { + case AggregateType.Vector2: + elementSize *= 2; + break; + case AggregateType.Vector3: + elementSize *= 3; + break; + case AggregateType.Vector4: + elementSize *= 4; + break; + } + + return elementSize; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs b/src/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs new file mode 100644 index 00000000..c4bd2cbf --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs @@ -0,0 +1,38 @@ +namespace Ryujinx.Graphics.Shader.Translation +{ + static class AttributeConsts + { + public const int PrimitiveId = 0x060; + public const int Layer = 0x064; + public const int ViewportIndex = 0x068; + 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 ViewportMask = 0x3a0; + 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 UserAttributePerPatchBase = 0x18; + public const int UserAttributePerPatchEnd = 0x200; + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/AttributeUsage.cs b/src/Ryujinx.Graphics.Shader/Translation/AttributeUsage.cs new file mode 100644 index 00000000..9dab9fdf --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/AttributeUsage.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace Ryujinx.Graphics.Shader.Translation +{ + class AttributeUsage + { + public bool NextUsesFixedFuncAttributes { get; private set; } + public int UsedInputAttributes { get; private set; } + public int UsedOutputAttributes { get; private set; } + public HashSet UsedInputAttributesPerPatch { get; } + public HashSet UsedOutputAttributesPerPatch { get; } + public HashSet NextUsedInputAttributesPerPatch { get; private set; } + public int PassthroughAttributes { get; private set; } + private int _nextUsedInputAttributes; + private int _thisUsedInputAttributes; + private Dictionary _perPatchAttributeLocations; + private readonly IGpuAccessor _gpuAccessor; + + public UInt128 NextInputAttributesComponents { get; private set; } + public UInt128 ThisInputAttributesComponents { get; private set; } + + public AttributeUsage(IGpuAccessor gpuAccessor) + { + _gpuAccessor = gpuAccessor; + + UsedInputAttributesPerPatch = new(); + UsedOutputAttributesPerPatch = new(); + } + + public void SetInputUserAttribute(int index, int component) + { + int mask = 1 << index; + + UsedInputAttributes |= mask; + _thisUsedInputAttributes |= mask; + ThisInputAttributesComponents |= UInt128.One << (index * 4 + component); + } + + public void SetInputUserAttributePerPatch(int index) + { + UsedInputAttributesPerPatch.Add(index); + } + + public void SetOutputUserAttribute(int index) + { + UsedOutputAttributes |= 1 << index; + } + + public void SetOutputUserAttributePerPatch(int index) + { + UsedOutputAttributesPerPatch.Add(index); + } + + public void MergeFromtNextStage(bool gpPassthrough, bool nextUsesFixedFunctionAttributes, AttributeUsage nextStage) + { + NextInputAttributesComponents = nextStage.ThisInputAttributesComponents; + NextUsedInputAttributesPerPatch = nextStage.UsedInputAttributesPerPatch; + NextUsesFixedFuncAttributes = nextUsesFixedFunctionAttributes; + MergeOutputUserAttributes(gpPassthrough, nextStage.UsedInputAttributes, nextStage.UsedInputAttributesPerPatch); + + if (UsedOutputAttributesPerPatch.Count != 0) + { + // Regular and per-patch input/output locations can't overlap, + // so we must assign on our location using unused regular input/output locations. + + Dictionary locationsMap = new(); + + int freeMask = ~UsedOutputAttributes; + + foreach (int attr in UsedOutputAttributesPerPatch) + { + int location = BitOperations.TrailingZeroCount(freeMask); + if (location == 32) + { + _gpuAccessor.Log($"No enough free locations for patch input/output 0x{attr:X}."); + break; + } + + locationsMap.Add(attr, location); + freeMask &= ~(1 << location); + } + + // Both stages must agree on the locations, so use the same "map" for both. + _perPatchAttributeLocations = locationsMap; + nextStage._perPatchAttributeLocations = locationsMap; + } + } + + private void MergeOutputUserAttributes(bool gpPassthrough, int mask, IEnumerable perPatch) + { + _nextUsedInputAttributes = mask; + + if (gpPassthrough) + { + PassthroughAttributes = mask & ~UsedOutputAttributes; + } + else + { + UsedOutputAttributes |= mask; + UsedOutputAttributesPerPatch.UnionWith(perPatch); + } + } + + public int GetPerPatchAttributeLocation(int index) + { + if (_perPatchAttributeLocations == null || !_perPatchAttributeLocations.TryGetValue(index, out int location)) + { + return index; + } + + return location; + } + + public bool IsUsedOutputAttribute(int attr) + { + // The check for fixed function attributes on the next stage is conservative, + // returning false if the output is just not used by the next stage is also valid. + if (NextUsesFixedFuncAttributes && + attr >= AttributeConsts.UserAttributeBase && + attr < AttributeConsts.UserAttributeEnd) + { + int index = (attr - AttributeConsts.UserAttributeBase) >> 4; + return (_nextUsedInputAttributes & (1 << index)) != 0; + } + + return true; + } + + public int GetFreeUserAttribute(bool isOutput, int index) + { + int useMask = isOutput ? _nextUsedInputAttributes : _thisUsedInputAttributes; + int bit = -1; + + while (useMask != -1) + { + bit = BitOperations.TrailingZeroCount(~useMask); + + if (bit == 32) + { + bit = -1; + break; + } + else if (index < 1) + { + break; + } + + useMask |= 1 << bit; + index--; + } + + return bit; + } + + public void SetAllInputUserAttributes() + { + UsedInputAttributes |= Constants.AllAttributesMask; + ThisInputAttributesComponents |= ~UInt128.Zero >> (128 - Constants.MaxAttributes * 4); + } + + public void SetAllOutputUserAttributes() + { + UsedOutputAttributes |= Constants.AllAttributesMask; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/ControlFlowGraph.cs b/src/Ryujinx.Graphics.Shader/Translation/ControlFlowGraph.cs new file mode 100644 index 00000000..9b07c28f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/ControlFlowGraph.cs @@ -0,0 +1,176 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Translation +{ + class ControlFlowGraph + { + public BasicBlock[] Blocks { get; } + public BasicBlock[] PostOrderBlocks { get; } + public int[] PostOrderMap { get; } + + public ControlFlowGraph(BasicBlock[] blocks) + { + Blocks = blocks; + + HashSet visited = new(); + + Stack blockStack = new(); + + List postOrderBlocks = new(blocks.Length); + + PostOrderMap = new int[blocks.Length]; + + visited.Add(blocks[0]); + + blockStack.Push(blocks[0]); + + while (blockStack.TryPop(out BasicBlock block)) + { + if (block.Next != null && visited.Add(block.Next)) + { + blockStack.Push(block); + blockStack.Push(block.Next); + } + else if (block.Branch != null && visited.Add(block.Branch)) + { + blockStack.Push(block); + blockStack.Push(block.Branch); + } + else + { + PostOrderMap[block.Index] = postOrderBlocks.Count; + + postOrderBlocks.Add(block); + } + } + + PostOrderBlocks = postOrderBlocks.ToArray(); + } + + public static ControlFlowGraph Create(Operation[] operations) + { + Dictionary labels = new(); + + List blocks = new(); + + BasicBlock currentBlock = null; + + void NextBlock(BasicBlock nextBlock) + { + if (currentBlock != null && !EndsWithUnconditionalInst(currentBlock.GetLastOp())) + { + currentBlock.Next = nextBlock; + } + + currentBlock = nextBlock; + } + + void NewNextBlock() + { + BasicBlock block = new(blocks.Count); + + blocks.Add(block); + + NextBlock(block); + } + + bool needsNewBlock = true; + + for (int index = 0; index < operations.Length; index++) + { + Operation operation = operations[index]; + + if (operation.Inst == Instruction.MarkLabel) + { + Operand label = operation.Dest; + + if (labels.TryGetValue(label, out BasicBlock nextBlock)) + { + nextBlock.Index = blocks.Count; + + blocks.Add(nextBlock); + + NextBlock(nextBlock); + } + else + { + NewNextBlock(); + + labels.Add(label, currentBlock); + } + } + else + { + if (needsNewBlock) + { + NewNextBlock(); + } + + currentBlock.Operations.AddLast(operation); + } + + needsNewBlock = operation.Inst == Instruction.Branch || + operation.Inst == Instruction.BranchIfTrue || + operation.Inst == Instruction.BranchIfFalse; + + if (needsNewBlock) + { + Operand label = operation.Dest; + + if (!labels.TryGetValue(label, out BasicBlock branchBlock)) + { + branchBlock = new BasicBlock(); + + labels.Add(label, branchBlock); + } + + currentBlock.Branch = branchBlock; + } + } + + // Remove unreachable blocks. + bool hasUnreachable; + + do + { + hasUnreachable = false; + + for (int blkIndex = 1; blkIndex < blocks.Count; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + if (block.Predecessors.Count == 0) + { + block.Next = null; + block.Branch = null; + blocks.RemoveAt(blkIndex--); + hasUnreachable = true; + } + else + { + block.Index = blkIndex; + } + } + } while (hasUnreachable); + + return new ControlFlowGraph(blocks.ToArray()); + } + + private static bool EndsWithUnconditionalInst(INode node) + { + if (node is Operation operation) + { + switch (operation.Inst) + { + case Instruction.Branch: + case Instruction.Discard: + case Instruction.Return: + return true; + } + } + + return false; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Dominance.cs b/src/Ryujinx.Graphics.Shader/Translation/Dominance.cs new file mode 100644 index 00000000..cd651ce0 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Dominance.cs @@ -0,0 +1,94 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class Dominance + { + // Those methods are an implementation of the algorithms on "A Simple, Fast Dominance Algorithm". + // https://www.cs.rice.edu/~keith/EMBED/dom.pdf + public static void FindDominators(ControlFlowGraph cfg) + { + BasicBlock Intersect(BasicBlock block1, BasicBlock block2) + { + while (block1 != block2) + { + while (cfg.PostOrderMap[block1.Index] < cfg.PostOrderMap[block2.Index]) + { + block1 = block1.ImmediateDominator; + } + + while (cfg.PostOrderMap[block2.Index] < cfg.PostOrderMap[block1.Index]) + { + block2 = block2.ImmediateDominator; + } + } + + return block1; + } + + cfg.Blocks[0].ImmediateDominator = cfg.Blocks[0]; + + bool modified; + + do + { + modified = false; + + for (int blkIndex = cfg.PostOrderBlocks.Length - 2; blkIndex >= 0; blkIndex--) + { + BasicBlock block = cfg.PostOrderBlocks[blkIndex]; + + BasicBlock newIDom = null; + + foreach (BasicBlock predecessor in block.Predecessors) + { + if (predecessor.ImmediateDominator != null) + { + if (newIDom != null) + { + newIDom = Intersect(predecessor, newIDom); + } + else + { + newIDom = predecessor; + } + } + } + + if (block.ImmediateDominator != newIDom) + { + block.ImmediateDominator = newIDom; + + modified = true; + } + } + } + while (modified); + } + + public static void FindDominanceFrontiers(BasicBlock[] blocks) + { + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + if (block.Predecessors.Count < 2) + { + continue; + } + + for (int pBlkIndex = 0; pBlkIndex < block.Predecessors.Count; pBlkIndex++) + { + BasicBlock current = block.Predecessors[pBlkIndex]; + + while (current != block.ImmediateDominator) + { + current.DominanceFrontiers.Add(block); + + current = current.ImmediateDominator; + } + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs new file mode 100644 index 00000000..5e07b39f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs @@ -0,0 +1,627 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + class EmitterContext + { + public DecodedProgram Program { get; } + public TranslatorContext TranslatorContext { get; } + public ResourceManager ResourceManager { get; } + + public bool VertexAsCompute { get; } + + public bool IsNonMain { get; } + + public Block CurrBlock { get; set; } + public InstOp CurrOp { get; set; } + + public int OperationsCount => _operations.Count; + + private readonly struct BrxTarget + { + public readonly Operand Selector; + public readonly int ExpectedValue; + public readonly ulong NextTargetAddress; + + public BrxTarget(Operand selector, int expectedValue, ulong nextTargetAddress) + { + Selector = selector; + ExpectedValue = expectedValue; + NextTargetAddress = nextTargetAddress; + } + } + + private class BlockLabel + { + public readonly Operand Label; + public BrxTarget BrxTarget; + + public BlockLabel(Operand label) + { + Label = label; + } + } + + private readonly List _operations; + private readonly Dictionary _labels; + + public EmitterContext() + { + _operations = new List(); + _labels = new Dictionary(); + } + + public EmitterContext( + TranslatorContext translatorContext, + ResourceManager resourceManager, + DecodedProgram program, + bool vertexAsCompute, + bool isNonMain) : this() + { + TranslatorContext = translatorContext; + ResourceManager = resourceManager; + Program = program; + VertexAsCompute = vertexAsCompute; + IsNonMain = isNonMain; + + EmitStart(); + } + + private void EmitStart() + { + if (TranslatorContext.Options.Flags.HasFlag(TranslationFlags.VertexA)) + { + return; + } + + // Vulkan requires the point size to be always written on the shader if the primitive topology is points. + // OpenGL requires the point size to be always written on the shader if PROGRAM_POINT_SIZE is set. + if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex) + { + this.Store(StorageKind.Output, IoVariable.PointSize, null, ConstF(TranslatorContext.Definitions.PointSize)); + } + + if (VertexAsCompute) + { + int vertexInfoCbBinding = ResourceManager.Reservations.VertexInfoConstantBufferBinding; + int countFieldIndex = TranslatorContext.Stage == ShaderStage.Vertex + ? (int)VertexInfoBufferField.VertexCounts + : (int)VertexInfoBufferField.GeometryCounts; + + Operand outputVertexOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(0)); + Operand vertexCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const(countFieldIndex), Const(0)); + Operand isVertexOob = this.ICompareGreaterOrEqualUnsigned(outputVertexOffset, vertexCount); + + Operand lblVertexInBounds = Label(); + + this.BranchIfFalse(lblVertexInBounds, isVertexOob); + this.Return(); + this.MarkLabel(lblVertexInBounds); + + Operand outputInstanceOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(1)); + Operand instanceCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(1)); + Operand firstVertex = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(2)); + Operand firstInstance = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(3)); + Operand ibBaseOffset = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.GeometryCounts), Const(3)); + Operand isInstanceOob = this.ICompareGreaterOrEqualUnsigned(outputInstanceOffset, instanceCount); + + Operand lblInstanceInBounds = Label(); + + this.BranchIfFalse(lblInstanceInBounds, isInstanceOob); + this.Return(); + this.MarkLabel(lblInstanceInBounds); + + if (TranslatorContext.Stage == ShaderStage.Vertex) + { + Operand vertexIndexVr = Local(); + + this.TextureSample( + SamplerType.TextureBuffer, + TextureFlags.IntCoords, + ResourceManager.Reservations.GetIndexBufferTextureSetAndBinding(), + 1, + new[] { vertexIndexVr }, + new[] { this.IAdd(ibBaseOffset, outputVertexOffset) }); + + this.Store(StorageKind.LocalMemory, ResourceManager.LocalVertexIndexVertexRateMemoryId, this.IAdd(firstVertex, vertexIndexVr)); + this.Store(StorageKind.LocalMemory, ResourceManager.LocalVertexIndexInstanceRateMemoryId, this.IAdd(firstInstance, outputInstanceOffset)); + } + else if (TranslatorContext.Stage == ShaderStage.Geometry) + { + int inputVertices = TranslatorContext.Definitions.InputTopology.ToInputVertices(); + + Operand baseVertex = this.IMultiply(outputVertexOffset, Const(inputVertices)); + + for (int index = 0; index < inputVertices; index++) + { + Operand vertexIndex = Local(); + + this.TextureSample( + SamplerType.TextureBuffer, + TextureFlags.IntCoords, + ResourceManager.Reservations.GetTopologyRemapBufferTextureSetAndBinding(), + 1, + new[] { vertexIndex }, + new[] { this.IAdd(baseVertex, Const(index)) }); + + this.Store(StorageKind.LocalMemory, ResourceManager.LocalTopologyRemapMemoryId, Const(index), vertexIndex); + } + + this.Store(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputVertexCountMemoryId, Const(0)); + this.Store(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputIndexCountMemoryId, Const(0)); + } + } + } + + public T GetOp() where T : unmanaged + { + Debug.Assert(Unsafe.SizeOf() == sizeof(ulong)); + ulong op = CurrOp.RawOpCode; + return Unsafe.As(ref op); + } + + public Operand Add(Instruction inst, Operand dest = null, params Operand[] sources) + { + Operation operation = new(inst, dest, sources); + + _operations.Add(operation); + + return dest; + } + + public Operand Add(Instruction inst, StorageKind storageKind, Operand dest = null, params Operand[] sources) + { + Operation operation = new(inst, storageKind, dest, sources); + + _operations.Add(operation); + + return dest; + } + + public (Operand, Operand) Add(Instruction inst, (Operand, Operand) dest, params Operand[] sources) + { + Operand[] dests = new[] { dest.Item1, dest.Item2 }; + + Operation operation = new(inst, 0, dests, sources); + + Add(operation); + + return dest; + } + + public void Add(Operation operation) + { + _operations.Add(operation); + } + + public void MarkLabel(Operand label) + { + Add(Instruction.MarkLabel, label); + } + + public Operand GetLabel(ulong address) + { + return EnsureBlockLabel(address).Label; + } + + public void SetBrxTarget(ulong address, Operand selector, int targetValue, ulong nextTargetAddress) + { + BlockLabel blockLabel = EnsureBlockLabel(address); + Debug.Assert(blockLabel.BrxTarget.Selector == null); + blockLabel.BrxTarget = new BrxTarget(selector, targetValue, nextTargetAddress); + } + + public void EnterBlock(ulong address) + { + BlockLabel blockLabel = EnsureBlockLabel(address); + + MarkLabel(blockLabel.Label); + + BrxTarget brxTarget = blockLabel.BrxTarget; + + if (brxTarget.Selector != null) + { + this.BranchIfFalse(GetLabel(brxTarget.NextTargetAddress), this.ICompareEqual(brxTarget.Selector, Const(brxTarget.ExpectedValue))); + } + } + + private BlockLabel EnsureBlockLabel(ulong address) + { + if (!_labels.TryGetValue(address, out BlockLabel blockLabel)) + { + blockLabel = new BlockLabel(Label()); + + _labels.Add(address, blockLabel); + } + + return blockLabel; + } + + public void PrepareForVertexReturn() + { + // TODO: Support transform feedback emulation on stages other than vertex. + // Those stages might produce more primitives, so it needs a way to "compact" the output after it is written. + + if (!TranslatorContext.GpuAccessor.QueryHostSupportsTransformFeedback() && + TranslatorContext.GpuAccessor.QueryTransformFeedbackEnabled() && + TranslatorContext.Stage == ShaderStage.Vertex) + { + Operand vertexCount = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.TfeVertexCount)); + + for (int tfbIndex = 0; tfbIndex < ResourceReservations.TfeBuffersCount; tfbIndex++) + { + var locations = TranslatorContext.GpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex); + var stride = TranslatorContext.GpuAccessor.QueryTransformFeedbackStride(tfbIndex); + + Operand baseOffset = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.TfeOffset), Const(tfbIndex)); + Operand baseVertex = this.Load(StorageKind.Input, IoVariable.BaseVertex); + Operand baseInstance = this.Load(StorageKind.Input, IoVariable.BaseInstance); + Operand vertexIndex = this.Load(StorageKind.Input, IoVariable.VertexIndex); + Operand instanceIndex = this.Load(StorageKind.Input, IoVariable.InstanceIndex); + + Operand outputVertexOffset = this.ISubtract(vertexIndex, baseVertex); + Operand outputInstanceOffset = this.ISubtract(instanceIndex, baseInstance); + + Operand outputBaseVertex = this.IMultiply(outputInstanceOffset, vertexCount); + + Operand vertexOffset = this.IMultiply(this.IAdd(outputBaseVertex, outputVertexOffset), Const(stride / 4)); + baseOffset = this.IAdd(baseOffset, vertexOffset); + + for (int j = 0; j < locations.Length; j++) + { + byte location = locations[j]; + if (location == 0xff) + { + continue; + } + + Operand offset = this.IAdd(baseOffset, Const(j)); + Operand value = Instructions.AttributeMap.GenerateAttributeLoad(this, null, location * 4, isOutput: true, isPerPatch: false); + + int binding = ResourceManager.Reservations.GetTfeBufferStorageBufferBinding(tfbIndex); + + this.Store(StorageKind.StorageBuffer, binding, Const(0), offset, value); + } + } + } + + if (TranslatorContext.Definitions.ViewportTransformDisable) + { + Operand x = this.Load(StorageKind.Output, IoVariable.Position, null, Const(0)); + Operand y = this.Load(StorageKind.Output, IoVariable.Position, null, Const(1)); + Operand xScale = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.ViewportInverse), Const(0)); + Operand yScale = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.ViewportInverse), Const(1)); + Operand negativeOne = ConstF(-1.0f); + + this.Store(StorageKind.Output, IoVariable.Position, null, Const(0), this.FPFusedMultiplyAdd(x, xScale, negativeOne)); + this.Store(StorageKind.Output, IoVariable.Position, null, Const(1), this.FPFusedMultiplyAdd(y, yScale, negativeOne)); + } + + if (TranslatorContext.Definitions.DepthMode && !TranslatorContext.GpuAccessor.QueryHostSupportsDepthClipControl()) + { + Operand z = this.Load(StorageKind.Output, IoVariable.Position, null, Const(2)); + Operand w = this.Load(StorageKind.Output, IoVariable.Position, null, Const(3)); + Operand halfW = this.FPMultiply(w, ConstF(0.5f)); + + this.Store(StorageKind.Output, IoVariable.Position, null, Const(2), this.FPFusedMultiplyAdd(z, ConstF(0.5f), halfW)); + } + } + + public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal) + { + if (TranslatorContext.Definitions.ViewportTransformDisable) + { + oldXLocal = Local(); + this.Copy(oldXLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(0))); + oldYLocal = Local(); + this.Copy(oldYLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(1))); + } + else + { + oldXLocal = null; + oldYLocal = null; + } + + if (TranslatorContext.Definitions.DepthMode && !TranslatorContext.GpuAccessor.QueryHostSupportsDepthClipControl()) + { + oldZLocal = Local(); + this.Copy(oldZLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(2))); + } + else + { + oldZLocal = null; + } + + PrepareForVertexReturn(); + } + + public bool PrepareForReturn() + { + if (IsNonMain) + { + return true; + } + + if (TranslatorContext.Definitions.LastInVertexPipeline && + (TranslatorContext.Definitions.Stage == ShaderStage.Vertex || TranslatorContext.Definitions.Stage == ShaderStage.TessellationEvaluation) && + (TranslatorContext.Options.Flags & TranslationFlags.VertexA) == 0) + { + PrepareForVertexReturn(); + } + else if (TranslatorContext.Definitions.Stage == ShaderStage.Geometry) + { + void WritePositionOutput(int primIndex) + { + Operand x = this.Load(StorageKind.Input, IoVariable.Position, Const(primIndex), Const(0)); + Operand y = this.Load(StorageKind.Input, IoVariable.Position, Const(primIndex), Const(1)); + Operand z = this.Load(StorageKind.Input, IoVariable.Position, Const(primIndex), Const(2)); + Operand w = this.Load(StorageKind.Input, IoVariable.Position, Const(primIndex), Const(3)); + + this.Store(StorageKind.Output, IoVariable.Position, null, Const(0), x); + this.Store(StorageKind.Output, IoVariable.Position, null, Const(1), y); + this.Store(StorageKind.Output, IoVariable.Position, null, Const(2), z); + this.Store(StorageKind.Output, IoVariable.Position, null, Const(3), w); + } + + void WriteUserDefinedOutput(int index, int primIndex) + { + Operand x = this.Load(StorageKind.Input, IoVariable.UserDefined, Const(index), Const(primIndex), Const(0)); + Operand y = this.Load(StorageKind.Input, IoVariable.UserDefined, Const(index), Const(primIndex), Const(1)); + Operand z = this.Load(StorageKind.Input, IoVariable.UserDefined, Const(index), Const(primIndex), Const(2)); + Operand w = this.Load(StorageKind.Input, IoVariable.UserDefined, Const(index), Const(primIndex), Const(3)); + + this.Store(StorageKind.Output, IoVariable.UserDefined, null, Const(index), Const(0), x); + this.Store(StorageKind.Output, IoVariable.UserDefined, null, Const(index), Const(1), y); + this.Store(StorageKind.Output, IoVariable.UserDefined, null, Const(index), Const(2), z); + this.Store(StorageKind.Output, IoVariable.UserDefined, null, Const(index), Const(3), w); + } + + if (TranslatorContext.Definitions.GpPassthrough && !TranslatorContext.GpuAccessor.QueryHostSupportsGeometryShaderPassthrough()) + { + int inputStart, inputEnd, inputStep; + + InputTopology topology = TranslatorContext.Definitions.InputTopology; + + if (topology == InputTopology.LinesAdjacency) + { + inputStart = 1; + inputEnd = 3; + inputStep = 1; + } + else if (topology == InputTopology.TrianglesAdjacency) + { + inputStart = 0; + inputEnd = 6; + inputStep = 2; + } + else + { + inputStart = 0; + inputEnd = topology.ToInputVerticesNoAdjacency(); + inputStep = 1; + } + + for (int primIndex = inputStart; primIndex < inputEnd; primIndex += inputStep) + { + WritePositionOutput(primIndex); + + int passthroughAttributes = TranslatorContext.AttributeUsage.PassthroughAttributes; + while (passthroughAttributes != 0) + { + int index = BitOperations.TrailingZeroCount(passthroughAttributes); + WriteUserDefinedOutput(index, primIndex); + passthroughAttributes &= ~(1 << index); + } + + this.EmitVertex(); + } + + this.EndPrimitive(); + } + } + else if (TranslatorContext.Definitions.Stage == ShaderStage.Fragment) + { + GenerateAlphaToCoverageDitherDiscard(); + + bool supportsBgra = TranslatorContext.GpuAccessor.QueryHostSupportsBgraFormat(); + + if (TranslatorContext.Definitions.OmapDepth) + { + Operand src = Register(TranslatorContext.GetDepthRegister(), RegisterType.Gpr); + + this.Store(StorageKind.Output, IoVariable.FragmentOutputDepth, null, src); + } + + AlphaTestOp alphaTestOp = TranslatorContext.Definitions.AlphaTestCompare; + + if (alphaTestOp != AlphaTestOp.Always) + { + if (alphaTestOp == AlphaTestOp.Never) + { + this.Discard(); + } + else if ((TranslatorContext.Definitions.OmapTargets & 8) != 0) + { + Instruction comparator = alphaTestOp switch + { + AlphaTestOp.Equal => Instruction.CompareEqual, + AlphaTestOp.Greater => Instruction.CompareGreater, + AlphaTestOp.GreaterOrEqual => Instruction.CompareGreaterOrEqual, + AlphaTestOp.Less => Instruction.CompareLess, + AlphaTestOp.LessOrEqual => Instruction.CompareLessOrEqual, + AlphaTestOp.NotEqual => Instruction.CompareNotEqual, + _ => 0, + }; + + Debug.Assert(comparator != 0, $"Invalid alpha test operation \"{alphaTestOp}\"."); + + Operand alpha = Register(3, RegisterType.Gpr); + Operand alphaRef = ConstF(TranslatorContext.Definitions.AlphaTestReference); + Operand alphaPass = Add(Instruction.FP32 | comparator, Local(), alpha, alphaRef); + Operand alphaPassLabel = Label(); + + this.BranchIfTrue(alphaPassLabel, alphaPass); + this.Discard(); + this.MarkLabel(alphaPassLabel); + } + } + + // We don't need to output anything if alpha test always fails. + if (alphaTestOp == AlphaTestOp.Never) + { + return false; + } + + int regIndexBase = 0; + + for (int rtIndex = 0; rtIndex < 8; rtIndex++) + { + for (int component = 0; component < 4; component++) + { + bool componentEnabled = (TranslatorContext.Definitions.OmapTargets & (1 << (rtIndex * 4 + component))) != 0; + if (!componentEnabled) + { + continue; + } + + Operand src = Register(regIndexBase + component, RegisterType.Gpr); + + // Perform B <-> R swap if needed, for BGRA formats (not supported on OpenGL). + if (!supportsBgra && (component == 0 || component == 2)) + { + Operand isBgra = this.Load(StorageKind.ConstantBuffer, SupportBuffer.Binding, Const((int)SupportBufferField.FragmentIsBgra), Const(rtIndex)); + + Operand lblIsBgra = Label(); + Operand lblEnd = Label(); + + this.BranchIfTrue(lblIsBgra, isBgra); + + this.Store(StorageKind.Output, IoVariable.FragmentOutputColor, null, Const(rtIndex), Const(component), src); + this.Branch(lblEnd); + + MarkLabel(lblIsBgra); + + this.Store(StorageKind.Output, IoVariable.FragmentOutputColor, null, Const(rtIndex), Const(2 - component), src); + + MarkLabel(lblEnd); + } + else + { + this.Store(StorageKind.Output, IoVariable.FragmentOutputColor, null, Const(rtIndex), Const(component), src); + } + } + + bool targetEnabled = (TranslatorContext.Definitions.OmapTargets & (0xf << (rtIndex * 4))) != 0; + if (targetEnabled) + { + regIndexBase += 4; + } + } + } + + if (VertexAsCompute) + { + if (TranslatorContext.Stage == ShaderStage.Vertex) + { + int vertexInfoCbBinding = ResourceManager.Reservations.VertexInfoConstantBufferBinding; + int vertexOutputSbBinding = ResourceManager.Reservations.VertexOutputStorageBufferBinding; + int stride = ResourceManager.Reservations.OutputSizePerInvocation; + + Operand vertexCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(0)); + + Operand outputVertexOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(0)); + Operand outputInstanceOffset = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(1)); + + Operand outputBaseVertex = this.IMultiply(outputInstanceOffset, vertexCount); + + Operand baseOffset = this.IMultiply(this.IAdd(outputBaseVertex, outputVertexOffset), Const(stride)); + + for (int offset = 0; offset < stride; offset++) + { + Operand vertexOffset = this.IAdd(baseOffset, Const(offset)); + Operand value = this.Load(StorageKind.LocalMemory, ResourceManager.LocalVertexDataMemoryId, Const(offset)); + + this.Store(StorageKind.StorageBuffer, vertexOutputSbBinding, Const(0), vertexOffset, value); + } + } + else if (TranslatorContext.Stage == ShaderStage.Geometry) + { + Operand lblLoopHead = Label(); + Operand lblExit = Label(); + + this.MarkLabel(lblLoopHead); + + Operand writtenIndices = this.Load(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputIndexCountMemoryId); + + int maxIndicesPerPrimitiveInvocation = TranslatorContext.Definitions.GetGeometryOutputIndexBufferStridePerInstance(); + int maxIndicesPerPrimitive = maxIndicesPerPrimitiveInvocation * TranslatorContext.Definitions.ThreadsPerInputPrimitive; + + this.BranchIfTrue(lblExit, this.ICompareGreaterOrEqualUnsigned(writtenIndices, Const(maxIndicesPerPrimitiveInvocation))); + + int vertexInfoCbBinding = ResourceManager.Reservations.VertexInfoConstantBufferBinding; + + Operand primitiveIndex = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(0)); + Operand instanceIndex = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(1)); + Operand invocationId = this.Load(StorageKind.Input, IoVariable.GlobalId, Const(2)); + Operand vertexCount = this.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(0)); + Operand primitiveId = this.IAdd(this.IMultiply(instanceIndex, vertexCount), primitiveIndex); + Operand ibOffset = this.IMultiply(primitiveId, Const(maxIndicesPerPrimitive)); + ibOffset = this.IAdd(ibOffset, this.IMultiply(invocationId, Const(maxIndicesPerPrimitiveInvocation))); + ibOffset = this.IAdd(ibOffset, writtenIndices); + + this.Store(StorageKind.StorageBuffer, ResourceManager.Reservations.GeometryIndexOutputStorageBufferBinding, Const(0), ibOffset, Const(-1)); + this.Store(StorageKind.LocalMemory, ResourceManager.LocalGeometryOutputIndexCountMemoryId, this.IAdd(writtenIndices, Const(1))); + + this.Branch(lblLoopHead); + + this.MarkLabel(lblExit); + } + } + + return true; + } + + private void GenerateAlphaToCoverageDitherDiscard() + { + // If the feature is disabled, or alpha is not written, then we're done. + if (!TranslatorContext.Definitions.AlphaToCoverageDitherEnable || (TranslatorContext.Definitions.OmapTargets & 8) == 0) + { + return; + } + + // 11 11 11 10 10 10 10 00 + // 11 01 01 01 01 00 00 00 + Operand ditherMask = Const(unchecked((int)0xfbb99110u)); + + Operand fragCoordX = this.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(0)); + Operand fragCoordY = this.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(1)); + + Operand x = this.BitwiseAnd(this.FP32ConvertToU32(fragCoordX), Const(1)); + Operand y = this.BitwiseAnd(this.FP32ConvertToU32(fragCoordY), Const(1)); + Operand xy = this.BitwiseOr(x, this.ShiftLeft(y, Const(1))); + + Operand alpha = Register(3, RegisterType.Gpr); + Operand scaledAlpha = this.FPMultiply(this.FPSaturate(alpha), ConstF(8)); + Operand quantizedAlpha = this.IMinimumU32(this.FP32ConvertToU32(scaledAlpha), Const(7)); + Operand shift = this.BitwiseOr(this.ShiftLeft(quantizedAlpha, Const(2)), xy); + Operand opaque = this.BitwiseAnd(this.ShiftRightU32(ditherMask, shift), Const(1)); + + Operand a2cDitherEndLabel = Label(); + + this.BranchIfTrue(a2cDitherEndLabel, opaque); + this.Discard(); + this.MarkLabel(a2cDitherEndLabel); + } + + public Operation[] GetOperations() + { + return _operations.ToArray(); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs new file mode 100644 index 00000000..5bdbb002 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs @@ -0,0 +1,1045 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class EmitterContextInsts + { + public static Operand AtomicAdd(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicAdd, storageKind, Local(), a, b, c); + } + + public static Operand AtomicAnd(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicAnd, storageKind, Local(), a, b, c); + } + + public static Operand AtomicCompareAndSwap(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c, Operand d) + { + return context.Add(Instruction.AtomicCompareAndSwap, storageKind, Local(), a, b, c, d); + } + + public static Operand AtomicMaxS32(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicMaxS32, storageKind, Local(), a, b, c); + } + + public static Operand AtomicMaxU32(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicMaxU32, storageKind, Local(), a, b, c); + } + + public static Operand AtomicMinS32(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicMinS32, storageKind, Local(), a, b, c); + } + + public static Operand AtomicMinU32(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicMinU32, storageKind, Local(), a, b, c); + } + + public static Operand AtomicOr(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicOr, storageKind, Local(), a, b, c); + } + + public static Operand AtomicSwap(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicSwap, storageKind, Local(), a, b, c); + } + + public static Operand AtomicXor(this EmitterContext context, StorageKind storageKind, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.AtomicXor, storageKind, Local(), a, b, c); + } + + public static Operand AtomicAdd(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value) + { + return context.Add(Instruction.AtomicAdd, storageKind, Local(), Const(binding), e0, e1, value); + } + + public static Operand AtomicAnd(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value) + { + return context.Add(Instruction.AtomicAnd, storageKind, Local(), Const(binding), e0, e1, value); + } + + public static Operand AtomicCompareAndSwap(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand compare, Operand value) + { + return context.Add(Instruction.AtomicCompareAndSwap, storageKind, Local(), Const(binding), e0, compare, value); + } + + public static Operand AtomicCompareAndSwap(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand compare, Operand value) + { + return context.Add(Instruction.AtomicCompareAndSwap, storageKind, Local(), Const(binding), e0, e1, compare, value); + } + + public static Operand AtomicMaxS32(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value) + { + return context.Add(Instruction.AtomicMaxS32, storageKind, Local(), Const(binding), e0, e1, value); + } + + public static Operand AtomicMaxU32(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value) + { + return context.Add(Instruction.AtomicMaxU32, storageKind, Local(), Const(binding), e0, e1, value); + } + + public static Operand AtomicMinS32(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value) + { + return context.Add(Instruction.AtomicMinS32, storageKind, Local(), Const(binding), e0, e1, value); + } + + public static Operand AtomicMinU32(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value) + { + return context.Add(Instruction.AtomicMinU32, storageKind, Local(), Const(binding), e0, e1, value); + } + + public static Operand AtomicOr(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value) + { + return context.Add(Instruction.AtomicOr, storageKind, Local(), Const(binding), e0, e1, value); + } + + public static Operand AtomicSwap(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value) + { + return context.Add(Instruction.AtomicSwap, storageKind, Local(), Const(binding), e0, e1, value); + } + + public static Operand AtomicXor(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value) + { + return context.Add(Instruction.AtomicXor, storageKind, Local(), Const(binding), e0, e1, value); + } + + public static Operand Ballot(this EmitterContext context, Operand a, int index) + { + Operand dest = Local(); + + context.Add(new Operation(Instruction.Ballot, index, dest, a)); + + return dest; + } + + public static Operand Barrier(this EmitterContext context) + { + return context.Add(Instruction.Barrier); + } + + public static Operand BitCount(this EmitterContext context, Operand a) + { + return context.Add(Instruction.BitCount, Local(), a); + } + + public static Operand BitfieldExtractS32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.BitfieldExtractS32, Local(), a, b, c); + } + + public static Operand BitfieldExtractU32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.BitfieldExtractU32, Local(), a, b, c); + } + + public static Operand BitfieldInsert(this EmitterContext context, Operand a, Operand b, Operand c, Operand d) + { + return context.Add(Instruction.BitfieldInsert, Local(), a, b, c, d); + } + + public static Operand BitfieldReverse(this EmitterContext context, Operand a) + { + return context.Add(Instruction.BitfieldReverse, Local(), a); + } + + public static Operand BitwiseAnd(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.BitwiseAnd, Local(), a, b); + } + + public static Operand BitwiseExclusiveOr(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.BitwiseExclusiveOr, Local(), a, b); + } + + public static Operand BitwiseNot(this EmitterContext context, Operand a, bool invert) + { + if (invert) + { + a = context.BitwiseNot(a); + } + + return a; + } + + public static Operand BitwiseNot(this EmitterContext context, Operand a) + { + return context.Add(Instruction.BitwiseNot, Local(), a); + } + + public static Operand BitwiseOr(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.BitwiseOr, Local(), a, b); + } + + public static Operand Branch(this EmitterContext context, Operand d) + { + return context.Add(Instruction.Branch, d); + } + + public static Operand BranchIfFalse(this EmitterContext context, Operand d, Operand a) + { + return context.Add(Instruction.BranchIfFalse, d, a); + } + + public static Operand BranchIfTrue(this EmitterContext context, Operand d, Operand a) + { + return context.Add(Instruction.BranchIfTrue, d, a); + } + + public static Operand Call(this EmitterContext context, int funcId, bool returns, params Operand[] args) + { + Operand[] args2 = new Operand[args.Length + 1]; + + args2[0] = Const(funcId); + args.CopyTo(args2, 1); + + return context.Add(Instruction.Call, returns ? Local() : null, args2); + } + + public static Operand ConditionalSelect(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ConditionalSelect, Local(), a, b, c); + } + + public static Operand Copy(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Copy, Local(), a); + } + + public static void Copy(this EmitterContext context, Operand d, Operand a) + { + if (d.Type == OperandType.Constant) + { + return; + } + + context.Add(Instruction.Copy, d, a); + } + + public static Operand Discard(this EmitterContext context) + { + return context.Add(Instruction.Discard); + } + + public static Operand EmitVertex(this EmitterContext context) + { + return context.Add(Instruction.EmitVertex); + } + + public static Operand EndPrimitive(this EmitterContext context) + { + return context.Add(Instruction.EndPrimitive); + } + + public static Operand FindLSB(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FindLSB, Local(), a); + } + + public static Operand FindMSBS32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FindMSBS32, Local(), a); + } + + public static Operand FindMSBU32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FindMSBU32, Local(), a); + } + + public static Operand FP32ConvertToFP64(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertFP32ToFP64, Local(), a); + } + + public static Operand FP64ConvertToFP32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertFP64ToFP32, Local(), a); + } + + public static Operand FPAbsNeg(this EmitterContext context, Operand a, bool abs, bool neg, Instruction fpType = Instruction.FP32) + { + return context.FPNegate(context.FPAbsolute(a, abs, fpType), neg, fpType); + } + + public static Operand FPAbsolute(this EmitterContext context, Operand a, bool abs, Instruction fpType = Instruction.FP32) + { + if (abs) + { + a = context.FPAbsolute(a, fpType); + } + + return a; + } + + public static Operand FPAbsolute(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.Absolute, Local(), a); + } + + public static Operand FPAdd(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.Add, Local(), a, b); + } + + public static Operand FPCeiling(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.Ceiling, Local(), a); + } + + public static Operand FPCompareEqual(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.CompareEqual, Local(), a, b); + } + + public static Operand FPCompareLess(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.CompareLess, Local(), a, b); + } + + public static Operand FP32ConvertToS32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertFP32ToS32, Local(), a); + } + + public static Operand FP32ConvertToU32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertFP32ToU32, Local(), a); + } + + public static Operand FP64ConvertToS32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertFP64ToS32, Local(), a); + } + + public static Operand FP64ConvertToU32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertFP64ToU32, Local(), a); + } + + public static Operand FPCosine(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP32 | Instruction.Cosine, Local(), a); + } + + public static Operand FPDivide(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.Divide, Local(), a, b); + } + + public static Operand FPExponentB2(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP32 | Instruction.ExponentB2, Local(), a); + } + + public static Operand FPFloor(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.Floor, Local(), a); + } + + public static Operand FPFusedMultiplyAdd(this EmitterContext context, Operand a, Operand b, Operand c, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.FusedMultiplyAdd, Local(), a, b, c); + } + + public static Operand FPLogarithmB2(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP32 | Instruction.LogarithmB2, Local(), a); + } + + public static Operand FPMaximum(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.Maximum, Local(), a, b); + } + + public static Operand FPMinimum(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.Minimum, Local(), a, b); + } + + public static Operand FPModulo(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.Modulo, Local(), a, b); + } + + public static Operand FPMultiply(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.Multiply, Local(), a, b); + } + + public static Operand FPNegate(this EmitterContext context, Operand a, bool neg, Instruction fpType = Instruction.FP32) + { + if (neg) + { + a = context.FPNegate(a, fpType); + } + + return a; + } + + public static Operand FPNegate(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.Negate, Local(), a); + } + + public static Operand FPReciprocal(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32) + { + return context.FPDivide(fpType == Instruction.FP64 ? context.PackDouble2x32(1.0) : ConstF(1), a, fpType); + } + + public static Operand FPReciprocalSquareRoot(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.ReciprocalSquareRoot, Local(), a); + } + + public static Operand FPRound(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.Round, Local(), a); + } + + public static Operand FPSaturate(this EmitterContext context, Operand a, bool sat, Instruction fpType = Instruction.FP32) + { + if (sat) + { + a = context.FPSaturate(a, fpType); + } + + return a; + } + + public static Operand FPSaturate(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32) + { + return fpType == Instruction.FP64 + ? context.Add(fpType | Instruction.Clamp, Local(), a, context.PackDouble2x32(0.0), context.PackDouble2x32(1.0)) + : context.Add(fpType | Instruction.Clamp, Local(), a, ConstF(0), ConstF(1)); + } + + public static Operand FPSine(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP32 | Instruction.Sine, Local(), a); + } + + public static Operand FPSquareRoot(this EmitterContext context, Operand a) + { + return context.Add(Instruction.FP32 | Instruction.SquareRoot, Local(), a); + } + + public static Operand FPSubtract(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.Subtract, Local(), a, b); + } + + public static Operand FPTruncate(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.Truncate, Local(), a); + } + + public static Operand FPSwizzleAdd(this EmitterContext context, Operand a, Operand b, int mask) + { + return context.Add(Instruction.SwizzleAdd, Local(), a, b, Const(mask)); + } + + public static void FSIBegin(this EmitterContext context) + { + context.Add(Instruction.FSIBegin); + } + + public static void FSIEnd(this EmitterContext context) + { + context.Add(Instruction.FSIEnd); + } + + public static Operand GroupMemoryBarrier(this EmitterContext context) + { + return context.Add(Instruction.GroupMemoryBarrier); + } + + public static Operand IAbsNeg(this EmitterContext context, Operand a, bool abs, bool neg) + { + return context.INegate(context.IAbsolute(a, abs), neg); + } + + public static Operand IAbsolute(this EmitterContext context, Operand a, bool abs) + { + if (abs) + { + a = context.IAbsolute(a); + } + + return a; + } + + public static Operand IAbsolute(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Absolute, Local(), a); + } + + public static Operand IAdd(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Add, Local(), a, b); + } + + public static Operand IClampS32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.Clamp, Local(), a, b, c); + } + + public static Operand IClampU32(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ClampU32, Local(), a, b, c); + } + + public static Operand ICompareEqual(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareEqual, Local(), a, b); + } + + public static Operand ICompareGreater(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareGreater, Local(), a, b); + } + + public static Operand ICompareGreaterOrEqual(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareGreaterOrEqual, Local(), a, b); + } + + public static Operand ICompareGreaterOrEqualUnsigned(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareGreaterOrEqualU32, Local(), a, b); + } + + public static Operand ICompareGreaterUnsigned(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareGreaterU32, Local(), a, b); + } + + public static Operand ICompareLess(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareLess, Local(), a, b); + } + + public static Operand ICompareLessOrEqual(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareLessOrEqual, Local(), a, b); + } + + public static Operand ICompareLessOrEqualUnsigned(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareLessOrEqualU32, Local(), a, b); + } + + public static Operand ICompareLessUnsigned(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareLessU32, Local(), a, b); + } + + public static Operand ICompareNotEqual(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.CompareNotEqual, Local(), a, b); + } + + public static Operand IConvertS32ToFP32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertS32ToFP32, Local(), a); + } + + public static Operand IConvertS32ToFP64(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertS32ToFP64, Local(), a); + } + + public static Operand IConvertU32ToFP32(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertU32ToFP32, Local(), a); + } + + public static Operand IConvertU32ToFP64(this EmitterContext context, Operand a) + { + return context.Add(Instruction.ConvertU32ToFP64, Local(), a); + } + + public static Operand IMaximumS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Maximum, Local(), a, b); + } + + public static Operand IMaximumU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.MaximumU32, Local(), a, b); + } + + public static Operand IMinimumS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Minimum, Local(), a, b); + } + + public static Operand IMinimumU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.MinimumU32, Local(), a, b); + } + + public static Operand IMultiply(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Multiply, Local(), a, b); + } + + public static Operand INegate(this EmitterContext context, Operand a, bool neg) + { + if (neg) + { + a = context.INegate(a); + } + + return a; + } + + public static Operand INegate(this EmitterContext context, Operand a) + { + return context.Add(Instruction.Negate, Local(), a); + } + + public static Operand ISubtract(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Subtract, Local(), a, b); + } + + public static Operand ImageAtomic( + this EmitterContext context, + SamplerType type, + TextureFormat format, + TextureFlags flags, + SetBindingPair setAndBinding, + Operand[] sources) + { + Operand dest = Local(); + + context.Add(new TextureOperation( + Instruction.ImageAtomic, + type, + format, + flags, + setAndBinding.SetIndex, + setAndBinding.Binding, + 0, + new[] { dest }, + sources)); + + return dest; + } + + public static void ImageLoad( + this EmitterContext context, + SamplerType type, + TextureFormat format, + TextureFlags flags, + SetBindingPair setAndBinding, + int compMask, + Operand[] dests, + Operand[] sources) + { + context.Add(new TextureOperation( + Instruction.ImageLoad, + type, + format, + flags, + setAndBinding.SetIndex, + setAndBinding.Binding, + compMask, + dests, + sources)); + } + + public static void ImageStore( + this EmitterContext context, + SamplerType type, + TextureFormat format, + TextureFlags flags, + SetBindingPair setAndBinding, + Operand[] sources) + { + context.Add(new TextureOperation( + Instruction.ImageStore, + type, + format, + flags, + setAndBinding.SetIndex, + setAndBinding.Binding, + 0, + null, + sources)); + } + + public static Operand IsNan(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32) + { + return context.Add(fpType | Instruction.IsNan, Local(), a); + } + + public static Operand Load(this EmitterContext context, StorageKind storageKind, Operand e0, Operand e1) + { + return context.Add(Instruction.Load, storageKind, Local(), e0, e1); + } + + public static Operand Load(this EmitterContext context, StorageKind storageKind, int binding) + { + return context.Add(Instruction.Load, storageKind, Local(), Const(binding)); + } + + public static Operand Load(this EmitterContext context, StorageKind storageKind, int binding, Operand e0) + { + return context.Add(Instruction.Load, storageKind, Local(), Const(binding), e0); + } + + public static Operand Load(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1) + { + return context.Add(Instruction.Load, storageKind, Local(), Const(binding), e0, e1); + } + + public static Operand Load(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand e2) + { + return context.Add(Instruction.Load, storageKind, Local(), Const(binding), e0, e1, e2); + } + + public static Operand Load(this EmitterContext context, StorageKind storageKind, IoVariable ioVariable, Operand primVertex = null) + { + return primVertex != null + ? context.Load(storageKind, (int)ioVariable, primVertex) + : context.Load(storageKind, (int)ioVariable); + } + + public static Operand Load( + this EmitterContext context, + StorageKind storageKind, + IoVariable ioVariable, + Operand primVertex, + Operand elemIndex) + { + return primVertex != null + ? context.Load(storageKind, (int)ioVariable, primVertex, elemIndex) + : context.Load(storageKind, (int)ioVariable, elemIndex); + } + + public static Operand Load( + this EmitterContext context, + StorageKind storageKind, + IoVariable ioVariable, + Operand primVertex, + Operand arrayIndex, + Operand elemIndex) + { + return primVertex != null + ? context.Load(storageKind, (int)ioVariable, primVertex, arrayIndex, elemIndex) + : context.Load(storageKind, (int)ioVariable, arrayIndex, elemIndex); + } + + public static Operand Lod( + this EmitterContext context, + SamplerType type, + TextureFlags flags, + SetBindingPair setAndBinding, + int compIndex, + Operand[] sources) + { + Operand dest = Local(); + + context.Add(new TextureOperation( + Instruction.Lod, + type, + TextureFormat.Unknown, + flags, + setAndBinding.SetIndex, + setAndBinding.Binding, + compIndex, + new[] { dest }, + sources)); + + return dest; + } + + public static Operand MemoryBarrier(this EmitterContext context) + { + return context.Add(Instruction.MemoryBarrier); + } + + public static Operand MultiplyHighS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.MultiplyHighS32, Local(), a, b); + } + + public static Operand MultiplyHighU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.MultiplyHighU32, Local(), a, b); + } + + public static Operand PackDouble2x32(this EmitterContext context, double value) + { + long valueAsLong = BitConverter.DoubleToInt64Bits(value); + + return context.Add(Instruction.PackDouble2x32, Local(), Const((int)valueAsLong), Const((int)(valueAsLong >> 32))); + } + + public static Operand PackDouble2x32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.PackDouble2x32, Local(), a, b); + } + + public static Operand PackHalf2x16(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.PackHalf2x16, Local(), a, b); + } + + public static void Return(this EmitterContext context) + { + context.Add(Instruction.Return); + } + + public static void Return(this EmitterContext context, Operand returnValue) + { + context.Add(Instruction.Return, null, returnValue); + } + + public static Operand ShiftLeft(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShiftLeft, Local(), a, b); + } + + public static Operand ShiftRightS32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShiftRightS32, Local(), a, b); + } + + public static Operand ShiftRightU32(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShiftRightU32, Local(), a, b); + } + + public static Operand Shuffle(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.Shuffle, Local(), a, b); + } + + public static (Operand, Operand) Shuffle(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.Shuffle, (Local(), Local()), a, b, c); + } + + public static Operand ShuffleDown(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShuffleDown, Local(), a, b); + } + + public static (Operand, Operand) ShuffleDown(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ShuffleDown, (Local(), Local()), a, b, c); + } + + public static Operand ShuffleUp(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShuffleUp, Local(), a, b); + } + + public static (Operand, Operand) ShuffleUp(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ShuffleUp, (Local(), Local()), a, b, c); + } + + public static Operand ShuffleXor(this EmitterContext context, Operand a, Operand b) + { + return context.Add(Instruction.ShuffleXor, Local(), a, b); + } + + public static (Operand, Operand) ShuffleXor(this EmitterContext context, Operand a, Operand b, Operand c) + { + return context.Add(Instruction.ShuffleXor, (Local(), Local()), a, b, c); + } + + public static Operand Store(this EmitterContext context, StorageKind storageKind, Operand e0, Operand e1, Operand value) + { + return context.Add(Instruction.Store, storageKind, null, e0, e1, value); + } + + public static Operand Store(this EmitterContext context, StorageKind storageKind, int binding, Operand value) + { + return context.Add(Instruction.Store, storageKind, null, Const(binding), value); + } + + public static Operand Store(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand value) + { + return context.Add(Instruction.Store, storageKind, null, Const(binding), e0, value); + } + + public static Operand Store(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value) + { + return context.Add(Instruction.Store, storageKind, null, Const(binding), e0, e1, value); + } + + public static Operand Store( + this EmitterContext context, + StorageKind storageKind, + IoVariable ioVariable, + Operand invocationId, + Operand value) + { + return invocationId != null + ? context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), invocationId, value) + : context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), value); + } + + public static Operand Store( + this EmitterContext context, + StorageKind storageKind, + IoVariable ioVariable, + Operand invocationId, + Operand elemIndex, + Operand value) + { + return invocationId != null + ? context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), invocationId, elemIndex, value) + : context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), elemIndex, value); + } + + public static Operand Store( + this EmitterContext context, + StorageKind storageKind, + IoVariable ioVariable, + Operand invocationId, + Operand arrayIndex, + Operand elemIndex, + Operand value) + { + return invocationId != null + ? context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), invocationId, arrayIndex, elemIndex, value) + : context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), arrayIndex, elemIndex, value); + } + + public static void TextureSample( + this EmitterContext context, + SamplerType type, + TextureFlags flags, + SetBindingPair setAndBinding, + int compMask, + Operand[] dests, + Operand[] sources) + { + context.Add(new TextureOperation( + Instruction.TextureSample, + type, + TextureFormat.Unknown, + flags, + setAndBinding.SetIndex, + setAndBinding.Binding, + compMask, + dests, + sources)); + } + + public static Operand TextureQuerySamples( + this EmitterContext context, + SamplerType type, + TextureFlags flags, + SetBindingPair setAndBinding, + Operand[] sources) + { + Operand dest = Local(); + + context.Add(new TextureOperation( + Instruction.TextureQuerySamples, + type, + TextureFormat.Unknown, + flags, + setAndBinding.SetIndex, + setAndBinding.Binding, + 0, + new[] { dest }, + sources)); + + return dest; + } + + public static Operand TextureQuerySize( + this EmitterContext context, + SamplerType type, + TextureFlags flags, + SetBindingPair setAndBinding, + int compIndex, + Operand[] sources) + { + Operand dest = Local(); + + context.Add(new TextureOperation( + Instruction.TextureQuerySize, + type, + TextureFormat.Unknown, + flags, + setAndBinding.SetIndex, + setAndBinding.Binding, + compIndex, + new[] { dest }, + sources)); + + return dest; + } + + public static Operand UnpackDouble2x32High(this EmitterContext context, Operand a) + { + return UnpackDouble2x32(context, a, 1); + } + + public static Operand UnpackDouble2x32Low(this EmitterContext context, Operand a) + { + return UnpackDouble2x32(context, a, 0); + } + + private static Operand UnpackDouble2x32(this EmitterContext context, Operand a, int index) + { + Operand dest = Local(); + + context.Add(new Operation(Instruction.UnpackDouble2x32, index, dest, a)); + + return dest; + } + + public static Operand UnpackHalf2x16High(this EmitterContext context, Operand a) + { + return UnpackHalf2x16(context, a, 1); + } + + public static Operand UnpackHalf2x16Low(this EmitterContext context, Operand a) + { + return UnpackHalf2x16(context, a, 0); + } + + private static Operand UnpackHalf2x16(this EmitterContext context, Operand a, int index) + { + Operand dest = Local(); + + context.Add(new Operation(Instruction.UnpackHalf2x16, index, dest, a)); + + return dest; + } + + public static Operand VoteAll(this EmitterContext context, Operand a) + { + return context.Add(Instruction.VoteAll, Local(), a); + } + + public static Operand VoteAllEqual(this EmitterContext context, Operand a) + { + return context.Add(Instruction.VoteAllEqual, Local(), a); + } + + public static Operand VoteAny(this EmitterContext context, Operand a) + { + return context.Add(Instruction.VoteAny, Local(), a); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs b/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs new file mode 100644 index 00000000..82a54db8 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs @@ -0,0 +1,30 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Translation +{ + /// + /// Features used by the shader that are important for the code generator to know in advance. + /// These typically change the declarations in the shader header. + /// + [Flags] + public enum FeatureFlags + { + None = 0, + + // Affected by resolution scaling. + FragCoordXY = 1 << 1, + + Bindless = 1 << 2, + InstanceId = 1 << 3, + DrawParameters = 1 << 4, + RtLayer = 1 << 5, + Shuffle = 1 << 6, + ViewportIndex = 1 << 7, + ViewportMask = 1 << 8, + FixedFuncAttr = 1 << 9, + LocalMemory = 1 << 10, + SharedMemory = 1 << 11, + Store = 1 << 12, + VtgAsCompute = 1 << 13, + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/FunctionMatch.cs b/src/Ryujinx.Graphics.Shader/Translation/FunctionMatch.cs new file mode 100644 index 00000000..714a9d68 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/FunctionMatch.cs @@ -0,0 +1,865 @@ +using Ryujinx.Graphics.Shader.Decoders; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class FunctionMatch + { + private static readonly IPatternTreeNode[] _fsiGetAddressTree = PatternTrees.GetFsiGetAddress(); + private static readonly IPatternTreeNode[] _fsiGetAddressV2Tree = PatternTrees.GetFsiGetAddressV2(); + private static readonly IPatternTreeNode[] _fsiIsLastWarpThreadPatternTree = PatternTrees.GetFsiIsLastWarpThread(); + private static readonly IPatternTreeNode[] _fsiBeginPatternTree = PatternTrees.GetFsiBeginPattern(); + private static readonly IPatternTreeNode[] _fsiEndPatternTree = PatternTrees.GetFsiEndPattern(); + + public static void RunPass(DecodedProgram program) + { + byte[] externalRegs = new byte[4]; + bool hasGetAddress = false; + + foreach (DecodedFunction function in program) + { + if (function == program.MainFunction) + { + continue; + } + + int externalReg4 = 0; + + TreeNode[] functionTree = BuildTree(function.Blocks); + + if (Matches(_fsiGetAddressTree, functionTree)) + { + externalRegs[1] = functionTree[0].GetRd(); + externalRegs[2] = functionTree[2].GetRd(); + externalRegs[3] = functionTree[1].GetRd(); + externalReg4 = functionTree[3].GetRd(); + } + else if (Matches(_fsiGetAddressV2Tree, functionTree)) + { + externalRegs[1] = functionTree[2].GetRd(); + externalRegs[2] = functionTree[1].GetRd(); + externalRegs[3] = functionTree[0].GetRd(); + externalReg4 = functionTree[3].GetRd(); + } + + // Ensure the register allocation is valid. + // If so, then we have a match. + if (externalRegs[1] != externalRegs[2] && + externalRegs[2] != externalRegs[3] && + externalRegs[1] != externalRegs[3] && + externalRegs[1] + 1 != externalRegs[2] && + externalRegs[1] + 1 != externalRegs[3] && + externalRegs[1] + 1 == externalReg4 && + externalRegs[2] != RegisterConsts.RegisterZeroIndex && + externalRegs[3] != RegisterConsts.RegisterZeroIndex && + externalReg4 != RegisterConsts.RegisterZeroIndex) + { + hasGetAddress = true; + function.Type = FunctionType.Unused; + break; + } + } + + foreach (DecodedFunction function in program) + { + if (function.IsCompilerGenerated || function == program.MainFunction) + { + continue; + } + + if (hasGetAddress) + { + TreeNode[] functionTree = BuildTree(function.Blocks); + + if (MatchesFsi(_fsiBeginPatternTree, program, function, functionTree, externalRegs)) + { + function.Type = FunctionType.BuiltInFSIBegin; + continue; + } + else if (MatchesFsi(_fsiEndPatternTree, program, function, functionTree, externalRegs)) + { + function.Type = FunctionType.BuiltInFSIEnd; + continue; + } + } + } + } + + private readonly struct TreeNodeUse + { + public TreeNode Node { get; } + public int Index { get; } + public bool Inverted { get; } + + private TreeNodeUse(int index, bool inverted, TreeNode node) + { + Index = index; + Inverted = inverted; + Node = node; + } + + public TreeNodeUse(int index, TreeNode node) : this(index, false, node) + { + } + + public TreeNodeUse Flip() + { + return new TreeNodeUse(Index, !Inverted, Node); + } + } + + private enum TreeNodeType : byte + { + Op, + Label, + } + + private class TreeNode + { + public readonly InstOp Op; + public readonly List Uses; + public TreeNodeType Type { get; } + public byte Order { get; } + + public TreeNode(byte order) + { + Type = TreeNodeType.Label; + Order = order; + } + + public TreeNode(InstOp op, byte order) + { + Op = op; + Uses = new List(); + Type = TreeNodeType.Op; + Order = order; + } + + public byte GetPd() + { + return (byte)((Op.RawOpCode >> 3) & 7); + } + + public byte GetRd() + { + return (byte)Op.RawOpCode; + } + } + + private static TreeNode[] BuildTree(Block[] blocks) + { + List nodes = new(); + + Dictionary labels = new(); + + TreeNodeUse[] predDefs = new TreeNodeUse[RegisterConsts.PredsCount]; + TreeNodeUse[] gprDefs = new TreeNodeUse[RegisterConsts.GprsCount]; + + void DefPred(byte predIndex, int index, TreeNode node) + { + if (predIndex != RegisterConsts.PredicateTrueIndex) + { + predDefs[predIndex] = new TreeNodeUse(index, node); + } + } + + void DefGpr(byte regIndex, int index, TreeNode node) + { + if (regIndex != RegisterConsts.RegisterZeroIndex) + { + gprDefs[regIndex] = new TreeNodeUse(index, node); + } + } + + TreeNodeUse UsePred(byte predIndex, bool predInv) + { + if (predIndex != RegisterConsts.PredicateTrueIndex) + { + TreeNodeUse use = predDefs[predIndex]; + + if (use.Node != null) + { + nodes.Remove(use.Node); + } + else + { + use = new TreeNodeUse(-(predIndex + 2), null); + } + + return predInv ? use.Flip() : use; + } + + return new TreeNodeUse(-1, null); + } + + TreeNodeUse UseGpr(byte regIndex) + { + if (regIndex != RegisterConsts.RegisterZeroIndex) + { + TreeNodeUse use = gprDefs[regIndex]; + + if (use.Node != null) + { + nodes.Remove(use.Node); + } + else + { + use = new TreeNodeUse(-(regIndex + 2), null); + } + + return use; + } + + return new TreeNodeUse(-1, null); + } + + byte order = 0; + + for (int index = 0; index < blocks.Length; index++) + { + Block block = blocks[index]; + + if (block.Predecessors.Count > 1) + { + TreeNode label = new(order++); + nodes.Add(label); + labels.Add(block.Address, label); + } + + for (int opIndex = 0; opIndex < block.OpCodes.Count; opIndex++) + { + InstOp op = block.OpCodes[opIndex]; + + TreeNode node = new(op, IsOrderDependant(op.Name) ? order : (byte)0); + + // Add uses. + + if (!op.Props.HasFlag(InstProps.NoPred)) + { + byte predIndex = (byte)((op.RawOpCode >> 16) & 7); + bool predInv = (op.RawOpCode & 0x80000) != 0; + node.Uses.Add(UsePred(predIndex, predInv)); + } + + if (op.Props.HasFlag(InstProps.Ps)) + { + byte predIndex = (byte)((op.RawOpCode >> 39) & 7); + bool predInv = (op.RawOpCode & 0x40000000000) != 0; + node.Uses.Add(UsePred(predIndex, predInv)); + } + + if (op.Props.HasFlag(InstProps.Ra)) + { + byte ra = (byte)(op.RawOpCode >> 8); + node.Uses.Add(UseGpr(ra)); + } + + if ((op.Props & (InstProps.Rb | InstProps.Rb2)) != 0) + { + byte rb = op.Props.HasFlag(InstProps.Rb2) ? (byte)op.RawOpCode : (byte)(op.RawOpCode >> 20); + node.Uses.Add(UseGpr(rb)); + } + + if (op.Props.HasFlag(InstProps.Rc)) + { + byte rc = (byte)(op.RawOpCode >> 39); + node.Uses.Add(UseGpr(rc)); + } + + if (op.Name == InstName.Bra && labels.TryGetValue(op.GetAbsoluteAddress(), out TreeNode label)) + { + node.Uses.Add(new TreeNodeUse(0, label)); + } + + // Make definitions. + + int defIndex = 0; + + InstProps pdType = op.Props & InstProps.PdMask; + + if (pdType != 0) + { + int bit = pdType switch + { + InstProps.Pd => 3, + InstProps.LPd => 48, + InstProps.SPd => 30, + InstProps.TPd => 51, + InstProps.VPd => 45, + _ => throw new InvalidOperationException($"Table has unknown predicate destination {pdType}."), + }; + + byte predIndex = (byte)((op.RawOpCode >> bit) & 7); + DefPred(predIndex, defIndex++, node); + } + + if (op.Props.HasFlag(InstProps.Rd)) + { + byte rd = (byte)op.RawOpCode; + DefGpr(rd, defIndex++, node); + } + + nodes.Add(node); + } + } + + return nodes.ToArray(); + } + + private static bool IsOrderDependant(InstName name) + { + switch (name) + { + case InstName.Atom: + case InstName.AtomCas: + case InstName.Atoms: + case InstName.AtomsCas: + case InstName.Ld: + case InstName.Ldg: + case InstName.Ldl: + case InstName.Lds: + case InstName.Suatom: + case InstName.SuatomB: + case InstName.SuatomB2: + case InstName.SuatomCas: + case InstName.SuatomCasB: + case InstName.Suld: + case InstName.SuldB: + case InstName.SuldD: + case InstName.SuldDB: + return true; + } + + return false; + } + + private interface IPatternTreeNode + { + List Uses { get; } + InstName Name { get; } + TreeNodeType Type { get; } + byte Order { get; } + bool IsImm { get; } + bool Matches(in InstOp opInfo); + } + + private readonly struct PatternTreeNodeUse + { + public IPatternTreeNode Node { get; } + public int Index { get; } + public bool Inverted { get; } + public PatternTreeNodeUse Inv => new(Index, !Inverted, Node); + + private PatternTreeNodeUse(int index, bool inverted, IPatternTreeNode node) + { + Index = index; + Inverted = inverted; + Node = node; + } + + public PatternTreeNodeUse(int index, IPatternTreeNode node) : this(index, false, node) + { + } + } + + private class PatternTreeNode : IPatternTreeNode + { + public List Uses { get; } + private readonly Func _match; + + public InstName Name { get; } + public TreeNodeType Type { get; } + public byte Order { get; } + public bool IsImm { get; } + public PatternTreeNodeUse Out => new(0, this); + + public PatternTreeNode(InstName name, Func match, TreeNodeType type = TreeNodeType.Op, byte order = 0, bool isImm = false) + { + Name = name; + _match = match; + Type = type; + Order = order; + IsImm = isImm; + Uses = new List(); + } + + public PatternTreeNode Use(PatternTreeNodeUse use) + { + Uses.Add(use); + return this; + } + + public PatternTreeNodeUse OutAt(int index) + { + return new PatternTreeNodeUse(index, this); + } + + public bool Matches(in InstOp opInfo) + { + if (opInfo.Name != Name) + { + return false; + } + + ulong rawOp = opInfo.RawOpCode; + T op = Unsafe.As(ref rawOp); + + if (!_match(op)) + { + return false; + } + + return true; + } + } + + private static bool MatchesFsi( + IPatternTreeNode[] pattern, + DecodedProgram program, + DecodedFunction function, + TreeNode[] functionTree, + byte[] externalRegs) + { + if (function.Blocks.Length == 0) + { + return false; + } + + InstOp callOp = function.Blocks[0].GetLastOp(); + + if (callOp.Name != InstName.Cal) + { + return false; + } + + DecodedFunction callTarget = program.GetFunctionByAddress(callOp.GetAbsoluteAddress()); + TreeNode[] callTargetTree; + + if (callTarget == null || !Matches(_fsiIsLastWarpThreadPatternTree, callTargetTree = BuildTree(callTarget.Blocks))) + { + return false; + } + + externalRegs[0] = callTargetTree[0].GetPd(); + + if (Matches(pattern, functionTree, externalRegs)) + { + callTarget.RemoveCaller(function); + return true; + } + + return false; + } + + private static bool Matches(IPatternTreeNode[] pTree, TreeNode[] cTree, byte[] externalRegs = null) + { + if (pTree.Length != cTree.Length) + { + return false; + } + + for (int index = 0; index < pTree.Length; index++) + { + if (!Matches(pTree[index], cTree[index], externalRegs)) + { + return false; + } + } + + return true; + } + + private static bool Matches(IPatternTreeNode pTreeNode, TreeNode cTreeNode, byte[] externalRegs) + { + if (!pTreeNode.Matches(in cTreeNode.Op) || + pTreeNode.Type != cTreeNode.Type || + pTreeNode.Order != cTreeNode.Order || + pTreeNode.IsImm != cTreeNode.Op.Props.HasFlag(InstProps.Ib)) + { + return false; + } + + if (pTreeNode.Type == TreeNodeType.Op) + { + if (pTreeNode.Uses.Count != cTreeNode.Uses.Count) + { + return false; + } + + for (int index = 0; index < pTreeNode.Uses.Count; index++) + { + var pUse = pTreeNode.Uses[index]; + var cUse = cTreeNode.Uses[index]; + + if (pUse.Index <= -2) + { + if (externalRegs[-pUse.Index - 2] != (-cUse.Index - 2)) + { + return false; + } + } + else if (pUse.Index != cUse.Index) + { + return false; + } + + if (pUse.Inverted != cUse.Inverted || (pUse.Node == null) != (cUse.Node == null)) + { + return false; + } + + if (pUse.Node != null && !Matches(pUse.Node, cUse.Node, externalRegs)) + { + return false; + } + } + } + + return true; + } + + private static class PatternTrees + { + public static IPatternTreeNode[] GetFsiGetAddress() + { + var affinityValue = S2r(SReg.Affinity).Use(PT).Out; + var orderingTicketValue = S2r(SReg.OrderingTicket).Use(PT).Out; + + return new IPatternTreeNode[] + { + Iscadd(cc: true, 2, 0, 404) + .Use(PT) + .Use(Iscadd(cc: false, 8) + .Use(PT) + .Use(Lop32i(LogicOp.And, 0xff) + .Use(PT) + .Use(affinityValue).Out) + .Use(Lop32i(LogicOp.And, 0xff) + .Use(PT) + .Use(orderingTicketValue).Out).Out), + ShrU32W(16) + .Use(PT) + .Use(orderingTicketValue), + Iadd32i(0x200) + .Use(PT) + .Use(Lop32i(LogicOp.And, 0xfe00) + .Use(PT) + .Use(orderingTicketValue).Out), + Iadd(x: true, 0, 405).Use(PT).Use(RZ), + Ret().Use(PT), + }; + } + + public static IPatternTreeNode[] GetFsiGetAddressV2() + { + var affinityValue = S2r(SReg.Affinity).Use(PT).Out; + var orderingTicketValue = S2r(SReg.OrderingTicket).Use(PT).Out; + + return new IPatternTreeNode[] + { + ShrU32W(16) + .Use(PT) + .Use(orderingTicketValue), + Iadd32i(0x200) + .Use(PT) + .Use(Lop32i(LogicOp.And, 0xfe00) + .Use(PT) + .Use(orderingTicketValue).Out), + Iscadd(cc: true, 2, 0, 404) + .Use(PT) + .Use(Bfi(0x808) + .Use(PT) + .Use(affinityValue) + .Use(Lop32i(LogicOp.And, 0xff) + .Use(PT) + .Use(orderingTicketValue).Out).Out), + Iadd(x: true, 0, 405).Use(PT).Use(RZ), + Ret().Use(PT), + }; + } + + public static IPatternTreeNode[] GetFsiIsLastWarpThread() + { + var threadKillValue = S2r(SReg.ThreadKill).Use(PT).Out; + var laneIdValue = S2r(SReg.LaneId).Use(PT).Out; + + return new IPatternTreeNode[] + { + IsetpU32(IComp.Eq) + .Use(PT) + .Use(PT) + .Use(FloU32() + .Use(PT) + .Use(Vote(VoteMode.Any) + .Use(PT) + .Use(IsetpU32(IComp.Ne) + .Use(PT) + .Use(PT) + .Use(Lop(negB: true, LogicOp.PassB) + .Use(PT) + .Use(RZ) + .Use(threadKillValue).OutAt(1)) + .Use(RZ).Out).OutAt(1)).Out) + .Use(laneIdValue), + Ret().Use(PT), + }; + } + + public static IPatternTreeNode[] GetFsiBeginPattern() + { + var addressLowValue = CallArg(1); + + static PatternTreeNodeUse HighU16Equals(PatternTreeNodeUse x) + { + var expectedValue = CallArg(3); + + return IsetpU32(IComp.Eq) + .Use(PT) + .Use(PT) + .Use(ShrU32W(16).Use(PT).Use(x).Out) + .Use(expectedValue).Out; + } + + PatternTreeNode label; + + return new IPatternTreeNode[] + { + Cal(), + Ret().Use(CallArg(0).Inv), + Ret() + .Use(HighU16Equals(LdgE(CacheOpLd.Cg, LsSize.B32) + .Use(PT) + .Use(addressLowValue).Out)), + label = Label(), + Bra() + .Use(HighU16Equals(LdgE(CacheOpLd.Cg, LsSize.B32, 1) + .Use(PT) + .Use(addressLowValue).Out).Inv) + .Use(label.Out), + Ret().Use(PT), + }; + } + + public static IPatternTreeNode[] GetFsiEndPattern() + { + var voteResult = Vote(VoteMode.All).Use(PT).Use(PT).OutAt(1); + var popcResult = Popc().Use(PT).Use(voteResult).Out; + var threadKillValue = S2r(SReg.ThreadKill).Use(PT).Out; + var laneIdValue = S2r(SReg.LaneId).Use(PT).Out; + + var addressLowValue = CallArg(1); + var incrementValue = CallArg(2); + + return new IPatternTreeNode[] + { + Cal(), + Ret().Use(CallArg(0).Inv), + Membar(Decoders.Membar.Vc).Use(PT), + Ret().Use(IsetpU32(IComp.Ne) + .Use(PT) + .Use(PT) + .Use(threadKillValue) + .Use(RZ).Out), + RedE(RedOp.Add, AtomSize.U32) + .Use(IsetpU32(IComp.Eq) + .Use(PT) + .Use(PT) + .Use(FloU32() + .Use(PT) + .Use(voteResult).Out) + .Use(laneIdValue).Out) + .Use(addressLowValue) + .Use(Xmad(XmadCop.Cbcc, psl: true, hiloA: true, hiloB: true) + .Use(PT) + .Use(incrementValue) + .Use(Xmad(XmadCop.Cfull, mrg: true, hiloB: true) + .Use(PT) + .Use(incrementValue) + .Use(popcResult) + .Use(RZ).Out) + .Use(Xmad(XmadCop.Cfull) + .Use(PT) + .Use(incrementValue) + .Use(popcResult) + .Use(RZ).Out).Out), + Ret().Use(PT), + }; + } + + private static PatternTreeNode Bfi(int imm) + { + return new(InstName.Bfi, (op) => !op.WriteCC && op.Imm20 == imm, isImm: true); + } + + private static PatternTreeNode Bra() + { + return new(InstName.Bra, (op) => op.Ccc == Ccc.T && !op.Ca); + } + + private static PatternTreeNode Cal() + { + return new(InstName.Cal, (op) => !op.Ca && op.Inc); + } + + private static PatternTreeNode FloU32() + { + return new(InstName.Flo, (op) => !op.Signed && !op.Sh && !op.NegB && !op.WriteCC); + } + + private static PatternTreeNode Iadd(bool x, int cbufSlot, int cbufOffset) + { + return new(InstName.Iadd, (op) => + !op.Sat && + !op.WriteCC && + op.X == x && + op.AvgMode == AvgMode.NoNeg && + op.CbufSlot == cbufSlot && + op.CbufOffset == cbufOffset); + } + + private static PatternTreeNode Iadd32i(int imm) + { + return new(InstName.Iadd32i, (op) => !op.Sat && !op.WriteCC && !op.X && op.AvgMode == AvgMode.NoNeg && op.Imm32 == imm); + } + + private static PatternTreeNode Iscadd(bool cc, int imm) + { + return new(InstName.Iscadd, (op) => op.WriteCC == cc && op.AvgMode == AvgMode.NoNeg && op.Imm5 == imm); + } + + private static PatternTreeNode Iscadd(bool cc, int imm, int cbufSlot, int cbufOffset) + { + return new(InstName.Iscadd, (op) => + op.WriteCC == cc && + op.AvgMode == AvgMode.NoNeg && + op.Imm5 == imm && + op.CbufSlot == cbufSlot && + op.CbufOffset == cbufOffset); + } + + private static PatternTreeNode IsetpU32(IComp comp) + { + return new(InstName.Isetp, (op) => !op.Signed && op.IComp == comp && op.Bop == BoolOp.And); + } + + private static PatternTreeNode Label() + { + return new(InstName.Invalid, (op) => true, type: TreeNodeType.Label); + } + + private static PatternTreeNode Lop(bool negB, LogicOp logicOp) + { + return new(InstName.Lop, (op) => !op.NegA && op.NegB == negB && !op.WriteCC && !op.X && op.Lop == logicOp && op.PredicateOp == PredicateOp.F); + } + + private static PatternTreeNode Lop32i(LogicOp logicOp, int imm) + { + return new(InstName.Lop32i, (op) => !op.NegA && !op.NegB && !op.X && !op.WriteCC && op.LogicOp == logicOp && op.Imm32 == imm); + } + + private static PatternTreeNode Membar(Membar membar) + { + return new(InstName.Membar, (op) => op.Membar == membar); + } + + private static PatternTreeNode Popc() + { + return new(InstName.Popc, (op) => !op.NegB); + } + + private static PatternTreeNode Ret() + { + return new(InstName.Ret, (op) => op.Ccc == Ccc.T); + } + + private static PatternTreeNode S2r(SReg reg) + { + return new(InstName.S2r, (op) => op.SReg == reg); + } + + private static PatternTreeNode ShrU32W(int imm) + { + return new(InstName.Shr, (op) => !op.Signed && !op.Brev && op.M && op.XMode == 0 && op.Imm20 == imm, isImm: true); + } + + private static PatternTreeNode LdgE(CacheOpLd cacheOp, LsSize size, byte order = 0) + { + return new(InstName.Ldg, (op) => op.E && op.CacheOp == cacheOp && op.LsSize == size, order: order); + } + + private static PatternTreeNode RedE(RedOp redOp, AtomSize size, byte order = 0) + { + return new(InstName.Red, (op) => op.E && op.RedOp == redOp && op.RedSize == size, order: order); + } + + private static PatternTreeNode Vote(VoteMode mode) + { + return new(InstName.Vote, (op) => op.VoteMode == mode); + } + + private static PatternTreeNode Xmad(XmadCop cop, bool psl = false, bool mrg = false, bool hiloA = false, bool hiloB = false) + { + return new(InstName.Xmad, (op) => op.XmadCop == cop && op.Psl == psl && op.Mrg == mrg && op.HiloA == hiloA && op.HiloB == hiloB); + } + + private static PatternTreeNodeUse PT => PTOrRZ(); + private static PatternTreeNodeUse RZ => PTOrRZ(); + + private static PatternTreeNodeUse CallArg(int index) + { + return new PatternTreeNodeUse(-(index + 2), null); + } + + private static PatternTreeNodeUse PTOrRZ() + { + return new PatternTreeNodeUse(-1, null); + } + } + + private static void PrintTreeNode(TreeNode node, string indentation) + { + Console.WriteLine($" {node.Op.Name}"); + + for (int i = 0; i < node.Uses.Count; i++) + { + TreeNodeUse use = node.Uses[i]; + bool last = i == node.Uses.Count - 1; + char separator = last ? '`' : '|'; + + if (use.Node != null) + { + Console.Write($"{indentation} {separator}- ({(use.Inverted ? "INV " : "")}{use.Index})"); + PrintTreeNode(use.Node, indentation + (last ? " " : " | ")); + } + else + { + Console.WriteLine($"{indentation} {separator}- ({(use.Inverted ? "INV " : "")}{use.Index}) NULL"); + } + } + } + + private static void PrintTreeNode(IPatternTreeNode node, string indentation) + { + Console.WriteLine($" {node.Name}"); + + for (int i = 0; i < node.Uses.Count; i++) + { + PatternTreeNodeUse use = node.Uses[i]; + bool last = i == node.Uses.Count - 1; + char separator = last ? '`' : '|'; + + if (use.Node != null) + { + Console.Write($"{indentation} {separator}- ({(use.Inverted ? "INV " : "")}{use.Index})"); + PrintTreeNode(use.Node, indentation + (last ? " " : " | ")); + } + else + { + Console.WriteLine($"{indentation} {separator}- ({(use.Inverted ? "INV " : "")}{use.Index}) NULL"); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs new file mode 100644 index 00000000..ef2f8759 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs @@ -0,0 +1,475 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + class HelperFunctionManager + { + private readonly List _functionList; + private readonly Dictionary _functionIds; + private readonly ShaderStage _stage; + + public HelperFunctionManager(List functionList, ShaderStage stage) + { + _functionList = functionList; + _functionIds = new Dictionary(); + _stage = stage; + } + + public int AddFunction(Function function) + { + int functionId = _functionList.Count; + _functionList.Add(function); + + return functionId; + } + + public int GetOrCreateFunctionId(HelperFunctionName functionName) + { + if (_functionIds.TryGetValue((int)functionName, out int functionId)) + { + return functionId; + } + + Function function = GenerateFunction(functionName); + functionId = AddFunction(function); + _functionIds.Add((int)functionName, functionId); + + return functionId; + } + + public int GetOrCreateFunctionId(HelperFunctionName functionName, int id) + { + int key = (int)functionName | (id << 16); + + if (_functionIds.TryGetValue(key, out int functionId)) + { + return functionId; + } + + Function function = GenerateFunction(functionName, id); + functionId = AddFunction(function); + _functionIds.Add(key, functionId); + + return functionId; + } + + public int GetOrCreateShuffleFunctionId(HelperFunctionName functionName, int subgroupSize) + { + if (_functionIds.TryGetValue((int)functionName, out int functionId)) + { + return functionId; + } + + Function function = GenerateShuffleFunction(functionName, subgroupSize); + functionId = AddFunction(function); + _functionIds.Add((int)functionName, functionId); + + return functionId; + } + + private Function GenerateFunction(HelperFunctionName functionName) + { + return functionName switch + { + HelperFunctionName.ConvertDoubleToFloat => GenerateConvertDoubleToFloatFunction(), + HelperFunctionName.ConvertFloatToDouble => GenerateConvertFloatToDoubleFunction(), + HelperFunctionName.TexelFetchScale => GenerateTexelFetchScaleFunction(), + HelperFunctionName.TextureSizeUnscale => GenerateTextureSizeUnscaleFunction(), + _ => throw new ArgumentException($"Invalid function name {functionName}"), + }; + } + + private static Function GenerateConvertDoubleToFloatFunction() + { + EmitterContext context = new(); + + Operand valueLow = Argument(0); + Operand valueHigh = Argument(1); + + Operand mantissaLow = context.BitwiseAnd(valueLow, Const(((1 << 22) - 1))); + Operand mantissa = context.ShiftRightU32(valueLow, Const(22)); + + mantissa = context.BitwiseOr(mantissa, context.ShiftLeft(context.BitwiseAnd(valueHigh, Const(0xfffff)), Const(10))); + mantissa = context.BitwiseOr(mantissa, context.ConditionalSelect(mantissaLow, Const(1), Const(0))); + + Operand exp = context.BitwiseAnd(context.ShiftRightU32(valueHigh, Const(20)), Const(0x7ff)); + Operand sign = context.ShiftRightS32(valueHigh, Const(31)); + + Operand resultSign = context.ShiftLeft(sign, Const(31)); + + Operand notZero = context.BitwiseOr(mantissa, exp); + + Operand lblNotZero = Label(); + + context.BranchIfTrue(lblNotZero, notZero); + + context.Return(resultSign); + + context.MarkLabel(lblNotZero); + + Operand notNaNOrInf = context.ICompareNotEqual(exp, Const(0x7ff)); + + mantissa = context.BitwiseOr(mantissa, Const(0x40000000)); + exp = context.ISubtract(exp, Const(0x381)); + + // Note: Overflow cases are not handled here and might produce incorrect results. + + Operand roundBits = context.BitwiseAnd(mantissa, Const(0x7f)); + Operand roundBitsXor64 = context.BitwiseExclusiveOr(roundBits, Const(0x40)); + mantissa = context.ShiftRightU32(context.IAdd(mantissa, Const(0x40)), Const(7)); + mantissa = context.BitwiseAnd(mantissa, context.ConditionalSelect(roundBitsXor64, Const(~0), Const(~1))); + + exp = context.ConditionalSelect(mantissa, exp, Const(0)); + exp = context.ConditionalSelect(notNaNOrInf, exp, Const(0xff)); + + Operand result = context.IAdd(context.IAdd(mantissa, context.ShiftLeft(exp, Const(23))), resultSign); + + context.Return(result); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "ConvertDoubleToFloat", true, 2, 0); + } + + private static Function GenerateConvertFloatToDoubleFunction() + { + EmitterContext context = new(); + + Operand value = Argument(0); + + Operand mantissa = context.BitwiseAnd(value, Const(0x7fffff)); + Operand exp = context.BitwiseAnd(context.ShiftRightU32(value, Const(23)), Const(0xff)); + Operand sign = context.ShiftRightS32(value, Const(31)); + + Operand notNaNOrInf = context.ICompareNotEqual(exp, Const(0xff)); + Operand expNotZero = context.ICompareNotEqual(exp, Const(0)); + Operand notDenorm = context.BitwiseOr(expNotZero, context.ICompareEqual(mantissa, Const(0))); + + exp = context.IAdd(exp, Const(0x380)); + + Operand shiftDist = context.ISubtract(Const(32), context.FindMSBU32(mantissa)); + Operand normExp = context.ISubtract(context.ISubtract(Const(1), shiftDist), Const(1)); + Operand normMant = context.ShiftLeft(mantissa, shiftDist); + + exp = context.ConditionalSelect(notNaNOrInf, exp, Const(0x7ff)); + exp = context.ConditionalSelect(notDenorm, exp, normExp); + mantissa = context.ConditionalSelect(expNotZero, mantissa, normMant); + + Operand resultLow = context.ShiftLeft(mantissa, Const(29)); + Operand resultHigh = context.ShiftRightU32(mantissa, Const(3)); + + resultHigh = context.IAdd(resultHigh, context.ShiftLeft(exp, Const(20))); + resultHigh = context.IAdd(resultHigh, context.ShiftLeft(sign, Const(31))); + + context.Copy(Argument(1), resultLow); + context.Copy(Argument(2), resultHigh); + context.Return(); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "ConvertFloatToDouble", false, 1, 2); + } + + private static Function GenerateFunction(HelperFunctionName functionName, int id) + { + return functionName switch + { + HelperFunctionName.SharedAtomicMaxS32 => GenerateSharedAtomicSigned(id, isMin: false), + HelperFunctionName.SharedAtomicMinS32 => GenerateSharedAtomicSigned(id, isMin: true), + HelperFunctionName.SharedStore8 => GenerateSharedStore8(id), + HelperFunctionName.SharedStore16 => GenerateSharedStore16(id), + _ => throw new ArgumentException($"Invalid function name {functionName}"), + }; + } + + private static Function GenerateSharedAtomicSigned(int id, bool isMin) + { + EmitterContext context = new(); + + Operand wordOffset = Argument(0); + Operand value = Argument(1); + + Operand result = GenerateSharedAtomicCasLoop(context, wordOffset, id, (memValue) => + { + return isMin + ? context.IMinimumS32(memValue, value) + : context.IMaximumS32(memValue, value); + }); + + context.Return(result); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, $"SharedAtomic{(isMin ? "Min" : "Max")}_{id}", true, 2, 0); + } + + private static Function GenerateSharedStore8(int id) + { + return GenerateSharedStore(id, 8); + } + + private static Function GenerateSharedStore16(int id) + { + return GenerateSharedStore(id, 16); + } + + private static Function GenerateSharedStore(int id, int bitSize) + { + EmitterContext context = new(); + + Operand offset = Argument(0); + Operand value = Argument(1); + + Operand wordOffset = context.ShiftRightU32(offset, Const(2)); + Operand bitOffset = GetBitOffset(context, offset); + + GenerateSharedAtomicCasLoop(context, wordOffset, id, (memValue) => + { + return context.BitfieldInsert(memValue, value, bitOffset, Const(bitSize)); + }); + + context.Return(); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, $"SharedStore{bitSize}_{id}", false, 2, 0); + } + + private static Function GenerateShuffleFunction(HelperFunctionName functionName, int subgroupSize) + { + return functionName switch + { + HelperFunctionName.Shuffle => GenerateShuffle(subgroupSize), + HelperFunctionName.ShuffleDown => GenerateShuffleDown(subgroupSize), + HelperFunctionName.ShuffleUp => GenerateShuffleUp(subgroupSize), + HelperFunctionName.ShuffleXor => GenerateShuffleXor(subgroupSize), + _ => throw new ArgumentException($"Invalid function name {functionName}"), + }; + } + + private static Function GenerateShuffle(int subgroupSize) + { + EmitterContext context = new(); + + Operand value = Argument(0); + Operand index = Argument(1); + Operand mask = Argument(2); + + Operand clamp = context.BitwiseAnd(mask, Const(0x1f)); + Operand segMask = context.BitwiseAnd(context.ShiftRightU32(mask, Const(8)), Const(0x1f)); + Operand minThreadId = context.BitwiseAnd(GenerateLoadSubgroupLaneId(context, subgroupSize), segMask); + Operand maxThreadId = context.BitwiseOr(context.BitwiseAnd(clamp, context.BitwiseNot(segMask)), minThreadId); + Operand srcThreadId = context.BitwiseOr(context.BitwiseAnd(index, context.BitwiseNot(segMask)), minThreadId); + Operand valid = context.ICompareLessOrEqualUnsigned(srcThreadId, maxThreadId); + + context.Copy(Argument(3), valid); + + Operand result = context.Shuffle(value, GenerateSubgroupShuffleIndex(context, srcThreadId, subgroupSize)); + + context.Return(context.ConditionalSelect(valid, result, value)); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "Shuffle", true, 3, 1); + } + + private static Function GenerateShuffleDown(int subgroupSize) + { + EmitterContext context = new(); + + Operand value = Argument(0); + Operand index = Argument(1); + Operand mask = Argument(2); + + Operand clamp = context.BitwiseAnd(mask, Const(0x1f)); + Operand segMask = context.BitwiseAnd(context.ShiftRightU32(mask, Const(8)), Const(0x1f)); + Operand laneId = GenerateLoadSubgroupLaneId(context, subgroupSize); + Operand minThreadId = context.BitwiseAnd(laneId, segMask); + Operand maxThreadId = context.BitwiseOr(context.BitwiseAnd(clamp, context.BitwiseNot(segMask)), minThreadId); + Operand srcThreadId = context.IAdd(laneId, index); + Operand valid = context.ICompareLessOrEqualUnsigned(srcThreadId, maxThreadId); + + context.Copy(Argument(3), valid); + + Operand result = context.Shuffle(value, GenerateSubgroupShuffleIndex(context, srcThreadId, subgroupSize)); + + context.Return(context.ConditionalSelect(valid, result, value)); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "ShuffleDown", true, 3, 1); + } + + private static Function GenerateShuffleUp(int subgroupSize) + { + EmitterContext context = new(); + + Operand value = Argument(0); + Operand index = Argument(1); + Operand mask = Argument(2); + + Operand segMask = context.BitwiseAnd(context.ShiftRightU32(mask, Const(8)), Const(0x1f)); + Operand laneId = GenerateLoadSubgroupLaneId(context, subgroupSize); + Operand minThreadId = context.BitwiseAnd(laneId, segMask); + Operand srcThreadId = context.ISubtract(laneId, index); + Operand valid = context.ICompareGreaterOrEqual(srcThreadId, minThreadId); + + context.Copy(Argument(3), valid); + + Operand result = context.Shuffle(value, GenerateSubgroupShuffleIndex(context, srcThreadId, subgroupSize)); + + context.Return(context.ConditionalSelect(valid, result, value)); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "ShuffleUp", true, 3, 1); + } + + private static Function GenerateShuffleXor(int subgroupSize) + { + EmitterContext context = new(); + + Operand value = Argument(0); + Operand index = Argument(1); + Operand mask = Argument(2); + + Operand clamp = context.BitwiseAnd(mask, Const(0x1f)); + Operand segMask = context.BitwiseAnd(context.ShiftRightU32(mask, Const(8)), Const(0x1f)); + Operand laneId = GenerateLoadSubgroupLaneId(context, subgroupSize); + Operand minThreadId = context.BitwiseAnd(laneId, segMask); + Operand maxThreadId = context.BitwiseOr(context.BitwiseAnd(clamp, context.BitwiseNot(segMask)), minThreadId); + Operand srcThreadId = context.BitwiseExclusiveOr(laneId, index); + Operand valid = context.ICompareLessOrEqualUnsigned(srcThreadId, maxThreadId); + + context.Copy(Argument(3), valid); + + Operand result = context.Shuffle(value, GenerateSubgroupShuffleIndex(context, srcThreadId, subgroupSize)); + + context.Return(context.ConditionalSelect(valid, result, value)); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "ShuffleXor", true, 3, 1); + } + + private static Operand GenerateLoadSubgroupLaneId(EmitterContext context, int subgroupSize) + { + if (subgroupSize <= 32) + { + return context.Load(StorageKind.Input, IoVariable.SubgroupLaneId); + } + + return context.BitwiseAnd(context.Load(StorageKind.Input, IoVariable.SubgroupLaneId), Const(0x1f)); + } + + private static Operand GenerateSubgroupShuffleIndex(EmitterContext context, Operand srcThreadId, int subgroupSize) + { + if (subgroupSize <= 32) + { + return srcThreadId; + } + + return context.BitwiseOr( + context.BitwiseAnd(context.Load(StorageKind.Input, IoVariable.SubgroupLaneId), Const(0x60)), + srcThreadId); + } + + private Function GenerateTexelFetchScaleFunction() + { + EmitterContext context = new(); + + Operand input = Argument(0); + Operand samplerIndex = Argument(1); + Operand index = GetScaleIndex(context, samplerIndex); + + Operand scale = context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.RenderScale), index); + + Operand scaleIsOne = context.FPCompareEqual(scale, ConstF(1f)); + Operand lblScaleNotOne = Label(); + + context.BranchIfFalse(lblScaleNotOne, scaleIsOne); + context.Return(input); + context.MarkLabel(lblScaleNotOne); + + int inArgumentsCount; + + if (_stage == ShaderStage.Fragment) + { + Operand scaleIsLessThanZero = context.FPCompareLess(scale, ConstF(0f)); + Operand lblScaleGreaterOrEqualZero = Label(); + + context.BranchIfFalse(lblScaleGreaterOrEqualZero, scaleIsLessThanZero); + + Operand negScale = context.FPNegate(scale); + Operand inputScaled = context.FPMultiply(context.IConvertS32ToFP32(input), negScale); + Operand fragCoordX = context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(0)); + Operand fragCoordY = context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(1)); + Operand fragCoord = context.ConditionalSelect(Argument(2), fragCoordY, fragCoordX); + Operand inputBias = context.FPModulo(fragCoord, negScale); + Operand inputWithBias = context.FPAdd(inputScaled, inputBias); + + context.Return(context.FP32ConvertToS32(inputWithBias)); + context.MarkLabel(lblScaleGreaterOrEqualZero); + + inArgumentsCount = 3; + } + else + { + inArgumentsCount = 2; + } + + Operand inputScaled2 = context.FPMultiply(context.IConvertS32ToFP32(input), scale); + + context.Return(context.FP32ConvertToS32(inputScaled2)); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TexelFetchScale", true, inArgumentsCount, 0); + } + + private Function GenerateTextureSizeUnscaleFunction() + { + EmitterContext context = new(); + + Operand input = Argument(0); + Operand samplerIndex = Argument(1); + Operand index = GetScaleIndex(context, samplerIndex); + + Operand scale = context.FPAbsolute(context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.RenderScale), index)); + + Operand scaleIsOne = context.FPCompareEqual(scale, ConstF(1f)); + Operand lblScaleNotOne = Label(); + + context.BranchIfFalse(lblScaleNotOne, scaleIsOne); + context.Return(input); + context.MarkLabel(lblScaleNotOne); + + Operand inputUnscaled = context.FPDivide(context.IConvertS32ToFP32(input), scale); + + context.Return(context.FP32ConvertToS32(inputUnscaled)); + + return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TextureSizeUnscale", true, 2, 0); + } + + private Operand GetScaleIndex(EmitterContext context, Operand index) + { + switch (_stage) + { + case ShaderStage.Vertex: + Operand fragScaleCount = context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.FragmentRenderScaleCount)); + return context.IAdd(Const(1), context.IAdd(index, fragScaleCount)); + default: + return context.IAdd(Const(1), index); + } + } + + public static Operand GetBitOffset(EmitterContext context, Operand offset) + { + return context.ShiftLeft(context.BitwiseAnd(offset, Const(3)), Const(3)); + } + + private static Operand GenerateSharedAtomicCasLoop(EmitterContext context, Operand wordOffset, int id, Func opCallback) + { + Operand lblLoopHead = Label(); + + context.MarkLabel(lblLoopHead); + + Operand oldValue = context.Load(StorageKind.SharedMemory, id, wordOffset); + Operand newValue = opCallback(oldValue); + + Operand casResult = context.AtomicCompareAndSwap(StorageKind.SharedMemory, id, wordOffset, oldValue, newValue); + + Operand casFail = context.ICompareNotEqual(casResult, oldValue); + + context.BranchIfTrue(lblLoopHead, casFail); + + return oldValue; + } + + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs new file mode 100644 index 00000000..09b17729 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/HelperFunctionName.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Graphics.Shader.Translation +{ + enum HelperFunctionName + { + Invalid, + + ConvertDoubleToFloat, + ConvertFloatToDouble, + SharedAtomicMaxS32, + SharedAtomicMinS32, + SharedStore8, + SharedStore16, + Shuffle, + ShuffleDown, + ShuffleUp, + ShuffleXor, + TexelFetchScale, + TextureSizeUnscale, + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs b/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs new file mode 100644 index 00000000..11fe6599 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.Graphics.Shader.Translation +{ + class HostCapabilities + { + public readonly bool ReducedPrecision; + public readonly bool SupportsFragmentShaderInterlock; + public readonly bool SupportsFragmentShaderOrderingIntel; + public readonly bool SupportsGeometryShaderPassthrough; + public readonly bool SupportsShaderBallot; + public readonly bool SupportsShaderBarrierDivergence; + public readonly bool SupportsShaderFloat64; + public readonly bool SupportsTextureShadowLod; + public readonly bool SupportsViewportMask; + + public HostCapabilities( + bool reducedPrecision, + bool supportsFragmentShaderInterlock, + bool supportsFragmentShaderOrderingIntel, + bool supportsGeometryShaderPassthrough, + bool supportsShaderBallot, + bool supportsShaderBarrierDivergence, + bool supportsShaderFloat64, + bool supportsTextureShadowLod, + bool supportsViewportMask) + { + ReducedPrecision = reducedPrecision; + SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock; + SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel; + SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough; + SupportsShaderBallot = supportsShaderBallot; + SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence; + SupportsShaderFloat64 = supportsShaderFloat64; + SupportsTextureShadowLod = supportsTextureShadowLod; + SupportsViewportMask = supportsViewportMask; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/IoUsage.cs b/src/Ryujinx.Graphics.Shader/Translation/IoUsage.cs new file mode 100644 index 00000000..8ce2da4a --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/IoUsage.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.Graphics.Shader.Translation +{ + readonly struct IoUsage + { + private readonly FeatureFlags _usedFeatures; + + public readonly bool UsesRtLayer => _usedFeatures.HasFlag(FeatureFlags.RtLayer); + public readonly bool UsesViewportIndex => _usedFeatures.HasFlag(FeatureFlags.ViewportIndex); + public readonly bool UsesViewportMask => _usedFeatures.HasFlag(FeatureFlags.ViewportMask); + public readonly byte ClipDistancesWritten { get; } + public readonly int UserDefinedMap { get; } + + public IoUsage(FeatureFlags usedFeatures, byte clipDistancesWritten, int userDefinedMap) + { + _usedFeatures = usedFeatures; + ClipDistancesWritten = clipDistancesWritten; + UserDefinedMap = userDefinedMap; + } + + public readonly IoUsage Combine(IoUsage other) + { + return new IoUsage( + _usedFeatures | other._usedFeatures, + (byte)(ClipDistancesWritten | other.ClipDistancesWritten), + UserDefinedMap | other.UserDefinedMap); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs new file mode 100644 index 00000000..1f2f79a2 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs @@ -0,0 +1,494 @@ +using Ryujinx.Graphics.Shader.Instructions; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + class BindlessElimination + { + public static void RunPass(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor) + { + // We can turn a bindless into regular access by recognizing the pattern + // produced by the compiler for separate texture and sampler. + // We check for the following conditions: + // - The handle is a constant buffer value. + // - The handle is the result of a bitwise OR logical operation. + // - Both sources of the OR operation comes from a constant buffer. + LinkedListNode nextNode; + + for (LinkedListNode node = block.Operations.First; node != null; node = nextNode) + { + nextNode = node.Next; + + if (node.Value is not TextureOperation texOp) + { + continue; + } + + if ((texOp.Flags & TextureFlags.Bindless) == 0) + { + continue; + } + + if (!TryConvertBindless(block, resourceManager, gpuAccessor, texOp) && + !GenerateBindlessAccess(block, resourceManager, gpuAccessor, texOp, node)) + { + // If we can't do bindless elimination, remove the texture operation. + // Set any destination variables to zero. + + string typeName = texOp.Inst.IsImage() + ? texOp.Type.ToGlslImageType(texOp.Format.GetComponentType()) + : texOp.Type.ToGlslTextureType(); + + gpuAccessor.Log($"Failed to find handle source for bindless access of type \"{typeName}\"."); + + for (int destIndex = 0; destIndex < texOp.DestsCount; destIndex++) + { + block.Operations.AddBefore(node, new Operation(Instruction.Copy, texOp.GetDest(destIndex), OperandHelper.Const(0))); + } + + Utils.DeleteNode(node, texOp); + } + } + } + + private static bool GenerateBindlessAccess( + BasicBlock block, + ResourceManager resourceManager, + IGpuAccessor gpuAccessor, + TextureOperation texOp, + LinkedListNode node) + { + if (!gpuAccessor.QueryHostSupportsSeparateSampler()) + { + // We depend on combining samplers and textures in the shader being supported for this. + + return false; + } + + Operand bindlessHandle = texOp.GetSource(0); + + if (bindlessHandle.AsgOp is PhiNode phi) + { + for (int srcIndex = 0; srcIndex < phi.SourcesCount; srcIndex++) + { + Operand phiSource = phi.GetSource(srcIndex); + + if (phiSource.AsgOp is not PhiNode && !IsBindlessAccessAllowed(phiSource)) + { + return false; + } + } + } + else if (!IsBindlessAccessAllowed(bindlessHandle)) + { + return false; + } + + Operand textureHandle = OperandHelper.Local(); + Operand samplerHandle = OperandHelper.Local(); + Operand textureIndex = OperandHelper.Local(); + + block.Operations.AddBefore(node, new Operation(Instruction.BitwiseAnd, textureHandle, bindlessHandle, OperandHelper.Const(0xfffff))); + block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, samplerHandle, bindlessHandle, OperandHelper.Const(20))); + + int texturePoolLength = Math.Max(BindlessToArray.MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromPool()); + + block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, textureIndex, textureHandle, OperandHelper.Const(texturePoolLength - 1))); + + texOp.SetSource(0, textureIndex); + + bool hasSampler = !texOp.Inst.IsImage(); + + SetBindingPair textureSetAndBinding = resourceManager.GetTextureOrImageBinding( + texOp.Inst, + texOp.Type, + texOp.Format, + texOp.Flags & ~TextureFlags.Bindless, + 0, + TextureHandle.PackOffsets(0, 0, TextureHandleType.Direct), + texturePoolLength, + hasSampler); + + if (hasSampler) + { + Operand samplerIndex = OperandHelper.Local(); + + int samplerPoolLength = Math.Max(BindlessToArray.MinimumArrayLength, gpuAccessor.QuerySamplerArrayLengthFromPool()); + + block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, samplerIndex, samplerHandle, OperandHelper.Const(samplerPoolLength - 1))); + + texOp.InsertSource(1, samplerIndex); + + SetBindingPair samplerSetAndBinding = resourceManager.GetTextureOrImageBinding( + texOp.Inst, + SamplerType.None, + texOp.Format, + TextureFlags.None, + 0, + TextureHandle.PackOffsets(0, 0, TextureHandleType.Direct), + samplerPoolLength); + + texOp.TurnIntoArray(textureSetAndBinding, samplerSetAndBinding); + } + else + { + texOp.TurnIntoArray(textureSetAndBinding); + } + + return true; + } + + private static bool IsBindlessAccessAllowed(Operand bindlessHandle) + { + if (bindlessHandle.Type == OperandType.ConstantBuffer) + { + // Bindless access with handles from constant buffer is allowed. + + return true; + } + + if (bindlessHandle.AsgOp is not Operation handleOp || + handleOp.Inst != Instruction.Load || + (handleOp.StorageKind != StorageKind.Input && handleOp.StorageKind != StorageKind.StorageBuffer)) + { + // Right now, we only allow bindless access when the handle comes from a shader input or storage buffer. + // This is an artificial limitation to prevent it from being used in cases where it + // would have a large performance impact of loading all textures in the pool. + // It might be removed in the future, if we can mitigate the performance impact. + + return false; + } + + return true; + } + + private static bool TryConvertBindless(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor, TextureOperation texOp) + { + if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery()) + { + Operand bindlessHandle = texOp.GetSource(0); + + // In some cases the compiler uses a shuffle operation to get the handle, + // for some textureGrad implementations. In those cases, we can skip the shuffle. + if (bindlessHandle.AsgOp is Operation shuffleOp && shuffleOp.Inst == Instruction.Shuffle) + { + bindlessHandle = shuffleOp.GetSource(0); + } + + bindlessHandle = Utils.FindLastOperation(bindlessHandle, block); + + // Some instructions do not encode an accurate sampler type: + // - Most instructions uses the same type for 1D and Buffer. + // - Query instructions may not have any type. + // For those cases, we need to try getting the type from current GPU state, + // as long bindless elimination is successful and we know where the texture descriptor is located. + bool rewriteSamplerType = + texOp.Type == SamplerType.TextureBuffer || + texOp.Inst == Instruction.TextureQuerySamples || + texOp.Inst == Instruction.TextureQuerySize; + + if (bindlessHandle.Type == OperandType.ConstantBuffer) + { + SetHandle( + resourceManager, + gpuAccessor, + texOp, + bindlessHandle.GetCbufOffset(), + bindlessHandle.GetCbufSlot(), + rewriteSamplerType, + isImage: false); + + return true; + } + + if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp)) + { + return false; + } + + if (handleCombineOp.Inst != Instruction.BitwiseOr) + { + return false; + } + + Operand src0 = Utils.FindLastOperation(handleCombineOp.GetSource(0), block); + Operand src1 = Utils.FindLastOperation(handleCombineOp.GetSource(1), block); + + // For cases where we have a constant, ensure that the constant is always + // the second operand. + // Since this is a commutative operation, both are fine, + // and having a "canonical" representation simplifies some checks below. + if (src0.Type == OperandType.Constant && src1.Type != OperandType.Constant) + { + (src0, src1) = (src1, src0); + } + + TextureHandleType handleType = TextureHandleType.SeparateSamplerHandle; + + // Try to match the following patterns: + // Masked pattern: + // - samplerHandle = samplerHandle & 0xFFF00000; + // - textureHandle = textureHandle & 0xFFFFF; + // - combinedHandle = samplerHandle | textureHandle; + // Where samplerHandle and textureHandle comes from a constant buffer. + // Shifted pattern: + // - samplerHandle = samplerId << 20; + // - combinedHandle = samplerHandle | textureHandle; + // Where samplerId and textureHandle comes from a constant buffer. + // Constant pattern: + // - combinedHandle = samplerHandleConstant | textureHandle; + // Where samplerHandleConstant is a constant value, and textureHandle comes from a constant buffer. + if (src0.AsgOp is Operation src0AsgOp) + { + if (src1.AsgOp is Operation src1AsgOp && + src0AsgOp.Inst == Instruction.BitwiseAnd && + src1AsgOp.Inst == Instruction.BitwiseAnd) + { + src0 = GetSourceForMaskedHandle(src0AsgOp, 0xFFFFF); + src1 = GetSourceForMaskedHandle(src1AsgOp, 0xFFF00000); + + // The OR operation is commutative, so we can also try to swap the operands to get a match. + if (src0 == null || src1 == null) + { + src0 = GetSourceForMaskedHandle(src1AsgOp, 0xFFFFF); + src1 = GetSourceForMaskedHandle(src0AsgOp, 0xFFF00000); + } + + if (src0 == null || src1 == null) + { + return false; + } + } + else if (src0AsgOp.Inst == Instruction.ShiftLeft) + { + Operand shift = src0AsgOp.GetSource(1); + + if (shift.Type == OperandType.Constant && shift.Value == 20) + { + src0 = src1; + src1 = src0AsgOp.GetSource(0); + handleType = TextureHandleType.SeparateSamplerId; + } + } + } + else if (src1.AsgOp is Operation src1AsgOp && src1AsgOp.Inst == Instruction.ShiftLeft) + { + Operand shift = src1AsgOp.GetSource(1); + + if (shift.Type == OperandType.Constant && shift.Value == 20) + { + src1 = src1AsgOp.GetSource(0); + handleType = TextureHandleType.SeparateSamplerId; + } + } + else if (src1.Type == OperandType.Constant && (src1.Value & 0xfffff) == 0) + { + handleType = TextureHandleType.SeparateConstantSamplerHandle; + } + + if (src0.Type != OperandType.ConstantBuffer) + { + return false; + } + + if (handleType == TextureHandleType.SeparateConstantSamplerHandle) + { + SetHandle( + resourceManager, + gpuAccessor, + texOp, + TextureHandle.PackOffsets(src0.GetCbufOffset(), (src1.Value >> 20) & 0xfff, handleType), + TextureHandle.PackSlots(src0.GetCbufSlot(), 0), + rewriteSamplerType, + isImage: false); + + return true; + } + else if (src1.Type == OperandType.ConstantBuffer) + { + SetHandle( + resourceManager, + gpuAccessor, + texOp, + TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType), + TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()), + rewriteSamplerType, + isImage: false); + + return true; + } + } + else if (texOp.Inst.IsImage()) + { + Operand src0 = Utils.FindLastOperation(texOp.GetSource(0), block); + + if (src0.Type == OperandType.ConstantBuffer) + { + int cbufOffset = src0.GetCbufOffset(); + int cbufSlot = src0.GetCbufSlot(); + + if (texOp.Format == TextureFormat.Unknown) + { + if (texOp.Inst == Instruction.ImageAtomic) + { + texOp.Format = ShaderProperties.GetTextureFormatAtomic(gpuAccessor, cbufOffset, cbufSlot); + } + else + { + texOp.Format = ShaderProperties.GetTextureFormat(gpuAccessor, cbufOffset, cbufSlot); + } + } + + bool rewriteSamplerType = texOp.Type == SamplerType.TextureBuffer; + + SetHandle(resourceManager, gpuAccessor, texOp, cbufOffset, cbufSlot, rewriteSamplerType, isImage: true); + + return true; + } + } + + return false; + } + + private static bool TryGetOperation(INode asgOp, out Operation outOperation) + { + if (asgOp is PhiNode phi) + { + // If we have a phi, let's check if all inputs are effectively the same value. + // If so, we can "see through" the phi and pick any of the inputs (since they are all the same). + + Operand firstSrc = phi.GetSource(0); + + for (int index = 1; index < phi.SourcesCount; index++) + { + if (!IsSameOperand(firstSrc, phi.GetSource(index))) + { + outOperation = null; + + return false; + } + } + + asgOp = firstSrc.AsgOp; + } + + if (asgOp is Operation operation) + { + outOperation = operation; + + return true; + } + + outOperation = null; + + return false; + } + + private static bool IsSameOperand(Operand x, Operand y) + { + if (x.Type == y.Type && x.Type == OperandType.LocalVariable) + { + return x.AsgOp is Operation xOp && + y.AsgOp is Operation yOp && + xOp.Inst == Instruction.BitwiseOr && + yOp.Inst == Instruction.BitwiseOr && + AreBothEqualConstantBuffers(xOp.GetSource(0), yOp.GetSource(0)) && + AreBothEqualConstantBuffers(xOp.GetSource(1), yOp.GetSource(1)); + } + + return false; + } + + private static bool AreBothEqualConstantBuffers(Operand x, Operand y) + { + return x.Type == y.Type && x.Value == y.Value && x.Type == OperandType.ConstantBuffer; + } + + private static Operand GetSourceForMaskedHandle(Operation asgOp, uint mask) + { + // Assume it was already checked that the operation is bitwise AND. + + Operand src0 = asgOp.GetSource(0); + Operand src1 = asgOp.GetSource(1); + + if (src0.Type == OperandType.ConstantBuffer && src1.Type == OperandType.ConstantBuffer) + { + // We can't check if the mask matches here as both operands are from a constant buffer. + // Be optimistic and assume it matches. Avoid constant buffer 1 as official drivers + // uses this one to store compiler constants. + + return src0.GetCbufSlot() == 1 ? src1 : src0; + } + else if (src0.Type == OperandType.ConstantBuffer && src1.Type == OperandType.Constant) + { + if ((uint)src1.Value == mask) + { + return src0; + } + } + else if (src0.Type == OperandType.Constant && src1.Type == OperandType.ConstantBuffer) + { + if ((uint)src0.Value == mask) + { + return src1; + } + } + + return null; + } + + private static void SetHandle( + ResourceManager resourceManager, + IGpuAccessor gpuAccessor, + TextureOperation texOp, + int cbufOffset, + int cbufSlot, + bool rewriteSamplerType, + bool isImage) + { + if (rewriteSamplerType) + { + SamplerType newType = gpuAccessor.QuerySamplerType(cbufOffset, cbufSlot); + + if (texOp.Inst.IsTextureQuery()) + { + texOp.Type = newType; + } + else if (texOp.Type == SamplerType.TextureBuffer && newType == SamplerType.Texture1D) + { + int coordsCount = 2; + + if (InstEmit.Sample1DAs2D) + { + newType = SamplerType.Texture2D; + texOp.InsertSource(coordsCount++, OperandHelper.Const(0)); + } + + if (!isImage && + (texOp.Flags & TextureFlags.IntCoords) != 0 && + (texOp.Flags & TextureFlags.LodLevel) == 0) + { + // IntCoords textures must always have explicit LOD. + texOp.SetLodLevelFlag(); + texOp.InsertSource(coordsCount, OperandHelper.Const(0)); + } + + texOp.Type = newType; + } + } + + SetBindingPair setAndBinding = resourceManager.GetTextureOrImageBinding( + texOp.Inst, + texOp.Type, + texOp.Format, + texOp.Flags & ~TextureFlags.Bindless, + cbufSlot, + cbufOffset); + + texOp.SetBinding(setAndBinding); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs new file mode 100644 index 00000000..1e0b3b64 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs @@ -0,0 +1,238 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class BindlessToArray + { + private const int NvnTextureBufferIndex = 2; + private const int HardcodedArrayLengthOgl = 4; + + // 1 and 0 elements are not considered arrays anymore. + public const int MinimumArrayLength = 2; + + public static void RunPassOgl(BasicBlock block, ResourceManager resourceManager) + { + // We can turn a bindless texture access into a indexed access, + // as long the following conditions are true: + // - The handle is loaded using a LDC instruction. + // - The handle is loaded from the constant buffer with the handles (CB2 for NVN). + // - The load has a constant offset. + // The base offset of the array of handles on the constant buffer is the constant offset. + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (node.Value is not TextureOperation texOp) + { + continue; + } + + if ((texOp.Flags & TextureFlags.Bindless) == 0) + { + continue; + } + + if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp) + { + continue; + } + + if (handleAsgOp.Inst != Instruction.Load || + handleAsgOp.StorageKind != StorageKind.ConstantBuffer || + handleAsgOp.SourcesCount != 4) + { + continue; + } + + Operand ldcSrc0 = handleAsgOp.GetSource(0); + + if (ldcSrc0.Type != OperandType.Constant || + !resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) || + src0CbufSlot != NvnTextureBufferIndex) + { + continue; + } + + Operand ldcSrc1 = handleAsgOp.GetSource(1); + + // We expect field index 0 to be accessed. + if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0) + { + continue; + } + + Operand ldcSrc2 = handleAsgOp.GetSource(2); + + // FIXME: This is missing some checks, for example, a check to ensure that the shift value is 2. + // Might be not worth fixing since if that doesn't kick in, the result will be no texture + // to access anyway which is also wrong. + // Plus this whole transform is fundamentally flawed as-is since we have no way to know the array size. + // Eventually, this should be entirely removed in favor of a implementation that supports true bindless + // texture access. + if (ldcSrc2.AsgOp is not Operation shrOp || shrOp.Inst != Instruction.ShiftRightU32) + { + continue; + } + + if (shrOp.GetSource(0).AsgOp is not Operation shrOp2 || shrOp2.Inst != Instruction.ShiftRightU32) + { + continue; + } + + if (shrOp2.GetSource(0).AsgOp is not Operation addOp || addOp.Inst != Instruction.Add) + { + continue; + } + + Operand addSrc1 = addOp.GetSource(1); + + if (addSrc1.Type != OperandType.Constant) + { + continue; + } + + TurnIntoArray(resourceManager, texOp, NvnTextureBufferIndex, addSrc1.Value / 4, HardcodedArrayLengthOgl); + + Operand index = Local(); + + Operand source = addOp.GetSource(0); + + Operation shrBy3 = new(Instruction.ShiftRightU32, index, source, Const(3)); + + block.Operations.AddBefore(node, shrBy3); + + texOp.SetSource(0, index); + } + } + + public static void RunPass(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor) + { + // We can turn a bindless texture access into a indexed access, + // as long the following conditions are true: + // - The handle is loaded using a LDC instruction. + // - The handle is loaded from the constant buffer with the handles (CB2 for NVN). + // - The load has a constant offset. + // The base offset of the array of handles on the constant buffer is the constant offset. + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (node.Value is not TextureOperation texOp) + { + continue; + } + + if ((texOp.Flags & TextureFlags.Bindless) == 0) + { + continue; + } + + Operand bindlessHandle = Utils.FindLastOperation(texOp.GetSource(0), block); + + if (bindlessHandle.AsgOp is not Operation handleAsgOp) + { + continue; + } + + int secondaryCbufSlot = 0; + int secondaryCbufOffset = 0; + bool hasSecondaryHandle = false; + + if (handleAsgOp.Inst == Instruction.BitwiseOr) + { + Operand src0 = Utils.FindLastOperation(handleAsgOp.GetSource(0), block); + Operand src1 = Utils.FindLastOperation(handleAsgOp.GetSource(1), block); + + if (src0.Type == OperandType.ConstantBuffer && src1.AsgOp is Operation) + { + handleAsgOp = src1.AsgOp as Operation; + secondaryCbufSlot = src0.GetCbufSlot(); + secondaryCbufOffset = src0.GetCbufOffset(); + hasSecondaryHandle = true; + } + else if (src0.AsgOp is Operation && src1.Type == OperandType.ConstantBuffer) + { + handleAsgOp = src0.AsgOp as Operation; + secondaryCbufSlot = src1.GetCbufSlot(); + secondaryCbufOffset = src1.GetCbufOffset(); + hasSecondaryHandle = true; + } + } + + if (handleAsgOp.Inst != Instruction.Load || + handleAsgOp.StorageKind != StorageKind.ConstantBuffer || + handleAsgOp.SourcesCount != 4) + { + continue; + } + + Operand ldcSrc0 = handleAsgOp.GetSource(0); + + if (ldcSrc0.Type != OperandType.Constant || + !resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot)) + { + continue; + } + + Operand ldcSrc1 = handleAsgOp.GetSource(1); + + // We expect field index 0 to be accessed. + if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0) + { + continue; + } + + Operand ldcVecIndex = handleAsgOp.GetSource(2); + Operand ldcElemIndex = handleAsgOp.GetSource(3); + + if (ldcVecIndex.Type != OperandType.LocalVariable || ldcElemIndex.Type != OperandType.LocalVariable) + { + continue; + } + + int cbufSlot; + int handleIndex; + + if (hasSecondaryHandle) + { + cbufSlot = TextureHandle.PackSlots(src0CbufSlot, secondaryCbufSlot); + handleIndex = TextureHandle.PackOffsets(0, secondaryCbufOffset, TextureHandleType.SeparateSamplerHandle); + } + else + { + cbufSlot = src0CbufSlot; + handleIndex = 0; + } + + int length = Math.Max(MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromBuffer(src0CbufSlot)); + + TurnIntoArray(resourceManager, texOp, cbufSlot, handleIndex, length); + + Operand vecIndex = Local(); + Operand elemIndex = Local(); + Operand index = Local(); + Operand indexMin = Local(); + + block.Operations.AddBefore(node, new Operation(Instruction.ShiftLeft, vecIndex, ldcVecIndex, Const(1))); + block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, elemIndex, ldcElemIndex, Const(1))); + block.Operations.AddBefore(node, new Operation(Instruction.Add, index, vecIndex, elemIndex)); + block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, indexMin, index, Const(length - 1))); + + texOp.SetSource(0, indexMin); + } + } + + private static void TurnIntoArray(ResourceManager resourceManager, TextureOperation texOp, int cbufSlot, int handleIndex, int length) + { + SetBindingPair setAndBinding = resourceManager.GetTextureOrImageBinding( + texOp.Inst, + texOp.Type, + texOp.Format, + texOp.Flags & ~TextureFlags.Bindless, + cbufSlot, + handleIndex, + length); + + texOp.TurnIntoArray(setAndBinding); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs new file mode 100644 index 00000000..bd2eceda --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs @@ -0,0 +1,64 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class BranchElimination + { + public static bool RunPass(BasicBlock block) + { + if (block.HasBranch && IsRedundantBranch((Operation)block.GetLastOp(), Next(block))) + { + block.Branch = null; + + return true; + } + + return false; + } + + private static bool IsRedundantBranch(Operation current, BasicBlock nextBlock) + { + // Here we check that: + // - The current block ends with a branch. + // - The next block only contains a branch. + // - The branch on the next block is unconditional. + // - Both branches are jumping to the same location. + // In this case, the branch on the current block can be removed, + // as the next block is going to jump to the same place anyway. + if (nextBlock == null) + { + return false; + } + + if (nextBlock.Operations.First?.Value is not Operation next) + { + return false; + } + + if (next.Inst != Instruction.Branch) + { + return false; + } + + return current.Dest == next.Dest; + } + + private static BasicBlock Next(BasicBlock block) + { + block = block.Next; + + while (block != null && block.Operations.Count == 0) + { + if (block.HasBranch) + { + throw new InvalidOperationException("Found a bogus empty block that \"ends with a branch\"."); + } + + block = block.Next; + } + + return block; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/ConstantFolding.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/ConstantFolding.cs new file mode 100644 index 00000000..3941303b --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/ConstantFolding.cs @@ -0,0 +1,351 @@ +using Ryujinx.Common.Utilities; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class ConstantFolding + { + public static void RunPass(ResourceManager resourceManager, Operation operation) + { + if (!AreAllSourcesConstant(operation)) + { + return; + } + + switch (operation.Inst) + { + case Instruction.Add: + EvaluateBinary(operation, (x, y) => x + y); + break; + + case Instruction.BitCount: + EvaluateUnary(operation, (x) => BitCount(x)); + break; + + case Instruction.BitwiseAnd: + EvaluateBinary(operation, (x, y) => x & y); + break; + + case Instruction.BitwiseExclusiveOr: + EvaluateBinary(operation, (x, y) => x ^ y); + break; + + case Instruction.BitwiseNot: + EvaluateUnary(operation, (x) => ~x); + break; + + case Instruction.BitwiseOr: + EvaluateBinary(operation, (x, y) => x | y); + break; + + case Instruction.BitfieldExtractS32: + BitfieldExtractS32(operation); + break; + + case Instruction.BitfieldExtractU32: + BitfieldExtractU32(operation); + break; + + case Instruction.Clamp: + EvaluateTernary(operation, (x, y, z) => Math.Clamp(x, y, z)); + break; + + case Instruction.ClampU32: + EvaluateTernary(operation, (x, y, z) => (int)Math.Clamp((uint)x, (uint)y, (uint)z)); + break; + + case Instruction.CompareEqual: + EvaluateBinary(operation, (x, y) => x == y); + break; + + case Instruction.CompareGreater: + EvaluateBinary(operation, (x, y) => x > y); + break; + + case Instruction.CompareGreaterOrEqual: + EvaluateBinary(operation, (x, y) => x >= y); + break; + + case Instruction.CompareGreaterOrEqualU32: + EvaluateBinary(operation, (x, y) => (uint)x >= (uint)y); + break; + + case Instruction.CompareGreaterU32: + EvaluateBinary(operation, (x, y) => (uint)x > (uint)y); + break; + + case Instruction.CompareLess: + EvaluateBinary(operation, (x, y) => x < y); + break; + + case Instruction.CompareLessOrEqual: + EvaluateBinary(operation, (x, y) => x <= y); + break; + + case Instruction.CompareLessOrEqualU32: + EvaluateBinary(operation, (x, y) => (uint)x <= (uint)y); + break; + + case Instruction.CompareLessU32: + EvaluateBinary(operation, (x, y) => (uint)x < (uint)y); + break; + + case Instruction.CompareNotEqual: + EvaluateBinary(operation, (x, y) => x != y); + break; + + case Instruction.Divide: + EvaluateBinary(operation, (x, y) => y != 0 ? x / y : 0); + break; + + case Instruction.FP32 | Instruction.Add: + EvaluateFPBinary(operation, (x, y) => x + y); + break; + + case Instruction.FP32 | Instruction.Clamp: + EvaluateFPTernary(operation, (x, y, z) => Math.Clamp(x, y, z)); + break; + + case Instruction.FP32 | Instruction.CompareEqual: + EvaluateFPBinary(operation, (x, y) => x == y); + break; + + case Instruction.FP32 | Instruction.CompareGreater: + EvaluateFPBinary(operation, (x, y) => x > y); + break; + + case Instruction.FP32 | Instruction.CompareGreaterOrEqual: + EvaluateFPBinary(operation, (x, y) => x >= y); + break; + + case Instruction.FP32 | Instruction.CompareLess: + EvaluateFPBinary(operation, (x, y) => x < y); + break; + + case Instruction.FP32 | Instruction.CompareLessOrEqual: + EvaluateFPBinary(operation, (x, y) => x <= y); + break; + + case Instruction.FP32 | Instruction.CompareNotEqual: + EvaluateFPBinary(operation, (x, y) => x != y); + break; + + case Instruction.FP32 | Instruction.Divide: + EvaluateFPBinary(operation, (x, y) => x / y); + break; + + case Instruction.FP32 | Instruction.Multiply: + EvaluateFPBinary(operation, (x, y) => x * y); + break; + + case Instruction.FP32 | Instruction.Negate: + EvaluateFPUnary(operation, (x) => -x); + break; + + case Instruction.FP32 | Instruction.Subtract: + EvaluateFPBinary(operation, (x, y) => x - y); + break; + + case Instruction.IsNan: + EvaluateFPUnary(operation, (x) => float.IsNaN(x)); + break; + + case Instruction.Load: + if (operation.StorageKind == StorageKind.ConstantBuffer && operation.SourcesCount == 4) + { + int binding = operation.GetSource(0).Value; + int fieldIndex = operation.GetSource(1).Value; + + if (resourceManager.TryGetConstantBufferSlot(binding, out int cbufSlot) && fieldIndex == 0) + { + int vecIndex = operation.GetSource(2).Value; + int elemIndex = operation.GetSource(3).Value; + int cbufOffset = vecIndex * 4 + elemIndex; + + operation.TurnIntoCopy(Cbuf(cbufSlot, cbufOffset)); + } + } + break; + + case Instruction.Maximum: + EvaluateBinary(operation, (x, y) => Math.Max(x, y)); + break; + + case Instruction.MaximumU32: + EvaluateBinary(operation, (x, y) => (int)Math.Max((uint)x, (uint)y)); + break; + + case Instruction.Minimum: + EvaluateBinary(operation, (x, y) => Math.Min(x, y)); + break; + + case Instruction.MinimumU32: + EvaluateBinary(operation, (x, y) => (int)Math.Min((uint)x, (uint)y)); + break; + + case Instruction.Multiply: + EvaluateBinary(operation, (x, y) => x * y); + break; + + case Instruction.Negate: + EvaluateUnary(operation, (x) => -x); + break; + + case Instruction.ShiftLeft: + EvaluateBinary(operation, (x, y) => x << y); + break; + + case Instruction.ShiftRightS32: + EvaluateBinary(operation, (x, y) => x >> y); + break; + + case Instruction.ShiftRightU32: + EvaluateBinary(operation, (x, y) => (int)((uint)x >> y)); + break; + + case Instruction.Subtract: + EvaluateBinary(operation, (x, y) => x - y); + break; + + case Instruction.UnpackHalf2x16: + UnpackHalf2x16(operation); + break; + } + } + + private static bool AreAllSourcesConstant(Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + if (operation.GetSource(index).Type != OperandType.Constant) + { + return false; + } + } + + return true; + } + + private static int BitCount(int value) + { + int count = 0; + + for (int bit = 0; bit < 32; bit++) + { + if (value.Extract(bit)) + { + count++; + } + } + + return count; + } + + private static void BitfieldExtractS32(Operation operation) + { + int value = GetBitfieldExtractValue(operation); + + int shift = 32 - operation.GetSource(2).Value; + + value = (value << shift) >> shift; + + operation.TurnIntoCopy(Const(value)); + } + + private static void BitfieldExtractU32(Operation operation) + { + operation.TurnIntoCopy(Const(GetBitfieldExtractValue(operation))); + } + + private static int GetBitfieldExtractValue(Operation operation) + { + int value = operation.GetSource(0).Value; + int lsb = operation.GetSource(1).Value; + int length = operation.GetSource(2).Value; + + return value.Extract(lsb, length); + } + + private static void UnpackHalf2x16(Operation operation) + { + int value = operation.GetSource(0).Value; + + value = (value >> operation.Index * 16) & 0xffff; + + operation.TurnIntoCopy(ConstF((float)BitConverter.UInt16BitsToHalf((ushort)value))); + } + + private static void EvaluateUnary(Operation operation, Func op) + { + int x = operation.GetSource(0).Value; + + operation.TurnIntoCopy(Const(op(x))); + } + + private static void EvaluateFPUnary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + + operation.TurnIntoCopy(ConstF(op(x))); + } + + private static void EvaluateFPUnary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + + operation.TurnIntoCopy(Const(op(x) ? IrConsts.True : IrConsts.False)); + } + + private static void EvaluateBinary(Operation operation, Func op) + { + int x = operation.GetSource(0).Value; + int y = operation.GetSource(1).Value; + + operation.TurnIntoCopy(Const(op(x, y))); + } + + private static void EvaluateBinary(Operation operation, Func op) + { + int x = operation.GetSource(0).Value; + int y = operation.GetSource(1).Value; + + operation.TurnIntoCopy(Const(op(x, y) ? IrConsts.True : IrConsts.False)); + } + + private static void EvaluateFPBinary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + float y = operation.GetSource(1).AsFloat(); + + operation.TurnIntoCopy(ConstF(op(x, y))); + } + + private static void EvaluateFPBinary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + float y = operation.GetSource(1).AsFloat(); + + operation.TurnIntoCopy(Const(op(x, y) ? IrConsts.True : IrConsts.False)); + } + + private static void EvaluateTernary(Operation operation, Func op) + { + int x = operation.GetSource(0).Value; + int y = operation.GetSource(1).Value; + int z = operation.GetSource(2).Value; + + operation.TurnIntoCopy(Const(op(x, y, z))); + } + + private static void EvaluateFPTernary(Operation operation, Func op) + { + float x = operation.GetSource(0).AsFloat(); + float y = operation.GetSource(1).AsFloat(); + float z = operation.GetSource(2).AsFloat(); + + operation.TurnIntoCopy(ConstF(op(x, y, z))); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/DoubleToFloat.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/DoubleToFloat.cs new file mode 100644 index 00000000..aec95a9c --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/DoubleToFloat.cs @@ -0,0 +1,70 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class DoubleToFloat + { + public static void RunPass(HelperFunctionManager hfm, BasicBlock block) + { + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (node.Value is not Operation) + { + continue; + } + + node = InsertSoftFloat64(hfm, node); + } + } + + private static LinkedListNode InsertSoftFloat64(HelperFunctionManager hfm, LinkedListNode node) + { + Operation operation = (Operation)node.Value; + + if (operation.Inst == Instruction.PackDouble2x32) + { + int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.ConvertDoubleToFloat); + + Operand[] callArgs = new Operand[] { Const(functionId), operation.GetSource(0), operation.GetSource(1) }; + + Operand floatValue = operation.Dest; + + operation.Dest = null; + + LinkedListNode newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, floatValue, callArgs)); + + Utils.DeleteNode(node, operation); + + return newNode; + } + else if (operation.Inst == Instruction.UnpackDouble2x32) + { + int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.ConvertFloatToDouble); + + // TODO: Allow UnpackDouble2x32 to produce two outputs and get rid of "operation.Index". + + Operand resultLow = operation.Index == 0 ? operation.Dest : Local(); + Operand resultHigh = operation.Index == 1 ? operation.Dest : Local(); + + operation.Dest = null; + + Operand[] callArgs = new Operand[] { Const(functionId), operation.GetSource(0), resultLow, resultHigh }; + + LinkedListNode newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, (Operand)null, callArgs)); + + Utils.DeleteNode(node, operation); + + return newNode; + } + else + { + operation.TurnDoubleIntoFloat(); + + return node; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs new file mode 100644 index 00000000..8a730ef7 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs @@ -0,0 +1,1175 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class GlobalToStorage + { + private const int DriverReservedCb = 0; + + enum LsMemoryType + { + Local, + Shared, + } + + private class GtsContext + { + private readonly struct Entry + { + public readonly int FunctionId; + public readonly Instruction Inst; + public readonly StorageKind StorageKind; + public readonly bool IsMultiTarget; + public readonly IReadOnlyList TargetCbs; + + public Entry( + int functionId, + Instruction inst, + StorageKind storageKind, + bool isMultiTarget, + IReadOnlyList targetCbs) + { + FunctionId = functionId; + Inst = inst; + StorageKind = storageKind; + IsMultiTarget = isMultiTarget; + TargetCbs = targetCbs; + } + } + + private readonly struct LsKey : IEquatable + { + public readonly Operand BaseOffset; + public readonly int ConstOffset; + public readonly LsMemoryType Type; + + public LsKey(Operand baseOffset, int constOffset, LsMemoryType type) + { + BaseOffset = baseOffset; + ConstOffset = constOffset; + Type = type; + } + + public override int GetHashCode() + { + return HashCode.Combine(BaseOffset, ConstOffset, Type); + } + + public override bool Equals(object obj) + { + return obj is LsKey other && Equals(other); + } + + public bool Equals(LsKey other) + { + return other.BaseOffset == BaseOffset && other.ConstOffset == ConstOffset && other.Type == Type; + } + } + + private readonly List _entries; + private readonly Dictionary> _sharedEntries; + private readonly HelperFunctionManager _hfm; + + public GtsContext(HelperFunctionManager hfm) + { + _entries = new List(); + _sharedEntries = new Dictionary>(); + _hfm = hfm; + } + + public int AddFunction(Operation baseOp, bool isMultiTarget, IReadOnlyList targetCbs, Function function) + { + int functionId = _hfm.AddFunction(function); + + _entries.Add(new Entry(functionId, baseOp.Inst, baseOp.StorageKind, isMultiTarget, targetCbs)); + + return functionId; + } + + public bool TryGetFunctionId(Operation baseOp, bool isMultiTarget, IReadOnlyList targetCbs, out int functionId) + { + foreach (Entry entry in _entries) + { + if (entry.Inst != baseOp.Inst || + entry.StorageKind != baseOp.StorageKind || + entry.IsMultiTarget != isMultiTarget || + entry.TargetCbs.Count != targetCbs.Count) + { + continue; + } + + bool allEqual = true; + + for (int index = 0; index < targetCbs.Count; index++) + { + if (targetCbs[index] != entry.TargetCbs[index]) + { + allEqual = false; + break; + } + } + + if (allEqual) + { + functionId = entry.FunctionId; + return true; + } + } + + functionId = -1; + return false; + } + + public void AddMemoryTargetCb(LsMemoryType type, Operand baseOffset, int constOffset, uint targetCb, SearchResult result) + { + LsKey key = new(baseOffset, constOffset, type); + + if (!_sharedEntries.TryGetValue(key, out Dictionary targetCbs)) + { + // No entry with this base offset, create a new one. + + targetCbs = new Dictionary() { { targetCb, result } }; + + _sharedEntries.Add(key, targetCbs); + } + else if (targetCbs.TryGetValue(targetCb, out SearchResult existingResult)) + { + // If our entry already exists, but does not match the new result, + // we set the offset to null to indicate there are multiple possible offsets. + // This will be used on the multi-target access that does not need to know the offset. + + if (existingResult.Offset != null && + (existingResult.Offset != result.Offset || + existingResult.ConstOffset != result.ConstOffset)) + { + targetCbs[targetCb] = new SearchResult(result.SbCbSlot, result.SbCbOffset); + } + } + else + { + // An entry for this base offset already exists, but not for the specified + // constant buffer region where the storage buffer base address and size + // comes from. + + targetCbs.Add(targetCb, result); + } + } + + public bool TryGetMemoryTargetCb(LsMemoryType type, Operand baseOffset, int constOffset, out SearchResult result) + { + LsKey key = new(baseOffset, constOffset, type); + + if (_sharedEntries.TryGetValue(key, out Dictionary targetCbs) && targetCbs.Count == 1) + { + SearchResult candidateResult = targetCbs.Values.First(); + + if (candidateResult.Found) + { + result = candidateResult; + + return true; + } + } + + result = default; + + return false; + } + } + + private readonly struct SearchResult + { + public static SearchResult NotFound => new(-1, 0); + public bool Found => SbCbSlot != -1; + public int SbCbSlot { get; } + public int SbCbOffset { get; } + public Operand Offset { get; } + public int ConstOffset { get; } + + public SearchResult(int sbCbSlot, int sbCbOffset) + { + SbCbSlot = sbCbSlot; + SbCbOffset = sbCbOffset; + } + + public SearchResult(int sbCbSlot, int sbCbOffset, Operand offset, int constOffset = 0) + { + SbCbSlot = sbCbSlot; + SbCbOffset = sbCbOffset; + Offset = offset; + ConstOffset = constOffset; + } + } + + public static void RunPass( + HelperFunctionManager hfm, + BasicBlock[] blocks, + ResourceManager resourceManager, + IGpuAccessor gpuAccessor, + TargetLanguage targetLanguage) + { + GtsContext gtsContext = new(hfm); + + foreach (BasicBlock block in blocks) + { + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (node.Value is not Operation operation) + { + continue; + } + + if (IsGlobalMemory(operation.StorageKind)) + { + LinkedListNode nextNode = ReplaceGlobalMemoryWithStorage( + gtsContext, + resourceManager, + gpuAccessor, + targetLanguage, + block, + node); + + if (nextNode == null) + { + // The returned value being null means that the global memory replacement failed, + // so we just make loads read 0 and stores do nothing. + + gpuAccessor.Log($"Failed to reserve storage buffer for global memory operation \"{operation.Inst}\"."); + + if (operation.Dest != null) + { + operation.TurnIntoCopy(Const(0)); + } + else + { + Utils.DeleteNode(node, operation); + } + } + else + { + node = nextNode; + } + } + else if (operation.Inst == Instruction.Store && + (operation.StorageKind == StorageKind.SharedMemory || + operation.StorageKind == StorageKind.LocalMemory)) + { + // The NVIDIA compiler can sometimes use shared or local memory as temporary + // storage to place the base address and size on, so we need + // to be able to find such information stored in memory too. + + if (TryGetMemoryOffsets(operation, out LsMemoryType type, out Operand baseOffset, out int constOffset)) + { + Operand value = operation.GetSource(operation.SourcesCount - 1); + + var result = FindUniqueBaseAddressCb(gtsContext, block, value, needsOffset: false); + if (result.Found) + { + uint targetCb = PackCbSlotAndOffset(result.SbCbSlot, result.SbCbOffset); + gtsContext.AddMemoryTargetCb(type, baseOffset, constOffset, targetCb, result); + } + } + } + } + } + } + + private static bool IsGlobalMemory(StorageKind storageKind) + { + return storageKind == StorageKind.GlobalMemory || + storageKind == StorageKind.GlobalMemoryS8 || + storageKind == StorageKind.GlobalMemoryS16 || + storageKind == StorageKind.GlobalMemoryU8 || + storageKind == StorageKind.GlobalMemoryU16; + } + + private static bool IsSmallInt(StorageKind storageKind) + { + return storageKind == StorageKind.GlobalMemoryS8 || + storageKind == StorageKind.GlobalMemoryS16 || + storageKind == StorageKind.GlobalMemoryU8 || + storageKind == StorageKind.GlobalMemoryU16; + } + + private static LinkedListNode ReplaceGlobalMemoryWithStorage( + GtsContext gtsContext, + ResourceManager resourceManager, + IGpuAccessor gpuAccessor, + TargetLanguage targetLanguage, + BasicBlock block, + LinkedListNode node) + { + Operation operation = node.Value as Operation; + Operand globalAddress = operation.GetSource(0); + SearchResult result = FindUniqueBaseAddressCb(gtsContext, block, globalAddress, needsOffset: true); + + if (result.Found) + { + // We found the storage buffer that is being accessed. + // There are two possible paths here, if the operation is simple enough, + // we just generate the storage access code inline. + // Otherwise, we generate a function call (and the function if necessary). + + Operand offset = result.Offset; + + bool storageUnaligned = gpuAccessor.QueryHasUnalignedStorageBuffer(); + + if (storageUnaligned) + { + Operand baseAddress = Cbuf(result.SbCbSlot, result.SbCbOffset); + + Operand baseAddressMasked = Local(); + Operand hostOffset = Local(); + + int alignment = gpuAccessor.QueryHostStorageBufferOffsetAlignment(); + + Operation maskOp = new(Instruction.BitwiseAnd, baseAddressMasked, baseAddress, Const(-alignment)); + Operation subOp = new(Instruction.Subtract, hostOffset, globalAddress, baseAddressMasked); + + node.List.AddBefore(node, maskOp); + node.List.AddBefore(node, subOp); + + offset = hostOffset; + } + else if (result.ConstOffset != 0) + { + Operand newOffset = Local(); + + Operation addOp = new(Instruction.Add, newOffset, offset, Const(result.ConstOffset)); + + node.List.AddBefore(node, addOp); + + offset = newOffset; + } + + if (CanUseInlineStorageOp(operation, targetLanguage)) + { + return GenerateInlineStorageOp(resourceManager, node, operation, offset, result); + } + else + { + if (!TryGenerateSingleTargetStorageOp( + gtsContext, + resourceManager, + targetLanguage, + operation, + result, + out int functionId)) + { + return null; + } + + return GenerateCallStorageOp(node, operation, offset, functionId); + } + } + else + { + // Failed to find the storage buffer directly. + // Try to walk through Phi chains and find all possible constant buffers where + // the base address might be stored. + // Generate a helper function that will check all possible storage buffers and use the right one. + + if (!TryGenerateMultiTargetStorageOp( + gtsContext, + resourceManager, + gpuAccessor, + targetLanguage, + block, + operation, + out int functionId)) + { + return null; + } + + return GenerateCallStorageOp(node, operation, null, functionId); + } + } + + private static bool CanUseInlineStorageOp(Operation operation, TargetLanguage targetLanguage) + { + if (operation.StorageKind != StorageKind.GlobalMemory) + { + return false; + } + + return (operation.Inst != Instruction.AtomicMaxS32 && + operation.Inst != Instruction.AtomicMinS32) || targetLanguage == TargetLanguage.Spirv; + } + + private static LinkedListNode GenerateInlineStorageOp( + ResourceManager resourceManager, + LinkedListNode node, + Operation operation, + Operand offset, + SearchResult result) + { + bool isStore = operation.Inst == Instruction.Store || operation.Inst.IsAtomic(); + if (!resourceManager.TryGetStorageBufferBinding(result.SbCbSlot, result.SbCbOffset, isStore, out int binding)) + { + return null; + } + + Operand wordOffset = Local(); + + Operand[] sources; + + if (operation.Inst == Instruction.AtomicCompareAndSwap) + { + sources = new[] + { + Const(binding), + Const(0), + wordOffset, + operation.GetSource(operation.SourcesCount - 2), + operation.GetSource(operation.SourcesCount - 1), + }; + } + else if (isStore) + { + sources = new[] { Const(binding), Const(0), wordOffset, operation.GetSource(operation.SourcesCount - 1) }; + } + else + { + sources = new[] { Const(binding), Const(0), wordOffset }; + } + + Operation shiftOp = new(Instruction.ShiftRightU32, wordOffset, offset, Const(2)); + Operation storageOp = new(operation.Inst, StorageKind.StorageBuffer, operation.Dest, sources); + + node.List.AddBefore(node, shiftOp); + LinkedListNode newNode = node.List.AddBefore(node, storageOp); + + Utils.DeleteNode(node, operation); + + return newNode; + } + + private static LinkedListNode GenerateCallStorageOp(LinkedListNode node, Operation operation, Operand offset, int functionId) + { + // Generate call to a helper function that will perform the storage buffer operation. + + Operand[] sources = new Operand[operation.SourcesCount - 1 + (offset == null ? 2 : 1)]; + + sources[0] = Const(functionId); + + if (offset != null) + { + // If the offset was supplised, we use that and skip the global address. + + sources[1] = offset; + + for (int srcIndex = 2; srcIndex < operation.SourcesCount; srcIndex++) + { + sources[srcIndex] = operation.GetSource(srcIndex); + } + } + else + { + // Use the 64-bit global address which is split in 2 32-bit arguments. + + for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++) + { + sources[srcIndex + 1] = operation.GetSource(srcIndex); + } + } + + bool returnsValue = operation.Dest != null; + Operand returnValue = returnsValue ? Local() : null; + + Operation callOp = new(Instruction.Call, returnValue, sources); + + LinkedListNode newNode = node.List.AddBefore(node, callOp); + + if (returnsValue) + { + operation.TurnIntoCopy(returnValue); + + return node; + } + else + { + Utils.DeleteNode(node, operation); + + return newNode; + } + } + + private static bool TryGenerateSingleTargetStorageOp( + GtsContext gtsContext, + ResourceManager resourceManager, + TargetLanguage targetLanguage, + Operation operation, + SearchResult result, + out int functionId) + { + List targetCbs = new() { PackCbSlotAndOffset(result.SbCbSlot, result.SbCbOffset) }; + + if (gtsContext.TryGetFunctionId(operation, isMultiTarget: false, targetCbs, out functionId)) + { + return true; + } + + int inArgumentsCount = 1; + + if (operation.Inst == Instruction.AtomicCompareAndSwap) + { + inArgumentsCount = 3; + } + else if (operation.Inst == Instruction.Store || operation.Inst.IsAtomic()) + { + inArgumentsCount = 2; + } + + EmitterContext context = new(); + + Operand offset = Argument(0); + Operand compare = null; + Operand value = null; + + if (inArgumentsCount == 3) + { + compare = Argument(1); + value = Argument(2); + } + else if (inArgumentsCount == 2) + { + value = Argument(1); + } + + if (!TryGenerateStorageOp( + resourceManager, + targetLanguage, + context, + operation.Inst, + operation.StorageKind, + offset, + compare, + value, + result, + out Operand resultValue)) + { + functionId = 0; + return false; + } + + bool returnsValue = resultValue != null; + + if (returnsValue) + { + context.Return(resultValue); + } + else + { + context.Return(); + } + + string functionName = GetFunctionName(operation, isMultiTarget: false, targetCbs); + + Function function = new( + ControlFlowGraph.Create(context.GetOperations()).Blocks, + functionName, + returnsValue, + inArgumentsCount, + 0); + + functionId = gtsContext.AddFunction(operation, isMultiTarget: false, targetCbs, function); + + return true; + } + + private static bool TryGenerateMultiTargetStorageOp( + GtsContext gtsContext, + ResourceManager resourceManager, + IGpuAccessor gpuAccessor, + TargetLanguage targetLanguage, + BasicBlock block, + Operation operation, + out int functionId) + { + Queue phis = new(); + HashSet visited = new(); + List targetCbs = new(); + + Operand globalAddress = operation.GetSource(0); + + if (globalAddress.AsgOp is Operation addOp && addOp.Inst == Instruction.Add) + { + Operand src1 = addOp.GetSource(0); + Operand src2 = addOp.GetSource(1); + + if (src1.Type == OperandType.Constant && src2.Type == OperandType.LocalVariable) + { + globalAddress = src2; + } + else if (src1.Type == OperandType.LocalVariable && src2.Type == OperandType.Constant) + { + globalAddress = src1; + } + } + + if (globalAddress.AsgOp is PhiNode phi && visited.Add(phi)) + { + phis.Enqueue(phi); + } + else + { + SearchResult result = FindUniqueBaseAddressCb(gtsContext, block, operation.GetSource(0), needsOffset: false); + + if (result.Found) + { + targetCbs.Add(PackCbSlotAndOffset(result.SbCbSlot, result.SbCbOffset)); + } + } + + while (phis.TryDequeue(out phi)) + { + for (int srcIndex = 0; srcIndex < phi.SourcesCount; srcIndex++) + { + BasicBlock phiBlock = phi.GetBlock(srcIndex); + Operand phiSource = phi.GetSource(srcIndex); + + SearchResult result = FindUniqueBaseAddressCb(gtsContext, phiBlock, phiSource, needsOffset: false); + + if (result.Found) + { + uint targetCb = PackCbSlotAndOffset(result.SbCbSlot, result.SbCbOffset); + + if (!targetCbs.Contains(targetCb)) + { + targetCbs.Add(targetCb); + } + } + else if (phiSource.AsgOp is PhiNode phi2 && visited.Add(phi2)) + { + phis.Enqueue(phi2); + } + } + } + + targetCbs.Sort(); + + if (targetCbs.Count == 0) + { + gpuAccessor.Log($"Failed to find storage buffer for global memory operation \"{operation.Inst}\"."); + } + + if (gtsContext.TryGetFunctionId(operation, isMultiTarget: true, targetCbs, out functionId)) + { + return true; + } + + int inArgumentsCount = 2; + + if (operation.Inst == Instruction.AtomicCompareAndSwap) + { + inArgumentsCount = 4; + } + else if (operation.Inst == Instruction.Store || operation.Inst.IsAtomic()) + { + inArgumentsCount = 3; + } + + EmitterContext context = new(); + + Operand globalAddressLow = Argument(0); + Operand globalAddressHigh = Argument(1); + + foreach (uint targetCb in targetCbs) + { + (int sbCbSlot, int sbCbOffset) = UnpackCbSlotAndOffset(targetCb); + + Operand baseAddrLow = Cbuf(sbCbSlot, sbCbOffset); + Operand baseAddrHigh = Cbuf(sbCbSlot, sbCbOffset + 1); + Operand size = Cbuf(sbCbSlot, sbCbOffset + 2); + + Operand offset = context.ISubtract(globalAddressLow, baseAddrLow); + Operand borrow = context.ICompareLessUnsigned(globalAddressLow, baseAddrLow); + + Operand inRangeLow = context.ICompareLessUnsigned(offset, size); + + Operand addrHighBorrowed = context.IAdd(globalAddressHigh, borrow); + + Operand inRangeHigh = context.ICompareEqual(addrHighBorrowed, baseAddrHigh); + + Operand inRange = context.BitwiseAnd(inRangeLow, inRangeHigh); + + Operand lblSkip = Label(); + context.BranchIfFalse(lblSkip, inRange); + + Operand compare = null; + Operand value = null; + + if (inArgumentsCount == 4) + { + compare = Argument(2); + value = Argument(3); + } + else if (inArgumentsCount == 3) + { + value = Argument(2); + } + + SearchResult result = new(sbCbSlot, sbCbOffset); + + int alignment = gpuAccessor.QueryHostStorageBufferOffsetAlignment(); + + Operand baseAddressMasked = context.BitwiseAnd(baseAddrLow, Const(-alignment)); + Operand hostOffset = context.ISubtract(globalAddressLow, baseAddressMasked); + + if (!TryGenerateStorageOp( + resourceManager, + targetLanguage, + context, + operation.Inst, + operation.StorageKind, + hostOffset, + compare, + value, + result, + out Operand resultValue)) + { + functionId = 0; + return false; + } + + if (resultValue != null) + { + context.Return(resultValue); + } + else + { + context.Return(); + } + + context.MarkLabel(lblSkip); + } + + bool returnsValue = operation.Dest != null; + + if (returnsValue) + { + context.Return(Const(0)); + } + else + { + context.Return(); + } + + string functionName = GetFunctionName(operation, isMultiTarget: true, targetCbs); + + Function function = new( + ControlFlowGraph.Create(context.GetOperations()).Blocks, + functionName, + returnsValue, + inArgumentsCount, + 0); + + functionId = gtsContext.AddFunction(operation, isMultiTarget: true, targetCbs, function); + + return true; + } + + private static uint PackCbSlotAndOffset(int cbSlot, int cbOffset) + { + return (uint)((ushort)cbSlot | ((ushort)cbOffset << 16)); + } + + private static (int, int) UnpackCbSlotAndOffset(uint packed) + { + return ((ushort)packed, (ushort)(packed >> 16)); + } + + private static string GetFunctionName(Operation baseOp, bool isMultiTarget, IReadOnlyList targetCbs) + { + StringBuilder nameBuilder = new(); + nameBuilder.Append(baseOp.Inst.ToString()); + + nameBuilder.Append(baseOp.StorageKind switch + { + StorageKind.GlobalMemoryS8 => "S8", + StorageKind.GlobalMemoryS16 => "S16", + StorageKind.GlobalMemoryU8 => "U8", + StorageKind.GlobalMemoryU16 => "U16", + _ => string.Empty, + }); + + if (isMultiTarget) + { + nameBuilder.Append("Multi"); + } + + foreach (uint targetCb in targetCbs) + { + (int sbCbSlot, int sbCbOffset) = UnpackCbSlotAndOffset(targetCb); + + nameBuilder.Append($"_c{sbCbSlot}o{sbCbOffset}"); + } + + return nameBuilder.ToString(); + } + + private static bool TryGenerateStorageOp( + ResourceManager resourceManager, + TargetLanguage targetLanguage, + EmitterContext context, + Instruction inst, + StorageKind storageKind, + Operand offset, + Operand compare, + Operand value, + SearchResult result, + out Operand resultValue) + { + resultValue = null; + bool isStore = inst.IsAtomic() || inst == Instruction.Store; + + if (!resourceManager.TryGetStorageBufferBinding(result.SbCbSlot, result.SbCbOffset, isStore, out int binding)) + { + return false; + } + + Operand wordOffset = context.ShiftRightU32(offset, Const(2)); + + if (inst.IsAtomic()) + { + if (IsSmallInt(storageKind)) + { + throw new NotImplementedException(); + } + + switch (inst) + { + case Instruction.AtomicAdd: + resultValue = context.AtomicAdd(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value); + break; + case Instruction.AtomicAnd: + resultValue = context.AtomicAnd(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value); + break; + case Instruction.AtomicCompareAndSwap: + resultValue = context.AtomicCompareAndSwap(StorageKind.StorageBuffer, binding, Const(0), wordOffset, compare, value); + break; + case Instruction.AtomicMaxS32: + if (targetLanguage == TargetLanguage.Spirv) + { + resultValue = context.AtomicMaxS32(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value); + } + else + { + resultValue = GenerateAtomicCasLoop(context, wordOffset, binding, (memValue) => + { + return context.IMaximumS32(memValue, value); + }); + } + break; + case Instruction.AtomicMaxU32: + resultValue = context.AtomicMaxU32(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value); + break; + case Instruction.AtomicMinS32: + if (targetLanguage == TargetLanguage.Spirv) + { + resultValue = context.AtomicMinS32(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value); + } + else + { + resultValue = GenerateAtomicCasLoop(context, wordOffset, binding, (memValue) => + { + return context.IMinimumS32(memValue, value); + }); + } + break; + case Instruction.AtomicMinU32: + resultValue = context.AtomicMinU32(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value); + break; + case Instruction.AtomicOr: + resultValue = context.AtomicOr(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value); + break; + case Instruction.AtomicSwap: + resultValue = context.AtomicSwap(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value); + break; + case Instruction.AtomicXor: + resultValue = context.AtomicXor(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value); + break; + } + } + else if (inst == Instruction.Store) + { + int bitSize = storageKind switch + { + StorageKind.GlobalMemoryS8 or + StorageKind.GlobalMemoryU8 => 8, + StorageKind.GlobalMemoryS16 or + StorageKind.GlobalMemoryU16 => 16, + _ => 32, + }; + + if (bitSize < 32) + { + Operand bitOffset = HelperFunctionManager.GetBitOffset(context, offset); + + GenerateAtomicCasLoop(context, wordOffset, binding, (memValue) => + { + return context.BitfieldInsert(memValue, value, bitOffset, Const(bitSize)); + }); + } + else + { + context.Store(StorageKind.StorageBuffer, binding, Const(0), wordOffset, value); + } + } + else + { + value = context.Load(StorageKind.StorageBuffer, binding, Const(0), wordOffset); + + if (IsSmallInt(storageKind)) + { + Operand bitOffset = HelperFunctionManager.GetBitOffset(context, offset); + + switch (storageKind) + { + case StorageKind.GlobalMemoryS8: + value = context.ShiftRightS32(value, bitOffset); + value = context.BitfieldExtractS32(value, Const(0), Const(8)); + break; + case StorageKind.GlobalMemoryS16: + value = context.ShiftRightS32(value, bitOffset); + value = context.BitfieldExtractS32(value, Const(0), Const(16)); + break; + case StorageKind.GlobalMemoryU8: + value = context.ShiftRightU32(value, bitOffset); + value = context.BitwiseAnd(value, Const(byte.MaxValue)); + break; + case StorageKind.GlobalMemoryU16: + value = context.ShiftRightU32(value, bitOffset); + value = context.BitwiseAnd(value, Const(ushort.MaxValue)); + break; + } + } + + resultValue = value; + } + + return true; + } + + private static Operand GenerateAtomicCasLoop(EmitterContext context, Operand wordOffset, int binding, Func opCallback) + { + Operand lblLoopHead = Label(); + + context.MarkLabel(lblLoopHead); + + Operand oldValue = context.Load(StorageKind.StorageBuffer, binding, Const(0), wordOffset); + Operand newValue = opCallback(oldValue); + + Operand casResult = context.AtomicCompareAndSwap( + StorageKind.StorageBuffer, + binding, + Const(0), + wordOffset, + oldValue, + newValue); + + Operand casFail = context.ICompareNotEqual(casResult, oldValue); + + context.BranchIfTrue(lblLoopHead, casFail); + + return oldValue; + } + + private static SearchResult FindUniqueBaseAddressCb(GtsContext gtsContext, BasicBlock block, Operand globalAddress, bool needsOffset) + { + globalAddress = Utils.FindLastOperation(globalAddress, block); + + if (globalAddress.Type == OperandType.ConstantBuffer) + { + return GetBaseAddressCbWithOffset(globalAddress, Const(0), 0); + } + + Operation operation = globalAddress.AsgOp as Operation; + + if (operation == null || operation.Inst != Instruction.Add) + { + return FindBaseAddressCbFromMemory(gtsContext, operation, 0, needsOffset); + } + + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + int constOffset = 0; + + if ((src1.Type == OperandType.LocalVariable && src2.Type == OperandType.Constant) || + (src2.Type == OperandType.LocalVariable && src1.Type == OperandType.Constant)) + { + Operand baseAddr; + Operand offset; + + if (src1.Type == OperandType.LocalVariable) + { + baseAddr = Utils.FindLastOperation(src1, block); + offset = src2; + } + else + { + baseAddr = Utils.FindLastOperation(src2, block); + offset = src1; + } + + var result = GetBaseAddressCbWithOffset(baseAddr, offset, 0); + if (result.Found) + { + return result; + } + + constOffset = offset.Value; + operation = baseAddr.AsgOp as Operation; + + if (operation == null || operation.Inst != Instruction.Add) + { + return FindBaseAddressCbFromMemory(gtsContext, operation, constOffset, needsOffset); + } + } + + src1 = operation.GetSource(0); + src2 = operation.GetSource(1); + + // If we have two possible results, we give preference to the ones from + // the driver reserved constant buffer, as those are the ones that + // contains the base address. + + // If both are constant buffer, give preference to the second operand, + // because constant buffer are always encoded as the second operand, + // so the second operand will always be the one from the last instruction. + + if (src1.Type != OperandType.ConstantBuffer || + (src1.Type == OperandType.ConstantBuffer && src2.Type == OperandType.ConstantBuffer) || + (src2.Type == OperandType.ConstantBuffer && src2.GetCbufSlot() == DriverReservedCb)) + { + return GetBaseAddressCbWithOffset(src2, src1, constOffset); + } + + return GetBaseAddressCbWithOffset(src1, src2, constOffset); + } + + private static SearchResult FindBaseAddressCbFromMemory(GtsContext gtsContext, Operation operation, int constOffset, bool needsOffset) + { + if (operation != null) + { + if (TryGetMemoryOffsets(operation, out LsMemoryType type, out Operand bo, out int co) && + gtsContext.TryGetMemoryTargetCb(type, bo, co, out SearchResult result) && + (result.Offset != null || !needsOffset)) + { + if (constOffset != 0) + { + return new SearchResult( + result.SbCbSlot, + result.SbCbOffset, + result.Offset, + result.ConstOffset + constOffset); + } + + return result; + } + } + + return SearchResult.NotFound; + } + + private static SearchResult GetBaseAddressCbWithOffset(Operand baseAddress, Operand offset, int constOffset) + { + if (baseAddress.Type == OperandType.ConstantBuffer) + { + int sbCbSlot = baseAddress.GetCbufSlot(); + int sbCbOffset = baseAddress.GetCbufOffset(); + + // We require the offset to be aligned to 1 word (64 bits), + // since the address size is 64-bit and the GPU only supports aligned memory access. + if ((sbCbOffset & 1) == 0) + { + return new SearchResult(sbCbSlot, sbCbOffset, offset, constOffset); + } + } + + return SearchResult.NotFound; + } + + private static bool TryGetMemoryOffsets(Operation operation, out LsMemoryType type, out Operand baseOffset, out int constOffset) + { + baseOffset = null; + + if (operation.Inst == Instruction.Load || operation.Inst == Instruction.Store) + { + if (operation.StorageKind == StorageKind.SharedMemory) + { + type = LsMemoryType.Shared; + return TryGetSharedMemoryOffsets(operation, out baseOffset, out constOffset); + } + else if (operation.StorageKind == StorageKind.LocalMemory) + { + type = LsMemoryType.Local; + return TryGetLocalMemoryOffset(operation, out constOffset); + } + } + + type = default; + constOffset = 0; + return false; + } + + private static bool TryGetSharedMemoryOffsets(Operation operation, out Operand baseOffset, out int constOffset) + { + baseOffset = null; + constOffset = 0; + + // The byte offset is right shifted by 2 to get the 32-bit word offset, + // so we want to get the byte offset back, since each one of those word + // offsets are a new "local variable" which will not match. + + if (operation.GetSource(1).AsgOp is Operation shiftRightOp && + shiftRightOp.Inst == Instruction.ShiftRightU32 && + shiftRightOp.GetSource(1).Type == OperandType.Constant && + shiftRightOp.GetSource(1).Value == 2) + { + baseOffset = shiftRightOp.GetSource(0); + } + + // Check if we have a constant offset being added to the base offset. + + if (baseOffset?.AsgOp is Operation addOp && addOp.Inst == Instruction.Add) + { + Operand src1 = addOp.GetSource(0); + Operand src2 = addOp.GetSource(1); + + if (src1.Type == OperandType.Constant && src2.Type == OperandType.LocalVariable) + { + constOffset = src1.Value; + baseOffset = src2; + } + else if (src1.Type == OperandType.LocalVariable && src2.Type == OperandType.Constant) + { + baseOffset = src1; + constOffset = src2.Value; + } + } + + return baseOffset != null && baseOffset.Type == OperandType.LocalVariable; + } + + private static bool TryGetLocalMemoryOffset(Operation operation, out int constOffset) + { + Operand offset = operation.GetSource(1); + + if (offset.Type == OperandType.Constant) + { + constOffset = offset.Value; + return true; + } + + constOffset = 0; + return false; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs new file mode 100644 index 00000000..1be7c5c5 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs @@ -0,0 +1,453 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class Optimizer + { + public static void RunPass(TransformContext context) + { + RunOptimizationPasses(context.Blocks, context.ResourceManager); + + // TODO: Some of those are not optimizations and shouldn't be here. + + GlobalToStorage.RunPass(context.Hfm, context.Blocks, context.ResourceManager, context.GpuAccessor, context.TargetLanguage); + + bool hostSupportsShaderFloat64 = context.GpuAccessor.QueryHostSupportsShaderFloat64(); + + // Those passes are looking for specific patterns and only needs to run once. + for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++) + { + if (context.TargetApi == TargetApi.OpenGL) + { + BindlessToArray.RunPassOgl(context.Blocks[blkIndex], context.ResourceManager); + } + else + { + BindlessToArray.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor); + } + + BindlessElimination.RunPass(context.Blocks[blkIndex], context.ResourceManager, context.GpuAccessor); + + // FragmentCoord only exists on fragment shaders, so we don't need to check other stages. + if (context.Stage == ShaderStage.Fragment) + { + EliminateMultiplyByFragmentCoordW(context.Blocks[blkIndex]); + } + + // If the host does not support double operations, we need to turn them into float operations. + if (!hostSupportsShaderFloat64) + { + DoubleToFloat.RunPass(context.Hfm, context.Blocks[blkIndex]); + } + } + + // Run optimizations one last time to remove any code that is now optimizable after above passes. + RunOptimizationPasses(context.Blocks, context.ResourceManager); + } + + private static void RunOptimizationPasses(BasicBlock[] blocks, ResourceManager resourceManager) + { + bool modified; + + do + { + modified = false; + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + LinkedListNode node = block.Operations.First; + + while (node != null) + { + LinkedListNode nextNode = node.Next; + + bool isUnused = IsUnused(node.Value); + + if (node.Value is not Operation operation || isUnused) + { + if (node.Value is PhiNode phi && !isUnused) + { + isUnused = PropagatePhi(phi); + } + + if (isUnused) + { + RemoveNode(block, node); + + modified = true; + } + + node = nextNode; + + continue; + } + + ConstantFolding.RunPass(resourceManager, operation); + Simplification.RunPass(operation); + + if (DestIsLocalVar(operation)) + { + if (operation.Inst == Instruction.Copy) + { + PropagateCopy(operation); + + RemoveNode(block, node); + + modified = true; + } + else if ((operation.Inst == Instruction.PackHalf2x16 && PropagatePack(operation)) || + (operation.Inst == Instruction.ShuffleXor && MatchDdxOrDdy(operation))) + { + if (DestHasNoUses(operation)) + { + RemoveNode(block, node); + } + + modified = true; + } + } + + node = nextNode; + } + + if (BranchElimination.RunPass(block)) + { + RemoveNode(block, block.Operations.Last); + + modified = true; + } + } + } + while (modified); + } + + private static void PropagateCopy(Operation copyOp) + { + // Propagate copy source operand to all uses of + // the destination operand. + + Operand dest = copyOp.Dest; + Operand src = copyOp.GetSource(0); + + INode[] uses = dest.UseOps.ToArray(); + + foreach (INode useNode in uses) + { + for (int index = 0; index < useNode.SourcesCount; index++) + { + if (useNode.GetSource(index) == dest) + { + useNode.SetSource(index, src); + } + } + } + } + + private static bool PropagatePhi(PhiNode phi) + { + // If all phi sources are the same, we can propagate it and remove the phi. + + if (!Utils.AreAllSourcesTheSameOperand(phi)) + { + return false; + } + + // All sources are equal, we can propagate the value. + + Operand firstSrc = phi.GetSource(0); + Operand dest = phi.Dest; + + INode[] uses = dest.UseOps.ToArray(); + + foreach (INode useNode in uses) + { + for (int index = 0; index < useNode.SourcesCount; index++) + { + if (useNode.GetSource(index) == dest) + { + useNode.SetSource(index, firstSrc); + } + } + } + + return true; + } + + private static bool PropagatePack(Operation packOp) + { + // Propagate pack source operands to uses by unpack + // instruction. The source depends on the unpack instruction. + bool modified = false; + + Operand dest = packOp.Dest; + Operand src0 = packOp.GetSource(0); + Operand src1 = packOp.GetSource(1); + + INode[] uses = dest.UseOps.ToArray(); + + foreach (INode useNode in uses) + { + if (useNode is not Operation operation || operation.Inst != Instruction.UnpackHalf2x16) + { + continue; + } + + if (operation.GetSource(0) == dest) + { + operation.TurnIntoCopy(operation.Index == 1 ? src1 : src0); + + modified = true; + } + } + + return modified; + } + + public static bool MatchDdxOrDdy(Operation operation) + { + // It's assumed that "operation.Inst" is ShuffleXor, + // that should be checked before calling this method. + Debug.Assert(operation.Inst == Instruction.ShuffleXor); + + bool modified = false; + + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + if (src2.Type != OperandType.Constant || (src2.Value != 1 && src2.Value != 2)) + { + return false; + } + + if (src3.Type != OperandType.Constant || src3.Value != 0x1c03) + { + return false; + } + + bool isDdy = src2.Value == 2; + bool isDdx = !isDdy; + + // We can replace any use by a FSWZADD with DDX/DDY, when + // the following conditions are true: + // - The mask should be 0b10100101 for DDY, or 0b10011001 for DDX. + // - The first source operand must be the shuffle output. + // - The second source operand must be the shuffle first source operand. + INode[] uses = operation.Dest.UseOps.ToArray(); + + foreach (INode use in uses) + { + if (use is not Operation test) + { + continue; + } + + if (use is not Operation useOp || useOp.Inst != Instruction.SwizzleAdd) + { + continue; + } + + Operand fswzaddSrc1 = useOp.GetSource(0); + Operand fswzaddSrc2 = useOp.GetSource(1); + Operand fswzaddSrc3 = useOp.GetSource(2); + + if (fswzaddSrc1 != operation.Dest) + { + continue; + } + + if (fswzaddSrc2 != operation.GetSource(0)) + { + continue; + } + + if (fswzaddSrc3.Type != OperandType.Constant) + { + continue; + } + + int mask = fswzaddSrc3.Value; + + if ((isDdx && mask != 0b10011001) || + (isDdy && mask != 0b10100101)) + { + continue; + } + + useOp.TurnInto(isDdx ? Instruction.Ddx : Instruction.Ddy, fswzaddSrc2); + + modified = true; + } + + return modified; + } + + private static void EliminateMultiplyByFragmentCoordW(BasicBlock block) + { + foreach (INode node in block.Operations) + { + if (node is Operation operation) + { + EliminateMultiplyByFragmentCoordW(operation); + } + } + } + + private static void EliminateMultiplyByFragmentCoordW(Operation operation) + { + // We're looking for the pattern: + // y = x * gl_FragCoord.w + // v = y * (1.0 / gl_FragCoord.w) + // Then we transform it into: + // v = x + // This pattern is common on fragment shaders due to the way how perspective correction is done. + + // We are expecting a multiplication by the reciprocal of gl_FragCoord.w. + if (operation.Inst != (Instruction.FP32 | Instruction.Multiply)) + { + return; + } + + Operand lhs = operation.GetSource(0); + Operand rhs = operation.GetSource(1); + + // Check LHS of the main multiplication operation. We expect an input being multiplied by gl_FragCoord.w. + if (lhs.AsgOp is not Operation attrMulOp || attrMulOp.Inst != (Instruction.FP32 | Instruction.Multiply)) + { + return; + } + + Operand attrMulLhs = attrMulOp.GetSource(0); + Operand attrMulRhs = attrMulOp.GetSource(1); + + // LHS should be any input, RHS should be exactly gl_FragCoord.w. + if (!Utils.IsInputLoad(attrMulLhs.AsgOp) || !Utils.IsInputLoad(attrMulRhs.AsgOp, IoVariable.FragmentCoord, 3)) + { + return; + } + + // RHS of the main multiplication should be a reciprocal operation (1.0 / x). + if (rhs.AsgOp is not Operation reciprocalOp || reciprocalOp.Inst != (Instruction.FP32 | Instruction.Divide)) + { + return; + } + + Operand reciprocalLhs = reciprocalOp.GetSource(0); + Operand reciprocalRhs = reciprocalOp.GetSource(1); + + // Check if the divisor is a constant equal to 1.0. + if (reciprocalLhs.Type != OperandType.Constant || reciprocalLhs.AsFloat() != 1.0f) + { + return; + } + + // Check if the dividend is gl_FragCoord.w. + if (!Utils.IsInputLoad(reciprocalRhs.AsgOp, IoVariable.FragmentCoord, 3)) + { + return; + } + + // If everything matches, we can replace the operation with the input load result. + operation.TurnIntoCopy(attrMulLhs); + } + + private static void RemoveNode(BasicBlock block, LinkedListNode llNode) + { + // Remove a node from the nodes list, and also remove itself + // from all the use lists on the operands that this node uses. + block.Operations.Remove(llNode); + + Queue nodes = new(); + + nodes.Enqueue(llNode.Value); + + while (nodes.TryDequeue(out INode node)) + { + for (int index = 0; index < node.SourcesCount; index++) + { + Operand src = node.GetSource(index); + + if (src.Type != OperandType.LocalVariable) + { + continue; + } + + if (src.UseOps.Remove(node) && src.UseOps.Count == 0) + { + Debug.Assert(src.AsgOp != null); + nodes.Enqueue(src.AsgOp); + } + } + } + } + + private static bool IsUnused(INode node) + { + return !HasSideEffects(node) && DestIsLocalVar(node) && DestHasNoUses(node); + } + + private static bool HasSideEffects(INode node) + { + if (node is Operation operation) + { + switch (operation.Inst & Instruction.Mask) + { + case Instruction.AtomicAdd: + case Instruction.AtomicAnd: + case Instruction.AtomicCompareAndSwap: + case Instruction.AtomicMaxS32: + case Instruction.AtomicMaxU32: + case Instruction.AtomicMinS32: + case Instruction.AtomicMinU32: + case Instruction.AtomicOr: + case Instruction.AtomicSwap: + case Instruction.AtomicXor: + case Instruction.Call: + case Instruction.ImageAtomic: + return true; + } + } + + return false; + } + + private static bool DestIsLocalVar(INode node) + { + if (node.DestsCount == 0) + { + return false; + } + + for (int index = 0; index < node.DestsCount; index++) + { + Operand dest = node.GetDest(index); + + if (dest != null && dest.Type != OperandType.LocalVariable) + { + return false; + } + } + + return true; + } + + private static bool DestHasNoUses(INode node) + { + for (int index = 0; index < node.DestsCount; index++) + { + Operand dest = node.GetDest(index); + + if (dest != null && dest.UseOps.Count != 0) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs new file mode 100644 index 00000000..097c8aa8 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs @@ -0,0 +1,235 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class Simplification + { + private const int AllOnes = ~0; + + public static void RunPass(Operation operation) + { + switch (operation.Inst) + { + case Instruction.Add: + TryEliminateBinaryOpCommutative(operation, 0); + break; + + case Instruction.BitwiseAnd: + TryEliminateBitwiseAnd(operation); + break; + + case Instruction.BitwiseExclusiveOr: + if (!TryEliminateXorSwap(operation)) + { + TryEliminateBinaryOpCommutative(operation, 0); + } + break; + + case Instruction.BitwiseOr: + TryEliminateBitwiseOr(operation); + break; + + case Instruction.CompareNotEqual: + TryEliminateCompareNotEqual(operation); + break; + + case Instruction.ConditionalSelect: + TryEliminateConditionalSelect(operation); + break; + + case Instruction.Divide: + TryEliminateBinaryOpY(operation, 1); + break; + + case Instruction.Multiply: + TryEliminateBinaryOpCommutative(operation, 1); + break; + + case Instruction.ShiftLeft: + case Instruction.ShiftRightS32: + case Instruction.ShiftRightU32: + case Instruction.Subtract: + TryEliminateBinaryOpY(operation, 0); + break; + } + } + + private static void TryEliminateBitwiseAnd(Operation operation) + { + // Try to recognize and optimize those 3 patterns (in order): + // x & 0xFFFFFFFF == x, 0xFFFFFFFF & y == y, + // x & 0x00000000 == 0x00000000, 0x00000000 & y == 0x00000000 + + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, AllOnes)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, AllOnes)) + { + operation.TurnIntoCopy(x); + } + else if (IsConstEqual(x, 0) || IsConstEqual(y, 0)) + { + operation.TurnIntoCopy(Const(0)); + } + } + + private static bool TryEliminateXorSwap(Operation xCopyOp) + { + // Try to recognize XOR swap pattern: + // x = x ^ y + // y = x ^ y + // x = x ^ y + // Or, in SSA: + // x2 = x ^ y + // y2 = x2 ^ y + // x3 = x2 ^ y2 + // Transform it into something more sane: + // temp = y + // y = x + // x = temp + + // Note that because XOR is commutative, there are actually + // multiple possible combinations of this pattern, for + // simplicity this only catches one of them. + + Operand x = xCopyOp.GetSource(0); + Operand y = xCopyOp.GetSource(1); + + if (x.AsgOp is not Operation tCopyOp || tCopyOp.Inst != Instruction.BitwiseExclusiveOr || + y.AsgOp is not Operation yCopyOp || yCopyOp.Inst != Instruction.BitwiseExclusiveOr) + { + return false; + } + + if (tCopyOp == yCopyOp) + { + return false; + } + + if (yCopyOp.GetSource(0) != x || + yCopyOp.GetSource(1) != tCopyOp.GetSource(1) || + x.UseOps.Count != 2) + { + return false; + } + + x = tCopyOp.GetSource(0); + y = tCopyOp.GetSource(1); + + tCopyOp.TurnIntoCopy(y); // Temp = Y + yCopyOp.TurnIntoCopy(x); // Y = X + xCopyOp.TurnIntoCopy(tCopyOp.Dest); // X = Temp + + return true; + } + + private static void TryEliminateBitwiseOr(Operation operation) + { + // Try to recognize and optimize those 3 patterns (in order): + // x | 0x00000000 == x, 0x00000000 | y == y, + // x | 0xFFFFFFFF == 0xFFFFFFFF, 0xFFFFFFFF | y == 0xFFFFFFFF + + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, 0)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, 0)) + { + operation.TurnIntoCopy(x); + } + else if (IsConstEqual(x, AllOnes) || IsConstEqual(y, AllOnes)) + { + operation.TurnIntoCopy(Const(AllOnes)); + } + } + + private static void TryEliminateBinaryOpY(Operation operation, int comparand) + { + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(y, comparand)) + { + operation.TurnIntoCopy(x); + } + } + + private static void TryEliminateBinaryOpCommutative(Operation operation, int comparand) + { + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, comparand)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, comparand)) + { + operation.TurnIntoCopy(x); + } + } + + private static void TryEliminateCompareNotEqual(Operation operation) + { + // Comparison instruction returns 0 if the result is false, and -1 if true. + // Doing a not equal zero comparison on the result is redundant, so we can just copy the first result in this case. + + Operand lhs = operation.GetSource(0); + Operand rhs = operation.GetSource(1); + + if (lhs.Type == OperandType.Constant) + { + (lhs, rhs) = (rhs, lhs); + } + + if (rhs.Type != OperandType.Constant || rhs.Value != 0) + { + return; + } + + if (lhs.AsgOp is not Operation compareOp || !compareOp.Inst.IsComparison()) + { + return; + } + + operation.TurnIntoCopy(lhs); + } + + private static void TryEliminateConditionalSelect(Operation operation) + { + Operand cond = operation.GetSource(0); + + if (cond.Type != OperandType.Constant) + { + return; + } + + // The condition is constant, we can turn it into a copy, and select + // the source based on the condition value. + int srcIndex = cond.Value != 0 ? 1 : 2; + + Operand source = operation.GetSource(srcIndex); + + operation.TurnIntoCopy(source); + } + + private static bool IsConstEqual(Operand operand, int comparand) + { + if (operand.Type != OperandType.Constant) + { + return false; + } + + return operand.Value == comparand; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs new file mode 100644 index 00000000..23180ff8 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs @@ -0,0 +1,180 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + static class Utils + { + public static bool IsInputLoad(INode node) + { + return (node is Operation operation) && + operation.Inst == Instruction.Load && + operation.StorageKind == StorageKind.Input; + } + + public static bool IsInputLoad(INode node, IoVariable ioVariable, int elemIndex) + { + if (node is not Operation operation || + operation.Inst != Instruction.Load || + operation.StorageKind != StorageKind.Input || + operation.SourcesCount != 2) + { + return false; + } + + Operand ioVariableSrc = operation.GetSource(0); + + if (ioVariableSrc.Type != OperandType.Constant || (IoVariable)ioVariableSrc.Value != ioVariable) + { + return false; + } + + Operand elemIndexSrc = operation.GetSource(1); + + return elemIndexSrc.Type == OperandType.Constant && elemIndexSrc.Value == elemIndex; + } + + private static bool IsSameOperand(Operand x, Operand y) + { + if (x.Type != y.Type || x.Value != y.Value) + { + return false; + } + + // TODO: Handle Load operations with the same storage and the same constant parameters. + return x == y || x.Type == OperandType.Constant || x.Type == OperandType.ConstantBuffer; + } + + private static bool AreAllSourcesEqual(INode node, INode otherNode) + { + if (node.SourcesCount != otherNode.SourcesCount) + { + return false; + } + + for (int index = 0; index < node.SourcesCount; index++) + { + if (!IsSameOperand(node.GetSource(index), otherNode.GetSource(index))) + { + return false; + } + } + + return true; + } + + public static bool AreAllSourcesTheSameOperand(INode node) + { + Operand firstSrc = node.GetSource(0); + + for (int index = 1; index < node.SourcesCount; index++) + { + if (!IsSameOperand(firstSrc, node.GetSource(index))) + { + return false; + } + } + + return true; + } + + private static Operation FindBranchSource(BasicBlock block) + { + foreach (BasicBlock sourceBlock in block.Predecessors) + { + if (sourceBlock.Operations.Count > 0) + { + if (sourceBlock.GetLastOp() is Operation lastOp && IsConditionalBranch(lastOp.Inst) && sourceBlock.Next == block) + { + return lastOp; + } + } + } + + return null; + } + + private static bool IsConditionalBranch(Instruction inst) + { + return inst == Instruction.BranchIfFalse || inst == Instruction.BranchIfTrue; + } + + private static bool IsSameCondition(Operand currentCondition, Operand queryCondition) + { + if (currentCondition == queryCondition) + { + return true; + } + + return currentCondition.AsgOp is Operation currentOperation && + queryCondition.AsgOp is Operation queryOperation && + currentOperation.Inst == queryOperation.Inst && + AreAllSourcesEqual(currentOperation, queryOperation); + } + + private static bool BlockConditionsMatch(BasicBlock currentBlock, BasicBlock queryBlock) + { + // Check if all the conditions for the query block are satisfied by the current block. + // Just checks the top-most conditional for now. + + Operation currentBranch = FindBranchSource(currentBlock); + Operation queryBranch = FindBranchSource(queryBlock); + + Operand currentCondition = currentBranch?.GetSource(0); + Operand queryCondition = queryBranch?.GetSource(0); + + // The condition should be the same operand instance. + + return currentBranch != null && queryBranch != null && + currentBranch.Inst == queryBranch.Inst && + IsSameCondition(currentCondition, queryCondition); + } + + public static Operand FindLastOperation(Operand source, BasicBlock block, bool recurse = true) + { + if (source.AsgOp is PhiNode phiNode) + { + // This source can have a different value depending on a previous branch. + // Ensure that conditions met for that branch are also met for the current one. + // Prefer the latest sources for the phi node. + + for (int i = phiNode.SourcesCount - 1; i >= 0; i--) + { + BasicBlock phiBlock = phiNode.GetBlock(i); + Operand phiSource = phiNode.GetSource(i); + + if (BlockConditionsMatch(block, phiBlock)) + { + return phiSource; + } + else if (recurse && phiSource.AsgOp is PhiNode) + { + // Phi source is another phi. + // Let's check if that phi has a block that matches our condition. + + Operand match = FindLastOperation(phiSource, block, false); + + if (match != phiSource) + { + return match; + } + } + } + } + + return source; + } + + public static void DeleteNode(LinkedListNode node, Operation operation) + { + node.List.Remove(node); + + for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++) + { + operation.SetSource(srcIndex, null); + } + + operation.Dest = null; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/RegisterUsage.cs b/src/Ryujinx.Graphics.Shader/Translation/RegisterUsage.cs new file mode 100644 index 00000000..1c724223 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/RegisterUsage.cs @@ -0,0 +1,491 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class RegisterUsage + { + private const int RegsCount = 256; + private const int RegsMask = RegsCount - 1; + + private const int GprMasks = 4; + private const int PredMasks = 1; + private const int FlagMasks = 1; + private const int TotalMasks = GprMasks + PredMasks + FlagMasks; + + private struct RegisterMask : IEquatable + { + public long GprMask0 { get; set; } + public long GprMask1 { get; set; } + public long GprMask2 { get; set; } + public long GprMask3 { get; set; } + public long PredMask { get; set; } + public long FlagMask { get; set; } + + public RegisterMask(long gprMask0, long gprMask1, long gprMask2, long gprMask3, long predMask, long flagMask) + { + GprMask0 = gprMask0; + GprMask1 = gprMask1; + GprMask2 = gprMask2; + GprMask3 = gprMask3; + PredMask = predMask; + FlagMask = flagMask; + } + + public readonly long GetMask(int index) + { + return index switch + { + 0 => GprMask0, + 1 => GprMask1, + 2 => GprMask2, + 3 => GprMask3, + 4 => PredMask, + 5 => FlagMask, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + } + + public static RegisterMask operator &(RegisterMask x, RegisterMask y) + { + return new RegisterMask( + x.GprMask0 & y.GprMask0, + x.GprMask1 & y.GprMask1, + x.GprMask2 & y.GprMask2, + x.GprMask3 & y.GprMask3, + x.PredMask & y.PredMask, + x.FlagMask & y.FlagMask); + } + + public static RegisterMask operator |(RegisterMask x, RegisterMask y) + { + return new RegisterMask( + x.GprMask0 | y.GprMask0, + x.GprMask1 | y.GprMask1, + x.GprMask2 | y.GprMask2, + x.GprMask3 | y.GprMask3, + x.PredMask | y.PredMask, + x.FlagMask | y.FlagMask); + } + + public static RegisterMask operator ~(RegisterMask x) + { + return new RegisterMask( + ~x.GprMask0, + ~x.GprMask1, + ~x.GprMask2, + ~x.GprMask3, + ~x.PredMask, + ~x.FlagMask); + } + + public static bool operator ==(RegisterMask x, RegisterMask y) + { + return x.Equals(y); + } + + public static bool operator !=(RegisterMask x, RegisterMask y) + { + return !x.Equals(y); + } + + public readonly override bool Equals(object obj) + { + return obj is RegisterMask regMask && Equals(regMask); + } + + public readonly bool Equals(RegisterMask other) + { + return GprMask0 == other.GprMask0 && + GprMask1 == other.GprMask1 && + GprMask2 == other.GprMask2 && + GprMask3 == other.GprMask3 && + PredMask == other.PredMask && + FlagMask == other.FlagMask; + } + + public readonly override int GetHashCode() + { + return HashCode.Combine(GprMask0, GprMask1, GprMask2, GprMask3, PredMask, FlagMask); + } + } + + public readonly struct FunctionRegisterUsage + { + public Register[] InArguments { get; } + public Register[] OutArguments { get; } + + public FunctionRegisterUsage(Register[] inArguments, Register[] outArguments) + { + InArguments = inArguments; + OutArguments = outArguments; + } + } + + public static FunctionRegisterUsage RunPass(ControlFlowGraph cfg) + { + List inArguments = new(); + List outArguments = new(); + + // Compute local register inputs and outputs used inside blocks. + RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Length]; + RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Length]; + + foreach (BasicBlock block in cfg.Blocks) + { + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + Operation operation = node.Value as Operation; + + for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++) + { + Operand source = operation.GetSource(srcIndex); + + if (source.Type != OperandType.Register) + { + continue; + } + + Register register = source.GetRegister(); + + localInputs[block.Index] |= GetMask(register) & ~localOutputs[block.Index]; + } + + for (int dstIndex = 0; dstIndex < operation.DestsCount; dstIndex++) + { + Operand dest = operation.GetDest(dstIndex); + + if (dest != null && dest.Type == OperandType.Register) + { + localOutputs[block.Index] |= GetMask(dest.GetRegister()); + } + } + } + } + + // Compute global register inputs and outputs used across blocks. + RegisterMask[] globalCmnOutputs = new RegisterMask[cfg.Blocks.Length]; + + RegisterMask[] globalInputs = new RegisterMask[cfg.Blocks.Length]; + RegisterMask[] globalOutputs = new RegisterMask[cfg.Blocks.Length]; + + RegisterMask allOutputs = new(); + RegisterMask allCmnOutputs = new(-1L, -1L, -1L, -1L, -1L, -1L); + + bool modified; + + bool firstPass = true; + + do + { + modified = false; + + // Compute register outputs. + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + if (block.Predecessors.Count != 0) + { + BasicBlock predecessor = block.Predecessors[0]; + + RegisterMask cmnOutputs = localOutputs[predecessor.Index] | globalCmnOutputs[predecessor.Index]; + + RegisterMask outputs = globalOutputs[predecessor.Index]; + + for (int pIndex = 1; pIndex < block.Predecessors.Count; pIndex++) + { + predecessor = block.Predecessors[pIndex]; + + cmnOutputs &= localOutputs[predecessor.Index] | globalCmnOutputs[predecessor.Index]; + + outputs |= globalOutputs[predecessor.Index]; + } + + globalInputs[block.Index] |= outputs & ~cmnOutputs; + + if (!firstPass) + { + cmnOutputs &= globalCmnOutputs[block.Index]; + } + + if (EndsWithReturn(block)) + { + allCmnOutputs &= cmnOutputs | localOutputs[block.Index]; + } + + if (Exchange(globalCmnOutputs, block.Index, cmnOutputs)) + { + modified = true; + } + + outputs |= localOutputs[block.Index]; + + if (Exchange(globalOutputs, block.Index, globalOutputs[block.Index] | outputs)) + { + allOutputs |= outputs; + modified = true; + } + } + else if (Exchange(globalOutputs, block.Index, localOutputs[block.Index])) + { + allOutputs |= localOutputs[block.Index]; + modified = true; + } + } + + // Compute register inputs. + for (int index = 0; index < cfg.PostOrderBlocks.Length; index++) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + RegisterMask inputs = localInputs[block.Index]; + + if (block.Next != null) + { + inputs |= globalInputs[block.Next.Index]; + } + + if (block.Branch != null) + { + inputs |= globalInputs[block.Branch.Index]; + } + + inputs &= ~globalCmnOutputs[block.Index]; + + if (Exchange(globalInputs, block.Index, globalInputs[block.Index] | inputs)) + { + modified = true; + } + } + + firstPass = false; + } + while (modified); + + // Insert load and store context instructions where needed. + foreach (BasicBlock block in cfg.Blocks) + { + // The only block without any predecessor should be the entry block. + // It always needs a context load as it is the first block to run. + if (block.Predecessors.Count == 0) + { + RegisterMask inputs = globalInputs[block.Index] | (allOutputs & ~allCmnOutputs); + + LoadLocals(block, inputs, inArguments); + } + + if (EndsWithReturn(block)) + { + StoreLocals(block, allOutputs, inArguments.Count, outArguments); + } + } + + return new FunctionRegisterUsage(inArguments.ToArray(), outArguments.ToArray()); + } + + public static void FixupCalls(BasicBlock[] blocks, FunctionRegisterUsage[] frus) + { + foreach (BasicBlock block in blocks) + { + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + Operation operation = node.Value as Operation; + + if (operation.Inst == Instruction.Call) + { + Operand funcId = operation.GetSource(0); + + Debug.Assert(funcId.Type == OperandType.Constant); + + var fru = frus[funcId.Value]; + + Operand[] inRegs = new Operand[fru.InArguments.Length]; + + for (int i = 0; i < fru.InArguments.Length; i++) + { + inRegs[i] = OperandHelper.Register(fru.InArguments[i]); + } + + operation.AppendSources(inRegs); + + Operand[] outRegs = new Operand[1 + fru.OutArguments.Length]; + + for (int i = 0; i < fru.OutArguments.Length; i++) + { + outRegs[1 + i] = OperandHelper.Register(fru.OutArguments[i]); + } + + operation.AppendDests(outRegs); + } + } + } + } + + private static bool StartsWith(BasicBlock block, Instruction inst) + { + if (block.Operations.Count == 0) + { + return false; + } + + return block.Operations.First.Value is Operation operation && operation.Inst == inst; + } + + private static bool EndsWith(BasicBlock block, Instruction inst) + { + if (block.Operations.Count == 0) + { + return false; + } + + return block.Operations.Last.Value is Operation operation && operation.Inst == inst; + } + + private static RegisterMask GetMask(Register register) + { + Span gprMasks = stackalloc long[4]; + long predMask = 0; + long flagMask = 0; + + switch (register.Type) + { + case RegisterType.Gpr: + gprMasks[register.Index >> 6] = 1L << (register.Index & 0x3f); + break; + case RegisterType.Predicate: + predMask = 1L << register.Index; + break; + case RegisterType.Flag: + flagMask = 1L << register.Index; + break; + } + + return new RegisterMask(gprMasks[0], gprMasks[1], gprMasks[2], gprMasks[3], predMask, flagMask); + } + + private static bool Exchange(RegisterMask[] masks, int blkIndex, RegisterMask value) + { + RegisterMask oldValue = masks[blkIndex]; + + masks[blkIndex] = value; + + return oldValue != value; + } + + private static void LoadLocals(BasicBlock block, RegisterMask masks, List inArguments) + { + bool fillArgsList = inArguments.Count == 0; + LinkedListNode node = null; + int argIndex = 0; + + for (int i = 0; i < TotalMasks; i++) + { + (RegisterType regType, int baseRegIndex) = GetRegTypeAndBaseIndex(i); + long mask = masks.GetMask(i); + + while (mask != 0) + { + int bit = BitOperations.TrailingZeroCount(mask); + + mask &= ~(1L << bit); + + Register register = new(baseRegIndex + bit, regType); + + if (fillArgsList) + { + inArguments.Add(register); + } + + Operation copyOp = new(Instruction.Copy, OperandHelper.Register(register), OperandHelper.Argument(argIndex++)); + + if (node == null) + { + node = block.Operations.AddFirst(copyOp); + } + else + { + node = block.Operations.AddAfter(node, copyOp); + } + } + } + + Debug.Assert(argIndex <= inArguments.Count); + } + + private static void StoreLocals(BasicBlock block, RegisterMask masks, int inArgumentsCount, List outArguments) + { + LinkedListNode node = null; + int argIndex = inArgumentsCount; + bool fillArgsList = outArguments.Count == 0; + + for (int i = 0; i < TotalMasks; i++) + { + (RegisterType regType, int baseRegIndex) = GetRegTypeAndBaseIndex(i); + long mask = masks.GetMask(i); + + while (mask != 0) + { + int bit = BitOperations.TrailingZeroCount(mask); + + mask &= ~(1L << bit); + + Register register = new(baseRegIndex + bit, regType); + + if (fillArgsList) + { + outArguments.Add(register); + } + + Operation copyOp = new(Instruction.Copy, OperandHelper.Argument(argIndex++), OperandHelper.Register(register)); + + if (node == null) + { + node = block.Operations.AddBefore(block.Operations.Last, copyOp); + } + else + { + node = block.Operations.AddAfter(node, copyOp); + } + } + } + + Debug.Assert(argIndex <= inArgumentsCount + outArguments.Count); + } + + private static (RegisterType RegType, int BaseRegIndex) GetRegTypeAndBaseIndex(int i) + { + RegisterType regType = RegisterType.Gpr; + int baseRegIndex = 0; + + if (i < GprMasks) + { + baseRegIndex = i * sizeof(long) * 8; + } + else if (i == GprMasks) + { + regType = RegisterType.Predicate; + } + else + { + regType = RegisterType.Flag; + } + + return (regType, baseRegIndex); + } + + private static bool EndsWithReturn(BasicBlock block) + { + if (block.GetLastOp() is not Operation operation) + { + return false; + } + + return operation.Inst == Instruction.Return; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs new file mode 100644 index 00000000..94691a5b --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs @@ -0,0 +1,649 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace Ryujinx.Graphics.Shader.Translation +{ + class ResourceManager + { + // Those values are used if the shader as local or shared memory access, + // but for some reason the supplied size was 0. + private const int DefaultLocalMemorySize = 128; + private const int DefaultSharedMemorySize = 4096; + + private static readonly string[] _stagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" }; + + private readonly IGpuAccessor _gpuAccessor; + private readonly ShaderStage _stage; + private readonly string _stagePrefix; + + private readonly SetBindingPair[] _cbSlotToBindingMap; + private readonly SetBindingPair[] _sbSlotToBindingMap; + private uint _sbSlotWritten; + + private readonly Dictionary _sbSlots; + private readonly Dictionary _sbSlotsReverse; + + private readonly HashSet _usedConstantBufferBindings; + + private readonly record struct TextureInfo(int CbufSlot, int Handle, int ArrayLength, bool Separate, SamplerType Type, TextureFormat Format); + + private struct TextureMeta + { + public int Set; + public int Binding; + public bool AccurateType; + public SamplerType Type; + public TextureUsageFlags UsageFlags; + } + + private readonly Dictionary _usedTextures; + private readonly Dictionary _usedImages; + + public int LocalMemoryId { get; private set; } + public int SharedMemoryId { get; private set; } + + public int LocalVertexDataMemoryId { get; private set; } + public int LocalTopologyRemapMemoryId { get; private set; } + public int LocalVertexIndexVertexRateMemoryId { get; private set; } + public int LocalVertexIndexInstanceRateMemoryId { get; private set; } + public int LocalGeometryOutputVertexCountMemoryId { get; private set; } + public int LocalGeometryOutputIndexCountMemoryId { get; private set; } + + public ShaderProperties Properties { get; } + + public ResourceReservations Reservations { get; } + + public ResourceManager(ShaderStage stage, IGpuAccessor gpuAccessor, ResourceReservations reservations = null) + { + _gpuAccessor = gpuAccessor; + Properties = new(); + Reservations = reservations; + _stage = stage; + _stagePrefix = GetShaderStagePrefix(stage); + + _cbSlotToBindingMap = new SetBindingPair[18]; + _sbSlotToBindingMap = new SetBindingPair[16]; + _cbSlotToBindingMap.AsSpan().Fill(new(-1, -1)); + _sbSlotToBindingMap.AsSpan().Fill(new(-1, -1)); + + _sbSlots = new(); + _sbSlotsReverse = new(); + + _usedConstantBufferBindings = new(); + + _usedTextures = new(); + _usedImages = new(); + + Properties.AddOrUpdateConstantBuffer(new(BufferLayout.Std140, 0, SupportBuffer.Binding, "support_buffer", SupportBuffer.GetStructureType())); + + LocalMemoryId = -1; + SharedMemoryId = -1; + } + + public void SetCurrentLocalMemory(int size, bool isUsed) + { + if (isUsed) + { + if (size <= 0) + { + size = DefaultLocalMemorySize; + } + + var lmem = new MemoryDefinition("local_memory", AggregateType.Array | AggregateType.U32, BitUtils.DivRoundUp(size, sizeof(uint))); + + LocalMemoryId = Properties.AddLocalMemory(lmem); + } + else + { + LocalMemoryId = -1; + } + } + + public void SetCurrentSharedMemory(int size, bool isUsed) + { + if (isUsed) + { + if (size <= 0) + { + size = DefaultSharedMemorySize; + } + + var smem = new MemoryDefinition("shared_memory", AggregateType.Array | AggregateType.U32, BitUtils.DivRoundUp(size, sizeof(uint))); + + SharedMemoryId = Properties.AddSharedMemory(smem); + } + else + { + SharedMemoryId = -1; + } + } + + public void SetVertexAsComputeLocalMemories(ShaderStage stage, InputTopology inputTopology) + { + LocalVertexDataMemoryId = AddMemoryDefinition("local_vertex_data", AggregateType.Array | AggregateType.FP32, Reservations.OutputSizePerInvocation); + + if (stage == ShaderStage.Vertex) + { + LocalVertexIndexVertexRateMemoryId = AddMemoryDefinition("local_vertex_index_vr", AggregateType.U32); + LocalVertexIndexInstanceRateMemoryId = AddMemoryDefinition("local_vertex_index_ir", AggregateType.U32); + } + else if (stage == ShaderStage.Geometry) + { + LocalTopologyRemapMemoryId = AddMemoryDefinition("local_topology_remap", AggregateType.Array | AggregateType.U32, inputTopology.ToInputVertices()); + + LocalGeometryOutputVertexCountMemoryId = AddMemoryDefinition("local_geometry_output_vertex", AggregateType.U32); + LocalGeometryOutputIndexCountMemoryId = AddMemoryDefinition("local_geometry_output_index", AggregateType.U32); + } + } + + private int AddMemoryDefinition(string name, AggregateType type, int arrayLength = 1) + { + return Properties.AddLocalMemory(new MemoryDefinition(name, type, arrayLength)); + } + + public int GetConstantBufferBinding(int slot) + { + SetBindingPair setAndBinding = _cbSlotToBindingMap[slot]; + if (setAndBinding.Binding < 0) + { + setAndBinding = _gpuAccessor.CreateConstantBufferBinding(slot); + _cbSlotToBindingMap[slot] = setAndBinding; + string slotNumber = slot.ToString(CultureInfo.InvariantCulture); + AddNewConstantBuffer(setAndBinding.SetIndex, setAndBinding.Binding, $"{_stagePrefix}_c{slotNumber}"); + } + + return setAndBinding.Binding; + } + + public bool TryGetStorageBufferBinding(int sbCbSlot, int sbCbOffset, bool write, out int binding) + { + if (!TryGetSbSlot((byte)sbCbSlot, (ushort)sbCbOffset, out int slot)) + { + binding = 0; + return false; + } + + SetBindingPair setAndBinding = _sbSlotToBindingMap[slot]; + + if (setAndBinding.Binding < 0) + { + setAndBinding = _gpuAccessor.CreateStorageBufferBinding(slot); + _sbSlotToBindingMap[slot] = setAndBinding; + string slotNumber = slot.ToString(CultureInfo.InvariantCulture); + AddNewStorageBuffer(setAndBinding.SetIndex, setAndBinding.Binding, $"{_stagePrefix}_s{slotNumber}"); + } + + if (write) + { + _sbSlotWritten |= 1u << slot; + } + + binding = setAndBinding.Binding; + return true; + } + + private bool TryGetSbSlot(byte sbCbSlot, ushort sbCbOffset, out int slot) + { + int key = PackSbCbInfo(sbCbSlot, sbCbOffset); + + if (!_sbSlots.TryGetValue(key, out slot)) + { + slot = _sbSlots.Count; + + if (slot >= _sbSlotToBindingMap.Length) + { + return false; + } + + _sbSlots.Add(key, slot); + _sbSlotsReverse.Add(slot, key); + } + + return true; + } + + public bool TryGetConstantBufferSlot(int binding, out int slot) + { + for (slot = 0; slot < _cbSlotToBindingMap.Length; slot++) + { + if (_cbSlotToBindingMap[slot].Binding == binding) + { + return true; + } + } + + slot = 0; + return false; + } + + public SetBindingPair GetTextureOrImageBinding( + Instruction inst, + SamplerType type, + TextureFormat format, + TextureFlags flags, + int cbufSlot, + int handle, + int arrayLength = 1, + bool separate = false) + { + inst &= Instruction.Mask; + bool isImage = inst.IsImage(); + bool isWrite = inst.IsImageStore(); + bool accurateType = !inst.IsTextureQuery(); + bool intCoords = isImage || flags.HasFlag(TextureFlags.IntCoords) || inst == Instruction.TextureQuerySize; + bool coherent = flags.HasFlag(TextureFlags.Coherent); + + if (!isImage) + { + format = TextureFormat.Unknown; + } + + SetBindingPair setAndBinding = GetTextureOrImageBinding( + cbufSlot, + handle, + arrayLength, + type, + format, + isImage, + intCoords, + isWrite, + accurateType, + coherent, + separate); + + _gpuAccessor.RegisterTexture(handle, cbufSlot); + + return setAndBinding; + } + + private SetBindingPair GetTextureOrImageBinding( + int cbufSlot, + int handle, + int arrayLength, + SamplerType type, + TextureFormat format, + bool isImage, + bool intCoords, + bool write, + bool accurateType, + bool coherent, + bool separate) + { + var dimensions = type == SamplerType.None ? 0 : type.GetDimensions(); + var dict = isImage ? _usedImages : _usedTextures; + + var usageFlags = TextureUsageFlags.None; + + if (intCoords) + { + usageFlags |= TextureUsageFlags.NeedsScaleValue; + + var canScale = _stage.SupportsRenderScale() && arrayLength == 1 && !write && dimensions == 2; + + if (!canScale) + { + // Resolution scaling cannot be applied to this texture right now. + // Flag so that we know to blacklist scaling on related textures when binding them. + usageFlags |= TextureUsageFlags.ResScaleUnsupported; + } + } + + if (write) + { + usageFlags |= TextureUsageFlags.ImageStore; + } + + if (coherent) + { + usageFlags |= TextureUsageFlags.ImageCoherent; + } + + // For array textures, we also want to use type as key, + // since we may have texture handles stores in the same buffer, but for textures with different types. + var keyType = arrayLength > 1 ? type : SamplerType.None; + var info = new TextureInfo(cbufSlot, handle, arrayLength, separate, keyType, format); + var meta = new TextureMeta() + { + AccurateType = accurateType, + Type = type, + UsageFlags = usageFlags, + }; + + int setIndex; + int binding; + + if (dict.TryGetValue(info, out var existingMeta)) + { + dict[info] = MergeTextureMeta(meta, existingMeta); + setIndex = existingMeta.Set; + binding = existingMeta.Binding; + } + else + { + if (arrayLength > 1 && (setIndex = _gpuAccessor.CreateExtraSet()) >= 0) + { + // We reserved an "extra set" for the array. + // In this case the binding is always the first one (0). + // Using separate sets for array is better as we need to do less descriptor set updates. + + binding = 0; + } + else + { + bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer; + + SetBindingPair setAndBinding = isImage + ? _gpuAccessor.CreateImageBinding(arrayLength, isBuffer) + : _gpuAccessor.CreateTextureBinding(arrayLength, isBuffer); + + setIndex = setAndBinding.SetIndex; + binding = setAndBinding.Binding; + } + + meta.Set = setIndex; + meta.Binding = binding; + + dict.Add(info, meta); + } + + string nameSuffix; + string prefix = isImage ? "i" : "t"; + + if (arrayLength != 1 && type != SamplerType.None) + { + prefix += type.ToShortSamplerType(); + } + + if (isImage) + { + nameSuffix = cbufSlot < 0 + ? $"{prefix}_tcb_{handle:X}_{format.ToGlslFormat()}" + : $"{prefix}_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}"; + } + else if (type == SamplerType.None) + { + nameSuffix = cbufSlot < 0 ? $"s_tcb_{handle:X}" : $"s_cb{cbufSlot}_{handle:X}"; + } + else + { + nameSuffix = cbufSlot < 0 ? $"{prefix}_tcb_{handle:X}" : $"{prefix}_cb{cbufSlot}_{handle:X}"; + } + + var definition = new TextureDefinition( + setIndex, + binding, + arrayLength, + separate, + $"{_stagePrefix}_{nameSuffix}", + meta.Type, + info.Format, + meta.UsageFlags); + + if (isImage) + { + Properties.AddOrUpdateImage(definition); + } + else + { + Properties.AddOrUpdateTexture(definition); + } + + return new SetBindingPair(setIndex, binding); + } + + private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta) + { + meta.Set = existingMeta.Set; + meta.Binding = existingMeta.Binding; + meta.UsageFlags |= existingMeta.UsageFlags; + + // If the texture we have has inaccurate type information, then + // we prefer the most accurate one. + if (existingMeta.AccurateType) + { + meta.AccurateType = true; + meta.Type = existingMeta.Type; + } + + return meta; + } + + public void SetUsageFlagsForTextureQuery(int binding, SamplerType type) + { + TextureInfo selectedInfo = default; + TextureMeta selectedMeta = default; + bool found = false; + + foreach ((TextureInfo info, TextureMeta meta) in _usedTextures) + { + if (meta.Binding == binding) + { + selectedInfo = info; + selectedMeta = meta; + found = true; + break; + } + } + + if (found) + { + selectedMeta.UsageFlags |= TextureUsageFlags.NeedsScaleValue; + + var dimensions = type.GetDimensions(); + var canScale = _stage.SupportsRenderScale() && selectedInfo.ArrayLength == 1 && dimensions == 2; + + if (!canScale) + { + // Resolution scaling cannot be applied to this texture right now. + // Flag so that we know to blacklist scaling on related textures when binding them. + selectedMeta.UsageFlags |= TextureUsageFlags.ResScaleUnsupported; + } + + _usedTextures[selectedInfo] = selectedMeta; + } + } + + public void SetUsedConstantBufferBinding(int binding) + { + _usedConstantBufferBindings.Add(binding); + } + + public BufferDescriptor[] GetConstantBufferDescriptors() + { + var descriptors = new BufferDescriptor[_usedConstantBufferBindings.Count]; + + int descriptorIndex = 0; + + for (int slot = 0; slot < _cbSlotToBindingMap.Length; slot++) + { + SetBindingPair setAndBinding = _cbSlotToBindingMap[slot]; + + if (setAndBinding.Binding >= 0 && _usedConstantBufferBindings.Contains(setAndBinding.Binding)) + { + descriptors[descriptorIndex++] = new BufferDescriptor(setAndBinding.SetIndex, setAndBinding.Binding, slot); + } + } + + if (descriptors.Length != descriptorIndex) + { + Array.Resize(ref descriptors, descriptorIndex); + } + + return descriptors; + } + + public BufferDescriptor[] GetStorageBufferDescriptors() + { + var descriptors = new BufferDescriptor[_sbSlots.Count]; + + int descriptorIndex = 0; + + foreach ((int key, int slot) in _sbSlots) + { + SetBindingPair setAndBinding = _sbSlotToBindingMap[slot]; + + if (setAndBinding.Binding >= 0) + { + (int sbCbSlot, int sbCbOffset) = UnpackSbCbInfo(key); + BufferUsageFlags flags = (_sbSlotWritten & (1u << slot)) != 0 ? BufferUsageFlags.Write : BufferUsageFlags.None; + descriptors[descriptorIndex++] = new BufferDescriptor(setAndBinding.SetIndex, setAndBinding.Binding, slot, sbCbSlot, sbCbOffset, flags); + } + } + + if (descriptors.Length != descriptorIndex) + { + Array.Resize(ref descriptors, descriptorIndex); + } + + return descriptors; + } + + public TextureDescriptor[] GetTextureDescriptors(bool includeArrays = true) + { + return GetDescriptors(_usedTextures, includeArrays); + } + + public TextureDescriptor[] GetImageDescriptors(bool includeArrays = true) + { + return GetDescriptors(_usedImages, includeArrays); + } + + private static TextureDescriptor[] GetDescriptors(IReadOnlyDictionary usedResources, bool includeArrays) + { + List descriptors = new(); + + bool hasAnyArray = false; + + foreach ((TextureInfo info, TextureMeta meta) in usedResources) + { + if (info.ArrayLength > 1) + { + hasAnyArray = true; + continue; + } + + descriptors.Add(new TextureDescriptor( + meta.Set, + meta.Binding, + meta.Type, + info.Format, + info.CbufSlot, + info.Handle, + info.ArrayLength, + info.Separate, + meta.UsageFlags)); + } + + if (hasAnyArray && includeArrays) + { + foreach ((TextureInfo info, TextureMeta meta) in usedResources) + { + if (info.ArrayLength <= 1) + { + continue; + } + + descriptors.Add(new TextureDescriptor( + meta.Set, + meta.Binding, + meta.Type, + info.Format, + info.CbufSlot, + info.Handle, + info.ArrayLength, + info.Separate, + meta.UsageFlags)); + } + } + + return descriptors.ToArray(); + } + + public bool TryGetCbufSlotAndHandleForTexture(int binding, out int cbufSlot, out int handle) + { + foreach ((TextureInfo info, TextureMeta meta) in _usedTextures) + { + if (meta.Binding == binding) + { + cbufSlot = info.CbufSlot; + handle = info.Handle; + + return true; + } + } + + cbufSlot = 0; + handle = 0; + return false; + } + + private static int FindDescriptorIndex(TextureDescriptor[] array, int binding) + { + return Array.FindIndex(array, x => x.Binding == binding); + } + + public int FindTextureDescriptorIndex(int binding) + { + return FindDescriptorIndex(GetTextureDescriptors(), binding); + } + + public int FindImageDescriptorIndex(int binding) + { + return FindDescriptorIndex(GetImageDescriptors(), binding); + } + + public bool IsArrayOfTexturesOrImages(int binding, bool isImage) + { + foreach ((TextureInfo info, TextureMeta meta) in isImage ? _usedImages : _usedTextures) + { + if (meta.Binding == binding) + { + return info.ArrayLength != 1; + } + } + + return false; + } + + private void AddNewConstantBuffer(int setIndex, int binding, string name) + { + StructureType type = new(new[] + { + new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32, "data", Constants.ConstantBufferSize / 16), + }); + + Properties.AddOrUpdateConstantBuffer(new(BufferLayout.Std140, setIndex, binding, name, type)); + } + + private void AddNewStorageBuffer(int setIndex, int binding, string name) + { + StructureType type = new(new[] + { + new StructureField(AggregateType.Array | AggregateType.U32, "data", 0), + }); + + Properties.AddOrUpdateStorageBuffer(new(BufferLayout.Std430, setIndex, binding, name, type)); + } + + public static string GetShaderStagePrefix(ShaderStage stage) + { + uint index = (uint)stage; + + return index >= _stagePrefixes.Length ? "invalid" : _stagePrefixes[index]; + } + + private static int PackSbCbInfo(int sbCbSlot, int sbCbOffset) + { + return sbCbOffset | (sbCbSlot << 16); + } + + private static (int, int) UnpackSbCbInfo(int key) + { + return ((byte)(key >> 16), (ushort)key); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs new file mode 100644 index 00000000..c89c4d0b --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs @@ -0,0 +1,203 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System.Collections.Generic; +using System.Numerics; + +namespace Ryujinx.Graphics.Shader.Translation +{ + public class ResourceReservations + { + public const int TfeBuffersCount = 4; + + public const int MaxVertexBufferTextures = 32; + + private const int TextureSetIndex = 2; // TODO: Get from GPU accessor. + + public int VertexInfoConstantBufferBinding { get; } + public int VertexOutputStorageBufferBinding { get; } + public int GeometryVertexOutputStorageBufferBinding { get; } + public int GeometryIndexOutputStorageBufferBinding { get; } + public int IndexBufferTextureBinding { get; } + public int TopologyRemapBufferTextureBinding { get; } + + public int ReservedConstantBuffers { get; } + public int ReservedStorageBuffers { get; } + public int ReservedTextures { get; } + public int ReservedImages { get; } + public int InputSizePerInvocation { get; } + public int OutputSizePerInvocation { get; } + public int OutputSizeInBytesPerInvocation => OutputSizePerInvocation * sizeof(uint); + + private readonly int _tfeBufferSbBaseBinding; + private readonly int _vertexBufferTextureBaseBinding; + + private readonly Dictionary _offsets; + internal IReadOnlyDictionary Offsets => _offsets; + + internal ResourceReservations(bool isTransformFeedbackEmulated, bool vertexAsCompute) + { + // All stages reserves the first constant buffer binding for the support buffer. + ReservedConstantBuffers = 1; + ReservedStorageBuffers = 0; + ReservedTextures = 0; + ReservedImages = 0; + + if (isTransformFeedbackEmulated) + { + // Transform feedback emulation currently always uses 4 storage buffers. + _tfeBufferSbBaseBinding = ReservedStorageBuffers; + ReservedStorageBuffers = TfeBuffersCount; + } + + if (vertexAsCompute) + { + // One constant buffer reserved for vertex related state. + VertexInfoConstantBufferBinding = ReservedConstantBuffers++; + + // One storage buffer for the output vertex data. + VertexOutputStorageBufferBinding = ReservedStorageBuffers++; + + // One storage buffer for the output geometry vertex data. + GeometryVertexOutputStorageBufferBinding = ReservedStorageBuffers++; + + // One storage buffer for the output geometry index data. + GeometryIndexOutputStorageBufferBinding = ReservedStorageBuffers++; + + // Enough textures reserved for all vertex attributes, plus the index buffer. + IndexBufferTextureBinding = ReservedTextures; + TopologyRemapBufferTextureBinding = ReservedTextures + 1; + _vertexBufferTextureBaseBinding = ReservedTextures + 2; + ReservedTextures += 2 + MaxVertexBufferTextures; + } + } + + internal ResourceReservations( + IGpuAccessor gpuAccessor, + bool isTransformFeedbackEmulated, + bool vertexAsCompute, + IoUsage? vacInput, + IoUsage vacOutput) : this(isTransformFeedbackEmulated, vertexAsCompute) + { + if (vertexAsCompute) + { + _offsets = new(); + + if (vacInput.HasValue) + { + InputSizePerInvocation = FillIoOffsetMap(gpuAccessor, StorageKind.Input, vacInput.Value); + } + + OutputSizePerInvocation = FillIoOffsetMap(gpuAccessor, StorageKind.Output, vacOutput); + } + } + + private int FillIoOffsetMap(IGpuAccessor gpuAccessor, StorageKind storageKind, IoUsage vacUsage) + { + int offset = 0; + + for (int c = 0; c < 4; c++) + { + _offsets.Add(new IoDefinition(storageKind, IoVariable.Position, 0, c), offset++); + } + + _offsets.Add(new IoDefinition(storageKind, IoVariable.PointSize), offset++); + + int clipDistancesWrittenMap = vacUsage.ClipDistancesWritten; + + while (clipDistancesWrittenMap != 0) + { + int index = BitOperations.TrailingZeroCount(clipDistancesWrittenMap); + + _offsets.Add(new IoDefinition(storageKind, IoVariable.ClipDistance, 0, index), offset++); + + clipDistancesWrittenMap &= ~(1 << index); + } + + if (vacUsage.UsesRtLayer) + { + _offsets.Add(new IoDefinition(storageKind, IoVariable.Layer), offset++); + } + + if (vacUsage.UsesViewportIndex && gpuAccessor.QueryHostSupportsViewportIndexVertexTessellation()) + { + _offsets.Add(new IoDefinition(storageKind, IoVariable.VertexIndex), offset++); + } + + if (vacUsage.UsesViewportMask && gpuAccessor.QueryHostSupportsViewportMask()) + { + _offsets.Add(new IoDefinition(storageKind, IoVariable.ViewportMask), offset++); + } + + int usedDefinedMap = vacUsage.UserDefinedMap; + + while (usedDefinedMap != 0) + { + int location = BitOperations.TrailingZeroCount(usedDefinedMap); + + for (int c = 0; c < 4; c++) + { + _offsets.Add(new IoDefinition(storageKind, IoVariable.UserDefined, location, c), offset++); + } + + usedDefinedMap &= ~(1 << location); + } + + return offset; + } + + internal static bool IsVectorOrArrayVariable(IoVariable variable) + { + return variable switch + { + IoVariable.ClipDistance or + IoVariable.Position => true, + _ => false, + }; + } + + public int GetTfeBufferStorageBufferBinding(int bufferIndex) + { + return _tfeBufferSbBaseBinding + bufferIndex; + } + + public int GetVertexBufferTextureBinding(int vaLocation) + { + return _vertexBufferTextureBaseBinding + vaLocation; + } + + public SetBindingPair GetVertexBufferTextureSetAndBinding(int vaLocation) + { + return new SetBindingPair(TextureSetIndex, GetVertexBufferTextureBinding(vaLocation)); + } + + public SetBindingPair GetIndexBufferTextureSetAndBinding() + { + return new SetBindingPair(TextureSetIndex, IndexBufferTextureBinding); + } + + public SetBindingPair GetTopologyRemapBufferTextureSetAndBinding() + { + return new SetBindingPair(TextureSetIndex, TopologyRemapBufferTextureBinding); + } + + internal bool TryGetOffset(StorageKind storageKind, int location, int component, out int offset) + { + return _offsets.TryGetValue(new IoDefinition(storageKind, IoVariable.UserDefined, location, component), out offset); + } + + internal bool TryGetOffset(StorageKind storageKind, IoVariable ioVariable, int location, int component, out int offset) + { + return _offsets.TryGetValue(new IoDefinition(storageKind, ioVariable, location, component), out offset); + } + + internal bool TryGetOffset(StorageKind storageKind, IoVariable ioVariable, int component, out int offset) + { + return _offsets.TryGetValue(new IoDefinition(storageKind, ioVariable, 0, component), out offset); + } + + internal bool TryGetOffset(StorageKind storageKind, IoVariable ioVariable, out int offset) + { + return _offsets.TryGetValue(new IoDefinition(storageKind, ioVariable, 0, 0), out offset); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs b/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs new file mode 100644 index 00000000..f831ec94 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/ShaderDefinitions.cs @@ -0,0 +1,355 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace Ryujinx.Graphics.Shader.Translation +{ + class ShaderDefinitions + { + private readonly GpuGraphicsState _graphicsState; + + public ShaderStage Stage { get; } + + public int ComputeLocalSizeX { get; } + public int ComputeLocalSizeY { get; } + public int ComputeLocalSizeZ { get; } + + public bool TessCw => _graphicsState.TessCw; + public TessPatchType TessPatchType => _graphicsState.TessPatchType; + public TessSpacing TessSpacing => _graphicsState.TessSpacing; + + public bool AlphaToCoverageDitherEnable => _graphicsState.AlphaToCoverageEnable && _graphicsState.AlphaToCoverageDitherEnable; + public bool ViewportTransformDisable => _graphicsState.ViewportTransformDisable; + + public bool DepthMode => _graphicsState.DepthMode; + + public float PointSize => _graphicsState.PointSize; + + public AlphaTestOp AlphaTestCompare => _graphicsState.AlphaTestCompare; + public float AlphaTestReference => _graphicsState.AlphaTestReference; + + public bool GpPassthrough { get; } + public bool LastInVertexPipeline { get; set; } + + public int ThreadsPerInputPrimitive { get; private set; } + + public InputTopology InputTopology => _graphicsState.Topology; + public OutputTopology OutputTopology { get; } + + public int MaxOutputVertices { get; } + + public bool DualSourceBlend => _graphicsState.DualSourceBlendEnable; + public bool EarlyZForce => _graphicsState.EarlyZForce; + + public bool YNegateEnabled => _graphicsState.YNegateEnabled; + public bool OriginUpperLeft => _graphicsState.OriginUpperLeft; + + public bool HalvePrimitiveId => _graphicsState.HalvePrimitiveId; + + public ImapPixelType[] ImapTypes { get; } + public bool IaIndexing { get; private set; } + public bool OaIndexing { get; private set; } + + public int OmapTargets { get; } + public bool OmapSampleMask { get; } + public bool OmapDepth { get; } + + public bool SupportsScaledVertexFormats { get; } + + public bool TransformFeedbackEnabled { get; } + + private readonly TransformFeedbackOutput[] _transformFeedbackOutputs; + + readonly struct TransformFeedbackVariable : IEquatable + { + public IoVariable IoVariable { get; } + public int Location { get; } + public int Component { get; } + + public TransformFeedbackVariable(IoVariable ioVariable, int location = 0, int component = 0) + { + IoVariable = ioVariable; + Location = location; + Component = component; + } + + public override bool Equals(object other) + { + return other is TransformFeedbackVariable tfbVar && Equals(tfbVar); + } + + public bool Equals(TransformFeedbackVariable other) + { + return IoVariable == other.IoVariable && + Location == other.Location && + Component == other.Component; + } + + public override int GetHashCode() + { + return (int)IoVariable | (Location << 8) | (Component << 16); + } + + public override string ToString() + { + return $"{IoVariable}.{Location}.{Component}"; + } + } + + private readonly Dictionary _transformFeedbackDefinitions; + + public ShaderDefinitions(ShaderStage stage, ulong transformFeedbackVecMap, TransformFeedbackOutput[] transformFeedbackOutputs) + { + Stage = stage; + TransformFeedbackEnabled = transformFeedbackOutputs != null; + _transformFeedbackOutputs = transformFeedbackOutputs; + _transformFeedbackDefinitions = new(); + + PopulateTransformFeedbackDefinitions(transformFeedbackVecMap, transformFeedbackOutputs); + } + + public ShaderDefinitions( + ShaderStage stage, + int computeLocalSizeX, + int computeLocalSizeY, + int computeLocalSizeZ) + { + Stage = stage; + ComputeLocalSizeX = computeLocalSizeX; + ComputeLocalSizeY = computeLocalSizeY; + ComputeLocalSizeZ = computeLocalSizeZ; + } + + public ShaderDefinitions( + ShaderStage stage, + GpuGraphicsState graphicsState, + bool gpPassthrough, + int threadsPerInputPrimitive, + OutputTopology outputTopology, + int maxOutputVertices) + { + Stage = stage; + _graphicsState = graphicsState; + GpPassthrough = gpPassthrough; + ThreadsPerInputPrimitive = threadsPerInputPrimitive; + OutputTopology = outputTopology; + MaxOutputVertices = maxOutputVertices; + } + + public ShaderDefinitions( + ShaderStage stage, + GpuGraphicsState graphicsState, + bool gpPassthrough, + int threadsPerInputPrimitive, + OutputTopology outputTopology, + int maxOutputVertices, + ImapPixelType[] imapTypes, + int omapTargets, + bool omapSampleMask, + bool omapDepth, + bool supportsScaledVertexFormats, + ulong transformFeedbackVecMap, + TransformFeedbackOutput[] transformFeedbackOutputs) + { + Stage = stage; + _graphicsState = graphicsState; + GpPassthrough = gpPassthrough; + ThreadsPerInputPrimitive = threadsPerInputPrimitive; + OutputTopology = outputTopology; + MaxOutputVertices = gpPassthrough ? graphicsState.Topology.ToInputVerticesNoAdjacency() : maxOutputVertices; + ImapTypes = imapTypes; + OmapTargets = omapTargets; + OmapSampleMask = omapSampleMask; + OmapDepth = omapDepth; + LastInVertexPipeline = stage < ShaderStage.Fragment; + SupportsScaledVertexFormats = supportsScaledVertexFormats; + TransformFeedbackEnabled = transformFeedbackOutputs != null; + _transformFeedbackOutputs = transformFeedbackOutputs; + _transformFeedbackDefinitions = new(); + + PopulateTransformFeedbackDefinitions(transformFeedbackVecMap, transformFeedbackOutputs); + } + + private void PopulateTransformFeedbackDefinitions(ulong transformFeedbackVecMap, TransformFeedbackOutput[] transformFeedbackOutputs) + { + while (transformFeedbackVecMap != 0) + { + int vecIndex = BitOperations.TrailingZeroCount(transformFeedbackVecMap); + + for (int subIndex = 0; subIndex < 4; subIndex++) + { + int wordOffset = vecIndex * 4 + subIndex; + int byteOffset = wordOffset * 4; + + if (transformFeedbackOutputs[wordOffset].Valid) + { + IoVariable ioVariable = Instructions.AttributeMap.GetIoVariable(this, byteOffset, out int location); + int component = 0; + + if (HasPerLocationInputOrOutputComponent(ioVariable, location, subIndex, isOutput: true)) + { + component = subIndex; + } + + var transformFeedbackVariable = new TransformFeedbackVariable(ioVariable, location, component); + _transformFeedbackDefinitions.TryAdd(transformFeedbackVariable, transformFeedbackOutputs[wordOffset]); + } + } + + transformFeedbackVecMap &= ~(1UL << vecIndex); + } + } + + public void EnableInputIndexing() + { + IaIndexing = true; + } + + public void EnableOutputIndexing() + { + OaIndexing = true; + } + + public bool TryGetTransformFeedbackOutput(IoVariable ioVariable, int location, int component, out TransformFeedbackOutput transformFeedbackOutput) + { + if (!HasTransformFeedbackOutputs()) + { + transformFeedbackOutput = default; + return false; + } + + var transformFeedbackVariable = new TransformFeedbackVariable(ioVariable, location, component); + return _transformFeedbackDefinitions.TryGetValue(transformFeedbackVariable, out transformFeedbackOutput); + } + + private bool HasTransformFeedbackOutputs() + { + return TransformFeedbackEnabled && (LastInVertexPipeline || Stage == ShaderStage.Fragment); + } + + public bool HasTransformFeedbackOutputs(bool isOutput) + { + return TransformFeedbackEnabled && ((isOutput && LastInVertexPipeline) || (!isOutput && Stage == ShaderStage.Fragment)); + } + + public bool HasPerLocationInputOrOutput(IoVariable ioVariable, bool isOutput) + { + if (ioVariable == IoVariable.UserDefined) + { + return (!isOutput && !IaIndexing) || (isOutput && !OaIndexing); + } + + return ioVariable == IoVariable.FragmentOutputColor; + } + + public bool HasPerLocationInputOrOutputComponent(IoVariable ioVariable, int location, int component, bool isOutput) + { + if (ioVariable != IoVariable.UserDefined || !HasTransformFeedbackOutputs(isOutput)) + { + return false; + } + + return GetTransformFeedbackOutputComponents(location, component) == 1; + } + + public TransformFeedbackOutput GetTransformFeedbackOutput(int wordOffset) + { + return _transformFeedbackOutputs[wordOffset]; + } + + public TransformFeedbackOutput GetTransformFeedbackOutput(int location, int component) + { + return GetTransformFeedbackOutput((AttributeConsts.UserAttributeBase / 4) + location * 4 + component); + } + + public int GetTransformFeedbackOutputComponents(int location, int component) + { + int baseIndex = (AttributeConsts.UserAttributeBase / 4) + location * 4; + int index = baseIndex + component; + 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; + } + + public AggregateType GetFragmentOutputColorType(int location) + { + return AggregateType.Vector4 | _graphicsState.FragmentOutputTypes[location].ToAggregateType(); + } + + public AggregateType GetUserDefinedType(int location, bool isOutput) + { + if ((!isOutput && IaIndexing) || (isOutput && OaIndexing)) + { + return AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32; + } + + AggregateType type = AggregateType.Vector4; + + if (Stage == ShaderStage.Vertex && !isOutput) + { + type |= _graphicsState.AttributeTypes[location].ToAggregateType(SupportsScaledVertexFormats); + } + else + { + type |= AggregateType.FP32; + } + + return type; + } + + public AttributeType GetAttributeType(int location) + { + return _graphicsState.AttributeTypes[location]; + } + + public bool IsAttributeSint(int location) + { + return (_graphicsState.AttributeTypes[location] & ~AttributeType.AnyPacked) == AttributeType.Sint; + } + + public bool IsAttributePacked(int location) + { + return _graphicsState.AttributeTypes[location].HasFlag(AttributeType.Packed); + } + + public bool IsAttributePackedRgb10A2Signed(int location) + { + return _graphicsState.AttributeTypes[location].HasFlag(AttributeType.PackedRgb10A2Signed); + } + + public int GetGeometryOutputIndexBufferStridePerInstance() + { + return MaxOutputVertices + OutputTopology switch + { + OutputTopology.LineStrip => MaxOutputVertices / 2, + OutputTopology.TriangleStrip => MaxOutputVertices / 3, + _ => MaxOutputVertices, + }; + } + + public int GetGeometryOutputIndexBufferStride() + { + return GetGeometryOutputIndexBufferStridePerInstance() * ThreadsPerInputPrimitive; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/ShaderHeader.cs b/src/Ryujinx.Graphics.Shader/Translation/ShaderHeader.cs new file mode 100644 index 00000000..39f01b1c --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/ShaderHeader.cs @@ -0,0 +1,168 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Shader.Translation +{ + enum PixelImap + { + Unused = 0, + Constant = 1, + Perspective = 2, + ScreenLinear = 3, + } + + readonly struct ImapPixelType + { + public PixelImap X { get; } + public PixelImap Y { get; } + public PixelImap Z { get; } + public PixelImap W { get; } + + public ImapPixelType(PixelImap x, PixelImap y, PixelImap z, PixelImap w) + { + X = x; + Y = y; + Z = z; + W = w; + } + + public PixelImap GetFirstUsedType() + { + if (X != PixelImap.Unused) + { + return X; + } + if (Y != PixelImap.Unused) + { + return Y; + } + if (Z != PixelImap.Unused) + { + return Z; + } + + return W; + } + } + + class ShaderHeader + { + public int SphType { get; } + public int Version { get; } + + public ShaderStage Stage { get; } + + public bool MrtEnable { get; } + + public bool KillsPixels { get; } + + public bool DoesGlobalStore { get; } + + public int SassVersion { get; } + + public bool GpPassthrough { get; } + + public bool DoesLoadOrStore { get; } + public bool DoesFp64 { get; } + + public int StreamOutMask { get; } + + public int ShaderLocalMemoryLowSize { get; } + + public int PerPatchAttributeCount { get; } + + public int ShaderLocalMemoryHighSize { get; } + + public int ThreadsPerInputPrimitive { get; } + + public int ShaderLocalMemoryCrsSize { get; } + + public OutputTopology OutputTopology { get; } + + public int MaxOutputVertexCount { get; } + + public int StoreReqStart { get; } + public int StoreReqEnd { get; } + + public ImapPixelType[] ImapTypes { get; } + + public int OmapTargets { get; } + public bool OmapSampleMask { get; } + public bool OmapDepth { get; } + + public ShaderHeader(IGpuAccessor gpuAccessor, ulong address) + { + ReadOnlySpan header = MemoryMarshal.Cast(gpuAccessor.GetCode(address, 0x50)); + + int commonWord0 = header[0]; + int commonWord1 = header[1]; + int commonWord2 = header[2]; + int commonWord3 = header[3]; + int commonWord4 = header[4]; + + SphType = commonWord0.Extract(0, 5); + Version = commonWord0.Extract(5, 5); + + Stage = (ShaderStage)commonWord0.Extract(10, 4); + + // Invalid. + if (Stage == ShaderStage.Compute) + { + Stage = ShaderStage.Vertex; + } + + MrtEnable = commonWord0.Extract(14); + + KillsPixels = commonWord0.Extract(15); + + DoesGlobalStore = commonWord0.Extract(16); + + SassVersion = commonWord0.Extract(17, 4); + + GpPassthrough = commonWord0.Extract(24); + + DoesLoadOrStore = commonWord0.Extract(26); + DoesFp64 = commonWord0.Extract(27); + + StreamOutMask = commonWord0.Extract(28, 4); + + ShaderLocalMemoryLowSize = commonWord1.Extract(0, 24); + + PerPatchAttributeCount = commonWord1.Extract(24, 8); + + ShaderLocalMemoryHighSize = commonWord2.Extract(0, 24); + + ThreadsPerInputPrimitive = commonWord2.Extract(24, 8); + + ShaderLocalMemoryCrsSize = commonWord3.Extract(0, 24); + + OutputTopology = (OutputTopology)commonWord3.Extract(24, 4); + + MaxOutputVertexCount = commonWord4.Extract(0, 12); + + StoreReqStart = commonWord4.Extract(12, 8); + StoreReqEnd = commonWord4.Extract(24, 8); + + ImapTypes = new ImapPixelType[32]; + + for (int i = 0; i < 32; i++) + { + byte imap = (byte)(header[6 + (i >> 2)] >> ((i & 3) * 8)); + + ImapTypes[i] = new ImapPixelType( + (PixelImap)((imap >> 0) & 3), + (PixelImap)((imap >> 2) & 3), + (PixelImap)((imap >> 4) & 3), + (PixelImap)((imap >> 6) & 3)); + } + + int type2OmapTarget = header[18]; + int type2Omap = header[19]; + + OmapTargets = type2OmapTarget; + OmapSampleMask = type2Omap.Extract(0); + OmapDepth = type2Omap.Extract(1); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Ssa.cs b/src/Ryujinx.Graphics.Shader/Translation/Ssa.cs new file mode 100644 index 00000000..89aaa3b4 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Ssa.cs @@ -0,0 +1,375 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + static class Ssa + { + private const int GprsAndPredsCount = RegisterConsts.GprsCount + RegisterConsts.PredsCount; + + private class DefMap + { + private readonly Dictionary _map; + + private readonly long[] _phiMasks; + + public DefMap() + { + _map = new Dictionary(); + + _phiMasks = new long[(RegisterConsts.TotalCount + 63) / 64]; + } + + public bool TryAddOperand(Register reg, Operand operand) + { + return _map.TryAdd(reg, operand); + } + + public bool TryGetOperand(Register reg, out Operand operand) + { + return _map.TryGetValue(reg, out operand); + } + + public bool AddPhi(Register reg) + { + int key = GetKeyFromRegister(reg); + + int index = key / 64; + int bit = key & 63; + + long mask = 1L << bit; + + if ((_phiMasks[index] & mask) != 0) + { + return false; + } + + _phiMasks[index] |= mask; + + return true; + } + + public bool HasPhi(Register reg) + { + int key = GetKeyFromRegister(reg); + + int index = key / 64; + int bit = key & 63; + + return (_phiMasks[index] & (1L << bit)) != 0; + } + } + + private class LocalDefMap + { + private readonly Operand[] _map; + private readonly int[] _uses; + public int UseCount { get; private set; } + + public LocalDefMap() + { + _map = new Operand[RegisterConsts.TotalCount]; + _uses = new int[RegisterConsts.TotalCount]; + } + + public Operand Get(int key) + { + return _map[key]; + } + + public void Add(int key, Operand operand) + { + if (_map[key] == null) + { + _uses[UseCount++] = key; + } + + _map[key] = operand; + } + + public Operand GetUse(int index, out int key) + { + key = _uses[index]; + + return _map[key]; + } + + public void Clear() + { + for (int i = 0; i < UseCount; i++) + { + _map[_uses[i]] = null; + } + + UseCount = 0; + } + } + + private readonly struct Definition + { + public BasicBlock Block { get; } + public Operand Local { get; } + + public Definition(BasicBlock block, Operand local) + { + Block = block; + Local = local; + } + } + + public static void Rename(BasicBlock[] blocks) + { + DefMap[] globalDefs = new DefMap[blocks.Length]; + LocalDefMap localDefs = new(); + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + globalDefs[blkIndex] = new DefMap(); + } + + Queue dfPhiBlocks = new(); + + // First pass, get all defs and locals uses. + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + Operand RenameLocal(Operand operand) + { + if (operand != null && operand.Type == OperandType.Register) + { + Operand local = localDefs.Get(GetKeyFromRegister(operand.GetRegister())); + + operand = local ?? operand; + } + + return operand; + } + + BasicBlock block = blocks[blkIndex]; + + LinkedListNode node = block.Operations.First; + + while (node != null) + { + if (node.Value is Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, RenameLocal(operation.GetSource(index))); + } + + for (int index = 0; index < operation.DestsCount; index++) + { + Operand dest = operation.GetDest(index); + + if (dest != null && dest.Type == OperandType.Register) + { + Operand local = Local(); + + localDefs.Add(GetKeyFromRegister(dest.GetRegister()), local); + + operation.SetDest(index, local); + } + } + } + + node = node.Next; + } + + int localUses = localDefs.UseCount; + for (int index = 0; index < localUses; index++) + { + Operand local = localDefs.GetUse(index, out int key); + + Register reg = GetRegisterFromKey(key); + + globalDefs[block.Index].TryAddOperand(reg, local); + + dfPhiBlocks.Enqueue(block); + + while (dfPhiBlocks.TryDequeue(out BasicBlock dfPhiBlock)) + { + foreach (BasicBlock domFrontier in dfPhiBlock.DominanceFrontiers) + { + if (globalDefs[domFrontier.Index].AddPhi(reg)) + { + dfPhiBlocks.Enqueue(domFrontier); + } + } + } + } + + localDefs.Clear(); + } + + // Second pass, rename variables with definitions on different blocks. + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + BasicBlock block = blocks[blkIndex]; + + Operand RenameGlobal(Operand operand) + { + if (operand != null && operand.Type == OperandType.Register) + { + int key = GetKeyFromRegister(operand.GetRegister()); + + Operand local = localDefs.Get(key); + + if (local != null) + { + return local; + } + + operand = FindDefinitionForCurr(globalDefs, block, operand.GetRegister()); + + localDefs.Add(key, operand); + } + + return operand; + } + + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (node.Value is Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + operation.SetSource(index, RenameGlobal(operation.GetSource(index))); + } + } + } + + if (blkIndex < blocks.Length - 1) + { + localDefs.Clear(); + } + } + } + + private static Operand FindDefinitionForCurr(DefMap[] globalDefs, BasicBlock current, Register reg) + { + if (globalDefs[current.Index].HasPhi(reg)) + { + return InsertPhi(globalDefs, current, reg); + } + + if (current != current.ImmediateDominator) + { + return FindDefinition(globalDefs, current.ImmediateDominator, reg).Local; + } + + return Undef(); + } + + private static Definition FindDefinition(DefMap[] globalDefs, BasicBlock current, Register reg) + { + foreach (BasicBlock block in SelfAndImmediateDominators(current)) + { + DefMap defMap = globalDefs[block.Index]; + + if (defMap.TryGetOperand(reg, out Operand lastDef)) + { + return new Definition(block, lastDef); + } + + if (defMap.HasPhi(reg)) + { + return new Definition(block, InsertPhi(globalDefs, block, reg)); + } + } + + return new Definition(current, Undef()); + } + + private static IEnumerable SelfAndImmediateDominators(BasicBlock block) + { + while (block != block.ImmediateDominator) + { + yield return block; + + block = block.ImmediateDominator; + } + + yield return block; + } + + private static Operand InsertPhi(DefMap[] globalDefs, BasicBlock block, Register reg) + { + // This block has a Phi that has not been materialized yet, but that + // would define a new version of the variable we're looking for. We need + // to materialize the Phi, add all the block/operand pairs into the Phi, and + // then use the definition from that Phi. + Operand local = Local(); + + PhiNode phi = new(local); + + AddPhi(block, phi); + + globalDefs[block.Index].TryAddOperand(reg, local); + + foreach (BasicBlock predecessor in block.Predecessors) + { + Definition def = FindDefinition(globalDefs, predecessor, reg); + + phi.AddSource(def.Block, def.Local); + } + + return local; + } + + private static void AddPhi(BasicBlock block, PhiNode phi) + { + LinkedListNode node = block.Operations.First; + + if (node != null) + { + while (node.Next?.Value is PhiNode) + { + node = node.Next; + } + } + + if (node?.Value is PhiNode) + { + block.Operations.AddAfter(node, phi); + } + else + { + block.Operations.AddFirst(phi); + } + } + + private static int GetKeyFromRegister(Register reg) + { + if (reg.Type == RegisterType.Gpr) + { + return reg.Index; + } + else if (reg.Type == RegisterType.Predicate) + { + return RegisterConsts.GprsCount + reg.Index; + } + else /* if (reg.Type == RegisterType.Flag) */ + { + return GprsAndPredsCount + reg.Index; + } + } + + private static Register GetRegisterFromKey(int key) + { + if (key < RegisterConsts.GprsCount) + { + return new Register(key, RegisterType.Gpr); + } + else if (key < GprsAndPredsCount) + { + return new Register(key - RegisterConsts.GprsCount, RegisterType.Predicate); + } + else /* if (key < RegisterConsts.TotalCount) */ + { + return new Register(key - GprsAndPredsCount, RegisterType.Flag); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/TargetApi.cs b/src/Ryujinx.Graphics.Shader/Translation/TargetApi.cs new file mode 100644 index 00000000..51960093 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/TargetApi.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Shader.Translation +{ + public enum TargetApi + { + OpenGL, + Vulkan, + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/TargetLanguage.cs b/src/Ryujinx.Graphics.Shader/Translation/TargetLanguage.cs new file mode 100644 index 00000000..863c7447 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/TargetLanguage.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Graphics.Shader.Translation +{ + public enum TargetLanguage + { + Glsl, + Spirv, + Arb, + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs new file mode 100644 index 00000000..1e87585f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/TransformContext.cs @@ -0,0 +1,39 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; + +namespace Ryujinx.Graphics.Shader.Translation +{ + readonly ref struct TransformContext + { + public readonly HelperFunctionManager Hfm; + public readonly BasicBlock[] Blocks; + public readonly ShaderDefinitions Definitions; + public readonly ResourceManager ResourceManager; + public readonly IGpuAccessor GpuAccessor; + public readonly TargetApi TargetApi; + public readonly TargetLanguage TargetLanguage; + public readonly ShaderStage Stage; + public readonly ref FeatureFlags UsedFeatures; + + public TransformContext( + HelperFunctionManager hfm, + BasicBlock[] blocks, + ShaderDefinitions definitions, + ResourceManager resourceManager, + IGpuAccessor gpuAccessor, + TargetApi targetApi, + TargetLanguage targetLanguage, + ShaderStage stage, + ref FeatureFlags usedFeatures) + { + Hfm = hfm; + Blocks = blocks; + Definitions = definitions; + ResourceManager = resourceManager; + GpuAccessor = gpuAccessor; + TargetApi = targetApi; + TargetLanguage = targetLanguage; + Stage = stage; + UsedFeatures = ref usedFeatures; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/TransformFeedbackOutput.cs b/src/Ryujinx.Graphics.Shader/Translation/TransformFeedbackOutput.cs new file mode 100644 index 00000000..7d5c7462 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/TransformFeedbackOutput.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.Shader.Translation +{ + readonly struct TransformFeedbackOutput + { + public readonly bool Valid; + public readonly int Buffer; + public readonly int Offset; + public readonly int Stride; + + public TransformFeedbackOutput(int buffer, int offset, int stride) + { + Valid = true; + Buffer = buffer; + Offset = offset; + Stride = stride; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/DrawParametersReplace.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/DrawParametersReplace.cs new file mode 100644 index 00000000..9e73013d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/DrawParametersReplace.cs @@ -0,0 +1,93 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Transforms +{ + class DrawParametersReplace : ITransformPass + { + public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures) + { + return stage == ShaderStage.Vertex; + } + + public static LinkedListNode RunPass(TransformContext context, LinkedListNode node) + { + Operation operation = (Operation)node.Value; + + if (context.GpuAccessor.QueryHasConstantBufferDrawParameters()) + { + if (ReplaceConstantBufferWithDrawParameters(node, operation)) + { + context.UsedFeatures |= FeatureFlags.DrawParameters; + } + } + else if (HasConstantBufferDrawParameters(operation)) + { + context.UsedFeatures |= FeatureFlags.DrawParameters; + } + + return node; + } + + private static bool ReplaceConstantBufferWithDrawParameters(LinkedListNode node, Operation operation) + { + Operand GenerateLoad(IoVariable ioVariable) + { + Operand value = Local(); + node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.Input, value, Const((int)ioVariable))); + return value; + } + + bool modified = false; + + for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++) + { + Operand src = operation.GetSource(srcIndex); + + if (src.Type == OperandType.ConstantBuffer && src.GetCbufSlot() == 0) + { + switch (src.GetCbufOffset()) + { + case Constants.NvnBaseVertexByteOffset / 4: + operation.SetSource(srcIndex, GenerateLoad(IoVariable.BaseVertex)); + modified = true; + break; + case Constants.NvnBaseInstanceByteOffset / 4: + operation.SetSource(srcIndex, GenerateLoad(IoVariable.BaseInstance)); + modified = true; + break; + case Constants.NvnDrawIndexByteOffset / 4: + operation.SetSource(srcIndex, GenerateLoad(IoVariable.DrawIndex)); + modified = true; + break; + } + } + } + + return modified; + } + + private static bool HasConstantBufferDrawParameters(Operation operation) + { + for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++) + { + Operand src = operation.GetSource(srcIndex); + + if (src.Type == OperandType.ConstantBuffer && src.GetCbufSlot() == 0) + { + switch (src.GetCbufOffset()) + { + case Constants.NvnBaseVertexByteOffset / 4: + case Constants.NvnBaseInstanceByteOffset / 4: + case Constants.NvnDrawIndexByteOffset / 4: + return true; + } + } + } + + return false; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/ForcePreciseEnable.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/ForcePreciseEnable.cs new file mode 100644 index 00000000..6b7e1410 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/ForcePreciseEnable.cs @@ -0,0 +1,36 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Translation.Transforms +{ + class ForcePreciseEnable : ITransformPass + { + public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures) + { + return stage == ShaderStage.Fragment && gpuAccessor.QueryHostReducedPrecision(); + } + + public static LinkedListNode RunPass(TransformContext context, LinkedListNode node) + { + // There are some cases where a small bias is added to values to prevent division by zero. + // When operating with reduced precision, it is possible for this bias to get rounded to 0 + // and cause a division by zero. + // To prevent that, we force those operations to be precise even if the host wants + // imprecise operations for performance. + + Operation operation = (Operation)node.Value; + + if (operation.Inst == (Instruction.FP32 | Instruction.Divide) && + operation.GetSource(0).Type == OperandType.Constant && + operation.GetSource(0).AsFloat() == 1f && + operation.GetSource(1).AsgOp is Operation addOp && + addOp.Inst == (Instruction.FP32 | Instruction.Add) && + addOp.GetSource(1).Type == OperandType.Constant) + { + addOp.ForcePrecise = true; + } + + return node; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/GeometryToCompute.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/GeometryToCompute.cs new file mode 100644 index 00000000..0013cf0e --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/GeometryToCompute.cs @@ -0,0 +1,378 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation.Optimizations; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Transforms +{ + class GeometryToCompute : ITransformPass + { + public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures) + { + return usedFeatures.HasFlag(FeatureFlags.VtgAsCompute); + } + + public static LinkedListNode RunPass(TransformContext context, LinkedListNode node) + { + if (context.Definitions.Stage != ShaderStage.Geometry) + { + return node; + } + + Operation operation = (Operation)node.Value; + + LinkedListNode newNode = node; + + switch (operation.Inst) + { + case Instruction.EmitVertex: + newNode = GenerateEmitVertex(context.Definitions, context.ResourceManager, node); + break; + case Instruction.EndPrimitive: + newNode = GenerateEndPrimitive(context.Definitions, context.ResourceManager, node); + break; + case Instruction.Load: + if (operation.StorageKind == StorageKind.Input) + { + IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value; + + if (TryGetOffset(context.ResourceManager, operation, StorageKind.Input, out int inputOffset)) + { + Operand primVertex = ioVariable == IoVariable.UserDefined + ? operation.GetSource(2) + : operation.GetSource(1); + + Operand vertexElemOffset = GenerateVertexOffset(context.ResourceManager, node, inputOffset, primVertex); + + newNode = node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.StorageBuffer, + operation.Dest, + new[] { Const(context.ResourceManager.Reservations.VertexOutputStorageBufferBinding), Const(0), vertexElemOffset })); + } + else + { + switch (ioVariable) + { + case IoVariable.InvocationId: + newNode = GenerateInvocationId(node, operation.Dest); + break; + case IoVariable.PrimitiveId: + newNode = GeneratePrimitiveId(context.ResourceManager, node, operation.Dest); + break; + case IoVariable.GlobalId: + case IoVariable.SubgroupEqMask: + case IoVariable.SubgroupGeMask: + case IoVariable.SubgroupGtMask: + case IoVariable.SubgroupLaneId: + case IoVariable.SubgroupLeMask: + case IoVariable.SubgroupLtMask: + // Those are valid or expected for geometry shaders. + break; + default: + context.GpuAccessor.Log($"Invalid input \"{ioVariable}\"."); + break; + } + } + } + else if (operation.StorageKind == StorageKind.Output) + { + if (TryGetOffset(context.ResourceManager, operation, StorageKind.Output, out int outputOffset)) + { + newNode = node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.LocalMemory, + operation.Dest, + new[] { Const(context.ResourceManager.LocalVertexDataMemoryId), Const(outputOffset) })); + } + else + { + context.GpuAccessor.Log($"Invalid output \"{(IoVariable)operation.GetSource(0).Value}\"."); + } + } + break; + case Instruction.Store: + if (operation.StorageKind == StorageKind.Output) + { + if (TryGetOffset(context.ResourceManager, operation, StorageKind.Output, out int outputOffset)) + { + Operand value = operation.GetSource(operation.SourcesCount - 1); + + newNode = node.List.AddBefore(node, new Operation( + Instruction.Store, + StorageKind.LocalMemory, + (Operand)null, + new[] { Const(context.ResourceManager.LocalVertexDataMemoryId), Const(outputOffset), value })); + } + else + { + context.GpuAccessor.Log($"Invalid output \"{(IoVariable)operation.GetSource(0).Value}\"."); + } + } + break; + } + + if (newNode != node) + { + Utils.DeleteNode(node, operation); + } + + return newNode; + } + + private static LinkedListNode GenerateEmitVertex(ShaderDefinitions definitions, ResourceManager resourceManager, LinkedListNode node) + { + int vbOutputBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding; + int ibOutputBinding = resourceManager.Reservations.GeometryIndexOutputStorageBufferBinding; + int stride = resourceManager.Reservations.OutputSizePerInvocation; + + Operand outputPrimVertex = IncrementLocalMemory(node, resourceManager.LocalGeometryOutputVertexCountMemoryId); + Operand baseVertexOffset = GenerateBaseOffset( + resourceManager, + node, + definitions.MaxOutputVertices * definitions.ThreadsPerInputPrimitive, + definitions.ThreadsPerInputPrimitive); + Operand outputBaseVertex = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, outputBaseVertex, new[] { baseVertexOffset, outputPrimVertex })); + + Operand outputPrimIndex = IncrementLocalMemory(node, resourceManager.LocalGeometryOutputIndexCountMemoryId); + Operand baseIndexOffset = GenerateBaseOffset( + resourceManager, + node, + definitions.GetGeometryOutputIndexBufferStride(), + definitions.ThreadsPerInputPrimitive); + Operand outputBaseIndex = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, outputBaseIndex, new[] { baseIndexOffset, outputPrimIndex })); + + node.List.AddBefore(node, new Operation( + Instruction.Store, + StorageKind.StorageBuffer, + null, + new[] { Const(ibOutputBinding), Const(0), outputBaseIndex, outputBaseVertex })); + + Operand baseOffset = Local(); + node.List.AddBefore(node, new Operation(Instruction.Multiply, baseOffset, new[] { outputBaseVertex, Const(stride) })); + + LinkedListNode newNode = node; + + for (int offset = 0; offset < stride; offset++) + { + Operand vertexOffset; + + if (offset > 0) + { + vertexOffset = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, vertexOffset, new[] { baseOffset, Const(offset) })); + } + else + { + vertexOffset = baseOffset; + } + + Operand value = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.LocalMemory, + value, + new[] { Const(resourceManager.LocalVertexDataMemoryId), Const(offset) })); + + newNode = node.List.AddBefore(node, new Operation( + Instruction.Store, + StorageKind.StorageBuffer, + null, + new[] { Const(vbOutputBinding), Const(0), vertexOffset, value })); + } + + return newNode; + } + + private static LinkedListNode GenerateEndPrimitive(ShaderDefinitions definitions, ResourceManager resourceManager, LinkedListNode node) + { + int ibOutputBinding = resourceManager.Reservations.GeometryIndexOutputStorageBufferBinding; + + Operand outputPrimIndex = IncrementLocalMemory(node, resourceManager.LocalGeometryOutputIndexCountMemoryId); + Operand baseIndexOffset = GenerateBaseOffset( + resourceManager, + node, + definitions.GetGeometryOutputIndexBufferStride(), + definitions.ThreadsPerInputPrimitive); + Operand outputBaseIndex = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, outputBaseIndex, new[] { baseIndexOffset, outputPrimIndex })); + + return node.List.AddBefore(node, new Operation( + Instruction.Store, + StorageKind.StorageBuffer, + null, + new[] { Const(ibOutputBinding), Const(0), outputBaseIndex, Const(-1) })); + } + + private static Operand GenerateBaseOffset(ResourceManager resourceManager, LinkedListNode node, int stride, int threadsPerInputPrimitive) + { + Operand primitiveId = Local(); + GeneratePrimitiveId(resourceManager, node, primitiveId); + + Operand baseOffset = Local(); + node.List.AddBefore(node, new Operation(Instruction.Multiply, baseOffset, new[] { primitiveId, Const(stride) })); + + Operand invocationId = Local(); + GenerateInvocationId(node, invocationId); + + Operand invocationOffset = Local(); + node.List.AddBefore(node, new Operation(Instruction.Multiply, invocationOffset, new[] { invocationId, Const(stride / threadsPerInputPrimitive) })); + + Operand combinedOffset = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, combinedOffset, new[] { baseOffset, invocationOffset })); + + return combinedOffset; + } + + private static Operand IncrementLocalMemory(LinkedListNode node, int memoryId) + { + Operand oldValue = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.LocalMemory, + oldValue, + new[] { Const(memoryId) })); + + Operand newValue = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, newValue, new[] { oldValue, Const(1) })); + + node.List.AddBefore(node, new Operation(Instruction.Store, StorageKind.LocalMemory, null, new[] { Const(memoryId), newValue })); + + return oldValue; + } + + private static Operand GenerateVertexOffset( + ResourceManager resourceManager, + LinkedListNode node, + int elementOffset, + Operand primVertex) + { + int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; + + Operand vertexCount = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + vertexCount, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexCounts), Const(0) })); + + Operand primInputVertex = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.LocalMemory, + primInputVertex, + new[] { Const(resourceManager.LocalTopologyRemapMemoryId), primVertex })); + + Operand instanceIndex = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.Input, + instanceIndex, + new[] { Const((int)IoVariable.GlobalId), Const(1) })); + + Operand baseVertex = Local(); + node.List.AddBefore(node, new Operation(Instruction.Multiply, baseVertex, new[] { instanceIndex, vertexCount })); + + Operand vertexIndex = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, vertexIndex, new[] { baseVertex, primInputVertex })); + + Operand vertexBaseOffset = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Multiply, + vertexBaseOffset, + new[] { vertexIndex, Const(resourceManager.Reservations.InputSizePerInvocation) })); + + Operand vertexElemOffset; + + if (elementOffset != 0) + { + vertexElemOffset = Local(); + + node.List.AddBefore(node, new Operation(Instruction.Add, vertexElemOffset, new[] { vertexBaseOffset, Const(elementOffset) })); + } + else + { + vertexElemOffset = vertexBaseOffset; + } + + return vertexElemOffset; + } + + private static LinkedListNode GeneratePrimitiveId(ResourceManager resourceManager, LinkedListNode node, Operand dest) + { + int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; + + Operand vertexCount = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + vertexCount, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexCounts), Const(0) })); + + Operand vertexIndex = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.Input, + vertexIndex, + new[] { Const((int)IoVariable.GlobalId), Const(0) })); + + Operand instanceIndex = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.Input, + instanceIndex, + new[] { Const((int)IoVariable.GlobalId), Const(1) })); + + Operand baseVertex = Local(); + node.List.AddBefore(node, new Operation(Instruction.Multiply, baseVertex, new[] { instanceIndex, vertexCount })); + + return node.List.AddBefore(node, new Operation(Instruction.Add, dest, new[] { baseVertex, vertexIndex })); + } + + private static LinkedListNode GenerateInvocationId(LinkedListNode node, Operand dest) + { + return node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.Input, + dest, + new[] { Const((int)IoVariable.GlobalId), Const(2) })); + } + + private static bool TryGetOffset(ResourceManager resourceManager, Operation operation, StorageKind storageKind, out int outputOffset) + { + bool isStore = operation.Inst == Instruction.Store; + + IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value; + + bool isValidOutput; + + if (ioVariable == IoVariable.UserDefined) + { + int lastIndex = operation.SourcesCount - (isStore ? 2 : 1); + + int location = operation.GetSource(1).Value; + int component = operation.GetSource(lastIndex).Value; + + isValidOutput = resourceManager.Reservations.TryGetOffset(storageKind, location, component, out outputOffset); + } + else + { + if (ResourceReservations.IsVectorOrArrayVariable(ioVariable)) + { + int component = operation.GetSource(operation.SourcesCount - (isStore ? 2 : 1)).Value; + + isValidOutput = resourceManager.Reservations.TryGetOffset(storageKind, ioVariable, component, out outputOffset); + } + else + { + isValidOutput = resourceManager.Reservations.TryGetOffset(storageKind, ioVariable, out outputOffset); + } + } + + return isValidOutput; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/ITransformPass.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/ITransformPass.cs new file mode 100644 index 00000000..0a109d1d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/ITransformPass.cs @@ -0,0 +1,11 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Translation.Transforms +{ + interface ITransformPass + { + abstract static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures); + abstract static LinkedListNode RunPass(TransformContext context, LinkedListNode node); + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/SharedAtomicSignedCas.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/SharedAtomicSignedCas.cs new file mode 100644 index 00000000..112b3b19 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/SharedAtomicSignedCas.cs @@ -0,0 +1,58 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation.Optimizations; +using System.Collections.Generic; +using System.Diagnostics; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Transforms +{ + class SharedAtomicSignedCas : ITransformPass + { + public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures) + { + return targetLanguage != TargetLanguage.Spirv && stage == ShaderStage.Compute && usedFeatures.HasFlag(FeatureFlags.SharedMemory); + } + + public static LinkedListNode RunPass(TransformContext context, LinkedListNode node) + { + Operation operation = (Operation)node.Value; + HelperFunctionName name; + + if (operation.Inst == Instruction.AtomicMaxS32) + { + name = HelperFunctionName.SharedAtomicMaxS32; + } + else if (operation.Inst == Instruction.AtomicMinS32) + { + name = HelperFunctionName.SharedAtomicMinS32; + } + else + { + return node; + } + + if (operation.StorageKind != StorageKind.SharedMemory) + { + return node; + } + + Operand result = operation.Dest; + Operand memoryId = operation.GetSource(0); + Operand byteOffset = operation.GetSource(1); + Operand value = operation.GetSource(2); + + Debug.Assert(memoryId.Type == OperandType.Constant); + + int functionId = context.Hfm.GetOrCreateFunctionId(name, memoryId.Value); + + Operand[] callArgs = new Operand[] { Const(functionId), byteOffset, value }; + + LinkedListNode newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, result, callArgs)); + + Utils.DeleteNode(node, operation); + + return newNode; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/SharedStoreSmallIntCas.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/SharedStoreSmallIntCas.cs new file mode 100644 index 00000000..e58be0a8 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/SharedStoreSmallIntCas.cs @@ -0,0 +1,57 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation.Optimizations; +using System.Collections.Generic; +using System.Diagnostics; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Transforms +{ + class SharedStoreSmallIntCas : ITransformPass + { + public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures) + { + return stage == ShaderStage.Compute && usedFeatures.HasFlag(FeatureFlags.SharedMemory); + } + + public static LinkedListNode RunPass(TransformContext context, LinkedListNode node) + { + Operation operation = (Operation)node.Value; + HelperFunctionName name; + + if (operation.StorageKind == StorageKind.SharedMemory8) + { + name = HelperFunctionName.SharedStore8; + } + else if (operation.StorageKind == StorageKind.SharedMemory16) + { + name = HelperFunctionName.SharedStore16; + } + else + { + return node; + } + + if (operation.Inst != Instruction.Store) + { + return node; + } + + Operand memoryId = operation.GetSource(0); + Operand byteOffset = operation.GetSource(1); + Operand value = operation.GetSource(2); + + Debug.Assert(memoryId.Type == OperandType.Constant); + + int functionId = context.Hfm.GetOrCreateFunctionId(name, memoryId.Value); + + Operand[] callArgs = new Operand[] { Const(functionId), byteOffset, value }; + + LinkedListNode newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, (Operand)null, callArgs)); + + Utils.DeleteNode(node, operation); + + return newNode; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/ShufflePass.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/ShufflePass.cs new file mode 100644 index 00000000..839d4f81 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/ShufflePass.cs @@ -0,0 +1,52 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation.Optimizations; +using System.Collections.Generic; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Transforms +{ + class ShufflePass : ITransformPass + { + public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures) + { + return usedFeatures.HasFlag(FeatureFlags.Shuffle); + } + + public static LinkedListNode RunPass(TransformContext context, LinkedListNode node) + { + Operation operation = (Operation)node.Value; + + HelperFunctionName functionName = operation.Inst switch + { + Instruction.Shuffle => HelperFunctionName.Shuffle, + Instruction.ShuffleDown => HelperFunctionName.ShuffleDown, + Instruction.ShuffleUp => HelperFunctionName.ShuffleUp, + Instruction.ShuffleXor => HelperFunctionName.ShuffleXor, + _ => HelperFunctionName.Invalid, + }; + + if (functionName == HelperFunctionName.Invalid || operation.SourcesCount != 3 || operation.DestsCount != 2) + { + return node; + } + + int functionId = context.Hfm.GetOrCreateShuffleFunctionId(functionName, context.GpuAccessor.QueryHostSubgroupSize()); + + Operand result = operation.GetDest(0); + Operand valid = operation.GetDest(1); + Operand value = operation.GetSource(0); + Operand index = operation.GetSource(1); + Operand mask = operation.GetSource(2); + + operation.Dest = null; + + Operand[] callArgs = new Operand[] { Const(functionId), value, index, mask, valid }; + + LinkedListNode newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, result, callArgs)); + + Utils.DeleteNode(node, operation); + + return newNode; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs new file mode 100644 index 00000000..6ba8cb44 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs @@ -0,0 +1,748 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; +using System.Linq; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Transforms +{ + class TexturePass : ITransformPass + { + public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures) + { + return true; + } + + public static LinkedListNode RunPass(TransformContext context, LinkedListNode node) + { + if (node.Value is TextureOperation texOp) + { + node = InsertTexelFetchScale(context.Hfm, node, context.ResourceManager, context.Stage); + node = InsertTextureSizeUnscale(context.Hfm, node, context.ResourceManager, context.Stage); + + if (texOp.Inst == Instruction.TextureSample) + { + node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage); + node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor); + node = InsertConstOffsets(node, context.ResourceManager, context.GpuAccessor, context.Stage); + + if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat()) + { + node = InsertSnormNormalization(node, context.ResourceManager, context.GpuAccessor); + } + } + } + + return node; + } + + private static LinkedListNode InsertTexelFetchScale( + HelperFunctionManager hfm, + LinkedListNode node, + ResourceManager resourceManager, + ShaderStage stage) + { + TextureOperation texOp = (TextureOperation)node.Value; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + + bool isImage = IsImageInstructionWithScale(texOp.Inst); + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage); + + if ((texOp.Inst == Instruction.TextureSample || isImage) && + (intCoords || isImage) && + !isBindless && + !isIndexed && + stage.SupportsRenderScale() && + TypeSupportsScale(texOp.Type)) + { + int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale); + int samplerIndex = isImage + ? resourceManager.GetTextureDescriptors(includeArrays: false).Length + resourceManager.FindImageDescriptorIndex(texOp.Binding) + : resourceManager.FindTextureDescriptorIndex(texOp.Binding); + + int coordsCount = texOp.Type.GetDimensions(); + int coordsIndex = isBindless ? 1 : 0; + + for (int index = 0; index < coordsCount; index++) + { + Operand scaledCoord = Local(); + Operand[] callArgs; + + if (stage == ShaderStage.Fragment) + { + callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex), Const(index) }; + } + else + { + callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex) }; + } + + node.List.AddBefore(node, new Operation(Instruction.Call, 0, scaledCoord, callArgs)); + + texOp.SetSource(coordsIndex + index, scaledCoord); + } + } + + return node; + } + + private static LinkedListNode InsertTextureSizeUnscale( + HelperFunctionManager hfm, + LinkedListNode node, + ResourceManager resourceManager, + ShaderStage stage) + { + TextureOperation texOp = (TextureOperation)node.Value; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false); + + if (texOp.Inst == Instruction.TextureQuerySize && + texOp.Index < 2 && + !isBindless && + !isIndexed && + stage.SupportsRenderScale() && + TypeSupportsScale(texOp.Type)) + { + int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale); + int samplerIndex = resourceManager.FindTextureDescriptorIndex(texOp.Binding); + + for (int index = texOp.DestsCount - 1; index >= 0; index--) + { + Operand dest = texOp.GetDest(index); + + Operand unscaledSize = Local(); + + // Replace all uses with the unscaled size value. + // This must be done before the call is added, since it also is a use of the original size. + foreach (INode useOp in dest.UseOps) + { + for (int srcIndex = 0; srcIndex < useOp.SourcesCount; srcIndex++) + { + if (useOp.GetSource(srcIndex) == dest) + { + useOp.SetSource(srcIndex, unscaledSize); + } + } + } + + Operand[] callArgs = new Operand[] { Const(functionId), dest, Const(samplerIndex) }; + + node.List.AddAfter(node, new Operation(Instruction.Call, 0, unscaledSize, callArgs)); + } + } + + return node; + } + + private static LinkedListNode InsertCoordNormalization( + HelperFunctionManager hfm, + LinkedListNode node, + ResourceManager resourceManager, + IGpuAccessor gpuAccessor, + ShaderStage stage) + { + // Emulate non-normalized coordinates by normalizing the coordinates on the shader. + // Without normalization, the coordinates are expected to the in the [0, W or H] range, + // and otherwise, it is expected to be in the [0, 1] range. + // We normalize by dividing the coords by the texture size. + + TextureOperation texOp = (TextureOperation)node.Value; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false); + + if (isBindless || isIndexed || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle)) + { + return node; + } + + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + + bool isCoordNormalized = gpuAccessor.QueryTextureCoordNormalized(handle, cbufSlot); + + if (isCoordNormalized || intCoords) + { + return node; + } + + int coordsCount = texOp.Type.GetDimensions(); + + int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount; + + for (int index = 0; index < normCoordsCount; index++) + { + Operand coordSize = Local(); + + Operand[] texSizeSources = new Operand[] { Const(0) }; + + LinkedListNode textureSizeNode = node.List.AddBefore(node, new TextureOperation( + Instruction.TextureQuerySize, + texOp.Type, + texOp.Format, + texOp.Flags, + texOp.Set, + texOp.Binding, + index, + new[] { coordSize }, + texSizeSources)); + + resourceManager.SetUsageFlagsForTextureQuery(texOp.Binding, texOp.Type); + + Operand source = texOp.GetSource(index); + + Operand coordNormalized = Local(); + + node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, GenerateI2f(node, coordSize))); + + texOp.SetSource(index, coordNormalized); + + InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage); + } + + return node; + } + + private static LinkedListNode InsertCoordGatherBias(LinkedListNode node, ResourceManager resourceManager, IGpuAccessor gpuAccessor) + { + // The gather behavior when the coordinate sits right in the middle of two texels is not well defined. + // To ensure the correct texel is sampled, we add a small bias value to the coordinate. + // This value is calculated as the minimum value required to change the texel it will sample from, + // and is 0 if the host does not require the bias. + + TextureOperation texOp = (TextureOperation)node.Value; + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; + + int gatherBiasPrecision = gpuAccessor.QueryHostGatherBiasPrecision(); + + if (!isGather || gatherBiasPrecision == 0) + { + return node; + } + + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false); + + int coordsCount = texOp.Type.GetDimensions(); + int coordsIndex = isBindless || isIndexed ? 1 : 0; + + int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount; + + for (int index = 0; index < normCoordsCount; index++) + { + Operand coordSize = Local(); + Operand scaledSize = Local(); + Operand bias = Local(); + + Operand[] texSizeSources; + + if (isBindless || isIndexed) + { + texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) }; + } + else + { + texSizeSources = new Operand[] { Const(0) }; + } + + node.List.AddBefore(node, new TextureOperation( + Instruction.TextureQuerySize, + texOp.Type, + texOp.Format, + texOp.Flags, + texOp.Set, + texOp.Binding, + index, + new[] { coordSize }, + texSizeSources)); + + node.List.AddBefore(node, new Operation( + Instruction.FP32 | Instruction.Multiply, + scaledSize, + GenerateI2f(node, coordSize), + ConstF((float)(1 << (gatherBiasPrecision + 1))))); + node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, bias, ConstF(1f), scaledSize)); + + Operand source = texOp.GetSource(coordsIndex + index); + + Operand coordBiased = Local(); + + node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordBiased, source, bias)); + + texOp.SetSource(coordsIndex + index, coordBiased); + } + + return node; + } + + private static LinkedListNode InsertConstOffsets(LinkedListNode node, ResourceManager resourceManager, IGpuAccessor gpuAccessor, ShaderStage stage) + { + // Non-constant texture offsets are not allowed (according to the spec), + // however some GPUs does support that. + // For GPUs where it is not supported, we can replace the instruction with the following: + // For texture*Offset, we replace it by texture*, and add the offset to the P coords. + // The offset can be calculated as offset / textureSize(lod), where lod = textureQueryLod(coords). + // For texelFetchOffset, we replace it by texelFetch and add the offset to the P coords directly. + // For textureGatherOffset, we split the operation into up to 4 operations, one for each component + // that is accessed, where each textureGather operation has a different offset for each pixel. + + TextureOperation texOp = (TextureOperation)node.Value; + + bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0; + bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; + + bool needsOffsetsEmulation = hasOffsets && !gpuAccessor.QueryHostSupportsTextureGatherOffsets(); + + bool hasInvalidOffset = needsOffsetsEmulation || ((hasOffset || hasOffsets) && !gpuAccessor.QueryHostSupportsNonConstantTextureOffset()); + + bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; + + if (!hasInvalidOffset) + { + return node; + } + + bool isGather = (texOp.Flags & TextureFlags.Gather) != 0; + bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0; + bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0; + bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0; + bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0; + + bool isArray = (texOp.Type & SamplerType.Array) != 0; + bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0; + bool isShadow = (texOp.Type & SamplerType.Shadow) != 0; + + int coordsCount = texOp.Type.GetDimensions(); + + int offsetsCount; + + if (hasOffsets) + { + offsetsCount = coordsCount * 4; + } + else if (hasOffset) + { + offsetsCount = coordsCount; + } + else + { + offsetsCount = 0; + } + + bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false); + + Operand[] offsets = new Operand[offsetsCount]; + Operand[] sources = new Operand[texOp.SourcesCount - offsetsCount]; + + int copyCount = 0; + + if (isBindless || isIndexed) + { + copyCount++; + } + + Operand[] lodSources = new Operand[copyCount + coordsCount]; + + for (int index = 0; index < lodSources.Length; index++) + { + lodSources[index] = texOp.GetSource(index); + } + + copyCount += coordsCount; + + if (isArray) + { + copyCount++; + } + + if (isShadow) + { + copyCount++; + } + + if (hasDerivatives) + { + copyCount += coordsCount * 2; + } + + if (isMultisample) + { + copyCount++; + } + else if (hasLodLevel) + { + copyCount++; + } + + int srcIndex = 0; + int dstIndex = 0; + + for (int index = 0; index < copyCount; index++) + { + sources[dstIndex++] = texOp.GetSource(srcIndex++); + } + + bool areAllOffsetsConstant = true; + + for (int index = 0; index < offsetsCount; index++) + { + Operand offset = texOp.GetSource(srcIndex++); + + areAllOffsetsConstant &= offset.Type == OperandType.Constant; + + offsets[index] = offset; + } + + if (!needsOffsetsEmulation) + { + hasInvalidOffset &= !areAllOffsetsConstant; + + if (!hasInvalidOffset) + { + return node; + } + } + + if (hasLodBias) + { + sources[dstIndex++] = texOp.GetSource(srcIndex++); + } + + if (isGather && !isShadow) + { + sources[dstIndex++] = texOp.GetSource(srcIndex++); + } + + int coordsIndex = isBindless || isIndexed ? 1 : 0; + + int componentIndex = texOp.Index; + + Operand[] dests = new Operand[texOp.DestsCount]; + + for (int i = 0; i < texOp.DestsCount; i++) + { + dests[i] = texOp.GetDest(i); + } + + Operand bindlessHandle = isBindless || isIndexed ? sources[0] : null; + + LinkedListNode oldNode = node; + + if (isGather && !isShadow && hasOffsets) + { + Operand[] newSources = new Operand[sources.Length]; + + sources.CopyTo(newSources, 0); + + Operand[] texSizes = InsertTextureBaseSize(node, texOp, bindlessHandle, coordsCount); + + int destIndex = 0; + + for (int compIndex = 0; compIndex < 4; compIndex++) + { + if (((texOp.Index >> compIndex) & 1) == 0) + { + continue; + } + + for (int index = 0; index < coordsCount; index++) + { + Operand offset = Local(); + + Operand intOffset = offsets[index + compIndex * coordsCount]; + + node.List.AddBefore(node, new Operation( + Instruction.FP32 | Instruction.Divide, + offset, + GenerateI2f(node, intOffset), + GenerateI2f(node, texSizes[index]))); + + Operand source = sources[coordsIndex + index]; + + Operand coordPlusOffset = Local(); + + node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordPlusOffset, source, offset)); + + newSources[coordsIndex + index] = coordPlusOffset; + } + + TextureOperation newTexOp = new( + Instruction.TextureSample, + texOp.Type, + texOp.Format, + texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets), + texOp.Set, + texOp.Binding, + 1 << 3, // W component: i=0, j=0 + new[] { dests[destIndex++] }, + newSources); + + node = node.List.AddBefore(node, newTexOp); + } + } + else + { + if (intCoords) + { + for (int index = 0; index < coordsCount; index++) + { + Operand source = sources[coordsIndex + index]; + + Operand coordPlusOffset = Local(); + + node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offsets[index])); + + sources[coordsIndex + index] = coordPlusOffset; + } + } + else + { + Operand[] texSizes = isGather + ? InsertTextureBaseSize(node, texOp, bindlessHandle, coordsCount) + : InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount, stage); + + for (int index = 0; index < coordsCount; index++) + { + Operand offset = Local(); + + Operand intOffset = offsets[index]; + + node.List.AddBefore(node, new Operation( + Instruction.FP32 | Instruction.Divide, + offset, + GenerateI2f(node, intOffset), + GenerateI2f(node, texSizes[index]))); + + Operand source = sources[coordsIndex + index]; + + Operand coordPlusOffset = Local(); + + node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordPlusOffset, source, offset)); + + sources[coordsIndex + index] = coordPlusOffset; + } + } + + TextureOperation newTexOp = new( + Instruction.TextureSample, + texOp.Type, + texOp.Format, + texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets), + texOp.Set, + texOp.Binding, + componentIndex, + dests, + sources); + + node = node.List.AddBefore(node, newTexOp); + } + + node.List.Remove(oldNode); + + for (int index = 0; index < texOp.SourcesCount; index++) + { + texOp.SetSource(index, null); + } + + return node; + } + + private static Operand[] InsertTextureBaseSize( + LinkedListNode node, + TextureOperation texOp, + Operand bindlessHandle, + int coordsCount) + { + Operand[] texSizes = new Operand[coordsCount]; + + for (int index = 0; index < coordsCount; index++) + { + texSizes[index] = Local(); + + Operand[] texSizeSources; + + if (bindlessHandle != null) + { + texSizeSources = new Operand[] { bindlessHandle, Const(0) }; + } + else + { + texSizeSources = new Operand[] { Const(0) }; + } + + node.List.AddBefore(node, new TextureOperation( + Instruction.TextureQuerySize, + texOp.Type, + texOp.Format, + texOp.Flags, + texOp.Set, + texOp.Binding, + index, + new[] { texSizes[index] }, + texSizeSources)); + } + + return texSizes; + } + + private static Operand[] InsertTextureLod( + LinkedListNode node, + TextureOperation texOp, + Operand[] lodSources, + Operand bindlessHandle, + int coordsCount, + ShaderStage stage) + { + Operand[] texSizes = new Operand[coordsCount]; + + Operand lod; + + if (stage == ShaderStage.Fragment) + { + lod = Local(); + + node.List.AddBefore(node, new TextureOperation( + Instruction.Lod, + texOp.Type, + texOp.Format, + texOp.Flags, + texOp.Set, + texOp.Binding, + 0, + new[] { lod }, + lodSources)); + } + else + { + lod = Const(0); + } + + for (int index = 0; index < coordsCount; index++) + { + texSizes[index] = Local(); + + Operand[] texSizeSources; + + if (bindlessHandle != null) + { + texSizeSources = new Operand[] { bindlessHandle, GenerateF2i(node, lod) }; + } + else + { + texSizeSources = new Operand[] { GenerateF2i(node, lod) }; + } + + node.List.AddBefore(node, new TextureOperation( + Instruction.TextureQuerySize, + texOp.Type, + texOp.Format, + texOp.Flags, + texOp.Set, + texOp.Binding, + index, + new[] { texSizes[index] }, + texSizeSources)); + } + + return texSizes; + } + + private static LinkedListNode InsertSnormNormalization(LinkedListNode node, ResourceManager resourceManager, IGpuAccessor gpuAccessor) + { + TextureOperation texOp = (TextureOperation)node.Value; + + // We can't query the format of a bindless texture, + // because the handle is unknown, it can have any format. + if (texOp.Flags.HasFlag(TextureFlags.Bindless) || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle)) + { + return node; + } + + TextureFormat format = gpuAccessor.QueryTextureFormat(handle, cbufSlot); + + int maxPositive = format switch + { + TextureFormat.R8Snorm => sbyte.MaxValue, + TextureFormat.R8G8Snorm => sbyte.MaxValue, + TextureFormat.R8G8B8A8Snorm => sbyte.MaxValue, + TextureFormat.R16Snorm => short.MaxValue, + TextureFormat.R16G16Snorm => short.MaxValue, + TextureFormat.R16G16B16A16Snorm => short.MaxValue, + _ => 0, + }; + + // The value being 0 means that the format is not a SNORM format, + // so there's nothing to do here. + if (maxPositive == 0) + { + return node; + } + + // Do normalization. We assume SINT formats are being used + // as replacement for SNORM (which is not supported). + for (int i = 0; i < texOp.DestsCount; i++) + { + Operand dest = texOp.GetDest(i); + + INode[] uses = dest.UseOps.ToArray(); + + Operation convOp = new(Instruction.ConvertS32ToFP32, Local(), dest); + Operation normOp = new(Instruction.FP32 | Instruction.Multiply, Local(), convOp.Dest, ConstF(1f / maxPositive)); + + node = node.List.AddAfter(node, convOp); + node = node.List.AddAfter(node, normOp); + + foreach (INode useOp in uses) + { + if (useOp is not Operation op) + { + continue; + } + + // Replace all uses of the texture pixel value with the normalized value. + for (int index = 0; index < op.SourcesCount; index++) + { + if (op.GetSource(index) == dest) + { + op.SetSource(index, normOp.Dest); + } + } + } + } + + return node; + } + + private static Operand GenerateI2f(LinkedListNode node, Operand value) + { + Operand res = Local(); + + node.List.AddBefore(node, new Operation(Instruction.ConvertS32ToFP32, res, value)); + + return res; + } + + private static Operand GenerateF2i(LinkedListNode node, Operand value) + { + Operand res = Local(); + + node.List.AddBefore(node, new Operation(Instruction.ConvertFP32ToS32, res, value)); + + return res; + } + + private static bool IsImageInstructionWithScale(Instruction inst) + { + // Currently, we don't support scaling images that are modified, + // so we only need to care about the load instruction. + return inst == Instruction.ImageLoad; + } + + private static bool TypeSupportsScale(SamplerType type) + { + return (type & SamplerType.Mask) == SamplerType.Texture2D; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TransformPasses.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TransformPasses.cs new file mode 100644 index 00000000..7ff3b8bf --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TransformPasses.cs @@ -0,0 +1,44 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Translation.Transforms +{ + static class TransformPasses + { + public static void RunPass(TransformContext context) + { + RunPass(context); + RunPass(context); + RunPass(context); + RunPass(context); + RunPass(context); + RunPass(context); + RunPass(context); + RunPass(context); + RunPass(context); + } + + private static void RunPass(TransformContext context) where T : ITransformPass + { + if (!T.IsEnabled(context.GpuAccessor, context.Stage, context.TargetLanguage, context.UsedFeatures)) + { + return; + } + + for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++) + { + BasicBlock block = context.Blocks[blkIndex]; + + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (node.Value is not Operation) + { + continue; + } + + node = T.RunPass(context, node); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/VectorComponentSelect.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/VectorComponentSelect.cs new file mode 100644 index 00000000..e55f4355 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/VectorComponentSelect.cs @@ -0,0 +1,96 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Transforms +{ + class VectorComponentSelect : ITransformPass + { + public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures) + { + return gpuAccessor.QueryHostHasVectorIndexingBug(); + } + + public static LinkedListNode RunPass(TransformContext context, LinkedListNode node) + { + Operation operation = (Operation)node.Value; + + if (operation.Inst != Instruction.Load || + operation.StorageKind != StorageKind.ConstantBuffer || + operation.SourcesCount < 3) + { + return node; + } + + Operand bindingIndex = operation.GetSource(0); + Operand fieldIndex = operation.GetSource(1); + Operand elemIndex = operation.GetSource(operation.SourcesCount - 1); + + if (bindingIndex.Type != OperandType.Constant || + fieldIndex.Type != OperandType.Constant || + elemIndex.Type == OperandType.Constant) + { + return node; + } + + BufferDefinition buffer = context.ResourceManager.Properties.ConstantBuffers[bindingIndex.Value]; + StructureField field = buffer.Type.Fields[fieldIndex.Value]; + + int elemCount = (field.Type & AggregateType.ElementCountMask) switch + { + AggregateType.Vector2 => 2, + AggregateType.Vector3 => 3, + AggregateType.Vector4 => 4, + _ => 1 + }; + + if (elemCount == 1) + { + return node; + } + + Operand result = null; + + for (int i = 0; i < elemCount; i++) + { + Operand value = Local(); + Operand[] inputs = new Operand[operation.SourcesCount]; + + for (int srcIndex = 0; srcIndex < inputs.Length - 1; srcIndex++) + { + inputs[srcIndex] = operation.GetSource(srcIndex); + } + + inputs[^1] = Const(i); + + Operation loadOp = new(Instruction.Load, StorageKind.ConstantBuffer, value, inputs); + + node.List.AddBefore(node, loadOp); + + if (i == 0) + { + result = value; + } + else + { + Operand isCurrentIndex = Local(); + Operand selection = Local(); + + Operation compareOp = new(Instruction.CompareEqual, isCurrentIndex, new Operand[] { elemIndex, Const(i) }); + Operation selectOp = new(Instruction.ConditionalSelect, selection, new Operand[] { isCurrentIndex, value, result }); + + node.List.AddBefore(node, compareOp); + node.List.AddBefore(node, selectOp); + + result = selection; + } + } + + operation.TurnIntoCopy(result); + + return node; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs new file mode 100644 index 00000000..ddd2134d --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs @@ -0,0 +1,368 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.Translation.Optimizations; +using System.Collections.Generic; + +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation.Transforms +{ + class VertexToCompute : ITransformPass + { + public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures) + { + return usedFeatures.HasFlag(FeatureFlags.VtgAsCompute); + } + + public static LinkedListNode RunPass(TransformContext context, LinkedListNode node) + { + if (context.Definitions.Stage != ShaderStage.Vertex) + { + return node; + } + + Operation operation = (Operation)node.Value; + + LinkedListNode newNode = node; + + if (operation.Inst == Instruction.Load && operation.StorageKind == StorageKind.Input) + { + Operand dest = operation.Dest; + + switch ((IoVariable)operation.GetSource(0).Value) + { + case IoVariable.BaseInstance: + newNode = GenerateBaseInstanceLoad(context.ResourceManager, node, dest); + break; + case IoVariable.BaseVertex: + newNode = GenerateBaseVertexLoad(context.ResourceManager, node, dest); + break; + case IoVariable.InstanceId: + newNode = GenerateInstanceIdLoad(node, dest); + break; + case IoVariable.InstanceIndex: + newNode = GenerateInstanceIndexLoad(context.ResourceManager, node, dest); + break; + case IoVariable.VertexId: + case IoVariable.VertexIndex: + newNode = GenerateVertexIndexLoad(context.ResourceManager, node, dest); + break; + case IoVariable.UserDefined: + int location = operation.GetSource(1).Value; + int component = operation.GetSource(2).Value; + + if (context.Definitions.IsAttributePacked(location)) + { + bool needsSextNorm = context.Definitions.IsAttributePackedRgb10A2Signed(location); + + SetBindingPair setAndBinding = context.ResourceManager.Reservations.GetVertexBufferTextureSetAndBinding(location); + Operand temp = needsSextNorm ? Local() : dest; + Operand vertexElemOffset = GenerateVertexOffset(context.ResourceManager, node, location, 0); + + newNode = node.List.AddBefore(node, new TextureOperation( + Instruction.TextureSample, + SamplerType.TextureBuffer, + TextureFormat.Unknown, + TextureFlags.IntCoords, + setAndBinding.SetIndex, + setAndBinding.Binding, + 1 << component, + new[] { temp }, + new[] { vertexElemOffset })); + + if (needsSextNorm) + { + bool sint = context.Definitions.IsAttributeSint(location); + CopySignExtendedNormalized(node, component == 3 ? 2 : 10, !sint, dest, temp); + } + } + else + { + SetBindingPair setAndBinding = context.ResourceManager.Reservations.GetVertexBufferTextureSetAndBinding(location); + Operand temp = component > 0 ? Local() : dest; + Operand vertexElemOffset = GenerateVertexOffset(context.ResourceManager, node, location, component); + + newNode = node.List.AddBefore(node, new TextureOperation( + Instruction.TextureSample, + SamplerType.TextureBuffer, + TextureFormat.Unknown, + TextureFlags.IntCoords, + setAndBinding.SetIndex, + setAndBinding.Binding, + 1, + new[] { temp }, + new[] { vertexElemOffset })); + + if (component > 0) + { + newNode = CopyMasked(context.ResourceManager, newNode, location, component, dest, temp); + } + } + break; + case IoVariable.GlobalId: + case IoVariable.SubgroupEqMask: + case IoVariable.SubgroupGeMask: + case IoVariable.SubgroupGtMask: + case IoVariable.SubgroupLaneId: + case IoVariable.SubgroupLeMask: + case IoVariable.SubgroupLtMask: + // Those are valid or expected for vertex shaders. + break; + default: + context.GpuAccessor.Log($"Invalid input \"{(IoVariable)operation.GetSource(0).Value}\"."); + break; + } + } + else if (operation.Inst == Instruction.Load && operation.StorageKind == StorageKind.Output) + { + if (TryGetOutputOffset(context.ResourceManager, operation, out int outputOffset)) + { + newNode = node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.LocalMemory, + operation.Dest, + new[] { Const(context.ResourceManager.LocalVertexDataMemoryId), Const(outputOffset) })); + } + else + { + context.GpuAccessor.Log($"Invalid output \"{(IoVariable)operation.GetSource(0).Value}\"."); + } + } + else if (operation.Inst == Instruction.Store && operation.StorageKind == StorageKind.Output) + { + if (TryGetOutputOffset(context.ResourceManager, operation, out int outputOffset)) + { + Operand value = operation.GetSource(operation.SourcesCount - 1); + + newNode = node.List.AddBefore(node, new Operation( + Instruction.Store, + StorageKind.LocalMemory, + (Operand)null, + new[] { Const(context.ResourceManager.LocalVertexDataMemoryId), Const(outputOffset), value })); + } + else + { + context.GpuAccessor.Log($"Invalid output \"{(IoVariable)operation.GetSource(0).Value}\"."); + } + } + + if (newNode != node) + { + Utils.DeleteNode(node, operation); + } + + return newNode; + } + + private static Operand GenerateVertexOffset(ResourceManager resourceManager, LinkedListNode node, int location, int component) + { + int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; + + Operand vertexIdVr = Local(); + GenerateVertexIdVertexRateLoad(resourceManager, node, vertexIdVr); + + Operand vertexIdIr = Local(); + GenerateVertexIdInstanceRateLoad(resourceManager, node, vertexIdIr); + + Operand attributeOffset = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + attributeOffset, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexOffsets), Const(location), Const(0) })); + + Operand isInstanceRate = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + isInstanceRate, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexOffsets), Const(location), Const(1) })); + + Operand vertexId = Local(); + node.List.AddBefore(node, new Operation( + Instruction.ConditionalSelect, + vertexId, + new[] { isInstanceRate, vertexIdIr, vertexIdVr })); + + Operand vertexStride = Local(); + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + vertexStride, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexStrides), Const(location), Const(0) })); + + Operand vertexBaseOffset = Local(); + node.List.AddBefore(node, new Operation(Instruction.Multiply, vertexBaseOffset, new[] { vertexId, vertexStride })); + + Operand vertexOffset = Local(); + node.List.AddBefore(node, new Operation(Instruction.Add, vertexOffset, new[] { attributeOffset, vertexBaseOffset })); + + Operand vertexElemOffset; + + if (component != 0) + { + vertexElemOffset = Local(); + + node.List.AddBefore(node, new Operation(Instruction.Add, vertexElemOffset, new[] { vertexOffset, Const(component) })); + } + else + { + vertexElemOffset = vertexOffset; + } + + return vertexElemOffset; + } + + private static LinkedListNode CopySignExtendedNormalized(LinkedListNode node, int bits, bool normalize, Operand dest, Operand src) + { + Operand leftShifted = Local(); + node = node.List.AddAfter(node, new Operation( + Instruction.ShiftLeft, + leftShifted, + new[] { src, Const(32 - bits) })); + + Operand rightShifted = normalize ? Local() : dest; + node = node.List.AddAfter(node, new Operation( + Instruction.ShiftRightS32, + rightShifted, + new[] { leftShifted, Const(32 - bits) })); + + if (normalize) + { + Operand asFloat = Local(); + node = node.List.AddAfter(node, new Operation(Instruction.ConvertS32ToFP32, asFloat, new[] { rightShifted })); + node = node.List.AddAfter(node, new Operation( + Instruction.FP32 | Instruction.Multiply, + dest, + new[] { asFloat, ConstF(1f / (1 << (bits - 1))) })); + } + + return node; + } + + private static LinkedListNode CopyMasked( + ResourceManager resourceManager, + LinkedListNode node, + int location, + int component, + Operand dest, + Operand src) + { + Operand componentExists = Local(); + int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; + node = node.List.AddAfter(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + componentExists, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexStrides), Const(location), Const(component) })); + + return node.List.AddAfter(node, new Operation( + Instruction.ConditionalSelect, + dest, + new[] { componentExists, src, ConstF(component == 3 ? 1f : 0f) })); + } + + private static LinkedListNode GenerateBaseVertexLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) + { + int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; + + return node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + dest, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexCounts), Const(2) })); + } + + private static LinkedListNode GenerateBaseInstanceLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) + { + int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; + + return node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.ConstantBuffer, + dest, + new[] { Const(vertexInfoCbBinding), Const((int)VertexInfoBufferField.VertexCounts), Const(3) })); + } + + private static LinkedListNode GenerateVertexIndexLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) + { + Operand baseVertex = Local(); + Operand vertexId = Local(); + + GenerateBaseVertexLoad(resourceManager, node, baseVertex); + GenerateVertexIdVertexRateLoad(resourceManager, node, vertexId); + + return node.List.AddBefore(node, new Operation(Instruction.Add, dest, new[] { baseVertex, vertexId })); + } + + private static LinkedListNode GenerateInstanceIndexLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) + { + Operand baseInstance = Local(); + Operand instanceId = Local(); + + GenerateBaseInstanceLoad(resourceManager, node, baseInstance); + + node.List.AddBefore(node, new Operation( + Instruction.Load, + StorageKind.Input, + instanceId, + new[] { Const((int)IoVariable.GlobalId), Const(1) })); + + return node.List.AddBefore(node, new Operation(Instruction.Add, dest, new[] { baseInstance, instanceId })); + } + + private static LinkedListNode GenerateVertexIdVertexRateLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) + { + Operand[] sources = new Operand[] { Const(resourceManager.LocalVertexIndexVertexRateMemoryId) }; + + return node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.LocalMemory, dest, sources)); + } + + private static LinkedListNode GenerateVertexIdInstanceRateLoad(ResourceManager resourceManager, LinkedListNode node, Operand dest) + { + Operand[] sources = new Operand[] { Const(resourceManager.LocalVertexIndexInstanceRateMemoryId) }; + + return node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.LocalMemory, dest, sources)); + } + + private static LinkedListNode GenerateInstanceIdLoad(LinkedListNode node, Operand dest) + { + Operand[] sources = new Operand[] { Const((int)IoVariable.GlobalId), Const(1) }; + + return node.List.AddBefore(node, new Operation(Instruction.Load, StorageKind.Input, dest, sources)); + } + + private static bool TryGetOutputOffset(ResourceManager resourceManager, Operation operation, out int outputOffset) + { + bool isStore = operation.Inst == Instruction.Store; + + IoVariable ioVariable = (IoVariable)operation.GetSource(0).Value; + + bool isValidOutput; + + if (ioVariable == IoVariable.UserDefined) + { + int lastIndex = operation.SourcesCount - (isStore ? 2 : 1); + + int location = operation.GetSource(1).Value; + int component = operation.GetSource(lastIndex).Value; + + isValidOutput = resourceManager.Reservations.TryGetOffset(StorageKind.Output, location, component, out outputOffset); + } + else + { + if (ResourceReservations.IsVectorOrArrayVariable(ioVariable)) + { + int component = operation.GetSource(operation.SourcesCount - (isStore ? 2 : 1)).Value; + + isValidOutput = resourceManager.Reservations.TryGetOffset(StorageKind.Output, ioVariable, component, out outputOffset); + } + else + { + isValidOutput = resourceManager.Reservations.TryGetOffset(StorageKind.Output, ioVariable, out outputOffset); + } + } + + return isValidOutput; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs new file mode 100644 index 00000000..df1e76a1 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.Graphics.Shader.Translation +{ + [Flags] + public enum TranslationFlags + { + None = 0, + + VertexA = 1 << 0, + Compute = 1 << 1, + DebugMode = 1 << 2, + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslationOptions.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslationOptions.cs new file mode 100644 index 00000000..d9829ac4 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/TranslationOptions.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Graphics.Shader.Translation +{ + public readonly struct TranslationOptions + { + public TargetLanguage TargetLanguage { get; } + public TargetApi TargetApi { get; } + public TranslationFlags Flags { get; } + + public TranslationOptions(TargetLanguage targetLanguage, TargetApi targetApi, TranslationFlags flags) + { + TargetLanguage = targetLanguage; + TargetApi = targetApi; + Flags = flags; + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/Translator.cs b/src/Ryujinx.Graphics.Shader/Translation/Translator.cs new file mode 100644 index 00000000..6a31ea2e --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/Translator.cs @@ -0,0 +1,373 @@ +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; +using System.Linq; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; + +namespace Ryujinx.Graphics.Shader.Translation +{ + public static class Translator + { + private const int ThreadsPerWarp = 32; + private const int HeaderSize = 0x50; + + internal readonly struct FunctionCode + { + public Operation[] Code { get; } + + public FunctionCode(Operation[] code) + { + Code = code; + } + } + + public static TranslatorContext CreateContext(ulong address, IGpuAccessor gpuAccessor, TranslationOptions options) + { + return DecodeShader(address, gpuAccessor, options); + } + + private static TranslatorContext DecodeShader(ulong address, IGpuAccessor gpuAccessor, TranslationOptions options) + { + int localMemorySize; + ShaderDefinitions definitions; + DecodedProgram program; + + if (options.Flags.HasFlag(TranslationFlags.Compute)) + { + definitions = CreateComputeDefinitions(gpuAccessor); + localMemorySize = gpuAccessor.QueryComputeLocalMemorySize(); + + program = Decoder.Decode(definitions, gpuAccessor, address); + } + else + { + ShaderHeader header = new(gpuAccessor, address); + + definitions = CreateGraphicsDefinitions(gpuAccessor, header); + localMemorySize = GetLocalMemorySize(header); + + program = Decoder.Decode(definitions, gpuAccessor, address + HeaderSize); + } + + ulong maxEndAddress = 0; + + foreach (DecodedFunction function in program) + { + foreach (Block block in function.Blocks) + { + if (maxEndAddress < block.EndAddress) + { + maxEndAddress = block.EndAddress; + } + } + } + + int size = (int)maxEndAddress + (options.Flags.HasFlag(TranslationFlags.Compute) ? 0 : HeaderSize); + + return new TranslatorContext(address, size, localMemorySize, definitions, gpuAccessor, options, program); + } + + private static ShaderDefinitions CreateComputeDefinitions(IGpuAccessor gpuAccessor) + { + return new ShaderDefinitions( + ShaderStage.Compute, + gpuAccessor.QueryComputeLocalSizeX(), + gpuAccessor.QueryComputeLocalSizeY(), + gpuAccessor.QueryComputeLocalSizeZ()); + } + + private static ShaderDefinitions CreateGraphicsDefinitions(IGpuAccessor gpuAccessor, ShaderHeader header) + { + TransformFeedbackOutput[] transformFeedbackOutputs = GetTransformFeedbackOutputs(gpuAccessor, out ulong transformFeedbackVecMap); + + return new ShaderDefinitions( + header.Stage, + gpuAccessor.QueryGraphicsState(), + header.Stage == ShaderStage.Geometry && header.GpPassthrough, + header.ThreadsPerInputPrimitive, + header.OutputTopology, + header.MaxOutputVertexCount, + header.ImapTypes, + header.OmapTargets, + header.OmapSampleMask, + header.OmapDepth, + gpuAccessor.QueryHostSupportsScaledVertexFormats(), + transformFeedbackVecMap, + transformFeedbackOutputs); + } + + internal static TransformFeedbackOutput[] GetTransformFeedbackOutputs(IGpuAccessor gpuAccessor, out ulong transformFeedbackVecMap) + { + bool transformFeedbackEnabled = + gpuAccessor.QueryTransformFeedbackEnabled() && + gpuAccessor.QueryHostSupportsTransformFeedback(); + TransformFeedbackOutput[] transformFeedbackOutputs = null; + transformFeedbackVecMap = 0UL; + + if (transformFeedbackEnabled) + { + transformFeedbackOutputs = new TransformFeedbackOutput[0xc0]; + + for (int tfbIndex = 0; tfbIndex < 4; tfbIndex++) + { + var locations = gpuAccessor.QueryTransformFeedbackVaryingLocations(tfbIndex); + var stride = gpuAccessor.QueryTransformFeedbackStride(tfbIndex); + + for (int i = 0; i < locations.Length; i++) + { + byte wordOffset = locations[i]; + if (wordOffset < 0xc0) + { + transformFeedbackOutputs[wordOffset] = new TransformFeedbackOutput(tfbIndex, i * 4, stride); + transformFeedbackVecMap |= 1UL << (wordOffset / 4); + } + } + } + } + + return transformFeedbackOutputs; + } + + private static int GetLocalMemorySize(ShaderHeader header) + { + return header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize + (header.ShaderLocalMemoryCrsSize / ThreadsPerWarp); + } + + internal static FunctionCode[] EmitShader( + TranslatorContext translatorContext, + ResourceManager resourceManager, + DecodedProgram program, + bool vertexAsCompute, + bool initializeOutputs, + out int initializationOperations) + { + initializationOperations = 0; + + FunctionMatch.RunPass(program); + + foreach (DecodedFunction function in program.Where(x => !x.IsCompilerGenerated).OrderBy(x => x.Address)) + { + program.AddFunctionAndSetId(function); + } + + FunctionCode[] functions = new FunctionCode[program.FunctionsWithIdCount]; + + for (int index = 0; index < functions.Length; index++) + { + EmitterContext context = new(translatorContext, resourceManager, program, vertexAsCompute, index != 0); + + if (initializeOutputs && index == 0) + { + EmitOutputsInitialization(context, translatorContext.AttributeUsage, translatorContext.GpuAccessor, translatorContext.Stage); + initializationOperations = context.OperationsCount; + } + + DecodedFunction function = program.GetFunctionById(index); + + foreach (Block block in function.Blocks) + { + context.CurrBlock = block; + + context.EnterBlock(block.Address); + + EmitOps(context, block); + } + + functions[index] = new(context.GetOperations()); + } + + return functions; + } + + private static void EmitOutputsInitialization(EmitterContext context, AttributeUsage attributeUsage, IGpuAccessor gpuAccessor, ShaderStage stage) + { + // Compute has no output attributes, and fragment is the last stage, so we + // don't need to initialize outputs on those stages. + if (stage == ShaderStage.Compute || stage == ShaderStage.Fragment) + { + return; + } + + if (stage == ShaderStage.Vertex) + { + InitializePositionOutput(context); + } + + UInt128 usedAttributes = context.TranslatorContext.AttributeUsage.NextInputAttributesComponents; + while (usedAttributes != UInt128.Zero) + { + int index = (int)UInt128.TrailingZeroCount(usedAttributes); + int vecIndex = index / 4; + + usedAttributes &= ~(UInt128.One << index); + + // We don't need to initialize passthrough attributes. + if ((context.TranslatorContext.AttributeUsage.PassthroughAttributes & (1 << vecIndex)) != 0) + { + continue; + } + + InitializeOutputComponent(context, vecIndex, index & 3, perPatch: false); + } + + if (context.TranslatorContext.AttributeUsage.NextUsedInputAttributesPerPatch != null) + { + foreach (int vecIndex in context.TranslatorContext.AttributeUsage.NextUsedInputAttributesPerPatch.Order()) + { + InitializeOutput(context, vecIndex, perPatch: true); + } + } + + if (attributeUsage.NextUsesFixedFuncAttributes) + { + bool supportsLayerFromVertexOrTess = gpuAccessor.QueryHostSupportsLayerVertexTessellation(); + int fixedStartAttr = supportsLayerFromVertexOrTess ? 0 : 1; + + for (int i = fixedStartAttr; i < fixedStartAttr + 5 + AttributeConsts.TexCoordCount; i++) + { + int index = attributeUsage.GetFreeUserAttribute(isOutput: true, i); + if (index < 0) + { + break; + } + + InitializeOutput(context, index, perPatch: false); + } + } + } + + private static void InitializePositionOutput(EmitterContext context) + { + for (int c = 0; c < 4; c++) + { + context.Store(StorageKind.Output, IoVariable.Position, null, Const(c), ConstF(c == 3 ? 1f : 0f)); + } + } + + private static void InitializeOutput(EmitterContext context, int location, bool perPatch) + { + for (int c = 0; c < 4; c++) + { + InitializeOutputComponent(context, location, c, perPatch); + } + } + + private static void InitializeOutputComponent(EmitterContext context, int location, int c, bool perPatch) + { + StorageKind storageKind = perPatch ? StorageKind.OutputPerPatch : StorageKind.Output; + + if (context.TranslatorContext.Definitions.OaIndexing) + { + Operand invocationId = null; + + if (context.TranslatorContext.Definitions.Stage == ShaderStage.TessellationControl && !perPatch) + { + invocationId = context.Load(StorageKind.Input, IoVariable.InvocationId); + } + + int index = location * 4 + c; + + context.Store(storageKind, IoVariable.UserDefined, invocationId, Const(index), ConstF(c == 3 ? 1f : 0f)); + } + else + { + if (context.TranslatorContext.Definitions.Stage == ShaderStage.TessellationControl && !perPatch) + { + Operand invocationId = context.Load(StorageKind.Input, IoVariable.InvocationId); + context.Store(storageKind, IoVariable.UserDefined, Const(location), invocationId, Const(c), ConstF(c == 3 ? 1f : 0f)); + } + else + { + context.Store(storageKind, IoVariable.UserDefined, null, Const(location), Const(c), ConstF(c == 3 ? 1f : 0f)); + } + } + } + + private static void EmitOps(EmitterContext context, Block block) + { + for (int opIndex = 0; opIndex < block.OpCodes.Count; opIndex++) + { + InstOp op = block.OpCodes[opIndex]; + + if (context.TranslatorContext.Options.Flags.HasFlag(TranslationFlags.DebugMode)) + { + string instName; + + if (op.Emitter != null) + { + instName = op.Name.ToString(); + } + else + { + instName = "???"; + + context.TranslatorContext.GpuAccessor.Log($"Invalid instruction at 0x{op.Address:X6} (0x{op.RawOpCode:X16})."); + } + + string dbgComment = $"0x{op.Address:X6}: 0x{op.RawOpCode:X16} {instName}"; + + context.Add(new CommentNode(dbgComment)); + } + + InstConditional opConditional = new(op.RawOpCode); + + bool noPred = op.Props.HasFlag(InstProps.NoPred); + if (!noPred && opConditional.Pred == RegisterConsts.PredicateTrueIndex && opConditional.PredInv) + { + continue; + } + + Operand predSkipLbl = null; + + if (Decoder.IsPopBranch(op.Name)) + { + // If the instruction is a SYNC or BRK instruction with only one + // possible target address, then the instruction is basically + // just a simple branch, we can generate code similar to branch + // instructions, with the condition check on the branch itself. + noPred = block.SyncTargets.Count <= 1; + } + else if (op.Name == InstName.Bra) + { + noPred = true; + } + + if (!(opConditional.Pred == RegisterConsts.PredicateTrueIndex || noPred)) + { + Operand label; + + if (opIndex == block.OpCodes.Count - 1 && block.HasNext()) + { + label = context.GetLabel(block.Successors[0].Address); + } + else + { + label = Label(); + + predSkipLbl = label; + } + + Operand pred = Register(opConditional.Pred, RegisterType.Predicate); + + if (opConditional.PredInv) + { + context.BranchIfTrue(label, pred); + } + else + { + context.BranchIfFalse(label, pred); + } + } + + context.CurrOp = op; + + op.Emitter?.Invoke(context); + + if (predSkipLbl != null) + { + context.MarkLabel(predSkipLbl); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs new file mode 100644 index 00000000..a579433f --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs @@ -0,0 +1,673 @@ +using Ryujinx.Graphics.Shader.CodeGen; +using Ryujinx.Graphics.Shader.CodeGen.Glsl; +using Ryujinx.Graphics.Shader.CodeGen.Spirv; +using Ryujinx.Graphics.Shader.Decoders; +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation.Optimizations; +using Ryujinx.Graphics.Shader.Translation.Transforms; +using System; +using System.Collections.Generic; +using System.Numerics; +using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; +using static Ryujinx.Graphics.Shader.Translation.Translator; + +namespace Ryujinx.Graphics.Shader.Translation +{ + public class TranslatorContext + { + private readonly DecodedProgram _program; + private readonly int _localMemorySize; + private IoUsage _vertexOutput; + + public ulong Address { get; } + public int Size { get; } + public int Cb1DataSize => _program.Cb1DataSize; + + internal AttributeUsage AttributeUsage => _program.AttributeUsage; + + internal ShaderDefinitions Definitions { get; } + + public ShaderStage Stage => Definitions.Stage; + + internal IGpuAccessor GpuAccessor { get; } + + internal TranslationOptions Options { get; } + + private bool IsTransformFeedbackEmulated => !GpuAccessor.QueryHostSupportsTransformFeedback() && GpuAccessor.QueryTransformFeedbackEnabled(); + public bool HasStore => _program.UsedFeatures.HasFlag(FeatureFlags.Store) || (IsTransformFeedbackEmulated && Definitions.LastInVertexPipeline); + + public bool LayerOutputWritten { get; private set; } + public int LayerOutputAttribute { get; private set; } + + internal TranslatorContext( + ulong address, + int size, + int localMemorySize, + ShaderDefinitions definitions, + IGpuAccessor gpuAccessor, + TranslationOptions options, + DecodedProgram program) + { + Address = address; + Size = size; + _program = program; + _localMemorySize = localMemorySize; + _vertexOutput = new IoUsage(FeatureFlags.None, 0, -1); + Definitions = definitions; + GpuAccessor = gpuAccessor; + Options = options; + } + + private static bool IsLoadUserDefined(Operation operation) + { + // TODO: Check if sources count match and all sources are constant. + return operation.Inst == Instruction.Load && (IoVariable)operation.GetSource(0).Value == IoVariable.UserDefined; + } + + private static bool IsStoreUserDefined(Operation operation) + { + // TODO: Check if sources count match and all sources are constant. + return operation.Inst == Instruction.Store && (IoVariable)operation.GetSource(0).Value == IoVariable.UserDefined; + } + + private static FunctionCode[] Combine(FunctionCode[] a, FunctionCode[] b, int aStart) + { + // Here we combine two shaders. + // For shader A: + // - All user attribute stores on shader A are turned into copies to a + // temporary variable. It's assumed that shader B will consume them. + // - All return instructions are turned into branch instructions, the + // branch target being the start of the shader B code. + // For shader B: + // - All user attribute loads on shader B are turned into copies from a + // temporary variable, as long that attribute is written by shader A. + FunctionCode[] output = new FunctionCode[a.Length + b.Length - 1]; + + List ops = new(a.Length + b.Length); + + Operand[] temps = new Operand[AttributeConsts.UserAttributesCount * 4]; + + Operand lblB = Label(); + + for (int index = aStart; index < a[0].Code.Length; index++) + { + Operation operation = a[0].Code[index]; + + if (IsStoreUserDefined(operation)) + { + int tIndex = operation.GetSource(1).Value * 4 + operation.GetSource(2).Value; + + Operand temp = temps[tIndex]; + + if (temp == null) + { + temp = Local(); + + temps[tIndex] = temp; + } + + operation.Dest = temp; + operation.TurnIntoCopy(operation.GetSource(operation.SourcesCount - 1)); + } + + if (operation.Inst == Instruction.Return) + { + ops.Add(new Operation(Instruction.Branch, lblB)); + } + else + { + ops.Add(operation); + } + } + + ops.Add(new Operation(Instruction.MarkLabel, lblB)); + + for (int index = 0; index < b[0].Code.Length; index++) + { + Operation operation = b[0].Code[index]; + + if (IsLoadUserDefined(operation)) + { + int tIndex = operation.GetSource(1).Value * 4 + operation.GetSource(2).Value; + + Operand temp = temps[tIndex]; + + if (temp != null) + { + operation.TurnIntoCopy(temp); + } + } + + ops.Add(operation); + } + + output[0] = new FunctionCode(ops.ToArray()); + + for (int i = 1; i < a.Length; i++) + { + output[i] = a[i]; + } + + for (int i = 1; i < b.Length; i++) + { + output[a.Length + i - 1] = b[i]; + } + + return output; + } + + internal int GetDepthRegister() + { + // The depth register is always two registers after the last color output. + return BitOperations.PopCount((uint)Definitions.OmapTargets) + 1; + } + + public void SetLayerOutputAttribute(int attr) + { + LayerOutputWritten = true; + LayerOutputAttribute = attr; + } + + public void SetLastInVertexPipeline() + { + Definitions.LastInVertexPipeline = true; + } + + public void SetNextStage(TranslatorContext nextStage) + { + AttributeUsage.MergeFromtNextStage( + Definitions.GpPassthrough, + nextStage._program.UsedFeatures.HasFlag(FeatureFlags.FixedFuncAttr), + nextStage.AttributeUsage); + + // We don't consider geometry shaders using the geometry shader passthrough feature + // as being the last because when this feature is used, it can't actually modify any of the outputs, + // so the stage that comes before it is the last one that can do modifications. + if (nextStage.Definitions.Stage != ShaderStage.Fragment && + (nextStage.Definitions.Stage != ShaderStage.Geometry || !nextStage.Definitions.GpPassthrough)) + { + Definitions.LastInVertexPipeline = false; + } + } + + public ShaderProgram Translate(bool asCompute = false) + { + ResourceManager resourceManager = CreateResourceManager(asCompute); + + bool usesLocalMemory = _program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory); + + resourceManager.SetCurrentLocalMemory(_localMemorySize, usesLocalMemory); + + if (Stage == ShaderStage.Compute) + { + bool usesSharedMemory = _program.UsedFeatures.HasFlag(FeatureFlags.SharedMemory); + + resourceManager.SetCurrentSharedMemory(GpuAccessor.QueryComputeSharedMemorySize(), usesSharedMemory); + } + + FunctionCode[] code = EmitShader(this, resourceManager, _program, asCompute, initializeOutputs: true, out _); + + return Translate(code, resourceManager, _program.UsedFeatures, _program.ClipDistancesWritten, asCompute); + } + + public ShaderProgram Translate(TranslatorContext other, bool asCompute = false) + { + ResourceManager resourceManager = CreateResourceManager(asCompute); + + bool usesLocalMemory = _program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory); + resourceManager.SetCurrentLocalMemory(_localMemorySize, usesLocalMemory); + + FunctionCode[] code = EmitShader(this, resourceManager, _program, asCompute, initializeOutputs: false, out _); + + bool otherUsesLocalMemory = other._program.UsedFeatures.HasFlag(FeatureFlags.LocalMemory); + resourceManager.SetCurrentLocalMemory(other._localMemorySize, otherUsesLocalMemory); + + FunctionCode[] otherCode = EmitShader(other, resourceManager, other._program, asCompute, initializeOutputs: true, out int aStart); + + code = Combine(otherCode, code, aStart); + + return Translate( + code, + resourceManager, + _program.UsedFeatures | other._program.UsedFeatures, + (byte)(_program.ClipDistancesWritten | other._program.ClipDistancesWritten), + asCompute); + } + + private ShaderProgram Translate(FunctionCode[] functions, ResourceManager resourceManager, FeatureFlags usedFeatures, byte clipDistancesWritten, bool asCompute) + { + if (asCompute) + { + usedFeatures |= FeatureFlags.VtgAsCompute; + } + + var cfgs = new ControlFlowGraph[functions.Length]; + var frus = new RegisterUsage.FunctionRegisterUsage[functions.Length]; + + for (int i = 0; i < functions.Length; i++) + { + cfgs[i] = ControlFlowGraph.Create(functions[i].Code); + + if (i != 0) + { + frus[i] = RegisterUsage.RunPass(cfgs[i]); + } + } + + List funcs = new(functions.Length); + + for (int i = 0; i < functions.Length; i++) + { + funcs.Add(null); + } + + HelperFunctionManager hfm = new(funcs, Definitions.Stage); + + for (int i = 0; i < functions.Length; i++) + { + var cfg = cfgs[i]; + + int inArgumentsCount = 0; + int outArgumentsCount = 0; + + if (i != 0) + { + var fru = frus[i]; + + inArgumentsCount = fru.InArguments.Length; + outArgumentsCount = fru.OutArguments.Length; + } + + if (cfg.Blocks.Length != 0) + { + RegisterUsage.FixupCalls(cfg.Blocks, frus); + + Dominance.FindDominators(cfg); + Dominance.FindDominanceFrontiers(cfg.Blocks); + + Ssa.Rename(cfg.Blocks); + + TransformContext context = new( + hfm, + cfg.Blocks, + Definitions, + resourceManager, + GpuAccessor, + Options.TargetApi, + Options.TargetLanguage, + Definitions.Stage, + ref usedFeatures); + + Optimizer.RunPass(context); + TransformPasses.RunPass(context); + } + + funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount); + } + + return Generate( + funcs, + AttributeUsage, + GetDefinitions(asCompute), + Definitions, + resourceManager, + usedFeatures, + clipDistancesWritten); + } + + private ShaderProgram Generate( + IReadOnlyList funcs, + AttributeUsage attributeUsage, + ShaderDefinitions definitions, + ShaderDefinitions originalDefinitions, + ResourceManager resourceManager, + FeatureFlags usedFeatures, + byte clipDistancesWritten) + { + var sInfo = StructuredProgram.MakeStructuredProgram( + funcs, + attributeUsage, + definitions, + resourceManager, + Options.TargetLanguage, + Options.Flags.HasFlag(TranslationFlags.DebugMode)); + + int geometryVerticesPerPrimitive = Definitions.OutputTopology switch + { + OutputTopology.LineStrip => 2, + OutputTopology.TriangleStrip => 3, + _ => 1 + }; + + var info = new ShaderProgramInfo( + resourceManager.GetConstantBufferDescriptors(), + resourceManager.GetStorageBufferDescriptors(), + resourceManager.GetTextureDescriptors(), + resourceManager.GetImageDescriptors(), + originalDefinitions.Stage, + geometryVerticesPerPrimitive, + originalDefinitions.MaxOutputVertices, + originalDefinitions.ThreadsPerInputPrimitive, + usedFeatures.HasFlag(FeatureFlags.FragCoordXY), + usedFeatures.HasFlag(FeatureFlags.InstanceId), + usedFeatures.HasFlag(FeatureFlags.DrawParameters), + usedFeatures.HasFlag(FeatureFlags.RtLayer), + clipDistancesWritten, + originalDefinitions.OmapTargets); + + var hostCapabilities = new HostCapabilities( + GpuAccessor.QueryHostReducedPrecision(), + GpuAccessor.QueryHostSupportsFragmentShaderInterlock(), + GpuAccessor.QueryHostSupportsFragmentShaderOrderingIntel(), + GpuAccessor.QueryHostSupportsGeometryShaderPassthrough(), + GpuAccessor.QueryHostSupportsShaderBallot(), + GpuAccessor.QueryHostSupportsShaderBarrierDivergence(), + GpuAccessor.QueryHostSupportsShaderFloat64(), + GpuAccessor.QueryHostSupportsTextureShadowLod(), + GpuAccessor.QueryHostSupportsViewportMask()); + + var parameters = new CodeGenParameters(attributeUsage, definitions, resourceManager.Properties, hostCapabilities, GpuAccessor, Options.TargetApi); + + return Options.TargetLanguage switch + { + TargetLanguage.Glsl => new ShaderProgram(info, TargetLanguage.Glsl, GlslGenerator.Generate(sInfo, parameters)), + TargetLanguage.Spirv => new ShaderProgram(info, TargetLanguage.Spirv, SpirvGenerator.Generate(sInfo, parameters)), + _ => throw new NotImplementedException(Options.TargetLanguage.ToString()), + }; + } + + private ResourceManager CreateResourceManager(bool vertexAsCompute) + { + ResourceManager resourceManager = new(Definitions.Stage, GpuAccessor, GetResourceReservations()); + + if (IsTransformFeedbackEmulated) + { + StructureType tfeDataStruct = new(new StructureField[] + { + new StructureField(AggregateType.Array | AggregateType.U32, "data", 0) + }); + + for (int i = 0; i < ResourceReservations.TfeBuffersCount; i++) + { + int binding = resourceManager.Reservations.GetTfeBufferStorageBufferBinding(i); + BufferDefinition tfeDataBuffer = new(BufferLayout.Std430, 1, binding, $"tfe_data{i}", tfeDataStruct); + resourceManager.Properties.AddOrUpdateStorageBuffer(tfeDataBuffer); + } + } + + if (vertexAsCompute) + { + int vertexInfoCbBinding = resourceManager.Reservations.VertexInfoConstantBufferBinding; + BufferDefinition vertexInfoBuffer = new(BufferLayout.Std140, 0, vertexInfoCbBinding, "vb_info", VertexInfoBuffer.GetStructureType()); + resourceManager.Properties.AddOrUpdateConstantBuffer(vertexInfoBuffer); + + StructureType vertexOutputStruct = new(new StructureField[] + { + new StructureField(AggregateType.Array | AggregateType.FP32, "data", 0) + }); + + int vertexOutputSbBinding = resourceManager.Reservations.VertexOutputStorageBufferBinding; + BufferDefinition vertexOutputBuffer = new(BufferLayout.Std430, 1, vertexOutputSbBinding, "vertex_output", vertexOutputStruct); + resourceManager.Properties.AddOrUpdateStorageBuffer(vertexOutputBuffer); + + if (Stage == ShaderStage.Vertex) + { + SetBindingPair ibSetAndBinding = resourceManager.Reservations.GetIndexBufferTextureSetAndBinding(); + TextureDefinition indexBuffer = new(ibSetAndBinding.SetIndex, ibSetAndBinding.Binding, "ib_data", SamplerType.TextureBuffer); + resourceManager.Properties.AddOrUpdateTexture(indexBuffer); + + int inputMap = _program.AttributeUsage.UsedInputAttributes; + + while (inputMap != 0) + { + int location = BitOperations.TrailingZeroCount(inputMap); + SetBindingPair setAndBinding = resourceManager.Reservations.GetVertexBufferTextureSetAndBinding(location); + TextureDefinition vaBuffer = new(setAndBinding.SetIndex, setAndBinding.Binding, $"vb_data{location}", SamplerType.TextureBuffer); + resourceManager.Properties.AddOrUpdateTexture(vaBuffer); + + inputMap &= ~(1 << location); + } + } + else if (Stage == ShaderStage.Geometry) + { + SetBindingPair trbSetAndBinding = resourceManager.Reservations.GetTopologyRemapBufferTextureSetAndBinding(); + TextureDefinition remapBuffer = new(trbSetAndBinding.SetIndex, trbSetAndBinding.Binding, "trb_data", SamplerType.TextureBuffer); + resourceManager.Properties.AddOrUpdateTexture(remapBuffer); + + int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding; + BufferDefinition geometryVbOutputBuffer = new(BufferLayout.Std430, 1, geometryVbOutputSbBinding, "geometry_vb_output", vertexOutputStruct); + resourceManager.Properties.AddOrUpdateStorageBuffer(geometryVbOutputBuffer); + + StructureType geometryIbOutputStruct = new(new StructureField[] + { + new StructureField(AggregateType.Array | AggregateType.U32, "data", 0) + }); + + int geometryIbOutputSbBinding = resourceManager.Reservations.GeometryIndexOutputStorageBufferBinding; + BufferDefinition geometryIbOutputBuffer = new(BufferLayout.Std430, 1, geometryIbOutputSbBinding, "geometry_ib_output", geometryIbOutputStruct); + resourceManager.Properties.AddOrUpdateStorageBuffer(geometryIbOutputBuffer); + } + + resourceManager.SetVertexAsComputeLocalMemories(Definitions.Stage, Definitions.InputTopology); + } + + return resourceManager; + } + + private ShaderDefinitions GetDefinitions(bool vertexAsCompute) + { + if (vertexAsCompute) + { + return new ShaderDefinitions(ShaderStage.Compute, 32, 32, 1); + } + else + { + return Definitions; + } + } + + public ResourceReservations GetResourceReservations() + { + IoUsage ioUsage = _program.GetIoUsage(); + + if (Definitions.GpPassthrough) + { + ioUsage = ioUsage.Combine(_vertexOutput); + } + + return new ResourceReservations(GpuAccessor, IsTransformFeedbackEmulated, vertexAsCompute: true, _vertexOutput, ioUsage); + } + + public void SetVertexOutputMapForGeometryAsCompute(TranslatorContext vertexContext) + { + _vertexOutput = vertexContext._program.GetIoUsage(); + } + + public ShaderProgram GenerateVertexPassthroughForCompute() + { + var attributeUsage = new AttributeUsage(GpuAccessor); + var resourceManager = new ResourceManager(ShaderStage.Vertex, GpuAccessor); + + var reservations = GetResourceReservations(); + + int vertexInfoCbBinding = reservations.VertexInfoConstantBufferBinding; + + if (Stage == ShaderStage.Vertex) + { + BufferDefinition vertexInfoBuffer = new(BufferLayout.Std140, 0, vertexInfoCbBinding, "vb_info", VertexInfoBuffer.GetStructureType()); + resourceManager.Properties.AddOrUpdateConstantBuffer(vertexInfoBuffer); + } + + StructureType vertexInputStruct = new(new StructureField[] + { + new StructureField(AggregateType.Array | AggregateType.FP32, "data", 0) + }); + + int vertexDataSbBinding = reservations.VertexOutputStorageBufferBinding; + BufferDefinition vertexOutputBuffer = new(BufferLayout.Std430, 1, vertexDataSbBinding, "vb_input", vertexInputStruct); + resourceManager.Properties.AddOrUpdateStorageBuffer(vertexOutputBuffer); + + var context = new EmitterContext(); + + Operand vertexIndex = Options.TargetApi == TargetApi.OpenGL + ? context.Load(StorageKind.Input, IoVariable.VertexId) + : context.Load(StorageKind.Input, IoVariable.VertexIndex); + + if (Stage == ShaderStage.Vertex) + { + Operand vertexCount = context.Load(StorageKind.ConstantBuffer, vertexInfoCbBinding, Const((int)VertexInfoBufferField.VertexCounts), Const(0)); + + // Base instance will be always zero when this shader is used, so which one we use here doesn't really matter. + Operand instanceId = Options.TargetApi == TargetApi.OpenGL + ? context.Load(StorageKind.Input, IoVariable.InstanceId) + : context.Load(StorageKind.Input, IoVariable.InstanceIndex); + + vertexIndex = context.IAdd(context.IMultiply(instanceId, vertexCount), vertexIndex); + } + + Operand baseOffset = context.IMultiply(vertexIndex, Const(reservations.OutputSizePerInvocation)); + + foreach ((IoDefinition ioDefinition, int inputOffset) in reservations.Offsets) + { + if (ioDefinition.StorageKind != StorageKind.Output) + { + continue; + } + + Operand vertexOffset = inputOffset != 0 ? context.IAdd(baseOffset, Const(inputOffset)) : baseOffset; + Operand value = context.Load(StorageKind.StorageBuffer, vertexDataSbBinding, Const(0), vertexOffset); + + if (ioDefinition.IoVariable == IoVariable.UserDefined) + { + context.Store(StorageKind.Output, ioDefinition.IoVariable, null, Const(ioDefinition.Location), Const(ioDefinition.Component), value); + attributeUsage.SetOutputUserAttribute(ioDefinition.Location); + } + else if (ResourceReservations.IsVectorOrArrayVariable(ioDefinition.IoVariable)) + { + context.Store(StorageKind.Output, ioDefinition.IoVariable, null, Const(ioDefinition.Component), value); + } + else + { + context.Store(StorageKind.Output, ioDefinition.IoVariable, null, value); + } + } + + var operations = context.GetOperations(); + var cfg = ControlFlowGraph.Create(operations); + var function = new Function(cfg.Blocks, "main", false, 0, 0); + + var transformFeedbackOutputs = GetTransformFeedbackOutputs(GpuAccessor, out ulong transformFeedbackVecMap); + + var definitions = new ShaderDefinitions(ShaderStage.Vertex, transformFeedbackVecMap, transformFeedbackOutputs) + { + LastInVertexPipeline = true + }; + + return Generate( + new[] { function }, + attributeUsage, + definitions, + definitions, + resourceManager, + FeatureFlags.None, + 0); + } + + public ShaderProgram GenerateGeometryPassthrough() + { + int outputAttributesMask = AttributeUsage.UsedOutputAttributes; + int layerOutputAttr = LayerOutputAttribute; + + if (LayerOutputWritten) + { + outputAttributesMask |= 1 << ((layerOutputAttr - AttributeConsts.UserAttributeBase) / 16); + } + + OutputTopology outputTopology; + int maxOutputVertices; + + switch (Definitions.InputTopology) + { + case InputTopology.Points: + outputTopology = OutputTopology.PointList; + maxOutputVertices = 1; + break; + case InputTopology.Lines: + case InputTopology.LinesAdjacency: + outputTopology = OutputTopology.LineStrip; + maxOutputVertices = 2; + break; + default: + outputTopology = OutputTopology.TriangleStrip; + maxOutputVertices = 3; + break; + } + + var attributeUsage = new AttributeUsage(GpuAccessor); + var resourceManager = new ResourceManager(ShaderStage.Geometry, GpuAccessor); + + var context = new EmitterContext(); + + for (int v = 0; v < maxOutputVertices; v++) + { + int outAttrsMask = outputAttributesMask; + + while (outAttrsMask != 0) + { + int attrIndex = BitOperations.TrailingZeroCount(outAttrsMask); + + outAttrsMask &= ~(1 << attrIndex); + + for (int c = 0; c < 4; c++) + { + int attr = AttributeConsts.UserAttributeBase + attrIndex * 16 + c * 4; + + Operand value = context.Load(StorageKind.Input, IoVariable.UserDefined, Const(attrIndex), Const(v), Const(c)); + + if (attr == layerOutputAttr) + { + context.Store(StorageKind.Output, IoVariable.Layer, null, value); + } + else + { + context.Store(StorageKind.Output, IoVariable.UserDefined, null, Const(attrIndex), Const(c), value); + } + } + } + + for (int c = 0; c < 4; c++) + { + Operand value = context.Load(StorageKind.Input, IoVariable.Position, Const(v), Const(c)); + + context.Store(StorageKind.Output, IoVariable.Position, null, Const(c), value); + } + + context.EmitVertex(); + } + + context.EndPrimitive(); + + var operations = context.GetOperations(); + var cfg = ControlFlowGraph.Create(operations); + var function = new Function(cfg.Blocks, "main", false, 0, 0); + + var definitions = new ShaderDefinitions( + ShaderStage.Geometry, + GpuAccessor.QueryGraphicsState(), + false, + 1, + outputTopology, + maxOutputVertices); + + return Generate( + new[] { function }, + attributeUsage, + definitions, + definitions, + resourceManager, + FeatureFlags.RtLayer, + 0); + } + } +} diff --git a/src/Ryujinx.Graphics.Shader/VertexInfoBuffer.cs b/src/Ryujinx.Graphics.Shader/VertexInfoBuffer.cs new file mode 100644 index 00000000..845135f8 --- /dev/null +++ b/src/Ryujinx.Graphics.Shader/VertexInfoBuffer.cs @@ -0,0 +1,59 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Shader.StructuredIr; +using Ryujinx.Graphics.Shader.Translation; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Shader +{ + enum VertexInfoBufferField + { + // Must match the order of the fields on the struct. + VertexCounts, + GeometryCounts, + VertexStrides, + VertexOffsets, + } + + public struct VertexInfoBuffer + { + public static readonly int RequiredSize; + + public static readonly int VertexCountsOffset; + public static readonly int GeometryCountsOffset; + public static readonly int VertexStridesOffset; + public static readonly int VertexOffsetsOffset; + + private static int OffsetOf(ref VertexInfoBuffer storage, ref T target) + { + return (int)Unsafe.ByteOffset(ref Unsafe.As(ref storage), ref target); + } + + static VertexInfoBuffer() + { + RequiredSize = Unsafe.SizeOf(); + + VertexInfoBuffer instance = new(); + + VertexCountsOffset = OffsetOf(ref instance, ref instance.VertexCounts); + GeometryCountsOffset = OffsetOf(ref instance, ref instance.GeometryCounts); + VertexStridesOffset = OffsetOf(ref instance, ref instance.VertexStrides); + VertexOffsetsOffset = OffsetOf(ref instance, ref instance.VertexOffsets); + } + + internal static StructureType GetStructureType() + { + return new StructureType(new[] + { + new StructureField(AggregateType.Vector4 | AggregateType.U32, "vertex_counts"), + new StructureField(AggregateType.Vector4 | AggregateType.U32, "geometry_counts"), + new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.U32, "vertex_strides", ResourceReservations.MaxVertexBufferTextures), + new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.U32, "vertex_offsets", ResourceReservations.MaxVertexBufferTextures), + }); + } + + public Vector4 VertexCounts; + public Vector4 GeometryCounts; + public Array32> VertexStrides; + public Array32> VertexOffsets; + } +} diff --git a/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs new file mode 100644 index 00000000..92e39d2e --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs @@ -0,0 +1,1708 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture.Astc +{ + // https://github.com/GammaUNC/FasTC/blob/master/ASTCEncoder/src/Decompressor.cpp + public class AstcDecoder + { + private ReadOnlyMemory InputBuffer { get; } + private Memory OutputBuffer { get; } + + private int BlockSizeX { get; } + private int BlockSizeY { get; } + + private AstcLevel[] Levels { get; } + + private bool Success { get; set; } + + public int TotalBlockCount { get; } + + public AstcDecoder( + ReadOnlyMemory inputBuffer, + Memory outputBuffer, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels, + int layers) + { + if ((uint)blockWidth > 12) + { + throw new ArgumentOutOfRangeException(nameof(blockWidth)); + } + + if ((uint)blockHeight > 12) + { + throw new ArgumentOutOfRangeException(nameof(blockHeight)); + } + + InputBuffer = inputBuffer; + OutputBuffer = outputBuffer; + + BlockSizeX = blockWidth; + BlockSizeY = blockHeight; + + Levels = new AstcLevel[levels * layers]; + + Success = true; + + TotalBlockCount = 0; + + int currentInputBlock = 0; + int currentOutputOffset = 0; + + for (int i = 0; i < levels; i++) + { + for (int j = 0; j < layers; j++) + { + ref AstcLevel level = ref Levels[i * layers + j]; + + level.ImageSizeX = Math.Max(1, width >> i); + level.ImageSizeY = Math.Max(1, height >> i); + level.ImageSizeZ = Math.Max(1, depth >> i); + + level.BlockCountX = (level.ImageSizeX + blockWidth - 1) / blockWidth; + level.BlockCountY = (level.ImageSizeY + blockHeight - 1) / blockHeight; + + level.StartBlock = currentInputBlock; + level.OutputByteOffset = currentOutputOffset; + + currentInputBlock += level.TotalBlockCount; + currentOutputOffset += level.PixelCount * 4; + } + } + + TotalBlockCount = currentInputBlock; + } + + private struct AstcLevel + { + public int ImageSizeX { get; set; } + public int ImageSizeY { get; set; } + public int ImageSizeZ { get; set; } + + public int BlockCountX { get; set; } + public int BlockCountY { get; set; } + + public int StartBlock { get; set; } + public int OutputByteOffset { get; set; } + + public readonly int TotalBlockCount => BlockCountX * BlockCountY * ImageSizeZ; + public readonly int PixelCount => ImageSizeX * ImageSizeY * ImageSizeZ; + } + + public static int QueryDecompressedSize(int sizeX, int sizeY, int sizeZ, int levelCount, int layerCount) + { + int size = 0; + + for (int i = 0; i < levelCount; i++) + { + int levelSizeX = Math.Max(1, sizeX >> i); + int levelSizeY = Math.Max(1, sizeY >> i); + int levelSizeZ = Math.Max(1, sizeZ >> i); + + size += levelSizeX * levelSizeY * levelSizeZ * layerCount; + } + + return size * 4; + } + + public void ProcessBlock(int index) + { + Buffer16 inputBlock = MemoryMarshal.Cast(InputBuffer.Span)[index]; + + Span decompressedData = stackalloc int[144]; + + try + { + DecompressBlock(inputBlock, decompressedData, BlockSizeX, BlockSizeY); + } + catch (Exception) + { + Success = false; + } + + Span decompressedBytes = MemoryMarshal.Cast(decompressedData); + + AstcLevel levelInfo = GetLevelInfo(index); + + WriteDecompressedBlock(decompressedBytes, OutputBuffer.Span[levelInfo.OutputByteOffset..], + index - levelInfo.StartBlock, levelInfo); + } + + private AstcLevel GetLevelInfo(int blockIndex) + { + foreach (AstcLevel levelInfo in Levels) + { + if (blockIndex < levelInfo.StartBlock + levelInfo.TotalBlockCount) + { + return levelInfo; + } + } + + throw new AstcDecoderException("Invalid block index."); + } + + private void WriteDecompressedBlock(ReadOnlySpan block, Span outputBuffer, int blockIndex, AstcLevel level) + { + int stride = level.ImageSizeX * 4; + + int blockCordX = blockIndex % level.BlockCountX; + int blockCordY = blockIndex / level.BlockCountX; + + int pixelCordX = blockCordX * BlockSizeX; + int pixelCordY = blockCordY * BlockSizeY; + + int outputPixelsX = Math.Min(pixelCordX + BlockSizeX, level.ImageSizeX) - pixelCordX; + int outputPixelsY = Math.Min(pixelCordY + BlockSizeY, level.ImageSizeY * level.ImageSizeZ) - pixelCordY; + + int outputStart = pixelCordX * 4 + pixelCordY * stride; + int outputOffset = outputStart; + + int inputOffset = 0; + + for (int i = 0; i < outputPixelsY; i++) + { + ReadOnlySpan blockRow = block.Slice(inputOffset, outputPixelsX * 4); + Span outputRow = outputBuffer[outputOffset..]; + blockRow.CopyTo(outputRow); + + inputOffset += BlockSizeX * 4; + outputOffset += stride; + } + } + + struct TexelWeightParams + { + public int Width; + public int Height; + public int MaxWeight; + public bool DualPlane; + public bool Error; + public bool VoidExtentLdr; + public bool VoidExtentHdr; + + public readonly int GetPackedBitSize() + { + // How many indices do we have? + int indices = Height * Width; + + if (DualPlane) + { + indices *= 2; + } + + IntegerEncoded intEncoded = IntegerEncoded.CreateEncoding(MaxWeight); + + return intEncoded.GetBitLength(indices); + } + + public readonly int GetNumWeightValues() + { + int ret = Width * Height; + + if (DualPlane) + { + ret *= 2; + } + + return ret; + } + } + + public static bool TryDecodeToRgba8( + ReadOnlyMemory data, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels, + int layers, + out Span decoded) + { + byte[] output = new byte[QueryDecompressedSize(width, height, depth, levels, layers)]; + + AstcDecoder decoder = new(data, output, blockWidth, blockHeight, width, height, depth, levels, layers); + + for (int i = 0; i < decoder.TotalBlockCount; i++) + { + decoder.ProcessBlock(i); + } + + decoded = output; + + return decoder.Success; + } + + public static bool TryDecodeToRgba8( + ReadOnlyMemory data, + Memory outputBuffer, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels, + int layers) + { + AstcDecoder decoder = new(data, outputBuffer, blockWidth, blockHeight, width, height, depth, levels, layers); + + for (int i = 0; i < decoder.TotalBlockCount; i++) + { + decoder.ProcessBlock(i); + } + + return decoder.Success; + } + + public static bool TryDecodeToRgba8P( + ReadOnlyMemory data, + Memory outputBuffer, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels, + int layers) + { + AstcDecoder decoder = new(data, outputBuffer, blockWidth, blockHeight, width, height, depth, levels, layers); + + // Lazy parallelism + Enumerable.Range(0, decoder.TotalBlockCount).AsParallel().ForAll(x => decoder.ProcessBlock(x)); + + return decoder.Success; + } + + public static bool TryDecodeToRgba8P( + ReadOnlyMemory data, + int blockWidth, + int blockHeight, + int width, + int height, + int depth, + int levels, + int layers, + out MemoryOwner decoded) + { + decoded = MemoryOwner.Rent(QueryDecompressedSize(width, height, depth, levels, layers)); + + AstcDecoder decoder = new(data, decoded.Memory, blockWidth, blockHeight, width, height, depth, levels, layers); + + Enumerable.Range(0, decoder.TotalBlockCount).AsParallel().ForAll(x => decoder.ProcessBlock(x)); + + return decoder.Success; + } + + public static bool DecompressBlock( + Buffer16 inputBlock, + Span outputBuffer, + int blockWidth, + int blockHeight) + { + BitStream128 bitStream = new(inputBlock); + + DecodeBlockInfo(ref bitStream, out TexelWeightParams texelParams); + + if (texelParams.Error) + { + throw new AstcDecoderException("Invalid block mode"); + } + + if (texelParams.VoidExtentLdr) + { + FillVoidExtentLdr(ref bitStream, outputBuffer, blockWidth, blockHeight); + + return true; + } + + if (texelParams.VoidExtentHdr) + { + throw new AstcDecoderException("HDR void extent blocks are not supported."); + } + + if (texelParams.Width > blockWidth) + { + throw new AstcDecoderException("Texel weight grid width should be smaller than block width."); + } + + if (texelParams.Height > blockHeight) + { + throw new AstcDecoderException("Texel weight grid height should be smaller than block height."); + } + + // Read num partitions + int numberPartitions = bitStream.ReadBits(2) + 1; + Debug.Assert(numberPartitions <= 4); + + if (numberPartitions == 4 && texelParams.DualPlane) + { + throw new AstcDecoderException("Dual plane mode is incompatible with four partition blocks."); + } + + // Based on the number of partitions, read the color endpoint mode for + // each partition. + + // Determine partitions, partition index, and color endpoint modes + int planeIndices; + int partitionIndex; + + Span colorEndpointMode = stackalloc uint[4]; + + BitStream128 colorEndpointStream = new(); + + // Read extra config data... + uint baseColorEndpointMode = 0; + + if (numberPartitions == 1) + { + colorEndpointMode[0] = (uint)bitStream.ReadBits(4); + partitionIndex = 0; + } + else + { + partitionIndex = bitStream.ReadBits(10); + baseColorEndpointMode = (uint)bitStream.ReadBits(6); + } + + uint baseMode = (baseColorEndpointMode & 3); + + // Remaining bits are color endpoint data... + int numberWeightBits = texelParams.GetPackedBitSize(); + int remainingBits = bitStream.BitsLeft - numberWeightBits; + + // Consider extra bits prior to texel data... + uint extraColorEndpointModeBits = 0; + + if (baseMode != 0) + { + switch (numberPartitions) + { + case 2: + extraColorEndpointModeBits += 2; + break; + case 3: + extraColorEndpointModeBits += 5; + break; + case 4: + extraColorEndpointModeBits += 8; + break; + default: + Debug.Assert(false); + break; + } + } + + remainingBits -= (int)extraColorEndpointModeBits; + + // Do we have a dual plane situation? + int planeSelectorBits = 0; + + if (texelParams.DualPlane) + { + planeSelectorBits = 2; + } + + remainingBits -= planeSelectorBits; + + // Read color data... + int colorDataBits = remainingBits; + + while (remainingBits > 0) + { + int numberBits = Math.Min(remainingBits, 8); + int bits = bitStream.ReadBits(numberBits); + colorEndpointStream.WriteBits(bits, numberBits); + remainingBits -= 8; + } + + // Read the plane selection bits + planeIndices = bitStream.ReadBits(planeSelectorBits); + + // Read the rest of the CEM + if (baseMode != 0) + { + uint extraColorEndpointMode = (uint)bitStream.ReadBits((int)extraColorEndpointModeBits); + uint tempColorEndpointMode = (extraColorEndpointMode << 6) | baseColorEndpointMode; + tempColorEndpointMode >>= 2; + + Span c = stackalloc bool[4]; + + for (int i = 0; i < numberPartitions; i++) + { + c[i] = (tempColorEndpointMode & 1) != 0; + tempColorEndpointMode >>= 1; + } + + Span m = stackalloc byte[4]; + + for (int i = 0; i < numberPartitions; i++) + { + m[i] = (byte)(tempColorEndpointMode & 3); + tempColorEndpointMode >>= 2; + Debug.Assert(m[i] <= 3); + } + + for (int i = 0; i < numberPartitions; i++) + { + colorEndpointMode[i] = baseMode; + + if (!(c[i])) + { + colorEndpointMode[i] -= 1; + } + + colorEndpointMode[i] <<= 2; + colorEndpointMode[i] |= m[i]; + } + } + else if (numberPartitions > 1) + { + uint tempColorEndpointMode = baseColorEndpointMode >> 2; + + for (int i = 0; i < numberPartitions; i++) + { + colorEndpointMode[i] = tempColorEndpointMode; + } + } + + // Make sure everything up till here is sane. + for (int i = 0; i < numberPartitions; i++) + { + Debug.Assert(colorEndpointMode[i] < 16); + } + Debug.Assert(bitStream.BitsLeft == texelParams.GetPackedBitSize()); + + // Decode both color data and texel weight data + Span colorValues = stackalloc int[32]; // Four values * two endpoints * four maximum partitions + DecodeColorValues(colorValues, ref colorEndpointStream, colorEndpointMode, numberPartitions, colorDataBits); + + EndPointSet endPoints; + + unsafe + { + // Skip struct initialization + _ = &endPoints; + } + + int colorValuesPosition = 0; + + for (int i = 0; i < numberPartitions; i++) + { + ComputeEndpoints(endPoints.Get(i), colorValues, colorEndpointMode[i], ref colorValuesPosition); + } + + // Read the texel weight data. + Buffer16 texelWeightData = inputBlock; + + // Reverse everything + for (int i = 0; i < 8; i++) + { + byte a = ReverseByte(texelWeightData[i]); + byte b = ReverseByte(texelWeightData[15 - i]); + + texelWeightData[i] = b; + texelWeightData[15 - i] = a; + } + + // Make sure that higher non-texel bits are set to zero + int clearByteStart = (texelParams.GetPackedBitSize() >> 3) + 1; + texelWeightData[clearByteStart - 1] &= (byte)((1 << (texelParams.GetPackedBitSize() % 8)) - 1); + + int cLen = 16 - clearByteStart; + for (int i = clearByteStart; i < clearByteStart + cLen; i++) + { + texelWeightData[i] = 0; + } + + IntegerSequence texelWeightValues; + + unsafe + { + // Skip struct initialization + _ = &texelWeightValues; + } + + texelWeightValues.Reset(); + + BitStream128 weightBitStream = new(texelWeightData); + + IntegerEncoded.DecodeIntegerSequence(ref texelWeightValues, ref weightBitStream, texelParams.MaxWeight, texelParams.GetNumWeightValues()); + + // Blocks can be at most 12x12, so we can have as many as 144 weights + Weights weights; + + unsafe + { + // Skip struct initialization + _ = &weights; + } + + UnquantizeTexelWeights(ref weights, ref texelWeightValues, ref texelParams, blockWidth, blockHeight); + + ushort[] table = Bits.Replicate8_16Table; + + // Now that we have endpoints and weights, we can interpolate and generate + // the proper decoding... + for (int j = 0; j < blockHeight; j++) + { + for (int i = 0; i < blockWidth; i++) + { + int partition = Select2dPartition(partitionIndex, i, j, numberPartitions, ((blockHeight * blockWidth) < 32)); + Debug.Assert(partition < numberPartitions); + + AstcPixel pixel = new(); + for (int component = 0; component < 4; component++) + { + int component0 = endPoints.Get(partition)[0].GetComponent(component); + component0 = table[component0]; + int component1 = endPoints.Get(partition)[1].GetComponent(component); + component1 = table[component1]; + + int plane = 0; + + if (texelParams.DualPlane && (((planeIndices + 1) & 3) == component)) + { + plane = 1; + } + + int weight = weights.Get(plane)[j * blockWidth + i]; + int finalComponent = (component0 * (64 - weight) + component1 * weight + 32) / 64; + + if (finalComponent == 65535) + { + pixel.SetComponent(component, 255); + } + else + { + double finalComponentFloat = finalComponent; + pixel.SetComponent(component, (int)(255.0 * (finalComponentFloat / 65536.0) + 0.5)); + } + } + + outputBuffer[j * blockWidth + i] = pixel.Pack(); + } + } + + return true; + } + + // Blocks can be at most 12x12, so we can have as many as 144 weights + [StructLayout(LayoutKind.Sequential, Size = 144 * sizeof(int) * Count)] + private struct Weights + { + private int _start; + + public const int Count = 2; + + public Span this[int index] + { + get + { + if ((uint)index >= Count) + { + throw new ArgumentOutOfRangeException(nameof(index), index, null); + } + + ref int start = ref Unsafe.Add(ref _start, index * 144); + + return MemoryMarshal.CreateSpan(ref start, 144); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span Get(int index) + { + ref int start = ref Unsafe.Add(ref _start, index * 144); + + return MemoryMarshal.CreateSpan(ref start, 144); + } + } + + private static int Select2dPartition(int seed, int x, int y, int partitionCount, bool isSmallBlock) + { + return SelectPartition(seed, x, y, 0, partitionCount, isSmallBlock); + } + + private static int SelectPartition(int seed, int x, int y, int z, int partitionCount, bool isSmallBlock) + { + if (partitionCount == 1) + { + return 0; + } + + if (isSmallBlock) + { + x <<= 1; + y <<= 1; + z <<= 1; + } + + seed += (partitionCount - 1) * 1024; + + int rightNum = Hash52((uint)seed); + byte seed01 = (byte)(rightNum & 0xF); + byte seed02 = (byte)((rightNum >> 4) & 0xF); + byte seed03 = (byte)((rightNum >> 8) & 0xF); + byte seed04 = (byte)((rightNum >> 12) & 0xF); + byte seed05 = (byte)((rightNum >> 16) & 0xF); + byte seed06 = (byte)((rightNum >> 20) & 0xF); + byte seed07 = (byte)((rightNum >> 24) & 0xF); + byte seed08 = (byte)((rightNum >> 28) & 0xF); + byte seed09 = (byte)((rightNum >> 18) & 0xF); + byte seed10 = (byte)((rightNum >> 22) & 0xF); + byte seed11 = (byte)((rightNum >> 26) & 0xF); + byte seed12 = (byte)(((rightNum >> 30) | (rightNum << 2)) & 0xF); + + seed01 *= seed01; + seed02 *= seed02; + seed03 *= seed03; + seed04 *= seed04; + seed05 *= seed05; + seed06 *= seed06; + seed07 *= seed07; + seed08 *= seed08; + seed09 *= seed09; + seed10 *= seed10; + seed11 *= seed11; + seed12 *= seed12; + + int seedHash1, seedHash2, seedHash3; + + if ((seed & 1) != 0) + { + seedHash1 = (seed & 2) != 0 ? 4 : 5; + seedHash2 = (partitionCount == 3) ? 6 : 5; + } + else + { + seedHash1 = (partitionCount == 3) ? 6 : 5; + seedHash2 = (seed & 2) != 0 ? 4 : 5; + } + + seedHash3 = (seed & 0x10) != 0 ? seedHash1 : seedHash2; + + seed01 >>= seedHash1; + seed02 >>= seedHash2; + seed03 >>= seedHash1; + seed04 >>= seedHash2; + seed05 >>= seedHash1; + seed06 >>= seedHash2; + seed07 >>= seedHash1; + seed08 >>= seedHash2; + seed09 >>= seedHash3; + seed10 >>= seedHash3; + seed11 >>= seedHash3; + seed12 >>= seedHash3; + + int a = seed01 * x + seed02 * y + seed11 * z + (rightNum >> 14); + int b = seed03 * x + seed04 * y + seed12 * z + (rightNum >> 10); + int c = seed05 * x + seed06 * y + seed09 * z + (rightNum >> 6); + int d = seed07 * x + seed08 * y + seed10 * z + (rightNum >> 2); + + a &= 0x3F; + b &= 0x3F; + c &= 0x3F; + d &= 0x3F; + + if (partitionCount < 4) + { + d = 0; + } + + if (partitionCount < 3) + { + c = 0; + } + + if (a >= b && a >= c && a >= d) + { + return 0; + } + else if (b >= c && b >= d) + { + return 1; + } + else if (c >= d) + { + return 2; + } + + return 3; + } + + static int Hash52(uint val) + { + val ^= val >> 15; + val -= val << 17; + val += val << 7; + val += val << 4; + val ^= val >> 5; + val += val << 16; + val ^= val >> 7; + val ^= val >> 3; + val ^= val << 6; + val ^= val >> 17; + + return (int)val; + } + + static void UnquantizeTexelWeights( + ref Weights outputBuffer, + ref IntegerSequence weights, + ref TexelWeightParams texelParams, + int blockWidth, + int blockHeight) + { + int weightIndices = 0; + Weights unquantized; + + unsafe + { + // Skip struct initialization + _ = &unquantized; + } + + Span weightsList = weights.List; + Span unquantized0 = unquantized[0]; + Span unquantized1 = unquantized[1]; + + for (int i = 0; i < weightsList.Length; i++) + { + unquantized0[weightIndices] = UnquantizeTexelWeight(weightsList[i]); + + if (texelParams.DualPlane) + { + i++; + unquantized1[weightIndices] = UnquantizeTexelWeight(weightsList[i]); + + if (i == weightsList.Length) + { + break; + } + } + + if (++weightIndices >= texelParams.Width * texelParams.Height) + { + break; + } + } + + // Do infill if necessary (Section C.2.18) ... + int ds = (1024 + blockWidth / 2) / (blockWidth - 1); + int dt = (1024 + blockHeight / 2) / (blockHeight - 1); + + int planeScale = texelParams.DualPlane ? 2 : 1; + + for (int plane = 0; plane < planeScale; plane++) + { + Span unquantizedSpan = unquantized.Get(plane); + Span outputSpan = outputBuffer.Get(plane); + + for (int t = 0; t < blockHeight; t++) + { + for (int s = 0; s < blockWidth; s++) + { + int cs = ds * s; + int ct = dt * t; + + int gs = (cs * (texelParams.Width - 1) + 32) >> 6; + int gt = (ct * (texelParams.Height - 1) + 32) >> 6; + + int js = gs >> 4; + int fs = gs & 0xF; + + int jt = gt >> 4; + int ft = gt & 0x0F; + + int w11 = (fs * ft + 8) >> 4; + + int v0 = js + jt * texelParams.Width; + + int weight = 8; + + int wxh = texelParams.Width * texelParams.Height; + + if (v0 < wxh) + { + weight += unquantizedSpan[v0] * (16 - fs - ft + w11); + + if (v0 + 1 < wxh) + { + weight += unquantizedSpan[v0 + 1] * (fs - w11); + } + } + + if (v0 + texelParams.Width < wxh) + { + weight += unquantizedSpan[v0 + texelParams.Width] * (ft - w11); + + if (v0 + texelParams.Width + 1 < wxh) + { + weight += unquantizedSpan[v0 + texelParams.Width + 1] * w11; + } + } + + outputSpan[t * blockWidth + s] = weight >> 4; + } + } + } + } + + static int UnquantizeTexelWeight(IntegerEncoded intEncoded) + { + int bitValue = intEncoded.BitValue; + int bitLength = intEncoded.NumberBits; + + int a = Bits.Replicate1_7(bitValue & 1); + int b = 0, c = 0, d = 0; + + int result = 0; + + switch (intEncoded.GetEncoding()) + { + case IntegerEncoded.EIntegerEncoding.JustBits: + result = Bits.Replicate(bitValue, bitLength, 6); + break; + + case IntegerEncoded.EIntegerEncoding.Trit: + { + d = intEncoded.TritValue; + Debug.Assert(d < 3); + + switch (bitLength) + { + case 0: + { + result = d switch + { + 0 => 0, + 1 => 32, + 2 => 63, + _ => 0, + }; + + break; + } + + case 1: + { + c = 50; + break; + } + + case 2: + { + c = 23; + int b2 = (bitValue >> 1) & 1; + b = (b2 << 6) | (b2 << 2) | b2; + + break; + } + + case 3: + { + c = 11; + int cb = (bitValue >> 1) & 3; + b = (cb << 5) | cb; + + break; + } + + default: + throw new AstcDecoderException("Invalid trit encoding for texel weight."); + } + + break; + } + + case IntegerEncoded.EIntegerEncoding.Quint: + { + d = intEncoded.QuintValue; + Debug.Assert(d < 5); + + switch (bitLength) + { + case 0: + { + result = d switch + { + 0 => 0, + 1 => 16, + 2 => 32, + 3 => 47, + 4 => 63, + _ => 0, + }; + + break; + } + + case 1: + { + c = 28; + + break; + } + + case 2: + { + c = 13; + int b2 = (bitValue >> 1) & 1; + b = (b2 << 6) | (b2 << 1); + + break; + } + + default: + throw new AstcDecoderException("Invalid quint encoding for texel weight."); + } + + break; + } + } + + if (intEncoded.GetEncoding() != IntegerEncoded.EIntegerEncoding.JustBits && bitLength > 0) + { + // Decode the value... + result = d * c + b; + result ^= a; + result = (a & 0x20) | (result >> 2); + } + + Debug.Assert(result < 64); + + // Change from [0,63] to [0,64] + if (result > 32) + { + result += 1; + } + + return result; + } + + static byte ReverseByte(byte b) + { + // Taken from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits + return (byte)((((b) * 0x80200802L) & 0x0884422110L) * 0x0101010101L >> 32); + } + + static Span ReadUintColorValues(int number, Span colorValues, ref int colorValuesPosition) + { + Span ret = colorValues.Slice(colorValuesPosition, number); + + colorValuesPosition += number; + + return MemoryMarshal.Cast(ret); + } + + static Span ReadIntColorValues(int number, Span colorValues, ref int colorValuesPosition) + { + Span ret = colorValues.Slice(colorValuesPosition, number); + + colorValuesPosition += number; + + return ret; + } + + static void ComputeEndpoints( + Span endPoints, + Span colorValues, + uint colorEndpointMode, + ref int colorValuesPosition) + { + switch (colorEndpointMode) + { + case 0: + { + Span val = ReadUintColorValues(2, colorValues, ref colorValuesPosition); + + endPoints[0] = new AstcPixel(0xFF, (short)val[0], (short)val[0], (short)val[0]); + endPoints[1] = new AstcPixel(0xFF, (short)val[1], (short)val[1], (short)val[1]); + + break; + } + + + case 1: + { + Span val = ReadUintColorValues(2, colorValues, ref colorValuesPosition); + int l0 = (int)((val[0] >> 2) | (val[1] & 0xC0)); + int l1 = (int)Math.Min(l0 + (val[1] & 0x3F), 0xFFU); + + endPoints[0] = new AstcPixel(0xFF, (short)l0, (short)l0, (short)l0); + endPoints[1] = new AstcPixel(0xFF, (short)l1, (short)l1, (short)l1); + + break; + } + + case 4: + { + Span val = ReadUintColorValues(4, colorValues, ref colorValuesPosition); + + endPoints[0] = new AstcPixel((short)val[2], (short)val[0], (short)val[0], (short)val[0]); + endPoints[1] = new AstcPixel((short)val[3], (short)val[1], (short)val[1], (short)val[1]); + + break; + } + + case 5: + { + Span val = ReadIntColorValues(4, colorValues, ref colorValuesPosition); + + Bits.BitTransferSigned(ref val[1], ref val[0]); + Bits.BitTransferSigned(ref val[3], ref val[2]); + + endPoints[0] = new AstcPixel((short)val[2], (short)val[0], (short)val[0], (short)val[0]); + endPoints[1] = new AstcPixel((short)(val[2] + val[3]), (short)(val[0] + val[1]), (short)(val[0] + val[1]), (short)(val[0] + val[1])); + + endPoints[0].ClampByte(); + endPoints[1].ClampByte(); + + break; + } + + case 6: + { + Span val = ReadUintColorValues(4, colorValues, ref colorValuesPosition); + + endPoints[0] = new AstcPixel(0xFF, (short)(val[0] * val[3] >> 8), (short)(val[1] * val[3] >> 8), (short)(val[2] * val[3] >> 8)); + endPoints[1] = new AstcPixel(0xFF, (short)val[0], (short)val[1], (short)val[2]); + + break; + } + + case 8: + { + Span val = ReadUintColorValues(6, colorValues, ref colorValuesPosition); + + if (val[1] + val[3] + val[5] >= val[0] + val[2] + val[4]) + { + endPoints[0] = new AstcPixel(0xFF, (short)val[0], (short)val[2], (short)val[4]); + endPoints[1] = new AstcPixel(0xFF, (short)val[1], (short)val[3], (short)val[5]); + } + else + { + endPoints[0] = AstcPixel.BlueContract(0xFF, (short)val[1], (short)val[3], (short)val[5]); + endPoints[1] = AstcPixel.BlueContract(0xFF, (short)val[0], (short)val[2], (short)val[4]); + } + + break; + } + + case 9: + { + Span val = ReadIntColorValues(6, colorValues, ref colorValuesPosition); + + Bits.BitTransferSigned(ref val[1], ref val[0]); + Bits.BitTransferSigned(ref val[3], ref val[2]); + Bits.BitTransferSigned(ref val[5], ref val[4]); + + if (val[1] + val[3] + val[5] >= 0) + { + endPoints[0] = new AstcPixel(0xFF, (short)val[0], (short)val[2], (short)val[4]); + endPoints[1] = new AstcPixel(0xFF, (short)(val[0] + val[1]), (short)(val[2] + val[3]), (short)(val[4] + val[5])); + } + else + { + endPoints[0] = AstcPixel.BlueContract(0xFF, val[0] + val[1], val[2] + val[3], val[4] + val[5]); + endPoints[1] = AstcPixel.BlueContract(0xFF, val[0], val[2], val[4]); + } + + endPoints[0].ClampByte(); + endPoints[1].ClampByte(); + + break; + } + + case 10: + { + Span val = ReadUintColorValues(6, colorValues, ref colorValuesPosition); + + endPoints[0] = new AstcPixel((short)val[4], (short)(val[0] * val[3] >> 8), (short)(val[1] * val[3] >> 8), (short)(val[2] * val[3] >> 8)); + endPoints[1] = new AstcPixel((short)val[5], (short)val[0], (short)val[1], (short)val[2]); + + break; + } + + case 12: + { + Span val = ReadUintColorValues(8, colorValues, ref colorValuesPosition); + + if (val[1] + val[3] + val[5] >= val[0] + val[2] + val[4]) + { + endPoints[0] = new AstcPixel((short)val[6], (short)val[0], (short)val[2], (short)val[4]); + endPoints[1] = new AstcPixel((short)val[7], (short)val[1], (short)val[3], (short)val[5]); + } + else + { + endPoints[0] = AstcPixel.BlueContract((short)val[7], (short)val[1], (short)val[3], (short)val[5]); + endPoints[1] = AstcPixel.BlueContract((short)val[6], (short)val[0], (short)val[2], (short)val[4]); + } + + break; + } + + case 13: + { + Span val = ReadIntColorValues(8, colorValues, ref colorValuesPosition); + + Bits.BitTransferSigned(ref val[1], ref val[0]); + Bits.BitTransferSigned(ref val[3], ref val[2]); + Bits.BitTransferSigned(ref val[5], ref val[4]); + Bits.BitTransferSigned(ref val[7], ref val[6]); + + if (val[1] + val[3] + val[5] >= 0) + { + endPoints[0] = new AstcPixel((short)val[6], (short)val[0], (short)val[2], (short)val[4]); + endPoints[1] = new AstcPixel((short)(val[7] + val[6]), (short)(val[0] + val[1]), (short)(val[2] + val[3]), (short)(val[4] + val[5])); + } + else + { + endPoints[0] = AstcPixel.BlueContract(val[6] + val[7], val[0] + val[1], val[2] + val[3], val[4] + val[5]); + endPoints[1] = AstcPixel.BlueContract(val[6], val[0], val[2], val[4]); + } + + endPoints[0].ClampByte(); + endPoints[1].ClampByte(); + + break; + } + + default: + throw new AstcDecoderException("Unsupported color endpoint mode (is it HDR?)"); + } + } + + static void DecodeColorValues( + Span outputValues, + ref BitStream128 colorBitStream, + Span modes, + int numberPartitions, + int numberBitsForColorData) + { + // First figure out how many color values we have + int numberValues = 0; + + for (int i = 0; i < numberPartitions; i++) + { + numberValues += (int)((modes[i] >> 2) + 1) << 1; + } + + // Then based on the number of values and the remaining number of bits, + // figure out the max value for each of them... + int range = 256; + + while (--range > 0) + { + IntegerEncoded intEncoded = IntegerEncoded.CreateEncoding(range); + int bitLength = intEncoded.GetBitLength(numberValues); + + if (bitLength <= numberBitsForColorData) + { + // Find the smallest possible range that matches the given encoding + while (--range > 0) + { + IntegerEncoded newIntEncoded = IntegerEncoded.CreateEncoding(range); + if (!newIntEncoded.MatchesEncoding(intEncoded)) + { + break; + } + } + + // Return to last matching range. + range++; + break; + } + } + + // We now have enough to decode our integer sequence. + IntegerSequence integerEncodedSequence; + + unsafe + { + // Skip struct initialization + _ = &integerEncodedSequence; + } + + integerEncodedSequence.Reset(); + + IntegerEncoded.DecodeIntegerSequence(ref integerEncodedSequence, ref colorBitStream, range, numberValues); + + // Once we have the decoded values, we need to dequantize them to the 0-255 range + // This procedure is outlined in ASTC spec C.2.13 + int outputIndices = 0; + + foreach (ref IntegerEncoded intEncoded in integerEncodedSequence.List) + { + int bitLength = intEncoded.NumberBits; + int bitValue = intEncoded.BitValue; + + Debug.Assert(bitLength >= 1); + + int b = 0, c = 0, d = 0; + // A is just the lsb replicated 9 times. + int a = Bits.Replicate(bitValue & 1, 1, 9); + + switch (intEncoded.GetEncoding()) + { + case IntegerEncoded.EIntegerEncoding.JustBits: + { + outputValues[outputIndices++] = Bits.Replicate(bitValue, bitLength, 8); + + break; + } + + case IntegerEncoded.EIntegerEncoding.Trit: + { + d = intEncoded.TritValue; + + switch (bitLength) + { + case 1: + { + c = 204; + + break; + } + + case 2: + { + c = 93; + // B = b000b0bb0 + int b2 = (bitValue >> 1) & 1; + b = (b2 << 8) | (b2 << 4) | (b2 << 2) | (b2 << 1); + + break; + } + + case 3: + { + c = 44; + // B = cb000cbcb + int cb = (bitValue >> 1) & 3; + b = (cb << 7) | (cb << 2) | cb; + + break; + } + + + case 4: + { + c = 22; + // B = dcb000dcb + int dcb = (bitValue >> 1) & 7; + b = (dcb << 6) | dcb; + + break; + } + + case 5: + { + c = 11; + // B = edcb000ed + int edcb = (bitValue >> 1) & 0xF; + b = (edcb << 5) | (edcb >> 2); + + break; + } + + case 6: + { + c = 5; + // B = fedcb000f + int fedcb = (bitValue >> 1) & 0x1F; + b = (fedcb << 4) | (fedcb >> 4); + + break; + } + + default: + throw new AstcDecoderException("Unsupported trit encoding for color values."); + } + + break; + } + + case IntegerEncoded.EIntegerEncoding.Quint: + { + d = intEncoded.QuintValue; + + switch (bitLength) + { + case 1: + { + c = 113; + + break; + } + + case 2: + { + c = 54; + // B = b0000bb00 + int b2 = (bitValue >> 1) & 1; + b = (b2 << 8) | (b2 << 3) | (b2 << 2); + + break; + } + + case 3: + { + c = 26; + // B = cb0000cbc + int cb = (bitValue >> 1) & 3; + b = (cb << 7) | (cb << 1) | (cb >> 1); + + break; + } + + case 4: + { + c = 13; + // B = dcb0000dc + int dcb = (bitValue >> 1) & 7; + b = (dcb << 6) | (dcb >> 1); + + break; + } + + case 5: + { + c = 6; + // B = edcb0000e + int edcb = (bitValue >> 1) & 0xF; + b = (edcb << 5) | (edcb >> 3); + + break; + } + + default: + throw new AstcDecoderException("Unsupported quint encoding for color values."); + } + break; + } + } + + if (intEncoded.GetEncoding() != IntegerEncoded.EIntegerEncoding.JustBits) + { + int T = d * c + b; + T ^= a; + T = (a & 0x80) | (T >> 2); + + outputValues[outputIndices++] = T; + } + } + + // Make sure that each of our values is in the proper range... + for (int i = 0; i < numberValues; i++) + { + Debug.Assert(outputValues[i] <= 255); + } + } + + static void FillVoidExtentLdr(ref BitStream128 bitStream, Span outputBuffer, int blockWidth, int blockHeight) + { + // Don't actually care about the void extent, just read the bits... + for (int i = 0; i < 4; ++i) + { + bitStream.ReadBits(13); + } + + // Decode the RGBA components and renormalize them to the range [0, 255] + ushort r = (ushort)bitStream.ReadBits(16); + ushort g = (ushort)bitStream.ReadBits(16); + ushort b = (ushort)bitStream.ReadBits(16); + ushort a = (ushort)bitStream.ReadBits(16); + + int rgba = (r >> 8) | (g & 0xFF00) | ((b) & 0xFF00) << 8 | ((a) & 0xFF00) << 16; + + for (int j = 0; j < blockHeight; j++) + { + for (int i = 0; i < blockWidth; i++) + { + outputBuffer[j * blockWidth + i] = rgba; + } + } + } + + static void DecodeBlockInfo(ref BitStream128 bitStream, out TexelWeightParams texelParams) + { + texelParams = new TexelWeightParams(); + + // Read the entire block mode all at once + ushort modeBits = (ushort)bitStream.ReadBits(11); + + // Does this match the void extent block mode? + if ((modeBits & 0x01FF) == 0x1FC) + { + if ((modeBits & 0x200) != 0) + { + texelParams.VoidExtentHdr = true; + } + else + { + texelParams.VoidExtentLdr = true; + } + + // Next two bits must be one. + if ((modeBits & 0x400) == 0 || bitStream.ReadBits(1) == 0) + { + texelParams.Error = true; + } + + return; + } + + // First check if the last four bits are zero + if ((modeBits & 0xF) == 0) + { + texelParams.Error = true; + + return; + } + + // If the last two bits are zero, then if bits + // [6-8] are all ones, this is also reserved. + if ((modeBits & 0x3) == 0 && (modeBits & 0x1C0) == 0x1C0) + { + texelParams.Error = true; + + return; + } + + // Otherwise, there is no error... Figure out the layout + // of the block mode. Layout is determined by a number + // between 0 and 9 corresponding to table C.2.8 of the + // ASTC spec. + int layout; + + if ((modeBits & 0x1) != 0 || (modeBits & 0x2) != 0) + { + // layout is in [0-4] + if ((modeBits & 0x8) != 0) + { + // layout is in [2-4] + if ((modeBits & 0x4) != 0) + { + // layout is in [3-4] + if ((modeBits & 0x100) != 0) + { + layout = 4; + } + else + { + layout = 3; + } + } + else + { + layout = 2; + } + } + else + { + // layout is in [0-1] + if ((modeBits & 0x4) != 0) + { + layout = 1; + } + else + { + layout = 0; + } + } + } + else + { + // layout is in [5-9] + if ((modeBits & 0x100) != 0) + { + // layout is in [7-9] + if ((modeBits & 0x80) != 0) + { + // layout is in [7-8] + Debug.Assert((modeBits & 0x40) == 0); + + if ((modeBits & 0x20) != 0) + { + layout = 8; + } + else + { + layout = 7; + } + } + else + { + layout = 9; + } + } + else + { + // layout is in [5-6] + if ((modeBits & 0x80) != 0) + { + layout = 6; + } + else + { + layout = 5; + } + } + } + + Debug.Assert(layout < 10); + + // Determine R + int r = (modeBits >> 4) & 1; + if (layout < 5) + { + r |= (modeBits & 0x3) << 1; + } + else + { + r |= (modeBits & 0xC) >> 1; + } + + Debug.Assert(2 <= r && r <= 7); + + // Determine width & height + switch (layout) + { + case 0: + { + int a = (modeBits >> 5) & 0x3; + int b = (modeBits >> 7) & 0x3; + + texelParams.Width = b + 4; + texelParams.Height = a + 2; + + break; + } + + case 1: + { + int a = (modeBits >> 5) & 0x3; + int b = (modeBits >> 7) & 0x3; + + texelParams.Width = b + 8; + texelParams.Height = a + 2; + + break; + } + + case 2: + { + int a = (modeBits >> 5) & 0x3; + int b = (modeBits >> 7) & 0x3; + + texelParams.Width = a + 2; + texelParams.Height = b + 8; + + break; + } + + case 3: + { + int a = (modeBits >> 5) & 0x3; + int b = (modeBits >> 7) & 0x1; + + texelParams.Width = a + 2; + texelParams.Height = b + 6; + + break; + } + + case 4: + { + int a = (modeBits >> 5) & 0x3; + int b = (modeBits >> 7) & 0x1; + + texelParams.Width = b + 2; + texelParams.Height = a + 2; + + break; + } + + case 5: + { + int a = (modeBits >> 5) & 0x3; + + texelParams.Width = 12; + texelParams.Height = a + 2; + + break; + } + + case 6: + { + int a = (modeBits >> 5) & 0x3; + + texelParams.Width = a + 2; + texelParams.Height = 12; + + break; + } + + case 7: + { + texelParams.Width = 6; + texelParams.Height = 10; + + break; + } + + case 8: + { + texelParams.Width = 10; + texelParams.Height = 6; + break; + } + + case 9: + { + int a = (modeBits >> 5) & 0x3; + int b = (modeBits >> 9) & 0x3; + + texelParams.Width = a + 6; + texelParams.Height = b + 6; + + break; + } + + default: + // Don't know this layout... + texelParams.Error = true; + break; + } + + // Determine whether or not we're using dual planes + // and/or high precision layouts. + bool d = ((layout != 9) && ((modeBits & 0x400) != 0)); + bool h = (layout != 9) && ((modeBits & 0x200) != 0); + + if (h) + { + ReadOnlySpan maxWeights = new byte[] { 9, 11, 15, 19, 23, 31 }; + texelParams.MaxWeight = maxWeights[r - 2]; + } + else + { + ReadOnlySpan maxWeights = new byte[] { 1, 2, 3, 4, 5, 7 }; + texelParams.MaxWeight = maxWeights[r - 2]; + } + + texelParams.DualPlane = d; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Astc/AstcDecoderException.cs b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoderException.cs new file mode 100644 index 00000000..9ff3ddb2 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoderException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.Graphics.Texture.Astc +{ + public class AstcDecoderException : Exception + { + public AstcDecoderException(string exMsg) : base(exMsg) { } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs b/src/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs new file mode 100644 index 00000000..b16cb002 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs @@ -0,0 +1,68 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture.Astc +{ + [StructLayout(LayoutKind.Sequential)] + struct AstcPixel + { + internal const int StructSize = 12; + + public short A; + public short R; + public short G; + public short B; + + private uint _bitDepthInt; + + private Span BitDepth => MemoryMarshal.CreateSpan(ref Unsafe.As(ref _bitDepthInt), 4); + private Span Components => MemoryMarshal.CreateSpan(ref A, 4); + + public AstcPixel(short a, short r, short g, short b) + { + A = a; + R = r; + G = g; + B = b; + + _bitDepthInt = 0x08080808; + } + + public void ClampByte() + { + R = Math.Min(Math.Max(R, (short)0), (short)255); + G = Math.Min(Math.Max(G, (short)0), (short)255); + B = Math.Min(Math.Max(B, (short)0), (short)255); + A = Math.Min(Math.Max(A, (short)0), (short)255); + } + + public short GetComponent(int index) + { + return Components[index]; + } + + public void SetComponent(int index, int value) + { + Components[index] = (short)value; + } + + public readonly int Pack() + { + return A << 24 | + B << 16 | + G << 8 | + R << 0; + } + + // Adds more precision to the blue channel as described + // in C.2.14 + public static AstcPixel BlueContract(int a, int r, int g, int b) + { + return new AstcPixel((short)(a), + (short)((r + b) >> 1), + (short)((g + b) >> 1), + (short)(b)); + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Astc/BitStream128.cs b/src/Ryujinx.Graphics.Texture/Astc/BitStream128.cs new file mode 100644 index 00000000..4402df7e --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Astc/BitStream128.cs @@ -0,0 +1,77 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Texture.Astc +{ + public struct BitStream128 + { +#pragma warning disable IDE0044 // Make field readonly + private Buffer16 _data; +#pragma warning restore IDE0044 + public int BitsLeft { get; set; } + + public BitStream128(Buffer16 data) + { + _data = data; + BitsLeft = 128; + } + + public int ReadBits(int bitCount) + { + Debug.Assert(bitCount < 32); + + if (bitCount == 0) + { + return 0; + } + + int mask = (1 << bitCount) - 1; + int value = _data.As() & mask; + + Span span = _data.AsSpan(); + + ulong carry = span[1] << (64 - bitCount); + span[0] = (span[0] >> bitCount) | carry; + span[1] >>= bitCount; + + BitsLeft -= bitCount; + + return value; + } + + public void WriteBits(int value, int bitCount) + { + Debug.Assert(bitCount < 32); + + if (bitCount == 0) + { + return; + } + + ulong maskedValue = (uint)(value & ((1 << bitCount) - 1)); + + Span span = _data.AsSpan(); + + if (BitsLeft < 64) + { + ulong lowMask = maskedValue << BitsLeft; + span[0] |= lowMask; + } + + if (BitsLeft + bitCount > 64) + { + if (BitsLeft > 64) + { + span[1] |= maskedValue << (BitsLeft - 64); + } + else + { + span[1] |= maskedValue >> (64 - BitsLeft); + } + } + + BitsLeft += bitCount; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Astc/Bits.cs b/src/Ryujinx.Graphics.Texture/Astc/Bits.cs new file mode 100644 index 00000000..bedc14f7 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Astc/Bits.cs @@ -0,0 +1,76 @@ +namespace Ryujinx.Graphics.Texture.Astc +{ + internal static class Bits + { + public static readonly ushort[] Replicate8_16Table; + public static readonly byte[] Replicate1_7Table; + + static Bits() + { + Replicate8_16Table = new ushort[0x200]; + Replicate1_7Table = new byte[0x200]; + + for (int i = 0; i < 0x200; i++) + { + Replicate8_16Table[i] = (ushort)Replicate(i, 8, 16); + Replicate1_7Table[i] = (byte)Replicate(i, 1, 7); + } + } + + public static int Replicate8_16(int value) + { + return Replicate8_16Table[value]; + } + + public static int Replicate1_7(int value) + { + return Replicate1_7Table[value]; + } + + public static int Replicate(int value, int numberBits, int toBit) + { + if (numberBits == 0) + { + return 0; + } + + if (toBit == 0) + { + return 0; + } + + int tempValue = value & ((1 << numberBits) - 1); + int retValue = tempValue; + int resLength = numberBits; + + while (resLength < toBit) + { + int comp = 0; + if (numberBits > toBit - resLength) + { + int newShift = toBit - resLength; + comp = numberBits - newShift; + numberBits = newShift; + } + retValue <<= numberBits; + retValue |= tempValue >> comp; + resLength += numberBits; + } + + return retValue; + } + + // Transfers a bit as described in C.2.14 + public static void BitTransferSigned(ref int a, ref int b) + { + b >>= 1; + b |= a & 0x80; + a >>= 1; + a &= 0x3F; + if ((a & 0x20) != 0) + { + a -= 0x40; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Astc/EndPointSet.cs b/src/Ryujinx.Graphics.Texture/Astc/EndPointSet.cs new file mode 100644 index 00000000..2cfb2201 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Astc/EndPointSet.cs @@ -0,0 +1,23 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture.Astc +{ + [StructLayout(LayoutKind.Sequential, Size = AstcPixel.StructSize * 8)] + internal struct EndPointSet + { + private AstcPixel _start; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span Get(int index) + { + Debug.Assert(index < 4); + + ref AstcPixel start = ref Unsafe.Add(ref _start, index * 2); + + return MemoryMarshal.CreateSpan(ref start, 2); + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs b/src/Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs new file mode 100644 index 00000000..fedd90ee --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs @@ -0,0 +1,345 @@ +using System; +using System.Numerics; + +namespace Ryujinx.Graphics.Texture.Astc +{ + internal struct IntegerEncoded + { + internal const int StructSize = 8; + private static readonly IntegerEncoded[] _encodings; + + public enum EIntegerEncoding : byte + { + JustBits, + Quint, + Trit, + } + + readonly EIntegerEncoding _encoding; + public byte NumberBits { get; private set; } + public byte TritValue { get; private set; } + public byte QuintValue { get; private set; } + public int BitValue { get; private set; } + + static IntegerEncoded() + { + _encodings = new IntegerEncoded[0x100]; + + for (int i = 0; i < _encodings.Length; i++) + { + _encodings[i] = CreateEncodingCalc(i); + } + } + + public IntegerEncoded(EIntegerEncoding encoding, int numBits) + { + _encoding = encoding; + NumberBits = (byte)numBits; + BitValue = 0; + TritValue = 0; + QuintValue = 0; + } + + public readonly bool MatchesEncoding(IntegerEncoded other) + { + return _encoding == other._encoding && NumberBits == other.NumberBits; + } + + public readonly EIntegerEncoding GetEncoding() + { + return _encoding; + } + + public readonly int GetBitLength(int numberVals) + { + int totalBits = NumberBits * numberVals; + if (_encoding == EIntegerEncoding.Trit) + { + totalBits += (numberVals * 8 + 4) / 5; + } + else if (_encoding == EIntegerEncoding.Quint) + { + totalBits += (numberVals * 7 + 2) / 3; + } + return totalBits; + } + + public static IntegerEncoded CreateEncoding(int maxVal) + { + return _encodings[maxVal]; + } + + private static IntegerEncoded CreateEncodingCalc(int maxVal) + { + while (maxVal > 0) + { + int check = maxVal + 1; + + // Is maxVal a power of two? + if ((check & (check - 1)) == 0) + { + return new IntegerEncoded(EIntegerEncoding.JustBits, BitOperations.PopCount((uint)maxVal)); + } + + // Is maxVal of the type 3*2^n - 1? + if ((check % 3 == 0) && ((check / 3) & ((check / 3) - 1)) == 0) + { + return new IntegerEncoded(EIntegerEncoding.Trit, BitOperations.PopCount((uint)(check / 3 - 1))); + } + + // Is maxVal of the type 5*2^n - 1? + if ((check % 5 == 0) && ((check / 5) & ((check / 5) - 1)) == 0) + { + return new IntegerEncoded(EIntegerEncoding.Quint, BitOperations.PopCount((uint)(check / 5 - 1))); + } + + // Apparently it can't be represented with a bounded integer sequence... + // just iterate. + maxVal--; + } + + return new IntegerEncoded(EIntegerEncoding.JustBits, 0); + } + + public static void DecodeTritBlock( + ref BitStream128 bitStream, + ref IntegerSequence listIntegerEncoded, + int numberBitsPerValue) + { + // Implement the algorithm in section C.2.12 + Span m = stackalloc int[5]; + + m[0] = bitStream.ReadBits(numberBitsPerValue); + int encoded = bitStream.ReadBits(2); + m[1] = bitStream.ReadBits(numberBitsPerValue); + encoded |= bitStream.ReadBits(2) << 2; + m[2] = bitStream.ReadBits(numberBitsPerValue); + encoded |= bitStream.ReadBits(1) << 4; + m[3] = bitStream.ReadBits(numberBitsPerValue); + encoded |= bitStream.ReadBits(2) << 5; + m[4] = bitStream.ReadBits(numberBitsPerValue); + encoded |= bitStream.ReadBits(1) << 7; + + ReadOnlySpan encodings = GetTritEncoding(encoded); + + IntegerEncoded intEncoded = new(EIntegerEncoding.Trit, numberBitsPerValue); + + for (int i = 0; i < 5; i++) + { + intEncoded.BitValue = m[i]; + intEncoded.TritValue = encodings[i]; + + listIntegerEncoded.Add(ref intEncoded); + } + } + + public static void DecodeQuintBlock( + ref BitStream128 bitStream, + ref IntegerSequence listIntegerEncoded, + int numberBitsPerValue) + { + ReadOnlySpan interleavedBits = new byte[] { 3, 2, 2 }; + + // Implement the algorithm in section C.2.12 + Span m = stackalloc int[3]; + ulong encoded = 0; + int encodedBitsRead = 0; + + for (int i = 0; i < m.Length; i++) + { + m[i] = bitStream.ReadBits(numberBitsPerValue); + + uint encodedBits = (uint)bitStream.ReadBits(interleavedBits[i]); + + encoded |= encodedBits << encodedBitsRead; + encodedBitsRead += interleavedBits[i]; + } + + ReadOnlySpan encodings = GetQuintEncoding((int)encoded); + + for (int i = 0; i < 3; i++) + { + IntegerEncoded intEncoded = new(EIntegerEncoding.Quint, numberBitsPerValue) + { + BitValue = m[i], + QuintValue = encodings[i], + }; + + listIntegerEncoded.Add(ref intEncoded); + } + } + + public static void DecodeIntegerSequence( + ref IntegerSequence decodeIntegerSequence, + ref BitStream128 bitStream, + int maxRange, + int numberValues) + { + // Determine encoding parameters + IntegerEncoded intEncoded = CreateEncoding(maxRange); + + // Start decoding + int numberValuesDecoded = 0; + while (numberValuesDecoded < numberValues) + { + switch (intEncoded.GetEncoding()) + { + case EIntegerEncoding.Quint: + { + DecodeQuintBlock(ref bitStream, ref decodeIntegerSequence, intEncoded.NumberBits); + numberValuesDecoded += 3; + + break; + } + + case EIntegerEncoding.Trit: + { + DecodeTritBlock(ref bitStream, ref decodeIntegerSequence, intEncoded.NumberBits); + numberValuesDecoded += 5; + + break; + } + + case EIntegerEncoding.JustBits: + { + intEncoded.BitValue = bitStream.ReadBits(intEncoded.NumberBits); + decodeIntegerSequence.Add(ref intEncoded); + numberValuesDecoded++; + + break; + } + } + } + } + + private static ReadOnlySpan GetTritEncoding(int index) + { + return TritEncodings.Slice(index * 5, 5); + } + + private static ReadOnlySpan GetQuintEncoding(int index) + { + return QuintEncodings.Slice(index * 3, 3); + } + + private static ReadOnlySpan TritEncodings => new byte[] + { + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, + 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, + 2, 1, 0, 0, 0, 1, 0, 2, 0, 0, 0, 2, 0, 0, 0, + 1, 2, 0, 0, 0, 2, 2, 0, 0, 0, 2, 0, 2, 0, 0, + 0, 2, 2, 0, 0, 1, 2, 2, 0, 0, 2, 2, 2, 0, 0, + 2, 0, 2, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, + 2, 0, 1, 0, 0, 0, 1, 2, 0, 0, 0, 1, 1, 0, 0, + 1, 1, 1, 0, 0, 2, 1, 1, 0, 0, 1, 1, 2, 0, 0, + 0, 2, 1, 0, 0, 1, 2, 1, 0, 0, 2, 2, 1, 0, 0, + 2, 1, 2, 0, 0, 0, 0, 0, 2, 2, 1, 0, 0, 2, 2, + 2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 0, 0, 0, 1, 0, + 1, 0, 0, 1, 0, 2, 0, 0, 1, 0, 0, 0, 2, 1, 0, + 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 2, 1, 0, 1, 0, + 1, 0, 2, 1, 0, 0, 2, 0, 1, 0, 1, 2, 0, 1, 0, + 2, 2, 0, 1, 0, 2, 0, 2, 1, 0, 0, 2, 2, 1, 0, + 1, 2, 2, 1, 0, 2, 2, 2, 1, 0, 2, 0, 2, 1, 0, + 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 2, 0, 1, 1, 0, + 0, 1, 2, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, + 2, 1, 1, 1, 0, 1, 1, 2, 1, 0, 0, 2, 1, 1, 0, + 1, 2, 1, 1, 0, 2, 2, 1, 1, 0, 2, 1, 2, 1, 0, + 0, 1, 0, 2, 2, 1, 1, 0, 2, 2, 2, 1, 0, 2, 2, + 1, 0, 2, 2, 2, 0, 0, 0, 2, 0, 1, 0, 0, 2, 0, + 2, 0, 0, 2, 0, 0, 0, 2, 2, 0, 0, 1, 0, 2, 0, + 1, 1, 0, 2, 0, 2, 1, 0, 2, 0, 1, 0, 2, 2, 0, + 0, 2, 0, 2, 0, 1, 2, 0, 2, 0, 2, 2, 0, 2, 0, + 2, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 2, 2, 0, + 2, 2, 2, 2, 0, 2, 0, 2, 2, 0, 0, 0, 1, 2, 0, + 1, 0, 1, 2, 0, 2, 0, 1, 2, 0, 0, 1, 2, 2, 0, + 0, 1, 1, 2, 0, 1, 1, 1, 2, 0, 2, 1, 1, 2, 0, + 1, 1, 2, 2, 0, 0, 2, 1, 2, 0, 1, 2, 1, 2, 0, + 2, 2, 1, 2, 0, 2, 1, 2, 2, 0, 0, 2, 0, 2, 2, + 1, 2, 0, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, + 0, 0, 0, 0, 2, 1, 0, 0, 0, 2, 2, 0, 0, 0, 2, + 0, 0, 2, 0, 2, 0, 1, 0, 0, 2, 1, 1, 0, 0, 2, + 2, 1, 0, 0, 2, 1, 0, 2, 0, 2, 0, 2, 0, 0, 2, + 1, 2, 0, 0, 2, 2, 2, 0, 0, 2, 2, 0, 2, 0, 2, + 0, 2, 2, 0, 2, 1, 2, 2, 0, 2, 2, 2, 2, 0, 2, + 2, 0, 2, 0, 2, 0, 0, 1, 0, 2, 1, 0, 1, 0, 2, + 2, 0, 1, 0, 2, 0, 1, 2, 0, 2, 0, 1, 1, 0, 2, + 1, 1, 1, 0, 2, 2, 1, 1, 0, 2, 1, 1, 2, 0, 2, + 0, 2, 1, 0, 2, 1, 2, 1, 0, 2, 2, 2, 1, 0, 2, + 2, 1, 2, 0, 2, 0, 2, 2, 2, 2, 1, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 1, 2, 0, 0, 0, 1, 0, 0, 2, 0, 1, + 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 2, 1, 0, 0, 1, + 1, 0, 2, 0, 1, 0, 2, 0, 0, 1, 1, 2, 0, 0, 1, + 2, 2, 0, 0, 1, 2, 0, 2, 0, 1, 0, 2, 2, 0, 1, + 1, 2, 2, 0, 1, 2, 2, 2, 0, 1, 2, 0, 2, 0, 1, + 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 2, 0, 1, 0, 1, + 0, 1, 2, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, + 2, 1, 1, 0, 1, 1, 1, 2, 0, 1, 0, 2, 1, 0, 1, + 1, 2, 1, 0, 1, 2, 2, 1, 0, 1, 2, 1, 2, 0, 1, + 0, 0, 1, 2, 2, 1, 0, 1, 2, 2, 2, 0, 1, 2, 2, + 0, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, + 2, 0, 0, 1, 1, 0, 0, 2, 1, 1, 0, 1, 0, 1, 1, + 1, 1, 0, 1, 1, 2, 1, 0, 1, 1, 1, 0, 2, 1, 1, + 0, 2, 0, 1, 1, 1, 2, 0, 1, 1, 2, 2, 0, 1, 1, + 2, 0, 2, 1, 1, 0, 2, 2, 1, 1, 1, 2, 2, 1, 1, + 2, 2, 2, 1, 1, 2, 0, 2, 1, 1, 0, 0, 1, 1, 1, + 1, 0, 1, 1, 1, 2, 0, 1, 1, 1, 0, 1, 2, 1, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, + 1, 1, 2, 1, 1, 0, 2, 1, 1, 1, 1, 2, 1, 1, 1, + 2, 2, 1, 1, 1, 2, 1, 2, 1, 1, 0, 1, 1, 2, 2, + 1, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, + 0, 0, 0, 2, 1, 1, 0, 0, 2, 1, 2, 0, 0, 2, 1, + 0, 0, 2, 2, 1, 0, 1, 0, 2, 1, 1, 1, 0, 2, 1, + 2, 1, 0, 2, 1, 1, 0, 2, 2, 1, 0, 2, 0, 2, 1, + 1, 2, 0, 2, 1, 2, 2, 0, 2, 1, 2, 0, 2, 2, 1, + 0, 2, 2, 2, 1, 1, 2, 2, 2, 1, 2, 2, 2, 2, 1, + 2, 0, 2, 2, 1, 0, 0, 1, 2, 1, 1, 0, 1, 2, 1, + 2, 0, 1, 2, 1, 0, 1, 2, 2, 1, 0, 1, 1, 2, 1, + 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1, + 0, 2, 1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 1, 2, 1, + 2, 1, 2, 2, 1, 0, 2, 1, 2, 2, 1, 2, 1, 2, 2, + 2, 2, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 1, 2, + 1, 0, 0, 1, 2, 2, 0, 0, 1, 2, 0, 0, 2, 1, 2, + 0, 1, 0, 1, 2, 1, 1, 0, 1, 2, 2, 1, 0, 1, 2, + 1, 0, 2, 1, 2, 0, 2, 0, 1, 2, 1, 2, 0, 1, 2, + 2, 2, 0, 1, 2, 2, 0, 2, 1, 2, 0, 2, 2, 1, 2, + 1, 2, 2, 1, 2, 2, 2, 2, 1, 2, 2, 0, 2, 1, 2, + 0, 0, 1, 1, 2, 1, 0, 1, 1, 2, 2, 0, 1, 1, 2, + 0, 1, 2, 1, 2, 0, 1, 1, 1, 2, 1, 1, 1, 1, 2, + 2, 1, 1, 1, 2, 1, 1, 2, 1, 2, 0, 2, 1, 1, 2, + 1, 2, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 2, 1, 2, + 0, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 1, 2, 2, 2, + }; + + private static ReadOnlySpan QuintEncodings => new byte[] + { + 0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, + 0, 4, 0, 4, 4, 0, 4, 4, 4, 0, 1, 0, 1, 1, 0, + 2, 1, 0, 3, 1, 0, 4, 1, 0, 1, 4, 0, 4, 4, 1, + 4, 4, 4, 0, 2, 0, 1, 2, 0, 2, 2, 0, 3, 2, 0, + 4, 2, 0, 2, 4, 0, 4, 4, 2, 4, 4, 4, 0, 3, 0, + 1, 3, 0, 2, 3, 0, 3, 3, 0, 4, 3, 0, 3, 4, 0, + 4, 4, 3, 4, 4, 4, 0, 0, 1, 1, 0, 1, 2, 0, 1, + 3, 0, 1, 4, 0, 1, 0, 4, 1, 4, 0, 4, 0, 4, 4, + 0, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 4, 1, 1, + 1, 4, 1, 4, 1, 4, 1, 4, 4, 0, 2, 1, 1, 2, 1, + 2, 2, 1, 3, 2, 1, 4, 2, 1, 2, 4, 1, 4, 2, 4, + 2, 4, 4, 0, 3, 1, 1, 3, 1, 2, 3, 1, 3, 3, 1, + 4, 3, 1, 3, 4, 1, 4, 3, 4, 3, 4, 4, 0, 0, 2, + 1, 0, 2, 2, 0, 2, 3, 0, 2, 4, 0, 2, 0, 4, 2, + 2, 0, 4, 3, 0, 4, 0, 1, 2, 1, 1, 2, 2, 1, 2, + 3, 1, 2, 4, 1, 2, 1, 4, 2, 2, 1, 4, 3, 1, 4, + 0, 2, 2, 1, 2, 2, 2, 2, 2, 3, 2, 2, 4, 2, 2, + 2, 4, 2, 2, 2, 4, 3, 2, 4, 0, 3, 2, 1, 3, 2, + 2, 3, 2, 3, 3, 2, 4, 3, 2, 3, 4, 2, 2, 3, 4, + 3, 3, 4, 0, 0, 3, 1, 0, 3, 2, 0, 3, 3, 0, 3, + 4, 0, 3, 0, 4, 3, 0, 0, 4, 1, 0, 4, 0, 1, 3, + 1, 1, 3, 2, 1, 3, 3, 1, 3, 4, 1, 3, 1, 4, 3, + 0, 1, 4, 1, 1, 4, 0, 2, 3, 1, 2, 3, 2, 2, 3, + 3, 2, 3, 4, 2, 3, 2, 4, 3, 0, 2, 4, 1, 2, 4, + 0, 3, 3, 1, 3, 3, 2, 3, 3, 3, 3, 3, 4, 3, 3, + 3, 4, 3, 0, 3, 4, 1, 3, 4, + }; + } +} diff --git a/src/Ryujinx.Graphics.Texture/Astc/IntegerSequence.cs b/src/Ryujinx.Graphics.Texture/Astc/IntegerSequence.cs new file mode 100644 index 00000000..9cdffd58 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Astc/IntegerSequence.cs @@ -0,0 +1,31 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture.Astc +{ + [StructLayout(LayoutKind.Sequential, Size = IntegerEncoded.StructSize * Capacity + sizeof(int))] + internal struct IntegerSequence + { + private const int Capacity = 100; + + private int _length; + private IntegerEncoded _start; + + public Span List => MemoryMarshal.CreateSpan(ref _start, _length); + + public void Reset() => _length = 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(ref IntegerEncoded item) + { + Debug.Assert(_length < Capacity); + + int oldLength = _length; + _length++; + + List[oldLength] = item; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/BC6Decoder.cs b/src/Ryujinx.Graphics.Texture/BC6Decoder.cs new file mode 100644 index 00000000..ae33e9cf --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/BC6Decoder.cs @@ -0,0 +1,819 @@ +using Ryujinx.Graphics.Texture.Utils; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture +{ + static class BC6Decoder + { + private const int HalfOne = 0x3C00; + + public static void Decode(Span output, ReadOnlySpan data, int width, int height, bool signed) + { + ReadOnlySpan blocks = MemoryMarshal.Cast(data); + + Span output64 = MemoryMarshal.Cast(output); + + int wInBlocks = (width + 3) / 4; + int hInBlocks = (height + 3) / 4; + + for (int y = 0; y < hInBlocks; y++) + { + int y2 = y * 4; + int bh = Math.Min(4, height - y2); + + for (int x = 0; x < wInBlocks; x++) + { + int x2 = x * 4; + int bw = Math.Min(4, width - x2); + + DecodeBlock(blocks[y * wInBlocks + x], output64[(y2 * width + x2)..], bw, bh, width, signed); + } + } + } + + private static void DecodeBlock(Block block, Span output, int w, int h, int width, bool signed) + { + int mode = (int)(block.Low & 3); + if ((mode & 2) != 0) + { + mode = (int)(block.Low & 0x1f); + } + + Span endPoints = stackalloc RgbaColor32[4]; + int subsetCount = DecodeEndPoints(ref block, endPoints, mode, signed); + if (subsetCount == 0) + { + // Mode is invalid, the spec mandates that hardware fills the block with + // a opaque black color. + for (int ty = 0; ty < h; ty++) + { + int baseOffs = ty * width; + + for (int tx = 0; tx < w; tx++) + { + output[baseOffs + tx] = (ulong)HalfOne << 48; + } + } + + return; + } + + int partition; + int indexBitCount; + ulong indices; + + if (subsetCount > 1) + { + partition = (int)((block.High >> 13) & 0x1F); + indexBitCount = 3; + + int fixUpIndex = BC67Tables.FixUpIndices[subsetCount - 1][partition][1] * 3; + ulong lowMask = (ulong.MaxValue >> (65 - fixUpIndex)) << 3; + ulong highMask = ulong.MaxValue << (fixUpIndex + 3); + + indices = ((block.High >> 16) & highMask) | ((block.High >> 17) & lowMask) | ((block.High >> 18) & 3); + } + else + { + partition = 0; + indexBitCount = 4; + indices = (block.High & ~0xFUL) | ((block.High >> 1) & 7); + } + + ulong indexMask = (1UL << indexBitCount) - 1; + + for (int ty = 0; ty < h; ty++) + { + int baseOffs = ty * width; + + for (int tx = 0; tx < w; tx++) + { + int offs = baseOffs + tx; + int index = (int)(indices & indexMask); + int endPointBase = BC67Tables.PartitionTable[subsetCount - 1][partition][ty * 4 + tx] << 1; + + RgbaColor32 color1 = endPoints[endPointBase]; + RgbaColor32 color2 = endPoints[endPointBase + 1]; + + RgbaColor32 color = BC67Utils.Interpolate(color1, color2, index, indexBitCount); + + output[offs] = + (ulong)FinishUnquantize(color.R, signed) | + ((ulong)FinishUnquantize(color.G, signed) << 16) | + ((ulong)FinishUnquantize(color.B, signed) << 32) | + ((ulong)HalfOne << 48); + + indices >>= indexBitCount; + } + } + } + + private static int DecodeEndPoints(ref Block block, Span endPoints, int mode, bool signed) + { + ulong low = block.Low; + ulong high = block.High; + + int r0 = 0, g0 = 0, b0 = 0, r1 = 0, g1 = 0, b1 = 0, r2 = 0, g2 = 0, b2 = 0, r3 = 0, g3 = 0, b3 = 0; + int subsetCount; + + switch (mode) + { + case 0: + r0 = (int)(low >> 5) & 0x3FF; + g0 = (int)(low >> 15) & 0x3FF; + b0 = (int)(low >> 25) & 0x3FF; + + if (signed) + { + r0 = SignExtend(r0, 10); + g0 = SignExtend(g0, 10); + b0 = SignExtend(b0, 10); + } + + r1 = r0 + SignExtend((int)(low >> 35), 5); + g1 = g0 + SignExtend((int)(low >> 45), 5); + b1 = b0 + SignExtend((int)(low >> 55), 5); + + r2 = r0 + SignExtend((int)(high >> 1), 5); + g2 = g0 + SignExtend((int)(((low << 2) & 0x10) | ((low >> 41) & 0xF)), 5); + b2 = b0 + SignExtend((int)(((low << 1) & 0x10) | ((high << 3) & 0x08) | (low >> 61)), 5); + + r3 = r0 + SignExtend((int)(high >> 7), 5); + g3 = g0 + SignExtend((int)(((low >> 36) & 0x10) | ((low >> 51) & 0xF)), 5); + b3 = b0 + SignExtend((int)( + ((low) & 0x10) | + ((high >> 9) & 0x08) | + ((high >> 4) & 0x04) | + ((low >> 59) & 0x02) | + ((low >> 50) & 0x01)), 5); + + r0 = Unquantize(r0, 10, signed); + g0 = Unquantize(g0, 10, signed); + b0 = Unquantize(b0, 10, signed); + + r1 = Unquantize(r1 & 0x3FF, 10, signed); + g1 = Unquantize(g1 & 0x3FF, 10, signed); + b1 = Unquantize(b1 & 0x3FF, 10, signed); + + r2 = Unquantize(r2 & 0x3FF, 10, signed); + g2 = Unquantize(g2 & 0x3FF, 10, signed); + b2 = Unquantize(b2 & 0x3FF, 10, signed); + + r3 = Unquantize(r3 & 0x3FF, 10, signed); + g3 = Unquantize(g3 & 0x3FF, 10, signed); + b3 = Unquantize(b3 & 0x3FF, 10, signed); + + subsetCount = 2; + break; + case 1: + r0 = (int)(low >> 5) & 0x7F; + g0 = (int)(low >> 15) & 0x7F; + b0 = (int)(low >> 25) & 0x7F; + + if (signed) + { + r0 = SignExtend(r0, 7); + g0 = SignExtend(g0, 7); + b0 = SignExtend(b0, 7); + } + + r1 = r0 + SignExtend((int)(low >> 35), 6); + g1 = g0 + SignExtend((int)(low >> 45), 6); + b1 = b0 + SignExtend((int)(low >> 55), 6); + + r2 = r0 + SignExtend((int)(high >> 1), 6); + g2 = g0 + SignExtend((int)(((low << 3) & 0x20) | ((low >> 20) & 0x10) | ((low >> 41) & 0x0F)), 6); + b2 = b0 + SignExtend((int)( + ((low >> 17) & 0x20) | + ((low >> 10) & 0x10) | + ((high << 3) & 0x08) | + (low >> 61)), 6); + + r3 = r0 + SignExtend((int)(high >> 7), 6); + g3 = g0 + SignExtend((int)(((low << 1) & 0x30) | ((low >> 51) & 0xF)), 6); + b3 = b0 + SignExtend((int)( + ((low >> 28) & 0x20) | + ((low >> 30) & 0x10) | + ((low >> 29) & 0x08) | + ((low >> 21) & 0x04) | + ((low >> 12) & 0x03)), 6); + + r0 = Unquantize(r0, 7, signed); + g0 = Unquantize(g0, 7, signed); + b0 = Unquantize(b0, 7, signed); + + r1 = Unquantize(r1 & 0x7F, 7, signed); + g1 = Unquantize(g1 & 0x7F, 7, signed); + b1 = Unquantize(b1 & 0x7F, 7, signed); + + r2 = Unquantize(r2 & 0x7F, 7, signed); + g2 = Unquantize(g2 & 0x7F, 7, signed); + b2 = Unquantize(b2 & 0x7F, 7, signed); + + r3 = Unquantize(r3 & 0x7F, 7, signed); + g3 = Unquantize(g3 & 0x7F, 7, signed); + b3 = Unquantize(b3 & 0x7F, 7, signed); + + subsetCount = 2; + break; + case 2: + r0 = (int)(((low >> 30) & 0x400) | ((low >> 5) & 0x3FF)); + g0 = (int)(((low >> 39) & 0x400) | ((low >> 15) & 0x3FF)); + b0 = (int)(((low >> 49) & 0x400) | ((low >> 25) & 0x3FF)); + + if (signed) + { + r0 = SignExtend(r0, 11); + g0 = SignExtend(g0, 11); + b0 = SignExtend(b0, 11); + } + + r1 = r0 + SignExtend((int)(low >> 35), 5); + g1 = g0 + SignExtend((int)(low >> 45), 4); + b1 = b0 + SignExtend((int)(low >> 55), 4); + + r2 = r0 + SignExtend((int)(high >> 1), 5); + g2 = g0 + SignExtend((int)(low >> 41), 4); + b2 = b0 + SignExtend((int)(((high << 3) & 8) | (low >> 61)), 4); + + r3 = r0 + SignExtend((int)(high >> 7), 5); + g3 = g0 + SignExtend((int)(low >> 51), 4); + b3 = b0 + SignExtend((int)( + ((high >> 9) & 8) | + ((high >> 4) & 4) | + ((low >> 59) & 2) | + ((low >> 50) & 1)), 4); + + r0 = Unquantize(r0, 11, signed); + g0 = Unquantize(g0, 11, signed); + b0 = Unquantize(b0, 11, signed); + + r1 = Unquantize(r1 & 0x7FF, 11, signed); + g1 = Unquantize(g1 & 0x7FF, 11, signed); + b1 = Unquantize(b1 & 0x7FF, 11, signed); + + r2 = Unquantize(r2 & 0x7FF, 11, signed); + g2 = Unquantize(g2 & 0x7FF, 11, signed); + b2 = Unquantize(b2 & 0x7FF, 11, signed); + + r3 = Unquantize(r3 & 0x7FF, 11, signed); + g3 = Unquantize(g3 & 0x7FF, 11, signed); + b3 = Unquantize(b3 & 0x7FF, 11, signed); + + subsetCount = 2; + break; + case 3: + r0 = (int)(low >> 5) & 0x3FF; + g0 = (int)(low >> 15) & 0x3FF; + b0 = (int)(low >> 25) & 0x3FF; + + r1 = (int)(low >> 35) & 0x3FF; + g1 = (int)(low >> 45) & 0x3FF; + b1 = (int)(((high << 9) & 0x200) | (low >> 55)); + + if (signed) + { + r0 = SignExtend(r0, 10); + g0 = SignExtend(g0, 10); + b0 = SignExtend(b0, 10); + + r1 = SignExtend(r1, 10); + g1 = SignExtend(g1, 10); + b1 = SignExtend(b1, 10); + } + + r0 = Unquantize(r0, 10, signed); + g0 = Unquantize(g0, 10, signed); + b0 = Unquantize(b0, 10, signed); + + r1 = Unquantize(r1, 10, signed); + g1 = Unquantize(g1, 10, signed); + b1 = Unquantize(b1, 10, signed); + + subsetCount = 1; + break; + case 6: + r0 = (int)(((low >> 29) & 0x400) | ((low >> 5) & 0x3FF)); + g0 = (int)(((low >> 40) & 0x400) | ((low >> 15) & 0x3FF)); + b0 = (int)(((low >> 49) & 0x400) | ((low >> 25) & 0x3FF)); + + if (signed) + { + r0 = SignExtend(r0, 11); + g0 = SignExtend(g0, 11); + b0 = SignExtend(b0, 11); + } + + r1 = r0 + SignExtend((int)(low >> 35), 4); + g1 = g0 + SignExtend((int)(low >> 45), 5); + b1 = b0 + SignExtend((int)(low >> 55), 4); + + r2 = r0 + SignExtend((int)(high >> 1), 4); + g2 = g0 + SignExtend((int)(((high >> 7) & 0x10) | ((low >> 41) & 0x0F)), 5); + b2 = b0 + SignExtend((int)(((high << 3) & 0x08) | ((low >> 61))), 4); + + r3 = r0 + SignExtend((int)(high >> 7), 4); + g3 = g0 + SignExtend((int)(((low >> 36) & 0x10) | ((low >> 51) & 0x0F)), 5); + b3 = b0 + SignExtend((int)( + ((high >> 9) & 8) | + ((high >> 4) & 4) | + ((low >> 59) & 2) | + ((high >> 5) & 1)), 4); + + r0 = Unquantize(r0, 11, signed); + g0 = Unquantize(g0, 11, signed); + b0 = Unquantize(b0, 11, signed); + + r1 = Unquantize(r1 & 0x7FF, 11, signed); + g1 = Unquantize(g1 & 0x7FF, 11, signed); + b1 = Unquantize(b1 & 0x7FF, 11, signed); + + r2 = Unquantize(r2 & 0x7FF, 11, signed); + g2 = Unquantize(g2 & 0x7FF, 11, signed); + b2 = Unquantize(b2 & 0x7FF, 11, signed); + + r3 = Unquantize(r3 & 0x7FF, 11, signed); + g3 = Unquantize(g3 & 0x7FF, 11, signed); + b3 = Unquantize(b3 & 0x7FF, 11, signed); + + subsetCount = 2; + break; + case 7: + r0 = (int)(((low >> 34) & 0x400) | ((low >> 5) & 0x3FF)); + g0 = (int)(((low >> 44) & 0x400) | ((low >> 15) & 0x3FF)); + b0 = (int)(((high << 10) & 0x400) | ((low >> 25) & 0x3FF)); + + if (signed) + { + r0 = SignExtend(r0, 11); + g0 = SignExtend(g0, 11); + b0 = SignExtend(b0, 11); + } + + r1 = (r0 + SignExtend((int)(low >> 35), 9)) & 0x7FF; + g1 = (g0 + SignExtend((int)(low >> 45), 9)) & 0x7FF; + b1 = (b0 + SignExtend((int)(low >> 55), 9)) & 0x7FF; + + r0 = Unquantize(r0, 11, signed); + g0 = Unquantize(g0, 11, signed); + b0 = Unquantize(b0, 11, signed); + + r1 = Unquantize(r1, 11, signed); + g1 = Unquantize(g1, 11, signed); + b1 = Unquantize(b1, 11, signed); + + subsetCount = 1; + break; + case 10: + r0 = (int)(((low >> 29) & 0x400) | ((low >> 5) & 0x3FF)); + g0 = (int)(((low >> 39) & 0x400) | ((low >> 15) & 0x3FF)); + b0 = (int)(((low >> 50) & 0x400) | ((low >> 25) & 0x3FF)); + + if (signed) + { + r0 = SignExtend(r0, 11); + g0 = SignExtend(g0, 11); + b0 = SignExtend(b0, 11); + } + + r1 = r0 + SignExtend((int)(low >> 35), 4); + g1 = g0 + SignExtend((int)(low >> 45), 4); + b1 = b0 + SignExtend((int)(low >> 55), 5); + + r2 = r0 + SignExtend((int)(high >> 1), 4); + g2 = g0 + SignExtend((int)(low >> 41), 4); + b2 = b0 + SignExtend((int)(((low >> 36) & 0x10) | ((high << 3) & 8) | (low >> 61)), 5); + + r3 = r0 + SignExtend((int)(high >> 7), 4); + g3 = g0 + SignExtend((int)(low >> 51), 4); + b3 = b0 + SignExtend((int)( + ((high >> 7) & 0x10) | + ((high >> 9) & 0x08) | + ((high >> 4) & 0x06) | + ((low >> 50) & 0x01)), 5); + + r0 = Unquantize(r0, 11, signed); + g0 = Unquantize(g0, 11, signed); + b0 = Unquantize(b0, 11, signed); + + r1 = Unquantize(r1 & 0x7FF, 11, signed); + g1 = Unquantize(g1 & 0x7FF, 11, signed); + b1 = Unquantize(b1 & 0x7FF, 11, signed); + + r2 = Unquantize(r2 & 0x7FF, 11, signed); + g2 = Unquantize(g2 & 0x7FF, 11, signed); + b2 = Unquantize(b2 & 0x7FF, 11, signed); + + r3 = Unquantize(r3 & 0x7FF, 11, signed); + g3 = Unquantize(g3 & 0x7FF, 11, signed); + b3 = Unquantize(b3 & 0x7FF, 11, signed); + + subsetCount = 2; + break; + case 11: + r0 = (int)(((low >> 32) & 0x800) | ((low >> 34) & 0x400) | ((low >> 5) & 0x3FF)); + g0 = (int)(((low >> 42) & 0x800) | ((low >> 44) & 0x400) | ((low >> 15) & 0x3FF)); + b0 = (int)(((low >> 52) & 0x800) | ((high << 10) & 0x400) | ((low >> 25) & 0x3FF)); + + if (signed) + { + r0 = SignExtend(r0, 12); + g0 = SignExtend(g0, 12); + b0 = SignExtend(b0, 12); + } + + r1 = (r0 + SignExtend((int)(low >> 35), 8)) & 0xFFF; + g1 = (g0 + SignExtend((int)(low >> 45), 8)) & 0xFFF; + b1 = (b0 + SignExtend((int)(low >> 55), 8)) & 0xFFF; + + r0 = Unquantize(r0, 12, signed); + g0 = Unquantize(g0, 12, signed); + b0 = Unquantize(b0, 12, signed); + + r1 = Unquantize(r1, 12, signed); + g1 = Unquantize(g1, 12, signed); + b1 = Unquantize(b1, 12, signed); + + subsetCount = 1; + break; + case 14: + r0 = (int)(low >> 5) & 0x1FF; + g0 = (int)(low >> 15) & 0x1FF; + b0 = (int)(low >> 25) & 0x1FF; + + if (signed) + { + r0 = SignExtend(r0, 9); + g0 = SignExtend(g0, 9); + b0 = SignExtend(b0, 9); + } + + r1 = r0 + SignExtend((int)(low >> 35), 5); + g1 = g0 + SignExtend((int)(low >> 45), 5); + b1 = b0 + SignExtend((int)(low >> 55), 5); + + r2 = r0 + SignExtend((int)(high >> 1), 5); + g2 = g0 + SignExtend((int)(((low >> 20) & 0x10) | ((low >> 41) & 0xF)), 5); + b2 = b0 + SignExtend((int)(((low >> 10) & 0x10) | ((high << 3) & 8) | (low >> 61)), 5); + + r3 = r0 + SignExtend((int)(high >> 7), 5); + g3 = g0 + SignExtend((int)(((low >> 36) & 0x10) | ((low >> 51) & 0xF)), 5); + b3 = b0 + SignExtend((int)( + ((low >> 30) & 0x10) | + ((high >> 9) & 0x08) | + ((high >> 4) & 0x04) | + ((low >> 59) & 0x02) | + ((low >> 50) & 0x01)), 5); + + r0 = Unquantize(r0, 9, signed); + g0 = Unquantize(g0, 9, signed); + b0 = Unquantize(b0, 9, signed); + + r1 = Unquantize(r1 & 0x1FF, 9, signed); + g1 = Unquantize(g1 & 0x1FF, 9, signed); + b1 = Unquantize(b1 & 0x1FF, 9, signed); + + r2 = Unquantize(r2 & 0x1FF, 9, signed); + g2 = Unquantize(g2 & 0x1FF, 9, signed); + b2 = Unquantize(b2 & 0x1FF, 9, signed); + + r3 = Unquantize(r3 & 0x1FF, 9, signed); + g3 = Unquantize(g3 & 0x1FF, 9, signed); + b3 = Unquantize(b3 & 0x1FF, 9, signed); + + subsetCount = 2; + break; + case 15: + r0 = (BitReverse6((int)(low >> 39) & 0x3F) << 10) | ((int)(low >> 5) & 0x3FF); + g0 = (BitReverse6((int)(low >> 49) & 0x3F) << 10) | ((int)(low >> 15) & 0x3FF); + b0 = ((BitReverse6((int)(low >> 59)) | (int)(high & 1)) << 10) | ((int)(low >> 25) & 0x3FF); + + if (signed) + { + r0 = SignExtend(r0, 16); + g0 = SignExtend(g0, 16); + b0 = SignExtend(b0, 16); + } + + r1 = (r0 + SignExtend((int)(low >> 35), 4)) & 0xFFFF; + g1 = (g0 + SignExtend((int)(low >> 45), 4)) & 0xFFFF; + b1 = (b0 + SignExtend((int)(low >> 55), 4)) & 0xFFFF; + + subsetCount = 1; + break; + case 18: + r0 = (int)(low >> 5) & 0xFF; + g0 = (int)(low >> 15) & 0xFF; + b0 = (int)(low >> 25) & 0xFF; + + if (signed) + { + r0 = SignExtend(r0, 8); + g0 = SignExtend(g0, 8); + b0 = SignExtend(b0, 8); + } + + r1 = r0 + SignExtend((int)(low >> 35), 6); + g1 = g0 + SignExtend((int)(low >> 45), 5); + b1 = b0 + SignExtend((int)(low >> 55), 5); + + r2 = r0 + SignExtend((int)(high >> 1), 6); + g2 = g0 + SignExtend((int)(((low >> 20) & 0x10) | ((low >> 41) & 0xF)), 5); + b2 = b0 + SignExtend((int)(((low >> 10) & 0x10) | ((high << 3) & 8) | (low >> 61)), 5); + + r3 = r0 + SignExtend((int)(high >> 7), 6); + g3 = g0 + SignExtend((int)(((low >> 9) & 0x10) | ((low >> 51) & 0xF)), 5); + b3 = b0 + SignExtend((int)( + ((low >> 30) & 0x18) | + ((low >> 21) & 0x04) | + ((low >> 59) & 0x02) | + ((low >> 50) & 0x01)), 5); + + r0 = Unquantize(r0, 8, signed); + g0 = Unquantize(g0, 8, signed); + b0 = Unquantize(b0, 8, signed); + + r1 = Unquantize(r1 & 0xFF, 8, signed); + g1 = Unquantize(g1 & 0xFF, 8, signed); + b1 = Unquantize(b1 & 0xFF, 8, signed); + + r2 = Unquantize(r2 & 0xFF, 8, signed); + g2 = Unquantize(g2 & 0xFF, 8, signed); + b2 = Unquantize(b2 & 0xFF, 8, signed); + + r3 = Unquantize(r3 & 0xFF, 8, signed); + g3 = Unquantize(g3 & 0xFF, 8, signed); + b3 = Unquantize(b3 & 0xFF, 8, signed); + + subsetCount = 2; + break; + case 22: + r0 = (int)(low >> 5) & 0xFF; + g0 = (int)(low >> 15) & 0xFF; + b0 = (int)(low >> 25) & 0xFF; + + if (signed) + { + r0 = SignExtend(r0, 8); + g0 = SignExtend(g0, 8); + b0 = SignExtend(b0, 8); + } + + r1 = r0 + SignExtend((int)(low >> 35), 5); + g1 = g0 + SignExtend((int)(low >> 45), 6); + b1 = b0 + SignExtend((int)(low >> 55), 5); + + r2 = r0 + SignExtend((int)(high >> 1), 5); + g2 = g0 + SignExtend((int)(((low >> 18) & 0x20) | ((low >> 20) & 0x10) | ((low >> 41) & 0xF)), 6); + b2 = b0 + SignExtend((int)(((low >> 10) & 0x10) | ((high << 3) & 0x08) | (low >> 61)), 5); + + r3 = r0 + SignExtend((int)(high >> 7), 5); + g3 = g0 + SignExtend((int)(((low >> 28) & 0x20) | ((low >> 36) & 0x10) | ((low >> 51) & 0x0F)), 6); + b3 = b0 + SignExtend((int)( + ((low >> 30) & 0x10) | + ((high >> 9) & 0x08) | + ((high >> 4) & 0x04) | + ((low >> 59) & 0x02) | + ((low >> 13) & 0x01)), 5); + + r0 = Unquantize(r0, 8, signed); + g0 = Unquantize(g0, 8, signed); + b0 = Unquantize(b0, 8, signed); + + r1 = Unquantize(r1 & 0xFF, 8, signed); + g1 = Unquantize(g1 & 0xFF, 8, signed); + b1 = Unquantize(b1 & 0xFF, 8, signed); + + r2 = Unquantize(r2 & 0xFF, 8, signed); + g2 = Unquantize(g2 & 0xFF, 8, signed); + b2 = Unquantize(b2 & 0xFF, 8, signed); + + r3 = Unquantize(r3 & 0xFF, 8, signed); + g3 = Unquantize(g3 & 0xFF, 8, signed); + b3 = Unquantize(b3 & 0xFF, 8, signed); + + subsetCount = 2; + break; + case 26: + r0 = (int)(low >> 5) & 0xFF; + g0 = (int)(low >> 15) & 0xFF; + b0 = (int)(low >> 25) & 0xFF; + + if (signed) + { + r0 = SignExtend(r0, 8); + g0 = SignExtend(g0, 8); + b0 = SignExtend(b0, 8); + } + + r1 = r0 + SignExtend((int)(low >> 35), 5); + g1 = g0 + SignExtend((int)(low >> 45), 5); + b1 = b0 + SignExtend((int)(low >> 55), 6); + + r2 = r0 + SignExtend((int)(high >> 1), 5); + g2 = g0 + SignExtend((int)(((low >> 20) & 0x10) | ((low >> 41) & 0xF)), 5); + b2 = b0 + SignExtend((int)( + ((low >> 18) & 0x20) | + ((low >> 10) & 0x10) | + ((high << 3) & 0x08) | + (low >> 61)), 6); + + r3 = r0 + SignExtend((int)(high >> 7), 5); + g3 = g0 + SignExtend((int)(((low >> 36) & 0x10) | ((low >> 51) & 0xF)), 5); + b3 = b0 + SignExtend((int)( + ((low >> 28) & 0x20) | + ((low >> 30) & 0x10) | + ((high >> 9) & 0x08) | + ((high >> 4) & 0x04) | + ((low >> 12) & 0x02) | + ((low >> 50) & 0x01)), 6); + + r0 = Unquantize(r0, 8, signed); + g0 = Unquantize(g0, 8, signed); + b0 = Unquantize(b0, 8, signed); + + r1 = Unquantize(r1 & 0xFF, 8, signed); + g1 = Unquantize(g1 & 0xFF, 8, signed); + b1 = Unquantize(b1 & 0xFF, 8, signed); + + r2 = Unquantize(r2 & 0xFF, 8, signed); + g2 = Unquantize(g2 & 0xFF, 8, signed); + b2 = Unquantize(b2 & 0xFF, 8, signed); + + r3 = Unquantize(r3 & 0xFF, 8, signed); + g3 = Unquantize(g3 & 0xFF, 8, signed); + b3 = Unquantize(b3 & 0xFF, 8, signed); + + subsetCount = 2; + break; + case 30: + r0 = (int)(low >> 5) & 0x3F; + g0 = (int)(low >> 15) & 0x3F; + b0 = (int)(low >> 25) & 0x3F; + + r1 = (int)(low >> 35) & 0x3F; + g1 = (int)(low >> 45) & 0x3F; + b1 = (int)(low >> 55) & 0x3F; + + r2 = (int)(high >> 1) & 0x3F; + g2 = (int)(((low >> 16) & 0x20) | ((low >> 20) & 0x10) | ((low >> 41) & 0xF)); + b2 = (int)(((low >> 17) & 0x20) | ((low >> 10) & 0x10) | ((high << 3) & 0x08) | (low >> 61)); + + r3 = (int)(high >> 7) & 0x3F; + g3 = (int)(((low >> 26) & 0x20) | ((low >> 7) & 0x10) | ((low >> 51) & 0xF)); + b3 = (int)( + ((low >> 28) & 0x20) | + ((low >> 30) & 0x10) | + ((low >> 29) & 0x08) | + ((low >> 21) & 0x04) | + ((low >> 12) & 0x03)); + + if (signed) + { + r0 = SignExtend(r0, 6); + g0 = SignExtend(g0, 6); + b0 = SignExtend(b0, 6); + + r1 = SignExtend(r1, 6); + g1 = SignExtend(g1, 6); + b1 = SignExtend(b1, 6); + + r2 = SignExtend(r2, 6); + g2 = SignExtend(g2, 6); + b2 = SignExtend(b2, 6); + + r3 = SignExtend(r3, 6); + g3 = SignExtend(g3, 6); + b3 = SignExtend(b3, 6); + } + + r0 = Unquantize(r0, 6, signed); + g0 = Unquantize(g0, 6, signed); + b0 = Unquantize(b0, 6, signed); + + r1 = Unquantize(r1, 6, signed); + g1 = Unquantize(g1, 6, signed); + b1 = Unquantize(b1, 6, signed); + + r2 = Unquantize(r2, 6, signed); + g2 = Unquantize(g2, 6, signed); + b2 = Unquantize(b2, 6, signed); + + r3 = Unquantize(r3, 6, signed); + g3 = Unquantize(g3, 6, signed); + b3 = Unquantize(b3, 6, signed); + + subsetCount = 2; + break; + default: + subsetCount = 0; + break; + } + + if (subsetCount > 0) + { + endPoints[0] = new RgbaColor32(r0, g0, b0, HalfOne); + endPoints[1] = new RgbaColor32(r1, g1, b1, HalfOne); + + if (subsetCount > 1) + { + endPoints[2] = new RgbaColor32(r2, g2, b2, HalfOne); + endPoints[3] = new RgbaColor32(r3, g3, b3, HalfOne); + } + } + + return subsetCount; + } + + private static int SignExtend(int value, int bits) + { + int shift = 32 - bits; + return (value << shift) >> shift; + } + + private static int Unquantize(int value, int bits, bool signed) + { + if (signed) + { + if (bits >= 16) + { + return value; + } + else + { + bool sign = value < 0; + + if (sign) + { + value = -value; + } + + if (value == 0) + { + return value; + } + else if (value >= ((1 << (bits - 1)) - 1)) + { + value = 0x7FFF; + } + else + { + value = ((value << 15) + 0x4000) >> (bits - 1); + } + + if (sign) + { + value = -value; + } + } + } + else + { + if (bits >= 15 || value == 0) + { + return value; + } + else if (value == ((1 << bits) - 1)) + { + return 0xFFFF; + } + else + { + return ((value << 16) + 0x8000) >> bits; + } + } + + return value; + } + + private static ushort FinishUnquantize(int value, bool signed) + { + if (signed) + { + value = value < 0 ? -((-value * 31) >> 5) : (value * 31) >> 5; + + int sign = 0; + if (value < 0) + { + sign = 0x8000; + value = -value; + } + + return (ushort)(sign | value); + } + else + { + return (ushort)((value * 31) >> 6); + } + } + + private static int BitReverse6(int value) + { + value = ((value >> 1) & 0x55) | ((value << 1) & 0xaa); + value = ((value >> 2) & 0x33) | ((value << 2) & 0xcc); + value = ((value >> 4) & 0x0f) | ((value << 4) & 0xf0); + return value >> 2; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/BC7Decoder.cs b/src/Ryujinx.Graphics.Texture/BC7Decoder.cs new file mode 100644 index 00000000..d8c6c1ed --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/BC7Decoder.cs @@ -0,0 +1,229 @@ +using Ryujinx.Graphics.Texture.Utils; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture +{ + static class BC7Decoder + { + public static void Decode(Span output, ReadOnlySpan data, int width, int height) + { + ReadOnlySpan blocks = MemoryMarshal.Cast(data); + + Span output32 = MemoryMarshal.Cast(output); + + int wInBlocks = (width + 3) / 4; + int hInBlocks = (height + 3) / 4; + + for (int y = 0; y < hInBlocks; y++) + { + int y2 = y * 4; + int bh = Math.Min(4, height - y2); + + for (int x = 0; x < wInBlocks; x++) + { + int x2 = x * 4; + int bw = Math.Min(4, width - x2); + + DecodeBlock(blocks[y * wInBlocks + x], output32[(y2 * width + x2)..], bw, bh, width); + } + } + } + + private static void DecodeBlock(Block block, Span output, int w, int h, int width) + { + int mode = BitOperations.TrailingZeroCount((byte)block.Low | 0x100); + if (mode == 8) + { + // Mode is invalid, the spec mandates that hardware fills the block with + // a transparent black color. + for (int ty = 0; ty < h; ty++) + { + int baseOffs = ty * width; + + for (int tx = 0; tx < w; tx++) + { + int offs = baseOffs + tx; + + output[offs] = 0; + } + } + + return; + } + + BC7ModeInfo modeInfo = BC67Tables.BC7ModeInfos[mode]; + + int offset = mode + 1; + int partition = (int)block.Decode(ref offset, modeInfo.PartitionBitCount); + int rotation = (int)block.Decode(ref offset, modeInfo.RotationBitCount); + int indexMode = (int)block.Decode(ref offset, modeInfo.IndexModeBitCount); + + Debug.Assert(partition < 64); + Debug.Assert(rotation < 4); + Debug.Assert(indexMode < 2); + + int endPointCount = modeInfo.SubsetCount * 2; + + Span endPoints = stackalloc RgbaColor32[endPointCount]; + Span pValues = stackalloc byte[modeInfo.PBits]; + + endPoints.Fill(new RgbaColor32(0, 0, 0, 255)); + + for (int i = 0; i < endPointCount; i++) + { + endPoints[i].R = (int)block.Decode(ref offset, modeInfo.ColorDepth); + } + + for (int i = 0; i < endPointCount; i++) + { + endPoints[i].G = (int)block.Decode(ref offset, modeInfo.ColorDepth); + } + + for (int i = 0; i < endPointCount; i++) + { + endPoints[i].B = (int)block.Decode(ref offset, modeInfo.ColorDepth); + } + + if (modeInfo.AlphaDepth != 0) + { + for (int i = 0; i < endPointCount; i++) + { + endPoints[i].A = (int)block.Decode(ref offset, modeInfo.AlphaDepth); + } + } + + for (int i = 0; i < modeInfo.PBits; i++) + { + pValues[i] = (byte)block.Decode(ref offset, 1); + } + + for (int i = 0; i < endPointCount; i++) + { + int pBit = -1; + + if (modeInfo.PBits != 0) + { + int pIndex = (i * modeInfo.PBits) / endPointCount; + pBit = pValues[pIndex]; + } + + Unquantize(ref endPoints[i], modeInfo.ColorDepth, modeInfo.AlphaDepth, pBit); + } + + byte[] partitionTable = BC67Tables.PartitionTable[modeInfo.SubsetCount - 1][partition]; + byte[] fixUpTable = BC67Tables.FixUpIndices[modeInfo.SubsetCount - 1][partition]; + + Span colorIndices = stackalloc byte[16]; + + for (int i = 0; i < 16; i++) + { + byte subset = partitionTable[i]; + int bitCount = i == fixUpTable[subset] ? modeInfo.ColorIndexBitCount - 1 : modeInfo.ColorIndexBitCount; + + colorIndices[i] = (byte)block.Decode(ref offset, bitCount); + Debug.Assert(colorIndices[i] < 16); + } + + Span alphaIndices = stackalloc byte[16]; + + if (modeInfo.AlphaIndexBitCount != 0) + { + for (int i = 0; i < 16; i++) + { + int bitCount = i != 0 ? modeInfo.AlphaIndexBitCount : modeInfo.AlphaIndexBitCount - 1; + + alphaIndices[i] = (byte)block.Decode(ref offset, bitCount); + Debug.Assert(alphaIndices[i] < 16); + } + } + + for (int ty = 0; ty < h; ty++) + { + int baseOffs = ty * width; + + for (int tx = 0; tx < w; tx++) + { + int i = ty * 4 + tx; + + RgbaColor32 color; + + byte subset = partitionTable[i]; + + RgbaColor32 color1 = endPoints[subset * 2]; + RgbaColor32 color2 = endPoints[subset * 2 + 1]; + + if (modeInfo.AlphaIndexBitCount != 0) + { + if (indexMode == 0) + { + color = BC67Utils.Interpolate(color1, color2, colorIndices[i], alphaIndices[i], modeInfo.ColorIndexBitCount, modeInfo.AlphaIndexBitCount); + } + else + { + color = BC67Utils.Interpolate(color1, color2, alphaIndices[i], colorIndices[i], modeInfo.AlphaIndexBitCount, modeInfo.ColorIndexBitCount); + } + } + else + { + color = BC67Utils.Interpolate(color1, color2, colorIndices[i], colorIndices[i], modeInfo.ColorIndexBitCount, modeInfo.ColorIndexBitCount); + } + + if (rotation != 0) + { + int a = color.A; + + switch (rotation) + { + case 1: + color.A = color.R; + color.R = a; + break; + case 2: + color.A = color.G; + color.G = a; + break; + case 3: + color.A = color.B; + color.B = a; + break; + } + } + + RgbaColor8 color8 = color.GetColor8(); + + output[baseOffs + tx] = color8.ToUInt32(); + } + } + } + + private static void Unquantize(ref RgbaColor32 color, int colorDepth, int alphaDepth, int pBit) + { + color.R = UnquantizeComponent(color.R, colorDepth, pBit); + color.G = UnquantizeComponent(color.G, colorDepth, pBit); + color.B = UnquantizeComponent(color.B, colorDepth, pBit); + color.A = alphaDepth != 0 ? UnquantizeComponent(color.A, alphaDepth, pBit) : 255; + } + + private static int UnquantizeComponent(int component, int bits, int pBit) + { + int shift = 8 - bits; + int value = component << shift; + + if (pBit >= 0) + { + Debug.Assert(pBit <= 1); + value |= value >> (bits + 1); + value |= pBit << (shift - 1); + } + else + { + value |= value >> bits; + } + + return value; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/BCnDecoder.cs b/src/Ryujinx.Graphics.Texture/BCnDecoder.cs new file mode 100644 index 00000000..d7b1f0fa --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/BCnDecoder.cs @@ -0,0 +1,897 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using System; +using System.Buffers.Binary; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Graphics.Texture +{ + public static class BCnDecoder + { + private const int BlockWidth = 4; + private const int BlockHeight = 4; + + public static MemoryOwner DecodeBC1(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + { + int size = 0; + + for (int l = 0; l < levels; l++) + { + size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; + } + + MemoryOwner output = MemoryOwner.Rent(size); + + Span tile = stackalloc byte[BlockWidth * BlockHeight * 4]; + + Span tileAsUint = MemoryMarshal.Cast(tile); + Span outputAsUint = MemoryMarshal.Cast(output.Span); + + Span> tileAsVector128 = MemoryMarshal.Cast>(tile); + + Span> outputLine0 = default; + Span> outputLine1 = default; + Span> outputLine2 = default; + Span> outputLine3 = default; + + int imageBaseOOffs = 0; + + for (int l = 0; l < levels; l++) + { + int w = BitUtils.DivRoundUp(width, BlockWidth); + int h = BitUtils.DivRoundUp(height, BlockHeight); + + for (int l2 = 0; l2 < layers; l2++) + { + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < h; y++) + { + int baseY = y * BlockHeight; + int copyHeight = Math.Min(BlockHeight, height - baseY); + int lineBaseOOffs = imageBaseOOffs + baseY * width; + + if (copyHeight == 4) + { + outputLine0 = MemoryMarshal.Cast>(outputAsUint[lineBaseOOffs..]); + outputLine1 = MemoryMarshal.Cast>(outputAsUint[(lineBaseOOffs + width)..]); + outputLine2 = MemoryMarshal.Cast>(outputAsUint[(lineBaseOOffs + width * 2)..]); + outputLine3 = MemoryMarshal.Cast>(outputAsUint[(lineBaseOOffs + width * 3)..]); + } + + for (int x = 0; x < w; x++) + { + int baseX = x * BlockWidth; + int copyWidth = Math.Min(BlockWidth, width - baseX); + + BC1DecodeTileRgb(tile, data); + + if ((copyWidth | copyHeight) == 4) + { + outputLine0[x] = tileAsVector128[0]; + outputLine1[x] = tileAsVector128[1]; + outputLine2[x] = tileAsVector128[2]; + outputLine3[x] = tileAsVector128[3]; + } + else + { + int pixelBaseOOffs = lineBaseOOffs + baseX; + + for (int tY = 0; tY < copyHeight; tY++) + { + tileAsUint.Slice(tY * 4, copyWidth).CopyTo(outputAsUint.Slice(pixelBaseOOffs + width * tY, copyWidth)); + } + } + + data = data[8..]; + } + } + + imageBaseOOffs += width * height; + } + } + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + depth = Math.Max(1, depth >> 1); + } + + return output; + } + + public static MemoryOwner DecodeBC2(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + { + int size = 0; + + for (int l = 0; l < levels; l++) + { + size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; + } + + MemoryOwner output = MemoryOwner.Rent(size); + + Span tile = stackalloc byte[BlockWidth * BlockHeight * 4]; + + Span tileAsUint = MemoryMarshal.Cast(tile); + Span outputAsUint = MemoryMarshal.Cast(output.Span); + + Span> tileAsVector128 = MemoryMarshal.Cast>(tile); + + Span> outputLine0 = default; + Span> outputLine1 = default; + Span> outputLine2 = default; + Span> outputLine3 = default; + + int imageBaseOOffs = 0; + + for (int l = 0; l < levels; l++) + { + int w = BitUtils.DivRoundUp(width, BlockWidth); + int h = BitUtils.DivRoundUp(height, BlockHeight); + + for (int l2 = 0; l2 < layers; l2++) + { + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < h; y++) + { + int baseY = y * BlockHeight; + int copyHeight = Math.Min(BlockHeight, height - baseY); + int lineBaseOOffs = imageBaseOOffs + baseY * width; + + if (copyHeight == 4) + { + outputLine0 = MemoryMarshal.Cast>(outputAsUint[lineBaseOOffs..]); + outputLine1 = MemoryMarshal.Cast>(outputAsUint[(lineBaseOOffs + width)..]); + outputLine2 = MemoryMarshal.Cast>(outputAsUint[(lineBaseOOffs + width * 2)..]); + outputLine3 = MemoryMarshal.Cast>(outputAsUint[(lineBaseOOffs + width * 3)..]); + } + + for (int x = 0; x < w; x++) + { + int baseX = x * BlockWidth; + int copyWidth = Math.Min(BlockWidth, width - baseX); + + BC23DecodeTileRgb(tile, data[8..]); + + ulong block = BinaryPrimitives.ReadUInt64LittleEndian(data); + + for (int i = 3; i < BlockWidth * BlockHeight * 4; i += 4, block >>= 4) + { + tile[i] = (byte)((block & 0xf) | (block << 4)); + } + + if ((copyWidth | copyHeight) == 4) + { + outputLine0[x] = tileAsVector128[0]; + outputLine1[x] = tileAsVector128[1]; + outputLine2[x] = tileAsVector128[2]; + outputLine3[x] = tileAsVector128[3]; + } + else + { + int pixelBaseOOffs = lineBaseOOffs + baseX; + + for (int tY = 0; tY < copyHeight; tY++) + { + tileAsUint.Slice(tY * 4, copyWidth).CopyTo(outputAsUint.Slice(pixelBaseOOffs + width * tY, copyWidth)); + } + } + + data = data[16..]; + } + } + + imageBaseOOffs += width * height; + } + } + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + depth = Math.Max(1, depth >> 1); + } + + return output; + } + + public static MemoryOwner DecodeBC3(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + { + int size = 0; + + for (int l = 0; l < levels; l++) + { + size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; + } + + MemoryOwner output = MemoryOwner.Rent(size); + + Span tile = stackalloc byte[BlockWidth * BlockHeight * 4]; + Span rPal = stackalloc byte[8]; + + Span tileAsUint = MemoryMarshal.Cast(tile); + Span outputAsUint = MemoryMarshal.Cast(output.Span); + + Span> tileAsVector128 = MemoryMarshal.Cast>(tile); + + Span> outputLine0 = default; + Span> outputLine1 = default; + Span> outputLine2 = default; + Span> outputLine3 = default; + + int imageBaseOOffs = 0; + + for (int l = 0; l < levels; l++) + { + int w = BitUtils.DivRoundUp(width, BlockWidth); + int h = BitUtils.DivRoundUp(height, BlockHeight); + + for (int l2 = 0; l2 < layers; l2++) + { + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < h; y++) + { + int baseY = y * BlockHeight; + int copyHeight = Math.Min(BlockHeight, height - baseY); + int lineBaseOOffs = imageBaseOOffs + baseY * width; + + if (copyHeight == 4) + { + outputLine0 = MemoryMarshal.Cast>(outputAsUint[lineBaseOOffs..]); + outputLine1 = MemoryMarshal.Cast>(outputAsUint[(lineBaseOOffs + width)..]); + outputLine2 = MemoryMarshal.Cast>(outputAsUint[(lineBaseOOffs + width * 2)..]); + outputLine3 = MemoryMarshal.Cast>(outputAsUint[(lineBaseOOffs + width * 3)..]); + } + + for (int x = 0; x < w; x++) + { + int baseX = x * BlockWidth; + int copyWidth = Math.Min(BlockWidth, width - baseX); + + BC23DecodeTileRgb(tile, data[8..]); + + ulong block = BinaryPrimitives.ReadUInt64LittleEndian(data); + + rPal[0] = (byte)block; + rPal[1] = (byte)(block >> 8); + + BCnLerpAlphaUnorm(rPal); + BCnDecodeTileAlphaRgba(tile, rPal, block >> 16); + + if ((copyWidth | copyHeight) == 4) + { + outputLine0[x] = tileAsVector128[0]; + outputLine1[x] = tileAsVector128[1]; + outputLine2[x] = tileAsVector128[2]; + outputLine3[x] = tileAsVector128[3]; + } + else + { + int pixelBaseOOffs = lineBaseOOffs + baseX; + + for (int tY = 0; tY < copyHeight; tY++) + { + tileAsUint.Slice(tY * 4, copyWidth).CopyTo(outputAsUint.Slice(pixelBaseOOffs + width * tY, copyWidth)); + } + } + + data = data[16..]; + } + } + + imageBaseOOffs += width * height; + } + } + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + depth = Math.Max(1, depth >> 1); + } + + return output; + } + + public static MemoryOwner DecodeBC4(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) + { + int size = 0; + + for (int l = 0; l < levels; l++) + { + size += BitUtils.AlignUp(Math.Max(1, width >> l), 4) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers; + } + + // Backends currently expect a stride alignment of 4 bytes, so output width must be aligned. + int alignedWidth = BitUtils.AlignUp(width, 4); + + MemoryOwner output = MemoryOwner.Rent(size); + Span outputSpan = output.Span; + + ReadOnlySpan data64 = MemoryMarshal.Cast(data); + + Span tile = stackalloc byte[BlockWidth * BlockHeight]; + Span rPal = stackalloc byte[8]; + + Span tileAsUint = MemoryMarshal.Cast(tile); + + Span outputLine0 = default; + Span outputLine1 = default; + Span outputLine2 = default; + Span outputLine3 = default; + + int imageBaseOOffs = 0; + + for (int l = 0; l < levels; l++) + { + int w = BitUtils.DivRoundUp(width, BlockWidth); + int h = BitUtils.DivRoundUp(height, BlockHeight); + + for (int l2 = 0; l2 < layers; l2++) + { + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < h; y++) + { + int baseY = y * BlockHeight; + int copyHeight = Math.Min(BlockHeight, height - baseY); + int lineBaseOOffs = imageBaseOOffs + baseY * alignedWidth; + + if (copyHeight == 4) + { + outputLine0 = MemoryMarshal.Cast(outputSpan[lineBaseOOffs..]); + outputLine1 = MemoryMarshal.Cast(outputSpan[(lineBaseOOffs + alignedWidth)..]); + outputLine2 = MemoryMarshal.Cast(outputSpan[(lineBaseOOffs + alignedWidth * 2)..]); + outputLine3 = MemoryMarshal.Cast(outputSpan[(lineBaseOOffs + alignedWidth * 3)..]); + } + + for (int x = 0; x < w; x++) + { + int baseX = x * BlockWidth; + int copyWidth = Math.Min(BlockWidth, width - baseX); + + ulong block = data64[0]; + + rPal[0] = (byte)block; + rPal[1] = (byte)(block >> 8); + + if (signed) + { + BCnLerpAlphaSnorm(rPal); + } + else + { + BCnLerpAlphaUnorm(rPal); + } + + BCnDecodeTileAlpha(tile, rPal, block >> 16); + + if ((copyWidth | copyHeight) == 4) + { + outputLine0[x] = tileAsUint[0]; + outputLine1[x] = tileAsUint[1]; + outputLine2[x] = tileAsUint[2]; + outputLine3[x] = tileAsUint[3]; + } + else + { + int pixelBaseOOffs = lineBaseOOffs + baseX; + + for (int tY = 0; tY < copyHeight; tY++) + { + tile.Slice(tY * 4, copyWidth).CopyTo(outputSpan.Slice(pixelBaseOOffs + alignedWidth * tY, copyWidth)); + } + } + + data64 = data64[1..]; + } + } + + imageBaseOOffs += alignedWidth * height; + } + } + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + depth = Math.Max(1, depth >> 1); + + alignedWidth = BitUtils.AlignUp(width, 4); + } + + return output; + } + + public static MemoryOwner DecodeBC5(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) + { + int size = 0; + + for (int l = 0; l < levels; l++) + { + size += BitUtils.AlignUp(Math.Max(1, width >> l), 2) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 2; + } + + // Backends currently expect a stride alignment of 4 bytes, so output width must be aligned. + int alignedWidth = BitUtils.AlignUp(width, 2); + + MemoryOwner output = MemoryOwner.Rent(size); + + ReadOnlySpan data64 = MemoryMarshal.Cast(data); + + Span rTile = stackalloc byte[BlockWidth * BlockHeight * 2]; + Span gTile = stackalloc byte[BlockWidth * BlockHeight * 2]; + Span rPal = stackalloc byte[8]; + Span gPal = stackalloc byte[8]; + + Span outputAsUshort = MemoryMarshal.Cast(output.Span); + + Span rTileAsUint = MemoryMarshal.Cast(rTile); + Span gTileAsUint = MemoryMarshal.Cast(gTile); + + Span outputLine0 = default; + Span outputLine1 = default; + Span outputLine2 = default; + Span outputLine3 = default; + + int imageBaseOOffs = 0; + + for (int l = 0; l < levels; l++) + { + int w = BitUtils.DivRoundUp(width, BlockWidth); + int h = BitUtils.DivRoundUp(height, BlockHeight); + + for (int l2 = 0; l2 < layers; l2++) + { + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < h; y++) + { + int baseY = y * BlockHeight; + int copyHeight = Math.Min(BlockHeight, height - baseY); + int lineBaseOOffs = imageBaseOOffs + baseY * alignedWidth; + + if (copyHeight == 4) + { + outputLine0 = MemoryMarshal.Cast(outputAsUshort[lineBaseOOffs..]); + outputLine1 = MemoryMarshal.Cast(outputAsUshort[(lineBaseOOffs + alignedWidth)..]); + outputLine2 = MemoryMarshal.Cast(outputAsUshort[(lineBaseOOffs + alignedWidth * 2)..]); + outputLine3 = MemoryMarshal.Cast(outputAsUshort[(lineBaseOOffs + alignedWidth * 3)..]); + } + + for (int x = 0; x < w; x++) + { + int baseX = x * BlockWidth; + int copyWidth = Math.Min(BlockWidth, width - baseX); + + ulong blockL = data64[0]; + ulong blockH = data64[1]; + + rPal[0] = (byte)blockL; + rPal[1] = (byte)(blockL >> 8); + gPal[0] = (byte)blockH; + gPal[1] = (byte)(blockH >> 8); + + if (signed) + { + BCnLerpAlphaSnorm(rPal); + BCnLerpAlphaSnorm(gPal); + } + else + { + BCnLerpAlphaUnorm(rPal); + BCnLerpAlphaUnorm(gPal); + } + + BCnDecodeTileAlpha(rTile, rPal, blockL >> 16); + BCnDecodeTileAlpha(gTile, gPal, blockH >> 16); + + if ((copyWidth | copyHeight) == 4) + { + outputLine0[x] = InterleaveBytes(rTileAsUint[0], gTileAsUint[0]); + outputLine1[x] = InterleaveBytes(rTileAsUint[1], gTileAsUint[1]); + outputLine2[x] = InterleaveBytes(rTileAsUint[2], gTileAsUint[2]); + outputLine3[x] = InterleaveBytes(rTileAsUint[3], gTileAsUint[3]); + } + else + { + int pixelBaseOOffs = lineBaseOOffs + baseX; + + for (int tY = 0; tY < copyHeight; tY++) + { + int line = pixelBaseOOffs + alignedWidth * tY; + + for (int tX = 0; tX < copyWidth; tX++) + { + int texel = tY * BlockWidth + tX; + + outputAsUshort[line + tX] = (ushort)(rTile[texel] | (gTile[texel] << 8)); + } + } + } + + data64 = data64[2..]; + } + } + + imageBaseOOffs += alignedWidth * height; + } + } + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + depth = Math.Max(1, depth >> 1); + + alignedWidth = BitUtils.AlignUp(width, 2); + } + + return output; + } + + public static MemoryOwner DecodeBC6(ReadOnlySpan data, int width, int height, int depth, int levels, int layers, bool signed) + { + int size = 0; + + for (int l = 0; l < levels; l++) + { + size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 8; + } + + MemoryOwner output = MemoryOwner.Rent(size); + Span outputSpan = output.Span; + + int inputOffset = 0; + int outputOffset = 0; + + for (int l = 0; l < levels; l++) + { + int w = BitUtils.DivRoundUp(width, BlockWidth); + int h = BitUtils.DivRoundUp(height, BlockHeight); + + for (int l2 = 0; l2 < layers; l2++) + { + for (int z = 0; z < depth; z++) + { + BC6Decoder.Decode(outputSpan[outputOffset..], data[inputOffset..], width, height, signed); + + inputOffset += w * h * 16; + outputOffset += width * height * 8; + } + } + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + depth = Math.Max(1, depth >> 1); + } + + return output; + } + + public static MemoryOwner DecodeBC7(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + { + int size = 0; + + for (int l = 0; l < levels; l++) + { + size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; + } + + MemoryOwner output = MemoryOwner.Rent(size); + Span outputSpan = output.Span; + + int inputOffset = 0; + int outputOffset = 0; + + for (int l = 0; l < levels; l++) + { + int w = BitUtils.DivRoundUp(width, BlockWidth); + int h = BitUtils.DivRoundUp(height, BlockHeight); + + for (int l2 = 0; l2 < layers; l2++) + { + for (int z = 0; z < depth; z++) + { + BC7Decoder.Decode(outputSpan[outputOffset..], data[inputOffset..], width, height); + + inputOffset += w * h * 16; + outputOffset += width * height * 4; + } + } + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + depth = Math.Max(1, depth >> 1); + } + + return output; + } + + private static ulong InterleaveBytes(uint left, uint right) + { + return InterleaveBytesWithZeros(left) | (InterleaveBytesWithZeros(right) << 8); + } + + private static ulong InterleaveBytesWithZeros(uint value) + { + ulong output = value; + output = (output ^ (output << 16)) & 0xffff0000ffffUL; + output = (output ^ (output << 8)) & 0xff00ff00ff00ffUL; + return output; + } + + private static void BCnLerpAlphaUnorm(Span alpha) + { + byte a0 = alpha[0]; + byte a1 = alpha[1]; + + if (a0 > a1) + { + alpha[2] = (byte)((6 * a0 + 1 * a1) / 7); + alpha[3] = (byte)((5 * a0 + 2 * a1) / 7); + alpha[4] = (byte)((4 * a0 + 3 * a1) / 7); + alpha[5] = (byte)((3 * a0 + 4 * a1) / 7); + alpha[6] = (byte)((2 * a0 + 5 * a1) / 7); + alpha[7] = (byte)((1 * a0 + 6 * a1) / 7); + } + else + { + alpha[2] = (byte)((4 * a0 + 1 * a1) / 5); + alpha[3] = (byte)((3 * a0 + 2 * a1) / 5); + alpha[4] = (byte)((2 * a0 + 3 * a1) / 5); + alpha[5] = (byte)((1 * a0 + 4 * a1) / 5); + alpha[6] = 0; + alpha[7] = 0xff; + } + } + + private static void BCnLerpAlphaSnorm(Span alpha) + { + sbyte a0 = (sbyte)alpha[0]; + sbyte a1 = (sbyte)alpha[1]; + + if (a0 > a1) + { + alpha[2] = (byte)((6 * a0 + 1 * a1) / 7); + alpha[3] = (byte)((5 * a0 + 2 * a1) / 7); + alpha[4] = (byte)((4 * a0 + 3 * a1) / 7); + alpha[5] = (byte)((3 * a0 + 4 * a1) / 7); + alpha[6] = (byte)((2 * a0 + 5 * a1) / 7); + alpha[7] = (byte)((1 * a0 + 6 * a1) / 7); + } + else + { + alpha[2] = (byte)((4 * a0 + 1 * a1) / 5); + alpha[3] = (byte)((3 * a0 + 2 * a1) / 5); + alpha[4] = (byte)((2 * a0 + 3 * a1) / 5); + alpha[5] = (byte)((1 * a0 + 4 * a1) / 5); + alpha[6] = 0x80; + alpha[7] = 0x7f; + } + } + + private unsafe static void BCnDecodeTileAlpha(Span output, Span rPal, ulong rI) + { + if (Avx2.IsSupported) + { + Span> outputAsVector128 = MemoryMarshal.Cast>(output); + + Vector128 shifts = Vector128.Create(0u, 3u, 6u, 9u); + Vector128 masks = Vector128.Create(7u); + + Vector128 vClut; + + fixed (byte* pRPal = rPal) + { + vClut = Sse2.LoadScalarVector128((ulong*)pRPal).AsByte(); + } + + Vector128 indices0 = Vector128.Create((uint)rI); + Vector128 indices1 = Vector128.Create((uint)(rI >> 24)); + Vector128 indices00 = Avx2.ShiftRightLogicalVariable(indices0, shifts); + Vector128 indices10 = Avx2.ShiftRightLogicalVariable(indices1, shifts); + Vector128 indices01 = Sse2.ShiftRightLogical(indices00, 12); + Vector128 indices11 = Sse2.ShiftRightLogical(indices10, 12); + indices00 = Sse2.And(indices00, masks); + indices10 = Sse2.And(indices10, masks); + indices01 = Sse2.And(indices01, masks); + indices11 = Sse2.And(indices11, masks); + + Vector128 indicesW0 = Sse41.PackUnsignedSaturate(indices00.AsInt32(), indices01.AsInt32()); + Vector128 indicesW1 = Sse41.PackUnsignedSaturate(indices10.AsInt32(), indices11.AsInt32()); + + Vector128 indices = Sse2.PackUnsignedSaturate(indicesW0.AsInt16(), indicesW1.AsInt16()); + + outputAsVector128[0] = Ssse3.Shuffle(vClut, indices); + } + else + { + for (int i = 0; i < BlockWidth * BlockHeight; i++, rI >>= 3) + { + output[i] = rPal[(int)(rI & 7)]; + } + } + } + + private unsafe static void BCnDecodeTileAlphaRgba(Span output, Span rPal, ulong rI) + { + if (Avx2.IsSupported) + { + Span> outputAsVector256 = MemoryMarshal.Cast>(output); + + Vector256 shifts = Vector256.Create(0u, 3u, 6u, 9u, 12u, 15u, 18u, 21u); + + Vector128 vClut128; + + fixed (byte* pRPal = rPal) + { + vClut128 = Sse2.LoadScalarVector128((ulong*)pRPal).AsUInt32(); + } + + Vector256 vClut = Avx2.ConvertToVector256Int32(vClut128.AsByte()).AsUInt32(); + vClut = Avx2.ShiftLeftLogical(vClut, 24); + + Vector256 indices0 = Vector256.Create((uint)rI); + Vector256 indices1 = Vector256.Create((uint)(rI >> 24)); + + indices0 = Avx2.ShiftRightLogicalVariable(indices0, shifts); + indices1 = Avx2.ShiftRightLogicalVariable(indices1, shifts); + + outputAsVector256[0] = Avx2.Or(outputAsVector256[0], Avx2.PermuteVar8x32(vClut, indices0)); + outputAsVector256[1] = Avx2.Or(outputAsVector256[1], Avx2.PermuteVar8x32(vClut, indices1)); + } + else + { + for (int i = 3; i < BlockWidth * BlockHeight * 4; i += 4, rI >>= 3) + { + output[i] = rPal[(int)(rI & 7)]; + } + } + } + + private unsafe static void BC1DecodeTileRgb(Span output, ReadOnlySpan input) + { + Span clut = stackalloc uint[4]; + + uint c0c1 = BinaryPrimitives.ReadUInt32LittleEndian(input); + uint c0 = (ushort)c0c1; + uint c1 = (ushort)(c0c1 >> 16); + + clut[0] = ConvertRgb565ToRgb888(c0) | 0xff000000; + clut[1] = ConvertRgb565ToRgb888(c1) | 0xff000000; + clut[2] = BC1LerpRgb2(clut[0], clut[1], c0, c1); + clut[3] = BC1LerpRgb3(clut[0], clut[1], c0, c1); + + BCnDecodeTileRgb(clut, output, input); + } + + private unsafe static void BC23DecodeTileRgb(Span output, ReadOnlySpan input) + { + Span clut = stackalloc uint[4]; + + uint c0c1 = BinaryPrimitives.ReadUInt32LittleEndian(input); + uint c0 = (ushort)c0c1; + uint c1 = (ushort)(c0c1 >> 16); + + clut[0] = ConvertRgb565ToRgb888(c0); + clut[1] = ConvertRgb565ToRgb888(c1); + clut[2] = BC23LerpRgb2(clut[0], clut[1]); + clut[3] = BC23LerpRgb3(clut[0], clut[1]); + + BCnDecodeTileRgb(clut, output, input); + } + + private unsafe static void BCnDecodeTileRgb(Span clut, Span output, ReadOnlySpan input) + { + if (Avx2.IsSupported) + { + Span> outputAsVector256 = MemoryMarshal.Cast>(output); + + Vector256 shifts0 = Vector256.Create(0u, 2u, 4u, 6u, 8u, 10u, 12u, 14u); + Vector256 shifts1 = Vector256.Create(16u, 18u, 20u, 22u, 24u, 26u, 28u, 30u); + Vector256 masks = Vector256.Create(3u); + + Vector256 vClut; + + fixed (uint* pClut = &clut[0]) + { + vClut = Sse2.LoadVector128(pClut).ToVector256Unsafe(); + } + + Vector256 indices0; + + fixed (byte* pInput = input) + { + indices0 = Avx2.BroadcastScalarToVector256((uint*)(pInput + 4)); + } + + Vector256 indices1 = indices0; + + indices0 = Avx2.ShiftRightLogicalVariable(indices0, shifts0); + indices1 = Avx2.ShiftRightLogicalVariable(indices1, shifts1); + indices0 = Avx2.And(indices0, masks); + indices1 = Avx2.And(indices1, masks); + + outputAsVector256[0] = Avx2.PermuteVar8x32(vClut, indices0); + outputAsVector256[1] = Avx2.PermuteVar8x32(vClut, indices1); + } + else + { + Span outputAsUint = MemoryMarshal.Cast(output); + + uint indices = BinaryPrimitives.ReadUInt32LittleEndian(input[4..]); + + for (int i = 0; i < BlockWidth * BlockHeight; i++, indices >>= 2) + { + outputAsUint[i] = clut[(int)(indices & 3)]; + } + } + } + + private static uint BC1LerpRgb2(uint color0, uint color1, uint c0, uint c1) + { + if (c0 > c1) + { + return BC23LerpRgb2(color0, color1) | 0xff000000; + } + + uint carry = color0 & color1; + uint addHalve = ((color0 ^ color1) >> 1) & 0x7f7f7f; + return (addHalve + carry) | 0xff000000; + } + + private static uint BC23LerpRgb2(uint color0, uint color1) + { + uint r0 = (byte)color0; + uint g0 = color0 & 0xff00; + uint b0 = color0 & 0xff0000; + + uint r1 = (byte)color1; + uint g1 = color1 & 0xff00; + uint b1 = color1 & 0xff0000; + + uint mixR = (2 * r0 + r1) / 3; + uint mixG = (2 * g0 + g1) / 3; + uint mixB = (2 * b0 + b1) / 3; + + return mixR | (mixG & 0xff00) | (mixB & 0xff0000); + } + + private static uint BC1LerpRgb3(uint color0, uint color1, uint c0, uint c1) + { + if (c0 > c1) + { + return BC23LerpRgb3(color0, color1) | 0xff000000; + } + + return 0; + } + + private static uint BC23LerpRgb3(uint color0, uint color1) + { + uint r0 = (byte)color0; + uint g0 = color0 & 0xff00; + uint b0 = color0 & 0xff0000; + + uint r1 = (byte)color1; + uint g1 = color1 & 0xff00; + uint b1 = color1 & 0xff0000; + + uint mixR = (2 * r1 + r0) / 3; + uint mixG = (2 * g1 + g0) / 3; + uint mixB = (2 * b1 + b0) / 3; + + return mixR | (mixG & 0xff00) | (mixB & 0xff0000); + } + + private static uint ConvertRgb565ToRgb888(uint value) + { + uint b = (value & 0x1f) << 19; + uint g = (value << 5) & 0xfc00; + uint r = (value >> 8) & 0xf8; + + b |= b >> 5; + g |= g >> 6; + r |= r >> 5; + + return r | (g & 0xff00) | (b & 0xff0000); + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/BCnEncoder.cs b/src/Ryujinx.Graphics.Texture/BCnEncoder.cs new file mode 100644 index 00000000..4db8a182 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/BCnEncoder.cs @@ -0,0 +1,60 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Texture.Encoders; +using System; + +namespace Ryujinx.Graphics.Texture +{ + public static class BCnEncoder + { + private const int BlockWidth = 4; + private const int BlockHeight = 4; + + public static MemoryOwner EncodeBC7(Memory data, int width, int height, int depth, int levels, int layers) + { + int size = 0; + + for (int l = 0; l < levels; l++) + { + int w = BitUtils.DivRoundUp(Math.Max(1, width >> l), BlockWidth); + int h = BitUtils.DivRoundUp(Math.Max(1, height >> l), BlockHeight); + + size += w * h * 16 * Math.Max(1, depth >> l) * layers; + } + + MemoryOwner output = MemoryOwner.Rent(size); + Memory outputMemory = output.Memory; + + int imageBaseIOffs = 0; + int imageBaseOOffs = 0; + + for (int l = 0; l < levels; l++) + { + int w = BitUtils.DivRoundUp(width, BlockWidth); + int h = BitUtils.DivRoundUp(height, BlockHeight); + + for (int l2 = 0; l2 < layers; l2++) + { + for (int z = 0; z < depth; z++) + { + BC7Encoder.Encode( + outputMemory[imageBaseOOffs..], + data[imageBaseIOffs..], + width, + height, + EncodeMode.Fast | EncodeMode.Multithreaded); + + imageBaseIOffs += width * height * 4; + imageBaseOOffs += w * h * 16; + } + } + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + depth = Math.Max(1, depth >> 1); + } + + return output; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/BlockLinearConstants.cs b/src/Ryujinx.Graphics.Texture/BlockLinearConstants.cs new file mode 100644 index 00000000..ad0dda85 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/BlockLinearConstants.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Texture +{ + static class BlockLinearConstants + { + public const int GobStride = 64; + public const int GobHeight = 8; + + public const int GobSize = GobStride * GobHeight; + } +} diff --git a/src/Ryujinx.Graphics.Texture/BlockLinearLayout.cs b/src/Ryujinx.Graphics.Texture/BlockLinearLayout.cs new file mode 100644 index 00000000..809a32cc --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/BlockLinearLayout.cs @@ -0,0 +1,195 @@ +using Ryujinx.Common; +using System.Numerics; +using System.Runtime.CompilerServices; + +using static Ryujinx.Graphics.Texture.BlockLinearConstants; + +namespace Ryujinx.Graphics.Texture +{ + class BlockLinearLayout + { + private struct RobAndSliceSizes + { + public int RobSize; + public int SliceSize; + + public RobAndSliceSizes(int robSize, int sliceSize) + { + RobSize = robSize; + SliceSize = sliceSize; + } + } + + private readonly int _texBpp; + + private readonly int _bhMask; + private readonly int _bdMask; + + private readonly int _bhShift; + private readonly int _bdShift; + private readonly int _bppShift; + + private readonly int _xShift; + + private readonly int _robSize; + private readonly int _sliceSize; + + // Variables for built in iteration. + private int _yPart; + private int _yzPart; + private int _zPart; + + public BlockLinearLayout( + int width, + int height, + int gobBlocksInY, + int gobBlocksInZ, + int bpp) + { + _texBpp = bpp; + + _bppShift = BitOperations.TrailingZeroCount(bpp); + + _bhMask = gobBlocksInY - 1; + _bdMask = gobBlocksInZ - 1; + + _bhShift = BitOperations.TrailingZeroCount(gobBlocksInY); + _bdShift = BitOperations.TrailingZeroCount(gobBlocksInZ); + + _xShift = BitOperations.TrailingZeroCount(GobSize * gobBlocksInY * gobBlocksInZ); + + RobAndSliceSizes rsSizes = GetRobAndSliceSizes(width, height, gobBlocksInY, gobBlocksInZ); + + _robSize = rsSizes.RobSize; + _sliceSize = rsSizes.SliceSize; + } + + private RobAndSliceSizes GetRobAndSliceSizes(int width, int height, int gobBlocksInY, int gobBlocksInZ) + { + int widthInGobs = BitUtils.DivRoundUp(width * _texBpp, GobStride); + + int robSize = GobSize * gobBlocksInY * gobBlocksInZ * widthInGobs; + + int sliceSize = BitUtils.DivRoundUp(height, gobBlocksInY * GobHeight) * robSize; + + return new RobAndSliceSizes(robSize, sliceSize); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffset(int x, int y, int z) + { + return GetOffsetWithLineOffset(x << _bppShift, y, z); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffsetWithLineOffset(int x, int y, int z) + { + int yh = y / GobHeight; + + int offset = (z >> _bdShift) * _sliceSize + (yh >> _bhShift) * _robSize; + + offset += (x / GobStride) << _xShift; + + offset += (yh & _bhMask) * GobSize; + + offset += ((z & _bdMask) * GobSize) << _bhShift; + + offset += ((x & 0x3f) >> 5) << 8; + offset += ((y & 0x07) >> 1) << 6; + offset += ((x & 0x1f) >> 4) << 5; + offset += ((y & 0x01) >> 0) << 4; + offset += ((x & 0x0f) >> 0) << 0; + + return offset; + } + + public (int offset, int size) GetRectangleRange(int x, int y, int width, int height) + { + // Justification: + // The 2D offset is a combination of separate x and y parts. + // Both components increase with input and never overlap bits. + // Therefore for each component, the minimum input value is the lowest that component can go. + // Minimum total value is minimum X component + minimum Y component. Similar goes for maximum. + + int start = GetOffset(x, y, 0); + int end = GetOffset(x + width - 1, y + height - 1, 0) + _texBpp; // Cover the last pixel. + return (start, end - start); + } + + public bool LayoutMatches(BlockLinearLayout other) + { + return _robSize == other._robSize && + _sliceSize == other._sliceSize && + _texBpp == other._texBpp && + _bhMask == other._bhMask && + _bdMask == other._bdMask; + } + + // Functions for built in iteration. + // Components of the offset can be updated separately, and combined to save some time. + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetY(int y) + { + int yh = y / GobHeight; + int offset = (yh >> _bhShift) * _robSize; + + offset += (yh & _bhMask) * GobSize; + + offset += ((y & 0x07) >> 1) << 6; + offset += ((y & 0x01) >> 0) << 4; + + _yPart = offset; + _yzPart = offset + _zPart; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetZ(int z) + { + int offset = (z >> _bdShift) * _sliceSize; + + offset += ((z & _bdMask) * GobSize) << _bhShift; + + _zPart = offset; + _yzPart = offset + _yPart; + } + + /// + /// Optimized conversion for line offset in bytes to an absolute offset. Input x must be divisible by 16. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffsetWithLineOffset16(int x) + { + int offset = (x / GobStride) << _xShift; + + offset += ((x & 0x3f) >> 5) << 8; + offset += ((x & 0x1f) >> 4) << 5; + + return offset + _yzPart; + } + + /// + /// Optimized conversion for line offset in bytes to an absolute offset. Input x must be divisible by 64. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffsetWithLineOffset64(int x) + { + int offset = (x / GobStride) << _xShift; + + return offset + _yzPart; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffset(int x) + { + x <<= _bppShift; + int offset = (x / GobStride) << _xShift; + + offset += ((x & 0x3f) >> 5) << 8; + offset += ((x & 0x1f) >> 4) << 5; + offset += (x & 0x0f); + + return offset + _yzPart; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Bpp12Pixel.cs b/src/Ryujinx.Graphics.Texture/Bpp12Pixel.cs new file mode 100644 index 00000000..bde96f0f --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Bpp12Pixel.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 12)] + public readonly struct Bpp12Pixel + { + private readonly ulong _elem1; + private readonly uint _elem2; + } +} diff --git a/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs b/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs new file mode 100644 index 00000000..49e7154c --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/ETC2Decoder.cs @@ -0,0 +1,683 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using System; +using System.Buffers.Binary; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Texture +{ + public static class ETC2Decoder + { + private const uint AlphaMask = 0xff000000u; + + private const int BlockWidth = 4; + private const int BlockHeight = 4; + + private static readonly int[][] _etc1Lut = + { + new int[] { 2, 8, -2, -8 }, + new int[] { 5, 17, -5, -17 }, + new int[] { 9, 29, -9, -29 }, + new int[] { 13, 42, -13, -42 }, + new int[] { 18, 60, -18, -60 }, + new int[] { 24, 80, -24, -80 }, + new int[] { 33, 106, -33, -106 }, + new int[] { 47, 183, -47, -183 }, + }; + + private static readonly int[] _etc2Lut = + { + 3, 6, 11, 16, 23, 32, 41, 64, + }; + + private static readonly int[][] _etc2AlphaLut = + { + new int[] { -3, -6, -9, -15, 2, 5, 8, 14 }, + new int[] { -3, -7, -10, -13, 2, 6, 9, 12 }, + new int[] { -2, -5, -8, -13, 1, 4, 7, 12 }, + new int[] { -2, -4, -6, -13, 1, 3, 5, 12 }, + new int[] { -3, -6, -8, -12, 2, 5, 7, 11 }, + new int[] { -3, -7, -9, -11, 2, 6, 8, 10 }, + new int[] { -4, -7, -8, -11, 3, 6, 7, 10 }, + new int[] { -3, -5, -8, -11, 2, 4, 7, 10 }, + new int[] { -2, -6, -8, -10, 1, 5, 7, 9 }, + new int[] { -2, -5, -8, -10, 1, 4, 7, 9 }, + new int[] { -2, -4, -8, -10, 1, 3, 7, 9 }, + new int[] { -2, -5, -7, -10, 1, 4, 6, 9 }, + new int[] { -3, -4, -7, -10, 2, 3, 6, 9 }, + new int[] { -1, -2, -3, -10, 0, 1, 2, 9 }, + new int[] { -4, -6, -8, -9, 3, 5, 7, 8 }, + new int[] { -3, -5, -7, -9, 2, 4, 6, 8 }, + }; + + public static MemoryOwner DecodeRgb(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + { + ReadOnlySpan dataUlong = MemoryMarshal.Cast(data); + + int inputOffset = 0; + + MemoryOwner output = MemoryOwner.Rent(CalculateOutputSize(width, height, depth, levels, layers)); + + Span outputUint = MemoryMarshal.Cast(output.Span); + Span tile = stackalloc uint[BlockWidth * BlockHeight]; + + int imageBaseOOffs = 0; + + for (int l = 0; l < levels; l++) + { + int wInBlocks = BitUtils.DivRoundUp(width, BlockWidth); + int hInBlocks = BitUtils.DivRoundUp(height, BlockHeight); + + for (int l2 = 0; l2 < layers; l2++) + { + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < hInBlocks; y++) + { + int ty = y * BlockHeight; + int bh = Math.Min(BlockHeight, height - ty); + + for (int x = 0; x < wInBlocks; x++) + { + int tx = x * BlockWidth; + int bw = Math.Min(BlockWidth, width - tx); + + ulong colorBlock = dataUlong[inputOffset++]; + + DecodeBlock(tile, colorBlock); + + for (int py = 0; py < bh; py++) + { + int oOffsBase = imageBaseOOffs + ((ty + py) * width) + tx; + + for (int px = 0; px < bw; px++) + { + int oOffs = oOffsBase + px; + + outputUint[oOffs] = tile[py * BlockWidth + px] | AlphaMask; + } + } + } + } + + imageBaseOOffs += width * height; + } + } + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + depth = Math.Max(1, depth >> 1); + } + + return output; + } + + public static MemoryOwner DecodePta(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + { + ReadOnlySpan dataUlong = MemoryMarshal.Cast(data); + + int inputOffset = 0; + + MemoryOwner output = MemoryOwner.Rent(CalculateOutputSize(width, height, depth, levels, layers)); + + Span outputUint = MemoryMarshal.Cast(output.Span); + Span tile = stackalloc uint[BlockWidth * BlockHeight]; + + int imageBaseOOffs = 0; + + for (int l = 0; l < levels; l++) + { + int wInBlocks = BitUtils.DivRoundUp(width, BlockWidth); + int hInBlocks = BitUtils.DivRoundUp(height, BlockHeight); + + for (int l2 = 0; l2 < layers; l2++) + { + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < hInBlocks; y++) + { + int ty = y * BlockHeight; + int bh = Math.Min(BlockHeight, height - ty); + + for (int x = 0; x < wInBlocks; x++) + { + int tx = x * BlockWidth; + int bw = Math.Min(BlockWidth, width - tx); + + ulong colorBlock = dataUlong[inputOffset++]; + + DecodeBlockPta(tile, colorBlock); + + for (int py = 0; py < bh; py++) + { + int oOffsBase = imageBaseOOffs + ((ty + py) * width) + tx; + + tile.Slice(py * BlockWidth, bw).CopyTo(outputUint.Slice(oOffsBase, bw)); + } + } + } + + imageBaseOOffs += width * height; + } + } + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + depth = Math.Max(1, depth >> 1); + } + + return output; + } + + public static MemoryOwner DecodeRgba(ReadOnlySpan data, int width, int height, int depth, int levels, int layers) + { + ReadOnlySpan dataUlong = MemoryMarshal.Cast(data); + + int inputOffset = 0; + + MemoryOwner output = MemoryOwner.Rent(CalculateOutputSize(width, height, depth, levels, layers)); + + Span outputUint = MemoryMarshal.Cast(output.Span); + Span tile = stackalloc uint[BlockWidth * BlockHeight]; + + int imageBaseOOffs = 0; + + for (int l = 0; l < levels; l++) + { + int wInBlocks = BitUtils.DivRoundUp(width, BlockWidth); + int hInBlocks = BitUtils.DivRoundUp(height, BlockHeight); + + for (int l2 = 0; l2 < layers; l2++) + { + for (int z = 0; z < depth; z++) + { + for (int y = 0; y < hInBlocks; y++) + { + int ty = y * BlockHeight; + int bh = Math.Min(BlockHeight, height - ty); + + for (int x = 0; x < wInBlocks; x++) + { + int tx = x * BlockWidth; + int bw = Math.Min(BlockWidth, width - tx); + + ulong alphaBlock = dataUlong[inputOffset]; + ulong colorBlock = dataUlong[inputOffset + 1]; + + inputOffset += 2; + + DecodeBlock(tile, colorBlock); + + byte alphaBase = (byte)alphaBlock; + int[] alphaTable = _etc2AlphaLut[(alphaBlock >> 8) & 0xf]; + int alphaMultiplier = (int)(alphaBlock >> 12) & 0xf; + ulong alphaIndices = BinaryPrimitives.ReverseEndianness(alphaBlock); + + if (alphaMultiplier != 0) + { + for (int py = 0; py < bh; py++) + { + int oOffsBase = imageBaseOOffs + ((ty + py) * width) + tx; + + for (int px = 0; px < bw; px++) + { + int oOffs = oOffsBase + px; + int alphaIndex = (int)((alphaIndices >> (((px * BlockHeight + py) ^ 0xf) * 3)) & 7); + + byte a = Saturate(alphaBase + alphaTable[alphaIndex] * alphaMultiplier); + + outputUint[oOffs] = tile[py * BlockWidth + px] | ((uint)a << 24); + } + } + } + else + { + uint a = (uint)alphaBase << 24; + + for (int py = 0; py < bh; py++) + { + int oOffsBase = imageBaseOOffs + ((ty + py) * width) + tx; + + for (int px = 0; px < bw; px++) + { + int oOffs = oOffsBase + px; + + outputUint[oOffs] = tile[py * BlockWidth + px] | a; + } + } + } + } + } + + imageBaseOOffs += width * height; + } + } + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + depth = Math.Max(1, depth >> 1); + } + + return output; + } + + private static void DecodeBlock(Span tile, ulong block) + { + uint blockLow = (uint)(block >> 0); + uint blockHigh = (uint)(block >> 32); + + uint r1, g1, b1; + uint r2, g2, b2; + + bool differentialMode = (blockLow & 0x2000000) != 0; + + if (differentialMode) + { + (r1, g1, b1, r2, g2, b2) = UnpackRgb555DiffEndPoints(blockLow); + + if (r2 > 31) + { + DecodeBlock59T(tile, blockLow, blockHigh); + } + else if (g2 > 31) + { + DecodeBlock58H(tile, blockLow, blockHigh); + } + else if (b2 > 31) + { + DecodeBlock57P(tile, block); + } + else + { + r1 |= r1 >> 5; + g1 |= g1 >> 5; + b1 |= b1 >> 5; + + r2 = (r2 << 3) | (r2 >> 2); + g2 = (g2 << 3) | (g2 >> 2); + b2 = (b2 << 3) | (b2 >> 2); + + DecodeBlockETC1(tile, blockLow, blockHigh, r1, g1, b1, r2, g2, b2); + } + } + else + { + r1 = (blockLow & 0x0000f0) >> 0; + g1 = (blockLow & 0x00f000) >> 8; + b1 = (blockLow & 0xf00000) >> 16; + + r2 = (blockLow & 0x00000f) << 4; + g2 = (blockLow & 0x000f00) >> 4; + b2 = (blockLow & 0x0f0000) >> 12; + + r1 |= r1 >> 4; + g1 |= g1 >> 4; + b1 |= b1 >> 4; + + r2 |= r2 >> 4; + g2 |= g2 >> 4; + b2 |= b2 >> 4; + + DecodeBlockETC1(tile, blockLow, blockHigh, r1, g1, b1, r2, g2, b2); + } + } + + private static void DecodeBlockPta(Span tile, ulong block) + { + uint blockLow = (uint)(block >> 0); + uint blockHigh = (uint)(block >> 32); + + (uint r1, uint g1, uint b1, uint r2, uint g2, uint b2) = UnpackRgb555DiffEndPoints(blockLow); + + bool fullyOpaque = (blockLow & 0x2000000) != 0; + + if (fullyOpaque) + { + if (r2 > 31) + { + DecodeBlock59T(tile, blockLow, blockHigh); + } + else if (g2 > 31) + { + DecodeBlock58H(tile, blockLow, blockHigh); + } + else if (b2 > 31) + { + DecodeBlock57P(tile, block); + } + else + { + r1 |= r1 >> 5; + g1 |= g1 >> 5; + b1 |= b1 >> 5; + + r2 = (r2 << 3) | (r2 >> 2); + g2 = (g2 << 3) | (g2 >> 2); + b2 = (b2 << 3) | (b2 >> 2); + + DecodeBlockETC1(tile, blockLow, blockHigh, r1, g1, b1, r2, g2, b2); + } + + for (int i = 0; i < tile.Length; i++) + { + tile[i] |= AlphaMask; + } + } + else + { + if (r2 > 31) + { + DecodeBlock59T(tile, blockLow, blockHigh, AlphaMask); + } + else if (g2 > 31) + { + DecodeBlock58H(tile, blockLow, blockHigh, AlphaMask); + } + else if (b2 > 31) + { + DecodeBlock57P(tile, block); + + for (int i = 0; i < tile.Length; i++) + { + tile[i] |= AlphaMask; + } + } + else + { + r1 |= r1 >> 5; + g1 |= g1 >> 5; + b1 |= b1 >> 5; + + r2 = (r2 << 3) | (r2 >> 2); + g2 = (g2 << 3) | (g2 >> 2); + b2 = (b2 << 3) | (b2 >> 2); + + DecodeBlockETC1(tile, blockLow, blockHigh, r1, g1, b1, r2, g2, b2, AlphaMask); + } + } + } + + private static (uint, uint, uint, uint, uint, uint) UnpackRgb555DiffEndPoints(uint blockLow) + { + uint r1 = (blockLow & 0x0000f8) >> 0; + uint g1 = (blockLow & 0x00f800) >> 8; + uint b1 = (blockLow & 0xf80000) >> 16; + + uint r2 = (uint)((sbyte)(r1 >> 3) + ((sbyte)((blockLow & 0x000007) << 5) >> 5)); + uint g2 = (uint)((sbyte)(g1 >> 3) + ((sbyte)((blockLow & 0x000700) >> 3) >> 5)); + uint b2 = (uint)((sbyte)(b1 >> 3) + ((sbyte)((blockLow & 0x070000) >> 11) >> 5)); + + return (r1, g1, b1, r2, g2, b2); + } + + private static void DecodeBlock59T(Span tile, uint blockLow, uint blockHigh, uint alphaMask = 0) + { + uint r1 = (blockLow & 3) | ((blockLow >> 1) & 0xc); + uint g1 = (blockLow >> 12) & 0xf; + uint b1 = (blockLow >> 8) & 0xf; + + uint r2 = (blockLow >> 20) & 0xf; + uint g2 = (blockLow >> 16) & 0xf; + uint b2 = (blockLow >> 28) & 0xf; + + r1 |= r1 << 4; + g1 |= g1 << 4; + b1 |= b1 << 4; + + r2 |= r2 << 4; + g2 |= g2 << 4; + b2 |= b2 << 4; + + int dist = _etc2Lut[((blockLow >> 24) & 1) | ((blockLow >> 25) & 6)]; + + Span palette = stackalloc uint[4]; + + palette[0] = Pack(r1, g1, b1); + palette[1] = Pack(r2, g2, b2, dist); + palette[2] = Pack(r2, g2, b2); + palette[3] = Pack(r2, g2, b2, -dist); + + blockHigh = BinaryPrimitives.ReverseEndianness(blockHigh); + + for (int y = 0; y < BlockHeight; y++) + { + for (int x = 0; x < BlockWidth; x++) + { + int offset = (y * 4) + x; + int index = (x * 4) + y; + + int paletteIndex = (int)((blockHigh >> index) & 1) | (int)((blockHigh >> (index + 15)) & 2); + + tile[offset] = palette[paletteIndex]; + + if (alphaMask != 0) + { + if (paletteIndex == 2) + { + tile[offset] = 0; + } + else + { + tile[offset] |= alphaMask; + } + } + } + } + } + + private static void DecodeBlock58H(Span tile, uint blockLow, uint blockHigh, uint alphaMask = 0) + { + uint r1 = (blockLow >> 3) & 0xf; + uint g1 = ((blockLow << 1) & 0xe) | ((blockLow >> 12) & 1); + uint b1 = ((blockLow >> 23) & 1) | ((blockLow >> 7) & 6) | ((blockLow >> 8) & 8); + + uint r2 = (blockLow >> 19) & 0xf; + uint g2 = ((blockLow >> 31) & 1) | ((blockLow >> 15) & 0xe); + uint b2 = (blockLow >> 27) & 0xf; + + uint rgb1 = Pack4Be(r1, g1, b1); + uint rgb2 = Pack4Be(r2, g2, b2); + + r1 |= r1 << 4; + g1 |= g1 << 4; + b1 |= b1 << 4; + + r2 |= r2 << 4; + g2 |= g2 << 4; + b2 |= b2 << 4; + + int dist = _etc2Lut[(rgb1 >= rgb2 ? 1u : 0u) | ((blockLow >> 23) & 2) | ((blockLow >> 24) & 4)]; + + Span palette = stackalloc uint[4]; + + palette[0] = Pack(r1, g1, b1, dist); + palette[1] = Pack(r1, g1, b1, -dist); + palette[2] = Pack(r2, g2, b2, dist); + palette[3] = Pack(r2, g2, b2, -dist); + + blockHigh = BinaryPrimitives.ReverseEndianness(blockHigh); + + for (int y = 0; y < BlockHeight; y++) + { + for (int x = 0; x < BlockWidth; x++) + { + int offset = (y * 4) + x; + int index = (x * 4) + y; + + int paletteIndex = (int)((blockHigh >> index) & 1) | (int)((blockHigh >> (index + 15)) & 2); + + tile[offset] = palette[paletteIndex]; + + if (alphaMask != 0) + { + if (paletteIndex == 2) + { + tile[offset] = 0; + } + else + { + tile[offset] |= alphaMask; + } + } + } + } + } + + private static void DecodeBlock57P(Span tile, ulong block) + { + int r0 = (int)((block >> 1) & 0x3f); + int g0 = (int)(((block >> 9) & 0x3f) | ((block & 1) << 6)); + int b0 = (int)(((block >> 31) & 1) | ((block >> 15) & 6) | ((block >> 16) & 0x18) | ((block >> 3) & 0x20)); + + int rh = (int)(((block >> 24) & 1) | ((block >> 25) & 0x3e)); + int gh = (int)((block >> 33) & 0x7f); + int bh = (int)(((block >> 43) & 0x1f) | ((block >> 27) & 0x20)); + + int rv = (int)(((block >> 53) & 7) | ((block >> 37) & 0x38)); + int gv = (int)(((block >> 62) & 3) | ((block >> 46) & 0x7c)); + int bv = (int)((block >> 56) & 0x3f); + + r0 = (r0 << 2) | (r0 >> 4); + g0 = (g0 << 1) | (g0 >> 6); + b0 = (b0 << 2) | (b0 >> 4); + + rh = (rh << 2) | (rh >> 4); + gh = (gh << 1) | (gh >> 6); + bh = (bh << 2) | (bh >> 4); + + rv = (rv << 2) | (rv >> 4); + gv = (gv << 1) | (gv >> 6); + bv = (bv << 2) | (bv >> 4); + + for (int y = 0; y < BlockHeight; y++) + { + for (int x = 0; x < BlockWidth; x++) + { + int offset = y * BlockWidth + x; + + byte r = Saturate(((x * (rh - r0)) + (y * (rv - r0)) + (r0 * 4) + 2) >> 2); + byte g = Saturate(((x * (gh - g0)) + (y * (gv - g0)) + (g0 * 4) + 2) >> 2); + byte b = Saturate(((x * (bh - b0)) + (y * (bv - b0)) + (b0 * 4) + 2) >> 2); + + tile[offset] = Pack(r, g, b); + } + } + } + + private static void DecodeBlockETC1( + Span tile, + uint blockLow, + uint blockHigh, + uint r1, + uint g1, + uint b1, + uint r2, + uint g2, + uint b2, + uint alphaMask = 0) + { + int[] table1 = _etc1Lut[(blockLow >> 29) & 7]; + int[] table2 = _etc1Lut[(blockLow >> 26) & 7]; + + bool flip = (blockLow & 0x1000000) != 0; + + if (!flip) + { + for (int y = 0; y < BlockHeight; y++) + { + for (int x = 0; x < BlockWidth / 2; x++) + { + uint color1 = CalculatePixel(r1, g1, b1, x + 0, y, blockHigh, table1, alphaMask); + uint color2 = CalculatePixel(r2, g2, b2, x + 2, y, blockHigh, table2, alphaMask); + + int offset1 = y * BlockWidth + x; + int offset2 = y * BlockWidth + x + 2; + + tile[offset1] = color1; + tile[offset2] = color2; + } + } + } + else + { + for (int y = 0; y < BlockHeight / 2; y++) + { + for (int x = 0; x < BlockWidth; x++) + { + uint color1 = CalculatePixel(r1, g1, b1, x, y + 0, blockHigh, table1, alphaMask); + uint color2 = CalculatePixel(r2, g2, b2, x, y + 2, blockHigh, table2, alphaMask); + + int offset1 = (y * BlockWidth) + x; + int offset2 = ((y + 2) * BlockWidth) + x; + + tile[offset1] = color1; + tile[offset2] = color2; + } + } + } + } + + private static uint CalculatePixel(uint r, uint g, uint b, int x, int y, uint block, int[] table, uint alphaMask) + { + int index = x * BlockHeight + y; + uint msb = block << 1; + uint tableIndex = index < 8 + ? ((block >> (index + 24)) & 1) + ((msb >> (index + 8)) & 2) + : ((block >> (index + 8)) & 1) + ((msb >> (index - 8)) & 2); + + if (alphaMask != 0) + { + if (tableIndex == 0) + { + return Pack(r, g, b) | alphaMask; + } + else if (tableIndex == 2) + { + return 0; + } + else + { + return Pack(r, g, b, table[tableIndex]) | alphaMask; + } + } + + return Pack(r, g, b, table[tableIndex]); + } + + private static uint Pack(uint r, uint g, uint b, int offset) + { + r = Saturate((int)(r + offset)); + g = Saturate((int)(g + offset)); + b = Saturate((int)(b + offset)); + + return Pack(r, g, b); + } + + private static uint Pack(uint r, uint g, uint b) + { + return r | (g << 8) | (b << 16); + } + + private static uint Pack4Be(uint r, uint g, uint b) + { + return (r << 8) | (g << 4) | b; + } + + private static byte Saturate(int value) + { + return value > byte.MaxValue ? byte.MaxValue : value < byte.MinValue ? byte.MinValue : (byte)value; + } + + private static int CalculateOutputSize(int width, int height, int depth, int levels, int layers) + { + int size = 0; + + for (int l = 0; l < levels; l++) + { + size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; + } + + return size; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Encoders/BC7Encoder.cs b/src/Ryujinx.Graphics.Texture/Encoders/BC7Encoder.cs new file mode 100644 index 00000000..5fdc9e91 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Encoders/BC7Encoder.cs @@ -0,0 +1,1005 @@ +using Ryujinx.Graphics.Texture.Utils; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using System.Threading.Tasks; + +namespace Ryujinx.Graphics.Texture.Encoders +{ + static class BC7Encoder + { + private const int MinColorVarianceForModeChange = 160; + + public static void Encode(Memory outputStorage, ReadOnlyMemory data, int width, int height, EncodeMode mode) + { + int widthInBlocks = (width + 3) / 4; + int heightInBlocks = (height + 3) / 4; + + bool fastMode = (mode & EncodeMode.ModeMask) == EncodeMode.Fast; + + if (mode.HasFlag(EncodeMode.Multithreaded)) + { + Parallel.For(0, heightInBlocks, (yInBlocks) => + { + Span output = MemoryMarshal.Cast(outputStorage.Span); + int y = yInBlocks * 4; + + for (int xInBlocks = 0; xInBlocks < widthInBlocks; xInBlocks++) + { + int x = xInBlocks * 4; + Block block = CompressBlock(data.Span, x, y, width, height, fastMode); + + int offset = (yInBlocks * widthInBlocks + xInBlocks) * 2; + output[offset] = block.Low; + output[offset + 1] = block.High; + } + }); + } + else + { + Span output = MemoryMarshal.Cast(outputStorage.Span); + int offset = 0; + + for (int y = 0; y < height; y += 4) + { + for (int x = 0; x < width; x += 4) + { + Block block = CompressBlock(data.Span, x, y, width, height, fastMode); + + output[offset++] = block.Low; + output[offset++] = block.High; + } + } + } + } + + private static readonly int[] _mostFrequentPartitions = new int[] + { + 0, 13, 2, 1, 15, 14, 10, 23, + }; + + private static Block CompressBlock(ReadOnlySpan data, int x, int y, int width, int height, bool fastMode) + { + int w = Math.Min(4, width - x); + int h = Math.Min(4, height - y); + + var dataUint = MemoryMarshal.Cast(data); + + int baseOffset = y * width + x; + + Span tile = stackalloc uint[w * h]; + + for (int ty = 0; ty < h; ty++) + { + int rowOffset = baseOffset + ty * width; + + for (int tx = 0; tx < w; tx++) + { + tile[ty * w + tx] = dataUint[rowOffset + tx]; + } + } + + return fastMode ? EncodeFast(tile, w, h) : EncodeExhaustive(tile, w, h); + } + + private static Block EncodeFast(ReadOnlySpan tile, int w, int h) + { + (RgbaColor8 minColor, RgbaColor8 maxColor) = BC67Utils.GetMinMaxColors(tile, w, h); + + bool alphaNotOne = minColor.A != 255 || maxColor.A != 255; + int variance = BC67Utils.SquaredDifference(minColor.GetColor32(), maxColor.GetColor32()); + int selectedMode; + int indexMode = 0; + + if (alphaNotOne) + { + bool constantAlpha = minColor.A == maxColor.A; + if (constantAlpha) + { + selectedMode = variance > MinColorVarianceForModeChange ? 7 : 6; + } + else + { + if (variance > MinColorVarianceForModeChange) + { + Span uniqueRGB = stackalloc uint[16]; + Span uniqueAlpha = stackalloc uint[16]; + + int uniqueRGBCount = 0; + int uniqueAlphaCount = 0; + + uint rgbMask = new RgbaColor8(255, 255, 255, 0).ToUInt32(); + uint alphaMask = new RgbaColor8(0, 0, 0, 255).ToUInt32(); + + for (int i = 0; i < tile.Length; i++) + { + uint c = tile[i]; + + if (!uniqueRGB[..uniqueRGBCount].Contains(c & rgbMask)) + { + uniqueRGB[uniqueRGBCount++] = c & rgbMask; + } + + if (!uniqueAlpha[..uniqueAlphaCount].Contains(c & alphaMask)) + { + uniqueAlpha[uniqueAlphaCount++] = c & alphaMask; + } + } + + selectedMode = 4; + indexMode = uniqueRGBCount > uniqueAlphaCount ? 1 : 0; + } + else + { + selectedMode = 5; + } + } + } + else + { + if (variance > MinColorVarianceForModeChange) + { + selectedMode = 1; + } + else + { + selectedMode = 6; + } + } + + int selectedPartition = 0; + + if (selectedMode == 1 || selectedMode == 7) + { + int partitionSelectionLowestError = int.MaxValue; + + for (int i = 0; i < _mostFrequentPartitions.Length; i++) + { + int p = _mostFrequentPartitions[i]; + int error = GetEndPointSelectionErrorFast(tile, 2, p, w, h, partitionSelectionLowestError); + if (error < partitionSelectionLowestError) + { + partitionSelectionLowestError = error; + selectedPartition = p; + } + } + } + + return Encode(selectedMode, selectedPartition, 0, indexMode, fastMode: true, tile, w, h, out _); + } + + private static Block EncodeExhaustive(ReadOnlySpan tile, int w, int h) + { + Block bestBlock = default; + int lowestError = int.MaxValue; + int lowestErrorSubsets = int.MaxValue; + + for (int m = 0; m < 8; m++) + { + for (int r = 0; r < (m == 4 || m == 5 ? 4 : 1); r++) + { + for (int im = 0; im < (m == 4 ? 2 : 1); im++) + { + for (int p = 0; p < 1 << BC67Tables.BC7ModeInfos[m].PartitionBitCount; p++) + { + Block block = Encode(m, p, r, im, fastMode: false, tile, w, h, out int maxError); + if (maxError < lowestError || (maxError == lowestError && BC67Tables.BC7ModeInfos[m].SubsetCount < lowestErrorSubsets)) + { + lowestError = maxError; + lowestErrorSubsets = BC67Tables.BC7ModeInfos[m].SubsetCount; + bestBlock = block; + } + } + } + } + } + + return bestBlock; + } + + private static Block Encode( + int mode, + int partition, + int rotation, + int indexMode, + bool fastMode, + ReadOnlySpan tile, + int w, + int h, + out int errorSum) + { + BC7ModeInfo modeInfo = BC67Tables.BC7ModeInfos[mode]; + int subsetCount = modeInfo.SubsetCount; + int partitionBitCount = modeInfo.PartitionBitCount; + int rotationBitCount = modeInfo.RotationBitCount; + int indexModeBitCount = modeInfo.IndexModeBitCount; + int colorDepth = modeInfo.ColorDepth; + int alphaDepth = modeInfo.AlphaDepth; + int pBits = modeInfo.PBits; + int colorIndexBitCount = modeInfo.ColorIndexBitCount; + int alphaIndexBitCount = modeInfo.AlphaIndexBitCount; + bool separateAlphaIndices = alphaIndexBitCount != 0; + + uint alphaMask; + + if (separateAlphaIndices) + { + alphaMask = rotation switch + { + 1 => new RgbaColor8(255, 0, 0, 0).ToUInt32(), + 2 => new RgbaColor8(0, 255, 0, 0).ToUInt32(), + 3 => new RgbaColor8(0, 0, 255, 0).ToUInt32(), + _ => new RgbaColor8(0, 0, 0, 255).ToUInt32(), + }; + } + else + { + alphaMask = new RgbaColor8(0, 0, 0, 0).ToUInt32(); + } + + if (indexMode != 0) + { + alphaMask = ~alphaMask; + } + + // + // Select color palette. + // + + Span endPoints0 = stackalloc uint[subsetCount]; + Span endPoints1 = stackalloc uint[subsetCount]; + + SelectEndPoints( + tile, + w, + h, + endPoints0, + endPoints1, + subsetCount, + partition, + colorIndexBitCount, + colorDepth, + alphaDepth, + ~alphaMask, + fastMode); + + if (separateAlphaIndices) + { + SelectEndPoints( + tile, + w, + h, + endPoints0, + endPoints1, + subsetCount, + partition, + alphaIndexBitCount, + colorDepth, + alphaDepth, + alphaMask, + fastMode); + } + + Span pBitValues = stackalloc int[pBits]; + + for (int i = 0; i < pBits; i++) + { + int pBit; + + if (pBits == subsetCount) + { + pBit = GetPBit(endPoints0[i], endPoints1[i], colorDepth, alphaDepth); + } + else + { + int subset = i >> 1; + uint color = (i & 1) == 0 ? endPoints0[subset] : endPoints1[subset]; + pBit = GetPBit(color, colorDepth, alphaDepth); + } + + pBitValues[i] = pBit; + } + + int colorIndexCount = 1 << colorIndexBitCount; + int alphaIndexCount = 1 << alphaIndexBitCount; + + Span colorIndices = stackalloc byte[16]; + Span alphaIndices = stackalloc byte[16]; + + errorSum = BC67Utils.SelectIndices( + tile, + w, + h, + endPoints0, + endPoints1, + pBitValues, + colorIndices, + subsetCount, + partition, + colorIndexBitCount, + colorIndexCount, + colorDepth, + alphaDepth, + pBits, + alphaMask); + + if (separateAlphaIndices) + { + errorSum += BC67Utils.SelectIndices( + tile, + w, + h, + endPoints0, + endPoints1, + pBitValues, + alphaIndices, + subsetCount, + partition, + alphaIndexBitCount, + alphaIndexCount, + colorDepth, + alphaDepth, + pBits, + ~alphaMask); + } + + Span colorSwapSubset = stackalloc bool[3]; + + for (int i = 0; i < 3; i++) + { + colorSwapSubset[i] = colorIndices[BC67Tables.FixUpIndices[subsetCount - 1][partition][i]] >= (colorIndexCount >> 1); + } + + bool alphaSwapSubset = alphaIndices[0] >= (alphaIndexCount >> 1); + + Block block = new(); + + int offset = 0; + + block.Encode(1UL << mode, ref offset, mode + 1); + block.Encode((ulong)partition, ref offset, partitionBitCount); + block.Encode((ulong)rotation, ref offset, rotationBitCount); + block.Encode((ulong)indexMode, ref offset, indexModeBitCount); + + for (int comp = 0; comp < 3; comp++) + { + int rotatedComp = comp; + + if (((comp + 1) & 3) == rotation) + { + rotatedComp = 3; + } + + for (int subset = 0; subset < subsetCount; subset++) + { + RgbaColor8 color0 = RgbaColor8.FromUInt32(endPoints0[subset]); + RgbaColor8 color1 = RgbaColor8.FromUInt32(endPoints1[subset]); + + int pBit0 = -1, pBit1 = -1; + + if (pBits == subsetCount) + { + pBit0 = pBit1 = pBitValues[subset]; + } + else if (pBits != 0) + { + pBit0 = pBitValues[subset * 2]; + pBit1 = pBitValues[subset * 2 + 1]; + } + + if (indexMode == 0 ? colorSwapSubset[subset] : alphaSwapSubset) + { + block.Encode(BC67Utils.QuantizeComponent(color1.GetComponent(rotatedComp), colorDepth, pBit1), ref offset, colorDepth); + block.Encode(BC67Utils.QuantizeComponent(color0.GetComponent(rotatedComp), colorDepth, pBit0), ref offset, colorDepth); + } + else + { + block.Encode(BC67Utils.QuantizeComponent(color0.GetComponent(rotatedComp), colorDepth, pBit0), ref offset, colorDepth); + block.Encode(BC67Utils.QuantizeComponent(color1.GetComponent(rotatedComp), colorDepth, pBit1), ref offset, colorDepth); + } + } + } + + if (alphaDepth != 0) + { + int rotatedComp = (rotation - 1) & 3; + + for (int subset = 0; subset < subsetCount; subset++) + { + RgbaColor8 color0 = RgbaColor8.FromUInt32(endPoints0[subset]); + RgbaColor8 color1 = RgbaColor8.FromUInt32(endPoints1[subset]); + + int pBit0 = -1, pBit1 = -1; + + if (pBits == subsetCount) + { + pBit0 = pBit1 = pBitValues[subset]; + } + else if (pBits != 0) + { + pBit0 = pBitValues[subset * 2]; + pBit1 = pBitValues[subset * 2 + 1]; + } + + if (separateAlphaIndices && indexMode == 0 ? alphaSwapSubset : colorSwapSubset[subset]) + { + block.Encode(BC67Utils.QuantizeComponent(color1.GetComponent(rotatedComp), alphaDepth, pBit1), ref offset, alphaDepth); + block.Encode(BC67Utils.QuantizeComponent(color0.GetComponent(rotatedComp), alphaDepth, pBit0), ref offset, alphaDepth); + } + else + { + block.Encode(BC67Utils.QuantizeComponent(color0.GetComponent(rotatedComp), alphaDepth, pBit0), ref offset, alphaDepth); + block.Encode(BC67Utils.QuantizeComponent(color1.GetComponent(rotatedComp), alphaDepth, pBit1), ref offset, alphaDepth); + } + } + } + + for (int i = 0; i < pBits; i++) + { + block.Encode((ulong)pBitValues[i], ref offset, 1); + } + + byte[] fixUpTable = BC67Tables.FixUpIndices[subsetCount - 1][partition]; + + for (int i = 0; i < 16; i++) + { + int subset = BC67Tables.PartitionTable[subsetCount - 1][partition][i]; + byte index = colorIndices[i]; + + if (colorSwapSubset[subset]) + { + index = (byte)(index ^ (colorIndexCount - 1)); + } + + int finalIndexBitCount = i == fixUpTable[subset] ? colorIndexBitCount - 1 : colorIndexBitCount; + + Debug.Assert(index < (1 << finalIndexBitCount)); + + block.Encode(index, ref offset, finalIndexBitCount); + } + + if (separateAlphaIndices) + { + for (int i = 0; i < 16; i++) + { + byte index = alphaIndices[i]; + + if (alphaSwapSubset) + { + index = (byte)(index ^ (alphaIndexCount - 1)); + } + + int finalIndexBitCount = i == 0 ? alphaIndexBitCount - 1 : alphaIndexBitCount; + + Debug.Assert(index < (1 << finalIndexBitCount)); + + block.Encode(index, ref offset, finalIndexBitCount); + } + } + + return block; + } + + private static unsafe int GetEndPointSelectionErrorFast(ReadOnlySpan tile, int subsetCount, int partition, int w, int h, int maxError) + { + byte[] partitionTable = BC67Tables.PartitionTable[subsetCount - 1][partition]; + + Span minColors = stackalloc RgbaColor8[subsetCount]; + Span maxColors = stackalloc RgbaColor8[subsetCount]; + + BC67Utils.GetMinMaxColors(partitionTable, tile, w, h, minColors, maxColors, subsetCount); + + Span endPoints0 = stackalloc uint[subsetCount]; + Span endPoints1 = stackalloc uint[subsetCount]; + + SelectEndPointsFast(partitionTable, tile, w, h, subsetCount, minColors, maxColors, endPoints0, endPoints1, uint.MaxValue); + + Span palette = stackalloc RgbaColor32[8]; + + int errorSum = 0; + + for (int subset = 0; subset < subsetCount; subset++) + { + RgbaColor32 blockDir = maxColors[subset].GetColor32() - minColors[subset].GetColor32(); + int sum = blockDir.R + blockDir.G + blockDir.B + blockDir.A; + if (sum != 0) + { + blockDir = (blockDir << 6) / new RgbaColor32(sum); + } + + uint c0 = endPoints0[subset]; + uint c1 = endPoints1[subset]; + + int pBit0 = GetPBit(c0, 6, 0); + int pBit1 = GetPBit(c1, 6, 0); + + c0 = BC67Utils.Quantize(RgbaColor8.FromUInt32(c0), 6, 0, pBit0).ToUInt32(); + c1 = BC67Utils.Quantize(RgbaColor8.FromUInt32(c1), 6, 0, pBit1).ToUInt32(); + + if (Sse41.IsSupported) + { + Vector128 c0Rep = Vector128.Create(c0).AsByte(); + Vector128 c1Rep = Vector128.Create(c1).AsByte(); + + Vector128 c0c1 = Sse2.UnpackLow(c0Rep, c1Rep); + + Vector128 rWeights; + Vector128 lWeights; + + fixed (byte* pWeights = BC67Tables.Weights[1], pInvWeights = BC67Tables.InverseWeights[1]) + { + rWeights = Sse2.LoadScalarVector128((ulong*)pWeights).AsByte(); + lWeights = Sse2.LoadScalarVector128((ulong*)pInvWeights).AsByte(); + } + + Vector128 iWeights = Sse2.UnpackLow(rWeights, lWeights); + Vector128 iWeights01 = Sse2.UnpackLow(iWeights.AsInt16(), iWeights.AsInt16()).AsByte(); + Vector128 iWeights23 = Sse2.UnpackHigh(iWeights.AsInt16(), iWeights.AsInt16()).AsByte(); + Vector128 iWeights0 = Sse2.UnpackLow(iWeights01.AsInt16(), iWeights01.AsInt16()).AsByte(); + Vector128 iWeights1 = Sse2.UnpackHigh(iWeights01.AsInt16(), iWeights01.AsInt16()).AsByte(); + Vector128 iWeights2 = Sse2.UnpackLow(iWeights23.AsInt16(), iWeights23.AsInt16()).AsByte(); + Vector128 iWeights3 = Sse2.UnpackHigh(iWeights23.AsInt16(), iWeights23.AsInt16()).AsByte(); + + static Vector128 ShiftRoundToNearest(Vector128 x) + { + return Sse2.ShiftRightLogical(Sse2.Add(x, Vector128.Create((short)32)), 6); + } + + Vector128 pal0 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights0.AsSByte())); + Vector128 pal1 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights1.AsSByte())); + Vector128 pal2 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights2.AsSByte())); + Vector128 pal3 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights3.AsSByte())); + + for (int i = 0; i < tile.Length; i++) + { + if (partitionTable[i] != subset) + { + continue; + } + + uint c = tile[i]; + + Vector128 color = Sse41.ConvertToVector128Int16(Vector128.Create(c).AsByte()); + + Vector128 delta0 = Sse2.Subtract(color, pal0); + Vector128 delta1 = Sse2.Subtract(color, pal1); + Vector128 delta2 = Sse2.Subtract(color, pal2); + Vector128 delta3 = Sse2.Subtract(color, pal3); + + Vector128 deltaSum0 = Sse2.MultiplyAddAdjacent(delta0, delta0); + Vector128 deltaSum1 = Sse2.MultiplyAddAdjacent(delta1, delta1); + Vector128 deltaSum2 = Sse2.MultiplyAddAdjacent(delta2, delta2); + Vector128 deltaSum3 = Sse2.MultiplyAddAdjacent(delta3, delta3); + + Vector128 deltaSum01 = Ssse3.HorizontalAdd(deltaSum0, deltaSum1); + Vector128 deltaSum23 = Ssse3.HorizontalAdd(deltaSum2, deltaSum3); + + Vector128 delta = Sse41.PackUnsignedSaturate(deltaSum01, deltaSum23); + + Vector128 min = Sse41.MinHorizontal(delta); + + errorSum += min.GetElement(0); + } + } + else + { + RgbaColor32 e032 = RgbaColor8.FromUInt32(c0).GetColor32(); + RgbaColor32 e132 = RgbaColor8.FromUInt32(c1).GetColor32(); + + palette[0] = e032; + palette[^1] = e132; + + for (int i = 1; i < palette.Length - 1; i++) + { + palette[i] = BC67Utils.Interpolate(e032, e132, i, 3); + } + + for (int i = 0; i < tile.Length; i++) + { + if (partitionTable[i] != subset) + { + continue; + } + + uint c = tile[i]; + RgbaColor32 color = Unsafe.As(ref c).GetColor32(); + + int bestMatchScore = int.MaxValue; + + for (int j = 0; j < palette.Length; j++) + { + int score = BC67Utils.SquaredDifference(color, palette[j]); + + if (score < bestMatchScore) + { + bestMatchScore = score; + } + } + + errorSum += bestMatchScore; + } + } + + // No point in continuing if we are already above maximum. + if (errorSum >= maxError) + { + return int.MaxValue; + } + } + + return errorSum; + } + + private static void SelectEndPoints( + ReadOnlySpan tile, + int w, + int h, + Span endPoints0, + Span endPoints1, + int subsetCount, + int partition, + int indexBitCount, + int colorDepth, + int alphaDepth, + uint writeMask, + bool fastMode) + { + byte[] partitionTable = BC67Tables.PartitionTable[subsetCount - 1][partition]; + + Span minColors = stackalloc RgbaColor8[subsetCount]; + Span maxColors = stackalloc RgbaColor8[subsetCount]; + + BC67Utils.GetMinMaxColors(partitionTable, tile, w, h, minColors, maxColors, subsetCount); + + uint inverseMask = ~writeMask; + + for (int i = 0; i < subsetCount; i++) + { + Unsafe.As(ref minColors[i]) |= inverseMask; + Unsafe.As(ref maxColors[i]) |= inverseMask; + } + + if (fastMode) + { + SelectEndPointsFast(partitionTable, tile, w, h, subsetCount, minColors, maxColors, endPoints0, endPoints1, writeMask); + } + else + { + Span colors = stackalloc RgbaColor8[subsetCount * 16]; + Span counts = stackalloc byte[subsetCount]; + + int i = 0; + for (int ty = 0; ty < h; ty++) + { + for (int tx = 0; tx < w; tx++) + { + int subset = partitionTable[ty * 4 + tx]; + RgbaColor8 color = RgbaColor8.FromUInt32(tile[i++] | inverseMask); + + static void AddIfNew(Span values, RgbaColor8 value, int subset, ref byte count) + { + for (int i = 0; i < count; i++) + { + if (values[subset * 16 + i] == value) + { + return; + } + } + + values[subset * 16 + count++] = value; + } + + AddIfNew(colors, color, subset, ref counts[subset]); + } + } + + for (int subset = 0; subset < subsetCount; subset++) + { + int offset = subset * 16; + + RgbaColor8 minColor = minColors[subset]; + RgbaColor8 maxColor = maxColors[subset]; + + ReadOnlySpan subsetColors = colors.Slice(offset, counts[subset]); + + (RgbaColor8 e0, RgbaColor8 e1) = SelectEndPoints(subsetColors, minColor, maxColor, indexBitCount, colorDepth, alphaDepth, inverseMask); + + endPoints0[subset] = (endPoints0[subset] & inverseMask) | (e0.ToUInt32() & writeMask); + endPoints1[subset] = (endPoints1[subset] & inverseMask) | (e1.ToUInt32() & writeMask); + } + } + } + + private static unsafe void SelectEndPointsFast( + ReadOnlySpan partitionTable, + ReadOnlySpan tile, + int w, + int h, + int subsetCount, + ReadOnlySpan minColors, + ReadOnlySpan maxColors, + Span endPoints0, + Span endPoints1, + uint writeMask) + { + uint inverseMask = ~writeMask; + + if (Sse41.IsSupported && w == 4 && h == 4) + { + Vector128 row0, row1, row2, row3; + Vector128 ones = Vector128.AllBitsSet; + + fixed (uint* pTile = tile) + { + row0 = Sse2.LoadVector128(pTile).AsByte(); + row1 = Sse2.LoadVector128(pTile + 4).AsByte(); + row2 = Sse2.LoadVector128(pTile + 8).AsByte(); + row3 = Sse2.LoadVector128(pTile + 12).AsByte(); + } + + Vector128 partitionMask; + + fixed (byte* pPartitionTable = partitionTable) + { + partitionMask = Sse2.LoadVector128(pPartitionTable); + } + + for (int subset = 0; subset < subsetCount; subset++) + { + RgbaColor32 blockDir = maxColors[subset].GetColor32() - minColors[subset].GetColor32(); + int sum = blockDir.R + blockDir.G + blockDir.B + blockDir.A; + if (sum != 0) + { + blockDir = (blockDir << 6) / new RgbaColor32(sum); + } + + Vector128 bd = Vector128.Create(blockDir.GetColor8().ToUInt32()).AsByte(); + + Vector128 delta0 = Ssse3.MultiplyAddAdjacent(row0, bd.AsSByte()); + Vector128 delta1 = Ssse3.MultiplyAddAdjacent(row1, bd.AsSByte()); + Vector128 delta2 = Ssse3.MultiplyAddAdjacent(row2, bd.AsSByte()); + Vector128 delta3 = Ssse3.MultiplyAddAdjacent(row3, bd.AsSByte()); + + Vector128 delta01 = Ssse3.HorizontalAdd(delta0, delta1); + Vector128 delta23 = Ssse3.HorizontalAdd(delta2, delta3); + + Vector128 subsetMask = Sse2.Xor(Sse2.CompareEqual(partitionMask, Vector128.Create((byte)subset)), ones.AsByte()); + + Vector128 subsetMask01 = Sse2.UnpackLow(subsetMask, subsetMask).AsInt16(); + Vector128 subsetMask23 = Sse2.UnpackHigh(subsetMask, subsetMask).AsInt16(); + + Vector128 min01 = Sse41.MinHorizontal(Sse2.Or(delta01, subsetMask01).AsUInt16()); + Vector128 min23 = Sse41.MinHorizontal(Sse2.Or(delta23, subsetMask23).AsUInt16()); + Vector128 max01 = Sse41.MinHorizontal(Sse2.Xor(Sse2.AndNot(subsetMask01, delta01), ones).AsUInt16()); + Vector128 max23 = Sse41.MinHorizontal(Sse2.Xor(Sse2.AndNot(subsetMask23, delta23), ones).AsUInt16()); + + uint minPos01 = min01.AsUInt32().GetElement(0); + uint minPos23 = min23.AsUInt32().GetElement(0); + uint maxPos01 = max01.AsUInt32().GetElement(0); + uint maxPos23 = max23.AsUInt32().GetElement(0); + + uint minDistColor = (ushort)minPos23 < (ushort)minPos01 + ? tile[(int)(minPos23 >> 16) + 8] + : tile[(int)(minPos01 >> 16)]; + + // Note that we calculate the maximum as the minimum of the inverse, so less here is actually greater. + uint maxDistColor = (ushort)maxPos23 < (ushort)maxPos01 + ? tile[(int)(maxPos23 >> 16) + 8] + : tile[(int)(maxPos01 >> 16)]; + + endPoints0[subset] = (endPoints0[subset] & inverseMask) | (minDistColor & writeMask); + endPoints1[subset] = (endPoints1[subset] & inverseMask) | (maxDistColor & writeMask); + } + } + else + { + for (int subset = 0; subset < subsetCount; subset++) + { + RgbaColor32 blockDir = maxColors[subset].GetColor32() - minColors[subset].GetColor32(); + blockDir = RgbaColor32.DivideGuarded(blockDir << 6, new RgbaColor32(blockDir.R + blockDir.G + blockDir.B + blockDir.A), 0); + + int minDist = int.MaxValue; + int maxDist = int.MinValue; + + RgbaColor8 minDistColor = default; + RgbaColor8 maxDistColor = default; + + int i = 0; + for (int ty = 0; ty < h; ty++) + { + for (int tx = 0; tx < w; tx++, i++) + { + if (partitionTable[ty * 4 + tx] != subset) + { + continue; + } + + RgbaColor8 color = RgbaColor8.FromUInt32(tile[i]); + int dist = RgbaColor32.Dot(color.GetColor32(), blockDir); + + if (minDist > dist) + { + minDist = dist; + minDistColor = color; + } + + if (maxDist < dist) + { + maxDist = dist; + maxDistColor = color; + } + } + } + + endPoints0[subset] = (endPoints0[subset] & inverseMask) | (minDistColor.ToUInt32() & writeMask); + endPoints1[subset] = (endPoints1[subset] & inverseMask) | (maxDistColor.ToUInt32() & writeMask); + } + } + } + + private static (RgbaColor8, RgbaColor8) SelectEndPoints( + ReadOnlySpan values, + RgbaColor8 minValue, + RgbaColor8 maxValue, + int indexBitCount, + int colorDepth, + int alphaDepth, + uint alphaMask) + { + int n = values.Length; + int numInterpolatedColors = 1 << indexBitCount; + int numInterpolatedColorsMinus1 = numInterpolatedColors - 1; + + if (n == 0) + { + return (default, default); + } + + minValue = BC67Utils.Quantize(minValue, colorDepth, alphaDepth); + maxValue = BC67Utils.Quantize(maxValue, colorDepth, alphaDepth); + + RgbaColor32 blockDir = maxValue.GetColor32() - minValue.GetColor32(); + blockDir = RgbaColor32.DivideGuarded(blockDir << 6, new RgbaColor32(blockDir.R + blockDir.G + blockDir.B + blockDir.A), 0); + + int minDist = int.MaxValue; + int maxDist = 0; + + for (int i = 0; i < values.Length; i++) + { + RgbaColor8 color = values[i]; + int dist = RgbaColor32.Dot(BC67Utils.Quantize(color, colorDepth, alphaDepth).GetColor32(), blockDir); + + if (minDist >= dist) + { + minDist = dist; + } + + if (maxDist <= dist) + { + maxDist = dist; + } + } + + Span palette = stackalloc RgbaColor8[numInterpolatedColors]; + + int distRange = Math.Max(1, maxDist - minDist); + + RgbaColor32 nV = new(n); + + int bestErrorSum = int.MaxValue; + RgbaColor8 bestE0 = default; + RgbaColor8 bestE1 = default; + + Span indices = stackalloc int[n]; + Span colors = stackalloc RgbaColor32[n]; + + for (int maxIndex = numInterpolatedColorsMinus1; maxIndex >= 1; maxIndex--) + { + int sumX = 0; + int sumXX = 0; + int sumXXIncrement = 0; + + for (int i = 0; i < values.Length; i++) + { + RgbaColor32 color = values[i].GetColor32(); + + int dist = RgbaColor32.Dot(color, blockDir); + + int normalizedValue = ((dist - minDist) << 6) / distRange; + int texelIndex = (normalizedValue * maxIndex + 32) >> 6; + + indices[i] = texelIndex; + colors[i] = color; + + sumX += texelIndex; + sumXX += texelIndex * texelIndex; + sumXXIncrement += 1 + texelIndex * 2; + } + + for (int start = 0; start < numInterpolatedColors - maxIndex; start++) + { + RgbaColor32 sumY = new(0); + RgbaColor32 sumXY = new(0); + + for (int i = 0; i < indices.Length; i++) + { + RgbaColor32 y = colors[i]; + + sumY += y; + sumXY += new RgbaColor32(start + indices[i]) * y; + } + + RgbaColor32 sumXV = new(sumX); + RgbaColor32 sumXXV = new(sumXX); + RgbaColor32 m = RgbaColor32.DivideGuarded((nV * sumXY - sumXV * sumY) << 6, nV * sumXXV - sumXV * sumXV, 0); + RgbaColor32 b = ((sumY << 6) - m * sumXV) / nV; + + RgbaColor8 candidateE0 = (b >> 6).GetColor8(); + RgbaColor8 candidateE1 = ((b + m * new RgbaColor32(numInterpolatedColorsMinus1)) >> 6).GetColor8(); + + int pBit0 = GetPBit(candidateE0.ToUInt32(), colorDepth, alphaDepth); + int pBit1 = GetPBit(candidateE1.ToUInt32(), colorDepth, alphaDepth); + + int errorSum = BC67Utils.SelectIndices( + MemoryMarshal.Cast(values), + candidateE0.ToUInt32(), + candidateE1.ToUInt32(), + pBit0, + pBit1, + indexBitCount, + numInterpolatedColors, + colorDepth, + alphaDepth, + alphaMask); + + if (errorSum <= bestErrorSum) + { + bestErrorSum = errorSum; + bestE0 = candidateE0; + bestE1 = candidateE1; + } + + sumX += n; + sumXX += sumXXIncrement; + sumXXIncrement += 2 * n; + } + } + + return (bestE0, bestE1); + } + + private static int GetPBit(uint color, int colorDepth, int alphaDepth) + { + uint mask = 0x808080u >> colorDepth; + + if (alphaDepth != 0) + { + // If alpha is 0, let's assume the color information is not too important and prefer + // to preserve alpha instead. + if ((color >> 24) == 0) + { + return 0; + } + + mask |= 0x80000000u >> alphaDepth; + } + + color &= 0x7f7f7f7fu; + color += mask >> 1; + + int onesCount = BitOperations.PopCount(color & mask); + return onesCount >= 2 ? 1 : 0; + } + + private static int GetPBit(uint c0, uint c1, int colorDepth, int alphaDepth) + { + // Giving preference to the first endpoint yields better results, + // might be a side effect of the endpoint selection algorithm? + return GetPBit(c0, colorDepth, alphaDepth); + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Encoders/EncodeMode.cs b/src/Ryujinx.Graphics.Texture/Encoders/EncodeMode.cs new file mode 100644 index 00000000..af2317e7 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Encoders/EncodeMode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Texture.Encoders +{ + enum EncodeMode + { + Fast, + Exhaustive, + ModeMask = 0xff, + Multithreaded = 1 << 8, + } +} diff --git a/src/Ryujinx.Graphics.Texture/LayoutConverter.cs b/src/Ryujinx.Graphics.Texture/LayoutConverter.cs new file mode 100644 index 00000000..5426af20 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/LayoutConverter.cs @@ -0,0 +1,593 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using System; +using System.Runtime.Intrinsics; +using static Ryujinx.Graphics.Texture.BlockLinearConstants; + +namespace Ryujinx.Graphics.Texture +{ + public static class LayoutConverter + { + public const int HostStrideAlignment = 4; + + public static void ConvertBlockLinearToLinear( + Span dst, + int width, + int height, + int stride, + int bytesPerPixel, + int gobBlocksInY, + ReadOnlySpan data) + { + int gobHeight = gobBlocksInY * GobHeight; + + int strideTrunc = BitUtils.AlignDown(width * bytesPerPixel, 16); + int strideTrunc64 = BitUtils.AlignDown(width * bytesPerPixel, 64); + + int xStart = strideTrunc / bytesPerPixel; + + int outStrideGap = stride - width * bytesPerPixel; + + int alignment = GobStride / bytesPerPixel; + + int wAligned = BitUtils.AlignUp(width, alignment); + + BlockLinearLayout layoutConverter = new(wAligned, height, gobBlocksInY, 1, bytesPerPixel); + + unsafe bool Convert(Span output, ReadOnlySpan data) where T : unmanaged + { + fixed (byte* outputPtr = output, dataPtr = data) + { + byte* outPtr = outputPtr; + + for (int y = 0; y < height; y++) + { + layoutConverter.SetY(y); + + for (int x = 0; x < strideTrunc64; x += 64, outPtr += 64) + { + byte* offset = dataPtr + layoutConverter.GetOffsetWithLineOffset64(x); + byte* offset2 = offset + 0x20; + byte* offset3 = offset + 0x100; + byte* offset4 = offset + 0x120; + + Vector128 value = *(Vector128*)offset; + Vector128 value2 = *(Vector128*)offset2; + Vector128 value3 = *(Vector128*)offset3; + Vector128 value4 = *(Vector128*)offset4; + + *(Vector128*)outPtr = value; + *(Vector128*)(outPtr + 16) = value2; + *(Vector128*)(outPtr + 32) = value3; + *(Vector128*)(outPtr + 48) = value4; + } + + for (int x = strideTrunc64; x < strideTrunc; x += 16, outPtr += 16) + { + byte* offset = dataPtr + layoutConverter.GetOffsetWithLineOffset16(x); + + *(Vector128*)outPtr = *(Vector128*)offset; + } + + for (int x = xStart; x < width; x++, outPtr += bytesPerPixel) + { + byte* offset = dataPtr + layoutConverter.GetOffset(x); + + *(T*)outPtr = *(T*)offset; + } + + outPtr += outStrideGap; + } + } + return true; + } + + bool _ = bytesPerPixel switch + { + 1 => Convert(dst, data), + 2 => Convert(dst, data), + 4 => Convert(dst, data), + 8 => Convert(dst, data), + 12 => Convert(dst, data), + 16 => Convert>(dst, data), + _ => throw new NotSupportedException($"Unable to convert ${bytesPerPixel} bpp pixel format."), + }; + } + + public static MemoryOwner ConvertBlockLinearToLinear( + int width, + int height, + int depth, + int sliceDepth, + int levels, + int layers, + int blockWidth, + int blockHeight, + int bytesPerPixel, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX, + SizeInfo sizeInfo, + ReadOnlySpan data) + { + int outSize = GetTextureSize( + width, + height, + sliceDepth, + levels, + layers, + blockWidth, + blockHeight, + bytesPerPixel); + + MemoryOwner outputOwner = MemoryOwner.Rent(outSize); + Span output = outputOwner.Span; + + int outOffs = 0; + + int mipGobBlocksInY = gobBlocksInY; + int mipGobBlocksInZ = gobBlocksInZ; + + int gobWidth = (GobStride / bytesPerPixel) * gobBlocksInTileX; + int gobHeight = gobBlocksInY * GobHeight; + + for (int level = 0; level < levels; level++) + { + int w = Math.Max(1, width >> level); + int h = Math.Max(1, height >> level); + int d = Math.Max(1, depth >> level); + + w = BitUtils.DivRoundUp(w, blockWidth); + h = BitUtils.DivRoundUp(h, blockHeight); + + while (h <= (mipGobBlocksInY >> 1) * GobHeight && mipGobBlocksInY != 1) + { + mipGobBlocksInY >>= 1; + } + + if (level > 0 && d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1) + { + mipGobBlocksInZ >>= 1; + } + + int strideTrunc = BitUtils.AlignDown(w * bytesPerPixel, 16); + int strideTrunc64 = BitUtils.AlignDown(w * bytesPerPixel, 64); + + int xStart = strideTrunc / bytesPerPixel; + + int stride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + + int outStrideGap = stride - w * bytesPerPixel; + + int alignment = gobWidth; + + if (d < gobBlocksInZ || w <= gobWidth || h <= gobHeight) + { + alignment = GobStride / bytesPerPixel; + } + + int wAligned = BitUtils.AlignUp(w, alignment); + + BlockLinearLayout layoutConverter = new( + wAligned, + h, + mipGobBlocksInY, + mipGobBlocksInZ, + bytesPerPixel); + + int sd = Math.Max(1, sliceDepth >> level); + + unsafe bool Convert(Span output, ReadOnlySpan data) where T : unmanaged + { + fixed (byte* outputPtr = output, dataPtr = data) + { + byte* outPtr = outputPtr + outOffs; + for (int layer = 0; layer < layers; layer++) + { + byte* inBaseOffset = dataPtr + (layer * sizeInfo.LayerSize + sizeInfo.GetMipOffset(level)); + + for (int z = 0; z < sd; z++) + { + layoutConverter.SetZ(z); + for (int y = 0; y < h; y++) + { + layoutConverter.SetY(y); + + for (int x = 0; x < strideTrunc64; x += 64, outPtr += 64) + { + byte* offset = inBaseOffset + layoutConverter.GetOffsetWithLineOffset64(x); + byte* offset2 = offset + 0x20; + byte* offset3 = offset + 0x100; + byte* offset4 = offset + 0x120; + + Vector128 value = *(Vector128*)offset; + Vector128 value2 = *(Vector128*)offset2; + Vector128 value3 = *(Vector128*)offset3; + Vector128 value4 = *(Vector128*)offset4; + + *(Vector128*)outPtr = value; + *(Vector128*)(outPtr + 16) = value2; + *(Vector128*)(outPtr + 32) = value3; + *(Vector128*)(outPtr + 48) = value4; + } + + for (int x = strideTrunc64; x < strideTrunc; x += 16, outPtr += 16) + { + byte* offset = inBaseOffset + layoutConverter.GetOffsetWithLineOffset16(x); + + *(Vector128*)outPtr = *(Vector128*)offset; + } + + for (int x = xStart; x < w; x++, outPtr += bytesPerPixel) + { + byte* offset = inBaseOffset + layoutConverter.GetOffset(x); + + *(T*)outPtr = *(T*)offset; + } + + outPtr += outStrideGap; + } + } + } + outOffs += stride * h * d * layers; + } + return true; + } + + bool _ = bytesPerPixel switch + { + 1 => Convert(output, data), + 2 => Convert(output, data), + 4 => Convert(output, data), + 8 => Convert(output, data), + 12 => Convert(output, data), + 16 => Convert>(output, data), + _ => throw new NotSupportedException($"Unable to convert ${bytesPerPixel} bpp pixel format."), + }; + } + return outputOwner; + } + + public static MemoryOwner ConvertLinearStridedToLinear( + int width, + int height, + int blockWidth, + int blockHeight, + int lineSize, + int stride, + int bytesPerPixel, + ReadOnlySpan data) + { + int w = BitUtils.DivRoundUp(width, blockWidth); + int h = BitUtils.DivRoundUp(height, blockHeight); + + int outStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + lineSize = Math.Min(lineSize, outStride); + + MemoryOwner output = MemoryOwner.Rent(h * outStride); + Span outSpan = output.Span; + + int outOffs = 0; + int inOffs = 0; + + for (int y = 0; y < h; y++) + { + data.Slice(inOffs, lineSize).CopyTo(outSpan.Slice(outOffs, lineSize)); + + inOffs += stride; + outOffs += outStride; + } + + return output; + } + + public static void ConvertLinearToBlockLinear( + Span dst, + int width, + int height, + int stride, + int bytesPerPixel, + int gobBlocksInY, + ReadOnlySpan data) + { + int gobHeight = gobBlocksInY * GobHeight; + + int strideTrunc = BitUtils.AlignDown(width * bytesPerPixel, 16); + int strideTrunc64 = BitUtils.AlignDown(width * bytesPerPixel, 64); + + int xStart = strideTrunc / bytesPerPixel; + + int inStrideGap = stride - width * bytesPerPixel; + + int alignment = GobStride / bytesPerPixel; + + int wAligned = BitUtils.AlignUp(width, alignment); + + BlockLinearLayout layoutConverter = new(wAligned, height, gobBlocksInY, 1, bytesPerPixel); + + unsafe bool Convert(Span output, ReadOnlySpan data) where T : unmanaged + { + fixed (byte* outputPtr = output, dataPtr = data) + { + byte* inPtr = dataPtr; + + for (int y = 0; y < height; y++) + { + layoutConverter.SetY(y); + + for (int x = 0; x < strideTrunc64; x += 64, inPtr += 64) + { + byte* offset = outputPtr + layoutConverter.GetOffsetWithLineOffset64(x); + byte* offset2 = offset + 0x20; + byte* offset3 = offset + 0x100; + byte* offset4 = offset + 0x120; + + Vector128 value = *(Vector128*)inPtr; + Vector128 value2 = *(Vector128*)(inPtr + 16); + Vector128 value3 = *(Vector128*)(inPtr + 32); + Vector128 value4 = *(Vector128*)(inPtr + 48); + + *(Vector128*)offset = value; + *(Vector128*)offset2 = value2; + *(Vector128*)offset3 = value3; + *(Vector128*)offset4 = value4; + } + + for (int x = strideTrunc64; x < strideTrunc; x += 16, inPtr += 16) + { + byte* offset = outputPtr + layoutConverter.GetOffsetWithLineOffset16(x); + + *(Vector128*)offset = *(Vector128*)inPtr; + } + + for (int x = xStart; x < width; x++, inPtr += bytesPerPixel) + { + byte* offset = outputPtr + layoutConverter.GetOffset(x); + + *(T*)offset = *(T*)inPtr; + } + + inPtr += inStrideGap; + } + } + return true; + } + + bool _ = bytesPerPixel switch + { + 1 => Convert(dst, data), + 2 => Convert(dst, data), + 4 => Convert(dst, data), + 8 => Convert(dst, data), + 12 => Convert(dst, data), + 16 => Convert>(dst, data), + _ => throw new NotSupportedException($"Unable to convert ${bytesPerPixel} bpp pixel format."), + }; + } + + public static ReadOnlySpan ConvertLinearToBlockLinear( + Span output, + int width, + int height, + int depth, + int sliceDepth, + int levels, + int layers, + int blockWidth, + int blockHeight, + int bytesPerPixel, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX, + SizeInfo sizeInfo, + ReadOnlySpan data) + { + if (output.Length == 0) + { + output = new byte[sizeInfo.TotalSize]; + } + + int inOffs = 0; + + int mipGobBlocksInY = gobBlocksInY; + int mipGobBlocksInZ = gobBlocksInZ; + + int gobWidth = (GobStride / bytesPerPixel) * gobBlocksInTileX; + int gobHeight = gobBlocksInY * GobHeight; + + for (int level = 0; level < levels; level++) + { + int w = Math.Max(1, width >> level); + int h = Math.Max(1, height >> level); + int d = Math.Max(1, depth >> level); + + w = BitUtils.DivRoundUp(w, blockWidth); + h = BitUtils.DivRoundUp(h, blockHeight); + + while (h <= (mipGobBlocksInY >> 1) * GobHeight && mipGobBlocksInY != 1) + { + mipGobBlocksInY >>= 1; + } + + if (level > 0 && d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1) + { + mipGobBlocksInZ >>= 1; + } + + int strideTrunc = BitUtils.AlignDown(w * bytesPerPixel, 16); + int strideTrunc64 = BitUtils.AlignDown(w * bytesPerPixel, 64); + + int xStart = strideTrunc / bytesPerPixel; + + int stride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + + int inStrideGap = stride - w * bytesPerPixel; + + int alignment = gobWidth; + + if (d < gobBlocksInZ || w <= gobWidth || h <= gobHeight) + { + alignment = GobStride / bytesPerPixel; + } + + int wAligned = BitUtils.AlignUp(w, alignment); + + BlockLinearLayout layoutConverter = new( + wAligned, + h, + mipGobBlocksInY, + mipGobBlocksInZ, + bytesPerPixel); + + int sd = Math.Max(1, sliceDepth >> level); + + unsafe bool Convert(Span output, ReadOnlySpan data) where T : unmanaged + { + fixed (byte* outputPtr = output, dataPtr = data) + { + byte* inPtr = dataPtr + inOffs; + for (int layer = 0; layer < layers; layer++) + { + byte* outBaseOffset = outputPtr + (layer * sizeInfo.LayerSize + sizeInfo.GetMipOffset(level)); + + for (int z = 0; z < sd; z++) + { + layoutConverter.SetZ(z); + for (int y = 0; y < h; y++) + { + layoutConverter.SetY(y); + + for (int x = 0; x < strideTrunc64; x += 64, inPtr += 64) + { + byte* offset = outBaseOffset + layoutConverter.GetOffsetWithLineOffset64(x); + byte* offset2 = offset + 0x20; + byte* offset3 = offset + 0x100; + byte* offset4 = offset + 0x120; + + Vector128 value = *(Vector128*)inPtr; + Vector128 value2 = *(Vector128*)(inPtr + 16); + Vector128 value3 = *(Vector128*)(inPtr + 32); + Vector128 value4 = *(Vector128*)(inPtr + 48); + + *(Vector128*)offset = value; + *(Vector128*)offset2 = value2; + *(Vector128*)offset3 = value3; + *(Vector128*)offset4 = value4; + } + + for (int x = strideTrunc64; x < strideTrunc; x += 16, inPtr += 16) + { + byte* offset = outBaseOffset + layoutConverter.GetOffsetWithLineOffset16(x); + + *(Vector128*)offset = *(Vector128*)inPtr; + } + + for (int x = xStart; x < w; x++, inPtr += bytesPerPixel) + { + byte* offset = outBaseOffset + layoutConverter.GetOffset(x); + + *(T*)offset = *(T*)inPtr; + } + + inPtr += inStrideGap; + } + } + } + inOffs += stride * h * d * layers; + } + return true; + } + + bool _ = bytesPerPixel switch + { + 1 => Convert(output, data), + 2 => Convert(output, data), + 4 => Convert(output, data), + 8 => Convert(output, data), + 12 => Convert(output, data), + 16 => Convert>(output, data), + _ => throw new NotSupportedException($"Unable to convert ${bytesPerPixel} bpp pixel format."), + }; + } + + return output; + } + + public static ReadOnlySpan ConvertLinearToLinearStrided( + Span output, + int width, + int height, + int blockWidth, + int blockHeight, + int stride, + int bytesPerPixel, + ReadOnlySpan data) + { + int w = BitUtils.DivRoundUp(width, blockWidth); + int h = BitUtils.DivRoundUp(height, blockHeight); + + int inStride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + int lineSize = width * bytesPerPixel; + + if (inStride == stride) + { + if (output.Length != 0) + { + data.CopyTo(output); + return output; + } + else + { + return data; + } + } + + if (output.Length == 0) + { + output = new byte[h * stride]; + } + + int inOffs = 0; + int outOffs = 0; + + for (int y = 0; y < h; y++) + { + data.Slice(inOffs, lineSize).CopyTo(output.Slice(outOffs, lineSize)); + + inOffs += inStride; + outOffs += stride; + } + + return output; + } + + private static int GetTextureSize( + int width, + int height, + int depth, + int levels, + int layers, + int blockWidth, + int blockHeight, + int bytesPerPixel) + { + int layerSize = 0; + + for (int level = 0; level < levels; level++) + { + int w = Math.Max(1, width >> level); + int h = Math.Max(1, height >> level); + int d = Math.Max(1, depth >> level); + + w = BitUtils.DivRoundUp(w, blockWidth); + h = BitUtils.DivRoundUp(h, blockHeight); + + int stride = BitUtils.AlignUp(w * bytesPerPixel, HostStrideAlignment); + + layerSize += stride * h * d; + } + + return layerSize * layers; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/OffsetCalculator.cs b/src/Ryujinx.Graphics.Texture/OffsetCalculator.cs new file mode 100644 index 00000000..48d36cdf --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/OffsetCalculator.cs @@ -0,0 +1,141 @@ +using Ryujinx.Common; +using System; +using System.Runtime.CompilerServices; +using static Ryujinx.Graphics.Texture.BlockLinearConstants; + +namespace Ryujinx.Graphics.Texture +{ + public class OffsetCalculator + { + private readonly int _width; + private readonly int _height; + private readonly int _stride; + private readonly bool _isLinear; + private readonly int _bytesPerPixel; + + private readonly BlockLinearLayout _layoutConverter; + + // Variables for built in iteration. + private int _yPart; + + public OffsetCalculator( + int width, + int height, + int stride, + bool isLinear, + int gobBlocksInY, + int gobBlocksInZ, + int bytesPerPixel) + { + _width = width; + _height = height; + _stride = stride; + _isLinear = isLinear; + _bytesPerPixel = bytesPerPixel; + + int wAlignment = GobStride / bytesPerPixel; + + int wAligned = BitUtils.AlignUp(width, wAlignment); + + if (!isLinear) + { + _layoutConverter = new BlockLinearLayout( + wAligned, + height, + gobBlocksInY, + gobBlocksInZ, + bytesPerPixel); + } + } + + public OffsetCalculator( + int width, + int height, + int stride, + bool isLinear, + int gobBlocksInY, + int bytesPerPixel) : this(width, height, stride, isLinear, gobBlocksInY, 1, bytesPerPixel) + { + } + + public void SetY(int y) + { + if (_isLinear) + { + _yPart = y * _stride; + } + else + { + _layoutConverter.SetY(y); + } + } + + public int GetOffset(int x, int y) + { + if (_isLinear) + { + return x * _bytesPerPixel + y * _stride; + } + else + { + return _layoutConverter.GetOffset(x, y, 0); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffset(int x) + { + if (_isLinear) + { + return x * _bytesPerPixel + _yPart; + } + else + { + return _layoutConverter.GetOffset(x); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffsetWithLineOffset64(int x) + { + if (_isLinear) + { + return x + _yPart; + } + else + { + return _layoutConverter.GetOffsetWithLineOffset64(x); + } + } + + public (int offset, int size) GetRectangleRange(int x, int y, int width, int height) + { + if (_isLinear) + { + int start = y * Math.Abs(_stride) + x * _bytesPerPixel; + int end = (y + height - 1) * Math.Abs(_stride) + (x + width) * _bytesPerPixel; + return (y * _stride + x * _bytesPerPixel, end - start); + } + else + { + return _layoutConverter.GetRectangleRange(x, y, width, height); + } + } + + public bool LayoutMatches(OffsetCalculator other) + { + if (_isLinear) + { + return other._isLinear && + _width == other._width && + _height == other._height && + _stride == other._stride && + _bytesPerPixel == other._bytesPerPixel; + } + else + { + return !other._isLinear && _layoutConverter.LayoutMatches(other._layoutConverter); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/PixelConverter.cs b/src/Ryujinx.Graphics.Texture/PixelConverter.cs new file mode 100644 index 00000000..3676d919 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/PixelConverter.cs @@ -0,0 +1,218 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Graphics.Texture +{ + public static class PixelConverter + { + private static (int remainder, int outRemainder, int height) GetLineRemainders(int length, int width, int bpp, int outBpp) + { + int stride = BitUtils.AlignUp(width * bpp, LayoutConverter.HostStrideAlignment); + int remainder = stride / bpp - width; + + int outStride = BitUtils.AlignUp(width * outBpp, LayoutConverter.HostStrideAlignment); + int outRemainder = outStride / outBpp - width; + + return (remainder, outRemainder, length / stride); + } + + public unsafe static MemoryOwner ConvertR4G4ToR4G4B4A4(ReadOnlySpan data, int width) + { + MemoryOwner output = MemoryOwner.Rent(data.Length * 2); + Span outputSpan = output.Span; + + (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 1, 2); + + Span outputSpanUInt16 = MemoryMarshal.Cast(outputSpan); + + if (remainder == 0) + { + int start = 0; + + if (Sse41.IsSupported) + { + int sizeTrunc = data.Length & ~7; + start = sizeTrunc; + + fixed (byte* inputPtr = data, outputPtr = outputSpan) + { + for (ulong offset = 0; offset < (ulong)sizeTrunc; offset += 8) + { + Sse2.Store(outputPtr + offset * 2, Sse41.ConvertToVector128Int16(inputPtr + offset).AsByte()); + } + } + } + + for (int i = start; i < data.Length; i++) + { + outputSpanUInt16[i] = data[i]; + } + } + else + { + int offset = 0; + int outOffset = 0; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + outputSpanUInt16[outOffset++] = data[offset++]; + } + + offset += remainder; + outOffset += outRemainder; + } + } + + return output; + } + + public static MemoryOwner ConvertR5G6B5ToR8G8B8A8(ReadOnlySpan data, int width) + { + MemoryOwner output = MemoryOwner.Rent(data.Length * 2); + int offset = 0; + int outOffset = 0; + + (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); + + ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); + Span outputSpan = MemoryMarshal.Cast(output.Span); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + uint packed = inputSpan[offset++]; + + uint outputPacked = 0xff000000; + outputPacked |= (packed << 3) & 0x000000f8; + outputPacked |= (packed << 8) & 0x00f80000; + + // Replicate 5 bit components. + outputPacked |= (outputPacked >> 5) & 0x00070007; + + // Include and replicate 6 bit component. + outputPacked |= ((packed << 5) & 0x0000fc00) | ((packed >> 1) & 0x00000300); + + outputSpan[outOffset++] = outputPacked; + } + + offset += remainder; + outOffset += outRemainder; + } + + return output; + } + + public static MemoryOwner ConvertR5G5B5ToR8G8B8A8(ReadOnlySpan data, int width, bool forceAlpha) + { + MemoryOwner output = MemoryOwner.Rent(data.Length * 2); + int offset = 0; + int outOffset = 0; + + (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); + + ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); + Span outputSpan = MemoryMarshal.Cast(output.Span); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + uint packed = inputSpan[offset++]; + + uint a = forceAlpha ? 1 : (packed >> 15); + + uint outputPacked = a * 0xff000000; + outputPacked |= (packed << 3) & 0x000000f8; + outputPacked |= (packed << 6) & 0x0000f800; + outputPacked |= (packed << 9) & 0x00f80000; + + // Replicate 5 bit components. + outputPacked |= (outputPacked >> 5) & 0x00070707; + + outputSpan[outOffset++] = outputPacked; + } + + offset += remainder; + outOffset += outRemainder; + } + + return output; + } + + public static MemoryOwner ConvertA1B5G5R5ToR8G8B8A8(ReadOnlySpan data, int width) + { + MemoryOwner output = MemoryOwner.Rent(data.Length * 2); + int offset = 0; + int outOffset = 0; + + (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); + + ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); + Span outputSpan = MemoryMarshal.Cast(output.Span); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + uint packed = inputSpan[offset++]; + + uint a = packed >> 15; + + uint outputPacked = a * 0xff000000; + outputPacked |= (packed >> 8) & 0x000000f8; + outputPacked |= (packed << 5) & 0x0000f800; + outputPacked |= (packed << 18) & 0x00f80000; + + // Replicate 5 bit components. + outputPacked |= (outputPacked >> 5) & 0x00070707; + + outputSpan[outOffset++] = outputPacked; + } + + offset += remainder; + outOffset += outRemainder; + } + + return output; + } + + public static MemoryOwner ConvertR4G4B4A4ToR8G8B8A8(ReadOnlySpan data, int width) + { + MemoryOwner output = MemoryOwner.Rent(data.Length * 2); + int offset = 0; + int outOffset = 0; + + (int remainder, int outRemainder, int height) = GetLineRemainders(data.Length, width, 2, 4); + + ReadOnlySpan inputSpan = MemoryMarshal.Cast(data); + Span outputSpan = MemoryMarshal.Cast(output.Span); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + uint packed = inputSpan[offset++]; + + uint outputPacked = packed & 0x0000000f; + outputPacked |= (packed << 4) & 0x00000f00; + outputPacked |= (packed << 8) & 0x000f0000; + outputPacked |= (packed << 12) & 0x0f000000; + + outputSpan[outOffset++] = outputPacked * 0x11; + } + + offset += remainder; + outOffset += outRemainder; + } + + return output; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Region.cs b/src/Ryujinx.Graphics.Texture/Region.cs new file mode 100644 index 00000000..aeb7f918 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Region.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Graphics.Texture +{ + public readonly struct Region + { + public int Offset { get; } + public int Size { get; } + + public Region(int offset, int size) + { + Offset = offset; + Size = size; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj b/src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj new file mode 100644 index 00000000..51721490 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj @@ -0,0 +1,11 @@ + + + net8.0 + true + + + + + + + diff --git a/src/Ryujinx.Graphics.Texture/Size.cs b/src/Ryujinx.Graphics.Texture/Size.cs new file mode 100644 index 00000000..78d3cb57 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Size.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Graphics.Texture +{ + public readonly struct Size + { + public int Width { get; } + public int Height { get; } + public int Depth { get; } + + public Size(int width, int height, int depth) + { + Width = width; + Height = height; + Depth = depth; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/SizeCalculator.cs b/src/Ryujinx.Graphics.Texture/SizeCalculator.cs new file mode 100644 index 00000000..e6122a6c --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/SizeCalculator.cs @@ -0,0 +1,309 @@ +using Ryujinx.Common; +using System; +using static Ryujinx.Graphics.Texture.BlockLinearConstants; + +namespace Ryujinx.Graphics.Texture +{ + public static class SizeCalculator + { + private const int StrideAlignment = 32; + + private static int Calculate3DOffsetCount(int levels, int depth) + { + int offsetCount = depth; + + while (--levels > 0) + { + depth = Math.Max(1, depth >> 1); + offsetCount += depth; + } + + return offsetCount; + } + + public static SizeInfo GetBlockLinearTextureSize( + int width, + int height, + int depth, + int levels, + int layers, + int blockWidth, + int blockHeight, + int bytesPerPixel, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX, + int gpuLayerSize = 0) + { + bool is3D = depth > 1 || gobBlocksInZ > 1; + + int layerSize = 0; + int layerSizeAligned = 0; + + int[] allOffsets = new int[is3D ? Calculate3DOffsetCount(levels, depth) : levels * layers * depth]; + int[] mipOffsets = new int[levels]; + int[] sliceSizes = new int[levels]; + int[] levelSizes = new int[levels]; + + int mipGobBlocksInY = gobBlocksInY; + int mipGobBlocksInZ = gobBlocksInZ; + + int gobWidth = (GobStride / bytesPerPixel) * gobBlocksInTileX; + int gobHeight = gobBlocksInY * GobHeight; + + int depthLevelOffset = 0; + + for (int level = 0; level < levels; level++) + { + int w = Math.Max(1, width >> level); + int h = Math.Max(1, height >> level); + int d = Math.Max(1, depth >> level); + + w = BitUtils.DivRoundUp(w, blockWidth); + h = BitUtils.DivRoundUp(h, blockHeight); + + while (h <= (mipGobBlocksInY >> 1) * GobHeight && mipGobBlocksInY != 1) + { + mipGobBlocksInY >>= 1; + } + + if (level > 0 && d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1) + { + mipGobBlocksInZ >>= 1; + } + + int widthInGobs = BitUtils.DivRoundUp(w * bytesPerPixel, GobStride); + + int alignment = gobBlocksInTileX; + + if (d < gobBlocksInZ || w <= gobWidth || h <= gobHeight) + { + alignment = 1; + } + + widthInGobs = BitUtils.AlignUp(widthInGobs, alignment); + + int totalBlocksOfGobsInZ = BitUtils.DivRoundUp(d, mipGobBlocksInZ); + int totalBlocksOfGobsInY = BitUtils.DivRoundUp(BitUtils.DivRoundUp(h, GobHeight), mipGobBlocksInY); + + int robSize = widthInGobs * mipGobBlocksInY * mipGobBlocksInZ * GobSize; + + mipOffsets[level] = layerSize; + sliceSizes[level] = totalBlocksOfGobsInY * robSize; + levelSizes[level] = totalBlocksOfGobsInZ * sliceSizes[level]; + + layerSizeAligned += levelSizes[level]; + + if (is3D) + { + int gobSize = mipGobBlocksInY * GobSize; + + int sliceSize = totalBlocksOfGobsInY * widthInGobs * gobSize; + + int baseOffset = layerSize; + + int mask = gobBlocksInZ - 1; + + for (int z = 0; z < d; z++) + { + int zLow = z & mask; + int zHigh = z & ~mask; + + allOffsets[z + depthLevelOffset] = baseOffset + zLow * gobSize + zHigh * sliceSize; + } + + int gobRemainderZ = d % mipGobBlocksInZ; + + if (gobRemainderZ != 0 && level == levels - 1) + { + // The slice only covers up to the end of this slice's depth, rather than the full aligned size. + // Avoids size being too large on partial views of 3d textures. + + levelSizes[level] -= gobSize * (mipGobBlocksInZ - gobRemainderZ); + + if (sliceSizes[level] > levelSizes[level]) + { + sliceSizes[level] = levelSizes[level]; + } + } + } + + layerSize += levelSizes[level]; + + depthLevelOffset += d; + } + + int totalSize; + + if (layers > 1) + { + layerSizeAligned = AlignLayerSize( + layerSizeAligned, + height, + depth, + blockHeight, + gobBlocksInY, + gobBlocksInZ, + gobBlocksInTileX); + + if (layerSizeAligned < gpuLayerSize) + { + totalSize = (layers - 1) * gpuLayerSize + layerSizeAligned; + layerSizeAligned = gpuLayerSize; + } + else + { + totalSize = layerSizeAligned * layers; + } + } + else + { + totalSize = layerSize; + } + + if (!is3D) + { + for (int layer = 0; layer < layers; layer++) + { + int baseIndex = layer * levels; + int baseOffset = layer * layerSizeAligned; + + for (int level = 0; level < levels; level++) + { + allOffsets[baseIndex + level] = baseOffset + mipOffsets[level]; + } + } + } + + return new SizeInfo(mipOffsets, allOffsets, sliceSizes, levelSizes, depth, levels, layerSizeAligned, totalSize, is3D); + } + + public static SizeInfo GetLinearTextureSize(int stride, int height, int blockHeight) + { + // Non-2D or mipmapped linear textures are not supported by the Switch GPU, + // so we only need to handle a single case (2D textures without mipmaps). + int totalSize = stride * BitUtils.DivRoundUp(height, blockHeight); + + return new SizeInfo(totalSize); + } + + private static int AlignLayerSize( + int size, + int height, + int depth, + int blockHeight, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX) + { + if (gobBlocksInTileX < 2) + { + height = BitUtils.DivRoundUp(height, blockHeight); + + while (height <= (gobBlocksInY >> 1) * GobHeight && gobBlocksInY != 1) + { + gobBlocksInY >>= 1; + } + + while (depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1) + { + gobBlocksInZ >>= 1; + } + + int blockOfGobsSize = gobBlocksInY * gobBlocksInZ * GobSize; + + int sizeInBlockOfGobs = size / blockOfGobsSize; + + if (size != sizeInBlockOfGobs * blockOfGobsSize) + { + size = (sizeInBlockOfGobs + 1) * blockOfGobsSize; + } + } + else + { + int alignment = (gobBlocksInTileX * GobSize) * gobBlocksInY * gobBlocksInZ; + + size = BitUtils.AlignUp(size, alignment); + } + + return size; + } + + public static Size GetBlockLinearAlignedSize( + int width, + int height, + int depth, + int blockWidth, + int blockHeight, + int bytesPerPixel, + int gobBlocksInY, + int gobBlocksInZ, + int gobBlocksInTileX) + { + width = BitUtils.DivRoundUp(width, blockWidth); + height = BitUtils.DivRoundUp(height, blockHeight); + + int gobWidth = (GobStride / bytesPerPixel) * gobBlocksInTileX; + int gobHeight = gobBlocksInY * GobHeight; + + int alignment = gobWidth; + + if (depth < gobBlocksInZ || width <= gobWidth || height <= gobHeight) + { + alignment = GobStride / bytesPerPixel; + } + + // Height has already been divided by block height, so pass it as 1. + (gobBlocksInY, gobBlocksInZ) = GetMipGobBlockSizes(height, depth, 1, gobBlocksInY, gobBlocksInZ); + + int blockOfGobsHeight = gobBlocksInY * GobHeight; + int blockOfGobsDepth = gobBlocksInZ; + + width = BitUtils.AlignUp(width, alignment); + height = BitUtils.AlignUp(height, blockOfGobsHeight); + depth = BitUtils.AlignUp(depth, blockOfGobsDepth); + + return new Size(width, height, depth); + } + + public static Size GetLinearAlignedSize( + int width, + int height, + int blockWidth, + int blockHeight, + int bytesPerPixel) + { + width = BitUtils.DivRoundUp(width, blockWidth); + height = BitUtils.DivRoundUp(height, blockHeight); + + int widthAlignment = StrideAlignment / bytesPerPixel; + + width = BitUtils.AlignUp(width, widthAlignment); + + return new Size(width, height, 1); + } + + public static (int, int) GetMipGobBlockSizes( + int height, + int depth, + int blockHeight, + int gobBlocksInY, + int gobBlocksInZ, + int level = int.MaxValue) + { + height = BitUtils.DivRoundUp(height, blockHeight); + + while (height <= (gobBlocksInY >> 1) * GobHeight && gobBlocksInY != 1) + { + gobBlocksInY >>= 1; + } + + while (level-- > 0 && depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1) + { + gobBlocksInZ >>= 1; + } + + return (gobBlocksInY, gobBlocksInZ); + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/SizeInfo.cs b/src/Ryujinx.Graphics.Texture/SizeInfo.cs new file mode 100644 index 00000000..3bec1203 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/SizeInfo.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Texture +{ + public readonly struct SizeInfo + { + private readonly int[] _mipOffsets; + + private readonly int _levels; + private readonly int _depth; + private readonly bool _is3D; + + public readonly int[] AllOffsets; + public readonly int[] SliceSizes; + public readonly int[] LevelSizes; + public int LayerSize { get; } + public int TotalSize { get; } + + public SizeInfo(int size) + { + _mipOffsets = new int[] { 0 }; + AllOffsets = new int[] { 0 }; + SliceSizes = new int[] { size }; + LevelSizes = new int[] { size }; + _depth = 1; + _levels = 1; + LayerSize = size; + TotalSize = size; + _is3D = false; + } + + internal SizeInfo( + int[] mipOffsets, + int[] allOffsets, + int[] sliceSizes, + int[] levelSizes, + int depth, + int levels, + int layerSize, + int totalSize, + bool is3D) + { + _mipOffsets = mipOffsets; + AllOffsets = allOffsets; + SliceSizes = sliceSizes; + LevelSizes = levelSizes; + _depth = depth; + _levels = levels; + LayerSize = layerSize; + TotalSize = totalSize; + _is3D = is3D; + } + + public int GetMipOffset(int level) + { + if ((uint)level >= _mipOffsets.Length) + { + throw new ArgumentOutOfRangeException(nameof(level)); + } + + return _mipOffsets[level]; + } + + public bool FindView(int offset, out int firstLayer, out int firstLevel) + { + int index = Array.BinarySearch(AllOffsets, offset); + + if (index < 0) + { + firstLayer = 0; + firstLevel = 0; + + return false; + } + + if (_is3D) + { + firstLayer = index; + firstLevel = 0; + + int levelDepth = _depth; + + while (firstLayer >= levelDepth) + { + firstLayer -= levelDepth; + firstLevel++; + levelDepth = Math.Max(levelDepth >> 1, 1); + } + } + else + { + firstLayer = index / _levels; + firstLevel = index - (firstLayer * _levels); + } + + return true; + } + + public IEnumerable AllRegions() + { + if (_is3D) + { + for (int i = 0; i < _mipOffsets.Length; i++) + { + int maxSize = TotalSize - _mipOffsets[i]; + yield return new Region(_mipOffsets[i], Math.Min(maxSize, LevelSizes[i])); + } + } + else + { + for (int i = 0; i < AllOffsets.Length; i++) + { + yield return new Region(AllOffsets[i], SliceSizes[i % _levels]); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Utils/BC67Tables.cs b/src/Ryujinx.Graphics.Texture/Utils/BC67Tables.cs new file mode 100644 index 00000000..52253855 --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Utils/BC67Tables.cs @@ -0,0 +1,297 @@ +namespace Ryujinx.Graphics.Texture.Utils +{ + static class BC67Tables + { + public static readonly BC7ModeInfo[] BC7ModeInfos = new BC7ModeInfo[] + { + new BC7ModeInfo(3, 4, 6, 0, 0, 3, 0, 4, 0), + new BC7ModeInfo(2, 6, 2, 0, 0, 3, 0, 6, 0), + new BC7ModeInfo(3, 6, 0, 0, 0, 2, 0, 5, 0), + new BC7ModeInfo(2, 6, 4, 0, 0, 2, 0, 7, 0), + new BC7ModeInfo(1, 0, 0, 2, 1, 2, 3, 5, 6), + new BC7ModeInfo(1, 0, 0, 2, 0, 2, 2, 7, 8), + new BC7ModeInfo(1, 0, 2, 0, 0, 4, 0, 7, 7), + new BC7ModeInfo(2, 6, 4, 0, 0, 2, 0, 5, 5), + }; + + public static readonly byte[][] Weights = + { + new byte[] { 0, 21, 43, 64 }, + new byte[] { 0, 9, 18, 27, 37, 46, 55, 64 }, + new byte[] { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 }, + }; + + public static readonly byte[][] InverseWeights = + { + new byte[] { 64, 43, 21, 0 }, + new byte[] { 64, 55, 46, 37, 27, 18, 9, 0 }, + new byte[] { 64, 60, 55, 51, 47, 43, 38, 34, 30, 26, 21, 17, 13, 9, 4, 0 }, + }; + + public static readonly byte[][][] FixUpIndices = new byte[3][][] + { + new byte[64][] + { + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, new byte[] { 0, 0, 0 }, + }, + new byte[64][] + { + new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, + new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, + new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, + new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, + new byte[] { 0, 15, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 8, 0 }, new byte[] { 0, 2, 0 }, + new byte[] { 0, 2, 0 }, new byte[] { 0, 8, 0 }, new byte[] { 0, 8, 0 }, new byte[] { 0, 15, 0 }, + new byte[] { 0, 2, 0 }, new byte[] { 0, 8, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 2, 0 }, + new byte[] { 0, 8, 0 }, new byte[] { 0, 8, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 2, 0 }, + new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 6, 0 }, new byte[] { 0, 8, 0 }, + new byte[] { 0, 2, 0 }, new byte[] { 0, 8, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, + new byte[] { 0, 2, 0 }, new byte[] { 0, 8, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 2, 0 }, + new byte[] { 0, 2, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 6, 0 }, + new byte[] { 0, 6, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 6, 0 }, new byte[] { 0, 8, 0 }, + new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 2, 0 }, + new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, new byte[] { 0, 15, 0 }, + new byte[] { 0, 15, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 2, 0 }, new byte[] { 0, 15, 0 }, + }, + new byte[64][] + { + new byte[] { 0, 3, 15 }, new byte[] { 0, 3, 8 }, new byte[] { 0, 15, 8 }, new byte[] { 0, 15, 3 }, + new byte[] { 0, 8, 15 }, new byte[] { 0, 3, 15 }, new byte[] { 0, 15, 3 }, new byte[] { 0, 15, 8 }, + new byte[] { 0, 8, 15 }, new byte[] { 0, 8, 15 }, new byte[] { 0, 6, 15 }, new byte[] { 0, 6, 15 }, + new byte[] { 0, 6, 15 }, new byte[] { 0, 5, 15 }, new byte[] { 0, 3, 15 }, new byte[] { 0, 3, 8 }, + new byte[] { 0, 3, 15 }, new byte[] { 0, 3, 8 }, new byte[] { 0, 8, 15 }, new byte[] { 0, 15, 3 }, + new byte[] { 0, 3, 15 }, new byte[] { 0, 3, 8 }, new byte[] { 0, 6, 15 }, new byte[] { 0, 10, 8 }, + new byte[] { 0, 5, 3 }, new byte[] { 0, 8, 15 }, new byte[] { 0, 8, 6 }, new byte[] { 0, 6, 10 }, + new byte[] { 0, 8, 15 }, new byte[] { 0, 5, 15 }, new byte[] { 0, 15, 10 }, new byte[] { 0, 15, 8 }, + new byte[] { 0, 8, 15 }, new byte[] { 0, 15, 3 }, new byte[] { 0, 3, 15 }, new byte[] { 0, 5, 10 }, + new byte[] { 0, 6, 10 }, new byte[] { 0, 10, 8 }, new byte[] { 0, 8, 9 }, new byte[] { 0, 15, 10 }, + new byte[] { 0, 15, 6 }, new byte[] { 0, 3, 15 }, new byte[] { 0, 15, 8 }, new byte[] { 0, 5, 15 }, + new byte[] { 0, 15, 3 }, new byte[] { 0, 15, 6 }, new byte[] { 0, 15, 6 }, new byte[] { 0, 15, 8 }, + new byte[] { 0, 3, 15 }, new byte[] { 0, 15, 3 }, new byte[] { 0, 5, 15 }, new byte[] { 0, 5, 15 }, + new byte[] { 0, 5, 15 }, new byte[] { 0, 8, 15 }, new byte[] { 0, 5, 15 }, new byte[] { 0, 10, 15 }, + new byte[] { 0, 5, 15 }, new byte[] { 0, 10, 15 }, new byte[] { 0, 8, 15 }, new byte[] { 0, 13, 15 }, + new byte[] { 0, 15, 3 }, new byte[] { 0, 12, 15 }, new byte[] { 0, 3, 15 }, new byte[] { 0, 3, 8 }, + }, + }; + + public static readonly byte[][][] PartitionTable = new byte[3][][] + { + new byte[64][] + { + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 1 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 2 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 3 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 4 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 5 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 6 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 7 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 8 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 9 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 10 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 11 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 12 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 13 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 14 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 15 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 16 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 17 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 18 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 19 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 20 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 21 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 22 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 23 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 24 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 25 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 26 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 27 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 28 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 29 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 30 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 31 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 32 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 33 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 34 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 35 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 36 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 37 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 38 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 39 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 40 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 41 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 42 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 43 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 44 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 45 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 46 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 47 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 48 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 49 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 50 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 51 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 52 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 53 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 54 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 55 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 56 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 57 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 58 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 59 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 60 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 61 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 62 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 63 + }, + new byte[64][] + { + new byte[16] { 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1 }, // 0 + new byte[16] { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1 }, // 1 + new byte[16] { 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1 }, // 2 + new byte[16] { 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1 }, // 3 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1 }, // 4 + new byte[16] { 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 }, // 5 + new byte[16] { 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 }, // 6 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1 }, // 7 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1 }, // 8 + new byte[16] { 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // 9 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1 }, // 10 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1 }, // 11 + new byte[16] { 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // 12 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 }, // 13 + new byte[16] { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // 14 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 }, // 15 + new byte[16] { 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1 }, // 16 + new byte[16] { 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // 17 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0 }, // 18 + new byte[16] { 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0 }, // 19 + new byte[16] { 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // 20 + new byte[16] { 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0 }, // 21 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 }, // 22 + new byte[16] { 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1 }, // 23 + new byte[16] { 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0 }, // 24 + new byte[16] { 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 }, // 25 + new byte[16] { 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0 }, // 26 + new byte[16] { 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0 }, // 27 + new byte[16] { 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0 }, // 28 + new byte[16] { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, // 29 + new byte[16] { 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0 }, // 30 + new byte[16] { 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0 }, // 31 + new byte[16] { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 }, // 32 + new byte[16] { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1 }, // 33 + new byte[16] { 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0 }, // 34 + new byte[16] { 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0 }, // 35 + new byte[16] { 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0 }, // 36 + new byte[16] { 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0 }, // 37 + new byte[16] { 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1 }, // 38 + new byte[16] { 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1 }, // 39 + new byte[16] { 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0 }, // 40 + new byte[16] { 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0 }, // 41 + new byte[16] { 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0 }, // 42 + new byte[16] { 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0 }, // 43 + new byte[16] { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }, // 44 + new byte[16] { 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1 }, // 45 + new byte[16] { 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1 }, // 46 + new byte[16] { 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // 47 + new byte[16] { 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, // 48 + new byte[16] { 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0 }, // 49 + new byte[16] { 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0 }, // 50 + new byte[16] { 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0 }, // 51 + new byte[16] { 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1 }, // 52 + new byte[16] { 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1 }, // 53 + new byte[16] { 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0 }, // 54 + new byte[16] { 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0 }, // 55 + new byte[16] { 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1 }, // 56 + new byte[16] { 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1 }, // 57 + new byte[16] { 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1 }, // 58 + new byte[16] { 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1 }, // 59 + new byte[16] { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1 }, // 60 + new byte[16] { 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, // 61 + new byte[16] { 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0 }, // 62 + new byte[16] { 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1 }, // 63 + }, + new byte[64][] + { + new byte[16] { 0, 0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 1, 2, 2, 2, 2 }, // 0 + new byte[16] { 0, 0, 0, 1, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 2, 1 }, // 1 + new byte[16] { 0, 0, 0, 0, 2, 0, 0, 1, 2, 2, 1, 1, 2, 2, 1, 1 }, // 2 + new byte[16] { 0, 2, 2, 2, 0, 0, 2, 2, 0, 0, 1, 1, 0, 1, 1, 1 }, // 3 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2 }, // 4 + new byte[16] { 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 2, 0, 0, 2, 2 }, // 5 + new byte[16] { 0, 0, 2, 2, 0, 0, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1 }, // 6 + new byte[16] { 0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1 }, // 7 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2 }, // 8 + new byte[16] { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2 }, // 9 + new byte[16] { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2 }, // 10 + new byte[16] { 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2 }, // 11 + new byte[16] { 0, 1, 1, 2, 0, 1, 1, 2, 0, 1, 1, 2, 0, 1, 1, 2 }, // 12 + new byte[16] { 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 2 }, // 13 + new byte[16] { 0, 0, 1, 1, 0, 1, 1, 2, 1, 1, 2, 2, 1, 2, 2, 2 }, // 14 + new byte[16] { 0, 0, 1, 1, 2, 0, 0, 1, 2, 2, 0, 0, 2, 2, 2, 0 }, // 15 + new byte[16] { 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 2, 1, 1, 2, 2 }, // 16 + new byte[16] { 0, 1, 1, 1, 0, 0, 1, 1, 2, 0, 0, 1, 2, 2, 0, 0 }, // 17 + new byte[16] { 0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2 }, // 18 + new byte[16] { 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 1, 1, 1, 1 }, // 19 + new byte[16] { 0, 1, 1, 1, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2 }, // 20 + new byte[16] { 0, 0, 0, 1, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 2, 1 }, // 21 + new byte[16] { 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 2, 2, 0, 1, 2, 2 }, // 22 + new byte[16] { 0, 0, 0, 0, 1, 1, 0, 0, 2, 2, 1, 0, 2, 2, 1, 0 }, // 23 + new byte[16] { 0, 1, 2, 2, 0, 1, 2, 2, 0, 0, 1, 1, 0, 0, 0, 0 }, // 24 + new byte[16] { 0, 0, 1, 2, 0, 0, 1, 2, 1, 1, 2, 2, 2, 2, 2, 2 }, // 25 + new byte[16] { 0, 1, 1, 0, 1, 2, 2, 1, 1, 2, 2, 1, 0, 1, 1, 0 }, // 26 + new byte[16] { 0, 0, 0, 0, 0, 1, 1, 0, 1, 2, 2, 1, 1, 2, 2, 1 }, // 27 + new byte[16] { 0, 0, 2, 2, 1, 1, 0, 2, 1, 1, 0, 2, 0, 0, 2, 2 }, // 28 + new byte[16] { 0, 1, 1, 0, 0, 1, 1, 0, 2, 0, 0, 2, 2, 2, 2, 2 }, // 29 + new byte[16] { 0, 0, 1, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 0, 1, 1 }, // 30 + new byte[16] { 0, 0, 0, 0, 2, 0, 0, 0, 2, 2, 1, 1, 2, 2, 2, 1 }, // 31 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 2, 2, 2 }, // 32 + new byte[16] { 0, 2, 2, 2, 0, 0, 2, 2, 0, 0, 1, 2, 0, 0, 1, 1 }, // 33 + new byte[16] { 0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 2, 2, 0, 2, 2, 2 }, // 34 + new byte[16] { 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 0 }, // 35 + new byte[16] { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 0 }, // 36 + new byte[16] { 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0 }, // 37 + new byte[16] { 0, 1, 2, 0, 2, 0, 1, 2, 1, 2, 0, 1, 0, 1, 2, 0 }, // 38 + new byte[16] { 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1 }, // 39 + new byte[16] { 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 0, 1, 1 }, // 40 + new byte[16] { 0, 1, 0, 1, 0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2 }, // 41 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 2, 1, 2, 1, 2, 1 }, // 42 + new byte[16] { 0, 0, 2, 2, 1, 1, 2, 2, 0, 0, 2, 2, 1, 1, 2, 2 }, // 43 + new byte[16] { 0, 0, 2, 2, 0, 0, 1, 1, 0, 0, 2, 2, 0, 0, 1, 1 }, // 44 + new byte[16] { 0, 2, 2, 0, 1, 2, 2, 1, 0, 2, 2, 0, 1, 2, 2, 1 }, // 45 + new byte[16] { 0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 0, 1 }, // 46 + new byte[16] { 0, 0, 0, 0, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1 }, // 47 + new byte[16] { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 2, 2, 2, 2 }, // 48 + new byte[16] { 0, 2, 2, 2, 0, 1, 1, 1, 0, 2, 2, 2, 0, 1, 1, 1 }, // 49 + new byte[16] { 0, 0, 0, 2, 1, 1, 1, 2, 0, 0, 0, 2, 1, 1, 1, 2 }, // 50 + new byte[16] { 0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2 }, // 51 + new byte[16] { 0, 2, 2, 2, 0, 1, 1, 1, 0, 1, 1, 1, 0, 2, 2, 2 }, // 52 + new byte[16] { 0, 0, 0, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2 }, // 53 + new byte[16] { 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 2, 2 }, // 54 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2, 2, 1, 1, 2 }, // 55 + new byte[16] { 0, 1, 1, 0, 0, 1, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2 }, // 56 + new byte[16] { 0, 0, 2, 2, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 2, 2 }, // 57 + new byte[16] { 0, 0, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 0, 0, 2, 2 }, // 58 + new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 1, 2 }, // 59 + new byte[16] { 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1 }, // 60 + new byte[16] { 0, 2, 2, 2, 1, 2, 2, 2, 0, 2, 2, 2, 1, 2, 2, 2 }, // 61 + new byte[16] { 0, 1, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, // 62 + new byte[16] { 0, 1, 1, 1, 2, 0, 1, 1, 2, 2, 0, 1, 2, 2, 2, 0 }, // 63 + }, + }; + } +} diff --git a/src/Ryujinx.Graphics.Texture/Utils/BC67Utils.cs b/src/Ryujinx.Graphics.Texture/Utils/BC67Utils.cs new file mode 100644 index 00000000..f548684a --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Utils/BC67Utils.cs @@ -0,0 +1,1329 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Graphics.Texture.Utils +{ + static class BC67Utils + { + private static readonly byte[][] _quantizationLut; + private static readonly byte[][] _quantizationLutNoPBit; + + static BC67Utils() + { + _quantizationLut = new byte[5][]; + _quantizationLutNoPBit = new byte[5][]; + + for (int depth = 4; depth < 9; depth++) + { + byte[] lut = new byte[512]; + byte[] lutNoPBit = new byte[256]; + + for (int i = 0; i < lut.Length; i++) + { + lut[i] = QuantizeComponentForLut((byte)i, depth, i >> 8); + + if (i < lutNoPBit.Length) + { + lutNoPBit[i] = QuantizeComponentForLut((byte)i, depth); + } + } + + _quantizationLut[depth - 4] = lut; + _quantizationLutNoPBit[depth - 4] = lutNoPBit; + } + } + + public static (RgbaColor8, RgbaColor8) GetMinMaxColors(ReadOnlySpan tile, int w, int h) + { + if (Sse41.IsSupported && w == 4 && h == 4) + { + GetMinMaxColorsOneSubset4x4Sse41(tile, out RgbaColor8 minColor, out RgbaColor8 maxColor); + + return (minColor, maxColor); + } + else + { + RgbaColor8 minColor = new(255, 255, 255, 255); + RgbaColor8 maxColor = default; + + for (int i = 0; i < tile.Length; i++) + { + RgbaColor8 color = RgbaColor8.FromUInt32(tile[i]); + + minColor.R = Math.Min(minColor.R, color.R); + minColor.G = Math.Min(minColor.G, color.G); + minColor.B = Math.Min(minColor.B, color.B); + minColor.A = Math.Min(minColor.A, color.A); + + maxColor.R = Math.Max(maxColor.R, color.R); + maxColor.G = Math.Max(maxColor.G, color.G); + maxColor.B = Math.Max(maxColor.B, color.B); + maxColor.A = Math.Max(maxColor.A, color.A); + } + + return (minColor, maxColor); + } + } + + public static void GetMinMaxColors( + ReadOnlySpan partitionTable, + ReadOnlySpan tile, + int w, + int h, + Span minColors, + Span maxColors, + int subsetCount) + { + if (Sse41.IsSupported && w == 4 && h == 4) + { + if (subsetCount == 1) + { + GetMinMaxColorsOneSubset4x4Sse41(tile, out minColors[0], out maxColors[0]); + return; + } + else if (subsetCount == 2) + { + GetMinMaxColorsTwoSubsets4x4Sse41(partitionTable, tile, minColors, maxColors); + return; + } + } + + minColors.Fill(new RgbaColor8(255, 255, 255, 255)); + + int i = 0; + for (int ty = 0; ty < h; ty++) + { + for (int tx = 0; tx < w; tx++) + { + int subset = partitionTable[ty * w + tx]; + RgbaColor8 color = RgbaColor8.FromUInt32(tile[i++]); + + minColors[subset].R = Math.Min(minColors[subset].R, color.R); + minColors[subset].G = Math.Min(minColors[subset].G, color.G); + minColors[subset].B = Math.Min(minColors[subset].B, color.B); + minColors[subset].A = Math.Min(minColors[subset].A, color.A); + + maxColors[subset].R = Math.Max(maxColors[subset].R, color.R); + maxColors[subset].G = Math.Max(maxColors[subset].G, color.G); + maxColors[subset].B = Math.Max(maxColors[subset].B, color.B); + maxColors[subset].A = Math.Max(maxColors[subset].A, color.A); + } + } + } + + private static unsafe void GetMinMaxColorsOneSubset4x4Sse41(ReadOnlySpan tile, out RgbaColor8 minColor, out RgbaColor8 maxColor) + { + Vector128 min = Vector128.AllBitsSet; + Vector128 max = Vector128.Zero; + Vector128 row0, row1, row2, row3; + + fixed (uint* pTile = tile) + { + row0 = Sse2.LoadVector128(pTile).AsByte(); + row1 = Sse2.LoadVector128(pTile + 4).AsByte(); + row2 = Sse2.LoadVector128(pTile + 8).AsByte(); + row3 = Sse2.LoadVector128(pTile + 12).AsByte(); + } + + min = Sse2.Min(min, row0); + max = Sse2.Max(max, row0); + min = Sse2.Min(min, row1); + max = Sse2.Max(max, row1); + min = Sse2.Min(min, row2); + max = Sse2.Max(max, row2); + min = Sse2.Min(min, row3); + max = Sse2.Max(max, row3); + + minColor = HorizontalMin(min); + maxColor = HorizontalMax(max); + } + + private static unsafe void GetMinMaxColorsTwoSubsets4x4Sse41( + ReadOnlySpan partitionTable, + ReadOnlySpan tile, + Span minColors, + Span maxColors) + { + Vector128 partitionMask; + + fixed (byte* pPartitionTable = partitionTable) + { + partitionMask = Sse2.LoadVector128(pPartitionTable); + } + + Vector128 subset0Mask = Sse2.CompareEqual(partitionMask, Vector128.Zero); + + Vector128 subset0MaskRep16Low = Sse2.UnpackLow(subset0Mask, subset0Mask); + Vector128 subset0MaskRep16High = Sse2.UnpackHigh(subset0Mask, subset0Mask); + + Vector128 subset0Mask0 = Sse2.UnpackLow(subset0MaskRep16Low.AsInt16(), subset0MaskRep16Low.AsInt16()).AsByte(); + Vector128 subset0Mask1 = Sse2.UnpackHigh(subset0MaskRep16Low.AsInt16(), subset0MaskRep16Low.AsInt16()).AsByte(); + Vector128 subset0Mask2 = Sse2.UnpackLow(subset0MaskRep16High.AsInt16(), subset0MaskRep16High.AsInt16()).AsByte(); + Vector128 subset0Mask3 = Sse2.UnpackHigh(subset0MaskRep16High.AsInt16(), subset0MaskRep16High.AsInt16()).AsByte(); + + Vector128 min0 = Vector128.AllBitsSet; + Vector128 min1 = Vector128.AllBitsSet; + Vector128 max0 = Vector128.Zero; + Vector128 max1 = Vector128.Zero; + + Vector128 row0, row1, row2, row3; + + fixed (uint* pTile = tile) + { + row0 = Sse2.LoadVector128(pTile).AsByte(); + row1 = Sse2.LoadVector128(pTile + 4).AsByte(); + row2 = Sse2.LoadVector128(pTile + 8).AsByte(); + row3 = Sse2.LoadVector128(pTile + 12).AsByte(); + } + + min0 = Sse2.Min(min0, Sse41.BlendVariable(min0, row0, subset0Mask0)); + min0 = Sse2.Min(min0, Sse41.BlendVariable(min0, row1, subset0Mask1)); + min0 = Sse2.Min(min0, Sse41.BlendVariable(min0, row2, subset0Mask2)); + min0 = Sse2.Min(min0, Sse41.BlendVariable(min0, row3, subset0Mask3)); + + min1 = Sse2.Min(min1, Sse2.Or(row0, subset0Mask0)); + min1 = Sse2.Min(min1, Sse2.Or(row1, subset0Mask1)); + min1 = Sse2.Min(min1, Sse2.Or(row2, subset0Mask2)); + min1 = Sse2.Min(min1, Sse2.Or(row3, subset0Mask3)); + + max0 = Sse2.Max(max0, Sse2.And(row0, subset0Mask0)); + max0 = Sse2.Max(max0, Sse2.And(row1, subset0Mask1)); + max0 = Sse2.Max(max0, Sse2.And(row2, subset0Mask2)); + max0 = Sse2.Max(max0, Sse2.And(row3, subset0Mask3)); + + max1 = Sse2.Max(max1, Sse2.AndNot(subset0Mask0, row0)); + max1 = Sse2.Max(max1, Sse2.AndNot(subset0Mask1, row1)); + max1 = Sse2.Max(max1, Sse2.AndNot(subset0Mask2, row2)); + max1 = Sse2.Max(max1, Sse2.AndNot(subset0Mask3, row3)); + + minColors[0] = HorizontalMin(min0); + minColors[1] = HorizontalMin(min1); + maxColors[0] = HorizontalMax(max0); + maxColors[1] = HorizontalMax(max1); + } + + private static RgbaColor8 HorizontalMin(Vector128 x) + { + x = Sse2.Min(x, Sse2.Shuffle(x.AsInt32(), 0x31).AsByte()); + x = Sse2.Min(x, Sse2.Shuffle(x.AsInt32(), 2).AsByte()); + return RgbaColor8.FromUInt32(x.AsUInt32().GetElement(0)); + } + + private static RgbaColor8 HorizontalMax(Vector128 x) + { + x = Sse2.Max(x, Sse2.Shuffle(x.AsInt32(), 0x31).AsByte()); + x = Sse2.Max(x, Sse2.Shuffle(x.AsInt32(), 2).AsByte()); + return RgbaColor8.FromUInt32(x.AsUInt32().GetElement(0)); + } + + public static int SelectIndices( + ReadOnlySpan values, + uint endPoint0, + uint endPoint1, + int pBit0, + int pBit1, + int indexBitCount, + int indexCount, + int colorDepth, + int alphaDepth, + uint alphaMask) + { + if (Sse41.IsSupported) + { + if (indexBitCount == 2) + { + return Select2BitIndicesSse41( + values, + endPoint0, + endPoint1, + pBit0, + pBit1, + indexBitCount, + indexCount, + colorDepth, + alphaDepth, + alphaMask); + } + else if (indexBitCount == 3) + { + return Select3BitIndicesSse41( + values, + endPoint0, + endPoint1, + pBit0, + pBit1, + indexBitCount, + indexCount, + colorDepth, + alphaDepth, + alphaMask); + } + else if (indexBitCount == 4) + { + return Select4BitIndicesOneSubsetSse41( + values, + endPoint0, + endPoint1, + pBit0, + pBit1, + indexBitCount, + indexCount, + colorDepth, + alphaDepth, + alphaMask); + } + } + + return SelectIndicesFallback( + values, + endPoint0, + endPoint1, + pBit0, + pBit1, + indexBitCount, + indexCount, + colorDepth, + alphaDepth, + alphaMask); + } + + private static unsafe int Select2BitIndicesSse41( + ReadOnlySpan values, + uint endPoint0, + uint endPoint1, + int pBit0, + int pBit1, + int indexBitCount, + int indexCount, + int colorDepth, + int alphaDepth, + uint alphaMask) + { + uint alphaMaskForPalette = alphaMask; + + if (alphaDepth == 0) + { + alphaMaskForPalette |= new RgbaColor8(0, 0, 0, 255).ToUInt32(); + } + + int errorSum = 0; + + RgbaColor8 c0 = Quantize(RgbaColor8.FromUInt32(endPoint0), colorDepth, alphaDepth, pBit0); + RgbaColor8 c1 = Quantize(RgbaColor8.FromUInt32(endPoint1), colorDepth, alphaDepth, pBit1); + + Vector128 c0Rep = Vector128.Create(c0.ToUInt32() | alphaMaskForPalette).AsByte(); + Vector128 c1Rep = Vector128.Create(c1.ToUInt32() | alphaMaskForPalette).AsByte(); + + Vector128 c0c1 = Sse2.UnpackLow(c0Rep, c1Rep); + + Vector128 rWeights; + Vector128 lWeights; + + fixed (byte* pWeights = BC67Tables.Weights[0], pInvWeights = BC67Tables.InverseWeights[0]) + { + rWeights = Sse2.LoadScalarVector128((uint*)pWeights).AsByte(); + lWeights = Sse2.LoadScalarVector128((uint*)pInvWeights).AsByte(); + } + + Vector128 iWeights = Sse2.UnpackLow(lWeights, rWeights); + Vector128 iWeights01 = Sse2.UnpackLow(iWeights.AsInt16(), iWeights.AsInt16()).AsByte(); + Vector128 iWeights0 = Sse2.UnpackLow(iWeights01.AsInt16(), iWeights01.AsInt16()).AsByte(); + Vector128 iWeights1 = Sse2.UnpackHigh(iWeights01.AsInt16(), iWeights01.AsInt16()).AsByte(); + + Vector128 pal0 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights0.AsSByte())); + Vector128 pal1 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights1.AsSByte())); + + for (int i = 0; i < values.Length; i++) + { + uint c = values[i] | alphaMask; + + Vector128 color = Sse41.ConvertToVector128Int16(Vector128.Create(c).AsByte()); + + Vector128 delta0 = Sse2.Subtract(color, pal0); + Vector128 delta1 = Sse2.Subtract(color, pal1); + + Vector128 deltaSum0 = Sse2.MultiplyAddAdjacent(delta0, delta0); + Vector128 deltaSum1 = Sse2.MultiplyAddAdjacent(delta1, delta1); + + Vector128 deltaSum01 = Ssse3.HorizontalAdd(deltaSum0, deltaSum1); + + Vector128 delta = Sse41.PackUnsignedSaturate(deltaSum01, deltaSum01); + + Vector128 min = Sse41.MinHorizontal(delta); + + ushort error = min.GetElement(0); + + errorSum += error; + } + + return errorSum; + } + + private static unsafe int Select3BitIndicesSse41( + ReadOnlySpan values, + uint endPoint0, + uint endPoint1, + int pBit0, + int pBit1, + int indexBitCount, + int indexCount, + int colorDepth, + int alphaDepth, + uint alphaMask) + { + uint alphaMaskForPalette = alphaMask; + + if (alphaDepth == 0) + { + alphaMaskForPalette |= new RgbaColor8(0, 0, 0, 255).ToUInt32(); + } + + int errorSum = 0; + + RgbaColor8 c0 = Quantize(RgbaColor8.FromUInt32(endPoint0), colorDepth, alphaDepth, pBit0); + RgbaColor8 c1 = Quantize(RgbaColor8.FromUInt32(endPoint1), colorDepth, alphaDepth, pBit1); + + Vector128 c0Rep = Vector128.Create(c0.ToUInt32() | alphaMaskForPalette).AsByte(); + Vector128 c1Rep = Vector128.Create(c1.ToUInt32() | alphaMaskForPalette).AsByte(); + + Vector128 c0c1 = Sse2.UnpackLow(c0Rep, c1Rep); + + Vector128 rWeights; + Vector128 lWeights; + + fixed (byte* pWeights = BC67Tables.Weights[1], pInvWeights = BC67Tables.InverseWeights[1]) + { + rWeights = Sse2.LoadScalarVector128((ulong*)pWeights).AsByte(); + lWeights = Sse2.LoadScalarVector128((ulong*)pInvWeights).AsByte(); + } + + Vector128 iWeights = Sse2.UnpackLow(lWeights, rWeights); + Vector128 iWeights01 = Sse2.UnpackLow(iWeights.AsInt16(), iWeights.AsInt16()).AsByte(); + Vector128 iWeights23 = Sse2.UnpackHigh(iWeights.AsInt16(), iWeights.AsInt16()).AsByte(); + Vector128 iWeights0 = Sse2.UnpackLow(iWeights01.AsInt16(), iWeights01.AsInt16()).AsByte(); + Vector128 iWeights1 = Sse2.UnpackHigh(iWeights01.AsInt16(), iWeights01.AsInt16()).AsByte(); + Vector128 iWeights2 = Sse2.UnpackLow(iWeights23.AsInt16(), iWeights23.AsInt16()).AsByte(); + Vector128 iWeights3 = Sse2.UnpackHigh(iWeights23.AsInt16(), iWeights23.AsInt16()).AsByte(); + + Vector128 pal0 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights0.AsSByte())); + Vector128 pal1 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights1.AsSByte())); + Vector128 pal2 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights2.AsSByte())); + Vector128 pal3 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights3.AsSByte())); + + for (int i = 0; i < values.Length; i++) + { + uint c = values[i] | alphaMask; + + Vector128 color = Sse41.ConvertToVector128Int16(Vector128.Create(c).AsByte()); + + Vector128 delta0 = Sse2.Subtract(color, pal0); + Vector128 delta1 = Sse2.Subtract(color, pal1); + Vector128 delta2 = Sse2.Subtract(color, pal2); + Vector128 delta3 = Sse2.Subtract(color, pal3); + + Vector128 deltaSum0 = Sse2.MultiplyAddAdjacent(delta0, delta0); + Vector128 deltaSum1 = Sse2.MultiplyAddAdjacent(delta1, delta1); + Vector128 deltaSum2 = Sse2.MultiplyAddAdjacent(delta2, delta2); + Vector128 deltaSum3 = Sse2.MultiplyAddAdjacent(delta3, delta3); + + Vector128 deltaSum01 = Ssse3.HorizontalAdd(deltaSum0, deltaSum1); + Vector128 deltaSum23 = Ssse3.HorizontalAdd(deltaSum2, deltaSum3); + + Vector128 delta = Sse41.PackUnsignedSaturate(deltaSum01, deltaSum23); + + Vector128 min = Sse41.MinHorizontal(delta); + + ushort error = min.GetElement(0); + + errorSum += error; + } + + return errorSum; + } + + private static unsafe int Select4BitIndicesOneSubsetSse41( + ReadOnlySpan values, + uint endPoint0, + uint endPoint1, + int pBit0, + int pBit1, + int indexBitCount, + int indexCount, + int colorDepth, + int alphaDepth, + uint alphaMask) + { + uint alphaMaskForPalette = alphaMask; + + if (alphaDepth == 0) + { + alphaMaskForPalette |= new RgbaColor8(0, 0, 0, 255).ToUInt32(); + } + + int errorSum = 0; + + RgbaColor8 c0 = Quantize(RgbaColor8.FromUInt32(endPoint0), colorDepth, alphaDepth, pBit0); + RgbaColor8 c1 = Quantize(RgbaColor8.FromUInt32(endPoint1), colorDepth, alphaDepth, pBit1); + + Vector128 c0Rep = Vector128.Create(c0.ToUInt32() | alphaMaskForPalette).AsByte(); + Vector128 c1Rep = Vector128.Create(c1.ToUInt32() | alphaMaskForPalette).AsByte(); + + Vector128 c0c1 = Sse2.UnpackLow(c0Rep, c1Rep); + + Vector128 rWeights; + Vector128 lWeights; + + fixed (byte* pWeights = BC67Tables.Weights[2], pInvWeights = BC67Tables.InverseWeights[2]) + { + rWeights = Sse2.LoadVector128(pWeights); + lWeights = Sse2.LoadVector128(pInvWeights); + } + + Vector128 iWeightsLow = Sse2.UnpackLow(lWeights, rWeights); + Vector128 iWeightsHigh = Sse2.UnpackHigh(lWeights, rWeights); + Vector128 iWeights01 = Sse2.UnpackLow(iWeightsLow.AsInt16(), iWeightsLow.AsInt16()).AsByte(); + Vector128 iWeights23 = Sse2.UnpackHigh(iWeightsLow.AsInt16(), iWeightsLow.AsInt16()).AsByte(); + Vector128 iWeights45 = Sse2.UnpackLow(iWeightsHigh.AsInt16(), iWeightsHigh.AsInt16()).AsByte(); + Vector128 iWeights67 = Sse2.UnpackHigh(iWeightsHigh.AsInt16(), iWeightsHigh.AsInt16()).AsByte(); + Vector128 iWeights0 = Sse2.UnpackLow(iWeights01.AsInt16(), iWeights01.AsInt16()).AsByte(); + Vector128 iWeights1 = Sse2.UnpackHigh(iWeights01.AsInt16(), iWeights01.AsInt16()).AsByte(); + Vector128 iWeights2 = Sse2.UnpackLow(iWeights23.AsInt16(), iWeights23.AsInt16()).AsByte(); + Vector128 iWeights3 = Sse2.UnpackHigh(iWeights23.AsInt16(), iWeights23.AsInt16()).AsByte(); + Vector128 iWeights4 = Sse2.UnpackLow(iWeights45.AsInt16(), iWeights45.AsInt16()).AsByte(); + Vector128 iWeights5 = Sse2.UnpackHigh(iWeights45.AsInt16(), iWeights45.AsInt16()).AsByte(); + Vector128 iWeights6 = Sse2.UnpackLow(iWeights67.AsInt16(), iWeights67.AsInt16()).AsByte(); + Vector128 iWeights7 = Sse2.UnpackHigh(iWeights67.AsInt16(), iWeights67.AsInt16()).AsByte(); + + Vector128 pal0 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights0.AsSByte())); + Vector128 pal1 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights1.AsSByte())); + Vector128 pal2 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights2.AsSByte())); + Vector128 pal3 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights3.AsSByte())); + Vector128 pal4 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights4.AsSByte())); + Vector128 pal5 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights5.AsSByte())); + Vector128 pal6 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights6.AsSByte())); + Vector128 pal7 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights7.AsSByte())); + + for (int i = 0; i < values.Length; i++) + { + uint c = values[i] | alphaMask; + + Vector128 color = Sse41.ConvertToVector128Int16(Vector128.Create(c).AsByte()); + + Vector128 delta0 = Sse2.Subtract(color, pal0); + Vector128 delta1 = Sse2.Subtract(color, pal1); + Vector128 delta2 = Sse2.Subtract(color, pal2); + Vector128 delta3 = Sse2.Subtract(color, pal3); + Vector128 delta4 = Sse2.Subtract(color, pal4); + Vector128 delta5 = Sse2.Subtract(color, pal5); + Vector128 delta6 = Sse2.Subtract(color, pal6); + Vector128 delta7 = Sse2.Subtract(color, pal7); + + Vector128 deltaSum0 = Sse2.MultiplyAddAdjacent(delta0, delta0); + Vector128 deltaSum1 = Sse2.MultiplyAddAdjacent(delta1, delta1); + Vector128 deltaSum2 = Sse2.MultiplyAddAdjacent(delta2, delta2); + Vector128 deltaSum3 = Sse2.MultiplyAddAdjacent(delta3, delta3); + Vector128 deltaSum4 = Sse2.MultiplyAddAdjacent(delta4, delta4); + Vector128 deltaSum5 = Sse2.MultiplyAddAdjacent(delta5, delta5); + Vector128 deltaSum6 = Sse2.MultiplyAddAdjacent(delta6, delta6); + Vector128 deltaSum7 = Sse2.MultiplyAddAdjacent(delta7, delta7); + + Vector128 deltaSum01 = Ssse3.HorizontalAdd(deltaSum0, deltaSum1); + Vector128 deltaSum23 = Ssse3.HorizontalAdd(deltaSum2, deltaSum3); + Vector128 deltaSum45 = Ssse3.HorizontalAdd(deltaSum4, deltaSum5); + Vector128 deltaSum67 = Ssse3.HorizontalAdd(deltaSum6, deltaSum7); + + Vector128 delta0123 = Sse41.PackUnsignedSaturate(deltaSum01, deltaSum23); + Vector128 delta4567 = Sse41.PackUnsignedSaturate(deltaSum45, deltaSum67); + + Vector128 min0123 = Sse41.MinHorizontal(delta0123); + Vector128 min4567 = Sse41.MinHorizontal(delta4567); + + ushort minPos0123 = min0123.GetElement(0); + ushort minPos4567 = min4567.GetElement(0); + + if (minPos4567 < minPos0123) + { + errorSum += minPos4567; + } + else + { + errorSum += minPos0123; + } + } + + return errorSum; + } + + private static int SelectIndicesFallback( + ReadOnlySpan values, + uint endPoint0, + uint endPoint1, + int pBit0, + int pBit1, + int indexBitCount, + int indexCount, + int colorDepth, + int alphaDepth, + uint alphaMask) + { + int errorSum = 0; + + uint alphaMaskForPalette = alphaMask; + + if (alphaDepth == 0) + { + alphaMaskForPalette |= new RgbaColor8(0, 0, 0, 255).ToUInt32(); + } + + Span palette = stackalloc uint[indexCount]; + + RgbaColor8 c0 = Quantize(RgbaColor8.FromUInt32(endPoint0), colorDepth, alphaDepth, pBit0); + RgbaColor8 c1 = Quantize(RgbaColor8.FromUInt32(endPoint1), colorDepth, alphaDepth, pBit1); + + Unsafe.As(ref c0) |= alphaMaskForPalette; + Unsafe.As(ref c1) |= alphaMaskForPalette; + + palette[0] = c0.ToUInt32(); + palette[indexCount - 1] = c1.ToUInt32(); + + for (int j = 1; j < indexCount - 1; j++) + { + palette[j] = Interpolate(c0, c1, j, indexBitCount).ToUInt32(); + } + + for (int i = 0; i < values.Length; i++) + { + uint color = values[i] | alphaMask; + + int bestMatchScore = int.MaxValue; + int bestMatchIndex = 0; + + for (int j = 0; j < indexCount; j++) + { + int score = SquaredDifference( + RgbaColor8.FromUInt32(color).GetColor32(), + RgbaColor8.FromUInt32(palette[j]).GetColor32()); + + if (score < bestMatchScore) + { + bestMatchScore = score; + bestMatchIndex = j; + } + } + + errorSum += bestMatchScore; + } + + return errorSum; + } + + public static int SelectIndices( + ReadOnlySpan tile, + int w, + int h, + ReadOnlySpan endPoints0, + ReadOnlySpan endPoints1, + ReadOnlySpan pBitValues, + Span indices, + int subsetCount, + int partition, + int indexBitCount, + int indexCount, + int colorDepth, + int alphaDepth, + int pBits, + uint alphaMask) + { + if (Sse41.IsSupported) + { + if (indexBitCount == 2) + { + return Select2BitIndicesSse41( + tile, + w, + h, + endPoints0, + endPoints1, + pBitValues, + indices, + subsetCount, + partition, + colorDepth, + alphaDepth, + pBits, + alphaMask); + } + else if (indexBitCount == 3) + { + return Select3BitIndicesSse41( + tile, + w, + h, + endPoints0, + endPoints1, + pBitValues, + indices, + subsetCount, + partition, + colorDepth, + alphaDepth, + pBits, + alphaMask); + } + else if (indexBitCount == 4) + { + Debug.Assert(subsetCount == 1); + + return Select4BitIndicesOneSubsetSse41( + tile, + w, + h, + endPoints0[0], + endPoints1[0], + pBitValues, + indices, + partition, + colorDepth, + alphaDepth, + pBits, + alphaMask); + } + } + + return SelectIndicesFallback( + tile, + w, + h, + endPoints0, + endPoints1, + pBitValues, + indices, + subsetCount, + partition, + indexBitCount, + indexCount, + colorDepth, + alphaDepth, + pBits, + alphaMask); + } + + private static unsafe int Select2BitIndicesSse41( + ReadOnlySpan tile, + int w, + int h, + ReadOnlySpan endPoints0, + ReadOnlySpan endPoints1, + ReadOnlySpan pBitValues, + Span indices, + int subsetCount, + int partition, + int colorDepth, + int alphaDepth, + int pBits, + uint alphaMask) + { + byte[] partitionTable = BC67Tables.PartitionTable[subsetCount - 1][partition]; + + uint alphaMaskForPalette = alphaMask; + + if (alphaDepth == 0) + { + alphaMaskForPalette |= new RgbaColor8(0, 0, 0, 255).ToUInt32(); + } + + int errorSum = 0; + + for (int subset = 0; subset < subsetCount; subset++) + { + int pBit0 = -1, pBit1 = -1; + + if (pBits == subsetCount) + { + pBit0 = pBit1 = pBitValues[subset]; + } + else if (pBits != 0) + { + pBit0 = pBitValues[subset * 2]; + pBit1 = pBitValues[subset * 2 + 1]; + } + + RgbaColor8 c0 = Quantize(RgbaColor8.FromUInt32(endPoints0[subset]), colorDepth, alphaDepth, pBit0); + RgbaColor8 c1 = Quantize(RgbaColor8.FromUInt32(endPoints1[subset]), colorDepth, alphaDepth, pBit1); + + Vector128 c0Rep = Vector128.Create(c0.ToUInt32() | alphaMaskForPalette).AsByte(); + Vector128 c1Rep = Vector128.Create(c1.ToUInt32() | alphaMaskForPalette).AsByte(); + + Vector128 c0c1 = Sse2.UnpackLow(c0Rep, c1Rep); + + Vector128 rWeights; + Vector128 lWeights; + + fixed (byte* pWeights = BC67Tables.Weights[0], pInvWeights = BC67Tables.InverseWeights[0]) + { + rWeights = Sse2.LoadScalarVector128((uint*)pWeights).AsByte(); + lWeights = Sse2.LoadScalarVector128((uint*)pInvWeights).AsByte(); + } + + Vector128 iWeights = Sse2.UnpackLow(lWeights, rWeights); + Vector128 iWeights01 = Sse2.UnpackLow(iWeights.AsInt16(), iWeights.AsInt16()).AsByte(); + Vector128 iWeights0 = Sse2.UnpackLow(iWeights01.AsInt16(), iWeights01.AsInt16()).AsByte(); + Vector128 iWeights1 = Sse2.UnpackHigh(iWeights01.AsInt16(), iWeights01.AsInt16()).AsByte(); + + Vector128 pal0 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights0.AsSByte())); + Vector128 pal1 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights1.AsSByte())); + + int i = 0; + for (int ty = 0; ty < h; ty++) + { + for (int tx = 0; tx < w; tx++, i++) + { + int tileOffset = ty * 4 + tx; + if (partitionTable[tileOffset] != subset) + { + continue; + } + + uint c = tile[i] | alphaMask; + + Vector128 color = Sse41.ConvertToVector128Int16(Vector128.Create(c).AsByte()); + + Vector128 delta0 = Sse2.Subtract(color, pal0); + Vector128 delta1 = Sse2.Subtract(color, pal1); + + Vector128 deltaSum0 = Sse2.MultiplyAddAdjacent(delta0, delta0); + Vector128 deltaSum1 = Sse2.MultiplyAddAdjacent(delta1, delta1); + + Vector128 deltaSum01 = Ssse3.HorizontalAdd(deltaSum0, deltaSum1); + + Vector128 delta = Sse41.PackUnsignedSaturate(deltaSum01, deltaSum01); + + Vector128 min = Sse41.MinHorizontal(delta); + + uint minPos = min.AsUInt32().GetElement(0); + ushort error = (ushort)minPos; + uint index = minPos >> 16; + + indices[tileOffset] = (byte)index; + errorSum += error; + } + } + } + + return errorSum; + } + + private static unsafe int Select3BitIndicesSse41( + ReadOnlySpan tile, + int w, + int h, + ReadOnlySpan endPoints0, + ReadOnlySpan endPoints1, + ReadOnlySpan pBitValues, + Span indices, + int subsetCount, + int partition, + int colorDepth, + int alphaDepth, + int pBits, + uint alphaMask) + { + byte[] partitionTable = BC67Tables.PartitionTable[subsetCount - 1][partition]; + + uint alphaMaskForPalette = alphaMask; + + if (alphaDepth == 0) + { + alphaMaskForPalette |= new RgbaColor8(0, 0, 0, 255).ToUInt32(); + } + + int errorSum = 0; + + for (int subset = 0; subset < subsetCount; subset++) + { + int pBit0 = -1, pBit1 = -1; + + if (pBits == subsetCount) + { + pBit0 = pBit1 = pBitValues[subset]; + } + else if (pBits != 0) + { + pBit0 = pBitValues[subset * 2]; + pBit1 = pBitValues[subset * 2 + 1]; + } + + RgbaColor8 c0 = Quantize(RgbaColor8.FromUInt32(endPoints0[subset]), colorDepth, alphaDepth, pBit0); + RgbaColor8 c1 = Quantize(RgbaColor8.FromUInt32(endPoints1[subset]), colorDepth, alphaDepth, pBit1); + + Vector128 c0Rep = Vector128.Create(c0.ToUInt32() | alphaMaskForPalette).AsByte(); + Vector128 c1Rep = Vector128.Create(c1.ToUInt32() | alphaMaskForPalette).AsByte(); + + Vector128 c0c1 = Sse2.UnpackLow(c0Rep, c1Rep); + + Vector128 rWeights; + Vector128 lWeights; + + fixed (byte* pWeights = BC67Tables.Weights[1], pInvWeights = BC67Tables.InverseWeights[1]) + { + rWeights = Sse2.LoadScalarVector128((ulong*)pWeights).AsByte(); + lWeights = Sse2.LoadScalarVector128((ulong*)pInvWeights).AsByte(); + } + + Vector128 iWeights = Sse2.UnpackLow(lWeights, rWeights); + Vector128 iWeights01 = Sse2.UnpackLow(iWeights.AsInt16(), iWeights.AsInt16()).AsByte(); + Vector128 iWeights23 = Sse2.UnpackHigh(iWeights.AsInt16(), iWeights.AsInt16()).AsByte(); + Vector128 iWeights0 = Sse2.UnpackLow(iWeights01.AsInt16(), iWeights01.AsInt16()).AsByte(); + Vector128 iWeights1 = Sse2.UnpackHigh(iWeights01.AsInt16(), iWeights01.AsInt16()).AsByte(); + Vector128 iWeights2 = Sse2.UnpackLow(iWeights23.AsInt16(), iWeights23.AsInt16()).AsByte(); + Vector128 iWeights3 = Sse2.UnpackHigh(iWeights23.AsInt16(), iWeights23.AsInt16()).AsByte(); + + Vector128 pal0 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights0.AsSByte())); + Vector128 pal1 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights1.AsSByte())); + Vector128 pal2 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights2.AsSByte())); + Vector128 pal3 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights3.AsSByte())); + + int i = 0; + for (int ty = 0; ty < h; ty++) + { + for (int tx = 0; tx < w; tx++, i++) + { + int tileOffset = ty * 4 + tx; + if (partitionTable[tileOffset] != subset) + { + continue; + } + + uint c = tile[i] | alphaMask; + + Vector128 color = Sse41.ConvertToVector128Int16(Vector128.Create(c).AsByte()); + + Vector128 delta0 = Sse2.Subtract(color, pal0); + Vector128 delta1 = Sse2.Subtract(color, pal1); + Vector128 delta2 = Sse2.Subtract(color, pal2); + Vector128 delta3 = Sse2.Subtract(color, pal3); + + Vector128 deltaSum0 = Sse2.MultiplyAddAdjacent(delta0, delta0); + Vector128 deltaSum1 = Sse2.MultiplyAddAdjacent(delta1, delta1); + Vector128 deltaSum2 = Sse2.MultiplyAddAdjacent(delta2, delta2); + Vector128 deltaSum3 = Sse2.MultiplyAddAdjacent(delta3, delta3); + + Vector128 deltaSum01 = Ssse3.HorizontalAdd(deltaSum0, deltaSum1); + Vector128 deltaSum23 = Ssse3.HorizontalAdd(deltaSum2, deltaSum3); + + Vector128 delta = Sse41.PackUnsignedSaturate(deltaSum01, deltaSum23); + + Vector128 min = Sse41.MinHorizontal(delta); + + uint minPos = min.AsUInt32().GetElement(0); + ushort error = (ushort)minPos; + uint index = minPos >> 16; + + indices[tileOffset] = (byte)index; + errorSum += error; + } + } + } + + return errorSum; + } + + private static unsafe int Select4BitIndicesOneSubsetSse41( + ReadOnlySpan tile, + int w, + int h, + uint endPoint0, + uint endPoint1, + ReadOnlySpan pBitValues, + Span indices, + int partition, + int colorDepth, + int alphaDepth, + int pBits, + uint alphaMask) + { + uint alphaMaskForPalette = alphaMask; + + if (alphaDepth == 0) + { + alphaMaskForPalette |= new RgbaColor8(0, 0, 0, 255).ToUInt32(); + } + + int errorSum = 0; + + int pBit0 = -1, pBit1 = -1; + + if (pBits != 0) + { + pBit0 = pBitValues[0]; + pBit1 = pBitValues[1]; + } + + RgbaColor8 c0 = Quantize(RgbaColor8.FromUInt32(endPoint0), colorDepth, alphaDepth, pBit0); + RgbaColor8 c1 = Quantize(RgbaColor8.FromUInt32(endPoint1), colorDepth, alphaDepth, pBit1); + + Vector128 c0Rep = Vector128.Create(c0.ToUInt32() | alphaMaskForPalette).AsByte(); + Vector128 c1Rep = Vector128.Create(c1.ToUInt32() | alphaMaskForPalette).AsByte(); + + Vector128 c0c1 = Sse2.UnpackLow(c0Rep, c1Rep); + + Vector128 rWeights; + Vector128 lWeights; + + fixed (byte* pWeights = BC67Tables.Weights[2], pInvWeights = BC67Tables.InverseWeights[2]) + { + rWeights = Sse2.LoadVector128(pWeights); + lWeights = Sse2.LoadVector128(pInvWeights); + } + + Vector128 iWeightsLow = Sse2.UnpackLow(lWeights, rWeights); + Vector128 iWeightsHigh = Sse2.UnpackHigh(lWeights, rWeights); + Vector128 iWeights01 = Sse2.UnpackLow(iWeightsLow.AsInt16(), iWeightsLow.AsInt16()).AsByte(); + Vector128 iWeights23 = Sse2.UnpackHigh(iWeightsLow.AsInt16(), iWeightsLow.AsInt16()).AsByte(); + Vector128 iWeights45 = Sse2.UnpackLow(iWeightsHigh.AsInt16(), iWeightsHigh.AsInt16()).AsByte(); + Vector128 iWeights67 = Sse2.UnpackHigh(iWeightsHigh.AsInt16(), iWeightsHigh.AsInt16()).AsByte(); + Vector128 iWeights0 = Sse2.UnpackLow(iWeights01.AsInt16(), iWeights01.AsInt16()).AsByte(); + Vector128 iWeights1 = Sse2.UnpackHigh(iWeights01.AsInt16(), iWeights01.AsInt16()).AsByte(); + Vector128 iWeights2 = Sse2.UnpackLow(iWeights23.AsInt16(), iWeights23.AsInt16()).AsByte(); + Vector128 iWeights3 = Sse2.UnpackHigh(iWeights23.AsInt16(), iWeights23.AsInt16()).AsByte(); + Vector128 iWeights4 = Sse2.UnpackLow(iWeights45.AsInt16(), iWeights45.AsInt16()).AsByte(); + Vector128 iWeights5 = Sse2.UnpackHigh(iWeights45.AsInt16(), iWeights45.AsInt16()).AsByte(); + Vector128 iWeights6 = Sse2.UnpackLow(iWeights67.AsInt16(), iWeights67.AsInt16()).AsByte(); + Vector128 iWeights7 = Sse2.UnpackHigh(iWeights67.AsInt16(), iWeights67.AsInt16()).AsByte(); + + Vector128 pal0 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights0.AsSByte())); + Vector128 pal1 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights1.AsSByte())); + Vector128 pal2 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights2.AsSByte())); + Vector128 pal3 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights3.AsSByte())); + Vector128 pal4 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights4.AsSByte())); + Vector128 pal5 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights5.AsSByte())); + Vector128 pal6 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights6.AsSByte())); + Vector128 pal7 = ShiftRoundToNearest(Ssse3.MultiplyAddAdjacent(c0c1, iWeights7.AsSByte())); + + int i = 0; + for (int ty = 0; ty < h; ty++) + { + for (int tx = 0; tx < w; tx++, i++) + { + uint c = tile[i] | alphaMask; + + Vector128 color = Sse41.ConvertToVector128Int16(Vector128.Create(c).AsByte()); + + Vector128 delta0 = Sse2.Subtract(color, pal0); + Vector128 delta1 = Sse2.Subtract(color, pal1); + Vector128 delta2 = Sse2.Subtract(color, pal2); + Vector128 delta3 = Sse2.Subtract(color, pal3); + Vector128 delta4 = Sse2.Subtract(color, pal4); + Vector128 delta5 = Sse2.Subtract(color, pal5); + Vector128 delta6 = Sse2.Subtract(color, pal6); + Vector128 delta7 = Sse2.Subtract(color, pal7); + + Vector128 deltaSum0 = Sse2.MultiplyAddAdjacent(delta0, delta0); + Vector128 deltaSum1 = Sse2.MultiplyAddAdjacent(delta1, delta1); + Vector128 deltaSum2 = Sse2.MultiplyAddAdjacent(delta2, delta2); + Vector128 deltaSum3 = Sse2.MultiplyAddAdjacent(delta3, delta3); + Vector128 deltaSum4 = Sse2.MultiplyAddAdjacent(delta4, delta4); + Vector128 deltaSum5 = Sse2.MultiplyAddAdjacent(delta5, delta5); + Vector128 deltaSum6 = Sse2.MultiplyAddAdjacent(delta6, delta6); + Vector128 deltaSum7 = Sse2.MultiplyAddAdjacent(delta7, delta7); + + Vector128 deltaSum01 = Ssse3.HorizontalAdd(deltaSum0, deltaSum1); + Vector128 deltaSum23 = Ssse3.HorizontalAdd(deltaSum2, deltaSum3); + Vector128 deltaSum45 = Ssse3.HorizontalAdd(deltaSum4, deltaSum5); + Vector128 deltaSum67 = Ssse3.HorizontalAdd(deltaSum6, deltaSum7); + + Vector128 delta0123 = Sse41.PackUnsignedSaturate(deltaSum01, deltaSum23); + Vector128 delta4567 = Sse41.PackUnsignedSaturate(deltaSum45, deltaSum67); + + Vector128 min0123 = Sse41.MinHorizontal(delta0123); + Vector128 min4567 = Sse41.MinHorizontal(delta4567); + + uint minPos0123 = min0123.AsUInt32().GetElement(0); + uint minPos4567 = min4567.AsUInt32().GetElement(0); + + if ((ushort)minPos4567 < (ushort)minPos0123) + { + errorSum += (ushort)minPos4567; + indices[ty * 4 + tx] = (byte)(8 + (minPos4567 >> 16)); + } + else + { + errorSum += (ushort)minPos0123; + indices[ty * 4 + tx] = (byte)(minPos0123 >> 16); + } + } + } + + return errorSum; + } + + private static Vector128 ShiftRoundToNearest(Vector128 x) + { + return Sse2.ShiftRightLogical(Sse2.Add(x, Vector128.Create((short)32)), 6); + } + + private static int SelectIndicesFallback( + ReadOnlySpan tile, + int w, + int h, + ReadOnlySpan endPoints0, + ReadOnlySpan endPoints1, + ReadOnlySpan pBitValues, + Span indices, + int subsetCount, + int partition, + int indexBitCount, + int indexCount, + int colorDepth, + int alphaDepth, + int pBits, + uint alphaMask) + { + int errorSum = 0; + + uint alphaMaskForPalette = alphaMask; + + if (alphaDepth == 0) + { + alphaMaskForPalette |= new RgbaColor8(0, 0, 0, 255).ToUInt32(); + } + + Span palette = stackalloc uint[subsetCount * indexCount]; + + for (int subset = 0; subset < subsetCount; subset++) + { + int palBase = subset * indexCount; + + int pBit0 = -1, pBit1 = -1; + + if (pBits == subsetCount) + { + pBit0 = pBit1 = pBitValues[subset]; + } + else if (pBits != 0) + { + pBit0 = pBitValues[subset * 2]; + pBit1 = pBitValues[subset * 2 + 1]; + } + + RgbaColor8 c0 = Quantize(RgbaColor8.FromUInt32(endPoints0[subset]), colorDepth, alphaDepth, pBit0); + RgbaColor8 c1 = Quantize(RgbaColor8.FromUInt32(endPoints1[subset]), colorDepth, alphaDepth, pBit1); + + Unsafe.As(ref c0) |= alphaMaskForPalette; + Unsafe.As(ref c1) |= alphaMaskForPalette; + + palette[palBase + 0] = c0.ToUInt32(); + palette[palBase + indexCount - 1] = c1.ToUInt32(); + + for (int j = 1; j < indexCount - 1; j++) + { + palette[palBase + j] = Interpolate(c0, c1, j, indexBitCount).ToUInt32(); + } + } + + int i = 0; + for (int ty = 0; ty < h; ty++) + { + for (int tx = 0; tx < w; tx++) + { + int subset = BC67Tables.PartitionTable[subsetCount - 1][partition][ty * 4 + tx]; + uint color = tile[i++] | alphaMask; + + int bestMatchScore = int.MaxValue; + int bestMatchIndex = 0; + + for (int j = 0; j < indexCount; j++) + { + int score = SquaredDifference( + RgbaColor8.FromUInt32(color).GetColor32(), + RgbaColor8.FromUInt32(palette[subset * indexCount + j]).GetColor32()); + + if (score < bestMatchScore) + { + bestMatchScore = score; + bestMatchIndex = j; + } + } + + indices[ty * 4 + tx] = (byte)bestMatchIndex; + errorSum += bestMatchScore; + } + } + + return errorSum; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int SquaredDifference(RgbaColor32 color1, RgbaColor32 color2) + { + RgbaColor32 delta = color1 - color2; + return RgbaColor32.Dot(delta, delta); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaColor8 Interpolate(RgbaColor8 color1, RgbaColor8 color2, int weightIndex, int indexBitCount) + { + return Interpolate(color1.GetColor32(), color2.GetColor32(), weightIndex, indexBitCount).GetColor8(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaColor32 Interpolate(RgbaColor32 color1, RgbaColor32 color2, int weightIndex, int indexBitCount) + { + Debug.Assert(indexBitCount >= 2 && indexBitCount <= 4); + + int weight = (((weightIndex << 7) / ((1 << indexBitCount) - 1)) + 1) >> 1; + + RgbaColor32 weightV = new(weight); + RgbaColor32 invWeightV = new(64 - weight); + + return (color1 * invWeightV + color2 * weightV + new RgbaColor32(32)) >> 6; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaColor32 Interpolate( + RgbaColor32 color1, + RgbaColor32 color2, + int colorWeightIndex, + int alphaWeightIndex, + int colorIndexBitCount, + int alphaIndexBitCount) + { + Debug.Assert(colorIndexBitCount >= 2 && colorIndexBitCount <= 4); + Debug.Assert(alphaIndexBitCount >= 2 && alphaIndexBitCount <= 4); + + int colorWeight = BC67Tables.Weights[colorIndexBitCount - 2][colorWeightIndex]; + int alphaWeight = BC67Tables.Weights[alphaIndexBitCount - 2][alphaWeightIndex]; + + RgbaColor32 weightV = new(colorWeight) + { + A = alphaWeight, + }; + RgbaColor32 invWeightV = new RgbaColor32(64) - weightV; + + return (color1 * invWeightV + color2 * weightV + new RgbaColor32(32)) >> 6; + } + + public static RgbaColor8 Quantize(RgbaColor8 color, int colorBits, int alphaBits, int pBit = -1) + { + if (alphaBits == 0) + { + int colorShift = 8 - colorBits; + + uint c; + + if (pBit >= 0) + { + byte[] lutColor = _quantizationLut[colorBits - 4]; + + Debug.Assert(pBit <= 1); + int high = pBit << 8; + uint mask = (0xffu >> (colorBits + 1)) * 0x10101; + + c = lutColor[color.R | high]; + c |= (uint)lutColor[color.G | high] << 8; + c |= (uint)lutColor[color.B | high] << 16; + + c <<= colorShift; + c |= (c >> (colorBits + 1)) & mask; + c |= ((uint)pBit * 0x10101) << (colorShift - 1); + } + else + { + byte[] lutColor = _quantizationLutNoPBit[colorBits - 4]; + + uint mask = (0xffu >> colorBits) * 0x10101; + + c = lutColor[color.R]; + c |= (uint)lutColor[color.G] << 8; + c |= (uint)lutColor[color.B] << 16; + + c <<= colorShift; + c |= (c >> colorBits) & mask; + } + + c |= (uint)color.A << 24; + + return RgbaColor8.FromUInt32(c); + } + + return QuantizeFallback(color, colorBits, alphaBits, pBit); + } + + private static RgbaColor8 QuantizeFallback(RgbaColor8 color, int colorBits, int alphaBits, int pBit = -1) + { + byte r = UnquantizeComponent(QuantizeComponent(color.R, colorBits, pBit), colorBits, pBit); + byte g = UnquantizeComponent(QuantizeComponent(color.G, colorBits, pBit), colorBits, pBit); + byte b = UnquantizeComponent(QuantizeComponent(color.B, colorBits, pBit), colorBits, pBit); + byte a = alphaBits == 0 ? color.A : UnquantizeComponent(QuantizeComponent(color.A, alphaBits, pBit), alphaBits, pBit); + return new RgbaColor8(r, g, b, a); + } + + public static byte QuantizeComponent(byte component, int bits, int pBit = -1) + { + return pBit >= 0 ? _quantizationLut[bits - 4][component | (pBit << 8)] : _quantizationLutNoPBit[bits - 4][component]; + } + + private static byte QuantizeComponentForLut(byte component, int bits, int pBit = -1) + { + int shift = 8 - bits; + int fill = component >> bits; + + if (pBit >= 0) + { + Debug.Assert(pBit <= 1); + fill >>= 1; + fill |= pBit << (shift - 1); + } + + int q1 = component >> shift; + int q2 = Math.Max(q1 - 1, 0); + int q3 = Math.Min(q1 + 1, (1 << bits) - 1); + + int delta1 = FastAbs(((q1 << shift) | fill) - component); + int delta2 = component - ((q2 << shift) | fill); + int delta3 = ((q3 << shift) | fill) - component; + + if (delta1 < delta2 && delta1 < delta3) + { + return (byte)q1; + } + else if (delta2 < delta3) + { + return (byte)q2; + } + else + { + return (byte)q3; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int FastAbs(int x) + { + int sign = x >> 31; + return (x + sign) ^ sign; + } + + private static byte UnquantizeComponent(byte component, int bits, int pBit) + { + int shift = 8 - bits; + int value = component << shift; + + if (pBit >= 0) + { + Debug.Assert(pBit <= 1); + value |= value >> (bits + 1); + value |= pBit << (shift - 1); + } + else + { + value |= value >> bits; + } + + return (byte)value; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Utils/BC7ModeInfo.cs b/src/Ryujinx.Graphics.Texture/Utils/BC7ModeInfo.cs new file mode 100644 index 00000000..91236f1b --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Utils/BC7ModeInfo.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.Graphics.Texture.Utils +{ + readonly struct BC7ModeInfo + { + public readonly int SubsetCount; + public readonly int PartitionBitCount; + public readonly int PBits; + public readonly int RotationBitCount; + public readonly int IndexModeBitCount; + public readonly int ColorIndexBitCount; + public readonly int AlphaIndexBitCount; + public readonly int ColorDepth; + public readonly int AlphaDepth; + + public BC7ModeInfo( + int subsetCount, + int partitionBitsCount, + int pBits, + int rotationBitCount, + int indexModeBitCount, + int colorIndexBitCount, + int alphaIndexBitCount, + int colorDepth, + int alphaDepth) + { + SubsetCount = subsetCount; + PartitionBitCount = partitionBitsCount; + PBits = pBits; + RotationBitCount = rotationBitCount; + IndexModeBitCount = indexModeBitCount; + ColorIndexBitCount = colorIndexBitCount; + AlphaIndexBitCount = alphaIndexBitCount; + ColorDepth = colorDepth; + AlphaDepth = alphaDepth; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Utils/Block.cs b/src/Ryujinx.Graphics.Texture/Utils/Block.cs new file mode 100644 index 00000000..3a1d50cd --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Utils/Block.cs @@ -0,0 +1,55 @@ +namespace Ryujinx.Graphics.Texture.Utils +{ + struct Block + { + public ulong Low; + public ulong High; + + public void Encode(ulong value, ref int offset, int bits) + { + if (offset >= 64) + { + High |= value << (offset - 64); + } + else + { + Low |= value << offset; + + if (offset + bits > 64) + { + int remainder = 64 - offset; + High |= value >> remainder; + } + } + + offset += bits; + } + + public readonly ulong Decode(ref int offset, int bits) + { + ulong value; + ulong mask = bits == 64 ? ulong.MaxValue : (1UL << bits) - 1; + + if (offset >= 64) + { + value = (High >> (offset - 64)) & mask; + } + else + { + value = Low >> offset; + + if (offset + bits > 64) + { + int remainder = 64 - offset; + value |= High << remainder; + } + + value &= mask; + } + + offset += bits; + + return value; + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Utils/RgbaColor32.cs b/src/Ryujinx.Graphics.Texture/Utils/RgbaColor32.cs new file mode 100644 index 00000000..7d46ad6e --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Utils/RgbaColor32.cs @@ -0,0 +1,230 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Graphics.Texture.Utils +{ + struct RgbaColor32 : IEquatable + { + private Vector128 _color; + + public int R + { + readonly get => _color.GetElement(0); + set => _color = _color.WithElement(0, value); + } + + public int G + { + readonly get => _color.GetElement(1); + set => _color = _color.WithElement(1, value); + } + + public int B + { + readonly get => _color.GetElement(2); + set => _color = _color.WithElement(2, value); + } + + public int A + { + readonly get => _color.GetElement(3); + set => _color = _color.WithElement(3, value); + } + + public RgbaColor32(Vector128 color) + { + _color = color; + } + + public RgbaColor32(int r, int g, int b, int a) + { + _color = Vector128.Create(r, g, b, a); + } + + public RgbaColor32(int scalar) + { + _color = Vector128.Create(scalar); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaColor32 operator +(RgbaColor32 x, RgbaColor32 y) + { + if (Sse2.IsSupported) + { + return new RgbaColor32(Sse2.Add(x._color, y._color)); + } + else + { + return new RgbaColor32(x.R + y.R, x.G + y.G, x.B + y.B, x.A + y.A); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaColor32 operator -(RgbaColor32 x, RgbaColor32 y) + { + if (Sse2.IsSupported) + { + return new RgbaColor32(Sse2.Subtract(x._color, y._color)); + } + else + { + return new RgbaColor32(x.R - y.R, x.G - y.G, x.B - y.B, x.A - y.A); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaColor32 operator *(RgbaColor32 x, RgbaColor32 y) + { + if (Sse41.IsSupported) + { + return new RgbaColor32(Sse41.MultiplyLow(x._color, y._color)); + } + else + { + return new RgbaColor32(x.R * y.R, x.G * y.G, x.B * y.B, x.A * y.A); + } + } + + public static RgbaColor32 operator /(RgbaColor32 x, RgbaColor32 y) + { + return new RgbaColor32(x.R / y.R, x.G / y.G, x.B / y.B, x.A / y.A); + } + + public static RgbaColor32 DivideGuarded(RgbaColor32 x, RgbaColor32 y, int resultIfZero) + { + return new RgbaColor32( + DivideGuarded(x.R, y.R, resultIfZero), + DivideGuarded(x.G, y.G, resultIfZero), + DivideGuarded(x.B, y.B, resultIfZero), + DivideGuarded(x.A, y.A, resultIfZero)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaColor32 operator <<(RgbaColor32 x, [ConstantExpected] byte shift) + { + if (Sse2.IsSupported) + { + return new RgbaColor32(Sse2.ShiftLeftLogical(x._color, shift)); + } + else + { + return new RgbaColor32(x.R << shift, x.G << shift, x.B << shift, x.A << shift); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaColor32 operator >>(RgbaColor32 x, [ConstantExpected] byte shift) + { + if (Sse2.IsSupported) + { + return new RgbaColor32(Sse2.ShiftRightLogical(x._color, shift)); + } + else + { + return new RgbaColor32(x.R >> shift, x.G >> shift, x.B >> shift, x.A >> shift); + } + } + + public static bool operator ==(RgbaColor32 x, RgbaColor32 y) + { + return x.Equals(y); + } + + public static bool operator !=(RgbaColor32 x, RgbaColor32 y) + { + return !x.Equals(y); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Dot(RgbaColor32 x, RgbaColor32 y) + { + if (Sse41.IsSupported) + { + Vector128 product = Sse41.MultiplyLow(x._color, y._color); + Vector128 sum = Ssse3.HorizontalAdd(product, product); + sum = Ssse3.HorizontalAdd(sum, sum); + return sum.GetElement(0); + } + else + { + return x.R * y.R + x.G * y.G + x.B * y.B + x.A * y.A; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaColor32 Max(RgbaColor32 x, RgbaColor32 y) + { + if (Sse41.IsSupported) + { + return new RgbaColor32(Sse41.Max(x._color, y._color)); + } + else + { + return new RgbaColor32(Math.Max(x.R, y.R), Math.Max(x.G, y.G), Math.Max(x.B, y.B), Math.Max(x.A, y.A)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaColor32 Min(RgbaColor32 x, RgbaColor32 y) + { + if (Sse41.IsSupported) + { + return new RgbaColor32(Sse41.Min(x._color, y._color)); + } + else + { + return new RgbaColor32(Math.Min(x.R, y.R), Math.Min(x.G, y.G), Math.Min(x.B, y.B), Math.Min(x.A, y.A)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly RgbaColor8 GetColor8() + { + if (Sse41.IsSupported) + { + Vector128 temp = _color; + Vector128 color16 = Sse41.PackUnsignedSaturate(temp, temp); + Vector128 color8 = Sse2.PackUnsignedSaturate(color16.AsInt16(), color16.AsInt16()); + uint color = color8.AsUInt32().GetElement(0); + return Unsafe.As(ref color); + } + else + { + return new RgbaColor8(ClampByte(R), ClampByte(G), ClampByte(B), ClampByte(A)); + } + } + + private static int DivideGuarded(int dividend, int divisor, int resultIfZero) + { + if (divisor == 0) + { + return resultIfZero; + } + + return dividend / divisor; + } + + private static byte ClampByte(int value) + { + return (byte)Math.Clamp(value, 0, 255); + } + + public readonly override int GetHashCode() + { + return HashCode.Combine(R, G, B, A); + } + + public readonly override bool Equals(object obj) + { + return obj is RgbaColor32 other && Equals(other); + } + + public readonly bool Equals(RgbaColor32 other) + { + return _color.Equals(other._color); + } + } +} diff --git a/src/Ryujinx.Graphics.Texture/Utils/RgbaColor8.cs b/src/Ryujinx.Graphics.Texture/Utils/RgbaColor8.cs new file mode 100644 index 00000000..91535c1a --- /dev/null +++ b/src/Ryujinx.Graphics.Texture/Utils/RgbaColor8.cs @@ -0,0 +1,84 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Graphics.Texture.Utils +{ + struct RgbaColor8 : IEquatable + { + public byte R; + public byte G; + public byte B; + public byte A; + + public RgbaColor8(byte r, byte g, byte b, byte a) + { + R = r; + G = g; + B = b; + A = a; + } + + public static RgbaColor8 FromUInt32(uint color) + { + return Unsafe.As(ref color); + } + + public static bool operator ==(RgbaColor8 x, RgbaColor8 y) + { + return x.Equals(y); + } + + public static bool operator !=(RgbaColor8 x, RgbaColor8 y) + { + return !x.Equals(y); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RgbaColor32 GetColor32() + { + if (Sse41.IsSupported) + { + Vector128 color = Vector128.CreateScalarUnsafe(Unsafe.As(ref this)).AsByte(); + return new RgbaColor32(Sse41.ConvertToVector128Int32(color)); + } + else + { + return new RgbaColor32(R, G, B, A); + } + } + + public uint ToUInt32() + { + return Unsafe.As(ref this); + } + + public readonly override int GetHashCode() + { + return HashCode.Combine(R, G, B, A); + } + + public readonly override bool Equals(object obj) + { + return obj is RgbaColor8 other && Equals(other); + } + + public readonly bool Equals(RgbaColor8 other) + { + return R == other.R && G == other.G && B == other.B && A == other.A; + } + + public readonly byte GetComponent(int index) + { + return index switch + { + 0 => R, + 1 => G, + 2 => B, + 3 => A, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Vic/Blender.cs b/src/Ryujinx.Graphics.Vic/Blender.cs new file mode 100644 index 00000000..3218604d --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Blender.cs @@ -0,0 +1,278 @@ +using Ryujinx.Graphics.Vic.Image; +using Ryujinx.Graphics.Vic.Types; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Graphics.Vic +{ + static class Blender + { + public static void BlendOne(Surface dst, Surface src, ref SlotStruct slot, Rectangle targetRect) + { + int x1 = targetRect.X; + int y1 = targetRect.Y; + int x2 = Math.Min(src.Width, x1 + targetRect.Width); + int y2 = Math.Min(src.Height, y1 + targetRect.Height); + + if (((x1 | x2) & 3) == 0) + { + if (Sse41.IsSupported) + { + BlendOneSse41(dst, src, ref slot, x1, y1, x2, y2); + return; + } + else if (AdvSimd.IsSupported) + { + BlendOneAdvSimd(dst, src, ref slot, x1, y1, x2, y2); + return; + } + } + + for (int y = y1; y < y2; y++) + { + for (int x = x1; x < x2; x++) + { + int inR = src.GetR(x, y); + int inG = src.GetG(x, y); + int inB = src.GetB(x, y); + + MatrixMultiply(ref slot.ColorMatrixStruct, inR, inG, inB, out int r, out int g, out int b); + + r = Math.Clamp(r, slot.SlotConfig.SoftClampLow, slot.SlotConfig.SoftClampHigh); + g = Math.Clamp(g, slot.SlotConfig.SoftClampLow, slot.SlotConfig.SoftClampHigh); + b = Math.Clamp(b, slot.SlotConfig.SoftClampLow, slot.SlotConfig.SoftClampHigh); + + dst.SetR(x, y, (ushort)r); + dst.SetG(x, y, (ushort)g); + dst.SetB(x, y, (ushort)b); + dst.SetA(x, y, src.GetA(x, y)); + } + } + } + + private unsafe static void BlendOneSse41(Surface dst, Surface src, ref SlotStruct slot, int x1, int y1, int x2, int y2) + { + Debug.Assert(((x1 | x2) & 3) == 0); + + ref MatrixStruct mtx = ref slot.ColorMatrixStruct; + + int one = 1 << (mtx.MatrixRShift + 8); + + Vector128 col1 = Vector128.Create(mtx.MatrixCoeff00, mtx.MatrixCoeff10, mtx.MatrixCoeff20, 0); + Vector128 col2 = Vector128.Create(mtx.MatrixCoeff01, mtx.MatrixCoeff11, mtx.MatrixCoeff21, 0); + Vector128 col3 = Vector128.Create(mtx.MatrixCoeff02, mtx.MatrixCoeff12, mtx.MatrixCoeff22, one); + Vector128 col4 = Vector128.Create(mtx.MatrixCoeff03, mtx.MatrixCoeff13, mtx.MatrixCoeff23, 0); + + Vector128 rShift = Vector128.CreateScalar(mtx.MatrixRShift); + Vector128 clMin = Vector128.Create((ushort)slot.SlotConfig.SoftClampLow); + Vector128 clMax = Vector128.Create((ushort)slot.SlotConfig.SoftClampHigh); + + fixed (Pixel* srcPtr = src.Data, dstPtr = dst.Data) + { + Pixel* ip = srcPtr; + Pixel* op = dstPtr; + + for (int y = y1; y < y2; y++, ip += src.Width, op += dst.Width) + { + for (int x = x1; x < x2; x += 4) + { + Vector128 pixel1 = Sse41.ConvertToVector128Int32((ushort*)(ip + (uint)x)); + Vector128 pixel2 = Sse41.ConvertToVector128Int32((ushort*)(ip + (uint)x + 1)); + Vector128 pixel3 = Sse41.ConvertToVector128Int32((ushort*)(ip + (uint)x + 2)); + Vector128 pixel4 = Sse41.ConvertToVector128Int32((ushort*)(ip + (uint)x + 3)); + + Vector128 pixel12, pixel34; + + if (mtx.MatrixEnable) + { + pixel12 = Sse41.PackUnsignedSaturate( + MatrixMultiplySse41(pixel1, col1, col2, col3, col4, rShift), + MatrixMultiplySse41(pixel2, col1, col2, col3, col4, rShift)); + pixel34 = Sse41.PackUnsignedSaturate( + MatrixMultiplySse41(pixel3, col1, col2, col3, col4, rShift), + MatrixMultiplySse41(pixel4, col1, col2, col3, col4, rShift)); + } + else + { + pixel12 = Sse41.PackUnsignedSaturate(pixel1, pixel2); + pixel34 = Sse41.PackUnsignedSaturate(pixel3, pixel4); + } + + pixel12 = Sse41.Min(pixel12, clMax); + pixel34 = Sse41.Min(pixel34, clMax); + pixel12 = Sse41.Max(pixel12, clMin); + pixel34 = Sse41.Max(pixel34, clMin); + + Sse2.Store((ushort*)(op + (uint)x + 0), pixel12); + Sse2.Store((ushort*)(op + (uint)x + 2), pixel34); + } + } + } + } + + private unsafe static void BlendOneAdvSimd(Surface dst, Surface src, ref SlotStruct slot, int x1, int y1, int x2, int y2) + { + Debug.Assert(((x1 | x2) & 3) == 0); + + ref MatrixStruct mtx = ref slot.ColorMatrixStruct; + + Vector128 col1 = Vector128.Create(mtx.MatrixCoeff00, mtx.MatrixCoeff10, mtx.MatrixCoeff20, 0); + Vector128 col2 = Vector128.Create(mtx.MatrixCoeff01, mtx.MatrixCoeff11, mtx.MatrixCoeff21, 0); + Vector128 col3 = Vector128.Create(mtx.MatrixCoeff02, mtx.MatrixCoeff12, mtx.MatrixCoeff22, 0); + Vector128 col4 = Vector128.Create(mtx.MatrixCoeff03, mtx.MatrixCoeff13, mtx.MatrixCoeff23, 0); + + Vector128 rShift = Vector128.Create(-mtx.MatrixRShift); + Vector128 selMask = Vector128.Create(0, 0, 0, -1); + Vector128 clMin = Vector128.Create((ushort)slot.SlotConfig.SoftClampLow); + Vector128 clMax = Vector128.Create((ushort)slot.SlotConfig.SoftClampHigh); + + fixed (Pixel* srcPtr = src.Data, dstPtr = dst.Data) + { + Pixel* ip = srcPtr; + Pixel* op = dstPtr; + + if (mtx.MatrixEnable) + { + for (int y = y1; y < y2; y++, ip += src.Width, op += dst.Width) + { + for (int x = x1; x < x2; x += 4) + { + Vector128 pixel12 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x)); + Vector128 pixel34 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x + 2)); + + Vector128 pixel1 = AdvSimd.ZeroExtendWideningLower(pixel12.GetLower()); + Vector128 pixel2 = AdvSimd.ZeroExtendWideningUpper(pixel12); + Vector128 pixel3 = AdvSimd.ZeroExtendWideningLower(pixel34.GetLower()); + Vector128 pixel4 = AdvSimd.ZeroExtendWideningUpper(pixel34); + + Vector128 t1 = MatrixMultiplyAdvSimd(pixel1.AsInt32(), col1, col2, col3, col4, rShift, selMask); + Vector128 t2 = MatrixMultiplyAdvSimd(pixel2.AsInt32(), col1, col2, col3, col4, rShift, selMask); + Vector128 t3 = MatrixMultiplyAdvSimd(pixel3.AsInt32(), col1, col2, col3, col4, rShift, selMask); + Vector128 t4 = MatrixMultiplyAdvSimd(pixel4.AsInt32(), col1, col2, col3, col4, rShift, selMask); + + Vector64 lower1 = AdvSimd.ExtractNarrowingSaturateUnsignedLower(t1); + Vector64 lower3 = AdvSimd.ExtractNarrowingSaturateUnsignedLower(t3); + + pixel12 = AdvSimd.ExtractNarrowingSaturateUnsignedUpper(lower1, t2); + pixel34 = AdvSimd.ExtractNarrowingSaturateUnsignedUpper(lower3, t4); + + pixel12 = AdvSimd.Min(pixel12, clMax); + pixel34 = AdvSimd.Min(pixel34, clMax); + pixel12 = AdvSimd.Max(pixel12, clMin); + pixel34 = AdvSimd.Max(pixel34, clMin); + + AdvSimd.Store((ushort*)(op + (uint)x + 0), pixel12); + AdvSimd.Store((ushort*)(op + (uint)x + 2), pixel34); + } + } + } + else + { + for (int y = y1; y < y2; y++, ip += src.Width, op += dst.Width) + { + for (int x = x1; x < x2; x += 4) + { + Vector128 pixel12 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x)); + Vector128 pixel34 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x + 2)); + + pixel12 = AdvSimd.Min(pixel12, clMax); + pixel34 = AdvSimd.Min(pixel34, clMax); + pixel12 = AdvSimd.Max(pixel12, clMin); + pixel34 = AdvSimd.Max(pixel34, clMin); + + AdvSimd.Store((ushort*)(op + (uint)x + 0), pixel12); + AdvSimd.Store((ushort*)(op + (uint)x + 2), pixel34); + } + } + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void MatrixMultiply(ref MatrixStruct mtx, int x, int y, int z, out int r, out int g, out int b) + { + if (mtx.MatrixEnable) + { + r = x * mtx.MatrixCoeff00 + y * mtx.MatrixCoeff01 + z * mtx.MatrixCoeff02; + g = x * mtx.MatrixCoeff10 + y * mtx.MatrixCoeff11 + z * mtx.MatrixCoeff12; + b = x * mtx.MatrixCoeff20 + y * mtx.MatrixCoeff21 + z * mtx.MatrixCoeff22; + + r >>= mtx.MatrixRShift; + g >>= mtx.MatrixRShift; + b >>= mtx.MatrixRShift; + + r += mtx.MatrixCoeff03; + g += mtx.MatrixCoeff13; + b += mtx.MatrixCoeff23; + + r >>= 8; + g >>= 8; + b >>= 8; + } + else + { + r = x; + g = y; + b = z; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 MatrixMultiplySse41( + Vector128 pixel, + Vector128 col1, + Vector128 col2, + Vector128 col3, + Vector128 col4, + Vector128 rShift) + { + Vector128 x = Sse2.Shuffle(pixel, 0); + Vector128 y = Sse2.Shuffle(pixel, 0x55); + Vector128 z = Sse2.Shuffle(pixel, 0xea); + + col1 = Sse41.MultiplyLow(col1, x); + col2 = Sse41.MultiplyLow(col2, y); + col3 = Sse41.MultiplyLow(col3, z); + + Vector128 res = Sse2.Add(col3, Sse2.Add(col1, col2)); + + res = Sse2.ShiftRightArithmetic(res, rShift); + res = Sse2.Add(res, col4); + res = Sse2.ShiftRightArithmetic(res, 8); + + return res; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 MatrixMultiplyAdvSimd( + Vector128 pixel, + Vector128 col1, + Vector128 col2, + Vector128 col3, + Vector128 col4, + Vector128 rShift, + Vector128 selectMask) + { + Vector128 x = AdvSimd.DuplicateSelectedScalarToVector128(pixel, 0); + Vector128 y = AdvSimd.DuplicateSelectedScalarToVector128(pixel, 1); + Vector128 z = AdvSimd.DuplicateSelectedScalarToVector128(pixel, 2); + + col1 = AdvSimd.Multiply(col1, x); + col2 = AdvSimd.Multiply(col2, y); + col3 = AdvSimd.Multiply(col3, z); + + Vector128 res = AdvSimd.Add(col3, AdvSimd.Add(col1, col2)); + + res = AdvSimd.ShiftArithmetic(res, rShift); + res = AdvSimd.Add(res, col4); + res = AdvSimd.ShiftRightArithmetic(res, 8); + res = AdvSimd.BitwiseSelect(selectMask, pixel, res); + + return res; + } + } +} diff --git a/src/Ryujinx.Graphics.Vic/Image/BufferPool.cs b/src/Ryujinx.Graphics.Vic/Image/BufferPool.cs new file mode 100644 index 00000000..6e396305 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Image/BufferPool.cs @@ -0,0 +1,103 @@ +using System; + +namespace Ryujinx.Graphics.Vic.Image +{ + class BufferPool + { + /// + /// Maximum number of buffers on the pool. + /// + private const int MaxBuffers = 4; + + /// + /// Maximum size of a buffer that can be added on the pool. + /// If the required buffer is larger than this, it won't be + /// added to the pool to avoid long term high memory usage. + /// + private const int MaxBufferSize = 2048 * 2048; + + private struct PoolItem + { + public bool InUse; + public T[] Buffer; + } + + private readonly PoolItem[] _pool = new PoolItem[MaxBuffers]; + + /// + /// Rents a buffer with the exact size requested. + /// + /// Size of the buffer + /// Span of the requested size + /// The index of the buffer on the pool + public int Rent(int length, out Span buffer) + { + int index = RentMinimum(length, out T[] bufferArray); + + buffer = new Span(bufferArray)[..length]; + + return index; + } + + /// + /// Rents a buffer with a size greater than or equal to the requested size. + /// + /// Size of the buffer + /// Array with a length greater than or equal to the requested length + /// The index of the buffer on the pool + public int RentMinimum(int length, out T[] buffer) + { + if ((uint)length > MaxBufferSize) + { + buffer = new T[length]; + return -1; + } + + // Try to find a buffer that is larger or the same size of the requested one. + // This will avoid an allocation. + for (int i = 0; i < MaxBuffers; i++) + { + ref PoolItem item = ref _pool[i]; + + if (!item.InUse && item.Buffer != null && item.Buffer.Length >= length) + { + buffer = item.Buffer; + item.InUse = true; + return i; + } + } + + buffer = new T[length]; + + // Try to add the new buffer to the pool. + // We try to find a slot that is not in use, and replace the buffer in it. + for (int i = 0; i < MaxBuffers; i++) + { + ref PoolItem item = ref _pool[i]; + + if (!item.InUse) + { + item.Buffer = buffer; + item.InUse = true; + return i; + } + } + + return -1; + } + + /// + /// Returns a buffer returned from to the pool. + /// + /// Index of the buffer on the pool + public void Return(int index) + { + if (index < 0) + { + return; + } + + _pool[index].InUse = false; + } + } +} diff --git a/src/Ryujinx.Graphics.Vic/Image/InputSurface.cs b/src/Ryujinx.Graphics.Vic/Image/InputSurface.cs new file mode 100644 index 00000000..de770c94 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Image/InputSurface.cs @@ -0,0 +1,86 @@ +using System; + +namespace Ryujinx.Graphics.Vic.Image +{ + ref struct RentedBuffer + { + public static RentedBuffer Empty => new(Span.Empty, -1); + + public Span Data; + public int Index; + + public RentedBuffer(Span data, int index) + { + Data = data; + Index = index; + } + + public readonly void Return(BufferPool pool) + { + if (Index != -1) + { + pool.Return(Index); + } + } + } + + ref struct InputSurface + { + public ReadOnlySpan Buffer0; + public ReadOnlySpan Buffer1; + public ReadOnlySpan Buffer2; + + public int Buffer0Index; + public int Buffer1Index; + public int Buffer2Index; + + public int Width; + public int Height; + + public int UvWidth; + public int UvHeight; + + public void Initialize() + { + Buffer0Index = -1; + Buffer1Index = -1; + Buffer2Index = -1; + } + + public void SetBuffer0(RentedBuffer buffer) + { + Buffer0 = buffer.Data; + Buffer0Index = buffer.Index; + } + + public void SetBuffer1(RentedBuffer buffer) + { + Buffer1 = buffer.Data; + Buffer1Index = buffer.Index; + } + + public void SetBuffer2(RentedBuffer buffer) + { + Buffer2 = buffer.Data; + Buffer2Index = buffer.Index; + } + + public readonly void Return(BufferPool pool) + { + if (Buffer0Index != -1) + { + pool.Return(Buffer0Index); + } + + if (Buffer1Index != -1) + { + pool.Return(Buffer1Index); + } + + if (Buffer2Index != -1) + { + pool.Return(Buffer2Index); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vic/Image/Pixel.cs b/src/Ryujinx.Graphics.Vic/Image/Pixel.cs new file mode 100644 index 00000000..41308af7 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Image/Pixel.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Graphics.Vic.Image +{ + struct Pixel + { + public ushort R; + public ushort G; + public ushort B; + public ushort A; + } +} diff --git a/src/Ryujinx.Graphics.Vic/Image/Surface.cs b/src/Ryujinx.Graphics.Vic/Image/Surface.cs new file mode 100644 index 00000000..af87d967 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Image/Surface.cs @@ -0,0 +1,46 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Vic.Image +{ + readonly struct Surface : IDisposable + { + private readonly int _bufferIndex; + + private readonly BufferPool _pool; + + public Pixel[] Data { get; } + + public int Width { get; } + public int Height { get; } + + public Surface(BufferPool pool, int width, int height) + { + _bufferIndex = pool.RentMinimum(width * height, out Pixel[] data); + _pool = pool; + Data = data; + Width = width; + Height = height; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort GetR(int x, int y) => Data[y * Width + x].R; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort GetG(int x, int y) => Data[y * Width + x].G; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort GetB(int x, int y) => Data[y * Width + x].B; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort GetA(int x, int y) => Data[y * Width + x].A; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetR(int x, int y, ushort value) => Data[y * Width + x].R = value; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetG(int x, int y, ushort value) => Data[y * Width + x].G = value; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetB(int x, int y, ushort value) => Data[y * Width + x].B = value; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetA(int x, int y, ushort value) => Data[y * Width + x].A = value; + + public void Dispose() => _pool.Return(_bufferIndex); + } +} diff --git a/src/Ryujinx.Graphics.Vic/Image/SurfaceCommon.cs b/src/Ryujinx.Graphics.Vic/Image/SurfaceCommon.cs new file mode 100644 index 00000000..635f083b --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Image/SurfaceCommon.cs @@ -0,0 +1,33 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.Texture; + +namespace Ryujinx.Graphics.Vic.Image +{ + static class SurfaceCommon + { + public static int GetPitch(int width, int bytesPerPixel) + { + return BitUtils.AlignUp(width * bytesPerPixel, 256); + } + + public static int GetBlockLinearSize(int width, int height, int bytesPerPixel, int gobBlocksInY) + { + return SizeCalculator.GetBlockLinearTextureSize(width, height, 1, 1, 1, 1, 1, bytesPerPixel, gobBlocksInY, 1, 1).TotalSize; + } + + public static ulong ExtendOffset(uint offset) + { + return (ulong)offset << 8; + } + + public static ushort Upsample(byte value) + { + return (ushort)(value << 2); + } + + public static byte Downsample(ushort value) + { + return (byte)(value >> 2); + } + } +} diff --git a/src/Ryujinx.Graphics.Vic/Image/SurfaceReader.cs b/src/Ryujinx.Graphics.Vic/Image/SurfaceReader.cs new file mode 100644 index 00000000..83f00f34 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Image/SurfaceReader.cs @@ -0,0 +1,496 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Texture; +using Ryujinx.Graphics.Vic.Types; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; +using static Ryujinx.Graphics.Vic.Image.SurfaceCommon; + +namespace Ryujinx.Graphics.Vic.Image +{ + static class SurfaceReader + { + public static Surface Read( + ResourceManager rm, + ref SlotConfig config, + ref SlotSurfaceConfig surfaceConfig, + ref Array8 offsets) + { + switch (surfaceConfig.SlotPixelFormat) + { + case PixelFormat.Y8___V8U8_N420: + return ReadNv12(rm, ref config, ref surfaceConfig, ref offsets); + } + + Logger.Error?.Print(LogClass.Vic, $"Unsupported pixel format \"{surfaceConfig.SlotPixelFormat}\"."); + + int lw = surfaceConfig.SlotLumaWidth + 1; + int lh = surfaceConfig.SlotLumaHeight + 1; + + return new Surface(rm.SurfacePool, lw, lh); + } + + private unsafe static Surface ReadNv12( + ResourceManager rm, + ref SlotConfig config, + ref SlotSurfaceConfig surfaceConfig, + ref Array8 offsets) + { + InputSurface input = ReadSurface(rm, ref config, ref surfaceConfig, ref offsets, 1, 2); + + int width = input.Width; + int height = input.Height; + + int yStride = GetPitch(width, 1); + int uvStride = GetPitch(input.UvWidth, 2); + + Surface output = new(rm.SurfacePool, width, height); + + if (Sse41.IsSupported) + { + Vector128 shufMask = Vector128.Create( + (byte)0, (byte)2, (byte)3, (byte)1, + (byte)4, (byte)6, (byte)7, (byte)5, + (byte)8, (byte)10, (byte)11, (byte)9, + (byte)12, (byte)14, (byte)15, (byte)13); + Vector128 alphaMask = Vector128.Create(0xff << 24).AsInt16(); + + int yStrideGap = yStride - width; + int uvStrideGap = uvStride - input.UvWidth; + + int widthTrunc = width & ~0xf; + + fixed (Pixel* dstPtr = output.Data) + { + Pixel* op = dstPtr; + + fixed (byte* src0Ptr = input.Buffer0, src1Ptr = input.Buffer1) + { + byte* i0p = src0Ptr; + + for (int y = 0; y < height; y++) + { + byte* i1p = src1Ptr + (y >> 1) * uvStride; + + int x = 0; + + for (; x < widthTrunc; x += 16, i0p += 16, i1p += 16) + { + Vector128 ya0 = Sse41.ConvertToVector128Int16(i0p); + Vector128 ya1 = Sse41.ConvertToVector128Int16(i0p + 8); + + Vector128 uv = Sse2.LoadVector128(i1p); + + Vector128 uv0 = Sse2.UnpackLow(uv.AsInt16(), uv.AsInt16()); + Vector128 uv1 = Sse2.UnpackHigh(uv.AsInt16(), uv.AsInt16()); + + Vector128 rgba0 = Sse2.UnpackLow(ya0, uv0); + Vector128 rgba1 = Sse2.UnpackHigh(ya0, uv0); + Vector128 rgba2 = Sse2.UnpackLow(ya1, uv1); + Vector128 rgba3 = Sse2.UnpackHigh(ya1, uv1); + + rgba0 = Ssse3.Shuffle(rgba0.AsByte(), shufMask).AsInt16(); + rgba1 = Ssse3.Shuffle(rgba1.AsByte(), shufMask).AsInt16(); + rgba2 = Ssse3.Shuffle(rgba2.AsByte(), shufMask).AsInt16(); + rgba3 = Ssse3.Shuffle(rgba3.AsByte(), shufMask).AsInt16(); + + rgba0 = Sse2.Or(rgba0, alphaMask); + rgba1 = Sse2.Or(rgba1, alphaMask); + rgba2 = Sse2.Or(rgba2, alphaMask); + rgba3 = Sse2.Or(rgba3, alphaMask); + + Vector128 rgba16_0 = Sse41.ConvertToVector128Int16(rgba0.AsByte()); + Vector128 rgba16_1 = Sse41.ConvertToVector128Int16(HighToLow(rgba0.AsByte())); + Vector128 rgba16_2 = Sse41.ConvertToVector128Int16(rgba1.AsByte()); + Vector128 rgba16_3 = Sse41.ConvertToVector128Int16(HighToLow(rgba1.AsByte())); + Vector128 rgba16_4 = Sse41.ConvertToVector128Int16(rgba2.AsByte()); + Vector128 rgba16_5 = Sse41.ConvertToVector128Int16(HighToLow(rgba2.AsByte())); + Vector128 rgba16_6 = Sse41.ConvertToVector128Int16(rgba3.AsByte()); + Vector128 rgba16_7 = Sse41.ConvertToVector128Int16(HighToLow(rgba3.AsByte())); + + rgba16_0 = Sse2.ShiftLeftLogical(rgba16_0, 2); + rgba16_1 = Sse2.ShiftLeftLogical(rgba16_1, 2); + rgba16_2 = Sse2.ShiftLeftLogical(rgba16_2, 2); + rgba16_3 = Sse2.ShiftLeftLogical(rgba16_3, 2); + rgba16_4 = Sse2.ShiftLeftLogical(rgba16_4, 2); + rgba16_5 = Sse2.ShiftLeftLogical(rgba16_5, 2); + rgba16_6 = Sse2.ShiftLeftLogical(rgba16_6, 2); + rgba16_7 = Sse2.ShiftLeftLogical(rgba16_7, 2); + + Sse2.Store((short*)(op + (uint)x + 0), rgba16_0); + Sse2.Store((short*)(op + (uint)x + 2), rgba16_1); + Sse2.Store((short*)(op + (uint)x + 4), rgba16_2); + Sse2.Store((short*)(op + (uint)x + 6), rgba16_3); + Sse2.Store((short*)(op + (uint)x + 8), rgba16_4); + Sse2.Store((short*)(op + (uint)x + 10), rgba16_5); + Sse2.Store((short*)(op + (uint)x + 12), rgba16_6); + Sse2.Store((short*)(op + (uint)x + 14), rgba16_7); + } + + for (; x < width; x++, i1p += (x & 1) * 2) + { + Pixel* px = op + (uint)x; + + px->R = Upsample(*i0p++); + px->G = Upsample(*i1p); + px->B = Upsample(*(i1p + 1)); + px->A = 0x3ff; + } + + op += width; + i0p += yStrideGap; + i1p += uvStrideGap; + } + } + } + } + else if (AdvSimd.Arm64.IsSupported) + { + Vector128 alphaMask = Vector128.Create(0xffu << 24).AsInt32(); + + int yStrideGap = yStride - width; + int uvStrideGap = uvStride - input.UvWidth; + + int widthTrunc = width & ~0xf; + + fixed (Pixel* dstPtr = output.Data) + { + Pixel* op = dstPtr; + + fixed (byte* src0Ptr = input.Buffer0, src1Ptr = input.Buffer1) + { + byte* i0p = src0Ptr; + + for (int y = 0; y < height; y++) + { + byte* i1p = src1Ptr + (y >> 1) * uvStride; + + int x = 0; + + for (; x < widthTrunc; x += 16, i0p += 16, i1p += 16) + { + Vector128 ya = AdvSimd.LoadVector128(i0p); + Vector128 uv = AdvSimd.LoadVector128(i1p); + + Vector128 ya0 = AdvSimd.ZeroExtendWideningLower(ya.GetLower()).AsInt16(); + Vector128 ya1 = AdvSimd.ZeroExtendWideningUpper(ya).AsInt16(); + + Vector128 uv0 = AdvSimd.Arm64.ZipLow(uv.AsInt16(), uv.AsInt16()); + Vector128 uv1 = AdvSimd.Arm64.ZipHigh(uv.AsInt16(), uv.AsInt16()); + + ya0 = AdvSimd.ShiftLeftLogical(ya0, 8); + ya1 = AdvSimd.ShiftLeftLogical(ya1, 8); + + Vector128 rgba0 = AdvSimd.Arm64.ZipLow(ya0, uv0); + Vector128 rgba1 = AdvSimd.Arm64.ZipHigh(ya0, uv0); + Vector128 rgba2 = AdvSimd.Arm64.ZipLow(ya1, uv1); + Vector128 rgba3 = AdvSimd.Arm64.ZipHigh(ya1, uv1); + + rgba0 = AdvSimd.ShiftRightLogicalAdd(alphaMask, rgba0.AsInt32(), 8).AsInt16(); + rgba1 = AdvSimd.ShiftRightLogicalAdd(alphaMask, rgba1.AsInt32(), 8).AsInt16(); + rgba2 = AdvSimd.ShiftRightLogicalAdd(alphaMask, rgba2.AsInt32(), 8).AsInt16(); + rgba3 = AdvSimd.ShiftRightLogicalAdd(alphaMask, rgba3.AsInt32(), 8).AsInt16(); + + Vector128 rgba16_0 = AdvSimd.ZeroExtendWideningLower(rgba0.AsByte().GetLower()).AsInt16(); + Vector128 rgba16_1 = AdvSimd.ZeroExtendWideningUpper(rgba0.AsByte()).AsInt16(); + Vector128 rgba16_2 = AdvSimd.ZeroExtendWideningLower(rgba1.AsByte().GetLower()).AsInt16(); + Vector128 rgba16_3 = AdvSimd.ZeroExtendWideningUpper(rgba1.AsByte()).AsInt16(); + Vector128 rgba16_4 = AdvSimd.ZeroExtendWideningLower(rgba2.AsByte().GetLower()).AsInt16(); + Vector128 rgba16_5 = AdvSimd.ZeroExtendWideningUpper(rgba2.AsByte()).AsInt16(); + Vector128 rgba16_6 = AdvSimd.ZeroExtendWideningLower(rgba3.AsByte().GetLower()).AsInt16(); + Vector128 rgba16_7 = AdvSimd.ZeroExtendWideningUpper(rgba3.AsByte()).AsInt16(); + + rgba16_0 = AdvSimd.ShiftLeftLogical(rgba16_0, 2); + rgba16_1 = AdvSimd.ShiftLeftLogical(rgba16_1, 2); + rgba16_2 = AdvSimd.ShiftLeftLogical(rgba16_2, 2); + rgba16_3 = AdvSimd.ShiftLeftLogical(rgba16_3, 2); + rgba16_4 = AdvSimd.ShiftLeftLogical(rgba16_4, 2); + rgba16_5 = AdvSimd.ShiftLeftLogical(rgba16_5, 2); + rgba16_6 = AdvSimd.ShiftLeftLogical(rgba16_6, 2); + rgba16_7 = AdvSimd.ShiftLeftLogical(rgba16_7, 2); + + AdvSimd.Store((short*)(op + (uint)x + 0), rgba16_0); + AdvSimd.Store((short*)(op + (uint)x + 2), rgba16_1); + AdvSimd.Store((short*)(op + (uint)x + 4), rgba16_2); + AdvSimd.Store((short*)(op + (uint)x + 6), rgba16_3); + AdvSimd.Store((short*)(op + (uint)x + 8), rgba16_4); + AdvSimd.Store((short*)(op + (uint)x + 10), rgba16_5); + AdvSimd.Store((short*)(op + (uint)x + 12), rgba16_6); + AdvSimd.Store((short*)(op + (uint)x + 14), rgba16_7); + } + + for (; x < width; x++, i1p += (x & 1) * 2) + { + Pixel* px = op + (uint)x; + + px->R = Upsample(*i0p++); + px->G = Upsample(*i1p); + px->B = Upsample(*(i1p + 1)); + px->A = 0x3ff; + } + + op += width; + i0p += yStrideGap; + i1p += uvStrideGap; + } + } + } + } + else + { + for (int y = 0; y < height; y++) + { + int uvBase = (y >> 1) * uvStride; + + for (int x = 0; x < width; x++) + { + output.SetR(x, y, Upsample(input.Buffer0[y * yStride + x])); + + int uvOffs = uvBase + (x & ~1); + + output.SetG(x, y, Upsample(input.Buffer1[uvOffs])); + output.SetB(x, y, Upsample(input.Buffer1[uvOffs + 1])); + output.SetA(x, y, 0x3ff); + } + } + } + + input.Return(rm.BufferPool); + + return output; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 HighToLow(Vector128 value) + { + return Sse.MoveHighToLow(value.AsSingle(), value.AsSingle()).AsByte(); + } + + private static InputSurface ReadSurface( + ResourceManager rm, + ref SlotConfig config, + ref SlotSurfaceConfig surfaceConfig, + ref Array8 offsets, + int bytesPerPixel, + int planes) + { + InputSurface surface = new(); + + surface.Initialize(); + + int gobBlocksInY = 1 << surfaceConfig.SlotBlkHeight; + + bool linear = surfaceConfig.SlotBlkKind == 0; + + int lw = surfaceConfig.SlotLumaWidth + 1; + int lh = surfaceConfig.SlotLumaHeight + 1; + + int cw = surfaceConfig.SlotChromaWidth + 1; + int ch = surfaceConfig.SlotChromaHeight + 1; + + // Interlaced inputs have double the height when deinterlaced. + int heightShift = config.FrameFormat.IsField() ? 1 : 0; + + surface.Width = lw; + surface.Height = lh << heightShift; + surface.UvWidth = cw; + surface.UvHeight = ch << heightShift; + + if (planes > 0) + { + surface.SetBuffer0(ReadBuffer(rm, ref config, ref offsets, linear, 0, lw, lh, bytesPerPixel, gobBlocksInY)); + } + + if (planes > 1) + { + surface.SetBuffer1(ReadBuffer(rm, ref config, ref offsets, linear, 1, cw, ch, planes == 2 ? 2 : 1, gobBlocksInY)); + } + + if (planes > 2) + { + surface.SetBuffer2(ReadBuffer(rm, ref config, ref offsets, linear, 2, cw, ch, 1, gobBlocksInY)); + } + + return surface; + } + + private static RentedBuffer ReadBuffer( + ResourceManager rm, + scoped ref SlotConfig config, + scoped ref Array8 offsets, + bool linear, + int plane, + int width, + int height, + int bytesPerPixel, + int gobBlocksInY) + { + FrameFormat frameFormat = config.FrameFormat; + bool isLuma = plane == 0; + bool isField = frameFormat.IsField(); + bool isTopField = frameFormat.IsTopField(isLuma); + int stride = GetPitch(width, bytesPerPixel); + uint offset = GetOffset(ref offsets[0], plane); + + int dstStart = 0; + int dstStride = stride; + + if (isField) + { + dstStart = isTopField ? 0 : stride; + dstStride = stride * 2; + } + + RentedBuffer buffer; + + if (linear) + { + buffer = ReadBufferLinear(rm, offset, width, height, dstStart, dstStride, bytesPerPixel); + } + else + { + buffer = ReadBufferBlockLinear(rm, offset, width, height, dstStart, dstStride, bytesPerPixel, gobBlocksInY); + } + + if (isField || frameFormat.IsInterlaced()) + { + RentedBuffer prevBuffer = RentedBuffer.Empty; + RentedBuffer nextBuffer = RentedBuffer.Empty; + + if (config.PrevFieldEnable) + { + prevBuffer = ReadBufferNoDeinterlace(rm, ref offsets[1], linear, plane, width, height, bytesPerPixel, gobBlocksInY); + } + + if (config.NextFieldEnable) + { + nextBuffer = ReadBufferNoDeinterlace(rm, ref offsets[2], linear, plane, width, height, bytesPerPixel, gobBlocksInY); + } + + int w = width * bytesPerPixel; + + switch (config.DeinterlaceMode) + { + case DeinterlaceMode.Weave: + Scaler.DeinterlaceWeave(buffer.Data, prevBuffer.Data, w, stride, isTopField); + break; + case DeinterlaceMode.BobField: + Scaler.DeinterlaceBob(buffer.Data, w, stride, isTopField); + break; + case DeinterlaceMode.Bob: + bool isCurrentTop = isLuma ? config.IsEven : config.ChromaEven; + Scaler.DeinterlaceBob(buffer.Data, w, stride, isCurrentTop ^ frameFormat.IsInterlacedBottomFirst()); + break; + case DeinterlaceMode.NewBob: + case DeinterlaceMode.Disi1: + Scaler.DeinterlaceMotionAdaptive(buffer.Data, prevBuffer.Data, nextBuffer.Data, w, stride, isTopField); + break; + case DeinterlaceMode.WeaveLumaBobFieldChroma: + if (isLuma) + { + Scaler.DeinterlaceWeave(buffer.Data, prevBuffer.Data, w, stride, isTopField); + } + else + { + Scaler.DeinterlaceBob(buffer.Data, w, stride, isTopField); + } + break; + default: + Logger.Error?.Print(LogClass.Vic, $"Unsupported deinterlace mode \"{config.DeinterlaceMode}\"."); + break; + } + + prevBuffer.Return(rm.BufferPool); + nextBuffer.Return(rm.BufferPool); + } + + return buffer; + } + + private static uint GetOffset(ref PlaneOffsets offsets, int plane) + { + return plane switch + { + 0 => offsets.LumaOffset, + 1 => offsets.ChromaUOffset, + 2 => offsets.ChromaVOffset, + _ => throw new ArgumentOutOfRangeException(nameof(plane)), + }; + } + + private static RentedBuffer ReadBufferNoDeinterlace( + ResourceManager rm, + ref PlaneOffsets offsets, + bool linear, + int plane, + int width, + int height, + int bytesPerPixel, + int gobBlocksInY) + { + int stride = GetPitch(width, bytesPerPixel); + uint offset = GetOffset(ref offsets, plane); + + if (linear) + { + return ReadBufferLinear(rm, offset, width, height, 0, stride, bytesPerPixel); + } + + return ReadBufferBlockLinear(rm, offset, width, height, 0, stride, bytesPerPixel, gobBlocksInY); + } + + private static RentedBuffer ReadBufferLinear( + ResourceManager rm, + uint offset, + int width, + int height, + int dstStart, + int dstStride, + int bytesPerPixel) + { + int srcStride = GetPitch(width, bytesPerPixel); + int inSize = srcStride * height; + + ReadOnlySpan src = rm.MemoryManager.GetSpan(ExtendOffset(offset), inSize); + + int outSize = dstStride * height; + int bufferIndex = rm.BufferPool.RentMinimum(outSize, out byte[] buffer); + Span dst = buffer; + dst = dst[..outSize]; + + for (int y = 0; y < height; y++) + { + src.Slice(y * srcStride, srcStride).CopyTo(dst.Slice(dstStart + y * dstStride, srcStride)); + } + + return new RentedBuffer(dst, bufferIndex); + } + + private static RentedBuffer ReadBufferBlockLinear( + ResourceManager rm, + uint offset, + int width, + int height, + int dstStart, + int dstStride, + int bytesPerPixel, + int gobBlocksInY) + { + int inSize = GetBlockLinearSize(width, height, bytesPerPixel, gobBlocksInY); + + ReadOnlySpan src = rm.MemoryManager.GetSpan(ExtendOffset(offset), inSize); + + int outSize = dstStride * height; + int bufferIndex = rm.BufferPool.RentMinimum(outSize, out byte[] buffer); + Span dst = buffer; + dst = dst[..outSize]; + + LayoutConverter.ConvertBlockLinearToLinear(dst[dstStart..], width, height, dstStride, bytesPerPixel, gobBlocksInY, src); + + return new RentedBuffer(dst, bufferIndex); + } + } +} diff --git a/src/Ryujinx.Graphics.Vic/Image/SurfaceWriter.cs b/src/Ryujinx.Graphics.Vic/Image/SurfaceWriter.cs new file mode 100644 index 00000000..b5008b7b --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Image/SurfaceWriter.cs @@ -0,0 +1,667 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Texture; +using Ryujinx.Graphics.Vic.Types; +using System; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; +using static Ryujinx.Graphics.Vic.Image.SurfaceCommon; + +namespace Ryujinx.Graphics.Vic.Image +{ + class SurfaceWriter + { + public static void Write(ResourceManager rm, Surface input, ref OutputSurfaceConfig config, ref PlaneOffsets offsets) + { + switch (config.OutPixelFormat) + { + case PixelFormat.A8B8G8R8: + case PixelFormat.X8B8G8R8: + WriteA8B8G8R8(rm, input, ref config, ref offsets); + break; + case PixelFormat.A8R8G8B8: + WriteA8R8G8B8(rm, input, ref config, ref offsets); + break; + case PixelFormat.Y8___V8U8_N420: + WriteNv12(rm, input, ref config, ref offsets); + break; + default: + Logger.Error?.Print(LogClass.Vic, $"Unsupported pixel format \"{config.OutPixelFormat}\"."); + break; + } + } + + private unsafe static void WriteA8B8G8R8(ResourceManager rm, Surface input, ref OutputSurfaceConfig config, ref PlaneOffsets offsets) + { + int width = input.Width; + int height = input.Height; + int stride = GetPitch(width, 4); + + int dstIndex = rm.BufferPool.Rent(height * stride, out Span dst); + + if (Sse2.IsSupported) + { + int widthTrunc = width & ~7; + int strideGap = stride - width * 4; + + fixed (Pixel* srcPtr = input.Data) + { + Pixel* ip = srcPtr; + + fixed (byte* dstPtr = dst) + { + byte* op = dstPtr; + + for (int y = 0; y < height; y++, ip += input.Width) + { + int x = 0; + + for (; x < widthTrunc; x += 8) + { + Vector128 pixel12 = Sse2.LoadVector128((ushort*)(ip + (uint)x)); + Vector128 pixel34 = Sse2.LoadVector128((ushort*)(ip + (uint)x + 2)); + Vector128 pixel56 = Sse2.LoadVector128((ushort*)(ip + (uint)x + 4)); + Vector128 pixel78 = Sse2.LoadVector128((ushort*)(ip + (uint)x + 6)); + + pixel12 = Sse2.ShiftRightLogical(pixel12, 2); + pixel34 = Sse2.ShiftRightLogical(pixel34, 2); + pixel56 = Sse2.ShiftRightLogical(pixel56, 2); + pixel78 = Sse2.ShiftRightLogical(pixel78, 2); + + Vector128 pixel1234 = Sse2.PackUnsignedSaturate(pixel12.AsInt16(), pixel34.AsInt16()); + Vector128 pixel5678 = Sse2.PackUnsignedSaturate(pixel56.AsInt16(), pixel78.AsInt16()); + + Sse2.Store(op + 0x00, pixel1234); + Sse2.Store(op + 0x10, pixel5678); + + op += 0x20; + } + + for (; x < width; x++) + { + Pixel* px = ip + (uint)x; + + *(op + 0) = Downsample(px->R); + *(op + 1) = Downsample(px->G); + *(op + 2) = Downsample(px->B); + *(op + 3) = Downsample(px->A); + + op += 4; + } + + op += strideGap; + } + } + } + } + else if (AdvSimd.IsSupported) + { + int widthTrunc = width & ~7; + int strideGap = stride - width * 4; + + fixed (Pixel* srcPtr = input.Data) + { + Pixel* ip = srcPtr; + + fixed (byte* dstPtr = dst) + { + byte* op = dstPtr; + + for (int y = 0; y < height; y++, ip += input.Width) + { + int x = 0; + + for (; x < widthTrunc; x += 8) + { + Vector128 pixel12 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x)); + Vector128 pixel34 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x + 2)); + Vector128 pixel56 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x + 4)); + Vector128 pixel78 = AdvSimd.LoadVector128((ushort*)(ip + (uint)x + 6)); + + pixel12 = AdvSimd.ShiftRightLogical(pixel12, 2); + pixel34 = AdvSimd.ShiftRightLogical(pixel34, 2); + pixel56 = AdvSimd.ShiftRightLogical(pixel56, 2); + pixel78 = AdvSimd.ShiftRightLogical(pixel78, 2); + + Vector64 lower12 = AdvSimd.ExtractNarrowingLower(pixel12.AsUInt16()); + Vector64 lower56 = AdvSimd.ExtractNarrowingLower(pixel56.AsUInt16()); + + Vector128 pixel1234 = AdvSimd.ExtractNarrowingUpper(lower12, pixel34.AsUInt16()); + Vector128 pixel5678 = AdvSimd.ExtractNarrowingUpper(lower56, pixel78.AsUInt16()); + + AdvSimd.Store(op + 0x00, pixel1234); + AdvSimd.Store(op + 0x10, pixel5678); + + op += 0x20; + } + + for (; x < width; x++) + { + Pixel* px = ip + (uint)x; + + *(op + 0) = Downsample(px->R); + *(op + 1) = Downsample(px->G); + *(op + 2) = Downsample(px->B); + *(op + 3) = Downsample(px->A); + + op += 4; + } + + op += strideGap; + } + } + } + } + else + { + for (int y = 0; y < height; y++) + { + int baseOffs = y * stride; + + for (int x = 0; x < width; x++) + { + int offs = baseOffs + x * 4; + + dst[offs + 0] = Downsample(input.GetR(x, y)); + dst[offs + 1] = Downsample(input.GetG(x, y)); + dst[offs + 2] = Downsample(input.GetB(x, y)); + dst[offs + 3] = Downsample(input.GetA(x, y)); + } + } + } + + bool outLinear = config.OutBlkKind == 0; + + int gobBlocksInY = 1 << config.OutBlkHeight; + + WriteBuffer(rm, dst, offsets.LumaOffset, outLinear, width, height, 4, gobBlocksInY); + + rm.BufferPool.Return(dstIndex); + } + + private unsafe static void WriteA8R8G8B8(ResourceManager rm, Surface input, ref OutputSurfaceConfig config, ref PlaneOffsets offsets) + { + int width = input.Width; + int height = input.Height; + int stride = GetPitch(width, 4); + + int dstIndex = rm.BufferPool.Rent(height * stride, out Span dst); + + if (Ssse3.IsSupported) + { + Vector128 shuffleMask = Vector128.Create( + (byte)2, (byte)1, (byte)0, (byte)3, + (byte)6, (byte)5, (byte)4, (byte)7, + (byte)10, (byte)9, (byte)8, (byte)11, + (byte)14, (byte)13, (byte)12, (byte)15); + + int widthTrunc = width & ~7; + int strideGap = stride - width * 4; + + fixed (Pixel* srcPtr = input.Data) + { + Pixel* ip = srcPtr; + + fixed (byte* dstPtr = dst) + { + byte* op = dstPtr; + + for (int y = 0; y < height; y++, ip += input.Width) + { + int x = 0; + + for (; x < widthTrunc; x += 8) + { + Vector128 pixel12 = Sse2.LoadVector128((ushort*)(ip + (uint)x)); + Vector128 pixel34 = Sse2.LoadVector128((ushort*)(ip + (uint)x + 2)); + Vector128 pixel56 = Sse2.LoadVector128((ushort*)(ip + (uint)x + 4)); + Vector128 pixel78 = Sse2.LoadVector128((ushort*)(ip + (uint)x + 6)); + + pixel12 = Sse2.ShiftRightLogical(pixel12, 2); + pixel34 = Sse2.ShiftRightLogical(pixel34, 2); + pixel56 = Sse2.ShiftRightLogical(pixel56, 2); + pixel78 = Sse2.ShiftRightLogical(pixel78, 2); + + Vector128 pixel1234 = Sse2.PackUnsignedSaturate(pixel12.AsInt16(), pixel34.AsInt16()); + Vector128 pixel5678 = Sse2.PackUnsignedSaturate(pixel56.AsInt16(), pixel78.AsInt16()); + + pixel1234 = Ssse3.Shuffle(pixel1234, shuffleMask); + pixel5678 = Ssse3.Shuffle(pixel5678, shuffleMask); + + Sse2.Store(op + 0x00, pixel1234); + Sse2.Store(op + 0x10, pixel5678); + + op += 0x20; + } + + for (; x < width; x++) + { + Pixel* px = ip + (uint)x; + + *(op + 0) = Downsample(px->B); + *(op + 1) = Downsample(px->G); + *(op + 2) = Downsample(px->R); + *(op + 3) = Downsample(px->A); + + op += 4; + } + + op += strideGap; + } + } + } + } + else + { + for (int y = 0; y < height; y++) + { + int baseOffs = y * stride; + + for (int x = 0; x < width; x++) + { + int offs = baseOffs + x * 4; + + dst[offs + 0] = Downsample(input.GetB(x, y)); + dst[offs + 1] = Downsample(input.GetG(x, y)); + dst[offs + 2] = Downsample(input.GetR(x, y)); + dst[offs + 3] = Downsample(input.GetA(x, y)); + } + } + } + + bool outLinear = config.OutBlkKind == 0; + + int gobBlocksInY = 1 << config.OutBlkHeight; + + WriteBuffer(rm, dst, offsets.LumaOffset, outLinear, width, height, 4, gobBlocksInY); + + rm.BufferPool.Return(dstIndex); + } + + private unsafe static void WriteNv12(ResourceManager rm, Surface input, ref OutputSurfaceConfig config, ref PlaneOffsets offsets) + { + int gobBlocksInY = 1 << config.OutBlkHeight; + + bool outLinear = config.OutBlkKind == 0; + + int width = Math.Min(config.OutLumaWidth + 1, input.Width); + int height = Math.Min(config.OutLumaHeight + 1, input.Height); + int yStride = GetPitch(config.OutLumaWidth + 1, 1); + + int dstYIndex = rm.BufferPool.Rent((config.OutLumaHeight + 1) * yStride, out Span dstY); + + if (Sse41.IsSupported) + { + Vector128 mask = Vector128.Create(0xffffUL).AsUInt16(); + + int widthTrunc = width & ~0xf; + int strideGap = yStride - width; + + fixed (Pixel* srcPtr = input.Data) + { + Pixel* ip = srcPtr; + + fixed (byte* dstPtr = dstY) + { + byte* op = dstPtr; + + for (int y = 0; y < height; y++, ip += input.Width) + { + int x = 0; + + for (; x < widthTrunc; x += 16) + { + byte* baseOffset = (byte*)(ip + (ulong)(uint)x); + + Vector128 pixelp1 = Sse2.LoadVector128((ushort*)baseOffset); + Vector128 pixelp2 = Sse2.LoadVector128((ushort*)(baseOffset + 0x10)); + Vector128 pixelp3 = Sse2.LoadVector128((ushort*)(baseOffset + 0x20)); + Vector128 pixelp4 = Sse2.LoadVector128((ushort*)(baseOffset + 0x30)); + Vector128 pixelp5 = Sse2.LoadVector128((ushort*)(baseOffset + 0x40)); + Vector128 pixelp6 = Sse2.LoadVector128((ushort*)(baseOffset + 0x50)); + Vector128 pixelp7 = Sse2.LoadVector128((ushort*)(baseOffset + 0x60)); + Vector128 pixelp8 = Sse2.LoadVector128((ushort*)(baseOffset + 0x70)); + + pixelp1 = Sse2.And(pixelp1, mask); + pixelp2 = Sse2.And(pixelp2, mask); + pixelp3 = Sse2.And(pixelp3, mask); + pixelp4 = Sse2.And(pixelp4, mask); + pixelp5 = Sse2.And(pixelp5, mask); + pixelp6 = Sse2.And(pixelp6, mask); + pixelp7 = Sse2.And(pixelp7, mask); + pixelp8 = Sse2.And(pixelp8, mask); + + Vector128 pixelq1 = Sse41.PackUnsignedSaturate(pixelp1.AsInt32(), pixelp2.AsInt32()); + Vector128 pixelq2 = Sse41.PackUnsignedSaturate(pixelp3.AsInt32(), pixelp4.AsInt32()); + Vector128 pixelq3 = Sse41.PackUnsignedSaturate(pixelp5.AsInt32(), pixelp6.AsInt32()); + Vector128 pixelq4 = Sse41.PackUnsignedSaturate(pixelp7.AsInt32(), pixelp8.AsInt32()); + + pixelq1 = Sse41.PackUnsignedSaturate(pixelq1.AsInt32(), pixelq2.AsInt32()); + pixelq2 = Sse41.PackUnsignedSaturate(pixelq3.AsInt32(), pixelq4.AsInt32()); + + pixelq1 = Sse2.ShiftRightLogical(pixelq1, 2); + pixelq2 = Sse2.ShiftRightLogical(pixelq2, 2); + + Vector128 pixel = Sse2.PackUnsignedSaturate(pixelq1.AsInt16(), pixelq2.AsInt16()); + + Sse2.Store(op, pixel); + + op += 0x10; + } + + for (; x < width; x++) + { + Pixel* px = ip + (uint)x; + + *op++ = Downsample(px->R); + } + + op += strideGap; + } + } + } + } + else if (AdvSimd.IsSupported) + { + Vector128 mask = Vector128.Create(0xffffUL).AsUInt16(); + + int widthTrunc = width & ~0xf; + int strideGap = yStride - width; + + fixed (Pixel* srcPtr = input.Data) + { + Pixel* ip = srcPtr; + + fixed (byte* dstPtr = dstY) + { + byte* op = dstPtr; + + for (int y = 0; y < height; y++, ip += input.Width) + { + int x = 0; + + for (; x < widthTrunc; x += 16) + { + byte* baseOffset = (byte*)(ip + (ulong)(uint)x); + + Vector128 pixelp1 = AdvSimd.LoadVector128((ushort*)baseOffset); + Vector128 pixelp2 = AdvSimd.LoadVector128((ushort*)(baseOffset + 0x10)); + Vector128 pixelp3 = AdvSimd.LoadVector128((ushort*)(baseOffset + 0x20)); + Vector128 pixelp4 = AdvSimd.LoadVector128((ushort*)(baseOffset + 0x30)); + Vector128 pixelp5 = AdvSimd.LoadVector128((ushort*)(baseOffset + 0x40)); + Vector128 pixelp6 = AdvSimd.LoadVector128((ushort*)(baseOffset + 0x50)); + Vector128 pixelp7 = AdvSimd.LoadVector128((ushort*)(baseOffset + 0x60)); + Vector128 pixelp8 = AdvSimd.LoadVector128((ushort*)(baseOffset + 0x70)); + + pixelp1 = AdvSimd.And(pixelp1, mask); + pixelp2 = AdvSimd.And(pixelp2, mask); + pixelp3 = AdvSimd.And(pixelp3, mask); + pixelp4 = AdvSimd.And(pixelp4, mask); + pixelp5 = AdvSimd.And(pixelp5, mask); + pixelp6 = AdvSimd.And(pixelp6, mask); + pixelp7 = AdvSimd.And(pixelp7, mask); + pixelp8 = AdvSimd.And(pixelp8, mask); + + Vector64 lowerp1 = AdvSimd.ExtractNarrowingLower(pixelp1.AsUInt32()); + Vector64 lowerp3 = AdvSimd.ExtractNarrowingLower(pixelp3.AsUInt32()); + Vector64 lowerp5 = AdvSimd.ExtractNarrowingLower(pixelp5.AsUInt32()); + Vector64 lowerp7 = AdvSimd.ExtractNarrowingLower(pixelp7.AsUInt32()); + + Vector128 pixelq1 = AdvSimd.ExtractNarrowingUpper(lowerp1, pixelp2.AsUInt32()); + Vector128 pixelq2 = AdvSimd.ExtractNarrowingUpper(lowerp3, pixelp4.AsUInt32()); + Vector128 pixelq3 = AdvSimd.ExtractNarrowingUpper(lowerp5, pixelp6.AsUInt32()); + Vector128 pixelq4 = AdvSimd.ExtractNarrowingUpper(lowerp7, pixelp8.AsUInt32()); + + Vector64 lowerq1 = AdvSimd.ExtractNarrowingLower(pixelq1.AsUInt32()); + Vector64 lowerq3 = AdvSimd.ExtractNarrowingLower(pixelq3.AsUInt32()); + + pixelq1 = AdvSimd.ExtractNarrowingUpper(lowerq1, pixelq2.AsUInt32()); + pixelq2 = AdvSimd.ExtractNarrowingUpper(lowerq3, pixelq4.AsUInt32()); + + pixelq1 = AdvSimd.ShiftRightLogical(pixelq1, 2); + pixelq2 = AdvSimd.ShiftRightLogical(pixelq2, 2); + + Vector64 pixelLower = AdvSimd.ExtractNarrowingLower(pixelq1.AsUInt16()); + + Vector128 pixel = AdvSimd.ExtractNarrowingUpper(pixelLower, pixelq2.AsUInt16()); + + AdvSimd.Store(op, pixel); + + op += 0x10; + } + + for (; x < width; x++) + { + Pixel* px = ip + (uint)x; + + *op++ = Downsample(px->R); + } + + op += strideGap; + } + } + } + } + else + { + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + dstY[y * yStride + x] = Downsample(input.GetR(x, y)); + } + } + } + + WriteBuffer( + rm, + dstY, + offsets.LumaOffset, + outLinear, + config.OutLumaWidth + 1, + config.OutLumaHeight + 1, + 1, + gobBlocksInY); + + rm.BufferPool.Return(dstYIndex); + + int uvWidth = Math.Min(config.OutChromaWidth + 1, (width + 1) >> 1); + int uvHeight = Math.Min(config.OutChromaHeight + 1, (height + 1) >> 1); + int uvStride = GetPitch(config.OutChromaWidth + 1, 2); + + int dstUvIndex = rm.BufferPool.Rent((config.OutChromaHeight + 1) * uvStride, out Span dstUv); + + if (Sse2.IsSupported) + { + int widthTrunc = uvWidth & ~7; + int strideGap = uvStride - uvWidth * 2; + + fixed (Pixel* srcPtr = input.Data) + { + Pixel* ip = srcPtr; + + fixed (byte* dstPtr = dstUv) + { + byte* op = dstPtr; + + for (int y = 0; y < uvHeight; y++, ip += input.Width * 2) + { + int x = 0; + + for (; x < widthTrunc; x += 8) + { + byte* baseOffset = (byte*)ip + (ulong)(uint)x * 16; + + Vector128 pixel1 = Sse2.LoadScalarVector128((uint*)(baseOffset + 0x02)); + Vector128 pixel2 = Sse2.LoadScalarVector128((uint*)(baseOffset + 0x12)); + Vector128 pixel3 = Sse2.LoadScalarVector128((uint*)(baseOffset + 0x22)); + Vector128 pixel4 = Sse2.LoadScalarVector128((uint*)(baseOffset + 0x32)); + Vector128 pixel5 = Sse2.LoadScalarVector128((uint*)(baseOffset + 0x42)); + Vector128 pixel6 = Sse2.LoadScalarVector128((uint*)(baseOffset + 0x52)); + Vector128 pixel7 = Sse2.LoadScalarVector128((uint*)(baseOffset + 0x62)); + Vector128 pixel8 = Sse2.LoadScalarVector128((uint*)(baseOffset + 0x72)); + + Vector128 pixel12 = Sse2.UnpackLow(pixel1, pixel2); + Vector128 pixel34 = Sse2.UnpackLow(pixel3, pixel4); + Vector128 pixel56 = Sse2.UnpackLow(pixel5, pixel6); + Vector128 pixel78 = Sse2.UnpackLow(pixel7, pixel8); + + Vector128 pixel1234 = Sse2.UnpackLow(pixel12.AsUInt64(), pixel34.AsUInt64()); + Vector128 pixel5678 = Sse2.UnpackLow(pixel56.AsUInt64(), pixel78.AsUInt64()); + + pixel1234 = Sse2.ShiftRightLogical(pixel1234, 2); + pixel5678 = Sse2.ShiftRightLogical(pixel5678, 2); + + Vector128 pixel = Sse2.PackUnsignedSaturate(pixel1234.AsInt16(), pixel5678.AsInt16()); + + Sse2.Store(op, pixel); + + op += 0x10; + } + + for (; x < uvWidth; x++) + { + Pixel* px = ip + (uint)(x << 1); + + *op++ = Downsample(px->G); + *op++ = Downsample(px->B); + } + + op += strideGap; + } + } + } + } + else if (AdvSimd.Arm64.IsSupported) + { + int widthTrunc = uvWidth & ~7; + int strideGap = uvStride - uvWidth * 2; + + fixed (Pixel* srcPtr = input.Data) + { + Pixel* ip = srcPtr; + + fixed (byte* dstPtr = dstUv) + { + byte* op = dstPtr; + + for (int y = 0; y < uvHeight; y++, ip += input.Width * 2) + { + int x = 0; + + for (; x < widthTrunc; x += 8) + { + byte* baseOffset = (byte*)ip + (ulong)(uint)x * 16; + + Vector128 pixel1 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x02)); + Vector128 pixel2 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x12)); + Vector128 pixel3 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x22)); + Vector128 pixel4 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x32)); + Vector128 pixel5 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x42)); + Vector128 pixel6 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x52)); + Vector128 pixel7 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x62)); + Vector128 pixel8 = AdvSimd.LoadAndReplicateToVector128((uint*)(baseOffset + 0x72)); + + Vector128 pixel12 = AdvSimd.Arm64.ZipLow(pixel1, pixel2); + Vector128 pixel34 = AdvSimd.Arm64.ZipLow(pixel3, pixel4); + Vector128 pixel56 = AdvSimd.Arm64.ZipLow(pixel5, pixel6); + Vector128 pixel78 = AdvSimd.Arm64.ZipLow(pixel7, pixel8); + + Vector128 pixel1234 = AdvSimd.Arm64.ZipLow(pixel12.AsUInt64(), pixel34.AsUInt64()); + Vector128 pixel5678 = AdvSimd.Arm64.ZipLow(pixel56.AsUInt64(), pixel78.AsUInt64()); + + pixel1234 = AdvSimd.ShiftRightLogical(pixel1234, 2); + pixel5678 = AdvSimd.ShiftRightLogical(pixel5678, 2); + + Vector64 pixelLower = AdvSimd.ExtractNarrowingLower(pixel1234.AsUInt16()); + + Vector128 pixel = AdvSimd.ExtractNarrowingUpper(pixelLower, pixel5678.AsUInt16()); + + AdvSimd.Store(op, pixel); + + op += 0x10; + } + + for (; x < uvWidth; x++) + { + Pixel* px = ip + (uint)(x << 1); + + *op++ = Downsample(px->G); + *op++ = Downsample(px->B); + } + + op += strideGap; + } + } + } + } + else + { + for (int y = 0; y < uvHeight; y++) + { + for (int x = 0; x < uvWidth; x++) + { + int xx = x << 1; + int yy = y << 1; + + int uvOffs = y * uvStride + xx; + + dstUv[uvOffs + 0] = Downsample(input.GetG(xx, yy)); + dstUv[uvOffs + 1] = Downsample(input.GetB(xx, yy)); + } + } + } + + WriteBuffer( + rm, + dstUv, + offsets.ChromaUOffset, + outLinear, + config.OutChromaWidth + 1, + config.OutChromaHeight + 1, 2, + gobBlocksInY); + + rm.BufferPool.Return(dstUvIndex); + } + + private static void WriteBuffer( + ResourceManager rm, + ReadOnlySpan src, + uint offset, + bool linear, + int width, + int height, + int bytesPerPixel, + int gobBlocksInY) + { + if (linear) + { + rm.MemoryManager.Write(ExtendOffset(offset), src); + return; + } + + WriteBuffer(rm, src, offset, width, height, bytesPerPixel, gobBlocksInY); + } + + private static void WriteBuffer( + ResourceManager rm, + ReadOnlySpan src, + uint offset, + int width, + int height, + int bytesPerPixel, + int gobBlocksInY) + { + int outSize = GetBlockLinearSize(width, height, bytesPerPixel, gobBlocksInY); + int dstStride = GetPitch(width, bytesPerPixel); + + int dstIndex = rm.BufferPool.Rent(outSize, out Span dst); + + LayoutConverter.ConvertLinearToBlockLinear(dst, width, height, dstStride, bytesPerPixel, gobBlocksInY, src); + + rm.MemoryManager.Write(ExtendOffset(offset), dst); + + rm.BufferPool.Return(dstIndex); + } + } +} diff --git a/src/Ryujinx.Graphics.Vic/Rectangle.cs b/src/Ryujinx.Graphics.Vic/Rectangle.cs new file mode 100644 index 00000000..b12560bd --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Rectangle.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Graphics.Vic +{ + readonly struct Rectangle + { + public readonly int X; + public readonly int Y; + public readonly int Width; + public readonly int Height; + + public Rectangle(int x, int y, int width, int height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + } +} diff --git a/src/Ryujinx.Graphics.Vic/ResourceManager.cs b/src/Ryujinx.Graphics.Vic/ResourceManager.cs new file mode 100644 index 00000000..e7d7ef74 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/ResourceManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Vic.Image; + +namespace Ryujinx.Graphics.Vic +{ + readonly struct ResourceManager + { + public DeviceMemoryManager MemoryManager { get; } + public BufferPool SurfacePool { get; } + public BufferPool BufferPool { get; } + + public ResourceManager(DeviceMemoryManager mm, BufferPool surfacePool, BufferPool bufferPool) + { + MemoryManager = mm; + SurfacePool = surfacePool; + BufferPool = bufferPool; + } + } +} diff --git a/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj b/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj new file mode 100644 index 00000000..a6c4fb2b --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + true + + + + + + + + + diff --git a/src/Ryujinx.Graphics.Vic/Scaler.cs b/src/Ryujinx.Graphics.Vic/Scaler.cs new file mode 100644 index 00000000..7d539299 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Scaler.cs @@ -0,0 +1,124 @@ +using System; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Graphics.Vic +{ + static class Scaler + { + public static void DeinterlaceWeave(Span data, ReadOnlySpan prevData, int width, int fieldSize, bool isTopField) + { + // Prev I Curr I Curr P + // TTTTTTTT BBBBBBBB TTTTTTTT + // -------- -------- BBBBBBBB + + if (isTopField) + { + for (int offset = 0; offset < data.Length; offset += fieldSize * 2) + { + prevData.Slice(offset >> 1, width).CopyTo(data.Slice(offset + fieldSize, width)); + } + } + else + { + for (int offset = 0; offset < data.Length; offset += fieldSize * 2) + { + prevData.Slice(offset >> 1, width).CopyTo(data.Slice(offset, width)); + } + } + } + + public static void DeinterlaceBob(Span data, int width, int fieldSize, bool isTopField) + { + // Curr I Curr P + // TTTTTTTT TTTTTTTT + // -------- TTTTTTTT + + if (isTopField) + { + for (int offset = 0; offset < data.Length; offset += fieldSize * 2) + { + data.Slice(offset, width).CopyTo(data.Slice(offset + fieldSize, width)); + } + } + else + { + for (int offset = 0; offset < data.Length; offset += fieldSize * 2) + { + data.Slice(offset + fieldSize, width).CopyTo(data.Slice(offset, width)); + } + } + } + + public unsafe static void DeinterlaceMotionAdaptive( + Span data, + ReadOnlySpan prevData, + ReadOnlySpan nextData, + int width, + int fieldSize, + bool isTopField) + { + // Very simple motion adaptive algorithm. + // If the pixel changed between previous and next frame, use Bob, otherwise use Weave. + // + // Example pseudo code: + // C_even = (P_even == N_even) ? P_even : C_odd + // Where: C is current frame, P is previous frame and N is next frame, and even/odd are the fields. + // + // Note: This does not fully match the hardware algorithm. + // The motion adaptive deinterlacing implemented on hardware is considerably more complex, + // and hard to implement accurately without proper documentation as for example, the + // method used for motion estimation is unknown. + + int start = isTopField ? fieldSize : 0; + int otherFieldOffset = isTopField ? -fieldSize : fieldSize; + + fixed (byte* pData = data, pPrevData = prevData, pNextData = nextData) + { + for (int offset = start; offset < data.Length; offset += fieldSize * 2) + { + int refOffset = (offset - start) >> 1; + int x = 0; + + if (Avx2.IsSupported) + { + for (; x < (width & ~0x1f); x += 32) + { + Vector256 prevPixels = Avx.LoadVector256(pPrevData + refOffset + x); + Vector256 nextPixels = Avx.LoadVector256(pNextData + refOffset + x); + Vector256 bob = Avx.LoadVector256(pData + offset + otherFieldOffset + x); + Vector256 diff = Avx2.CompareEqual(prevPixels, nextPixels); + Avx.Store(pData + offset + x, Avx2.BlendVariable(bob, prevPixels, diff)); + } + } + else if (Sse41.IsSupported) + { + for (; x < (width & ~0xf); x += 16) + { + Vector128 prevPixels = Sse2.LoadVector128(pPrevData + refOffset + x); + Vector128 nextPixels = Sse2.LoadVector128(pNextData + refOffset + x); + Vector128 bob = Sse2.LoadVector128(pData + offset + otherFieldOffset + x); + Vector128 diff = Sse2.CompareEqual(prevPixels, nextPixels); + Sse2.Store(pData + offset + x, Sse41.BlendVariable(bob, prevPixels, diff)); + } + } + + for (; x < width; x++) + { + byte prevPixel = prevData[refOffset + x]; + byte nextPixel = nextData[refOffset + x]; + + if (nextPixel != prevPixel) + { + data[offset + x] = data[offset + otherFieldOffset + x]; + } + else + { + data[offset + x] = prevPixel; + } + } + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vic/Types/BlendingSlotStruct.cs b/src/Ryujinx.Graphics.Vic/Types/BlendingSlotStruct.cs new file mode 100644 index 00000000..0037befe --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Types/BlendingSlotStruct.cs @@ -0,0 +1,29 @@ +using Ryujinx.Common.Utilities; + +namespace Ryujinx.Graphics.Vic.Types +{ + readonly struct BlendingSlotStruct + { + private readonly long _word0; + private readonly long _word1; + + public int AlphaK1 => (int)_word0.Extract(0, 10); + public int AlphaK2 => (int)_word0.Extract(16, 10); + public int SrcFactCMatchSelect => (int)_word0.Extract(32, 3); + public int DstFactCMatchSelect => (int)_word0.Extract(36, 3); + public int SrcFactAMatchSelect => (int)_word0.Extract(40, 3); + public int DstFactAMatchSelect => (int)_word0.Extract(44, 3); + public int OverrideR => (int)_word1.Extract(66, 10); + public int OverrideG => (int)_word1.Extract(76, 10); + public int OverrideB => (int)_word1.Extract(86, 10); + public int OverrideA => (int)_word1.Extract(96, 10); + public bool UseOverrideR => _word1.Extract(108); + public bool UseOverrideG => _word1.Extract(109); + public bool UseOverrideB => _word1.Extract(110); + public bool UseOverrideA => _word1.Extract(111); + public bool MaskR => _word1.Extract(112); + public bool MaskG => _word1.Extract(113); + public bool MaskB => _word1.Extract(114); + public bool MaskA => _word1.Extract(115); + } +} diff --git a/src/Ryujinx.Graphics.Vic/Types/ClearRectStruct.cs b/src/Ryujinx.Graphics.Vic/Types/ClearRectStruct.cs new file mode 100644 index 00000000..2e78f5d4 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Types/ClearRectStruct.cs @@ -0,0 +1,21 @@ +using Ryujinx.Common.Utilities; + +namespace Ryujinx.Graphics.Vic.Types +{ + readonly struct ClearRectStruct + { +#pragma warning disable CS0649 // Field is never assigned to + private readonly long _word0; + private readonly long _word1; +#pragma warning restore CS0649 + + public int ClearRect0Left => (int)_word0.Extract(0, 14); + public int ClearRect0Right => (int)_word0.Extract(16, 14); + public int ClearRect0Top => (int)_word0.Extract(32, 14); + public int ClearRect0Bottom => (int)_word0.Extract(48, 14); + public int ClearRect1Left => (int)_word1.Extract(64, 14); + public int ClearRect1Right => (int)_word1.Extract(80, 14); + public int ClearRect1Top => (int)_word1.Extract(96, 14); + public int ClearRect1Bottom => (int)_word1.Extract(112, 14); + } +} diff --git a/src/Ryujinx.Graphics.Vic/Types/ConfigStruct.cs b/src/Ryujinx.Graphics.Vic/Types/ConfigStruct.cs new file mode 100644 index 00000000..2496a62e --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Types/ConfigStruct.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Vic.Types +{ + struct ConfigStruct + { +#pragma warning disable CS0649 // Field is never assigned to + public PipeConfig PipeConfig; + public OutputConfig OutputConfig; + public OutputSurfaceConfig OutputSurfaceConfig; + public MatrixStruct OutColorMatrix; + public Array4 ClearRectStruct; + public Array8 SlotStruct; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Vic/Types/DeinterlaceMode.cs b/src/Ryujinx.Graphics.Vic/Types/DeinterlaceMode.cs new file mode 100644 index 00000000..f1b729f0 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Types/DeinterlaceMode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Vic.Types +{ + enum DeinterlaceMode + { + Weave, + BobField, + Bob, + NewBob, + Disi1, + WeaveLumaBobFieldChroma, + } +} diff --git a/src/Ryujinx.Graphics.Vic/Types/FrameFormat.cs b/src/Ryujinx.Graphics.Vic/Types/FrameFormat.cs new file mode 100644 index 00000000..b8f82163 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Types/FrameFormat.cs @@ -0,0 +1,79 @@ +namespace Ryujinx.Graphics.Vic.Types +{ + enum FrameFormat + { + Progressive, + InterlacedTopFieldFirst, + InterlacedBottomFieldFirst, + TopField, + BottomField, + SubPicProgressive, + SubPicInterlacedTopFieldFirst, + SubPicInterlacedBottomFieldFirst, + SubPicTopField, + SubPicBottomField, + TopFieldChromaBottom, + BottomFieldChromaTop, + SubPicTopFieldChromaBottom, + SubPicBottomFieldChromaTop, + } + + static class FrameFormatExtensions + { + public static bool IsField(this FrameFormat frameFormat) + { + switch (frameFormat) + { + case FrameFormat.TopField: + case FrameFormat.BottomField: + case FrameFormat.SubPicTopField: + case FrameFormat.SubPicBottomField: + case FrameFormat.TopFieldChromaBottom: + case FrameFormat.BottomFieldChromaTop: + case FrameFormat.SubPicTopFieldChromaBottom: + case FrameFormat.SubPicBottomFieldChromaTop: + return true; + } + + return false; + } + + public static bool IsInterlaced(this FrameFormat frameFormat) + { + switch (frameFormat) + { + case FrameFormat.InterlacedTopFieldFirst: + case FrameFormat.InterlacedBottomFieldFirst: + case FrameFormat.SubPicInterlacedTopFieldFirst: + case FrameFormat.SubPicInterlacedBottomFieldFirst: + return true; + } + + return false; + } + + public static bool IsInterlacedBottomFirst(this FrameFormat frameFormat) + { + return frameFormat == FrameFormat.InterlacedBottomFieldFirst || + frameFormat == FrameFormat.SubPicInterlacedBottomFieldFirst; + } + + public static bool IsTopField(this FrameFormat frameFormat, bool isLuma) + { + switch (frameFormat) + { + case FrameFormat.TopField: + case FrameFormat.SubPicTopField: + return true; + case FrameFormat.TopFieldChromaBottom: + case FrameFormat.SubPicTopFieldChromaBottom: + return isLuma; + case FrameFormat.BottomFieldChromaTop: + case FrameFormat.SubPicBottomFieldChromaTop: + return !isLuma; + } + + return false; + } + } +} diff --git a/src/Ryujinx.Graphics.Vic/Types/LumaKeyStruct.cs b/src/Ryujinx.Graphics.Vic/Types/LumaKeyStruct.cs new file mode 100644 index 00000000..87deb4e4 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Types/LumaKeyStruct.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common.Utilities; + +namespace Ryujinx.Graphics.Vic.Types +{ + readonly struct LumaKeyStruct + { + private readonly long _word0; + private readonly long _word1; + + public int LumaCoeff0 => (int)_word0.Extract(0, 20); + public int LumaCoeff1 => (int)_word0.Extract(20, 20); + public int LumaCoeff2 => (int)_word0.Extract(40, 20); + public int LumaRShift => (int)_word0.Extract(60, 4); + public int LumaCoeff3 => (int)_word1.Extract(64, 20); + public int LumaKeyLower => (int)_word1.Extract(84, 10); + public int LumaKeyUpper => (int)_word1.Extract(94, 10); + public bool LumaKeyEnabled => _word1.Extract(104); + } +} diff --git a/src/Ryujinx.Graphics.Vic/Types/MatrixStruct.cs b/src/Ryujinx.Graphics.Vic/Types/MatrixStruct.cs new file mode 100644 index 00000000..b634e3f4 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Types/MatrixStruct.cs @@ -0,0 +1,27 @@ +using Ryujinx.Common.Utilities; + +namespace Ryujinx.Graphics.Vic.Types +{ + readonly struct MatrixStruct + { + private readonly long _word0; + private readonly long _word1; + private readonly long _word2; + private readonly long _word3; + + public int MatrixCoeff00 => (int)_word0.ExtractSx(0, 20); + public int MatrixCoeff10 => (int)_word0.ExtractSx(20, 20); + public int MatrixCoeff20 => (int)_word0.ExtractSx(40, 20); + public int MatrixRShift => (int)_word0.Extract(60, 4); + public int MatrixCoeff01 => (int)_word1.ExtractSx(64, 20); + public int MatrixCoeff11 => (int)_word1.ExtractSx(84, 20); + public int MatrixCoeff21 => (int)_word1.ExtractSx(104, 20); + public bool MatrixEnable => _word1.Extract(127); + public int MatrixCoeff02 => (int)_word2.ExtractSx(128, 20); + public int MatrixCoeff12 => (int)_word2.ExtractSx(148, 20); + public int MatrixCoeff22 => (int)_word2.ExtractSx(168, 20); + public int MatrixCoeff03 => (int)_word3.ExtractSx(192, 20); + public int MatrixCoeff13 => (int)_word3.ExtractSx(212, 20); + public int MatrixCoeff23 => (int)_word3.ExtractSx(232, 20); + } +} diff --git a/src/Ryujinx.Graphics.Vic/Types/OutputConfig.cs b/src/Ryujinx.Graphics.Vic/Types/OutputConfig.cs new file mode 100644 index 00000000..3ce6c93c --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Types/OutputConfig.cs @@ -0,0 +1,27 @@ +using Ryujinx.Common.Utilities; + +namespace Ryujinx.Graphics.Vic.Types +{ + readonly struct OutputConfig + { +#pragma warning disable CS0649 // Field is never assigned to + private readonly long _word0; + private readonly long _word1; +#pragma warning restore CS0649 + + public int AlphaFillMode => (int)_word0.Extract(0, 3); + public int AlphaFillSlot => (int)_word0.Extract(3, 3); + public int BackgroundAlpha => (int)_word0.Extract(6, 10); + public int BackgroundR => (int)_word0.Extract(16, 10); + public int BackgroundG => (int)_word0.Extract(26, 10); + public int BackgroundB => (int)_word0.Extract(36, 10); + public int RegammaMode => (int)_word0.Extract(46, 2); + public bool OutputFlipX => _word0.Extract(48); + public bool OutputFlipY => _word0.Extract(49); + public bool OutputTranspose => _word0.Extract(50); + public int TargetRectLeft => (int)_word1.Extract(64, 14); + public int TargetRectRight => (int)_word1.Extract(80, 14); + public int TargetRectTop => (int)_word1.Extract(96, 14); + public int TargetRectBottom => (int)_word1.Extract(112, 14); + } +} diff --git a/src/Ryujinx.Graphics.Vic/Types/OutputSurfaceConfig.cs b/src/Ryujinx.Graphics.Vic/Types/OutputSurfaceConfig.cs new file mode 100644 index 00000000..e72a6a12 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Types/OutputSurfaceConfig.cs @@ -0,0 +1,24 @@ +using Ryujinx.Common.Utilities; + +namespace Ryujinx.Graphics.Vic.Types +{ + readonly struct OutputSurfaceConfig + { +#pragma warning disable CS0649 // Field is never assigned to + private readonly long _word0; + private readonly long _word1; +#pragma warning restore CS0649 + + public PixelFormat OutPixelFormat => (PixelFormat)_word0.Extract(0, 7); + public int OutChromaLocHoriz => (int)_word0.Extract(7, 2); + public int OutChromaLocVert => (int)_word0.Extract(9, 2); + public int OutBlkKind => (int)_word0.Extract(11, 4); + public int OutBlkHeight => (int)_word0.Extract(15, 4); + public int OutSurfaceWidth => (int)_word0.Extract(32, 14); + public int OutSurfaceHeight => (int)_word0.Extract(46, 14); + public int OutLumaWidth => (int)_word1.Extract(64, 14); + public int OutLumaHeight => (int)_word1.Extract(78, 14); + public int OutChromaWidth => (int)_word1.Extract(96, 14); + public int OutChromaHeight => (int)_word1.Extract(110, 14); + } +} diff --git a/src/Ryujinx.Graphics.Vic/Types/PipeConfig.cs b/src/Ryujinx.Graphics.Vic/Types/PipeConfig.cs new file mode 100644 index 00000000..1e0a7e64 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Types/PipeConfig.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Utilities; + +namespace Ryujinx.Graphics.Vic.Types +{ + readonly struct PipeConfig + { +#pragma warning disable CS0169, CS0649, IDE0051 // Remove unused private member + private readonly long _word0; + private readonly long _word1; +#pragma warning restore CS0169, CS0649, IDE0051 + + public int DownsampleHoriz => (int)_word0.Extract(0, 11); + public int DownsampleVert => (int)_word0.Extract(16, 11); + } +} diff --git a/src/Ryujinx.Graphics.Vic/Types/PixelFormat.cs b/src/Ryujinx.Graphics.Vic/Types/PixelFormat.cs new file mode 100644 index 00000000..58df3f30 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Types/PixelFormat.cs @@ -0,0 +1,81 @@ +namespace Ryujinx.Graphics.Vic.Types +{ + enum PixelFormat + { + A8, + L8, + A4L4, + L4A4, + R8, + A8L8, + L8A8, + R8G8, + G8R8, + B5G6R5, + R5G6B5, + B6G5R5, + R5G5B6, + A1B5G5R5, + A1R5G5B5, + B5G5R5A1, + R5G5B5A1, + A5B5G5R1, + A5R1G5B5, + B5G5R1A5, + R1G5B5A5, + X1B5G5R5, + X1R5G5B5, + B5G5R5X1, + R5G5B5X1, + A4B4G4R4, + A4R4G4B4, + B4G4R4A4, + R4G4B4A4, + B8_G8_R8, + R8_G8_B8, + A8B8G8R8, + A8R8G8B8, + B8G8R8A8, + R8G8B8A8, + X8B8G8R8, + X8R8G8B8, + B8G8R8X8, + R8G8B8X8, + A2B10G10R10, + A2R10G10B10, + B10G10R10A2, + R10G10B10A2, + A4P4, + P4A4, + P8A845, + A8P8, + P8, + P1, + U8V8, + V8U8, + A8Y8U8V8, + V8U8Y8A8, + Y8_U8_V8, + Y8_V8_U8, + U8_V8_Y8, + V8_U8_Y8, + Y8_U8__Y8_V8, + Y8_V8__Y8_U8, + U8_Y8__V8_Y8, + V8_Y8__U8_Y8, + Y8___U8V8_N444, + Y8___V8U8_N444, + Y8___U8V8_N422, + Y8___V8U8_N422, + Y8___U8V8_N422R, + Y8___V8U8_N422R, + Y8___U8V8_N420, + Y8___V8U8_N420, + Y8___U8___V8_N444, + Y8___U8___V8_N422, + Y8___U8___V8_N422R, + Y8___U8___V8_N420, + U8, + V8, + } +} diff --git a/src/Ryujinx.Graphics.Vic/Types/SlotConfig.cs b/src/Ryujinx.Graphics.Vic/Types/SlotConfig.cs new file mode 100644 index 00000000..148d0681 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Types/SlotConfig.cs @@ -0,0 +1,67 @@ +using Ryujinx.Common.Utilities; + +namespace Ryujinx.Graphics.Vic.Types +{ + readonly struct SlotConfig + { + private readonly long _word0; + private readonly long _word1; + private readonly long _word2; + private readonly long _word3; + private readonly long _word4; + private readonly long _word5; + private readonly long _word6; +#pragma warning disable IDE0051 // Remove unused private member + private readonly long _word7; +#pragma warning restore IDE0051 + + public bool SlotEnable => _word0.Extract(0); + public bool DeNoise => _word0.Extract(1); + public bool AdvancedDenoise => _word0.Extract(2); + public bool CadenceDetect => _word0.Extract(3); + public bool MotionMap => _word0.Extract(4); + public bool MMapCombine => _word0.Extract(5); + public bool IsEven => _word0.Extract(6); + public bool ChromaEven => _word0.Extract(7); + public bool CurrentFieldEnable => _word0.Extract(8); + public bool PrevFieldEnable => _word0.Extract(9); + public bool NextFieldEnable => _word0.Extract(10); + public bool NextNrFieldEnable => _word0.Extract(11); + public bool CurMotionFieldEnable => _word0.Extract(12); + public bool PrevMotionFieldEnable => _word0.Extract(13); + public bool PpMotionFieldEnable => _word0.Extract(14); + public bool CombMotionFieldEnable => _word0.Extract(15); + public FrameFormat FrameFormat => (FrameFormat)_word0.Extract(16, 4); + public int FilterLengthY => (int)_word0.Extract(20, 2); + public int FilterLengthX => (int)_word0.Extract(22, 2); + public int Panoramic => (int)_word0.Extract(24, 12); + public int DetailFltClamp => (int)_word0.Extract(58, 6); + public int FilterNoise => (int)_word1.Extract(64, 10); + public int FilterDetail => (int)_word1.Extract(74, 10); + public int ChromaNoise => (int)_word1.Extract(84, 10); + public int ChromaDetail => (int)_word1.Extract(94, 10); + public DeinterlaceMode DeinterlaceMode => (DeinterlaceMode)_word1.Extract(104, 4); + public int MotionAccumWeight => (int)_word1.Extract(108, 3); + public int NoiseIir => (int)_word1.Extract(111, 11); + public int LightLevel => (int)_word1.Extract(122, 4); + public int SoftClampLow => (int)_word2.Extract(128, 10); + public int SoftClampHigh => (int)_word2.Extract(138, 10); + public int PlanarAlpha => (int)_word2.Extract(160, 10); + public bool ConstantAlpha => _word2.Extract(170); + public int StereoInterleave => (int)_word2.Extract(171, 3); + public bool ClipEnabled => _word2.Extract(174); + public int ClearRectMask => (int)_word2.Extract(175, 8); + public int DegammaMode => (int)_word2.Extract(183, 2); + public bool DecompressEnable => _word2.Extract(186); + public int DecompressCtbCount => (int)_word3.Extract(192, 8); + public int DecompressZbcColor => (int)_word3.Extract(200, 32); + public int SourceRectLeft => (int)_word4.Extract(256, 30); + public int SourceRectRight => (int)_word4.Extract(288, 30); + public int SourceRectTop => (int)_word5.Extract(320, 30); + public int SourceRectBottom => (int)_word5.Extract(352, 30); + public int DstRectLeft => (int)_word6.Extract(384, 14); + public int DstRectRight => (int)_word6.Extract(400, 14); + public int DstRectTop => (int)_word6.Extract(416, 14); + public int DstRectBottom => (int)_word6.Extract(432, 14); + } +} diff --git a/src/Ryujinx.Graphics.Vic/Types/SlotStruct.cs b/src/Ryujinx.Graphics.Vic/Types/SlotStruct.cs new file mode 100644 index 00000000..b7726683 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Types/SlotStruct.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Vic.Types +{ + struct SlotStruct + { + public SlotConfig SlotConfig; + public SlotSurfaceConfig SlotSurfaceConfig; + public LumaKeyStruct LumaKeyStruct; + public MatrixStruct ColorMatrixStruct; + public MatrixStruct GamutMatrixStruct; + public BlendingSlotStruct BlendingSlotStruct; + } +} diff --git a/src/Ryujinx.Graphics.Vic/Types/SlotSurfaceConfig.cs b/src/Ryujinx.Graphics.Vic/Types/SlotSurfaceConfig.cs new file mode 100644 index 00000000..3acab4de --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/Types/SlotSurfaceConfig.cs @@ -0,0 +1,23 @@ +using Ryujinx.Common.Utilities; + +namespace Ryujinx.Graphics.Vic.Types +{ + readonly struct SlotSurfaceConfig + { + private readonly long _word0; + private readonly long _word1; + + public PixelFormat SlotPixelFormat => (PixelFormat)_word0.Extract(0, 7); + public int SlotChromaLocHoriz => (int)_word0.Extract(7, 2); + public int SlotChromaLocVert => (int)_word0.Extract(9, 2); + public int SlotBlkKind => (int)_word0.Extract(11, 4); + public int SlotBlkHeight => (int)_word0.Extract(15, 4); + public int SlotCacheWidth => (int)_word0.Extract(19, 3); + public int SlotSurfaceWidth => (int)_word0.Extract(32, 14); + public int SlotSurfaceHeight => (int)_word0.Extract(46, 14); + public int SlotLumaWidth => (int)_word1.Extract(64, 14); + public int SlotLumaHeight => (int)_word1.Extract(78, 14); + public int SlotChromaWidth => (int)_word1.Extract(96, 14); + public int SlotChromaHeight => (int)_word1.Extract(110, 14); + } +} diff --git a/src/Ryujinx.Graphics.Vic/VicDevice.cs b/src/Ryujinx.Graphics.Vic/VicDevice.cs new file mode 100644 index 00000000..2b25a74c --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/VicDevice.cs @@ -0,0 +1,73 @@ +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Vic.Image; +using Ryujinx.Graphics.Vic.Types; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vic +{ + public class VicDevice : IDeviceState + { + private readonly DeviceMemoryManager _mm; + private readonly ResourceManager _rm; + private readonly DeviceState _state; + + public VicDevice(DeviceMemoryManager mm) + { + _mm = mm; + _rm = new ResourceManager(mm, new BufferPool(), new BufferPool()); + _state = new DeviceState(new Dictionary + { + { nameof(VicRegisters.Execute), new RwCallback(Execute, null) }, + }); + } + + public int Read(int offset) => _state.Read(offset); + public void Write(int offset, int data) => _state.Write(offset, data); + + private void Execute(int data) + { + ConfigStruct config = ReadIndirect(_state.State.SetConfigStructOffset); + + using Surface output = new( + _rm.SurfacePool, + config.OutputSurfaceConfig.OutSurfaceWidth + 1, + config.OutputSurfaceConfig.OutSurfaceHeight + 1); + + for (int i = 0; i < config.SlotStruct.Length; i++) + { + ref SlotStruct slot = ref config.SlotStruct[i]; + + if (!slot.SlotConfig.SlotEnable) + { + continue; + } + + ref var offsets = ref _state.State.SetSurfacexSlotx[i]; + + using Surface src = SurfaceReader.Read(_rm, ref slot.SlotConfig, ref slot.SlotSurfaceConfig, ref offsets); + + int x1 = config.OutputConfig.TargetRectLeft; + int y1 = config.OutputConfig.TargetRectTop; + int x2 = config.OutputConfig.TargetRectRight + 1; + int y2 = config.OutputConfig.TargetRectBottom + 1; + + int targetX = Math.Min(x1, x2); + int targetY = Math.Min(y1, y2); + int targetW = Math.Min(output.Width - targetX, Math.Abs(x2 - x1)); + int targetH = Math.Min(output.Height - targetY, Math.Abs(y2 - y1)); + + Rectangle targetRect = new(targetX, targetY, targetW, targetH); + + Blender.BlendOne(output, src, ref slot, targetRect); + } + + SurfaceWriter.Write(_rm, output, ref config.OutputSurfaceConfig, ref _state.State.SetOutputSurface); + } + + private T ReadIndirect(uint offset) where T : unmanaged + { + return _mm.Read((ulong)offset << 8); + } + } +} diff --git a/src/Ryujinx.Graphics.Vic/VicRegisters.cs b/src/Ryujinx.Graphics.Vic/VicRegisters.cs new file mode 100644 index 00000000..7ebc10d9 --- /dev/null +++ b/src/Ryujinx.Graphics.Vic/VicRegisters.cs @@ -0,0 +1,51 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Vic +{ + struct PlaneOffsets + { +#pragma warning disable CS0649 // Field is never assigned to + public uint LumaOffset; + public uint ChromaUOffset; + public uint ChromaVOffset; +#pragma warning restore CS0649 + } + + struct VicRegisters + { +#pragma warning disable CS0649 // Field is never assigned to + public Array64 Reserved0; + public uint Nop; + public Array15 Reserved104; + public uint PmTrigger; + public Array47 Reserved144; + public uint SetApplicationID; + public uint SetWatchdogTimer; + public Array14 Reserved208; + public uint SemaphoreA; + public uint SemaphoreB; + public uint SemaphoreC; + public uint CtxSaveArea; + public uint CtxSwitch; + public Array43 Reserved254; + public uint Execute; + public uint SemaphoreD; + public Array62 Reserved308; + public Array8> SetSurfacexSlotx; + public uint SetPictureIndex; + public uint SetControlParams; + public uint SetConfigStructOffset; + public uint SetFilterStructOffset; + public uint SetPaletteOffset; + public uint SetHistOffset; + public uint SetContextId; + public uint SetFceUcodeSize; + public PlaneOffsets SetOutputSurface; + public uint SetFceUcodeOffset; + public Array4 Reserved730; + public Array8 SetSlotContextId; + public Array8 SetCompTagBufferOffset; + public Array8 SetHistoryBufferOffset; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Video/FrameField.cs b/src/Ryujinx.Graphics.Video/FrameField.cs new file mode 100644 index 00000000..dbfd5c44 --- /dev/null +++ b/src/Ryujinx.Graphics.Video/FrameField.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Video +{ + public enum FrameField + { + Progressive, + Interlaced, + } +} diff --git a/src/Ryujinx.Graphics.Video/H264PictureInfo.cs b/src/Ryujinx.Graphics.Video/H264PictureInfo.cs new file mode 100644 index 00000000..294de055 --- /dev/null +++ b/src/Ryujinx.Graphics.Video/H264PictureInfo.cs @@ -0,0 +1,47 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Video +{ + public struct H264PictureInfo + { + public Array2 FieldOrderCnt; + public bool IsReference; + public ushort ChromaFormatIdc; + public ushort FrameNum; + public bool FieldPicFlag; + public bool BottomFieldFlag; + public uint NumRefFrames; + public bool MbAdaptiveFrameFieldFlag; + public bool ConstrainedIntraPredFlag; + public bool WeightedPredFlag; + public uint WeightedBipredIdc; + public bool FrameMbsOnlyFlag; + public bool Transform8x8ModeFlag; + public int ChromaQpIndexOffset; + public int SecondChromaQpIndexOffset; + public int PicInitQpMinus26; + public uint NumRefIdxL0ActiveMinus1; + public uint NumRefIdxL1ActiveMinus1; + public uint Log2MaxFrameNumMinus4; + public uint PicOrderCntType; + public uint Log2MaxPicOrderCntLsbMinus4; + public bool DeltaPicOrderAlwaysZeroFlag; + public bool Direct8x8InferenceFlag; + public bool EntropyCodingModeFlag; + public bool PicOrderPresentFlag; + public bool DeblockingFilterControlPresentFlag; + public bool RedundantPicCntPresentFlag; + public uint NumSliceGroupsMinus1; + public uint SliceGroupMapType; + public uint SliceGroupChangeRateMinus1; + // TODO: Slice group map + public bool FmoAsoEnable; + public bool ScalingMatrixPresent; + public Array6> ScalingLists4x4; + public Array2> ScalingLists8x8; + public uint FrameType; + public uint PicWidthInMbsMinus1; + public uint PicHeightInMapUnitsMinus1; + public bool QpprimeYZeroTransformBypassFlag; + } +} diff --git a/src/Ryujinx.Graphics.Video/IDecoder.cs b/src/Ryujinx.Graphics.Video/IDecoder.cs new file mode 100644 index 00000000..44c3375c --- /dev/null +++ b/src/Ryujinx.Graphics.Video/IDecoder.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.Graphics.Video +{ + public interface IDecoder : IDisposable + { + bool IsHardwareAccelerated { get; } + + ISurface CreateSurface(int width, int height); + } +} diff --git a/src/Ryujinx.Graphics.Video/IH264Decoder.cs b/src/Ryujinx.Graphics.Video/IH264Decoder.cs new file mode 100644 index 00000000..f204436d --- /dev/null +++ b/src/Ryujinx.Graphics.Video/IH264Decoder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.Graphics.Video +{ + public interface IH264Decoder : IDecoder + { + bool Decode(ref H264PictureInfo pictureInfo, ISurface output, ReadOnlySpan bitstream); + } +} diff --git a/src/Ryujinx.Graphics.Video/ISurface.cs b/src/Ryujinx.Graphics.Video/ISurface.cs new file mode 100644 index 00000000..6176b6a4 --- /dev/null +++ b/src/Ryujinx.Graphics.Video/ISurface.cs @@ -0,0 +1,20 @@ +using System; + +namespace Ryujinx.Graphics.Video +{ + public interface ISurface : IDisposable + { + Plane YPlane { get; } + Plane UPlane { get; } + Plane VPlane { get; } + + FrameField Field { get; } + + int Width { get; } + int Height { get; } + int Stride { get; } + int UvWidth { get; } + int UvHeight { get; } + int UvStride { get; } + } +} diff --git a/src/Ryujinx.Graphics.Video/IVp9Decoder.cs b/src/Ryujinx.Graphics.Video/IVp9Decoder.cs new file mode 100644 index 00000000..78ed90a4 --- /dev/null +++ b/src/Ryujinx.Graphics.Video/IVp9Decoder.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.Graphics.Video +{ + public interface IVp9Decoder : IDecoder + { + bool Decode( + ref Vp9PictureInfo pictureInfo, + ISurface output, + ReadOnlySpan bitstream, + ReadOnlySpan mvsIn, + Span mvsOut); + } +} diff --git a/src/Ryujinx.Graphics.Video/Plane.cs b/src/Ryujinx.Graphics.Video/Plane.cs new file mode 100644 index 00000000..b895cad9 --- /dev/null +++ b/src/Ryujinx.Graphics.Video/Plane.cs @@ -0,0 +1,6 @@ +using System; + +namespace Ryujinx.Graphics.Video +{ + public readonly record struct Plane(IntPtr Pointer, int Length); +} diff --git a/src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj b/src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj new file mode 100644 index 00000000..abff58a5 --- /dev/null +++ b/src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + + + + + + + diff --git a/src/Ryujinx.Graphics.Video/Vp8PictureInfo.cs b/src/Ryujinx.Graphics.Video/Vp8PictureInfo.cs new file mode 100644 index 00000000..6aa7fb7f --- /dev/null +++ b/src/Ryujinx.Graphics.Video/Vp8PictureInfo.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Graphics.Video +{ + public ref struct Vp8PictureInfo + { + public bool KeyFrame; + public uint FirstPartSize; + public uint Version; + public ushort FrameWidth; + public ushort FrameHeight; + } +} diff --git a/src/Ryujinx.Graphics.Video/Vp9BackwardUpdates.cs b/src/Ryujinx.Graphics.Video/Vp9BackwardUpdates.cs new file mode 100644 index 00000000..0fb54366 --- /dev/null +++ b/src/Ryujinx.Graphics.Video/Vp9BackwardUpdates.cs @@ -0,0 +1,32 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Video +{ + public struct Vp9BackwardUpdates + { + public Array4> YMode; + public Array10> UvMode; + public Array16> Partition; + public Array4>>>>> Coef; + public Array4>>>> EobBranch; + public Array4> SwitchableInterp; + public Array7> InterMode; + public Array4> IntraInter; + public Array5> CompInter; + public Array5>> SingleRef; + public Array5> CompRef; + public Array2> Tx32x32; + public Array2> Tx16x16; + public Array2> Tx8x8; + public Array3> Skip; + public Array4 Joints; + public Array2> Sign; + public Array2> Classes; + public Array2> Class0; + public Array2>> Bits; + public Array2>> Class0Fp; + public Array2> Fp; + public Array2> Class0Hp; + public Array2> Hp; + } +} diff --git a/src/Ryujinx.Graphics.Video/Vp9EntropyProbs.cs b/src/Ryujinx.Graphics.Video/Vp9EntropyProbs.cs new file mode 100644 index 00000000..a5fc25a4 --- /dev/null +++ b/src/Ryujinx.Graphics.Video/Vp9EntropyProbs.cs @@ -0,0 +1,36 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Video +{ + public struct Vp9EntropyProbs + { + public Array10>> KfYModeProb; + public Array7 SegTreeProb; + public Array3 SegPredProb; + public Array10> KfUvModeProb; + public Array4> YModeProb; + public Array10> UvModeProb; + public Array16> KfPartitionProb; + public Array16> PartitionProb; + public Array4>>>>> CoefProbs; + public Array4> SwitchableInterpProb; + public Array7> InterModeProb; + public Array4 IntraInterProb; + public Array5 CompInterProb; + public Array5> SingleRefProb; + public Array5 CompRefProb; + public Array2> Tx32x32Prob; + public Array2> Tx16x16Prob; + public Array2> Tx8x8Prob; + public Array3 SkipProb; + public Array3 Joints; + public Array2 Sign; + public Array2> Classes; + public Array2> Class0; + public Array2> Bits; + public Array2>> Class0Fp; + public Array2> Fp; + public Array2 Class0Hp; + public Array2 Hp; + } +} diff --git a/src/Ryujinx.Graphics.Video/Vp9Mv.cs b/src/Ryujinx.Graphics.Video/Vp9Mv.cs new file mode 100644 index 00000000..ce842a1d --- /dev/null +++ b/src/Ryujinx.Graphics.Video/Vp9Mv.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Graphics.Video +{ + public struct Vp9Mv + { + public short Row; + public short Col; + } +} diff --git a/src/Ryujinx.Graphics.Video/Vp9MvRef.cs b/src/Ryujinx.Graphics.Video/Vp9MvRef.cs new file mode 100644 index 00000000..7962544d --- /dev/null +++ b/src/Ryujinx.Graphics.Video/Vp9MvRef.cs @@ -0,0 +1,11 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Video +{ + // This must match the structure used by NVDEC, do not modify. + public struct Vp9MvRef + { + public Array2 Mvs; + public Array2 RefFrames; + } +} diff --git a/src/Ryujinx.Graphics.Video/Vp9PictureInfo.cs b/src/Ryujinx.Graphics.Video/Vp9PictureInfo.cs new file mode 100644 index 00000000..bcdaf0df --- /dev/null +++ b/src/Ryujinx.Graphics.Video/Vp9PictureInfo.cs @@ -0,0 +1,39 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Video +{ + public ref struct Vp9PictureInfo + { + public ISurface LastReference; + public ISurface GoldenReference; + public ISurface AltReference; + public bool IsKeyFrame; + public bool IntraOnly; + public Array4 RefFrameSignBias; + public int BaseQIndex; + public int YDcDeltaQ; + public int UvDcDeltaQ; + public int UvAcDeltaQ; + public bool Lossless; + public int TransformMode; + public bool AllowHighPrecisionMv; + public int InterpFilter; + public int ReferenceMode; + public sbyte CompFixedRef; + public Array2 CompVarRef; + public int Log2TileCols; + public int Log2TileRows; + public bool SegmentEnabled; + public bool SegmentMapUpdate; + public bool SegmentMapTemporalUpdate; + public int SegmentAbsDelta; + public Array8 SegmentFeatureEnable; + public Array8> SegmentFeatureData; + public bool ModeRefDeltaEnabled; + public bool UsePrevInFindMvRefs; + public Array4 RefDeltas; + public Array2 ModeDeltas; + public Vp9EntropyProbs Entropy; + public Vp9BackwardUpdates BackwardUpdateCounts; + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Auto.cs b/src/Ryujinx.Graphics.Vulkan/Auto.cs new file mode 100644 index 00000000..606c088e --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Auto.cs @@ -0,0 +1,191 @@ +using System; +using System.Diagnostics; +using System.Threading; + +namespace Ryujinx.Graphics.Vulkan +{ + interface IAuto + { + bool HasCommandBufferDependency(CommandBufferScoped cbs); + + void IncrementReferenceCount(); + void DecrementReferenceCount(int cbIndex); + void DecrementReferenceCount(); + } + + interface IAutoPrivate : IAuto + { + void AddCommandBufferDependencies(CommandBufferScoped cbs); + } + + interface IMirrorable where T : IDisposable + { + Auto GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored); + void ClearMirrors(CommandBufferScoped cbs, int offset, int size); + } + + class Auto : IAutoPrivate, IDisposable where T : IDisposable + { + private int _referenceCount; + private T _value; + + private readonly BitMap _cbOwnership; + private readonly MultiFenceHolder _waitable; + private readonly IAutoPrivate[] _referencedObjs; + private readonly IMirrorable _mirrorable; + + private bool _disposed; + private bool _destroyed; + + public Auto(T value) + { + _referenceCount = 1; + _value = value; + _cbOwnership = new BitMap(CommandBufferPool.MaxCommandBuffers); + } + + public Auto(T value, IMirrorable mirrorable, MultiFenceHolder waitable, params IAutoPrivate[] referencedObjs) : this(value, waitable, referencedObjs) + { + _mirrorable = mirrorable; + } + + public Auto(T value, MultiFenceHolder waitable, params IAutoPrivate[] referencedObjs) : this(value) + { + _waitable = waitable; + _referencedObjs = referencedObjs; + + for (int i = 0; i < referencedObjs.Length; i++) + { + referencedObjs[i].IncrementReferenceCount(); + } + } + + public T GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored) + { + var mirror = _mirrorable.GetMirrorable(cbs, ref offset, size, out mirrored); + mirror._waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size, false); + return mirror.Get(cbs); + } + + public T Get(CommandBufferScoped cbs, int offset, int size, bool write = false) + { + _mirrorable?.ClearMirrors(cbs, offset, size); + _waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size, write); + return Get(cbs); + } + + public T GetUnsafe() + { + return _value; + } + + public T Get(CommandBufferScoped cbs) + { + if (!_destroyed) + { + AddCommandBufferDependencies(cbs); + } + + return _value; + } + + public bool HasCommandBufferDependency(CommandBufferScoped cbs) + { + return _cbOwnership.IsSet(cbs.CommandBufferIndex); + } + + public bool HasRentedCommandBufferDependency(CommandBufferPool cbp) + { + return _cbOwnership.AnySet(); + } + + public void AddCommandBufferDependencies(CommandBufferScoped cbs) + { + // We don't want to add a reference to this object to the command buffer + // more than once, so if we detect that the command buffer already has ownership + // of this object, then we can just return without doing anything else. + if (_cbOwnership.Set(cbs.CommandBufferIndex)) + { + if (_waitable != null) + { + cbs.AddWaitable(_waitable); + } + + cbs.AddDependant(this); + + // We need to add a dependency on the command buffer to all objects this object + // references aswell. + if (_referencedObjs != null) + { + for (int i = 0; i < _referencedObjs.Length; i++) + { + _referencedObjs[i].AddCommandBufferDependencies(cbs); + } + } + } + } + + public bool TryIncrementReferenceCount() + { + int lastValue; + do + { + lastValue = _referenceCount; + + if (lastValue == 0) + { + return false; + } + } + while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue); + + return true; + } + + public void IncrementReferenceCount() + { + if (Interlocked.Increment(ref _referenceCount) == 1) + { + Interlocked.Decrement(ref _referenceCount); + throw new InvalidOperationException("Attempted to increment the reference count of an object that was already destroyed."); + } + } + + public void DecrementReferenceCount(int cbIndex) + { + _cbOwnership.Clear(cbIndex); + DecrementReferenceCount(); + } + + public void DecrementReferenceCount() + { + if (Interlocked.Decrement(ref _referenceCount) == 0) + { + _value.Dispose(); + _value = default; + _destroyed = true; + + // Value is no longer in use by the GPU, dispose all other + // resources that it references. + if (_referencedObjs != null) + { + for (int i = 0; i < _referencedObjs.Length; i++) + { + _referencedObjs[i].DecrementReferenceCount(); + } + } + } + + Debug.Assert(_referenceCount >= 0); + } + + public void Dispose() + { + if (!_disposed) + { + DecrementReferenceCount(); + _disposed = true; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/AutoFlushCounter.cs b/src/Ryujinx.Graphics.Vulkan/AutoFlushCounter.cs new file mode 100644 index 00000000..266893a6 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/AutoFlushCounter.cs @@ -0,0 +1,179 @@ +using Ryujinx.Common.Logging; +using System; +using System.Diagnostics; +using System.Linq; + +namespace Ryujinx.Graphics.Vulkan +{ + internal class AutoFlushCounter + { + // How often to flush on framebuffer change. + private readonly static long _framebufferFlushTimer = Stopwatch.Frequency / 1000; // (1ms) + + // How often to flush on draw when fast flush mode is enabled. + private readonly static long _drawFlushTimer = Stopwatch.Frequency / 666; // (1.5ms) + + // Average wait time that triggers fast flush mode to be entered. + private readonly static long _fastFlushEnterThreshold = Stopwatch.Frequency / 666; // (1.5ms) + + // Average wait time that triggers fast flush mode to be exited. + private readonly static long _fastFlushExitThreshold = Stopwatch.Frequency / 10000; // (0.1ms) + + // Number of frames to average waiting times over. + private const int SyncWaitAverageCount = 20; + + private const int MinDrawCountForFlush = 10; + private const int MinConsecutiveQueryForFlush = 10; + private const int InitialQueryCountForFlush = 32; + + private readonly VulkanRenderer _gd; + + private long _lastFlush; + private ulong _lastDrawCount; + private bool _hasPendingQuery; + private int _consecutiveQueries; + private int _queryCount; + + private readonly int[] _queryCountHistory = new int[3]; + private int _queryCountHistoryIndex; + private int _remainingQueries; + + private readonly long[] _syncWaitHistory = new long[SyncWaitAverageCount]; + private int _syncWaitHistoryIndex; + + private bool _fastFlushMode; + + public AutoFlushCounter(VulkanRenderer gd) + { + _gd = gd; + } + + public void RegisterFlush(ulong drawCount) + { + _lastFlush = Stopwatch.GetTimestamp(); + _lastDrawCount = drawCount; + + _hasPendingQuery = false; + _consecutiveQueries = 0; + } + + public bool RegisterPendingQuery() + { + _hasPendingQuery = true; + _consecutiveQueries++; + _remainingQueries--; + + _queryCountHistory[_queryCountHistoryIndex]++; + + // Interrupt render passes to flush queries, so that early results arrive sooner. + if (++_queryCount == InitialQueryCountForFlush) + { + return true; + } + + return false; + } + + public int GetRemainingQueries() + { + if (_remainingQueries <= 0) + { + _remainingQueries = 16; + } + + if (_queryCount < InitialQueryCountForFlush) + { + return Math.Min(InitialQueryCountForFlush - _queryCount, _remainingQueries); + } + + return _remainingQueries; + } + + public bool ShouldFlushQuery() + { + return _hasPendingQuery; + } + + public bool ShouldFlushDraw(ulong drawCount) + { + if (_fastFlushMode) + { + long draws = (long)(drawCount - _lastDrawCount); + + if (draws < MinDrawCountForFlush) + { + if (draws == 0) + { + _lastFlush = Stopwatch.GetTimestamp(); + } + + return false; + } + + long flushTimeout = _drawFlushTimer; + + long now = Stopwatch.GetTimestamp(); + + return now > _lastFlush + flushTimeout; + } + + return false; + } + + public bool ShouldFlushAttachmentChange(ulong drawCount) + { + _queryCount = 0; + + // Flush when there's an attachment change out of a large block of queries. + if (_consecutiveQueries > MinConsecutiveQueryForFlush) + { + return true; + } + + _consecutiveQueries = 0; + + long draws = (long)(drawCount - _lastDrawCount); + + if (draws < MinDrawCountForFlush) + { + if (draws == 0) + { + _lastFlush = Stopwatch.GetTimestamp(); + } + + return false; + } + + long flushTimeout = _framebufferFlushTimer; + + long now = Stopwatch.GetTimestamp(); + + return now > _lastFlush + flushTimeout; + } + + public void Present() + { + // Query flush prediction. + + _queryCountHistoryIndex = (_queryCountHistoryIndex + 1) % 3; + + _remainingQueries = _queryCountHistory.Max() + 10; + + _queryCountHistory[_queryCountHistoryIndex] = 0; + + // Fast flush mode toggle. + + _syncWaitHistory[_syncWaitHistoryIndex] = _gd.SyncManager.GetAndResetWaitTicks(); + + _syncWaitHistoryIndex = (_syncWaitHistoryIndex + 1) % SyncWaitAverageCount; + + long averageWait = (long)_syncWaitHistory.Average(); + + if (_fastFlushMode ? averageWait < _fastFlushExitThreshold : averageWait > _fastFlushEnterThreshold) + { + _fastFlushMode = !_fastFlushMode; + Logger.Debug?.PrintMsg(LogClass.Gpu, $"Switched fast flush mode: ({_fastFlushMode})"); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/BackgroundResources.cs b/src/Ryujinx.Graphics.Vulkan/BackgroundResources.cs new file mode 100644 index 00000000..0290987f --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/BackgroundResources.cs @@ -0,0 +1,120 @@ +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.Vulkan +{ + class BackgroundResource : IDisposable + { + private readonly VulkanRenderer _gd; + private Device _device; + + private CommandBufferPool _pool; + private PersistentFlushBuffer _flushBuffer; + + public BackgroundResource(VulkanRenderer gd, Device device) + { + _gd = gd; + _device = device; + } + + public CommandBufferPool GetPool() + { + if (_pool == null) + { + bool useBackground = _gd.BackgroundQueue.Handle != 0 && _gd.Vendor != Vendor.Amd; + Queue queue = useBackground ? _gd.BackgroundQueue : _gd.Queue; + object queueLock = useBackground ? _gd.BackgroundQueueLock : _gd.QueueLock; + + lock (queueLock) + { + _pool = new CommandBufferPool( + _gd.Api, + _device, + queue, + queueLock, + _gd.QueueFamilyIndex, + _gd.IsQualcommProprietary, + isLight: true); + } + } + + return _pool; + } + + public PersistentFlushBuffer GetFlushBuffer() + { + _flushBuffer ??= new PersistentFlushBuffer(_gd); + + return _flushBuffer; + } + + public void Dispose() + { + _pool?.Dispose(); + _flushBuffer?.Dispose(); + } + } + + class BackgroundResources : IDisposable + { + private readonly VulkanRenderer _gd; + private Device _device; + + private readonly Dictionary _resources; + + public BackgroundResources(VulkanRenderer gd, Device device) + { + _gd = gd; + _device = device; + + _resources = new Dictionary(); + } + + private void Cleanup() + { + lock (_resources) + { + foreach (KeyValuePair tuple in _resources) + { + if (!tuple.Key.IsAlive) + { + tuple.Value.Dispose(); + _resources.Remove(tuple.Key); + } + } + } + } + + public BackgroundResource Get() + { + Thread thread = Thread.CurrentThread; + + lock (_resources) + { + if (!_resources.TryGetValue(thread, out BackgroundResource resource)) + { + Cleanup(); + + resource = new BackgroundResource(_gd, _device); + + _resources[thread] = resource; + } + + return resource; + } + } + + public void Dispose() + { + lock (_resources) + { + foreach (var resource in _resources.Values) + { + resource.Dispose(); + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs new file mode 100644 index 00000000..bcfb3dbf --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs @@ -0,0 +1,458 @@ +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Vulkan +{ + internal class BarrierBatch : IDisposable + { + private const int MaxBarriersPerCall = 16; + + private const AccessFlags BaseAccess = AccessFlags.ShaderReadBit | AccessFlags.ShaderWriteBit; + private const AccessFlags BufferAccess = AccessFlags.IndexReadBit | AccessFlags.VertexAttributeReadBit | AccessFlags.UniformReadBit; + private const AccessFlags CommandBufferAccess = AccessFlags.IndirectCommandReadBit; + + private readonly VulkanRenderer _gd; + + private readonly NativeArray _memoryBarrierBatch = new(MaxBarriersPerCall); + private readonly NativeArray _bufferBarrierBatch = new(MaxBarriersPerCall); + private readonly NativeArray _imageBarrierBatch = new(MaxBarriersPerCall); + + private readonly List> _memoryBarriers = new(); + private readonly List> _bufferBarriers = new(); + private readonly List> _imageBarriers = new(); + private int _queuedBarrierCount; + + private enum IncoherentBarrierType + { + None, + Texture, + All, + CommandBuffer + } + + private bool _feedbackLoopActive; + private PipelineStageFlags _incoherentBufferWriteStages; + private PipelineStageFlags _incoherentTextureWriteStages; + private PipelineStageFlags _extraStages; + private IncoherentBarrierType _queuedIncoherentBarrier; + private bool _queuedFeedbackLoopBarrier; + + public BarrierBatch(VulkanRenderer gd) + { + _gd = gd; + } + + public static (AccessFlags Access, PipelineStageFlags Stages) GetSubpassAccessSuperset(VulkanRenderer gd) + { + AccessFlags access = BufferAccess; + PipelineStageFlags stages = PipelineStageFlags.AllGraphicsBit; + + if (gd.TransformFeedbackApi != null) + { + access |= AccessFlags.TransformFeedbackWriteBitExt; + stages |= PipelineStageFlags.TransformFeedbackBitExt; + } + + return (access, stages); + } + + private readonly record struct StageFlags : IEquatable + { + public readonly PipelineStageFlags Source; + public readonly PipelineStageFlags Dest; + + public StageFlags(PipelineStageFlags source, PipelineStageFlags dest) + { + Source = source; + Dest = dest; + } + } + + private readonly struct BarrierWithStageFlags where T : unmanaged + { + public readonly StageFlags Flags; + public readonly T Barrier; + public readonly T2 Resource; + + public BarrierWithStageFlags(StageFlags flags, T barrier) + { + Flags = flags; + Barrier = barrier; + Resource = default; + } + + public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier, T2 resource) + { + Flags = new StageFlags(srcStageFlags, dstStageFlags); + Barrier = barrier; + Resource = resource; + } + } + + private void QueueBarrier(List> list, T barrier, T2 resource, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged + { + list.Add(new BarrierWithStageFlags(srcStageFlags, dstStageFlags, barrier, resource)); + _queuedBarrierCount++; + } + + public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) + { + QueueBarrier(_memoryBarriers, barrier, default, srcStageFlags, dstStageFlags); + } + + public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) + { + QueueBarrier(_bufferBarriers, barrier, default, srcStageFlags, dstStageFlags); + } + + public void QueueBarrier(ImageMemoryBarrier barrier, TextureStorage resource, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) + { + QueueBarrier(_imageBarriers, barrier, resource, srcStageFlags, dstStageFlags); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void FlushMemoryBarrier(ShaderCollection program, bool inRenderPass) + { + if (_queuedIncoherentBarrier > IncoherentBarrierType.None) + { + // We should emit a memory barrier if there's a write access in the program (current program, or program since last barrier) + bool hasTextureWrite = _incoherentTextureWriteStages != PipelineStageFlags.None; + bool hasBufferWrite = _incoherentBufferWriteStages != PipelineStageFlags.None; + bool hasBufferBarrier = _queuedIncoherentBarrier > IncoherentBarrierType.Texture; + + if (hasTextureWrite || (hasBufferBarrier && hasBufferWrite)) + { + AccessFlags access = BaseAccess; + + PipelineStageFlags stages = inRenderPass ? PipelineStageFlags.AllGraphicsBit : PipelineStageFlags.AllCommandsBit; + + if (hasBufferBarrier && hasBufferWrite) + { + access |= BufferAccess; + + if (_gd.TransformFeedbackApi != null) + { + access |= AccessFlags.TransformFeedbackWriteBitExt; + stages |= PipelineStageFlags.TransformFeedbackBitExt; + } + } + + if (_queuedIncoherentBarrier == IncoherentBarrierType.CommandBuffer) + { + access |= CommandBufferAccess; + stages |= PipelineStageFlags.DrawIndirectBit; + } + + MemoryBarrier barrier = new MemoryBarrier() + { + SType = StructureType.MemoryBarrier, + SrcAccessMask = access, + DstAccessMask = access + }; + + QueueBarrier(barrier, stages, stages); + + _incoherentTextureWriteStages = program?.IncoherentTextureWriteStages ?? PipelineStageFlags.None; + + if (_queuedIncoherentBarrier > IncoherentBarrierType.Texture) + { + if (program != null) + { + _incoherentBufferWriteStages = program.IncoherentBufferWriteStages | _extraStages; + } + else + { + _incoherentBufferWriteStages = PipelineStageFlags.None; + } + } + + _queuedIncoherentBarrier = IncoherentBarrierType.None; + _queuedFeedbackLoopBarrier = false; + } + else if (_feedbackLoopActive && _queuedFeedbackLoopBarrier) + { + // Feedback loop barrier. + + MemoryBarrier barrier = new MemoryBarrier() + { + SType = StructureType.MemoryBarrier, + SrcAccessMask = AccessFlags.ShaderWriteBit, + DstAccessMask = AccessFlags.ShaderReadBit + }; + + QueueBarrier(barrier, PipelineStageFlags.FragmentShaderBit, PipelineStageFlags.AllGraphicsBit); + + _queuedFeedbackLoopBarrier = false; + } + + _feedbackLoopActive = false; + } + } + + public unsafe void Flush(CommandBufferScoped cbs, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass) + { + Flush(cbs, null, false, inRenderPass, rpHolder, endRenderPass); + } + + public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool feedbackLoopActive, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass) + { + if (program != null) + { + _incoherentBufferWriteStages |= program.IncoherentBufferWriteStages | _extraStages; + _incoherentTextureWriteStages |= program.IncoherentTextureWriteStages; + } + + _feedbackLoopActive |= feedbackLoopActive; + + FlushMemoryBarrier(program, inRenderPass); + + if (!inRenderPass && rpHolder != null) + { + // Render pass is about to begin. Queue any fences that normally interrupt the pass. + rpHolder.InsertForcedFences(cbs); + } + + while (_queuedBarrierCount > 0) + { + int memoryCount = 0; + int bufferCount = 0; + int imageCount = 0; + + bool hasBarrier = false; + StageFlags flags = default; + + static void AddBarriers( + Span target, + ref int queuedBarrierCount, + ref bool hasBarrier, + ref StageFlags flags, + ref int count, + List> list) where T : unmanaged + { + int firstMatch = -1; + int end = list.Count; + + for (int i = 0; i < list.Count; i++) + { + BarrierWithStageFlags barrier = list[i]; + + if (!hasBarrier) + { + flags = barrier.Flags; + hasBarrier = true; + + target[count++] = barrier.Barrier; + queuedBarrierCount--; + firstMatch = i; + + if (count >= target.Length) + { + end = i + 1; + break; + } + } + else + { + if (flags.Equals(barrier.Flags)) + { + target[count++] = barrier.Barrier; + queuedBarrierCount--; + + if (firstMatch == -1) + { + firstMatch = i; + } + + if (count >= target.Length) + { + end = i + 1; + break; + } + } + else + { + // Delete consumed barriers from the first match to the current non-match. + if (firstMatch != -1) + { + int deleteCount = i - firstMatch; + list.RemoveRange(firstMatch, deleteCount); + i -= deleteCount; + + firstMatch = -1; + end = list.Count; + } + } + } + } + + if (firstMatch == 0 && end == list.Count) + { + list.Clear(); + } + else if (firstMatch != -1) + { + int deleteCount = end - firstMatch; + + list.RemoveRange(firstMatch, deleteCount); + } + } + + if (inRenderPass && _imageBarriers.Count > 0) + { + // Image barriers queued in the batch are meant to be globally scoped, + // but inside a render pass they're scoped to just the range of the render pass. + + // On MoltenVK, we just break the rules and always use image barrier. + // On desktop GPUs, all barriers are globally scoped, so we just replace it with a generic memory barrier. + // Generally, we want to avoid this from happening in the future, so flag the texture to immediately + // emit a barrier whenever the current render pass is bound again. + + bool anyIsNonAttachment = false; + + foreach (BarrierWithStageFlags barrier in _imageBarriers) + { + // If the binding is an attachment, don't add it as a forced fence. + bool isAttachment = rpHolder.ContainsAttachment(barrier.Resource); + + if (!isAttachment) + { + rpHolder.AddForcedFence(barrier.Resource, barrier.Flags.Dest); + anyIsNonAttachment = true; + } + } + + if (_gd.IsTBDR) + { + if (!_gd.IsMoltenVk) + { + if (!anyIsNonAttachment) + { + // This case is a feedback loop. To prevent this from causing an absolute performance disaster, + // remove the barriers entirely. + // If this is not here, there will be a lot of single draw render passes. + // TODO: explicit handling for feedback loops, likely outside this class. + + _queuedBarrierCount -= _imageBarriers.Count; + _imageBarriers.Clear(); + } + else + { + // TBDR GPUs are sensitive to barriers, so we need to end the pass to ensure the data is available. + // Metal already has hazard tracking so MVK doesn't need this. + endRenderPass(); + inRenderPass = false; + } + } + } + else + { + // Generic pipeline memory barriers will work for desktop GPUs. + // They do require a few more access flags on the subpass dependency, though. + foreach (var barrier in _imageBarriers) + { + _memoryBarriers.Add(new BarrierWithStageFlags( + barrier.Flags, + new MemoryBarrier() + { + SType = StructureType.MemoryBarrier, + SrcAccessMask = barrier.Barrier.SrcAccessMask, + DstAccessMask = barrier.Barrier.DstAccessMask + })); + } + + _imageBarriers.Clear(); + } + } + + if (inRenderPass && _memoryBarriers.Count > 0) + { + PipelineStageFlags allFlags = PipelineStageFlags.None; + + foreach (var barrier in _memoryBarriers) + { + allFlags |= barrier.Flags.Dest; + } + + if (allFlags.HasFlag(PipelineStageFlags.DrawIndirectBit) || !_gd.SupportsRenderPassBarrier(allFlags)) + { + endRenderPass(); + inRenderPass = false; + } + } + + AddBarriers(_memoryBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref memoryCount, _memoryBarriers); + AddBarriers(_bufferBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref bufferCount, _bufferBarriers); + AddBarriers(_imageBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref imageCount, _imageBarriers); + + if (hasBarrier) + { + PipelineStageFlags srcStageFlags = flags.Source; + + if (inRenderPass) + { + // Inside a render pass, barrier stages can only be from rasterization. + srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit; + } + + _gd.Api.CmdPipelineBarrier( + cbs.CommandBuffer, + srcStageFlags, + flags.Dest, + 0, + (uint)memoryCount, + _memoryBarrierBatch.Pointer, + (uint)bufferCount, + _bufferBarrierBatch.Pointer, + (uint)imageCount, + _imageBarrierBatch.Pointer); + } + } + } + + private void QueueIncoherentBarrier(IncoherentBarrierType type) + { + if (type > _queuedIncoherentBarrier) + { + _queuedIncoherentBarrier = type; + } + + _queuedFeedbackLoopBarrier = true; + } + + public void QueueTextureBarrier() + { + QueueIncoherentBarrier(IncoherentBarrierType.Texture); + } + + public void QueueMemoryBarrier() + { + QueueIncoherentBarrier(IncoherentBarrierType.All); + } + + public void QueueCommandBufferBarrier() + { + QueueIncoherentBarrier(IncoherentBarrierType.CommandBuffer); + } + + public void EnableTfbBarriers(bool enable) + { + if (enable) + { + _extraStages |= PipelineStageFlags.TransformFeedbackBitExt; + } + else + { + _extraStages &= ~PipelineStageFlags.TransformFeedbackBitExt; + } + } + + public void Dispose() + { + _memoryBarrierBatch.Dispose(); + _bufferBarrierBatch.Dispose(); + _imageBarrierBatch.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/BitMap.cs b/src/Ryujinx.Graphics.Vulkan/BitMap.cs new file mode 100644 index 00000000..22abe719 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/BitMap.cs @@ -0,0 +1,157 @@ +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct BitMap + { + public const int IntSize = 64; + + private const int IntShift = 6; + private const int IntMask = IntSize - 1; + + private readonly long[] _masks; + + public BitMap(int count) + { + _masks = new long[(count + IntMask) / IntSize]; + } + + public bool AnySet() + { + for (int i = 0; i < _masks.Length; i++) + { + if (_masks[i] != 0) + { + return true; + } + } + + return false; + } + + public bool IsSet(int bit) + { + int wordIndex = bit >> IntShift; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + return (_masks[wordIndex] & wordMask) != 0; + } + + public bool IsSet(int start, int end) + { + if (start == end) + { + return IsSet(start); + } + + int startIndex = start >> IntShift; + int startBit = start & IntMask; + long startMask = -1L << startBit; + + int endIndex = end >> IntShift; + int endBit = end & IntMask; + long endMask = (long)(ulong.MaxValue >> (IntMask - endBit)); + + if (startIndex == endIndex) + { + return (_masks[startIndex] & startMask & endMask) != 0; + } + + if ((_masks[startIndex] & startMask) != 0) + { + return true; + } + + for (int i = startIndex + 1; i < endIndex; i++) + { + if (_masks[i] != 0) + { + return true; + } + } + + if ((_masks[endIndex] & endMask) != 0) + { + return true; + } + + return false; + } + + public bool Set(int bit) + { + int wordIndex = bit >> IntShift; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + if ((_masks[wordIndex] & wordMask) != 0) + { + return false; + } + + _masks[wordIndex] |= wordMask; + + return true; + } + + public void SetRange(int start, int end) + { + if (start == end) + { + Set(start); + return; + } + + int startIndex = start >> IntShift; + int startBit = start & IntMask; + long startMask = -1L << startBit; + + int endIndex = end >> IntShift; + int endBit = end & IntMask; + long endMask = (long)(ulong.MaxValue >> (IntMask - endBit)); + + if (startIndex == endIndex) + { + _masks[startIndex] |= startMask & endMask; + } + else + { + _masks[startIndex] |= startMask; + + for (int i = startIndex + 1; i < endIndex; i++) + { + _masks[i] |= -1; + } + + _masks[endIndex] |= endMask; + } + } + + public void Clear(int bit) + { + int wordIndex = bit >> IntShift; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + _masks[wordIndex] &= ~wordMask; + } + + public void Clear() + { + for (int i = 0; i < _masks.Length; i++) + { + _masks[i] = 0; + } + } + + public void ClearInt(int start, int end) + { + for (int i = start; i <= end; i++) + { + _masks[i] = 0; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/BitMapStruct.cs b/src/Ryujinx.Graphics.Vulkan/BitMapStruct.cs new file mode 100644 index 00000000..43107427 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/BitMapStruct.cs @@ -0,0 +1,263 @@ +using Ryujinx.Common.Memory; +using System; +using System.Numerics; + +namespace Ryujinx.Graphics.Vulkan +{ + interface IBitMapListener + { + void BitMapSignal(int index, int count); + } + + struct BitMapStruct where T : IArray + { + public const int IntSize = 64; + + private const int IntShift = 6; + private const int IntMask = IntSize - 1; + + private T _masks; + + public BitMapStruct() + { + _masks = default; + } + + public bool BecomesUnsetFrom(in BitMapStruct from, ref BitMapStruct into) + { + bool result = false; + + int masks = _masks.Length; + for (int i = 0; i < masks; i++) + { + long fromMask = from._masks[i]; + long unsetMask = (~fromMask) & (fromMask ^ _masks[i]); + into._masks[i] = unsetMask; + + result |= unsetMask != 0; + } + + return result; + } + + public void SetAndSignalUnset(in BitMapStruct from, ref T2 listener) where T2 : struct, IBitMapListener + { + BitMapStruct result = new(); + + if (BecomesUnsetFrom(from, ref result)) + { + // Iterate the set bits in the result, and signal them. + + int offset = 0; + int masks = _masks.Length; + ref T resultMasks = ref result._masks; + for (int i = 0; i < masks; i++) + { + long value = resultMasks[i]; + while (value != 0) + { + int bit = BitOperations.TrailingZeroCount((ulong)value); + + listener.BitMapSignal(offset + bit, 1); + + value &= ~(1L << bit); + } + + offset += IntSize; + } + } + + _masks = from._masks; + } + + public void SignalSet(Action action) + { + // Iterate the set bits in the result, and signal them. + + int offset = 0; + int masks = _masks.Length; + for (int i = 0; i < masks; i++) + { + long value = _masks[i]; + while (value != 0) + { + int bit = BitOperations.TrailingZeroCount((ulong)value); + + action(offset + bit, 1); + + value &= ~(1L << bit); + } + + offset += IntSize; + } + } + + public bool AnySet() + { + for (int i = 0; i < _masks.Length; i++) + { + if (_masks[i] != 0) + { + return true; + } + } + + return false; + } + + public bool IsSet(int bit) + { + int wordIndex = bit >> IntShift; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + return (_masks[wordIndex] & wordMask) != 0; + } + + public bool IsSet(int start, int end) + { + if (start == end) + { + return IsSet(start); + } + + int startIndex = start >> IntShift; + int startBit = start & IntMask; + long startMask = -1L << startBit; + + int endIndex = end >> IntShift; + int endBit = end & IntMask; + long endMask = (long)(ulong.MaxValue >> (IntMask - endBit)); + + if (startIndex == endIndex) + { + return (_masks[startIndex] & startMask & endMask) != 0; + } + + if ((_masks[startIndex] & startMask) != 0) + { + return true; + } + + for (int i = startIndex + 1; i < endIndex; i++) + { + if (_masks[i] != 0) + { + return true; + } + } + + if ((_masks[endIndex] & endMask) != 0) + { + return true; + } + + return false; + } + + public bool Set(int bit) + { + int wordIndex = bit >> IntShift; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + if ((_masks[wordIndex] & wordMask) != 0) + { + return false; + } + + _masks[wordIndex] |= wordMask; + + return true; + } + + public void Set(int bit, bool value) + { + if (value) + { + Set(bit); + } + else + { + Clear(bit); + } + } + + public void SetRange(int start, int end) + { + if (start == end) + { + Set(start); + return; + } + + int startIndex = start >> IntShift; + int startBit = start & IntMask; + long startMask = -1L << startBit; + + int endIndex = end >> IntShift; + int endBit = end & IntMask; + long endMask = (long)(ulong.MaxValue >> (IntMask - endBit)); + + if (startIndex == endIndex) + { + _masks[startIndex] |= startMask & endMask; + } + else + { + _masks[startIndex] |= startMask; + + for (int i = startIndex + 1; i < endIndex; i++) + { + _masks[i] |= -1L; + } + + _masks[endIndex] |= endMask; + } + } + + public BitMapStruct Union(BitMapStruct other) + { + var result = new BitMapStruct(); + + ref var masks = ref _masks; + ref var otherMasks = ref other._masks; + ref var newMasks = ref result._masks; + + for (int i = 0; i < masks.Length; i++) + { + newMasks[i] = masks[i] | otherMasks[i]; + } + + return result; + } + + public void Clear(int bit) + { + int wordIndex = bit >> IntShift; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + _masks[wordIndex] &= ~wordMask; + } + + public void Clear() + { + for (int i = 0; i < _masks.Length; i++) + { + _masks[i] = 0; + } + } + + public void ClearInt(int start, int end) + { + for (int i = start; i <= end; i++) + { + _masks[i] = 0; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/BufferAllocationType.cs b/src/Ryujinx.Graphics.Vulkan/BufferAllocationType.cs new file mode 100644 index 00000000..345191f1 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/BufferAllocationType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Graphics.Vulkan +{ + internal enum BufferAllocationType + { + Auto = 0, + + HostMappedNoCache, + HostMapped, + DeviceLocal, + DeviceLocalMapped, + Sparse, + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs new file mode 100644 index 00000000..e840fdc0 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs @@ -0,0 +1,922 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using VkBuffer = Silk.NET.Vulkan.Buffer; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + class BufferHolder : IDisposable, IMirrorable, IMirrorable + { + private const int MaxUpdateBufferSize = 0x10000; + + private const int SetCountThreshold = 100; + private const int WriteCountThreshold = 50; + private const int FlushCountThreshold = 5; + + public const int DeviceLocalSizeThreshold = 256 * 1024; // 256kb + + public const AccessFlags DefaultAccessFlags = + AccessFlags.IndirectCommandReadBit | + AccessFlags.ShaderReadBit | + AccessFlags.ShaderWriteBit | + AccessFlags.TransferReadBit | + AccessFlags.TransferWriteBit | + AccessFlags.UniformReadBit; + + private readonly VulkanRenderer _gd; + private readonly Device _device; + private readonly MemoryAllocation _allocation; + private readonly Auto _buffer; + private readonly Auto _allocationAuto; + private readonly bool _allocationImported; + private readonly ulong _bufferHandle; + + private CacheByRange _cachedConvertedBuffers; + + public int Size { get; } + + private readonly IntPtr _map; + + private readonly MultiFenceHolder _waitable; + + private bool _lastAccessIsWrite; + + private readonly BufferAllocationType _baseType; + private readonly BufferAllocationType _activeType; + + private readonly ReaderWriterLockSlim _flushLock; + private FenceHolder _flushFence; + private int _flushWaiting; + + private byte[] _pendingData; + private BufferMirrorRangeList _pendingDataRanges; + private Dictionary _mirrors; + private bool _useMirrors; + + public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size, BufferAllocationType type, BufferAllocationType currentType) + { + _gd = gd; + _device = device; + _allocation = allocation; + _allocationAuto = new Auto(allocation); + _waitable = new MultiFenceHolder(size); + _buffer = new Auto(new DisposableBuffer(gd.Api, device, buffer), this, _waitable, _allocationAuto); + _bufferHandle = buffer.Handle; + Size = size; + _map = allocation.HostPointer; + + _baseType = type; + _activeType = currentType; + + _flushLock = new ReaderWriterLockSlim(); + _useMirrors = gd.IsTBDR; + } + + public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, Auto allocation, int size, BufferAllocationType type, BufferAllocationType currentType, int offset) + { + _gd = gd; + _device = device; + _allocation = allocation.GetUnsafe(); + _allocationAuto = allocation; + _allocationImported = true; + _waitable = new MultiFenceHolder(size); + _buffer = new Auto(new DisposableBuffer(gd.Api, device, buffer), this, _waitable, _allocationAuto); + _bufferHandle = buffer.Handle; + Size = size; + _map = _allocation.HostPointer + offset; + + _baseType = type; + _activeType = currentType; + + _flushLock = new ReaderWriterLockSlim(); + } + + public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, int size, Auto[] storageAllocations) + { + _gd = gd; + _device = device; + _waitable = new MultiFenceHolder(size); + _buffer = new Auto(new DisposableBuffer(gd.Api, device, buffer), _waitable, storageAllocations); + _bufferHandle = buffer.Handle; + Size = size; + + _baseType = BufferAllocationType.Sparse; + _activeType = BufferAllocationType.Sparse; + + _flushLock = new ReaderWriterLockSlim(); + } + + public unsafe Auto CreateView(VkFormat format, int offset, int size, Action invalidateView) + { + var bufferViewCreateInfo = new BufferViewCreateInfo + { + SType = StructureType.BufferViewCreateInfo, + Buffer = new VkBuffer(_bufferHandle), + Format = format, + Offset = (uint)offset, + Range = (uint)size, + }; + + _gd.Api.CreateBufferView(_device, in bufferViewCreateInfo, null, out var bufferView).ThrowOnError(); + + return new Auto(new DisposableBufferView(_gd.Api, _device, bufferView), this, _waitable, _buffer); + } + + public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite) + { + // If the last access is write, we always need a barrier to be sure we will read or modify + // the correct data. + // If the last access is read, and current one is a write, we need to wait until the + // read finishes to avoid overwriting data still in use. + // Otherwise, if the last access is a read and the current one too, we don't need barriers. + bool needsBarrier = isWrite || _lastAccessIsWrite; + + _lastAccessIsWrite = isWrite; + + if (needsBarrier) + { + MemoryBarrier memoryBarrier = new() + { + SType = StructureType.MemoryBarrier, + SrcAccessMask = DefaultAccessFlags, + DstAccessMask = DefaultAccessFlags, + }; + + _gd.Api.CmdPipelineBarrier( + commandBuffer, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.AllCommandsBit, + DependencyFlags.DeviceGroupBit, + 1, + in memoryBarrier, + 0, + null, + 0, + null); + } + } + + private static ulong ToMirrorKey(int offset, int size) + { + return ((ulong)offset << 32) | (uint)size; + } + + private static (int offset, int size) FromMirrorKey(ulong key) + { + return ((int)(key >> 32), (int)key); + } + + private unsafe bool TryGetMirror(CommandBufferScoped cbs, ref int offset, int size, out Auto buffer) + { + size = Math.Min(size, Size - offset); + + // Does this binding need to be mirrored? + + if (!_pendingDataRanges.OverlapsWith(offset, size)) + { + buffer = null; + return false; + } + + var key = ToMirrorKey(offset, size); + + if (_mirrors.TryGetValue(key, out StagingBufferReserved reserved)) + { + buffer = reserved.Buffer.GetBuffer(); + offset = reserved.Offset; + + return true; + } + + // Is this mirror allowed to exist? Can't be used for write in any in-flight write. + if (_waitable.IsBufferRangeInUse(offset, size, true)) + { + // Some of the data is not mirrorable, so upload the whole range. + ClearMirrors(cbs, offset, size); + + buffer = null; + return false; + } + + // Build data for the new mirror. + + var baseData = new Span((void*)(_map + offset), size); + var modData = _pendingData.AsSpan(offset, size); + + StagingBufferReserved? newMirror = _gd.BufferManager.StagingBuffer.TryReserveData(cbs, size); + + if (newMirror != null) + { + var mirror = newMirror.Value; + _pendingDataRanges.FillData(baseData, modData, offset, new Span((void*)(mirror.Buffer._map + mirror.Offset), size)); + + if (_mirrors.Count == 0) + { + _gd.PipelineInternal.RegisterActiveMirror(this); + } + + _mirrors.Add(key, mirror); + + buffer = mirror.Buffer.GetBuffer(); + offset = mirror.Offset; + + return true; + } + else + { + // Data could not be placed on the mirror, likely out of space. Force the data to flush. + ClearMirrors(cbs, offset, size); + + buffer = null; + return false; + } + } + + public Auto GetBuffer() + { + return _buffer; + } + + public Auto GetBuffer(CommandBuffer commandBuffer, bool isWrite = false, bool isSSBO = false) + { + if (isWrite) + { + SignalWrite(0, Size); + } + + return _buffer; + } + + public Auto GetBuffer(CommandBuffer commandBuffer, int offset, int size, bool isWrite = false) + { + if (isWrite) + { + SignalWrite(offset, size); + } + + return _buffer; + } + + public Auto GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored) + { + if (_pendingData != null && TryGetMirror(cbs, ref offset, size, out Auto result)) + { + mirrored = true; + return result; + } + + mirrored = false; + return _buffer; + } + + Auto IMirrorable.GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored) + { + // Cannot mirror buffer views right now. + + throw new NotImplementedException(); + } + + public void ClearMirrors() + { + // Clear mirrors without forcing a flush. This happens when the command buffer is switched, + // as all reserved areas on the staging buffer are released. + + if (_pendingData != null) + { + _mirrors.Clear(); + }; + } + + public void ClearMirrors(CommandBufferScoped cbs, int offset, int size) + { + // Clear mirrors in the given range, and submit overlapping pending data. + + if (_pendingData != null) + { + bool hadMirrors = _mirrors.Count > 0 && RemoveOverlappingMirrors(offset, size); + + if (_pendingDataRanges.Count() != 0) + { + UploadPendingData(cbs, offset, size); + } + + if (hadMirrors) + { + _gd.PipelineInternal.Rebind(_buffer, offset, size); + } + }; + } + + public void UseMirrors() + { + _useMirrors = true; + } + + private void UploadPendingData(CommandBufferScoped cbs, int offset, int size) + { + var ranges = _pendingDataRanges.FindOverlaps(offset, size); + + if (ranges != null) + { + _pendingDataRanges.Remove(offset, size); + + foreach (var range in ranges) + { + int rangeOffset = Math.Max(offset, range.Offset); + int rangeSize = Math.Min(offset + size, range.End) - rangeOffset; + + if (_gd.PipelineInternal.CurrentCommandBuffer.CommandBuffer.Handle == cbs.CommandBuffer.Handle) + { + SetData(rangeOffset, _pendingData.AsSpan(rangeOffset, rangeSize), cbs, _gd.PipelineInternal.EndRenderPassDelegate, false); + } + else + { + SetData(rangeOffset, _pendingData.AsSpan(rangeOffset, rangeSize), cbs, null, false); + } + } + } + } + + public Auto GetAllocation() + { + return _allocationAuto; + } + + public (DeviceMemory, ulong) GetDeviceMemoryAndOffset() + { + return (_allocation.Memory, _allocation.Offset); + } + + public void SignalWrite(int offset, int size) + { + if (offset == 0 && size == Size) + { + _cachedConvertedBuffers.Clear(); + } + else + { + _cachedConvertedBuffers.ClearRange(offset, size); + } + } + + public BufferHandle GetHandle() + { + var handle = _bufferHandle; + return Unsafe.As(ref handle); + } + + public IntPtr Map(int offset, int mappingSize) + { + return _map; + } + + private void ClearFlushFence() + { + // Assumes _flushLock is held as writer. + + if (_flushFence != null) + { + if (_flushWaiting == 0) + { + _flushFence.Put(); + } + + _flushFence = null; + } + } + + private void WaitForFlushFence() + { + if (_flushFence == null) + { + return; + } + + // If storage has changed, make sure the fence has been reached so that the data is in place. + _flushLock.ExitReadLock(); + _flushLock.EnterWriteLock(); + + if (_flushFence != null) + { + var fence = _flushFence; + Interlocked.Increment(ref _flushWaiting); + + // Don't wait in the lock. + + _flushLock.ExitWriteLock(); + + fence.Wait(); + + _flushLock.EnterWriteLock(); + + if (Interlocked.Decrement(ref _flushWaiting) == 0) + { + fence.Put(); + } + + _flushFence = null; + } + + // Assumes the _flushLock is held as reader, returns in same state. + _flushLock.ExitWriteLock(); + _flushLock.EnterReadLock(); + } + + public PinnedSpan GetData(int offset, int size) + { + _flushLock.EnterReadLock(); + + WaitForFlushFence(); + + Span result; + + if (_map != IntPtr.Zero) + { + result = GetDataStorage(offset, size); + + // Need to be careful here, the buffer can't be unmapped while the data is being used. + _buffer.IncrementReferenceCount(); + + _flushLock.ExitReadLock(); + + return PinnedSpan.UnsafeFromSpan(result, _buffer.DecrementReferenceCount); + } + + BackgroundResource resource = _gd.BackgroundResources.Get(); + + if (_gd.CommandBufferPool.OwnedByCurrentThread) + { + _gd.FlushAllCommands(); + + result = resource.GetFlushBuffer().GetBufferData(_gd.CommandBufferPool, this, offset, size); + } + else + { + result = resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size); + } + + _flushLock.ExitReadLock(); + + // Flush buffer is pinned until the next GetBufferData on the thread, which is fine for current uses. + return PinnedSpan.UnsafeFromSpan(result); + } + + public unsafe Span GetDataStorage(int offset, int size) + { + int mappingSize = Math.Min(size, Size - offset); + + if (_map != IntPtr.Zero) + { + return new Span((void*)(_map + offset), mappingSize); + } + + throw new InvalidOperationException("The buffer is not host mapped."); + } + + public bool RemoveOverlappingMirrors(int offset, int size) + { + List toRemove = null; + foreach (var key in _mirrors.Keys) + { + (int keyOffset, int keySize) = FromMirrorKey(key); + if (!(offset + size <= keyOffset || offset >= keyOffset + keySize)) + { + toRemove ??= new List(); + + toRemove.Add(key); + } + } + + if (toRemove != null) + { + foreach (var key in toRemove) + { + _mirrors.Remove(key); + } + + return true; + } + + return false; + } + + public unsafe void SetData(int offset, ReadOnlySpan data, CommandBufferScoped? cbs = null, Action endRenderPass = null, bool allowCbsWait = true) + { + int dataSize = Math.Min(data.Length, Size - offset); + if (dataSize == 0) + { + return; + } + + bool allowMirror = _useMirrors && allowCbsWait && cbs != null && _activeType <= BufferAllocationType.HostMapped; + + if (_map != IntPtr.Zero) + { + // If persistently mapped, set the data directly if the buffer is not currently in use. + bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool); + + // If the buffer is rented, take a little more time and check if the use overlaps this handle. + bool needsFlush = isRented && _waitable.IsBufferRangeInUse(offset, dataSize, false); + + if (!needsFlush) + { + WaitForFences(offset, dataSize); + + data[..dataSize].CopyTo(new Span((void*)(_map + offset), dataSize)); + + if (_pendingData != null) + { + bool removed = _pendingDataRanges.Remove(offset, dataSize); + if (RemoveOverlappingMirrors(offset, dataSize) || removed) + { + // If any mirrors were removed, rebind the buffer range. + _gd.PipelineInternal.Rebind(_buffer, offset, dataSize); + } + } + + SignalWrite(offset, dataSize); + + return; + } + } + + // If the buffer does not have an in-flight write (including an inline update), then upload data to a pendingCopy. + if (allowMirror && !_waitable.IsBufferRangeInUse(offset, dataSize, true)) + { + if (_pendingData == null) + { + _pendingData = new byte[Size]; + _mirrors = new Dictionary(); + } + + data[..dataSize].CopyTo(_pendingData.AsSpan(offset, dataSize)); + _pendingDataRanges.Add(offset, dataSize); + + // Remove any overlapping mirrors. + RemoveOverlappingMirrors(offset, dataSize); + + // Tell the graphics device to rebind any constant buffer that overlaps the newly modified range, as it should access a mirror. + _gd.PipelineInternal.Rebind(_buffer, offset, dataSize); + + return; + } + + if (_pendingData != null) + { + _pendingDataRanges.Remove(offset, dataSize); + } + + if (cbs != null && + _gd.PipelineInternal.RenderPassActive && + !(_buffer.HasCommandBufferDependency(cbs.Value) && + _waitable.IsBufferRangeInUse(cbs.Value.CommandBufferIndex, offset, dataSize))) + { + // If the buffer hasn't been used on the command buffer yet, try to preload the data. + // This avoids ending and beginning render passes on each buffer data upload. + + cbs = _gd.PipelineInternal.GetPreloadCommandBuffer(); + endRenderPass = null; + } + + if (cbs == null || + !VulkanConfiguration.UseFastBufferUpdates || + data.Length > MaxUpdateBufferSize || + !TryPushData(cbs.Value, endRenderPass, offset, data)) + { + if (allowCbsWait) + { + _gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, cbs, endRenderPass, this, offset, data); + } + else + { + bool rentCbs = cbs == null; + if (rentCbs) + { + cbs = _gd.CommandBufferPool.Rent(); + } + + if (!_gd.BufferManager.StagingBuffer.TryPushData(cbs.Value, endRenderPass, this, offset, data)) + { + // Need to do a slow upload. + BufferHolder srcHolder = _gd.BufferManager.Create(_gd, dataSize, baseType: BufferAllocationType.HostMapped); + srcHolder.SetDataUnchecked(0, data); + + var srcBuffer = srcHolder.GetBuffer(); + var dstBuffer = this.GetBuffer(cbs.Value.CommandBuffer, true); + + Copy(_gd, cbs.Value, srcBuffer, dstBuffer, 0, offset, dataSize); + + srcHolder.Dispose(); + } + + if (rentCbs) + { + cbs.Value.Dispose(); + } + } + } + } + + public unsafe void SetDataUnchecked(int offset, ReadOnlySpan data) + { + int dataSize = Math.Min(data.Length, Size - offset); + if (dataSize == 0) + { + return; + } + + if (_map != IntPtr.Zero) + { + data[..dataSize].CopyTo(new Span((void*)(_map + offset), dataSize)); + } + else + { + _gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, null, null, this, offset, data); + } + } + + public unsafe void SetDataUnchecked(int offset, ReadOnlySpan data) where T : unmanaged + { + SetDataUnchecked(offset, MemoryMarshal.AsBytes(data)); + } + + public void SetDataInline(CommandBufferScoped cbs, Action endRenderPass, int dstOffset, ReadOnlySpan data) + { + if (!TryPushData(cbs, endRenderPass, dstOffset, data)) + { + throw new ArgumentException($"Invalid offset 0x{dstOffset:X} or data size 0x{data.Length:X}."); + } + } + + private unsafe bool TryPushData(CommandBufferScoped cbs, Action endRenderPass, int dstOffset, ReadOnlySpan data) + { + if ((dstOffset & 3) != 0 || (data.Length & 3) != 0) + { + return false; + } + + endRenderPass?.Invoke(); + + var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length, true).Value; + + InsertBufferBarrier( + _gd, + cbs.CommandBuffer, + dstBuffer, + DefaultAccessFlags, + AccessFlags.TransferWriteBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.TransferBit, + dstOffset, + data.Length); + + fixed (byte* pData = data) + { + for (ulong offset = 0; offset < (ulong)data.Length;) + { + ulong size = Math.Min(MaxUpdateBufferSize, (ulong)data.Length - offset); + _gd.Api.CmdUpdateBuffer(cbs.CommandBuffer, dstBuffer, (ulong)dstOffset + offset, size, pData + offset); + offset += size; + } + } + + InsertBufferBarrier( + _gd, + cbs.CommandBuffer, + dstBuffer, + AccessFlags.TransferWriteBit, + DefaultAccessFlags, + PipelineStageFlags.TransferBit, + PipelineStageFlags.AllCommandsBit, + dstOffset, + data.Length); + + return true; + } + + public static unsafe void Copy( + VulkanRenderer gd, + CommandBufferScoped cbs, + Auto src, + Auto dst, + int srcOffset, + int dstOffset, + int size, + bool registerSrcUsage = true) + { + var srcBuffer = registerSrcUsage ? src.Get(cbs, srcOffset, size).Value : src.GetUnsafe().Value; + var dstBuffer = dst.Get(cbs, dstOffset, size, true).Value; + + InsertBufferBarrier( + gd, + cbs.CommandBuffer, + dstBuffer, + DefaultAccessFlags, + AccessFlags.TransferWriteBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.TransferBit, + dstOffset, + size); + + var region = new BufferCopy((ulong)srcOffset, (ulong)dstOffset, (ulong)size); + + gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, 1, ®ion); + + InsertBufferBarrier( + gd, + cbs.CommandBuffer, + dstBuffer, + AccessFlags.TransferWriteBit, + DefaultAccessFlags, + PipelineStageFlags.TransferBit, + PipelineStageFlags.AllCommandsBit, + dstOffset, + size); + } + + public static unsafe void InsertBufferBarrier( + VulkanRenderer gd, + CommandBuffer commandBuffer, + VkBuffer buffer, + AccessFlags srcAccessMask, + AccessFlags dstAccessMask, + PipelineStageFlags srcStageMask, + PipelineStageFlags dstStageMask, + int offset, + int size) + { + BufferMemoryBarrier memoryBarrier = new() + { + SType = StructureType.BufferMemoryBarrier, + SrcAccessMask = srcAccessMask, + DstAccessMask = dstAccessMask, + SrcQueueFamilyIndex = Vk.QueueFamilyIgnored, + DstQueueFamilyIndex = Vk.QueueFamilyIgnored, + Buffer = buffer, + Offset = (ulong)offset, + Size = (ulong)size, + }; + + gd.Api.CmdPipelineBarrier( + commandBuffer, + srcStageMask, + dstStageMask, + 0, + 0, + null, + 1, + in memoryBarrier, + 0, + null); + } + + public void WaitForFences() + { + _waitable.WaitForFences(_gd.Api, _device); + } + + public void WaitForFences(int offset, int size) + { + _waitable.WaitForFences(_gd.Api, _device, offset, size); + } + + private bool BoundToRange(int offset, ref int size) + { + if (offset >= Size) + { + return false; + } + + size = Math.Min(Size - offset, size); + + return true; + } + + public Auto GetBufferI8ToI16(CommandBufferScoped cbs, int offset, int size) + { + if (!BoundToRange(offset, ref size)) + { + return null; + } + + var key = new I8ToI16CacheKey(_gd); + + if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder)) + { + holder = _gd.BufferManager.Create(_gd, (size * 2 + 3) & ~3, baseType: BufferAllocationType.DeviceLocal); + + _gd.PipelineInternal.EndRenderPass(); + _gd.HelperShader.ConvertI8ToI16(_gd, cbs, this, holder, offset, size); + + key.SetBuffer(holder.GetBuffer()); + + _cachedConvertedBuffers.Add(offset, size, key, holder); + } + + return holder.GetBuffer(); + } + + public Auto GetAlignedVertexBuffer(CommandBufferScoped cbs, int offset, int size, int stride, int alignment) + { + if (!BoundToRange(offset, ref size)) + { + return null; + } + + var key = new AlignedVertexBufferCacheKey(_gd, stride, alignment); + + if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder)) + { + int alignedStride = (stride + (alignment - 1)) & -alignment; + + holder = _gd.BufferManager.Create(_gd, (size / stride) * alignedStride, baseType: BufferAllocationType.DeviceLocal); + + _gd.PipelineInternal.EndRenderPass(); + _gd.HelperShader.ChangeStride(_gd, cbs, this, holder, offset, size, stride, alignedStride); + + key.SetBuffer(holder.GetBuffer()); + + _cachedConvertedBuffers.Add(offset, size, key, holder); + } + + return holder.GetBuffer(); + } + + public Auto GetBufferTopologyConversion(CommandBufferScoped cbs, int offset, int size, IndexBufferPattern pattern, int indexSize) + { + if (!BoundToRange(offset, ref size)) + { + return null; + } + + var key = new TopologyConversionCacheKey(_gd, pattern, indexSize); + + if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder)) + { + // The destination index size is always I32. + + int indexCount = size / indexSize; + + int convertedCount = pattern.GetConvertedCount(indexCount); + + holder = _gd.BufferManager.Create(_gd, convertedCount * 4, baseType: BufferAllocationType.DeviceLocal); + + _gd.PipelineInternal.EndRenderPass(); + _gd.HelperShader.ConvertIndexBuffer(_gd, cbs, this, holder, pattern, indexSize, offset, indexCount); + + key.SetBuffer(holder.GetBuffer()); + + _cachedConvertedBuffers.Add(offset, size, key, holder); + } + + return holder.GetBuffer(); + } + + public bool TryGetCachedConvertedBuffer(int offset, int size, ICacheKey key, out BufferHolder holder) + { + return _cachedConvertedBuffers.TryGetValue(offset, size, key, out holder); + } + + public void AddCachedConvertedBuffer(int offset, int size, ICacheKey key, BufferHolder holder) + { + _cachedConvertedBuffers.Add(offset, size, key, holder); + } + + public void AddCachedConvertedBufferDependency(int offset, int size, ICacheKey key, Dependency dependency) + { + _cachedConvertedBuffers.AddDependency(offset, size, key, dependency); + } + + public void RemoveCachedConvertedBuffer(int offset, int size, ICacheKey key) + { + _cachedConvertedBuffers.Remove(offset, size, key); + } + + public void Dispose() + { + _gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size); + + _buffer.Dispose(); + _cachedConvertedBuffers.Dispose(); + if (_allocationImported) + { + _allocationAuto.DecrementReferenceCount(); + } + else + { + _allocationAuto?.Dispose(); + } + + _flushLock.EnterWriteLock(); + + ClearFlushFence(); + + _flushLock.ExitWriteLock(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/BufferManager.cs b/src/Ryujinx.Graphics.Vulkan/BufferManager.cs new file mode 100644 index 00000000..7523913e --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/BufferManager.cs @@ -0,0 +1,679 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using VkBuffer = Silk.NET.Vulkan.Buffer; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct ScopedTemporaryBuffer : IDisposable + { + private readonly BufferManager _bufferManager; + private readonly bool _isReserved; + + public readonly BufferRange Range; + public readonly BufferHolder Holder; + + public BufferHandle Handle => Range.Handle; + public int Offset => Range.Offset; + + public ScopedTemporaryBuffer(BufferManager bufferManager, BufferHolder holder, BufferHandle handle, int offset, int size, bool isReserved) + { + _bufferManager = bufferManager; + + Range = new BufferRange(handle, offset, size); + Holder = holder; + + _isReserved = isReserved; + } + + public void Dispose() + { + if (!_isReserved) + { + _bufferManager.Delete(Range.Handle); + } + } + } + + class BufferManager : IDisposable + { + public const MemoryPropertyFlags DefaultBufferMemoryFlags = + MemoryPropertyFlags.HostVisibleBit | + MemoryPropertyFlags.HostCoherentBit | + MemoryPropertyFlags.HostCachedBit; + + // Some drivers don't expose a "HostCached" memory type, + // so we need those alternative flags for the allocation to succeed there. + private const MemoryPropertyFlags DefaultBufferMemoryNoCacheFlags = + MemoryPropertyFlags.HostVisibleBit | + MemoryPropertyFlags.HostCoherentBit; + + private const MemoryPropertyFlags DeviceLocalBufferMemoryFlags = + MemoryPropertyFlags.DeviceLocalBit; + + private const MemoryPropertyFlags DeviceLocalMappedBufferMemoryFlags = + MemoryPropertyFlags.DeviceLocalBit | + MemoryPropertyFlags.HostVisibleBit | + MemoryPropertyFlags.HostCoherentBit; + + private const BufferUsageFlags DefaultBufferUsageFlags = + BufferUsageFlags.TransferSrcBit | + BufferUsageFlags.TransferDstBit | + BufferUsageFlags.UniformTexelBufferBit | + BufferUsageFlags.StorageTexelBufferBit | + BufferUsageFlags.UniformBufferBit | + BufferUsageFlags.StorageBufferBit | + BufferUsageFlags.IndexBufferBit | + BufferUsageFlags.VertexBufferBit | + BufferUsageFlags.TransformFeedbackBufferBitExt; + + private const BufferUsageFlags HostImportedBufferUsageFlags = + BufferUsageFlags.TransferSrcBit | + BufferUsageFlags.TransferDstBit; + + private readonly Device _device; + + private readonly IdList _buffers; + + public int BufferCount { get; private set; } + + public StagingBuffer StagingBuffer { get; } + + public MemoryRequirements HostImportedBufferMemoryRequirements { get; } + + public BufferManager(VulkanRenderer gd, Device device) + { + _device = device; + _buffers = new IdList(); + StagingBuffer = new StagingBuffer(gd, this); + + HostImportedBufferMemoryRequirements = GetHostImportedUsageRequirements(gd); + } + + public unsafe BufferHandle CreateHostImported(VulkanRenderer gd, nint pointer, int size) + { + var usage = HostImportedBufferUsageFlags; + + if (gd.Capabilities.SupportsIndirectParameters) + { + usage |= BufferUsageFlags.IndirectBufferBit; + } + + var externalMemoryBuffer = new ExternalMemoryBufferCreateInfo + { + SType = StructureType.ExternalMemoryBufferCreateInfo, + HandleTypes = ExternalMemoryHandleTypeFlags.HostAllocationBitExt, + }; + + var bufferCreateInfo = new BufferCreateInfo + { + SType = StructureType.BufferCreateInfo, + Size = (ulong)size, + Usage = usage, + SharingMode = SharingMode.Exclusive, + PNext = &externalMemoryBuffer, + }; + + gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError(); + + (Auto allocation, ulong offset) = gd.HostMemoryAllocator.GetExistingAllocation(pointer, (ulong)size); + + gd.Api.BindBufferMemory(_device, buffer, allocation.GetUnsafe().Memory, allocation.GetUnsafe().Offset + offset); + + var holder = new BufferHolder(gd, _device, buffer, allocation, size, BufferAllocationType.HostMapped, BufferAllocationType.HostMapped, (int)offset); + + BufferCount++; + + ulong handle64 = (uint)_buffers.Add(holder); + + return Unsafe.As(ref handle64); + } + + public unsafe BufferHandle CreateSparse(VulkanRenderer gd, ReadOnlySpan storageBuffers) + { + var usage = DefaultBufferUsageFlags; + + if (gd.Capabilities.SupportsIndirectParameters) + { + usage |= BufferUsageFlags.IndirectBufferBit; + } + + ulong size = 0; + + foreach (BufferRange range in storageBuffers) + { + size += (ulong)range.Size; + } + + var bufferCreateInfo = new BufferCreateInfo() + { + SType = StructureType.BufferCreateInfo, + Size = size, + Usage = usage, + SharingMode = SharingMode.Exclusive, + Flags = BufferCreateFlags.SparseBindingBit | BufferCreateFlags.SparseAliasedBit + }; + + gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError(); + + var memoryBinds = new SparseMemoryBind[storageBuffers.Length]; + var storageAllocations = new Auto[storageBuffers.Length]; + int storageAllocationsCount = 0; + + ulong dstOffset = 0; + + for (int index = 0; index < storageBuffers.Length; index++) + { + BufferRange range = storageBuffers[index]; + + if (TryGetBuffer(range.Handle, out var existingHolder)) + { + (var memory, var offset) = existingHolder.GetDeviceMemoryAndOffset(); + + memoryBinds[index] = new SparseMemoryBind() + { + ResourceOffset = dstOffset, + Size = (ulong)range.Size, + Memory = memory, + MemoryOffset = offset + (ulong)range.Offset, + Flags = SparseMemoryBindFlags.None + }; + + storageAllocations[storageAllocationsCount++] = existingHolder.GetAllocation(); + } + else + { + memoryBinds[index] = new SparseMemoryBind() + { + ResourceOffset = dstOffset, + Size = (ulong)range.Size, + Memory = default, + MemoryOffset = 0UL, + Flags = SparseMemoryBindFlags.None + }; + } + + dstOffset += (ulong)range.Size; + } + + if (storageAllocations.Length != storageAllocationsCount) + { + Array.Resize(ref storageAllocations, storageAllocationsCount); + } + + fixed (SparseMemoryBind* pMemoryBinds = memoryBinds) + { + SparseBufferMemoryBindInfo bufferBind = new SparseBufferMemoryBindInfo() + { + Buffer = buffer, + BindCount = (uint)memoryBinds.Length, + PBinds = pMemoryBinds + }; + + BindSparseInfo bindSparseInfo = new BindSparseInfo() + { + SType = StructureType.BindSparseInfo, + BufferBindCount = 1, + PBufferBinds = &bufferBind + }; + + gd.Api.QueueBindSparse(gd.Queue, 1, in bindSparseInfo, default).ThrowOnError(); + } + + var holder = new BufferHolder(gd, _device, buffer, (int)size, storageAllocations); + + BufferCount++; + + ulong handle64 = (uint)_buffers.Add(holder); + + return Unsafe.As(ref handle64); + } + + public BufferHandle CreateWithHandle( + VulkanRenderer gd, + int size, + bool sparseCompatible = false, + BufferAllocationType baseType = BufferAllocationType.HostMapped, + bool forceMirrors = false) + { + return CreateWithHandle(gd, size, out _, sparseCompatible, baseType, forceMirrors); + } + + public BufferHandle CreateWithHandle( + VulkanRenderer gd, + int size, + out BufferHolder holder, + bool sparseCompatible = false, + BufferAllocationType baseType = BufferAllocationType.HostMapped, + bool forceMirrors = false) + { + holder = Create(gd, size, forConditionalRendering: false, sparseCompatible, baseType); + if (holder == null) + { + return BufferHandle.Null; + } + + if (forceMirrors) + { + holder.UseMirrors(); + } + + BufferCount++; + + ulong handle64 = (uint)_buffers.Add(holder); + + return Unsafe.As(ref handle64); + } + + public ScopedTemporaryBuffer ReserveOrCreate(VulkanRenderer gd, CommandBufferScoped cbs, int size) + { + StagingBufferReserved? result = StagingBuffer.TryReserveData(cbs, size); + + if (result.HasValue) + { + return new ScopedTemporaryBuffer(this, result.Value.Buffer, StagingBuffer.Handle, result.Value.Offset, result.Value.Size, true); + } + else + { + // Create a temporary buffer. + BufferHandle handle = CreateWithHandle(gd, size, out BufferHolder holder); + + return new ScopedTemporaryBuffer(this, holder, handle, 0, size, false); + } + } + + public unsafe MemoryRequirements GetHostImportedUsageRequirements(VulkanRenderer gd) + { + var usage = HostImportedBufferUsageFlags; + + if (gd.Capabilities.SupportsIndirectParameters) + { + usage |= BufferUsageFlags.IndirectBufferBit; + } + + var bufferCreateInfo = new BufferCreateInfo + { + SType = StructureType.BufferCreateInfo, + Size = (ulong)Environment.SystemPageSize, + Usage = usage, + SharingMode = SharingMode.Exclusive, + }; + + gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError(); + + gd.Api.GetBufferMemoryRequirements(_device, buffer, out var requirements); + + gd.Api.DestroyBuffer(_device, buffer, null); + + return requirements; + } + + public unsafe (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) CreateBacking( + VulkanRenderer gd, + int size, + BufferAllocationType type, + bool forConditionalRendering = false, + bool sparseCompatible = false, + BufferAllocationType fallbackType = BufferAllocationType.Auto) + { + var usage = DefaultBufferUsageFlags; + + if (forConditionalRendering && gd.Capabilities.SupportsConditionalRendering) + { + usage |= BufferUsageFlags.ConditionalRenderingBitExt; + } + else if (gd.Capabilities.SupportsIndirectParameters) + { + usage |= BufferUsageFlags.IndirectBufferBit; + } + + var bufferCreateInfo = new BufferCreateInfo + { + SType = StructureType.BufferCreateInfo, + Size = (ulong)size, + Usage = usage, + SharingMode = SharingMode.Exclusive, + }; + + gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError(); + gd.Api.GetBufferMemoryRequirements(_device, buffer, out var requirements); + + if (sparseCompatible) + { + requirements.Alignment = Math.Max(requirements.Alignment, Constants.SparseBufferAlignment); + } + + MemoryAllocation allocation; + + do + { + var allocateFlags = type switch + { + BufferAllocationType.HostMappedNoCache => DefaultBufferMemoryNoCacheFlags, + BufferAllocationType.HostMapped => DefaultBufferMemoryFlags, + BufferAllocationType.DeviceLocal => DeviceLocalBufferMemoryFlags, + BufferAllocationType.DeviceLocalMapped => DeviceLocalMappedBufferMemoryFlags, + _ => DefaultBufferMemoryFlags, + }; + + // If an allocation with this memory type fails, fall back to the previous one. + try + { + allocation = gd.MemoryAllocator.AllocateDeviceMemory(requirements, allocateFlags, true); + } + catch (VulkanException) + { + allocation = default; + } + } + while (allocation.Memory.Handle == 0 && (--type != fallbackType)); + + if (allocation.Memory.Handle == 0UL) + { + gd.Api.DestroyBuffer(_device, buffer, null); + return default; + } + + gd.Api.BindBufferMemory(_device, buffer, allocation.Memory, allocation.Offset); + + return (buffer, allocation, type); + } + + public BufferHolder Create( + VulkanRenderer gd, + int size, + bool forConditionalRendering = false, + bool sparseCompatible = false, + BufferAllocationType baseType = BufferAllocationType.HostMapped) + { + BufferAllocationType type = baseType; + + if (baseType == BufferAllocationType.Auto) + { + type = BufferAllocationType.HostMapped; + } + + (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) = + CreateBacking(gd, size, type, forConditionalRendering, sparseCompatible); + + if (buffer.Handle != 0) + { + var holder = new BufferHolder(gd, _device, buffer, allocation, size, baseType, resultType); + + return holder; + } + + Logger.Error?.Print(LogClass.Gpu, $"Failed to create buffer with size 0x{size:X} and type \"{baseType}\"."); + + return null; + } + + public Auto CreateView(BufferHandle handle, VkFormat format, int offset, int size, Action invalidateView) + { + if (TryGetBuffer(handle, out var holder)) + { + return holder.CreateView(format, offset, size, invalidateView); + } + + return null; + } + + public Auto GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite, bool isSSBO = false) + { + if (TryGetBuffer(handle, out var holder)) + { + return holder.GetBuffer(commandBuffer, isWrite, isSSBO); + } + + return null; + } + + public Auto GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, int offset, int size, bool isWrite) + { + if (TryGetBuffer(handle, out var holder)) + { + return holder.GetBuffer(commandBuffer, offset, size, isWrite); + } + + return null; + } + + public Auto GetBufferI8ToI16(CommandBufferScoped cbs, BufferHandle handle, int offset, int size) + { + if (TryGetBuffer(handle, out var holder)) + { + return holder.GetBufferI8ToI16(cbs, offset, size); + } + + return null; + } + + public Auto GetAlignedVertexBuffer(CommandBufferScoped cbs, BufferHandle handle, int offset, int size, int stride, int alignment) + { + if (TryGetBuffer(handle, out var holder)) + { + return holder.GetAlignedVertexBuffer(cbs, offset, size, stride, alignment); + } + + return null; + } + + public Auto GetBufferTopologyConversion(CommandBufferScoped cbs, BufferHandle handle, int offset, int size, IndexBufferPattern pattern, int indexSize) + { + if (TryGetBuffer(handle, out var holder)) + { + return holder.GetBufferTopologyConversion(cbs, offset, size, pattern, indexSize); + } + + return null; + } + + public (Auto, Auto) GetBufferTopologyConversionIndirect( + VulkanRenderer gd, + CommandBufferScoped cbs, + BufferRange indexBuffer, + BufferRange indirectBuffer, + BufferRange drawCountBuffer, + IndexBufferPattern pattern, + int indexSize, + bool hasDrawCount, + int maxDrawCount, + int indirectDataStride) + { + BufferHolder drawCountBufferHolder = null; + + if (!TryGetBuffer(indexBuffer.Handle, out var indexBufferHolder) || + !TryGetBuffer(indirectBuffer.Handle, out var indirectBufferHolder) || + (hasDrawCount && !TryGetBuffer(drawCountBuffer.Handle, out drawCountBufferHolder))) + { + return (null, null); + } + + var indexBufferKey = new TopologyConversionIndirectCacheKey( + gd, + pattern, + indexSize, + indirectBufferHolder, + indirectBuffer.Offset, + indirectBuffer.Size); + + bool hasConvertedIndexBuffer = indexBufferHolder.TryGetCachedConvertedBuffer( + indexBuffer.Offset, + indexBuffer.Size, + indexBufferKey, + out var convertedIndexBuffer); + + var indirectBufferKey = new IndirectDataCacheKey(pattern); + bool hasConvertedIndirectBuffer = indirectBufferHolder.TryGetCachedConvertedBuffer( + indirectBuffer.Offset, + indirectBuffer.Size, + indirectBufferKey, + out var convertedIndirectBuffer); + + var drawCountBufferKey = new DrawCountCacheKey(); + bool hasCachedDrawCount = true; + + if (hasDrawCount) + { + hasCachedDrawCount = drawCountBufferHolder.TryGetCachedConvertedBuffer( + drawCountBuffer.Offset, + drawCountBuffer.Size, + drawCountBufferKey, + out _); + } + + if (!hasConvertedIndexBuffer || !hasConvertedIndirectBuffer || !hasCachedDrawCount) + { + // The destination index size is always I32. + + int indexCount = indexBuffer.Size / indexSize; + + int convertedCount = pattern.GetConvertedCount(indexCount); + + if (!hasConvertedIndexBuffer) + { + convertedIndexBuffer = Create(gd, convertedCount * 4); + indexBufferKey.SetBuffer(convertedIndexBuffer.GetBuffer()); + indexBufferHolder.AddCachedConvertedBuffer(indexBuffer.Offset, indexBuffer.Size, indexBufferKey, convertedIndexBuffer); + } + + if (!hasConvertedIndirectBuffer) + { + convertedIndirectBuffer = Create(gd, indirectBuffer.Size); + indirectBufferHolder.AddCachedConvertedBuffer(indirectBuffer.Offset, indirectBuffer.Size, indirectBufferKey, convertedIndirectBuffer); + } + + gd.PipelineInternal.EndRenderPass(); + gd.HelperShader.ConvertIndexBufferIndirect( + gd, + cbs, + indirectBufferHolder, + convertedIndirectBuffer, + drawCountBuffer, + indexBufferHolder, + convertedIndexBuffer, + pattern, + indexSize, + indexBuffer.Offset, + indexBuffer.Size, + indirectBuffer.Offset, + hasDrawCount, + maxDrawCount, + indirectDataStride); + + // Any modification of the indirect buffer should invalidate the index buffers that are associated with it, + // since we used the indirect data to find the range of the index buffer that is used. + + var indexBufferDependency = new Dependency( + indexBufferHolder, + indexBuffer.Offset, + indexBuffer.Size, + indexBufferKey); + + indirectBufferHolder.AddCachedConvertedBufferDependency( + indirectBuffer.Offset, + indirectBuffer.Size, + indirectBufferKey, + indexBufferDependency); + + if (hasDrawCount) + { + if (!hasCachedDrawCount) + { + drawCountBufferHolder.AddCachedConvertedBuffer(drawCountBuffer.Offset, drawCountBuffer.Size, drawCountBufferKey, null); + } + + // If we have a draw count, any modification of the draw count should invalidate all indirect buffers + // where we used it to find the range of indirect data that is actually used. + + var indirectBufferDependency = new Dependency( + indirectBufferHolder, + indirectBuffer.Offset, + indirectBuffer.Size, + indirectBufferKey); + + drawCountBufferHolder.AddCachedConvertedBufferDependency( + drawCountBuffer.Offset, + drawCountBuffer.Size, + drawCountBufferKey, + indirectBufferDependency); + } + } + + return (convertedIndexBuffer.GetBuffer(), convertedIndirectBuffer.GetBuffer()); + } + + public Auto GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite, out int size) + { + if (TryGetBuffer(handle, out var holder)) + { + size = holder.Size; + return holder.GetBuffer(commandBuffer, isWrite); + } + + size = 0; + return null; + } + + public PinnedSpan GetData(BufferHandle handle, int offset, int size) + { + if (TryGetBuffer(handle, out var holder)) + { + return holder.GetData(offset, size); + } + + return new PinnedSpan(); + } + + public void SetData(BufferHandle handle, int offset, ReadOnlySpan data) where T : unmanaged + { + SetData(handle, offset, MemoryMarshal.Cast(data), null, null); + } + + public void SetData(BufferHandle handle, int offset, ReadOnlySpan data, CommandBufferScoped? cbs, Action endRenderPass) + { + if (TryGetBuffer(handle, out var holder)) + { + holder.SetData(offset, data, cbs, endRenderPass); + } + } + + public void Delete(BufferHandle handle) + { + if (TryGetBuffer(handle, out var holder)) + { + holder.Dispose(); + _buffers.Remove((int)Unsafe.As(ref handle)); + } + } + + private bool TryGetBuffer(BufferHandle handle, out BufferHolder holder) + { + return _buffers.TryGetValue((int)Unsafe.As(ref handle), out holder); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + StagingBuffer.Dispose(); + + foreach (BufferHolder buffer in _buffers) + { + buffer.Dispose(); + } + + _buffers.Clear(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/BufferMirrorRangeList.cs b/src/Ryujinx.Graphics.Vulkan/BufferMirrorRangeList.cs new file mode 100644 index 00000000..5722ca1a --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/BufferMirrorRangeList.cs @@ -0,0 +1,305 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + /// + /// A structure tracking pending upload ranges for buffers. + /// Where a range is present, pending data exists that can either be used to build mirrors + /// or upload directly to the buffer. + /// + struct BufferMirrorRangeList + { + internal readonly struct Range + { + public int Offset { get; } + public int Size { get; } + + public int End => Offset + Size; + + public Range(int offset, int size) + { + Offset = offset; + Size = size; + } + + public bool OverlapsWith(int offset, int size) + { + return Offset < offset + size && offset < Offset + Size; + } + } + + private List _ranges; + + public readonly IEnumerable All() + { + return _ranges; + } + + public readonly bool Remove(int offset, int size) + { + var list = _ranges; + bool removedAny = false; + if (list != null) + { + int overlapIndex = BinarySearch(list, offset, size); + + if (overlapIndex >= 0) + { + // Overlaps with a range. Search back to find the first one it doesn't overlap with. + + while (overlapIndex > 0 && list[overlapIndex - 1].OverlapsWith(offset, size)) + { + overlapIndex--; + } + + int endOffset = offset + size; + int startIndex = overlapIndex; + + var currentOverlap = list[overlapIndex]; + + // Orphan the start of the overlap. + if (currentOverlap.Offset < offset) + { + list[overlapIndex] = new Range(currentOverlap.Offset, offset - currentOverlap.Offset); + currentOverlap = new Range(offset, currentOverlap.End - offset); + list.Insert(++overlapIndex, currentOverlap); + startIndex++; + + removedAny = true; + } + + // Remove any middle overlaps. + while (currentOverlap.Offset < endOffset) + { + if (currentOverlap.End > endOffset) + { + // Update the end overlap instead of removing it, if it spans beyond the removed range. + list[overlapIndex] = new Range(endOffset, currentOverlap.End - endOffset); + + removedAny = true; + break; + } + + if (++overlapIndex >= list.Count) + { + break; + } + + currentOverlap = list[overlapIndex]; + } + + int count = overlapIndex - startIndex; + + list.RemoveRange(startIndex, count); + + removedAny |= count > 0; + } + } + + return removedAny; + } + + public void Add(int offset, int size) + { + var list = _ranges; + if (list != null) + { + int overlapIndex = BinarySearch(list, offset, size); + if (overlapIndex >= 0) + { + while (overlapIndex > 0 && list[overlapIndex - 1].OverlapsWith(offset, size)) + { + overlapIndex--; + } + + int endOffset = offset + size; + int startIndex = overlapIndex; + + while (overlapIndex < list.Count && list[overlapIndex].OverlapsWith(offset, size)) + { + var currentOverlap = list[overlapIndex]; + var currentOverlapEndOffset = currentOverlap.Offset + currentOverlap.Size; + + if (offset > currentOverlap.Offset) + { + offset = currentOverlap.Offset; + } + + if (endOffset < currentOverlapEndOffset) + { + endOffset = currentOverlapEndOffset; + } + + overlapIndex++; + size = endOffset - offset; + } + + int count = overlapIndex - startIndex; + + list.RemoveRange(startIndex, count); + + overlapIndex = startIndex; + } + else + { + overlapIndex = ~overlapIndex; + } + + list.Insert(overlapIndex, new Range(offset, size)); + } + else + { + _ranges = new List + { + new Range(offset, size) + }; + } + } + + public readonly bool OverlapsWith(int offset, int size) + { + var list = _ranges; + if (list == null) + { + return false; + } + + return BinarySearch(list, offset, size) >= 0; + } + + public readonly List FindOverlaps(int offset, int size) + { + var list = _ranges; + if (list == null) + { + return null; + } + + List result = null; + + int index = BinarySearch(list, offset, size); + + if (index >= 0) + { + while (index > 0 && list[index - 1].OverlapsWith(offset, size)) + { + index--; + } + + do + { + (result ??= new List()).Add(list[index++]); + } + while (index < list.Count && list[index].OverlapsWith(offset, size)); + } + + return result; + } + + private static int BinarySearch(List list, int offset, int size) + { + int left = 0; + int right = list.Count - 1; + + while (left <= right) + { + int range = right - left; + + int middle = left + (range >> 1); + + var item = list[middle]; + + if (item.OverlapsWith(offset, size)) + { + return middle; + } + + if (offset < item.Offset) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return ~left; + } + + public readonly void FillData(Span baseData, Span modData, int offset, Span result) + { + int size = baseData.Length; + int endOffset = offset + size; + + var list = _ranges; + if (list == null) + { + baseData.CopyTo(result); + } + + int srcOffset = offset; + int dstOffset = 0; + bool activeRange = false; + + for (int i = 0; i < list.Count; i++) + { + var range = list[i]; + + int rangeEnd = range.Offset + range.Size; + + if (activeRange) + { + if (range.Offset >= endOffset) + { + break; + } + } + else + { + if (rangeEnd <= offset) + { + continue; + } + + activeRange = true; + } + + int baseSize = range.Offset - srcOffset; + + if (baseSize > 0) + { + baseData.Slice(dstOffset, baseSize).CopyTo(result.Slice(dstOffset, baseSize)); + srcOffset += baseSize; + dstOffset += baseSize; + } + + int modSize = Math.Min(rangeEnd - srcOffset, endOffset - srcOffset); + if (modSize != 0) + { + modData.Slice(dstOffset, modSize).CopyTo(result.Slice(dstOffset, modSize)); + srcOffset += modSize; + dstOffset += modSize; + } + } + + int baseSizeEnd = endOffset - srcOffset; + + if (baseSizeEnd > 0) + { + baseData.Slice(dstOffset, baseSizeEnd).CopyTo(result.Slice(dstOffset, baseSizeEnd)); + } + } + + public readonly int Count() + { + return _ranges?.Count ?? 0; + } + + public void Clear() + { + _ranges = null; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/BufferState.cs b/src/Ryujinx.Graphics.Vulkan/BufferState.cs new file mode 100644 index 00000000..e49df765 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/BufferState.cs @@ -0,0 +1,56 @@ +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + struct BufferState : IDisposable + { + public static BufferState Null => new(null, 0, 0); + + private readonly int _offset; + private readonly int _size; + + private Auto _buffer; + + public BufferState(Auto buffer, int offset, int size) + { + _buffer = buffer; + _offset = offset; + _size = size; + buffer?.IncrementReferenceCount(); + } + + public readonly void BindTransformFeedbackBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding) + { + if (_buffer != null) + { + var buffer = _buffer.Get(cbs, _offset, _size, true).Value; + + ulong offset = (ulong)_offset; + ulong size = (ulong)_size; + + gd.TransformFeedbackApi.CmdBindTransformFeedbackBuffers(cbs.CommandBuffer, binding, 1, in buffer, in offset, in size); + } + } + + public void Swap(Auto from, Auto to) + { + if (_buffer == from) + { + _buffer.DecrementReferenceCount(); + to.IncrementReferenceCount(); + + _buffer = to; + } + } + + public readonly bool Overlaps(Auto buffer, int offset, int size) + { + return buffer == _buffer && offset < _offset + _size && offset + size > _offset; + } + + public readonly void Dispose() + { + _buffer?.DecrementReferenceCount(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/BufferUsageBitmap.cs b/src/Ryujinx.Graphics.Vulkan/BufferUsageBitmap.cs new file mode 100644 index 00000000..8cf6a7ea --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/BufferUsageBitmap.cs @@ -0,0 +1,82 @@ +namespace Ryujinx.Graphics.Vulkan +{ + internal class BufferUsageBitmap + { + private readonly BitMap _bitmap; + private readonly int _size; + private readonly int _granularity; + private readonly int _bits; + private readonly int _writeBitOffset; + + private readonly int _intsPerCb; + private readonly int _bitsPerCb; + + public BufferUsageBitmap(int size, int granularity) + { + _size = size; + _granularity = granularity; + + // There are two sets of bits - one for read tracking, and the other for write. + int bits = (size + (granularity - 1)) / granularity; + _writeBitOffset = bits; + _bits = bits << 1; + + _intsPerCb = (_bits + (BitMap.IntSize - 1)) / BitMap.IntSize; + _bitsPerCb = _intsPerCb * BitMap.IntSize; + + _bitmap = new BitMap(_bitsPerCb * CommandBufferPool.MaxCommandBuffers); + } + + public void Add(int cbIndex, int offset, int size, bool write) + { + if (size == 0) + { + return; + } + + // Some usages can be out of bounds (vertex buffer on amd), so bound if necessary. + if (offset + size > _size) + { + size = _size - offset; + } + + int cbBase = cbIndex * _bitsPerCb + (write ? _writeBitOffset : 0); + int start = cbBase + offset / _granularity; + int end = cbBase + (offset + size - 1) / _granularity; + + _bitmap.SetRange(start, end); + } + + public bool OverlapsWith(int cbIndex, int offset, int size, bool write = false) + { + if (size == 0) + { + return false; + } + + int cbBase = cbIndex * _bitsPerCb + (write ? _writeBitOffset : 0); + int start = cbBase + offset / _granularity; + int end = cbBase + (offset + size - 1) / _granularity; + + return _bitmap.IsSet(start, end); + } + + public bool OverlapsWith(int offset, int size, bool write) + { + for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++) + { + if (OverlapsWith(i, offset, size, write)) + { + return true; + } + } + + return false; + } + + public void Clear(int cbIndex) + { + _bitmap.ClearInt(cbIndex * _intsPerCb, (cbIndex + 1) * _intsPerCb - 1); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/CacheByRange.cs b/src/Ryujinx.Graphics.Vulkan/CacheByRange.cs new file mode 100644 index 00000000..16954d21 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/CacheByRange.cs @@ -0,0 +1,394 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + interface ICacheKey : IDisposable + { + bool KeyEqual(ICacheKey other); + } + + struct I8ToI16CacheKey : ICacheKey + { + // Used to notify the pipeline that bindings have invalidated on dispose. + private readonly VulkanRenderer _gd; + private Auto _buffer; + + public I8ToI16CacheKey(VulkanRenderer gd) + { + _gd = gd; + _buffer = null; + } + + public readonly bool KeyEqual(ICacheKey other) + { + return other is I8ToI16CacheKey; + } + + public void SetBuffer(Auto buffer) + { + _buffer = buffer; + } + + public readonly void Dispose() + { + _gd.PipelineInternal.DirtyIndexBuffer(_buffer); + } + } + + struct AlignedVertexBufferCacheKey : ICacheKey + { + private readonly int _stride; + private readonly int _alignment; + + // Used to notify the pipeline that bindings have invalidated on dispose. + private readonly VulkanRenderer _gd; + private Auto _buffer; + + public AlignedVertexBufferCacheKey(VulkanRenderer gd, int stride, int alignment) + { + _gd = gd; + _stride = stride; + _alignment = alignment; + _buffer = null; + } + + public readonly bool KeyEqual(ICacheKey other) + { + return other is AlignedVertexBufferCacheKey entry && + entry._stride == _stride && + entry._alignment == _alignment; + } + + public void SetBuffer(Auto buffer) + { + _buffer = buffer; + } + + public readonly void Dispose() + { + _gd.PipelineInternal.DirtyVertexBuffer(_buffer); + } + } + + struct TopologyConversionCacheKey : ICacheKey + { + private readonly IndexBufferPattern _pattern; + private readonly int _indexSize; + + // Used to notify the pipeline that bindings have invalidated on dispose. + private readonly VulkanRenderer _gd; + private Auto _buffer; + + public TopologyConversionCacheKey(VulkanRenderer gd, IndexBufferPattern pattern, int indexSize) + { + _gd = gd; + _pattern = pattern; + _indexSize = indexSize; + _buffer = null; + } + + public readonly bool KeyEqual(ICacheKey other) + { + return other is TopologyConversionCacheKey entry && + entry._pattern == _pattern && + entry._indexSize == _indexSize; + } + + public void SetBuffer(Auto buffer) + { + _buffer = buffer; + } + + public readonly void Dispose() + { + _gd.PipelineInternal.DirtyIndexBuffer(_buffer); + } + } + + readonly struct TopologyConversionIndirectCacheKey : ICacheKey + { + private readonly TopologyConversionCacheKey _baseKey; + private readonly BufferHolder _indirectDataBuffer; + private readonly int _indirectDataOffset; + private readonly int _indirectDataSize; + + public TopologyConversionIndirectCacheKey( + VulkanRenderer gd, + IndexBufferPattern pattern, + int indexSize, + BufferHolder indirectDataBuffer, + int indirectDataOffset, + int indirectDataSize) + { + _baseKey = new TopologyConversionCacheKey(gd, pattern, indexSize); + _indirectDataBuffer = indirectDataBuffer; + _indirectDataOffset = indirectDataOffset; + _indirectDataSize = indirectDataSize; + } + + public bool KeyEqual(ICacheKey other) + { + return other is TopologyConversionIndirectCacheKey entry && + entry._baseKey.KeyEqual(_baseKey) && + entry._indirectDataBuffer == _indirectDataBuffer && + entry._indirectDataOffset == _indirectDataOffset && + entry._indirectDataSize == _indirectDataSize; + } + + public void SetBuffer(Auto buffer) + { + _baseKey.SetBuffer(buffer); + } + + public void Dispose() + { + _baseKey.Dispose(); + } + } + + readonly struct IndirectDataCacheKey : ICacheKey + { + private readonly IndexBufferPattern _pattern; + + public IndirectDataCacheKey(IndexBufferPattern pattern) + { + _pattern = pattern; + } + + public bool KeyEqual(ICacheKey other) + { + return other is IndirectDataCacheKey entry && entry._pattern == _pattern; + } + + public void Dispose() + { + } + } + + struct DrawCountCacheKey : ICacheKey + { + public readonly bool KeyEqual(ICacheKey other) + { + return other is DrawCountCacheKey; + } + + public readonly void Dispose() + { + } + } + + readonly struct Dependency + { + private readonly BufferHolder _buffer; + private readonly int _offset; + private readonly int _size; + private readonly ICacheKey _key; + + public Dependency(BufferHolder buffer, int offset, int size, ICacheKey key) + { + _buffer = buffer; + _offset = offset; + _size = size; + _key = key; + } + + public void RemoveFromOwner() + { + _buffer.RemoveCachedConvertedBuffer(_offset, _size, _key); + } + } + + struct CacheByRange where T : IDisposable + { + private struct Entry + { + public ICacheKey Key; + public T Value; + public List DependencyList; + + public Entry(ICacheKey key, T value) + { + Key = key; + Value = value; + DependencyList = null; + } + + public readonly void InvalidateDependencies() + { + if (DependencyList != null) + { + foreach (Dependency dependency in DependencyList) + { + dependency.RemoveFromOwner(); + } + + DependencyList.Clear(); + } + } + } + + private Dictionary> _ranges; + + public void Add(int offset, int size, ICacheKey key, T value) + { + List entries = GetEntries(offset, size); + + entries.Add(new Entry(key, value)); + } + + public void AddDependency(int offset, int size, ICacheKey key, Dependency dependency) + { + List entries = GetEntries(offset, size); + + for (int i = 0; i < entries.Count; i++) + { + Entry entry = entries[i]; + + if (entry.Key.KeyEqual(key)) + { + if (entry.DependencyList == null) + { + entry.DependencyList = new List(); + entries[i] = entry; + } + + entry.DependencyList.Add(dependency); + + break; + } + } + } + + public void Remove(int offset, int size, ICacheKey key) + { + List entries = GetEntries(offset, size); + + for (int i = 0; i < entries.Count; i++) + { + Entry entry = entries[i]; + + if (entry.Key.KeyEqual(key)) + { + entries.RemoveAt(i--); + + DestroyEntry(entry); + } + } + + if (entries.Count == 0) + { + _ranges.Remove(PackRange(offset, size)); + } + } + + public bool TryGetValue(int offset, int size, ICacheKey key, out T value) + { + List entries = GetEntries(offset, size); + + foreach (Entry entry in entries) + { + if (entry.Key.KeyEqual(key)) + { + value = entry.Value; + + return true; + } + } + + value = default; + return false; + } + + public void Clear() + { + if (_ranges != null) + { + foreach (List entries in _ranges.Values) + { + foreach (Entry entry in entries) + { + DestroyEntry(entry); + } + } + + _ranges.Clear(); + _ranges = null; + } + } + + public readonly void ClearRange(int offset, int size) + { + if (_ranges != null && _ranges.Count > 0) + { + int end = offset + size; + + List toRemove = null; + + foreach (KeyValuePair> range in _ranges) + { + (int rOffset, int rSize) = UnpackRange(range.Key); + + int rEnd = rOffset + rSize; + + if (rEnd > offset && rOffset < end) + { + List entries = range.Value; + + foreach (Entry entry in entries) + { + DestroyEntry(entry); + } + + (toRemove ??= new List()).Add(range.Key); + } + } + + if (toRemove != null) + { + foreach (ulong range in toRemove) + { + _ranges.Remove(range); + } + } + } + } + + private List GetEntries(int offset, int size) + { + _ranges ??= new Dictionary>(); + + ulong key = PackRange(offset, size); + + if (!_ranges.TryGetValue(key, out List value)) + { + value = new List(); + _ranges.Add(key, value); + } + + return value; + } + + private static void DestroyEntry(Entry entry) + { + entry.Key.Dispose(); + entry.Value?.Dispose(); + entry.InvalidateDependencies(); + } + + private static ulong PackRange(int offset, int size) + { + return (uint)offset | ((ulong)size << 32); + } + + private static (int offset, int size) UnpackRange(ulong range) + { + return ((int)range, (int)(range >> 32)); + } + + public void Dispose() + { + Clear(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs b/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs new file mode 100644 index 00000000..e1fd3fb9 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs @@ -0,0 +1,370 @@ +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using Semaphore = Silk.NET.Vulkan.Semaphore; + +namespace Ryujinx.Graphics.Vulkan +{ + class CommandBufferPool : IDisposable + { + public const int MaxCommandBuffers = 16; + + private readonly int _totalCommandBuffers; + private readonly int _totalCommandBuffersMask; + + private readonly Vk _api; + private readonly Device _device; + private readonly Queue _queue; + private readonly object _queueLock; + private readonly bool _concurrentFenceWaitUnsupported; + private readonly CommandPool _pool; + private readonly Thread _owner; + + public bool OwnedByCurrentThread => _owner == Thread.CurrentThread; + + private struct ReservedCommandBuffer + { + public bool InUse; + public bool InConsumption; + public int SubmissionCount; + public CommandBuffer CommandBuffer; + public FenceHolder Fence; + + public List Dependants; + public List Waitables; + + public void Initialize(Vk api, Device device, CommandPool pool) + { + var allocateInfo = new CommandBufferAllocateInfo + { + SType = StructureType.CommandBufferAllocateInfo, + CommandBufferCount = 1, + CommandPool = pool, + Level = CommandBufferLevel.Primary, + }; + + api.AllocateCommandBuffers(device, in allocateInfo, out CommandBuffer); + + Dependants = new List(); + Waitables = new List(); + } + } + + private readonly ReservedCommandBuffer[] _commandBuffers; + + private readonly int[] _queuedIndexes; + private int _queuedIndexesPtr; + private int _queuedCount; + private int _inUseCount; + + public unsafe CommandBufferPool( + Vk api, + Device device, + Queue queue, + object queueLock, + uint queueFamilyIndex, + bool concurrentFenceWaitUnsupported, + bool isLight = false) + { + _api = api; + _device = device; + _queue = queue; + _queueLock = queueLock; + _concurrentFenceWaitUnsupported = concurrentFenceWaitUnsupported; + _owner = Thread.CurrentThread; + + var commandPoolCreateInfo = new CommandPoolCreateInfo + { + SType = StructureType.CommandPoolCreateInfo, + QueueFamilyIndex = queueFamilyIndex, + Flags = CommandPoolCreateFlags.TransientBit | + CommandPoolCreateFlags.ResetCommandBufferBit, + }; + + api.CreateCommandPool(device, in commandPoolCreateInfo, null, out _pool).ThrowOnError(); + + // We need at least 2 command buffers to get texture data in some cases. + _totalCommandBuffers = isLight ? 2 : MaxCommandBuffers; + _totalCommandBuffersMask = _totalCommandBuffers - 1; + + _commandBuffers = new ReservedCommandBuffer[_totalCommandBuffers]; + + _queuedIndexes = new int[_totalCommandBuffers]; + _queuedIndexesPtr = 0; + _queuedCount = 0; + + for (int i = 0; i < _totalCommandBuffers; i++) + { + _commandBuffers[i].Initialize(api, device, _pool); + WaitAndDecrementRef(i); + } + } + + public void AddDependant(int cbIndex, IAuto dependant) + { + dependant.IncrementReferenceCount(); + _commandBuffers[cbIndex].Dependants.Add(dependant); + } + + public void AddWaitable(MultiFenceHolder waitable) + { + lock (_commandBuffers) + { + for (int i = 0; i < _totalCommandBuffers; i++) + { + ref var entry = ref _commandBuffers[i]; + + if (entry.InConsumption) + { + AddWaitable(i, waitable); + } + } + } + } + + public void AddInUseWaitable(MultiFenceHolder waitable) + { + lock (_commandBuffers) + { + for (int i = 0; i < _totalCommandBuffers; i++) + { + ref var entry = ref _commandBuffers[i]; + + if (entry.InUse) + { + AddWaitable(i, waitable); + } + } + } + } + + public void AddWaitable(int cbIndex, MultiFenceHolder waitable) + { + ref var entry = ref _commandBuffers[cbIndex]; + if (waitable.AddFence(cbIndex, entry.Fence)) + { + entry.Waitables.Add(waitable); + } + } + + public bool HasWaitableOnRentedCommandBuffer(MultiFenceHolder waitable, int offset, int size) + { + lock (_commandBuffers) + { + for (int i = 0; i < _totalCommandBuffers; i++) + { + ref var entry = ref _commandBuffers[i]; + + if (entry.InUse && + waitable.HasFence(i) && + waitable.IsBufferRangeInUse(i, offset, size)) + { + return true; + } + } + } + + return false; + } + + public bool IsFenceOnRentedCommandBuffer(FenceHolder fence) + { + lock (_commandBuffers) + { + for (int i = 0; i < _totalCommandBuffers; i++) + { + ref var entry = ref _commandBuffers[i]; + + if (entry.InUse && entry.Fence == fence) + { + return true; + } + } + } + + return false; + } + + public FenceHolder GetFence(int cbIndex) + { + return _commandBuffers[cbIndex].Fence; + } + + public int GetSubmissionCount(int cbIndex) + { + return _commandBuffers[cbIndex].SubmissionCount; + } + + private int FreeConsumed(bool wait) + { + int freeEntry = 0; + + while (_queuedCount > 0) + { + int index = _queuedIndexes[_queuedIndexesPtr]; + + ref var entry = ref _commandBuffers[index]; + + if (wait || !entry.InConsumption || entry.Fence.IsSignaled()) + { + WaitAndDecrementRef(index); + + wait = false; + freeEntry = index; + + _queuedCount--; + _queuedIndexesPtr = (_queuedIndexesPtr + 1) % _totalCommandBuffers; + } + else + { + break; + } + } + + return freeEntry; + } + + public CommandBufferScoped ReturnAndRent(CommandBufferScoped cbs) + { + Return(cbs); + return Rent(); + } + + public CommandBufferScoped Rent() + { + lock (_commandBuffers) + { + int cursor = FreeConsumed(_inUseCount + _queuedCount == _totalCommandBuffers); + + for (int i = 0; i < _totalCommandBuffers; i++) + { + ref var entry = ref _commandBuffers[cursor]; + + if (!entry.InUse && !entry.InConsumption) + { + entry.InUse = true; + + _inUseCount++; + + var commandBufferBeginInfo = new CommandBufferBeginInfo + { + SType = StructureType.CommandBufferBeginInfo, + }; + + _api.BeginCommandBuffer(entry.CommandBuffer, in commandBufferBeginInfo).ThrowOnError(); + + return new CommandBufferScoped(this, entry.CommandBuffer, cursor); + } + + cursor = (cursor + 1) & _totalCommandBuffersMask; + } + } + + throw new InvalidOperationException($"Out of command buffers (In use: {_inUseCount}, queued: {_queuedCount}, total: {_totalCommandBuffers})"); + } + + public void Return(CommandBufferScoped cbs) + { + Return(cbs, null, null, null); + } + + public unsafe void Return( + CommandBufferScoped cbs, + ReadOnlySpan waitSemaphores, + ReadOnlySpan waitDstStageMask, + ReadOnlySpan signalSemaphores) + { + lock (_commandBuffers) + { + int cbIndex = cbs.CommandBufferIndex; + + ref var entry = ref _commandBuffers[cbIndex]; + + Debug.Assert(entry.InUse); + Debug.Assert(entry.CommandBuffer.Handle == cbs.CommandBuffer.Handle); + entry.InUse = false; + entry.InConsumption = true; + entry.SubmissionCount++; + _inUseCount--; + + var commandBuffer = entry.CommandBuffer; + + _api.EndCommandBuffer(commandBuffer).ThrowOnError(); + + fixed (Semaphore* pWaitSemaphores = waitSemaphores, pSignalSemaphores = signalSemaphores) + { + fixed (PipelineStageFlags* pWaitDstStageMask = waitDstStageMask) + { + SubmitInfo sInfo = new() + { + SType = StructureType.SubmitInfo, + WaitSemaphoreCount = !waitSemaphores.IsEmpty ? (uint)waitSemaphores.Length : 0, + PWaitSemaphores = pWaitSemaphores, + PWaitDstStageMask = pWaitDstStageMask, + CommandBufferCount = 1, + PCommandBuffers = &commandBuffer, + SignalSemaphoreCount = !signalSemaphores.IsEmpty ? (uint)signalSemaphores.Length : 0, + PSignalSemaphores = pSignalSemaphores, + }; + + lock (_queueLock) + { + _api.QueueSubmit(_queue, 1, in sInfo, entry.Fence.GetUnsafe()).ThrowOnError(); + } + } + } + + int ptr = (_queuedIndexesPtr + _queuedCount) % _totalCommandBuffers; + _queuedIndexes[ptr] = cbIndex; + _queuedCount++; + } + } + + private void WaitAndDecrementRef(int cbIndex, bool refreshFence = true) + { + ref var entry = ref _commandBuffers[cbIndex]; + + if (entry.InConsumption) + { + entry.Fence.Wait(); + entry.InConsumption = false; + } + + foreach (var dependant in entry.Dependants) + { + dependant.DecrementReferenceCount(cbIndex); + } + + foreach (var waitable in entry.Waitables) + { + waitable.RemoveFence(cbIndex); + waitable.RemoveBufferUses(cbIndex); + } + + entry.Dependants.Clear(); + entry.Waitables.Clear(); + entry.Fence?.Dispose(); + + if (refreshFence) + { + entry.Fence = new FenceHolder(_api, _device, _concurrentFenceWaitUnsupported); + } + else + { + entry.Fence = null; + } + } + + public unsafe void Dispose() + { + for (int i = 0; i < _totalCommandBuffers; i++) + { + WaitAndDecrementRef(i, refreshFence: false); + } + + _api.DestroyCommandPool(_device, _pool, null); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs b/src/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs new file mode 100644 index 00000000..2accd69b --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs @@ -0,0 +1,39 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct CommandBufferScoped : IDisposable + { + private readonly CommandBufferPool _pool; + public CommandBuffer CommandBuffer { get; } + public int CommandBufferIndex { get; } + + public CommandBufferScoped(CommandBufferPool pool, CommandBuffer commandBuffer, int commandBufferIndex) + { + _pool = pool; + CommandBuffer = commandBuffer; + CommandBufferIndex = commandBufferIndex; + } + + public void AddDependant(IAuto dependant) + { + _pool.AddDependant(CommandBufferIndex, dependant); + } + + public void AddWaitable(MultiFenceHolder waitable) + { + _pool.AddWaitable(CommandBufferIndex, waitable); + } + + public FenceHolder GetFence() + { + return _pool.GetFence(CommandBufferIndex); + } + + public void Dispose() + { + _pool?.Return(this); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Constants.cs b/src/Ryujinx.Graphics.Vulkan/Constants.cs new file mode 100644 index 00000000..20ce6581 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Constants.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Graphics.Vulkan +{ + static class Constants + { + public const int MaxVertexAttributes = 32; + public const int MaxVertexBuffers = 32; + public const int MaxTransformFeedbackBuffers = 4; + public const int MaxRenderTargets = 8; + public const int MaxViewports = 16; + public const int MaxShaderStages = 5; + public const int MaxUniformBuffersPerStage = 18; + public const int MaxStorageBuffersPerStage = 16; + public const int MaxTexturesPerStage = 64; + public const int MaxImagesPerStage = 16; + public const int MaxUniformBufferBindings = MaxUniformBuffersPerStage * MaxShaderStages; + public const int MaxStorageBufferBindings = MaxStorageBuffersPerStage * MaxShaderStages; + public const int MaxTextureBindings = MaxTexturesPerStage * MaxShaderStages; + public const int MaxImageBindings = MaxImagesPerStage * MaxShaderStages; + public const int MaxPushDescriptorBinding = 64; + + public const ulong SparseBufferAlignment = 0x10000; + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs new file mode 100644 index 00000000..40fc01b2 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs @@ -0,0 +1,222 @@ +using Silk.NET.Vulkan; +using System; +using VkBuffer = Silk.NET.Vulkan.Buffer; + +namespace Ryujinx.Graphics.Vulkan +{ + struct DescriptorSetCollection : IDisposable + { + private DescriptorSetManager.DescriptorPoolHolder _holder; + private readonly DescriptorSet[] _descriptorSets; + public readonly int SetsCount => _descriptorSets.Length; + + public DescriptorSetCollection(DescriptorSetManager.DescriptorPoolHolder holder, DescriptorSet[] descriptorSets) + { + _holder = holder; + _descriptorSets = descriptorSets; + } + + public void InitializeBuffers(int setIndex, int baseBinding, int count, DescriptorType type, VkBuffer dummyBuffer) + { + Span infos = stackalloc DescriptorBufferInfo[count]; + + infos.Fill(new DescriptorBufferInfo + { + Buffer = dummyBuffer, + Range = Vk.WholeSize, + }); + + UpdateBuffers(setIndex, baseBinding, infos, type); + } + + public unsafe void UpdateBuffer(int setIndex, int bindingIndex, DescriptorBufferInfo bufferInfo, DescriptorType type) + { + if (bufferInfo.Buffer.Handle != 0UL) + { + var writeDescriptorSet = new WriteDescriptorSet + { + SType = StructureType.WriteDescriptorSet, + DstSet = _descriptorSets[setIndex], + DstBinding = (uint)bindingIndex, + DescriptorType = type, + DescriptorCount = 1, + PBufferInfo = &bufferInfo, + }; + + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null); + } + } + + public unsafe void UpdateBuffers(int setIndex, int baseBinding, ReadOnlySpan bufferInfo, DescriptorType type) + { + if (bufferInfo.Length == 0) + { + return; + } + + fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo) + { + var writeDescriptorSet = new WriteDescriptorSet + { + SType = StructureType.WriteDescriptorSet, + DstSet = _descriptorSets[setIndex], + DstBinding = (uint)baseBinding, + DescriptorType = type, + DescriptorCount = (uint)bufferInfo.Length, + PBufferInfo = pBufferInfo, + }; + + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null); + } + } + + public unsafe void UpdateImage(int setIndex, int bindingIndex, DescriptorImageInfo imageInfo, DescriptorType type) + { + if (imageInfo.ImageView.Handle != 0UL) + { + var writeDescriptorSet = new WriteDescriptorSet + { + SType = StructureType.WriteDescriptorSet, + DstSet = _descriptorSets[setIndex], + DstBinding = (uint)bindingIndex, + DescriptorType = type, + DescriptorCount = 1, + PImageInfo = &imageInfo, + }; + + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null); + } + } + + public unsafe void UpdateImages(int setIndex, int baseBinding, ReadOnlySpan imageInfo, DescriptorType type) + { + if (imageInfo.Length == 0) + { + return; + } + + fixed (DescriptorImageInfo* pImageInfo = imageInfo) + { + var writeDescriptorSet = new WriteDescriptorSet + { + SType = StructureType.WriteDescriptorSet, + DstSet = _descriptorSets[setIndex], + DstBinding = (uint)baseBinding, + DescriptorType = type, + DescriptorCount = (uint)imageInfo.Length, + PImageInfo = pImageInfo, + }; + + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null); + } + } + + public unsafe void UpdateImagesCombined(int setIndex, int baseBinding, ReadOnlySpan imageInfo, DescriptorType type) + { + if (imageInfo.Length == 0) + { + return; + } + + fixed (DescriptorImageInfo* pImageInfo = imageInfo) + { + for (int i = 0; i < imageInfo.Length; i++) + { + bool nonNull = imageInfo[i].ImageView.Handle != 0 && imageInfo[i].Sampler.Handle != 0; + if (nonNull) + { + int count = 1; + + while (i + count < imageInfo.Length && + imageInfo[i + count].ImageView.Handle != 0 && + imageInfo[i + count].Sampler.Handle != 0) + { + count++; + } + + var writeDescriptorSet = new WriteDescriptorSet + { + SType = StructureType.WriteDescriptorSet, + DstSet = _descriptorSets[setIndex], + DstBinding = (uint)(baseBinding + i), + DescriptorType = DescriptorType.CombinedImageSampler, + DescriptorCount = (uint)count, + PImageInfo = pImageInfo, + }; + + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null); + + i += count - 1; + } + } + } + } + + public unsafe void UpdateBufferImage(int setIndex, int bindingIndex, BufferView texelBufferView, DescriptorType type) + { + if (texelBufferView.Handle != 0UL) + { + var writeDescriptorSet = new WriteDescriptorSet + { + SType = StructureType.WriteDescriptorSet, + DstSet = _descriptorSets[setIndex], + DstBinding = (uint)bindingIndex, + DescriptorType = type, + DescriptorCount = 1, + PTexelBufferView = &texelBufferView, + }; + + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null); + } + } + + public unsafe void UpdateBufferImages(int setIndex, int baseBinding, ReadOnlySpan texelBufferView, DescriptorType type) + { + if (texelBufferView.Length == 0) + { + return; + } + + fixed (BufferView* pTexelBufferView = texelBufferView) + { + for (uint i = 0; i < texelBufferView.Length;) + { + uint count = 1; + + if (texelBufferView[(int)i].Handle != 0UL) + { + while (i + count < texelBufferView.Length && texelBufferView[(int)(i + count)].Handle != 0UL) + { + count++; + } + + var writeDescriptorSet = new WriteDescriptorSet + { + SType = StructureType.WriteDescriptorSet, + DstSet = _descriptorSets[setIndex], + DstBinding = (uint)baseBinding + i, + DescriptorType = type, + DescriptorCount = count, + PTexelBufferView = pTexelBufferView + i, + }; + + _holder.Api.UpdateDescriptorSets(_holder.Device, 1, in writeDescriptorSet, 0, null); + } + + i += count; + } + } + } + + public readonly DescriptorSet[] GetSets() + { + return _descriptorSets; + } + + public void Dispose() + { + _holder?.FreeDescriptorSets(this); + _holder = null; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs new file mode 100644 index 00000000..97669942 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs @@ -0,0 +1,231 @@ +using Silk.NET.Vulkan; +using System; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Vulkan +{ + class DescriptorSetManager : IDisposable + { + public const uint MaxSets = 8; + + public class DescriptorPoolHolder : IDisposable + { + public Vk Api { get; } + public Device Device { get; } + + private readonly DescriptorPool _pool; + private int _freeDescriptors; + private int _totalSets; + private int _setsInUse; + private bool _done; + + public unsafe DescriptorPoolHolder(Vk api, Device device, ReadOnlySpan poolSizes, bool updateAfterBind) + { + Api = api; + Device = device; + + foreach (var poolSize in poolSizes) + { + _freeDescriptors += (int)poolSize.DescriptorCount; + } + + fixed (DescriptorPoolSize* pPoolsSize = poolSizes) + { + var descriptorPoolCreateInfo = new DescriptorPoolCreateInfo + { + SType = StructureType.DescriptorPoolCreateInfo, + Flags = updateAfterBind ? DescriptorPoolCreateFlags.UpdateAfterBindBit : DescriptorPoolCreateFlags.None, + MaxSets = MaxSets, + PoolSizeCount = (uint)poolSizes.Length, + PPoolSizes = pPoolsSize, + }; + + Api.CreateDescriptorPool(device, in descriptorPoolCreateInfo, null, out _pool).ThrowOnError(); + } + } + + public unsafe DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan layouts, int consumedDescriptors) + { + TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: false, out var dsc); + return dsc; + } + + public bool TryAllocateDescriptorSets(ReadOnlySpan layouts, int consumedDescriptors, out DescriptorSetCollection dsc) + { + return TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: true, out dsc); + } + + private unsafe bool TryAllocateDescriptorSets( + ReadOnlySpan layouts, + int consumedDescriptors, + bool isTry, + out DescriptorSetCollection dsc) + { + Debug.Assert(!_done); + + DescriptorSet[] descriptorSets = new DescriptorSet[layouts.Length]; + + fixed (DescriptorSet* pDescriptorSets = descriptorSets) + { + fixed (DescriptorSetLayout* pLayouts = layouts) + { + var descriptorSetAllocateInfo = new DescriptorSetAllocateInfo + { + SType = StructureType.DescriptorSetAllocateInfo, + DescriptorPool = _pool, + DescriptorSetCount = (uint)layouts.Length, + PSetLayouts = pLayouts, + }; + + var result = Api.AllocateDescriptorSets(Device, &descriptorSetAllocateInfo, pDescriptorSets); + if (isTry && result == Result.ErrorOutOfPoolMemory) + { + _totalSets = (int)MaxSets; + _done = true; + DestroyIfDone(); + dsc = default; + return false; + } + + result.ThrowOnError(); + } + } + + _freeDescriptors -= consumedDescriptors; + _totalSets += layouts.Length; + _setsInUse += layouts.Length; + + dsc = new DescriptorSetCollection(this, descriptorSets); + return true; + } + + public void FreeDescriptorSets(DescriptorSetCollection dsc) + { + _setsInUse -= dsc.SetsCount; + Debug.Assert(_setsInUse >= 0); + DestroyIfDone(); + } + + public bool CanFit(int setsCount, int descriptorsCount) + { + // Try to determine if an allocation with the given parameters will succeed. + // An allocation may fail if the sets count or descriptors count exceeds the available counts + // of the pool. + // Not getting that right is not fatal, it will just create a new pool and try again, + // but it is less efficient. + + if (_totalSets + setsCount <= MaxSets && _freeDescriptors >= descriptorsCount) + { + return true; + } + + _done = true; + DestroyIfDone(); + return false; + } + + private unsafe void DestroyIfDone() + { + if (_done && _setsInUse == 0) + { + Api.DestroyDescriptorPool(Device, _pool, null); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + unsafe + { + Api.DestroyDescriptorPool(Device, _pool, null); + } + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + } + + private readonly Device _device; + private readonly DescriptorPoolHolder[] _currentPools; + + public DescriptorSetManager(Device device, int poolCount) + { + _device = device; + _currentPools = new DescriptorPoolHolder[poolCount]; + } + + public Auto AllocateDescriptorSet( + Vk api, + DescriptorSetLayout layout, + ReadOnlySpan poolSizes, + int poolIndex, + int consumedDescriptors, + bool updateAfterBind) + { + Span layouts = stackalloc DescriptorSetLayout[1]; + layouts[0] = layout; + return AllocateDescriptorSets(api, layouts, poolSizes, poolIndex, consumedDescriptors, updateAfterBind); + } + + public Auto AllocateDescriptorSets( + Vk api, + ReadOnlySpan layouts, + ReadOnlySpan poolSizes, + int poolIndex, + int consumedDescriptors, + bool updateAfterBind) + { + // If we fail the first time, just create a new pool and try again. + + var pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind); + if (!pool.TryAllocateDescriptorSets(layouts, consumedDescriptors, out var dsc)) + { + pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind); + dsc = pool.AllocateDescriptorSets(layouts, consumedDescriptors); + } + + return new Auto(dsc); + } + + private DescriptorPoolHolder GetPool( + Vk api, + ReadOnlySpan poolSizes, + int poolIndex, + int setsCount, + int descriptorsCount, + bool updateAfterBind) + { + ref DescriptorPoolHolder currentPool = ref _currentPools[poolIndex]; + + if (currentPool == null || !currentPool.CanFit(setsCount, descriptorsCount)) + { + currentPool = new DescriptorPoolHolder(api, _device, poolSizes, updateAfterBind); + } + + return currentPool; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + for (int index = 0; index < _currentPools.Length; index++) + { + _currentPools[index]?.Dispose(); + _currentPools[index] = null; + } + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs new file mode 100644 index 00000000..117f79bb --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplate.cs @@ -0,0 +1,210 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Vulkan +{ + class DescriptorSetTemplate : IDisposable + { + /// + /// Renderdoc seems to crash when doing a templated uniform update with count > 1 on a push descriptor. + /// When this is true, consecutive buffers are always updated individually. + /// + private const bool RenderdocPushCountBug = true; + + private readonly VulkanRenderer _gd; + private readonly Device _device; + + public readonly DescriptorUpdateTemplate Template; + public readonly int Size; + + public unsafe DescriptorSetTemplate( + VulkanRenderer gd, + Device device, + ResourceBindingSegment[] segments, + PipelineLayoutCacheEntry plce, + PipelineBindPoint pbp, + int setIndex) + { + _gd = gd; + _device = device; + + // Create a template from the set usages. Assumes the descriptor set is updated in segment order then binding order. + + DescriptorUpdateTemplateEntry* entries = stackalloc DescriptorUpdateTemplateEntry[segments.Length]; + nuint structureOffset = 0; + + for (int seg = 0; seg < segments.Length; seg++) + { + ResourceBindingSegment segment = segments[seg]; + + int binding = segment.Binding; + int count = segment.Count; + + if (IsBufferType(segment.Type)) + { + entries[seg] = new DescriptorUpdateTemplateEntry() + { + DescriptorType = segment.Type.Convert(), + DstBinding = (uint)binding, + DescriptorCount = (uint)count, + Offset = structureOffset, + Stride = (nuint)Unsafe.SizeOf() + }; + + structureOffset += (nuint)(Unsafe.SizeOf() * count); + } + else if (IsBufferTextureType(segment.Type)) + { + entries[seg] = new DescriptorUpdateTemplateEntry() + { + DescriptorType = segment.Type.Convert(), + DstBinding = (uint)binding, + DescriptorCount = (uint)count, + Offset = structureOffset, + Stride = (nuint)Unsafe.SizeOf() + }; + + structureOffset += (nuint)(Unsafe.SizeOf() * count); + } + else + { + entries[seg] = new DescriptorUpdateTemplateEntry() + { + DescriptorType = segment.Type.Convert(), + DstBinding = (uint)binding, + DescriptorCount = (uint)count, + Offset = structureOffset, + Stride = (nuint)Unsafe.SizeOf() + }; + + structureOffset += (nuint)(Unsafe.SizeOf() * count); + } + } + + Size = (int)structureOffset; + + var info = new DescriptorUpdateTemplateCreateInfo() + { + SType = StructureType.DescriptorUpdateTemplateCreateInfo, + DescriptorUpdateEntryCount = (uint)segments.Length, + PDescriptorUpdateEntries = entries, + + TemplateType = DescriptorUpdateTemplateType.DescriptorSet, + DescriptorSetLayout = plce.DescriptorSetLayouts[setIndex], + PipelineBindPoint = pbp, + PipelineLayout = plce.PipelineLayout, + Set = (uint)setIndex, + }; + + DescriptorUpdateTemplate result; + gd.Api.CreateDescriptorUpdateTemplate(device, &info, null, &result).ThrowOnError(); + + Template = result; + } + + public unsafe DescriptorSetTemplate( + VulkanRenderer gd, + Device device, + ResourceDescriptorCollection descriptors, + long updateMask, + PipelineLayoutCacheEntry plce, + PipelineBindPoint pbp, + int setIndex) + { + _gd = gd; + _device = device; + + // Create a template from the set usages. Assumes the descriptor set is updated in segment order then binding order. + int segmentCount = BitOperations.PopCount((ulong)updateMask); + + DescriptorUpdateTemplateEntry* entries = stackalloc DescriptorUpdateTemplateEntry[segmentCount]; + int entry = 0; + nuint structureOffset = 0; + + void AddBinding(int binding, int count) + { + entries[entry++] = new DescriptorUpdateTemplateEntry() + { + DescriptorType = DescriptorType.UniformBuffer, + DstBinding = (uint)binding, + DescriptorCount = (uint)count, + Offset = structureOffset, + Stride = (nuint)Unsafe.SizeOf() + }; + + structureOffset += (nuint)(Unsafe.SizeOf() * count); + } + + int startBinding = 0; + int bindingCount = 0; + + foreach (ResourceDescriptor descriptor in descriptors.Descriptors) + { + for (int i = 0; i < descriptor.Count; i++) + { + int binding = descriptor.Binding + i; + + if ((updateMask & (1L << binding)) != 0) + { + if (bindingCount > 0 && (RenderdocPushCountBug || startBinding + bindingCount != binding)) + { + AddBinding(startBinding, bindingCount); + + bindingCount = 0; + } + + if (bindingCount == 0) + { + startBinding = binding; + } + + bindingCount++; + } + } + } + + if (bindingCount > 0) + { + AddBinding(startBinding, bindingCount); + } + + Size = (int)structureOffset; + + var info = new DescriptorUpdateTemplateCreateInfo() + { + SType = StructureType.DescriptorUpdateTemplateCreateInfo, + DescriptorUpdateEntryCount = (uint)entry, + PDescriptorUpdateEntries = entries, + + TemplateType = DescriptorUpdateTemplateType.PushDescriptorsKhr, + DescriptorSetLayout = plce.DescriptorSetLayouts[setIndex], + PipelineBindPoint = pbp, + PipelineLayout = plce.PipelineLayout, + Set = (uint)setIndex, + }; + + DescriptorUpdateTemplate result; + gd.Api.CreateDescriptorUpdateTemplate(device, &info, null, &result).ThrowOnError(); + + Template = result; + } + + private static bool IsBufferType(ResourceType type) + { + return type == ResourceType.UniformBuffer || type == ResourceType.StorageBuffer; + } + + private static bool IsBufferTextureType(ResourceType type) + { + return type == ResourceType.BufferTexture || type == ResourceType.BufferImage; + } + + public unsafe void Dispose() + { + _gd.Api.DestroyDescriptorUpdateTemplate(_device, Template, null); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplateUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplateUpdater.cs new file mode 100644 index 00000000..88db7e76 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetTemplateUpdater.cs @@ -0,0 +1,77 @@ +using Ryujinx.Common; +using Silk.NET.Vulkan; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + ref struct DescriptorSetTemplateWriter + { + private Span _data; + + public DescriptorSetTemplateWriter(Span data) + { + _data = data; + } + + public void Push(ReadOnlySpan values) where T : unmanaged + { + Span target = MemoryMarshal.Cast(_data); + + values.CopyTo(target); + + _data = _data[(Unsafe.SizeOf() * values.Length)..]; + } + } + + unsafe class DescriptorSetTemplateUpdater : IDisposable + { + private const int SizeGranularity = 512; + + private DescriptorSetTemplate _activeTemplate; + private NativeArray _data; + + private void EnsureSize(int size) + { + if (_data == null || _data.Length < size) + { + _data?.Dispose(); + + int dataSize = BitUtils.AlignUp(size, SizeGranularity); + _data = new NativeArray(dataSize); + } + } + + public DescriptorSetTemplateWriter Begin(DescriptorSetTemplate template) + { + _activeTemplate = template; + + EnsureSize(template.Size); + + return new DescriptorSetTemplateWriter(new Span(_data.Pointer, template.Size)); + } + + public DescriptorSetTemplateWriter Begin(int maxSize) + { + EnsureSize(maxSize); + + return new DescriptorSetTemplateWriter(new Span(_data.Pointer, maxSize)); + } + + public void Commit(VulkanRenderer gd, Device device, DescriptorSet set) + { + gd.Api.UpdateDescriptorSetWithTemplate(device, set, _activeTemplate.Template, _data.Pointer); + } + + public void CommitPushDescriptor(VulkanRenderer gd, CommandBufferScoped cbs, DescriptorSetTemplate template, PipelineLayout layout) + { + gd.PushDescriptorApi.CmdPushDescriptorSetWithTemplate(cbs.CommandBuffer, template.Template, layout, 0, _data.Pointer); + } + + public void Dispose() + { + _data?.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs new file mode 100644 index 00000000..298526d5 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs @@ -0,0 +1,1199 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Silk.NET.Vulkan; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using CompareOp = Ryujinx.Graphics.GAL.CompareOp; +using Format = Ryujinx.Graphics.GAL.Format; +using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo; + +namespace Ryujinx.Graphics.Vulkan +{ + class DescriptorSetUpdater + { + private const ulong StorageBufferMaxMirrorable = 0x2000; + + private const int ArrayGrowthSize = 16; + + private record struct BufferRef + { + public Auto Buffer; + public int Offset; + public bool Write; + + public BufferRef(Auto buffer) + { + Buffer = buffer; + Offset = 0; + Write = true; + } + + public BufferRef(Auto buffer, ref BufferRange range) + { + Buffer = buffer; + Offset = range.Offset; + Write = range.Write; + } + } + + private record struct TextureRef + { + public ShaderStage Stage; + public TextureView View; + public Auto ImageView; + public Auto Sampler; + + public TextureRef(ShaderStage stage, TextureView view, Auto imageView, Auto sampler) + { + Stage = stage; + View = view; + ImageView = imageView; + Sampler = sampler; + } + } + + private record struct ImageRef + { + public ShaderStage Stage; + public TextureView View; + public Auto ImageView; + + public ImageRef(ShaderStage stage, TextureView view, Auto imageView) + { + Stage = stage; + View = view; + ImageView = imageView; + } + } + + private readonly record struct ArrayRef(ShaderStage Stage, T Array); + + private readonly VulkanRenderer _gd; + private readonly Device _device; + private ShaderCollection _program; + + private readonly BufferRef[] _uniformBufferRefs; + private readonly BufferRef[] _storageBufferRefs; + private readonly TextureRef[] _textureRefs; + private readonly ImageRef[] _imageRefs; + private readonly TextureBuffer[] _bufferTextureRefs; + private readonly TextureBuffer[] _bufferImageRefs; + private readonly Format[] _bufferImageFormats; + + private ArrayRef[] _textureArrayRefs; + private ArrayRef[] _imageArrayRefs; + + private ArrayRef[] _textureArrayExtraRefs; + private ArrayRef[] _imageArrayExtraRefs; + + private readonly DescriptorBufferInfo[] _uniformBuffers; + private readonly DescriptorBufferInfo[] _storageBuffers; + private readonly DescriptorImageInfo[] _textures; + private readonly DescriptorImageInfo[] _images; + private readonly BufferView[] _bufferTextures; + private readonly BufferView[] _bufferImages; + + private readonly DescriptorSetTemplateUpdater _templateUpdater; + + private BitMapStruct> _uniformSet; + private BitMapStruct> _storageSet; + private BitMapStruct> _uniformMirrored; + private BitMapStruct> _storageMirrored; + private readonly int[] _uniformSetPd; + private int _pdSequence = 1; + + private bool _updateDescriptorCacheCbIndex; + + [Flags] + private enum DirtyFlags + { + None = 0, + Uniform = 1 << 0, + Storage = 1 << 1, + Texture = 1 << 2, + Image = 1 << 3, + All = Uniform | Storage | Texture | Image, + } + + private DirtyFlags _dirty; + + private readonly BufferHolder _dummyBuffer; + private readonly TextureView _dummyTexture; + private readonly SamplerHolder _dummySampler; + + public List FeedbackLoopHazards { get; private set; } + + public DescriptorSetUpdater(VulkanRenderer gd, Device device) + { + _gd = gd; + _device = device; + + // Some of the bindings counts needs to be multiplied by 2 because we have buffer and + // regular textures/images interleaved on the same descriptor set. + + _uniformBufferRefs = new BufferRef[Constants.MaxUniformBufferBindings]; + _storageBufferRefs = new BufferRef[Constants.MaxStorageBufferBindings]; + _textureRefs = new TextureRef[Constants.MaxTextureBindings * 2]; + _imageRefs = new ImageRef[Constants.MaxImageBindings * 2]; + _bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2]; + _bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2]; + _bufferImageFormats = new Format[Constants.MaxImageBindings * 2]; + + _textureArrayRefs = Array.Empty>(); + _imageArrayRefs = Array.Empty>(); + + _textureArrayExtraRefs = Array.Empty>(); + _imageArrayExtraRefs = Array.Empty>(); + + _uniformBuffers = new DescriptorBufferInfo[Constants.MaxUniformBufferBindings]; + _storageBuffers = new DescriptorBufferInfo[Constants.MaxStorageBufferBindings]; + _textures = new DescriptorImageInfo[Constants.MaxTexturesPerStage]; + _images = new DescriptorImageInfo[Constants.MaxImagesPerStage]; + _bufferTextures = new BufferView[Constants.MaxTexturesPerStage]; + _bufferImages = new BufferView[Constants.MaxImagesPerStage]; + + _uniformSetPd = new int[Constants.MaxUniformBufferBindings]; + + var initialImageInfo = new DescriptorImageInfo + { + ImageLayout = ImageLayout.General, + }; + + _textures.AsSpan().Fill(initialImageInfo); + _images.AsSpan().Fill(initialImageInfo); + + if (gd.Capabilities.SupportsNullDescriptors) + { + // If null descriptors are supported, we can pass null as the handle. + _dummyBuffer = null; + } + else + { + // If null descriptors are not supported, we need to pass the handle of a dummy buffer on unused bindings. + _dummyBuffer = gd.BufferManager.Create(gd, 0x10000, forConditionalRendering: false, baseType: BufferAllocationType.DeviceLocal); + } + + _dummyTexture = gd.CreateTextureView(new TextureCreateInfo( + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + Format.R8G8B8A8Unorm, + DepthStencilMode.Depth, + Target.Texture2D, + SwizzleComponent.Red, + SwizzleComponent.Green, + SwizzleComponent.Blue, + SwizzleComponent.Alpha)); + + _dummySampler = (SamplerHolder)gd.CreateSampler(new SamplerCreateInfo( + MinFilter.Nearest, + MagFilter.Nearest, + false, + AddressMode.Repeat, + AddressMode.Repeat, + AddressMode.Repeat, + CompareMode.None, + CompareOp.Always, + new ColorF(0, 0, 0, 0), + 0, + 0, + 0, + 1f)); + + _templateUpdater = new(); + } + + public void Initialize(bool isMainPipeline) + { + MemoryOwner dummyTextureData = MemoryOwner.RentCleared(4); + _dummyTexture.SetData(dummyTextureData); + + if (isMainPipeline) + { + FeedbackLoopHazards = new(); + } + } + + private static bool BindingOverlaps(ref DescriptorBufferInfo info, int bindingOffset, int offset, int size) + { + return offset < bindingOffset + (int)info.Range && (offset + size) > bindingOffset; + } + + internal void Rebind(Auto buffer, int offset, int size) + { + if (_program == null) + { + return; + } + + // Check stage bindings + + _uniformMirrored.Union(_uniformSet).SignalSet((int binding, int count) => + { + for (int i = 0; i < count; i++) + { + ref BufferRef bufferRef = ref _uniformBufferRefs[binding]; + if (bufferRef.Buffer == buffer) + { + ref DescriptorBufferInfo info = ref _uniformBuffers[binding]; + int bindingOffset = bufferRef.Offset; + + if (BindingOverlaps(ref info, bindingOffset, offset, size)) + { + _uniformSet.Clear(binding); + _uniformSetPd[binding] = 0; + SignalDirty(DirtyFlags.Uniform); + } + } + + binding++; + } + }); + + _storageMirrored.Union(_storageSet).SignalSet((int binding, int count) => + { + for (int i = 0; i < count; i++) + { + ref BufferRef bufferRef = ref _storageBufferRefs[binding]; + if (bufferRef.Buffer == buffer) + { + ref DescriptorBufferInfo info = ref _storageBuffers[binding]; + int bindingOffset = bufferRef.Offset; + + if (BindingOverlaps(ref info, bindingOffset, offset, size)) + { + _storageSet.Clear(binding); + SignalDirty(DirtyFlags.Storage); + } + } + + binding++; + } + }); + } + + public void InsertBindingBarriers(CommandBufferScoped cbs) + { + if ((FeedbackLoopHazards?.Count ?? 0) > 0) + { + // Clear existing hazards - they will be rebuilt. + + foreach (TextureView hazard in FeedbackLoopHazards) + { + hazard.DecrementHazardUses(); + } + + FeedbackLoopHazards.Clear(); + } + + foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.TextureSetIndex]) + { + if (segment.Type == ResourceType.TextureAndSampler) + { + if (!segment.IsArray) + { + for (int i = 0; i < segment.Count; i++) + { + ref var texture = ref _textureRefs[segment.Binding + i]; + texture.View?.PrepareForUsage(cbs, texture.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); + } + } + else + { + ref var arrayRef = ref _textureArrayRefs[segment.Binding]; + PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags(); + arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags); + } + } + } + + foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.ImageSetIndex]) + { + if (segment.Type == ResourceType.Image) + { + if (!segment.IsArray) + { + for (int i = 0; i < segment.Count; i++) + { + ref var image = ref _imageRefs[segment.Binding + i]; + image.View?.PrepareForUsage(cbs, image.Stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); + } + } + else + { + ref var arrayRef = ref _imageArrayRefs[segment.Binding]; + PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags(); + arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags); + } + } + } + + for (int setIndex = PipelineBase.DescriptorSetLayouts; setIndex < _program.BindingSegments.Length; setIndex++) + { + var bindingSegments = _program.BindingSegments[setIndex]; + + if (bindingSegments.Length == 0) + { + continue; + } + + ResourceBindingSegment segment = bindingSegments[0]; + + if (segment.IsArray) + { + if (segment.Type == ResourceType.Texture || + segment.Type == ResourceType.Sampler || + segment.Type == ResourceType.TextureAndSampler || + segment.Type == ResourceType.BufferTexture) + { + ref var arrayRef = ref _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts]; + PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags(); + arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags); + } + else if (segment.Type == ResourceType.Image || segment.Type == ResourceType.BufferImage) + { + ref var arrayRef = ref _imageArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts]; + PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags(); + arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags); + } + } + } + } + + public void AdvancePdSequence() + { + if (++_pdSequence == 0) + { + _pdSequence = 1; + } + } + + public void SetProgram(CommandBufferScoped cbs, ShaderCollection program, bool isBound) + { + if (!program.HasSameLayout(_program)) + { + // When the pipeline layout changes, push descriptor bindings are invalidated. + + AdvancePdSequence(); + } + + _program = program; + _updateDescriptorCacheCbIndex = true; + _dirty = DirtyFlags.All; + } + + public void SetImage( + CommandBufferScoped cbs, + ShaderStage stage, + int binding, + ITexture image, + Format imageFormat) + { + if (image is TextureBuffer imageBuffer) + { + _bufferImageRefs[binding] = imageBuffer; + _bufferImageFormats[binding] = imageFormat; + } + else if (image is TextureView view) + { + ref ImageRef iRef = ref _imageRefs[binding]; + + iRef.View?.ClearUsage(FeedbackLoopHazards); + view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); + + iRef = new(stage, view, view.GetView(imageFormat).GetIdentityImageView()); + } + else + { + _imageRefs[binding] = default; + _bufferImageRefs[binding] = null; + _bufferImageFormats[binding] = default; + } + + SignalDirty(DirtyFlags.Image); + } + + public void SetImage(int binding, Auto image) + { + _imageRefs[binding] = new(ShaderStage.Compute, null, image); + + SignalDirty(DirtyFlags.Image); + } + + public void SetStorageBuffers(CommandBuffer commandBuffer, ReadOnlySpan buffers) + { + for (int i = 0; i < buffers.Length; i++) + { + var assignment = buffers[i]; + var buffer = assignment.Range; + int index = assignment.Binding; + + Auto vkBuffer = buffer.Handle == BufferHandle.Null + ? null + : _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, buffer.Write, isSSBO: true); + + ref BufferRef currentBufferRef = ref _storageBufferRefs[index]; + + DescriptorBufferInfo info = new() + { + Offset = (ulong)buffer.Offset, + Range = (ulong)buffer.Size, + }; + + var newRef = new BufferRef(vkBuffer, ref buffer); + + ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index]; + + if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range) + { + _storageSet.Clear(index); + + currentInfo = info; + currentBufferRef = newRef; + } + } + + SignalDirty(DirtyFlags.Storage); + } + + public void SetStorageBuffers(CommandBuffer commandBuffer, int first, ReadOnlySpan> buffers) + { + for (int i = 0; i < buffers.Length; i++) + { + var vkBuffer = buffers[i]; + int index = first + i; + + ref BufferRef currentBufferRef = ref _storageBufferRefs[index]; + + DescriptorBufferInfo info = new() + { + Offset = 0, + Range = Vk.WholeSize, + }; + + BufferRef newRef = new(vkBuffer); + + ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index]; + + if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range) + { + _storageSet.Clear(index); + + currentInfo = info; + currentBufferRef = newRef; + } + } + + SignalDirty(DirtyFlags.Storage); + } + + public void SetTextureAndSampler( + CommandBufferScoped cbs, + ShaderStage stage, + int binding, + ITexture texture, + ISampler sampler) + { + if (texture is TextureBuffer textureBuffer) + { + _bufferTextureRefs[binding] = textureBuffer; + } + else if (texture is TextureView view) + { + ref TextureRef iRef = ref _textureRefs[binding]; + + iRef.View?.ClearUsage(FeedbackLoopHazards); + view?.PrepareForUsage(cbs, stage.ConvertToPipelineStageFlags(), FeedbackLoopHazards); + + iRef = new(stage, view, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler()); + } + else + { + _textureRefs[binding] = default; + _bufferTextureRefs[binding] = null; + } + + SignalDirty(DirtyFlags.Texture); + } + + public void SetTextureAndSamplerIdentitySwizzle( + CommandBufferScoped cbs, + ShaderStage stage, + int binding, + ITexture texture, + ISampler sampler) + { + if (texture is TextureView view) + { + view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags()); + + _textureRefs[binding] = new(stage, view, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler()); + + SignalDirty(DirtyFlags.Texture); + } + else + { + SetTextureAndSampler(cbs, stage, binding, texture, sampler); + } + } + + public void SetTextureArray(CommandBufferScoped cbs, ShaderStage stage, int binding, ITextureArray array) + { + ref ArrayRef arrayRef = ref GetArrayRef(ref _textureArrayRefs, binding, ArrayGrowthSize); + + if (arrayRef.Stage != stage || arrayRef.Array != array) + { + arrayRef.Array?.DecrementBindCount(); + + if (array is TextureArray textureArray) + { + textureArray.IncrementBindCount(); + textureArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags()); + } + + arrayRef = new ArrayRef(stage, array as TextureArray); + + SignalDirty(DirtyFlags.Texture); + } + } + + public void SetTextureArraySeparate(CommandBufferScoped cbs, ShaderStage stage, int setIndex, ITextureArray array) + { + ref ArrayRef arrayRef = ref GetArrayRef(ref _textureArrayExtraRefs, setIndex - PipelineBase.DescriptorSetLayouts); + + if (arrayRef.Stage != stage || arrayRef.Array != array) + { + arrayRef.Array?.DecrementBindCount(); + + if (array is TextureArray textureArray) + { + textureArray.IncrementBindCount(); + textureArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags()); + } + + arrayRef = new ArrayRef(stage, array as TextureArray); + + SignalDirty(DirtyFlags.Texture); + } + } + + public void SetImageArray(CommandBufferScoped cbs, ShaderStage stage, int binding, IImageArray array) + { + ref ArrayRef arrayRef = ref GetArrayRef(ref _imageArrayRefs, binding, ArrayGrowthSize); + + if (arrayRef.Stage != stage || arrayRef.Array != array) + { + arrayRef.Array?.DecrementBindCount(); + + if (array is ImageArray imageArray) + { + imageArray.IncrementBindCount(); + imageArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags()); + } + + arrayRef = new ArrayRef(stage, array as ImageArray); + + SignalDirty(DirtyFlags.Image); + } + } + + public void SetImageArraySeparate(CommandBufferScoped cbs, ShaderStage stage, int setIndex, IImageArray array) + { + ref ArrayRef arrayRef = ref GetArrayRef(ref _imageArrayExtraRefs, setIndex - PipelineBase.DescriptorSetLayouts); + + if (arrayRef.Stage != stage || arrayRef.Array != array) + { + arrayRef.Array?.DecrementBindCount(); + + if (array is ImageArray imageArray) + { + imageArray.IncrementBindCount(); + imageArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags()); + } + + arrayRef = new ArrayRef(stage, array as ImageArray); + + SignalDirty(DirtyFlags.Image); + } + } + + private static ref ArrayRef GetArrayRef(ref ArrayRef[] array, int index, int growthSize = 1) + { + ArgumentOutOfRangeException.ThrowIfNegative(index); + + if (array.Length <= index) + { + Array.Resize(ref array, index + growthSize); + } + + return ref array[index]; + } + + public void SetUniformBuffers(CommandBuffer commandBuffer, ReadOnlySpan buffers) + { + for (int i = 0; i < buffers.Length; i++) + { + var assignment = buffers[i]; + var buffer = assignment.Range; + int index = assignment.Binding; + + Auto vkBuffer = buffer.Handle == BufferHandle.Null + ? null + : _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false); + + ref BufferRef currentBufferRef = ref _uniformBufferRefs[index]; + + DescriptorBufferInfo info = new() + { + Offset = (ulong)buffer.Offset, + Range = (ulong)buffer.Size, + }; + + BufferRef newRef = new(vkBuffer, ref buffer); + + ref DescriptorBufferInfo currentInfo = ref _uniformBuffers[index]; + + if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range) + { + _uniformSet.Clear(index); + _uniformSetPd[index] = 0; + + currentInfo = info; + currentBufferRef = newRef; + } + } + + SignalDirty(DirtyFlags.Uniform); + } + + private void SignalDirty(DirtyFlags flag) + { + _dirty |= flag; + } + + public void UpdateAndBindDescriptorSets(CommandBufferScoped cbs, PipelineBindPoint pbp) + { + if ((_dirty & DirtyFlags.All) == 0) + { + return; + } + + var program = _program; + + if (_dirty.HasFlag(DirtyFlags.Uniform)) + { + if (program.UsePushDescriptors) + { + UpdateAndBindUniformBufferPd(cbs); + } + else + { + UpdateAndBind(cbs, program, PipelineBase.UniformSetIndex, pbp); + } + } + + if (_dirty.HasFlag(DirtyFlags.Storage)) + { + UpdateAndBind(cbs, program, PipelineBase.StorageSetIndex, pbp); + } + + if (_dirty.HasFlag(DirtyFlags.Texture)) + { + if (program.UpdateTexturesWithoutTemplate) + { + UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp); + } + else + { + UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp); + } + } + + if (_dirty.HasFlag(DirtyFlags.Image)) + { + UpdateAndBind(cbs, program, PipelineBase.ImageSetIndex, pbp); + } + + if (program.BindingSegments.Length > PipelineBase.DescriptorSetLayouts) + { + // Program is using extra sets, we need to bind those too. + + BindExtraSets(cbs, program, pbp); + } + + _dirty = DirtyFlags.None; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UpdateBuffer( + CommandBufferScoped cbs, + ref DescriptorBufferInfo info, + ref BufferRef buffer, + Auto dummyBuffer, + bool mirrorable) + { + int offset = buffer.Offset; + bool mirrored = false; + + if (mirrorable) + { + info.Buffer = buffer.Buffer?.GetMirrorable(cbs, ref offset, (int)info.Range, out mirrored).Value ?? default; + } + else + { + info.Buffer = buffer.Buffer?.Get(cbs, offset, (int)info.Range, buffer.Write).Value ?? default; + } + + info.Offset = (ulong)offset; + + // The spec requires that buffers with null handle have offset as 0 and range as VK_WHOLE_SIZE. + if (info.Buffer.Handle == 0) + { + info.Buffer = dummyBuffer?.Get(cbs).Value ?? default; + info.Offset = 0; + info.Range = Vk.WholeSize; + } + + return mirrored; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateAndBind(CommandBufferScoped cbs, ShaderCollection program, int setIndex, PipelineBindPoint pbp) + { + var bindingSegments = program.BindingSegments[setIndex]; + + if (bindingSegments.Length == 0) + { + return; + } + + var dummyBuffer = _dummyBuffer?.GetBuffer(); + + if (_updateDescriptorCacheCbIndex) + { + _updateDescriptorCacheCbIndex = false; + program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex); + } + + var dsc = program.GetNewDescriptorSetCollection(setIndex, out var isNew).Get(cbs); + + if (!program.HasMinimalLayout) + { + if (isNew) + { + Initialize(cbs, setIndex, dsc); + } + } + + DescriptorSetTemplate template = program.Templates[setIndex]; + + DescriptorSetTemplateWriter tu = _templateUpdater.Begin(template); + + foreach (ResourceBindingSegment segment in bindingSegments) + { + int binding = segment.Binding; + int count = segment.Count; + + if (setIndex == PipelineBase.UniformSetIndex) + { + for (int i = 0; i < count; i++) + { + int index = binding + i; + + if (_uniformSet.Set(index)) + { + ref BufferRef buffer = ref _uniformBufferRefs[index]; + + bool mirrored = UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true); + + _uniformMirrored.Set(index, mirrored); + } + } + + ReadOnlySpan uniformBuffers = _uniformBuffers; + + tu.Push(uniformBuffers.Slice(binding, count)); + } + else if (setIndex == PipelineBase.StorageSetIndex) + { + for (int i = 0; i < count; i++) + { + int index = binding + i; + + ref BufferRef buffer = ref _storageBufferRefs[index]; + + if (_storageSet.Set(index)) + { + ref var info = ref _storageBuffers[index]; + + bool mirrored = UpdateBuffer(cbs, + ref info, + ref _storageBufferRefs[index], + dummyBuffer, + !buffer.Write && info.Range <= StorageBufferMaxMirrorable); + + _storageMirrored.Set(index, mirrored); + } + } + + ReadOnlySpan storageBuffers = _storageBuffers; + + tu.Push(storageBuffers.Slice(binding, count)); + } + else if (setIndex == PipelineBase.TextureSetIndex) + { + if (!segment.IsArray) + { + if (segment.Type != ResourceType.BufferTexture) + { + Span textures = _textures; + + for (int i = 0; i < count; i++) + { + ref var texture = ref textures[i]; + ref var refs = ref _textureRefs[binding + i]; + + texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default; + texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; + + if (texture.ImageView.Handle == 0) + { + texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value; + } + + if (texture.Sampler.Handle == 0) + { + texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value; + } + } + + tu.Push(textures[..count]); + } + else + { + Span bufferTextures = _bufferTextures; + + for (int i = 0; i < count; i++) + { + bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default; + } + + tu.Push(bufferTextures[..count]); + } + } + else + { + if (segment.Type != ResourceType.BufferTexture) + { + tu.Push(_textureArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler)); + } + else + { + tu.Push(_textureArrayRefs[binding].Array.GetBufferViews(cbs)); + } + } + } + else if (setIndex == PipelineBase.ImageSetIndex) + { + if (!segment.IsArray) + { + if (segment.Type != ResourceType.BufferImage) + { + Span images = _images; + + for (int i = 0; i < count; i++) + { + images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? default; + } + + tu.Push(images[..count]); + } + else + { + Span bufferImages = _bufferImages; + + for (int i = 0; i < count; i++) + { + bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i], true) ?? default; + } + + tu.Push(bufferImages[..count]); + } + } + else + { + if (segment.Type != ResourceType.BufferTexture) + { + tu.Push(_imageArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture)); + } + else + { + tu.Push(_imageArrayRefs[binding].Array.GetBufferViews(cbs)); + } + } + } + } + + var sets = dsc.GetSets(); + _templateUpdater.Commit(_gd, _device, sets[0]); + + _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty); + } + + private void UpdateAndBindTexturesWithoutTemplate(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp) + { + int setIndex = PipelineBase.TextureSetIndex; + var bindingSegments = program.BindingSegments[setIndex]; + + if (bindingSegments.Length == 0) + { + return; + } + + if (_updateDescriptorCacheCbIndex) + { + _updateDescriptorCacheCbIndex = false; + program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex); + } + + var dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs); + + foreach (ResourceBindingSegment segment in bindingSegments) + { + int binding = segment.Binding; + int count = segment.Count; + + if (!segment.IsArray) + { + if (segment.Type != ResourceType.BufferTexture) + { + Span textures = _textures; + + for (int i = 0; i < count; i++) + { + ref var texture = ref textures[i]; + ref var refs = ref _textureRefs[binding + i]; + + texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default; + texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; + + if (texture.ImageView.Handle == 0) + { + texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value; + } + + if (texture.Sampler.Handle == 0) + { + texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value; + } + } + + dsc.UpdateImages(0, binding, textures[..count], DescriptorType.CombinedImageSampler); + } + else + { + Span bufferTextures = _bufferTextures; + + for (int i = 0; i < count; i++) + { + bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default; + } + + dsc.UpdateBufferImages(0, binding, bufferTextures[..count], DescriptorType.UniformTexelBuffer); + } + } + else + { + if (segment.Type != ResourceType.BufferTexture) + { + dsc.UpdateImages(0, binding, _textureArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler), DescriptorType.CombinedImageSampler); + } + else + { + dsc.UpdateBufferImages(0, binding, _textureArrayRefs[binding].Array.GetBufferViews(cbs), DescriptorType.UniformTexelBuffer); + } + } + } + + var sets = dsc.GetSets(); + + _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs) + { + int sequence = _pdSequence; + var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex]; + var dummyBuffer = _dummyBuffer?.GetBuffer(); + + long updatedBindings = 0; + DescriptorSetTemplateWriter writer = _templateUpdater.Begin(32 * Unsafe.SizeOf()); + + foreach (ResourceBindingSegment segment in bindingSegments) + { + int binding = segment.Binding; + int count = segment.Count; + + ReadOnlySpan uniformBuffers = _uniformBuffers; + + for (int i = 0; i < count; i++) + { + int index = binding + i; + + if (_uniformSet.Set(index)) + { + ref BufferRef buffer = ref _uniformBufferRefs[index]; + + bool mirrored = UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true); + + _uniformMirrored.Set(index, mirrored); + } + + if (_uniformSetPd[index] != sequence) + { + // Need to set this push descriptor (even if the buffer binding has not changed) + + _uniformSetPd[index] = sequence; + updatedBindings |= 1L << index; + + writer.Push(MemoryMarshal.CreateReadOnlySpan(ref _uniformBuffers[index], 1)); + } + } + } + + if (updatedBindings > 0) + { + DescriptorSetTemplate template = _program.GetPushDescriptorTemplate(updatedBindings); + _templateUpdater.CommitPushDescriptor(_gd, cbs, template, _program.PipelineLayout); + } + } + + private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc) + { + // We don't support clearing texture descriptors currently. + if (setIndex != PipelineBase.UniformSetIndex && setIndex != PipelineBase.StorageSetIndex) + { + return; + } + + var dummyBuffer = _dummyBuffer?.GetBuffer().Get(cbs).Value ?? default; + + foreach (ResourceBindingSegment segment in _program.ClearSegments[setIndex]) + { + dsc.InitializeBuffers(0, segment.Binding, segment.Count, segment.Type.Convert(), dummyBuffer); + } + } + + private void BindExtraSets(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp) + { + for (int setIndex = PipelineBase.DescriptorSetLayouts; setIndex < program.BindingSegments.Length; setIndex++) + { + var bindingSegments = program.BindingSegments[setIndex]; + + if (bindingSegments.Length == 0) + { + continue; + } + + ResourceBindingSegment segment = bindingSegments[0]; + + if (segment.IsArray) + { + DescriptorSet[] sets = null; + + if (segment.Type == ResourceType.Texture || + segment.Type == ResourceType.Sampler || + segment.Type == ResourceType.TextureAndSampler || + segment.Type == ResourceType.BufferTexture) + { + sets = _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts].Array.GetDescriptorSets( + _device, + cbs, + _templateUpdater, + program, + setIndex, + _dummyTexture, + _dummySampler); + } + else if (segment.Type == ResourceType.Image || segment.Type == ResourceType.BufferImage) + { + sets = _imageArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts].Array.GetDescriptorSets( + _device, + cbs, + _templateUpdater, + program, + setIndex, + _dummyTexture); + } + + if (sets != null) + { + _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty); + } + } + } + } + + public void SignalCommandBufferChange() + { + _updateDescriptorCacheCbIndex = true; + _dirty = DirtyFlags.All; + + _uniformSet.Clear(); + _storageSet.Clear(); + AdvancePdSequence(); + } + + public void ForceTextureDirty() + { + SignalDirty(DirtyFlags.Texture); + } + + public void ForceImageDirty() + { + SignalDirty(DirtyFlags.Image); + } + + private static void SwapBuffer(BufferRef[] list, Auto from, Auto to) + { + for (int i = 0; i < list.Length; i++) + { + if (list[i].Buffer == from) + { + list[i].Buffer = to; + } + } + } + + public void SwapBuffer(Auto from, Auto to) + { + SwapBuffer(_uniformBufferRefs, from, to); + SwapBuffer(_storageBufferRefs, from, to); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _dummyTexture.Dispose(); + _dummySampler.Dispose(); + _templateUpdater.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DisposableBuffer.cs b/src/Ryujinx.Graphics.Vulkan/DisposableBuffer.cs new file mode 100644 index 00000000..98e0e9dd --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/DisposableBuffer.cs @@ -0,0 +1,26 @@ +using Silk.NET.Vulkan; +using System; +using Buffer = Silk.NET.Vulkan.Buffer; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct DisposableBuffer : IDisposable + { + private readonly Vk _api; + private readonly Device _device; + + public Buffer Value { get; } + + public DisposableBuffer(Vk api, Device device, Buffer buffer) + { + _api = api; + _device = device; + Value = buffer; + } + + public void Dispose() + { + _api.DestroyBuffer(_device, Value, Span.Empty); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DisposableBufferView.cs b/src/Ryujinx.Graphics.Vulkan/DisposableBufferView.cs new file mode 100644 index 00000000..dc9efc8a --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/DisposableBufferView.cs @@ -0,0 +1,25 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct DisposableBufferView : IDisposable + { + private readonly Vk _api; + private readonly Device _device; + + public BufferView Value { get; } + + public DisposableBufferView(Vk api, Device device, BufferView bufferView) + { + _api = api; + _device = device; + Value = bufferView; + } + + public void Dispose() + { + _api.DestroyBufferView(_device, Value, Span.Empty); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DisposableFramebuffer.cs b/src/Ryujinx.Graphics.Vulkan/DisposableFramebuffer.cs new file mode 100644 index 00000000..e0e53647 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/DisposableFramebuffer.cs @@ -0,0 +1,25 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct DisposableFramebuffer : IDisposable + { + private readonly Vk _api; + private readonly Device _device; + + public Framebuffer Value { get; } + + public DisposableFramebuffer(Vk api, Device device, Framebuffer framebuffer) + { + _api = api; + _device = device; + Value = framebuffer; + } + + public void Dispose() + { + _api.DestroyFramebuffer(_device, Value, Span.Empty); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DisposableImage.cs b/src/Ryujinx.Graphics.Vulkan/DisposableImage.cs new file mode 100644 index 00000000..98860cc0 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/DisposableImage.cs @@ -0,0 +1,25 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct DisposableImage : IDisposable + { + private readonly Vk _api; + private readonly Device _device; + + public Image Value { get; } + + public DisposableImage(Vk api, Device device, Image image) + { + _api = api; + _device = device; + Value = image; + } + + public void Dispose() + { + _api.DestroyImage(_device, Value, Span.Empty); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DisposableImageView.cs b/src/Ryujinx.Graphics.Vulkan/DisposableImageView.cs new file mode 100644 index 00000000..d3b6224c --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/DisposableImageView.cs @@ -0,0 +1,25 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct DisposableImageView : IDisposable + { + private readonly Vk _api; + private readonly Device _device; + + public ImageView Value { get; } + + public DisposableImageView(Vk api, Device device, ImageView imageView) + { + _api = api; + _device = device; + Value = imageView; + } + + public void Dispose() + { + _api.DestroyImageView(_device, Value, Span.Empty); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DisposableMemory.cs b/src/Ryujinx.Graphics.Vulkan/DisposableMemory.cs new file mode 100644 index 00000000..c8d135b2 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/DisposableMemory.cs @@ -0,0 +1,24 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct DisposableMemory : IDisposable + { + private readonly Vk _api; + private readonly Device _device; + private readonly DeviceMemory _memory; + + public DisposableMemory(Vk api, Device device, DeviceMemory memory) + { + _api = api; + _device = device; + _memory = memory; + } + + public void Dispose() + { + _api.FreeMemory(_device, _memory, Span.Empty); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DisposablePipeline.cs b/src/Ryujinx.Graphics.Vulkan/DisposablePipeline.cs new file mode 100644 index 00000000..10e88a04 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/DisposablePipeline.cs @@ -0,0 +1,25 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct DisposablePipeline : IDisposable + { + private readonly Vk _api; + private readonly Device _device; + + public Pipeline Value { get; } + + public DisposablePipeline(Vk api, Device device, Pipeline pipeline) + { + _api = api; + _device = device; + Value = pipeline; + } + + public void Dispose() + { + _api.DestroyPipeline(_device, Value, Span.Empty); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DisposableRenderPass.cs b/src/Ryujinx.Graphics.Vulkan/DisposableRenderPass.cs new file mode 100644 index 00000000..f42dbeff --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/DisposableRenderPass.cs @@ -0,0 +1,25 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct DisposableRenderPass : IDisposable + { + private readonly Vk _api; + private readonly Device _device; + + public RenderPass Value { get; } + + public DisposableRenderPass(Vk api, Device device, RenderPass renderPass) + { + _api = api; + _device = device; + Value = renderPass; + } + + public void Dispose() + { + _api.DestroyRenderPass(_device, Value, Span.Empty); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/DisposableSampler.cs b/src/Ryujinx.Graphics.Vulkan/DisposableSampler.cs new file mode 100644 index 00000000..7e04c47a --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/DisposableSampler.cs @@ -0,0 +1,25 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct DisposableSampler : IDisposable + { + private readonly Vk _api; + private readonly Device _device; + + public Sampler Value { get; } + + public DisposableSampler(Vk api, Device device, Sampler sampler) + { + _api = api; + _device = device; + Value = sampler; + } + + public void Dispose() + { + _api.DestroySampler(_device, Value, Span.Empty); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs b/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs new file mode 100644 index 00000000..c4501ca1 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs @@ -0,0 +1,172 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using Silk.NET.Vulkan; +using System; +using Extent2D = Ryujinx.Graphics.GAL.Extents2D; +using Format = Silk.NET.Vulkan.Format; +using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo; + +namespace Ryujinx.Graphics.Vulkan.Effects +{ + internal class FsrScalingFilter : IScalingFilter + { + private readonly VulkanRenderer _renderer; + private PipelineHelperShader _pipeline; + private ISampler _sampler; + private ShaderCollection _scalingProgram; + private ShaderCollection _sharpeningProgram; + private float _sharpeningLevel = 1; + private Device _device; + private TextureView _intermediaryTexture; + + public float Level + { + get => _sharpeningLevel; + set + { + _sharpeningLevel = MathF.Max(0.01f, value); + } + } + + public FsrScalingFilter(VulkanRenderer renderer, Device device) + { + _device = device; + _renderer = renderer; + + Initialize(); + } + + public void Dispose() + { + _pipeline.Dispose(); + _scalingProgram.Dispose(); + _sharpeningProgram.Dispose(); + _sampler.Dispose(); + _intermediaryTexture?.Dispose(); + } + + public void Initialize() + { + _pipeline = new PipelineHelperShader(_renderer, _device); + + _pipeline.Initialize(); + + var scalingShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv"); + var sharpeningShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv"); + + var scalingResourceLayout = new ResourceLayoutBuilder() + .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2) + .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1) + .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build(); + + var sharpeningResourceLayout = new ResourceLayoutBuilder() + .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2) + .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 3) + .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 4) + .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1) + .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build(); + + _sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear)); + + _scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(scalingShader, ShaderStage.Compute, TargetLanguage.Spirv), + }, scalingResourceLayout); + + _sharpeningProgram = _renderer.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(sharpeningShader, ShaderStage.Compute, TargetLanguage.Spirv), + }, sharpeningResourceLayout); + } + + public void Run( + TextureView view, + CommandBufferScoped cbs, + Auto destinationTexture, + Format format, + int width, + int height, + Extent2D source, + Extent2D destination) + { + if (_intermediaryTexture == null + || _intermediaryTexture.Info.Width != width + || _intermediaryTexture.Info.Height != height + || !_intermediaryTexture.Info.Equals(view.Info)) + { + var originalInfo = view.Info; + + var info = new TextureCreateInfo( + width, + height, + originalInfo.Depth, + originalInfo.Levels, + originalInfo.Samples, + originalInfo.BlockWidth, + originalInfo.BlockHeight, + originalInfo.BytesPerPixel, + originalInfo.Format, + originalInfo.DepthStencilMode, + originalInfo.Target, + originalInfo.SwizzleR, + originalInfo.SwizzleG, + originalInfo.SwizzleB, + originalInfo.SwizzleA); + _intermediaryTexture?.Dispose(); + _intermediaryTexture = _renderer.CreateTexture(info) as TextureView; + } + + _pipeline.SetCommandBuffer(cbs); + _pipeline.SetProgram(_scalingProgram); + _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _sampler); + + float srcWidth = Math.Abs(source.X2 - source.X1); + float srcHeight = Math.Abs(source.Y2 - source.Y1); + float scaleX = srcWidth / view.Width; + float scaleY = srcHeight / view.Height; + + ReadOnlySpan dimensionsBuffer = stackalloc float[] + { + source.X1, + source.X2, + source.Y1, + source.Y2, + destination.X1, + destination.X2, + destination.Y1, + destination.Y2, + scaleX, + scaleY, + }; + + int rangeSize = dimensionsBuffer.Length * sizeof(float); + using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize); + buffer.Holder.SetDataUnchecked(buffer.Offset, dimensionsBuffer); + + ReadOnlySpan sharpeningBufferData = stackalloc float[] { 1.5f - (Level * 0.01f * 1.5f) }; + using var sharpeningBuffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, sizeof(float)); + sharpeningBuffer.Holder.SetDataUnchecked(sharpeningBuffer.Offset, sharpeningBufferData); + + int threadGroupWorkRegionDim = 16; + int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; + int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; + + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) }); + _pipeline.SetImage(ShaderStage.Compute, 0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); + _pipeline.DispatchCompute(dispatchX, dispatchY, 1); + _pipeline.ComputeBarrier(); + + // Sharpening pass + _pipeline.SetProgram(_sharpeningProgram); + _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _intermediaryTexture, _sampler); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(4, sharpeningBuffer.Range) }); + _pipeline.SetImage(0, destinationTexture); + _pipeline.DispatchCompute(dispatchX, dispatchY, 1); + _pipeline.ComputeBarrier(); + + _pipeline.Finish(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs new file mode 100644 index 00000000..70b3b32a --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs @@ -0,0 +1,88 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using Silk.NET.Vulkan; +using System; +using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo; + +namespace Ryujinx.Graphics.Vulkan.Effects +{ + internal class FxaaPostProcessingEffect : IPostProcessingEffect + { + private readonly VulkanRenderer _renderer; + private ISampler _samplerLinear; + private ShaderCollection _shaderProgram; + + private readonly PipelineHelperShader _pipeline; + private TextureView _texture; + + public FxaaPostProcessingEffect(VulkanRenderer renderer, Device device) + { + _renderer = renderer; + _pipeline = new PipelineHelperShader(renderer, device); + + Initialize(); + } + + public void Dispose() + { + _shaderProgram.Dispose(); + _pipeline.Dispose(); + _samplerLinear.Dispose(); + _texture?.Dispose(); + } + + private void Initialize() + { + _pipeline.Initialize(); + + var shader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.spv"); + + var resourceLayout = new ResourceLayoutBuilder() + .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2) + .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1) + .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build(); + + _samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear)); + + _shaderProgram = _renderer.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(shader, ShaderStage.Compute, TargetLanguage.Spirv), + }, resourceLayout); + } + + public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height) + { + if (_texture == null || _texture.Width != view.Width || _texture.Height != view.Height) + { + _texture?.Dispose(); + _texture = _renderer.CreateTexture(view.Info) as TextureView; + } + + _pipeline.SetCommandBuffer(cbs); + _pipeline.SetProgram(_shaderProgram); + _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear); + + ReadOnlySpan resolutionBuffer = stackalloc float[] { view.Width, view.Height }; + int rangeSize = resolutionBuffer.Length * sizeof(float); + using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize); + + buffer.Holder.SetDataUnchecked(buffer.Offset, resolutionBuffer); + + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) }); + + var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize); + var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize); + + _pipeline.SetImage(ShaderStage.Compute, 0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); + _pipeline.DispatchCompute(dispatchX, dispatchY, 1); + + _pipeline.ComputeBarrier(); + + _pipeline.Finish(); + + return _texture; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/IPostProcessingEffect.cs b/src/Ryujinx.Graphics.Vulkan/Effects/IPostProcessingEffect.cs new file mode 100644 index 00000000..d13641c6 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Effects/IPostProcessingEffect.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ryujinx.Graphics.Vulkan.Effects +{ + internal interface IPostProcessingEffect : IDisposable + { + const int LocalGroupSize = 64; + TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height); + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/IScalingFilter.cs b/src/Ryujinx.Graphics.Vulkan/Effects/IScalingFilter.cs new file mode 100644 index 00000000..50fc3710 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Effects/IScalingFilter.cs @@ -0,0 +1,20 @@ +using Silk.NET.Vulkan; +using System; +using Extent2D = Ryujinx.Graphics.GAL.Extents2D; + +namespace Ryujinx.Graphics.Vulkan.Effects +{ + internal interface IScalingFilter : IDisposable + { + float Level { get; set; } + void Run( + TextureView view, + CommandBufferScoped cbs, + Auto destinationTexture, + Format format, + int width, + int height, + Extent2D source, + Extent2D destination); + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.glsl b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.glsl new file mode 100644 index 00000000..5eb74b3d --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.glsl @@ -0,0 +1,3945 @@ +// Scaling + +#version 430 core +layout (local_size_x = 64) in; +layout( rgba8, binding = 0, set = 3) uniform image2D imgOutput; +layout( binding = 1, set = 2) uniform sampler2D Source; +layout( binding = 2 ) uniform dimensions{ + float srcX0; + float srcX1; + float srcY0; + float srcY1; + float dstX0; + float dstX1; + float dstY0; + float dstY1; + float scaleX; + float scaleY; +}; + +#define A_GPU 1 +#define A_GLSL 1 +//============================================================================================================================== +// +// [A] SHADER PORTABILITY 1.20210629 +// +//============================================================================================================================== +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +// MIT LICENSE +// =========== +// Copyright (c) 2014 Michal Drobot (for concepts used in "FLOAT APPROXIMATIONS"). +// ----------- +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// ----------- +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +// Software. +// ----------- +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +// ABOUT +// ===== +// Common central point for high-level shading language and C portability for various shader headers. +//------------------------------------------------------------------------------------------------------------------------------ +// DEFINES +// ======= +// A_CPU ..... Include the CPU related code. +// A_GPU ..... Include the GPU related code. +// A_GLSL .... Using GLSL. +// A_HLSL .... Using HLSL. +// A_HLSL_6_2 Using HLSL 6.2 with new 'uint16_t' and related types (requires '-enable-16bit-types'). +// A_NO_16_BIT_CAST Don't use instructions that are not availabe in SPIR-V (needed for running A_HLSL_6_2 on Vulkan) +// A_GCC ..... Using a GCC compatible compiler (else assume MSVC compatible compiler by default). +// ======= +// A_BYTE .... Support 8-bit integer. +// A_HALF .... Support 16-bit integer and floating point. +// A_LONG .... Support 64-bit integer. +// A_DUBL .... Support 64-bit floating point. +// ======= +// A_WAVE .... Support wave-wide operations. +//------------------------------------------------------------------------------------------------------------------------------ +// To get #include "ffx_a.h" working in GLSL use '#extension GL_GOOGLE_include_directive:require'. +//------------------------------------------------------------------------------------------------------------------------------ +// SIMPLIFIED TYPE SYSTEM +// ====================== +// - All ints will be unsigned with exception of when signed is required. +// - Type naming simplified and shortened "A<#components>", +// - H = 16-bit float (half) +// - F = 32-bit float (float) +// - D = 64-bit float (double) +// - P = 1-bit integer (predicate, not using bool because 'B' is used for byte) +// - B = 8-bit integer (byte) +// - W = 16-bit integer (word) +// - U = 32-bit integer (unsigned) +// - L = 64-bit integer (long) +// - Using "AS<#components>" for signed when required. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure 'ALerp*(a,b,m)' does 'b*m+(-a*m+a)' (2 ops). +//------------------------------------------------------------------------------------------------------------------------------ +// CHANGE LOG +// ========== +// 20200914 - Expanded wave ops and prx code. +// 20200713 - Added [ZOL] section, fixed serious bugs in sRGB and Rec.709 color conversion code, etc. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COMMON +//============================================================================================================================== +#define A_2PI 6.28318530718 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// CPU +// +// +//============================================================================================================================== +#ifdef A_CPU + // Supporting user defined overrides. + #ifndef A_RESTRICT + #define A_RESTRICT __restrict + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifndef A_STATIC + #define A_STATIC static + #endif +//------------------------------------------------------------------------------------------------------------------------------ + // Same types across CPU and GPU. + // Predicate uses 32-bit integer (C friendly bool). + typedef uint32_t AP1; + typedef float AF1; + typedef double AD1; + typedef uint8_t AB1; + typedef uint16_t AW1; + typedef uint32_t AU1; + typedef uint64_t AL1; + typedef int8_t ASB1; + typedef int16_t ASW1; + typedef int32_t ASU1; + typedef int64_t ASL1; +//------------------------------------------------------------------------------------------------------------------------------ + #define AD1_(a) ((AD1)(a)) + #define AF1_(a) ((AF1)(a)) + #define AL1_(a) ((AL1)(a)) + #define AU1_(a) ((AU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1_(a) ((ASL1)(a)) + #define ASU1_(a) ((ASU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AU1 AU1_AF1(AF1 a){union{AF1 f;AU1 u;}bits;bits.f=a;return bits.u;} +//------------------------------------------------------------------------------------------------------------------------------ + #define A_TRUE 1 + #define A_FALSE 0 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// CPU/GPU PORTING +// +//------------------------------------------------------------------------------------------------------------------------------ +// Get CPU and GPU to share all setup code, without duplicate code paths. +// This uses a lower-case prefix for special vector constructs. +// - In C restrict pointers are used. +// - In the shading language, in/inout/out arguments are used. +// This depends on the ability to access a vector value in both languages via array syntax (aka color[2]). +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD1 *A_RESTRICT + #define retAD3 AD1 *A_RESTRICT + #define retAD4 AD1 *A_RESTRICT + #define retAF2 AF1 *A_RESTRICT + #define retAF3 AF1 *A_RESTRICT + #define retAF4 AF1 *A_RESTRICT + #define retAL2 AL1 *A_RESTRICT + #define retAL3 AL1 *A_RESTRICT + #define retAL4 AL1 *A_RESTRICT + #define retAU2 AU1 *A_RESTRICT + #define retAU3 AU1 *A_RESTRICT + #define retAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 AD1 *A_RESTRICT + #define inAD3 AD1 *A_RESTRICT + #define inAD4 AD1 *A_RESTRICT + #define inAF2 AF1 *A_RESTRICT + #define inAF3 AF1 *A_RESTRICT + #define inAF4 AF1 *A_RESTRICT + #define inAL2 AL1 *A_RESTRICT + #define inAL3 AL1 *A_RESTRICT + #define inAL4 AL1 *A_RESTRICT + #define inAU2 AU1 *A_RESTRICT + #define inAU3 AU1 *A_RESTRICT + #define inAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 AD1 *A_RESTRICT + #define inoutAD3 AD1 *A_RESTRICT + #define inoutAD4 AD1 *A_RESTRICT + #define inoutAF2 AF1 *A_RESTRICT + #define inoutAF3 AF1 *A_RESTRICT + #define inoutAF4 AF1 *A_RESTRICT + #define inoutAL2 AL1 *A_RESTRICT + #define inoutAL3 AL1 *A_RESTRICT + #define inoutAL4 AL1 *A_RESTRICT + #define inoutAU2 AU1 *A_RESTRICT + #define inoutAU3 AU1 *A_RESTRICT + #define inoutAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 AD1 *A_RESTRICT + #define outAD3 AD1 *A_RESTRICT + #define outAD4 AD1 *A_RESTRICT + #define outAF2 AF1 *A_RESTRICT + #define outAF3 AF1 *A_RESTRICT + #define outAF4 AF1 *A_RESTRICT + #define outAL2 AL1 *A_RESTRICT + #define outAL3 AL1 *A_RESTRICT + #define outAL4 AL1 *A_RESTRICT + #define outAU2 AU1 *A_RESTRICT + #define outAU3 AU1 *A_RESTRICT + #define outAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD1 x[2] + #define varAD3(x) AD1 x[3] + #define varAD4(x) AD1 x[4] + #define varAF2(x) AF1 x[2] + #define varAF3(x) AF1 x[3] + #define varAF4(x) AF1 x[4] + #define varAL2(x) AL1 x[2] + #define varAL3(x) AL1 x[3] + #define varAL4(x) AL1 x[4] + #define varAU2(x) AU1 x[2] + #define varAU3(x) AU1 x[3] + #define varAU4(x) AU1 x[4] +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) {x,y} + #define initAD3(x,y,z) {x,y,z} + #define initAD4(x,y,z,w) {x,y,z,w} + #define initAF2(x,y) {x,y} + #define initAF3(x,y,z) {x,y,z} + #define initAF4(x,y,z,w) {x,y,z,w} + #define initAL2(x,y) {x,y} + #define initAL3(x,y,z) {x,y,z} + #define initAL4(x,y,z,w) {x,y,z,w} + #define initAU2(x,y) {x,y} + #define initAU3(x,y,z) {x,y,z} + #define initAU4(x,y,z,w) {x,y,z,w} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Replace transcendentals with manual versions. +//============================================================================================================================== + #ifdef A_GCC + A_STATIC AD1 AAbsD1(AD1 a){return __builtin_fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return __builtin_fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(__builtin_abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(__builtin_llabs(ASL1_(a)));} + #else + A_STATIC AD1 AAbsD1(AD1 a){return fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(labs((long)ASL1_(a)));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ACosD1(AD1 a){return __builtin_cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return __builtin_cosf(a);} + #else + A_STATIC AD1 ACosD1(AD1 a){return cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return cosf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ADotD2(inAD2 a,inAD2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AD1 ADotD3(inAD3 a,inAD3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AD1 ADotD4(inAD4 a,inAD4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} + A_STATIC AF1 ADotF2(inAF2 a,inAF2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AF1 ADotF3(inAF3 a,inAF3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AF1 ADotF4(inAF4 a,inAF4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AExp2D1(AD1 a){return __builtin_exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return __builtin_exp2f(a);} + #else + A_STATIC AD1 AExp2D1(AD1 a){return exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return exp2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AFloorD1(AD1 a){return __builtin_floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return __builtin_floorf(a);} + #else + A_STATIC AD1 AFloorD1(AD1 a){return floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return floorf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ALerpD1(AD1 a,AD1 b,AD1 c){return b*c+(-a*c+a);} + A_STATIC AF1 ALerpF1(AF1 a,AF1 b,AF1 c){return b*c+(-a*c+a);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ALog2D1(AD1 a){return __builtin_log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return __builtin_log2f(a);} + #else + A_STATIC AD1 ALog2D1(AD1 a){return log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return log2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMaxD1(AD1 a,AD1 b){return a>b?a:b;} + A_STATIC AF1 AMaxF1(AF1 a,AF1 b){return a>b?a:b;} + A_STATIC AL1 AMaxL1(AL1 a,AL1 b){return a>b?a:b;} + A_STATIC AU1 AMaxU1(AU1 a,AU1 b){return a>b?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + // These follow the convention that A integer types don't have signage, until they are operated on. + A_STATIC AL1 AMaxSL1(AL1 a,AL1 b){return (ASL1_(a)>ASL1_(b))?a:b;} + A_STATIC AU1 AMaxSU1(AU1 a,AU1 b){return (ASU1_(a)>ASU1_(b))?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMinD1(AD1 a,AD1 b){return a>ASL1_(b));} + A_STATIC AU1 AShrSU1(AU1 a,AU1 b){return AU1_(ASU1_(a)>>ASU1_(b));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASinD1(AD1 a){return __builtin_sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return __builtin_sinf(a);} + #else + A_STATIC AD1 ASinD1(AD1 a){return sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return sinf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASqrtD1(AD1 a){return __builtin_sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return __builtin_sqrtf(a);} + #else + A_STATIC AD1 ASqrtD1(AD1 a){return sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return sqrtf(a);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + A_STATIC AD1 AClampD1(AD1 x,AD1 n,AD1 m){return AMaxD1(n,AMinD1(x,m));} + A_STATIC AF1 AClampF1(AF1 x,AF1 n,AF1 m){return AMaxF1(n,AMinF1(x,m));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AFractD1(AD1 a){return a-AFloorD1(a);} + A_STATIC AF1 AFractF1(AF1 a){return a-AFloorF1(a);} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 APowD1(AD1 a,AD1 b){return AExp2D1(b*ALog2D1(a));} + A_STATIC AF1 APowF1(AF1 a,AF1 b){return AExp2F1(b*ALog2F1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ARsqD1(AD1 a){return ARcpD1(ASqrtD1(a));} + A_STATIC AF1 ARsqF1(AF1 a){return ARcpF1(ASqrtF1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ASatD1(AD1 a){return AMinD1(1.0,AMaxD1(0.0,a));} + A_STATIC AF1 ASatF1(AF1 a){return AMinF1(1.0f,AMaxF1(0.0f,a));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + A_STATIC retAD2 opAAbsD2(outAD2 d,inAD2 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);return d;} + A_STATIC retAD3 opAAbsD3(outAD3 d,inAD3 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);return d;} + A_STATIC retAD4 opAAbsD4(outAD4 d,inAD4 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);d[3]=AAbsD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAbsF2(outAF2 d,inAF2 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);return d;} + A_STATIC retAF3 opAAbsF3(outAF3 d,inAF3 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);return d;} + A_STATIC retAF4 opAAbsF4(outAF4 d,inAF4 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);d[3]=AAbsF1(a[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} + A_STATIC retAD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} + A_STATIC retAD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} + A_STATIC retAF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} + A_STATIC retAF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opACpyD2(outAD2 d,inAD2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAD3 opACpyD3(outAD3 d,inAD3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAD4 opACpyD4(outAD4 d,inAD4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opACpyF2(outAF2 d,inAF2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAF3 opACpyF3(outAF3 d,inAF3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAF4 opACpyF4(outAF4 d,inAF4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);return d;} + A_STATIC retAD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);return d;} + A_STATIC retAD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);d[3]=ALerpD1(a[3],b[3],c[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);return d;} + A_STATIC retAF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);return d;} + A_STATIC retAF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);d[3]=ALerpF1(a[3],b[3],c[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);return d;} + A_STATIC retAD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);return d;} + A_STATIC retAD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);d[3]=ALerpD1(a[3],b[3],c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);return d;} + A_STATIC retAF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);return d;} + A_STATIC retAF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);d[3]=ALerpF1(a[3],b[3],c);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);d[3]=AMaxD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);d[3]=AMaxF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);d[3]=AMinD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);d[3]=AMinF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opANegD2(outAD2 d,inAD2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAD3 opANegD3(outAD3 d,inAD3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAD4 opANegD4(outAD4 d,inAD4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opANegF2(outAF2 d,inAF2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAF3 opANegF3(outAF3 d,inAF3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAF4 opANegF4(outAF4 d,inAF4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opARcpD2(outAD2 d,inAD2 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);return d;} + A_STATIC retAD3 opARcpD3(outAD3 d,inAD3 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);return d;} + A_STATIC retAD4 opARcpD4(outAD4 d,inAD4 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);d[3]=ARcpD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opARcpF2(outAF2 d,inAF2 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);return d;} + A_STATIC retAF3 opARcpF3(outAF3 d,inAF3 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);return d;} + A_STATIC retAF4 opARcpF4(outAF4 d,inAF4 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);d[3]=ARcpF1(a[3]);return d;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF FLOAT PACKING +//============================================================================================================================== + // Convert float to half (in lower 16-bits of output). + // Same fast technique as documented here: ftp://ftp.fox-toolkit.org/pub/fasthalffloatconversion.pdf + // Supports denormals. + // Conversion rules are to make computations possibly "safer" on the GPU, + // -INF & -NaN -> -65504 + // +INF & +NaN -> +65504 + A_STATIC AU1 AU1_AH1_AF1(AF1 f){ + static AW1 base[512]={ + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0001,0x0002,0x0004,0x0008,0x0010,0x0020,0x0040,0x0080,0x0100, + 0x0200,0x0400,0x0800,0x0c00,0x1000,0x1400,0x1800,0x1c00,0x2000,0x2400,0x2800,0x2c00,0x3000,0x3400,0x3800,0x3c00, + 0x4000,0x4400,0x4800,0x4c00,0x5000,0x5400,0x5800,0x5c00,0x6000,0x6400,0x6800,0x6c00,0x7000,0x7400,0x7800,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8001,0x8002,0x8004,0x8008,0x8010,0x8020,0x8040,0x8080,0x8100, + 0x8200,0x8400,0x8800,0x8c00,0x9000,0x9400,0x9800,0x9c00,0xa000,0xa400,0xa800,0xac00,0xb000,0xb400,0xb800,0xbc00, + 0xc000,0xc400,0xc800,0xcc00,0xd000,0xd400,0xd800,0xdc00,0xe000,0xe400,0xe800,0xec00,0xf000,0xf400,0xf800,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff}; + static AB1 shift[512]={ + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18}; + union{AF1 f;AU1 u;}bits;bits.f=f;AU1 u=bits.u;AU1 i=u>>23;return (AU1)(base[i])+((u&0x7fffff)>>shift[i]);} +//------------------------------------------------------------------------------------------------------------------------------ + // Used to output packed constant. + A_STATIC AU1 AU1_AH2_AF2(inAF2 a){return AU1_AH1_AF1(a[0])+(AU1_AH1_AF1(a[1])<<16);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GLSL +// +// +//============================================================================================================================== +#if defined(A_GLSL) && defined(A_GPU) + #ifndef A_SKIP_EXT + #ifdef A_HALF + #extension GL_EXT_shader_16bit_storage:require + #extension GL_EXT_shader_explicit_arithmetic_types:require + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_LONG + #extension GL_ARB_gpu_shader_int64:require + #extension GL_NV_shader_atomic_int64:require + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_WAVE + #extension GL_KHR_shader_subgroup_arithmetic:require + #extension GL_KHR_shader_subgroup_ballot:require + #extension GL_KHR_shader_subgroup_quad:require + #extension GL_KHR_shader_subgroup_shuffle:require + #endif + #endif +//============================================================================================================================== + #define AP1 bool + #define AP2 bvec2 + #define AP3 bvec3 + #define AP4 bvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 vec2 + #define AF3 vec3 + #define AF4 vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uvec2 + #define AU3 uvec3 + #define AU4 uvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 ivec2 + #define ASU3 ivec3 + #define ASU4 ivec4 +//============================================================================================================================== + #define AF1_AU1(x) uintBitsToFloat(AU1(x)) + #define AF2_AU2(x) uintBitsToFloat(AU2(x)) + #define AF3_AU3(x) uintBitsToFloat(AU3(x)) + #define AF4_AU4(x) uintBitsToFloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) floatBitsToUint(AF1(x)) + #define AU2_AF2(x) floatBitsToUint(AF2(x)) + #define AU3_AF3(x) floatBitsToUint(AF3(x)) + #define AU4_AF4(x) floatBitsToUint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH1_AF1_x(AF1 a){return packHalf2x16(AF2(a,0.0));} + #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2_AF2 packHalf2x16 + #define AU1_AW2Unorm_AF2 packUnorm2x16 + #define AU1_AB4Unorm_AF4 packUnorm4x8 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF2_AH2_AU1 unpackHalf2x16 + #define AF2_AW2Unorm_AU1 unpackUnorm2x16 + #define AF4_AB4Unorm_AU1 unpackUnorm4x8 +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){return bitfieldExtract(src,ASU1(off),ASU1(bits));} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + // Proxy for V_BFI_B32 where the 'mask' is set as 'bits', 'mask=(1<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #define AB1 uint8_t + #define AB2 u8vec2 + #define AB3 u8vec3 + #define AB4 u8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASB1 int8_t + #define ASB2 i8vec2 + #define ASB3 i8vec3 + #define ASB4 i8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + AB1 AB1_x(AB1 a){return AB1(a);} + AB2 AB2_x(AB1 a){return AB2(a,a);} + AB3 AB3_x(AB1 a){return AB3(a,a,a);} + AB4 AB4_x(AB1 a){return AB4(a,a,a,a);} + #define AB1_(a) AB1_x(AB1(a)) + #define AB2_(a) AB2_x(AB1(a)) + #define AB3_(a) AB3_x(AB1(a)) + #define AB4_(a) AB4_x(AB1(a)) + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #define AH1 float16_t + #define AH2 f16vec2 + #define AH3 f16vec3 + #define AH4 f16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 u16vec2 + #define AW3 u16vec3 + #define AW4 u16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 i16vec2 + #define ASW3 i16vec3 + #define ASW4 i16vec4 +//============================================================================================================================== + #define AH2_AU1(x) unpackFloat2x16(AU1(x)) + AH4 AH4_AU2_x(AU2 x){return AH4(unpackFloat2x16(x.x),unpackFloat2x16(x.y));} + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) unpackUint2x16(AU1(x)) + #define AW4_AU2(x) unpackUint4x16(pack64(AU2(x))) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2(x) packFloat2x16(AH2(x)) + AU2 AU2_AH4_x(AH4 x){return AU2(packFloat2x16(x.xy),packFloat2x16(x.zw));} + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) packUint2x16(AW2(x)) + #define AU2_AW4(x) unpack32(packUint4x16(AW4(x))) +//============================================================================================================================== + #define AW1_AH1(x) halfBitsToUint16(AH1(x)) + #define AW2_AH2(x) halfBitsToUint16(AH2(x)) + #define AW3_AH3(x) halfBitsToUint16(AH3(x)) + #define AW4_AH4(x) halfBitsToUint16(AH4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AH1_AW1(x) uint16BitsToHalf(AW1(x)) + #define AH2_AW2(x) uint16BitsToHalf(AW2(x)) + #define AH3_AW3(x) uint16BitsToHalf(AW3(x)) + #define AH4_AW4(x) uint16BitsToHalf(AW4(x)) +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AClampH1(AH1 x,AH1 n,AH1 m){return clamp(x,n,m);} + AH2 AClampH2(AH2 x,AH2 n,AH2 m){return clamp(x,n,m);} + AH3 AClampH3(AH3 x,AH3 n,AH3 m){return clamp(x,n,m);} + AH4 AClampH4(AH4 x,AH4 n,AH4 m){return clamp(x,n,m);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFractH1(AH1 x){return fract(x);} + AH2 AFractH2(AH2 x){return fract(x);} + AH3 AFractH3(AH3 x){return fract(x);} + AH4 AFractH4(AH4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return mix(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return mix(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return mix(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of max3. + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of min3. + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return AH1_(1.0)/x;} + AH2 ARcpH2(AH2 x){return AH2_(1.0)/x;} + AH3 ARcpH3(AH3 x){return AH3_(1.0)/x;} + AH4 ARcpH4(AH4 x){return AH4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return AH1_(1.0)/sqrt(x);} + AH2 ARsqH2(AH2 x){return AH2_(1.0)/sqrt(x);} + AH3 ARsqH3(AH3 x){return AH3_(1.0)/sqrt(x);} + AH4 ARsqH4(AH4 x){return AH4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return clamp(x,AH1_(0.0),AH1_(1.0));} + AH2 ASatH2(AH2 x){return clamp(x,AH2_(0.0),AH2_(1.0));} + AH3 ASatH3(AH3 x){return clamp(x,AH3_(0.0),AH3_(1.0));} + AH4 ASatH4(AH4 x){return clamp(x,AH4_(0.0),AH4_(1.0));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #define AD1 double + #define AD2 dvec2 + #define AD3 dvec3 + #define AD4 dvec4 +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 x){return fract(x);} + AD2 AFractD2(AD2 x){return fract(x);} + AD3 AFractD3(AD3 x){return fract(x);} + AD4 AFractD4(AD4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return mix(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return mix(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return mix(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return AD1_(1.0)/x;} + AD2 ARcpD2(AD2 x){return AD2_(1.0)/x;} + AD3 ARcpD3(AD3 x){return AD3_(1.0)/x;} + AD4 ARcpD4(AD4 x){return AD4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return AD1_(1.0)/sqrt(x);} + AD2 ARsqD2(AD2 x){return AD2_(1.0)/sqrt(x);} + AD3 ARsqD3(AD3 x){return AD3_(1.0)/sqrt(x);} + AD4 ARsqD4(AD4 x){return AD4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return clamp(x,AD1_(0.0),AD1_(1.0));} + AD2 ASatD2(AD2 x){return clamp(x,AD2_(0.0),AD2_(1.0));} + AD3 ASatD3(AD3 x){return clamp(x,AD3_(0.0),AD3_(1.0));} + AD4 ASatD4(AD4 x){return clamp(x,AD4_(0.0),AD4_(1.0));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL LONG +//============================================================================================================================== + #ifdef A_LONG + #define AL1 uint64_t + #define AL2 u64vec2 + #define AL3 u64vec3 + #define AL4 u64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1 int64_t + #define ASL2 i64vec2 + #define ASL3 i64vec3 + #define ASL4 i64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AL1_AU2(x) packUint2x32(AU2(x)) + #define AU2_AL1(x) unpackUint2x32(AL1(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AL1_x(AL1 a){return AL1(a);} + AL2 AL2_x(AL1 a){return AL2(a,a);} + AL3 AL3_x(AL1 a){return AL3(a,a,a);} + AL4 AL4_x(AL1 a){return AL4(a,a,a,a);} + #define AL1_(a) AL1_x(AL1(a)) + #define AL2_(a) AL2_x(AL1(a)) + #define AL3_(a) AL3_x(AL1(a)) + #define AL4_(a) AL4_x(AL1(a)) +//============================================================================================================================== + AL1 AAbsSL1(AL1 a){return AL1(abs(ASL1(a)));} + AL2 AAbsSL2(AL2 a){return AL2(abs(ASL2(a)));} + AL3 AAbsSL3(AL3 a){return AL3(abs(ASL3(a)));} + AL4 AAbsSL4(AL4 a){return AL4(abs(ASL4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMaxSL1(AL1 a,AL1 b){return AL1(max(ASU1(a),ASU1(b)));} + AL2 AMaxSL2(AL2 a,AL2 b){return AL2(max(ASU2(a),ASU2(b)));} + AL3 AMaxSL3(AL3 a,AL3 b){return AL3(max(ASU3(a),ASU3(b)));} + AL4 AMaxSL4(AL4 a,AL4 b){return AL4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMinSL1(AL1 a,AL1 b){return AL1(min(ASU1(a),ASU1(b)));} + AL2 AMinSL2(AL2 a,AL2 b){return AL2(min(ASU2(a),ASU2(b)));} + AL3 AMinSL3(AL3 a,AL3 b){return AL3(min(ASU3(a),ASU3(b)));} + AL4 AMinSL4(AL4 a,AL4 b){return AL4(min(ASU4(a),ASU4(b)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// WAVE OPERATIONS +//============================================================================================================================== + #ifdef A_WAVE + // Where 'x' must be a compile time literal. + AF1 AWaveXorF1(AF1 v,AU1 x){return subgroupShuffleXor(v,x);} + AF2 AWaveXorF2(AF2 v,AU1 x){return subgroupShuffleXor(v,x);} + AF3 AWaveXorF3(AF3 v,AU1 x){return subgroupShuffleXor(v,x);} + AF4 AWaveXorF4(AF4 v,AU1 x){return subgroupShuffleXor(v,x);} + AU1 AWaveXorU1(AU1 v,AU1 x){return subgroupShuffleXor(v,x);} + AU2 AWaveXorU2(AU2 v,AU1 x){return subgroupShuffleXor(v,x);} + AU3 AWaveXorU3(AU3 v,AU1 x){return subgroupShuffleXor(v,x);} + AU4 AWaveXorU4(AU4 v,AU1 x){return subgroupShuffleXor(v,x);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(subgroupShuffleXor(AU1_AH2(v),x));} + AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(subgroupShuffleXor(AU2_AH4(v),x));} + AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(subgroupShuffleXor(AU1_AW2(v),x));} + AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU2(subgroupShuffleXor(AU2_AW4(v),x));} + #endif + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// HLSL +// +// +//============================================================================================================================== +#if defined(A_HLSL) && defined(A_GPU) + #ifdef A_HLSL_6_2 + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float32_t + #define AF2 float32_t2 + #define AF3 float32_t3 + #define AF4 float32_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint32_t + #define AU2 uint32_t2 + #define AU3 uint32_t3 + #define AU4 uint32_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int32_t + #define ASU2 int32_t2 + #define ASU3 int32_t3 + #define ASU4 int32_t4 + #else + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 float2 + #define AF3 float3 + #define AF4 float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uint2 + #define AU3 uint3 + #define AU4 uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 int2 + #define ASU3 int3 + #define ASU4 int4 + #endif +//============================================================================================================================== + #define AF1_AU1(x) asfloat(AU1(x)) + #define AF2_AU2(x) asfloat(AU2(x)) + #define AF3_AU3(x) asfloat(AU3(x)) + #define AF4_AU4(x) asfloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) asuint(AF1(x)) + #define AU2_AF2(x) asuint(AF2(x)) + #define AU3_AF3(x) asuint(AF3(x)) + #define AU4_AF4(x) asuint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH1_AF1_x(AF1 a){return f32tof16(a);} + #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_AF2_x(AF2 a){return f32tof16(a.x)|(f32tof16(a.y)<<16);} + #define AU1_AH2_AF2(a) AU1_AH2_AF2_x(AF2(a)) + #define AU1_AB4Unorm_AF4(x) D3DCOLORtoUBYTE4(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AF2 AF2_AH2_AU1_x(AU1 x){return AF2(f16tof32(x&0xFFFF),f16tof32(x>>16));} + #define AF2_AH2_AU1(x) AF2_AH2_AU1_x(AU1(x)) +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){AU1 mask=(1u<>off)&mask;} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + AU1 ABfiM(AU1 src,AU1 ins,AU1 bits){AU1 mask=(1u<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #ifdef A_HLSL_6_2 + #define AH1 float16_t + #define AH2 float16_t2 + #define AH3 float16_t3 + #define AH4 float16_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 uint16_t2 + #define AW3 uint16_t3 + #define AW4 uint16_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 int16_t2 + #define ASW3 int16_t3 + #define ASW4 int16_t4 + #else + #define AH1 min16float + #define AH2 min16float2 + #define AH3 min16float3 + #define AH4 min16float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 min16uint + #define AW2 min16uint2 + #define AW3 min16uint3 + #define AW4 min16uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 min16int + #define ASW2 min16int2 + #define ASW3 min16int3 + #define ASW4 min16int4 + #endif +//============================================================================================================================== + // Need to use manual unpack to get optimal execution (don't use packed types in buffers directly). + // Unpack requires this pattern: https://gpuopen.com/first-steps-implementing-fp16/ + AH2 AH2_AU1_x(AU1 x){AF2 t=f16tof32(AU2(x&0xFFFF,x>>16));return AH2(t);} + AH4 AH4_AU2_x(AU2 x){return AH4(AH2_AU1_x(x.x),AH2_AU1_x(x.y));} + AW2 AW2_AU1_x(AU1 x){AU2 t=AU2(x&0xFFFF,x>>16);return AW2(t);} + AW4 AW4_AU2_x(AU2 x){return AW4(AW2_AU1_x(x.x),AW2_AU1_x(x.y));} + #define AH2_AU1(x) AH2_AU1_x(AU1(x)) + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) AW2_AU1_x(AU1(x)) + #define AW4_AU2(x) AW4_AU2_x(AU2(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_x(AH2 x){return f32tof16(x.x)+(f32tof16(x.y)<<16);} + AU2 AU2_AH4_x(AH4 x){return AU2(AU1_AH2_x(x.xy),AU1_AH2_x(x.zw));} + AU1 AU1_AW2_x(AW2 x){return AU1(x.x)+(AU1(x.y)<<16);} + AU2 AU2_AW4_x(AW4 x){return AU2(AU1_AW2_x(x.xy),AU1_AW2_x(x.zw));} + #define AU1_AH2(x) AU1_AH2_x(AH2(x)) + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) AU1_AW2_x(AW2(x)) + #define AU2_AW4(x) AU2_AW4_x(AW4(x)) +//============================================================================================================================== + #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) + #define AW1_AH1(x) asuint16(x) + #define AW2_AH2(x) asuint16(x) + #define AW3_AH3(x) asuint16(x) + #define AW4_AH4(x) asuint16(x) + #else + #define AW1_AH1(a) AW1(f32tof16(AF1(a))) + #define AW2_AH2(a) AW2(AW1_AH1((a).x),AW1_AH1((a).y)) + #define AW3_AH3(a) AW3(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z)) + #define AW4_AH4(a) AW4(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z),AW1_AH1((a).w)) + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) + #define AH1_AW1(x) asfloat16(x) + #define AH2_AW2(x) asfloat16(x) + #define AH3_AW3(x) asfloat16(x) + #define AH4_AW4(x) asfloat16(x) + #else + #define AH1_AW1(a) AH1(f16tof32(AU1(a))) + #define AH2_AW2(a) AH2(AH1_AW1((a).x),AH1_AW1((a).y)) + #define AH3_AW3(a) AH3(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z)) + #define AH4_AW4(a) AH4(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z),AH1_AW1((a).w)) + #endif +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AClampH1(AH1 x,AH1 n,AH1 m){return max(n,min(x,m));} + AH2 AClampH2(AH2 x,AH2 n,AH2 m){return max(n,min(x,m));} + AH3 AClampH3(AH3 x,AH3 n,AH3 m){return max(n,min(x,m));} + AH4 AClampH4(AH4 x,AH4 n,AH4 m){return max(n,min(x,m));} +//------------------------------------------------------------------------------------------------------------------------------ + // V_FRACT_F16 (note DX frac() is different). + AH1 AFractH1(AH1 x){return x-floor(x);} + AH2 AFractH2(AH2 x){return x-floor(x);} + AH3 AFractH3(AH3 x){return x-floor(x);} + AH4 AFractH4(AH4 x){return x-floor(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return lerp(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return lerp(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return lerp(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return rcp(x);} + AH2 ARcpH2(AH2 x){return rcp(x);} + AH3 ARcpH3(AH3 x){return rcp(x);} + AH4 ARcpH4(AH4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return rsqrt(x);} + AH2 ARsqH2(AH2 x){return rsqrt(x);} + AH3 ARsqH3(AH3 x){return rsqrt(x);} + AH4 ARsqH4(AH4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return saturate(x);} + AH2 ASatH2(AH2 x){return saturate(x);} + AH3 ASatH3(AH3 x){return saturate(x);} + AH4 ASatH4(AH4 x){return saturate(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #ifdef A_HLSL_6_2 + #define AD1 float64_t + #define AD2 float64_t2 + #define AD3 float64_t3 + #define AD4 float64_t4 + #else + #define AD1 double + #define AD2 double2 + #define AD3 double3 + #define AD4 double4 + #endif +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 a){return a-floor(a);} + AD2 AFractD2(AD2 a){return a-floor(a);} + AD3 AFractD3(AD3 a){return a-floor(a);} + AD4 AFractD4(AD4 a){return a-floor(a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return lerp(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return lerp(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return lerp(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return rcp(x);} + AD2 ARcpD2(AD2 x){return rcp(x);} + AD3 ARcpD3(AD3 x){return rcp(x);} + AD4 ARcpD4(AD4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return rsqrt(x);} + AD2 ARsqD2(AD2 x){return rsqrt(x);} + AD3 ARsqD3(AD3 x){return rsqrt(x);} + AD4 ARsqD4(AD4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return saturate(x);} + AD2 ASatD2(AD2 x){return saturate(x);} + AD3 ASatD3(AD3 x){return saturate(x);} + AD4 ASatD4(AD4 x){return saturate(x);} + #endif +//============================================================================================================================== +// HLSL WAVE +//============================================================================================================================== + #ifdef A_WAVE + // Where 'x' must be a compile time literal. + AF1 AWaveXorF1(AF1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF2 AWaveXorF2(AF2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF3 AWaveXorF3(AF3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF4 AWaveXorF4(AF4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU1 AWaveXorU1(AU1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU2 AWaveXorU1(AU2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU3 AWaveXorU1(AU3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU4 AWaveXorU1(AU4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(WaveReadLaneAt(AU1_AH2(v),WaveGetLaneIndex()^x));} + AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(WaveReadLaneAt(AU2_AH4(v),WaveGetLaneIndex()^x));} + AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(WaveReadLaneAt(AU1_AW2(v),WaveGetLaneIndex()^x));} + AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU1(WaveReadLaneAt(AU1_AW4(v),WaveGetLaneIndex()^x));} + #endif + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU COMMON +// +// +//============================================================================================================================== +#ifdef A_GPU + // Negative and positive infinity. + #define A_INFP_F AF1_AU1(0x7f800000u) + #define A_INFN_F AF1_AU1(0xff800000u) +//------------------------------------------------------------------------------------------------------------------------------ + // Copy sign from 's' to positive 'd'. + AF1 ACpySgnF1(AF1 d,AF1 s){return AF1_AU1(AU1_AF1(d)|(AU1_AF1(s)&AU1_(0x80000000u)));} + AF2 ACpySgnF2(AF2 d,AF2 s){return AF2_AU2(AU2_AF2(d)|(AU2_AF2(s)&AU2_(0x80000000u)));} + AF3 ACpySgnF3(AF3 d,AF3 s){return AF3_AU3(AU3_AF3(d)|(AU3_AF3(s)&AU3_(0x80000000u)));} + AF4 ACpySgnF4(AF4 d,AF4 s){return AF4_AU4(AU4_AF4(d)|(AU4_AF4(s)&AU4_(0x80000000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Single operation to return (useful to create a mask to use in lerp for branch free logic), + // m=NaN := 0 + // m>=0 := 0 + // m<0 := 1 + // Uses the following useful floating point logic, + // saturate(+a*(-INF)==-INF) := 0 + // saturate( 0*(-INF)== NaN) := 0 + // saturate(-a*(-INF)==+INF) := 1 + AF1 ASignedF1(AF1 m){return ASatF1(m*AF1_(A_INFN_F));} + AF2 ASignedF2(AF2 m){return ASatF2(m*AF2_(A_INFN_F));} + AF3 ASignedF3(AF3 m){return ASatF3(m*AF3_(A_INFN_F));} + AF4 ASignedF4(AF4 m){return ASatF4(m*AF4_(A_INFN_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AGtZeroF1(AF1 m){return ASatF1(m*AF1_(A_INFP_F));} + AF2 AGtZeroF2(AF2 m){return ASatF2(m*AF2_(A_INFP_F));} + AF3 AGtZeroF3(AF3 m){return ASatF3(m*AF3_(A_INFP_F));} + AF4 AGtZeroF4(AF4 m){return ASatF4(m*AF4_(A_INFP_F));} +//============================================================================================================================== + #ifdef A_HALF + #ifdef A_HLSL_6_2 + #define A_INFP_H AH1_AW1((uint16_t)0x7c00u) + #define A_INFN_H AH1_AW1((uint16_t)0xfc00u) + #else + #define A_INFP_H AH1_AW1(0x7c00u) + #define A_INFN_H AH1_AW1(0xfc00u) + #endif + +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ACpySgnH1(AH1 d,AH1 s){return AH1_AW1(AW1_AH1(d)|(AW1_AH1(s)&AW1_(0x8000u)));} + AH2 ACpySgnH2(AH2 d,AH2 s){return AH2_AW2(AW2_AH2(d)|(AW2_AH2(s)&AW2_(0x8000u)));} + AH3 ACpySgnH3(AH3 d,AH3 s){return AH3_AW3(AW3_AH3(d)|(AW3_AH3(s)&AW3_(0x8000u)));} + AH4 ACpySgnH4(AH4 d,AH4 s){return AH4_AW4(AW4_AH4(d)|(AW4_AH4(s)&AW4_(0x8000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASignedH1(AH1 m){return ASatH1(m*AH1_(A_INFN_H));} + AH2 ASignedH2(AH2 m){return ASatH2(m*AH2_(A_INFN_H));} + AH3 ASignedH3(AH3 m){return ASatH3(m*AH3_(A_INFN_H));} + AH4 ASignedH4(AH4 m){return ASatH4(m*AH4_(A_INFN_H));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AGtZeroH1(AH1 m){return ASatH1(m*AH1_(A_INFP_H));} + AH2 AGtZeroH2(AH2 m){return ASatH2(m*AH2_(A_INFP_H));} + AH3 AGtZeroH3(AH3 m){return ASatH3(m*AH3_(A_INFP_H));} + AH4 AGtZeroH4(AH4 m){return ASatH4(m*AH4_(A_INFP_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [FIS] FLOAT INTEGER SORTABLE +//------------------------------------------------------------------------------------------------------------------------------ +// Float to integer sortable. +// - If sign bit=0, flip the sign bit (positives). +// - If sign bit=1, flip all bits (negatives). +// Integer sortable to float. +// - If sign bit=1, flip the sign bit (positives). +// - If sign bit=0, flip all bits (negatives). +// Has nice side effects. +// - Larger integers are more positive values. +// - Float zero is mapped to center of integers (so clear to integer zero is a nice default for atomic max usage). +// Burns 3 ops for conversion {shift,or,xor}. +//============================================================================================================================== + AU1 AFisToU1(AU1 x){return x^(( AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} + AU1 AFisFromU1(AU1 x){return x^((~AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} +//------------------------------------------------------------------------------------------------------------------------------ + // Just adjust high 16-bit value (useful when upper part of 32-bit word is a 16-bit float value). + AU1 AFisToHiU1(AU1 x){return x^(( AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} + AU1 AFisFromHiU1(AU1 x){return x^((~AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AW1 AFisToW1(AW1 x){return x^(( AShrSW1(x,AW1_(15)))|AW1_(0x8000));} + AW1 AFisFromW1(AW1 x){return x^((~AShrSW1(x,AW1_(15)))|AW1_(0x8000));} +//------------------------------------------------------------------------------------------------------------------------------ + AW2 AFisToW2(AW2 x){return x^(( AShrSW2(x,AW2_(15)))|AW2_(0x8000));} + AW2 AFisFromW2(AW2 x){return x^((~AShrSW2(x,AW2_(15)))|AW2_(0x8000));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [PERM] V_PERM_B32 +//------------------------------------------------------------------------------------------------------------------------------ +// Support for V_PERM_B32 started in the 3rd generation of GCN. +//------------------------------------------------------------------------------------------------------------------------------ +// yyyyxxxx - The 'i' input. +// 76543210 +// ======== +// HGFEDCBA - Naming on permutation. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure compiler optimizes this. +//============================================================================================================================== + #ifdef A_HALF + AU1 APerm0E0A(AU2 i){return((i.x )&0xffu)|((i.y<<16)&0xff0000u);} + AU1 APerm0F0B(AU2 i){return((i.x>> 8)&0xffu)|((i.y<< 8)&0xff0000u);} + AU1 APerm0G0C(AU2 i){return((i.x>>16)&0xffu)|((i.y )&0xff0000u);} + AU1 APerm0H0D(AU2 i){return((i.x>>24)&0xffu)|((i.y>> 8)&0xff0000u);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 APermHGFA(AU2 i){return((i.x )&0x000000ffu)|(i.y&0xffffff00u);} + AU1 APermHGFC(AU2 i){return((i.x>>16)&0x000000ffu)|(i.y&0xffffff00u);} + AU1 APermHGAE(AU2 i){return((i.x<< 8)&0x0000ff00u)|(i.y&0xffff00ffu);} + AU1 APermHGCE(AU2 i){return((i.x>> 8)&0x0000ff00u)|(i.y&0xffff00ffu);} + AU1 APermHAFE(AU2 i){return((i.x<<16)&0x00ff0000u)|(i.y&0xff00ffffu);} + AU1 APermHCFE(AU2 i){return((i.x )&0x00ff0000u)|(i.y&0xff00ffffu);} + AU1 APermAGFE(AU2 i){return((i.x<<24)&0xff000000u)|(i.y&0x00ffffffu);} + AU1 APermCGFE(AU2 i){return((i.x<< 8)&0xff000000u)|(i.y&0x00ffffffu);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 APermGCEA(AU2 i){return((i.x)&0x00ff00ffu)|((i.y<<8)&0xff00ff00u);} + AU1 APermGECA(AU2 i){return(((i.x)&0xffu)|((i.x>>8)&0xff00u)|((i.y<<16)&0xff0000u)|((i.y<<8)&0xff000000u));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [BUC] BYTE UNSIGNED CONVERSION +//------------------------------------------------------------------------------------------------------------------------------ +// Designed to use the optimal conversion, enables the scaling to possibly be factored into other computation. +// Works on a range of {0 to A_BUC_<32,16>}, for <32-bit, and 16-bit> respectively. +//------------------------------------------------------------------------------------------------------------------------------ +// OPCODE NOTES +// ============ +// GCN does not do UNORM or SNORM for bytes in opcodes. +// - V_CVT_F32_UBYTE{0,1,2,3} - Unsigned byte to float. +// - V_CVT_PKACC_U8_F32 - Float to unsigned byte (does bit-field insert into 32-bit integer). +// V_PERM_B32 does byte packing with ability to zero fill bytes as well. +// - Can pull out byte values from two sources, and zero fill upper 8-bits of packed hi and lo. +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U1() - Designed for V_CVT_F32_UBYTE* and V_CVT_PKACCUM_U8_F32 ops. +// ==== ===== +// 0 : 0 +// 1 : 1 +// ... +// 255 : 255 +// : 256 (just outside the encoding range) +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. +// ==== ===== +// 0 : 0 +// 1 : 1/512 +// 2 : 1/256 +// ... +// 64 : 1/8 +// 128 : 1/4 +// 255 : 255/512 +// : 1/2 (just outside the encoding range) +//------------------------------------------------------------------------------------------------------------------------------ +// OPTIMAL IMPLEMENTATIONS ON AMD ARCHITECTURES +// ============================================ +// r=ABuc0FromU1(i) +// V_CVT_F32_UBYTE0 r,i +// -------------------------------------------- +// r=ABuc0ToU1(d,i) +// V_CVT_PKACCUM_U8_F32 r,i,0,d +// -------------------------------------------- +// d=ABuc0FromU2(i) +// Where 'k0' is an SGPR with 0x0E0A +// Where 'k1' is an SGPR with {32768.0} packed into the lower 16-bits +// V_PERM_B32 d,i.x,i.y,k0 +// V_PK_FMA_F16 d,d,k1.x,0 +// -------------------------------------------- +// r=ABuc0ToU2(d,i) +// Where 'k0' is an SGPR with {1.0/32768.0} packed into the lower 16-bits +// Where 'k1' is an SGPR with 0x???? +// Where 'k2' is an SGPR with 0x???? +// V_PK_FMA_F16 i,i,k0.x,0 +// V_PERM_B32 r.x,i,i,k1 +// V_PERM_B32 r.y,i,i,k2 +//============================================================================================================================== + // Peak range for 32-bit and 16-bit operations. + #define A_BUC_32 (255.0) + #define A_BUC_16 (255.0/512.0) +//============================================================================================================================== + #if 1 + // Designed to be one V_CVT_PKACCUM_U8_F32. + // The extra min is required to pattern match to V_CVT_PKACCUM_U8_F32. + AU1 ABuc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i),255u) )&(0x000000ffu));} + AU1 ABuc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i),255u)<< 8)&(0x0000ff00u));} + AU1 ABuc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i),255u)<<16)&(0x00ff0000u));} + AU1 ABuc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i),255u)<<24)&(0xff000000u));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed to be one V_CVT_F32_UBYTE*. + AF1 ABuc0FromU1(AU1 i){return AF1((i )&255u);} + AF1 ABuc1FromU1(AU1 i){return AF1((i>> 8)&255u);} + AF1 ABuc2FromU1(AU1 i){return AF1((i>>16)&255u);} + AF1 ABuc3FromU1(AU1 i){return AF1((i>>24)&255u);} + #endif +//============================================================================================================================== + #ifdef A_HALF + // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. + AW2 ABuc01ToW2(AH2 x,AH2 y){x*=AH2_(1.0/32768.0);y*=AH2_(1.0/32768.0); + return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed for 3 ops to do SOA to AOS and conversion. + AU2 ABuc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABuc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABuc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABuc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed for 2 ops to do both AOS to SOA, and conversion. + AH2 ABuc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0);} + AH2 ABuc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0);} + AH2 ABuc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0);} + AH2 ABuc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [BSC] BYTE SIGNED CONVERSION +//------------------------------------------------------------------------------------------------------------------------------ +// Similar to [BUC]. +// Works on a range of {-/+ A_BSC_<32,16>}, for <32-bit, and 16-bit> respectively. +//------------------------------------------------------------------------------------------------------------------------------ +// ENCODING (without zero-based encoding) +// ======== +// 0 = unused (can be used to mean something else) +// 1 = lowest value +// 128 = exact zero center (zero based encoding +// 255 = highest value +//------------------------------------------------------------------------------------------------------------------------------ +// Zero-based [Zb] flips the MSB bit of the byte (making 128 "exact zero" actually zero). +// This is useful if there is a desire for cleared values to decode as zero. +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABsc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. +// ==== ===== +// 0 : -127/512 (unused) +// 1 : -126/512 +// 2 : -125/512 +// ... +// 128 : 0 +// ... +// 255 : 127/512 +// : 1/4 (just outside the encoding range) +//============================================================================================================================== + // Peak range for 32-bit and 16-bit operations. + #define A_BSC_32 (127.0) + #define A_BSC_16 (127.0/512.0) +//============================================================================================================================== + #if 1 + AU1 ABsc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i+128.0),255u) )&(0x000000ffu));} + AU1 ABsc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i+128.0),255u)<< 8)&(0x0000ff00u));} + AU1 ABsc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i+128.0),255u)<<16)&(0x00ff0000u));} + AU1 ABsc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i+128.0),255u)<<24)&(0xff000000u));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABsc0ToZbU1(AU1 d,AF1 i){return ((d&0xffffff00u)|((min(AU1(trunc(i)+128.0),255u) )&(0x000000ffu)))^0x00000080u;} + AU1 ABsc1ToZbU1(AU1 d,AF1 i){return ((d&0xffff00ffu)|((min(AU1(trunc(i)+128.0),255u)<< 8)&(0x0000ff00u)))^0x00008000u;} + AU1 ABsc2ToZbU1(AU1 d,AF1 i){return ((d&0xff00ffffu)|((min(AU1(trunc(i)+128.0),255u)<<16)&(0x00ff0000u)))^0x00800000u;} + AU1 ABsc3ToZbU1(AU1 d,AF1 i){return ((d&0x00ffffffu)|((min(AU1(trunc(i)+128.0),255u)<<24)&(0xff000000u)))^0x80000000u;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ABsc0FromU1(AU1 i){return AF1((i )&255u)-128.0;} + AF1 ABsc1FromU1(AU1 i){return AF1((i>> 8)&255u)-128.0;} + AF1 ABsc2FromU1(AU1 i){return AF1((i>>16)&255u)-128.0;} + AF1 ABsc3FromU1(AU1 i){return AF1((i>>24)&255u)-128.0;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ABsc0FromZbU1(AU1 i){return AF1(((i )&255u)^0x80u)-128.0;} + AF1 ABsc1FromZbU1(AU1 i){return AF1(((i>> 8)&255u)^0x80u)-128.0;} + AF1 ABsc2FromZbU1(AU1 i){return AF1(((i>>16)&255u)^0x80u)-128.0;} + AF1 ABsc3FromZbU1(AU1 i){return AF1(((i>>24)&255u)^0x80u)-128.0;} + #endif +//============================================================================================================================== + #ifdef A_HALF + // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. + AW2 ABsc01ToW2(AH2 x,AH2 y){x=x*AH2_(1.0/32768.0)+AH2_(0.25/32768.0);y=y*AH2_(1.0/32768.0)+AH2_(0.25/32768.0); + return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} +//------------------------------------------------------------------------------------------------------------------------------ + AU2 ABsc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABsc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABsc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABsc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU2 ABsc0ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABsc1ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABsc2ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABsc3ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 ABsc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0)-AH2_(0.25);} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 ABsc0FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc1FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc2FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc3FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These support only positive inputs. +// Did not see value yet in specialization for range. +// Using quick testing, ended up mostly getting the same "best" approximation for various ranges. +// With hardware that can co-execute transcendentals, the value in approximations could be less than expected. +// However from a latency perspective, if execution of a transcendental is 4 clk, with no packed support, -> 8 clk total. +// And co-execution would require a compiler interleaving a lot of independent work for packed usage. +//------------------------------------------------------------------------------------------------------------------------------ +// The one Newton Raphson iteration form of rsq() was skipped (requires 6 ops total). +// Same with sqrt(), as this could be x*rsq() (7 ops). +//============================================================================================================================== + #ifdef A_HALF + // Minimize squared error across full positive range, 2 ops. + // The 0x1de2 based approximation maps {0 to 1} input maps to < 1 output. + AH1 APrxLoSqrtH1(AH1 a){return AH1_AW1((AW1_AH1(a)>>AW1_(1))+AW1_(0x1de2));} + AH2 APrxLoSqrtH2(AH2 a){return AH2_AW2((AW2_AH2(a)>>AW2_(1))+AW2_(0x1de2));} + AH3 APrxLoSqrtH3(AH3 a){return AH3_AW3((AW3_AH3(a)>>AW3_(1))+AW3_(0x1de2));} + AH4 APrxLoSqrtH4(AH4 a){return AH4_AW4((AW4_AH4(a)>>AW4_(1))+AW4_(0x1de2));} +//------------------------------------------------------------------------------------------------------------------------------ + // Lower precision estimation, 1 op. + // Minimize squared error across {smallest normal to 16384.0}. + AH1 APrxLoRcpH1(AH1 a){return AH1_AW1(AW1_(0x7784)-AW1_AH1(a));} + AH2 APrxLoRcpH2(AH2 a){return AH2_AW2(AW2_(0x7784)-AW2_AH2(a));} + AH3 APrxLoRcpH3(AH3 a){return AH3_AW3(AW3_(0x7784)-AW3_AH3(a));} + AH4 APrxLoRcpH4(AH4 a){return AH4_AW4(AW4_(0x7784)-AW4_AH4(a));} +//------------------------------------------------------------------------------------------------------------------------------ + // Medium precision estimation, one Newton Raphson iteration, 3 ops. + AH1 APrxMedRcpH1(AH1 a){AH1 b=AH1_AW1(AW1_(0x778d)-AW1_AH1(a));return b*(-b*a+AH1_(2.0));} + AH2 APrxMedRcpH2(AH2 a){AH2 b=AH2_AW2(AW2_(0x778d)-AW2_AH2(a));return b*(-b*a+AH2_(2.0));} + AH3 APrxMedRcpH3(AH3 a){AH3 b=AH3_AW3(AW3_(0x778d)-AW3_AH3(a));return b*(-b*a+AH3_(2.0));} + AH4 APrxMedRcpH4(AH4 a){AH4 b=AH4_AW4(AW4_(0x778d)-AW4_AH4(a));return b*(-b*a+AH4_(2.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // Minimize squared error across {smallest normal to 16384.0}, 2 ops. + AH1 APrxLoRsqH1(AH1 a){return AH1_AW1(AW1_(0x59a3)-(AW1_AH1(a)>>AW1_(1)));} + AH2 APrxLoRsqH2(AH2 a){return AH2_AW2(AW2_(0x59a3)-(AW2_AH2(a)>>AW2_(1)));} + AH3 APrxLoRsqH3(AH3 a){return AH3_AW3(AW3_(0x59a3)-(AW3_AH3(a)>>AW3_(1)));} + AH4 APrxLoRsqH4(AH4 a){return AH4_AW4(AW4_(0x59a3)-(AW4_AH4(a)>>AW4_(1)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// FLOAT APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// Michal Drobot has an excellent presentation on these: "Low Level Optimizations For GCN", +// - Idea dates back to SGI, then to Quake 3, etc. +// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf +// - sqrt(x)=rsqrt(x)*x +// - rcp(x)=rsqrt(x)*rsqrt(x) for positive x +// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h +//------------------------------------------------------------------------------------------------------------------------------ +// These below are from perhaps less complete searching for optimal. +// Used FP16 normal range for testing with +4096 32-bit step size for sampling error. +// So these match up well with the half approximations. +//============================================================================================================================== + AF1 APrxLoSqrtF1(AF1 a){return AF1_AU1((AU1_AF1(a)>>AU1_(1))+AU1_(0x1fbc4639));} + AF1 APrxLoRcpF1(AF1 a){return AF1_AU1(AU1_(0x7ef07ebb)-AU1_AF1(a));} + AF1 APrxMedRcpF1(AF1 a){AF1 b=AF1_AU1(AU1_(0x7ef19fff)-AU1_AF1(a));return b*(-b*a+AF1_(2.0));} + AF1 APrxLoRsqF1(AF1 a){return AF1_AU1(AU1_(0x5f347d74)-(AU1_AF1(a)>>AU1_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 APrxLoSqrtF2(AF2 a){return AF2_AU2((AU2_AF2(a)>>AU2_(1))+AU2_(0x1fbc4639));} + AF2 APrxLoRcpF2(AF2 a){return AF2_AU2(AU2_(0x7ef07ebb)-AU2_AF2(a));} + AF2 APrxMedRcpF2(AF2 a){AF2 b=AF2_AU2(AU2_(0x7ef19fff)-AU2_AF2(a));return b*(-b*a+AF2_(2.0));} + AF2 APrxLoRsqF2(AF2 a){return AF2_AU2(AU2_(0x5f347d74)-(AU2_AF2(a)>>AU2_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF3 APrxLoSqrtF3(AF3 a){return AF3_AU3((AU3_AF3(a)>>AU3_(1))+AU3_(0x1fbc4639));} + AF3 APrxLoRcpF3(AF3 a){return AF3_AU3(AU3_(0x7ef07ebb)-AU3_AF3(a));} + AF3 APrxMedRcpF3(AF3 a){AF3 b=AF3_AU3(AU3_(0x7ef19fff)-AU3_AF3(a));return b*(-b*a+AF3_(2.0));} + AF3 APrxLoRsqF3(AF3 a){return AF3_AU3(AU3_(0x5f347d74)-(AU3_AF3(a)>>AU3_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF4 APrxLoSqrtF4(AF4 a){return AF4_AU4((AU4_AF4(a)>>AU4_(1))+AU4_(0x1fbc4639));} + AF4 APrxLoRcpF4(AF4 a){return AF4_AU4(AU4_(0x7ef07ebb)-AU4_AF4(a));} + AF4 APrxMedRcpF4(AF4 a){AF4 b=AF4_AU4(AU4_(0x7ef19fff)-AU4_AF4(a));return b*(-b*a+AF4_(2.0));} + AF4 APrxLoRsqF4(AF4 a){return AF4_AU4(AU4_(0x5f347d74)-(AU4_AF4(a)>>AU4_(1)));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PQ APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// PQ is very close to x^(1/8). The functions below Use the fast float approximation method to do +// PQ<~>Gamma2 (4th power and fast 4th root) and PQ<~>Linear (8th power and fast 8th root). Maximum error is ~0.2%. +//============================================================================================================================== +// Helpers + AF1 Quart(AF1 a) { a = a * a; return a * a;} + AF1 Oct(AF1 a) { a = a * a; a = a * a; return a * a; } + AF2 Quart(AF2 a) { a = a * a; return a * a; } + AF2 Oct(AF2 a) { a = a * a; a = a * a; return a * a; } + AF3 Quart(AF3 a) { a = a * a; return a * a; } + AF3 Oct(AF3 a) { a = a * a; a = a * a; return a * a; } + AF4 Quart(AF4 a) { a = a * a; return a * a; } + AF4 Oct(AF4 a) { a = a * a; a = a * a; return a * a; } + //------------------------------------------------------------------------------------------------------------------------------ + AF1 APrxPQToGamma2(AF1 a) { return Quart(a); } + AF1 APrxPQToLinear(AF1 a) { return Oct(a); } + AF1 APrxLoGamma2ToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); } + AF1 APrxMedGamma2ToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); AF1 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF1 APrxHighGamma2ToPQ(AF1 a) { return sqrt(sqrt(a)); } + AF1 APrxLoLinearToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); } + AF1 APrxMedLinearToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); AF1 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF1 APrxHighLinearToPQ(AF1 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF2 APrxPQToGamma2(AF2 a) { return Quart(a); } + AF2 APrxPQToLinear(AF2 a) { return Oct(a); } + AF2 APrxLoGamma2ToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); } + AF2 APrxMedGamma2ToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); AF2 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF2 APrxHighGamma2ToPQ(AF2 a) { return sqrt(sqrt(a)); } + AF2 APrxLoLinearToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); } + AF2 APrxMedLinearToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); AF2 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF2 APrxHighLinearToPQ(AF2 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF3 APrxPQToGamma2(AF3 a) { return Quart(a); } + AF3 APrxPQToLinear(AF3 a) { return Oct(a); } + AF3 APrxLoGamma2ToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); } + AF3 APrxMedGamma2ToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); AF3 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF3 APrxHighGamma2ToPQ(AF3 a) { return sqrt(sqrt(a)); } + AF3 APrxLoLinearToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); } + AF3 APrxMedLinearToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); AF3 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF3 APrxHighLinearToPQ(AF3 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF4 APrxPQToGamma2(AF4 a) { return Quart(a); } + AF4 APrxPQToLinear(AF4 a) { return Oct(a); } + AF4 APrxLoGamma2ToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); } + AF4 APrxMedGamma2ToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); AF4 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF4 APrxHighGamma2ToPQ(AF4 a) { return sqrt(sqrt(a)); } + AF4 APrxLoLinearToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); } + AF4 APrxMedLinearToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); AF4 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF4 APrxHighLinearToPQ(AF4 a) { return sqrt(sqrt(sqrt(a))); } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PARABOLIC SIN & COS +//------------------------------------------------------------------------------------------------------------------------------ +// Approximate answers to transcendental questions. +//------------------------------------------------------------------------------------------------------------------------------ +//============================================================================================================================== + #if 1 + // Valid input range is {-1 to 1} representing {0 to 2 pi}. + // Output range is {-1/4 to 1/4} representing {-1 to 1}. + AF1 APSinF1(AF1 x){return x*abs(x)-x;} // MAD. + AF2 APSinF2(AF2 x){return x*abs(x)-x;} + AF1 APCosF1(AF1 x){x=AFractF1(x*AF1_(0.5)+AF1_(0.75));x=x*AF1_(2.0)-AF1_(1.0);return APSinF1(x);} // 3x MAD, FRACT + AF2 APCosF2(AF2 x){x=AFractF2(x*AF2_(0.5)+AF2_(0.75));x=x*AF2_(2.0)-AF2_(1.0);return APSinF2(x);} + AF2 APSinCosF1(AF1 x){AF1 y=AFractF1(x*AF1_(0.5)+AF1_(0.75));y=y*AF1_(2.0)-AF1_(1.0);return APSinF2(AF2(x,y));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + // For a packed {sin,cos} pair, + // - Native takes 16 clocks and 4 issue slots (no packed transcendentals). + // - Parabolic takes 8 clocks and 8 issue slots (only fract is non-packed). + AH1 APSinH1(AH1 x){return x*abs(x)-x;} + AH2 APSinH2(AH2 x){return x*abs(x)-x;} // AND,FMA + AH1 APCosH1(AH1 x){x=AFractH1(x*AH1_(0.5)+AH1_(0.75));x=x*AH1_(2.0)-AH1_(1.0);return APSinH1(x);} + AH2 APCosH2(AH2 x){x=AFractH2(x*AH2_(0.5)+AH2_(0.75));x=x*AH2_(2.0)-AH2_(1.0);return APSinH2(x);} // 3x FMA, 2xFRACT, AND + AH2 APSinCosH1(AH1 x){AH1 y=AFractH1(x*AH1_(0.5)+AH1_(0.75));y=y*AH1_(2.0)-AH1_(1.0);return APSinH2(AH2(x,y));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [ZOL] ZERO ONE LOGIC +//------------------------------------------------------------------------------------------------------------------------------ +// Conditional free logic designed for easy 16-bit packing, and backwards porting to 32-bit. +//------------------------------------------------------------------------------------------------------------------------------ +// 0 := false +// 1 := true +//------------------------------------------------------------------------------------------------------------------------------ +// AndNot(x,y) -> !(x&y) .... One op. +// AndOr(x,y,z) -> (x&y)|z ... One op. +// GtZero(x) -> x>0.0 ..... One op. +// Sel(x,y,z) -> x?y:z ..... Two ops, has no precision loss. +// Signed(x) -> x<0.0 ..... One op. +// ZeroPass(x,y) -> x?0:y ..... Two ops, 'y' is a pass through safe for aliasing as integer. +//------------------------------------------------------------------------------------------------------------------------------ +// OPTIMIZATION NOTES +// ================== +// - On Vega to use 2 constants in a packed op, pass in as one AW2 or one AH2 'k.xy' and use as 'k.xx' and 'k.yy'. +// For example 'a.xy*k.xx+k.yy'. +//============================================================================================================================== + #if 1 + AU1 AZolAndU1(AU1 x,AU1 y){return min(x,y);} + AU2 AZolAndU2(AU2 x,AU2 y){return min(x,y);} + AU3 AZolAndU3(AU3 x,AU3 y){return min(x,y);} + AU4 AZolAndU4(AU4 x,AU4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AZolNotU1(AU1 x){return x^AU1_(1);} + AU2 AZolNotU2(AU2 x){return x^AU2_(1);} + AU3 AZolNotU3(AU3 x){return x^AU3_(1);} + AU4 AZolNotU4(AU4 x){return x^AU4_(1);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AZolOrU1(AU1 x,AU1 y){return max(x,y);} + AU2 AZolOrU2(AU2 x,AU2 y){return max(x,y);} + AU3 AZolOrU3(AU3 x,AU3 y){return max(x,y);} + AU4 AZolOrU4(AU4 x,AU4 y){return max(x,y);} +//============================================================================================================================== + AU1 AZolF1ToU1(AF1 x){return AU1(x);} + AU2 AZolF2ToU2(AF2 x){return AU2(x);} + AU3 AZolF3ToU3(AF3 x){return AU3(x);} + AU4 AZolF4ToU4(AF4 x){return AU4(x);} +//------------------------------------------------------------------------------------------------------------------------------ + // 2 ops, denormals don't work in 32-bit on PC (and if they are enabled, OMOD is disabled). + AU1 AZolNotF1ToU1(AF1 x){return AU1(AF1_(1.0)-x);} + AU2 AZolNotF2ToU2(AF2 x){return AU2(AF2_(1.0)-x);} + AU3 AZolNotF3ToU3(AF3 x){return AU3(AF3_(1.0)-x);} + AU4 AZolNotF4ToU4(AF4 x){return AU4(AF4_(1.0)-x);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolU1ToF1(AU1 x){return AF1(x);} + AF2 AZolU2ToF2(AU2 x){return AF2(x);} + AF3 AZolU3ToF3(AU3 x){return AF3(x);} + AF4 AZolU4ToF4(AU4 x){return AF4(x);} +//============================================================================================================================== + AF1 AZolAndF1(AF1 x,AF1 y){return min(x,y);} + AF2 AZolAndF2(AF2 x,AF2 y){return min(x,y);} + AF3 AZolAndF3(AF3 x,AF3 y){return min(x,y);} + AF4 AZolAndF4(AF4 x,AF4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ASolAndNotF1(AF1 x,AF1 y){return (-x)*y+AF1_(1.0);} + AF2 ASolAndNotF2(AF2 x,AF2 y){return (-x)*y+AF2_(1.0);} + AF3 ASolAndNotF3(AF3 x,AF3 y){return (-x)*y+AF3_(1.0);} + AF4 ASolAndNotF4(AF4 x,AF4 y){return (-x)*y+AF4_(1.0);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolAndOrF1(AF1 x,AF1 y,AF1 z){return ASatF1(x*y+z);} + AF2 AZolAndOrF2(AF2 x,AF2 y,AF2 z){return ASatF2(x*y+z);} + AF3 AZolAndOrF3(AF3 x,AF3 y,AF3 z){return ASatF3(x*y+z);} + AF4 AZolAndOrF4(AF4 x,AF4 y,AF4 z){return ASatF4(x*y+z);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolGtZeroF1(AF1 x){return ASatF1(x*AF1_(A_INFP_F));} + AF2 AZolGtZeroF2(AF2 x){return ASatF2(x*AF2_(A_INFP_F));} + AF3 AZolGtZeroF3(AF3 x){return ASatF3(x*AF3_(A_INFP_F));} + AF4 AZolGtZeroF4(AF4 x){return ASatF4(x*AF4_(A_INFP_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolNotF1(AF1 x){return AF1_(1.0)-x;} + AF2 AZolNotF2(AF2 x){return AF2_(1.0)-x;} + AF3 AZolNotF3(AF3 x){return AF3_(1.0)-x;} + AF4 AZolNotF4(AF4 x){return AF4_(1.0)-x;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolOrF1(AF1 x,AF1 y){return max(x,y);} + AF2 AZolOrF2(AF2 x,AF2 y){return max(x,y);} + AF3 AZolOrF3(AF3 x,AF3 y){return max(x,y);} + AF4 AZolOrF4(AF4 x,AF4 y){return max(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolSelF1(AF1 x,AF1 y,AF1 z){AF1 r=(-x)*z+z;return x*y+r;} + AF2 AZolSelF2(AF2 x,AF2 y,AF2 z){AF2 r=(-x)*z+z;return x*y+r;} + AF3 AZolSelF3(AF3 x,AF3 y,AF3 z){AF3 r=(-x)*z+z;return x*y+r;} + AF4 AZolSelF4(AF4 x,AF4 y,AF4 z){AF4 r=(-x)*z+z;return x*y+r;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolSignedF1(AF1 x){return ASatF1(x*AF1_(A_INFN_F));} + AF2 AZolSignedF2(AF2 x){return ASatF2(x*AF2_(A_INFN_F));} + AF3 AZolSignedF3(AF3 x){return ASatF3(x*AF3_(A_INFN_F));} + AF4 AZolSignedF4(AF4 x){return ASatF4(x*AF4_(A_INFN_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolZeroPassF1(AF1 x,AF1 y){return AF1_AU1((AU1_AF1(x)!=AU1_(0))?AU1_(0):AU1_AF1(y));} + AF2 AZolZeroPassF2(AF2 x,AF2 y){return AF2_AU2((AU2_AF2(x)!=AU2_(0))?AU2_(0):AU2_AF2(y));} + AF3 AZolZeroPassF3(AF3 x,AF3 y){return AF3_AU3((AU3_AF3(x)!=AU3_(0))?AU3_(0):AU3_AF3(y));} + AF4 AZolZeroPassF4(AF4 x,AF4 y){return AF4_AU4((AU4_AF4(x)!=AU4_(0))?AU4_(0):AU4_AF4(y));} + #endif +//============================================================================================================================== + #ifdef A_HALF + AW1 AZolAndW1(AW1 x,AW1 y){return min(x,y);} + AW2 AZolAndW2(AW2 x,AW2 y){return min(x,y);} + AW3 AZolAndW3(AW3 x,AW3 y){return min(x,y);} + AW4 AZolAndW4(AW4 x,AW4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AZolNotW1(AW1 x){return x^AW1_(1);} + AW2 AZolNotW2(AW2 x){return x^AW2_(1);} + AW3 AZolNotW3(AW3 x){return x^AW3_(1);} + AW4 AZolNotW4(AW4 x){return x^AW4_(1);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AZolOrW1(AW1 x,AW1 y){return max(x,y);} + AW2 AZolOrW2(AW2 x,AW2 y){return max(x,y);} + AW3 AZolOrW3(AW3 x,AW3 y){return max(x,y);} + AW4 AZolOrW4(AW4 x,AW4 y){return max(x,y);} +//============================================================================================================================== + // Uses denormal trick. + AW1 AZolH1ToW1(AH1 x){return AW1_AH1(x*AH1_AW1(AW1_(1)));} + AW2 AZolH2ToW2(AH2 x){return AW2_AH2(x*AH2_AW2(AW2_(1)));} + AW3 AZolH3ToW3(AH3 x){return AW3_AH3(x*AH3_AW3(AW3_(1)));} + AW4 AZolH4ToW4(AH4 x){return AW4_AH4(x*AH4_AW4(AW4_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + // AMD arch lacks a packed conversion opcode. + AH1 AZolW1ToH1(AW1 x){return AH1_AW1(x*AW1_AH1(AH1_(1.0)));} + AH2 AZolW2ToH2(AW2 x){return AH2_AW2(x*AW2_AH2(AH2_(1.0)));} + AH3 AZolW1ToH3(AW3 x){return AH3_AW3(x*AW3_AH3(AH3_(1.0)));} + AH4 AZolW2ToH4(AW4 x){return AH4_AW4(x*AW4_AH4(AH4_(1.0)));} +//============================================================================================================================== + AH1 AZolAndH1(AH1 x,AH1 y){return min(x,y);} + AH2 AZolAndH2(AH2 x,AH2 y){return min(x,y);} + AH3 AZolAndH3(AH3 x,AH3 y){return min(x,y);} + AH4 AZolAndH4(AH4 x,AH4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASolAndNotH1(AH1 x,AH1 y){return (-x)*y+AH1_(1.0);} + AH2 ASolAndNotH2(AH2 x,AH2 y){return (-x)*y+AH2_(1.0);} + AH3 ASolAndNotH3(AH3 x,AH3 y){return (-x)*y+AH3_(1.0);} + AH4 ASolAndNotH4(AH4 x,AH4 y){return (-x)*y+AH4_(1.0);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolAndOrH1(AH1 x,AH1 y,AH1 z){return ASatH1(x*y+z);} + AH2 AZolAndOrH2(AH2 x,AH2 y,AH2 z){return ASatH2(x*y+z);} + AH3 AZolAndOrH3(AH3 x,AH3 y,AH3 z){return ASatH3(x*y+z);} + AH4 AZolAndOrH4(AH4 x,AH4 y,AH4 z){return ASatH4(x*y+z);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolGtZeroH1(AH1 x){return ASatH1(x*AH1_(A_INFP_H));} + AH2 AZolGtZeroH2(AH2 x){return ASatH2(x*AH2_(A_INFP_H));} + AH3 AZolGtZeroH3(AH3 x){return ASatH3(x*AH3_(A_INFP_H));} + AH4 AZolGtZeroH4(AH4 x){return ASatH4(x*AH4_(A_INFP_H));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolNotH1(AH1 x){return AH1_(1.0)-x;} + AH2 AZolNotH2(AH2 x){return AH2_(1.0)-x;} + AH3 AZolNotH3(AH3 x){return AH3_(1.0)-x;} + AH4 AZolNotH4(AH4 x){return AH4_(1.0)-x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolOrH1(AH1 x,AH1 y){return max(x,y);} + AH2 AZolOrH2(AH2 x,AH2 y){return max(x,y);} + AH3 AZolOrH3(AH3 x,AH3 y){return max(x,y);} + AH4 AZolOrH4(AH4 x,AH4 y){return max(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolSelH1(AH1 x,AH1 y,AH1 z){AH1 r=(-x)*z+z;return x*y+r;} + AH2 AZolSelH2(AH2 x,AH2 y,AH2 z){AH2 r=(-x)*z+z;return x*y+r;} + AH3 AZolSelH3(AH3 x,AH3 y,AH3 z){AH3 r=(-x)*z+z;return x*y+r;} + AH4 AZolSelH4(AH4 x,AH4 y,AH4 z){AH4 r=(-x)*z+z;return x*y+r;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolSignedH1(AH1 x){return ASatH1(x*AH1_(A_INFN_H));} + AH2 AZolSignedH2(AH2 x){return ASatH2(x*AH2_(A_INFN_H));} + AH3 AZolSignedH3(AH3 x){return ASatH3(x*AH3_(A_INFN_H));} + AH4 AZolSignedH4(AH4 x){return ASatH4(x*AH4_(A_INFN_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COLOR CONVERSIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These are all linear to/from some other space (where 'linear' has been shortened out of the function name). +// So 'ToGamma' is 'LinearToGamma', and 'FromGamma' is 'LinearFromGamma'. +// These are branch free implementations. +// The AToSrgbF1() function is useful for stores for compute shaders for GPUs without hardware linear->sRGB store conversion. +//------------------------------------------------------------------------------------------------------------------------------ +// TRANSFER FUNCTIONS +// ================== +// 709 ..... Rec709 used for some HDTVs +// Gamma ... Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native +// Pq ...... PQ native for HDR10 +// Srgb .... The sRGB output, typical of PC displays, useful for 10-bit output, or storing to 8-bit UNORM without SRGB type +// Two ..... Gamma 2.0, fastest conversion (useful for intermediate pass approximations) +// Three ... Gamma 3.0, less fast, but good for HDR. +//------------------------------------------------------------------------------------------------------------------------------ +// KEEPING TO SPEC +// =============== +// Both Rec.709 and sRGB have a linear segment which as spec'ed would intersect the curved segment 2 times. +// (a.) For 8-bit sRGB, steps {0 to 10.3} are in the linear region (4% of the encoding range). +// (b.) For 8-bit 709, steps {0 to 20.7} are in the linear region (8% of the encoding range). +// Also there is a slight step in the transition regions. +// Precision of the coefficients in the spec being the likely cause. +// Main usage case of the sRGB code is to do the linear->sRGB converstion in a compute shader before store. +// This is to work around lack of hardware (typically only ROP does the conversion for free). +// To "correct" the linear segment, would be to introduce error, because hardware decode of sRGB->linear is fixed (and free). +// So this header keeps with the spec. +// For linear->sRGB transforms, the linear segment in some respects reduces error, because rounding in that region is linear. +// Rounding in the curved region in hardware (and fast software code) introduces error due to rounding in non-linear. +//------------------------------------------------------------------------------------------------------------------------------ +// FOR PQ +// ====== +// Both input and output is {0.0-1.0}, and where output 1.0 represents 10000.0 cd/m^2. +// All constants are only specified to FP32 precision. +// External PQ source reference, +// - https://github.com/ampas/aces-dev/blob/master/transforms/ctl/utilities/ACESlib.Utilities_Color.a1.0.1.ctl +//------------------------------------------------------------------------------------------------------------------------------ +// PACKED VERSIONS +// =============== +// These are the A*H2() functions. +// There is no PQ functions as FP16 seemed to not have enough precision for the conversion. +// The remaining functions are "good enough" for 8-bit, and maybe 10-bit if not concerned about a few 1-bit errors. +// Precision is lowest in the 709 conversion, higher in sRGB, higher still in Two and Gamma (when using 2.2 at least). +//------------------------------------------------------------------------------------------------------------------------------ +// NOTES +// ===== +// Could be faster for PQ conversions to be in ALU or a texture lookup depending on usage case. +//============================================================================================================================== + #if 1 + AF1 ATo709F1(AF1 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AF2 ATo709F2(AF2 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AF3 ATo709F3(AF3 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + // Note 'rcpX' is '1/x', where the 'x' is what would be used in AFromGamma(). + AF1 AToGammaF1(AF1 c,AF1 rcpX){return pow(c,AF1_(rcpX));} + AF2 AToGammaF2(AF2 c,AF1 rcpX){return pow(c,AF2_(rcpX));} + AF3 AToGammaF3(AF3 c,AF1 rcpX){return pow(c,AF3_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToPqF1(AF1 x){AF1 p=pow(x,AF1_(0.159302)); + return pow((AF1_(0.835938)+AF1_(18.8516)*p)/(AF1_(1.0)+AF1_(18.6875)*p),AF1_(78.8438));} + AF2 AToPqF1(AF2 x){AF2 p=pow(x,AF2_(0.159302)); + return pow((AF2_(0.835938)+AF2_(18.8516)*p)/(AF2_(1.0)+AF2_(18.6875)*p),AF2_(78.8438));} + AF3 AToPqF1(AF3 x){AF3 p=pow(x,AF3_(0.159302)); + return pow((AF3_(0.835938)+AF3_(18.8516)*p)/(AF3_(1.0)+AF3_(18.6875)*p),AF3_(78.8438));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToSrgbF1(AF1 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AF2 AToSrgbF2(AF2 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AF3 AToSrgbF3(AF3 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToTwoF1(AF1 c){return sqrt(c);} + AF2 AToTwoF2(AF2 c){return sqrt(c);} + AF3 AToTwoF3(AF3 c){return sqrt(c);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToThreeF1(AF1 c){return pow(c,AF1_(1.0/3.0));} + AF2 AToThreeF2(AF2 c){return pow(c,AF2_(1.0/3.0));} + AF3 AToThreeF3(AF3 c){return pow(c,AF3_(1.0/3.0));} + #endif +//============================================================================================================================== + #if 1 + // Unfortunately median won't work here. + AF1 AFrom709F1(AF1 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AF2 AFrom709F2(AF2 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AF3 AFrom709F3(AF3 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromGammaF1(AF1 c,AF1 x){return pow(c,AF1_(x));} + AF2 AFromGammaF2(AF2 c,AF1 x){return pow(c,AF2_(x));} + AF3 AFromGammaF3(AF3 c,AF1 x){return pow(c,AF3_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromPqF1(AF1 x){AF1 p=pow(x,AF1_(0.0126833)); + return pow(ASatF1(p-AF1_(0.835938))/(AF1_(18.8516)-AF1_(18.6875)*p),AF1_(6.27739));} + AF2 AFromPqF1(AF2 x){AF2 p=pow(x,AF2_(0.0126833)); + return pow(ASatF2(p-AF2_(0.835938))/(AF2_(18.8516)-AF2_(18.6875)*p),AF2_(6.27739));} + AF3 AFromPqF1(AF3 x){AF3 p=pow(x,AF3_(0.0126833)); + return pow(ASatF3(p-AF3_(0.835938))/(AF3_(18.8516)-AF3_(18.6875)*p),AF3_(6.27739));} +//------------------------------------------------------------------------------------------------------------------------------ + // Unfortunately median won't work here. + AF1 AFromSrgbF1(AF1 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AF2 AFromSrgbF2(AF2 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AF3 AFromSrgbF3(AF3 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromTwoF1(AF1 c){return c*c;} + AF2 AFromTwoF2(AF2 c){return c*c;} + AF3 AFromTwoF3(AF3 c){return c*c;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromThreeF1(AF1 c){return c*c*c;} + AF2 AFromThreeF2(AF2 c){return c*c*c;} + AF3 AFromThreeF3(AF3 c){return c*c*c;} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH1 ATo709H1(AH1 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AH2 ATo709H2(AH2 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AH3 ATo709H3(AH3 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToGammaH1(AH1 c,AH1 rcpX){return pow(c,AH1_(rcpX));} + AH2 AToGammaH2(AH2 c,AH1 rcpX){return pow(c,AH2_(rcpX));} + AH3 AToGammaH3(AH3 c,AH1 rcpX){return pow(c,AH3_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToSrgbH1(AH1 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AH2 AToSrgbH2(AH2 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AH3 AToSrgbH3(AH3 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToTwoH1(AH1 c){return sqrt(c);} + AH2 AToTwoH2(AH2 c){return sqrt(c);} + AH3 AToTwoH3(AH3 c){return sqrt(c);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToThreeF1(AH1 c){return pow(c,AH1_(1.0/3.0));} + AH2 AToThreeF2(AH2 c){return pow(c,AH2_(1.0/3.0));} + AH3 AToThreeF3(AH3 c){return pow(c,AH3_(1.0/3.0));} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH1 AFrom709H1(AH1 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AH2 AFrom709H2(AH2 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AH3 AFrom709H3(AH3 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromGammaH1(AH1 c,AH1 x){return pow(c,AH1_(x));} + AH2 AFromGammaH2(AH2 c,AH1 x){return pow(c,AH2_(x));} + AH3 AFromGammaH3(AH3 c,AH1 x){return pow(c,AH3_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AHromSrgbF1(AH1 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AH2 AHromSrgbF2(AH2 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AH3 AHromSrgbF3(AH3 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromTwoH1(AH1 c){return c*c;} + AH2 AFromTwoH2(AH2 c){return c*c;} + AH3 AFromTwoH3(AH3 c){return c*c;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromThreeH1(AH1 c){return c*c*c;} + AH2 AFromThreeH2(AH2 c){return c*c*c;} + AH3 AFromThreeH3(AH3 c){return c*c*c;} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CS REMAP +//============================================================================================================================== + // Simple remap 64x1 to 8x8 with rotated 2x2 pixel quads in quad linear. + // 543210 + // ====== + // ..xxx. + // yy...y + AU2 ARmp8x8(AU1 a){return AU2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} +//============================================================================================================================== + // More complex remap 64x1 to 8x8 which is necessary for 2D wave reductions. + // 543210 + // ====== + // .xx..x + // y..yy. + // Details, + // LANE TO 8x8 MAPPING + // =================== + // 00 01 08 09 10 11 18 19 + // 02 03 0a 0b 12 13 1a 1b + // 04 05 0c 0d 14 15 1c 1d + // 06 07 0e 0f 16 17 1e 1f + // 20 21 28 29 30 31 38 39 + // 22 23 2a 2b 32 33 3a 3b + // 24 25 2c 2d 34 35 3c 3d + // 26 27 2e 2f 36 37 3e 3f + AU2 ARmpRed8x8(AU1 a){return AU2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} +//============================================================================================================================== + #ifdef A_HALF + AW2 ARmp8x8H(AU1 a){return AW2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} + AW2 ARmpRed8x8H(AU1 a){return AW2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} + #endif +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// REFERENCE +// +//------------------------------------------------------------------------------------------------------------------------------ +// IEEE FLOAT RULES +// ================ +// - saturate(NaN)=0, saturate(-INF)=0, saturate(+INF)=1 +// - {+/-}0 * {+/-}INF = NaN +// - -INF + (+INF) = NaN +// - {+/-}0 / {+/-}0 = NaN +// - {+/-}INF / {+/-}INF = NaN +// - a<(-0) := sqrt(a) = NaN (a=-0.0 won't NaN) +// - 0 == -0 +// - 4/0 = +INF +// - 4/-0 = -INF +// - 4+INF = +INF +// - 4-INF = -INF +// - 4*(+INF) = +INF +// - 4*(-INF) = -INF +// - -4*(+INF) = -INF +// - sqrt(+INF) = +INF +//------------------------------------------------------------------------------------------------------------------------------ +// FP16 ENCODING +// ============= +// fedcba9876543210 +// ---------------- +// ......mmmmmmmmmm 10-bit mantissa (encodes 11-bit 0.5 to 1.0 except for denormals) +// .eeeee.......... 5-bit exponent +// .00000.......... denormals +// .00001.......... -14 exponent +// .11110.......... 15 exponent +// .111110000000000 infinity +// .11111nnnnnnnnnn NaN with n!=0 +// s............... sign +//------------------------------------------------------------------------------------------------------------------------------ +// FP16/INT16 ALIASING DENORMAL +// ============================ +// 11-bit unsigned integers alias with half float denormal/normal values, +// 1 = 2^(-24) = 1/16777216 ....................... first denormal value +// 2 = 2^(-23) +// ... +// 1023 = 2^(-14)*(1-2^(-10)) = 2^(-14)*(1-1/1024) ... last denormal value +// 1024 = 2^(-14) = 1/16384 .......................... first normal value that still maps to integers +// 2047 .............................................. last normal value that still maps to integers +// Scaling limits, +// 2^15 = 32768 ...................................... largest power of 2 scaling +// Largest pow2 conversion mapping is at *32768, +// 1 : 2^(-9) = 1/512 +// 2 : 1/256 +// 4 : 1/128 +// 8 : 1/64 +// 16 : 1/32 +// 32 : 1/16 +// 64 : 1/8 +// 128 : 1/4 +// 256 : 1/2 +// 512 : 1 +// 1024 : 2 +// 2047 : a little less than 4 +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU/CPU PORTABILITY +// +// +//------------------------------------------------------------------------------------------------------------------------------ +// This is the GPU implementation. +// See the CPU implementation for docs. +//============================================================================================================================== +#ifdef A_GPU + #define A_TRUE true + #define A_FALSE false + #define A_STATIC +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD2 + #define retAD3 AD3 + #define retAD4 AD4 + #define retAF2 AF2 + #define retAF3 AF3 + #define retAF4 AF4 + #define retAL2 AL2 + #define retAL3 AL3 + #define retAL4 AL4 + #define retAU2 AU2 + #define retAU3 AU3 + #define retAU4 AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 in AD2 + #define inAD3 in AD3 + #define inAD4 in AD4 + #define inAF2 in AF2 + #define inAF3 in AF3 + #define inAF4 in AF4 + #define inAL2 in AL2 + #define inAL3 in AL3 + #define inAL4 in AL4 + #define inAU2 in AU2 + #define inAU3 in AU3 + #define inAU4 in AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 inout AD2 + #define inoutAD3 inout AD3 + #define inoutAD4 inout AD4 + #define inoutAF2 inout AF2 + #define inoutAF3 inout AF3 + #define inoutAF4 inout AF4 + #define inoutAL2 inout AL2 + #define inoutAL3 inout AL3 + #define inoutAL4 inout AL4 + #define inoutAU2 inout AU2 + #define inoutAU3 inout AU3 + #define inoutAU4 inout AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 out AD2 + #define outAD3 out AD3 + #define outAD4 out AD4 + #define outAF2 out AF2 + #define outAF3 out AF3 + #define outAF4 out AF4 + #define outAL2 out AL2 + #define outAL3 out AL3 + #define outAL4 out AL4 + #define outAU2 out AU2 + #define outAU3 out AU3 + #define outAU4 out AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD2 x + #define varAD3(x) AD3 x + #define varAD4(x) AD4 x + #define varAF2(x) AF2 x + #define varAF3(x) AF3 x + #define varAF4(x) AF4 x + #define varAL2(x) AL2 x + #define varAL3(x) AL3 x + #define varAL4(x) AL4 x + #define varAU2(x) AU2 x + #define varAU3(x) AU3 x + #define varAU4(x) AU4 x +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) AD2(x,y) + #define initAD3(x,y,z) AD3(x,y,z) + #define initAD4(x,y,z,w) AD4(x,y,z,w) + #define initAF2(x,y) AF2(x,y) + #define initAF3(x,y,z) AF3(x,y,z) + #define initAF4(x,y,z,w) AF4(x,y,z,w) + #define initAL2(x,y) AL2(x,y) + #define initAL3(x,y,z) AL3(x,y,z) + #define initAL4(x,y,z,w) AL4(x,y,z,w) + #define initAU2(x,y) AU2(x,y) + #define initAU3(x,y,z) AU3(x,y,z) + #define initAU4(x,y,z,w) AU4(x,y,z,w) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//============================================================================================================================== + #define AAbsD1(a) abs(AD1(a)) + #define AAbsF1(a) abs(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ACosD1(a) cos(AD1(a)) + #define ACosF1(a) cos(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ADotD2(a,b) dot(AD2(a),AD2(b)) + #define ADotD3(a,b) dot(AD3(a),AD3(b)) + #define ADotD4(a,b) dot(AD4(a),AD4(b)) + #define ADotF2(a,b) dot(AF2(a),AF2(b)) + #define ADotF3(a,b) dot(AF3(a),AF3(b)) + #define ADotF4(a,b) dot(AF4(a),AF4(b)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AExp2D1(a) exp2(AD1(a)) + #define AExp2F1(a) exp2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AFloorD1(a) floor(AD1(a)) + #define AFloorF1(a) floor(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ALog2D1(a) log2(AD1(a)) + #define ALog2F1(a) log2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMaxD1(a,b) max(a,b) + #define AMaxF1(a,b) max(a,b) + #define AMaxL1(a,b) max(a,b) + #define AMaxU1(a,b) max(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMinD1(a,b) min(a,b) + #define AMinF1(a,b) min(a,b) + #define AMinL1(a,b) min(a,b) + #define AMinU1(a,b) min(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASinD1(a) sin(AD1(a)) + #define ASinF1(a) sin(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASqrtD1(a) sqrt(AD1(a)) + #define ASqrtF1(a) sqrt(AF1(a)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + #define APowD1(a,b) pow(AD1(a),AF1(b)) + #define APowF1(a,b) pow(AF1(a),AF1(b)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + #ifdef A_DUBL + AD2 opAAbsD2(outAD2 d,inAD2 a){d=abs(a);return d;} + AD3 opAAbsD3(outAD3 d,inAD3 a){d=abs(a);return d;} + AD4 opAAbsD4(outAD4 d,inAD4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d=a+b;return d;} + AD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d=a+b;return d;} + AD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d=a+AD2_(b);return d;} + AD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d=a+AD3_(b);return d;} + AD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d=a+AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opACpyD2(outAD2 d,inAD2 a){d=a;return d;} + AD3 opACpyD3(outAD3 d,inAD3 a){d=a;return d;} + AD4 opACpyD4(outAD4 d,inAD4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d=ALerpD2(a,b,c);return d;} + AD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d=ALerpD3(a,b,c);return d;} + AD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d=ALerpD4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d=ALerpD2(a,b,AD2_(c));return d;} + AD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d=ALerpD3(a,b,AD3_(c));return d;} + AD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d=ALerpD4(a,b,AD4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d=max(a,b);return d;} + AD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d=max(a,b);return d;} + AD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d=min(a,b);return d;} + AD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d=min(a,b);return d;} + AD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d=a*b;return d;} + AD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d=a*b;return d;} + AD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d=a*AD2_(b);return d;} + AD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d=a*AD3_(b);return d;} + AD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d=a*AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opANegD2(outAD2 d,inAD2 a){d=-a;return d;} + AD3 opANegD3(outAD3 d,inAD3 a){d=-a;return d;} + AD4 opANegD4(outAD4 d,inAD4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opARcpD2(outAD2 d,inAD2 a){d=ARcpD2(a);return d;} + AD3 opARcpD3(outAD3 d,inAD3 a){d=ARcpD3(a);return d;} + AD4 opARcpD4(outAD4 d,inAD4 a){d=ARcpD4(a);return d;} + #endif +//============================================================================================================================== + AF2 opAAbsF2(outAF2 d,inAF2 a){d=abs(a);return d;} + AF3 opAAbsF3(outAF3 d,inAF3 a){d=abs(a);return d;} + AF4 opAAbsF4(outAF4 d,inAF4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d=a+b;return d;} + AF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d=a+b;return d;} + AF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d=a+AF2_(b);return d;} + AF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d=a+AF3_(b);return d;} + AF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d=a+AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opACpyF2(outAF2 d,inAF2 a){d=a;return d;} + AF3 opACpyF3(outAF3 d,inAF3 a){d=a;return d;} + AF4 opACpyF4(outAF4 d,inAF4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d=ALerpF2(a,b,c);return d;} + AF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d=ALerpF3(a,b,c);return d;} + AF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d=ALerpF4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d=ALerpF2(a,b,AF2_(c));return d;} + AF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d=ALerpF3(a,b,AF3_(c));return d;} + AF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d=ALerpF4(a,b,AF4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d=max(a,b);return d;} + AF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d=max(a,b);return d;} + AF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d=min(a,b);return d;} + AF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d=min(a,b);return d;} + AF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d=a*b;return d;} + AF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d=a*b;return d;} + AF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d=a*AF2_(b);return d;} + AF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d=a*AF3_(b);return d;} + AF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d=a*AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opANegF2(outAF2 d,inAF2 a){d=-a;return d;} + AF3 opANegF3(outAF3 d,inAF3 a){d=-a;return d;} + AF4 opANegF4(outAF4 d,inAF4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opARcpF2(outAF2 d,inAF2 a){d=ARcpF2(a);return d;} + AF3 opARcpF3(outAF3 d,inAF3 a){d=ARcpF3(a);return d;} + AF4 opARcpF4(outAF4 d,inAF4 a){d=ARcpF4(a);return d;} +#endif + +#define FSR_EASU_F 1 +AU4 con0, con1, con2, con3; +float srcW, srcH, dstW, dstH; +vec2 bLeft, tRight; + +AF2 translate(AF2 pos) { + return AF2(pos.x * scaleX, pos.y * scaleY); +} + +void setBounds(vec2 bottomLeft, vec2 topRight) { + bLeft = bottomLeft; + tRight = topRight; +} + +AF4 FsrEasuRF(AF2 p) { AF4 res = textureGather(Source, translate(p), 0); return res; } +AF4 FsrEasuGF(AF2 p) { AF4 res = textureGather(Source, translate(p), 1); return res; } +AF4 FsrEasuBF(AF2 p) { AF4 res = textureGather(Source, translate(p), 2); return res; } + +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// AMD FidelityFX SUPER RESOLUTION [FSR 1] ::: SPATIAL SCALING & EXTRAS - v1.20210629 +// +// +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// ABOUT +// ===== +// FSR is a collection of algorithms relating to generating a higher resolution image. +// This specific header focuses on single-image non-temporal image scaling, and related tools. +// +// The core functions are EASU and RCAS: +// [EASU] Edge Adaptive Spatial Upsampling ....... 1x to 4x area range spatial scaling, clamped adaptive elliptical filter. +// [RCAS] Robust Contrast Adaptive Sharpening .... A non-scaling variation on CAS. +// RCAS needs to be applied after EASU as a separate pass. +// +// Optional utility functions are: +// [LFGA] Linear Film Grain Applicator ........... Tool to apply film grain after scaling. +// [SRTM] Simple Reversible Tone-Mapper .......... Linear HDR {0 to FP16_MAX} to {0 to 1} and back. +// [TEPD] Temporal Energy Preserving Dither ...... Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. +// See each individual sub-section for inline documentation. +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// FUNCTION PERMUTATIONS +// ===================== +// *F() ..... Single item computation with 32-bit. +// *H() ..... Single item computation with 16-bit, with packing (aka two 16-bit ops in parallel) when possible. +// *Hx2() ... Processing two items in parallel with 16-bit, easier packing. +// Not all interfaces in this file have a *Hx2() form. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [EASU] EDGE ADAPTIVE SPATIAL UPSAMPLING +// +//------------------------------------------------------------------------------------------------------------------------------ +// EASU provides a high quality spatial-only scaling at relatively low cost. +// Meaning EASU is appropiate for laptops and other low-end GPUs. +// Quality from 1x to 4x area scaling is good. +//------------------------------------------------------------------------------------------------------------------------------ +// The scalar uses a modified fast approximation to the standard lanczos(size=2) kernel. +// EASU runs in a single pass, so it applies a directionally and anisotropically adaptive radial lanczos. +// This is also kept as simple as possible to have minimum runtime. +//------------------------------------------------------------------------------------------------------------------------------ +// The lanzcos filter has negative lobes, so by itself it will introduce ringing. +// To remove all ringing, the algorithm uses the nearest 2x2 input texels as a neighborhood, +// and limits output to the minimum and maximum of that neighborhood. +//------------------------------------------------------------------------------------------------------------------------------ +// Input image requirements: +// +// Color needs to be encoded as 3 channel[red, green, blue](e.g.XYZ not supported) +// Each channel needs to be in the range[0, 1] +// Any color primaries are supported +// Display / tonemapping curve needs to be as if presenting to sRGB display or similar(e.g.Gamma 2.0) +// There should be no banding in the input +// There should be no high amplitude noise in the input +// There should be no noise in the input that is not at input pixel granularity +// For performance purposes, use 32bpp formats +//------------------------------------------------------------------------------------------------------------------------------ +// Best to apply EASU at the end of the frame after tonemapping +// but before film grain or composite of the UI. +//------------------------------------------------------------------------------------------------------------------------------ +// Example of including this header for D3D HLSL : +// +// #define A_GPU 1 +// #define A_HLSL 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of including this header for Vulkan GLSL : +// +// #define A_GPU 1 +// #define A_GLSL 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of including this header for Vulkan HLSL : +// +// #define A_GPU 1 +// #define A_HLSL 1 +// #define A_HLSL_6_2 1 +// #define A_NO_16_BIT_CAST 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of declaring the required input callbacks for GLSL : +// The callbacks need to gather4 for each color channel using the specified texture coordinate 'p'. +// EASU uses gather4 to reduce position computation logic and for free Arrays of Structures to Structures of Arrays conversion. +// +// AH4 FsrEasuRH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,0));} +// AH4 FsrEasuGH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,1));} +// AH4 FsrEasuBH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,2));} +// ... +// The FsrEasuCon function needs to be called from the CPU or GPU to set up constants. +// The difference in viewport and input image size is there to support Dynamic Resolution Scaling. +// To use FsrEasuCon() on the CPU, define A_CPU before including ffx_a and ffx_fsr1. +// Including a GPU example here, the 'con0' through 'con3' values would be stored out to a constant buffer. +// AU4 con0,con1,con2,con3; +// FsrEasuCon(con0,con1,con2,con3, +// 1920.0,1080.0, // Viewport size (top left aligned) in the input image which is to be scaled. +// 3840.0,2160.0, // The size of the input image. +// 2560.0,1440.0); // The output resolution. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CONSTANT SETUP +//============================================================================================================================== +// Call to setup required constant values (works on CPU or GPU). +A_STATIC void FsrEasuCon( +outAU4 con0, +outAU4 con1, +outAU4 con2, +outAU4 con3, +// This the rendered image resolution being upscaled +AF1 inputViewportInPixelsX, +AF1 inputViewportInPixelsY, +// This is the resolution of the resource containing the input image (useful for dynamic resolution) +AF1 inputSizeInPixelsX, +AF1 inputSizeInPixelsY, +// This is the display resolution which the input image gets upscaled to +AF1 outputSizeInPixelsX, +AF1 outputSizeInPixelsY){ + // Output integer position to a pixel position in viewport. + con0[0]=AU1_AF1(inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)); + con0[1]=AU1_AF1(inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)); + con0[2]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)-AF1_(0.5)); + con0[3]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)-AF1_(0.5)); + // Viewport pixel position to normalized image space. + // This is used to get upper-left of 'F' tap. + con1[0]=AU1_AF1(ARcpF1(inputSizeInPixelsX)); + con1[1]=AU1_AF1(ARcpF1(inputSizeInPixelsY)); + // Centers of gather4, first offset from upper-left of 'F'. + // +---+---+ + // | | | + // +--(0)--+ + // | b | c | + // +---F---+---+---+ + // | e | f | g | h | + // +--(1)--+--(2)--+ + // | i | j | k | l | + // +---+---+---+---+ + // | n | o | + // +--(3)--+ + // | | | + // +---+---+ + con1[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); + con1[3]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsY)); + // These are from (0) instead of 'F'. + con2[0]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsX)); + con2[1]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); + con2[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); + con2[3]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); + con3[0]=AU1_AF1(AF1_( 0.0)*ARcpF1(inputSizeInPixelsX)); + con3[1]=AU1_AF1(AF1_( 4.0)*ARcpF1(inputSizeInPixelsY)); + con3[2]=con3[3]=0;} + +//If the an offset into the input image resource +A_STATIC void FsrEasuConOffset( + outAU4 con0, + outAU4 con1, + outAU4 con2, + outAU4 con3, + // This the rendered image resolution being upscaled + AF1 inputViewportInPixelsX, + AF1 inputViewportInPixelsY, + // This is the resolution of the resource containing the input image (useful for dynamic resolution) + AF1 inputSizeInPixelsX, + AF1 inputSizeInPixelsY, + // This is the display resolution which the input image gets upscaled to + AF1 outputSizeInPixelsX, + AF1 outputSizeInPixelsY, + // This is the input image offset into the resource containing it (useful for dynamic resolution) + AF1 inputOffsetInPixelsX, + AF1 inputOffsetInPixelsY) { + FsrEasuCon(con0, con1, con2, con3, inputViewportInPixelsX, inputViewportInPixelsY, inputSizeInPixelsX, inputSizeInPixelsY, outputSizeInPixelsX, outputSizeInPixelsY); + con0[2] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsX * ARcpF1(outputSizeInPixelsX) - AF1_(0.5) + inputOffsetInPixelsX); + con0[3] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsY * ARcpF1(outputSizeInPixelsY) - AF1_(0.5) + inputOffsetInPixelsY); +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 32-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(FSR_EASU_F) + // Input callback prototypes, need to be implemented by calling shader + AF4 FsrEasuRF(AF2 p); + AF4 FsrEasuGF(AF2 p); + AF4 FsrEasuBF(AF2 p); +//------------------------------------------------------------------------------------------------------------------------------ + // Filtering for a given tap for the scalar. + void FsrEasuTapF( + inout AF3 aC, // Accumulated color, with negative lobe. + inout AF1 aW, // Accumulated weight. + AF2 off, // Pixel offset from resolve position to tap. + AF2 dir, // Gradient direction. + AF2 len, // Length. + AF1 lob, // Negative lobe strength. + AF1 clp, // Clipping point. + AF3 c){ // Tap color. + // Rotate offset by direction. + AF2 v; + v.x=(off.x*( dir.x))+(off.y*dir.y); + v.y=(off.x*(-dir.y))+(off.y*dir.x); + // Anisotropy. + v*=len; + // Compute distance^2. + AF1 d2=v.x*v.x+v.y*v.y; + // Limit to the window as at corner, 2 taps can easily be outside. + d2=min(d2,clp); + // Approximation of lancos2 without sin() or rcp(), or sqrt() to get x. + // (25/16 * (2/5 * x^2 - 1)^2 - (25/16 - 1)) * (1/4 * x^2 - 1)^2 + // |_______________________________________| |_______________| + // base window + // The general form of the 'base' is, + // (a*(b*x^2-1)^2-(a-1)) + // Where 'a=1/(2*b-b^2)' and 'b' moves around the negative lobe. + AF1 wB=AF1_(2.0/5.0)*d2+AF1_(-1.0); + AF1 wA=lob*d2+AF1_(-1.0); + wB*=wB; + wA*=wA; + wB=AF1_(25.0/16.0)*wB+AF1_(-(25.0/16.0-1.0)); + AF1 w=wB*wA; + // Do weighted average. + aC+=c*w;aW+=w;} +//------------------------------------------------------------------------------------------------------------------------------ + // Accumulate direction and length. + void FsrEasuSetF( + inout AF2 dir, + inout AF1 len, + AF2 pp, + AP1 biS,AP1 biT,AP1 biU,AP1 biV, + AF1 lA,AF1 lB,AF1 lC,AF1 lD,AF1 lE){ + // Compute bilinear weight, branches factor out as predicates are compiler time immediates. + // s t + // u v + AF1 w = AF1_(0.0); + if(biS)w=(AF1_(1.0)-pp.x)*(AF1_(1.0)-pp.y); + if(biT)w= pp.x *(AF1_(1.0)-pp.y); + if(biU)w=(AF1_(1.0)-pp.x)* pp.y ; + if(biV)w= pp.x * pp.y ; + // Direction is the '+' diff. + // a + // b c d + // e + // Then takes magnitude from abs average of both sides of 'c'. + // Length converts gradient reversal to 0, smoothly to non-reversal at 1, shaped, then adding horz and vert terms. + AF1 dc=lD-lC; + AF1 cb=lC-lB; + AF1 lenX=max(abs(dc),abs(cb)); + lenX=APrxLoRcpF1(lenX); + AF1 dirX=lD-lB; + dir.x+=dirX*w; + lenX=ASatF1(abs(dirX)*lenX); + lenX*=lenX; + len+=lenX*w; + // Repeat for the y axis. + AF1 ec=lE-lC; + AF1 ca=lC-lA; + AF1 lenY=max(abs(ec),abs(ca)); + lenY=APrxLoRcpF1(lenY); + AF1 dirY=lE-lA; + dir.y+=dirY*w; + lenY=ASatF1(abs(dirY)*lenY); + lenY*=lenY; + len+=lenY*w;} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrEasuF( + out AF3 pix, + AU2 ip, // Integer pixel position in output. + AU4 con0, // Constants generated by FsrEasuCon(). + AU4 con1, + AU4 con2, + AU4 con3){ +//------------------------------------------------------------------------------------------------------------------------------ + // Get position of 'f'. + AF2 pp=AF2(ip)*AF2_AU2(con0.xy)+AF2_AU2(con0.zw); + AF2 fp=floor(pp); + pp-=fp; +//------------------------------------------------------------------------------------------------------------------------------ + // 12-tap kernel. + // b c + // e f g h + // i j k l + // n o + // Gather 4 ordering. + // a b + // r g + // For packed FP16, need either {rg} or {ab} so using the following setup for gather in all versions, + // a b <- unused (z) + // r g + // a b a b + // r g r g + // a b + // r g <- unused (z) + // Allowing dead-code removal to remove the 'z's. + AF2 p0=fp*AF2_AU2(con1.xy)+AF2_AU2(con1.zw); + // These are from p0 to avoid pulling two constants on pre-Navi hardware. + AF2 p1=p0+AF2_AU2(con2.xy); + AF2 p2=p0+AF2_AU2(con2.zw); + AF2 p3=p0+AF2_AU2(con3.xy); + AF4 bczzR=FsrEasuRF(p0); + AF4 bczzG=FsrEasuGF(p0); + AF4 bczzB=FsrEasuBF(p0); + AF4 ijfeR=FsrEasuRF(p1); + AF4 ijfeG=FsrEasuGF(p1); + AF4 ijfeB=FsrEasuBF(p1); + AF4 klhgR=FsrEasuRF(p2); + AF4 klhgG=FsrEasuGF(p2); + AF4 klhgB=FsrEasuBF(p2); + AF4 zzonR=FsrEasuRF(p3); + AF4 zzonG=FsrEasuGF(p3); + AF4 zzonB=FsrEasuBF(p3); +//------------------------------------------------------------------------------------------------------------------------------ + // Simplest multi-channel approximate luma possible (luma times 2, in 2 FMA/MAD). + AF4 bczzL=bczzB*AF4_(0.5)+(bczzR*AF4_(0.5)+bczzG); + AF4 ijfeL=ijfeB*AF4_(0.5)+(ijfeR*AF4_(0.5)+ijfeG); + AF4 klhgL=klhgB*AF4_(0.5)+(klhgR*AF4_(0.5)+klhgG); + AF4 zzonL=zzonB*AF4_(0.5)+(zzonR*AF4_(0.5)+zzonG); + // Rename. + AF1 bL=bczzL.x; + AF1 cL=bczzL.y; + AF1 iL=ijfeL.x; + AF1 jL=ijfeL.y; + AF1 fL=ijfeL.z; + AF1 eL=ijfeL.w; + AF1 kL=klhgL.x; + AF1 lL=klhgL.y; + AF1 hL=klhgL.z; + AF1 gL=klhgL.w; + AF1 oL=zzonL.z; + AF1 nL=zzonL.w; + // Accumulate for bilinear interpolation. + AF2 dir=AF2_(0.0); + AF1 len=AF1_(0.0); + FsrEasuSetF(dir,len,pp,true, false,false,false,bL,eL,fL,gL,jL); + FsrEasuSetF(dir,len,pp,false,true ,false,false,cL,fL,gL,hL,kL); + FsrEasuSetF(dir,len,pp,false,false,true ,false,fL,iL,jL,kL,nL); + FsrEasuSetF(dir,len,pp,false,false,false,true ,gL,jL,kL,lL,oL); +//------------------------------------------------------------------------------------------------------------------------------ + // Normalize with approximation, and cleanup close to zero. + AF2 dir2=dir*dir; + AF1 dirR=dir2.x+dir2.y; + AP1 zro=dirR w = -m/(n+e+w+s) +// 1 == (w*(n+e+w+s)+m)/(4*w+1) -> w = (1-m)/(n+e+w+s-4*1) +// Then chooses the 'w' which results in no clipping, limits 'w', and multiplies by the 'sharp' amount. +// This solution above has issues with MSAA input as the steps along the gradient cause edge detection issues. +// So RCAS uses 4x the maximum and 4x the minimum (depending on equation)in place of the individual taps. +// As well as switching from 'm' to either the minimum or maximum (depending on side), to help in energy conservation. +// This stabilizes RCAS. +// RCAS does a simple highpass which is normalized against the local contrast then shaped, +// 0.25 +// 0.25 -1 0.25 +// 0.25 +// This is used as a noise detection filter, to reduce the effect of RCAS on grain, and focus on real edges. +// +// GLSL example for the required callbacks : +// +// AH4 FsrRcasLoadH(ASW2 p){return AH4(imageLoad(imgSrc,ASU2(p)));} +// void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b) +// { +// //do any simple input color conversions here or leave empty if none needed +// } +// +// FsrRcasCon need to be called from the CPU or GPU to set up constants. +// Including a GPU example here, the 'con' value would be stored out to a constant buffer. +// +// AU4 con; +// FsrRcasCon(con, +// 0.0); // The scale is {0.0 := maximum sharpness, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. +// --------------- +// RCAS sharpening supports a CAS-like pass-through alpha via, +// #define FSR_RCAS_PASSTHROUGH_ALPHA 1 +// RCAS also supports a define to enable a more expensive path to avoid some sharpening of noise. +// Would suggest it is better to apply film grain after RCAS sharpening (and after scaling) instead of using this define, +// #define FSR_RCAS_DENOISE 1 +//============================================================================================================================== +// This is set at the limit of providing unnatural results for sharpening. +#define FSR_RCAS_LIMIT (0.25-(1.0/16.0)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CONSTANT SETUP +//============================================================================================================================== +// Call to setup required constant values (works on CPU or GPU). +A_STATIC void FsrRcasCon( +outAU4 con, +// The scale is {0.0 := maximum, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. +AF1 sharpness){ + // Transform from stops to linear value. + sharpness=AExp2F1(-sharpness); + varAF2(hSharp)=initAF2(sharpness,sharpness); + con[0]=AU1_AF1(sharpness); + con[1]=AU1_AH2_AF2(hSharp); + con[2]=0; + con[3]=0;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 32-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(FSR_RCAS_F) + // Input callback prototypes that need to be implemented by calling shader + AF4 FsrRcasLoadF(ASU2 p); + void FsrRcasInputF(inout AF1 r,inout AF1 g,inout AF1 b); +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasF( + out AF1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. + out AF1 pixG, + out AF1 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AF1 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // Algorithm uses minimal 3x3 pixel neighborhood. + // b + // d e f + // h + ASU2 sp=ASU2(ip); + AF3 b=FsrRcasLoadF(sp+ASU2( 0,-1)).rgb; + AF3 d=FsrRcasLoadF(sp+ASU2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AF4 ee=FsrRcasLoadF(sp); + AF3 e=ee.rgb;pixA=ee.a; + #else + AF3 e=FsrRcasLoadF(sp).rgb; + #endif + AF3 f=FsrRcasLoadF(sp+ASU2( 1, 0)).rgb; + AF3 h=FsrRcasLoadF(sp+ASU2( 0, 1)).rgb; + // Rename (32-bit) or regroup (16-bit). + AF1 bR=b.r; + AF1 bG=b.g; + AF1 bB=b.b; + AF1 dR=d.r; + AF1 dG=d.g; + AF1 dB=d.b; + AF1 eR=e.r; + AF1 eG=e.g; + AF1 eB=e.b; + AF1 fR=f.r; + AF1 fG=f.g; + AF1 fB=f.b; + AF1 hR=h.r; + AF1 hG=h.g; + AF1 hB=h.b; + // Run optional input transform. + FsrRcasInputF(bR,bG,bB); + FsrRcasInputF(dR,dG,dB); + FsrRcasInputF(eR,eG,eB); + FsrRcasInputF(fR,fG,fB); + FsrRcasInputF(hR,hG,hB); + // Luma times 2. + AF1 bL=bB*AF1_(0.5)+(bR*AF1_(0.5)+bG); + AF1 dL=dB*AF1_(0.5)+(dR*AF1_(0.5)+dG); + AF1 eL=eB*AF1_(0.5)+(eR*AF1_(0.5)+eG); + AF1 fL=fB*AF1_(0.5)+(fR*AF1_(0.5)+fG); + AF1 hL=hB*AF1_(0.5)+(hR*AF1_(0.5)+hG); + // Noise detection. + AF1 nz=AF1_(0.25)*bL+AF1_(0.25)*dL+AF1_(0.25)*fL+AF1_(0.25)*hL-eL; + nz=ASatF1(abs(nz)*APrxMedRcpF1(AMax3F1(AMax3F1(bL,dL,eL),fL,hL)-AMin3F1(AMin3F1(bL,dL,eL),fL,hL))); + nz=AF1_(-0.5)*nz+AF1_(1.0); + // Min and max of ring. + AF1 mn4R=min(AMin3F1(bR,dR,fR),hR); + AF1 mn4G=min(AMin3F1(bG,dG,fG),hG); + AF1 mn4B=min(AMin3F1(bB,dB,fB),hB); + AF1 mx4R=max(AMax3F1(bR,dR,fR),hR); + AF1 mx4G=max(AMax3F1(bG,dG,fG),hG); + AF1 mx4B=max(AMax3F1(bB,dB,fB),hB); + // Immediate constants for peak range. + AF2 peakC=AF2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AF1 hitMinR=min(mn4R,eR)*ARcpF1(AF1_(4.0)*mx4R); + AF1 hitMinG=min(mn4G,eG)*ARcpF1(AF1_(4.0)*mx4G); + AF1 hitMinB=min(mn4B,eB)*ARcpF1(AF1_(4.0)*mx4B); + AF1 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpF1(AF1_(4.0)*mn4R+peakC.y); + AF1 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpF1(AF1_(4.0)*mn4G+peakC.y); + AF1 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpF1(AF1_(4.0)*mn4B+peakC.y); + AF1 lobeR=max(-hitMinR,hitMaxR); + AF1 lobeG=max(-hitMinG,hitMaxG); + AF1 lobeB=max(-hitMinB,hitMaxB); + AF1 lobe=max(AF1_(-FSR_RCAS_LIMIT),min(AMax3F1(lobeR,lobeG,lobeB),AF1_(0.0)))*AF1_AU1(con.x); + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AF1 rcpL=APrxMedRcpF1(AF1_(4.0)*lobe+AF1_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL; + return;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 16-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_H) + // Input callback prototypes that need to be implemented by calling shader + AH4 FsrRcasLoadH(ASW2 p); + void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b); +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasH( + out AH1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. + out AH1 pixG, + out AH1 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AH1 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // Sharpening algorithm uses minimal 3x3 pixel neighborhood. + // b + // d e f + // h + ASW2 sp=ASW2(ip); + AH3 b=FsrRcasLoadH(sp+ASW2( 0,-1)).rgb; + AH3 d=FsrRcasLoadH(sp+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee=FsrRcasLoadH(sp); + AH3 e=ee.rgb;pixA=ee.a; + #else + AH3 e=FsrRcasLoadH(sp).rgb; + #endif + AH3 f=FsrRcasLoadH(sp+ASW2( 1, 0)).rgb; + AH3 h=FsrRcasLoadH(sp+ASW2( 0, 1)).rgb; + // Rename (32-bit) or regroup (16-bit). + AH1 bR=b.r; + AH1 bG=b.g; + AH1 bB=b.b; + AH1 dR=d.r; + AH1 dG=d.g; + AH1 dB=d.b; + AH1 eR=e.r; + AH1 eG=e.g; + AH1 eB=e.b; + AH1 fR=f.r; + AH1 fG=f.g; + AH1 fB=f.b; + AH1 hR=h.r; + AH1 hG=h.g; + AH1 hB=h.b; + // Run optional input transform. + FsrRcasInputH(bR,bG,bB); + FsrRcasInputH(dR,dG,dB); + FsrRcasInputH(eR,eG,eB); + FsrRcasInputH(fR,fG,fB); + FsrRcasInputH(hR,hG,hB); + // Luma times 2. + AH1 bL=bB*AH1_(0.5)+(bR*AH1_(0.5)+bG); + AH1 dL=dB*AH1_(0.5)+(dR*AH1_(0.5)+dG); + AH1 eL=eB*AH1_(0.5)+(eR*AH1_(0.5)+eG); + AH1 fL=fB*AH1_(0.5)+(fR*AH1_(0.5)+fG); + AH1 hL=hB*AH1_(0.5)+(hR*AH1_(0.5)+hG); + // Noise detection. + AH1 nz=AH1_(0.25)*bL+AH1_(0.25)*dL+AH1_(0.25)*fL+AH1_(0.25)*hL-eL; + nz=ASatH1(abs(nz)*APrxMedRcpH1(AMax3H1(AMax3H1(bL,dL,eL),fL,hL)-AMin3H1(AMin3H1(bL,dL,eL),fL,hL))); + nz=AH1_(-0.5)*nz+AH1_(1.0); + // Min and max of ring. + AH1 mn4R=min(AMin3H1(bR,dR,fR),hR); + AH1 mn4G=min(AMin3H1(bG,dG,fG),hG); + AH1 mn4B=min(AMin3H1(bB,dB,fB),hB); + AH1 mx4R=max(AMax3H1(bR,dR,fR),hR); + AH1 mx4G=max(AMax3H1(bG,dG,fG),hG); + AH1 mx4B=max(AMax3H1(bB,dB,fB),hB); + // Immediate constants for peak range. + AH2 peakC=AH2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AH1 hitMinR=min(mn4R,eR)*ARcpH1(AH1_(4.0)*mx4R); + AH1 hitMinG=min(mn4G,eG)*ARcpH1(AH1_(4.0)*mx4G); + AH1 hitMinB=min(mn4B,eB)*ARcpH1(AH1_(4.0)*mx4B); + AH1 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpH1(AH1_(4.0)*mn4R+peakC.y); + AH1 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpH1(AH1_(4.0)*mn4G+peakC.y); + AH1 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpH1(AH1_(4.0)*mn4B+peakC.y); + AH1 lobeR=max(-hitMinR,hitMaxR); + AH1 lobeG=max(-hitMinG,hitMaxG); + AH1 lobeB=max(-hitMinB,hitMaxB); + AH1 lobe=max(AH1_(-FSR_RCAS_LIMIT),min(AMax3H1(lobeR,lobeG,lobeB),AH1_(0.0)))*AH2_AU1(con.y).x; + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AH1 rcpL=APrxMedRcpH1(AH1_(4.0)*lobe+AH1_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PACKED 16-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_HX2) + // Input callback prototypes that need to be implemented by the calling shader + AH4 FsrRcasLoadHx2(ASW2 p); + void FsrRcasInputHx2(inout AH2 r,inout AH2 g,inout AH2 b); +//------------------------------------------------------------------------------------------------------------------------------ + // Can be used to convert from packed Structures of Arrays to Arrays of Structures for store. + void FsrRcasDepackHx2(out AH4 pix0,out AH4 pix1,AH2 pixR,AH2 pixG,AH2 pixB){ + #ifdef A_HLSL + // Invoke a slower path for DX only, since it won't allow uninitialized values. + pix0.a=pix1.a=0.0; + #endif + pix0.rgb=AH3(pixR.x,pixG.x,pixB.x); + pix1.rgb=AH3(pixR.y,pixG.y,pixB.y);} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasHx2( + // Output values are for 2 8x8 tiles in a 16x8 region. + // pix.x = left 8x8 tile + // pix.y = right 8x8 tile + // This enables later processing to easily be packed as well. + out AH2 pixR, + out AH2 pixG, + out AH2 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AH2 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // No scaling algorithm uses minimal 3x3 pixel neighborhood. + ASW2 sp0=ASW2(ip); + AH3 b0=FsrRcasLoadHx2(sp0+ASW2( 0,-1)).rgb; + AH3 d0=FsrRcasLoadHx2(sp0+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee0=FsrRcasLoadHx2(sp0); + AH3 e0=ee0.rgb;pixA.r=ee0.a; + #else + AH3 e0=FsrRcasLoadHx2(sp0).rgb; + #endif + AH3 f0=FsrRcasLoadHx2(sp0+ASW2( 1, 0)).rgb; + AH3 h0=FsrRcasLoadHx2(sp0+ASW2( 0, 1)).rgb; + ASW2 sp1=sp0+ASW2(8,0); + AH3 b1=FsrRcasLoadHx2(sp1+ASW2( 0,-1)).rgb; + AH3 d1=FsrRcasLoadHx2(sp1+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee1=FsrRcasLoadHx2(sp1); + AH3 e1=ee1.rgb;pixA.g=ee1.a; + #else + AH3 e1=FsrRcasLoadHx2(sp1).rgb; + #endif + AH3 f1=FsrRcasLoadHx2(sp1+ASW2( 1, 0)).rgb; + AH3 h1=FsrRcasLoadHx2(sp1+ASW2( 0, 1)).rgb; + // Arrays of Structures to Structures of Arrays conversion. + AH2 bR=AH2(b0.r,b1.r); + AH2 bG=AH2(b0.g,b1.g); + AH2 bB=AH2(b0.b,b1.b); + AH2 dR=AH2(d0.r,d1.r); + AH2 dG=AH2(d0.g,d1.g); + AH2 dB=AH2(d0.b,d1.b); + AH2 eR=AH2(e0.r,e1.r); + AH2 eG=AH2(e0.g,e1.g); + AH2 eB=AH2(e0.b,e1.b); + AH2 fR=AH2(f0.r,f1.r); + AH2 fG=AH2(f0.g,f1.g); + AH2 fB=AH2(f0.b,f1.b); + AH2 hR=AH2(h0.r,h1.r); + AH2 hG=AH2(h0.g,h1.g); + AH2 hB=AH2(h0.b,h1.b); + // Run optional input transform. + FsrRcasInputHx2(bR,bG,bB); + FsrRcasInputHx2(dR,dG,dB); + FsrRcasInputHx2(eR,eG,eB); + FsrRcasInputHx2(fR,fG,fB); + FsrRcasInputHx2(hR,hG,hB); + // Luma times 2. + AH2 bL=bB*AH2_(0.5)+(bR*AH2_(0.5)+bG); + AH2 dL=dB*AH2_(0.5)+(dR*AH2_(0.5)+dG); + AH2 eL=eB*AH2_(0.5)+(eR*AH2_(0.5)+eG); + AH2 fL=fB*AH2_(0.5)+(fR*AH2_(0.5)+fG); + AH2 hL=hB*AH2_(0.5)+(hR*AH2_(0.5)+hG); + // Noise detection. + AH2 nz=AH2_(0.25)*bL+AH2_(0.25)*dL+AH2_(0.25)*fL+AH2_(0.25)*hL-eL; + nz=ASatH2(abs(nz)*APrxMedRcpH2(AMax3H2(AMax3H2(bL,dL,eL),fL,hL)-AMin3H2(AMin3H2(bL,dL,eL),fL,hL))); + nz=AH2_(-0.5)*nz+AH2_(1.0); + // Min and max of ring. + AH2 mn4R=min(AMin3H2(bR,dR,fR),hR); + AH2 mn4G=min(AMin3H2(bG,dG,fG),hG); + AH2 mn4B=min(AMin3H2(bB,dB,fB),hB); + AH2 mx4R=max(AMax3H2(bR,dR,fR),hR); + AH2 mx4G=max(AMax3H2(bG,dG,fG),hG); + AH2 mx4B=max(AMax3H2(bB,dB,fB),hB); + // Immediate constants for peak range. + AH2 peakC=AH2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AH2 hitMinR=min(mn4R,eR)*ARcpH2(AH2_(4.0)*mx4R); + AH2 hitMinG=min(mn4G,eG)*ARcpH2(AH2_(4.0)*mx4G); + AH2 hitMinB=min(mn4B,eB)*ARcpH2(AH2_(4.0)*mx4B); + AH2 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpH2(AH2_(4.0)*mn4R+peakC.y); + AH2 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpH2(AH2_(4.0)*mn4G+peakC.y); + AH2 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpH2(AH2_(4.0)*mn4B+peakC.y); + AH2 lobeR=max(-hitMinR,hitMaxR); + AH2 lobeG=max(-hitMinG,hitMaxG); + AH2 lobeB=max(-hitMinB,hitMaxB); + AH2 lobe=max(AH2_(-FSR_RCAS_LIMIT),min(AMax3H2(lobeR,lobeG,lobeB),AH2_(0.0)))*AH2_(AH2_AU1(con.y).x); + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AH2 rcpL=APrxMedRcpH2(AH2_(4.0)*lobe+AH2_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [LFGA] LINEAR FILM GRAIN APPLICATOR +// +//------------------------------------------------------------------------------------------------------------------------------ +// Adding output-resolution film grain after scaling is a good way to mask both rendering and scaling artifacts. +// Suggest using tiled blue noise as film grain input, with peak noise frequency set for a specific look and feel. +// The 'Lfga*()' functions provide a convenient way to introduce grain. +// These functions limit grain based on distance to signal limits. +// This is done so that the grain is temporally energy preserving, and thus won't modify image tonality. +// Grain application should be done in a linear colorspace. +// The grain should be temporally changing, but have a temporal sum per pixel that adds to zero (non-biased). +//------------------------------------------------------------------------------------------------------------------------------ +// Usage, +// FsrLfga*( +// color, // In/out linear colorspace color {0 to 1} ranged. +// grain, // Per pixel grain texture value {-0.5 to 0.5} ranged, input is 3-channel to support colored grain. +// amount); // Amount of grain (0 to 1} ranged. +//------------------------------------------------------------------------------------------------------------------------------ +// Example if grain texture is monochrome: 'FsrLfgaF(color,AF3_(grain),amount)' +//============================================================================================================================== +#if defined(A_GPU) + // Maximum grain is the minimum distance to the signal limit. + void FsrLfgaF(inout AF3 c,AF3 t,AF1 a){c+=(t*AF3_(a))*min(AF3_(1.0)-c,c);} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + // Half precision version (slower). + void FsrLfgaH(inout AH3 c,AH3 t,AH1 a){c+=(t*AH3_(a))*min(AH3_(1.0)-c,c);} +//------------------------------------------------------------------------------------------------------------------------------ + // Packed half precision version (faster). + void FsrLfgaHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 tR,AH2 tG,AH2 tB,AH1 a){ + cR+=(tR*AH2_(a))*min(AH2_(1.0)-cR,cR);cG+=(tG*AH2_(a))*min(AH2_(1.0)-cG,cG);cB+=(tB*AH2_(a))*min(AH2_(1.0)-cB,cB);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [SRTM] SIMPLE REVERSIBLE TONE-MAPPER +// +//------------------------------------------------------------------------------------------------------------------------------ +// This provides a way to take linear HDR color {0 to FP16_MAX} and convert it into a temporary {0 to 1} ranged post-tonemapped linear. +// The tonemapper preserves RGB ratio, which helps maintain HDR color bleed during filtering. +//------------------------------------------------------------------------------------------------------------------------------ +// Reversible tonemapper usage, +// FsrSrtm*(color); // {0 to FP16_MAX} converted to {0 to 1}. +// FsrSrtmInv*(color); // {0 to 1} converted into {0 to 32768, output peak safe for FP16}. +//============================================================================================================================== +#if defined(A_GPU) + void FsrSrtmF(inout AF3 c){c*=AF3_(ARcpF1(AMax3F1(c.r,c.g,c.b)+AF1_(1.0)));} + // The extra max solves the c=1.0 case (which is a /0). + void FsrSrtmInvF(inout AF3 c){c*=AF3_(ARcpF1(max(AF1_(1.0/32768.0),AF1_(1.0)-AMax3F1(c.r,c.g,c.b))));} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + void FsrSrtmH(inout AH3 c){c*=AH3_(ARcpH1(AMax3H1(c.r,c.g,c.b)+AH1_(1.0)));} + void FsrSrtmInvH(inout AH3 c){c*=AH3_(ARcpH1(max(AH1_(1.0/32768.0),AH1_(1.0)-AMax3H1(c.r,c.g,c.b))));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrSrtmHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ + AH2 rcp=ARcpH2(AMax3H2(cR,cG,cB)+AH2_(1.0));cR*=rcp;cG*=rcp;cB*=rcp;} + void FsrSrtmInvHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ + AH2 rcp=ARcpH2(max(AH2_(1.0/32768.0),AH2_(1.0)-AMax3H2(cR,cG,cB)));cR*=rcp;cG*=rcp;cB*=rcp;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [TEPD] TEMPORAL ENERGY PRESERVING DITHER +// +//------------------------------------------------------------------------------------------------------------------------------ +// Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. +// Gamma 2.0 is used so that the conversion back to linear is just to square the color. +// The conversion comes in 8-bit and 10-bit modes, designed for output to 8-bit UNORM or 10:10:10:2 respectively. +// Given good non-biased temporal blue noise as dither input, +// the output dither will temporally conserve energy. +// This is done by choosing the linear nearest step point instead of perceptual nearest. +// See code below for details. +//------------------------------------------------------------------------------------------------------------------------------ +// DX SPEC RULES FOR FLOAT->UNORM 8-BIT CONVERSION +// =============================================== +// - Output is 'uint(floor(saturate(n)*255.0+0.5))'. +// - Thus rounding is to nearest. +// - NaN gets converted to zero. +// - INF is clamped to {0.0 to 1.0}. +//============================================================================================================================== +#if defined(A_GPU) + // Hand tuned integer position to dither value, with more values than simple checkerboard. + // Only 32-bit has enough precision for this compddation. + // Output is {0 to <1}. + AF1 FsrTepdDitF(AU2 p,AU1 f){ + AF1 x=AF1_(p.x+f); + AF1 y=AF1_(p.y); + // The 1.61803 golden ratio. + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + // Number designed to provide a good visual pattern. + AF1 b=AF1_(1.0/3.69); + x=x*a+(y*b); + return AFractF1(x);} +//------------------------------------------------------------------------------------------------------------------------------ + // This version is 8-bit gamma 2.0. + // The 'c' input is {0 to 1}. + // Output is {0 to 1} ready for image store. + void FsrTepdC8F(inout AF3 c,AF1 dit){ + AF3 n=sqrt(c); + n=floor(n*AF3_(255.0))*AF3_(1.0/255.0); + AF3 a=n*n; + AF3 b=n+AF3_(1.0/255.0);b=b*b; + // Ratio of 'a' to 'b' required to produce 'c'. + // APrxLoRcpF1() won't work here (at least for very high dynamic ranges). + // APrxMedRcpF1() is an IADD,FMA,MUL. + AF3 r=(c-b)*APrxMedRcpF3(a-b); + // Use the ratio as a cutoff to choose 'a' or 'b'. + // AGtZeroF1() is a MUL. + c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // This version is 10-bit gamma 2.0. + // The 'c' input is {0 to 1}. + // Output is {0 to 1} ready for image store. + void FsrTepdC10F(inout AF3 c,AF1 dit){ + AF3 n=sqrt(c); + n=floor(n*AF3_(1023.0))*AF3_(1.0/1023.0); + AF3 a=n*n; + AF3 b=n+AF3_(1.0/1023.0);b=b*b; + AF3 r=(c-b)*APrxMedRcpF3(a-b); + c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/1023.0));} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + AH1 FsrTepdDitH(AU2 p,AU1 f){ + AF1 x=AF1_(p.x+f); + AF1 y=AF1_(p.y); + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + AF1 b=AF1_(1.0/3.69); + x=x*a+(y*b); + return AH1(AFractF1(x));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC8H(inout AH3 c,AH1 dit){ + AH3 n=sqrt(c); + n=floor(n*AH3_(255.0))*AH3_(1.0/255.0); + AH3 a=n*n; + AH3 b=n+AH3_(1.0/255.0);b=b*b; + AH3 r=(c-b)*APrxMedRcpH3(a-b); + c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC10H(inout AH3 c,AH1 dit){ + AH3 n=sqrt(c); + n=floor(n*AH3_(1023.0))*AH3_(1.0/1023.0); + AH3 a=n*n; + AH3 b=n+AH3_(1.0/1023.0);b=b*b; + AH3 r=(c-b)*APrxMedRcpH3(a-b); + c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/1023.0));} +//============================================================================================================================== + // This computes dither for positions 'p' and 'p+{8,0}'. + AH2 FsrTepdDitHx2(AU2 p,AU1 f){ + AF2 x; + x.x=AF1_(p.x+f); + x.y=x.x+AF1_(8.0); + AF1 y=AF1_(p.y); + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + AF1 b=AF1_(1.0/3.69); + x=x*AF2_(a)+AF2_(y*b); + return AH2(AFractF2(x));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC8Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ + AH2 nR=sqrt(cR); + AH2 nG=sqrt(cG); + AH2 nB=sqrt(cB); + nR=floor(nR*AH2_(255.0))*AH2_(1.0/255.0); + nG=floor(nG*AH2_(255.0))*AH2_(1.0/255.0); + nB=floor(nB*AH2_(255.0))*AH2_(1.0/255.0); + AH2 aR=nR*nR; + AH2 aG=nG*nG; + AH2 aB=nB*nB; + AH2 bR=nR+AH2_(1.0/255.0);bR=bR*bR; + AH2 bG=nG+AH2_(1.0/255.0);bG=bG*bG; + AH2 bB=nB+AH2_(1.0/255.0);bB=bB*bB; + AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); + AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); + AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); + cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/255.0)); + cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/255.0)); + cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC10Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ + AH2 nR=sqrt(cR); + AH2 nG=sqrt(cG); + AH2 nB=sqrt(cB); + nR=floor(nR*AH2_(1023.0))*AH2_(1.0/1023.0); + nG=floor(nG*AH2_(1023.0))*AH2_(1.0/1023.0); + nB=floor(nB*AH2_(1023.0))*AH2_(1.0/1023.0); + AH2 aR=nR*nR; + AH2 aG=nG*nG; + AH2 aB=nB*nB; + AH2 bR=nR+AH2_(1.0/1023.0);bR=bR*bR; + AH2 bG=nG+AH2_(1.0/1023.0);bG=bG*bG; + AH2 bB=nB+AH2_(1.0/1023.0);bB=bB*bB; + AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); + AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); + AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); + cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/1023.0)); + cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/1023.0)); + cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/1023.0));} +#endif + + +float insideBox(vec2 v) { + vec2 s = step(bLeft, v) - step(tRight, v); + return s.x * s.y; +} + +AF2 translateDest(AF2 pos) { + AF2 translatedPos = AF2(pos.x, pos.y); + translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x; + translatedPos.y = dstY0 < dstY1 ? dstY1 + dstY0 - translatedPos.y - 1 : translatedPos.y; + return translatedPos; +} + +void CurrFilter(AU2 pos) +{ + if((insideBox(vec2(pos.x, pos.y))) == 0) { + imageStore(imgOutput, ASU2(pos.x, pos.y), AF4(0,0,0,1)); + return; + } + AF3 c; + FsrEasuF(c, AU2(pos.x - bLeft.x, pos.y - bLeft.y), con0, con1, con2, con3); + imageStore(imgOutput, ASU2(translateDest(pos)), AF4(c, 1)); +} + +void main() { + srcW = abs(srcX1 - srcX0); + srcH = abs(srcY1 - srcY0); + dstW = abs(dstX1 - dstX0); + dstH = abs(dstY1 - dstY0); + + AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u); + + setBounds(vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1), + vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0)); + + // Upscaling + FsrEasuCon(con0, con1, con2, con3, + srcW, srcH, // Viewport size (top left aligned) in the input image which is to be scaled. + srcW, srcH, // The size of the input image. + dstW, dstH); // The output resolution. + + CurrFilter(gxy); + gxy.x += 8u; + CurrFilter(gxy); + gxy.y += 8u; + CurrFilter(gxy); + gxy.x -= 8u; + CurrFilter(gxy); +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv new file mode 100644 index 0000000000000000000000000000000000000000..c15b72ec6c278e354720e2518f556e6328973ab3 GIT binary patch literal 44672 zcma)_1-Mq@g7jIGXClw(+TYFxM02HJNVFut_Ao*j9+w>H+^Z|uZvM*oz=92=o`tabNR@1K0e z9n>V!r`J@UZmrGr=|6JJx)axJubB3}Q|;Yb+knT99WZL#hymk=Fk`Dzgg#rNj~VS8 zPuJQ3JZ{MN-lHds8a%G;6Tfb)?Xe9UJ%0S?k=qOzI^NiBt$yYlJ*NNgVf&+Wtn1FS zeaDU6Y{0k){reXE?1Vn1@!9PspUq33T??N*fAZ;F`s^{)XV%sMKlyArdepK9PgryP z^5DX${^9Snwjb@F(WBbevD|rvdrGzK13UQY;e$H3rvvusTZ3tbj~X*!{4T?X95QC~ z*zsGA>NkAikP+h;nD!z6FZ=G=r)v$3eaGRGhIERZt~Km`*_ychv5lTEo{l>QnsfO7 zwCzrRv$Q5)Yu03^0b`uaHJlk2H%G@rX~5U>VkGT=O*^nRv;i!R`e<;+HQlAXo`Z*v zZR-c2j~FtljmL^d4{T#E_(3Daw6Q0NbLrjdc-p3>9fyqXSmCCofvc@Q_22)sfY#u( zmwpcer{BZDV>rq5IRZRz_>OISWE=0)#z(dB&TTxojdy9|qroG%pp2a&=AsiHBW~v5 zSaF{Y{W$Sv;+a}!f}62)Ozy!GnAz%V#K(Lm(~cQFv5ijw4{z7owRI}EJSR`<;8V}Z z(>wUobMlN1-kg)&Tj#>LR}LRMr1$8F#dUWM`oSEYBNxz4y$AFeGH!g~cRsd``#|^B zCE!gbj2+u|_=xdC#&TV8v#|Dyr}}hly&@hkc7W%J*U+04f4kxzRQ%J5e^v2+SNw;H z|5$O~RLgp%t$3G;cdK~!icjB(cWccKA3S{I5T+FJX* z(xo-01@m$p7Y7gQxW%{mlJN1(9o}92gwyv!$E8ef-yH>o7t-i`gk%Kj02 z_yI$QB>%^i{gcL?{GV3#FB*IACjXa}{cHGvBlaJb{NGge?;3mZe_z>uXzab4{6ALq zX}EDunlyS;@=psd=cij^PyX(eeTK%K{4-YeS>VpUpV^(?*(&>76<+}Ee8zvl%D!mD zmx4P#@n5F0uTb&T;Ep%`YgG1iI`J;84dDZE_8i*;JgC8&f`>P_FZh54ZwVgS;BCMT z#Mte@2R67rctnGD2JheC-M|h+|2@H@8@vzLg4G9rJ3cRVZ4Ctfv{qeOgW(;YAG@}O zbh34A4X^lt6(3pg(G?$4@q;Qpw&LR|KB3}=RQ%A2A6D_n6`xY^V=I1q#ZRdCi4{Mo z;wM-9)QX>8@iQxacEvBK_(c`Jq~e!W{IZH)Uhyj`er3h4uK2YTzrNx(R{YkA-(K-M zDt>3h@2dFS6~CwA_g4J=ia%KKhb#VQ#h-+8-K@@Si0i0p>zNMjZLfV@y#Svyc68hJ zQWg73#ows-2NnOM;$K$$U+{6`#|{}kX#aM9Gx0(Br|YxhzKjU_7txzF{@$V}B?~2dB5548}JyXSJsralF zpS|L9R($S?&s*^&E53Bam#z4662Rq<^q zzDLFPs`x$?-?!oeDn6*0g&#w5n6+f@y z7gYSBieFOkODld;#c!$jZ56-0;&)X1&Whh%@p~)&NW~xT#JjYfg7X-R_u}rYSHV-C zD-Q0*ZxcE`FU-k9s-LdmZmk92!$%I=zWoTh`3$iX+MvdN9dYqoe%%i4^w_{51V)>W1GJH`@&%(_T6SQ&+4`ubui6%9yD&*C%MMq`_tIEw|}>Y|E{CQ z9=Q3~(G$k#>1VVDJNb2OJyh{WD*kB2AFKG|6@Q}QPgeY?ia%5FXDj}E#b2)Y8x{Xc z#Xm0i7R-RpX?-ox(X^r|tMN87RpZC{w&pX^`0+8bt<79$#uz($p>fK$wYdw8Qrg=5 zg*IKIEmUasXTHS>&1bS|OLA<^&#aEa`ZlLxpU*B1_7&a(9J|k9wU=Bwa@*hQPP=0> zUasAj+}~Bt1YWS={@z;tvxa}#@CD#=(Tn}`Ae;Hb^MY_`E!IA|@|%WM`%vpae#a|T zpAYWb$~*FOTxgT$Fm6S-arUj>N)1-e;duJMjn8;C1FMLRt;9~!8yAft+DbB zt!ryXuw#VF3urIl>FSps`jlo?r%U(NiEZ!Wmkl>T!!HuJ~+Gqf2Rdk?U- znK{fS_jf#M;r@+s(s{O%6>RQ@Ua`RAV7DE2OP)_QvC`cL2H zvp=#NWAVHm#$qnt;Qhggd(lO1wbl*)nHOWLHMNc<;qOj5_u&<}rk(GJaL+C4jdl*2 z=U=JaiY8Z&-!n~YslDH5=8WHeo7htO8E;O;m9cA~IZvh5uhGib1Dn`VJHFA3Prp~9 z>7(X+cwWAN#=oijZ3yK}<)6b{ulRoj_gwQln-7CF(sg>dH0JQ!nqLQA`g(53eJ^Wk z@E&06FlI$Edk!ywW}P`4zqyt}H`GJEcEi1&np^JYQSIS=9xZv#iu-vqcJurBu;jZp zJmcR3?kQz{{r$Yizp32Mi*oP(V*p+Y`djZo@M*y6#>;&lSJOVe!N$by`?_}fS3eA% z^^v~@_r4PQC-6<+D^i91_&Gq$`=7_!tajE1+OF+iD4qlM={?!cxUR8$AzDAjhTEP{ z@$)KvO~vmhxbFde-c3HapLfH3&+zkZ$set_pLa{UpLfG;{k&UpKkt^@&$}h}^KQxg zyjyZV?}j@+e%>v)pLa{{=iQR~c~{hamT@$o(%pMB8O z)BnC;Sp4`L1_V2wcxeq{7kI#XPkN3a$j6hRQ|0BW1si*%@;PkKE{=Fxto7-!A0*BYV zpE=C!b$oE6>+kg@?}>jOY=?k1r&UjmL&4q~${ds6>c1w=oQH$eyk{it2(WS93)1(I zaP`C;1y(bzUsJF;x4yS;f=W-2?K~s;AF;CFSWBUzw_r~@l ztvt4;!HIhcERXHC;KV%xmdExScmRj}JxeQ(?RVh)8{6}=@}B&qg7v=u_WRP0&_2rX z7{}ur)@*IoU|;4m)|`G8{yo?j`&)-r9@~pxb7@?{W;Uf-j*}_nh$a3FS&lHi7rPI@EvQ6U(|B z{%ink{~ZfW`{#;vi1o9p&nCRKea2{h{Vr|vAJN6h;k|t}4qrYeOpaN>=J1(pa$Esc z55KbEx!$jWU%{bnKl1eBGrqYwe9g&`e&zz(&pfordo5T!{JM%?-|&8n%)B>n7^9yy z`&LWe9nV@2c+JO=zI~2t-#&*;UpIl(!*8zmEfv4D;r*C-`?-z79LDLZ-FnnhkLS#y z9KIIjupY0qML4WyFumjs)8Y1-tz3#@KwLZXMPUEy$vszj6@^D{e&C9@3|FQ*M4s6XU(58lm z!TN_k0yZZ6F|cu|@o}))Gpvs_KEYuQ;~wSEZk_6>({pMy4qq#ASlf!UD;Ic`0`t|+ zcAcx!T8Ht!;m|+)DX=l&PlJt{nY#3UhGQBI$D*(H*nSI+&Aj?rvwB(c+HhZG&HnN( z^{-jrwZP8pI<%?jd9eQBzXKZ+{sP#z)b)F?b$K4@t39?C!LgZFUu#xR&E3I0Iebme zVV%>_uFGL<>(TmLL+up~TknrzpEG%XG{>tPGjga~tJ-TE<~gzPc^#~ux^>9QIyQj& zD(hGuZXFxarj9qk))D@8!_)7d+qRB6^?idQb-o3*PIbp&Y&nj1;M&z^rV_{TE{F5z zHDGRYyu;xbw3TDn81Aba!$xq&unBFR5q$v8T#Dc4utu+s4>=a)P|ukD3O0||lsM_}XB^|9`cIn=FFT-Mne?yIb`7u-5GrA?imf~_&@=Zhj{Q@a0KoynfpA%w$He_3Pt0%CnAsM%sqM*H#>^ntqispBy!>oCFSeaIeC^1QK6e7!&n~oHJ4+L{3`fpo*JY)K+n2v5Tam-x zk)`jI!RE1VedXQDr+UV*K3L6h zh|6*8Nj_iYIQD=$j=g9zj*W;b$FWJnGmeen;zU%4LgIaA@A{WeeA+oDJ>(~!&9qRdg&j7G-+A`LGVD*f3uy*1G(VADTkJ#&{ z>|-e0KGZXgVPMxIb?y(>Pdz>dfU`zkgYt~^z&6HCn;av+=Fw&yBWcSzM!~H^J!2gW zHcnf{ItHwsu^t58kGOo+lIxQ?$AP_f*{AmcdFmYB#{3?PHu)!j)sz2V@Brd-?#lH^ z{)ymWO@8k!^5j3Xjrsi)ZSqe7t0(_q-~q(vyp-!>z22K%#+H0Xz?~!Y^nVmMX@qQ_f z|4D7k@1$r`)5&1-XiH6}fYnpeso(+hlXaHsV@;>gmNlIYwZ$1>aOUYkusr@3w=usL zqfJehfX$;VHT@i{o|-NNTT{*_xjxo(8Ett!T@JS<_0)7FIP-J`Se}}$YU5T*JvCho zHjlQ{bPZTNHC+p~ra`pUA=gKI9c?*J*TbzzJvH43&OF@!mZzp)v@y?0X;afpVDo58 zO*ezpQ`0SAYs&c~*Teg!sWO;_Y;M;#xt~T;kIyq; zbC*89MN?1iXTip)AKiRjd=6}%+LG&eu=8!5p4W2yV*eeu9_tHe>hbwKxUT0#H1(sJ z&$Tas)l%Odz~)g;o|nP(*k3_YkI$>%dR|{cQ%~;K!N#c{-HiQ@VEfdTTz>*P_FPMH z{harincqKyJ%_{Jfa`BR+TR4L>F3;OQ%nB0!I|5vc)HTDEs_3+&c3g3Y0xIhYA-oVJWXEq%-aF6UrYxbu`gXM^jbZoKzkHRI=?ui3%AcO>5&aD7ha z9M(Q3SReJo%mwy6%o_EX8?L6WeP~ll&UwJ*44)V5`(DO4A6!j8P@1@71`#O{( z&le{(*!Ppe3jOc~PozDfz(*GNr~*%Juz8Ly@RR}{Q{ZC@d>q*K-V4R?K=i}G{m&)}Z->e`(5GGnRX49|ROVe0ZeI1|T?%a8^8I9KH1*_N25hW) zuG3|~_NOg#pyqeCGCteP$#P)NW#?oCurKFCeR&Qw=R};^Rs`2$UI|UzK0W8k@vQ=P zeCo-uD!3lsYG~>i-|Apv)ib^|!1kpr<5P2dsns^)TNCW{>G;+L`*M8hYjLPKKC$_{ zKFZqHgc($Ps}}!$U~}es;5&f1Y^jdt zrG;OAu(9#mvGCKD&s94$`m)5EOF#Sdo~bSU?gCcR=KWKi_v&528xyadShd9N4ld_x z5BL=PGiQ6k^-(w8`>|T$_X4Yh?+u=xBWt)1SReJQ{k~w|56gSnerW1Pa}K$d1Hfv= zCvG4(an>T&FMSUJTTAA3FgWwCEq+75&R1$23f5QMyu)bKGKc$vH=ri<%%NKH4F{Vq z{D6ih|ABBd{SvPh{}JFaek8n{!%=X3)Dtrr?B|Hg*BH2(zV@L_E&gM{zW;}hgO}IF zc(^|5#veqh7XO36<+X7L+|M65PbR|kQ8#`9ty zu8kub-D|^K`q}THwAzwyGFVO9QMB@`>(OAZ4fVvTCH5F_IcLYh%P}7Z*GJv>DYR;d zKOU?Wege2$*AwCTsOKC%3G6vu-WN|sQ_otT0#-9Vai@Y4XDxF5()Vd#YstKx4$k~* zi{BYw=PT##nP7d@&3hKDTITR)_|}s*@XhBJC*+_V>D{7WlLRpI+cI zz`i%0MVn_`m(XuMfBNj}=8E4@@y9FvY{g$L_|h!sn*}%jTLsttZo##`S8(kg6kPj< z1=s#j!L@%}aP6NIT>Ga5*ZxJpwSQS~?OzvM`!@yG{$0Vfe_wFzKNMX1j|JC0O_$E| z?;em@5AEFwZhZHGYoDRu+T8=PjCT*na9j6)EV+9?hMUhlAj56l1G41q0U56UQUzCc z56IYU-2*aQfA@e4w{;K5lDh|F$=w68+jx?iP!Glk>UQ#?%t6lckjrOyLV*C z#}-`Oy(43{b??ZMyLV*C-8-`6?j0F!eUl5W?%t85{n&zA|M3-f@5uOTckjrOpHy&l z_l}I+*1aQ3?%t6lckjrOpIvb4ckjs3?%t8%+TA-c-1^)*vgGa^S#tM|Ecq1$SHH62 zS6BSnio17Y^4Z>4aO-pL$kOiKk>T3!D7f{zcVz5x_l_*NdqFIC+ABTM@qEAD=hrTx8%e^7Dvmn{9ithoD2 zmiC$WVJqD6xW8n$t@}%s-2Ej>?*5V`cYn!}_o(>d6?c!x(%(HMOTI$E{e0m*ld;SD zRNQ?gOMBmnyU%24A6jwunJn$@Gg)%?nGCmeugQ|T*JR1vZ!+9^rxaZM*n&I%$5-6_ zCKIpS{U*b0PpP>3O~!72?l)O-_nQp2b-&4Q^IuuHyLjK?l)O-_nQp2 zb-&4Q`***|lDpqz$!9OPy8BJWZtH%N;r8!-lOHyLj0 zev>74zsYd>cfZM!Z(MNoUIn**_nVBryidj5Z!&gU_nR!a`%Q-1zxz#=-2EoQZTBj; z{kh*{?DBmpKA_74zsZsxUT}5yn~dFd za>4E2{U&3VpILBq_nVB}{@rh~ehXO5XE*X&!S*qIV>8C*2V?=}x%M zIkHZ7!PPu`mFsjjns#5An|m6qoSS>m^wpNRxeu(Kb-EvHE$Y^0Ef0XpS{{U}dGN2( zIz5D--B)UPxY5d59zoMrTWWa}te$mx3~W8>&Y90@)G}vJfYnmxufaYK%A7q3SM%^y z&e?C!wEIe(Pc>Ru=hJBVYD=BZfYnpyZ^720Zk;|uQcIoBfy+9dhnIE!4zA|GzfSY{ z0)lp5sq^=ZR@V6|el%^;w)evG0R-BUatmcWKoU`&V#cKLpDY`#12O#Ht(n0j*kM{|@%N(DpH{ zJhA@(TeG_Mk7(5r`x!W~pMvFy{Tyt~>c)OTtCrZWz={15EKlq|!Pcy9>=(3ZiTxJr zIil?wT6tps1-52&?O)TXCHCLo#C{K!C-y&JYgRY*J6g5G{ui9sAHnj(;@+-V-Pj*! z)e_qU?D?Y2XN~g2b_H9rx^|yUswH+haAMofA>oOg9&F9(#&)AsOYBVG#QKap43s*z-l3&o6i3f=TS`@CEQu_a3eP9AIO#CC8j#&vkS7%(KifH{2ZR z$uSSu7;VWhFW7V5>(GAYgX^arZGN!*YqKw(zp5q2&%mBX+I+TJ*0B)WI@D9g!eC>x zCC4IQuK{!TJhsfS7~CA{$EVZFP{yoCCAcW&o6C0 z_buyK7H%Etsbe{?G1`)2d9c@pIeb=J=2#JK4)x?%32cnEiTa< zE06!SVEwiX|VE06!aVExPd`@z-q--lK% z9!UEJ^XUE6&*g*Qv(c(+A3&=n9zq*m*Jvo*?+&SJA55#}nwaYVu<_c4)5^16Bf$D= zJCIhMxKZFsntQ}Zuw1{u@YsZvNwG zGxsNevnD4N`biB=Kc~Pq;V}MWT6t`zfiu^qg5|ND0nS{X4wlDu7T9acoM+O?V><`D zePcVDR-Sb@7wkGrrghAY%`rI^$6)Q&Xf5_>ZgZI1bvO@fto=H#^7MN?*qqv&S9#(t z1ZN#C0Lv41F*xgR5m=tMpM$dwmw@HwzmzuXa9M#b2WL&LX!P`R6+Cl&C0HKYHQ-G- z%zZVjJhtn=Ij^q;%VWC%oVmUpERXFMVDC5PypdL(b+`%aIye`O*|9k$$Kn{Q-5RaM zKFw_obGr^VgN-$}^D0ljw}8#5&3Tn4?ly4N;a0FbalZs-9c~B96Zb1{*5MAY-28Xa zW*zP-@ZANz2ke@ppZmZ$ukQuRV|xIcxxODPkL@9F=K4XfJhn%`{W+}ZVOn`?kAe4W zY>(2)vks4gT?gmFF*`QL7vb7iiUz?^ST}y#kge-)rFB+iY!8Q6R| z*FOiV>1%#%YRUfvcyMF;k~TkUcn*FAcW%_pr}j^Prs0YI7OtjW z;?*)A-+?n9$?-j!dVK!iK@{++bt0CC5Bq&!N;bFI+$M%=dg?``2b)e!rpYYXP{mscZN94*vHsGWLZUyL$Rv z7+m(d2wXq)^t&k7+}g4i)M8-k(Pm$MH$zRlIBj`NEdjSKb?r;hF3FL)mTv6oscRW< zS=X{~{nS&}a$s|7Gj9)Cwd7b4tftNHYRJ>)N?^|eb?tt)LoMgxDqx=zl|Lu0il!c) z)xbUnDt%T*Q;*LYV4qo)K5L?>XK&KAz~(kqTVmG+`+TST*>fE<_4up{_W4ce(-TcS zKI?&f)>QhekEWg)HUOL3SZ#^j5bQjZ_m7Rx)Z?=;*m)>@HbGO5PcN|ZQ2O-N&XF26 z1)JMgZLUinTIWZ+8Lf4S`_k&~zH^zq5Ut6N7$7d_B^H}<9 zjiw%-ZNScB>9Z}Gdd9FF*xbfy^ZsOg+k?GtE!EVjpW|4WR$Kbm0bHMV{n6Cpvm>}Z z?{-2{kI&BF`n=l(O+B^l3O2W~+7i1PxIXW8M^lf_9^m@C+Y?PaK6`=d^KNf6_0+Hr z*xbfyOYFYj`n=l@O+7vX!1Z}I5KTQkgTVE9HyBMlH4Fip+gNR`%TQY9M?8$yI>r0b z>Ywv&ICwSIRDb=nr|$#6^?7$7ntFUjfa~*aB$|4BMuF?|ZZw*D#xMqKZez9OygLZ& zdFT70wd&_M`q63=kE6}AD9_dL@VRN#wU4D$6CX?)Uq2fi0{7mou6+Wnn)hHon;i=F z=e+!`dJ=phhkC|$82HWVx;h+9Jw8W({ry3C?H!4x9-pJY{;s3+nT)2Mc{&^L366k2)SM~(wKE^Wus%EiaiI-ZR6M6jPokp*0Jvv%J?|fTkXw3&E~u>2nd9dVDShyPl=bC1~nd z&!2^I-ZR6YH-%`DzH4ZYr$F1Yryj4zaE_R zybdgn?M86c^9HcozJEdMShAisfn85)w?=ET2KzFnvBsF!^}HFZZ`MS^4J~)XFVSQ%ai|caMtrNuspV3 zgR`DbfaUi6B&}n~dj1CNdRn_RTAMZ4mpP3!#=NfQQ(%3wp7QkhG}s(jPq{YN^S9t~ zJ)ea?!=avacn(~@w?2=i9-rTV>-W|d(A4AedvN{U`XZWo*7GH>xsBD9@x2Vrdj0_{ z&-h*iXFXp5%N^@$w2miZ{UbQ*`8rr0+n>Q%&p(0X$^Rxe>-h#)9^2dCtmj)`xqZJw z>sYd$?}A-VYqv&gvj+Pzr?JME*Y$i4tZ&v+o<9EqHb>S|uFduQ09>x;hw%3~)UytM z1=pV$|AwX>pO3)xXU30>D}4SAu0J#W15G{a`3czE#%jy>J_BbxKLyJ(zAwO8&(Fbf z$ND9$)$eCJ zo{V)iaMsiBYRhAr1Dy5rd)xBlp9`Gz^gG=0*yaIeJ^enn+`j!Tw`0kA&Ifiqt=$@} z%^K{>oW>esUe|Meu)bMOdHP%cY>up_T$}5;Ah^6f7lQlW{#4I8EDWxH4p;z@M_LsO4W4{-f+z~X4?S$xOYp7AXM&U*Ttce!Kr`|ge>V_hDc z_4K>%^4L}cXFXQ{%aea)aMsiB#LHt_6`b|-`|)!7_Pg0vYv8nuIJj|ay{39uf?IBbyyeddX}F{d!nhwXFag%S^BJx zrXHUSz^-TMvmu&#)^j7UxsBD9@ofUmdTtDsXMDZES9xdm7r+t%Q$=T>03eQ#sSk@egb?0Qn(*K-GOxt{&u{W#RK4m*Nf&+-}FPH5`!*%|D5mOi_nsmEtmu8b`-0`M4FqRB2Y}_t zKNy_#90ZofHWZxo90Hcx_b^(=lJ(pl?0Qp22kuIEVjfgI{thf!eHvwYthjiw%-F<{rT^f?GkJw9W>u4m~p4oyAlIUa0o zW3^>`2ZOVo6TtF}Zz4GBc?ekUSP!LjJQ?d@;H>8)uspUSz**13!Sduk3Y_&k5-gAH zXmHkZGFWckQ)nGa*7F#!>uK%QXl>SDU*}Y5&lAAq zdY%YBov%HO^TAoq^T6`hE(B*iF96Gv|6*{~^CGZ3wx5Huo|k~- z_I)X>W664626jEI-5RaU8tluQ#u{T@*Yk3)zFALs`n&>cj;yC#o9lTMxLnVx;a75~ zXC1BqyPoBBbuF5De69n#o~6(AXzKB~0qlB~J~yJNXFY!bHn*|bGQOL^SE;%IgK^Oysqc{V12Wm^7Q!t*c@3;dEy=f_iKKBG0#Krl?(nb+<8o{ zN8y>fN5Jy@&h4=_?)W=1`+6MSp>^!(_ypYN&g%Xi)c$@AwhwK2ru`&1&$O*apV)rW z*p?#J+V!=sli}LRePVomeJ+QuvpL)+#=o;Whr{ncoJX6zUY;U0e+%b%{xp1Xig4WW zXW+(Jhy1s2Yh0B>U+wXI7QAE;`y9NCeIA}zeYNNL`R~By*2i&Kms+lg7r<)eGq&Hu z)#LMG<5Pa_df(64|d)zq|Lm&N}ThSJt_ZE z@oyWR+Fpa3r>y^VH1+uWvGFPE{}Y;een0(ZuyN|nlXa+>V{Rt*P4JwwZ_wu4eG9A} zpLZIcw+o+l!Rq;b(tBXz)cqdO2eiKYK908cIsVS!yoi(gLvY>yU(wVv&cA`JyPS)U z;OhAA<(WhYhNWRa&$!EM=pN#Vh zu;cukHaWfotHF_0|Ba>| zpZ_#IWsN_esUOYx;(g*ruyN|fc~1Wq?AXimP-pe5&oppj)uT-dR`s)S(?^G z9A5YCrFAifHUFG8*ZrKt<+@)HeW{ACUvP75P;mF;+OXmqSG-rnH!ZmL@U0r|m=}W2 z1h>vyOACT$MpO6inQ2Rk|Ak8vS1b^jjn9&#=W zR!htx;ChUUqNyilF|e9r>_NLYhk4Wok=HuZmf-OH)jF*=eJl;G>skg)J@dFMSk0K_ zXqV?$oa;901#PX+=(&zogd3+mk~&V34fb<``Y8N8m$u-&x)P&3OH_d%yL3bqw~aJ)Z~v4>v$_kpKVy literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.glsl b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.glsl new file mode 100644 index 00000000..785bc0c8 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.glsl @@ -0,0 +1,3904 @@ +// Sharpening +#version 430 core +layout (local_size_x = 64) in; +layout( rgba8, binding = 0, set = 3) uniform image2D imgOutput; +layout( binding = 2 ) uniform invResolution +{ + vec2 invResolution_data; +}; +layout( binding = 3 ) uniform outvResolution +{ + vec2 outvResolution_data; +}; +layout( binding = 1, set = 2) uniform sampler2D source; +layout( binding = 4 ) uniform sharpening +{ + float sharpening_data; +}; + +#define A_GPU 1 +#define A_GLSL 1 +//============================================================================================================================== +// +// [A] SHADER PORTABILITY 1.20210629 +// +//============================================================================================================================== +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +// MIT LICENSE +// =========== +// Copyright (c) 2014 Michal Drobot (for concepts used in "FLOAT APPROXIMATIONS"). +// ----------- +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// ----------- +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +// Software. +// ----------- +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +// ABOUT +// ===== +// Common central point for high-level shading language and C portability for various shader headers. +//------------------------------------------------------------------------------------------------------------------------------ +// DEFINES +// ======= +// A_CPU ..... Include the CPU related code. +// A_GPU ..... Include the GPU related code. +// A_GLSL .... Using GLSL. +// A_HLSL .... Using HLSL. +// A_HLSL_6_2 Using HLSL 6.2 with new 'uint16_t' and related types (requires '-enable-16bit-types'). +// A_NO_16_BIT_CAST Don't use instructions that are not availabe in SPIR-V (needed for running A_HLSL_6_2 on Vulkan) +// A_GCC ..... Using a GCC compatible compiler (else assume MSVC compatible compiler by default). +// ======= +// A_BYTE .... Support 8-bit integer. +// A_HALF .... Support 16-bit integer and floating point. +// A_LONG .... Support 64-bit integer. +// A_DUBL .... Support 64-bit floating point. +// ======= +// A_WAVE .... Support wave-wide operations. +//------------------------------------------------------------------------------------------------------------------------------ +// To get #include "ffx_a.h" working in GLSL use '#extension GL_GOOGLE_include_directive:require'. +//------------------------------------------------------------------------------------------------------------------------------ +// SIMPLIFIED TYPE SYSTEM +// ====================== +// - All ints will be unsigned with exception of when signed is required. +// - Type naming simplified and shortened "A<#components>", +// - H = 16-bit float (half) +// - F = 32-bit float (float) +// - D = 64-bit float (double) +// - P = 1-bit integer (predicate, not using bool because 'B' is used for byte) +// - B = 8-bit integer (byte) +// - W = 16-bit integer (word) +// - U = 32-bit integer (unsigned) +// - L = 64-bit integer (long) +// - Using "AS<#components>" for signed when required. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure 'ALerp*(a,b,m)' does 'b*m+(-a*m+a)' (2 ops). +//------------------------------------------------------------------------------------------------------------------------------ +// CHANGE LOG +// ========== +// 20200914 - Expanded wave ops and prx code. +// 20200713 - Added [ZOL] section, fixed serious bugs in sRGB and Rec.709 color conversion code, etc. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COMMON +//============================================================================================================================== +#define A_2PI 6.28318530718 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// CPU +// +// +//============================================================================================================================== +#ifdef A_CPU + // Supporting user defined overrides. + #ifndef A_RESTRICT + #define A_RESTRICT __restrict + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifndef A_STATIC + #define A_STATIC static + #endif +//------------------------------------------------------------------------------------------------------------------------------ + // Same types across CPU and GPU. + // Predicate uses 32-bit integer (C friendly bool). + typedef uint32_t AP1; + typedef float AF1; + typedef double AD1; + typedef uint8_t AB1; + typedef uint16_t AW1; + typedef uint32_t AU1; + typedef uint64_t AL1; + typedef int8_t ASB1; + typedef int16_t ASW1; + typedef int32_t ASU1; + typedef int64_t ASL1; +//------------------------------------------------------------------------------------------------------------------------------ + #define AD1_(a) ((AD1)(a)) + #define AF1_(a) ((AF1)(a)) + #define AL1_(a) ((AL1)(a)) + #define AU1_(a) ((AU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1_(a) ((ASL1)(a)) + #define ASU1_(a) ((ASU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AU1 AU1_AF1(AF1 a){union{AF1 f;AU1 u;}bits;bits.f=a;return bits.u;} +//------------------------------------------------------------------------------------------------------------------------------ + #define A_TRUE 1 + #define A_FALSE 0 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// CPU/GPU PORTING +// +//------------------------------------------------------------------------------------------------------------------------------ +// Get CPU and GPU to share all setup code, without duplicate code paths. +// This uses a lower-case prefix for special vector constructs. +// - In C restrict pointers are used. +// - In the shading language, in/inout/out arguments are used. +// This depends on the ability to access a vector value in both languages via array syntax (aka color[2]). +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD1 *A_RESTRICT + #define retAD3 AD1 *A_RESTRICT + #define retAD4 AD1 *A_RESTRICT + #define retAF2 AF1 *A_RESTRICT + #define retAF3 AF1 *A_RESTRICT + #define retAF4 AF1 *A_RESTRICT + #define retAL2 AL1 *A_RESTRICT + #define retAL3 AL1 *A_RESTRICT + #define retAL4 AL1 *A_RESTRICT + #define retAU2 AU1 *A_RESTRICT + #define retAU3 AU1 *A_RESTRICT + #define retAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 AD1 *A_RESTRICT + #define inAD3 AD1 *A_RESTRICT + #define inAD4 AD1 *A_RESTRICT + #define inAF2 AF1 *A_RESTRICT + #define inAF3 AF1 *A_RESTRICT + #define inAF4 AF1 *A_RESTRICT + #define inAL2 AL1 *A_RESTRICT + #define inAL3 AL1 *A_RESTRICT + #define inAL4 AL1 *A_RESTRICT + #define inAU2 AU1 *A_RESTRICT + #define inAU3 AU1 *A_RESTRICT + #define inAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 AD1 *A_RESTRICT + #define inoutAD3 AD1 *A_RESTRICT + #define inoutAD4 AD1 *A_RESTRICT + #define inoutAF2 AF1 *A_RESTRICT + #define inoutAF3 AF1 *A_RESTRICT + #define inoutAF4 AF1 *A_RESTRICT + #define inoutAL2 AL1 *A_RESTRICT + #define inoutAL3 AL1 *A_RESTRICT + #define inoutAL4 AL1 *A_RESTRICT + #define inoutAU2 AU1 *A_RESTRICT + #define inoutAU3 AU1 *A_RESTRICT + #define inoutAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 AD1 *A_RESTRICT + #define outAD3 AD1 *A_RESTRICT + #define outAD4 AD1 *A_RESTRICT + #define outAF2 AF1 *A_RESTRICT + #define outAF3 AF1 *A_RESTRICT + #define outAF4 AF1 *A_RESTRICT + #define outAL2 AL1 *A_RESTRICT + #define outAL3 AL1 *A_RESTRICT + #define outAL4 AL1 *A_RESTRICT + #define outAU2 AU1 *A_RESTRICT + #define outAU3 AU1 *A_RESTRICT + #define outAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD1 x[2] + #define varAD3(x) AD1 x[3] + #define varAD4(x) AD1 x[4] + #define varAF2(x) AF1 x[2] + #define varAF3(x) AF1 x[3] + #define varAF4(x) AF1 x[4] + #define varAL2(x) AL1 x[2] + #define varAL3(x) AL1 x[3] + #define varAL4(x) AL1 x[4] + #define varAU2(x) AU1 x[2] + #define varAU3(x) AU1 x[3] + #define varAU4(x) AU1 x[4] +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) {x,y} + #define initAD3(x,y,z) {x,y,z} + #define initAD4(x,y,z,w) {x,y,z,w} + #define initAF2(x,y) {x,y} + #define initAF3(x,y,z) {x,y,z} + #define initAF4(x,y,z,w) {x,y,z,w} + #define initAL2(x,y) {x,y} + #define initAL3(x,y,z) {x,y,z} + #define initAL4(x,y,z,w) {x,y,z,w} + #define initAU2(x,y) {x,y} + #define initAU3(x,y,z) {x,y,z} + #define initAU4(x,y,z,w) {x,y,z,w} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Replace transcendentals with manual versions. +//============================================================================================================================== + #ifdef A_GCC + A_STATIC AD1 AAbsD1(AD1 a){return __builtin_fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return __builtin_fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(__builtin_abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(__builtin_llabs(ASL1_(a)));} + #else + A_STATIC AD1 AAbsD1(AD1 a){return fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(labs((long)ASL1_(a)));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ACosD1(AD1 a){return __builtin_cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return __builtin_cosf(a);} + #else + A_STATIC AD1 ACosD1(AD1 a){return cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return cosf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ADotD2(inAD2 a,inAD2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AD1 ADotD3(inAD3 a,inAD3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AD1 ADotD4(inAD4 a,inAD4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} + A_STATIC AF1 ADotF2(inAF2 a,inAF2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AF1 ADotF3(inAF3 a,inAF3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AF1 ADotF4(inAF4 a,inAF4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AExp2D1(AD1 a){return __builtin_exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return __builtin_exp2f(a);} + #else + A_STATIC AD1 AExp2D1(AD1 a){return exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return exp2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AFloorD1(AD1 a){return __builtin_floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return __builtin_floorf(a);} + #else + A_STATIC AD1 AFloorD1(AD1 a){return floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return floorf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ALerpD1(AD1 a,AD1 b,AD1 c){return b*c+(-a*c+a);} + A_STATIC AF1 ALerpF1(AF1 a,AF1 b,AF1 c){return b*c+(-a*c+a);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ALog2D1(AD1 a){return __builtin_log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return __builtin_log2f(a);} + #else + A_STATIC AD1 ALog2D1(AD1 a){return log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return log2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMaxD1(AD1 a,AD1 b){return a>b?a:b;} + A_STATIC AF1 AMaxF1(AF1 a,AF1 b){return a>b?a:b;} + A_STATIC AL1 AMaxL1(AL1 a,AL1 b){return a>b?a:b;} + A_STATIC AU1 AMaxU1(AU1 a,AU1 b){return a>b?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + // These follow the convention that A integer types don't have signage, until they are operated on. + A_STATIC AL1 AMaxSL1(AL1 a,AL1 b){return (ASL1_(a)>ASL1_(b))?a:b;} + A_STATIC AU1 AMaxSU1(AU1 a,AU1 b){return (ASU1_(a)>ASU1_(b))?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMinD1(AD1 a,AD1 b){return a>ASL1_(b));} + A_STATIC AU1 AShrSU1(AU1 a,AU1 b){return AU1_(ASU1_(a)>>ASU1_(b));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASinD1(AD1 a){return __builtin_sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return __builtin_sinf(a);} + #else + A_STATIC AD1 ASinD1(AD1 a){return sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return sinf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASqrtD1(AD1 a){return __builtin_sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return __builtin_sqrtf(a);} + #else + A_STATIC AD1 ASqrtD1(AD1 a){return sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return sqrtf(a);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + A_STATIC AD1 AClampD1(AD1 x,AD1 n,AD1 m){return AMaxD1(n,AMinD1(x,m));} + A_STATIC AF1 AClampF1(AF1 x,AF1 n,AF1 m){return AMaxF1(n,AMinF1(x,m));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AFractD1(AD1 a){return a-AFloorD1(a);} + A_STATIC AF1 AFractF1(AF1 a){return a-AFloorF1(a);} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 APowD1(AD1 a,AD1 b){return AExp2D1(b*ALog2D1(a));} + A_STATIC AF1 APowF1(AF1 a,AF1 b){return AExp2F1(b*ALog2F1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ARsqD1(AD1 a){return ARcpD1(ASqrtD1(a));} + A_STATIC AF1 ARsqF1(AF1 a){return ARcpF1(ASqrtF1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ASatD1(AD1 a){return AMinD1(1.0,AMaxD1(0.0,a));} + A_STATIC AF1 ASatF1(AF1 a){return AMinF1(1.0f,AMaxF1(0.0f,a));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + A_STATIC retAD2 opAAbsD2(outAD2 d,inAD2 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);return d;} + A_STATIC retAD3 opAAbsD3(outAD3 d,inAD3 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);return d;} + A_STATIC retAD4 opAAbsD4(outAD4 d,inAD4 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);d[3]=AAbsD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAbsF2(outAF2 d,inAF2 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);return d;} + A_STATIC retAF3 opAAbsF3(outAF3 d,inAF3 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);return d;} + A_STATIC retAF4 opAAbsF4(outAF4 d,inAF4 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);d[3]=AAbsF1(a[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} + A_STATIC retAD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} + A_STATIC retAD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} + A_STATIC retAF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} + A_STATIC retAF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opACpyD2(outAD2 d,inAD2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAD3 opACpyD3(outAD3 d,inAD3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAD4 opACpyD4(outAD4 d,inAD4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opACpyF2(outAF2 d,inAF2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAF3 opACpyF3(outAF3 d,inAF3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAF4 opACpyF4(outAF4 d,inAF4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);return d;} + A_STATIC retAD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);return d;} + A_STATIC retAD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);d[3]=ALerpD1(a[3],b[3],c[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);return d;} + A_STATIC retAF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);return d;} + A_STATIC retAF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);d[3]=ALerpF1(a[3],b[3],c[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);return d;} + A_STATIC retAD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);return d;} + A_STATIC retAD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);d[3]=ALerpD1(a[3],b[3],c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);return d;} + A_STATIC retAF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);return d;} + A_STATIC retAF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);d[3]=ALerpF1(a[3],b[3],c);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);d[3]=AMaxD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);d[3]=AMaxF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);d[3]=AMinD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);d[3]=AMinF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opANegD2(outAD2 d,inAD2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAD3 opANegD3(outAD3 d,inAD3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAD4 opANegD4(outAD4 d,inAD4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opANegF2(outAF2 d,inAF2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAF3 opANegF3(outAF3 d,inAF3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAF4 opANegF4(outAF4 d,inAF4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opARcpD2(outAD2 d,inAD2 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);return d;} + A_STATIC retAD3 opARcpD3(outAD3 d,inAD3 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);return d;} + A_STATIC retAD4 opARcpD4(outAD4 d,inAD4 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);d[3]=ARcpD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opARcpF2(outAF2 d,inAF2 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);return d;} + A_STATIC retAF3 opARcpF3(outAF3 d,inAF3 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);return d;} + A_STATIC retAF4 opARcpF4(outAF4 d,inAF4 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);d[3]=ARcpF1(a[3]);return d;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF FLOAT PACKING +//============================================================================================================================== + // Convert float to half (in lower 16-bits of output). + // Same fast technique as documented here: ftp://ftp.fox-toolkit.org/pub/fasthalffloatconversion.pdf + // Supports denormals. + // Conversion rules are to make computations possibly "safer" on the GPU, + // -INF & -NaN -> -65504 + // +INF & +NaN -> +65504 + A_STATIC AU1 AU1_AH1_AF1(AF1 f){ + static AW1 base[512]={ + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0001,0x0002,0x0004,0x0008,0x0010,0x0020,0x0040,0x0080,0x0100, + 0x0200,0x0400,0x0800,0x0c00,0x1000,0x1400,0x1800,0x1c00,0x2000,0x2400,0x2800,0x2c00,0x3000,0x3400,0x3800,0x3c00, + 0x4000,0x4400,0x4800,0x4c00,0x5000,0x5400,0x5800,0x5c00,0x6000,0x6400,0x6800,0x6c00,0x7000,0x7400,0x7800,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8001,0x8002,0x8004,0x8008,0x8010,0x8020,0x8040,0x8080,0x8100, + 0x8200,0x8400,0x8800,0x8c00,0x9000,0x9400,0x9800,0x9c00,0xa000,0xa400,0xa800,0xac00,0xb000,0xb400,0xb800,0xbc00, + 0xc000,0xc400,0xc800,0xcc00,0xd000,0xd400,0xd800,0xdc00,0xe000,0xe400,0xe800,0xec00,0xf000,0xf400,0xf800,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff}; + static AB1 shift[512]={ + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18}; + union{AF1 f;AU1 u;}bits;bits.f=f;AU1 u=bits.u;AU1 i=u>>23;return (AU1)(base[i])+((u&0x7fffff)>>shift[i]);} +//------------------------------------------------------------------------------------------------------------------------------ + // Used to output packed constant. + A_STATIC AU1 AU1_AH2_AF2(inAF2 a){return AU1_AH1_AF1(a[0])+(AU1_AH1_AF1(a[1])<<16);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GLSL +// +// +//============================================================================================================================== +#if defined(A_GLSL) && defined(A_GPU) + #ifndef A_SKIP_EXT + #ifdef A_HALF + #extension GL_EXT_shader_16bit_storage:require + #extension GL_EXT_shader_explicit_arithmetic_types:require + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_LONG + #extension GL_ARB_gpu_shader_int64:require + #extension GL_NV_shader_atomic_int64:require + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_WAVE + #extension GL_KHR_shader_subgroup_arithmetic:require + #extension GL_KHR_shader_subgroup_ballot:require + #extension GL_KHR_shader_subgroup_quad:require + #extension GL_KHR_shader_subgroup_shuffle:require + #endif + #endif +//============================================================================================================================== + #define AP1 bool + #define AP2 bvec2 + #define AP3 bvec3 + #define AP4 bvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 vec2 + #define AF3 vec3 + #define AF4 vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uvec2 + #define AU3 uvec3 + #define AU4 uvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 ivec2 + #define ASU3 ivec3 + #define ASU4 ivec4 +//============================================================================================================================== + #define AF1_AU1(x) uintBitsToFloat(AU1(x)) + #define AF2_AU2(x) uintBitsToFloat(AU2(x)) + #define AF3_AU3(x) uintBitsToFloat(AU3(x)) + #define AF4_AU4(x) uintBitsToFloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) floatBitsToUint(AF1(x)) + #define AU2_AF2(x) floatBitsToUint(AF2(x)) + #define AU3_AF3(x) floatBitsToUint(AF3(x)) + #define AU4_AF4(x) floatBitsToUint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH1_AF1_x(AF1 a){return packHalf2x16(AF2(a,0.0));} + #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2_AF2 packHalf2x16 + #define AU1_AW2Unorm_AF2 packUnorm2x16 + #define AU1_AB4Unorm_AF4 packUnorm4x8 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF2_AH2_AU1 unpackHalf2x16 + #define AF2_AW2Unorm_AU1 unpackUnorm2x16 + #define AF4_AB4Unorm_AU1 unpackUnorm4x8 +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){return bitfieldExtract(src,ASU1(off),ASU1(bits));} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + // Proxy for V_BFI_B32 where the 'mask' is set as 'bits', 'mask=(1<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #define AB1 uint8_t + #define AB2 u8vec2 + #define AB3 u8vec3 + #define AB4 u8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASB1 int8_t + #define ASB2 i8vec2 + #define ASB3 i8vec3 + #define ASB4 i8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + AB1 AB1_x(AB1 a){return AB1(a);} + AB2 AB2_x(AB1 a){return AB2(a,a);} + AB3 AB3_x(AB1 a){return AB3(a,a,a);} + AB4 AB4_x(AB1 a){return AB4(a,a,a,a);} + #define AB1_(a) AB1_x(AB1(a)) + #define AB2_(a) AB2_x(AB1(a)) + #define AB3_(a) AB3_x(AB1(a)) + #define AB4_(a) AB4_x(AB1(a)) + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #define AH1 float16_t + #define AH2 f16vec2 + #define AH3 f16vec3 + #define AH4 f16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 u16vec2 + #define AW3 u16vec3 + #define AW4 u16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 i16vec2 + #define ASW3 i16vec3 + #define ASW4 i16vec4 +//============================================================================================================================== + #define AH2_AU1(x) unpackFloat2x16(AU1(x)) + AH4 AH4_AU2_x(AU2 x){return AH4(unpackFloat2x16(x.x),unpackFloat2x16(x.y));} + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) unpackUint2x16(AU1(x)) + #define AW4_AU2(x) unpackUint4x16(pack64(AU2(x))) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2(x) packFloat2x16(AH2(x)) + AU2 AU2_AH4_x(AH4 x){return AU2(packFloat2x16(x.xy),packFloat2x16(x.zw));} + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) packUint2x16(AW2(x)) + #define AU2_AW4(x) unpack32(packUint4x16(AW4(x))) +//============================================================================================================================== + #define AW1_AH1(x) halfBitsToUint16(AH1(x)) + #define AW2_AH2(x) halfBitsToUint16(AH2(x)) + #define AW3_AH3(x) halfBitsToUint16(AH3(x)) + #define AW4_AH4(x) halfBitsToUint16(AH4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AH1_AW1(x) uint16BitsToHalf(AW1(x)) + #define AH2_AW2(x) uint16BitsToHalf(AW2(x)) + #define AH3_AW3(x) uint16BitsToHalf(AW3(x)) + #define AH4_AW4(x) uint16BitsToHalf(AW4(x)) +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AClampH1(AH1 x,AH1 n,AH1 m){return clamp(x,n,m);} + AH2 AClampH2(AH2 x,AH2 n,AH2 m){return clamp(x,n,m);} + AH3 AClampH3(AH3 x,AH3 n,AH3 m){return clamp(x,n,m);} + AH4 AClampH4(AH4 x,AH4 n,AH4 m){return clamp(x,n,m);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFractH1(AH1 x){return fract(x);} + AH2 AFractH2(AH2 x){return fract(x);} + AH3 AFractH3(AH3 x){return fract(x);} + AH4 AFractH4(AH4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return mix(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return mix(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return mix(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of max3. + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of min3. + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return AH1_(1.0)/x;} + AH2 ARcpH2(AH2 x){return AH2_(1.0)/x;} + AH3 ARcpH3(AH3 x){return AH3_(1.0)/x;} + AH4 ARcpH4(AH4 x){return AH4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return AH1_(1.0)/sqrt(x);} + AH2 ARsqH2(AH2 x){return AH2_(1.0)/sqrt(x);} + AH3 ARsqH3(AH3 x){return AH3_(1.0)/sqrt(x);} + AH4 ARsqH4(AH4 x){return AH4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return clamp(x,AH1_(0.0),AH1_(1.0));} + AH2 ASatH2(AH2 x){return clamp(x,AH2_(0.0),AH2_(1.0));} + AH3 ASatH3(AH3 x){return clamp(x,AH3_(0.0),AH3_(1.0));} + AH4 ASatH4(AH4 x){return clamp(x,AH4_(0.0),AH4_(1.0));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #define AD1 double + #define AD2 dvec2 + #define AD3 dvec3 + #define AD4 dvec4 +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 x){return fract(x);} + AD2 AFractD2(AD2 x){return fract(x);} + AD3 AFractD3(AD3 x){return fract(x);} + AD4 AFractD4(AD4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return mix(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return mix(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return mix(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return AD1_(1.0)/x;} + AD2 ARcpD2(AD2 x){return AD2_(1.0)/x;} + AD3 ARcpD3(AD3 x){return AD3_(1.0)/x;} + AD4 ARcpD4(AD4 x){return AD4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return AD1_(1.0)/sqrt(x);} + AD2 ARsqD2(AD2 x){return AD2_(1.0)/sqrt(x);} + AD3 ARsqD3(AD3 x){return AD3_(1.0)/sqrt(x);} + AD4 ARsqD4(AD4 x){return AD4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return clamp(x,AD1_(0.0),AD1_(1.0));} + AD2 ASatD2(AD2 x){return clamp(x,AD2_(0.0),AD2_(1.0));} + AD3 ASatD3(AD3 x){return clamp(x,AD3_(0.0),AD3_(1.0));} + AD4 ASatD4(AD4 x){return clamp(x,AD4_(0.0),AD4_(1.0));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL LONG +//============================================================================================================================== + #ifdef A_LONG + #define AL1 uint64_t + #define AL2 u64vec2 + #define AL3 u64vec3 + #define AL4 u64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1 int64_t + #define ASL2 i64vec2 + #define ASL3 i64vec3 + #define ASL4 i64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AL1_AU2(x) packUint2x32(AU2(x)) + #define AU2_AL1(x) unpackUint2x32(AL1(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AL1_x(AL1 a){return AL1(a);} + AL2 AL2_x(AL1 a){return AL2(a,a);} + AL3 AL3_x(AL1 a){return AL3(a,a,a);} + AL4 AL4_x(AL1 a){return AL4(a,a,a,a);} + #define AL1_(a) AL1_x(AL1(a)) + #define AL2_(a) AL2_x(AL1(a)) + #define AL3_(a) AL3_x(AL1(a)) + #define AL4_(a) AL4_x(AL1(a)) +//============================================================================================================================== + AL1 AAbsSL1(AL1 a){return AL1(abs(ASL1(a)));} + AL2 AAbsSL2(AL2 a){return AL2(abs(ASL2(a)));} + AL3 AAbsSL3(AL3 a){return AL3(abs(ASL3(a)));} + AL4 AAbsSL4(AL4 a){return AL4(abs(ASL4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMaxSL1(AL1 a,AL1 b){return AL1(max(ASU1(a),ASU1(b)));} + AL2 AMaxSL2(AL2 a,AL2 b){return AL2(max(ASU2(a),ASU2(b)));} + AL3 AMaxSL3(AL3 a,AL3 b){return AL3(max(ASU3(a),ASU3(b)));} + AL4 AMaxSL4(AL4 a,AL4 b){return AL4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMinSL1(AL1 a,AL1 b){return AL1(min(ASU1(a),ASU1(b)));} + AL2 AMinSL2(AL2 a,AL2 b){return AL2(min(ASU2(a),ASU2(b)));} + AL3 AMinSL3(AL3 a,AL3 b){return AL3(min(ASU3(a),ASU3(b)));} + AL4 AMinSL4(AL4 a,AL4 b){return AL4(min(ASU4(a),ASU4(b)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// WAVE OPERATIONS +//============================================================================================================================== + #ifdef A_WAVE + // Where 'x' must be a compile time literal. + AF1 AWaveXorF1(AF1 v,AU1 x){return subgroupShuffleXor(v,x);} + AF2 AWaveXorF2(AF2 v,AU1 x){return subgroupShuffleXor(v,x);} + AF3 AWaveXorF3(AF3 v,AU1 x){return subgroupShuffleXor(v,x);} + AF4 AWaveXorF4(AF4 v,AU1 x){return subgroupShuffleXor(v,x);} + AU1 AWaveXorU1(AU1 v,AU1 x){return subgroupShuffleXor(v,x);} + AU2 AWaveXorU2(AU2 v,AU1 x){return subgroupShuffleXor(v,x);} + AU3 AWaveXorU3(AU3 v,AU1 x){return subgroupShuffleXor(v,x);} + AU4 AWaveXorU4(AU4 v,AU1 x){return subgroupShuffleXor(v,x);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(subgroupShuffleXor(AU1_AH2(v),x));} + AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(subgroupShuffleXor(AU2_AH4(v),x));} + AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(subgroupShuffleXor(AU1_AW2(v),x));} + AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU2(subgroupShuffleXor(AU2_AW4(v),x));} + #endif + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// HLSL +// +// +//============================================================================================================================== +#if defined(A_HLSL) && defined(A_GPU) + #ifdef A_HLSL_6_2 + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float32_t + #define AF2 float32_t2 + #define AF3 float32_t3 + #define AF4 float32_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint32_t + #define AU2 uint32_t2 + #define AU3 uint32_t3 + #define AU4 uint32_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int32_t + #define ASU2 int32_t2 + #define ASU3 int32_t3 + #define ASU4 int32_t4 + #else + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 float2 + #define AF3 float3 + #define AF4 float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uint2 + #define AU3 uint3 + #define AU4 uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 int2 + #define ASU3 int3 + #define ASU4 int4 + #endif +//============================================================================================================================== + #define AF1_AU1(x) asfloat(AU1(x)) + #define AF2_AU2(x) asfloat(AU2(x)) + #define AF3_AU3(x) asfloat(AU3(x)) + #define AF4_AU4(x) asfloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) asuint(AF1(x)) + #define AU2_AF2(x) asuint(AF2(x)) + #define AU3_AF3(x) asuint(AF3(x)) + #define AU4_AF4(x) asuint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH1_AF1_x(AF1 a){return f32tof16(a);} + #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_AF2_x(AF2 a){return f32tof16(a.x)|(f32tof16(a.y)<<16);} + #define AU1_AH2_AF2(a) AU1_AH2_AF2_x(AF2(a)) + #define AU1_AB4Unorm_AF4(x) D3DCOLORtoUBYTE4(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AF2 AF2_AH2_AU1_x(AU1 x){return AF2(f16tof32(x&0xFFFF),f16tof32(x>>16));} + #define AF2_AH2_AU1(x) AF2_AH2_AU1_x(AU1(x)) +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){AU1 mask=(1u<>off)&mask;} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + AU1 ABfiM(AU1 src,AU1 ins,AU1 bits){AU1 mask=(1u<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #ifdef A_HLSL_6_2 + #define AH1 float16_t + #define AH2 float16_t2 + #define AH3 float16_t3 + #define AH4 float16_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 uint16_t2 + #define AW3 uint16_t3 + #define AW4 uint16_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 int16_t2 + #define ASW3 int16_t3 + #define ASW4 int16_t4 + #else + #define AH1 min16float + #define AH2 min16float2 + #define AH3 min16float3 + #define AH4 min16float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 min16uint + #define AW2 min16uint2 + #define AW3 min16uint3 + #define AW4 min16uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 min16int + #define ASW2 min16int2 + #define ASW3 min16int3 + #define ASW4 min16int4 + #endif +//============================================================================================================================== + // Need to use manual unpack to get optimal execution (don't use packed types in buffers directly). + // Unpack requires this pattern: https://gpuopen.com/first-steps-implementing-fp16/ + AH2 AH2_AU1_x(AU1 x){AF2 t=f16tof32(AU2(x&0xFFFF,x>>16));return AH2(t);} + AH4 AH4_AU2_x(AU2 x){return AH4(AH2_AU1_x(x.x),AH2_AU1_x(x.y));} + AW2 AW2_AU1_x(AU1 x){AU2 t=AU2(x&0xFFFF,x>>16);return AW2(t);} + AW4 AW4_AU2_x(AU2 x){return AW4(AW2_AU1_x(x.x),AW2_AU1_x(x.y));} + #define AH2_AU1(x) AH2_AU1_x(AU1(x)) + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) AW2_AU1_x(AU1(x)) + #define AW4_AU2(x) AW4_AU2_x(AU2(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_x(AH2 x){return f32tof16(x.x)+(f32tof16(x.y)<<16);} + AU2 AU2_AH4_x(AH4 x){return AU2(AU1_AH2_x(x.xy),AU1_AH2_x(x.zw));} + AU1 AU1_AW2_x(AW2 x){return AU1(x.x)+(AU1(x.y)<<16);} + AU2 AU2_AW4_x(AW4 x){return AU2(AU1_AW2_x(x.xy),AU1_AW2_x(x.zw));} + #define AU1_AH2(x) AU1_AH2_x(AH2(x)) + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) AU1_AW2_x(AW2(x)) + #define AU2_AW4(x) AU2_AW4_x(AW4(x)) +//============================================================================================================================== + #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) + #define AW1_AH1(x) asuint16(x) + #define AW2_AH2(x) asuint16(x) + #define AW3_AH3(x) asuint16(x) + #define AW4_AH4(x) asuint16(x) + #else + #define AW1_AH1(a) AW1(f32tof16(AF1(a))) + #define AW2_AH2(a) AW2(AW1_AH1((a).x),AW1_AH1((a).y)) + #define AW3_AH3(a) AW3(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z)) + #define AW4_AH4(a) AW4(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z),AW1_AH1((a).w)) + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) + #define AH1_AW1(x) asfloat16(x) + #define AH2_AW2(x) asfloat16(x) + #define AH3_AW3(x) asfloat16(x) + #define AH4_AW4(x) asfloat16(x) + #else + #define AH1_AW1(a) AH1(f16tof32(AU1(a))) + #define AH2_AW2(a) AH2(AH1_AW1((a).x),AH1_AW1((a).y)) + #define AH3_AW3(a) AH3(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z)) + #define AH4_AW4(a) AH4(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z),AH1_AW1((a).w)) + #endif +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AClampH1(AH1 x,AH1 n,AH1 m){return max(n,min(x,m));} + AH2 AClampH2(AH2 x,AH2 n,AH2 m){return max(n,min(x,m));} + AH3 AClampH3(AH3 x,AH3 n,AH3 m){return max(n,min(x,m));} + AH4 AClampH4(AH4 x,AH4 n,AH4 m){return max(n,min(x,m));} +//------------------------------------------------------------------------------------------------------------------------------ + // V_FRACT_F16 (note DX frac() is different). + AH1 AFractH1(AH1 x){return x-floor(x);} + AH2 AFractH2(AH2 x){return x-floor(x);} + AH3 AFractH3(AH3 x){return x-floor(x);} + AH4 AFractH4(AH4 x){return x-floor(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return lerp(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return lerp(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return lerp(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return rcp(x);} + AH2 ARcpH2(AH2 x){return rcp(x);} + AH3 ARcpH3(AH3 x){return rcp(x);} + AH4 ARcpH4(AH4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return rsqrt(x);} + AH2 ARsqH2(AH2 x){return rsqrt(x);} + AH3 ARsqH3(AH3 x){return rsqrt(x);} + AH4 ARsqH4(AH4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return saturate(x);} + AH2 ASatH2(AH2 x){return saturate(x);} + AH3 ASatH3(AH3 x){return saturate(x);} + AH4 ASatH4(AH4 x){return saturate(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #ifdef A_HLSL_6_2 + #define AD1 float64_t + #define AD2 float64_t2 + #define AD3 float64_t3 + #define AD4 float64_t4 + #else + #define AD1 double + #define AD2 double2 + #define AD3 double3 + #define AD4 double4 + #endif +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 a){return a-floor(a);} + AD2 AFractD2(AD2 a){return a-floor(a);} + AD3 AFractD3(AD3 a){return a-floor(a);} + AD4 AFractD4(AD4 a){return a-floor(a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return lerp(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return lerp(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return lerp(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return rcp(x);} + AD2 ARcpD2(AD2 x){return rcp(x);} + AD3 ARcpD3(AD3 x){return rcp(x);} + AD4 ARcpD4(AD4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return rsqrt(x);} + AD2 ARsqD2(AD2 x){return rsqrt(x);} + AD3 ARsqD3(AD3 x){return rsqrt(x);} + AD4 ARsqD4(AD4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return saturate(x);} + AD2 ASatD2(AD2 x){return saturate(x);} + AD3 ASatD3(AD3 x){return saturate(x);} + AD4 ASatD4(AD4 x){return saturate(x);} + #endif +//============================================================================================================================== +// HLSL WAVE +//============================================================================================================================== + #ifdef A_WAVE + // Where 'x' must be a compile time literal. + AF1 AWaveXorF1(AF1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF2 AWaveXorF2(AF2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF3 AWaveXorF3(AF3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF4 AWaveXorF4(AF4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU1 AWaveXorU1(AU1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU2 AWaveXorU1(AU2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU3 AWaveXorU1(AU3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU4 AWaveXorU1(AU4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(WaveReadLaneAt(AU1_AH2(v),WaveGetLaneIndex()^x));} + AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(WaveReadLaneAt(AU2_AH4(v),WaveGetLaneIndex()^x));} + AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(WaveReadLaneAt(AU1_AW2(v),WaveGetLaneIndex()^x));} + AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU1(WaveReadLaneAt(AU1_AW4(v),WaveGetLaneIndex()^x));} + #endif + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU COMMON +// +// +//============================================================================================================================== +#ifdef A_GPU + // Negative and positive infinity. + #define A_INFP_F AF1_AU1(0x7f800000u) + #define A_INFN_F AF1_AU1(0xff800000u) +//------------------------------------------------------------------------------------------------------------------------------ + // Copy sign from 's' to positive 'd'. + AF1 ACpySgnF1(AF1 d,AF1 s){return AF1_AU1(AU1_AF1(d)|(AU1_AF1(s)&AU1_(0x80000000u)));} + AF2 ACpySgnF2(AF2 d,AF2 s){return AF2_AU2(AU2_AF2(d)|(AU2_AF2(s)&AU2_(0x80000000u)));} + AF3 ACpySgnF3(AF3 d,AF3 s){return AF3_AU3(AU3_AF3(d)|(AU3_AF3(s)&AU3_(0x80000000u)));} + AF4 ACpySgnF4(AF4 d,AF4 s){return AF4_AU4(AU4_AF4(d)|(AU4_AF4(s)&AU4_(0x80000000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Single operation to return (useful to create a mask to use in lerp for branch free logic), + // m=NaN := 0 + // m>=0 := 0 + // m<0 := 1 + // Uses the following useful floating point logic, + // saturate(+a*(-INF)==-INF) := 0 + // saturate( 0*(-INF)== NaN) := 0 + // saturate(-a*(-INF)==+INF) := 1 + AF1 ASignedF1(AF1 m){return ASatF1(m*AF1_(A_INFN_F));} + AF2 ASignedF2(AF2 m){return ASatF2(m*AF2_(A_INFN_F));} + AF3 ASignedF3(AF3 m){return ASatF3(m*AF3_(A_INFN_F));} + AF4 ASignedF4(AF4 m){return ASatF4(m*AF4_(A_INFN_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AGtZeroF1(AF1 m){return ASatF1(m*AF1_(A_INFP_F));} + AF2 AGtZeroF2(AF2 m){return ASatF2(m*AF2_(A_INFP_F));} + AF3 AGtZeroF3(AF3 m){return ASatF3(m*AF3_(A_INFP_F));} + AF4 AGtZeroF4(AF4 m){return ASatF4(m*AF4_(A_INFP_F));} +//============================================================================================================================== + #ifdef A_HALF + #ifdef A_HLSL_6_2 + #define A_INFP_H AH1_AW1((uint16_t)0x7c00u) + #define A_INFN_H AH1_AW1((uint16_t)0xfc00u) + #else + #define A_INFP_H AH1_AW1(0x7c00u) + #define A_INFN_H AH1_AW1(0xfc00u) + #endif + +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ACpySgnH1(AH1 d,AH1 s){return AH1_AW1(AW1_AH1(d)|(AW1_AH1(s)&AW1_(0x8000u)));} + AH2 ACpySgnH2(AH2 d,AH2 s){return AH2_AW2(AW2_AH2(d)|(AW2_AH2(s)&AW2_(0x8000u)));} + AH3 ACpySgnH3(AH3 d,AH3 s){return AH3_AW3(AW3_AH3(d)|(AW3_AH3(s)&AW3_(0x8000u)));} + AH4 ACpySgnH4(AH4 d,AH4 s){return AH4_AW4(AW4_AH4(d)|(AW4_AH4(s)&AW4_(0x8000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASignedH1(AH1 m){return ASatH1(m*AH1_(A_INFN_H));} + AH2 ASignedH2(AH2 m){return ASatH2(m*AH2_(A_INFN_H));} + AH3 ASignedH3(AH3 m){return ASatH3(m*AH3_(A_INFN_H));} + AH4 ASignedH4(AH4 m){return ASatH4(m*AH4_(A_INFN_H));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AGtZeroH1(AH1 m){return ASatH1(m*AH1_(A_INFP_H));} + AH2 AGtZeroH2(AH2 m){return ASatH2(m*AH2_(A_INFP_H));} + AH3 AGtZeroH3(AH3 m){return ASatH3(m*AH3_(A_INFP_H));} + AH4 AGtZeroH4(AH4 m){return ASatH4(m*AH4_(A_INFP_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [FIS] FLOAT INTEGER SORTABLE +//------------------------------------------------------------------------------------------------------------------------------ +// Float to integer sortable. +// - If sign bit=0, flip the sign bit (positives). +// - If sign bit=1, flip all bits (negatives). +// Integer sortable to float. +// - If sign bit=1, flip the sign bit (positives). +// - If sign bit=0, flip all bits (negatives). +// Has nice side effects. +// - Larger integers are more positive values. +// - Float zero is mapped to center of integers (so clear to integer zero is a nice default for atomic max usage). +// Burns 3 ops for conversion {shift,or,xor}. +//============================================================================================================================== + AU1 AFisToU1(AU1 x){return x^(( AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} + AU1 AFisFromU1(AU1 x){return x^((~AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} +//------------------------------------------------------------------------------------------------------------------------------ + // Just adjust high 16-bit value (useful when upper part of 32-bit word is a 16-bit float value). + AU1 AFisToHiU1(AU1 x){return x^(( AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} + AU1 AFisFromHiU1(AU1 x){return x^((~AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AW1 AFisToW1(AW1 x){return x^(( AShrSW1(x,AW1_(15)))|AW1_(0x8000));} + AW1 AFisFromW1(AW1 x){return x^((~AShrSW1(x,AW1_(15)))|AW1_(0x8000));} +//------------------------------------------------------------------------------------------------------------------------------ + AW2 AFisToW2(AW2 x){return x^(( AShrSW2(x,AW2_(15)))|AW2_(0x8000));} + AW2 AFisFromW2(AW2 x){return x^((~AShrSW2(x,AW2_(15)))|AW2_(0x8000));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [PERM] V_PERM_B32 +//------------------------------------------------------------------------------------------------------------------------------ +// Support for V_PERM_B32 started in the 3rd generation of GCN. +//------------------------------------------------------------------------------------------------------------------------------ +// yyyyxxxx - The 'i' input. +// 76543210 +// ======== +// HGFEDCBA - Naming on permutation. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure compiler optimizes this. +//============================================================================================================================== + #ifdef A_HALF + AU1 APerm0E0A(AU2 i){return((i.x )&0xffu)|((i.y<<16)&0xff0000u);} + AU1 APerm0F0B(AU2 i){return((i.x>> 8)&0xffu)|((i.y<< 8)&0xff0000u);} + AU1 APerm0G0C(AU2 i){return((i.x>>16)&0xffu)|((i.y )&0xff0000u);} + AU1 APerm0H0D(AU2 i){return((i.x>>24)&0xffu)|((i.y>> 8)&0xff0000u);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 APermHGFA(AU2 i){return((i.x )&0x000000ffu)|(i.y&0xffffff00u);} + AU1 APermHGFC(AU2 i){return((i.x>>16)&0x000000ffu)|(i.y&0xffffff00u);} + AU1 APermHGAE(AU2 i){return((i.x<< 8)&0x0000ff00u)|(i.y&0xffff00ffu);} + AU1 APermHGCE(AU2 i){return((i.x>> 8)&0x0000ff00u)|(i.y&0xffff00ffu);} + AU1 APermHAFE(AU2 i){return((i.x<<16)&0x00ff0000u)|(i.y&0xff00ffffu);} + AU1 APermHCFE(AU2 i){return((i.x )&0x00ff0000u)|(i.y&0xff00ffffu);} + AU1 APermAGFE(AU2 i){return((i.x<<24)&0xff000000u)|(i.y&0x00ffffffu);} + AU1 APermCGFE(AU2 i){return((i.x<< 8)&0xff000000u)|(i.y&0x00ffffffu);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 APermGCEA(AU2 i){return((i.x)&0x00ff00ffu)|((i.y<<8)&0xff00ff00u);} + AU1 APermGECA(AU2 i){return(((i.x)&0xffu)|((i.x>>8)&0xff00u)|((i.y<<16)&0xff0000u)|((i.y<<8)&0xff000000u));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [BUC] BYTE UNSIGNED CONVERSION +//------------------------------------------------------------------------------------------------------------------------------ +// Designed to use the optimal conversion, enables the scaling to possibly be factored into other computation. +// Works on a range of {0 to A_BUC_<32,16>}, for <32-bit, and 16-bit> respectively. +//------------------------------------------------------------------------------------------------------------------------------ +// OPCODE NOTES +// ============ +// GCN does not do UNORM or SNORM for bytes in opcodes. +// - V_CVT_F32_UBYTE{0,1,2,3} - Unsigned byte to float. +// - V_CVT_PKACC_U8_F32 - Float to unsigned byte (does bit-field insert into 32-bit integer). +// V_PERM_B32 does byte packing with ability to zero fill bytes as well. +// - Can pull out byte values from two sources, and zero fill upper 8-bits of packed hi and lo. +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U1() - Designed for V_CVT_F32_UBYTE* and V_CVT_PKACCUM_U8_F32 ops. +// ==== ===== +// 0 : 0 +// 1 : 1 +// ... +// 255 : 255 +// : 256 (just outside the encoding range) +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. +// ==== ===== +// 0 : 0 +// 1 : 1/512 +// 2 : 1/256 +// ... +// 64 : 1/8 +// 128 : 1/4 +// 255 : 255/512 +// : 1/2 (just outside the encoding range) +//------------------------------------------------------------------------------------------------------------------------------ +// OPTIMAL IMPLEMENTATIONS ON AMD ARCHITECTURES +// ============================================ +// r=ABuc0FromU1(i) +// V_CVT_F32_UBYTE0 r,i +// -------------------------------------------- +// r=ABuc0ToU1(d,i) +// V_CVT_PKACCUM_U8_F32 r,i,0,d +// -------------------------------------------- +// d=ABuc0FromU2(i) +// Where 'k0' is an SGPR with 0x0E0A +// Where 'k1' is an SGPR with {32768.0} packed into the lower 16-bits +// V_PERM_B32 d,i.x,i.y,k0 +// V_PK_FMA_F16 d,d,k1.x,0 +// -------------------------------------------- +// r=ABuc0ToU2(d,i) +// Where 'k0' is an SGPR with {1.0/32768.0} packed into the lower 16-bits +// Where 'k1' is an SGPR with 0x???? +// Where 'k2' is an SGPR with 0x???? +// V_PK_FMA_F16 i,i,k0.x,0 +// V_PERM_B32 r.x,i,i,k1 +// V_PERM_B32 r.y,i,i,k2 +//============================================================================================================================== + // Peak range for 32-bit and 16-bit operations. + #define A_BUC_32 (255.0) + #define A_BUC_16 (255.0/512.0) +//============================================================================================================================== + #if 1 + // Designed to be one V_CVT_PKACCUM_U8_F32. + // The extra min is required to pattern match to V_CVT_PKACCUM_U8_F32. + AU1 ABuc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i),255u) )&(0x000000ffu));} + AU1 ABuc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i),255u)<< 8)&(0x0000ff00u));} + AU1 ABuc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i),255u)<<16)&(0x00ff0000u));} + AU1 ABuc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i),255u)<<24)&(0xff000000u));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed to be one V_CVT_F32_UBYTE*. + AF1 ABuc0FromU1(AU1 i){return AF1((i )&255u);} + AF1 ABuc1FromU1(AU1 i){return AF1((i>> 8)&255u);} + AF1 ABuc2FromU1(AU1 i){return AF1((i>>16)&255u);} + AF1 ABuc3FromU1(AU1 i){return AF1((i>>24)&255u);} + #endif +//============================================================================================================================== + #ifdef A_HALF + // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. + AW2 ABuc01ToW2(AH2 x,AH2 y){x*=AH2_(1.0/32768.0);y*=AH2_(1.0/32768.0); + return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed for 3 ops to do SOA to AOS and conversion. + AU2 ABuc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABuc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABuc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABuc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed for 2 ops to do both AOS to SOA, and conversion. + AH2 ABuc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0);} + AH2 ABuc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0);} + AH2 ABuc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0);} + AH2 ABuc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [BSC] BYTE SIGNED CONVERSION +//------------------------------------------------------------------------------------------------------------------------------ +// Similar to [BUC]. +// Works on a range of {-/+ A_BSC_<32,16>}, for <32-bit, and 16-bit> respectively. +//------------------------------------------------------------------------------------------------------------------------------ +// ENCODING (without zero-based encoding) +// ======== +// 0 = unused (can be used to mean something else) +// 1 = lowest value +// 128 = exact zero center (zero based encoding +// 255 = highest value +//------------------------------------------------------------------------------------------------------------------------------ +// Zero-based [Zb] flips the MSB bit of the byte (making 128 "exact zero" actually zero). +// This is useful if there is a desire for cleared values to decode as zero. +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABsc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. +// ==== ===== +// 0 : -127/512 (unused) +// 1 : -126/512 +// 2 : -125/512 +// ... +// 128 : 0 +// ... +// 255 : 127/512 +// : 1/4 (just outside the encoding range) +//============================================================================================================================== + // Peak range for 32-bit and 16-bit operations. + #define A_BSC_32 (127.0) + #define A_BSC_16 (127.0/512.0) +//============================================================================================================================== + #if 1 + AU1 ABsc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i+128.0),255u) )&(0x000000ffu));} + AU1 ABsc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i+128.0),255u)<< 8)&(0x0000ff00u));} + AU1 ABsc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i+128.0),255u)<<16)&(0x00ff0000u));} + AU1 ABsc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i+128.0),255u)<<24)&(0xff000000u));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABsc0ToZbU1(AU1 d,AF1 i){return ((d&0xffffff00u)|((min(AU1(trunc(i)+128.0),255u) )&(0x000000ffu)))^0x00000080u;} + AU1 ABsc1ToZbU1(AU1 d,AF1 i){return ((d&0xffff00ffu)|((min(AU1(trunc(i)+128.0),255u)<< 8)&(0x0000ff00u)))^0x00008000u;} + AU1 ABsc2ToZbU1(AU1 d,AF1 i){return ((d&0xff00ffffu)|((min(AU1(trunc(i)+128.0),255u)<<16)&(0x00ff0000u)))^0x00800000u;} + AU1 ABsc3ToZbU1(AU1 d,AF1 i){return ((d&0x00ffffffu)|((min(AU1(trunc(i)+128.0),255u)<<24)&(0xff000000u)))^0x80000000u;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ABsc0FromU1(AU1 i){return AF1((i )&255u)-128.0;} + AF1 ABsc1FromU1(AU1 i){return AF1((i>> 8)&255u)-128.0;} + AF1 ABsc2FromU1(AU1 i){return AF1((i>>16)&255u)-128.0;} + AF1 ABsc3FromU1(AU1 i){return AF1((i>>24)&255u)-128.0;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ABsc0FromZbU1(AU1 i){return AF1(((i )&255u)^0x80u)-128.0;} + AF1 ABsc1FromZbU1(AU1 i){return AF1(((i>> 8)&255u)^0x80u)-128.0;} + AF1 ABsc2FromZbU1(AU1 i){return AF1(((i>>16)&255u)^0x80u)-128.0;} + AF1 ABsc3FromZbU1(AU1 i){return AF1(((i>>24)&255u)^0x80u)-128.0;} + #endif +//============================================================================================================================== + #ifdef A_HALF + // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. + AW2 ABsc01ToW2(AH2 x,AH2 y){x=x*AH2_(1.0/32768.0)+AH2_(0.25/32768.0);y=y*AH2_(1.0/32768.0)+AH2_(0.25/32768.0); + return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} +//------------------------------------------------------------------------------------------------------------------------------ + AU2 ABsc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABsc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABsc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABsc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU2 ABsc0ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABsc1ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABsc2ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABsc3ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 ABsc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0)-AH2_(0.25);} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 ABsc0FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc1FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc2FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc3FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These support only positive inputs. +// Did not see value yet in specialization for range. +// Using quick testing, ended up mostly getting the same "best" approximation for various ranges. +// With hardware that can co-execute transcendentals, the value in approximations could be less than expected. +// However from a latency perspective, if execution of a transcendental is 4 clk, with no packed support, -> 8 clk total. +// And co-execution would require a compiler interleaving a lot of independent work for packed usage. +//------------------------------------------------------------------------------------------------------------------------------ +// The one Newton Raphson iteration form of rsq() was skipped (requires 6 ops total). +// Same with sqrt(), as this could be x*rsq() (7 ops). +//============================================================================================================================== + #ifdef A_HALF + // Minimize squared error across full positive range, 2 ops. + // The 0x1de2 based approximation maps {0 to 1} input maps to < 1 output. + AH1 APrxLoSqrtH1(AH1 a){return AH1_AW1((AW1_AH1(a)>>AW1_(1))+AW1_(0x1de2));} + AH2 APrxLoSqrtH2(AH2 a){return AH2_AW2((AW2_AH2(a)>>AW2_(1))+AW2_(0x1de2));} + AH3 APrxLoSqrtH3(AH3 a){return AH3_AW3((AW3_AH3(a)>>AW3_(1))+AW3_(0x1de2));} + AH4 APrxLoSqrtH4(AH4 a){return AH4_AW4((AW4_AH4(a)>>AW4_(1))+AW4_(0x1de2));} +//------------------------------------------------------------------------------------------------------------------------------ + // Lower precision estimation, 1 op. + // Minimize squared error across {smallest normal to 16384.0}. + AH1 APrxLoRcpH1(AH1 a){return AH1_AW1(AW1_(0x7784)-AW1_AH1(a));} + AH2 APrxLoRcpH2(AH2 a){return AH2_AW2(AW2_(0x7784)-AW2_AH2(a));} + AH3 APrxLoRcpH3(AH3 a){return AH3_AW3(AW3_(0x7784)-AW3_AH3(a));} + AH4 APrxLoRcpH4(AH4 a){return AH4_AW4(AW4_(0x7784)-AW4_AH4(a));} +//------------------------------------------------------------------------------------------------------------------------------ + // Medium precision estimation, one Newton Raphson iteration, 3 ops. + AH1 APrxMedRcpH1(AH1 a){AH1 b=AH1_AW1(AW1_(0x778d)-AW1_AH1(a));return b*(-b*a+AH1_(2.0));} + AH2 APrxMedRcpH2(AH2 a){AH2 b=AH2_AW2(AW2_(0x778d)-AW2_AH2(a));return b*(-b*a+AH2_(2.0));} + AH3 APrxMedRcpH3(AH3 a){AH3 b=AH3_AW3(AW3_(0x778d)-AW3_AH3(a));return b*(-b*a+AH3_(2.0));} + AH4 APrxMedRcpH4(AH4 a){AH4 b=AH4_AW4(AW4_(0x778d)-AW4_AH4(a));return b*(-b*a+AH4_(2.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // Minimize squared error across {smallest normal to 16384.0}, 2 ops. + AH1 APrxLoRsqH1(AH1 a){return AH1_AW1(AW1_(0x59a3)-(AW1_AH1(a)>>AW1_(1)));} + AH2 APrxLoRsqH2(AH2 a){return AH2_AW2(AW2_(0x59a3)-(AW2_AH2(a)>>AW2_(1)));} + AH3 APrxLoRsqH3(AH3 a){return AH3_AW3(AW3_(0x59a3)-(AW3_AH3(a)>>AW3_(1)));} + AH4 APrxLoRsqH4(AH4 a){return AH4_AW4(AW4_(0x59a3)-(AW4_AH4(a)>>AW4_(1)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// FLOAT APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// Michal Drobot has an excellent presentation on these: "Low Level Optimizations For GCN", +// - Idea dates back to SGI, then to Quake 3, etc. +// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf +// - sqrt(x)=rsqrt(x)*x +// - rcp(x)=rsqrt(x)*rsqrt(x) for positive x +// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h +//------------------------------------------------------------------------------------------------------------------------------ +// These below are from perhaps less complete searching for optimal. +// Used FP16 normal range for testing with +4096 32-bit step size for sampling error. +// So these match up well with the half approximations. +//============================================================================================================================== + AF1 APrxLoSqrtF1(AF1 a){return AF1_AU1((AU1_AF1(a)>>AU1_(1))+AU1_(0x1fbc4639));} + AF1 APrxLoRcpF1(AF1 a){return AF1_AU1(AU1_(0x7ef07ebb)-AU1_AF1(a));} + AF1 APrxMedRcpF1(AF1 a){AF1 b=AF1_AU1(AU1_(0x7ef19fff)-AU1_AF1(a));return b*(-b*a+AF1_(2.0));} + AF1 APrxLoRsqF1(AF1 a){return AF1_AU1(AU1_(0x5f347d74)-(AU1_AF1(a)>>AU1_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 APrxLoSqrtF2(AF2 a){return AF2_AU2((AU2_AF2(a)>>AU2_(1))+AU2_(0x1fbc4639));} + AF2 APrxLoRcpF2(AF2 a){return AF2_AU2(AU2_(0x7ef07ebb)-AU2_AF2(a));} + AF2 APrxMedRcpF2(AF2 a){AF2 b=AF2_AU2(AU2_(0x7ef19fff)-AU2_AF2(a));return b*(-b*a+AF2_(2.0));} + AF2 APrxLoRsqF2(AF2 a){return AF2_AU2(AU2_(0x5f347d74)-(AU2_AF2(a)>>AU2_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF3 APrxLoSqrtF3(AF3 a){return AF3_AU3((AU3_AF3(a)>>AU3_(1))+AU3_(0x1fbc4639));} + AF3 APrxLoRcpF3(AF3 a){return AF3_AU3(AU3_(0x7ef07ebb)-AU3_AF3(a));} + AF3 APrxMedRcpF3(AF3 a){AF3 b=AF3_AU3(AU3_(0x7ef19fff)-AU3_AF3(a));return b*(-b*a+AF3_(2.0));} + AF3 APrxLoRsqF3(AF3 a){return AF3_AU3(AU3_(0x5f347d74)-(AU3_AF3(a)>>AU3_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF4 APrxLoSqrtF4(AF4 a){return AF4_AU4((AU4_AF4(a)>>AU4_(1))+AU4_(0x1fbc4639));} + AF4 APrxLoRcpF4(AF4 a){return AF4_AU4(AU4_(0x7ef07ebb)-AU4_AF4(a));} + AF4 APrxMedRcpF4(AF4 a){AF4 b=AF4_AU4(AU4_(0x7ef19fff)-AU4_AF4(a));return b*(-b*a+AF4_(2.0));} + AF4 APrxLoRsqF4(AF4 a){return AF4_AU4(AU4_(0x5f347d74)-(AU4_AF4(a)>>AU4_(1)));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PQ APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// PQ is very close to x^(1/8). The functions below Use the fast float approximation method to do +// PQ<~>Gamma2 (4th power and fast 4th root) and PQ<~>Linear (8th power and fast 8th root). Maximum error is ~0.2%. +//============================================================================================================================== +// Helpers + AF1 Quart(AF1 a) { a = a * a; return a * a;} + AF1 Oct(AF1 a) { a = a * a; a = a * a; return a * a; } + AF2 Quart(AF2 a) { a = a * a; return a * a; } + AF2 Oct(AF2 a) { a = a * a; a = a * a; return a * a; } + AF3 Quart(AF3 a) { a = a * a; return a * a; } + AF3 Oct(AF3 a) { a = a * a; a = a * a; return a * a; } + AF4 Quart(AF4 a) { a = a * a; return a * a; } + AF4 Oct(AF4 a) { a = a * a; a = a * a; return a * a; } + //------------------------------------------------------------------------------------------------------------------------------ + AF1 APrxPQToGamma2(AF1 a) { return Quart(a); } + AF1 APrxPQToLinear(AF1 a) { return Oct(a); } + AF1 APrxLoGamma2ToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); } + AF1 APrxMedGamma2ToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); AF1 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF1 APrxHighGamma2ToPQ(AF1 a) { return sqrt(sqrt(a)); } + AF1 APrxLoLinearToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); } + AF1 APrxMedLinearToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); AF1 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF1 APrxHighLinearToPQ(AF1 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF2 APrxPQToGamma2(AF2 a) { return Quart(a); } + AF2 APrxPQToLinear(AF2 a) { return Oct(a); } + AF2 APrxLoGamma2ToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); } + AF2 APrxMedGamma2ToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); AF2 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF2 APrxHighGamma2ToPQ(AF2 a) { return sqrt(sqrt(a)); } + AF2 APrxLoLinearToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); } + AF2 APrxMedLinearToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); AF2 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF2 APrxHighLinearToPQ(AF2 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF3 APrxPQToGamma2(AF3 a) { return Quart(a); } + AF3 APrxPQToLinear(AF3 a) { return Oct(a); } + AF3 APrxLoGamma2ToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); } + AF3 APrxMedGamma2ToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); AF3 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF3 APrxHighGamma2ToPQ(AF3 a) { return sqrt(sqrt(a)); } + AF3 APrxLoLinearToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); } + AF3 APrxMedLinearToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); AF3 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF3 APrxHighLinearToPQ(AF3 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF4 APrxPQToGamma2(AF4 a) { return Quart(a); } + AF4 APrxPQToLinear(AF4 a) { return Oct(a); } + AF4 APrxLoGamma2ToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); } + AF4 APrxMedGamma2ToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); AF4 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF4 APrxHighGamma2ToPQ(AF4 a) { return sqrt(sqrt(a)); } + AF4 APrxLoLinearToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); } + AF4 APrxMedLinearToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); AF4 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF4 APrxHighLinearToPQ(AF4 a) { return sqrt(sqrt(sqrt(a))); } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PARABOLIC SIN & COS +//------------------------------------------------------------------------------------------------------------------------------ +// Approximate answers to transcendental questions. +//------------------------------------------------------------------------------------------------------------------------------ +//============================================================================================================================== + #if 1 + // Valid input range is {-1 to 1} representing {0 to 2 pi}. + // Output range is {-1/4 to 1/4} representing {-1 to 1}. + AF1 APSinF1(AF1 x){return x*abs(x)-x;} // MAD. + AF2 APSinF2(AF2 x){return x*abs(x)-x;} + AF1 APCosF1(AF1 x){x=AFractF1(x*AF1_(0.5)+AF1_(0.75));x=x*AF1_(2.0)-AF1_(1.0);return APSinF1(x);} // 3x MAD, FRACT + AF2 APCosF2(AF2 x){x=AFractF2(x*AF2_(0.5)+AF2_(0.75));x=x*AF2_(2.0)-AF2_(1.0);return APSinF2(x);} + AF2 APSinCosF1(AF1 x){AF1 y=AFractF1(x*AF1_(0.5)+AF1_(0.75));y=y*AF1_(2.0)-AF1_(1.0);return APSinF2(AF2(x,y));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + // For a packed {sin,cos} pair, + // - Native takes 16 clocks and 4 issue slots (no packed transcendentals). + // - Parabolic takes 8 clocks and 8 issue slots (only fract is non-packed). + AH1 APSinH1(AH1 x){return x*abs(x)-x;} + AH2 APSinH2(AH2 x){return x*abs(x)-x;} // AND,FMA + AH1 APCosH1(AH1 x){x=AFractH1(x*AH1_(0.5)+AH1_(0.75));x=x*AH1_(2.0)-AH1_(1.0);return APSinH1(x);} + AH2 APCosH2(AH2 x){x=AFractH2(x*AH2_(0.5)+AH2_(0.75));x=x*AH2_(2.0)-AH2_(1.0);return APSinH2(x);} // 3x FMA, 2xFRACT, AND + AH2 APSinCosH1(AH1 x){AH1 y=AFractH1(x*AH1_(0.5)+AH1_(0.75));y=y*AH1_(2.0)-AH1_(1.0);return APSinH2(AH2(x,y));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [ZOL] ZERO ONE LOGIC +//------------------------------------------------------------------------------------------------------------------------------ +// Conditional free logic designed for easy 16-bit packing, and backwards porting to 32-bit. +//------------------------------------------------------------------------------------------------------------------------------ +// 0 := false +// 1 := true +//------------------------------------------------------------------------------------------------------------------------------ +// AndNot(x,y) -> !(x&y) .... One op. +// AndOr(x,y,z) -> (x&y)|z ... One op. +// GtZero(x) -> x>0.0 ..... One op. +// Sel(x,y,z) -> x?y:z ..... Two ops, has no precision loss. +// Signed(x) -> x<0.0 ..... One op. +// ZeroPass(x,y) -> x?0:y ..... Two ops, 'y' is a pass through safe for aliasing as integer. +//------------------------------------------------------------------------------------------------------------------------------ +// OPTIMIZATION NOTES +// ================== +// - On Vega to use 2 constants in a packed op, pass in as one AW2 or one AH2 'k.xy' and use as 'k.xx' and 'k.yy'. +// For example 'a.xy*k.xx+k.yy'. +//============================================================================================================================== + #if 1 + AU1 AZolAndU1(AU1 x,AU1 y){return min(x,y);} + AU2 AZolAndU2(AU2 x,AU2 y){return min(x,y);} + AU3 AZolAndU3(AU3 x,AU3 y){return min(x,y);} + AU4 AZolAndU4(AU4 x,AU4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AZolNotU1(AU1 x){return x^AU1_(1);} + AU2 AZolNotU2(AU2 x){return x^AU2_(1);} + AU3 AZolNotU3(AU3 x){return x^AU3_(1);} + AU4 AZolNotU4(AU4 x){return x^AU4_(1);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AZolOrU1(AU1 x,AU1 y){return max(x,y);} + AU2 AZolOrU2(AU2 x,AU2 y){return max(x,y);} + AU3 AZolOrU3(AU3 x,AU3 y){return max(x,y);} + AU4 AZolOrU4(AU4 x,AU4 y){return max(x,y);} +//============================================================================================================================== + AU1 AZolF1ToU1(AF1 x){return AU1(x);} + AU2 AZolF2ToU2(AF2 x){return AU2(x);} + AU3 AZolF3ToU3(AF3 x){return AU3(x);} + AU4 AZolF4ToU4(AF4 x){return AU4(x);} +//------------------------------------------------------------------------------------------------------------------------------ + // 2 ops, denormals don't work in 32-bit on PC (and if they are enabled, OMOD is disabled). + AU1 AZolNotF1ToU1(AF1 x){return AU1(AF1_(1.0)-x);} + AU2 AZolNotF2ToU2(AF2 x){return AU2(AF2_(1.0)-x);} + AU3 AZolNotF3ToU3(AF3 x){return AU3(AF3_(1.0)-x);} + AU4 AZolNotF4ToU4(AF4 x){return AU4(AF4_(1.0)-x);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolU1ToF1(AU1 x){return AF1(x);} + AF2 AZolU2ToF2(AU2 x){return AF2(x);} + AF3 AZolU3ToF3(AU3 x){return AF3(x);} + AF4 AZolU4ToF4(AU4 x){return AF4(x);} +//============================================================================================================================== + AF1 AZolAndF1(AF1 x,AF1 y){return min(x,y);} + AF2 AZolAndF2(AF2 x,AF2 y){return min(x,y);} + AF3 AZolAndF3(AF3 x,AF3 y){return min(x,y);} + AF4 AZolAndF4(AF4 x,AF4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ASolAndNotF1(AF1 x,AF1 y){return (-x)*y+AF1_(1.0);} + AF2 ASolAndNotF2(AF2 x,AF2 y){return (-x)*y+AF2_(1.0);} + AF3 ASolAndNotF3(AF3 x,AF3 y){return (-x)*y+AF3_(1.0);} + AF4 ASolAndNotF4(AF4 x,AF4 y){return (-x)*y+AF4_(1.0);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolAndOrF1(AF1 x,AF1 y,AF1 z){return ASatF1(x*y+z);} + AF2 AZolAndOrF2(AF2 x,AF2 y,AF2 z){return ASatF2(x*y+z);} + AF3 AZolAndOrF3(AF3 x,AF3 y,AF3 z){return ASatF3(x*y+z);} + AF4 AZolAndOrF4(AF4 x,AF4 y,AF4 z){return ASatF4(x*y+z);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolGtZeroF1(AF1 x){return ASatF1(x*AF1_(A_INFP_F));} + AF2 AZolGtZeroF2(AF2 x){return ASatF2(x*AF2_(A_INFP_F));} + AF3 AZolGtZeroF3(AF3 x){return ASatF3(x*AF3_(A_INFP_F));} + AF4 AZolGtZeroF4(AF4 x){return ASatF4(x*AF4_(A_INFP_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolNotF1(AF1 x){return AF1_(1.0)-x;} + AF2 AZolNotF2(AF2 x){return AF2_(1.0)-x;} + AF3 AZolNotF3(AF3 x){return AF3_(1.0)-x;} + AF4 AZolNotF4(AF4 x){return AF4_(1.0)-x;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolOrF1(AF1 x,AF1 y){return max(x,y);} + AF2 AZolOrF2(AF2 x,AF2 y){return max(x,y);} + AF3 AZolOrF3(AF3 x,AF3 y){return max(x,y);} + AF4 AZolOrF4(AF4 x,AF4 y){return max(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolSelF1(AF1 x,AF1 y,AF1 z){AF1 r=(-x)*z+z;return x*y+r;} + AF2 AZolSelF2(AF2 x,AF2 y,AF2 z){AF2 r=(-x)*z+z;return x*y+r;} + AF3 AZolSelF3(AF3 x,AF3 y,AF3 z){AF3 r=(-x)*z+z;return x*y+r;} + AF4 AZolSelF4(AF4 x,AF4 y,AF4 z){AF4 r=(-x)*z+z;return x*y+r;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolSignedF1(AF1 x){return ASatF1(x*AF1_(A_INFN_F));} + AF2 AZolSignedF2(AF2 x){return ASatF2(x*AF2_(A_INFN_F));} + AF3 AZolSignedF3(AF3 x){return ASatF3(x*AF3_(A_INFN_F));} + AF4 AZolSignedF4(AF4 x){return ASatF4(x*AF4_(A_INFN_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolZeroPassF1(AF1 x,AF1 y){return AF1_AU1((AU1_AF1(x)!=AU1_(0))?AU1_(0):AU1_AF1(y));} + AF2 AZolZeroPassF2(AF2 x,AF2 y){return AF2_AU2((AU2_AF2(x)!=AU2_(0))?AU2_(0):AU2_AF2(y));} + AF3 AZolZeroPassF3(AF3 x,AF3 y){return AF3_AU3((AU3_AF3(x)!=AU3_(0))?AU3_(0):AU3_AF3(y));} + AF4 AZolZeroPassF4(AF4 x,AF4 y){return AF4_AU4((AU4_AF4(x)!=AU4_(0))?AU4_(0):AU4_AF4(y));} + #endif +//============================================================================================================================== + #ifdef A_HALF + AW1 AZolAndW1(AW1 x,AW1 y){return min(x,y);} + AW2 AZolAndW2(AW2 x,AW2 y){return min(x,y);} + AW3 AZolAndW3(AW3 x,AW3 y){return min(x,y);} + AW4 AZolAndW4(AW4 x,AW4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AZolNotW1(AW1 x){return x^AW1_(1);} + AW2 AZolNotW2(AW2 x){return x^AW2_(1);} + AW3 AZolNotW3(AW3 x){return x^AW3_(1);} + AW4 AZolNotW4(AW4 x){return x^AW4_(1);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AZolOrW1(AW1 x,AW1 y){return max(x,y);} + AW2 AZolOrW2(AW2 x,AW2 y){return max(x,y);} + AW3 AZolOrW3(AW3 x,AW3 y){return max(x,y);} + AW4 AZolOrW4(AW4 x,AW4 y){return max(x,y);} +//============================================================================================================================== + // Uses denormal trick. + AW1 AZolH1ToW1(AH1 x){return AW1_AH1(x*AH1_AW1(AW1_(1)));} + AW2 AZolH2ToW2(AH2 x){return AW2_AH2(x*AH2_AW2(AW2_(1)));} + AW3 AZolH3ToW3(AH3 x){return AW3_AH3(x*AH3_AW3(AW3_(1)));} + AW4 AZolH4ToW4(AH4 x){return AW4_AH4(x*AH4_AW4(AW4_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + // AMD arch lacks a packed conversion opcode. + AH1 AZolW1ToH1(AW1 x){return AH1_AW1(x*AW1_AH1(AH1_(1.0)));} + AH2 AZolW2ToH2(AW2 x){return AH2_AW2(x*AW2_AH2(AH2_(1.0)));} + AH3 AZolW1ToH3(AW3 x){return AH3_AW3(x*AW3_AH3(AH3_(1.0)));} + AH4 AZolW2ToH4(AW4 x){return AH4_AW4(x*AW4_AH4(AH4_(1.0)));} +//============================================================================================================================== + AH1 AZolAndH1(AH1 x,AH1 y){return min(x,y);} + AH2 AZolAndH2(AH2 x,AH2 y){return min(x,y);} + AH3 AZolAndH3(AH3 x,AH3 y){return min(x,y);} + AH4 AZolAndH4(AH4 x,AH4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASolAndNotH1(AH1 x,AH1 y){return (-x)*y+AH1_(1.0);} + AH2 ASolAndNotH2(AH2 x,AH2 y){return (-x)*y+AH2_(1.0);} + AH3 ASolAndNotH3(AH3 x,AH3 y){return (-x)*y+AH3_(1.0);} + AH4 ASolAndNotH4(AH4 x,AH4 y){return (-x)*y+AH4_(1.0);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolAndOrH1(AH1 x,AH1 y,AH1 z){return ASatH1(x*y+z);} + AH2 AZolAndOrH2(AH2 x,AH2 y,AH2 z){return ASatH2(x*y+z);} + AH3 AZolAndOrH3(AH3 x,AH3 y,AH3 z){return ASatH3(x*y+z);} + AH4 AZolAndOrH4(AH4 x,AH4 y,AH4 z){return ASatH4(x*y+z);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolGtZeroH1(AH1 x){return ASatH1(x*AH1_(A_INFP_H));} + AH2 AZolGtZeroH2(AH2 x){return ASatH2(x*AH2_(A_INFP_H));} + AH3 AZolGtZeroH3(AH3 x){return ASatH3(x*AH3_(A_INFP_H));} + AH4 AZolGtZeroH4(AH4 x){return ASatH4(x*AH4_(A_INFP_H));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolNotH1(AH1 x){return AH1_(1.0)-x;} + AH2 AZolNotH2(AH2 x){return AH2_(1.0)-x;} + AH3 AZolNotH3(AH3 x){return AH3_(1.0)-x;} + AH4 AZolNotH4(AH4 x){return AH4_(1.0)-x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolOrH1(AH1 x,AH1 y){return max(x,y);} + AH2 AZolOrH2(AH2 x,AH2 y){return max(x,y);} + AH3 AZolOrH3(AH3 x,AH3 y){return max(x,y);} + AH4 AZolOrH4(AH4 x,AH4 y){return max(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolSelH1(AH1 x,AH1 y,AH1 z){AH1 r=(-x)*z+z;return x*y+r;} + AH2 AZolSelH2(AH2 x,AH2 y,AH2 z){AH2 r=(-x)*z+z;return x*y+r;} + AH3 AZolSelH3(AH3 x,AH3 y,AH3 z){AH3 r=(-x)*z+z;return x*y+r;} + AH4 AZolSelH4(AH4 x,AH4 y,AH4 z){AH4 r=(-x)*z+z;return x*y+r;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolSignedH1(AH1 x){return ASatH1(x*AH1_(A_INFN_H));} + AH2 AZolSignedH2(AH2 x){return ASatH2(x*AH2_(A_INFN_H));} + AH3 AZolSignedH3(AH3 x){return ASatH3(x*AH3_(A_INFN_H));} + AH4 AZolSignedH4(AH4 x){return ASatH4(x*AH4_(A_INFN_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COLOR CONVERSIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These are all linear to/from some other space (where 'linear' has been shortened out of the function name). +// So 'ToGamma' is 'LinearToGamma', and 'FromGamma' is 'LinearFromGamma'. +// These are branch free implementations. +// The AToSrgbF1() function is useful for stores for compute shaders for GPUs without hardware linear->sRGB store conversion. +//------------------------------------------------------------------------------------------------------------------------------ +// TRANSFER FUNCTIONS +// ================== +// 709 ..... Rec709 used for some HDTVs +// Gamma ... Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native +// Pq ...... PQ native for HDR10 +// Srgb .... The sRGB output, typical of PC displays, useful for 10-bit output, or storing to 8-bit UNORM without SRGB type +// Two ..... Gamma 2.0, fastest conversion (useful for intermediate pass approximations) +// Three ... Gamma 3.0, less fast, but good for HDR. +//------------------------------------------------------------------------------------------------------------------------------ +// KEEPING TO SPEC +// =============== +// Both Rec.709 and sRGB have a linear segment which as spec'ed would intersect the curved segment 2 times. +// (a.) For 8-bit sRGB, steps {0 to 10.3} are in the linear region (4% of the encoding range). +// (b.) For 8-bit 709, steps {0 to 20.7} are in the linear region (8% of the encoding range). +// Also there is a slight step in the transition regions. +// Precision of the coefficients in the spec being the likely cause. +// Main usage case of the sRGB code is to do the linear->sRGB converstion in a compute shader before store. +// This is to work around lack of hardware (typically only ROP does the conversion for free). +// To "correct" the linear segment, would be to introduce error, because hardware decode of sRGB->linear is fixed (and free). +// So this header keeps with the spec. +// For linear->sRGB transforms, the linear segment in some respects reduces error, because rounding in that region is linear. +// Rounding in the curved region in hardware (and fast software code) introduces error due to rounding in non-linear. +//------------------------------------------------------------------------------------------------------------------------------ +// FOR PQ +// ====== +// Both input and output is {0.0-1.0}, and where output 1.0 represents 10000.0 cd/m^2. +// All constants are only specified to FP32 precision. +// External PQ source reference, +// - https://github.com/ampas/aces-dev/blob/master/transforms/ctl/utilities/ACESlib.Utilities_Color.a1.0.1.ctl +//------------------------------------------------------------------------------------------------------------------------------ +// PACKED VERSIONS +// =============== +// These are the A*H2() functions. +// There is no PQ functions as FP16 seemed to not have enough precision for the conversion. +// The remaining functions are "good enough" for 8-bit, and maybe 10-bit if not concerned about a few 1-bit errors. +// Precision is lowest in the 709 conversion, higher in sRGB, higher still in Two and Gamma (when using 2.2 at least). +//------------------------------------------------------------------------------------------------------------------------------ +// NOTES +// ===== +// Could be faster for PQ conversions to be in ALU or a texture lookup depending on usage case. +//============================================================================================================================== + #if 1 + AF1 ATo709F1(AF1 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AF2 ATo709F2(AF2 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AF3 ATo709F3(AF3 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + // Note 'rcpX' is '1/x', where the 'x' is what would be used in AFromGamma(). + AF1 AToGammaF1(AF1 c,AF1 rcpX){return pow(c,AF1_(rcpX));} + AF2 AToGammaF2(AF2 c,AF1 rcpX){return pow(c,AF2_(rcpX));} + AF3 AToGammaF3(AF3 c,AF1 rcpX){return pow(c,AF3_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToPqF1(AF1 x){AF1 p=pow(x,AF1_(0.159302)); + return pow((AF1_(0.835938)+AF1_(18.8516)*p)/(AF1_(1.0)+AF1_(18.6875)*p),AF1_(78.8438));} + AF2 AToPqF1(AF2 x){AF2 p=pow(x,AF2_(0.159302)); + return pow((AF2_(0.835938)+AF2_(18.8516)*p)/(AF2_(1.0)+AF2_(18.6875)*p),AF2_(78.8438));} + AF3 AToPqF1(AF3 x){AF3 p=pow(x,AF3_(0.159302)); + return pow((AF3_(0.835938)+AF3_(18.8516)*p)/(AF3_(1.0)+AF3_(18.6875)*p),AF3_(78.8438));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToSrgbF1(AF1 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AF2 AToSrgbF2(AF2 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AF3 AToSrgbF3(AF3 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToTwoF1(AF1 c){return sqrt(c);} + AF2 AToTwoF2(AF2 c){return sqrt(c);} + AF3 AToTwoF3(AF3 c){return sqrt(c);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToThreeF1(AF1 c){return pow(c,AF1_(1.0/3.0));} + AF2 AToThreeF2(AF2 c){return pow(c,AF2_(1.0/3.0));} + AF3 AToThreeF3(AF3 c){return pow(c,AF3_(1.0/3.0));} + #endif +//============================================================================================================================== + #if 1 + // Unfortunately median won't work here. + AF1 AFrom709F1(AF1 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AF2 AFrom709F2(AF2 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AF3 AFrom709F3(AF3 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromGammaF1(AF1 c,AF1 x){return pow(c,AF1_(x));} + AF2 AFromGammaF2(AF2 c,AF1 x){return pow(c,AF2_(x));} + AF3 AFromGammaF3(AF3 c,AF1 x){return pow(c,AF3_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromPqF1(AF1 x){AF1 p=pow(x,AF1_(0.0126833)); + return pow(ASatF1(p-AF1_(0.835938))/(AF1_(18.8516)-AF1_(18.6875)*p),AF1_(6.27739));} + AF2 AFromPqF1(AF2 x){AF2 p=pow(x,AF2_(0.0126833)); + return pow(ASatF2(p-AF2_(0.835938))/(AF2_(18.8516)-AF2_(18.6875)*p),AF2_(6.27739));} + AF3 AFromPqF1(AF3 x){AF3 p=pow(x,AF3_(0.0126833)); + return pow(ASatF3(p-AF3_(0.835938))/(AF3_(18.8516)-AF3_(18.6875)*p),AF3_(6.27739));} +//------------------------------------------------------------------------------------------------------------------------------ + // Unfortunately median won't work here. + AF1 AFromSrgbF1(AF1 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AF2 AFromSrgbF2(AF2 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AF3 AFromSrgbF3(AF3 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromTwoF1(AF1 c){return c*c;} + AF2 AFromTwoF2(AF2 c){return c*c;} + AF3 AFromTwoF3(AF3 c){return c*c;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromThreeF1(AF1 c){return c*c*c;} + AF2 AFromThreeF2(AF2 c){return c*c*c;} + AF3 AFromThreeF3(AF3 c){return c*c*c;} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH1 ATo709H1(AH1 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AH2 ATo709H2(AH2 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AH3 ATo709H3(AH3 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToGammaH1(AH1 c,AH1 rcpX){return pow(c,AH1_(rcpX));} + AH2 AToGammaH2(AH2 c,AH1 rcpX){return pow(c,AH2_(rcpX));} + AH3 AToGammaH3(AH3 c,AH1 rcpX){return pow(c,AH3_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToSrgbH1(AH1 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AH2 AToSrgbH2(AH2 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AH3 AToSrgbH3(AH3 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToTwoH1(AH1 c){return sqrt(c);} + AH2 AToTwoH2(AH2 c){return sqrt(c);} + AH3 AToTwoH3(AH3 c){return sqrt(c);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToThreeF1(AH1 c){return pow(c,AH1_(1.0/3.0));} + AH2 AToThreeF2(AH2 c){return pow(c,AH2_(1.0/3.0));} + AH3 AToThreeF3(AH3 c){return pow(c,AH3_(1.0/3.0));} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH1 AFrom709H1(AH1 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AH2 AFrom709H2(AH2 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AH3 AFrom709H3(AH3 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromGammaH1(AH1 c,AH1 x){return pow(c,AH1_(x));} + AH2 AFromGammaH2(AH2 c,AH1 x){return pow(c,AH2_(x));} + AH3 AFromGammaH3(AH3 c,AH1 x){return pow(c,AH3_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AHromSrgbF1(AH1 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AH2 AHromSrgbF2(AH2 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AH3 AHromSrgbF3(AH3 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromTwoH1(AH1 c){return c*c;} + AH2 AFromTwoH2(AH2 c){return c*c;} + AH3 AFromTwoH3(AH3 c){return c*c;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromThreeH1(AH1 c){return c*c*c;} + AH2 AFromThreeH2(AH2 c){return c*c*c;} + AH3 AFromThreeH3(AH3 c){return c*c*c;} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CS REMAP +//============================================================================================================================== + // Simple remap 64x1 to 8x8 with rotated 2x2 pixel quads in quad linear. + // 543210 + // ====== + // ..xxx. + // yy...y + AU2 ARmp8x8(AU1 a){return AU2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} +//============================================================================================================================== + // More complex remap 64x1 to 8x8 which is necessary for 2D wave reductions. + // 543210 + // ====== + // .xx..x + // y..yy. + // Details, + // LANE TO 8x8 MAPPING + // =================== + // 00 01 08 09 10 11 18 19 + // 02 03 0a 0b 12 13 1a 1b + // 04 05 0c 0d 14 15 1c 1d + // 06 07 0e 0f 16 17 1e 1f + // 20 21 28 29 30 31 38 39 + // 22 23 2a 2b 32 33 3a 3b + // 24 25 2c 2d 34 35 3c 3d + // 26 27 2e 2f 36 37 3e 3f + AU2 ARmpRed8x8(AU1 a){return AU2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} +//============================================================================================================================== + #ifdef A_HALF + AW2 ARmp8x8H(AU1 a){return AW2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} + AW2 ARmpRed8x8H(AU1 a){return AW2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} + #endif +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// REFERENCE +// +//------------------------------------------------------------------------------------------------------------------------------ +// IEEE FLOAT RULES +// ================ +// - saturate(NaN)=0, saturate(-INF)=0, saturate(+INF)=1 +// - {+/-}0 * {+/-}INF = NaN +// - -INF + (+INF) = NaN +// - {+/-}0 / {+/-}0 = NaN +// - {+/-}INF / {+/-}INF = NaN +// - a<(-0) := sqrt(a) = NaN (a=-0.0 won't NaN) +// - 0 == -0 +// - 4/0 = +INF +// - 4/-0 = -INF +// - 4+INF = +INF +// - 4-INF = -INF +// - 4*(+INF) = +INF +// - 4*(-INF) = -INF +// - -4*(+INF) = -INF +// - sqrt(+INF) = +INF +//------------------------------------------------------------------------------------------------------------------------------ +// FP16 ENCODING +// ============= +// fedcba9876543210 +// ---------------- +// ......mmmmmmmmmm 10-bit mantissa (encodes 11-bit 0.5 to 1.0 except for denormals) +// .eeeee.......... 5-bit exponent +// .00000.......... denormals +// .00001.......... -14 exponent +// .11110.......... 15 exponent +// .111110000000000 infinity +// .11111nnnnnnnnnn NaN with n!=0 +// s............... sign +//------------------------------------------------------------------------------------------------------------------------------ +// FP16/INT16 ALIASING DENORMAL +// ============================ +// 11-bit unsigned integers alias with half float denormal/normal values, +// 1 = 2^(-24) = 1/16777216 ....................... first denormal value +// 2 = 2^(-23) +// ... +// 1023 = 2^(-14)*(1-2^(-10)) = 2^(-14)*(1-1/1024) ... last denormal value +// 1024 = 2^(-14) = 1/16384 .......................... first normal value that still maps to integers +// 2047 .............................................. last normal value that still maps to integers +// Scaling limits, +// 2^15 = 32768 ...................................... largest power of 2 scaling +// Largest pow2 conversion mapping is at *32768, +// 1 : 2^(-9) = 1/512 +// 2 : 1/256 +// 4 : 1/128 +// 8 : 1/64 +// 16 : 1/32 +// 32 : 1/16 +// 64 : 1/8 +// 128 : 1/4 +// 256 : 1/2 +// 512 : 1 +// 1024 : 2 +// 2047 : a little less than 4 +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU/CPU PORTABILITY +// +// +//------------------------------------------------------------------------------------------------------------------------------ +// This is the GPU implementation. +// See the CPU implementation for docs. +//============================================================================================================================== +#ifdef A_GPU + #define A_TRUE true + #define A_FALSE false + #define A_STATIC +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD2 + #define retAD3 AD3 + #define retAD4 AD4 + #define retAF2 AF2 + #define retAF3 AF3 + #define retAF4 AF4 + #define retAL2 AL2 + #define retAL3 AL3 + #define retAL4 AL4 + #define retAU2 AU2 + #define retAU3 AU3 + #define retAU4 AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 in AD2 + #define inAD3 in AD3 + #define inAD4 in AD4 + #define inAF2 in AF2 + #define inAF3 in AF3 + #define inAF4 in AF4 + #define inAL2 in AL2 + #define inAL3 in AL3 + #define inAL4 in AL4 + #define inAU2 in AU2 + #define inAU3 in AU3 + #define inAU4 in AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 inout AD2 + #define inoutAD3 inout AD3 + #define inoutAD4 inout AD4 + #define inoutAF2 inout AF2 + #define inoutAF3 inout AF3 + #define inoutAF4 inout AF4 + #define inoutAL2 inout AL2 + #define inoutAL3 inout AL3 + #define inoutAL4 inout AL4 + #define inoutAU2 inout AU2 + #define inoutAU3 inout AU3 + #define inoutAU4 inout AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 out AD2 + #define outAD3 out AD3 + #define outAD4 out AD4 + #define outAF2 out AF2 + #define outAF3 out AF3 + #define outAF4 out AF4 + #define outAL2 out AL2 + #define outAL3 out AL3 + #define outAL4 out AL4 + #define outAU2 out AU2 + #define outAU3 out AU3 + #define outAU4 out AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD2 x + #define varAD3(x) AD3 x + #define varAD4(x) AD4 x + #define varAF2(x) AF2 x + #define varAF3(x) AF3 x + #define varAF4(x) AF4 x + #define varAL2(x) AL2 x + #define varAL3(x) AL3 x + #define varAL4(x) AL4 x + #define varAU2(x) AU2 x + #define varAU3(x) AU3 x + #define varAU4(x) AU4 x +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) AD2(x,y) + #define initAD3(x,y,z) AD3(x,y,z) + #define initAD4(x,y,z,w) AD4(x,y,z,w) + #define initAF2(x,y) AF2(x,y) + #define initAF3(x,y,z) AF3(x,y,z) + #define initAF4(x,y,z,w) AF4(x,y,z,w) + #define initAL2(x,y) AL2(x,y) + #define initAL3(x,y,z) AL3(x,y,z) + #define initAL4(x,y,z,w) AL4(x,y,z,w) + #define initAU2(x,y) AU2(x,y) + #define initAU3(x,y,z) AU3(x,y,z) + #define initAU4(x,y,z,w) AU4(x,y,z,w) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//============================================================================================================================== + #define AAbsD1(a) abs(AD1(a)) + #define AAbsF1(a) abs(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ACosD1(a) cos(AD1(a)) + #define ACosF1(a) cos(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ADotD2(a,b) dot(AD2(a),AD2(b)) + #define ADotD3(a,b) dot(AD3(a),AD3(b)) + #define ADotD4(a,b) dot(AD4(a),AD4(b)) + #define ADotF2(a,b) dot(AF2(a),AF2(b)) + #define ADotF3(a,b) dot(AF3(a),AF3(b)) + #define ADotF4(a,b) dot(AF4(a),AF4(b)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AExp2D1(a) exp2(AD1(a)) + #define AExp2F1(a) exp2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AFloorD1(a) floor(AD1(a)) + #define AFloorF1(a) floor(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ALog2D1(a) log2(AD1(a)) + #define ALog2F1(a) log2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMaxD1(a,b) max(a,b) + #define AMaxF1(a,b) max(a,b) + #define AMaxL1(a,b) max(a,b) + #define AMaxU1(a,b) max(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMinD1(a,b) min(a,b) + #define AMinF1(a,b) min(a,b) + #define AMinL1(a,b) min(a,b) + #define AMinU1(a,b) min(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASinD1(a) sin(AD1(a)) + #define ASinF1(a) sin(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASqrtD1(a) sqrt(AD1(a)) + #define ASqrtF1(a) sqrt(AF1(a)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + #define APowD1(a,b) pow(AD1(a),AF1(b)) + #define APowF1(a,b) pow(AF1(a),AF1(b)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + #ifdef A_DUBL + AD2 opAAbsD2(outAD2 d,inAD2 a){d=abs(a);return d;} + AD3 opAAbsD3(outAD3 d,inAD3 a){d=abs(a);return d;} + AD4 opAAbsD4(outAD4 d,inAD4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d=a+b;return d;} + AD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d=a+b;return d;} + AD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d=a+AD2_(b);return d;} + AD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d=a+AD3_(b);return d;} + AD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d=a+AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opACpyD2(outAD2 d,inAD2 a){d=a;return d;} + AD3 opACpyD3(outAD3 d,inAD3 a){d=a;return d;} + AD4 opACpyD4(outAD4 d,inAD4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d=ALerpD2(a,b,c);return d;} + AD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d=ALerpD3(a,b,c);return d;} + AD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d=ALerpD4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d=ALerpD2(a,b,AD2_(c));return d;} + AD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d=ALerpD3(a,b,AD3_(c));return d;} + AD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d=ALerpD4(a,b,AD4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d=max(a,b);return d;} + AD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d=max(a,b);return d;} + AD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d=min(a,b);return d;} + AD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d=min(a,b);return d;} + AD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d=a*b;return d;} + AD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d=a*b;return d;} + AD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d=a*AD2_(b);return d;} + AD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d=a*AD3_(b);return d;} + AD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d=a*AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opANegD2(outAD2 d,inAD2 a){d=-a;return d;} + AD3 opANegD3(outAD3 d,inAD3 a){d=-a;return d;} + AD4 opANegD4(outAD4 d,inAD4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opARcpD2(outAD2 d,inAD2 a){d=ARcpD2(a);return d;} + AD3 opARcpD3(outAD3 d,inAD3 a){d=ARcpD3(a);return d;} + AD4 opARcpD4(outAD4 d,inAD4 a){d=ARcpD4(a);return d;} + #endif +//============================================================================================================================== + AF2 opAAbsF2(outAF2 d,inAF2 a){d=abs(a);return d;} + AF3 opAAbsF3(outAF3 d,inAF3 a){d=abs(a);return d;} + AF4 opAAbsF4(outAF4 d,inAF4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d=a+b;return d;} + AF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d=a+b;return d;} + AF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d=a+AF2_(b);return d;} + AF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d=a+AF3_(b);return d;} + AF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d=a+AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opACpyF2(outAF2 d,inAF2 a){d=a;return d;} + AF3 opACpyF3(outAF3 d,inAF3 a){d=a;return d;} + AF4 opACpyF4(outAF4 d,inAF4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d=ALerpF2(a,b,c);return d;} + AF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d=ALerpF3(a,b,c);return d;} + AF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d=ALerpF4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d=ALerpF2(a,b,AF2_(c));return d;} + AF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d=ALerpF3(a,b,AF3_(c));return d;} + AF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d=ALerpF4(a,b,AF4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d=max(a,b);return d;} + AF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d=max(a,b);return d;} + AF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d=min(a,b);return d;} + AF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d=min(a,b);return d;} + AF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d=a*b;return d;} + AF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d=a*b;return d;} + AF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d=a*AF2_(b);return d;} + AF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d=a*AF3_(b);return d;} + AF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d=a*AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opANegF2(outAF2 d,inAF2 a){d=-a;return d;} + AF3 opANegF3(outAF3 d,inAF3 a){d=-a;return d;} + AF4 opANegF4(outAF4 d,inAF4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opARcpF2(outAF2 d,inAF2 a){d=ARcpF2(a);return d;} + AF3 opARcpF3(outAF3 d,inAF3 a){d=ARcpF3(a);return d;} + AF4 opARcpF4(outAF4 d,inAF4 a){d=ARcpF4(a);return d;} +#endif + + +#define FSR_RCAS_F 1 +AU4 con0; + +AF4 FsrRcasLoadF(ASU2 p) { return AF4(texelFetch(source, p, 0)); } +void FsrRcasInputF(inout AF1 r, inout AF1 g, inout AF1 b) {} + +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// AMD FidelityFX SUPER RESOLUTION [FSR 1] ::: SPATIAL SCALING & EXTRAS - v1.20210629 +// +// +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// ABOUT +// ===== +// FSR is a collection of algorithms relating to generating a higher resolution image. +// This specific header focuses on single-image non-temporal image scaling, and related tools. +// +// The core functions are EASU and RCAS: +// [EASU] Edge Adaptive Spatial Upsampling ....... 1x to 4x area range spatial scaling, clamped adaptive elliptical filter. +// [RCAS] Robust Contrast Adaptive Sharpening .... A non-scaling variation on CAS. +// RCAS needs to be applied after EASU as a separate pass. +// +// Optional utility functions are: +// [LFGA] Linear Film Grain Applicator ........... Tool to apply film grain after scaling. +// [SRTM] Simple Reversible Tone-Mapper .......... Linear HDR {0 to FP16_MAX} to {0 to 1} and back. +// [TEPD] Temporal Energy Preserving Dither ...... Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. +// See each individual sub-section for inline documentation. +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// FUNCTION PERMUTATIONS +// ===================== +// *F() ..... Single item computation with 32-bit. +// *H() ..... Single item computation with 16-bit, with packing (aka two 16-bit ops in parallel) when possible. +// *Hx2() ... Processing two items in parallel with 16-bit, easier packing. +// Not all interfaces in this file have a *Hx2() form. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [EASU] EDGE ADAPTIVE SPATIAL UPSAMPLING +// +//------------------------------------------------------------------------------------------------------------------------------ +// EASU provides a high quality spatial-only scaling at relatively low cost. +// Meaning EASU is appropiate for laptops and other low-end GPUs. +// Quality from 1x to 4x area scaling is good. +//------------------------------------------------------------------------------------------------------------------------------ +// The scalar uses a modified fast approximation to the standard lanczos(size=2) kernel. +// EASU runs in a single pass, so it applies a directionally and anisotropically adaptive radial lanczos. +// This is also kept as simple as possible to have minimum runtime. +//------------------------------------------------------------------------------------------------------------------------------ +// The lanzcos filter has negative lobes, so by itself it will introduce ringing. +// To remove all ringing, the algorithm uses the nearest 2x2 input texels as a neighborhood, +// and limits output to the minimum and maximum of that neighborhood. +//------------------------------------------------------------------------------------------------------------------------------ +// Input image requirements: +// +// Color needs to be encoded as 3 channel[red, green, blue](e.g.XYZ not supported) +// Each channel needs to be in the range[0, 1] +// Any color primaries are supported +// Display / tonemapping curve needs to be as if presenting to sRGB display or similar(e.g.Gamma 2.0) +// There should be no banding in the input +// There should be no high amplitude noise in the input +// There should be no noise in the input that is not at input pixel granularity +// For performance purposes, use 32bpp formats +//------------------------------------------------------------------------------------------------------------------------------ +// Best to apply EASU at the end of the frame after tonemapping +// but before film grain or composite of the UI. +//------------------------------------------------------------------------------------------------------------------------------ +// Example of including this header for D3D HLSL : +// +// #define A_GPU 1 +// #define A_HLSL 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of including this header for Vulkan GLSL : +// +// #define A_GPU 1 +// #define A_GLSL 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of including this header for Vulkan HLSL : +// +// #define A_GPU 1 +// #define A_HLSL 1 +// #define A_HLSL_6_2 1 +// #define A_NO_16_BIT_CAST 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of declaring the required input callbacks for GLSL : +// The callbacks need to gather4 for each color channel using the specified texture coordinate 'p'. +// EASU uses gather4 to reduce position computation logic and for free Arrays of Structures to Structures of Arrays conversion. +// +// AH4 FsrEasuRH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,0));} +// AH4 FsrEasuGH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,1));} +// AH4 FsrEasuBH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,2));} +// ... +// The FsrEasuCon function needs to be called from the CPU or GPU to set up constants. +// The difference in viewport and input image size is there to support Dynamic Resolution Scaling. +// To use FsrEasuCon() on the CPU, define A_CPU before including ffx_a and ffx_fsr1. +// Including a GPU example here, the 'con0' through 'con3' values would be stored out to a constant buffer. +// AU4 con0,con1,con2,con3; +// FsrEasuCon(con0,con1,con2,con3, +// 1920.0,1080.0, // Viewport size (top left aligned) in the input image which is to be scaled. +// 3840.0,2160.0, // The size of the input image. +// 2560.0,1440.0); // The output resolution. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CONSTANT SETUP +//============================================================================================================================== +// Call to setup required constant values (works on CPU or GPU). +A_STATIC void FsrEasuCon( +outAU4 con0, +outAU4 con1, +outAU4 con2, +outAU4 con3, +// This the rendered image resolution being upscaled +AF1 inputViewportInPixelsX, +AF1 inputViewportInPixelsY, +// This is the resolution of the resource containing the input image (useful for dynamic resolution) +AF1 inputSizeInPixelsX, +AF1 inputSizeInPixelsY, +// This is the display resolution which the input image gets upscaled to +AF1 outputSizeInPixelsX, +AF1 outputSizeInPixelsY){ + // Output integer position to a pixel position in viewport. + con0[0]=AU1_AF1(inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)); + con0[1]=AU1_AF1(inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)); + con0[2]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)-AF1_(0.5)); + con0[3]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)-AF1_(0.5)); + // Viewport pixel position to normalized image space. + // This is used to get upper-left of 'F' tap. + con1[0]=AU1_AF1(ARcpF1(inputSizeInPixelsX)); + con1[1]=AU1_AF1(ARcpF1(inputSizeInPixelsY)); + // Centers of gather4, first offset from upper-left of 'F'. + // +---+---+ + // | | | + // +--(0)--+ + // | b | c | + // +---F---+---+---+ + // | e | f | g | h | + // +--(1)--+--(2)--+ + // | i | j | k | l | + // +---+---+---+---+ + // | n | o | + // +--(3)--+ + // | | | + // +---+---+ + con1[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); + con1[3]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsY)); + // These are from (0) instead of 'F'. + con2[0]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsX)); + con2[1]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); + con2[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); + con2[3]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); + con3[0]=AU1_AF1(AF1_( 0.0)*ARcpF1(inputSizeInPixelsX)); + con3[1]=AU1_AF1(AF1_( 4.0)*ARcpF1(inputSizeInPixelsY)); + con3[2]=con3[3]=0;} + +//If the an offset into the input image resource +A_STATIC void FsrEasuConOffset( + outAU4 con0, + outAU4 con1, + outAU4 con2, + outAU4 con3, + // This the rendered image resolution being upscaled + AF1 inputViewportInPixelsX, + AF1 inputViewportInPixelsY, + // This is the resolution of the resource containing the input image (useful for dynamic resolution) + AF1 inputSizeInPixelsX, + AF1 inputSizeInPixelsY, + // This is the display resolution which the input image gets upscaled to + AF1 outputSizeInPixelsX, + AF1 outputSizeInPixelsY, + // This is the input image offset into the resource containing it (useful for dynamic resolution) + AF1 inputOffsetInPixelsX, + AF1 inputOffsetInPixelsY) { + FsrEasuCon(con0, con1, con2, con3, inputViewportInPixelsX, inputViewportInPixelsY, inputSizeInPixelsX, inputSizeInPixelsY, outputSizeInPixelsX, outputSizeInPixelsY); + con0[2] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsX * ARcpF1(outputSizeInPixelsX) - AF1_(0.5) + inputOffsetInPixelsX); + con0[3] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsY * ARcpF1(outputSizeInPixelsY) - AF1_(0.5) + inputOffsetInPixelsY); +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 32-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(FSR_EASU_F) + // Input callback prototypes, need to be implemented by calling shader + AF4 FsrEasuRF(AF2 p); + AF4 FsrEasuGF(AF2 p); + AF4 FsrEasuBF(AF2 p); +//------------------------------------------------------------------------------------------------------------------------------ + // Filtering for a given tap for the scalar. + void FsrEasuTapF( + inout AF3 aC, // Accumulated color, with negative lobe. + inout AF1 aW, // Accumulated weight. + AF2 off, // Pixel offset from resolve position to tap. + AF2 dir, // Gradient direction. + AF2 len, // Length. + AF1 lob, // Negative lobe strength. + AF1 clp, // Clipping point. + AF3 c){ // Tap color. + // Rotate offset by direction. + AF2 v; + v.x=(off.x*( dir.x))+(off.y*dir.y); + v.y=(off.x*(-dir.y))+(off.y*dir.x); + // Anisotropy. + v*=len; + // Compute distance^2. + AF1 d2=v.x*v.x+v.y*v.y; + // Limit to the window as at corner, 2 taps can easily be outside. + d2=min(d2,clp); + // Approximation of lancos2 without sin() or rcp(), or sqrt() to get x. + // (25/16 * (2/5 * x^2 - 1)^2 - (25/16 - 1)) * (1/4 * x^2 - 1)^2 + // |_______________________________________| |_______________| + // base window + // The general form of the 'base' is, + // (a*(b*x^2-1)^2-(a-1)) + // Where 'a=1/(2*b-b^2)' and 'b' moves around the negative lobe. + AF1 wB=AF1_(2.0/5.0)*d2+AF1_(-1.0); + AF1 wA=lob*d2+AF1_(-1.0); + wB*=wB; + wA*=wA; + wB=AF1_(25.0/16.0)*wB+AF1_(-(25.0/16.0-1.0)); + AF1 w=wB*wA; + // Do weighted average. + aC+=c*w;aW+=w;} +//------------------------------------------------------------------------------------------------------------------------------ + // Accumulate direction and length. + void FsrEasuSetF( + inout AF2 dir, + inout AF1 len, + AF2 pp, + AP1 biS,AP1 biT,AP1 biU,AP1 biV, + AF1 lA,AF1 lB,AF1 lC,AF1 lD,AF1 lE){ + // Compute bilinear weight, branches factor out as predicates are compiler time immediates. + // s t + // u v + AF1 w = AF1_(0.0); + if(biS)w=(AF1_(1.0)-pp.x)*(AF1_(1.0)-pp.y); + if(biT)w= pp.x *(AF1_(1.0)-pp.y); + if(biU)w=(AF1_(1.0)-pp.x)* pp.y ; + if(biV)w= pp.x * pp.y ; + // Direction is the '+' diff. + // a + // b c d + // e + // Then takes magnitude from abs average of both sides of 'c'. + // Length converts gradient reversal to 0, smoothly to non-reversal at 1, shaped, then adding horz and vert terms. + AF1 dc=lD-lC; + AF1 cb=lC-lB; + AF1 lenX=max(abs(dc),abs(cb)); + lenX=APrxLoRcpF1(lenX); + AF1 dirX=lD-lB; + dir.x+=dirX*w; + lenX=ASatF1(abs(dirX)*lenX); + lenX*=lenX; + len+=lenX*w; + // Repeat for the y axis. + AF1 ec=lE-lC; + AF1 ca=lC-lA; + AF1 lenY=max(abs(ec),abs(ca)); + lenY=APrxLoRcpF1(lenY); + AF1 dirY=lE-lA; + dir.y+=dirY*w; + lenY=ASatF1(abs(dirY)*lenY); + lenY*=lenY; + len+=lenY*w;} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrEasuF( + out AF3 pix, + AU2 ip, // Integer pixel position in output. + AU4 con0, // Constants generated by FsrEasuCon(). + AU4 con1, + AU4 con2, + AU4 con3){ +//------------------------------------------------------------------------------------------------------------------------------ + // Get position of 'f'. + AF2 pp=AF2(ip)*AF2_AU2(con0.xy)+AF2_AU2(con0.zw); + AF2 fp=floor(pp); + pp-=fp; +//------------------------------------------------------------------------------------------------------------------------------ + // 12-tap kernel. + // b c + // e f g h + // i j k l + // n o + // Gather 4 ordering. + // a b + // r g + // For packed FP16, need either {rg} or {ab} so using the following setup for gather in all versions, + // a b <- unused (z) + // r g + // a b a b + // r g r g + // a b + // r g <- unused (z) + // Allowing dead-code removal to remove the 'z's. + AF2 p0=fp*AF2_AU2(con1.xy)+AF2_AU2(con1.zw); + // These are from p0 to avoid pulling two constants on pre-Navi hardware. + AF2 p1=p0+AF2_AU2(con2.xy); + AF2 p2=p0+AF2_AU2(con2.zw); + AF2 p3=p0+AF2_AU2(con3.xy); + AF4 bczzR=FsrEasuRF(p0); + AF4 bczzG=FsrEasuGF(p0); + AF4 bczzB=FsrEasuBF(p0); + AF4 ijfeR=FsrEasuRF(p1); + AF4 ijfeG=FsrEasuGF(p1); + AF4 ijfeB=FsrEasuBF(p1); + AF4 klhgR=FsrEasuRF(p2); + AF4 klhgG=FsrEasuGF(p2); + AF4 klhgB=FsrEasuBF(p2); + AF4 zzonR=FsrEasuRF(p3); + AF4 zzonG=FsrEasuGF(p3); + AF4 zzonB=FsrEasuBF(p3); +//------------------------------------------------------------------------------------------------------------------------------ + // Simplest multi-channel approximate luma possible (luma times 2, in 2 FMA/MAD). + AF4 bczzL=bczzB*AF4_(0.5)+(bczzR*AF4_(0.5)+bczzG); + AF4 ijfeL=ijfeB*AF4_(0.5)+(ijfeR*AF4_(0.5)+ijfeG); + AF4 klhgL=klhgB*AF4_(0.5)+(klhgR*AF4_(0.5)+klhgG); + AF4 zzonL=zzonB*AF4_(0.5)+(zzonR*AF4_(0.5)+zzonG); + // Rename. + AF1 bL=bczzL.x; + AF1 cL=bczzL.y; + AF1 iL=ijfeL.x; + AF1 jL=ijfeL.y; + AF1 fL=ijfeL.z; + AF1 eL=ijfeL.w; + AF1 kL=klhgL.x; + AF1 lL=klhgL.y; + AF1 hL=klhgL.z; + AF1 gL=klhgL.w; + AF1 oL=zzonL.z; + AF1 nL=zzonL.w; + // Accumulate for bilinear interpolation. + AF2 dir=AF2_(0.0); + AF1 len=AF1_(0.0); + FsrEasuSetF(dir,len,pp,true, false,false,false,bL,eL,fL,gL,jL); + FsrEasuSetF(dir,len,pp,false,true ,false,false,cL,fL,gL,hL,kL); + FsrEasuSetF(dir,len,pp,false,false,true ,false,fL,iL,jL,kL,nL); + FsrEasuSetF(dir,len,pp,false,false,false,true ,gL,jL,kL,lL,oL); +//------------------------------------------------------------------------------------------------------------------------------ + // Normalize with approximation, and cleanup close to zero. + AF2 dir2=dir*dir; + AF1 dirR=dir2.x+dir2.y; + AP1 zro=dirR w = -m/(n+e+w+s) +// 1 == (w*(n+e+w+s)+m)/(4*w+1) -> w = (1-m)/(n+e+w+s-4*1) +// Then chooses the 'w' which results in no clipping, limits 'w', and multiplies by the 'sharp' amount. +// This solution above has issues with MSAA input as the steps along the gradient cause edge detection issues. +// So RCAS uses 4x the maximum and 4x the minimum (depending on equation)in place of the individual taps. +// As well as switching from 'm' to either the minimum or maximum (depending on side), to help in energy conservation. +// This stabilizes RCAS. +// RCAS does a simple highpass which is normalized against the local contrast then shaped, +// 0.25 +// 0.25 -1 0.25 +// 0.25 +// This is used as a noise detection filter, to reduce the effect of RCAS on grain, and focus on real edges. +// +// GLSL example for the required callbacks : +// +// AH4 FsrRcasLoadH(ASW2 p){return AH4(imageLoad(imgSrc,ASU2(p)));} +// void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b) +// { +// //do any simple input color conversions here or leave empty if none needed +// } +// +// FsrRcasCon need to be called from the CPU or GPU to set up constants. +// Including a GPU example here, the 'con' value would be stored out to a constant buffer. +// +// AU4 con; +// FsrRcasCon(con, +// 0.0); // The scale is {0.0 := maximum sharpness, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. +// --------------- +// RCAS sharpening supports a CAS-like pass-through alpha via, +// #define FSR_RCAS_PASSTHROUGH_ALPHA 1 +// RCAS also supports a define to enable a more expensive path to avoid some sharpening of noise. +// Would suggest it is better to apply film grain after RCAS sharpening (and after scaling) instead of using this define, +// #define FSR_RCAS_DENOISE 1 +//============================================================================================================================== +// This is set at the limit of providing unnatural results for sharpening. +#define FSR_RCAS_LIMIT (0.25-(1.0/16.0)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CONSTANT SETUP +//============================================================================================================================== +// Call to setup required constant values (works on CPU or GPU). +A_STATIC void FsrRcasCon( +outAU4 con, +// The scale is {0.0 := maximum, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. +AF1 sharpness){ + // Transform from stops to linear value. + sharpness=AExp2F1(-sharpness); + varAF2(hSharp)=initAF2(sharpness,sharpness); + con[0]=AU1_AF1(sharpness); + con[1]=AU1_AH2_AF2(hSharp); + con[2]=0; + con[3]=0;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 32-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(FSR_RCAS_F) + // Input callback prototypes that need to be implemented by calling shader + AF4 FsrRcasLoadF(ASU2 p); + void FsrRcasInputF(inout AF1 r,inout AF1 g,inout AF1 b); +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasF( + out AF1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. + out AF1 pixG, + out AF1 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AF1 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // Algorithm uses minimal 3x3 pixel neighborhood. + // b + // d e f + // h + ASU2 sp=ASU2(ip); + AF3 b=FsrRcasLoadF(sp+ASU2( 0,-1)).rgb; + AF3 d=FsrRcasLoadF(sp+ASU2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AF4 ee=FsrRcasLoadF(sp); + AF3 e=ee.rgb;pixA=ee.a; + #else + AF3 e=FsrRcasLoadF(sp).rgb; + #endif + AF3 f=FsrRcasLoadF(sp+ASU2( 1, 0)).rgb; + AF3 h=FsrRcasLoadF(sp+ASU2( 0, 1)).rgb; + // Rename (32-bit) or regroup (16-bit). + AF1 bR=b.r; + AF1 bG=b.g; + AF1 bB=b.b; + AF1 dR=d.r; + AF1 dG=d.g; + AF1 dB=d.b; + AF1 eR=e.r; + AF1 eG=e.g; + AF1 eB=e.b; + AF1 fR=f.r; + AF1 fG=f.g; + AF1 fB=f.b; + AF1 hR=h.r; + AF1 hG=h.g; + AF1 hB=h.b; + // Run optional input transform. + FsrRcasInputF(bR,bG,bB); + FsrRcasInputF(dR,dG,dB); + FsrRcasInputF(eR,eG,eB); + FsrRcasInputF(fR,fG,fB); + FsrRcasInputF(hR,hG,hB); + // Luma times 2. + AF1 bL=bB*AF1_(0.5)+(bR*AF1_(0.5)+bG); + AF1 dL=dB*AF1_(0.5)+(dR*AF1_(0.5)+dG); + AF1 eL=eB*AF1_(0.5)+(eR*AF1_(0.5)+eG); + AF1 fL=fB*AF1_(0.5)+(fR*AF1_(0.5)+fG); + AF1 hL=hB*AF1_(0.5)+(hR*AF1_(0.5)+hG); + // Noise detection. + AF1 nz=AF1_(0.25)*bL+AF1_(0.25)*dL+AF1_(0.25)*fL+AF1_(0.25)*hL-eL; + nz=ASatF1(abs(nz)*APrxMedRcpF1(AMax3F1(AMax3F1(bL,dL,eL),fL,hL)-AMin3F1(AMin3F1(bL,dL,eL),fL,hL))); + nz=AF1_(-0.5)*nz+AF1_(1.0); + // Min and max of ring. + AF1 mn4R=min(AMin3F1(bR,dR,fR),hR); + AF1 mn4G=min(AMin3F1(bG,dG,fG),hG); + AF1 mn4B=min(AMin3F1(bB,dB,fB),hB); + AF1 mx4R=max(AMax3F1(bR,dR,fR),hR); + AF1 mx4G=max(AMax3F1(bG,dG,fG),hG); + AF1 mx4B=max(AMax3F1(bB,dB,fB),hB); + // Immediate constants for peak range. + AF2 peakC=AF2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AF1 hitMinR=min(mn4R,eR)*ARcpF1(AF1_(4.0)*mx4R); + AF1 hitMinG=min(mn4G,eG)*ARcpF1(AF1_(4.0)*mx4G); + AF1 hitMinB=min(mn4B,eB)*ARcpF1(AF1_(4.0)*mx4B); + AF1 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpF1(AF1_(4.0)*mn4R+peakC.y); + AF1 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpF1(AF1_(4.0)*mn4G+peakC.y); + AF1 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpF1(AF1_(4.0)*mn4B+peakC.y); + AF1 lobeR=max(-hitMinR,hitMaxR); + AF1 lobeG=max(-hitMinG,hitMaxG); + AF1 lobeB=max(-hitMinB,hitMaxB); + AF1 lobe=max(AF1_(-FSR_RCAS_LIMIT),min(AMax3F1(lobeR,lobeG,lobeB),AF1_(0.0)))*AF1_AU1(con.x); + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AF1 rcpL=APrxMedRcpF1(AF1_(4.0)*lobe+AF1_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL; + return;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 16-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_H) + // Input callback prototypes that need to be implemented by calling shader + AH4 FsrRcasLoadH(ASW2 p); + void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b); +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasH( + out AH1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. + out AH1 pixG, + out AH1 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AH1 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // Sharpening algorithm uses minimal 3x3 pixel neighborhood. + // b + // d e f + // h + ASW2 sp=ASW2(ip); + AH3 b=FsrRcasLoadH(sp+ASW2( 0,-1)).rgb; + AH3 d=FsrRcasLoadH(sp+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee=FsrRcasLoadH(sp); + AH3 e=ee.rgb;pixA=ee.a; + #else + AH3 e=FsrRcasLoadH(sp).rgb; + #endif + AH3 f=FsrRcasLoadH(sp+ASW2( 1, 0)).rgb; + AH3 h=FsrRcasLoadH(sp+ASW2( 0, 1)).rgb; + // Rename (32-bit) or regroup (16-bit). + AH1 bR=b.r; + AH1 bG=b.g; + AH1 bB=b.b; + AH1 dR=d.r; + AH1 dG=d.g; + AH1 dB=d.b; + AH1 eR=e.r; + AH1 eG=e.g; + AH1 eB=e.b; + AH1 fR=f.r; + AH1 fG=f.g; + AH1 fB=f.b; + AH1 hR=h.r; + AH1 hG=h.g; + AH1 hB=h.b; + // Run optional input transform. + FsrRcasInputH(bR,bG,bB); + FsrRcasInputH(dR,dG,dB); + FsrRcasInputH(eR,eG,eB); + FsrRcasInputH(fR,fG,fB); + FsrRcasInputH(hR,hG,hB); + // Luma times 2. + AH1 bL=bB*AH1_(0.5)+(bR*AH1_(0.5)+bG); + AH1 dL=dB*AH1_(0.5)+(dR*AH1_(0.5)+dG); + AH1 eL=eB*AH1_(0.5)+(eR*AH1_(0.5)+eG); + AH1 fL=fB*AH1_(0.5)+(fR*AH1_(0.5)+fG); + AH1 hL=hB*AH1_(0.5)+(hR*AH1_(0.5)+hG); + // Noise detection. + AH1 nz=AH1_(0.25)*bL+AH1_(0.25)*dL+AH1_(0.25)*fL+AH1_(0.25)*hL-eL; + nz=ASatH1(abs(nz)*APrxMedRcpH1(AMax3H1(AMax3H1(bL,dL,eL),fL,hL)-AMin3H1(AMin3H1(bL,dL,eL),fL,hL))); + nz=AH1_(-0.5)*nz+AH1_(1.0); + // Min and max of ring. + AH1 mn4R=min(AMin3H1(bR,dR,fR),hR); + AH1 mn4G=min(AMin3H1(bG,dG,fG),hG); + AH1 mn4B=min(AMin3H1(bB,dB,fB),hB); + AH1 mx4R=max(AMax3H1(bR,dR,fR),hR); + AH1 mx4G=max(AMax3H1(bG,dG,fG),hG); + AH1 mx4B=max(AMax3H1(bB,dB,fB),hB); + // Immediate constants for peak range. + AH2 peakC=AH2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AH1 hitMinR=min(mn4R,eR)*ARcpH1(AH1_(4.0)*mx4R); + AH1 hitMinG=min(mn4G,eG)*ARcpH1(AH1_(4.0)*mx4G); + AH1 hitMinB=min(mn4B,eB)*ARcpH1(AH1_(4.0)*mx4B); + AH1 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpH1(AH1_(4.0)*mn4R+peakC.y); + AH1 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpH1(AH1_(4.0)*mn4G+peakC.y); + AH1 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpH1(AH1_(4.0)*mn4B+peakC.y); + AH1 lobeR=max(-hitMinR,hitMaxR); + AH1 lobeG=max(-hitMinG,hitMaxG); + AH1 lobeB=max(-hitMinB,hitMaxB); + AH1 lobe=max(AH1_(-FSR_RCAS_LIMIT),min(AMax3H1(lobeR,lobeG,lobeB),AH1_(0.0)))*AH2_AU1(con.y).x; + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AH1 rcpL=APrxMedRcpH1(AH1_(4.0)*lobe+AH1_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PACKED 16-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_HX2) + // Input callback prototypes that need to be implemented by the calling shader + AH4 FsrRcasLoadHx2(ASW2 p); + void FsrRcasInputHx2(inout AH2 r,inout AH2 g,inout AH2 b); +//------------------------------------------------------------------------------------------------------------------------------ + // Can be used to convert from packed Structures of Arrays to Arrays of Structures for store. + void FsrRcasDepackHx2(out AH4 pix0,out AH4 pix1,AH2 pixR,AH2 pixG,AH2 pixB){ + #ifdef A_HLSL + // Invoke a slower path for DX only, since it won't allow uninitialized values. + pix0.a=pix1.a=0.0; + #endif + pix0.rgb=AH3(pixR.x,pixG.x,pixB.x); + pix1.rgb=AH3(pixR.y,pixG.y,pixB.y);} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasHx2( + // Output values are for 2 8x8 tiles in a 16x8 region. + // pix.x = left 8x8 tile + // pix.y = right 8x8 tile + // This enables later processing to easily be packed as well. + out AH2 pixR, + out AH2 pixG, + out AH2 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AH2 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // No scaling algorithm uses minimal 3x3 pixel neighborhood. + ASW2 sp0=ASW2(ip); + AH3 b0=FsrRcasLoadHx2(sp0+ASW2( 0,-1)).rgb; + AH3 d0=FsrRcasLoadHx2(sp0+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee0=FsrRcasLoadHx2(sp0); + AH3 e0=ee0.rgb;pixA.r=ee0.a; + #else + AH3 e0=FsrRcasLoadHx2(sp0).rgb; + #endif + AH3 f0=FsrRcasLoadHx2(sp0+ASW2( 1, 0)).rgb; + AH3 h0=FsrRcasLoadHx2(sp0+ASW2( 0, 1)).rgb; + ASW2 sp1=sp0+ASW2(8,0); + AH3 b1=FsrRcasLoadHx2(sp1+ASW2( 0,-1)).rgb; + AH3 d1=FsrRcasLoadHx2(sp1+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee1=FsrRcasLoadHx2(sp1); + AH3 e1=ee1.rgb;pixA.g=ee1.a; + #else + AH3 e1=FsrRcasLoadHx2(sp1).rgb; + #endif + AH3 f1=FsrRcasLoadHx2(sp1+ASW2( 1, 0)).rgb; + AH3 h1=FsrRcasLoadHx2(sp1+ASW2( 0, 1)).rgb; + // Arrays of Structures to Structures of Arrays conversion. + AH2 bR=AH2(b0.r,b1.r); + AH2 bG=AH2(b0.g,b1.g); + AH2 bB=AH2(b0.b,b1.b); + AH2 dR=AH2(d0.r,d1.r); + AH2 dG=AH2(d0.g,d1.g); + AH2 dB=AH2(d0.b,d1.b); + AH2 eR=AH2(e0.r,e1.r); + AH2 eG=AH2(e0.g,e1.g); + AH2 eB=AH2(e0.b,e1.b); + AH2 fR=AH2(f0.r,f1.r); + AH2 fG=AH2(f0.g,f1.g); + AH2 fB=AH2(f0.b,f1.b); + AH2 hR=AH2(h0.r,h1.r); + AH2 hG=AH2(h0.g,h1.g); + AH2 hB=AH2(h0.b,h1.b); + // Run optional input transform. + FsrRcasInputHx2(bR,bG,bB); + FsrRcasInputHx2(dR,dG,dB); + FsrRcasInputHx2(eR,eG,eB); + FsrRcasInputHx2(fR,fG,fB); + FsrRcasInputHx2(hR,hG,hB); + // Luma times 2. + AH2 bL=bB*AH2_(0.5)+(bR*AH2_(0.5)+bG); + AH2 dL=dB*AH2_(0.5)+(dR*AH2_(0.5)+dG); + AH2 eL=eB*AH2_(0.5)+(eR*AH2_(0.5)+eG); + AH2 fL=fB*AH2_(0.5)+(fR*AH2_(0.5)+fG); + AH2 hL=hB*AH2_(0.5)+(hR*AH2_(0.5)+hG); + // Noise detection. + AH2 nz=AH2_(0.25)*bL+AH2_(0.25)*dL+AH2_(0.25)*fL+AH2_(0.25)*hL-eL; + nz=ASatH2(abs(nz)*APrxMedRcpH2(AMax3H2(AMax3H2(bL,dL,eL),fL,hL)-AMin3H2(AMin3H2(bL,dL,eL),fL,hL))); + nz=AH2_(-0.5)*nz+AH2_(1.0); + // Min and max of ring. + AH2 mn4R=min(AMin3H2(bR,dR,fR),hR); + AH2 mn4G=min(AMin3H2(bG,dG,fG),hG); + AH2 mn4B=min(AMin3H2(bB,dB,fB),hB); + AH2 mx4R=max(AMax3H2(bR,dR,fR),hR); + AH2 mx4G=max(AMax3H2(bG,dG,fG),hG); + AH2 mx4B=max(AMax3H2(bB,dB,fB),hB); + // Immediate constants for peak range. + AH2 peakC=AH2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AH2 hitMinR=min(mn4R,eR)*ARcpH2(AH2_(4.0)*mx4R); + AH2 hitMinG=min(mn4G,eG)*ARcpH2(AH2_(4.0)*mx4G); + AH2 hitMinB=min(mn4B,eB)*ARcpH2(AH2_(4.0)*mx4B); + AH2 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpH2(AH2_(4.0)*mn4R+peakC.y); + AH2 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpH2(AH2_(4.0)*mn4G+peakC.y); + AH2 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpH2(AH2_(4.0)*mn4B+peakC.y); + AH2 lobeR=max(-hitMinR,hitMaxR); + AH2 lobeG=max(-hitMinG,hitMaxG); + AH2 lobeB=max(-hitMinB,hitMaxB); + AH2 lobe=max(AH2_(-FSR_RCAS_LIMIT),min(AMax3H2(lobeR,lobeG,lobeB),AH2_(0.0)))*AH2_(AH2_AU1(con.y).x); + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AH2 rcpL=APrxMedRcpH2(AH2_(4.0)*lobe+AH2_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [LFGA] LINEAR FILM GRAIN APPLICATOR +// +//------------------------------------------------------------------------------------------------------------------------------ +// Adding output-resolution film grain after scaling is a good way to mask both rendering and scaling artifacts. +// Suggest using tiled blue noise as film grain input, with peak noise frequency set for a specific look and feel. +// The 'Lfga*()' functions provide a convenient way to introduce grain. +// These functions limit grain based on distance to signal limits. +// This is done so that the grain is temporally energy preserving, and thus won't modify image tonality. +// Grain application should be done in a linear colorspace. +// The grain should be temporally changing, but have a temporal sum per pixel that adds to zero (non-biased). +//------------------------------------------------------------------------------------------------------------------------------ +// Usage, +// FsrLfga*( +// color, // In/out linear colorspace color {0 to 1} ranged. +// grain, // Per pixel grain texture value {-0.5 to 0.5} ranged, input is 3-channel to support colored grain. +// amount); // Amount of grain (0 to 1} ranged. +//------------------------------------------------------------------------------------------------------------------------------ +// Example if grain texture is monochrome: 'FsrLfgaF(color,AF3_(grain),amount)' +//============================================================================================================================== +#if defined(A_GPU) + // Maximum grain is the minimum distance to the signal limit. + void FsrLfgaF(inout AF3 c,AF3 t,AF1 a){c+=(t*AF3_(a))*min(AF3_(1.0)-c,c);} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + // Half precision version (slower). + void FsrLfgaH(inout AH3 c,AH3 t,AH1 a){c+=(t*AH3_(a))*min(AH3_(1.0)-c,c);} +//------------------------------------------------------------------------------------------------------------------------------ + // Packed half precision version (faster). + void FsrLfgaHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 tR,AH2 tG,AH2 tB,AH1 a){ + cR+=(tR*AH2_(a))*min(AH2_(1.0)-cR,cR);cG+=(tG*AH2_(a))*min(AH2_(1.0)-cG,cG);cB+=(tB*AH2_(a))*min(AH2_(1.0)-cB,cB);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [SRTM] SIMPLE REVERSIBLE TONE-MAPPER +// +//------------------------------------------------------------------------------------------------------------------------------ +// This provides a way to take linear HDR color {0 to FP16_MAX} and convert it into a temporary {0 to 1} ranged post-tonemapped linear. +// The tonemapper preserves RGB ratio, which helps maintain HDR color bleed during filtering. +//------------------------------------------------------------------------------------------------------------------------------ +// Reversible tonemapper usage, +// FsrSrtm*(color); // {0 to FP16_MAX} converted to {0 to 1}. +// FsrSrtmInv*(color); // {0 to 1} converted into {0 to 32768, output peak safe for FP16}. +//============================================================================================================================== +#if defined(A_GPU) + void FsrSrtmF(inout AF3 c){c*=AF3_(ARcpF1(AMax3F1(c.r,c.g,c.b)+AF1_(1.0)));} + // The extra max solves the c=1.0 case (which is a /0). + void FsrSrtmInvF(inout AF3 c){c*=AF3_(ARcpF1(max(AF1_(1.0/32768.0),AF1_(1.0)-AMax3F1(c.r,c.g,c.b))));} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + void FsrSrtmH(inout AH3 c){c*=AH3_(ARcpH1(AMax3H1(c.r,c.g,c.b)+AH1_(1.0)));} + void FsrSrtmInvH(inout AH3 c){c*=AH3_(ARcpH1(max(AH1_(1.0/32768.0),AH1_(1.0)-AMax3H1(c.r,c.g,c.b))));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrSrtmHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ + AH2 rcp=ARcpH2(AMax3H2(cR,cG,cB)+AH2_(1.0));cR*=rcp;cG*=rcp;cB*=rcp;} + void FsrSrtmInvHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ + AH2 rcp=ARcpH2(max(AH2_(1.0/32768.0),AH2_(1.0)-AMax3H2(cR,cG,cB)));cR*=rcp;cG*=rcp;cB*=rcp;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [TEPD] TEMPORAL ENERGY PRESERVING DITHER +// +//------------------------------------------------------------------------------------------------------------------------------ +// Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. +// Gamma 2.0 is used so that the conversion back to linear is just to square the color. +// The conversion comes in 8-bit and 10-bit modes, designed for output to 8-bit UNORM or 10:10:10:2 respectively. +// Given good non-biased temporal blue noise as dither input, +// the output dither will temporally conserve energy. +// This is done by choosing the linear nearest step point instead of perceptual nearest. +// See code below for details. +//------------------------------------------------------------------------------------------------------------------------------ +// DX SPEC RULES FOR FLOAT->UNORM 8-BIT CONVERSION +// =============================================== +// - Output is 'uint(floor(saturate(n)*255.0+0.5))'. +// - Thus rounding is to nearest. +// - NaN gets converted to zero. +// - INF is clamped to {0.0 to 1.0}. +//============================================================================================================================== +#if defined(A_GPU) + // Hand tuned integer position to dither value, with more values than simple checkerboard. + // Only 32-bit has enough precision for this compddation. + // Output is {0 to <1}. + AF1 FsrTepdDitF(AU2 p,AU1 f){ + AF1 x=AF1_(p.x+f); + AF1 y=AF1_(p.y); + // The 1.61803 golden ratio. + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + // Number designed to provide a good visual pattern. + AF1 b=AF1_(1.0/3.69); + x=x*a+(y*b); + return AFractF1(x);} +//------------------------------------------------------------------------------------------------------------------------------ + // This version is 8-bit gamma 2.0. + // The 'c' input is {0 to 1}. + // Output is {0 to 1} ready for image store. + void FsrTepdC8F(inout AF3 c,AF1 dit){ + AF3 n=sqrt(c); + n=floor(n*AF3_(255.0))*AF3_(1.0/255.0); + AF3 a=n*n; + AF3 b=n+AF3_(1.0/255.0);b=b*b; + // Ratio of 'a' to 'b' required to produce 'c'. + // APrxLoRcpF1() won't work here (at least for very high dynamic ranges). + // APrxMedRcpF1() is an IADD,FMA,MUL. + AF3 r=(c-b)*APrxMedRcpF3(a-b); + // Use the ratio as a cutoff to choose 'a' or 'b'. + // AGtZeroF1() is a MUL. + c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // This version is 10-bit gamma 2.0. + // The 'c' input is {0 to 1}. + // Output is {0 to 1} ready for image store. + void FsrTepdC10F(inout AF3 c,AF1 dit){ + AF3 n=sqrt(c); + n=floor(n*AF3_(1023.0))*AF3_(1.0/1023.0); + AF3 a=n*n; + AF3 b=n+AF3_(1.0/1023.0);b=b*b; + AF3 r=(c-b)*APrxMedRcpF3(a-b); + c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/1023.0));} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + AH1 FsrTepdDitH(AU2 p,AU1 f){ + AF1 x=AF1_(p.x+f); + AF1 y=AF1_(p.y); + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + AF1 b=AF1_(1.0/3.69); + x=x*a+(y*b); + return AH1(AFractF1(x));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC8H(inout AH3 c,AH1 dit){ + AH3 n=sqrt(c); + n=floor(n*AH3_(255.0))*AH3_(1.0/255.0); + AH3 a=n*n; + AH3 b=n+AH3_(1.0/255.0);b=b*b; + AH3 r=(c-b)*APrxMedRcpH3(a-b); + c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC10H(inout AH3 c,AH1 dit){ + AH3 n=sqrt(c); + n=floor(n*AH3_(1023.0))*AH3_(1.0/1023.0); + AH3 a=n*n; + AH3 b=n+AH3_(1.0/1023.0);b=b*b; + AH3 r=(c-b)*APrxMedRcpH3(a-b); + c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/1023.0));} +//============================================================================================================================== + // This computes dither for positions 'p' and 'p+{8,0}'. + AH2 FsrTepdDitHx2(AU2 p,AU1 f){ + AF2 x; + x.x=AF1_(p.x+f); + x.y=x.x+AF1_(8.0); + AF1 y=AF1_(p.y); + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + AF1 b=AF1_(1.0/3.69); + x=x*AF2_(a)+AF2_(y*b); + return AH2(AFractF2(x));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC8Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ + AH2 nR=sqrt(cR); + AH2 nG=sqrt(cG); + AH2 nB=sqrt(cB); + nR=floor(nR*AH2_(255.0))*AH2_(1.0/255.0); + nG=floor(nG*AH2_(255.0))*AH2_(1.0/255.0); + nB=floor(nB*AH2_(255.0))*AH2_(1.0/255.0); + AH2 aR=nR*nR; + AH2 aG=nG*nG; + AH2 aB=nB*nB; + AH2 bR=nR+AH2_(1.0/255.0);bR=bR*bR; + AH2 bG=nG+AH2_(1.0/255.0);bG=bG*bG; + AH2 bB=nB+AH2_(1.0/255.0);bB=bB*bB; + AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); + AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); + AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); + cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/255.0)); + cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/255.0)); + cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC10Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ + AH2 nR=sqrt(cR); + AH2 nG=sqrt(cG); + AH2 nB=sqrt(cB); + nR=floor(nR*AH2_(1023.0))*AH2_(1.0/1023.0); + nG=floor(nG*AH2_(1023.0))*AH2_(1.0/1023.0); + nB=floor(nB*AH2_(1023.0))*AH2_(1.0/1023.0); + AH2 aR=nR*nR; + AH2 aG=nG*nG; + AH2 aB=nB*nB; + AH2 bR=nR+AH2_(1.0/1023.0);bR=bR*bR; + AH2 bG=nG+AH2_(1.0/1023.0);bG=bG*bG; + AH2 bB=nB+AH2_(1.0/1023.0);bB=bB*bB; + AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); + AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); + AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); + cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/1023.0)); + cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/1023.0)); + cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/1023.0));} +#endif + + +void CurrFilter(AU2 pos) +{ + AF3 c; + FsrRcasF(c.r, c.g, c.b, pos, con0); + imageStore(imgOutput, ASU2(pos), AF4(c, 1)); +} + +void main() { + FsrRcasCon(con0, sharpening_data); + + AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u); + CurrFilter(gxy); + gxy.x += 8u; + CurrFilter(gxy); + gxy.y += 8u; + CurrFilter(gxy); + gxy.x -= 8u; + CurrFilter(gxy); +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv new file mode 100644 index 0000000000000000000000000000000000000000..b2e30e1fe82d1a17c9b837a184d99a88f1c112bb GIT binary patch literal 20472 zcmZvj37nNx`Nvj$;`DZGxsIRT++%V%U%E9@4fFcoH?KWjpOG>a3|Fm$l6sIeO3Cbm&!79rf9?s4wd1YiUaw zo0{p0PrAZ!=}o-$&1mCj`rB&wA1=jBkUBil*g7md(8f<1HGAdo1*7%_*WterePadJ zkr(|a_2nJqObad1@o1w|Ef9qU|-fcw~}uQaLa-PmGQR(&uMB~MhEX!Y)emk z)3hXkHEfMN;`@P{nwM4Lw*ha6uP+*mpVqi?k4dAPLG>Rta}1zgS>l28t4chG{>v3y zUksxkKdq_x|BD}j7V+DIBYr5j8edoJX#C8%ORK|I#~t1gS67TspViow>N|Jn^~J90 zGg?( zH+6+iuX3jXuPJ8GH!(r+5#Z|h-HM~Z6PLHPPHI}*HouiRSrL7X!oIYndOe*aZftE_ zLhsmTWc;j7yrwu8UR{UU;`~lpZE;z~ugdt<8NVju*Jk{>j9;Je8!~=l#&6E}ZSZ9+ z%UkEpug>)W_@Y^EP<8E|P%rVb;F#xn@P;+2DPDxnqq$zMWZG+R(pR+CGwludf>L`Y z)82(IDzy(Y?GyMMt_Jt6OBXO#g1A=9RUobh&nta;h)bVM#HCMf@ch!JkGS;dD=vNd zfftlM{l%rvKym3a7`&+T87eM)hKnmcwZ+ch4Qp0g?2_?aGrniW_s;m(j31cs@fn|( z@hKTUEaTHMJ}cu#W&D^0;!av^v9uGfDOSMe zm%b}IX|=`bPQ0c#4ZfiCUE4{kEza)5Yl`#Xi%Q=MI%&1V#hrLfaT&asH+--6D>`Yl zMSI4t$@ujd|5?Uw&G>B@zdhr3Wc<#Iugmy78NWB<_h76s#{Zo0r!xL5 zd`a_Y?w;y;K9}h)WPE+bU#iA)$5rEB&h%F^{#wT0sK#@bR^#8y^tUtqPR8Fie&vSx zKgjftGX6=%Yr0nAJL<26r|%QpGTuGon^fZ`ME$)oy?4ep&-hmI`Hd$|Ea$%sd{I*y zPe3y(*Z088e@MoMb@)%N`0teIBQrj#!+%1>f6q)GoALch|3*Ikz27Eg`k@)0*5O~R z=g3SyHsi;2_*d(hpXrM;-U46TGRKdNN9*55@W1keaH18Ixx(5O-mL|U*5)pW@XH};w@~IXTtf-P0b4nz5&!%Y}T;BK6_qcTO;dNQ~U>iU#|5# zo$}Wf|Lw$UiVxunSFXahd*yr4!o{Dzui8-|U>a1;8yOE_nnR)o^H<-q>i{K4l-kvJly;3(sU9Rry5Gr!t zd=05~_|#Nvn)P>|26t#xZhUR2`#p7I@U|s)z2)1Ke0a(IuIG5MKIwScVm#L;9Z&9f z>c+>~r19D!-nB{N<;K$$eelw+0VWss1$CnzRB5$bwMC_AYhae$>BZW&g!iN$MDsNW zz6rgWcKv&mSbGDJnf(gXz_@H?R+r=#2}zK8L~evEBw& z&Ns6>w?4*L%aNt7ZD+Xi@VkO9xpl9uur&t;Ut8f--TKa>efqt(P7eLjsy~=jf<17LU!b{W+Qarz$+bt^TVUrEK5v7y=@Yj1 zORn8GYnfeLjE?ocyu#NU5c*?P{qEpfJDFn5I&(VjesK3td-x2f`iu*HN`;TR%Q-&* zc3iJB*I4f7nKt+A>uHK8JDfe?Y<$eyQ+|Ob8c&c!3-le%;UdQ`W zgZuPeX+MC9tw%l?o+9O9DHupWv(G9NsUgFHz9jwjkA>uXy8`q8=WA}h-kGPG&+Kih~*3c7d zKD9NaZ4lbxwRqAS95AvvEYp``_k9_^W=4;0uYqSkE?NP_JU~Op~{oxVkSaSWM zjsc~v=5;8KItGHRLwnRQ2psc@IvTKPKdoGk!C-A^9Yf#|=U8(6qK=`ZuIBYGk2;2d ztwa0AysM0%dEa^;cn09h_maJd^}gAN<~_VIz4`U~OlkiFyEt;Vf4ynGdeO|`zHdr1 z$7b{~&tprwImBs>zHnbY3q_7T3Em>Xo+FrhEBeTN99aM0v%$u!rvAO?kEdx1y%B7l z(C2`)J!z@eE}Uu)K$*1RL!S9&kG2G+I{eSH3%1ol4jUKRW7 z2-0yfXtrOz6$e%h@=p4Kq}?klZhXSj9jLLYUU0k)3dXO%q0JsobG zb?Up87ImHpwodKp#-{T)yVSM!q!QS2~B0aOdEU7E$xL z;FyE&JLe}{`(-7!Mz!;3&cXZV>oniHw8xw;06UKNoIV%Qw8eY#MPTE!>*Kt>LDTLS z)+3L67lXA0|7OWgDPu2zYYY8bV8=hB-0N?H_0evA=W{77_Th4{`=I^QGUf`facYt0 zJ78;3bG_xU-lM>yX})%+#rxMDVAp$3de__ay^5Bu_thD{Cgay;{JM-^pYas?)bBL`u&G#d? z`SjOMT|eid7P)T%M{egO*Uve%)1tl~gZ*qVUO)ACX8Q@)`t@-i<*|=r!TZsC?L%`P z$I$Ofb07Dox0YM!e?hygk*Cd*G4yK=6;(Gc+XvQ8&?|pDT{R1>L*Rvb_Z)p0(eSL3f>xruVTbgUJ z9Zk)B*I#?&co3{D_(LUk@9zUYOw&iZ@z$U%{C@{FKHj^257rjXo__#4uSaOc%k?qd z{C}jG)0%tIKT5M^uO+p}^Cz%*+G*yI>l^x?!Rn9E)a8yD`r}~rD{1O-^?YAuV)HeX z7W+Fb!G|Y!dWjFCpHX7>@d%oGH;dlA^L}`OR$uaG=$}ov`uc>cznF0KR}!xNTEf*| zPq_LU30Hq7;p*=uT>Zm@TjwVk@4}`;{r0sP_dGn+dt}^m@l^M0JmsE`r`$90lzUE| z^8Ok3ygbzhXMAYJJv&eRJwH#m=jSQ+{5<8JpQqgO^OSpjo^sF6Q$9Z9o}Z_>=jSQ+ z{5<8JpQqgO^OSpjo^sF6Q||eB%8$?Zyo{faanI4yc+b&O?m2qOJxfpd>WrV3@wFNE zJUxx~JU!)}si)jC^^|+2p7JX)-k$MmGJbu=Jy%cjd9I#v&(%}zxq8YyS5LX;>M38B z@q03UZ^k`WPvbpTPx(U`_gp>IJy%b;=jtigzN9QpP=3PyIbt zPx)&Z_gp>IJy%b;=jtiY>{Y%(&<3sXi>@o~x(2=jtgRmGM0@J~rc?tEcgvtEb#^ z^^|+Ap7J9zer(2%%ed$2X}ss^Dfe7GXmGP4^z9!?ItEc%qS5Ntw8TVX0)i2Dr z=jy3`NyaZNxzA0b*xx5pEczAsy#>3=5LPjzm&EY==EP;+Wda! zbN63qKGUzKP3PP2)AWC%X}9^}cHjx88t=1=dW`pXa5~=0aBVNq9Pbr+Z86@fV8_!Q ze=yxA{uP^EpjD#(NK(j`smv+xs-f^EpjhjQ0`P@wCTyAA_~q ze5K>P3s?8KxSsw~n)~Oour+j}H$c1N`5Y(@|E^&D+iBrngH5~sJ}1hG|0{ywM5 z!+$fd*F;*+=Ge6B?{lm?{I>w>-%gA9Zi!91{yyi*!+$HV{%Jj1W7Dp`&%yHW-v;b8 zlh(5>HtqWRoGcIj?ZEoC)1saM*tF~4pI#pRgTVTy^)z79uD{RO^6(!5_L@rT*&dsA z{Rh*_!+#i9|8`o`vjaBm`VXa-hyRXX{nL7O!lqsS;q>yzH3IB4tL8JkJhYKuuTM3f z@8zNG2KHK1^I2c+ZzI0PjRH@gH=l9p5w|;7yK#;y4{Z;y{%VdZ4{bEqJZg?B4{a~7 z*SXrB^zzWgfW4;G_NJGIwh!3rSIzGq@|eTEVCP^y^zzWAg1vXt4x^Wcb~xDkL~R>SKz zoO;B~0BbkSapj>M0oGs5apj@S1e-_Aapj>M2_9T(v*_ia9S!z=GwvvQd1#*jdvB>7 zLobgxd=~5+%x9c>#C;B|-8jdUhjuJje>KOIhxU1}dDI+N9@=r>xHpdn&!+kL=Vw(T zxCg!V_}zI9*ci3QF&7-~3-iEfj`?tNXpbBVz{aRWj)mYZv{TEm7lHND9=0a11FEmVv#W zqwY4ae%iyf9PIdNj&%aPw#e}%aNMga!D$_<;MSo%>i9C)7`4dp6>x8w`B&4YIZlF` zLwn>n8ElMN+F>EMB-ww6Av;|#cUXpcJ11RJ9k zInDxm-72JTOby3Ymcr#)=vfgNAXu}-7c7CF8S9$sn} z(5H1=2)7RHQO8AKW7HzYH^AP%=D(Oe&GAjRIkZQPOTfmcMUHQQhti_%Z-e#I9=1!t zj<4oe=hJHwUqOF4E$;R2fE`zRjPYHt_gK{NJ+OY-&CyPu=C~4W4(*ZSDzMjf%it#Gw=2EY2F{f&8t1~-T-!; zBJYi0{j^8ko51E)i@2M?UiYV$?_WO#>!;nZFQeBM{yzb0i}Qq^g89d{grZOjzgxiW zfjNFgFAweKVAoddR(g48zW`g0+HLf5KVy7f|0TGA-h9TX>vuc7TIBc@*fmzWgI*rm zU0~Nq?M`}m5Tjk)>qMLQ&-9Pc!uN5o*Rb}O%M)N@V!S88`e`@EQ}nBe zi5ySE&7nQ6@n^ussYMOiVvOg(@tOY|SRUG6!1XlaUZ9tU_E&JfQd>_ik9GbV*mX9a zaq1EGB6u(@{)Xf4VE$G9zQZ_m$9tAuE#h7QcPX`(>E)sQ1Kgw3UZt0Z_ByzKsl7%o zk2?PewodaIrygel%Zy;{V*1+Fc%H|gb}{Rg~Bsl81v5ADC; zfu;5iy*%ptAJ{t0XPkP(y$c>ri#p!}^RH5;aq8Cj2EAIueF%(-#-D%qfWmFHZZpNj8l)eE@X`@*A6{vO_s7CE*Bd!GgG z5AO|+YiT>UIfv5pRX2~nn-8GH`_3S+?>pKfR|D9+jQ6g=aQ(EK!{6GqMUL&kJCZ|t z#0&*ngPJk^ey=UY*a7VQ7koIpPg4Jm@Tgy3b@TW;ep>&|aO>9|xkiB9X^-{# z9N1Xxj(se>wpg#vgIzD}G1eEr)~sgCG~HDivW*A}%c1#d%(zjgW|SX=0=V6Vw|e_sZUwb57Iye;%<5w{#X zj23xUfVGAGC9r!Jc~^oXufFQ$ZKGFvgsX{5-IKM-}hCJ;CQ=(;j~V`*pCk zxP~qOn^U{>olLJyd=Y(m{eJ^)t=eM^E(RMD`MwF(PrErTp-;b)d<$+4?Q!jY8*H4K zF_+S7i#56o>~}5g>KD>$iyAHmTSM?Gz&^`HP2Yj*qdk1S3r=Ic2lp8_V%p*QXpcIt z1e;4O@>~T@^L!sZILUK0Tp#U`=NhoN)FRIhz-gXq;XYSKJwJr&qdoFm2R4^lN~Av`5TOz-i1+;Xa#3 z%+KKZXpdTN0h>!L^4tne^ZXp{^LXUB4X%&&$ny)Zxzr-hFTrV^+u?p6h&*?|_0b-A zeg!s{TI9JC?B{Rz+y&Q9d-&W9PSH0nf*H63aJDv5_mj9jV(b#<*N%QYi z{TtMyX#U;lG4%f3sekABII%v@%I%-Z_|qAGCgJ9MHsJ@upD(%f`*$eM!5!CH^_8m~ zgzW|JlQcEs= edgeVert; + FxaaFloat subpixA = subpixNSWE * 2.0 + subpixNWSWNESE; +/*--------------------------------------------------------------------------*/ + if(!horzSpan) lumaN = lumaW; + if(!horzSpan) lumaS = lumaE; + if(horzSpan) lengthSign = fxaaQualityRcpFrame.y; + FxaaFloat subpixB = (subpixA * (1.0/12.0)) - lumaM; +/*--------------------------------------------------------------------------*/ + FxaaFloat gradientN = lumaN - lumaM; + FxaaFloat gradientS = lumaS - lumaM; + FxaaFloat lumaNN = lumaN + lumaM; + FxaaFloat lumaSS = lumaS + lumaM; + FxaaBool pairN = abs(gradientN) >= abs(gradientS); + FxaaFloat gradient = max(abs(gradientN), abs(gradientS)); + if(pairN) lengthSign = -lengthSign; + FxaaFloat subpixC = FxaaSat(abs(subpixB) * subpixRcpRange); +/*--------------------------------------------------------------------------*/ + FxaaFloat2 posB; + posB.x = posM.x; + posB.y = posM.y; + FxaaFloat2 offNP; + offNP.x = (!horzSpan) ? 0.0 : fxaaQualityRcpFrame.x; + offNP.y = ( horzSpan) ? 0.0 : fxaaQualityRcpFrame.y; + if(!horzSpan) posB.x += lengthSign * 0.5; + if( horzSpan) posB.y += lengthSign * 0.5; +/*--------------------------------------------------------------------------*/ + FxaaFloat2 posN; + posN.x = posB.x - offNP.x * FXAA_QUALITY_P0; + posN.y = posB.y - offNP.y * FXAA_QUALITY_P0; + FxaaFloat2 posP; + posP.x = posB.x + offNP.x * FXAA_QUALITY_P0; + posP.y = posB.y + offNP.y * FXAA_QUALITY_P0; + FxaaFloat subpixD = ((-2.0)*subpixC) + 3.0; + FxaaFloat lumaEndN = FxaaLuma(FxaaTexTop(tex, posN)); + FxaaFloat subpixE = subpixC * subpixC; + FxaaFloat lumaEndP = FxaaLuma(FxaaTexTop(tex, posP)); +/*--------------------------------------------------------------------------*/ + if(!pairN) lumaNN = lumaSS; + FxaaFloat gradientScaled = gradient * 1.0/4.0; + FxaaFloat lumaMM = lumaM - lumaNN * 0.5; + FxaaFloat subpixF = subpixD * subpixE; + FxaaBool lumaMLTZero = lumaMM < 0.0; +/*--------------------------------------------------------------------------*/ + lumaEndN -= lumaNN * 0.5; + lumaEndP -= lumaNN * 0.5; + FxaaBool doneN = abs(lumaEndN) >= gradientScaled; + FxaaBool doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P1; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P1; + FxaaBool doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P1; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P1; +/*--------------------------------------------------------------------------*/ + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P2; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P2; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P2; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P2; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY_PS > 3) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P3; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P3; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P3; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P3; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY_PS > 4) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P4; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P4; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P4; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P4; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY_PS > 5) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P5; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P5; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P5; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P5; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY_PS > 6) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P6; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P6; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P6; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P6; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY_PS > 7) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P7; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P7; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P7; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P7; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY_PS > 8) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P8; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P8; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P8; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P8; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY_PS > 9) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P9; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P9; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P9; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P9; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY_PS > 10) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P10; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P10; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P10; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P10; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY_PS > 11) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P11; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P11; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P11; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P11; +/*--------------------------------------------------------------------------*/ + #if (FXAA_QUALITY_PS > 12) + if(doneNP) { + if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P12; + if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P12; + doneNP = (!doneN) || (!doneP); + if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P12; + if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P12; +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } + #endif +/*--------------------------------------------------------------------------*/ + } +/*--------------------------------------------------------------------------*/ + FxaaFloat dstN = posM.x - posN.x; + FxaaFloat dstP = posP.x - posM.x; + if(!horzSpan) dstN = posM.y - posN.y; + if(!horzSpan) dstP = posP.y - posM.y; +/*--------------------------------------------------------------------------*/ + FxaaBool goodSpanN = (lumaEndN < 0.0) != lumaMLTZero; + FxaaFloat spanLength = (dstP + dstN); + FxaaBool goodSpanP = (lumaEndP < 0.0) != lumaMLTZero; + FxaaFloat spanLengthRcp = 1.0/spanLength; +/*--------------------------------------------------------------------------*/ + FxaaBool directionN = dstN < dstP; + FxaaFloat dst = min(dstN, dstP); + FxaaBool goodSpan = directionN ? goodSpanN : goodSpanP; + FxaaFloat subpixG = subpixF * subpixF; + FxaaFloat pixelOffset = (dst * (-spanLengthRcp)) + 0.5; + FxaaFloat subpixH = subpixG * fxaaQualitySubpix; +/*--------------------------------------------------------------------------*/ + FxaaFloat pixelOffsetGood = goodSpan ? pixelOffset : 0.0; + FxaaFloat pixelOffsetSubpix = max(pixelOffsetGood, subpixH); + if(!horzSpan) posM.x += pixelOffsetSubpix * lengthSign; + if( horzSpan) posM.y += pixelOffsetSubpix * lengthSign; + #if (FXAA_DISCARD == 1) + return FxaaTexTop(tex, posM); + #else + return FxaaFloat4(FxaaTexTop(tex, posM).xyz, lumaM); + #endif +} +/*==========================================================================*/ +#endif + +vec4 mainImage(vec2 fragCoord) +{ + vec2 rcpFrame = 1./invResolution_data.xy; + vec2 uv2 = fragCoord.xy / invResolution_data.xy; + + float fxaaQualitySubpix = 0.75; // [0..1], default 0.75 + float fxaaQualityEdgeThreshold = 0.166; // [0.125..0.33], default 0.166 + float fxaaQualityEdgeThresholdMin = 0.02;//0.0625; // ? + vec4 dummy4 = vec4(0.0,0.0,0.0,0.0); + float dummy1 = 0.0; + + vec4 col = FxaaPixelShader(uv2, dummy4, + inputImage, inputImage, inputImage, + rcpFrame, dummy4, dummy4, dummy4, + fxaaQualitySubpix, fxaaQualityEdgeThreshold, + fxaaQualityEdgeThresholdMin, + dummy1, dummy1, dummy1, dummy4); + + vec4 fragColor = vec4( col.xyz, 1. ); + + return fragColor; +} + +void main() +{ + ivec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4); + for(int i = 0; i < 4; i++) + { + for(int j = 0; j < 4; j++) + { + ivec2 texelCoord = ivec2(loc.x + i, loc.y + j); + vec4 outColor = mainImage(texelCoord + vec2(0.5)); + imageStore(imgOutput, texelCoord, outColor); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.spv b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.spv new file mode 100644 index 0000000000000000000000000000000000000000..b466bcb659d56d910d30aac5d781d27b8ff1313f GIT binary patch literal 25012 zcmaK!34m5r{r+Es0mKdWeL-&>gWNJTx6DjaE6d6@Tilo5=kvbz84tJrzuTP7^E~H!zRS7iz31gJIB=XhDXx9M-u)NRwZssFTsw2{{;_?+F{i~k`+ zXf&qde@yqR-tMlBiQT>YG-D5JtVTblV;*+H8mlj9x806gPwtpEcHZp0I@^0Ebj&z# zR)_fxYpn6V?Is`Jt=*8unw8x_bKAQ*=gdEN>g;`b+Gln!Hng$Ul0Gta;Osfh9oks? z|2w`d<4ZTzS#q3Q3m4nA;;*}8{7G|1i_jL3g-Q6{fp{C9H z|HI}0wugQ;sBEgW)em>DXZEa)UYEFOGvdE({wvmo|HGygYiMJmC2gqLsB`x2>@m*Z z#%7GWZ^zASpV86WLF#RdO`X@%K4VOGcaO!n2i^Saj|1qMd}n(0F=RR(_gg5(VG6ISY^xZsY;tP<^34XqQ&3#mEDV#roU(}7k1lKcCQq6 z(OxU;fX)2gF6>$w&+-9T!wFL(z_A{U*(BZ$8uA zAItX7AC1MJ#>jrYS98u5@EJYr(>gn5&1vn?w#GJv#;tA6+X=0?P8jKD_li%d?)@0} z?Do!{_!w9CXw@>PF&VA3AI3C&9Oj(vUOs6ppY8DO>C-3p88gONt#Ms8`2{WRaFbsK zZ~5CB-qyGZZjNJTP3xa?t#gV!eSQC&vCTO*zCWkm@2yMB8QfS5&l;7Vz5A-U2UMT^ zhv2Pz_G$WlG~?qZAJ);+jW($94BE8rSskr)8q|29;;H%Vj3a2jAqQ`n{}wgApvKRt z@#kv%r5fLkbAMWI^VT=x?b71aTC_C|g3svgp5~s)y*&i27e2nZ|ID+kaadv3s%3EF zh$ZZJ1~l(mTjMD7X`MYCQ|EMc>(q06x?1m?rmtfPU%A)a`f5H)_HEkq!a3zSx(=K^ zy|=@8JPZ46_Ji{*T+(J=%0M5|=vz{s^!|C-*0_wZo*dxKdYvQg10rEg}1&NbanGJqPYeyp>=gn1rKe0 z|DDlw#J*kKN40m2n{{0G)OPRmxVdzdsNABm^dpSOmaqaznyE0z&I|6I%#jAk^({Dgq9s42l za;BjTVpSVWO??D>X?n-iwO_8{`qF-RdTrI$#CipK?FZ1xD|~I@4`)C-=dV~9smWKO zPfjDKVC9M%#~idzi>7XK?*FSfW-Xi0>)Uy&!aqR20nKZRif;in*Kj_1%}Z`w{yE9Z z`J?DJpv7K3y5g?YK43N1%K74bY434u$=-2%ze-b2Ouy7PXBGaxkKT1Th2GrdnR^b{ zc+QP>e#O<3^JU;hW5J%`*ED%!QQ|FzyFW7id~1AE_$C7yjVl)}+&%mRI9I>Fg-t%H z-~OH^U(nBAZt_L__CpNT7&Y28x(MvLDy_%=``K|XS@RWO*FkO9iu-v|GnY~HSHiut zzp5H@esaC3IXK3&xG)r$28u=d_V z>-Zzw{iKPx{0U&$>gF!@v#jR$%N2Hxx!C^=EuX_z!TKLnjoZIljeD=HaaD}H7dAh8 zYr2cYBoO~I}$%Worg)|b(A)GeY!S2 zr_En(Ek27&ZbISO`%DgRewGXF=hbJi<9;J{?fyb@J^g&{jx*Qqua$lpHuAs2)m+yT zu^EN-53qXtyaLvbb;Zv=;p*}8FR)ta=ihMkiNtab{0FRW^*vY->sJ$7yZiJF*64M5 z*UJ7?a(a{gEt;3(Z&%~?<}(WY9k4#GBUT^$J+PYl;Z1DhZ-{C778{qL{^LoPhk(@*SH2WYU-}liuHNsZ!LC8>&nR3CLsO6cWx&?8fH{sY z3)eeU7i>Ja^Vb7AXDVD<$JM+y z-Y3`BUi(qluMY+`Qu792wb97A_iE11z4t!J-EaD{kG~O&mG@#pxLW){w}uS_;r0o(!BgWpkJQ} zt5nz+zDHE|yMgZwgI6!`8Wnb~-;a`?@B4z+F0kLZ)XmrTb-})$3-&!+@CF4QQQ!?L zY@Qp@T#JqAeKwlQ)--FBYvcQ_lKZ}^3B#S>wmn zxbL}2|GwWUx$n2aPXV7=aP#qrOW+=4rPUcnvromT7}_nlVBFRt-RYkXmiUtQzA z!z%sVR^y9m{Jt9ZomDyi@f!DCRXP4_jlWpqFW2}hHST+<(%;)PK48F--l^oia|-wKaafJF7u@;NYJ6tF zo$ouT(x307!kzCssgnCnD%}0%`>1g3eIHeF-$#XOe`~?DUtHt9gNnW5zJm&PeS8O1 za^F9d{Dm6#{Zqz`@B62c`~Ioq3u@f=PvyAppGxlgr*QM#j^}>KcPaQOj2~2R?GGuq zh2~i%_dCdkY1$u3 zw&uGVO&j&4X#SZ+Kb{|s#m^q#($Ai7{TxYU`q_)7jr!;&9?0C59v^$dOCP?pZ}}M7 z^f3;ujk-SEhx^gYXAHgLsrw^f{g>-I9`m#jyo*YPH>o>o5Ip1;P zc^*0zUk8Ef@eW2)kIf`-J>FzA^~C!q*m$1Z&Uf5+6X_j`uS3Dco6_uC`{!femfqrr z!Ob(iKMqbU+B&Z9L+Blg?@xg1zCVf9(p$cdfSXf%9|=z0+B&Z9!|9Kt#rIKQ?^T)i zRJeLX{TE@>DaPraCas3=i?^xy? z2iAA^@!<7nr&r&l=Yh3Rcm7;@wb-8kF6Vz5Zj4i_`6q(4QFs1)dbP~|40!#5pA0re z>R15IdT8soeomraK#QN#!1@k99jwjG+B^PPn%ZlOokj2EpKI06 zq^bFxS?ru4^q-^cO!LnQV#l>}d^r6VXscCxIr=Zswx%7*lgasCqG_W(jJ7N79Gdx^ zOYgXUwsYTnnYIi~-SM+4EwR1=FJpZbUdH+wTpRVoIv=dB3+NqBtgnNOrSAB7^lJG$ zd;?tn%zP6~JvJACcc*RB>|1;PTVVCRs-|y)ou_V`3+dJ3<2zvEls>)-SC7plVB<__ z_N_SI1FI*__rcCnH_pZMYObAYdpXU$b7qy-72wM%UHePHYO%i(Y_8!~fiI>d-Veaq zs5^fly;^d<7VN&+f)*dwfz|W5yB>TEO+7Y01RFPTegxJ|-8fg%t0m5j;Ch^!(9{#> zX0UP86XzE2#k9n^6|9}Qac-bji~a3j_haJT(HvW{zB|#>5HMa~(?#e+E0>adUW)UM*|+7qIth8(Q-ED_A}8{|0tnsK@5-VDD4n`~$3= zx^Z5nS4*6Kg5Bq3oPWX96X)MxtJJNUETm^T^x(u zn_%xp{JsU&R$afZ(W}M&9k8+D_g!%OIu^V4z^-xpYNV~Ye&427i~Uky*D-t`{5E*z z4}xo>?tI@_sEy`aSP8qq;5F&Xbsd7Hu6^^J1~~qg23uSBFt|D0iN8MjW#HPVJKuL8 zYO!Aqyb!zGv*qDx<=$KYZl0-OI9NM%efl0nO`m=ES`loV%PYPTSk3P}tI&J-y+?gz znwsBh#94<`!S#K-8k%}+R{5v4>m9J7(wr49_ky=)XYPi7#o7!CtJ{x z$3|%C#&n;^jhXrOvE3N#ean5{6s)!hEipC&?_7(qIhuODdw&4zTy^7)q*sfNEy3Q$ za=ku?rXHKE!1XvELQ_wit-;PyH;(V-)Z$}XaQ%6-9h!P|K!QNokGS9)WV1C$ryypPKZXd8~5xafC{50(x%kySGus$3&-WYnd z_}L#^#`_4IpQazjVmBUK#yg;}b1Z*$n^5VS;X_~A8Fw7LWBTi3>_D)&_!&Eh-pkLJ z`b3(VpD}TAI2hbUI~hCg*(9)<{?+AvzB1Q7b3Y0mNy~a20_LY#r(?030=^VIpTk4p z+Nv+hpW%%AF)%;P&${Egf-j?)$7HzU**6~tZ_QYFejJXb9-B{q52NMzB-bY6p9Jfp ze6}2crXHIk!TQj~JmlJBy^jJ9rR5Bm3RZLdr_+16{^}jH88p{ltiNgW*6I3c@0d1; ze>Av$PIjWHXTH3gub-^@F<@iF->1Osw5*9-o7B?Z)M&|XzG2{v+pxtHRosE$>7Yhez|tmpIq!y&jN7n z$0^|Cuii&fGkQ(_d6awq47hso|14O|`N{uGaORo6Ts!kmF7~ni zTxDN=PCt*P9-Fhl*^{Z^3ux-O2VVrM`8>!Ob`D%$j>XrPz-Q62zvbFx{9JJIPpmJa zsmJC#aQ0YizJjKne7*`+EAu%Yt}n;p>uX^1$$ph&vnD`Zo9iTGmIdUB)j4*VpGeXzH=~F1Wrvm!PR9pYMUy%6z^L z*Oz1Qbt%|xFU~SZ${}8=e>>mT` zGw;eD2diaIJqlKf{S)9a=byvX%sDnsg57JW;TK>vZOy?kwb(xeE`9zIu2%Yd8t%S` z&tHMnwAH6$YVr9j*f{x~_iHde>=NEb0AlwX*n5}n$O;uSlQ!$0IRvj|48rU9#?;frsf_OXV3i!+)gW> z&3}fgXFvP}toAZ3^Zp7xi&oD28(cl>{dcgM^E2-s;LNioxpsc8lAnEQd<9(A_)oZ+ zHU67EHLCxMre=-e#QYD~8h!p|tzU(!r^eU7YGsYD!_Bpv_Xb=&HNFW}bAIBz1qDaR?GaQz~%gba5dMk zjo!(R%D z`nohVeTenpcZ>DG<(a(!-2I(1djwn?b$PSfL){TZQ06R-5*Uo&!dlk)qKaF zdE?7k=6)1SJvANzRx4|q0?!$n8V^NNPmLb~t2saM4g+VNHOjTK#^h(88b1y$YdjpT zmhn%3%g@gz;c7XXj{rMI-FQB~)iVD`a5=vnuI3s}rOz6wA4OAh4aKQx8o0hsI?&X! zhSR}na_=Zwu79*w5nSDnqBU^V9_-Z9|JvnIKAu3_@CPmP}fmo;|5)vR$AeQH#n zNmH{%abk9Z>-%IjntEzH7OYm**aOcQn;Lu3)KlXeu$uD|Z!S3VtWmC=H6}m%)OZ{? zXY)L8YEnO*re;mz%$W}^_tpt;HP_`t`uI@)G)+w(Vtsfvp9C(?x6i=c-#On-hHInF zPyhM00ODBAw?44vo4WP-tW~q##I;ZEr+~|QdMaGa+)t<}bm^Gw)ouT6yMu8SXsCvc_uWnHcuT^E`05#$SP}ndkZRUgoL(RhpW4 zij(Kpz~$L|0lduf>u|L)&kNzsb1dtrW}b;*pBUc&muK@g;oJf;cM*6o138<& z1=m*HINlSr`29AxJex0u$FF0t`wqDD`(3!U>iTsJ)wJ&;r%S-){r?_Z&2_z$-ph4W z|2|F4brt9Na2dG%%)J~nz-sppKl2vCbH*nBE78<*Ze0adbAIOi0GxT&B-hS+ zlKkvb@8Pa_a{mKd8+CsAbAJiqSaSa(*xc34 z-RHTQxhJlDa{m*!T=zf2)y(}b^j_w!{xVI?+{OCvZ2l{_Je&UpFVDQc!_~?&?;mjI zIhHk6Gtb1ZPoA%U%QgNdT+KZHP48u%>i?ptnWs2;{s&y1&9B1CJYR#Wm3h7ncb;Qe zPc`#Q4Ew}*16-cXZ^F3+WX7Dr?k(_Q268sP4cAuPINlSr_5UXY)E>=cpUc=eJtsuL~~cuLoCi4L6|A8mg~PQ*#Z)sc8hbzE3tpQ_mW1 z1Xe58a3nluY}RmNH1)pfY~BQ{=KRFl6r6e1B-hS0On&yMaWimPZ56Du8}x- zi~*a+7PRELH=25E#)9+tjmdAFqu=CWjCTiY`#I;ZE`+>{c$HCRi{Uh{VKC9LD zr>U8{IC+f+XTKz`1JKlCGXY$$??5#5)C*j(u>+>*rwfH;|Y@GZKLOa;sNl@4R2zs^HPX(KE_%yIJC8rLsHtNnl zie4@DGr;Biqv6ia?|62CDY5zQQMGkkKhx2YAS_&Xl#9QEvh zd0=(F(;1@|tQMaqfQ=jeY4Ez#7oR7>wNZEee0nwQU5C$rtvmBi1{=e5^1JZ@ur})X zeT|dA>X~y2SS|ciuyJw^PXm*F^WQLQ>$th}(L0u0P6wCIhcn=6J|E7c_wxCm{#lxu z&j)d0p9R)$_~*cF1kZYW93>>(AvcpsD9Rd=aeX{LDKCoO#wG*Uo21a-h@2tmmt6ZPb(3`QZBV?`vr4spkT)n)5U7>)_0@ z9=UeblicjhZ8*ta2rmE3_YJu7^Jl(q!nIN7NN(<*1Npt?i{Rg)Id1cEe`u3jzKvf0 z-KLAt)DNnjkKY07$1B=*D@}jVE~zyAPi(aQy8_=sbH4VD>)U^4t6u&$dHmgSuT5w> zu_nvYZ%Xq%ZBC#6zRhLK8&dG=3huw{xv|D?uJKz7J{0@g3-0_o3-0(`1vjU~1$X@J zf;)b1!5zQ9#viEh2Mccehim+i8h@%rd7#Q7n-9_L4B>hXC4*m>&u^j@op zz0WsQ*n50Sg}uMGRoHubM}@twIj_z4$8i1W%X=y}SNGOEVCOpK{gtPV`@q)bnD<(q zc|QSXp7&jzv7Z)W?g4qm9xBG%4|2ym7k&n|R^z!hC=4uJnAoTe+^d4^X@sYn$0WEyF8 tex +#define SMAALoad(tex, pos, sample) tex.Load(pos, sample) +#if defined(SMAA_HLSL_4_1) +#define SMAAGather(tex, coord) tex.Gather(LinearSampler, coord, 0) +#endif +#endif +#if defined(SMAA_GLSL_3) || defined(SMAA_GLSL_4) +#define SMAATexture2D(tex) sampler2D tex +#define SMAATexturePass2D(tex) tex +#define SMAASampleLevelZero(tex, coord) textureLod(tex, coord, 0.0) +#define SMAASampleLevelZeroPoint(tex, coord) textureLod(tex, coord, 0.0) +#define SMAASampleLevelZeroOffset(tex, coord, offset) textureLodOffset(tex, coord, 0.0, offset) +#define SMAASample(tex, coord) texture(tex, coord) +#define SMAASamplePoint(tex, coord) texture(tex, coord) +#define SMAASampleOffset(tex, coord, offset) texture(tex, coord, offset) +#define SMAA_FLATTEN +#define SMAA_BRANCH +#define lerp(a, b, t) mix(a, b, t) +#define saturate(a) clamp(a, 0.0, 1.0) +#if defined(SMAA_GLSL_4) +#define mad(a, b, c) fma(a, b, c) +#define SMAAGather(tex, coord) textureGather(tex, coord) +#else +#define mad(a, b, c) (a * b + c) +#endif +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 +#define int2 ivec2 +#define int3 ivec3 +#define int4 ivec4 +#define bool2 bvec2 +#define bool3 bvec3 +#define bool4 bvec4 +#endif + +#if !defined(SMAA_HLSL_3) && !defined(SMAA_HLSL_4) && !defined(SMAA_HLSL_4_1) && !defined(SMAA_GLSL_3) && !defined(SMAA_GLSL_4) && !defined(SMAA_CUSTOM_SL) +#error you must define the shading language: SMAA_HLSL_*, SMAA_GLSL_* or SMAA_CUSTOM_SL +#endif + +//----------------------------------------------------------------------------- +// Misc functions + +/** + * Gathers current pixel, and the top-left neighbors. + */ +float3 SMAAGatherNeighbours(float2 texcoord, + float4 offset[3], + SMAATexture2D(tex)) { + #ifdef SMAAGather + return SMAAGather(tex, texcoord + SMAA_RT_METRICS.xy * float2(-0.5, -0.5)).grb; + #else + float P = SMAASamplePoint(tex, texcoord).r; + float Pleft = SMAASamplePoint(tex, offset[0].xy).r; + float Ptop = SMAASamplePoint(tex, offset[0].zw).r; + return float3(P, Pleft, Ptop); + #endif +} + +/** + * Adjusts the threshold by means of predication. + */ +float2 SMAACalculatePredicatedThreshold(float2 texcoord, + float4 offset[3], + SMAATexture2D(predicationTex)) { + float3 neighbours = SMAAGatherNeighbours(texcoord, offset, SMAATexturePass2D(predicationTex)); + float2 delta = abs(neighbours.xx - neighbours.yz); + float2 edges = step(SMAA_PREDICATION_THRESHOLD, delta); + return SMAA_PREDICATION_SCALE * SMAA_THRESHOLD * (1.0 - SMAA_PREDICATION_STRENGTH * edges); +} + +/** + * Conditional move: + */ +void SMAAMovc(bool2 cond, inout float2 variable, float2 value) { + SMAA_FLATTEN if (cond.x) variable.x = value.x; + SMAA_FLATTEN if (cond.y) variable.y = value.y; +} + +void SMAAMovc(bool4 cond, inout float4 variable, float4 value) { + SMAAMovc(cond.xy, variable.xy, value.xy); + SMAAMovc(cond.zw, variable.zw, value.zw); +} + + +#if SMAA_INCLUDE_VS +//----------------------------------------------------------------------------- +// Vertex Shaders + +/** + * Edge Detection Vertex Shader + */ +void SMAAEdgeDetectionVS(float2 texcoord, + out float4 offset[3]) { + offset[0] = mad(SMAA_RT_METRICS.xyxy, float4(-1.0, 0.0, 0.0, -1.0), texcoord.xyxy); + offset[1] = mad(SMAA_RT_METRICS.xyxy, float4( 1.0, 0.0, 0.0, 1.0), texcoord.xyxy); + offset[2] = mad(SMAA_RT_METRICS.xyxy, float4(-2.0, 0.0, 0.0, -2.0), texcoord.xyxy); +} + +/** + * Blend Weight Calculation Vertex Shader + */ +void SMAABlendingWeightCalculationVS(float2 texcoord, + out float2 pixcoord, + out float4 offset[3]) { + pixcoord = texcoord * SMAA_RT_METRICS.zw; + + // We will use these offsets for the searches later on (see @PSEUDO_GATHER4): + offset[0] = mad(SMAA_RT_METRICS.xyxy, float4(-0.25, -0.125, 1.25, -0.125), texcoord.xyxy); + offset[1] = mad(SMAA_RT_METRICS.xyxy, float4(-0.125, -0.25, -0.125, 1.25), texcoord.xyxy); + + // And these for the searches, they indicate the ends of the loops: + offset[2] = mad(SMAA_RT_METRICS.xxyy, + float4(-2.0, 2.0, -2.0, 2.0) * float(SMAA_MAX_SEARCH_STEPS), + float4(offset[0].xz, offset[1].yw)); +} + +/** + * Neighborhood Blending Vertex Shader + */ +void SMAANeighborhoodBlendingVS(float2 texcoord, + out float4 offset) { + offset = mad(SMAA_RT_METRICS.xyxy, float4( 1.0, 0.0, 0.0, 1.0), texcoord.xyxy); +} +#endif // SMAA_INCLUDE_VS + +#if SMAA_INCLUDE_PS +//----------------------------------------------------------------------------- +// Edge Detection Pixel Shaders (First Pass) + +/** + * Luma Edge Detection + * + * IMPORTANT NOTICE: luma edge detection requires gamma-corrected colors, and + * thus 'colorTex' should be a non-sRGB texture. + */ +float2 SMAALumaEdgeDetectionPS(float2 texcoord, + float4 offset[3], + SMAATexture2D(colorTex) + #if SMAA_PREDICATION + , SMAATexture2D(predicationTex) + #endif + ) { + // Calculate the threshold: + #if SMAA_PREDICATION + float2 threshold = SMAACalculatePredicatedThreshold(texcoord, offset, SMAATexturePass2D(predicationTex)); + #else + float2 threshold = float2(SMAA_THRESHOLD, SMAA_THRESHOLD); + #endif + + // Calculate lumas: + float3 weights = float3(0.2126, 0.7152, 0.0722); + float L = dot(SMAASamplePoint(colorTex, texcoord).rgb, weights); + + float Lleft = dot(SMAASamplePoint(colorTex, offset[0].xy).rgb, weights); + float Ltop = dot(SMAASamplePoint(colorTex, offset[0].zw).rgb, weights); + + // We do the usual threshold: + float4 delta; + delta.xy = abs(L - float2(Lleft, Ltop)); + float2 edges = step(threshold, delta.xy); + + // Then discard if there is no edge: + if (dot(edges, float2(1.0, 1.0)) == 0.0) + return float2(-2.0, -2.0); + + // Calculate right and bottom deltas: + float Lright = dot(SMAASamplePoint(colorTex, offset[1].xy).rgb, weights); + float Lbottom = dot(SMAASamplePoint(colorTex, offset[1].zw).rgb, weights); + delta.zw = abs(L - float2(Lright, Lbottom)); + + // Calculate the maximum delta in the direct neighborhood: + float2 maxDelta = max(delta.xy, delta.zw); + + // Calculate left-left and top-top deltas: + float Lleftleft = dot(SMAASamplePoint(colorTex, offset[2].xy).rgb, weights); + float Ltoptop = dot(SMAASamplePoint(colorTex, offset[2].zw).rgb, weights); + delta.zw = abs(float2(Lleft, Ltop) - float2(Lleftleft, Ltoptop)); + + // Calculate the final maximum delta: + maxDelta = max(maxDelta.xy, delta.zw); + float finalDelta = max(maxDelta.x, maxDelta.y); + + // Local contrast adaptation: + edges.xy *= step(finalDelta, SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR * delta.xy); + + return edges; +} + +/** + * Color Edge Detection + * + * IMPORTANT NOTICE: color edge detection requires gamma-corrected colors, and + * thus 'colorTex' should be a non-sRGB texture. + */ +float2 SMAAColorEdgeDetectionPS(float2 texcoord, + float4 offset[3], + SMAATexture2D(colorTex) + #if SMAA_PREDICATION + , SMAATexture2D(predicationTex) + #endif + ) { + // Calculate the threshold: + #if SMAA_PREDICATION + float2 threshold = SMAACalculatePredicatedThreshold(texcoord, offset, predicationTex); + #else + float2 threshold = float2(SMAA_THRESHOLD, SMAA_THRESHOLD); + #endif + + // Calculate color deltas: + float4 delta; + float3 C = SMAASamplePoint(colorTex, texcoord).rgb; + + float3 Cleft = SMAASamplePoint(colorTex, offset[0].xy).rgb; + float3 t = abs(C - Cleft); + delta.x = max(max(t.r, t.g), t.b); + + float3 Ctop = SMAASamplePoint(colorTex, offset[0].zw).rgb; + t = abs(C - Ctop); + delta.y = max(max(t.r, t.g), t.b); + + // We do the usual threshold: + float2 edges = step(threshold, delta.xy); + + // Then discard if there is no edge: + if (dot(edges, float2(1.0, 1.0)) == 0.0) + return float2(-2.0, -2.0); + + // Calculate right and bottom deltas: + float3 Cright = SMAASamplePoint(colorTex, offset[1].xy).rgb; + t = abs(C - Cright); + delta.z = max(max(t.r, t.g), t.b); + + float3 Cbottom = SMAASamplePoint(colorTex, offset[1].zw).rgb; + t = abs(C - Cbottom); + delta.w = max(max(t.r, t.g), t.b); + + // Calculate the maximum delta in the direct neighborhood: + float2 maxDelta = max(delta.xy, delta.zw); + + // Calculate left-left and top-top deltas: + float3 Cleftleft = SMAASamplePoint(colorTex, offset[2].xy).rgb; + t = abs(C - Cleftleft); + delta.z = max(max(t.r, t.g), t.b); + + float3 Ctoptop = SMAASamplePoint(colorTex, offset[2].zw).rgb; + t = abs(C - Ctoptop); + delta.w = max(max(t.r, t.g), t.b); + + // Calculate the final maximum delta: + maxDelta = max(maxDelta.xy, delta.zw); + float finalDelta = max(maxDelta.x, maxDelta.y); + + // Local contrast adaptation: + edges.xy *= step(finalDelta, SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR * delta.xy); + + return edges; +} + +/** + * Depth Edge Detection + */ +float2 SMAADepthEdgeDetectionPS(float2 texcoord, + float4 offset[3], + SMAATexture2D(depthTex)) { + float3 neighbours = SMAAGatherNeighbours(texcoord, offset, SMAATexturePass2D(depthTex)); + float2 delta = abs(neighbours.xx - float2(neighbours.y, neighbours.z)); + float2 edges = step(SMAA_DEPTH_THRESHOLD, delta); + + if (dot(edges, float2(1.0, 1.0)) == 0.0) + return float2(-2.0, -2.0); + + return edges; +} + +//----------------------------------------------------------------------------- +// Diagonal Search Functions + +#if !defined(SMAA_DISABLE_DIAG_DETECTION) + +/** + * Allows to decode two binary values from a bilinear-filtered access. + */ +float2 SMAADecodeDiagBilinearAccess(float2 e) { + // Bilinear access for fetching 'e' have a 0.25 offset, and we are + // interested in the R and G edges: + // + // +---G---+-------+ + // | x o R x | + // +-------+-------+ + // + // Then, if one of these edge is enabled: + // Red: (0.75 * X + 0.25 * 1) => 0.25 or 1.0 + // Green: (0.75 * 1 + 0.25 * X) => 0.75 or 1.0 + // + // This function will unpack the values (mad + mul + round): + // wolframalpha.com: round(x * abs(5 * x - 5 * 0.75)) plot 0 to 1 + e.r = e.r * abs(5.0 * e.r - 5.0 * 0.75); + return round(e); +} + +float4 SMAADecodeDiagBilinearAccess(float4 e) { + e.rb = e.rb * abs(5.0 * e.rb - 5.0 * 0.75); + return round(e); +} + +/** + * These functions allows to perform diagonal pattern searches. + */ +float2 SMAASearchDiag1(SMAATexture2D(edgesTex), float2 texcoord, float2 dir, out float2 e) { + float4 coord = float4(texcoord, -1.0, 1.0); + float3 t = float3(SMAA_RT_METRICS.xy, 1.0); + while (coord.z < float(SMAA_MAX_SEARCH_STEPS_DIAG - 1) && + coord.w > 0.9) { + coord.xyz = mad(t, float3(dir, 1.0), coord.xyz); + e = SMAASampleLevelZero(edgesTex, coord.xy).rg; + coord.w = dot(e, float2(0.5, 0.5)); + } + return coord.zw; +} + +float2 SMAASearchDiag2(SMAATexture2D(edgesTex), float2 texcoord, float2 dir, out float2 e) { + float4 coord = float4(texcoord, -1.0, 1.0); + coord.x += 0.25 * SMAA_RT_METRICS.x; // See @SearchDiag2Optimization + float3 t = float3(SMAA_RT_METRICS.xy, 1.0); + while (coord.z < float(SMAA_MAX_SEARCH_STEPS_DIAG - 1) && + coord.w > 0.9) { + coord.xyz = mad(t, float3(dir, 1.0), coord.xyz); + + // @SearchDiag2Optimization + // Fetch both edges at once using bilinear filtering: + e = SMAASampleLevelZero(edgesTex, coord.xy).rg; + e = SMAADecodeDiagBilinearAccess(e); + + // Non-optimized version: + // e.g = SMAASampleLevelZero(edgesTex, coord.xy).g; + // e.r = SMAASampleLevelZeroOffset(edgesTex, coord.xy, int2(1, 0)).r; + + coord.w = dot(e, float2(0.5, 0.5)); + } + return coord.zw; +} + +/** + * Similar to SMAAArea, this calculates the area corresponding to a certain + * diagonal distance and crossing edges 'e'. + */ +float2 SMAAAreaDiag(SMAATexture2D(areaTex), float2 dist, float2 e, float offset) { + float2 texcoord = mad(float2(SMAA_AREATEX_MAX_DISTANCE_DIAG, SMAA_AREATEX_MAX_DISTANCE_DIAG), e, dist); + + // We do a scale and bias for mapping to texel space: + texcoord = mad(SMAA_AREATEX_PIXEL_SIZE, texcoord, 0.5 * SMAA_AREATEX_PIXEL_SIZE); + + // Diagonal areas are on the second half of the texture: + texcoord.x += 0.5; + + // Move to proper place, according to the subpixel offset: + texcoord.y += SMAA_AREATEX_SUBTEX_SIZE * offset; + + // Do it! + return SMAA_AREATEX_SELECT(SMAASampleLevelZero(areaTex, texcoord)); +} + +/** + * This searches for diagonal patterns and returns the corresponding weights. + */ +float2 SMAACalculateDiagWeights(SMAATexture2D(edgesTex), SMAATexture2D(areaTex), float2 texcoord, float2 e, float4 subsampleIndices) { + float2 weights = float2(0.0, 0.0); + + // Search for the line ends: + float4 d; + float2 end; + if (e.r > 0.0) { + d.xz = SMAASearchDiag1(SMAATexturePass2D(edgesTex), texcoord, float2(-1.0, 1.0), end); + d.x += float(end.y > 0.9); + } else + d.xz = float2(0.0, 0.0); + d.yw = SMAASearchDiag1(SMAATexturePass2D(edgesTex), texcoord, float2(1.0, -1.0), end); + + SMAA_BRANCH + if (d.x + d.y > 2.0) { // d.x + d.y + 1 > 3 + // Fetch the crossing edges: + float4 coords = mad(float4(-d.x + 0.25, d.x, d.y, -d.y - 0.25), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + float4 c; + c.xy = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).rg; + c.zw = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).rg; + c.yxwz = SMAADecodeDiagBilinearAccess(c.xyzw); + + // Non-optimized version: + // float4 coords = mad(float4(-d.x, d.x, d.y, -d.y), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + // float4 c; + // c.x = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).g; + // c.y = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2( 0, 0)).r; + // c.z = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).g; + // c.w = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, -1)).r; + + // Merge crossing edges at each side into a single value: + float2 cc = mad(float2(2.0, 2.0), c.xz, c.yw); + + // Remove the crossing edge if we didn't found the end of the line: + SMAAMovc(bool2(step(0.9, d.zw)), cc, float2(0.0, 0.0)); + + // Fetch the areas for this line: + weights += SMAAAreaDiag(SMAATexturePass2D(areaTex), d.xy, cc, subsampleIndices.z); + } + + // Search for the line ends: + d.xz = SMAASearchDiag2(SMAATexturePass2D(edgesTex), texcoord, float2(-1.0, -1.0), end); + if (SMAASampleLevelZeroOffset(edgesTex, texcoord, int2(1, 0)).r > 0.0) { + d.yw = SMAASearchDiag2(SMAATexturePass2D(edgesTex), texcoord, float2(1.0, 1.0), end); + d.y += float(end.y > 0.9); + } else + d.yw = float2(0.0, 0.0); + + SMAA_BRANCH + if (d.x + d.y > 2.0) { // d.x + d.y + 1 > 3 + // Fetch the crossing edges: + float4 coords = mad(float4(-d.x, -d.x, d.y, d.y), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + float4 c; + c.x = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).g; + c.y = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2( 0, -1)).r; + c.zw = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).gr; + float2 cc = mad(float2(2.0, 2.0), c.xz, c.yw); + + // Remove the crossing edge if we didn't found the end of the line: + SMAAMovc(bool2(step(0.9, d.zw)), cc, float2(0.0, 0.0)); + + // Fetch the areas for this line: + weights += SMAAAreaDiag(SMAATexturePass2D(areaTex), d.xy, cc, subsampleIndices.w).gr; + } + + return weights; +} +#endif + +//----------------------------------------------------------------------------- +// Horizontal/Vertical Search Functions + +/** + * This allows to determine how much length should we add in the last step + * of the searches. It takes the bilinearly interpolated edge (see + * @PSEUDO_GATHER4), and adds 0, 1 or 2, depending on which edges and + * crossing edges are active. + */ +float SMAASearchLength(SMAATexture2D(searchTex), float2 e, float offset) { + // The texture is flipped vertically, with left and right cases taking half + // of the space horizontally: + float2 scale = SMAA_SEARCHTEX_SIZE * float2(0.5, -1.0); + float2 bias = SMAA_SEARCHTEX_SIZE * float2(offset, 1.0); + + // Scale and bias to access texel centers: + scale += float2(-1.0, 1.0); + bias += float2( 0.5, -0.5); + + // Convert from pixel coordinates to texcoords: + // (We use SMAA_SEARCHTEX_PACKED_SIZE because the texture is cropped) + scale *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; + bias *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; + + // Lookup the search texture: + return SMAA_SEARCHTEX_SELECT(SMAASampleLevelZero(searchTex, mad(scale, e, bias))); +} + +/** + * Horizontal/vertical search functions for the 2nd pass. + */ +float SMAASearchXLeft(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + /** + * @PSEUDO_GATHER4 + * This texcoord has been offset by (-0.25, -0.125) in the vertex shader to + * sample between edge, thus fetching four edges in a row. + * Sampling with different offsets in each direction allows to disambiguate + * which edges are active from the four fetched ones. + */ + float2 e = float2(0.0, 1.0); + while (texcoord.x > end && + e.g > 0.8281 && // Is there some edge not activated? + e.r == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(-float2(2.0, 0.0), SMAA_RT_METRICS.xy, texcoord); + } + + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.0), 3.25); + return mad(SMAA_RT_METRICS.x, offset, texcoord.x); + + // Non-optimized version: + // We correct the previous (-0.25, -0.125) offset we applied: + // texcoord.x += 0.25 * SMAA_RT_METRICS.x; + + // The searches are bias by 1, so adjust the coords accordingly: + // texcoord.x += SMAA_RT_METRICS.x; + + // Disambiguate the length added by the last step: + // texcoord.x += 2.0 * SMAA_RT_METRICS.x; // Undo last step + // texcoord.x -= SMAA_RT_METRICS.x * (255.0 / 127.0) * SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.0); + // return mad(SMAA_RT_METRICS.x, offset, texcoord.x); +} + +float SMAASearchXRight(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(0.0, 1.0); + while (texcoord.x < end && + e.g > 0.8281 && // Is there some edge not activated? + e.r == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(float2(2.0, 0.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.5), 3.25); + return mad(-SMAA_RT_METRICS.x, offset, texcoord.x); +} + +float SMAASearchYUp(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(1.0, 0.0); + while (texcoord.y > end && + e.r > 0.8281 && // Is there some edge not activated? + e.g == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(-float2(0.0, 2.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e.gr, 0.0), 3.25); + return mad(SMAA_RT_METRICS.y, offset, texcoord.y); +} + +float SMAASearchYDown(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(1.0, 0.0); + while (texcoord.y < end && + e.r > 0.8281 && // Is there some edge not activated? + e.g == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(float2(0.0, 2.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e.gr, 0.5), 3.25); + return mad(-SMAA_RT_METRICS.y, offset, texcoord.y); +} + +/** + * Ok, we have the distance and both crossing edges. So, what are the areas + * at each side of current edge? + */ +float2 SMAAArea(SMAATexture2D(areaTex), float2 dist, float e1, float e2, float offset) { + // Rounding prevents precision errors of bilinear filtering: + float2 texcoord = mad(float2(SMAA_AREATEX_MAX_DISTANCE, SMAA_AREATEX_MAX_DISTANCE), round(4.0 * float2(e1, e2)), dist); + + // We do a scale and bias for mapping to texel space: + texcoord = mad(SMAA_AREATEX_PIXEL_SIZE, texcoord, 0.5 * SMAA_AREATEX_PIXEL_SIZE); + + // Move to proper place, according to the subpixel offset: + texcoord.y = mad(SMAA_AREATEX_SUBTEX_SIZE, offset, texcoord.y); + + // Do it! + return SMAA_AREATEX_SELECT(SMAASampleLevelZero(areaTex, texcoord)); +} + +//----------------------------------------------------------------------------- +// Corner Detection Functions + +void SMAADetectHorizontalCornerPattern(SMAATexture2D(edgesTex), inout float2 weights, float4 texcoord, float2 d) { + #if !defined(SMAA_DISABLE_CORNER_DETECTION) + float2 leftRight = step(d.xy, d.yx); + float2 rounding = (1.0 - SMAA_CORNER_ROUNDING_NORM) * leftRight; + + rounding /= leftRight.x + leftRight.y; // Reduce blending for pixels in the center of a line. + + float2 factor = float2(1.0, 1.0); + factor.x -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(0, 1)).r; + factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(1, 1)).r; + factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(0, -2)).r; + factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(1, -2)).r; + + weights *= saturate(factor); + #endif +} + +void SMAADetectVerticalCornerPattern(SMAATexture2D(edgesTex), inout float2 weights, float4 texcoord, float2 d) { + #if !defined(SMAA_DISABLE_CORNER_DETECTION) + float2 leftRight = step(d.xy, d.yx); + float2 rounding = (1.0 - SMAA_CORNER_ROUNDING_NORM) * leftRight; + + rounding /= leftRight.x + leftRight.y; + + float2 factor = float2(1.0, 1.0); + factor.x -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2( 1, 0)).g; + factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2( 1, 1)).g; + factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(-2, 0)).g; + factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(-2, 1)).g; + + weights *= saturate(factor); + #endif +} + +//----------------------------------------------------------------------------- +// Blending Weight Calculation Pixel Shader (Second Pass) + +float4 SMAABlendingWeightCalculationPS(float2 texcoord, + float2 pixcoord, + float4 offset[3], + SMAATexture2D(edgesTex), + SMAATexture2D(areaTex), + SMAATexture2D(searchTex), + float4 subsampleIndices) { // Just pass zero for SMAA 1x, see @SUBSAMPLE_INDICES. + float4 weights = float4(0.0, 0.0, 0.0, 0.0); + + float2 e = SMAASample(edgesTex, texcoord).rg; + + SMAA_BRANCH + if (e.g > 0.0) { // Edge at north + #if !defined(SMAA_DISABLE_DIAG_DETECTION) + // Diagonals have both north and west edges, so searching for them in + // one of the boundaries is enough. + weights.rg = SMAACalculateDiagWeights(SMAATexturePass2D(edgesTex), SMAATexturePass2D(areaTex), texcoord, e, subsampleIndices); + + // We give priority to diagonals, so if we find a diagonal we skip + // horizontal/vertical processing. + SMAA_BRANCH + if (weights.r == -weights.g) { // weights.r + weights.g == 0.0 + #endif + + float2 d; + + // Find the distance to the left: + float3 coords; + coords.x = SMAASearchXLeft(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[0].xy, offset[2].x); + coords.y = offset[1].y; // offset[1].y = texcoord.y - 0.25 * SMAA_RT_METRICS.y (@CROSSING_OFFSET) + d.x = coords.x; + + // Now fetch the left crossing edges, two at a time using bilinear + // filtering. Sampling at -0.25 (see @CROSSING_OFFSET) enables to + // discern what value each edge has: + float e1 = SMAASampleLevelZero(edgesTex, coords.xy).r; + + // Find the distance to the right: + coords.z = SMAASearchXRight(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[0].zw, offset[2].y); + d.y = coords.z; + + // We want the distances to be in pixel units (doing this here allow to + // better interleave arithmetic and memory accesses): + d = abs(round(mad(SMAA_RT_METRICS.zz, d, -pixcoord.xx))); + + // SMAAArea below needs a sqrt, as the areas texture is compressed + // quadratically: + float2 sqrt_d = sqrt(d); + + // Fetch the right crossing edges: + float e2 = SMAASampleLevelZeroOffset(edgesTex, coords.zy, int2(1, 0)).r; + + // Ok, we know how this pattern looks like, now it is time for getting + // the actual area: + weights.rg = SMAAArea(SMAATexturePass2D(areaTex), sqrt_d, e1, e2, subsampleIndices.y); + + // Fix corners: + coords.y = texcoord.y; + SMAADetectHorizontalCornerPattern(SMAATexturePass2D(edgesTex), weights.rg, coords.xyzy, d); + + #if !defined(SMAA_DISABLE_DIAG_DETECTION) + } else + e.r = 0.0; // Skip vertical processing. + #endif + } + + SMAA_BRANCH + if (e.r > 0.0) { // Edge at west + float2 d; + + // Find the distance to the top: + float3 coords; + coords.y = SMAASearchYUp(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[1].xy, offset[2].z); + coords.x = offset[0].x; // offset[1].x = texcoord.x - 0.25 * SMAA_RT_METRICS.x; + d.x = coords.y; + + // Fetch the top crossing edges: + float e1 = SMAASampleLevelZero(edgesTex, coords.xy).g; + + // Find the distance to the bottom: + coords.z = SMAASearchYDown(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[1].zw, offset[2].w); + d.y = coords.z; + + // We want the distances to be in pixel units: + d = abs(round(mad(SMAA_RT_METRICS.ww, d, -pixcoord.yy))); + + // SMAAArea below needs a sqrt, as the areas texture is compressed + // quadratically: + float2 sqrt_d = sqrt(d); + + // Fetch the bottom crossing edges: + float e2 = SMAASampleLevelZeroOffset(edgesTex, coords.xz, int2(0, 1)).g; + + // Get the area for this direction: + weights.ba = SMAAArea(SMAATexturePass2D(areaTex), sqrt_d, e1, e2, subsampleIndices.x); + + // Fix corners: + coords.x = texcoord.x; + SMAADetectVerticalCornerPattern(SMAATexturePass2D(edgesTex), weights.ba, coords.xyxz, d); + } + + return weights; +} + +//----------------------------------------------------------------------------- +// Neighborhood Blending Pixel Shader (Third Pass) + +float4 SMAANeighborhoodBlendingPS(float2 texcoord, + float4 offset, + SMAATexture2D(colorTex), + SMAATexture2D(blendTex) + #if SMAA_REPROJECTION + , SMAATexture2D(velocityTex) + #endif + ) { + // Fetch the blending weights for current pixel: + float4 a; + a.x = SMAASample(blendTex, offset.xy).a; // Right + a.y = SMAASample(blendTex, offset.zw).g; // Top + a.wz = SMAASample(blendTex, texcoord).xz; // Bottom / Left + + // Is there any blending weight with a value greater than 0.0? + SMAA_BRANCH + if (dot(a, float4(1.0, 1.0, 1.0, 1.0)) < 1e-5) { + float4 color = SMAASampleLevelZero(colorTex, texcoord); + + #if SMAA_REPROJECTION + float2 velocity = SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, texcoord)); + + // Pack velocity into the alpha channel: + color.a = sqrt(5.0 * length(velocity)); + #endif + + return color; + } else { + bool h = max(a.x, a.z) > max(a.y, a.w); // max(horizontal) > max(vertical) + + // Calculate the blending offsets: + float4 blendingOffset = float4(0.0, a.y, 0.0, a.w); + float2 blendingWeight = a.yw; + SMAAMovc(bool4(h, h, h, h), blendingOffset, float4(a.x, 0.0, a.z, 0.0)); + SMAAMovc(bool2(h, h), blendingWeight, a.xz); + blendingWeight /= dot(blendingWeight, float2(1.0, 1.0)); + + // Calculate the texture coordinates: + float4 blendingCoord = mad(blendingOffset, float4(SMAA_RT_METRICS.xy, -SMAA_RT_METRICS.xy), texcoord.xyxy); + + // We exploit bilinear filtering to mix current pixel with the chosen + // neighbor: + float4 color = blendingWeight.x * SMAASampleLevelZero(colorTex, blendingCoord.xy); + color += blendingWeight.y * SMAASampleLevelZero(colorTex, blendingCoord.zw); + + #if SMAA_REPROJECTION + // Antialias velocity for proper reprojection in a later stage: + float2 velocity = blendingWeight.x * SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, blendingCoord.xy)); + velocity += blendingWeight.y * SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, blendingCoord.zw)); + + // Pack velocity into the alpha channel: + color.a = sqrt(5.0 * length(velocity)); + #endif + + return color; + } +} + +//----------------------------------------------------------------------------- +// Temporal Resolve Pixel Shader (Optional Pass) + +float4 SMAAResolvePS(float2 texcoord, + SMAATexture2D(currentColorTex), + SMAATexture2D(previousColorTex) + #if SMAA_REPROJECTION + , SMAATexture2D(velocityTex) + #endif + ) { + #if SMAA_REPROJECTION + // Velocity is assumed to be calculated for motion blur, so we need to + // inverse it for reprojection: + float2 velocity = -SMAA_DECODE_VELOCITY(SMAASamplePoint(velocityTex, texcoord).rg); + + // Fetch current pixel: + float4 current = SMAASamplePoint(currentColorTex, texcoord); + + // Reproject current coordinates and fetch previous pixel: + float4 previous = SMAASamplePoint(previousColorTex, texcoord + velocity); + + // Attenuate the previous pixel if the velocity is different: + float delta = abs(current.a * current.a - previous.a * previous.a) / 5.0; + float weight = 0.5 * saturate(1.0 - sqrt(delta) * SMAA_REPROJECTION_WEIGHT_SCALE); + + // Blend the pixels according to the calculated weight: + return lerp(current, previous, weight); + #else + // Just blend the pixels: + float4 current = SMAASamplePoint(currentColorTex, texcoord); + float4 previous = SMAASamplePoint(previousColorTex, texcoord); + return lerp(current, previous, 0.5); + #endif +} + +//----------------------------------------------------------------------------- +// Separate Multisamples Pixel Shader (Optional Pass) + +#ifdef SMAALoad +void SMAASeparatePS(float4 position, + float2 texcoord, + out float4 target0, + out float4 target1, + SMAATexture2DMS2(colorTexMS)) { + int2 pos = int2(position.xy); + target0 = SMAALoad(colorTexMS, pos, 0); + target1 = SMAALoad(colorTexMS, pos, 1); +} +#endif + +//----------------------------------------------------------------------------- +#endif // SMAA_INCLUDE_PS + +layout(rgba8, binding = 0, set = 3) uniform image2D imgOutput; + +layout(binding = 1, set = 2) uniform sampler2D inputImg; +layout(binding = 3, set = 2) uniform sampler2D samplerArea; +layout(binding = 4, set = 2) uniform sampler2D samplerSearch; +layout( binding = 2 ) uniform invResolution +{ + vec2 invResolution_data; +}; + +void main() { + ivec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4); + for(int i = 0; i < 4; i++) + { + for(int j = 0; j < 4; j++) + { + ivec2 texelCoord = ivec2(loc.x + i, loc.y + j); + vec2 coord = (texelCoord + vec2(0.5)) / invResolution_data; + vec2 pixCoord; + vec4 offset[3]; + + SMAABlendingWeightCalculationVS( coord, pixCoord, offset); + + vec4 oColor = SMAABlendingWeightCalculationPS(coord, pixCoord, offset, inputImg, samplerArea, samplerSearch, ivec4(0)); + + imageStore(imgOutput, texelCoord, oColor); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.spv b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.spv new file mode 100644 index 0000000000000000000000000000000000000000..8efa011f77f3c49ed762e7465ae685ebd01b8348 GIT binary patch literal 33728 zcmai-2b@*a`L(Yw17h#JjlCDFSOGymQBdq9IKqG^G6(}2D=4N|5-TP#i7kn-L}N78 zSYnDPc1_b5jnNca>>6YFp6AY4%$^hfzi(%D_FC(G-`)2<=UztW+h?8rjlK&t7HKTp zIH#iNXCr4Ra!+k426y*p=5*l_ky19zG|sXL?Vzc}TD&S?`V z;H~tf(U{#nV@mr`Q#-n~7W$+%b(Vr2t5RC$(5W5MCQg|)`Jj#|laKD&y?yG0SyS7) zrgTm_aNLGzX7;3Q4%_;Ootky8L|j+Li4!_IXDn!MWwhy2O0A`_3R>r+Ni#dTY;V!V znpJzlIwo{Z>=-tsee%#LQ>RSpXrD1;!i0{QGn|KqmVsft^t zh}*o18%GNhj<&t6Hk>(ds|8c%*l=l%X=CFiPVShwf5(Z@HUab=XWz!AXcMQ*X!db4 zW5|we8%pQ0r>%kiXIpNTdRtqe)!Q0~mbSJwhTO`I+?mq0hRo<_cT~L^nzU6lrk2JI z__gEXklZ7K;1j3JWHq|yct^_aI$IhB^{mwrjbU)x$ZlvZSN8^)*=(=(L{{_P+StuH zd(C4@V=!7zKUy0@;PtT%HICfc=-=3#a^|d~X0{(UeQL)jwkDecsX6vNDSPI5bjP&G zT}Lk%|AMi%Hbz)SbKM<%_jpHAX1t@oJ>y-Zu`j%*-h)SXOzP@2Osn=>?QI>s#@E`| z2d#Hot&K5g^)Ze$mLuwZIS5>|x1T5Pe;UNcHpdxPYjc0JdYcEJ)!RJK*k+rHG>!oG zw0Xz@(?6p@+dIUVT6>40)!RD^t=`_@##Zf302l2I>pWrF|Fmd(_&E zdq)}DY;Qp0Sg`GRE}9K_z*)HNALp{AF$tfZz1Y&23@^{b(bmUd^bVRL<`5cp47g{% zEZLX=x2<6vT^$p;Mt07aa#H8CuJ)?` zlz9lcU*3T`x7s%1IbPa!fXCJjp58vA{kW!YYvWjLy`HBnjkD2u)}wFZ0(ke__ibDR z?%wk)jf=rOb+t6+*Z8G1enXA>zEQ^CRO9!-o8#aHc8pKSt1-UP%eQai4Q&`5|3|%S zEsc+B{L>om)2DYoT5G(2jW1E-OV#+&HNIAjuU+Ho)c9`ja@_mLo8#tQ5Le^us>RK! z@e^wNq#8f9#!s*DFT-bYvh&n-9_IOkILDqjOgCy%wptC$B`C)IOoBlgryX_Sw&fd*L(UUh5Ut(pbNx_u1SK zp1BwZF6V8V8sDzQhr@gJSxaM&ntjh+yl-Pa_{3@-9tiG!R<$$^@8#FhII_lP!e<^o zqig)c?sGgFo@d)BHQ&=}d`^v@QRC;;`1v({QH@^(FUNd!&3;WU-nVfRd}1}`TfiCf zoxS{88u!%rWAJj!Pa0c2&;L~OeWu2rt?}n-{N);dt;XNz#rrlsf=}(7z^+@k`8ja% z)bS&xb{^F}b=0)kofDd$wnh!B#<*B(qxtUPeY-r^cMtdL>R|SM2QL}TPhQ@$8=y7k zvgy;Wv5B$MX7ATAvvcY!Kf|H-qfK>FFWdNu?OpBN`(d-nZaV|PJb$ZJw6`?2Ml0`@ zZS-023~Om@SL55)_)f;}-Z?dAZ)fwMFA&3BsdWA__AZutJ=M~^v( zKJ;(wSJU?%K5Wzhd=St!u4Ws_`$6}3`ZxBk*$x=J|9(R-j$l50*WeKd>gK*<$O&oM z{jOk9@M`3ds9C>t;HDAm4K=RtroZdAcGJ?s+u*S~-`Y#A-8Suyb6?u!+PV6gcGsb_ z%eANf&ZqYH%e9-V-?_CdliWF#o8``Q5LSNJg~|6Ig8RU zyPsk|54=#t9m9NbqcLZ!@@n=jm8&;xlg_gT8o@``Q>Dj^&9`4CYy6_ z_;;JUv4DTC$*Lyu=dhFzPF(}e`)t_FzJEtpLFw~ zsp}rNKI$ps`*73uioxNJH~I7?_pH1`F57y&X`geSXX`Dn^WfQW4c?}74b)Q~U(J%9 zUr_%5_@qgbc8h)WroG3%t(!AveuKtc1y?gCX7i@coL$4GHu)7p5_23}%^aWO!4E(5 z&`#kq;8!m3@{Wm_3D-wGKC{3NKQwwl%xw6TOB}Nx<^;Gt>WMiOyxyo~c1^wKz#XGG z+J#`}(ft_hdNjFu>be_^G}yWRBiMbCwGjUa?NgFE1KMFoO|u3XrB~&?SU#z zJ#mj#aiz9sOLuJcpY^X)Y29sh-;EofIleM`r~DV)JH%3ojWl=}{6@;T->e1|iI`wmxf-{DH` zJDe%=a{$~w$J&&qsq^hSotoKa>UKIL`N@8_l1<$l%)Uje>#!L|E2Cw94?bHerab53|0+|N1T`ujPhr?6TaFDma)%uc8sn)6$te$%Mfvv}RPCfn6)KkyGV70QI z0dV!PwB=b@1Z>^vyRt&Ahnm>6@I3y9HCr6)+F8CwpO&Otifq!pbY-`+Kklz(z}9gy zZTY^n99YeB^C3R+CCJug-D2By&C~7*mCe$2SE5{*Y_i=|D!XOcT@`E{X?HcSTH2Mb zNVYE96+5r)zct9tLDt7{tXb))e=WG$f3TtrPBjvJqHqE`5`43|3nkU;F0Wvjy4v$T_wi{kE+1 zFI90{!Hre7UC)_X{I>m53rBwHPd8QIKsM3t{tje|dxoJnRzS=yu z^27}Wr#;W0Jhq`=>oU&sC{LZkz}BU0cS^ZTjI)fxgy1f^tvtsEWHDdx2dK?bbUQY|I3>zS`B?yY6H6pQZlpk-fp?p4bPj<}=Xt zwW%4Ov*A9LdmgRNGWCrCmwRb7xV0fvo>LvVHSD_##>Ted_x%rOA6i{Yzvu>k(Vec9dt6tv~hn z96YDe<8v!$2-^ko#n{#Q zTvzD>DQ)8#vT?3q8|C$6=h*wxvB{0e*nR-6kL|`vE5~*dn!eiVWBVbRcC)GMXU6s; z^m1&sz}3vAGA3iYwbC7%ZQM*YE@S&K*snPbPh4E#Q_diLM_m8NcT4<`Pn;EW^j51^?h{%2q{vu(S7_pQ#y&%yVT zP0@Y}0^v_s+19mL8k=<`{ zW6C>X5%`*9)9U2h5o>_G3)Z5vzU?S~OI9!M(1$8LK978c&!cGSp78;ckC7iHtDjkE zPgMHjl#6l8>-Qwt@wJe(C;ln0?P^cEzXPXlj#aK-_WbWFyLCI}Kaj24tnCxVs+MP$ z`(|CTX>GFe(nh%s*?C!yGW+A{O3(gy2Cil{^&!Xi&tU6_@3UYvvq|5tFemo$FJ$e` zmG53`w+ zYD-_8i*CJ7<0G(hXX$6BPbfbnn|$A}GVPYx4-0`EulvFI^!)ZgQ@?|K+7I3WR`WaC z_nDu5lqTPgw6&7etXrJA`-81}IdbY=7)|}Is_sR=YJUG~A5)LzyR5qHFABCD`>nri z*tYt|$d1#t#kTVnJo9vOr;hVxUze~W?OwpFCH080hY&}as^ws8^Tc2lOX}Dt> z!#jk2%fQvlCJSb|dFN|i7C~*G8o+vu$y_Z5w!ZQ#FOO!N$$hDhT4GiJ8*?5xV^|R_ zZ5gAFT4GkJV#@Qsaus8LjnPNVv2K923fQxov9Aj69((tD;c9R-v&rIM*3Nla17f^B z*6;UtITLGwwVO>Aw!ayrwyfP+V6}_cGw#oI;A(4=Gp=>P_Q}0w9diBLQ=S3$howB{ z#C?-7tyjgTZ|lR=%=Wu4*?UgCyyvz-H0anZ1Jqm2{{?s;-tY+Kd)V(Lzy34zJFSvT{ z?$Kbi0c87_dMtBy?+vyc`>nri*tYr*vg5REvFq+#xewU6$vz(g_UvSS$HLW2(e|x0 z<;?kh5Ph|kclS8BW6RyWKUmFdvMBHF0}#~ggMG7JV=`9mw?1mlop<-)VDBUOGL%QayT{&rcYhA9W;R*m z?j8>@UZ48ieI%N8v&q6fd#`EB+8qT})8_ppAH;d~9dQD9O-l8|swH+J*x20r9dNa^ z$(fT$V8@etU#_3~(evZpvMkToP8r+!hPPp~?`O$fs4(F%2ACCj*ua9$RUD^}d z1y)P!iC})3u`>bMPar2&d*=4@U}MWW`6Rgd-0Ds~8LZYuPTVQr#96OgKl|jpej3<$ z$n)TI_^D*|)G-Ha9iETG&4sI{?K8w=;}dr#IB~Wu*UvgU5A(pTQ|9Na%0GMl3vhkZ z>wEr-Xxhyt3*QOU%e^tIX?0F}KKtxe;K3Dk?S~Y2XocG-cPG0~hEq2Gtc>z(+R6KY zJ{J_+x-P2mi)(y-jbB>hH`MrzHGWgU+i35;8h@(BUn#i#d!yhB!#^sx_K$1)(;D}8 z%rk!dTMO>l>R)i}{*HO+@9&tG+}|-T`C2vZ@0gc%f5$w$jr#o^^OE~J=Ha&Q@0o`? zUVqm-T)V$(UUGleyyX6_dCC1<^Ki%O@0!cq=kASj$T>Un;D1+2C@ z+0S+7Q<{7}Yx^qsdt~>aIA`V(@DTiL*F7c|yT2|Z5325|uYvs@z`d`ZcK2NB`Fh2T zb??g4|8IcZ-{mv)GPre`jL}CeG2a9mQ$8QR1viGDW{f^+iTO6zw#$3wa=3ct=?bvg z0P?kbF7yt&60Gifh4YvCt^%juj#J;O(bVTwcjkA%YSxjs?}8KO*yQ^8+;Ux91J@${ z*MQ@nccAa1xd%+vp^sW(t_9cU=Q=cF_-V%IqgJ+S`&qLefQMAR-WPJ)@~*g^oVEWU zIBTz;cJGhWb92Rwb^hh)=a0blvwSzY1#X=tWAsr=%&lN!d=JQc{1|QwKg}3@)Dm+W z*qHK++zwZd&mEOdxj*hiQ=eO{=Urgq)U#)PA|~HW_H5ltY4U7oyN9gix{A}@{os0^ zeu}1kdbOV(0IQ|_pMljjC#RmDgX{JD0!=+WzpQ-9u|0^U9-m)*hR?6j)YG5efYtJy z@NdCt_9ve?9|GG?`(PXTil(o&p3i9Af6nt5ns&3PDw<{X z+~bHtu;tH&o`9>FO;yn>^IiRui0Z!kI|kb^&OPY!PrLPRfcf2f+P{bElh2WV z0IQiz76(^<2K5ZYczt}w^GWB6&t=%bePdKGMp^}RwV_p^#?`5Jg#O7-}^4%R>W z>EB>AeOvKuA^UvxJ51};SD(bc1@4aT{w(%3T+M894&(by&G%innwg*GT<0DCy-IV> zz6rKo{q4gWl-g78```=6S%(k6YUZqA{tV+kXalOe7$b8XKZNts{EVsH_}dx1C#g}?FoX-^#sVN=W4=sEk7dBy;OF_(PrL8G zsb|@W8+#G;%QNQXz|LcNzbubtohD=SQA^AUU}MTNyCRw~$jumi)a--z=Stw*FV4lv z@FA4BBUgdzqpp2*D)J7=^L|xyeN1@{uLf7w*XQsWmEGsCw$;f4$mngPo)D4&DIGI!(stqn4Nr!N!#L+D2%` zAU9+5QA^CmVAr&K&ToRIo_X37tmgU7JMLz1_4+&R=4k1+tWcyc*VsbhPv z?YiGHz8%oi<1?u8Dd%)YH1)aF9P9)(PCfIyGg$p@>UTfx3O2bPwe3Pyb1uYbZ!oyt zry*$Sc@G<^znS*^e7HNf9y1J0Ju$<=MZq&pF`#3^ye_Jn%PwL=Lj_8^+|sY2d6)u1M6c-f5yY@k7vOC z$n{Hqjs%zeX_u4JpQFHPW`3xlXMQFi7_U$I<6WQFiRk*69fv9X>Oiw!<+EoJ+!&L6 z)kiHclflN6_r}q1W31m8ebgME>wOH^nDpsbu$tLq5#Onh()T#Hn%Vjtd*0usRa)NP ztXF^gFojZk>g@!3Uekx^a5XbO&3g0xc05A;{cQ%C?fYj0+Ktcq+f1=GX8C;I-6u6q%lyx}?t}S(*22Q_D z1tnV*ru1VTTKR5w7F^93 z+tQ}yb7)se?|jR|e*vtI-!Y5x9kYGjv)K~&GUfKv##nxjO8M*_0Fx{b1w#~ zUC8*-rds?j1zSh_zXn#DPfq=6@&6{+`qQ6pfz>V}r+&5ge;aK5@xL6b7JqGO`g_-3 z0d{^9e3pR`tj3zU(Mb7Jp}D0_kz6K z3;TAPn*Z%>_QKw1!TVI$eKDrO?u)Tx_r-pc*$3BBclL!oj}+Ya#|p0fiHbYN#$HEu zKVMI2-Io4&_75m;B%8F~RM{(VEdi-saxRssQW(kV@i|n zQ`&ANtJxQE+PV#Ff6Mo`+u`b|`%bXh9c25QdMxwj+joI&$8qRy8}{QTl<9{)cazoZ zhdBMX2W&seG2aVUzm>D%cL(=@)%*-&KT?ln`f)$lcI=z}w&5J8-#~W$ZCmVIx~G2% zcAsR=J^*+AvnPKB*GHY7X8XDKevY8sY;tdB4`|D~-mk#k6Xkb%zpk|MUGFz&`fBTW z*X#a{;kRho%_iGQpC3ZA&pBrg!*k9a1RJZ5ebZ07bLd`lJ}l+gug+8IejM!D80)^5 zi{10Ch2KxOC!d6SAA8R9({6vX{SKV|JPOwLDYEr=wzOxieh;qC)gLOYoU1>g>8mYs zmAm3kXxdGV!#+F>x2^2CXTbGy_GdKp^!r(G`fZ)op^x{Ew(|ZtsM#9t>6E#1#ua$~ z3VVMXP+{+m1Iezz!IZgwo~Q2e{#l3@qj2N<6x=#mD(-w5oBQXlVC%N@{`ni_i)54Q z{ZeJO%)I;^Z2!u$_zyJo%;i79_B+2{^DnqQ>Yn>oC{3O{Z7-A6?29;Uy$ZI!<^A&- zTs`;C>tMCqKdHwu=lbZa30juTy zNj;Y7$Gc$Lv2Xg@hI63)0@?YuZLxFd{qr8!eUkI`KHT}w{qq4_A9a3u-9P_9&~7%l zSF;DS}h^|6eKWw~FSr_{Y2*tJPNwg;=3 zZNp@}o(XMY&qMjHGzjipF1U3ZQ*qbD*xXHffUVooyJ;llD6+}DuxDkr z%)IOcwtwY09gU{$esM1M2HWqvEA0c;Y{# zQ%8d9XR5u@$}@Eon!eg{rgA4tK+|qE*;e{I5zRj5Zt8$*_dAjCU}N>MZ~AF>|G5{P z4@-IWtMin)j|RIo>BkhXn%Oo?*6W$jCiXm(?@`CXy_=3C>!&?ud@4A7^={HPca!H% zd*<;taD5)9Ra!ZZooM=M%RJ_8nvSO3HuD~JJY3x|d%k9XZ9DsOCfIo^_h%QHdd4ve zoN-v6b?D<=r7d^WBGhv%+0;SK{WPh<&HJUo-YrKLcuIkf0lRinDRXa~NITwJ8OJGb zB)4}Q|lk?wB&H+2N=A41EH|ByTqi5fpQTe&=P68XJk9)$t zX;18#V70`~184qb0<@n%nON-^$5~*+>aW>OU1=9r~n>FN0Hu zZOVRJ^oKM~Eh44#i{K|@FJQu*NC;Y;S=iFZeS2NpxQ+`+F;!4{R&3g28 z4()@s)OjgbEqm>2V72*V{q3V#*5vD8{mbXmH{j~=xvcUj*Z!Mm>T~&?&-45(uyN|? z%eTR{m$Ps=SUnUG`^1E=? zF?0MqxSHAIJm=3fzmI0TKK9M$;5FdfYuAGHG3EFFuY=nMZRzLrVEbP7^9Hzje$Vg+ zU^U|tcOy7)j$f`{>^FfOf4L4ngsW%#H-pRZ{|H`={}#BK+4DfLVEjLZ7_U#pe=9iS zzYVO9DdWE#ZXdLzpLc*W4*TW)xD!o1C*b<}--D)}@!tzB z$A2HZ9RK}rHM6N)|DT~5uTRGR0663SIanW4#{UbrebAPE{u1ms%5{1WuAcG#3an;) z;(iTIoa2}47yEC(_4WTPntI0n5V#!w!|-zakHFQ;_4R)Y&3Jt>{zt(X|KnhN%JDw| zw-4IV&nLkdN7nx-H1&-CcVIQ+6Zd;?;vBzRKkKkRf2i!aPyPt^jGCkU3C;HxbDn!o zqq)wTRCO4qU+R4Z?7g2qPx~`m%{cp^O|ASpES$R&$)@sI(gpXq;Mx%qFVy&J6;Gf40=J&<=PI6i?s>SH*|D4QEct7tZHZ<*`Z`Ygpe=R& z4Xl=D$%|mM`DFd=qgvMQC9wYGz4v#xdVKy-`IKklpJ?iHt7pl-z{aVkFE4{_FYEjY zSU+=~C9i_@)0Y0JiJialS@JsE`pUlk8?J7fK1<#JyPk5NC2zuA$IS6ta5b~ZdCs%s zZ8YQc$ys_4hn%H%(DgAUcgeeO`=BlJ^&Z&1m;HPnuAZ~>0a(rW#Qg`HIL9y7FZK_? zj=x-o|H9QX{*S=r_&MIOAtZ8~T*vUkJ@UXiGo)fHMyJ z<+G$OntI0H0#-9VajoFQIexi*vG)Vl*S|lSdd9ynxE%igcsc$>;A&=5x&Dix8Lv;q z@827m@h=Y6ryTzhaQmPw{ah04ILdWe3QaxZUmC1teBzb?C(iN9^^1L3aDDxkLsQTA zmj{>QUjbf@e?_>OxxW4@qZzMH#_!*en(?m!)~6i*s&M(7$4(dOf8&a-44H2dtc#5#=AFZHeq_TJC4WIec= zarQ%-TKO#TZaIT&I*FX;$H@ggrNS#vo?77zDNif#=>?us;Wo;-WS=)@Qu@46Tc19Z z&l~?8k&^pwiIm)bOC;R*?F#-je0afq_U%#d?cn}fB8k`TzaMp!HxId6^Xy&J-^`EFDkg>zpCKcuP(UlUsG`H{<|Wn zU;8Zu*Y3YBQrhn+xc0{iZv2x4*Y3YDQpP`1Ra=teMtCjcS=5Y0V zuet?T&HCS`pId=VeqX6=OR}10L!AC@4Yt3I&2u1+ZCkMIYV%CUwRwiO2hXkIJRkBw zJh8lQb^s5fbY48C)@!_e8Pg!Jb2$%t?#UhD#+qzXAGO5n1UANbNX*V~V={O8s3m3> zaN4o&K2vu^Q;*MXl}|apgVEIUo!$_zTE0IX3br0~=X)5X$@$i{JNYuQ^DVZmHtZw7 z-f5SUGnXUL)Uz)}fz`~WjBO9Nwjunsk>_A9uyx%=o=493Mx$$U4!w)y{+^^`-y7`j zp}975$kwS(=3*bP_uD*d#>}qeud(RbQqR6%>(R%0S=pCSk0XG1?%#z*OvKg2WJh-`8*0;n=waH%Cm+Ozz5Nftf6)4 zlX;s6b{_n_YUAa`#NJWZGxw9w)Z;T5?A&MlxiiSSUrF~LQzq-I`iJ1kqo;AtZjG0NPZjAf(1aP@;PlT8I_VaKxvvXp7 z`K)ykn(_KLR`2`rv)0Mz`j~9ncLlZd_f)X`(RK=@T$^{p>0tM|w$mu(vCRcrm$o^S z^6Zr}<;0i!cOII${%26i^L#i9e5~z}?SpMur+%sP3*hz1^YF>J{36^~llAJOmY6Sr zjj*L(`Ec^=CcC$CX3RW|l()M|9 zZN_
!S;{C6)Gg4N6>lRyg8}{#j^Q^OO*ZR9? z>RIdWfz`~WjQeW1w%qgI2j`ymUQS=HMc3y1UPCF*J%1he2z+zTTcf8s3ejp0FglxGd^2T#B^YiOPNWZr%Xb{_JqmKziM z1BE^9{R~Y#K0gOL_t^(>eezuX1-SmK{$-_=&*}%!^wm~>R{si3yV?0M+aK?tU!&Q6 z_;0}Fv--DiHM8qv&a?UOm@E1jaDk85F{)YGrW z!D@+l0&G1#tF;;P7^S)~?%Sup<-YwLyxh0HhpU-Q?w>rX|A1z^K6zGO!^CCp{t;at z`(WEog4NRBr@{6|+n*@q+Pvfb40f+;dxlaT+h4%erR`ZtdG^Zl;JL)lBgg-*XzKbu zM=8&<`UP;F)%L-*tW&?#`8V+TWcP5!_9EO^llAJOmYA2o#(0hr^LMy0nGb!`67vtR z&+2*P^!=Y`>hbv(*!7uLwJX<0fA8;Cz_y#S|0-C`T%P@x;o6LQi@4Xp)|+Sbzrku| zlYKT>hvR(>T_59J`!~VvJ#BAL%JZy#3+$h-xX*0YSbY-vHdsAld tex +#define SMAALoad(tex, pos, sample) tex.Load(pos, sample) +#if defined(SMAA_HLSL_4_1) +#define SMAAGather(tex, coord) tex.Gather(LinearSampler, coord, 0) +#endif +#endif +#if defined(SMAA_GLSL_3) || defined(SMAA_GLSL_4) +#define SMAATexture2D(tex) sampler2D tex +#define SMAATexturePass2D(tex) tex +#define SMAASampleLevelZero(tex, coord) textureLod(tex, coord, 0.0) +#define SMAASampleLevelZeroPoint(tex, coord) textureLod(tex, coord, 0.0) +#define SMAASampleLevelZeroOffset(tex, coord, offset) textureLodOffset(tex, coord, 0.0, offset) +#define SMAASample(tex, coord) texture(tex, coord) +#define SMAASamplePoint(tex, coord) texture(tex, coord) +#define SMAASampleOffset(tex, coord, offset) texture(tex, coord, offset) +#define SMAA_FLATTEN +#define SMAA_BRANCH +#define lerp(a, b, t) mix(a, b, t) +#define saturate(a) clamp(a, 0.0, 1.0) +#if defined(SMAA_GLSL_4) +#define mad(a, b, c) fma(a, b, c) +#define SMAAGather(tex, coord) textureGather(tex, coord) +#else +#define mad(a, b, c) (a * b + c) +#endif +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 +#define int2 ivec2 +#define int3 ivec3 +#define int4 ivec4 +#define bool2 bvec2 +#define bool3 bvec3 +#define bool4 bvec4 +#endif + +#if !defined(SMAA_HLSL_3) && !defined(SMAA_HLSL_4) && !defined(SMAA_HLSL_4_1) && !defined(SMAA_GLSL_3) && !defined(SMAA_GLSL_4) && !defined(SMAA_CUSTOM_SL) +#error you must define the shading language: SMAA_HLSL_*, SMAA_GLSL_* or SMAA_CUSTOM_SL +#endif + +//----------------------------------------------------------------------------- +// Misc functions + +/** + * Gathers current pixel, and the top-left neighbors. + */ +float3 SMAAGatherNeighbours(float2 texcoord, + float4 offset[3], + SMAATexture2D(tex)) { + #ifdef SMAAGather + return SMAAGather(tex, texcoord + SMAA_RT_METRICS.xy * float2(-0.5, -0.5)).grb; + #else + float P = SMAASamplePoint(tex, texcoord).r; + float Pleft = SMAASamplePoint(tex, offset[0].xy).r; + float Ptop = SMAASamplePoint(tex, offset[0].zw).r; + return float3(P, Pleft, Ptop); + #endif +} + +/** + * Adjusts the threshold by means of predication. + */ +float2 SMAACalculatePredicatedThreshold(float2 texcoord, + float4 offset[3], + SMAATexture2D(predicationTex)) { + float3 neighbours = SMAAGatherNeighbours(texcoord, offset, SMAATexturePass2D(predicationTex)); + float2 delta = abs(neighbours.xx - neighbours.yz); + float2 edges = step(SMAA_PREDICATION_THRESHOLD, delta); + return SMAA_PREDICATION_SCALE * SMAA_THRESHOLD * (1.0 - SMAA_PREDICATION_STRENGTH * edges); +} + +/** + * Conditional move: + */ +void SMAAMovc(bool2 cond, inout float2 variable, float2 value) { + SMAA_FLATTEN if (cond.x) variable.x = value.x; + SMAA_FLATTEN if (cond.y) variable.y = value.y; +} + +void SMAAMovc(bool4 cond, inout float4 variable, float4 value) { + SMAAMovc(cond.xy, variable.xy, value.xy); + SMAAMovc(cond.zw, variable.zw, value.zw); +} + + +#if SMAA_INCLUDE_VS +//----------------------------------------------------------------------------- +// Vertex Shaders + +/** + * Edge Detection Vertex Shader + */ +void SMAAEdgeDetectionVS(float2 texcoord, + out float4 offset[3]) { + offset[0] = mad(SMAA_RT_METRICS.xyxy, float4(-1.0, 0.0, 0.0, -1.0), texcoord.xyxy); + offset[1] = mad(SMAA_RT_METRICS.xyxy, float4( 1.0, 0.0, 0.0, 1.0), texcoord.xyxy); + offset[2] = mad(SMAA_RT_METRICS.xyxy, float4(-2.0, 0.0, 0.0, -2.0), texcoord.xyxy); +} + +/** + * Blend Weight Calculation Vertex Shader + */ +void SMAABlendingWeightCalculationVS(float2 texcoord, + out float2 pixcoord, + out float4 offset[3]) { + pixcoord = texcoord * SMAA_RT_METRICS.zw; + + // We will use these offsets for the searches later on (see @PSEUDO_GATHER4): + offset[0] = mad(SMAA_RT_METRICS.xyxy, float4(-0.25, -0.125, 1.25, -0.125), texcoord.xyxy); + offset[1] = mad(SMAA_RT_METRICS.xyxy, float4(-0.125, -0.25, -0.125, 1.25), texcoord.xyxy); + + // And these for the searches, they indicate the ends of the loops: + offset[2] = mad(SMAA_RT_METRICS.xxyy, + float4(-2.0, 2.0, -2.0, 2.0) * float(SMAA_MAX_SEARCH_STEPS), + float4(offset[0].xz, offset[1].yw)); +} + +/** + * Neighborhood Blending Vertex Shader + */ +void SMAANeighborhoodBlendingVS(float2 texcoord, + out float4 offset) { + offset = mad(SMAA_RT_METRICS.xyxy, float4( 1.0, 0.0, 0.0, 1.0), texcoord.xyxy); +} +#endif // SMAA_INCLUDE_VS + +#if SMAA_INCLUDE_PS +//----------------------------------------------------------------------------- +// Edge Detection Pixel Shaders (First Pass) + +/** + * Luma Edge Detection + * + * IMPORTANT NOTICE: luma edge detection requires gamma-corrected colors, and + * thus 'colorTex' should be a non-sRGB texture. + */ +float2 SMAALumaEdgeDetectionPS(float2 texcoord, + float4 offset[3], + SMAATexture2D(colorTex) + #if SMAA_PREDICATION + , SMAATexture2D(predicationTex) + #endif + ) { + // Calculate the threshold: + #if SMAA_PREDICATION + float2 threshold = SMAACalculatePredicatedThreshold(texcoord, offset, SMAATexturePass2D(predicationTex)); + #else + float2 threshold = float2(SMAA_THRESHOLD, SMAA_THRESHOLD); + #endif + + // Calculate lumas: + float3 weights = float3(0.2126, 0.7152, 0.0722); + float L = dot(SMAASamplePoint(colorTex, texcoord).rgb, weights); + + float Lleft = dot(SMAASamplePoint(colorTex, offset[0].xy).rgb, weights); + float Ltop = dot(SMAASamplePoint(colorTex, offset[0].zw).rgb, weights); + + // We do the usual threshold: + float4 delta; + delta.xy = abs(L - float2(Lleft, Ltop)); + float2 edges = step(threshold, delta.xy); + + // Then discard if there is no edge: + if (dot(edges, float2(1.0, 1.0)) == 0.0) + return float2(-2.0, -2.0); + + // Calculate right and bottom deltas: + float Lright = dot(SMAASamplePoint(colorTex, offset[1].xy).rgb, weights); + float Lbottom = dot(SMAASamplePoint(colorTex, offset[1].zw).rgb, weights); + delta.zw = abs(L - float2(Lright, Lbottom)); + + // Calculate the maximum delta in the direct neighborhood: + float2 maxDelta = max(delta.xy, delta.zw); + + // Calculate left-left and top-top deltas: + float Lleftleft = dot(SMAASamplePoint(colorTex, offset[2].xy).rgb, weights); + float Ltoptop = dot(SMAASamplePoint(colorTex, offset[2].zw).rgb, weights); + delta.zw = abs(float2(Lleft, Ltop) - float2(Lleftleft, Ltoptop)); + + // Calculate the final maximum delta: + maxDelta = max(maxDelta.xy, delta.zw); + float finalDelta = max(maxDelta.x, maxDelta.y); + + // Local contrast adaptation: + edges.xy *= step(finalDelta, SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR * delta.xy); + + return edges; +} + +/** + * Color Edge Detection + * + * IMPORTANT NOTICE: color edge detection requires gamma-corrected colors, and + * thus 'colorTex' should be a non-sRGB texture. + */ +float2 SMAAColorEdgeDetectionPS(float2 texcoord, + float4 offset[3], + SMAATexture2D(colorTex) + #if SMAA_PREDICATION + , SMAATexture2D(predicationTex) + #endif + ) { + // Calculate the threshold: + #if SMAA_PREDICATION + float2 threshold = SMAACalculatePredicatedThreshold(texcoord, offset, predicationTex); + #else + float2 threshold = float2(SMAA_THRESHOLD, SMAA_THRESHOLD); + #endif + + // Calculate color deltas: + float4 delta; + float3 C = SMAASamplePoint(colorTex, texcoord).rgb; + + float3 Cleft = SMAASamplePoint(colorTex, offset[0].xy).rgb; + float3 t = abs(C - Cleft); + delta.x = max(max(t.r, t.g), t.b); + + float3 Ctop = SMAASamplePoint(colorTex, offset[0].zw).rgb; + t = abs(C - Ctop); + delta.y = max(max(t.r, t.g), t.b); + + // We do the usual threshold: + float2 edges = step(threshold, delta.xy); + + // Then discard if there is no edge: + if (dot(edges, float2(1.0, 1.0)) == 0.0) + return float2(-2.0, -2.0); + + // Calculate right and bottom deltas: + float3 Cright = SMAASamplePoint(colorTex, offset[1].xy).rgb; + t = abs(C - Cright); + delta.z = max(max(t.r, t.g), t.b); + + float3 Cbottom = SMAASamplePoint(colorTex, offset[1].zw).rgb; + t = abs(C - Cbottom); + delta.w = max(max(t.r, t.g), t.b); + + // Calculate the maximum delta in the direct neighborhood: + float2 maxDelta = max(delta.xy, delta.zw); + + // Calculate left-left and top-top deltas: + float3 Cleftleft = SMAASamplePoint(colorTex, offset[2].xy).rgb; + t = abs(C - Cleftleft); + delta.z = max(max(t.r, t.g), t.b); + + float3 Ctoptop = SMAASamplePoint(colorTex, offset[2].zw).rgb; + t = abs(C - Ctoptop); + delta.w = max(max(t.r, t.g), t.b); + + // Calculate the final maximum delta: + maxDelta = max(maxDelta.xy, delta.zw); + float finalDelta = max(maxDelta.x, maxDelta.y); + + // Local contrast adaptation: + edges.xy *= step(finalDelta, SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR * delta.xy); + + return edges; +} + +/** + * Depth Edge Detection + */ +float2 SMAADepthEdgeDetectionPS(float2 texcoord, + float4 offset[3], + SMAATexture2D(depthTex)) { + float3 neighbours = SMAAGatherNeighbours(texcoord, offset, SMAATexturePass2D(depthTex)); + float2 delta = abs(neighbours.xx - float2(neighbours.y, neighbours.z)); + float2 edges = step(SMAA_DEPTH_THRESHOLD, delta); + + if (dot(edges, float2(1.0, 1.0)) == 0.0) + return float2(-2.0, -2.0); + + return edges; +} + +//----------------------------------------------------------------------------- +// Diagonal Search Functions + +#if !defined(SMAA_DISABLE_DIAG_DETECTION) + +/** + * Allows to decode two binary values from a bilinear-filtered access. + */ +float2 SMAADecodeDiagBilinearAccess(float2 e) { + // Bilinear access for fetching 'e' have a 0.25 offset, and we are + // interested in the R and G edges: + // + // +---G---+-------+ + // | x o R x | + // +-------+-------+ + // + // Then, if one of these edge is enabled: + // Red: (0.75 * X + 0.25 * 1) => 0.25 or 1.0 + // Green: (0.75 * 1 + 0.25 * X) => 0.75 or 1.0 + // + // This function will unpack the values (mad + mul + round): + // wolframalpha.com: round(x * abs(5 * x - 5 * 0.75)) plot 0 to 1 + e.r = e.r * abs(5.0 * e.r - 5.0 * 0.75); + return round(e); +} + +float4 SMAADecodeDiagBilinearAccess(float4 e) { + e.rb = e.rb * abs(5.0 * e.rb - 5.0 * 0.75); + return round(e); +} + +/** + * These functions allows to perform diagonal pattern searches. + */ +float2 SMAASearchDiag1(SMAATexture2D(edgesTex), float2 texcoord, float2 dir, out float2 e) { + float4 coord = float4(texcoord, -1.0, 1.0); + float3 t = float3(SMAA_RT_METRICS.xy, 1.0); + while (coord.z < float(SMAA_MAX_SEARCH_STEPS_DIAG - 1) && + coord.w > 0.9) { + coord.xyz = mad(t, float3(dir, 1.0), coord.xyz); + e = SMAASampleLevelZero(edgesTex, coord.xy).rg; + coord.w = dot(e, float2(0.5, 0.5)); + } + return coord.zw; +} + +float2 SMAASearchDiag2(SMAATexture2D(edgesTex), float2 texcoord, float2 dir, out float2 e) { + float4 coord = float4(texcoord, -1.0, 1.0); + coord.x += 0.25 * SMAA_RT_METRICS.x; // See @SearchDiag2Optimization + float3 t = float3(SMAA_RT_METRICS.xy, 1.0); + while (coord.z < float(SMAA_MAX_SEARCH_STEPS_DIAG - 1) && + coord.w > 0.9) { + coord.xyz = mad(t, float3(dir, 1.0), coord.xyz); + + // @SearchDiag2Optimization + // Fetch both edges at once using bilinear filtering: + e = SMAASampleLevelZero(edgesTex, coord.xy).rg; + e = SMAADecodeDiagBilinearAccess(e); + + // Non-optimized version: + // e.g = SMAASampleLevelZero(edgesTex, coord.xy).g; + // e.r = SMAASampleLevelZeroOffset(edgesTex, coord.xy, int2(1, 0)).r; + + coord.w = dot(e, float2(0.5, 0.5)); + } + return coord.zw; +} + +/** + * Similar to SMAAArea, this calculates the area corresponding to a certain + * diagonal distance and crossing edges 'e'. + */ +float2 SMAAAreaDiag(SMAATexture2D(areaTex), float2 dist, float2 e, float offset) { + float2 texcoord = mad(float2(SMAA_AREATEX_MAX_DISTANCE_DIAG, SMAA_AREATEX_MAX_DISTANCE_DIAG), e, dist); + + // We do a scale and bias for mapping to texel space: + texcoord = mad(SMAA_AREATEX_PIXEL_SIZE, texcoord, 0.5 * SMAA_AREATEX_PIXEL_SIZE); + + // Diagonal areas are on the second half of the texture: + texcoord.x += 0.5; + + // Move to proper place, according to the subpixel offset: + texcoord.y += SMAA_AREATEX_SUBTEX_SIZE * offset; + + // Do it! + return SMAA_AREATEX_SELECT(SMAASampleLevelZero(areaTex, texcoord)); +} + +/** + * This searches for diagonal patterns and returns the corresponding weights. + */ +float2 SMAACalculateDiagWeights(SMAATexture2D(edgesTex), SMAATexture2D(areaTex), float2 texcoord, float2 e, float4 subsampleIndices) { + float2 weights = float2(0.0, 0.0); + + // Search for the line ends: + float4 d; + float2 end; + if (e.r > 0.0) { + d.xz = SMAASearchDiag1(SMAATexturePass2D(edgesTex), texcoord, float2(-1.0, 1.0), end); + d.x += float(end.y > 0.9); + } else + d.xz = float2(0.0, 0.0); + d.yw = SMAASearchDiag1(SMAATexturePass2D(edgesTex), texcoord, float2(1.0, -1.0), end); + + SMAA_BRANCH + if (d.x + d.y > 2.0) { // d.x + d.y + 1 > 3 + // Fetch the crossing edges: + float4 coords = mad(float4(-d.x + 0.25, d.x, d.y, -d.y - 0.25), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + float4 c; + c.xy = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).rg; + c.zw = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).rg; + c.yxwz = SMAADecodeDiagBilinearAccess(c.xyzw); + + // Non-optimized version: + // float4 coords = mad(float4(-d.x, d.x, d.y, -d.y), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + // float4 c; + // c.x = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).g; + // c.y = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2( 0, 0)).r; + // c.z = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).g; + // c.w = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, -1)).r; + + // Merge crossing edges at each side into a single value: + float2 cc = mad(float2(2.0, 2.0), c.xz, c.yw); + + // Remove the crossing edge if we didn't found the end of the line: + SMAAMovc(bool2(step(0.9, d.zw)), cc, float2(0.0, 0.0)); + + // Fetch the areas for this line: + weights += SMAAAreaDiag(SMAATexturePass2D(areaTex), d.xy, cc, subsampleIndices.z); + } + + // Search for the line ends: + d.xz = SMAASearchDiag2(SMAATexturePass2D(edgesTex), texcoord, float2(-1.0, -1.0), end); + if (SMAASampleLevelZeroOffset(edgesTex, texcoord, int2(1, 0)).r > 0.0) { + d.yw = SMAASearchDiag2(SMAATexturePass2D(edgesTex), texcoord, float2(1.0, 1.0), end); + d.y += float(end.y > 0.9); + } else + d.yw = float2(0.0, 0.0); + + SMAA_BRANCH + if (d.x + d.y > 2.0) { // d.x + d.y + 1 > 3 + // Fetch the crossing edges: + float4 coords = mad(float4(-d.x, -d.x, d.y, d.y), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + float4 c; + c.x = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).g; + c.y = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2( 0, -1)).r; + c.zw = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).gr; + float2 cc = mad(float2(2.0, 2.0), c.xz, c.yw); + + // Remove the crossing edge if we didn't found the end of the line: + SMAAMovc(bool2(step(0.9, d.zw)), cc, float2(0.0, 0.0)); + + // Fetch the areas for this line: + weights += SMAAAreaDiag(SMAATexturePass2D(areaTex), d.xy, cc, subsampleIndices.w).gr; + } + + return weights; +} +#endif + +//----------------------------------------------------------------------------- +// Horizontal/Vertical Search Functions + +/** + * This allows to determine how much length should we add in the last step + * of the searches. It takes the bilinearly interpolated edge (see + * @PSEUDO_GATHER4), and adds 0, 1 or 2, depending on which edges and + * crossing edges are active. + */ +float SMAASearchLength(SMAATexture2D(searchTex), float2 e, float offset) { + // The texture is flipped vertically, with left and right cases taking half + // of the space horizontally: + float2 scale = SMAA_SEARCHTEX_SIZE * float2(0.5, -1.0); + float2 bias = SMAA_SEARCHTEX_SIZE * float2(offset, 1.0); + + // Scale and bias to access texel centers: + scale += float2(-1.0, 1.0); + bias += float2( 0.5, -0.5); + + // Convert from pixel coordinates to texcoords: + // (We use SMAA_SEARCHTEX_PACKED_SIZE because the texture is cropped) + scale *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; + bias *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; + + // Lookup the search texture: + return SMAA_SEARCHTEX_SELECT(SMAASampleLevelZero(searchTex, mad(scale, e, bias))); +} + +/** + * Horizontal/vertical search functions for the 2nd pass. + */ +float SMAASearchXLeft(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + /** + * @PSEUDO_GATHER4 + * This texcoord has been offset by (-0.25, -0.125) in the vertex shader to + * sample between edge, thus fetching four edges in a row. + * Sampling with different offsets in each direction allows to disambiguate + * which edges are active from the four fetched ones. + */ + float2 e = float2(0.0, 1.0); + while (texcoord.x > end && + e.g > 0.8281 && // Is there some edge not activated? + e.r == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(-float2(2.0, 0.0), SMAA_RT_METRICS.xy, texcoord); + } + + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.0), 3.25); + return mad(SMAA_RT_METRICS.x, offset, texcoord.x); + + // Non-optimized version: + // We correct the previous (-0.25, -0.125) offset we applied: + // texcoord.x += 0.25 * SMAA_RT_METRICS.x; + + // The searches are bias by 1, so adjust the coords accordingly: + // texcoord.x += SMAA_RT_METRICS.x; + + // Disambiguate the length added by the last step: + // texcoord.x += 2.0 * SMAA_RT_METRICS.x; // Undo last step + // texcoord.x -= SMAA_RT_METRICS.x * (255.0 / 127.0) * SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.0); + // return mad(SMAA_RT_METRICS.x, offset, texcoord.x); +} + +float SMAASearchXRight(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(0.0, 1.0); + while (texcoord.x < end && + e.g > 0.8281 && // Is there some edge not activated? + e.r == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(float2(2.0, 0.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.5), 3.25); + return mad(-SMAA_RT_METRICS.x, offset, texcoord.x); +} + +float SMAASearchYUp(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(1.0, 0.0); + while (texcoord.y > end && + e.r > 0.8281 && // Is there some edge not activated? + e.g == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(-float2(0.0, 2.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e.gr, 0.0), 3.25); + return mad(SMAA_RT_METRICS.y, offset, texcoord.y); +} + +float SMAASearchYDown(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(1.0, 0.0); + while (texcoord.y < end && + e.r > 0.8281 && // Is there some edge not activated? + e.g == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(float2(0.0, 2.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e.gr, 0.5), 3.25); + return mad(-SMAA_RT_METRICS.y, offset, texcoord.y); +} + +/** + * Ok, we have the distance and both crossing edges. So, what are the areas + * at each side of current edge? + */ +float2 SMAAArea(SMAATexture2D(areaTex), float2 dist, float e1, float e2, float offset) { + // Rounding prevents precision errors of bilinear filtering: + float2 texcoord = mad(float2(SMAA_AREATEX_MAX_DISTANCE, SMAA_AREATEX_MAX_DISTANCE), round(4.0 * float2(e1, e2)), dist); + + // We do a scale and bias for mapping to texel space: + texcoord = mad(SMAA_AREATEX_PIXEL_SIZE, texcoord, 0.5 * SMAA_AREATEX_PIXEL_SIZE); + + // Move to proper place, according to the subpixel offset: + texcoord.y = mad(SMAA_AREATEX_SUBTEX_SIZE, offset, texcoord.y); + + // Do it! + return SMAA_AREATEX_SELECT(SMAASampleLevelZero(areaTex, texcoord)); +} + +//----------------------------------------------------------------------------- +// Corner Detection Functions + +void SMAADetectHorizontalCornerPattern(SMAATexture2D(edgesTex), inout float2 weights, float4 texcoord, float2 d) { + #if !defined(SMAA_DISABLE_CORNER_DETECTION) + float2 leftRight = step(d.xy, d.yx); + float2 rounding = (1.0 - SMAA_CORNER_ROUNDING_NORM) * leftRight; + + rounding /= leftRight.x + leftRight.y; // Reduce blending for pixels in the center of a line. + + float2 factor = float2(1.0, 1.0); + factor.x -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(0, 1)).r; + factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(1, 1)).r; + factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(0, -2)).r; + factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(1, -2)).r; + + weights *= saturate(factor); + #endif +} + +void SMAADetectVerticalCornerPattern(SMAATexture2D(edgesTex), inout float2 weights, float4 texcoord, float2 d) { + #if !defined(SMAA_DISABLE_CORNER_DETECTION) + float2 leftRight = step(d.xy, d.yx); + float2 rounding = (1.0 - SMAA_CORNER_ROUNDING_NORM) * leftRight; + + rounding /= leftRight.x + leftRight.y; + + float2 factor = float2(1.0, 1.0); + factor.x -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2( 1, 0)).g; + factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2( 1, 1)).g; + factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(-2, 0)).g; + factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(-2, 1)).g; + + weights *= saturate(factor); + #endif +} + +//----------------------------------------------------------------------------- +// Blending Weight Calculation Pixel Shader (Second Pass) + +float4 SMAABlendingWeightCalculationPS(float2 texcoord, + float2 pixcoord, + float4 offset[3], + SMAATexture2D(edgesTex), + SMAATexture2D(areaTex), + SMAATexture2D(searchTex), + float4 subsampleIndices) { // Just pass zero for SMAA 1x, see @SUBSAMPLE_INDICES. + float4 weights = float4(0.0, 0.0, 0.0, 0.0); + + float2 e = SMAASample(edgesTex, texcoord).rg; + + SMAA_BRANCH + if (e.g > 0.0) { // Edge at north + #if !defined(SMAA_DISABLE_DIAG_DETECTION) + // Diagonals have both north and west edges, so searching for them in + // one of the boundaries is enough. + weights.rg = SMAACalculateDiagWeights(SMAATexturePass2D(edgesTex), SMAATexturePass2D(areaTex), texcoord, e, subsampleIndices); + + // We give priority to diagonals, so if we find a diagonal we skip + // horizontal/vertical processing. + SMAA_BRANCH + if (weights.r == -weights.g) { // weights.r + weights.g == 0.0 + #endif + + float2 d; + + // Find the distance to the left: + float3 coords; + coords.x = SMAASearchXLeft(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[0].xy, offset[2].x); + coords.y = offset[1].y; // offset[1].y = texcoord.y - 0.25 * SMAA_RT_METRICS.y (@CROSSING_OFFSET) + d.x = coords.x; + + // Now fetch the left crossing edges, two at a time using bilinear + // filtering. Sampling at -0.25 (see @CROSSING_OFFSET) enables to + // discern what value each edge has: + float e1 = SMAASampleLevelZero(edgesTex, coords.xy).r; + + // Find the distance to the right: + coords.z = SMAASearchXRight(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[0].zw, offset[2].y); + d.y = coords.z; + + // We want the distances to be in pixel units (doing this here allow to + // better interleave arithmetic and memory accesses): + d = abs(round(mad(SMAA_RT_METRICS.zz, d, -pixcoord.xx))); + + // SMAAArea below needs a sqrt, as the areas texture is compressed + // quadratically: + float2 sqrt_d = sqrt(d); + + // Fetch the right crossing edges: + float e2 = SMAASampleLevelZeroOffset(edgesTex, coords.zy, int2(1, 0)).r; + + // Ok, we know how this pattern looks like, now it is time for getting + // the actual area: + weights.rg = SMAAArea(SMAATexturePass2D(areaTex), sqrt_d, e1, e2, subsampleIndices.y); + + // Fix corners: + coords.y = texcoord.y; + SMAADetectHorizontalCornerPattern(SMAATexturePass2D(edgesTex), weights.rg, coords.xyzy, d); + + #if !defined(SMAA_DISABLE_DIAG_DETECTION) + } else + e.r = 0.0; // Skip vertical processing. + #endif + } + + SMAA_BRANCH + if (e.r > 0.0) { // Edge at west + float2 d; + + // Find the distance to the top: + float3 coords; + coords.y = SMAASearchYUp(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[1].xy, offset[2].z); + coords.x = offset[0].x; // offset[1].x = texcoord.x - 0.25 * SMAA_RT_METRICS.x; + d.x = coords.y; + + // Fetch the top crossing edges: + float e1 = SMAASampleLevelZero(edgesTex, coords.xy).g; + + // Find the distance to the bottom: + coords.z = SMAASearchYDown(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[1].zw, offset[2].w); + d.y = coords.z; + + // We want the distances to be in pixel units: + d = abs(round(mad(SMAA_RT_METRICS.ww, d, -pixcoord.yy))); + + // SMAAArea below needs a sqrt, as the areas texture is compressed + // quadratically: + float2 sqrt_d = sqrt(d); + + // Fetch the bottom crossing edges: + float e2 = SMAASampleLevelZeroOffset(edgesTex, coords.xz, int2(0, 1)).g; + + // Get the area for this direction: + weights.ba = SMAAArea(SMAATexturePass2D(areaTex), sqrt_d, e1, e2, subsampleIndices.x); + + // Fix corners: + coords.x = texcoord.x; + SMAADetectVerticalCornerPattern(SMAATexturePass2D(edgesTex), weights.ba, coords.xyxz, d); + } + + return weights; +} + +//----------------------------------------------------------------------------- +// Neighborhood Blending Pixel Shader (Third Pass) + +float4 SMAANeighborhoodBlendingPS(float2 texcoord, + float4 offset, + SMAATexture2D(colorTex), + SMAATexture2D(blendTex) + #if SMAA_REPROJECTION + , SMAATexture2D(velocityTex) + #endif + ) { + // Fetch the blending weights for current pixel: + float4 a; + a.x = SMAASample(blendTex, offset.xy).a; // Right + a.y = SMAASample(blendTex, offset.zw).g; // Top + a.wz = SMAASample(blendTex, texcoord).xz; // Bottom / Left + + // Is there any blending weight with a value greater than 0.0? + SMAA_BRANCH + if (dot(a, float4(1.0, 1.0, 1.0, 1.0)) < 1e-5) { + float4 color = SMAASampleLevelZero(colorTex, texcoord); + + #if SMAA_REPROJECTION + float2 velocity = SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, texcoord)); + + // Pack velocity into the alpha channel: + color.a = sqrt(5.0 * length(velocity)); + #endif + + return color; + } else { + bool h = max(a.x, a.z) > max(a.y, a.w); // max(horizontal) > max(vertical) + + // Calculate the blending offsets: + float4 blendingOffset = float4(0.0, a.y, 0.0, a.w); + float2 blendingWeight = a.yw; + SMAAMovc(bool4(h, h, h, h), blendingOffset, float4(a.x, 0.0, a.z, 0.0)); + SMAAMovc(bool2(h, h), blendingWeight, a.xz); + blendingWeight /= dot(blendingWeight, float2(1.0, 1.0)); + + // Calculate the texture coordinates: + float4 blendingCoord = mad(blendingOffset, float4(SMAA_RT_METRICS.xy, -SMAA_RT_METRICS.xy), texcoord.xyxy); + + // We exploit bilinear filtering to mix current pixel with the chosen + // neighbor: + float4 color = blendingWeight.x * SMAASampleLevelZero(colorTex, blendingCoord.xy); + color += blendingWeight.y * SMAASampleLevelZero(colorTex, blendingCoord.zw); + + #if SMAA_REPROJECTION + // Antialias velocity for proper reprojection in a later stage: + float2 velocity = blendingWeight.x * SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, blendingCoord.xy)); + velocity += blendingWeight.y * SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, blendingCoord.zw)); + + // Pack velocity into the alpha channel: + color.a = sqrt(5.0 * length(velocity)); + #endif + + return color; + } +} + +//----------------------------------------------------------------------------- +// Temporal Resolve Pixel Shader (Optional Pass) + +float4 SMAAResolvePS(float2 texcoord, + SMAATexture2D(currentColorTex), + SMAATexture2D(previousColorTex) + #if SMAA_REPROJECTION + , SMAATexture2D(velocityTex) + #endif + ) { + #if SMAA_REPROJECTION + // Velocity is assumed to be calculated for motion blur, so we need to + // inverse it for reprojection: + float2 velocity = -SMAA_DECODE_VELOCITY(SMAASamplePoint(velocityTex, texcoord).rg); + + // Fetch current pixel: + float4 current = SMAASamplePoint(currentColorTex, texcoord); + + // Reproject current coordinates and fetch previous pixel: + float4 previous = SMAASamplePoint(previousColorTex, texcoord + velocity); + + // Attenuate the previous pixel if the velocity is different: + float delta = abs(current.a * current.a - previous.a * previous.a) / 5.0; + float weight = 0.5 * saturate(1.0 - sqrt(delta) * SMAA_REPROJECTION_WEIGHT_SCALE); + + // Blend the pixels according to the calculated weight: + return lerp(current, previous, weight); + #else + // Just blend the pixels: + float4 current = SMAASamplePoint(currentColorTex, texcoord); + float4 previous = SMAASamplePoint(previousColorTex, texcoord); + return lerp(current, previous, 0.5); + #endif +} + +//----------------------------------------------------------------------------- +// Separate Multisamples Pixel Shader (Optional Pass) + +#ifdef SMAALoad +void SMAASeparatePS(float4 position, + float2 texcoord, + out float4 target0, + out float4 target1, + SMAATexture2DMS2(colorTexMS)) { + int2 pos = int2(position.xy); + target0 = SMAALoad(colorTexMS, pos, 0); + target1 = SMAALoad(colorTexMS, pos, 1); +} +#endif + +//----------------------------------------------------------------------------- +#endif // SMAA_INCLUDE_PS + +layout(rgba8, binding = 0, set = 3) uniform image2D imgOutput; + +layout(binding = 1, set = 2) uniform sampler2D inputImg; +layout( binding = 2 ) uniform invResolution +{ + vec2 invResolution_data; +}; + +void main() +{ + vec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4); + for(int i = 0; i < 4; i++) + { + for(int j = 0; j < 4; j++) + { + ivec2 texelCoord = ivec2(loc.x + i, loc.y + j); + vec2 coord = (texelCoord + vec2(0.5)) / invResolution_data; + vec4 offset[3]; + SMAAEdgeDetectionVS(coord, offset); + vec2 oColor = SMAAColorEdgeDetectionPS(coord, offset, inputImg); + if (oColor != float2(-2.0, -2.0)) + { + imageStore(imgOutput, texelCoord, vec4(oColor, 0.0, 1.0)); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.spv b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.spv new file mode 100644 index 0000000000000000000000000000000000000000..1062a9e3abd0624aec557d24c06e1a161dec6874 GIT binary patch literal 8464 zcmZ9Q33OG}6^1X6m!RNOkwH<3D54-DqKJqPl8{7$K@tSC7N6k-Vv{^d9+Xt&aRhtmfQ31e^2L}eeS(4Oc+1ADVs1Z zJ20D=Z5o&5Z`oRDtJEs}wZZDhMZI%(4J=x=Yhdvu zr(e2^xDs`Mb;o`Q*Y1~abkS+c#&VRh zX@L5?)A!3;%4VSVo3pR7dn~RoI~K5}y>DGt>*ftzZGD}=H$TmHws&=O_MyA}Rm`<* zJ1V2wszYv|#JO}Zx8}GZ>lP1H2G~k&>t!Cp4cSKUmda4AoZA|*0p`|PbvqNj4cV2< z70z!o>HC;lcMNXbmY+*QcC-5B)mp7OjDIP+4Sl%0yDg>6xjXf-{{Pp|klias9ZMPa zAo{@INO>qlx`%(l4ORQGPR#GY)}hTEL)FX6LtP`gs{LiV+0}-RzKz_0!GsS1UyTRu{8;KfRQ78Z*4LXJ?JL zJf<=0h4Y?nUc0Wnx4mz3ch3fLHD!Hu{F?T*uJvoI)0C~RvvqQ}#@5i3T~udV-`%&a z1!D*6^PXY`jp@xv_ZimivpWfyiF8sa&T}@pqk}V)^BLCP^E@uMJgu8E?4FVKqOKh} zkM}&Z`$jRJx^{=UXTyK_Q1?vK6?M-+9Qob5dgR-P;(mN)564TtW~|!9Q$dZ)vyf@z zYhqT{HVb_svz(&+0A}r;-x2)(K;~J9V_wp&Xa1S!lbMZ?Gv7hX=8|vbd=F-}mUXnv z!257!Ym7sRBlM%lJ_Un*<~$-f$u$!>mDw7RQ@d-mr?U|AxK{O}5r?`pkIA(?D;6c) z`;ui3w}sxE>o0B#{}Xb3&)uQV$@MIo&kEf4iHJV(ir6*JV|I=D#9B@PyS9j31a>_U zD-Pc!V9zb;o&#pw6S2!tja z$uGmM?sF{X`W{Qzm{`xh!QNr>jmL2&*!AgOLU%uM`a2&4r@!`T$!?r>_k9wWu_E?x zGP;~{3@i1WVNT;u0q1AI?0VIGk3{`b6V_k5_t*D`!}Yd+Jum%zURI;KhU5Yo8`@*d z9m%M#In{mFIK12L(RYm_bl){a-FHn<_g&+NGu?phZ;<({#$NdDkyE@6XL8?SO{jVQ z9)l@?U6aqN-_O{$eefGu)csC|ZXf(shHkvy$Bu)yWhCbwfl_=UAy17qVD%B zbp8FFskg9d@73docg^QvIeYMqJ%PwyhmZPy66c+}DxLL{iH|%_p_|8Ziah@X%SRp? zELY4k4o-fpXUoh_+05MXRcg7`hQy!CiF>~4V$b$y^vCHz6WCrjKSJCA%m*S4?URz- z+4Xr}CWFm!JGH%!2ZQB&R-V8|eIjBmbBnEO52Nm($>wa`XD}a*IIKG**`1^A5nyvf z-Kk)?sH=V$VlL~7J!jujGr*of^vmbwNI3b(e-zk#d4Aewg5|Czhx*ZAW8_EDSsVkF zk2=SKtrPt;ZWf$;)M*C0kEnATST5?!1{)(Eb&dziN54-1TU*|<^?8u9#(3rv5xKYv za}#F`@5VfIIp0^V{Z!_Ki0>nNWFGxaOZ>hRw+P)>dF%Sz$%X$JU^#6*bLvYuJ)i$2 zVE@jM_xUr%-vehQ-58%+^~iS)IP&>Ssz=VP1vE?Irdm*600hP5h(8w}TztbNMzz&if`d?`q~Qq%-OE<2=NEMBVej z*1eFr-jQywoFlk3iIZN-uKdk#0gArb=Aio>?Wd;otfAfKC)VVw?z80@on3>^aSzyg zxQ8{UuLaBbd&INSCTG0QsqsEr>b@h?og?3c;6^0!tpm$NK5cTw$9u$>?|a{ z6W$SrzY&+P4&TrI-7EH8?6)Rg3$yn!f5yO`g}>W^J1%j}%yE9R!Ltx;`fBUpWIfyC z5$%eDrRBpp`ZgErS|a8ou$6vA46p^?O^IxZ>W}UfK}*GKIMl zanI4GF0f~_F}?531M4F{9+`vm@Lc&f$@z$O#bFQiv5se?J?g9hTPJ#V0oZeoZ=KI+ z4skk=U2zoGWv{fmF89!fxYp3ugY~(VcZ2qe5Pjq) z^QM#AfS9uZ(e8fjxxD-H%r8OYHX-J2WPTvJg*KAXYvlQ{unE<>y}7cpLY z#9t0JzKleCKUhBEw}2!5GO+fDuYkRquF*ZH>u)aa##W@*vu)`0JsX6RkDgrtmQ(D@ z1SER)Y_K`?sqfhkoOZ?Gn)PvA1I*f8m*+o>6ni#;u21x=ntbG=XWPN%v}f9*XYz3# z&jHu>YzLft_>3l>V$W)D@^R)n!ExrsYmfL{;QF3D7fwFncY`C|T-qc4d0=~HefOZQ zzh~3L{Cs3p(tW0`LiCURydZJ%4(nLg*cT>!A+ve(wTAB}ZBgSzV7VL9xBqIe+#ci( zxNDfNMdaro{%*UT+2QXtZPy`kzH7x#G4D;feB8m8fQxtVrRdXJAidwhA~ z@DD8&)$k&-?O*D$w$v_0m~_#dGWpRc64*zg4Fly9dOze z$2cTqi62{o70|YkDkfLdAt`~-?R6@$%oJTlTWc{ zAApmOGyfpiTK3F%?GgVWaDC4{3@0D)w}K%l;rlUg(f1B?ImNTLcdn_4`Qu2e$(;IH|F&d{J98I$@y>h#eLAuPiMpQz%f}i& z1(s7B)`{o*(_nMzh>U7xr!cPAhDxHI>F z&FP)d9(U#*aP;HzVEefVv6j#I7ZCaI`65`K;+^>tx_tEf%V2AHXN=b#@m~QOUq+(F zSHbcT|21&Ln@fAde;sU}jdu^~`kQMC^EZ$gi2n1K??dW)c7NidXWvAZQ#^C;O7!eo zU~}rDt=O|~qia_ju2~<~buY8_=-GF`#h!f^U7zUL_rUtdN6)?wHm5z)9zBzf^Y{VS z^DOr4hv@R*^P}Wb?Aedu$8kzry$i<%i1TOCB&**ZB!<;v8XWi3Z5bX~k{@b41 uUlDUEvG>1$ja!IlH!jZf@8Bzu>r?#0U^!!4uQs{x{|8vk-#lXf?d5;9EWxh; literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.glsl b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.glsl new file mode 100644 index 00000000..df30d727 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.glsl @@ -0,0 +1,1403 @@ +#version 430 core +#define SMAA_GLSL_4 1 + +layout (constant_id = 0) const int SMAA_PRESET_LOW = 0; +layout (constant_id = 1) const int SMAA_PRESET_MEDIUM = 0; +layout (constant_id = 2) const int SMAA_PRESET_HIGH = 0; +layout (constant_id = 3) const int SMAA_PRESET_ULTRA = 0; +layout (constant_id = 4) const float METRIC_WIDTH = 1920.0; +layout (constant_id = 5) const float METRIC_HEIGHT = 1080.0; + +#define SMAA_RT_METRICS float4(1.0 / METRIC_WIDTH, 1.0 / METRIC_HEIGHT, METRIC_WIDTH, METRIC_HEIGHT) + +layout (local_size_x = 16, local_size_y = 16) in; +/** + * Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com) + * Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) + * Copyright (C) 2013 Belen Masia (bmasia@unizar.es) + * Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com) + * Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to + * do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. As clarification, there + * is no requirement that the copyright notice and permission be included in + * binary distributions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +/** + * _______ ___ ___ ___ ___ + * / || \/ | / \ / \ + * | (---- | \ / | / ^ \ / ^ \ + * \ \ | |\/| | / /_\ \ / /_\ \ + * ----) | | | | | / _____ \ / _____ \ + * |_______/ |__| |__| /__/ \__\ /__/ \__\ + * + * E N H A N C E D + * S U B P I X E L M O R P H O L O G I C A L A N T I A L I A S I N G + * + * http://www.iryoku.com/smaa/ + * + * Hi, welcome aboard! + * + * Here you'll find instructions to get the shader up and running as fast as + * possible. + * + * IMPORTANTE NOTICE: when updating, remember to update both this file and the + * precomputed textures! They may change from version to version. + * + * The shader has three passes, chained together as follows: + * + * |input|------------------ + * v | + * [ SMAA*EdgeDetection ] | + * v | + * |edgesTex| | + * v | + * [ SMAABlendingWeightCalculation ] | + * v | + * |blendTex| | + * v | + * [ SMAANeighborhoodBlending ] <------ + * v + * |output| + * + * Note that each [pass] has its own vertex and pixel shader. Remember to use + * oversized triangles instead of quads to avoid overshading along the + * diagonal. + * + * You've three edge detection methods to choose from: luma, color or depth. + * They represent different quality/performance and anti-aliasing/sharpness + * tradeoffs, so our recommendation is for you to choose the one that best + * suits your particular scenario: + * + * - Depth edge detection is usually the fastest but it may miss some edges. + * + * - Luma edge detection is usually more expensive than depth edge detection, + * but catches visible edges that depth edge detection can miss. + * + * - Color edge detection is usually the most expensive one but catches + * chroma-only edges. + * + * For quickstarters: just use luma edge detection. + * + * The general advice is to not rush the integration process and ensure each + * step is done correctly (don't try to integrate SMAA T2x with predicated edge + * detection from the start!). Ok then, let's go! + * + * 1. The first step is to create two RGBA temporal render targets for holding + * |edgesTex| and |blendTex|. + * + * In DX10 or DX11, you can use a RG render target for the edges texture. + * In the case of NVIDIA GPUs, using RG render targets seems to actually be + * slower. + * + * On the Xbox 360, you can use the same render target for resolving both + * |edgesTex| and |blendTex|, as they aren't needed simultaneously. + * + * 2. Both temporal render targets |edgesTex| and |blendTex| must be cleared + * each frame. Do not forget to clear the alpha channel! + * + * 3. The next step is loading the two supporting precalculated textures, + * 'areaTex' and 'searchTex'. You'll find them in the 'Textures' folder as + * C++ headers, and also as regular DDS files. They'll be needed for the + * 'SMAABlendingWeightCalculation' pass. + * + * If you use the C++ headers, be sure to load them in the format specified + * inside of them. + * + * You can also compress 'areaTex' and 'searchTex' using BC5 and BC4 + * respectively, if you have that option in your content processor pipeline. + * When compressing then, you get a non-perceptible quality decrease, and a + * marginal performance increase. + * + * 4. All samplers must be set to linear filtering and clamp. + * + * After you get the technique working, remember that 64-bit inputs have + * half-rate linear filtering on GCN. + * + * If SMAA is applied to 64-bit color buffers, switching to point filtering + * when accesing them will increase the performance. Search for + * 'SMAASamplePoint' to see which textures may benefit from point + * filtering, and where (which is basically the color input in the edge + * detection and resolve passes). + * + * 5. All texture reads and buffer writes must be non-sRGB, with the exception + * of the input read and the output write in + * 'SMAANeighborhoodBlending' (and only in this pass!). If sRGB reads in + * this last pass are not possible, the technique will work anyway, but + * will perform antialiasing in gamma space. + * + * IMPORTANT: for best results the input read for the color/luma edge + * detection should *NOT* be sRGB. + * + * 6. Before including SMAA.h you'll have to setup the render target metrics, + * the target and any optional configuration defines. Optionally you can + * use a preset. + * + * You have the following targets available: + * SMAA_HLSL_3 + * SMAA_HLSL_4 + * SMAA_HLSL_4_1 + * SMAA_GLSL_3 * + * SMAA_GLSL_4 * + * + * * (See SMAA_INCLUDE_VS and SMAA_INCLUDE_PS below). + * + * And four presets: + * SMAA_PRESET_LOW (%60 of the quality) + * SMAA_PRESET_MEDIUM (%80 of the quality) + * SMAA_PRESET_HIGH (%95 of the quality) + * SMAA_PRESET_ULTRA (%99 of the quality) + * + * For example: + * #define SMAA_RT_METRICS float4(1.0 / 1280.0, 1.0 / 720.0, 1280.0, 720.0) + * #define SMAA_HLSL_4 + * #define SMAA_PRESET_HIGH + * #include "SMAA.h" + * + * Note that SMAA_RT_METRICS doesn't need to be a macro, it can be a + * uniform variable. The code is designed to minimize the impact of not + * using a constant value, but it is still better to hardcode it. + * + * Depending on how you encoded 'areaTex' and 'searchTex', you may have to + * add (and customize) the following defines before including SMAA.h: + * #define SMAA_AREATEX_SELECT(sample) sample.rg + * #define SMAA_SEARCHTEX_SELECT(sample) sample.r + * + * If your engine is already using porting macros, you can define + * SMAA_CUSTOM_SL, and define the porting functions by yourself. + * + * 7. Then, you'll have to setup the passes as indicated in the scheme above. + * You can take a look into SMAA.fx, to see how we did it for our demo. + * Checkout the function wrappers, you may want to copy-paste them! + * + * 8. It's recommended to validate the produced |edgesTex| and |blendTex|. + * You can use a screenshot from your engine to compare the |edgesTex| + * and |blendTex| produced inside of the engine with the results obtained + * with the reference demo. + * + * 9. After you get the last pass to work, it's time to optimize. You'll have + * to initialize a stencil buffer in the first pass (discard is already in + * the code), then mask execution by using it the second pass. The last + * pass should be executed in all pixels. + * + * + * After this point you can choose to enable predicated thresholding, + * temporal supersampling and motion blur integration: + * + * a) If you want to use predicated thresholding, take a look into + * SMAA_PREDICATION; you'll need to pass an extra texture in the edge + * detection pass. + * + * b) If you want to enable temporal supersampling (SMAA T2x): + * + * 1. The first step is to render using subpixel jitters. I won't go into + * detail, but it's as simple as moving each vertex position in the + * vertex shader, you can check how we do it in our DX10 demo. + * + * 2. Then, you must setup the temporal resolve. You may want to take a look + * into SMAAResolve for resolving 2x modes. After you get it working, you'll + * probably see ghosting everywhere. But fear not, you can enable the + * CryENGINE temporal reprojection by setting the SMAA_REPROJECTION macro. + * Check out SMAA_DECODE_VELOCITY if your velocity buffer is encoded. + * + * 3. The next step is to apply SMAA to each subpixel jittered frame, just as + * done for 1x. + * + * 4. At this point you should already have something usable, but for best + * results the proper area textures must be set depending on current jitter. + * For this, the parameter 'subsampleIndices' of + * 'SMAABlendingWeightCalculationPS' must be set as follows, for our T2x + * mode: + * + * @SUBSAMPLE_INDICES + * + * | S# | Camera Jitter | subsampleIndices | + * +----+------------------+---------------------+ + * | 0 | ( 0.25, -0.25) | float4(1, 1, 1, 0) | + * | 1 | (-0.25, 0.25) | float4(2, 2, 2, 0) | + * + * These jitter positions assume a bottom-to-top y axis. S# stands for the + * sample number. + * + * More information about temporal supersampling here: + * http://iryoku.com/aacourse/downloads/13-Anti-Aliasing-Methods-in-CryENGINE-3.pdf + * + * c) If you want to enable spatial multisampling (SMAA S2x): + * + * 1. The scene must be rendered using MSAA 2x. The MSAA 2x buffer must be + * created with: + * - DX10: see below (*) + * - DX10.1: D3D10_STANDARD_MULTISAMPLE_PATTERN or + * - DX11: D3D11_STANDARD_MULTISAMPLE_PATTERN + * + * This allows to ensure that the subsample order matches the table in + * @SUBSAMPLE_INDICES. + * + * (*) In the case of DX10, we refer the reader to: + * - SMAA::detectMSAAOrder and + * - SMAA::msaaReorder + * + * These functions allow to match the standard multisample patterns by + * detecting the subsample order for a specific GPU, and reordering + * them appropriately. + * + * 2. A shader must be run to output each subsample into a separate buffer + * (DX10 is required). You can use SMAASeparate for this purpose, or just do + * it in an existing pass (for example, in the tone mapping pass, which has + * the advantage of feeding tone mapped subsamples to SMAA, which will yield + * better results). + * + * 3. The full SMAA 1x pipeline must be run for each separated buffer, storing + * the results in the final buffer. The second run should alpha blend with + * the existing final buffer using a blending factor of 0.5. + * 'subsampleIndices' must be adjusted as in the SMAA T2x case (see point + * b). + * + * d) If you want to enable temporal supersampling on top of SMAA S2x + * (which actually is SMAA 4x): + * + * 1. SMAA 4x consists on temporally jittering SMAA S2x, so the first step is + * to calculate SMAA S2x for current frame. In this case, 'subsampleIndices' + * must be set as follows: + * + * | F# | S# | Camera Jitter | Net Jitter | subsampleIndices | + * +----+----+--------------------+-------------------+----------------------+ + * | 0 | 0 | ( 0.125, 0.125) | ( 0.375, -0.125) | float4(5, 3, 1, 3) | + * | 0 | 1 | ( 0.125, 0.125) | (-0.125, 0.375) | float4(4, 6, 2, 3) | + * +----+----+--------------------+-------------------+----------------------+ + * | 1 | 2 | (-0.125, -0.125) | ( 0.125, -0.375) | float4(3, 5, 1, 4) | + * | 1 | 3 | (-0.125, -0.125) | (-0.375, 0.125) | float4(6, 4, 2, 4) | + * + * These jitter positions assume a bottom-to-top y axis. F# stands for the + * frame number. S# stands for the sample number. + * + * 2. After calculating SMAA S2x for current frame (with the new subsample + * indices), previous frame must be reprojected as in SMAA T2x mode (see + * point b). + * + * e) If motion blur is used, you may want to do the edge detection pass + * together with motion blur. This has two advantages: + * + * 1. Pixels under heavy motion can be omitted from the edge detection process. + * For these pixels we can just store "no edge", as motion blur will take + * care of them. + * 2. The center pixel tap is reused. + * + * Note that in this case depth testing should be used instead of stenciling, + * as we have to write all the pixels in the motion blur pass. + * + * That's it! + */ + +//----------------------------------------------------------------------------- +// SMAA Presets + +/** + * Note that if you use one of these presets, the following configuration + * macros will be ignored if set in the "Configurable Defines" section. + */ + +#if defined(SMAA_PRESET_LOW) +#define SMAA_THRESHOLD 0.15 +#define SMAA_MAX_SEARCH_STEPS 4 +#define SMAA_DISABLE_DIAG_DETECTION +#define SMAA_DISABLE_CORNER_DETECTION +#elif defined(SMAA_PRESET_MEDIUM) +#define SMAA_THRESHOLD 0.1 +#define SMAA_MAX_SEARCH_STEPS 8 +#define SMAA_DISABLE_DIAG_DETECTION +#define SMAA_DISABLE_CORNER_DETECTION +#elif defined(SMAA_PRESET_HIGH) +#define SMAA_THRESHOLD 0.1 +#define SMAA_MAX_SEARCH_STEPS 16 +#define SMAA_MAX_SEARCH_STEPS_DIAG 8 +#define SMAA_CORNER_ROUNDING 25 +#elif defined(SMAA_PRESET_ULTRA) +#define SMAA_THRESHOLD 0.05 +#define SMAA_MAX_SEARCH_STEPS 32 +#define SMAA_MAX_SEARCH_STEPS_DIAG 16 +#define SMAA_CORNER_ROUNDING 25 +#endif + +//----------------------------------------------------------------------------- +// Configurable Defines + +/** + * SMAA_THRESHOLD specifies the threshold or sensitivity to edges. + * Lowering this value you will be able to detect more edges at the expense of + * performance. + * + * Range: [0, 0.5] + * 0.1 is a reasonable value, and allows to catch most visible edges. + * 0.05 is a rather overkill value, that allows to catch 'em all. + * + * If temporal supersampling is used, 0.2 could be a reasonable value, as low + * contrast edges are properly filtered by just 2x. + */ +#ifndef SMAA_THRESHOLD +#define SMAA_THRESHOLD 0.1 +#endif + +/** + * SMAA_DEPTH_THRESHOLD specifies the threshold for depth edge detection. + * + * Range: depends on the depth range of the scene. + */ +#ifndef SMAA_DEPTH_THRESHOLD +#define SMAA_DEPTH_THRESHOLD (0.1 * SMAA_THRESHOLD) +#endif + +/** + * SMAA_MAX_SEARCH_STEPS specifies the maximum steps performed in the + * horizontal/vertical pattern searches, at each side of the pixel. + * + * In number of pixels, it's actually the double. So the maximum line length + * perfectly handled by, for example 16, is 64 (by perfectly, we meant that + * longer lines won't look as good, but still antialiased). + * + * Range: [0, 112] + */ +#ifndef SMAA_MAX_SEARCH_STEPS +#define SMAA_MAX_SEARCH_STEPS 16 +#endif + +/** + * SMAA_MAX_SEARCH_STEPS_DIAG specifies the maximum steps performed in the + * diagonal pattern searches, at each side of the pixel. In this case we jump + * one pixel at time, instead of two. + * + * Range: [0, 20] + * + * On high-end machines it is cheap (between a 0.8x and 0.9x slower for 16 + * steps), but it can have a significant impact on older machines. + * + * Define SMAA_DISABLE_DIAG_DETECTION to disable diagonal processing. + */ +#ifndef SMAA_MAX_SEARCH_STEPS_DIAG +#define SMAA_MAX_SEARCH_STEPS_DIAG 8 +#endif + +/** + * SMAA_CORNER_ROUNDING specifies how much sharp corners will be rounded. + * + * Range: [0, 100] + * + * Define SMAA_DISABLE_CORNER_DETECTION to disable corner processing. + */ +#ifndef SMAA_CORNER_ROUNDING +#define SMAA_CORNER_ROUNDING 25 +#endif + +/** + * If there is an neighbor edge that has SMAA_LOCAL_CONTRAST_FACTOR times + * bigger contrast than current edge, current edge will be discarded. + * + * This allows to eliminate spurious crossing edges, and is based on the fact + * that, if there is too much contrast in a direction, that will hide + * perceptually contrast in the other neighbors. + */ +#ifndef SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR +#define SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR 2.0 +#endif + +/** + * Predicated thresholding allows to better preserve texture details and to + * improve performance, by decreasing the number of detected edges using an + * additional buffer like the light accumulation buffer, object ids or even the + * depth buffer (the depth buffer usage may be limited to indoor or short range + * scenes). + * + * It locally decreases the luma or color threshold if an edge is found in an + * additional buffer (so the global threshold can be higher). + * + * This method was developed by Playstation EDGE MLAA team, and used in + * Killzone 3, by using the light accumulation buffer. More information here: + * http://iryoku.com/aacourse/downloads/06-MLAA-on-PS3.pptx + */ +#ifndef SMAA_PREDICATION +#define SMAA_PREDICATION 0 +#endif + +/** + * Threshold to be used in the additional predication buffer. + * + * Range: depends on the input, so you'll have to find the magic number that + * works for you. + */ +#ifndef SMAA_PREDICATION_THRESHOLD +#define SMAA_PREDICATION_THRESHOLD 0.01 +#endif + +/** + * How much to scale the global threshold used for luma or color edge + * detection when using predication. + * + * Range: [1, 5] + */ +#ifndef SMAA_PREDICATION_SCALE +#define SMAA_PREDICATION_SCALE 2.0 +#endif + +/** + * How much to locally decrease the threshold. + * + * Range: [0, 1] + */ +#ifndef SMAA_PREDICATION_STRENGTH +#define SMAA_PREDICATION_STRENGTH 0.4 +#endif + +/** + * Temporal reprojection allows to remove ghosting artifacts when using + * temporal supersampling. We use the CryEngine 3 method which also introduces + * velocity weighting. This feature is of extreme importance for totally + * removing ghosting. More information here: + * http://iryoku.com/aacourse/downloads/13-Anti-Aliasing-Methods-in-CryENGINE-3.pdf + * + * Note that you'll need to setup a velocity buffer for enabling reprojection. + * For static geometry, saving the previous depth buffer is a viable + * alternative. + */ +#ifndef SMAA_REPROJECTION +#define SMAA_REPROJECTION 0 +#endif + +/** + * SMAA_REPROJECTION_WEIGHT_SCALE controls the velocity weighting. It allows to + * remove ghosting trails behind the moving object, which are not removed by + * just using reprojection. Using low values will exhibit ghosting, while using + * high values will disable temporal supersampling under motion. + * + * Behind the scenes, velocity weighting removes temporal supersampling when + * the velocity of the subsamples differs (meaning they are different objects). + * + * Range: [0, 80] + */ +#ifndef SMAA_REPROJECTION_WEIGHT_SCALE +#define SMAA_REPROJECTION_WEIGHT_SCALE 30.0 +#endif + +/** + * On some compilers, discard cannot be used in vertex shaders. Thus, they need + * to be compiled separately. + */ +#ifndef SMAA_INCLUDE_VS +#define SMAA_INCLUDE_VS 1 +#endif +#ifndef SMAA_INCLUDE_PS +#define SMAA_INCLUDE_PS 1 +#endif + +//----------------------------------------------------------------------------- +// Texture Access Defines + +#ifndef SMAA_AREATEX_SELECT +#if defined(SMAA_HLSL_3) +#define SMAA_AREATEX_SELECT(sample) sample.ra +#else +#define SMAA_AREATEX_SELECT(sample) sample.rg +#endif +#endif + +#ifndef SMAA_SEARCHTEX_SELECT +#define SMAA_SEARCHTEX_SELECT(sample) sample.r +#endif + +#ifndef SMAA_DECODE_VELOCITY +#define SMAA_DECODE_VELOCITY(sample) sample.rg +#endif + +//----------------------------------------------------------------------------- +// Non-Configurable Defines + +#define SMAA_AREATEX_MAX_DISTANCE 16 +#define SMAA_AREATEX_MAX_DISTANCE_DIAG 20 +#define SMAA_AREATEX_PIXEL_SIZE (1.0 / float2(160.0, 560.0)) +#define SMAA_AREATEX_SUBTEX_SIZE (1.0 / 7.0) +#define SMAA_SEARCHTEX_SIZE float2(66.0, 33.0) +#define SMAA_SEARCHTEX_PACKED_SIZE float2(64.0, 16.0) +#define SMAA_CORNER_ROUNDING_NORM (float(SMAA_CORNER_ROUNDING) / 100.0) + +//----------------------------------------------------------------------------- +// Porting Functions + +#if defined(SMAA_HLSL_3) +#define SMAATexture2D(tex) sampler2D tex +#define SMAATexturePass2D(tex) tex +#define SMAASampleLevelZero(tex, coord) tex2Dlod(tex, float4(coord, 0.0, 0.0)) +#define SMAASampleLevelZeroPoint(tex, coord) tex2Dlod(tex, float4(coord, 0.0, 0.0)) +#define SMAASampleLevelZeroOffset(tex, coord, offset) tex2Dlod(tex, float4(coord + offset * SMAA_RT_METRICS.xy, 0.0, 0.0)) +#define SMAASample(tex, coord) tex2D(tex, coord) +#define SMAASamplePoint(tex, coord) tex2D(tex, coord) +#define SMAASampleOffset(tex, coord, offset) tex2D(tex, coord + offset * SMAA_RT_METRICS.xy) +#define SMAA_FLATTEN [flatten] +#define SMAA_BRANCH [branch] +#endif +#if defined(SMAA_HLSL_4) || defined(SMAA_HLSL_4_1) +SamplerState LinearSampler { Filter = MIN_MAG_LINEAR_MIP_POINT; AddressU = Clamp; AddressV = Clamp; }; +SamplerState PointSampler { Filter = MIN_MAG_MIP_POINT; AddressU = Clamp; AddressV = Clamp; }; +#define SMAATexture2D(tex) Texture2D tex +#define SMAATexturePass2D(tex) tex +#define SMAASampleLevelZero(tex, coord) tex.SampleLevel(LinearSampler, coord, 0) +#define SMAASampleLevelZeroPoint(tex, coord) tex.SampleLevel(PointSampler, coord, 0) +#define SMAASampleLevelZeroOffset(tex, coord, offset) tex.SampleLevel(LinearSampler, coord, 0, offset) +#define SMAASample(tex, coord) tex.Sample(LinearSampler, coord) +#define SMAASamplePoint(tex, coord) tex.Sample(PointSampler, coord) +#define SMAASampleOffset(tex, coord, offset) tex.Sample(LinearSampler, coord, offset) +#define SMAA_FLATTEN [flatten] +#define SMAA_BRANCH [branch] +#define SMAATexture2DMS2(tex) Texture2DMS tex +#define SMAALoad(tex, pos, sample) tex.Load(pos, sample) +#if defined(SMAA_HLSL_4_1) +#define SMAAGather(tex, coord) tex.Gather(LinearSampler, coord, 0) +#endif +#endif +#if defined(SMAA_GLSL_3) || defined(SMAA_GLSL_4) +#define SMAATexture2D(tex) sampler2D tex +#define SMAATexturePass2D(tex) tex +#define SMAASampleLevelZero(tex, coord) textureLod(tex, coord, 0.0) +#define SMAASampleLevelZeroPoint(tex, coord) textureLod(tex, coord, 0.0) +#define SMAASampleLevelZeroOffset(tex, coord, offset) textureLodOffset(tex, coord, 0.0, offset) +#define SMAASample(tex, coord) texture(tex, coord) +#define SMAASamplePoint(tex, coord) texture(tex, coord) +#define SMAASampleOffset(tex, coord, offset) texture(tex, coord, offset) +#define SMAA_FLATTEN +#define SMAA_BRANCH +#define lerp(a, b, t) mix(a, b, t) +#define saturate(a) clamp(a, 0.0, 1.0) +#if defined(SMAA_GLSL_4) +#define mad(a, b, c) fma(a, b, c) +#define SMAAGather(tex, coord) textureGather(tex, coord) +#else +#define mad(a, b, c) (a * b + c) +#endif +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 +#define int2 ivec2 +#define int3 ivec3 +#define int4 ivec4 +#define bool2 bvec2 +#define bool3 bvec3 +#define bool4 bvec4 +#endif + +#if !defined(SMAA_HLSL_3) && !defined(SMAA_HLSL_4) && !defined(SMAA_HLSL_4_1) && !defined(SMAA_GLSL_3) && !defined(SMAA_GLSL_4) && !defined(SMAA_CUSTOM_SL) +#error you must define the shading language: SMAA_HLSL_*, SMAA_GLSL_* or SMAA_CUSTOM_SL +#endif + +//----------------------------------------------------------------------------- +// Misc functions + +/** + * Gathers current pixel, and the top-left neighbors. + */ +float3 SMAAGatherNeighbours(float2 texcoord, + float4 offset[3], + SMAATexture2D(tex)) { + #ifdef SMAAGather + return SMAAGather(tex, texcoord + SMAA_RT_METRICS.xy * float2(-0.5, -0.5)).grb; + #else + float P = SMAASamplePoint(tex, texcoord).r; + float Pleft = SMAASamplePoint(tex, offset[0].xy).r; + float Ptop = SMAASamplePoint(tex, offset[0].zw).r; + return float3(P, Pleft, Ptop); + #endif +} + +/** + * Adjusts the threshold by means of predication. + */ +float2 SMAACalculatePredicatedThreshold(float2 texcoord, + float4 offset[3], + SMAATexture2D(predicationTex)) { + float3 neighbours = SMAAGatherNeighbours(texcoord, offset, SMAATexturePass2D(predicationTex)); + float2 delta = abs(neighbours.xx - neighbours.yz); + float2 edges = step(SMAA_PREDICATION_THRESHOLD, delta); + return SMAA_PREDICATION_SCALE * SMAA_THRESHOLD * (1.0 - SMAA_PREDICATION_STRENGTH * edges); +} + +/** + * Conditional move: + */ +void SMAAMovc(bool2 cond, inout float2 variable, float2 value) { + SMAA_FLATTEN if (cond.x) variable.x = value.x; + SMAA_FLATTEN if (cond.y) variable.y = value.y; +} + +void SMAAMovc(bool4 cond, inout float4 variable, float4 value) { + SMAAMovc(cond.xy, variable.xy, value.xy); + SMAAMovc(cond.zw, variable.zw, value.zw); +} + + +#if SMAA_INCLUDE_VS +//----------------------------------------------------------------------------- +// Vertex Shaders + +/** + * Edge Detection Vertex Shader + */ +void SMAAEdgeDetectionVS(float2 texcoord, + out float4 offset[3]) { + offset[0] = mad(SMAA_RT_METRICS.xyxy, float4(-1.0, 0.0, 0.0, -1.0), texcoord.xyxy); + offset[1] = mad(SMAA_RT_METRICS.xyxy, float4( 1.0, 0.0, 0.0, 1.0), texcoord.xyxy); + offset[2] = mad(SMAA_RT_METRICS.xyxy, float4(-2.0, 0.0, 0.0, -2.0), texcoord.xyxy); +} + +/** + * Blend Weight Calculation Vertex Shader + */ +void SMAABlendingWeightCalculationVS(float2 texcoord, + out float2 pixcoord, + out float4 offset[3]) { + pixcoord = texcoord * SMAA_RT_METRICS.zw; + + // We will use these offsets for the searches later on (see @PSEUDO_GATHER4): + offset[0] = mad(SMAA_RT_METRICS.xyxy, float4(-0.25, -0.125, 1.25, -0.125), texcoord.xyxy); + offset[1] = mad(SMAA_RT_METRICS.xyxy, float4(-0.125, -0.25, -0.125, 1.25), texcoord.xyxy); + + // And these for the searches, they indicate the ends of the loops: + offset[2] = mad(SMAA_RT_METRICS.xxyy, + float4(-2.0, 2.0, -2.0, 2.0) * float(SMAA_MAX_SEARCH_STEPS), + float4(offset[0].xz, offset[1].yw)); +} + +/** + * Neighborhood Blending Vertex Shader + */ +void SMAANeighborhoodBlendingVS(float2 texcoord, + out float4 offset) { + offset = mad(SMAA_RT_METRICS.xyxy, float4( 1.0, 0.0, 0.0, 1.0), texcoord.xyxy); +} +#endif // SMAA_INCLUDE_VS + +#if SMAA_INCLUDE_PS +//----------------------------------------------------------------------------- +// Edge Detection Pixel Shaders (First Pass) + +/** + * Luma Edge Detection + * + * IMPORTANT NOTICE: luma edge detection requires gamma-corrected colors, and + * thus 'colorTex' should be a non-sRGB texture. + */ +float2 SMAALumaEdgeDetectionPS(float2 texcoord, + float4 offset[3], + SMAATexture2D(colorTex) + #if SMAA_PREDICATION + , SMAATexture2D(predicationTex) + #endif + ) { + // Calculate the threshold: + #if SMAA_PREDICATION + float2 threshold = SMAACalculatePredicatedThreshold(texcoord, offset, SMAATexturePass2D(predicationTex)); + #else + float2 threshold = float2(SMAA_THRESHOLD, SMAA_THRESHOLD); + #endif + + // Calculate lumas: + float3 weights = float3(0.2126, 0.7152, 0.0722); + float L = dot(SMAASamplePoint(colorTex, texcoord).rgb, weights); + + float Lleft = dot(SMAASamplePoint(colorTex, offset[0].xy).rgb, weights); + float Ltop = dot(SMAASamplePoint(colorTex, offset[0].zw).rgb, weights); + + // We do the usual threshold: + float4 delta; + delta.xy = abs(L - float2(Lleft, Ltop)); + float2 edges = step(threshold, delta.xy); + + // Then discard if there is no edge: + if (dot(edges, float2(1.0, 1.0)) == 0.0) + return float2(-2.0, -2.0); + + // Calculate right and bottom deltas: + float Lright = dot(SMAASamplePoint(colorTex, offset[1].xy).rgb, weights); + float Lbottom = dot(SMAASamplePoint(colorTex, offset[1].zw).rgb, weights); + delta.zw = abs(L - float2(Lright, Lbottom)); + + // Calculate the maximum delta in the direct neighborhood: + float2 maxDelta = max(delta.xy, delta.zw); + + // Calculate left-left and top-top deltas: + float Lleftleft = dot(SMAASamplePoint(colorTex, offset[2].xy).rgb, weights); + float Ltoptop = dot(SMAASamplePoint(colorTex, offset[2].zw).rgb, weights); + delta.zw = abs(float2(Lleft, Ltop) - float2(Lleftleft, Ltoptop)); + + // Calculate the final maximum delta: + maxDelta = max(maxDelta.xy, delta.zw); + float finalDelta = max(maxDelta.x, maxDelta.y); + + // Local contrast adaptation: + edges.xy *= step(finalDelta, SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR * delta.xy); + + return edges; +} + +/** + * Color Edge Detection + * + * IMPORTANT NOTICE: color edge detection requires gamma-corrected colors, and + * thus 'colorTex' should be a non-sRGB texture. + */ +float2 SMAAColorEdgeDetectionPS(float2 texcoord, + float4 offset[3], + SMAATexture2D(colorTex) + #if SMAA_PREDICATION + , SMAATexture2D(predicationTex) + #endif + ) { + // Calculate the threshold: + #if SMAA_PREDICATION + float2 threshold = SMAACalculatePredicatedThreshold(texcoord, offset, predicationTex); + #else + float2 threshold = float2(SMAA_THRESHOLD, SMAA_THRESHOLD); + #endif + + // Calculate color deltas: + float4 delta; + float3 C = SMAASamplePoint(colorTex, texcoord).rgb; + + float3 Cleft = SMAASamplePoint(colorTex, offset[0].xy).rgb; + float3 t = abs(C - Cleft); + delta.x = max(max(t.r, t.g), t.b); + + float3 Ctop = SMAASamplePoint(colorTex, offset[0].zw).rgb; + t = abs(C - Ctop); + delta.y = max(max(t.r, t.g), t.b); + + // We do the usual threshold: + float2 edges = step(threshold, delta.xy); + + // Then discard if there is no edge: + if (dot(edges, float2(1.0, 1.0)) == 0.0) + return float2(-2.0, -2.0); + + // Calculate right and bottom deltas: + float3 Cright = SMAASamplePoint(colorTex, offset[1].xy).rgb; + t = abs(C - Cright); + delta.z = max(max(t.r, t.g), t.b); + + float3 Cbottom = SMAASamplePoint(colorTex, offset[1].zw).rgb; + t = abs(C - Cbottom); + delta.w = max(max(t.r, t.g), t.b); + + // Calculate the maximum delta in the direct neighborhood: + float2 maxDelta = max(delta.xy, delta.zw); + + // Calculate left-left and top-top deltas: + float3 Cleftleft = SMAASamplePoint(colorTex, offset[2].xy).rgb; + t = abs(C - Cleftleft); + delta.z = max(max(t.r, t.g), t.b); + + float3 Ctoptop = SMAASamplePoint(colorTex, offset[2].zw).rgb; + t = abs(C - Ctoptop); + delta.w = max(max(t.r, t.g), t.b); + + // Calculate the final maximum delta: + maxDelta = max(maxDelta.xy, delta.zw); + float finalDelta = max(maxDelta.x, maxDelta.y); + + // Local contrast adaptation: + edges.xy *= step(finalDelta, SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR * delta.xy); + + return edges; +} + +/** + * Depth Edge Detection + */ +float2 SMAADepthEdgeDetectionPS(float2 texcoord, + float4 offset[3], + SMAATexture2D(depthTex)) { + float3 neighbours = SMAAGatherNeighbours(texcoord, offset, SMAATexturePass2D(depthTex)); + float2 delta = abs(neighbours.xx - float2(neighbours.y, neighbours.z)); + float2 edges = step(SMAA_DEPTH_THRESHOLD, delta); + + if (dot(edges, float2(1.0, 1.0)) == 0.0) + return float2(-2.0, -2.0); + + return edges; +} + +//----------------------------------------------------------------------------- +// Diagonal Search Functions + +#if !defined(SMAA_DISABLE_DIAG_DETECTION) + +/** + * Allows to decode two binary values from a bilinear-filtered access. + */ +float2 SMAADecodeDiagBilinearAccess(float2 e) { + // Bilinear access for fetching 'e' have a 0.25 offset, and we are + // interested in the R and G edges: + // + // +---G---+-------+ + // | x o R x | + // +-------+-------+ + // + // Then, if one of these edge is enabled: + // Red: (0.75 * X + 0.25 * 1) => 0.25 or 1.0 + // Green: (0.75 * 1 + 0.25 * X) => 0.75 or 1.0 + // + // This function will unpack the values (mad + mul + round): + // wolframalpha.com: round(x * abs(5 * x - 5 * 0.75)) plot 0 to 1 + e.r = e.r * abs(5.0 * e.r - 5.0 * 0.75); + return round(e); +} + +float4 SMAADecodeDiagBilinearAccess(float4 e) { + e.rb = e.rb * abs(5.0 * e.rb - 5.0 * 0.75); + return round(e); +} + +/** + * These functions allows to perform diagonal pattern searches. + */ +float2 SMAASearchDiag1(SMAATexture2D(edgesTex), float2 texcoord, float2 dir, out float2 e) { + float4 coord = float4(texcoord, -1.0, 1.0); + float3 t = float3(SMAA_RT_METRICS.xy, 1.0); + while (coord.z < float(SMAA_MAX_SEARCH_STEPS_DIAG - 1) && + coord.w > 0.9) { + coord.xyz = mad(t, float3(dir, 1.0), coord.xyz); + e = SMAASampleLevelZero(edgesTex, coord.xy).rg; + coord.w = dot(e, float2(0.5, 0.5)); + } + return coord.zw; +} + +float2 SMAASearchDiag2(SMAATexture2D(edgesTex), float2 texcoord, float2 dir, out float2 e) { + float4 coord = float4(texcoord, -1.0, 1.0); + coord.x += 0.25 * SMAA_RT_METRICS.x; // See @SearchDiag2Optimization + float3 t = float3(SMAA_RT_METRICS.xy, 1.0); + while (coord.z < float(SMAA_MAX_SEARCH_STEPS_DIAG - 1) && + coord.w > 0.9) { + coord.xyz = mad(t, float3(dir, 1.0), coord.xyz); + + // @SearchDiag2Optimization + // Fetch both edges at once using bilinear filtering: + e = SMAASampleLevelZero(edgesTex, coord.xy).rg; + e = SMAADecodeDiagBilinearAccess(e); + + // Non-optimized version: + // e.g = SMAASampleLevelZero(edgesTex, coord.xy).g; + // e.r = SMAASampleLevelZeroOffset(edgesTex, coord.xy, int2(1, 0)).r; + + coord.w = dot(e, float2(0.5, 0.5)); + } + return coord.zw; +} + +/** + * Similar to SMAAArea, this calculates the area corresponding to a certain + * diagonal distance and crossing edges 'e'. + */ +float2 SMAAAreaDiag(SMAATexture2D(areaTex), float2 dist, float2 e, float offset) { + float2 texcoord = mad(float2(SMAA_AREATEX_MAX_DISTANCE_DIAG, SMAA_AREATEX_MAX_DISTANCE_DIAG), e, dist); + + // We do a scale and bias for mapping to texel space: + texcoord = mad(SMAA_AREATEX_PIXEL_SIZE, texcoord, 0.5 * SMAA_AREATEX_PIXEL_SIZE); + + // Diagonal areas are on the second half of the texture: + texcoord.x += 0.5; + + // Move to proper place, according to the subpixel offset: + texcoord.y += SMAA_AREATEX_SUBTEX_SIZE * offset; + + // Do it! + return SMAA_AREATEX_SELECT(SMAASampleLevelZero(areaTex, texcoord)); +} + +/** + * This searches for diagonal patterns and returns the corresponding weights. + */ +float2 SMAACalculateDiagWeights(SMAATexture2D(edgesTex), SMAATexture2D(areaTex), float2 texcoord, float2 e, float4 subsampleIndices) { + float2 weights = float2(0.0, 0.0); + + // Search for the line ends: + float4 d; + float2 end; + if (e.r > 0.0) { + d.xz = SMAASearchDiag1(SMAATexturePass2D(edgesTex), texcoord, float2(-1.0, 1.0), end); + d.x += float(end.y > 0.9); + } else + d.xz = float2(0.0, 0.0); + d.yw = SMAASearchDiag1(SMAATexturePass2D(edgesTex), texcoord, float2(1.0, -1.0), end); + + SMAA_BRANCH + if (d.x + d.y > 2.0) { // d.x + d.y + 1 > 3 + // Fetch the crossing edges: + float4 coords = mad(float4(-d.x + 0.25, d.x, d.y, -d.y - 0.25), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + float4 c; + c.xy = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).rg; + c.zw = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).rg; + c.yxwz = SMAADecodeDiagBilinearAccess(c.xyzw); + + // Non-optimized version: + // float4 coords = mad(float4(-d.x, d.x, d.y, -d.y), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + // float4 c; + // c.x = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).g; + // c.y = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2( 0, 0)).r; + // c.z = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).g; + // c.w = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, -1)).r; + + // Merge crossing edges at each side into a single value: + float2 cc = mad(float2(2.0, 2.0), c.xz, c.yw); + + // Remove the crossing edge if we didn't found the end of the line: + SMAAMovc(bool2(step(0.9, d.zw)), cc, float2(0.0, 0.0)); + + // Fetch the areas for this line: + weights += SMAAAreaDiag(SMAATexturePass2D(areaTex), d.xy, cc, subsampleIndices.z); + } + + // Search for the line ends: + d.xz = SMAASearchDiag2(SMAATexturePass2D(edgesTex), texcoord, float2(-1.0, -1.0), end); + if (SMAASampleLevelZeroOffset(edgesTex, texcoord, int2(1, 0)).r > 0.0) { + d.yw = SMAASearchDiag2(SMAATexturePass2D(edgesTex), texcoord, float2(1.0, 1.0), end); + d.y += float(end.y > 0.9); + } else + d.yw = float2(0.0, 0.0); + + SMAA_BRANCH + if (d.x + d.y > 2.0) { // d.x + d.y + 1 > 3 + // Fetch the crossing edges: + float4 coords = mad(float4(-d.x, -d.x, d.y, d.y), SMAA_RT_METRICS.xyxy, texcoord.xyxy); + float4 c; + c.x = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).g; + c.y = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2( 0, -1)).r; + c.zw = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).gr; + float2 cc = mad(float2(2.0, 2.0), c.xz, c.yw); + + // Remove the crossing edge if we didn't found the end of the line: + SMAAMovc(bool2(step(0.9, d.zw)), cc, float2(0.0, 0.0)); + + // Fetch the areas for this line: + weights += SMAAAreaDiag(SMAATexturePass2D(areaTex), d.xy, cc, subsampleIndices.w).gr; + } + + return weights; +} +#endif + +//----------------------------------------------------------------------------- +// Horizontal/Vertical Search Functions + +/** + * This allows to determine how much length should we add in the last step + * of the searches. It takes the bilinearly interpolated edge (see + * @PSEUDO_GATHER4), and adds 0, 1 or 2, depending on which edges and + * crossing edges are active. + */ +float SMAASearchLength(SMAATexture2D(searchTex), float2 e, float offset) { + // The texture is flipped vertically, with left and right cases taking half + // of the space horizontally: + float2 scale = SMAA_SEARCHTEX_SIZE * float2(0.5, -1.0); + float2 bias = SMAA_SEARCHTEX_SIZE * float2(offset, 1.0); + + // Scale and bias to access texel centers: + scale += float2(-1.0, 1.0); + bias += float2( 0.5, -0.5); + + // Convert from pixel coordinates to texcoords: + // (We use SMAA_SEARCHTEX_PACKED_SIZE because the texture is cropped) + scale *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; + bias *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; + + // Lookup the search texture: + return SMAA_SEARCHTEX_SELECT(SMAASampleLevelZero(searchTex, mad(scale, e, bias))); +} + +/** + * Horizontal/vertical search functions for the 2nd pass. + */ +float SMAASearchXLeft(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + /** + * @PSEUDO_GATHER4 + * This texcoord has been offset by (-0.25, -0.125) in the vertex shader to + * sample between edge, thus fetching four edges in a row. + * Sampling with different offsets in each direction allows to disambiguate + * which edges are active from the four fetched ones. + */ + float2 e = float2(0.0, 1.0); + while (texcoord.x > end && + e.g > 0.8281 && // Is there some edge not activated? + e.r == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(-float2(2.0, 0.0), SMAA_RT_METRICS.xy, texcoord); + } + + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.0), 3.25); + return mad(SMAA_RT_METRICS.x, offset, texcoord.x); + + // Non-optimized version: + // We correct the previous (-0.25, -0.125) offset we applied: + // texcoord.x += 0.25 * SMAA_RT_METRICS.x; + + // The searches are bias by 1, so adjust the coords accordingly: + // texcoord.x += SMAA_RT_METRICS.x; + + // Disambiguate the length added by the last step: + // texcoord.x += 2.0 * SMAA_RT_METRICS.x; // Undo last step + // texcoord.x -= SMAA_RT_METRICS.x * (255.0 / 127.0) * SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.0); + // return mad(SMAA_RT_METRICS.x, offset, texcoord.x); +} + +float SMAASearchXRight(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(0.0, 1.0); + while (texcoord.x < end && + e.g > 0.8281 && // Is there some edge not activated? + e.r == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(float2(2.0, 0.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.5), 3.25); + return mad(-SMAA_RT_METRICS.x, offset, texcoord.x); +} + +float SMAASearchYUp(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(1.0, 0.0); + while (texcoord.y > end && + e.r > 0.8281 && // Is there some edge not activated? + e.g == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(-float2(0.0, 2.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e.gr, 0.0), 3.25); + return mad(SMAA_RT_METRICS.y, offset, texcoord.y); +} + +float SMAASearchYDown(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { + float2 e = float2(1.0, 0.0); + while (texcoord.y < end && + e.r > 0.8281 && // Is there some edge not activated? + e.g == 0.0) { // Or is there a crossing edge that breaks the line? + e = SMAASampleLevelZero(edgesTex, texcoord).rg; + texcoord = mad(float2(0.0, 2.0), SMAA_RT_METRICS.xy, texcoord); + } + float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e.gr, 0.5), 3.25); + return mad(-SMAA_RT_METRICS.y, offset, texcoord.y); +} + +/** + * Ok, we have the distance and both crossing edges. So, what are the areas + * at each side of current edge? + */ +float2 SMAAArea(SMAATexture2D(areaTex), float2 dist, float e1, float e2, float offset) { + // Rounding prevents precision errors of bilinear filtering: + float2 texcoord = mad(float2(SMAA_AREATEX_MAX_DISTANCE, SMAA_AREATEX_MAX_DISTANCE), round(4.0 * float2(e1, e2)), dist); + + // We do a scale and bias for mapping to texel space: + texcoord = mad(SMAA_AREATEX_PIXEL_SIZE, texcoord, 0.5 * SMAA_AREATEX_PIXEL_SIZE); + + // Move to proper place, according to the subpixel offset: + texcoord.y = mad(SMAA_AREATEX_SUBTEX_SIZE, offset, texcoord.y); + + // Do it! + return SMAA_AREATEX_SELECT(SMAASampleLevelZero(areaTex, texcoord)); +} + +//----------------------------------------------------------------------------- +// Corner Detection Functions + +void SMAADetectHorizontalCornerPattern(SMAATexture2D(edgesTex), inout float2 weights, float4 texcoord, float2 d) { + #if !defined(SMAA_DISABLE_CORNER_DETECTION) + float2 leftRight = step(d.xy, d.yx); + float2 rounding = (1.0 - SMAA_CORNER_ROUNDING_NORM) * leftRight; + + rounding /= leftRight.x + leftRight.y; // Reduce blending for pixels in the center of a line. + + float2 factor = float2(1.0, 1.0); + factor.x -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(0, 1)).r; + factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(1, 1)).r; + factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(0, -2)).r; + factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(1, -2)).r; + + weights *= saturate(factor); + #endif +} + +void SMAADetectVerticalCornerPattern(SMAATexture2D(edgesTex), inout float2 weights, float4 texcoord, float2 d) { + #if !defined(SMAA_DISABLE_CORNER_DETECTION) + float2 leftRight = step(d.xy, d.yx); + float2 rounding = (1.0 - SMAA_CORNER_ROUNDING_NORM) * leftRight; + + rounding /= leftRight.x + leftRight.y; + + float2 factor = float2(1.0, 1.0); + factor.x -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2( 1, 0)).g; + factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2( 1, 1)).g; + factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(-2, 0)).g; + factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(-2, 1)).g; + + weights *= saturate(factor); + #endif +} + +//----------------------------------------------------------------------------- +// Blending Weight Calculation Pixel Shader (Second Pass) + +float4 SMAABlendingWeightCalculationPS(float2 texcoord, + float2 pixcoord, + float4 offset[3], + SMAATexture2D(edgesTex), + SMAATexture2D(areaTex), + SMAATexture2D(searchTex), + float4 subsampleIndices) { // Just pass zero for SMAA 1x, see @SUBSAMPLE_INDICES. + float4 weights = float4(0.0, 0.0, 0.0, 0.0); + + float2 e = SMAASample(edgesTex, texcoord).rg; + + SMAA_BRANCH + if (e.g > 0.0) { // Edge at north + #if !defined(SMAA_DISABLE_DIAG_DETECTION) + // Diagonals have both north and west edges, so searching for them in + // one of the boundaries is enough. + weights.rg = SMAACalculateDiagWeights(SMAATexturePass2D(edgesTex), SMAATexturePass2D(areaTex), texcoord, e, subsampleIndices); + + // We give priority to diagonals, so if we find a diagonal we skip + // horizontal/vertical processing. + SMAA_BRANCH + if (weights.r == -weights.g) { // weights.r + weights.g == 0.0 + #endif + + float2 d; + + // Find the distance to the left: + float3 coords; + coords.x = SMAASearchXLeft(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[0].xy, offset[2].x); + coords.y = offset[1].y; // offset[1].y = texcoord.y - 0.25 * SMAA_RT_METRICS.y (@CROSSING_OFFSET) + d.x = coords.x; + + // Now fetch the left crossing edges, two at a time using bilinear + // filtering. Sampling at -0.25 (see @CROSSING_OFFSET) enables to + // discern what value each edge has: + float e1 = SMAASampleLevelZero(edgesTex, coords.xy).r; + + // Find the distance to the right: + coords.z = SMAASearchXRight(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[0].zw, offset[2].y); + d.y = coords.z; + + // We want the distances to be in pixel units (doing this here allow to + // better interleave arithmetic and memory accesses): + d = abs(round(mad(SMAA_RT_METRICS.zz, d, -pixcoord.xx))); + + // SMAAArea below needs a sqrt, as the areas texture is compressed + // quadratically: + float2 sqrt_d = sqrt(d); + + // Fetch the right crossing edges: + float e2 = SMAASampleLevelZeroOffset(edgesTex, coords.zy, int2(1, 0)).r; + + // Ok, we know how this pattern looks like, now it is time for getting + // the actual area: + weights.rg = SMAAArea(SMAATexturePass2D(areaTex), sqrt_d, e1, e2, subsampleIndices.y); + + // Fix corners: + coords.y = texcoord.y; + SMAADetectHorizontalCornerPattern(SMAATexturePass2D(edgesTex), weights.rg, coords.xyzy, d); + + #if !defined(SMAA_DISABLE_DIAG_DETECTION) + } else + e.r = 0.0; // Skip vertical processing. + #endif + } + + SMAA_BRANCH + if (e.r > 0.0) { // Edge at west + float2 d; + + // Find the distance to the top: + float3 coords; + coords.y = SMAASearchYUp(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[1].xy, offset[2].z); + coords.x = offset[0].x; // offset[1].x = texcoord.x - 0.25 * SMAA_RT_METRICS.x; + d.x = coords.y; + + // Fetch the top crossing edges: + float e1 = SMAASampleLevelZero(edgesTex, coords.xy).g; + + // Find the distance to the bottom: + coords.z = SMAASearchYDown(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[1].zw, offset[2].w); + d.y = coords.z; + + // We want the distances to be in pixel units: + d = abs(round(mad(SMAA_RT_METRICS.ww, d, -pixcoord.yy))); + + // SMAAArea below needs a sqrt, as the areas texture is compressed + // quadratically: + float2 sqrt_d = sqrt(d); + + // Fetch the bottom crossing edges: + float e2 = SMAASampleLevelZeroOffset(edgesTex, coords.xz, int2(0, 1)).g; + + // Get the area for this direction: + weights.ba = SMAAArea(SMAATexturePass2D(areaTex), sqrt_d, e1, e2, subsampleIndices.x); + + // Fix corners: + coords.x = texcoord.x; + SMAADetectVerticalCornerPattern(SMAATexturePass2D(edgesTex), weights.ba, coords.xyxz, d); + } + + return weights; +} + +//----------------------------------------------------------------------------- +// Neighborhood Blending Pixel Shader (Third Pass) + +float4 SMAANeighborhoodBlendingPS(float2 texcoord, + float4 offset, + SMAATexture2D(colorTex), + SMAATexture2D(blendTex) + #if SMAA_REPROJECTION + , SMAATexture2D(velocityTex) + #endif + ) { + // Fetch the blending weights for current pixel: + float4 a; + a.x = SMAASample(blendTex, offset.xy).a; // Right + a.y = SMAASample(blendTex, offset.zw).g; // Top + a.wz = SMAASample(blendTex, texcoord).xz; // Bottom / Left + + // Is there any blending weight with a value greater than 0.0? + SMAA_BRANCH + if (dot(a, float4(1.0, 1.0, 1.0, 1.0)) < 1e-5) { + float4 color = SMAASampleLevelZero(colorTex, texcoord); + + #if SMAA_REPROJECTION + float2 velocity = SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, texcoord)); + + // Pack velocity into the alpha channel: + color.a = sqrt(5.0 * length(velocity)); + #endif + + return color; + } else { + bool h = max(a.x, a.z) > max(a.y, a.w); // max(horizontal) > max(vertical) + + // Calculate the blending offsets: + float4 blendingOffset = float4(0.0, a.y, 0.0, a.w); + float2 blendingWeight = a.yw; + SMAAMovc(bool4(h, h, h, h), blendingOffset, float4(a.x, 0.0, a.z, 0.0)); + SMAAMovc(bool2(h, h), blendingWeight, a.xz); + blendingWeight /= dot(blendingWeight, float2(1.0, 1.0)); + + // Calculate the texture coordinates: + float4 blendingCoord = mad(blendingOffset, float4(SMAA_RT_METRICS.xy, -SMAA_RT_METRICS.xy), texcoord.xyxy); + + // We exploit bilinear filtering to mix current pixel with the chosen + // neighbor: + float4 color = blendingWeight.x * SMAASampleLevelZero(colorTex, blendingCoord.xy); + color += blendingWeight.y * SMAASampleLevelZero(colorTex, blendingCoord.zw); + + #if SMAA_REPROJECTION + // Antialias velocity for proper reprojection in a later stage: + float2 velocity = blendingWeight.x * SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, blendingCoord.xy)); + velocity += blendingWeight.y * SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, blendingCoord.zw)); + + // Pack velocity into the alpha channel: + color.a = sqrt(5.0 * length(velocity)); + #endif + + return color; + } +} + +//----------------------------------------------------------------------------- +// Temporal Resolve Pixel Shader (Optional Pass) + +float4 SMAAResolvePS(float2 texcoord, + SMAATexture2D(currentColorTex), + SMAATexture2D(previousColorTex) + #if SMAA_REPROJECTION + , SMAATexture2D(velocityTex) + #endif + ) { + #if SMAA_REPROJECTION + // Velocity is assumed to be calculated for motion blur, so we need to + // inverse it for reprojection: + float2 velocity = -SMAA_DECODE_VELOCITY(SMAASamplePoint(velocityTex, texcoord).rg); + + // Fetch current pixel: + float4 current = SMAASamplePoint(currentColorTex, texcoord); + + // Reproject current coordinates and fetch previous pixel: + float4 previous = SMAASamplePoint(previousColorTex, texcoord + velocity); + + // Attenuate the previous pixel if the velocity is different: + float delta = abs(current.a * current.a - previous.a * previous.a) / 5.0; + float weight = 0.5 * saturate(1.0 - sqrt(delta) * SMAA_REPROJECTION_WEIGHT_SCALE); + + // Blend the pixels according to the calculated weight: + return lerp(current, previous, weight); + #else + // Just blend the pixels: + float4 current = SMAASamplePoint(currentColorTex, texcoord); + float4 previous = SMAASamplePoint(previousColorTex, texcoord); + return lerp(current, previous, 0.5); + #endif +} + +//----------------------------------------------------------------------------- +// Separate Multisamples Pixel Shader (Optional Pass) + +#ifdef SMAALoad +void SMAASeparatePS(float4 position, + float2 texcoord, + out float4 target0, + out float4 target1, + SMAATexture2DMS2(colorTexMS)) { + int2 pos = int2(position.xy); + target0 = SMAALoad(colorTexMS, pos, 0); + target1 = SMAALoad(colorTexMS, pos, 1); +} +#endif + +//----------------------------------------------------------------------------- +#endif // SMAA_INCLUDE_PS + +layout(rgba8, binding = 0, set = 3) uniform image2D imgOutput; + +layout(binding = 1, set = 2) uniform sampler2D inputImg; +layout(binding = 3, set = 2) uniform sampler2D samplerBlend; +layout( binding = 2 ) uniform invResolution +{ + vec2 invResolution_data; +}; + +void main() { + vec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4); + for(int i = 0; i < 4; i++) + { + for(int j = 0; j < 4; j++) + { + ivec2 texelCoord = ivec2(loc.x + i, loc.y + j); + vec2 coord = (texelCoord + vec2(0.5)) / invResolution_data; + vec2 pixCoord; + vec4 offset; + + SMAANeighborhoodBlendingVS(coord, offset); + + vec4 oColor = SMAANeighborhoodBlendingPS(coord, offset, inputImg, samplerBlend); + + imageStore(imgOutput, texelCoord, oColor); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.spv b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.spv new file mode 100644 index 0000000000000000000000000000000000000000..fa0208f25069dbd07bff6133f52792e1e769f681 GIT binary patch literal 8328 zcmaKw37D2u6~|wgjWv~3P+3Jl22?~4QD9`onK2NAQ7FstV;Ep;n2&~Gva&>Tt0!%< z#Wvel)T}hJGRth=7u(DB=dmo?+)C~Dd*>c`c^{wd)A9b#|19_1bMJk>p_<{d8?u^V z*{EzpcJ{C=A4g=vGvVm0KA&6HZd$u&&%m}tOHMgahi7Fqxt~6V;Zw(4i|nfO^l5ky zIUHW@h#aeDd9E5_hl6h+xDJnETpcy&w`pBd)4J+l*Sx{4i&qS89};-|$1-s|i=3mg>FDM> zr@Lpz&aKt{oz?2LGkd%Hw)OPwIDgZ;p{kdx$ZI?X-+}H+x~kRwRCjGQ4X(O<`=0Ir zYaE-+|3CRQ9L%?8@kuL`Jnx)m$a~x&dF!&d=v~#`YJW%fCBQsx9y9Ig+x9e9o2`V~ zUFomv%6)3Hrjp)V($|&r4JCckA$on*iN3C-V`KZ8&du%39c`%8QlC{0@@*~ct!*7S zUz1(Ttj#mOnmOM?vR%i#GspGWjm-I8===Jo`&pmeRNyyzMtPrWv;FYJecoKsZ!PJ! zm-IVI`hi3A`s^O`sAo+&aQAQ@d~pvCp!Zh0*x!i!z3k}iZ0)UXt@O6{4OY7<13lHg zc6(mOdwGJnC*cO(jlJ>i9}ebTb@!%)TxS|wTr~T8EP7Ah;KuGf)!w~kCAOY5LD{i>vLdlC6!qH&jDI)>^Wix3*(r6UJ70;XTF-8qzCC_Zz7FLFUoO(MTIBMW1J* zJ6btUIlqDW+vi!i<)A(jJ?!>Qdr{X8oyXh9qFr6PUug5&FYQHLyF3w`b~# zx_uJI`u0XW^0lM5U%!vz@zQT5R_)>m{I{NYGPxR<#qu-IM=;ANlZY6Z^FzL)!0O@a z8v4o4B+Hm&Glw?CHO4W!hUeoNUp*b1ej0w|h1B z;-p(o_%8(yE9hIm?jh_KfITO5<1R$9Y~QNIN%yX2*+b2tS91ND=J3ydaN!!M#C>c> z%ptFc!?rK!av_oD3b_6E&Ry0>pFQ8Z3O=6MpV94ywYt~8FuO1L6-oEq$!Y&~6neDBd@y<^oj?n7}jyrT1Sby`xo@San%T~pnW}(X| z)7XjM0Qc&8bHJ`G=YG`vE<`SBBAy?=}|c#7GxaDJG$VeE4_;?T}N zA#%I3^|^;yusLpHZNF3X=yJZD$MI43f|<+QV%PNyW8D$S=Ipwom`5WH*Bz7W&av)T zusLGgXMyEnUG%%k7@#9y1@jz>3E-gSLad1?LCnV%T7h&`|CH_F$P&i9sZ3q zHetVc{+nA~?7y|G!++;{KNd1CLiCR}UGee9XKKJGDcI!A5Gz}6P;&e~2zLrLG1bnn?X@G8W5*0YN1tVZM%2UjZJuXU_O;(eS6wszzEW~@Qv6i38a zueOM50lUwLI}0qQI3mt{Xo(m?i_Hjck9vR6l?ZxT-WkUv_%ij1s8j_5nWCx#(8$y zBCZ2m?A>|ja!N7IGu9TpJ0EQCtiznzqj#GV7kzjRy0vXVq7Tmn>*sJk`p8Ah^T5Vj zfJDsm!Nx@I^pPvSkLhrZqmlRy9h0!{V;bU{n8EDtk!N`kav{=*1h+MD73K}xnF-8Y z$VG@YeYLG8gSKsmcEw?x4(I624zRUE%ucYJ;wZ+9NxwloiT9i58ru=$?3;VhUhILj z%tsu@BGH363EP9?5}wUGw}9t??alGbelPUB7-=ZzR~Ph!;Oh#y{x=qMzZW+ZbiY;m z3%dR{m-Jf;`Uvpt1>Jmil=K4y-F)|y^!t+T8O#E|0CAu3271BvBfcNIz;cSiGue{P zzYlD@KE97`B<^Pwti1+_d%PQL{~8g`OI^RH<%Qs4E&b@lTK1sJDXwSksAT|bygubx z_QGkm_NZkLY%NO>Yf;xPYIzYjY8eH-1o6(qdM^g+BOmKU&t3wjU2$lSbzcg0-Qrum z6x|qywdf-kF)srfvjvI%z8q|fdoo5Jxj4tmz{cE=-t^^QxqZk1xO(PSAo5or{+?gO z?C|$o+m*^sQ)%8eavLdlE0P>$qO*`+Bf@ z@V*!`lKC}=eE7T;tdG43pVxuq$8b-Lc|BsEJWu1bNBkSW#(NJU{sypo_`DIUk7pJ> zZvxB58gB;28pdnCGxhl`U~ACFKKPB0i~Mf|muq+%oP7AaJ^2)Ccn6$(e2?D=_6-{A ztljne25GbQ3C!<8TrckHyAvnx9(-5E`K{7socHBDh_#3Q-je>llK%drTi-bF2M}`@ zr?2+d-v`0gR6Oesq05KQhm%k7tUm%LA3gXe*f{sE&Dp)|XLkMQ!7X6>qU~m8b@8pt z=7`_+kAbHm#_Oj&a@>|UlTXIyaK z1sm%K?t5T4{bJwW2buMgAy`gvn8)E9e_Q=1>5G{?3-cT2S>3~| zJ>KGv!8aj|h-;2y{s|(lK8yLM$lRp+-T4_}o;aJICr;jBE%E#M3$XF}c%Oa8*K@bb z^Gig#>si}G$2k*Mw0VEL%+esHn2-=G(3`z^Yh;`%j6)b=~@{fIv0+I|nG q-Su3{djEh} _quality; + set + { + _quality = value; + + _recreatePipelines = true; + } + } + + public void Dispose() + { + DeletePipelines(); + _samplerLinear?.Dispose(); + _outputTexture?.Dispose(); + _edgeOutputTexture?.Dispose(); + _blendOutputTexture?.Dispose(); + _areaTexture?.Dispose(); + _searchTexture?.Dispose(); + } + + private void RecreateShaders(int width, int height) + { + _recreatePipelines = false; + + DeletePipelines(); + _pipeline = new PipelineHelperShader(_renderer, _device); + + _pipeline.Initialize(); + + var edgeShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.spv"); + var blendShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.spv"); + var neighbourShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.spv"); + + var edgeResourceLayout = new ResourceLayoutBuilder() + .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2) + .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1) + .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build(); + + var blendResourceLayout = new ResourceLayoutBuilder() + .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2) + .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1) + .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3) + .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 4) + .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build(); + + var neighbourResourceLayout = new ResourceLayoutBuilder() + .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2) + .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1) + .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3) + .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build(); + + _samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear)); + + _specConstants = new SmaaConstants + { + Width = width, + Height = height, + QualityLow = Quality == 0 ? 1 : 0, + QualityMedium = Quality == 1 ? 1 : 0, + QualityHigh = Quality == 2 ? 1 : 0, + QualityUltra = Quality == 3 ? 1 : 0, + }; + + var specInfo = new SpecDescription( + (0, SpecConstType.Int32), + (1, SpecConstType.Int32), + (2, SpecConstType.Int32), + (3, SpecConstType.Int32), + (4, SpecConstType.Float32), + (5, SpecConstType.Float32)); + + _edgeProgram = _renderer.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(edgeShader, ShaderStage.Compute, TargetLanguage.Spirv), + }, edgeResourceLayout, new[] { specInfo }); + + _blendProgram = _renderer.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(blendShader, ShaderStage.Compute, TargetLanguage.Spirv), + }, blendResourceLayout, new[] { specInfo }); + + _neighbourProgram = _renderer.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(neighbourShader, ShaderStage.Compute, TargetLanguage.Spirv), + }, neighbourResourceLayout, new[] { specInfo }); + } + + public void DeletePipelines() + { + _pipeline?.Dispose(); + _edgeProgram?.Dispose(); + _blendProgram?.Dispose(); + _neighbourProgram?.Dispose(); + } + + private void Initialize() + { + var areaInfo = new TextureCreateInfo(AreaWidth, + AreaHeight, + 1, + 1, + 1, + 1, + 1, + 1, + Format.R8G8Unorm, + DepthStencilMode.Depth, + Target.Texture2D, + SwizzleComponent.Red, + SwizzleComponent.Green, + SwizzleComponent.Blue, + SwizzleComponent.Alpha); + + var searchInfo = new TextureCreateInfo(SearchWidth, + SearchHeight, + 1, + 1, + 1, + 1, + 1, + 1, + Format.R8Unorm, + DepthStencilMode.Depth, + Target.Texture2D, + SwizzleComponent.Red, + SwizzleComponent.Green, + SwizzleComponent.Blue, + SwizzleComponent.Alpha); + + var areaTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin"); + var searchTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin"); + + _areaTexture = _renderer.CreateTexture(areaInfo) as TextureView; + _searchTexture = _renderer.CreateTexture(searchInfo) as TextureView; + + _areaTexture.SetData(areaTexture); + _searchTexture.SetData(searchTexture); + } + + public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height) + { + if (_recreatePipelines || _outputTexture == null || _outputTexture.Info.Width != view.Width || _outputTexture.Info.Height != view.Height) + { + RecreateShaders(view.Width, view.Height); + _outputTexture?.Dispose(); + _edgeOutputTexture?.Dispose(); + _blendOutputTexture?.Dispose(); + + _outputTexture = _renderer.CreateTexture(view.Info) as TextureView; + _edgeOutputTexture = _renderer.CreateTexture(view.Info) as TextureView; + _blendOutputTexture = _renderer.CreateTexture(view.Info) as TextureView; + } + + _pipeline.SetCommandBuffer(cbs); + + Clear(_edgeOutputTexture); + Clear(_blendOutputTexture); + + _renderer.Pipeline.TextureBarrier(); + + var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize); + var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize); + + // Edge pass + _pipeline.SetProgram(_edgeProgram); + _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear); + _pipeline.Specialize(_specConstants); + + ReadOnlySpan resolutionBuffer = stackalloc float[] { view.Width, view.Height }; + int rangeSize = resolutionBuffer.Length * sizeof(float); + using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize); + + buffer.Holder.SetDataUnchecked(buffer.Offset, resolutionBuffer); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) }); + _pipeline.SetImage(ShaderStage.Compute, 0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); + _pipeline.DispatchCompute(dispatchX, dispatchY, 1); + _pipeline.ComputeBarrier(); + + // Blend pass + _pipeline.SetProgram(_blendProgram); + _pipeline.Specialize(_specConstants); + _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear); + _pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear); + _pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear); + _pipeline.SetImage(ShaderStage.Compute, 0, _blendOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); + _pipeline.DispatchCompute(dispatchX, dispatchY, 1); + _pipeline.ComputeBarrier(); + + // Neighbour pass + _pipeline.SetProgram(_neighbourProgram); + _pipeline.Specialize(_specConstants); + _pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear); + _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear); + _pipeline.SetImage(ShaderStage.Compute, 0, _outputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)); + _pipeline.DispatchCompute(dispatchX, dispatchY, 1); + _pipeline.ComputeBarrier(); + + _pipeline.Finish(); + + return _outputTexture; + } + + private void Clear(TextureView texture) + { + Span colorMasks = stackalloc uint[1]; + + colorMasks[0] = 0xf; + + Span> scissors = stackalloc Rectangle[1]; + + scissors[0] = new Rectangle(0, 0, texture.Width, texture.Height); + + _pipeline.SetRenderTarget(texture, (uint)texture.Width, (uint)texture.Height); + _pipeline.SetRenderTargetColorMasks(colorMasks); + _pipeline.SetScissors(scissors); + _pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f)); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin b/src/Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin new file mode 100644 index 0000000000000000000000000000000000000000..f4a7a1b417766c12bbac4e4bdc56796f18538bd6 GIT binary patch literal 179200 zcmdSChkqN_mHs{GL?MVqfW3FHfnW!V2!g%$UL;B)B~rcj*s?5HvMgIJaxaPFIB^oE z$4Q**W;dH`Nwy@L&2IMd`(NJY+?hck0nA9^T7EGfTOP1u^T`PkOdDsFqO$-AyZ^Es}aGe~|W&IcazaIQlpzkNAFWxdq_ zZ=fzv-`U`A>}>Kk`J1u%XzTF#TlkGzGW9t-GduG36t$POm$z58RkeBAYTCTmYTN3t z)weZZlY8AbP3LzV1VNSLr(%BKF%Or`?}%~6Ig;6**IU$6+U*Wh1gZkn0Z*W&)7x3w zS?jOstoPUZ8~hF2hITS>z|oT$$n4DN$nPlHQ@p3N-3^iz?Un6S?bYqojOVWLJIO6_ z6`Ud_Ac-B1c{s=0F49>gq;@57+A)$kl-Zxtm)}#^UEE#TRn}Dwl9hp~&T69B z37Xsy^@4uPJZSHAb*F)0wm;8b&{5b?yr*PO>7KG6Nx9dRXg6bs|#_uY3i95|5;ufGlALm9KNt9w1zW*@~m(LG@VN<hCOjw|L;Ia4^H_&;Ce9_w(~{4R5+lv}EUP~ftD(Ku_G5eYg*V8??@g~Sm2 zjwv@_!OKj6Qy@4RMv$TjRG8YmtNd_H?f`l*gIpVII3JPN7@t~bFY$Yf!ykg*Y2{JX zhGswcK*PLoHZiC{c)}sFW5y8S>^LIGe)X!BS0KD#h#)+npu%0`cLmox!EGRhjB{OF z6IagVaLID-im!5yb+~MPR}ff_DG#X+X!ns1G|rpC2-=}RiY8!4ZBZOnkp(Y7fwOv2 z;JA4-X_#3t#S%mlXYa1^yTF~|Ho1M=3>3JBtK~|$Ec9jcA(h4ciTFLn;Sa&@tm3%x zuxeeiMhXPMInzwylwidph$UbM72=OMq1b{4+^1P#3Y;+z!7(CeB|pe}!v0<5cM*N) zqp;uwZj9?j6shKl5JzlWLTqm09^&^Hhd&g*6Yzj!!F$OE!iGW641(5i>zHlKK1!Z2 z8T?Kvj*th01uy9qpulP4q$!M`jS-a1FB?7MgXp20M>8*DyMpa%*mi}R<&MLJT|gX; zwqFDlRq#6new(o11F+x~ z){kK}j7U5Q4e|uR?=*Ta6hYQBt5Dzq`hgThreMPqi3NgkuPZcLff1@^j1o*E%I-&R z`3QO}ClEExfa5yoCF1Tk`u-T)#J7dSLpnj3!F!co~ zjtB%9zvGHcdl5r;1yU3dy&xkf_qt*`@ozffLObpXN)o?sX&D>;$I<+~*G9USkeQ+y{!6V9)bx{7->?^UnPd zC)oK*#Q0y7ve$Bf2kZM!41tUGCJgzu_?Iak15-jq}jI*LV-Dm4c z95PNM%;*+0%bLCFHT412x^hE#NV%!pQXEknRUDH?kGmxIy4zZ8$K6FWdME+(^v2QO zTR_YWAOG9Af7=PR{~CEXdX3x8FSXy(nK|GXGfx{A5|(v)wQHIK>UH&o>X7O%NFKq) z(>%V{rUAox>E=3qe+vd8N+h`n`=gv7{H-YCe-Xz^?XTNY9LVf-4cbPNCK9KOv-VF?TUwL$BzBs*OudZaux>;yKe^Y9-fE0cInnDY zg$kn&;Dz?bIDz- zIvgWl?EF&uo7yWoiaK+;(|cV5jNq7gB5}${mOQ6lNLbJDz@I43_pr#Q1L zts2=iE8X3d3qz>8qWL|e?i^Ja0V~i!<03Pj^c*Q#$BVabE{mpHjJ*EEqKsFSZ z(w{tNCxW9%|oUY=UzgeBXFp)biNm!I70 zN;E%#@rylxCpm&ed}1HYB}naWZmVf`cNBK!c4c;_^}2}QFi#M6Oe!Qxh7Qjt_N%9L z14e&Rv#r)ym0Ffr1P$gEWtOJ9Q!Amun&dhg7|Q1-@46EFIL7ZW2Jnbayu+pT``WyS zAjST?Kz3IKY&eV{c|sz{Ecq-Ri_dC@3|)zBmIgkZiagab(aYSTXTiQ_pKAV8Pv{j--~PMv5Y2!)b^k z0>SJe`TXQvSF!<5_yfeR1`(tTeHgOfZ1RCUf(=s~8R7|^=Z-24s1`Mox?z2)$K?E#pD-TD%MTi7v@5DLJjB>!4=_%E<|%sM!CHKlJxHQQNl(k<$L(>s%1u69 zmu5&kp_)-HDwh>|6>Exvigm>X9z1S>+7XcL$Qd%uYL_wOPgmYj98-i}nW)mksPmG4 z|C9bePx46Ow0=&zqS?nD2C+v$N<0Ws&?BVd3ZCX^L35(cV(X8aGL@&N7u#wQn~fa_ zJ-R{7h-N}Pt)5lQE0>hZ%2g%N+^^^;>v4`GPU;ufmG|Q@RQQ$Y+WeKDG#{z!cV+fF zM$MDPISkulz!?ud@JLOyp*&2F)Ue_45hxy4@HQ)h-)e%-R+5=nwg@{ncG~a5Z9{Vpm?qAZ_@SH~9xwxUSUS|D^W^@_JK;f{%}g zA^-S@Q4DfCqLj_=pr$La##xk=RhZ&-c&znF&89Y^-_WJ+(e>*FwIjq)GpX+I(v=78 zW9-4zT-bvvy7Ic}knjyYX+Bcd?=0%c>~{^>&<^(aXo0n4Rf~s5f+C;+fheX?sE;ZD?^1C?52l*{FT{* zWwL|ESlK4k^4;0rS=yD;mo|_*BD7=HFdu9QJ%Zbh2f_zb2UW899Z`)ZbeL-$?z93B zOfPX&BztVN7J(s@7%+50iyifyrQJEbX@l&_?9pG?l~;pT-knzd-<_q?-Id>)*_Sfp z071)ydCD|noI^_%NeyJpyb?9C`5jZv=z2`eR*$nZy&xwiKcgho?W}Ut*y=2eNzLXK zQ@g1H6gwK|%Fuq)D=+Kt0F_-?VtO8zogIIApcFZ&>4Q?|@2m(Ec4zmd54eULqd|hR zMyL=B;Sc$iXl3)mgC@;@p*^YIUYSywkq?5I#i?bk3TL&=%NY8=u+7xbNLTL4?qgRT zv5Mave`T5s!CzTw`XASBFvOc4D8)~5`k=)5{Z)bD?mXxqbir;i}){3HPo-Z&SCZ>9yBEcOwAT=az$!MCL>ssR+dr@6~dBjbxDo5cj;)N zD|hAfvMb}ei@5R}=63N{ruN5~p2uNsYP(7mdwQT0KgsEX66g0<2THs0yGaKrgUN`* z!FIro$*T)3;Yo?y#DAmm+p8Ye^%&cd8f+e?JGEFKm|hAyu5?!0Vac|7;)g2-@_U3U z4@X>ihSxtB^6if~J&!|oAM1Y=idfSFrT9rsACx%1za~)DRoI;i9i$Do29rbWfG316 zoF}gihO+sc#)GCQZNI^v*lekFRJqF1iZb(abMrEb(%}iA!s=wQCb;PY~s2$?1a<=l6Tz$4d|ib4Y>xDIn+w5u8k^#9xJ061HA$riH9fSq9n@NFq(1amE3=n3C z1mOu8zpBo%F5$}XzF}9!dI*7FWNYcSi0OG8n?4XR|8Lv=ohOKRXXpIBZO8BUK;(X8 z&ZtKAg5s_MQebZe)&PMzvuV|O_~v1ufcDkVg6bN*|0mc zD6=p~Fa!K>3@>v_t%48 zIXqw?d2*(})WH!*yY?vZ(kRbTs%7`HB z%IL+A_Jdc(y^wI_u%?Fn7&g5x>IA*8{b5H%`!VX_q3M4#KP1Zf;?Vh#wZ(%7QVKgl z3<>IhdBG8Ts2!2q;^+?#niTsmRbgB=sP8s*Fa=T+sdTwn9N`IOceI3G8F#{>0@;;G z{XD^FZKn5v)B9pf&CO~KXTsGfL|FhXykY2hIGJem?E*z4pC{59L0CT z^hXbx_A3|EliFd#U_U)*N~%K?sdnN)6N@8-+3e-&f>uZ-z`;1_%SublJC zT?e!!H-FO?n*R#Z`{GW}lbrq+|8QCWD{+3g{AA~E`l9n+Nlnm;Ct&Q8JY3=fa`RBh z&foL}=f9E~pOe6c@k5FA#S$lwt3TQKo4)A$R}$lM63qv^`0IbAj+e_%cK)U>I{%f# z_?%SxrPlXK9WR%k?EFn%bp9)e@j0pXORevfI$kb6+4-Bk==@g_<8xB&4;~(CeX+y| z5I>Q6?c3te(UM}Ez03s_yA!1GsxH9S!7U@mA~nW z&VLm(f#2hf|AhtscZ;9w{7t)?|0@2UPXdZDfcLoLf5;m}>x(5`^7Z!+`I~k(|5f}J z(D=yz(fA)fz!?AXa`{E(Z$dvhcK)VQirvnC6|V`zFXjT?#~uHZ&VVQP{_UaiH|>7@ ztJnl3#^+)m$nSZ~!;$}w6v(SE^zY96#aHnqpGF47EhL0N7F5bYDP#c@ag>*M>Iw7JH_;#=3?BgPr}IeVx6XJ^mhlH#VvS zBGdwzxmGz_JJitE)YIJM3-|&powNnZ6VTViwl}T~&GrrT^mX-gbp-;Qfll`F^REtn zaEtM0hu+TSG@)M}{-+i0@xB1L`un=`bpF25xr&ME(VF4f!MgtXzJ}h$-o~D$ZrYl= z*cR~RR~6QkHB>fvn!UbSUtLQBwnm|j3SUWuucdOXc*QeaH{LkXG}t`g>+|)t^tSY1 z>u&9C?P6P?l{QHtUgUFnj9L0`a4V1X1<2+13&l(N^VUt*zKq43+5D-(@shE!5%+NU zP~~9NKy|;T&(l}a>+SL873P-}RhD_my_L1q^`3^B25)0+V_j2yQ+;y-w#Md0UlX>A zX&VJAW%HF&o(b<*?MU5F{b0jD<3MA7Qy;e8<{oU_zHXl+zb38{<0g;%`Zjk5YoU~n ze7xM}AHU_kro64YXgX%yNZyySoW77bn=_p^nLklDRy0~NTsl-XeI?6c?41xJxVCRpr$co~oK^Z?%_E zv#PCk*ok+W8?QoL9?S3W}wt0z2THKR2nBFDM`c!?Oz4!zyVH6jyMChl1u z>Gf0YTigvK!Yg^CqdD2}_usgma$n`{t8Qp7>(3aEnGahJ+Sig-T+69MaVB#*dn%ha z;#H7eR9I3}3WDYCit?(8>WbEBSevLlx$WTawmGYLM{b&T#xYjIrly8RjhOD<#He4X!-p9$o&%E)LV)dRL^KG zC!95$Fl{9rvL3XpIaZy^uBFt4w7K+|jOi@!%g=`b3rmUxg6@h6o?$i5(BrM~dc9Zd zXHt%49LheBx36Fo1Q$x@%4T?mL=g-}JtN>K*{C*-EZB`G;*ggyJMxUb;K+iXLKJD` zG9q`$`c1C={)ziL?)y;SYs%+U*ELsk=k%wH#}W@GZSV|}SDec!i>dQzj9)=PVPSDm zDI@51S3rf8#IUlus=8WG;uZT@*YWhttc{!ld3!-{33dz>y2BWfJ$ob@LHruHDkv}= zUQqgmW7`Qo=YGO{#Jzw?z~lHDQn&4u{#EY%+kbNZfGd5>z0KWM-c~&gg6H+8NrmP^ zV0gf`W?yxHp$jQ>@?pV+1x1C$#k>aHZrCwY7-9&DS8V5;CsL0@48e~qm{(wkAXHdZQ69z+{E|nU6qt#ebBUZ*@|ds>{F3___g(H??m0FMy#px) zAFc?HTz>z>{S!Rk54dk|?{KdwUQpg7g4zoSXJE(2OJ780;VtAvfb|kWAVZ_Ls=z_ZRM8xWDCo46T2adlNQ%7X+Uog1WP0$D~4H zXc76rg5d+14TlkAcFY)FwOvR)<2sRcB>iwEq6oa;zWmkv)k65e;>8j{h0tNxpBnu~ zo?injxE$FOvth$lL}J%f_#H}9le;7+!EIZFPMjiNOE8D`TZUdgtYz<)c!#6 zqVkUNhMEXo)SXQ@tv_izX4*0xHV65U1ydA(4HuQ*g&rX^$VU?p0sj zhX?`-<`qabOmQTfAXK>H58nO;>5n1^S#SkzBl6Mo6nMczc?5~yPjPScby)Con2m54 ziOu^E&`Z%Bl52U6Zcm$FqpcvtZ|#_NCT_EMe-K+Q^14Uc zzn%BC-+e}%3!bwc-vC`<$2EF%Nl8$(7ak(&C*<<`AMQU8fq#u?^#ktP__p3f6nPmo z{5%L==Lu@h!xIMiQ6FZf0)qsvS})npA%ZXqW(s6cM2sW(tA)5T#)jr5e{7wr8tH3zg{_ViF{r(;bVdQaoBhHZzMpaN6U4uB$ zRa(Bz5kd55^em?~eWC`js$Td+v)uc)|KW(=KfnY26nEU8a3A8X@h$WOUxF9Bt$a2} zP~=AuB&a|>ibN2+#`8l2IhhK6=tq+cQxriQ;Uh76geyhC=33#`3K1d1zh7+QAhcJ4 z);VMB51t_U51%4#%;MI?oS-zrZ{{E~JYB=XID*m-;Hi#)U?v50qMVJtq7J&BQTVS8}VXF{Li8CcQenDx(5hd8RwFEUPT56k7>4@j$76SM1%crZg{|CDYB0 zFUc-uTTymVPEk&wutl^v{BJL3Dyf0`TG3K|{4d)6h!gDmDOXN+CvSl80tlAkYr+795_dRu_L)4cu%V=)uq3}Izc9Z*peT?enqjZc zxfc|t)T;?o#=*p%B)_%I=Ce0B8j|aswN9_ACdHFdomz!WikrCqn040LZ|_LE-F=8Sad-jIzv9Y$e#XwOEh4ixkkxW36*TiIL-fJNIup!S-J%9=?r7 z$cqRHIh<2%PSBZP$;2mfq_@P+j{imOk8*D6K27w|tx;!c2Z4Lg+zIiyKG}u_Q@xSo> z!Gogz!1H+rwsi_N+>M}`!=#98Em5d;6QyD{^Ae0!U#XlnQeg}QN7rAHPO;2Ki!2uNQS#_xXs1-_1Pit9KNO%hs1M%fi&sF_0XnNeg$9W;o1r2f5V zJ!w4*1uj`HGINf1G-@jr3@jsqj#;K#2% zMvT3QoRRyOg;s)1nlf2q(V6u8_`H-r@i*TFVB!Q@NJf8ockKEeIyJt*)9Yse%!7 zs;%14_`(}#_f^>N8dTbk%hn+p=W{I$jo#X7Pi0kkMOk@BgSg=*9kLhG;h(vGgpz5T z;w|nz;`!4ccoq|dwxGiOs#Wz8uS1QqgJ8VzKiV(x;`w|Rqr$Ipμw<5_SoZr#d3FkR_X{|a|ppP>)nXr zKe~z%eto!bJvg^EHrCd8m;%cy+!*2%tQdA2vSiruf8p-!R~V`N7L@!tyx=VmL`_i? zt3AS0xCU=X45he<`!8G1!UN(dvUM+Pcn*CT(jX(qROp8{Y=pANQSnxU&xME#IZ+Z&ZK8PSZA)*PXki4M;KaTNxtnokM^8-YPcLjYNh7FG(j?}}$ zWkWL*VSdEg{T7~*JPY4{Tqrf%i;K55HPzR9z121311n&|Zg;60k9_dr2?`AV0)Btb zp0#`o1>(LJ-_}h=5Op!hj(HU-rMQXviQh?B@FD8~roef`5vqebf*#=j*)gxehk2AE ziTeNI13bT4C=idgEAWVso>7Pdal0?P3je~!??1;k!H@nuz<2R{kl-0+$D7DSEb@{1 z_pm?iXNX2S@QC+z$G2S4(FWWi8i6|><8g6LJ37W3oxf5&>`H2(Zm zj9NT^$9(~JJ~x;KFQPwliWBuE_3src@gono$rQMcDR7>}kqPDpQEVOr<9+vEhJCHg zzD9~5wG=}{1@c}nq(KlQel&hhPy4=yUd-1KMc#xBzl1pQ9D3$t#TOBcrMQXviQif9 zI|d8JJT543FM5K!7o5U99Yqo%_+?-e?{5Gx)1cH|yg5O2(I|UC&7L3_5R#G6@FnPfc!FYfBmtkM4&xZ(tDWS}Q z5kt_AVK3ebx-oTy`ZD16C-h)`3lB)Wp-%+`z6Be88D5YGUS}E<^(FQ173)RxU{1pW z@)jIYAlWbx9A_H*GBAqwHxR$3`Uc*DDTeSqQ0&PB`_shl4|oFnYxcbPd+5b{13r+R zV!jH3FM!}pMo@~IxIe^?x8QZ_ekgDiHoSoQIX;dM!FYfBmto)Qu*ZLZW&Rud+n>1q z8k z-@#bTk6_ck07q)KNK!7pJX@D~75$i-=v}`9hTlT0`5`F&9Qx+Z_xJ3(xu$~ z>li1yf&07Hai8*aoR9xjegbv>Lg0AKa990?;;Y#AefW-_gYB=GMt{dNN(v<(@~>ZZ zd|Y3~jCH@^xb}kTY2|J7h9BU*;sdn#n`rZ=j3TxA=d$_bx*Cmx>Se_idehIKZ~ZF1 zllO7Hj}fsyh1z}uTA$&3*NnF{uP8~Op9p?~^!e+c-bG9QvIFeNnM__WAJU)HTm->e zu;W)?$?t;Uhpf%tMeE3-W!rB`U1E=RO0{2c90aeUUvnQSd`DpT36%XkM)8{IIqiMb z8;bX#cOv*BX5-{Dc!uOHNsC|hS9kts%7S&jaVz1p<`M|rgdM*G)x0S%{03S_%8|=2 zH?1bgpD?OkRBm84=Oqxl4Lg2K_)bWNWX+#)*Aj2(?x|l>z6~4x2CncucuC&ADSi<{ zKH^kiWy>4b&-+la{?FkrY5Nu1e8yw&5udWiM?N-k@2@2BzlmoacVQzR;aeqdwfzh| zx@)i=ii7;#@N3gw&{a5`K4V`sZy1j0&N70}D(@&>M4$Kp&PqD`8uN&9`Q@coTiXl+ zniG$P>H^Rg((eh9wh2@Y6pJ3w{6v^8S|hkNn@rM;T&BFAY|h9zi^M6%@(ZDZ2d- ziu(!L!<8#-d3dIH5#Q08r_6uMWdk`ADf9NdN$bWV`coiynGw9DxXV=d8Z7x8{M~Z- zhEc66xHGqR^L_x2DGK$*Ts%?Sz;*86J9-CuKW3E4(@MSG!e2IyNl8ty&hOk|ys@;I}HDxXI*xzF1C zmTZ2x85NEObBCc{H=$lot|<4hbqz%$tApk<0lq+2{{#OyzFLJpqzEMgWA?Y+;uE&rW}$H)5jBb@0t z=qY2JzY&j-^qC;IfU{r2H$=TWzDMy1Jb7n%U;b#uv}?h(H|d~h({L=|Oc=pCiWd|w z!MnUFn_pf=d2)SHo3U3ns+m=q7#MMaD8Kvw z%IDYrDllCw%K2$^Y^nycC)@Gdd;=|c8Vp~?H^hH`ot3?X!&#H5bB-13n)#4%3tsSS zkl?fA31P?gW%J9;bUSM;t)?#hpmq`p+zT5%f;OFJ8hjS&xC=XenfMuRCEV4%q<)1d z@IBaYC_0P&?Xzty&cIXuUaT-UD8%;XI5e3QlA<#?G&z`oHV73ShS$A{Co^~PcT2s( z*Z(RoD`?00X?9#bXH3HE$#y*TU4vRLp=V3|GK#SNs@|fZobj|-=OV8_<^|7cFKDj7 zimxl5#oXMxihBy#{x7r4>9zVyov`3BC~#T1UvUUt@U%ei1_<7PIG}^ScL*NOk1$w)4Fp( zf;W`6;0fjO%grit)!3TMdknq05zQ28U9Q50H}S2V!BaYlB+nq0-Qlh!T{qs;-_gCm zEcgxOTZ(rPhd*R-g#5~<=uJ>8+4*up3C(!=KZSV|hdIn&^C7qtb1CXXbAk&5X-e-X zRJe|7O4o1Qx&C+C`Dt}*6&|D;6QIsY5FCalJjl-d6=>Y==_wt^8_k?bolRbb0uLk} zh7BK2I1@qercyq?(iD%aF{zy_ST_y@E-BYw!+fpZ5JB)W-PGUKy}&G3P#|oWyx=zx zJw<{$`B9S1LKsyiW(mv@KXLx4O=q&`QdBvZ;n)s4p218E@RfK)TmQT5{62gog_tX+ zOoGhm+(YOuQT5`}uw=idyR5%pBzuA^*iH&uH*La(PwIGrSD6*xcqo3=_J*WZQven` z3I#5pA44{L1on0o7DpPqPWm%F3x0Q*1;3_zL-{uPF%*Zt9zigA^KF>CTMg6gE(u0I_{@S*r6*I9ju9mZ~0@EB9z3T$|TdBIcg>zANGfgd8sJq^X+2ciO*4S&S$ zlfMlsj{Qo)Nca!ERqIl-YxQ)c6hMtDN0SM2q_!ue@_a3jQCh}q;O z1!kOP;H#>|W&5D-1#T(ri1TFf8OJ&MdHV(XMcYN&CAM9*UABg{E7o5sKh!?Zzht;; zylr|e@mAuqiO-sEV0#AJ^(20KI_c>oFP=gk!*jKBn4fVG+a+w`d9GMP+cm4aEnfSn zk`o$2D;VlvvPA&uP?`0{q=aNf&<){8z2dF^70%D+S15(PZJbsM8D=9O*D-=)+-Si{ z_QCYc)T1fKU8kIyi^NRMq?q0%O{qy=e zhTDecjJL4eG~F~k%eH)bH=drY;hFdmJlj2qC%k9yocA2#c^=QqX(O)k=v|X|{)QE% z;q!NMp%IHF(P*&foa%IFPLGbC9WW2;Jy1`L7IEc5-SxP1FV|l- zQ?Qb=KWii7aQYFVm~zr}5+qLxB#9>R{11$=WA>!ttIBs&Z>V1*irN=-_jGp??k0ew z{j>JsOl()xXtK|*2HnM``C zmM18!KZ*6fA$=FZbgi6D1shI99I4<5R!)@67p&y1WgpBs1cqB!4S6*6c*+T^OA9CY zXN+g^tIt1FypLGx2x~Gd5KyY8qf$Vjxg5((z#bZ20M)JS#)a-9@NBVvA z%|23mRq-BU_`2#f^(!Jp?LF;1U0!-svdL((G6pZpyZ_;bV&n!WKc_jRc7eV(E64HX!w zUWFF(GTp9PhtJlDxqHKy!!nK87)!8Zo*`NDA=vZguGud#%l% zAS!|*8qY$7i%{Vz<_M83AKX2D6614H?U!2LD|LKlL$7zJdW;lUI#;|HPB3K2P$ED7 z>L2k`>{qbipI~g{Q`{YV3!d=nOoe30#4tCjB&`w_ObYD8N`wJ{;0&S(sgNw0819<> zB*y2Y+Ap=fSL*oA#@^aN&q(D&`7~^Jo(SecgS;mUv*iE5T$F#s{EB~o0_mPRMDXLF z3f}|6yzJt%3YRyz(cWV3KpgHxUuFdJ&?XT}<`7L55l@KWuJPl?Ph*bHNwr^UeXrE< zosB(p17yJ!6YiNZ*l_V;;WmQIlK%>R|BgP)@9<=n?s+~#91%6h2=Xda`nmvSThcyC}n*S_KPkaQL7=vf&=Yks-l~r{4~z^ zoK*Xz*7r&s?{Dg=?;{UbJ;p1LdNNFdB0<(CWc>aJ4@eeF3gm70C%7jL5hOd#1HZD= zN>>dmm{(vYY?$H*PjCX!nCy5L^%r-1POAO<_(AOTy;8^fo4Xo%$$~ww;7am=WWx~z zSuFWq%m)1v`p{&-{~#!kZ1~692MqkmvXB;71nx_ksd{CJ_9dV8!3!a&rnZOTn+2 zS#UFaV7tvPc)@UjyT*?nKdr$~YSi&LsrE~)@0B_p{1Abe2P6xI0@07*domFOi62Ga zKcEluJ3)cuJ3d2ChT;fM@Y@2v@)USLX2DP(`Y~j~A+$3?($TE+cD{GD>^ ze-v-jjMeuyq9_8@_28>|w1mp6P#Q9}b(o#S`JS@6%0^Fy_HNq&!6z~eE?hsftwQdm(^?XIb;t*Z0XhgJ0u z>w5V3x*iC~ca$e}2TY6BndFI-(e&ZWp`3xd{`|he-lE=;p3VG;26#D0eV# zprF67uc$ZqWctCJm4f-=>C%bvv5L{E;p!pJV9h{HzqhZpudWvxV=2k+F$;Lm@XceC z50S4ws!CB>>@GoN6sk#4Db)24>rsFs7ID@$AUuBusb!y0o!1`MA2RMYuUeMubICKV z>D0-z@r<#|(d?1zk=$X&nbgg!{dp?|3&pdg(`A$8;}v6-BcM3!A&&iCY_$k$lKf}@ z^f3x}Q0MzG%7@73Csw5>1HlS+h#~5Fu!&8XNqa5J zwgtzWa~2e*(k9Z!Ge+&_P`+X#dkq8^i{?sZ7{iH*@ybz99PtoE#!-@A_yj)55AXay zWct9J`=#EIo4*sO>Tl^=TW}mFcO~bV+2WsGiB56iE?5%RykTVQjHF&XISd* zM+fS$3V4L3Z#`1^5c&F}x-wK31zBKOE_Sgd1<#NuQk5PrZjyhEo4F4#N%uAgUecV^ zozNdK95Nj=6T?;8l6{esXuse@;#ex6lCu|SP8W+7ilIW>`b-f+bmWO5lqkt>=L9~< zL&85m{36o_hVPaBS#JJfr1BK2Qcym7KAXK*lOn{h1~)0c!Jxu7Ftzn1#VtngobFV@ zQNyM&#BjyFi1GoaP`=`DCKX@U3xZ2Ui^cOyg&~F$m1B}Eh&F*w@}STMqNNWE?UnxN zVfh8CQt$+M6^1JGRO2S)ceo{?2_+BU1#j{MwWkw~>9>rVrj5h{(BVGl@M7{Al&?VX zfGi{|4ibbNGZh9IO13~8K#rWiCwZ9o0si*+k;{j`e(5HH|9|M;(2a%o6B6_C8J$B_ zDL}B4a!gQt9!iS4s>_=5+B3|Kw~U8P>tMLgb}9L6 z%1Kr{U;{S1mPZd=mI`UuXCBdnEP0x2S!`#_fB6CAM=szIKKy{)7ThUUe=La4pi!>@?Yg%RNO{#ZIR&VgyV=NP@(OT5>}3R(nH3AsB&?~?e~dqYFZwZI@ACX&r4J1IV2mFh zQh(bN$f{C=2ttJz=Hd&5P|Cayp+F>gQlQKQBX~u_6Fj9qZo7>1?PtQuSFi#q6i2X$ z1ktC7@n?tL4h_IRQUMR*_ZaB|A5wqJ1MaL!5hCaYKPnXRD~u!XiP}(P05<$QmeY|2 z89{i$(^ld~_tfwm1ujbv;;y6K4VN zL+LLVgM&6KRs;wV1V1ViLKC&Vk0~;wz}I2J_Yg;(RbCeeB9?&P1;#H_zJf2lz(?ZM z{1x7c(dhUKfTb4jMx_>d)1^}ugJ8@B#HgMUcLDH2^CJu96&P8S0tBf-6cXY55&XnN zZQp=CAb3@CQG1@}hX}$7&`=RBA&Q8R7zA-w!Y|;6w*Xjj0dI&^w4cg3 zn-VjdV=f>rqUtGe768k>|78IL_ABrUQy@|-vKJ%B`28~?2tU>GTUZeD9-_!A=*gg5 z68bWvK@bGLilVcvbr1;BFoiFHwkUkn8j#4^TDEEGZ({1HUC9N!5og%(3qSqMtkl{-FvE{{npZ=D@nRFTl2<})wEKDz?C>C`AaM@n#dEIT*>sU4=rUIgLN<@-R zfF&fXvW=a@EDCMNT`t!Eta#(s1xd3?k_}}*ZJ5CV!MsWEsXE3UrAh>M- zad3KJj0M1zbFNL(MeR-1eZ^ZyjPWsgz2C?96D1$uliBd;s(zQ;m9VOmPHXi$x%><- zsozydr@#7RWNX=PkqdNpj{imOk8*S?vPBhy}#K<-Z(C_e5C$?3u{e zXFYDXq`jefLHQcSOemcVr3m=}C{h9fApw@F)i|YIS4gF``n6ns#=Dv~lp)Q>N`Lil z;cX569KJv|+W24i{@_8;e+Z2~+hD^wi;8M+g)JZsn_fsZykh~dcQj`)dBc2Ce_8X4 z>UrgT1)tc0l8EuiFodKrDUFGP+GW+2B7Rz{&*kzn-PXRMdPgCZ{_6M9Ta6xtxBxi% z_+My$j1!1&i{sB&KFpbw27}hBO2q=f-~wXWFHA3_sz*B(0M`!Z&!+6NY#GlaT-IDy z-B#XHh{>BMiP*QHL|RDy`X z3v^?R|B3r|92ofqzdTh~KFlR*^d_yH5fl~>6F;i6z?T1Vs5!E20dU=5(Ny}fecgP_ za8`Fk^R()g@&%|+Ox{E(F<1hubg#v47}YGQHWcyFT76G8zgr3SG}7s>ejC}^h~M}H zy0ORqgay12$L#oJ+wx)9aDrLmP*Qy=wtzTd`7ejEQQH;(*Y}r=PjZF(V9-HKQM+|XAxmNT2O zVn3L)Wjv)nue%BjB83?2_`X6+-UNmjRd!!ukA6(Es6L=Ps)&`=>Kk(TnOXWPHPT-x zKTzIA{Pk;4yJ6jy_P9peG1dg+SH{$aMUxfGMyGm@0eb= zZ2@p&uX`kKI&Co-OP&*t8crvi*Iv~;rM>}A_ySZ&DSqC<2*7(t%hY5J7>1$1cxkP^ zDx2R8!yVm=npdH~c@Hzai!k=9DHpk7lRLVCn#X|3LU z2!6rzSBN5fk~}`*QzAey{greAy6rr_;NP2iDu)UuvSw449c$JN^Oo_X{)|o}h{QQc zB*20SA7qw0>n&}jUj3++rL|ISDWavddQ&#PXN=DwebasQYfOP3zzar8fAxzm7{^0j zuBp3fuxKm?7VKPw0uPyv!V8|&T@*BU3!aed_&%kzvLdaOVNf>-1+uhOh$Gw6TFFi{ zCZ@mAu=H1mBGJ=d{p_I|^aUT#)LlJLGMYP;G3SB;EgR-d({b4FS=}W@@Ve?I%KO5O zk(SA8^O=KbtyKGA!#mSjy)0LMNZ)iz|2#ZkF#Q#?;qB?KwkHt#g6T+KuDRROUpkyO znK_fXl)P#?kaQ?<%XmV6I^i4$UPC0gj%a*a`8+M_wKXN}fd!A~rZn@2A_viv*`C(w z1^N7tz6t5Cm<1#K6`}~)Fr`rpC2#r>;_;XK)!gOjD;p{p&z??OaIVzCW3PS~y%;1`QzNYvY&blv)pgnYSo$lLzUjVNNPmSW!rO2t{nhuLFdoUv`vTsc z^1h-{^bIi!o+-AUUHr*FC{N6 zFCAM3+cNVq^Rj}Q#cR)ZvHcmr_p|fFEhkUho-iKC%hwgU^NWKOfTxOHxZLE0%6+liizK<*abIQ_50H z(@N5c)3Id~(Uw`rZ;}mo!XIcXt1K#`>gQBJKBD%ysJw^dheGqP=2hjCdQjVw;5W3H ze2GowhNOB+t<`I*v3cy(j;iD;%Q4HWrQh0tGV~3O+Tk7@>V zJ^Fy5!`NnOHTldpNA;2+ULYlikr9}v)6<|^W2tVzj9GEqaM=?>-u%Q`YuDK zVUMXD99t57mUEUvmSxMN1qI}-owjyc3m7&!>K%2USmX4#h$BcoVLXzT_jzk8tKAhu zaC-&$uYp=$sQD|_osOt}ULc>}wnFoWCI^(O z$_4eTdQvl{8PV|+yDb+j$1UrKB6Ex&3dlo+9X7t`G%1m%So?(WNM63BuEFE2tfuPS z0>P*iLGU|iISd6ZS!OKb&>)pt4OsoQ zJpw~gA}A(d5ZH?Zr!AP}*d}c4n2aKBMxRSZxgeX_Cw6{D3cxJRJBY0pxl*H@U)PhA zL>XfnV_c!7p`ix#&nr;>oUeCT%qrFf>rNx4U@MT%FG`_#%&$A7K%sf%vT7bWoYG8a z#w?dCrz~5RgO*j;@H7Yt1>~(LARo?E_#5$n`#4$vvjpL3fi*3q6Ota)a;RDC; z&q(oW#lzkvupPsRZt3jrcwA*ixfU6uL)-_!I72G&o3kh z)U%wObcGr`53U!k(2V-=O|^AYyOt`~Qq4=A;I``Lg@sbw#Qj*m5TnpMheGpE;Tq4- za@leg9`LZ`fMqXicn*CT(x6a49xC)hhhQiIuwn{XA(FU?f4|5GGoN0@?8n>$vsp<~ z2a4HeJd7i?QtCAb?UitPFpP2jumpjWIW`-AMJb*KSA!#cSFC1< z=|s_uYf$}eRR5*Qp-h4Bf@N+jZ^NrNSV0~=Ln&_JezyK2PN8|&F;gMqcM=wSNKoKB z;>aW;7%U)9DhzLb_}^ZRR(psKtrfe6RO)^%N8>OU6V*0YS{AIi2rVoRKRoKse)zpg zXb+i+k(oHeCV2iG2?CW!Vr*yE$OA>8{y8j|DX;=@I7CqNgixW#N9tej`&Z0;i&JPG zcD$+B0KfByAjd45Oo6LV;M@*^-PW$iM(dG4I0dcEV|3h5B4#*|g;BFO#Vx^7x&LptaTksmA=3an(+yCVtmp0HSoo48+C{}H#) zJlV133izFd2i!soAsm z21BAI30fkCQZJg{QU^=S#^?3;aFp|t1;2(kyunSN+f)fB>%cr1g%%90T(EvT#Srp= zV(n?silM<$EY=eFNd3#!f5<2_4}KRAfluHrP=9g$^z0-FX9)u{&(B?#ra)P69nprUt=SAK#C!} z0;3Qtko$@q;x0l$Bc7X zh2|N*v$zL21`9sK6o@Pdf)|{^Jsrhj5Daf?=w~f_y#;2JLj0(GUqd;ft0whE8{|l@p{U&Cy+(F@pO_W?1f)#t=Ie}&H<3Sa*lJU>4_DQ1E|q5e75kLN9zSDk@h({B{K_f*bdu@+s|C^w$j6jMq%p64_?HCT>aB#LaR|-WHo5O{Yb~~7xT7X zMLDN)C>u3{!GLCT{j!zmm{BkBBK^_)t7z+gh4cRs5$gL;_&e|sH!x#m3#EG|Q9h-e zCZ^V`6mDc6$vBaEGUc@Etn-}nT=Mzk3&|H87ae@zk4q^2fz5@o9*B~MU~R|YwN4>+ zo&`3pTuI9G(Io+*rDzUrhWgJJ@s0ozrX{1gG=I}|pcz&_g|FZK_g!&<7MNmeEJKR@L zHfjTv!IEe)p16BMp%w03#T&|3RQJ{QGz+%vm2gXc6Wg-}Y`en` zaQFm>KY)jSSJ1~{*zgGANIh=#vL*0AL=x+NB@zTSj#ti=tQ4%}t!E#~+RQkTel+cP z8mW=CQ?4`6BqN#bP42LvtOxx23K$+lpM$4(OyJ0qJdNnf3a!AN-T}kc7{wPg#1SNQ zM3PuOw_E(A#^1NW&5 zu;jf0LsDXpBFn!KEVKd@zNetV0Ed8lwjU`W<{h^NT%M?@4_ z;XXiVMxNn=5XD{NCow)J)qbh%^CcmI6iKUtstKtQmz?RIltBKZ)@qkx=_3X4X);sCp?gIFgt=_R%u$btKLCH6@;=L=;6*ng^N((D()S~AHpLB@87l+ zT7e4RQ@o>Kg;rqA4|a{8#Q2<4`=!?RN*xb=gWl0t z!$MgP5S$TINR~_t_p|lqQ46gIDijK>>>59b@j1!%WAqB;ePgcgl{~(uaiDg@Gf_EJ z&TNw9JJ>uH1sgasppR8Ara^F5hxg7Ac)vL3KtR#X8|gfDI+ zl=a{Vaxx38>>|Io<8xB&ms;N|b$n}}nfQ?hBn##hDB3XYiUmKoO8ih5N+|0gD6kVY z99Gr?BgT^gKgmKXyUH)l_?%SxrPlXK9p4)8Q3NIr$XhV4K)xqK8eE~0g!#k|Wj#SvSp0&VC=XmF^f6(9W@ALQid;H!0F17{yv~|)pc&&J-s@of=M?H!r zKh>e2Dipqsmf*(M33=gK&rqPdGtfy#`TZTSHpvE5agxs{@(3u5zzR$RnmU_0oBg!;#7*DnY|m~hY%6UmZ>wsnZmVhYw$--Pwbf&5 zz}CpNrnWiHNaH|rudk=2yQQnOt2NNtiA~Iw9Qrr@Uyr?mxP4Gcek$e%u3@MjPdOg> zC70i8n$t;ZuKCQ#+_8e;;=$5>cV9(cWp7olr^nM>1HpL%wVkz{b^1mH^pG(N@`Ah-UPH&EZ-(BIhC z)YH_>ZzvHIVkwDb4g#vjRG~+Fy~aI*P*e8EUw~ZweaPKbo!1>P9k8t07o0PxQ|S{~ z2JRo1@j$lIF|sWkH2`&H2qd zLq@TMr?^x!S2kTSQ8fmFBi^C90b*FkD>0m+WV4nqd$%7ZSbzyJkMMd64?QpAac&Fc z>3oEjTz=og6u4)V7f}*r69h3;1qD%Nozp2`m_Cs?o`te9tUQmt+tiWNZfi|$VFa7A zmF27Ikc>j*#n(CbPM8aX?K@>z;wW2VOV}=-}f?>u)<`^g%x=ns_ zyA^ARoGmG&K}Il#ESVTK6*U(#pUC*Rrz(O3Yes5=D$$RgRGWw@^it_KP%#e_D= z`9j;z_cjs)-Npj?WrU&fM{ojUy`j1b{BNP&L+JN!WBSKE#SP^p^%?E4ghNEo%*yiE zXOnq`Oo;cFbEdeB$=@?)ZHf zvkj)W0o-ZSz=mBk6#p=^ImB-U<`;kh%Q>1`OVe;=zutjey~JJTPC|kG2;_MW=|<)9<0m-X zQCx=t&+-HfAegw1ibJ7H*(?}N!7nALE13nUzX{XO*rk$oUNxvlRG-;dmZ~1=jxws%eg1C?<#}Xsl=aEQW z+yk87ec$t*d(OL<_F@Kp2N0dD_&$%Y6yHMFLJ$$k@~K$(eS}=Ri1X?x27>i?lQ-Ri zA(=PGm6mBm|F%H9-o6pLa|j0U^jdrbLZ`N{fY{(&Or= zlm}22WL^S0b{cjhNeZ*zCl+`bzuk?55)Gk%jm%{5OwNv=@cS6Kc!eOQ^GM)!8tEl2 zCYYrm%AVhM3Dx_6JN2sN4ib3X@s#tj>tfDXgJ2#6kwW-Q!cT1QNL&J=rO5<`841y2 z;ioK!jDdy(b|mbGA%*7gzcUQ{2N9f|Xz&CQ*pC86P{AUU&}m8^r$Mp6SVlDXG7>10 z@ewAIb=(4v`Q=&L-yf1*?i)zDXz#w;Wu&>L(o0X0nw(aR62Yp|m&R5_kh0zKR_=pCBkBVS=B`VbmDN zJSJ8W9|h+L>im=ifuNd4$L&ZeLAvjP;3PAYJ@i2+9*XxvXEjAA>jrghZ`$)F{Pt=k zb~Ju1Q3<=$T{@8Z2{TNIFv&5*a!2AD$afHu9NF{xV_XNs*1x5_W_d|TAUez_h$YTB z6W8JsUihtJ1~}#$^D7OGOJKA#m0(n14fq8BFGhF zZUX_4eLTTUq&;uK?+%RxEK15pF&Bpef;@G>8O6L20zWr*p`ChXKsq*dOrpW{Hb z&u`*j{B6dH_X!htow9hh83S*i!;FGri4z13elh~0!6Jb&lNKG0kAk`kQrd&@FTqcr zN9z)Z9hslY#Jg`{g{qYq@*4WM8H&=&J*pSS_`i?AzIuuCCj8#Tp?+NE%s$+Rx{>8E z?#I;pC&=o;79nWGD7SDIN703i{An8;`3~9h`wYkWud|Bqj?!R}z?-(~N{6u{SYllU z6+dP$ir;FnAxNOiV?>9O2eCkPs&U{d#sunV0qT#ckjAGcyGcQWqngK9Mk zcVZ%rPl3ZqmN#&p_=JymI+ozrYFVY0oZPM4ttk2~6nRHe ztYmG$xPQVonYYf%k-J_$;E-}$s2FZ;?5-LJK>-h#)dgz`*5t4CkNL;_`Bi zBlW7Ke~b0h4F+RHv?Hw~wJ_;bNA4uYy4-asea>fQ(o7Erk%BQs^^MAQ?1iVk{+I52 zN&}03`3m}U6>;tWjy;EfrW&No_=HJqgFM~cz;r}G$74+nnqG&6my{Or9VWX5Yled( zMWX?|ABvM4D>aDmu%%gT3s;4HT~`8Y~&0}FrsDkIYK+6it(GX$M3^jQA{VHnfaNcpfA39OSv?rK@yvGKNY z^&NVcYRCEp>xZhMp;*a?E>%c_$b)dyk2uYbv~$j#dEU{^&;O=CFRP}5Y^Qqfl4 z5$a@5v!|rDq>r89{!~g!|F%66ZR}`iXs_+8>?!LF_H!o&*%jqJ6mTE76GC&;pU8iZ z=(iUMs9`kkK#zps!u7v}=MxvE|KSf9k?v}jFb5;dn)5Z6gbo&is(~0V2+7lltZ#{# zl3MfX*;w9D#oI|~F)-9JR5w&PTox@IDIOu?#b`j+DWSMlILg0yV_(;9SdY54=T78D zi~53{<*ijsHTBe_u5GMts%)ufEpH1!GSsy+vD7oW!(;XRjqS~iZFL>hofX|>y`jER zp(ygKB)UM6Ow(%#v#&$~T6oszSF`8T^}p2fX&OlW)7NNm zS4DSuPpCI&P>gd-?rY*_x9VRlqkS9v7O(#$pHEzr{s&pVdPlp>>cK&DnBZZ+xa~GK zf>w%*MdbW@^!@65XeS@egN+SkCSAhNaOY6dP#A(0(X!zn7P5G>NHVXC1V&``p(w)f zJFHPWWj*fL>)Mh#=^G_=Uw=tYu%oQ4qPeoMx}h#oR~xRauW4ACSn8R5;Z5Pyk%5M; z##SnVN80MzYlLA}c~4nSP$)7Qh%~0}HAA^1LZE+&-{SSZ`SVLOVEp-OYEJtK8hR3A zu5SYfL2QZeDUN2s;DrLw82p++)Urczq^xBcNQ;c+Ceub~ry zO)aExL<*}rDurQpSx*RxFkJfiR}vMJ@SH2_SLxROjPnMlCH@yaA27l_$Lhgh%y|@% zu0#esc880Rut3%qK19P`V4wH|Q_Xdl6+K%5DG;c}FdXd}ZX0SG4iD7|!I&Z_QaEBT zEF39-;b+{1JKAN-N&7*^F4txj3D){Y3Wfp$q?_v!hGngl&6N#ROA|{yb0EAeye={t zSykWD(9zgRnw13BBnSp|DTEdRhBscj--0XwEidOO

mcNCv}CI)%wgy9@(bQ za#?1S@cle38tWVGM1vdHAFE|$sZwMx8XAV6NTJA~Jb{h=XXN~Ko@Fjsp0FKZ6pRzh zTkVe`h5e$*ieYy2JnIjJcQOKwM@Axp=x|qKyH2nLf+B@5tWQY#QCYNN-Z&!FKJQ-fY7&uVhD+DF$ zQ47gfTf<0UjjqY&`6nO9`k%pPj@z*d9hRz&Qc=pU)(1afgnSbTy~#ZB6n1($?})-! zQb@!`28O$%ZNtq-U?f^QRAUf~g~XN^QW*O_tEcZODZglW!a8f;@7V3y7AMF^h!hS6 z`UCyN+4I{S-WpyX9*e|~zynF#3TeOmbS;M>zG5~%AiE9@1b#fl($ ztfWx(FW*q*-Y`#$W5BA7ejUF#e5N0k(2%kNNl4;Ex&cp|bI30#E^%P80; zGRSF{<&K+)h>WC#gFhm#315~9a#%kAIieZAs0+=&LaFpEI~bsbJ2 zD0*z{U%te%&SjB6>x}(?V_FEhH$jm33?t!)tlwvK912fkfv1!PkLnVL4yO{7k?`~G z|8m=pjSgagyU^ekoH!53qQLIm|8Q>K84it;g&~7zOt@cjyFVM#!8nJ9?h= zhr)ZYz+0HZOvEK{puUfp45Q$@7Nf_@?ce8p{fOe%9qovT28#q@MU)OJORSHBjD&x} z{-wCLzsgMfI17m55LDLq z^X~t0+lSvsKNh4z*I3c4ex8b9+kT!CZ?NY0I<3CX)3+)B$^6@BS=yfU z-LSk0L%rOve4>2TXX+jrcTu=L?60lqf#lce@i$eU|B#)_ ztob!J`qwyjT93j|FEVO)kA&vewb`PJuA8=(EN^h;L;gyMP4#Ji2ub74 z$$fj+UmHuO^NzXZ>{qNeEVnE#B8k%5cNE7D>90=m)2#WqyQ_jddE>6__5;@AmN|TK zu4Aunb9aoE-^GXFLv1!V=e_ED&i119HOreY{2=MGzKI5Wn|mf&@UXwuSMCTL^d8SS z>v+=kwDqR-mgNo>_Dig|3PtJb`}FOj?D-9Zs#^ksxf8Cfj=k0y%SjksV#a*~tNj8x zeOGaOQ=2V6?SIn!wBtG39qV0-A))#mGxS}g|6zYk)^07C&Oe-cGUtr*qW!Avx?*_S zBJ!eBd|OFlw)*QHE{#NjUHQ@6N!J#~9{WM-QBsc2u|IQ_SV!vj)VSN@kyloL_c@2Gj zgIQnJ{GvroH5DyIeg07hPC0hj_t}o1$&@ODp_JMaeZB?1Q$=&WOB4-%27_X*S;tb!1;YX~bZ^J*}ZDnJ=idFjqEREP7<7g||%gV5BgXFxNO+IgS1K!Y%)BbDd z*oR2`;`X}>W*EEQQ#RFT-}r3#f4q4^&6d#aqJ8;?@{Z-6a-VUXb3W;~iWFYg8CqYk z+(DbOmcP{no#o+hd24Y`!BE~B&t%SKitSC?_alWzVR#x{zQBk$TYaMROu_lQOP;4F zc@06k5PStG{F3F3B!=QTwvE~POX$FR%F5`vEj<-lmgW-sUS#?%jD@&ZyssvaFynU| zSE^a==Zk1ry7q118rGzrWh@gLXN)Wc=dWqwt?TQi%6F7Z2lo4qet*on*0T zk-%#dY<^Y|d{OjRN#UC?6#FGFJ9~x)(f((l_$4(L`m*YaXgEGZjRDIzK2%qcqq|Ue z2b(AQug{ju_&pE32)-G!1pTLNf1Orc4dc^Tz!%l0%M8zG{S$4xeO+X8)wa-{qJ8M_ zjQ51+l>4;ntn-5NvMz-%yooMn&o95Xw5h75zO1dJCm;n!#}vVB6rI^?KVTOrJZ?E< znXNe(2k;`2Pk)JP3|ses zHr6rOu(^6m+0K&bLK6D;5M(42Jw^(% z<~QzNRn!q`tgekzAc4ib=y24##vq6_-h&iUz8ZcfOBn(4FX$484kr;*wnX$ewbMbo zE5wz150Y>SN|!afho?qxf=f-B_9d*VMay?X zu$LdVbB9jAP+Gq!{8~0d(BSeN!9B%$1Ef)76g-}LQpQ0=@B(^##gQ#P&$w@}urt_H zSyxxt7-}u)3iKBYi5Qysk1YSFJJJN#qqz3+TYL38pUa-RI^Dnx{i43`x!S+X=AYjY>#GPun#TX(GT zWaw1!nZh}2h>}3hHPK;3@D`Tj1=}60@q+HEU&W2P4NYB(U?2!G0!qgF7F;fJa~$FX zEoaA&lU?a9Px+hSADJBgyQux047+{YHr(_J`0RG(Q{%|^Zax}*?UPL#!)S2D_Rt=Y zz`%Z)$zVs~GRT~I&UH`xdP~|uja7BERSjjWNMJWQJm`;NN7i}_g2L}a=v2w+LNr)x zh%SLjhi}SkFitR~vx#5)jP*^7MkB&cMnG}uk@)FitH*86#{;dI4v!**X0P<|zbWk> z1k^@^t_`Q6Tsz*A;)FlI2W~{h$NI)Qupp7mHB*(?5F}7p5oR*8dB?pc^l=b^+4J*8 z3;IerLQR$7T1h&lB(PWMa11+Q5LEoiPU$0{vLTFto~NYby0Rk%!KM12hpv!f)PxvIpaW+mt=W1Hp2g@6rvZGvdFNKK?hUeYroA%wpOY8uB$;4sM4+ zuot1+)-v6+zwtoBA$1&X;HWo_BlR2+{dmuJqv&FAuKaZ66P2f`PFA0&IZ<=G=6J1s z9IG>ql5mVY$?fc|O|!$cKYW1WpgInPjpK0mu%vX$>d4f6sRyX|iT~tg2E0LrraH!X zufyeGtd~FpvzPAwmp=YCsr{WO>nI~cJx*B!F?f`Velrrjp?h1$uC_fbdz<$)?-!Dd zdh$t$u1Y?>X?fLk(|gtbWZ-$G737v6G$h-S`VlbL@qx9NE#E4QK8@5vKG0laovhdl(k05RY7^D;I+1 zX^Dj2((%8vZ^XCD{aJ+sstgMjAWALACT+5BbN9B+o$b5Z_K><$9SM@j{JyKbXM4%{ zocn3-72l=&iv{Nk=Zel2pDj64db;#<@QKi=(5W&xJcGg3`o@m>ei*I}kFz_z0ghAn zs%=9mb(TW&!SI9by#M3|hV)fPpo-wPc$`ilX!g?mBMtw_H1WTw_otf?q8uaaw6QK! z3c-nijlEkSxP$b&$YJZW&M=9jE|K`}THdg|=y)#YhUc31O5P>^#rz8e=L6?-j>Tt6 z;0Q@itf;H1rJ=FCz9%w(SH@^~Efgn_#*K>O7A2G09~eKg^*J+sX7Rmd*N+cw=-=G4 zwR>CFc0G2lZFed|MRH%WI6m6=?BG+DMZHgIkWzo#rK+BKQ=VEYD52~-l=XPMv+0%XzPcJh)D#+|L-g0 z5PRMFitV=Jmh-0ThDf0>^j<;|VJP}s;9lz|IH|0ox;3H*cGP#{6*LfMI0{42=ZOc$ z&t!ehtp8^5y=K>s4NnfPmux>W3d+%$AQ&GJbvgV~c4NQBOzACFm0nf^pLIPWQV7E< zafVO&F8JMJzM;b2U}r^pZA+xN2^s7_k9+ZJSrr*layX(S@qx*o$@-jG|IOli&8{Dd zPN2aX`{O#S2x3c;4>cl6{)11k^zT~Uu)b=0$u2SoL6Jhy=c*5}OnZx-KccD>>!8Vo_D!$Q!oCG!;9e~5R=H`$vZejgpa zs|Y^td`|RONukd0ve!M47b_qrDb!WbUeg**5bSK|MhbOJ9>S(PFn$s}m|=a+tp8^5 zy=K>s#jqfQ>xAFD4yTTUisFy(MfnzU%@2{lH__o&Y%d~%ae}%O%4nEN_Mwsd!J@v> z?(&YR_F4!wP|8|lQ1n=rLKzJo7(YFJddc-Uv;LdK_nKX=_zkUFg$8pdJFJg`sUu;> zkMN9={0ASiyCo9%Dx;u5Q0cKqA)_JLheq>Na3&JiQQcPC8i^BZQv~BuXrwhawIgQV zU#*{JSf4Yy-dMj{D!$k3dUE|qrcvF7B#(j%M#3NAr}syUfS)J{e9Q8sd4i7T^pVhY zov{#ps|f@_gM(e=9hK;CT}v`SU5|So96wqA%dkFY)_=43UbE}rH#{i>#fGHnaLP#d zXYl(jBj8urR}~3-10BAL4Binf7J@PoG8W>755EFJTT6S&kU-I4?1&+Q=y9jg;|Hd{ z!Y{-6oLT?P;(N`m7k*e^G+5b?B?N{3&uz7!{jHHP3bF8HnS z5zM8+tCa+b4r51}8`U`2rU-UENcofX(#!F)Te!6JcgT1AInu_Y6HR_7OkpJ;HmE`jLqB7zT4{tmO1wB-7nS^xF*tEJ<6 z&8~-^Sm0z0Hl9WdJEBHH;rGW_kZ+*DA2W*)3Dk91C-?$dEHX&;ur-G56rQ>_eu214OuffSPnK(t^_Zb%Cn@WQ} zWDI=A@+La0kAg3v#X3Rwt@VxMs}Ya{2}%O7BIvN#k$HmIu#@+geSfp{IkW4{;(N`m zho5eNl>{maB4ePwqL3tk{{>&}&+w=H7IT6@PY9&S)ViOzwpZx-)nZg;-_2S#4J!zoPQco6oudSlm+=zY48Vldl?eQ zC@7X#5hVNCSRTQwY6R><0=r}kL;@28Wj@{1^uYKnvw+7k%ZF&$*8k|aI=FXuTWoV| zaCm5VC^{UC#yF(!qsB2Z%AwtI z?DKCe-B`K4b}};2INm(oI^I6sG1fWO&CxT~%hAWty0v?K-{io=AXQeR&WcfEMX#dN;IYi|AzAZVTQXcTR8NRz`%ot-V|t>!!+pd3!vn(u(N)n^vB4N(Mkw8o z_GQbH&e^=_z|NAbWmA=#YBtoZkF0B$Y@BGGXqjl8XcL;_o#Rc@9oxDo#?rrGKow&d zn&6-u3oKPRme^QK4K1epmRZ1KndL*YZ0moF7mNg>RixReC&;j6sBNg7ki)KMcT`9U zOQG2x?PoZC-ge$~%zH3@x^QR7_RyA!sjAJ@o9i}(H%2zpuWww}G}#Qx#{F%(I=6Li z>Dk=3seeQNdML7tp(aw3$t+FzEwg~fGRuc#Eq{7hl;KLGka85mjYuLChgDe=!v7UX z!Z>@GV!vv6+CG zDGOvU)Jd)znl$CN%mN z#c?+0WbRDf!Tf!J>7qR)JA>QHww7`fr@K3Lkn(wJ_f+pDq)?}rEQwGw z<(KThXZq&FZ%9oaxcI!;U$V{Lxh`*Q0Vx7YAxK#i62+*pD8mNBmLbZb#GuGS$_LuZ zmK(Mwoo66;H1Cjqf5E;&VYn-ZB$jWfP!t;uHSbg9E4Hho*}5JZ43$3X+HCgymHWjq z3V19`A87h{3*L}5KQBcAB$;!NRDnXUMi@rPzXZcZ#zfKQmf=<=iC-bX>@Dll_KOfa zm3u7ji0@$j{=nYC>Eb;lyMsGJN)j6mHIe*T6%Wt}Dk)Sln4lHp6EuS{Rk-^WIzEDD8<3en+Uv@8a};c5skkOU zpiG19s^h%tjQdpXaqo=pkpDozzQEohVYnxVBsLtD@(c0u6-K(>AVoby3Ww_`w3T4UGS{a(n!JhxUbkP? z33^U=kLDfrAIKL?jx&Vc0k%i=@)cr7^uw?wIz9e1?Lm46KGVyLH!QP!NY?TfFN+c& z!KWfPEHWqrl@yXNfjuFP29_DV#ltb_wvfPUj!VvSB7+b-mN$bQAHbf-XgFPexba|8 z`HCch-CGjYBpZH9Iq(^PO#V-Hf9u5`=pM_5+!Mbszi6fX|m(Dlmnj$IsLTZ zUUi@4L+*i}D(jIyYDi!(suL7jq6iKN!7$4VQps4#43MH6D{|Fw(RnsO@TgC$37RbY z#DXN1uh8vCT#t=8jVXjo4t%B;C%eDhSNV|a`NhkktX2}JM#11Pso2qCouF<_YT@@C zEC_`{n8iq$1}PMB!F4uAWbh<1D0+-F5q_$GPjc}9!;a_-l{GO%klBIH^zwQ4xBDs| zau57eSr5@*T>_O3&l6N5A$m+A4M`O9J{tV0lECZe@MY)u6oRupTwLKN7KDR&wC?aQ zKME$K&=i2F17GGAmYF{AUiqoQMluE}36xRLkU`y+R4abpU`qxK)?QK)C^{^5WL^f3 zdym#15%*THARP1MD;5&mW*Pu&7QmYspH|ey?n#CK;(~5GG&2MMXFY#mVVN}A6gEPG zbqN$Zyoey_Vx&-rqzrvSyK8xYG4KXDd{x;IgCK=M;{3#dhy>0%+$7r(q>%1s4gfX_ z;5FgbY_XR>aLEATuuaS8V3<8WDeFNha?xPMK%=w~iBgkfFj@saDHQSv8zgU1DC9-G zPzXAFg;2UV*BLbqp2|HDQSzr-5Mv%4*WzS5BK$H30GkHzW(G+3ux1J1L9;{vG46#@ zKV``P;B24Y^s*?5AEn78CGTpI%PI*JOKfOyh2r;ZEQl%;!YoFYz^Cmp3SJ-(hcXvf zl2bZAV+2f*z+D|l1mnG5Isn)-fHyNg6+SGLDV7KzR_=vDaESomd*Nr4^)MumnRJ4n zl*toYO!_6gP{;?2fO@GP3WZp&D;>Us9f=c+^J5i8Ka4VPN(Oh%7lTXm-a=$Tx&U3% z0Nz9kX@7`^4-3Jx0mO0lLLrzo0J!{R$A^}$v$30;0Lw^HHJ|?KM->-r*`&4lVYZ19 zeD7JZNq?1^04rU9u4w>oT5O3w#>0m-j}?Mx0*K@8g))Yu4FGPO^9o2tW9H5)L{+Bj@cR>pa zeocfAYdLlZE(#z{xEE^V+)on#+`hZ?c-}?Vb^8m}S4bWr39$53N|KN&DFIe@V{lF0 zPS;WUIqOrFXR-L{(^_Sn=qY&7d(-)n?G5X@mQ3ldzPF&e8nH2{0l=mKyo*{$`9o6p zu!OcP3Ls8$FVqN{Srh==wYBm<;Dq;l&Qp$OY|j&LO9~d|zIp;IN!KAsPrj~wI#5;J z6By6k?mFN&Zk@ASwxmyM^-;F+S3c*z>VD4gqU|;7n@aZQQ*9-uzxr-UXZ2rF19TU! z|D~Q!(?If{Qo@I&{>*{^V&R)`FVsjhu^<4rcXRFTlEe8YbI;{mc0O&t3BwmjNJaV! zMbSur_3>~h(h?lZpUBEh%i^?FZ)eM|@^s)y@6$QYIqul*T3@%m1;hFD zEy=Q<+*#x2wn4Tjcb&hQlrscf*Il2Qq2jFvVj`eu04)}_M^5ZV0h7zn%3%W*8H9*I`6yU zx#7A6LDFAY->|$5LrKkMB&m@!LZ2?t?fC%R^y`1~=a*=}`16ABVeE+A4Z%bJ@dEck zu_Gw~zys?Ww^r^hIaqKMf@gCsx~@2`Id0fUfMva7eHn(L&rlpGYOASf3HAj>eUqLk z*DmKi#}WH+>l4;wJ^k3#s{$900s(UGr4U&d}bX!}-VZPUW6) zUvOPP3ZJ$=6K6DipK13f-upS7KgGt|>s-N=?-^~ut+ z1sC(Kc%II=>4c#DCG_|;E9tM|42>i|sohQfO%KqO^_PqRyvdg={yBO5G#);TCANEz zK@~tO=NGsaN)@w20Px^s>!!Nx6?;nd2WI@o;{-1`uOfxlZ92o-l8%M6Oyv#Xs@7m{ z;gElgS0^~_*iRB|(pp(hqstd8mq?6~WuP_bo6Z!T_h0fp<-YDR2uj+VFQLiv3_n=h z;bcwi6Ek9-8Rb*f3SSvusbvzQV)#M|~$Ccv=X$F2|+thK&SRYxDa`TdKnK zm2JVEqE-1LzA;5`n~Sto4$@j-PmT-2toa=;BY8=|1>Ys_Rdo0n2tJP%3qh6s3MnLo z7!1u`4`lsM$}gvc4{IKaQxROky-@0JsQ}=i@y-p6o9nh!><&&B?GGIBAIsAT&S6W0 zpp1oRGHIDg+A8Y9)y-ucC4GS*|7hNrcRez=%|%)(^jMd|6WQ`p>6^|5&ikK40!4>O zf0ZDp(qAEkl0xhue+`d!Z)hgazG@c|xVI1;7J|7VgJ*LtxGpI@W-KHvQ*lRmV{L6i zMO(1DcpxzBC#{ttxYf1Oxd%Nat(ATDUig)sDLhB&4Us_6VbWhIf}|c|Mx)YSSZ4*?H1oS;4uUKuAWwL92S8L6voEN=~VVMR!5g&moI;3f!)E!pGPe=q!k zr&aoz!=d$OyQlYNsxNg$MIz{33%o!81AM zDDQiD!be)B($N@SAAg&_s%i>g^S}1d0yN`i_x)C{EBl=ei(itw_sME{W-?nn=skg%uGU9?e_J zY*3d$2;LJv5&)9G?!4cSK+$0mQ8Jf#-thuj3_;Tle8F#sj`dA+Y)}cD(BQH?G6s@< zNMW7 zIj^6caO>wL$#G{K>qwyVaKBcUP~_LYCT}ffV2tUUen?OBKCJV|kv&)Kf^FE}7_6!e zMM|5B+6p@hx(j;p`}_m`RsKQWkZ(9|I4|mrdBd^#I*d?JZ^1zRpnu30^~Lf=@+z9+*{Zm7$_LbAIcvl z*C#AFMtq}rqsxjd`dzRet&df*cPr)NDSW;s-~tYTv1=%7-|Z(l(v?%m2{MJ7L!aYTHn#w&|KSA*-=Ib`jXz_zM}rZ z0S44T4oGrD%@ctAE+A$2jM3&;U9=Ma0V!5X(QX(PNa1^-C>$vuuLx#GZ@sK1-O!HO zx4R~CqyD}?XK`z=sjR*-TwPmRT~`^YXb_H}mJlh}#v=Xo?TwAib*HD+7_#i)dqzqqT zv?JDt|F@J}En_4sjTS40g(8PYVuE7U{8XWN%W>;o#}?P5XEblf-yi5H>JWyZ#`1>B zNL5`;U2P316jcHF`mTmnMX;{5roE~|7?yPfyLE~seZ?8R`a^MQv@6yUtH*n}YB)|1 z?`9a{<5##^*Ji~qYksQGyf$ZL3~v{vqczwXLYqm8N&)%ss1%T|@0cejQaI1BD=1~+J4xQU zjWkXB;*|7$=zZ1p+DjTqj=O~vs6zRG;yC{fGk#OJ1#M7m?@ix62?D7yO{VD`iEm)s zDY>7cy(8_hrf5A;9My4xVoOS+B86g6@bnWoya#@&&^!`&)V9yD!?ihQooCEDnm6oU zRUn!iX9&M|0eP&5Dj<&xHjpBrPDx>cq4ZyBBMjg`wh4;6=!M=>A$3s0lVqk<3j1i{tbs9(lHG+7vul2;Xw532(5jDo6wyg?8t z6ir5#_5NGP1lvk_-8C@U3`L=|NA-d|rt5Sp-=~Gih8ui~x9@gr(+TFvNEr1GB8C0%TU0>a zkill1U@cNuliYuCOlgMDAW|?!;?9jw#KY-rihb|B3$AkzBdq|cX z(?(Z~_QWV3j|PhbQoI`-u3AK}G;4l~3eBU#6q;u~y_w}5A-Ebnj>2zI0eN&dl^}Y& zu>W2ppoY=B13i*H;=Lgnd+7}JJ##ChkVpq+c zI-_MVnotkJZsFGpKhfaEAyqzJbU1~e%&N2Jmr`h6Ng##h83lJbx4Je#Q06mhyd#QV zynsBj7+nH;gdl}~4H-m_7xzCGU8`iQYM}~9zlO)`$M`org9L5`7d0jvIUZ{OIjw_o z`{K(N{AF?c@1pjC$#t8UjN^xDIpw3PPyiWcM!5q8vuPpuLNqBl@QW9a$A-j6Z!8ih zI^3C%!6oaT=P(eJQ00U59!UZFB}$f^=W#$DC)!Z&e7ncx$fbW~FYyoBhxy)sJxz}P zO=>?wfTR}%Ex0v8YMTbZbgtleJ-Q4_#XHzC^WAm@R*fC^XRZxVntB#TcUtG zPsE*AVx`4P_ur-YWY!`6Yf{$!HX3%4DfK!=!Dhhb+dM9($FlVG`WeH&YH|E;Qu|W& z{bknW=6Lwt&L}v*;9HFm@oKi7{VfNZ4mBQbJlt@^IHc@H{fu!$W;kYhV)dIM=4Cx7 z5$KyOnhnmD&sK6&%~sFWsH1jPKk8=n!!&X1tp1Kn{dY;~E8YL^9o4^cs()jc=0=7l zzb)74%yhk+oA<9;9RC|{Uo6y;2?7VVb?@q!Zrj_kzxhD(fu{7uKZukZtZVIVjMk4u zGMDw(r@d#n;kxLZ^PdhpQFN;0Wa)|EiO}(~AdQG(tF-Fmw&e4OyTLGCyGy%oGLw8 zdNO!2bTV|JjN`%Z1CH+hXY_9lf!c)*4`W9nOfgK?|4rk6rTq*E0;3xSrh0`S5v1*V z5)36OZJ(0KgSt$vF6ymnZ)k3B>}eQ);b>%SBwbmL?Zkn-XL-r?oa5=7E1pZ{BjY2dq)DK9m zDHtg0tZi#-Zfopn=&N5)~oTuDZaxZzG%)8*b;5#Q2l{B7x zVEjzh=gj(V7T;@j{mA6dhE<#TMTdppeDMl3A|@no@0fq6xVNmcx;@e&1UnkKk-~vE z!%-NDJ|m0dFQL$i_NL{oB6!nz9Vxu(Q4G;$T@s=A!1$T0&zbe#EWX$5`jN@u^@Eb_ zXNw{j9|_};;7KDQWhn~#gWVM!wQUg|Pa%jNtFj*TLrM-Q>miaz{tZ=VMWfJ)^)@ni z(|JQlq0Uf7M3Kbv4~(D5`kYz+&Ek8_t{<6*t{d7oKh@?0UsdG#DLbCZiwoBcZ`@4`nF|28;VbNMKESSSQ%j&Pdpe6zZBRk1jDS zQD{Yb!}2QW-Qxt66zZC+7(Ot53krBFT%R-Rzgc{*+4UpiG0F8P{N{BybtF^_CrIi% zRMcPEQ{GjD4u@MCL|uRbt-M*Qj$>vLxPH;eBzyPhO}!cVs$$)n(ckx-ST2&^jZ3nGD) z=&&TTPY_gk+#_Y8;FnTp1s#6bAgD&dxD-A>exLC4w&ePpS^v%Ad(Ezg-{i0m6dR&O z!MGjPrO=pF@0#?kk)-0H!SS*lZDL0nnnjB#N&!L9<6ihB6%Uoiui5p&j~UFo1~1fMWlK6m{@^!Ms5CfU)&m`0L=b){ zg;tP2DVC%Pttf&rpSkIL=7I9dus&zje_8*_6yIxhJ^XYFJg>pV(}-b5pioLT?%_0y%}d(Ey-;%C^9#X1Z@;it+{ z6j7EUsK!9CB9!&e39@S|<6xWOmr`g&%3LTN)(O6VEk*_(D8CHrb7uWF)}K?#`zFQr znq3b+-2#gaZ;IQH#X7uAm8Hm6WhqMZ!ZM73l=X<~uso3{f{NdQLMzN;UMDtRMnTGi z*c8DB$}hwEoLT?p*Po^Qa-zI%BEHw`dc{w-z{V^vZif?3BTA1IKYx4#OqM{=VHpKw zE(1Yri9#!J9ZnE@VEkNWJr^^g2nKkjYIcwJ;O_xJjh#TtDkK0jjP-E@;F zQv;9sxr(^cza z54pYW`Sq`wXz)i^k@a|V{T{xAUpY1;fHOzVy%YR5?Vq&&!Ir~II2Mc`%D;Cn;a7pD zK`}0kUV=qz_eS8i+OM_0W7Fo#xF7AoF(`Czmp}UN7Jijza0wFVLx-LBM)0@VZ?vCj zKi0m11fIrqrVCO2y?RyS#Z!p}2XSUAK!@G;M)2RYf6@Lw?XTIikwB8e$`}8k54wAh zH`w48LIMl1B3?#8NA?rqf7kw1`=$0j+4lMaT;R@2L=1k@4}I9@gCi*XWCScj@k9cB zxG>)X!GG8OP5Vbiz`q~`_B(Mm^dVfg%sp2hTo=C3mge~du_1+6kvv907nV4ybp_$~ zf3^Ru{T16@UqJ$&#GiZv4wyLdf1%y_oqf2@Pewq|U|j;eY81?h;D2bp)&7||^nXW# zzd@kFO{~ZsB8wzY!PI&1ch<>ee2|N7gwW3_4q>(M7*!!@J$Q{r7M+v#n!Kj$1#U|L zx?j#`RH{!&`JAJcu+8R1u7iva@($xY#=A_~e+W)nAy^v!_Chh0WUC4UX=Tu_UDUTM z_}$fpEPj{O%j~C=`y>=oD1C~9VYOzrFeVf*J_yD6_Xx{J#I>$h#h;siKp)o%F3Z>F$>^?lmEMl4mFVc@uvBr2T|vw;Ak3ovqaA zhoQq`%~hXNz^GUJYwhnTNOYTbOt8jP4YFK$$hIhoUhdhW^m3p-#+@%g&yWwSMlJvA z>woFaoAmED+W*02`+00`mlm;>JBpmX9Jkx)L7Q{=EdRf1zu@WaE6faMh&LWkH>(i5 zPFXLs-rt&;$yfS3PurOr%Uu`Q5bY^JB$C(v7N0lyHuCiGV;qhz5x&`_HCU_crOtpW zPcgK6EV(i}{GSkhTZOPru~IF85#@ZmEce{wVui_f`JJ4$GdC6~^v4;>(?|09-@@}I zUrN@menQE=yV?_Ehv?E8tu?kXq%hy*g`vwM>ob3^{W(_TRqZk|xRnsaK0;?|5&gnP zOnu`hY$%UgNnt(=gQZzvS~K{nOX4{wq(vuOnXvl$B^^ z&Rk^=If|VHE`5FBZ*U9v7SG4Gu+=k!$*x0>dk7*7o3#GWp0+V^=b*<@Q77H{-~4%# zE{J^nf~U9d6ZUhP0Ly8f-8!{qOT=1jFL#tW<@xxZ(BQvB0>4U_>`g-8Bt&cjq0|FN z;UoJJ+vh@$dL*wEj?^wlWcOCipF0|I^Q#bYWh;B=GEIbal}Sn2N(tSIH?u91#oh8fQkp}>#yO9+YlEw%n9=S^Po@+IppZ!^N3X9a7D=Z#LS z6@qg9H_UND>(zfY4Q*~;xB6wzjW(=rfYz@eEkD+yg%nT<6~wi z*IB{Zk9NvTuhsG&nv9VD$SnIOtg(I%#_#i<=O`4T6MV$^NnJ(Vf-GbG?>AVGzr#ZO z06On7OSwWalxZwP6oSI$yBv6uLNU-}&X56Qd!N;%11RfK3OyDtr#{?b| zcue3ifyV?M6L?JEF@eVf9us&>;4y*61RfK3OyG+mu!_i*AF+D=E4&>4g_X>IV?F)1 ztZC}Ue`r(oyLkBgoE6UBEI9igtbP86>DT@ayBS|4Wr(zquKlByPgyzr6?-81XB(fZ zw{G6P=@pclX({l#df3(eGCnFlhT=~-`wPYSpA~7dum4r(wZiauq%d9kHg`3v^l!2^@?-W$ z{vKZhxl4N6au*+smzCX|Q|wH9#17b>(v!cYFMo&E_s=!0)ang6y6NNdNYQuD_5X?_ z{VhHH8GZeQY4`p!R~^KYW$jY!7uqd7w0;{Otk2kE`fGMFrEUEV{*!6@56c@Y?B(o6 zlV3v8zCpCh57`a-D@ft{63BH|*}K?}y@VIgN9=NcpWX1kZeA8PncmfCUyt~y&6 zzEMxGFZMdSkl#X9eu%XErD^ZP^Zu(U{@0qm{W7hDNye z|GS`lER^QR z_YrxLw?C)YS?;K@HCS7b!Kmr?&m8}&+7}DV_@G&H_>gM6#s_#_ zk(#hGV!s~sm(EY}Z}=^_E+nu39j>s~#tE8rPxn8~5dSOfOMHn>bJ+MNh|hfowrd>U zQz7`Mzm~f`w*umOmvsx5yFRx<;(PC#;d0mKR!n^F{SsW}`rHbN@4Y{Q%UqvZQSrU^ zLvWevb1N*q_udIEbA4{b#rNJT!DX(`t-$!+dn35a^|=)p-+M0vW%Xbg>vJnKzBhY< z%UqwEkH1`D@x9p+T;}@ReEj8#i|@^j;4;_e^!Upa8sD1@!T9?9{jSdmzZD$cYfe!3 zEn|Id#mD!W5nSf_-0yvS?=lN`EVF!w+1-8k&o8rp$1=-@Jp6pjzTq+pcr3Ggh}pM$ z_|Gr1fX6b+hdlgz%)a3=3wSKEe2Ce%d-%^Uvw(;G_;TOnLmqxUX5TQ`fzRx{Y0k^^ zaT(=9(zN)9{VCOf@1DQBF@3zx@*(&9c@NzMiyipx=_?Aq)bxS(borzI=5!8x_s}ch zw=jL+JzW0izcsxBU)KE+e)0f)pXEcc{+x&QjY~Q3W%-4dN*|cz<&XZ`mUiIFs&59r z#pwgHy8O|9Qzi$#EP9tIeP9-sKl*RU?7(N*zj=P?(g&Je{^-BK)PXN^7j=I3T|Ojp z;}64GvjAYT0A7^N!kEn+zT}xPF8LJuxS8qW`1h@+2#t^Bp4@605R@`QkiAt1^_3o|D_G!&CE}Q z4{L6Zv(y@a;GzIx9$To6F4+zU08XMbe| z054wuTM)o&#!rP0iv$+gtE`O>ObH;4yBDt90N`}%e<=aH=KKgB);x|JpR?3fV`)ND zlLCn2?u8oFMpkA3aQgMXqyXN`{0#S_Tr1Uj0**3Ut)*E7O2-3;nhYnZR!ir!#fH>h^s1ZT95(9vjUjK^+@S5=x4JCY7%e5ha`OZ>C z!HA^=8B_tp3HL&o#jL~t;7sd(Du6c|eky!eLjt|7LPyA6ZLL=X2_QD~o63VLGXOaA z`k!eDT7CaWg%4{UyW5@PcNRO!Z8g>gODi%MO>!^PsJOK90)Usj{%7pJp}`i93kfVh zhbwHgIzizZ5C4_=p(`%{IJ0^_KKq!!V*-x}JSOm%z+(cB2|On7n80HKj|n^`@R-13 z0*?tiCh(ZRV*(E`0T(+HA+`>yIKn{N$8LL-E8zBc?74{^mck=s`u%&5y)w2Y^OkCVQG!4R&MF$?n6odH*B|&DiE?b?m)a8evBiAbLEMT+Dynk(IZUsDKdpB+S zX%hrmG@jASdl02e(bU~+G-qS&LUTSju(ce=}CNXufPO> z(!(4a^2BDU^i@Bj=++-n_~$Ffw&nY zwZ9@01lkz6bI@bNOXTAh6oL9a1z~Pem}466M+jOY_G+w2X=ZwQ`uxT5zbWm@2=Vun zI$BW)0>$>Z&|~9n+1>uojm3Uhv9LCUegMT`b{wCGea zm}p=3ulX5wXoV#RwBi!#6n=($e1-(Rj1JFId}dS;Y_Qd4%WrY~Z@his_iM`K{S8+B zdz6iOhtfUQD4#OJjA@E80*~-ZdPs@T-%?Id52zd8S_bzKp*334zg|Kx1D3Zt&sTM zjEFLxT;}@Riiz*d2r0wKWvvJnIzBetXCH}a~^|=)q-@62?G=H97f4Q&qIeGS_yzk13?@fa$ z?Vsk?U+!ytPWY+vzAG=jH!ZG3f7I7s?t6W1MaTCpf|ceE`ufW+*+cqotN{KID_Cz( xTKj5zb!khQD^mWn!sB~W`kMBaWft&Q@$tQBp(Xusnd@`L{x9WyS7dze{|`Wm#zg=C literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin b/src/Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin new file mode 100644 index 0000000000000000000000000000000000000000..db5bf73f7d5a0b5e436d336849c90bfbc24d76dc GIT binary patch literal 1024 zcmezOkD ShaderStageFlags.VertexBit, + ShaderStage.Geometry => ShaderStageFlags.GeometryBit, + ShaderStage.TessellationControl => ShaderStageFlags.TessellationControlBit, + ShaderStage.TessellationEvaluation => ShaderStageFlags.TessellationEvaluationBit, + ShaderStage.Fragment => ShaderStageFlags.FragmentBit, + ShaderStage.Compute => ShaderStageFlags.ComputeBit, + _ => LogInvalidAndReturn(stage, nameof(ShaderStage), (ShaderStageFlags)0), + }; + } + + public static PipelineStageFlags ConvertToPipelineStageFlags(this ShaderStage stage) + { + return stage switch + { + ShaderStage.Vertex => PipelineStageFlags.VertexShaderBit, + ShaderStage.Geometry => PipelineStageFlags.GeometryShaderBit, + ShaderStage.TessellationControl => PipelineStageFlags.TessellationControlShaderBit, + ShaderStage.TessellationEvaluation => PipelineStageFlags.TessellationEvaluationShaderBit, + ShaderStage.Fragment => PipelineStageFlags.FragmentShaderBit, + ShaderStage.Compute => PipelineStageFlags.ComputeShaderBit, + _ => LogInvalidAndReturn(stage, nameof(ShaderStage), (PipelineStageFlags)0), + }; + } + + public static ShaderStageFlags Convert(this ResourceStages stages) + { + ShaderStageFlags stageFlags = stages.HasFlag(ResourceStages.Compute) + ? ShaderStageFlags.ComputeBit + : ShaderStageFlags.None; + + if (stages.HasFlag(ResourceStages.Vertex)) + { + stageFlags |= ShaderStageFlags.VertexBit; + } + + if (stages.HasFlag(ResourceStages.TessellationControl)) + { + stageFlags |= ShaderStageFlags.TessellationControlBit; + } + + if (stages.HasFlag(ResourceStages.TessellationEvaluation)) + { + stageFlags |= ShaderStageFlags.TessellationEvaluationBit; + } + + if (stages.HasFlag(ResourceStages.Geometry)) + { + stageFlags |= ShaderStageFlags.GeometryBit; + } + + if (stages.HasFlag(ResourceStages.Fragment)) + { + stageFlags |= ShaderStageFlags.FragmentBit; + } + + return stageFlags; + } + + public static DescriptorType Convert(this ResourceType type) + { + return type switch + { + ResourceType.UniformBuffer => DescriptorType.UniformBuffer, + ResourceType.StorageBuffer => DescriptorType.StorageBuffer, + ResourceType.Texture => DescriptorType.SampledImage, + ResourceType.Sampler => DescriptorType.Sampler, + ResourceType.TextureAndSampler => DescriptorType.CombinedImageSampler, + ResourceType.Image => DescriptorType.StorageImage, + ResourceType.BufferTexture => DescriptorType.UniformTexelBuffer, + ResourceType.BufferImage => DescriptorType.StorageTexelBuffer, + _ => throw new ArgumentException($"Invalid resource type \"{type}\"."), + }; + } + + public static SamplerAddressMode Convert(this AddressMode mode) + { + return mode switch + { + AddressMode.Clamp => SamplerAddressMode.ClampToEdge, // TODO: Should be clamp. + AddressMode.Repeat => SamplerAddressMode.Repeat, + AddressMode.MirrorClamp => SamplerAddressMode.ClampToEdge, // TODO: Should be mirror clamp. + AddressMode.MirrorClampToEdge => SamplerAddressMode.MirrorClampToEdgeKhr, + AddressMode.MirrorClampToBorder => SamplerAddressMode.ClampToBorder, // TODO: Should be mirror clamp to border. + AddressMode.ClampToBorder => SamplerAddressMode.ClampToBorder, + AddressMode.MirroredRepeat => SamplerAddressMode.MirroredRepeat, + AddressMode.ClampToEdge => SamplerAddressMode.ClampToEdge, + _ => LogInvalidAndReturn(mode, nameof(AddressMode), SamplerAddressMode.ClampToEdge), // TODO: Should be clamp. + }; + } + + public static BlendFactor Convert(this GAL.BlendFactor factor) + { + return factor switch + { + GAL.BlendFactor.Zero or GAL.BlendFactor.ZeroGl => BlendFactor.Zero, + GAL.BlendFactor.One or GAL.BlendFactor.OneGl => BlendFactor.One, + GAL.BlendFactor.SrcColor or GAL.BlendFactor.SrcColorGl => BlendFactor.SrcColor, + GAL.BlendFactor.OneMinusSrcColor or GAL.BlendFactor.OneMinusSrcColorGl => BlendFactor.OneMinusSrcColor, + GAL.BlendFactor.SrcAlpha or GAL.BlendFactor.SrcAlphaGl => BlendFactor.SrcAlpha, + GAL.BlendFactor.OneMinusSrcAlpha or GAL.BlendFactor.OneMinusSrcAlphaGl => BlendFactor.OneMinusSrcAlpha, + GAL.BlendFactor.DstAlpha or GAL.BlendFactor.DstAlphaGl => BlendFactor.DstAlpha, + GAL.BlendFactor.OneMinusDstAlpha or GAL.BlendFactor.OneMinusDstAlphaGl => BlendFactor.OneMinusDstAlpha, + GAL.BlendFactor.DstColor or GAL.BlendFactor.DstColorGl => BlendFactor.DstColor, + GAL.BlendFactor.OneMinusDstColor or GAL.BlendFactor.OneMinusDstColorGl => BlendFactor.OneMinusDstColor, + GAL.BlendFactor.SrcAlphaSaturate or GAL.BlendFactor.SrcAlphaSaturateGl => BlendFactor.SrcAlphaSaturate, + GAL.BlendFactor.Src1Color or GAL.BlendFactor.Src1ColorGl => BlendFactor.Src1Color, + GAL.BlendFactor.OneMinusSrc1Color or GAL.BlendFactor.OneMinusSrc1ColorGl => BlendFactor.OneMinusSrc1Color, + GAL.BlendFactor.Src1Alpha or GAL.BlendFactor.Src1AlphaGl => BlendFactor.Src1Alpha, + GAL.BlendFactor.OneMinusSrc1Alpha or GAL.BlendFactor.OneMinusSrc1AlphaGl => BlendFactor.OneMinusSrc1Alpha, + GAL.BlendFactor.ConstantColor => BlendFactor.ConstantColor, + GAL.BlendFactor.OneMinusConstantColor => BlendFactor.OneMinusConstantColor, + GAL.BlendFactor.ConstantAlpha => BlendFactor.ConstantAlpha, + GAL.BlendFactor.OneMinusConstantAlpha => BlendFactor.OneMinusConstantAlpha, + _ => LogInvalidAndReturn(factor, nameof(GAL.BlendFactor), BlendFactor.Zero), + }; + } + + public static BlendOp Convert(this AdvancedBlendOp op) + { + return op switch + { + AdvancedBlendOp.Zero => BlendOp.ZeroExt, + AdvancedBlendOp.Src => BlendOp.SrcExt, + AdvancedBlendOp.Dst => BlendOp.DstExt, + AdvancedBlendOp.SrcOver => BlendOp.SrcOverExt, + AdvancedBlendOp.DstOver => BlendOp.DstOverExt, + AdvancedBlendOp.SrcIn => BlendOp.SrcInExt, + AdvancedBlendOp.DstIn => BlendOp.DstInExt, + AdvancedBlendOp.SrcOut => BlendOp.SrcOutExt, + AdvancedBlendOp.DstOut => BlendOp.DstOutExt, + AdvancedBlendOp.SrcAtop => BlendOp.SrcAtopExt, + AdvancedBlendOp.DstAtop => BlendOp.DstAtopExt, + AdvancedBlendOp.Xor => BlendOp.XorExt, + AdvancedBlendOp.Plus => BlendOp.PlusExt, + AdvancedBlendOp.PlusClamped => BlendOp.PlusClampedExt, + AdvancedBlendOp.PlusClampedAlpha => BlendOp.PlusClampedAlphaExt, + AdvancedBlendOp.PlusDarker => BlendOp.PlusDarkerExt, + AdvancedBlendOp.Multiply => BlendOp.MultiplyExt, + AdvancedBlendOp.Screen => BlendOp.ScreenExt, + AdvancedBlendOp.Overlay => BlendOp.OverlayExt, + AdvancedBlendOp.Darken => BlendOp.DarkenExt, + AdvancedBlendOp.Lighten => BlendOp.LightenExt, + AdvancedBlendOp.ColorDodge => BlendOp.ColordodgeExt, + AdvancedBlendOp.ColorBurn => BlendOp.ColorburnExt, + AdvancedBlendOp.HardLight => BlendOp.HardlightExt, + AdvancedBlendOp.SoftLight => BlendOp.SoftlightExt, + AdvancedBlendOp.Difference => BlendOp.DifferenceExt, + AdvancedBlendOp.Minus => BlendOp.MinusExt, + AdvancedBlendOp.MinusClamped => BlendOp.MinusClampedExt, + AdvancedBlendOp.Exclusion => BlendOp.ExclusionExt, + AdvancedBlendOp.Contrast => BlendOp.ContrastExt, + AdvancedBlendOp.Invert => BlendOp.InvertExt, + AdvancedBlendOp.InvertRGB => BlendOp.InvertRgbExt, + AdvancedBlendOp.InvertOvg => BlendOp.InvertOvgExt, + AdvancedBlendOp.LinearDodge => BlendOp.LineardodgeExt, + AdvancedBlendOp.LinearBurn => BlendOp.LinearburnExt, + AdvancedBlendOp.VividLight => BlendOp.VividlightExt, + AdvancedBlendOp.LinearLight => BlendOp.LinearlightExt, + AdvancedBlendOp.PinLight => BlendOp.PinlightExt, + AdvancedBlendOp.HardMix => BlendOp.HardmixExt, + AdvancedBlendOp.Red => BlendOp.RedExt, + AdvancedBlendOp.Green => BlendOp.GreenExt, + AdvancedBlendOp.Blue => BlendOp.BlueExt, + AdvancedBlendOp.HslHue => BlendOp.HslHueExt, + AdvancedBlendOp.HslSaturation => BlendOp.HslSaturationExt, + AdvancedBlendOp.HslColor => BlendOp.HslColorExt, + AdvancedBlendOp.HslLuminosity => BlendOp.HslLuminosityExt, + _ => LogInvalidAndReturn(op, nameof(AdvancedBlendOp), BlendOp.Add), + }; + } + + public static BlendOp Convert(this GAL.BlendOp op) + { + return op switch + { + GAL.BlendOp.Add or GAL.BlendOp.AddGl => BlendOp.Add, + GAL.BlendOp.Subtract or GAL.BlendOp.SubtractGl => BlendOp.Subtract, + GAL.BlendOp.ReverseSubtract or GAL.BlendOp.ReverseSubtractGl => BlendOp.ReverseSubtract, + GAL.BlendOp.Minimum or GAL.BlendOp.MinimumGl => BlendOp.Min, + GAL.BlendOp.Maximum or GAL.BlendOp.MaximumGl => BlendOp.Max, + _ => LogInvalidAndReturn(op, nameof(GAL.BlendOp), BlendOp.Add), + }; + } + + public static BlendOverlapEXT Convert(this AdvancedBlendOverlap overlap) + { + return overlap switch + { + AdvancedBlendOverlap.Uncorrelated => BlendOverlapEXT.UncorrelatedExt, + AdvancedBlendOverlap.Disjoint => BlendOverlapEXT.DisjointExt, + AdvancedBlendOverlap.Conjoint => BlendOverlapEXT.ConjointExt, + _ => LogInvalidAndReturn(overlap, nameof(AdvancedBlendOverlap), BlendOverlapEXT.UncorrelatedExt), + }; + } + + public static CompareOp Convert(this GAL.CompareOp op) + { + return op switch + { + GAL.CompareOp.Never or GAL.CompareOp.NeverGl => CompareOp.Never, + GAL.CompareOp.Less or GAL.CompareOp.LessGl => CompareOp.Less, + GAL.CompareOp.Equal or GAL.CompareOp.EqualGl => CompareOp.Equal, + GAL.CompareOp.LessOrEqual or GAL.CompareOp.LessOrEqualGl => CompareOp.LessOrEqual, + GAL.CompareOp.Greater or GAL.CompareOp.GreaterGl => CompareOp.Greater, + GAL.CompareOp.NotEqual or GAL.CompareOp.NotEqualGl => CompareOp.NotEqual, + GAL.CompareOp.GreaterOrEqual or GAL.CompareOp.GreaterOrEqualGl => CompareOp.GreaterOrEqual, + GAL.CompareOp.Always or GAL.CompareOp.AlwaysGl => CompareOp.Always, + _ => LogInvalidAndReturn(op, nameof(GAL.CompareOp), CompareOp.Never), + }; + } + + public static CullModeFlags Convert(this Face face) + { + return face switch + { + Face.Back => CullModeFlags.BackBit, + Face.Front => CullModeFlags.FrontBit, + Face.FrontAndBack => CullModeFlags.FrontAndBack, + _ => LogInvalidAndReturn(face, nameof(Face), CullModeFlags.BackBit), + }; + } + + public static FrontFace Convert(this GAL.FrontFace frontFace) + { + // Flipped to account for origin differences. + return frontFace switch + { + GAL.FrontFace.Clockwise => FrontFace.CounterClockwise, + GAL.FrontFace.CounterClockwise => FrontFace.Clockwise, + _ => LogInvalidAndReturn(frontFace, nameof(GAL.FrontFace), FrontFace.Clockwise), + }; + } + + public static IndexType Convert(this GAL.IndexType type) + { + return type switch + { + GAL.IndexType.UByte => IndexType.Uint8Ext, + GAL.IndexType.UShort => IndexType.Uint16, + GAL.IndexType.UInt => IndexType.Uint32, + _ => LogInvalidAndReturn(type, nameof(GAL.IndexType), IndexType.Uint16), + }; + } + + public static Filter Convert(this MagFilter filter) + { + return filter switch + { + MagFilter.Nearest => Filter.Nearest, + MagFilter.Linear => Filter.Linear, + _ => LogInvalidAndReturn(filter, nameof(MagFilter), Filter.Nearest), + }; + } + + public static (Filter, SamplerMipmapMode) Convert(this MinFilter filter) + { + return filter switch + { + MinFilter.Nearest => (Filter.Nearest, SamplerMipmapMode.Nearest), + MinFilter.Linear => (Filter.Linear, SamplerMipmapMode.Nearest), + MinFilter.NearestMipmapNearest => (Filter.Nearest, SamplerMipmapMode.Nearest), + MinFilter.LinearMipmapNearest => (Filter.Linear, SamplerMipmapMode.Nearest), + MinFilter.NearestMipmapLinear => (Filter.Nearest, SamplerMipmapMode.Linear), + MinFilter.LinearMipmapLinear => (Filter.Linear, SamplerMipmapMode.Linear), + _ => LogInvalidAndReturn(filter, nameof(MinFilter), (Filter.Nearest, SamplerMipmapMode.Nearest)), + }; + } + + public static PrimitiveTopology Convert(this GAL.PrimitiveTopology topology) + { + return topology switch + { + GAL.PrimitiveTopology.Points => PrimitiveTopology.PointList, + GAL.PrimitiveTopology.Lines => PrimitiveTopology.LineList, + GAL.PrimitiveTopology.LineStrip => PrimitiveTopology.LineStrip, + GAL.PrimitiveTopology.Triangles => PrimitiveTopology.TriangleList, + GAL.PrimitiveTopology.TriangleStrip => PrimitiveTopology.TriangleStrip, + GAL.PrimitiveTopology.TriangleFan => PrimitiveTopology.TriangleFan, + GAL.PrimitiveTopology.LinesAdjacency => PrimitiveTopology.LineListWithAdjacency, + GAL.PrimitiveTopology.LineStripAdjacency => PrimitiveTopology.LineStripWithAdjacency, + GAL.PrimitiveTopology.TrianglesAdjacency => PrimitiveTopology.TriangleListWithAdjacency, + GAL.PrimitiveTopology.TriangleStripAdjacency => PrimitiveTopology.TriangleStripWithAdjacency, + GAL.PrimitiveTopology.Patches => PrimitiveTopology.PatchList, + GAL.PrimitiveTopology.Polygon => PrimitiveTopology.TriangleFan, + GAL.PrimitiveTopology.Quads => throw new NotSupportedException("Quad topology is not available in Vulkan."), + GAL.PrimitiveTopology.QuadStrip => throw new NotSupportedException("QuadStrip topology is not available in Vulkan."), + _ => LogInvalidAndReturn(topology, nameof(GAL.PrimitiveTopology), PrimitiveTopology.TriangleList), + }; + } + + public static StencilOp Convert(this GAL.StencilOp op) + { + return op switch + { + GAL.StencilOp.Keep or GAL.StencilOp.KeepGl => StencilOp.Keep, + GAL.StencilOp.Zero or GAL.StencilOp.ZeroGl => StencilOp.Zero, + GAL.StencilOp.Replace or GAL.StencilOp.ReplaceGl => StencilOp.Replace, + GAL.StencilOp.IncrementAndClamp or GAL.StencilOp.IncrementAndClampGl => StencilOp.IncrementAndClamp, + GAL.StencilOp.DecrementAndClamp or GAL.StencilOp.DecrementAndClampGl => StencilOp.DecrementAndClamp, + GAL.StencilOp.Invert or GAL.StencilOp.InvertGl => StencilOp.Invert, + GAL.StencilOp.IncrementAndWrap or GAL.StencilOp.IncrementAndWrapGl => StencilOp.IncrementAndWrap, + GAL.StencilOp.DecrementAndWrap or GAL.StencilOp.DecrementAndWrapGl => StencilOp.DecrementAndWrap, + _ => LogInvalidAndReturn(op, nameof(GAL.StencilOp), StencilOp.Keep), + }; + } + + public static ComponentSwizzle Convert(this SwizzleComponent swizzleComponent) + { + return swizzleComponent switch + { + SwizzleComponent.Zero => ComponentSwizzle.Zero, + SwizzleComponent.One => ComponentSwizzle.One, + SwizzleComponent.Red => ComponentSwizzle.R, + SwizzleComponent.Green => ComponentSwizzle.G, + SwizzleComponent.Blue => ComponentSwizzle.B, + SwizzleComponent.Alpha => ComponentSwizzle.A, + _ => LogInvalidAndReturn(swizzleComponent, nameof(SwizzleComponent), ComponentSwizzle.Zero), + }; + } + + public static ImageType Convert(this Target target) + { + return target switch + { + Target.Texture1D or + Target.Texture1DArray or + Target.TextureBuffer => ImageType.Type1D, + Target.Texture2D or + Target.Texture2DArray or + Target.Texture2DMultisample or + Target.Cubemap or + Target.CubemapArray => ImageType.Type2D, + Target.Texture3D => ImageType.Type3D, + _ => LogInvalidAndReturn(target, nameof(Target), ImageType.Type2D), + }; + } + + public static ImageViewType ConvertView(this Target target) + { + return target switch + { + Target.Texture1D => ImageViewType.Type1D, + Target.Texture2D or Target.Texture2DMultisample => ImageViewType.Type2D, + Target.Texture3D => ImageViewType.Type3D, + Target.Texture1DArray => ImageViewType.Type1DArray, + Target.Texture2DArray => ImageViewType.Type2DArray, + Target.Cubemap => ImageViewType.TypeCube, + Target.CubemapArray => ImageViewType.TypeCubeArray, + _ => LogInvalidAndReturn(target, nameof(Target), ImageViewType.Type2D), + }; + } + + public static ImageAspectFlags ConvertAspectFlags(this Format format) + { + return format switch + { + Format.D16Unorm or Format.D32Float or Format.X8UintD24Unorm => ImageAspectFlags.DepthBit, + Format.S8Uint => ImageAspectFlags.StencilBit, + Format.D24UnormS8Uint or + Format.D32FloatS8Uint or + Format.S8UintD24Unorm => ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit, + _ => ImageAspectFlags.ColorBit, + }; + } + + public static ImageAspectFlags ConvertAspectFlags(this Format format, DepthStencilMode depthStencilMode) + { + return format switch + { + Format.D16Unorm or Format.D32Float or Format.X8UintD24Unorm => ImageAspectFlags.DepthBit, + Format.S8Uint => ImageAspectFlags.StencilBit, + Format.D24UnormS8Uint or + Format.D32FloatS8Uint or + Format.S8UintD24Unorm => depthStencilMode == DepthStencilMode.Stencil ? ImageAspectFlags.StencilBit : ImageAspectFlags.DepthBit, + _ => ImageAspectFlags.ColorBit, + }; + } + + public static LogicOp Convert(this LogicalOp op) + { + return op switch + { + LogicalOp.Clear => LogicOp.Clear, + LogicalOp.And => LogicOp.And, + LogicalOp.AndReverse => LogicOp.AndReverse, + LogicalOp.Copy => LogicOp.Copy, + LogicalOp.AndInverted => LogicOp.AndInverted, + LogicalOp.Noop => LogicOp.NoOp, + LogicalOp.Xor => LogicOp.Xor, + LogicalOp.Or => LogicOp.Or, + LogicalOp.Nor => LogicOp.Nor, + LogicalOp.Equiv => LogicOp.Equivalent, + LogicalOp.Invert => LogicOp.Invert, + LogicalOp.OrReverse => LogicOp.OrReverse, + LogicalOp.CopyInverted => LogicOp.CopyInverted, + LogicalOp.OrInverted => LogicOp.OrInverted, + LogicalOp.Nand => LogicOp.Nand, + LogicalOp.Set => LogicOp.Set, + _ => LogInvalidAndReturn(op, nameof(LogicalOp), LogicOp.Copy), + }; + } + + public static BufferAllocationType Convert(this BufferAccess access) + { + BufferAccess memType = access & BufferAccess.MemoryTypeMask; + + if (memType == BufferAccess.HostMemory || access.HasFlag(BufferAccess.Stream)) + { + return BufferAllocationType.HostMapped; + } + else if (memType == BufferAccess.DeviceMemory) + { + return BufferAllocationType.DeviceLocal; + } + else if (memType == BufferAccess.DeviceMemoryMapped) + { + return BufferAllocationType.DeviceLocalMapped; + } + + return BufferAllocationType.Auto; + } + + private static T2 LogInvalidAndReturn(T1 value, string name, T2 defaultValue = default) + { + Logger.Debug?.Print(LogClass.Gpu, $"Invalid {name} enum value: {value}."); + + return defaultValue; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/FeedbackLoopAspects.cs b/src/Ryujinx.Graphics.Vulkan/FeedbackLoopAspects.cs new file mode 100644 index 00000000..22f73679 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/FeedbackLoopAspects.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + [Flags] + internal enum FeedbackLoopAspects + { + None = 0, + Color = 1 << 0, + Depth = 1 << 1, + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/FenceHelper.cs b/src/Ryujinx.Graphics.Vulkan/FenceHelper.cs new file mode 100644 index 00000000..6649af7f --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/FenceHelper.cs @@ -0,0 +1,30 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + static class FenceHelper + { + private const ulong DefaultTimeout = 100000000; // 100ms + + public static bool AnySignaled(Vk api, Device device, ReadOnlySpan fences, ulong timeout = 0) + { + return api.WaitForFences(device, (uint)fences.Length, fences, false, timeout) == Result.Success; + } + + public static bool AllSignaled(Vk api, Device device, ReadOnlySpan fences, ulong timeout = 0) + { + return api.WaitForFences(device, (uint)fences.Length, fences, true, timeout) == Result.Success; + } + + public static void WaitAllIndefinitely(Vk api, Device device, ReadOnlySpan fences) + { + Result result; + while ((result = api.WaitForFences(device, (uint)fences.Length, fences, true, DefaultTimeout)) == Result.Timeout) + { + // Keep waiting while the fence is not signaled. + } + result.ThrowOnError(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/FenceHolder.cs b/src/Ryujinx.Graphics.Vulkan/FenceHolder.cs new file mode 100644 index 00000000..0cdb93f2 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/FenceHolder.cs @@ -0,0 +1,159 @@ +using Silk.NET.Vulkan; +using System; +using System.Threading; + +namespace Ryujinx.Graphics.Vulkan +{ + class FenceHolder : IDisposable + { + private readonly Vk _api; + private readonly Device _device; + private Fence _fence; + private int _referenceCount; + private int _lock; + private readonly bool _concurrentWaitUnsupported; + private bool _disposed; + + public unsafe FenceHolder(Vk api, Device device, bool concurrentWaitUnsupported) + { + _api = api; + _device = device; + _concurrentWaitUnsupported = concurrentWaitUnsupported; + + var fenceCreateInfo = new FenceCreateInfo + { + SType = StructureType.FenceCreateInfo, + }; + + api.CreateFence(device, in fenceCreateInfo, null, out _fence).ThrowOnError(); + + _referenceCount = 1; + } + + public Fence GetUnsafe() + { + return _fence; + } + + public bool TryGet(out Fence fence) + { + int lastValue; + do + { + lastValue = _referenceCount; + + if (lastValue == 0) + { + fence = default; + return false; + } + } + while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue); + + if (_concurrentWaitUnsupported) + { + AcquireLock(); + } + + fence = _fence; + return true; + } + + public Fence Get() + { + Interlocked.Increment(ref _referenceCount); + return _fence; + } + + public void PutLock() + { + Put(); + + if (_concurrentWaitUnsupported) + { + ReleaseLock(); + } + } + + public void Put() + { + if (Interlocked.Decrement(ref _referenceCount) == 0) + { + _api.DestroyFence(_device, _fence, Span.Empty); + _fence = default; + } + } + + private void AcquireLock() + { + while (!TryAcquireLock()) + { + Thread.SpinWait(32); + } + } + + private bool TryAcquireLock() + { + return Interlocked.Exchange(ref _lock, 1) == 0; + } + + private void ReleaseLock() + { + Interlocked.Exchange(ref _lock, 0); + } + + public void Wait() + { + if (_concurrentWaitUnsupported) + { + AcquireLock(); + + try + { + FenceHelper.WaitAllIndefinitely(_api, _device, stackalloc Fence[] { _fence }); + } + finally + { + ReleaseLock(); + } + } + else + { + FenceHelper.WaitAllIndefinitely(_api, _device, stackalloc Fence[] { _fence }); + } + } + + public bool IsSignaled() + { + if (_concurrentWaitUnsupported) + { + if (!TryAcquireLock()) + { + return false; + } + + try + { + return FenceHelper.AllSignaled(_api, _device, stackalloc Fence[] { _fence }); + } + finally + { + ReleaseLock(); + } + } + else + { + return FenceHelper.AllSignaled(_api, _device, stackalloc Fence[] { _fence }); + } + } + + public void Dispose() + { + if (!_disposed) + { + Put(); + _disposed = true; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs b/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs new file mode 100644 index 00000000..9ea8cec4 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs @@ -0,0 +1,233 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using Format = Ryujinx.Graphics.GAL.Format; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + class FormatCapabilities + { + private static readonly GAL.Format[] _scaledFormats = { + GAL.Format.R8Uscaled, + GAL.Format.R8Sscaled, + GAL.Format.R16Uscaled, + GAL.Format.R16Sscaled, + GAL.Format.R8G8Uscaled, + GAL.Format.R8G8Sscaled, + GAL.Format.R16G16Uscaled, + GAL.Format.R16G16Sscaled, + GAL.Format.R8G8B8Uscaled, + GAL.Format.R8G8B8Sscaled, + GAL.Format.R16G16B16Uscaled, + GAL.Format.R16G16B16Sscaled, + GAL.Format.R8G8B8A8Uscaled, + GAL.Format.R8G8B8A8Sscaled, + GAL.Format.R16G16B16A16Uscaled, + GAL.Format.R16G16B16A16Sscaled, + GAL.Format.R10G10B10A2Uscaled, + GAL.Format.R10G10B10A2Sscaled, + }; + + private static readonly GAL.Format[] _intFormats = { + GAL.Format.R8Uint, + GAL.Format.R8Sint, + GAL.Format.R16Uint, + GAL.Format.R16Sint, + GAL.Format.R8G8Uint, + GAL.Format.R8G8Sint, + GAL.Format.R16G16Uint, + GAL.Format.R16G16Sint, + GAL.Format.R8G8B8Uint, + GAL.Format.R8G8B8Sint, + GAL.Format.R16G16B16Uint, + GAL.Format.R16G16B16Sint, + GAL.Format.R8G8B8A8Uint, + GAL.Format.R8G8B8A8Sint, + GAL.Format.R16G16B16A16Uint, + GAL.Format.R16G16B16A16Sint, + GAL.Format.R10G10B10A2Uint, + GAL.Format.R10G10B10A2Sint, + }; + + private readonly FormatFeatureFlags[] _bufferTable; + private readonly FormatFeatureFlags[] _optimalTable; + + private readonly Vk _api; + private readonly PhysicalDevice _physicalDevice; + + public FormatCapabilities(Vk api, PhysicalDevice physicalDevice) + { + _api = api; + _physicalDevice = physicalDevice; + + int totalFormats = Enum.GetNames(typeof(Format)).Length; + + _bufferTable = new FormatFeatureFlags[totalFormats]; + _optimalTable = new FormatFeatureFlags[totalFormats]; + } + + public bool BufferFormatsSupport(FormatFeatureFlags flags, params Format[] formats) + { + foreach (Format format in formats) + { + if (!BufferFormatSupports(flags, format)) + { + return false; + } + } + + return true; + } + + public bool OptimalFormatsSupport(FormatFeatureFlags flags, params Format[] formats) + { + foreach (Format format in formats) + { + if (!OptimalFormatSupports(flags, format)) + { + return false; + } + } + + return true; + } + + public bool BufferFormatSupports(FormatFeatureFlags flags, Format format) + { + var formatFeatureFlags = _bufferTable[(int)format]; + + if (formatFeatureFlags == 0) + { + _api.GetPhysicalDeviceFormatProperties(_physicalDevice, FormatTable.GetFormat(format), out var fp); + formatFeatureFlags = fp.BufferFeatures; + _bufferTable[(int)format] = formatFeatureFlags; + } + + return (formatFeatureFlags & flags) == flags; + } + + public bool SupportsScaledVertexFormats() + { + // We want to check is all scaled formats are supported, + // but if the integer variant is not supported either, + // then the format is likely not supported at all, + // we ignore formats that are entirely unsupported here. + + for (int i = 0; i < _scaledFormats.Length; i++) + { + if (!BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, _scaledFormats[i]) && + BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, _intFormats[i])) + { + return false; + } + } + + return true; + } + + public bool BufferFormatSupports(FormatFeatureFlags flags, VkFormat format) + { + _api.GetPhysicalDeviceFormatProperties(_physicalDevice, format, out var fp); + + return (fp.BufferFeatures & flags) == flags; + } + + public bool OptimalFormatSupports(FormatFeatureFlags flags, Format format) + { + var formatFeatureFlags = _optimalTable[(int)format]; + + if (formatFeatureFlags == 0) + { + _api.GetPhysicalDeviceFormatProperties(_physicalDevice, FormatTable.GetFormat(format), out var fp); + formatFeatureFlags = fp.OptimalTilingFeatures; + _optimalTable[(int)format] = formatFeatureFlags; + } + + return (formatFeatureFlags & flags) == flags; + } + + public VkFormat ConvertToVkFormat(Format srcFormat) + { + var format = FormatTable.GetFormat(srcFormat); + + var requiredFeatures = FormatFeatureFlags.SampledImageBit | + FormatFeatureFlags.TransferSrcBit | + FormatFeatureFlags.TransferDstBit; + + if (srcFormat.IsDepthOrStencil()) + { + requiredFeatures |= FormatFeatureFlags.DepthStencilAttachmentBit; + } + else if (srcFormat.IsRtColorCompatible()) + { + requiredFeatures |= FormatFeatureFlags.ColorAttachmentBit; + } + + if (srcFormat.IsImageCompatible()) + { + requiredFeatures |= FormatFeatureFlags.StorageImageBit; + } + + if (!OptimalFormatSupports(requiredFeatures, srcFormat) || (IsD24S8(srcFormat) && VulkanConfiguration.ForceD24S8Unsupported)) + { + // The format is not supported. Can we convert it to a higher precision format? + if (IsD24S8(srcFormat)) + { + format = VkFormat.D32SfloatS8Uint; + } + else if (srcFormat == Format.R4G4B4A4Unorm) + { + format = VkFormat.R4G4B4A4UnormPack16; + } + else + { + Logger.Error?.Print(LogClass.Gpu, $"Format {srcFormat} is not supported by the host."); + } + } + + return format; + } + + public VkFormat ConvertToVertexVkFormat(Format srcFormat) + { + var format = FormatTable.GetFormat(srcFormat); + + if (!BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, srcFormat) || + (IsRGB16IntFloat(srcFormat) && VulkanConfiguration.ForceRGB16IntFloatUnsupported)) + { + // The format is not supported. Can we convert it to an alternative format? + switch (srcFormat) + { + case Format.R16G16B16Float: + format = VkFormat.R16G16B16A16Sfloat; + break; + case Format.R16G16B16Sint: + format = VkFormat.R16G16B16A16Sint; + break; + case Format.R16G16B16Uint: + format = VkFormat.R16G16B16A16Uint; + break; + default: + Logger.Error?.Print(LogClass.Gpu, $"Format {srcFormat} is not supported by the host."); + break; + } + } + + return format; + } + + public static bool IsD24S8(Format format) + { + return format == Format.D24UnormS8Uint || format == Format.S8UintD24Unorm || format == Format.X8UintD24Unorm; + } + + private static bool IsRGB16IntFloat(Format format) + { + return format == Format.R16G16B16Float || + format == Format.R16G16B16Sint || + format == Format.R16G16B16Uint; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/FormatConverter.cs b/src/Ryujinx.Graphics.Vulkan/FormatConverter.cs new file mode 100644 index 00000000..33472ae4 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/FormatConverter.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + class FormatConverter + { + public static void ConvertD24S8ToD32FS8(Span output, ReadOnlySpan input) + { + const float UnormToFloat = 1f / 0xffffff; + + Span outputUint = MemoryMarshal.Cast(output); + ReadOnlySpan inputUint = MemoryMarshal.Cast(input); + + int i = 0; + + for (; i < inputUint.Length; i++) + { + uint depthStencil = inputUint[i]; + uint depth = depthStencil >> 8; + uint stencil = depthStencil & 0xff; + + int j = i * 2; + + outputUint[j] = (uint)BitConverter.SingleToInt32Bits(depth * UnormToFloat); + outputUint[j + 1] = stencil; + } + } + + public static void ConvertD32FS8ToD24S8(Span output, ReadOnlySpan input) + { + Span outputUint = MemoryMarshal.Cast(output); + ReadOnlySpan inputUint = MemoryMarshal.Cast(input); + + int i = 0; + + for (; i < inputUint.Length; i += 2) + { + float depth = BitConverter.Int32BitsToSingle((int)inputUint[i]); + uint stencil = inputUint[i + 1]; + uint depthStencil = (Math.Clamp((uint)(depth * 0xffffff), 0, 0xffffff) << 8) | (stencil & 0xff); + + int j = i >> 1; + + outputUint[j] = depthStencil; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/FormatTable.cs b/src/Ryujinx.Graphics.Vulkan/FormatTable.cs new file mode 100644 index 00000000..98796d9b --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/FormatTable.cs @@ -0,0 +1,358 @@ +using Ryujinx.Graphics.GAL; +using System; +using System.Collections.Generic; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + static class FormatTable + { + private static readonly VkFormat[] _table; + private static readonly Dictionary _reverseMap; + + static FormatTable() + { + _table = new VkFormat[Enum.GetNames(typeof(Format)).Length]; + _reverseMap = new Dictionary(); + +#pragma warning disable IDE0055 // Disable formatting + Add(Format.R8Unorm, VkFormat.R8Unorm); + Add(Format.R8Snorm, VkFormat.R8SNorm); + Add(Format.R8Uint, VkFormat.R8Uint); + Add(Format.R8Sint, VkFormat.R8Sint); + Add(Format.R16Float, VkFormat.R16Sfloat); + Add(Format.R16Unorm, VkFormat.R16Unorm); + Add(Format.R16Snorm, VkFormat.R16SNorm); + Add(Format.R16Uint, VkFormat.R16Uint); + Add(Format.R16Sint, VkFormat.R16Sint); + Add(Format.R32Float, VkFormat.R32Sfloat); + Add(Format.R32Uint, VkFormat.R32Uint); + Add(Format.R32Sint, VkFormat.R32Sint); + Add(Format.R8G8Unorm, VkFormat.R8G8Unorm); + Add(Format.R8G8Snorm, VkFormat.R8G8SNorm); + Add(Format.R8G8Uint, VkFormat.R8G8Uint); + Add(Format.R8G8Sint, VkFormat.R8G8Sint); + Add(Format.R16G16Float, VkFormat.R16G16Sfloat); + Add(Format.R16G16Unorm, VkFormat.R16G16Unorm); + Add(Format.R16G16Snorm, VkFormat.R16G16SNorm); + Add(Format.R16G16Uint, VkFormat.R16G16Uint); + Add(Format.R16G16Sint, VkFormat.R16G16Sint); + Add(Format.R32G32Float, VkFormat.R32G32Sfloat); + Add(Format.R32G32Uint, VkFormat.R32G32Uint); + Add(Format.R32G32Sint, VkFormat.R32G32Sint); + Add(Format.R8G8B8Unorm, VkFormat.R8G8B8Unorm); + Add(Format.R8G8B8Snorm, VkFormat.R8G8B8SNorm); + Add(Format.R8G8B8Uint, VkFormat.R8G8B8Uint); + Add(Format.R8G8B8Sint, VkFormat.R8G8B8Sint); + Add(Format.R16G16B16Float, VkFormat.R16G16B16Sfloat); + Add(Format.R16G16B16Unorm, VkFormat.R16G16B16Unorm); + Add(Format.R16G16B16Snorm, VkFormat.R16G16B16SNorm); + Add(Format.R16G16B16Uint, VkFormat.R16G16B16Uint); + Add(Format.R16G16B16Sint, VkFormat.R16G16B16Sint); + Add(Format.R32G32B32Float, VkFormat.R32G32B32Sfloat); + Add(Format.R32G32B32Uint, VkFormat.R32G32B32Uint); + Add(Format.R32G32B32Sint, VkFormat.R32G32B32Sint); + Add(Format.R8G8B8A8Unorm, VkFormat.R8G8B8A8Unorm); + Add(Format.R8G8B8A8Snorm, VkFormat.R8G8B8A8SNorm); + Add(Format.R8G8B8A8Uint, VkFormat.R8G8B8A8Uint); + Add(Format.R8G8B8A8Sint, VkFormat.R8G8B8A8Sint); + Add(Format.R16G16B16A16Float, VkFormat.R16G16B16A16Sfloat); + Add(Format.R16G16B16A16Unorm, VkFormat.R16G16B16A16Unorm); + Add(Format.R16G16B16A16Snorm, VkFormat.R16G16B16A16SNorm); + Add(Format.R16G16B16A16Uint, VkFormat.R16G16B16A16Uint); + Add(Format.R16G16B16A16Sint, VkFormat.R16G16B16A16Sint); + Add(Format.R32G32B32A32Float, VkFormat.R32G32B32A32Sfloat); + Add(Format.R32G32B32A32Uint, VkFormat.R32G32B32A32Uint); + Add(Format.R32G32B32A32Sint, VkFormat.R32G32B32A32Sint); + Add(Format.S8Uint, VkFormat.S8Uint); + Add(Format.D16Unorm, VkFormat.D16Unorm); + Add(Format.S8UintD24Unorm, VkFormat.D24UnormS8Uint); + Add(Format.X8UintD24Unorm, VkFormat.X8D24UnormPack32); + Add(Format.D32Float, VkFormat.D32Sfloat); + Add(Format.D24UnormS8Uint, VkFormat.D24UnormS8Uint); + Add(Format.D32FloatS8Uint, VkFormat.D32SfloatS8Uint); + Add(Format.R8G8B8A8Srgb, VkFormat.R8G8B8A8Srgb); + Add(Format.R4G4Unorm, VkFormat.R4G4UnormPack8); + Add(Format.R4G4B4A4Unorm, VkFormat.A4B4G4R4UnormPack16Ext); + Add(Format.R5G5B5X1Unorm, VkFormat.A1R5G5B5UnormPack16); + Add(Format.R5G5B5A1Unorm, VkFormat.A1R5G5B5UnormPack16); + Add(Format.R5G6B5Unorm, VkFormat.R5G6B5UnormPack16); + Add(Format.R10G10B10A2Unorm, VkFormat.A2B10G10R10UnormPack32); + Add(Format.R10G10B10A2Uint, VkFormat.A2B10G10R10UintPack32); + Add(Format.R11G11B10Float, VkFormat.B10G11R11UfloatPack32); + Add(Format.R9G9B9E5Float, VkFormat.E5B9G9R9UfloatPack32); + Add(Format.Bc1RgbaUnorm, VkFormat.BC1RgbaUnormBlock); + Add(Format.Bc2Unorm, VkFormat.BC2UnormBlock); + Add(Format.Bc3Unorm, VkFormat.BC3UnormBlock); + Add(Format.Bc1RgbaSrgb, VkFormat.BC1RgbaSrgbBlock); + Add(Format.Bc2Srgb, VkFormat.BC2SrgbBlock); + Add(Format.Bc3Srgb, VkFormat.BC3SrgbBlock); + Add(Format.Bc4Unorm, VkFormat.BC4UnormBlock); + Add(Format.Bc4Snorm, VkFormat.BC4SNormBlock); + Add(Format.Bc5Unorm, VkFormat.BC5UnormBlock); + Add(Format.Bc5Snorm, VkFormat.BC5SNormBlock); + Add(Format.Bc7Unorm, VkFormat.BC7UnormBlock); + Add(Format.Bc7Srgb, VkFormat.BC7SrgbBlock); + Add(Format.Bc6HSfloat, VkFormat.BC6HSfloatBlock); + Add(Format.Bc6HUfloat, VkFormat.BC6HUfloatBlock); + Add(Format.Etc2RgbUnorm, VkFormat.Etc2R8G8B8UnormBlock); + Add(Format.Etc2RgbaUnorm, VkFormat.Etc2R8G8B8A8UnormBlock); + Add(Format.Etc2RgbPtaUnorm, VkFormat.Etc2R8G8B8A1UnormBlock); + Add(Format.Etc2RgbSrgb, VkFormat.Etc2R8G8B8SrgbBlock); + Add(Format.Etc2RgbaSrgb, VkFormat.Etc2R8G8B8A8SrgbBlock); + Add(Format.Etc2RgbPtaSrgb, VkFormat.Etc2R8G8B8A1SrgbBlock); + Add(Format.R8Uscaled, VkFormat.R8Uscaled); + Add(Format.R8Sscaled, VkFormat.R8Sscaled); + Add(Format.R16Uscaled, VkFormat.R16Uscaled); + Add(Format.R16Sscaled, VkFormat.R16Sscaled); + // Add(Format.R32Uscaled, VkFormat.R32Uscaled); + // Add(Format.R32Sscaled, VkFormat.R32Sscaled); + Add(Format.R8G8Uscaled, VkFormat.R8G8Uscaled); + Add(Format.R8G8Sscaled, VkFormat.R8G8Sscaled); + Add(Format.R16G16Uscaled, VkFormat.R16G16Uscaled); + Add(Format.R16G16Sscaled, VkFormat.R16G16Sscaled); + // Add(Format.R32G32Uscaled, VkFormat.R32G32Uscaled); + // Add(Format.R32G32Sscaled, VkFormat.R32G32Sscaled); + Add(Format.R8G8B8Uscaled, VkFormat.R8G8B8Uscaled); + Add(Format.R8G8B8Sscaled, VkFormat.R8G8B8Sscaled); + Add(Format.R16G16B16Uscaled, VkFormat.R16G16B16Uscaled); + Add(Format.R16G16B16Sscaled, VkFormat.R16G16B16Sscaled); + // Add(Format.R32G32B32Uscaled, VkFormat.R32G32B32Uscaled); + // Add(Format.R32G32B32Sscaled, VkFormat.R32G32B32Sscaled); + Add(Format.R8G8B8A8Uscaled, VkFormat.R8G8B8A8Uscaled); + Add(Format.R8G8B8A8Sscaled, VkFormat.R8G8B8A8Sscaled); + Add(Format.R16G16B16A16Uscaled, VkFormat.R16G16B16A16Uscaled); + Add(Format.R16G16B16A16Sscaled, VkFormat.R16G16B16A16Sscaled); + // Add(Format.R32G32B32A32Uscaled, VkFormat.R32G32B32A32Uscaled); + // Add(Format.R32G32B32A32Sscaled, VkFormat.R32G32B32A32Sscaled); + Add(Format.R10G10B10A2Snorm, VkFormat.A2B10G10R10SNormPack32); + Add(Format.R10G10B10A2Sint, VkFormat.A2B10G10R10SintPack32); + Add(Format.R10G10B10A2Uscaled, VkFormat.A2B10G10R10UscaledPack32); + Add(Format.R10G10B10A2Sscaled, VkFormat.A2B10G10R10SscaledPack32); + Add(Format.Astc4x4Unorm, VkFormat.Astc4x4UnormBlock); + Add(Format.Astc5x4Unorm, VkFormat.Astc5x4UnormBlock); + Add(Format.Astc5x5Unorm, VkFormat.Astc5x5UnormBlock); + Add(Format.Astc6x5Unorm, VkFormat.Astc6x5UnormBlock); + Add(Format.Astc6x6Unorm, VkFormat.Astc6x6UnormBlock); + Add(Format.Astc8x5Unorm, VkFormat.Astc8x5UnormBlock); + Add(Format.Astc8x6Unorm, VkFormat.Astc8x6UnormBlock); + Add(Format.Astc8x8Unorm, VkFormat.Astc8x8UnormBlock); + Add(Format.Astc10x5Unorm, VkFormat.Astc10x5UnormBlock); + Add(Format.Astc10x6Unorm, VkFormat.Astc10x6UnormBlock); + Add(Format.Astc10x8Unorm, VkFormat.Astc10x8UnormBlock); + Add(Format.Astc10x10Unorm, VkFormat.Astc10x10UnormBlock); + Add(Format.Astc12x10Unorm, VkFormat.Astc12x10UnormBlock); + Add(Format.Astc12x12Unorm, VkFormat.Astc12x12UnormBlock); + Add(Format.Astc4x4Srgb, VkFormat.Astc4x4SrgbBlock); + Add(Format.Astc5x4Srgb, VkFormat.Astc5x4SrgbBlock); + Add(Format.Astc5x5Srgb, VkFormat.Astc5x5SrgbBlock); + Add(Format.Astc6x5Srgb, VkFormat.Astc6x5SrgbBlock); + Add(Format.Astc6x6Srgb, VkFormat.Astc6x6SrgbBlock); + Add(Format.Astc8x5Srgb, VkFormat.Astc8x5SrgbBlock); + Add(Format.Astc8x6Srgb, VkFormat.Astc8x6SrgbBlock); + Add(Format.Astc8x8Srgb, VkFormat.Astc8x8SrgbBlock); + Add(Format.Astc10x5Srgb, VkFormat.Astc10x5SrgbBlock); + Add(Format.Astc10x6Srgb, VkFormat.Astc10x6SrgbBlock); + Add(Format.Astc10x8Srgb, VkFormat.Astc10x8SrgbBlock); + Add(Format.Astc10x10Srgb, VkFormat.Astc10x10SrgbBlock); + Add(Format.Astc12x10Srgb, VkFormat.Astc12x10SrgbBlock); + Add(Format.Astc12x12Srgb, VkFormat.Astc12x12SrgbBlock); + Add(Format.B5G6R5Unorm, VkFormat.R5G6B5UnormPack16); + Add(Format.B5G5R5A1Unorm, VkFormat.A1R5G5B5UnormPack16); + Add(Format.A1B5G5R5Unorm, VkFormat.R5G5B5A1UnormPack16); + Add(Format.B8G8R8A8Unorm, VkFormat.B8G8R8A8Unorm); + Add(Format.B8G8R8A8Srgb, VkFormat.B8G8R8A8Srgb); + Add(Format.B10G10R10A2Unorm, VkFormat.A2R10G10B10UnormPack32); +#pragma warning restore IDE0055 + } + + private static void Add(Format format, VkFormat vkFormat) + { + _table[(int)format] = vkFormat; + _reverseMap[vkFormat] = format; + } + + public static VkFormat GetFormat(Format format) + { + return _table[(int)format]; + } + + public static Format GetFormat(VkFormat format) + { + if (!_reverseMap.TryGetValue(format, out Format result)) + { + return Format.B8G8R8A8Unorm; + } + + return result; + } + + public static Format ConvertRgba8SrgbToUnorm(Format format) + { + return format switch + { + Format.R8G8B8A8Srgb => Format.R8G8B8A8Unorm, + Format.B8G8R8A8Srgb => Format.B8G8R8A8Unorm, + _ => format, + }; + } + + public static int GetAttributeFormatSize(VkFormat format) + { + switch (format) + { + case VkFormat.R8Unorm: + case VkFormat.R8SNorm: + case VkFormat.R8Uint: + case VkFormat.R8Sint: + case VkFormat.R8Uscaled: + case VkFormat.R8Sscaled: + return 1; + + case VkFormat.R8G8Unorm: + case VkFormat.R8G8SNorm: + case VkFormat.R8G8Uint: + case VkFormat.R8G8Sint: + case VkFormat.R8G8Uscaled: + case VkFormat.R8G8Sscaled: + case VkFormat.R16Sfloat: + case VkFormat.R16Unorm: + case VkFormat.R16SNorm: + case VkFormat.R16Uint: + case VkFormat.R16Sint: + case VkFormat.R16Uscaled: + case VkFormat.R16Sscaled: + return 2; + + case VkFormat.R8G8B8Unorm: + case VkFormat.R8G8B8SNorm: + case VkFormat.R8G8B8Uint: + case VkFormat.R8G8B8Sint: + case VkFormat.R8G8B8Uscaled: + case VkFormat.R8G8B8Sscaled: + return 3; + + case VkFormat.R8G8B8A8Unorm: + case VkFormat.R8G8B8A8SNorm: + case VkFormat.R8G8B8A8Uint: + case VkFormat.R8G8B8A8Sint: + case VkFormat.R8G8B8A8Srgb: + case VkFormat.R8G8B8A8Uscaled: + case VkFormat.R8G8B8A8Sscaled: + case VkFormat.B8G8R8A8Unorm: + case VkFormat.B8G8R8A8Srgb: + case VkFormat.R16G16Sfloat: + case VkFormat.R16G16Unorm: + case VkFormat.R16G16SNorm: + case VkFormat.R16G16Uint: + case VkFormat.R16G16Sint: + case VkFormat.R16G16Uscaled: + case VkFormat.R16G16Sscaled: + case VkFormat.R32Sfloat: + case VkFormat.R32Uint: + case VkFormat.R32Sint: + case VkFormat.A2B10G10R10UnormPack32: + case VkFormat.A2B10G10R10UintPack32: + case VkFormat.B10G11R11UfloatPack32: + case VkFormat.E5B9G9R9UfloatPack32: + case VkFormat.A2B10G10R10SNormPack32: + case VkFormat.A2B10G10R10SintPack32: + case VkFormat.A2B10G10R10UscaledPack32: + case VkFormat.A2B10G10R10SscaledPack32: + return 4; + + case VkFormat.R16G16B16Sfloat: + case VkFormat.R16G16B16Unorm: + case VkFormat.R16G16B16SNorm: + case VkFormat.R16G16B16Uint: + case VkFormat.R16G16B16Sint: + case VkFormat.R16G16B16Uscaled: + case VkFormat.R16G16B16Sscaled: + return 6; + + case VkFormat.R16G16B16A16Sfloat: + case VkFormat.R16G16B16A16Unorm: + case VkFormat.R16G16B16A16SNorm: + case VkFormat.R16G16B16A16Uint: + case VkFormat.R16G16B16A16Sint: + case VkFormat.R16G16B16A16Uscaled: + case VkFormat.R16G16B16A16Sscaled: + case VkFormat.R32G32Sfloat: + case VkFormat.R32G32Uint: + case VkFormat.R32G32Sint: + return 8; + + case VkFormat.R32G32B32Sfloat: + case VkFormat.R32G32B32Uint: + case VkFormat.R32G32B32Sint: + return 12; + + case VkFormat.R32G32B32A32Sfloat: + case VkFormat.R32G32B32A32Uint: + case VkFormat.R32G32B32A32Sint: + return 16; + } + + return 1; + } + + public static VkFormat DropLastComponent(VkFormat format) + { + return format switch + { + VkFormat.R8G8Unorm => VkFormat.R8Unorm, + VkFormat.R8G8SNorm => VkFormat.R8SNorm, + VkFormat.R8G8Uint => VkFormat.R8Uint, + VkFormat.R8G8Sint => VkFormat.R8Sint, + VkFormat.R8G8Uscaled => VkFormat.R8Uscaled, + VkFormat.R8G8Sscaled => VkFormat.R8Sscaled, + VkFormat.R8G8B8Unorm => VkFormat.R8G8Unorm, + VkFormat.R8G8B8SNorm => VkFormat.R8G8SNorm, + VkFormat.R8G8B8Uint => VkFormat.R8G8Uint, + VkFormat.R8G8B8Sint => VkFormat.R8G8Sint, + VkFormat.R8G8B8Uscaled => VkFormat.R8G8Uscaled, + VkFormat.R8G8B8Sscaled => VkFormat.R8G8Sscaled, + VkFormat.R8G8B8A8Unorm => VkFormat.R8G8B8Unorm, + VkFormat.R8G8B8A8SNorm => VkFormat.R8G8B8SNorm, + VkFormat.R8G8B8A8Uint => VkFormat.R8G8B8Uint, + VkFormat.R8G8B8A8Sint => VkFormat.R8G8B8Sint, + VkFormat.R8G8B8A8Srgb => VkFormat.R8G8B8Srgb, + VkFormat.R8G8B8A8Uscaled => VkFormat.R8G8B8Uscaled, + VkFormat.R8G8B8A8Sscaled => VkFormat.R8G8B8Sscaled, + VkFormat.B8G8R8A8Unorm => VkFormat.B8G8R8Unorm, + VkFormat.B8G8R8A8Srgb => VkFormat.B8G8R8Srgb, + VkFormat.R16G16Sfloat => VkFormat.R16Sfloat, + VkFormat.R16G16Unorm => VkFormat.R16Unorm, + VkFormat.R16G16SNorm => VkFormat.R16SNorm, + VkFormat.R16G16Uint => VkFormat.R16Uint, + VkFormat.R16G16Sint => VkFormat.R16Sint, + VkFormat.R16G16Uscaled => VkFormat.R16Uscaled, + VkFormat.R16G16Sscaled => VkFormat.R16Sscaled, + VkFormat.R16G16B16Sfloat => VkFormat.R16G16Sfloat, + VkFormat.R16G16B16Unorm => VkFormat.R16G16Unorm, + VkFormat.R16G16B16SNorm => VkFormat.R16G16SNorm, + VkFormat.R16G16B16Uint => VkFormat.R16G16Uint, + VkFormat.R16G16B16Sint => VkFormat.R16G16Sint, + VkFormat.R16G16B16Uscaled => VkFormat.R16G16Uscaled, + VkFormat.R16G16B16Sscaled => VkFormat.R16G16Sscaled, + VkFormat.R16G16B16A16Sfloat => VkFormat.R16G16B16Sfloat, + VkFormat.R16G16B16A16Unorm => VkFormat.R16G16B16Unorm, + VkFormat.R16G16B16A16SNorm => VkFormat.R16G16B16SNorm, + VkFormat.R16G16B16A16Uint => VkFormat.R16G16B16Uint, + VkFormat.R16G16B16A16Sint => VkFormat.R16G16B16Sint, + VkFormat.R16G16B16A16Uscaled => VkFormat.R16G16B16Uscaled, + VkFormat.R16G16B16A16Sscaled => VkFormat.R16G16B16Sscaled, + VkFormat.R32G32Sfloat => VkFormat.R32Sfloat, + VkFormat.R32G32Uint => VkFormat.R32Uint, + VkFormat.R32G32Sint => VkFormat.R32Sint, + VkFormat.R32G32B32Sfloat => VkFormat.R32G32Sfloat, + VkFormat.R32G32B32Uint => VkFormat.R32G32Uint, + VkFormat.R32G32B32Sint => VkFormat.R32G32Sint, + VkFormat.R32G32B32A32Sfloat => VkFormat.R32G32B32Sfloat, + VkFormat.R32G32B32A32Uint => VkFormat.R32G32B32Uint, + VkFormat.R32G32B32A32Sint => VkFormat.R32G32B32Sint, + _ => format, + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs new file mode 100644 index 00000000..8d80e9d0 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs @@ -0,0 +1,344 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Linq; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + class FramebufferParams + { + private readonly Device _device; + private readonly Auto[] _attachments; + private readonly TextureView[] _colors; + private readonly TextureView _depthStencil; + private readonly TextureView[] _colorsCanonical; + private readonly TextureView _baseAttachment; + private readonly uint _validColorAttachments; + + public uint Width { get; } + public uint Height { get; } + public uint Layers { get; } + + public uint[] AttachmentSamples { get; } + public VkFormat[] AttachmentFormats { get; } + public int[] AttachmentIndices { get; } + public uint AttachmentIntegerFormatMask { get; } + public bool LogicOpsAllowed { get; } + + public int AttachmentsCount { get; } + public int MaxColorAttachmentIndex => AttachmentIndices.Length > 0 ? AttachmentIndices[^1] : -1; + public bool HasDepthStencil { get; } + public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0); + + public FramebufferParams(Device device, TextureView view, uint width, uint height) + { + var format = view.Info.Format; + + bool isDepthStencil = format.IsDepthOrStencil(); + + _device = device; + _attachments = new[] { view.GetImageViewForAttachment() }; + _validColorAttachments = isDepthStencil ? 0u : 1u; + _baseAttachment = view; + + if (isDepthStencil) + { + _depthStencil = view; + } + else + { + _colors = new TextureView[] { view }; + _colorsCanonical = _colors; + } + + Width = width; + Height = height; + Layers = 1; + + AttachmentSamples = new[] { (uint)view.Info.Samples }; + AttachmentFormats = new[] { view.VkFormat }; + AttachmentIndices = isDepthStencil ? Array.Empty() : new[] { 0 }; + AttachmentIntegerFormatMask = format.IsInteger() ? 1u : 0u; + LogicOpsAllowed = !format.IsFloatOrSrgb(); + + AttachmentsCount = 1; + + HasDepthStencil = isDepthStencil; + } + + public FramebufferParams(Device device, ITexture[] colors, ITexture depthStencil) + { + _device = device; + + int colorsCount = colors.Count(IsValidTextureView); + + int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0); + + _attachments = new Auto[count]; + _colors = new TextureView[colorsCount]; + _colorsCanonical = colors.Select(color => color is TextureView view && view.Valid ? view : null).ToArray(); + + AttachmentSamples = new uint[count]; + AttachmentFormats = new VkFormat[count]; + AttachmentIndices = new int[colorsCount]; + + uint width = uint.MaxValue; + uint height = uint.MaxValue; + uint layers = uint.MaxValue; + + int index = 0; + int bindIndex = 0; + uint attachmentIntegerFormatMask = 0; + bool allFormatsFloatOrSrgb = colorsCount != 0; + + foreach (ITexture color in colors) + { + if (IsValidTextureView(color)) + { + var texture = (TextureView)color; + + _attachments[index] = texture.GetImageViewForAttachment(); + _colors[index] = texture; + _validColorAttachments |= 1u << bindIndex; + _baseAttachment = texture; + + AttachmentSamples[index] = (uint)texture.Info.Samples; + AttachmentFormats[index] = texture.VkFormat; + AttachmentIndices[index] = bindIndex; + + var format = texture.Info.Format; + + if (format.IsInteger()) + { + attachmentIntegerFormatMask |= 1u << bindIndex; + } + + allFormatsFloatOrSrgb &= format.IsFloatOrSrgb(); + + width = Math.Min(width, (uint)texture.Width); + height = Math.Min(height, (uint)texture.Height); + layers = Math.Min(layers, (uint)texture.Layers); + + if (++index >= colorsCount) + { + break; + } + } + + bindIndex++; + } + + AttachmentIntegerFormatMask = attachmentIntegerFormatMask; + LogicOpsAllowed = !allFormatsFloatOrSrgb; + + if (depthStencil is TextureView dsTexture && dsTexture.Valid) + { + _attachments[count - 1] = dsTexture.GetImageViewForAttachment(); + _depthStencil = dsTexture; + _baseAttachment ??= dsTexture; + + AttachmentSamples[count - 1] = (uint)dsTexture.Info.Samples; + AttachmentFormats[count - 1] = dsTexture.VkFormat; + + width = Math.Min(width, (uint)dsTexture.Width); + height = Math.Min(height, (uint)dsTexture.Height); + layers = Math.Min(layers, (uint)dsTexture.Layers); + + HasDepthStencil = true; + } + + if (count == 0) + { + width = height = layers = 1; + } + + Width = width; + Height = height; + Layers = layers; + + AttachmentsCount = count; + } + + public Auto GetAttachment(int index) + { + if ((uint)index >= _attachments.Length) + { + return null; + } + + return _attachments[index]; + } + + public Auto GetDepthStencilAttachment() + { + if (!HasDepthStencil) + { + return null; + } + + return _attachments[AttachmentsCount - 1]; + } + + public ComponentType GetAttachmentComponentType(int index) + { + if (_colors != null && (uint)index < _colors.Length) + { + var format = _colors[index].Info.Format; + + if (format.IsSint()) + { + return ComponentType.SignedInteger; + } + + if (format.IsUint()) + { + return ComponentType.UnsignedInteger; + } + } + + return ComponentType.Float; + } + + public ImageAspectFlags GetDepthStencilAspectFlags() + { + if (_depthStencil == null) + { + return ImageAspectFlags.None; + } + + return _depthStencil.Info.Format.ConvertAspectFlags(); + } + + public bool IsValidColorAttachment(int bindIndex) + { + return (uint)bindIndex < Constants.MaxRenderTargets && (_validColorAttachments & (1u << bindIndex)) != 0; + } + + private static bool IsValidTextureView(ITexture texture) + { + return texture is TextureView view && view.Valid; + } + + public ClearRect GetClearRect(Rectangle scissor, int layer, int layerCount) + { + int x = scissor.X; + int y = scissor.Y; + int width = Math.Min((int)Width - scissor.X, scissor.Width); + int height = Math.Min((int)Height - scissor.Y, scissor.Height); + + return new ClearRect(new Rect2D(new Offset2D(x, y), new Extent2D((uint)width, (uint)height)), (uint)layer, (uint)layerCount); + } + + public unsafe Auto Create(Vk api, CommandBufferScoped cbs, Auto renderPass) + { + ImageView* attachments = stackalloc ImageView[_attachments.Length]; + + for (int i = 0; i < _attachments.Length; i++) + { + attachments[i] = _attachments[i].Get(cbs).Value; + } + + var framebufferCreateInfo = new FramebufferCreateInfo + { + SType = StructureType.FramebufferCreateInfo, + RenderPass = renderPass.Get(cbs).Value, + AttachmentCount = (uint)_attachments.Length, + PAttachments = attachments, + Width = Width, + Height = Height, + Layers = Layers, + }; + + api.CreateFramebuffer(_device, in framebufferCreateInfo, null, out var framebuffer).ThrowOnError(); + return new Auto(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments); + } + + public TextureView[] GetAttachmentViews() + { + var result = new TextureView[_attachments.Length]; + + _colors?.CopyTo(result, 0); + + if (_depthStencil != null) + { + result[^1] = _depthStencil; + } + + return result; + } + + public RenderPassCacheKey GetRenderPassCacheKey() + { + return new RenderPassCacheKey(_depthStencil, _colorsCanonical); + } + + public void InsertLoadOpBarriers(VulkanRenderer gd, CommandBufferScoped cbs) + { + if (_colors != null) + { + foreach (var color in _colors) + { + // If Clear or DontCare were used, this would need to be write bit. + color.Storage?.QueueLoadOpBarrier(cbs, false); + } + } + + _depthStencil?.Storage?.QueueLoadOpBarrier(cbs, true); + + gd.Barriers.Flush(cbs, false, null, null); + } + + public void AddStoreOpUsage() + { + if (_colors != null) + { + foreach (var color in _colors) + { + color.Storage?.AddStoreOpUsage(false); + } + } + + _depthStencil?.Storage?.AddStoreOpUsage(true); + } + + public void ClearBindings() + { + _depthStencil?.Storage.ClearBindings(); + + for (int i = 0; i < _colorsCanonical.Length; i++) + { + _colorsCanonical[i]?.Storage.ClearBindings(); + } + } + + public void AddBindings() + { + _depthStencil?.Storage.AddBinding(_depthStencil); + + for (int i = 0; i < _colorsCanonical.Length; i++) + { + TextureView color = _colorsCanonical[i]; + color?.Storage.AddBinding(color); + } + } + + public (RenderPassHolder rpHolder, Auto framebuffer) GetPassAndFramebuffer( + VulkanRenderer gd, + Device device, + CommandBufferScoped cbs) + { + return _baseAttachment.GetPassAndFramebuffer(gd, device, cbs, this); + } + + public TextureView GetColorView(int index) + { + return _colorsCanonical[index]; + } + + public TextureView GetDepthStencilView() + { + return _depthStencil; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs new file mode 100644 index 00000000..bd17867b --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs @@ -0,0 +1,138 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + [Flags] + enum PortabilitySubsetFlags + { + None = 0, + + NoTriangleFans = 1, + NoPointMode = 1 << 1, + No3DImageView = 1 << 2, + NoLodBias = 1 << 3, + } + + readonly struct HardwareCapabilities + { + public readonly bool SupportsIndexTypeUint8; + public readonly bool SupportsCustomBorderColor; + public readonly bool SupportsBlendEquationAdvanced; + public readonly bool SupportsBlendEquationAdvancedCorrelatedOverlap; + public readonly bool SupportsBlendEquationAdvancedNonPreMultipliedSrcColor; + public readonly bool SupportsBlendEquationAdvancedNonPreMultipliedDstColor; + public readonly bool SupportsIndirectParameters; + public readonly bool SupportsFragmentShaderInterlock; + public readonly bool SupportsGeometryShaderPassthrough; + public readonly bool SupportsShaderFloat64; + public readonly bool SupportsShaderInt8; + public readonly bool SupportsShaderStencilExport; + public readonly bool SupportsShaderStorageImageMultisample; + public readonly bool SupportsConditionalRendering; + public readonly bool SupportsExtendedDynamicState; + public readonly bool SupportsMultiView; + public readonly bool SupportsNullDescriptors; + public readonly bool SupportsPushDescriptors; + public readonly uint MaxPushDescriptors; + public readonly bool SupportsPrimitiveTopologyListRestart; + public readonly bool SupportsPrimitiveTopologyPatchListRestart; + public readonly bool SupportsTransformFeedback; + public readonly bool SupportsTransformFeedbackQueries; + public readonly bool SupportsPreciseOcclusionQueries; + public readonly bool SupportsPipelineStatisticsQuery; + public readonly bool SupportsGeometryShader; + public readonly bool SupportsTessellationShader; + public readonly bool SupportsViewportArray2; + public readonly bool SupportsHostImportedMemory; + public readonly bool SupportsDepthClipControl; + public readonly bool SupportsAttachmentFeedbackLoop; + public readonly bool SupportsDynamicAttachmentFeedbackLoop; + public readonly uint SubgroupSize; + public readonly SampleCountFlags SupportedSampleCounts; + public readonly PortabilitySubsetFlags PortabilitySubset; + public readonly uint VertexBufferAlignment; + public readonly uint SubTexelPrecisionBits; + public readonly ulong MinResourceAlignment; + + public HardwareCapabilities( + bool supportsIndexTypeUint8, + bool supportsCustomBorderColor, + bool supportsBlendEquationAdvanced, + bool supportsBlendEquationAdvancedCorrelatedOverlap, + bool supportsBlendEquationAdvancedNonPreMultipliedSrcColor, + bool supportsBlendEquationAdvancedNonPreMultipliedDstColor, + bool supportsIndirectParameters, + bool supportsFragmentShaderInterlock, + bool supportsGeometryShaderPassthrough, + bool supportsShaderFloat64, + bool supportsShaderInt8, + bool supportsShaderStencilExport, + bool supportsShaderStorageImageMultisample, + bool supportsConditionalRendering, + bool supportsExtendedDynamicState, + bool supportsMultiView, + bool supportsNullDescriptors, + bool supportsPushDescriptors, + uint maxPushDescriptors, + bool supportsPrimitiveTopologyListRestart, + bool supportsPrimitiveTopologyPatchListRestart, + bool supportsTransformFeedback, + bool supportsTransformFeedbackQueries, + bool supportsPreciseOcclusionQueries, + bool supportsPipelineStatisticsQuery, + bool supportsGeometryShader, + bool supportsTessellationShader, + bool supportsViewportArray2, + bool supportsHostImportedMemory, + bool supportsDepthClipControl, + bool supportsAttachmentFeedbackLoop, + bool supportsDynamicAttachmentFeedbackLoop, + uint subgroupSize, + SampleCountFlags supportedSampleCounts, + PortabilitySubsetFlags portabilitySubset, + uint vertexBufferAlignment, + uint subTexelPrecisionBits, + ulong minResourceAlignment) + { + SupportsIndexTypeUint8 = supportsIndexTypeUint8; + SupportsCustomBorderColor = supportsCustomBorderColor; + SupportsBlendEquationAdvanced = supportsBlendEquationAdvanced; + SupportsBlendEquationAdvancedCorrelatedOverlap = supportsBlendEquationAdvancedCorrelatedOverlap; + SupportsBlendEquationAdvancedNonPreMultipliedSrcColor = supportsBlendEquationAdvancedNonPreMultipliedSrcColor; + SupportsBlendEquationAdvancedNonPreMultipliedDstColor = supportsBlendEquationAdvancedNonPreMultipliedDstColor; + SupportsIndirectParameters = supportsIndirectParameters; + SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock; + SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough; + SupportsShaderFloat64 = supportsShaderFloat64; + SupportsShaderInt8 = supportsShaderInt8; + SupportsShaderStencilExport = supportsShaderStencilExport; + SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample; + SupportsConditionalRendering = supportsConditionalRendering; + SupportsExtendedDynamicState = supportsExtendedDynamicState; + SupportsMultiView = supportsMultiView; + SupportsNullDescriptors = supportsNullDescriptors; + SupportsPushDescriptors = supportsPushDescriptors; + MaxPushDescriptors = maxPushDescriptors; + SupportsPrimitiveTopologyListRestart = supportsPrimitiveTopologyListRestart; + SupportsPrimitiveTopologyPatchListRestart = supportsPrimitiveTopologyPatchListRestart; + SupportsTransformFeedback = supportsTransformFeedback; + SupportsTransformFeedbackQueries = supportsTransformFeedbackQueries; + SupportsPreciseOcclusionQueries = supportsPreciseOcclusionQueries; + SupportsPipelineStatisticsQuery = supportsPipelineStatisticsQuery; + SupportsGeometryShader = supportsGeometryShader; + SupportsTessellationShader = supportsTessellationShader; + SupportsViewportArray2 = supportsViewportArray2; + SupportsHostImportedMemory = supportsHostImportedMemory; + SupportsDepthClipControl = supportsDepthClipControl; + SupportsAttachmentFeedbackLoop = supportsAttachmentFeedbackLoop; + SupportsDynamicAttachmentFeedbackLoop = supportsDynamicAttachmentFeedbackLoop; + SubgroupSize = subgroupSize; + SupportedSampleCounts = supportedSampleCounts; + PortabilitySubset = portabilitySubset; + VertexBufferAlignment = vertexBufferAlignment; + SubTexelPrecisionBits = subTexelPrecisionBits; + MinResourceAlignment = minResourceAlignment; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs b/src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs new file mode 100644 index 00000000..3796e3c5 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Vulkan +{ + interface IRefEquatable + { + bool Equals(ref T other); + } + + class HashTableSlim where TKey : IRefEquatable + { + private const int TotalBuckets = 16; // Must be power of 2 + private const int TotalBucketsMask = TotalBuckets - 1; + + private struct Entry + { + public int Hash; + public TKey Key; + public TValue Value; + } + + private struct Bucket + { + public int Length; + public Entry[] Entries; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Span AsSpan() + { + return Entries == null ? Span.Empty : Entries.AsSpan(0, Length); + } + } + + private readonly Bucket[] _hashTable = new Bucket[TotalBuckets]; + + public IEnumerable Keys + { + get + { + foreach (Bucket bucket in _hashTable) + { + for (int i = 0; i < bucket.Length; i++) + { + yield return bucket.Entries[i].Key; + } + } + } + } + + public IEnumerable Values + { + get + { + foreach (Bucket bucket in _hashTable) + { + for (int i = 0; i < bucket.Length; i++) + { + yield return bucket.Entries[i].Value; + } + } + } + } + + public void Add(ref TKey key, TValue value) + { + var entry = new Entry + { + Hash = key.GetHashCode(), + Key = key, + Value = value, + }; + + int hashCode = key.GetHashCode(); + int bucketIndex = hashCode & TotalBucketsMask; + + ref var bucket = ref _hashTable[bucketIndex]; + if (bucket.Entries != null) + { + int index = bucket.Length; + + if (index >= bucket.Entries.Length) + { + Array.Resize(ref bucket.Entries, index + 1); + } + + bucket.Entries[index] = entry; + } + else + { + bucket.Entries = new[] + { + entry, + }; + } + + bucket.Length++; + } + + public bool Remove(ref TKey key) + { + int hashCode = key.GetHashCode(); + + ref var bucket = ref _hashTable[hashCode & TotalBucketsMask]; + var entries = bucket.AsSpan(); + for (int i = 0; i < entries.Length; i++) + { + ref var entry = ref entries[i]; + + if (entry.Hash == hashCode && entry.Key.Equals(ref key)) + { + entries[(i + 1)..].CopyTo(entries[i..]); + bucket.Length--; + + return true; + } + } + + return false; + } + + public bool TryGetValue(ref TKey key, out TValue value) + { + int hashCode = key.GetHashCode(); + + var entries = _hashTable[hashCode & TotalBucketsMask].AsSpan(); + for (int i = 0; i < entries.Length; i++) + { + ref var entry = ref entries[i]; + + if (entry.Hash == hashCode && entry.Key.Equals(ref key)) + { + value = entry.Value; + return true; + } + } + + value = default; + return false; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/HelperShader.cs b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs new file mode 100644 index 00000000..73aa95c7 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/HelperShader.cs @@ -0,0 +1,1740 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Numerics; +using CompareOp = Ryujinx.Graphics.GAL.CompareOp; +using Format = Ryujinx.Graphics.GAL.Format; +using PrimitiveTopology = Ryujinx.Graphics.GAL.PrimitiveTopology; +using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo; +using StencilOp = Ryujinx.Graphics.GAL.StencilOp; +using Viewport = Ryujinx.Graphics.GAL.Viewport; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + enum ComponentType + { + Float, + SignedInteger, + UnsignedInteger, + } + + class HelperShader : IDisposable + { + private const int UniformBufferAlignment = 256; + private const int ConvertElementsPerWorkgroup = 32 * 100; // Work group size of 32 times 100 elements. + private const string ShaderBinariesPath = "Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries"; + + private readonly PipelineHelperShader _pipeline; + private readonly ISampler _samplerLinear; + private readonly ISampler _samplerNearest; + private readonly IProgram _programColorBlit; + private readonly IProgram _programColorBlitMs; + private readonly IProgram _programColorBlitClearAlpha; + private readonly IProgram _programColorClearF; + private readonly IProgram _programColorClearSI; + private readonly IProgram _programColorClearUI; + private readonly IProgram _programDepthStencilClear; + private readonly IProgram _programStrideChange; + private readonly IProgram _programConvertD32S8ToD24S8; + private readonly IProgram _programConvertIndexBuffer; + private readonly IProgram _programConvertIndirectData; + private readonly IProgram _programColorCopyShortening; + private readonly IProgram _programColorCopyToNonMs; + private readonly IProgram _programColorCopyWidening; + private readonly IProgram _programColorDrawToMs; + private readonly IProgram _programDepthBlit; + private readonly IProgram _programDepthBlitMs; + private readonly IProgram _programDepthDrawToMs; + private readonly IProgram _programDepthDrawToNonMs; + private readonly IProgram _programStencilBlit; + private readonly IProgram _programStencilBlitMs; + private readonly IProgram _programStencilDrawToMs; + private readonly IProgram _programStencilDrawToNonMs; + + public HelperShader(VulkanRenderer gd, Device device) + { + _pipeline = new PipelineHelperShader(gd, device); + _pipeline.Initialize(); + + _samplerLinear = gd.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear)); + _samplerNearest = gd.CreateSampler(SamplerCreateInfo.Create(MinFilter.Nearest, MagFilter.Nearest)); + + var blitResourceLayout = new ResourceLayoutBuilder() + .Add(ResourceStages.Vertex, ResourceType.UniformBuffer, 1) + .Add(ResourceStages.Fragment, ResourceType.TextureAndSampler, 0).Build(); + + _programColorBlit = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorBlitVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("ColorBlitFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, blitResourceLayout); + + _programColorBlitMs = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorBlitVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("ColorBlitMsFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, blitResourceLayout); + + _programColorBlitClearAlpha = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorBlitVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("ColorBlitClearAlphaFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, blitResourceLayout); + + var colorClearResourceLayout = new ResourceLayoutBuilder().Add(ResourceStages.Vertex, ResourceType.UniformBuffer, 1).Build(); + + _programColorClearF = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorClearVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("ColorClearFFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, colorClearResourceLayout); + + _programColorClearSI = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorClearVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("ColorClearSIFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, colorClearResourceLayout); + + _programColorClearUI = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorClearVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("ColorClearUIFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, colorClearResourceLayout); + + _programDepthStencilClear = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorClearVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("DepthStencilClearFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, colorClearResourceLayout); + + var strideChangeResourceLayout = new ResourceLayoutBuilder() + .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0) + .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1) + .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build(); + + _programStrideChange = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ChangeBufferStride.spv"), ShaderStage.Compute, TargetLanguage.Spirv), + }, strideChangeResourceLayout); + + var colorCopyResourceLayout = new ResourceLayoutBuilder() + .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0) + .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 0) + .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build(); + + _programColorCopyShortening = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorCopyShorteningCompute.spv"), ShaderStage.Compute, TargetLanguage.Spirv), + }, colorCopyResourceLayout); + + _programColorCopyToNonMs = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorCopyToNonMsCompute.spv"), ShaderStage.Compute, TargetLanguage.Spirv), + }, colorCopyResourceLayout); + + _programColorCopyWidening = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorCopyWideningCompute.spv"), ShaderStage.Compute, TargetLanguage.Spirv), + }, colorCopyResourceLayout); + + var colorDrawToMsResourceLayout = new ResourceLayoutBuilder() + .Add(ResourceStages.Fragment, ResourceType.UniformBuffer, 0) + .Add(ResourceStages.Fragment, ResourceType.TextureAndSampler, 0).Build(); + + _programColorDrawToMs = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorDrawToMsVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("ColorDrawToMsFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, colorDrawToMsResourceLayout); + + var convertD32S8ToD24S8ResourceLayout = new ResourceLayoutBuilder() + .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0) + .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1) + .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build(); + + _programConvertD32S8ToD24S8 = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ConvertD32S8ToD24S8.spv"), ShaderStage.Compute, TargetLanguage.Spirv), + }, convertD32S8ToD24S8ResourceLayout); + + var convertIndexBufferResourceLayout = new ResourceLayoutBuilder() + .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0) + .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1) + .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build(); + + _programConvertIndexBuffer = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ConvertIndexBuffer.spv"), ShaderStage.Compute, TargetLanguage.Spirv), + }, convertIndexBufferResourceLayout); + + var convertIndirectDataResourceLayout = new ResourceLayoutBuilder() + .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0) + .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1) + .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true) + .Add(ResourceStages.Compute, ResourceType.StorageBuffer, 3).Build(); + + _programConvertIndirectData = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ConvertIndirectData.spv"), ShaderStage.Compute, TargetLanguage.Spirv), + }, convertIndirectDataResourceLayout); + + _programDepthBlit = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorBlitVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("DepthBlitFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, blitResourceLayout); + + _programDepthBlitMs = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorBlitVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("DepthBlitMsFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, blitResourceLayout); + + _programDepthDrawToMs = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorDrawToMsVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("DepthDrawToMsFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, colorDrawToMsResourceLayout); + + _programDepthDrawToNonMs = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorDrawToMsVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("DepthDrawToNonMsFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, colorDrawToMsResourceLayout); + + if (gd.Capabilities.SupportsShaderStencilExport) + { + _programStencilBlit = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorBlitVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("StencilBlitFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, blitResourceLayout); + + _programStencilBlitMs = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorBlitVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("StencilBlitMsFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, blitResourceLayout); + + _programStencilDrawToMs = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorDrawToMsVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("StencilDrawToMsFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, colorDrawToMsResourceLayout); + + _programStencilDrawToNonMs = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ReadSpirv("ColorDrawToMsVertex.spv"), ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ReadSpirv("StencilDrawToNonMsFragment.spv"), ShaderStage.Fragment, TargetLanguage.Spirv), + }, colorDrawToMsResourceLayout); + } + } + + private static byte[] ReadSpirv(string fileName) + { + return EmbeddedResources.Read(string.Join('/', ShaderBinariesPath, fileName)); + } + + public void Blit( + VulkanRenderer gd, + TextureView src, + TextureView dst, + Extents2D srcRegion, + Extents2D dstRegion, + int layers, + int levels, + bool isDepthOrStencil, + bool linearFilter, + bool clearAlpha = false) + { + gd.FlushAllCommands(); + + using var cbs = gd.CommandBufferPool.Rent(); + + for (int l = 0; l < levels; l++) + { + var mipSrcRegion = new Extents2D( + srcRegion.X1 >> l, + srcRegion.Y1 >> l, + srcRegion.X2 >> l, + srcRegion.Y2 >> l); + + var mipDstRegion = new Extents2D( + dstRegion.X1 >> l, + dstRegion.Y1 >> l, + dstRegion.X2 >> l, + dstRegion.Y2 >> l); + + for (int z = 0; z < layers; z++) + { + var srcView = Create2DLayerView(src, z, l); + var dstView = Create2DLayerView(dst, z, l); + + if (isDepthOrStencil) + { + BlitDepthStencil( + gd, + cbs, + srcView, + dstView, + mipSrcRegion, + mipDstRegion); + } + else + { + BlitColor( + gd, + cbs, + srcView, + dstView, + mipSrcRegion, + mipDstRegion, + linearFilter, + clearAlpha); + } + + if (srcView != src) + { + srcView.Release(); + } + + if (dstView != dst) + { + dstView.Release(); + } + } + } + } + + public void CopyColor( + VulkanRenderer gd, + CommandBufferScoped cbs, + TextureView src, + TextureView dst, + int srcLayer, + int dstLayer, + int srcLevel, + int dstLevel, + int depth, + int levels) + { + for (int l = 0; l < levels; l++) + { + int mipSrcLevel = srcLevel + l; + int mipDstLevel = dstLevel + l; + + int srcWidth = Math.Max(1, src.Width >> mipSrcLevel); + int srcHeight = Math.Max(1, src.Height >> mipSrcLevel); + + int dstWidth = Math.Max(1, dst.Width >> mipDstLevel); + int dstHeight = Math.Max(1, dst.Height >> mipDstLevel); + + var extents = new Extents2D( + 0, + 0, + Math.Min(srcWidth, dstWidth), + Math.Min(srcHeight, dstHeight)); + + for (int z = 0; z < depth; z++) + { + var srcView = Create2DLayerView(src, srcLayer + z, mipSrcLevel); + var dstView = Create2DLayerView(dst, dstLayer + z, mipDstLevel); + + BlitColor( + gd, + cbs, + srcView, + dstView, + extents, + extents, + false); + + if (srcView != src) + { + srcView.Release(); + } + + if (dstView != dst) + { + dstView.Release(); + } + } + } + } + + public void BlitColor( + VulkanRenderer gd, + CommandBufferScoped cbs, + TextureView src, + TextureView dst, + Extents2D srcRegion, + Extents2D dstRegion, + bool linearFilter, + bool clearAlpha = false) + { + _pipeline.SetCommandBuffer(cbs); + + const int RegionBufferSize = 16; + + var sampler = linearFilter ? _samplerLinear : _samplerNearest; + + _pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Fragment, 0, src, sampler); + + Span region = stackalloc float[RegionBufferSize / sizeof(float)]; + + region[0] = (float)srcRegion.X1 / src.Width; + region[1] = (float)srcRegion.X2 / src.Width; + region[2] = (float)srcRegion.Y1 / src.Height; + region[3] = (float)srcRegion.Y2 / src.Height; + + if (dstRegion.X1 > dstRegion.X2) + { + (region[0], region[1]) = (region[1], region[0]); + } + + if (dstRegion.Y1 > dstRegion.Y2) + { + (region[2], region[3]) = (region[3], region[2]); + } + + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, RegionBufferSize); + + buffer.Holder.SetDataUnchecked(buffer.Offset, region); + + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, buffer.Range) }); + + Span viewports = stackalloc Viewport[1]; + + var rect = new Rectangle( + MathF.Min(dstRegion.X1, dstRegion.X2), + MathF.Min(dstRegion.Y1, dstRegion.Y2), + MathF.Abs(dstRegion.X2 - dstRegion.X1), + MathF.Abs(dstRegion.Y2 - dstRegion.Y1)); + + viewports[0] = new Viewport( + rect, + ViewportSwizzle.PositiveX, + ViewportSwizzle.PositiveY, + ViewportSwizzle.PositiveZ, + ViewportSwizzle.PositiveW, + 0f, + 1f); + + bool dstIsDepthOrStencil = dst.Info.Format.IsDepthOrStencil(); + + if (dstIsDepthOrStencil) + { + _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit); + _pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always)); + } + else if (src.Info.Target.IsMultisample()) + { + _pipeline.SetProgram(_programColorBlitMs); + } + else if (clearAlpha) + { + _pipeline.SetProgram(_programColorBlitClearAlpha); + } + else + { + _pipeline.SetProgram(_programColorBlit); + } + + int dstWidth = dst.Width; + int dstHeight = dst.Height; + + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); + _pipeline.SetRenderTargetColorMasks(new uint[] { 0xf }); + _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dstWidth, dstHeight) }); + + if (clearAlpha) + { + _pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f)); + } + + _pipeline.SetViewports(viewports); + _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); + _pipeline.Draw(4, 1, 0, 0); + + if (dstIsDepthOrStencil) + { + _pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always)); + } + + _pipeline.Finish(gd, cbs); + } + + private void BlitDepthStencil( + VulkanRenderer gd, + CommandBufferScoped cbs, + TextureView src, + TextureView dst, + Extents2D srcRegion, + Extents2D dstRegion) + { + _pipeline.SetCommandBuffer(cbs); + + const int RegionBufferSize = 16; + + Span region = stackalloc float[RegionBufferSize / sizeof(float)]; + + region[0] = (float)srcRegion.X1 / src.Width; + region[1] = (float)srcRegion.X2 / src.Width; + region[2] = (float)srcRegion.Y1 / src.Height; + region[3] = (float)srcRegion.Y2 / src.Height; + + if (dstRegion.X1 > dstRegion.X2) + { + (region[0], region[1]) = (region[1], region[0]); + } + + if (dstRegion.Y1 > dstRegion.Y2) + { + (region[2], region[3]) = (region[3], region[2]); + } + + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, RegionBufferSize); + + buffer.Holder.SetDataUnchecked(buffer.Offset, region); + + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, buffer.Range) }); + + Span viewports = stackalloc Viewport[1]; + + var rect = new Rectangle( + MathF.Min(dstRegion.X1, dstRegion.X2), + MathF.Min(dstRegion.Y1, dstRegion.Y2), + MathF.Abs(dstRegion.X2 - dstRegion.X1), + MathF.Abs(dstRegion.Y2 - dstRegion.Y1)); + + viewports[0] = new Viewport( + rect, + ViewportSwizzle.PositiveX, + ViewportSwizzle.PositiveY, + ViewportSwizzle.PositiveZ, + ViewportSwizzle.PositiveW, + 0f, + 1f); + + int dstWidth = dst.Width; + int dstHeight = dst.Height; + + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); + _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dstWidth, dstHeight) }); + _pipeline.SetViewports(viewports); + _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); + + var aspectFlags = src.Info.Format.ConvertAspectFlags(); + + if (aspectFlags.HasFlag(ImageAspectFlags.DepthBit)) + { + var depthTexture = CreateDepthOrStencilView(src, DepthStencilMode.Depth); + + BlitDepthStencilDraw(depthTexture, isDepth: true); + + if (depthTexture != src) + { + depthTexture.Release(); + } + } + + if (aspectFlags.HasFlag(ImageAspectFlags.StencilBit) && _programStencilBlit != null) + { + var stencilTexture = CreateDepthOrStencilView(src, DepthStencilMode.Stencil); + + BlitDepthStencilDraw(stencilTexture, isDepth: false); + + if (stencilTexture != src) + { + stencilTexture.Release(); + } + } + + _pipeline.Finish(gd, cbs); + } + + private static TextureView CreateDepthOrStencilView(TextureView depthStencilTexture, DepthStencilMode depthStencilMode) + { + if (depthStencilTexture.Info.DepthStencilMode == depthStencilMode) + { + return depthStencilTexture; + } + + return (TextureView)depthStencilTexture.CreateView(new TextureCreateInfo( + depthStencilTexture.Info.Width, + depthStencilTexture.Info.Height, + depthStencilTexture.Info.Depth, + depthStencilTexture.Info.Levels, + depthStencilTexture.Info.Samples, + depthStencilTexture.Info.BlockWidth, + depthStencilTexture.Info.BlockHeight, + depthStencilTexture.Info.BytesPerPixel, + depthStencilTexture.Info.Format, + depthStencilMode, + depthStencilTexture.Info.Target, + SwizzleComponent.Red, + SwizzleComponent.Green, + SwizzleComponent.Blue, + SwizzleComponent.Alpha), 0, 0); + } + + private void BlitDepthStencilDraw(TextureView src, bool isDepth) + { + _pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Fragment, 0, src, _samplerNearest); + + if (isDepth) + { + _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit); + _pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always)); + } + else + { + _pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programStencilBlitMs : _programStencilBlit); + _pipeline.SetStencilTest(CreateStencilTestDescriptor(true)); + } + + _pipeline.Draw(4, 1, 0, 0); + + if (isDepth) + { + _pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always)); + } + else + { + _pipeline.SetStencilTest(CreateStencilTestDescriptor(false)); + } + } + + private static StencilTestDescriptor CreateStencilTestDescriptor( + bool enabled, + int refValue = 0, + int compareMask = 0xff, + int writeMask = 0xff) + { + return new StencilTestDescriptor( + enabled, + CompareOp.Always, + StencilOp.Replace, + StencilOp.Replace, + StencilOp.Replace, + refValue, + compareMask, + writeMask, + CompareOp.Always, + StencilOp.Replace, + StencilOp.Replace, + StencilOp.Replace, + refValue, + compareMask, + writeMask); + } + + public void Clear( + VulkanRenderer gd, + TextureView dst, + ReadOnlySpan clearColor, + uint componentMask, + int dstWidth, + int dstHeight, + ComponentType type, + Rectangle scissor) + { + const int ClearColorBufferSize = 16; + + gd.FlushAllCommands(); + + using var cbs = gd.CommandBufferPool.Rent(); + + _pipeline.SetCommandBuffer(cbs); + + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, ClearColorBufferSize); + + buffer.Holder.SetDataUnchecked(buffer.Offset, clearColor); + + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, buffer.Range) }); + + Span viewports = stackalloc Viewport[1]; + + viewports[0] = new Viewport( + new Rectangle(0, 0, dstWidth, dstHeight), + ViewportSwizzle.PositiveX, + ViewportSwizzle.PositiveY, + ViewportSwizzle.PositiveZ, + ViewportSwizzle.PositiveW, + 0f, + 1f); + + IProgram program; + + if (type == ComponentType.SignedInteger) + { + program = _programColorClearSI; + } + else if (type == ComponentType.UnsignedInteger) + { + program = _programColorClearUI; + } + else + { + program = _programColorClearF; + } + + _pipeline.SetProgram(program); + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); + _pipeline.SetRenderTargetColorMasks(new[] { componentMask }); + _pipeline.SetViewports(viewports); + _pipeline.SetScissors(stackalloc Rectangle[] { scissor }); + _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); + _pipeline.Draw(4, 1, 0, 0); + _pipeline.Finish(); + } + + public void Clear( + VulkanRenderer gd, + TextureView dst, + float depthValue, + bool depthMask, + int stencilValue, + int stencilMask, + int dstWidth, + int dstHeight, + VkFormat dstFormat, + Rectangle scissor) + { + const int ClearColorBufferSize = 16; + + gd.FlushAllCommands(); + + using var cbs = gd.CommandBufferPool.Rent(); + + _pipeline.SetCommandBuffer(cbs); + + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, ClearColorBufferSize); + + buffer.Holder.SetDataUnchecked(buffer.Offset, stackalloc float[] { depthValue }); + + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, buffer.Range) }); + + Span viewports = stackalloc Viewport[1]; + + viewports[0] = new Viewport( + new Rectangle(0, 0, dstWidth, dstHeight), + ViewportSwizzle.PositiveX, + ViewportSwizzle.PositiveY, + ViewportSwizzle.PositiveZ, + ViewportSwizzle.PositiveW, + 0f, + 1f); + + _pipeline.SetProgram(_programDepthStencilClear); + _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight); + _pipeline.SetViewports(viewports); + _pipeline.SetScissors(stackalloc Rectangle[] { scissor }); + _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); + _pipeline.SetDepthTest(new DepthTestDescriptor(true, depthMask, CompareOp.Always)); + _pipeline.SetStencilTest(CreateStencilTestDescriptor(stencilMask != 0, stencilValue, 0xff, stencilMask)); + _pipeline.Draw(4, 1, 0, 0); + _pipeline.Finish(); + } + + public void DrawTexture( + VulkanRenderer gd, + PipelineBase pipeline, + TextureView src, + ISampler srcSampler, + Extents2DF srcRegion, + Extents2DF dstRegion) + { + const int RegionBufferSize = 16; + + pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, srcSampler); + + Span region = stackalloc float[RegionBufferSize / sizeof(float)]; + + region[0] = srcRegion.X1 / src.Width; + region[1] = srcRegion.X2 / src.Width; + region[2] = srcRegion.Y1 / src.Height; + region[3] = srcRegion.Y2 / src.Height; + + if (dstRegion.X1 > dstRegion.X2) + { + (region[0], region[1]) = (region[1], region[0]); + } + + if (dstRegion.Y1 > dstRegion.Y2) + { + (region[2], region[3]) = (region[3], region[2]); + } + + var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize); + + gd.BufferManager.SetData(bufferHandle, 0, region); + + pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) }); + + Span viewports = stackalloc Viewport[1]; + + var rect = new Rectangle( + MathF.Min(dstRegion.X1, dstRegion.X2), + MathF.Min(dstRegion.Y1, dstRegion.Y2), + MathF.Abs(dstRegion.X2 - dstRegion.X1), + MathF.Abs(dstRegion.Y2 - dstRegion.Y1)); + + viewports[0] = new Viewport( + rect, + ViewportSwizzle.PositiveX, + ViewportSwizzle.PositiveY, + ViewportSwizzle.PositiveZ, + ViewportSwizzle.PositiveW, + 0f, + 1f); + + pipeline.SetProgram(_programColorBlit); + pipeline.SetViewports(viewports); + pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); + pipeline.Draw(4, 1, 0, 0); + + gd.BufferManager.Delete(bufferHandle); + } + + public void ConvertI8ToI16(VulkanRenderer gd, CommandBufferScoped cbs, BufferHolder src, BufferHolder dst, int srcOffset, int size) + { + ChangeStride(gd, cbs, src, dst, srcOffset, size, 1, 2); + } + + public unsafe void ChangeStride(VulkanRenderer gd, CommandBufferScoped cbs, BufferHolder src, BufferHolder dst, int srcOffset, int size, int stride, int newStride) + { + bool supportsUint8 = gd.Capabilities.SupportsShaderInt8; + + int elems = size / stride; + int newSize = elems * newStride; + + var srcBufferAuto = src.GetBuffer(); + var dstBufferAuto = dst.GetBuffer(); + + var srcBuffer = srcBufferAuto.Get(cbs, srcOffset, size).Value; + var dstBuffer = dstBufferAuto.Get(cbs, 0, newSize).Value; + + var access = supportsUint8 ? AccessFlags.ShaderWriteBit : AccessFlags.TransferWriteBit; + var stage = supportsUint8 ? PipelineStageFlags.ComputeShaderBit : PipelineStageFlags.TransferBit; + + BufferHolder.InsertBufferBarrier( + gd, + cbs.CommandBuffer, + dstBuffer, + BufferHolder.DefaultAccessFlags, + access, + PipelineStageFlags.AllCommandsBit, + stage, + 0, + newSize); + + if (supportsUint8) + { + const int ParamsBufferSize = 16; + + Span shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)]; + + shaderParams[0] = stride; + shaderParams[1] = newStride; + shaderParams[2] = size; + shaderParams[3] = srcOffset; + + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, ParamsBufferSize); + + buffer.Holder.SetDataUnchecked(buffer.Offset, shaderParams); + + _pipeline.SetCommandBuffer(cbs); + + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, buffer.Range) }); + + Span> sbRanges = new Auto[2]; + + sbRanges[0] = srcBufferAuto; + sbRanges[1] = dstBufferAuto; + + _pipeline.SetStorageBuffers(1, sbRanges); + + _pipeline.SetProgram(_programStrideChange); + _pipeline.DispatchCompute(1 + elems / ConvertElementsPerWorkgroup, 1, 1); + + _pipeline.Finish(gd, cbs); + } + else + { + gd.Api.CmdFillBuffer(cbs.CommandBuffer, dstBuffer, 0, Vk.WholeSize, 0); + + var bufferCopy = new BufferCopy[elems]; + + for (ulong i = 0; i < (ulong)elems; i++) + { + bufferCopy[i] = new BufferCopy((ulong)srcOffset + i * (ulong)stride, i * (ulong)newStride, (ulong)stride); + } + + fixed (BufferCopy* pBufferCopy = bufferCopy) + { + gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, (uint)elems, pBufferCopy); + } + } + + BufferHolder.InsertBufferBarrier( + gd, + cbs.CommandBuffer, + dstBuffer, + access, + BufferHolder.DefaultAccessFlags, + stage, + PipelineStageFlags.AllCommandsBit, + 0, + newSize); + } + + public unsafe void ConvertIndexBuffer(VulkanRenderer gd, + CommandBufferScoped cbs, + BufferHolder src, + BufferHolder dst, + IndexBufferPattern pattern, + int indexSize, + int srcOffset, + int indexCount) + { + // TODO: Support conversion with primitive restart enabled. + // TODO: Convert with a compute shader? + + int convertedCount = pattern.GetConvertedCount(indexCount); + int outputIndexSize = 4; + + var srcBuffer = src.GetBuffer().Get(cbs, srcOffset, indexCount * indexSize).Value; + var dstBuffer = dst.GetBuffer().Get(cbs, 0, convertedCount * outputIndexSize).Value; + + gd.Api.CmdFillBuffer(cbs.CommandBuffer, dstBuffer, 0, Vk.WholeSize, 0); + + var bufferCopy = new List(); + int outputOffset = 0; + + // Try to merge copies of adjacent indices to reduce copy count. + int sequenceStart = 0; + int sequenceLength = 0; + + foreach (var index in pattern.GetIndexMapping(indexCount)) + { + if (sequenceLength > 0) + { + if (index == sequenceStart + sequenceLength && indexSize == outputIndexSize) + { + sequenceLength++; + continue; + } + + // Commit the copy so far. + bufferCopy.Add(new BufferCopy((ulong)(srcOffset + sequenceStart * indexSize), (ulong)outputOffset, (ulong)(indexSize * sequenceLength))); + outputOffset += outputIndexSize * sequenceLength; + } + + sequenceStart = index; + sequenceLength = 1; + } + + if (sequenceLength > 0) + { + // Commit final pending copy. + bufferCopy.Add(new BufferCopy((ulong)(srcOffset + sequenceStart * indexSize), (ulong)outputOffset, (ulong)(indexSize * sequenceLength))); + } + + var bufferCopyArray = bufferCopy.ToArray(); + + BufferHolder.InsertBufferBarrier( + gd, + cbs.CommandBuffer, + dstBuffer, + BufferHolder.DefaultAccessFlags, + AccessFlags.TransferWriteBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.TransferBit, + 0, + convertedCount * outputIndexSize); + + fixed (BufferCopy* pBufferCopy = bufferCopyArray) + { + gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, (uint)bufferCopyArray.Length, pBufferCopy); + } + + BufferHolder.InsertBufferBarrier( + gd, + cbs.CommandBuffer, + dstBuffer, + AccessFlags.TransferWriteBit, + BufferHolder.DefaultAccessFlags, + PipelineStageFlags.TransferBit, + PipelineStageFlags.AllCommandsBit, + 0, + convertedCount * outputIndexSize); + } + + public void CopyIncompatibleFormats( + VulkanRenderer gd, + CommandBufferScoped cbs, + TextureView src, + TextureView dst, + int srcLayer, + int dstLayer, + int srcLevel, + int dstLevel, + int depth, + int levels) + { + const int ParamsBufferSize = 4; + + Span shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)]; + + int srcBpp = src.Info.BytesPerPixel; + int dstBpp = dst.Info.BytesPerPixel; + + int ratio = srcBpp < dstBpp ? dstBpp / srcBpp : srcBpp / dstBpp; + + shaderParams[0] = BitOperations.Log2((uint)ratio); + + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, ParamsBufferSize); + + buffer.Holder.SetDataUnchecked(buffer.Offset, shaderParams); + + TextureView.InsertImageBarrier( + gd.Api, + cbs.CommandBuffer, + src.GetImage().Get(cbs).Value, + TextureStorage.DefaultAccessMask, + AccessFlags.ShaderReadBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.ComputeShaderBit, + ImageAspectFlags.ColorBit, + src.FirstLayer + srcLayer, + src.FirstLevel + srcLevel, + depth, + levels); + + _pipeline.SetCommandBuffer(cbs); + + _pipeline.SetProgram(srcBpp < dstBpp ? _programColorCopyWidening : _programColorCopyShortening); + + // Calculate ideal component size, given our constraints: + // - Component size must not exceed bytes per pixel of source and destination image formats. + // - Maximum component size is 4 (R32). + int componentSize = Math.Min(Math.Min(srcBpp, dstBpp), 4); + + var srcFormat = GetFormat(componentSize, srcBpp / componentSize); + var dstFormat = GetFormat(componentSize, dstBpp / componentSize); + + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, buffer.Range) }); + + for (int l = 0; l < levels; l++) + { + for (int z = 0; z < depth; z++) + { + var srcView = Create2DLayerView(src, srcLayer + z, srcLevel + l, srcFormat); + var dstView = Create2DLayerView(dst, dstLayer + z, dstLevel + l); + + _pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null); + _pipeline.SetImage(ShaderStage.Compute, 0, dstView, dstFormat); + + int dispatchX = (Math.Min(srcView.Info.Width, dstView.Info.Width) + 31) / 32; + int dispatchY = (Math.Min(srcView.Info.Height, dstView.Info.Height) + 31) / 32; + + _pipeline.DispatchCompute(dispatchX, dispatchY, 1); + + if (srcView != src) + { + srcView.Release(); + } + + if (dstView != dst) + { + dstView.Release(); + } + } + } + + _pipeline.Finish(gd, cbs); + + TextureView.InsertImageBarrier( + gd.Api, + cbs.CommandBuffer, + dst.GetImage().Get(cbs).Value, + AccessFlags.ShaderWriteBit, + TextureStorage.DefaultAccessMask, + PipelineStageFlags.ComputeShaderBit, + PipelineStageFlags.AllCommandsBit, + ImageAspectFlags.ColorBit, + dst.FirstLayer + dstLayer, + dst.FirstLevel + dstLevel, + depth, + levels); + } + + public void CopyMSToNonMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, TextureView dst, int srcLayer, int dstLayer, int depth) + { + const int ParamsBufferSize = 16; + + Span shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)]; + + int samples = src.Info.Samples; + bool isDepthOrStencil = src.Info.Format.IsDepthOrStencil(); + var aspectFlags = src.Info.Format.ConvertAspectFlags(); + + // X and Y are the expected texture samples. + // Z and W are the actual texture samples used. + // They may differ if the GPU does not support the samples count requested and we had to use a lower amount. + (shaderParams[0], shaderParams[1]) = GetSampleCountXYLog2(samples); + (shaderParams[2], shaderParams[3]) = GetSampleCountXYLog2((int)TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)samples)); + + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, ParamsBufferSize); + + buffer.Holder.SetDataUnchecked(buffer.Offset, shaderParams); + + TextureView.InsertImageBarrier( + gd.Api, + cbs.CommandBuffer, + src.GetImage().Get(cbs).Value, + TextureStorage.DefaultAccessMask, + AccessFlags.ShaderReadBit, + PipelineStageFlags.AllCommandsBit, + isDepthOrStencil ? PipelineStageFlags.FragmentShaderBit : PipelineStageFlags.ComputeShaderBit, + aspectFlags, + src.FirstLayer + srcLayer, + src.FirstLevel, + depth, + 1); + + _pipeline.SetCommandBuffer(cbs); + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, buffer.Range) }); + + if (isDepthOrStencil) + { + // We can't use compute for this case because compute can't modify depth textures. + + Span viewports = stackalloc Viewport[1]; + + var rect = new Rectangle(0, 0, dst.Width, dst.Height); + + viewports[0] = new Viewport( + rect, + ViewportSwizzle.PositiveX, + ViewportSwizzle.PositiveY, + ViewportSwizzle.PositiveZ, + ViewportSwizzle.PositiveW, + 0f, + 1f); + + _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dst.Width, dst.Height) }); + _pipeline.SetViewports(viewports); + _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); + + for (int z = 0; z < depth; z++) + { + var srcView = Create2DLayerView(src, srcLayer + z, 0); + var dstView = Create2DLayerView(dst, dstLayer + z, 0); + + _pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height); + + CopyMSDraw(srcView, aspectFlags, fromMS: true); + + if (srcView != src) + { + srcView.Release(); + } + + if (dstView != dst) + { + dstView.Release(); + } + } + } + else + { + var format = GetFormat(src.Info.BytesPerPixel); + + int dispatchX = (dst.Info.Width + 31) / 32; + int dispatchY = (dst.Info.Height + 31) / 32; + + _pipeline.SetProgram(_programColorCopyToNonMs); + + for (int z = 0; z < depth; z++) + { + var srcView = Create2DLayerView(src, srcLayer + z, 0, format); + var dstView = Create2DLayerView(dst, dstLayer + z, 0); + + _pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null); + _pipeline.SetImage(ShaderStage.Compute, 0, dstView, format); + + _pipeline.DispatchCompute(dispatchX, dispatchY, 1); + + if (srcView != src) + { + srcView.Release(); + } + + if (dstView != dst) + { + dstView.Release(); + } + } + } + + _pipeline.Finish(gd, cbs); + + TextureView.InsertImageBarrier( + gd.Api, + cbs.CommandBuffer, + dst.GetImage().Get(cbs).Value, + isDepthOrStencil ? AccessFlags.DepthStencilAttachmentWriteBit : AccessFlags.ShaderWriteBit, + TextureStorage.DefaultAccessMask, + isDepthOrStencil ? PipelineStageFlags.LateFragmentTestsBit : PipelineStageFlags.ComputeShaderBit, + PipelineStageFlags.AllCommandsBit, + aspectFlags, + dst.FirstLayer + dstLayer, + dst.FirstLevel, + depth, + 1); + } + + public void CopyNonMSToMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, TextureView dst, int srcLayer, int dstLayer, int depth) + { + const int ParamsBufferSize = 16; + + Span shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)]; + + int samples = dst.Info.Samples; + bool isDepthOrStencil = src.Info.Format.IsDepthOrStencil(); + var aspectFlags = src.Info.Format.ConvertAspectFlags(); + + // X and Y are the expected texture samples. + // Z and W are the actual texture samples used. + // They may differ if the GPU does not support the samples count requested and we had to use a lower amount. + (shaderParams[0], shaderParams[1]) = GetSampleCountXYLog2(samples); + (shaderParams[2], shaderParams[3]) = GetSampleCountXYLog2((int)TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)samples)); + + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, ParamsBufferSize); + + buffer.Holder.SetDataUnchecked(buffer.Offset, shaderParams); + + TextureView.InsertImageBarrier( + gd.Api, + cbs.CommandBuffer, + src.GetImage().Get(cbs).Value, + TextureStorage.DefaultAccessMask, + AccessFlags.ShaderReadBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.FragmentShaderBit, + aspectFlags, + src.FirstLayer + srcLayer, + src.FirstLevel, + depth, + 1); + + _pipeline.SetCommandBuffer(cbs); + + Span viewports = stackalloc Viewport[1]; + + var rect = new Rectangle(0, 0, dst.Width, dst.Height); + + viewports[0] = new Viewport( + rect, + ViewportSwizzle.PositiveX, + ViewportSwizzle.PositiveY, + ViewportSwizzle.PositiveZ, + ViewportSwizzle.PositiveW, + 0f, + 1f); + + _pipeline.SetRenderTargetColorMasks(new uint[] { 0xf }); + _pipeline.SetScissors(stackalloc Rectangle[] { new Rectangle(0, 0, dst.Width, dst.Height) }); + _pipeline.SetViewports(viewports); + _pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip); + + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, buffer.Range) }); + + if (isDepthOrStencil) + { + for (int z = 0; z < depth; z++) + { + var srcView = Create2DLayerView(src, srcLayer + z, 0); + var dstView = Create2DLayerView(dst, dstLayer + z, 0); + + _pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height); + + CopyMSDraw(srcView, aspectFlags, fromMS: false); + + if (srcView != src) + { + srcView.Release(); + } + + if (dstView != dst) + { + dstView.Release(); + } + } + } + else + { + _pipeline.SetProgram(_programColorDrawToMs); + + var format = GetFormat(src.Info.BytesPerPixel); + var vkFormat = FormatTable.GetFormat(format); + + for (int z = 0; z < depth; z++) + { + var srcView = Create2DLayerView(src, srcLayer + z, 0, format); + var dstView = Create2DLayerView(dst, dstLayer + z, 0); + + _pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Fragment, 0, srcView, null); + _pipeline.SetRenderTarget(dstView.GetView(format), (uint)dst.Width, (uint)dst.Height); + + _pipeline.Draw(4, 1, 0, 0); + + if (srcView != src) + { + srcView.Release(); + } + + if (dstView != dst) + { + dstView.Release(); + } + } + } + + _pipeline.Finish(gd, cbs); + + TextureView.InsertImageBarrier( + gd.Api, + cbs.CommandBuffer, + dst.GetImage().Get(cbs).Value, + isDepthOrStencil ? AccessFlags.DepthStencilAttachmentWriteBit : AccessFlags.ColorAttachmentWriteBit, + TextureStorage.DefaultAccessMask, + isDepthOrStencil ? PipelineStageFlags.LateFragmentTestsBit : PipelineStageFlags.ColorAttachmentOutputBit, + PipelineStageFlags.AllCommandsBit, + aspectFlags, + dst.FirstLayer + dstLayer, + dst.FirstLevel, + depth, + 1); + } + + private void CopyMSDraw(TextureView src, ImageAspectFlags aspectFlags, bool fromMS) + { + if (aspectFlags.HasFlag(ImageAspectFlags.DepthBit)) + { + var depthTexture = CreateDepthOrStencilView(src, DepthStencilMode.Depth); + + CopyMSAspectDraw(depthTexture, fromMS, isDepth: true); + + if (depthTexture != src) + { + depthTexture.Release(); + } + } + + if (aspectFlags.HasFlag(ImageAspectFlags.StencilBit) && _programStencilDrawToMs != null) + { + var stencilTexture = CreateDepthOrStencilView(src, DepthStencilMode.Stencil); + + CopyMSAspectDraw(stencilTexture, fromMS, isDepth: false); + + if (stencilTexture != src) + { + stencilTexture.Release(); + } + } + } + + private void CopyMSAspectDraw(TextureView src, bool fromMS, bool isDepth) + { + _pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Fragment, 0, src, _samplerNearest); + + if (isDepth) + { + _pipeline.SetProgram(fromMS ? _programDepthDrawToNonMs : _programDepthDrawToMs); + _pipeline.SetDepthTest(new DepthTestDescriptor(true, true, CompareOp.Always)); + } + else + { + _pipeline.SetProgram(fromMS ? _programStencilDrawToNonMs : _programStencilDrawToMs); + _pipeline.SetStencilTest(CreateStencilTestDescriptor(true)); + } + + _pipeline.Draw(4, 1, 0, 0); + + if (isDepth) + { + _pipeline.SetDepthTest(new DepthTestDescriptor(false, false, CompareOp.Always)); + } + else + { + _pipeline.SetStencilTest(CreateStencilTestDescriptor(false)); + } + } + + private static (int, int) GetSampleCountXYLog2(int samples) + { + int samplesInXLog2 = 0; + int samplesInYLog2 = 0; + + switch (samples) + { + case 2: // 2x1 + samplesInXLog2 = 1; + break; + case 4: // 2x2 + samplesInXLog2 = 1; + samplesInYLog2 = 1; + break; + case 8: // 4x2 + samplesInXLog2 = 2; + samplesInYLog2 = 1; + break; + case 16: // 4x4 + samplesInXLog2 = 2; + samplesInYLog2 = 2; + break; + case 32: // 8x4 + samplesInXLog2 = 3; + samplesInYLog2 = 2; + break; + case 64: // 8x8 + samplesInXLog2 = 3; + samplesInYLog2 = 3; + break; + } + + return (samplesInXLog2, samplesInYLog2); + } + + private static TextureView Create2DLayerView(TextureView from, int layer, int level, Format? format = null) + { + if (from.Info.Target == Target.Texture2D && level == 0 && (format == null || format.Value == from.Info.Format)) + { + return from; + } + + var target = from.Info.Target switch + { + Target.Texture1DArray => Target.Texture1D, + Target.Texture2DMultisampleArray => Target.Texture2DMultisample, + _ => Target.Texture2D, + }; + + var info = new TextureCreateInfo( + Math.Max(1, from.Info.Width >> level), + Math.Max(1, from.Info.Height >> level), + 1, + 1, + from.Info.Samples, + from.Info.BlockWidth, + from.Info.BlockHeight, + from.Info.BytesPerPixel, + format ?? from.Info.Format, + from.Info.DepthStencilMode, + target, + from.Info.SwizzleR, + from.Info.SwizzleG, + from.Info.SwizzleB, + from.Info.SwizzleA); + + return from.CreateViewImpl(info, layer, level); + } + + private static Format GetFormat(int bytesPerPixel) + { + return bytesPerPixel switch + { + 1 => Format.R8Uint, + 2 => Format.R16Uint, + 4 => Format.R32Uint, + 8 => Format.R32G32Uint, + 16 => Format.R32G32B32A32Uint, + _ => throw new ArgumentException($"Invalid bytes per pixel {bytesPerPixel}."), + }; + } + + private static Format GetFormat(int componentSize, int componentsCount) + { + if (componentSize == 1) + { + return componentsCount switch + { + 1 => Format.R8Uint, + 2 => Format.R8G8Uint, + 4 => Format.R8G8B8A8Uint, + _ => throw new ArgumentException($"Invalid components count {componentsCount}."), + }; + } + + if (componentSize == 2) + { + return componentsCount switch + { + 1 => Format.R16Uint, + 2 => Format.R16G16Uint, + 4 => Format.R16G16B16A16Uint, + _ => throw new ArgumentException($"Invalid components count {componentsCount}."), + }; + } + + if (componentSize == 4) + { + return componentsCount switch + { + 1 => Format.R32Uint, + 2 => Format.R32G32Uint, + 4 => Format.R32G32B32A32Uint, + _ => throw new ArgumentException($"Invalid components count {componentsCount}."), + }; + } + + throw new ArgumentException($"Invalid component size {componentSize}."); + } + + public void ConvertIndexBufferIndirect( + VulkanRenderer gd, + CommandBufferScoped cbs, + BufferHolder srcIndirectBuffer, + BufferHolder dstIndirectBuffer, + BufferRange drawCountBuffer, + BufferHolder srcIndexBuffer, + BufferHolder dstIndexBuffer, + IndexBufferPattern pattern, + int indexSize, + int srcIndexBufferOffset, + int srcIndexBufferSize, + int srcIndirectBufferOffset, + bool hasDrawCount, + int maxDrawCount, + int indirectDataStride) + { + // TODO: Support conversion with primitive restart enabled. + + BufferRange drawCountBufferAligned = new( + drawCountBuffer.Handle, + drawCountBuffer.Offset & ~(UniformBufferAlignment - 1), + UniformBufferAlignment); + + int indirectDataSize = maxDrawCount * indirectDataStride; + + int indexCount = srcIndexBufferSize / indexSize; + int primitivesCount = pattern.GetPrimitiveCount(indexCount); + int convertedCount = pattern.GetConvertedCount(indexCount); + int outputIndexSize = 4; + + var srcBuffer = srcIndexBuffer.GetBuffer().Get(cbs, srcIndexBufferOffset, indexCount * indexSize).Value; + var dstBuffer = dstIndexBuffer.GetBuffer().Get(cbs, 0, convertedCount * outputIndexSize).Value; + + const int ParamsBufferSize = 24 * sizeof(int); + const int ParamsIndirectDispatchOffset = 16 * sizeof(int); + const int ParamsIndirectDispatchSize = 3 * sizeof(int); + + Span shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)]; + + shaderParams[8] = pattern.PrimitiveVertices; + shaderParams[9] = pattern.PrimitiveVerticesOut; + shaderParams[10] = indexSize; + shaderParams[11] = outputIndexSize; + shaderParams[12] = pattern.BaseIndex; + shaderParams[13] = pattern.IndexStride; + shaderParams[14] = srcIndexBufferOffset; + shaderParams[15] = primitivesCount; + shaderParams[16] = 1; + shaderParams[17] = 1; + shaderParams[18] = 1; + shaderParams[19] = hasDrawCount ? 1 : 0; + shaderParams[20] = maxDrawCount; + shaderParams[21] = (drawCountBuffer.Offset & (UniformBufferAlignment - 1)) / 4; + shaderParams[22] = indirectDataStride / 4; + shaderParams[23] = srcIndirectBufferOffset / 4; + + pattern.OffsetIndex.CopyTo(shaderParams[..pattern.OffsetIndex.Length]); + + using var patternScoped = gd.BufferManager.ReserveOrCreate(gd, cbs, ParamsBufferSize); + var patternBuffer = patternScoped.Holder; + var patternBufferAuto = patternBuffer.GetBuffer(); + + patternBuffer.SetDataUnchecked(patternScoped.Offset, shaderParams); + + _pipeline.SetCommandBuffer(cbs); + + BufferHolder.InsertBufferBarrier( + gd, + cbs.CommandBuffer, + srcIndirectBuffer.GetBuffer().Get(cbs, srcIndirectBufferOffset, indirectDataSize).Value, + BufferHolder.DefaultAccessFlags, + AccessFlags.ShaderReadBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.ComputeShaderBit, + srcIndirectBufferOffset, + indirectDataSize); + + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, drawCountBufferAligned) }); + _pipeline.SetStorageBuffers(1, new[] { srcIndirectBuffer.GetBuffer(), dstIndirectBuffer.GetBuffer() }); + _pipeline.SetStorageBuffers(stackalloc[] { new BufferAssignment(3, patternScoped.Range) }); + + _pipeline.SetProgram(_programConvertIndirectData); + _pipeline.DispatchCompute(1, 1, 1); + + BufferHolder.InsertBufferBarrier( + gd, + cbs.CommandBuffer, + patternBufferAuto.Get(cbs, patternScoped.Offset + ParamsIndirectDispatchOffset, ParamsIndirectDispatchSize).Value, + AccessFlags.ShaderWriteBit, + AccessFlags.IndirectCommandReadBit, + PipelineStageFlags.ComputeShaderBit, + PipelineStageFlags.DrawIndirectBit, + patternScoped.Offset + ParamsIndirectDispatchOffset, + ParamsIndirectDispatchSize); + + BufferHolder.InsertBufferBarrier( + gd, + cbs.CommandBuffer, + dstBuffer, + BufferHolder.DefaultAccessFlags, + AccessFlags.TransferWriteBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.TransferBit, + 0, + convertedCount * outputIndexSize); + + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(patternScoped.Handle, patternScoped.Offset, ParamsBufferSize)) }); + _pipeline.SetStorageBuffers(1, new[] { srcIndexBuffer.GetBuffer(), dstIndexBuffer.GetBuffer() }); + + _pipeline.SetProgram(_programConvertIndexBuffer); + _pipeline.DispatchComputeIndirect(patternBufferAuto, patternScoped.Offset + ParamsIndirectDispatchOffset); + + BufferHolder.InsertBufferBarrier( + gd, + cbs.CommandBuffer, + dstBuffer, + AccessFlags.TransferWriteBit, + BufferHolder.DefaultAccessFlags, + PipelineStageFlags.TransferBit, + PipelineStageFlags.AllCommandsBit, + 0, + convertedCount * outputIndexSize); + + _pipeline.Finish(gd, cbs); + } + + public unsafe void ConvertD32S8ToD24S8(VulkanRenderer gd, CommandBufferScoped cbs, BufferHolder src, Auto dstBufferAuto, int pixelCount, int dstOffset) + { + int inSize = pixelCount * 2 * sizeof(int); + int outSize = pixelCount * sizeof(int); + + var srcBufferAuto = src.GetBuffer(); + + var srcBuffer = srcBufferAuto.Get(cbs, 0, inSize).Value; + var dstBuffer = dstBufferAuto.Get(cbs, dstOffset, outSize).Value; + + var access = AccessFlags.ShaderWriteBit; + var stage = PipelineStageFlags.ComputeShaderBit; + + BufferHolder.InsertBufferBarrier( + gd, + cbs.CommandBuffer, + srcBuffer, + BufferHolder.DefaultAccessFlags, + AccessFlags.ShaderReadBit, + PipelineStageFlags.AllCommandsBit, + stage, + 0, + outSize); + + BufferHolder.InsertBufferBarrier( + gd, + cbs.CommandBuffer, + dstBuffer, + BufferHolder.DefaultAccessFlags, + access, + PipelineStageFlags.AllCommandsBit, + stage, + 0, + outSize); + + const int ParamsBufferSize = sizeof(int) * 2; + + Span shaderParams = stackalloc int[2]; + + shaderParams[0] = pixelCount; + shaderParams[1] = dstOffset; + + using var buffer = gd.BufferManager.ReserveOrCreate(gd, cbs, ParamsBufferSize); + + buffer.Holder.SetDataUnchecked(buffer.Offset, shaderParams); + + _pipeline.SetCommandBuffer(cbs); + + _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, buffer.Range) }); + + Span> sbRanges = new Auto[2]; + + sbRanges[0] = srcBufferAuto; + sbRanges[1] = dstBufferAuto; + + _pipeline.SetStorageBuffers(1, sbRanges); + + _pipeline.SetProgram(_programConvertD32S8ToD24S8); + _pipeline.DispatchCompute(1 + inSize / ConvertElementsPerWorkgroup, 1, 1); + + _pipeline.Finish(gd, cbs); + + BufferHolder.InsertBufferBarrier( + gd, + cbs.CommandBuffer, + dstBuffer, + access, + BufferHolder.DefaultAccessFlags, + stage, + PipelineStageFlags.AllCommandsBit, + 0, + outSize); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _programColorBlitClearAlpha.Dispose(); + _programColorBlit.Dispose(); + _programColorBlitMs.Dispose(); + _programColorClearF.Dispose(); + _programColorClearSI.Dispose(); + _programColorClearUI.Dispose(); + _programDepthStencilClear.Dispose(); + _programStrideChange.Dispose(); + _programConvertIndexBuffer.Dispose(); + _programConvertIndirectData.Dispose(); + _programColorCopyShortening.Dispose(); + _programColorCopyToNonMs.Dispose(); + _programColorCopyWidening.Dispose(); + _programColorDrawToMs.Dispose(); + _programDepthBlit.Dispose(); + _programDepthBlitMs.Dispose(); + _programDepthDrawToMs.Dispose(); + _programDepthDrawToNonMs.Dispose(); + _programStencilBlit?.Dispose(); + _programStencilBlitMs?.Dispose(); + _programStencilDrawToMs?.Dispose(); + _programStencilDrawToNonMs?.Dispose(); + _samplerNearest.Dispose(); + _samplerLinear.Dispose(); + _pipeline.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/HostMemoryAllocator.cs b/src/Ryujinx.Graphics.Vulkan/HostMemoryAllocator.cs new file mode 100644 index 00000000..ff156524 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/HostMemoryAllocator.cs @@ -0,0 +1,188 @@ +using Ryujinx.Common; +using Ryujinx.Common.Collections; +using Ryujinx.Common.Logging; +using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.EXT; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + internal class HostMemoryAllocator + { + private readonly struct HostMemoryAllocation + { + public readonly Auto Allocation; + public readonly IntPtr Pointer; + public readonly ulong Size; + + public ulong Start => (ulong)Pointer; + public ulong End => (ulong)Pointer + Size; + + public HostMemoryAllocation(Auto allocation, IntPtr pointer, ulong size) + { + Allocation = allocation; + Pointer = pointer; + Size = size; + } + } + + private readonly MemoryAllocator _allocator; + private readonly Vk _api; + private readonly ExtExternalMemoryHost _hostMemoryApi; + private readonly Device _device; + private readonly object _lock = new(); + + private readonly List _allocations; + private readonly IntervalTree _allocationTree; + + public HostMemoryAllocator(MemoryAllocator allocator, Vk api, ExtExternalMemoryHost hostMemoryApi, Device device) + { + _allocator = allocator; + _api = api; + _hostMemoryApi = hostMemoryApi; + _device = device; + + _allocations = new List(); + _allocationTree = new IntervalTree(); + } + + public unsafe bool TryImport( + MemoryRequirements requirements, + MemoryPropertyFlags flags, + IntPtr pointer, + ulong size) + { + lock (_lock) + { + // Does a compatible allocation exist in the tree? + var allocations = new HostMemoryAllocation[10]; + + ulong start = (ulong)pointer; + ulong end = start + size; + + int count = _allocationTree.Get(start, end, ref allocations); + + // A compatible range is one that where the start and end completely cover the requested range. + for (int i = 0; i < count; i++) + { + HostMemoryAllocation existing = allocations[i]; + + if (start >= existing.Start && end <= existing.End) + { + try + { + existing.Allocation.IncrementReferenceCount(); + + return true; + } + catch (InvalidOperationException) + { + // Can throw if the allocation has been disposed. + // Just continue the search if this happens. + } + } + } + + nint pageAlignedPointer = BitUtils.AlignDown(pointer, Environment.SystemPageSize); + nint pageAlignedEnd = BitUtils.AlignUp((nint)((ulong)pointer + size), Environment.SystemPageSize); + ulong pageAlignedSize = (ulong)(pageAlignedEnd - pageAlignedPointer); + + Result getResult = _hostMemoryApi.GetMemoryHostPointerProperties(_device, ExternalMemoryHandleTypeFlags.HostAllocationBitExt, (void*)pageAlignedPointer, out MemoryHostPointerPropertiesEXT properties); + if (getResult < Result.Success) + { + return false; + } + + int memoryTypeIndex = _allocator.FindSuitableMemoryTypeIndex(properties.MemoryTypeBits & requirements.MemoryTypeBits, flags); + if (memoryTypeIndex < 0) + { + return false; + } + + ImportMemoryHostPointerInfoEXT importInfo = new() + { + SType = StructureType.ImportMemoryHostPointerInfoExt, + HandleType = ExternalMemoryHandleTypeFlags.HostAllocationBitExt, + PHostPointer = (void*)pageAlignedPointer, + }; + + var memoryAllocateInfo = new MemoryAllocateInfo + { + SType = StructureType.MemoryAllocateInfo, + AllocationSize = pageAlignedSize, + MemoryTypeIndex = (uint)memoryTypeIndex, + PNext = &importInfo, + }; + + Result result = _api.AllocateMemory(_device, in memoryAllocateInfo, null, out var deviceMemory); + + if (result < Result.Success) + { + Logger.Debug?.PrintMsg(LogClass.Gpu, $"Host mapping import 0x{pageAlignedPointer:x16} 0x{pageAlignedSize:x8} failed."); + return false; + } + + var allocation = new MemoryAllocation(this, deviceMemory, pageAlignedPointer, 0, pageAlignedSize); + var allocAuto = new Auto(allocation); + var hostAlloc = new HostMemoryAllocation(allocAuto, pageAlignedPointer, pageAlignedSize); + + allocAuto.IncrementReferenceCount(); + allocAuto.Dispose(); // Kept alive by ref count only. + + // Register this mapping for future use. + + _allocationTree.Add(hostAlloc.Start, hostAlloc.End, hostAlloc); + _allocations.Add(hostAlloc); + } + + return true; + } + + public (Auto, ulong) GetExistingAllocation(IntPtr pointer, ulong size) + { + lock (_lock) + { + // Does a compatible allocation exist in the tree? + var allocations = new HostMemoryAllocation[10]; + + ulong start = (ulong)pointer; + ulong end = start + size; + + int count = _allocationTree.Get(start, end, ref allocations); + + // A compatible range is one that where the start and end completely cover the requested range. + for (int i = 0; i < count; i++) + { + HostMemoryAllocation existing = allocations[i]; + + if (start >= existing.Start && end <= existing.End) + { + return (existing.Allocation, start - existing.Start); + } + } + + throw new InvalidOperationException($"No host allocation was prepared for requested range 0x{pointer:x16}:0x{size:x16}."); + } + } + + public void Free(DeviceMemory memory, ulong offset, ulong size) + { + lock (_lock) + { + _allocations.RemoveAll(allocation => + { + if (allocation.Allocation.GetUnsafe().Memory.Handle == memory.Handle) + { + _allocationTree.Remove(allocation.Start, allocation); + return true; + } + + return false; + }); + } + + _api.FreeMemory(_device, memory, ReadOnlySpan.Empty); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/IdList.cs b/src/Ryujinx.Graphics.Vulkan/IdList.cs new file mode 100644 index 00000000..598d6858 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/IdList.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + class IdList where T : class + { + private readonly List _list; + private int _freeMin; + + public IdList() + { + _list = new List(); + _freeMin = 0; + } + + public int Add(T value) + { + int id; + int count = _list.Count; + id = _list.IndexOf(null, _freeMin); + + if ((uint)id < (uint)count) + { + _list[id] = value; + } + else + { + id = count; + _freeMin = id + 1; + + _list.Add(value); + } + + return id + 1; + } + + public void Remove(int id) + { + id--; + + int count = _list.Count; + + if ((uint)id >= (uint)count) + { + return; + } + + if (id + 1 == count) + { + // Trim unused items. + int removeIndex = id; + + while (removeIndex > 0 && _list[removeIndex - 1] == null) + { + removeIndex--; + } + + _list.RemoveRange(removeIndex, count - removeIndex); + + if (_freeMin > removeIndex) + { + _freeMin = removeIndex; + } + } + else + { + _list[id] = null; + + if (_freeMin > id) + { + _freeMin = id; + } + } + } + + public bool TryGetValue(int id, out T value) + { + id--; + + try + { + if ((uint)id < (uint)_list.Count) + { + value = _list[id]; + return value != null; + } + + value = null; + return false; + } + catch (ArgumentOutOfRangeException) + { + value = null; + return false; + } + catch (IndexOutOfRangeException) + { + value = null; + return false; + } + } + + public void Clear() + { + _list.Clear(); + _freeMin = 0; + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < _list.Count; i++) + { + if (_list[i] != null) + { + yield return _list[i]; + } + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/ImageArray.cs b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs new file mode 100644 index 00000000..467b0111 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs @@ -0,0 +1,218 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + class ImageArray : ResourceArray, IImageArray + { + private readonly VulkanRenderer _gd; + + private record struct TextureRef + { + public TextureStorage Storage; + public TextureView View; + public GAL.Format ImageFormat; + } + + private readonly TextureRef[] _textureRefs; + private readonly TextureBuffer[] _bufferTextureRefs; + + private readonly DescriptorImageInfo[] _textures; + private readonly BufferView[] _bufferTextures; + + private HashSet _storages; + + private int _cachedCommandBufferIndex; + private int _cachedSubmissionCount; + + private readonly bool _isBuffer; + + public ImageArray(VulkanRenderer gd, int size, bool isBuffer) + { + _gd = gd; + + if (isBuffer) + { + _bufferTextureRefs = new TextureBuffer[size]; + _bufferTextures = new BufferView[size]; + } + else + { + _textureRefs = new TextureRef[size]; + _textures = new DescriptorImageInfo[size]; + } + + _storages = null; + + _cachedCommandBufferIndex = -1; + _cachedSubmissionCount = 0; + + _isBuffer = isBuffer; + } + + public void SetFormats(int index, GAL.Format[] imageFormats) + { + for (int i = 0; i < imageFormats.Length; i++) + { + _textureRefs[index + i].ImageFormat = imageFormats[i]; + } + + SetDirty(); + } + + public void SetImages(int index, ITexture[] images) + { + for (int i = 0; i < images.Length; i++) + { + ITexture image = images[i]; + + if (image is TextureBuffer textureBuffer) + { + _bufferTextureRefs[index + i] = textureBuffer; + } + else if (image is TextureView view) + { + _textureRefs[index + i].Storage = view.Storage; + _textureRefs[index + i].View = view; + } + else if (!_isBuffer) + { + _textureRefs[index + i].Storage = null; + _textureRefs[index + i].View = default; + } + else + { + _bufferTextureRefs[index + i] = null; + } + } + + SetDirty(); + } + + private void SetDirty() + { + _cachedCommandBufferIndex = -1; + _storages = null; + SetDirty(_gd, isImage: true); + } + + public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags) + { + HashSet storages = _storages; + + if (storages == null) + { + storages = new HashSet(); + + for (int index = 0; index < _textureRefs.Length; index++) + { + if (_textureRefs[index].Storage != null) + { + storages.Add(_textureRefs[index].Storage); + } + } + + _storages = storages; + } + + foreach (TextureStorage storage in storages) + { + storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stageFlags); + } + } + + public ReadOnlySpan GetImageInfos(VulkanRenderer gd, CommandBufferScoped cbs, TextureView dummyTexture) + { + int submissionCount = gd.CommandBufferPool.GetSubmissionCount(cbs.CommandBufferIndex); + + Span textures = _textures; + + if (cbs.CommandBufferIndex == _cachedCommandBufferIndex && submissionCount == _cachedSubmissionCount) + { + return textures; + } + + _cachedCommandBufferIndex = cbs.CommandBufferIndex; + _cachedSubmissionCount = submissionCount; + + for (int i = 0; i < textures.Length; i++) + { + ref var texture = ref textures[i]; + ref var refs = ref _textureRefs[i]; + + if (i > 0 && _textureRefs[i - 1].View == refs.View && _textureRefs[i - 1].ImageFormat == refs.ImageFormat) + { + texture = textures[i - 1]; + + continue; + } + + texture.ImageLayout = ImageLayout.General; + texture.ImageView = refs.View?.GetView(refs.ImageFormat).GetIdentityImageView().Get(cbs).Value ?? default; + + if (texture.ImageView.Handle == 0) + { + texture.ImageView = dummyTexture.GetImageView().Get(cbs).Value; + } + } + + return textures; + } + + public ReadOnlySpan GetBufferViews(CommandBufferScoped cbs) + { + Span bufferTextures = _bufferTextures; + + for (int i = 0; i < bufferTextures.Length; i++) + { + bufferTextures[i] = _bufferTextureRefs[i]?.GetBufferView(cbs, _textureRefs[i].ImageFormat, true) ?? default; + } + + return bufferTextures; + } + + public DescriptorSet[] GetDescriptorSets( + Device device, + CommandBufferScoped cbs, + DescriptorSetTemplateUpdater templateUpdater, + ShaderCollection program, + int setIndex, + TextureView dummyTexture) + { + if (TryGetCachedDescriptorSets(cbs, program, setIndex, out DescriptorSet[] sets)) + { + // We still need to ensure the current command buffer holds a reference to all used textures. + + if (!_isBuffer) + { + GetImageInfos(_gd, cbs, dummyTexture); + } + else + { + GetBufferViews(cbs); + } + + return sets; + } + + DescriptorSetTemplate template = program.Templates[setIndex]; + + DescriptorSetTemplateWriter tu = templateUpdater.Begin(template); + + if (!_isBuffer) + { + tu.Push(GetImageInfos(_gd, cbs, dummyTexture)); + } + else + { + tu.Push(GetBufferViews(cbs)); + } + + templateUpdater.Commit(_gd, device, sets[0]); + + return sets; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs b/src/Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs new file mode 100644 index 00000000..7b01dd4c --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs @@ -0,0 +1,139 @@ +using Ryujinx.Graphics.GAL; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + internal class IndexBufferPattern : IDisposable + { + public int PrimitiveVertices { get; } + public int PrimitiveVerticesOut { get; } + public int BaseIndex { get; } + public int[] OffsetIndex { get; } + public int IndexStride { get; } + public bool RepeatStart { get; } + + private readonly VulkanRenderer _gd; + private int _currentSize; + private BufferHandle _repeatingBuffer; + + public IndexBufferPattern(VulkanRenderer gd, + int primitiveVertices, + int primitiveVerticesOut, + int baseIndex, + int[] offsetIndex, + int indexStride, + bool repeatStart) + { + PrimitiveVertices = primitiveVertices; + PrimitiveVerticesOut = primitiveVerticesOut; + BaseIndex = baseIndex; + OffsetIndex = offsetIndex; + IndexStride = indexStride; + RepeatStart = repeatStart; + + _gd = gd; + } + + public int GetPrimitiveCount(int vertexCount) + { + return Math.Max(0, (vertexCount - BaseIndex) / IndexStride); + } + + public int GetConvertedCount(int indexCount) + { + int primitiveCount = GetPrimitiveCount(indexCount); + return primitiveCount * OffsetIndex.Length; + } + + public IEnumerable GetIndexMapping(int indexCount) + { + int primitiveCount = GetPrimitiveCount(indexCount); + int index = BaseIndex; + + for (int i = 0; i < primitiveCount; i++) + { + if (RepeatStart) + { + // Used for triangle fan + yield return 0; + } + + for (int j = RepeatStart ? 1 : 0; j < OffsetIndex.Length; j++) + { + yield return index + OffsetIndex[j]; + } + + index += IndexStride; + } + } + + public BufferHandle GetRepeatingBuffer(int vertexCount, out int indexCount) + { + int primitiveCount = GetPrimitiveCount(vertexCount); + indexCount = primitiveCount * PrimitiveVerticesOut; + + int expectedSize = primitiveCount * OffsetIndex.Length; + + if (expectedSize <= _currentSize && _repeatingBuffer != BufferHandle.Null) + { + return _repeatingBuffer; + } + + // Expand the repeating pattern to the number of requested primitives. + BufferHandle newBuffer = _gd.BufferManager.CreateWithHandle(_gd, expectedSize * sizeof(int)); + + // Copy the old data to the new one. + if (_repeatingBuffer != BufferHandle.Null) + { + _gd.Pipeline.CopyBuffer(_repeatingBuffer, newBuffer, 0, 0, _currentSize * sizeof(int)); + _gd.DeleteBuffer(_repeatingBuffer); + } + + _repeatingBuffer = newBuffer; + + // Add the additional repeats on top. + int newPrimitives = primitiveCount; + int oldPrimitives = (_currentSize) / OffsetIndex.Length; + + int[] newData; + + newPrimitives -= oldPrimitives; + newData = new int[expectedSize - _currentSize]; + + int outOffset = 0; + int index = oldPrimitives * IndexStride + BaseIndex; + + for (int i = 0; i < newPrimitives; i++) + { + if (RepeatStart) + { + // Used for triangle fan + newData[outOffset++] = 0; + } + + for (int j = RepeatStart ? 1 : 0; j < OffsetIndex.Length; j++) + { + newData[outOffset++] = index + OffsetIndex[j]; + } + + index += IndexStride; + } + + _gd.SetBufferData(newBuffer, _currentSize * sizeof(int), MemoryMarshal.Cast(newData)); + _currentSize = expectedSize; + + return newBuffer; + } + + public void Dispose() + { + if (_repeatingBuffer != BufferHandle.Null) + { + _gd.DeleteBuffer(_repeatingBuffer); + _repeatingBuffer = BufferHandle.Null; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/IndexBufferState.cs b/src/Ryujinx.Graphics.Vulkan/IndexBufferState.cs new file mode 100644 index 00000000..d26fea30 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/IndexBufferState.cs @@ -0,0 +1,171 @@ +using Ryujinx.Graphics.GAL; +using IndexType = Silk.NET.Vulkan.IndexType; + +namespace Ryujinx.Graphics.Vulkan +{ + internal struct IndexBufferState + { + private const int IndexBufferMaxMirrorable = 0x20000; + + public static IndexBufferState Null => new(BufferHandle.Null, 0, 0); + + private readonly int _offset; + private readonly int _size; + private readonly IndexType _type; + + private readonly BufferHandle _handle; + private Auto _buffer; + + public IndexBufferState(BufferHandle handle, int offset, int size, IndexType type) + { + _handle = handle; + _offset = offset; + _size = size; + _type = type; + _buffer = null; + } + + public IndexBufferState(BufferHandle handle, int offset, int size) + { + _handle = handle; + _offset = offset; + _size = size; + _type = IndexType.Uint16; + _buffer = null; + } + + public void BindIndexBuffer(VulkanRenderer gd, CommandBufferScoped cbs) + { + Auto autoBuffer; + int offset, size; + IndexType type = _type; + bool mirrorable = false; + + if (_type == IndexType.Uint8Ext && !gd.Capabilities.SupportsIndexTypeUint8) + { + // Index type is not supported. Convert to I16. + autoBuffer = gd.BufferManager.GetBufferI8ToI16(cbs, _handle, _offset, _size); + + type = IndexType.Uint16; + offset = 0; + size = _size * 2; + } + else + { + autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int bufferSize); + + if (_offset >= bufferSize) + { + autoBuffer = null; + } + + mirrorable = _size < IndexBufferMaxMirrorable; + + offset = _offset; + size = _size; + } + + _buffer = autoBuffer; + + if (autoBuffer != null) + { + DisposableBuffer buffer = mirrorable ? autoBuffer.GetMirrorable(cbs, ref offset, size, out _) : autoBuffer.Get(cbs, offset, size); + + gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, buffer.Value, (ulong)offset, type); + } + } + + public void BindConvertedIndexBuffer( + VulkanRenderer gd, + CommandBufferScoped cbs, + int firstIndex, + int indexCount, + int convertedCount, + IndexBufferPattern pattern) + { + Auto autoBuffer; + + // Convert the index buffer using the given pattern. + int indexSize = GetIndexSize(); + + int firstIndexOffset = firstIndex * indexSize; + + autoBuffer = gd.BufferManager.GetBufferTopologyConversion(cbs, _handle, _offset + firstIndexOffset, indexCount * indexSize, pattern, indexSize); + + int size = convertedCount * 4; + + _buffer = autoBuffer; + + if (autoBuffer != null) + { + gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, autoBuffer.Get(cbs, 0, size).Value, 0, IndexType.Uint32); + } + } + + public Auto BindConvertedIndexBufferIndirect( + VulkanRenderer gd, + CommandBufferScoped cbs, + BufferRange indirectBuffer, + BufferRange drawCountBuffer, + IndexBufferPattern pattern, + bool hasDrawCount, + int maxDrawCount, + int indirectDataStride) + { + // Convert the index buffer using the given pattern. + int indexSize = GetIndexSize(); + + (var indexBufferAuto, var indirectBufferAuto) = gd.BufferManager.GetBufferTopologyConversionIndirect( + gd, + cbs, + new BufferRange(_handle, _offset, _size), + indirectBuffer, + drawCountBuffer, + pattern, + indexSize, + hasDrawCount, + maxDrawCount, + indirectDataStride); + + int convertedCount = pattern.GetConvertedCount(_size / indexSize); + int size = convertedCount * 4; + + _buffer = indexBufferAuto; + + if (indexBufferAuto != null) + { + gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, indexBufferAuto.Get(cbs, 0, size).Value, 0, IndexType.Uint32); + } + + return indirectBufferAuto; + } + + private readonly int GetIndexSize() + { + return _type switch + { + IndexType.Uint32 => 4, + IndexType.Uint16 => 2, + _ => 1, + }; + } + + public readonly bool BoundEquals(Auto buffer) + { + return _buffer == buffer; + } + + public void Swap(Auto from, Auto to) + { + if (_buffer == from) + { + _buffer = to; + } + } + + public readonly bool Overlaps(Auto buffer, int offset, int size) + { + return buffer == _buffer && offset < _offset + _size && offset + size > _offset; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/MemoryAllocation.cs b/src/Ryujinx.Graphics.Vulkan/MemoryAllocation.cs new file mode 100644 index 00000000..3f134e28 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/MemoryAllocation.cs @@ -0,0 +1,59 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct MemoryAllocation : IDisposable + { + private readonly MemoryAllocatorBlockList _owner; + private readonly MemoryAllocatorBlockList.Block _block; + private readonly HostMemoryAllocator _hostMemory; + + public DeviceMemory Memory { get; } + public IntPtr HostPointer { get; } + public ulong Offset { get; } + public ulong Size { get; } + + public MemoryAllocation( + MemoryAllocatorBlockList owner, + MemoryAllocatorBlockList.Block block, + DeviceMemory memory, + IntPtr hostPointer, + ulong offset, + ulong size) + { + _owner = owner; + _block = block; + Memory = memory; + HostPointer = hostPointer; + Offset = offset; + Size = size; + } + + public MemoryAllocation( + HostMemoryAllocator hostMemory, + DeviceMemory memory, + IntPtr hostPointer, + ulong offset, + ulong size) + { + _hostMemory = hostMemory; + Memory = memory; + HostPointer = hostPointer; + Offset = offset; + Size = size; + } + + public void Dispose() + { + if (_hostMemory != null) + { + _hostMemory.Free(Memory, Offset, Size); + } + else + { + _owner.Free(_block, Offset, Size); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/MemoryAllocator.cs b/src/Ryujinx.Graphics.Vulkan/MemoryAllocator.cs new file mode 100644 index 00000000..339754db --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/MemoryAllocator.cs @@ -0,0 +1,120 @@ +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Graphics.Vulkan +{ + class MemoryAllocator : IDisposable + { + private const ulong MaxDeviceMemoryUsageEstimate = 16UL * 1024 * 1024 * 1024; + + private readonly Vk _api; + private readonly VulkanPhysicalDevice _physicalDevice; + private readonly Device _device; + private readonly List _blockLists; + private readonly int _blockAlignment; + private readonly ReaderWriterLockSlim _lock; + + public MemoryAllocator(Vk api, VulkanPhysicalDevice physicalDevice, Device device) + { + _api = api; + _physicalDevice = physicalDevice; + _device = device; + _blockLists = new List(); + _blockAlignment = (int)Math.Min(int.MaxValue, MaxDeviceMemoryUsageEstimate / _physicalDevice.PhysicalDeviceProperties.Limits.MaxMemoryAllocationCount); + _lock = new(LockRecursionPolicy.NoRecursion); + } + + public MemoryAllocation AllocateDeviceMemory( + MemoryRequirements requirements, + MemoryPropertyFlags flags = 0, + bool isBuffer = false) + { + int memoryTypeIndex = FindSuitableMemoryTypeIndex(requirements.MemoryTypeBits, flags); + if (memoryTypeIndex < 0) + { + return default; + } + + bool map = flags.HasFlag(MemoryPropertyFlags.HostVisibleBit); + return Allocate(memoryTypeIndex, requirements.Size, requirements.Alignment, map, isBuffer); + } + + private MemoryAllocation Allocate(int memoryTypeIndex, ulong size, ulong alignment, bool map, bool isBuffer) + { + _lock.EnterReadLock(); + + try + { + for (int i = 0; i < _blockLists.Count; i++) + { + var bl = _blockLists[i]; + if (bl.MemoryTypeIndex == memoryTypeIndex && bl.ForBuffer == isBuffer) + { + return bl.Allocate(size, alignment, map); + } + } + } + finally + { + _lock.ExitReadLock(); + } + + _lock.EnterWriteLock(); + + try + { + var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment, isBuffer); + _blockLists.Add(newBl); + + return newBl.Allocate(size, alignment, map); + } + finally + { + _lock.ExitWriteLock(); + } + } + + internal int FindSuitableMemoryTypeIndex( + uint memoryTypeBits, + MemoryPropertyFlags flags) + { + for (int i = 0; i < _physicalDevice.PhysicalDeviceMemoryProperties.MemoryTypeCount; i++) + { + var type = _physicalDevice.PhysicalDeviceMemoryProperties.MemoryTypes[i]; + + if ((memoryTypeBits & (1 << i)) != 0) + { + if (type.PropertyFlags.HasFlag(flags)) + { + return i; + } + } + } + + return -1; + } + + public static bool IsDeviceMemoryShared(VulkanPhysicalDevice physicalDevice) + { + for (int i = 0; i < physicalDevice.PhysicalDeviceMemoryProperties.MemoryHeapCount; i++) + { + if (!physicalDevice.PhysicalDeviceMemoryProperties.MemoryHeaps[i].Flags.HasFlag(MemoryHeapFlags.DeviceLocalBit)) + { + return false; + } + } + + return true; + } + + public void Dispose() + { + for (int i = 0; i < _blockLists.Count; i++) + { + _blockLists[i].Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs b/src/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs new file mode 100644 index 00000000..3d42ed7e --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs @@ -0,0 +1,310 @@ +using Ryujinx.Common; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; + +namespace Ryujinx.Graphics.Vulkan +{ + class MemoryAllocatorBlockList : IDisposable + { + private const ulong InvalidOffset = ulong.MaxValue; + + public class Block : IComparable + { + public DeviceMemory Memory { get; private set; } + public IntPtr HostPointer { get; private set; } + public ulong Size { get; } + public bool Mapped => HostPointer != IntPtr.Zero; + + private readonly struct Range : IComparable + { + public ulong Offset { get; } + public ulong Size { get; } + + public Range(ulong offset, ulong size) + { + Offset = offset; + Size = size; + } + + public int CompareTo(Range other) + { + return Offset.CompareTo(other.Offset); + } + } + + private readonly List _freeRanges; + + public Block(DeviceMemory memory, IntPtr hostPointer, ulong size) + { + Memory = memory; + HostPointer = hostPointer; + Size = size; + _freeRanges = new List + { + new Range(0, size), + }; + } + + public ulong Allocate(ulong size, ulong alignment) + { + for (int i = 0; i < _freeRanges.Count; i++) + { + var range = _freeRanges[i]; + + ulong alignedOffset = BitUtils.AlignUp(range.Offset, alignment); + ulong sizeDelta = alignedOffset - range.Offset; + ulong usableSize = range.Size - sizeDelta; + + if (sizeDelta < range.Size && usableSize >= size) + { + _freeRanges.RemoveAt(i); + + if (sizeDelta != 0) + { + InsertFreeRange(range.Offset, sizeDelta); + } + + ulong endOffset = range.Offset + range.Size; + ulong remainingSize = endOffset - (alignedOffset + size); + if (remainingSize != 0) + { + InsertFreeRange(endOffset - remainingSize, remainingSize); + } + + return alignedOffset; + } + } + + return InvalidOffset; + } + + public void Free(ulong offset, ulong size) + { + InsertFreeRangeComingled(offset, size); + } + + private void InsertFreeRange(ulong offset, ulong size) + { + var range = new Range(offset, size); + int index = _freeRanges.BinarySearch(range); + if (index < 0) + { + index = ~index; + } + + _freeRanges.Insert(index, range); + } + + private void InsertFreeRangeComingled(ulong offset, ulong size) + { + ulong endOffset = offset + size; + var range = new Range(offset, size); + int index = _freeRanges.BinarySearch(range); + if (index < 0) + { + index = ~index; + } + + if (index < _freeRanges.Count && _freeRanges[index].Offset == endOffset) + { + endOffset = _freeRanges[index].Offset + _freeRanges[index].Size; + _freeRanges.RemoveAt(index); + } + + if (index > 0 && _freeRanges[index - 1].Offset + _freeRanges[index - 1].Size == offset) + { + offset = _freeRanges[index - 1].Offset; + _freeRanges.RemoveAt(--index); + } + + range = new Range(offset, endOffset - offset); + + _freeRanges.Insert(index, range); + } + + public bool IsTotallyFree() + { + if (_freeRanges.Count == 1 && _freeRanges[0].Size == Size) + { + Debug.Assert(_freeRanges[0].Offset == 0); + return true; + } + + return false; + } + + public int CompareTo(Block other) + { + return Size.CompareTo(other.Size); + } + + public unsafe void Destroy(Vk api, Device device) + { + if (Mapped) + { + api.UnmapMemory(device, Memory); + HostPointer = IntPtr.Zero; + } + + if (Memory.Handle != 0) + { + api.FreeMemory(device, Memory, null); + Memory = default; + } + } + } + + private readonly List _blocks; + + private readonly Vk _api; + private readonly Device _device; + + public int MemoryTypeIndex { get; } + public bool ForBuffer { get; } + + private readonly int _blockAlignment; + + private readonly ReaderWriterLockSlim _lock; + + public MemoryAllocatorBlockList(Vk api, Device device, int memoryTypeIndex, int blockAlignment, bool forBuffer) + { + _blocks = new List(); + _api = api; + _device = device; + MemoryTypeIndex = memoryTypeIndex; + ForBuffer = forBuffer; + _blockAlignment = blockAlignment; + _lock = new(LockRecursionPolicy.NoRecursion); + } + + public unsafe MemoryAllocation Allocate(ulong size, ulong alignment, bool map) + { + // Ensure we have a sane alignment value. + if ((ulong)(int)alignment != alignment || (int)alignment <= 0) + { + throw new ArgumentOutOfRangeException(nameof(alignment), $"Invalid alignment 0x{alignment:X}."); + } + + _lock.EnterReadLock(); + + try + { + for (int i = 0; i < _blocks.Count; i++) + { + var block = _blocks[i]; + + if (block.Mapped == map && block.Size >= size) + { + ulong offset = block.Allocate(size, alignment); + if (offset != InvalidOffset) + { + return new MemoryAllocation(this, block, block.Memory, GetHostPointer(block, offset), offset, size); + } + } + } + } + finally + { + _lock.ExitReadLock(); + } + + ulong blockAlignedSize = BitUtils.AlignUp(size, (ulong)_blockAlignment); + + var memoryAllocateInfo = new MemoryAllocateInfo + { + SType = StructureType.MemoryAllocateInfo, + AllocationSize = blockAlignedSize, + MemoryTypeIndex = (uint)MemoryTypeIndex, + }; + + _api.AllocateMemory(_device, in memoryAllocateInfo, null, out var deviceMemory).ThrowOnError(); + + IntPtr hostPointer = IntPtr.Zero; + + if (map) + { + void* pointer = null; + _api.MapMemory(_device, deviceMemory, 0, blockAlignedSize, 0, ref pointer).ThrowOnError(); + hostPointer = (IntPtr)pointer; + } + + var newBlock = new Block(deviceMemory, hostPointer, blockAlignedSize); + + InsertBlock(newBlock); + + ulong newBlockOffset = newBlock.Allocate(size, alignment); + Debug.Assert(newBlockOffset != InvalidOffset); + + return new MemoryAllocation(this, newBlock, deviceMemory, GetHostPointer(newBlock, newBlockOffset), newBlockOffset, size); + } + + private static IntPtr GetHostPointer(Block block, ulong offset) + { + if (block.HostPointer == IntPtr.Zero) + { + return IntPtr.Zero; + } + + return (IntPtr)((nuint)block.HostPointer + offset); + } + + public void Free(Block block, ulong offset, ulong size) + { + block.Free(offset, size); + + if (block.IsTotallyFree()) + { + _lock.EnterWriteLock(); + + try + { + for (int i = 0; i < _blocks.Count; i++) + { + if (_blocks[i] == block) + { + _blocks.RemoveAt(i); + break; + } + } + } + finally + { + _lock.ExitWriteLock(); + } + + block.Destroy(_api, _device); + } + } + + private void InsertBlock(Block block) + { + _lock.EnterWriteLock(); + + try + { + int index = _blocks.BinarySearch(block); + if (index < 0) + { + index = ~index; + } + + _blocks.Insert(index, block); + } + finally + { + _lock.ExitWriteLock(); + } + } + + public void Dispose() + { + for (int i = 0; i < _blocks.Count; i++) + { + _blocks[i].Destroy(_api, _device); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs b/src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs new file mode 100644 index 00000000..bdf606e8 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs @@ -0,0 +1,104 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan.MoltenVK +{ + enum MVKConfigLogLevel + { + None = 0, + Error = 1, + Warning = 2, + Info = 3, + Debug = 4, + } + + enum MVKConfigTraceVulkanCalls + { + None = 0, + Enter = 1, + EnterExit = 2, + Duration = 3, + } + + enum MVKConfigAutoGPUCaptureScope + { + None = 0, + Device = 1, + Frame = 2, + } + + [Flags] + enum MVKConfigAdvertiseExtensions + { + All = 0x00000001, + MoltenVK = 0x00000002, + WSI = 0x00000004, + Portability = 0x00000008, + } + + enum MVKVkSemaphoreSupportStyle + { + MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE = 0, + MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS_WHERE_SAFE = 1, + MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS = 2, + MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_CALLBACK = 3, + MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_MAX_ENUM = 0x7FFFFFFF, + } + + readonly struct Bool32 + { + uint Value { get; } + + public Bool32(uint value) + { + Value = value; + } + + public Bool32(bool value) + { + Value = value ? 1u : 0u; + } + + public static implicit operator bool(Bool32 val) => val.Value == 1; + public static implicit operator Bool32(bool val) => new(val); + } + + [StructLayout(LayoutKind.Sequential)] + struct MVKConfiguration + { + public Bool32 DebugMode; + public Bool32 ShaderConversionFlipVertexY; + public Bool32 SynchronousQueueSubmits; + public Bool32 PrefillMetalCommandBuffers; + public uint MaxActiveMetalCommandBuffersPerQueue; + public Bool32 SupportLargeQueryPools; + public Bool32 PresentWithCommandBuffer; + public Bool32 SwapchainMagFilterUseNearest; + public ulong MetalCompileTimeout; + public Bool32 PerformanceTracking; + public uint PerformanceLoggingFrameCount; + public Bool32 DisplayWatermark; + public Bool32 SpecializedQueueFamilies; + public Bool32 SwitchSystemGPU; + public Bool32 FullImageViewSwizzle; + public uint DefaultGPUCaptureScopeQueueFamilyIndex; + public uint DefaultGPUCaptureScopeQueueIndex; + public Bool32 FastMathEnabled; + public MVKConfigLogLevel LogLevel; + public MVKConfigTraceVulkanCalls TraceVulkanCalls; + public Bool32 ForceLowPowerGPU; + public Bool32 SemaphoreUseMTLFence; + public MVKVkSemaphoreSupportStyle SemaphoreSupportStyle; + public MVKConfigAutoGPUCaptureScope AutoGPUCaptureScope; + public IntPtr AutoGPUCaptureOutputFilepath; + public Bool32 Texture1DAs2D; + public Bool32 PreallocateDescriptors; + public Bool32 UseCommandPooling; + public Bool32 UseMTLHeap; + public Bool32 LogActivityPerformanceInline; + public uint ApiVersionToAdvertise; + public MVKConfigAdvertiseExtensions AdvertiseExtensions; + public Bool32 ResumeLostDevice; + public Bool32 UseMetalArgumentBuffers; + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs b/src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs new file mode 100644 index 00000000..930d6b52 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs @@ -0,0 +1,51 @@ +using Silk.NET.Core.Loader; +using Silk.NET.Vulkan; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Graphics.Vulkan.MoltenVK +{ + [SupportedOSPlatform("macos")] + public static partial class MVKInitialization + { + private const string VulkanLib = "libvulkan.dylib"; + + [LibraryImport("libMoltenVK.dylib")] + private static partial Result vkGetMoltenVKConfigurationMVK(IntPtr unusedInstance, out MVKConfiguration config, in IntPtr configSize); + + [LibraryImport("libMoltenVK.dylib")] + private static partial Result vkSetMoltenVKConfigurationMVK(IntPtr unusedInstance, in MVKConfiguration config, in IntPtr configSize); + + public static void Initialize() + { + var configSize = (IntPtr)Marshal.SizeOf(); + + vkGetMoltenVKConfigurationMVK(IntPtr.Zero, out MVKConfiguration config, configSize); + + config.UseMetalArgumentBuffers = true; + + config.SemaphoreSupportStyle = MVKVkSemaphoreSupportStyle.MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE; + config.SynchronousQueueSubmits = false; + + config.ResumeLostDevice = true; + + vkSetMoltenVKConfigurationMVK(IntPtr.Zero, config, configSize); + } + + private static string[] Resolver(string path) + { + if (path.EndsWith(VulkanLib)) + { + path = path[..^VulkanLib.Length] + "libMoltenVK.dylib"; + return [path]; + } + return Array.Empty(); + } + + public static void InitializeResolver() + { + ((DefaultPathResolver)PathResolver.Default).Resolvers.Insert(0, Resolver); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs new file mode 100644 index 00000000..b4252471 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs @@ -0,0 +1,267 @@ +using Ryujinx.Common.Memory; +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + ///

+ /// Holder for multiple host GPU fences. + /// + class MultiFenceHolder + { + private const int BufferUsageTrackingGranularity = 4096; + + private readonly FenceHolder[] _fences; + private readonly BufferUsageBitmap _bufferUsageBitmap; + + /// + /// Creates a new instance of the multiple fence holder. + /// + public MultiFenceHolder() + { + _fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers]; + } + + /// + /// Creates a new instance of the multiple fence holder, with a given buffer size in mind. + /// + /// Size of the buffer + public MultiFenceHolder(int size) + { + _fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers]; + _bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity); + } + + /// + /// Adds read/write buffer usage information to the uses list. + /// + /// Index of the command buffer where the buffer is used + /// Offset of the buffer being used + /// Size of the buffer region being used, in bytes + /// Whether the access is a write or not + public void AddBufferUse(int cbIndex, int offset, int size, bool write) + { + _bufferUsageBitmap.Add(cbIndex, offset, size, false); + + if (write) + { + _bufferUsageBitmap.Add(cbIndex, offset, size, true); + } + } + + /// + /// Removes all buffer usage information for a given command buffer. + /// + /// Index of the command buffer where the buffer is used + public void RemoveBufferUses(int cbIndex) + { + _bufferUsageBitmap?.Clear(cbIndex); + } + + /// + /// Checks if a given range of a buffer is being used by a command buffer still being processed by the GPU. + /// + /// Index of the command buffer where the buffer is used + /// Offset of the buffer being used + /// Size of the buffer region being used, in bytes + /// True if in use, false otherwise + public bool IsBufferRangeInUse(int cbIndex, int offset, int size) + { + return _bufferUsageBitmap.OverlapsWith(cbIndex, offset, size); + } + + /// + /// Checks if a given range of a buffer is being used by any command buffer still being processed by the GPU. + /// + /// Offset of the buffer being used + /// Size of the buffer region being used, in bytes + /// True if only write usages should count + /// True if in use, false otherwise + public bool IsBufferRangeInUse(int offset, int size, bool write) + { + return _bufferUsageBitmap.OverlapsWith(offset, size, write); + } + + /// + /// Adds a fence to the holder. + /// + /// Command buffer index of the command buffer that owns the fence + /// Fence to be added + /// True if the command buffer's previous fence value was null + public bool AddFence(int cbIndex, FenceHolder fence) + { + ref FenceHolder fenceRef = ref _fences[cbIndex]; + + if (fenceRef == null) + { + fenceRef = fence; + return true; + } + + return false; + } + + /// + /// Removes a fence from the holder. + /// + /// Command buffer index of the command buffer that owns the fence + public void RemoveFence(int cbIndex) + { + _fences[cbIndex] = null; + } + + /// + /// Determines if a fence referenced on the given command buffer. + /// + /// Index of the command buffer to check if it's used + /// True if referenced, false otherwise + public bool HasFence(int cbIndex) + { + return _fences[cbIndex] != null; + } + + /// + /// Wait until all the fences on the holder are signaled. + /// + /// Vulkan API instance + /// GPU device that the fences belongs to + public void WaitForFences(Vk api, Device device) + { + WaitForFencesImpl(api, device, 0, 0, false, 0UL); + } + + /// + /// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled. + /// + /// Vulkan API instance + /// GPU device that the fences belongs to + /// Start offset of the buffer range + /// Size of the buffer range in bytes + public void WaitForFences(Vk api, Device device, int offset, int size) + { + WaitForFencesImpl(api, device, offset, size, false, 0UL); + } + + /// + /// Wait until all the fences on the holder are signaled, or the timeout expires. + /// + /// Vulkan API instance + /// GPU device that the fences belongs to + /// Timeout in nanoseconds + /// True if all fences were signaled, false otherwise + public bool WaitForFences(Vk api, Device device, ulong timeout) + { + return WaitForFencesImpl(api, device, 0, 0, true, timeout); + } + + /// + /// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled. + /// + /// Vulkan API instance + /// GPU device that the fences belongs to + /// Start offset of the buffer range + /// Size of the buffer range in bytes + /// Indicates if should be used + /// Timeout in nanoseconds + /// True if all fences were signaled before the timeout expired, false otherwise + private bool WaitForFencesImpl(Vk api, Device device, int offset, int size, bool hasTimeout, ulong timeout) + { + using SpanOwner fenceHoldersOwner = SpanOwner.Rent(CommandBufferPool.MaxCommandBuffers); + Span fenceHolders = fenceHoldersOwner.Span; + + int count = size != 0 ? GetOverlappingFences(fenceHolders, offset, size) : GetFences(fenceHolders); + Span fences = stackalloc Fence[count]; + + int fenceCount = 0; + + for (int i = 0; i < fences.Length; i++) + { + if (fenceHolders[i].TryGet(out Fence fence)) + { + fences[fenceCount] = fence; + + if (fenceCount < i) + { + fenceHolders[fenceCount] = fenceHolders[i]; + } + + fenceCount++; + } + } + + if (fenceCount == 0) + { + return true; + } + + bool signaled = true; + + try + { + if (hasTimeout) + { + signaled = FenceHelper.AllSignaled(api, device, fences[..fenceCount], timeout); + } + else + { + FenceHelper.WaitAllIndefinitely(api, device, fences[..fenceCount]); + } + } + finally + { + for (int i = 0; i < fenceCount; i++) + { + fenceHolders[i].PutLock(); + } + } + + return signaled; + } + + /// + /// Gets fences to wait for. + /// + /// Span to store fences in + /// Number of fences placed in storage + private int GetFences(Span storage) + { + int count = 0; + + for (int i = 0; i < _fences.Length; i++) + { + var fence = _fences[i]; + + if (fence != null) + { + storage[count++] = fence; + } + } + + return count; + } + + /// + /// Gets fences to wait for use of a given buffer region. + /// + /// Span to store overlapping fences in + /// Offset of the range + /// Size of the range in bytes + /// Number of fences for the specified region placed in storage + private int GetOverlappingFences(Span storage, int offset, int size) + { + int count = 0; + + for (int i = 0; i < _fences.Length; i++) + { + var fence = _fences[i]; + + if (fence != null && _bufferUsageBitmap.OverlapsWith(i, offset, size)) + { + storage[count++] = fence; + } + } + + return count; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/NativeArray.cs b/src/Ryujinx.Graphics.Vulkan/NativeArray.cs new file mode 100644 index 00000000..7678b63c --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/NativeArray.cs @@ -0,0 +1,48 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + unsafe class NativeArray : IDisposable where T : unmanaged + { + public T* Pointer { get; private set; } + public int Length { get; } + + public ref T this[int index] + { + get => ref Pointer[Checked(index)]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int Checked(int index) + { + if ((uint)index >= (uint)Length) + { + throw new IndexOutOfRangeException(); + } + + return index; + } + + public NativeArray(int length) + { + Pointer = (T*)Marshal.AllocHGlobal(checked(length * Unsafe.SizeOf())); + Length = length; + } + + public Span AsSpan() + { + return new Span(Pointer, Length); + } + + public void Dispose() + { + if (Pointer != null) + { + Marshal.FreeHGlobal((IntPtr)Pointer); + Pointer = null; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/PersistentFlushBuffer.cs b/src/Ryujinx.Graphics.Vulkan/PersistentFlushBuffer.cs new file mode 100644 index 00000000..5e0ed077 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/PersistentFlushBuffer.cs @@ -0,0 +1,97 @@ +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + internal class PersistentFlushBuffer : IDisposable + { + private readonly VulkanRenderer _gd; + + private BufferHolder _flushStorage; + + public PersistentFlushBuffer(VulkanRenderer gd) + { + _gd = gd; + } + + private BufferHolder ResizeIfNeeded(int size) + { + var flushStorage = _flushStorage; + + if (flushStorage == null || size > _flushStorage.Size) + { + flushStorage?.Dispose(); + + flushStorage = _gd.BufferManager.Create(_gd, size); + _flushStorage = flushStorage; + } + + return flushStorage; + } + + public Span GetBufferData(CommandBufferPool cbp, BufferHolder buffer, int offset, int size) + { + var flushStorage = ResizeIfNeeded(size); + Auto srcBuffer; + + using (var cbs = cbp.Rent()) + { + srcBuffer = buffer.GetBuffer(cbs.CommandBuffer); + var dstBuffer = flushStorage.GetBuffer(cbs.CommandBuffer); + + if (srcBuffer.TryIncrementReferenceCount()) + { + BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, offset, 0, size, registerSrcUsage: false); + } + else + { + // Source buffer is no longer alive, don't copy anything to flush storage. + srcBuffer = null; + } + } + + flushStorage.WaitForFences(); + srcBuffer?.DecrementReferenceCount(); + return flushStorage.GetDataStorage(0, size); + } + + public Span GetTextureData(CommandBufferPool cbp, TextureView view, int size) + { + TextureCreateInfo info = view.Info; + + var flushStorage = ResizeIfNeeded(size); + + using (var cbs = cbp.Rent()) + { + var buffer = flushStorage.GetBuffer(cbs.CommandBuffer).Get(cbs).Value; + var image = view.GetImage().Get(cbs).Value; + + view.CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, 0, 0, info.GetLayers(), info.Levels, singleSlice: false); + } + + flushStorage.WaitForFences(); + return flushStorage.GetDataStorage(0, size); + } + + public Span GetTextureData(CommandBufferPool cbp, TextureView view, int size, int layer, int level) + { + var flushStorage = ResizeIfNeeded(size); + + using (var cbs = cbp.Rent()) + { + var buffer = flushStorage.GetBuffer(cbs.CommandBuffer).Get(cbs).Value; + var image = view.GetImage().Get(cbs).Value; + + view.CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, layer, level, 1, 1, singleSlice: true); + } + + flushStorage.WaitForFences(); + return flushStorage.GetDataStorage(0, size); + } + + public void Dispose() + { + _flushStorage.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs new file mode 100644 index 00000000..20c4b257 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -0,0 +1,1810 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using CompareOp = Ryujinx.Graphics.GAL.CompareOp; +using Format = Ryujinx.Graphics.GAL.Format; +using FrontFace = Ryujinx.Graphics.GAL.FrontFace; +using IndexType = Ryujinx.Graphics.GAL.IndexType; +using PolygonMode = Ryujinx.Graphics.GAL.PolygonMode; +using PrimitiveTopology = Ryujinx.Graphics.GAL.PrimitiveTopology; +using Viewport = Ryujinx.Graphics.GAL.Viewport; + +namespace Ryujinx.Graphics.Vulkan +{ + class PipelineBase : IDisposable + { + public const int DescriptorSetLayouts = 4; + + public const int UniformSetIndex = 0; + public const int StorageSetIndex = 1; + public const int TextureSetIndex = 2; + public const int ImageSetIndex = 3; + + protected readonly VulkanRenderer Gd; + protected readonly Device Device; + public readonly PipelineCache PipelineCache; + + public readonly AutoFlushCounter AutoFlush; + public readonly Action EndRenderPassDelegate; + + protected PipelineDynamicState DynamicState; + protected bool IsMainPipeline; + private PipelineState _newState; + private bool _graphicsStateDirty; + private bool _computeStateDirty; + private bool _bindingBarriersDirty; + private PrimitiveTopology _topology; + + private ulong _currentPipelineHandle; + + protected Auto Pipeline; + + protected PipelineBindPoint Pbp; + + protected CommandBufferScoped Cbs; + protected CommandBufferScoped? PreloadCbs; + protected CommandBuffer CommandBuffer; + + public CommandBufferScoped CurrentCommandBuffer => Cbs; + + private ShaderCollection _program; + + protected FramebufferParams FramebufferParams; + private Auto _framebuffer; + private RenderPassHolder _rpHolder; + private Auto _renderPass; + private RenderPassHolder _nullRenderPass; + private int _writtenAttachmentCount; + + private bool _framebufferUsingColorWriteMask; + + private ITexture[] _preMaskColors; + private ITexture _preMaskDepthStencil; + + private readonly DescriptorSetUpdater _descriptorSetUpdater; + + private IndexBufferState _indexBuffer; + private IndexBufferPattern _indexBufferPattern; + private readonly BufferState[] _transformFeedbackBuffers; + private readonly VertexBufferState[] _vertexBuffers; + private ulong _vertexBuffersDirty; + protected Rectangle ClearScissor; + + private readonly VertexBufferUpdater _vertexBufferUpdater; + + public IndexBufferPattern QuadsToTrisPattern; + public IndexBufferPattern TriFanToTrisPattern; + + private bool _needsIndexBufferRebind; + private bool _needsTransformFeedbackBuffersRebind; + + private bool _tfEnabled; + private bool _tfActive; + + private FeedbackLoopAspects _feedbackLoop; + private bool _passWritesDepthStencil; + + private readonly PipelineColorBlendAttachmentState[] _storedBlend; + public ulong DrawCount { get; private set; } + public bool RenderPassActive { get; private set; } + + public unsafe PipelineBase(VulkanRenderer gd, Device device) + { + Gd = gd; + Device = device; + + AutoFlush = new AutoFlushCounter(gd); + EndRenderPassDelegate = EndRenderPass; + + var pipelineCacheCreateInfo = new PipelineCacheCreateInfo + { + SType = StructureType.PipelineCacheCreateInfo, + }; + + gd.Api.CreatePipelineCache(device, in pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError(); + + _descriptorSetUpdater = new DescriptorSetUpdater(gd, device); + _vertexBufferUpdater = new VertexBufferUpdater(gd); + + _transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers]; + _vertexBuffers = new VertexBufferState[Constants.MaxVertexBuffers + 1]; + + const int EmptyVbSize = 16; + + using var emptyVb = gd.BufferManager.Create(gd, EmptyVbSize); + emptyVb.SetData(0, new byte[EmptyVbSize]); + _vertexBuffers[0] = new VertexBufferState(emptyVb.GetBuffer(), 0, 0, EmptyVbSize); + _vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length); + + ClearScissor = new Rectangle(0, 0, 0xffff, 0xffff); + + _storedBlend = new PipelineColorBlendAttachmentState[Constants.MaxRenderTargets]; + + _newState.Initialize(); + } + + public void Initialize() + { + _descriptorSetUpdater.Initialize(IsMainPipeline); + + QuadsToTrisPattern = new IndexBufferPattern(Gd, 4, 6, 0, new[] { 0, 1, 2, 0, 2, 3 }, 4, false); + TriFanToTrisPattern = new IndexBufferPattern(Gd, 3, 3, 2, new[] { int.MinValue, -1, 0 }, 1, true); + } + + public unsafe void Barrier() + { + Gd.Barriers.QueueMemoryBarrier(); + } + + public void ComputeBarrier() + { + MemoryBarrier memoryBarrier = new() + { + SType = StructureType.MemoryBarrier, + SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit, + DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit, + }; + + Gd.Api.CmdPipelineBarrier( + CommandBuffer, + PipelineStageFlags.ComputeShaderBit, + PipelineStageFlags.AllCommandsBit, + 0, + 1, + new ReadOnlySpan(in memoryBarrier), + 0, + ReadOnlySpan.Empty, + 0, + ReadOnlySpan.Empty); + } + + public void BeginTransformFeedback(PrimitiveTopology topology) + { + Gd.Barriers.EnableTfbBarriers(true); + _tfEnabled = true; + } + + public void ClearBuffer(BufferHandle destination, int offset, int size, uint value) + { + EndRenderPass(); + + var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, offset, size, true).Get(Cbs, offset, size, true).Value; + + BufferHolder.InsertBufferBarrier( + Gd, + Cbs.CommandBuffer, + dst, + BufferHolder.DefaultAccessFlags, + AccessFlags.TransferWriteBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.TransferBit, + offset, + size); + + Gd.Api.CmdFillBuffer(CommandBuffer, dst, (ulong)offset, (ulong)size, value); + + BufferHolder.InsertBufferBarrier( + Gd, + Cbs.CommandBuffer, + dst, + AccessFlags.TransferWriteBit, + BufferHolder.DefaultAccessFlags, + PipelineStageFlags.TransferBit, + PipelineStageFlags.AllCommandsBit, + offset, + size); + } + + public unsafe void ClearRenderTargetColor(int index, int layer, int layerCount, ColorF color) + { + if (FramebufferParams == null || !FramebufferParams.IsValidColorAttachment(index)) + { + return; + } + + if (_renderPass == null) + { + CreateRenderPass(); + } + + Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate); + + BeginRenderPass(); + + var clearValue = new ClearValue(new ClearColorValue(color.Red, color.Green, color.Blue, color.Alpha)); + var attachment = new ClearAttachment(ImageAspectFlags.ColorBit, (uint)index, clearValue); + var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount); + + Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect); + } + + public unsafe void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, bool stencilMask) + { + if (FramebufferParams == null || !FramebufferParams.HasDepthStencil) + { + return; + } + + var clearValue = new ClearValue(null, new ClearDepthStencilValue(depthValue, (uint)stencilValue)); + var flags = depthMask ? ImageAspectFlags.DepthBit : 0; + + if (stencilMask) + { + flags |= ImageAspectFlags.StencilBit; + } + + flags &= FramebufferParams.GetDepthStencilAspectFlags(); + + if (flags == ImageAspectFlags.None) + { + return; + } + + if (_renderPass == null) + { + CreateRenderPass(); + } + + Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate); + + BeginRenderPass(); + + var attachment = new ClearAttachment(flags, 0, clearValue); + var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount); + + Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect); + } + + public unsafe void CommandBufferBarrier() + { + Gd.Barriers.QueueCommandBufferBarrier(); + } + + public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size) + { + EndRenderPass(); + + var src = Gd.BufferManager.GetBuffer(CommandBuffer, source, srcOffset, size, false); + var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, dstOffset, size, true); + + BufferHolder.Copy(Gd, Cbs, src, dst, srcOffset, dstOffset, size); + } + + public void DirtyVertexBuffer(Auto buffer) + { + for (int i = 0; i < _vertexBuffers.Length; i++) + { + if (_vertexBuffers[i].BoundEquals(buffer)) + { + _vertexBuffersDirty |= 1UL << i; + } + } + } + + public void DirtyIndexBuffer(Auto buffer) + { + if (_indexBuffer.BoundEquals(buffer)) + { + _needsIndexBufferRebind = true; + } + } + + public void DispatchCompute(int groupsX, int groupsY, int groupsZ) + { + if (!_program.IsLinked) + { + return; + } + + EndRenderPass(); + RecreateComputePipelineIfNeeded(); + + Gd.Api.CmdDispatch(CommandBuffer, (uint)groupsX, (uint)groupsY, (uint)groupsZ); + } + + public void DispatchComputeIndirect(Auto indirectBuffer, int indirectBufferOffset) + { + if (!_program.IsLinked) + { + return; + } + + EndRenderPass(); + RecreateComputePipelineIfNeeded(); + + Gd.Api.CmdDispatchIndirect(CommandBuffer, indirectBuffer.Get(Cbs, indirectBufferOffset, 12).Value, (ulong)indirectBufferOffset); + } + + public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance) + { + if (vertexCount == 0) + { + return; + } + + if (!RecreateGraphicsPipelineIfNeeded()) + { + return; + } + + BeginRenderPass(); + DrawCount++; + + if (Gd.TopologyUnsupported(_topology)) + { + // Temporarily bind a conversion pattern as an index buffer. + _needsIndexBufferRebind = true; + + IndexBufferPattern pattern = _topology switch + { + PrimitiveTopology.Quads => QuadsToTrisPattern, + PrimitiveTopology.TriangleFan or + PrimitiveTopology.Polygon => TriFanToTrisPattern, + _ => throw new NotSupportedException($"Unsupported topology: {_topology}"), + }; + + BufferHandle handle = pattern.GetRepeatingBuffer(vertexCount, out int indexCount); + var buffer = Gd.BufferManager.GetBuffer(CommandBuffer, handle, false); + + Gd.Api.CmdBindIndexBuffer(CommandBuffer, buffer.Get(Cbs, 0, indexCount * sizeof(int)).Value, 0, Silk.NET.Vulkan.IndexType.Uint32); + + BeginRenderPass(); // May have been interrupted to set buffer data. + ResumeTransformFeedbackInternal(); + + Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)indexCount, (uint)instanceCount, 0, firstVertex, (uint)firstInstance); + } + else + { + ResumeTransformFeedbackInternal(); + + Gd.Api.CmdDraw(CommandBuffer, (uint)vertexCount, (uint)instanceCount, (uint)firstVertex, (uint)firstInstance); + } + } + + private void UpdateIndexBufferPattern() + { + IndexBufferPattern pattern = null; + + if (Gd.TopologyUnsupported(_topology)) + { + pattern = _topology switch + { + PrimitiveTopology.Quads => QuadsToTrisPattern, + PrimitiveTopology.TriangleFan or + PrimitiveTopology.Polygon => TriFanToTrisPattern, + _ => throw new NotSupportedException($"Unsupported topology: {_topology}"), + }; + } + + if (_indexBufferPattern != pattern) + { + _indexBufferPattern = pattern; + _needsIndexBufferRebind = true; + } + } + + public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance) + { + if (indexCount == 0) + { + return; + } + + UpdateIndexBufferPattern(); + + if (!RecreateGraphicsPipelineIfNeeded()) + { + return; + } + + BeginRenderPass(); + DrawCount++; + + if (_indexBufferPattern != null) + { + // Convert the index buffer into a supported topology. + IndexBufferPattern pattern = _indexBufferPattern; + + int convertedCount = pattern.GetConvertedCount(indexCount); + + if (_needsIndexBufferRebind) + { + _indexBuffer.BindConvertedIndexBuffer(Gd, Cbs, firstIndex, indexCount, convertedCount, pattern); + + _needsIndexBufferRebind = false; + } + + BeginRenderPass(); // May have been interrupted to set buffer data. + ResumeTransformFeedbackInternal(); + + Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)convertedCount, (uint)instanceCount, 0, firstVertex, (uint)firstInstance); + } + else + { + ResumeTransformFeedbackInternal(); + + Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)indexCount, (uint)instanceCount, (uint)firstIndex, firstVertex, (uint)firstInstance); + } + } + + public void DrawIndexedIndirect(BufferRange indirectBuffer) + { + var buffer = Gd.BufferManager + .GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false) + .Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value; + + UpdateIndexBufferPattern(); + + if (!RecreateGraphicsPipelineIfNeeded()) + { + return; + } + + BeginRenderPass(); + DrawCount++; + + if (_indexBufferPattern != null) + { + // Convert the index buffer into a supported topology. + IndexBufferPattern pattern = _indexBufferPattern; + + Auto indirectBufferAuto = _indexBuffer.BindConvertedIndexBufferIndirect( + Gd, + Cbs, + indirectBuffer, + BufferRange.Empty, + pattern, + false, + 1, + indirectBuffer.Size); + + _needsIndexBufferRebind = false; + + BeginRenderPass(); // May have been interrupted to set buffer data. + ResumeTransformFeedbackInternal(); + + Gd.Api.CmdDrawIndexedIndirect(CommandBuffer, indirectBufferAuto.Get(Cbs, 0, indirectBuffer.Size).Value, 0, 1, (uint)indirectBuffer.Size); + } + else + { + ResumeTransformFeedbackInternal(); + + Gd.Api.CmdDrawIndexedIndirect(CommandBuffer, buffer, (ulong)indirectBuffer.Offset, 1, (uint)indirectBuffer.Size); + } + } + + public void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + var countBuffer = Gd.BufferManager + .GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false) + .Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value; + + var buffer = Gd.BufferManager + .GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false) + .Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value; + + UpdateIndexBufferPattern(); + + if (!RecreateGraphicsPipelineIfNeeded()) + { + return; + } + + BeginRenderPass(); + DrawCount++; + + if (_indexBufferPattern != null) + { + // Convert the index buffer into a supported topology. + IndexBufferPattern pattern = _indexBufferPattern; + + Auto indirectBufferAuto = _indexBuffer.BindConvertedIndexBufferIndirect( + Gd, + Cbs, + indirectBuffer, + parameterBuffer, + pattern, + true, + maxDrawCount, + stride); + + _needsIndexBufferRebind = false; + + BeginRenderPass(); // May have been interrupted to set buffer data. + ResumeTransformFeedbackInternal(); + + if (Gd.Capabilities.SupportsIndirectParameters) + { + Gd.DrawIndirectCountApi.CmdDrawIndexedIndirectCount( + CommandBuffer, + indirectBufferAuto.Get(Cbs, 0, indirectBuffer.Size).Value, + 0, + countBuffer, + (ulong)parameterBuffer.Offset, + (uint)maxDrawCount, + (uint)stride); + } + else + { + // This is also fine because the indirect data conversion always zeros + // the entries that are past the current draw count. + + Gd.Api.CmdDrawIndexedIndirect( + CommandBuffer, + indirectBufferAuto.Get(Cbs, 0, indirectBuffer.Size).Value, + 0, + (uint)maxDrawCount, + (uint)stride); + } + } + else + { + ResumeTransformFeedbackInternal(); + + if (Gd.Capabilities.SupportsIndirectParameters) + { + Gd.DrawIndirectCountApi.CmdDrawIndexedIndirectCount( + CommandBuffer, + buffer, + (ulong)indirectBuffer.Offset, + countBuffer, + (ulong)parameterBuffer.Offset, + (uint)maxDrawCount, + (uint)stride); + } + else + { + // Not fully correct, but we can't do much better if the host does not support indirect count. + Gd.Api.CmdDrawIndexedIndirect( + CommandBuffer, + buffer, + (ulong)indirectBuffer.Offset, + (uint)maxDrawCount, + (uint)stride); + } + } + } + + public void DrawIndirect(BufferRange indirectBuffer) + { + // TODO: Support quads and other unsupported topologies. + + var buffer = Gd.BufferManager + .GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false) + .Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size, false).Value; + + if (!RecreateGraphicsPipelineIfNeeded()) + { + return; + } + + BeginRenderPass(); + ResumeTransformFeedbackInternal(); + DrawCount++; + + Gd.Api.CmdDrawIndirect(CommandBuffer, buffer, (ulong)indirectBuffer.Offset, 1, (uint)indirectBuffer.Size); + } + + public void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) + { + if (!Gd.Capabilities.SupportsIndirectParameters) + { + // TODO: Fallback for when this is not supported. + throw new NotSupportedException(); + } + + var buffer = Gd.BufferManager + .GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false) + .Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size, false).Value; + + var countBuffer = Gd.BufferManager + .GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false) + .Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size, false).Value; + + // TODO: Support quads and other unsupported topologies. + + if (!RecreateGraphicsPipelineIfNeeded()) + { + return; + } + + BeginRenderPass(); + ResumeTransformFeedbackInternal(); + DrawCount++; + + Gd.DrawIndirectCountApi.CmdDrawIndirectCount( + CommandBuffer, + buffer, + (ulong)indirectBuffer.Offset, + countBuffer, + (ulong)parameterBuffer.Offset, + (uint)maxDrawCount, + (uint)stride); + } + + public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion) + { + if (texture is TextureView srcTexture) + { + var oldCullMode = _newState.CullMode; + var oldStencilTestEnable = _newState.StencilTestEnable; + var oldDepthTestEnable = _newState.DepthTestEnable; + var oldDepthWriteEnable = _newState.DepthWriteEnable; + var oldTopology = _newState.Topology; + var oldViewports = DynamicState.Viewports; + var oldViewportsCount = _newState.ViewportsCount; + + _newState.CullMode = CullModeFlags.None; + _newState.StencilTestEnable = false; + _newState.DepthTestEnable = false; + _newState.DepthWriteEnable = false; + SignalStateChange(); + + Gd.HelperShader.DrawTexture( + Gd, + this, + srcTexture, + sampler, + srcRegion, + dstRegion); + + _newState.CullMode = oldCullMode; + _newState.StencilTestEnable = oldStencilTestEnable; + _newState.DepthTestEnable = oldDepthTestEnable; + _newState.DepthWriteEnable = oldDepthWriteEnable; + _newState.Topology = oldTopology; + + DynamicState.SetViewports(ref oldViewports, oldViewportsCount); + + _newState.ViewportsCount = oldViewportsCount; + SignalStateChange(); + } + } + + public void EndTransformFeedback() + { + Gd.Barriers.EnableTfbBarriers(false); + PauseTransformFeedbackInternal(); + _tfEnabled = false; + } + + public bool IsCommandBufferActive(CommandBuffer cb) + { + return CommandBuffer.Handle == cb.Handle; + } + + internal void Rebind(Auto buffer, int offset, int size) + { + _descriptorSetUpdater.Rebind(buffer, offset, size); + + if (_indexBuffer.Overlaps(buffer, offset, size)) + { + _indexBuffer.BindIndexBuffer(Gd, Cbs); + } + + for (int i = 0; i < _vertexBuffers.Length; i++) + { + if (_vertexBuffers[i].Overlaps(buffer, offset, size)) + { + _vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState, _vertexBufferUpdater); + } + } + + _vertexBufferUpdater.Commit(Cbs); + } + + public void SetAlphaTest(bool enable, float reference, CompareOp op) + { + // This is currently handled using shader specialization, as Vulkan does not support alpha test. + // In the future, we may want to use this to write the reference value into the support buffer, + // to avoid creating one version of the shader per reference value used. + } + + public void SetBlendState(AdvancedBlendDescriptor blend) + { + for (int index = 0; index < Constants.MaxRenderTargets; index++) + { + ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[index]; + + if (index == 0) + { + var blendOp = blend.Op.Convert(); + + vkBlend = new PipelineColorBlendAttachmentState( + blendEnable: true, + colorBlendOp: blendOp, + alphaBlendOp: blendOp, + colorWriteMask: vkBlend.ColorWriteMask); + + if (Gd.Capabilities.SupportsBlendEquationAdvancedNonPreMultipliedSrcColor) + { + _newState.AdvancedBlendSrcPreMultiplied = blend.SrcPreMultiplied; + } + + if (Gd.Capabilities.SupportsBlendEquationAdvancedCorrelatedOverlap) + { + _newState.AdvancedBlendOverlap = blend.Overlap.Convert(); + } + } + else + { + vkBlend = new PipelineColorBlendAttachmentState( + colorWriteMask: vkBlend.ColorWriteMask); + } + + if (vkBlend.ColorWriteMask == 0) + { + _storedBlend[index] = vkBlend; + + vkBlend = new PipelineColorBlendAttachmentState(); + } + } + + SignalStateChange(); + } + + public void SetBlendState(int index, BlendDescriptor blend) + { + ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[index]; + + if (blend.Enable) + { + vkBlend.BlendEnable = blend.Enable; + vkBlend.SrcColorBlendFactor = blend.ColorSrcFactor.Convert(); + vkBlend.DstColorBlendFactor = blend.ColorDstFactor.Convert(); + vkBlend.ColorBlendOp = blend.ColorOp.Convert(); + vkBlend.SrcAlphaBlendFactor = blend.AlphaSrcFactor.Convert(); + vkBlend.DstAlphaBlendFactor = blend.AlphaDstFactor.Convert(); + vkBlend.AlphaBlendOp = blend.AlphaOp.Convert(); + } + else + { + vkBlend = new PipelineColorBlendAttachmentState( + colorWriteMask: vkBlend.ColorWriteMask); + } + + if (vkBlend.ColorWriteMask == 0) + { + _storedBlend[index] = vkBlend; + + vkBlend = new PipelineColorBlendAttachmentState(); + } + + DynamicState.SetBlendConstants( + blend.BlendConstant.Red, + blend.BlendConstant.Green, + blend.BlendConstant.Blue, + blend.BlendConstant.Alpha); + + // Reset advanced blend state back defaults to the cache to help the pipeline cache. + _newState.AdvancedBlendSrcPreMultiplied = true; + _newState.AdvancedBlendDstPreMultiplied = true; + _newState.AdvancedBlendOverlap = BlendOverlapEXT.UncorrelatedExt; + + SignalStateChange(); + } + + public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp) + { + DynamicState.SetDepthBias(factor, units, clamp); + + _newState.DepthBiasEnable = enables != 0; + SignalStateChange(); + } + + public void SetDepthClamp(bool clamp) + { + _newState.DepthClampEnable = clamp; + SignalStateChange(); + } + + public void SetDepthMode(DepthMode mode) + { + bool oldMode = _newState.DepthMode; + _newState.DepthMode = mode == DepthMode.MinusOneToOne; + if (_newState.DepthMode != oldMode) + { + SignalStateChange(); + } + } + + public void SetDepthTest(DepthTestDescriptor depthTest) + { + _newState.DepthTestEnable = depthTest.TestEnable; + _newState.DepthWriteEnable = depthTest.WriteEnable; + _newState.DepthCompareOp = depthTest.Func.Convert(); + + UpdatePassDepthStencil(); + SignalStateChange(); + } + + public void SetFaceCulling(bool enable, Face face) + { + _newState.CullMode = enable ? face.Convert() : CullModeFlags.None; + SignalStateChange(); + } + + public void SetFrontFace(FrontFace frontFace) + { + _newState.FrontFace = frontFace.Convert(); + SignalStateChange(); + } + + public void SetImage(ShaderStage stage, int binding, ITexture image, Format imageFormat) + { + _descriptorSetUpdater.SetImage(Cbs, stage, binding, image, imageFormat); + } + + public void SetImage(int binding, Auto image) + { + _descriptorSetUpdater.SetImage(binding, image); + } + + public void SetImageArray(ShaderStage stage, int binding, IImageArray array) + { + _descriptorSetUpdater.SetImageArray(Cbs, stage, binding, array); + } + + public void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array) + { + _descriptorSetUpdater.SetImageArraySeparate(Cbs, stage, setIndex, array); + } + + public void SetIndexBuffer(BufferRange buffer, IndexType type) + { + if (buffer.Handle != BufferHandle.Null) + { + _indexBuffer = new IndexBufferState(buffer.Handle, buffer.Offset, buffer.Size, type.Convert()); + } + else + { + _indexBuffer = IndexBufferState.Null; + } + + _needsIndexBufferRebind = true; + } + + public void SetLineParameters(float width, bool smooth) + { + _newState.LineWidth = width; + SignalStateChange(); + } + + public void SetLogicOpState(bool enable, LogicalOp op) + { + _newState.LogicOpEnable = enable; + _newState.LogicOp = op.Convert(); + SignalStateChange(); + } + + public void SetMultisampleState(MultisampleDescriptor multisample) + { + _newState.AlphaToCoverageEnable = multisample.AlphaToCoverageEnable; + _newState.AlphaToOneEnable = multisample.AlphaToOneEnable; + SignalStateChange(); + } + + public void SetPatchParameters(int vertices, ReadOnlySpan defaultOuterLevel, ReadOnlySpan defaultInnerLevel) + { + _newState.PatchControlPoints = (uint)vertices; + SignalStateChange(); + + // TODO: Default levels (likely needs emulation on shaders?) + } + + public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin) + { + // TODO. + } + + public void SetPolygonMode(PolygonMode frontMode, PolygonMode backMode) + { + // TODO. + } + + public void SetPrimitiveRestart(bool enable, int index) + { + _newState.PrimitiveRestartEnable = enable; + // TODO: What to do about the index? + SignalStateChange(); + } + + public void SetPrimitiveTopology(PrimitiveTopology topology) + { + _topology = topology; + + var vkTopology = Gd.TopologyRemap(topology).Convert(); + + _newState.Topology = vkTopology; + + SignalStateChange(); + } + + public void SetProgram(IProgram program) + { + var internalProgram = (ShaderCollection)program; + var stages = internalProgram.GetInfos(); + + _program = internalProgram; + + _descriptorSetUpdater.SetProgram(Cbs, internalProgram, _currentPipelineHandle != 0); + _bindingBarriersDirty = true; + + _newState.PipelineLayout = internalProgram.PipelineLayout; + _newState.HasTessellationControlShader = internalProgram.HasTessellationControlShader; + _newState.StagesCount = (uint)stages.Length; + + stages.CopyTo(_newState.Stages.AsSpan()[..stages.Length]); + + SignalStateChange(); + + if (internalProgram.IsCompute) + { + EndRenderPass(); + } + } + + public void Specialize(in T data) where T : unmanaged + { + var dataSpan = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in data), 1)); + + if (!dataSpan.SequenceEqual(_newState.SpecializationData.Span)) + { + _newState.SpecializationData = new SpecData(dataSpan); + + SignalStateChange(); + } + } + + protected virtual void SignalAttachmentChange() + { + } + + public void SetRasterizerDiscard(bool discard) + { + _newState.RasterizerDiscardEnable = discard; + SignalStateChange(); + + if (!discard && Gd.IsQualcommProprietary) + { + // On Adreno, enabling rasterizer discard somehow corrupts the viewport state. + // Force it to be updated on next use to work around this bug. + DynamicState.ForceAllDirty(); + } + } + + public void SetRenderTargetColorMasks(ReadOnlySpan componentMask) + { + int count = Math.Min(Constants.MaxRenderTargets, componentMask.Length); + int writtenAttachments = 0; + + for (int i = 0; i < count; i++) + { + ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[i]; + var newMask = (ColorComponentFlags)componentMask[i]; + + // When color write mask is 0, remove all blend state to help the pipeline cache. + // Restore it when the mask becomes non-zero. + if (vkBlend.ColorWriteMask != newMask) + { + if (newMask == 0) + { + _storedBlend[i] = vkBlend; + + vkBlend = new PipelineColorBlendAttachmentState(); + } + else if (vkBlend.ColorWriteMask == 0) + { + vkBlend = _storedBlend[i]; + } + } + + vkBlend.ColorWriteMask = newMask; + + if (componentMask[i] != 0) + { + writtenAttachments++; + } + } + + if (_framebufferUsingColorWriteMask) + { + SetRenderTargetsInternal(_preMaskColors, _preMaskDepthStencil, true); + } + else + { + SignalStateChange(); + + if (writtenAttachments != _writtenAttachmentCount) + { + SignalAttachmentChange(); + _writtenAttachmentCount = writtenAttachments; + } + } + } + + private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked) + { + CreateFramebuffer(colors, depthStencil, filterWriteMasked); + CreateRenderPass(); + SignalStateChange(); + SignalAttachmentChange(); + } + + public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) + { + _framebufferUsingColorWriteMask = false; + SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR); + } + + public void SetScissors(ReadOnlySpan> regions) + { + int maxScissors = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1; + int count = Math.Min(maxScissors, regions.Length); + if (count > 0) + { + ClearScissor = regions[0]; + } + + for (int i = 0; i < count; i++) + { + var region = regions[i]; + var offset = new Offset2D(region.X, region.Y); + var extent = new Extent2D((uint)region.Width, (uint)region.Height); + + DynamicState.SetScissor(i, new Rect2D(offset, extent)); + } + + DynamicState.ScissorsCount = count; + + _newState.ScissorsCount = (uint)count; + SignalStateChange(); + } + + public void SetStencilTest(StencilTestDescriptor stencilTest) + { + DynamicState.SetStencilMasks( + (uint)stencilTest.BackFuncMask, + (uint)stencilTest.BackMask, + (uint)stencilTest.BackFuncRef, + (uint)stencilTest.FrontFuncMask, + (uint)stencilTest.FrontMask, + (uint)stencilTest.FrontFuncRef); + + _newState.StencilTestEnable = stencilTest.TestEnable; + _newState.StencilBackFailOp = stencilTest.BackSFail.Convert(); + _newState.StencilBackPassOp = stencilTest.BackDpPass.Convert(); + _newState.StencilBackDepthFailOp = stencilTest.BackDpFail.Convert(); + _newState.StencilBackCompareOp = stencilTest.BackFunc.Convert(); + _newState.StencilFrontFailOp = stencilTest.FrontSFail.Convert(); + _newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert(); + _newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert(); + _newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert(); + + UpdatePassDepthStencil(); + SignalStateChange(); + } + + public void SetStorageBuffers(ReadOnlySpan buffers) + { + _descriptorSetUpdater.SetStorageBuffers(CommandBuffer, buffers); + } + + public void SetStorageBuffers(int first, ReadOnlySpan> buffers) + { + _descriptorSetUpdater.SetStorageBuffers(CommandBuffer, first, buffers); + } + + public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler) + { + _descriptorSetUpdater.SetTextureAndSampler(Cbs, stage, binding, texture, sampler); + } + + public void SetTextureAndSamplerIdentitySwizzle(ShaderStage stage, int binding, ITexture texture, ISampler sampler) + { + _descriptorSetUpdater.SetTextureAndSamplerIdentitySwizzle(Cbs, stage, binding, texture, sampler); + } + + public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array) + { + _descriptorSetUpdater.SetTextureArray(Cbs, stage, binding, array); + } + + public void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array) + { + _descriptorSetUpdater.SetTextureArraySeparate(Cbs, stage, setIndex, array); + } + + public void SetTransformFeedbackBuffers(ReadOnlySpan buffers) + { + PauseTransformFeedbackInternal(); + + int count = Math.Min(Constants.MaxTransformFeedbackBuffers, buffers.Length); + + for (int i = 0; i < count; i++) + { + var range = buffers[i]; + + _transformFeedbackBuffers[i].Dispose(); + + if (range.Handle != BufferHandle.Null) + { + _transformFeedbackBuffers[i] = + new BufferState(Gd.BufferManager.GetBuffer(CommandBuffer, range.Handle, range.Offset, range.Size, true), range.Offset, range.Size); + _transformFeedbackBuffers[i].BindTransformFeedbackBuffer(Gd, Cbs, (uint)i); + } + else + { + _transformFeedbackBuffers[i] = BufferState.Null; + } + } + } + + public void SetUniformBuffers(ReadOnlySpan buffers) + { + _descriptorSetUpdater.SetUniformBuffers(CommandBuffer, buffers); + } + + public void SetUserClipDistance(int index, bool enableClip) + { + // TODO. + } + + public void SetVertexAttribs(ReadOnlySpan vertexAttribs) + { + var formatCapabilities = Gd.FormatCapabilities; + + Span newVbScalarSizes = stackalloc int[Constants.MaxVertexBuffers]; + + int count = Math.Min(Constants.MaxVertexAttributes, vertexAttribs.Length); + uint dirtyVbSizes = 0; + + for (int i = 0; i < count; i++) + { + var attribute = vertexAttribs[i]; + var rawIndex = attribute.BufferIndex; + var bufferIndex = attribute.IsZero ? 0 : rawIndex + 1; + + if (!attribute.IsZero) + { + newVbScalarSizes[rawIndex] = Math.Max(newVbScalarSizes[rawIndex], attribute.Format.GetScalarSize()); + dirtyVbSizes |= 1u << rawIndex; + } + + _newState.Internal.VertexAttributeDescriptions[i] = new VertexInputAttributeDescription( + (uint)i, + (uint)bufferIndex, + formatCapabilities.ConvertToVertexVkFormat(attribute.Format), + (uint)attribute.Offset); + } + + while (dirtyVbSizes != 0) + { + int dirtyBit = BitOperations.TrailingZeroCount(dirtyVbSizes); + + ref var buffer = ref _vertexBuffers[dirtyBit + 1]; + + if (buffer.AttributeScalarAlignment != newVbScalarSizes[dirtyBit]) + { + _vertexBuffersDirty |= 1UL << (dirtyBit + 1); + buffer.AttributeScalarAlignment = newVbScalarSizes[dirtyBit]; + } + + dirtyVbSizes &= ~(1u << dirtyBit); + } + + _newState.VertexAttributeDescriptionsCount = (uint)count; + SignalStateChange(); + } + + public void SetVertexBuffers(ReadOnlySpan vertexBuffers) + { + int count = Math.Min(Constants.MaxVertexBuffers, vertexBuffers.Length); + + _newState.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex); + + int validCount = 1; + + BufferHandle lastHandle = default; + Auto lastBuffer = default; + + for (int i = 0; i < count; i++) + { + var vertexBuffer = vertexBuffers[i]; + + // TODO: Support divisor > 1 + var inputRate = vertexBuffer.Divisor != 0 ? VertexInputRate.Instance : VertexInputRate.Vertex; + + if (vertexBuffer.Buffer.Handle != BufferHandle.Null) + { + Auto vb = (vertexBuffer.Buffer.Handle == lastHandle) ? lastBuffer : + Gd.BufferManager.GetBuffer(CommandBuffer, vertexBuffer.Buffer.Handle, false); + + lastHandle = vertexBuffer.Buffer.Handle; + lastBuffer = vb; + + if (vb != null) + { + int binding = i + 1; + int descriptorIndex = validCount++; + + _newState.Internal.VertexBindingDescriptions[descriptorIndex] = new VertexInputBindingDescription( + (uint)binding, + (uint)vertexBuffer.Stride, + inputRate); + + int vbSize = vertexBuffer.Buffer.Size; + + if (Gd.Vendor == Vendor.Amd && !Gd.IsMoltenVk && vertexBuffer.Stride > 0) + { + // AMD has a bug where if offset + stride * count is greater than + // the size, then the last attribute will have the wrong value. + // As a workaround, simply use the full buffer size. + int remainder = vbSize % vertexBuffer.Stride; + if (remainder != 0) + { + vbSize += vertexBuffer.Stride - remainder; + } + } + + ref var buffer = ref _vertexBuffers[binding]; + int oldScalarAlign = buffer.AttributeScalarAlignment; + + if (Gd.Capabilities.VertexBufferAlignment < 2 && + (vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0) + { + if (!buffer.Matches(vb, descriptorIndex, vertexBuffer.Buffer.Offset, vbSize, vertexBuffer.Stride)) + { + buffer.Dispose(); + + buffer = new VertexBufferState( + vb, + descriptorIndex, + vertexBuffer.Buffer.Offset, + vbSize, + vertexBuffer.Stride); + + buffer.BindVertexBuffer(Gd, Cbs, (uint)binding, ref _newState, _vertexBufferUpdater); + } + } + else + { + // May need to be rewritten. Bind this buffer before draw. + + buffer.Dispose(); + + buffer = new VertexBufferState( + vertexBuffer.Buffer.Handle, + descriptorIndex, + vertexBuffer.Buffer.Offset, + vbSize, + vertexBuffer.Stride); + + _vertexBuffersDirty |= 1UL << binding; + } + + buffer.AttributeScalarAlignment = oldScalarAlign; + } + } + } + + _vertexBufferUpdater.Commit(Cbs); + + _newState.VertexBindingDescriptionsCount = (uint)validCount; + SignalStateChange(); + } + + public void SetViewports(ReadOnlySpan viewports) + { + int maxViewports = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1; + int count = Math.Min(maxViewports, viewports.Length); + + static float Clamp(float value) + { + return Math.Clamp(value, 0f, 1f); + } + + DynamicState.ViewportsCount = (uint)count; + + for (int i = 0; i < count; i++) + { + var viewport = viewports[i]; + + DynamicState.SetViewport(i, new Silk.NET.Vulkan.Viewport( + viewport.Region.X, + viewport.Region.Y, + viewport.Region.Width == 0f ? 1f : viewport.Region.Width, + viewport.Region.Height == 0f ? 1f : viewport.Region.Height, + Clamp(viewport.DepthNear), + Clamp(viewport.DepthFar))); + } + + _newState.ViewportsCount = (uint)count; + SignalStateChange(); + } + + public void SwapBuffer(Auto from, Auto to) + { + _indexBuffer.Swap(from, to); + + for (int i = 0; i < _vertexBuffers.Length; i++) + { + _vertexBuffers[i].Swap(from, to); + } + + for (int i = 0; i < _transformFeedbackBuffers.Length; i++) + { + _transformFeedbackBuffers[i].Swap(from, to); + } + + _descriptorSetUpdater.SwapBuffer(from, to); + + SignalCommandBufferChange(); + } + + public void ForceTextureDirty() + { + _descriptorSetUpdater.ForceTextureDirty(); + } + + public void ForceImageDirty() + { + _descriptorSetUpdater.ForceImageDirty(); + } + + public unsafe void TextureBarrier() + { + Gd.Barriers.QueueTextureBarrier(); + } + + public void TextureBarrierTiled() + { + TextureBarrier(); + } + + protected void SignalCommandBufferChange() + { + _needsIndexBufferRebind = true; + _needsTransformFeedbackBuffersRebind = true; + _vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length); + + _descriptorSetUpdater.SignalCommandBufferChange(); + DynamicState.ForceAllDirty(); + _currentPipelineHandle = 0; + } + + private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked) + { + if (filterWriteMasked) + { + // TBDR GPUs don't work properly if the same attachment is bound to multiple targets, + // due to each attachment being a copy of the real attachment, rather than a direct write. + + // Just try to remove duplicate attachments. + // Save a copy of the array to rebind when mask changes. + + void MaskOut() + { + if (!_framebufferUsingColorWriteMask) + { + _preMaskColors = colors.ToArray(); + _preMaskDepthStencil = depthStencil; + } + + // If true, then the framebuffer must be recreated when the mask changes. + _framebufferUsingColorWriteMask = true; + } + + // Look for textures that are masked out. + + for (int i = 0; i < colors.Length; i++) + { + if (colors[i] == null) + { + continue; + } + + ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[i]; + + for (int j = 0; j < i; j++) + { + // Check each binding for a duplicate binding before it. + + if (colors[i] == colors[j]) + { + // Prefer the binding with no write mask. + ref var vkBlend2 = ref _newState.Internal.ColorBlendAttachmentState[j]; + if (vkBlend.ColorWriteMask == 0) + { + colors[i] = null; + MaskOut(); + } + else if (vkBlend2.ColorWriteMask == 0) + { + colors[j] = null; + MaskOut(); + } + } + } + } + } + + if (IsMainPipeline) + { + FramebufferParams?.ClearBindings(); + } + + FramebufferParams = new FramebufferParams(Device, colors, depthStencil); + + if (IsMainPipeline) + { + FramebufferParams.AddBindings(); + + _newState.FeedbackLoopAspects = FeedbackLoopAspects.None; + _bindingBarriersDirty = true; + } + + _passWritesDepthStencil = false; + UpdatePassDepthStencil(); + UpdatePipelineAttachmentFormats(); + } + + protected void UpdatePipelineAttachmentFormats() + { + var dstAttachmentFormats = _newState.Internal.AttachmentFormats.AsSpan(); + FramebufferParams.AttachmentFormats.CopyTo(dstAttachmentFormats); + _newState.Internal.AttachmentIntegerFormatMask = FramebufferParams.AttachmentIntegerFormatMask; + _newState.Internal.LogicOpsAllowed = FramebufferParams.LogicOpsAllowed; + + for (int i = FramebufferParams.AttachmentFormats.Length; i < dstAttachmentFormats.Length; i++) + { + dstAttachmentFormats[i] = 0; + } + + _newState.ColorBlendAttachmentStateCount = (uint)(FramebufferParams.MaxColorAttachmentIndex + 1); + _newState.HasDepthStencil = FramebufferParams.HasDepthStencil; + _newState.SamplesCount = FramebufferParams.AttachmentSamples.Length != 0 ? FramebufferParams.AttachmentSamples[0] : 1; + } + + protected unsafe void CreateRenderPass() + { + var hasFramebuffer = FramebufferParams != null; + + EndRenderPass(); + + if (!hasFramebuffer || FramebufferParams.AttachmentsCount == 0) + { + // Use the null framebuffer. + _nullRenderPass ??= new RenderPassHolder(Gd, Device, new RenderPassCacheKey(), FramebufferParams); + + _rpHolder = _nullRenderPass; + _renderPass = _nullRenderPass.GetRenderPass(); + _framebuffer = _nullRenderPass.GetFramebuffer(Gd, Cbs, FramebufferParams); + } + else + { + (_rpHolder, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs); + + _renderPass = _rpHolder.GetRenderPass(); + } + } + + protected void SignalStateChange() + { + _graphicsStateDirty = true; + _computeStateDirty = true; + } + + private void RecreateComputePipelineIfNeeded() + { + if (_computeStateDirty || Pbp != PipelineBindPoint.Compute) + { + CreatePipeline(PipelineBindPoint.Compute); + _computeStateDirty = false; + Pbp = PipelineBindPoint.Compute; + + if (_bindingBarriersDirty) + { + // Stale barriers may have been activated by switching program. Emit any that are relevant. + _descriptorSetUpdater.InsertBindingBarriers(Cbs); + + _bindingBarriersDirty = false; + } + } + + Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate); + + _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute); + } + + private bool ChangeFeedbackLoop(FeedbackLoopAspects aspects) + { + if (_feedbackLoop != aspects) + { + if (Gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop) + { + DynamicState.SetFeedbackLoop(aspects); + } + else + { + _newState.FeedbackLoopAspects = aspects; + } + + _feedbackLoop = aspects; + + return true; + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool UpdateFeedbackLoop() + { + List hazards = _descriptorSetUpdater.FeedbackLoopHazards; + + if ((hazards?.Count ?? 0) > 0) + { + FeedbackLoopAspects aspects = 0; + + foreach (TextureView view in hazards) + { + // May need to enforce feedback loop layout here in the future. + // Though technically, it should always work with the general layout. + + if (view.Info.Format.IsDepthOrStencil()) + { + if (_passWritesDepthStencil) + { + // If depth/stencil isn't written in the pass, it doesn't count as a feedback loop. + + aspects |= FeedbackLoopAspects.Depth; + } + } + else + { + aspects |= FeedbackLoopAspects.Color; + } + } + + return ChangeFeedbackLoop(aspects); + } + else if (_feedbackLoop != 0) + { + return ChangeFeedbackLoop(FeedbackLoopAspects.None); + } + + return false; + } + + private void UpdatePassDepthStencil() + { + if (!RenderPassActive) + { + _passWritesDepthStencil = false; + } + + // Stencil test being enabled doesn't necessarily mean a write, but it's not critical to check. + _passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable; + } + + private bool RecreateGraphicsPipelineIfNeeded() + { + if (AutoFlush.ShouldFlushDraw(DrawCount)) + { + Gd.FlushAllCommands(); + } + + DynamicState.ReplayIfDirty(Gd, CommandBuffer); + + if (_needsIndexBufferRebind && _indexBufferPattern == null) + { + _indexBuffer.BindIndexBuffer(Gd, Cbs); + _needsIndexBufferRebind = false; + } + + if (_needsTransformFeedbackBuffersRebind) + { + PauseTransformFeedbackInternal(); + + for (int i = 0; i < Constants.MaxTransformFeedbackBuffers; i++) + { + _transformFeedbackBuffers[i].BindTransformFeedbackBuffer(Gd, Cbs, (uint)i); + } + + _needsTransformFeedbackBuffersRebind = false; + } + + if (_vertexBuffersDirty != 0) + { + while (_vertexBuffersDirty != 0) + { + int i = BitOperations.TrailingZeroCount(_vertexBuffersDirty); + + _vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState, _vertexBufferUpdater); + + _vertexBuffersDirty &= ~(1UL << i); + } + + _vertexBufferUpdater.Commit(Cbs); + } + + if (_bindingBarriersDirty) + { + // Stale barriers may have been activated by switching program. Emit any that are relevant. + _descriptorSetUpdater.InsertBindingBarriers(Cbs); + + _bindingBarriersDirty = false; + } + + if (UpdateFeedbackLoop() || _graphicsStateDirty || Pbp != PipelineBindPoint.Graphics) + { + if (!CreatePipeline(PipelineBindPoint.Graphics)) + { + return false; + } + + _graphicsStateDirty = false; + Pbp = PipelineBindPoint.Graphics; + } + + Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate); + + _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics); + + return true; + } + + private bool CreatePipeline(PipelineBindPoint pbp) + { + // We can only create a pipeline if the have the shader stages set. + if (_newState.Stages != null) + { + if (pbp == PipelineBindPoint.Graphics && _renderPass == null) + { + CreateRenderPass(); + } + + if (!_program.IsLinked) + { + // Background compile failed, we likely can't create the pipeline because the shader is broken + // or the driver failed to compile it. + + return false; + } + + var pipeline = pbp == PipelineBindPoint.Compute + ? _newState.CreateComputePipeline(Gd, Device, _program, PipelineCache) + : _newState.CreateGraphicsPipeline(Gd, Device, _program, PipelineCache, _renderPass.Get(Cbs).Value); + + if (pipeline == null) + { + // Host failed to create the pipeline, likely due to driver bugs. + + return false; + } + + ulong pipelineHandle = pipeline.GetUnsafe().Value.Handle; + + if (_currentPipelineHandle != pipelineHandle) + { + _currentPipelineHandle = pipelineHandle; + Pipeline = pipeline; + + PauseTransformFeedbackInternal(); + Gd.Api.CmdBindPipeline(CommandBuffer, pbp, Pipeline.Get(Cbs).Value); + } + } + + return true; + } + + private unsafe void BeginRenderPass() + { + if (!RenderPassActive) + { + FramebufferParams.InsertLoadOpBarriers(Gd, Cbs); + + var renderArea = new Rect2D(null, new Extent2D(FramebufferParams.Width, FramebufferParams.Height)); + var clearValue = new ClearValue(); + + var renderPassBeginInfo = new RenderPassBeginInfo + { + SType = StructureType.RenderPassBeginInfo, + RenderPass = _renderPass.Get(Cbs).Value, + Framebuffer = _framebuffer.Get(Cbs).Value, + RenderArea = renderArea, + PClearValues = &clearValue, + ClearValueCount = 1, + }; + + Gd.Api.CmdBeginRenderPass(CommandBuffer, in renderPassBeginInfo, SubpassContents.Inline); + RenderPassActive = true; + } + } + + public void EndRenderPass() + { + if (RenderPassActive) + { + FramebufferParams.AddStoreOpUsage(); + + PauseTransformFeedbackInternal(); + Gd.Api.CmdEndRenderPass(CommandBuffer); + SignalRenderPassEnd(); + RenderPassActive = false; + } + } + + protected virtual void SignalRenderPassEnd() + { + } + + private void PauseTransformFeedbackInternal() + { + if (_tfEnabled && _tfActive) + { + EndTransformFeedbackInternal(); + _tfActive = false; + } + } + + private void ResumeTransformFeedbackInternal() + { + if (_tfEnabled && !_tfActive) + { + BeginTransformFeedbackInternal(); + _tfActive = true; + } + } + + private unsafe void BeginTransformFeedbackInternal() + { + Gd.TransformFeedbackApi.CmdBeginTransformFeedback(CommandBuffer, 0, 0, null, null); + } + + private unsafe void EndTransformFeedbackInternal() + { + Gd.TransformFeedbackApi.CmdEndTransformFeedback(CommandBuffer, 0, 0, null, null); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _nullRenderPass?.Dispose(); + _newState.Dispose(); + _descriptorSetUpdater.Dispose(); + _vertexBufferUpdater.Dispose(); + + for (int i = 0; i < _vertexBuffers.Length; i++) + { + _vertexBuffers[i].Dispose(); + } + + for (int i = 0; i < _transformFeedbackBuffers.Length; i++) + { + _transformFeedbackBuffers[i].Dispose(); + } + + Pipeline?.Dispose(); + + unsafe + { + Gd.Api.DestroyPipelineCache(Device, PipelineCache, null); + } + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs new file mode 100644 index 00000000..85069c6b --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs @@ -0,0 +1,324 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using Format = Silk.NET.Vulkan.Format; +using PolygonMode = Silk.NET.Vulkan.PolygonMode; + +namespace Ryujinx.Graphics.Vulkan +{ + static class PipelineConverter + { + public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device) + { + const int MaxAttachments = Constants.MaxRenderTargets + 1; + + AttachmentDescription[] attachmentDescs = null; + + var subpass = new SubpassDescription + { + PipelineBindPoint = PipelineBindPoint.Graphics, + }; + + AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments]; + + Span attachmentIndices = stackalloc int[MaxAttachments]; + Span attachmentFormats = stackalloc Format[MaxAttachments]; + + int attachmentCount = 0; + int colorCount = 0; + int maxColorAttachmentIndex = -1; + + for (int i = 0; i < state.AttachmentEnable.Length; i++) + { + if (state.AttachmentEnable[i]) + { + attachmentFormats[attachmentCount] = gd.FormatCapabilities.ConvertToVkFormat(state.AttachmentFormats[i]); + + attachmentIndices[attachmentCount++] = i; + colorCount++; + maxColorAttachmentIndex = i; + } + } + + if (state.DepthStencilEnable) + { + attachmentFormats[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(state.DepthStencilFormat); + } + + if (attachmentCount != 0) + { + attachmentDescs = new AttachmentDescription[attachmentCount]; + + for (int i = 0; i < attachmentCount; i++) + { + int bindIndex = attachmentIndices[i]; + + attachmentDescs[i] = new AttachmentDescription( + 0, + attachmentFormats[i], + TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)state.SamplesCount), + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + ImageLayout.General, + ImageLayout.General); + } + + int colorAttachmentsCount = colorCount; + + if (colorAttachmentsCount > MaxAttachments - 1) + { + colorAttachmentsCount = MaxAttachments - 1; + } + + if (colorAttachmentsCount != 0) + { + subpass.ColorAttachmentCount = (uint)maxColorAttachmentIndex + 1; + subpass.PColorAttachments = &attachmentReferences[0]; + + // Fill with VK_ATTACHMENT_UNUSED to cover any gaps. + for (int i = 0; i <= maxColorAttachmentIndex; i++) + { + subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined); + } + + for (int i = 0; i < colorAttachmentsCount; i++) + { + int bindIndex = attachmentIndices[i]; + + subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General); + } + } + + if (state.DepthStencilEnable) + { + uint dsIndex = (uint)attachmentCount - 1; + + subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1]; + *subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General); + } + } + + var subpassDependency = CreateSubpassDependency(gd); + + fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs) + { + var renderPassCreateInfo = new RenderPassCreateInfo + { + SType = StructureType.RenderPassCreateInfo, + PAttachments = pAttachmentDescs, + AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0, + PSubpasses = &subpass, + SubpassCount = 1, + PDependencies = &subpassDependency, + DependencyCount = 1, + }; + + gd.Api.CreateRenderPass(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError(); + + return new DisposableRenderPass(gd.Api, device, renderPass); + } + } + + public static SubpassDependency CreateSubpassDependency(VulkanRenderer gd) + { + var (access, stages) = BarrierBatch.GetSubpassAccessSuperset(gd); + + return new SubpassDependency( + 0, + 0, + stages, + stages, + access, + access, + 0); + } + + public unsafe static SubpassDependency2 CreateSubpassDependency2(VulkanRenderer gd) + { + var (access, stages) = BarrierBatch.GetSubpassAccessSuperset(gd); + + return new SubpassDependency2( + StructureType.SubpassDependency2, + null, + 0, + 0, + stages, + stages, + access, + access, + 0); + } + + public static PipelineState ToVulkanPipelineState(this ProgramPipelineState state, VulkanRenderer gd) + { + PipelineState pipeline = new(); + pipeline.Initialize(); + + // It is assumed that Dynamic State is enabled when this conversion is used. + + pipeline.CullMode = state.CullEnable ? state.CullMode.Convert() : CullModeFlags.None; + + pipeline.DepthBoundsTestEnable = false; // Not implemented. + + pipeline.DepthClampEnable = state.DepthClampEnable; + + pipeline.DepthTestEnable = state.DepthTest.TestEnable; + pipeline.DepthWriteEnable = state.DepthTest.WriteEnable; + pipeline.DepthCompareOp = state.DepthTest.Func.Convert(); + pipeline.DepthMode = state.DepthMode == DepthMode.MinusOneToOne; + + pipeline.FrontFace = state.FrontFace.Convert(); + + pipeline.HasDepthStencil = state.DepthStencilEnable; + pipeline.LineWidth = state.LineWidth; + pipeline.LogicOpEnable = state.LogicOpEnable; + pipeline.LogicOp = state.LogicOp.Convert(); + + pipeline.PatchControlPoints = state.PatchControlPoints; + pipeline.PolygonMode = PolygonMode.Fill; // Not implemented. + pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable; + pipeline.RasterizerDiscardEnable = state.RasterizerDiscard; + pipeline.SamplesCount = (uint)state.SamplesCount; + + if (gd.Capabilities.SupportsMultiView) + { + pipeline.ScissorsCount = Constants.MaxViewports; + pipeline.ViewportsCount = Constants.MaxViewports; + } + else + { + pipeline.ScissorsCount = 1; + pipeline.ViewportsCount = 1; + } + + pipeline.DepthBiasEnable = state.BiasEnable != 0; + + // Stencil masks and ref are dynamic, so are 0 in the Vulkan pipeline. + + pipeline.StencilFrontFailOp = state.StencilTest.FrontSFail.Convert(); + pipeline.StencilFrontPassOp = state.StencilTest.FrontDpPass.Convert(); + pipeline.StencilFrontDepthFailOp = state.StencilTest.FrontDpFail.Convert(); + pipeline.StencilFrontCompareOp = state.StencilTest.FrontFunc.Convert(); + + pipeline.StencilBackFailOp = state.StencilTest.BackSFail.Convert(); + pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert(); + pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert(); + pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert(); + + pipeline.StencilTestEnable = state.StencilTest.TestEnable; + + pipeline.Topology = gd.TopologyRemap(state.Topology).Convert(); + + int vaCount = Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount); + int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount); + + Span vbScalarSizes = stackalloc int[vbCount]; + + for (int i = 0; i < vaCount; i++) + { + var attribute = state.VertexAttribs[i]; + var bufferIndex = attribute.IsZero ? 0 : attribute.BufferIndex + 1; + + pipeline.Internal.VertexAttributeDescriptions[i] = new VertexInputAttributeDescription( + (uint)i, + (uint)bufferIndex, + gd.FormatCapabilities.ConvertToVertexVkFormat(attribute.Format), + (uint)attribute.Offset); + + if (!attribute.IsZero && bufferIndex < vbCount) + { + vbScalarSizes[bufferIndex - 1] = Math.Max(attribute.Format.GetScalarSize(), vbScalarSizes[bufferIndex - 1]); + } + } + + int descriptorIndex = 1; + pipeline.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex); + + for (int i = 0; i < vbCount; i++) + { + var vertexBuffer = state.VertexBuffers[i]; + + if (vertexBuffer.Enable) + { + var inputRate = vertexBuffer.Divisor != 0 ? VertexInputRate.Instance : VertexInputRate.Vertex; + + int alignedStride = vertexBuffer.Stride; + + if (gd.NeedsVertexBufferAlignment(vbScalarSizes[i], out int alignment)) + { + alignedStride = BitUtils.AlignUp(vertexBuffer.Stride, alignment); + } + + // TODO: Support divisor > 1 + pipeline.Internal.VertexBindingDescriptions[descriptorIndex++] = new VertexInputBindingDescription( + (uint)i + 1, + (uint)alignedStride, + inputRate); + } + } + + pipeline.VertexBindingDescriptionsCount = (uint)descriptorIndex; + + // NOTE: Viewports, Scissors are dynamic. + + for (int i = 0; i < Constants.MaxRenderTargets; i++) + { + var blend = state.BlendDescriptors[i]; + + if (blend.Enable && state.ColorWriteMask[i] != 0) + { + pipeline.Internal.ColorBlendAttachmentState[i] = new PipelineColorBlendAttachmentState( + blend.Enable, + blend.ColorSrcFactor.Convert(), + blend.ColorDstFactor.Convert(), + blend.ColorOp.Convert(), + blend.AlphaSrcFactor.Convert(), + blend.AlphaDstFactor.Convert(), + blend.AlphaOp.Convert(), + (ColorComponentFlags)state.ColorWriteMask[i]); + } + else + { + pipeline.Internal.ColorBlendAttachmentState[i] = new PipelineColorBlendAttachmentState( + colorWriteMask: (ColorComponentFlags)state.ColorWriteMask[i]); + } + } + + int attachmentCount = 0; + int maxColorAttachmentIndex = -1; + uint attachmentIntegerFormatMask = 0; + bool allFormatsFloatOrSrgb = true; + + for (int i = 0; i < Constants.MaxRenderTargets; i++) + { + if (state.AttachmentEnable[i]) + { + pipeline.Internal.AttachmentFormats[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(state.AttachmentFormats[i]); + maxColorAttachmentIndex = i; + + if (state.AttachmentFormats[i].IsInteger()) + { + attachmentIntegerFormatMask |= 1u << i; + } + + allFormatsFloatOrSrgb &= state.AttachmentFormats[i].IsFloatOrSrgb(); + } + } + + if (state.DepthStencilEnable) + { + pipeline.Internal.AttachmentFormats[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(state.DepthStencilFormat); + } + + pipeline.ColorBlendAttachmentStateCount = (uint)(maxColorAttachmentIndex + 1); + pipeline.VertexAttributeDescriptionsCount = (uint)Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount); + pipeline.Internal.AttachmentIntegerFormatMask = attachmentIntegerFormatMask; + pipeline.Internal.LogicOpsAllowed = attachmentCount == 0 || !allFormatsFloatOrSrgb; + + return pipeline; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs new file mode 100644 index 00000000..ad26ff7b --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs @@ -0,0 +1,203 @@ +using Ryujinx.Common.Memory; +using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.EXT; + +namespace Ryujinx.Graphics.Vulkan +{ + struct PipelineDynamicState + { + private float _depthBiasSlopeFactor; + private float _depthBiasConstantFactor; + private float _depthBiasClamp; + + public int ScissorsCount; + private Array16 _scissors; + + private uint _backCompareMask; + private uint _backWriteMask; + private uint _backReference; + private uint _frontCompareMask; + private uint _frontWriteMask; + private uint _frontReference; + + private Array4 _blendConstants; + + private FeedbackLoopAspects _feedbackLoopAspects; + + public uint ViewportsCount; + public Array16 Viewports; + + private enum DirtyFlags + { + None = 0, + Blend = 1 << 0, + DepthBias = 1 << 1, + Scissor = 1 << 2, + Stencil = 1 << 3, + Viewport = 1 << 4, + FeedbackLoop = 1 << 5, + All = Blend | DepthBias | Scissor | Stencil | Viewport | FeedbackLoop, + } + + private DirtyFlags _dirty; + + public void SetBlendConstants(float r, float g, float b, float a) + { + _blendConstants[0] = r; + _blendConstants[1] = g; + _blendConstants[2] = b; + _blendConstants[3] = a; + + _dirty |= DirtyFlags.Blend; + } + + public void SetDepthBias(float slopeFactor, float constantFactor, float clamp) + { + _depthBiasSlopeFactor = slopeFactor; + _depthBiasConstantFactor = constantFactor; + _depthBiasClamp = clamp; + + _dirty |= DirtyFlags.DepthBias; + } + + public void SetScissor(int index, Rect2D scissor) + { + _scissors[index] = scissor; + + _dirty |= DirtyFlags.Scissor; + } + + public void SetStencilMasks( + uint backCompareMask, + uint backWriteMask, + uint backReference, + uint frontCompareMask, + uint frontWriteMask, + uint frontReference) + { + _backCompareMask = backCompareMask; + _backWriteMask = backWriteMask; + _backReference = backReference; + _frontCompareMask = frontCompareMask; + _frontWriteMask = frontWriteMask; + _frontReference = frontReference; + + _dirty |= DirtyFlags.Stencil; + } + + public void SetViewport(int index, Viewport viewport) + { + Viewports[index] = viewport; + + _dirty |= DirtyFlags.Viewport; + } + + public void SetViewports(ref Array16 viewports, uint viewportsCount) + { + Viewports = viewports; + ViewportsCount = viewportsCount; + + if (ViewportsCount != 0) + { + _dirty |= DirtyFlags.Viewport; + } + } + + public void SetFeedbackLoop(FeedbackLoopAspects aspects) + { + _feedbackLoopAspects = aspects; + + _dirty |= DirtyFlags.FeedbackLoop; + } + + public void ForceAllDirty() + { + _dirty = DirtyFlags.All; + } + + public void ReplayIfDirty(VulkanRenderer gd, CommandBuffer commandBuffer) + { + Vk api = gd.Api; + + if (_dirty.HasFlag(DirtyFlags.Blend)) + { + RecordBlend(api, commandBuffer); + } + + if (_dirty.HasFlag(DirtyFlags.DepthBias)) + { + RecordDepthBias(api, commandBuffer); + } + + if (_dirty.HasFlag(DirtyFlags.Scissor)) + { + RecordScissor(api, commandBuffer); + } + + if (_dirty.HasFlag(DirtyFlags.Stencil)) + { + RecordStencilMasks(api, commandBuffer); + } + + if (_dirty.HasFlag(DirtyFlags.Viewport)) + { + RecordViewport(api, commandBuffer); + } + + if (_dirty.HasFlag(DirtyFlags.FeedbackLoop) && gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop) + { + RecordFeedbackLoop(gd.DynamicFeedbackLoopApi, commandBuffer); + } + + _dirty = DirtyFlags.None; + } + + private void RecordBlend(Vk api, CommandBuffer commandBuffer) + { + api.CmdSetBlendConstants(commandBuffer, _blendConstants.AsSpan()); + } + + private readonly void RecordDepthBias(Vk api, CommandBuffer commandBuffer) + { + api.CmdSetDepthBias(commandBuffer, _depthBiasConstantFactor, _depthBiasClamp, _depthBiasSlopeFactor); + } + + private void RecordScissor(Vk api, CommandBuffer commandBuffer) + { + if (ScissorsCount != 0) + { + api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.AsSpan()); + } + } + + private readonly void RecordStencilMasks(Vk api, CommandBuffer commandBuffer) + { + api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backCompareMask); + api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backWriteMask); + api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.FaceBackBit, _backReference); + api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontCompareMask); + api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontWriteMask); + api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontReference); + } + + private void RecordViewport(Vk api, CommandBuffer commandBuffer) + { + if (ViewportsCount != 0) + { + api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan()); + } + } + + private readonly void RecordFeedbackLoop(ExtAttachmentFeedbackLoopDynamicState api, CommandBuffer commandBuffer) + { + ImageAspectFlags aspects = (_feedbackLoopAspects & FeedbackLoopAspects.Color) != 0 ? ImageAspectFlags.ColorBit : 0; + + if ((_feedbackLoopAspects & FeedbackLoopAspects.Depth) != 0) + { + aspects |= ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit; + } + + api.CmdSetAttachmentFeedbackLoopEnable(commandBuffer, aspects); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs new file mode 100644 index 00000000..54d43bdb --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs @@ -0,0 +1,351 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Vulkan.Queries; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + class PipelineFull : PipelineBase, IPipeline + { + private const ulong MinByteWeightForFlush = 256 * 1024 * 1024; // MiB + + private readonly List<(QueryPool, bool)> _activeQueries; + private CounterQueueEvent _activeConditionalRender; + + private readonly List _pendingQueryCopies; + private readonly List _activeBufferMirrors; + + private ulong _byteWeight; + + private readonly List _backingSwaps; + + public PipelineFull(VulkanRenderer gd, Device device) : base(gd, device) + { + _activeQueries = new List<(QueryPool, bool)>(); + _pendingQueryCopies = new(); + _backingSwaps = new(); + _activeBufferMirrors = new(); + + CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer; + + IsMainPipeline = true; + } + + private void CopyPendingQuery() + { + foreach (var query in _pendingQueryCopies) + { + query.PoolCopy(Cbs); + } + + _pendingQueryCopies.Clear(); + } + + public void ClearRenderTargetColor(int index, int layer, int layerCount, uint componentMask, ColorF color) + { + if (FramebufferParams == null) + { + return; + } + + if (componentMask != 0xf || Gd.IsQualcommProprietary) + { + // We can't use CmdClearAttachments if not writing all components, + // because on Vulkan, the pipeline state does not affect clears. + // On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all. + var dstTexture = FramebufferParams.GetColorView(index); + if (dstTexture == null) + { + return; + } + + Span clearColor = stackalloc float[4]; + clearColor[0] = color.Red; + clearColor[1] = color.Green; + clearColor[2] = color.Blue; + clearColor[3] = color.Alpha; + + // TODO: Clear only the specified layer. + Gd.HelperShader.Clear( + Gd, + dstTexture, + clearColor, + componentMask, + (int)FramebufferParams.Width, + (int)FramebufferParams.Height, + FramebufferParams.GetAttachmentComponentType(index), + ClearScissor); + } + else + { + ClearRenderTargetColor(index, layer, layerCount, color); + } + } + + public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask) + { + if (FramebufferParams == null) + { + return; + } + + if ((stencilMask != 0 && stencilMask != 0xff) || Gd.IsQualcommProprietary) + { + // We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits, + // because on Vulkan, the pipeline state does not affect clears. + // On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all. + var dstTexture = FramebufferParams.GetDepthStencilView(); + if (dstTexture == null) + { + return; + } + + // TODO: Clear only the specified layer. + Gd.HelperShader.Clear( + Gd, + dstTexture, + depthValue, + depthMask, + stencilValue, + stencilMask, + (int)FramebufferParams.Width, + (int)FramebufferParams.Height, + FramebufferParams.AttachmentFormats[FramebufferParams.AttachmentsCount - 1], + ClearScissor); + } + else + { + ClearRenderTargetDepthStencil(layer, layerCount, depthValue, depthMask, stencilValue, stencilMask != 0); + } + } + + public void EndHostConditionalRendering() + { + if (Gd.Capabilities.SupportsConditionalRendering) + { + // Gd.ConditionalRenderingApi.CmdEndConditionalRendering(CommandBuffer); + } + else + { + // throw new NotSupportedException(); + } + + _activeConditionalRender?.ReleaseHostAccess(); + _activeConditionalRender = null; + } + + public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual) + { + // Compare an event and a constant value. + if (value is CounterQueueEvent evt) + { + // Easy host conditional rendering when the check matches what GL can do: + // - Event is of type samples passed. + // - Result is not a combination of multiple queries. + // - Comparing against 0. + // - Event has not already been flushed. + + if (compare == 0 && evt.Type == CounterType.SamplesPassed && evt.ClearCounter) + { + if (!value.ReserveForHostAccess()) + { + // If the event has been flushed, then just use the values on the CPU. + // The query object may already be repurposed for another draw (eg. begin + end). + return false; + } + + if (Gd.Capabilities.SupportsConditionalRendering) + { + // var buffer = evt.GetBuffer().Get(Cbs, 0, sizeof(long)).Value; + // var flags = isEqual ? ConditionalRenderingFlagsEXT.InvertedBitExt : 0; + + // var conditionalRenderingBeginInfo = new ConditionalRenderingBeginInfoEXT + // { + // SType = StructureType.ConditionalRenderingBeginInfoExt, + // Buffer = buffer, + // Flags = flags, + // }; + + // Gd.ConditionalRenderingApi.CmdBeginConditionalRendering(CommandBuffer, conditionalRenderingBeginInfo); + } + + _activeConditionalRender = evt; + return true; + } + } + + // The GPU will flush the queries to CPU and evaluate the condition there instead. + + FlushPendingQuery(); // The thread will be stalled manually flushing the counter, so flush commands now. + return false; + } + + public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual) + { + FlushPendingQuery(); // The thread will be stalled manually flushing the counter, so flush commands now. + return false; + } + + private void FlushPendingQuery() + { + if (AutoFlush.ShouldFlushQuery()) + { + FlushCommandsImpl(); + } + } + + public CommandBufferScoped GetPreloadCommandBuffer() + { + PreloadCbs ??= Gd.CommandBufferPool.Rent(); + + return PreloadCbs.Value; + } + + public void FlushCommandsIfWeightExceeding(IAuto disposedResource, ulong byteWeight) + { + bool usedByCurrentCb = disposedResource.HasCommandBufferDependency(Cbs); + + if (PreloadCbs != null && !usedByCurrentCb) + { + usedByCurrentCb = disposedResource.HasCommandBufferDependency(PreloadCbs.Value); + } + + if (usedByCurrentCb) + { + // Since we can only free memory after the command buffer that uses a given resource was executed, + // keeping the command buffer might cause a high amount of memory to be in use. + // To prevent that, we force submit command buffers if the memory usage by resources + // in use by the current command buffer is above a given limit, and those resources were disposed. + _byteWeight += byteWeight; + + if (_byteWeight >= MinByteWeightForFlush) + { + FlushCommandsImpl(); + } + } + } + + public void Restore() + { + if (Pipeline != null) + { + Gd.Api.CmdBindPipeline(CommandBuffer, Pbp, Pipeline.Get(Cbs).Value); + } + + SignalCommandBufferChange(); + + if (Pipeline != null && Pbp == PipelineBindPoint.Graphics) + { + DynamicState.ReplayIfDirty(Gd, CommandBuffer); + } + } + + public void FlushCommandsImpl() + { + AutoFlush.RegisterFlush(DrawCount); + EndRenderPass(); + + foreach ((var queryPool, _) in _activeQueries) + { + Gd.Api.CmdEndQuery(CommandBuffer, queryPool, 0); + } + + _byteWeight = 0; + + if (PreloadCbs != null) + { + PreloadCbs.Value.Dispose(); + PreloadCbs = null; + } + + Gd.Barriers.Flush(Cbs, false, null, null); + CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer; + Gd.RegisterFlush(); + + // Restore per-command buffer state. + foreach (BufferHolder buffer in _activeBufferMirrors) + { + buffer.ClearMirrors(); + } + + _activeBufferMirrors.Clear(); + + foreach ((var queryPool, var isOcclusion) in _activeQueries) + { + bool isPrecise = Gd.Capabilities.SupportsPreciseOcclusionQueries && isOcclusion; + + Gd.Api.CmdResetQueryPool(CommandBuffer, queryPool, 0, 1); + Gd.Api.CmdBeginQuery(CommandBuffer, queryPool, 0, isPrecise ? QueryControlFlags.PreciseBit : 0); + } + + Gd.ResetCounterPool(); + + Restore(); + } + + public void RegisterActiveMirror(BufferHolder buffer) + { + _activeBufferMirrors.Add(buffer); + } + + public void BeginQuery(BufferedQuery query, QueryPool pool, bool needsReset, bool isOcclusion, bool fromSamplePool) + { + if (needsReset) + { + EndRenderPass(); + + Gd.Api.CmdResetQueryPool(CommandBuffer, pool, 0, 1); + + if (fromSamplePool) + { + // Try reset some additional queries in advance. + + Gd.ResetFutureCounters(CommandBuffer, AutoFlush.GetRemainingQueries()); + } + } + + bool isPrecise = Gd.Capabilities.SupportsPreciseOcclusionQueries && isOcclusion; + Gd.Api.CmdBeginQuery(CommandBuffer, pool, 0, isPrecise ? QueryControlFlags.PreciseBit : 0); + + _activeQueries.Add((pool, isOcclusion)); + } + + public void EndQuery(QueryPool pool) + { + Gd.Api.CmdEndQuery(CommandBuffer, pool, 0); + + for (int i = 0; i < _activeQueries.Count; i++) + { + if (_activeQueries[i].Item1.Handle == pool.Handle) + { + _activeQueries.RemoveAt(i); + break; + } + } + } + + public void CopyQueryResults(BufferedQuery query) + { + _pendingQueryCopies.Add(query); + + if (AutoFlush.RegisterPendingQuery()) + { + FlushCommandsImpl(); + } + } + + protected override void SignalAttachmentChange() + { + if (AutoFlush.ShouldFlushAttachmentChange(DrawCount)) + { + FlushCommandsImpl(); + } + } + + protected override void SignalRenderPassEnd() + { + CopyPendingQuery(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs b/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs new file mode 100644 index 00000000..dfbf1901 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs @@ -0,0 +1,54 @@ +using Silk.NET.Vulkan; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + class PipelineHelperShader : PipelineBase + { + public PipelineHelperShader(VulkanRenderer gd, Device device) : base(gd, device) + { + } + + public void SetRenderTarget(TextureView view, uint width, uint height) + { + CreateFramebuffer(view, width, height); + CreateRenderPass(); + SignalStateChange(); + } + + private void CreateFramebuffer(TextureView view, uint width, uint height) + { + FramebufferParams = new FramebufferParams(Device, view, width, height); + UpdatePipelineAttachmentFormats(); + } + + public void SetCommandBuffer(CommandBufferScoped cbs) + { + CommandBuffer = (Cbs = cbs).CommandBuffer; + + // Restore per-command buffer state. + + if (Pipeline != null) + { + Gd.Api.CmdBindPipeline(CommandBuffer, Pbp, Pipeline.Get(CurrentCommandBuffer).Value); + } + + SignalCommandBufferChange(); + } + + public void Finish() + { + EndRenderPass(); + } + + public void Finish(VulkanRenderer gd, CommandBufferScoped cbs) + { + Finish(); + + if (gd.PipelineInternal.IsCommandBufferActive(cbs.CommandBuffer)) + { + gd.PipelineInternal.Restore(); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs new file mode 100644 index 00000000..5d0cada9 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs @@ -0,0 +1,107 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Concurrent; +using System.Collections.ObjectModel; + +namespace Ryujinx.Graphics.Vulkan +{ + class PipelineLayoutCache + { + private readonly struct PlceKey : IEquatable + { + public readonly ReadOnlyCollection SetDescriptors; + public readonly bool UsePushDescriptors; + + public PlceKey(ReadOnlyCollection setDescriptors, bool usePushDescriptors) + { + SetDescriptors = setDescriptors; + UsePushDescriptors = usePushDescriptors; + } + + public override int GetHashCode() + { + HashCode hasher = new(); + + if (SetDescriptors != null) + { + foreach (var setDescriptor in SetDescriptors) + { + hasher.Add(setDescriptor); + } + } + + hasher.Add(UsePushDescriptors); + + return hasher.ToHashCode(); + } + + public override bool Equals(object obj) + { + return obj is PlceKey other && Equals(other); + } + + public bool Equals(PlceKey other) + { + if ((SetDescriptors == null) != (other.SetDescriptors == null)) + { + return false; + } + + if (SetDescriptors != null) + { + if (SetDescriptors.Count != other.SetDescriptors.Count) + { + return false; + } + + for (int index = 0; index < SetDescriptors.Count; index++) + { + if (!SetDescriptors[index].Equals(other.SetDescriptors[index])) + { + return false; + } + } + } + + return UsePushDescriptors == other.UsePushDescriptors; + } + } + + private readonly ConcurrentDictionary _plces; + + public PipelineLayoutCache() + { + _plces = new ConcurrentDictionary(); + } + + public PipelineLayoutCacheEntry GetOrCreate( + VulkanRenderer gd, + Device device, + ReadOnlyCollection setDescriptors, + bool usePushDescriptors) + { + var key = new PlceKey(setDescriptors, usePushDescriptors); + + return _plces.GetOrAdd(key, newKey => new PipelineLayoutCacheEntry(gd, device, setDescriptors, usePushDescriptors)); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (var plce in _plces.Values) + { + plce.Dispose(); + } + + _plces.Clear(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs new file mode 100644 index 00000000..ae296b03 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs @@ -0,0 +1,383 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + class PipelineLayoutCacheEntry + { + private const int MaxPoolSizesPerSet = 8; + + private readonly VulkanRenderer _gd; + private readonly Device _device; + + public DescriptorSetLayout[] DescriptorSetLayouts { get; } + public bool[] DescriptorSetLayoutsUpdateAfterBind { get; } + public PipelineLayout PipelineLayout { get; } + + private readonly int[] _consumedDescriptorsPerSet; + private readonly DescriptorPoolSize[][] _poolSizes; + + private readonly DescriptorSetManager _descriptorSetManager; + + private readonly List>[][] _dsCache; + private List>[] _currentDsCache; + private readonly int[] _dsCacheCursor; + private int _dsLastCbIndex; + private int _dsLastSubmissionCount; + + private struct ManualDescriptorSetEntry + { + public Auto DescriptorSet; + public uint CbRefMask; + public bool InUse; + + public ManualDescriptorSetEntry(Auto descriptorSet, int cbIndex) + { + DescriptorSet = descriptorSet; + CbRefMask = 1u << cbIndex; + InUse = true; + } + } + + private readonly struct PendingManualDsConsumption + { + public FenceHolder Fence { get; } + public int CommandBufferIndex { get; } + public int SetIndex { get; } + public int CacheIndex { get; } + + public PendingManualDsConsumption(FenceHolder fence, int commandBufferIndex, int setIndex, int cacheIndex) + { + Fence = fence; + CommandBufferIndex = commandBufferIndex; + SetIndex = setIndex; + CacheIndex = cacheIndex; + fence.Get(); + } + } + + private readonly List[] _manualDsCache; + private readonly Queue _pendingManualDsConsumptions; + private readonly Queue[] _freeManualDsCacheEntries; + + private readonly Dictionary _pdTemplates; + private readonly ResourceDescriptorCollection _pdDescriptors; + private long _lastPdUsage; + private DescriptorSetTemplate _lastPdTemplate; + + private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount) + { + _gd = gd; + _device = device; + + _dsCache = new List>[CommandBufferPool.MaxCommandBuffers][]; + + for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++) + { + _dsCache[i] = new List>[setsCount]; + + for (int j = 0; j < _dsCache[i].Length; j++) + { + _dsCache[i][j] = new List>(); + } + } + + _dsCacheCursor = new int[setsCount]; + _manualDsCache = new List[setsCount]; + _pendingManualDsConsumptions = new Queue(); + _freeManualDsCacheEntries = new Queue[setsCount]; + } + + public PipelineLayoutCacheEntry( + VulkanRenderer gd, + Device device, + ReadOnlyCollection setDescriptors, + bool usePushDescriptors) : this(gd, device, setDescriptors.Count) + { + ResourceLayouts layouts = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors); + + DescriptorSetLayouts = layouts.DescriptorSetLayouts; + DescriptorSetLayoutsUpdateAfterBind = layouts.DescriptorSetLayoutsUpdateAfterBind; + PipelineLayout = layouts.PipelineLayout; + + _consumedDescriptorsPerSet = new int[setDescriptors.Count]; + _poolSizes = new DescriptorPoolSize[setDescriptors.Count][]; + + Span poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet]; + + for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++) + { + int count = 0; + + foreach (var descriptor in setDescriptors[setIndex].Descriptors) + { + count += descriptor.Count; + } + + _consumedDescriptorsPerSet[setIndex] = count; + _poolSizes[setIndex] = GetDescriptorPoolSizes(poolSizes, setDescriptors[setIndex], DescriptorSetManager.MaxSets).ToArray(); + } + + if (usePushDescriptors) + { + _pdDescriptors = setDescriptors[0]; + _pdTemplates = new(); + } + + _descriptorSetManager = new DescriptorSetManager(_device, setDescriptors.Count); + } + + public void UpdateCommandBufferIndex(int commandBufferIndex) + { + int submissionCount = _gd.CommandBufferPool.GetSubmissionCount(commandBufferIndex); + + if (_dsLastCbIndex != commandBufferIndex || _dsLastSubmissionCount != submissionCount) + { + _dsLastCbIndex = commandBufferIndex; + _dsLastSubmissionCount = submissionCount; + Array.Clear(_dsCacheCursor); + } + + _currentDsCache = _dsCache[commandBufferIndex]; + } + + public Auto GetNewDescriptorSetCollection(int setIndex, out bool isNew) + { + var list = _currentDsCache[setIndex]; + int index = _dsCacheCursor[setIndex]++; + if (index == list.Count) + { + var dsc = _descriptorSetManager.AllocateDescriptorSet( + _gd.Api, + DescriptorSetLayouts[setIndex], + _poolSizes[setIndex], + setIndex, + _consumedDescriptorsPerSet[setIndex], + DescriptorSetLayoutsUpdateAfterBind[setIndex]); + + list.Add(dsc); + isNew = true; + return dsc; + } + + isNew = false; + return list[index]; + } + + public Auto GetNewManualDescriptorSetCollection(CommandBufferScoped cbs, int setIndex, out int cacheIndex) + { + FreeCompletedManualDescriptorSets(); + + var list = _manualDsCache[setIndex] ??= new(); + var span = CollectionsMarshal.AsSpan(list); + + Queue freeQueue = _freeManualDsCacheEntries[setIndex]; + + // Do we have at least one freed descriptor set? If so, just use that. + if (freeQueue != null && freeQueue.TryDequeue(out int freeIndex)) + { + ref ManualDescriptorSetEntry entry = ref span[freeIndex]; + + Debug.Assert(!entry.InUse && entry.CbRefMask == 0); + + entry.InUse = true; + entry.CbRefMask = 1u << cbs.CommandBufferIndex; + cacheIndex = freeIndex; + + _pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, freeIndex)); + + return entry.DescriptorSet; + } + + // Otherwise create a new descriptor set, and add to our pending queue for command buffer consumption tracking. + var dsc = _descriptorSetManager.AllocateDescriptorSet( + _gd.Api, + DescriptorSetLayouts[setIndex], + _poolSizes[setIndex], + setIndex, + _consumedDescriptorsPerSet[setIndex], + DescriptorSetLayoutsUpdateAfterBind[setIndex]); + + cacheIndex = list.Count; + list.Add(new ManualDescriptorSetEntry(dsc, cbs.CommandBufferIndex)); + _pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, cacheIndex)); + + return dsc; + } + + public void UpdateManualDescriptorSetCollectionOwnership(CommandBufferScoped cbs, int setIndex, int cacheIndex) + { + FreeCompletedManualDescriptorSets(); + + var list = _manualDsCache[setIndex]; + var span = CollectionsMarshal.AsSpan(list); + ref var entry = ref span[cacheIndex]; + + uint cbMask = 1u << cbs.CommandBufferIndex; + + if ((entry.CbRefMask & cbMask) == 0) + { + entry.CbRefMask |= cbMask; + + _pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, cacheIndex)); + } + } + + private void FreeCompletedManualDescriptorSets() + { + FenceHolder signalledFence = null; + while (_pendingManualDsConsumptions.TryPeek(out var pds) && (pds.Fence == signalledFence || pds.Fence.IsSignaled())) + { + signalledFence = pds.Fence; // Already checked - don't need to do it again. + var dequeued = _pendingManualDsConsumptions.Dequeue(); + Debug.Assert(dequeued.Fence == pds.Fence); + pds.Fence.Put(); + + var span = CollectionsMarshal.AsSpan(_manualDsCache[dequeued.SetIndex]); + ref var entry = ref span[dequeued.CacheIndex]; + entry.CbRefMask &= ~(1u << dequeued.CommandBufferIndex); + + if (!entry.InUse && entry.CbRefMask == 0) + { + // If not in use by any array, and not bound to any command buffer, the descriptor set can be re-used immediately. + (_freeManualDsCacheEntries[dequeued.SetIndex] ??= new()).Enqueue(dequeued.CacheIndex); + } + } + } + + public void ReleaseManualDescriptorSetCollection(int setIndex, int cacheIndex) + { + var list = _manualDsCache[setIndex]; + var span = CollectionsMarshal.AsSpan(list); + + span[cacheIndex].InUse = false; + + if (span[cacheIndex].CbRefMask == 0) + { + // This is no longer in use by any array, so if not bound to any command buffer, the descriptor set can be re-used immediately. + (_freeManualDsCacheEntries[setIndex] ??= new()).Enqueue(cacheIndex); + } + } + + private static Span GetDescriptorPoolSizes(Span output, ResourceDescriptorCollection setDescriptor, uint multiplier) + { + int count = 0; + + for (int index = 0; index < setDescriptor.Descriptors.Count; index++) + { + ResourceDescriptor descriptor = setDescriptor.Descriptors[index]; + DescriptorType descriptorType = descriptor.Type.Convert(); + + bool found = false; + + for (int poolSizeIndex = 0; poolSizeIndex < count; poolSizeIndex++) + { + if (output[poolSizeIndex].Type == descriptorType) + { + output[poolSizeIndex].DescriptorCount += (uint)descriptor.Count * multiplier; + found = true; + break; + } + } + + if (!found) + { + output[count++] = new DescriptorPoolSize() + { + Type = descriptorType, + DescriptorCount = (uint)descriptor.Count, + }; + } + } + + return output[..count]; + } + + public DescriptorSetTemplate GetPushDescriptorTemplate(PipelineBindPoint pbp, long updateMask) + { + if (_lastPdUsage == updateMask && _lastPdTemplate != null) + { + // Most likely result is that it asks to update the same buffers. + return _lastPdTemplate; + } + + if (!_pdTemplates.TryGetValue(updateMask, out DescriptorSetTemplate template)) + { + template = new DescriptorSetTemplate(_gd, _device, _pdDescriptors, updateMask, this, pbp, 0); + + _pdTemplates.Add(updateMask, template); + } + + _lastPdUsage = updateMask; + _lastPdTemplate = template; + + return template; + } + + protected virtual unsafe void Dispose(bool disposing) + { + if (disposing) + { + if (_pdTemplates != null) + { + foreach (DescriptorSetTemplate template in _pdTemplates.Values) + { + template.Dispose(); + } + } + + for (int i = 0; i < _dsCache.Length; i++) + { + for (int j = 0; j < _dsCache[i].Length; j++) + { + for (int k = 0; k < _dsCache[i][j].Count; k++) + { + _dsCache[i][j][k].Dispose(); + } + + _dsCache[i][j].Clear(); + } + } + + for (int i = 0; i < _manualDsCache.Length; i++) + { + if (_manualDsCache[i] == null) + { + continue; + } + + for (int j = 0; j < _manualDsCache[i].Count; j++) + { + _manualDsCache[i][j].DescriptorSet.Dispose(); + } + + _manualDsCache[i].Clear(); + } + + _gd.Api.DestroyPipelineLayout(_device, PipelineLayout, null); + + for (int i = 0; i < DescriptorSetLayouts.Length; i++) + { + _gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null); + } + + while (_pendingManualDsConsumptions.TryDequeue(out var pds)) + { + pds.Fence.Put(); + } + + _descriptorSetManager.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs new file mode 100644 index 00000000..8d781561 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs @@ -0,0 +1,115 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.ObjectModel; + +namespace Ryujinx.Graphics.Vulkan +{ + record struct ResourceLayouts(DescriptorSetLayout[] DescriptorSetLayouts, bool[] DescriptorSetLayoutsUpdateAfterBind, PipelineLayout PipelineLayout); + + static class PipelineLayoutFactory + { + public static unsafe ResourceLayouts Create( + VulkanRenderer gd, + Device device, + ReadOnlyCollection setDescriptors, + bool usePushDescriptors) + { + DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count]; + bool[] updateAfterBindFlags = new bool[setDescriptors.Count]; + + bool isMoltenVk = gd.IsMoltenVk; + + for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++) + { + ResourceDescriptorCollection rdc = setDescriptors[setIndex]; + + ResourceStages activeStages = ResourceStages.None; + + if (isMoltenVk) + { + for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++) + { + activeStages |= rdc.Descriptors[descIndex].Stages; + } + } + + DescriptorSetLayoutBinding[] layoutBindings = new DescriptorSetLayoutBinding[rdc.Descriptors.Count]; + + bool hasArray = false; + + for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++) + { + ResourceDescriptor descriptor = rdc.Descriptors[descIndex]; + ResourceStages stages = descriptor.Stages; + + if (descriptor.Type == ResourceType.StorageBuffer && isMoltenVk) + { + // There's a bug on MoltenVK where using the same buffer across different stages + // causes invalid resource errors, allow the binding on all active stages as workaround. + stages = activeStages; + } + + layoutBindings[descIndex] = new DescriptorSetLayoutBinding + { + Binding = (uint)descriptor.Binding, + DescriptorType = descriptor.Type.Convert(), + DescriptorCount = (uint)descriptor.Count, + StageFlags = stages.Convert(), + }; + + if (descriptor.Count > 1) + { + hasArray = true; + } + } + + fixed (DescriptorSetLayoutBinding* pLayoutBindings = layoutBindings) + { + DescriptorSetLayoutCreateFlags flags = DescriptorSetLayoutCreateFlags.None; + + if (usePushDescriptors && setIndex == 0) + { + flags = DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr; + } + + if (gd.Vendor == Vendor.Intel && hasArray) + { + // Some vendors (like Intel) have low per-stage limits. + // We must set the flag if we exceed those limits. + flags |= DescriptorSetLayoutCreateFlags.UpdateAfterBindPoolBit; + + updateAfterBindFlags[setIndex] = true; + } + + var descriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + PBindings = pLayoutBindings, + BindingCount = (uint)layoutBindings.Length, + Flags = flags, + }; + + gd.Api.CreateDescriptorSetLayout(device, in descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError(); + } + } + + PipelineLayout layout; + + fixed (DescriptorSetLayout* pLayouts = layouts) + { + var pipelineLayoutCreateInfo = new PipelineLayoutCreateInfo + { + SType = StructureType.PipelineLayoutCreateInfo, + PSetLayouts = pLayouts, + SetLayoutCount = (uint)layouts.Length, + }; + + gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError(); + } + + return new ResourceLayouts(layouts, updateAfterBindFlags, layout); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs new file mode 100644 index 00000000..a726b9ed --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs @@ -0,0 +1,732 @@ +using Ryujinx.Common.Memory; +using Silk.NET.Vulkan; +using System; +using System.Numerics; + +namespace Ryujinx.Graphics.Vulkan +{ + struct PipelineState : IDisposable + { + private const int RequiredSubgroupSize = 32; + private const int MaxDynamicStatesCount = 9; + + public PipelineUid Internal; + + public float LineWidth + { + readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id0 >> 0) & 0xFFFFFFFF)); + set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0); + } + + public float DepthBiasClamp + { + readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id0 >> 32) & 0xFFFFFFFF)); + set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32); + } + + public float DepthBiasConstantFactor + { + readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id1 >> 0) & 0xFFFFFFFF)); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0); + } + + public float DepthBiasSlopeFactor + { + readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id1 >> 32) & 0xFFFFFFFF)); + set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32); + } + + public uint StencilFrontCompareMask + { + readonly get => (uint)((Internal.Id2 >> 0) & 0xFFFFFFFF); + set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF00000000) | ((ulong)value << 0); + } + + public uint StencilFrontWriteMask + { + readonly get => (uint)((Internal.Id2 >> 32) & 0xFFFFFFFF); + set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF) | ((ulong)value << 32); + } + + public uint StencilFrontReference + { + readonly get => (uint)((Internal.Id3 >> 0) & 0xFFFFFFFF); + set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFF00000000) | ((ulong)value << 0); + } + + public uint StencilBackCompareMask + { + readonly get => (uint)((Internal.Id3 >> 32) & 0xFFFFFFFF); + set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFF) | ((ulong)value << 32); + } + + public uint StencilBackWriteMask + { + readonly get => (uint)((Internal.Id4 >> 0) & 0xFFFFFFFF); + set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF00000000) | ((ulong)value << 0); + } + + public uint StencilBackReference + { + readonly get => (uint)((Internal.Id4 >> 32) & 0xFFFFFFFF); + set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF) | ((ulong)value << 32); + } + + public PolygonMode PolygonMode + { + readonly get => (PolygonMode)((Internal.Id5 >> 0) & 0x3FFFFFFF); + set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFFC0000000) | ((ulong)value << 0); + } + + public uint StagesCount + { + readonly get => (byte)((Internal.Id5 >> 30) & 0xFF); + set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30); + } + + public uint VertexAttributeDescriptionsCount + { + readonly get => (byte)((Internal.Id5 >> 38) & 0xFF); + set => Internal.Id5 = (Internal.Id5 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38); + } + + public uint VertexBindingDescriptionsCount + { + readonly get => (byte)((Internal.Id5 >> 46) & 0xFF); + set => Internal.Id5 = (Internal.Id5 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46); + } + + public uint ViewportsCount + { + readonly get => (byte)((Internal.Id5 >> 54) & 0xFF); + set => Internal.Id5 = (Internal.Id5 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54); + } + + public uint ScissorsCount + { + readonly get => (byte)((Internal.Id6 >> 0) & 0xFF); + set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0); + } + + public uint ColorBlendAttachmentStateCount + { + readonly get => (byte)((Internal.Id6 >> 8) & 0xFF); + set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8); + } + + public PrimitiveTopology Topology + { + readonly get => (PrimitiveTopology)((Internal.Id6 >> 16) & 0xF); + set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16); + } + + public LogicOp LogicOp + { + readonly get => (LogicOp)((Internal.Id6 >> 20) & 0xF); + set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20); + } + + public CompareOp DepthCompareOp + { + readonly get => (CompareOp)((Internal.Id6 >> 24) & 0x7); + set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24); + } + + public StencilOp StencilFrontFailOp + { + readonly get => (StencilOp)((Internal.Id6 >> 27) & 0x7); + set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27); + } + + public StencilOp StencilFrontPassOp + { + readonly get => (StencilOp)((Internal.Id6 >> 30) & 0x7); + set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30); + } + + public StencilOp StencilFrontDepthFailOp + { + readonly get => (StencilOp)((Internal.Id6 >> 33) & 0x7); + set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33); + } + + public CompareOp StencilFrontCompareOp + { + readonly get => (CompareOp)((Internal.Id6 >> 36) & 0x7); + set => Internal.Id6 = (Internal.Id6 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36); + } + + public StencilOp StencilBackFailOp + { + readonly get => (StencilOp)((Internal.Id6 >> 39) & 0x7); + set => Internal.Id6 = (Internal.Id6 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39); + } + + public StencilOp StencilBackPassOp + { + readonly get => (StencilOp)((Internal.Id6 >> 42) & 0x7); + set => Internal.Id6 = (Internal.Id6 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42); + } + + public StencilOp StencilBackDepthFailOp + { + readonly get => (StencilOp)((Internal.Id6 >> 45) & 0x7); + set => Internal.Id6 = (Internal.Id6 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45); + } + + public CompareOp StencilBackCompareOp + { + readonly get => (CompareOp)((Internal.Id6 >> 48) & 0x7); + set => Internal.Id6 = (Internal.Id6 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48); + } + + public CullModeFlags CullMode + { + readonly get => (CullModeFlags)((Internal.Id6 >> 51) & 0x3); + set => Internal.Id6 = (Internal.Id6 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51); + } + + public bool PrimitiveRestartEnable + { + readonly get => ((Internal.Id6 >> 53) & 0x1) != 0UL; + set => Internal.Id6 = (Internal.Id6 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53); + } + + public bool DepthClampEnable + { + readonly get => ((Internal.Id6 >> 54) & 0x1) != 0UL; + set => Internal.Id6 = (Internal.Id6 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54); + } + + public bool RasterizerDiscardEnable + { + readonly get => ((Internal.Id6 >> 55) & 0x1) != 0UL; + set => Internal.Id6 = (Internal.Id6 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55); + } + + public FrontFace FrontFace + { + readonly get => (FrontFace)((Internal.Id6 >> 56) & 0x1); + set => Internal.Id6 = (Internal.Id6 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56); + } + + public bool DepthBiasEnable + { + readonly get => ((Internal.Id6 >> 57) & 0x1) != 0UL; + set => Internal.Id6 = (Internal.Id6 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57); + } + + public bool DepthTestEnable + { + readonly get => ((Internal.Id6 >> 58) & 0x1) != 0UL; + set => Internal.Id6 = (Internal.Id6 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58); + } + + public bool DepthWriteEnable + { + readonly get => ((Internal.Id6 >> 59) & 0x1) != 0UL; + set => Internal.Id6 = (Internal.Id6 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59); + } + + public bool DepthBoundsTestEnable + { + readonly get => ((Internal.Id6 >> 60) & 0x1) != 0UL; + set => Internal.Id6 = (Internal.Id6 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60); + } + + public bool StencilTestEnable + { + readonly get => ((Internal.Id6 >> 61) & 0x1) != 0UL; + set => Internal.Id6 = (Internal.Id6 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61); + } + + public bool LogicOpEnable + { + readonly get => ((Internal.Id6 >> 62) & 0x1) != 0UL; + set => Internal.Id6 = (Internal.Id6 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62); + } + + public bool HasDepthStencil + { + readonly get => ((Internal.Id6 >> 63) & 0x1) != 0UL; + set => Internal.Id6 = (Internal.Id6 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63); + } + + public uint PatchControlPoints + { + readonly get => (uint)((Internal.Id7 >> 0) & 0xFFFFFFFF); + set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF00000000) | ((ulong)value << 0); + } + + public uint SamplesCount + { + readonly get => (uint)((Internal.Id7 >> 32) & 0xFFFFFFFF); + set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF) | ((ulong)value << 32); + } + + public bool AlphaToCoverageEnable + { + readonly get => ((Internal.Id8 >> 0) & 0x1) != 0UL; + set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0); + } + + public bool AlphaToOneEnable + { + readonly get => ((Internal.Id8 >> 1) & 0x1) != 0UL; + set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1); + } + + public bool AdvancedBlendSrcPreMultiplied + { + readonly get => ((Internal.Id8 >> 2) & 0x1) != 0UL; + set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2); + } + + public bool AdvancedBlendDstPreMultiplied + { + readonly get => ((Internal.Id8 >> 3) & 0x1) != 0UL; + set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3); + } + + public BlendOverlapEXT AdvancedBlendOverlap + { + readonly get => (BlendOverlapEXT)((Internal.Id8 >> 4) & 0x3); + set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4); + } + + public bool DepthMode + { + readonly get => ((Internal.Id8 >> 6) & 0x1) != 0UL; + set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6); + } + + public FeedbackLoopAspects FeedbackLoopAspects + { + readonly get => (FeedbackLoopAspects)((Internal.Id8 >> 7) & 0x3); + set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFE7F) | (((ulong)value) << 7); + } + + public bool HasTessellationControlShader; + public NativeArray Stages; + public PipelineLayout PipelineLayout; + public SpecData SpecializationData; + + private Array32 _vertexAttributeDescriptions2; + + public void Initialize() + { + HasTessellationControlShader = false; + Stages = new NativeArray(Constants.MaxShaderStages); + + AdvancedBlendSrcPreMultiplied = true; + AdvancedBlendDstPreMultiplied = true; + AdvancedBlendOverlap = BlendOverlapEXT.UncorrelatedExt; + + LineWidth = 1f; + SamplesCount = 1; + DepthMode = true; + } + + public unsafe Auto CreateComputePipeline( + VulkanRenderer gd, + Device device, + ShaderCollection program, + PipelineCache cache) + { + if (program.TryGetComputePipeline(ref SpecializationData, out var pipeline)) + { + return pipeline; + } + + var pipelineCreateInfo = new ComputePipelineCreateInfo + { + SType = StructureType.ComputePipelineCreateInfo, + Stage = Stages[0], + BasePipelineIndex = -1, + Layout = PipelineLayout, + }; + + Pipeline pipelineHandle = default; + + bool hasSpec = program.SpecDescriptions != null; + + var desc = hasSpec ? program.SpecDescriptions[0] : SpecDescription.Empty; + + if (hasSpec && SpecializationData.Length < (int)desc.Info.DataSize) + { + throw new InvalidOperationException("Specialization data size does not match description"); + } + + fixed (SpecializationInfo* info = &desc.Info) + fixed (SpecializationMapEntry* map = desc.Map) + fixed (byte* data = SpecializationData.Span) + { + if (hasSpec) + { + info->PMapEntries = map; + info->PData = data; + pipelineCreateInfo.Stage.PSpecializationInfo = info; + } + + gd.Api.CreateComputePipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle).ThrowOnError(); + } + + pipeline = new Auto(new DisposablePipeline(gd.Api, device, pipelineHandle)); + + program.AddComputePipeline(ref SpecializationData, pipeline); + + return pipeline; + } + + public unsafe Auto CreateGraphicsPipeline( + VulkanRenderer gd, + Device device, + ShaderCollection program, + PipelineCache cache, + RenderPass renderPass, + bool throwOnError = false) + { + if (program.TryGetGraphicsPipeline(ref Internal, out var pipeline)) + { + return pipeline; + } + + Pipeline pipelineHandle = default; + + bool isMoltenVk = gd.IsMoltenVk; + + if (isMoltenVk) + { + UpdateVertexAttributeDescriptions(gd); + } + + fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions = &Internal.VertexAttributeDescriptions[0]) + fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions2 = &_vertexAttributeDescriptions2[0]) + fixed (VertexInputBindingDescription* pVertexBindingDescriptions = &Internal.VertexBindingDescriptions[0]) + fixed (PipelineColorBlendAttachmentState* pColorBlendAttachmentState = &Internal.ColorBlendAttachmentState[0]) + { + var vertexInputState = new PipelineVertexInputStateCreateInfo + { + SType = StructureType.PipelineVertexInputStateCreateInfo, + VertexAttributeDescriptionCount = VertexAttributeDescriptionsCount, + PVertexAttributeDescriptions = isMoltenVk ? pVertexAttributeDescriptions2 : pVertexAttributeDescriptions, + VertexBindingDescriptionCount = VertexBindingDescriptionsCount, + PVertexBindingDescriptions = pVertexBindingDescriptions, + }; + + // Using patches topology without a tessellation shader is invalid. + // If we find such a case, return null pipeline to skip the draw. + if (Topology == PrimitiveTopology.PatchList && !HasTessellationControlShader) + { + program.AddGraphicsPipeline(ref Internal, null); + + return null; + } + + bool primitiveRestartEnable = PrimitiveRestartEnable; + + bool topologySupportsRestart; + + if (gd.Capabilities.SupportsPrimitiveTopologyListRestart) + { + topologySupportsRestart = gd.Capabilities.SupportsPrimitiveTopologyPatchListRestart || Topology != PrimitiveTopology.PatchList; + } + else + { + topologySupportsRestart = Topology == PrimitiveTopology.LineStrip || + Topology == PrimitiveTopology.TriangleStrip || + Topology == PrimitiveTopology.TriangleFan || + Topology == PrimitiveTopology.LineStripWithAdjacency || + Topology == PrimitiveTopology.TriangleStripWithAdjacency; + } + + primitiveRestartEnable &= topologySupportsRestart; + + var inputAssemblyState = new PipelineInputAssemblyStateCreateInfo + { + SType = StructureType.PipelineInputAssemblyStateCreateInfo, + PrimitiveRestartEnable = primitiveRestartEnable, + Topology = HasTessellationControlShader ? PrimitiveTopology.PatchList : Topology, + }; + + var tessellationState = new PipelineTessellationStateCreateInfo + { + SType = StructureType.PipelineTessellationStateCreateInfo, + PatchControlPoints = PatchControlPoints, + }; + + var rasterizationState = new PipelineRasterizationStateCreateInfo + { + SType = StructureType.PipelineRasterizationStateCreateInfo, + DepthClampEnable = DepthClampEnable, + RasterizerDiscardEnable = RasterizerDiscardEnable, + PolygonMode = PolygonMode, + LineWidth = LineWidth, + CullMode = CullMode, + FrontFace = FrontFace, + DepthBiasEnable = DepthBiasEnable, + }; + + var viewportState = new PipelineViewportStateCreateInfo + { + SType = StructureType.PipelineViewportStateCreateInfo, + ViewportCount = ViewportsCount, + ScissorCount = ScissorsCount, + }; + + if (gd.Capabilities.SupportsDepthClipControl) + { + var viewportDepthClipControlState = new PipelineViewportDepthClipControlCreateInfoEXT + { + SType = StructureType.PipelineViewportDepthClipControlCreateInfoExt, + NegativeOneToOne = DepthMode, + }; + + viewportState.PNext = &viewportDepthClipControlState; + } + + var multisampleState = new PipelineMultisampleStateCreateInfo + { + SType = StructureType.PipelineMultisampleStateCreateInfo, + SampleShadingEnable = false, + RasterizationSamples = TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, SamplesCount), + MinSampleShading = 1, + AlphaToCoverageEnable = AlphaToCoverageEnable, + AlphaToOneEnable = AlphaToOneEnable, + }; + + var stencilFront = new StencilOpState( + StencilFrontFailOp, + StencilFrontPassOp, + StencilFrontDepthFailOp, + StencilFrontCompareOp); + + var stencilBack = new StencilOpState( + StencilBackFailOp, + StencilBackPassOp, + StencilBackDepthFailOp, + StencilBackCompareOp); + + var depthStencilState = new PipelineDepthStencilStateCreateInfo + { + SType = StructureType.PipelineDepthStencilStateCreateInfo, + DepthTestEnable = DepthTestEnable, + DepthWriteEnable = DepthWriteEnable, + DepthCompareOp = DepthCompareOp, + DepthBoundsTestEnable = false, + StencilTestEnable = StencilTestEnable, + Front = stencilFront, + Back = stencilBack, + }; + + uint blendEnables = 0; + + if (gd.IsMoltenVk && Internal.AttachmentIntegerFormatMask != 0) + { + // Blend can't be enabled for integer formats, so let's make sure it is disabled. + uint attachmentIntegerFormatMask = Internal.AttachmentIntegerFormatMask; + + while (attachmentIntegerFormatMask != 0) + { + int i = BitOperations.TrailingZeroCount(attachmentIntegerFormatMask); + + if (Internal.ColorBlendAttachmentState[i].BlendEnable) + { + blendEnables |= 1u << i; + } + + Internal.ColorBlendAttachmentState[i].BlendEnable = false; + attachmentIntegerFormatMask &= ~(1u << i); + } + } + + // Vendors other than NVIDIA have a bug where it enables logical operations even for float formats, + // so we need to force disable them here. + bool logicOpEnable = LogicOpEnable && (gd.Vendor == Vendor.Nvidia || Internal.LogicOpsAllowed); + + var colorBlendState = new PipelineColorBlendStateCreateInfo + { + SType = StructureType.PipelineColorBlendStateCreateInfo, + LogicOpEnable = logicOpEnable, + LogicOp = LogicOp, + AttachmentCount = ColorBlendAttachmentStateCount, + PAttachments = pColorBlendAttachmentState, + }; + + PipelineColorBlendAdvancedStateCreateInfoEXT colorBlendAdvancedState; + + if (!AdvancedBlendSrcPreMultiplied || + !AdvancedBlendDstPreMultiplied || + AdvancedBlendOverlap != BlendOverlapEXT.UncorrelatedExt) + { + colorBlendAdvancedState = new PipelineColorBlendAdvancedStateCreateInfoEXT + { + SType = StructureType.PipelineColorBlendAdvancedStateCreateInfoExt, + SrcPremultiplied = AdvancedBlendSrcPreMultiplied, + DstPremultiplied = AdvancedBlendDstPreMultiplied, + BlendOverlap = AdvancedBlendOverlap, + }; + + colorBlendState.PNext = &colorBlendAdvancedState; + } + + bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState; + bool supportsFeedbackLoopDynamicState = gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop; + + DynamicState* dynamicStates = stackalloc DynamicState[MaxDynamicStatesCount]; + + int dynamicStatesCount = 7; + + dynamicStates[0] = DynamicState.Viewport; + dynamicStates[1] = DynamicState.Scissor; + dynamicStates[2] = DynamicState.DepthBias; + dynamicStates[3] = DynamicState.StencilCompareMask; + dynamicStates[4] = DynamicState.StencilWriteMask; + dynamicStates[5] = DynamicState.StencilReference; + dynamicStates[6] = DynamicState.BlendConstants; + + if (supportsExtDynamicState) + { + dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt; + } + + if (supportsFeedbackLoopDynamicState) + { + dynamicStates[dynamicStatesCount++] = DynamicState.AttachmentFeedbackLoopEnableExt; + } + + var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo + { + SType = StructureType.PipelineDynamicStateCreateInfo, + DynamicStateCount = (uint)dynamicStatesCount, + PDynamicStates = dynamicStates, + }; + + PipelineCreateFlags flags = 0; + + if (gd.Capabilities.SupportsAttachmentFeedbackLoop) + { + FeedbackLoopAspects aspects = FeedbackLoopAspects; + + if ((aspects & FeedbackLoopAspects.Color) != 0) + { + flags |= PipelineCreateFlags.CreateColorAttachmentFeedbackLoopBitExt; + } + + if ((aspects & FeedbackLoopAspects.Depth) != 0) + { + flags |= PipelineCreateFlags.CreateDepthStencilAttachmentFeedbackLoopBitExt; + } + } + + var pipelineCreateInfo = new GraphicsPipelineCreateInfo + { + SType = StructureType.GraphicsPipelineCreateInfo, + Flags = flags, + StageCount = StagesCount, + PStages = Stages.Pointer, + PVertexInputState = &vertexInputState, + PInputAssemblyState = &inputAssemblyState, + PTessellationState = &tessellationState, + PViewportState = &viewportState, + PRasterizationState = &rasterizationState, + PMultisampleState = &multisampleState, + PDepthStencilState = &depthStencilState, + PColorBlendState = &colorBlendState, + PDynamicState = &pipelineDynamicStateCreateInfo, + Layout = PipelineLayout, + RenderPass = renderPass, + }; + + Result result = gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle); + + if (throwOnError) + { + result.ThrowOnError(); + } + else if (result.IsError()) + { + program.AddGraphicsPipeline(ref Internal, null); + + return null; + } + + // Restore previous blend enable values if we changed it. + while (blendEnables != 0) + { + int i = BitOperations.TrailingZeroCount(blendEnables); + + Internal.ColorBlendAttachmentState[i].BlendEnable = true; + blendEnables &= ~(1u << i); + } + } + + pipeline = new Auto(new DisposablePipeline(gd.Api, device, pipelineHandle)); + + program.AddGraphicsPipeline(ref Internal, pipeline); + + return pipeline; + } + + private void UpdateVertexAttributeDescriptions(VulkanRenderer gd) + { + // Vertex attributes exceeding the stride are invalid. + // In metal, they cause glitches with the vertex shader fetching incorrect values. + // To work around this, we reduce the format to something that doesn't exceed the stride if possible. + // The assumption is that the exceeding components are not actually accessed on the shader. + + for (int index = 0; index < VertexAttributeDescriptionsCount; index++) + { + var attribute = Internal.VertexAttributeDescriptions[index]; + int vbIndex = GetVertexBufferIndex(attribute.Binding); + + if (vbIndex >= 0) + { + ref var vb = ref Internal.VertexBindingDescriptions[vbIndex]; + + Format format = attribute.Format; + + while (vb.Stride != 0 && attribute.Offset + FormatTable.GetAttributeFormatSize(format) > vb.Stride) + { + Format newFormat = FormatTable.DropLastComponent(format); + + if (newFormat == format) + { + // That case means we failed to find a format that fits within the stride, + // so just restore the original format and give up. + format = attribute.Format; + break; + } + + format = newFormat; + } + + if (attribute.Format != format && gd.FormatCapabilities.BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, format)) + { + attribute.Format = format; + } + } + + _vertexAttributeDescriptions2[index] = attribute; + } + } + + private int GetVertexBufferIndex(uint binding) + { + for (int index = 0; index < VertexBindingDescriptionsCount; index++) + { + if (Internal.VertexBindingDescriptions[index].Binding == binding) + { + return index; + } + } + + return -1; + } + + public readonly void Dispose() + { + Stages.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs b/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs new file mode 100644 index 00000000..c5622421 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs @@ -0,0 +1,125 @@ +using Ryujinx.Common.Memory; +using Silk.NET.Vulkan; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace Ryujinx.Graphics.Vulkan +{ + struct PipelineUid : IRefEquatable + { + public ulong Id0; + public ulong Id1; + public ulong Id2; + public ulong Id3; + + public ulong Id4; + public ulong Id5; + public ulong Id6; + + public ulong Id7; + public ulong Id8; + + private readonly uint VertexAttributeDescriptionsCount => (byte)((Id5 >> 38) & 0xFF); + private readonly uint VertexBindingDescriptionsCount => (byte)((Id5 >> 46) & 0xFF); + private readonly uint ColorBlendAttachmentStateCount => (byte)((Id6 >> 8) & 0xFF); + private readonly bool HasDepthStencil => ((Id6 >> 63) & 0x1) != 0UL; + + public Array32 VertexAttributeDescriptions; + public Array33 VertexBindingDescriptions; + public Array8 ColorBlendAttachmentState; + public Array9 AttachmentFormats; + public uint AttachmentIntegerFormatMask; + public bool LogicOpsAllowed; + + public readonly override bool Equals(object obj) + { + return obj is PipelineUid other && Equals(other); + } + + public bool Equals(ref PipelineUid other) + { + if (!Unsafe.As>(ref Id0).Equals(Unsafe.As>(ref other.Id0)) || + !Unsafe.As>(ref Id4).Equals(Unsafe.As>(ref other.Id4)) || + !Unsafe.As>(ref Id7).Equals(Unsafe.As>(ref other.Id7))) + { + return false; + } + + if (!SequenceEqual(VertexAttributeDescriptions.AsSpan(), other.VertexAttributeDescriptions.AsSpan(), VertexAttributeDescriptionsCount)) + { + return false; + } + + if (!SequenceEqual(VertexBindingDescriptions.AsSpan(), other.VertexBindingDescriptions.AsSpan(), VertexBindingDescriptionsCount)) + { + return false; + } + + if (!SequenceEqual(ColorBlendAttachmentState.AsSpan(), other.ColorBlendAttachmentState.AsSpan(), ColorBlendAttachmentStateCount)) + { + return false; + } + + if (!SequenceEqual(AttachmentFormats.AsSpan(), other.AttachmentFormats.AsSpan(), ColorBlendAttachmentStateCount + (HasDepthStencil ? 1u : 0u))) + { + return false; + } + + return true; + } + + private static bool SequenceEqual(ReadOnlySpan x, ReadOnlySpan y, uint count) where T : unmanaged + { + return MemoryMarshal.Cast(x[..(int)count]).SequenceEqual(MemoryMarshal.Cast(y[..(int)count])); + } + + public override int GetHashCode() + { + ulong hash64 = Id0 * 23 ^ + Id1 * 23 ^ + Id2 * 23 ^ + Id3 * 23 ^ + Id4 * 23 ^ + Id5 * 23 ^ + Id6 * 23 ^ + Id7 * 23 ^ + Id8 * 23; + + for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++) + { + hash64 ^= VertexAttributeDescriptions[i].Binding * 23; + hash64 ^= (uint)VertexAttributeDescriptions[i].Format * 23; + hash64 ^= VertexAttributeDescriptions[i].Location * 23; + hash64 ^= VertexAttributeDescriptions[i].Offset * 23; + } + + for (int i = 0; i < (int)VertexBindingDescriptionsCount; i++) + { + hash64 ^= VertexBindingDescriptions[i].Binding * 23; + hash64 ^= (uint)VertexBindingDescriptions[i].InputRate * 23; + hash64 ^= VertexBindingDescriptions[i].Stride * 23; + } + + for (int i = 0; i < (int)ColorBlendAttachmentStateCount; i++) + { + hash64 ^= ColorBlendAttachmentState[i].BlendEnable * 23; + hash64 ^= (uint)ColorBlendAttachmentState[i].SrcColorBlendFactor * 23; + hash64 ^= (uint)ColorBlendAttachmentState[i].DstColorBlendFactor * 23; + hash64 ^= (uint)ColorBlendAttachmentState[i].ColorBlendOp * 23; + hash64 ^= (uint)ColorBlendAttachmentState[i].SrcAlphaBlendFactor * 23; + hash64 ^= (uint)ColorBlendAttachmentState[i].DstAlphaBlendFactor * 23; + hash64 ^= (uint)ColorBlendAttachmentState[i].AlphaBlendOp * 23; + hash64 ^= (uint)ColorBlendAttachmentState[i].ColorWriteMask * 23; + } + + for (int i = 0; i < (int)ColorBlendAttachmentStateCount; i++) + { + hash64 ^= (uint)AttachmentFormats[i] * 23; + } + + return (int)hash64 ^ ((int)(hash64 >> 32) * 17); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs b/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs new file mode 100644 index 00000000..c9a54664 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs @@ -0,0 +1,216 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Graphics.Vulkan.Queries +{ + class BufferedQuery : IDisposable + { + private const int MaxQueryRetries = 5000; + private const long DefaultValue = unchecked((long)0xFFFFFFFEFFFFFFFE); + private const long DefaultValueInt = 0xFFFFFFFE; + private const ulong HighMask = 0xFFFFFFFF00000000; + + private readonly Vk _api; + private readonly Device _device; + private readonly PipelineFull _pipeline; + + private QueryPool _queryPool; + + private readonly BufferHolder _buffer; + private readonly IntPtr _bufferMap; + private readonly CounterType _type; + private readonly bool _result32Bit; + private readonly bool _isSupported; + + private readonly long _defaultValue; + private int? _resetSequence; + + public unsafe BufferedQuery(VulkanRenderer gd, Device device, PipelineFull pipeline, CounterType type, bool result32Bit) + { + _api = gd.Api; + _device = device; + _pipeline = pipeline; + _type = type; + _result32Bit = result32Bit; + + _isSupported = QueryTypeSupported(gd, type); + + if (_isSupported) + { + QueryPipelineStatisticFlags flags = type == CounterType.PrimitivesGenerated ? + QueryPipelineStatisticFlags.GeometryShaderPrimitivesBit : 0; + + var queryPoolCreateInfo = new QueryPoolCreateInfo + { + SType = StructureType.QueryPoolCreateInfo, + QueryCount = 1, + QueryType = GetQueryType(type), + PipelineStatistics = flags, + }; + + gd.Api.CreateQueryPool(device, in queryPoolCreateInfo, null, out _queryPool).ThrowOnError(); + } + + var buffer = gd.BufferManager.Create(gd, sizeof(long), forConditionalRendering: true); + + _bufferMap = buffer.Map(0, sizeof(long)); + _defaultValue = result32Bit ? DefaultValueInt : DefaultValue; + Marshal.WriteInt64(_bufferMap, _defaultValue); + _buffer = buffer; + } + + private static bool QueryTypeSupported(VulkanRenderer gd, CounterType type) + { + return type switch + { + CounterType.SamplesPassed => true, + CounterType.PrimitivesGenerated => gd.Capabilities.SupportsPipelineStatisticsQuery, + CounterType.TransformFeedbackPrimitivesWritten => gd.Capabilities.SupportsTransformFeedbackQueries, + _ => false, + }; + } + + private static QueryType GetQueryType(CounterType type) + { + return type switch + { + CounterType.SamplesPassed => QueryType.Occlusion, + CounterType.PrimitivesGenerated => QueryType.PipelineStatistics, + CounterType.TransformFeedbackPrimitivesWritten => QueryType.TransformFeedbackStreamExt, + _ => QueryType.Occlusion, + }; + } + + public Auto GetBuffer() + { + return _buffer.GetBuffer(); + } + + public void Reset() + { + End(false); + Begin(null); + } + + public void Begin(int? resetSequence) + { + if (_isSupported) + { + bool needsReset = resetSequence == null || _resetSequence == null || resetSequence.Value != _resetSequence.Value; + bool isOcclusion = _type == CounterType.SamplesPassed; + _pipeline.BeginQuery(this, _queryPool, needsReset, isOcclusion, isOcclusion && resetSequence != null); + } + _resetSequence = null; + } + + public void End(bool withResult) + { + if (_isSupported) + { + _pipeline.EndQuery(_queryPool); + } + + if (withResult && _isSupported) + { + Marshal.WriteInt64(_bufferMap, _defaultValue); + _pipeline.CopyQueryResults(this); + } + else + { + // Dummy result, just return 0. + Marshal.WriteInt64(_bufferMap, 0); + } + } + + private bool WaitingForValue(long data) + { + return data == _defaultValue || + (!_result32Bit && ((ulong)data & HighMask) == ((ulong)_defaultValue & HighMask)); + } + + public bool TryGetResult(out long result) + { + result = Marshal.ReadInt64(_bufferMap); + + return result != _defaultValue; + } + + public long AwaitResult(AutoResetEvent wakeSignal = null) + { + long data = _defaultValue; + + if (wakeSignal == null) + { + while (WaitingForValue(data)) + { + data = Marshal.ReadInt64(_bufferMap); + } + } + else + { + int iterations = 0; + while (WaitingForValue(data) && iterations++ < MaxQueryRetries) + { + data = Marshal.ReadInt64(_bufferMap); + if (WaitingForValue(data)) + { + wakeSignal.WaitOne(1); + } + } + + if (iterations >= MaxQueryRetries) + { + Logger.Error?.Print(LogClass.Gpu, $"Error: Query result {_type} timed out. Took more than {MaxQueryRetries} tries."); + } + } + + return data; + } + + public void PoolReset(CommandBuffer cmd, int resetSequence) + { + if (_isSupported) + { + _api.CmdResetQueryPool(cmd, _queryPool, 0, 1); + } + + _resetSequence = resetSequence; + } + + public void PoolCopy(CommandBufferScoped cbs) + { + var buffer = _buffer.GetBuffer(cbs.CommandBuffer, true).Get(cbs, 0, sizeof(long), true).Value; + + QueryResultFlags flags = QueryResultFlags.ResultWaitBit; + + if (!_result32Bit) + { + flags |= QueryResultFlags.Result64Bit; + } + + _api.CmdCopyQueryPoolResults( + cbs.CommandBuffer, + _queryPool, + 0, + 1, + buffer, + 0, + (ulong)(_result32Bit ? sizeof(int) : sizeof(long)), + flags); + } + + public unsafe void Dispose() + { + _buffer.Dispose(); + if (_isSupported) + { + _api.DestroyQueryPool(_device, _queryPool, null); + } + _queryPool = default; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs b/src/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs new file mode 100644 index 00000000..0d133e50 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs @@ -0,0 +1,252 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Ryujinx.Graphics.Vulkan.Queries +{ + class CounterQueue : IDisposable + { + private const int QueryPoolInitialSize = 100; + + private readonly VulkanRenderer _gd; + private readonly Device _device; + private readonly PipelineFull _pipeline; + + public CounterType Type { get; } + public bool Disposed { get; private set; } + + private readonly Queue _events = new(); + private CounterQueueEvent _current; + + private ulong _accumulatedCounter; + private int _waiterCount; + + private readonly object _lock = new(); + + private readonly Queue _queryPool; + private readonly AutoResetEvent _queuedEvent = new(false); + private readonly AutoResetEvent _wakeSignal = new(false); + private readonly AutoResetEvent _eventConsumed = new(false); + + private readonly Thread _consumerThread; + + public int ResetSequence { get; private set; } + + internal CounterQueue(VulkanRenderer gd, Device device, PipelineFull pipeline, CounterType type) + { + _gd = gd; + _device = device; + _pipeline = pipeline; + + Type = type; + + _queryPool = new Queue(QueryPoolInitialSize); + for (int i = 0; i < QueryPoolInitialSize; i++) + { + // AMD Polaris GPUs on Windows seem to have issues reporting 64-bit query results. + _queryPool.Enqueue(new BufferedQuery(_gd, _device, _pipeline, type, gd.IsAmdWindows)); + } + + _current = new CounterQueueEvent(this, type, 0); + + _consumerThread = new Thread(EventConsumer); + _consumerThread.Start(); + } + + public void ResetCounterPool() + { + ResetSequence++; + } + + public void ResetFutureCounters(CommandBuffer cmd, int count) + { + // Pre-emptively reset queries to avoid render pass splitting. + lock (_queryPool) + { + count = Math.Min(count, _queryPool.Count); + + if (count > 0) + { + foreach (BufferedQuery query in _queryPool) + { + query.PoolReset(cmd, ResetSequence); + + if (--count == 0) + { + break; + } + } + } + } + } + + private void EventConsumer() + { + while (!Disposed) + { + CounterQueueEvent evt = null; + lock (_lock) + { + if (_events.Count > 0) + { + evt = _events.Dequeue(); + } + } + + if (evt == null) + { + _queuedEvent.WaitOne(); // No more events to go through, wait for more. + } + else + { + // Spin-wait rather than sleeping if there are any waiters, by passing null instead of the wake signal. + evt.TryConsume(ref _accumulatedCounter, true, _waiterCount == 0 ? _wakeSignal : null); + } + + if (_waiterCount > 0) + { + _eventConsumed.Set(); + } + } + } + + internal BufferedQuery GetQueryObject() + { + // Creating/disposing query objects on a context we're sharing with will cause issues. + // So instead, make a lot of query objects on the main thread and reuse them. + + lock (_lock) + { + if (_queryPool.Count > 0) + { + BufferedQuery result = _queryPool.Dequeue(); + return result; + } + + return new BufferedQuery(_gd, _device, _pipeline, Type, _gd.IsAmdWindows); + } + } + + internal void ReturnQueryObject(BufferedQuery query) + { + lock (_lock) + { + // The query will be reset when it dequeues. + _queryPool.Enqueue(query); + } + } + + public CounterQueueEvent QueueReport(EventHandler resultHandler, float divisor, ulong lastDrawIndex, bool hostReserved) + { + CounterQueueEvent result; + ulong draws = lastDrawIndex - _current.DrawIndex; + + lock (_lock) + { + // A query's result only matters if more than one draw was performed during it. + // Otherwise, dummy it out and return 0 immediately. + + if (hostReserved) + { + // This counter event is guaranteed to be available for host conditional rendering. + _current.ReserveForHostAccess(); + } + + _current.Complete(draws > 0 && Type != CounterType.TransformFeedbackPrimitivesWritten, divisor); + _events.Enqueue(_current); + + _current.OnResult += resultHandler; + + result = _current; + + _current = new CounterQueueEvent(this, Type, lastDrawIndex); + } + + _queuedEvent.Set(); + + return result; + } + + public void QueueReset(ulong lastDrawIndex) + { + ulong draws = lastDrawIndex - _current.DrawIndex; + + lock (_lock) + { + _current.Clear(draws != 0); + } + } + + public void Flush(bool blocking) + { + if (!blocking) + { + // Just wake the consumer thread - it will update the queries. + _wakeSignal.Set(); + return; + } + + lock (_lock) + { + // Tell the queue to process all events. + while (_events.Count > 0) + { + CounterQueueEvent flush = _events.Peek(); + if (!flush.TryConsume(ref _accumulatedCounter, true)) + { + return; // If not blocking, then return when we encounter an event that is not ready yet. + } + _events.Dequeue(); + } + } + } + + public void FlushTo(CounterQueueEvent evt) + { + // Flush the counter queue on the main thread. + Interlocked.Increment(ref _waiterCount); + + _wakeSignal.Set(); + + while (!evt.Disposed) + { + _eventConsumed.WaitOne(1); + } + + Interlocked.Decrement(ref _waiterCount); + } + + public void Dispose() + { + lock (_lock) + { + while (_events.Count > 0) + { + CounterQueueEvent evt = _events.Dequeue(); + + evt.Dispose(); + } + + Disposed = true; + } + + _queuedEvent.Set(); + + _consumerThread.Join(); + + _current?.Dispose(); + + foreach (BufferedQuery query in _queryPool) + { + query.Dispose(); + } + + _queuedEvent.Dispose(); + _wakeSignal.Dispose(); + _eventConsumed.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Queries/CounterQueueEvent.cs b/src/Ryujinx.Graphics.Vulkan/Queries/CounterQueueEvent.cs new file mode 100644 index 00000000..14d3050b --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Queries/CounterQueueEvent.cs @@ -0,0 +1,170 @@ +using Ryujinx.Graphics.GAL; +using System; +using System.Threading; + +namespace Ryujinx.Graphics.Vulkan.Queries +{ + class CounterQueueEvent : ICounterEvent + { + public event EventHandler OnResult; + + public CounterType Type { get; } + public bool ClearCounter { get; private set; } + + public bool Disposed { get; private set; } + public bool Invalid { get; set; } + + public ulong DrawIndex { get; } + + private readonly CounterQueue _queue; + private readonly BufferedQuery _counter; + + private bool _hostAccessReserved; + private int _refCount = 1; // Starts with a reference from the counter queue. + + private readonly object _lock = new(); + private ulong _result = ulong.MaxValue; + private double _divisor = 1f; + + public CounterQueueEvent(CounterQueue queue, CounterType type, ulong drawIndex) + { + _queue = queue; + + _counter = queue.GetQueryObject(); + Type = type; + + DrawIndex = drawIndex; + + _counter.Begin(_queue.ResetSequence); + } + + public Auto GetBuffer() + { + return _counter.GetBuffer(); + } + + internal void Clear(bool counterReset) + { + if (counterReset) + { + _counter.Reset(); + } + + ClearCounter = true; + } + + internal void Complete(bool withResult, double divisor) + { + _counter.End(withResult); + + _divisor = divisor; + } + + internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null) + { + lock (_lock) + { + if (Disposed) + { + return true; + } + + if (ClearCounter) + { + result = 0; + } + + long queryResult; + + if (block) + { + queryResult = _counter.AwaitResult(wakeSignal); + } + else + { + if (!_counter.TryGetResult(out queryResult)) + { + return false; + } + } + + result += _divisor == 1 ? (ulong)queryResult : (ulong)Math.Ceiling(queryResult / _divisor); + + _result = result; + + OnResult?.Invoke(this, result); + + Dispose(); // Return the our resources to the pool. + + return true; + } + } + + public void Flush() + { + if (Disposed) + { + return; + } + + // Tell the queue to process all events up to this one. + _queue.FlushTo(this); + } + + public void DecrementRefCount() + { + if (Interlocked.Decrement(ref _refCount) == 0) + { + DisposeInternal(); + } + } + + public bool ReserveForHostAccess() + { + if (_hostAccessReserved) + { + return true; + } + + if (IsValueAvailable()) + { + return false; + } + + if (Interlocked.Increment(ref _refCount) == 1) + { + Interlocked.Decrement(ref _refCount); + + return false; + } + + _hostAccessReserved = true; + + return true; + } + + public void ReleaseHostAccess() + { + _hostAccessReserved = false; + + DecrementRefCount(); + } + + private void DisposeInternal() + { + _queue.ReturnQueryObject(_counter); + } + + private bool IsValueAvailable() + { + return _result != ulong.MaxValue || _counter.TryGetResult(out _); + } + + public void Dispose() + { + Disposed = true; + + DecrementRefCount(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Queries/Counters.cs b/src/Ryujinx.Graphics.Vulkan/Queries/Counters.cs new file mode 100644 index 00000000..518ede5f --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Queries/Counters.cs @@ -0,0 +1,71 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan.Queries +{ + class Counters : IDisposable + { + private readonly CounterQueue[] _counterQueues; + private readonly PipelineFull _pipeline; + + public Counters(VulkanRenderer gd, Device device, PipelineFull pipeline) + { + _pipeline = pipeline; + + int count = Enum.GetNames(typeof(CounterType)).Length; + + _counterQueues = new CounterQueue[count]; + + for (int index = 0; index < _counterQueues.Length; index++) + { + CounterType type = (CounterType)index; + _counterQueues[index] = new CounterQueue(gd, device, pipeline, type); + } + } + + public void ResetCounterPool() + { + foreach (var queue in _counterQueues) + { + queue.ResetCounterPool(); + } + } + + public void ResetFutureCounters(CommandBuffer cmd, int count) + { + _counterQueues[(int)CounterType.SamplesPassed].ResetFutureCounters(cmd, count); + } + + public CounterQueueEvent QueueReport(CounterType type, EventHandler resultHandler, float divisor, bool hostReserved) + { + return _counterQueues[(int)type].QueueReport(resultHandler, divisor, _pipeline.DrawCount, hostReserved); + } + + public void QueueReset(CounterType type) + { + _counterQueues[(int)type].QueueReset(_pipeline.DrawCount); + } + + public void Update() + { + foreach (var queue in _counterQueues) + { + queue.Flush(false); + } + } + + public void Flush(CounterType type) + { + _counterQueues[(int)type].Flush(true); + } + + public void Dispose() + { + foreach (var queue in _counterQueues) + { + queue.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/RenderPassCacheKey.cs b/src/Ryujinx.Graphics.Vulkan/RenderPassCacheKey.cs new file mode 100644 index 00000000..7c57b8fe --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/RenderPassCacheKey.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; + +namespace Ryujinx.Graphics.Vulkan +{ + internal readonly struct RenderPassCacheKey : IRefEquatable + { + private readonly TextureView _depthStencil; + private readonly TextureView[] _colors; + + public RenderPassCacheKey(TextureView depthStencil, TextureView[] colors) + { + _depthStencil = depthStencil; + _colors = colors; + } + + public override int GetHashCode() + { + HashCode hc = new(); + + hc.Add(_depthStencil); + + if (_colors != null) + { + foreach (var color in _colors) + { + hc.Add(color); + } + } + + return hc.ToHashCode(); + } + + public bool Equals(ref RenderPassCacheKey other) + { + bool colorsNull = _colors == null; + bool otherNull = other._colors == null; + return other._depthStencil == _depthStencil && + colorsNull == otherNull && + (colorsNull || other._colors.SequenceEqual(_colors)); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs new file mode 100644 index 00000000..a364c571 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs @@ -0,0 +1,221 @@ +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Vulkan +{ + internal class RenderPassHolder + { + private readonly struct FramebufferCacheKey : IRefEquatable + { + private readonly uint _width; + private readonly uint _height; + private readonly uint _layers; + + public FramebufferCacheKey(uint width, uint height, uint layers) + { + _width = width; + _height = height; + _layers = layers; + } + + public override int GetHashCode() + { + return HashCode.Combine(_width, _height, _layers); + } + + public bool Equals(ref FramebufferCacheKey other) + { + return other._width == _width && other._height == _height && other._layers == _layers; + } + } + + private readonly record struct ForcedFence(TextureStorage Texture, PipelineStageFlags StageFlags); + + private readonly TextureView[] _textures; + private readonly Auto _renderPass; + private readonly HashTableSlim> _framebuffers; + private readonly RenderPassCacheKey _key; + private readonly List _forcedFences; + + public unsafe RenderPassHolder(VulkanRenderer gd, Device device, RenderPassCacheKey key, FramebufferParams fb) + { + // Create render pass using framebuffer params. + + const int MaxAttachments = Constants.MaxRenderTargets + 1; + + AttachmentDescription[] attachmentDescs = null; + + var subpass = new SubpassDescription + { + PipelineBindPoint = PipelineBindPoint.Graphics, + }; + + AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments]; + + var hasFramebuffer = fb != null; + + if (hasFramebuffer && fb.AttachmentsCount != 0) + { + attachmentDescs = new AttachmentDescription[fb.AttachmentsCount]; + + for (int i = 0; i < fb.AttachmentsCount; i++) + { + attachmentDescs[i] = new AttachmentDescription( + 0, + fb.AttachmentFormats[i], + TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, fb.AttachmentSamples[i]), + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + ImageLayout.General, + ImageLayout.General); + } + + int colorAttachmentsCount = fb.ColorAttachmentsCount; + + if (colorAttachmentsCount > MaxAttachments - 1) + { + colorAttachmentsCount = MaxAttachments - 1; + } + + if (colorAttachmentsCount != 0) + { + int maxAttachmentIndex = fb.MaxColorAttachmentIndex; + subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1; + subpass.PColorAttachments = &attachmentReferences[0]; + + // Fill with VK_ATTACHMENT_UNUSED to cover any gaps. + for (int i = 0; i <= maxAttachmentIndex; i++) + { + subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined); + } + + for (int i = 0; i < colorAttachmentsCount; i++) + { + int bindIndex = fb.AttachmentIndices[i]; + + subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General); + } + } + + if (fb.HasDepthStencil) + { + uint dsIndex = (uint)fb.AttachmentsCount - 1; + + subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1]; + *subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General); + } + } + + var subpassDependency = PipelineConverter.CreateSubpassDependency(gd); + + fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs) + { + var renderPassCreateInfo = new RenderPassCreateInfo + { + SType = StructureType.RenderPassCreateInfo, + PAttachments = pAttachmentDescs, + AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0, + PSubpasses = &subpass, + SubpassCount = 1, + PDependencies = &subpassDependency, + DependencyCount = 1, + }; + + gd.Api.CreateRenderPass(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError(); + + _renderPass = new Auto(new DisposableRenderPass(gd.Api, device, renderPass)); + } + + _framebuffers = new HashTableSlim>(); + + // Register this render pass with all render target views. + + var textures = fb.GetAttachmentViews(); + + foreach (var texture in textures) + { + texture.AddRenderPass(key, this); + } + + _textures = textures; + _key = key; + + _forcedFences = new List(); + } + + public Auto GetFramebuffer(VulkanRenderer gd, CommandBufferScoped cbs, FramebufferParams fb) + { + var key = new FramebufferCacheKey(fb.Width, fb.Height, fb.Layers); + + if (!_framebuffers.TryGetValue(ref key, out Auto result)) + { + result = fb.Create(gd.Api, cbs, _renderPass); + + _framebuffers.Add(ref key, result); + } + + return result; + } + + public Auto GetRenderPass() + { + return _renderPass; + } + + public void AddForcedFence(TextureStorage storage, PipelineStageFlags stageFlags) + { + if (!_forcedFences.Any(fence => fence.Texture == storage)) + { + _forcedFences.Add(new ForcedFence(storage, stageFlags)); + } + } + + public void InsertForcedFences(CommandBufferScoped cbs) + { + if (_forcedFences.Count > 0) + { + _forcedFences.RemoveAll((entry) => + { + if (entry.Texture.Disposed) + { + return true; + } + + entry.Texture.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, entry.StageFlags); + + return false; + }); + } + } + + public bool ContainsAttachment(TextureStorage storage) + { + return _textures.Any(view => view.Storage == storage); + } + + public void Dispose() + { + // Dispose all framebuffers. + + foreach (var fb in _framebuffers.Values) + { + fb.Dispose(); + } + + // Notify all texture views that this render pass has been disposed. + + foreach (var texture in _textures) + { + texture.RemoveRenderPass(_key); + } + + // Dispose render pass. + + _renderPass.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/ResourceArray.cs b/src/Ryujinx.Graphics.Vulkan/ResourceArray.cs new file mode 100644 index 00000000..f96b4a84 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/ResourceArray.cs @@ -0,0 +1,81 @@ +using Silk.NET.Vulkan; +using System; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Vulkan +{ + class ResourceArray : IDisposable + { + private DescriptorSet[] _cachedDescriptorSets; + + private ShaderCollection _cachedDscProgram; + private int _cachedDscSetIndex; + private int _cachedDscIndex; + + private int _bindCount; + + protected void SetDirty(VulkanRenderer gd, bool isImage) + { + ReleaseDescriptorSet(); + + if (_bindCount != 0) + { + if (isImage) + { + gd.PipelineInternal.ForceImageDirty(); + } + else + { + gd.PipelineInternal.ForceTextureDirty(); + } + } + } + + public bool TryGetCachedDescriptorSets(CommandBufferScoped cbs, ShaderCollection program, int setIndex, out DescriptorSet[] sets) + { + if (_cachedDescriptorSets != null) + { + _cachedDscProgram.UpdateManualDescriptorSetCollectionOwnership(cbs, _cachedDscSetIndex, _cachedDscIndex); + + sets = _cachedDescriptorSets; + + return true; + } + + var dsc = program.GetNewManualDescriptorSetCollection(cbs, setIndex, out _cachedDscIndex).Get(cbs); + + sets = dsc.GetSets(); + + _cachedDescriptorSets = sets; + _cachedDscProgram = program; + _cachedDscSetIndex = setIndex; + + return false; + } + + public void IncrementBindCount() + { + _bindCount++; + } + + public void DecrementBindCount() + { + int newBindCount = --_bindCount; + Debug.Assert(newBindCount >= 0); + } + + private void ReleaseDescriptorSet() + { + if (_cachedDescriptorSets != null) + { + _cachedDscProgram.ReleaseManualDescriptorSetCollection(_cachedDscSetIndex, _cachedDscIndex); + _cachedDescriptorSets = null; + } + } + + public void Dispose() + { + ReleaseDescriptorSet(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs b/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs new file mode 100644 index 00000000..6e27da4a --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/ResourceBindingSegment.cs @@ -0,0 +1,22 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct ResourceBindingSegment + { + public readonly int Binding; + public readonly int Count; + public readonly ResourceType Type; + public readonly ResourceStages Stages; + public readonly bool IsArray; + + public ResourceBindingSegment(int binding, int count, ResourceType type, ResourceStages stages, bool isArray) + { + Binding = binding; + Count = count; + Type = type; + Stages = stages; + IsArray = isArray; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs b/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs new file mode 100644 index 00000000..730a0a2f --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/ResourceLayoutBuilder.cs @@ -0,0 +1,57 @@ +using Ryujinx.Graphics.GAL; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + class ResourceLayoutBuilder + { + private const int TotalSets = PipelineBase.DescriptorSetLayouts; + + private readonly List[] _resourceDescriptors; + private readonly List[] _resourceUsages; + + public ResourceLayoutBuilder() + { + _resourceDescriptors = new List[TotalSets]; + _resourceUsages = new List[TotalSets]; + + for (int index = 0; index < TotalSets; index++) + { + _resourceDescriptors[index] = new(); + _resourceUsages[index] = new(); + } + } + + public ResourceLayoutBuilder Add(ResourceStages stages, ResourceType type, int binding, bool write = false) + { + int setIndex = type switch + { + ResourceType.UniformBuffer => PipelineBase.UniformSetIndex, + ResourceType.StorageBuffer => PipelineBase.StorageSetIndex, + ResourceType.TextureAndSampler or ResourceType.BufferTexture => PipelineBase.TextureSetIndex, + ResourceType.Image or ResourceType.BufferImage => PipelineBase.ImageSetIndex, + _ => throw new ArgumentException($"Invalid resource type \"{type}\"."), + }; + + _resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages)); + _resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages, write)); + + return this; + } + + public ResourceLayout Build() + { + var descriptors = new ResourceDescriptorCollection[TotalSets]; + var usages = new ResourceUsageCollection[TotalSets]; + + for (int index = 0; index < TotalSets; index++) + { + descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly()); + usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly()); + } + + return new ResourceLayout(descriptors.AsReadOnly(), usages.AsReadOnly()); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj new file mode 100644 index 00000000..f6a7be91 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj @@ -0,0 +1,65 @@ + + + + net8.0 + + + + true + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.Graphics.Vulkan/SamplerHolder.cs b/src/Ryujinx.Graphics.Vulkan/SamplerHolder.cs new file mode 100644 index 00000000..7f37ab13 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/SamplerHolder.cs @@ -0,0 +1,120 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo; + +namespace Ryujinx.Graphics.Vulkan +{ + class SamplerHolder : ISampler + { + private readonly VulkanRenderer _gd; + private readonly Auto _sampler; + + public unsafe SamplerHolder(VulkanRenderer gd, Device device, SamplerCreateInfo info) + { + _gd = gd; + + gd.Samplers.Add(this); + + (Filter minFilter, SamplerMipmapMode mipFilter) = info.MinFilter.Convert(); + + float minLod = info.MinLod; + float maxLod = info.MaxLod; + + if (info.MinFilter == MinFilter.Nearest || info.MinFilter == MinFilter.Linear) + { + minLod = 0; + maxLod = 0.25f; + } + + var borderColor = GetConstrainedBorderColor(info.BorderColor, out var cantConstrain); + + var samplerCreateInfo = new Silk.NET.Vulkan.SamplerCreateInfo + { + SType = StructureType.SamplerCreateInfo, + MagFilter = info.MagFilter.Convert(), + MinFilter = minFilter, + MipmapMode = mipFilter, + AddressModeU = info.AddressU.Convert(), + AddressModeV = info.AddressV.Convert(), + AddressModeW = info.AddressP.Convert(), + MipLodBias = info.MipLodBias, + AnisotropyEnable = info.MaxAnisotropy != 1f, + MaxAnisotropy = info.MaxAnisotropy, + CompareEnable = info.CompareMode == CompareMode.CompareRToTexture, + CompareOp = info.CompareOp.Convert(), + MinLod = minLod, + MaxLod = maxLod, + BorderColor = borderColor, + UnnormalizedCoordinates = false, // TODO: Use unnormalized coordinates. + }; + + SamplerCustomBorderColorCreateInfoEXT customBorderColor; + + if (cantConstrain && gd.Capabilities.SupportsCustomBorderColor) + { + var color = new ClearColorValue( + info.BorderColor.Red, + info.BorderColor.Green, + info.BorderColor.Blue, + info.BorderColor.Alpha); + + customBorderColor = new SamplerCustomBorderColorCreateInfoEXT + { + SType = StructureType.SamplerCustomBorderColorCreateInfoExt, + CustomBorderColor = color, + }; + + samplerCreateInfo.PNext = &customBorderColor; + samplerCreateInfo.BorderColor = BorderColor.FloatCustomExt; + } + + gd.Api.CreateSampler(device, in samplerCreateInfo, null, out var sampler).ThrowOnError(); + + _sampler = new Auto(new DisposableSampler(gd.Api, device, sampler)); + } + + private static BorderColor GetConstrainedBorderColor(ColorF arbitraryBorderColor, out bool cantConstrain) + { + float r = arbitraryBorderColor.Red; + float g = arbitraryBorderColor.Green; + float b = arbitraryBorderColor.Blue; + float a = arbitraryBorderColor.Alpha; + + if (r == 0f && g == 0f && b == 0f) + { + if (a == 1f) + { + cantConstrain = false; + return BorderColor.FloatOpaqueBlack; + } + + if (a == 0f) + { + cantConstrain = false; + return BorderColor.FloatTransparentBlack; + } + } + else if (r == 1f && g == 1f && b == 1f && a == 1f) + { + cantConstrain = false; + return BorderColor.FloatOpaqueWhite; + } + + cantConstrain = true; + return BorderColor.FloatOpaqueBlack; + } + + public Auto GetSampler() + { + return _sampler; + } + + public void Dispose() + { + if (_gd.Samplers.Remove(this)) + { + _sampler.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Shader.cs b/src/Ryujinx.Graphics.Vulkan/Shader.cs new file mode 100644 index 00000000..1c8caffd --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shader.cs @@ -0,0 +1,160 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using shaderc; +using Silk.NET.Vulkan; +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Ryujinx.Graphics.Vulkan +{ + class Shader : IDisposable + { + // The shaderc.net dependency's Options constructor and dispose are not thread safe. + // Take this lock when using them. + private static readonly object _shaderOptionsLock = new(); + + private static readonly IntPtr _ptrMainEntryPointName = Marshal.StringToHGlobalAnsi("main"); + + private readonly Vk _api; + private readonly Device _device; + private readonly ShaderStageFlags _stage; + + private bool _disposed; + private ShaderModule _module; + + public ShaderStageFlags StageFlags => _stage; + + public ProgramLinkStatus CompileStatus { private set; get; } + + public readonly Task CompileTask; + + public unsafe Shader(Vk api, Device device, ShaderSource shaderSource) + { + _api = api; + _device = device; + + CompileStatus = ProgramLinkStatus.Incomplete; + + _stage = shaderSource.Stage.Convert(); + + CompileTask = Task.Run(() => + { + byte[] spirv = shaderSource.BinaryCode; + + if (spirv == null) + { + spirv = GlslToSpirv(shaderSource.Code, shaderSource.Stage); + + if (spirv == null) + { + CompileStatus = ProgramLinkStatus.Failure; + + return; + } + } + + fixed (byte* pCode = spirv) + { + var shaderModuleCreateInfo = new ShaderModuleCreateInfo + { + SType = StructureType.ShaderModuleCreateInfo, + CodeSize = (uint)spirv.Length, + PCode = (uint*)pCode, + }; + + api.CreateShaderModule(device, in shaderModuleCreateInfo, null, out _module).ThrowOnError(); + } + + CompileStatus = ProgramLinkStatus.Success; + }); + } + + private unsafe static byte[] GlslToSpirv(string glsl, ShaderStage stage) + { + Options options; + + lock (_shaderOptionsLock) + { + options = new Options(false) + { + SourceLanguage = SourceLanguage.Glsl, + TargetSpirVVersion = new SpirVVersion(1, 5), + }; + } + + options.SetTargetEnvironment(TargetEnvironment.Vulkan, EnvironmentVersion.Vulkan_1_2); + Compiler compiler = new(options); + var scr = compiler.Compile(glsl, "Ryu", GetShaderCShaderStage(stage)); + + lock (_shaderOptionsLock) + { + options.Dispose(); + } + + if (scr.Status != Status.Success) + { + Logger.Error?.Print(LogClass.Gpu, $"Shader compilation error: {scr.Status} {scr.ErrorMessage}"); + + return null; + } + + var spirvBytes = new Span((void*)scr.CodePointer, (int)scr.CodeLength); + + byte[] code = new byte[(scr.CodeLength + 3) & ~3]; + + spirvBytes.CopyTo(code.AsSpan()[..(int)scr.CodeLength]); + + return code; + } + + private static ShaderKind GetShaderCShaderStage(ShaderStage stage) + { + switch (stage) + { + case ShaderStage.Vertex: + return ShaderKind.GlslVertexShader; + case ShaderStage.Geometry: + return ShaderKind.GlslGeometryShader; + case ShaderStage.TessellationControl: + return ShaderKind.GlslTessControlShader; + case ShaderStage.TessellationEvaluation: + return ShaderKind.GlslTessEvaluationShader; + case ShaderStage.Fragment: + return ShaderKind.GlslFragmentShader; + case ShaderStage.Compute: + return ShaderKind.GlslComputeShader; + } + + Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(ShaderStage)} enum value: {stage}."); + + return ShaderKind.GlslVertexShader; + } + + public unsafe PipelineShaderStageCreateInfo GetInfo() + { + return new PipelineShaderStageCreateInfo + { + SType = StructureType.PipelineShaderStageCreateInfo, + Stage = _stage, + Module = _module, + PName = (byte*)_ptrMainEntryPointName, + }; + } + + public void WaitForCompile() + { + CompileTask.Wait(); + } + + public unsafe void Dispose() + { + if (!_disposed) + { + _api.DestroyShaderModule(_device, _module, null); + _disposed = true; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs new file mode 100644 index 00000000..c9aab401 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -0,0 +1,757 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; + +namespace Ryujinx.Graphics.Vulkan +{ + class ShaderCollection : IProgram + { + private readonly PipelineShaderStageCreateInfo[] _infos; + private readonly Shader[] _shaders; + + private readonly PipelineLayoutCacheEntry _plce; + + public PipelineLayout PipelineLayout => _plce.PipelineLayout; + + public bool HasMinimalLayout { get; } + public bool UsePushDescriptors { get; } + public bool IsCompute { get; } + public bool HasTessellationControlShader => (Stages & (1u << 3)) != 0; + + public bool UpdateTexturesWithoutTemplate { get; } + + public uint Stages { get; } + + public PipelineStageFlags IncoherentBufferWriteStages { get; } + public PipelineStageFlags IncoherentTextureWriteStages { get; } + + public ResourceBindingSegment[][] ClearSegments { get; } + public ResourceBindingSegment[][] BindingSegments { get; } + public DescriptorSetTemplate[] Templates { get; } + + public ProgramLinkStatus LinkStatus { get; private set; } + + public readonly SpecDescription[] SpecDescriptions; + + public bool IsLinked + { + get + { + if (LinkStatus == ProgramLinkStatus.Incomplete) + { + CheckProgramLink(true); + } + + return LinkStatus == ProgramLinkStatus.Success; + } + } + + private HashTableSlim> _graphicsPipelineCache; + private HashTableSlim> _computePipelineCache; + + private readonly VulkanRenderer _gd; + private Device _device; + private bool _initialized; + + private ProgramPipelineState _state; + private DisposableRenderPass _dummyRenderPass; + private readonly Task _compileTask; + private bool _firstBackgroundUse; + + public ShaderCollection( + VulkanRenderer gd, + Device device, + ShaderSource[] shaders, + ResourceLayout resourceLayout, + SpecDescription[] specDescription = null, + bool isMinimal = false) + { + _gd = gd; + _device = device; + + if (specDescription != null && specDescription.Length != shaders.Length) + { + throw new ArgumentException($"{nameof(specDescription)} array length must match {nameof(shaders)} array if provided"); + } + + gd.Shaders.Add(this); + + var internalShaders = new Shader[shaders.Length]; + + _infos = new PipelineShaderStageCreateInfo[shaders.Length]; + + SpecDescriptions = specDescription; + + LinkStatus = ProgramLinkStatus.Incomplete; + + uint stages = 0; + + for (int i = 0; i < shaders.Length; i++) + { + var shader = new Shader(gd.Api, device, shaders[i]); + + stages |= 1u << shader.StageFlags switch + { + ShaderStageFlags.FragmentBit => 1, + ShaderStageFlags.GeometryBit => 2, + ShaderStageFlags.TessellationControlBit => 3, + ShaderStageFlags.TessellationEvaluationBit => 4, + _ => 0, + }; + + if (shader.StageFlags == ShaderStageFlags.ComputeBit) + { + IsCompute = true; + } + + internalShaders[i] = shader; + } + + _shaders = internalShaders; + + bool usePushDescriptors = !isMinimal && + VulkanConfiguration.UsePushDescriptors && + _gd.Capabilities.SupportsPushDescriptors && + !IsCompute && + !HasPushDescriptorsBug(gd) && + CanUsePushDescriptors(gd, resourceLayout, IsCompute); + + ReadOnlyCollection sets = usePushDescriptors ? + BuildPushDescriptorSets(gd, resourceLayout.Sets) : resourceLayout.Sets; + + _plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, sets, usePushDescriptors); + + HasMinimalLayout = isMinimal; + UsePushDescriptors = usePushDescriptors; + + Stages = stages; + + ClearSegments = BuildClearSegments(sets); + BindingSegments = BuildBindingSegments(resourceLayout.SetUsages, out bool usesBufferTextures); + Templates = BuildTemplates(usePushDescriptors); + (IncoherentBufferWriteStages, IncoherentTextureWriteStages) = BuildIncoherentStages(resourceLayout.SetUsages); + + // Updating buffer texture bindings using template updates crashes the Adreno driver on Windows. + UpdateTexturesWithoutTemplate = gd.IsQualcommProprietary && usesBufferTextures; + + _compileTask = Task.CompletedTask; + _firstBackgroundUse = false; + } + + public ShaderCollection( + VulkanRenderer gd, + Device device, + ShaderSource[] sources, + ResourceLayout resourceLayout, + ProgramPipelineState state, + bool fromCache) : this(gd, device, sources, resourceLayout) + { + _state = state; + + _compileTask = BackgroundCompilation(); + _firstBackgroundUse = !fromCache; + } + + private static bool HasPushDescriptorsBug(VulkanRenderer gd) + { + // Those GPUs/drivers do not work properly with push descriptors, so we must force disable them. + return gd.IsNvidiaPreTuring || (gd.IsIntelArc && gd.IsIntelWindows); + } + + private static bool CanUsePushDescriptors(VulkanRenderer gd, ResourceLayout layout, bool isCompute) + { + // If binding 3 is immediately used, use an alternate set of reserved bindings. + ReadOnlyCollection uniformUsage = layout.SetUsages[0].Usages; + bool hasBinding3 = uniformUsage.Any(x => x.Binding == 3); + int[] reserved = isCompute ? Array.Empty() : gd.GetPushDescriptorReservedBindings(hasBinding3); + + // Can't use any of the reserved usages. + for (int i = 0; i < uniformUsage.Count; i++) + { + var binding = uniformUsage[i].Binding; + + if (reserved.Contains(binding) || + binding >= Constants.MaxPushDescriptorBinding || + binding >= gd.Capabilities.MaxPushDescriptors + reserved.Count(id => id < binding)) + { + return false; + } + } + + return true; + } + + private static ReadOnlyCollection BuildPushDescriptorSets( + VulkanRenderer gd, + ReadOnlyCollection sets) + { + // The reserved bindings were selected when determining if push descriptors could be used. + int[] reserved = gd.GetPushDescriptorReservedBindings(false); + + var result = new ResourceDescriptorCollection[sets.Count]; + + for (int i = 0; i < sets.Count; i++) + { + if (i == 0) + { + // Push descriptors apply here. Remove reserved bindings. + ResourceDescriptorCollection original = sets[i]; + + var pdUniforms = new ResourceDescriptor[original.Descriptors.Count]; + int j = 0; + + foreach (ResourceDescriptor descriptor in original.Descriptors) + { + if (reserved.Contains(descriptor.Binding)) + { + // If the binding is reserved, set its descriptor count to 0. + pdUniforms[j++] = new ResourceDescriptor( + descriptor.Binding, + 0, + descriptor.Type, + descriptor.Stages); + } + else + { + pdUniforms[j++] = descriptor; + } + } + + result[i] = new ResourceDescriptorCollection(new(pdUniforms)); + } + else + { + result[i] = sets[i]; + } + } + + return new(result); + } + + private static ResourceBindingSegment[][] BuildClearSegments(ReadOnlyCollection sets) + { + ResourceBindingSegment[][] segments = new ResourceBindingSegment[sets.Count][]; + + for (int setIndex = 0; setIndex < sets.Count; setIndex++) + { + List currentSegments = new(); + + ResourceDescriptor currentDescriptor = default; + int currentCount = 0; + + for (int index = 0; index < sets[setIndex].Descriptors.Count; index++) + { + ResourceDescriptor descriptor = sets[setIndex].Descriptors[index]; + + if (currentDescriptor.Binding + currentCount != descriptor.Binding || + currentDescriptor.Type != descriptor.Type || + currentDescriptor.Stages != descriptor.Stages || + currentDescriptor.Count > 1 || + descriptor.Count > 1) + { + if (currentCount != 0) + { + currentSegments.Add(new ResourceBindingSegment( + currentDescriptor.Binding, + currentCount, + currentDescriptor.Type, + currentDescriptor.Stages, + currentDescriptor.Count > 1)); + } + + currentDescriptor = descriptor; + currentCount = descriptor.Count; + } + else + { + currentCount += descriptor.Count; + } + } + + if (currentCount != 0) + { + currentSegments.Add(new ResourceBindingSegment( + currentDescriptor.Binding, + currentCount, + currentDescriptor.Type, + currentDescriptor.Stages, + currentDescriptor.Count > 1)); + } + + segments[setIndex] = currentSegments.ToArray(); + } + + return segments; + } + + private static ResourceBindingSegment[][] BuildBindingSegments(ReadOnlyCollection setUsages, out bool usesBufferTextures) + { + usesBufferTextures = false; + + ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][]; + + for (int setIndex = 0; setIndex < setUsages.Count; setIndex++) + { + List currentSegments = new(); + + ResourceUsage currentUsage = default; + int currentCount = 0; + + for (int index = 0; index < setUsages[setIndex].Usages.Count; index++) + { + ResourceUsage usage = setUsages[setIndex].Usages[index]; + + if (usage.Type == ResourceType.BufferTexture) + { + usesBufferTextures = true; + } + + if (currentUsage.Binding + currentCount != usage.Binding || + currentUsage.Type != usage.Type || + currentUsage.Stages != usage.Stages || + currentUsage.ArrayLength > 1 || + usage.ArrayLength > 1) + { + if (currentCount != 0) + { + currentSegments.Add(new ResourceBindingSegment( + currentUsage.Binding, + currentCount, + currentUsage.Type, + currentUsage.Stages, + currentUsage.ArrayLength > 1)); + } + + currentUsage = usage; + currentCount = usage.ArrayLength; + } + else + { + currentCount++; + } + } + + if (currentCount != 0) + { + currentSegments.Add(new ResourceBindingSegment( + currentUsage.Binding, + currentCount, + currentUsage.Type, + currentUsage.Stages, + currentUsage.ArrayLength > 1)); + } + + segments[setIndex] = currentSegments.ToArray(); + } + + return segments; + } + + private DescriptorSetTemplate[] BuildTemplates(bool usePushDescriptors) + { + var templates = new DescriptorSetTemplate[BindingSegments.Length]; + + for (int setIndex = 0; setIndex < BindingSegments.Length; setIndex++) + { + if (usePushDescriptors && setIndex == 0) + { + // Push descriptors get updated using templates owned by the pipeline layout. + continue; + } + + ResourceBindingSegment[] segments = BindingSegments[setIndex]; + + if (segments != null && segments.Length > 0) + { + templates[setIndex] = new DescriptorSetTemplate( + _gd, + _device, + segments, + _plce, + IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, + setIndex); + } + } + + return templates; + } + + private PipelineStageFlags GetPipelineStages(ResourceStages stages) + { + PipelineStageFlags result = 0; + + if ((stages & ResourceStages.Compute) != 0) + { + result |= PipelineStageFlags.ComputeShaderBit; + } + + if ((stages & ResourceStages.Vertex) != 0) + { + result |= PipelineStageFlags.VertexShaderBit; + } + + if ((stages & ResourceStages.Fragment) != 0) + { + result |= PipelineStageFlags.FragmentShaderBit; + } + + if ((stages & ResourceStages.Geometry) != 0) + { + result |= PipelineStageFlags.GeometryShaderBit; + } + + if ((stages & ResourceStages.TessellationControl) != 0) + { + result |= PipelineStageFlags.TessellationControlShaderBit; + } + + if ((stages & ResourceStages.TessellationEvaluation) != 0) + { + result |= PipelineStageFlags.TessellationEvaluationShaderBit; + } + + return result; + } + + private (PipelineStageFlags Buffer, PipelineStageFlags Texture) BuildIncoherentStages(ReadOnlyCollection setUsages) + { + PipelineStageFlags buffer = PipelineStageFlags.None; + PipelineStageFlags texture = PipelineStageFlags.None; + + foreach (var set in setUsages) + { + foreach (var range in set.Usages) + { + if (range.Write) + { + PipelineStageFlags stages = GetPipelineStages(range.Stages); + + switch (range.Type) + { + case ResourceType.Image: + texture |= stages; + break; + case ResourceType.StorageBuffer: + case ResourceType.BufferImage: + buffer |= stages; + break; + } + } + } + } + + return (buffer, texture); + } + + private async Task BackgroundCompilation() + { + await Task.WhenAll(_shaders.Select(shader => shader.CompileTask)); + + if (Array.Exists(_shaders, shader => shader.CompileStatus == ProgramLinkStatus.Failure)) + { + LinkStatus = ProgramLinkStatus.Failure; + + return; + } + + try + { + if (IsCompute) + { + CreateBackgroundComputePipeline(); + } + else + { + CreateBackgroundGraphicsPipeline(); + } + } + catch (VulkanException e) + { + Logger.Error?.PrintMsg(LogClass.Gpu, $"Background Compilation failed: {e.Message}"); + + LinkStatus = ProgramLinkStatus.Failure; + } + } + + private void EnsureShadersReady() + { + if (!_initialized) + { + CheckProgramLink(true); + + ProgramLinkStatus resultStatus = ProgramLinkStatus.Success; + + for (int i = 0; i < _shaders.Length; i++) + { + var shader = _shaders[i]; + + if (shader.CompileStatus != ProgramLinkStatus.Success) + { + resultStatus = ProgramLinkStatus.Failure; + } + + _infos[i] = shader.GetInfo(); + } + + // If the link status was already set as failure by background compilation, prefer that decision. + if (LinkStatus != ProgramLinkStatus.Failure) + { + LinkStatus = resultStatus; + } + + _initialized = true; + } + } + + public PipelineShaderStageCreateInfo[] GetInfos() + { + EnsureShadersReady(); + + return _infos; + } + + protected DisposableRenderPass CreateDummyRenderPass() + { + if (_dummyRenderPass.Value.Handle != 0) + { + return _dummyRenderPass; + } + + return _dummyRenderPass = _state.ToRenderPass(_gd, _device); + } + + public void CreateBackgroundComputePipeline() + { + PipelineState pipeline = new(); + pipeline.Initialize(); + + pipeline.Stages[0] = _shaders[0].GetInfo(); + pipeline.StagesCount = 1; + pipeline.PipelineLayout = PipelineLayout; + + pipeline.CreateComputePipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache); + pipeline.Dispose(); + } + + public void CreateBackgroundGraphicsPipeline() + { + // To compile shaders in the background in Vulkan, we need to create valid pipelines using the shader modules. + // The GPU provides pipeline state via the GAL that can be converted into our internal Vulkan pipeline state. + // This should match the pipeline state at the time of the first draw. If it doesn't, then it'll likely be + // close enough that the GPU driver will reuse the compiled shader for the different state. + + // First, we need to create a render pass object compatible with the one that will be used at runtime. + // The active attachment formats have been provided by the abstraction layer. + var renderPass = CreateDummyRenderPass(); + + PipelineState pipeline = _state.ToVulkanPipelineState(_gd); + + // Copy the shader stage info to the pipeline. + var stages = pipeline.Stages.AsSpan(); + + for (int i = 0; i < _shaders.Length; i++) + { + stages[i] = _shaders[i].GetInfo(); + } + + pipeline.HasTessellationControlShader = HasTessellationControlShader; + pipeline.StagesCount = (uint)_shaders.Length; + pipeline.PipelineLayout = PipelineLayout; + + pipeline.CreateGraphicsPipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache, renderPass.Value, throwOnError: true); + pipeline.Dispose(); + } + + public ProgramLinkStatus CheckProgramLink(bool blocking) + { + if (LinkStatus == ProgramLinkStatus.Incomplete) + { + ProgramLinkStatus resultStatus = ProgramLinkStatus.Success; + + foreach (Shader shader in _shaders) + { + if (shader.CompileStatus == ProgramLinkStatus.Incomplete) + { + if (blocking) + { + // Wait for this shader to finish compiling. + shader.WaitForCompile(); + + if (shader.CompileStatus != ProgramLinkStatus.Success) + { + resultStatus = ProgramLinkStatus.Failure; + } + } + else + { + return ProgramLinkStatus.Incomplete; + } + } + } + + if (!_compileTask.IsCompleted) + { + if (blocking) + { + _compileTask.Wait(); + + if (LinkStatus == ProgramLinkStatus.Failure) + { + return ProgramLinkStatus.Failure; + } + } + else + { + return ProgramLinkStatus.Incomplete; + } + } + + return resultStatus; + } + + return LinkStatus; + } + + public byte[] GetBinary() + { + return null; + } + + public DescriptorSetTemplate GetPushDescriptorTemplate(long updateMask) + { + return _plce.GetPushDescriptorTemplate(IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, updateMask); + } + + public void AddComputePipeline(ref SpecData key, Auto pipeline) + { + (_computePipelineCache ??= new()).Add(ref key, pipeline); + } + + public void AddGraphicsPipeline(ref PipelineUid key, Auto pipeline) + { + (_graphicsPipelineCache ??= new()).Add(ref key, pipeline); + } + + public bool TryGetComputePipeline(ref SpecData key, out Auto pipeline) + { + if (_computePipelineCache == null) + { + pipeline = default; + return false; + } + + if (_computePipelineCache.TryGetValue(ref key, out pipeline)) + { + return true; + } + + return false; + } + + public bool TryGetGraphicsPipeline(ref PipelineUid key, out Auto pipeline) + { + if (_graphicsPipelineCache == null) + { + pipeline = default; + return false; + } + + if (!_graphicsPipelineCache.TryGetValue(ref key, out pipeline)) + { + if (_firstBackgroundUse) + { + Logger.Warning?.Print(LogClass.Gpu, "Background pipeline compile missed on draw - incorrect pipeline state?"); + _firstBackgroundUse = false; + } + + return false; + } + + _firstBackgroundUse = false; + + return true; + } + + public void UpdateDescriptorCacheCommandBufferIndex(int commandBufferIndex) + { + _plce.UpdateCommandBufferIndex(commandBufferIndex); + } + + public Auto GetNewDescriptorSetCollection(int setIndex, out bool isNew) + { + return _plce.GetNewDescriptorSetCollection(setIndex, out isNew); + } + + public Auto GetNewManualDescriptorSetCollection(CommandBufferScoped cbs, int setIndex, out int cacheIndex) + { + return _plce.GetNewManualDescriptorSetCollection(cbs, setIndex, out cacheIndex); + } + + public void UpdateManualDescriptorSetCollectionOwnership(CommandBufferScoped cbs, int setIndex, int cacheIndex) + { + _plce.UpdateManualDescriptorSetCollectionOwnership(cbs, setIndex, cacheIndex); + } + + public void ReleaseManualDescriptorSetCollection(int setIndex, int cacheIndex) + { + _plce.ReleaseManualDescriptorSetCollection(setIndex, cacheIndex); + } + + public bool HasSameLayout(ShaderCollection other) + { + return other != null && _plce == other._plce; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (!_gd.Shaders.Remove(this)) + { + return; + } + + for (int i = 0; i < _shaders.Length; i++) + { + _shaders[i].Dispose(); + } + + if (_graphicsPipelineCache != null) + { + foreach (Auto pipeline in _graphicsPipelineCache.Values) + { + pipeline?.Dispose(); + } + } + + if (_computePipelineCache != null) + { + foreach (Auto pipeline in _computePipelineCache.Values) + { + pipeline.Dispose(); + } + } + + for (int i = 0; i < Templates.Length; i++) + { + Templates[i]?.Dispose(); + } + + if (_dummyRenderPass.Value.Handle != 0) + { + _dummyRenderPass.Dispose(); + } + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ChangeBufferStrideShaderSource.comp b/src/Ryujinx.Graphics.Vulkan/Shaders/ChangeBufferStrideShaderSource.comp new file mode 100644 index 00000000..4deba30d --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ChangeBufferStrideShaderSource.comp @@ -0,0 +1,64 @@ +#version 450 core + +#extension GL_EXT_shader_8bit_storage : require + +layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in; + +layout (std140, set = 0, binding = 0) uniform stride_arguments +{ + ivec4 stride_arguments_data; +}; + +layout (std430, set = 1, binding = 1) buffer in_s +{ + uint8_t[] in_data; +}; + +layout (std430, set = 1, binding = 2) buffer out_s +{ + uint8_t[] out_data; +}; + +void main() +{ + // Determine what slice of the stride copies this invocation will perform. + + int sourceStride = stride_arguments_data.x; + int targetStride = stride_arguments_data.y; + int bufferSize = stride_arguments_data.z; + int sourceOffset = stride_arguments_data.w; + + int strideRemainder = targetStride - sourceStride; + int invocations = int(gl_WorkGroupSize.x * gl_NumWorkGroups.x); + + int copiesRequired = bufferSize / sourceStride; + + // Find the copies that this invocation should perform. + + // - Copies that all invocations perform. + int allInvocationCopies = copiesRequired / invocations; + + // - Extra remainder copy that this invocation performs. + int index = int(gl_GlobalInvocationID.x); + int extra = (index < (copiesRequired % invocations)) ? 1 : 0; + + int copyCount = allInvocationCopies + extra; + + // Finally, get the starting offset. Make sure to count extra copies. + + int startCopy = allInvocationCopies * index + min(copiesRequired % invocations, index); + + int srcOffset = sourceOffset + startCopy * sourceStride; + int dstOffset = startCopy * targetStride; + + // Perform the copies for this region + for (int i=0; i> 1; + tex_coord.x = tex_coord_in_data[low]; + tex_coord.y = tex_coord_in_data[2 + high]; + gl_Position.x = (float(low) - 0.5f) * 2.0f; + gl_Position.y = (float(high) - 0.5f) * 2.0f; + gl_Position.z = 0.0f; + gl_Position.w = 1.0f; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearFFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearFFragmentShaderSource.frag new file mode 100644 index 00000000..ddd4369c --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearFFragmentShaderSource.frag @@ -0,0 +1,9 @@ +#version 450 core + +layout (location = 0) in vec4 clear_colour; +layout (location = 0) out vec4 colour; + +void main() +{ + colour = clear_colour; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearSIFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearSIFragmentShaderSource.frag new file mode 100644 index 00000000..4254f4f8 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearSIFragmentShaderSource.frag @@ -0,0 +1,9 @@ +#version 450 core + +layout (location = 0) in vec4 clear_colour; +layout (location = 0) out ivec4 colour; + +void main() +{ + colour = floatBitsToInt(clear_colour); +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearUIFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearUIFragmentShaderSource.frag new file mode 100644 index 00000000..08a6b864 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearUIFragmentShaderSource.frag @@ -0,0 +1,9 @@ +#version 450 core + +layout (location = 0) in vec4 clear_colour; +layout (location = 0) out uvec4 colour; + +void main() +{ + colour = floatBitsToUint(clear_colour); +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearVertexShaderSource.vert b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearVertexShaderSource.vert new file mode 100644 index 00000000..2f1b9b2c --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearVertexShaderSource.vert @@ -0,0 +1,19 @@ +#version 450 core + +layout (std140, binding = 1) uniform clear_colour_in +{ + vec4 clear_colour_in_data; +}; + +layout (location = 0) out vec4 clear_colour; + +void main() +{ + int low = gl_VertexIndex & 1; + int high = gl_VertexIndex >> 1; + clear_colour = clear_colour_in_data; + gl_Position.x = (float(low) - 0.5f) * 2.0f; + gl_Position.y = (float(high) - 0.5f) * 2.0f; + gl_Position.z = 0.0f; + gl_Position.w = 1.0f; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyShorteningComputeShaderSource.comp b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyShorteningComputeShaderSource.comp new file mode 100644 index 00000000..78cc1cc6 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyShorteningComputeShaderSource.comp @@ -0,0 +1,36 @@ +#version 450 core + +layout (std140, binding = 0) uniform ratio_in +{ + int ratio; +}; + +layout (set = 2, binding = 0) uniform usampler2D src; +layout (set = 3, binding = 0) writeonly uniform uimage2D dst; + +layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in; + +void main() +{ + uvec2 coords = gl_GlobalInvocationID.xy; + ivec2 textureSz = textureSize(src, 0); + + if (int(coords.x) >= textureSz.x || int(coords.y) >= textureSz.y) + { + return; + } + + uint coordsShifted = coords.x << ratio; + + uvec2 dstCoords0 = uvec2(coordsShifted, coords.y); + uvec2 dstCoords1 = uvec2(coordsShifted + 1, coords.y); + uvec2 dstCoords2 = uvec2(coordsShifted + 2, coords.y); + uvec2 dstCoords3 = uvec2(coordsShifted + 3, coords.y); + + uvec4 rgba = texelFetch(src, ivec2(coords), 0); + + imageStore(dst, ivec2(dstCoords0), rgba.rrrr); + imageStore(dst, ivec2(dstCoords1), rgba.gggg); + imageStore(dst, ivec2(dstCoords2), rgba.bbbb); + imageStore(dst, ivec2(dstCoords3), rgba.aaaa); +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyToNonMsComputeShaderSource.comp b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyToNonMsComputeShaderSource.comp new file mode 100644 index 00000000..a3fe02ca --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyToNonMsComputeShaderSource.comp @@ -0,0 +1,37 @@ +#version 450 core + +layout (std140, binding = 0) uniform sample_counts_log2_in +{ + ivec4 sample_counts_log2; +}; + +layout (set = 2, binding = 0) uniform usampler2DMS srcMS; +layout (set = 3, binding = 0) writeonly uniform uimage2D dst; + +layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in; + +void main() +{ + uvec2 coords = gl_GlobalInvocationID.xy; + ivec2 imageSz = imageSize(dst); + + if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y) + { + return; + } + + int deltaX = sample_counts_log2.x - sample_counts_log2.z; + int deltaY = sample_counts_log2.y - sample_counts_log2.w; + int samplesInXLog2 = sample_counts_log2.z; + int samplesInYLog2 = sample_counts_log2.w; + int samplesInX = 1 << samplesInXLog2; + int samplesInY = 1 << samplesInYLog2; + int sampleIdx = ((int(coords.x) >> deltaX) & (samplesInX - 1)) | (((int(coords.y) >> deltaY) & (samplesInY - 1)) << samplesInXLog2); + + samplesInXLog2 = sample_counts_log2.x; + samplesInYLog2 = sample_counts_log2.y; + + ivec2 shiftedCoords = ivec2(int(coords.x) >> samplesInXLog2, int(coords.y) >> samplesInYLog2); + + imageStore(dst, ivec2(coords), texelFetch(srcMS, shiftedCoords, sampleIdx)); +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyWideningComputeShaderSource.comp b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyWideningComputeShaderSource.comp new file mode 100644 index 00000000..a9be454f --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyWideningComputeShaderSource.comp @@ -0,0 +1,31 @@ +#version 450 core + +layout (std140, binding = 0) uniform ratio_in +{ + int ratio; +}; + +layout (set = 2, binding = 0) uniform usampler2D src; +layout (set = 3, binding = 0) writeonly uniform uimage2D dst; + +layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in; + +void main() +{ + uvec2 coords = gl_GlobalInvocationID.xy; + ivec2 imageSz = imageSize(dst); + + if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y) + { + return; + } + + uvec2 srcCoords = uvec2(coords.x << ratio, coords.y); + + uint r = texelFetchOffset(src, ivec2(srcCoords), 0, ivec2(0, 0)).r; + uint g = texelFetchOffset(src, ivec2(srcCoords), 0, ivec2(1, 0)).r; + uint b = texelFetchOffset(src, ivec2(srcCoords), 0, ivec2(2, 0)).r; + uint a = texelFetchOffset(src, ivec2(srcCoords), 0, ivec2(3, 0)).r; + + imageStore(dst, ivec2(coords), uvec4(r, g, b, a)); +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ColorDrawToMsFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorDrawToMsFragmentShaderSource.frag new file mode 100644 index 00000000..e9151c44 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorDrawToMsFragmentShaderSource.frag @@ -0,0 +1,27 @@ +#version 450 core + +layout (std140, binding = 0) uniform sample_counts_log2_in +{ + ivec4 sample_counts_log2; +}; + +layout (set = 2, binding = 0) uniform usampler2D src; + +layout (location = 0) out uvec4 colour; + +void main() +{ + int deltaX = sample_counts_log2.x - sample_counts_log2.z; + int deltaY = sample_counts_log2.y - sample_counts_log2.w; + int samplesInXLog2 = sample_counts_log2.z; + int samplesInYLog2 = sample_counts_log2.w; + int samplesInX = 1 << samplesInXLog2; + int samplesInY = 1 << samplesInYLog2; + + int sampleIndex = gl_SampleID; + + int inX = (int(gl_FragCoord.x) << sample_counts_log2.x) | ((sampleIndex & (samplesInX - 1)) << deltaX); + int inY = (int(gl_FragCoord.y) << sample_counts_log2.y) | ((sampleIndex >> samplesInXLog2) << deltaY); + + colour = texelFetch(src, ivec2(inX, inY), 0); +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ColorDrawToMsVertexShaderSource.vert b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorDrawToMsVertexShaderSource.vert new file mode 100644 index 00000000..558792cc --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ColorDrawToMsVertexShaderSource.vert @@ -0,0 +1,11 @@ +#version 450 core + +void main() +{ + int low = gl_VertexIndex & 1; + int high = gl_VertexIndex >> 1; + gl_Position.x = (float(low) - 0.5f) * 2.0f; + gl_Position.y = (float(high) - 0.5f) * 2.0f; + gl_Position.z = 0.0f; + gl_Position.w = 1.0f; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ConvertD32S8ToD24S8ShaderSource.comp b/src/Ryujinx.Graphics.Vulkan/Shaders/ConvertD32S8ToD24S8ShaderSource.comp new file mode 100644 index 00000000..96cbdebb --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ConvertD32S8ToD24S8ShaderSource.comp @@ -0,0 +1,58 @@ +#version 450 core + +#extension GL_EXT_scalar_block_layout : require + +layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in; + +layout (std430, set = 0, binding = 0) uniform stride_arguments +{ + int pixelCount; + int dstStartOffset; +}; + +layout (std430, set = 1, binding = 1) buffer in_s +{ + uint[] in_data; +}; + +layout (std430, set = 1, binding = 2) buffer out_s +{ + uint[] out_data; +}; + +void main() +{ + // Determine what slice of the stride copies this invocation will perform. + int invocations = int(gl_WorkGroupSize.x * gl_NumWorkGroups.x); + + int copiesRequired = pixelCount; + + // Find the copies that this invocation should perform. + + // - Copies that all invocations perform. + int allInvocationCopies = copiesRequired / invocations; + + // - Extra remainder copy that this invocation performs. + int index = int(gl_GlobalInvocationID.x); + int extra = (index < (copiesRequired % invocations)) ? 1 : 0; + + int copyCount = allInvocationCopies + extra; + + // Finally, get the starting offset. Make sure to count extra copies. + + int startCopy = allInvocationCopies * index + min(copiesRequired % invocations, index); + + int srcOffset = startCopy * 2; + int dstOffset = dstStartOffset + startCopy; + + // Perform the conversion for this region. + for (int i = 0; i < copyCount; i++) + { + float depth = uintBitsToFloat(in_data[srcOffset++]); + uint stencil = in_data[srcOffset++]; + + uint rescaledDepth = uint(clamp(depth, 0.0, 1.0) * 16777215.0); + + out_data[dstOffset++] = (rescaledDepth << 8) | (stencil & 0xff); + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ConvertIndexBufferShaderSource.comp b/src/Ryujinx.Graphics.Vulkan/Shaders/ConvertIndexBufferShaderSource.comp new file mode 100644 index 00000000..d56d6cfd --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ConvertIndexBufferShaderSource.comp @@ -0,0 +1,58 @@ +#version 450 core + +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_shader_8bit_storage : require + +layout (local_size_x = 16, local_size_y = 1, local_size_z = 1) in; + +layout (std430, set = 0, binding = 0) uniform index_buffer_pattern +{ + int ibp_pattern[8]; + int ibp_primitive_vertices; + int ibp_primitive_vertices_out; + int ibp_index_size; + int ibp_index_size_out; + int ibp_base_index; + int ibp_index_stride; + int src_offset; + int total_primitives; +}; + +layout (std430, set = 1, binding = 1) buffer in_s +{ + uint8_t[] in_data; +}; + +layout (std430, set = 1, binding = 2) buffer out_s +{ + uint8_t[] out_data; +}; + +void main() +{ + int primitiveIndex = int(gl_GlobalInvocationID.x); + if (primitiveIndex >= total_primitives) + { + return; + } + + int inOffset = primitiveIndex * ibp_index_stride; + int outOffset = primitiveIndex * ibp_primitive_vertices_out; + + for (int i = 0; i < ibp_primitive_vertices_out; i++) + { + int j; + int io = max(0, inOffset + ibp_base_index + ibp_pattern[i]) * ibp_index_size; + int oo = (outOffset + i) * ibp_index_size_out; + + for (j = 0; j < ibp_index_size; j++) + { + out_data[oo + j] = in_data[src_offset + io + j]; + } + + for (; j < ibp_index_size_out; j++) + { + out_data[oo + j] = uint8_t(0); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/ConvertIndirectDataShaderSource.comp b/src/Ryujinx.Graphics.Vulkan/Shaders/ConvertIndirectDataShaderSource.comp new file mode 100644 index 00000000..6ee96b7b --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/ConvertIndirectDataShaderSource.comp @@ -0,0 +1,103 @@ +#version 450 core + +#extension GL_EXT_scalar_block_layout : require + +layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + +layout (std430, set = 0, binding = 0) uniform draw_count_uniform +{ + uint[64] draw_count_buffer; +}; + +layout (std430, set = 1, binding = 1) buffer indirect_in +{ + int[] indirect_data_in; +}; + +layout (std430, set = 1, binding = 2) buffer indirect_out +{ + int[] indirect_data_out; +}; + +layout (std430, set = 1, binding = 3) buffer index_buffer_pattern +{ + int ibp_pattern[8]; + int ibp_primitive_vertices; + int ibp_primitive_vertices_out; + int ibp_index_size; + int ibp_index_size_out; + int ibp_base_index; + int ibp_index_stride; + int src_offset; + int total_primitives; + int dispatch_x; + int dispatch_y; + int dispatch_z; + int has_draw_count; + uint max_draw_count; + int draw_count_offset; + int indirect_data_stride; + int indirect_data_offset; +}; + +int GetPrimitiveCount(int vertexCount) +{ + return max(0, (vertexCount - ibp_base_index) / ibp_index_stride); +} + +int GetConvertedCount(int indexCount) +{ + int primitiveCount = GetPrimitiveCount(indexCount); + return primitiveCount * ibp_primitive_vertices_out; +} + +void main() +{ + uint drawCount = has_draw_count != 0 ? min(draw_count_buffer[draw_count_offset], max_draw_count) : max_draw_count; + uint i = 0; + + if (drawCount != 0) + { + int firstIndex = indirect_data_in[indirect_data_offset + 2]; + int endIndex = firstIndex + indirect_data_in[indirect_data_offset]; + + for (i = 1; i < drawCount; i++) + { + int offset = int(i) * indirect_data_stride; + int inOffset = indirect_data_offset + offset; + + int currentFirstIndex = indirect_data_in[inOffset + 2]; + firstIndex = min(firstIndex, currentFirstIndex); + endIndex = max(endIndex, currentFirstIndex + indirect_data_in[inOffset]); + } + + int indexCount = endIndex - firstIndex; + + dispatch_x = (indexCount + 15) / 16; + src_offset += firstIndex * ibp_index_size; + total_primitives = GetPrimitiveCount(indexCount); + + for (i = 0; i < drawCount; i++) + { + int offset = int(i) * indirect_data_stride; + int inOffset = indirect_data_offset + offset; + + indirect_data_out[offset] = GetConvertedCount(indirect_data_in[inOffset]); // Index count + indirect_data_out[offset + 1] = indirect_data_in[inOffset + 1]; // Instance count + indirect_data_out[offset + 2] = GetConvertedCount(indirect_data_in[inOffset + 2] - firstIndex); // First index + indirect_data_out[offset + 3] = indirect_data_in[inOffset + 3]; // Vertex offset + indirect_data_out[offset + 4] = indirect_data_in[inOffset + 4]; // First instance + } + } + + for (; i < max_draw_count; i++) + { + int offset = int(i) * indirect_data_stride; + + indirect_data_out[offset] = 0; + indirect_data_out[offset + 1] = 0; + indirect_data_out[offset + 2] = 0; + indirect_data_out[offset + 3] = 0; + indirect_data_out[offset + 4] = 0; + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/DepthBlitFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/DepthBlitFragmentShaderSource.frag new file mode 100644 index 00000000..55b7be13 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/DepthBlitFragmentShaderSource.frag @@ -0,0 +1,10 @@ +#version 450 core + +layout (binding = 0, set = 2) uniform sampler2D texDepth; + +layout (location = 0) in vec2 tex_coord; + +void main() +{ + gl_FragDepth = texture(texDepth, tex_coord).r; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/DepthBlitMsFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/DepthBlitMsFragmentShaderSource.frag new file mode 100644 index 00000000..c93c7e7f --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/DepthBlitMsFragmentShaderSource.frag @@ -0,0 +1,10 @@ +#version 450 core + +layout (binding = 0, set = 2) uniform sampler2DMS texDepth; + +layout (location = 0) in vec2 tex_coord; + +void main() +{ + gl_FragDepth = texelFetch(texDepth, ivec2(tex_coord * vec2(textureSize(texDepth).xy)), gl_SampleID).r; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/DepthDrawToMsFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/DepthDrawToMsFragmentShaderSource.frag new file mode 100644 index 00000000..bf5f612f --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/DepthDrawToMsFragmentShaderSource.frag @@ -0,0 +1,25 @@ +#version 450 core + +layout (std140, binding = 0) uniform sample_counts_log2_in +{ + ivec4 sample_counts_log2; +}; + +layout (set = 2, binding = 0) uniform sampler2D src; + +void main() +{ + int deltaX = sample_counts_log2.x - sample_counts_log2.z; + int deltaY = sample_counts_log2.y - sample_counts_log2.w; + int samplesInXLog2 = sample_counts_log2.z; + int samplesInYLog2 = sample_counts_log2.w; + int samplesInX = 1 << samplesInXLog2; + int samplesInY = 1 << samplesInYLog2; + + int sampleIndex = gl_SampleID; + + int inX = (int(gl_FragCoord.x) << sample_counts_log2.x) | ((sampleIndex & (samplesInX - 1)) << deltaX); + int inY = (int(gl_FragCoord.y) << sample_counts_log2.y) | ((sampleIndex >> samplesInXLog2) << deltaY); + + gl_FragDepth = texelFetch(src, ivec2(inX, inY), 0).r; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/DepthDrawToNonMsFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/DepthDrawToNonMsFragmentShaderSource.frag new file mode 100644 index 00000000..e376b2e7 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/DepthDrawToNonMsFragmentShaderSource.frag @@ -0,0 +1,28 @@ +#version 450 core + +layout (std140, binding = 0) uniform sample_counts_log2_in +{ + ivec4 sample_counts_log2; +}; + +layout (set = 2, binding = 0) uniform sampler2DMS srcMS; + +void main() +{ + uvec2 coords = uvec2(gl_FragCoord.xy); + + int deltaX = sample_counts_log2.x - sample_counts_log2.z; + int deltaY = sample_counts_log2.y - sample_counts_log2.w; + int samplesInXLog2 = sample_counts_log2.z; + int samplesInYLog2 = sample_counts_log2.w; + int samplesInX = 1 << samplesInXLog2; + int samplesInY = 1 << samplesInYLog2; + int sampleIdx = ((int(coords.x) >> deltaX) & (samplesInX - 1)) | (((int(coords.y) >> deltaY) & (samplesInY - 1)) << samplesInXLog2); + + samplesInXLog2 = sample_counts_log2.x; + samplesInYLog2 = sample_counts_log2.y; + + ivec2 shiftedCoords = ivec2(int(coords.x) >> samplesInXLog2, int(coords.y) >> samplesInYLog2); + + gl_FragDepth = texelFetch(srcMS, shiftedCoords, sampleIdx).r; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/DepthStencilClearFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/DepthStencilClearFragmentShaderSource.frag new file mode 100644 index 00000000..689a0fff --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/DepthStencilClearFragmentShaderSource.frag @@ -0,0 +1,8 @@ +#version 450 core + +layout (location = 0) in vec4 clear_colour; + +void main() +{ + gl_FragDepth = clear_colour.x; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ChangeBufferStride.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ChangeBufferStride.spv new file mode 100644 index 0000000000000000000000000000000000000000..1e0d8810fbc2a6513b0edb32ce596c3aa68bb6ce GIT binary patch literal 3932 zcmZ9MX>(Ln5QZ-#ldvxW;syjzL0JR^L=j93IG~XTL4%?WlguOxBr{vUtXW! z>r3t8$k@pKR=cwQ(BT6{u1R{*1Y_4GE0O}b7nv>B>N-{-yO1Nub>t?pj#|c0HW@_B zReJa>MgNJ>n=DI;Bc)RpFO*tW%9U!fbabNDF16Z?W_h}*e}(?y=xA}|RB3W<4%hrl zwKP+!S4)*zvpU(X-ALooI$2z;J~`8=tGbdAjZ}KX-daK=BoJAoS#%9g=;YO$KhU~kDZZVWL>&$1|Il1QK!;Kp^ z4p(Wlb6ihy39gpr^dz*OTupIr(m*#lTpV;CKW#s`>}aUilip2v_trtz>S+oYh1Avu zm!=r+J@ykXpk41YmUqO@Gw>_om-Aj1sqG!uf%t{JebG#9-^xnlvCQvWwV%ptA5|a1 z6G_*L{p8n^i}ieSu4laKX}ezdoqfKZd-u!NlZ$xwov){Dy{P{y*nS^JHjriBc4p9n z7H^^l*Be008NlvGEApGMmuI*ay)~PkoUzunjx#-$+0ImM54JT{BYv*620fs`r(b(* zYAx1V2R7Hd@NWd$TiBjO)HmO=a6jRH8a#;D`!i@`^xN;VXlu#aueN6_r+;^b%?ZC} zu74w|zK(Vs`y0x5_caUlf68wQtJTqR&dc-Hw*DO2`N&yc+dCj~82VAl?NJJ|m3atqwo4rBn4H{bJ=8^XUE{T#9jiCOqgp3nGu#Axq9ym#_< zvU%;zcx!qmw4;apVD}dG3t)S?pY?PAY>d45-XXb&e-SJf_Df*bi2OrfW8}^E&dJ4| zUIrgQ@_RapEg${A0+uu1JE|S)9RtgS{Tevm&v9(~d64br1lSn)Kj8dN8APn3?{`kZ zvv>m;M1~OWpKJRb;NvXxi2sQ(yB|{D;(p9?e{#mzzk6{_b*%X#cp;U= w|C67;M1& literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorBlitClearAlphaFragment.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorBlitClearAlphaFragment.spv new file mode 100644 index 0000000000000000000000000000000000000000..84d317c899714ebaa8f04f471020eaa2df52345e GIT binary patch literal 656 zcmY+AJ4*vm5QWFAd6;M(1`=aH5-o*bp(2P{rb(e#Sc)VTg1QhRSo+)iRW^d(x5XE$QIdzC(Z?Q391oa94dnTZ`@iSF@hQdbqDr<@3CM zkgiK{ld6`ud}rSzcE~KZLq>dFo$}8-r*}h--ylWm z1*mt{N$+!3bDLD}4wi3hlXApwz|`-O;zo|`(2MCK$9gMF57(%vw@Z3nzI(l1;>>^O J?gxc4@)y+P9U1@t literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorBlitFragment.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorBlitFragment.spv new file mode 100644 index 0000000000000000000000000000000000000000..9bd6a0b2c5856822c418a71287d46bdf7bba2fdc GIT binary patch literal 552 zcmY+AO-lk{6oqe2qo(C&6cL2hURq=Xky|(JTC|MN7C|*&2K~N%RU4t_8D~H*+`0Fh z_uQ{ZDp$3Ly=X)|j$>opsK7)tqngL*>~S{wS}jI5<7*YINOGZ?cGO~@sDHe?efn^K zckvE7c?OUrbdC6>D23hDi>;Yw@7ed#_au(MtJfd$H19?JJkPS_0)ATPFUU2q`b*py zB_5J%V)rTaeN^g(`Bs~n6Y?tD06#_V&SLi*@U0WQTK>Mk?kso4_6?$X&fBpPpQCK@ zqRxJYg-XtTVsFr#-zM76J=J%J_L5gG_9nSo{*gL&)!gY1CM7<=I-`Z#Uu=&aZv7J~ SZ|dV}lOF literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorBlitMsFragment.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorBlitMsFragment.spv new file mode 100644 index 0000000000000000000000000000000000000000..ea73a84b00d81ba3af0db7ed6d648c0dfa9b17cd GIT binary patch literal 808 zcmYk3?@9tu5XC3AHEYX1m73X~HNQ(lMG!?F0tx))11#c71*G?s~cQhS{10R+5r*R^l14iRaM&BiFDdvEgbCvI9nnibqejwYG?MVTRR<^}L zuckOJE|k@!#g1V13iU0>^HT7)4znX%L)&i2ldtFaJH$)eR3`n&LvPzKGFI-i)DjzybeJ$fx^-Xt29`#_}g!*f}KYiGh!%p8~eEB=E%*Z=3 zAD9_!30fsFd^}EvtcJ@l6A)HjtTLJ#Q4Yb z!N25-iRbI?+Dwz8t4^IhRb4&MY^+W@H|1vBS-0!LnsW^y&ds`(-wzIt4!h%O(0#G~ z1ja?z^h7w9bdGgK_NAW}_%pJrvPZI}ej3_;iJXX4=OxmI`+3;=l#QzFRVf9A}dpQzG|mIGp6@hq>Xin@E62tX-{S7ri#x+AABpu+?o7ZrtgWdrSL>e~Mjvb1(Irpc#NqEM)IsC1Olz0A#7}+b-dg-B#J(^3 zcrCNO;J@+tQ-7ZPwv3t^`hMUsPZro;A&K^Fp}Z;I2JiVtDDj1e@ut5ccDaV_iN$P} zWz3(Ma|S(nxhW&Jr?aW{j*K^E);sEe7|vK8-hQYSz46ZvjCucz*t3W;bLywspUZe7?q+?1!8Pr5`Sfhs8}h;G+AkG@ o*R?klo3`~02G{Cu)i}I0cLU=)FwRbm!+XO4<6HQruXI!PAGT0rzW@LL literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorClearFFragment.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorClearFFragment.spv new file mode 100644 index 0000000000000000000000000000000000000000..556384e686d3e85203648a4e646498dd11489b65 GIT binary patch literal 380 zcmYjM%?g5G5S%o%v|oa{h0$F)R0L61FW!292`A`*?+yTX3{*%ABFm^nNPFRq1aa}5SO|!wR*zxsqlaf@|YMmn0mu z`G@>AnDsZ}*Cp01;?(zEKhMuUMfHB($ma~jKjPktDP-|^jmhdSNP5872^ZR CVHNfO literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorClearSIFragment.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorClearSIFragment.spv new file mode 100644 index 0000000000000000000000000000000000000000..0b5acf01bf80eabdfdb5cfd98ddffcbec9f76d46 GIT binary patch literal 428 zcmYk1%W48a6htdd9LKj3Om;Goy|_>bgkpnUvy;r0s)s(2C`{}A=MXD%;f9l_sXz2%{5G>wjZ z=pNuKa#!i>qO#RTKb)H7ZnHb>d5=T=a`U^`{}ie;%YA)&IdJ9_{KXTPF$mKX@}51h zwbWqWH*@wuV~5mt;>eHCKXcALXs^C&j;TRy-4R~YHk{KJomx0Mp-uf0_QA`c-^EJV a@hk7ua*qGsocV17?b~j4fM4D1!?*#t2^TN` literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorClearUIFragment.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorClearUIFragment.spv new file mode 100644 index 0000000000000000000000000000000000000000..0f5bba47ae42a179e5659e50d95cacd9448c8255 GIT binary patch literal 428 zcmYk1%W48q5JW3Z9LKj3Om;Goy|_>bgkP5nR+ZocpA}QwwG%xT!B;AG8?#E@sM( ZUwN;VbM*hl%x~M_zO7~l@YUTu>^Hbc7cT$+ literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorClearVertex.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorClearVertex.spv new file mode 100644 index 0000000000000000000000000000000000000000..568f413560ba0c495dbcc7bd53bb2c9debfa69f4 GIT binary patch literal 1424 zcmZ9L+e;fk6voHIto71XYt>$COpLcy8+%a^N-t7D3qA;fuVG0RSy;1@tRQ_7r2km^ z;J@UH;P;!I2}@5nnK|G2<~wK3B;`_L#F!y7W*(UxliQRj2{C5e1a>_*Iyq|gvrcns z`vZ(8rfi9Dp6VPnEI$w9F8eY0b9r4})~BTPugDd#ioA;S;hrU})2Nq4SHHTQ=n7t7 z&$p8(?6ulyl3w;iv-phGBHmKTz>FGj?7v*A6J{ZJMSaY%k)gk!RdtV}US1kJa5#H% zX+O^56#Ws)=e`z?yV*(nJA&tP+)=roNqn&%_p`9ujs`iVMeZ_5208j+KG=b#!W_os z?_A9Ms&ed59sBz=a!6;&7Naibx!d9(m&fSSGv~Q4Mh(nk%!o$pAjq2T9cz&Q#|VS;9p$Koyo5|{)CHXr8K2|O+KgGGp)qbMr&FtwZPFL zW?o=beKRX^aA&pVJ>^_tW(DT!zR(_n|57VC&U24>i|b1UpZJY@NItKaH=;MNs&36( ze81#ybZc7ak32mS*X3a7qOl~GefRN79{=!l*Yeovz15zxob!D9=X5cBew6!r!uu>Y z=Gy9$-;$%Tqu&QS=8J*d38`rPDwH?l?Rl?Tp~R0J#+&_f*kl3Q6UHoG$(c7ZMvK|J zmV+63iaF{zBFy6#-whJ6)4+o5Qxz{ghSN;IYi(t?I literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorCopyShorteningCompute.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorCopyShorteningCompute.spv new file mode 100644 index 0000000000000000000000000000000000000000..d5e50b954083becb4ced18bdb0b7678dd20c045c GIT binary patch literal 2040 zcmZ9M+fGzL5Je9&45+9;5D+ywh_{I1C5m`UhQtIxBBIe3FARbr3XB0nO?>ckeDfcS zzhu0{#I@#h#S@$Cbk**vUDb7ZD3^xXvr0?Wk!{P~XZh;RN?8l0`}wqwv3F!`O@3nf z-t@@E=HkeOi)YQ)ot531XPw#htb*MNR_B-3ZI zqDF=O)LB}Yn^|FlbVCA^n2*Y8iQn-tKExU8+#SF`tjS@5pjI&;E3X-#)zB6_Wr+RyHm%LK8Kk?S^o4AbAKLM8gKBc;1uf4e+$S7?>5T&Qq#IF`}qOm@9aC?TS?Y$Y-jThc*mv_6m*??v7i{UKnk z>oc6+^a!W|&pweScNEBbx8v-tU!2Jw19IL^JL;drw!Zg>`oq}rQU5fy^^J-8BiP1y z_7ly}cK^nt?B@)yALnVjb?xmOyUX5;@t>iby=klGx6zj$=gz*lD_{&bKWFZ2t^)b5 zX`a`xot=E#v4$OYyhB_&{x~qU20o_T8;SGnMBD_nd<{h0Eo^hAK*Zg~mal<`yNhj{ zzlDgqk1byV&f9nK5csa*-8{nf-OSOczn#ZG&bU(b-~Ig^&4W4!tp#lFHpB1C+-Jai z@&EEazWL@DmsRt*s{0(&LFm4~b{3)g5<7HTu!~UFd~=K|>b}CQgV0^Yc6OoLzz*FK tb`k2DZ;o+A-PgEv5W4Hw&N_5Au|v0vU4**kn`4~1#W(0%{KE~$z+db4eChxI literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorCopyToNonMsCompute.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ColorCopyToNonMsCompute.spv new file mode 100644 index 0000000000000000000000000000000000000000..23ebd33d50843fcd34f8189a72f7655e376e72d0 GIT binary patch literal 2044 zcmZXUc}tX06o+3m&9qEur-f0AB2C&X+id}piV7i$qG=k#glQ(4X%8w}K1=`g9a`uU zMH@lS^Uggu&>0TroZngRx%Zup_Ti;j)|T~T-P!xBNds9sNR}!0COLuZ&AM`Y>io?4 zv3g^6Z2$OfGy1X)MebWkOegvh_+DwgD!v@<2NxTF3yJN~!LJ?p38RyU*1S$;GIy?0 zoT}7rmnzfMN40XPF<+}rp8@YAcMWp3-azlpXRVj+-LKp!mTM2Ije4WdNntP6YR;lR;+?J4{|4LH?6-<2;|7_*+0@t2qYGJprFGa`dXDMW?n$;e1#IiJ z!Qw}f?GD7Ow-Q-Mw)y?&V%FP`En|*medAk6zhsZqO*N0Thqil^R#Ry`as=GjJ)0Ev zHgs#-dk@myzMU-(uD>Zyc74KY-LH9=#4FEbK{a_Z0c((4EC| zKS4ULc94458_9f%ZJhm{r64iq)AoKP&-MywJDWA5p?79{i0^nR(@wI&NB)-kGnJ8zRos=l$_0&U9gz5sax-Dvc*rb zDc{~f*!St1^lsd-`>JpngcPt0UN8&UHa`jg#C7Wm&QDX`(XV~bG-R~FpK>J`(?t4yuVlIuM=kNN!aIua{=nKcfVMt{tbc6z?`g*1p(S4!2&6n4= z)@NHsjoG>RYwqX|ovQZQKsX+X*j-@1wtFCd0*r!rPy&PGaeW9R8LZdIuLJ!Vr$|IT zN0I)_^JcGF-fM2x_SO#GH|w>d-R8mC1Nb6)t_eBUX%F2Q2c7b0_t=x7 zZEvl^dMH!bT7BRw&^Pxa@TAe7z*fpx?Q!hYT%D=*YPxPep3S}1G?x~(Z z4`R!iZ=5?HMw{RGytuAdVeC6D0+yYSS;U?NOdHcSH{$Jnm9A~JvOpI~vUqLI&zPB_;E z`nHb!DdzhQ<9GZG@2w>A3)_2n2V7S#vCEt%f$wF5m*!jR&p3I-vl{C=IR(7;9en3C zmT{X@v|qouxbFio-qpE`H%31J?o&R&`F19NaS8ayKDkLC?>R1@m7lE1PXRg4q8;mB z!nS?^#QK-9hh8FLlL*_*cc zya#>x70&EiUIe~(@7wu0n_EEsOE%Bj*v?cwp7AbrJY$78?`#Pe>zcWi(f2_KI0x_Q zAuz6i?>A8f##H%#_MNX~oN>KL+Lzx$tos<-7$xS|#Lnky}i8<=n`5X;=xqOao{FvhvwlPY4+pn>G+i_ky*nSHYChUCn cfiv(7_F#@W^LL-Iu7~8``}W>{^U@Z0ZBi& zy;iez((cxIL!i^%OV7!2CXf5@tkFlzd#rH-d)`Dm;ZJtmg6kXuIi}r@jAY*(=?`Y# zeN7-9YdMFtqnCcvu7ZkB7F+FdM#-sw)eu&kuHc_Xi@EEGJiAy8#NO8MK8xA+K4N$9 z&AW(rtnEE&IvnHr&hD|^5ZYYxB3{i{_*e0rE&Oq8Uz5Ihw-DpjyPIus=hJtG#xIjK zjjkcya|W&NJ-&Ag#@OmMTE6v8pYX?F z?hue?zt@>0W<7nsr^o*9(B9Mi&GYWm&f>A>ACNM78J2q;EiG%`&Rri+-EmeJaWgdBiHX1x!16b1@HPPp5HTgH?WOG58t-A;y-iW+t_|{ z`$pb9Y`^m|oH_J7GK<7Lr?DSq{3zdhA!AW@5ns%GME@o1==VGKe+*_nW0AXzE$&W! zW!@8PYZ{BWR;n5b>C6HDghG9be2n{K&O8u${pj1aA{t+?~Y?FR;xw=B{?o vZxP?o7PWjs?-6H}yAQ|r_W_aD;J>i%`D4c9`p$eqOT04Qx3Ryx>ILKs#C?fNRE#?AOEh>;5yTCJD0mPAuOd3gK%7W2f_M_dKc)x&k{7}E z&2*dTP^qq0Z(hHynQUgh!^7!SheDtPtbxgH+WJE;Xvr$IBi4e$K_9yg))T&w-}a`KIT zR#mR?*`rufoXH{nSYRxNB{O51EsV3_agO7$2V)*pb4SEEg z#+30)A5YZDH_-AlA&kX2XqMv29J+cssgIlwFR5ewS?$s1ImchIeaBx_&CFcC9QWU1 zeeSicKx0Sm5FRsNf&B={DentquDl;+`Yn{Y&tlB=%VL8mtZwf#X}Ry>;B`s?#$7)w zHe+$_dn|SCHm6`-%*az5$Q2k|r@f#(XFBag?ZML8ORB-s+RLh)Ht!K_Fu3k~zHP^0 Yj&Q)3vEyu{ILs0b7<2yB7qhGQ1Mdn%V*mgE literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ConvertD32S8ToD24S8.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/ConvertD32S8ToD24S8.spv new file mode 100644 index 0000000000000000000000000000000000000000..d8e19ff689bbf0526182ba57552bd0ba642469cb GIT binary patch literal 3356 zcmZ9NYjYG;5Qa~(m&7ClkO1Dm4e<&wA_Ah^HYRb2!~{t+sOUP`orE!y*=1)oCTf*@ z!19B?#E<+1O3Qy_sZ~~dpV?`g%AuO>_w7F2ea`8f6#6FyT&d3uy94ff*IS2Ozw47Z z?8>RGEi5lg#YuDO{DrebjJiS^NX$VuOVXoe2x9f4T8~XJy zi2Qmp==hB!*!G{~$>M^j5q6vTfRZxh`z`4NgX$rOwp$G^30hGsW*|MsMp$3$ZmqUD zo3&1>+m00`U)haGH(Kq$k8k_;yQce5`h`8bO@Aki9Zc_D zz1PClnwQ%#Kb!iC?qz9zC+T=8F1aaboo6>|OF2vKoVb{VRA)Dfy8_o~Sc_>~Rh)Xy zaRoOgE|AdME&P5vxtHPF_@a7tFC_agS)$qjk!jV6Ry#_>LI%uP)>h+}Ta!5f{D zundp)V2ZJ+!TaI=Sh_4B7y0e{R+Id2mVZSy`RRfEU~(Gfp7Z(a-1&TPmXGu2^MT1{ z{_kYd@3WF4V&TWtQ$ZU0sCuAYMM4i1;Q?s`9{*D*um7~pWOzt+RdQUI{KSxx{Ug%k zg1?+$-UiP0kIH5de^449IZg`m9@OsjRdmN>_j>K+1M^OBHwmm7VYBHDUVsCF|#P*~Gv<5_ekq zjD$S+epT1>{e}d7Qo>zOoA-yeT5rncJciH8Cgwp_>n+*Dz_XuufwTCxW#{*w7p_Qn zJ9@YvAqL*|T$G(Z-#fzj^IZ}q2Hy6(E4v~Y(KM0wJxQO0dmtXa^?X@2b%~*8W*W|N zU6l=IJzh_J#P;H^NW3_LwlrQxvo&F~(7>$m-Z zG{155pPJPh((@7){tu<`qpkK$+2k<1Ae(owS@=jcG4TH=C%7gdH@UEF?_^4M|h-ljaovhJ=`P$yRozdl^rDPd3=j zuqC@Hv3rYT!`T_|!C9{@+2r_5^|-J561cX+Vmh)p#~)ctA{#E2{GM@L+3dw<_p>cK rl<+3>K)-OrGDFN0?~bZ^<<8Q2!*r&dxozB{#MJE{ z)K5bE3VtW|nD{(*?$N2ymz7`q5r2R(L&U$IR-EG`S+u4WR)ncRl{jAyQ z{j0-qG7Q%pC!e(!+v!}pThH28I&0lVR^;8zm5boo_rvo}v-Lq*UtL%r<#JXOtzOx8 zjh?l8^7``MrdqPK#O=Std-+mcTt<@{?MysC?xvlhZw*U4?%b9BI+Zt|Azd$e68{)w$^j4gV3v%JfgjR%qRdyTZau+VR@yyzBL zJ6(?Hn97}vCJ!la-1j?h21zxVQ{5xh%!*9enD-Po#?;|?rwk!tywtUUl_B~!1vqo~ zdxzVhgO$Dy?p*HWGVyUoQ|O++ydSa2ARr@#5! zz4@YEhCH0Fj_w)GCl>jj`$6pSX2d4Q?*0R_xBnw^KZt!W z$DkK=?@Y{E#y$*LOMmyEO?)4c?>7A0v3Tf5k%#k4qkF=B2HAHP`UPZn6#7+U->N%( z3vLgdLwNaa#3qn^AL{n^F5H@8?pED5B4%6$kGMLrx^WG-K4QkH`xeBaAAekY|N6Hp zo4LZig1o8Hy{j)E{~l;p{}R%EoGp3xzAb&krRUg&^_mKK;F6cY>YmSLgM%F@s1Bc#);d@V{p&! z_-mVh#Oz1;H@N+(i$|>^$kv*IqSh10;_KA1_LGpc#3Sx$WaExP5qAt(JmUO4Gfv$4 zC*anX|HRFi`yS*ww5N6G6!b$ivyE=Lw~4}sl579ZrQmEZXi zdK0pD?dJ2l??BqT%Qn0V`CFskZRiEQ6EXcFzjK>=1sdLybE*4(4d;``-o8UNMxW22 z0*bwTjI7=*84#@cxL8IIH8?W&p897 zl4umMK1jTzfDj*4UX0fmFFa_Bg2t#ZF~mzSUIO|cB%mSO6x84EJKul02Yv9PtNXwH z>$2Bgd+k%kR-Ut_s#a9%tF_hM6;=Ihu2#ZS)rM-kUZ9eU?&3{$33OU^FE@(K7IQ7klK9MovR-*q9Vio&MO}DN-cx`Ji+dkCpw`LCYW^Za8 zYTw#h99HC9wJhgq{Ldh^yD--|+L~FMpYQZrN7}<-r(f?m&Ys%+x-&Y9Nq&9F-+GeYQ1Zv*N9W!Me`9;ln(Mc3Y0dT)7wQ^Dwo|YjZXbP_ZPb%f z&9@Y@R6cn}ce4pQz3lcov%}V0d)RIbinEV=PWvyPbir8}tIovkx0+fnXD#+yTou6H!|*744fndtHA+ofMUL8BVv z{g&q#Ihf;tk-3`U1)L6cE}X0VWX+79)A)pQzU3y6aL%*bYYWcVl{>fK%q@38!8xaL zKKSIb4!Io#=S<4&E;#2=&R!=%J@v3@`UIVtD_TRbMj>LW$xV#T%&Ch&(=99Dc#@hq)`9(LM zpZYsk%Y2!yGM`-bcfQK{)XkTAzXdMyW!B1ka>?hcmHE`om-@#@?47&{IUQDhJJH6A zw{Qmcn?S7L{6@F$e%k$aU_ymYyZS2-=VTjtW?b%UH18vvx&L1=Z|OTh?k#ZEY0f>3 z?w!h|)~#UYUH?}$HuJ>34P5HJoig4x;*6dFdkJgKxEnl)ct+>O`sB{0d(McQd+uR1 zpPV(SJ2&MXd(oY5{U;ln`BLLPux}!J`&RtUVz11Y`#MYN=JB1lhn)MWJ5S}l*P;7f zl6MxoqS5V15Am*3$8m6JzZ2cJAN!}l{$@l!0d|(6e-`XqSa)XGx}8~d>;4X_HOg7H zx^r9B{Rq0hYg_qyB?Z|l_ql+ZRClgDOJpY#V7xJY`aTciTwKN9&c(%uv*Ug5#nbb@ z0g?X_KHmL~hO;Nnk!2UF-#_rzwiB5`%q>ptE5YXWo|F5HVEL=qX|i5Nb75|6`gyK5 zH8yiig8e-;*FGdZS2z4dx^K)|5L!^Z4l!Q)3)E!2)@F^#w;%lSd~b!5FJp{-rpdpP z)!0Km@w7ngXvkq~3bUoNQu0ztJgJAg&a(3_IU5MPdh<)?k z-iIt4A@-q_G6CK+Fk93bzR1J_3=C(Ksrcl zyRn%=+Z~0?d)kG~8P)c&#`g7Q)^2L-_VQVJH{aM<>RxJhA9?E$--e&{EFk7iZ;znc z+a8j0z7H&ad(+!K*f{z4EP~7a--0fuZ~Ai#EMMNq`_a=|?a6l=SetWdo%V_{tJfp; zP!ZItx6k(GIBR*I_VGhaFFp=7&YH5% zC*b7m;YS<4PlDy0+4TBTV0(QhawYSxu3zkTfwiANa;Ce%^0|w9!0s)dxX*x%^EWne z_k!h9<9%Q`6^3vpt1SJ!aWG~%yP-~5IFtNo;rR6))v1XgR`f(-BV7#}F1daN&Ys${=g+~~@(z9hmh&CP=OS=~CQCyxG5ljgwCwp9ZIo z+U=vv;PYTX%>%(|d6b*TnWXNGPI(N?k8)S=6-2D{$T9WnSzXB5@bgi=8RA7weqj z&i`4pKfI4}g9~+C>fKe7v!B=d_&4j!HFx}i q$%&V+&fLPCC$?u3x8FS~d$n*i^0#pI_HX62E4uoAxo@^O$A19B%^pSo literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthBlitMsFragment.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthBlitMsFragment.spv new file mode 100644 index 0000000000000000000000000000000000000000..1b8dba6259e70dcee0df19e6c09d152552f5357e GIT binary patch literal 856 zcmY+B-AV#c6oq$=W0s|vshO33_YzSNMA3yLfj8j;h@@!XALd0b(2Mm{-2|=g%o(UN zEcRaO?B6*-qP1q?YS6oz+>GaW3CX_+NsLIjbOsR@!{nI=d%nL)=vR4hx|&-ySO+`oO`;p-v46I}XD#;$6Du2$iyVqPV%B?KI)Qb7wmX0=epR`&>+huI6 zm^t%{JBP=gO1_ddFxp33=^5G1=5a@!W%|i^pJi@h@9T&s@K_0pSm)`^KJ6}~KV$bM z?#b94iia|G4r@m*anv4wl^-j%TgD7BPYte)umIs}c+W|+*n66LA8nkxxnlP%r++%zWsT0X26hgy@#042hS}oF>@)x52|PmfIu7Hk z|0)N`Sx@Zu^EmGtwD)pfGrTjkvwNK5H%t}1K$Ls!L;N;=4|o2MX!H9KeGzvNJK_eg zBW@5|-`C<7QNA&AH}n>9R%5q`@%_C+jBE0rcoO|SbH@74d_xWLo&N)(-!sq4YoO)+@JpFR{sQ_p BVKM*! literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthDrawToNonMsFragment.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthDrawToNonMsFragment.spv new file mode 100644 index 0000000000000000000000000000000000000000..3341b648dce69e38363e6560a7530447c4fc0343 GIT binary patch literal 1576 zcmZXSTTfI`3`RE$H$}N<01?H5c#R41N{j?WQ6Y#C!-FsCFb-tE07Dpj^U0s)CH^H} zOkC@nQ!P+isbLReo+cE}H zB`3-mOueZKtM1iy_D!5dtLP9saRwawr|>KMI#+WM9lONIJWD@1h%Qs<8I`4N0E z`%RX27qj;mwm3u4+t_`G-70&H<15CV`FtJ~^%MAV?H~Ec5{uZqdzOf=;aAGox;to` zbq!z4o}TX`R<8H+?#6x(vGH^wZD7q=AEF=e1k4)o25P@gSVhc!#&rQ?mbc!57caU*$Po;V+=(V#Ygs@8Gux>^;77-Q60szUvRDTyyTb thTTN#x$R0D*hcRA#(VSkwq0V@E}+i${=WGg)V%VT*ILDj{beH$(SKqNU?~6q literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthStencilClearFragment.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/DepthStencilClearFragment.spv new file mode 100644 index 0000000000000000000000000000000000000000..dcd3235b6c9a8eda2441711583ea42ad04b924bf GIT binary patch literal 468 zcmYk1%}T>i5QVQvlUA$!L99E8xD<*DErKWrVmICS043B?g=i(#7xbyz2!7vf6>pf# z%(-XInVTjj%@DRiJG4SStWQ5AAOS3oc|3WT4BuCe!}E(X8J&zj~KCL^?WILU5CsM0#zkpVyFqj^!8FEM_#*WJy-82iCg}WT zG~9HD%l+>8&OP6mn@k*J%vP*mS=+WaDwYspMt!fEE4^Zbo+kbO+$a& z8;(4G_BIJZaSN7?W}2-|t1%7xjicj3)QXm*M0X|2SW0K|wL2VxXO$IYSv+u=`8$8= z?WDehurC=($w#y0UAC|OwPB7}%leL9;Eln^y9v63PVBqwKTAch_>QfrhW>1R7h`x5 z=Jh6%ps#aNs^}DyAM!=d+`bZji@vTiL662>)R|Sqf>)3JD4y9ACoRlXvTs*C|Ifz2 z&*nYvg6-)4bMrg+%dzNp#mwsh9(wpCz|f<|H?bvdQPow}bXQWP$0xI8Rdm6brRSf( z;A;($PxgJHJu=6c!0)#ZNA+AN@2@F&n;y+|C3=ryp|zoe1AZrr9n_TI#D!GZ1=x>V HoGO0-|8+Xg literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/StencilBlitMsFragment.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/StencilBlitMsFragment.spv new file mode 100644 index 0000000000000000000000000000000000000000..4d130a59b091e6345535557d4c60d03a8fb5426d GIT binary patch literal 944 zcmZ{h-AY1H5QQgy`q9$LwEt4OkBEvOii%1CFG?f2JBXf4@F&!ZUZ6*)7wAoTs&0bT zcibnsX&)A|XU*(2Ywr_{Y)zPrS<)tL%SLO?B4W&laKv2~-!f(io3iT7y?6O==MA1} z4ZrUVhJLr+YJ2|6bFV*CZ_>sCjZ(Q<-X07a+k5*vqAg`nLQqRaTwQ->#5ILE&9-;duQjV9?-l=1Dx%dN zw`s+p|Ke&R4qxKDdau`SxITPJr&{YgxBcr2D`^tiN#R{y$EMj?2CkzQ8k$HnCZ{#jl*@5L*_AGsV)aLanHDOaOETAe)~vOg7+N^*+&PzV!|tt%*Q6T~;vD;e-#=%a8+9YD zS~&`~o*x9^`&y&f3&MW0Q$KD8&5x(uUSHgdOGmM#a`ecv`U(Apgnd(UN%c}H2gUu3|F+^%Vhz?vsqU!a!`jJd zyBXBGA3FUoXm?vnLDWRr`$hQ&$0=XJUBIc8(vO~7?VuW^+u0P7j?N%#yH{&H>2`aK zm^&Pu%j4B}X8X-|>OS@-+@X}PS9ed9g&tYVo>fv^FwS1miRj^`gk|=8Y(`xA8uLE% zjAY8&%ouyz+sqsLi{?u%`XIN}!nWL5q2Tk;O~jmO#iWJhB)S%NMfQkxFlImJdk4!2 zp%ytf&ziS657=$l#9frI$jv!&7+UzT>77Ns3GKuZXMP>!EFb)<5_&WLtaM(Z)m!v_ z^4;+|`REUu84`b4QTMgyB%E_eJ2vMe_JMYI;KX7xZ!rAJUXODmT}3v1SS@Vs18hc9 z-1k1>2bbBiVpipVNB#p(0;4`Q_s62&7uu=CyjJwi*y)``&EG;&+KXZ_*GUO?!+kLG zM`9B{CBbLBOVW*(m2SLg>G(cHUk3AwW5F$UM!LmvPZoPkIzHoFAK-C+#=9XMpVgR? zPAvEjU-y>uMG18+?yhw1wJ6TA_7zD%V&~+J9(ugv&%Np~%ey8UjCol7b?H{`m#_a= z81?X3Y*9KmGx_Ox8`8;%&-&VwPG2?~`r49!TVLBAv)ntf!I;B$pSvub9+-jgcBO+e zGwb1*bmH+bE8fzP{Uz>0Jwa4J{&Uiz6zLz)B@w3+T O^7gcY{n3}QD)|eP_j}O* literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/StencilDrawToNonMsFragment.spv b/src/Ryujinx.Graphics.Vulkan/Shaders/SpirvBinaries/StencilDrawToNonMsFragment.spv new file mode 100644 index 0000000000000000000000000000000000000000..e41cee388261f1a4badca0242703d5b82c51b4de GIT binary patch literal 1664 zcmZ{j+fGwa5QbL|vfX8dOuM8I=g^;LoEvjTT&4Uh z+v|Xsmho}8|8mGrxf29+sKp(8k4n6oo#tCj z8pqSFE~DS8KdML`#++H@WP}A0&b)ly0u1h)xMR|Rgc!Z>*>#}*N2L=I@Kx_~M=Fz!vn)R;7uNZwK4_MKNpgs}kzHk>-F=55Eqe zv!ZW>zU+xZkKaN@q}PPnH^uHB>jl~Sl!WgL;m~(aT;6wDLVR6YV^60f?3R79LuxYn zX$djoosn%kc4@poHnC5dh`gMH+(2Tv=VV)MUbf|)mrcxg7l(Mfhw(1SCT2Y@%O)56 zhwpnsHg6UTVJjYk_e4CtlRFZ2!1?I!x+)v|yRWq-8;rYH&wH}1$1l$-2!l(^>Tk%V zenMjPH)Vrc{VmyG+{5Y@W!t<~e_I$_Vs_65vcUt%Pd~>)*#!wP>+xJRci=4ydnuc~ z?5?a_zUx;KxWw3ZO}ZheM>_4Q(yhqm8_(*u?A)%$$Sp|d&;5D-wuCt6uD;e#8tjiY Ha$E8jPTG5v literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/StencilBlitFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/StencilBlitFragmentShaderSource.frag new file mode 100644 index 00000000..1919269b --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/StencilBlitFragmentShaderSource.frag @@ -0,0 +1,12 @@ +#version 450 core + +#extension GL_ARB_shader_stencil_export : require + +layout (binding = 0, set = 2) uniform isampler2D texStencil; + +layout (location = 0) in vec2 tex_coord; + +void main() +{ + gl_FragStencilRefARB = texture(texStencil, tex_coord).r; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/StencilBlitMsFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/StencilBlitMsFragmentShaderSource.frag new file mode 100644 index 00000000..7e26672a --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/StencilBlitMsFragmentShaderSource.frag @@ -0,0 +1,12 @@ +#version 450 core + +#extension GL_ARB_shader_stencil_export : require + +layout (binding = 0, set = 2) uniform isampler2DMS texStencil; + +layout (location = 0) in vec2 tex_coord; + +void main() +{ + gl_FragStencilRefARB = texelFetch(texStencil, ivec2(tex_coord * vec2(textureSize(texStencil).xy)), gl_SampleID).r; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/StencilDrawToMsFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/StencilDrawToMsFragmentShaderSource.frag new file mode 100644 index 00000000..a07ae9d1 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/StencilDrawToMsFragmentShaderSource.frag @@ -0,0 +1,27 @@ +#version 450 core + +#extension GL_ARB_shader_stencil_export : require + +layout (std140, binding = 0) uniform sample_counts_log2_in +{ + ivec4 sample_counts_log2; +}; + +layout (set = 2, binding = 0) uniform isampler2D src; + +void main() +{ + int deltaX = sample_counts_log2.x - sample_counts_log2.z; + int deltaY = sample_counts_log2.y - sample_counts_log2.w; + int samplesInXLog2 = sample_counts_log2.z; + int samplesInYLog2 = sample_counts_log2.w; + int samplesInX = 1 << samplesInXLog2; + int samplesInY = 1 << samplesInYLog2; + + int sampleIndex = gl_SampleID; + + int inX = (int(gl_FragCoord.x) << sample_counts_log2.x) | ((sampleIndex & (samplesInX - 1)) << deltaX); + int inY = (int(gl_FragCoord.y) << sample_counts_log2.y) | ((sampleIndex >> samplesInXLog2) << deltaY); + + gl_FragStencilRefARB = texelFetch(src, ivec2(inX, inY), 0).r; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/Shaders/StencilDrawToNonMsFragmentShaderSource.frag b/src/Ryujinx.Graphics.Vulkan/Shaders/StencilDrawToNonMsFragmentShaderSource.frag new file mode 100644 index 00000000..3addd9d1 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Shaders/StencilDrawToNonMsFragmentShaderSource.frag @@ -0,0 +1,30 @@ +#version 450 core + +#extension GL_ARB_shader_stencil_export : require + +layout (std140, binding = 0) uniform sample_counts_log2_in +{ + ivec4 sample_counts_log2; +}; + +layout (set = 2, binding = 0) uniform isampler2DMS srcMS; + +void main() +{ + uvec2 coords = uvec2(gl_FragCoord.xy); + + int deltaX = sample_counts_log2.x - sample_counts_log2.z; + int deltaY = sample_counts_log2.y - sample_counts_log2.w; + int samplesInXLog2 = sample_counts_log2.z; + int samplesInYLog2 = sample_counts_log2.w; + int samplesInX = 1 << samplesInXLog2; + int samplesInY = 1 << samplesInYLog2; + int sampleIdx = ((int(coords.x) >> deltaX) & (samplesInX - 1)) | (((int(coords.y) >> deltaY) & (samplesInY - 1)) << samplesInXLog2); + + samplesInXLog2 = sample_counts_log2.x; + samplesInYLog2 = sample_counts_log2.y; + + ivec2 shiftedCoords = ivec2(int(coords.x) >> samplesInXLog2, int(coords.y) >> samplesInYLog2); + + gl_FragStencilRefARB = texelFetch(srcMS, shiftedCoords, sampleIdx).r; +} \ No newline at end of file diff --git a/src/Ryujinx.Graphics.Vulkan/SpecInfo.cs b/src/Ryujinx.Graphics.Vulkan/SpecInfo.cs new file mode 100644 index 00000000..ecb2abfc --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/SpecInfo.cs @@ -0,0 +1,100 @@ +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + public enum SpecConstType + { + Bool32, + Int16, + Int32, + Int64, + Float16, + Float32, + Float64, + } + + sealed class SpecDescription + { + public readonly SpecializationInfo Info; + public readonly SpecializationMapEntry[] Map; + + // For mapping a simple packed struct or single entry + public SpecDescription(params (uint Id, SpecConstType Type)[] description) + { + int count = description.Length; + Map = new SpecializationMapEntry[count]; + + uint structSize = 0; + + for (int i = 0; i < Map.Length; ++i) + { + var typeSize = SizeOf(description[i].Type); + Map[i] = new SpecializationMapEntry(description[i].Id, structSize, typeSize); + structSize += typeSize; + } + + Info = new SpecializationInfo + { + DataSize = structSize, + MapEntryCount = (uint)count, + }; + } + + // For advanced mapping with overlapping or staggered fields + public SpecDescription(SpecializationMapEntry[] map) + { + Map = map; + + uint structSize = 0; + for (int i = 0; i < map.Length; ++i) + { + structSize = Math.Max(structSize, map[i].Offset + (uint)map[i].Size); + } + + Info = new SpecializationInfo + { + DataSize = structSize, + MapEntryCount = (uint)map.Length, + }; + } + + private static uint SizeOf(SpecConstType type) => type switch + { + SpecConstType.Int16 or SpecConstType.Float16 => 2, + SpecConstType.Bool32 or SpecConstType.Int32 or SpecConstType.Float32 => 4, + SpecConstType.Int64 or SpecConstType.Float64 => 8, + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; + + private SpecDescription() + { + Info = new(); + } + + public static readonly SpecDescription Empty = new(); + } + + readonly struct SpecData : IRefEquatable + { + private readonly byte[] _data; + private readonly int _hash; + + public int Length => _data.Length; + public ReadOnlySpan Span => _data.AsSpan(); + public override int GetHashCode() => _hash; + + public SpecData(ReadOnlySpan data) + { + _data = new byte[data.Length]; + data.CopyTo(_data); + + var hc = new HashCode(); + hc.AddBytes(data); + _hash = hc.ToHashCode(); + } + + public override bool Equals(object obj) => obj is SpecData other && Equals(other); + public bool Equals(ref SpecData other) => _data.AsSpan().SequenceEqual(other._data); + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/StagingBuffer.cs b/src/Ryujinx.Graphics.Vulkan/StagingBuffer.cs new file mode 100644 index 00000000..90a47bb6 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/StagingBuffer.cs @@ -0,0 +1,297 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct StagingBufferReserved + { + public readonly BufferHolder Buffer; + public readonly int Offset; + public readonly int Size; + + public StagingBufferReserved(BufferHolder buffer, int offset, int size) + { + Buffer = buffer; + Offset = offset; + Size = size; + } + } + + class StagingBuffer : IDisposable + { + private const int BufferSize = 32 * 1024 * 1024; + + private int _freeOffset; + private int _freeSize; + + private readonly VulkanRenderer _gd; + private readonly BufferHolder _buffer; + private readonly int _resourceAlignment; + + public readonly BufferHandle Handle; + + private readonly struct PendingCopy + { + public FenceHolder Fence { get; } + public int Size { get; } + + public PendingCopy(FenceHolder fence, int size) + { + Fence = fence; + Size = size; + fence.Get(); + } + } + + private readonly Queue _pendingCopies; + + public StagingBuffer(VulkanRenderer gd, BufferManager bufferManager) + { + _gd = gd; + Handle = bufferManager.CreateWithHandle(gd, BufferSize, out _buffer); + _pendingCopies = new Queue(); + _freeSize = BufferSize; + _resourceAlignment = (int)gd.Capabilities.MinResourceAlignment; + } + + public void PushData(CommandBufferPool cbp, CommandBufferScoped? cbs, Action endRenderPass, BufferHolder dst, int dstOffset, ReadOnlySpan data) + { + bool isRender = cbs != null; + CommandBufferScoped scoped = cbs ?? cbp.Rent(); + + // Must push all data to the buffer. If it can't fit, split it up. + + endRenderPass?.Invoke(); + + while (data.Length > 0) + { + if (_freeSize < data.Length) + { + FreeCompleted(); + } + + while (_freeSize == 0) + { + if (!WaitFreeCompleted(cbp)) + { + if (isRender) + { + _gd.FlushAllCommands(); + scoped = cbp.Rent(); + isRender = false; + } + else + { + scoped = cbp.ReturnAndRent(scoped); + } + } + } + + int chunkSize = Math.Min(_freeSize, data.Length); + + PushDataImpl(scoped, dst, dstOffset, data[..chunkSize]); + + dstOffset += chunkSize; + data = data[chunkSize..]; + } + + if (!isRender) + { + scoped.Dispose(); + } + } + + private void PushDataImpl(CommandBufferScoped cbs, BufferHolder dst, int dstOffset, ReadOnlySpan data) + { + var srcBuffer = _buffer.GetBuffer(); + var dstBuffer = dst.GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true); + + int offset = _freeOffset; + int capacity = BufferSize - offset; + if (capacity < data.Length) + { + _buffer.SetDataUnchecked(offset, data[..capacity]); + _buffer.SetDataUnchecked(0, data[capacity..]); + + BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, offset, dstOffset, capacity); + BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, 0, dstOffset + capacity, data.Length - capacity); + } + else + { + _buffer.SetDataUnchecked(offset, data); + + BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, offset, dstOffset, data.Length); + } + + _freeOffset = (offset + data.Length) & (BufferSize - 1); + _freeSize -= data.Length; + Debug.Assert(_freeSize >= 0); + + _pendingCopies.Enqueue(new PendingCopy(cbs.GetFence(), data.Length)); + } + + public bool TryPushData(CommandBufferScoped cbs, Action endRenderPass, BufferHolder dst, int dstOffset, ReadOnlySpan data) + { + if (data.Length > BufferSize) + { + return false; + } + + if (_freeSize < data.Length) + { + FreeCompleted(); + + if (_freeSize < data.Length) + { + return false; + } + } + + endRenderPass?.Invoke(); + + PushDataImpl(cbs, dst, dstOffset, data); + + return true; + } + + private StagingBufferReserved ReserveDataImpl(CommandBufferScoped cbs, int size, int alignment) + { + // Assumes the caller has already determined that there is enough space. + int offset = BitUtils.AlignUp(_freeOffset, alignment); + int padding = offset - _freeOffset; + + int capacity = Math.Min(_freeSize, BufferSize - offset); + int reservedLength = size + padding; + if (capacity < size) + { + offset = 0; // Place at start. + reservedLength += capacity; + } + + _freeOffset = (_freeOffset + reservedLength) & (BufferSize - 1); + _freeSize -= reservedLength; + Debug.Assert(_freeSize >= 0); + + _pendingCopies.Enqueue(new PendingCopy(cbs.GetFence(), reservedLength)); + + return new StagingBufferReserved(_buffer, offset, size); + } + + private int GetContiguousFreeSize(int alignment) + { + int alignedFreeOffset = BitUtils.AlignUp(_freeOffset, alignment); + int padding = alignedFreeOffset - _freeOffset; + + // Free regions: + // - Aligned free offset to end (minimum free size - padding) + // - 0 to _freeOffset + freeSize wrapped (only if free area contains 0) + + int endOffset = (_freeOffset + _freeSize) & (BufferSize - 1); + + return Math.Max( + Math.Min(_freeSize - padding, BufferSize - alignedFreeOffset), + endOffset <= _freeOffset ? Math.Min(_freeSize, endOffset) : 0 + ); + } + + /// + /// Reserve a range on the staging buffer for the current command buffer and upload data to it. + /// + /// Command buffer to reserve the data on + /// The minimum size the reserved data requires + /// The required alignment for the buffer offset + /// The reserved range of the staging buffer + public unsafe StagingBufferReserved? TryReserveData(CommandBufferScoped cbs, int size, int alignment) + { + if (size > BufferSize) + { + return null; + } + + // Temporary reserved data cannot be fragmented. + + if (GetContiguousFreeSize(alignment) < size) + { + FreeCompleted(); + + if (GetContiguousFreeSize(alignment) < size) + { + Logger.Debug?.PrintMsg(LogClass.Gpu, $"Staging buffer out of space to reserve data of size {size}."); + return null; + } + } + + return ReserveDataImpl(cbs, size, alignment); + } + + /// + /// Reserve a range on the staging buffer for the current command buffer and upload data to it. + /// Uses the most permissive byte alignment. + /// + /// Command buffer to reserve the data on + /// The minimum size the reserved data requires + /// The reserved range of the staging buffer + public unsafe StagingBufferReserved? TryReserveData(CommandBufferScoped cbs, int size) + { + return TryReserveData(cbs, size, _resourceAlignment); + } + + private bool WaitFreeCompleted(CommandBufferPool cbp) + { + if (_pendingCopies.TryPeek(out var pc)) + { + if (!pc.Fence.IsSignaled()) + { + if (cbp.IsFenceOnRentedCommandBuffer(pc.Fence)) + { + return false; + } + + pc.Fence.Wait(); + } + + var dequeued = _pendingCopies.Dequeue(); + Debug.Assert(dequeued.Fence == pc.Fence); + _freeSize += pc.Size; + pc.Fence.Put(); + } + + return true; + } + + public void FreeCompleted() + { + FenceHolder signalledFence = null; + while (_pendingCopies.TryPeek(out var pc) && (pc.Fence == signalledFence || pc.Fence.IsSignaled())) + { + signalledFence = pc.Fence; // Already checked - don't need to do it again. + var dequeued = _pendingCopies.Dequeue(); + Debug.Assert(dequeued.Fence == pc.Fence); + _freeSize += pc.Size; + pc.Fence.Put(); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _gd.BufferManager.Delete(Handle); + + while (_pendingCopies.TryDequeue(out var pc)) + { + pc.Fence.Put(); + } + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/SyncManager.cs b/src/Ryujinx.Graphics.Vulkan/SyncManager.cs new file mode 100644 index 00000000..35e9ab97 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/SyncManager.cs @@ -0,0 +1,215 @@ +using Ryujinx.Common.Logging; +using Silk.NET.Vulkan; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Ryujinx.Graphics.Vulkan +{ + class SyncManager + { + private class SyncHandle + { + public ulong ID; + public MultiFenceHolder Waitable; + public ulong FlushId; + public bool Signalled; + + public bool NeedsFlush(ulong currentFlushId) + { + return (long)(FlushId - currentFlushId) >= 0; + } + } + + private ulong _firstHandle; + + private readonly VulkanRenderer _gd; + private readonly Device _device; + private readonly List _handles; + private ulong _flushId; + private long _waitTicks; + + public SyncManager(VulkanRenderer gd, Device device) + { + _gd = gd; + _device = device; + _handles = new List(); + } + + public void RegisterFlush() + { + _flushId++; + } + + public void Create(ulong id, bool strict) + { + ulong flushId = _flushId; + MultiFenceHolder waitable = new(); + if (strict || _gd.InterruptAction == null) + { + _gd.FlushAllCommands(); + _gd.CommandBufferPool.AddWaitable(waitable); + } + else + { + // Don't flush commands, instead wait for the current command buffer to finish. + // If this sync is waited on before the command buffer is submitted, interrupt the gpu thread and flush it manually. + + _gd.CommandBufferPool.AddInUseWaitable(waitable); + } + + SyncHandle handle = new() + { + ID = id, + Waitable = waitable, + FlushId = flushId, + }; + + lock (_handles) + { + _handles.Add(handle); + } + } + + public ulong GetCurrent() + { + lock (_handles) + { + ulong lastHandle = _firstHandle; + + foreach (SyncHandle handle in _handles) + { + lock (handle) + { + if (handle.Waitable == null) + { + continue; + } + + if (handle.ID > lastHandle) + { + bool signaled = handle.Signalled || handle.Waitable.WaitForFences(_gd.Api, _device, 0); + if (signaled) + { + lastHandle = handle.ID; + handle.Signalled = true; + } + } + } + } + + return lastHandle; + } + } + + public void Wait(ulong id) + { + SyncHandle result = null; + + lock (_handles) + { + if ((long)(_firstHandle - id) > 0) + { + return; // The handle has already been signalled or deleted. + } + + foreach (SyncHandle handle in _handles) + { + if (handle.ID == id) + { + result = handle; + break; + } + } + } + + if (result != null) + { + if (result.Waitable == null) + { + return; + } + + long beforeTicks = Stopwatch.GetTimestamp(); + + if (result.NeedsFlush(_flushId)) + { + _gd.InterruptAction(() => + { + if (result.NeedsFlush(_flushId)) + { + _gd.FlushAllCommands(); + } + }); + } + + lock (result) + { + if (result.Waitable == null) + { + return; + } + + bool signaled = result.Signalled || result.Waitable.WaitForFences(_gd.Api, _device, 1000000000); + + if (!signaled) + { + Logger.Error?.PrintMsg(LogClass.Gpu, $"VK Sync Object {result.ID} failed to signal within 1000ms. Continuing..."); + } + else + { + _waitTicks += Stopwatch.GetTimestamp() - beforeTicks; + result.Signalled = true; + } + } + } + } + + public void Cleanup() + { + // Iterate through handles and remove any that have already been signalled. + + while (true) + { + SyncHandle first = null; + lock (_handles) + { + first = _handles.FirstOrDefault(); + } + + if (first == null || first.NeedsFlush(_flushId)) + { + break; + } + + bool signaled = first.Waitable.WaitForFences(_gd.Api, _device, 0); + if (signaled) + { + // Delete the sync object. + lock (_handles) + { + lock (first) + { + _firstHandle = first.ID + 1; + _handles.RemoveAt(0); + first.Waitable = null; + } + } + } + else + { + // This sync handle and any following have not been reached yet. + break; + } + } + } + + public long GetAndResetWaitTicks() + { + long result = _waitTicks; + _waitTicks = 0; + + return result; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/TextureArray.cs b/src/Ryujinx.Graphics.Vulkan/TextureArray.cs new file mode 100644 index 00000000..99238b1f --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/TextureArray.cs @@ -0,0 +1,234 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Vulkan +{ + class TextureArray : ResourceArray, ITextureArray + { + private readonly VulkanRenderer _gd; + + private struct TextureRef + { + public TextureStorage Storage; + public Auto View; + public Auto Sampler; + } + + private readonly TextureRef[] _textureRefs; + private readonly TextureBuffer[] _bufferTextureRefs; + + private readonly DescriptorImageInfo[] _textures; + private readonly BufferView[] _bufferTextures; + + private HashSet _storages; + + private int _cachedCommandBufferIndex; + private int _cachedSubmissionCount; + + private readonly bool _isBuffer; + + public TextureArray(VulkanRenderer gd, int size, bool isBuffer) + { + _gd = gd; + + if (isBuffer) + { + _bufferTextureRefs = new TextureBuffer[size]; + _bufferTextures = new BufferView[size]; + } + else + { + _textureRefs = new TextureRef[size]; + _textures = new DescriptorImageInfo[size]; + } + + _storages = null; + + _cachedCommandBufferIndex = -1; + _cachedSubmissionCount = 0; + + _isBuffer = isBuffer; + } + + public void SetSamplers(int index, ISampler[] samplers) + { + for (int i = 0; i < samplers.Length; i++) + { + ISampler sampler = samplers[i]; + + if (sampler is SamplerHolder samplerHolder) + { + _textureRefs[index + i].Sampler = samplerHolder.GetSampler(); + } + else + { + _textureRefs[index + i].Sampler = default; + } + } + + SetDirty(); + } + + public void SetTextures(int index, ITexture[] textures) + { + for (int i = 0; i < textures.Length; i++) + { + ITexture texture = textures[i]; + + if (texture is TextureBuffer textureBuffer) + { + _bufferTextureRefs[index + i] = textureBuffer; + } + else if (texture is TextureView view) + { + _textureRefs[index + i].Storage = view.Storage; + _textureRefs[index + i].View = view.GetImageView(); + } + else if (!_isBuffer) + { + _textureRefs[index + i].Storage = null; + _textureRefs[index + i].View = default; + } + else + { + _bufferTextureRefs[index + i] = null; + } + } + + SetDirty(); + } + + private void SetDirty() + { + _cachedCommandBufferIndex = -1; + _storages = null; + SetDirty(_gd, isImage: false); + } + + public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags) + { + HashSet storages = _storages; + + if (storages == null) + { + storages = new HashSet(); + + for (int index = 0; index < _textureRefs.Length; index++) + { + if (_textureRefs[index].Storage != null) + { + storages.Add(_textureRefs[index].Storage); + } + } + + _storages = storages; + } + + foreach (TextureStorage storage in storages) + { + storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stageFlags); + } + } + + public ReadOnlySpan GetImageInfos(VulkanRenderer gd, CommandBufferScoped cbs, TextureView dummyTexture, SamplerHolder dummySampler) + { + int submissionCount = gd.CommandBufferPool.GetSubmissionCount(cbs.CommandBufferIndex); + + Span textures = _textures; + + if (cbs.CommandBufferIndex == _cachedCommandBufferIndex && submissionCount == _cachedSubmissionCount) + { + return textures; + } + + _cachedCommandBufferIndex = cbs.CommandBufferIndex; + _cachedSubmissionCount = submissionCount; + + for (int i = 0; i < textures.Length; i++) + { + ref var texture = ref textures[i]; + ref var refs = ref _textureRefs[i]; + + if (i > 0 && _textureRefs[i - 1].View == refs.View && _textureRefs[i - 1].Sampler == refs.Sampler) + { + texture = textures[i - 1]; + + continue; + } + + texture.ImageLayout = ImageLayout.General; + texture.ImageView = refs.View?.Get(cbs).Value ?? default; + texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; + + if (texture.ImageView.Handle == 0) + { + texture.ImageView = dummyTexture.GetImageView().Get(cbs).Value; + } + + if (texture.Sampler.Handle == 0) + { + texture.Sampler = dummySampler.GetSampler().Get(cbs).Value; + } + } + + return textures; + } + + public ReadOnlySpan GetBufferViews(CommandBufferScoped cbs) + { + Span bufferTextures = _bufferTextures; + + for (int i = 0; i < bufferTextures.Length; i++) + { + bufferTextures[i] = _bufferTextureRefs[i]?.GetBufferView(cbs, false) ?? default; + } + + return bufferTextures; + } + + public DescriptorSet[] GetDescriptorSets( + Device device, + CommandBufferScoped cbs, + DescriptorSetTemplateUpdater templateUpdater, + ShaderCollection program, + int setIndex, + TextureView dummyTexture, + SamplerHolder dummySampler) + { + if (TryGetCachedDescriptorSets(cbs, program, setIndex, out DescriptorSet[] sets)) + { + // We still need to ensure the current command buffer holds a reference to all used textures. + + if (!_isBuffer) + { + GetImageInfos(_gd, cbs, dummyTexture, dummySampler); + } + else + { + GetBufferViews(cbs); + } + + return sets; + } + + DescriptorSetTemplate template = program.Templates[setIndex]; + + DescriptorSetTemplateWriter tu = templateUpdater.Begin(template); + + if (!_isBuffer) + { + tu.Push(GetImageInfos(_gd, cbs, dummyTexture, dummySampler)); + } + else + { + tu.Push(GetBufferViews(cbs)); + } + + templateUpdater.Commit(_gd, device, sets[0]); + + return sets; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs b/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs new file mode 100644 index 00000000..e0694b19 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs @@ -0,0 +1,164 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Buffers; +using System.Collections.Generic; +using Format = Ryujinx.Graphics.GAL.Format; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + class TextureBuffer : ITexture + { + private readonly VulkanRenderer _gd; + + private BufferHandle _bufferHandle; + private int _offset; + private int _size; + private Auto _bufferView; + private Dictionary> _selfManagedViews; + + private int _bufferCount; + + public int Width { get; } + public int Height { get; } + + public VkFormat VkFormat { get; } + + public TextureBuffer(VulkanRenderer gd, TextureCreateInfo info) + { + _gd = gd; + Width = info.Width; + Height = info.Height; + VkFormat = FormatTable.GetFormat(info.Format); + + gd.Textures.Add(this); + } + + public void CopyTo(ITexture destination, int firstLayer, int firstLevel) + { + throw new NotSupportedException(); + } + + public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel) + { + throw new NotSupportedException(); + } + + public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter) + { + throw new NotSupportedException(); + } + + public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + throw new NotSupportedException(); + } + + public PinnedSpan GetData() + { + return _gd.GetBufferData(_bufferHandle, _offset, _size); + } + + public PinnedSpan GetData(int layer, int level) + { + return GetData(); + } + + public void CopyTo(BufferRange range, int layer, int level, int stride) + { + throw new NotImplementedException(); + } + + public void Release() + { + if (_gd.Textures.Remove(this)) + { + ReleaseImpl(); + } + } + + private void ReleaseImpl() + { + if (_selfManagedViews != null) + { + foreach (var bufferView in _selfManagedViews.Values) + { + bufferView.Dispose(); + } + + _selfManagedViews = null; + } + + _bufferView?.Dispose(); + _bufferView = null; + } + + /// + public void SetData(IMemoryOwner data) + { + _gd.SetBufferData(_bufferHandle, _offset, data.Memory.Span); + data.Dispose(); + } + + /// + public void SetData(IMemoryOwner data, int layer, int level) + { + throw new NotSupportedException(); + } + + /// + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) + { + throw new NotSupportedException(); + } + + public void SetStorage(BufferRange buffer) + { + if (_bufferHandle == buffer.Handle && + _offset == buffer.Offset && + _size == buffer.Size && + _bufferCount == _gd.BufferManager.BufferCount) + { + return; + } + + _bufferHandle = buffer.Handle; + _offset = buffer.Offset; + _size = buffer.Size; + _bufferCount = _gd.BufferManager.BufferCount; + + ReleaseImpl(); + } + + public BufferView GetBufferView(CommandBufferScoped cbs, bool write) + { + _bufferView ??= _gd.BufferManager.CreateView(_bufferHandle, VkFormat, _offset, _size, ReleaseImpl); + + return _bufferView?.Get(cbs, _offset, _size, write).Value ?? default; + } + + public BufferView GetBufferView(CommandBufferScoped cbs, Format format, bool write) + { + var vkFormat = FormatTable.GetFormat(format); + if (vkFormat == VkFormat) + { + return GetBufferView(cbs, write); + } + + if (_selfManagedViews != null && _selfManagedViews.TryGetValue(format, out var bufferView)) + { + return bufferView.Get(cbs, _offset, _size, write).Value; + } + + bufferView = _gd.BufferManager.CreateView(_bufferHandle, vkFormat, _offset, _size, ReleaseImpl); + + if (bufferView != null) + { + (_selfManagedViews ??= new Dictionary>()).Add(format, bufferView); + } + + return bufferView?.Get(cbs, _offset, _size, write).Value ?? default; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs b/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs new file mode 100644 index 00000000..45cddd77 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs @@ -0,0 +1,473 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Numerics; + +namespace Ryujinx.Graphics.Vulkan +{ + static class TextureCopy + { + public static void Blit( + Vk api, + CommandBuffer commandBuffer, + Image srcImage, + Image dstImage, + TextureCreateInfo srcInfo, + TextureCreateInfo dstInfo, + Extents2D srcRegion, + Extents2D dstRegion, + int srcLayer, + int dstLayer, + int srcLevel, + int dstLevel, + int layers, + int levels, + bool linearFilter, + ImageAspectFlags srcAspectFlags = 0, + ImageAspectFlags dstAspectFlags = 0) + { + static (Offset3D, Offset3D) ExtentsToOffset3D(Extents2D extents, int width, int height, int level) + { + static int Clamp(int value, int max) + { + return Math.Clamp(value, 0, max); + } + + var xy1 = new Offset3D(Clamp(extents.X1, width) >> level, Clamp(extents.Y1, height) >> level, 0); + var xy2 = new Offset3D(Clamp(extents.X2, width) >> level, Clamp(extents.Y2, height) >> level, 1); + + return (xy1, xy2); + } + + if (srcAspectFlags == 0) + { + srcAspectFlags = srcInfo.Format.ConvertAspectFlags(); + } + + if (dstAspectFlags == 0) + { + dstAspectFlags = dstInfo.Format.ConvertAspectFlags(); + } + + var srcOffsets = new ImageBlit.SrcOffsetsBuffer(); + var dstOffsets = new ImageBlit.DstOffsetsBuffer(); + + var filter = linearFilter && !dstInfo.Format.IsDepthOrStencil() ? Filter.Linear : Filter.Nearest; + + TextureView.InsertImageBarrier( + api, + commandBuffer, + srcImage, + TextureStorage.DefaultAccessMask, + AccessFlags.TransferReadBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.TransferBit, + srcAspectFlags, + srcLayer, + srcLevel, + layers, + levels); + + uint copySrcLevel = (uint)srcLevel; + uint copyDstLevel = (uint)dstLevel; + + for (int level = 0; level < levels; level++) + { + var srcSl = new ImageSubresourceLayers(srcAspectFlags, copySrcLevel, (uint)srcLayer, (uint)layers); + var dstSl = new ImageSubresourceLayers(dstAspectFlags, copyDstLevel, (uint)dstLayer, (uint)layers); + + (srcOffsets.Element0, srcOffsets.Element1) = ExtentsToOffset3D(srcRegion, srcInfo.Width, srcInfo.Height, level); + (dstOffsets.Element0, dstOffsets.Element1) = ExtentsToOffset3D(dstRegion, dstInfo.Width, dstInfo.Height, level); + + var region = new ImageBlit + { + SrcSubresource = srcSl, + SrcOffsets = srcOffsets, + DstSubresource = dstSl, + DstOffsets = dstOffsets, + }; + + api.CmdBlitImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, in region, filter); + + copySrcLevel++; + copyDstLevel++; + + if (srcInfo.Target == Target.Texture3D || dstInfo.Target == Target.Texture3D) + { + layers = Math.Max(1, layers >> 1); + } + } + + TextureView.InsertImageBarrier( + api, + commandBuffer, + dstImage, + AccessFlags.TransferWriteBit, + TextureStorage.DefaultAccessMask, + PipelineStageFlags.TransferBit, + PipelineStageFlags.AllCommandsBit, + dstAspectFlags, + dstLayer, + dstLevel, + layers, + levels); + } + + public static void Copy( + Vk api, + CommandBuffer commandBuffer, + Image srcImage, + Image dstImage, + TextureCreateInfo srcInfo, + TextureCreateInfo dstInfo, + int srcViewLayer, + int dstViewLayer, + int srcViewLevel, + int dstViewLevel, + int srcLayer, + int dstLayer, + int srcLevel, + int dstLevel) + { + int srcDepth = srcInfo.GetDepthOrLayers(); + int srcLevels = srcInfo.Levels; + + int dstDepth = dstInfo.GetDepthOrLayers(); + int dstLevels = dstInfo.Levels; + + if (dstInfo.Target == Target.Texture3D) + { + dstDepth = Math.Max(1, dstDepth >> dstLevel); + } + + int depth = Math.Min(srcDepth, dstDepth); + int levels = Math.Min(srcLevels, dstLevels); + + Copy( + api, + commandBuffer, + srcImage, + dstImage, + srcInfo, + dstInfo, + srcViewLayer, + dstViewLayer, + srcViewLevel, + dstViewLevel, + srcLayer, + dstLayer, + srcLevel, + dstLevel, + depth, + levels); + } + + private static int ClampLevels(TextureCreateInfo info, int levels) + { + int width = info.Width; + int height = info.Height; + int depth = info.Target == Target.Texture3D ? info.Depth : 1; + + int maxLevels = 1 + BitOperations.Log2((uint)Math.Max(Math.Max(width, height), depth)); + + if (levels > maxLevels) + { + levels = maxLevels; + } + + return levels; + } + + public static void Copy( + Vk api, + CommandBuffer commandBuffer, + Image srcImage, + Image dstImage, + TextureCreateInfo srcInfo, + TextureCreateInfo dstInfo, + int srcViewLayer, + int dstViewLayer, + int srcViewLevel, + int dstViewLevel, + int srcDepthOrLayer, + int dstDepthOrLayer, + int srcLevel, + int dstLevel, + int depthOrLayers, + int levels) + { + int srcZ; + int srcLayer; + int srcDepth; + int srcLayers; + + if (srcInfo.Target == Target.Texture3D) + { + srcZ = srcDepthOrLayer; + srcLayer = 0; + srcDepth = depthOrLayers; + srcLayers = 1; + } + else + { + srcZ = 0; + srcLayer = srcDepthOrLayer; + srcDepth = 1; + srcLayers = depthOrLayers; + } + + int dstZ; + int dstLayer; + int dstLayers; + + if (dstInfo.Target == Target.Texture3D) + { + dstZ = dstDepthOrLayer; + dstLayer = 0; + dstLayers = 1; + } + else + { + dstZ = 0; + dstLayer = dstDepthOrLayer; + dstLayers = depthOrLayers; + } + + int srcWidth = srcInfo.Width; + int srcHeight = srcInfo.Height; + + int dstWidth = dstInfo.Width; + int dstHeight = dstInfo.Height; + + srcWidth = Math.Max(1, srcWidth >> srcLevel); + srcHeight = Math.Max(1, srcHeight >> srcLevel); + + dstWidth = Math.Max(1, dstWidth >> dstLevel); + dstHeight = Math.Max(1, dstHeight >> dstLevel); + + int blockWidth = 1; + int blockHeight = 1; + bool sizeInBlocks = false; + + // When copying from a compressed to a non-compressed format, + // the non-compressed texture will have the size of the texture + // in blocks (not in texels), so we must adjust that size to + // match the size in texels of the compressed texture. + if (!srcInfo.IsCompressed && dstInfo.IsCompressed) + { + srcWidth *= dstInfo.BlockWidth; + srcHeight *= dstInfo.BlockHeight; + blockWidth = dstInfo.BlockWidth; + blockHeight = dstInfo.BlockHeight; + + sizeInBlocks = true; + } + else if (srcInfo.IsCompressed && !dstInfo.IsCompressed) + { + dstWidth *= srcInfo.BlockWidth; + dstHeight *= srcInfo.BlockHeight; + blockWidth = srcInfo.BlockWidth; + blockHeight = srcInfo.BlockHeight; + } + + int width = Math.Min(srcWidth, dstWidth); + int height = Math.Min(srcHeight, dstHeight); + + ImageAspectFlags srcAspect = srcInfo.Format.ConvertAspectFlags(); + ImageAspectFlags dstAspect = dstInfo.Format.ConvertAspectFlags(); + + TextureView.InsertImageBarrier( + api, + commandBuffer, + srcImage, + TextureStorage.DefaultAccessMask, + AccessFlags.TransferReadBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.TransferBit, + srcAspect, + srcViewLayer + srcLayer, + srcViewLevel + srcLevel, + srcLayers, + levels); + + for (int level = 0; level < levels; level++) + { + // Stop copy if we are already out of the levels range. + if (level >= srcInfo.Levels || dstLevel + level >= dstInfo.Levels) + { + break; + } + + var srcSl = new ImageSubresourceLayers( + srcAspect, + (uint)(srcViewLevel + srcLevel + level), + (uint)(srcViewLayer + srcLayer), + (uint)srcLayers); + + var dstSl = new ImageSubresourceLayers( + dstAspect, + (uint)(dstViewLevel + dstLevel + level), + (uint)(dstViewLayer + dstLayer), + (uint)dstLayers); + + int copyWidth = sizeInBlocks ? BitUtils.DivRoundUp(width, blockWidth) : width; + int copyHeight = sizeInBlocks ? BitUtils.DivRoundUp(height, blockHeight) : height; + + var extent = new Extent3D((uint)copyWidth, (uint)copyHeight, (uint)srcDepth); + + if (srcInfo.Samples > 1 && srcInfo.Samples != dstInfo.Samples) + { + var region = new ImageResolve(srcSl, new Offset3D(0, 0, srcZ), dstSl, new Offset3D(0, 0, dstZ), extent); + + api.CmdResolveImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, in region); + } + else + { + var region = new ImageCopy(srcSl, new Offset3D(0, 0, srcZ), dstSl, new Offset3D(0, 0, dstZ), extent); + + api.CmdCopyImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, in region); + } + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + + if (srcInfo.Target == Target.Texture3D) + { + srcDepth = Math.Max(1, srcDepth >> 1); + } + } + + TextureView.InsertImageBarrier( + api, + commandBuffer, + dstImage, + AccessFlags.TransferWriteBit, + TextureStorage.DefaultAccessMask, + PipelineStageFlags.TransferBit, + PipelineStageFlags.AllCommandsBit, + dstAspect, + dstViewLayer + dstLayer, + dstViewLevel + dstLevel, + dstLayers, + levels); + } + + public unsafe static void ResolveDepthStencil( + VulkanRenderer gd, + Device device, + CommandBufferScoped cbs, + TextureView src, + TextureView dst) + { + var dsAttachmentReference = new AttachmentReference2(StructureType.AttachmentReference2, null, 0, ImageLayout.General); + var dsResolveAttachmentReference = new AttachmentReference2(StructureType.AttachmentReference2, null, 1, ImageLayout.General); + + var subpassDsResolve = new SubpassDescriptionDepthStencilResolve + { + SType = StructureType.SubpassDescriptionDepthStencilResolve, + PDepthStencilResolveAttachment = &dsResolveAttachmentReference, + DepthResolveMode = ResolveModeFlags.SampleZeroBit, + StencilResolveMode = ResolveModeFlags.SampleZeroBit, + }; + + var subpass = new SubpassDescription2 + { + SType = StructureType.SubpassDescription2, + PipelineBindPoint = PipelineBindPoint.Graphics, + PDepthStencilAttachment = &dsAttachmentReference, + PNext = &subpassDsResolve, + }; + + AttachmentDescription2[] attachmentDescs = new AttachmentDescription2[2]; + + attachmentDescs[0] = new AttachmentDescription2( + StructureType.AttachmentDescription2, + null, + 0, + src.VkFormat, + TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)src.Info.Samples), + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + ImageLayout.General, + ImageLayout.General); + + attachmentDescs[1] = new AttachmentDescription2( + StructureType.AttachmentDescription2, + null, + 0, + dst.VkFormat, + TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)dst.Info.Samples), + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + AttachmentLoadOp.Load, + AttachmentStoreOp.Store, + ImageLayout.General, + ImageLayout.General); + + var subpassDependency = PipelineConverter.CreateSubpassDependency2(gd); + + fixed (AttachmentDescription2* pAttachmentDescs = attachmentDescs) + { + var renderPassCreateInfo = new RenderPassCreateInfo2 + { + SType = StructureType.RenderPassCreateInfo2, + PAttachments = pAttachmentDescs, + AttachmentCount = (uint)attachmentDescs.Length, + PSubpasses = &subpass, + SubpassCount = 1, + PDependencies = &subpassDependency, + DependencyCount = 1, + }; + + gd.Api.CreateRenderPass2(device, in renderPassCreateInfo, null, out var renderPass).ThrowOnError(); + + using var rp = new Auto(new DisposableRenderPass(gd.Api, device, renderPass)); + + ImageView* attachments = stackalloc ImageView[2]; + + var srcView = src.GetImageViewForAttachment(); + var dstView = dst.GetImageViewForAttachment(); + + attachments[0] = srcView.Get(cbs).Value; + attachments[1] = dstView.Get(cbs).Value; + + var framebufferCreateInfo = new FramebufferCreateInfo + { + SType = StructureType.FramebufferCreateInfo, + RenderPass = rp.Get(cbs).Value, + AttachmentCount = 2, + PAttachments = attachments, + Width = (uint)src.Width, + Height = (uint)src.Height, + Layers = (uint)src.Layers, + }; + + gd.Api.CreateFramebuffer(device, in framebufferCreateInfo, null, out var framebuffer).ThrowOnError(); + using var fb = new Auto(new DisposableFramebuffer(gd.Api, device, framebuffer), null, srcView, dstView); + + var renderArea = new Rect2D(null, new Extent2D((uint)src.Info.Width, (uint)src.Info.Height)); + var clearValue = new ClearValue(); + + var renderPassBeginInfo = new RenderPassBeginInfo + { + SType = StructureType.RenderPassBeginInfo, + RenderPass = rp.Get(cbs).Value, + Framebuffer = fb.Get(cbs).Value, + RenderArea = renderArea, + PClearValues = &clearValue, + ClearValueCount = 1, + }; + + // The resolve operation happens at the end of the subpass, so let's just do a begin/end + // to resolve the depth-stencil texture. + // TODO: Do speculative resolve and part of the same render pass as the draw to avoid + // ending the current render pass? + gd.Api.CmdBeginRenderPass(cbs.CommandBuffer, in renderPassBeginInfo, SubpassContents.Inline); + gd.Api.CmdEndRenderPass(cbs.CommandBuffer); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs new file mode 100644 index 00000000..10b36a3f --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs @@ -0,0 +1,618 @@ +using Ryujinx.Common; +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using Format = Ryujinx.Graphics.GAL.Format; +using VkBuffer = Silk.NET.Vulkan.Buffer; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + class TextureStorage : IDisposable + { + private struct TextureSliceInfo + { + public int BindCount; + } + + private const MemoryPropertyFlags DefaultImageMemoryFlags = + MemoryPropertyFlags.DeviceLocalBit; + + private const ImageUsageFlags DefaultUsageFlags = + ImageUsageFlags.SampledBit | + ImageUsageFlags.TransferSrcBit | + ImageUsageFlags.TransferDstBit; + + public const AccessFlags DefaultAccessMask = + AccessFlags.ShaderReadBit | + AccessFlags.ShaderWriteBit | + AccessFlags.ColorAttachmentReadBit | + AccessFlags.ColorAttachmentWriteBit | + AccessFlags.DepthStencilAttachmentReadBit | + AccessFlags.DepthStencilAttachmentWriteBit | + AccessFlags.TransferReadBit | + AccessFlags.TransferWriteBit; + + private readonly VulkanRenderer _gd; + + private readonly Device _device; + + private TextureCreateInfo _info; + + public TextureCreateInfo Info => _info; + + public bool Disposed { get; private set; } + + private readonly Image _image; + private readonly Auto _imageAuto; + private readonly Auto _allocationAuto; + private readonly int _depthOrLayers; + private Auto _foreignAllocationAuto; + + private Dictionary _aliasedStorages; + + private AccessFlags _lastModificationAccess; + private PipelineStageFlags _lastModificationStage; + private AccessFlags _lastReadAccess; + private PipelineStageFlags _lastReadStage; + + private int _viewsCount; + private readonly ulong _size; + + private int _bindCount; + private readonly TextureSliceInfo[] _slices; + + public VkFormat VkFormat { get; } + + public unsafe TextureStorage( + VulkanRenderer gd, + Device device, + TextureCreateInfo info, + Auto foreignAllocation = null) + { + _gd = gd; + _device = device; + _info = info; + + var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format); + var levels = (uint)info.Levels; + var layers = (uint)info.GetLayers(); + var depth = (uint)(info.Target == Target.Texture3D ? info.Depth : 1); + + VkFormat = format; + _depthOrLayers = info.GetDepthOrLayers(); + + var type = info.Target.Convert(); + + var extent = new Extent3D((uint)info.Width, (uint)info.Height, depth); + + var sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples); + + var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities); + + var flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit; + + // This flag causes mipmapped texture arrays to break on AMD GCN, so for that copy dependencies are forced for aliasing as cube. + bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray; + bool cubeCompatible = gd.IsAmdGcn ? isCube : (info.Width == info.Height && layers >= 6); + + if (type == ImageType.Type2D && cubeCompatible) + { + flags |= ImageCreateFlags.CreateCubeCompatibleBit; + } + + if (type == ImageType.Type3D && !gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.No3DImageView)) + { + flags |= ImageCreateFlags.Create2DArrayCompatibleBit; + } + + var imageCreateInfo = new ImageCreateInfo + { + SType = StructureType.ImageCreateInfo, + ImageType = type, + Format = format, + Extent = extent, + MipLevels = levels, + ArrayLayers = layers, + Samples = sampleCountFlags, + Tiling = ImageTiling.Optimal, + Usage = usage, + SharingMode = SharingMode.Exclusive, + InitialLayout = ImageLayout.Undefined, + Flags = flags, + }; + + gd.Api.CreateImage(device, in imageCreateInfo, null, out _image).ThrowOnError(); + + if (foreignAllocation == null) + { + gd.Api.GetImageMemoryRequirements(device, _image, out var requirements); + var allocation = gd.MemoryAllocator.AllocateDeviceMemory(requirements, DefaultImageMemoryFlags); + + if (allocation.Memory.Handle == 0UL) + { + gd.Api.DestroyImage(device, _image, null); + throw new Exception("Image initialization failed."); + } + + _size = requirements.Size; + + gd.Api.BindImageMemory(device, _image, allocation.Memory, allocation.Offset).ThrowOnError(); + + _allocationAuto = new Auto(allocation); + _imageAuto = new Auto(new DisposableImage(_gd.Api, device, _image), null, _allocationAuto); + + InitialTransition(ImageLayout.Undefined, ImageLayout.General); + } + else + { + _foreignAllocationAuto = foreignAllocation; + foreignAllocation.IncrementReferenceCount(); + var allocation = foreignAllocation.GetUnsafe(); + + gd.Api.BindImageMemory(device, _image, allocation.Memory, allocation.Offset).ThrowOnError(); + + _imageAuto = new Auto(new DisposableImage(_gd.Api, device, _image)); + + InitialTransition(ImageLayout.Preinitialized, ImageLayout.General); + } + + _slices = new TextureSliceInfo[levels * _depthOrLayers]; + } + + public TextureStorage CreateAliasedColorForDepthStorageUnsafe(Format format) + { + var colorFormat = format switch + { + Format.S8Uint => Format.R8Unorm, + Format.D16Unorm => Format.R16Unorm, + Format.D24UnormS8Uint or Format.S8UintD24Unorm or Format.X8UintD24Unorm => Format.R8G8B8A8Unorm, + Format.D32Float => Format.R32Float, + Format.D32FloatS8Uint => Format.R32G32Float, + _ => throw new ArgumentException($"\"{format}\" is not a supported depth or stencil format."), + }; + + return CreateAliasedStorageUnsafe(colorFormat); + } + + public TextureStorage CreateAliasedStorageUnsafe(Format format) + { + if (_aliasedStorages == null || !_aliasedStorages.TryGetValue(format, out var storage)) + { + _aliasedStorages ??= new Dictionary(); + + var info = NewCreateInfoWith(ref _info, format, _info.BytesPerPixel); + + storage = new TextureStorage(_gd, _device, info, _allocationAuto); + + _aliasedStorages.Add(format, storage); + } + + return storage; + } + + public static TextureCreateInfo NewCreateInfoWith(ref TextureCreateInfo info, Format format, int bytesPerPixel) + { + return NewCreateInfoWith(ref info, format, bytesPerPixel, info.Width, info.Height); + } + + public static TextureCreateInfo NewCreateInfoWith( + ref TextureCreateInfo info, + Format format, + int bytesPerPixel, + int width, + int height) + { + return new TextureCreateInfo( + width, + height, + info.Depth, + info.Levels, + info.Samples, + info.BlockWidth, + info.BlockHeight, + bytesPerPixel, + format, + info.DepthStencilMode, + info.Target, + info.SwizzleR, + info.SwizzleG, + info.SwizzleB, + info.SwizzleA); + } + + public Auto GetImage() + { + return _imageAuto; + } + + public Image GetImageForViewCreation() + { + return _image; + } + + public bool HasCommandBufferDependency(CommandBufferScoped cbs) + { + if (_foreignAllocationAuto != null) + { + return _foreignAllocationAuto.HasCommandBufferDependency(cbs); + } + else if (_allocationAuto != null) + { + return _allocationAuto.HasCommandBufferDependency(cbs); + } + + return false; + } + + private unsafe void InitialTransition(ImageLayout srcLayout, ImageLayout dstLayout) + { + CommandBufferScoped cbs; + bool useTempCbs = !_gd.CommandBufferPool.OwnedByCurrentThread; + + if (useTempCbs) + { + cbs = _gd.BackgroundResources.Get().GetPool().Rent(); + } + else + { + if (_gd.PipelineInternal != null) + { + cbs = _gd.PipelineInternal.GetPreloadCommandBuffer(); + } + else + { + cbs = _gd.CommandBufferPool.Rent(); + useTempCbs = true; + } + } + + var aspectFlags = _info.Format.ConvertAspectFlags(); + + var subresourceRange = new ImageSubresourceRange(aspectFlags, 0, (uint)_info.Levels, 0, (uint)_info.GetLayers()); + + var barrier = new ImageMemoryBarrier + { + SType = StructureType.ImageMemoryBarrier, + SrcAccessMask = 0, + DstAccessMask = DefaultAccessMask, + OldLayout = srcLayout, + NewLayout = dstLayout, + SrcQueueFamilyIndex = Vk.QueueFamilyIgnored, + DstQueueFamilyIndex = Vk.QueueFamilyIgnored, + Image = _imageAuto.Get(cbs).Value, + SubresourceRange = subresourceRange, + }; + + _gd.Api.CmdPipelineBarrier( + cbs.CommandBuffer, + PipelineStageFlags.TopOfPipeBit, + PipelineStageFlags.AllCommandsBit, + 0, + 0, + null, + 0, + null, + 1, + in barrier); + + if (useTempCbs) + { + cbs.Dispose(); + } + } + + public static ImageUsageFlags GetImageUsage(Format format, Target target, in HardwareCapabilities capabilities) + { + var usage = DefaultUsageFlags; + + if (format.IsDepthOrStencil()) + { + usage |= ImageUsageFlags.DepthStencilAttachmentBit; + } + else if (format.IsRtColorCompatible()) + { + usage |= ImageUsageFlags.ColorAttachmentBit; + } + + bool supportsMsStorage = capabilities.SupportsShaderStorageImageMultisample; + + if (format.IsImageCompatible() && (supportsMsStorage || !target.IsMultisample())) + { + usage |= ImageUsageFlags.StorageBit; + } + + if (capabilities.SupportsAttachmentFeedbackLoop && + (usage & (ImageUsageFlags.DepthStencilAttachmentBit | ImageUsageFlags.ColorAttachmentBit)) != 0) + { + usage |= ImageUsageFlags.AttachmentFeedbackLoopBitExt; + } + + return usage; + } + + public static SampleCountFlags ConvertToSampleCountFlags(SampleCountFlags supportedSampleCounts, uint samples) + { + if (samples == 0 || samples > (uint)SampleCountFlags.Count64Bit) + { + return SampleCountFlags.Count1Bit; + } + + // Round up to the nearest power of two. + SampleCountFlags converted = (SampleCountFlags)(1u << (31 - BitOperations.LeadingZeroCount(samples))); + + // Pick nearest sample count that the host actually supports. + while (converted != SampleCountFlags.Count1Bit && (converted & supportedSampleCounts) == 0) + { + converted = (SampleCountFlags)((uint)converted >> 1); + } + + return converted; + } + + public TextureView CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + return new TextureView(_gd, _device, info, this, firstLayer, firstLevel); + } + + public void CopyFromOrToBuffer( + CommandBuffer commandBuffer, + VkBuffer buffer, + Image image, + int size, + bool to, + int x, + int y, + int dstLayer, + int dstLevel, + int dstLayers, + int dstLevels, + bool singleSlice, + ImageAspectFlags aspectFlags, + bool forFlush) + { + bool is3D = Info.Target == Target.Texture3D; + int width = Info.Width; + int height = Info.Height; + int depth = is3D && !singleSlice ? Info.Depth : 1; + int layer = is3D ? 0 : dstLayer; + int layers = dstLayers; + int levels = dstLevels; + + int offset = 0; + + for (int level = 0; level < levels; level++) + { + int mipSize = Info.GetMipSize(level); + + if (forFlush) + { + mipSize = GetBufferDataLength(mipSize); + } + + int endOffset = offset + mipSize; + + if ((uint)endOffset > (uint)size) + { + break; + } + + int rowLength = (Info.GetMipStride(level) / Info.BytesPerPixel) * Info.BlockWidth; + + var sl = new ImageSubresourceLayers( + aspectFlags, + (uint)(dstLevel + level), + (uint)layer, + (uint)layers); + + var extent = new Extent3D((uint)width, (uint)height, (uint)depth); + + int z = is3D ? dstLayer : 0; + + var region = new BufferImageCopy( + (ulong)offset, + (uint)BitUtils.AlignUp(rowLength, Info.BlockWidth), + (uint)BitUtils.AlignUp(height, Info.BlockHeight), + sl, + new Offset3D(x, y, z), + extent); + + if (to) + { + _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region); + } + else + { + _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region); + } + + offset += mipSize; + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + + if (Info.Target == Target.Texture3D) + { + depth = Math.Max(1, depth >> 1); + } + } + } + + private int GetBufferDataLength(int length) + { + if (NeedsD24S8Conversion()) + { + return length * 2; + } + + return length; + } + + private bool NeedsD24S8Conversion() + { + return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint; + } + + public void AddStoreOpUsage(bool depthStencil) + { + _lastModificationStage = depthStencil ? + PipelineStageFlags.LateFragmentTestsBit : + PipelineStageFlags.ColorAttachmentOutputBit; + + _lastModificationAccess = depthStencil ? + AccessFlags.DepthStencilAttachmentWriteBit : + AccessFlags.ColorAttachmentWriteBit; + } + + public void QueueLoadOpBarrier(CommandBufferScoped cbs, bool depthStencil) + { + PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage; + PipelineStageFlags dstStageFlags = depthStencil ? + PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit : + PipelineStageFlags.ColorAttachmentOutputBit; + + AccessFlags srcAccessFlags = _lastModificationAccess | _lastReadAccess; + AccessFlags dstAccessFlags = depthStencil ? + AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.DepthStencilAttachmentReadBit : + AccessFlags.ColorAttachmentWriteBit | AccessFlags.ColorAttachmentReadBit; + + if (srcAccessFlags != AccessFlags.None) + { + ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags(); + ImageMemoryBarrier barrier = TextureView.GetImageBarrier( + _imageAuto.Get(cbs).Value, + srcAccessFlags, + dstAccessFlags, + aspectFlags, + 0, + 0, + _info.GetLayers(), + _info.Levels); + + _gd.Barriers.QueueBarrier(barrier, this, srcStageFlags, dstStageFlags); + + _lastReadStage = PipelineStageFlags.None; + _lastReadAccess = AccessFlags.None; + } + + _lastModificationStage = depthStencil ? + PipelineStageFlags.LateFragmentTestsBit : + PipelineStageFlags.ColorAttachmentOutputBit; + + _lastModificationAccess = depthStencil ? + AccessFlags.DepthStencilAttachmentWriteBit : + AccessFlags.ColorAttachmentWriteBit; + } + + public void QueueWriteToReadBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags) + { + _lastReadAccess |= dstAccessFlags; + _lastReadStage |= dstStageFlags; + + if (_lastModificationAccess != AccessFlags.None) + { + ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags(); + ImageMemoryBarrier barrier = TextureView.GetImageBarrier( + _imageAuto.Get(cbs).Value, + _lastModificationAccess, + dstAccessFlags, + aspectFlags, + 0, + 0, + _info.GetLayers(), + _info.Levels); + + _gd.Barriers.QueueBarrier(barrier, this, _lastModificationStage, dstStageFlags); + + _lastModificationAccess = AccessFlags.None; + } + } + + public void AddBinding(TextureView view) + { + // Assumes a view only has a first level. + + int index = view.FirstLevel * _depthOrLayers + view.FirstLayer; + int layers = view.Layers; + + for (int i = 0; i < layers; i++) + { + ref TextureSliceInfo info = ref _slices[index++]; + + info.BindCount++; + } + + _bindCount++; + } + + public void ClearBindings() + { + if (_bindCount != 0) + { + Array.Clear(_slices, 0, _slices.Length); + + _bindCount = 0; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsBound(TextureView view) + { + if (_bindCount != 0) + { + int index = view.FirstLevel * _depthOrLayers + view.FirstLayer; + int layers = view.Layers; + + for (int i = 0; i < layers; i++) + { + ref TextureSliceInfo info = ref _slices[index++]; + + if (info.BindCount != 0) + { + return true; + } + } + } + + return false; + } + + public void IncrementViewsCount() + { + _viewsCount++; + } + + public void DecrementViewsCount() + { + if (--_viewsCount == 0) + { + _gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_imageAuto, _size); + + Dispose(); + } + } + + public void Dispose() + { + Disposed = true; + + if (_aliasedStorages != null) + { + foreach (var storage in _aliasedStorages.Values) + { + storage.Dispose(); + } + + _aliasedStorages.Clear(); + } + + _imageAuto.Dispose(); + _allocationAuto?.Dispose(); + _foreignAllocationAuto?.DecrementReferenceCount(); + _foreignAllocationAuto = null; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs new file mode 100644 index 00000000..9b3f4666 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -0,0 +1,1152 @@ +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Format = Ryujinx.Graphics.GAL.Format; +using VkBuffer = Silk.NET.Vulkan.Buffer; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + class TextureView : ITexture, IDisposable + { + private readonly VulkanRenderer _gd; + + private readonly Device _device; + + private readonly Auto _imageView; + private readonly Auto _imageViewDraw; + private readonly Auto _imageViewIdentity; + private readonly Auto _imageView2dArray; + private Dictionary _selfManagedViews; + + private int _hazardUses; + + private readonly TextureCreateInfo _info; + + private HashTableSlim _renderPasses; + + public TextureCreateInfo Info => _info; + + public TextureStorage Storage { get; } + + public int Width => Info.Width; + public int Height => Info.Height; + public int Layers => Info.GetDepthOrLayers(); + public int FirstLayer { get; } + public int FirstLevel { get; } + public VkFormat VkFormat { get; } + private int _isValid; + public bool Valid => Volatile.Read(ref _isValid) != 0; + + public TextureView( + VulkanRenderer gd, + Device device, + TextureCreateInfo info, + TextureStorage storage, + int firstLayer, + int firstLevel) + { + _gd = gd; + _device = device; + _info = info; + Storage = storage; + FirstLayer = firstLayer; + FirstLevel = firstLevel; + + storage.IncrementViewsCount(); + + gd.Textures.Add(this); + + var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format); + var usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities); + var levels = (uint)info.Levels; + var layers = (uint)info.GetLayers(); + + VkFormat = format; + + var type = info.Target.ConvertView(); + + var swizzleR = info.SwizzleR.Convert(); + var swizzleG = info.SwizzleG.Convert(); + var swizzleB = info.SwizzleB.Convert(); + var swizzleA = info.SwizzleA.Convert(); + + if (info.Format == Format.R5G5B5A1Unorm || + info.Format == Format.R5G5B5X1Unorm || + info.Format == Format.R5G6B5Unorm) + { + (swizzleB, swizzleR) = (swizzleR, swizzleB); + } + else if (VkFormat == VkFormat.R4G4B4A4UnormPack16 || info.Format == Format.A1B5G5R5Unorm) + { + var tempB = swizzleB; + var tempA = swizzleA; + + swizzleB = swizzleG; + swizzleA = swizzleR; + swizzleR = tempA; + swizzleG = tempB; + } + + var componentMapping = new ComponentMapping(swizzleR, swizzleG, swizzleB, swizzleA); + + var aspectFlags = info.Format.ConvertAspectFlags(info.DepthStencilMode); + var aspectFlagsDepth = info.Format.ConvertAspectFlags(); + + var subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, layers); + var subresourceRangeDepth = new ImageSubresourceRange(aspectFlagsDepth, (uint)firstLevel, levels, (uint)firstLayer, layers); + + unsafe Auto CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType, ImageUsageFlags usageFlags) + { + var imageViewUsage = new ImageViewUsageCreateInfo + { + SType = StructureType.ImageViewUsageCreateInfo, + Usage = usageFlags, + }; + + var imageCreateInfo = new ImageViewCreateInfo + { + SType = StructureType.ImageViewCreateInfo, + Image = storage.GetImageForViewCreation(), + ViewType = viewType, + Format = format, + Components = cm, + SubresourceRange = sr, + PNext = &imageViewUsage, + }; + + gd.Api.CreateImageView(device, in imageCreateInfo, null, out var imageView).ThrowOnError(); + return new Auto(new DisposableImageView(gd.Api, device, imageView), null, storage.GetImage()); + } + + ImageUsageFlags shaderUsage = ImageUsageFlags.SampledBit; + + if (info.Format.IsImageCompatible() && (_gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample())) + { + shaderUsage |= ImageUsageFlags.StorageBit; + } + + _imageView = CreateImageView(componentMapping, subresourceRange, type, shaderUsage); + + // Framebuffer attachments and storage images requires a identity component mapping. + var identityComponentMapping = new ComponentMapping( + ComponentSwizzle.R, + ComponentSwizzle.G, + ComponentSwizzle.B, + ComponentSwizzle.A); + + _imageViewDraw = CreateImageView(identityComponentMapping, subresourceRangeDepth, type, usage); + _imageViewIdentity = aspectFlagsDepth == aspectFlags ? _imageViewDraw : CreateImageView(identityComponentMapping, subresourceRange, type, usage); + + // Framebuffer attachments also require 3D textures to be bound as 2D array. + if (info.Target == Target.Texture3D) + { + if (gd.Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.No3DImageView)) + { + if (levels == 1 && (info.Format.IsRtColorCompatible() || info.Format.IsDepthOrStencil())) + { + subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, 1); + + _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2D, ImageUsageFlags.ColorAttachmentBit); + } + } + else + { + subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, 1, (uint)firstLayer, (uint)info.Depth); + + _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray, usage); + } + } + + _isValid = 1; + } + + /// + /// Create a texture view for an existing swapchain image view. + /// Does not set storage, so only appropriate for swapchain use. + /// + /// Do not use this for normal textures, and make sure uses do not try to read storage. + public TextureView(VulkanRenderer gd, Device device, DisposableImageView view, TextureCreateInfo info, VkFormat format) + { + _gd = gd; + _device = device; + + _imageView = new Auto(view); + _imageViewDraw = _imageView; + _imageViewIdentity = _imageView; + _info = info; + + VkFormat = format; + + _isValid = 1; + } + + public Auto GetImage() + { + return Storage.GetImage(); + } + + public Auto GetImageView() + { + return _imageView; + } + + public Auto GetIdentityImageView() + { + return _imageViewIdentity; + } + + public Auto GetImageViewForAttachment() + { + return _imageView2dArray ?? _imageViewDraw; + } + + public void CopyTo(ITexture destination, int firstLayer, int firstLevel) + { + var src = this; + var dst = (TextureView)destination; + + if (!Valid || !dst.Valid) + { + return; + } + + _gd.PipelineInternal.EndRenderPass(); + + var cbs = _gd.PipelineInternal.CurrentCommandBuffer; + + var srcImage = src.GetImage().Get(cbs).Value; + var dstImage = dst.GetImage().Get(cbs).Value; + + if (!dst.Info.Target.IsMultisample() && Info.Target.IsMultisample()) + { + int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer); + _gd.HelperShader.CopyMSToNonMS(_gd, cbs, src, dst, 0, firstLayer, layers); + } + else if (dst.Info.Target.IsMultisample() && !Info.Target.IsMultisample()) + { + int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer); + _gd.HelperShader.CopyNonMSToMS(_gd, cbs, src, dst, 0, firstLayer, layers); + } + else if (dst.Info.BytesPerPixel != Info.BytesPerPixel) + { + int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer); + int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel); + _gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, 0, firstLayer, 0, firstLevel, layers, levels); + } + else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil()) + { + int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer); + int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel); + + _gd.HelperShader.CopyColor(_gd, cbs, src, dst, 0, firstLayer, 0, FirstLevel, layers, levels); + } + else + { + TextureCopy.Copy( + _gd.Api, + cbs.CommandBuffer, + srcImage, + dstImage, + src.Info, + dst.Info, + src.FirstLayer, + dst.FirstLayer, + src.FirstLevel, + dst.FirstLevel, + 0, + firstLayer, + 0, + firstLevel); + } + } + + public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel) + { + var src = this; + var dst = (TextureView)destination; + + if (!Valid || !dst.Valid) + { + return; + } + + _gd.PipelineInternal.EndRenderPass(); + + var cbs = _gd.PipelineInternal.CurrentCommandBuffer; + + var srcImage = src.GetImage().Get(cbs).Value; + var dstImage = dst.GetImage().Get(cbs).Value; + + if (!dst.Info.Target.IsMultisample() && Info.Target.IsMultisample()) + { + _gd.HelperShader.CopyMSToNonMS(_gd, cbs, src, dst, srcLayer, dstLayer, 1); + } + else if (dst.Info.Target.IsMultisample() && !Info.Target.IsMultisample()) + { + _gd.HelperShader.CopyNonMSToMS(_gd, cbs, src, dst, srcLayer, dstLayer, 1); + } + else if (dst.Info.BytesPerPixel != Info.BytesPerPixel) + { + _gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1); + } + else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil()) + { + _gd.HelperShader.CopyColor(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1); + } + else + { + TextureCopy.Copy( + _gd.Api, + cbs.CommandBuffer, + srcImage, + dstImage, + src.Info, + dst.Info, + src.FirstLayer, + dst.FirstLayer, + src.FirstLevel, + dst.FirstLevel, + srcLayer, + dstLayer, + srcLevel, + dstLevel, + 1, + 1); + } + } + + public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter) + { + var dst = (TextureView)destination; + + if (_gd.CommandBufferPool.OwnedByCurrentThread) + { + _gd.PipelineInternal.EndRenderPass(); + + var cbs = _gd.PipelineInternal.CurrentCommandBuffer; + + CopyToImpl(cbs, dst, srcRegion, dstRegion, linearFilter); + } + else + { + var cbp = _gd.BackgroundResources.Get().GetPool(); + + using var cbs = cbp.Rent(); + + CopyToImpl(cbs, dst, srcRegion, dstRegion, linearFilter); + } + } + + private void CopyToImpl(CommandBufferScoped cbs, TextureView dst, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter) + { + var src = this; + + var srcFormat = GetCompatibleGalFormat(src.Info.Format); + var dstFormat = GetCompatibleGalFormat(dst.Info.Format); + + bool srcUsesStorageFormat = src.VkFormat == src.Storage.VkFormat; + bool dstUsesStorageFormat = dst.VkFormat == dst.Storage.VkFormat; + + int layers = Math.Min(dst.Info.GetDepthOrLayers(), src.Info.GetDepthOrLayers()); + int levels = Math.Min(dst.Info.Levels, src.Info.Levels); + + if (srcUsesStorageFormat && dstUsesStorageFormat) + { + if ((srcRegion.X1 | dstRegion.X1) == 0 && + (srcRegion.Y1 | dstRegion.Y1) == 0 && + srcRegion.X2 == src.Width && + srcRegion.Y2 == src.Height && + dstRegion.X2 == dst.Width && + dstRegion.Y2 == dst.Height && + src.Width == dst.Width && + src.Height == dst.Height && + src.VkFormat == dst.VkFormat) + { + if (src.Info.Samples > 1 && src.Info.Samples != dst.Info.Samples && src.Info.Format.IsDepthOrStencil()) + { + // CmdResolveImage does not support depth-stencil resolve, so we need to use an alternative path + // for those textures. + TextureCopy.ResolveDepthStencil(_gd, _device, cbs, src, dst); + } + else + { + TextureCopy.Copy( + _gd.Api, + cbs.CommandBuffer, + src.GetImage().Get(cbs).Value, + dst.GetImage().Get(cbs).Value, + src.Info, + dst.Info, + src.FirstLayer, + dst.FirstLayer, + src.FirstLevel, + dst.FirstLevel, + 0, + 0, + 0, + 0, + layers, + levels); + } + + return; + } + + if (_gd.FormatCapabilities.OptimalFormatSupports(FormatFeatureFlags.BlitSrcBit, srcFormat) && + _gd.FormatCapabilities.OptimalFormatSupports(FormatFeatureFlags.BlitDstBit, dstFormat)) + { + TextureCopy.Blit( + _gd.Api, + cbs.CommandBuffer, + src.GetImage().Get(cbs).Value, + dst.GetImage().Get(cbs).Value, + src.Info, + dst.Info, + srcRegion, + dstRegion, + src.FirstLayer, + dst.FirstLayer, + src.FirstLevel, + dst.FirstLevel, + layers, + levels, + linearFilter); + + return; + } + } + + bool isDepthOrStencil = dst.Info.Format.IsDepthOrStencil(); + + if (!VulkanConfiguration.UseUnsafeBlit || (_gd.Vendor != Vendor.Nvidia && _gd.Vendor != Vendor.Intel)) + { + _gd.HelperShader.Blit( + _gd, + src, + dst, + srcRegion, + dstRegion, + layers, + levels, + isDepthOrStencil, + linearFilter); + + return; + } + + Auto srcImage; + Auto dstImage; + + if (isDepthOrStencil) + { + srcImage = src.Storage.CreateAliasedColorForDepthStorageUnsafe(srcFormat).GetImage(); + dstImage = dst.Storage.CreateAliasedColorForDepthStorageUnsafe(dstFormat).GetImage(); + } + else + { + srcImage = src.Storage.CreateAliasedStorageUnsafe(srcFormat).GetImage(); + dstImage = dst.Storage.CreateAliasedStorageUnsafe(dstFormat).GetImage(); + } + + TextureCopy.Blit( + _gd.Api, + cbs.CommandBuffer, + srcImage.Get(cbs).Value, + dstImage.Get(cbs).Value, + src.Info, + dst.Info, + srcRegion, + dstRegion, + src.FirstLayer, + dst.FirstLayer, + src.FirstLevel, + dst.FirstLevel, + layers, + levels, + linearFilter, + ImageAspectFlags.ColorBit, + ImageAspectFlags.ColorBit); + } + + public static unsafe void InsertMemoryBarrier( + Vk api, + CommandBuffer commandBuffer, + AccessFlags srcAccessMask, + AccessFlags dstAccessMask, + PipelineStageFlags srcStageMask, + PipelineStageFlags dstStageMask) + { + MemoryBarrier memoryBarrier = new() + { + SType = StructureType.MemoryBarrier, + SrcAccessMask = srcAccessMask, + DstAccessMask = dstAccessMask, + }; + + api.CmdPipelineBarrier( + commandBuffer, + srcStageMask, + dstStageMask, + DependencyFlags.None, + 1, + in memoryBarrier, + 0, + null, + 0, + null); + } + + public static ImageMemoryBarrier GetImageBarrier( + Image image, + AccessFlags srcAccessMask, + AccessFlags dstAccessMask, + ImageAspectFlags aspectFlags, + int firstLayer, + int firstLevel, + int layers, + int levels) + { + return new() + { + SType = StructureType.ImageMemoryBarrier, + SrcAccessMask = srcAccessMask, + DstAccessMask = dstAccessMask, + SrcQueueFamilyIndex = Vk.QueueFamilyIgnored, + DstQueueFamilyIndex = Vk.QueueFamilyIgnored, + Image = image, + OldLayout = ImageLayout.General, + NewLayout = ImageLayout.General, + SubresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, (uint)levels, (uint)firstLayer, (uint)layers), + }; + } + + public static unsafe void InsertImageBarrier( + Vk api, + CommandBuffer commandBuffer, + Image image, + AccessFlags srcAccessMask, + AccessFlags dstAccessMask, + PipelineStageFlags srcStageMask, + PipelineStageFlags dstStageMask, + ImageAspectFlags aspectFlags, + int firstLayer, + int firstLevel, + int layers, + int levels) + { + ImageMemoryBarrier memoryBarrier = GetImageBarrier( + image, + srcAccessMask, + dstAccessMask, + aspectFlags, + firstLayer, + firstLevel, + layers, + levels); + + api.CmdPipelineBarrier( + commandBuffer, + srcStageMask, + dstStageMask, + 0, + 0, + null, + 0, + null, + 1, + in memoryBarrier); + } + + public TextureView GetView(Format format) + { + if (format == Info.Format) + { + return this; + } + + if (_selfManagedViews != null && _selfManagedViews.TryGetValue(format, out var view)) + { + return view; + } + + view = CreateViewImpl(new TextureCreateInfo( + Info.Width, + Info.Height, + Info.Depth, + Info.Levels, + Info.Samples, + Info.BlockWidth, + Info.BlockHeight, + Info.BytesPerPixel, + format, + Info.DepthStencilMode, + Info.Target, + Info.SwizzleR, + Info.SwizzleG, + Info.SwizzleB, + Info.SwizzleA), 0, 0); + + (_selfManagedViews ??= new Dictionary()).Add(format, view); + + return view; + } + + public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel) + { + return CreateViewImpl(info, firstLayer, firstLevel); + } + + public TextureView CreateViewImpl(TextureCreateInfo info, int firstLayer, int firstLevel) + { + return new TextureView(_gd, _device, info, Storage, FirstLayer + firstLayer, FirstLevel + firstLevel); + } + + public byte[] GetData(int x, int y, int width, int height) + { + int size = width * height * Info.BytesPerPixel; + using var bufferHolder = _gd.BufferManager.Create(_gd, size); + + using (var cbs = _gd.CommandBufferPool.Rent()) + { + var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value; + var image = GetImage().Get(cbs).Value; + + CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, 0, 0, x, y, width, height); + } + + bufferHolder.WaitForFences(); + byte[] bitmap = new byte[size]; + GetDataFromBuffer(bufferHolder.GetDataStorage(0, size), size, Span.Empty).CopyTo(bitmap); + return bitmap; + } + + public PinnedSpan GetData() + { + BackgroundResource resources = _gd.BackgroundResources.Get(); + + if (_gd.CommandBufferPool.OwnedByCurrentThread) + { + _gd.FlushAllCommands(); + + return PinnedSpan.UnsafeFromSpan(GetData(_gd.CommandBufferPool, resources.GetFlushBuffer())); + } + + return PinnedSpan.UnsafeFromSpan(GetData(resources.GetPool(), resources.GetFlushBuffer())); + } + + public PinnedSpan GetData(int layer, int level) + { + BackgroundResource resources = _gd.BackgroundResources.Get(); + + if (_gd.CommandBufferPool.OwnedByCurrentThread) + { + _gd.FlushAllCommands(); + + return PinnedSpan.UnsafeFromSpan(GetData(_gd.CommandBufferPool, resources.GetFlushBuffer(), layer, level)); + } + + return PinnedSpan.UnsafeFromSpan(GetData(resources.GetPool(), resources.GetFlushBuffer(), layer, level)); + } + + public void CopyTo(BufferRange range, int layer, int level, int stride) + { + _gd.PipelineInternal.EndRenderPass(); + var cbs = _gd.PipelineInternal.CurrentCommandBuffer; + + int outSize = Info.GetMipSize(level); + int hostSize = GetBufferDataLength(outSize); + + var image = GetImage().Get(cbs).Value; + int offset = range.Offset; + + Auto autoBuffer = _gd.BufferManager.GetBuffer(cbs.CommandBuffer, range.Handle, true); + VkBuffer buffer = autoBuffer.Get(cbs, range.Offset, outSize).Value; + + if (PrepareOutputBuffer(cbs, hostSize, buffer, out VkBuffer copyToBuffer, out BufferHolder tempCopyHolder)) + { + // No barrier necessary, as this is a temporary copy buffer. + offset = 0; + } + else + { + BufferHolder.InsertBufferBarrier( + _gd, + cbs.CommandBuffer, + copyToBuffer, + BufferHolder.DefaultAccessFlags, + AccessFlags.TransferWriteBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.TransferBit, + offset, + outSize); + } + + InsertImageBarrier( + _gd.Api, + cbs.CommandBuffer, + image, + TextureStorage.DefaultAccessMask, + AccessFlags.TransferReadBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.TransferBit, + Info.Format.ConvertAspectFlags(), + FirstLayer + layer, + FirstLevel + level, + 1, + 1); + + CopyFromOrToBuffer(cbs.CommandBuffer, copyToBuffer, image, hostSize, true, layer, level, 1, 1, singleSlice: true, offset, stride); + + if (tempCopyHolder != null) + { + CopyDataToOutputBuffer(cbs, tempCopyHolder, autoBuffer, hostSize, range.Offset); + tempCopyHolder.Dispose(); + } + else + { + BufferHolder.InsertBufferBarrier( + _gd, + cbs.CommandBuffer, + copyToBuffer, + AccessFlags.TransferWriteBit, + BufferHolder.DefaultAccessFlags, + PipelineStageFlags.TransferBit, + PipelineStageFlags.AllCommandsBit, + offset, + outSize); + } + } + + private ReadOnlySpan GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer) + { + int size = 0; + + for (int level = 0; level < Info.Levels; level++) + { + size += Info.GetMipSize(level); + } + + size = GetBufferDataLength(size); + + Span result = flushBuffer.GetTextureData(cbp, this, size); + return GetDataFromBuffer(result, size, result); + } + + private ReadOnlySpan GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer, int layer, int level) + { + int size = GetBufferDataLength(Info.GetMipSize(level)); + + Span result = flushBuffer.GetTextureData(cbp, this, size, layer, level); + return GetDataFromBuffer(result, size, result); + } + + /// + public void SetData(IMemoryOwner data) + { + SetData(data.Memory.Span, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false); + data.Dispose(); + } + + /// + public void SetData(IMemoryOwner data, int layer, int level) + { + SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true); + data.Dispose(); + } + + /// + public void SetData(IMemoryOwner data, int layer, int level, Rectangle region) + { + SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true, region); + data.Dispose(); + } + + private void SetData(ReadOnlySpan data, int layer, int level, int layers, int levels, bool singleSlice, Rectangle? region = null) + { + int bufferDataLength = GetBufferDataLength(data.Length); + + using var bufferHolder = _gd.BufferManager.Create(_gd, bufferDataLength); + + Auto imageAuto = GetImage(); + + // Load texture data inline if the texture has been used on the current command buffer. + + bool loadInline = Storage.HasCommandBufferDependency(_gd.PipelineInternal.CurrentCommandBuffer); + + var cbs = loadInline ? _gd.PipelineInternal.CurrentCommandBuffer : _gd.PipelineInternal.GetPreloadCommandBuffer(); + + if (loadInline) + { + _gd.PipelineInternal.EndRenderPass(); + } + + CopyDataToBuffer(bufferHolder.GetDataStorage(0, bufferDataLength), data); + + var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value; + var image = imageAuto.Get(cbs).Value; + + if (region.HasValue) + { + CopyFromOrToBuffer( + cbs.CommandBuffer, + buffer, + image, + bufferDataLength, + false, + layer, + level, + region.Value.X, + region.Value.Y, + region.Value.Width, + region.Value.Height); + } + else + { + CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, bufferDataLength, false, layer, level, layers, levels, singleSlice); + } + } + + private int GetBufferDataLength(int length) + { + if (NeedsD24S8Conversion()) + { + return length * 2; + } + + return length; + } + + private Format GetCompatibleGalFormat(Format format) + { + if (NeedsD24S8Conversion()) + { + return Format.D32FloatS8Uint; + } + + return format; + } + + private void CopyDataToBuffer(Span storage, ReadOnlySpan input) + { + if (NeedsD24S8Conversion()) + { + FormatConverter.ConvertD24S8ToD32FS8(storage, input); + return; + } + + input.CopyTo(storage); + } + + private ReadOnlySpan GetDataFromBuffer(ReadOnlySpan storage, int size, Span output) + { + if (NeedsD24S8Conversion()) + { + if (output.IsEmpty) + { + output = new byte[GetBufferDataLength(size)]; + } + + FormatConverter.ConvertD32FS8ToD24S8(output, storage); + return output; + } + + return storage; + } + + private bool PrepareOutputBuffer(CommandBufferScoped cbs, int hostSize, VkBuffer target, out VkBuffer copyTarget, out BufferHolder copyTargetHolder) + { + if (NeedsD24S8Conversion()) + { + copyTargetHolder = _gd.BufferManager.Create(_gd, hostSize); + copyTarget = copyTargetHolder.GetBuffer().Get(cbs, 0, hostSize).Value; + + return true; + } + + copyTarget = target; + copyTargetHolder = null; + + return false; + } + + private void CopyDataToOutputBuffer(CommandBufferScoped cbs, BufferHolder hostData, Auto copyTarget, int hostSize, int dstOffset) + { + if (NeedsD24S8Conversion()) + { + _gd.HelperShader.ConvertD32S8ToD24S8(_gd, cbs, hostData, copyTarget, hostSize / (2 * sizeof(int)), dstOffset); + } + } + + private bool NeedsD24S8Conversion() + { + return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint; + } + + public void CopyFromOrToBuffer( + CommandBuffer commandBuffer, + VkBuffer buffer, + Image image, + int size, + bool to, + int dstLayer, + int dstLevel, + int dstLayers, + int dstLevels, + bool singleSlice, + int offset = 0, + int stride = 0) + { + bool is3D = Info.Target == Target.Texture3D; + int width = Math.Max(1, Info.Width >> dstLevel); + int height = Math.Max(1, Info.Height >> dstLevel); + int depth = is3D && !singleSlice ? Math.Max(1, Info.Depth >> dstLevel) : 1; + int layer = is3D ? 0 : dstLayer; + int layers = dstLayers; + int levels = dstLevels; + + for (int level = 0; level < levels; level++) + { + int mipSize = GetBufferDataLength(is3D && !singleSlice + ? Info.GetMipSize(dstLevel + level) + : Info.GetMipSize2D(dstLevel + level) * dstLayers); + + int endOffset = offset + mipSize; + + if ((uint)endOffset > (uint)size) + { + break; + } + + int rowLength = ((stride == 0 ? Info.GetMipStride(dstLevel + level) : stride) / Info.BytesPerPixel) * Info.BlockWidth; + + var aspectFlags = Info.Format.ConvertAspectFlags(); + + if (aspectFlags == (ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit)) + { + aspectFlags = ImageAspectFlags.DepthBit; + } + + var sl = new ImageSubresourceLayers( + aspectFlags, + (uint)(FirstLevel + dstLevel + level), + (uint)(FirstLayer + layer), + (uint)layers); + + var extent = new Extent3D((uint)width, (uint)height, (uint)depth); + + int z = is3D ? dstLayer : 0; + + var region = new BufferImageCopy( + (ulong)offset, + (uint)AlignUpNpot(rowLength, Info.BlockWidth), + (uint)AlignUpNpot(height, Info.BlockHeight), + sl, + new Offset3D(0, 0, z), + extent); + + if (to) + { + _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region); + } + else + { + _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region); + } + + offset += mipSize; + + width = Math.Max(1, width >> 1); + height = Math.Max(1, height >> 1); + + if (Info.Target == Target.Texture3D) + { + depth = Math.Max(1, depth >> 1); + } + } + } + + private void CopyFromOrToBuffer( + CommandBuffer commandBuffer, + VkBuffer buffer, + Image image, + int size, + bool to, + int dstLayer, + int dstLevel, + int x, + int y, + int width, + int height) + { + var aspectFlags = Info.Format.ConvertAspectFlags(); + + if (aspectFlags == (ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit)) + { + aspectFlags = ImageAspectFlags.DepthBit; + } + + var sl = new ImageSubresourceLayers(aspectFlags, (uint)(FirstLevel + dstLevel), (uint)(FirstLayer + dstLayer), 1); + + var extent = new Extent3D((uint)width, (uint)height, 1); + + int rowLengthAlignment = Info.BlockWidth; + + // We expect all data being written into the texture to have a stride aligned by 4. + if (!to && Info.BytesPerPixel < 4) + { + rowLengthAlignment = 4 / Info.BytesPerPixel; + } + + var region = new BufferImageCopy( + 0, + (uint)AlignUpNpot(width, rowLengthAlignment), + (uint)AlignUpNpot(height, Info.BlockHeight), + sl, + new Offset3D(x, y, 0), + extent); + + if (to) + { + _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, in region); + } + else + { + _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, in region); + } + } + + private static int AlignUpNpot(int size, int alignment) + { + int remainder = size % alignment; + if (remainder == 0) + { + return size; + } + + return size + (alignment - remainder); + } + + public void SetStorage(BufferRange buffer) + { + throw new NotImplementedException(); + } + + public void PrepareForUsage(CommandBufferScoped cbs, PipelineStageFlags flags, List feedbackLoopHazards) + { + Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, flags); + + if (feedbackLoopHazards != null && Storage.IsBound(this)) + { + feedbackLoopHazards.Add(this); + _hazardUses++; + } + } + + public void ClearUsage(List feedbackLoopHazards) + { + if (_hazardUses != 0 && feedbackLoopHazards != null) + { + feedbackLoopHazards.Remove(this); + _hazardUses--; + } + } + + public void DecrementHazardUses() + { + if (_hazardUses != 0) + { + _hazardUses--; + } + } + + public (RenderPassHolder rpHolder, Auto framebuffer) GetPassAndFramebuffer( + VulkanRenderer gd, + Device device, + CommandBufferScoped cbs, + FramebufferParams fb) + { + var key = fb.GetRenderPassCacheKey(); + + if (_renderPasses == null || !_renderPasses.TryGetValue(ref key, out RenderPassHolder rpHolder)) + { + rpHolder = new RenderPassHolder(gd, device, key, fb); + } + + return (rpHolder, rpHolder.GetFramebuffer(gd, cbs, fb)); + } + + public void AddRenderPass(RenderPassCacheKey key, RenderPassHolder renderPass) + { + _renderPasses ??= new HashTableSlim(); + + _renderPasses.Add(ref key, renderPass); + } + + public void RemoveRenderPass(RenderPassCacheKey key) + { + _renderPasses.Remove(ref key); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + bool wasValid = Interlocked.Exchange(ref _isValid, 0) != 0; + if (wasValid) + { + _gd.Textures.Remove(this); + + _imageView.Dispose(); + _imageView2dArray?.Dispose(); + + if (_imageViewIdentity != _imageView) + { + _imageViewIdentity.Dispose(); + } + + if (_imageViewDraw != _imageViewIdentity) + { + _imageViewDraw.Dispose(); + } + + Storage?.DecrementViewsCount(); + + if (_renderPasses != null) + { + var renderPasses = _renderPasses.Values.ToArray(); + + foreach (var pass in renderPasses) + { + pass.Dispose(); + } + } + + if (_selfManagedViews != null) + { + foreach (var view in _selfManagedViews.Values) + { + view.Dispose(); + } + + _selfManagedViews = null; + } + } + } + } + + public void Dispose() + { + Dispose(true); + } + + public void Release() + { + Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Vendor.cs b/src/Ryujinx.Graphics.Vulkan/Vendor.cs new file mode 100644 index 00000000..55ae0cd8 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Vendor.cs @@ -0,0 +1,100 @@ +using Silk.NET.Vulkan; +using System.Text.RegularExpressions; + +namespace Ryujinx.Graphics.Vulkan +{ + enum Vendor + { + Amd, + ImgTec, + Intel, + Nvidia, + ARM, + Broadcom, + Qualcomm, + Apple, + Unknown, + } + + static partial class VendorUtils + { + [GeneratedRegex("Radeon (((HD|R(5|7|9|X)) )?((M?[2-6]\\d{2}(\\D|$))|([7-8]\\d{3}(\\D|$))|Fury|Nano))|(Pro Duo)")] + public static partial Regex AmdGcnRegex(); + + [GeneratedRegex("NVIDIA GeForce (R|G)?TX? (\\d{3}\\d?)M?")] + public static partial Regex NvidiaConsumerClassRegex(); + + public static Vendor FromId(uint id) + { + return id switch + { + 0x1002 => Vendor.Amd, + 0x1010 => Vendor.ImgTec, + 0x106B => Vendor.Apple, + 0x10DE => Vendor.Nvidia, + 0x13B5 => Vendor.ARM, + 0x14E4 => Vendor.Broadcom, + 0x8086 => Vendor.Intel, + 0x5143 => Vendor.Qualcomm, + _ => Vendor.Unknown, + }; + } + + public static string GetNameFromId(uint id) + { + return id switch + { + 0x1002 => "AMD", + 0x1010 => "ImgTec", + 0x106B => "Apple", + 0x10DE => "NVIDIA", + 0x13B5 => "ARM", + 0x14E4 => "Broadcom", + 0x1AE0 => "Google", + 0x5143 => "Qualcomm", + 0x8086 => "Intel", + 0x10001 => "Vivante", + 0x10002 => "VeriSilicon", + 0x10003 => "Kazan", + 0x10004 => "Codeplay Software Ltd.", + 0x10005 => "Mesa", + 0x10006 => "PoCL", + _ => $"0x{id:X}", + }; + } + + public static string GetFriendlyDriverName(DriverId id) + { + return id switch + { + DriverId.AmdProprietary => "AMD", + DriverId.AmdOpenSource => "AMD (Open)", + DriverId.MesaRadv => "RADV", + DriverId.NvidiaProprietary => "NVIDIA", + DriverId.IntelProprietaryWindows => "Intel", + DriverId.IntelOpenSourceMesa => "Intel (Open)", + DriverId.ImaginationProprietary => "Imagination", + DriverId.QualcommProprietary => "Qualcomm", + DriverId.ArmProprietary => "ARM", + DriverId.GoogleSwiftshader => "SwiftShader", + DriverId.GgpProprietary => "GGP", + DriverId.BroadcomProprietary => "Broadcom", + DriverId.MesaLlvmpipe => "LLVMpipe", + DriverId.Moltenvk => "MoltenVK", + DriverId.CoreaviProprietary => "CoreAVI", + DriverId.JuiceProprietary => "Juice", + DriverId.VerisiliconProprietary => "Verisilicon", + DriverId.MesaTurnip => "Turnip", + DriverId.MesaV3DV => "V3DV", + DriverId.MesaPanvk => "PanVK", + DriverId.SamsungProprietary => "Samsung", + DriverId.MesaVenus => "Venus", + DriverId.MesaDozen => "Dozen", + DriverId.MesaNvk => "NVK", + DriverId.ImaginationOpenSourceMesa => "Imagination (Open)", + DriverId.MesaAgxv => "Honeykrisp", + _ => id.ToString(), + }; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/VertexBufferState.cs b/src/Ryujinx.Graphics.Vulkan/VertexBufferState.cs new file mode 100644 index 00000000..6f27bb68 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/VertexBufferState.cs @@ -0,0 +1,137 @@ +using Ryujinx.Graphics.GAL; + +namespace Ryujinx.Graphics.Vulkan +{ + internal struct VertexBufferState + { + private const int VertexBufferMaxMirrorable = 0x20000; + + public static VertexBufferState Null => new(null, 0, 0, 0); + + private readonly int _offset; + private readonly int _size; + private readonly int _stride; + + private readonly BufferHandle _handle; + private Auto _buffer; + + internal readonly int DescriptorIndex; + internal int AttributeScalarAlignment; + + public VertexBufferState(Auto buffer, int descriptorIndex, int offset, int size, int stride = 0) + { + _buffer = buffer; + _handle = BufferHandle.Null; + + _offset = offset; + _size = size; + _stride = stride; + + DescriptorIndex = descriptorIndex; + AttributeScalarAlignment = 1; + + buffer?.IncrementReferenceCount(); + } + + public VertexBufferState(BufferHandle handle, int descriptorIndex, int offset, int size, int stride = 0) + { + // This buffer state may be rewritten at bind time, so it must be retrieved on bind. + + _buffer = null; + _handle = handle; + + _offset = offset; + _size = size; + _stride = stride; + + DescriptorIndex = descriptorIndex; + AttributeScalarAlignment = 1; + } + + public void BindVertexBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding, ref PipelineState state, VertexBufferUpdater updater) + { + var autoBuffer = _buffer; + + if (_handle != BufferHandle.Null) + { + // May need to restride the vertex buffer. + + if (gd.NeedsVertexBufferAlignment(AttributeScalarAlignment, out int alignment) && (_stride % alignment) != 0) + { + autoBuffer = gd.BufferManager.GetAlignedVertexBuffer(cbs, _handle, _offset, _size, _stride, alignment); + + if (autoBuffer != null) + { + int stride = (_stride + (alignment - 1)) & -alignment; + int newSize = (_size / _stride) * stride; + + var buffer = autoBuffer.Get(cbs, 0, newSize).Value; + + updater.BindVertexBuffer(cbs, binding, buffer, 0, (ulong)newSize, (ulong)stride); + + _buffer = autoBuffer; + + state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)stride; + } + + return; + } + + autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int size); + + // The original stride must be reapplied in case it was rewritten. + state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride; + + if (_offset >= size) + { + autoBuffer = null; + } + } + + if (autoBuffer != null) + { + int offset = _offset; + bool mirrorable = _size <= VertexBufferMaxMirrorable; + var buffer = mirrorable ? autoBuffer.GetMirrorable(cbs, ref offset, _size, out _).Value : autoBuffer.Get(cbs, offset, _size).Value; + + updater.BindVertexBuffer(cbs, binding, buffer, (ulong)offset, (ulong)_size, (ulong)_stride); + } + } + + public readonly bool BoundEquals(Auto buffer) + { + return _buffer == buffer; + } + + public readonly bool Overlaps(Auto buffer, int offset, int size) + { + return buffer == _buffer && offset < _offset + _size && offset + size > _offset; + } + + public readonly bool Matches(Auto buffer, int descriptorIndex, int offset, int size, int stride = 0) + { + return _buffer == buffer && DescriptorIndex == descriptorIndex && _offset == offset && _size == size && _stride == stride; + } + + public void Swap(Auto from, Auto to) + { + if (_buffer == from) + { + _buffer.DecrementReferenceCount(); + to.IncrementReferenceCount(); + + _buffer = to; + } + } + + public readonly void Dispose() + { + // Only dispose if this buffer is not refetched on each bind. + + if (_handle == BufferHandle.Null) + { + _buffer?.DecrementReferenceCount(); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs b/src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs new file mode 100644 index 00000000..94269dd7 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs @@ -0,0 +1,82 @@ +using System; +using VkBuffer = Silk.NET.Vulkan.Buffer; + +namespace Ryujinx.Graphics.Vulkan +{ + internal class VertexBufferUpdater : IDisposable + { + private readonly VulkanRenderer _gd; + + private uint _baseBinding; + private uint _count; + + private readonly NativeArray _buffers; + private readonly NativeArray _offsets; + private readonly NativeArray _sizes; + private readonly NativeArray _strides; + + public VertexBufferUpdater(VulkanRenderer gd) + { + _gd = gd; + + _buffers = new NativeArray(Constants.MaxVertexBuffers); + _offsets = new NativeArray(Constants.MaxVertexBuffers); + _sizes = new NativeArray(Constants.MaxVertexBuffers); + _strides = new NativeArray(Constants.MaxVertexBuffers); + } + + public void BindVertexBuffer(CommandBufferScoped cbs, uint binding, VkBuffer buffer, ulong offset, ulong size, ulong stride) + { + if (_count == 0) + { + _baseBinding = binding; + } + else if (_baseBinding + _count != binding) + { + Commit(cbs); + _baseBinding = binding; + } + + int index = (int)_count; + + _buffers[index] = buffer; + _offsets[index] = offset; + _sizes[index] = size; + _strides[index] = stride; + + _count++; + } + + public unsafe void Commit(CommandBufferScoped cbs) + { + if (_count != 0) + { + if (_gd.Capabilities.SupportsExtendedDynamicState) + { + _gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2( + cbs.CommandBuffer, + _baseBinding, + _count, + _buffers.Pointer, + _offsets.Pointer, + _sizes.Pointer, + _strides.Pointer); + } + else + { + _gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, _baseBinding, _count, _buffers.Pointer, _offsets.Pointer); + } + + _count = 0; + } + } + + public void Dispose() + { + _buffers.Dispose(); + _offsets.Dispose(); + _sizes.Dispose(); + _strides.Dispose(); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs b/src/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs new file mode 100644 index 00000000..596c0e17 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Graphics.Vulkan +{ + static class VulkanConfiguration + { + public const bool UseFastBufferUpdates = true; + public const bool UseUnsafeBlit = true; + public const bool UsePushDescriptors = true; + + public const bool ForceD24S8Unsupported = false; + public const bool ForceRGB16IntFloatUnsupported = false; + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanDebugMessenger.cs b/src/Ryujinx.Graphics.Vulkan/VulkanDebugMessenger.cs new file mode 100644 index 00000000..496a90fb --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/VulkanDebugMessenger.cs @@ -0,0 +1,133 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.EXT; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + class VulkanDebugMessenger : IDisposable + { + private readonly Vk _api; + private readonly Instance _instance; + private readonly GraphicsDebugLevel _logLevel; + private readonly ExtDebugUtils _debugUtils; + private readonly DebugUtilsMessengerEXT? _debugUtilsMessenger; + private bool _disposed; + + public VulkanDebugMessenger(Vk api, Instance instance, GraphicsDebugLevel logLevel) + { + _api = api; + _instance = instance; + _logLevel = logLevel; + + _api.TryGetInstanceExtension(instance, out _debugUtils); + + Result result = TryInitialize(out _debugUtilsMessenger); + + if (result != Result.Success) + { + Logger.Error?.Print(LogClass.Gpu, $"Vulkan debug messenger initialization failed with error {result}"); + } + } + + private Result TryInitialize(out DebugUtilsMessengerEXT? debugUtilsMessengerHandle) + { + debugUtilsMessengerHandle = null; + + if (_debugUtils != null && _logLevel != GraphicsDebugLevel.None) + { + var messageType = _logLevel switch + { + GraphicsDebugLevel.Error => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt, + GraphicsDebugLevel.Slowdowns => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt | + DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt, + GraphicsDebugLevel.All => DebugUtilsMessageTypeFlagsEXT.GeneralBitExt | + DebugUtilsMessageTypeFlagsEXT.ValidationBitExt | + DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt, + _ => throw new ArgumentException($"Invalid log level \"{_logLevel}\"."), + }; + + var messageSeverity = _logLevel switch + { + GraphicsDebugLevel.Error => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt, + GraphicsDebugLevel.Slowdowns => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt | + DebugUtilsMessageSeverityFlagsEXT.WarningBitExt, + GraphicsDebugLevel.All => DebugUtilsMessageSeverityFlagsEXT.InfoBitExt | + DebugUtilsMessageSeverityFlagsEXT.WarningBitExt | + DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt | + DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt, + _ => throw new ArgumentException($"Invalid log level \"{_logLevel}\"."), + }; + + var debugUtilsMessengerCreateInfo = new DebugUtilsMessengerCreateInfoEXT + { + SType = StructureType.DebugUtilsMessengerCreateInfoExt, + MessageType = messageType, + MessageSeverity = messageSeverity, + }; + + unsafe + { + debugUtilsMessengerCreateInfo.PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(UserCallback); + } + + DebugUtilsMessengerEXT messengerHandle = default; + + Result result = _debugUtils.CreateDebugUtilsMessenger(_instance, SpanHelpers.AsReadOnlySpan(ref debugUtilsMessengerCreateInfo), ReadOnlySpan.Empty, SpanHelpers.AsSpan(ref messengerHandle)); + + if (result == Result.Success) + { + debugUtilsMessengerHandle = messengerHandle; + } + + return result; + } + + return Result.Success; + } + + private unsafe static uint UserCallback( + DebugUtilsMessageSeverityFlagsEXT messageSeverity, + DebugUtilsMessageTypeFlagsEXT messageTypes, + DebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) + { + var msg = Marshal.PtrToStringAnsi((IntPtr)pCallbackData->PMessage); + + if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt)) + { + Logger.Error?.Print(LogClass.Gpu, msg); + } + else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.WarningBitExt)) + { + Logger.Warning?.Print(LogClass.Gpu, msg); + } + else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.InfoBitExt)) + { + Logger.Info?.Print(LogClass.Gpu, msg); + } + else // if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt)) + { + Logger.Debug?.Print(LogClass.Gpu, msg); + } + + return 0; + } + + public void Dispose() + { + if (!_disposed) + { + if (_debugUtilsMessenger.HasValue) + { + _debugUtils.DestroyDebugUtilsMessenger(_instance, _debugUtilsMessenger.Value, Span.Empty); + } + + _disposed = true; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanException.cs b/src/Ryujinx.Graphics.Vulkan/VulkanException.cs new file mode 100644 index 00000000..e203a3a2 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/VulkanException.cs @@ -0,0 +1,43 @@ +using Silk.NET.Vulkan; +using System; +using System.Runtime.Serialization; + +namespace Ryujinx.Graphics.Vulkan +{ + static class ResultExtensions + { + public static bool IsError(this Result result) + { + // Only negative result codes are errors. + return result < Result.Success; + } + + public static void ThrowOnError(this Result result) + { + // Only negative result codes are errors. + if (result.IsError()) + { + throw new VulkanException(result); + } + } + } + + class VulkanException : Exception + { + public VulkanException() + { + } + + public VulkanException(Result result) : base($"Unexpected API error \"{result}\".") + { + } + + public VulkanException(string message) : base(message) + { + } + + public VulkanException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs new file mode 100644 index 00000000..2c327fdb --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -0,0 +1,618 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.EXT; +using Silk.NET.Vulkan.Extensions.KHR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + public unsafe static class VulkanInitialization + { + private const uint InvalidIndex = uint.MaxValue; + private static readonly uint _minimalVulkanVersion = Vk.Version11.Value; + private static readonly uint _minimalInstanceVulkanVersion = Vk.Version12.Value; + private static readonly uint _maximumVulkanVersion = Vk.Version12.Value; + private const string AppName = "Ryujinx.Graphics.Vulkan"; + private const int QueuesCount = 2; + + private static readonly string[] _desirableExtensions = { + ExtConditionalRendering.ExtensionName, + ExtExtendedDynamicState.ExtensionName, + ExtTransformFeedback.ExtensionName, + KhrDrawIndirectCount.ExtensionName, + KhrPushDescriptor.ExtensionName, + ExtExternalMemoryHost.ExtensionName, + "VK_EXT_blend_operation_advanced", + "VK_EXT_custom_border_color", + "VK_EXT_descriptor_indexing", // Enabling this works around an issue with disposed buffer bindings on RADV. + "VK_EXT_fragment_shader_interlock", + "VK_EXT_index_type_uint8", + "VK_EXT_primitive_topology_list_restart", + "VK_EXT_robustness2", + "VK_EXT_shader_stencil_export", + "VK_KHR_shader_float16_int8", + "VK_EXT_shader_subgroup_ballot", + "VK_NV_geometry_shader_passthrough", + "VK_NV_viewport_array2", + "VK_EXT_depth_clip_control", + "VK_KHR_portability_subset", // As per spec, we should enable this if present. + "VK_EXT_4444_formats", + "VK_KHR_8bit_storage", + "VK_KHR_maintenance2", + "VK_EXT_attachment_feedback_loop_layout", + "VK_EXT_attachment_feedback_loop_dynamic_state", + }; + + private static readonly string[] _requiredExtensions = { + KhrSwapchain.ExtensionName, + }; + + internal static VulkanInstance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions) + { + var enabledLayers = new List(); + + var instanceExtensions = VulkanInstance.GetInstanceExtensions(api); + var instanceLayers = VulkanInstance.GetInstanceLayers(api); + + void AddAvailableLayer(string layerName) + { + if (instanceLayers.Contains(layerName)) + { + enabledLayers.Add(layerName); + } + else + { + Logger.Warning?.Print(LogClass.Gpu, $"Missing layer {layerName}"); + } + } + + if (logLevel != GraphicsDebugLevel.None) + { + AddAvailableLayer("VK_LAYER_KHRONOS_validation"); + } + + var enabledExtensions = requiredExtensions; + + if (instanceExtensions.Contains("VK_EXT_debug_utils")) + { + enabledExtensions = enabledExtensions.Append(ExtDebugUtils.ExtensionName).ToArray(); + } + + var appName = Marshal.StringToHGlobalAnsi(AppName); + + var applicationInfo = new ApplicationInfo + { + PApplicationName = (byte*)appName, + ApplicationVersion = 1, + PEngineName = (byte*)appName, + EngineVersion = 1, + ApiVersion = _maximumVulkanVersion, + }; + + IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length]; + IntPtr* ppEnabledLayers = stackalloc IntPtr[enabledLayers.Count]; + + for (int i = 0; i < enabledExtensions.Length; i++) + { + ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]); + } + + for (int i = 0; i < enabledLayers.Count; i++) + { + ppEnabledLayers[i] = Marshal.StringToHGlobalAnsi(enabledLayers[i]); + } + + var instanceCreateInfo = new InstanceCreateInfo + { + SType = StructureType.InstanceCreateInfo, + PApplicationInfo = &applicationInfo, + PpEnabledExtensionNames = (byte**)ppEnabledExtensions, + PpEnabledLayerNames = (byte**)ppEnabledLayers, + EnabledExtensionCount = (uint)enabledExtensions.Length, + EnabledLayerCount = (uint)enabledLayers.Count, + }; + + Result result = VulkanInstance.Create(api, ref instanceCreateInfo, out var instance); + + Marshal.FreeHGlobal(appName); + + for (int i = 0; i < enabledExtensions.Length; i++) + { + Marshal.FreeHGlobal(ppEnabledExtensions[i]); + } + + for (int i = 0; i < enabledLayers.Count; i++) + { + Marshal.FreeHGlobal(ppEnabledLayers[i]); + } + + result.ThrowOnError(); + + return instance; + } + + internal static VulkanPhysicalDevice FindSuitablePhysicalDevice(Vk api, VulkanInstance instance, SurfaceKHR surface, string preferredGpuId) + { + instance.EnumeratePhysicalDevices(out var physicalDevices).ThrowOnError(); + + // First we try to pick the user preferred GPU. + for (int i = 0; i < physicalDevices.Length; i++) + { + if (IsPreferredAndSuitableDevice(api, physicalDevices[i], surface, preferredGpuId)) + { + return physicalDevices[i]; + } + } + + // If we fail to do that, just use the first compatible GPU. + for (int i = 0; i < physicalDevices.Length; i++) + { + if (IsSuitableDevice(api, physicalDevices[i], surface)) + { + return physicalDevices[i]; + } + } + + throw new VulkanException("Initialization failed, none of the available GPUs meets the minimum requirements."); + } + + internal static DeviceInfo[] GetSuitablePhysicalDevices(Vk api) + { + var appName = Marshal.StringToHGlobalAnsi(AppName); + + var applicationInfo = new ApplicationInfo + { + PApplicationName = (byte*)appName, + ApplicationVersion = 1, + PEngineName = (byte*)appName, + EngineVersion = 1, + ApiVersion = _maximumVulkanVersion, + }; + + var instanceCreateInfo = new InstanceCreateInfo + { + SType = StructureType.InstanceCreateInfo, + PApplicationInfo = &applicationInfo, + PpEnabledExtensionNames = null, + PpEnabledLayerNames = null, + EnabledExtensionCount = 0, + EnabledLayerCount = 0, + }; + + Result result = VulkanInstance.Create(api, ref instanceCreateInfo, out var rawInstance); + + Marshal.FreeHGlobal(appName); + + result.ThrowOnError(); + + using VulkanInstance instance = rawInstance; + + // We currently assume that the instance is compatible with Vulkan 1.2 + // TODO: Remove this once we relax our initialization codepaths. + if (instance.InstanceVersion < _minimalInstanceVulkanVersion) + { + return Array.Empty(); + } + + instance.EnumeratePhysicalDevices(out VulkanPhysicalDevice[] physicalDevices).ThrowOnError(); + + List deviceInfos = new(); + + foreach (VulkanPhysicalDevice physicalDevice in physicalDevices) + { + if (physicalDevice.PhysicalDeviceProperties.ApiVersion < _minimalVulkanVersion) + { + continue; + } + + deviceInfos.Add(physicalDevice.ToDeviceInfo()); + } + + return deviceInfos.ToArray(); + } + + private static bool IsPreferredAndSuitableDevice(Vk api, VulkanPhysicalDevice physicalDevice, SurfaceKHR surface, string preferredGpuId) + { + if (physicalDevice.Id != preferredGpuId) + { + return false; + } + + return IsSuitableDevice(api, physicalDevice, surface); + } + + private static bool IsSuitableDevice(Vk api, VulkanPhysicalDevice physicalDevice, SurfaceKHR surface) + { + int extensionMatches = 0; + + foreach (string requiredExtension in _requiredExtensions) + { + if (physicalDevice.IsDeviceExtensionPresent(requiredExtension)) + { + extensionMatches++; + } + } + + return extensionMatches == _requiredExtensions.Length && FindSuitableQueueFamily(api, physicalDevice, surface, out _) != InvalidIndex; + } + + internal static uint FindSuitableQueueFamily(Vk api, VulkanPhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount) + { + const QueueFlags RequiredFlags = QueueFlags.GraphicsBit | QueueFlags.ComputeBit; + + var khrSurface = new KhrSurface(api.Context); + + for (uint index = 0; index < physicalDevice.QueueFamilyProperties.Length; index++) + { + ref QueueFamilyProperties property = ref physicalDevice.QueueFamilyProperties[index]; + + khrSurface.GetPhysicalDeviceSurfaceSupport(physicalDevice.PhysicalDevice, index, surface, out var surfaceSupported).ThrowOnError(); + + if (property.QueueFlags.HasFlag(RequiredFlags) && surfaceSupported) + { + queueCount = property.QueueCount; + + return index; + } + } + + queueCount = 0; + + return InvalidIndex; + } + + internal static Device CreateDevice(Vk api, VulkanPhysicalDevice physicalDevice, uint queueFamilyIndex, uint queueCount) + { + if (queueCount > QueuesCount) + { + queueCount = QueuesCount; + } + + float* queuePriorities = stackalloc float[(int)queueCount]; + + for (int i = 0; i < queueCount; i++) + { + queuePriorities[i] = 1f; + } + + var queueCreateInfo = new DeviceQueueCreateInfo + { + SType = StructureType.DeviceQueueCreateInfo, + QueueFamilyIndex = queueFamilyIndex, + QueueCount = queueCount, + PQueuePriorities = queuePriorities, + }; + + bool useRobustBufferAccess = VendorUtils.FromId(physicalDevice.PhysicalDeviceProperties.VendorID) == Vendor.Nvidia; + + PhysicalDeviceFeatures2 features2 = new() + { + SType = StructureType.PhysicalDeviceFeatures2, + }; + + PhysicalDeviceVulkan11Features supportedFeaturesVk11 = new() + { + SType = StructureType.PhysicalDeviceVulkan11Features, + PNext = features2.PNext, + }; + + features2.PNext = &supportedFeaturesVk11; + + PhysicalDeviceCustomBorderColorFeaturesEXT supportedFeaturesCustomBorderColor = new() + { + SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt, + PNext = features2.PNext, + }; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color")) + { + features2.PNext = &supportedFeaturesCustomBorderColor; + } + + PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT supportedFeaturesPrimitiveTopologyListRestart = new() + { + SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt, + PNext = features2.PNext, + }; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart")) + { + features2.PNext = &supportedFeaturesPrimitiveTopologyListRestart; + } + + PhysicalDeviceTransformFeedbackFeaturesEXT supportedFeaturesTransformFeedback = new() + { + SType = StructureType.PhysicalDeviceTransformFeedbackFeaturesExt, + PNext = features2.PNext, + }; + + if (physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName)) + { + features2.PNext = &supportedFeaturesTransformFeedback; + } + + PhysicalDeviceRobustness2FeaturesEXT supportedFeaturesRobustness2 = new() + { + SType = StructureType.PhysicalDeviceRobustness2FeaturesExt, + }; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2")) + { + supportedFeaturesRobustness2.PNext = features2.PNext; + + features2.PNext = &supportedFeaturesRobustness2; + } + + PhysicalDeviceDepthClipControlFeaturesEXT supportedFeaturesDepthClipControl = new() + { + SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt, + PNext = features2.PNext, + }; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control")) + { + features2.PNext = &supportedFeaturesDepthClipControl; + } + + PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT supportedFeaturesAttachmentFeedbackLoopLayout = new() + { + SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt, + PNext = features2.PNext, + }; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout")) + { + features2.PNext = &supportedFeaturesAttachmentFeedbackLoopLayout; + } + + PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT supportedFeaturesDynamicAttachmentFeedbackLoopLayout = new() + { + SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt, + PNext = features2.PNext, + }; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state")) + { + features2.PNext = &supportedFeaturesDynamicAttachmentFeedbackLoopLayout; + } + + PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new() + { + SType = StructureType.PhysicalDeviceVulkan12Features, + PNext = features2.PNext, + }; + + features2.PNext = &supportedPhysicalDeviceVulkan12Features; + + api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2); + + var supportedFeatures = features2.Features; + + var features = new PhysicalDeviceFeatures + { + DepthBiasClamp = supportedFeatures.DepthBiasClamp, + DepthClamp = supportedFeatures.DepthClamp, + DualSrcBlend = supportedFeatures.DualSrcBlend, + FragmentStoresAndAtomics = supportedFeatures.FragmentStoresAndAtomics, + GeometryShader = supportedFeatures.GeometryShader, + ImageCubeArray = supportedFeatures.ImageCubeArray, + IndependentBlend = supportedFeatures.IndependentBlend, + LogicOp = supportedFeatures.LogicOp, + OcclusionQueryPrecise = supportedFeatures.OcclusionQueryPrecise, + MultiViewport = supportedFeatures.MultiViewport, + PipelineStatisticsQuery = supportedFeatures.PipelineStatisticsQuery, + SamplerAnisotropy = supportedFeatures.SamplerAnisotropy, + ShaderClipDistance = supportedFeatures.ShaderClipDistance, + ShaderFloat64 = supportedFeatures.ShaderFloat64, + ShaderImageGatherExtended = supportedFeatures.ShaderImageGatherExtended, + ShaderStorageImageMultisample = supportedFeatures.ShaderStorageImageMultisample, + ShaderStorageImageReadWithoutFormat = supportedFeatures.ShaderStorageImageReadWithoutFormat, + ShaderStorageImageWriteWithoutFormat = supportedFeatures.ShaderStorageImageWriteWithoutFormat, + TessellationShader = supportedFeatures.TessellationShader, + VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics, + RobustBufferAccess = useRobustBufferAccess, + SampleRateShading = supportedFeatures.SampleRateShading, + }; + + void* pExtendedFeatures = null; + + PhysicalDeviceTransformFeedbackFeaturesEXT featuresTransformFeedback; + + if (physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName)) + { + featuresTransformFeedback = new PhysicalDeviceTransformFeedbackFeaturesEXT + { + SType = StructureType.PhysicalDeviceTransformFeedbackFeaturesExt, + PNext = pExtendedFeatures, + TransformFeedback = supportedFeaturesTransformFeedback.TransformFeedback, + }; + + pExtendedFeatures = &featuresTransformFeedback; + } + + PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT featuresPrimitiveTopologyListRestart; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart")) + { + featuresPrimitiveTopologyListRestart = new PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT + { + SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt, + PNext = pExtendedFeatures, + PrimitiveTopologyListRestart = supportedFeaturesPrimitiveTopologyListRestart.PrimitiveTopologyListRestart, + PrimitiveTopologyPatchListRestart = supportedFeaturesPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart, + }; + + pExtendedFeatures = &featuresPrimitiveTopologyListRestart; + } + + PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2")) + { + featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT + { + SType = StructureType.PhysicalDeviceRobustness2FeaturesExt, + PNext = pExtendedFeatures, + NullDescriptor = supportedFeaturesRobustness2.NullDescriptor, + }; + + pExtendedFeatures = &featuresRobustness2; + } + + var featuresExtendedDynamicState = new PhysicalDeviceExtendedDynamicStateFeaturesEXT + { + SType = StructureType.PhysicalDeviceExtendedDynamicStateFeaturesExt, + PNext = pExtendedFeatures, + ExtendedDynamicState = physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName), + }; + + pExtendedFeatures = &featuresExtendedDynamicState; + + var featuresVk11 = new PhysicalDeviceVulkan11Features + { + SType = StructureType.PhysicalDeviceVulkan11Features, + PNext = pExtendedFeatures, + ShaderDrawParameters = supportedFeaturesVk11.ShaderDrawParameters, + }; + + pExtendedFeatures = &featuresVk11; + + var featuresVk12 = new PhysicalDeviceVulkan12Features + { + SType = StructureType.PhysicalDeviceVulkan12Features, + PNext = pExtendedFeatures, + DescriptorIndexing = supportedPhysicalDeviceVulkan12Features.DescriptorIndexing, + DrawIndirectCount = supportedPhysicalDeviceVulkan12Features.DrawIndirectCount, + UniformBufferStandardLayout = supportedPhysicalDeviceVulkan12Features.UniformBufferStandardLayout, + UniformAndStorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.UniformAndStorageBuffer8BitAccess, + StorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.StorageBuffer8BitAccess, + }; + + pExtendedFeatures = &featuresVk12; + + PhysicalDeviceIndexTypeUint8FeaturesEXT featuresIndexU8; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8")) + { + featuresIndexU8 = new PhysicalDeviceIndexTypeUint8FeaturesEXT + { + SType = StructureType.PhysicalDeviceIndexTypeUint8FeaturesExt, + PNext = pExtendedFeatures, + IndexTypeUint8 = true, + }; + + pExtendedFeatures = &featuresIndexU8; + } + + PhysicalDeviceFragmentShaderInterlockFeaturesEXT featuresFragmentShaderInterlock; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock")) + { + featuresFragmentShaderInterlock = new PhysicalDeviceFragmentShaderInterlockFeaturesEXT + { + SType = StructureType.PhysicalDeviceFragmentShaderInterlockFeaturesExt, + PNext = pExtendedFeatures, + FragmentShaderPixelInterlock = true, + }; + + pExtendedFeatures = &featuresFragmentShaderInterlock; + } + + PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color") && + supportedFeaturesCustomBorderColor.CustomBorderColors && + supportedFeaturesCustomBorderColor.CustomBorderColorWithoutFormat) + { + featuresCustomBorderColor = new PhysicalDeviceCustomBorderColorFeaturesEXT + { + SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt, + PNext = pExtendedFeatures, + CustomBorderColors = true, + CustomBorderColorWithoutFormat = true, + }; + + pExtendedFeatures = &featuresCustomBorderColor; + } + + PhysicalDeviceDepthClipControlFeaturesEXT featuresDepthClipControl; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control") && + supportedFeaturesDepthClipControl.DepthClipControl) + { + featuresDepthClipControl = new PhysicalDeviceDepthClipControlFeaturesEXT + { + SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt, + PNext = pExtendedFeatures, + DepthClipControl = true, + }; + + pExtendedFeatures = &featuresDepthClipControl; + } + + PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoopLayout; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout") && + supportedFeaturesAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopLayout) + { + featuresAttachmentFeedbackLoopLayout = new() + { + SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt, + PNext = pExtendedFeatures, + AttachmentFeedbackLoopLayout = true, + }; + + pExtendedFeatures = &featuresAttachmentFeedbackLoopLayout; + } + + PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoopLayout; + + if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state") && + supportedFeaturesDynamicAttachmentFeedbackLoopLayout.AttachmentFeedbackLoopDynamicState) + { + featuresDynamicAttachmentFeedbackLoopLayout = new() + { + SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt, + PNext = pExtendedFeatures, + AttachmentFeedbackLoopDynamicState = true, + }; + + pExtendedFeatures = &featuresDynamicAttachmentFeedbackLoopLayout; + } + + var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray(); + + IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length]; + + for (int i = 0; i < enabledExtensions.Length; i++) + { + ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]); + } + + var deviceCreateInfo = new DeviceCreateInfo + { + SType = StructureType.DeviceCreateInfo, + PNext = pExtendedFeatures, + QueueCreateInfoCount = 1, + PQueueCreateInfos = &queueCreateInfo, + PpEnabledExtensionNames = (byte**)ppEnabledExtensions, + EnabledExtensionCount = (uint)enabledExtensions.Length, + PEnabledFeatures = &features, + }; + + api.CreateDevice(physicalDevice.PhysicalDevice, in deviceCreateInfo, null, out var device).ThrowOnError(); + + for (int i = 0; i < enabledExtensions.Length; i++) + { + Marshal.FreeHGlobal(ppEnabledExtensions[i]); + } + + return device; + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInstance.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInstance.cs new file mode 100644 index 00000000..843d3412 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/VulkanInstance.cs @@ -0,0 +1,127 @@ +using Ryujinx.Common.Utilities; +using Silk.NET.Core; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + class VulkanInstance : IDisposable + { + private readonly Vk _api; + public readonly Instance Instance; + public readonly Version32 InstanceVersion; + + private bool _disposed; + + private VulkanInstance(Vk api, Instance instance) + { + _api = api; + Instance = instance; + + if (api.GetInstanceProcAddr(instance, "vkEnumerateInstanceVersion") == IntPtr.Zero) + { + InstanceVersion = Vk.Version10; + } + else + { + uint rawInstanceVersion = 0; + + if (api.EnumerateInstanceVersion(ref rawInstanceVersion) != Result.Success) + { + rawInstanceVersion = Vk.Version11.Value; + } + + InstanceVersion = (Version32)rawInstanceVersion; + } + } + + public static Result Create(Vk api, ref InstanceCreateInfo createInfo, out VulkanInstance instance) + { + instance = null; + + Instance rawInstance = default; + + Result result = api.CreateInstance(SpanHelpers.AsReadOnlySpan(ref createInfo), ReadOnlySpan.Empty, SpanHelpers.AsSpan(ref rawInstance)); + + if (result == Result.Success) + { + instance = new VulkanInstance(api, rawInstance); + } + + return result; + } + + public Result EnumeratePhysicalDevices(out VulkanPhysicalDevice[] physicalDevices) + { + physicalDevices = null; + + uint physicalDeviceCount = 0; + + Result result = _api.EnumeratePhysicalDevices(Instance, SpanHelpers.AsSpan(ref physicalDeviceCount), Span.Empty); + + if (result != Result.Success) + { + return result; + } + + PhysicalDevice[] rawPhysicalDevices = new PhysicalDevice[physicalDeviceCount]; + + result = _api.EnumeratePhysicalDevices(Instance, SpanHelpers.AsSpan(ref physicalDeviceCount), rawPhysicalDevices); + + if (result != Result.Success) + { + return result; + } + + physicalDevices = rawPhysicalDevices.Select(x => new VulkanPhysicalDevice(_api, x)).ToArray(); + + return Result.Success; + } + + public static IReadOnlySet GetInstanceExtensions(Vk api) + { + uint propertiesCount = 0; + + api.EnumerateInstanceExtensionProperties(ReadOnlySpan.Empty, SpanHelpers.AsSpan(ref propertiesCount), Span.Empty).ThrowOnError(); + + ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount]; + + api.EnumerateInstanceExtensionProperties(ReadOnlySpan.Empty, SpanHelpers.AsSpan(ref propertiesCount), extensionProperties).ThrowOnError(); + + unsafe + { + return extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToImmutableHashSet(); + } + } + + public static IReadOnlySet GetInstanceLayers(Vk api) + { + uint propertiesCount = 0; + + api.EnumerateInstanceLayerProperties(SpanHelpers.AsSpan(ref propertiesCount), Span.Empty).ThrowOnError(); + + LayerProperties[] layerProperties = new LayerProperties[propertiesCount]; + + api.EnumerateInstanceLayerProperties(SpanHelpers.AsSpan(ref propertiesCount), layerProperties).ThrowOnError(); + + unsafe + { + return layerProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.LayerName)).ToImmutableHashSet(); + } + } + + public void Dispose() + { + if (!_disposed) + { + _api.DestroyInstance(Instance, ReadOnlySpan.Empty); + + _disposed = true; + } + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanPhysicalDevice.cs b/src/Ryujinx.Graphics.Vulkan/VulkanPhysicalDevice.cs new file mode 100644 index 00000000..3bee1e9d --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/VulkanPhysicalDevice.cs @@ -0,0 +1,97 @@ +using Ryujinx.Common.Utilities; +using Ryujinx.Graphics.GAL; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.Graphics.Vulkan +{ + readonly struct VulkanPhysicalDevice + { + public readonly PhysicalDevice PhysicalDevice; + public readonly PhysicalDeviceFeatures PhysicalDeviceFeatures; + public readonly PhysicalDeviceProperties PhysicalDeviceProperties; + public readonly PhysicalDeviceMemoryProperties PhysicalDeviceMemoryProperties; + public readonly QueueFamilyProperties[] QueueFamilyProperties; + public readonly string DeviceName; + public readonly IReadOnlySet DeviceExtensions; + + public VulkanPhysicalDevice(Vk api, PhysicalDevice physicalDevice) + { + PhysicalDevice = physicalDevice; + PhysicalDeviceFeatures = api.GetPhysicalDeviceFeature(PhysicalDevice); + + api.GetPhysicalDeviceProperties(PhysicalDevice, out var physicalDeviceProperties); + PhysicalDeviceProperties = physicalDeviceProperties; + + api.GetPhysicalDeviceMemoryProperties(PhysicalDevice, out PhysicalDeviceMemoryProperties); + + unsafe + { + DeviceName = Marshal.PtrToStringAnsi((IntPtr)physicalDeviceProperties.DeviceName); + } + + uint propertiesCount = 0; + + api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, SpanHelpers.AsSpan(ref propertiesCount), Span.Empty); + + QueueFamilyProperties = new QueueFamilyProperties[propertiesCount]; + + api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, SpanHelpers.AsSpan(ref propertiesCount), QueueFamilyProperties); + + api.EnumerateDeviceExtensionProperties(PhysicalDevice, Span.Empty, SpanHelpers.AsSpan(ref propertiesCount), Span.Empty).ThrowOnError(); + + ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount]; + + api.EnumerateDeviceExtensionProperties(PhysicalDevice, Span.Empty, SpanHelpers.AsSpan(ref propertiesCount), extensionProperties).ThrowOnError(); + + unsafe + { + DeviceExtensions = extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToImmutableHashSet(); + } + } + + public string Id => $"0x{PhysicalDeviceProperties.VendorID:X}_0x{PhysicalDeviceProperties.DeviceID:X}"; + + public bool IsDeviceExtensionPresent(string extension) => DeviceExtensions.Contains(extension); + + public unsafe bool TryGetPhysicalDeviceDriverPropertiesKHR(Vk api, out PhysicalDeviceDriverPropertiesKHR res) + { + if (!IsDeviceExtensionPresent("VK_KHR_driver_properties")) + { + res = default; + + return false; + } + + PhysicalDeviceDriverPropertiesKHR physicalDeviceDriverProperties = new() + { + SType = StructureType.PhysicalDeviceDriverPropertiesKhr + }; + + PhysicalDeviceProperties2 physicalDeviceProperties2 = new() + { + SType = StructureType.PhysicalDeviceProperties2, + PNext = &physicalDeviceDriverProperties + }; + + api.GetPhysicalDeviceProperties2(PhysicalDevice, &physicalDeviceProperties2); + + res = physicalDeviceDriverProperties; + + return true; + } + + public DeviceInfo ToDeviceInfo() + { + return new DeviceInfo( + Id, + VendorUtils.GetNameFromId(PhysicalDeviceProperties.VendorID), + DeviceName, + PhysicalDeviceProperties.DeviceType == PhysicalDeviceType.DiscreteGpu); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs new file mode 100644 index 00000000..33e41ab4 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -0,0 +1,1031 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using Ryujinx.Graphics.Vulkan.MoltenVK; +using Ryujinx.Graphics.Vulkan.Queries; +using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.EXT; +using Silk.NET.Vulkan.Extensions.KHR; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Format = Ryujinx.Graphics.GAL.Format; +using PrimitiveTopology = Ryujinx.Graphics.GAL.PrimitiveTopology; +using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo; + +namespace Ryujinx.Graphics.Vulkan +{ + public sealed class VulkanRenderer : IRenderer + { + private VulkanInstance _instance; + private SurfaceKHR _surface; + private VulkanPhysicalDevice _physicalDevice; + private Device _device; + private WindowBase _window; + + private bool _initialized; + + internal FormatCapabilities FormatCapabilities { get; private set; } + internal HardwareCapabilities Capabilities; + + internal Vk Api { get; private set; } + internal KhrSurface SurfaceApi { get; private set; } + internal KhrSwapchain SwapchainApi { get; private set; } + internal ExtConditionalRendering ConditionalRenderingApi { get; private set; } + internal ExtExtendedDynamicState ExtendedDynamicStateApi { get; private set; } + internal KhrPushDescriptor PushDescriptorApi { get; private set; } + internal ExtTransformFeedback TransformFeedbackApi { get; private set; } + internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; } + internal ExtAttachmentFeedbackLoopDynamicState DynamicFeedbackLoopApi { get; private set; } + + internal uint QueueFamilyIndex { get; private set; } + internal Queue Queue { get; private set; } + internal Queue BackgroundQueue { get; private set; } + internal object BackgroundQueueLock { get; private set; } + internal object QueueLock { get; private set; } + + internal MemoryAllocator MemoryAllocator { get; private set; } + internal HostMemoryAllocator HostMemoryAllocator { get; private set; } + internal CommandBufferPool CommandBufferPool { get; private set; } + internal PipelineLayoutCache PipelineLayoutCache { get; private set; } + internal BackgroundResources BackgroundResources { get; private set; } + internal Action InterruptAction { get; private set; } + internal SyncManager SyncManager { get; private set; } + + internal BufferManager BufferManager { get; private set; } + + internal HashSet Shaders { get; } + internal HashSet Textures { get; } + internal HashSet Samplers { get; } + + private VulkanDebugMessenger _debugMessenger; + private Counters _counters; + + private PipelineFull _pipeline; + + internal HelperShader HelperShader { get; private set; } + internal PipelineFull PipelineInternal => _pipeline; + + internal BarrierBatch Barriers { get; private set; } + + public IPipeline Pipeline => _pipeline; + + public IWindow Window => _window; + + private readonly Func _getSurface; + private readonly Func _getRequiredExtensions; + private readonly string _preferredGpuId; + + private int[] _pdReservedBindings; + private readonly static int[] _pdReservedBindingsNvn = { 3, 18, 21, 36, 30 }; + private readonly static int[] _pdReservedBindingsOgl = { 17, 18, 34, 35, 36 }; + + internal Vendor Vendor { get; private set; } + internal bool IsAmdWindows { get; private set; } + internal bool IsIntelWindows { get; private set; } + internal bool IsAmdGcn { get; private set; } + internal bool IsNvidiaPreTuring { get; private set; } + internal bool IsIntelArc { get; private set; } + internal bool IsQualcommProprietary { get; private set; } + internal bool IsMoltenVk { get; private set; } + internal bool IsTBDR { get; private set; } + internal bool IsSharedMemory { get; private set; } + + public string GpuVendor { get; private set; } + public string GpuDriver { get; private set; } + public string GpuRenderer { get; private set; } + public string GpuVersion { get; private set; } + + public bool PreferThreading => true; + + public event EventHandler ScreenCaptured; + + public VulkanRenderer(Vk api, Func surfaceFunc, Func requiredExtensionsFunc, string preferredGpuId) + { + _getSurface = surfaceFunc; + _getRequiredExtensions = requiredExtensionsFunc; + _preferredGpuId = preferredGpuId; + Api = api; + Shaders = new HashSet(); + Textures = new HashSet(); + Samplers = new HashSet(); + + if (OperatingSystem.IsMacOS()) + { + MVKInitialization.Initialize(); + + // Any device running on MacOS is using MoltenVK, even Intel and AMD vendors. + IsMoltenVk = true; + } + } + + private unsafe void LoadFeatures(uint maxQueueCount, uint queueFamilyIndex) + { + FormatCapabilities = new FormatCapabilities(Api, _physicalDevice.PhysicalDevice); + + if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtConditionalRendering conditionalRenderingApi)) + { + ConditionalRenderingApi = conditionalRenderingApi; + } + + if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExtendedDynamicState extendedDynamicStateApi)) + { + ExtendedDynamicStateApi = extendedDynamicStateApi; + } + + if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrPushDescriptor pushDescriptorApi)) + { + PushDescriptorApi = pushDescriptorApi; + } + + if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtTransformFeedback transformFeedbackApi)) + { + TransformFeedbackApi = transformFeedbackApi; + } + + if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrDrawIndirectCount drawIndirectCountApi)) + { + DrawIndirectCountApi = drawIndirectCountApi; + } + + if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtAttachmentFeedbackLoopDynamicState dynamicFeedbackLoopApi)) + { + DynamicFeedbackLoopApi = dynamicFeedbackLoopApi; + } + + if (maxQueueCount >= 2) + { + Api.GetDeviceQueue(_device, queueFamilyIndex, 1, out var backgroundQueue); + BackgroundQueue = backgroundQueue; + BackgroundQueueLock = new object(); + } + + PhysicalDeviceProperties2 properties2 = new() + { + SType = StructureType.PhysicalDeviceProperties2, + }; + + PhysicalDeviceSubgroupProperties propertiesSubgroup = new() + { + SType = StructureType.PhysicalDeviceSubgroupProperties, + PNext = properties2.PNext, + }; + + properties2.PNext = &propertiesSubgroup; + + PhysicalDeviceBlendOperationAdvancedPropertiesEXT propertiesBlendOperationAdvanced = new() + { + SType = StructureType.PhysicalDeviceBlendOperationAdvancedPropertiesExt, + }; + + bool supportsBlendOperationAdvanced = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_blend_operation_advanced"); + + if (supportsBlendOperationAdvanced) + { + propertiesBlendOperationAdvanced.PNext = properties2.PNext; + properties2.PNext = &propertiesBlendOperationAdvanced; + } + + bool supportsTransformFeedback = _physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName); + + PhysicalDeviceTransformFeedbackPropertiesEXT propertiesTransformFeedback = new() + { + SType = StructureType.PhysicalDeviceTransformFeedbackPropertiesExt, + }; + + if (supportsTransformFeedback) + { + propertiesTransformFeedback.PNext = properties2.PNext; + properties2.PNext = &propertiesTransformFeedback; + } + + PhysicalDevicePortabilitySubsetPropertiesKHR propertiesPortabilitySubset = new() + { + SType = StructureType.PhysicalDevicePortabilitySubsetPropertiesKhr, + }; + + bool supportsPushDescriptors = _physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName); + + PhysicalDevicePushDescriptorPropertiesKHR propertiesPushDescriptor = new PhysicalDevicePushDescriptorPropertiesKHR() + { + SType = StructureType.PhysicalDevicePushDescriptorPropertiesKhr + }; + + if (supportsPushDescriptors) + { + propertiesPushDescriptor.PNext = properties2.PNext; + properties2.PNext = &propertiesPushDescriptor; + } + + PhysicalDeviceFeatures2 features2 = new() + { + SType = StructureType.PhysicalDeviceFeatures2, + }; + + PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT featuresPrimitiveTopologyListRestart = new() + { + SType = StructureType.PhysicalDevicePrimitiveTopologyListRestartFeaturesExt, + }; + + PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2 = new() + { + SType = StructureType.PhysicalDeviceRobustness2FeaturesExt, + }; + + PhysicalDeviceShaderFloat16Int8FeaturesKHR featuresShaderInt8 = new() + { + SType = StructureType.PhysicalDeviceShaderFloat16Int8Features, + }; + + PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor = new() + { + SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt, + }; + + PhysicalDeviceDepthClipControlFeaturesEXT featuresDepthClipControl = new() + { + SType = StructureType.PhysicalDeviceDepthClipControlFeaturesExt, + }; + + PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT featuresAttachmentFeedbackLoop = new() + { + SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesExt, + }; + + PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT featuresDynamicAttachmentFeedbackLoop = new() + { + SType = StructureType.PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesExt, + }; + + PhysicalDevicePortabilitySubsetFeaturesKHR featuresPortabilitySubset = new() + { + SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr, + }; + + if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart")) + { + features2.PNext = &featuresPrimitiveTopologyListRestart; + } + + if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2")) + { + featuresRobustness2.PNext = features2.PNext; + features2.PNext = &featuresRobustness2; + } + + if (_physicalDevice.IsDeviceExtensionPresent("VK_KHR_shader_float16_int8")) + { + featuresShaderInt8.PNext = features2.PNext; + features2.PNext = &featuresShaderInt8; + } + + if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color")) + { + featuresCustomBorderColor.PNext = features2.PNext; + features2.PNext = &featuresCustomBorderColor; + } + + bool supportsDepthClipControl = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_depth_clip_control"); + + if (supportsDepthClipControl) + { + featuresDepthClipControl.PNext = features2.PNext; + features2.PNext = &featuresDepthClipControl; + } + + bool supportsAttachmentFeedbackLoop = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_layout"); + + if (supportsAttachmentFeedbackLoop) + { + featuresAttachmentFeedbackLoop.PNext = features2.PNext; + features2.PNext = &featuresAttachmentFeedbackLoop; + } + + bool supportsDynamicAttachmentFeedbackLoop = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_attachment_feedback_loop_dynamic_state"); + + if (supportsDynamicAttachmentFeedbackLoop) + { + featuresDynamicAttachmentFeedbackLoop.PNext = features2.PNext; + features2.PNext = &featuresDynamicAttachmentFeedbackLoop; + } + + bool usePortability = _physicalDevice.IsDeviceExtensionPresent("VK_KHR_portability_subset"); + + if (usePortability) + { + propertiesPortabilitySubset.PNext = properties2.PNext; + properties2.PNext = &propertiesPortabilitySubset; + + featuresPortabilitySubset.PNext = features2.PNext; + features2.PNext = &featuresPortabilitySubset; + } + + Api.GetPhysicalDeviceProperties2(_physicalDevice.PhysicalDevice, &properties2); + Api.GetPhysicalDeviceFeatures2(_physicalDevice.PhysicalDevice, &features2); + + var portabilityFlags = PortabilitySubsetFlags.None; + uint vertexBufferAlignment = 1; + + if (usePortability) + { + vertexBufferAlignment = propertiesPortabilitySubset.MinVertexInputBindingStrideAlignment; + + portabilityFlags |= featuresPortabilitySubset.TriangleFans ? 0 : PortabilitySubsetFlags.NoTriangleFans; + portabilityFlags |= featuresPortabilitySubset.PointPolygons ? 0 : PortabilitySubsetFlags.NoPointMode; + portabilityFlags |= featuresPortabilitySubset.ImageView2DOn3DImage ? 0 : PortabilitySubsetFlags.No3DImageView; + portabilityFlags |= featuresPortabilitySubset.SamplerMipLodBias ? 0 : PortabilitySubsetFlags.NoLodBias; + } + + bool supportsCustomBorderColor = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color") && + featuresCustomBorderColor.CustomBorderColors && + featuresCustomBorderColor.CustomBorderColorWithoutFormat; + + ref var properties = ref properties2.Properties; + + var hasDriverProperties = _physicalDevice.TryGetPhysicalDeviceDriverPropertiesKHR(Api, out var driverProperties); + + Vendor = VendorUtils.FromId(properties.VendorID); + + IsAmdWindows = Vendor == Vendor.Amd && OperatingSystem.IsWindows(); + IsIntelWindows = Vendor == Vendor.Intel && OperatingSystem.IsWindows(); + IsTBDR = + Vendor == Vendor.Apple || + Vendor == Vendor.Qualcomm || + Vendor == Vendor.ARM || + Vendor == Vendor.Broadcom || + Vendor == Vendor.ImgTec; + + GpuVendor = VendorUtils.GetNameFromId(properties.VendorID); + GpuDriver = hasDriverProperties && !OperatingSystem.IsMacOS() ? + VendorUtils.GetFriendlyDriverName(driverProperties.DriverID) : GpuVendor; // Fallback to vendor name if driver is unavailable or on MacOS where vendor is preferred. + + fixed (byte* deviceName = properties.DeviceName) + { + GpuRenderer = Marshal.PtrToStringAnsi((IntPtr)deviceName); + } + + GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}"; + + IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer); + + if (Vendor == Vendor.Nvidia) + { + var match = VendorUtils.NvidiaConsumerClassRegex().Match(GpuRenderer); + + if (match != null && int.TryParse(match.Groups[2].Value, out int gpuNumber)) + { + IsNvidiaPreTuring = gpuNumber < 2000; + } + else if (GpuRenderer.Contains("TITAN") && !GpuRenderer.Contains("RTX")) + { + IsNvidiaPreTuring = true; + } + } + else if (Vendor == Vendor.Intel) + { + IsIntelArc = GpuRenderer.StartsWith("Intel(R) Arc(TM)"); + } + + IsQualcommProprietary = hasDriverProperties && driverProperties.DriverID == DriverId.QualcommProprietary; + + ulong minResourceAlignment = Math.Max( + Math.Max( + properties.Limits.MinStorageBufferOffsetAlignment, + properties.Limits.MinUniformBufferOffsetAlignment), + properties.Limits.MinTexelBufferOffsetAlignment + ); + + SampleCountFlags supportedSampleCounts = + properties.Limits.FramebufferColorSampleCounts & + properties.Limits.FramebufferDepthSampleCounts & + properties.Limits.FramebufferStencilSampleCounts; + + Capabilities = new HardwareCapabilities( + _physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"), + supportsCustomBorderColor, + supportsBlendOperationAdvanced, + propertiesBlendOperationAdvanced.AdvancedBlendCorrelatedOverlap, + propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedSrcColor, + propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedDstColor, + _physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName), + _physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock"), + _physicalDevice.IsDeviceExtensionPresent("VK_NV_geometry_shader_passthrough"), + features2.Features.ShaderFloat64, + featuresShaderInt8.ShaderInt8, + _physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"), + features2.Features.ShaderStorageImageMultisample, + _physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName), + _physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName), + features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue + featuresRobustness2.NullDescriptor || IsMoltenVk, + supportsPushDescriptors && !IsMoltenVk, + propertiesPushDescriptor.MaxPushDescriptors, + featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart, + featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart, + supportsTransformFeedback, + propertiesTransformFeedback.TransformFeedbackQueries, + features2.Features.OcclusionQueryPrecise, + _physicalDevice.PhysicalDeviceFeatures.PipelineStatisticsQuery, + _physicalDevice.PhysicalDeviceFeatures.GeometryShader, + _physicalDevice.PhysicalDeviceFeatures.TessellationShader, + _physicalDevice.IsDeviceExtensionPresent("VK_NV_viewport_array2"), + _physicalDevice.IsDeviceExtensionPresent(ExtExternalMemoryHost.ExtensionName), + supportsDepthClipControl && featuresDepthClipControl.DepthClipControl, + supportsAttachmentFeedbackLoop && featuresAttachmentFeedbackLoop.AttachmentFeedbackLoopLayout, + supportsDynamicAttachmentFeedbackLoop && featuresDynamicAttachmentFeedbackLoop.AttachmentFeedbackLoopDynamicState, + propertiesSubgroup.SubgroupSize, + supportedSampleCounts, + portabilityFlags, + vertexBufferAlignment, + properties.Limits.SubTexelPrecisionBits, + minResourceAlignment); + + IsSharedMemory = MemoryAllocator.IsDeviceMemoryShared(_physicalDevice); + + MemoryAllocator = new MemoryAllocator(Api, _physicalDevice, _device); + + Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi); + HostMemoryAllocator = new HostMemoryAllocator(MemoryAllocator, Api, hostMemoryApi, _device); + + CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex, IsQualcommProprietary); + + PipelineLayoutCache = new PipelineLayoutCache(); + + BackgroundResources = new BackgroundResources(this, _device); + + BufferManager = new BufferManager(this, _device); + + SyncManager = new SyncManager(this, _device); + _pipeline = new PipelineFull(this, _device); + _pipeline.Initialize(); + + HelperShader = new HelperShader(this, _device); + + Barriers = new BarrierBatch(this); + + _counters = new Counters(this, _device, _pipeline); + } + + private void SetupContext(GraphicsDebugLevel logLevel) + { + _instance = VulkanInitialization.CreateInstance(Api, logLevel, _getRequiredExtensions()); + _debugMessenger = new VulkanDebugMessenger(Api, _instance.Instance, logLevel); + + if (Api.TryGetInstanceExtension(_instance.Instance, out KhrSurface surfaceApi)) + { + SurfaceApi = surfaceApi; + } + + _surface = _getSurface(_instance.Instance, Api); + _physicalDevice = VulkanInitialization.FindSuitablePhysicalDevice(Api, _instance, _surface, _preferredGpuId); + + var queueFamilyIndex = VulkanInitialization.FindSuitableQueueFamily(Api, _physicalDevice, _surface, out uint maxQueueCount); + + _device = VulkanInitialization.CreateDevice(Api, _physicalDevice, queueFamilyIndex, maxQueueCount); + + if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrSwapchain swapchainApi)) + { + SwapchainApi = swapchainApi; + } + + Api.GetDeviceQueue(_device, queueFamilyIndex, 0, out var queue); + Queue = queue; + QueueLock = new object(); + + LoadFeatures(maxQueueCount, queueFamilyIndex); + + QueueFamilyIndex = queueFamilyIndex; + + _window = new Window(this, _surface, _physicalDevice.PhysicalDevice, _device); + + _initialized = true; + } + + internal int[] GetPushDescriptorReservedBindings(bool isOgl) + { + // The first call of this method determines what push descriptor layout is used for all shaders on this renderer. + // This is chosen to minimize shaders that can't fit their uniforms on the device's max number of push descriptors. + if (_pdReservedBindings == null) + { + if (Capabilities.MaxPushDescriptors <= Constants.MaxUniformBuffersPerStage * 2) + { + _pdReservedBindings = isOgl ? _pdReservedBindingsOgl : _pdReservedBindingsNvn; + } + else + { + _pdReservedBindings = Array.Empty(); + } + } + + return _pdReservedBindings; + } + + public BufferHandle CreateBuffer(int size, BufferAccess access) + { + return BufferManager.CreateWithHandle(this, size, access.HasFlag(BufferAccess.SparseCompatible), access.Convert(), access.HasFlag(BufferAccess.Stream)); + } + + public BufferHandle CreateBuffer(nint pointer, int size) + { + return BufferManager.CreateHostImported(this, pointer, size); + } + + public BufferHandle CreateBufferSparse(ReadOnlySpan storageBuffers) + { + return BufferManager.CreateSparse(this, storageBuffers); + } + + public IImageArray CreateImageArray(int size, bool isBuffer) + { + return new ImageArray(this, size, isBuffer); + } + + public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info) + { + bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute; + + if (info.State.HasValue || isCompute) + { + return new ShaderCollection(this, _device, sources, info.ResourceLayout, info.State ?? default, info.FromCache); + } + + return new ShaderCollection(this, _device, sources, info.ResourceLayout); + } + + internal ShaderCollection CreateProgramWithMinimalLayout(ShaderSource[] sources, ResourceLayout resourceLayout, SpecDescription[] specDescription = null) + { + return new ShaderCollection(this, _device, sources, resourceLayout, specDescription, isMinimal: true); + } + + public ISampler CreateSampler(SamplerCreateInfo info) + { + return new SamplerHolder(this, _device, info); + } + + public ITexture CreateTexture(TextureCreateInfo info) + { + if (info.Target == Target.TextureBuffer) + { + return new TextureBuffer(this, info); + } + + return CreateTextureView(info); + } + + public ITextureArray CreateTextureArray(int size, bool isBuffer) + { + return new TextureArray(this, size, isBuffer); + } + + internal TextureView CreateTextureView(TextureCreateInfo info) + { + // This should be disposed when all views are destroyed. + var storage = CreateTextureStorage(info); + return storage.CreateView(info, 0, 0); + } + + internal TextureStorage CreateTextureStorage(TextureCreateInfo info) + { + return new TextureStorage(this, _device, info); + } + + public void DeleteBuffer(BufferHandle buffer) + { + BufferManager.Delete(buffer); + } + + internal void FlushAllCommands() + { + _pipeline?.FlushCommandsImpl(); + } + + internal void RegisterFlush() + { + SyncManager.RegisterFlush(); + + // Periodically free unused regions of the staging buffer to avoid doing it all at once. + BufferManager.StagingBuffer.FreeCompleted(); + } + + public PinnedSpan GetBufferData(BufferHandle buffer, int offset, int size) + { + return BufferManager.GetData(buffer, offset, size); + } + + public unsafe Capabilities GetCapabilities() + { + FormatFeatureFlags compressedFormatFeatureFlags = + FormatFeatureFlags.SampledImageBit | + FormatFeatureFlags.SampledImageFilterLinearBit | + FormatFeatureFlags.BlitSrcBit | + FormatFeatureFlags.TransferSrcBit | + FormatFeatureFlags.TransferDstBit; + + bool supportsBc123CompressionFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags, + Format.Bc1RgbaSrgb, + Format.Bc1RgbaUnorm, + Format.Bc2Srgb, + Format.Bc2Unorm, + Format.Bc3Srgb, + Format.Bc3Unorm); + + bool supportsBc45CompressionFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags, + Format.Bc4Snorm, + Format.Bc4Unorm, + Format.Bc5Snorm, + Format.Bc5Unorm); + + bool supportsBc67CompressionFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags, + Format.Bc6HSfloat, + Format.Bc6HUfloat, + Format.Bc7Srgb, + Format.Bc7Unorm); + + bool supportsEtc2CompressionFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags, + Format.Etc2RgbaSrgb, + Format.Etc2RgbaUnorm, + Format.Etc2RgbPtaSrgb, + Format.Etc2RgbPtaUnorm, + Format.Etc2RgbSrgb, + Format.Etc2RgbUnorm); + + bool supports5BitComponentFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags, + Format.R5G6B5Unorm, + Format.R5G5B5A1Unorm, + Format.R5G5B5X1Unorm, + Format.B5G6R5Unorm, + Format.B5G5R5A1Unorm, + Format.A1B5G5R5Unorm); + + bool supportsR4G4B4A4Format = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags, + Format.R4G4B4A4Unorm); + + bool supportsAstcFormats = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags, + Format.Astc4x4Unorm, + Format.Astc5x4Unorm, + Format.Astc5x5Unorm, + Format.Astc6x5Unorm, + Format.Astc6x6Unorm, + Format.Astc8x5Unorm, + Format.Astc8x6Unorm, + Format.Astc8x8Unorm, + Format.Astc10x5Unorm, + Format.Astc10x6Unorm, + Format.Astc10x8Unorm, + Format.Astc10x10Unorm, + Format.Astc12x10Unorm, + Format.Astc12x12Unorm, + Format.Astc4x4Srgb, + Format.Astc5x4Srgb, + Format.Astc5x5Srgb, + Format.Astc6x5Srgb, + Format.Astc6x6Srgb, + Format.Astc8x5Srgb, + Format.Astc8x6Srgb, + Format.Astc8x8Srgb, + Format.Astc10x5Srgb, + Format.Astc10x6Srgb, + Format.Astc10x8Srgb, + Format.Astc10x10Srgb, + Format.Astc12x10Srgb, + Format.Astc12x12Srgb); + + PhysicalDeviceVulkan12Features featuresVk12 = new() + { + SType = StructureType.PhysicalDeviceVulkan12Features, + }; + + PhysicalDeviceFeatures2 features2 = new() + { + SType = StructureType.PhysicalDeviceFeatures2, + PNext = &featuresVk12, + }; + + Api.GetPhysicalDeviceFeatures2(_physicalDevice.PhysicalDevice, &features2); + + var limits = _physicalDevice.PhysicalDeviceProperties.Limits; + var mainQueueProperties = _physicalDevice.QueueFamilyProperties[QueueFamilyIndex]; + + SystemMemoryType memoryType; + + if (IsSharedMemory) + { + memoryType = SystemMemoryType.UnifiedMemory; + } + else + { + memoryType = Vendor == Vendor.Nvidia ? + SystemMemoryType.DedicatedMemorySlowStorage : + SystemMemoryType.DedicatedMemory; + } + + return new Capabilities( + api: TargetApi.Vulkan, + GpuVendor, + memoryType: memoryType, + hasFrontFacingBug: IsIntelWindows, + hasVectorIndexingBug: IsQualcommProprietary, + needsFragmentOutputSpecialization: IsMoltenVk, + reduceShaderPrecision: IsMoltenVk, + supportsAstcCompression: features2.Features.TextureCompressionAstcLdr && supportsAstcFormats, + supportsBc123Compression: supportsBc123CompressionFormat, + supportsBc45Compression: supportsBc45CompressionFormat, + supportsBc67Compression: supportsBc67CompressionFormat, + supportsEtc2Compression: supportsEtc2CompressionFormat, + supports3DTextureCompression: true, + supportsBgraFormat: true, + supportsR4G4Format: false, + supportsR4G4B4A4Format: supportsR4G4B4A4Format, + supportsScaledVertexFormats: FormatCapabilities.SupportsScaledVertexFormats(), + supportsSnormBufferTextureFormat: true, + supports5BitComponentFormat: supports5BitComponentFormat, + supportsSparseBuffer: features2.Features.SparseBinding && mainQueueProperties.QueueFlags.HasFlag(QueueFlags.SparseBindingBit), + supportsBlendEquationAdvanced: Capabilities.SupportsBlendEquationAdvanced, + supportsFragmentShaderInterlock: Capabilities.SupportsFragmentShaderInterlock, + supportsFragmentShaderOrderingIntel: false, + supportsGeometryShader: Capabilities.SupportsGeometryShader, + supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough, + supportsTransformFeedback: Capabilities.SupportsTransformFeedback, + supportsImageLoadFormatted: features2.Features.ShaderStorageImageReadWithoutFormat, + supportsLayerVertexTessellation: featuresVk12.ShaderOutputLayer, + supportsMismatchingViewFormat: true, + supportsCubemapView: !IsAmdGcn, + supportsNonConstantTextureOffset: false, + supportsQuads: false, + supportsSeparateSampler: true, + supportsShaderBallot: false, + supportsShaderBarrierDivergence: Vendor != Vendor.Intel, + supportsShaderFloat64: Capabilities.SupportsShaderFloat64, + supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended && !IsMoltenVk, + supportsTextureShadowLod: false, + supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics, + supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex, + supportsViewportMask: Capabilities.SupportsViewportArray2, + supportsViewportSwizzle: false, + supportsIndirectParameters: true, + supportsDepthClipControl: Capabilities.SupportsDepthClipControl, + uniformBufferSetIndex: PipelineBase.UniformSetIndex, + storageBufferSetIndex: PipelineBase.StorageSetIndex, + textureSetIndex: PipelineBase.TextureSetIndex, + imageSetIndex: PipelineBase.ImageSetIndex, + extraSetBaseIndex: PipelineBase.DescriptorSetLayouts, + maximumExtraSets: Math.Max(0, (int)limits.MaxBoundDescriptorSets - PipelineBase.DescriptorSetLayouts), + maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage, + maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage, + maximumTexturesPerStage: Constants.MaxTexturesPerStage, + maximumImagesPerStage: Constants.MaxImagesPerStage, + maximumComputeSharedMemorySize: (int)limits.MaxComputeSharedMemorySize, + maximumSupportedAnisotropy: (int)limits.MaxSamplerAnisotropy, + shaderSubgroupSize: (int)Capabilities.SubgroupSize, + storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment, + textureBufferOffsetAlignment: (int)limits.MinTexelBufferOffsetAlignment, + gatherBiasPrecision: IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0); + } + + public HardwareInfo GetHardwareInfo() + { + return new HardwareInfo(GpuVendor, GpuRenderer, GpuDriver); + } + + /// + /// Gets the available Vulkan devices using the default Vulkan API + /// object returned by + /// + /// + public static DeviceInfo[] GetPhysicalDevices() + { + try + { + return VulkanInitialization.GetSuitablePhysicalDevices(Vk.GetApi()); + } + catch (Exception ex) + { + Logger.Error?.PrintMsg(LogClass.Gpu, $"Error querying Vulkan devices: {ex.Message}"); + + return Array.Empty(); + } + } + + public static DeviceInfo[] GetPhysicalDevices(Vk api) + { + try + { + return VulkanInitialization.GetSuitablePhysicalDevices(api); + } + catch (Exception) + { + // If we got an exception here, Vulkan is most likely not supported. + return Array.Empty(); + } + } + + private static string ParseStandardVulkanVersion(uint version) + { + return $"{version >> 22}.{(version >> 12) & 0x3FF}.{version & 0xFFF}"; + } + + private static string ParseDriverVersion(ref PhysicalDeviceProperties properties) + { + uint driverVersionRaw = properties.DriverVersion; + + // NVIDIA differ from the standard here and uses a different format. + if (properties.VendorID == 0x10DE) + { + return $"{(driverVersionRaw >> 22) & 0x3FF}.{(driverVersionRaw >> 14) & 0xFF}.{(driverVersionRaw >> 6) & 0xFF}.{driverVersionRaw & 0x3F}"; + } + + return ParseStandardVulkanVersion(driverVersionRaw); + } + + internal PrimitiveTopology TopologyRemap(PrimitiveTopology topology) + { + return topology switch + { + PrimitiveTopology.Quads => PrimitiveTopology.Triangles, + PrimitiveTopology.QuadStrip => PrimitiveTopology.TriangleStrip, + PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans) + ? PrimitiveTopology.Triangles + : topology, + _ => topology, + }; + } + + internal bool TopologyUnsupported(PrimitiveTopology topology) + { + return topology switch + { + PrimitiveTopology.Quads => true, + PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => Capabilities.PortabilitySubset.HasFlag(PortabilitySubsetFlags.NoTriangleFans), + _ => false, + }; + } + + private void PrintGpuInformation() + { + Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})"); + } + + public void Initialize(GraphicsDebugLevel logLevel) + { + SetupContext(logLevel); + + PrintGpuInformation(); + } + + internal bool NeedsVertexBufferAlignment(int attrScalarAlignment, out int alignment) + { + if (Capabilities.VertexBufferAlignment > 1) + { + alignment = (int)Capabilities.VertexBufferAlignment; + + return true; + } + else if (Vendor != Vendor.Nvidia) + { + // Vulkan requires that vertex attributes are globally aligned by their component size, + // so buffer strides that don't divide by the largest scalar element are invalid. + // Guest applications do this, NVIDIA GPUs are OK with it, others are not. + + alignment = attrScalarAlignment; + + return true; + } + + alignment = 1; + + return false; + } + + public void PreFrame() + { + SyncManager.Cleanup(); + } + + public ICounterEvent ReportCounter(CounterType type, EventHandler resultHandler, float divisor, bool hostReserved) + { + return _counters.QueueReport(type, resultHandler, divisor, hostReserved); + } + + public void ResetCounter(CounterType type) + { + _counters.QueueReset(type); + } + + public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan data) + { + BufferManager.SetData(buffer, offset, data, _pipeline.CurrentCommandBuffer, _pipeline.EndRenderPassDelegate); + } + + public void UpdateCounters() + { + _counters.Update(); + } + + public void ResetCounterPool() + { + _counters.ResetCounterPool(); + } + + public void ResetFutureCounters(CommandBuffer cmd, int count) + { + _counters?.ResetFutureCounters(cmd, count); + } + + public void BackgroundContextAction(Action action, bool alwaysBackground = false) + { + action(); + } + + public void CreateSync(ulong id, bool strict) + { + SyncManager.Create(id, strict); + } + + public IProgram LoadProgramBinary(byte[] programBinary, bool isFragment, ShaderInfo info) + { + throw new NotImplementedException(); + } + + public void WaitSync(ulong id) + { + SyncManager.Wait(id); + } + + public ulong GetCurrentSync() + { + return SyncManager.GetCurrent(); + } + + public void SetInterruptAction(Action interruptAction) + { + InterruptAction = interruptAction; + } + + public void Screenshot() + { + _window.ScreenCaptureRequested = true; + } + + public void OnScreenCaptured(ScreenCaptureImageInfo bitmap) + { + ScreenCaptured?.Invoke(this, bitmap); + } + + public bool SupportsRenderPassBarrier(PipelineStageFlags flags) + { + return !(IsMoltenVk || IsQualcommProprietary); + } + + public unsafe void Dispose() + { + if (!_initialized) + { + return; + } + + CommandBufferPool.Dispose(); + BackgroundResources.Dispose(); + _counters.Dispose(); + _window.Dispose(); + HelperShader.Dispose(); + _pipeline.Dispose(); + BufferManager.Dispose(); + PipelineLayoutCache.Dispose(); + Barriers.Dispose(); + + MemoryAllocator.Dispose(); + + foreach (var shader in Shaders) + { + shader.Dispose(); + } + + foreach (var texture in Textures) + { + texture.Release(); + } + + foreach (var sampler in Samplers) + { + sampler.Dispose(); + } + + SurfaceApi.DestroySurface(_instance.Instance, _surface, null); + + Api.DestroyDevice(_device, null); + + _debugMessenger.Dispose(); + + // Last step destroy the instance + _instance.Dispose(); + } + + public bool PrepareHostMapping(nint address, ulong size) + { + return Capabilities.SupportsHostImportedMemory && + HostMemoryAllocator.TryImport(BufferManager.HostImportedBufferMemoryRequirements, BufferManager.DefaultBufferMemoryFlags, address, size); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs new file mode 100644 index 00000000..d67362be --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/Window.cs @@ -0,0 +1,670 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Vulkan.Effects; +using Silk.NET.Vulkan; +using Silk.NET.Vulkan.Extensions.KHR; +using System; +using System.Linq; +using VkFormat = Silk.NET.Vulkan.Format; + +namespace Ryujinx.Graphics.Vulkan +{ + class Window : WindowBase, IDisposable + { + private const int SurfaceWidth = 1280; + private const int SurfaceHeight = 720; + + private readonly VulkanRenderer _gd; + private readonly SurfaceKHR _surface; + private readonly PhysicalDevice _physicalDevice; + private readonly Device _device; + private SwapchainKHR _swapchain; + + private Image[] _swapchainImages; + private TextureView[] _swapchainImageViews; + + private Semaphore[] _imageAvailableSemaphores; + private Semaphore[] _renderFinishedSemaphores; + + private int _frameIndex; + + private int _width; + private int _height; + private bool _vsyncEnabled; + private bool _swapchainIsDirty; + private VkFormat _format; + private AntiAliasing _currentAntiAliasing; + private bool _updateEffect; + private IPostProcessingEffect _effect; + private IScalingFilter _scalingFilter; + private bool _isLinear; + private float _scalingFilterLevel; + private bool _updateScalingFilter; + private ScalingFilter _currentScalingFilter; + private bool _colorSpacePassthroughEnabled; + + public unsafe Window(VulkanRenderer gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device) + { + _gd = gd; + _physicalDevice = physicalDevice; + _device = device; + _surface = surface; + + CreateSwapchain(); + } + + private void RecreateSwapchain() + { + var oldSwapchain = _swapchain; + _swapchainIsDirty = false; + + for (int i = 0; i < _swapchainImageViews.Length; i++) + { + _swapchainImageViews[i].Dispose(); + } + + // Destroy old Swapchain. + + _gd.Api.DeviceWaitIdle(_device); + + unsafe + { + for (int i = 0; i < _imageAvailableSemaphores.Length; i++) + { + _gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null); + } + + for (int i = 0; i < _renderFinishedSemaphores.Length; i++) + { + _gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[i], null); + } + } + + _gd.SwapchainApi.DestroySwapchain(_device, oldSwapchain, Span.Empty); + + CreateSwapchain(); + } + + private unsafe void CreateSwapchain() + { + _gd.SurfaceApi.GetPhysicalDeviceSurfaceCapabilities(_physicalDevice, _surface, out var capabilities); + + uint surfaceFormatsCount; + + _gd.SurfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &surfaceFormatsCount, null); + + var surfaceFormats = new SurfaceFormatKHR[surfaceFormatsCount]; + + fixed (SurfaceFormatKHR* pSurfaceFormats = surfaceFormats) + { + _gd.SurfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &surfaceFormatsCount, pSurfaceFormats); + } + + uint presentModesCount; + + _gd.SurfaceApi.GetPhysicalDeviceSurfacePresentModes(_physicalDevice, _surface, &presentModesCount, null); + + var presentModes = new PresentModeKHR[presentModesCount]; + + fixed (PresentModeKHR* pPresentModes = presentModes) + { + _gd.SurfaceApi.GetPhysicalDeviceSurfacePresentModes(_physicalDevice, _surface, &presentModesCount, pPresentModes); + } + + uint imageCount = capabilities.MinImageCount + 1; + if (capabilities.MaxImageCount > 0 && imageCount > capabilities.MaxImageCount) + { + imageCount = capabilities.MaxImageCount; + } + + var surfaceFormat = ChooseSwapSurfaceFormat(surfaceFormats, _colorSpacePassthroughEnabled); + + var extent = ChooseSwapExtent(capabilities); + + _width = (int)extent.Width; + _height = (int)extent.Height; + _format = surfaceFormat.Format; + + var oldSwapchain = _swapchain; + + var swapchainCreateInfo = new SwapchainCreateInfoKHR + { + SType = StructureType.SwapchainCreateInfoKhr, + Surface = _surface, + MinImageCount = imageCount, + ImageFormat = surfaceFormat.Format, + ImageColorSpace = surfaceFormat.ColorSpace, + ImageExtent = extent, + ImageUsage = ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit | ImageUsageFlags.StorageBit, + ImageSharingMode = SharingMode.Exclusive, + ImageArrayLayers = 1, + PreTransform = capabilities.CurrentTransform, + CompositeAlpha = ChooseCompositeAlpha(capabilities.SupportedCompositeAlpha), + PresentMode = ChooseSwapPresentMode(presentModes, _vsyncEnabled), + Clipped = true, + }; + + var textureCreateInfo = new TextureCreateInfo( + _width, + _height, + 1, + 1, + 1, + 1, + 1, + 1, + FormatTable.GetFormat(surfaceFormat.Format), + DepthStencilMode.Depth, + Target.Texture2D, + SwizzleComponent.Red, + SwizzleComponent.Green, + SwizzleComponent.Blue, + SwizzleComponent.Alpha); + + _gd.SwapchainApi.CreateSwapchain(_device, in swapchainCreateInfo, null, out _swapchain).ThrowOnError(); + + _gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null); + + _swapchainImages = new Image[imageCount]; + + fixed (Image* pSwapchainImages = _swapchainImages) + { + _gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, pSwapchainImages); + } + + _swapchainImageViews = new TextureView[imageCount]; + + for (int i = 0; i < _swapchainImageViews.Length; i++) + { + _swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format, textureCreateInfo); + } + + var semaphoreCreateInfo = new SemaphoreCreateInfo + { + SType = StructureType.SemaphoreCreateInfo, + }; + + _imageAvailableSemaphores = new Semaphore[imageCount]; + + for (int i = 0; i < _imageAvailableSemaphores.Length; i++) + { + _gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _imageAvailableSemaphores[i]).ThrowOnError(); + } + + _renderFinishedSemaphores = new Semaphore[imageCount]; + + for (int i = 0; i < _renderFinishedSemaphores.Length; i++) + { + _gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _renderFinishedSemaphores[i]).ThrowOnError(); + } + } + + private unsafe TextureView CreateSwapchainImageView(Image swapchainImage, VkFormat format, TextureCreateInfo info) + { + var componentMapping = new ComponentMapping( + ComponentSwizzle.R, + ComponentSwizzle.G, + ComponentSwizzle.B, + ComponentSwizzle.A); + + var aspectFlags = ImageAspectFlags.ColorBit; + + var subresourceRange = new ImageSubresourceRange(aspectFlags, 0, 1, 0, 1); + + var imageCreateInfo = new ImageViewCreateInfo + { + SType = StructureType.ImageViewCreateInfo, + Image = swapchainImage, + ViewType = ImageViewType.Type2D, + Format = format, + Components = componentMapping, + SubresourceRange = subresourceRange, + }; + + _gd.Api.CreateImageView(_device, in imageCreateInfo, null, out var imageView).ThrowOnError(); + + return new TextureView(_gd, _device, new DisposableImageView(_gd.Api, _device, imageView), info, format); + } + + private static SurfaceFormatKHR ChooseSwapSurfaceFormat(SurfaceFormatKHR[] availableFormats, bool colorSpacePassthroughEnabled) + { + if (availableFormats.Length == 1 && availableFormats[0].Format == VkFormat.Undefined) + { + return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, ColorSpaceKHR.PaceSrgbNonlinearKhr); + } + + var formatToReturn = availableFormats[0]; + if (colorSpacePassthroughEnabled) + { + foreach (var format in availableFormats) + { + if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.SpacePassThroughExt) + { + formatToReturn = format; + break; + } + else if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.PaceSrgbNonlinearKhr) + { + formatToReturn = format; + } + } + } + else + { + foreach (var format in availableFormats) + { + if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.PaceSrgbNonlinearKhr) + { + formatToReturn = format; + break; + } + } + } + + return formatToReturn; + } + + private static CompositeAlphaFlagsKHR ChooseCompositeAlpha(CompositeAlphaFlagsKHR supportedFlags) + { + if (supportedFlags.HasFlag(CompositeAlphaFlagsKHR.OpaqueBitKhr)) + { + return CompositeAlphaFlagsKHR.OpaqueBitKhr; + } + else if (supportedFlags.HasFlag(CompositeAlphaFlagsKHR.PreMultipliedBitKhr)) + { + return CompositeAlphaFlagsKHR.PreMultipliedBitKhr; + } + else + { + return CompositeAlphaFlagsKHR.InheritBitKhr; + } + } + + private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, bool vsyncEnabled) + { + if (!vsyncEnabled && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr)) + { + return PresentModeKHR.ImmediateKhr; + } + else if (availablePresentModes.Contains(PresentModeKHR.MailboxKhr)) + { + return PresentModeKHR.MailboxKhr; + } + else + { + return PresentModeKHR.FifoKhr; + } + } + + public static Extent2D ChooseSwapExtent(SurfaceCapabilitiesKHR capabilities) + { + if (capabilities.CurrentExtent.Width != uint.MaxValue) + { + return capabilities.CurrentExtent; + } + + uint width = Math.Max(capabilities.MinImageExtent.Width, Math.Min(capabilities.MaxImageExtent.Width, SurfaceWidth)); + uint height = Math.Max(capabilities.MinImageExtent.Height, Math.Min(capabilities.MaxImageExtent.Height, SurfaceHeight)); + + return new Extent2D(width, height); + } + + public unsafe override void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback) + { + _gd.PipelineInternal.AutoFlush.Present(); + + uint nextImage = 0; + int semaphoreIndex = _frameIndex++ % _imageAvailableSemaphores.Length; + + while (true) + { + var acquireResult = _gd.SwapchainApi.AcquireNextImage( + _device, + _swapchain, + ulong.MaxValue, + _imageAvailableSemaphores[semaphoreIndex], + new Fence(), + ref nextImage); + + if (acquireResult == Result.ErrorOutOfDateKhr || + acquireResult == Result.SuboptimalKhr || + _swapchainIsDirty) + { + RecreateSwapchain(); + semaphoreIndex = (_frameIndex - 1) % _imageAvailableSemaphores.Length; + } + else + { + acquireResult.ThrowOnError(); + break; + } + } + + var swapchainImage = _swapchainImages[nextImage]; + + _gd.FlushAllCommands(); + + var cbs = _gd.CommandBufferPool.Rent(); + + Transition( + cbs.CommandBuffer, + swapchainImage, + 0, + AccessFlags.TransferWriteBit, + ImageLayout.Undefined, + ImageLayout.General); + + var view = (TextureView)texture; + + UpdateEffect(); + + if (_effect != null) + { + view = _effect.Run(view, cbs, _width, _height); + } + + int srcX0, srcX1, srcY0, srcY1; + + if (crop.Left == 0 && crop.Right == 0) + { + srcX0 = 0; + srcX1 = view.Width; + } + else + { + srcX0 = crop.Left; + srcX1 = crop.Right; + } + + if (crop.Top == 0 && crop.Bottom == 0) + { + srcY0 = 0; + srcY1 = view.Height; + } + else + { + srcY0 = crop.Top; + srcY1 = crop.Bottom; + } + + if (ScreenCaptureRequested) + { + if (_effect != null) + { + _gd.CommandBufferPool.Return( + cbs, + null, + stackalloc[] { PipelineStageFlags.ColorAttachmentOutputBit }, + null); + _gd.FlushAllCommands(); + cbs.GetFence().Wait(); + cbs = _gd.CommandBufferPool.Rent(); + } + + CaptureFrame(view, srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, view.Info.Format.IsBgr(), crop.FlipX, crop.FlipY); + + ScreenCaptureRequested = false; + } + + float ratioX = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _height * crop.AspectRatioX / (_width * crop.AspectRatioY)); + float ratioY = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _width * crop.AspectRatioY / (_height * crop.AspectRatioX)); + + int dstWidth = (int)(_width * ratioX); + int dstHeight = (int)(_height * ratioY); + + int dstPaddingX = (_width - dstWidth) / 2; + int dstPaddingY = (_height - dstHeight) / 2; + + int dstX0 = crop.FlipX ? _width - dstPaddingX : dstPaddingX; + int dstX1 = crop.FlipX ? dstPaddingX : _width - dstPaddingX; + + int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY; + int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY; + + if (_scalingFilter != null) + { + _scalingFilter.Run( + view, + cbs, + _swapchainImageViews[nextImage].GetImageViewForAttachment(), + _format, + _width, + _height, + new Extents2D(srcX0, srcY0, srcX1, srcY1), + new Extents2D(dstX0, dstY0, dstX1, dstY1) + ); + } + else + { + _gd.HelperShader.BlitColor( + _gd, + cbs, + view, + _swapchainImageViews[nextImage], + new Extents2D(srcX0, srcY0, srcX1, srcY1), + new Extents2D(dstX0, dstY1, dstX1, dstY0), + _isLinear, + true); + } + + Transition( + cbs.CommandBuffer, + swapchainImage, + 0, + 0, + ImageLayout.General, + ImageLayout.PresentSrcKhr); + + _gd.CommandBufferPool.Return( + cbs, + stackalloc[] { _imageAvailableSemaphores[semaphoreIndex] }, + stackalloc[] { PipelineStageFlags.ColorAttachmentOutputBit }, + stackalloc[] { _renderFinishedSemaphores[semaphoreIndex] }); + + // TODO: Present queue. + var semaphore = _renderFinishedSemaphores[semaphoreIndex]; + var swapchain = _swapchain; + + Result result; + + var presentInfo = new PresentInfoKHR + { + SType = StructureType.PresentInfoKhr, + WaitSemaphoreCount = 1, + PWaitSemaphores = &semaphore, + SwapchainCount = 1, + PSwapchains = &swapchain, + PImageIndices = &nextImage, + PResults = &result, + }; + + lock (_gd.QueueLock) + { + _gd.SwapchainApi.QueuePresent(_gd.Queue, in presentInfo); + } + } + + public override void SetAntiAliasing(AntiAliasing effect) + { + if (_currentAntiAliasing == effect && _effect != null) + { + return; + } + + _currentAntiAliasing = effect; + + _updateEffect = true; + } + + public override void SetScalingFilter(ScalingFilter type) + { + if (_currentScalingFilter == type && _effect != null) + { + return; + } + + _currentScalingFilter = type; + + _updateScalingFilter = true; + } + + public override void SetColorSpacePassthrough(bool colorSpacePassthroughEnabled) + { + _colorSpacePassthroughEnabled = colorSpacePassthroughEnabled; + _swapchainIsDirty = true; + } + + private void UpdateEffect() + { + if (_updateEffect) + { + _updateEffect = false; + + switch (_currentAntiAliasing) + { + case AntiAliasing.Fxaa: + _effect?.Dispose(); + _effect = new FxaaPostProcessingEffect(_gd, _device); + break; + case AntiAliasing.None: + _effect?.Dispose(); + _effect = null; + break; + case AntiAliasing.SmaaLow: + case AntiAliasing.SmaaMedium: + case AntiAliasing.SmaaHigh: + case AntiAliasing.SmaaUltra: + var quality = _currentAntiAliasing - AntiAliasing.SmaaLow; + if (_effect is SmaaPostProcessingEffect smaa) + { + smaa.Quality = quality; + } + else + { + _effect?.Dispose(); + _effect = new SmaaPostProcessingEffect(_gd, _device, quality); + } + break; + } + } + + if (_updateScalingFilter) + { + _updateScalingFilter = false; + + switch (_currentScalingFilter) + { + case ScalingFilter.Bilinear: + case ScalingFilter.Nearest: + _scalingFilter?.Dispose(); + _scalingFilter = null; + _isLinear = _currentScalingFilter == ScalingFilter.Bilinear; + break; + case ScalingFilter.Fsr: + if (_scalingFilter is not FsrScalingFilter) + { + _scalingFilter?.Dispose(); + _scalingFilter = new FsrScalingFilter(_gd, _device); + } + + _scalingFilter.Level = _scalingFilterLevel; + break; + } + } + } + + public override void SetScalingFilterLevel(float level) + { + _scalingFilterLevel = level; + _updateScalingFilter = true; + } + + private unsafe void Transition( + CommandBuffer commandBuffer, + Image image, + AccessFlags srcAccess, + AccessFlags dstAccess, + ImageLayout srcLayout, + ImageLayout dstLayout) + { + var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ColorBit, 0, 1, 0, 1); + + var barrier = new ImageMemoryBarrier + { + SType = StructureType.ImageMemoryBarrier, + SrcAccessMask = srcAccess, + DstAccessMask = dstAccess, + OldLayout = srcLayout, + NewLayout = dstLayout, + SrcQueueFamilyIndex = Vk.QueueFamilyIgnored, + DstQueueFamilyIndex = Vk.QueueFamilyIgnored, + Image = image, + SubresourceRange = subresourceRange, + }; + + _gd.Api.CmdPipelineBarrier( + commandBuffer, + PipelineStageFlags.TopOfPipeBit, + PipelineStageFlags.AllCommandsBit, + 0, + 0, + null, + 0, + null, + 1, + in barrier); + } + + private void CaptureFrame(TextureView texture, int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY) + { + byte[] bitmap = texture.GetData(x, y, width, height); + + _gd.OnScreenCaptured(new ScreenCaptureImageInfo(width, height, isBgra, bitmap, flipX, flipY)); + } + + public override void SetSize(int width, int height) + { + // We don't need to use width and height as we can get the size from the surface. + _swapchainIsDirty = true; + } + + public override void ChangeVSyncMode(bool vsyncEnabled) + { + _vsyncEnabled = vsyncEnabled; + _swapchainIsDirty = true; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + unsafe + { + for (int i = 0; i < _swapchainImageViews.Length; i++) + { + _swapchainImageViews[i].Dispose(); + } + + for (int i = 0; i < _imageAvailableSemaphores.Length; i++) + { + _gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null); + } + + for (int i = 0; i < _renderFinishedSemaphores.Length; i++) + { + _gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[i], null); + } + + _gd.SwapchainApi.DestroySwapchain(_device, _swapchain, null); + } + + _effect?.Dispose(); + _scalingFilter?.Dispose(); + } + } + + public override void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Graphics.Vulkan/WindowBase.cs b/src/Ryujinx.Graphics.Vulkan/WindowBase.cs new file mode 100644 index 00000000..edb9c688 --- /dev/null +++ b/src/Ryujinx.Graphics.Vulkan/WindowBase.cs @@ -0,0 +1,19 @@ +using Ryujinx.Graphics.GAL; +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + internal abstract class WindowBase : IWindow + { + public bool ScreenCaptureRequested { get; set; } + + public abstract void Dispose(); + public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback); + public abstract void SetSize(int width, int height); + public abstract void ChangeVSyncMode(bool vsyncEnabled); + public abstract void SetAntiAliasing(AntiAliasing effect); + public abstract void SetScalingFilter(ScalingFilter scalerType); + public abstract void SetScalingFilterLevel(float scale); + public abstract void SetColorSpacePassthrough(bool colorSpacePassthroughEnabled); + } +} diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs new file mode 100644 index 00000000..ff7a2c3b --- /dev/null +++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs @@ -0,0 +1,205 @@ +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using System; +using System.Collections.Generic; +using System.Numerics; +using ConfigKey = Ryujinx.Common.Configuration.Hid.Key; + +namespace Ryujinx.Input.GTK3 +{ + public class GTK3Keyboard : IKeyboard + { + private class ButtonMappingEntry + { + public readonly GamepadButtonInputId To; + public readonly Key From; + + public ButtonMappingEntry(GamepadButtonInputId to, Key from) + { + To = to; + From = from; + } + } + + private readonly object _userMappingLock = new(); + + private readonly GTK3KeyboardDriver _driver; + private StandardKeyboardInputConfig _configuration; + private readonly List _buttonsUserMapping; + + public GTK3Keyboard(GTK3KeyboardDriver driver, string id, string name) + { + _driver = driver; + Id = id; + Name = name; + _buttonsUserMapping = new List(); + } + + private bool HasConfiguration => _configuration != null; + + public string Id { get; } + + public string Name { get; } + + public bool IsConnected => true; + + public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None; + + public void Dispose() + { + // No operations + GC.SuppressFinalize(this); + } + + public KeyboardStateSnapshot GetKeyboardStateSnapshot() + { + return IKeyboard.GetStateSnapshot(this); + } + + private static float ConvertRawStickValue(short value) + { + const float ConvertRate = 1.0f / (short.MaxValue + 0.5f); + + return value * ConvertRate; + } + + private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick stickConfig) + { + short stickX = 0; + short stickY = 0; + + if (snapshot.IsPressed((Key)stickConfig.StickUp)) + { + stickY += 1; + } + + if (snapshot.IsPressed((Key)stickConfig.StickDown)) + { + stickY -= 1; + } + + if (snapshot.IsPressed((Key)stickConfig.StickRight)) + { + stickX += 1; + } + + if (snapshot.IsPressed((Key)stickConfig.StickLeft)) + { + stickX -= 1; + } + + OpenTK.Mathematics.Vector2 stick = new(stickX, stickY); + + stick.NormalizeFast(); + + return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue)); + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot(); + GamepadStateSnapshot result = default; + + lock (_userMappingLock) + { + if (!HasConfiguration) + { + return result; + } + + foreach (ButtonMappingEntry entry in _buttonsUserMapping) + { + if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound) + { + continue; + } + + // Do not touch state of button already pressed + if (!result.IsPressed(entry.To)) + { + result.SetPressed(entry.To, rawState.IsPressed(entry.From)); + } + } + + (short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick); + (short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick); + + result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY)); + result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY)); + } + + return result; + } + + public GamepadStateSnapshot GetStateSnapshot() + { + throw new NotSupportedException(); + } + + public (float, float) GetStick(StickInputId inputId) + { + throw new NotSupportedException(); + } + + public bool IsPressed(GamepadButtonInputId inputId) + { + throw new NotSupportedException(); + } + + public bool IsPressed(Key key) + { + return _driver.IsPressed(key); + } + + public void SetConfiguration(InputConfig configuration) + { + lock (_userMappingLock) + { + _configuration = (StandardKeyboardInputConfig)configuration; + + _buttonsUserMapping.Clear(); + + // Then left joycon + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl)); + + // Finally right joycon + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl)); + } + } + + public void SetTriggerThreshold(float triggerThreshold) + { + // No operations + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + // No operations + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + // No operations + + return Vector3.Zero; + } + } +} diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs new file mode 100644 index 00000000..bd71c793 --- /dev/null +++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs @@ -0,0 +1,99 @@ +using Gdk; +using Gtk; +using System; +using System.Collections.Generic; +using GtkKey = Gdk.Key; + +namespace Ryujinx.Input.GTK3 +{ + public class GTK3KeyboardDriver : IGamepadDriver + { + private readonly Widget _widget; + private readonly HashSet _pressedKeys; + + public GTK3KeyboardDriver(Widget widget) + { + _widget = widget; + _pressedKeys = new HashSet(); + + _widget.KeyPressEvent += OnKeyPress; + _widget.KeyReleaseEvent += OnKeyRelease; + } + + public string DriverName => "GTK3"; + + private static readonly string[] _keyboardIdentifers = new string[1] { "0" }; + + public ReadOnlySpan GamepadsIds => _keyboardIdentifers; + + public event Action OnGamepadConnected + { + add { } + remove { } + } + + public event Action OnGamepadDisconnected + { + add { } + remove { } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _widget.KeyPressEvent -= OnKeyPress; + _widget.KeyReleaseEvent -= OnKeyRelease; + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + [GLib.ConnectBefore] + protected void OnKeyPress(object sender, KeyPressEventArgs args) + { + GtkKey key = (GtkKey)Keyval.ToLower((uint)args.Event.Key); + + _pressedKeys.Add(key); + } + + [GLib.ConnectBefore] + protected void OnKeyRelease(object sender, KeyReleaseEventArgs args) + { + GtkKey key = (GtkKey)Keyval.ToLower((uint)args.Event.Key); + + _pressedKeys.Remove(key); + } + + internal bool IsPressed(Key key) + { + if (key == Key.Unbound || key == Key.Unknown) + { + return false; + } + + GtkKey nativeKey = GTK3MappingHelper.ToGtkKey(key); + + return _pressedKeys.Contains(nativeKey); + } + + public void Clear() + { + _pressedKeys.Clear(); + } + + public IGamepad GetGamepad(string id) + { + if (!_keyboardIdentifers[0].Equals(id)) + { + return null; + } + + return new GTK3Keyboard(this, _keyboardIdentifers[0], "All keyboards"); + } + } +} diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs new file mode 100644 index 00000000..422a9603 --- /dev/null +++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3MappingHelper.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using GtkKey = Gdk.Key; + +namespace Ryujinx.Input.GTK3 +{ + public static class GTK3MappingHelper + { + private static readonly GtkKey[] _keyMapping = new GtkKey[(int)Key.Count] + { + // NOTE: invalid + GtkKey.blank, + + GtkKey.Shift_L, + GtkKey.Shift_R, + GtkKey.Control_L, + GtkKey.Control_R, + GtkKey.Alt_L, + GtkKey.Alt_R, + GtkKey.Super_L, + GtkKey.Super_R, + GtkKey.Menu, + GtkKey.F1, + GtkKey.F2, + GtkKey.F3, + GtkKey.F4, + GtkKey.F5, + GtkKey.F6, + GtkKey.F7, + GtkKey.F8, + GtkKey.F9, + GtkKey.F10, + GtkKey.F11, + GtkKey.F12, + GtkKey.F13, + GtkKey.F14, + GtkKey.F15, + GtkKey.F16, + GtkKey.F17, + GtkKey.F18, + GtkKey.F19, + GtkKey.F20, + GtkKey.F21, + GtkKey.F22, + GtkKey.F23, + GtkKey.F24, + GtkKey.F25, + GtkKey.F26, + GtkKey.F27, + GtkKey.F28, + GtkKey.F29, + GtkKey.F30, + GtkKey.F31, + GtkKey.F32, + GtkKey.F33, + GtkKey.F34, + GtkKey.F35, + GtkKey.Up, + GtkKey.Down, + GtkKey.Left, + GtkKey.Right, + GtkKey.Return, + GtkKey.Escape, + GtkKey.space, + GtkKey.Tab, + GtkKey.BackSpace, + GtkKey.Insert, + GtkKey.Delete, + GtkKey.Page_Up, + GtkKey.Page_Down, + GtkKey.Home, + GtkKey.End, + GtkKey.Caps_Lock, + GtkKey.Scroll_Lock, + GtkKey.Print, + GtkKey.Pause, + GtkKey.Num_Lock, + GtkKey.Clear, + GtkKey.KP_0, + GtkKey.KP_1, + GtkKey.KP_2, + GtkKey.KP_3, + GtkKey.KP_4, + GtkKey.KP_5, + GtkKey.KP_6, + GtkKey.KP_7, + GtkKey.KP_8, + GtkKey.KP_9, + GtkKey.KP_Divide, + GtkKey.KP_Multiply, + GtkKey.KP_Subtract, + GtkKey.KP_Add, + GtkKey.KP_Decimal, + GtkKey.KP_Enter, + GtkKey.a, + GtkKey.b, + GtkKey.c, + GtkKey.d, + GtkKey.e, + GtkKey.f, + GtkKey.g, + GtkKey.h, + GtkKey.i, + GtkKey.j, + GtkKey.k, + GtkKey.l, + GtkKey.m, + GtkKey.n, + GtkKey.o, + GtkKey.p, + GtkKey.q, + GtkKey.r, + GtkKey.s, + GtkKey.t, + GtkKey.u, + GtkKey.v, + GtkKey.w, + GtkKey.x, + GtkKey.y, + GtkKey.z, + GtkKey.Key_0, + GtkKey.Key_1, + GtkKey.Key_2, + GtkKey.Key_3, + GtkKey.Key_4, + GtkKey.Key_5, + GtkKey.Key_6, + GtkKey.Key_7, + GtkKey.Key_8, + GtkKey.Key_9, + GtkKey.grave, + GtkKey.grave, + GtkKey.minus, + GtkKey.plus, + GtkKey.bracketleft, + GtkKey.bracketright, + GtkKey.semicolon, + GtkKey.quoteright, + GtkKey.comma, + GtkKey.period, + GtkKey.slash, + GtkKey.backslash, + + // NOTE: invalid + GtkKey.blank, + }; + + private static readonly Dictionary _gtkKeyMapping; + + static GTK3MappingHelper() + { + var inputKeys = Enum.GetValues().SkipLast(1); + + // GtkKey is not contiguous and quite large, so use a dictionary instead of an array. + _gtkKeyMapping = new Dictionary(); + + foreach (var key in inputKeys) + { + var index = ToGtkKey(key); + _gtkKeyMapping[index] = key; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static GtkKey ToGtkKey(Key key) + { + return _keyMapping[(int)key]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Key ToInputKey(GtkKey key) + { + return _gtkKeyMapping.GetValueOrDefault(key, Key.Unknown); + } + } +} diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs new file mode 100644 index 00000000..0ab817ec --- /dev/null +++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs @@ -0,0 +1,90 @@ +using Ryujinx.Common.Configuration.Hid; +using System; +using System.Drawing; +using System.Numerics; + +namespace Ryujinx.Input.GTK3 +{ + public class GTK3Mouse : IMouse + { + private GTK3MouseDriver _driver; + + public GamepadFeaturesFlag Features => throw new NotImplementedException(); + + public string Id => "0"; + + public string Name => "GTKMouse"; + + public bool IsConnected => true; + + public bool[] Buttons => _driver.PressedButtons; + + public GTK3Mouse(GTK3MouseDriver driver) + { + _driver = driver; + } + + public Size ClientSize => _driver.GetClientSize(); + + public Vector2 GetPosition() + { + return _driver.CurrentPosition; + } + + public Vector2 GetScroll() + { + return _driver.Scroll; + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + throw new NotImplementedException(); + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + throw new NotImplementedException(); + } + + public GamepadStateSnapshot GetStateSnapshot() + { + throw new NotImplementedException(); + } + + public (float, float) GetStick(StickInputId inputId) + { + throw new NotImplementedException(); + } + + public bool IsButtonPressed(MouseButton button) + { + return _driver.IsButtonPressed(button); + } + + public bool IsPressed(GamepadButtonInputId inputId) + { + throw new NotImplementedException(); + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + throw new NotImplementedException(); + } + + public void SetConfiguration(InputConfig configuration) + { + throw new NotImplementedException(); + } + + public void SetTriggerThreshold(float triggerThreshold) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _driver = null; + } + } +} diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs new file mode 100644 index 00000000..5962bcb2 --- /dev/null +++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3MouseDriver.cs @@ -0,0 +1,108 @@ +using Gdk; +using Gtk; +using System; +using System.Numerics; +using Size = System.Drawing.Size; + +namespace Ryujinx.Input.GTK3 +{ + public class GTK3MouseDriver : IGamepadDriver + { + private Widget _widget; + private bool _isDisposed; + + public bool[] PressedButtons { get; } + + public Vector2 CurrentPosition { get; private set; } + public Vector2 Scroll { get; private set; } + + public GTK3MouseDriver(Widget parent) + { + _widget = parent; + + _widget.MotionNotifyEvent += Parent_MotionNotifyEvent; + _widget.ButtonPressEvent += Parent_ButtonPressEvent; + _widget.ButtonReleaseEvent += Parent_ButtonReleaseEvent; + _widget.ScrollEvent += Parent_ScrollEvent; + + PressedButtons = new bool[(int)MouseButton.Count]; + } + + + [GLib.ConnectBefore] + private void Parent_ScrollEvent(object o, ScrollEventArgs args) + { + Scroll = new Vector2((float)args.Event.X, (float)args.Event.Y); + } + + [GLib.ConnectBefore] + private void Parent_ButtonReleaseEvent(object o, ButtonReleaseEventArgs args) + { + PressedButtons[args.Event.Button - 1] = false; + } + + [GLib.ConnectBefore] + private void Parent_ButtonPressEvent(object o, ButtonPressEventArgs args) + { + PressedButtons[args.Event.Button - 1] = true; + } + + [GLib.ConnectBefore] + private void Parent_MotionNotifyEvent(object o, MotionNotifyEventArgs args) + { + if (args.Event.Device.InputSource == InputSource.Mouse) + { + CurrentPosition = new Vector2((float)args.Event.X, (float)args.Event.Y); + } + } + + public bool IsButtonPressed(MouseButton button) + { + return PressedButtons[(int)button]; + } + + public Size GetClientSize() + { + return new Size(_widget.AllocatedWidth, _widget.AllocatedHeight); + } + + public string DriverName => "GTK3"; + + public event Action OnGamepadConnected + { + add { } + remove { } + } + + public event Action OnGamepadDisconnected + { + add { } + remove { } + } + + public ReadOnlySpan GamepadsIds => new[] { "0" }; + + public IGamepad GetGamepad(string id) + { + return new GTK3Mouse(this); + } + + public void Dispose() + { + if (_isDisposed) + { + return; + } + + GC.SuppressFinalize(this); + + _isDisposed = true; + + _widget.MotionNotifyEvent -= Parent_MotionNotifyEvent; + _widget.ButtonPressEvent -= Parent_ButtonPressEvent; + _widget.ButtonReleaseEvent -= Parent_ButtonReleaseEvent; + + _widget = null; + } + } +} diff --git a/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.cs b/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.cs new file mode 100644 index 00000000..43bde942 --- /dev/null +++ b/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.cs @@ -0,0 +1,95 @@ +using Gdk; +using Gtk; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.UI; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using System; +using System.Diagnostics; +using System.Reflection; + +namespace Ryujinx.Modules +{ + public class UpdateDialog : Gtk.Window + { +#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier + [Builder.Object] public Label MainText; + [Builder.Object] public Label SecondaryText; + [Builder.Object] public LevelBar ProgressBar; + [Builder.Object] public Button YesButton; + [Builder.Object] public Button NoButton; +#pragma warning restore CS0649, IDE0044 + + private readonly MainWindow _mainWindow; + private readonly string _buildUrl; + private bool _restartQuery; + + public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx.Gtk3.Modules.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { } + + private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetRawOwnedObject("UpdateDialog")) + { + builder.Autoconnect(this); + + _mainWindow = mainWindow; + _buildUrl = buildUrl; + + Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"); + MainText.Text = "Do you want to update Ryujinx to the latest version?"; + SecondaryText.Text = $"{Program.Version} -> {newVersion}"; + + ProgressBar.Hide(); + + YesButton.Clicked += YesButton_Clicked; + NoButton.Clicked += NoButton_Clicked; + } + + private void YesButton_Clicked(object sender, EventArgs args) + { + if (_restartQuery) + { + string ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"; + + ProcessStartInfo processStart = new(ryuName) + { + UseShellExecute = true, + WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory + }; + + foreach (string argument in CommandLineState.Arguments) + { + processStart.ArgumentList.Add(argument); + } + + Process.Start(processStart); + + Environment.Exit(0); + } + else + { + Window.Functions = _mainWindow.Window.Functions = WMFunction.All & WMFunction.Close; + _mainWindow.ExitMenuItem.Sensitive = false; + + YesButton.Hide(); + NoButton.Hide(); + ProgressBar.Show(); + + SecondaryText.Text = ""; + _restartQuery = true; + + Updater.UpdateRyujinx(this, _buildUrl); + } + } + + private void NoButton_Clicked(object sender, EventArgs args) + { + Updater.Running = false; + _mainWindow.Window.Functions = WMFunction.All; + + _mainWindow.ExitMenuItem.Sensitive = true; + _mainWindow.UpdateMenuItem.Sensitive = true; + + Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.glade b/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.glade new file mode 100644 index 00000000..cc80167e --- /dev/null +++ b/src/Ryujinx.Gtk3/Modules/Updater/UpdateDialog.glade @@ -0,0 +1,127 @@ + + + + + + False + Ryujinx - Updater + False + center + 400 + 130 + + + + + + True + False + 10 + 10 + 10 + 10 + vertical + + + True + False + vertical + + + True + False + 5 + 5 + + + + + + + False + True + 0 + + + + + True + False + 5 + 5 + + + False + True + 1 + + + + + 20 + True + False + 5 + 5 + 100 + + + False + True + 2 + + + + + True + True + 0 + + + + + True + False + + + Yes + True + True + True + 5 + 5 + 5 + + + True + True + 0 + + + + + No + True + True + True + 5 + 5 + 5 + + + True + True + 1 + + + + + False + True + 1 + + + + + + diff --git a/src/Ryujinx.Gtk3/Modules/Updater/Updater.cs b/src/Ryujinx.Gtk3/Modules/Updater/Updater.cs new file mode 100644 index 00000000..8b006f63 --- /dev/null +++ b/src/Ryujinx.Gtk3/Modules/Updater/Updater.cs @@ -0,0 +1,622 @@ +using Gtk; +using ICSharpCode.SharpZipLib.GZip; +using ICSharpCode.SharpZipLib.Tar; +using ICSharpCode.SharpZipLib.Zip; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.UI; +using Ryujinx.UI.Common.Models.Github; +using Ryujinx.UI.Widgets; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.Modules +{ + public static class Updater + { + private const string GitHubApiUrl = "https://api.github.com"; + private const int ConnectionCount = 4; + + internal static bool Running; + + private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory; + private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); + private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish"); + + private static string _buildVer; + private static string _platformExt; + private static string _buildUrl; + private static long _buildSize; + + private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + // On Windows, GtkSharp.Dependencies adds these extra dirs that must be cleaned during updates. + private static readonly string[] _windowsDependencyDirs = { "bin", "etc", "lib", "share" }; + + private static HttpClient ConstructHttpClient() + { + HttpClient result = new(); + + // Required by GitHub to interact with APIs. + result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0"); + + return result; + } + + public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate) + { + if (Running) + { + return; + } + + Running = true; + mainWindow.UpdateMenuItem.Sensitive = false; + + int artifactIndex = -1; + + // Detect current platform + if (OperatingSystem.IsMacOS()) + { + _platformExt = "osx_x64.zip"; + artifactIndex = 1; + } + else if (OperatingSystem.IsWindows()) + { + _platformExt = "win_x64.zip"; + artifactIndex = 2; + } + else if (OperatingSystem.IsLinux()) + { + var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; + _platformExt = $"linux_{arch}.tar.gz"; + artifactIndex = 0; + } + + if (artifactIndex == -1) + { + GtkDialog.CreateErrorDialog("Your platform is not supported!"); + + return; + } + + Version newVersion; + Version currentVersion; + + try + { + currentVersion = Version.Parse(Program.Version); + } + catch + { + GtkDialog.CreateWarningDialog("Failed to convert the current Ryujinx version.", "Cancelling Update!"); + Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); + + return; + } + + // Get latest version number from GitHub API + try + { + using HttpClient jsonClient = ConstructHttpClient(); + string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; + + // Fetch latest build information + string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl); + var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse); + _buildVer = fetched.Name; + + foreach (var asset in fetched.Assets) + { + if (asset.Name.StartsWith("gtk-ryujinx") && asset.Name.EndsWith(_platformExt)) + { + _buildUrl = asset.BrowserDownloadUrl; + + if (asset.State != "uploaded") + { + if (showVersionUpToDate) + { + GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); + } + + return; + } + + break; + } + } + + if (_buildUrl == null) + { + if (showVersionUpToDate) + { + GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); + } + + return; + } + } + catch (Exception exception) + { + Logger.Error?.Print(LogClass.Application, exception.Message); + GtkDialog.CreateErrorDialog("An error occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes."); + + return; + } + + try + { + newVersion = Version.Parse(_buildVer); + } + catch + { + GtkDialog.CreateWarningDialog("Failed to convert the received Ryujinx version from GitHub Release.", "Cancelling Update!"); + Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from GitHub Release!"); + + return; + } + + if (newVersion <= currentVersion) + { + if (showVersionUpToDate) + { + GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", ""); + } + + Running = false; + mainWindow.UpdateMenuItem.Sensitive = true; + + return; + } + + // Fetch build size information to learn chunk sizes. + using HttpClient buildSizeClient = ConstructHttpClient(); + try + { + buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0"); + + HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead); + + _buildSize = message.Content.Headers.ContentRange.Length.Value; + } + catch (Exception ex) + { + Logger.Warning?.Print(LogClass.Application, ex.Message); + Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater"); + + _buildSize = -1; + } + + // Show a message asking the user if they want to update + UpdateDialog updateDialog = new(mainWindow, newVersion, _buildUrl); + updateDialog.Show(); + } + + public static void UpdateRyujinx(UpdateDialog updateDialog, string downloadUrl) + { + // Empty update dir, although it shouldn't ever have anything inside it + if (Directory.Exists(_updateDir)) + { + Directory.Delete(_updateDir, true); + } + + Directory.CreateDirectory(_updateDir); + + string updateFile = Path.Combine(_updateDir, "update.bin"); + + // Download the update .zip + updateDialog.MainText.Text = "Downloading Update..."; + updateDialog.ProgressBar.Value = 0; + updateDialog.ProgressBar.MaxValue = 100; + + if (_buildSize >= 0) + { + DoUpdateWithMultipleThreads(updateDialog, downloadUrl, updateFile); + } + else + { + DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); + } + } + + private static void DoUpdateWithMultipleThreads(UpdateDialog updateDialog, string downloadUrl, string updateFile) + { + // Multi-Threaded Updater + long chunkSize = _buildSize / ConnectionCount; + long remainderChunk = _buildSize % ConnectionCount; + + int completedRequests = 0; + int totalProgressPercentage = 0; + int[] progressPercentage = new int[ConnectionCount]; + + List list = new(ConnectionCount); + List webClients = new(ConnectionCount); + + for (int i = 0; i < ConnectionCount; i++) + { + list.Add(Array.Empty()); + } + + for (int i = 0; i < ConnectionCount; i++) + { +#pragma warning disable SYSLIB0014 + // TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient. + using WebClient client = new(); +#pragma warning restore SYSLIB0014 + webClients.Add(client); + + if (i == ConnectionCount - 1) + { + client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}"); + } + else + { + client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}"); + } + + client.DownloadProgressChanged += (_, args) => + { + int index = (int)args.UserState; + + Interlocked.Add(ref totalProgressPercentage, -1 * progressPercentage[index]); + Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage); + Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage); + + updateDialog.ProgressBar.Value = totalProgressPercentage / ConnectionCount; + }; + + client.DownloadDataCompleted += (_, args) => + { + int index = (int)args.UserState; + + if (args.Cancelled) + { + webClients[index].Dispose(); + + return; + } + + list[index] = args.Result; + Interlocked.Increment(ref completedRequests); + + if (Equals(completedRequests, ConnectionCount)) + { + byte[] mergedFileBytes = new byte[_buildSize]; + for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++) + { + Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length); + destinationOffset += list[connectionIndex].Length; + } + + File.WriteAllBytes(updateFile, mergedFileBytes); + + try + { + InstallUpdate(updateDialog, updateFile); + } + catch (Exception e) + { + Logger.Warning?.Print(LogClass.Application, e.Message); + Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); + + DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); + + return; + } + } + }; + + try + { + client.DownloadDataAsync(new Uri(downloadUrl), i); + } + catch (WebException ex) + { + Logger.Warning?.Print(LogClass.Application, ex.Message); + Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); + + foreach (WebClient webClient in webClients) + { + webClient.CancelAsync(); + } + + DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile); + + return; + } + } + } + + private static void DoUpdateWithSingleThreadWorker(UpdateDialog updateDialog, string downloadUrl, string updateFile) + { + using HttpClient client = new(); + // We do not want to timeout while downloading + client.Timeout = TimeSpan.FromDays(1); + + using HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result; + using Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result; + using Stream updateFileStream = File.Open(updateFile, FileMode.Create); + + long totalBytes = response.Content.Headers.ContentLength.Value; + long byteWritten = 0; + + byte[] buffer = new byte[32 * 1024]; + + while (true) + { + int readSize = remoteFileStream.Read(buffer); + + if (readSize == 0) + { + break; + } + + byteWritten += readSize; + + updateDialog.ProgressBar.Value = ((double)byteWritten / totalBytes) * 100; + updateFileStream.Write(buffer, 0, readSize); + } + + InstallUpdate(updateDialog, updateFile); + } + + private static void DoUpdateWithSingleThread(UpdateDialog updateDialog, string downloadUrl, string updateFile) + { + Thread worker = new(() => DoUpdateWithSingleThreadWorker(updateDialog, downloadUrl, updateFile)) + { + Name = "Updater.SingleThreadWorker", + }; + worker.Start(); + } + + private static async void InstallUpdate(UpdateDialog updateDialog, string updateFile) + { + // Extract Update + updateDialog.MainText.Text = "Extracting Update..."; + updateDialog.ProgressBar.Value = 0; + + if (OperatingSystem.IsLinux()) + { + using Stream inStream = File.OpenRead(updateFile); + using Stream gzipStream = new GZipInputStream(inStream); + using TarInputStream tarStream = new(gzipStream, Encoding.ASCII); + updateDialog.ProgressBar.MaxValue = inStream.Length; + + await Task.Run(() => + { + TarEntry tarEntry; + + if (!OperatingSystem.IsWindows()) + { + while ((tarEntry = tarStream.GetNextEntry()) != null) + { + if (tarEntry.IsDirectory) + { + continue; + } + + string outPath = Path.Combine(_updateDir, tarEntry.Name); + + Directory.CreateDirectory(Path.GetDirectoryName(outPath)); + + using FileStream outStream = File.OpenWrite(outPath); + tarStream.CopyEntryContents(outStream); + + File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode); + File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc)); + + TarEntry entry = tarEntry; + + Application.Invoke(delegate + { + updateDialog.ProgressBar.Value += entry.Size; + }); + } + } + }); + + updateDialog.ProgressBar.Value = inStream.Length; + } + else + { + using Stream inStream = File.OpenRead(updateFile); + using ZipFile zipFile = new(inStream); + updateDialog.ProgressBar.MaxValue = zipFile.Count; + + await Task.Run(() => + { + foreach (ZipEntry zipEntry in zipFile) + { + if (zipEntry.IsDirectory) + { + continue; + } + + string outPath = Path.Combine(_updateDir, zipEntry.Name); + + Directory.CreateDirectory(Path.GetDirectoryName(outPath)); + + using Stream zipStream = zipFile.GetInputStream(zipEntry); + using FileStream outStream = File.OpenWrite(outPath); + zipStream.CopyTo(outStream); + + File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc)); + + Application.Invoke(delegate + { + updateDialog.ProgressBar.Value++; + }); + } + }); + } + + // Delete downloaded zip + File.Delete(updateFile); + + List allFiles = EnumerateFilesToDelete().ToList(); + + updateDialog.MainText.Text = "Renaming Old Files..."; + updateDialog.ProgressBar.Value = 0; + updateDialog.ProgressBar.MaxValue = allFiles.Count; + + // Replace old files + await Task.Run(() => + { + foreach (string file in allFiles) + { + try + { + File.Move(file, file + ".ryuold"); + + Application.Invoke(delegate + { + updateDialog.ProgressBar.Value++; + }); + } + catch + { + Logger.Warning?.Print(LogClass.Application, "Updater was unable to rename file: " + file); + } + } + + Application.Invoke(delegate + { + updateDialog.MainText.Text = "Adding New Files..."; + updateDialog.ProgressBar.Value = 0; + updateDialog.ProgressBar.MaxValue = Directory.GetFiles(_updatePublishDir, "*", SearchOption.AllDirectories).Length; + }); + + MoveAllFilesOver(_updatePublishDir, _homeDir, updateDialog); + }); + + Directory.Delete(_updateDir, true); + + updateDialog.MainText.Text = "Update Complete!"; + updateDialog.SecondaryText.Text = "Do you want to restart Ryujinx now?"; + updateDialog.Modal = true; + + updateDialog.ProgressBar.Hide(); + updateDialog.YesButton.Show(); + updateDialog.NoButton.Show(); + } + + public static bool CanUpdate(bool showWarnings) + { +#if !DISABLE_UPDATER + if (!NetworkInterface.GetIsNetworkAvailable()) + { + if (showWarnings) + { + GtkDialog.CreateWarningDialog("You are not connected to the Internet!", "Please verify that you have a working Internet connection!"); + } + + return false; + } + + if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid) + { + if (showWarnings) + { + GtkDialog.CreateWarningDialog("You cannot update a Dirty build of Ryujinx!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version."); + } + + return false; + } + + return true; +#else + if (showWarnings) + { + if (ReleaseInformation.IsFlatHubBuild) + { + GtkDialog.CreateWarningDialog("Updater Disabled!", "Please update Ryujinx via FlatHub."); + } + else + { + GtkDialog.CreateWarningDialog("Updater Disabled!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version."); + } + } + + return false; +#endif + } + + // NOTE: This method should always reflect the latest build layout. + private static IEnumerable EnumerateFilesToDelete() + { + var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir. + + // Determine and exclude user files only when the updater is running, not when cleaning old files + if (Running) + { + // Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list. + var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); + var newFiles = Directory.EnumerateFiles(_updatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); + var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(_homeDir, filename)); + + // Remove user files from the paths in files. + files = files.Except(userFiles); + } + + if (OperatingSystem.IsWindows()) + { + foreach (string dir in _windowsDependencyDirs) + { + string dirPath = Path.Combine(_homeDir, dir); + if (Directory.Exists(dirPath)) + { + files = files.Concat(Directory.EnumerateFiles(dirPath, "*", SearchOption.AllDirectories)); + } + } + } + + return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); + } + + private static void MoveAllFilesOver(string root, string dest, UpdateDialog dialog) + { + foreach (string directory in Directory.GetDirectories(root)) + { + string dirName = Path.GetFileName(directory); + + if (!Directory.Exists(Path.Combine(dest, dirName))) + { + Directory.CreateDirectory(Path.Combine(dest, dirName)); + } + + MoveAllFilesOver(directory, Path.Combine(dest, dirName), dialog); + } + + foreach (string file in Directory.GetFiles(root)) + { + File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true); + + Application.Invoke(delegate + { + dialog.ProgressBar.Value++; + }); + } + } + + public static void CleanupUpdate() + { + foreach (string file in EnumerateFilesToDelete()) + { + if (Path.GetExtension(file).EndsWith(".ryuold")) + { + File.Delete(file); + } + } + } + } +} diff --git a/src/Ryujinx.Gtk3/Program.cs b/src/Ryujinx.Gtk3/Program.cs new file mode 100644 index 00000000..2d350374 --- /dev/null +++ b/src/Ryujinx.Gtk3/Program.cs @@ -0,0 +1,403 @@ +using Gtk; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.GraphicsDriver; +using Ryujinx.Common.Logging; +using Ryujinx.Common.SystemInterop; +using Ryujinx.Common.Utilities; +using Ryujinx.Graphics.Vulkan.MoltenVK; +using Ryujinx.Modules; +using Ryujinx.SDL2.Common; +using Ryujinx.UI; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Common.SystemInfo; +using Ryujinx.UI.Widgets; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Ryujinx +{ + partial class Program + { + public static double WindowScaleFactor { get; private set; } + + public static string Version { get; private set; } + + public static string ConfigurationPath { get; set; } + + public static string CommandLineProfile { get; set; } + + private const string X11LibraryName = "libX11"; + + [LibraryImport(X11LibraryName)] + private static partial int XInitThreads(); + + [LibraryImport("user32.dll", SetLastError = true)] + public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); + + private const uint MbIconWarning = 0x30; + + static Program() + { + if (OperatingSystem.IsLinux()) + { + NativeLibrary.SetDllImportResolver(typeof(Program).Assembly, (name, assembly, path) => + { + if (name != X11LibraryName) + { + return IntPtr.Zero; + } + + if (!NativeLibrary.TryLoad("libX11.so.6", assembly, path, out IntPtr result)) + { + if (!NativeLibrary.TryLoad("libX11.so", assembly, path, out result)) + { + return IntPtr.Zero; + } + } + + return result; + }); + } + } + + static void Main(string[] args) + { + Version = ReleaseInformation.Version; + + if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) + { + MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 1803 and newer.\n", $"Ryujinx {Version}", MbIconWarning); + } + + // Parse arguments + CommandLineState.ParseArguments(args); + + // Hook unhandled exception and process exit events. + GLib.ExceptionManager.UnhandledException += (GLib.UnhandledExceptionArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); + AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating); + AppDomain.CurrentDomain.ProcessExit += (object sender, EventArgs e) => Exit(); + + // Make process DPI aware for proper window sizing on high-res screens. + ForceDpiAware.Windows(); + WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor(); + + // Delete backup files after updating. + Task.Run(Updater.CleanupUpdate); + + Console.Title = $"Ryujinx Console {Version}"; + + // NOTE: GTK3 doesn't init X11 in a multi threaded way. + // This ends up causing race condition and abort of XCB when a context is created by SPB (even if SPB do call XInitThreads). + if (OperatingSystem.IsLinux()) + { + if (XInitThreads() == 0) + { + throw new NotSupportedException("Failed to initialize multi-threading support."); + } + + OsUtils.SetEnvironmentVariableNoCaching("GDK_BACKEND", "x11"); + } + + if (OperatingSystem.IsMacOS()) + { + MVKInitialization.InitializeResolver(); + + string baseDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); + string resourcesDataDir; + + if (Path.GetFileName(baseDirectory) == "MacOS") + { + resourcesDataDir = Path.Combine(Directory.GetParent(baseDirectory).FullName, "Resources"); + } + else + { + resourcesDataDir = baseDirectory; + } + + // On macOS, GTK3 needs XDG_DATA_DIRS to be set, otherwise it will try searching for "gschemas.compiled" in system directories. + OsUtils.SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share")); + + // On macOS, GTK3 needs GDK_PIXBUF_MODULE_FILE to be set, otherwise it will try searching for "loaders.cache" in system directories. + OsUtils.SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache")); + + OsUtils.SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache")); + } + + string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); + Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}"); + + // Setup base data directory. + AppDataManager.Initialize(CommandLineState.BaseDirPathArg); + + // Initialize the configuration. + ConfigurationState.Initialize(); + + // Initialize the logger system. + LoggerModule.Initialize(); + + // Initialize Discord integration. + DiscordIntegrationModule.Initialize(); + + // Initialize SDL2 driver + SDL2Driver.MainThreadDispatcher = action => + { + Application.Invoke(delegate + { + action(); + }); + }; + + string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); + string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); + + // Now load the configuration as the other subsystems are now registered + ConfigurationPath = File.Exists(localConfigurationPath) + ? localConfigurationPath + : File.Exists(appDataConfigurationPath) + ? appDataConfigurationPath + : null; + + if (ConfigurationPath == null) + { + // No configuration, we load the default values and save it to disk + ConfigurationPath = appDataConfigurationPath; + Logger.Notice.Print(LogClass.Application, $"No configuration file found. Saving default configuration to: {ConfigurationPath}"); + + ConfigurationState.Instance.LoadDefault(); + ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath); + } + else + { + Logger.Notice.Print(LogClass.Application, $"Loading configuration from: {ConfigurationPath}"); + + if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat)) + { + ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath); + } + else + { + Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location: {ConfigurationPath}"); + + ConfigurationState.Instance.LoadDefault(); + } + } + + // Check if graphics backend was overridden. + if (CommandLineState.OverrideGraphicsBackend != null) + { + if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl") + { + ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl; + } + else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan") + { + ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan; + } + } + + // Check if HideCursor was overridden. + if (CommandLineState.OverrideHideCursor is not null) + { + ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch + { + "never" => HideCursorMode.Never, + "onidle" => HideCursorMode.OnIdle, + "always" => HideCursorMode.Always, + _ => ConfigurationState.Instance.HideCursor.Value, + }; + } + + // Check if docked mode was overridden. + if (CommandLineState.OverrideDockedMode.HasValue) + { + ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value; + } + + // Logging system information. + PrintSystemInfo(); + + // Enable OGL multithreading on the driver, and some other flags. + BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; + DriverUtilities.InitDriverConfig(threadingMode == BackendThreading.Off); + + // Initialize Gtk. + Application.Init(); + + // Check if keys exists. + bool hasSystemProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")); + bool hasCommonProdKeys = AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys")); + if (!hasSystemProdKeys && !hasCommonProdKeys) + { + UserErrorDialog.CreateUserErrorDialog(UserError.NoKeys); + } + + // Show the main window UI. + MainWindow mainWindow = new(); + mainWindow.Show(); + + // Load the game table if no application was requested by the command line + if (CommandLineState.LaunchPathArg == null) + { + mainWindow.UpdateGameTable(); + } + + if (OperatingSystem.IsLinux()) + { + int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount; + + if (LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount) + { + Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({currentVmMaxMapCount})"); + + if (LinuxHelper.PkExecPath is not null) + { + var buttonTexts = new Dictionary() + { + { 0, "Yes, until the next restart" }, + { 1, "Yes, permanently" }, + { 2, "No" }, + }; + + ResponseType response = GtkDialog.CreateCustomDialog( + "Ryujinx - Low limit for memory mappings detected", + $"Would you like to increase the value of vm.max_map_count to {LinuxHelper.RecommendedVmMaxMapCount}?", + "Some games might try to create more memory mappings than currently allowed. " + + "Ryujinx will crash as soon as this limit gets exceeded.", + buttonTexts, + MessageType.Question); + + int rc; + + switch ((int)response) + { + case 0: + rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}"); + if (rc == 0) + { + Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart."); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}"); + } + break; + case 1: + rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}"); + if (rc == 0) + { + Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}"); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}"); + } + break; + } + } + else + { + GtkDialog.CreateWarningDialog( + "Max amount of memory mappings is lower than recommended.", + $"The current value of vm.max_map_count ({currentVmMaxMapCount}) is lower than {LinuxHelper.RecommendedVmMaxMapCount}." + + "Some games might try to create more memory mappings than currently allowed. " + + "Ryujinx will crash as soon as this limit gets exceeded.\n\n" + + "You might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that."); + } + } + } + + if (CommandLineState.LaunchPathArg != null) + { + if (mainWindow.ApplicationLibrary.TryGetApplicationsFromFile(CommandLineState.LaunchPathArg, out List applications)) + { + ApplicationData applicationData; + + if (CommandLineState.LaunchApplicationId != null) + { + applicationData = applications.Find(application => application.IdString == CommandLineState.LaunchApplicationId); + + if (applicationData != null) + { + mainWindow.RunApplication(applicationData, CommandLineState.StartFullscreenArg); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Couldn't find requested application id '{CommandLineState.LaunchApplicationId}' in '{CommandLineState.LaunchPathArg}'."); + UserErrorDialog.CreateUserErrorDialog(UserError.ApplicationNotFound); + } + } + else + { + applicationData = applications[0]; + mainWindow.RunApplication(applicationData, CommandLineState.StartFullscreenArg); + } + } + else + { + Logger.Error?.Print(LogClass.Application, $"Couldn't find any application in '{CommandLineState.LaunchPathArg}'."); + UserErrorDialog.CreateUserErrorDialog(UserError.ApplicationNotFound); + } + } + + if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) + { + Updater.BeginParse(mainWindow, false).ContinueWith(task => + { + Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"); + }, TaskContinuationOptions.OnlyOnFaulted); + } + + Application.Run(); + } + + private static void PrintSystemInfo() + { + Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}"); + SystemInfo.Gather().Print(); + + var enabledLogs = Logger.GetEnabledLevels(); + Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogs.Count == 0 ? "" : string.Join(", ", enabledLogs))}"); + + if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom) + { + Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}"); + } + else + { + Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}"); + } + } + + private static void ProcessUnhandledException(Exception ex, bool isTerminating) + { + string message = $"Unhandled exception caught: {ex}"; + + Logger.Error?.PrintMsg(LogClass.Application, message); + + if (Logger.Error == null) + { + Logger.Notice.PrintMsg(LogClass.Application, message); + } + + if (isTerminating) + { + Exit(); + } + } + + public static void Exit() + { + DiscordIntegrationModule.Exit(); + + Logger.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj b/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj new file mode 100644 index 00000000..722d6080 --- /dev/null +++ b/src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj @@ -0,0 +1,103 @@ + + + + net8.0 + win-x64;osx-x64;linux-x64 + Exe + true + 1.0.0-dirty + $(DefineConstants);$(ExtraDefineConstants) + + true + true + + + + true + false + true + partial + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + alsoft.ini + + + Always + THIRDPARTY.md + + + Always + LICENSE.txt + + + + + + Always + + + Always + mime\Ryujinx.xml + + + + + + false + Ryujinx.ico + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.Gtk3/Ryujinx.ico b/src/Ryujinx.Gtk3/Ryujinx.ico new file mode 100644 index 0000000000000000000000000000000000000000..edf1b93f71fc22179767d13e0ad307a217d440b9 GIT binary patch literal 108122 zcmeHQ2|Sd~8-I75k*Lr?uIM61hjckAm6UEF_brrCDJd(Jq9~PAlujy%4qc8dp%X8$}4bDW67WM!NDK@u2t(Hh=__t5)6JQ%j*7!KnB7x^ujG3?g` z9Cm1h#v(3`(Hsypm)hb5tI%Bpc9gX4mLNz|a&^>_*rzKbRgV%(M~5>atszd9a0B zF7b+{i!-+_vk8|Q#`lRcig*>A|5A9$XtS0$5{I&)@Ed(1Z6#Wg9|ps4a}nnLnP4K0jMYCCyI z+SsIFX*}6A#iq~SZ1%HGnt}thC2Zo9FehG*bm1qTY80!-1hOS9;S@=D{%sex`JlsF z>sV%cCK|400ZkU=dMO=>Eg_HLPfk^_cyB)@{tLF=2*)n#IkUPbuaa}CU{0+~I9AW= z^Q0>I!NX#Oxo@wsD{rV}d-XwBz1S^z=G9Dv^>59!vE#(Tq{^h(j~Xrt##1j5uQC7H!Q((+$F0#yt?>s=as5k zC!a7TynDOn!irQkP3E+XQZ?~A$L_BUtvfs=e9rg%Gq29B&^FDvc17v4tniAHc%6MW z*@?DV>x6USc=KFkCRJ?B9#O6+HKjsYYUSjj1PO5hs}{5Inh`4N+%oe7Lr)0Ke#9Is zvSzr};sqOs8-Cb(Rc2LPT8kT*U%9_hVQx)&dsxx&VulyA<@c`K%PD$!ma z&rh%?F0CbxS}JnYh0pMaP=hCAV^M>%{T!ADF_*1Lx3zR?7dU$STrpB}MP0UO`fRK+UOu0?AU^BAQjGbN>^ zo>cs#t{W#s7)~4(zDAoxKgNk=|1iQJr{r6SW2arYFs^*#ua6pr3zqsB2RrEx3*J(> zVN1bJ111ZTV@gauFSwOvCEw(+9_~`F_2oquL6>(De8}7@L zn-^TES}GMk(VHys^1=UkN!AEG%)|WCte7F+nXwN$jO=C^f@l~9TN+n(&p1P5T zR*x&060V!HN`BR_SDXEAUSeLu{M~DRicBoG(B#;Cv3!$rLl#AanvIVfwUIRY&8Ml- zn$~3ohc>KLRlHy~y4pbN={uhVbL4N#eUPbec0Tiom63}K9TwDom{7`Lw?$GT8=s~S z8Efe1Jb3Z5c?a$g1Mg=*?eR=>WoKp?x?@J@QNh`@%M54E`IW?3`;J@wTvbhc@+>b- zLT356D<8^8d!$C&395_k5pCF=?Yl{G#8VdHX2-JxRkl5&eLcO#?4IayI-oYgT<{wT z7kJ{hMUkM&62%%JcHS8Ug%2GXsuaLI$>|0u?cHkP(%kfT>2UiMBe3|1tT=VisO2_~ zOBK!@o%mBt(Q~yRK0Ky`ZR|$_&g)8d>chCbq?`+49y;Vd!W1;Z*tPbDBtJV-zVPm` z>&5EuzR)G&dO{UfTG}odm>#mEDa48!YP-%RJgI)deKLbh>8_DFaWZ<;JEe%P zJV5N0$;tia*s|rfh}_2n10O8V@+1xk4qAMrGQn+L;RWt{PEctfd@xhY-l*YM2Ky~E zb>S)(wc{ji=J5Pl;5<82GH3|fN1xp|$iUT3MXbBAa`}~GMtt<~s75kA8Cu3Pn8{7W zlW@YCo8+XfNq}tRJ&$u)c{Dk+o`BO4Oh!4c6v0<5KwOb<`HC4~5l%4B*_ZgD`o0bB z0Ys4+aGz6zZCm+u?>_;qcU)mnXBr>^>XR9#y25IjJb^jLl^s!Ej0AymW607Bj z5z(S3T8o3fLINTJPv#&|$Sc)0&4~3UcqVRQcSxMajOl`6!lN$7PBB8yZVD=5%g{WR z2W6unUSx9ka0i^7pW=Mgg*v-KP@LVND$GGV4?1r(8K*tk$1gCYWTXH25&nePcWiVYu(1bGx>u=;@8ZEmT*VBKlKw z&362mgRew%)x1*Q$MFW1J;2J9_U2(?+*lo(d_wJQ_ZdZF1ssxr5fCet7do4H^ExWCFQ>snS@K zD5aLeQ6g@qSgf+VK0|2~?tqqllH^bQu*FOp0)yUa9*A1xanj1a2*o}4%6*rs@tQ8` zchl>cUJyl|227Z*>u==imn5g?8cX&Uiem{dsL9J6CZCylWG3$HLv|;BdB3f@u1lH_ zF9bNQNNBjsuRM8QA~9cU-;+CJM?ug`;^j->w7f6EHs<=5j}$~04vFS4A7pCm>b}M$ zFO_s;d9@G$pN3Be+{d#pRJ*2d+EeF2K4nTwbCwHOmrr;XNn9WL>S@enbpmsZ`nDVc zBRkwrgL#euL~|?I;}PzMltXyaQX48#!-e%5wwuO(R8EvO+|NgO@POR#2F1}gFOG28 z#B?ix$K{B3(b$9rm5Pvn!yHGZe2`rELo)2S(|v=8#L?1NgF4IE2K8x*IkU3;){hHx z-T3SMKiG*_&$+K0tKY8BTI_(I#X@8WaQ@=Km7q6m7{P}SNI0JL?aGX>DN0xYkAjq) zh2xMl%q)4w=0s|Z*2=5&aL|ci33v@JKWw4Lr`MACRcc}@_RPIf(49O7F$k~khxt#gIm^=70EQWkL1}U6hs*%N!xYql@;KLk`mZ$Nr zBF~fBqJQk;F-KeNK}F0`>>&6eezMYF!KKccHYGAR`y_pWuDF=l`v#VakA{DgdTOU> zgp)Wlt&}-nFXXecsnNqhqdz+H+Si-mkIrUMxFCup4Pz=`wpi;sNc4wef@@oQF9FPoF(v*fI5cJpQ-T6HW&Cm6d1&f~HE9xUspD z&${OmJ+y`ybAXzi8E~ys8(rR$N=F#a@C_-kKLIsCzmm?bn=W(rcOhRsN3Sf_+%0`IH=})W_DW zW3rTZpS8#}Z?@|6*`&Q3YZo|QzFay+vXyr@Avi=|2)9`FZM{Z_bb@G-h2E2hD*V9-mpvnbZ+<@V=Cm#OwGaOl>o>Dc zL>-u;PAK>`e+RdVX9hk&k2Tcv3^#V3_$k|VdLB_RQF;DiqW3n^9>;HPyeg`uPPV6a zWlK-d(HrWmHA#)@B(dU+j0pZ%P>A!Uy?LGiv#yJg#frB{dSioJl!)$!oNkUSj)Jzp z41Yv#SHj|qhhJ=*#-0(_s$W;VHd;nPZhpmRiDe;GGd`2T0w&GavxH==X}v0A^0Qe7 zIh7s0?61gseCj|=G@smEo73;A&$ADIX&;fFp%(YqM`d3?=*sWMwq6sx{_X%NbdV|^ zv}iwOO0CJ=3ccB)RWW&GksMBvIdZp$p5-GwIC4XPpd0s;F#bLH*4qykm#7V2z{jWN zJofX<{fC2OMPCpC<`b?rWJo<13uQl(CC9uJyX+}3Y4Y}rBMyV1bA0gyzGNT!TfDPI z)EGxK$JvK>);f=pWb!HHmA^r9D<@|Q@*1V)UHmq6m&$?TTGxN5WblMI{89+obAAH1 z&$O9qmSoHxe(ce_xjrR}!k*ht`gPbn*zR6g*uAPJ%#*Y|U&L=7Z@37`qqH3Vd4m}F z{4DJ%i;H0+?qDHYW6!afTb1WN-tO*`KWER!Qq_lR6Xe$1A?>{(@{%KG*s;WeelhZ+ zY_%{;1x4$bsZ8Uc5a=xs_6@?FlFt+nzIxTmFol$zB^>%A_N3{SjE~mj>^r$P`92BN z);uP--qzP&>a)glp+tl(*AENA@Li;rV^an_FoaJOH=C?QhGi{z(l~sMWlUIQ#;y8! z%mr%v7F8#X?@+L;^na&%)ML`C5Y=~cYVtT|=|R)%JTmawf)V2|IsSE<0v8%E7icWl zzVQ0|WnMCpx@t%FzhrXtsQwyx^Z~}}FB5&Hkd*AY7WaZRcXaqwMHPWMa#e^F7rvPN zx`Vmb#Jyr=leWkQnX@c(D%4lj#oU+sJUh;Z*@j@c@q&bhl9=k569lGTah1l_93D8m zI?{5|_**!w6}lLQM$q8HCLf)L7~gUBIXcxtmH&;&CuQl2;-gr{saD8pJ=fTO-Q92b z0{sh-td}%SZN^PdG_!Hl*uQY5Z0MJ0)-}ifIrMGn)-$>HJq~OV#ToCvE_UNV*{uW+ z`uu#Nm$9a@&q6;eK5UY*?akM0-nBe-(bHBPTIcfe(DQ#{SN)hd$G+)Vc8}KvXFUug zoVN@e@0^~AT`}Dz&7oOyf9KgP_z%YU%mpcAd?tQ{(-5r6O+opZMKY=axQ3p8$}<5#T6y7k61J$j0K$=S(R>=J^E z_iAjC+8D!W0b{>REHt<#dHedjxy51rDNk4e9-Igbj^1oj`IdEqIc|>YHr$Kpix=fv z`otj~h2f5m{2$NEf##mH?=fBk$&7yjHE~dTm$h~qOb?p6a zm%32qOTo%&V*Hhl7YICL(I`&Cuh2aZ8cAjgZ6KdMwP(oGh8%Cmy@^DX>DVUD>z6L$ zMOLu+s~yb=V|QI`_siz{R!3Kka25rRcNWH^S)WL=9SX?Kq_fWJ6=zP-^qlr(OoIQi z=q1Y8H$-Qx=2|(sj?+)*$z(#t6oMJ~h)F)V;LaS*h=sfERlHEYvP5FlcaK5K!kG{9 zWrwU`U5=HZUQ#8nDSa9!@XBV z?BcK!M&iFnV)YN^ToJc(Df1Q?9BW3c)}zW>M|Uq;;Jv_)vk+Fr+d#Z7fuqZ3tVN$H^FNj6qzlF;l#fIv1LxzJhw+c0D?DvV_3b0`zF3gVr(^j9_q;Jy zxwp2ImGOErf5Ck_Mv`{Jf~)5FUU|6YVIm(yw17Q=t1g2)>BtzTo&T_IDXUC9TxBvu z`SrQc6TjV_7N7oO=a67&ozhLa6EA(e^g`qli)G{@l@(*ITRL5y=d?rN+FU~R24*qr zptEY+$(fs4CzCu*}y&gRke`&)<@H;(~}JOHe@mHa2_F z>oK`ua|VAm0~zA=e619{UVq(r{~hb-*E7$)Ci9IpBN{KWNOsBNm~SolO-^ZSo#ssk zH+R#y*ewaXc#t{Qg3khkNo&?#{$5c_cuL?%y>{TISJ+hvjSID zs(c1ffW=TW(#xs{Gp&52Y~mgL#?%0}_vwn`uFLt1kFLGPo3`B%)8u94)xNm$UQ#Z6 znf)^d*(5|Mld{jl`&AC1Ay1Xwl|PnYB6BOSNL^1-_Igw|P6$ z=Gg9=9!m~dwPk#-!cL;g) zBBOi!PY2mF6qk>Z#$r`0*_G{Yzs1{)zQg=W8n;AQbxnNO4^6r(7fh9x@vSY2NX}f1Gc;_JEl3@M9jt*mOr*+$QY(DQK11 zo;=<9_|{G*7WD+}0}AUbCkH{#Roz2w3a7FuPV3ROozf1rDuMT*WnkU=`bF_&QB2Nf znFEV`!VhVj^u-vWv`g|cox_WihpF%2P*^?rVc8Y12=P+Oh_x7!kok2=aSYg)d(CWf0HVe{M|@ zWYa9J|KMC%;d~w_9(-V@3GSG09Eq%(ykWJy(jBRVmNvhx;jN8x?8>6OP&OzX-k)6Sj>1WVC~g;2v=`RCkHDWVO%tD9DA81y_tnlHo-LG2so_ z8?h&fvr- zKJ!yc_+*kl<^a8}jj{fd6Z*vYP<^cGM626ulA4d^hlKDuX{P;(RI0kQOjofYcVqM( z3lA8Z8JXt2QXH+Sd|PAQpmEqbS)A5CyXtSp?Q*%lw+O#Ne5J}-({U>1CFQA6@hk!N zN5wp~*FGf#PhAXSer|h_?W0`q$s~4yyEw-g5&J{W1&br67hmv4vm~^de8ZRh?*0I07d|d&BQ6zw-n>K4 z?ZXbP!@jaYS4>_VY}v!q->Zebicr5hlw@rzq>#vq*TrQpYhP0ja;S@)z1|BVv=@z3 z+Wk87><|C**{^RjIO2}l?&VbGKE71r+vSr6yG|%o1#y?#dY*FJ>}Puz>@dwnmqd50 zde*zmwY-gXm%C!v+K5SqGs`8wKy&Aal`57U>Tlr*Mw^@B_?rny?qG-%2E()Lvau2$ zH~T4GT+6Afv*TQ*0?Q>n&a_c!S2h|1fu(Tyjg^FN_j1O>=j!Qbg@wy%;*X66VBRcD z++e~J8%!9L_A-~8>Ur;0%DY0CUeQq0xs5@`CRlwLR~b&q@PbOQaE=0t`K6mr_+2uI zSSVZd=Ctp=M|Ze~X7V29n$D^=Zv3>PZ&($JdZHGKM zpvruXC2EM&wmO!Xo{6(;2Z4feFgW*PAS|#H28{Ya`tx3G=1@tQ zKwJw^<`C?SHq@#U6-uyA%bwda2?n}-;l){ISQ)Zci-iua)?UiY`lw;v7_jc2orL|9 z^-K+HZVh}k=ZVXih9WcFahq_KqopH!B|_Pf*y`sR@(gEzT;BJ33tnG!^eBb>lLzS& zYdIg8By4s(eDgL&+|EH7X%uc4no_Ld}_kB;gXQs^s>~gZ(h^HU6j=3=|LO^}g zc6G4n?z?#xo1z)|dcF+i%>Iq(8=D}@IG6MxdTyCEoLiY5+uwi2maufS+%aa!3Agv! zN<`QG|Oxy7wHSqd}8OMb9l{b(Ghy?|@|FsDJ# zl_SrvAvg~nzBk%Kg6D{dRjh07Zka11AB)VKdr+6SJul^i{+6hZp-;-Clt};-Fj1kPXemrsRSd^_BYBSGYts>7}y37l6 z!0N)TJh@Pwvsubf)}4!_VpDQz_ST1HHVNnP`te(Ybf!EMw79V{T~J=``-yWy3Hd8x zSd)hFPaYX7MG&>&F~`fk`iKwx=%c)E5HWGulFPfMiwCY#Q1^Jf%TWgv)=A?qY~e0kVcs5#&Jh!^qw$9d@D_xW4>&MBw)$;Z9s%C*8-x=h~{nNM<^{MLH$Kg@<) z26eON?)zFOAyrp-l~)IQFT`SqjIw7cPA{&w=B zSHSMvm%fmVlPe3if<3#|?bfvt4%r)%r9Hmm?fmwrXvNOUR=Y79k9>PCnb)pc*}z)D z9ICY^t28BrGgReFX06b6d%qjnv7bmcY-GhWd2t6rrJ|*N$a6W&@*G<-cbP|#xTxP1 zRW=dcMVKIzR5|%18Iv#0k~VhB-H{#q)LKK8^at)vd=Iop-!2I(QI~PY#C_kJC2#y* z?;x1$xo4Qd5nF7-HCE-Rs>85U+Z+Wh-W_`B6Vo34Opn)pV}JA_J0Vkn>yzv)@>CA; z2!-QS%24!I&)Goqdn$R4XZM)luDZyCzIlaBN%ZBC2V%<&e?;pHsnhd7sk2GkR6Q-S zoXLV!sVbIhHt7sZ`}i3jHPncXCu9=W5jHp#1Yx7+{?ZC@tu+^kpEzWC8s6?&`E!#p zqrLOUrWA*6EHJwn=h)T;oiLnN+L;zc07foTziTr#=^KY79MkwV^(m z=@m!m_%C}0Yau)R2i4k|SY{+W+ zbHdK-2@JbJ^g;Id@1jpwYgkmHZj>lxv%YZV_)zJT9lbp`@h;@UR!{AK5V63Ok<3n< z_T_WErbpj=m;Pu3N@S)_So!zQJTtGA#1m^!mgjzAd59^fswUOSm+aJz`BY^QY#trK zyhbJZz#!uNu!1O$tO=R-PD7rnUOB}gg3IG5(+cxEPQ308J)Vnh!gc!hWn^Pmh!c4a zEgt+qwPK2_l)ntmSxilA3;)^I%Q>X*IV+`^PASVUc^vdxt2I>O#>eP+UVL}A;p{k6 z&y2lg%oJvl@@vKg&zXfjeEcltL(Ny{vzdqblJY0rzW(&b`y8Rd?0fpB$lMom%stG* z=AD66449g##)6P}hI=Q#H30>{5(l&fqUjGjjK4m~0ZqetuC)fFGiwdV4FGaCP_Jp& zkPHaXG$ifQG$5^q_-28BBwVwCi|-Rf7>RT%2Y~OPfDk};`EOlj>L9KRCR|GiKarmi!rAGF@QsMmF3B+&UB=w<#XWuX?* zc?D%aukpLU_$G9opk98mkNBsQgAz#ZFqDBw&=xTI1-;B|XQJ<8{!uxAwt#dUFnzTF znbFbj3`xECOh52XX$!*Pd!W#ZsxhMOL=N;L|7g4sj@kiDg9Z$N@z?h`(2xA1cAy$? zUDKdmjKP23bB|K&XZ}$cD1tWNfR<4;+V8~p>*E}t1OCx>;1iUA6@9Ea>U**R{;&I> zbihAq11bQQVO%KOhh+ebKiL@kH`;%D%l=2{>v!amfd4Ph257G{s)JpyefIZ%UH4Hg z(@FhD}fFS@uz<7WhKnb7*SlUG_gXiY} zcmV^h4A39{`CqOdbdl!)k&!adN0a$uN?%s@( z{`e<-0sdF_q|U$#6JRpH7;poS0r&-|1(16HUmy-m02)sYs6YDSpIiWafDJv7iNFWq zT^L{jxC1B$^aS6n<6!XLy|sg=&(|7get75K@((<70Z^Na+W1@m3DBCip7JyJ|C7ho zymrO!{5Sjq&!Yg=fLK5!pr?4I6%T{|p5>p=UH*YyW;2*da z0PF|k02%;2$#HveG5GIi{(QO5I`S*`gs&i`qE|a-%tDl z-*~`GKtexpkIET?|9;>f@*M-9Z+-xv9?+L{o;ocC|9#9qaE-WU2j~D$e~+4vzP@Jg z-}n5ZIUH2?1N)hKRMr^$_dWl}wu|h0{nh8AZetky_dWj{z`YxQnuq?nX7Jzl{Er0g z(VT66aZa5sga5wge+Hd!kIDgq|Gwwnj=_I-XG#8jj}GVm(L7;?^FP4-RR;h6^4Q(0 z0nGnf2LIh<{onTnF#q`s{{Q8%yH^94|DIaAN8OL_?lq(T*Z2Lu?sC-|?=kpi@XuKP z+jC`r(9!pdj=ueYhv(7U6`H@Gpj~xgPb>y|VsNl78mDDcgEKJ8YxnuC+#JB) z|K4cFyBlX$`R4}i;|GwuxD;9_L9-_G`v?l?T zoh1O8=N_y#DHn$_a2NO|K{*IpZ&EQSGqQ_B(|DZ0f8XQ3iv{>! z58PJ)QnZb$jP=b5+1sGA%RvnO`;C8b;2iBmMSac?z=R*q+TK47eBqrI`^ zT1K^dbxnV9cbV)qUSROwZ~Sut=N^DMz$<_N@ak4M5-jJb*4>P+RQTZ2EvfeIEh!{s{B|$xsfWbWO?> z49yGv`n(AS|NX;1aE@{egG-}^RnQ~5?cJ}7*~nF7{DL;fOYGPYKiMj%BInJwzJ_3{(Fyq;1jj)=xi`r zP=tHC0I2}<4R8f0XUAc)6Ca4paoiE9sB2uRq;Fc>2Trx|*bwDWq#f@x2W;yY!W&tO6S{Pa6SR0rX z+B9PWaOnbd`V}ez&=<(jGyUZT-vGE@WZMQdLftnrw#<=X@ZTnHZE@M1ur~RpmFKO) zfp2X9I(HJKjch_R$8|=Q*?hP*9`u9q+R%1{)K?^U;wfMWCKP4XwnFruR?Yp;I0nJ!Z}zEAVBls4sI~`?|=TY zp5fSlZ!`x?Ig=KR3){2Ydy14&h~hwb&tV6eee(S!NY-bA^_F@uYh)c0|9KmO>;qD z3x=|A9BjkGXmQ+LID>!s;6EiAL-WDs0m%M?)&nTD$D5JmPjSjz5cC1Uq5g|(*z}QR zpMh3f4F2hZf5Z=PZUsO#;2OXtTAZP9tBqe-!441ra{+bG7yJn8L1r78=ePUKXe&Mj z|Gm%uk7qb002bYe)8=%CC_ls=VEgRjtw^)xbBu z`d>7l-%LdOgZ-4$0PO(Wr-^F#UJzldFq^@DkJNeU^3f6hsBgy)SPRf<2WSon*?~|W z5S4?|fOV-vF6~P`+jQ0%)akA_u0U9ipkrKN1pC3B0QWV}2P}d$fe!FZ&;#hU0X?u2 zEr)faEo_C1`rjjEpsv?Hz_$c|(g)lN*EIWdCYBj^sLwm04_E+g0tw2)444D# z(inijKi%<!9U&c&kWq3 z1t2>hvI9+Mi#JLbwC9pw3w{ecl3|(`0NNiybFc4_eaB&6K$QW^1LXmiWL=X|n~pfA zzR%#FF8N3HqKg1D#zf_y#h8tnKXeW2z?GoxCjplvC=0h+-*3;);Gh2Z&y2zFz`1=h z{~6#eZjVC)^L$a519${nqPc)<$DI*v?jvn04hH}9$3Nl;IM)E6HNnMzc7G4mY(pwL zi~;;$Z})pad7r^QUGtCnfxkiFjFzi!UWDnJ7dL{|Gp)lJ z{L?4@t$FG#KZAb;|4;@P`~UuK4N#BG|Mylsrxh=q*8g_x9Y`1WE>f?*>F;X>|BY+V z&^olg1qt>vHSD5QUMb-W{wX=}cP<^)e>mTSWZRKe4E{UHq0Zf>L;k5TvP!2hy!2K6-0KA>pD;GdEcf9KK>|FzKXzYY-6GOX)RDF*)?W>Kde zpdpzd43x4*--A1TJ*zf<&f_z<1&59^l5ch?yze8F({Qw>C4`(0emAARd8ed5(D}a!{4}2< z^|$MHTD%XxLqh%vy!*koekJtvXr61|n%|E6!2LfA{yWO0j_(6E==`@^J;*<@i37l!?bG|w>TSjs=20PfE*`2RCsx*D?9pn(Im%fMASAg!x_dYuC6b>4!D zewXk(0OBI)0TckEA^vvvt#nmh%JaZI`fV*r``h0xYX8>(IyHAe(SaUZO_*y1p2UE+ z#ehX!1@Nv4{LcYb2Gw(Lhr>6X1J0!RtNvX_c>{SZNk{ynbD9Pu|0th=!}g1 zECUSw`=M|E=d6Gw0M!5PZ~pZ_21fhe$K?R>9Q6Ya1JHSi{Z$6g_g@FV;J@$rNBw{) zfX9IT`VI`}@n0WP*5k^sDfp>IX7P1L`?uRnK;J=@E zr%o1lN57$G)emKW!9O(-{d5hyqqEad888E&Zvo}ElzRJ{z~H~1IBzRi;2nJnRs$jc zeK9Uz@ZS~@{TK$kqduV=;0OTm)0=Gpga3ZynpV=lIT{-x`v9^Lodlq>w0q-Qz~G-2 z33Lz+oFh9S>KDue>;$|3^kg|mhWG~T{4X6))axPuKWM%HwFhY45VZ+t{Q&AGqIE=Q zEg4!*h}M*n09`E`sGl(d@Ym}adp#otXaeNlfAtuK`|W`X@MF$Y0mv`h%Fo@(&(_M% z)PbKm0Th2{{H4utHa@RJCb#el|LLcQ(D*!+z!X2JIaP{34Z|STCjBvZp3u?{kGHsA z-O69m%AeNCPi*BUwDNeYvQM?ekJmgYT*{~|KYE1d7s$IpVpC|)bf2O zKrPZMY3+Azb^i}PTg&&ITl>9Q-2dI446yns+)Ccoex+9Tom=_IUGuZGdcL)vyVZTA zR(|J}e)yiXNFP2=|N5b2g?9d5KWg*4?*F&_ZF;|vb)}|j^uM+L-}b!u8B}YOny-;V z=)j-W>ON9{`h6r2wI3x&?MGWesr(bbs|+9jaF_`Ehd<@U{{!|Pth?2QHJ50A46Soh zVQ)O?72HSrCsx2ZTOL?j+h=Pmdpl!6FZ~Ymr_L9&hbR&9K^yX*SJssGw!W?7@1p*c zynsFPq*H(%+e_10uD_*_E0StAiTN++4|zbk-CKKXx|y$?eys=iM`fT0z6o-jP81#lI}HMwAK(Xw118dd z1cZ0#_q4krM-SDcUE`w+(6MknwI{c1FGAfx~MlL zj|Q~h}h6{ioKWyVq^%59Nv(AOhf_?svi2y|5R&VXU@MH79jAt?M4_ z|Nhbc>w&V5@{E@LTyXC>U?1SOJyzSeiXZmEr)nA17-mM&()!=#q&Eqi>7=MSuA1U7VIt?QY0}SUuCy4(q@G*MBnT zj@Hb~P6vAm`m@>e0fVx?7}|g=_$_L2O1{u?2e|&I4M+fN1W>mDV?qB6IL}y(mfn=` zfuuj^I0zs_1=Rir1K{_`al%(lZ5GlqDG`BkF9~hnD<}si^i7L}0U}K(5;3y)Ihdk5 zatE0H$d2#>kVl1bxUNP1@Js9Rzz&xWb9DLOR)cOe=$Z-zkSDtato|_=6X+=eKzl7I zSP$1_0PUEY%2N%^^W+W83a7(&&O1(2lX7uq@_Cp zqjE6N^rtSn)YqW96W}G_cU?ELO2;*|0d+yp9=6#A{)hBe2Hi^lg4Fo}ZGau{pKf4Y z(B|2n{~`T{f$l|s<<$B^KVS~@11j}Ri`x8si~oxL&<{X;Lp0BHfLedl_CxD&-CB)f2@BAj5Fl_kM(~H`u}n6=>JLi zuLRv!K>w;W{ir`i-Hvtlnx5M~v~T1bj4}S`SE0=KkL!yXCQ!$`~ z24F6U{0R^Ou!6Qu2y99%#ux4Af4PVBm!Xx$--ZL1gFvrwv;g{wK^^A>8v+~roR#3Ly?zN}L191-a4L*l7JeMVIcl#qj>rfuuJ@R*)O*b=VB32Xsew z^bWG=O`*g&&|Hw68-Uj6bXR}W{`Xq_?~D8d`NaTn-OPht{r>fl{-`g2@&J{CuGWPC zuRkS^L3dOaPHT!KCbu-ch0BUzB1le(%T^uJbiX>Y zZMWW}DZGAjOSdsUx}hg}kFEJV?q*lG*;Q(Gots=7xzPn`TDxf|pW&}D|Eb(Ye!$bx* z@XXlOadg$+P5Dt7T0xx_$bw`y0n`9Gk{#WrH~G<8VkA>%WTz|nL1r}Pf4vK`)0_Mv zAbVHxp5Ej~?P6!k4dpqyDgPk*jsY&eb5F<*aiDL(`8=m6^;2g6#|{p(H2Xzl@{Gx?Q4b|L@r?+cCAm;Gc6ed`@cpaz&dCy3K?`DKO4w<1@NL7 zpTRBt8ad*niPewSFwgcD`id360jyuIdxHRL84uEp{IHx5?x&0xHlaXp)5#|NI*NOCZ{!{Xi;)3j`FNL_6LkWXCw}<@qYikb?y~&Sc z2HCv<+bQxx`#A%~ORv_Omi^urK)FW`^8a{-L+y?@0G)-8unq9=d-PCAW2(xd!HE|4AB0sPT5`hz|koaxZ;4fZUJXbNfiPsj30gZ%FRjrk3G5O5&>b2uk- z;Q)~z?K9T~{I+qy8n*>-oU}kJx{x^Q5!aU1)#OK=%1<#S8h5&TZ&yyF+=dr`n%|paaZlk>I;Uu^-9R|6mi?Ibtkdz-ckiZUQOn&(RIq7LY+ zffnb8P~z)r7vwOgn$ zBb`C^@qh;aG;d02Yua+@Req!cvZ<*7&I8aoyE;H?9nhL;q3-b9lkdP%Au=Y6VT~?# zvkN1pMnAYDXSffo7saiIJIG(s?4~ul#8xh$*-zWeRzmR*u#DEOa8r0@vx_k``CGfC zUQOXO%`O4gmYq33+gisYbS-?P9Mqe+m1?+1KJJL#-MZ9 zP`}L`aJU)h8{Fb-Im$b2xt-7-rT>)rU5K9(fX@19Yn?>}gtxE{QNP>zx|8XrP~L;M zkzFth(7G+9_WF|HFf%E9w!LEgw8&L^e>oNWwdB7dNX}gW+Cn40DhQzMDxIY zAV-Tl=&kf2O*9vW_8DvdEO*&`n+I(7&M*(81oNLfa5iG&dVpR`AFUTe^RVb_-dAp} zfnJb4tiNse2g<@RSeLLFT2T z48Q?}!5oq*lmk=B{XR;c1=7EW(uB1De4zW8zDysb1?i);s4OrCg7P4cat2Rd=--?^ zY6~0H)-@|->g)6y6K>;QC-qOFyb3zB)#g)&{cis$@3iN3GW{!*IQk)d7!UmWyK?=I z{u!9_`u)v>^wIih>ilkf-BbBr3Ea4W?ktqNM=qqD+Clo2aK9`4-w}|G2Ne#&wI!r0 z2JIAe(v8;;H(FQGT4!oMO236|{f|igA^_qR0ieBr)M-**qx5@q+z9a_TQ4mcy2O8X zvjWmb`GB}c1yGmCa`5wYH{SN%Ba^FP<5xAVt{G{<)i@%6&>F*b;0QblIKoH(#@>XH zBXI;lU%1AaTy!l2_kXhSziF^9gOu`qy*281fcgRs6zDyJp>#~0HA(PEC7v}ey{hUMAF9lC>~3Ab`&5V zKq=SA4%wpLjN+j2J{pg2qv-j}|-BkKbFmqW02NV$ve4~F*_Wj$j)1nU>}K^&{0FE|O}pm0ZV zQU0|he`Ef`z17ff90Bhubv1p&8N5F}If@q#@=JE-eG~`fkH-53?a5Dh7rAY`{{zbJ zP$h9S h0UK7aRDdghsTr6u*5VKK1Q96rnV!Hk*hF0c{{!KrZ8-n{ literal 0 HcmV?d00001 diff --git a/src/Ryujinx.Gtk3/UI/Applet/ErrorAppletDialog.cs b/src/Ryujinx.Gtk3/UI/Applet/ErrorAppletDialog.cs new file mode 100644 index 00000000..cb8103ca --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Applet/ErrorAppletDialog.cs @@ -0,0 +1,31 @@ +using Gtk; +using Ryujinx.UI.Common.Configuration; +using System.Reflection; + +namespace Ryujinx.UI.Applet +{ + internal class ErrorAppletDialog : MessageDialog + { + public ErrorAppletDialog(Window parentWindow, DialogFlags dialogFlags, MessageType messageType, string[] buttons) : base(parentWindow, dialogFlags, messageType, ButtonsType.None, null) + { + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"); + + int responseId = 0; + + if (buttons != null) + { + foreach (string buttonText in buttons) + { + AddButton(buttonText, responseId); + responseId++; + } + } + else + { + AddButton("OK", 0); + } + + ShowAll(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Applet/GtkDynamicTextInputHandler.cs b/src/Ryujinx.Gtk3/UI/Applet/GtkDynamicTextInputHandler.cs new file mode 100644 index 00000000..0e560b78 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Applet/GtkDynamicTextInputHandler.cs @@ -0,0 +1,108 @@ +using Gtk; +using Ryujinx.HLE.UI; +using Ryujinx.Input.GTK3; +using Ryujinx.UI.Widgets; +using System.Threading; + +namespace Ryujinx.UI.Applet +{ + /// + /// Class that forwards key events to a GTK Entry so they can be processed into text. + /// + internal class GtkDynamicTextInputHandler : IDynamicTextInputHandler + { + private readonly Window _parent; + private readonly OffscreenWindow _inputToTextWindow = new(); + private readonly RawInputToTextEntry _inputToTextEntry = new(); + + private bool _canProcessInput; + + public event DynamicTextChangedHandler TextChangedEvent; + public event KeyPressedHandler KeyPressedEvent; + public event KeyReleasedHandler KeyReleasedEvent; + + public bool TextProcessingEnabled + { + get + { + return Volatile.Read(ref _canProcessInput); + } + + set + { + Volatile.Write(ref _canProcessInput, value); + } + } + + public GtkDynamicTextInputHandler(Window parent) + { + _parent = parent; + _parent.KeyPressEvent += HandleKeyPressEvent; + _parent.KeyReleaseEvent += HandleKeyReleaseEvent; + + _inputToTextWindow.Add(_inputToTextEntry); + + _inputToTextEntry.TruncateMultiline = true; + + // Start with input processing turned off so the text box won't accumulate text + // if the user is playing on the keyboard. + _canProcessInput = false; + } + + [GLib.ConnectBefore()] + private void HandleKeyPressEvent(object o, KeyPressEventArgs args) + { + var key = (Ryujinx.Common.Configuration.Hid.Key)GTK3MappingHelper.ToInputKey(args.Event.Key); + + if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true)) + { + return; + } + + if (_canProcessInput) + { + _inputToTextEntry.SendKeyPressEvent(o, args); + _inputToTextEntry.GetSelectionBounds(out int selectionStart, out int selectionEnd); + TextChangedEvent?.Invoke(_inputToTextEntry.Text, selectionStart, selectionEnd, _inputToTextEntry.OverwriteMode); + } + } + + [GLib.ConnectBefore()] + private void HandleKeyReleaseEvent(object o, KeyReleaseEventArgs args) + { + var key = (Ryujinx.Common.Configuration.Hid.Key)GTK3MappingHelper.ToInputKey(args.Event.Key); + + if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true)) + { + return; + } + + if (_canProcessInput) + { + // TODO (caian): This solution may have problems if the pause is sent after a key press + // and before a key release. But for now GTK Entry does not seem to use release events. + _inputToTextEntry.SendKeyReleaseEvent(o, args); + _inputToTextEntry.GetSelectionBounds(out int selectionStart, out int selectionEnd); + TextChangedEvent?.Invoke(_inputToTextEntry.Text, selectionStart, selectionEnd, _inputToTextEntry.OverwriteMode); + } + } + + public void SetText(string text, int cursorBegin) + { + _inputToTextEntry.Text = text; + _inputToTextEntry.Position = cursorBegin; + } + + public void SetText(string text, int cursorBegin, int cursorEnd) + { + _inputToTextEntry.Text = text; + _inputToTextEntry.SelectRegion(cursorBegin, cursorEnd); + } + + public void Dispose() + { + _parent.KeyPressEvent -= HandleKeyPressEvent; + _parent.KeyReleaseEvent -= HandleKeyReleaseEvent; + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs new file mode 100644 index 00000000..b3f509a0 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs @@ -0,0 +1,203 @@ +using Gtk; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; +using Ryujinx.HLE.UI; +using Ryujinx.UI.Widgets; +using System; +using System.Threading; + +namespace Ryujinx.UI.Applet +{ + internal class GtkHostUIHandler : IHostUIHandler + { + private readonly Window _parent; + + public IHostUITheme HostUITheme { get; } + + public GtkHostUIHandler(Window parent) + { + _parent = parent; + + HostUITheme = new GtkHostUITheme(parent); + } + + public bool DisplayMessageDialog(ControllerAppletUIArgs args) + { + string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}"; + + string message = $"Application requests {playerCount} player(s) with:\n\n" + + $"TYPES: {args.SupportedStyles}\n\n" + + $"PLAYERS: {string.Join(", ", args.SupportedPlayers)}\n\n" + + (args.IsDocked ? "Docked mode set. Handheld is also invalid.\n\n" : "") + + "Please reconfigure Input now and then press OK."; + + return DisplayMessageDialog("Controller Applet", message); + } + + public bool DisplayMessageDialog(string title, string message) + { + ManualResetEvent dialogCloseEvent = new(false); + + bool okPressed = false; + + Application.Invoke(delegate + { + MessageDialog msgDialog = null; + + try + { + msgDialog = new MessageDialog(_parent, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null) + { + Title = title, + Text = message, + UseMarkup = true, + }; + + msgDialog.SetDefaultSize(400, 0); + + msgDialog.Response += (object o, ResponseArgs args) => + { + if (args.ResponseId == ResponseType.Ok) + { + okPressed = true; + } + + dialogCloseEvent.Set(); + msgDialog?.Dispose(); + }; + + msgDialog.Show(); + } + catch (Exception ex) + { + GtkDialog.CreateErrorDialog($"Error displaying Message Dialog: {ex}"); + + dialogCloseEvent.Set(); + } + }); + + dialogCloseEvent.WaitOne(); + + return okPressed; + } + + public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText) + { + ManualResetEvent dialogCloseEvent = new(false); + + bool okPressed = false; + bool error = false; + string inputText = args.InitialText ?? ""; + + Application.Invoke(delegate + { + try + { + var swkbdDialog = new SwkbdAppletDialog(_parent) + { + Title = "Software Keyboard", + Text = args.HeaderText, + SecondaryText = args.SubtitleText, + }; + + swkbdDialog.InputEntry.Text = inputText; + swkbdDialog.InputEntry.PlaceholderText = args.GuideText; + swkbdDialog.OkButton.Label = args.SubmitText; + + swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); + swkbdDialog.SetInputValidation(args.KeyboardMode); + + ((MainWindow)_parent).RendererWidget.NpadManager.BlockInputUpdates(); + + if (swkbdDialog.Run() == (int)ResponseType.Ok) + { + inputText = swkbdDialog.InputEntry.Text; + okPressed = true; + } + + swkbdDialog.Dispose(); + } + catch (Exception ex) + { + error = true; + + GtkDialog.CreateErrorDialog($"Error displaying Software Keyboard: {ex}"); + } + finally + { + dialogCloseEvent.Set(); + } + }); + + dialogCloseEvent.WaitOne(); + ((MainWindow)_parent).RendererWidget.NpadManager.UnblockInputUpdates(); + + userText = error ? null : inputText; + + return error || okPressed; + } + + public void ExecuteProgram(HLE.Switch device, ProgramSpecifyKind kind, ulong value) + { + device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value); + ((MainWindow)_parent).RendererWidget?.Exit(); + } + + public bool DisplayErrorAppletDialog(string title, string message, string[] buttons) + { + ManualResetEvent dialogCloseEvent = new(false); + + bool showDetails = false; + + Application.Invoke(delegate + { + try + { + ErrorAppletDialog msgDialog = new(_parent, DialogFlags.DestroyWithParent, MessageType.Error, buttons) + { + Title = title, + Text = message, + UseMarkup = true, + WindowPosition = WindowPosition.CenterAlways, + }; + + msgDialog.SetDefaultSize(400, 0); + + msgDialog.Response += (object o, ResponseArgs args) => + { + if (buttons != null) + { + if (buttons.Length > 1) + { + if (args.ResponseId != (ResponseType)(buttons.Length - 1)) + { + showDetails = true; + } + } + } + + dialogCloseEvent.Set(); + msgDialog?.Dispose(); + }; + + msgDialog.Show(); + } + catch (Exception ex) + { + GtkDialog.CreateErrorDialog($"Error displaying ErrorApplet Dialog: {ex}"); + + dialogCloseEvent.Set(); + } + }); + + dialogCloseEvent.WaitOne(); + + return showDetails; + } + + public IDynamicTextInputHandler CreateDynamicTextInputHandler() + { + return new GtkDynamicTextInputHandler(_parent); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Applet/GtkHostUITheme.cs b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUITheme.cs new file mode 100644 index 00000000..52d1123b --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUITheme.cs @@ -0,0 +1,90 @@ +using Gtk; +using Ryujinx.HLE.UI; +using System.Diagnostics; + +namespace Ryujinx.UI.Applet +{ + internal class GtkHostUITheme : IHostUITheme + { + private const int RenderSurfaceWidth = 32; + private const int RenderSurfaceHeight = 32; + + public string FontFamily { get; private set; } + + public ThemeColor DefaultBackgroundColor { get; } + public ThemeColor DefaultForegroundColor { get; } + public ThemeColor DefaultBorderColor { get; } + public ThemeColor SelectionBackgroundColor { get; } + public ThemeColor SelectionForegroundColor { get; } + + public GtkHostUITheme(Window parent) + { + Entry entry = new(); + entry.SetStateFlags(StateFlags.Selected, true); + + // Get the font and some colors directly from GTK. + FontFamily = entry.PangoContext.FontDescription.Family; + + // Get foreground colors from the style context. + + var defaultForegroundColor = entry.StyleContext.GetColor(StateFlags.Normal); + var selectedForegroundColor = entry.StyleContext.GetColor(StateFlags.Selected); + + DefaultForegroundColor = new ThemeColor((float)defaultForegroundColor.Alpha, (float)defaultForegroundColor.Red, (float)defaultForegroundColor.Green, (float)defaultForegroundColor.Blue); + SelectionForegroundColor = new ThemeColor((float)selectedForegroundColor.Alpha, (float)selectedForegroundColor.Red, (float)selectedForegroundColor.Green, (float)selectedForegroundColor.Blue); + + ListBoxRow row = new(); + row.SetStateFlags(StateFlags.Selected, true); + + // Request the main thread to render some UI elements to an image to get an approximation for the color. + // NOTE (caian): This will only take the color of the top-left corner of the background, which may be incorrect + // if someone provides a custom style with a gradient or image. + + using (var surface = new Cairo.ImageSurface(Cairo.Format.Argb32, RenderSurfaceWidth, RenderSurfaceHeight)) + using (var context = new Cairo.Context(surface)) + { + context.SetSourceRGBA(1, 1, 1, 1); + context.Rectangle(0, 0, RenderSurfaceWidth, RenderSurfaceHeight); + context.Fill(); + + // The background color must be from the main Window because entry uses a different color. + parent.StyleContext.RenderBackground(context, 0, 0, RenderSurfaceWidth, RenderSurfaceHeight); + + DefaultBackgroundColor = ToThemeColor(surface.Data); + + context.SetSourceRGBA(1, 1, 1, 1); + context.Rectangle(0, 0, RenderSurfaceWidth, RenderSurfaceHeight); + context.Fill(); + + // Use the background color of the list box row when selected as the text box frame color because they are the + // same in the default theme. + row.StyleContext.RenderBackground(context, 0, 0, RenderSurfaceWidth, RenderSurfaceHeight); + + DefaultBorderColor = ToThemeColor(surface.Data); + } + + // Use the border color as the text selection color. + SelectionBackgroundColor = DefaultBorderColor; + } + + private static ThemeColor ToThemeColor(byte[] data) + { + Debug.Assert(data.Length == 4 * RenderSurfaceWidth * RenderSurfaceHeight); + + // Take the center-bottom pixel of the surface. + int position = 4 * (RenderSurfaceWidth * (RenderSurfaceHeight - 1) + RenderSurfaceWidth / 2); + + if (position + 4 > data.Length) + { + return new ThemeColor(1, 0, 0, 0); + } + + float a = data[position + 3] / 255.0f; + float r = data[position + 2] / 255.0f; + float g = data[position + 1] / 255.0f; + float b = data[position + 0] / 255.0f; + + return new ThemeColor(a, r, g, b); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Applet/SwkbdAppletDialog.cs b/src/Ryujinx.Gtk3/UI/Applet/SwkbdAppletDialog.cs new file mode 100644 index 00000000..8045da91 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Applet/SwkbdAppletDialog.cs @@ -0,0 +1,127 @@ +using Gtk; +using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; +using System; +using System.Linq; + +namespace Ryujinx.UI.Applet +{ + public class SwkbdAppletDialog : MessageDialog + { + private int _inputMin; + private int _inputMax; +#pragma warning disable IDE0052 // Remove unread private member + private KeyboardMode _mode; +#pragma warning restore IDE0052 + + private string _validationInfoText = ""; + + private Predicate _checkLength = _ => true; + private Predicate _checkInput = _ => true; + + private readonly Label _validationInfo; + + public Entry InputEntry { get; } + public Button OkButton { get; } + public Button CancelButton { get; } + + public SwkbdAppletDialog(Window parent) : base(parent, DialogFlags.Modal | DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.None, null) + { + SetDefaultSize(300, 0); + + _validationInfo = new Label() + { + Visible = false, + }; + + InputEntry = new Entry() + { + Visible = true, + }; + + InputEntry.Activated += OnInputActivated; + InputEntry.Changed += OnInputChanged; + + OkButton = (Button)AddButton("OK", ResponseType.Ok); + CancelButton = (Button)AddButton("Cancel", ResponseType.Cancel); + + ((Box)MessageArea).PackEnd(_validationInfo, true, true, 0); + ((Box)MessageArea).PackEnd(InputEntry, true, true, 4); + } + + private void ApplyValidationInfo() + { + _validationInfo.Visible = !string.IsNullOrEmpty(_validationInfoText); + _validationInfo.Markup = _validationInfoText; + } + + public void SetInputLengthValidation(int min, int max) + { + _inputMin = Math.Min(min, max); + _inputMax = Math.Max(min, max); + + _validationInfo.Visible = false; + + if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable. + { + _validationInfo.Visible = false; + + _checkLength = _ => true; + } + else if (_inputMin > 0 && _inputMax == int.MaxValue) + { + _validationInfoText = $"Must be at least {_inputMin} characters long. "; + + _checkLength = length => _inputMin <= length; + } + else + { + _validationInfoText = $"Must be {_inputMin}-{_inputMax} characters long. "; + + _checkLength = length => _inputMin <= length && length <= _inputMax; + } + + ApplyValidationInfo(); + OnInputChanged(this, EventArgs.Empty); + } + + public void SetInputValidation(KeyboardMode mode) + { + _mode = mode; + + switch (mode) + { + case KeyboardMode.Numeric: + _validationInfoText += "Must be 0-9 or '.' only."; + _checkInput = text => text.All(NumericCharacterValidation.IsNumeric); + break; + case KeyboardMode.Alphabet: + _validationInfoText += "Must be non CJK-characters only."; + _checkInput = text => text.All(value => !CJKCharacterValidation.IsCJK(value)); + break; + case KeyboardMode.ASCII: + _validationInfoText += "Must be ASCII text only."; + _checkInput = text => text.All(char.IsAscii); + break; + default: + _checkInput = _ => true; + break; + } + + ApplyValidationInfo(); + OnInputChanged(this, EventArgs.Empty); + } + + private void OnInputActivated(object sender, EventArgs e) + { + if (OkButton.IsSensitive) + { + Respond(ResponseType.Ok); + } + } + + private void OnInputChanged(object sender, EventArgs e) + { + OkButton.Sensitive = _checkLength(InputEntry.Text.Length) && _checkInput(InputEntry.Text); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Helper/ButtonHelper.cs b/src/Ryujinx.Gtk3/UI/Helper/ButtonHelper.cs new file mode 100644 index 00000000..5a8ca96a --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Helper/ButtonHelper.cs @@ -0,0 +1,158 @@ +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Input; +using System; +using System.Collections.Generic; +using Key = Ryujinx.Common.Configuration.Hid.Key; +using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; + +namespace Ryujinx.UI.Helper +{ + public static class ButtonHelper + { + private static readonly Dictionary _keysMap = new() + { + { Key.Unknown, "Unknown" }, + { Key.ShiftLeft, "ShiftLeft" }, + { Key.ShiftRight, "ShiftRight" }, + { Key.ControlLeft, "CtrlLeft" }, + { Key.ControlRight, "CtrlRight" }, + { Key.AltLeft, OperatingSystem.IsMacOS() ? "OptLeft" : "AltLeft" }, + { Key.AltRight, OperatingSystem.IsMacOS() ? "OptRight" : "AltRight" }, + { Key.WinLeft, OperatingSystem.IsMacOS() ? "CmdLeft" : "WinLeft" }, + { Key.WinRight, OperatingSystem.IsMacOS() ? "CmdRight" : "WinRight" }, + { Key.Up, "Up" }, + { Key.Down, "Down" }, + { Key.Left, "Left" }, + { Key.Right, "Right" }, + { Key.Enter, "Enter" }, + { Key.Escape, "Escape" }, + { Key.Space, "Space" }, + { Key.Tab, "Tab" }, + { Key.BackSpace, "Backspace" }, + { Key.Insert, "Insert" }, + { Key.Delete, "Delete" }, + { Key.PageUp, "PageUp" }, + { Key.PageDown, "PageDown" }, + { Key.Home, "Home" }, + { Key.End, "End" }, + { Key.CapsLock, "CapsLock" }, + { Key.ScrollLock, "ScrollLock" }, + { Key.PrintScreen, "PrintScreen" }, + { Key.Pause, "Pause" }, + { Key.NumLock, "NumLock" }, + { Key.Clear, "Clear" }, + { Key.Keypad0, "Keypad0" }, + { Key.Keypad1, "Keypad1" }, + { Key.Keypad2, "Keypad2" }, + { Key.Keypad3, "Keypad3" }, + { Key.Keypad4, "Keypad4" }, + { Key.Keypad5, "Keypad5" }, + { Key.Keypad6, "Keypad6" }, + { Key.Keypad7, "Keypad7" }, + { Key.Keypad8, "Keypad8" }, + { Key.Keypad9, "Keypad9" }, + { Key.KeypadDivide, "KeypadDivide" }, + { Key.KeypadMultiply, "KeypadMultiply" }, + { Key.KeypadSubtract, "KeypadSubtract" }, + { Key.KeypadAdd, "KeypadAdd" }, + { Key.KeypadDecimal, "KeypadDecimal" }, + { Key.KeypadEnter, "KeypadEnter" }, + { Key.Number0, "0" }, + { Key.Number1, "1" }, + { Key.Number2, "2" }, + { Key.Number3, "3" }, + { Key.Number4, "4" }, + { Key.Number5, "5" }, + { Key.Number6, "6" }, + { Key.Number7, "7" }, + { Key.Number8, "8" }, + { Key.Number9, "9" }, + { Key.Tilde, "~" }, + { Key.Grave, "`" }, + { Key.Minus, "-" }, + { Key.Plus, "+" }, + { Key.BracketLeft, "[" }, + { Key.BracketRight, "]" }, + { Key.Semicolon, ";" }, + { Key.Quote, "'" }, + { Key.Comma, "," }, + { Key.Period, "." }, + { Key.Slash, "/" }, + { Key.BackSlash, "\\" }, + { Key.Unbound, "Unbound" }, + }; + + private static readonly Dictionary _gamepadInputIdMap = new() + { + { GamepadInputId.LeftStick, "LeftStick" }, + { GamepadInputId.RightStick, "RightStick" }, + { GamepadInputId.LeftShoulder, "LeftShoulder" }, + { GamepadInputId.RightShoulder, "RightShoulder" }, + { GamepadInputId.LeftTrigger, "LeftTrigger" }, + { GamepadInputId.RightTrigger, "RightTrigger" }, + { GamepadInputId.DpadUp, "DpadUp" }, + { GamepadInputId.DpadDown, "DpadDown" }, + { GamepadInputId.DpadLeft, "DpadLeft" }, + { GamepadInputId.DpadRight, "DpadRight" }, + { GamepadInputId.Minus, "Minus" }, + { GamepadInputId.Plus, "Plus" }, + { GamepadInputId.Guide, "Guide" }, + { GamepadInputId.Misc1, "Misc1" }, + { GamepadInputId.Paddle1, "Paddle1" }, + { GamepadInputId.Paddle2, "Paddle2" }, + { GamepadInputId.Paddle3, "Paddle3" }, + { GamepadInputId.Paddle4, "Paddle4" }, + { GamepadInputId.Touchpad, "Touchpad" }, + { GamepadInputId.SingleLeftTrigger0, "SingleLeftTrigger0" }, + { GamepadInputId.SingleRightTrigger0, "SingleRightTrigger0" }, + { GamepadInputId.SingleLeftTrigger1, "SingleLeftTrigger1" }, + { GamepadInputId.SingleRightTrigger1, "SingleRightTrigger1" }, + { GamepadInputId.Unbound, "Unbound" }, + }; + + private static readonly Dictionary _stickInputIdMap = new() + { + { StickInputId.Left, "StickLeft" }, + { StickInputId.Right, "StickRight" }, + { StickInputId.Unbound, "Unbound" }, + }; + + public static string ToString(Button button) + { + string keyString = ""; + + switch (button.Type) + { + case ButtonType.Key: + var key = button.AsHidType(); + + if (!_keysMap.TryGetValue(button.AsHidType(), out keyString)) + { + keyString = key.ToString(); + } + + break; + case ButtonType.GamepadButtonInputId: + var gamepadButton = button.AsHidType(); + + if (!_gamepadInputIdMap.TryGetValue(button.AsHidType(), out keyString)) + { + keyString = gamepadButton.ToString(); + } + + break; + case ButtonType.StickId: + var stickInput = button.AsHidType(); + + if (!_stickInputIdMap.TryGetValue(button.AsHidType(), out keyString)) + { + keyString = stickInput.ToString(); + } + + break; + } + + return keyString; + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Helper/MetalHelper.cs b/src/Ryujinx.Gtk3/UI/Helper/MetalHelper.cs new file mode 100644 index 00000000..c2c32d3a --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Helper/MetalHelper.cs @@ -0,0 +1,135 @@ +using Gdk; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.UI.Helper +{ + public delegate void UpdateBoundsCallbackDelegate(Window window); + + [SupportedOSPlatform("macos")] + static partial class MetalHelper + { + private const string LibObjCImport = "/usr/lib/libobjc.A.dylib"; + + private readonly struct Selector + { + public readonly IntPtr NativePtr; + + public unsafe Selector(string value) + { + int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length); + byte* data = stackalloc byte[size]; + + fixed (char* pValue = value) + { + System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size); + } + + NativePtr = sel_registerName(data); + } + + public static implicit operator Selector(string value) => new(value); + } + + private static unsafe IntPtr GetClass(string value) + { + int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length); + byte* data = stackalloc byte[size]; + + fixed (char* pValue = value) + { + System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size); + } + + return objc_getClass(data); + } + + private struct NsPoint + { + public double X; + public double Y; + + public NsPoint(double x, double y) + { + X = x; + Y = y; + } + } + + private struct NsRect + { + public NsPoint Pos; + public NsPoint Size; + + public NsRect(double x, double y, double width, double height) + { + Pos = new NsPoint(x, y); + Size = new NsPoint(width, height); + } + } + + public static IntPtr GetMetalLayer(Display display, Window window, out IntPtr nsView, out UpdateBoundsCallbackDelegate updateBounds) + { + nsView = gdk_quartz_window_get_nsview(window.Handle); + + // Create a new CAMetalLayer. + IntPtr layerClass = GetClass("CAMetalLayer"); + IntPtr metalLayer = IntPtr_objc_msgSend(layerClass, "alloc"); + objc_msgSend(metalLayer, "init"); + + // Create a child NSView to render into. + IntPtr nsViewClass = GetClass("NSView"); + IntPtr child = IntPtr_objc_msgSend(nsViewClass, "alloc"); + objc_msgSend(child, "init", new NsRect()); + + // Add it as a child. + objc_msgSend(nsView, "addSubview:", child); + + // Make its renderer our metal layer. + objc_msgSend(child, "setWantsLayer:", (byte)1); + objc_msgSend(child, "setLayer:", metalLayer); + objc_msgSend(metalLayer, "setContentsScale:", (double)display.GetMonitorAtWindow(window).ScaleFactor); + + // Set the frame position/location. + updateBounds = (Window window) => + { + window.GetPosition(out int x, out int y); + int width = window.Width; + int height = window.Height; + objc_msgSend(child, "setFrame:", new NsRect(x, y, width, height)); + }; + + updateBounds(window); + + return metalLayer; + } + + [LibraryImport(LibObjCImport)] + private static unsafe partial IntPtr sel_registerName(byte* data); + + [LibraryImport(LibObjCImport)] + private static unsafe partial IntPtr objc_getClass(byte* data); + + [LibraryImport(LibObjCImport)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector); + + [LibraryImport(LibObjCImport)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value); + + [LibraryImport(LibObjCImport)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value); + + [LibraryImport(LibObjCImport)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector, NsRect point); + + [LibraryImport(LibObjCImport)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector, double value); + + [LibraryImport(LibObjCImport, EntryPoint = "objc_msgSend")] + private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector); + + [LibraryImport("libgdk-3.0.dylib")] + private static partial IntPtr gdk_quartz_window_get_nsview(IntPtr gdkWindow); + } +} diff --git a/src/Ryujinx.Gtk3/UI/Helper/SortHelper.cs b/src/Ryujinx.Gtk3/UI/Helper/SortHelper.cs new file mode 100644 index 00000000..3e3fbeaa --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Helper/SortHelper.cs @@ -0,0 +1,33 @@ +using Gtk; +using Ryujinx.UI.Common.Helper; +using System; + +namespace Ryujinx.UI.Helper +{ + static class SortHelper + { + public static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b) + { + TimeSpan aTimeSpan = ValueFormatUtils.ParseTimeSpan(model.GetValue(a, 5).ToString()); + TimeSpan bTimeSpan = ValueFormatUtils.ParseTimeSpan(model.GetValue(b, 5).ToString()); + + return TimeSpan.Compare(aTimeSpan, bTimeSpan); + } + + public static int LastPlayedSort(ITreeModel model, TreeIter a, TreeIter b) + { + DateTime aDateTime = ValueFormatUtils.ParseDateTime(model.GetValue(a, 6).ToString()); + DateTime bDateTime = ValueFormatUtils.ParseDateTime(model.GetValue(b, 6).ToString()); + + return DateTime.Compare(aDateTime, bDateTime); + } + + public static int FileSizeSort(ITreeModel model, TreeIter a, TreeIter b) + { + long aSize = ValueFormatUtils.ParseFileSize(model.GetValue(a, 8).ToString()); + long bSize = ValueFormatUtils.ParseFileSize(model.GetValue(b, 8).ToString()); + + return aSize.CompareTo(bSize); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Helper/ThemeHelper.cs b/src/Ryujinx.Gtk3/UI/Helper/ThemeHelper.cs new file mode 100644 index 00000000..e1fed1c4 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Helper/ThemeHelper.cs @@ -0,0 +1,36 @@ +using Gtk; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.UI.Common.Configuration; +using System.IO; + +namespace Ryujinx.UI.Helper +{ + static class ThemeHelper + { + public static void ApplyTheme() + { + if (!ConfigurationState.Instance.UI.EnableCustomTheme) + { + return; + } + + if (File.Exists(ConfigurationState.Instance.UI.CustomThemePath) && (Path.GetExtension(ConfigurationState.Instance.UI.CustomThemePath) == ".css")) + { + CssProvider cssProvider = new(); + + cssProvider.LoadFromPath(ConfigurationState.Instance.UI.CustomThemePath); + + StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800); + } + else + { + Logger.Warning?.Print(LogClass.Application, $"The \"custom_theme_path\" section in \"{ReleaseInformation.ConfigName}\" contains an invalid path: \"{ConfigurationState.Instance.UI.CustomThemePath}\"."); + + ConfigurationState.Instance.UI.CustomThemePath.Value = ""; + ConfigurationState.Instance.UI.EnableCustomTheme.Value = false; + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/MainWindow.cs b/src/Ryujinx.Gtk3/UI/MainWindow.cs new file mode 100644 index 00000000..66c0afae --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/MainWindow.cs @@ -0,0 +1,1993 @@ +using Gtk; +using LibHac.Common; +using LibHac.Common.Keys; +using LibHac.Ncm; +using LibHac.Ns; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Audio.Backends.Dummy; +using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SoundIo; +using Ryujinx.Audio.Integration; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Multiplayer; +using Ryujinx.Common.Logging; +using Ryujinx.Common.SystemInterop; +using Ryujinx.Cpu; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.Input.GTK3; +using Ryujinx.Input.HLE; +using Ryujinx.Input.SDL2; +using Ryujinx.Modules; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Applet; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Helper; +using Ryujinx.UI.Widgets; +using Ryujinx.UI.Windows; +using Silk.NET.Vulkan; +using SPB.Graphics.Vulkan; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using GUI = Gtk.Builder.ObjectAttribute; +using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState; + +namespace Ryujinx.UI +{ + public class MainWindow : Window + { + private readonly VirtualFileSystem _virtualFileSystem; + private readonly ContentManager _contentManager; + private readonly AccountManager _accountManager; + private readonly LibHacHorizonManager _libHacHorizonManager; + + private UserChannelPersistence _userChannelPersistence; + + private HLE.Switch _emulationContext; + + private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; + + private readonly GtkHostUIHandler _uiHandler; + private readonly AutoResetEvent _deviceExitStatus; + private readonly ListStore _tableStore; + + private bool _updatingGameTable; + private bool _gameLoaded; + private bool _ending; + + private ApplicationData _currentApplicationData = null; + + private string _lastScannedAmiiboId = ""; + private bool _lastScannedAmiiboShowAll = false; + + public readonly ApplicationLibrary ApplicationLibrary; + public RendererWidgetBase RendererWidget; + public InputManager InputManager; + + public bool IsFocused; + +#pragma warning disable CS0169, CS0649, IDE0044, IDE0051 // Field is never assigned to, Add readonly modifier, Remove unused private member + + [GUI] public MenuItem ExitMenuItem; + [GUI] public MenuItem UpdateMenuItem; + [GUI] MenuBar _menuBar; + [GUI] Box _footerBox; + [GUI] Box _statusBar; + [GUI] MenuItem _optionMenu; + [GUI] MenuItem _manageUserProfiles; + [GUI] MenuItem _fileMenu; + [GUI] MenuItem _loadApplicationFile; + [GUI] MenuItem _loadApplicationFolder; + [GUI] MenuItem _appletMenu; + [GUI] MenuItem _actionMenu; + [GUI] MenuItem _pauseEmulation; + [GUI] MenuItem _resumeEmulation; + [GUI] MenuItem _stopEmulation; + [GUI] MenuItem _simulateWakeUpMessage; + [GUI] MenuItem _scanAmiibo; + [GUI] MenuItem _takeScreenshot; + [GUI] MenuItem _hideUI; + [GUI] MenuItem _fullScreen; + [GUI] CheckMenuItem _startFullScreen; + [GUI] CheckMenuItem _showConsole; + [GUI] CheckMenuItem _favToggle; + [GUI] MenuItem _firmwareInstallDirectory; + [GUI] MenuItem _firmwareInstallFile; + [GUI] MenuItem _fileTypesSubMenu; + [GUI] Label _fifoStatus; + [GUI] CheckMenuItem _iconToggle; + [GUI] CheckMenuItem _developerToggle; + [GUI] CheckMenuItem _appToggle; + [GUI] CheckMenuItem _timePlayedToggle; + [GUI] CheckMenuItem _versionToggle; + [GUI] CheckMenuItem _lastPlayedToggle; + [GUI] CheckMenuItem _fileExtToggle; + [GUI] CheckMenuItem _pathToggle; + [GUI] CheckMenuItem _fileSizeToggle; + [GUI] CheckMenuItem _nspShown; + [GUI] CheckMenuItem _pfs0Shown; + [GUI] CheckMenuItem _xciShown; + [GUI] CheckMenuItem _ncaShown; + [GUI] CheckMenuItem _nroShown; + [GUI] CheckMenuItem _nsoShown; + [GUI] Label _gpuBackend; + [GUI] Label _dockedMode; + [GUI] Label _aspectRatio; + [GUI] Label _gameStatus; + [GUI] TreeView _gameTable; + [GUI] TreeSelection _gameTableSelection; + [GUI] ScrolledWindow _gameTableWindow; + [GUI] Label _gpuName; + [GUI] Label _progressLabel; + [GUI] Label _firmwareVersionLabel; + [GUI] Gtk.ProgressBar _progressBar; + [GUI] Box _viewBox; + [GUI] Label _vSyncStatus; + [GUI] Label _volumeStatus; + [GUI] Box _listStatusBox; + [GUI] Label _loadingStatusLabel; + [GUI] Gtk.ProgressBar _loadingStatusBar; + +#pragma warning restore CS0649, IDE0044, CS0169, IDE0051 + + public MainWindow() : this(new Builder("Ryujinx.Gtk3.UI.MainWindow.glade")) { } + + private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin")) + { + builder.Autoconnect(this); + + // Apply custom theme if needed. + ThemeHelper.ApplyTheme(); + + SetWindowSizePosition(); + + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + Title = $"Ryujinx {Program.Version}"; + + // Hide emulation context status bar. + _statusBar.Hide(); + + // Instantiate HLE objects. + _virtualFileSystem = VirtualFileSystem.CreateInstance(); + _libHacHorizonManager = new LibHacHorizonManager(); + + _libHacHorizonManager.InitializeFsServer(_virtualFileSystem); + _libHacHorizonManager.InitializeArpServer(); + _libHacHorizonManager.InitializeBcatServer(); + _libHacHorizonManager.InitializeSystemClients(); + + // Save data created before we supported extra data in directory save data will not work properly if + // given empty extra data. Luckily some of that extra data can be created using the data from the + // save data indexer, which should be enough to check access permissions for user saves. + // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened. + // Consider removing this at some point in the future when we don't need to worry about old saves. + VirtualFileSystem.FixExtraData(_libHacHorizonManager.RyujinxClient); + + _contentManager = new ContentManager(_virtualFileSystem); + _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, CommandLineState.Profile); + _userChannelPersistence = new UserChannelPersistence(); + + IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks + ? IntegrityCheckLevel.ErrorOnInvalid + : IntegrityCheckLevel.None; + + // Instantiate GUI objects. + ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel) + { + DesiredLanguage = ConfigurationState.Instance.System.Language, + }; + _uiHandler = new GtkHostUIHandler(this); + _deviceExitStatus = new AutoResetEvent(false); + + WindowStateEvent += WindowStateEvent_Changed; + DeleteEvent += Window_Close; + FocusInEvent += MainWindow_FocusInEvent; + FocusOutEvent += MainWindow_FocusOutEvent; + + ApplicationLibrary.ApplicationAdded += Application_Added; + ApplicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated; + + _fileMenu.StateChanged += FileMenu_StateChanged; + _actionMenu.StateChanged += ActionMenu_StateChanged; + _optionMenu.StateChanged += OptionMenu_StateChanged; + + _gameTable.ButtonReleaseEvent += Row_Clicked; + _fullScreen.Activated += FullScreen_Toggled; + + RendererWidgetBase.StatusUpdatedEvent += Update_StatusBar; + + ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState; + ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState; + ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; + + ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerMode; + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateMultiplayerLanInterfaceId; + + if (ConfigurationState.Instance.UI.StartFullscreen) + { + _startFullScreen.Active = true; + } + + _showConsole.Active = ConfigurationState.Instance.UI.ShowConsole.Value; + _showConsole.Visible = ConsoleHelper.SetConsoleWindowStateSupported; + + _actionMenu.Sensitive = false; + _pauseEmulation.Sensitive = false; + _resumeEmulation.Sensitive = false; + + _nspShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value; + _pfs0Shown.Active = ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value; + _xciShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value; + _ncaShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value; + _nroShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value; + _nsoShown.Active = ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value; + + _nspShown.Toggled += NSP_Shown_Toggled; + _pfs0Shown.Toggled += PFS0_Shown_Toggled; + _xciShown.Toggled += XCI_Shown_Toggled; + _ncaShown.Toggled += NCA_Shown_Toggled; + _nroShown.Toggled += NRO_Shown_Toggled; + _nsoShown.Toggled += NSO_Shown_Toggled; + + _fileTypesSubMenu.Visible = FileAssociationHelper.IsTypeAssociationSupported; + + if (ConfigurationState.Instance.UI.GuiColumns.FavColumn) + { + _favToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.IconColumn) + { + _iconToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.AppColumn) + { + _appToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.DevColumn) + { + _developerToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.VersionColumn) + { + _versionToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn) + { + _timePlayedToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn) + { + _lastPlayedToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.FileExtColumn) + { + _fileExtToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn) + { + _fileSizeToggle.Active = true; + } + if (ConfigurationState.Instance.UI.GuiColumns.PathColumn) + { + _pathToggle.Active = true; + } + + _favToggle.Toggled += Fav_Toggled; + _iconToggle.Toggled += Icon_Toggled; + _appToggle.Toggled += App_Toggled; + _developerToggle.Toggled += Developer_Toggled; + _versionToggle.Toggled += Version_Toggled; + _timePlayedToggle.Toggled += TimePlayed_Toggled; + _lastPlayedToggle.Toggled += LastPlayed_Toggled; + _fileExtToggle.Toggled += FileExt_Toggled; + _fileSizeToggle.Toggled += FileSize_Toggled; + _pathToggle.Toggled += Path_Toggled; + + _gameTable.Model = _tableStore = new ListStore( + typeof(bool), + typeof(Gdk.Pixbuf), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(string), + typeof(BlitStruct)); + + _tableStore.SetSortFunc(5, SortHelper.TimePlayedSort); + _tableStore.SetSortFunc(6, SortHelper.LastPlayedSort); + _tableStore.SetSortFunc(8, SortHelper.FileSizeSort); + + int columnId = ConfigurationState.Instance.UI.ColumnSort.SortColumnId; + bool ascending = ConfigurationState.Instance.UI.ColumnSort.SortAscending; + + _tableStore.SetSortColumnId(columnId, ascending ? SortType.Ascending : SortType.Descending); + + _gameTable.EnableSearch = true; + _gameTable.SearchColumn = 2; + _gameTable.SearchEqualFunc = (model, col, key, iter) => !((string)model.GetValue(iter, col)).Contains(key, StringComparison.InvariantCultureIgnoreCase); + + _hideUI.Label = _hideUI.Label.Replace("SHOWUIKEY", ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI.ToString()); + + UpdateColumns(); + + ConfigurationState.Instance.UI.GameDirs.Event += (sender, args) => + { + if (args.OldValue != args.NewValue) + { + UpdateGameTable(); + } + }; + + Task.Run(RefreshFirmwareLabel); + + InputManager = new InputManager(new GTK3KeyboardDriver(this), new SDL2GamepadDriver()); + } + + private void UpdateMultiplayerLanInterfaceId(object sender, ReactiveEventArgs args) + { + if (_emulationContext != null) + { + _emulationContext.Configuration.MultiplayerLanInterfaceId = args.NewValue; + } + } + + private void UpdateMultiplayerMode(object sender, ReactiveEventArgs args) + { + if (_emulationContext != null) + { + _emulationContext.Configuration.MultiplayerMode = args.NewValue; + } + } + + private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs args) + { + if (_emulationContext != null) + { + _emulationContext.Configuration.IgnoreMissingServices = args.NewValue; + } + } + + private void UpdateAspectRatioState(object sender, ReactiveEventArgs args) + { + if (_emulationContext != null) + { + _emulationContext.Configuration.AspectRatio = args.NewValue; + } + } + + private void UpdateDockedModeState(object sender, ReactiveEventArgs e) + { + _emulationContext?.System.ChangeDockedModeState(e.NewValue); + } + + private void UpdateAudioVolumeState(object sender, ReactiveEventArgs e) + { + _emulationContext?.SetVolume(e.NewValue); + } + + private void WindowStateEvent_Changed(object o, WindowStateEventArgs args) + { + _fullScreen.Label = args.Event.NewWindowState.HasFlag(Gdk.WindowState.Fullscreen) ? "Exit Fullscreen" : "Enter Fullscreen"; + } + + private void MainWindow_FocusOutEvent(object o, FocusOutEventArgs args) + { + IsFocused = false; + } + + private void MainWindow_FocusInEvent(object o, FocusInEventArgs args) + { + IsFocused = true; + } + + private void UpdateColumns() + { + foreach (TreeViewColumn column in _gameTable.Columns) + { + _gameTable.RemoveColumn(column); + } + + CellRendererToggle favToggle = new(); + favToggle.Toggled += FavToggle_Toggled; + + if (ConfigurationState.Instance.UI.GuiColumns.FavColumn) + { + _gameTable.AppendColumn("Fav", favToggle, "active", 0); + } + if (ConfigurationState.Instance.UI.GuiColumns.IconColumn) + { + _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 1); + } + if (ConfigurationState.Instance.UI.GuiColumns.AppColumn) + { + _gameTable.AppendColumn("Application", new CellRendererText(), "text", 2); + } + if (ConfigurationState.Instance.UI.GuiColumns.DevColumn) + { + _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 3); + } + if (ConfigurationState.Instance.UI.GuiColumns.VersionColumn) + { + _gameTable.AppendColumn("Version", new CellRendererText(), "text", 4); + } + if (ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn) + { + _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 5); + } + if (ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn) + { + _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 6); + } + if (ConfigurationState.Instance.UI.GuiColumns.FileExtColumn) + { + _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 7); + } + if (ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn) + { + _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 8); + } + if (ConfigurationState.Instance.UI.GuiColumns.PathColumn) + { + _gameTable.AppendColumn("Path", new CellRendererText(), "text", 9); + } + + foreach (TreeViewColumn column in _gameTable.Columns) + { + switch (column.Title) + { + case "Fav": + column.SortColumnId = 0; + column.Clicked += Column_Clicked; + break; + case "Application": + column.SortColumnId = 2; + column.Clicked += Column_Clicked; + break; + case "Developer": + column.SortColumnId = 3; + column.Clicked += Column_Clicked; + break; + case "Version": + column.SortColumnId = 4; + column.Clicked += Column_Clicked; + break; + case "Time Played": + column.SortColumnId = 5; + column.Clicked += Column_Clicked; + break; + case "Last Played": + column.SortColumnId = 6; + column.Clicked += Column_Clicked; + break; + case "File Ext": + column.SortColumnId = 7; + column.Clicked += Column_Clicked; + break; + case "File Size": + column.SortColumnId = 8; + column.Clicked += Column_Clicked; + break; + case "Path": + column.SortColumnId = 9; + column.Clicked += Column_Clicked; + break; + } + } + } + + protected override void OnDestroyed() + { + InputManager.Dispose(); + } + + private void InitializeSwitchInstance() + { + _virtualFileSystem.ReloadKeySet(); + + IRenderer renderer; + + if (ConfigurationState.Instance.Graphics.GraphicsBackend == GraphicsBackend.Vulkan) + { + string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value; + renderer = new Graphics.Vulkan.VulkanRenderer(Vk.GetApi(), CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu); + } + else + { + renderer = new Graphics.OpenGL.OpenGLRenderer(); + } + + BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; + + bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); + + if (threadedGAL) + { + renderer = new ThreadedRenderer(renderer); + } + + Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {threadedGAL}"); + + IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver(); + + if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SDL2) + { + if (SDL2HardwareDeviceDriver.IsSupported) + { + deviceDriver = new SDL2HardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to OpenAL."); + + if (OpenALHardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl; + SaveConfig(); + + deviceDriver = new OpenALHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SoundIO."); + + if (SoundIoHardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo; + SaveConfig(); + + deviceDriver = new SoundIoHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out."); + } + } + } + } + else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo) + { + if (SoundIoHardwareDeviceDriver.IsSupported) + { + deviceDriver = new SoundIoHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, trying to fall back to SDL2."); + + if (SDL2HardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2; + SaveConfig(); + + deviceDriver = new SDL2HardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to OpenAL."); + + if (OpenALHardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl; + SaveConfig(); + + deviceDriver = new OpenALHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, falling back to dummy audio out."); + } + } + } + } + else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.OpenAl) + { + if (OpenALHardwareDeviceDriver.IsSupported) + { + deviceDriver = new OpenALHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SDL2."); + + if (SDL2HardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2; + SaveConfig(); + + deviceDriver = new SDL2HardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SDL2 is not supported, trying to fall back to SoundIO."); + + if (SoundIoHardwareDeviceDriver.IsSupported) + { + Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration."); + + ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo; + SaveConfig(); + + deviceDriver = new SoundIoHardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out."); + } + } + } + } + + var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value + ? HLE.MemoryConfiguration.MemoryConfiguration6GiB + : HLE.MemoryConfiguration.MemoryConfiguration4GiB; + + IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; + + HLE.HLEConfiguration configuration = new(_virtualFileSystem, + _libHacHorizonManager, + _contentManager, + _accountManager, + _userChannelPersistence, + renderer, + deviceDriver, + memoryConfiguration, + _uiHandler, + (SystemLanguage)ConfigurationState.Instance.System.Language.Value, + (RegionCode)ConfigurationState.Instance.System.Region.Value, + ConfigurationState.Instance.Graphics.EnableVsync, + ConfigurationState.Instance.System.EnableDockedMode, + ConfigurationState.Instance.System.EnablePtc, + ConfigurationState.Instance.System.EnableInternetAccess, + fsIntegrityCheckLevel, + ConfigurationState.Instance.System.FsGlobalAccessLogMode, + ConfigurationState.Instance.System.SystemTimeOffset, + ConfigurationState.Instance.System.TimeZone, + ConfigurationState.Instance.System.MemoryManagerMode, + ConfigurationState.Instance.System.IgnoreMissingServices, + ConfigurationState.Instance.Graphics.AspectRatio, + ConfigurationState.Instance.System.AudioVolume, + ConfigurationState.Instance.System.UseHypervisor, + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, + ConfigurationState.Instance.Multiplayer.Mode); + + _emulationContext = new HLE.Switch(configuration); + } + + private SurfaceKHR CreateVulkanSurface(Instance instance, Vk vk) + { + return new SurfaceKHR((ulong)((VulkanRenderer)RendererWidget).CreateWindowSurface(instance.Handle)); + } + + private void SetupProgressUIHandlers() + { + if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null) + { + _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler; + _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler; + } + + _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; + _emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler; + } + + private void ProgressHandler(T state, int current, int total) where T : Enum + { + bool visible; + string label; + + switch (state) + { + case LoadState ptcState: + visible = ptcState != LoadState.Loaded; + label = $"PTC : {current}/{total}"; + break; + case ShaderCacheLoadingState shaderCacheState: + visible = shaderCacheState != ShaderCacheLoadingState.Loaded; + label = $"Shaders : {current}/{total}"; + break; + default: + throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}"); + } + + Application.Invoke(delegate + { + _loadingStatusLabel.Text = label; + _loadingStatusBar.Fraction = total > 0 ? (double)current / total : 0; + _loadingStatusBar.Visible = visible; + _loadingStatusLabel.Visible = visible; + }); + } + + public void UpdateGameTable() + { + if (_updatingGameTable || _gameLoaded) + { + return; + } + + _updatingGameTable = true; + + _tableStore.Clear(); + + Thread applicationLibraryThread = new(() => + { + ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language; + ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs); + + _updatingGameTable = false; + }) + { + Name = "GUI.ApplicationLibraryThread", + IsBackground = true, + }; + applicationLibraryThread.Start(); + } + + [Conditional("RELEASE")] + public void PerformanceCheck() + { + if (ConfigurationState.Instance.Logger.EnableTrace.Value) + { + MessageDialog debugWarningDialog = new(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null) + { + Title = "Ryujinx - Warning", + Text = "You have trace logging enabled, which is designed to be used by developers only.", + SecondaryText = "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?", + }; + + if (debugWarningDialog.Run() == (int)ResponseType.Yes) + { + ConfigurationState.Instance.Logger.EnableTrace.Value = false; + SaveConfig(); + } + + debugWarningDialog.Dispose(); + } + + if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value)) + { + MessageDialog shadersDumpWarningDialog = new(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null) + { + Title = "Ryujinx - Warning", + Text = "You have shader dumping enabled, which is designed to be used by developers only.", + SecondaryText = "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?", + }; + + if (shadersDumpWarningDialog.Run() == (int)ResponseType.Yes) + { + ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = ""; + SaveConfig(); + } + + shadersDumpWarningDialog.Dispose(); + } + } + + private bool LoadApplication(string path, ulong applicationId, bool isFirmwareTitle) + { + SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); + + if (!SetupValidator.CanStartApplication(_contentManager, path, out UserError userError)) + { + if (SetupValidator.CanFixStartApplication(_contentManager, path, userError, out firmwareVersion)) + { + string message = $"Would you like to install the firmware embedded in this game? (Firmware {firmwareVersion.VersionString})"; + + ResponseType responseDialog = (ResponseType)GtkDialog.CreateConfirmationDialog("No Firmware Installed", message).Run(); + + if (responseDialog != ResponseType.Yes || !SetupValidator.TryFixStartApplication(_contentManager, path, userError, out _)) + { + UserErrorDialog.CreateUserErrorDialog(userError); + + return false; + } + + // Tell the user that we installed a firmware for them. + + firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); + + RefreshFirmwareLabel(); + + message = $"No installed firmware was found but Ryujinx was able to install firmware {firmwareVersion.VersionString} from the provided game.\nThe emulator will now start."; + + GtkDialog.CreateInfoDialog($"Firmware {firmwareVersion.VersionString} was installed", message); + } + else + { + UserErrorDialog.CreateUserErrorDialog(userError); + + return false; + } + } + + Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); + + if (isFirmwareTitle) + { + Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); + + return _emulationContext.LoadNca(path); + } + + if (Directory.Exists(path)) + { + string[] romFsFiles = Directory.GetFiles(path, "*.istorage"); + + if (romFsFiles.Length == 0) + { + romFsFiles = Directory.GetFiles(path, "*.romfs"); + } + + if (romFsFiles.Length > 0) + { + Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); + + return _emulationContext.LoadCart(path, romFsFiles[0]); + } + + Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); + + return _emulationContext.LoadCart(path); + } + + if (File.Exists(path)) + { + switch (System.IO.Path.GetExtension(path).ToLowerInvariant()) + { + case ".xci": + Logger.Info?.Print(LogClass.Application, "Loading as XCI."); + + return _emulationContext.LoadXci(path, applicationId); + case ".nca": + Logger.Info?.Print(LogClass.Application, "Loading as NCA."); + + return _emulationContext.LoadNca(path); + case ".nsp": + case ".pfs0": + Logger.Info?.Print(LogClass.Application, "Loading as NSP."); + + return _emulationContext.LoadNsp(path, applicationId); + default: + Logger.Info?.Print(LogClass.Application, "Loading as Homebrew."); + try + { + return _emulationContext.LoadProgram(path); + } + catch (ArgumentOutOfRangeException) + { + Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); + + return false; + } + } + } + + Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); + + return false; + } + + public void RunApplication(ApplicationData application, bool startFullscreen = false) + { + if (_gameLoaded) + { + GtkDialog.CreateInfoDialog("A game has already been loaded", "Please stop emulation or close the emulator before launching another game."); + } + else + { + PerformanceCheck(); + + Logger.RestartTime(); + + RendererWidget = CreateRendererWidget(); + + SwitchToRenderWidget(startFullscreen); + + InitializeSwitchInstance(); + + UpdateGraphicsConfig(); + + bool isFirmwareTitle = false; + + if (application.Path.StartsWith("@SystemContent")) + { + application.Path = VirtualFileSystem.SwitchPathToSystemPath(application.Path); + + isFirmwareTitle = true; + } + + if (!LoadApplication(application.Path, application.Id, isFirmwareTitle)) + { + _emulationContext.Dispose(); + SwitchToGameTable(); + + return; + } + + SetupProgressUIHandlers(); + + _currentApplicationData = application; + + _deviceExitStatus.Reset(); + + Thread windowThread = new(CreateGameWindow) + { + Name = "GUI.WindowThread", + }; + + windowThread.Start(); + + _gameLoaded = true; + _actionMenu.Sensitive = true; + UpdateMenuItem.Sensitive = false; + + _lastScannedAmiiboId = ""; + + _firmwareInstallFile.Sensitive = false; + _firmwareInstallDirectory.Sensitive = false; + + DiscordIntegrationModule.SwitchToPlayingState(_emulationContext.Processes.ActiveApplication.ProgramIdText, + _emulationContext.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString()); + + ApplicationLibrary.LoadAndSaveMetaData(_emulationContext.Processes.ActiveApplication.ProgramIdText, appMetadata => + { + appMetadata.UpdatePreGame(); + }); + } + } + + private RendererWidgetBase CreateRendererWidget() + { + if (ConfigurationState.Instance.Graphics.GraphicsBackend == GraphicsBackend.Vulkan) + { + return new VulkanRenderer(InputManager, ConfigurationState.Instance.Logger.GraphicsDebugLevel); + } + else + { + return new OpenGLRenderer(InputManager, ConfigurationState.Instance.Logger.GraphicsDebugLevel); + } + } + + private void SwitchToRenderWidget(bool startFullscreen = false) + { + _viewBox.Remove(_gameTableWindow); + RendererWidget.Expand = true; + _viewBox.Child = RendererWidget; + + RendererWidget.ShowAll(); + EditFooterForGameRenderer(); + + if (Window.State.HasFlag(Gdk.WindowState.Fullscreen)) + { + ToggleExtraWidgets(false); + } + else if (startFullscreen || ConfigurationState.Instance.UI.StartFullscreen.Value) + { + FullScreen_Toggled(null, null); + } + } + + private void SwitchToGameTable() + { + if (Window.State.HasFlag(Gdk.WindowState.Fullscreen)) + { + ToggleExtraWidgets(true); + } + + RendererWidget.Exit(); + + if (RendererWidget.Window != Window && RendererWidget.Window != null) + { + RendererWidget.Window.Dispose(); + } + + RendererWidget.Dispose(); + + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution?.Dispose(); + _windowsMultimediaTimerResolution = null; + } + + DisplaySleep.Restore(); + + _viewBox.Remove(RendererWidget); + _viewBox.Add(_gameTableWindow); + + _gameTableWindow.Expand = true; + + Window.Title = $"Ryujinx {Program.Version}"; + + _emulationContext = null; + _gameLoaded = false; + RendererWidget = null; + + DiscordIntegrationModule.SwitchToMainMenu(); + + RecreateFooterForMenu(); + + UpdateColumns(); + UpdateGameTable(); + + RefreshFirmwareLabel(); + HandleRelaunch(); + } + + private void CreateGameWindow() + { + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1); + } + + DisplaySleep.Prevent(); + + RendererWidget.Initialize(_emulationContext); + + RendererWidget.WaitEvent.WaitOne(); + + RendererWidget.Start(); + + _emulationContext.Dispose(); + _deviceExitStatus.Set(); + + // NOTE: Everything that is here will not be executed when you close the UI. + Application.Invoke(delegate + { + SwitchToGameTable(); + }); + } + + private void RecreateFooterForMenu() + { + _listStatusBox.Show(); + _statusBar.Hide(); + } + + private void EditFooterForGameRenderer() + { + _listStatusBox.Hide(); + _statusBar.Show(); + } + + public void ToggleExtraWidgets(bool show) + { + if (RendererWidget != null) + { + if (show) + { + _menuBar.ShowAll(); + _footerBox.Show(); + _statusBar.Show(); + } + else + { + _menuBar.Hide(); + _footerBox.Hide(); + } + } + } + + private void UpdateGameMetadata(string titleId) + { + if (_gameLoaded) + { + ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => + { + appMetadata.UpdatePostGame(); + }); + } + } + + public static void UpdateGraphicsConfig() + { + int resScale = ConfigurationState.Instance.Graphics.ResScale; + float resScaleCustom = ConfigurationState.Instance.Graphics.ResScaleCustom; + + Graphics.Gpu.GraphicsConfig.ResScale = (resScale == -1) ? resScaleCustom : resScale; + Graphics.Gpu.GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy; + Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; + Graphics.Gpu.GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache; + Graphics.Gpu.GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression; + Graphics.Gpu.GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE; + } + + public void UpdateInternetAccess() + { + if (_gameLoaded) + { + _emulationContext.Configuration.EnableInternetAccess = ConfigurationState.Instance.System.EnableInternetAccess.Value; + } + } + + public static void SaveConfig() + { + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + + private void End() + { + if (_ending) + { + return; + } + + _ending = true; + + if (_emulationContext != null) + { + UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText); + + if (RendererWidget != null) + { + // We tell the widget that we are exiting. + RendererWidget.Exit(); + + // Wait for the other thread to dispose the HLE context before exiting. + _deviceExitStatus.WaitOne(); + RendererWidget.Dispose(); + } + } + + Dispose(); + + Program.Exit(); + Application.Quit(); + } + + // + // Events + // + private void Application_Added(object sender, ApplicationAddedEventArgs args) + { + Application.Invoke(delegate + { + _tableStore.AppendValues( + args.AppData.Favorite, + new Gdk.Pixbuf(args.AppData.Icon, 75, 75), + $"{args.AppData.Name}\n{args.AppData.IdString.ToUpper()}", + args.AppData.Developer, + args.AppData.Version, + args.AppData.TimePlayedString, + args.AppData.LastPlayedString, + args.AppData.FileExtension, + args.AppData.FileSizeString, + args.AppData.Path, + args.AppData.ControlHolder); + }); + } + + private void ApplicationCount_Updated(object sender, ApplicationCountUpdatedEventArgs args) + { + Application.Invoke(delegate + { + _progressLabel.Text = $"{args.NumAppsLoaded}/{args.NumAppsFound} Games Loaded"; + float barValue = 0; + + if (args.NumAppsFound != 0) + { + barValue = (float)args.NumAppsLoaded / args.NumAppsFound; + } + + _progressBar.Fraction = barValue; + + // Reset the vertical scrollbar to the top when titles finish loading + if (args.NumAppsLoaded == args.NumAppsFound) + { + _gameTableWindow.Vadjustment.Value = 0; + } + }); + } + + private void Update_StatusBar(object sender, StatusUpdatedEventArgs args) + { + Application.Invoke(delegate + { + _gameStatus.Text = args.GameStatus; + _fifoStatus.Text = args.FifoStatus; + _gpuName.Text = args.GpuName; + _dockedMode.Text = args.DockedMode; + _aspectRatio.Text = args.AspectRatio; + _gpuBackend.Text = args.GpuBackend; + _volumeStatus.Text = GetVolumeLabelText(args.Volume); + + if (args.VSyncEnabled) + { + _vSyncStatus.Attributes = new Pango.AttrList(); + _vSyncStatus.Attributes.Insert(new Pango.AttrForeground(11822, 60138, 51657)); + } + else + { + _vSyncStatus.Attributes = new Pango.AttrList(); + _vSyncStatus.Attributes.Insert(new Pango.AttrForeground(ushort.MaxValue, 17733, 21588)); + } + }); + } + + private void FavToggle_Toggled(object sender, ToggledArgs args) + { + _tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path)); + + string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower(); + bool newToggleValue = !(bool)_tableStore.GetValue(treeIter, 0); + + _tableStore.SetValue(treeIter, 0, newToggleValue); + + ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => + { + appMetadata.Favorite = newToggleValue; + }); + } + + private void Column_Clicked(object sender, EventArgs args) + { + TreeViewColumn column = (TreeViewColumn)sender; + + ConfigurationState.Instance.UI.ColumnSort.SortColumnId.Value = column.SortColumnId; + ConfigurationState.Instance.UI.ColumnSort.SortAscending.Value = column.SortOrder == SortType.Ascending; + + SaveConfig(); + } + + private void Row_Activated(object sender, RowActivatedArgs args) + { + _gameTableSelection.GetSelected(out TreeIter treeIter); + + ApplicationData application = new() + { + Favorite = (bool)_tableStore.GetValue(treeIter, 0), + Name = ((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[0], + Id = ulong.Parse(((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[1], NumberStyles.HexNumber), + Developer = (string)_tableStore.GetValue(treeIter, 3), + Version = (string)_tableStore.GetValue(treeIter, 4), + TimePlayed = ValueFormatUtils.ParseTimeSpan((string)_tableStore.GetValue(treeIter, 5)), + LastPlayed = ValueFormatUtils.ParseDateTime((string)_tableStore.GetValue(treeIter, 6)), + FileExtension = (string)_tableStore.GetValue(treeIter, 7), + FileSize = ValueFormatUtils.ParseFileSize((string)_tableStore.GetValue(treeIter, 8)), + Path = (string)_tableStore.GetValue(treeIter, 9), + ControlHolder = (BlitStruct)_tableStore.GetValue(treeIter, 10), + }; + + RunApplication(application); + } + + private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args) + { + _emulationContext.EnableDeviceVsync = !_emulationContext.EnableDeviceVsync; + + Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {_emulationContext.EnableDeviceVsync}"); + } + + private void DockedMode_Clicked(object sender, ButtonReleaseEventArgs args) + { + ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value; + } + + private static string GetVolumeLabelText(float volume) + { + string icon = volume == 0 ? "🔇" : "🔊"; + + return $"{icon} {(int)(volume * 100)}%"; + } + + private void VolumeStatus_Clicked(object sender, ButtonReleaseEventArgs args) + { + if (_emulationContext != null) + { + if (_emulationContext.IsAudioMuted()) + { + _emulationContext.SetVolume(ConfigurationState.Instance.System.AudioVolume); + } + else + { + _emulationContext.SetVolume(0); + } + } + } + + private void AspectRatio_Clicked(object sender, ButtonReleaseEventArgs args) + { + AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value; + + ConfigurationState.Instance.Graphics.AspectRatio.Value = ((int)aspectRatio + 1) > Enum.GetNames().Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1; + } + + private void Row_Clicked(object sender, ButtonReleaseEventArgs args) + { + if (args.Event.Button != 3 /* Right Click */) + { + return; + } + + _gameTableSelection.GetSelected(out TreeIter treeIter); + + if (treeIter.UserData == IntPtr.Zero) + { + return; + } + + ApplicationData application = new() + { + Favorite = (bool)_tableStore.GetValue(treeIter, 0), + Name = ((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[0], + Id = ulong.Parse(((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[1], NumberStyles.HexNumber), + Developer = (string)_tableStore.GetValue(treeIter, 3), + Version = (string)_tableStore.GetValue(treeIter, 4), + TimePlayed = ValueFormatUtils.ParseTimeSpan((string)_tableStore.GetValue(treeIter, 5)), + LastPlayed = ValueFormatUtils.ParseDateTime((string)_tableStore.GetValue(treeIter, 6)), + FileExtension = (string)_tableStore.GetValue(treeIter, 7), + FileSize = ValueFormatUtils.ParseFileSize((string)_tableStore.GetValue(treeIter, 8)), + Path = (string)_tableStore.GetValue(treeIter, 9), + ControlHolder = (BlitStruct)_tableStore.GetValue(treeIter, 10), + }; + + _ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, application); + } + + private void Load_Application_File(object sender, EventArgs args) + { + using FileChooserNative fileChooser = new("Choose the file to open", this, FileChooserAction.Open, "Open", "Cancel"); + + FileFilter filter = new() + { + Name = "Switch Executables", + }; + filter.AddPattern("*.xci"); + filter.AddPattern("*.nsp"); + filter.AddPattern("*.pfs0"); + filter.AddPattern("*.nca"); + filter.AddPattern("*.nro"); + filter.AddPattern("*.nso"); + + fileChooser.AddFilter(filter); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + if (ApplicationLibrary.TryGetApplicationsFromFile(fileChooser.Filename, + out List applications)) + { + RunApplication(applications[0]); + } + else + { + GtkDialog.CreateErrorDialog("No applications found in selected file."); + } + } + } + + private void Load_Application_Folder(object sender, EventArgs args) + { + using FileChooserNative fileChooser = new("Choose the folder to open", this, FileChooserAction.SelectFolder, "Open", "Cancel"); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + ApplicationData applicationData = new() + { + Name = System.IO.Path.GetFileNameWithoutExtension(fileChooser.Filename), + Path = fileChooser.Filename, + }; + + RunApplication(applicationData); + } + } + + private void FileMenu_StateChanged(object o, StateChangedArgs args) + { + _appletMenu.Sensitive = _emulationContext == null && _contentManager.GetCurrentFirmwareVersion() != null && _contentManager.GetCurrentFirmwareVersion().Major > 3; + _loadApplicationFile.Sensitive = _emulationContext == null; + _loadApplicationFolder.Sensitive = _emulationContext == null; + } + + private void Load_Mii_Edit_Applet(object sender, EventArgs args) + { + string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); + + ApplicationData applicationData = new() + { + Name = "miiEdit", + Id = 0x0100000000001009ul, + Path = contentPath, + }; + + RunApplication(applicationData); + } + + private void Open_Ryu_Folder(object sender, EventArgs args) + { + OpenHelper.OpenFolder(AppDataManager.BaseDirPath); + } + + private void OpenLogsFolder_Pressed(object sender, EventArgs args) + { + string logPath = AppDataManager.GetOrCreateLogsDir(); + if (!string.IsNullOrEmpty(logPath)) + { + OpenHelper.OpenFolder(logPath); + } + } + + private void Exit_Pressed(object sender, EventArgs args) + { + if (!_gameLoaded || !ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog()) + { + SaveWindowSizePosition(); + End(); + } + } + + private void Window_Close(object sender, DeleteEventArgs args) + { + if (!_gameLoaded || !ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog()) + { + SaveWindowSizePosition(); + End(); + } + else + { + args.RetVal = true; + } + } + + private void SetWindowSizePosition() + { + DefaultWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth; + DefaultHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight; + + Move(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX, ConfigurationState.Instance.UI.WindowStartup.WindowPositionY); + + if (ConfigurationState.Instance.UI.WindowStartup.WindowMaximized) + { + Maximize(); + } + } + + private void SaveWindowSizePosition() + { + GetSize(out int windowWidth, out int windowHeight); + GetPosition(out int windowXPos, out int windowYPos); + + ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = IsMaximized; + ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = windowWidth; + ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = windowHeight; + ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = windowXPos; + ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = windowYPos; + + SaveConfig(); + } + + private void StopEmulation_Pressed(object sender, EventArgs args) + { + if (_emulationContext != null) + { + UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText); + } + + _pauseEmulation.Sensitive = false; + _resumeEmulation.Sensitive = false; + UpdateMenuItem.Sensitive = true; + RendererWidget?.Exit(); + } + + private void PauseEmulation_Pressed(object sender, EventArgs args) + { + _pauseEmulation.Sensitive = false; + _resumeEmulation.Sensitive = true; + _emulationContext.System.TogglePauseEmulation(true); + Title = TitleHelper.ActiveApplicationTitle(_emulationContext.Processes.ActiveApplication, Program.Version, "Paused"); + Logger.Info?.Print(LogClass.Emulation, "Emulation was paused"); + } + + private void ResumeEmulation_Pressed(object sender, EventArgs args) + { + _pauseEmulation.Sensitive = true; + _resumeEmulation.Sensitive = false; + _emulationContext.System.TogglePauseEmulation(false); + Title = TitleHelper.ActiveApplicationTitle(_emulationContext.Processes.ActiveApplication, Program.Version); + Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed"); + } + + public void ActivatePauseMenu() + { + _pauseEmulation.Sensitive = true; + _resumeEmulation.Sensitive = false; + } + + public void TogglePause() + { + _pauseEmulation.Sensitive ^= true; + _resumeEmulation.Sensitive ^= true; + _emulationContext.System.TogglePauseEmulation(_resumeEmulation.Sensitive); + } + + private void Installer_File_Pressed(object o, EventArgs args) + { + FileChooserNative fileChooser = new("Choose the firmware file to open", this, FileChooserAction.Open, "Open", "Cancel"); + + FileFilter filter = new() + { + Name = "Switch Firmware Files", + }; + filter.AddPattern("*.zip"); + filter.AddPattern("*.xci"); + + fileChooser.AddFilter(filter); + + HandleInstallerDialog(fileChooser); + } + + private void Installer_Directory_Pressed(object o, EventArgs args) + { + FileChooserNative directoryChooser = new("Choose the firmware directory to open", this, FileChooserAction.SelectFolder, "Open", "Cancel"); + + HandleInstallerDialog(directoryChooser); + } + + private void HandleInstallerDialog(FileChooserNative fileChooser) + { + if (fileChooser.Run() == (int)ResponseType.Accept) + { + try + { + string filename = fileChooser.Filename; + + fileChooser.Dispose(); + + SystemVersion firmwareVersion = _contentManager.VerifyFirmwarePackage(filename); + + if (firmwareVersion is null) + { + GtkDialog.CreateErrorDialog($"A valid system firmware was not found in {filename}."); + + return; + } + + string dialogTitle = $"Install Firmware {firmwareVersion.VersionString}"; + + SystemVersion currentVersion = _contentManager.GetCurrentFirmwareVersion(); + + string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed."; + + if (currentVersion != null) + { + dialogMessage += $"\n\nThis will replace the current system version {currentVersion.VersionString}. "; + } + + dialogMessage += "\n\nDo you want to continue?"; + + ResponseType responseInstallDialog = (ResponseType)GtkDialog.CreateConfirmationDialog(dialogTitle, dialogMessage).Run(); + + MessageDialog waitingDialog = GtkDialog.CreateWaitingDialog(dialogTitle, "Installing firmware..."); + + if (responseInstallDialog == ResponseType.Yes) + { + Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}"); + + Thread thread = new(() => + { + Application.Invoke(delegate + { + waitingDialog.Run(); + + }); + + try + { + _contentManager.InstallFirmware(filename); + + Application.Invoke(delegate + { + waitingDialog.Dispose(); + + string message = $"System version {firmwareVersion.VersionString} successfully installed."; + + GtkDialog.CreateInfoDialog(dialogTitle, message); + Logger.Info?.Print(LogClass.Application, message); + + // Purge Applet Cache. + + DirectoryInfo miiEditorCacheFolder = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache")); + + if (miiEditorCacheFolder.Exists) + { + miiEditorCacheFolder.Delete(true); + } + }); + } + catch (Exception ex) + { + Application.Invoke(delegate + { + waitingDialog.Dispose(); + + GtkDialog.CreateErrorDialog(ex.Message); + }); + } + finally + { + RefreshFirmwareLabel(); + } + }) + { + Name = "GUI.FirmwareInstallerThread", + }; + thread.Start(); + } + } + catch (MissingKeyException ex) + { + Logger.Error?.Print(LogClass.Application, ex.ToString()); + UserErrorDialog.CreateUserErrorDialog(UserError.FirmwareParsingFailed); + } + catch (Exception ex) + { + GtkDialog.CreateErrorDialog(ex.Message); + } + } + else + { + fileChooser.Dispose(); + } + } + + private void RefreshFirmwareLabel() + { + SystemVersion currentFirmware = _contentManager.GetCurrentFirmwareVersion(); + + Application.Invoke(delegate + { + _firmwareVersionLabel.Text = currentFirmware != null ? currentFirmware.VersionString : "0.0.0"; + }); + } + + private void InstallFileTypes_Pressed(object sender, EventArgs e) + { + if (FileAssociationHelper.Install()) + { + GtkDialog.CreateInfoDialog("Install file types", "File types successfully installed!"); + } + else + { + GtkDialog.CreateErrorDialog("Failed to install file types."); + } + } + + private void UninstallFileTypes_Pressed(object sender, EventArgs e) + { + if (FileAssociationHelper.Uninstall()) + { + GtkDialog.CreateInfoDialog("Uninstall file types", "File types successfully uninstalled!"); + } + else + { + GtkDialog.CreateErrorDialog("Failed to uninstall file types."); + } + } + + private void HandleRelaunch() + { + if (_userChannelPersistence.PreviousIndex != -1 && _userChannelPersistence.ShouldRestart) + { + _userChannelPersistence.ShouldRestart = false; + + RunApplication(_currentApplicationData); + } + else + { + // otherwise, clear state. + _userChannelPersistence = new UserChannelPersistence(); + _currentApplicationData = null; + _actionMenu.Sensitive = false; + _firmwareInstallFile.Sensitive = true; + _firmwareInstallDirectory.Sensitive = true; + } + } + + private void FullScreen_Toggled(object sender, EventArgs args) + { + if (!Window.State.HasFlag(Gdk.WindowState.Fullscreen)) + { + Fullscreen(); + + ToggleExtraWidgets(false); + } + else + { + Unfullscreen(); + + ToggleExtraWidgets(true); + } + } + + private void StartFullScreen_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.StartFullscreen.Value = _startFullScreen.Active; + + SaveConfig(); + } + + private void ShowConsole_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.ShowConsole.Value = _showConsole.Active; + + SaveConfig(); + } + + private void OptionMenu_StateChanged(object o, StateChangedArgs args) + { + _manageUserProfiles.Sensitive = _emulationContext == null; + } + + private void Settings_Pressed(object sender, EventArgs args) + { + SettingsWindow settingsWindow = new(this, _virtualFileSystem, _contentManager); + + settingsWindow.SetSizeRequest((int)(settingsWindow.DefaultWidth * Program.WindowScaleFactor), (int)(settingsWindow.DefaultHeight * Program.WindowScaleFactor)); + settingsWindow.Show(); + } + + private void HideUI_Pressed(object sender, EventArgs args) + { + ToggleExtraWidgets(false); + } + + private void ManageCheats_Pressed(object sender, EventArgs args) + { + var window = new CheatWindow( + _virtualFileSystem, + _emulationContext.Processes.ActiveApplication.ProgramId, + _emulationContext.Processes.ActiveApplication.ApplicationControlProperties + .Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString(), + _currentApplicationData.Path); + + window.Destroyed += CheatWindow_Destroyed; + window.Show(); + } + + private void CheatWindow_Destroyed(object sender, EventArgs e) + { + _emulationContext.EnableCheats(); + (sender as CheatWindow).Destroyed -= CheatWindow_Destroyed; + } + + private void ManageUserProfiles_Pressed(object sender, EventArgs args) + { + UserProfilesManagerWindow userProfilesManagerWindow = new(_accountManager, _contentManager, _virtualFileSystem); + + userProfilesManagerWindow.SetSizeRequest((int)(userProfilesManagerWindow.DefaultWidth * Program.WindowScaleFactor), (int)(userProfilesManagerWindow.DefaultHeight * Program.WindowScaleFactor)); + userProfilesManagerWindow.Show(); + } + + private void Simulate_WakeUp_Message_Pressed(object sender, EventArgs args) + { + _emulationContext?.System.SimulateWakeUpMessage(); + } + + private void ActionMenu_StateChanged(object o, StateChangedArgs args) + { + _scanAmiibo.Sensitive = _emulationContext != null && _emulationContext.System.SearchingForAmiibo(out int _); + _takeScreenshot.Sensitive = _emulationContext != null; + } + + private void Scan_Amiibo(object sender, EventArgs args) + { + if (_emulationContext.System.SearchingForAmiibo(out int deviceId)) + { + AmiiboWindow amiiboWindow = new() + { + LastScannedAmiiboShowAll = _lastScannedAmiiboShowAll, + LastScannedAmiiboId = _lastScannedAmiiboId, + DeviceId = deviceId, + TitleId = _emulationContext.Processes.ActiveApplication.ProgramIdText.ToUpper(), + }; + + amiiboWindow.DeleteEvent += AmiiboWindow_DeleteEvent; + + amiiboWindow.Show(); + } + else + { + GtkDialog.CreateInfoDialog($"Amiibo", "The game is currently not ready to receive Amiibo scan data. Ensure that you have an Amiibo-compatible game open and ready to receive Amiibo scan data."); + } + } + + private void Take_Screenshot(object sender, EventArgs args) + { + if (_emulationContext != null && RendererWidget != null) + { + RendererWidget.ScreenshotRequested = true; + } + } + + private void AmiiboWindow_DeleteEvent(object sender, DeleteEventArgs args) + { + if (((AmiiboWindow)sender).AmiiboId != "" && ((AmiiboWindow)sender).Response == ResponseType.Ok) + { + _lastScannedAmiiboId = ((AmiiboWindow)sender).AmiiboId; + _lastScannedAmiiboShowAll = ((AmiiboWindow)sender).LastScannedAmiiboShowAll; + + _emulationContext.System.ScanAmiibo(((AmiiboWindow)sender).DeviceId, ((AmiiboWindow)sender).AmiiboId, ((AmiiboWindow)sender).UseRandomUuid); + } + } + + private void Update_Pressed(object sender, EventArgs args) + { + if (Updater.CanUpdate(true)) + { + Updater.BeginParse(this, true).ContinueWith(task => + { + Logger.Error?.Print(LogClass.Application, $"Updater error: {task.Exception}"); + }, TaskContinuationOptions.OnlyOnFaulted); + } + } + + private void About_Pressed(object sender, EventArgs args) + { + AboutWindow aboutWindow = new(); + + aboutWindow.SetSizeRequest((int)(aboutWindow.DefaultWidth * Program.WindowScaleFactor), (int)(aboutWindow.DefaultHeight * Program.WindowScaleFactor)); + aboutWindow.Show(); + } + + private void Fav_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.FavColumn.Value = _favToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void Icon_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.IconColumn.Value = _iconToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void App_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.AppColumn.Value = _appToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void Developer_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.DevColumn.Value = _developerToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void Version_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.VersionColumn.Value = _versionToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void TimePlayed_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.TimePlayedColumn.Value = _timePlayedToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void LastPlayed_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.LastPlayedColumn.Value = _lastPlayedToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void FileExt_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.FileExtColumn.Value = _fileExtToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void FileSize_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.FileSizeColumn.Value = _fileSizeToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void Path_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.GuiColumns.PathColumn.Value = _pathToggle.Active; + + SaveConfig(); + UpdateColumns(); + } + + private void NSP_Shown_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value = _nspShown.Active; + + SaveConfig(); + UpdateGameTable(); + } + + private void PFS0_Shown_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value = _pfs0Shown.Active; + + SaveConfig(); + UpdateGameTable(); + } + + private void XCI_Shown_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value = _xciShown.Active; + + SaveConfig(); + UpdateGameTable(); + } + + private void NCA_Shown_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value = _ncaShown.Active; + + SaveConfig(); + UpdateGameTable(); + } + + private void NRO_Shown_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value = _nroShown.Active; + + SaveConfig(); + UpdateGameTable(); + } + + private void NSO_Shown_Toggled(object sender, EventArgs args) + { + ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value = _nsoShown.Active; + + SaveConfig(); + UpdateGameTable(); + } + + private void RefreshList_Pressed(object sender, ButtonReleaseEventArgs args) + { + UpdateGameTable(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/MainWindow.glade b/src/Ryujinx.Gtk3/UI/MainWindow.glade new file mode 100644 index 00000000..d1b6872a --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/MainWindow.glade @@ -0,0 +1,1006 @@ + + + + + + False + Ryujinx + center + + + True + False + vertical + + + True + False + + + True + False + File + True + + + True + False + + + True + False + Open a file explorer to choose a Switch compatible file to load + Load Application from File + True + + + + + + True + False + Open a file explorer to choose a Switch compatible, unpacked application to load + Load Unpacked Game + True + + + + + + True + False + Load Applet + True + + + True + False + + + True + False + Open Mii Editor Applet in Standalone mode + Mii Editor + True + + + + + + + + + + True + False + + + + + True + False + Open Ryujinx filesystem folder + Open Ryujinx Folder + True + + + + + + True + False + Opens the folder where logs are written to. + Open Logs Folder + True + + + + + + True + False + + + + + True + False + Exit Ryujinx + Exit + True + + + + + + + + + + True + False + Options + True + + + True + False + + + True + False + Enter Fullscreen + True + + + + + True + False + Start Games in Fullscreen Mode + True + + + + + + True + False + Show Log Console + True + + + + + + True + False + + + + + True + False + Select which GUI columns to enable + Enable GUI Columns + True + + + True + False + + + True + False + Enable or Disable Favorite Games Column in the game list + Enable Favorite Games Column + True + + + + + True + False + Enable or Disable Icon Column in the game list + Enable Icon Column + True + + + + + True + False + Enable or Disable Title Name/ID Column in the game list + Enable Title Name/ID Column + True + + + + + True + False + Enable or Disable Developer Column in the game list + Enable Developer Column + True + + + + + True + False + Enable or Disable Version Column in the game list + Enable Version Column + True + + + + + True + False + Enable or Disable Time Played Column in the game list + Enable Time Played Column + True + + + + + True + False + Enable or Disable Last Played Column in the game list + Enable Last Played Column + True + + + + + True + False + Enable or Disable file extension column in the game list + Enable File Ext Column + True + + + + + True + False + Enable or Disable File Size Column in the game list + Enable File Size Column + True + + + + + True + False + Enable or Disable Path Column in the game list + Enable Path Column + True + + + + + + + + + True + False + Select which file types to show + Show File Types + True + + + True + False + + + True + False + Shows .NSP files in the games list + .NSP + True + + + + + True + False + Shows .PFS0 files in the games list + .PFS0 + True + + + + + True + False + Shows .XCI files in the games list + .XCI + True + + + + + True + False + Shows .NCA files in the games list + .NCA + True + + + + + True + False + Shows .NRO files in the games list + .NRO + True + + + + + True + False + Shows .NSO files in the games list + .NSO + True + + + + + + + + + True + False + + + + + True + False + Open settings window + Settings + True + + + + + + True + False + Open User Profiles Manager window + Manage User Profiles + True + + + + + + + + + + True + False + Actions + True + + + True + False + + + True + False + Pause emulation + Pause Emulation + True + + + + + + True + False + Resume emulation + Resume Emulation + True + + + + + + True + False + Stop emulation of the current game and return to game selection + Stop Emulation + True + + + + + + True + False + + + + + True + False + Simulate a Wake-up Message + Simulate Wake-up Message + True + + + + + + True + False + Scan an Amiibo + Scan an Amiibo + True + + + + + + True + False + Take a screenshot + Take Screenshot + + + + + + True + False + Hide UI (SHOWUIKEY to show) + True + + + + + + True + False + Manage Cheats + + + + + + + + + + True + False + Tools + True + + + True + False + + + True + False + Install Firmware + True + + + True + False + + + True + False + Install a firmware from XCI or ZIP + True + + + + + + True + False + Install a firmware from a directory + True + + + + + + + + + + True + False + Manage file types + True + + + True + False + + + True + False + Install file types + + + + + + True + False + Uninstall file types + + + + + + + + + + + + + + True + False + Help + True + + + True + False + + + True + False + Check for updates to Ryujinx + Check for Updates + True + + + + + + True + False + + + + + True + False + Open about window + About + True + + + + + + + + + + False + True + 0 + + + + + True + False + vertical + + + True + False + vertical + + + True + True + in + + + True + True + True + True + + + + + + + + + True + True + 0 + + + + + True + True + 0 + + + + + 19 + True + False + + + True + False + + + True + False + 5 + + + + RefreshList + True + False + gtk-refresh + + + + + False + False + 0 + + + + + True + False + 10 + 5 + 2 + 2 + 0/0 Games Loaded + + + False + True + 1 + + + + + 200 + True + False + start + 10 + 5 + 6 + + + True + True + 2 + + + + + True + True + 0 + + + + + True + False + + + True + False + + + + True + False + start + 5 + 5 + VSync + + + + + False + True + 0 + + + + + True + False + + + False + True + 1 + + + + + True + False + + + + True + False + start + 5 + 5 + + + + + False + True + 2 + + + + + True + False + + + False + True + 3 + + + + + True + False + + + + True + False + start + 5 + 5 + + + + + False + True + 4 + + + + + True + False + + + False + True + 5 + + + + + True + False + + + + True + False + start + 5 + 5 + + + + + False + True + 6 + + + + + True + False + + + False + True + 7 + + + + + True + False + start + 5 + 5 + + + False + True + 8 + + + + + True + False + + + False + True + 9 + + + + + True + False + start + 5 + 5 + + + False + True + 10 + + + + + True + False + + + False + True + 11 + + + + + True + False + start + 5 + 5 + + + False + True + 12 + + + + + True + False + + + False + True + 13 + + + + + True + False + start + 5 + 5 + + + True + True + 14 + + + + + True + True + 1 + + + + + True + False + 5 + + + True + False + System Version + + + False + True + 0 + + + + + 50 + True + False + 5 + 5 + + + False + True + end + 1 + + + + + False + True + end + 4 + + + + + False + 5 + 5 + 0/0 + + + False + True + 11 + + + + + 200 + False + 5 + 5 + 6 + + + False + True + 12 + + + + + False + True + 1 + + + + + True + True + 1 + + + + + + diff --git a/src/Ryujinx.Gtk3/UI/OpenGLRenderer.cs b/src/Ryujinx.Gtk3/UI/OpenGLRenderer.cs new file mode 100644 index 00000000..1fdabc75 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/OpenGLRenderer.cs @@ -0,0 +1,142 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Input.HLE; +using SPB.Graphics; +using SPB.Graphics.Exceptions; +using SPB.Graphics.OpenGL; +using SPB.Platform; +using SPB.Platform.GLX; +using SPB.Platform.WGL; +using SPB.Windowing; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.UI +{ + public partial class OpenGLRenderer : RendererWidgetBase + { + private readonly GraphicsDebugLevel _glLogLevel; + + private bool _initializedOpenGL; + + private OpenGLContextBase _openGLContext; + private SwappableNativeWindowBase _nativeWindow; + + public OpenGLRenderer(InputManager inputManager, GraphicsDebugLevel glLogLevel) : base(inputManager, glLogLevel) + { + _glLogLevel = glLogLevel; + } + + protected override bool OnDrawn(Cairo.Context cr) + { + if (!_initializedOpenGL) + { + IntializeOpenGL(); + } + + return true; + } + + private void IntializeOpenGL() + { + _nativeWindow = RetrieveNativeWindow(); + + Window.EnsureNative(); + + _openGLContext = PlatformHelper.CreateOpenGLContext(GetGraphicsMode(), 3, 3, _glLogLevel == GraphicsDebugLevel.None ? OpenGLContextFlags.Compat : OpenGLContextFlags.Compat | OpenGLContextFlags.Debug); + _openGLContext.Initialize(_nativeWindow); + _openGLContext.MakeCurrent(_nativeWindow); + + // Release the GL exclusivity that SPB gave us as we aren't going to use it in GTK Thread. + _openGLContext.MakeCurrent(null); + + WaitEvent.Set(); + + _initializedOpenGL = true; + } + + private SwappableNativeWindowBase RetrieveNativeWindow() + { + if (OperatingSystem.IsWindows()) + { + IntPtr windowHandle = gdk_win32_window_get_handle(Window.Handle); + + return new WGLWindow(new NativeHandle(windowHandle)); + } + else if (OperatingSystem.IsLinux()) + { + IntPtr displayHandle = gdk_x11_display_get_xdisplay(Display.Handle); + IntPtr windowHandle = gdk_x11_window_get_xid(Window.Handle); + + return new GLXWindow(new NativeHandle(displayHandle), new NativeHandle(windowHandle)); + } + + throw new NotImplementedException(); + } + + [LibraryImport("libgdk-3-0.dll")] + private static partial IntPtr gdk_win32_window_get_handle(IntPtr d); + + [LibraryImport("libgdk-3.so.0")] + private static partial IntPtr gdk_x11_display_get_xdisplay(IntPtr gdkDisplay); + + [LibraryImport("libgdk-3.so.0")] + private static partial IntPtr gdk_x11_window_get_xid(IntPtr gdkWindow); + + private static FramebufferFormat GetGraphicsMode() + { + return Environment.OSVersion.Platform == PlatformID.Unix ? new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false) : FramebufferFormat.Default; + } + + public override void InitializeRenderer() + { + // First take exclusivity on the OpenGL context. + ((Graphics.OpenGL.OpenGLRenderer)Renderer).InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(_openGLContext)); + + _openGLContext.MakeCurrent(_nativeWindow); + + GL.ClearColor(0, 0, 0, 1.0f); + GL.Clear(ClearBufferMask.ColorBufferBit); + SwapBuffers(); + } + + public override void SwapBuffers() + { + _nativeWindow.SwapBuffers(); + } + + protected override string GetGpuBackendName() + { + return "OpenGL"; + } + + protected override void Dispose(bool disposing) + { + // Try to bind the OpenGL context before calling the shutdown event. + try + { + _openGLContext?.MakeCurrent(_nativeWindow); + } + catch (ContextException e) + { + Logger.Warning?.Print(LogClass.UI, $"Failed to bind OpenGL context: {e}"); + } + + Device?.DisposeGpu(); + NpadManager.Dispose(); + + // Unbind context and destroy everything. + try + { + _openGLContext?.MakeCurrent(null); + } + catch (ContextException e) + { + Logger.Warning?.Print(LogClass.UI, $"Failed to unbind OpenGL context: {e}"); + } + + _openGLContext?.Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/OpenToolkitBindingsContext.cs b/src/Ryujinx.Gtk3/UI/OpenToolkitBindingsContext.cs new file mode 100644 index 00000000..1224ccfe --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/OpenToolkitBindingsContext.cs @@ -0,0 +1,20 @@ +using SPB.Graphics; +using System; + +namespace Ryujinx.UI +{ + public class OpenToolkitBindingsContext : OpenTK.IBindingsContext + { + private readonly IBindingsContext _bindingContext; + + public OpenToolkitBindingsContext(IBindingsContext bindingsContext) + { + _bindingContext = bindingsContext; + } + + public IntPtr GetProcAddress(string procName) + { + return _bindingContext.GetProcAddress(procName); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs b/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs new file mode 100644 index 00000000..12139e87 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/RendererWidgetBase.cs @@ -0,0 +1,813 @@ +using Gdk; +using Gtk; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Input; +using Ryujinx.Input.GTK3; +using Ryujinx.Input.HLE; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Widgets; +using SkiaSharp; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Key = Ryujinx.Input.Key; +using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter; +using Switch = Ryujinx.HLE.Switch; + +namespace Ryujinx.UI +{ + public abstract class RendererWidgetBase : DrawingArea + { + private const int SwitchPanelWidth = 1280; + private const int SwitchPanelHeight = 720; + private const int TargetFps = 60; + private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping. + private const float VolumeDelta = 0.05f; + + public ManualResetEvent WaitEvent { get; set; } + public NpadManager NpadManager { get; } + public TouchScreenManager TouchScreenManager { get; } + public Switch Device { get; private set; } + public IRenderer Renderer { get; private set; } + + public bool ScreenshotRequested { get; set; } + protected int WindowWidth { get; private set; } + protected int WindowHeight { get; private set; } + + public static event EventHandler StatusUpdatedEvent; + + private bool _isActive; + private bool _isStopped; + + private bool _toggleFullscreen; + private bool _toggleDockedMode; + + private readonly long _ticksPerFrame; + + private long _ticks = 0; + private float _newVolume; + + private readonly Stopwatch _chrono; + + private KeyboardHotkeyState _prevHotkeyState; + + private readonly ManualResetEvent _exitEvent; + private readonly ManualResetEvent _gpuDoneEvent; + + private readonly CancellationTokenSource _gpuCancellationTokenSource; + + // Hide Cursor + const int CursorHideIdleTime = 5; // seconds + private static readonly Cursor _invisibleCursor = new(Display.Default, CursorType.BlankCursor); + private long _lastCursorMoveTime; + private HideCursorMode _hideCursorMode; + private readonly InputManager _inputManager; + private readonly IKeyboard _keyboardInterface; + private readonly GraphicsDebugLevel _glLogLevel; + private string _gpuBackendName; + private string _gpuDriverName; + private bool _isMouseInClient; + + public RendererWidgetBase(InputManager inputManager, GraphicsDebugLevel glLogLevel) + { + var mouseDriver = new GTK3MouseDriver(this); + + _inputManager = inputManager; + _inputManager.SetMouseDriver(mouseDriver); + NpadManager = _inputManager.CreateNpadManager(); + TouchScreenManager = _inputManager.CreateTouchScreenManager(); + _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); + + WaitEvent = new ManualResetEvent(false); + + _glLogLevel = glLogLevel; + + Destroyed += Renderer_Destroyed; + + _chrono = new Stopwatch(); + + _ticksPerFrame = Stopwatch.Frequency / TargetFps; + + AddEvents((int)(EventMask.ButtonPressMask + | EventMask.ButtonReleaseMask + | EventMask.PointerMotionMask + | EventMask.ScrollMask + | EventMask.EnterNotifyMask + | EventMask.LeaveNotifyMask + | EventMask.KeyPressMask + | EventMask.KeyReleaseMask)); + + _exitEvent = new ManualResetEvent(false); + _gpuDoneEvent = new ManualResetEvent(false); + + _gpuCancellationTokenSource = new CancellationTokenSource(); + + _hideCursorMode = ConfigurationState.Instance.HideCursor; + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + + ConfigurationState.Instance.HideCursor.Event += HideCursorStateChanged; + ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAnriAliasing; + ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter; + ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel; + } + + private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs e) + { + Renderer.Window.SetScalingFilter((ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + Renderer.Window.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + } + + private void UpdateScalingFilter(object sender, ReactiveEventArgs e) + { + Renderer.Window.SetScalingFilter((ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + Renderer.Window.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + } + + public abstract void InitializeRenderer(); + + public abstract void SwapBuffers(); + + protected abstract string GetGpuBackendName(); + + private string GetGpuDriverName() + { + return Renderer.GetHardwareInfo().GpuDriver; + } + + private void HideCursorStateChanged(object sender, ReactiveEventArgs state) + { + Application.Invoke(delegate + { + _hideCursorMode = state.NewValue; + + switch (_hideCursorMode) + { + case HideCursorMode.Never: + Window.Cursor = null; + break; + case HideCursorMode.OnIdle: + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + break; + case HideCursorMode.Always: + Window.Cursor = _invisibleCursor; + break; + default: + throw new ArgumentOutOfRangeException(nameof(state)); + } + }); + } + + private void Renderer_Destroyed(object sender, EventArgs e) + { + ConfigurationState.Instance.HideCursor.Event -= HideCursorStateChanged; + ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAnriAliasing; + ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter; + ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel; + + NpadManager.Dispose(); + Dispose(); + } + + private void UpdateAnriAliasing(object sender, ReactiveEventArgs e) + { + Renderer?.Window.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue); + } + + protected override bool OnMotionNotifyEvent(EventMotion evnt) + { + if (_hideCursorMode == HideCursorMode.OnIdle) + { + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + } + + if (ConfigurationState.Instance.Hid.EnableMouse) + { + Window.Cursor = _invisibleCursor; + } + + _isMouseInClient = true; + + return false; + } + + protected override bool OnEnterNotifyEvent(EventCrossing evnt) + { + Window.Cursor = ConfigurationState.Instance.Hid.EnableMouse ? _invisibleCursor : null; + + _isMouseInClient = true; + + return base.OnEnterNotifyEvent(evnt); + } + + protected override bool OnLeaveNotifyEvent(EventCrossing evnt) + { + Window.Cursor = null; + + _isMouseInClient = false; + + return base.OnLeaveNotifyEvent(evnt); + } + + protected override void OnGetPreferredHeight(out int minimumHeight, out int naturalHeight) + { + Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window); + + // If the monitor is at least 1080p, use the Switch panel size as minimal size. + if (monitor.Geometry.Height >= 1080) + { + minimumHeight = SwitchPanelHeight; + } + // Otherwise, we default minimal size to 480p 16:9. + else + { + minimumHeight = 480; + } + + naturalHeight = minimumHeight; + } + + protected override void OnGetPreferredWidth(out int minimumWidth, out int naturalWidth) + { + Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window); + + // If the monitor is at least 1080p, use the Switch panel size as minimal size. + if (monitor.Geometry.Height >= 1080) + { + minimumWidth = SwitchPanelWidth; + } + // Otherwise, we default minimal size to 480p 16:9. + else + { + minimumWidth = 854; + } + + naturalWidth = minimumWidth; + } + + protected override bool OnConfigureEvent(EventConfigure evnt) + { + bool result = base.OnConfigureEvent(evnt); + + Gdk.Monitor monitor = Display.GetMonitorAtWindow(Window); + + WindowWidth = evnt.Width * monitor.ScaleFactor; + WindowHeight = evnt.Height * monitor.ScaleFactor; + + Renderer?.Window?.SetSize(WindowWidth, WindowHeight); + + return result; + } + + private void HandleScreenState(KeyboardStateSnapshot keyboard) + { + bool toggleFullscreen = keyboard.IsPressed(Key.F11) + || ((keyboard.IsPressed(Key.AltLeft) + || keyboard.IsPressed(Key.AltRight)) + && keyboard.IsPressed(Key.Enter)) + || keyboard.IsPressed(Key.Escape); + + bool fullScreenToggled = ParentWindow.State.HasFlag(WindowState.Fullscreen); + + if (toggleFullscreen != _toggleFullscreen) + { + if (toggleFullscreen) + { + if (fullScreenToggled) + { + ParentWindow.Unfullscreen(); + (Toplevel as MainWindow)?.ToggleExtraWidgets(true); + } + else + { + if (keyboard.IsPressed(Key.Escape)) + { + if (!ConfigurationState.Instance.ShowConfirmExit || GtkDialog.CreateExitDialog()) + { + Exit(); + } + } + else + { + ParentWindow.Fullscreen(); + (Toplevel as MainWindow)?.ToggleExtraWidgets(false); + } + } + } + } + + _toggleFullscreen = toggleFullscreen; + + bool toggleDockedMode = keyboard.IsPressed(Key.F9); + + if (toggleDockedMode != _toggleDockedMode) + { + if (toggleDockedMode) + { + ConfigurationState.Instance.System.EnableDockedMode.Value = + !ConfigurationState.Instance.System.EnableDockedMode.Value; + } + } + + _toggleDockedMode = toggleDockedMode; + + if (_isMouseInClient) + { + if (ConfigurationState.Instance.Hid.EnableMouse.Value) + { + Window.Cursor = _invisibleCursor; + } + else + { + switch (_hideCursorMode) + { + case HideCursorMode.OnIdle: + long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime; + Window.Cursor = (cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency) ? _invisibleCursor : null; + break; + case HideCursorMode.Always: + Window.Cursor = _invisibleCursor; + break; + case HideCursorMode.Never: + Window.Cursor = null; + break; + } + } + } + } + + public void Initialize(Switch device) + { + Device = device; + + IRenderer renderer = Device.Gpu.Renderer; + + if (renderer is ThreadedRenderer tr) + { + renderer = tr.BaseRenderer; + } + + Renderer = renderer; + Renderer?.Window?.SetSize(WindowWidth, WindowHeight); + + if (Renderer != null) + { + Renderer.ScreenCaptured += Renderer_ScreenCaptured; + } + + NpadManager.Initialize(device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + TouchScreenManager.Initialize(device); + } + + private unsafe void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e) + { + if (e.Data.Length > 0 && e.Height > 0 && e.Width > 0) + { + Task.Run(() => + { + lock (this) + { + string applicationName = Device.Processes.ActiveApplication.Name; + string sanitizedApplicationName = FileSystemUtils.SanitizeFileName(applicationName); + DateTime currentTime = DateTime.Now; + + string filename = $"{sanitizedApplicationName}_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; + + string directory = AppDataManager.Mode switch + { + AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => System.IO.Path.Combine(AppDataManager.BaseDirPath, "screenshots"), + _ => System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx"), + }; + + string path = System.IO.Path.Combine(directory, filename); + + try + { + Directory.CreateDirectory(directory); + } + catch (Exception ex) + { + Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {directory}. Error : {ex.GetType().Name}", "Screenshot"); + + return; + } + + var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888; + using var image = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul)); + + Marshal.Copy(e.Data, 0, image.GetPixels(), e.Data.Length); + using var surface = SKSurface.Create(image.Info); + var canvas = surface.Canvas; + + if (e.FlipX || e.FlipY) + { + canvas.Clear(SKColors.Transparent); + + float scaleX = e.FlipX ? -1 : 1; + float scaleY = e.FlipY ? -1 : 1; + + var matrix = SKMatrix.CreateScale(scaleX, scaleY, image.Width / 2f, image.Height / 2f); + + canvas.SetMatrix(matrix); + } + canvas.DrawBitmap(image, new SKPoint()); + + surface.Flush(); + using var snapshot = surface.Snapshot(); + using var encoded = snapshot.Encode(SKEncodedImageFormat.Png, 80); + using var file = File.OpenWrite(path); + encoded.SaveTo(file); + + image.Dispose(); + + Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot"); + } + }); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Screenshot is empty. Size : {e.Data.Length} bytes. Resolution : {e.Width}x{e.Height}", "Screenshot"); + } + } + + public void Render() + { + Gtk.Window parent = Toplevel as Gtk.Window; + parent.Present(); + + InitializeRenderer(); + + Device.Gpu.Renderer.Initialize(_glLogLevel); + + Renderer.Window.SetAntiAliasing((Graphics.GAL.AntiAliasing)ConfigurationState.Instance.Graphics.AntiAliasing.Value); + Renderer.Window.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + Renderer.Window.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + + _gpuBackendName = GetGpuBackendName(); + _gpuDriverName = GetGpuDriverName(); + + Device.Gpu.Renderer.RunLoop(() => + { + Device.Gpu.SetGpuThread(); + Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); + + Renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync); + + (Toplevel as MainWindow)?.ActivatePauseMenu(); + + while (_isActive) + { + if (_isStopped) + { + return; + } + + _ticks += _chrono.ElapsedTicks; + + _chrono.Restart(); + + if (Device.WaitFifo()) + { + Device.Statistics.RecordFifoStart(); + Device.ProcessFrame(); + Device.Statistics.RecordFifoEnd(); + } + + while (Device.ConsumeFrameAvailable()) + { + Device.PresentFrame(SwapBuffers); + } + + if (_ticks >= _ticksPerFrame) + { + string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? "Docked" : "Handheld"; + float scale = GraphicsConfig.ResScale; + if (scale != 1) + { + dockedMode += $" ({scale}x)"; + } + + StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( + Device.EnableDeviceVsync, + Device.GetVolume(), + _gpuBackendName, + dockedMode, + ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), + $"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", + $"FIFO: {Device.Statistics.GetFifoPercent():0.00} %", + $"GPU: {_gpuDriverName}")); + + _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame); + } + } + + // Make sure all commands in the run loop are fully executed before leaving the loop. + if (Device.Gpu.Renderer is ThreadedRenderer threaded) + { + threaded.FlushThreadedCommands(); + } + + _gpuDoneEvent.Set(); + }); + } + + public void Start() + { + _chrono.Restart(); + + _isActive = true; + + Gtk.Window parent = Toplevel as Gtk.Window; + + Application.Invoke(delegate + { + parent.Present(); + + var activeProcess = Device.Processes.ActiveApplication; + + parent.Title = TitleHelper.ActiveApplicationTitle(activeProcess, Program.Version); + }); + + Thread renderLoopThread = new(Render) + { + Name = "GUI.RenderLoop", + }; + renderLoopThread.Start(); + + Thread nvidiaStutterWorkaround = null; + if (Renderer is Graphics.OpenGL.OpenGLRenderer) + { + nvidiaStutterWorkaround = new Thread(NvidiaStutterWorkaround) + { + Name = "GUI.NvidiaStutterWorkaround", + }; + nvidiaStutterWorkaround.Start(); + } + + MainLoop(); + + // NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose. + // We only need to wait for all commands submitted during the main gpu loop to be processed. + _gpuDoneEvent.WaitOne(); + _gpuDoneEvent.Dispose(); + nvidiaStutterWorkaround?.Join(); + + Exit(); + } + + public void Exit() + { + TouchScreenManager?.Dispose(); + NpadManager?.Dispose(); + + if (_isStopped) + { + return; + } + + _gpuCancellationTokenSource.Cancel(); + + _isStopped = true; + + if (_isActive) + { + _isActive = false; + + _exitEvent.WaitOne(); + _exitEvent.Dispose(); + } + } + + private void NvidiaStutterWorkaround() + { + while (_isActive) + { + // When NVIDIA Threaded Optimization is on, the driver will snapshot all threads in the system whenever the application creates any new ones. + // The ThreadPool has something called a "GateThread" which terminates itself after some inactivity. + // However, it immediately starts up again, since the rules regarding when to terminate and when to start differ. + // This creates a new thread every second or so. + // The main problem with this is that the thread snapshot can take 70ms, is on the OpenGL thread and will delay rendering any graphics. + // This is a little over budget on a frame time of 16ms, so creates a large stutter. + // The solution is to keep the ThreadPool active so that it never has a reason to terminate the GateThread. + + // TODO: This should be removed when the issue with the GateThread is resolved. + + ThreadPool.QueueUserWorkItem((state) => { }); + Thread.Sleep(300); + } + } + + public void MainLoop() + { + while (_isActive) + { + UpdateFrame(); + + // Polling becomes expensive if it's not slept + Thread.Sleep(1); + } + + _exitEvent.Set(); + } + + private bool UpdateFrame() + { + if (!_isActive) + { + return true; + } + + if (_isStopped) + { + return false; + } + + if ((Toplevel as MainWindow).IsFocused) + { + Application.Invoke(delegate + { + KeyboardStateSnapshot keyboard = _keyboardInterface.GetKeyboardStateSnapshot(); + + HandleScreenState(keyboard); + + if (keyboard.IsPressed(Key.Delete)) + { + if (!ParentWindow.State.HasFlag(WindowState.Fullscreen)) + { + Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel(); + } + } + }); + } + + NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + + if ((Toplevel as MainWindow).IsFocused) + { + KeyboardHotkeyState currentHotkeyState = GetHotkeyState(); + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ToggleVSync) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ToggleVSync)) + { + Device.EnableDeviceVsync = !Device.EnableDeviceVsync; + } + + if ((currentHotkeyState.HasFlag(KeyboardHotkeyState.Screenshot) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.Screenshot)) || ScreenshotRequested) + { + ScreenshotRequested = false; + + Renderer.Screenshot(); + } + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ShowUI) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ShowUI)) + { + (Toplevel as MainWindow).ToggleExtraWidgets(true); + } + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.Pause) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.Pause)) + { + (Toplevel as MainWindow)?.TogglePause(); + } + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ToggleMute) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ToggleMute)) + { + if (Device.IsAudioMuted()) + { + Device.SetVolume(ConfigurationState.Instance.System.AudioVolume); + } + else + { + Device.SetVolume(0); + } + } + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleUp) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleUp)) + { + GraphicsConfig.ResScale = GraphicsConfig.ResScale % MaxResolutionScale + 1; + } + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleDown) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleDown)) + { + GraphicsConfig.ResScale = + (MaxResolutionScale + GraphicsConfig.ResScale - 2) % MaxResolutionScale + 1; + } + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.VolumeUp) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.VolumeUp)) + { + _newVolume = MathF.Round((Device.GetVolume() + VolumeDelta), 2); + Device.SetVolume(_newVolume); + } + + if (currentHotkeyState.HasFlag(KeyboardHotkeyState.VolumeDown) && + !_prevHotkeyState.HasFlag(KeyboardHotkeyState.VolumeDown)) + { + _newVolume = MathF.Round((Device.GetVolume() - VolumeDelta), 2); + Device.SetVolume(_newVolume); + } + + _prevHotkeyState = currentHotkeyState; + } + + // Touchscreen + bool hasTouch = false; + + // Get screen touch position + if ((Toplevel as MainWindow).IsFocused && !ConfigurationState.Instance.Hid.EnableMouse) + { + hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as GTK3MouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + } + + if (!hasTouch) + { + TouchScreenManager.Update(false); + } + + Device.Hid.DebugPad.Update(); + + return true; + } + + [Flags] + private enum KeyboardHotkeyState + { + None = 0, + ToggleVSync = 1 << 0, + Screenshot = 1 << 1, + ShowUI = 1 << 2, + Pause = 1 << 3, + ToggleMute = 1 << 4, + ResScaleUp = 1 << 5, + ResScaleDown = 1 << 6, + VolumeUp = 1 << 7, + VolumeDown = 1 << 8, + } + + private KeyboardHotkeyState GetHotkeyState() + { + KeyboardHotkeyState state = KeyboardHotkeyState.None; + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync)) + { + state |= KeyboardHotkeyState.ToggleVSync; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot)) + { + state |= KeyboardHotkeyState.Screenshot; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI)) + { + state |= KeyboardHotkeyState.ShowUI; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause)) + { + state |= KeyboardHotkeyState.Pause; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleMute)) + { + state |= KeyboardHotkeyState.ToggleMute; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleUp)) + { + state |= KeyboardHotkeyState.ResScaleUp; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleDown)) + { + state |= KeyboardHotkeyState.ResScaleDown; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeUp)) + { + state |= KeyboardHotkeyState.VolumeUp; + } + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeDown)) + { + state |= KeyboardHotkeyState.VolumeDown; + } + + return state; + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/SPBOpenGLContext.cs b/src/Ryujinx.Gtk3/UI/SPBOpenGLContext.cs new file mode 100644 index 00000000..97feb434 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/SPBOpenGLContext.cs @@ -0,0 +1,49 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.OpenGL; +using SPB.Graphics; +using SPB.Graphics.OpenGL; +using SPB.Platform; +using SPB.Windowing; + +namespace Ryujinx.UI +{ + class SPBOpenGLContext : IOpenGLContext + { + private readonly OpenGLContextBase _context; + private readonly NativeWindowBase _window; + + private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window) + { + _context = context; + _window = window; + } + + public void Dispose() + { + _context.Dispose(); + _window.Dispose(); + } + + public void MakeCurrent() + { + _context.MakeCurrent(_window); + } + + public bool HasContext() => _context.IsCurrent; + + public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext) + { + OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext); + NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100); + + context.Initialize(window); + context.MakeCurrent(window); + + GL.LoadBindings(new OpenToolkitBindingsContext(context)); + + context.MakeCurrent(null); + + return new SPBOpenGLContext(context, window); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/StatusUpdatedEventArgs.cs b/src/Ryujinx.Gtk3/UI/StatusUpdatedEventArgs.cs new file mode 100644 index 00000000..db467ebf --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/StatusUpdatedEventArgs.cs @@ -0,0 +1,28 @@ +using System; + +namespace Ryujinx.UI +{ + public class StatusUpdatedEventArgs : EventArgs + { + public bool VSyncEnabled; + public float Volume; + public string DockedMode; + public string AspectRatio; + public string GameStatus; + public string FifoStatus; + public string GpuName; + public string GpuBackend; + + public StatusUpdatedEventArgs(bool vSyncEnabled, float volume, string gpuBackend, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, string gpuName) + { + VSyncEnabled = vSyncEnabled; + Volume = volume; + GpuBackend = gpuBackend; + DockedMode = dockedMode; + AspectRatio = aspectRatio; + GameStatus = gameStatus; + FifoStatus = fifoStatus; + GpuName = gpuName; + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/VulkanRenderer.cs b/src/Ryujinx.Gtk3/UI/VulkanRenderer.cs new file mode 100644 index 00000000..eefc5699 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/VulkanRenderer.cs @@ -0,0 +1,93 @@ +using Gdk; +using Ryujinx.Common.Configuration; +using Ryujinx.Input.HLE; +using Ryujinx.UI.Helper; +using SPB.Graphics.Vulkan; +using SPB.Platform.Metal; +using SPB.Platform.Win32; +using SPB.Platform.X11; +using SPB.Windowing; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.UI +{ + public partial class VulkanRenderer : RendererWidgetBase + { + public NativeWindowBase NativeWindow { get; private set; } + private UpdateBoundsCallbackDelegate _updateBoundsCallback; + + public VulkanRenderer(InputManager inputManager, GraphicsDebugLevel glLogLevel) : base(inputManager, glLogLevel) { } + + private NativeWindowBase RetrieveNativeWindow() + { + if (OperatingSystem.IsWindows()) + { + IntPtr windowHandle = gdk_win32_window_get_handle(Window.Handle); + + return new SimpleWin32Window(new NativeHandle(windowHandle)); + } + else if (OperatingSystem.IsLinux()) + { + IntPtr displayHandle = gdk_x11_display_get_xdisplay(Display.Handle); + IntPtr windowHandle = gdk_x11_window_get_xid(Window.Handle); + + return new SimpleX11Window(new NativeHandle(displayHandle), new NativeHandle(windowHandle)); + } + else if (OperatingSystem.IsMacOS()) + { + IntPtr metalLayer = MetalHelper.GetMetalLayer(Display, Window, out IntPtr nsView, out _updateBoundsCallback); + + return new SimpleMetalWindow(new NativeHandle(nsView), new NativeHandle(metalLayer)); + } + + throw new NotImplementedException(); + } + + [LibraryImport("libgdk-3-0.dll")] + private static partial IntPtr gdk_win32_window_get_handle(IntPtr d); + + [LibraryImport("libgdk-3.so.0")] + private static partial IntPtr gdk_x11_display_get_xdisplay(IntPtr gdkDisplay); + + [LibraryImport("libgdk-3.so.0")] + private static partial IntPtr gdk_x11_window_get_xid(IntPtr gdkWindow); + + protected override bool OnConfigureEvent(EventConfigure evnt) + { + if (NativeWindow == null) + { + NativeWindow = RetrieveNativeWindow(); + + WaitEvent.Set(); + } + + bool result = base.OnConfigureEvent(evnt); + + _updateBoundsCallback?.Invoke(Window); + + return result; + } + + public unsafe IntPtr CreateWindowSurface(IntPtr instance) + { + return VulkanHelper.CreateWindowSurface(instance, NativeWindow); + } + + public override void InitializeRenderer() { } + + public override void SwapBuffers() { } + + protected override string GetGpuBackendName() + { + return "Vulkan"; + } + + protected override void Dispose(bool disposing) + { + Device?.DisposeGpu(); + + NpadManager.Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.Designer.cs b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.Designer.cs new file mode 100644 index 00000000..8ee1cd2f --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.Designer.cs @@ -0,0 +1,233 @@ +using Gtk; +using System; + +namespace Ryujinx.UI.Widgets +{ + public partial class GameTableContextMenu : Menu + { + private MenuItem _openSaveUserDirMenuItem; + private MenuItem _openSaveDeviceDirMenuItem; + private MenuItem _openSaveBcatDirMenuItem; + private MenuItem _manageTitleUpdatesMenuItem; + private MenuItem _manageDlcMenuItem; + private MenuItem _manageCheatMenuItem; + private MenuItem _openTitleModDirMenuItem; + private MenuItem _openTitleSdModDirMenuItem; + private Menu _extractSubMenu; + private MenuItem _extractMenuItem; + private MenuItem _extractRomFsMenuItem; + private MenuItem _extractExeFsMenuItem; + private MenuItem _extractLogoMenuItem; + private Menu _manageSubMenu; + private MenuItem _manageCacheMenuItem; + private MenuItem _purgePtcCacheMenuItem; + private MenuItem _purgeShaderCacheMenuItem; + private MenuItem _openPtcDirMenuItem; + private MenuItem _openShaderCacheDirMenuItem; + private MenuItem _createShortcutMenuItem; + + private void InitializeComponent() + { + // + // _openSaveUserDirMenuItem + // + _openSaveUserDirMenuItem = new MenuItem("Open User Save Directory") + { + TooltipText = "Open the directory which contains Application's User Saves.", + }; + _openSaveUserDirMenuItem.Activated += OpenSaveUserDir_Clicked; + + // + // _openSaveDeviceDirMenuItem + // + _openSaveDeviceDirMenuItem = new MenuItem("Open Device Save Directory") + { + TooltipText = "Open the directory which contains Application's Device Saves.", + }; + _openSaveDeviceDirMenuItem.Activated += OpenSaveDeviceDir_Clicked; + + // + // _openSaveBcatDirMenuItem + // + _openSaveBcatDirMenuItem = new MenuItem("Open BCAT Save Directory") + { + TooltipText = "Open the directory which contains Application's BCAT Saves.", + }; + _openSaveBcatDirMenuItem.Activated += OpenSaveBcatDir_Clicked; + + // + // _manageTitleUpdatesMenuItem + // + _manageTitleUpdatesMenuItem = new MenuItem("Manage Title Updates") + { + TooltipText = "Open the Title Update management window", + }; + _manageTitleUpdatesMenuItem.Activated += ManageTitleUpdates_Clicked; + + // + // _manageDlcMenuItem + // + _manageDlcMenuItem = new MenuItem("Manage DLC") + { + TooltipText = "Open the DLC management window", + }; + _manageDlcMenuItem.Activated += ManageDlc_Clicked; + + // + // _manageCheatMenuItem + // + _manageCheatMenuItem = new MenuItem("Manage Cheats") + { + TooltipText = "Open the Cheat management window", + }; + _manageCheatMenuItem.Activated += ManageCheats_Clicked; + + // + // _openTitleModDirMenuItem + // + _openTitleModDirMenuItem = new MenuItem("Open Mods Directory") + { + TooltipText = "Open the directory which contains Application's Mods.", + }; + _openTitleModDirMenuItem.Activated += OpenTitleModDir_Clicked; + + // + // _openTitleSdModDirMenuItem + // + _openTitleSdModDirMenuItem = new MenuItem("Open Atmosphere Mods Directory") + { + TooltipText = "Open the alternative SD card atmosphere directory which contains the Application's Mods.", + }; + _openTitleSdModDirMenuItem.Activated += OpenTitleSdModDir_Clicked; + + // + // _extractSubMenu + // + _extractSubMenu = new Menu(); + + // + // _extractMenuItem + // + _extractMenuItem = new MenuItem("Extract Data") + { + Submenu = _extractSubMenu + }; + + // + // _extractRomFsMenuItem + // + _extractRomFsMenuItem = new MenuItem("RomFS") + { + TooltipText = "Extract the RomFS section from Application's current config (including updates).", + }; + _extractRomFsMenuItem.Activated += ExtractRomFs_Clicked; + + // + // _extractExeFsMenuItem + // + _extractExeFsMenuItem = new MenuItem("ExeFS") + { + TooltipText = "Extract the ExeFS section from Application's current config (including updates).", + }; + _extractExeFsMenuItem.Activated += ExtractExeFs_Clicked; + + // + // _extractLogoMenuItem + // + _extractLogoMenuItem = new MenuItem("Logo") + { + TooltipText = "Extract the Logo section from Application's current config (including updates).", + }; + _extractLogoMenuItem.Activated += ExtractLogo_Clicked; + + // + // _manageSubMenu + // + _manageSubMenu = new Menu(); + + // + // _manageCacheMenuItem + // + _manageCacheMenuItem = new MenuItem("Cache Management") + { + Submenu = _manageSubMenu, + }; + + // + // _purgePtcCacheMenuItem + // + _purgePtcCacheMenuItem = new MenuItem("Queue PPTC Rebuild") + { + TooltipText = "Trigger PPTC to rebuild at boot time on the next game launch.", + }; + _purgePtcCacheMenuItem.Activated += PurgePtcCache_Clicked; + + // + // _purgeShaderCacheMenuItem + // + _purgeShaderCacheMenuItem = new MenuItem("Purge Shader Cache") + { + TooltipText = "Delete the Application's shader cache.", + }; + _purgeShaderCacheMenuItem.Activated += PurgeShaderCache_Clicked; + + // + // _openPtcDirMenuItem + // + _openPtcDirMenuItem = new MenuItem("Open PPTC Directory") + { + TooltipText = "Open the directory which contains the Application's PPTC cache.", + }; + _openPtcDirMenuItem.Activated += OpenPtcDir_Clicked; + + // + // _openShaderCacheDirMenuItem + // + _openShaderCacheDirMenuItem = new MenuItem("Open Shader Cache Directory") + { + TooltipText = "Open the directory which contains the Application's shader cache.", + }; + _openShaderCacheDirMenuItem.Activated += OpenShaderCacheDir_Clicked; + + // + // _createShortcutMenuItem + // + _createShortcutMenuItem = new MenuItem("Create Application Shortcut") + { + TooltipText = OperatingSystem.IsMacOS() ? "Create a shortcut in macOS's Applications folder that launches the selected Application" : "Create a Desktop Shortcut that launches the selected Application." + }; + _createShortcutMenuItem.Activated += CreateShortcut_Clicked; + + ShowComponent(); + } + + private void ShowComponent() + { + _extractSubMenu.Append(_extractExeFsMenuItem); + _extractSubMenu.Append(_extractRomFsMenuItem); + _extractSubMenu.Append(_extractLogoMenuItem); + + _manageSubMenu.Append(_purgePtcCacheMenuItem); + _manageSubMenu.Append(_purgeShaderCacheMenuItem); + _manageSubMenu.Append(_openPtcDirMenuItem); + _manageSubMenu.Append(_openShaderCacheDirMenuItem); + + Add(_createShortcutMenuItem); + Add(new SeparatorMenuItem()); + Add(_openSaveUserDirMenuItem); + Add(_openSaveDeviceDirMenuItem); + Add(_openSaveBcatDirMenuItem); + Add(new SeparatorMenuItem()); + Add(_manageTitleUpdatesMenuItem); + Add(_manageDlcMenuItem); + Add(_manageCheatMenuItem); + Add(_openTitleModDirMenuItem); + Add(_openTitleSdModDirMenuItem); + Add(new SeparatorMenuItem()); + Add(_manageCacheMenuItem); + Add(_extractMenuItem); + + ShowAll(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs new file mode 100644 index 00000000..a3e3d4c8 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/GameTableContextMenu.cs @@ -0,0 +1,634 @@ +using Gtk; +using LibHac; +using LibHac.Account; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Fs.Shim; +using LibHac.FsSystem; +using LibHac.Ns; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.Loaders.Processes.Extensions; +using Ryujinx.HLE.Utilities; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Ryujinx.UI.Windows; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Threading; + +namespace Ryujinx.UI.Widgets +{ + public partial class GameTableContextMenu : Menu + { + private readonly MainWindow _parent; + private readonly VirtualFileSystem _virtualFileSystem; + private readonly AccountManager _accountManager; + private readonly HorizonClient _horizonClient; + + private readonly ApplicationData _applicationData; + + private MessageDialog _dialog; + private bool _cancel; + + public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, ApplicationData applicationData) + { + _parent = parent; + + InitializeComponent(); + + _virtualFileSystem = virtualFileSystem; + _accountManager = accountManager; + _horizonClient = horizonClient; + _applicationData = applicationData; + + if (!_applicationData.ControlHolder.ByteSpan.IsZeros()) + { + _openSaveUserDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.UserAccountSaveDataSize > 0; + _openSaveDeviceDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.DeviceSaveDataSize > 0; + _openSaveBcatDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0; + } + else + { + _openSaveUserDirMenuItem.Sensitive = false; + _openSaveDeviceDirMenuItem.Sensitive = false; + _openSaveBcatDirMenuItem.Sensitive = false; + } + + string fileExt = System.IO.Path.GetExtension(_applicationData.Path).ToLower(); + bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci"; + + _extractRomFsMenuItem.Sensitive = hasNca; + _extractExeFsMenuItem.Sensitive = hasNca; + _extractLogoMenuItem.Sensitive = hasNca; + + _createShortcutMenuItem.Sensitive = !ReleaseInformation.IsFlatHubBuild; + + PopupAtPointer(null); + } + + private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct controlHolder, in SaveDataFilter filter, out ulong saveDataId) + { + saveDataId = default; + + Result result = _horizonClient.Fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, in filter); + + if (ResultFs.TargetNotFound.Includes(result)) + { + ref ApplicationControlProperty control = ref controlHolder.Value; + + Logger.Info?.Print(LogClass.Application, $"Creating save directory for Title: {titleName} [{titleId:x16}]"); + + if (Utilities.IsZeros(controlHolder.ByteSpan)) + { + // If the current application doesn't have a loaded control property, create a dummy one + // and set the savedata sizes so a user savedata will be created. + control = ref new BlitStruct(1).Value; + + // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. + control.UserAccountSaveDataSize = 0x4000; + control.UserAccountSaveDataJournalSize = 0x4000; + + Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); + } + + Uid user = new((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low); + + result = _horizonClient.Fs.EnsureApplicationSaveData(out _, new LibHac.Ncm.ApplicationId(titleId), in control, in user); + + if (result.IsFailure()) + { + GtkDialog.CreateErrorDialog($"There was an error creating the specified savedata: {result.ToStringWithName()}"); + + return false; + } + + // Try to find the savedata again after creating it + result = _horizonClient.Fs.FindSaveDataWithFilter(out saveDataInfo, SaveDataSpaceId.User, in filter); + } + + if (result.IsSuccess()) + { + saveDataId = saveDataInfo.SaveDataId; + + return true; + } + + GtkDialog.CreateErrorDialog($"There was an error finding the specified savedata: {result.ToStringWithName()}"); + + return false; + } + + private void OpenSaveDir(in SaveDataFilter saveDataFilter) + { + if (!TryFindSaveData(_applicationData.Name, _applicationData.Id, _applicationData.ControlHolder, in saveDataFilter, out ulong saveDataId)) + { + return; + } + + string saveRootPath = System.IO.Path.Combine(VirtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}"); + + if (!Directory.Exists(saveRootPath)) + { + // Inconsistent state. Create the directory + Directory.CreateDirectory(saveRootPath); + } + + string committedPath = System.IO.Path.Combine(saveRootPath, "0"); + string workingPath = System.IO.Path.Combine(saveRootPath, "1"); + + // If the committed directory exists, that path will be loaded the next time the savedata is mounted + if (Directory.Exists(committedPath)) + { + OpenHelper.OpenFolder(committedPath); + } + else + { + // If the working directory exists and the committed directory doesn't, + // the working directory will be loaded the next time the savedata is mounted + if (!Directory.Exists(workingPath)) + { + Directory.CreateDirectory(workingPath); + } + + OpenHelper.OpenFolder(workingPath); + } + } + + private void ExtractSection(NcaSectionType ncaSectionType, int programIndex = 0) + { + FileChooserNative fileChooser = new("Choose the folder to extract into", _parent, FileChooserAction.SelectFolder, "Extract", "Cancel"); + + ResponseType response = (ResponseType)fileChooser.Run(); + string destination = fileChooser.Filename; + + fileChooser.Dispose(); + + if (response == ResponseType.Accept) + { + Thread extractorThread = new(() => + { + Gtk.Application.Invoke(delegate + { + _dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null) + { + Title = "Ryujinx - NCA Section Extractor", + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"), + SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_applicationData.Path)}...", + WindowPosition = WindowPosition.Center, + }; + + int dialogResponse = _dialog.Run(); + if (dialogResponse == (int)ResponseType.Cancel || dialogResponse == (int)ResponseType.DeleteEvent) + { + _cancel = true; + _dialog.Dispose(); + } + }); + + using FileStream file = new(_applicationData.Path, FileMode.Open, FileAccess.Read); + + Nca mainNca = null; + Nca patchNca = null; + + if ((System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".nsp") || + (System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".pfs0") || + (System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".xci")) + { + IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(_applicationData.Path, _virtualFileSystem); + + foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) + { + using var ncaFile = new UniqueRef(); + + pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Release().AsStorage()); + + if (nca.Header.ContentType == NcaContentType.Program) + { + int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); + + if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()) + { + patchNca = nca; + } + else + { + mainNca = nca; + } + } + } + } + else if (System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".nca") + { + mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage()); + } + + if (mainNca == null) + { + Logger.Error?.Print(LogClass.Application, "Extraction failure. The main NCA is not present in the selected file."); + + Gtk.Application.Invoke(delegate + { + GtkDialog.CreateErrorDialog("Extraction failure. The main NCA is not present in the selected file."); + }); + + return; + } + + IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks + ? IntegrityCheckLevel.ErrorOnInvalid + : IntegrityCheckLevel.None; + + (Nca updatePatchNca, _) = mainNca.GetUpdateData(_virtualFileSystem, checkLevel, programIndex, out _); + + if (updatePatchNca != null) + { + patchNca = updatePatchNca; + } + + int index = Nca.GetSectionIndexFromType(ncaSectionType, mainNca.Header.ContentType); + + bool sectionExistsInPatch = false; + + if (patchNca != null) + { + sectionExistsInPatch = patchNca.CanOpenSection(index); + } + + IFileSystem ncaFileSystem = sectionExistsInPatch ? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid) + : mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid); + + FileSystemClient fsClient = _horizonClient.Fs; + + string source = DateTime.Now.ToFileTime().ToString()[10..]; + string output = DateTime.Now.ToFileTime().ToString()[10..]; + + using var uniqueSourceFs = new UniqueRef(ncaFileSystem); + using var uniqueOutputFs = new UniqueRef(new LocalFileSystem(destination)); + + fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref); + fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref); + + (Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/"); + + if (!canceled) + { + if (resultCode.Value.IsFailure()) + { + Logger.Error?.Print(LogClass.Application, $"LibHac returned error code: {resultCode.Value.ErrorCode}"); + + Gtk.Application.Invoke(delegate + { + _dialog?.Dispose(); + + GtkDialog.CreateErrorDialog("Extraction failed. Read the log file for further information."); + }); + } + else if (resultCode.Value.IsSuccess()) + { + Gtk.Application.Invoke(delegate + { + _dialog?.Dispose(); + + MessageDialog dialog = new(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Ok, null) + { + Title = "Ryujinx - NCA Section Extractor", + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"), + SecondaryText = "Extraction completed successfully.", + WindowPosition = WindowPosition.Center, + }; + + dialog.Run(); + dialog.Dispose(); + }); + } + } + + fsClient.Unmount(source.ToU8Span()); + fsClient.Unmount(output.ToU8Span()); + }) + { + Name = "GUI.NcaSectionExtractorThread", + IsBackground = true, + }; + extractorThread.Start(); + } + } + + private (Result? result, bool canceled) CopyDirectory(FileSystemClient fs, string sourcePath, string destPath) + { + Result rc = fs.OpenDirectory(out DirectoryHandle sourceHandle, sourcePath.ToU8Span(), OpenDirectoryMode.All); + if (rc.IsFailure()) + { + return (rc, false); + } + + using (sourceHandle) + { + foreach (DirectoryEntryEx entry in fs.EnumerateEntries(sourcePath, "*", SearchOptions.Default)) + { + if (_cancel) + { + return (null, true); + } + + string subSrcPath = PathTools.Normalize(PathTools.Combine(sourcePath, entry.Name)); + string subDstPath = PathTools.Normalize(PathTools.Combine(destPath, entry.Name)); + + if (entry.Type == DirectoryEntryType.Directory) + { + fs.EnsureDirectoryExists(subDstPath); + + (Result? result, bool canceled) = CopyDirectory(fs, subSrcPath, subDstPath); + if (canceled || result.Value.IsFailure()) + { + return (result, canceled); + } + } + + if (entry.Type == DirectoryEntryType.File) + { + fs.CreateOrOverwriteFile(subDstPath, entry.Size); + + rc = CopyFile(fs, subSrcPath, subDstPath); + if (rc.IsFailure()) + { + return (rc, false); + } + } + } + } + + return (Result.Success, false); + } + + public static Result CopyFile(FileSystemClient fs, string sourcePath, string destPath) + { + Result rc = fs.OpenFile(out FileHandle sourceHandle, sourcePath.ToU8Span(), OpenMode.Read); + if (rc.IsFailure()) + { + return rc; + } + + using (sourceHandle) + { + rc = fs.OpenFile(out FileHandle destHandle, destPath.ToU8Span(), OpenMode.Write | OpenMode.AllowAppend); + if (rc.IsFailure()) + { + return rc; + } + + using (destHandle) + { + const int MaxBufferSize = 1024 * 1024; + + rc = fs.GetFileSize(out long fileSize, sourceHandle); + if (rc.IsFailure()) + { + return rc; + } + + int bufferSize = (int)Math.Min(MaxBufferSize, fileSize); + + byte[] buffer = ArrayPool.Shared.Rent(bufferSize); + try + { + for (long offset = 0; offset < fileSize; offset += bufferSize) + { + int toRead = (int)Math.Min(fileSize - offset, bufferSize); + Span buf = buffer.AsSpan(0, toRead); + + rc = fs.ReadFile(out long _, sourceHandle, offset, buf); + if (rc.IsFailure()) + { + return rc; + } + + rc = fs.WriteFile(destHandle, offset, buf, WriteOption.None); + if (rc.IsFailure()) + { + return rc; + } + } + } + finally + { + ArrayPool.Shared.Return(buffer); + } + + rc = fs.FlushFile(destHandle); + if (rc.IsFailure()) + { + return rc; + } + } + } + + return Result.Success; + } + + // + // Events + // + private void OpenSaveUserDir_Clicked(object sender, EventArgs args) + { + var userId = new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low); + var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, saveType: default, userId, saveDataId: default, index: default); + + OpenSaveDir(in saveDataFilter); + } + + private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args) + { + var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, SaveDataType.Device, userId: default, saveDataId: default, index: default); + + OpenSaveDir(in saveDataFilter); + } + + private void OpenSaveBcatDir_Clicked(object sender, EventArgs args) + { + var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, SaveDataType.Bcat, userId: default, saveDataId: default, index: default); + + OpenSaveDir(in saveDataFilter); + } + + private void ManageTitleUpdates_Clicked(object sender, EventArgs args) + { + new TitleUpdateWindow(_parent, _virtualFileSystem, _applicationData).Show(); + } + + private void ManageDlc_Clicked(object sender, EventArgs args) + { + new DlcWindow(_virtualFileSystem, _applicationData.IdBaseString, _applicationData).Show(); + } + + private void ManageCheats_Clicked(object sender, EventArgs args) + { + new CheatWindow(_virtualFileSystem, _applicationData.Id, _applicationData.Name, _applicationData.Path).Show(); + } + + private void OpenTitleModDir_Clicked(object sender, EventArgs args) + { + string modsBasePath = ModLoader.GetModsBasePath(); + string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _applicationData.IdString); + + OpenHelper.OpenFolder(titleModsPath); + } + + private void OpenTitleSdModDir_Clicked(object sender, EventArgs args) + { + string sdModsBasePath = ModLoader.GetSdModsBasePath(); + string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _applicationData.IdString); + + OpenHelper.OpenFolder(titleModsPath); + } + + private void ExtractRomFs_Clicked(object sender, EventArgs args) + { + ExtractSection(NcaSectionType.Data); + } + + private void ExtractExeFs_Clicked(object sender, EventArgs args) + { + ExtractSection(NcaSectionType.Code); + } + + private void ExtractLogo_Clicked(object sender, EventArgs args) + { + ExtractSection(NcaSectionType.Logo); + } + + private void OpenPtcDir_Clicked(object sender, EventArgs args) + { + string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu"); + + string mainPath = System.IO.Path.Combine(ptcDir, "0"); + string backupPath = System.IO.Path.Combine(ptcDir, "1"); + + if (!Directory.Exists(ptcDir)) + { + Directory.CreateDirectory(ptcDir); + Directory.CreateDirectory(mainPath); + Directory.CreateDirectory(backupPath); + } + + OpenHelper.OpenFolder(ptcDir); + } + + private void OpenShaderCacheDir_Clicked(object sender, EventArgs args) + { + string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "shader"); + + if (!Directory.Exists(shaderCacheDir)) + { + Directory.CreateDirectory(shaderCacheDir); + } + + OpenHelper.OpenFolder(shaderCacheDir); + } + + private void PurgePtcCache_Clicked(object sender, EventArgs args) + { + DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu", "0")); + DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu", "1")); + + MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to queue a PPTC rebuild on the next boot of:\n\n{_applicationData.Name}\n\nAre you sure you want to proceed?"); + + List cacheFiles = new(); + + if (mainDir.Exists) + { + cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); + } + + if (backupDir.Exists) + { + cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); + } + + if (cacheFiles.Count > 0 && warningDialog.Run() == (int)ResponseType.Yes) + { + foreach (FileInfo file in cacheFiles) + { + try + { + file.Delete(); + } + catch (Exception e) + { + GtkDialog.CreateErrorDialog($"Error purging PPTC cache {file.Name}: {e}"); + } + } + } + + warningDialog.Dispose(); + } + + private void PurgeShaderCache_Clicked(object sender, EventArgs args) + { + DirectoryInfo shaderCacheDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "shader")); + + using MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n{_applicationData.Name}\n\nAre you sure you want to proceed?"); + + List oldCacheDirectories = new(); + List newCacheFiles = new(); + + if (shaderCacheDir.Exists) + { + oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*")); + newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc")); + newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data")); + } + + if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0) && warningDialog.Run() == (int)ResponseType.Yes) + { + foreach (DirectoryInfo directory in oldCacheDirectories) + { + try + { + directory.Delete(true); + } + catch (Exception e) + { + GtkDialog.CreateErrorDialog($"Error purging shader cache at {directory.Name}: {e}"); + } + } + + foreach (FileInfo file in newCacheFiles) + { + try + { + file.Delete(); + } + catch (Exception e) + { + GtkDialog.CreateErrorDialog($"Error purging shader cache at {file.Name}: {e}"); + } + } + } + } + + private void CreateShortcut_Clicked(object sender, EventArgs args) + { + IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks + ? IntegrityCheckLevel.ErrorOnInvalid + : IntegrityCheckLevel.None; + byte[] appIcon = new ApplicationLibrary(_virtualFileSystem, checkLevel).GetApplicationIcon(_applicationData.Path, ConfigurationState.Instance.System.Language, _applicationData.Id); + ShortcutHelper.CreateAppShortcut(_applicationData.Path, _applicationData.Name, _applicationData.IdString, appIcon); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Widgets/GtkDialog.cs b/src/Ryujinx.Gtk3/UI/Widgets/GtkDialog.cs new file mode 100644 index 00000000..567f9ad6 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/GtkDialog.cs @@ -0,0 +1,114 @@ +using Gtk; +using Ryujinx.Common.Logging; +using Ryujinx.UI.Common.Configuration; +using System.Collections.Generic; +using System.Reflection; + +namespace Ryujinx.UI.Widgets +{ + internal class GtkDialog : MessageDialog + { + private static bool _isChoiceDialogOpen; + + private GtkDialog(string title, string mainText, string secondaryText, MessageType messageType = MessageType.Other, ButtonsType buttonsType = ButtonsType.Ok) + : base(null, DialogFlags.Modal, messageType, buttonsType, null) + { + Title = title; + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + Text = mainText; + SecondaryText = secondaryText; + WindowPosition = WindowPosition.Center; + SecondaryUseMarkup = true; + + Response += GtkDialog_Response; + + SetSizeRequest(200, 20); + } + + private void GtkDialog_Response(object sender, ResponseArgs args) + { + Dispose(); + } + + internal static void CreateInfoDialog(string mainText, string secondaryText) + { + new GtkDialog("Ryujinx - Info", mainText, secondaryText, MessageType.Info).Run(); + } + + internal static void CreateUpdaterInfoDialog(string mainText, string secondaryText) + { + new GtkDialog("Ryujinx - Updater", mainText, secondaryText, MessageType.Info).Run(); + } + + internal static MessageDialog CreateWaitingDialog(string mainText, string secondaryText) + { + return new GtkDialog("Ryujinx - Waiting", mainText, secondaryText, MessageType.Info, ButtonsType.None); + } + + internal static void CreateWarningDialog(string mainText, string secondaryText) + { + new GtkDialog("Ryujinx - Warning", mainText, secondaryText, MessageType.Warning).Run(); + } + + internal static void CreateErrorDialog(string errorMessage) + { + Logger.Error?.Print(LogClass.Application, errorMessage); + + new GtkDialog("Ryujinx - Error", "Ryujinx has encountered an error", errorMessage, MessageType.Error).Run(); + } + + internal static MessageDialog CreateConfirmationDialog(string mainText, string secondaryText = "") + { + return new GtkDialog("Ryujinx - Confirmation", mainText, secondaryText, MessageType.Question, ButtonsType.YesNo); + } + + internal static bool CreateChoiceDialog(string title, string mainText, string secondaryText) + { + if (_isChoiceDialogOpen) + { + return false; + } + + _isChoiceDialogOpen = true; + + ResponseType response = (ResponseType)new GtkDialog(title, mainText, secondaryText, MessageType.Question, ButtonsType.YesNo).Run(); + + _isChoiceDialogOpen = false; + + return response == ResponseType.Yes; + } + + internal static ResponseType CreateCustomDialog(string title, string mainText, string secondaryText, Dictionary buttons, MessageType messageType = MessageType.Other) + { + GtkDialog gtkDialog = new(title, mainText, secondaryText, messageType, ButtonsType.None); + + foreach (var button in buttons) + { + gtkDialog.AddButton(button.Value, button.Key); + } + + return (ResponseType)gtkDialog.Run(); + } + + internal static string CreateInputDialog(Window parent, string title, string mainText, uint inputMax) + { + GtkInputDialog gtkDialog = new(parent, title, mainText, inputMax); + ResponseType response = (ResponseType)gtkDialog.Run(); + string responseText = gtkDialog.InputEntry.Text.TrimEnd(); + + gtkDialog.Dispose(); + + if (response == ResponseType.Ok) + { + return responseText; + } + + return ""; + } + + internal static bool CreateExitDialog() + { + return CreateChoiceDialog("Ryujinx - Exit", "Are you sure you want to close Ryujinx?", "All unsaved data will be lost!"); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Widgets/GtkInputDialog.cs b/src/Ryujinx.Gtk3/UI/Widgets/GtkInputDialog.cs new file mode 100644 index 00000000..fd85248b --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/GtkInputDialog.cs @@ -0,0 +1,37 @@ +using Gtk; + +namespace Ryujinx.UI.Widgets +{ + public class GtkInputDialog : MessageDialog + { + public Entry InputEntry { get; } + + public GtkInputDialog(Window parent, string title, string mainText, uint inputMax) : base(parent, DialogFlags.Modal | DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.OkCancel, null) + { + SetDefaultSize(300, 0); + + Title = title; + + Label mainTextLabel = new() + { + Text = mainText, + }; + + InputEntry = new Entry + { + MaxLength = (int)inputMax, + }; + + Label inputMaxTextLabel = new() + { + Text = $"(Max length: {inputMax})", + }; + + ((Box)MessageArea).PackStart(mainTextLabel, true, true, 0); + ((Box)MessageArea).PackStart(InputEntry, true, true, 5); + ((Box)MessageArea).PackStart(inputMaxTextLabel, true, true, 0); + + ShowAll(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.cs b/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.cs new file mode 100644 index 00000000..3b3e2fbb --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.cs @@ -0,0 +1,57 @@ +using Gtk; +using Ryujinx.UI.Common.Configuration; +using System; +using System.Reflection; +using GUI = Gtk.Builder.ObjectAttribute; + +namespace Ryujinx.UI.Widgets +{ + public class ProfileDialog : Dialog + { + public string FileName { get; private set; } + +#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier + [GUI] Entry _profileEntry; + [GUI] Label _errorMessage; +#pragma warning restore CS0649, IDE0044 + + public ProfileDialog() : this(new Builder("Ryujinx.Gtk3.UI.Widgets.ProfileDialog.glade")) { } + + private ProfileDialog(Builder builder) : base(builder.GetRawOwnedObject("_profileDialog")) + { + builder.Autoconnect(this); + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + } + + private void OkToggle_Activated(object sender, EventArgs args) + { + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); + + bool validFileName = true; + + foreach (char invalidChar in System.IO.Path.GetInvalidFileNameChars()) + { + if (_profileEntry.Text.Contains(invalidChar)) + { + validFileName = false; + } + } + + if (validFileName && !string.IsNullOrEmpty(_profileEntry.Text)) + { + FileName = $"{_profileEntry.Text}.json"; + + Respond(ResponseType.Ok); + } + else + { + _errorMessage.Text = "The file name contains invalid characters. Please try again."; + } + } + + private void CancelToggle_Activated(object sender, EventArgs args) + { + Respond(ResponseType.Cancel); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.glade b/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.glade new file mode 100644 index 00000000..adaf6608 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/ProfileDialog.glade @@ -0,0 +1,124 @@ + + + + + + False + Ryujinx - Profile Dialog + True + center + 400 + dialog + + + + + + False + vertical + 2 + + + False + end + + + OK + True + True + True + + + + False + True + 0 + + + + + Cancel + True + True + True + + + + False + True + 5 + 1 + + + + + False + False + 0 + + + + + True + False + vertical + + + True + False + 10 + 10 + 20 + 10 + Enter a name for the new profile: + + + True + True + 0 + + + + + True + True + 20 + 20 + 20 + + + True + True + 1 + + + + + True + False + start + 20 + 10 + 10 + 10 + + + + + + False + True + 2 + + + + + True + True + 1 + + + + + + diff --git a/src/Ryujinx.Gtk3/UI/Widgets/RawInputToTextEntry.cs b/src/Ryujinx.Gtk3/UI/Widgets/RawInputToTextEntry.cs new file mode 100644 index 00000000..6470790e --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/RawInputToTextEntry.cs @@ -0,0 +1,27 @@ +using Gtk; + +namespace Ryujinx.UI.Widgets +{ + public class RawInputToTextEntry : Entry + { + public void SendKeyPressEvent(object o, KeyPressEventArgs args) + { + base.OnKeyPressEvent(args.Event); + } + + public void SendKeyReleaseEvent(object o, KeyReleaseEventArgs args) + { + base.OnKeyReleaseEvent(args.Event); + } + + public void SendButtonPressEvent(object o, ButtonPressEventArgs args) + { + base.OnButtonPressEvent(args.Event); + } + + public void SendButtonReleaseEvent(object o, ButtonReleaseEventArgs args) + { + base.OnButtonReleaseEvent(args.Event); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Widgets/UserErrorDialog.cs b/src/Ryujinx.Gtk3/UI/Widgets/UserErrorDialog.cs new file mode 100644 index 00000000..f0b55cd8 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Widgets/UserErrorDialog.cs @@ -0,0 +1,123 @@ +using Gtk; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Helper; + +namespace Ryujinx.UI.Widgets +{ + internal class UserErrorDialog : MessageDialog + { + private const string SetupGuideUrl = "https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide"; + private const int OkResponseId = 0; + private const int SetupGuideResponseId = 1; + + private readonly UserError _userError; + + private UserErrorDialog(UserError error) : base(null, DialogFlags.Modal, MessageType.Error, ButtonsType.None, null) + { + _userError = error; + + WindowPosition = WindowPosition.Center; + SecondaryUseMarkup = true; + + Response += UserErrorDialog_Response; + + SetSizeRequest(120, 50); + + AddButton("OK", OkResponseId); + + bool isInSetupGuide = IsCoveredBySetupGuide(error); + + if (isInSetupGuide) + { + AddButton("Open the Setup Guide", SetupGuideResponseId); + } + + string errorCode = GetErrorCode(error); + + SecondaryUseMarkup = true; + + Title = $"Ryujinx error ({errorCode})"; + Text = $"{errorCode}: {GetErrorTitle(error)}"; + SecondaryText = GetErrorDescription(error); + + if (isInSetupGuide) + { + SecondaryText += "\nFor more information on how to fix this error, follow our Setup Guide."; + } + } + + private static string GetErrorCode(UserError error) + { + return $"RYU-{(uint)error:X4}"; + } + + private static string GetErrorTitle(UserError error) + { + return error switch + { + UserError.NoKeys => "Keys not found", + UserError.NoFirmware => "Firmware not found", + UserError.FirmwareParsingFailed => "Firmware parsing error", + UserError.ApplicationNotFound => "Application not found", + UserError.Unknown => "Unknown error", + _ => "Undefined error", + }; + } + + private static string GetErrorDescription(UserError error) + { + return error switch + { + UserError.NoKeys => "Ryujinx was unable to find your 'prod.keys' file", + UserError.NoFirmware => "Ryujinx was unable to find any firmwares installed", + UserError.FirmwareParsingFailed => "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", + UserError.ApplicationNotFound => "Ryujinx couldn't find a valid application at the given path.", + UserError.Unknown => "An unknown error occured!", + _ => "An undefined error occured! This shouldn't happen, please contact a dev!", + }; + } + + private static bool IsCoveredBySetupGuide(UserError error) + { + return error switch + { + UserError.NoKeys or + UserError.NoFirmware or + UserError.FirmwareParsingFailed => true, + _ => false, + }; + } + + private static string GetSetupGuideUrl(UserError error) + { + if (!IsCoveredBySetupGuide(error)) + { + return null; + } + + return error switch + { + UserError.NoKeys => SetupGuideUrl + "#initial-setup---placement-of-prodkeys", + UserError.NoFirmware => SetupGuideUrl + "#initial-setup-continued---installation-of-firmware", + _ => SetupGuideUrl, + }; + } + + private void UserErrorDialog_Response(object sender, ResponseArgs args) + { + int responseId = (int)args.ResponseId; + + if (responseId == SetupGuideResponseId) + { + OpenHelper.OpenUrl(GetSetupGuideUrl(_userError)); + } + + Dispose(); + } + + public static void CreateUserErrorDialog(UserError error) + { + new UserErrorDialog(error).Run(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/AboutWindow.Designer.cs b/src/Ryujinx.Gtk3/UI/Windows/AboutWindow.Designer.cs new file mode 100644 index 00000000..fd912ef9 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/AboutWindow.Designer.cs @@ -0,0 +1,511 @@ +using Gtk; +using Pango; +using Ryujinx.UI.Common.Configuration; +using System.Reflection; + +namespace Ryujinx.UI.Windows +{ + public partial class AboutWindow : Window + { + private Box _mainBox; + private Box _leftBox; + private Box _logoBox; + private Image _ryujinxLogo; + private Box _logoTextBox; + private Label _ryujinxLabel; + private Label _ryujinxPhoneticLabel; + private EventBox _ryujinxLink; + private Label _ryujinxLinkLabel; + private Label _versionLabel; + private Label _disclaimerLabel; + private EventBox _amiiboApiLink; + private Label _amiiboApiLinkLabel; + private Box _socialBox; + private EventBox _patreonEventBox; + private Box _patreonBox; + private Image _patreonLogo; + private Label _patreonLabel; + private EventBox _githubEventBox; + private Box _githubBox; + private Image _githubLogo; + private Label _githubLabel; + private Box _discordBox; + private EventBox _discordEventBox; + private Image _discordLogo; + private Label _discordLabel; + private EventBox _twitterEventBox; + private Box _twitterBox; + private Image _twitterLogo; + private Label _twitterLabel; + private Separator _separator; + private Box _rightBox; + private Label _aboutLabel; + private Label _aboutDescriptionLabel; + private Label _createdByLabel; + private TextView _createdByText; + private EventBox _contributorsEventBox; + private Label _contributorsLinkLabel; + private Label _patreonNamesLabel; + private ScrolledWindow _patreonNamesScrolled; + private TextView _patreonNamesText; + private EventBox _changelogEventBox; + private Label _changelogLinkLabel; + + private void InitializeComponent() + { + + // + // AboutWindow + // + CanFocus = false; + Resizable = false; + Modal = true; + WindowPosition = WindowPosition.Center; + DefaultWidth = 800; + DefaultHeight = 450; + TypeHint = Gdk.WindowTypeHint.Dialog; + + // + // _mainBox + // + _mainBox = new Box(Orientation.Horizontal, 0); + + // + // _leftBox + // + _leftBox = new Box(Orientation.Vertical, 0) + { + Margin = 15, + MarginStart = 30, + MarginEnd = 0, + }; + + // + // _logoBox + // + _logoBox = new Box(Orientation.Horizontal, 0); + + // + // _ryujinxLogo + // + _ryujinxLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png", 100, 100)) + { + Margin = 10, + MarginStart = 15, + }; + + // + // _logoTextBox + // + _logoTextBox = new Box(Orientation.Vertical, 0); + + // + // _ryujinxLabel + // + _ryujinxLabel = new Label("Ryujinx") + { + MarginTop = 15, + Justify = Justification.Center, + Attributes = new AttrList(), + }; + _ryujinxLabel.Attributes.Insert(new Pango.AttrScale(2.7f)); + + // + // _ryujinxPhoneticLabel + // + _ryujinxPhoneticLabel = new Label("(REE-YOU-JINX)") + { + Justify = Justification.Center, + }; + + // + // _ryujinxLink + // + _ryujinxLink = new EventBox() + { + Margin = 5 + }; + _ryujinxLink.ButtonPressEvent += RyujinxButton_Pressed; + + // + // _ryujinxLinkLabel + // + _ryujinxLinkLabel = new Label("www.ryujinx.org") + { + TooltipText = "Click to open the Ryujinx website in your default browser.", + Justify = Justification.Center, + Attributes = new AttrList(), + }; + _ryujinxLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _versionLabel + // + _versionLabel = new Label(Program.Version) + { + Expand = true, + Justify = Justification.Center, + Margin = 5, + }; + + // + // _changelogEventBox + // + _changelogEventBox = new EventBox(); + _changelogEventBox.ButtonPressEvent += ChangelogButton_Pressed; + + // + // _changelogLinkLabel + // + _changelogLinkLabel = new Label("View Changelog on GitHub") + { + TooltipText = "Click to open the changelog for this version in your default browser.", + Justify = Justification.Center, + Attributes = new AttrList(), + }; + _changelogLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _disclaimerLabel + // + _disclaimerLabel = new Label("Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.") + { + Expand = true, + Justify = Justification.Center, + Margin = 5, + Attributes = new AttrList(), + }; + _disclaimerLabel.Attributes.Insert(new Pango.AttrScale(0.8f)); + + // + // _amiiboApiLink + // + _amiiboApiLink = new EventBox() + { + Margin = 5, + }; + _amiiboApiLink.ButtonPressEvent += AmiiboApiButton_Pressed; + + // + // _amiiboApiLinkLabel + // + _amiiboApiLinkLabel = new Label("AmiiboAPI (www.amiiboapi.com) is used\nin our Amiibo emulation.") + { + TooltipText = "Click to open the AmiiboAPI website in your default browser.", + Justify = Justification.Center, + Attributes = new AttrList(), + }; + _amiiboApiLinkLabel.Attributes.Insert(new Pango.AttrScale(0.9f)); + + // + // _socialBox + // + _socialBox = new Box(Orientation.Horizontal, 0) + { + Margin = 25, + MarginBottom = 10, + }; + + // + // _patreonEventBox + // + _patreonEventBox = new EventBox() + { + TooltipText = "Click to open the Ryujinx Patreon page in your default browser.", + }; + _patreonEventBox.ButtonPressEvent += PatreonButton_Pressed; + + // + // _patreonBox + // + _patreonBox = new Box(Orientation.Vertical, 0); + + // + // _patreonLogo + // + _patreonLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Patreon_Light.png", 30, 30)) + { + Margin = 10, + }; + + // + // _patreonLabel + // + _patreonLabel = new Label("Patreon") + { + Justify = Justification.Center, + }; + + // + // _githubEventBox + // + _githubEventBox = new EventBox() + { + TooltipText = "Click to open the Ryujinx GitHub page in your default browser.", + }; + _githubEventBox.ButtonPressEvent += GitHubButton_Pressed; + + // + // _githubBox + // + _githubBox = new Box(Orientation.Vertical, 0); + + // + // _githubLogo + // + _githubLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_GitHub_Light.png", 30, 30)) + { + Margin = 10, + }; + + // + // _githubLabel + // + _githubLabel = new Label("GitHub") + { + Justify = Justification.Center, + }; + + // + // _discordBox + // + _discordBox = new Box(Orientation.Vertical, 0); + + // + // _discordEventBox + // + _discordEventBox = new EventBox() + { + TooltipText = "Click to open an invite to the Ryujinx Discord server in your default browser.", + }; + _discordEventBox.ButtonPressEvent += DiscordButton_Pressed; + + // + // _discordLogo + // + _discordLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Discord_Light.png", 30, 30)) + { + Margin = 10, + }; + + // + // _discordLabel + // + _discordLabel = new Label("Discord") + { + Justify = Justification.Center, + }; + + // + // _twitterEventBox + // + _twitterEventBox = new EventBox() + { + TooltipText = "Click to open the Ryujinx Twitter page in your default browser.", + }; + _twitterEventBox.ButtonPressEvent += TwitterButton_Pressed; + + // + // _twitterBox + // + _twitterBox = new Box(Orientation.Vertical, 0); + + // + // _twitterLogo + // + _twitterLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Twitter_Light.png", 30, 30)) + { + Margin = 10, + }; + + // + // _twitterLabel + // + _twitterLabel = new Label("Twitter") + { + Justify = Justification.Center, + }; + + // + // _separator + // + _separator = new Separator(Orientation.Vertical) + { + Margin = 15, + }; + + // + // _rightBox + // + _rightBox = new Box(Orientation.Vertical, 0) + { + Margin = 15, + MarginTop = 40, + }; + + // + // _aboutLabel + // + _aboutLabel = new Label("About :") + { + Halign = Align.Start, + Attributes = new AttrList(), + }; + _aboutLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); + _aboutLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _aboutDescriptionLabel + // + _aboutDescriptionLabel = new Label("Ryujinx is an emulator for the Nintendo Switch™.\n" + + "Please support us on Patreon.\n" + + "Get all the latest news on our Twitter or Discord.\n" + + "Developers interested in contributing can find out more on our GitHub or Discord.") + { + Margin = 15, + Halign = Align.Start, + }; + + // + // _createdByLabel + // + _createdByLabel = new Label("Maintained by :") + { + Halign = Align.Start, + Attributes = new AttrList(), + }; + _createdByLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); + _createdByLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _createdByText + // + _createdByText = new TextView() + { + WrapMode = Gtk.WrapMode.Word, + Editable = false, + CursorVisible = false, + Margin = 15, + MarginEnd = 30, + }; + _createdByText.Buffer.Text = "gdkchan, Ac_K, Thog, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, Xpl0itR, GoffyDude, »jD« and more..."; + + // + // _contributorsEventBox + // + _contributorsEventBox = new EventBox(); + _contributorsEventBox.ButtonPressEvent += ContributorsButton_Pressed; + + // + // _contributorsLinkLabel + // + _contributorsLinkLabel = new Label("See All Contributors...") + { + TooltipText = "Click to open the Contributors page in your default browser.", + MarginEnd = 30, + Halign = Align.End, + Attributes = new AttrList(), + }; + _contributorsLinkLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _patreonNamesLabel + // + _patreonNamesLabel = new Label("Supported on Patreon by :") + { + Halign = Align.Start, + Attributes = new AttrList(), + }; + _patreonNamesLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); + _patreonNamesLabel.Attributes.Insert(new Pango.AttrUnderline(Underline.Single)); + + // + // _patreonNamesScrolled + // + _patreonNamesScrolled = new ScrolledWindow() + { + Margin = 15, + MarginEnd = 30, + Expand = true, + ShadowType = ShadowType.In, + }; + _patreonNamesScrolled.SetPolicy(PolicyType.Never, PolicyType.Automatic); + + // + // _patreonNamesText + // + _patreonNamesText = new TextView() + { + WrapMode = Gtk.WrapMode.Word, + }; + _patreonNamesText.Buffer.Text = "Loading..."; + _patreonNamesText.SetProperty("editable", new GLib.Value(false)); + + ShowComponent(); + } + + private void ShowComponent() + { + _logoBox.Add(_ryujinxLogo); + + _ryujinxLink.Add(_ryujinxLinkLabel); + + _logoTextBox.Add(_ryujinxLabel); + _logoTextBox.Add(_ryujinxPhoneticLabel); + _logoTextBox.Add(_ryujinxLink); + + _logoBox.Add(_logoTextBox); + + _amiiboApiLink.Add(_amiiboApiLinkLabel); + + _patreonBox.Add(_patreonLogo); + _patreonBox.Add(_patreonLabel); + _patreonEventBox.Add(_patreonBox); + + _githubBox.Add(_githubLogo); + _githubBox.Add(_githubLabel); + _githubEventBox.Add(_githubBox); + + _discordBox.Add(_discordLogo); + _discordBox.Add(_discordLabel); + _discordEventBox.Add(_discordBox); + + _twitterBox.Add(_twitterLogo); + _twitterBox.Add(_twitterLabel); + _twitterEventBox.Add(_twitterBox); + + _socialBox.Add(_patreonEventBox); + _socialBox.Add(_githubEventBox); + _socialBox.Add(_discordEventBox); + _socialBox.Add(_twitterEventBox); + + _changelogEventBox.Add(_changelogLinkLabel); + + _leftBox.Add(_logoBox); + _leftBox.Add(_versionLabel); + _leftBox.Add(_changelogEventBox); + _leftBox.Add(_disclaimerLabel); + _leftBox.Add(_amiiboApiLink); + _leftBox.Add(_socialBox); + + _contributorsEventBox.Add(_contributorsLinkLabel); + _patreonNamesScrolled.Add(_patreonNamesText); + + _rightBox.Add(_aboutLabel); + _rightBox.Add(_aboutDescriptionLabel); + _rightBox.Add(_createdByLabel); + _rightBox.Add(_createdByText); + _rightBox.Add(_contributorsEventBox); + _rightBox.Add(_patreonNamesLabel); + _rightBox.Add(_patreonNamesScrolled); + + _mainBox.Add(_leftBox); + _mainBox.Add(_separator); + _mainBox.Add(_rightBox); + + Add(_mainBox); + + ShowAll(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/AboutWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/AboutWindow.cs new file mode 100644 index 00000000..f4bb533c --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/AboutWindow.cs @@ -0,0 +1,85 @@ +using Gtk; +using Ryujinx.Common.Utilities; +using Ryujinx.UI.Common.Helper; +using System.Net.Http; +using System.Net.NetworkInformation; +using System.Reflection; +using System.Threading.Tasks; + +namespace Ryujinx.UI.Windows +{ + public partial class AboutWindow : Window + { + public AboutWindow() : base($"Ryujinx {Program.Version} - About") + { + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(OpenHelper)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + InitializeComponent(); + + _ = DownloadPatronsJson(); + } + + private async Task DownloadPatronsJson() + { + if (!NetworkInterface.GetIsNetworkAvailable()) + { + _patreonNamesText.Buffer.Text = "Connection Error."; + } + + HttpClient httpClient = new(); + + try + { + string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/"); + + _patreonNamesText.Buffer.Text = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray)); + } + catch + { + _patreonNamesText.Buffer.Text = "API Error."; + } + } + + // + // Events + // + private void RyujinxButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://ryujinx.org"); + } + + private void AmiiboApiButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://amiiboapi.com"); + } + + private void PatreonButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://www.patreon.com/ryujinx"); + } + + private void GitHubButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx"); + } + + private void DiscordButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://discordapp.com/invite/N2FmfVc"); + } + + private void TwitterButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://twitter.com/RyujinxEmu"); + } + + private void ContributorsButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a"); + } + + private void ChangelogButton_Pressed(object sender, ButtonPressEventArgs args) + { + OpenHelper.OpenUrl("https://github.com/Ryujinx/Ryujinx/wiki/Changelog#ryujinx-changelog"); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.Designer.cs b/src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.Designer.cs new file mode 100644 index 00000000..3bf73318 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.Designer.cs @@ -0,0 +1,190 @@ +using Gtk; + +namespace Ryujinx.UI.Windows +{ + public partial class AmiiboWindow : Window + { + private Box _mainBox; + private ButtonBox _buttonBox; + private Button _scanButton; + private Button _cancelButton; + private CheckButton _randomUuidCheckBox; + private Box _amiiboBox; + private Box _amiiboHeadBox; + private Box _amiiboSeriesBox; + private Label _amiiboSeriesLabel; + private ComboBoxText _amiiboSeriesComboBox; + private Box _amiiboCharsBox; + private Label _amiiboCharsLabel; + private ComboBoxText _amiiboCharsComboBox; + private CheckButton _showAllCheckBox; + private Image _amiiboImage; + private Label _gameUsageLabel; + + private void InitializeComponent() + { + // + // AmiiboWindow + // + CanFocus = false; + Resizable = false; + Modal = true; + WindowPosition = WindowPosition.Center; + DefaultWidth = 600; + DefaultHeight = 470; + TypeHint = Gdk.WindowTypeHint.Dialog; + + // + // _mainBox + // + _mainBox = new Box(Orientation.Vertical, 2); + + // + // _buttonBox + // + _buttonBox = new ButtonBox(Orientation.Horizontal) + { + Margin = 20, + LayoutStyle = ButtonBoxStyle.End, + }; + + // + // _scanButton + // + _scanButton = new Button() + { + Label = "Scan It!", + CanFocus = true, + ReceivesDefault = true, + MarginStart = 10, + }; + _scanButton.Clicked += ScanButton_Pressed; + + // + // _randomUuidCheckBox + // + _randomUuidCheckBox = new CheckButton() + { + Label = "Hack: Use Random Tag Uuid", + TooltipText = "This allows multiple scans of a single Amiibo.\n(used in The Legend of Zelda: Breath of the Wild)", + }; + + // + // _cancelButton + // + _cancelButton = new Button() + { + Label = "Cancel", + CanFocus = true, + ReceivesDefault = true, + MarginStart = 10, + }; + _cancelButton.Clicked += CancelButton_Pressed; + + // + // _amiiboBox + // + _amiiboBox = new Box(Orientation.Vertical, 0); + + // + // _amiiboHeadBox + // + _amiiboHeadBox = new Box(Orientation.Horizontal, 0) + { + Margin = 20, + Hexpand = true, + }; + + // + // _amiiboSeriesBox + // + _amiiboSeriesBox = new Box(Orientation.Horizontal, 0) + { + Hexpand = true, + }; + + // + // _amiiboSeriesLabel + // + _amiiboSeriesLabel = new Label("Amiibo Series:"); + + // + // _amiiboSeriesComboBox + // + _amiiboSeriesComboBox = new ComboBoxText(); + + // + // _amiiboCharsBox + // + _amiiboCharsBox = new Box(Orientation.Horizontal, 0) + { + Hexpand = true, + }; + + // + // _amiiboCharsLabel + // + _amiiboCharsLabel = new Label("Character:"); + + // + // _amiiboCharsComboBox + // + _amiiboCharsComboBox = new ComboBoxText(); + + // + // _showAllCheckBox + // + _showAllCheckBox = new CheckButton() + { + Label = "Show All Amiibo", + }; + + // + // _amiiboImage + // + _amiiboImage = new Image() + { + HeightRequest = 350, + WidthRequest = 350, + }; + + // + // _gameUsageLabel + // + _gameUsageLabel = new Label("") + { + MarginTop = 20, + }; + + ShowComponent(); + } + + private void ShowComponent() + { + _buttonBox.Add(_showAllCheckBox); + _buttonBox.Add(_randomUuidCheckBox); + _buttonBox.Add(_scanButton); + _buttonBox.Add(_cancelButton); + + _amiiboSeriesBox.Add(_amiiboSeriesLabel); + _amiiboSeriesBox.Add(_amiiboSeriesComboBox); + + _amiiboCharsBox.Add(_amiiboCharsLabel); + _amiiboCharsBox.Add(_amiiboCharsComboBox); + + _amiiboHeadBox.Add(_amiiboSeriesBox); + _amiiboHeadBox.Add(_amiiboCharsBox); + + _amiiboBox.PackStart(_amiiboHeadBox, true, true, 0); + _amiiboBox.PackEnd(_gameUsageLabel, false, false, 0); + _amiiboBox.PackEnd(_amiiboImage, false, false, 0); + + _mainBox.Add(_amiiboBox); + _mainBox.PackEnd(_buttonBox, false, false, 0); + + Add(_mainBox); + + ShowAll(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.cs new file mode 100644 index 00000000..d8c0b0c0 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/AmiiboWindow.cs @@ -0,0 +1,438 @@ +using Gdk; +using Gtk; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Models.Amiibo; +using Ryujinx.UI.Widgets; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Reflection; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Window = Gtk.Window; + +namespace Ryujinx.UI.Windows +{ + public partial class AmiiboWindow : Window + { + private const string DefaultJson = "{ \"amiibo\": [] }"; + + public string AmiiboId { get; private set; } + + public int DeviceId { get; set; } + public string TitleId { get; set; } + public string LastScannedAmiiboId { get; set; } + public bool LastScannedAmiiboShowAll { get; set; } + + public ResponseType Response { get; private set; } + + public bool UseRandomUuid + { + get + { + return _randomUuidCheckBox.Active; + } + } + + private readonly HttpClient _httpClient; + private readonly string _amiiboJsonPath; + + private readonly byte[] _amiiboLogoBytes; + + private List _amiiboList; + + private static readonly AmiiboJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public AmiiboWindow() : base($"Ryujinx {Program.Version} - Amiibo") + { + Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + + InitializeComponent(); + + _httpClient = new HttpClient + { + Timeout = TimeSpan.FromSeconds(30), + }; + + Directory.CreateDirectory(System.IO.Path.Join(AppDataManager.BaseDirPath, "system", "amiibo")); + + _amiiboJsonPath = System.IO.Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json"); + _amiiboList = new List(); + + _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.UI.Common/Resources/Logo_Amiibo.png"); + _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes); + + _scanButton.Sensitive = false; + _randomUuidCheckBox.Sensitive = false; + + _ = LoadContentAsync(); + } + + private static bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson) + { + if (string.IsNullOrEmpty(json)) + { + amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); + + return false; + } + + try + { + amiiboJson = JsonHelper.Deserialize(json, _serializerContext.AmiiboJson); + + return true; + } + catch (JsonException exception) + { + Logger.Error?.Print(LogClass.Application, $"Unable to deserialize amiibo data: {exception}"); + amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); + + return false; + } + } + + private async Task GetMostRecentAmiiboListOrDefaultJson() + { + bool localIsValid = false; + bool remoteIsValid = false; + AmiiboJson amiiboJson = new(); + + try + { + try + { + if (File.Exists(_amiiboJsonPath)) + { + localIsValid = TryGetAmiiboJson(await File.ReadAllTextAsync(_amiiboJsonPath), out amiiboJson); + } + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"Unable to read data from '{_amiiboJsonPath}': {exception}"); + } + + if (!localIsValid || await NeedsUpdate(amiiboJson.LastUpdated)) + { + remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson); + } + } + catch (Exception exception) + { + if (!(localIsValid || remoteIsValid)) + { + Logger.Error?.Print(LogClass.Application, $"Couldn't get valid amiibo data: {exception}"); + + // Neither local or remote files are valid JSON, close window. + ShowInfoDialog(); + Close(); + } + else if (!remoteIsValid) + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't update amiibo data: {exception}"); + + // Only the local file is valid, the local one should be used + // but the user should be warned. + ShowInfoDialog(); + } + } + + return amiiboJson; + } + + private async Task LoadContentAsync() + { + AmiiboJson amiiboJson = await GetMostRecentAmiiboListOrDefaultJson(); + + _amiiboList = amiiboJson.Amiibo.OrderBy(amiibo => amiibo.AmiiboSeries).ToList(); + + if (LastScannedAmiiboShowAll) + { + _showAllCheckBox.Click(); + } + + ParseAmiiboData(); + + _showAllCheckBox.Clicked += ShowAllCheckBox_Clicked; + } + + private void ParseAmiiboData() + { + List comboxItemList = new(); + + for (int i = 0; i < _amiiboList.Count; i++) + { + if (!comboxItemList.Contains(_amiiboList[i].AmiiboSeries)) + { + if (!_showAllCheckBox.Active) + { + foreach (var game in _amiiboList[i].GamesSwitch) + { + if (game != null) + { + if (game.GameId.Contains(TitleId)) + { + comboxItemList.Add(_amiiboList[i].AmiiboSeries); + _amiiboSeriesComboBox.Append(_amiiboList[i].AmiiboSeries, _amiiboList[i].AmiiboSeries); + + break; + } + } + } + } + else + { + comboxItemList.Add(_amiiboList[i].AmiiboSeries); + _amiiboSeriesComboBox.Append(_amiiboList[i].AmiiboSeries, _amiiboList[i].AmiiboSeries); + } + } + } + + _amiiboSeriesComboBox.Changed += SeriesComboBox_Changed; + _amiiboCharsComboBox.Changed += CharacterComboBox_Changed; + + if (LastScannedAmiiboId != "") + { + SelectLastScannedAmiibo(); + } + else + { + _amiiboSeriesComboBox.Active = 0; + } + } + + private void SelectLastScannedAmiibo() + { + bool isSet = _amiiboSeriesComboBox.SetActiveId(_amiiboList.Find(amiibo => amiibo.Head + amiibo.Tail == LastScannedAmiiboId).AmiiboSeries); + isSet = _amiiboCharsComboBox.SetActiveId(LastScannedAmiiboId); + + if (isSet == false) + { + _amiiboSeriesComboBox.Active = 0; + } + } + + private async Task NeedsUpdate(DateTime oldLastModified) + { + try + { + HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/")); + + if (response.IsSuccessStatusCode) + { + return response.Content.Headers.LastModified != oldLastModified; + } + } + catch (HttpRequestException exception) + { + Logger.Error?.Print(LogClass.Application, $"Unable to check for amiibo data updates: {exception}"); + } + + return false; + } + + private async Task DownloadAmiiboJson() + { + try + { + HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/"); + + if (response.IsSuccessStatusCode) + { + string amiiboJsonString = await response.Content.ReadAsStringAsync(); + + try + { + using FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough); + dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString)); + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't write amiibo data to file '{_amiiboJsonPath}: {exception}'"); + } + + return amiiboJsonString; + } + + Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}"); + } + catch (HttpRequestException exception) + { + Logger.Error?.Print(LogClass.Application, $"Failed to request amiibo data: {exception}"); + } + + GtkDialog.CreateInfoDialog("Amiibo API", "An error occured while fetching information from the API."); + + return null; + } + + private async Task UpdateAmiiboPreview(string imageUrl) + { + HttpResponseMessage response = await _httpClient.GetAsync(imageUrl); + + if (response.IsSuccessStatusCode) + { + byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync(); + Pixbuf amiiboPreview = new(amiiboPreviewBytes); + + float ratio = Math.Min((float)_amiiboImage.AllocatedWidth / amiiboPreview.Width, + (float)_amiiboImage.AllocatedHeight / amiiboPreview.Height); + + int resizeHeight = (int)(amiiboPreview.Height * ratio); + int resizeWidth = (int)(amiiboPreview.Width * ratio); + + _amiiboImage.Pixbuf = amiiboPreview.ScaleSimple(resizeWidth, resizeHeight, InterpType.Bilinear); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Failed to get amiibo preview. Response status code: {response.StatusCode}"); + } + } + + private static void ShowInfoDialog() + { + GtkDialog.CreateInfoDialog("Amiibo API", "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online."); + } + + // + // Events + // + private void SeriesComboBox_Changed(object sender, EventArgs args) + { + _amiiboCharsComboBox.Changed -= CharacterComboBox_Changed; + + _amiiboCharsComboBox.RemoveAll(); + + List amiiboSortedList = _amiiboList.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeriesComboBox.ActiveId).OrderBy(amiibo => amiibo.Name).ToList(); + + List comboxItemList = new(); + + for (int i = 0; i < amiiboSortedList.Count; i++) + { + if (!comboxItemList.Contains(amiiboSortedList[i].Head + amiiboSortedList[i].Tail)) + { + if (!_showAllCheckBox.Active) + { + foreach (var game in amiiboSortedList[i].GamesSwitch) + { + if (game != null) + { + if (game.GameId.Contains(TitleId)) + { + comboxItemList.Add(amiiboSortedList[i].Head + amiiboSortedList[i].Tail); + _amiiboCharsComboBox.Append(amiiboSortedList[i].Head + amiiboSortedList[i].Tail, amiiboSortedList[i].Name); + + break; + } + } + } + } + else + { + comboxItemList.Add(amiiboSortedList[i].Head + amiiboSortedList[i].Tail); + _amiiboCharsComboBox.Append(amiiboSortedList[i].Head + amiiboSortedList[i].Tail, amiiboSortedList[i].Name); + } + } + } + + _amiiboCharsComboBox.Changed += CharacterComboBox_Changed; + + _amiiboCharsComboBox.Active = 0; + + _scanButton.Sensitive = true; + _randomUuidCheckBox.Sensitive = true; + } + + private void CharacterComboBox_Changed(object sender, EventArgs args) + { + AmiiboId = _amiiboCharsComboBox.ActiveId; + + _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes); + + string imageUrl = _amiiboList.Find(amiibo => amiibo.Head + amiibo.Tail == _amiiboCharsComboBox.ActiveId).Image; + + var usageStringBuilder = new StringBuilder(); + + for (int i = 0; i < _amiiboList.Count; i++) + { + if (_amiiboList[i].Head + _amiiboList[i].Tail == _amiiboCharsComboBox.ActiveId) + { + bool writable = false; + + foreach (var item in _amiiboList[i].GamesSwitch) + { + if (item.GameId.Contains(TitleId)) + { + foreach (AmiiboApiUsage usageItem in item.AmiiboUsage) + { + usageStringBuilder.Append(Environment.NewLine); + usageStringBuilder.Append($"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}"); + + writable = usageItem.Write; + } + } + } + + if (usageStringBuilder.Length == 0) + { + usageStringBuilder.Append("Unknown."); + } + + _gameUsageLabel.Text = $"Usage{(writable ? " (Writable)" : "")} : {usageStringBuilder}"; + } + } + + _ = UpdateAmiiboPreview(imageUrl); + } + + private void ShowAllCheckBox_Clicked(object sender, EventArgs e) + { + _amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes); + + _amiiboSeriesComboBox.Changed -= SeriesComboBox_Changed; + _amiiboCharsComboBox.Changed -= CharacterComboBox_Changed; + + _amiiboSeriesComboBox.RemoveAll(); + _amiiboCharsComboBox.RemoveAll(); + + _scanButton.Sensitive = false; + _randomUuidCheckBox.Sensitive = false; + + new Task(ParseAmiiboData).Start(); + } + + private void ScanButton_Pressed(object sender, EventArgs args) + { + LastScannedAmiiboShowAll = _showAllCheckBox.Active; + + Response = ResponseType.Ok; + + Close(); + } + + private void CancelButton_Pressed(object sender, EventArgs args) + { + AmiiboId = ""; + LastScannedAmiiboId = ""; + LastScannedAmiiboShowAll = false; + + Response = ResponseType.Cancel; + + Close(); + } + + protected override void Dispose(bool disposing) + { + _httpClient.Dispose(); + + base.Dispose(disposing); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs new file mode 100644 index 00000000..fcd960df --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs @@ -0,0 +1,298 @@ +using Gtk; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.FileSystem; +using Ryujinx.UI.Common.Configuration; +using SkiaSharp; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Ryujinx.UI.Windows +{ + public class AvatarWindow : Window + { + public byte[] SelectedProfileImage; + public bool NewUser; + + private static readonly Dictionary _avatarDict = new(); + + private readonly ListStore _listStore; + private readonly IconView _iconView; + private readonly Button _setBackgroungColorButton; + private Gdk.RGBA _backgroundColor; + + public AvatarWindow() : base($"Ryujinx {Program.Version} - Manage Accounts - Avatar") + { + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + + CanFocus = false; + Resizable = false; + Modal = true; + TypeHint = Gdk.WindowTypeHint.Dialog; + + SetDefaultSize(740, 400); + SetPosition(WindowPosition.Center); + + Box vbox = new(Orientation.Vertical, 0); + Add(vbox); + + ScrolledWindow scrolledWindow = new() + { + ShadowType = ShadowType.EtchedIn, + }; + scrolledWindow.SetPolicy(PolicyType.Automatic, PolicyType.Automatic); + + Box hbox = new(Orientation.Horizontal, 0); + + Button chooseButton = new() + { + Label = "Choose", + CanFocus = true, + ReceivesDefault = true, + }; + chooseButton.Clicked += ChooseButton_Pressed; + + _setBackgroungColorButton = new Button() + { + Label = "Set Background Color", + CanFocus = true, + }; + _setBackgroungColorButton.Clicked += SetBackgroungColorButton_Pressed; + + _backgroundColor.Red = 1; + _backgroundColor.Green = 1; + _backgroundColor.Blue = 1; + _backgroundColor.Alpha = 1; + + Button closeButton = new() + { + Label = "Close", + CanFocus = true, + }; + closeButton.Clicked += CloseButton_Pressed; + + vbox.PackStart(scrolledWindow, true, true, 0); + hbox.PackStart(chooseButton, true, true, 0); + hbox.PackStart(_setBackgroungColorButton, true, true, 0); + hbox.PackStart(closeButton, true, true, 0); + vbox.PackStart(hbox, false, false, 0); + + _listStore = new ListStore(typeof(string), typeof(Gdk.Pixbuf)); + _listStore.SetSortColumnId(0, SortType.Ascending); + + _iconView = new IconView(_listStore) + { + ItemWidth = 64, + ItemPadding = 10, + PixbufColumn = 1, + }; + + _iconView.SelectionChanged += IconView_SelectionChanged; + + scrolledWindow.Add(_iconView); + + _iconView.GrabFocus(); + + ProcessAvatars(); + + ShowAll(); + } + + public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem) + { + if (_avatarDict.Count > 0) + { + return; + } + + string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem, NcaContentType.Data); + string avatarPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath); + + if (!string.IsNullOrWhiteSpace(avatarPath)) + { + using IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open); + + Nca nca = new(virtualFileSystem.KeySet, ncaFileStream); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + foreach (var item in romfs.EnumerateEntries()) + { + // TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy. + + if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") && item.FullPath.Contains("szs")) + { + using var file = new UniqueRef(); + + romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + using MemoryStream streamPng = MemoryStreamManager.Shared.GetStream(); + file.Get.AsStream().CopyTo(stream); + + stream.Position = 0; + + using var avatarImage = new SKBitmap(new SKImageInfo(256, 256, SKColorType.Rgba8888)); + var data = DecompressYaz0(stream); + Marshal.Copy(data, 0, avatarImage.GetPixels(), data.Length); + + avatarImage.Encode(streamPng, SKEncodedImageFormat.Png, 80); + + _avatarDict.Add(item.FullPath, streamPng.ToArray()); + } + } + } + } + + private void ProcessAvatars() + { + _listStore.Clear(); + + foreach (var avatar in _avatarDict) + { + _listStore.AppendValues(avatar.Key, new Gdk.Pixbuf(ProcessImage(avatar.Value), 96, 96)); + } + + _iconView.SelectPath(new TreePath(new[] { 0 })); + } + + private byte[] ProcessImage(byte[] data) + { + using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream(); + + using var avatarImage = SKBitmap.Decode(data); + using var surface = SKSurface.Create(avatarImage.Info); + + var background = new SKColor( + (byte)(_backgroundColor.Red * 255), + (byte)(_backgroundColor.Green * 255), + (byte)(_backgroundColor.Blue * 255), + (byte)(_backgroundColor.Alpha * 255) + ); + var canvas = surface.Canvas; + canvas.Clear(background); + canvas.DrawBitmap(avatarImage, new SKPoint()); + + surface.Flush(); + using var snapshot = surface.Snapshot(); + using var encoded = snapshot.Encode(SKEncodedImageFormat.Jpeg, 80); + encoded.SaveTo(streamJpg); + + return streamJpg.ToArray(); + } + + private void CloseButton_Pressed(object sender, EventArgs e) + { + SelectedProfileImage = null; + + Close(); + } + + private void IconView_SelectionChanged(object sender, EventArgs e) + { + if (_iconView.SelectedItems.Length > 0) + { + _listStore.GetIter(out TreeIter iter, _iconView.SelectedItems[0]); + + SelectedProfileImage = ProcessImage(_avatarDict[(string)_listStore.GetValue(iter, 0)]); + } + } + + private void SetBackgroungColorButton_Pressed(object sender, EventArgs e) + { + using ColorChooserDialog colorChooserDialog = new("Set Background Color", this); + + colorChooserDialog.UseAlpha = false; + colorChooserDialog.Rgba = _backgroundColor; + + if (colorChooserDialog.Run() == (int)ResponseType.Ok) + { + _backgroundColor = colorChooserDialog.Rgba; + + ProcessAvatars(); + } + + colorChooserDialog.Hide(); + } + + private void ChooseButton_Pressed(object sender, EventArgs e) + { + Close(); + } + + private static byte[] DecompressYaz0(Stream stream) + { + using BinaryReader reader = new(stream); + + reader.ReadInt32(); // Magic + + uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32()); + + reader.ReadInt64(); // Padding + + byte[] input = new byte[stream.Length - stream.Position]; + stream.ReadExactly(input, 0, input.Length); + + long inputOffset = 0; + + byte[] output = new byte[decodedLength]; + long outputOffset = 0; + + ushort mask = 0; + byte header = 0; + + while (outputOffset < decodedLength) + { + if ((mask >>= 1) == 0) + { + header = input[inputOffset++]; + mask = 0x80; + } + + if ((header & mask) > 0) + { + if (outputOffset == output.Length) + { + break; + } + + output[outputOffset++] = input[inputOffset++]; + } + else + { + byte byte1 = input[inputOffset++]; + byte byte2 = input[inputOffset++]; + + int dist = ((byte1 & 0xF) << 8) | byte2; + int position = (int)outputOffset - (dist + 1); + + int length = byte1 >> 4; + if (length == 0) + { + length = input[inputOffset++] + 0x12; + } + else + { + length += 2; + } + + while (length-- > 0) + { + output[outputOffset++] = output[position++]; + } + } + } + + return output; + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs new file mode 100644 index 00000000..d9f01918 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.cs @@ -0,0 +1,163 @@ +using Gtk; +using LibHac.Tools.FsSystem; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common.Configuration; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using GUI = Gtk.Builder.ObjectAttribute; + +namespace Ryujinx.UI.Windows +{ + public class CheatWindow : Window + { + private readonly string _enabledCheatsPath; + private readonly bool _noCheatsFound; + +#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier + [GUI] Label _baseTitleInfoLabel; + [GUI] TextView _buildIdTextView; + [GUI] TreeView _cheatTreeView; + [GUI] Button _saveButton; +#pragma warning restore CS0649, IDE0044 + + public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : this(new Builder("Ryujinx.Gtk3.UI.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName, titlePath) { } + + private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow")) + { + builder.Autoconnect(this); + + IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks + ? IntegrityCheckLevel.ErrorOnInvalid + : IntegrityCheckLevel.None; + + _baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]"; + _buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetBuildId(virtualFileSystem, checkLevel, titlePath)}"; + + string modsBasePath = ModLoader.GetModsBasePath(); + string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId.ToString("X16")); + + _enabledCheatsPath = System.IO.Path.Combine(titleModsPath, "cheats", "enabled.txt"); + + _cheatTreeView.Model = new TreeStore(typeof(bool), typeof(string), typeof(string), typeof(string)); + + CellRendererToggle enableToggle = new(); + enableToggle.Toggled += (sender, args) => + { + _cheatTreeView.Model.GetIter(out TreeIter treeIter, new TreePath(args.Path)); + bool newValue = !(bool)_cheatTreeView.Model.GetValue(treeIter, 0); + _cheatTreeView.Model.SetValue(treeIter, 0, newValue); + + if (_cheatTreeView.Model.IterChildren(out TreeIter childIter, treeIter)) + { + do + { + _cheatTreeView.Model.SetValue(childIter, 0, newValue); + } + while (_cheatTreeView.Model.IterNext(ref childIter)); + } + }; + + _cheatTreeView.AppendColumn("Enabled", enableToggle, "active", 0); + _cheatTreeView.AppendColumn("Name", new CellRendererText(), "text", 1); + _cheatTreeView.AppendColumn("Path", new CellRendererText(), "text", 2); + + var buildIdColumn = _cheatTreeView.AppendColumn("Build Id", new CellRendererText(), "text", 3); + buildIdColumn.Visible = false; + + string[] enabled = Array.Empty(); + + if (File.Exists(_enabledCheatsPath)) + { + enabled = File.ReadAllLines(_enabledCheatsPath); + } + + int cheatAdded = 0; + + var mods = new ModLoader.ModCache(); + + ModLoader.QueryContentsDir(mods, new DirectoryInfo(System.IO.Path.Combine(modsBasePath, "contents")), titleId); + + string currentCheatFile = string.Empty; + string buildId = string.Empty; + TreeIter parentIter = default; + + foreach (var cheat in mods.Cheats) + { + if (cheat.Path.FullName != currentCheatFile) + { + currentCheatFile = cheat.Path.FullName; + string parentPath = currentCheatFile.Replace(titleModsPath, ""); + + buildId = System.IO.Path.GetFileNameWithoutExtension(currentCheatFile).ToUpper(); + parentIter = ((TreeStore)_cheatTreeView.Model).AppendValues(false, buildId, parentPath, ""); + } + + string cleanName = cheat.Name[1..^7]; + ((TreeStore)_cheatTreeView.Model).AppendValues(parentIter, enabled.Contains($"{buildId}-{cheat.Name}"), cleanName, "", buildId); + + cheatAdded++; + } + + if (cheatAdded == 0) + { + ((TreeStore)_cheatTreeView.Model).AppendValues(false, "No Cheats Found", "", ""); + _cheatTreeView.GetColumn(0).Visible = false; + + _noCheatsFound = true; + + _saveButton.Visible = false; + } + + _cheatTreeView.ExpandAll(); + } + + private void SaveButton_Clicked(object sender, EventArgs args) + { + if (_noCheatsFound) + { + return; + } + + List enabledCheats = new(); + + if (_cheatTreeView.Model.GetIterFirst(out TreeIter parentIter)) + { + do + { + if (_cheatTreeView.Model.IterChildren(out TreeIter childIter, parentIter)) + { + do + { + var enabled = (bool)_cheatTreeView.Model.GetValue(childIter, 0); + + if (enabled) + { + var name = _cheatTreeView.Model.GetValue(childIter, 1).ToString(); + var buildId = _cheatTreeView.Model.GetValue(childIter, 3).ToString(); + + enabledCheats.Add($"{buildId}-<{name} Cheat>"); + } + } + while (_cheatTreeView.Model.IterNext(ref childIter)); + } + } + while (_cheatTreeView.Model.IterNext(ref parentIter)); + } + + Directory.CreateDirectory(System.IO.Path.GetDirectoryName(_enabledCheatsPath)); + + File.WriteAllLines(_enabledCheatsPath, enabledCheats); + + Dispose(); + } + + private void CancelButton_Clicked(object sender, EventArgs args) + { + Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.glade new file mode 100644 index 00000000..9a165f1a --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/CheatWindow.glade @@ -0,0 +1,150 @@ + + + + + + False + Ryujinx - Cheat Manager + 440 + 550 + + + True + False + vertical + + + True + False + vertical + + + True + False + 10 + 10 + Available Cheats + + + False + True + 0 + + + + + True + 10 + center + 10 + False + False + + + False + True + 1 + + + + + True + True + 10 + 10 + in + + + True + False + + + True + True + + + + + + + + + + True + True + 2 + + + + + True + True + 0 + + + + + True + False + + + True + False + 10 + 10 + end + + + Save + True + True + True + 10 + 2 + 2 + + + + True + True + 0 + + + + + Cancel + True + True + True + 10 + 2 + 2 + + + + True + True + 1 + + + + + True + True + 1 + + + + + False + True + 1 + + + + + + + + + diff --git a/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs new file mode 100644 index 00000000..d0b8266f --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.cs @@ -0,0 +1,1232 @@ +using Gtk; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.Input; +using Ryujinx.Input.Assigner; +using Ryujinx.Input.GTK3; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Helper; +using Ryujinx.UI.Widgets; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text.Json; +using System.Threading; +using Button = Ryujinx.Input.Button; +using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; +using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; +using GUI = Gtk.Builder.ObjectAttribute; +using Key = Ryujinx.Common.Configuration.Hid.Key; + +namespace Ryujinx.UI.Windows +{ + public class ControllerWindow : Window + { + private readonly PlayerIndex _playerIndex; + private readonly InputConfig _inputConfig; + + private bool _isWaitingForInput; + +#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier + [GUI] Adjustment _controllerStrongRumble; + [GUI] Adjustment _controllerWeakRumble; + [GUI] Adjustment _controllerDeadzoneLeft; + [GUI] Adjustment _controllerDeadzoneRight; + [GUI] Adjustment _controllerRangeLeft; + [GUI] Adjustment _controllerRangeRight; + [GUI] Adjustment _controllerTriggerThreshold; + [GUI] Adjustment _slotNumber; + [GUI] Adjustment _altSlotNumber; + [GUI] Adjustment _sensitivity; + [GUI] Adjustment _gyroDeadzone; + [GUI] CheckButton _enableMotion; + [GUI] CheckButton _enableCemuHook; + [GUI] CheckButton _mirrorInput; + [GUI] Entry _dsuServerHost; + [GUI] Entry _dsuServerPort; + [GUI] ComboBoxText _inputDevice; + [GUI] ComboBoxText _profile; + [GUI] Box _settingsBox; + [GUI] Box _motionAltBox; + [GUI] Box _motionBox; + [GUI] Box _dsuServerHostBox; + [GUI] Box _dsuServerPortBox; + [GUI] Box _motionControllerSlot; + [GUI] Grid _leftStickKeyboard; + [GUI] Grid _leftStickController; + [GUI] Box _deadZoneLeftBox; + [GUI] Box _rangeLeftBox; + [GUI] Grid _rightStickKeyboard; + [GUI] Grid _rightStickController; + [GUI] Box _deadZoneRightBox; + [GUI] Box _rangeRightBox; + [GUI] Grid _leftSideTriggerBox; + [GUI] Grid _rightSideTriggerBox; + [GUI] Box _triggerThresholdBox; + [GUI] ComboBoxText _controllerType; + [GUI] ToggleButton _lStick; + [GUI] CheckButton _invertLStickX; + [GUI] CheckButton _invertLStickY; + [GUI] CheckButton _rotateL90CW; + [GUI] ToggleButton _lStickUp; + [GUI] ToggleButton _lStickDown; + [GUI] ToggleButton _lStickLeft; + [GUI] ToggleButton _lStickRight; + [GUI] ToggleButton _lStickButton; + [GUI] ToggleButton _dpadUp; + [GUI] ToggleButton _dpadDown; + [GUI] ToggleButton _dpadLeft; + [GUI] ToggleButton _dpadRight; + [GUI] ToggleButton _minus; + [GUI] ToggleButton _l; + [GUI] ToggleButton _zL; + [GUI] ToggleButton _rStick; + [GUI] CheckButton _invertRStickX; + [GUI] CheckButton _invertRStickY; + [GUI] CheckButton _rotateR90CW; + [GUI] ToggleButton _rStickUp; + [GUI] ToggleButton _rStickDown; + [GUI] ToggleButton _rStickLeft; + [GUI] ToggleButton _rStickRight; + [GUI] ToggleButton _rStickButton; + [GUI] ToggleButton _a; + [GUI] ToggleButton _b; + [GUI] ToggleButton _x; + [GUI] ToggleButton _y; + [GUI] ToggleButton _plus; + [GUI] ToggleButton _r; + [GUI] ToggleButton _zR; + [GUI] ToggleButton _lSl; + [GUI] ToggleButton _lSr; + [GUI] ToggleButton _rSl; + [GUI] ToggleButton _rSr; + [GUI] Image _controllerImage; + [GUI] CheckButton _enableRumble; + [GUI] Box _rumbleBox; +#pragma warning restore CS0649, IDE0044 + + private readonly MainWindow _mainWindow; + private readonly IGamepadDriver _gtk3KeyboardDriver; + private IGamepad _selectedGamepad; + private bool _mousePressed; + private bool _middleMousePressed; + + private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.Gtk3.UI.Windows.ControllerWindow.glade"), controllerId) { } + + private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin")) + { + _mainWindow = mainWindow; + _selectedGamepad = null; + + // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused... + _gtk3KeyboardDriver = new GTK3KeyboardDriver(this); + + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + + builder.Autoconnect(this); + + _playerIndex = controllerId; + _inputConfig = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerIndex); + + Title = $"Ryujinx - Controller Settings - {_playerIndex}"; + + if (_playerIndex == PlayerIndex.Handheld) + { + _controllerType.Append(ControllerType.Handheld.ToString(), "Handheld"); + _controllerType.Sensitive = false; + } + else + { + _controllerType.Append(ControllerType.ProController.ToString(), "Pro Controller"); + _controllerType.Append(ControllerType.JoyconPair.ToString(), "Joycon Pair"); + _controllerType.Append(ControllerType.JoyconLeft.ToString(), "Joycon Left"); + _controllerType.Append(ControllerType.JoyconRight.ToString(), "Joycon Right"); + } + + _controllerType.Active = 0; // Set initial value to first in list. + + // Bind Events. + _lStick.Clicked += ButtonForStick_Pressed; + _lStickUp.Clicked += Button_Pressed; + _lStickDown.Clicked += Button_Pressed; + _lStickLeft.Clicked += Button_Pressed; + _lStickRight.Clicked += Button_Pressed; + _lStickButton.Clicked += Button_Pressed; + _dpadUp.Clicked += Button_Pressed; + _dpadDown.Clicked += Button_Pressed; + _dpadLeft.Clicked += Button_Pressed; + _dpadRight.Clicked += Button_Pressed; + _minus.Clicked += Button_Pressed; + _l.Clicked += Button_Pressed; + _zL.Clicked += Button_Pressed; + _lSl.Clicked += Button_Pressed; + _lSr.Clicked += Button_Pressed; + _rStick.Clicked += ButtonForStick_Pressed; + _rStickUp.Clicked += Button_Pressed; + _rStickDown.Clicked += Button_Pressed; + _rStickLeft.Clicked += Button_Pressed; + _rStickRight.Clicked += Button_Pressed; + _rStickButton.Clicked += Button_Pressed; + _a.Clicked += Button_Pressed; + _b.Clicked += Button_Pressed; + _x.Clicked += Button_Pressed; + _y.Clicked += Button_Pressed; + _plus.Clicked += Button_Pressed; + _r.Clicked += Button_Pressed; + _zR.Clicked += Button_Pressed; + _rSl.Clicked += Button_Pressed; + _rSr.Clicked += Button_Pressed; + _enableCemuHook.Clicked += CemuHookCheckButtonPressed; + + // Setup current values. + UpdateInputDeviceList(); + SetAvailableOptions(); + + ClearValues(); + if (_inputDevice.ActiveId != null) + { + SetCurrentValues(); + } + + mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; + mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; + + _mainWindow.RendererWidget?.NpadManager.BlockInputUpdates(); + } + + private void CemuHookCheckButtonPressed(object sender, EventArgs e) + { + UpdateCemuHookSpecificFieldsVisibility(); + } + + private void HandleOnGamepadDisconnected(string id) + { + Application.Invoke(delegate + { + UpdateInputDeviceList(); + }); + } + + private void HandleOnGamepadConnected(string id) + { + Application.Invoke(delegate + { + UpdateInputDeviceList(); + }); + } + + protected override void OnDestroyed() + { + _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; + _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; + + _mainWindow.RendererWidget?.NpadManager.UnblockInputUpdates(); + + _selectedGamepad?.Dispose(); + + _gtk3KeyboardDriver.Dispose(); + } + + private static string GetShortGamepadName(string str) + { + const string ShrinkChars = "..."; + const int MaxSize = 50; + + if (str.Length > MaxSize) + { + return $"{str.AsSpan(0, MaxSize - ShrinkChars.Length)}{ShrinkChars}"; + } + + return str; + } + + private void UpdateInputDeviceList() + { + _inputDevice.RemoveAll(); + _inputDevice.Append("disabled", "Disabled"); + _inputDevice.SetActiveId("disabled"); + + foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds) + { + IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); + + if (gamepad != null) + { + _inputDevice.Append($"keyboard/{id}", GetShortGamepadName($"{gamepad.Name} ({id})")); + + gamepad.Dispose(); + } + } + + foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds) + { + IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); + + if (gamepad != null) + { + _inputDevice.Append($"controller/{id}", GetShortGamepadName($"{gamepad.Name} ({id})")); + + gamepad.Dispose(); + } + } + + switch (_inputConfig) + { + case StandardKeyboardInputConfig keyboard: + _inputDevice.SetActiveId($"keyboard/{keyboard.Id}"); + break; + case StandardControllerInputConfig controller: + _inputDevice.SetActiveId($"controller/{controller.Id}"); + break; + } + } + + private void UpdateCemuHookSpecificFieldsVisibility() + { + if (_enableCemuHook.Active) + { + _dsuServerHostBox.Show(); + _dsuServerPortBox.Show(); + _motionControllerSlot.Show(); + _motionAltBox.Show(); + _mirrorInput.Show(); + } + else + { + _dsuServerHostBox.Hide(); + _dsuServerPortBox.Hide(); + _motionControllerSlot.Hide(); + _motionAltBox.Hide(); + _mirrorInput.Hide(); + } + } + + private void SetAvailableOptions() + { + if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("keyboard")) + { + ShowAll(); + _leftStickController.Hide(); + _rightStickController.Hide(); + _deadZoneLeftBox.Hide(); + _deadZoneRightBox.Hide(); + _rangeLeftBox.Hide(); + _rangeRightBox.Hide(); + _triggerThresholdBox.Hide(); + _motionBox.Hide(); + _rumbleBox.Hide(); + } + else if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("controller")) + { + ShowAll(); + _leftStickKeyboard.Hide(); + _rightStickKeyboard.Hide(); + + UpdateCemuHookSpecificFieldsVisibility(); + } + else + { + _settingsBox.Hide(); + } + + ClearValues(); + } + + private void SetCurrentValues() + { + SetControllerSpecificFields(); + + SetProfiles(); + + if (_inputDevice.ActiveId.StartsWith("keyboard") && _inputConfig is StandardKeyboardInputConfig) + { + SetValues(_inputConfig); + } + else if (_inputDevice.ActiveId.StartsWith("controller") && _inputConfig is StandardControllerInputConfig) + { + SetValues(_inputConfig); + } + } + + private void SetControllerSpecificFields() + { + _leftSideTriggerBox.Hide(); + _rightSideTriggerBox.Hide(); + _motionAltBox.Hide(); + + switch (_controllerType.ActiveId) + { + case "JoyconLeft": + _leftSideTriggerBox.Show(); + break; + case "JoyconRight": + _rightSideTriggerBox.Show(); + break; + case "JoyconPair": + _motionAltBox.Show(); + break; + } + + if (!OperatingSystem.IsMacOS()) + { + _controllerImage.Pixbuf = _controllerType.ActiveId switch + { + "ProController" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_ProCon.svg", 400, 400), + "JoyconLeft" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConLeft.svg", 400, 500), + "JoyconRight" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConRight.svg", 400, 500), + _ => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConPair.svg", 400, 500), + }; + } + } + + private void ClearValues() + { + _lStick.Label = "Unbound"; + _lStickUp.Label = "Unbound"; + _lStickDown.Label = "Unbound"; + _lStickLeft.Label = "Unbound"; + _lStickRight.Label = "Unbound"; + _lStickButton.Label = "Unbound"; + _dpadUp.Label = "Unbound"; + _dpadDown.Label = "Unbound"; + _dpadLeft.Label = "Unbound"; + _dpadRight.Label = "Unbound"; + _minus.Label = "Unbound"; + _l.Label = "Unbound"; + _zL.Label = "Unbound"; + _lSl.Label = "Unbound"; + _lSr.Label = "Unbound"; + _rStick.Label = "Unbound"; + _rStickUp.Label = "Unbound"; + _rStickDown.Label = "Unbound"; + _rStickLeft.Label = "Unbound"; + _rStickRight.Label = "Unbound"; + _rStickButton.Label = "Unbound"; + _a.Label = "Unbound"; + _b.Label = "Unbound"; + _x.Label = "Unbound"; + _y.Label = "Unbound"; + _plus.Label = "Unbound"; + _r.Label = "Unbound"; + _zR.Label = "Unbound"; + _rSl.Label = "Unbound"; + _rSr.Label = "Unbound"; + _controllerStrongRumble.Value = 1; + _controllerWeakRumble.Value = 1; + _controllerDeadzoneLeft.Value = 0; + _controllerDeadzoneRight.Value = 0; + _controllerRangeLeft.Value = 1; + _controllerRangeRight.Value = 1; + _controllerTriggerThreshold.Value = 0; + _mirrorInput.Active = false; + _enableMotion.Active = false; + _enableCemuHook.Active = false; + _slotNumber.Value = 0; + _altSlotNumber.Value = 0; + _sensitivity.Value = 100; + _gyroDeadzone.Value = 1; + _dsuServerHost.Buffer.Text = ""; + _dsuServerPort.Buffer.Text = ""; + _enableRumble.Active = false; + } + + private void SetValues(InputConfig config) + { + switch (config) + { + case StandardKeyboardInputConfig keyboardConfig: + if (!_controllerType.SetActiveId(keyboardConfig.ControllerType.ToString())) + { + _controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld + ? ControllerType.Handheld.ToString() + : ControllerType.ProController.ToString()); + } + + _lStickUp.Label = keyboardConfig.LeftJoyconStick.StickUp.ToString(); + _lStickDown.Label = keyboardConfig.LeftJoyconStick.StickDown.ToString(); + _lStickLeft.Label = keyboardConfig.LeftJoyconStick.StickLeft.ToString(); + _lStickRight.Label = keyboardConfig.LeftJoyconStick.StickRight.ToString(); + _lStickButton.Label = keyboardConfig.LeftJoyconStick.StickButton.ToString(); + _dpadUp.Label = keyboardConfig.LeftJoycon.DpadUp.ToString(); + _dpadDown.Label = keyboardConfig.LeftJoycon.DpadDown.ToString(); + _dpadLeft.Label = keyboardConfig.LeftJoycon.DpadLeft.ToString(); + _dpadRight.Label = keyboardConfig.LeftJoycon.DpadRight.ToString(); + _minus.Label = keyboardConfig.LeftJoycon.ButtonMinus.ToString(); + _l.Label = keyboardConfig.LeftJoycon.ButtonL.ToString(); + _zL.Label = keyboardConfig.LeftJoycon.ButtonZl.ToString(); + _lSl.Label = keyboardConfig.LeftJoycon.ButtonSl.ToString(); + _lSr.Label = keyboardConfig.LeftJoycon.ButtonSr.ToString(); + _rStickUp.Label = keyboardConfig.RightJoyconStick.StickUp.ToString(); + _rStickDown.Label = keyboardConfig.RightJoyconStick.StickDown.ToString(); + _rStickLeft.Label = keyboardConfig.RightJoyconStick.StickLeft.ToString(); + _rStickRight.Label = keyboardConfig.RightJoyconStick.StickRight.ToString(); + _rStickButton.Label = keyboardConfig.RightJoyconStick.StickButton.ToString(); + _a.Label = keyboardConfig.RightJoycon.ButtonA.ToString(); + _b.Label = keyboardConfig.RightJoycon.ButtonB.ToString(); + _x.Label = keyboardConfig.RightJoycon.ButtonX.ToString(); + _y.Label = keyboardConfig.RightJoycon.ButtonY.ToString(); + _plus.Label = keyboardConfig.RightJoycon.ButtonPlus.ToString(); + _r.Label = keyboardConfig.RightJoycon.ButtonR.ToString(); + _zR.Label = keyboardConfig.RightJoycon.ButtonZr.ToString(); + _rSl.Label = keyboardConfig.RightJoycon.ButtonSl.ToString(); + _rSr.Label = keyboardConfig.RightJoycon.ButtonSr.ToString(); + break; + + case StandardControllerInputConfig controllerConfig: + if (!_controllerType.SetActiveId(controllerConfig.ControllerType.ToString())) + { + _controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld + ? ControllerType.Handheld.ToString() + : ControllerType.ProController.ToString()); + } + + _lStick.Label = controllerConfig.LeftJoyconStick.Joystick.ToString(); + _invertLStickX.Active = controllerConfig.LeftJoyconStick.InvertStickX; + _invertLStickY.Active = controllerConfig.LeftJoyconStick.InvertStickY; + _rotateL90CW.Active = controllerConfig.LeftJoyconStick.Rotate90CW; + _lStickButton.Label = controllerConfig.LeftJoyconStick.StickButton.ToString(); + _dpadUp.Label = controllerConfig.LeftJoycon.DpadUp.ToString(); + _dpadDown.Label = controllerConfig.LeftJoycon.DpadDown.ToString(); + _dpadLeft.Label = controllerConfig.LeftJoycon.DpadLeft.ToString(); + _dpadRight.Label = controllerConfig.LeftJoycon.DpadRight.ToString(); + _minus.Label = controllerConfig.LeftJoycon.ButtonMinus.ToString(); + _l.Label = controllerConfig.LeftJoycon.ButtonL.ToString(); + _zL.Label = controllerConfig.LeftJoycon.ButtonZl.ToString(); + _lSl.Label = controllerConfig.LeftJoycon.ButtonSl.ToString(); + _lSr.Label = controllerConfig.LeftJoycon.ButtonSr.ToString(); + _rStick.Label = controllerConfig.RightJoyconStick.Joystick.ToString(); + _invertRStickX.Active = controllerConfig.RightJoyconStick.InvertStickX; + _invertRStickY.Active = controllerConfig.RightJoyconStick.InvertStickY; + _rotateR90CW.Active = controllerConfig.RightJoyconStick.Rotate90CW; + _rStickButton.Label = controllerConfig.RightJoyconStick.StickButton.ToString(); + _a.Label = controllerConfig.RightJoycon.ButtonA.ToString(); + _b.Label = controllerConfig.RightJoycon.ButtonB.ToString(); + _x.Label = controllerConfig.RightJoycon.ButtonX.ToString(); + _y.Label = controllerConfig.RightJoycon.ButtonY.ToString(); + _plus.Label = controllerConfig.RightJoycon.ButtonPlus.ToString(); + _r.Label = controllerConfig.RightJoycon.ButtonR.ToString(); + _zR.Label = controllerConfig.RightJoycon.ButtonZr.ToString(); + _rSl.Label = controllerConfig.RightJoycon.ButtonSl.ToString(); + _rSr.Label = controllerConfig.RightJoycon.ButtonSr.ToString(); + _controllerStrongRumble.Value = controllerConfig.Rumble.StrongRumble; + _controllerWeakRumble.Value = controllerConfig.Rumble.WeakRumble; + _enableRumble.Active = controllerConfig.Rumble.EnableRumble; + _controllerDeadzoneLeft.Value = controllerConfig.DeadzoneLeft; + _controllerDeadzoneRight.Value = controllerConfig.DeadzoneRight; + _controllerRangeLeft.Value = controllerConfig.RangeLeft; + _controllerRangeRight.Value = controllerConfig.RangeRight; + _controllerTriggerThreshold.Value = controllerConfig.TriggerThreshold; + _sensitivity.Value = controllerConfig.Motion.Sensitivity; + _gyroDeadzone.Value = controllerConfig.Motion.GyroDeadzone; + _enableMotion.Active = controllerConfig.Motion.EnableMotion; + _enableCemuHook.Active = controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook; + + // If both stick ranges are 0 (usually indicative of an outdated profile load) then both sticks will be set to 1.0. + if (_controllerRangeLeft.Value <= 0.0 && _controllerRangeRight.Value <= 0.0) + { + _controllerRangeLeft.Value = 1.0; + _controllerRangeRight.Value = 1.0; + + Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration"); + } + + if (controllerConfig.Motion is CemuHookMotionConfigController cemuHookMotionConfig) + { + _slotNumber.Value = cemuHookMotionConfig.Slot; + _altSlotNumber.Value = cemuHookMotionConfig.AltSlot; + _mirrorInput.Active = cemuHookMotionConfig.MirrorInput; + _dsuServerHost.Buffer.Text = cemuHookMotionConfig.DsuServerHost; + _dsuServerPort.Buffer.Text = cemuHookMotionConfig.DsuServerPort.ToString(); + } + + break; + } + } + + private InputConfig GetValues() + { + if (_inputDevice.ActiveId.StartsWith("keyboard")) + { +#pragma warning disable CA1806, IDE0055 // Disable formatting + Enum.TryParse(_lStickUp.Label, out Key lStickUp); + Enum.TryParse(_lStickDown.Label, out Key lStickDown); + Enum.TryParse(_lStickLeft.Label, out Key lStickLeft); + Enum.TryParse(_lStickRight.Label, out Key lStickRight); + Enum.TryParse(_lStickButton.Label, out Key lStickButton); + Enum.TryParse(_dpadUp.Label, out Key lDPadUp); + Enum.TryParse(_dpadDown.Label, out Key lDPadDown); + Enum.TryParse(_dpadLeft.Label, out Key lDPadLeft); + Enum.TryParse(_dpadRight.Label, out Key lDPadRight); + Enum.TryParse(_minus.Label, out Key lButtonMinus); + Enum.TryParse(_l.Label, out Key lButtonL); + Enum.TryParse(_zL.Label, out Key lButtonZl); + Enum.TryParse(_lSl.Label, out Key lButtonSl); + Enum.TryParse(_lSr.Label, out Key lButtonSr); + + Enum.TryParse(_rStickUp.Label, out Key rStickUp); + Enum.TryParse(_rStickDown.Label, out Key rStickDown); + Enum.TryParse(_rStickLeft.Label, out Key rStickLeft); + Enum.TryParse(_rStickRight.Label, out Key rStickRight); + Enum.TryParse(_rStickButton.Label, out Key rStickButton); + Enum.TryParse(_a.Label, out Key rButtonA); + Enum.TryParse(_b.Label, out Key rButtonB); + Enum.TryParse(_x.Label, out Key rButtonX); + Enum.TryParse(_y.Label, out Key rButtonY); + Enum.TryParse(_plus.Label, out Key rButtonPlus); + Enum.TryParse(_r.Label, out Key rButtonR); + Enum.TryParse(_zR.Label, out Key rButtonZr); + Enum.TryParse(_rSl.Label, out Key rButtonSl); + Enum.TryParse(_rSr.Label, out Key rButtonSr); +#pragma warning restore CA1806, IDE0055 + + return new StandardKeyboardInputConfig + { + Backend = InputBackendType.WindowKeyboard, + Version = InputConfig.CurrentVersion, + Id = _inputDevice.ActiveId.Split("/")[1], + ControllerType = Enum.Parse(_controllerType.ActiveId), + PlayerIndex = _playerIndex, + LeftJoycon = new LeftJoyconCommonConfig + { + ButtonMinus = lButtonMinus, + ButtonL = lButtonL, + ButtonZl = lButtonZl, + ButtonSl = lButtonSl, + ButtonSr = lButtonSr, + DpadUp = lDPadUp, + DpadDown = lDPadDown, + DpadLeft = lDPadLeft, + DpadRight = lDPadRight, + }, + LeftJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = lStickUp, + StickDown = lStickDown, + StickLeft = lStickLeft, + StickRight = lStickRight, + StickButton = lStickButton, + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = rButtonA, + ButtonB = rButtonB, + ButtonX = rButtonX, + ButtonY = rButtonY, + ButtonPlus = rButtonPlus, + ButtonR = rButtonR, + ButtonZr = rButtonZr, + ButtonSl = rButtonSl, + ButtonSr = rButtonSr, + }, + RightJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = rStickUp, + StickDown = rStickDown, + StickLeft = rStickLeft, + StickRight = rStickRight, + StickButton = rStickButton, + }, + }; + } + + if (_inputDevice.ActiveId.StartsWith("controller")) + { +#pragma warning disable CA1806, IDE0055 // Disable formatting + Enum.TryParse(_lStick.Label, out ConfigStickInputId lStick); + Enum.TryParse(_lStickButton.Label, out ConfigGamepadInputId lStickButton); + Enum.TryParse(_minus.Label, out ConfigGamepadInputId lButtonMinus); + Enum.TryParse(_l.Label, out ConfigGamepadInputId lButtonL); + Enum.TryParse(_zL.Label, out ConfigGamepadInputId lButtonZl); + Enum.TryParse(_lSl.Label, out ConfigGamepadInputId lButtonSl); + Enum.TryParse(_lSr.Label, out ConfigGamepadInputId lButtonSr); + Enum.TryParse(_dpadUp.Label, out ConfigGamepadInputId lDPadUp); + Enum.TryParse(_dpadDown.Label, out ConfigGamepadInputId lDPadDown); + Enum.TryParse(_dpadLeft.Label, out ConfigGamepadInputId lDPadLeft); + Enum.TryParse(_dpadRight.Label, out ConfigGamepadInputId lDPadRight); + + Enum.TryParse(_rStick.Label, out ConfigStickInputId rStick); + Enum.TryParse(_rStickButton.Label, out ConfigGamepadInputId rStickButton); + Enum.TryParse(_a.Label, out ConfigGamepadInputId rButtonA); + Enum.TryParse(_b.Label, out ConfigGamepadInputId rButtonB); + Enum.TryParse(_x.Label, out ConfigGamepadInputId rButtonX); + Enum.TryParse(_y.Label, out ConfigGamepadInputId rButtonY); + Enum.TryParse(_plus.Label, out ConfigGamepadInputId rButtonPlus); + Enum.TryParse(_r.Label, out ConfigGamepadInputId rButtonR); + Enum.TryParse(_zR.Label, out ConfigGamepadInputId rButtonZr); + Enum.TryParse(_rSl.Label, out ConfigGamepadInputId rButtonSl); + Enum.TryParse(_rSr.Label, out ConfigGamepadInputId rButtonSr); + + int.TryParse(_dsuServerPort.Buffer.Text, out int port); +#pragma warning restore CA1806, IDE0055 + + MotionConfigController motionConfig; + + if (_enableCemuHook.Active) + { + motionConfig = new CemuHookMotionConfigController + { + MotionBackend = MotionInputBackendType.CemuHook, + EnableMotion = _enableMotion.Active, + Sensitivity = (int)_sensitivity.Value, + GyroDeadzone = _gyroDeadzone.Value, + MirrorInput = _mirrorInput.Active, + Slot = (int)_slotNumber.Value, + AltSlot = (int)_altSlotNumber.Value, + DsuServerHost = _dsuServerHost.Buffer.Text, + DsuServerPort = port, + }; + } + else + { + motionConfig = new StandardMotionConfigController + { + MotionBackend = MotionInputBackendType.GamepadDriver, + EnableMotion = _enableMotion.Active, + Sensitivity = (int)_sensitivity.Value, + GyroDeadzone = _gyroDeadzone.Value, + }; + } + + return new StandardControllerInputConfig + { + Backend = InputBackendType.GamepadSDL2, + Version = InputConfig.CurrentVersion, + Id = _inputDevice.ActiveId.Split("/")[1].Split(" ")[0], + ControllerType = Enum.Parse(_controllerType.ActiveId), + PlayerIndex = _playerIndex, + DeadzoneLeft = (float)_controllerDeadzoneLeft.Value, + DeadzoneRight = (float)_controllerDeadzoneRight.Value, + RangeLeft = (float)_controllerRangeLeft.Value, + RangeRight = (float)_controllerRangeRight.Value, + TriggerThreshold = (float)_controllerTriggerThreshold.Value, + LeftJoycon = new LeftJoyconCommonConfig + { + ButtonMinus = lButtonMinus, + ButtonL = lButtonL, + ButtonZl = lButtonZl, + ButtonSl = lButtonSl, + ButtonSr = lButtonSr, + DpadUp = lDPadUp, + DpadDown = lDPadDown, + DpadLeft = lDPadLeft, + DpadRight = lDPadRight, + }, + LeftJoyconStick = new JoyconConfigControllerStick + { + InvertStickX = _invertLStickX.Active, + Joystick = lStick, + InvertStickY = _invertLStickY.Active, + StickButton = lStickButton, + Rotate90CW = _rotateL90CW.Active, + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = rButtonA, + ButtonB = rButtonB, + ButtonX = rButtonX, + ButtonY = rButtonY, + ButtonPlus = rButtonPlus, + ButtonR = rButtonR, + ButtonZr = rButtonZr, + ButtonSl = rButtonSl, + ButtonSr = rButtonSr, + }, + RightJoyconStick = new JoyconConfigControllerStick + { + InvertStickX = _invertRStickX.Active, + Joystick = rStick, + InvertStickY = _invertRStickY.Active, + StickButton = rStickButton, + Rotate90CW = _rotateR90CW.Active, + }, + Motion = motionConfig, + Rumble = new RumbleConfigController + { + StrongRumble = (float)_controllerStrongRumble.Value, + WeakRumble = (float)_controllerWeakRumble.Value, + EnableRumble = _enableRumble.Active, + }, + }; + } + + if (!_inputDevice.ActiveId.StartsWith("disabled")) + { + GtkDialog.CreateErrorDialog("Invalid data detected in one or more fields; the configuration was not saved."); + } + + return null; + } + + private string GetProfileBasePath() + { + if (_inputDevice.ActiveId.StartsWith("keyboard")) + { + return System.IO.Path.Combine(AppDataManager.ProfilesDirPath, "keyboard"); + } + else if (_inputDevice.ActiveId.StartsWith("controller")) + { + return System.IO.Path.Combine(AppDataManager.ProfilesDirPath, "controller"); + } + + return AppDataManager.ProfilesDirPath; + } + + // + // Events + // + private void InputDevice_Changed(object sender, EventArgs args) + { + SetAvailableOptions(); + SetControllerSpecificFields(); + + _selectedGamepad?.Dispose(); + _selectedGamepad = null; + + if (_inputDevice.ActiveId != null) + { + SetProfiles(); + + string id = GetCurrentGamepadId(); + + if (_inputDevice.ActiveId.StartsWith("keyboard")) + { + if (_inputConfig is StandardKeyboardInputConfig) + { + SetValues(_inputConfig); + } + + if (_mainWindow.InputManager.KeyboardDriver is GTK3KeyboardDriver) + { + // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused... + _selectedGamepad = _gtk3KeyboardDriver.GetGamepad(id); + } + else + { + _selectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); + } + } + else if (_inputDevice.ActiveId.StartsWith("controller")) + { + if (_inputConfig is StandardControllerInputConfig) + { + SetValues(_inputConfig); + } + + _selectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); + } + } + } + + private string GetCurrentGamepadId() + { + if (_inputDevice.ActiveId == null || _inputDevice.ActiveId == "disabled") + { + return null; + } + + return _inputDevice.ActiveId.Split("/")[1].Split(" ")[0]; + } + + private void Controller_Changed(object sender, EventArgs args) + { + SetControllerSpecificFields(); + } + + private IButtonAssigner CreateButtonAssigner(bool forStick) + { + IButtonAssigner assigner; + + if (_inputDevice.ActiveId.StartsWith("keyboard")) + { + assigner = new KeyboardKeyAssigner((IKeyboard)_selectedGamepad); + } + else if (_inputDevice.ActiveId.StartsWith("controller")) + { + assigner = new GamepadButtonAssigner(_selectedGamepad, (float)_controllerTriggerThreshold.Value, forStick); + } + else + { + throw new Exception("Controller not supported"); + } + + return assigner; + } + + private void HandleButtonPressed(ToggleButton button, bool forStick) + { + if (_isWaitingForInput) + { + button.Active = false; + + return; + } + + _mousePressed = false; + + ButtonPressEvent += MouseClick; + + IButtonAssigner assigner = CreateButtonAssigner(forStick); + + _isWaitingForInput = true; + + // Open GTK3 keyboard for cancel operations + IKeyboard keyboard = (IKeyboard)_gtk3KeyboardDriver.GetGamepad("0"); + + Thread inputThread = new(() => + { + assigner.Initialize(); + + while (true) + { + Thread.Sleep(10); + assigner.ReadInput(); + + if (_mousePressed || keyboard.IsPressed(Ryujinx.Input.Key.Escape) || assigner.IsAnyButtonPressed() || assigner.ShouldCancel()) + { + break; + } + } + + string pressedButton = ButtonHelper.ToString(assigner.GetPressedButton() ?? new Button(Input.Key.Unknown)); + + Application.Invoke(delegate + { + if (_middleMousePressed) + { + button.Label = "Unbound"; + } + else if (pressedButton != "") + { + button.Label = pressedButton; + } + + _middleMousePressed = false; + + ButtonPressEvent -= MouseClick; + keyboard.Dispose(); + + button.Active = false; + _isWaitingForInput = false; + }); + }) + { + Name = "GUI.InputThread", + IsBackground = true, + }; + inputThread.Start(); + } + + private void Button_Pressed(object sender, EventArgs args) + { + HandleButtonPressed((ToggleButton)sender, false); + } + + private void ButtonForStick_Pressed(object sender, EventArgs args) + { + HandleButtonPressed((ToggleButton)sender, true); + } + + private void MouseClick(object sender, ButtonPressEventArgs args) + { + _mousePressed = true; + _middleMousePressed = args.Event.Button == 2; + } + + private void SetProfiles() + { + _profile.RemoveAll(); + + string basePath = GetProfileBasePath(); + + if (!Directory.Exists(basePath)) + { + Directory.CreateDirectory(basePath); + } + + if (_inputDevice.ActiveId == null || _inputDevice.ActiveId.Equals("disabled")) + { + _profile.Append("default", "None"); + } + else + { + _profile.Append("default", "Default"); + + foreach (string profile in Directory.GetFiles(basePath, "*.*", SearchOption.AllDirectories)) + { + _profile.Append(System.IO.Path.GetFileName(profile), System.IO.Path.GetFileNameWithoutExtension(profile)); + } + } + + _profile.SetActiveId("default"); + } + + private void ProfileLoad_Activated(object sender, EventArgs args) + { + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); + + if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == null) + { + return; + } + + InputConfig config = null; + int pos = _profile.Active; + + if (_profile.ActiveId == "default") + { + if (_inputDevice.ActiveId.StartsWith("keyboard")) + { + config = new StandardKeyboardInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.WindowKeyboard, + Id = null, + ControllerType = ControllerType.ProController, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = Key.Up, + DpadDown = Key.Down, + DpadLeft = Key.Left, + DpadRight = Key.Right, + ButtonMinus = Key.Minus, + ButtonL = Key.E, + ButtonZl = Key.Q, + ButtonSl = Key.Unbound, + ButtonSr = Key.Unbound, + }, + + LeftJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = Key.W, + StickDown = Key.S, + StickLeft = Key.A, + StickRight = Key.D, + StickButton = Key.F, + }, + + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = Key.Z, + ButtonB = Key.X, + ButtonX = Key.C, + ButtonY = Key.V, + ButtonPlus = Key.Plus, + ButtonR = Key.U, + ButtonZr = Key.O, + ButtonSl = Key.Unbound, + ButtonSr = Key.Unbound, + }, + + RightJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = Key.I, + StickDown = Key.K, + StickLeft = Key.J, + StickRight = Key.L, + StickButton = Key.H, + }, + }; + } + else if (_inputDevice.ActiveId.StartsWith("controller")) + { + bool isNintendoStyle = _inputDevice.ActiveText.Contains("Nintendo"); + + config = new StandardControllerInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.GamepadSDL2, + Id = null, + ControllerType = ControllerType.JoyconPair, + DeadzoneLeft = 0.1f, + DeadzoneRight = 0.1f, + RangeLeft = 1.0f, + RangeRight = 1.0f, + TriggerThreshold = 0.5f, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = ConfigGamepadInputId.DpadUp, + DpadDown = ConfigGamepadInputId.DpadDown, + DpadLeft = ConfigGamepadInputId.DpadLeft, + DpadRight = ConfigGamepadInputId.DpadRight, + ButtonMinus = ConfigGamepadInputId.Minus, + ButtonL = ConfigGamepadInputId.LeftShoulder, + ButtonZl = ConfigGamepadInputId.LeftTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + + LeftJoyconStick = new JoyconConfigControllerStick + { + Joystick = ConfigStickInputId.Left, + StickButton = ConfigGamepadInputId.LeftStick, + InvertStickX = false, + InvertStickY = false, + Rotate90CW = false, + }, + + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B, + ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A, + ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y, + ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X, + ButtonPlus = ConfigGamepadInputId.Plus, + ButtonR = ConfigGamepadInputId.RightShoulder, + ButtonZr = ConfigGamepadInputId.RightTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + + RightJoyconStick = new JoyconConfigControllerStick + { + Joystick = ConfigStickInputId.Right, + StickButton = ConfigGamepadInputId.RightStick, + InvertStickX = false, + InvertStickY = false, + Rotate90CW = false, + }, + + Motion = new StandardMotionConfigController + { + MotionBackend = MotionInputBackendType.GamepadDriver, + EnableMotion = true, + Sensitivity = 100, + GyroDeadzone = 1, + }, + Rumble = new RumbleConfigController + { + StrongRumble = 1f, + WeakRumble = 1f, + EnableRumble = false, + }, + }; + } + } + else + { + string path = System.IO.Path.Combine(GetProfileBasePath(), _profile.ActiveId); + + if (!File.Exists(path)) + { + if (pos >= 0) + { + _profile.Remove(pos); + } + + return; + } + + try + { + config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig); + } + catch (JsonException) { } + } + + SetValues(config); + } + + private void ProfileAdd_Activated(object sender, EventArgs args) + { + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); + + if (_inputDevice.ActiveId == "disabled") + { + return; + } + + InputConfig inputConfig = GetValues(); + ProfileDialog profileDialog = new(); + + if (inputConfig == null) + { + return; + } + + if (profileDialog.Run() == (int)ResponseType.Ok) + { + string path = System.IO.Path.Combine(GetProfileBasePath(), profileDialog.FileName); + string jsonString = JsonHelper.Serialize(inputConfig, _serializerContext.InputConfig); + + File.WriteAllText(path, jsonString); + } + + profileDialog.Dispose(); + + SetProfiles(); + } + + private void ProfileRemove_Activated(object sender, EventArgs args) + { + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); + + if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == "default" || _profile.ActiveId == null) + { + return; + } + + MessageDialog confirmDialog = GtkDialog.CreateConfirmationDialog("Deleting Profile", "This action is irreversible, are you sure you want to continue?"); + + if (confirmDialog.Run() == (int)ResponseType.Yes) + { + string path = System.IO.Path.Combine(GetProfileBasePath(), _profile.ActiveId); + + if (File.Exists(path)) + { + File.Delete(path); + } + + SetProfiles(); + } + } + + private void SaveToggle_Activated(object sender, EventArgs args) + { + InputConfig inputConfig = GetValues(); + + var newConfig = new List(); + newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value); + + if (_inputConfig == null && inputConfig != null) + { + newConfig.Add(inputConfig); + } + else + { + if (_inputDevice.ActiveId == "disabled") + { + newConfig.Remove(_inputConfig); + } + else if (inputConfig != null) + { + int index = newConfig.IndexOf(_inputConfig); + + newConfig[index] = inputConfig; + } + } + + _mainWindow.RendererWidget?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + + // Atomically replace and signal input change. + // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. + ConfigurationState.Instance.Hid.InputConfig.Value = newConfig; + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + + Dispose(); + } + + private void CloseToggle_Activated(object sender, EventArgs args) + { + Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.glade new file mode 100644 index 00000000..e433f5cc --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/ControllerWindow.glade @@ -0,0 +1,2241 @@ + + + + + + 4 + 1 + 4 + + + 0.1 + 10 + 1.0 + 0.1 + 1.0 + + + 0.1 + 10 + 1.0 + 0.1 + 1.0 + + + 1 + 0.050000000000000003 + 0.01 + 0.10000000000000001 + + + 1 + 0.050000000000000003 + 0.01 + 0.10000000000000001 + + + 2 + 1.000000000000000003 + 0.01 + 0.10000000000000001 + + + 2 + 1.000000000000000003 + 0.01 + 0.10000000000000001 + + + 1 + 0.5 + 0.01 + 0.10000000000000001 + + + 100 + 0.01 + 0.01 + 0.10000000000000001 + 0.10000000000000001 + + + 1000 + 100 + 1 + 4 + + + 4 + 1 + 4 + + + False + Ryujinx - Controller Settings + True + center + 1200 + 720 + + + + + + True + False + vertical + + + True + True + in + + + True + False + + + True + False + vertical + + + True + False + 10 + 10 + 10 + + + True + False + + + True + False + 5 + Input Device + + + False + True + 0 + + + + + True + False + 0 + disabled + + Disabled + + + + + True + True + 1 + + + + + False + True + 0 + + + + + True + False + 20 + + + True + False + The controller's type + center + 5 + Controller Type: + + + False + True + 0 + + + + + True + False + The controller's type + 0 + + + + False + True + 1 + + + + + False + True + 1 + + + + + True + False + 20 + + + True + False + 5 + Profile: + + + False + True + 0 + + + + + True + False + 5 + 0 + default + + + False + True + 1 + + + + + Load + 60 + True + True + True + 5 + + + + False + True + 2 + + + + + Add + 60 + True + True + True + 5 + + + + False + True + 3 + + + + + Remove + 60 + True + True + True + + + + False + True + 4 + + + + + False + True + 2 + + + + + False + True + 0 + + + + + True + False + + + True + False + vertical + + + True + False + 10 + 5 + + + 156 + True + False + 10 + vertical + + + True + False + 5 + 5 + Buttons + + + + + + False + True + 0 + + + + + True + False + 3 + 10 + + + 80 + True + False + A + + + 0 + 0 + + + + + 80 + True + False + B + + + 0 + 1 + + + + + 80 + True + False + X + + + 0 + 2 + + + + + 80 + True + False + Y + + + 0 + 3 + + + + + + 70 + True + True + True + + + 1 + 0 + + + + + + 70 + True + True + True + + + 1 + 1 + + + + + + 70 + True + True + True + + + 1 + 2 + + + + + + 70 + True + True + True + + + 1 + 3 + + + + + 80 + True + False + + + + + 0 + 4 + + + + + 80 + True + False + - + + + 0 + 5 + + + + + + 70 + True + True + True + + + 1 + 5 + + + + + + 70 + True + True + True + + + 1 + 4 + + + + + False + True + 1 + + + + + False + True + 0 + + + + + True + False + + + False + True + 1 + + + + + 160 + True + False + 10 + 10 + vertical + + + True + False + 5 + 5 + Left Stick + + + + + + False + True + 0 + + + + + True + False + 5 + 3 + 10 + + + 80 + True + False + LStick Button + 0 + + + 0 + 0 + + + + + + 65 + True + True + True + + + 1 + 0 + + + + + False + True + 1 + + + + + True + False + 3 + 10 + + + + 65 + True + True + True + + + 1 + 1 + + + + + + 65 + True + True + True + + + 1 + 0 + + + + + + 65 + True + True + True + + + 1 + 2 + + + + + + 65 + True + True + True + + + 1 + 3 + + + + + 80 + True + False + LStick Down + 0 + + + 0 + 1 + + + + + 80 + True + False + LStick Up + 0 + + + 0 + 0 + + + + + 80 + True + False + LStick Right + 0 + + + 0 + 3 + + + + + 80 + True + False + LStick Left + 0 + + + 0 + 2 + + + + + False + True + 2 + + + + + True + False + 3 + 10 + + + 80 + True + False + LStick + 0 + + + 0 + 0 + + + + + + 65 + True + True + True + + + 1 + 0 + + + + + Invert Stick X + True + True + False + True + + + 2 + 0 + + + + + Invert Stick Y + True + True + False + True + + + 2 + 1 + + + + + Rotate 90° Clockwise + True + True + False + True + + + 2 + 2 + + + + + False + True + 3 + + + + + True + False + 10 + vertical + + + True + False + start + Deadzone Left + + + False + True + 0 + + + + + True + True + _controllerDeadzoneLeft + 2 + 2 + + + True + True + 1 + + + + + False + True + 4 + + + + + True + False + 10 + vertical + + + True + False + start + Range Left + + + False + True + 0 + + + + + True + True + _controllerRangeLeft + 2 + 2 + + + True + True + 1 + + + + + False + True + 5 + + + + + False + True + 2 + + + + + True + False + + + False + True + 3 + + + + + 150 + True + False + 10 + 10 + vertical + + + True + False + 5 + 5 + Triggers + + + + + + False + True + 0 + + + + + True + False + 3 + 10 + + + 80 + True + False + L + + + 0 + 0 + + + + + 80 + True + False + R + + + 0 + 1 + + + + + + 65 + True + True + True + + + 1 + 0 + + + + + + 65 + True + True + True + + + 1 + 1 + + + + + 80 + True + False + ZL + + + 0 + 2 + + + + + 80 + True + False + ZR + + + 0 + 3 + + + + + + 65 + True + True + True + + + 1 + 2 + + + + + + 65 + True + True + True + + + 1 + 3 + + + + + False + True + 1 + + + + + _sideTriggerBox + True + False + 5 + 3 + 10 + + + 80 + True + False + Left SL + + + 0 + 0 + + + + + 80 + True + False + Left SR + + + 0 + 1 + + + + + + 65 + True + True + True + + + 1 + 0 + + + + + + 65 + True + True + True + + + 1 + 1 + + + + + False + True + 2 + + + + + _sideTriggerBox + True + False + 5 + 3 + 10 + + + 80 + True + False + Right SL + + + 0 + 0 + + + + + 80 + True + False + Right SR + + + 0 + 1 + + + + + + 65 + True + True + True + + + 1 + 0 + + + + + + 65 + True + True + True + + + 1 + 1 + + + + + False + True + 3 + + + + + True + False + 10 + vertical + + + True + False + start + 10 + Trigger Threshold + + + False + True + 0 + + + + + True + True + _controllerTriggerThreshold + 2 + 2 + + + True + True + 1 + + + + + False + True + 4 + + + + + False + True + 4 + + + + + False + True + 0 + + + + + True + False + 10 + + + 156 + True + False + 10 + 10 + vertical + + + True + False + 5 + 5 + Directional Pad + + + + + + False + True + 0 + + + + + True + False + 3 + 10 + + + 80 + True + False + Dpad Up + 0 + + + 0 + 0 + + + + + 80 + True + False + Dpad Down + 0 + + + 0 + 1 + + + + + 80 + True + False + Dpad Left + 0 + + + 0 + 2 + + + + + 80 + True + False + Dpad Right + 0 + + + 0 + 3 + + + + + + 70 + True + True + True + + + 1 + 0 + + + + + + 70 + True + True + True + + + 1 + 1 + + + + + + 70 + True + True + True + + + 1 + 2 + + + + + + 70 + True + True + True + + + 1 + 3 + + + + + False + True + 1 + + + + + True + False + 10 + vertical + + + True + False + 10 + 5 + Rumble + + + + + + False + True + 0 + + + + + Enable + True + True + False + True + + + False + True + 1 + + + + + True + False + 10 + vertical + + + True + False + start + Strong rumble multiplier + + + False + True + 0 + + + + + True + True + _controllerStrongRumble + 1 + 1 + + + True + True + 1 + + + + + False + True + 2 + + + + + True + False + 10 + vertical + + + True + False + start + Weak rumble multiplier + + + False + True + 0 + + + + + True + True + _controllerWeakRumble + 1 + 1 + + + True + True + 1 + + + + + False + True + 3 + + + + + False + True + 2 + + + + + False + True + 0 + + + + + True + False + + + False + True + 1 + + + + + 160 + True + False + 10 + 10 + vertical + + + True + False + 5 + 5 + Right Stick + + + + + + False + True + 0 + + + + + True + False + 5 + 3 + 10 + + + 80 + True + False + RStick Button + 0 + + + 0 + 0 + + + + + + 65 + True + True + True + + + 1 + 0 + + + + + False + True + 1 + + + + + True + False + 3 + 10 + + + 80 + True + False + RStick Up + 0 + + + 0 + 0 + + + + + 80 + True + False + RStick Down + 0 + + + 0 + 1 + + + + + 80 + True + False + RStick Left + 0 + + + 0 + 2 + + + + + 80 + True + False + RStick Right + 0 + + + 0 + 3 + + + + + + 65 + True + True + True + + + 1 + 0 + + + + + + 65 + True + True + True + + + 1 + 1 + + + + + + 65 + True + True + True + + + 1 + 2 + + + + + + 65 + True + True + True + + + 1 + 3 + + + + + False + True + 2 + + + + + True + False + 3 + 10 + + + 80 + True + False + RStick + 0 + + + 0 + 0 + + + + + + 65 + True + True + True + + + 1 + 0 + + + + + Invert Stick X + True + True + False + True + + + 2 + 0 + + + + + Invert Stick Y + True + True + False + True + + + 2 + 1 + + + + + Rotate 90° Clockwise + True + True + False + True + + + 2 + 2 + + + + + False + True + 3 + + + + + True + False + 10 + vertical + + + True + False + start + Deadzone Right + + + False + True + 0 + + + + + True + True + _controllerDeadzoneRight + 2 + 2 + + + True + True + 1 + + + + + False + True + 4 + + + + + True + False + 10 + vertical + + + True + False + start + Range Right + + + False + True + 0 + + + + + True + True + _controllerRangeRight + 2 + 2 + + + True + True + 1 + + + + + False + True + 5 + + + + + False + True + 2 + + + + + True + False + + + False + True + 3 + + + + + True + False + 10 + 10 + vertical + 5 + + + True + False + 5 + 5 + Motion + + + + + + False + True + 0 + + + + + Enable Motion Controls + True + True + False + True + + + False + True + 1 + + + + + Use CemuHook compatible motion + True + True + False + True + + + False + True + 2 + + + + + True + False + 10 + + + True + False + 17 + Controller Slot + + + False + True + 5 + 0 + + + + + True + True + 10 + _slotNumber + 1 + True + True + + + False + True + 1 + + + + + False + True + 5 + 3 + + + + + True + False + 10 + + + True + False + 5 + Gyro Sensitivity % + + + False + True + 5 + 0 + + + + + True + True + 0 + _sensitivity + 1 + True + True + + + False + True + 1 + + + + + False + True + 5 + 4 + + + + + True + False + vertical + + + Mirror Input + True + True + False + True + + + False + True + 0 + + + + + True + False + 10 + + + True + False + Right JoyCon Slot + + + False + True + 5 + 0 + + + + + True + True + 0 + _altSlotNumber + 1 + True + True + + + False + True + 1 + + + + + False + True + 5 + 1 + + + + + False + True + 5 + + + + + True + False + 30 + + + True + False + Server Host + + + False + True + 5 + 0 + + + + + True + True + + + False + True + 1 + + + + + False + True + 5 + 6 + + + + + True + False + 30 + + + True + False + Server Port + + + False + True + 5 + 0 + + + + + True + True + + + False + True + 1 + + + + + False + True + 5 + 7 + + + + + True + False + start + Gyro Deadzone + + + False + True + 8 + + + + + True + True + _gyroDeadzone + 2 + 2 + + + True + True + 9 + + + + + False + True + 4 + + + + + False + True + 1 + + + + + True + True + 0 + + + + + True + False + 10 + 20 + 5 + 5 + + + True + True + 1 + + + + + True + True + 1 + + + + + + + + + True + True + 0 + + + + + True + False + 5 + 3 + 3 + end + + + Save + True + True + True + + + + False + True + 0 + + + + + Close + True + True + True + 4 + + + + False + True + 5 + 1 + + + + + False + False + 1 + + + + + + diff --git a/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs new file mode 100644 index 00000000..fb3189e1 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.cs @@ -0,0 +1,288 @@ +using Gtk; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.Loaders.Processes.Extensions; +using Ryujinx.HLE.Utilities; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Widgets; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using GUI = Gtk.Builder.ObjectAttribute; + +namespace Ryujinx.UI.Windows +{ + public class DlcWindow : Window + { + private readonly VirtualFileSystem _virtualFileSystem; + private readonly string _applicationIdBase; + private readonly string _dlcJsonPath; + private readonly List _dlcContainerList; + + private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + +#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier + [GUI] Label _baseTitleInfoLabel; + [GUI] TreeView _dlcTreeView; + [GUI] TreeSelection _dlcTreeSelection; +#pragma warning restore CS0649, IDE0044 + + public DlcWindow(VirtualFileSystem virtualFileSystem, string applicationIdBase, ApplicationData applicationData) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, applicationIdBase, applicationData) { } + + private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string applicationIdBase, ApplicationData applicationData) : base(builder.GetRawOwnedObject("_dlcWindow")) + { + builder.Autoconnect(this); + + _applicationIdBase = applicationIdBase; + _virtualFileSystem = virtualFileSystem; + _dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationIdBase, "dlc.json"); + _baseTitleInfoLabel.Text = $"DLC Available for {applicationData.Name} [{applicationIdBase.ToUpper()}]"; + + try + { + _dlcContainerList = JsonHelper.DeserializeFromFile(_dlcJsonPath, _serializerContext.ListDownloadableContentContainer); + } + catch + { + _dlcContainerList = new List(); + } + + _dlcTreeView.Model = new TreeStore(typeof(bool), typeof(string), typeof(string)); + + CellRendererToggle enableToggle = new(); + enableToggle.Toggled += (sender, args) => + { + _dlcTreeView.Model.GetIter(out TreeIter treeIter, new TreePath(args.Path)); + bool newValue = !(bool)_dlcTreeView.Model.GetValue(treeIter, 0); + _dlcTreeView.Model.SetValue(treeIter, 0, newValue); + + if (_dlcTreeView.Model.IterChildren(out TreeIter childIter, treeIter)) + { + do + { + _dlcTreeView.Model.SetValue(childIter, 0, newValue); + } + while (_dlcTreeView.Model.IterNext(ref childIter)); + } + }; + + _dlcTreeView.AppendColumn("Enabled", enableToggle, "active", 0); + _dlcTreeView.AppendColumn("ApplicationId", new CellRendererText(), "text", 1); + _dlcTreeView.AppendColumn("Path", new CellRendererText(), "text", 2); + + foreach (DownloadableContentContainer dlcContainer in _dlcContainerList) + { + if (File.Exists(dlcContainer.ContainerPath)) + { + // The parent tree item has its own "enabled" check box, but it's the actual + // nca entries that store the enabled / disabled state. A bit of a UI inconsistency. + // Maybe a tri-state check box would be better, but for now we check the parent + // "enabled" box if all child NCAs are enabled. Usually fine since each nsp has only one nca. + bool areAllContentPacksEnabled = dlcContainer.DownloadableContentNcaList.TrueForAll((nca) => nca.Enabled); + TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(areAllContentPacksEnabled, "", dlcContainer.ContainerPath); + + using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(dlcContainer.ContainerPath, _virtualFileSystem, false); + + if (partitionFileSystem == null) + { + continue; + } + + foreach (DownloadableContentNca dlcNca in dlcContainer.DownloadableContentNcaList) + { + using var ncaFile = new UniqueRef(); + + partitionFileSystem.OpenFile(ref ncaFile.Ref, dlcNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.ContainerPath); + + if (nca != null) + { + ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter, dlcNca.Enabled, nca.Header.TitleId.ToString("X16"), dlcNca.FullPath); + } + } + } + else + { + // DLC file moved or renamed. Allow the user to remove it without crashing the whole dialog. + TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(false, "", $"(MISSING) {dlcContainer.ContainerPath}"); + } + } + + // NOTE: Try to load downloadable contents from PFS last to preserve enabled state. + AddDlc(applicationData.Path, true); + } + + private Nca TryCreateNca(IStorage ncaStorage, string containerPath) + { + try + { + return new Nca(_virtualFileSystem.KeySet, ncaStorage); + } + catch (Exception exception) + { + GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {containerPath}"); + } + + return null; + } + + private void AddDlc(string path, bool ignoreNotFound = false) + { + if (!File.Exists(path) || _dlcContainerList.Any(x => x.ContainerPath == path)) + { + return; + } + + using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem); + + bool containsDlc = false; + + TreeIter? parentIter = null; + + foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca")) + { + using var ncaFile = new UniqueRef(); + + partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path); + + if (nca == null) + { + continue; + } + + if (nca.Header.ContentType == NcaContentType.PublicData) + { + if (nca.GetProgramIdBase() != ulong.Parse(_applicationIdBase, NumberStyles.HexNumber)) + { + continue; + } + + parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", path); + + ((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath); + containsDlc = true; + } + } + + if (!containsDlc && !ignoreNotFound) + { + GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!"); + } + } + + private void AddButton_Clicked(object sender, EventArgs args) + { + FileChooserNative fileChooser = new("Select DLC files", this, FileChooserAction.Open, "Add", "Cancel") + { + SelectMultiple = true, + }; + + FileFilter filter = new() + { + Name = "Switch Game DLCs", + }; + filter.AddPattern("*.nsp"); + + fileChooser.AddFilter(filter); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + foreach (string containerPath in fileChooser.Filenames) + { + AddDlc(containerPath); + } + } + + fileChooser.Dispose(); + } + + private void RemoveButton_Clicked(object sender, EventArgs args) + { + if (_dlcTreeSelection.GetSelected(out ITreeModel treeModel, out TreeIter treeIter)) + { + if (_dlcTreeView.Model.IterParent(out TreeIter parentIter, treeIter) && _dlcTreeView.Model.IterNChildren(parentIter) <= 1) + { + ((TreeStore)treeModel).Remove(ref parentIter); + } + else + { + ((TreeStore)treeModel).Remove(ref treeIter); + } + } + } + + private void RemoveAllButton_Clicked(object sender, EventArgs args) + { + List toRemove = new(); + + if (_dlcTreeView.Model.GetIterFirst(out TreeIter iter)) + { + do + { + toRemove.Add(iter); + } + while (_dlcTreeView.Model.IterNext(ref iter)); + } + + foreach (TreeIter i in toRemove) + { + TreeIter j = i; + ((TreeStore)_dlcTreeView.Model).Remove(ref j); + } + } + + private void SaveButton_Clicked(object sender, EventArgs args) + { + _dlcContainerList.Clear(); + + if (_dlcTreeView.Model.GetIterFirst(out TreeIter parentIter)) + { + do + { + if (_dlcTreeView.Model.IterChildren(out TreeIter childIter, parentIter)) + { + DownloadableContentContainer dlcContainer = new() + { + ContainerPath = (string)_dlcTreeView.Model.GetValue(parentIter, 2), + DownloadableContentNcaList = new List(), + }; + + do + { + dlcContainer.DownloadableContentNcaList.Add(new DownloadableContentNca + { + Enabled = (bool)_dlcTreeView.Model.GetValue(childIter, 0), + TitleId = Convert.ToUInt64(_dlcTreeView.Model.GetValue(childIter, 1).ToString(), 16), + FullPath = (string)_dlcTreeView.Model.GetValue(childIter, 2), + }); + } + while (_dlcTreeView.Model.IterNext(ref childIter)); + + _dlcContainerList.Add(dlcContainer); + } + } + while (_dlcTreeView.Model.IterNext(ref parentIter)); + } + + JsonHelper.SerializeToFile(_dlcJsonPath, _dlcContainerList, _serializerContext.ListDownloadableContentContainer); + + Dispose(); + } + + private void CancelButton_Clicked(object sender, EventArgs args) + { + Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.glade new file mode 100644 index 00000000..bdb0e647 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/DlcWindow.glade @@ -0,0 +1,202 @@ + + + + + + False + Ryujinx - DLC Manager + True + center + 550 + 350 + + + True + False + vertical + + + True + False + vertical + + + True + False + 10 + 10 + 10 + 10 + Available DLC + + + False + True + 0 + + + + + True + True + 10 + 10 + in + + + True + False + + + True + True + False + + + + + + + + + + True + True + 1 + + + + + True + True + 0 + + + + + True + False + + + True + False + 10 + 10 + start + + + Add + True + True + True + Adds a DLC to this list + 10 + + + + True + True + 0 + + + + + Remove + True + True + True + Removes the selected DLC + 10 + + + + True + True + 1 + + + + + Remove All + True + True + True + Removes all DLCs + 10 + + + + True + True + 2 + + + + + True + True + 0 + + + + + True + False + 10 + 10 + end + + + Save + True + True + True + 10 + 2 + 2 + + + + True + True + 0 + + + + + Cancel + True + True + True + 10 + 2 + 2 + + + + True + True + 1 + + + + + True + True + 1 + + + + + False + True + 1 + + + + + + + + + diff --git a/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs new file mode 100644 index 00000000..dc467c0f --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.cs @@ -0,0 +1,847 @@ +using Gtk; +using LibHac.Tools.FsSystem; +using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SoundIo; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Multiplayer; +using Ryujinx.Common.GraphicsDriver; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Configuration.System; +using Ryujinx.UI.Helper; +using Ryujinx.UI.Widgets; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net.NetworkInformation; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using GUI = Gtk.Builder.ObjectAttribute; + +namespace Ryujinx.UI.Windows +{ + public class SettingsWindow : Window + { + private readonly MainWindow _parent; + private readonly ListStore _gameDirsBoxStore; + private readonly ListStore _audioBackendStore; + private readonly TimeZoneContentManager _timeZoneContentManager; + private readonly HashSet _validTzRegions; + + private long _systemTimeOffset; + private float _previousVolumeLevel; + private bool _directoryChanged = false; + +#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier + [GUI] CheckButton _traceLogToggle; + [GUI] CheckButton _errorLogToggle; + [GUI] CheckButton _warningLogToggle; + [GUI] CheckButton _infoLogToggle; + [GUI] CheckButton _stubLogToggle; + [GUI] CheckButton _debugLogToggle; + [GUI] CheckButton _fileLogToggle; + [GUI] CheckButton _guestLogToggle; + [GUI] CheckButton _fsAccessLogToggle; + [GUI] Adjustment _fsLogSpinAdjustment; + [GUI] ComboBoxText _graphicsDebugLevel; + [GUI] CheckButton _dockedModeToggle; + [GUI] CheckButton _discordToggle; + [GUI] CheckButton _checkUpdatesToggle; + [GUI] CheckButton _showConfirmExitToggle; + [GUI] RadioButton _hideCursorNever; + [GUI] RadioButton _hideCursorOnIdle; + [GUI] RadioButton _hideCursorAlways; + [GUI] CheckButton _vSyncToggle; + [GUI] CheckButton _shaderCacheToggle; + [GUI] CheckButton _textureRecompressionToggle; + [GUI] CheckButton _macroHLEToggle; + [GUI] CheckButton _ptcToggle; + [GUI] CheckButton _internetToggle; + [GUI] CheckButton _fsicToggle; + [GUI] RadioButton _mmSoftware; + [GUI] RadioButton _mmHost; + [GUI] RadioButton _mmHostUnsafe; + [GUI] CheckButton _expandRamToggle; + [GUI] CheckButton _ignoreToggle; + [GUI] CheckButton _directKeyboardAccess; + [GUI] CheckButton _directMouseAccess; + [GUI] ComboBoxText _systemLanguageSelect; + [GUI] ComboBoxText _systemRegionSelect; + [GUI] Entry _systemTimeZoneEntry; + [GUI] EntryCompletion _systemTimeZoneCompletion; + [GUI] Box _audioBackendBox; + [GUI] ComboBox _audioBackendSelect; + [GUI] Label _audioVolumeLabel; + [GUI] Scale _audioVolumeSlider; + [GUI] SpinButton _systemTimeYearSpin; + [GUI] SpinButton _systemTimeMonthSpin; + [GUI] SpinButton _systemTimeDaySpin; + [GUI] SpinButton _systemTimeHourSpin; + [GUI] SpinButton _systemTimeMinuteSpin; + [GUI] Adjustment _systemTimeYearSpinAdjustment; + [GUI] Adjustment _systemTimeMonthSpinAdjustment; + [GUI] Adjustment _systemTimeDaySpinAdjustment; + [GUI] Adjustment _systemTimeHourSpinAdjustment; + [GUI] Adjustment _systemTimeMinuteSpinAdjustment; + [GUI] ComboBoxText _multiLanSelect; + [GUI] ComboBoxText _multiModeSelect; + [GUI] CheckButton _custThemeToggle; + [GUI] Entry _custThemePath; + [GUI] ToggleButton _browseThemePath; + [GUI] Label _custThemePathLabel; + [GUI] TreeView _gameDirsBox; + [GUI] Entry _addGameDirBox; + [GUI] ComboBoxText _galThreading; + [GUI] Entry _graphicsShadersDumpPath; + [GUI] ComboBoxText _anisotropy; + [GUI] ComboBoxText _aspectRatio; + [GUI] ComboBoxText _antiAliasing; + [GUI] ComboBoxText _scalingFilter; + [GUI] ComboBoxText _graphicsBackend; + [GUI] ComboBoxText _preferredGpu; + [GUI] ComboBoxText _resScaleCombo; + [GUI] Entry _resScaleText; + [GUI] Adjustment _scalingFilterLevel; + [GUI] Scale _scalingFilterSlider; + [GUI] ToggleButton _configureController1; + [GUI] ToggleButton _configureController2; + [GUI] ToggleButton _configureController3; + [GUI] ToggleButton _configureController4; + [GUI] ToggleButton _configureController5; + [GUI] ToggleButton _configureController6; + [GUI] ToggleButton _configureController7; + [GUI] ToggleButton _configureController8; + [GUI] ToggleButton _configureControllerH; + +#pragma warning restore CS0649, IDE0044 + + public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.Gtk3.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } + + private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : base(builder.GetRawOwnedObject("_settingsWin")) + { + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + + _parent = parent; + + builder.Autoconnect(this); + + _timeZoneContentManager = new TimeZoneContentManager(); + _timeZoneContentManager.InitializeInstance(virtualFileSystem, contentManager, IntegrityCheckLevel.None); + + _validTzRegions = new HashSet(_timeZoneContentManager.LocationNameCache.Length, StringComparer.Ordinal); // Zone regions are identifiers. Must match exactly. + + // Bind Events. + _configureController1.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player1); + _configureController2.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player2); + _configureController3.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player3); + _configureController4.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player4); + _configureController5.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player5); + _configureController6.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player6); + _configureController7.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player7); + _configureController8.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Player8); + _configureControllerH.Pressed += (sender, args) => ConfigureController_Pressed(sender, PlayerIndex.Handheld); + _systemTimeZoneEntry.FocusOutEvent += TimeZoneEntry_FocusOut; + + _resScaleCombo.Changed += (sender, args) => _resScaleText.Visible = _resScaleCombo.ActiveId == "-1"; + _scalingFilter.Changed += (sender, args) => _scalingFilterSlider.Visible = _scalingFilter.ActiveId == "2"; + _galThreading.Changed += (sender, args) => + { + if (_galThreading.ActiveId != ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString()) + { + GtkDialog.CreateInfoDialog("Warning - Backend Threading", "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's."); + } + }; + + // Setup Currents. + if (ConfigurationState.Instance.Logger.EnableTrace) + { + _traceLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableFileLog) + { + _fileLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableError) + { + _errorLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableWarn) + { + _warningLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableInfo) + { + _infoLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableStub) + { + _stubLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableDebug) + { + _debugLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableGuest) + { + _guestLogToggle.Click(); + } + + if (ConfigurationState.Instance.Logger.EnableFsAccessLog) + { + _fsAccessLogToggle.Click(); + } + + foreach (GraphicsDebugLevel level in Enum.GetValues()) + { + _graphicsDebugLevel.Append(level.ToString(), level.ToString()); + } + + _graphicsDebugLevel.SetActiveId(ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value.ToString()); + + if (ConfigurationState.Instance.System.EnableDockedMode) + { + _dockedModeToggle.Click(); + } + + if (ConfigurationState.Instance.EnableDiscordIntegration) + { + _discordToggle.Click(); + } + + if (ConfigurationState.Instance.CheckUpdatesOnStart) + { + _checkUpdatesToggle.Click(); + } + + if (ConfigurationState.Instance.ShowConfirmExit) + { + _showConfirmExitToggle.Click(); + } + + switch (ConfigurationState.Instance.HideCursor.Value) + { + case HideCursorMode.Never: + _hideCursorNever.Click(); + break; + case HideCursorMode.OnIdle: + _hideCursorOnIdle.Click(); + break; + case HideCursorMode.Always: + _hideCursorAlways.Click(); + break; + } + + if (ConfigurationState.Instance.Graphics.EnableVsync) + { + _vSyncToggle.Click(); + } + + if (ConfigurationState.Instance.Graphics.EnableShaderCache) + { + _shaderCacheToggle.Click(); + } + + if (ConfigurationState.Instance.Graphics.EnableTextureRecompression) + { + _textureRecompressionToggle.Click(); + } + + if (ConfigurationState.Instance.Graphics.EnableMacroHLE) + { + _macroHLEToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnablePtc) + { + _ptcToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnableInternetAccess) + { + _internetToggle.Click(); + } + + if (ConfigurationState.Instance.System.EnableFsIntegrityChecks) + { + _fsicToggle.Click(); + } + + switch (ConfigurationState.Instance.System.MemoryManagerMode.Value) + { + case MemoryManagerMode.SoftwarePageTable: + _mmSoftware.Click(); + break; + case MemoryManagerMode.HostMapped: + _mmHost.Click(); + break; + case MemoryManagerMode.HostMappedUnsafe: + _mmHostUnsafe.Click(); + break; + } + + if (ConfigurationState.Instance.System.ExpandRam) + { + _expandRamToggle.Click(); + } + + if (ConfigurationState.Instance.System.IgnoreMissingServices) + { + _ignoreToggle.Click(); + } + + if (ConfigurationState.Instance.Hid.EnableKeyboard) + { + _directKeyboardAccess.Click(); + } + + if (ConfigurationState.Instance.Hid.EnableMouse) + { + _directMouseAccess.Click(); + } + + if (ConfigurationState.Instance.UI.EnableCustomTheme) + { + _custThemeToggle.Click(); + } + + // Custom EntryCompletion Columns. If added to glade, need to override more signals + ListStore tzList = new(typeof(string), typeof(string), typeof(string)); + _systemTimeZoneCompletion.Model = tzList; + + CellRendererText offsetCol = new(); + CellRendererText abbrevCol = new(); + + _systemTimeZoneCompletion.PackStart(offsetCol, false); + _systemTimeZoneCompletion.AddAttribute(offsetCol, "text", 0); + _systemTimeZoneCompletion.TextColumn = 1; // Regions Column + _systemTimeZoneCompletion.PackStart(abbrevCol, false); + _systemTimeZoneCompletion.AddAttribute(abbrevCol, "text", 2); + + int maxLocationLength = 0; + + foreach (var (offset, location, abbr) in _timeZoneContentManager.ParseTzOffsets()) + { + var hours = Math.DivRem(offset, 3600, out int seconds); + var minutes = Math.Abs(seconds) / 60; + + var abbr2 = (abbr.StartsWith('+') || abbr.StartsWith('-')) ? string.Empty : abbr; + + tzList.AppendValues($"UTC{hours:+0#;-0#;+00}:{minutes:D2} ", location, abbr2); + _validTzRegions.Add(location); + + maxLocationLength = Math.Max(maxLocationLength, location.Length); + } + + _systemTimeZoneEntry.WidthChars = Math.Max(20, maxLocationLength + 1); // Ensure minimum Entry width + _systemTimeZoneEntry.Text = _timeZoneContentManager.SanityCheckDeviceLocationName(ConfigurationState.Instance.System.TimeZone); + + _systemTimeZoneCompletion.MatchFunc = TimeZoneMatchFunc; + + _systemLanguageSelect.SetActiveId(ConfigurationState.Instance.System.Language.Value.ToString()); + _systemRegionSelect.SetActiveId(ConfigurationState.Instance.System.Region.Value.ToString()); + _galThreading.SetActiveId(ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString()); + _resScaleCombo.SetActiveId(ConfigurationState.Instance.Graphics.ResScale.Value.ToString()); + _anisotropy.SetActiveId(ConfigurationState.Instance.Graphics.MaxAnisotropy.Value.ToString()); + _aspectRatio.SetActiveId(((int)ConfigurationState.Instance.Graphics.AspectRatio.Value).ToString()); + _graphicsBackend.SetActiveId(((int)ConfigurationState.Instance.Graphics.GraphicsBackend.Value).ToString()); + _antiAliasing.SetActiveId(((int)ConfigurationState.Instance.Graphics.AntiAliasing.Value).ToString()); + _scalingFilter.SetActiveId(((int)ConfigurationState.Instance.Graphics.ScalingFilter.Value).ToString()); + + UpdatePreferredGpuComboBox(); + + _graphicsBackend.Changed += (sender, e) => UpdatePreferredGpuComboBox(); + PopulateNetworkInterfaces(); + _multiLanSelect.SetActiveId(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value); + _multiModeSelect.SetActiveId(ConfigurationState.Instance.Multiplayer.Mode.Value.ToString()); + + _custThemePath.Buffer.Text = ConfigurationState.Instance.UI.CustomThemePath; + _resScaleText.Buffer.Text = ConfigurationState.Instance.Graphics.ResScaleCustom.Value.ToString(); + _scalingFilterLevel.Value = ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value; + _resScaleText.Visible = _resScaleCombo.ActiveId == "-1"; + _scalingFilterSlider.Visible = _scalingFilter.ActiveId == "2"; + _graphicsShadersDumpPath.Buffer.Text = ConfigurationState.Instance.Graphics.ShadersDumpPath; + _fsLogSpinAdjustment.Value = ConfigurationState.Instance.System.FsGlobalAccessLogMode; + _systemTimeOffset = ConfigurationState.Instance.System.SystemTimeOffset; + + _gameDirsBox.AppendColumn("", new CellRendererText(), "text", 0); + _gameDirsBoxStore = new ListStore(typeof(string)); + _gameDirsBox.Model = _gameDirsBoxStore; + + foreach (string gameDir in ConfigurationState.Instance.UI.GameDirs.Value) + { + _gameDirsBoxStore.AppendValues(gameDir); + } + + if (_custThemeToggle.Active == false) + { + _custThemePath.Sensitive = false; + _custThemePathLabel.Sensitive = false; + _browseThemePath.Sensitive = false; + } + + // Setup system time spinners + UpdateSystemTimeSpinners(); + + _audioBackendStore = new ListStore(typeof(string), typeof(AudioBackend)); + + TreeIter openAlIter = _audioBackendStore.AppendValues("OpenAL", AudioBackend.OpenAl); + TreeIter soundIoIter = _audioBackendStore.AppendValues("SoundIO", AudioBackend.SoundIo); + TreeIter sdl2Iter = _audioBackendStore.AppendValues("SDL2", AudioBackend.SDL2); + TreeIter dummyIter = _audioBackendStore.AppendValues("Dummy", AudioBackend.Dummy); + + _audioBackendSelect = ComboBox.NewWithModelAndEntry(_audioBackendStore); + _audioBackendSelect.EntryTextColumn = 0; + _audioBackendSelect.Entry.IsEditable = false; + + switch (ConfigurationState.Instance.System.AudioBackend.Value) + { + case AudioBackend.OpenAl: + _audioBackendSelect.SetActiveIter(openAlIter); + break; + case AudioBackend.SoundIo: + _audioBackendSelect.SetActiveIter(soundIoIter); + break; + case AudioBackend.SDL2: + _audioBackendSelect.SetActiveIter(sdl2Iter); + break; + case AudioBackend.Dummy: + _audioBackendSelect.SetActiveIter(dummyIter); + break; + default: + throw new InvalidOperationException($"{nameof(ConfigurationState.Instance.System.AudioBackend)} contains an invalid value: {ConfigurationState.Instance.System.AudioBackend.Value}"); + } + + _audioBackendBox.Add(_audioBackendSelect); + _audioBackendSelect.Show(); + + _previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume; + _audioVolumeLabel = new Label("Volume: "); + _audioVolumeSlider = new Scale(Orientation.Horizontal, 0, 100, 1); + _audioVolumeLabel.MarginStart = 10; + _audioVolumeSlider.ValuePos = PositionType.Right; + _audioVolumeSlider.WidthRequest = 200; + + _audioVolumeSlider.Value = _previousVolumeLevel * 100; + _audioVolumeSlider.ValueChanged += VolumeSlider_OnChange; + _audioBackendBox.Add(_audioVolumeLabel); + _audioBackendBox.Add(_audioVolumeSlider); + _audioVolumeLabel.Show(); + _audioVolumeSlider.Show(); + + bool openAlIsSupported = false; + bool soundIoIsSupported = false; + bool sdl2IsSupported = false; + + Task.Run(() => + { + openAlIsSupported = OpenALHardwareDeviceDriver.IsSupported; + soundIoIsSupported = !OperatingSystem.IsMacOS() && SoundIoHardwareDeviceDriver.IsSupported; + sdl2IsSupported = SDL2HardwareDeviceDriver.IsSupported; + }); + + // This function runs whenever the dropdown is opened + _audioBackendSelect.SetCellDataFunc(_audioBackendSelect.Cells[0], (layout, cell, model, iter) => + { + cell.Sensitive = ((AudioBackend)_audioBackendStore.GetValue(iter, 1)) switch + { + AudioBackend.OpenAl => openAlIsSupported, + AudioBackend.SoundIo => soundIoIsSupported, + AudioBackend.SDL2 => sdl2IsSupported, + AudioBackend.Dummy => true, + _ => throw new InvalidOperationException($"{nameof(_audioBackendStore)} contains an invalid value for iteration {iter}: {_audioBackendStore.GetValue(iter, 1)}"), + }; + }); + + if (OperatingSystem.IsMacOS()) + { + var store = (_graphicsBackend.Model as ListStore); + store.GetIter(out TreeIter openglIter, new TreePath(new[] { 1 })); + store.Remove(ref openglIter); + + _graphicsBackend.Model = store; + } + } + + private void UpdatePreferredGpuComboBox() + { + _preferredGpu.RemoveAll(); + + if (Enum.Parse(_graphicsBackend.ActiveId) == GraphicsBackend.Vulkan) + { + var devices = Graphics.Vulkan.VulkanRenderer.GetPhysicalDevices(); + string preferredGpuIdFromConfig = ConfigurationState.Instance.Graphics.PreferredGpu.Value; + string preferredGpuId = preferredGpuIdFromConfig; + bool noGpuId = string.IsNullOrEmpty(preferredGpuIdFromConfig); + + foreach (var device in devices) + { + string dGpu = device.IsDiscrete ? " (dGPU)" : ""; + _preferredGpu.Append(device.Id, $"{device.Name}{dGpu}"); + + // If there's no GPU selected yet, we just pick the first GPU. + // If there's a discrete GPU available, we always prefer that over the previous selection, + // as it is likely to have better performance and more features. + // If the configuration file already has a GPU selection, we always prefer that instead. + if (noGpuId && (string.IsNullOrEmpty(preferredGpuId) || device.IsDiscrete)) + { + preferredGpuId = device.Id; + } + } + + if (!string.IsNullOrEmpty(preferredGpuId)) + { + _preferredGpu.SetActiveId(preferredGpuId); + } + } + } + + private void PopulateNetworkInterfaces() + { + NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces(); + + foreach (NetworkInterface nif in interfaces) + { + string guid = nif.Id; + string name = nif.Name; + + _multiLanSelect.Append(guid, name); + } + } + + private void UpdateSystemTimeSpinners() + { + //Bind system time events + _systemTimeYearSpin.ValueChanged -= SystemTimeSpin_ValueChanged; + _systemTimeMonthSpin.ValueChanged -= SystemTimeSpin_ValueChanged; + _systemTimeDaySpin.ValueChanged -= SystemTimeSpin_ValueChanged; + _systemTimeHourSpin.ValueChanged -= SystemTimeSpin_ValueChanged; + _systemTimeMinuteSpin.ValueChanged -= SystemTimeSpin_ValueChanged; + + //Apply actual system time + SystemTimeOffset to system time spin buttons + DateTime systemTime = DateTime.Now.AddSeconds(_systemTimeOffset); + + _systemTimeYearSpinAdjustment.Value = systemTime.Year; + _systemTimeMonthSpinAdjustment.Value = systemTime.Month; + _systemTimeDaySpinAdjustment.Value = systemTime.Day; + _systemTimeHourSpinAdjustment.Value = systemTime.Hour; + _systemTimeMinuteSpinAdjustment.Value = systemTime.Minute; + + //Format spin buttons text to include leading zeros + _systemTimeYearSpin.Text = systemTime.Year.ToString("0000"); + _systemTimeMonthSpin.Text = systemTime.Month.ToString("00"); + _systemTimeDaySpin.Text = systemTime.Day.ToString("00"); + _systemTimeHourSpin.Text = systemTime.Hour.ToString("00"); + _systemTimeMinuteSpin.Text = systemTime.Minute.ToString("00"); + + //Bind system time events + _systemTimeYearSpin.ValueChanged += SystemTimeSpin_ValueChanged; + _systemTimeMonthSpin.ValueChanged += SystemTimeSpin_ValueChanged; + _systemTimeDaySpin.ValueChanged += SystemTimeSpin_ValueChanged; + _systemTimeHourSpin.ValueChanged += SystemTimeSpin_ValueChanged; + _systemTimeMinuteSpin.ValueChanged += SystemTimeSpin_ValueChanged; + } + + private void SaveSettings() + { + if (_directoryChanged) + { + List gameDirs = new(); + + _gameDirsBoxStore.GetIterFirst(out TreeIter treeIter); + + for (int i = 0; i < _gameDirsBoxStore.IterNChildren(); i++) + { + gameDirs.Add((string)_gameDirsBoxStore.GetValue(treeIter, 0)); + + _gameDirsBoxStore.IterNext(ref treeIter); + } + + ConfigurationState.Instance.UI.GameDirs.Value = gameDirs; + + _directoryChanged = false; + } + + HideCursorMode hideCursor = HideCursorMode.Never; + + if (_hideCursorOnIdle.Active) + { + hideCursor = HideCursorMode.OnIdle; + } + + if (_hideCursorAlways.Active) + { + hideCursor = HideCursorMode.Always; + } + + if (!float.TryParse(_resScaleText.Buffer.Text, out float resScaleCustom) || resScaleCustom <= 0.0f) + { + resScaleCustom = 1.0f; + } + + if (_validTzRegions.Contains(_systemTimeZoneEntry.Text)) + { + ConfigurationState.Instance.System.TimeZone.Value = _systemTimeZoneEntry.Text; + } + + MemoryManagerMode memoryMode = MemoryManagerMode.SoftwarePageTable; + + if (_mmHost.Active) + { + memoryMode = MemoryManagerMode.HostMapped; + } + + if (_mmHostUnsafe.Active) + { + memoryMode = MemoryManagerMode.HostMappedUnsafe; + } + + BackendThreading backendThreading = Enum.Parse(_galThreading.ActiveId); + if (ConfigurationState.Instance.Graphics.BackendThreading != backendThreading) + { + DriverUtilities.ToggleOGLThreading(backendThreading == BackendThreading.Off); + } + + ConfigurationState.Instance.Logger.EnableError.Value = _errorLogToggle.Active; + ConfigurationState.Instance.Logger.EnableTrace.Value = _traceLogToggle.Active; + ConfigurationState.Instance.Logger.EnableWarn.Value = _warningLogToggle.Active; + ConfigurationState.Instance.Logger.EnableInfo.Value = _infoLogToggle.Active; + ConfigurationState.Instance.Logger.EnableStub.Value = _stubLogToggle.Active; + ConfigurationState.Instance.Logger.EnableDebug.Value = _debugLogToggle.Active; + ConfigurationState.Instance.Logger.EnableGuest.Value = _guestLogToggle.Active; + ConfigurationState.Instance.Logger.EnableFsAccessLog.Value = _fsAccessLogToggle.Active; + ConfigurationState.Instance.Logger.EnableFileLog.Value = _fileLogToggle.Active; + ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value = Enum.Parse(_graphicsDebugLevel.ActiveId); + ConfigurationState.Instance.System.EnableDockedMode.Value = _dockedModeToggle.Active; + ConfigurationState.Instance.EnableDiscordIntegration.Value = _discordToggle.Active; + ConfigurationState.Instance.CheckUpdatesOnStart.Value = _checkUpdatesToggle.Active; + ConfigurationState.Instance.ShowConfirmExit.Value = _showConfirmExitToggle.Active; + ConfigurationState.Instance.HideCursor.Value = hideCursor; + ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active; + ConfigurationState.Instance.Graphics.EnableShaderCache.Value = _shaderCacheToggle.Active; + ConfigurationState.Instance.Graphics.EnableTextureRecompression.Value = _textureRecompressionToggle.Active; + ConfigurationState.Instance.Graphics.EnableMacroHLE.Value = _macroHLEToggle.Active; + ConfigurationState.Instance.System.EnablePtc.Value = _ptcToggle.Active; + ConfigurationState.Instance.System.EnableInternetAccess.Value = _internetToggle.Active; + ConfigurationState.Instance.System.EnableFsIntegrityChecks.Value = _fsicToggle.Active; + ConfigurationState.Instance.System.MemoryManagerMode.Value = memoryMode; + ConfigurationState.Instance.System.ExpandRam.Value = _expandRamToggle.Active; + ConfigurationState.Instance.System.IgnoreMissingServices.Value = _ignoreToggle.Active; + ConfigurationState.Instance.Hid.EnableKeyboard.Value = _directKeyboardAccess.Active; + ConfigurationState.Instance.Hid.EnableMouse.Value = _directMouseAccess.Active; + ConfigurationState.Instance.UI.EnableCustomTheme.Value = _custThemeToggle.Active; + ConfigurationState.Instance.System.Language.Value = Enum.Parse(_systemLanguageSelect.ActiveId); + ConfigurationState.Instance.System.Region.Value = Enum.Parse(_systemRegionSelect.ActiveId); + ConfigurationState.Instance.System.SystemTimeOffset.Value = _systemTimeOffset; + ConfigurationState.Instance.UI.CustomThemePath.Value = _custThemePath.Buffer.Text; + ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = _graphicsShadersDumpPath.Buffer.Text; + ConfigurationState.Instance.System.FsGlobalAccessLogMode.Value = (int)_fsLogSpinAdjustment.Value; + ConfigurationState.Instance.Graphics.MaxAnisotropy.Value = float.Parse(_anisotropy.ActiveId, CultureInfo.InvariantCulture); + ConfigurationState.Instance.Graphics.AspectRatio.Value = Enum.Parse(_aspectRatio.ActiveId); + ConfigurationState.Instance.Graphics.BackendThreading.Value = backendThreading; + ConfigurationState.Instance.Graphics.GraphicsBackend.Value = Enum.Parse(_graphicsBackend.ActiveId); + ConfigurationState.Instance.Graphics.PreferredGpu.Value = _preferredGpu.ActiveId; + ConfigurationState.Instance.Graphics.ResScale.Value = int.Parse(_resScaleCombo.ActiveId); + ConfigurationState.Instance.Graphics.ResScaleCustom.Value = resScaleCustom; + ConfigurationState.Instance.System.AudioVolume.Value = (float)_audioVolumeSlider.Value / 100.0f; + ConfigurationState.Instance.Graphics.AntiAliasing.Value = Enum.Parse(_antiAliasing.ActiveId); + ConfigurationState.Instance.Graphics.ScalingFilter.Value = Enum.Parse(_scalingFilter.ActiveId); + ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value = (int)_scalingFilterLevel.Value; + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _multiLanSelect.ActiveId; + + _previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value; + + ConfigurationState.Instance.Multiplayer.Mode.Value = Enum.Parse(_multiModeSelect.ActiveId); + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _multiLanSelect.ActiveId; + + if (_audioBackendSelect.GetActiveIter(out TreeIter activeIter)) + { + ConfigurationState.Instance.System.AudioBackend.Value = (AudioBackend)_audioBackendStore.GetValue(activeIter, 1); + } + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + + _parent.UpdateInternetAccess(); + MainWindow.UpdateGraphicsConfig(); + ThemeHelper.ApplyTheme(); + } + + // + // Events + // + private void TimeZoneEntry_FocusOut(object sender, FocusOutEventArgs e) + { + if (!_validTzRegions.Contains(_systemTimeZoneEntry.Text)) + { + _systemTimeZoneEntry.Text = _timeZoneContentManager.SanityCheckDeviceLocationName(ConfigurationState.Instance.System.TimeZone); + } + } + + private bool TimeZoneMatchFunc(EntryCompletion compl, string key, TreeIter iter) + { + key = key.Trim().Replace(' ', '_'); + + return ((string)compl.Model.GetValue(iter, 1)).Contains(key, StringComparison.OrdinalIgnoreCase) || // region + ((string)compl.Model.GetValue(iter, 2)).StartsWith(key, StringComparison.OrdinalIgnoreCase) || // abbr + ((string)compl.Model.GetValue(iter, 0))[3..].StartsWith(key); // offset + } + + private void SystemTimeSpin_ValueChanged(object sender, EventArgs e) + { + int year = _systemTimeYearSpin.ValueAsInt; + int month = _systemTimeMonthSpin.ValueAsInt; + int day = _systemTimeDaySpin.ValueAsInt; + int hour = _systemTimeHourSpin.ValueAsInt; + int minute = _systemTimeMinuteSpin.ValueAsInt; + + if (!DateTime.TryParse(year + "-" + month + "-" + day + " " + hour + ":" + minute, out DateTime newTime)) + { + UpdateSystemTimeSpinners(); + + return; + } + + newTime = newTime.AddSeconds(DateTime.Now.Second).AddMilliseconds(DateTime.Now.Millisecond); + + long systemTimeOffset = (long)Math.Ceiling((newTime - DateTime.Now).TotalMinutes) * 60L; + + if (_systemTimeOffset != systemTimeOffset) + { + _systemTimeOffset = systemTimeOffset; + UpdateSystemTimeSpinners(); + } + } + + private void AddDir_Pressed(object sender, EventArgs args) + { + if (Directory.Exists(_addGameDirBox.Buffer.Text)) + { + _gameDirsBoxStore.AppendValues(_addGameDirBox.Buffer.Text); + _directoryChanged = true; + } + else + { + FileChooserNative fileChooser = new("Choose the game directory to add to the list", this, FileChooserAction.SelectFolder, "Add", "Cancel") + { + SelectMultiple = true, + }; + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + _directoryChanged = false; + foreach (string directory in fileChooser.Filenames) + { + if (_gameDirsBoxStore.GetIterFirst(out TreeIter treeIter)) + { + do + { + if (directory.Equals((string)_gameDirsBoxStore.GetValue(treeIter, 0))) + { + break; + } + } while (_gameDirsBoxStore.IterNext(ref treeIter)); + } + + if (!_directoryChanged) + { + _gameDirsBoxStore.AppendValues(directory); + } + } + + _directoryChanged = true; + } + + fileChooser.Dispose(); + } + + _addGameDirBox.Buffer.Text = ""; + + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); + } + + private void RemoveDir_Pressed(object sender, EventArgs args) + { + TreeSelection selection = _gameDirsBox.Selection; + + if (selection.GetSelected(out TreeIter treeIter)) + { + _gameDirsBoxStore.Remove(ref treeIter); + + _directoryChanged = true; + } + + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); + } + + private void CustThemeToggle_Activated(object sender, EventArgs args) + { + _custThemePath.Sensitive = _custThemeToggle.Active; + _custThemePathLabel.Sensitive = _custThemeToggle.Active; + _browseThemePath.Sensitive = _custThemeToggle.Active; + } + + private void BrowseThemeDir_Pressed(object sender, EventArgs args) + { + using (FileChooserNative fileChooser = new("Choose the theme to load", this, FileChooserAction.Open, "Select", "Cancel")) + { + FileFilter filter = new() + { + Name = "Theme Files", + }; + filter.AddPattern("*.css"); + + fileChooser.AddFilter(filter); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + _custThemePath.Buffer.Text = fileChooser.Filename; + } + } + + _browseThemePath.SetStateFlags(StateFlags.Normal, true); + } + + private void ConfigureController_Pressed(object sender, PlayerIndex playerIndex) + { + ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); + + ControllerWindow controllerWindow = new(_parent, playerIndex); + + controllerWindow.SetSizeRequest((int)(controllerWindow.DefaultWidth * Program.WindowScaleFactor), (int)(controllerWindow.DefaultHeight * Program.WindowScaleFactor)); + controllerWindow.Show(); + } + + private void VolumeSlider_OnChange(object sender, EventArgs args) + { + ConfigurationState.Instance.System.AudioVolume.Value = (float)(_audioVolumeSlider.Value / 100); + } + + private void SaveToggle_Activated(object sender, EventArgs args) + { + SaveSettings(); + Dispose(); + } + + private void ApplyToggle_Activated(object sender, EventArgs args) + { + SaveSettings(); + } + + private void CloseToggle_Activated(object sender, EventArgs args) + { + ConfigurationState.Instance.System.AudioVolume.Value = _previousVolumeLevel; + Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade new file mode 100644 index 00000000..f0dbd6b6 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/SettingsWindow.glade @@ -0,0 +1,3221 @@ + + + + + + 3 + 1 + 10 + + + 101 + 1 + 5 + 1 + + + 1 + 31 + 1 + 5 + + + 23 + 1 + 5 + + + 59 + 1 + 5 + + + 1 + 12 + 1 + 5 + + + 2000 + 2060 + 1 + 10 + + + 0 + True + True + + + False + Ryujinx - Settings + True + center + 650 + 650 + + + True + False + vertical + + + True + True + in + + + True + False + + + True + True + + + True + False + 5 + 10 + 5 + vertical + + + True + False + 5 + 5 + vertical + + + True + False + start + 5 + General + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + vertical + + + Enable Discord Rich Presence + True + True + False + Choose whether or not to display Ryujinx on your "currently playing" Discord activity + start + True + + + False + True + 5 + 0 + + + + + Check for Updates on Launch + True + True + False + start + True + + + False + True + 5 + 1 + + + + + Show "Confirm Exit" Dialog + True + True + False + start + True + + + False + True + 5 + 2 + + + + + True + False + + + True + False + end + Hide Cursor: + + + False + True + 5 + 2 + + + + + Never + True + True + False + start + 5 + 5 + True + True + + + False + True + 3 + + + + + On Idle + True + True + False + start + 5 + 5 + True + _hideCursorNever + + + False + True + 4 + + + + + Always + True + True + False + start + 5 + 5 + True + _hideCursorNever + + + False + True + 5 + + + + + False + True + 5 + 4 + + + + + True + True + 1 + + + + + False + True + 5 + 1 + + + + + True + False + 5 + 5 + + + False + True + 5 + 2 + + + + + True + False + 5 + 5 + vertical + + + True + False + start + 5 + Game Directories + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + vertical + + + True + True + 10 + in + + + True + True + False + False + + + + + + + + + True + True + 0 + + + + + True + False + + + True + True + Enter a game directory to add to the list + + + True + True + 0 + + + + + Add + 80 + True + True + True + Add a game directory to the list + 5 + + + + False + True + 1 + + + + + Remove + 80 + True + True + True + Remove selected game directory + 5 + + + + False + True + 3 + + + + + False + True + 1 + + + + + True + True + 1 + + + + + True + True + 5 + 4 + + + + + True + False + 5 + 5 + + + False + True + 5 + 5 + + + + + True + False + 5 + 5 + vertical + + + True + False + start + 5 + Themes + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + vertical + + + Use Custom Theme + True + True + False + Enable or disable custom themes in the GUI + start + True + + + + False + True + 5 + 1 + + + + + True + False + + + True + False + Path to custom GUI theme + Custom Theme Path: + + + False + True + 5 + 0 + + + + + True + True + Path to custom GUI theme + center + + + True + True + 1 + + + + + Browse... + 80 + True + True + True + Browse for a custom GUI theme + 5 + + + + False + True + 2 + + + + + False + True + 10 + 2 + + + + + False + True + 1 + + + + + False + True + 5 + 6 + + + + + + + True + False + General + + + False + + + + + True + False + 5 + 10 + 5 + vertical + + + True + False + 5 + 5 + + + Enable Docked Mode + True + True + False + Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality. Configure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode. Leave ON if unsure. + True + + + False + True + 10 + 0 + + + + + Direct Keyboard Access + True + True + False + Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device. + True + + + False + False + 10 + 1 + + + + + Direct Mouse Access + True + True + False + Direct mouse access (HID) support. Provides games access to your mouse as a pointing device. + True + + + False + False + 10 + 2 + + + + + False + True + 5 + 0 + + + + + True + False + + + False + True + 1 + + + + + + True + False + center + center + 20 + + + True + False + vertical + + + True + False + 20 + 20 + Player 1 + + + False + True + 0 + + + + + Configure + True + True + True + 20 + 20 + 20 + 20 + + + False + True + 1 + + + + + 0 + 0 + + + + + True + False + vertical + + + True + False + 20 + 20 + Player 3 + + + False + True + 0 + + + + + Configure + True + True + True + 20 + 20 + 20 + 20 + + + False + True + 1 + + + + + 4 + 0 + + + + + True + False + vertical + + + True + False + 20 + 20 + Player 2 + + + False + True + 0 + + + + + Configure + True + True + True + 20 + 20 + 20 + 20 + + + False + True + 1 + + + + + 2 + 0 + + + + + True + False + vertical + + + True + False + 20 + 20 + Handheld + + + False + True + 0 + + + + + Configure + True + True + True + 20 + 20 + 20 + 20 + + + False + True + 1 + + + + + 4 + 4 + + + + + True + False + vertical + + + True + False + 20 + 20 + Player 6 + + + False + True + 0 + + + + + Configure + True + True + True + 20 + 20 + 20 + 20 + + + False + True + 1 + + + + + 4 + 2 + + + + + True + False + vertical + + + True + False + 20 + 20 + Player 5 + + + False + True + 0 + + + + + Configure + True + True + True + 20 + 20 + 20 + 20 + + + False + True + 1 + + + + + 2 + 2 + + + + + True + False + vertical + + + True + False + 20 + 20 + Player 7 + + + False + True + 0 + + + + + Configure + True + True + True + 20 + 20 + 20 + 20 + + + False + True + 1 + + + + + 0 + 4 + + + + + True + False + vertical + + + True + False + 20 + 20 + Player 4 + + + False + True + 0 + + + + + Configure + True + True + True + 20 + 20 + 20 + 20 + + + False + True + 1 + + + + + 0 + 2 + + + + + True + False + vertical + + + True + False + 20 + 20 + Player 8 + + + False + True + 0 + + + + + Configure + True + True + True + 20 + 20 + 20 + 20 + + + False + True + 1 + + + + + 2 + 4 + + + + + True + False + + + 1 + 0 + + + + + True + False + + + 3 + 0 + + + + + True + False + + + 3 + 2 + + + + + True + False + + + 3 + 4 + + + + + True + False + + + 1 + 2 + + + + + True + False + + + 1 + 4 + + + + + True + False + + + 1 + 1 + + + + + True + False + + + 1 + 3 + + + + + True + False + + + 3 + 1 + + + + + True + False + + + 3 + 3 + + + + + True + False + + + 0 + 1 + + + + + True + False + + + 2 + 1 + + + + + True + False + + + 4 + 1 + + + + + True + False + + + 0 + 3 + + + + + True + False + + + 2 + 3 + + + + + True + False + + + 4 + 3 + + + + + True + True + 2 + + + + + True + False + + + False + True + 3 + + + + + 1 + + + + + True + False + Input + + + 1 + False + + + + + True + False + 5 + 10 + 5 + vertical + + + True + False + start + 5 + 5 + vertical + + + True + False + start + 5 + Core + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + vertical + + + True + False + + + True + False + Change System Region + end + System Region: + + + False + True + 5 + 2 + + + + + True + False + Change System Region + 5 + + Japan + USA + Europe + Australia + China + Korea + Taiwan + + + + False + True + 3 + + + + + False + True + 5 + 0 + + + + + True + False + + + True + False + Change System Language + end + System Language: + + + False + True + 5 + 0 + + + + + True + False + Change System Language + + American English + British English + Canadian French + Chinese + Dutch + French + German + Italian + Japanese + Korean + Latin American Spanish + Portuguese + Russian + Simplified Chinese + Spanish + Taiwanese + Traditional Chinese + Brazilian Portuguese + + + + False + True + 1 + + + + + False + True + 5 + 1 + + + + + True + False + + + True + False + Change System TimeZone + end + System TimeZone: + + + False + True + 5 + 1 + + + + + True + True + Change System TimeZone + 5 + _systemTimeZoneCompletion + + + False + True + 2 + + + + + False + True + 5 + 2 + + + + + True + False + + + True + False + Change System Time + end + System Time: + + + False + True + 5 + 0 + + + + + True + True + 2000 + vertical + _systemTimeYearSpinAdjustment + True + 2000 + + + False + True + 1 + + + + + True + False + end + - + + + False + True + 5 + 2 + + + + + True + True + 1 + vertical + _systemTimeMonthSpinAdjustment + True + 1 + + + False + True + 3 + + + + + True + False + end + - + + + False + True + 5 + 4 + + + + + True + True + 1 + vertical + _systemTimeDaySpinAdjustment + True + 1 + + + False + True + 5 + + + + + True + True + 0 + vertical + _systemTimeHourSpinAdjustment + True + + + False + True + 6 + + + + + True + False + end + : + + + False + True + 5 + 7 + + + + + True + True + 0 + vertical + _systemTimeMinuteSpinAdjustment + True + + + False + True + 8 + + + + + False + True + 5 + 3 + + + + + Enable VSync + True + True + False + Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck. Can be toggled in-game with a hotkey of your preference. We recommend doing this if you plan on disabling it. Leave ON if unsure. + start + 5 + 5 + True + + + False + True + 4 + + + + + Enable PPTC (Profiled Persistent Translation Cache) + True + True + False + Saves translated JIT functions so that they do not need to be translated every time the game loads. Reduces stuttering and significantly speeds up boot times after the first boot of a game. Leave ON if unsure. + start + 5 + 5 + True + + + False + True + 6 + + + + + Enable Guest Internet Access + True + True + False + Allows the emulated application to connect to the Internet. Games with a LAN mode can connect to each other when this is enabled and the systems are connected to the same access point. This includes real consoles as well. Does NOT allow connecting to Nintendo servers. May cause crashing in certain games that try to connect to the Internet. Leave OFF if unsure. + start + 5 + 5 + True + + + False + True + 7 + + + + + Enable FS Integrity Checks + True + True + False + Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log. Has no impact on performance and is meant to help troubleshooting. Leave ON if unsure. + start + 5 + 5 + True + + + False + True + 8 + + + + + True + True + 1 + + + + + True + False + + + + + + True + False + Changes the backend used to render audio. SDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound. Set to SDL2 if unsure. + end + 5 + Audio Backend: + + + False + True + 5 + 2 + + + + + False + True + 5 + 2 + + + + + True + False + + + + + + True + False + Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance. Set to HOST UNCHECKED if unsure. + end + 5 + Memory Manager Mode: + + + False + True + 5 + 2 + + + + + Software + True + True + False + Use a software page table for address translation. Highest accuracy but slowest performance. + start + 5 + 5 + True + + + False + True + 3 + + + + + Host (fast) + True + True + False + Directly map memory in the host address space. Much faster JIT compilation and execution. + start + 5 + 5 + True + _mmSoftware + + + False + True + 4 + + + + + Host Unchecked (fastest, unsafe) + True + True + False + Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode. + start + 5 + 5 + True + _mmSoftware + + + False + True + 5 + + + + + False + True + 5 + 3 + + + + + False + True + 5 + 0 + + + + + True + False + 5 + 5 + + + False + True + 5 + 1 + + + + + True + False + start + 5 + 5 + vertical + + + True + False + + + True + False + start + 5 + Hacks + + + + + + False + True + 0 + + + + + True + False + start + 5 + (may cause instability) + + + False + True + 1 + + + + + False + True + 1 + + + + + True + False + 10 + 10 + vertical + + + Use alternative memory layout (Developers) + True + True + False + Utilizes an alternative MemoryMode layout to mimic a Switch development model. This is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance. Leave OFF if unsure. + start + 5 + 5 + True + + + False + True + 0 + + + + + Ignore Missing Services + True + True + False + Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games. Leave OFF if unsure. + start + 5 + 5 + True + + + False + True + 1 + + + + + True + True + 2 + + + + + False + True + 5 + 4 + + + + + 2 + + + + + True + False + end + System + + + 2 + False + + + + + True + False + 5 + vertical + + + True + False + 5 + 5 + vertical + + + True + False + start + 5 + 5 + 5 + Features + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + vertical + + + True + False + 5 + 5 + + + True + False + Executes graphics backend commands on a second thread. Speeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading. Set to AUTO if unsure. + Graphics Backend Multithreading: + + + False + True + 5 + 0 + + + + + True + False + Executes graphics backend commands on a second thread. Speeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading. Set to AUTO if unsure. + -1 + + Auto + Off + On + + + + False + True + 1 + + + + + False + True + 5 + 0 + + + + + True + False + 5 + 5 + + + True + False + Graphics Backend to use + Graphics Backend: + + + False + True + 5 + 0 + + + + + True + False + Graphics Backend to use + -1 + + Vulkan + OpenGL + + + + False + True + 1 + + + + + False + True + 5 + 1 + + + + + True + False + 5 + 5 + + + True + False + Preferred GPU (Vulkan only) + Preferred GPU: + + + False + True + 5 + 0 + + + + + True + False + Preferred GPU (Vulkan only) + -1 + + + False + True + 1 + + + + + False + True + 5 + 2 + + + + + False + True + 2 + + + + + False + True + 5 + 0 + + + + + True + False + 5 + 5 + vertical + + + True + False + start + 5 + 5 + 5 + Enhancements + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + vertical + + + Enable Shader Cache + True + True + False + Saves a disk shader cache which reduces stuttering in subsequent runs. Leave ON if unsure. + start + 5 + 5 + True + + + False + True + 0 + + + + + Enable Texture Recompression + True + True + False + Enables or disables Texture Recompression. Reduces VRAM usage at the cost of texture quality, and may also increase stuttering + start + 5 + 5 + True + + + False + True + 1 + + + + + Enable Macro HLE + True + True + False + Enables or disables high-level emulation of Macro code. Improves performance but may cause graphical glitches in some games + start + 5 + 5 + True + + + False + True + 2 + + + + + True + False + 5 + 5 + + + True + False + Resolution Scale applied to applicable render targets. + Resolution Scale: + + + False + True + 5 + 0 + + + + + True + False + Resolution Scale applied to applicable render targets. + 1 + + Native (720p/1080p) + 2x (1440p/2160p) + 3x (2160p/3240p) + 4x (2880p/4320p) + Custom (not recommended) + + + + False + True + 1 + + + + + True + True + Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash. + center + False + 1.0 + number + + + True + True + 2 + + + + + False + True + 5 + 3 + + + + + True + False + 5 + 5 + + + True + False + Applies a final effect to the game render + Post Processing Effect: + + + False + True + 5 + 0 + + + + + True + False + Applies anti-aliasing to the game render + 1 + + None + FXAA + SMAA Low + SMAA Medium + SMAA High + SMAA Ultra + + + + False + True + 1 + + + + + False + True + 5 + 4 + + + + + 100 + True + False + 5 + 5 + + + True + False + Enables Framebuffer Upscaling + Upscale: + + + False + True + 5 + 0 + + + + + True + False + Enables Framebuffer Upscaling + 1 + + Bilinear + Nearest + FSR + + + + False + True + 1 + + + + + 200 + True + True + 5 + _scalingFilterLevel + 1 + right + + + False + True + 3 + + + + + False + True + 5 + 5 + + + + + True + False + 5 + 5 + + + True + False + Level of Anisotropic Filtering (set to Auto to use the value requested by the game) + Anisotropic Filtering: + + + False + True + 5 + 0 + + + + + True + False + Level of Anisotropic Filtering (set to Auto to use the value requested by the game) + -1 + + Auto + 2x + 4x + 8x + 16x + + + + False + True + 1 + + + + + False + True + 5 + 6 + + + + + True + False + 5 + 5 + + + True + False + Aspect Ratio applied to the renderer window. + Aspect Ratio: + + + False + True + 5 + 0 + + + + + True + False + Aspect Ratio applied to the renderer window. + 1 + + 4:3 + 16:9 + 16:10 + 21:9 + 32:9 + Stretch to Fit Window + + + + False + True + 1 + + + + + False + True + 5 + 7 + + + + + False + True + 2 + + + + + False + True + 5 + 2 + + + + + True + False + + + False + True + 5 + 3 + + + + + True + False + 5 + 5 + vertical + + + True + False + start + 5 + 5 + 5 + Developer Options + + + + + + False + True + 0 + + + + + True + False + 10 + 10 + vertical + + + True + False + 5 + 5 + + + True + False + Graphics Shaders Dump Path + Graphics Shaders Dump Path: + + + False + True + 5 + 0 + + + + + True + True + Graphics Shaders Dump Path + center + False + + + True + True + 1 + + + + + False + True + 5 + 0 + + + + + False + True + 1 + + + + + False + True + 5 + 4 + + + + + 3 + + + + + True + False + Graphics + + + 3 + False + + + + + True + False + 5 + 10 + 5 + vertical + + + True + False + 5 + 5 + vertical + + + True + False + start + 5 + Logging + + + + + + False + True + 0 + + + + + True + False + start + 10 + 10 + vertical + + + Enable Logging to File + True + True + False + Saves console logging to a log file on disk. Does not affect performance. + start + 5 + 5 + True + + + False + True + 0 + + + + + Enable Stub Logs + True + True + False + Prints stub log messages in the console. Does not affect performance. + start + 5 + 5 + True + + + False + True + 3 + + + + + Enable Info Logs + True + True + False + Prints info log messages in the console. Does not affect performance. + start + 5 + 5 + True + + + False + True + 4 + + + + + Enable Warning Logs + True + True + False + Prints warning log messages in the console. Does not affect performance. + start + 5 + 5 + True + + + False + True + 5 + + + + + Enable Error Logs + True + True + False + Prints error log messages in the console. Does not affect performance. + start + 5 + 5 + True + + + False + True + 6 + + + + + Enable Guest Logs + True + True + False + Prints guest log messages in the console. Does not affect performance. + start + 5 + 5 + True + + + False + True + 7 + + + + + Enable Fs Access Logs + True + True + False + Enables FS access log output to the console. Possible modes are 0-3 + start + 5 + 5 + True + + + False + True + 8 + + + + + True + False + + + True + False + Enables FS access log output to the console. Possible modes are 0-3 + Fs Global Access Log Mode: + + + False + True + 5 + 0 + + + + + True + True + Enables FS access log output to the console. Possible modes are 0-3 + 0 + _fsLogSpinAdjustment + + + True + True + 1 + + + + + False + True + 5 + 9 + + + + + True + True + 1 + + + + + False + True + 5 + 0 + + + + + True + False + 5 + 5 + 10 + vertical + + + True + False + Use with care + start + 5 + Developer Options (WARNING: Will reduce performance) + + + + + + False + True + 0 + + + + + True + False + start + 10 + 10 + vertical + + + True + False + 5 + + + True + False + Requires appropriate log levels enabled. + Graphics Backend Log Level + + + False + True + 5 + 22 + + + + + True + False + Requires appropriate log levels enabled. + 5 + + + False + True + 22 + + + + + False + True + 1 + + + + + Enable Debug Logs + True + True + False + Prints debug log messages in the console. Only use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance. + start + 5 + 5 + True + + + False + True + 21 + + + + + Enable Trace Logs + True + True + False + Prints trace log messages in the console. Does not affect performance. + start + 5 + 5 + True + + + False + True + 22 + + + + + False + True + 1 + + + + + False + True + 5 + 22 + + + + + 4 + + + + + True + False + Logging + + + 4 + False + + + + + True + False + 5 + 10 + 5 + vertical + + + True + False + start + 5 + 5 + vertical + + + True + False + start + 5 + Multiplayer + + + + + + False + True + 0 + + + + + True + False + start + 10 + 10 + vertical + + + True + False + + + True + False + Change Multiplayer Mode + end + Mode: + + + False + True + 5 + 0 + + + + + True + False + Change Multiplayer Mode + Disabled + + Disabled + ldn_mitm + + + + False + True + 1 + + + + + False + True + 3 + + + + + True + True + 2 + + + + + False + True + 5 + 0 + + + + + True + False + start + 5 + 5 + vertical + + + True + False + start + 5 + LAN Mode + + + + + + False + True + 0 + + + + + True + False + start + 10 + 10 + vertical + + + True + False + + + True + False + The network interface used for LAN/LDN features + end + Network Interface: + + + False + True + 5 + 0 + + + + + True + False + The network interface used for LAN/LDN features + 0 + + Default + + + + False + True + 1 + + + + + False + True + 5 + 1 + + + + + True + False + start + 5 + To use LAN functionality in games, Enable Guest Internet Access must be checked in System. + True + + + False + True + 1 + + + + + True + True + 2 + + + + + False + True + 5 + 1 + + + + + 5 + + + + + True + False + Multiplayer + + + 5 + False + + + + + + + + + True + True + 0 + + + + + True + False + 5 + 3 + 3 + 5 + end + + + Save + True + True + True + + + + False + False + 0 + + + + + Close + True + True + True + + + + False + False + 1 + + + + + Apply + True + True + True + + + + True + True + 2 + + + + + False + False + 1 + + + + + + diff --git a/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs new file mode 100644 index 00000000..a08f5959 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.cs @@ -0,0 +1,234 @@ +using Gtk; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Ncm; +using LibHac.Ns; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.Loaders.Processes.Extensions; +using Ryujinx.HLE.Utilities; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Widgets; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using GUI = Gtk.Builder.ObjectAttribute; +using SpanHelpers = LibHac.Common.SpanHelpers; + +namespace Ryujinx.UI.Windows +{ + public class TitleUpdateWindow : Window + { + private readonly MainWindow _parent; + private readonly VirtualFileSystem _virtualFileSystem; + private readonly ApplicationData _applicationData; + private readonly string _updateJsonPath; + + private TitleUpdateMetadata _titleUpdateWindowData; + + private readonly Dictionary _radioButtonToPathDictionary; + private static readonly TitleUpdateMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + +#pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier + [GUI] Label _baseTitleInfoLabel; + [GUI] Box _availableUpdatesBox; + [GUI] RadioButton _noUpdateRadioButton; +#pragma warning restore CS0649, IDE0044 + + public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ApplicationData applicationData) : this(new Builder("Ryujinx.Gtk3.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, applicationData) { } + + private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, ApplicationData applicationData) : base(builder.GetRawOwnedObject("_titleUpdateWindow")) + { + _parent = parent; + + builder.Autoconnect(this); + + _applicationData = applicationData; + _virtualFileSystem = virtualFileSystem; + _updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, applicationData.IdBaseString, "updates.json"); + _radioButtonToPathDictionary = new Dictionary(); + + try + { + _titleUpdateWindowData = JsonHelper.DeserializeFromFile(_updateJsonPath, _serializerContext.TitleUpdateMetadata); + } + catch + { + _titleUpdateWindowData = new TitleUpdateMetadata + { + Selected = "", + Paths = new List(), + }; + } + + _baseTitleInfoLabel.Text = $"Updates Available for {applicationData.Name} [{applicationData.IdBaseString}]"; + + // Try to get updates from PFS first + AddUpdate(_applicationData.Path, true); + + foreach (string path in _titleUpdateWindowData.Paths) + { + AddUpdate(path); + } + + if (_titleUpdateWindowData.Selected == "") + { + _noUpdateRadioButton.Active = true; + } + else + { + foreach ((RadioButton update, var _) in _radioButtonToPathDictionary.Where(keyValuePair => keyValuePair.Value == _titleUpdateWindowData.Selected)) + { + update.Active = true; + } + } + } + + private void AddUpdate(string path, bool ignoreNotFound = false) + { + if (!File.Exists(path) || _radioButtonToPathDictionary.ContainsValue(path)) + { + return; + } + + IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks + ? IntegrityCheckLevel.ErrorOnInvalid + : IntegrityCheckLevel.None; + + try + { + using IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem); + + Dictionary updates = pfs.GetContentData(ContentMetaType.Patch, _virtualFileSystem, checkLevel); + + Nca patchNca = null; + Nca controlNca = null; + + if (updates.TryGetValue(_applicationData.Id, out ContentMetaData update)) + { + patchNca = update.GetNcaByType(_virtualFileSystem.KeySet, LibHac.Ncm.ContentType.Program); + controlNca = update.GetNcaByType(_virtualFileSystem.KeySet, LibHac.Ncm.ContentType.Control); + } + + if (controlNca != null && patchNca != null) + { + ApplicationControlProperty controlData = new(); + + using var nacpFile = new UniqueRef(); + + controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); + + string radioLabel = $"Version {controlData.DisplayVersionString.ToString()} - {path}"; + + if (System.IO.Path.GetExtension(path).ToLower() == ".xci") + { + radioLabel = "Bundled: " + radioLabel; + } + + RadioButton radioButton = new(radioLabel); + radioButton.JoinGroup(_noUpdateRadioButton); + + _availableUpdatesBox.Add(radioButton); + _radioButtonToPathDictionary.Add(radioButton, path); + + radioButton.Show(); + radioButton.Active = true; + } + else + { + if (!ignoreNotFound) + { + GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!"); + } + } + } + catch (Exception exception) + { + GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}"); + } + } + + private void RemoveUpdates(bool removeSelectedOnly = false) + { + foreach (RadioButton radioButton in _noUpdateRadioButton.Group) + { + if (radioButton.Label != "No Update" && (!removeSelectedOnly || radioButton.Active)) + { + _availableUpdatesBox.Remove(radioButton); + _radioButtonToPathDictionary.Remove(radioButton); + radioButton.Dispose(); + } + } + } + + private void AddButton_Clicked(object sender, EventArgs args) + { + using FileChooserNative fileChooser = new("Select update files", this, FileChooserAction.Open, "Add", "Cancel"); + + fileChooser.SelectMultiple = true; + + FileFilter filter = new() + { + Name = "Switch Game Updates", + }; + filter.AddPattern("*.nsp"); + + fileChooser.AddFilter(filter); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + foreach (string path in fileChooser.Filenames) + { + AddUpdate(path); + } + } + } + + private void RemoveButton_Clicked(object sender, EventArgs args) + { + RemoveUpdates(true); + } + + private void RemoveAllButton_Clicked(object sender, EventArgs args) + { + RemoveUpdates(); + } + + private void SaveButton_Clicked(object sender, EventArgs args) + { + _titleUpdateWindowData.Paths.Clear(); + _titleUpdateWindowData.Selected = ""; + + foreach (string paths in _radioButtonToPathDictionary.Values) + { + _titleUpdateWindowData.Paths.Add(paths); + } + + foreach (RadioButton radioButton in _noUpdateRadioButton.Group) + { + if (radioButton.Active) + { + _titleUpdateWindowData.Selected = _radioButtonToPathDictionary.TryGetValue(radioButton, out string updatePath) ? updatePath : ""; + } + } + + JsonHelper.SerializeToFile(_updateJsonPath, _titleUpdateWindowData, _serializerContext.TitleUpdateMetadata); + + _parent.UpdateGameTable(); + + Dispose(); + } + + private void CancelButton_Clicked(object sender, EventArgs args) + { + Dispose(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.glade b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.glade new file mode 100644 index 00000000..cfbac86d --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/TitleUpdateWindow.glade @@ -0,0 +1,214 @@ + + + + + + False + Ryujinx - Title Update Manager + True + center + 550 + 250 + + + True + False + vertical + + + True + False + vertical + + + True + False + 10 + 10 + 10 + 10 + Available Updates + + + False + True + 0 + + + + + True + True + 10 + 10 + in + + + True + False + + + True + False + vertical + + + No Update + True + True + False + True + True + + + False + True + 0 + + + + + + + + + True + True + 1 + + + + + True + True + 0 + + + + + True + False + + + True + False + 10 + 10 + start + + + Add + True + True + True + Adds an update to this list + 10 + + + + True + True + 0 + + + + + Remove + True + True + True + Removes the selected update + 10 + + + + True + True + 1 + + + + + Remove All + True + True + True + Removes all the updates + 10 + + + + True + True + 2 + + + + + True + True + 0 + + + + + True + False + 10 + 10 + end + + + Save + True + True + True + 10 + 2 + 2 + + + + True + True + 0 + + + + + Cancel + True + True + True + 10 + 2 + 2 + + + + True + True + 1 + + + + + True + True + 1 + + + + + False + True + 1 + + + + + + + + + diff --git a/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.Designer.cs b/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.Designer.cs new file mode 100644 index 00000000..bc5a18f9 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.Designer.cs @@ -0,0 +1,255 @@ +using Gtk; +using Pango; +using System; + +namespace Ryujinx.UI.Windows +{ + public partial class UserProfilesManagerWindow : Window + { + private Box _mainBox; + private Label _selectedLabel; + private Box _selectedUserBox; + private Image _selectedUserImage; + private Box _selectedUserInfoBox; + private Entry _selectedUserNameEntry; + private Label _selectedUserIdLabel; + private Box _selectedUserButtonsBox; + private Button _saveProfileNameButton; + private Button _changeProfileImageButton; + private Box _usersTreeViewBox; + private Label _availableUsersLabel; + private ScrolledWindow _usersTreeViewWindow; + private ListStore _tableStore; + private TreeView _usersTreeView; + private Box _bottomBox; + private Button _addButton; + private Button _deleteButton; + private Button _closeButton; + + private void InitializeComponent() + { + // + // UserProfilesManagerWindow + // + CanFocus = false; + Resizable = false; + Modal = true; + WindowPosition = WindowPosition.Center; + DefaultWidth = 620; + DefaultHeight = 548; + TypeHint = Gdk.WindowTypeHint.Dialog; + + // + // _mainBox + // + _mainBox = new Box(Orientation.Vertical, 0); + + // + // _selectedLabel + // + _selectedLabel = new Label("Selected User Profile:") + { + Margin = 15, + Attributes = new AttrList(), + Halign = Align.Start, + }; + _selectedLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); + + // + // _viewBox + // + _usersTreeViewBox = new Box(Orientation.Vertical, 0); + + // + // _SelectedUserBox + // + _selectedUserBox = new Box(Orientation.Horizontal, 0) + { + MarginStart = 30, + }; + + // + // _selectedUserImage + // + _selectedUserImage = new Image(); + + // + // _selectedUserInfoBox + // + _selectedUserInfoBox = new Box(Orientation.Vertical, 0) + { + Homogeneous = true, + }; + + // + // _selectedUserNameEntry + // + _selectedUserNameEntry = new Entry("") + { + MarginStart = 15, + MaxLength = (int)MaxProfileNameLength, + }; + _selectedUserNameEntry.KeyReleaseEvent += SelectedUserNameEntry_KeyReleaseEvent; + + // + // _selectedUserIdLabel + // + _selectedUserIdLabel = new Label("") + { + MarginTop = 15, + MarginStart = 15, + }; + + // + // _selectedUserButtonsBox + // + _selectedUserButtonsBox = new Box(Orientation.Vertical, 0) + { + MarginEnd = 30, + }; + + // + // _saveProfileNameButton + // + _saveProfileNameButton = new Button() + { + Label = "Save Profile Name", + CanFocus = true, + ReceivesDefault = true, + Sensitive = false, + }; + _saveProfileNameButton.Clicked += EditProfileNameButton_Pressed; + + // + // _changeProfileImageButton + // + _changeProfileImageButton = new Button() + { + Label = "Change Profile Image", + CanFocus = true, + ReceivesDefault = true, + MarginTop = 10, + }; + _changeProfileImageButton.Clicked += ChangeProfileImageButton_Pressed; + + // + // _availableUsersLabel + // + _availableUsersLabel = new Label("Available User Profiles:") + { + Margin = 15, + Attributes = new AttrList(), + Halign = Align.Start, + }; + _availableUsersLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold)); + + // + // _usersTreeViewWindow + // + _usersTreeViewWindow = new ScrolledWindow() + { + ShadowType = ShadowType.In, + CanFocus = true, + Expand = true, + MarginStart = 30, + MarginEnd = 30, + MarginBottom = 15, + }; + + // + // _tableStore + // + _tableStore = new ListStore(typeof(bool), typeof(Gdk.Pixbuf), typeof(string), typeof(Gdk.RGBA)); + + // + // _usersTreeView + // + _usersTreeView = new TreeView(_tableStore) + { + HoverSelection = true, + HeadersVisible = false, + }; + _usersTreeView.RowActivated += UsersTreeView_Activated; + + // + // _bottomBox + // + _bottomBox = new Box(Orientation.Horizontal, 0) + { + MarginStart = 30, + MarginEnd = 30, + MarginBottom = 15, + }; + + // + // _addButton + // + _addButton = new Button() + { + Label = "Add New Profile", + CanFocus = true, + ReceivesDefault = true, + HeightRequest = 35, + }; + _addButton.Clicked += AddButton_Pressed; + + // + // _deleteButton + // + _deleteButton = new Button() + { + Label = "Delete Selected Profile", + CanFocus = true, + ReceivesDefault = true, + HeightRequest = 35, + MarginStart = 10, + }; + _deleteButton.Clicked += DeleteButton_Pressed; + + // + // _closeButton + // + _closeButton = new Button() + { + Label = "Close", + CanFocus = true, + ReceivesDefault = true, + HeightRequest = 35, + WidthRequest = 80, + }; + _closeButton.Clicked += CloseButton_Pressed; + + ShowComponent(); + } + + private void ShowComponent() + { + _usersTreeViewWindow.Add(_usersTreeView); + + _usersTreeViewBox.Add(_usersTreeViewWindow); + _bottomBox.PackStart(_addButton, false, false, 0); + _bottomBox.PackStart(_deleteButton, false, false, 0); + _bottomBox.PackEnd(_closeButton, false, false, 0); + + _selectedUserInfoBox.Add(_selectedUserNameEntry); + _selectedUserInfoBox.Add(_selectedUserIdLabel); + + _selectedUserButtonsBox.Add(_saveProfileNameButton); + _selectedUserButtonsBox.Add(_changeProfileImageButton); + + _selectedUserBox.Add(_selectedUserImage); + _selectedUserBox.PackStart(_selectedUserInfoBox, false, false, 0); + _selectedUserBox.PackEnd(_selectedUserButtonsBox, false, false, 0); + + _mainBox.PackStart(_selectedLabel, false, false, 0); + _mainBox.PackStart(_selectedUserBox, false, true, 0); + _mainBox.PackStart(_availableUsersLabel, false, false, 0); + _mainBox.Add(_usersTreeViewBox); + _mainBox.Add(_bottomBox); + + Add(_mainBox); + + ShowAll(); + } + } +} diff --git a/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs new file mode 100644 index 00000000..77afc5d1 --- /dev/null +++ b/src/Ryujinx.Gtk3/UI/Windows/UserProfilesManagerWindow.cs @@ -0,0 +1,326 @@ +using Gtk; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Widgets; +using SkiaSharp; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.UI.Windows +{ + public partial class UserProfilesManagerWindow : Window + { + private const uint MaxProfileNameLength = 0x20; + + private readonly AccountManager _accountManager; + private readonly ContentManager _contentManager; + + private byte[] _bufferImageProfile; + private string _tempNewProfileName; + + private Gdk.RGBA _selectedColor; + + private readonly ManualResetEvent _avatarsPreloadingEvent = new(false); + + public UserProfilesManagerWindow(AccountManager accountManager, ContentManager contentManager, VirtualFileSystem virtualFileSystem) : base($"Ryujinx {Program.Version} - Manage User Profiles") + { + Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); + + InitializeComponent(); + + _selectedColor.Red = 0.212; + _selectedColor.Green = 0.843; + _selectedColor.Blue = 0.718; + _selectedColor.Alpha = 1; + + _accountManager = accountManager; + _contentManager = contentManager; + + CellRendererToggle userSelectedToggle = new(); + userSelectedToggle.Toggled += UserSelectedToggle_Toggled; + + // NOTE: Uncomment following line when multiple selection of user profiles is supported. + //_usersTreeView.AppendColumn("Selected", userSelectedToggle, "active", 0); + _usersTreeView.AppendColumn("User Icon", new CellRendererPixbuf(), "pixbuf", 1); + _usersTreeView.AppendColumn("User Info", new CellRendererText(), "text", 2, "background-rgba", 3); + + _tableStore.SetSortColumnId(0, SortType.Descending); + + RefreshList(); + + if (_contentManager.GetCurrentFirmwareVersion() != null) + { + Task.Run(() => + { + AvatarWindow.PreloadAvatars(contentManager, virtualFileSystem); + _avatarsPreloadingEvent.Set(); + }); + } + } + + public void RefreshList() + { + _tableStore.Clear(); + + foreach (UserProfile userProfile in _accountManager.GetAllUsers()) + { + _tableStore.AppendValues(userProfile.AccountState == AccountState.Open, new Gdk.Pixbuf(userProfile.Image, 96, 96), $"{userProfile.Name}\n{userProfile.UserId}", Gdk.RGBA.Zero); + + if (userProfile.AccountState == AccountState.Open) + { + _selectedUserImage.Pixbuf = new Gdk.Pixbuf(userProfile.Image, 96, 96); + _selectedUserIdLabel.Text = userProfile.UserId.ToString(); + _selectedUserNameEntry.Text = userProfile.Name; + + _deleteButton.Sensitive = userProfile.UserId != AccountManager.DefaultUserId; + + _usersTreeView.Model.GetIterFirst(out TreeIter firstIter); + _tableStore.SetValue(firstIter, 3, _selectedColor); + } + } + } + + // + // Events + // + + private void UsersTreeView_Activated(object o, RowActivatedArgs args) + { + SelectUserTreeView(); + } + + private void UserSelectedToggle_Toggled(object o, ToggledArgs args) + { + SelectUserTreeView(); + } + + private void SelectUserTreeView() + { + // Get selected item informations. + _usersTreeView.Selection.GetSelected(out TreeIter selectedIter); + + Gdk.Pixbuf userPicture = (Gdk.Pixbuf)_tableStore.GetValue(selectedIter, 1); + + string userName = _tableStore.GetValue(selectedIter, 2).ToString().Split("\n")[0]; + string userId = _tableStore.GetValue(selectedIter, 2).ToString().Split("\n")[1]; + + // Unselect the first user. + _usersTreeView.Model.GetIterFirst(out TreeIter firstIter); + _tableStore.SetValue(firstIter, 0, false); + _tableStore.SetValue(firstIter, 3, Gdk.RGBA.Zero); + + // Set new informations. + _tableStore.SetValue(selectedIter, 0, true); + + _selectedUserImage.Pixbuf = userPicture; + _selectedUserNameEntry.Text = userName; + _selectedUserIdLabel.Text = userId; + _saveProfileNameButton.Sensitive = false; + + // Open the selected one. + _accountManager.OpenUser(new UserId(userId)); + + _deleteButton.Sensitive = userId != AccountManager.DefaultUserId.ToString(); + + _tableStore.SetValue(selectedIter, 3, _selectedColor); + } + + private void SelectedUserNameEntry_KeyReleaseEvent(object o, KeyReleaseEventArgs args) + { + if (_saveProfileNameButton.Sensitive == false) + { + _saveProfileNameButton.Sensitive = true; + } + } + + private void AddButton_Pressed(object sender, EventArgs e) + { + _tempNewProfileName = GtkDialog.CreateInputDialog(this, "Choose the Profile Name", "Please Enter a Profile Name", MaxProfileNameLength); + + if (_tempNewProfileName != "") + { + SelectProfileImage(true); + + if (_bufferImageProfile != null) + { + AddUser(); + } + } + } + + private void DeleteButton_Pressed(object sender, EventArgs e) + { + if (GtkDialog.CreateChoiceDialog("Delete User Profile", "Are you sure you want to delete the profile ?", "Deleting this profile will also delete all associated save data.")) + { + _accountManager.DeleteUser(GetSelectedUserId()); + + RefreshList(); + } + } + + private void EditProfileNameButton_Pressed(object sender, EventArgs e) + { + _saveProfileNameButton.Sensitive = false; + + _accountManager.SetUserName(GetSelectedUserId(), _selectedUserNameEntry.Text); + + RefreshList(); + } + + private void ProcessProfileImage(byte[] buffer) + { + using var image = SKBitmap.Decode(buffer); + + image.Resize(new SKImageInfo(256, 256), SKFilterQuality.High); + + using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream(); + + image.Encode(streamJpg, SKEncodedImageFormat.Jpeg, 80); + + _bufferImageProfile = streamJpg.ToArray(); + } + + private void ProfileImageFileChooser() + { + FileChooserNative fileChooser = new("Import Custom Profile Image", this, FileChooserAction.Open, "Import", "Cancel") + { + SelectMultiple = false, + }; + + FileFilter filter = new() + { + Name = "Custom Profile Images", + }; + filter.AddPattern("*.jpg"); + filter.AddPattern("*.jpeg"); + filter.AddPattern("*.png"); + filter.AddPattern("*.bmp"); + + fileChooser.AddFilter(filter); + + if (fileChooser.Run() == (int)ResponseType.Accept) + { + ProcessProfileImage(File.ReadAllBytes(fileChooser.Filename)); + } + + fileChooser.Dispose(); + } + + private void SelectProfileImage(bool newUser = false) + { + if (_contentManager.GetCurrentFirmwareVersion() == null) + { + ProfileImageFileChooser(); + } + else + { + Dictionary buttons = new() + { + { 0, "Import Image File" }, + { 1, "Select Firmware Avatar" }, + }; + + ResponseType responseDialog = GtkDialog.CreateCustomDialog("Profile Image Selection", + "Choose a Profile Image", + "You may import a custom profile image, or select an avatar from the system firmware.", + buttons, MessageType.Question); + + if (responseDialog == 0) + { + ProfileImageFileChooser(); + } + else if (responseDialog == (ResponseType)1) + { + AvatarWindow avatarWindow = new() + { + NewUser = newUser, + }; + + avatarWindow.DeleteEvent += AvatarWindow_DeleteEvent; + + avatarWindow.SetSizeRequest((int)(avatarWindow.DefaultWidth * Program.WindowScaleFactor), (int)(avatarWindow.DefaultHeight * Program.WindowScaleFactor)); + avatarWindow.Show(); + } + } + } + + private void ChangeProfileImageButton_Pressed(object sender, EventArgs e) + { + if (_contentManager.GetCurrentFirmwareVersion() != null) + { + _avatarsPreloadingEvent.WaitOne(); + } + + SelectProfileImage(); + + if (_bufferImageProfile != null) + { + SetUserImage(); + } + } + + private void AvatarWindow_DeleteEvent(object sender, DeleteEventArgs args) + { + _bufferImageProfile = ((AvatarWindow)sender).SelectedProfileImage; + + if (_bufferImageProfile != null) + { + if (((AvatarWindow)sender).NewUser) + { + AddUser(); + } + else + { + SetUserImage(); + } + } + } + + private void AddUser() + { + _accountManager.AddUser(_tempNewProfileName, _bufferImageProfile); + + _bufferImageProfile = null; + _tempNewProfileName = ""; + + RefreshList(); + } + + private void SetUserImage() + { + _accountManager.SetUserImage(GetSelectedUserId(), _bufferImageProfile); + + _bufferImageProfile = null; + + RefreshList(); + } + + private UserId GetSelectedUserId() + { + if (_usersTreeView.Model.GetIterFirst(out TreeIter iter)) + { + do + { + if ((bool)_tableStore.GetValue(iter, 0)) + { + break; + } + } + while (_usersTreeView.Model.IterNext(ref iter)); + } + + return new UserId(_tableStore.GetValue(iter, 2).ToString().Split("\n")[1]); + } + + private void CloseButton_Pressed(object sender, EventArgs e) + { + Close(); + } + } +} diff --git a/src/Ryujinx.HLE.Generators/CodeGenerator.cs b/src/Ryujinx.HLE.Generators/CodeGenerator.cs new file mode 100644 index 00000000..7e4848ad --- /dev/null +++ b/src/Ryujinx.HLE.Generators/CodeGenerator.cs @@ -0,0 +1,63 @@ +using System.Text; + +namespace Ryujinx.HLE.Generators +{ + class CodeGenerator + { + private const int IndentLength = 4; + + private readonly StringBuilder _sb; + private int _currentIndentCount; + + public CodeGenerator() + { + _sb = new StringBuilder(); + } + + public void EnterScope(string header = null) + { + if (header != null) + { + AppendLine(header); + } + + AppendLine("{"); + IncreaseIndentation(); + } + + public void LeaveScope(string suffix = "") + { + DecreaseIndentation(); + AppendLine($"}}{suffix}"); + } + + public void IncreaseIndentation() + { + _currentIndentCount++; + } + + public void DecreaseIndentation() + { + if (_currentIndentCount - 1 >= 0) + { + _currentIndentCount--; + } + } + + public void AppendLine() + { + _sb.AppendLine(); + } + + public void AppendLine(string text) + { + _sb.Append(' ', IndentLength * _currentIndentCount); + _sb.AppendLine(text); + } + + public override string ToString() + { + return _sb.ToString(); + } + } +} diff --git a/src/Ryujinx.HLE.Generators/IpcServiceGenerator.cs b/src/Ryujinx.HLE.Generators/IpcServiceGenerator.cs new file mode 100644 index 00000000..19fdbe19 --- /dev/null +++ b/src/Ryujinx.HLE.Generators/IpcServiceGenerator.cs @@ -0,0 +1,76 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Linq; + +namespace Ryujinx.HLE.Generators +{ + [Generator] + public class IpcServiceGenerator : ISourceGenerator + { + public void Execute(GeneratorExecutionContext context) + { + var syntaxReceiver = (ServiceSyntaxReceiver)context.SyntaxReceiver; + CodeGenerator generator = new CodeGenerator(); + + generator.AppendLine("using System;"); + generator.EnterScope($"namespace Ryujinx.HLE.HOS.Services.Sm"); + generator.EnterScope($"partial class IUserInterface"); + + generator.EnterScope($"public IpcService? GetServiceInstance(Type type, ServiceCtx context, object? parameter = null)"); + foreach (var className in syntaxReceiver.Types) + { + if (className.Modifiers.Any(SyntaxKind.AbstractKeyword) || className.Modifiers.Any(SyntaxKind.PrivateKeyword) || !className.AttributeLists.Any(x => x.Attributes.Any(y => y.ToString().StartsWith("Service")))) + continue; + var name = GetFullName(className, context).Replace("global::", ""); + if (!name.StartsWith("Ryujinx.HLE.HOS.Services")) + continue; + var constructors = className.ChildNodes().Where(x => x.IsKind(SyntaxKind.ConstructorDeclaration)).Select(y => y as ConstructorDeclarationSyntax); + + if (!constructors.Any(x => x.ParameterList.Parameters.Count >= 1)) + continue; + + if (constructors.Where(x => x.ParameterList.Parameters.Count >= 1).FirstOrDefault().ParameterList.Parameters[0].Type.ToString() == "ServiceCtx") + { + generator.EnterScope($"if (type == typeof({GetFullName(className, context)}))"); + if (constructors.Any(x => x.ParameterList.Parameters.Count == 2)) + { + var type = constructors.Where(x => x.ParameterList.Parameters.Count == 2).FirstOrDefault().ParameterList.Parameters[1].Type; + var model = context.Compilation.GetSemanticModel(type.SyntaxTree); + var typeSymbol = model.GetSymbolInfo(type).Symbol as INamedTypeSymbol; + var fullName = typeSymbol.ToString(); + generator.EnterScope("if (parameter != null)"); + generator.AppendLine($"return new {GetFullName(className, context)}(context, ({fullName})parameter);"); + generator.LeaveScope(); + } + + if (constructors.Any(x => x.ParameterList.Parameters.Count == 1)) + { + generator.AppendLine($"return new {GetFullName(className, context)}(context);"); + } + + generator.LeaveScope(); + } + } + + generator.AppendLine("return null;"); + generator.LeaveScope(); + + generator.LeaveScope(); + generator.LeaveScope(); + context.AddSource($"IUserInterface.g.cs", generator.ToString()); + } + + private string GetFullName(ClassDeclarationSyntax syntaxNode, GeneratorExecutionContext context) + { + var typeSymbol = context.Compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetDeclaredSymbol(syntaxNode); + + return typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + } + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new ServiceSyntaxReceiver()); + } + } +} diff --git a/src/Ryujinx.HLE.Generators/Ryujinx.HLE.Generators.csproj b/src/Ryujinx.HLE.Generators/Ryujinx.HLE.Generators.csproj new file mode 100644 index 00000000..eeab9c0e --- /dev/null +++ b/src/Ryujinx.HLE.Generators/Ryujinx.HLE.Generators.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.0 + true + true + Generated + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/src/Ryujinx.HLE.Generators/ServiceSyntaxReceiver.cs b/src/Ryujinx.HLE.Generators/ServiceSyntaxReceiver.cs new file mode 100644 index 00000000..e4269cb9 --- /dev/null +++ b/src/Ryujinx.HLE.Generators/ServiceSyntaxReceiver.cs @@ -0,0 +1,24 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; + +namespace Ryujinx.HLE.Generators +{ + internal class ServiceSyntaxReceiver : ISyntaxReceiver + { + public HashSet Types = new HashSet(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is ClassDeclarationSyntax classDeclaration) + { + if (classDeclaration.BaseList == null) + { + return; + } + + Types.Add(classDeclaration); + } + } + } +} diff --git a/src/Ryujinx.HLE/AssemblyInfo.cs b/src/Ryujinx.HLE/AssemblyInfo.cs new file mode 100644 index 00000000..a3b3e594 --- /dev/null +++ b/src/Ryujinx.HLE/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Ryujinx.Tests")] diff --git a/src/Ryujinx.HLE/Exceptions/GuestBrokeExecutionException.cs b/src/Ryujinx.HLE/Exceptions/GuestBrokeExecutionException.cs new file mode 100644 index 00000000..4e06ab77 --- /dev/null +++ b/src/Ryujinx.HLE/Exceptions/GuestBrokeExecutionException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.HLE.Exceptions +{ + public class GuestBrokeExecutionException : Exception + { + private const string ExMsg = "The guest program broke execution!"; + + public GuestBrokeExecutionException() : base(ExMsg) { } + } +} diff --git a/src/Ryujinx.HLE/Exceptions/InternalServiceException.cs b/src/Ryujinx.HLE/Exceptions/InternalServiceException.cs new file mode 100644 index 00000000..ad764889 --- /dev/null +++ b/src/Ryujinx.HLE/Exceptions/InternalServiceException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.Exceptions +{ + class InternalServiceException : Exception + { + public InternalServiceException(string message) : base(message) { } + } +} diff --git a/src/Ryujinx.HLE/Exceptions/InvalidFirmwarePackageException.cs b/src/Ryujinx.HLE/Exceptions/InvalidFirmwarePackageException.cs new file mode 100644 index 00000000..fd911032 --- /dev/null +++ b/src/Ryujinx.HLE/Exceptions/InvalidFirmwarePackageException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.Exceptions +{ + class InvalidFirmwarePackageException : Exception + { + public InvalidFirmwarePackageException(string message) : base(message) { } + } +} diff --git a/src/Ryujinx.HLE/Exceptions/InvalidNpdmException.cs b/src/Ryujinx.HLE/Exceptions/InvalidNpdmException.cs new file mode 100644 index 00000000..78d75680 --- /dev/null +++ b/src/Ryujinx.HLE/Exceptions/InvalidNpdmException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.Exceptions +{ + public class InvalidNpdmException : Exception + { + public InvalidNpdmException(string message) : base(message) { } + } +} diff --git a/src/Ryujinx.HLE/Exceptions/InvalidStructLayoutException.cs b/src/Ryujinx.HLE/Exceptions/InvalidStructLayoutException.cs new file mode 100644 index 00000000..6852dcef --- /dev/null +++ b/src/Ryujinx.HLE/Exceptions/InvalidStructLayoutException.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.Exceptions +{ + public class InvalidStructLayoutException : Exception + { + static readonly Type _structType = typeof(T); + + public InvalidStructLayoutException(string message) : base(message) { } + + public InvalidStructLayoutException(int expectedSize) + : base($"Type {_structType.Name} has the wrong size. Expected: {expectedSize} bytes, got: {Unsafe.SizeOf()} bytes") { } + } +} diff --git a/src/Ryujinx.HLE/Exceptions/InvalidSystemResourceException.cs b/src/Ryujinx.HLE/Exceptions/InvalidSystemResourceException.cs new file mode 100644 index 00000000..9e95c0cf --- /dev/null +++ b/src/Ryujinx.HLE/Exceptions/InvalidSystemResourceException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.Exceptions +{ + public class InvalidSystemResourceException : Exception + { + public InvalidSystemResourceException(string message) : base(message) { } + } +} diff --git a/src/Ryujinx.HLE/Exceptions/ServiceNotImplementedException.cs b/src/Ryujinx.HLE/Exceptions/ServiceNotImplementedException.cs new file mode 100644 index 00000000..408b5baa --- /dev/null +++ b/src/Ryujinx.HLE/Exceptions/ServiceNotImplementedException.cs @@ -0,0 +1,162 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services; +using System; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; + +namespace Ryujinx.HLE.Exceptions +{ + [Serializable] + internal class ServiceNotImplementedException : Exception + { + public IpcService Service { get; } + public ServiceCtx Context { get; } + public IpcMessage Request { get; } + + public ServiceNotImplementedException(IpcService service, ServiceCtx context) + : this(service, context, "The service call is not implemented.") { } + + public ServiceNotImplementedException(IpcService service, ServiceCtx context, string message) : base(message) + { + Service = service; + Context = context; + Request = context.Request; + } + + public ServiceNotImplementedException(IpcService service, ServiceCtx context, string message, Exception inner) : base(message, inner) + { + Service = service; + Context = context; + Request = context.Request; + } + + public override string Message + { + get + { + return base.Message + Environment.NewLine + Environment.NewLine + BuildMessage(); + } + } + + private string BuildMessage() + { + StringBuilder sb = new(); + + // Print the IPC command details (service name, command ID, and handler) + (Type callingType, MethodBase callingMethod) = WalkStackTrace(new StackTrace(this)); + + if (callingType != null && callingMethod != null) + { + // If the type is past 0xF, we are using TIPC + var ipcCommands = Request.Type > IpcMessageType.TipcCloseSession ? Service.TipcCommands : Service.CmifCommands; + + // Find the handler for the method called + var ipcHandler = ipcCommands.FirstOrDefault(x => x.Value == callingMethod); + var ipcCommandId = ipcHandler.Key; + var ipcMethod = ipcHandler.Value; + + if (ipcMethod != null) + { + sb.AppendLine($"Service Command: {Service.GetType().FullName}: {ipcCommandId} ({ipcMethod.Name})"); + sb.AppendLine(); + } + } + + sb.AppendLine("Guest Stack Trace:"); + sb.AppendLine(Context.Thread.GetGuestStackTrace()); + + // Print buffer information + if (Request.PtrBuff.Count > 0 || + Request.SendBuff.Count > 0 || + Request.ReceiveBuff.Count > 0 || + Request.ExchangeBuff.Count > 0 || + Request.RecvListBuff.Count > 0) + { + sb.AppendLine("Buffer Information:"); + + if (Request.PtrBuff.Count > 0) + { + sb.AppendLine("\tPtrBuff:"); + + foreach (var buff in Request.PtrBuff) + { + sb.AppendLine($"\t[{buff.Index}] Position: 0x{buff.Position:x16} Size: 0x{buff.Size:x16}"); + } + } + + if (Request.SendBuff.Count > 0) + { + sb.AppendLine("\tSendBuff:"); + + foreach (var buff in Request.SendBuff) + { + sb.AppendLine($"\tPosition: 0x{buff.Position:x16} Size: 0x{buff.Size:x16} Flags: {buff.Flags}"); + } + } + + if (Request.ReceiveBuff.Count > 0) + { + sb.AppendLine("\tReceiveBuff:"); + + foreach (var buff in Request.ReceiveBuff) + { + sb.AppendLine($"\tPosition: 0x{buff.Position:x16} Size: 0x{buff.Size:x16} Flags: {buff.Flags}"); + } + } + + if (Request.ExchangeBuff.Count > 0) + { + sb.AppendLine("\tExchangeBuff:"); + + foreach (var buff in Request.ExchangeBuff) + { + sb.AppendLine($"\tPosition: 0x{buff.Position:x16} Size: 0x{buff.Size:x16} Flags: {buff.Flags}"); + } + } + + if (Request.RecvListBuff.Count > 0) + { + sb.AppendLine("\tRecvListBuff:"); + + foreach (var buff in Request.RecvListBuff) + { + sb.AppendLine($"\tPosition: 0x{buff.Position:x16} Size: 0x{buff.Size:x16}"); + } + } + + sb.AppendLine(); + } + + sb.AppendLine("Raw Request Data:"); + sb.Append(HexUtils.HexTable(Request.RawData)); + + return sb.ToString(); + } + + private static (Type, MethodBase) WalkStackTrace(StackTrace trace) + { + int i = 0; + + StackFrame frame; + + // Find the IIpcService method that threw this exception + while ((frame = trace.GetFrame(i++)) != null) + { + var method = frame.GetMethod(); + var declType = method.DeclaringType; + + if (typeof(IpcService).IsAssignableFrom(declType)) + { + return (declType, method); + } + } + + return (null, null); + } + } +} diff --git a/src/Ryujinx.HLE/Exceptions/TamperCompilationException.cs b/src/Ryujinx.HLE/Exceptions/TamperCompilationException.cs new file mode 100644 index 00000000..370df5d3 --- /dev/null +++ b/src/Ryujinx.HLE/Exceptions/TamperCompilationException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.Exceptions +{ + public class TamperCompilationException : Exception + { + public TamperCompilationException(string message) : base(message) { } + } +} diff --git a/src/Ryujinx.HLE/Exceptions/TamperExecutionException.cs b/src/Ryujinx.HLE/Exceptions/TamperExecutionException.cs new file mode 100644 index 00000000..1f132607 --- /dev/null +++ b/src/Ryujinx.HLE/Exceptions/TamperExecutionException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.Exceptions +{ + public class TamperExecutionException : Exception + { + public TamperExecutionException(string message) : base(message) { } + } +} diff --git a/src/Ryujinx.HLE/Exceptions/UndefinedInstructionException.cs b/src/Ryujinx.HLE/Exceptions/UndefinedInstructionException.cs new file mode 100644 index 00000000..3886e5e7 --- /dev/null +++ b/src/Ryujinx.HLE/Exceptions/UndefinedInstructionException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Ryujinx.HLE.Exceptions +{ + public class UndefinedInstructionException : Exception + { + private const string ExMsg = "The instruction at 0x{0:x16} (opcode 0x{1:x8}) is undefined!"; + + public UndefinedInstructionException() : base() { } + + public UndefinedInstructionException(ulong address, int opCode) : base(string.Format(ExMsg, address, opCode)) { } + } +} diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs new file mode 100644 index 00000000..e6c0fce0 --- /dev/null +++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs @@ -0,0 +1,967 @@ +using LibHac.Common; +using LibHac.Common.Keys; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using LibHac.Tools.Ncm; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Services.Ssl; +using Ryujinx.HLE.HOS.Services.Time; +using Ryujinx.HLE.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using Path = System.IO.Path; + +namespace Ryujinx.HLE.FileSystem +{ + public class ContentManager + { + private const ulong SystemVersionTitleId = 0x0100000000000809; + private const ulong SystemUpdateTitleId = 0x0100000000000816; + + private Dictionary> _locationEntries; + + private readonly Dictionary _sharedFontTitleDictionary; + private readonly Dictionary _systemTitlesNameDictionary; + private readonly Dictionary _sharedFontFilenameDictionary; + + private SortedDictionary<(ulong titleId, NcaContentType type), string> _contentDictionary; + + private readonly struct AocItem + { + public readonly string ContainerPath; + public readonly string NcaPath; + + public AocItem(string containerPath, string ncaPath) + { + ContainerPath = containerPath; + NcaPath = ncaPath; + } + } + + private SortedList AocData { get; } + + private readonly VirtualFileSystem _virtualFileSystem; + + private readonly object _lock = new(); + + public ContentManager(VirtualFileSystem virtualFileSystem) + { + _contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>(); + _locationEntries = new Dictionary>(); + + _sharedFontTitleDictionary = new Dictionary + { + { "FontStandard", 0x0100000000000811 }, + { "FontChineseSimplified", 0x0100000000000814 }, + { "FontExtendedChineseSimplified", 0x0100000000000814 }, + { "FontKorean", 0x0100000000000812 }, + { "FontChineseTraditional", 0x0100000000000813 }, + { "FontNintendoExtended", 0x0100000000000810 }, + }; + + _systemTitlesNameDictionary = new Dictionary() + { + { 0x010000000000080E, "TimeZoneBinary" }, + { 0x0100000000000810, "FontNintendoExtension" }, + { 0x0100000000000811, "FontStandard" }, + { 0x0100000000000812, "FontKorean" }, + { 0x0100000000000813, "FontChineseTraditional" }, + { 0x0100000000000814, "FontChineseSimple" }, + }; + + _sharedFontFilenameDictionary = new Dictionary + { + { "FontStandard", "nintendo_udsg-r_std_003.bfttf" }, + { "FontChineseSimplified", "nintendo_udsg-r_org_zh-cn_003.bfttf" }, + { "FontExtendedChineseSimplified", "nintendo_udsg-r_ext_zh-cn_003.bfttf" }, + { "FontKorean", "nintendo_udsg-r_ko_003.bfttf" }, + { "FontChineseTraditional", "nintendo_udjxh-db_zh-tw_003.bfttf" }, + { "FontNintendoExtended", "nintendo_ext_003.bfttf" }, + }; + + _virtualFileSystem = virtualFileSystem; + + AocData = new SortedList(); + } + + public void LoadEntries(Switch device = null) + { + lock (_lock) + { + _contentDictionary = new SortedDictionary<(ulong, NcaContentType), string>(); + _locationEntries = new Dictionary>(); + + foreach (StorageId storageId in Enum.GetValues()) + { + if (!ContentPath.TryGetContentPath(storageId, out var contentPathString)) + { + continue; + } + if (!ContentPath.TryGetRealPath(contentPathString, out var contentDirectory)) + { + continue; + } + var registeredDirectory = Path.Combine(contentDirectory, "registered"); + + Directory.CreateDirectory(registeredDirectory); + + LinkedList locationList = new(); + + void AddEntry(LocationEntry entry) + { + locationList.AddLast(entry); + } + + foreach (string directoryPath in Directory.EnumerateDirectories(registeredDirectory)) + { + if (Directory.GetFiles(directoryPath).Length > 0) + { + string ncaName = new DirectoryInfo(directoryPath).Name.Replace(".nca", string.Empty); + + using FileStream ncaFile = File.OpenRead(Directory.GetFiles(directoryPath)[0]); + Nca nca = new(_virtualFileSystem.KeySet, ncaFile.AsStorage()); + + string switchPath = contentPathString + ":/" + ncaFile.Name.Replace(contentDirectory, string.Empty).TrimStart(Path.DirectorySeparatorChar); + + // Change path format to switch's + switchPath = switchPath.Replace('\\', '/'); + + LocationEntry entry = new(switchPath, 0, nca.Header.TitleId, nca.Header.ContentType); + + AddEntry(entry); + + _contentDictionary.Add((nca.Header.TitleId, nca.Header.ContentType), ncaName); + } + } + + foreach (string filePath in Directory.EnumerateFiles(contentDirectory)) + { + if (Path.GetExtension(filePath) == ".nca") + { + string ncaName = Path.GetFileNameWithoutExtension(filePath); + + using FileStream ncaFile = new(filePath, FileMode.Open, FileAccess.Read); + Nca nca = new(_virtualFileSystem.KeySet, ncaFile.AsStorage()); + + string switchPath = contentPathString + ":/" + filePath.Replace(contentDirectory, string.Empty).TrimStart(Path.DirectorySeparatorChar); + + // Change path format to switch's + switchPath = switchPath.Replace('\\', '/'); + + LocationEntry entry = new(switchPath, 0, nca.Header.TitleId, nca.Header.ContentType); + + AddEntry(entry); + + _contentDictionary.Add((nca.Header.TitleId, nca.Header.ContentType), ncaName); + } + } + + if (_locationEntries.TryGetValue(storageId, out var locationEntriesItem) && locationEntriesItem?.Count == 0) + { + _locationEntries.Remove(storageId); + } + + _locationEntries.TryAdd(storageId, locationList); + } + + if (device != null) + { + TimeManager.Instance.InitializeTimeZone(device); + BuiltInCertificateManager.Instance.Initialize(device); + device.System.SharedFontManager.Initialize(); + } + } + } + + public void AddAocItem(ulong titleId, string containerPath, string ncaPath, bool mergedToContainer = false) + { + // TODO: Check Aoc version. + if (!AocData.TryAdd(titleId, new AocItem(containerPath, ncaPath))) + { + Logger.Warning?.Print(LogClass.Application, $"Duplicate AddOnContent detected. TitleId {titleId:X16}"); + } + else + { + Logger.Info?.Print(LogClass.Application, $"Found AddOnContent with TitleId {titleId:X16}"); + + if (!mergedToContainer) + { + using var pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(containerPath, _virtualFileSystem); + } + } + } + + public void ClearAocData() => AocData.Clear(); + + public int GetAocCount() => AocData.Count; + + public IList GetAocTitleIds() => AocData.Select(e => e.Key).ToList(); + + public bool GetAocDataStorage(ulong aocTitleId, out IStorage aocStorage, IntegrityCheckLevel integrityCheckLevel) + { + aocStorage = null; + + if (AocData.TryGetValue(aocTitleId, out AocItem aoc)) + { + var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read); + using var ncaFile = new UniqueRef(); + + switch (Path.GetExtension(aoc.ContainerPath)) + { + case ".xci": + var xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure); + xci.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + break; + case ".nsp": + var pfs = new PartitionFileSystem(); + pfs.Initialize(file.AsStorage()); + pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + break; + default: + return false; // Print error? + } + + aocStorage = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage()).OpenStorage(NcaSectionType.Data, integrityCheckLevel); + + return true; + } + + return false; + } + + public void ClearEntry(ulong titleId, NcaContentType contentType, StorageId storageId) + { + lock (_lock) + { + RemoveLocationEntry(titleId, contentType, storageId); + } + } + + public void RefreshEntries(StorageId storageId, int flag) + { + lock (_lock) + { + LinkedList locationList = _locationEntries[storageId]; + LinkedListNode locationEntry = locationList.First; + + while (locationEntry != null) + { + LinkedListNode nextLocationEntry = locationEntry.Next; + + if (locationEntry.Value.Flag == flag) + { + locationList.Remove(locationEntry.Value); + } + + locationEntry = nextLocationEntry; + } + } + } + + public bool HasNca(string ncaId, StorageId storageId) + { + lock (_lock) + { + if (_contentDictionary.ContainsValue(ncaId)) + { + var content = _contentDictionary.FirstOrDefault(x => x.Value == ncaId); + ulong titleId = content.Key.titleId; + + NcaContentType contentType = content.Key.type; + StorageId storage = GetInstalledStorage(titleId, contentType, storageId); + + return storage == storageId; + } + } + + return false; + } + + public UInt128 GetInstalledNcaId(ulong titleId, NcaContentType contentType) + { + lock (_lock) + { + if (_contentDictionary.TryGetValue((titleId, contentType), out var contentDictionaryItem)) + { + return UInt128Utils.FromHex(contentDictionaryItem); + } + } + + return new UInt128(); + } + + public StorageId GetInstalledStorage(ulong titleId, NcaContentType contentType, StorageId storageId) + { + lock (_lock) + { + LocationEntry locationEntry = GetLocation(titleId, contentType, storageId); + + return locationEntry.ContentPath != null ? ContentPath.GetStorageId(locationEntry.ContentPath) : StorageId.None; + } + } + + public string GetInstalledContentPath(ulong titleId, StorageId storageId, NcaContentType contentType) + { + lock (_lock) + { + LocationEntry locationEntry = GetLocation(titleId, contentType, storageId); + + if (VerifyContentType(locationEntry, contentType)) + { + return locationEntry.ContentPath; + } + } + + return string.Empty; + } + + public void RedirectLocation(LocationEntry newEntry, StorageId storageId) + { + lock (_lock) + { + LocationEntry locationEntry = GetLocation(newEntry.TitleId, newEntry.ContentType, storageId); + + if (locationEntry.ContentPath != null) + { + RemoveLocationEntry(newEntry.TitleId, newEntry.ContentType, storageId); + } + + AddLocationEntry(newEntry, storageId); + } + } + + private bool VerifyContentType(LocationEntry locationEntry, NcaContentType contentType) + { + if (locationEntry.ContentPath == null) + { + return false; + } + + string installedPath = VirtualFileSystem.SwitchPathToSystemPath(locationEntry.ContentPath); + + if (!string.IsNullOrWhiteSpace(installedPath)) + { + if (File.Exists(installedPath)) + { + using FileStream file = new(installedPath, FileMode.Open, FileAccess.Read); + Nca nca = new(_virtualFileSystem.KeySet, file.AsStorage()); + bool contentCheck = nca.Header.ContentType == contentType; + + return contentCheck; + } + } + + return false; + } + + private void AddLocationEntry(LocationEntry entry, StorageId storageId) + { + LinkedList locationList = null; + + if (_locationEntries.TryGetValue(storageId, out LinkedList locationEntry)) + { + locationList = locationEntry; + } + + if (locationList != null) + { + locationList.Remove(entry); + + locationList.AddLast(entry); + } + } + + private void RemoveLocationEntry(ulong titleId, NcaContentType contentType, StorageId storageId) + { + LinkedList locationList = null; + + if (_locationEntries.TryGetValue(storageId, out LinkedList locationEntry)) + { + locationList = locationEntry; + } + + if (locationList != null) + { + LocationEntry entry = + locationList.ToList().Find(x => x.TitleId == titleId && x.ContentType == contentType); + + if (entry.ContentPath != null) + { + locationList.Remove(entry); + } + } + } + + public bool TryGetFontTitle(string fontName, out ulong titleId) + { + return _sharedFontTitleDictionary.TryGetValue(fontName, out titleId); + } + + public bool TryGetFontFilename(string fontName, out string filename) + { + return _sharedFontFilenameDictionary.TryGetValue(fontName, out filename); + } + + public bool TryGetSystemTitlesName(ulong titleId, out string name) + { + return _systemTitlesNameDictionary.TryGetValue(titleId, out name); + } + + private LocationEntry GetLocation(ulong titleId, NcaContentType contentType, StorageId storageId) + { + LinkedList locationList = _locationEntries[storageId]; + + return locationList.ToList().Find(x => x.TitleId == titleId && x.ContentType == contentType); + } + + public void InstallFirmware(string firmwareSource) + { + ContentPath.TryGetContentPath(StorageId.BuiltInSystem, out var contentPathString); + ContentPath.TryGetRealPath(contentPathString, out var contentDirectory); + string registeredDirectory = Path.Combine(contentDirectory, "registered"); + string temporaryDirectory = Path.Combine(contentDirectory, "temp"); + + if (Directory.Exists(temporaryDirectory)) + { + Directory.Delete(temporaryDirectory, true); + } + + if (Directory.Exists(firmwareSource)) + { + InstallFromDirectory(firmwareSource, temporaryDirectory); + FinishInstallation(temporaryDirectory, registeredDirectory); + + return; + } + + if (!File.Exists(firmwareSource)) + { + throw new FileNotFoundException("Firmware file does not exist."); + } + + FileInfo info = new(firmwareSource); + + using FileStream file = File.OpenRead(firmwareSource); + + switch (info.Extension) + { + case ".zip": + using (ZipArchive archive = ZipFile.OpenRead(firmwareSource)) + { + InstallFromZip(archive, temporaryDirectory); + } + break; + case ".xci": + Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage()); + InstallFromCart(xci, temporaryDirectory); + break; + default: + throw new InvalidFirmwarePackageException("Input file is not a valid firmware package"); + } + + FinishInstallation(temporaryDirectory, registeredDirectory); + } + + private void FinishInstallation(string temporaryDirectory, string registeredDirectory) + { + if (Directory.Exists(registeredDirectory)) + { + new DirectoryInfo(registeredDirectory).Delete(true); + } + + Directory.Move(temporaryDirectory, registeredDirectory); + + LoadEntries(); + } + + private void InstallFromDirectory(string firmwareDirectory, string temporaryDirectory) + { + InstallFromPartition(new LocalFileSystem(firmwareDirectory), temporaryDirectory); + } + + private void InstallFromPartition(IFileSystem filesystem, string temporaryDirectory) + { + foreach (var entry in filesystem.EnumerateEntries("/", "*.nca")) + { + Nca nca = new(_virtualFileSystem.KeySet, OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage()); + + SaveNca(nca, entry.Name.Remove(entry.Name.IndexOf('.')), temporaryDirectory); + } + } + + private void InstallFromCart(Xci gameCard, string temporaryDirectory) + { + if (gameCard.HasPartition(XciPartitionType.Update)) + { + XciPartition partition = gameCard.OpenPartition(XciPartitionType.Update); + + InstallFromPartition(partition, temporaryDirectory); + } + else + { + throw new Exception("Update not found in xci file."); + } + } + + private static void InstallFromZip(ZipArchive archive, string temporaryDirectory) + { + foreach (var entry in archive.Entries) + { + if (entry.FullName.EndsWith(".nca") || entry.FullName.EndsWith(".nca/00")) + { + // Clean up the name and get the NcaId + + string[] pathComponents = entry.FullName.Replace(".cnmt", "").Split('/'); + + string ncaId = pathComponents[^1]; + + // If this is a fragmented nca, we need to get the previous element.GetZip + if (ncaId.Equals("00")) + { + ncaId = pathComponents[^2]; + } + + if (ncaId.Contains(".nca")) + { + string newPath = Path.Combine(temporaryDirectory, ncaId); + + Directory.CreateDirectory(newPath); + + entry.ExtractToFile(Path.Combine(newPath, "00")); + } + } + } + } + + public static void SaveNca(Nca nca, string ncaId, string temporaryDirectory) + { + string newPath = Path.Combine(temporaryDirectory, ncaId + ".nca"); + + Directory.CreateDirectory(newPath); + + using FileStream file = File.Create(Path.Combine(newPath, "00")); + nca.BaseStorage.AsStream().CopyTo(file); + } + + private static IFile OpenPossibleFragmentedFile(IFileSystem filesystem, string path, OpenMode mode) + { + using var file = new UniqueRef(); + + if (filesystem.FileExists($"{path}/00")) + { + filesystem.OpenFile(ref file.Ref, $"{path}/00".ToU8Span(), mode).ThrowIfFailure(); + } + else + { + filesystem.OpenFile(ref file.Ref, path.ToU8Span(), mode).ThrowIfFailure(); + } + + return file.Release(); + } + + private static Stream GetZipStream(ZipArchiveEntry entry) + { + MemoryStream dest = MemoryStreamManager.Shared.GetStream(); + + using Stream src = entry.Open(); + src.CopyTo(dest); + + return dest; + } + + public SystemVersion VerifyFirmwarePackage(string firmwarePackage) + { + _virtualFileSystem.ReloadKeySet(); + + // LibHac.NcaHeader's DecryptHeader doesn't check if HeaderKey is empty and throws InvalidDataException instead + // So, we check it early for a better user experience. + if (_virtualFileSystem.KeySet.HeaderKey.IsZeros()) + { + throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers."); + } + + Dictionary> updateNcas = new(); + + if (Directory.Exists(firmwarePackage)) + { + return VerifyAndGetVersionDirectory(firmwarePackage); + } + + if (!File.Exists(firmwarePackage)) + { + throw new FileNotFoundException("Firmware file does not exist."); + } + + FileInfo info = new(firmwarePackage); + + using FileStream file = File.OpenRead(firmwarePackage); + + switch (info.Extension) + { + case ".zip": + using (ZipArchive archive = ZipFile.OpenRead(firmwarePackage)) + { + return VerifyAndGetVersionZip(archive); + } + case ".xci": + Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage()); + + if (xci.HasPartition(XciPartitionType.Update)) + { + XciPartition partition = xci.OpenPartition(XciPartitionType.Update); + + return VerifyAndGetVersion(partition); + } + else + { + throw new InvalidFirmwarePackageException("Update not found in xci file."); + } + default: + break; + } + + SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory) + { + return VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory)); + } + + SystemVersion VerifyAndGetVersionZip(ZipArchive archive) + { + SystemVersion systemVersion = null; + + foreach (var entry in archive.Entries) + { + if (entry.FullName.EndsWith(".nca") || entry.FullName.EndsWith(".nca/00")) + { + using Stream ncaStream = GetZipStream(entry); + IStorage storage = ncaStream.AsStorage(); + + Nca nca = new(_virtualFileSystem.KeySet, storage); + + if (updateNcas.TryGetValue(nca.Header.TitleId, out var updateNcasItem)) + { + updateNcasItem.Add((nca.Header.ContentType, entry.FullName)); + } + else + { + updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>()); + updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName)); + } + } + } + + if (updateNcas.TryGetValue(SystemUpdateTitleId, out var ncaEntry)) + { + string metaPath = ncaEntry.Find(x => x.type == NcaContentType.Meta).path; + + CnmtContentMetaEntry[] metaEntries = null; + + var fileEntry = archive.GetEntry(metaPath); + + using (Stream ncaStream = GetZipStream(fileEntry)) + { + Nca metaNca = new(_virtualFileSystem.KeySet, ncaStream.AsStorage()); + + IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; + + using var metaFile = new UniqueRef(); + + if (fs.OpenFile(ref metaFile.Ref, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess()) + { + var meta = new Cnmt(metaFile.Get.AsStream()); + + if (meta.Type == ContentMetaType.SystemUpdate) + { + metaEntries = meta.MetaEntries; + + updateNcas.Remove(SystemUpdateTitleId); + } + } + } + + if (metaEntries == null) + { + throw new FileNotFoundException("System update title was not found in the firmware package."); + } + + if (updateNcas.TryGetValue(SystemVersionTitleId, out var updateNcasItem)) + { + string versionEntry = updateNcasItem.Find(x => x.type != NcaContentType.Meta).path; + + using Stream ncaStream = GetZipStream(archive.GetEntry(versionEntry)); + Nca nca = new(_virtualFileSystem.KeySet, ncaStream.AsStorage()); + + var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + using var systemVersionFile = new UniqueRef(); + + if (romfs.OpenFile(ref systemVersionFile.Ref, "/file".ToU8Span(), OpenMode.Read).IsSuccess()) + { + systemVersion = new SystemVersion(systemVersionFile.Get.AsStream()); + } + } + + foreach (CnmtContentMetaEntry metaEntry in metaEntries) + { + if (updateNcas.TryGetValue(metaEntry.TitleId, out ncaEntry)) + { + metaPath = ncaEntry.Find(x => x.type == NcaContentType.Meta).path; + + string contentPath = ncaEntry.Find(x => x.type != NcaContentType.Meta).path; + + // Nintendo in 9.0.0, removed PPC and only kept the meta nca of it. + // This is a perfect valid case, so we should just ignore the missing content nca and continue. + if (contentPath == null) + { + updateNcas.Remove(metaEntry.TitleId); + + continue; + } + + ZipArchiveEntry metaZipEntry = archive.GetEntry(metaPath); + ZipArchiveEntry contentZipEntry = archive.GetEntry(contentPath); + + using Stream metaNcaStream = GetZipStream(metaZipEntry); + using Stream contentNcaStream = GetZipStream(contentZipEntry); + Nca metaNca = new(_virtualFileSystem.KeySet, metaNcaStream.AsStorage()); + + IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; + + using var metaFile = new UniqueRef(); + + if (fs.OpenFile(ref metaFile.Ref, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess()) + { + var meta = new Cnmt(metaFile.Get.AsStream()); + + IStorage contentStorage = contentNcaStream.AsStorage(); + if (contentStorage.GetSize(out long size).IsSuccess()) + { + byte[] contentData = new byte[size]; + + Span content = new(contentData); + + contentStorage.Read(0, content); + + Span hash = new(new byte[32]); + + LibHac.Crypto.Sha256.GenerateSha256Hash(content, hash); + + if (LibHac.Common.Utilities.ArraysEqual(hash.ToArray(), meta.ContentEntries[0].Hash)) + { + updateNcas.Remove(metaEntry.TitleId); + } + } + } + } + } + + if (updateNcas.Count > 0) + { + StringBuilder extraNcas = new(); + + foreach (var entry in updateNcas) + { + foreach (var (type, path) in entry.Value) + { + extraNcas.AppendLine(path); + } + } + + throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}"); + } + } + else + { + throw new FileNotFoundException("System update title was not found in the firmware package."); + } + + return systemVersion; + } + + SystemVersion VerifyAndGetVersion(IFileSystem filesystem) + { + SystemVersion systemVersion = null; + + CnmtContentMetaEntry[] metaEntries = null; + + foreach (var entry in filesystem.EnumerateEntries("/", "*.nca")) + { + IStorage ncaStorage = OpenPossibleFragmentedFile(filesystem, entry.FullPath, OpenMode.Read).AsStorage(); + + Nca nca = new(_virtualFileSystem.KeySet, ncaStorage); + + if (nca.Header.TitleId == SystemUpdateTitleId && nca.Header.ContentType == NcaContentType.Meta) + { + IFileSystem fs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; + + using var metaFile = new UniqueRef(); + + if (fs.OpenFile(ref metaFile.Ref, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess()) + { + var meta = new Cnmt(metaFile.Get.AsStream()); + + if (meta.Type == ContentMetaType.SystemUpdate) + { + metaEntries = meta.MetaEntries; + } + } + + continue; + } + else if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data) + { + var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + using var systemVersionFile = new UniqueRef(); + + if (romfs.OpenFile(ref systemVersionFile.Ref, "/file".ToU8Span(), OpenMode.Read).IsSuccess()) + { + systemVersion = new SystemVersion(systemVersionFile.Get.AsStream()); + } + } + + if (updateNcas.TryGetValue(nca.Header.TitleId, out var updateNcasItem)) + { + updateNcasItem.Add((nca.Header.ContentType, entry.FullPath)); + } + else + { + updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>()); + updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath)); + } + + ncaStorage.Dispose(); + } + + if (metaEntries == null) + { + throw new FileNotFoundException("System update title was not found in the firmware package."); + } + + foreach (CnmtContentMetaEntry metaEntry in metaEntries) + { + if (updateNcas.TryGetValue(metaEntry.TitleId, out var ncaEntry)) + { + string metaNcaPath = ncaEntry.Find(x => x.type == NcaContentType.Meta).path; + string contentPath = ncaEntry.Find(x => x.type != NcaContentType.Meta).path; + + // Nintendo in 9.0.0, removed PPC and only kept the meta nca of it. + // This is a perfect valid case, so we should just ignore the missing content nca and continue. + if (contentPath == null) + { + updateNcas.Remove(metaEntry.TitleId); + + continue; + } + + IStorage metaStorage = OpenPossibleFragmentedFile(filesystem, metaNcaPath, OpenMode.Read).AsStorage(); + IStorage contentStorage = OpenPossibleFragmentedFile(filesystem, contentPath, OpenMode.Read).AsStorage(); + + Nca metaNca = new(_virtualFileSystem.KeySet, metaStorage); + + IFileSystem fs = metaNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + string cnmtPath = fs.EnumerateEntries("/", "*.cnmt").Single().FullPath; + + using var metaFile = new UniqueRef(); + + if (fs.OpenFile(ref metaFile.Ref, cnmtPath.ToU8Span(), OpenMode.Read).IsSuccess()) + { + var meta = new Cnmt(metaFile.Get.AsStream()); + + if (contentStorage.GetSize(out long size).IsSuccess()) + { + byte[] contentData = new byte[size]; + + Span content = new(contentData); + + contentStorage.Read(0, content); + + Span hash = new(new byte[32]); + + LibHac.Crypto.Sha256.GenerateSha256Hash(content, hash); + + if (LibHac.Common.Utilities.ArraysEqual(hash.ToArray(), meta.ContentEntries[0].Hash)) + { + updateNcas.Remove(metaEntry.TitleId); + } + } + } + } + } + + if (updateNcas.Count > 0) + { + StringBuilder extraNcas = new(); + + foreach (var entry in updateNcas) + { + foreach (var (type, path) in entry.Value) + { + extraNcas.AppendLine(path); + } + } + + throw new InvalidFirmwarePackageException($"Firmware package contains unrelated archives. Please remove these paths: {Environment.NewLine}{extraNcas}"); + } + + return systemVersion; + } + + return null; + } + + public SystemVersion GetCurrentFirmwareVersion() + { + LoadEntries(); + + lock (_lock) + { + var locationEnties = _locationEntries[StorageId.BuiltInSystem]; + + foreach (var entry in locationEnties) + { + if (entry.ContentType == NcaContentType.Data) + { + var path = VirtualFileSystem.SwitchPathToSystemPath(entry.ContentPath); + + using FileStream fileStream = File.OpenRead(path); + Nca nca = new(_virtualFileSystem.KeySet, fileStream.AsStorage()); + + if (nca.Header.TitleId == SystemVersionTitleId && nca.Header.ContentType == NcaContentType.Data) + { + var romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + using var systemVersionFile = new UniqueRef(); + + if (romfs.OpenFile(ref systemVersionFile.Ref, "/file".ToU8Span(), OpenMode.Read).IsSuccess()) + { + return new SystemVersion(systemVersionFile.Get.AsStream()); + } + } + } + } + } + + return null; + } + } +} diff --git a/src/Ryujinx.HLE/FileSystem/ContentMetaData.cs b/src/Ryujinx.HLE/FileSystem/ContentMetaData.cs new file mode 100644 index 00000000..aebcf798 --- /dev/null +++ b/src/Ryujinx.HLE/FileSystem/ContentMetaData.cs @@ -0,0 +1,61 @@ +using LibHac.Common.Keys; +using LibHac.Fs.Fsa; +using LibHac.Ncm; +using LibHac.Tools.FsSystem.NcaUtils; +using LibHac.Tools.Ncm; +using Ryujinx.HLE.Loaders.Processes.Extensions; +using System; + +namespace Ryujinx.HLE.FileSystem +{ + /// + /// Thin wrapper around + /// + public class ContentMetaData + { + private readonly IFileSystem _pfs; + private readonly Cnmt _cnmt; + + public ulong Id => _cnmt.TitleId; + public TitleVersion Version => _cnmt.TitleVersion; + public ContentMetaType Type => _cnmt.Type; + public ulong ApplicationId => _cnmt.ApplicationTitleId; + public ulong PatchId => _cnmt.PatchTitleId; + public TitleVersion RequiredSystemVersion => _cnmt.MinimumSystemVersion; + public TitleVersion RequiredApplicationVersion => _cnmt.MinimumApplicationVersion; + public byte[] Digest => _cnmt.Hash; + + public ulong ProgramBaseId => Id & ~0x1FFFUL; + public bool IsSystemTitle => _cnmt.Type < ContentMetaType.Application; + + public ContentMetaData(IFileSystem pfs, Cnmt cnmt) + { + _pfs = pfs; + _cnmt = cnmt; + } + + public Nca GetNcaByType(KeySet keySet, ContentType type, int programIndex = 0) + { + // TODO: Replace this with a check for IdOffset as soon as LibHac supports it: + // && entry.IdOffset == programIndex + + foreach (var entry in _cnmt.ContentEntries) + { + if (entry.Type != type) + { + continue; + } + + string ncaId = BitConverter.ToString(entry.NcaId).Replace("-", null).ToLower(); + Nca nca = _pfs.GetNca(keySet, $"/{ncaId}.nca"); + + if (nca.GetProgramIndex() == programIndex) + { + return nca; + } + } + + return null; + } + } +} diff --git a/src/Ryujinx.HLE/FileSystem/ContentPath.cs b/src/Ryujinx.HLE/FileSystem/ContentPath.cs new file mode 100644 index 00000000..ffc212f7 --- /dev/null +++ b/src/Ryujinx.HLE/FileSystem/ContentPath.cs @@ -0,0 +1,85 @@ +using LibHac.Fs; +using LibHac.Ncm; +using Ryujinx.Common.Configuration; +using System; +using static Ryujinx.HLE.FileSystem.VirtualFileSystem; +using Path = System.IO.Path; + +namespace Ryujinx.HLE.FileSystem +{ + internal static class ContentPath + { + public const string SystemContent = "@SystemContent"; + public const string UserContent = "@UserContent"; + public const string SdCardContent = "@SdCardContent"; + public const string SdCard = "@Sdcard"; + public const string CalibFile = "@CalibFile"; + public const string Safe = "@Safe"; + public const string User = "@User"; + public const string System = "@System"; + public const string Host = "@Host"; + public const string GamecardApp = "@GcApp"; + public const string GamecardContents = "@GcS00000001"; + public const string GamecardUpdate = "@upp"; + public const string RegisteredUpdate = "@RegUpdate"; + + public const string Nintendo = "Nintendo"; + public const string Contents = "Contents"; + + public static bool TryGetRealPath(string switchContentPath, out string realPath) + { + realPath = switchContentPath switch + { + SystemContent => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath, Contents), + UserContent => Path.Combine(AppDataManager.BaseDirPath, UserNandPath, Contents), + SdCardContent => Path.Combine(GetSdCardPath(), Nintendo, Contents), + System => Path.Combine(AppDataManager.BaseDirPath, SystemNandPath), + User => Path.Combine(AppDataManager.BaseDirPath, UserNandPath), + _ => null, + }; + + return realPath != null; + } + + public static string GetContentPath(ContentStorageId contentStorageId) + { + return contentStorageId switch + { + ContentStorageId.System => SystemContent, + ContentStorageId.User => UserContent, + ContentStorageId.SdCard => SdCardContent, + _ => throw new NotSupportedException($"Content Storage Id \"`{contentStorageId}`\" is not supported."), + }; + } + + public static bool TryGetContentPath(StorageId storageId, out string contentPath) + { + contentPath = storageId switch + { + StorageId.BuiltInSystem => SystemContent, + StorageId.BuiltInUser => UserContent, + StorageId.SdCard => SdCardContent, + _ => null, + }; + + return contentPath != null; + } + + public static StorageId GetStorageId(string contentPathString) + { + return contentPathString.Split(':')[0] switch + { + SystemContent or + System => StorageId.BuiltInSystem, + UserContent or + User => StorageId.BuiltInUser, + SdCardContent => StorageId.SdCard, + Host => StorageId.Host, + GamecardApp or + GamecardContents or + GamecardUpdate => StorageId.GameCard, + _ => StorageId.None, + }; + } + } +} diff --git a/src/Ryujinx.HLE/FileSystem/EncryptedFileSystemCreator.cs b/src/Ryujinx.HLE/FileSystem/EncryptedFileSystemCreator.cs new file mode 100644 index 00000000..64b02a28 --- /dev/null +++ b/src/Ryujinx.HLE/FileSystem/EncryptedFileSystemCreator.cs @@ -0,0 +1,26 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSrv.FsCreator; + +namespace Ryujinx.HLE.FileSystem +{ + public class EncryptedFileSystemCreator : IEncryptedFileSystemCreator + { + public Result Create(ref SharedRef outEncryptedFileSystem, + ref SharedRef baseFileSystem, IEncryptedFileSystemCreator.KeyId idIndex, + in EncryptionSeed encryptionSeed) + { + if (idIndex < IEncryptedFileSystemCreator.KeyId.Save || idIndex > IEncryptedFileSystemCreator.KeyId.CustomStorage) + { + return ResultFs.InvalidArgument.Log(); + } + + // TODO: Reenable when AesXtsFileSystem is fixed. + outEncryptedFileSystem = SharedRef.CreateMove(ref baseFileSystem); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.HLE/FileSystem/LocationEntry.cs b/src/Ryujinx.HLE/FileSystem/LocationEntry.cs new file mode 100644 index 00000000..fb717665 --- /dev/null +++ b/src/Ryujinx.HLE/FileSystem/LocationEntry.cs @@ -0,0 +1,25 @@ +using LibHac.Tools.FsSystem.NcaUtils; + +namespace Ryujinx.HLE.FileSystem +{ + public struct LocationEntry + { + public string ContentPath { get; private set; } + public int Flag { get; private set; } + public ulong TitleId { get; private set; } + public NcaContentType ContentType { get; private set; } + + public LocationEntry(string contentPath, int flag, ulong titleId, NcaContentType contentType) + { + ContentPath = contentPath; + Flag = flag; + TitleId = titleId; + ContentType = contentType; + } + + public void SetFlag(int flag) + { + Flag = flag; + } + } +} diff --git a/src/Ryujinx.HLE/FileSystem/SystemVersion.cs b/src/Ryujinx.HLE/FileSystem/SystemVersion.cs new file mode 100644 index 00000000..bd64d7f2 --- /dev/null +++ b/src/Ryujinx.HLE/FileSystem/SystemVersion.cs @@ -0,0 +1,38 @@ +using Ryujinx.HLE.Utilities; +using System.IO; + +namespace Ryujinx.HLE.FileSystem +{ + public class SystemVersion + { + public byte Major { get; } + public byte Minor { get; } + public byte Micro { get; } + public byte RevisionMajor { get; } + public byte RevisionMinor { get; } + public string PlatformString { get; } + public string Hex { get; } + public string VersionString { get; } + public string VersionTitle { get; } + + public SystemVersion(Stream systemVersionFile) + { + using BinaryReader reader = new(systemVersionFile); + Major = reader.ReadByte(); + Minor = reader.ReadByte(); + Micro = reader.ReadByte(); + + reader.ReadByte(); // Padding + + RevisionMajor = reader.ReadByte(); + RevisionMinor = reader.ReadByte(); + + reader.ReadBytes(2); // Padding + + PlatformString = StringUtils.ReadInlinedAsciiString(reader, 0x20); + Hex = StringUtils.ReadInlinedAsciiString(reader, 0x40); + VersionString = StringUtils.ReadInlinedAsciiString(reader, 0x18); + VersionTitle = StringUtils.ReadInlinedAsciiString(reader, 0x80); + } + } +} diff --git a/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs new file mode 100644 index 00000000..0827266a --- /dev/null +++ b/src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -0,0 +1,670 @@ +using LibHac; +using LibHac.Common; +using LibHac.Common.Keys; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Fs.Shim; +using LibHac.FsSrv; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Sdmmc; +using LibHac.Spl; +using LibHac.Tools.Es; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS; +using System; +using System.Buffers.Text; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using Path = System.IO.Path; + +namespace Ryujinx.HLE.FileSystem +{ + public class VirtualFileSystem : IDisposable + { + public static readonly string SafeNandPath = Path.Combine(AppDataManager.DefaultNandDir, "safe"); + public static readonly string SystemNandPath = Path.Combine(AppDataManager.DefaultNandDir, "system"); + public static readonly string UserNandPath = Path.Combine(AppDataManager.DefaultNandDir, "user"); + + public KeySet KeySet { get; private set; } + public EmulatedGameCard GameCard { get; private set; } + public SdmmcApi SdCard { get; private set; } + public ModLoader ModLoader { get; private set; } + + private readonly ConcurrentDictionary _romFsByPid; + + private static bool _isInitialized = false; + + public static VirtualFileSystem CreateInstance() + { + if (_isInitialized) + { + throw new InvalidOperationException("VirtualFileSystem can only be instantiated once!"); + } + + _isInitialized = true; + + return new VirtualFileSystem(); + } + + private VirtualFileSystem() + { + ReloadKeySet(); + ModLoader = new ModLoader(); // Should only be created once + _romFsByPid = new ConcurrentDictionary(); + } + + public void LoadRomFs(ulong pid, string fileName) + { + var romfsStream = new FileStream(fileName, FileMode.Open, FileAccess.Read); + + _romFsByPid.AddOrUpdate(pid, romfsStream, (pid, oldStream) => + { + oldStream.Close(); + + return romfsStream; + }); + } + + public void SetRomFs(ulong pid, Stream romfsStream) + { + _romFsByPid.AddOrUpdate(pid, romfsStream, (pid, oldStream) => + { + oldStream.Close(); + + return romfsStream; + }); + } + + public Stream GetRomFs(ulong pid) + { + return _romFsByPid[pid]; + } + + public static string GetFullPath(string basePath, string fileName) + { + if (fileName.StartsWith("//")) + { + fileName = fileName[2..]; + } + else if (fileName.StartsWith('/')) + { + fileName = fileName[1..]; + } + else + { + return null; + } + + string fullPath = Path.GetFullPath(Path.Combine(basePath, fileName)); + + if (!fullPath.StartsWith(AppDataManager.BaseDirPath)) + { + return null; + } + + return fullPath; + } + + internal static string GetSdCardPath() => MakeFullPath(AppDataManager.DefaultSdcardDir); + public static string GetNandPath() => MakeFullPath(AppDataManager.DefaultNandDir); + + public static string SwitchPathToSystemPath(string switchPath) + { + string[] parts = switchPath.Split(":"); + + if (parts.Length != 2) + { + return null; + } + + return GetFullPath(MakeFullPath(parts[0]), parts[1]); + } + + public static string SystemPathToSwitchPath(string systemPath) + { + string baseSystemPath = AppDataManager.BaseDirPath + Path.DirectorySeparatorChar; + + if (systemPath.StartsWith(baseSystemPath)) + { + string rawPath = systemPath.Replace(baseSystemPath, ""); + int firstSeparatorOffset = rawPath.IndexOf(Path.DirectorySeparatorChar); + + if (firstSeparatorOffset == -1) + { + return $"{rawPath}:/"; + } + + var basePath = rawPath.AsSpan(0, firstSeparatorOffset); + var fileName = rawPath.AsSpan(firstSeparatorOffset + 1); + + return $"{basePath}:/{fileName}"; + } + + return null; + } + + private static string MakeFullPath(string path, bool isDirectory = true) + { + // Handles Common Switch Content Paths + switch (path) + { + case ContentPath.SdCard: + path = AppDataManager.DefaultSdcardDir; + break; + case ContentPath.User: + path = UserNandPath; + break; + case ContentPath.System: + path = SystemNandPath; + break; + case ContentPath.SdCardContent: + path = Path.Combine(AppDataManager.DefaultSdcardDir, "Nintendo", "Contents"); + break; + case ContentPath.UserContent: + path = Path.Combine(UserNandPath, "Contents"); + break; + case ContentPath.SystemContent: + path = Path.Combine(SystemNandPath, "Contents"); + break; + } + + string fullPath = Path.Combine(AppDataManager.BaseDirPath, path); + + if (isDirectory && !Directory.Exists(fullPath)) + { + Directory.CreateDirectory(fullPath); + } + + return fullPath; + } + + public void InitializeFsServer(LibHac.Horizon horizon, out HorizonClient fsServerClient) + { + LocalFileSystem serverBaseFs = new(useUnixTimeStamps: true); + Result result = serverBaseFs.Initialize(AppDataManager.BaseDirPath, LocalFileSystem.PathMode.DefaultCaseSensitivity, ensurePathExists: true); + if (result.IsFailure()) + { + throw new HorizonResultException(result, "Error creating LocalFileSystem."); + } + + fsServerClient = horizon.CreatePrivilegedHorizonClient(); + var fsServer = new FileSystemServer(fsServerClient); + + RandomDataGenerator randomGenerator = Random.Shared.NextBytes; + + DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator); + + // Use our own encrypted fs creator that doesn't actually do any encryption + fsServerObjects.FsCreators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(); + + GameCard = fsServerObjects.GameCard; + SdCard = fsServerObjects.Sdmmc; + + SdCard.SetSdCardInserted(true); + + var fsServerConfig = new FileSystemServerConfig + { + ExternalKeySet = KeySet.ExternalKeySet, + FsCreators = fsServerObjects.FsCreators, + StorageDeviceManagerFactory = fsServerObjects.StorageDeviceManagerFactory, + RandomGenerator = randomGenerator, + }; + + FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig); + } + + public void ReloadKeySet() + { + KeySet ??= KeySet.CreateDefaultKeySet(); + + string keyFile = null; + string titleKeyFile = null; + string consoleKeyFile = null; + + if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile) + { + LoadSetAtPath(AppDataManager.KeysDirPathUser); + } + + LoadSetAtPath(AppDataManager.KeysDirPath); + + void LoadSetAtPath(string basePath) + { + string localKeyFile = Path.Combine(basePath, "prod.keys"); + string localTitleKeyFile = Path.Combine(basePath, "title.keys"); + string localConsoleKeyFile = Path.Combine(basePath, "console.keys"); + + if (File.Exists(localKeyFile)) + { + keyFile = localKeyFile; + } + + if (File.Exists(localTitleKeyFile)) + { + titleKeyFile = localTitleKeyFile; + } + + if (File.Exists(localConsoleKeyFile)) + { + consoleKeyFile = localConsoleKeyFile; + } + } + + ExternalKeyReader.ReadKeyFile(KeySet, keyFile, titleKeyFile, consoleKeyFile, null); + } + + public void ImportTickets(IFileSystem fs) + { + foreach (DirectoryEntryEx ticketEntry in fs.EnumerateEntries("/", "*.tik")) + { + using var ticketFile = new UniqueRef(); + + Result result = fs.OpenFile(ref ticketFile.Ref, ticketEntry.FullPath.ToU8Span(), OpenMode.Read); + + if (result.IsSuccess()) + { + // When reading a file from a Sha256PartitionFileSystem, you can't start a read in the middle + // of the hashed portion (usually the first 0x200 bytes) of the file and end the read after + // the end of the hashed portion, so we read the ticket file using a single read. + byte[] ticketData = new byte[0x2C0]; + result = ticketFile.Get.Read(out long bytesRead, 0, ticketData); + + if (result.IsFailure() || bytesRead != ticketData.Length) + continue; + + Ticket ticket = new(new MemoryStream(ticketData)); + var titleKey = ticket.GetTitleKey(KeySet); + + if (titleKey != null) + { + KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(titleKey)); + } + } + } + } + + // Save data created before we supported extra data in directory save data will not work properly if + // given empty extra data. Luckily some of that extra data can be created using the data from the + // save data indexer, which should be enough to check access permissions for user saves. + // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened. + // Consider removing this at some point in the future when we don't need to worry about old saves. + public static Result FixExtraData(HorizonClient hos) + { + Result rc = GetSystemSaveList(hos, out List systemSaveIds); + if (rc.IsFailure()) + { + return rc; + } + + rc = FixUnindexedSystemSaves(hos, systemSaveIds); + if (rc.IsFailure()) + { + return rc; + } + + rc = FixExtraDataInSpaceId(hos, SaveDataSpaceId.System); + if (rc.IsFailure()) + { + return rc; + } + + rc = FixExtraDataInSpaceId(hos, SaveDataSpaceId.User); + if (rc.IsFailure()) + { + return rc; + } + + return Result.Success; + } + + private static Result FixExtraDataInSpaceId(HorizonClient hos, SaveDataSpaceId spaceId) + { + Span info = stackalloc SaveDataInfo[8]; + + using var iterator = new UniqueRef(); + + Result rc = hos.Fs.OpenSaveDataIterator(ref iterator.Ref, spaceId); + if (rc.IsFailure()) + { + return rc; + } + + while (true) + { + rc = iterator.Get.ReadSaveDataInfo(out long count, info); + if (rc.IsFailure()) + { + return rc; + } + + if (count == 0) + { + return Result.Success; + } + + for (int i = 0; i < count; i++) + { + rc = FixExtraData(out bool wasFixNeeded, hos, in info[i]); + + if (ResultFs.TargetNotFound.Includes(rc)) + { + // If the save wasn't found, try to create the directory for its save data ID + rc = CreateSaveDataDirectory(hos, in info[i]); + + if (rc.IsFailure()) + { + Logger.Warning?.Print(LogClass.Application, $"Error {rc.ToStringWithName()} when creating save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space"); + + // Don't bother fixing the extra data if we couldn't create the directory + continue; + } + + Logger.Info?.Print(LogClass.Application, $"Recreated directory for save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space"); + + // Try to fix the extra data in the new directory + rc = FixExtraData(out wasFixNeeded, hos, in info[i]); + } + + if (rc.IsFailure()) + { + Logger.Warning?.Print(LogClass.Application, $"Error {rc.ToStringWithName()} when fixing extra data for save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space"); + } + else if (wasFixNeeded) + { + Logger.Info?.Print(LogClass.Application, $"Fixed extra data for save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space"); + } + } + } + } + + private static Result CreateSaveDataDirectory(HorizonClient hos, in SaveDataInfo info) + { + if (info.SpaceId != SaveDataSpaceId.User && info.SpaceId != SaveDataSpaceId.System) + { + return Result.Success; + } + + const string MountName = "SaveDir"; + var mountNameU8 = MountName.ToU8Span(); + + BisPartitionId partitionId = info.SpaceId switch + { + SaveDataSpaceId.System => BisPartitionId.System, + SaveDataSpaceId.User => BisPartitionId.User, + _ => throw new ArgumentOutOfRangeException(nameof(info), info.SpaceId, null), + }; + + Result rc = hos.Fs.MountBis(mountNameU8, partitionId); + if (rc.IsFailure()) + { + return rc; + } + + try + { + var path = $"{MountName}:/save/{info.SaveDataId:x16}".ToU8Span(); + + rc = hos.Fs.GetEntryType(out _, path); + + if (ResultFs.PathNotFound.Includes(rc)) + { + rc = hos.Fs.CreateDirectory(path); + } + + return rc; + } + finally + { + hos.Fs.Unmount(mountNameU8); + } + } + + // Gets a list of all the save data files or directories in the system partition. + private static Result GetSystemSaveList(HorizonClient hos, out List list) + { + list = null; + + var mountName = "system".ToU8Span(); + DirectoryHandle handle = default; + List localList = new(); + + try + { + Result rc = hos.Fs.MountBis(mountName, BisPartitionId.System); + if (rc.IsFailure()) + { + return rc; + } + + rc = hos.Fs.OpenDirectory(out handle, "system:/save".ToU8Span(), OpenDirectoryMode.All); + if (rc.IsFailure()) + { + return rc; + } + + DirectoryEntry entry = new(); + + while (true) + { + rc = hos.Fs.ReadDirectory(out long readCount, SpanHelpers.AsSpan(ref entry), handle); + if (rc.IsFailure()) + { + return rc; + } + + if (readCount == 0) + { + break; + } + + if (Utf8Parser.TryParse(entry.Name, out ulong saveDataId, out int bytesRead, 'x') && bytesRead == 16 && (long)saveDataId < 0) + { + localList.Add(saveDataId); + } + } + + list = localList; + + return Result.Success; + } + finally + { + if (handle.IsValid) + { + hos.Fs.CloseDirectory(handle); + } + + if (hos.Fs.IsMounted(mountName)) + { + hos.Fs.Unmount(mountName); + } + } + } + + // Adds system save data that isn't in the save data indexer to the indexer and creates extra data for it. + // Only save data IDs added to SystemExtraDataFixInfo will be fixed. + private static Result FixUnindexedSystemSaves(HorizonClient hos, List existingSaveIds) + { + foreach (var fixInfo in _systemExtraDataFixInfo) + { + if (!existingSaveIds.Contains(fixInfo.StaticSaveDataId)) + { + continue; + } + + Result rc = FixSystemExtraData(out bool wasFixNeeded, hos, in fixInfo); + + if (rc.IsFailure()) + { + Logger.Warning?.Print(LogClass.Application, + $"Error {rc.ToStringWithName()} when fixing extra data for system save data 0x{fixInfo.StaticSaveDataId:x}"); + } + else if (wasFixNeeded) + { + Logger.Info?.Print(LogClass.Application, + $"Tried to rebuild extra data for system save data 0x{fixInfo.StaticSaveDataId:x}"); + } + } + + return Result.Success; + } + + private static Result FixSystemExtraData(out bool wasFixNeeded, HorizonClient hos, in ExtraDataFixInfo info) + { + wasFixNeeded = true; + + Result rc = hos.Fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, info.StaticSaveDataId); + if (!rc.IsSuccess()) + { + if (!ResultFs.TargetNotFound.Includes(rc)) + { + return rc; + } + + // We'll reach this point only if the save data directory exists but it's not in the save data indexer. + // Creating the save will add it to the indexer while leaving its existing contents intact. + return hos.Fs.CreateSystemSaveData(info.StaticSaveDataId, UserId.InvalidId, info.OwnerId, info.DataSize, + info.JournalSize, info.Flags); + } + + if (extraData.Attribute.StaticSaveDataId != 0 && extraData.OwnerId != 0) + { + wasFixNeeded = false; + return Result.Success; + } + + extraData = new SaveDataExtraData + { + Attribute = { StaticSaveDataId = info.StaticSaveDataId }, + OwnerId = info.OwnerId, + Flags = info.Flags, + DataSize = info.DataSize, + JournalSize = info.JournalSize, + }; + + // Make a mask for writing the entire extra data + Unsafe.SkipInit(out SaveDataExtraData extraDataMask); + SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); + + return hos.Fs.Impl.WriteSaveDataFileSystemExtraData(SaveDataSpaceId.System, info.StaticSaveDataId, + in extraData, in extraDataMask); + } + + private static Result FixExtraData(out bool wasFixNeeded, HorizonClient hos, in SaveDataInfo info) + { + wasFixNeeded = true; + + Result rc = hos.Fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, info.SpaceId, info.SaveDataId); + if (rc.IsFailure()) + { + return rc; + } + + // The extra data should have program ID or static save data ID set if it's valid. + // We only try to fix the extra data if the info from the save data indexer has a program ID or static save data ID. + bool canFixByProgramId = extraData.Attribute.ProgramId == ProgramId.InvalidId && + info.ProgramId != ProgramId.InvalidId; + + bool canFixBySaveDataId = extraData.Attribute.StaticSaveDataId == 0 && info.StaticSaveDataId != 0; + + bool hasEmptyOwnerId = extraData.OwnerId == 0 && info.Type != SaveDataType.System; + + if (!canFixByProgramId && !canFixBySaveDataId && !hasEmptyOwnerId) + { + wasFixNeeded = false; + return Result.Success; + } + + // The save data attribute struct can be completely created from the save data info. + extraData.Attribute.ProgramId = info.ProgramId; + extraData.Attribute.UserId = info.UserId; + extraData.Attribute.StaticSaveDataId = info.StaticSaveDataId; + extraData.Attribute.Type = info.Type; + extraData.Attribute.Rank = info.Rank; + extraData.Attribute.Index = info.Index; + + // The rest of the extra data can't be created from the save data info. + // On user saves the owner ID will almost certainly be the same as the program ID. + if (info.Type != SaveDataType.System) + { + extraData.OwnerId = info.ProgramId.Value; + } + else + { + // Try to match the system save with one of the known saves + foreach (ExtraDataFixInfo fixInfo in _systemExtraDataFixInfo) + { + if (extraData.Attribute.StaticSaveDataId == fixInfo.StaticSaveDataId) + { + extraData.OwnerId = fixInfo.OwnerId; + extraData.Flags = fixInfo.Flags; + extraData.DataSize = fixInfo.DataSize; + extraData.JournalSize = fixInfo.JournalSize; + + break; + } + } + } + + // Make a mask for writing the entire extra data + Unsafe.SkipInit(out SaveDataExtraData extraDataMask); + SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF); + + return hos.Fs.Impl.WriteSaveDataFileSystemExtraData(info.SpaceId, info.SaveDataId, in extraData, in extraDataMask); + } + + struct ExtraDataFixInfo + { + public ulong StaticSaveDataId; + public ulong OwnerId; + public SaveDataFlags Flags; + public long DataSize; + public long JournalSize; + } + + private static readonly ExtraDataFixInfo[] _systemExtraDataFixInfo = + { + new ExtraDataFixInfo() + { + StaticSaveDataId = 0x8000000000000030, + OwnerId = 0x010000000000001F, + Flags = SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData, + DataSize = 0x10000, + JournalSize = 0x10000, + }, + new ExtraDataFixInfo() + { + StaticSaveDataId = 0x8000000000001040, + OwnerId = 0x0100000000001009, + Flags = SaveDataFlags.None, + DataSize = 0xC000, + JournalSize = 0xC000, + }, + }; + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (var stream in _romFsByPid.Values) + { + stream.Close(); + } + + _romFsByPid.Clear(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HLEConfiguration.cs b/src/Ryujinx.HLE/HLEConfiguration.cs new file mode 100644 index 00000000..955fee4b --- /dev/null +++ b/src/Ryujinx.HLE/HLEConfiguration.cs @@ -0,0 +1,227 @@ +using LibHac.Tools.FsSystem; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Multiplayer; +using Ryujinx.Graphics.GAL; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.HLE.UI; +using System; + +namespace Ryujinx.HLE +{ + /// + /// HLE configuration. + /// + public class HLEConfiguration + { + /// + /// The virtual file system used by the FS service. + /// + /// This cannot be changed after instantiation. + internal readonly VirtualFileSystem VirtualFileSystem; + + /// + /// The manager for handling a LibHac Horizon instance. + /// + /// This cannot be changed after instantiation. + internal readonly LibHacHorizonManager LibHacHorizonManager; + + /// + /// The account manager used by the account service. + /// + /// This cannot be changed after instantiation. + internal readonly AccountManager AccountManager; + + /// + /// The content manager used by the NCM service. + /// + /// This cannot be changed after instantiation. + internal readonly ContentManager ContentManager; + + /// + /// The persistent information between run for multi-application capabilities. + /// + /// This cannot be changed after instantiation. + public readonly UserChannelPersistence UserChannelPersistence; + + /// + /// The GPU renderer to use for all GPU operations. + /// + /// This cannot be changed after instantiation. + internal readonly IRenderer GpuRenderer; + + /// + /// The audio device driver to use for all audio operations. + /// + /// This cannot be changed after instantiation. + internal readonly IHardwareDeviceDriver AudioDeviceDriver; + + /// + /// The handler for various UI related operations needed outside of HLE. + /// + /// This cannot be changed after instantiation. + internal readonly IHostUIHandler HostUIHandler; + + /// + /// Control the memory configuration used by the emulation context. + /// + /// This cannot be changed after instantiation. + internal readonly MemoryConfiguration MemoryConfiguration; + + /// + /// The system language to use in the settings service. + /// + /// This cannot be changed after instantiation. + internal readonly SystemLanguage SystemLanguage; + + /// + /// The system region to use in the settings service. + /// + /// This cannot be changed after instantiation. + internal readonly RegionCode Region; + + /// + /// Control the initial state of the vertical sync in the SurfaceFlinger service. + /// + internal readonly bool EnableVsync; + + /// + /// Control the initial state of the docked mode. + /// + internal readonly bool EnableDockedMode; + + /// + /// Control if the Profiled Translation Cache (PTC) should be used. + /// + internal readonly bool EnablePtc; + + /// + /// Control if the guest application should be told that there is a Internet connection available. + /// + public bool EnableInternetAccess { internal get; set; } + + /// + /// Control LibHac's integrity check level. + /// + /// This cannot be changed after instantiation. + internal readonly IntegrityCheckLevel FsIntegrityCheckLevel; + + /// + /// Control LibHac's global access logging level. Value must be between 0 and 3. + /// + /// This cannot be changed after instantiation. + internal readonly int FsGlobalAccessLogMode; + + /// + /// The system time offset to apply to the time service steady and local clocks. + /// + /// This cannot be changed after instantiation. + internal readonly long SystemTimeOffset; + + /// + /// The system timezone used by the time service. + /// + /// This cannot be changed after instantiation. + internal readonly string TimeZone; + + /// + /// Type of the memory manager used on CPU emulation. + /// + public MemoryManagerMode MemoryManagerMode { internal get; set; } + + /// + /// Control the initial state of the ignore missing services setting. + /// If this is set to true, when a missing service is encountered, it will try to automatically handle it instead of throwing an exception. + /// + /// TODO: Update this again. + public bool IgnoreMissingServices { internal get; set; } + + /// + /// Aspect Ratio applied to the renderer window by the SurfaceFlinger service. + /// + public AspectRatio AspectRatio { get; set; } + + /// + /// The audio volume level. + /// + public float AudioVolume { get; set; } + + /// + /// Use Hypervisor over JIT if available. + /// + internal readonly bool UseHypervisor; + + /// + /// Multiplayer LAN Interface ID (device GUID) + /// + public string MultiplayerLanInterfaceId { internal get; set; } + + /// + /// Multiplayer Mode + /// + public MultiplayerMode MultiplayerMode { internal get; set; } + + /// + /// An action called when HLE force a refresh of output after docked mode changed. + /// + public Action RefreshInputConfig { internal get; set; } + + public HLEConfiguration(VirtualFileSystem virtualFileSystem, + LibHacHorizonManager libHacHorizonManager, + ContentManager contentManager, + AccountManager accountManager, + UserChannelPersistence userChannelPersistence, + IRenderer gpuRenderer, + IHardwareDeviceDriver audioDeviceDriver, + MemoryConfiguration memoryConfiguration, + IHostUIHandler hostUIHandler, + SystemLanguage systemLanguage, + RegionCode region, + bool enableVsync, + bool enableDockedMode, + bool enablePtc, + bool enableInternetAccess, + IntegrityCheckLevel fsIntegrityCheckLevel, + int fsGlobalAccessLogMode, + long systemTimeOffset, + string timeZone, + MemoryManagerMode memoryManagerMode, + bool ignoreMissingServices, + AspectRatio aspectRatio, + float audioVolume, + bool useHypervisor, + string multiplayerLanInterfaceId, + MultiplayerMode multiplayerMode) + { + VirtualFileSystem = virtualFileSystem; + LibHacHorizonManager = libHacHorizonManager; + AccountManager = accountManager; + ContentManager = contentManager; + UserChannelPersistence = userChannelPersistence; + GpuRenderer = gpuRenderer; + AudioDeviceDriver = audioDeviceDriver; + MemoryConfiguration = memoryConfiguration; + HostUIHandler = hostUIHandler; + SystemLanguage = systemLanguage; + Region = region; + EnableVsync = enableVsync; + EnableDockedMode = enableDockedMode; + EnablePtc = enablePtc; + EnableInternetAccess = enableInternetAccess; + FsIntegrityCheckLevel = fsIntegrityCheckLevel; + FsGlobalAccessLogMode = fsGlobalAccessLogMode; + SystemTimeOffset = systemTimeOffset; + TimeZone = timeZone; + MemoryManagerMode = memoryManagerMode; + IgnoreMissingServices = ignoreMissingServices; + AspectRatio = aspectRatio; + AudioVolume = audioVolume; + UseHypervisor = useHypervisor; + MultiplayerLanInterfaceId = multiplayerLanInterfaceId; + MultiplayerMode = multiplayerMode; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs b/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs new file mode 100644 index 00000000..3c34d5c7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs @@ -0,0 +1,34 @@ +using Ryujinx.HLE.HOS.Applets.Browser; +using Ryujinx.HLE.HOS.Applets.Error; +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Applets +{ + static class AppletManager + { + public static IApplet Create(AppletId applet, Horizon system) + { + switch (applet) + { + case AppletId.Controller: + return new ControllerApplet(system); + case AppletId.Error: + return new ErrorApplet(system); + case AppletId.PlayerSelect: + return new PlayerSelectApplet(system); + case AppletId.SoftwareKeyboard: + return new SoftwareKeyboardApplet(system); + case AppletId.LibAppletWeb: + return new BrowserApplet(system); + case AppletId.LibAppletShop: + return new BrowserApplet(system); + case AppletId.LibAppletOff: + return new BrowserApplet(system); + } + + throw new NotImplementedException($"{applet} applet is not implemented."); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/BootDisplayKind.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/BootDisplayKind.cs new file mode 100644 index 00000000..3c5b1f1c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/BootDisplayKind.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Applets.Browser +{ + enum BootDisplayKind + { + White, + Offline, + Black, + Share, + Lobby, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs new file mode 100644 index 00000000..6afbe4a7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs @@ -0,0 +1,99 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Applets.Browser +{ + internal class BrowserApplet : IApplet + { + public event EventHandler AppletStateChanged; + + private AppletSession _normalSession; + + private CommonArguments _commonArguments; + private List _arguments; + private ShimKind _shimKind; + + public BrowserApplet(Horizon system) { } + + public ResultCode GetResult() + { + return ResultCode.Success; + } + + public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) + { + _normalSession = normalSession; + + _commonArguments = IApplet.ReadStruct(_normalSession.Pop()); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, $"WebApplet version: 0x{_commonArguments.AppletVersion:x8}"); + + ReadOnlySpan webArguments = _normalSession.Pop(); + + (_shimKind, _arguments) = BrowserArgument.ParseArguments(webArguments); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, $"Web Arguments: {_arguments.Count}"); + + foreach (BrowserArgument argument in _arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm, $"{argument.Type}: {argument.GetValue()}"); + } + + if ((_commonArguments.AppletVersion >= 0x80000 && _shimKind == ShimKind.Web) || (_commonArguments.AppletVersion >= 0x30000 && _shimKind == ShimKind.Share)) + { + List result = new() + { + new BrowserOutput(BrowserOutputType.ExitReason, (uint)WebExitReason.ExitButton), + }; + + _normalSession.Push(BuildResponseNew(result)); + } + else + { + WebCommonReturnValue result = new() + { + ExitReason = WebExitReason.ExitButton, + }; + + _normalSession.Push(BuildResponseOld(result)); + } + + AppletStateChanged?.Invoke(this, null); + + return ResultCode.Success; + } + + private static byte[] BuildResponseOld(WebCommonReturnValue result) + { + using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + using BinaryWriter writer = new(stream); + writer.WriteStruct(result); + + return stream.ToArray(); + } + private byte[] BuildResponseNew(List outputArguments) + { + using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + using BinaryWriter writer = new(stream); + writer.WriteStruct(new WebArgHeader + { + Count = (ushort)outputArguments.Count, + ShimKind = _shimKind, + }); + + foreach (BrowserOutput output in outputArguments) + { + output.Write(writer); + } + + writer.Write(new byte[0x2000 - writer.BaseStream.Position]); + + return stream.ToArray(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserArgument.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserArgument.cs new file mode 100644 index 00000000..d2e1d55c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserArgument.cs @@ -0,0 +1,133 @@ +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Applets.Browser +{ + class BrowserArgument + { + public WebArgTLVType Type { get; } + public byte[] Value { get; } + + public BrowserArgument(WebArgTLVType type, byte[] value) + { + Type = type; + Value = value; + } + + private static readonly Dictionary _typeRegistry = new() + { + { WebArgTLVType.InitialURL, typeof(string) }, + { WebArgTLVType.CallbackUrl, typeof(string) }, + { WebArgTLVType.CallbackableUrl, typeof(string) }, + { WebArgTLVType.ApplicationId, typeof(ulong) }, + { WebArgTLVType.DocumentPath, typeof(string) }, + { WebArgTLVType.DocumentKind, typeof(DocumentKind) }, + { WebArgTLVType.SystemDataId, typeof(ulong) }, + { WebArgTLVType.Whitelist, typeof(string) }, + { WebArgTLVType.NewsFlag, typeof(bool) }, + { WebArgTLVType.UserID, typeof(UserId) }, + { WebArgTLVType.ScreenShotEnabled, typeof(bool) }, + { WebArgTLVType.EcClientCertEnabled, typeof(bool) }, + { WebArgTLVType.UnknownFlag0x14, typeof(bool) }, + { WebArgTLVType.UnknownFlag0x15, typeof(bool) }, + { WebArgTLVType.PlayReportEnabled, typeof(bool) }, + { WebArgTLVType.BootDisplayKind, typeof(BootDisplayKind) }, + { WebArgTLVType.FooterEnabled, typeof(bool) }, + { WebArgTLVType.PointerEnabled, typeof(bool) }, + { WebArgTLVType.LeftStickMode, typeof(LeftStickMode) }, + { WebArgTLVType.KeyRepeatFrame1, typeof(int) }, + { WebArgTLVType.KeyRepeatFrame2, typeof(int) }, + { WebArgTLVType.BootAsMediaPlayerInverted, typeof(bool) }, + { WebArgTLVType.DisplayUrlKind, typeof(bool) }, + { WebArgTLVType.BootAsMediaPlayer, typeof(bool) }, + { WebArgTLVType.ShopJumpEnabled, typeof(bool) }, + { WebArgTLVType.MediaAutoPlayEnabled, typeof(bool) }, + { WebArgTLVType.LobbyParameter, typeof(string) }, + { WebArgTLVType.JsExtensionEnabled, typeof(bool) }, + { WebArgTLVType.AdditionalCommentText, typeof(string) }, + { WebArgTLVType.TouchEnabledOnContents, typeof(bool) }, + { WebArgTLVType.UserAgentAdditionalString, typeof(string) }, + { WebArgTLVType.MediaPlayerAutoCloseEnabled, typeof(bool) }, + { WebArgTLVType.PageCacheEnabled, typeof(bool) }, + { WebArgTLVType.WebAudioEnabled, typeof(bool) }, + { WebArgTLVType.PageFadeEnabled, typeof(bool) }, + { WebArgTLVType.BootLoadingIconEnabled, typeof(bool) }, + { WebArgTLVType.PageScrollIndicatorEnabled, typeof(bool) }, + { WebArgTLVType.MediaPlayerSpeedControlEnabled, typeof(bool) }, + { WebArgTLVType.OverrideWebAudioVolume, typeof(float) }, + { WebArgTLVType.OverrideMediaAudioVolume, typeof(float) }, + { WebArgTLVType.MediaPlayerUiEnabled, typeof(bool) }, + }; + + public static (ShimKind, List) ParseArguments(ReadOnlySpan data) + { + List browserArguments = new(); + + WebArgHeader header = IApplet.ReadStruct(data[..8]); + + ReadOnlySpan rawTLVs = data[8..]; + + for (int i = 0; i < header.Count; i++) + { + WebArgTLV tlv = IApplet.ReadStruct(rawTLVs); + ReadOnlySpan tlvData = rawTLVs.Slice(Unsafe.SizeOf(), tlv.Size); + + browserArguments.Add(new BrowserArgument((WebArgTLVType)tlv.Type, tlvData.ToArray())); + + rawTLVs = rawTLVs[(Unsafe.SizeOf() + tlv.Size)..]; + } + + return (header.ShimKind, browserArguments); + } + + public object GetValue() + { + if (_typeRegistry.TryGetValue(Type, out Type valueType)) + { + if (valueType == typeof(string)) + { + return Encoding.UTF8.GetString(Value); + } + else if (valueType == typeof(bool)) + { + return Value[0] == 1; + } + else if (valueType == typeof(uint)) + { + return BitConverter.ToUInt32(Value); + } + else if (valueType == typeof(int)) + { + return BitConverter.ToInt32(Value); + } + else if (valueType == typeof(ulong)) + { + return BitConverter.ToUInt64(Value); + } + else if (valueType == typeof(long)) + { + return BitConverter.ToInt64(Value); + } + else if (valueType == typeof(float)) + { + return BitConverter.ToSingle(Value); + } + else if (valueType == typeof(UserId)) + { + return new UserId(Value); + } + else if (valueType.IsEnum) + { + return Enum.ToObject(valueType, BitConverter.ToInt32(Value)); + } + + return $"{valueType.Name} parsing not implemented"; + } + + return $"Unknown value format (raw length: {Value.Length})"; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutput.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutput.cs new file mode 100644 index 00000000..b0e2d0e1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutput.cs @@ -0,0 +1,47 @@ +using Ryujinx.Common; +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Applets.Browser +{ + class BrowserOutput + { + public BrowserOutputType Type { get; } + public byte[] Value { get; } + + public BrowserOutput(BrowserOutputType type, byte[] value) + { + Type = type; + Value = value; + } + + public BrowserOutput(BrowserOutputType type, uint value) + { + Type = type; + Value = BitConverter.GetBytes(value); + } + + public BrowserOutput(BrowserOutputType type, ulong value) + { + Type = type; + Value = BitConverter.GetBytes(value); + } + + public BrowserOutput(BrowserOutputType type, bool value) + { + Type = type; + Value = BitConverter.GetBytes(value); + } + + public void Write(BinaryWriter writer) + { + writer.WriteStruct(new WebArgTLV + { + Type = (ushort)Type, + Size = (ushort)Value.Length, + }); + + writer.Write(Value); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutputType.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutputType.cs new file mode 100644 index 00000000..b6908c47 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutputType.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Applets.Browser +{ + enum BrowserOutputType : ushort + { + ExitReason = 0x1, + LastUrl = 0x2, + LastUrlSize = 0x3, + SharePostResult = 0x4, + PostServiceName = 0x5, + PostServiceNameSize = 0x6, + PostId = 0x7, + MediaPlayerAutoClosedByCompletion = 0x8, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/DocumentKind.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/DocumentKind.cs new file mode 100644 index 00000000..9e089a20 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/DocumentKind.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Applets.Browser +{ + enum DocumentKind + { + OfflineHtmlPage = 1, + ApplicationLegalInformation, + SystemDataPage, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/LeftStickMode.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/LeftStickMode.cs new file mode 100644 index 00000000..7c2f31a2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/LeftStickMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Applets.Browser +{ + enum LeftStickMode + { + Pointer = 0, + Cursor, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs new file mode 100644 index 00000000..60de546d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Applets.Browser +{ + public enum ShimKind : uint + { + Shop = 1, + Login, + Offline, + Share, + Web, + Wifi, + Lobby, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgHeader.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgHeader.cs new file mode 100644 index 00000000..e67d7091 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgHeader.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Applets.Browser +{ + public struct WebArgHeader + { + public ushort Count; + public ushort Padding; + public ShimKind ShimKind; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs new file mode 100644 index 00000000..a331e82f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Applets.Browser +{ + public struct WebArgTLV + { + public ushort Type; + public ushort Size; + public uint Padding; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLVType.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLVType.cs new file mode 100644 index 00000000..2f2450dc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLVType.cs @@ -0,0 +1,62 @@ +namespace Ryujinx.HLE.HOS.Applets.Browser +{ + enum WebArgTLVType : ushort + { + InitialURL = 0x1, + CallbackUrl = 0x3, + CallbackableUrl = 0x4, + ApplicationId = 0x5, + DocumentPath = 0x6, + DocumentKind = 0x7, + SystemDataId = 0x8, + ShareStartPage = 0x9, + Whitelist = 0xA, + NewsFlag = 0xB, + UserID = 0xE, + AlbumEntry0 = 0xF, + ScreenShotEnabled = 0x10, + EcClientCertEnabled = 0x11, + PlayReportEnabled = 0x13, + UnknownFlag0x14 = 0x14, + UnknownFlag0x15 = 0x15, + BootDisplayKind = 0x17, + BackgroundKind = 0x18, + FooterEnabled = 0x19, + PointerEnabled = 0x1A, + LeftStickMode = 0x1B, + KeyRepeatFrame1 = 0x1C, + KeyRepeatFrame2 = 0x1D, + BootAsMediaPlayerInverted = 0x1E, + DisplayUrlKind = 0x1F, + BootAsMediaPlayer = 0x21, + ShopJumpEnabled = 0x22, + MediaAutoPlayEnabled = 0x23, + LobbyParameter = 0x24, + ApplicationAlbumEntry = 0x26, + JsExtensionEnabled = 0x27, + AdditionalCommentText = 0x28, + TouchEnabledOnContents = 0x29, + UserAgentAdditionalString = 0x2A, + AdditionalMediaData0 = 0x2B, + MediaPlayerAutoCloseEnabled = 0x2C, + PageCacheEnabled = 0x2D, + WebAudioEnabled = 0x2E, + FooterFixedKind = 0x32, + PageFadeEnabled = 0x33, + MediaCreatorApplicationRatingAge = 0x34, + BootLoadingIconEnabled = 0x35, + PageScrollIndicatorEnabled = 0x36, + MediaPlayerSpeedControlEnabled = 0x37, + AlbumEntry1 = 0x38, + AlbumEntry2 = 0x39, + AlbumEntry3 = 0x3A, + AdditionalMediaData1 = 0x3B, + AdditionalMediaData2 = 0x3C, + AdditionalMediaData3 = 0x3D, + BootFooterButton = 0x3E, + OverrideWebAudioVolume = 0x3F, + OverrideMediaAudioVolume = 0x40, + BootMode = 0x41, + MediaPlayerUiEnabled = 0x43, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/WebCommonReturnValue.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/WebCommonReturnValue.cs new file mode 100644 index 00000000..cc1f3706 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/WebCommonReturnValue.cs @@ -0,0 +1,12 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.HLE.HOS.Applets.Browser +{ + public struct WebCommonReturnValue + { + public WebExitReason ExitReason; + public uint Padding; + public ByteArray4096 LastUrl; + public ulong LastUrlSize; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Browser/WebExitReason.cs b/src/Ryujinx.HLE/HOS/Applets/Browser/WebExitReason.cs new file mode 100644 index 00000000..ebb705ac --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Browser/WebExitReason.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Applets.Browser +{ + public enum WebExitReason : uint + { + ExitButton, + BackButton, + Requested, + LastUrl, + ErrorDialog = 7, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/CommonArguments.cs b/src/Ryujinx.HLE/HOS/Applets/CommonArguments.cs new file mode 100644 index 00000000..0360aff8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/CommonArguments.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets +{ + [StructLayout(LayoutKind.Sequential, Pack = 8)] + struct CommonArguments + { + public uint Version; + public uint StructureSize; + public uint AppletVersion; + public uint ThemeColor; + [MarshalAs(UnmanagedType.I1)] + public bool PlayStartupSound; + public ulong SystemTicks; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs new file mode 100644 index 00000000..5ec9d4b0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs @@ -0,0 +1,145 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using Ryujinx.HLE.HOS.Services.Hid; +using Ryujinx.HLE.HOS.Services.Hid.Types; +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.HLE.HOS.Services.Hid.HidServer.HidUtils; + +namespace Ryujinx.HLE.HOS.Applets +{ + internal class ControllerApplet : IApplet + { + private readonly Horizon _system; + + private AppletSession _normalSession; + + public event EventHandler AppletStateChanged; + + public ControllerApplet(Horizon system) + { + _system = system; + } + + public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) + { + _normalSession = normalSession; + + byte[] launchParams = _normalSession.Pop(); + byte[] controllerSupportArgPrivate = _normalSession.Pop(); + ControllerSupportArgPrivate privateArg = IApplet.ReadStruct(controllerSupportArgPrivate); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet ArgPriv {privateArg.PrivateSize} {privateArg.ArgSize} {privateArg.Mode} " + + $"HoldType:{(NpadJoyHoldType)privateArg.NpadJoyHoldType} StyleSets:{(ControllerType)privateArg.NpadStyleSet}"); + + if (privateArg.Mode != ControllerSupportMode.ShowControllerSupport) + { + _normalSession.Push(BuildResponse()); // Dummy response for other modes + AppletStateChanged?.Invoke(this, null); + + return ResultCode.Success; + } + + byte[] controllerSupportArg = _normalSession.Pop(); + + ControllerSupportArgHeader argHeader; + + if (privateArg.ArgSize == Marshal.SizeOf()) + { + ControllerSupportArgV7 arg = IApplet.ReadStruct(controllerSupportArg); + argHeader = arg.Header; + + Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerSupportArg Version 7 EnableExplainText={arg.EnableExplainText != 0}"); + // Read enable text here? + } + else if (privateArg.ArgSize == Marshal.SizeOf()) + { + ControllerSupportArgVPre7 arg = IApplet.ReadStruct(controllerSupportArg); + argHeader = arg.Header; + + Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerSupportArg Version Pre-7 EnableExplainText={arg.EnableExplainText != 0}"); + // Read enable text here? + } + else + { + Logger.Stub?.PrintStub(LogClass.ServiceHid, "ControllerSupportArg Version Unknown"); + + argHeader = IApplet.ReadStruct(controllerSupportArg); // Read just the header + } + + int playerMin = argHeader.PlayerCountMin; + int playerMax = argHeader.PlayerCountMax; + bool singleMode = argHeader.EnableSingleMode != 0; + + Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet Arg {playerMin} {playerMax} {argHeader.EnableTakeOverConnection} {argHeader.EnableSingleMode}"); + + if (singleMode) + { + // Applications can set an arbitrary player range even with SingleMode, so clamp it + playerMin = playerMax = 1; + } + + int configuredCount; + PlayerIndex primaryIndex; + while (!_system.Device.Hid.Npads.Validate(playerMin, playerMax, (ControllerType)privateArg.NpadStyleSet, out configuredCount, out primaryIndex)) + { + ControllerAppletUIArgs uiArgs = new() + { + PlayerCountMin = playerMin, + PlayerCountMax = playerMax, + SupportedStyles = (ControllerType)privateArg.NpadStyleSet, + SupportedPlayers = _system.Device.Hid.Npads.GetSupportedPlayers(), + IsDocked = _system.State.DockedMode, + }; + + if (!_system.Device.UIHandler.DisplayMessageDialog(uiArgs)) + { + break; + } + } + + ControllerSupportResultInfo result = new() + { + PlayerCount = (sbyte)configuredCount, + SelectedId = (uint)GetNpadIdTypeFromIndex(primaryIndex), + }; + + Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet ReturnResult {result.PlayerCount} {result.SelectedId}"); + + _normalSession.Push(BuildResponse(result)); + AppletStateChanged?.Invoke(this, null); + + _system.ReturnFocus(); + + return ResultCode.Success; + } + + public ResultCode GetResult() + { + return ResultCode.Success; + } + + private static byte[] BuildResponse(ControllerSupportResultInfo result) + { + using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + using BinaryWriter writer = new(stream); + + writer.Write(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref result, Unsafe.SizeOf()))); + + return stream.ToArray(); + } + + private static byte[] BuildResponse() + { + using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + using BinaryWriter writer = new(stream); + + writer.Write((ulong)ResultCode.Success); + + return stream.ToArray(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUIArgs.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUIArgs.cs new file mode 100644 index 00000000..10cba58b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUIArgs.cs @@ -0,0 +1,14 @@ +using Ryujinx.HLE.HOS.Services.Hid; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Applets +{ + public struct ControllerAppletUIArgs + { + public int PlayerCountMin; + public int PlayerCountMax; + public ControllerType SupportedStyles; + public IEnumerable SupportedPlayers; + public bool IsDocked; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgHeader.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgHeader.cs new file mode 100644 index 00000000..b066da8e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgHeader.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets +{ +#pragma warning disable CS0649 // Field is never assigned to + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ControllerSupportArgHeader + { + public sbyte PlayerCountMin; + public sbyte PlayerCountMax; + public byte EnableTakeOverConnection; + public byte EnableLeftJustify; + public byte EnablePermitJoyDual; + public byte EnableSingleMode; + public byte EnableIdentificationColor; + } +#pragma warning restore CS0649 +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgPrivate.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgPrivate.cs new file mode 100644 index 00000000..d5a18d46 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgPrivate.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Applets +{ +#pragma warning disable CS0649 // Field is never assigned to + struct ControllerSupportArgPrivate + { + public uint PrivateSize; + public uint ArgSize; + public byte Flag0; + public byte Flag1; + public ControllerSupportMode Mode; + public byte ControllerSupportCaller; + public uint NpadStyleSet; + public uint NpadJoyHoldType; + } +#pragma warning restore CS0649 +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgV7.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgV7.cs new file mode 100644 index 00000000..0969493f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgV7.cs @@ -0,0 +1,26 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets +{ +#pragma warning disable CS0649 // Field is never assigned to + // (8.0.0+ version) + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ControllerSupportArgV7 + { + public ControllerSupportArgHeader Header; + public Array8 IdentificationColor; + public byte EnableExplainText; + public ExplainTextStruct ExplainText; + + [StructLayout(LayoutKind.Sequential, Size = 8 * 0x81)] + public struct ExplainTextStruct + { + private byte element; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref element, 8 * 0x81); + } + } +#pragma warning restore CS0649 +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgVPre7.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgVPre7.cs new file mode 100644 index 00000000..88678255 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgVPre7.cs @@ -0,0 +1,26 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets +{ +#pragma warning disable CS0649 // Field is never assigned to + // (1.0.0+ version) + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ControllerSupportArgVPre7 + { + public ControllerSupportArgHeader Header; + public Array4 IdentificationColor; + public byte EnableExplainText; + public ExplainTextStruct ExplainText; + + [StructLayout(LayoutKind.Sequential, Size = 4 * 0x81)] + public struct ExplainTextStruct + { + private byte element; + + public Span AsSpan() => MemoryMarshal.CreateSpan(ref element, 4 * 0x81); + } + } +#pragma warning restore CS0649 +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportMode.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportMode.cs new file mode 100644 index 00000000..5e181e19 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Applets +{ + enum ControllerSupportMode : byte + { + ShowControllerSupport = 0, + ShowControllerStrapGuide = 1, + ShowControllerFirmwareUpdate = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportResultInfo.cs b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportResultInfo.cs new file mode 100644 index 00000000..96cfd590 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportResultInfo.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets +{ +#pragma warning disable CS0649 // Field is never assigned to + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ControllerSupportResultInfo + { + public sbyte PlayerCount; + private Array3 _padding; + public uint SelectedId; + public uint Result; + } +#pragma warning restore CS0649 +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ApplicationErrorArg.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ApplicationErrorArg.cs new file mode 100644 index 00000000..0a20bee4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Error/ApplicationErrorArg.cs @@ -0,0 +1,14 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.Error +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ApplicationErrorArg + { + public uint ErrorNumber; + public ulong LanguageCode; + public ByteArray2048 MessageText; + public ByteArray2048 DetailsText; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs new file mode 100644 index 00000000..7ee9b9e9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs @@ -0,0 +1,217 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using Ryujinx.HLE.HOS.SystemState; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; + +namespace Ryujinx.HLE.HOS.Applets.Error +{ + internal partial class ErrorApplet : IApplet + { + private const long ErrorMessageBinaryTitleId = 0x0100000000000801; + + private readonly Horizon _horizon; + private AppletSession _normalSession; + private CommonArguments _commonArguments; + private ErrorCommonHeader _errorCommonHeader; + private byte[] _errorStorage; + + public event EventHandler AppletStateChanged; + + [GeneratedRegex(@"[^\u0000\u0009\u000A\u000D\u0020-\uFFFF]..")] + private static partial Regex CleanTextRegex(); + + public ErrorApplet(Horizon horizon) + { + _horizon = horizon; + } + + public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) + { + _normalSession = normalSession; + _commonArguments = IApplet.ReadStruct(_normalSession.Pop()); + + Logger.Info?.PrintMsg(LogClass.ServiceAm, $"ErrorApplet version: 0x{_commonArguments.AppletVersion:x8}"); + + _errorStorage = _normalSession.Pop(); + _errorCommonHeader = IApplet.ReadStruct(_errorStorage); + _errorStorage = _errorStorage.Skip(Marshal.SizeOf()).ToArray(); + + switch (_errorCommonHeader.Type) + { + case ErrorType.ErrorCommonArg: + { + ParseErrorCommonArg(); + + break; + } + case ErrorType.ApplicationErrorArg: + { + ParseApplicationErrorArg(); + + break; + } + default: + throw new NotImplementedException($"ErrorApplet type {_errorCommonHeader.Type} is not implemented."); + } + + AppletStateChanged?.Invoke(this, null); + + return ResultCode.Success; + } + + private static (uint module, uint description) HexToResultCode(uint resultCode) + { + return ((resultCode & 0x1FF) + 2000, (resultCode >> 9) & 0x3FFF); + } + + private static string SystemLanguageToLanguageKey(SystemLanguage systemLanguage) + { + return systemLanguage switch + { +#pragma warning disable IDE0055 // Disable formatting + SystemLanguage.Japanese => "ja", + SystemLanguage.AmericanEnglish => "en-US", + SystemLanguage.French => "fr", + SystemLanguage.German => "de", + SystemLanguage.Italian => "it", + SystemLanguage.Spanish => "es", + SystemLanguage.Chinese => "zh-Hans", + SystemLanguage.Korean => "ko", + SystemLanguage.Dutch => "nl", + SystemLanguage.Portuguese => "pt", + SystemLanguage.Russian => "ru", + SystemLanguage.Taiwanese => "zh-HansT", + SystemLanguage.BritishEnglish => "en-GB", + SystemLanguage.CanadianFrench => "fr-CA", + SystemLanguage.LatinAmericanSpanish => "es-419", + SystemLanguage.SimplifiedChinese => "zh-Hans", + SystemLanguage.TraditionalChinese => "zh-Hant", + SystemLanguage.BrazilianPortuguese => "pt-BR", + _ => "en-US", +#pragma warning restore IDE0055 + }; + } + + private static string CleanText(string value) + { + return CleanTextRegex().Replace(value, "").Replace("\0", ""); + } + + private string GetMessageText(uint module, uint description, string key) + { + string binaryTitleContentPath = _horizon.ContentManager.GetInstalledContentPath(ErrorMessageBinaryTitleId, StorageId.BuiltInSystem, NcaContentType.Data); + + using LibHac.Fs.IStorage ncaFileStream = new LocalStorage(FileSystem.VirtualFileSystem.SwitchPathToSystemPath(binaryTitleContentPath), FileAccess.Read, FileMode.Open); + Nca nca = new(_horizon.Device.FileSystem.KeySet, ncaFileStream); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _horizon.FsIntegrityCheckLevel); + string languageCode = SystemLanguageToLanguageKey(_horizon.State.DesiredSystemLanguage); + string filePath = $"/{module}/{description:0000}/{languageCode}_{key}"; + + if (romfs.FileExists(filePath)) + { + using var binaryFile = new UniqueRef(); + + romfs.OpenFile(ref binaryFile.Ref, filePath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + StreamReader reader = new(binaryFile.Get.AsStream(), Encoding.Unicode); + + return CleanText(reader.ReadToEnd()); + } + else + { + return ""; + } + } + + private string[] GetButtonsText(uint module, uint description, string key) + { + string buttonsText = GetMessageText(module, description, key); + + return (buttonsText == "") ? null : buttonsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + } + + private void ParseErrorCommonArg() + { + ErrorCommonArg errorCommonArg = IApplet.ReadStruct(_errorStorage); + + uint module = errorCommonArg.Module; + uint description = errorCommonArg.Description; + + if (_errorCommonHeader.MessageFlag == 0) + { + (module, description) = HexToResultCode(errorCommonArg.ResultCode); + } + + string message = GetMessageText(module, description, "DlgMsg"); + + if (message == "") + { + message = "An error has occured.\n\n" + + "Please try again later.\n\n" + + "If the problem persists, please refer to the Ryujinx website.\n" + + "www.ryujinx.org"; + } + + string[] buttons = GetButtonsText(module, description, "DlgBtn"); + + bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons); + if (showDetails) + { + message = GetMessageText(module, description, "FlvMsg"); + buttons = GetButtonsText(module, description, "FlvBtn"); + + _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons); + } + } + + private void ParseApplicationErrorArg() + { + ApplicationErrorArg applicationErrorArg = IApplet.ReadStruct(_errorStorage); + + byte[] messageTextBuffer = new byte[0x800]; + byte[] detailsTextBuffer = new byte[0x800]; + + applicationErrorArg.MessageText.AsSpan().CopyTo(messageTextBuffer); + applicationErrorArg.DetailsText.AsSpan().CopyTo(detailsTextBuffer); + + string messageText = Encoding.ASCII.GetString(messageTextBuffer.TakeWhile(b => !b.Equals(0)).ToArray()); + string detailsText = Encoding.ASCII.GetString(detailsTextBuffer.TakeWhile(b => !b.Equals(0)).ToArray()); + + List buttons = new(); + + // TODO: Handle the LanguageCode to return the translated "OK" and "Details". + + if (detailsText.Trim() != "") + { + buttons.Add("Details"); + } + + buttons.Add("OK"); + + bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber}", "\n" + messageText, buttons.ToArray()); + if (showDetails) + { + buttons.RemoveAt(0); + + _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber} (Details)", "\n" + detailsText, buttons.ToArray()); + } + } + + public ResultCode GetResult() + { + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs new file mode 100644 index 00000000..73e03a1b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.Error +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ErrorCommonArg + { + public uint Module; + public uint Description; + public uint ResultCode; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs new file mode 100644 index 00000000..d4f77d6e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.Error +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ErrorCommonHeader + { + public ErrorType Type; + public byte JumpFlag; + public byte ReservedFlag1; + public byte ReservedFlag2; + public byte ReservedFlag3; + public byte ContextFlag; + public byte MessageFlag; + public byte ContextFlag2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs new file mode 100644 index 00000000..54817698 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Applets.Error +{ + enum ErrorType : byte + { + ErrorCommonArg, + SystemErrorArg, + ApplicationErrorArg, + ErrorEulaArg, + ErrorPctlArg, + ErrorRecordArg, + SystemUpdateEulaArg = 8, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/IApplet.cs b/src/Ryujinx.HLE/HOS/Applets/IApplet.cs new file mode 100644 index 00000000..985887c4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/IApplet.cs @@ -0,0 +1,28 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using Ryujinx.HLE.UI; +using Ryujinx.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets +{ + interface IApplet + { + event EventHandler AppletStateChanged; + + ResultCode Start(AppletSession normalSession, + AppletSession interactiveSession); + + ResultCode GetResult(); + + bool DrawTo(RenderingSurfaceInfo surfaceInfo, IVirtualMemoryManager destination, ulong position) + { + return false; + } + + static T ReadStruct(ReadOnlySpan data) where T : unmanaged + { + return MemoryMarshal.Cast(data)[0]; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs new file mode 100644 index 00000000..ccc761ba --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs @@ -0,0 +1,59 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Applets +{ + internal class PlayerSelectApplet : IApplet + { + private readonly Horizon _system; + + private AppletSession _normalSession; +#pragma warning disable IDE0052 // Remove unread private member + private AppletSession _interactiveSession; +#pragma warning restore IDE0052 + + public event EventHandler AppletStateChanged; + + public PlayerSelectApplet(Horizon system) + { + _system = system; + } + + public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) + { + _normalSession = normalSession; + _interactiveSession = interactiveSession; + + // TODO(jduncanator): Parse PlayerSelectConfig from input data + _normalSession.Push(BuildResponse()); + + AppletStateChanged?.Invoke(this, null); + + _system.ReturnFocus(); + + return ResultCode.Success; + } + + public ResultCode GetResult() + { + return ResultCode.Success; + } + + private byte[] BuildResponse() + { + UserProfile currentUser = _system.AccountManager.LastOpenedUser; + + using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + using BinaryWriter writer = new(stream); + + writer.Write((ulong)PlayerSelectResult.Success); + + currentUser.UserId.Write(writer); + + return stream.ToArray(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectResult.cs b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectResult.cs new file mode 100644 index 00000000..c0475b75 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectResult.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Applets +{ + enum PlayerSelectResult : ulong + { + Success = 0, + Failure = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/CJKCharacterValidation.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/CJKCharacterValidation.cs new file mode 100644 index 00000000..6134a3cd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/CJKCharacterValidation.cs @@ -0,0 +1,17 @@ +using System.Text.RegularExpressions; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + public static partial class CJKCharacterValidation + { + public static bool IsCJK(char value) + { + Regex regex = CJKRegex(); + + return regex.IsMatch(value.ToString()); + } + + [GeneratedRegex("\\p{IsHangulJamo}|\\p{IsCJKRadicalsSupplement}|\\p{IsCJKSymbolsandPunctuation}|\\p{IsEnclosedCJKLettersandMonths}|\\p{IsCJKCompatibility}|\\p{IsCJKUnifiedIdeographsExtensionA}|\\p{IsCJKUnifiedIdeographs}|\\p{IsHangulSyllables}|\\p{IsCJKCompatibilityForms}")] + private static partial Regex CJKRegex(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs new file mode 100644 index 00000000..a95af67a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the initial position of the cursor displayed in the area. + /// + enum InitialCursorPosition : uint + { + /// + /// Position the cursor at the beginning of the text + /// + Start, + + /// + /// Position the cursor at the end of the text + /// + End, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardRequest.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardRequest.cs new file mode 100644 index 00000000..f95a3627 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardRequest.cs @@ -0,0 +1,48 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Possible requests to the software keyboard when running in inline mode. + /// + enum InlineKeyboardRequest : uint + { + /// + /// Finalize the keyboard applet. + /// + Finalize = 0x4, + + /// + /// Set user words for text prediction. + /// + SetUserWordInfo = 0x6, + + /// + /// Sets the CustomizeDic data. Can't be used if CustomizedDictionaries is already set. + /// + SetCustomizeDic = 0x7, + + /// + /// Configure the keyboard applet and put it in a state where it is processing input. + /// + Calc = 0xA, + + /// + /// Set custom dictionaries for text prediction. Can't be used if SetCustomizeDic is already set. + /// + SetCustomizedDictionaries = 0xB, + + /// + /// Release custom dictionaries data. + /// + UnsetCustomizedDictionaries = 0xC, + + /// + /// [8.0.0+] Request the keyboard applet to use the ChangedStringV2 response when notifying changes in text data. + /// + UseChangedStringV2 = 0xD, + + /// + /// [8.0.0+] Request the keyboard applet to use the MovedCursorV2 response when notifying changes in cursor position. + /// + UseMovedCursorV2 = 0xE, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardResponse.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardResponse.cs new file mode 100644 index 00000000..ed4b78c8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardResponse.cs @@ -0,0 +1,93 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Possible responses from the software keyboard when running in inline mode. + /// + enum InlineKeyboardResponse : uint + { + /// + /// The software keyboard received a Calc and it is fully initialized. Reply data is ignored by the user-process. + /// + FinishedInitialize = 0x0, + + /// + /// Default response. Official sw has no handling for this besides just closing the storage. + /// + Default = 0x1, + + /// + /// The text data in the software keyboard changed (UTF-16 encoding). + /// + ChangedString = 0x2, + + /// + /// The cursor position in the software keyboard changed (UTF-16 encoding). + /// + MovedCursor = 0x3, + + /// + /// A tab in the software keyboard changed. + /// + MovedTab = 0x4, + + /// + /// The OK key was pressed in the software keyboard, confirming the input text (UTF-16 encoding). + /// + DecidedEnter = 0x5, + + /// + /// The Cancel key was pressed in the software keyboard, cancelling the input. + /// + DecidedCancel = 0x6, + + /// + /// Same as ChangedString, but with UTF-8 encoding. + /// + ChangedStringUtf8 = 0x7, + + /// + /// Same as MovedCursor, but with UTF-8 encoding. + /// + MovedCursorUtf8 = 0x8, + + /// + /// Same as DecidedEnter, but with UTF-8 encoding. + /// + DecidedEnterUtf8 = 0x9, + + /// + /// They software keyboard is releasing the data previously set by a SetCustomizeDic request. + /// + UnsetCustomizeDic = 0xA, + + /// + /// They software keyboard is releasing the data previously set by a SetUserWordInfo request. + /// + ReleasedUserWordInfo = 0xB, + + /// + /// They software keyboard is releasing the data previously set by a SetCustomizedDictionaries request. + /// + UnsetCustomizedDictionaries = 0xC, + + /// + /// Same as ChangedString, but with additional fields. + /// + ChangedStringV2 = 0xD, + + /// + /// Same as MovedCursor, but with additional fields. + /// + MovedCursorV2 = 0xE, + + /// + /// Same as ChangedStringUtf8, but with additional fields. + /// + ChangedStringUtf8V2 = 0xF, + + /// + /// Same as MovedCursorUtf8, but with additional fields. + /// + MovedCursorUtf8V2 = 0x10, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardState.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardState.cs new file mode 100644 index 00000000..91d0164e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardState.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Possible states for the software keyboard when running in inline mode. + /// + enum InlineKeyboardState : uint + { + /// + /// The software keyboard has just been created or finalized and is uninitialized. + /// + Uninitialized = 0x0, + + /// + /// The software keyboard is initialized, but it is not visible and not processing input. + /// + Initialized = 0x1, + + /// + /// The software keyboard is transitioning to a visible state. + /// + Appearing = 0x2, + + /// + /// The software keyboard is visible and receiving processing input. + /// + Shown = 0x3, + + /// + /// software keyboard is transitioning to a hidden state because the user pressed either OK or Cancel. + /// + Disappearing = 0x4, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineResponses.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineResponses.cs new file mode 100644 index 00000000..46e541a9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineResponses.cs @@ -0,0 +1,281 @@ +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + internal class InlineResponses + { + private const uint MaxStrLenUTF8 = 0x7D4; + private const uint MaxStrLenUTF16 = 0x3EC; + + private static void BeginResponse(InlineKeyboardState state, InlineKeyboardResponse resCode, BinaryWriter writer) + { + writer.Write((uint)state); + writer.Write((uint)resCode); + } + + private static uint WriteString(string text, BinaryWriter writer, uint maxSize, Encoding encoding) + { + // Ensure the text fits in the buffer, but do not straight cut the bytes because + // this may corrupt the encoding. Search for a cut in the source string that fits. + + byte[] bytes = null; + + for (int maxStr = text.Length; maxStr >= 0; maxStr--) + { + // This loop will probably will run only once. + bytes = encoding.GetBytes(text, 0, maxStr); + if (bytes.Length <= maxSize) + { + break; + } + } + + writer.Write(bytes); + writer.Seek((int)maxSize - bytes.Length, SeekOrigin.Current); + writer.Write((uint)text.Length); // String size + + return (uint)text.Length; // Return the cursor position at the end of the text + } + + private static void WriteStringWithCursor(string text, uint cursor, BinaryWriter writer, uint maxSize, Encoding encoding, bool padMiddle) + { + uint length = WriteString(text, writer, maxSize, encoding); + + if (cursor > length) + { + cursor = length; + } + + if (padMiddle) + { + writer.Write((int)-1); // ? + writer.Write((int)-1); // ? + } + + writer.Write(cursor); // Cursor position + } + + public static byte[] FinishedInitialize(InlineKeyboardState state) + { + uint resSize = 2 * sizeof(uint) + 0x1; + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.FinishedInitialize, writer); + writer.Write((byte)1); // Data (ignored by the program) + + return stream.ToArray(); + } + + public static byte[] Default(InlineKeyboardState state) + { + uint resSize = 2 * sizeof(uint); + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.Default, writer); + + return stream.ToArray(); + } + + public static byte[] ChangedString(string text, uint cursor, InlineKeyboardState state) + { + uint resSize = 6 * sizeof(uint) + MaxStrLenUTF16; + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.ChangedString, writer); + WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF16, Encoding.Unicode, true); + + return stream.ToArray(); + } + + public static byte[] MovedCursor(string text, uint cursor, InlineKeyboardState state) + { + uint resSize = 4 * sizeof(uint) + MaxStrLenUTF16; + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.MovedCursor, writer); + WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF16, Encoding.Unicode, false); + + return stream.ToArray(); + } + + public static byte[] MovedTab(string text, uint cursor, InlineKeyboardState state) + { + // Should be the same as MovedCursor. + + uint resSize = 4 * sizeof(uint) + MaxStrLenUTF16; + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.MovedTab, writer); + WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF16, Encoding.Unicode, false); + + return stream.ToArray(); + } + + public static byte[] DecidedEnter(string text, InlineKeyboardState state) + { + uint resSize = 3 * sizeof(uint) + MaxStrLenUTF16; + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.DecidedEnter, writer); + WriteString(text, writer, MaxStrLenUTF16, Encoding.Unicode); + + return stream.ToArray(); + } + + public static byte[] DecidedCancel(InlineKeyboardState state) + { + uint resSize = 2 * sizeof(uint); + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.DecidedCancel, writer); + + return stream.ToArray(); + } + + public static byte[] ChangedStringUtf8(string text, uint cursor, InlineKeyboardState state) + { + uint resSize = 6 * sizeof(uint) + MaxStrLenUTF8; + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.ChangedStringUtf8, writer); + WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF8, Encoding.UTF8, true); + + return stream.ToArray(); + } + + public static byte[] MovedCursorUtf8(string text, uint cursor, InlineKeyboardState state) + { + uint resSize = 4 * sizeof(uint) + MaxStrLenUTF8; + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.MovedCursorUtf8, writer); + WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF8, Encoding.UTF8, false); + + return stream.ToArray(); + } + + public static byte[] DecidedEnterUtf8(string text, InlineKeyboardState state) + { + uint resSize = 3 * sizeof(uint) + MaxStrLenUTF8; + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.DecidedEnterUtf8, writer); + WriteString(text, writer, MaxStrLenUTF8, Encoding.UTF8); + + return stream.ToArray(); + } + + public static byte[] UnsetCustomizeDic(InlineKeyboardState state) + { + uint resSize = 2 * sizeof(uint); + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.UnsetCustomizeDic, writer); + + return stream.ToArray(); + } + + public static byte[] ReleasedUserWordInfo(InlineKeyboardState state) + { + uint resSize = 2 * sizeof(uint); + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.ReleasedUserWordInfo, writer); + + return stream.ToArray(); + } + + public static byte[] UnsetCustomizedDictionaries(InlineKeyboardState state) + { + uint resSize = 2 * sizeof(uint); + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.UnsetCustomizedDictionaries, writer); + + return stream.ToArray(); + } + + public static byte[] ChangedStringV2(string text, uint cursor, InlineKeyboardState state) + { + uint resSize = 6 * sizeof(uint) + MaxStrLenUTF16 + 0x1; + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.ChangedStringV2, writer); + WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF16, Encoding.Unicode, true); + writer.Write((byte)0); // Flag == 0 + + return stream.ToArray(); + } + + public static byte[] MovedCursorV2(string text, uint cursor, InlineKeyboardState state) + { + uint resSize = 4 * sizeof(uint) + MaxStrLenUTF16 + 0x1; + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.MovedCursorV2, writer); + WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF16, Encoding.Unicode, false); + writer.Write((byte)0); // Flag == 0 + + return stream.ToArray(); + } + + public static byte[] ChangedStringUtf8V2(string text, uint cursor, InlineKeyboardState state) + { + uint resSize = 6 * sizeof(uint) + MaxStrLenUTF8 + 0x1; + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.ChangedStringUtf8V2, writer); + WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF8, Encoding.UTF8, true); + writer.Write((byte)0); // Flag == 0 + + return stream.ToArray(); + } + + public static byte[] MovedCursorUtf8V2(string text, uint cursor, InlineKeyboardState state) + { + uint resSize = 4 * sizeof(uint) + MaxStrLenUTF8 + 0x1; + + using MemoryStream stream = new(new byte[resSize]); + using BinaryWriter writer = new(stream); + + BeginResponse(state, InlineKeyboardResponse.MovedCursorUtf8V2, writer); + WriteStringWithCursor(text, cursor, writer, MaxStrLenUTF8, Encoding.UTF8, false); + writer.Write((byte)0); // Flag == 0 + + return stream.ToArray(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs new file mode 100644 index 00000000..780c82b5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the text entry mode. + /// + enum InputFormMode : uint + { + /// + /// Displays the text entry area as a single-line field. + /// + SingleLine, + + /// + /// Displays the text entry area as a multi-line field. + /// + MultiLine, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidButtonFlags.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidButtonFlags.cs new file mode 100644 index 00000000..8d81d4a1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidButtonFlags.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies prohibited buttons. + /// + [Flags] + enum InvalidButtonFlags : uint + { + None = 0, + AnalogStickL = 1 << 1, + AnalogStickR = 1 << 2, + ZL = 1 << 3, + ZR = 1 << 4, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs new file mode 100644 index 00000000..aa703f5d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs @@ -0,0 +1,56 @@ +using System; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies prohibited character sets. + /// + [Flags] + enum InvalidCharFlags : uint + { + /// + /// No characters are prohibited. + /// + None = 0 << 1, + + /// + /// Prohibits spaces. + /// + Space = 1 << 1, + + /// + /// Prohibits the at (@) symbol. + /// + AtSymbol = 1 << 2, + + /// + /// Prohibits the percent (%) symbol. + /// + Percent = 1 << 3, + + /// + /// Prohibits the forward slash (/) symbol. + /// + ForwardSlash = 1 << 4, + + /// + /// Prohibits the backward slash (\) symbol. + /// + BackSlash = 1 << 5, + + /// + /// Prohibits numbers. + /// + Numbers = 1 << 6, + + /// + /// Prohibits characters outside of those allowed in download codes. + /// + DownloadCode = 1 << 7, + + /// + /// Prohibits characters outside of those allowed in Mii Nicknames. + /// + Username = 1 << 8, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardCalcFlags.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardCalcFlags.cs new file mode 100644 index 00000000..6f7f6d2a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardCalcFlags.cs @@ -0,0 +1,26 @@ +using System; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Bitmask of commands encoded in the Flags field of the Calc structs. + /// + [Flags] + enum KeyboardCalcFlags : ulong + { + Initialize = 0x1, + SetVolume = 0x2, + Appear = 0x4, + SetInputText = 0x8, + SetCursorPos = 0x10, + SetUtf8Mode = 0x20, + SetKeyboardBackground = 0x100, + SetKeyboardOptions1 = 0x200, + SetKeyboardOptions2 = 0x800, + EnableSeGroup = 0x2000, + DisableSeGroup = 0x4000, + SetBackspaceEnabled = 0x8000, + AppearTrigger = 0x10000, + MustShow = Appear | SetInputText | AppearTrigger, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardInputMode.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardInputMode.cs new file mode 100644 index 00000000..a75ef8cc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardInputMode.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Active input options set by the keyboard applet. These options allow keyboard + /// players to input text without conflicting with the controller mappings. + /// + enum KeyboardInputMode : uint + { + ControllerAndKeyboard, + KeyboardOnly, + ControllerOnly, + Count, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMiniaturizationMode.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMiniaturizationMode.cs new file mode 100644 index 00000000..3c96a61a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMiniaturizationMode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// The miniaturization mode used by the keyboard in inline mode. + /// + enum KeyboardMiniaturizationMode : byte + { + None = 0, + Auto = 1, + Forced = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs new file mode 100644 index 00000000..aa79dd42 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs @@ -0,0 +1,39 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the variant of keyboard displayed on screen. + /// + public enum KeyboardMode : uint + { + /// + /// All UTF-16 characters allowed. + /// + Default = 0, + + /// + /// Only 0-9 or '.' allowed. + /// + Numeric = 1, + + /// + /// Only ASCII characters allowed. + /// + ASCII = 2, + + /// + /// Synonymous with default. + /// + FullLatin = 3, + + /// + /// All UTF-16 characters except CJK characters allowed. + /// + Alphabet = 4, + + SimplifiedChinese = 5, + TraditionalChinese = 6, + Korean = 7, + LanguageSet2 = 8, + LanguageSet2Latin = 9, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardResult.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardResult.cs new file mode 100644 index 00000000..7c6d5c70 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardResult.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// The intention of the user when they finish the interaction with the keyboard. + /// + enum KeyboardResult + { + NotSet = 0, + Accept = 1, + Cancel = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/NumericCharacterValidation.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/NumericCharacterValidation.cs new file mode 100644 index 00000000..d72b68ea --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/NumericCharacterValidation.cs @@ -0,0 +1,17 @@ +using System.Text.RegularExpressions; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + public static partial class NumericCharacterValidation + { + public static bool IsNumeric(char value) + { + Regex regex = NumericRegex(); + + return regex.IsMatch(value.ToString()); + } + + [GeneratedRegex("[0-9]|.")] + private static partial Regex NumericRegex(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs new file mode 100644 index 00000000..040eee3e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the display mode of text in a password field. + /// + enum PasswordMode : uint + { + /// + /// Display input characters. + /// + Disabled, + + /// + /// Hide input characters. + /// + Enabled, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnA.png b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnA.png new file mode 100644 index 0000000000000000000000000000000000000000..a8ee784dd95fa8804d04c55961ee6719b06c3f4f GIT binary patch literal 1074 zcmV-21kL-2P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H11HVZ` zK~z|UwU^&Z$vVT<9xqq&((0|Ip=xabI$vo z_iL~TNwYt*LA_eXISY~ z5Qm3{gu`Ksu{LmGVggBarUJYJeDZ<;N%w#c?XECAJ2`!o6pA=ElYb(P$K73=Itpbar+E zu(Gnk{{Frb6Yop%y0FS?sohzVlam14y?YnG-%n#>BOZ^3bUIBa6mnwVCE#5bR&BS1 zL?XfB;vxV&Jv{)p-EKNMIsgcTLZnhDCnoN@uzB+V=H})|BoesYZo0a<&c}OudjTsu zmY0{E7^rsvZ`fsLw!gW#8K2L0K3-c}OJ!vx;P^7$aRImNFr(2Zkw^r19`QwF#@5%@ z+1c5#Yry9!%&`y(g^+~T>!qowDW`#=q9P%Uzu%IJI6gjRVPPTX zQDxz=8>@|KzKoT*d=(WH3=a?I<)zbU1_uW@Iyxd44B9d9yBn*A`yd}97K;%MhXE`1 z%gf6zU9r2no3XJmB9RDNTU%6DS6@b-b^*Tt@qCP_sj2hgaxwSwp})WX{C9!N7zf5( z#+Vb}=e!or9-g|oIw~qE3L2=Ys-mXG>P2T~XNkx2#`cpj<|LDbUjomv8JnA%Y;SJ^ z&r7cz^!4=tR_UIZnaM>-0$;${b8L+H6ZkfpwY0Q^$K%20^U>bkZreatR~KbvWq3Ru z=I7^gQNA(8Jbq#1tR;Ksq__Hycp&LUPUn)!B|ZHg#djpVcIoy>s+ZM5UpJmgdehb! zO1dp+?i$4(=&T2{4M{g74M{q=g7DvxK3Bdow`rtY(svGxoJjg!(w#! + + + + + + + image/svg+xml + + + + + + + + A + + diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.png b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.png new file mode 100644 index 0000000000000000000000000000000000000000..e1fa3454abf242b32b293cd6dcedde5b94464c4e GIT binary patch literal 992 zcmV<610Vc}P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H118qq} zK~z|UwU<9kYjqUIKc{KaqR-Y4OrSF83x7fiD7Y}-@ZKmaFo0or3kx`~fYAjFi7{e~ zn2<zXFwziW!XsFSF&+HV(mo9RI$Xd~Xzy+4`;NcgIp6R1-h0mJ zso@cl!ayJJ5O@W633&eE`cL2x*a5x=;%4@bS8g}q7D-=8%E&u1lD?GGd>7#wNuNmi zw*ui}oJsmvQm9h#W=X%?LAV%Ok{Y~sL#&M$Y!$~ zA0M-~w}&KZYHFC9o1?L@v6P84&}?SEUkbicO1v0DLqpWo*4}!zySvNu^fcLQmZha7 zrlzJ!(cA;x1K#qQ@bIvgv`#~_4Rd@mzRmfV)XR%l%xC0 z2R!yF?uJk(#Kgn|;jr^nx7`e6 zGNr$knbFqP#^B(fTl;-#W(RO{CHn!mk(o-RTq`;p4ik+=Zw#qhumwCYvnMxhB-Kkg zdWuWwv7{Hv+LP3IvAtFsN0Le|ip3^gkhES!@l6%2<6cYzCCx}m-$D3^qz@&zPNZ^1 z>Lq<%p^>ztFC^V}<2Sc}0wecu_Qwant2m2H;otMez&1`Nt(sZd3-5mcp(!A_>O#r@ O0000Bk literal 0 HcmV?d00001 diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.svg b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.svg new file mode 100644 index 00000000..ea6bb9bd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.svg @@ -0,0 +1,93 @@ + + + + + + + + image/svg+xml + + + + + + + + B + B + + diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_KeyF6.png b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_KeyF6.png new file mode 100644 index 0000000000000000000000000000000000000000..d6dbdc1a3c36dc1d2ffd8609ead0aae8f2fefd54 GIT binary patch literal 842 zcmV-Q1GW5#P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10@q1I zK~z|U?bbU;BS9R;@$a5%P!UA5h$ez!^jH-SQ^9ob zaEi8864XL$mIOtV9Eb!g77lzBfeqo3;)wV_$Q4KS6nsUESorJaKg-OAnPFyyv9Ym} z`uh4`ot>ROMNv%hO1Zka5>{7NKkx7Fe-w_7j}=u_znq<&(c0QdQBhIG0kK$&&CN}6 zb8~5LZ~rbNlSywfnH=}~{R9F5N=iyHj?i@-hr@x>=|m8OZz6yo2uPBIrfCch4`&>q zC<>A!AqWBhA2L4UBl0E?;$0ox-Q64=9o?uE6ci8$1a8!HU1xrNp1r+2bX}*Xr-#we z(U(DJnuf(- zk&zL0c6LZ45;Qb4FgZDi&1QQ!L?{$udV1Pux3shvh-frQBod*pun-FMv zI?*(Zyu7@ZL3DL>F*`eZYoEzvqPDh{iHQlLW;UC#+wBB{K^zXpqY+O&6J=#(gu~$* zwc_Gp04^>r(i-c@i00;I!r?Hgsv0$)&qscKK8=lyX^nO7cH9@2%Z10|p`)V%S(Zs8 z5^Qa4v9PdUtjMzw>ZKHJ0g{LL@MuyRNe%_xVBS2DT< + + + + + + + image/svg+xml + + + + + + + + F6 + + + + + + + diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Logo_Ryujinx.png b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Logo_Ryujinx.png new file mode 100644 index 0000000000000000000000000000000000000000..0e8da15e6ee20873f2ad7924f77d2952c20c4b14 GIT binary patch literal 52972 zcmX`SbwE_z_dR?k=Wf2pI_h03gxUQa1tsF!oz8H$E=*#YH210s8`V zR?$@ffSP2YYdak5&+PVEM!EnH%nJbUC;<3}{S|y00Q|)OVAmD^Q&MWtup#t^~ zxX*Mn)v>SGFO*s<3;@O;+UhFCfeU}ygX)FmO_or<_&6mFE~A61aHkct1m7A9ewpa4 zC#Oss<;RWdVrP`0AKCRlyGCw*28qf%Bd-PQf4^AUm~Q_z`dqYiQD_avgJba33|v_? zQ8iMhEX_FbdEiN&Pi);)vM;BkgvWaOy9=ky)^{zPPoKOCvT<_gjq@G5*kAd>^;b>5 zIx1zV=PfJY`jH%tge}e-Fb3oxo?icY9@+z9i-%0TGAHfMI<5AO4XXo~% zO+*vqMIqC#W*om~7NUw!gSf98;IdNuzzL}c(as=_DvRZREGgU{aH1xF8LrGgYUU4Q z!iHCYsbc!!h%&XnBb7FAdTl;8M#eh`UZNZtfe8xc5Ym z5M=6d!an&N=XIvD#B?Df9yW=Szsh*W@U(YN>e%MoP;90o>q~3tX^Ea<;SRNykxJgj+B-Qxn*d?lqfc)h3&9gn>A zxo>hJe2-*51{(aVu<}TL(PBXfNH3Bx^zeM>6f+$|IT7wK9I`MnGCP0KSBq6^_s8uq z&%_v|VLn8|p>gE)af11{%94=fH%?jSZxa6=;{#@#=uZwPI+0Mk(7POZH)VT*Zgf-^ zpehKO%S(VT;ymrmTF;brTu?may2JiEH6Hb`6QK&UylPCF26smMMdUPYzyIZ^Voxd8L81A9|Y*b>-aMuU$ zGxPyMsg849jx|3}Su%63$UE+Q4(Kr~8Ft*}SxblARh}5;Bs|g54H2ycTzWNABMK&y z6$?Neij)vckW8ST`@RG7iKD>oVb8fVJc*w_|;Y1fQFDE}m`uTdf ziq=QCcrFO?Nyo*(c^dagG&x{~`)@wI`pd&wf@x88x_E8+4X((~BlOFSGHu)=wkij4 zgXo!D=qz>*t+9N9s-p!S!u+EOUQImB9&2@x11_qh?Cf!N)(ctB7A)r2eJt-y*oME_ z;KdxiRylTNtJy*9p)rmhpm1`DLwzdCu`SM>05~2TBTWeZ*5Tkg<~NC33HlZF7HA|a zOuD8M$3yI|22fe97Wt}8FrLqbscMm*fayuehn!G&$61WWqGT1_Ls@_EoL*Dm-&E*Rv%Y#6xaWRpPo2Lm+<|W>mUR zk2wCgfAb$(mr1Rb93XN4ckoVQ8lQVi1&ZdnB1pWZdjiNJH4rkC68{Q#n$Z|iFTTww zrMTZS{AV*oj2}mL9gl4g%s}7SDw^^~s+9GiY>g%M+A@6e2gh0TloaJb3}Ng1W#^E56oiJX1!G#6`A$PDMaQql{&>K(x+ zQoV|7X^S1KhO@vb^v4>U|KP8SQt@JfC;(UP%Q{3dXqg4m!GQT(ET@|EGl#g2(ksYu zLFVV9=Ls1Q_7*(t3A?7SUPJk9f@&}L6Pg5l^rLsz`riQlitoCr4D$ut8T*SLJ5Q_^ zsh{=2Bpk!!2qqMy{MS2U;=#$7-*A1sjP6rDNw%$I#Pl8zD3@+h2FR}Ul_-N%M6XIp`3_ZQ9t#3vmi;1Z%mN&Axw_7F4hBLWL9?rDCkF}`QaXvH3iF#; z=1t*EKqp9{bDg64t36%hay++k%sy^&!S$W#qj+X`i)kn)I!I{JFvjdKuao0s- zq~^|OfhKSPyZf@6dA_$B2V5QtS9ySShv#scSUxKb=uvormLO`vfciV~KqS*EPtqX>&JAE>PNKe7xo%Hi0yQqQ2}2-r8UX(A1M0)AD3)vDVaaa^Z{Ei< z0!~HKN!B~5PN@*FCRTvXN69&efET0eS>}9YR%}^zzS>O~4Od97Fb_YR%KU zQrsf2@oVYhQH0e9GvJg7F_>Ku;zM|)31RkFPCD~h;W==O!+PgWW@kA7?}=0<#5Nyb z!XeT@^Koq2WLFt~Sxpgy@RJi`>DfSb2!Nh#CW?nw_=#S$Pef=eQki%z4`zVrdVreJEo&aCV z;NvD{_)dWGFoD>})UnbNameCcqyyyQhUcW5d5WEW6TFG9b_yq9DaS_7Y5U|w)LQuJdJ0B65hV!$RpYj?1UC4) z^rUn+t-}!OcK?g2v`WZer1PJCIxTPqaRGwS{W@4QP}&;ur#<1zylzmv)F4GDtZ{o{ zTsRS8+QUi;PSrKz1O2Q%w8g{~+G(ePhqOrNk93F%1+{62!5F9-ZOAfqdw;}(t_Y0$ zut$BYIOaKB0F^>#C(d;;Vss{z1TWFXc^x+AQp*fbfpq4N$ZlqzEatsVMCr7fNE0X! zU&|mA=Ojec6w}^^+l77lEG~qHP=~MZ-+wiqrXmqp>}6mhd4W+`x4&JyJ2mrV z)sH_9hRIK=q&K~B5IS%+$QYGnTPDg=)}8oQMew$H<86pe%i~l33NErclXTEjhd?gg z67Fvme8}xxRVi2nJ#pOY+(rW%Zr>Wsu(XF0i%PD@6zo?Nbgcp~EQjP>X9WwNJY=D+ z+5q>A{&}()wu%(h>&Bta24Nh}(05}L%79C09eIbabKWB zK%!f2sV7Fvz(mE4F^mnGv`0s>%|LMAKzZtQ6=)pKI-vzklFl?ti_0$)_1}9R9WD_+ zMF;bXi-u#-z7jd00P^7M#oR+&P}O2aUr91g)7Q5H58eyY3gIc!-ofGxA+;arjoAQ> z=@L-0Pi`EEsQ{6*8xn-b;rDHjSi-1R?xfWjFz5c{2R%_1q%a}tmU9bV1Ib^!(L!T` zM=Vpw82}aiJyjqjEv2~O;HH&kuHOovyKBGx%Ue}IBS4G$ z>W#}yCqeUQ1*`|UsVP7Z+;HE7A^$%!F0X2*+_rI&OgC7SIM*1V(%!rdv z<?W)tzZUh+(1vTnJ2B=$xXq;y+P^ROlK1PqrR%AgY8i^-rS|?k&o$}Fi(rS&}%kb%Miw$Zp%Brx!f5p3d=vQ-l0L)eFd zZFHfJQm+}YEwnf8OP*uVgQ*!%2i`zM4g>Eth(EoviI+iIM(8;Uz~Bv*q^SPH zg26fvuJF-kX#aj+;&?bGpRF(<5NS7cQuqWN^9YuZ11aqJEQsY?YDkn()G%qlx8V$| z>%lg2CF4P*H<|N9@SLw|jJ&ZPgAlR&-!_!THrLE>u15}7HJ*M+wew_h;P8us5=#Gv zjW^Z;NPtm9tQ==95pT&ARc!bL9x3v5vL_X@;kTDlhg*C!X%1+dU*KsduF)ykf~ydKR~CQs%Xe zR|ddgr}p`U*`0(eG@*S7X{H@n6hjzB;>T-pb=NV(Tkn|xE39ese)Ay`9Y*o<<6kOe zT2lJgL=98$rzp+&SR^=H!PCp4nkbvW^M{366>gS7Y8m(q|i-=neqbz68SEw*8EJ&r8rE#`nr7m zKoE@Rt9r5U$1QtxM7nI!`7Z^%+!~6TYaQU`OZyxBn#w8Y{?n6R64vD-wFHhcx*tCt zZ)8xS^)ffGsuR#&Xv72a;@5ix$ZE_%03>ZrG|6M;i4D}4y0SlQ^5dYpc((aCa7``J znn6e07a_S9^7av4TJ`ExW*501@F&s?pQ7tBH&V-pU&y=*?3B|M$I=Sc1<4CrXX|*6 z)L!+=rF@R2V_3MDH+IiXowTQajt~kaMTEL`?xjagT)mcJbQ!X(SG{oky6yL&^Hb~J zWuhai@Pp^jZ$Ut-dyS6R|69ekL@c@lm^8ywx|O>j4Dt88l9VSe!(dH=-o1L52G{47dWQy0chV|#Z>9FkBBgDMPaAxuXF_B9tjusq{?PCjJP2Y4^mhw1I}+4#6lxvXXpV1A84A*QBU2jF{Z%@X)K`Jw^>Vhmr)DEuxGE+CmkQynE`8uYw2eQV-wws?rydD=ls_ zG!rY3DA2I!6DdI4>0W&5l0>cs%9lP^vKb%y5!SgO29A4pNWy%wV?C zlPV%_9(1zl$=JF_m5C=cvAO}cd_DWvAm{dR-`f45y8$`0?8P|(w zGDOn2^*l*8*78=_1Dfsq-`VL*oRN!m8)^rc9^j#Ql^=IohRQQOD|sGkO;_4V7F6ZG z6df@>-F)BZRdFZ5Z_+lwV4#uUFGk7iQYXWCb*q3(;SoMdDD9;IHR)Hby#o6u=(qP3 zo{zA7`ICa(HD0heAF4lA1=~K>|YxwePL8qwFa zX&9+$umbR#O%b9G6eKNc0U_yt6^FroqB*2J^^LGQ#SDt z+a-&oiYoDno>j7wPCDi3$PainCsB`j>}>5FUsAck*XL zBNw+-2X8QtkHZ@G3>l8hV~62iiyT&v&REJiy^Ky$L8oIq903;gD;4-Db_+wl-U=}j zfsFhAb#DUPDvPm;=W1t0?RW?=777E_a0ahCADGd@1eut-NDgEOe3|m{g7G;S^Fo4V zGK$Rur7y8*BO_2nMF{$8ULf`;$mfnwPSAzBe$ewYRlHLlVAFJt+8^lkxWH$ursryO z+dQL!B&9?JvDw9~v?H=0nB3~8$cCrwZuWiG{amo*3DYip{!JS`z;qRdiTKAk5UcAR zGp^c>s2b@k|5O06Z3=;=SL;FPon)@dlB1(G-U)O`o~T9y5Ve^h^%z#&l-$j_k4qlJ zqaMehvV<{4sxM$hEMXNbE;j{@SDx8j+%h}Dt#r@N4d=&kPuy?RT9)dL=J6M*JW3SY zn)k|jrX%E4(v=%`p<#;KTPn-KcBs&Ur8nwm&UWbXzhnYy2r7g$7Bze+j*$nYJUfE* z{AmL+A!6?FrGp^bzrMg~OZiT_ng>kfQtn6jsk7{RnL_i-CkmnK2c0+yyoY4m#=N<) z8#cYyVJ%EQ&C$#~$D;ZJ<-7o~qy+Bpy<~=HJ*7-Yb(F*7ksJSpewOF{f7HYXI-^Q)^`gNfkb0(*0`BWQw~8_Y=jqY<4vYmsI9ogq7jmJ<(Q zL5kRJ>d1xI%D!8IN31>)7fQhomOP|G=p@^tww|h=5pfl=!<4GWFwL2;AS3H8L4dJx z94?W;=qT0(93UdBHwWnN9R(_03oeGvZ^K+k22x4}6B!tVdRXQvlG zAgAB7fHun}je|NN*AQD8ZbR)x4Y=<7Uw@H5Ef{pL*PGHH4uf{bsV^&8ZtJ!*l3z5E=?51D`YVayd74* zU&9+4-3^g-tb`%y|k8_4`58kj)(Yr%0Kh$=#MTSbbga0k`KM) zRi#<7p_+q;{Pcgb)*N-3axpVWT&LPV3b%{uD#zzMx%VhU4v6mA+?F@1v8(y|K^=L9 z7>9VW{~4|uKQ3?>VWR?~_n=Kx&P>mByQZ>vqQILne2+GNM~Z*^Re9xC5V!8we2`TA z5XliyKo#hF`Rnu<`sTv!YXkw%Qz6V!t|*>}Blwka3gl49^pMO*F1jeL_o?%I9_Tk- zn^j7u4n7$(Y=ordI>8_Jc0K0+wg4)TjSd%QZe4<9zJEZaOV3;PV!9eftgR`$?j4fh zgmXvZeILta9X&oseuRzg2}jiR;ZZ`~50a0r#VQoG=9O0k@Fm=T zj}c^U0@^U@MN8ph$=vI!gGaY{hdQVK0+AU1B&MTK-}(g+GasB7!EiJ!J_qQB(onR> zew-!wT}wH6lnyM(@C2ZD!n4^a;RTlo862Ovo&xwO-yA%ubnwc6ry2+j-+*_QR@Kn2 z#hHPES}F(78#cJi9)}!gEd^F{rA%Qh1+vX9u5Dn3N8)<(T>Rot`EnH+?p){L_%>(p z%x<;$A()oe!s8R79igvd&M6BTR{FV)&-!3rcL=vfND&xwu}ysNxT|?GUqT6}imr zm(xBzr)qs4EdrAkXwMo7Aq^5$TgtldW8EzH1Eg_ZdXS3LwcdXpUZ()4F~lL%5Uuzv zAkFIQwYh1PW6j=&ASNe|9rIA%s0V~jg?tn-ymHq&8gsw;zNEF{=8Le)o?U*5II(Jz z}z!9V!*m);HdPSw2>Rt*lM#DDQSJ z@1IMm*BMSe#mym!M;`?v=~TjvKinGVr}d~bNE z%sx(;A|gWJ#cC8Y5lyY`!%fw@(flZ-my2N~Os(+xKGRm1Ovm`z6{$9_R@$1xYwj51 z$gfjctM2KDU0$UNf2Cff@Uz*eC!+>5KZ_e#es;0KnTgUF&I3q69dxG$#Cx)@Ehpl` z=^vnNb3hDEF+P?*)Difsz%=^-PRvEkP8(TY#IY*>`q8}=vNAhVVSiR59c}1e?~RMz zixncSj^rFF?VW&zxo_t78RCJwhlDTm(*na+H9S>)E)SH>@w6(DloOQ#@c4H!?xZsG zoQw$3NHobnf$>+Eh5<==oVwbZm8F^0|0Uq8+N=5)T|YY-Q8{qTy|`I6df2%af8qUbCrp2k<`c*DrB3JDg0Rc86m^#D?0rK9qD?CC z#3{ou5QcCiDbf2f2ho%Wd4?*vI8=4_y}VUG!C*d!1$-ooKK1j;(`D`*75U!U1fU!)E( z!tJJIu|nrG=xU0Gq$g=Qpx~KCNY;huFLstkJEwwBQ)i@8Q3auW0mtW}7+f#8K!dF5 zJNX+_AY&-}y_PibJ2Fa@4w^t6W}mmsovhDMIe^#*qbZ)Pwz+$OpCKFcm5x?gZb8tF z%<-_zdGMid?^QNc;jj=GdbGmex7hB2QHE1N5!dL`{Rt4}-pp z=}hKCadQ)IE=5EZgkFxe-AsEz<-NUKoT9BT^U&Ir2@v0Q`zv%5K9djhSO#R<%(^@M z4ls8>FN{02->d2(;1=oPkyAQ3Y&sqj4S`(Ju7|(#1zWzMLqx$>;O~raOejx7jLwv} z%OVMbds+UM3UX}tgC2q7RP2uWKp=_L%8RN?(j|16Oi|^%ZGx2I7nMFqi3E;4D&LJM zOm9FDq6d82NlP!Vy5)pZ4zWc?bnq$DSjMMgmp$KfI2L1rx1bW@5B77-8TcNvGSR^) z8)dBe0#1AhLXyU{U_Mp~+`WyQD)))>CIK*wB=9;+7sb2`N1NEhJe2Xm9Irz&tZp{{ z$s|hV8rhm4iK_|Q2<*ag2dANMJJV`;AbKGeuzaPXsC08Ib5t2)L$+Z3IT!w%1^(<= zKX#odAJ(7Qv}hyi5xRxT&~FmYx5Tl6*WlA24>)}@RsbQx<{lqn*Z4Ag<*g^8)nfv0 zIF+hU%`Yv^p@%<=4`L09q}^ghwy6VLj;BYb6oCjWesOhWg5Ma zSce*miZro4qxar4mPbSCh7h6H|tm&V>fv7c08DOBu-R8^rwN^E=3QaoM8 zyT!P44~c@fU_Ihd>j@EAAN`a)K_1&DZLDPqB+;&2y8u0@W9Rcegu49o>2Swu*!AHo z=Yr}nvnAvGW?4O-uM{BM9>Ww+6&|^xOdz;k&b|6J9id4wMEwS{;$?Cmy8}4)I#l7B zHv9;S)LERWQk|=ue9^JyQYswmg9kTS$ooiPWgTi7S=|95^4tFHx?#=&^sI2gyK(_< z0qwMt$D$!r2f^D7$a}Fi**H9X3z%o2vg}=iV1}goqw*@9c&8i_<)xK6hs3hC*UR-= zV6Xqc;(%EKJ**_nJzT3ov4*elRB43aDFn{Ow!2oIeKNJaM>QC2|4ak<0>O$joZ$`x z<=$orJ`EB}?28JEjCrM(>mL%+Tc0u2-Fe&u=II*s5hiS;+(MSCdE? zP%@am=D{*lGV=D)4IKP@c(a7El$O?(vQY=x0&|}&_|R_qj|!#Yru;>gHO(+?k9P`a znXG>8%2?%JRHb(ExI?=%Im*r(vv~sGf(0x z);%%G619QmaG*SK)_^AcUEH2Wwpa|=hk)v1lM<9EDM1vkt;X?|E~>OoNP}w3;A3YS zw}=*Fxi(Ygd$`S=NJV3y{q+U0WTTfDQNNf^@=o!04U0ZGMvHr)>tUVUk;zJL#w9{I zk*^)l{Tg|=67)$4Rk+d-v=bSJ6~%^*Vk5mnw_zStU^<#4=)eEUPwu#yZP_|DsjR5@ z;gh77mevY=+d`aoTe3KVW&8HS6km25Un@i;ubcV}>AdhMkm z4E@(;B`JY!`;+bf4O?q!8Ioc_`{5mJqS-F&0|U3U{fys?cPTybV!~LJ6fO*r8X>-= zH*|QpZejPh+$`6SD2_)5$mFyO7@n1IwmLw9(gPL zIWe9?eR_+R&r@vnD!k;+>ptJ|p!&AZg;yDHWrY16TSwhNZu=+kuWUDXjbwi#h> zb1zGs7@DW9->rR$6hXIO(fLg=2%|BV#471Vt|vBVh&W968M>MLWcl&^{)M zx;a|;$u1}}*M@ZhExmd^#BI!RS2WTzW1P9tx32S6-V(1bo}e{GVE&?8TnAxz0rzhl zVo`f8;+PN8{=c=9HB|KwgJ;lNnHYh>5xm_3$c(Sx|5Vs^eo(2lnfCWg-L7w%)#X44 zTA<0gLtVx-?ZL1O=UMW!ofNvtn8859g^z)~Vm&XNAE>~loVJ;4j(W~uI~%)>J0s34 z0FE%>aH)2d_EWT;s%~vVd#+OO^1YU|9X{2O0uSs!&?g{O9z>ampKqXlD;Xn2k&8N7 z(BI_dcgf&(GYVoh{(y1URw;L^Wbz;H0hm`_XOCN!gZ|}VNO;~a5i3r8qG6k)Wffym zauCC-iGh23#a#atKpk;Ht@p9wSvgivXbXLf@z|U5Olah-{6dJ9_ZR9zg@%>I-^4!;R-1mZa)HLZux!@KmrniNKY)NH+AwVVe#KYlFJX#zo8gKH zs7D$*;LcEJw@Et|`}lOQ+Dap=UW@12)eUcdlNbEJRgtCsIw2dgYn?^bhOzpMI!*ty zJAj=K>wBnN$J)@35TvAwi6r^Yko{JmN-yuhoi=QGcZBgX|-o*dx0|h>r@ge2o{v+z$zqm8E z<(tVIvz!KzQ}dBdQ44sA=Y*Ut(C$vVH$;xiaQ?CZ8NLt6$6TXrHvX0ra2pcPpnuLf zwT=38X(;irx8!pcUf(k3a~u>bLoDv>4?KLkW%|C<*G^hQXxehp?Uze_4!E@8 z`s#G!sQSl-BJ42dcL0hq=-Vr%Skx@r*1#XWHJI-Nm6z4~v0FsHetS75_FwXANtJ6( zD%>k$yZ}a>S@0tB;*yGQ=jmPWZtV>f_M~~zH2feE^XjVORMwSLcliOm2THPgne_g< zkBhNg)1cXsa+BWLE-{h{?5ylo?{oQ>eK!{VRB308l6a9$x;Vxw&&K4!+X|?v6D6CB zg}h1_iRTr^JFk$KuJ$aEDZTgH_Yc03$|hbX|4#Bb{cCa#9WiIV)!D?)wu{PVQf0Tj zYa>>!3}PVX4^mJ4!IYx4OjXFJjyQiw4H}m7&053|JJyW)Yagw zSTo!Hd?&Pzj&ETDi};j`y@V6dJT zk9}yu?z^IA(k?)pdv>3J9D6)ow@#3fO_g#LF%7CcY`R-*`ZwcV!4;(^dX*2={pA0B|uZs8I=G)h`kM92(pBy=9S(gNT!>6k?X|x)G3Q{vm@!i&Y zM3?Co=^VB$jx6x_4;E=6#Y(Z9B{LuRiPyO^$dcqh6;&MCwO-PBq-{g&ub12RSjQaY zF2aw*mk!stD)=5&hDHD2l0uy|i&6C5TbXxwn=nuK?ep(We~`v!f9u;sw0;r7%^$%w zt-H>*#iwxaGo(ZQtlfl035ROnFm9o~=CC3)*{)ysPk*d!pi1 zZzZj(pLDCGUW7@>?>Cxpd!$-?^!5N|sk<>s)YMJxa-bU?czhYyqGEuw{Fkr-Lx&}u zdGgCYA5$(2tBOnYu8aNh?#vS|a&rVEouA6h-@=FV7CUy8dqAReq^O%793Dtn5GlKK z-KXy(H|pYQu3tso8<80HQvmiso`F#C(71NUcV4iid3(jsCkt89#C!`v>^_Ee2_p94 z%fIztaJP=tdwEbFuJ)=5rK9ig$*BJQ?uIue@}p2T`%TG~6&2S+%-#J1)S>#fu0HpM zCRBRagM%bJ9RMq)k=WMSZO^d_Bz_{w6y#(7cCO*#SC}sUw3VeXuX=+yB#tX9#aQNy zh^4j_`61m_U=t=_)umab+guU8%~>!u6^gYdSY0tEBtu+JgJg^NY~_5w4uu3iyYd6a z8Z8{Gx4B6wi**~V9$2rp=$=%v-R(zoQO(O|)xJgRB|G9K!5)5YlElJM3~SScq-ZNi z5i6(73UZ*b72E=&jmED;v4kzrgc0{#zrTFXVa?#I2v1r@+J_Bt#3Ir?-s1tpwi&v)v)x=?^4r6X1)|-Q_Fnws`+BZo=AaLKu4zx2iG!P*OxDrz`q? z8gjD7F(QeV3HKR#9yjaRh>cr^g@~2HhUKkEyZs;+Axjo`s$W;O9Oz_YjsMl0swShu zKIy$dLRK@OyMNz~&Fe~3I=wNV40NXud-akTJ2CEiOQ6qNZu#(i>Ag@!Y60^g!Ha?a zt5`nKRwpTfLJ_3q3!W>W60-@`Q)XVJIbdEs`;vUpR-PqyV8K1QBO?p{J7a@Q<* zn_z-M0f@;L##eav)mOj*XY0WcLm{C@upHp%cx%J3Qb`o^x(Xr|-`*z5r@Sf2SNO2c znz{b1%~itk+UNc9PkAER$tb)8+=Fi!Gg5YVHZo_T#%>iw5YboFYZpi;dFv;A9P zKBAR|6jtQKR(;p1(@{;i*w=JaSnHt3x)w~7jbpa03mpovH{Yvlo4Dge99yjobDxy3wto3Vp0%M@yefPeWB z8Is1(Chj68rC;~!3(A9#pkN56a@5!QuIUPbVB+_0`v3G_uME0J5@An@VysH_T7O`D z)-4ZJ?Xh8Dc^IDyf0IpR5C|TU1EJTwRXe4UqZ-hw6ZH`RS6u(T2yXpql|ASg>f#4~ z`blo#`n`c0@--Ozz11VtAPA|Wot`jxtaKih_T}>?R%HJpxbNcX>}{ONMFsOOD!Fj% zT8#7%4Z>|oM#5nNaUy0x14p77-O}I2QvX;!@JU|3B6y;qWzynWbwIG40C#Cg@Dg^b z&XdAb0Ll9lwVfIX;MLKPFnp3v zONnDf3C?2DJ+~y>9nN~<&`-Cu#9yhxp~0OHEpKM|{qa1bV6oK=>BO_?{*F7-UrN?* z8;ZZmb6wl)5BnAu!7UbQZfGR+yk+Fq7u(T}et~2yt4)omqa@s^b|bvrmV5{&?!i}` zK7|&w5=65nq}4Kl*hgJg)oHde+BPn$l&<>M$JW|T&nFmU`o98S!0xAQ1 ziVTGC+a}z7hOPC$ZiP%o!Il~Z!K>rg{f~Q-<+rofLKPL{)7I}+(Lrpi!&PbRTJJvk zzh7HrzliR-wINLtf-L6g#C8ypzh2VP$OY+3g;>C3fTsN$PKk^v!O{(9Avxd-&skkg z)4GJc@A6$ei?t zWH0QLp#$~wDD0d{TJJwi>3T72hKG#Jhm(_%|K5BGt7E5=ob=JCvW)yuLT)xpMuIGd zn?2YIze5Pbu|_zB=7Oc+onZ8!Y)M6!bTVw9#XkT~lkzu8Ji0(<{()!^n3^(zX+gNs ziR3u@)coh~aSfMy0ZaET{I!?bDf)0gT-u4ke$Nt>X`{UMyySS4kOG2um1Ob;jstE<;p9JTDsdd4=Up zc<=v-JK?Yub~>9696+0DqCF0xxs~ZMe^2hLidt!S$m+&9n;CcjtkAJZn;=8{>SIy= z2!G_1A+hO)0kuQ^AxWl^CwqeL1TPiV@u>fp#=;{Wk?#m}k9x%Dt-gv>V~*SX9^Gj3 zbCtb+b_H!$VYYiY+1MdC$GY3lC#wll-OL~URv3M=29IO6tH zxLcM(#2(&ZG2m04F;lB4d_|fgu;B3_tMbDus#M=X8SowAN{>647@GN0kq%D~Oe}wY z`C_d+Ch4SY<1yRn%p!~4mB<&|i*VN)8&T21t^Jv)!LwM#+~^{ecNgP2#-21Ty(~C+ zkJXKNpejG(qLDGD8hs^qbpr>BOI)6pn1uz3^?Mw0D0+VHs@Mw|rgnTE&_V9>0R11* zUSaKwv|4X2yx}glHrQJpZ*R}*>uc-gU4yYgAIQxHY+6;>sP>u(aRBHiZG9K8#E4U` zFkKJX+}wY2PP%$x+oz*+QB{gL2vw3Zh?fT)aTxNeeEKQoNF@j6yKOh*D?*6i<~j@mdX95rqRmZlo`6*!|MHKkLzaW zRWMTc2cy$N^};WKqJPp-?#0j%jQQ`BXIoR?o8(?}Q5DMnSsyEG)6$s1Ct}fA7j5^` z6xdfP?bdAfO)k?a1-7H2yLBoRAz(R)^OyG>NoJObluN`#NXxLk?A9nI>bm&@yzJm$ zl@loy9uN;GNPP7rb$g6q-E_wA)n)jyTFjrR5`NY;XVDkE3kt934lCJkCm4_SrwREL z`5hv6M+*ZcY--I(CW%xZk&r8>iu?^|iw!o@0(afxFbq5#`oX>4zw5Q|i^x{++w}iY z{PxU*^7u;`k*XEIVT^9&^VxsFm8O|AK!4=K;-HShK|({IrrzO<3Zd}vUN<7e%Lr^s zy6)L~G?Alm_JBkOqfQa05`%0>;bf(R_45S&#*`F)gT2m0$i3eUI+}K#*e0OQG4tQm zyYqkc^RmMUyUM?`?JsIimNL5vI~o`nbpc!E`LKbj#XHRSm8*DiTpa& zAqK$c67SzLTN7hXs3J`goN8xFtk)#WNR}6%oKDa?`_XrdKzqJk9`0N5YFo!H^72UX zj|1-@{QsfMfVZ0DYY@29qUq&5FP{4{nhFb;#e3WM(KUj_dH6VOg(48`33r~9FuT_} z1&QqmcA$T%2s*onQhdS_)@fbCb^$|E90CIPH>I5_F9O-B*m2Bv*N&~9ba#j^49oDP zsIC3>=bP_LVTF=LojvQz>svMG!tQ@pz6|dIk4mf_BWaNW$UghTy=CTPU&3-la=TS# zt)U&F0yU#PH|vabfxx;8#bqk61Uc_SzLHX?2~xTw_$fQhPi=k~{+`A^ZPA)(?0OnGfGnbY9>e?%YUm z$Ao~3IC6EeJpTu3EjisBy2%yw7BIh__dOBq2ZK^y{1KqxlQ-@6OXR zhv@Ej3MwEj+b_e-DOx@bd2a9rQPXei!fxQ|rPlml-3+|Od?rOc_P;m=l`o|1{@=FC zqRhXFq#;zy-Kt}zVNW;e8#-?rM2~|B{Vgfjf5BqqLZozki8uF>Ah|%XXL`Cpt0R)L z2%jKoD?D*2VZuKtQ^!@C@S$49FX;OzQHT12=U1$_nj+ULKiu9VBv-vSHf|h~oF_D! zk_x1)CkNex67b-Y#T+80omKVV%sw05qHuw}O6;)eHvM1BN7WHpxrHAOHEOPwHISy= z&*6rLba?1+OGlfK2aMc6+{Zlm`<=l_{Xb05yQe`f>xhrrV3|+l?Z3o={@r1CImJ7h zwyC%p##yHO3ZH(#5ZaY~nB}a0v!yvRmbPXPk>qPR`d%SRI(YE4zp#L^&gKsX`KJL>gZzIyV#~cL2|dSm=Y;U{*X=u(fNydKksw(oxMxlh4CHEWpk z1zB_1TeDhDrhKK0)%;n{xb^0JlE@YViRetF$qOV6@Ji<%C@ZVmk9|l>S#DZwCE+XW zM)^F<&A8E2yeq(M{Y$j<`1kau@<4At0-ZA-W;s=n?o7AC?IB(?xjvk#A7Ylz5tqYY za%Nf+ zH!w>?<2Ik!w%3oP2IaqL$h$x351|}z{8o{*noaqo*Oa{t`GCoL1d+X-vcDQvs@L11 z$K?PQv$Z1s^&$mlM96;Fs=TjmhS zy`bcAqra{;j&%(&!6cj!UAkcu{grNq^P8^nY0TRMT7BM_8DDR@E;MOk+{LH&`giaO zLk0g&X1%aRTXO@4LH^}9J-;+=B4B!{uygTv{?T&1MGkh_R=@vnA5)$K78OVxO1ZflnADKhASv zftTTn{Wn?~f*4#Zh&}s3bM6wF%9*R`N9RX!8bL9@7Q6vV-b7HaIlU>k>BW#@Q8S&>G!R2#Qr~2I# z&%czW>OqU?1&$5By88W3RzLEwd)ITXC402JcAF=RJ9wZU!9GF0ft$hXNVQQ}i~R82 zKBaUu?>bde#3l7z?5bCr*8yiKUx|%yH6K_iv~h!y^+iQ`0n%AbTM5*Lns@NKZ`;xJ1L>!uSAcdgX2< zcV;BK#C##zlh!_kQ_eYdgc!r7=ze`n9Dc|YGIi7Sn@Cp>i(r^(+gASK@b86%@2D?4 zTmLr)P`zz>dYU}!CCX3P%h_!VITG!9CprJn2SQ&GMs4@1lp){hjnvK}qx$dJJ#G!% zcr9AHu-{AK_lggpvHC(gk}R+DwMddWjmZ<=3BNK6@CBJMDKn=kbys=#Ba;wsv?{~v zywoVVzgFs#)|@tZql+&E3fwAw_)ihx{n@KuH6LxQ_%|j+1vEHS{I8VLvseXjNCDl6 zI>TI$X9Ah+LOrn?o#f}lvbE=j_<{OO=HoUZhmk1|-LudQUmM%HMerFnWz)XKVBXq8 z`!|@2nP22*pSl?g3y5}c+RcLZm`lG#AXTlFx&U6eOg(L%O9~rP-yMebLYN_x=a>xp(fFnRCvahv?LRD(?6p zVsX(yX*lqM&^x{LL^=uj>j3?6C??)G;y;SyRrXV!!brqUzw)Plt?hLHGwzWwmIx$3VdWk5NjhsmemDA-PHO+tB+zc5G z$Lv@GLo$sLk=I9Xe=A({Pp^daXn{QLF|4u1yBGHZ05nMbzWDSaXrynApaWs7Z!|EwrQ8md4iF0wOqr)%B|0v;7HeUCQ?u| zra()_)GW?z&!;%O9>;u{{m@5Gj>LOErkrTWoY?7_JWLUUVS|uXpbOV%ZcZ~YQ%%LP zjFbu!>ED!y@R|}`&G`{-is+$|aT%qUfv}<^QGw{LvoDH%`SU8SM$hYejrFqMyK8Q< zE;y?~H9M=aFMkvD^ve2Xd|Ugi+%jovoxl;Fd$Mq7UGhh(>|QpF($7vU-gGDo z8zs-UayZ`c9$+x#N4v!nofy$Ac9sN$!Wf4zk+#d>j&6O@2(06@ZCAi;K}i1;hxYYL zdmk5H$xrz3H&U$W`ljowOz}d3plOF;tP-aLwfnUO04DY6?iCo9<<`F9gjPyd_zwI!8$VZsW`DYnCSNK zl9(rSaah)p@!N*M6^v?Ro4&di98ovpFj%O>rG=2EE+s#!YlCHmR++t{w49EQ>+370 zz6h0*mDGJ)o+!JQ>ah2BWQNsnR4zu%=cZh*x^bcCtDa=yY6DYCcHW={lOXeb`;95R zWRs|B(qX;;&lf%Y4|w_f2kjPH!$LY==Pt1#_Y8a^)>fC^k9M$Do}u?Q7h%D^sWgiC zLEJZwBNZxvnggq_UdTE4=c7WuYIbrA>1G36tHjw_hcYktil0`|FT;0LueGg#H`E=e zg@>-&9-CMe87zKgqLR8)W#7Q&P2 zzFpZ%-g_ExUbJhI^x}QAD8gQUIne%mG!QeBPNxg=EwLxu4_-sUh;f112p6Zli#}NH z?5BGah+p{nW>p#!n4b zVPnYs(UjDs)vQAkb)EFgaYa(t(n9C$@b#72U30LOlg2G<`?We|m^Ks8WqI@}$`p2>G#Rh*6z~ zypQUB1o(#}jP)ByC()fNR+@W)ZJ#7s&K&;=NpE4Rd>x3po=HNlANN6za`=XU8o0(A zwtIT>(>en+zN!Pk7@U~GV-gR`J%6tA?*&i)AR6^s`_b(M@PQ-;4P6rXZGOGVFZ2xT z=)RM5Kk2Z&m`mCHZ?3VKabNBX%3fS{@57$pq=pJjlgt{2W9(1}o|7}Zm`W}B%x^c} ziNYjDY>5shRihUC?e+>^%~tV9M3m&x*F8-*-35cg`>YCeTbW zqjXHUCSYHkw|5k&n9j727P+S>^Vtivw~-NFUf)UPubYK;GkMG1zo_|Twurwd*q~*B zsW!e0A`y8yJsM+teG+JF88zR4iYB|a;nxxso@)A)ybIF~*_kEW342rpju(|6bIf?C zO6=ife4f+pY&9}1@{jUYoKTuMPFJjzT%vdX4KH_ZyV39;1?NAx4waKrt$4Xr=^c)= zzYth8_MT<;Hddq~AKelXMcKMu3LNwle?g(st3PvG?$;BG3ZHniEzN@;ne6x|h+&bB zXMTCEl~)&HyhKl(FIhspZ7@TMbw+z=q$mZg-K5WVU|b37qMQ8qsi^u@xaL4kzKbM9 z*|BKKL`SCxV7r5w^;JtY`K~GCOnY)a1(ka z>j`&&I76)93}8$QWs6RJM`x*cK^0zI_pX`DLy+tu<|`z|KB5$PB_0y;Oe1%vU=2wYGw;$i{_SkdWRY>*_S?XqHJ;aD#TS?>o zEsJb85Oi8Ozy$+dsp2-~uE>@o!!N?!F33Muj<~A%!6ZB#c!wi)w)rC%tjGYf!UOZ8 znI8wV_PeAyCRbv(-WZ1Ab8*NqVXd~5EyZ$Ie+syB(tJKzn(Qc5pRe&}){L#*9R~ua z5qKkpY!6Zev0pFLnAH*k-xTnFH3mfs5GNQk9*q2sTm#~;d=I4SU;OS<>vG|&4`8Yk zz`8>!k->rqH1rvvWM)jY1JrNuk28bpTgJlA_mpAdohU_z>(%6DwieYzmqnmDZ9^>_ zV*TmJr8%bf48xi+rRu?%aX$QhDsK?)K2twX_$kx|ssuUcY56R5l}Bm}RqE|6g2d?# zd_|`Hy;4HsgGu_{Tn^1uQ7$#Hckx!Ns=u3204TwZ2;{ccpuU_+vn~NjKJwnLd|}4} z*&h?a{166|H{=_+w26gfAouocUB2Y-8c$b5W8|%MqdUsdb1^G<2n;L70XvJo*~x0S zL7Cu{YF&9@f51a&<{M}`Qi?ozQz|-KN{nvwH{iSAV4IaXdINH8`uawPhe)!XaeM?E923}T7)u4hYgG><~Det^{%rBs{r&8S)XV0~FR{!a* zm0i?>+yyJ80r-{3+inBZ^~b2G;s-0dQ~qrEBYkhv?bEE}Yh*fB{-)`*Z~5tss&nXy z1U6QDAFpXe`p3zA$h}rkHv2juC?B4qrZq2wVYMjtn?2hu9%~Vz4(B-&^ePYO^H|wa z{}NOkdb!j?li{%igW?m|6b;hg*8SWa{%${klMud=M^lbIm}#n@RHx^YHYL&GNcfwm zd)}{+kWLoNKGY+1vPg!3;A>*b3!jNclTq7JA*{*g!iY_3#wr;3z$0?i>;_BNMD{gB zkO$)tz+LZn%Lh`4nSX4ftcD<8DP|`_{ALn6N%9Dyikk zNMj13iaWz%i53j{ib3!|sAD(ty#VXYB4w(i1!3?W6%E8vit^oWmr#Kq{}~d3+9D3@ zI1lG|c~|jL5iAa&jB$9+*c)JFwtGB$EDNFrB`nr=4C&UO3$oLL&4Je0v0ro>J?pTU ze{X}Gx#z1x#J@KIPEEF#ZP$*B@&sdft#6=RS+buV>WFx={OdQ;<}RBM5-yDKuFo9T zXE*~OJeX2=*u?>l0a2Ig7B)kYABFl7uC^#xp8y!@bFw%!xEw>OqoJo7okUKn`S!8A zQWkFFqcl!XT%B>zE-mCIlWZUH0*yBO)m4$Ql&6fl#f$`_>6tp3XFakw0d^P1kLe)s za7`FI8HNHQX~LD6wG~0QgaxcAAWq*J!`gDBME>h1ZooaYA%|y}g_`4gT7=J_FAjmKu=mg} ze)O#g<8dRH6>C$;%IIYGi=J2fl!|4X`h^hkdH)@LjJjeDwmKGmaD0#(EY>aV*_@|8 zDNJtSZH63v7h8XsG`;!|uPrV)Reqw<5^iunoV1UFU%e2P%>q9JgYTdL0R#?+n58q=a7y78BMoqY zs&dc4V$bzFo9{fhJJFd;F;g~o9n^VUr{8~pYs;YjwK!W+)HZaL71xJG`>l+8`sU#3 zFpO>`%)t`g6^do^KU9iA?#(TOtK$ZQxrKgt^bZy7JW1%NpnUq z=&>~{g+CF{M*OopMpP9T8?9GXbdjZJw0mv`mgi&HZ8WC*VuKKpq5hpR&5IN)GPv^$ zJCw8Vok(9shYoh_b(o@RGVE>f5gk9x9GDJe2()V7QAIK1Shn5oTPMIPA3-D^u{6QF zYjzqM8l=brbT=lQqaGlYig$|{4?cdSscAkk09JmEp2?7d64+tXauP!;m z*Bz_C&JsowSPLv>(<&91&}2VOL!Iy_$e}zOu6ih zU~F&Fg>>#9+KFZ7`YrU@Stg!LHi9smDLp^GkB2q)MCaz?!Q+*GGj2Z=);GBG;Gto#h$m!V&vhOS5Z+%hs*)P6VD`^%YA6*`F}-(23b-u6=}G5jW9yUb`1 z>d$e#p`(U@-OQ}@;TC9@?(rrph8>ystg!p6g!u9=su(*`J_GoXQ?7rWQFn+!*~n`m zL0*dcS!0L4+EiJ7x!I#yXE$J=~!_&Fcr>C*a?7miE1%o@DAxZCE<#h0}>B=YFj*_f+P0ZnKoo7Qhkx z(ZyZCMY%MyNpLXe=S~=rR2MOvK9hEl0kDmxpI$p~j?U=XR0B=p3+fWcj-{S+45mL; z-MhB;M@(u3VXhV@lcp_hC3YXSK0bjnuJjvKonq%Cl23!^*o!ms%eDx_I(7v-2Qo5N zTHK34%q-=9NM*0u#FdcIO^7QX8bWuGBL2h#c1T#&@-Ow4e<8ht2G+U!03eF>)bV+) zEBjTUf#p<2vXDdqfwP=2>?w|7-onjYFaNp~{;&kbK?FFEOJ$NEbaKK$`5-F7d;@*= zOWXWtHGu01=s}e+uO6qN+2eM2tX(-t1@*vjd*=~hkl}jsh#*qE{poYv`tfYami|s>H{8=W^2<_ClqC61 zb7Z|B0di~-*9z|dIb8`#;`h6gXTGRMS1RJ$+Tw?o zWH}1TOtv;uH=9I;H!d-rC1l?CRA4Mf+HuO>00(2FdOx}|AyNv->tAzf40E1rFD)o*_C!h0EB)EVJ3JCArRmk$Jmdq_xCZ8PNnVVwz<=}Wkeekm_a z-V^r4*jmbXZ@tt&e=J(BT<{vL5nuic0r!2lJIJXut0U8YV8n1%B15L)EAry=^80dy zSEo1Y8`N_kQ`gr)aYHkBNmcYC#^{#{uuRIGE1VFX5xXC+0?ozu{5cz-@buL8_*IW% z=fNg}Q=KkVQYLR(eF4d+z({#q8=kA^yQn5%>nWUnF@<@uPUj2y)r#X+%dSKFmc6`= zd9<^qQL;APrsXg zs+aE%Qs;NUHzi*YSiaxMgsF8xb9OhpkNww6^BSr~H-%=vkvw=q3KdRFQkg6>s0jetrI6dcW>V`8&>y zXm4Rl+@NKf6Oj`)28?@$=d9~2E8o8R;&4n*MKQnCWFcJysOS7K4tfV+;n7I?DqXrw z`exVCA%3I#I;gdZJJEcrnkB|@`#n$Ql`I>bqRLodu0!X=Ep0AY*8k2vtW@Y~efM)~ zE?j^`*zdC0%X}OLD7%Vu3+Q(ml82M?rk(+%!?~4rURGv0v9kRrSo{X2#?hX36GIkX z76OjiQ~Zdf9%af99>pG9ev$_3UoXQ=l7g@rk8c9_u9y3RfS?gqORajt#jK2GR|>LP z+h{=2t24VmVbZts8H5i;PuusPX3xj@G` zbt7`S2dlHM;G*YH%Lz1A>FV7)(zVDzpO=-oPV7+kG2WR0R9bM7fdHLOq1>&mzAT82 zmWn&<4V<|I11sXx-zYhtrxaOOYnv^^=yZ*gNUos#{u}EvIe%Y33?warAL>4xuXTgJiq{-y0_VdRWPlOon(t; zJmOE~Xr>defuF*fPUi_;x&WPR>(y7^TQ@XpRf68Z+tO-`{!k~4Q4+Oc&08huzS=Hc z_<8ej^R0B%F!DPRmq>{&oY&@Cf?(ueYQFcEIqVYfnU&%iNpl_2ud}eXH@OQm!|(k} zR}h6v#`kTzY;z`BgK`Mxvm?>Bnr{a=0bWcjY3yq%4eN-Rr@C2`qU&cIumu_)d)TcL z;ykPz^}&xIwQ2z1_0=z=*Ary}prlTZd`gyyqxF$Te7VoYrl@vkcW~FtG~el!XG1;L z{;9qeTwgW+@=>HfLI)@e|f32iXwus1*u&kXkcX_zIKXvfZH=Cm4t1f ziH>SXNB~AV`0ViamC_?4w^uxr%M&+&tgG^>+m!}tQbf_HKE}WHqksavrGKOY@~`nU-Ss6sTEt(_rh!T z_)cDA-NNSnw763%dFO=@@V?787Sc`boA@MQ#eF9Kt~NuJK=fI9q}j79Ew3(~UD+cm zEVbLM6=!jNhCzrA>mNK(=u9`!5zgvo$sYal^z1=-Ni8ZH?UhRF)XnOfbKrTB{l63T zkoO{50vr_Wci&cklR)781Jx+m?$*d8kafbUm0m1|-{bDPbte8jAa>_6e*&0vWZhR* zXYz-4jbm)<_wPXubQwJbMjJjuKBZUjZViVCPE%GHrK5Okf*wG1%cC71;^Dnpj)}64 zj4~u*@OP4*);fKUXS_-9ca02^PMEG^(c^FVV8Vp?{f5$Ws#l@4A^Xk`&p#}v)>N9l z!2a+!CM$L9#q?YoP|1dJ+AQdSrAlB9)Yz$2zE19XuLJUEKLgk{9EH0L0ahm z%1(BK7zQo^nCZmnj)bKm_OR?>b^&%-c9t_-TKS{7$xNEmqeaOnbPRx>{qkoKbqxFA04JoKERvaA6f7Cezi}+>lhSKVz;+vXBSDVPA zN`iiL<#GBSGu5&5v$6fYV&7ehOcou3(M*^j8L;S!yn4?UgVsl4&=7jC0Ru@}lwym% z8N89;dYXHb81ii13UT2h95Q9@ym+y5x_n(BuLob`>G&=~2W=FaV|~Oe)5XrfEG|An z6mx-Fia{I8XZ%8OKL#ClO+cQ%o;@4j)l$%B~Kphg=+|CwyNmu!&C_ zyO4unc(2&WxUa&_>Ot1QZ~oXRgN{TpANh%^uGCC_mv>qo zKU9psD)diCiw1+KH{Ke~z{#exKN})C{;-dnh8{V>CwWkkk?D4I=XaG@_2oTZ=m1!D zo+Bp$?q4;!y5VG}3s=wu`*yJyQ$?5tL`Un1uw6@|?ZP#sF65Gl%M>JGIRpvh#GMJH z!S5~BgfuB7T>d!LyT4N6JYNveW3t0|=L!so4Er~OTb-lGZ@$on+P3?E9!r$jgtvYwonPRY0e3E1w>EU6j^ z^i1~-A|sErJwrn)=&UN`W@MjeH(J-Y>__NK$_Fskk)BfD8u_xkSx$jZWd1>*@k$7= z{2_2SLsN-iAe}beljuP44P(*PDl68gdI^@erKebL3ul*oyX((V ztr^Ip&giw)q@hU3_;jrU5r?xU(E;kTyUj|xLG7qGVUm@sp>{3ac=f(&%3z;bzmrCM z$E3qO&zv$VD{?-~N=zM1GU|-Fv*P{oIS=~gGTHz>^E6#7wZXC@t+iG5UPb7NDV`gl z8U9`hdnse(gZ@HIFK@3Bqbo0-LO`FS#zltl6X7PY&DH5F{OA2WBvm$E^EDR#C|E(+ zXSMg>fu0;JTT*o!<01pRQvQP7-zuG1h}|Y;H^tt3ZUE^F=>%~gsHNA7KML2!&>AFb z7?MJW#8Gu{;QewmMbmFWCnqV5wuBJ^;=)8BBzrwjjaF8+eF`r~uH9{ANQGApSc2Ln%W-kNV(fJhJEUxAE`c z8~!xn&ay|+s>deMe%26GBqww&tui1;jU)IBreF$xC1W)e$j+J3BThUOse>2CG>F`1exN*Mnq>PbiJSkKK^z( z|9Qi|_+y53^H}euCFqKQWF|3q^}WY<+#LlL$Fgy0pd?<1yM8JuFRStM&!LM|HzH0* z?Ucmup#C?k7A0W;!EdF9*jx_XfnLFEb$q#q->GoEyv>Cv%c1RsEb{Si_$w@(5#eJ_ zuU}55|6^Aj>_pKD4#5(zq5G$}5W2$39_7wJ(*Q3`x0gJ6bU5LgQ84N~XcdChb2UyD zS#Qn8`K2c!3P+}Ko@I`~W4q(Cq~^+&*F)3$o0#*FK|AKqU6{l7GNMv(N@{VQrOVPU zf6BX|c7age0Fdt$2x%fT@TRxeKp_z$O6XyV;4R16YS5zBUY0RLEU1AqSEDO)<{)J zFzSG(suU>|9;)|2(q7AInXBxb5udns4<9KkM&k9WH;wj;k}++fBriI6|45+C!>>(A z;g%&n75PR|8gi~*uiA^>PP!#r9Ak-(p=C5@+2`*=1Mc8Xh~E*T*qiy7X5J^zNj`kE zO`RK7jPjQ7yOOlBHI?K$oBaFQL1YQM3;h_z6&)_7$1aNO1akR`#kOA|y7Y9W?V0p#v5B+3~oF(sfDdq^lJq z$XQmGEBfE!Sgmh`w#V+sYVk==s85`7_bA2A@txx2 zQo9}PU@c~ND5W0=mwesBf+KK?0^-fDJ9uPaG3~v7PMcC611$dXLfNu_rwxzgaT*xd z+n`OnK=Rq&(2OkWp&JRqUU*cTZ#ZQXFEO0YEYM9}l{)Ku-5SJ+e+Zv3-Tw-P=UagO)pyt$R4z z6_bvdRu|caK4fiyqxE|>^8WBg`$5>bto0kP6DXAT!Ahk^OXU=g?rA|3pELotD z`W413*8x(wK@C|T_z;@lpi{GgBQVCj>9r~785DT8Fd{^ZQd-M(cw#!sl`r}0gzAnM zhTVy6X*_OzbFzKABboi*YB^vNs~f;76gR(7)vrjT;7sUcK9#rw=?>0+js@N>LSuTK zCy1D=_~`j6$CiaCw%Vk2LO`mtM|f6%-Ua<`-)XUv^gV25`C2Pbjat=@z2LhN8})}v zG=u`(3LSX9rzqs5ob-2bt)4w0(4)1U>F@}DEgp6y+ppdMep%3$fqXN5651mO!=DEP z?cHB}WvNoSfbs@;E2s&Y0qnc)ov^bUF|wcFL1u!Z^C&;1^aBcAOiks-GO^Jj@RZ zp?^DkWTa#VICI64qbSvyT#Lj36{GfcyVKnX5IoRS%yVs9>z_;bzXa}ccHND{mJyg6 zyQe+PMe@SS`$6NXQk#x_TPsaWtk9@J9;-XK(v&)sTKQucy{@MRfpK z3IkT^H?+t1Kn)u#Zg5WfHL`)*??O9!kf!FB0Pkf1-JN7&biu741ow2T?rU7=4w~^F zMx#IqfhpVBd+*+<>{sNZ{2Yw4-W=~=wzYMIgHT3o{A8kJI9 z5E(TP!$GKI2AaNM!BQ>T%Zvj4bE%fs;uQ-G08~gNsEEk+NQ?Wh%yQIsWi#Dj^pZR z>9;y>jk>{OIr18QgI=E8KcJOY9;LO9+XMW5hEdP;8IGN5<6hZ~!6;SEH1_g+TRw-R zk@$DQ2iKpJJd*L8zlbP(ScRuz>}*);mls;RHOHa;k$zhC0h7x|MMMP zsFDdmRpFPf?}hXTlHrH5fPCUEHKZ3SNlTs&Q~x@`Sil#y&Nic$8Q``n{7cMdMNkC! zy;DO0pl-e$?i?~(ReN!MTq~_Hc<62Nm7Anbr1BO+&4wk7)#Yq!S%hM3#k8S@tMdId zF_^1Tkh|04rN3Dint48Xk{`w#D;vJ$YaX33vY)?}G3#c8p7vdNkcS{7rnd%`!w1^E z!+3-PhV!1~m6SD9MOF35$*o7E60_zA32gpoDTI)Cs=HE$xVz)WHcM{@_YU>OIgwo(vq{7x3r?VdKOlZW%~<6`Or+NXF)Sr84_%9`OzLb!NSor%XjgZ z!pIrz2d>eqb)jfqSTdd4NkFNRzezyCR`54Syd8>E>gcf(*n znr0&U*re$--lZ&V+qITB^CRDiu%!IWw@6gk0v~4&NZZCaQzHpO#wN8_TFs>if%;E8 znp#!KF(zj4Q*nnOUCu-3TQlO6CC%@493;05?r;hKc+DZ?9j38;v>Q@e~V4vjOBxu!@=KqU^vFj=V4_q~VyVRw-&Y5r4% z8KU=ThO!fU|oytR`X*$Jp_P<R;qL0~?QyW^F(-c*@Jo8^ zupzgwO_@}zC)~rwz#tqmGUqqsjoh{^>33?zF^`*`&|SaAVH(7QT3uSLRX1=VeSEEk<@p|{->gvGRf~aU*l|iU$m}Ho4>jl(g#=I zl>A%{W+`OpqeYhf$`ws{(JK!5*(p*J(-t;!Tw;R=mn#KN^}lL$HESQ;8m5uf;&JNa z7B(=XpkeFaxO_l|PTa)7FYVlu63~PIrU@vMzggo1Jkh{B_(%#C%->QDIedDWz$4>$ zNm-qSb%HYwOMu0qvWF>dCBjSmimau9BWGF@Coqy>m2z>>cjjf+>@?QSgIHW9LMe^E z{jSOJ7@S5kv8M&Jw1RI@5M%#x`%~kMAB_E*Ctz{=C+(4n_&>cp)fdPwJxm_Io}Z^K zJadWBM489vaTGN;!+ao-(fE@GL6NnkMI1HD5u1Q&5#n^3L}VOwBL1+LHP0Qdg=qC6 zjG#Gv?al~cT$oyU_Jfw79_qR!4|zJzBLjtQ4*9$(s8X>{AvlbP>=U1y77rcj`^$EGF8dhZ64u#;kYV;Asy}8Is%| z_UODRR*m(-K^I4T0|dWura5|L{jpoh*f%Q?n@r=((Q}qNoI+rCkp1iBf|#Q#^ootX z`7|A!4bSR9A-|-pD z*lj+dS8NtkFr>VYe2LL{q%mYPMs|})5I>*6l1L@iKah_wKlw6vAOU-XlnV&c3$x8r zf17mkb|nuHO3RIH<|gG?zx%>()@9n^RsM7FWz|NBTQlZ}Qz>$n%vHPnYIoW=+1wX; zC}ubMTHXD1hRS->_xCKGYDfq85BkXyU~+O~&`Tu1{bUV!V3wqT%`K@EV}?{*8c}2` zdr#XZ%q?nGEET|3kThQD%+WM&N>XJp^6+43(u@e@Rou)IZ64bM9m-K+zp>YBTFXC} zY@uB0dr{1f9ZjUYB9v0R{*tfqtANZ{E2)-!7D!>@LIM-X_>Dk!Zaxt}m^g|4@^4MA zZq_80eqVC>$jaVTcPzGcqtVtP z@2|bUC26f`gGPZ5RLxXH=I)3B{yzGtO9WB0s1~5d_Fq))#Gyi`p2aJb;|VeUIZQq- zfAWx(*%mE?fXV!uq{QF{)gB7c-vQ(>sZqCH%Ub!?v&%8DF_JY1?g2IT*VxVgG~u&P0Ip7hdnw!f>v4&v;Ghb{WFbkT z+|L|np-;iQwtjqzXpU-AYMwazu>E|@pUKbU9*?P+{b%Wu5*DA9#qada^)nGd>##tV zdL}p@L{xLbspft{hYCX~O>};ds!n+Nud_!{2j;Fl@r3`>VT^ughjj{`8= zKK&BLH-Bw~V{dCB&4kRB?xoFKa8ECp96d?=M~mq2@mtYt!Jg_MX6oBkugY_ zZqMnu+wApKu(^TCZ_S&UTKP%RBI za2AI&KnMk8kwjO*G`s~1XAeuG-?QK~E$b+Gs8A<;jgo`g-V;)h+~N!(H~{?Y3CIx& zY0+5-Q&)*^s-LachrTiY;th3-(6!!8+@2&O7203}eS=H8 zhCTua?U6c|WM5eWoP$)3^k=S5d&u`(%0rJ1zvT_iU`$2yIZJ4FfKG7%0}INaFboP_ zK#r{sJMg+lqf5S`MUXQ$bIwd=UQqFU>+Vp%2OxSse<7(xT=;W>D@aAaOr(lQc*kML zZ2o$S?ht4$VP0;wY~-Y&P5*^8NZ}i2D^=xf%#j$&mrJu@Uyrw4MzQ%)B^-;iM@h^4 zLc`P{tqB}oKihjkEmV9zApKXdZaS9zym#MTY@K>Oc-BL~k?&K)>!mECpR14*?y9@D z&(@t8Oi{7JB(Z$`7tnc!OQ1OBde83p6At8KXydTeQ=V>A21Np`$MRPaD=puz!<#)l zjA2?|4QASPVzHt?+szT*K#>U}r7#B=bIFi)xNLWxUdDt@%)+zQD$L=dB%f7$PsGoG&=q5 ze)IEA{oEfyjHIqGihX8d46@%ev{{d5*m7@S^27&SBAa&B>kXt&_3K)?B)*H_D{$Qf z1OIoa|5}@lJ^;{8bhh<&L)-}a<`{2V6-2KW?5?9mjApPd2MkCz4bHdggpCh0F&}ZB z46n95_2>YoPj1`S7J+C%S!E*x3y@tuk|xp=g?sTplQ9i?U*IQ>NNzq|9B_XHz(GN2 z4*9^>(f*wcJW_rW@3FwY;PS6tq z1C=yHrO5}bIE1E~DVr3AJn1J8BA2+X1QKse*{%x0Z&$N>1^rS ziwF`buoquP@_p_cgUb2+Y;vSZ3g}Acr-p7hX5wDNP#Lfcha1Se{ zXeCm?B3|l{H2NsA4CzW}**@*r)&#x*635*e-DhdsnN9Tn-N}0Lqu=NQC)Q1lGzY5) z6c0s~dfvTb!OxW8cBDMia78JynAsd*Iw#}j`!I!()o-kiO=mo7O#COC^G#)D+J8Ou z#l{`R>NQV~4-)4s`=_`a4cfqkQkw^4PWh+k0k|qvdx@CSePqibY{ z^dq{f?CoxTzjgRLHR&e4=`Q{oi9n+-QqwLG*+$f__L4e#41XParzq!&2IkY*PO2f` zf9pMqM(&-gfRR6C8z^tz<@qFHDy&_B@;bjIk?{Rco(i(0a9JxBKU3JPy)GWVvkB^; zH^t`0&Qo8gi`YC)d<%cG>F%`wU);0nI3ithwfh{)?@(J9MjPsb@QOSluL5R(!LN_3 z+cJ{BE<;z*PvuY?bIq%i4h$AlFbmN|wfL$fO)gu~;Rk7Yv+xbfLt-3PWnusHOIhRn zeSos!c>hl5V{?u}d_%^cF1%v0yc)AWvL5p--&prf0+@n4IB2L-BukiQtY2JTQQF@5 zhF$0FFvgS`qro>Tdp&}%TV)3Y%V>%TwpKv)1hUQWwYNHL9sPm)3|awE6rE|G(N4&1 zEtBy<+4089xEL9DOpLQAbmftx3UVCbxfQMY4P0^>%$z$`Bi@`(jO#A-qtoh4W- zcOW436{7x?dojMJR)>J~s1f1lk%yG?t*Yf(x_tKZ!c3wNq5j%abU8S+$ ziXf~QLN@hgvnJ@=a~z|M73$4IW;E>H^k>iiYm;)l&nPS#nkB?=K{_V{!LZwz2yNR_ zKy)MbQw-kFKuFxypQm5KP3#cGs|OLM?(5%t_nnOzzMU}E+5Iiy2BBmv8U!sm6l(TH*C-oG*k_S`v|omn@Yiv@V>g|sog+08FWnV9a7w11Xl57(zBRLVO7 z{ST`*2yj3hg+pJxBb{K>zTIVQF&w;kn-|C@6WQHR^Z$tY%77@F@9leo?(PujE)kJf zK)R(vLO@DFNy(+9K|v6Zl$H<_P*4_-kdSVH1(Z&ahTZq(`9077{lu4H_RN{{d?B;%oDfSAX6t!^4R391@JpNPwOmA=tn`v=Ep8A~(qzXFy?~~`& zwaK%EDx@5mLIzS$ynRS<*>kXDGuUY|P!KRQ>C=(Yb_H#_%H#QDG0nTImlL~JIj-zx zk(9KA`Fkb2wr4+xP`(9L1uIzOkWOQPHuSamX44f5rIb;#W_h2y&affwhrx2UUTG3b zP;vUMAxu4=h@$D!!i6dIRlY=bssrDj-JS5~_&0~Q2HQ5tltgE4Ma=@cFDVkrQ-Cx!ZikMT&f}|HUGe{MmhS;)^6^>R6oC z`WFSw>q0AB)>q0QF8Z>2)H(Twjkc=lA^{P?NU4{h%NWjsST2i@rW0Lv4#=DbZ42x# z`2nX~0%7d`1&JO&fIp1l^fbO-PR$wep+I6d@JU%Hb+%ZC;`^?>#S>mnyXhI39z_=e zdkZVVvsb_l)7NFC5LEA!*-@7CapMhq5)IKxof9Oz&)D*wnBDV{3zeDF=>4g;YZfLG zO+^n2`tm-TEC44DJeHH#URY3PgoZaDh#TApMRnyH!jesPv zlYUnbebQJ04T7AI370^l|F!M_ni*ZSKpYMUDgQj_7;FYtp?ngD}Fsc z3<8Fx?{+U3N-aFy5_1zFv3uc+s(x5h_49f7&nR`I@nWeaoIog2hx513LfC&C{B?@h zKb91UBj2)ovc%*}L>dG(O$Dw5% z1@pR0iiKkftdZezPvcX=CFzW3M&rdx_20=fZp!dA%f2l5FFO602n2kNU-RV#xT;RD zVQ+Y|mCG+{&9Y&W3*%o33>L4BOy&0pLbn!pc&{LqUppH6wTh+%=GEu!^)(Il5UECy z81+P0-`+P=o!+i4R#R5eiLiil?lF!hRMNXYlECXdc|sIQI_tRM*+hJm{QgE&-EX%> ztHCbUj>cMD2^c%h)qiLYsht-RN70#w0K;tICKSMC7a8`<2YD~Wx3VEJX% z)2INt0u!0xlkY?u{s1Sr14lT8fgeujS3|tVJsZB99II^;SrxZeiP#;h-lVu3;RXv* zkH2--rxFPXwb~0FU-_kbMB#Io$~35mUwh4Wp@w{*^OD@Fb43}2ZYjZgbM%4b8i(G}7%bsztzHC0TElIqq`RFDFC zkDELk>hOjntOo?Fk7O%V#T1RyIU6D3Q>SG_=k0K z-GQaYCnEN_YR()N5|qrGjoFh=Zm0wlw`g2c!6qzAm=jj<_g&HR0HcgJmgq`ZNbH8& zRhe*U)c#d31unj?fJDK~C>BKp`g-UCRk?~qGO=)V9NE<=fkeY6^hc9%Pkhs9xCb{9 zm@Nnc$9^u{L>oCAxfAtGPdM$!fsNw-afg=P-#gvkXdrPyA9Xp}@K8e-Q?sz9ZH!O5 zj$1f=AAb=S669x;yfUWP=>AIz(N&Qq@0{+6P^2KJ2;x&*#Krsxj*)*YKHdoSjQdSw zXqiZ=+BHyVXh2hCo#dJWmEKNu;3H_pG2`tqH=23plz5~)_V>TEaMmOL??v@TvhFGV zHwkt^d6wyQ@kTEEI}Y_V-tq=}pKN_0Ra5(HBmJ-8H&Tx04vz zzO%E>R$i`NH8nO)50eP9ZPP|?-b;5k2-UvNa)|@`1Me!w7MUKqi7ns>J@k|v?^dFQ zyr8jr#&^E)M1|9VZ-MPL;jriw2`EBk!`CM%qmbT>|LH#mALTv{kxmwQ_V8lL9k&6s;RH0ajy$0Si_HKjmI+mA)KK>WecCf)23zLO8ln1qnQ7q3p^ z+f0{qAw2)(u|gEhkxDsEnoxOi{`Hg(4Ex%$bcVJm$^vbfI72XA22(?tyrI@}!u*g8 zbAGYsCyK(&)lhZ0Fj2;ZBYf3#Z(}Z;&|-a?8-do*Y+|2ftw!Hcy53wgxu^1xx0Q+s zMry4ci;+9c6!uNim6nNM*c=_>5aTK+`#R7G%|M?`H9;@`jTPr)z)dK{!au^9^+&qLof-kc1lun*tmUNRV3)-83e z&xigFF{R)o*k=kptyqBCQ#^tyWItB&L9^Z_*wWwO8KUnBETlB9MSo&6vWP z*ciXyZ9`~w_@^iLYkb2GhtFUDcHrv`FJ(9r9_fkeRY~+S%HOCOe#9JSwBWrj z88scL`07*K0n?5<;}oA4d)%uQg}N61iNnlUanpB9)BulSQk2ebmZ})64c@|P?K^t(w)bWc)+?5R9eJ=_2jZT+H47!sD51o)RV) ze!0DzQoC=5lgDL*A*%DP_a51}V4`tlI0Qe%e9~3UGg~}rW5OiL^KJ9XuL}_o=QBB1}o%(;9n`K zf6hvZX_rr0t8Lnv9JtQ16Mj>J9#cbV(NP>Fpk_M+$+rM*b&?Gs>DE7B{dJpcA_5Ac z@>~8$PUP*eheg?5p8#4GQhr$kMDq&Tz9lT z+4}${?jNl;?p!LPy4oWxU)QdN<_KT5vDxtW(cFl_rbVy??mW|eP`pXUoZ(X)^x~@$ z#Lk^$s!wbx-r&Y2tB%&85?34d94WGzf6+w5P|LiMaWC!l6xuI=@_Aehcbg}E*Yi?G z#UnVVbb6ktc}rcR+AGKPX2y3bm*_X^#`cv70C@A1cbKy-|A1zP@VBRa$?l z4sd?B(YS&9tv18{CXJkgT>9hQoQJRm!CUtz)eIfehToN)A7LJP|Nqd_e3NBSk<*sO zX>0iBKKH&vWXAUw8LjkTuTV5fjOZw9kC!uayr^#0)G}7Q#0Svg*;u))(6R7PDoceCyXbC-3v72+6hhD3k0U%?|ky! zRy`P=<`6wT8qC`CpM5a+?-9i$1yThYN;jiqd#=q1@y@zq=0O+my5?$c=jHk53a47$ z^0HC-f~k8lJYSn0lj{+F=E~Et37b^6NSeI0q4Bs@0;y=-#4o~UU-54KYBahZ54%++ z-zoBCj|cG>2z{@vjrY#t$D%3}|JRaW?qM=K>d5mvyZSsCiv6EmUblu37&(;M1m2x9 z%O886tdXAC1}5&2&tin{?JwTFMPi^LY8%hq_0oCk1b6O36nnsdOZ6LahoG)!gnROL z{dwIl^17)bIQT^b>OF_t#}}~@*wv3tdm$gr^8|*xLY_w6z8w~Y{X3(8{`sz>OEQwU z8?gbZrP~5lD@cA*zCDR2qTH#t@7$*7F z#e)7l3`3zS#)yY$uV7D|b7yk;lzoTB6%}^q(93 zIIk!JfL=7u?2lRlJG2hRisu6~PbM9TdyDqlvTDxhTRlCW@>T_`rb#Mju@`1cvdrtitt=5A%q?Y*-fcNVW|tY5N>X7*sx>`1<1Aw*us>M6GlUYSU)=`q>NhK=HbzCpaqPrsM#Wxsf5FMj@AHNE9S ze$;6ak)I6z)dEYdAV^I_4b;A^Q=fE^2VzQ4l0b~3+Mt6RK7vXIxnLG3;43!=i;2g^ zGUUOff1@1@Bo9YS_gO-ZBinlH$|9MFl?u`+HTd|Kx)(~+&_pUnRmMeuVUn)haz}Pe zz_R?y7tHF}em|^t8!l?Ff@GlwnTOpuUtf)xl1UebG>$9q3tT1)g;2N0-aKnf#yB!mnI*DPF>hWj@~^u%zV#%3>X>GAudLvC zu!+}ZhT`oTJ8xO8W_uKR1ca@ZCCWUOQG9Y!5Z}Kl^z5?1gCxO6B~CNCW9;4im6m6UDN?!iJUyO!!S|S5=ZlYAET7GA zP|Uc1>Bdu}{qRi3-9iykgMh!|E*>*&eSVvqDs{+MEog;lL1DJUp+X7*uzl;n;Wx#FARHOf{(x)v6Xad{S& zJZfooE|kh%Bs)P?4JCV(x!G+tAd&*;?6&U;0ZOf_+KT_?TlD>^hlNSvUX_LOBwIpy ztpMf9yx|#2N|s;0gGWZCnkeNM=oU7n@PAS^5xcf4NjH$;J6-TC5U_Q9XV3VBS_x#w zxSmiuU2*OA>9*e*wpXkimgu`Sn)VL4OvbYeKUDM(-qQ) za-1MUV;Mm(w;aJ|aBwSAGJ#41QA3b=f2b&Qz(l&}Uu+b=SCt2nyu1hYFNjavGf%vn zgpkTg!?MyIXPiWK5oj9g;W_)BvePU(I-MZKN%1_kFn7kti@N*leii?sRx8_|hy89)OJrytFiFnAx*^SlIWL>lm@o=1Wb$ zMa`*FO#u^An6o^9dlQIB>W(=ByjT?H7eyT1U2cqMFHe~g=puVIu})`Cr$RYpz3R`=4gA&jBN+K@=G|Jj60_7E7t|gSPYOuEzi- z(USp7Z&xDjCt_EOi4?P325jNdPVS;Rezfurz!{OeoM=8#Fr;_hp1+xT%37hf;jf!w1dVCC6Pnx2 zQ(#2Ui*1bJkFuP^pI%T@1J3_Z@m_9?CNcRvK(McNZf+ej#Zo=tSJaTJ)FeOCh z?Z75QT!ggboWqplrmVnv!@(vef>IZg@v~OM_y|UiEY*ch4ZoXUFCLjpSow48^5giJ zd{1k+Wx6tTpa!unioi9U*~?>LwWZ!6u73kbQU_9>FZ+&*=@XSi(M$qa|6`V70YHtj z*ey;{p4$TVhlp;}(jf;rD0j9+dv=BZ{LXN~JEfT&XQ?f*HGe=(!RUt}MGCG>Xug8w z@Ox8QQ7;z1rXOBAORTzvZ(EH;EJ)q77hAHI12d)9t*Pc2&J;S^=3%!zejU+sfX2d5Ye4 zp6?jiTvstcwL}Tbl|jubMtIYy9s?)n`rf0J)4k0a|Ihv=pUr}TsG&m_Ks zZdVj)JN=vx#eD|k4v@#g+jCGyrbE$Iaz};@m9ljv9$1n0jRC(o=RUDD_!sJ5Wo~}( zV`mX|w^7yvFQLRJREn+m`{@5T7A8%R_(95(oeg*96f7Y`xsK`{LPY5yqK_q|2Jvj; zMheLtZ}k!4cDy0Gl#0DO_nene_j}=E_%!FK=`-BK-&-=;CJlf914Zi6NT+=U5SM{` zGY0`^LtL5?YO zVaF?qm1_>G18uXS>fIX1ajQlsL3v9(A+c4*?;SVwC-$7S+uwzmANw@=KQXIMdEI)t z@2m-BX58G^ocSYAT`lo1o2!XC!EIknlSeO$UA8x@M&XjRaJT`w&)WLm83S;T8t>cn+1TEaQ;*#X*p2Qvz>nkg zz1tm+ccI>?9Fc8}5+6%tjL^3v=#_-18AHiay+)UO4X9EHSeQ(qgd*yu7hlU+ts<|Z zft}*%M;Cyg&v5+Ae>oO z&MDBG3t7PrDLSG_qWO;bHzpjpUUJqQmBh|9<=oTEq2R<_bSjIE#%3c%hwn5e`lUuB zk$f6lasm!WE5d8;wj%qRWTi&n>y>(Uz?J3FL+YZZ>xi?+P|QWj_yh!tf6vssT;g5H z;mJ{iLedu-2Yr~aZ|tw5;o2nxy_M{Jx4z74>L5z=kTsXPhSxLjLh5j4>JmRa0^^%l zbU;EN+21>Fj@O$xMYi0!4%Oz*oj93wU|(4qDA6xsG5`V|xW-5B0&RFiXF*NpTM$W| zae{rgo#GNK*9&2HlZF)tJWJ!MO}Htl7=kS~+QAq29lZBBq;%!=~-2MT*eTifIv){Bjn_ottzGfgK3xSL4;O*RQ8ObTOia% zyQh>m<_sT zg46x;uf$oKH_n`SjyqEB0o&cNLVYnRL$|7nRyu&pJDqeV)OCGC@@->=i`TPc)9OU&PB3nyu*DbJpewt z&XMrvhL^3fo!R~CDu(1X1dXO``Up0SDq{=K0-7NCjFJv=gxEMeZG8F)a2TuWap zUb(1Z)d6kE>mk)1e<6vjBTr|M1T!cR*KJdV*`H_f%W|`}0 zl%g5jfY+uPskTKVArf58D^1`yyx#;zRFHlZChBHIj*xr4b}oQ-xn8C);MTS|_bm8= z*ySgu^icW}ZUOWi)%LSfxC2q{Djr9*^w2*xK#tTxb*aONV^o}^6XIENGS9RgZBjw! zD=;^(=$|~zc38rQ{kS#R6r(Y-`YEc!Cl##eQvIb^7G*yI7^q_iVZO66L|F`uxn zpL(>t!2DJdpt&-2S8sXPh^p>xG`DTE2I9aQBnQDHU|W@P-<*lG6KW?9G(;hy-}s;^ zLQ}$hHb`-Oaz`_TLue@T>lf!&hkZLB@&Xj%K6y92i1U#jUq8Br@CYHw5$)Y5%0w$W z1zJNT#PD+{szNaZzFuCoCHLTn_%i|C8&Ydq_724|tu`Y8 z9{GSB(9K8=l#!REltoj>E_^~jIqlGtxIfb31ykC$G!nHMG*O7p#R^<*Pi7jn7bmSd zNAXdL?*Lh4Q5^B33OfqWviLZv$~g$*_~XuViJP;=D+yU|(6rxG<0s8$2_2IT?mZAc zp~5!=-5=aHxqq%~vK`Z;UUH58A~;&s>qmW9XM_ZEjtvzaA3Ur_^pt0TOrhr{FMn!$ zDwvewaEOwtZ7xf^{eFgQ$cK>UDQGYi5WP`PYQ*<=^N7TO=UU|kP|@dbG@&BCv#20{ zzZe@5x0u>0tbrDRkC{j6dK>Yxb!4iH=gnb9$^M@nzYvf!d#70J}Y|r zI80oC#tcHq9`y>}O%WQO4@L%dE^su1(is-I56KCL!D$DJAJL--Xs#;TMJ}8-Zip0j zpaRNIN<-%mE_Pa#*mp>Gds=3wqvNMKLidr8s-M6+>FaE-b95vwT!HHYApcPLp&9#( zB)Fz_!789x@RX_^D_fg6%(EXwe0vP)z=1M?^HBCiL@nON25=o=P0t&J?S5`r7uF0p z{OhUkqTG>rh4{m^W=7XE<15gX{0jTvc%PN70lQtOuHSky`bC(@Fm>| z&-LT(AvS-k=G2-G8$3dQcbB`=ibCgiQv@%X(4Jd|9 z_U2(}_)#VZZeoY=H)`S{NDgCOeBf|V(zCmy>TYS}<1+}$(H6DbHTy4vH-t&*$S!5t znZXIxtR&cw!Ch=Zqgthq-bidYyVv-yCNl{ABg{$*;7omaB)#+?N5QTz2JznYX&>42 zpGe(<%$)?2Z?BKDUpsfxfz{6o6KC~zs#6!M7 zdvYqX((W8Cw3)a7y<52QRV37YFo%qAFWX^h18LpdNpk@lq92!F^}$`ijBW|M7yu@) zF(s5peojP{kyth($p2M4SO0Sp9PQ* zpJf?ZryGRX!u@_tp_;%4(SyctA++h^=fvLDwPnP2a+uB%7O=&f$dL!Y{e@|@ym`6_ z%$Y8-qXsEvSWubzZczCs_z2Q7bm=d}b7-@?&OkgtgM2JtOL%z;J@j5EJmyU0%j5+x zzKfb#5+&)!8W54Y@hCT;{uCtVLsAXn-=l?lWXjb_&+?e7rbQ3-9OS^Ra8n^1U}9tE zhz~@4#X82+AorwHU9(m`yUPP5{`q_G=UibvMnp(n;a9luq`Ua^Gkf_0SQWxRq+K+U z;-Tn|?$yDb%~@E_mv!^iwvro%YQCM1BCjp;6@Zu~Jf-#`48R+1`tB^v4Z?`y=c|VE zG)&v3`kuleBz3gftcd7lP1ua*l#Gm2tmsYOrH~&q$cL+KZ%T7GGp>E7CIx`Qn!=V3 zgwEAVbuzD7RD}RF0~$`^R_gb;};wVDrM}Wgdh3pTPmZ&|`?(l;K$WPDklg8PH)l zIEJw#w0Fju1VYun@_3#83dTd+7|#5T_(-51sl(7yaxO#jGAsAY(_|=&a$fv;Y2*O1 zjG!9125BSQ{^8;x`hl$a7?P^`-IsXj*`&=dz1#w4|Fw^&lOxg^ zUP+pXIlin?y#|ti8UV0zKh~PT!}#0PGZHs+qb3L0sz>L0G?Cil5b6*>^``y%DLp$p zB&`5UT;`XG4(hG6T-jvErQ;a3i1zPg%4a&k@8Tf!G!x6>2XonJ6|hh`WZmj_sU|}_ zC~KXb8*0Ej6t)=$(nCvKy&6ytOc@G_2)(R=(65!PUJisw1YwM4+CUYXyZ{iQ@L9Yc z(6-o&a^i;Q@X2$)+0ojRv|WV_X-1Knwu>>-AC$lhK%1M15Sp|oan>~7``;$-ya(L# zCXhjrW`Lo%Qw3N9fo1(_DfI~;c69sD8=YEM_kzez6nSl^sn&&xRBn`^(&|bwSRqqlMe1 zc*&z)qKspzfk->b`iU<`o2&2OdqjiJ7|zVtAu|T;ujvr42Hh^TKc1(uWr(SvM%aZ( zDl}O_q|cpvmC^+o?|`A8-)u@_&rUAowSYRHfIZ+rzTf)~?P2 z@eqWpK*=FcPn@6iTgxsVu>?mHyd}ZM*3xK%W|Vs^%zS+$7k_HqIMJ|z=dSwBT`JANs2vW`gxem=f zBjESsB9;j^v7ESr7Rhp@IJV<_`hA}jUGiLYr4V(&E$HRd$rYc0bI!cx(2oqrPqpd%rrdbGpUG{~3|;|}$5<}1a*J}> zU%!<44NBrpJ*_j4UeBs%uze;n1ywFgP3JnSbxWe=S`*;b0BDZ=5V)A_mc(7<;!ls} za{q*rx1tKQru9C*#gBWm1Ed_vxIQmt4?v~-Jy2t_5coC4{aT@u_Fi!$j2|nKejd4v zSf(X+>>-;T^%=d>KVHg_NY6B+##40M1h|H=q|fT*#n)IgdDiqWqbBd?R(gW+O1LwAZ>G%M@-6R| zEr|h&#s`#s-xD}}91t8VGB1{mG$c5U?ru48mv&32Sh zO9M~|UXELmbRL&VpRG}mOaUJh3Yr#I(3>>J!Vezp^*NG~oxi=3&vtcYb{oJ<#J*Cb z#tSr-l}{z8k7avLdi=f%%>_T+wqKRZ@?7lwnlGndZI%?h{mx+IkWYNyXFN8amIy;7 zj{Ur0EBa0c2S8h+8&E;+0VE|j)ma<#4D<}S??}kSciK%d^>)f+|Gw~iuyx|QKf2e+ z_2%W zH>7LmpJjqzT%+G57NsAJf7~NlKT4K@Ig%$lnZzW~_=iobWj(1*qXzvxY zU~*iH7+Ae*rQ?}-4lwk##^RkZAXe!pbV(&Y&~^Q3+wsjyg~sD=QjZ?lU{&1JEXVq- zIl!t_H#o{ITyHwHiI%Te4QlBpal?()H}xHs!LnldjvdMKC+`Vu85ufNK~T1}?+*aV z7qp-G`wf-va8c{a5>K{iy)eTm(7-`xeVcB?GDy7u-lFKBYg*e=!^zRYbA-Ip&(^2} z0QMXMNS*X#6g}O^CC~K1W()}zxD==jME2YI*?!C*4`7aisyU;eLH_jj-=U*l2_Vrx z+_`89mw3zxK@Q`dvZ8&n#I!BZVt6P%egILAQO<~eu*;`W-(GhBx6Q1h+4e>GQ+*5P z4xR~yqBR|cXj`)hk*HN?V7tcSKBRY-!qG_7P4qD{1PI6~ytQ48x+%3`nYQG>2oMpe znE;cEc&HqbeaWr5H!tm+Ng%)}_5K}OvSy-F8Z<+TAd?M&Xj#Keet2opt^5$ z2_8AcloU2VYnLd%fU%88R?!)+l{!GaG-kg1TjPgehU7}%G0GgUwn`PJ2b)pZfE-_F zOoJqDLA4Ovg|Xr;fF=Mc z?mtCK(DF)oycakJ=w0tCg(nRIZ$A}{czyc~GKO;1bw7Jqc<}RK!qXE}&yKjeHV!*g z2(1K*@|kMyEnv$AkRU5fWQ1K7W(26p)i;FK1=pS0MnZ6*;ORvT(MKw)KD1Z^mh9QVlD0Mx_r+0@a_c^(dXjkK*3GeRU@$ z7ojz?AFT;al_vKR0p=(E*|-HCe-6c<>I#rHx%N=m ziv4l!%xrn6mGbm0uZ2}qoD;SS?U|j#Z2+pp-Ll|%nZ0NmtA{^`tell=H+qmc$gOZ2 zL1J}_=A~dAlS+Y5v9gdazfYQbPb>vpWIDdTbfiOA{&L0r0j?_o)I9AQJ!iZ4XQ^88 zR<19n9q3MPbcl7f5p2`pcEe&(*1>NDm<#&k&wXv!Xij;qE{n{{sC!=(?k3+NK`_xd zUzfJ3!0PHYq1Da@F9a*>t4Jtj2YfE&5dT%D9_2tx4Nyi9qih3gi++x-_VD^0MVnF# zZOTPO2@vH37cwF#!U+u~1>hAB7fDU*3HDnOi z#gmaZjwo+GKb#lINhFMMsEg$(kwgb{PlxpgYmiNN>@<$mme;=p+u?rC60MU)7+bagnnRT89ztqO-#9quRjfv8>$ zOeg!>XM50_^q+r!ibv;#NIC8_`~;D#5GqF{ji0H&#az%(*T zR0wCOl0DyDgE<{qD`G@kDIOwM3MKg-pjDex*+uGY4fXL#_71l zxrft?y&iudD|zsln?g8!^+obRsnMS?VeiuwDmd6_UaKG6Ay~0|6>l6@KNZ$k;5d=i zgCCIXvi=I2pXAJlNW*Ar3 zysol5mIi;!05Q8y`EA&_JpPP_N*u{gVg2A3t!0zhfA)J%qL2|$QI9Cx)TgDKyhG$# zQE%t0DGahjw^heATVy>>A@_q=kulRlq(>qYtvBg5jZgG#78G197vHX#QUHY(L`Wg^ zew}NE`P!d!tvI$QdAKa=if)S};EVYnffav!p>?;f3R~XvR)SdZjY=KP6;*B-xctT6 z-QaH|^hBa+aD}q>KK$`ZNK?@J_>udS>94jWVPk3r^YL$|pY5#q3PEdX=D)5tFi7lB z@e|{J7L;*sZczP78%Ok*SBH>rxsvKUfQg^|z5{Uo@)$}8jWJm)27f~6q~V6BpfsuV zy#w{(N4^$+N>|pz2Nn%UI1xbE(uGp*c0-FnKg4=i9sXfCbg&YJ+xevUn#DiK|6{w; z0<6RHGk@>8au#a>Q;^zqTe9)Utr+z^8Ru6$T4K#X>M#R+5*is5ge1lqhXSs$jHTOq zVC+UlUz*?Y-wvbAc*z?9=wqjo;<=xFVT9O?eAsZvL0k9EgSXg0k!pqPniiN1_T*skh-@jr z8W{S7^JSHIdmEYZdq$B>#J`-lGJ<==Ivg)fiLt-u8B432CoTK_V$Q{IsmV>Q0@nF`6_8#8@K2jSU|$&a}RUHWB% zzPkwZ{}Om_*`5!R9g zZ_9M8Yr&uFC&p$)@e3tC#PuFZU!e)dR#_6D?Msfjwz_D4m|lcO;jBoEuK5%p(&VkS z?y}gC|2(c|oBfgXUy(M3-GHNgmghKO$~rjU`RYG$iVnX+mQ#5v_=sd^2yY=&v|D!S zCHz~Sg86D#kNf`dUDFelMSuTcxU`rKvM|&m2e-gI!3PXz9ot38WC<_tc)@xbp8w^2 zuhrz?-`D$QiSrQ#deTa}fH|KCquL7=_<2@2O{zZzr*}0}0(oim>9tW&Ed5#YmF}C%S?%(%J5B9(cP&iz6^LZL0+lkS-<=~qrzr=~AwBH6^CQsp7-xWTZhUh6 zX-_Q+XvSH|h&&kSvR~T?6i|2pP9=k z{rfQa1?nYZ{gq9E4V$@A-?@%Q#x9a(b!zGa~y)1;8@mqMGJ7|EdvmpSSb!`$Prz&Cp z4L5KgGmX5Vt<6%a+V1@TqiCb5yib+?3{-+@Qw_)8{dpZ*4_EYF?lek`+lk08RQ16a zFR@is9iNrps6dQ2(giyEk`fHog)-+PDzA^$lpo;BaWd~75}-5hIp|T4MQBR1`!iB{ zL9fU6#JE7EFv7R51!xkmI);NH7!AGr+JO`8MD1Z3pl)+SnFlHl!92`_PWzg1t%YL+foLP{5VlU=NXnmVeO(is8wU2 zUJnfIN>!{0`%9W`h*76@KgJeE{3HmjaZP?sI`}u|1(J&#)S?J;DZzBFvn?ZXvS1yC z7QDEf{wfl(&xa-*ZcA6%7s=xixyj&Bz#xugXeXk%ja7 zQN^KqI2d5v$>r89Z383KS%^$HT<{&#Ru20Td@7S*8o$qz5!vV%aQ{>AZ0|=FKE;Eu zwvH>*?`7%=4#SRGUqa1>3hQ@10ufc z<-kKt(5*)Ieg;yKKixafL^;c@3vzCWT>9phdab#e9NTd3w%~mrQ~1NUpGHMXUsx@; zl}5RFZ31Grf!}`X5;m3Ecj>y`%I(cI!{P^gL#uO^}$_31>Z+ z`}NVKDS0V@+2VDw=x(*2zysx$;;inbLF=IOG9tr{Gz`FV>iA#Cca-en3a?z`u*RI= zLe2WGm1%OQ@c7$oBC8kKz6E;5g~Q8G2l3uG>>W*aAjS;@?mI^~0PYVf^$@g|HHP+S z7@8FAi;@^?_*TphkvaB$v+~tq>#P<6ZoXgDb=-vGG!wQpf@h7>%HTuiph_vNL4_EH z6SwUS9P@N8dZ)m|g-ZMo%e4y@^0PZ>e8njg|&UG77u}DYiacvx3pq9eHfacFKcl zL2ltgQSDJaLGhX>X#;v!Xv|CZ%Ga)P*juqK&`BPyp8yG>2Taj~n?$OQ<3~!9oI)5$ zPtQW56^xr1r13_pw37h1Dqy8bM)OLw?Lkv=TySM?2OO{AH zY7c`Y3BoT*9@gGxYo-KQ@j#*jcJgqMozgZNnUQ#%7zhyXvTb-w*AczjH$f+arwCXP z1v5%BbK5?>SDv53&3>U(qp|8Kha~|`+eAVQ05vCani&El%D9u>Cz!j$uy`S)*Vn8r zrSqIc3Mm1#$FL81VOsw;KZ%vj{YMW`M_)o>GmX~yg6a0Z2CxMzYgW+=C_EneA+qs z*Q$y7Mhg6VWGQMn;9B=rJJ#m_H4KGrSefE-T2eKz|@ zpA9XRdBq-GTH=6E1^CE0H74buAb(R>*+o_Q0Dg1T!k(Fl^*59A))lBm7w7M9IJWNc zN|^=)eeH(DLKQziV4@6E8TEoyZc2t7fjmWv^Kq%e)G0@kE)jkPWfcZuxNqGO97R| zhUjJLKuYcqwHtiK&{A_EZd2%{wd>)==yy71g4eNVrHK=grjENFO!<$^(`X325rU9^OK-CmT7$N8_Dug{>XEyssA9kr5RuJr0>pd(8ZPI=n<)Xdsg4hZ`Hi(727c+s4F4*@x$jGW z52^4Az#kJ1?`cp*7~ae|Nzh0Fypl>d5h#0V(x5CM6TUZ(c0h;k|3;eY$LJ+QC5ev343{ z{tB_ICXWLT03TNP3qsldJTO3-QE#pJUn%^XR|Li?0mFttQ%%4kNPrOl!}0Krw?Jnb zUiQjd5U47MZ4sznHAM2#zxv?~+}-Zb?{CAP2M!W{m33DEBpY1;C@-+pUzZERHEpu2 z4}AdncC#(JVQS-UQ*xwnOwR!iDSQZcFYs5ug-Empv(O4@kg-eb%~MecI7$<+3=&`j zz%ZXm!1gx$haZo286m1^Bmu?)3U{e!2>u>$X{bOtfvp|-1BGvS4q4T4umSHlMd*yI zL7P+#7%uTuOMfwNA%87ozfLdp(V}NFU_@xEwS1bD&QW z1bdIg#}mLa3Wp&EOQyBBHj;xwl>WTu#;^rYAOTZo0+vAni~tzsgI{_b9PE?4{8gX{ zzBh<%0jvEEZ@IYEOCSQ$74&is&YYopYKQ(453&)B{{q}t1whyM`c(v_%Cu3AjAbML z6W3}DLG3_7AnV&5Lkyp!2#16o{j+3y^8-y`uZm1go)o{>8 zFA|1*0x3vmO;jdl?c<`rs!Bl7C%`od*4^s7r~=+fo%l`vJE;QFY!yCp?|-E9ERVaX?7_l+4CNYzAy~Pc{q-h z04Y@hx;h8tXNd&p@O;s+cjqt|=82EIpY*T?sm4}-p?q%!WxNNs`QtOx0LY47jq$H_ zBs@!fhIb5eBQ5<=Ek=vc%yk}dXx$1eS(EHQg)1Se4=YMx=+Z(!A1L%x=xG?cUKqnX z=%^0000bbVXQnWMOn=I&E)c wX=Zr + /// A structure with appearance configurations for the software keyboard when running in inline mode. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)] + struct SoftwareKeyboardAppear + { + public const int OkTextLength = SoftwareKeyboardAppearEx.OkTextLength; + + public KeyboardMode KeyboardMode; + + /// + /// The string displayed in the Submit button. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = OkTextLength + 1)] + public string OkText; + + /// + /// The character displayed in the left button of the numeric keyboard. + /// + public char LeftOptionalSymbolKey; + + /// + /// The character displayed in the right button of the numeric keyboard. + /// + public char RightOptionalSymbolKey; + + /// + /// When set, predictive typing is enabled making use of the system dictionary, and any custom user dictionary. + /// + [MarshalAs(UnmanagedType.I1)] + public bool PredictionEnabled; + + /// + /// When set, there is only the option to accept the input. + /// + [MarshalAs(UnmanagedType.I1)] + public bool CancelButtonDisabled; + + /// + /// Specifies prohibited characters that cannot be input into the text entry area. + /// + public InvalidCharFlags InvalidChars; + + /// + /// Maximum text length allowed. + /// + public int TextMaxLength; + + /// + /// Minimum text length allowed. + /// + public int TextMinLength; + + /// + /// Indicates the return button is enabled in the keyboard. This allows for input with multiple lines. + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseNewLine; + + /// + /// [10.0.0+] If value is 1 or 2, then keytopAsFloating=0 and footerScalable=1 in Calc. + /// + public KeyboardMiniaturizationMode MiniaturizationMode; + + public byte Reserved1; + public byte Reserved2; + + /// + /// Bit field with invalid buttons for the keyboard. + /// + public InvalidButtonFlags InvalidButtons; + + [MarshalAs(UnmanagedType.I1)] + public bool UseSaveData; + + public uint Reserved3; + public ushort Reserved4; + public byte Reserved5; + public ulong Reserved6; + public ulong Reserved7; + + public SoftwareKeyboardAppearEx ToExtended() + { + SoftwareKeyboardAppearEx appear = new() + { + KeyboardMode = KeyboardMode, + OkText = OkText, + LeftOptionalSymbolKey = LeftOptionalSymbolKey, + RightOptionalSymbolKey = RightOptionalSymbolKey, + PredictionEnabled = PredictionEnabled, + CancelButtonDisabled = CancelButtonDisabled, + InvalidChars = InvalidChars, + TextMaxLength = TextMaxLength, + TextMinLength = TextMinLength, + UseNewLine = UseNewLine, + MiniaturizationMode = MiniaturizationMode, + Reserved1 = Reserved1, + Reserved2 = Reserved2, + InvalidButtons = InvalidButtons, + UseSaveData = UseSaveData, + Reserved3 = Reserved3, + Reserved4 = Reserved4, + Reserved5 = Reserved5, + Uid0 = Reserved6, + Uid1 = Reserved7, + SamplingNumber = 0, + Reserved6 = 0, + Reserved7 = 0, + Reserved8 = 0, + Reserved9 = 0, + }; + + return appear; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppearEx.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppearEx.cs new file mode 100644 index 00000000..31cb27e2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppearEx.cs @@ -0,0 +1,100 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// A structure with appearance configurations for the software keyboard when running in inline mode. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)] + struct SoftwareKeyboardAppearEx + { + public const int OkTextLength = 8; + + public KeyboardMode KeyboardMode; + + /// + /// The string displayed in the Submit button. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = OkTextLength + 1)] + public string OkText; + + /// + /// The character displayed in the left button of the numeric keyboard. + /// + public char LeftOptionalSymbolKey; + + /// + /// The character displayed in the right button of the numeric keyboard. + /// + public char RightOptionalSymbolKey; + + /// + /// When set, predictive typing is enabled making use of the system dictionary, and any custom user dictionary. + /// + [MarshalAs(UnmanagedType.I1)] + public bool PredictionEnabled; + + /// + /// When set, there is only the option to accept the input. + /// + [MarshalAs(UnmanagedType.I1)] + public bool CancelButtonDisabled; + + /// + /// Specifies prohibited characters that cannot be input into the text entry area. + /// + public InvalidCharFlags InvalidChars; + + /// + /// Maximum text length allowed. + /// + public int TextMaxLength; + + /// + /// Minimum text length allowed. + /// + public int TextMinLength; + + /// + /// Indicates the return button is enabled in the keyboard. This allows for input with multiple lines. + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseNewLine; + + /// + /// [10.0.0+] If value is 1 or 2, then keytopAsFloating=0 and footerScalable=1 in Calc. + /// + public KeyboardMiniaturizationMode MiniaturizationMode; + + public byte Reserved1; + public byte Reserved2; + + /// + /// Bit field with invalid buttons for the keyboard. + /// + public InvalidButtonFlags InvalidButtons; + + [MarshalAs(UnmanagedType.I1)] + public bool UseSaveData; + + public uint Reserved3; + public ushort Reserved4; + public byte Reserved5; + + /// + /// The id of the user associated with the appear request. + /// + public ulong Uid0; + public ulong Uid1; + + /// + /// The sampling number for the keyboard appearance. + /// + public ulong SamplingNumber; + + public ulong Reserved6; + public ulong Reserved7; + public ulong Reserved8; + public ulong Reserved9; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs new file mode 100644 index 00000000..0462a5b0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs @@ -0,0 +1,816 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; +using Ryujinx.HLE.UI; +using Ryujinx.HLE.UI.Input; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Applets +{ + internal class SoftwareKeyboardApplet : IApplet + { + private const string DefaultInputText = "Ryujinx"; + + private const int StandardBufferSize = 0x7D8; + private const int InteractiveBufferSize = 0x7D4; + private const int MaxUserWords = 0x1388; + private const int MaxUiTextSize = 100; + + private const Key CycleInputModesKey = Key.F6; + + private readonly Switch _device; + + private SoftwareKeyboardState _foregroundState = SoftwareKeyboardState.Uninitialized; + private volatile InlineKeyboardState _backgroundState = InlineKeyboardState.Uninitialized; + + private bool _isBackground = false; + + private AppletSession _normalSession; + private AppletSession _interactiveSession; + + // Configuration for foreground mode. + private SoftwareKeyboardConfig _keyboardForegroundConfig; + + // Configuration for background (inline) mode. +#pragma warning disable IDE0052 // Remove unread private member + private SoftwareKeyboardInitialize _keyboardBackgroundInitialize; + private SoftwareKeyboardCustomizeDic _keyboardBackgroundDic; + private SoftwareKeyboardDictSet _keyboardBackgroundDictSet; +#pragma warning restore IDE0052 + private SoftwareKeyboardUserWord[] _keyboardBackgroundUserWords; + + private byte[] _transferMemory; + + private string _textValue = ""; + private int _cursorBegin = 0; + private Encoding _encoding = Encoding.Unicode; + private KeyboardResult _lastResult = KeyboardResult.NotSet; + + private IDynamicTextInputHandler _dynamicTextInputHandler = null; + private SoftwareKeyboardRenderer _keyboardRenderer = null; + private NpadReader _npads = null; + private bool _canAcceptController = false; + private KeyboardInputMode _inputMode = KeyboardInputMode.ControllerAndKeyboard; + + private readonly object _lock = new(); + + public event EventHandler AppletStateChanged; + + public SoftwareKeyboardApplet(Horizon system) + { + _device = system.Device; + } + + public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession) + { + lock (_lock) + { + _normalSession = normalSession; + _interactiveSession = interactiveSession; + + _interactiveSession.DataAvailable += OnInteractiveData; + + var launchParams = _normalSession.Pop(); + var keyboardConfig = _normalSession.Pop(); + + _isBackground = keyboardConfig.Length == Unsafe.SizeOf(); + + if (_isBackground) + { + // Initialize the keyboard applet in background mode. + + _keyboardBackgroundInitialize = MemoryMarshal.Read(keyboardConfig); + _backgroundState = InlineKeyboardState.Uninitialized; + + if (_device.UIHandler == null) + { + Logger.Error?.Print(LogClass.ServiceAm, "GUI Handler is not set, software keyboard applet will not work properly"); + } + else + { + // Create a text handler that converts keyboard strokes to strings. + _dynamicTextInputHandler = _device.UIHandler.CreateDynamicTextInputHandler(); + _dynamicTextInputHandler.TextChangedEvent += HandleTextChangedEvent; + _dynamicTextInputHandler.KeyPressedEvent += HandleKeyPressedEvent; + + _npads = new NpadReader(_device); + _npads.NpadButtonDownEvent += HandleNpadButtonDownEvent; + _npads.NpadButtonUpEvent += HandleNpadButtonUpEvent; + + _keyboardRenderer = new SoftwareKeyboardRenderer(_device.UIHandler.HostUITheme); + } + + return ResultCode.Success; + } + else + { + // Initialize the keyboard applet in foreground mode. + + if (keyboardConfig.Length < Marshal.SizeOf()) + { + Logger.Error?.Print(LogClass.ServiceAm, $"SoftwareKeyboardConfig size mismatch. Expected {Marshal.SizeOf():x}. Got {keyboardConfig.Length:x}"); + } + else + { + _keyboardForegroundConfig = ReadStruct(keyboardConfig); + } + + if (!_normalSession.TryPop(out _transferMemory)) + { + Logger.Error?.Print(LogClass.ServiceAm, "SwKbd Transfer Memory is null"); + } + + if (_keyboardForegroundConfig.UseUtf8) + { + _encoding = Encoding.UTF8; + } + + _foregroundState = SoftwareKeyboardState.Ready; + + ExecuteForegroundKeyboard(); + + return ResultCode.Success; + } + } + } + + public ResultCode GetResult() + { + return ResultCode.Success; + } + + private bool IsKeyboardActive() + { + return _backgroundState >= InlineKeyboardState.Appearing && _backgroundState < InlineKeyboardState.Disappearing; + } + + private bool InputModeControllerEnabled() + { + return _inputMode == KeyboardInputMode.ControllerAndKeyboard || + _inputMode == KeyboardInputMode.ControllerOnly; + } + + private bool InputModeTypingEnabled() + { + return _inputMode == KeyboardInputMode.ControllerAndKeyboard || + _inputMode == KeyboardInputMode.KeyboardOnly; + } + + private void AdvanceInputMode() + { + _inputMode = (KeyboardInputMode)((int)(_inputMode + 1) % (int)KeyboardInputMode.Count); + } + + public bool DrawTo(RenderingSurfaceInfo surfaceInfo, IVirtualMemoryManager destination, ulong position) + { + _npads?.Update(); + + _keyboardRenderer?.SetSurfaceInfo(surfaceInfo); + + return _keyboardRenderer?.DrawTo(destination, position) ?? false; + } + + private void ExecuteForegroundKeyboard() + { + string initialText = null; + + // Initial Text is always encoded as a UTF-16 string in the work buffer (passed as transfer memory) + // InitialStringOffset points to the memory offset and InitialStringLength is the number of UTF-16 characters + if (_transferMemory != null && _keyboardForegroundConfig.InitialStringLength > 0) + { + initialText = Encoding.Unicode.GetString(_transferMemory, _keyboardForegroundConfig.InitialStringOffset, + 2 * _keyboardForegroundConfig.InitialStringLength); + } + + // If the max string length is 0, we set it to a large default + // length. + if (_keyboardForegroundConfig.StringLengthMax == 0) + { + _keyboardForegroundConfig.StringLengthMax = 100; + } + + if (_device.UIHandler == null) + { + Logger.Warning?.Print(LogClass.Application, "GUI Handler is not set. Falling back to default"); + + _textValue = DefaultInputText; + _lastResult = KeyboardResult.Accept; + } + else + { + // Call the configured GUI handler to get user's input. + var args = new SoftwareKeyboardUIArgs + { + KeyboardMode = _keyboardForegroundConfig.Mode, + HeaderText = StripUnicodeControlCodes(_keyboardForegroundConfig.HeaderText), + SubtitleText = StripUnicodeControlCodes(_keyboardForegroundConfig.SubtitleText), + GuideText = StripUnicodeControlCodes(_keyboardForegroundConfig.GuideText), + SubmitText = (!string.IsNullOrWhiteSpace(_keyboardForegroundConfig.SubmitText) ? + _keyboardForegroundConfig.SubmitText : "OK"), + StringLengthMin = _keyboardForegroundConfig.StringLengthMin, + StringLengthMax = _keyboardForegroundConfig.StringLengthMax, + InitialText = initialText, + }; + + _lastResult = _device.UIHandler.DisplayInputDialog(args, out _textValue) ? KeyboardResult.Accept : KeyboardResult.Cancel; + _textValue ??= initialText ?? DefaultInputText; + } + + // If the game requests a string with a minimum length less + // than our default text, repeat our default text until we meet + // the minimum length requirement. + // This should always be done before the text truncation step. + while (_textValue.Length < _keyboardForegroundConfig.StringLengthMin) + { + _textValue = String.Join(" ", _textValue, _textValue); + } + + // If our default text is longer than the allowed length, + // we truncate it. + if (_textValue.Length > _keyboardForegroundConfig.StringLengthMax) + { + _textValue = _textValue[.._keyboardForegroundConfig.StringLengthMax]; + } + + // Does the application want to validate the text itself? + if (_keyboardForegroundConfig.CheckText) + { + // The application needs to validate the response, so we + // submit it to the interactive output buffer, and poll it + // for validation. Once validated, the application will submit + // back a validation status, which is handled in OnInteractiveDataPushIn. + _foregroundState = SoftwareKeyboardState.ValidationPending; + + PushForegroundResponse(true); + } + else + { + // If the application doesn't need to validate the response, + // we push the data to the non-interactive output buffer + // and poll it for completion. + _foregroundState = SoftwareKeyboardState.Complete; + + PushForegroundResponse(false); + + AppletStateChanged?.Invoke(this, null); + } + } + + private void OnInteractiveData(object sender, EventArgs e) + { + // Obtain the validation status response. + var data = _interactiveSession.Pop(); + + if (_isBackground) + { + lock (_lock) + { + OnBackgroundInteractiveData(data); + } + } + else + { + OnForegroundInteractiveData(data); + } + } + + private void OnForegroundInteractiveData(byte[] data) + { + if (_foregroundState == SoftwareKeyboardState.ValidationPending) + { + // TODO(jduncantor): + // If application rejects our "attempt", submit another attempt, + // and put the applet back in PendingValidation state. + + // For now we assume success, so we push the final result + // to the standard output buffer and carry on our merry way. + PushForegroundResponse(false); + + AppletStateChanged?.Invoke(this, null); + + _foregroundState = SoftwareKeyboardState.Complete; + } + else if (_foregroundState == SoftwareKeyboardState.Complete) + { + // If we have already completed, we push the result text + // back on the output buffer and poll the application. + PushForegroundResponse(false); + + AppletStateChanged?.Invoke(this, null); + } + else + { + // We shouldn't be able to get here through standard swkbd execution. + throw new InvalidOperationException("Software Keyboard is in an invalid state."); + } + } + + private void OnBackgroundInteractiveData(byte[] data) + { + // WARNING: Only invoke applet state changes after an explicit finalization + // request from the game, this is because the inline keyboard is expected to + // keep running in the background sending data by itself. + + using MemoryStream stream = new(data); + using BinaryReader reader = new(stream); + + var request = (InlineKeyboardRequest)reader.ReadUInt32(); + + long remaining; + + Logger.Debug?.Print(LogClass.ServiceAm, $"Keyboard received command {request} in state {_backgroundState}"); + + switch (request) + { + case InlineKeyboardRequest.UseChangedStringV2: + Logger.Stub?.Print(LogClass.ServiceAm, "Inline keyboard request UseChangedStringV2"); + break; + case InlineKeyboardRequest.UseMovedCursorV2: + Logger.Stub?.Print(LogClass.ServiceAm, "Inline keyboard request UseMovedCursorV2"); + break; + case InlineKeyboardRequest.SetUserWordInfo: + // Read the user word info data. + remaining = stream.Length - stream.Position; + if (remaining < sizeof(int)) + { + Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard User Word Info of {remaining} bytes"); + } + else + { + int wordsCount = reader.ReadInt32(); + int wordSize = Unsafe.SizeOf(); + remaining = stream.Length - stream.Position; + + if (wordsCount > MaxUserWords) + { + Logger.Warning?.Print(LogClass.ServiceAm, $"Received {wordsCount} User Words but the maximum is {MaxUserWords}"); + } + else if (wordsCount * wordSize != remaining) + { + Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard User Word Info data of {remaining} bytes for {wordsCount} words"); + } + else + { + _keyboardBackgroundUserWords = new SoftwareKeyboardUserWord[wordsCount]; + + for (int word = 0; word < wordsCount; word++) + { + _keyboardBackgroundUserWords[word] = reader.ReadStruct(); + } + } + } + _interactiveSession.Push(InlineResponses.ReleasedUserWordInfo(_backgroundState)); + break; + case InlineKeyboardRequest.SetCustomizeDic: + // Read the custom dic data. + remaining = stream.Length - stream.Position; + if (remaining != Unsafe.SizeOf()) + { + Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard Customize Dic of {remaining} bytes"); + } + else + { + _keyboardBackgroundDic = reader.ReadStruct(); + } + break; + case InlineKeyboardRequest.SetCustomizedDictionaries: + // Read the custom dictionaries data. + remaining = stream.Length - stream.Position; + if (remaining != Unsafe.SizeOf()) + { + Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard DictSet of {remaining} bytes"); + } + else + { + _keyboardBackgroundDictSet = reader.ReadStruct(); + } + break; + case InlineKeyboardRequest.Calc: + // The Calc request is used to communicate configuration changes and commands to the keyboard. + // Fields in the Calc struct and operations are masked by the Flags field. + + // Read the Calc data. + SoftwareKeyboardCalcEx newCalc; + remaining = stream.Length - stream.Position; + if (remaining == Marshal.SizeOf()) + { + var keyboardCalcData = reader.ReadBytes((int)remaining); + var keyboardCalc = ReadStruct(keyboardCalcData); + + newCalc = keyboardCalc.ToExtended(); + } + else if (remaining == Marshal.SizeOf() || remaining == SoftwareKeyboardCalcEx.AlternativeSize) + { + var keyboardCalcData = reader.ReadBytes((int)remaining); + + newCalc = ReadStruct(keyboardCalcData); + } + else + { + Logger.Error?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard Calc of {remaining} bytes"); + + newCalc = new SoftwareKeyboardCalcEx(); + } + + // Process each individual operation specified in the flags. + + bool updateText = false; + + if ((newCalc.Flags & KeyboardCalcFlags.Initialize) != 0) + { + _interactiveSession.Push(InlineResponses.FinishedInitialize(_backgroundState)); + + _backgroundState = InlineKeyboardState.Initialized; + } + + if ((newCalc.Flags & KeyboardCalcFlags.SetCursorPos) != 0) + { + _cursorBegin = newCalc.CursorPos; + updateText = true; + + Logger.Debug?.Print(LogClass.ServiceAm, $"Cursor position set to {_cursorBegin}"); + } + + if ((newCalc.Flags & KeyboardCalcFlags.SetInputText) != 0) + { + _textValue = newCalc.InputText; + updateText = true; + + Logger.Debug?.Print(LogClass.ServiceAm, $"Input text set to {_textValue}"); + } + + if ((newCalc.Flags & KeyboardCalcFlags.SetUtf8Mode) != 0) + { + _encoding = newCalc.UseUtf8 ? Encoding.UTF8 : Encoding.Default; + + Logger.Debug?.Print(LogClass.ServiceAm, $"Encoding set to {_encoding}"); + } + + if (updateText) + { + _dynamicTextInputHandler.SetText(_textValue, _cursorBegin); + _keyboardRenderer.UpdateTextState(_textValue, _cursorBegin, _cursorBegin, null, null); + } + + if ((newCalc.Flags & KeyboardCalcFlags.MustShow) != 0) + { + ActivateFrontend(); + + _backgroundState = InlineKeyboardState.Shown; + + PushChangedString(_textValue, (uint)_cursorBegin, _backgroundState); + } + + // Send the response to the Calc + _interactiveSession.Push(InlineResponses.Default(_backgroundState)); + break; + case InlineKeyboardRequest.Finalize: + // Destroy the frontend. + DestroyFrontend(); + // The calling application wants to close the keyboard applet and will wait for a state change. + _backgroundState = InlineKeyboardState.Uninitialized; + AppletStateChanged?.Invoke(this, null); + break; + default: + // We shouldn't be able to get here through standard swkbd execution. + Logger.Warning?.Print(LogClass.ServiceAm, $"Invalid Software Keyboard request {request} during state {_backgroundState}"); + _interactiveSession.Push(InlineResponses.Default(_backgroundState)); + break; + } + } + + private void ActivateFrontend() + { + Logger.Debug?.Print(LogClass.ServiceAm, "Activating software keyboard frontend"); + + _inputMode = KeyboardInputMode.ControllerAndKeyboard; + + _npads.Update(true); + + NpadButton buttons = _npads.GetCurrentButtonsOfAllNpads(); + + // Block the input if the current accept key is pressed so the applet won't be instantly closed. + _canAcceptController = (buttons & NpadButton.A) == 0; + + _dynamicTextInputHandler.TextProcessingEnabled = true; + + _keyboardRenderer.UpdateCommandState(null, null, true); + _keyboardRenderer.UpdateTextState(null, null, null, null, true); + } + + private void DeactivateFrontend() + { + Logger.Debug?.Print(LogClass.ServiceAm, "Deactivating software keyboard frontend"); + + _inputMode = KeyboardInputMode.ControllerAndKeyboard; + _canAcceptController = false; + + _dynamicTextInputHandler.TextProcessingEnabled = false; + _dynamicTextInputHandler.SetText(_textValue, _cursorBegin); + } + + private void DestroyFrontend() + { + Logger.Debug?.Print(LogClass.ServiceAm, "Destroying software keyboard frontend"); + + _keyboardRenderer?.Dispose(); + _keyboardRenderer = null; + + if (_dynamicTextInputHandler != null) + { + _dynamicTextInputHandler.TextChangedEvent -= HandleTextChangedEvent; + _dynamicTextInputHandler.KeyPressedEvent -= HandleKeyPressedEvent; + _dynamicTextInputHandler.Dispose(); + _dynamicTextInputHandler = null; + } + + if (_npads != null) + { + _npads.NpadButtonDownEvent -= HandleNpadButtonDownEvent; + _npads.NpadButtonUpEvent -= HandleNpadButtonUpEvent; + _npads = null; + } + } + + private bool HandleKeyPressedEvent(Key key) + { + if (key == CycleInputModesKey) + { + lock (_lock) + { + if (IsKeyboardActive()) + { + AdvanceInputMode(); + + bool typingEnabled = InputModeTypingEnabled(); + bool controllerEnabled = InputModeControllerEnabled(); + + _dynamicTextInputHandler.TextProcessingEnabled = typingEnabled; + + _keyboardRenderer.UpdateTextState(null, null, null, null, typingEnabled); + _keyboardRenderer.UpdateCommandState(null, null, controllerEnabled); + } + } + } + + return true; + } + + private void HandleTextChangedEvent(string text, int cursorBegin, int cursorEnd, bool overwriteMode) + { + lock (_lock) + { + // Text processing should not run with typing disabled. + Debug.Assert(InputModeTypingEnabled()); + + if (text.Length > MaxUiTextSize) + { + // Limit the text size and change it back. + text = text[..MaxUiTextSize]; + cursorBegin = Math.Min(cursorBegin, MaxUiTextSize); + cursorEnd = Math.Min(cursorEnd, MaxUiTextSize); + + _dynamicTextInputHandler.SetText(text, cursorBegin, cursorEnd); + } + + _textValue = text; + _cursorBegin = cursorBegin; + _keyboardRenderer.UpdateTextState(text, cursorBegin, cursorEnd, overwriteMode, null); + + PushUpdatedState(text, cursorBegin, KeyboardResult.NotSet); + } + } + + private void HandleNpadButtonDownEvent(int npadIndex, NpadButton button) + { + lock (_lock) + { + if (!IsKeyboardActive()) + { + return; + } + + switch (button) + { + case NpadButton.A: + _keyboardRenderer.UpdateCommandState(_canAcceptController, null, null); + break; + case NpadButton.B: + _keyboardRenderer.UpdateCommandState(null, _canAcceptController, null); + break; + } + } + } + + private void HandleNpadButtonUpEvent(int npadIndex, NpadButton button) + { + lock (_lock) + { + KeyboardResult result = KeyboardResult.NotSet; + + switch (button) + { + case NpadButton.A: + result = KeyboardResult.Accept; + _keyboardRenderer.UpdateCommandState(false, null, null); + break; + case NpadButton.B: + result = KeyboardResult.Cancel; + _keyboardRenderer.UpdateCommandState(null, false, null); + break; + } + + if (IsKeyboardActive()) + { + if (!_canAcceptController) + { + _canAcceptController = true; + } + else if (InputModeControllerEnabled()) + { + PushUpdatedState(_textValue, _cursorBegin, result); + } + } + } + } + + private void PushUpdatedState(string text, int cursorBegin, KeyboardResult result) + { + _lastResult = result; + _textValue = text; + + bool cancel = result == KeyboardResult.Cancel; + bool accept = result == KeyboardResult.Accept; + + if (!IsKeyboardActive()) + { + // Keyboard is not active. + + return; + } + + if (accept == false && cancel == false) + { + Logger.Debug?.Print(LogClass.ServiceAm, $"Updating keyboard text to {text} and cursor position to {cursorBegin}"); + + PushChangedString(text, (uint)cursorBegin, _backgroundState); + } + else + { + // Disable the frontend. + DeactivateFrontend(); + + // The 'Complete' state indicates the Calc request has been fulfilled by the applet. + _backgroundState = InlineKeyboardState.Disappearing; + + if (accept) + { + Logger.Debug?.Print(LogClass.ServiceAm, $"Sending keyboard OK with text {text}"); + + DecidedEnter(text, _backgroundState); + } + else if (cancel) + { + Logger.Debug?.Print(LogClass.ServiceAm, "Sending keyboard Cancel"); + + DecidedCancel(_backgroundState); + } + + _interactiveSession.Push(InlineResponses.Default(_backgroundState)); + + Logger.Debug?.Print(LogClass.ServiceAm, $"Resetting state of the keyboard to {_backgroundState}"); + + // Set the state of the applet to 'Initialized' as it is the only known state so far + // that does not soft-lock the keyboard after use. + + _backgroundState = InlineKeyboardState.Initialized; + + _interactiveSession.Push(InlineResponses.Default(_backgroundState)); + } + } + + private void PushChangedString(string text, uint cursor, InlineKeyboardState state) + { + // TODO (Caian): The *V2 methods are not supported because the applications that request + // them do not seem to accept them. The regular methods seem to work just fine in all cases. + + if (_encoding == Encoding.UTF8) + { + _interactiveSession.Push(InlineResponses.ChangedStringUtf8(text, cursor, state)); + } + else + { + _interactiveSession.Push(InlineResponses.ChangedString(text, cursor, state)); + } + } + + private void DecidedEnter(string text, InlineKeyboardState state) + { + if (_encoding == Encoding.UTF8) + { + _interactiveSession.Push(InlineResponses.DecidedEnterUtf8(text, state)); + } + else + { + _interactiveSession.Push(InlineResponses.DecidedEnter(text, state)); + } + } + + private void DecidedCancel(InlineKeyboardState state) + { + _interactiveSession.Push(InlineResponses.DecidedCancel(state)); + } + + private void PushForegroundResponse(bool interactive) + { + int bufferSize = interactive ? InteractiveBufferSize : StandardBufferSize; + + using MemoryStream stream = new(new byte[bufferSize]); + using BinaryWriter writer = new(stream); + byte[] output = _encoding.GetBytes(_textValue); + + if (!interactive) + { + // Result Code. + writer.Write(_lastResult == KeyboardResult.Accept ? 0U : 1U); + } + else + { + // In interactive mode, we write the length of the text as a long, rather than + // a result code. This field is inclusive of the 64-bit size. + writer.Write((long)output.Length + 8); + } + + writer.Write(output); + + if (!interactive) + { + _normalSession.Push(stream.ToArray()); + } + else + { + _interactiveSession.Push(stream.ToArray()); + } + } + + /// + /// Removes all Unicode control code characters from the input string. + /// This includes CR/LF, tabs, null characters, escape characters, + /// and special control codes which are used for formatting by the real keyboard applet. + /// + /// + /// Some games send special control codes (such as 0x13 "Device Control 3") as part of the string. + /// Future implementations of the emulated keyboard applet will need to handle these as well. + /// + /// The input string to sanitize (may be null). + /// The sanitized string. + internal static string StripUnicodeControlCodes(string input) + { + if (input is null) + { + return null; + } + + if (input.Length == 0) + { + return string.Empty; + } + + StringBuilder sb = new(capacity: input.Length); + foreach (char c in input) + { + if (!char.IsControl(c)) + { + sb.Append(c); + } + } + + return sb.ToString(); + } + + private static T ReadStruct<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>(byte[] data) + where T : struct + { + GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); + + try + { + return Marshal.PtrToStructure(handle.AddrOfPinnedObject()); + } + finally + { + handle.Free(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalc.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalc.cs new file mode 100644 index 00000000..cb02f8b4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalc.cs @@ -0,0 +1,221 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// A structure with configuration options of the software keyboard when starting a new input request in inline mode. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)] + struct SoftwareKeyboardCalc + { + public const int InputTextLength = SoftwareKeyboardCalcEx.InputTextLength; + + public uint Unknown; + + /// + /// The size of the Calc struct, as reported by the process communicating with the applet. + /// + public ushort Size; + + public byte Unknown1; + public byte Unknown2; + + /// + /// Configuration flags. Each bit in the bitfield enabled a different operation of the keyboard + /// using the data provided with the Calc structure. + /// + public KeyboardCalcFlags Flags; + + /// + /// The original parameters used when initializing the keyboard applet. + /// Flag: 0x1 + /// + public SoftwareKeyboardInitialize Initialize; + + /// + /// The audio volume used by the sound effects of the keyboard. + /// Flag: 0x2 + /// + public float Volume; + + /// + /// The initial position of the text cursor (caret) in the provided input text. + /// Flag: 0x10 + /// + public int CursorPos; + + /// + /// Appearance configurations for the on-screen keyboard. + /// + public SoftwareKeyboardAppear Appear; + + /// + /// The initial input text to be used by the software keyboard. + /// Flag: 0x8 + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = InputTextLength + 1)] + public string InputText; + + /// + /// When set, the strings communicated by software keyboard will be encoded as UTF-8 instead of UTF-16. + /// Flag: 0x20 + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseUtf8; + + public byte Unknown3; + + /// + /// [5.0.0+] Enable the backspace key in the software keyboard. + /// Flag: 0x8000 + /// + [MarshalAs(UnmanagedType.I1)] + public bool BackspaceEnabled; + + public short Unknown4; + public byte Unknown5; + + /// + /// Flag: 0x200 + /// + [MarshalAs(UnmanagedType.I1)] + public bool KeytopAsFloating; + + /// + /// Flag: 0x100 + /// + [MarshalAs(UnmanagedType.I1)] + public bool FooterScalable; + + /// + /// Flag: 0x100 + /// + [MarshalAs(UnmanagedType.I1)] + public bool AlphaEnabledInInputMode; + + /// + /// Flag: 0x100 + /// + public byte InputModeFadeType; + + /// + /// When set, the software keyboard ignores touch input. + /// Flag: 0x200 + /// + [MarshalAs(UnmanagedType.I1)] + public bool TouchDisabled; + + /// + /// When set, the software keyboard ignores hardware keyboard commands. + /// Flag: 0x800 + /// + [MarshalAs(UnmanagedType.I1)] + public bool HardwareKeyboardDisabled; + + public uint Unknown6; + public uint Unknown7; + + /// + /// Default value is 1.0. + /// Flag: 0x200 + /// + public float KeytopScale0; + + /// + /// Default value is 1.0. + /// Flag: 0x200 + /// + public float KeytopScale1; + + public float KeytopTranslate0; + public float KeytopTranslate1; + + /// + /// Default value is 1.0. + /// Flag: 0x100 + /// + public float KeytopBgAlpha; + + /// + /// Default value is 1.0. + /// Flag: 0x100 + /// + public float FooterBgAlpha; + + /// + /// Default value is 1.0. + /// Flag: 0x200 + /// + public float BalloonScale; + + public float Unknown8; + public uint Unknown9; + public uint Unknown10; + public uint Unknown11; + + /// + /// [5.0.0+] Enable sound effect. + /// Flag: Enable: 0x2000 + /// Disable: 0x4000 + /// + public byte SeGroup; + + /// + /// [6.0.0+] Enables the Trigger field when Trigger is non-zero. + /// + public byte TriggerFlag; + + /// + /// [6.0.0+] Always set to zero. + /// + public byte Trigger; + + public byte Padding; + + public SoftwareKeyboardCalcEx ToExtended() + { + SoftwareKeyboardCalcEx calc = new() + { + Unknown = Unknown, + Size = Size, + Unknown1 = Unknown1, + Unknown2 = Unknown2, + Flags = Flags, + Initialize = Initialize, + Volume = Volume, + CursorPos = CursorPos, + Appear = Appear.ToExtended(), + InputText = InputText, + UseUtf8 = UseUtf8, + Unknown3 = Unknown3, + BackspaceEnabled = BackspaceEnabled, + Unknown4 = Unknown4, + Unknown5 = Unknown5, + KeytopAsFloating = KeytopAsFloating, + FooterScalable = FooterScalable, + AlphaEnabledInInputMode = AlphaEnabledInInputMode, + InputModeFadeType = InputModeFadeType, + TouchDisabled = TouchDisabled, + HardwareKeyboardDisabled = HardwareKeyboardDisabled, + Unknown6 = Unknown6, + Unknown7 = Unknown7, + KeytopScale0 = KeytopScale0, + KeytopScale1 = KeytopScale1, + KeytopTranslate0 = KeytopTranslate0, + KeytopTranslate1 = KeytopTranslate1, + KeytopBgAlpha = KeytopBgAlpha, + FooterBgAlpha = FooterBgAlpha, + BalloonScale = BalloonScale, + Unknown8 = Unknown8, + Unknown9 = Unknown9, + Unknown10 = Unknown10, + Unknown11 = Unknown11, + SeGroup = SeGroup, + TriggerFlag = TriggerFlag, + Trigger = Trigger, + }; + + return calc; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalcEx.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalcEx.cs new file mode 100644 index 00000000..abc3950c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalcEx.cs @@ -0,0 +1,182 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// A structure with configuration options of the software keyboard when starting a new input request in inline mode. + /// This is the extended version of the structure with extended appear options. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Unicode)] + struct SoftwareKeyboardCalcEx + { + /// + /// This struct was built following Switchbrew's specs, but this size (larger) is also found in real games. + /// It's assumed that this is padding at the end of this struct, because all members seem OK. + /// + public const int AlternativeSize = 1256; + + public const int InputTextLength = 505; + + public uint Unknown; + + /// + /// The size of the Calc struct, as reported by the process communicating with the applet. + /// + public ushort Size; + + public byte Unknown1; + public byte Unknown2; + + /// + /// Configuration flags. Each bit in the bitfield enabled a different operation of the keyboard + /// using the data provided with the Calc structure. + /// + public KeyboardCalcFlags Flags; + + /// + /// The original parameters used when initializing the keyboard applet. + /// Flag: 0x1 + /// + public SoftwareKeyboardInitialize Initialize; + + /// + /// The audio volume used by the sound effects of the keyboard. + /// Flag: 0x2 + /// + public float Volume; + + /// + /// The initial position of the text cursor (caret) in the provided input text. + /// Flag: 0x10 + /// + public int CursorPos; + + /// + /// Appearance configurations for the on-screen keyboard. + /// + public SoftwareKeyboardAppearEx Appear; + + /// + /// The initial input text to be used by the software keyboard. + /// Flag: 0x8 + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = InputTextLength + 1)] + public string InputText; + + /// + /// When set, the strings communicated by software keyboard will be encoded as UTF-8 instead of UTF-16. + /// Flag: 0x20 + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseUtf8; + + public byte Unknown3; + + /// + /// [5.0.0+] Enable the backspace key in the software keyboard. + /// Flag: 0x8000 + /// + [MarshalAs(UnmanagedType.I1)] + public bool BackspaceEnabled; + + public short Unknown4; + public byte Unknown5; + + /// + /// Flag: 0x200 + /// + [MarshalAs(UnmanagedType.I1)] + public bool KeytopAsFloating; + + /// + /// Flag: 0x100 + /// + [MarshalAs(UnmanagedType.I1)] + public bool FooterScalable; + + /// + /// Flag: 0x100 + /// + [MarshalAs(UnmanagedType.I1)] + public bool AlphaEnabledInInputMode; + + /// + /// Flag: 0x100 + /// + public byte InputModeFadeType; + + /// + /// When set, the software keyboard ignores touch input. + /// Flag: 0x200 + /// + [MarshalAs(UnmanagedType.I1)] + public bool TouchDisabled; + + /// + /// When set, the software keyboard ignores hardware keyboard commands. + /// Flag: 0x800 + /// + [MarshalAs(UnmanagedType.I1)] + public bool HardwareKeyboardDisabled; + + public uint Unknown6; + public uint Unknown7; + + /// + /// Default value is 1.0. + /// Flag: 0x200 + /// + public float KeytopScale0; + + /// + /// Default value is 1.0. + /// Flag: 0x200 + /// + public float KeytopScale1; + + public float KeytopTranslate0; + public float KeytopTranslate1; + + /// + /// Default value is 1.0. + /// Flag: 0x100 + /// + public float KeytopBgAlpha; + + /// + /// Default value is 1.0. + /// Flag: 0x100 + /// + public float FooterBgAlpha; + + /// + /// Default value is 1.0. + /// Flag: 0x200 + /// + public float BalloonScale; + + public float Unknown8; + public uint Unknown9; + public uint Unknown10; + public uint Unknown11; + + /// + /// [5.0.0+] Enable sound effect. + /// Flag: Enable: 0x2000 + /// Disable: 0x4000 + /// + public byte SeGroup; + + /// + /// [6.0.0+] Enables the Trigger field when Trigger is non-zero. + /// + public byte TriggerFlag; + + /// + /// [6.0.0+] Always set to zero. + /// + public byte Trigger; + + public byte Padding; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs new file mode 100644 index 00000000..5261bc8f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs @@ -0,0 +1,138 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// A structure that defines the configuration options of the software keyboard. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + struct SoftwareKeyboardConfig + { + private const int SubmitTextLength = 8; + private const int HeaderTextLength = 64; + private const int SubtitleTextLength = 128; + private const int GuideTextLength = 256; + + /// + /// Type of keyboard. + /// + public KeyboardMode Mode; + + /// + /// The string displayed in the Submit button. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SubmitTextLength + 1)] + public string SubmitText; + + /// + /// The character displayed in the left button of the numeric keyboard. + /// This is ignored when Mode is not set to NumbersOnly. + /// + public char LeftOptionalSymbolKey; + + /// + /// The character displayed in the right button of the numeric keyboard. + /// This is ignored when Mode is not set to NumbersOnly. + /// + public char RightOptionalSymbolKey; + + /// + /// When set, predictive typing is enabled making use of the system dictionary, + /// and any custom user dictionary. + /// + [MarshalAs(UnmanagedType.I1)] + public bool PredictionEnabled; + + /// + /// Specifies prohibited characters that cannot be input into the text entry area. + /// + public InvalidCharFlags InvalidCharFlag; + + /// + /// The initial position of the text cursor displayed in the text entry area. + /// + public InitialCursorPosition InitialCursorPosition; + + /// + /// The string displayed in the header area of the keyboard. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = HeaderTextLength + 1)] + public string HeaderText; + + /// + /// The string displayed in the subtitle area of the keyboard. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SubtitleTextLength + 1)] + public string SubtitleText; + + /// + /// The placeholder string displayed in the text entry area when no text is entered. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = GuideTextLength + 1)] + public string GuideText; + + /// + /// When non-zero, specifies the maximum allowed length of the string entered into the text entry area. + /// + public int StringLengthMax; + + /// + /// When non-zero, specifies the minimum allowed length of the string entered into the text entry area. + /// + public int StringLengthMin; + + /// + /// When enabled, hides input characters as dots in the text entry area. + /// + public PasswordMode PasswordMode; + + /// + /// Specifies whether the text entry area is displayed as a single-line entry, or a multi-line entry field. + /// + public InputFormMode InputFormMode; + + /// + /// When set, enables or disables the return key. This value is ignored when single-line entry is specified as the InputFormMode. + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseNewLine; + + /// + /// When set, the software keyboard will return a UTF-8 encoded string, rather than UTF-16. + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseUtf8; + + /// + /// When set, the software keyboard will blur the game application rendered behind the keyboard. + /// + [MarshalAs(UnmanagedType.I1)] + public bool UseBlurBackground; + + /// + /// Offset into the work buffer of the initial text when the keyboard is first displayed. + /// + public int InitialStringOffset; + + /// + /// Length of the initial text. + /// + public int InitialStringLength; + + /// + /// Offset into the work buffer of the custom user dictionary. + /// + public int CustomDictionaryOffset; + + /// + /// Number of entries in the custom user dictionary. + /// + public int CustomDictionaryCount; + + /// + /// When set, the text entered will be validated on the application side after the keyboard has been submitted. + /// + [MarshalAs(UnmanagedType.I1)] + public bool CheckText; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCustomizeDic.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCustomizeDic.cs new file mode 100644 index 00000000..4d08dda1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCustomizeDic.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// A structure used by SetCustomizeDic request to software keyboard. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x70)] + struct SoftwareKeyboardCustomizeDic + { + // Unknown + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardDictSet.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardDictSet.cs new file mode 100644 index 00000000..0d757c44 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardDictSet.cs @@ -0,0 +1,34 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// A structure with custom dictionary words for the software keyboard. + /// + [StructLayout(LayoutKind.Sequential, Pack = 2)] + struct SoftwareKeyboardDictSet + { + /// + /// A 0x1000-byte aligned buffer position. + /// + public ulong BufferPosition; + + /// + /// A 0x1000-byte aligned buffer size. + /// + public uint BufferSize; + + /// + /// Array of word entries in the buffer. + /// + public Array24 Entries; + + /// + /// Number of used entries in the Entries field. + /// + public ushort TotalEntries; + + public ushort Padding1; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardInitialize.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardInitialize.cs new file mode 100644 index 00000000..743d40a2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardInitialize.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// A structure that mirrors the parameters used to initialize the keyboard applet. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + struct SoftwareKeyboardInitialize + { + public uint Unknown; + + /// + /// The applet mode used when launching the swkb. The bits regarding the background vs foreground mode can be wrong. + /// + public byte LibMode; + + /// + /// [5.0.0+] Set to 0x1 to indicate a firmware version >= 5.0.0. + /// + public byte FivePlus; + + public byte Padding1; + public byte Padding2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs new file mode 100644 index 00000000..239535ad --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs @@ -0,0 +1,169 @@ +using Ryujinx.HLE.UI; +using Ryujinx.Memory; +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Class that manages the renderer base class and its state in a multithreaded context. + /// + internal class SoftwareKeyboardRenderer : IDisposable + { + private const int TextBoxBlinkSleepMilliseconds = 100; + private const int RendererWaitTimeoutMilliseconds = 100; + + private readonly object _stateLock = new(); + + private readonly SoftwareKeyboardUIState _state = new(); + private readonly SoftwareKeyboardRendererBase _renderer; + + private readonly TimedAction _textBoxBlinkTimedAction = new(); + private readonly TimedAction _renderAction = new(); + + public SoftwareKeyboardRenderer(IHostUITheme uiTheme) + { + _renderer = new SoftwareKeyboardRendererBase(uiTheme); + + StartTextBoxBlinker(_textBoxBlinkTimedAction, _state, _stateLock); + StartRenderer(_renderAction, _renderer, _state, _stateLock); + } + + private static void StartTextBoxBlinker(TimedAction timedAction, SoftwareKeyboardUIState state, object stateLock) + { + timedAction.Reset(() => + { + lock (stateLock) + { + // The blinker is on half of the time and events such as input + // changes can reset the blinker. + state.TextBoxBlinkCounter = (state.TextBoxBlinkCounter + 1) % (2 * SoftwareKeyboardRendererBase.TextBoxBlinkThreshold); + + // Tell the render thread there is something new to render. + Monitor.PulseAll(stateLock); + } + }, TextBoxBlinkSleepMilliseconds); + } + + private static void StartRenderer(TimedAction timedAction, SoftwareKeyboardRendererBase renderer, SoftwareKeyboardUIState state, object stateLock) + { + SoftwareKeyboardUIState internalState = new(); + + bool canCreateSurface = false; + bool needsUpdate = true; + + timedAction.Reset(() => + { + lock (stateLock) + { + if (!Monitor.Wait(stateLock, RendererWaitTimeoutMilliseconds)) + { + return; + } + +#pragma warning disable IDE0055 // Disable formatting + needsUpdate = UpdateStateField(ref state.InputText, ref internalState.InputText); + needsUpdate |= UpdateStateField(ref state.CursorBegin, ref internalState.CursorBegin); + needsUpdate |= UpdateStateField(ref state.CursorEnd, ref internalState.CursorEnd); + needsUpdate |= UpdateStateField(ref state.AcceptPressed, ref internalState.AcceptPressed); + needsUpdate |= UpdateStateField(ref state.CancelPressed, ref internalState.CancelPressed); + needsUpdate |= UpdateStateField(ref state.OverwriteMode, ref internalState.OverwriteMode); + needsUpdate |= UpdateStateField(ref state.TypingEnabled, ref internalState.TypingEnabled); + needsUpdate |= UpdateStateField(ref state.ControllerEnabled, ref internalState.ControllerEnabled); + needsUpdate |= UpdateStateField(ref state.TextBoxBlinkCounter, ref internalState.TextBoxBlinkCounter); +#pragma warning restore IDE0055 + + canCreateSurface = state.SurfaceInfo != null && internalState.SurfaceInfo == null; + + if (canCreateSurface) + { + internalState.SurfaceInfo = state.SurfaceInfo; + } + } + + if (canCreateSurface) + { + renderer.CreateSurface(internalState.SurfaceInfo); + } + + if (needsUpdate) + { + renderer.DrawMutableElements(internalState); + renderer.CopyImageToBuffer(); + needsUpdate = false; + } + }); + } + + private static bool UpdateStateField(ref T source, ref T destination) where T : IEquatable + { + if (!source.Equals(destination)) + { + destination = source; + return true; + } + + return false; + } + + public void UpdateTextState(string inputText, int? cursorBegin, int? cursorEnd, bool? overwriteMode, bool? typingEnabled) + { + lock (_stateLock) + { + // Update the parameters that were provided. + _state.InputText = inputText ?? _state.InputText; + _state.CursorBegin = Math.Max(0, cursorBegin.GetValueOrDefault(_state.CursorBegin)); + _state.CursorEnd = Math.Min(cursorEnd.GetValueOrDefault(_state.CursorEnd), _state.InputText.Length); + _state.OverwriteMode = overwriteMode.GetValueOrDefault(_state.OverwriteMode); + _state.TypingEnabled = typingEnabled.GetValueOrDefault(_state.TypingEnabled); + + var begin = _state.CursorBegin; + var end = _state.CursorEnd; + _state.CursorBegin = Math.Min(begin, end); + _state.CursorEnd = Math.Max(begin, end); + + // Reset the cursor blink. + _state.TextBoxBlinkCounter = 0; + + // Tell the render thread there is something new to render. + Monitor.PulseAll(_stateLock); + } + } + + public void UpdateCommandState(bool? acceptPressed, bool? cancelPressed, bool? controllerEnabled) + { + lock (_stateLock) + { + // Update the parameters that were provided. + _state.AcceptPressed = acceptPressed.GetValueOrDefault(_state.AcceptPressed); + _state.CancelPressed = cancelPressed.GetValueOrDefault(_state.CancelPressed); + _state.ControllerEnabled = controllerEnabled.GetValueOrDefault(_state.ControllerEnabled); + + // Tell the render thread there is something new to render. + Monitor.PulseAll(_stateLock); + } + } + + public void SetSurfaceInfo(RenderingSurfaceInfo surfaceInfo) + { + lock (_stateLock) + { + _state.SurfaceInfo = surfaceInfo; + + // Tell the render thread there is something new to render. + Monitor.PulseAll(_stateLock); + } + } + + internal bool DrawTo(IVirtualMemoryManager destination, ulong position) + { + return _renderer.WriteBufferToMemory(destination, position); + } + + public void Dispose() + { + _textBoxBlinkTimedAction.RequestCancel(); + _renderAction.RequestCancel(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs new file mode 100644 index 00000000..cc62eca1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs @@ -0,0 +1,632 @@ +using Ryujinx.HLE.UI; +using Ryujinx.Memory; +using SkiaSharp; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Base class that generates the graphics for the software keyboard applet during inline mode. + /// + internal class SoftwareKeyboardRendererBase + { + public const int TextBoxBlinkThreshold = 8; + + const string MessageText = "Please use the keyboard to input text"; + const string AcceptText = "Accept"; + const string CancelText = "Cancel"; + const string ControllerToggleText = "Toggle input"; + + private readonly object _bufferLock = new(); + + private RenderingSurfaceInfo _surfaceInfo = null; + private SKImageInfo _imageInfo; + private SKSurface _surface = null; + private byte[] _bufferData = null; + + private readonly SKBitmap _ryujinxLogo = null; + private readonly SKBitmap _padAcceptIcon = null; + private readonly SKBitmap _padCancelIcon = null; + private readonly SKBitmap _keyModeIcon = null; + + private readonly float _textBoxOutlineWidth; + private readonly float _padPressedPenWidth; + + private readonly SKColor _textNormalColor; + private readonly SKColor _textSelectedColor; + private readonly SKColor _textOverCursorColor; + + private readonly SKPaint _panelBrush; + private readonly SKPaint _disabledBrush; + private readonly SKPaint _cursorBrush; + private readonly SKPaint _selectionBoxBrush; + + private readonly SKPaint _textBoxOutlinePen; + private readonly SKPaint _cursorPen; + private readonly SKPaint _selectionBoxPen; + private readonly SKPaint _padPressedPen; + + private readonly int _inputTextFontSize; + private SKFont _messageFont; + private SKFont _inputTextFont; + private SKFont _labelsTextFont; + + private SKRect _panelRectangle; + private SKPoint _logoPosition; + private float _messagePositionY; + + public SoftwareKeyboardRendererBase(IHostUITheme uiTheme) + { + int ryujinxLogoSize = 32; + + string ryujinxIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Logo_Ryujinx.png"; + _ryujinxLogo = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, ryujinxIconPath, ryujinxLogoSize, ryujinxLogoSize); + + string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png"; + string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png"; + string keyModeIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_KeyF6.png"; + + _padAcceptIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, padAcceptIconPath, 0, 0); + _padCancelIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, padCancelIconPath, 0, 0); + _keyModeIcon = LoadResource(typeof(SoftwareKeyboardRendererBase).Assembly, keyModeIconPath, 0, 0); + + var panelColor = ToColor(uiTheme.DefaultBackgroundColor, 255); + var panelTransparentColor = ToColor(uiTheme.DefaultBackgroundColor, 150); + var borderColor = ToColor(uiTheme.DefaultBorderColor); + var selectionBackgroundColor = ToColor(uiTheme.SelectionBackgroundColor); + + _textNormalColor = ToColor(uiTheme.DefaultForegroundColor); + _textSelectedColor = ToColor(uiTheme.SelectionForegroundColor); + _textOverCursorColor = ToColor(uiTheme.DefaultForegroundColor, null, true); + + float cursorWidth = 2; + + _textBoxOutlineWidth = 2; + _padPressedPenWidth = 2; + + _panelBrush = new SKPaint() + { + Color = panelColor, + IsAntialias = true + }; + _disabledBrush = new SKPaint() + { + Color = panelTransparentColor, + IsAntialias = true + }; + _cursorBrush = new SKPaint() { Color = _textNormalColor, IsAntialias = true }; + _selectionBoxBrush = new SKPaint() { Color = selectionBackgroundColor, IsAntialias = true }; + + _textBoxOutlinePen = new SKPaint() + { + Color = borderColor, + StrokeWidth = _textBoxOutlineWidth, + IsStroke = true, + IsAntialias = true + }; + _cursorPen = new SKPaint() { Color = _textNormalColor, StrokeWidth = cursorWidth, IsStroke = true, IsAntialias = true }; + _selectionBoxPen = new SKPaint() { Color = selectionBackgroundColor, StrokeWidth = cursorWidth, IsStroke = true, IsAntialias = true }; + _padPressedPen = new SKPaint() { Color = borderColor, StrokeWidth = _padPressedPenWidth, IsStroke = true, IsAntialias = true }; + + _inputTextFontSize = 20; + + CreateFonts(uiTheme.FontFamily); + } + + private void CreateFonts(string uiThemeFontFamily) + { + // Try a list of fonts in case any of them is not available in the system. + + string[] availableFonts = { + uiThemeFontFamily, + "Liberation Sans", + "FreeSans", + "DejaVu Sans", + "Lucida Grande", + }; + + foreach (string fontFamily in availableFonts) + { + try + { + using var typeface = SKTypeface.FromFamilyName(fontFamily, SKFontStyle.Normal); + _messageFont = new SKFont(typeface, 26); + _inputTextFont = new SKFont(typeface, _inputTextFontSize); + _labelsTextFont = new SKFont(typeface, 24); + + return; + } + catch + { + } + } + + throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!"); + } + + private static SKColor ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false) + { + var a = (byte)(color.A * 255); + var r = (byte)(color.R * 255); + var g = (byte)(color.G * 255); + var b = (byte)(color.B * 255); + + if (flipRgb) + { + r = (byte)(255 - r); + g = (byte)(255 - g); + b = (byte)(255 - b); + } + + return new SKColor(r, g, b, overrideAlpha.GetValueOrDefault(a)); + } + + private static SKBitmap LoadResource(Assembly assembly, string resourcePath, int newWidth, int newHeight) + { + Stream resourceStream = assembly.GetManifestResourceStream(resourcePath); + + return LoadResource(resourceStream, newWidth, newHeight); + } + + private static SKBitmap LoadResource(Stream resourceStream, int newWidth, int newHeight) + { + Debug.Assert(resourceStream != null); + + var bitmap = SKBitmap.Decode(resourceStream); + + if (newHeight != 0 && newWidth != 0) + { + var resized = bitmap.Resize(new SKImageInfo(newWidth, newHeight), SKFilterQuality.High); + if (resized != null) + { + bitmap.Dispose(); + bitmap = resized; + } + } + + return bitmap; + } + + private void DrawImmutableElements() + { + if (_surface == null) + { + return; + } + var canvas = _surface.Canvas; + + canvas.Clear(SKColors.Transparent); + canvas.DrawRect(_panelRectangle, _panelBrush); + canvas.DrawBitmap(_ryujinxLogo, _logoPosition); + + float halfWidth = _panelRectangle.Width / 2; + float buttonsY = _panelRectangle.Top + 185; + + SKPoint disableButtonPosition = new(halfWidth + 180, buttonsY); + + DrawControllerToggle(canvas, disableButtonPosition); + } + + public void DrawMutableElements(SoftwareKeyboardUIState state) + { + if (_surface == null) + { + return; + } + + using var paint = new SKPaint(_messageFont) + { + Color = _textNormalColor, + IsAntialias = true + }; + + var canvas = _surface.Canvas; + var messageRectangle = MeasureString(MessageText, paint); + float messagePositionX = (_panelRectangle.Width - messageRectangle.Width) / 2 - messageRectangle.Left; + float messagePositionY = _messagePositionY - messageRectangle.Top; + var messagePosition = new SKPoint(messagePositionX, messagePositionY); + var messageBoundRectangle = SKRect.Create(messagePositionX, messagePositionY, messageRectangle.Width, messageRectangle.Height); + + canvas.DrawRect(messageBoundRectangle, _panelBrush); + + canvas.DrawText(MessageText, messagePosition.X, messagePosition.Y + _messageFont.Metrics.XHeight + _messageFont.Metrics.Descent, paint); + + if (!state.TypingEnabled) + { + // Just draw a semi-transparent rectangle on top to fade the component with the background. + // TODO (caian): This will not work if one decides to add make background semi-transparent as well. + + canvas.DrawRect(messageBoundRectangle, _disabledBrush); + } + + DrawTextBox(canvas, state); + + float halfWidth = _panelRectangle.Width / 2; + float buttonsY = _panelRectangle.Top + 185; + + SKPoint acceptButtonPosition = new(halfWidth - 180, buttonsY); + SKPoint cancelButtonPosition = new(halfWidth, buttonsY); + SKPoint disableButtonPosition = new(halfWidth + 180, buttonsY); + + DrawPadButton(canvas, acceptButtonPosition, _padAcceptIcon, AcceptText, state.AcceptPressed, state.ControllerEnabled); + DrawPadButton(canvas, cancelButtonPosition, _padCancelIcon, CancelText, state.CancelPressed, state.ControllerEnabled); + + } + + public void CreateSurface(RenderingSurfaceInfo surfaceInfo) + { + if (_surfaceInfo != null) + { + return; + } + + _surfaceInfo = surfaceInfo; + + Debug.Assert(_surfaceInfo.ColorFormat == Services.SurfaceFlinger.ColorFormat.A8B8G8R8); + + // Use the whole area of the image to draw, even the alignment, otherwise it may shear the final + // image if the pitch is different. + uint totalWidth = _surfaceInfo.Pitch / 4; + uint totalHeight = _surfaceInfo.Size / _surfaceInfo.Pitch; + + Debug.Assert(_surfaceInfo.Width <= totalWidth); + Debug.Assert(_surfaceInfo.Height <= totalHeight); + Debug.Assert(_surfaceInfo.Pitch * _surfaceInfo.Height <= _surfaceInfo.Size); + + _imageInfo = new SKImageInfo((int)totalWidth, (int)totalHeight, SKColorType.Rgba8888); + _surface = SKSurface.Create(_imageInfo); + + ComputeConstants(); + DrawImmutableElements(); + } + + private void ComputeConstants() + { + int totalWidth = (int)_surfaceInfo.Width; + int totalHeight = (int)_surfaceInfo.Height; + + int panelHeight = 240; + int panelPositionY = totalHeight - panelHeight; + + _panelRectangle = SKRect.Create(0, panelPositionY, totalWidth, panelHeight); + + _messagePositionY = panelPositionY + 60; + + int logoPositionX = (totalWidth - _ryujinxLogo.Width) / 2; + int logoPositionY = panelPositionY + 18; + + _logoPosition = new SKPoint(logoPositionX, logoPositionY); + } + private static SKRect MeasureString(string text, SKPaint paint) + { + SKRect bounds = SKRect.Empty; + + if (text == "") + { + paint.MeasureText(" ", ref bounds); + } + else + { + paint.MeasureText(text, ref bounds); + } + + return bounds; + } + + private static SKRect MeasureString(ReadOnlySpan text, SKPaint paint) + { + SKRect bounds = SKRect.Empty; + + if (text == "") + { + paint.MeasureText(" ", ref bounds); + } + else + { + paint.MeasureText(text, ref bounds); + } + + return bounds; + } + + private void DrawTextBox(SKCanvas canvas, SoftwareKeyboardUIState state) + { + using var textPaint = new SKPaint(_labelsTextFont) + { + IsAntialias = true, + Color = _textNormalColor + }; + var inputTextRectangle = MeasureString(state.InputText, textPaint); + + float boxWidth = (int)(Math.Max(300, inputTextRectangle.Width + inputTextRectangle.Left + 8)); + float boxHeight = 32; + float boxY = _panelRectangle.Top + 110; + float boxX = (int)((_panelRectangle.Width - boxWidth) / 2); + + SKRect boxRectangle = SKRect.Create(boxX, boxY, boxWidth, boxHeight); + + SKRect boundRectangle = SKRect.Create(_panelRectangle.Left, boxY - _textBoxOutlineWidth, + _panelRectangle.Width, boxHeight + 2 * _textBoxOutlineWidth); + + canvas.DrawRect(boundRectangle, _panelBrush); + + canvas.DrawRect(boxRectangle, _textBoxOutlinePen); + + float inputTextX = (_panelRectangle.Width - inputTextRectangle.Width) / 2 - inputTextRectangle.Left; + float inputTextY = boxY + 5; + + var inputTextPosition = new SKPoint(inputTextX, inputTextY); + canvas.DrawText(state.InputText, inputTextPosition.X, inputTextPosition.Y + (_labelsTextFont.Metrics.XHeight + _labelsTextFont.Metrics.Descent), textPaint); + + // Draw the cursor on top of the text and redraw the text with a different color if necessary. + + SKColor cursorTextColor; + SKPaint cursorBrush; + SKPaint cursorPen; + + float cursorPositionYTop = inputTextY + 1; + float cursorPositionYBottom = cursorPositionYTop + _inputTextFontSize + 1; + float cursorPositionXLeft; + float cursorPositionXRight; + + bool cursorVisible = false; + + if (state.CursorBegin != state.CursorEnd) + { + Debug.Assert(state.InputText.Length > 0); + + cursorTextColor = _textSelectedColor; + cursorBrush = _selectionBoxBrush; + cursorPen = _selectionBoxPen; + + ReadOnlySpan textUntilBegin = state.InputText.AsSpan(0, state.CursorBegin); + ReadOnlySpan textUntilEnd = state.InputText.AsSpan(0, state.CursorEnd); + + var selectionBeginRectangle = MeasureString(textUntilBegin, textPaint); + var selectionEndRectangle = MeasureString(textUntilEnd, textPaint); + + cursorVisible = true; + cursorPositionXLeft = inputTextX + selectionBeginRectangle.Width + selectionBeginRectangle.Left; + cursorPositionXRight = inputTextX + selectionEndRectangle.Width + selectionEndRectangle.Left; + } + else + { + cursorTextColor = _textOverCursorColor; + cursorBrush = _cursorBrush; + cursorPen = _cursorPen; + + if (state.TextBoxBlinkCounter < TextBoxBlinkThreshold) + { + // Show the blinking cursor. + + int cursorBegin = Math.Min(state.InputText.Length, state.CursorBegin); + ReadOnlySpan textUntilCursor = state.InputText.AsSpan(0, cursorBegin); + var cursorTextRectangle = MeasureString(textUntilCursor, textPaint); + + cursorVisible = true; + cursorPositionXLeft = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.Left; + + if (state.OverwriteMode) + { + // The blinking cursor is in overwrite mode so it takes the size of a character. + + if (state.CursorBegin < state.InputText.Length) + { + textUntilCursor = state.InputText.AsSpan(0, cursorBegin + 1); + cursorTextRectangle = MeasureString(textUntilCursor, textPaint); + cursorPositionXRight = inputTextX + cursorTextRectangle.Width + cursorTextRectangle.Left; + } + else + { + cursorPositionXRight = cursorPositionXLeft + _inputTextFontSize / 2; + } + } + else + { + // The blinking cursor is in insert mode so it is only a line. + cursorPositionXRight = cursorPositionXLeft; + } + } + else + { + cursorPositionXLeft = inputTextX; + cursorPositionXRight = inputTextX; + } + } + + if (state.TypingEnabled && cursorVisible) + { + float cursorWidth = cursorPositionXRight - cursorPositionXLeft; + float cursorHeight = cursorPositionYBottom - cursorPositionYTop; + + if (cursorWidth == 0) + { + canvas.DrawLine(new SKPoint(cursorPositionXLeft, cursorPositionYTop), + new SKPoint(cursorPositionXLeft, cursorPositionYBottom), + cursorPen); + } + else + { + var cursorRectangle = SKRect.Create(cursorPositionXLeft, cursorPositionYTop, cursorWidth, cursorHeight); + + canvas.DrawRect(cursorRectangle, cursorPen); + canvas.DrawRect(cursorRectangle, cursorBrush); + + using var textOverCursor = SKSurface.Create(new SKImageInfo((int)cursorRectangle.Width, (int)cursorRectangle.Height, SKColorType.Rgba8888)); + var textOverCanvas = textOverCursor.Canvas; + var textRelativePosition = new SKPoint(inputTextPosition.X - cursorRectangle.Left, inputTextPosition.Y - cursorRectangle.Top); + + using var cursorPaint = new SKPaint(_inputTextFont) + { + Color = cursorTextColor, + IsAntialias = true + }; + + textOverCanvas.DrawText(state.InputText, textRelativePosition.X, textRelativePosition.Y + _inputTextFont.Metrics.XHeight + _inputTextFont.Metrics.Descent, cursorPaint); + + var cursorPosition = new SKPoint((int)cursorRectangle.Left, (int)cursorRectangle.Top); + textOverCursor.Flush(); + canvas.DrawSurface(textOverCursor, cursorPosition); + } + } + else if (!state.TypingEnabled) + { + // Just draw a semi-transparent rectangle on top to fade the component with the background. + // TODO (caian): This will not work if one decides to add make background semi-transparent as well. + + canvas.DrawRect(boundRectangle, _disabledBrush); + } + } + + private void DrawPadButton(SKCanvas canvas, SKPoint point, SKBitmap icon, string label, bool pressed, bool enabled) + { + // Use relative positions so we can center the entire drawing later. + + float iconX = 0; + float iconY = 0; + float iconWidth = icon.Width; + float iconHeight = icon.Height; + + using var paint = new SKPaint(_labelsTextFont) + { + Color = _textNormalColor, + IsAntialias = true + }; + + var labelRectangle = MeasureString(label, paint); + + float labelPositionX = iconWidth + 8 - labelRectangle.Left; + float labelPositionY = 3; + + float fullWidth = labelPositionX + labelRectangle.Width + labelRectangle.Left; + float fullHeight = iconHeight; + + // Convert all relative positions into absolute. + + float originX = (int)(point.X - fullWidth / 2); + float originY = (int)(point.Y - fullHeight / 2); + + iconX += originX; + iconY += originY; + + var iconPosition = new SKPoint((int)iconX, (int)iconY); + var labelPosition = new SKPoint(labelPositionX + originX, labelPositionY + originY); + + var selectedRectangle = SKRect.Create(originX - 2 * _padPressedPenWidth, originY - 2 * _padPressedPenWidth, + fullWidth + 4 * _padPressedPenWidth, fullHeight + 4 * _padPressedPenWidth); + + var boundRectangle = SKRect.Create(originX, originY, fullWidth, fullHeight); + boundRectangle.Inflate(4 * _padPressedPenWidth, 4 * _padPressedPenWidth); + + canvas.DrawRect(boundRectangle, _panelBrush); + canvas.DrawBitmap(icon, iconPosition); + canvas.DrawText(label, labelPosition.X, labelPosition.Y + _labelsTextFont.Metrics.XHeight + _labelsTextFont.Metrics.Descent, paint); + + if (enabled) + { + if (pressed) + { + canvas.DrawRect(selectedRectangle, _padPressedPen); + } + } + else + { + // Just draw a semi-transparent rectangle on top to fade the component with the background. + // TODO (caian): This will not work if one decides to add make background semi-transparent as well. + + canvas.DrawRect(boundRectangle, _disabledBrush); + } + } + + private void DrawControllerToggle(SKCanvas canvas, SKPoint point) + { + using var paint = new SKPaint(_labelsTextFont) + { + IsAntialias = true, + Color = _textNormalColor + }; + var labelRectangle = MeasureString(ControllerToggleText, paint); + + // Use relative positions so we can center the entire drawing later. + + float keyWidth = _keyModeIcon.Width; + float keyHeight = _keyModeIcon.Height; + + float labelPositionX = keyWidth + 8 - labelRectangle.Left; + float labelPositionY = -labelRectangle.Top - 1; + + float keyX = 0; + float keyY = (int)((labelPositionY + labelRectangle.Height - keyHeight) / 2); + + float fullWidth = labelPositionX + labelRectangle.Width; + float fullHeight = Math.Max(labelPositionY + labelRectangle.Height, keyHeight); + + // Convert all relative positions into absolute. + + float originX = (int)(point.X - fullWidth / 2); + float originY = (int)(point.Y - fullHeight / 2); + + keyX += originX; + keyY += originY; + + var labelPosition = new SKPoint(labelPositionX + originX, labelPositionY + originY); + var overlayPosition = new SKPoint((int)keyX, (int)keyY); + + canvas.DrawBitmap(_keyModeIcon, overlayPosition); + canvas.DrawText(ControllerToggleText, labelPosition.X, labelPosition.Y + _labelsTextFont.Metrics.XHeight, paint); + } + + public unsafe void CopyImageToBuffer() + { + lock (_bufferLock) + { + if (_surface == null) + { + return; + } + + // Convert the pixel format used in the image to the one used in the Switch surface. + _surface.Flush(); + + var buffer = new byte[_imageInfo.BytesSize]; + fixed (byte* bufferPtr = buffer) + { + if (!_surface.ReadPixels(_imageInfo, (nint)bufferPtr, _imageInfo.RowBytes, 0, 0)) + { + return; + } + } + + _bufferData = buffer; + + Debug.Assert(buffer.Length == _surfaceInfo.Size); + } + } + + public bool WriteBufferToMemory(IVirtualMemoryManager destination, ulong position) + { + lock (_bufferLock) + { + if (_bufferData == null) + { + return false; + } + + try + { + destination.Write(position, _bufferData); + } + catch + { + return false; + } + + return true; + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs new file mode 100644 index 00000000..86302e49 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Identifies the software keyboard state. + /// + enum SoftwareKeyboardState + { + /// + /// swkbd is uninitialized. + /// + Uninitialized, + + /// + /// swkbd is ready to process data. + /// + Ready, + + /// + /// swkbd is awaiting an interactive reply with a validation status. + /// + ValidationPending, + + /// + /// swkbd has completed. + /// + Complete, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIArgs.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIArgs.cs new file mode 100644 index 00000000..854f04a3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIArgs.cs @@ -0,0 +1,16 @@ +using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; + +namespace Ryujinx.HLE.HOS.Applets +{ + public struct SoftwareKeyboardUIArgs + { + public KeyboardMode KeyboardMode; + public string HeaderText; + public string SubtitleText; + public string InitialText; + public string GuideText; + public string SubmitText; + public int StringLengthMin; + public int StringLengthMax; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIState.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIState.cs new file mode 100644 index 00000000..6199ff66 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUIState.cs @@ -0,0 +1,22 @@ +using Ryujinx.HLE.UI; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// TODO + /// + internal class SoftwareKeyboardUIState + { + public string InputText = ""; + public int CursorBegin = 0; + public int CursorEnd = 0; + public bool AcceptPressed = false; + public bool CancelPressed = false; + public bool OverwriteMode = false; + public bool TypingEnabled = true; + public bool ControllerEnabled = true; + public int TextBoxBlinkCounter = 0; + + public RenderingSurfaceInfo SurfaceInfo = null; + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUserWord.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUserWord.cs new file mode 100644 index 00000000..84836884 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUserWord.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// A structure used by SetUserWordInfo request to the software keyboard. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x64)] + struct SoftwareKeyboardUserWord + { + // Unknown + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TRef.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TRef.cs new file mode 100644 index 00000000..32d9e68d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TRef.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// Wraps a type in a class so it gets stored in the GC managed heap. This is used as communication mechanism + /// between classed that need to be disposed and, thus, can't share their references. + /// + /// The internal type. + class TRef + { + public T Value; + + public TRef() { } + + public TRef(T value) + { + Value = value; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TimedAction.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TimedAction.cs new file mode 100644 index 00000000..3eaf6459 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TimedAction.cs @@ -0,0 +1,186 @@ +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + /// + /// A threaded executor of periodic actions that can be cancelled. The total execution time is optional + /// and, in this case, a progress is reported back to the action. + /// + class TimedAction + { + public const int MaxThreadSleep = 100; + + private class SleepSubstepData + { + public readonly int SleepMilliseconds; + public readonly int SleepCount; + public readonly int SleepRemainderMilliseconds; + + public SleepSubstepData(int sleepMilliseconds) + { + SleepMilliseconds = Math.Min(sleepMilliseconds, MaxThreadSleep); + SleepCount = sleepMilliseconds / SleepMilliseconds; + SleepRemainderMilliseconds = sleepMilliseconds - SleepCount * SleepMilliseconds; + } + } + + private TRef _cancelled = null; + private Thread _thread = null; + private readonly object _lock = new(); + + public bool IsRunning + { + get + { + lock (_lock) + { + if (_thread == null) + { + return false; + } + + return _thread.IsAlive; + } + } + } + + public void RequestCancel() + { + lock (_lock) + { + if (_cancelled != null) + { + Volatile.Write(ref _cancelled.Value, true); + } + } + } + + public TimedAction() { } + + private void Reset(Thread thread, TRef cancelled) + { + lock (_lock) + { + // Cancel the current task. + if (_cancelled != null) + { + Volatile.Write(ref _cancelled.Value, true); + } + + _cancelled = cancelled; + + _thread = thread; + _thread.IsBackground = true; + _thread.Start(); + } + } + + public void Reset(Action action, int totalMilliseconds, int sleepMilliseconds) + { + // Create a dedicated cancel token for each task. + var cancelled = new TRef(false); + + Reset(new Thread(() => + { + var substepData = new SleepSubstepData(sleepMilliseconds); + + int totalCount = totalMilliseconds / sleepMilliseconds; + int totalRemainder = totalMilliseconds - totalCount * sleepMilliseconds; + + if (Volatile.Read(ref cancelled.Value)) + { + action(-1); + + return; + } + + action(0); + + for (int i = 1; i <= totalCount; i++) + { + if (SleepWithSubstep(substepData, cancelled)) + { + action(-1); + + return; + } + + action((float)(i * sleepMilliseconds) / totalMilliseconds); + } + + if (totalRemainder > 0) + { + if (SleepWithSubstep(substepData, cancelled)) + { + action(-1); + + return; + } + + action(1); + } + }), cancelled); + } + + public void Reset(Action action, int sleepMilliseconds) + { + // Create a dedicated cancel token for each task. + var cancelled = new TRef(false); + + Reset(new Thread(() => + { + var substepData = new SleepSubstepData(sleepMilliseconds); + + while (!Volatile.Read(ref cancelled.Value)) + { + action(); + + if (SleepWithSubstep(substepData, cancelled)) + { + return; + } + } + }), cancelled); + } + + public void Reset(Action action) + { + // Create a dedicated cancel token for each task. + var cancelled = new TRef(false); + + Reset(new Thread(() => + { + while (!Volatile.Read(ref cancelled.Value)) + { + action(); + } + }), cancelled); + } + + private static bool SleepWithSubstep(SleepSubstepData substepData, TRef cancelled) + { + for (int i = 0; i < substepData.SleepCount; i++) + { + if (Volatile.Read(ref cancelled.Value)) + { + return true; + } + + Thread.Sleep(substepData.SleepMilliseconds); + } + + if (substepData.SleepRemainderMilliseconds > 0) + { + if (Volatile.Read(ref cancelled.Value)) + { + return true; + } + + Thread.Sleep(substepData.SleepRemainderMilliseconds); + } + + return Volatile.Read(ref cancelled.Value); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContext.cs b/src/Ryujinx.HLE/HOS/ArmProcessContext.cs new file mode 100644 index 00000000..fde489ab --- /dev/null +++ b/src/Ryujinx.HLE/HOS/ArmProcessContext.cs @@ -0,0 +1,94 @@ +using ARMeilleure.Memory; +using Ryujinx.Cpu; +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.Memory; + +namespace Ryujinx.HLE.HOS +{ + interface IArmProcessContext : IProcessContext + { + IDiskCacheLoadState Initialize( + string titleIdText, + string displayVersion, + bool diskCacheEnabled, + ulong codeAddress, + ulong codeSize); + } + + class ArmProcessContext : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, IMemoryManager + { + private readonly ulong _pid; + private readonly GpuContext _gpuContext; + private readonly ICpuContext _cpuContext; + private T _memoryManager; + + public IVirtualMemoryManager AddressSpace => _memoryManager; + + public ulong AddressSpaceSize { get; } + + public ArmProcessContext( + ulong pid, + ICpuEngine cpuEngine, + GpuContext gpuContext, + T memoryManager, + ulong addressSpaceSize, + bool for64Bit) + { + if (memoryManager is IRefCounted rc) + { + rc.IncrementReferenceCount(); + } + + gpuContext.RegisterProcess(pid, memoryManager); + + _pid = pid; + _gpuContext = gpuContext; + _cpuContext = cpuEngine.CreateCpuContext(memoryManager, for64Bit); + _memoryManager = memoryManager; + + AddressSpaceSize = addressSpaceSize; + } + + public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks) + { + return _cpuContext.CreateExecutionContext(exceptionCallbacks); + } + + public void Execute(IExecutionContext context, ulong codeAddress) + { + // We must wait until shader cache is loaded, among other things, before executing CPU code. + _gpuContext.WaitUntilGpuReady(); + _cpuContext.Execute(context, codeAddress); + } + + public IDiskCacheLoadState Initialize( + string titleIdText, + string displayVersion, + bool diskCacheEnabled, + ulong codeAddress, + ulong codeSize) + { + _cpuContext.PrepareCodeRange(codeAddress, codeSize); + return _cpuContext.LoadDiskCache(titleIdText, displayVersion, diskCacheEnabled); + } + + public void InvalidateCacheRegion(ulong address, ulong size) + { + _cpuContext.InvalidateCacheRegion(address, size); + } + + public void Dispose() + { + if (_memoryManager is IRefCounted rc) + { + rc.DecrementReferenceCount(); + + _memoryManager = null; + _gpuContext.UnregisterProcess(_pid); + } + + _cpuContext.Dispose(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs new file mode 100644 index 00000000..6646826c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs @@ -0,0 +1,122 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.Cpu.AppleHv; +using Ryujinx.Cpu.Jit; +using Ryujinx.Cpu.LightningJit; +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS +{ + class ArmProcessContextFactory : IProcessContextFactory + { + private readonly ITickSource _tickSource; + private readonly GpuContext _gpu; + private readonly string _titleIdText; + private readonly string _displayVersion; + private readonly bool _diskCacheEnabled; + private readonly ulong _codeAddress; + private readonly ulong _codeSize; + + public IDiskCacheLoadState DiskCacheLoadState { get; private set; } + + public ArmProcessContextFactory( + ITickSource tickSource, + GpuContext gpu, + string titleIdText, + string displayVersion, + bool diskCacheEnabled, + ulong codeAddress, + ulong codeSize) + { + _tickSource = tickSource; + _gpu = gpu; + _titleIdText = titleIdText; + _displayVersion = displayVersion; + _diskCacheEnabled = diskCacheEnabled; + _codeAddress = codeAddress; + _codeSize = codeSize; + } + + public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit) + { + IArmProcessContext processContext; + + bool isArm64Host = RuntimeInformation.ProcessArchitecture == Architecture.Arm64; + + if (OperatingSystem.IsMacOS() && isArm64Host && for64Bit && context.Device.Configuration.UseHypervisor) + { + var cpuEngine = new HvEngine(_tickSource); + var memoryManager = new HvMemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler); + processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManager, addressSpaceSize, for64Bit); + } + else + { + MemoryManagerMode mode = context.Device.Configuration.MemoryManagerMode; + + if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible)) + { + Logger.Warning?.Print(LogClass.Cpu, "Host system doesn't support views, falling back to software page table"); + + mode = MemoryManagerMode.SoftwarePageTable; + } + + ICpuEngine cpuEngine = isArm64Host && (mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe) + ? new LightningJitEngine(_tickSource) + : new JitEngine(_tickSource); + + AddressSpace addressSpace = null; + + // We want to use host tracked mode if the host page size is > 4KB. + if ((mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe) && MemoryBlock.GetPageSize() <= 0x1000) + { + if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, out addressSpace)) + { + Logger.Warning?.Print(LogClass.Cpu, "Address space creation failed, falling back to software page table"); + + mode = MemoryManagerMode.SoftwarePageTable; + } + } + + switch (mode) + { + case MemoryManagerMode.SoftwarePageTable: + var memoryManager = new MemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler); + processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManager, addressSpaceSize, for64Bit); + break; + + case MemoryManagerMode.HostMapped: + case MemoryManagerMode.HostMappedUnsafe: + if (addressSpace == null) + { + var memoryManagerHostTracked = new MemoryManagerHostTracked(context.Memory, addressSpaceSize, mode == MemoryManagerMode.HostMappedUnsafe, invalidAccessHandler); + processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManagerHostTracked, addressSpaceSize, for64Bit); + } + else + { + if (addressSpaceSize != addressSpace.AddressSpaceSize) + { + Logger.Warning?.Print(LogClass.Emulation, $"Allocated address space (0x{addressSpace.AddressSpaceSize:X}) is smaller than guest application requirements (0x{addressSpaceSize:X})"); + } + + var memoryManagerHostMapped = new MemoryManagerHostMapped(addressSpace, mode == MemoryManagerMode.HostMappedUnsafe, invalidAccessHandler); + processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManagerHostMapped, addressSpace.AddressSpaceSize, for64Bit); + } + break; + + default: + throw new InvalidOperationException($"{nameof(mode)} contains an invalid value: {mode}"); + } + } + + DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize); + + return processContext; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs new file mode 100644 index 00000000..8248b420 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs @@ -0,0 +1,25 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ArraySubscriptingExpression : BaseNode + { + private readonly BaseNode _leftNode; + private readonly BaseNode _subscript; + + public ArraySubscriptingExpression(BaseNode leftNode, BaseNode subscript) : base(NodeType.ArraySubscriptingExpression) + { + _leftNode = leftNode; + _subscript = subscript; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("("); + _leftNode.Print(writer); + writer.Write(")["); + _subscript.Print(writer); + writer.Write("]"); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs new file mode 100644 index 00000000..59fd915a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs @@ -0,0 +1,59 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ArrayType : BaseNode + { + private readonly BaseNode _base; + private readonly BaseNode _dimensionExpression; + private readonly string _dimensionString; + + public ArrayType(BaseNode Base, BaseNode dimensionExpression = null) : base(NodeType.ArrayType) + { + _base = Base; + _dimensionExpression = dimensionExpression; + } + + public ArrayType(BaseNode Base, string dimensionString) : base(NodeType.ArrayType) + { + _base = Base; + _dimensionString = dimensionString; + } + + public override bool HasRightPart() + { + return true; + } + + public override bool IsArray() + { + return true; + } + + public override void PrintLeft(TextWriter writer) + { + _base.PrintLeft(writer); + } + + public override void PrintRight(TextWriter writer) + { + // FIXME: detect if previous char was a ]. + writer.Write(" "); + + writer.Write("["); + + if (_dimensionString != null) + { + writer.Write(_dimensionString); + } + else + { + _dimensionExpression?.Print(writer); + } + + writer.Write("]"); + + _base.PrintRight(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs new file mode 100644 index 00000000..fc60fb6e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs @@ -0,0 +1,113 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public enum NodeType + { + CvQualifierType, + SimpleReferenceType, + NameType, + EncodedFunction, + NestedName, + SpecialName, + LiteralOperator, + NodeArray, + ElaboratedType, + PostfixQualifiedType, + SpecialSubstitution, + ExpandedSpecialSubstitution, + CtorDtorNameType, + EnclosedExpression, + ForwardTemplateReference, + NameTypeWithTemplateArguments, + PackedTemplateArgument, + TemplateArguments, + BooleanExpression, + CastExpression, + CallExpression, + IntegerCastExpression, + PackedTemplateParameter, + PackedTemplateParameterExpansion, + IntegerLiteral, + DeleteExpression, + MemberExpression, + ArraySubscriptingExpression, + InitListExpression, + PostfixExpression, + ConditionalExpression, + ThrowExpression, + FunctionParameter, + ConversionExpression, + BinaryExpression, + PrefixExpression, + BracedExpression, + BracedRangeExpression, + NewExpression, + QualifiedName, + StdQualifiedName, + DtOrName, + GlobalQualifiedName, + NoexceptSpec, + DynamicExceptionSpec, + FunctionType, + PointerType, + ReferenceType, + ConversionOperatorType, + LocalName, + CtorVtableSpecialName, + ArrayType, + } + + public abstract class BaseNode + { + public NodeType Type { get; protected set; } + + public BaseNode(NodeType type) + { + Type = type; + } + + public virtual void Print(TextWriter writer) + { + PrintLeft(writer); + + if (HasRightPart()) + { + PrintRight(writer); + } + } + + public abstract void PrintLeft(TextWriter writer); + + public virtual bool HasRightPart() + { + return false; + } + + public virtual bool IsArray() + { + return false; + } + + public virtual bool HasFunctions() + { + return false; + } + + public virtual string GetName() + { + return null; + } + + public virtual void PrintRight(TextWriter writer) { } + + public override string ToString() + { + StringWriter writer = new(); + + Print(writer); + + return writer.ToString(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs new file mode 100644 index 00000000..81ea80db --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs @@ -0,0 +1,41 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class BinaryExpression : BaseNode + { + private readonly BaseNode _leftPart; + private readonly string _name; + private readonly BaseNode _rightPart; + + public BinaryExpression(BaseNode leftPart, string name, BaseNode rightPart) : base(NodeType.BinaryExpression) + { + _leftPart = leftPart; + _name = name; + _rightPart = rightPart; + } + + public override void PrintLeft(TextWriter writer) + { + if (_name.Equals(">")) + { + writer.Write("("); + } + + writer.Write("("); + _leftPart.Print(writer); + writer.Write(") "); + + writer.Write(_name); + + writer.Write(" ("); + _rightPart.Print(writer); + writer.Write(")"); + + if (_name.Equals(">")) + { + writer.Write(")"); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs new file mode 100644 index 00000000..53fbb204 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs @@ -0,0 +1,40 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class BracedExpression : BaseNode + { + private readonly BaseNode _element; + private readonly BaseNode _expression; + private readonly bool _isArrayExpression; + + public BracedExpression(BaseNode element, BaseNode expression, bool isArrayExpression) : base(NodeType.BracedExpression) + { + _element = element; + _expression = expression; + _isArrayExpression = isArrayExpression; + } + + public override void PrintLeft(TextWriter writer) + { + if (_isArrayExpression) + { + writer.Write("["); + _element.Print(writer); + writer.Write("]"); + } + else + { + writer.Write("."); + _element.Print(writer); + } + + if (!_expression.GetType().Equals(NodeType.BracedExpression) || !_expression.GetType().Equals(NodeType.BracedRangeExpression)) + { + writer.Write(" = "); + } + + _expression.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs new file mode 100644 index 00000000..38d8ccd4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs @@ -0,0 +1,34 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class BracedRangeExpression : BaseNode + { + private readonly BaseNode _firstNode; + private readonly BaseNode _lastNode; + private readonly BaseNode _expression; + + public BracedRangeExpression(BaseNode firstNode, BaseNode lastNode, BaseNode expression) : base(NodeType.BracedRangeExpression) + { + _firstNode = firstNode; + _lastNode = lastNode; + _expression = expression; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("["); + _firstNode.Print(writer); + writer.Write(" ... "); + _lastNode.Print(writer); + writer.Write("]"); + + if (!_expression.GetType().Equals(NodeType.BracedExpression) || !_expression.GetType().Equals(NodeType.BracedRangeExpression)) + { + writer.Write(" = "); + } + + _expression.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs new file mode 100644 index 00000000..0ee2e9d7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class CallExpression : NodeArray + { + private readonly BaseNode _callee; + + public CallExpression(BaseNode callee, List nodes) : base(nodes, NodeType.CallExpression) + { + _callee = callee; + } + + public override void PrintLeft(TextWriter writer) + { + _callee.Print(writer); + + writer.Write("("); + writer.Write(string.Join(", ", Nodes.ToArray())); + writer.Write(")"); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs new file mode 100644 index 00000000..b35d06dd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs @@ -0,0 +1,28 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class CastExpression : BaseNode + { + private readonly string _kind; + private readonly BaseNode _to; + private readonly BaseNode _from; + + public CastExpression(string kind, BaseNode to, BaseNode from) : base(NodeType.CastExpression) + { + _kind = kind; + _to = to; + _from = from; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write(_kind); + writer.Write("<"); + _to.PrintLeft(writer); + writer.Write(">("); + _from.PrintLeft(writer); + writer.Write(")"); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs new file mode 100644 index 00000000..b441a377 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs @@ -0,0 +1,29 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ConditionalExpression : BaseNode + { + private readonly BaseNode _thenNode; + private readonly BaseNode _elseNode; + private readonly BaseNode _conditionNode; + + public ConditionalExpression(BaseNode conditionNode, BaseNode thenNode, BaseNode elseNode) : base(NodeType.ConditionalExpression) + { + _thenNode = thenNode; + _conditionNode = conditionNode; + _elseNode = elseNode; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("("); + _conditionNode.Print(writer); + writer.Write(") ? ("); + _thenNode.Print(writer); + writer.Write(") : ("); + _elseNode.Print(writer); + writer.Write(")"); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs new file mode 100644 index 00000000..d30912f4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ConversionExpression : BaseNode + { + private readonly BaseNode _typeNode; + private readonly BaseNode _expressions; + + public ConversionExpression(BaseNode typeNode, BaseNode expressions) : base(NodeType.ConversionExpression) + { + _typeNode = typeNode; + _expressions = expressions; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("("); + _typeNode.Print(writer); + writer.Write(")("); + _expressions.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs new file mode 100644 index 00000000..814b4a2f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs @@ -0,0 +1,15 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ConversionOperatorType : ParentNode + { + public ConversionOperatorType(BaseNode child) : base(NodeType.ConversionOperatorType, child) { } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("operator "); + Child.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs new file mode 100644 index 00000000..068662a6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class CtorDtorNameType : ParentNode + { + private readonly bool _isDestructor; + + public CtorDtorNameType(BaseNode name, bool isDestructor) : base(NodeType.CtorDtorNameType, name) + { + _isDestructor = isDestructor; + } + + public override void PrintLeft(TextWriter writer) + { + if (_isDestructor) + { + writer.Write("~"); + } + + writer.Write(Child.GetName()); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs new file mode 100644 index 00000000..92496956 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class CtorVtableSpecialName : BaseNode + { + private readonly BaseNode _firstType; + private readonly BaseNode _secondType; + + public CtorVtableSpecialName(BaseNode firstType, BaseNode secondType) : base(NodeType.CtorVtableSpecialName) + { + _firstType = firstType; + _secondType = secondType; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("construction vtable for "); + _firstType.Print(writer); + writer.Write("-in-"); + _secondType.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs new file mode 100644 index 00000000..20d0bd8a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs @@ -0,0 +1,33 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class DeleteExpression : ParentNode + { + private readonly bool _isGlobal; + private readonly bool _isArrayExpression; + + public DeleteExpression(BaseNode child, bool isGlobal, bool isArrayExpression) : base(NodeType.DeleteExpression, child) + { + _isGlobal = isGlobal; + _isArrayExpression = isArrayExpression; + } + + public override void PrintLeft(TextWriter writer) + { + if (_isGlobal) + { + writer.Write("::"); + } + + writer.Write("delete"); + + if (_isArrayExpression) + { + writer.Write("[] "); + } + + Child.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs new file mode 100644 index 00000000..b584dba9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs @@ -0,0 +1,15 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class DtorName : ParentNode + { + public DtorName(BaseNode name) : base(NodeType.DtOrName, name) { } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("~"); + Child.PrintLeft(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs new file mode 100644 index 00000000..98619b20 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs @@ -0,0 +1,16 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class DynamicExceptionSpec : ParentNode + { + public DynamicExceptionSpec(BaseNode child) : base(NodeType.DynamicExceptionSpec, child) { } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("throw("); + Child.Print(writer); + writer.Write(")"); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs new file mode 100644 index 00000000..2877557d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs @@ -0,0 +1,21 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ElaboratedType : ParentNode + { + private readonly string _elaborated; + + public ElaboratedType(string elaborated, BaseNode type) : base(NodeType.ElaboratedType, type) + { + _elaborated = elaborated; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write(_elaborated); + writer.Write(" "); + Child.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs new file mode 100644 index 00000000..178db512 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs @@ -0,0 +1,25 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class EnclosedExpression : BaseNode + { + private readonly string _prefix; + private readonly BaseNode _expression; + private readonly string _postfix; + + public EnclosedExpression(string prefix, BaseNode expression, string postfix) : base(NodeType.EnclosedExpression) + { + _prefix = prefix; + _expression = expression; + _postfix = postfix; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write(_prefix); + _expression.Print(writer); + writer.Write(_postfix); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs new file mode 100644 index 00000000..de559804 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs @@ -0,0 +1,56 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class EncodedFunction : BaseNode + { + private readonly BaseNode _name; + private readonly BaseNode _params; + private readonly BaseNode _cv; + private readonly BaseNode _ref; + private readonly BaseNode _attrs; + private readonly BaseNode _ret; + + public EncodedFunction(BaseNode name, BaseNode Params, BaseNode cv, BaseNode Ref, BaseNode attrs, BaseNode ret) : base(NodeType.NameType) + { + _name = name; + _params = Params; + _cv = cv; + _ref = Ref; + _attrs = attrs; + _ret = ret; + } + + public override void PrintLeft(TextWriter writer) + { + if (_ret != null) + { + _ret.PrintLeft(writer); + + if (!_ret.HasRightPart()) + { + writer.Write(" "); + } + } + + _name.Print(writer); + + } + + public override bool HasRightPart() + { + return true; + } + + public override void PrintRight(TextWriter writer) + { + writer.Write("("); + _params?.Print(writer); + writer.Write(")"); + _ret?.PrintRight(writer); + _cv?.Print(writer); + _ref?.Print(writer); + _attrs?.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs new file mode 100644 index 00000000..ed73fa09 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs @@ -0,0 +1,48 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class FoldExpression : BaseNode + { + private readonly bool _isLeftFold; + private readonly string _operatorName; + private readonly BaseNode _expression; + private readonly BaseNode _initializer; + + public FoldExpression(bool isLeftFold, string operatorName, BaseNode expression, BaseNode initializer) : base(NodeType.FunctionParameter) + { + _isLeftFold = isLeftFold; + _operatorName = operatorName; + _expression = expression; + _initializer = initializer; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("("); + + if (_isLeftFold && _initializer != null) + { + _initializer.Print(writer); + writer.Write(" "); + writer.Write(_operatorName); + writer.Write(" "); + } + + writer.Write(_isLeftFold ? "... " : " "); + writer.Write(_operatorName); + writer.Write(!_isLeftFold ? " ..." : " "); + _expression.Print(writer); + + if (!_isLeftFold && _initializer != null) + { + _initializer.Print(writer); + writer.Write(" "); + writer.Write(_operatorName); + writer.Write(" "); + } + + writer.Write(")"); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs new file mode 100644 index 00000000..30c838ca --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs @@ -0,0 +1,38 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ForwardTemplateReference : BaseNode + { + // TODO: Compute inside the Demangler + public BaseNode Reference; +#pragma warning disable IDE0052 // Remove unread private member + private readonly int _index; +#pragma warning restore IDE0052 + + public ForwardTemplateReference(int index) : base(NodeType.ForwardTemplateReference) + { + _index = index; + } + + public override string GetName() + { + return Reference.GetName(); + } + + public override void PrintLeft(TextWriter writer) + { + Reference.PrintLeft(writer); + } + + public override void PrintRight(TextWriter writer) + { + Reference.PrintRight(writer); + } + + public override bool HasRightPart() + { + return Reference.HasRightPart(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs new file mode 100644 index 00000000..9c2d0955 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class FunctionParameter : BaseNode + { + private readonly string _number; + + public FunctionParameter(string number) : base(NodeType.FunctionParameter) + { + _number = number; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("fp "); + + if (_number != null) + { + writer.Write(_number); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs new file mode 100644 index 00000000..431c2111 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs @@ -0,0 +1,61 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class FunctionType : BaseNode + { + private readonly BaseNode _returnType; + private readonly BaseNode _params; + private readonly BaseNode _cvQualifier; + private readonly SimpleReferenceType _referenceQualifier; + private readonly BaseNode _exceptionSpec; + + public FunctionType(BaseNode returnType, BaseNode Params, BaseNode cvQualifier, SimpleReferenceType referenceQualifier, BaseNode exceptionSpec) : base(NodeType.FunctionType) + { + _returnType = returnType; + _params = Params; + _cvQualifier = cvQualifier; + _referenceQualifier = referenceQualifier; + _exceptionSpec = exceptionSpec; + } + + public override void PrintLeft(TextWriter writer) + { + _returnType.PrintLeft(writer); + writer.Write(" "); + } + + public override void PrintRight(TextWriter writer) + { + writer.Write("("); + _params.Print(writer); + writer.Write(")"); + + _returnType.PrintRight(writer); + + _cvQualifier.Print(writer); + + if (_referenceQualifier.Qualifier != Reference.None) + { + writer.Write(" "); + _referenceQualifier.PrintQualifier(writer); + } + + if (_exceptionSpec != null) + { + writer.Write(" "); + _exceptionSpec.Print(writer); + } + } + + public override bool HasRightPart() + { + return true; + } + + public override bool HasFunctions() + { + return true; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs new file mode 100644 index 00000000..d3b6a558 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs @@ -0,0 +1,15 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class GlobalQualifiedName : ParentNode + { + public GlobalQualifiedName(BaseNode child) : base(NodeType.GlobalQualifiedName, child) { } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("::"); + Child.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs new file mode 100644 index 00000000..908319c3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class InitListExpression : BaseNode + { + private readonly BaseNode _typeNode; + private readonly List _nodes; + + public InitListExpression(BaseNode typeNode, List nodes) : base(NodeType.InitListExpression) + { + _typeNode = typeNode; + _nodes = nodes; + } + + public override void PrintLeft(TextWriter writer) + { + _typeNode?.Print(writer); + + writer.Write("{"); + writer.Write(string.Join(", ", _nodes.ToArray())); + writer.Write("}"); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs new file mode 100644 index 00000000..eed36b21 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs @@ -0,0 +1,22 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class IntegerCastExpression : ParentNode + { + private readonly string _number; + + public IntegerCastExpression(BaseNode type, string number) : base(NodeType.IntegerCastExpression, type) + { + _number = number; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("("); + Child.Print(writer); + writer.Write(")"); + writer.Write(_number); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs new file mode 100644 index 00000000..762ef7ae --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class IntegerLiteral : BaseNode + { + private readonly string _literalName; + private readonly string _literalValue; + + public IntegerLiteral(string literalName, string literalValue) : base(NodeType.IntegerLiteral) + { + _literalValue = literalValue; + _literalName = literalName; + } + + public override void PrintLeft(TextWriter writer) + { + if (_literalName.Length > 3) + { + writer.Write("("); + writer.Write(_literalName); + writer.Write(")"); + } + + if (_literalValue[0] == 'n') + { + writer.Write("-"); + writer.Write(_literalValue.AsSpan(1)); + } + else + { + writer.Write(_literalValue); + } + + if (_literalName.Length <= 3) + { + writer.Write(_literalName); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs new file mode 100644 index 00000000..f2c934af --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs @@ -0,0 +1,16 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class LiteralOperator : ParentNode + { + public LiteralOperator(BaseNode child) : base(NodeType.LiteralOperator, child) { } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("operator \""); + Child.PrintLeft(writer); + writer.Write("\""); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs new file mode 100644 index 00000000..661043b4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs @@ -0,0 +1,23 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class LocalName : BaseNode + { + private readonly BaseNode _encoding; + private readonly BaseNode _entity; + + public LocalName(BaseNode encoding, BaseNode entity) : base(NodeType.LocalName) + { + _encoding = encoding; + _entity = entity; + } + + public override void PrintLeft(TextWriter writer) + { + _encoding.Print(writer); + writer.Write("::"); + _entity.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs new file mode 100644 index 00000000..6617ec20 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs @@ -0,0 +1,25 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class MemberExpression : BaseNode + { + private readonly BaseNode _leftNode; + private readonly string _kind; + private readonly BaseNode _rightNode; + + public MemberExpression(BaseNode leftNode, string kind, BaseNode rightNode) : base(NodeType.MemberExpression) + { + _leftNode = leftNode; + _kind = kind; + _rightNode = rightNode; + } + + public override void PrintLeft(TextWriter writer) + { + _leftNode.Print(writer); + writer.Write(_kind); + _rightNode.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs new file mode 100644 index 00000000..5ea21a2b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs @@ -0,0 +1,29 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NameType : BaseNode + { + private readonly string _nameValue; + + public NameType(string nameValue, NodeType type) : base(type) + { + _nameValue = nameValue; + } + + public NameType(string nameValue) : base(NodeType.NameType) + { + _nameValue = nameValue; + } + + public override string GetName() + { + return _nameValue; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write(_nameValue); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs new file mode 100644 index 00000000..9512926a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs @@ -0,0 +1,27 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NameTypeWithTemplateArguments : BaseNode + { + private readonly BaseNode _prev; + private readonly BaseNode _templateArgument; + + public NameTypeWithTemplateArguments(BaseNode prev, BaseNode templateArgument) : base(NodeType.NameTypeWithTemplateArguments) + { + _prev = prev; + _templateArgument = templateArgument; + } + + public override string GetName() + { + return _prev.GetName(); + } + + public override void PrintLeft(TextWriter writer) + { + _prev.Print(writer); + _templateArgument.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs new file mode 100644 index 00000000..cadea66c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs @@ -0,0 +1,26 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NestedName : ParentNode + { + private readonly BaseNode _name; + + public NestedName(BaseNode name, BaseNode type) : base(NodeType.NestedName, type) + { + _name = name; + } + + public override string GetName() + { + return _name.GetName(); + } + + public override void PrintLeft(TextWriter writer) + { + Child.Print(writer); + writer.Write("::"); + _name.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs new file mode 100644 index 00000000..4c321afd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs @@ -0,0 +1,55 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NewExpression : BaseNode + { + private readonly NodeArray _expressions; + private readonly BaseNode _typeNode; + private readonly NodeArray _initializers; + + private readonly bool _isGlobal; + private readonly bool _isArrayExpression; + + public NewExpression(NodeArray expressions, BaseNode typeNode, NodeArray initializers, bool isGlobal, bool isArrayExpression) : base(NodeType.NewExpression) + { + _expressions = expressions; + _typeNode = typeNode; + _initializers = initializers; + + _isGlobal = isGlobal; + _isArrayExpression = isArrayExpression; + } + + public override void PrintLeft(TextWriter writer) + { + if (_isGlobal) + { + writer.Write("::operator "); + } + + writer.Write("new "); + + if (_isArrayExpression) + { + writer.Write("[] "); + } + + if (_expressions.Nodes.Count != 0) + { + writer.Write("("); + _expressions.Print(writer); + writer.Write(")"); + } + + _typeNode.Print(writer); + + if (_initializers.Nodes.Count != 0) + { + writer.Write("("); + _initializers.Print(writer); + writer.Write(")"); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs new file mode 100644 index 00000000..395ad1a0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NodeArray : BaseNode + { + public List Nodes { get; protected set; } + + public NodeArray(List nodes) : base(NodeType.NodeArray) + { + Nodes = nodes; + } + + public NodeArray(List nodes, NodeType type) : base(type) + { + Nodes = nodes; + } + + public override bool IsArray() + { + return true; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write(string.Join(", ", Nodes.ToArray())); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs new file mode 100644 index 00000000..49044493 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs @@ -0,0 +1,16 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class NoexceptSpec : ParentNode + { + public NoexceptSpec(BaseNode child) : base(NodeType.NoexceptSpec, child) { } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("noexcept("); + Child.Print(writer); + writer.Write(")"); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs new file mode 100644 index 00000000..23104fff --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PackedTemplateParameter : NodeArray + { + public PackedTemplateParameter(List nodes) : base(nodes, NodeType.PackedTemplateParameter) { } + + public override void PrintLeft(TextWriter writer) + { + foreach (BaseNode node in Nodes) + { + node.PrintLeft(writer); + } + } + + public override void PrintRight(TextWriter writer) + { + foreach (BaseNode node in Nodes) + { + node.PrintLeft(writer); + } + } + + public override bool HasRightPart() + { + foreach (BaseNode node in Nodes) + { + if (node.HasRightPart()) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs new file mode 100644 index 00000000..45e4084a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PackedTemplateParameterExpansion : ParentNode + { + public PackedTemplateParameterExpansion(BaseNode child) : base(NodeType.PackedTemplateParameterExpansion, child) { } + + public override void PrintLeft(TextWriter writer) + { + if (Child is PackedTemplateParameter parameter) + { + if (parameter.Nodes.Count != 0) + { + parameter.Print(writer); + } + } + else + { + writer.Write("..."); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs new file mode 100644 index 00000000..848727db --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public abstract class ParentNode : BaseNode + { + public BaseNode Child { get; private set; } + + public ParentNode(NodeType type, BaseNode child) : base(type) + { + Child = child; + } + + public override string GetName() + { + return Child.GetName(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs new file mode 100644 index 00000000..f331e833 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs @@ -0,0 +1,45 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PointerType : BaseNode + { + private readonly BaseNode _child; + + public PointerType(BaseNode child) : base(NodeType.PointerType) + { + _child = child; + } + + public override bool HasRightPart() + { + return _child.HasRightPart(); + } + + public override void PrintLeft(TextWriter writer) + { + _child.PrintLeft(writer); + if (_child.IsArray()) + { + writer.Write(" "); + } + + if (_child.IsArray() || _child.HasFunctions()) + { + writer.Write("("); + } + + writer.Write("*"); + } + + public override void PrintRight(TextWriter writer) + { + if (_child.IsArray() || _child.HasFunctions()) + { + writer.Write(")"); + } + + _child.PrintRight(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs new file mode 100644 index 00000000..dfbe11ae --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs @@ -0,0 +1,22 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PostfixExpression : ParentNode + { + private readonly string _operator; + + public PostfixExpression(BaseNode type, string Operator) : base(NodeType.PostfixExpression, type) + { + _operator = Operator; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("("); + Child.Print(writer); + writer.Write(")"); + writer.Write(_operator); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs new file mode 100644 index 00000000..ec1dd142 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs @@ -0,0 +1,20 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PostfixQualifiedType : ParentNode + { + private readonly string _postfixQualifier; + + public PostfixQualifiedType(string postfixQualifier, BaseNode type) : base(NodeType.PostfixQualifiedType, type) + { + _postfixQualifier = postfixQualifier; + } + + public override void PrintLeft(TextWriter writer) + { + Child.Print(writer); + writer.Write(_postfixQualifier); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs new file mode 100644 index 00000000..309c575d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs @@ -0,0 +1,22 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class PrefixExpression : ParentNode + { + private readonly string _prefix; + + public PrefixExpression(string prefix, BaseNode child) : base(NodeType.PrefixExpression, child) + { + _prefix = prefix; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write(_prefix); + writer.Write("("); + Child.Print(writer); + writer.Write(")"); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs new file mode 100644 index 00000000..ae43f041 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs @@ -0,0 +1,23 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class QualifiedName : BaseNode + { + private readonly BaseNode _qualifier; + private readonly BaseNode _name; + + public QualifiedName(BaseNode qualifier, BaseNode name) : base(NodeType.QualifiedName) + { + _qualifier = qualifier; + _name = name; + } + + public override void PrintLeft(TextWriter writer) + { + _qualifier.Print(writer); + writer.Write("::"); + _name.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs new file mode 100644 index 00000000..e926c344 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs @@ -0,0 +1,111 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public enum Cv + { + None, + Const, + Volatile, + Restricted = 4, + } + + public enum Reference + { + None, + RValue, + LValue, + } + + public class CvType : ParentNode + { + public Cv Qualifier; + + public CvType(Cv qualifier, BaseNode child) : base(NodeType.CvQualifierType, child) + { + Qualifier = qualifier; + } + + public void PrintQualifier(TextWriter writer) + { + if ((Qualifier & Cv.Const) != 0) + { + writer.Write(" const"); + } + + if ((Qualifier & Cv.Volatile) != 0) + { + writer.Write(" volatile"); + } + + if ((Qualifier & Cv.Restricted) != 0) + { + writer.Write(" restrict"); + } + } + + public override void PrintLeft(TextWriter writer) + { + Child?.PrintLeft(writer); + + PrintQualifier(writer); + } + + public override bool HasRightPart() + { + return Child != null && Child.HasRightPart(); + } + + public override void PrintRight(TextWriter writer) + { + Child?.PrintRight(writer); + } + } + + public class SimpleReferenceType : ParentNode + { + public Reference Qualifier; + + public SimpleReferenceType(Reference qualifier, BaseNode child) : base(NodeType.SimpleReferenceType, child) + { + Qualifier = qualifier; + } + + public void PrintQualifier(TextWriter writer) + { + if ((Qualifier & Reference.LValue) != 0) + { + writer.Write("&"); + } + + if ((Qualifier & Reference.RValue) != 0) + { + writer.Write("&&"); + } + } + + public override void PrintLeft(TextWriter writer) + { + if (Child != null) + { + Child.PrintLeft(writer); + } + else if (Qualifier != Reference.None) + { + writer.Write(" "); + } + + PrintQualifier(writer); + } + + public override bool HasRightPart() + { + return Child != null && Child.HasRightPart(); + } + + public override void PrintRight(TextWriter writer) + { + Child?.PrintRight(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs new file mode 100644 index 00000000..22a601c9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs @@ -0,0 +1,47 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ReferenceType : BaseNode + { + private readonly string _reference; + private readonly BaseNode _child; + + public ReferenceType(string reference, BaseNode child) : base(NodeType.ReferenceType) + { + _reference = reference; + _child = child; + } + + public override bool HasRightPart() + { + return _child.HasRightPart(); + } + + public override void PrintLeft(TextWriter writer) + { + _child.PrintLeft(writer); + + if (_child.IsArray()) + { + writer.Write(" "); + } + + if (_child.IsArray() || _child.HasFunctions()) + { + writer.Write("("); + } + + writer.Write(_reference); + } + public override void PrintRight(TextWriter writer) + { + if (_child.IsArray() || _child.HasFunctions()) + { + writer.Write(")"); + } + + _child.PrintRight(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs new file mode 100644 index 00000000..8a968d63 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs @@ -0,0 +1,20 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class SpecialName : ParentNode + { + private readonly string _specialValue; + + public SpecialName(string specialValue, BaseNode type) : base(NodeType.SpecialName, type) + { + _specialValue = specialValue; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write(_specialValue); + Child.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs new file mode 100644 index 00000000..793d9498 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs @@ -0,0 +1,82 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class SpecialSubstitution : BaseNode + { + public enum SpecialType + { + Allocator, + BasicString, + String, + IStream, + OStream, + IOStream + } + + private readonly SpecialType _specialSubstitutionKey; + + public SpecialSubstitution(SpecialType specialSubstitutionKey) : base(NodeType.SpecialSubstitution) + { + _specialSubstitutionKey = specialSubstitutionKey; + } + + public void SetExtended() + { + Type = NodeType.ExpandedSpecialSubstitution; + } + + public override string GetName() + { + switch (_specialSubstitutionKey) + { + case SpecialType.Allocator: + return "allocator"; + case SpecialType.BasicString: + return "basic_string"; + case SpecialType.String: + if (Type == NodeType.ExpandedSpecialSubstitution) + { + return "basic_string"; + } + + return "string"; + case SpecialType.IStream: + return "istream"; + case SpecialType.OStream: + return "ostream"; + case SpecialType.IOStream: + return "iostream"; + } + + return null; + } + + private string GetExtendedName() + { + return _specialSubstitutionKey switch + { + SpecialType.Allocator => "std::allocator", + SpecialType.BasicString => "std::basic_string", + SpecialType.String => "std::basic_string, std::allocator >", + SpecialType.IStream => "std::basic_istream >", + SpecialType.OStream => "std::basic_ostream >", + SpecialType.IOStream => "std::basic_iostream >", + _ => null, + }; + } + + public override void PrintLeft(TextWriter writer) + { + if (Type == NodeType.ExpandedSpecialSubstitution) + { + writer.Write(GetExtendedName()); + } + else + { + writer.Write("std::"); + writer.Write(GetName()); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs new file mode 100644 index 00000000..c3a97d60 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs @@ -0,0 +1,15 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class StdQualifiedName : ParentNode + { + public StdQualifiedName(BaseNode child) : base(NodeType.StdQualifiedName, child) { } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("std::"); + Child.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs new file mode 100644 index 00000000..d9f25ac6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class TemplateArguments : NodeArray + { + public TemplateArguments(List nodes) : base(nodes, NodeType.TemplateArguments) { } + + public override void PrintLeft(TextWriter writer) + { + string paramsString = string.Join(", ", Nodes.ToArray()); + + writer.Write("<"); + + writer.Write(paramsString); + + if (paramsString.EndsWith('>')) + { + writer.Write(" "); + } + + writer.Write(">"); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs new file mode 100644 index 00000000..4edff138 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs @@ -0,0 +1,20 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast +{ + public class ThrowExpression : BaseNode + { + private readonly BaseNode _expression; + + public ThrowExpression(BaseNode expression) : base(NodeType.ThrowExpression) + { + _expression = expression; + } + + public override void PrintLeft(TextWriter writer) + { + writer.Write("throw "); + _expression.Print(writer); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs new file mode 100644 index 00000000..171a083f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs @@ -0,0 +1,3360 @@ +using Ryujinx.HLE.HOS.Diagnostics.Demangler.Ast; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Diagnostics.Demangler +{ + class Demangler + { + private const string Base36 = "0123456789abcdefghijklmnopqrstuvwxyz"; + private readonly List _substitutionList = new(); + private List _templateParamList = new(); + + private readonly List _forwardTemplateReferenceList = new(); + + public string Mangled { get; private set; } + + private int _position; + private readonly int _length; + + private bool _canForwardTemplateReference; + private bool _canParseTemplateArgs; + + public Demangler(string mangled) + { + Mangled = mangled; + _position = 0; + _length = mangled.Length; + _canParseTemplateArgs = true; + } + + private bool ConsumeIf(string toConsume) + { + var mangledPart = Mangled.AsSpan(_position); + + if (mangledPart.StartsWith(toConsume.AsSpan())) + { + _position += toConsume.Length; + + return true; + } + + return false; + } + + private ReadOnlySpan PeekString(int offset = 0, int length = 1) + { + if (_position + offset >= length) + { + return null; + } + + return Mangled.AsSpan(_position + offset, length); + } + + private char Peek(int offset = 0) + { + if (_position + offset >= _length) + { + return '\0'; + } + + return Mangled[_position + offset]; + } + + private char Consume() + { + if (_position < _length) + { + return Mangled[_position++]; + } + + return '\0'; + } + + private int Count() + { + return _length - _position; + } + + private static int FromBase36(string encoded) + { + char[] reversedEncoded = encoded.ToLower().ToCharArray().Reverse().ToArray(); + + int result = 0; + + for (int i = 0; i < reversedEncoded.Length; i++) + { + int value = Base36.IndexOf(reversedEncoded[i]); + if (value == -1) + { + return -1; + } + + result += value * (int)Math.Pow(36, i); + } + + return result; + } + + private int ParseSeqId() + { + ReadOnlySpan part = Mangled.AsSpan(_position); + int seqIdLen = 0; + + for (; seqIdLen < part.Length; seqIdLen++) + { + if (!char.IsLetterOrDigit(part[seqIdLen])) + { + break; + } + } + + _position += seqIdLen; + + return FromBase36(new string(part[..seqIdLen])); + } + + // ::= S _ + // ::= S_ + // ::= St # std:: + // ::= Sa # std::allocator + // ::= Sb # std::basic_string + // ::= Ss # std::basic_string, std::allocator > + // ::= Si # std::basic_istream > + // ::= So # std::basic_ostream > + // ::= Sd # std::basic_iostream > + private BaseNode ParseSubstitution() + { + if (!ConsumeIf("S")) + { + return null; + } + + char substitutionSecondChar = Peek(); + if (char.IsLower(substitutionSecondChar)) + { + switch (substitutionSecondChar) + { + case 'a': + _position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.Allocator); + case 'b': + _position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.BasicString); + case 's': + _position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.String); + case 'i': + _position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.IStream); + case 'o': + _position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.OStream); + case 'd': + _position++; + return new SpecialSubstitution(SpecialSubstitution.SpecialType.IOStream); + default: + return null; + } + } + + // ::= S_ + if (ConsumeIf("_")) + { + if (_substitutionList.Count != 0) + { + return _substitutionList[0]; + } + + return null; + } + + // ::= S _ + int seqId = ParseSeqId(); + if (seqId < 0) + { + return null; + } + + seqId++; + + if (!ConsumeIf("_") || seqId >= _substitutionList.Count) + { + return null; + } + + return _substitutionList[seqId]; + } + + // NOTE: thoses data aren't used in the output + // ::= h _ + // ::= v _ + // ::= + // # non-virtual base override + // ::= _ + // # virtual base override, with vcall offset + private bool ParseCallOffset() + { + if (ConsumeIf("h")) + { + return ParseNumber(true).Length == 0 || !ConsumeIf("_"); + } + else if (ConsumeIf("v")) + { + return ParseNumber(true).Length == 0 || !ConsumeIf("_") || ParseNumber(true).Length == 0 || !ConsumeIf("_"); + } + + return true; + } + + + // ::= # non-dependent type name, dependent type name, or dependent typename-specifier + // ::= Ts # dependent elaborated type specifier using 'struct' or 'class' + // ::= Tu # dependent elaborated type specifier using 'union' + // ::= Te # dependent elaborated type specifier using 'enum' + private BaseNode ParseClassEnumType() + { + string elaboratedType = null; + + if (ConsumeIf("Ts")) + { + elaboratedType = "struct"; + } + else if (ConsumeIf("Tu")) + { + elaboratedType = "union"; + } + else if (ConsumeIf("Te")) + { + elaboratedType = "enum"; + } + + BaseNode name = ParseName(); + if (name == null) + { + return null; + } + + if (elaboratedType == null) + { + return name; + } + + return new ElaboratedType(elaboratedType, name); + } + + // ::= [] [] [Dx] F [Y] [] E + // ::= + + // # types are possible return type, then parameter types + // ::= Do # non-throwing exception-specification (e.g., noexcept, throw()) + // ::= DO E # computed (instantiation-dependent) noexcept + // ::= Dw + E # dynamic exception specification with instantiation-dependent types + private BaseNode ParseFunctionType() + { + Cv cvQualifiers = ParseCvQualifiers(); + + BaseNode exceptionSpec = null; + + if (ConsumeIf("Do")) + { + exceptionSpec = new NameType("noexcept"); + } + else if (ConsumeIf("DO")) + { + BaseNode expression = ParseExpression(); + if (expression == null || !ConsumeIf("E")) + { + return null; + } + + exceptionSpec = new NoexceptSpec(expression); + } + else if (ConsumeIf("Dw")) + { + List types = new(); + + while (!ConsumeIf("E")) + { + BaseNode type = ParseType(); + if (type == null) + { + return null; + } + + types.Add(type); + } + + exceptionSpec = new DynamicExceptionSpec(new NodeArray(types)); + } + + // We don't need the transaction + ConsumeIf("Dx"); + + if (!ConsumeIf("F")) + { + return null; + } + + // extern "C" + ConsumeIf("Y"); + + BaseNode returnType = ParseType(); + if (returnType == null) + { + return null; + } + + Reference referenceQualifier = Reference.None; + List paramsList = new(); + + while (true) + { + if (ConsumeIf("E")) + { + break; + } + + if (ConsumeIf("v")) + { + continue; + } + + if (ConsumeIf("RE")) + { + referenceQualifier = Reference.LValue; + break; + } + else if (ConsumeIf("OE")) + { + referenceQualifier = Reference.RValue; + break; + } + + BaseNode type = ParseType(); + if (type == null) + { + return null; + } + + paramsList.Add(type); + } + + return new FunctionType(returnType, new NodeArray(paramsList), new CvType(cvQualifiers, null), new SimpleReferenceType(referenceQualifier, null), exceptionSpec); + } + + // ::= A _ + // ::= A [] _ + private BaseNode ParseArrayType() + { + if (!ConsumeIf("A")) + { + return null; + } + + BaseNode elementType; + if (char.IsDigit(Peek())) + { + string dimension = ParseNumber(); + if (dimension.Length == 0 || !ConsumeIf("_")) + { + return null; + } + + elementType = ParseType(); + if (elementType == null) + { + return null; + } + + return new ArrayType(elementType, dimension); + } + + if (!ConsumeIf("_")) + { + BaseNode dimensionExpression = ParseExpression(); + if (dimensionExpression == null || !ConsumeIf("_")) + { + return null; + } + + elementType = ParseType(); + if (elementType == null) + { + return null; + } + + return new ArrayType(elementType, dimensionExpression); + } + + elementType = ParseType(); + if (elementType == null) + { + return null; + } + + return new ArrayType(elementType); + } + + // ::= + // ::= (PARTIAL) + // ::= + // ::= + // ::= (TODO) + // ::= (TODO) + // ::= + // ::= + // ::= + // ::= P # pointer + // ::= R # l-value reference + // ::= O # r-value reference (C++11) + // ::= C # complex pair (C99) + // ::= G # imaginary (C99) + // ::= # See Compression below + private BaseNode ParseType(NameParserContext context = null) + { + // Temporary context + context ??= new NameParserContext(); + + BaseNode result; + switch (Peek()) + { + case 'r': + case 'V': + case 'K': + int typePos = 0; + + if (Peek(typePos) == 'r') + { + typePos++; + } + + if (Peek(typePos) == 'V') + { + typePos++; + } + + if (Peek(typePos) == 'K') + { + typePos++; + } + + if (Peek(typePos) == 'F' || (Peek(typePos) == 'D' && (Peek(typePos + 1) == 'o' || Peek(typePos + 1) == 'O' || Peek(typePos + 1) == 'w' || Peek(typePos + 1) == 'x'))) + { + result = ParseFunctionType(); + break; + } + + Cv cv = ParseCvQualifiers(); + + result = ParseType(context); + + if (result == null) + { + return null; + } + + result = new CvType(cv, result); + break; + case 'U': + // TODO: + return null; + case 'v': + _position++; + return new NameType("void"); + case 'w': + _position++; + return new NameType("wchar_t"); + case 'b': + _position++; + return new NameType("bool"); + case 'c': + _position++; + return new NameType("char"); + case 'a': + _position++; + return new NameType("signed char"); + case 'h': + _position++; + return new NameType("unsigned char"); + case 's': + _position++; + return new NameType("short"); + case 't': + _position++; + return new NameType("unsigned short"); + case 'i': + _position++; + return new NameType("int"); + case 'j': + _position++; + return new NameType("unsigned int"); + case 'l': + _position++; + return new NameType("long"); + case 'm': + _position++; + return new NameType("unsigned long"); + case 'x': + _position++; + return new NameType("long long"); + case 'y': + _position++; + return new NameType("unsigned long long"); + case 'n': + _position++; + return new NameType("__int128"); + case 'o': + _position++; + return new NameType("unsigned __int128"); + case 'f': + _position++; + return new NameType("float"); + case 'd': + _position++; + return new NameType("double"); + case 'e': + _position++; + return new NameType("long double"); + case 'g': + _position++; + return new NameType("__float128"); + case 'z': + _position++; + return new NameType("..."); + case 'u': + _position++; + return ParseSourceName(); + case 'D': + switch (Peek(1)) + { + case 'd': + _position += 2; + return new NameType("decimal64"); + case 'e': + _position += 2; + return new NameType("decimal128"); + case 'f': + _position += 2; + return new NameType("decimal32"); + case 'h': + _position += 2; + // FIXME: GNU c++flit returns this but that is not what is supposed to be returned. + return new NameType("half"); // return new NameType("decimal16"); + case 'i': + _position += 2; + return new NameType("char32_t"); + case 's': + _position += 2; + return new NameType("char16_t"); + case 'a': + _position += 2; + return new NameType("decltype(auto)"); + case 'n': + _position += 2; + // FIXME: GNU c++flit returns this but that is not what is supposed to be returned. + return new NameType("decltype(nullptr)"); // return new NameType("std::nullptr_t"); + case 't': + case 'T': + _position += 2; + result = ParseDecltype(); + break; + case 'o': + case 'O': + case 'w': + case 'x': + result = ParseFunctionType(); + break; + default: + return null; + } + break; + case 'F': + result = ParseFunctionType(); + break; + case 'A': + return ParseArrayType(); + case 'M': + // TODO: + _position++; + return null; + case 'T': + // might just be a class enum type + if (Peek(1) == 's' || Peek(1) == 'u' || Peek(1) == 'e') + { + result = ParseClassEnumType(); + break; + } + + result = ParseTemplateParam(); + if (result == null) + { + return null; + } + + if (_canParseTemplateArgs && Peek() == 'I') + { + BaseNode templateArguments = ParseTemplateArguments(); + if (templateArguments == null) + { + return null; + } + + result = new NameTypeWithTemplateArguments(result, templateArguments); + } + break; + case 'P': + _position++; + result = ParseType(context); + + if (result == null) + { + return null; + } + + result = new PointerType(result); + break; + case 'R': + _position++; + result = ParseType(context); + + if (result == null) + { + return null; + } + + result = new ReferenceType("&", result); + break; + case 'O': + _position++; + result = ParseType(context); + + if (result == null) + { + return null; + } + + result = new ReferenceType("&&", result); + break; + case 'C': + _position++; + result = ParseType(context); + + if (result == null) + { + return null; + } + + result = new PostfixQualifiedType(" complex", result); + break; + case 'G': + _position++; + result = ParseType(context); + + if (result == null) + { + return null; + } + + result = new PostfixQualifiedType(" imaginary", result); + break; + case 'S': + if (Peek(1) != 't') + { + BaseNode substitution = ParseSubstitution(); + if (substitution == null) + { + return null; + } + + if (_canParseTemplateArgs && Peek() == 'I') + { + BaseNode templateArgument = ParseTemplateArgument(); + if (templateArgument == null) + { + return null; + } + + result = new NameTypeWithTemplateArguments(substitution, templateArgument); + break; + } + return substitution; + } + else + { + result = ParseClassEnumType(); + break; + } + default: + result = ParseClassEnumType(); + break; + } + if (result != null) + { + _substitutionList.Add(result); + } + + return result; + } + + // ::= TV # virtual table + // ::= TT # VTT structure (construction vtable index) + // ::= TI # typeinfo structure + // ::= TS # typeinfo name (null-terminated byte string) + // ::= Tc + // ::= TW # Thread-local wrapper + // ::= TH # Thread-local initialization + // ::= T + // # base is the nominal target function of thunk + // ::= GV # Guard variable for one-time initialization + private BaseNode ParseSpecialName(NameParserContext context = null) + { + if (Peek() != 'T') + { + if (ConsumeIf("GV")) + { + BaseNode name = ParseName(); + if (name == null) + { + return null; + } + + return new SpecialName("guard variable for ", name); + } + return null; + } + + BaseNode node; + switch (Peek(1)) + { + // ::= TV # virtual table + case 'V': + _position += 2; + node = ParseType(context); + if (node == null) + { + return null; + } + + return new SpecialName("vtable for ", node); + // ::= TT # VTT structure (construction vtable index) + case 'T': + _position += 2; + node = ParseType(context); + if (node == null) + { + return null; + } + + return new SpecialName("VTT for ", node); + // ::= TI # typeinfo structure + case 'I': + _position += 2; + node = ParseType(context); + if (node == null) + { + return null; + } + + return new SpecialName("typeinfo for ", node); + // ::= TS # typeinfo name (null-terminated byte string) + case 'S': + _position += 2; + node = ParseType(context); + if (node == null) + { + return null; + } + + return new SpecialName("typeinfo name for ", node); + // ::= Tc + case 'c': + _position += 2; + if (ParseCallOffset() || ParseCallOffset()) + { + return null; + } + + node = ParseEncoding(); + if (node == null) + { + return null; + } + + return new SpecialName("covariant return thunk to ", node); + // extension ::= TC _ + case 'C': + _position += 2; + BaseNode firstType = ParseType(); + if (firstType == null || ParseNumber(true).Length == 0 || !ConsumeIf("_")) + { + return null; + } + + BaseNode secondType = ParseType(); + + return new CtorVtableSpecialName(secondType, firstType); + // ::= TH # Thread-local initialization + case 'H': + _position += 2; + node = ParseName(); + if (node == null) + { + return null; + } + + return new SpecialName("thread-local initialization routine for ", node); + // ::= TW # Thread-local wrapper + case 'W': + _position += 2; + node = ParseName(); + if (node == null) + { + return null; + } + + return new SpecialName("thread-local wrapper routine for ", node); + default: + _position++; + bool isVirtual = Peek() == 'v'; + if (ParseCallOffset()) + { + return null; + } + + node = ParseEncoding(); + if (node == null) + { + return null; + } + + if (isVirtual) + { + return new SpecialName("virtual thunk to ", node); + } + + return new SpecialName("non-virtual thunk to ", node); + } + } + + // ::= [r] [V] [K] # restrict (C99), volatile, const + private Cv ParseCvQualifiers() + { + Cv qualifiers = Cv.None; + + if (ConsumeIf("r")) + { + qualifiers |= Cv.Restricted; + } + if (ConsumeIf("V")) + { + qualifiers |= Cv.Volatile; + } + if (ConsumeIf("K")) + { + qualifiers |= Cv.Const; + } + + return qualifiers; + } + + + // ::= R # & ref-qualifier + // ::= O # && ref-qualifier + private SimpleReferenceType ParseRefQualifiers() + { + Reference result = Reference.None; + if (ConsumeIf("O")) + { + result = Reference.RValue; + } + else if (ConsumeIf("R")) + { + result = Reference.LValue; + } + return new SimpleReferenceType(result, null); + } + + private static BaseNode CreateNameNode(BaseNode prev, BaseNode name, NameParserContext context) + { + BaseNode result = name; + if (prev != null) + { + result = new NestedName(name, prev); + } + + if (context != null) + { + context.FinishWithTemplateArguments = false; + } + + return result; + } + + private int ParsePositiveNumber() + { + ReadOnlySpan part = Mangled.AsSpan(_position); + int numberLength = 0; + + for (; numberLength < part.Length; numberLength++) + { + if (!char.IsDigit(part[numberLength])) + { + break; + } + } + + _position += numberLength; + + if (numberLength == 0) + { + return -1; + } + + return int.Parse(part[..numberLength]); + } + + private string ParseNumber(bool isSigned = false) + { + if (isSigned) + { + ConsumeIf("n"); + } + + if (Count() == 0 || !char.IsDigit(Mangled[_position])) + { + return null; + } + + ReadOnlySpan part = Mangled.AsSpan(_position); + int numberLength = 0; + + for (; numberLength < part.Length; numberLength++) + { + if (!char.IsDigit(part[numberLength])) + { + break; + } + } + + _position += numberLength; + + return new string(part[..numberLength]); + } + + // ::= + private BaseNode ParseSourceName() + { + int length = ParsePositiveNumber(); + if (Count() < length || length <= 0) + { + return null; + } + + string name = Mangled.Substring(_position, length); + _position += length; + if (name.StartsWith("_GLOBAL__N")) + { + return new NameType("(anonymous namespace)"); + } + + return new NameType(name); + } + + // ::= nw # new + // ::= na # new[] + // ::= dl # delete + // ::= da # delete[] + // ::= ps # + (unary) + // ::= ng # - (unary) + // ::= ad # & (unary) + // ::= de # * (unary) + // ::= co # ~ + // ::= pl # + + // ::= mi # - + // ::= ml # * + // ::= dv # / + // ::= rm # % + // ::= an # & + // ::= or # | + // ::= eo # ^ + // ::= aS # = + // ::= pL # += + // ::= mI # -= + // ::= mL # *= + // ::= dV # /= + // ::= rM # %= + // ::= aN # &= + // ::= oR # |= + // ::= eO # ^= + // ::= ls # << + // ::= rs # >> + // ::= lS # <<= + // ::= rS # >>= + // ::= eq # == + // ::= ne # != + // ::= lt # < + // ::= gt # > + // ::= le # <= + // ::= ge # >= + // ::= ss # <=> + // ::= nt # ! + // ::= aa # && + // ::= oo # || + // ::= pp # ++ (postfix in context) + // ::= mm # -- (postfix in context) + // ::= cm # , + // ::= pm # ->* + // ::= pt # -> + // ::= cl # () + // ::= ix # [] + // ::= qu # ? + // ::= cv # (cast) (TODO) + // ::= li # operator "" + // ::= v # vendor extended operator (TODO) + private BaseNode ParseOperatorName(NameParserContext context) + { + switch (Peek()) + { + case 'a': + switch (Peek(1)) + { + case 'a': + _position += 2; + return new NameType("operator&&"); + case 'd': + case 'n': + _position += 2; + return new NameType("operator&"); + case 'N': + _position += 2; + return new NameType("operator&="); + case 'S': + _position += 2; + return new NameType("operator="); + default: + return null; + } + case 'c': + switch (Peek(1)) + { + case 'l': + _position += 2; + return new NameType("operator()"); + case 'm': + _position += 2; + return new NameType("operator,"); + case 'o': + _position += 2; + return new NameType("operator~"); + case 'v': + _position += 2; + + bool canParseTemplateArgsBackup = _canParseTemplateArgs; + bool canForwardTemplateReferenceBackup = _canForwardTemplateReference; + + _canParseTemplateArgs = false; + _canForwardTemplateReference = canForwardTemplateReferenceBackup || context != null; + + BaseNode type = ParseType(); + + _canParseTemplateArgs = canParseTemplateArgsBackup; + _canForwardTemplateReference = canForwardTemplateReferenceBackup; + + if (type == null) + { + return null; + } + + if (context != null) + { + context.CtorDtorConversion = true; + } + + return new ConversionOperatorType(type); + default: + return null; + } + case 'd': + switch (Peek(1)) + { + case 'a': + _position += 2; + return new NameType("operator delete[]"); + case 'e': + _position += 2; + return new NameType("operator*"); + case 'l': + _position += 2; + return new NameType("operator delete"); + case 'v': + _position += 2; + return new NameType("operator/"); + case 'V': + _position += 2; + return new NameType("operator/="); + default: + return null; + } + case 'e': + switch (Peek(1)) + { + case 'o': + _position += 2; + return new NameType("operator^"); + case 'O': + _position += 2; + return new NameType("operator^="); + case 'q': + _position += 2; + return new NameType("operator=="); + default: + return null; + } + case 'g': + switch (Peek(1)) + { + case 'e': + _position += 2; + return new NameType("operator>="); + case 't': + _position += 2; + return new NameType("operator>"); + default: + return null; + } + case 'i': + if (Peek(1) == 'x') + { + _position += 2; + return new NameType("operator[]"); + } + return null; + case 'l': + switch (Peek(1)) + { + case 'e': + _position += 2; + return new NameType("operator<="); + case 'i': + _position += 2; + BaseNode sourceName = ParseSourceName(); + if (sourceName == null) + { + return null; + } + + return new LiteralOperator(sourceName); + case 's': + _position += 2; + return new NameType("operator<<"); + case 'S': + _position += 2; + return new NameType("operator<<="); + case 't': + _position += 2; + return new NameType("operator<"); + default: + return null; + } + case 'm': + switch (Peek(1)) + { + case 'i': + _position += 2; + return new NameType("operator-"); + case 'I': + _position += 2; + return new NameType("operator-="); + case 'l': + _position += 2; + return new NameType("operator*"); + case 'L': + _position += 2; + return new NameType("operator*="); + case 'm': + _position += 2; + return new NameType("operator--"); + default: + return null; + } + case 'n': + switch (Peek(1)) + { + case 'a': + _position += 2; + return new NameType("operator new[]"); + case 'e': + _position += 2; + return new NameType("operator!="); + case 'g': + _position += 2; + return new NameType("operator-"); + case 't': + _position += 2; + return new NameType("operator!"); + case 'w': + _position += 2; + return new NameType("operator new"); + default: + return null; + } + case 'o': + switch (Peek(1)) + { + case 'o': + _position += 2; + return new NameType("operator||"); + case 'r': + _position += 2; + return new NameType("operator|"); + case 'R': + _position += 2; + return new NameType("operator|="); + default: + return null; + } + case 'p': + switch (Peek(1)) + { + case 'm': + _position += 2; + return new NameType("operator->*"); + case 's': + case 'l': + _position += 2; + return new NameType("operator+"); + case 'L': + _position += 2; + return new NameType("operator+="); + case 'p': + _position += 2; + return new NameType("operator++"); + case 't': + _position += 2; + return new NameType("operator->"); + default: + return null; + } + case 'q': + if (Peek(1) == 'u') + { + _position += 2; + return new NameType("operator?"); + } + return null; + case 'r': + switch (Peek(1)) + { + case 'm': + _position += 2; + return new NameType("operator%"); + case 'M': + _position += 2; + return new NameType("operator%="); + case 's': + _position += 2; + return new NameType("operator>>"); + case 'S': + _position += 2; + return new NameType("operator>>="); + default: + return null; + } + case 's': + if (Peek(1) == 's') + { + _position += 2; + return new NameType("operator<=>"); + } + return null; + case 'v': + // TODO: ::= v # vendor extended operator + return null; + default: + return null; + } + } + + // ::= [ (TODO)] + // ::= (TODO) + // ::= + // ::= (TODO) + // ::= DC + E # structured binding declaration (TODO) + private BaseNode ParseUnqualifiedName(NameParserContext context) + { + BaseNode result = null; + char c = Peek(); + if (c == 'U') + { + // TODO: Unnamed Type Name + // throw new Exception("Unnamed Type Name not implemented"); + } + else if (char.IsDigit(c)) + { + result = ParseSourceName(); + } + else if (ConsumeIf("DC")) + { + // TODO: Structured Binding Declaration + // throw new Exception("Structured Binding Declaration not implemented"); + } + else + { + result = ParseOperatorName(context); + } + + if (result != null) + { + // TODO: ABI Tags + // throw new Exception("ABI Tags not implemented"); + } + return result; + } + + // ::= C1 # complete object constructor + // ::= C2 # base object constructor + // ::= C3 # complete object allocating constructor + // ::= D0 # deleting destructor + // ::= D1 # complete object destructor + // ::= D2 # base object destructor + private BaseNode ParseCtorDtorName(NameParserContext context, BaseNode prev) + { + if (prev.Type == NodeType.SpecialSubstitution && prev is SpecialSubstitution substitution) + { + substitution.SetExtended(); + } + + if (ConsumeIf("C")) + { + bool isInherited = ConsumeIf("I"); + + char ctorDtorType = Peek(); + if (ctorDtorType != '1' && ctorDtorType != '2' && ctorDtorType != '3') + { + return null; + } + + _position++; + + if (context != null) + { + context.CtorDtorConversion = true; + } + + if (isInherited && ParseName(context) == null) + { + return null; + } + + return new CtorDtorNameType(prev, false); + } + + if (ConsumeIf("D")) + { + char c = Peek(); + if (c != '0' && c != '1' && c != '2') + { + return null; + } + + _position++; + + if (context != null) + { + context.CtorDtorConversion = true; + } + + return new CtorDtorNameType(prev, true); + } + + return null; + } + + // ::= fp _ # L == 0, first parameter + // ::= fp _ # L == 0, second and later parameters + // ::= fL p _ # L > 0, first parameter + // ::= fL p _ # L > 0, second and later parameters + private BaseNode ParseFunctionParameter() + { + if (ConsumeIf("fp")) + { + // ignored + ParseCvQualifiers(); + + if (!ConsumeIf("_")) + { + return null; + } + + return new FunctionParameter(ParseNumber()); + } + else if (ConsumeIf("fL")) + { + string l1Number = ParseNumber(); + if (l1Number == null || l1Number.Length == 0) + { + return null; + } + + if (!ConsumeIf("p")) + { + return null; + } + + // ignored + ParseCvQualifiers(); + + if (!ConsumeIf("_")) + { + return null; + } + + return new FunctionParameter(ParseNumber()); + } + + return null; + } + + // ::= fL + // ::= fR + // ::= fl + // ::= fr + private BaseNode ParseFoldExpression() + { + if (!ConsumeIf("f")) + { + return null; + } + + char foldKind = Peek(); + bool hasInitializer = foldKind == 'L' || foldKind == 'R'; + bool isLeftFold = foldKind == 'l' || foldKind == 'L'; + + if (!isLeftFold && !(foldKind == 'r' || foldKind == 'R')) + { + return null; + } + + _position++; + + string operatorName; + + switch (PeekString(0, 2)) + { + case "aa": + operatorName = "&&"; + break; + case "an": + operatorName = "&"; + break; + case "aN": + operatorName = "&="; + break; + case "aS": + operatorName = "="; + break; + case "cm": + operatorName = ","; + break; + case "ds": + operatorName = ".*"; + break; + case "dv": + operatorName = "/"; + break; + case "dV": + operatorName = "/="; + break; + case "eo": + operatorName = "^"; + break; + case "eO": + operatorName = "^="; + break; + case "eq": + operatorName = "=="; + break; + case "ge": + operatorName = ">="; + break; + case "gt": + operatorName = ">"; + break; + case "le": + operatorName = "<="; + break; + case "ls": + operatorName = "<<"; + break; + case "lS": + operatorName = "<<="; + break; + case "lt": + operatorName = "<"; + break; + case "mi": + operatorName = "-"; + break; + case "mI": + operatorName = "-="; + break; + case "ml": + operatorName = "*"; + break; + case "mL": + operatorName = "*="; + break; + case "ne": + operatorName = "!="; + break; + case "oo": + operatorName = "||"; + break; + case "or": + operatorName = "|"; + break; + case "oR": + operatorName = "|="; + break; + case "pl": + operatorName = "+"; + break; + case "pL": + operatorName = "+="; + break; + case "rm": + operatorName = "%"; + break; + case "rM": + operatorName = "%="; + break; + case "rs": + operatorName = ">>"; + break; + case "rS": + operatorName = ">>="; + break; + default: + return null; + } + + _position += 2; + + BaseNode expression = ParseExpression(); + if (expression == null) + { + return null; + } + + BaseNode initializer = null; + + if (hasInitializer) + { + initializer = ParseExpression(); + if (initializer == null) + { + return null; + } + } + + if (isLeftFold && initializer != null) + { + (initializer, expression) = (expression, initializer); + } + + return new FoldExpression(isLeftFold, operatorName, new PackedTemplateParameterExpansion(expression), initializer); + } + + + // ::= cv # type (expression), conversion with one argument + // ::= cv _ * E # type (expr-list), conversion with other than one argument + private BaseNode ParseConversionExpression() + { + if (!ConsumeIf("cv")) + { + return null; + } + + bool canParseTemplateArgsBackup = _canParseTemplateArgs; + _canParseTemplateArgs = false; + BaseNode type = ParseType(); + _canParseTemplateArgs = canParseTemplateArgsBackup; + + if (type == null) + { + return null; + } + + List expressions = new(); + if (ConsumeIf("_")) + { + while (!ConsumeIf("E")) + { + BaseNode expression = ParseExpression(); + if (expression == null) + { + return null; + } + + expressions.Add(expression); + } + } + else + { + BaseNode expression = ParseExpression(); + if (expression == null) + { + return null; + } + + expressions.Add(expression); + } + + return new ConversionExpression(type, new NodeArray(expressions)); + } + + private BaseNode ParseBinaryExpression(string name) + { + BaseNode leftPart = ParseExpression(); + if (leftPart == null) + { + return null; + } + + BaseNode rightPart = ParseExpression(); + if (rightPart == null) + { + return null; + } + + return new BinaryExpression(leftPart, name, rightPart); + } + + private BaseNode ParsePrefixExpression(string name) + { + BaseNode expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new PrefixExpression(name, expression); + } + + + // ::= + // ::= di # .name = expr + // ::= dx # [expr] = expr + // ::= dX + // # [expr ... expr] = expr + private BaseNode ParseBracedExpression() + { + if (Peek() == 'd') + { + BaseNode bracedExpressionNode; + switch (Peek(1)) + { + case 'i': + _position += 2; + BaseNode field = ParseSourceName(); + if (field == null) + { + return null; + } + + bracedExpressionNode = ParseBracedExpression(); + if (bracedExpressionNode == null) + { + return null; + } + + return new BracedExpression(field, bracedExpressionNode, false); + case 'x': + _position += 2; + BaseNode index = ParseExpression(); + if (index == null) + { + return null; + } + + bracedExpressionNode = ParseBracedExpression(); + if (bracedExpressionNode == null) + { + return null; + } + + return new BracedExpression(index, bracedExpressionNode, true); + case 'X': + _position += 2; + BaseNode rangeBeginExpression = ParseExpression(); + if (rangeBeginExpression == null) + { + return null; + } + + BaseNode rangeEndExpression = ParseExpression(); + if (rangeEndExpression == null) + { + return null; + } + + bracedExpressionNode = ParseBracedExpression(); + if (bracedExpressionNode == null) + { + return null; + } + + return new BracedRangeExpression(rangeBeginExpression, rangeEndExpression, bracedExpressionNode); + } + } + + return ParseExpression(); + } + + // ::= [gs] nw * _ E # new (expr-list) type + // ::= [gs] nw * _ # new (expr-list) type (init) + // ::= [gs] na * _ E # new[] (expr-list) type + // ::= [gs] na * _ # new[] (expr-list) type (init) + // + // ::= pi * E # parenthesized initialization + private BaseNode ParseNewExpression() + { + bool isGlobal = ConsumeIf("gs"); + bool isArray = Peek(1) == 'a'; + + if (!ConsumeIf("nw") || !ConsumeIf("na")) + { + return null; + } + + List expressions = new(); + List initializers = new(); + + while (!ConsumeIf("_")) + { + BaseNode expression = ParseExpression(); + if (expression == null) + { + return null; + } + + expressions.Add(expression); + } + + BaseNode typeNode = ParseType(); + if (typeNode == null) + { + return null; + } + + if (ConsumeIf("pi")) + { + while (!ConsumeIf("E")) + { + BaseNode initializer = ParseExpression(); + if (initializer == null) + { + return null; + } + + initializers.Add(initializer); + } + } + else if (!ConsumeIf("E")) + { + return null; + } + + return new NewExpression(new NodeArray(expressions), typeNode, new NodeArray(initializers), isGlobal, isArray); + } + + + // ::= + // ::= + // ::= + // ::= pp_ # prefix ++ + // ::= mm_ # prefix -- + // ::= cl + E # expression (expr-list), call + // ::= cv # type (expression), conversion with one argument + // ::= cv _ * E # type (expr-list), conversion with other than one argument + // ::= tl * E # type {expr-list}, conversion with braced-init-list argument + // ::= il * E # {expr-list}, braced-init-list in any other context + // ::= [gs] nw * _ E # new (expr-list) type + // ::= [gs] nw * _ # new (expr-list) type (init) + // ::= [gs] na * _ E # new[] (expr-list) type + // ::= [gs] na * _ # new[] (expr-list) type (init) + // ::= [gs] dl # delete expression + // ::= [gs] da # delete[] expression + // ::= dc # dynamic_cast (expression) + // ::= sc # static_cast (expression) + // ::= cc # const_cast (expression) + // ::= rc # reinterpret_cast (expression) + // ::= ti # typeid (type) + // ::= te # typeid (expression) + // ::= st # sizeof (type) + // ::= sz # sizeof (expression) + // ::= at # alignof (type) + // ::= az # alignof (expression) + // ::= nx # noexcept (expression) + // ::= + // ::= + // ::= dt # expr.name + // ::= pt # expr->name + // ::= ds # expr.*expr + // ::= sZ # sizeof...(T), size of a template parameter pack + // ::= sZ # sizeof...(parameter), size of a function parameter pack + // ::= sP * E # sizeof...(T), size of a captured template parameter pack from an alias template + // ::= sp # expression..., pack expansion + // ::= tw # throw expression + // ::= tr # throw with no operand (rethrow) + // ::= # f(p), N::f(p), ::f(p), + // # freestanding dependent name (e.g., T::x), + // # objectless nonstatic member reference + // ::= + private BaseNode ParseExpression() + { + bool isGlobal = ConsumeIf("gs"); + BaseNode expression; + if (Count() < 2) + { + return null; + } + + switch (Peek()) + { + case 'L': + return ParseExpressionPrimary(); + case 'T': + return ParseTemplateParam(); + case 'f': + char c = Peek(1); + if (c == 'p' || (c == 'L' && char.IsDigit(Peek(2)))) + { + return ParseFunctionParameter(); + } + + return ParseFoldExpression(); + case 'a': + switch (Peek(1)) + { + case 'a': + _position += 2; + return ParseBinaryExpression("&&"); + case 'd': + case 'n': + _position += 2; + return ParseBinaryExpression("&"); + case 'N': + _position += 2; + return ParseBinaryExpression("&="); + case 'S': + _position += 2; + return ParseBinaryExpression("="); + case 't': + _position += 2; + BaseNode type = ParseType(); + if (type == null) + { + return null; + } + + return new EnclosedExpression("alignof (", type, ")"); + case 'z': + _position += 2; + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new EnclosedExpression("alignof (", expression, ")"); + } + return null; + case 'c': + switch (Peek(1)) + { + case 'c': + _position += 2; + BaseNode to = ParseType(); + if (to == null) + { + return null; + } + + BaseNode from = ParseExpression(); + if (from == null) + { + return null; + } + + return new CastExpression("const_cast", to, from); + case 'l': + _position += 2; + BaseNode callee = ParseExpression(); + if (callee == null) + { + return null; + } + + List names = new(); + while (!ConsumeIf("E")) + { + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + names.Add(expression); + } + return new CallExpression(callee, names); + case 'm': + _position += 2; + return ParseBinaryExpression(","); + case 'o': + _position += 2; + return ParsePrefixExpression("~"); + case 'v': + return ParseConversionExpression(); + } + return null; + case 'd': + BaseNode leftNode; + BaseNode rightNode; + switch (Peek(1)) + { + case 'a': + _position += 2; + expression = ParseExpression(); + if (expression == null) + { + return expression; + } + + return new DeleteExpression(expression, isGlobal, true); + case 'c': + _position += 2; + BaseNode type = ParseType(); + if (type == null) + { + return null; + } + + expression = ParseExpression(); + if (expression == null) + { + return expression; + } + + return new CastExpression("dynamic_cast", type, expression); + case 'e': + _position += 2; + return ParsePrefixExpression("*"); + case 'l': + _position += 2; + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new DeleteExpression(expression, isGlobal, false); + case 'n': + return ParseUnresolvedName(); + case 's': + _position += 2; + leftNode = ParseExpression(); + if (leftNode == null) + { + return null; + } + + rightNode = ParseExpression(); + if (rightNode == null) + { + return null; + } + + return new MemberExpression(leftNode, ".*", rightNode); + case 't': + _position += 2; + leftNode = ParseExpression(); + if (leftNode == null) + { + return null; + } + + rightNode = ParseExpression(); + if (rightNode == null) + { + return null; + } + + return new MemberExpression(leftNode, ".", rightNode); + case 'v': + _position += 2; + return ParseBinaryExpression("/"); + case 'V': + _position += 2; + return ParseBinaryExpression("/="); + } + return null; + case 'e': + switch (Peek(1)) + { + case 'o': + _position += 2; + return ParseBinaryExpression("^"); + case 'O': + _position += 2; + return ParseBinaryExpression("^="); + case 'q': + _position += 2; + return ParseBinaryExpression("=="); + } + return null; + case 'g': + switch (Peek(1)) + { + case 'e': + _position += 2; + return ParseBinaryExpression(">="); + case 't': + _position += 2; + return ParseBinaryExpression(">"); + } + return null; + case 'i': + switch (Peek(1)) + { + case 'x': + _position += 2; + BaseNode Base = ParseExpression(); + if (Base == null) + { + return null; + } + + BaseNode subscript = ParseExpression(); + if (Base == null) + { + return null; + } + + return new ArraySubscriptingExpression(Base, subscript); + case 'l': + _position += 2; + + List bracedExpressions = new(); + while (!ConsumeIf("E")) + { + expression = ParseBracedExpression(); + if (expression == null) + { + return null; + } + + bracedExpressions.Add(expression); + } + return new InitListExpression(null, bracedExpressions); + } + return null; + case 'l': + switch (Peek(1)) + { + case 'e': + _position += 2; + return ParseBinaryExpression("<="); + case 's': + _position += 2; + return ParseBinaryExpression("<<"); + case 'S': + _position += 2; + return ParseBinaryExpression("<<="); + case 't': + _position += 2; + return ParseBinaryExpression("<"); + } + return null; + case 'm': + switch (Peek(1)) + { + case 'i': + _position += 2; + return ParseBinaryExpression("-"); + case 'I': + _position += 2; + return ParseBinaryExpression("-="); + case 'l': + _position += 2; + return ParseBinaryExpression("*"); + case 'L': + _position += 2; + return ParseBinaryExpression("*="); + case 'm': + _position += 2; + if (ConsumeIf("_")) + { + return ParsePrefixExpression("--"); + } + + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new PostfixExpression(expression, "--"); + } + return null; + case 'n': + switch (Peek(1)) + { + case 'a': + case 'w': + _position += 2; + return ParseNewExpression(); + case 'e': + _position += 2; + return ParseBinaryExpression("!="); + case 'g': + _position += 2; + return ParsePrefixExpression("-"); + case 't': + _position += 2; + return ParsePrefixExpression("!"); + case 'x': + _position += 2; + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new EnclosedExpression("noexcept (", expression, ")"); + } + return null; + case 'o': + switch (Peek(1)) + { + case 'n': + return ParseUnresolvedName(); + case 'o': + _position += 2; + return ParseBinaryExpression("||"); + case 'r': + _position += 2; + return ParseBinaryExpression("|"); + case 'R': + _position += 2; + return ParseBinaryExpression("|="); + } + return null; + case 'p': + switch (Peek(1)) + { + case 'm': + _position += 2; + return ParseBinaryExpression("->*"); + case 'l': + case 's': + _position += 2; + return ParseBinaryExpression("+"); + case 'L': + _position += 2; + return ParseBinaryExpression("+="); + case 'p': + _position += 2; + if (ConsumeIf("_")) + { + return ParsePrefixExpression("++"); + } + + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new PostfixExpression(expression, "++"); + case 't': + _position += 2; + leftNode = ParseExpression(); + if (leftNode == null) + { + return null; + } + + rightNode = ParseExpression(); + if (rightNode == null) + { + return null; + } + + return new MemberExpression(leftNode, "->", rightNode); + } + return null; + case 'q': + if (Peek(1) == 'u') + { + _position += 2; + BaseNode condition = ParseExpression(); + if (condition == null) + { + return null; + } + + leftNode = ParseExpression(); + if (leftNode == null) + { + return null; + } + + rightNode = ParseExpression(); + if (rightNode == null) + { + return null; + } + + return new ConditionalExpression(condition, leftNode, rightNode); + } + return null; + case 'r': + switch (Peek(1)) + { + case 'c': + _position += 2; + BaseNode to = ParseType(); + if (to == null) + { + return null; + } + + BaseNode from = ParseExpression(); + if (from == null) + { + return null; + } + + return new CastExpression("reinterpret_cast", to, from); + case 'm': + _position += 2; + return ParseBinaryExpression("%"); + case 'M': + _position += 2; + return ParseBinaryExpression("%"); + case 's': + _position += 2; + return ParseBinaryExpression(">>"); + case 'S': + _position += 2; + return ParseBinaryExpression(">>="); + } + return null; + case 's': + switch (Peek(1)) + { + case 'c': + _position += 2; + BaseNode to = ParseType(); + if (to == null) + { + return null; + } + + BaseNode from = ParseExpression(); + if (from == null) + { + return null; + } + + return new CastExpression("static_cast", to, from); + case 'p': + _position += 2; + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new PackedTemplateParameterExpansion(expression); + case 'r': + return ParseUnresolvedName(); + case 't': + _position += 2; + BaseNode enclosedType = ParseType(); + if (enclosedType == null) + { + return null; + } + + return new EnclosedExpression("sizeof (", enclosedType, ")"); + case 'z': + _position += 2; + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new EnclosedExpression("sizeof (", expression, ")"); + case 'Z': + _position += 2; + BaseNode sizeofParamNode; + switch (Peek()) + { + case 'T': + // FIXME: ??? Not entire sure if it's right + sizeofParamNode = ParseFunctionParameter(); + if (sizeofParamNode == null) + { + return null; + } + + return new EnclosedExpression("sizeof...(", new PackedTemplateParameterExpansion(sizeofParamNode), ")"); + case 'f': + sizeofParamNode = ParseFunctionParameter(); + if (sizeofParamNode == null) + { + return null; + } + + return new EnclosedExpression("sizeof...(", sizeofParamNode, ")"); + } + return null; + case 'P': + _position += 2; + List arguments = new(); + while (!ConsumeIf("E")) + { + BaseNode argument = ParseTemplateArgument(); + if (argument == null) + { + return null; + } + + arguments.Add(argument); + } + return new EnclosedExpression("sizeof...(", new NodeArray(arguments), ")"); + } + return null; + case 't': + switch (Peek(1)) + { + case 'e': + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new EnclosedExpression("typeid (", expression, ")"); + case 't': + BaseNode enclosedType = ParseExpression(); + if (enclosedType == null) + { + return null; + } + + return new EnclosedExpression("typeid (", enclosedType, ")"); + case 'l': + _position += 2; + BaseNode typeNode = ParseType(); + if (typeNode == null) + { + return null; + } + + List bracedExpressions = new(); + while (!ConsumeIf("E")) + { + expression = ParseBracedExpression(); + if (expression == null) + { + return null; + } + + bracedExpressions.Add(expression); + } + return new InitListExpression(typeNode, bracedExpressions); + case 'r': + _position += 2; + return new NameType("throw"); + case 'w': + _position += 2; + expression = ParseExpression(); + if (expression == null) + { + return null; + } + + return new ThrowExpression(expression); + } + return null; + } + + if (char.IsDigit(Peek())) + { + return ParseUnresolvedName(); + } + + return null; + } + + private BaseNode ParseIntegerLiteral(string literalName) + { + string number = ParseNumber(true); + if (number == null || number.Length == 0 || !ConsumeIf("E")) + { + return null; + } + + return new IntegerLiteral(literalName, number); + } + + // ::= L E # integer literal + // ::= L E # floating literal (TODO) + // ::= L E # string literal + // ::= L E # nullptr literal (i.e., "LDnE") + // ::= L 0 E # null pointer template argument + // ::= L _ E # complex floating point literal (C 2000) + // ::= L _Z E # external name + private BaseNode ParseExpressionPrimary() + { + if (!ConsumeIf("L")) + { + return null; + } + + switch (Peek()) + { + case 'w': + _position++; + return ParseIntegerLiteral("wchar_t"); + case 'b': + if (ConsumeIf("b0E")) + { + return new NameType("false", NodeType.BooleanExpression); + } + + if (ConsumeIf("b1E")) + { + return new NameType("true", NodeType.BooleanExpression); + } + + return null; + case 'c': + _position++; + return ParseIntegerLiteral("char"); + case 'a': + _position++; + return ParseIntegerLiteral("signed char"); + case 'h': + _position++; + return ParseIntegerLiteral("unsigned char"); + case 's': + _position++; + return ParseIntegerLiteral("short"); + case 't': + _position++; + return ParseIntegerLiteral("unsigned short"); + case 'i': + _position++; + return ParseIntegerLiteral(""); + case 'j': + _position++; + return ParseIntegerLiteral("u"); + case 'l': + _position++; + return ParseIntegerLiteral("l"); + case 'm': + _position++; + return ParseIntegerLiteral("ul"); + case 'x': + _position++; + return ParseIntegerLiteral("ll"); + case 'y': + _position++; + return ParseIntegerLiteral("ull"); + case 'n': + _position++; + return ParseIntegerLiteral("__int128"); + case 'o': + _position++; + return ParseIntegerLiteral("unsigned __int128"); + case 'd': + case 'e': + case 'f': + // TODO: floating literal + return null; + case '_': + if (ConsumeIf("_Z")) + { + BaseNode encoding = ParseEncoding(); + if (encoding != null && ConsumeIf("E")) + { + return encoding; + } + } + return null; + case 'T': + return null; + default: + BaseNode type = ParseType(); + if (type == null) + { + return null; + } + + string number = ParseNumber(); + if (number == null || number.Length == 0 || !ConsumeIf("E")) + { + return null; + } + + return new IntegerCastExpression(type, number); + } + } + + // ::= Dt E # decltype of an id-expression or class member access (C++0x) + // ::= DT E # decltype of an expression (C++0x) + private BaseNode ParseDecltype() + { + if (!ConsumeIf("D") || (!ConsumeIf("t") && !ConsumeIf("T"))) + { + return null; + } + + BaseNode expression = ParseExpression(); + if (expression == null) + { + return null; + } + + if (!ConsumeIf("E")) + { + return null; + } + + return new EnclosedExpression("decltype(", expression, ")"); + } + + // ::= T_ # first template parameter + // ::= T _ + // ::= + // ::= + private BaseNode ParseTemplateParam() + { + if (!ConsumeIf("T")) + { + return null; + } + + int index = 0; + if (!ConsumeIf("_")) + { + index = ParsePositiveNumber(); + if (index < 0) + { + return null; + } + + index++; + if (!ConsumeIf("_")) + { + return null; + } + } + + // 5.1.8: TODO: lambda? + // if (IsParsingLambdaParameters) + // return new NameType("auto"); + + if (_canForwardTemplateReference) + { + ForwardTemplateReference forwardTemplateReference = new(index); + _forwardTemplateReferenceList.Add(forwardTemplateReference); + return forwardTemplateReference; + } + if (index >= _templateParamList.Count) + { + return null; + } + + return _templateParamList[index]; + } + + // ::= I + E + private BaseNode ParseTemplateArguments(bool hasContext = false) + { + if (!ConsumeIf("I")) + { + return null; + } + + if (hasContext) + { + _templateParamList.Clear(); + } + + List args = new(); + while (!ConsumeIf("E")) + { + if (hasContext) + { + List templateParamListTemp = new(_templateParamList); + BaseNode templateArgument = ParseTemplateArgument(); + _templateParamList = templateParamListTemp; + if (templateArgument == null) + { + return null; + } + + args.Add(templateArgument); + if (templateArgument.GetType().Equals(NodeType.PackedTemplateArgument)) + { + templateArgument = new PackedTemplateParameter(((NodeArray)templateArgument).Nodes); + } + _templateParamList.Add(templateArgument); + } + else + { + BaseNode templateArgument = ParseTemplateArgument(); + if (templateArgument == null) + { + return null; + } + + args.Add(templateArgument); + } + } + return new TemplateArguments(args); + } + + + // ::= # type or template + // ::= X E # expression + // ::= # simple expressions + // ::= J * E # argument pack + private BaseNode ParseTemplateArgument() + { + switch (Peek()) + { + // X E + case 'X': + _position++; + BaseNode expression = ParseExpression(); + if (expression == null || !ConsumeIf("E")) + { + return null; + } + + return expression; + // + case 'L': + return ParseExpressionPrimary(); + // J * E + case 'J': + _position++; + List templateArguments = new(); + while (!ConsumeIf("E")) + { + BaseNode templateArgument = ParseTemplateArgument(); + if (templateArgument == null) + { + return null; + } + + templateArguments.Add(templateArgument); + } + return new NodeArray(templateArguments, NodeType.PackedTemplateArgument); + // + default: + return ParseType(); + } + } + + class NameParserContext + { + public CvType Cv; + public SimpleReferenceType Ref; + public bool FinishWithTemplateArguments; + public bool CtorDtorConversion; + } + + + // ::= [ ] # T:: or T:: + // ::= # decltype(p):: + // ::= + private BaseNode ParseUnresolvedType() + { + if (Peek() == 'T') + { + BaseNode templateParam = ParseTemplateParam(); + if (templateParam == null) + { + return null; + } + + _substitutionList.Add(templateParam); + return templateParam; + } + else if (Peek() == 'D') + { + BaseNode declType = ParseDecltype(); + if (declType == null) + { + return null; + } + + _substitutionList.Add(declType); + return declType; + } + return ParseSubstitution(); + } + + // ::= [ ] + private BaseNode ParseSimpleId() + { + BaseNode sourceName = ParseSourceName(); + if (sourceName == null) + { + return null; + } + + if (Peek() == 'I') + { + BaseNode templateArguments = ParseTemplateArguments(); + if (templateArguments == null) + { + return null; + } + + return new NameTypeWithTemplateArguments(sourceName, templateArguments); + } + return sourceName; + } + + // ::= # e.g., ~T or ~decltype(f()) + // ::= # e.g., ~A<2*N> + private BaseNode ParseDestructorName() + { + BaseNode node; + if (char.IsDigit(Peek())) + { + node = ParseSimpleId(); + } + else + { + node = ParseUnresolvedType(); + } + if (node == null) + { + return null; + } + + return new DtorName(node); + } + + // ::= # unresolved name + // extension ::= # unresolved operator-function-id + // extension ::= # unresolved operator template-id + // ::= on # unresolved operator-function-id + // ::= on # unresolved operator template-id + // ::= dn # destructor or pseudo-destructor; + // # e.g. ~X or ~X + private BaseNode ParseBaseUnresolvedName() + { + if (char.IsDigit(Peek())) + { + return ParseSimpleId(); + } + else if (ConsumeIf("dn")) + { + return ParseDestructorName(); + } + + ConsumeIf("on"); + BaseNode operatorName = ParseOperatorName(null); + if (operatorName == null) + { + return null; + } + + if (Peek() == 'I') + { + BaseNode templateArguments = ParseTemplateArguments(); + if (templateArguments == null) + { + return null; + } + + return new NameTypeWithTemplateArguments(operatorName, templateArguments); + } + return operatorName; + } + + // ::= [gs] # x or (with "gs") ::x + // ::= sr # T::x / decltype(p)::x + // ::= srN + E + // # T::N::x /decltype(p)::N::x + // ::= [gs] sr + E + // # A::x, N::y, A::z; "gs" means leading "::" + private BaseNode ParseUnresolvedName(NameParserContext context = null) + { + BaseNode result = null; + if (ConsumeIf("srN")) + { + result = ParseUnresolvedType(); + if (result == null) + { + return null; + } + + if (Peek() == 'I') + { + BaseNode templateArguments = ParseTemplateArguments(); + if (templateArguments == null) + { + return null; + } + + result = new NameTypeWithTemplateArguments(result, templateArguments); + if (result == null) + { + return null; + } + } + + while (!ConsumeIf("E")) + { + BaseNode simpleId = ParseSimpleId(); + if (simpleId == null) + { + return null; + } + + result = new QualifiedName(result, simpleId); + if (result == null) + { + return null; + } + } + + BaseNode baseName = ParseBaseUnresolvedName(); + if (baseName == null) + { + return null; + } + + return new QualifiedName(result, baseName); + } + + bool isGlobal = ConsumeIf("gs"); + + // ::= [gs] # x or (with "gs") ::x + if (!ConsumeIf("sr")) + { + result = ParseBaseUnresolvedName(); + if (result == null) + { + return null; + } + + if (isGlobal) + { + result = new GlobalQualifiedName(result); + } + + return result; + } + + // ::= [gs] sr + E + if (char.IsDigit(Peek())) + { + do + { + BaseNode qualifier = ParseSimpleId(); + if (qualifier == null) + { + return null; + } + + if (result != null) + { + result = new QualifiedName(result, qualifier); + } + else if (isGlobal) + { + result = new GlobalQualifiedName(qualifier); + } + else + { + result = qualifier; + } + + if (result == null) + { + return null; + } + } while (!ConsumeIf("E")); + } + // ::= sr [template-args] # T::x / decltype(p)::x + else + { + result = ParseUnresolvedType(); + if (result == null) + { + return null; + } + + if (Peek() == 'I') + { + BaseNode templateArguments = ParseTemplateArguments(); + if (templateArguments == null) + { + return null; + } + + result = new NameTypeWithTemplateArguments(result, templateArguments); + if (result == null) + { + return null; + } + } + } + + if (result == null) + { + return null; + } + + BaseNode baseUnresolvedName = ParseBaseUnresolvedName(); + if (baseUnresolvedName == null) + { + return null; + } + + return new QualifiedName(result, baseUnresolvedName); + } + + // ::= + // ::= St # ::std:: + private BaseNode ParseUnscopedName(NameParserContext context) + { + if (ConsumeIf("St")) + { + BaseNode unresolvedName = ParseUnresolvedName(context); + if (unresolvedName == null) + { + return null; + } + + return new StdQualifiedName(unresolvedName); + } + return ParseUnresolvedName(context); + } + + // ::= N [] [] E + // ::= N [] [] E + private BaseNode ParseNestedName(NameParserContext context) + { + // Impossible in theory + if (Consume() != 'N') + { + return null; + } + + BaseNode result = null; + CvType cv = new(ParseCvQualifiers(), null); + if (context != null) + { + context.Cv = cv; + } + + SimpleReferenceType Ref = ParseRefQualifiers(); + if (context != null) + { + context.Ref = Ref; + } + + if (ConsumeIf("St")) + { + result = new NameType("std"); + } + + while (!ConsumeIf("E")) + { + // end + if (ConsumeIf("M")) + { + if (result == null) + { + return null; + } + + continue; + } + char c = Peek(); + + // TODO: template args + if (c == 'T') + { + BaseNode templateParam = ParseTemplateParam(); + if (templateParam == null) + { + return null; + } + + result = CreateNameNode(result, templateParam, context); + _substitutionList.Add(result); + continue; + } + + // + if (c == 'I') + { + BaseNode templateArgument = ParseTemplateArguments(context != null); + if (templateArgument == null || result == null) + { + return null; + } + + result = new NameTypeWithTemplateArguments(result, templateArgument); + if (context != null) + { + context.FinishWithTemplateArguments = true; + } + + _substitutionList.Add(result); + continue; + } + + // + if (c == 'D' && (Peek(1) == 't' || Peek(1) == 'T')) + { + BaseNode decltype = ParseDecltype(); + if (decltype == null) + { + return null; + } + + result = CreateNameNode(result, decltype, context); + _substitutionList.Add(result); + continue; + } + + // + if (c == 'S' && Peek(1) != 't') + { + BaseNode substitution = ParseSubstitution(); + if (substitution == null) + { + return null; + } + + result = CreateNameNode(result, substitution, context); + if (result != substitution) + { + _substitutionList.Add(substitution); + } + + continue; + } + + // of ParseUnqualifiedName + if (c == 'C' || (c == 'D' && Peek(1) != 'C')) + { + // We cannot have nothing before this + if (result == null) + { + return null; + } + + BaseNode ctOrDtorName = ParseCtorDtorName(context, result); + + if (ctOrDtorName == null) + { + return null; + } + + result = CreateNameNode(result, ctOrDtorName, context); + + // TODO: ABI Tags (before) + if (result == null) + { + return null; + } + + _substitutionList.Add(result); + continue; + } + + BaseNode unqualifiedName = ParseUnqualifiedName(context); + if (unqualifiedName == null) + { + return null; + } + result = CreateNameNode(result, unqualifiedName, context); + + _substitutionList.Add(result); + } + if (result == null || _substitutionList.Count == 0) + { + return null; + } + + _substitutionList.RemoveAt(_substitutionList.Count - 1); + return result; + } + + // ::= _ # when number < 10 + // ::= __ _ # when number >= 10 + private void ParseDiscriminator() + { + if (Count() == 0) + { + return; + } + // We ignore the discriminator, we don't need it. + if (ConsumeIf("_")) + { + ConsumeIf("_"); + while (char.IsDigit(Peek()) && Count() != 0) + { + Consume(); + } + ConsumeIf("_"); + } + } + + // ::= Z E [] + // ::= Z E s [] + // ::= Z Ed [ ] _ + private BaseNode ParseLocalName(NameParserContext context) + { + if (!ConsumeIf("Z")) + { + return null; + } + + BaseNode encoding = ParseEncoding(); + if (encoding == null || !ConsumeIf("E")) + { + return null; + } + + BaseNode entityName; + if (ConsumeIf("s")) + { + ParseDiscriminator(); + return new LocalName(encoding, new NameType("string literal")); + } + else if (ConsumeIf("d")) + { + ParseNumber(true); + if (!ConsumeIf("_")) + { + return null; + } + + entityName = ParseName(context); + if (entityName == null) + { + return null; + } + + return new LocalName(encoding, entityName); + } + + entityName = ParseName(context); + if (entityName == null) + { + return null; + } + + ParseDiscriminator(); + return new LocalName(encoding, entityName); + } + + // ::= + // ::= + // ::= + // ::= # See Scope Encoding below (TODO) + private BaseNode ParseName(NameParserContext context = null) + { + ConsumeIf("L"); + + if (Peek() == 'N') + { + return ParseNestedName(context); + } + + if (Peek() == 'Z') + { + return ParseLocalName(context); + } + + if (Peek() == 'S' && Peek(1) != 't') + { + BaseNode substitution = ParseSubstitution(); + if (substitution == null) + { + return null; + } + + if (Peek() != 'I') + { + return null; + } + + BaseNode templateArguments = ParseTemplateArguments(context != null); + if (templateArguments == null) + { + return null; + } + + if (context != null) + { + context.FinishWithTemplateArguments = true; + } + + return new NameTypeWithTemplateArguments(substitution, templateArguments); + } + + BaseNode result = ParseUnscopedName(context); + if (result == null) + { + return null; + } + + if (Peek() == 'I') + { + _substitutionList.Add(result); + BaseNode templateArguments = ParseTemplateArguments(context != null); + if (templateArguments == null) + { + return null; + } + + if (context != null) + { + context.FinishWithTemplateArguments = true; + } + + return new NameTypeWithTemplateArguments(result, templateArguments); + } + + return result; + } + + private bool IsEncodingEnd() + { + char c = Peek(); + return Count() == 0 || c == 'E' || c == '.' || c == '_'; + } + + // ::= + // ::= + // ::= + private BaseNode ParseEncoding() + { + NameParserContext context = new(); + if (Peek() == 'T' || (Peek() == 'G' && Peek(1) == 'V')) + { + return ParseSpecialName(context); + } + + BaseNode name = ParseName(context); + if (name == null) + { + return null; + } + + // TODO: compute template refs here + + if (IsEncodingEnd()) + { + return name; + } + + // TODO: Ua9enable_ifI + + BaseNode returnType = null; + if (!context.CtorDtorConversion && context.FinishWithTemplateArguments) + { + returnType = ParseType(); + if (returnType == null) + { + return null; + } + } + + if (ConsumeIf("v")) + { + return new EncodedFunction(name, null, context.Cv, context.Ref, null, returnType); + } + + List paramsList = new(); + + // backup because that can be destroyed by parseType + CvType cv = context.Cv; + SimpleReferenceType Ref = context.Ref; + + while (!IsEncodingEnd()) + { + BaseNode param = ParseType(); + if (param == null) + { + return null; + } + + paramsList.Add(param); + } + + return new EncodedFunction(name, new NodeArray(paramsList), cv, Ref, null, returnType); + } + + // ::= _Z + // ::= + private BaseNode Parse() + { + if (ConsumeIf("_Z")) + { + BaseNode encoding = ParseEncoding(); + if (encoding != null && Count() == 0) + { + return encoding; + } + return null; + } + else + { + BaseNode type = ParseType(); + if (type != null && Count() == 0) + { + return type; + } + return null; + } + } + + public static string Parse(string originalMangled) + { + Demangler instance = new(originalMangled); + BaseNode resNode = instance.Parse(); + + if (resNode != null) + { + StringWriter writer = new(); + resNode.Print(writer); + return writer.ToString(); + } + + return originalMangled; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs b/src/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs new file mode 100644 index 00000000..02b39da3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS +{ + class HomebrewRomFsStream : Stream + { + private readonly Stream _baseStream; + private readonly long _positionOffset; + + public HomebrewRomFsStream(Stream baseStream, long positionOffset) + { + _baseStream = baseStream; + _positionOffset = positionOffset; + + _baseStream.Position = _positionOffset; + } + + public override bool CanRead => _baseStream.CanRead; + + public override bool CanSeek => _baseStream.CanSeek; + + public override bool CanWrite => false; + + public override long Length => _baseStream.Length - _positionOffset; + + public override long Position + { + get + { + return _baseStream.Position - _positionOffset; + } + set + { + _baseStream.Position = value + _positionOffset; + } + } + + public override void Flush() + { + _baseStream.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _baseStream.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + if (origin == SeekOrigin.Begin) + { + offset += _positionOffset; + } + + return _baseStream.Seek(offset, origin); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _baseStream.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs new file mode 100644 index 00000000..64b08e30 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -0,0 +1,477 @@ +using LibHac.Common; +using LibHac.Common.Keys; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.FsSystem; +using LibHac.Tools.FsSystem; +using Ryujinx.Cpu; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; +using Ryujinx.HLE.HOS.Services.Apm; +using Ryujinx.HLE.HOS.Services.Caps; +using Ryujinx.HLE.HOS.Services.Mii; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; +using Ryujinx.HLE.HOS.Services.Nv; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; +using Ryujinx.HLE.HOS.Services.Pcv.Bpc; +using Ryujinx.HLE.HOS.Services.Sdb.Pl; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.HLE.HOS.Services.Sm; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Loaders.Processes; +using Ryujinx.Horizon; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; + +namespace Ryujinx.HLE.HOS +{ + using TimeServiceManager = Services.Time.TimeManager; + + public class Horizon : IDisposable + { + internal const int HidSize = 0x40000; + internal const int FontSize = 0x1100000; + internal const int IirsSize = 0x8000; + internal const int TimeSize = 0x1000; + internal const int AppletCaptureBufferSize = 0x384000; + + internal KernelContext KernelContext { get; } + + internal Switch Device { get; private set; } + + internal ITickSource TickSource { get; } + + internal SurfaceFlinger SurfaceFlinger { get; private set; } + + public SystemStateMgr State { get; private set; } + + internal PerformanceState PerformanceState { get; private set; } + + internal AppletStateMgr AppletState { get; private set; } + + internal List NfpDevices { get; private set; } + + internal SmRegistry SmRegistry { get; private set; } + + internal ServerBase SmServer { get; private set; } + internal ServerBase BsdServer { get; private set; } + internal ServerBase FsServer { get; private set; } + internal ServerBase HidServer { get; private set; } + internal ServerBase NvDrvServer { get; private set; } + internal ServerBase TimeServer { get; private set; } + internal ServerBase ViServer { get; private set; } + internal ServerBase ViServerM { get; private set; } + internal ServerBase ViServerS { get; private set; } + internal ServerBase LdnServer { get; private set; } + + internal KSharedMemory HidSharedMem { get; private set; } + internal KSharedMemory FontSharedMem { get; private set; } + internal KSharedMemory IirsSharedMem { get; private set; } + + internal KTransferMemory AppletCaptureBufferTransfer { get; private set; } + + internal SharedFontManager SharedFontManager { get; private set; } + internal AccountManager AccountManager { get; private set; } + internal ContentManager ContentManager { get; private set; } + internal CaptureManager CaptureManager { get; private set; } + + internal KEvent VsyncEvent { get; private set; } + + internal KEvent DisplayResolutionChangeEvent { get; private set; } + + public KeySet KeySet => Device.FileSystem.KeySet; + + private bool _isDisposed; + + public bool EnablePtc { get; set; } + + public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; } + + public int GlobalAccessLogMode { get; set; } + + internal SharedMemoryStorage HidStorage { get; private set; } + + internal NvHostSyncpt HostSyncpoint { get; private set; } + + internal LibHacHorizonManager LibHacHorizonManager { get; private set; } + + internal ServiceTable ServiceTable { get; private set; } + + public bool IsPaused { get; private set; } + + public Horizon(Switch device) + { + TickSource = new TickSource(KernelConstants.CounterFrequency); + + KernelContext = new KernelContext( + TickSource, + device, + device.Memory, + device.Configuration.MemoryConfiguration.ToKernelMemorySize(), + device.Configuration.MemoryConfiguration.ToKernelMemoryArrange()); + + Device = device; + + State = new SystemStateMgr(); + + PerformanceState = new PerformanceState(); + + NfpDevices = new List(); + + // Note: This is not really correct, but with HLE of services, the only memory + // region used that is used is Application, so we can use the other ones for anything. + KMemoryRegionManager region = KernelContext.MemoryManager.MemoryRegions[(int)MemoryRegion.NvServices]; + + ulong hidPa = region.Address; + ulong fontPa = region.Address + HidSize; + ulong iirsPa = region.Address + HidSize + FontSize; + ulong timePa = region.Address + HidSize + FontSize + IirsSize; + ulong appletCaptureBufferPa = region.Address + HidSize + FontSize + IirsSize + TimeSize; + + KPageList hidPageList = new(); + KPageList fontPageList = new(); + KPageList iirsPageList = new(); + KPageList timePageList = new(); + KPageList appletCaptureBufferPageList = new(); + + hidPageList.AddRange(hidPa, HidSize / KPageTableBase.PageSize); + fontPageList.AddRange(fontPa, FontSize / KPageTableBase.PageSize); + iirsPageList.AddRange(iirsPa, IirsSize / KPageTableBase.PageSize); + timePageList.AddRange(timePa, TimeSize / KPageTableBase.PageSize); + appletCaptureBufferPageList.AddRange(appletCaptureBufferPa, AppletCaptureBufferSize / KPageTableBase.PageSize); + + var hidStorage = new SharedMemoryStorage(KernelContext, hidPageList); + var fontStorage = new SharedMemoryStorage(KernelContext, fontPageList); + var iirsStorage = new SharedMemoryStorage(KernelContext, iirsPageList); + var timeStorage = new SharedMemoryStorage(KernelContext, timePageList); + var appletCaptureBufferStorage = new SharedMemoryStorage(KernelContext, appletCaptureBufferPageList); + + HidStorage = hidStorage; + + HidSharedMem = new KSharedMemory(KernelContext, hidStorage, 0, 0, KMemoryPermission.Read); + FontSharedMem = new KSharedMemory(KernelContext, fontStorage, 0, 0, KMemoryPermission.Read); + IirsSharedMem = new KSharedMemory(KernelContext, iirsStorage, 0, 0, KMemoryPermission.Read); + + KSharedMemory timeSharedMemory = new(KernelContext, timeStorage, 0, 0, KMemoryPermission.Read); + + TimeServiceManager.Instance.Initialize(device, this, timeSharedMemory, timeStorage, TimeSize); + + AppletCaptureBufferTransfer = new KTransferMemory(KernelContext, appletCaptureBufferStorage); + + AppletState = new AppletStateMgr(this); + + AppletState.SetFocus(true); + + VsyncEvent = new KEvent(KernelContext); + + DisplayResolutionChangeEvent = new KEvent(KernelContext); + + SharedFontManager = new SharedFontManager(device, fontStorage); + AccountManager = device.Configuration.AccountManager; + ContentManager = device.Configuration.ContentManager; + CaptureManager = new CaptureManager(device); + + LibHacHorizonManager = device.Configuration.LibHacHorizonManager; + + // We hardcode a clock source id to avoid it changing between each start. + // TODO: use set:sys (and get external clock source id from settings) + // TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate. + UInt128 clockSourceId = new(0x36a0328702ce8bc1, 0x1608eaba02333284); + IRtcManager.GetExternalRtcValue(out ulong rtcValue); + + // We assume the rtc is system time. + TimeSpanType systemTime = TimeSpanType.FromSeconds((long)rtcValue); + + // Configure and setup internal offset + TimeSpanType internalOffset = TimeSpanType.FromSeconds(device.Configuration.SystemTimeOffset); + + TimeSpanType systemTimeOffset = new(systemTime.NanoSeconds + internalOffset.NanoSeconds); + + if (systemTime.IsDaylightSavingTime() && !systemTimeOffset.IsDaylightSavingTime()) + { + internalOffset = internalOffset.AddSeconds(3600L); + } + else if (!systemTime.IsDaylightSavingTime() && systemTimeOffset.IsDaylightSavingTime()) + { + internalOffset = internalOffset.AddSeconds(-3600L); + } + + systemTime = new TimeSpanType(systemTime.NanoSeconds + internalOffset.NanoSeconds); + + // First init the standard steady clock + TimeServiceManager.Instance.SetupStandardSteadyClock(TickSource, clockSourceId, TimeSpanType.Zero, TimeSpanType.Zero, TimeSpanType.Zero, false); + TimeServiceManager.Instance.SetupStandardLocalSystemClock(TickSource, new SystemClockContext(), systemTime.ToSeconds()); + TimeServiceManager.Instance.StandardLocalSystemClock.GetClockContext(TickSource, out SystemClockContext localSytemClockContext); + + if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes)) + { + TimeSpanType standardNetworkClockSufficientAccuracy = new((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000); + + // The network system clock needs a valid system clock, as such we setup this system clock using the local system clock. + TimeServiceManager.Instance.SetupStandardNetworkSystemClock(localSytemClockContext, standardNetworkClockSufficientAccuracy); + } + + TimeServiceManager.Instance.SetupStandardUserSystemClock(TickSource, true, localSytemClockContext.SteadyTimePoint); + + // FIXME: TimeZone should be init here but it's actually done in ContentManager + + TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock(); + + DatabaseImpl.Instance.InitializeDatabase(TickSource, LibHacHorizonManager.SdbClient); + + HostSyncpoint = new NvHostSyncpt(device); + + SurfaceFlinger = new SurfaceFlinger(device); + } + + public void InitializeServices() + { + SmRegistry = new SmRegistry(); + SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry)); + + // Wait until SM server thread is done with initialization, + // only then doing connections to SM is safe. + SmServer.InitDone.WaitOne(); + + BsdServer = new ServerBase(KernelContext, "BsdServer"); + FsServer = new ServerBase(KernelContext, "FsServer"); + HidServer = new ServerBase(KernelContext, "HidServer"); + NvDrvServer = new ServerBase(KernelContext, "NvservicesServer"); + TimeServer = new ServerBase(KernelContext, "TimeServer"); + ViServer = new ServerBase(KernelContext, "ViServerU"); + ViServerM = new ServerBase(KernelContext, "ViServerM"); + ViServerS = new ServerBase(KernelContext, "ViServerS"); + LdnServer = new ServerBase(KernelContext, "LdnServer"); + + StartNewServices(); + } + + private void StartNewServices() + { + HorizonFsClient fsClient = new(this); + + ServiceTable = new ServiceTable(); + var services = ServiceTable.GetServices(new HorizonOptions + (Device.Configuration.IgnoreMissingServices, + LibHacHorizonManager.BcatClient, + fsClient, + AccountManager, + Device.AudioDeviceDriver, + TickSource)); + + foreach (var service in services) + { + const ProcessCreationFlags Flags = + ProcessCreationFlags.EnableAslr | + ProcessCreationFlags.AddressSpace64Bit | + ProcessCreationFlags.Is64Bit | + ProcessCreationFlags.PoolPartitionSystem; + + ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0); + + uint[] defaultCapabilities = { + 0x030363F7, + 0x1FFFFFCF, + 0x207FFFEF, + 0x47E0060F, + 0x0048BFFF, + 0x01007FFF, + }; + + // TODO: + // - Pass enough information (capabilities, process creation info, etc) on ServiceEntry for proper initialization. + // - Have the ThreadStart function take the syscall, address space and thread context parameters instead of passing them here. + KernelStatic.StartInitialProcess(KernelContext, creationInfo, defaultCapabilities, 44, () => + { + service.Start(KernelContext.Syscall, KernelStatic.GetCurrentProcess().CpuMemory, KernelStatic.GetCurrentThread().ThreadContext); + }); + } + } + + public bool LoadKip(string kipPath) + { + using var kipFile = new SharedRef(new LocalStorage(kipPath, FileAccess.Read)); + + return ProcessLoaderHelper.LoadKip(KernelContext, new KipExecutable(in kipFile)); + } + + public void ChangeDockedModeState(bool newState) + { + if (newState != State.DockedMode) + { + State.DockedMode = newState; + PerformanceState.PerformanceMode = State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default; + + AppletState.Messages.Enqueue(AppletMessage.OperationModeChanged); + AppletState.Messages.Enqueue(AppletMessage.PerformanceModeChanged); + AppletState.MessageEvent.ReadableEvent.Signal(); + + SignalDisplayResolutionChange(); + + Device.Configuration.RefreshInputConfig?.Invoke(); + } + } + + public void ReturnFocus() + { + AppletState.SetFocus(true); + } + + public void SimulateWakeUpMessage() + { + AppletState.Messages.Enqueue(AppletMessage.Resume); + AppletState.MessageEvent.ReadableEvent.Signal(); + } + + public void ScanAmiibo(int nfpDeviceId, string amiiboId, bool useRandomUuid) + { + if (NfpDevices[nfpDeviceId].State == NfpDeviceState.SearchingForTag) + { + NfpDevices[nfpDeviceId].State = NfpDeviceState.TagFound; + NfpDevices[nfpDeviceId].AmiiboId = amiiboId; + NfpDevices[nfpDeviceId].UseRandomUuid = useRandomUuid; + } + } + + public bool SearchingForAmiibo(out int nfpDeviceId) + { + nfpDeviceId = default; + + for (int i = 0; i < NfpDevices.Count; i++) + { + if (NfpDevices[i].State == NfpDeviceState.SearchingForTag) + { + nfpDeviceId = i; + + return true; + } + } + + return false; + } + + public void SignalDisplayResolutionChange() + { + DisplayResolutionChangeEvent.ReadableEvent.Signal(); + } + + public void SignalVsync() + { + VsyncEvent.ReadableEvent.Signal(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (!_isDisposed && disposing) + { + _isDisposed = true; + + // "Soft" stops AudioRenderer and AudioManager to avoid some sound between resume and stop. + if (IsPaused) + { + TogglePauseEmulation(false); + } + + KProcess terminationProcess = new(KernelContext); + KThread terminationThread = new(KernelContext); + + terminationThread.Initialize(0, 0, 0, 3, 0, terminationProcess, ThreadType.Kernel, () => + { + // Force all threads to exit. + lock (KernelContext.Processes) + { + // Terminate application. + foreach (KProcess process in KernelContext.Processes.Values.Where(x => x.IsApplication)) + { + process.Terminate(); + process.DecrementReferenceCount(); + } + + // The application existed, now surface flinger can exit too. + SurfaceFlinger.Dispose(); + + // Terminate HLE services (must be done after the application is already terminated, + // otherwise the application will receive errors due to service termination). + foreach (KProcess process in KernelContext.Processes.Values.Where(x => !x.IsApplication)) + { + process.Terminate(); + process.DecrementReferenceCount(); + } + + KernelContext.Processes.Clear(); + } + + // Exit ourself now! + KernelStatic.GetCurrentThread().Exit(); + }); + + terminationThread.Start(); + + // Wait until the thread is actually started. + while (terminationThread.HostThread.ThreadState == ThreadState.Unstarted) + { + Thread.Sleep(10); + } + + // Wait until the termination thread is done terminating all the other threads. + terminationThread.HostThread.Join(); + + // Destroy nvservices channels as KThread could be waiting on some user events. + // This is safe as KThread that are likely to call ioctls are going to be terminated by the post handler hook on the SVC facade. + INvDrvServices.Destroy(); + + if (LibHacHorizonManager.ApplicationClient != null) + { + LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure(); + } + + KernelContext.Dispose(); + } + } + + public void TogglePauseEmulation(bool pause) + { + lock (KernelContext.Processes) + { + foreach (KProcess process in KernelContext.Processes.Values) + { + if (process.IsApplication) + { + // Only game process should be paused. + process.SetActivity(pause); + } + } + + if (pause && !IsPaused) + { + Device.AudioDeviceDriver.GetPauseEvent().Reset(); + TickSource.Suspend(); + } + else if (!pause && IsPaused) + { + Device.AudioDeviceDriver.GetPauseEvent().Set(); + TickSource.Resume(); + } + } + IsPaused = pause; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/HorizonFsClient.cs b/src/Ryujinx.HLE/HOS/HorizonFsClient.cs new file mode 100644 index 00000000..3dbafa88 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/HorizonFsClient.cs @@ -0,0 +1,119 @@ +using LibHac.Common; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.HLE.FileSystem; +using Ryujinx.Horizon; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Fs; +using System; +using System.Collections.Concurrent; +using System.IO; + +namespace Ryujinx.HLE.HOS +{ + class HorizonFsClient : IFsClient + { + private readonly Horizon _system; + private readonly LibHac.Fs.FileSystemClient _fsClient; + private readonly ConcurrentDictionary _mountedStorages; + + public HorizonFsClient(Horizon system) + { + _system = system; + _fsClient = _system.LibHacHorizonManager.FsClient.Fs; + _mountedStorages = new(); + } + + public void CloseFile(FileHandle handle) + { + _fsClient.CloseFile((LibHac.Fs.FileHandle)handle.Value); + } + + public Result GetFileSize(out long size, FileHandle handle) + { + return _fsClient.GetFileSize(out size, (LibHac.Fs.FileHandle)handle.Value).ToHorizonResult(); + } + + public Result MountSystemData(string mountName, ulong dataId) + { + string contentPath = _system.ContentManager.GetInstalledContentPath(dataId, StorageId.BuiltInSystem, NcaContentType.PublicData); + string installPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath); + + if (!string.IsNullOrWhiteSpace(installPath)) + { + string ncaPath = installPath; + + if (File.Exists(ncaPath)) + { + LocalStorage ncaStorage = null; + + try + { + ncaStorage = new LocalStorage(ncaPath, FileAccess.Read, FileMode.Open); + + Nca nca = new(_system.KeySet, ncaStorage); + + using var ncaFileSystem = nca.OpenFileSystem(NcaSectionType.Data, _system.FsIntegrityCheckLevel); + using var ncaFsRef = new UniqueRef(ncaFileSystem); + + Result result = _fsClient.Register(mountName.ToU8Span(), ref ncaFsRef.Ref).ToHorizonResult(); + if (result.IsFailure) + { + ncaStorage.Dispose(); + } + else + { + _mountedStorages.TryAdd(mountName, ncaStorage); + } + + return result; + } + catch (HorizonResultException ex) + { + ncaStorage?.Dispose(); + + return ex.ResultValue.ToHorizonResult(); + } + } + } + + // TODO: Return correct result here, this is likely wrong. + + return LibHac.Fs.ResultFs.TargetNotFound.Handle().ToHorizonResult(); + } + + public Result OpenFile(out FileHandle handle, string path, OpenMode openMode) + { + var result = _fsClient.OpenFile(out var libhacHandle, path.ToU8Span(), (LibHac.Fs.OpenMode)openMode); + handle = new(libhacHandle); + + return result.ToHorizonResult(); + } + + public Result QueryMountSystemDataCacheSize(out long size, ulong dataId) + { + // TODO. + + size = 0; + + return Result.Success; + } + + public Result ReadFile(FileHandle handle, long offset, Span destination) + { + return _fsClient.ReadFile((LibHac.Fs.FileHandle)handle.Value, offset, destination).ToHorizonResult(); + } + + public void Unmount(string mountName) + { + if (_mountedStorages.TryRemove(mountName, out LocalStorage ncaStorage)) + { + ncaStorage.Dispose(); + } + + _fsClient.Unmount(mountName.ToU8Span()); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/IdDictionary.cs b/src/Ryujinx.HLE/HOS/IdDictionary.cs new file mode 100644 index 00000000..56ffcd0c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/IdDictionary.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS +{ + class IdDictionary + { + private readonly ConcurrentDictionary _objs; + + public ICollection Values => _objs.Values; + + public IdDictionary() + { + _objs = new ConcurrentDictionary(); + } + + public bool Add(int id, object data) + { + return _objs.TryAdd(id, data); + } + + public int Add(object data) + { + for (int id = 1; id < int.MaxValue; id++) + { + if (_objs.TryAdd(id, data)) + { + return id; + } + } + + throw new InvalidOperationException(); + } + + public object GetData(int id) + { + if (_objs.TryGetValue(id, out object data)) + { + return data; + } + + return null; + } + + public T GetData(int id) + { + if (_objs.TryGetValue(id, out object dataObject) && dataObject is T data) + { + return data; + } + + return default; + } + + public object Delete(int id) + { + if (_objs.TryRemove(id, out object obj)) + { + return obj; + } + + return null; + } + + public ICollection Clear() + { + ICollection values = _objs.Values; + + _objs.Clear(); + + return values; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs new file mode 100644 index 00000000..aa917435 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs @@ -0,0 +1,27 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Ipc +{ + struct IpcBuffDesc + { + public ulong Position { get; private set; } + public ulong Size { get; private set; } + public byte Flags { get; private set; } + + public IpcBuffDesc(BinaryReader reader) + { + ulong word0 = reader.ReadUInt32(); + ulong word1 = reader.ReadUInt32(); + ulong word2 = reader.ReadUInt32(); + + Position = word1; + Position |= (word2 << 4) & 0x0f00000000; + Position |= (word2 << 34) & 0x7000000000; + + Size = word0; + Size |= (word2 << 8) & 0xf00000000; + + Flags = (byte)(word2 & 3); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs new file mode 100644 index 00000000..887fe28e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs @@ -0,0 +1,93 @@ +using Microsoft.IO; +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Ipc +{ + class IpcHandleDesc + { + public bool HasPId { get; private set; } + + public ulong PId { get; private set; } + + public int[] ToCopy { get; private set; } + public int[] ToMove { get; private set; } + + public IpcHandleDesc(BinaryReader reader) + { + int word = reader.ReadInt32(); + + HasPId = (word & 1) != 0; + + PId = HasPId ? reader.ReadUInt64() : 0; + + int toCopySize = (word >> 1) & 0xf; + int[] toCopy = toCopySize == 0 ? Array.Empty() : new int[toCopySize]; + + for (int index = 0; index < toCopy.Length; index++) + { + toCopy[index] = reader.ReadInt32(); + } + + ToCopy = toCopy; + + int toMoveSize = (word >> 5) & 0xf; + int[] toMove = toMoveSize == 0 ? Array.Empty() : new int[toMoveSize]; + + for (int index = 0; index < toMove.Length; index++) + { + toMove[index] = reader.ReadInt32(); + } + + ToMove = toMove; + } + + public IpcHandleDesc(int[] copy, int[] move) + { + ToCopy = copy ?? throw new ArgumentNullException(nameof(copy)); + ToMove = move ?? throw new ArgumentNullException(nameof(move)); + } + + public IpcHandleDesc(int[] copy, int[] move, ulong pId) : this(copy, move) + { + PId = pId; + + HasPId = true; + } + + public static IpcHandleDesc MakeCopy(params int[] handles) + { + return new IpcHandleDesc(handles, Array.Empty()); + } + + public static IpcHandleDesc MakeMove(params int[] handles) + { + return new IpcHandleDesc(Array.Empty(), handles); + } + + public RecyclableMemoryStream GetStream() + { + RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(); + + int word = HasPId ? 1 : 0; + + word |= (ToCopy.Length & 0xf) << 1; + word |= (ToMove.Length & 0xf) << 5; + + ms.Write(word); + + if (HasPId) + { + ms.Write(PId); + } + + ms.Write(ToCopy); + ms.Write(ToMove); + + ms.Position = 0; + return ms; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs new file mode 100644 index 00000000..05d60907 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Ipc +{ + abstract class IpcMagic + { + public const long Sfci = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'I' << 24; + public const long Sfco = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'O' << 24; + } +} diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs new file mode 100644 index 00000000..feba09fe --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs @@ -0,0 +1,281 @@ +using Microsoft.IO; +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +namespace Ryujinx.HLE.HOS.Ipc +{ + class IpcMessage + { + public IpcMessageType Type { get; set; } + + public IpcHandleDesc HandleDesc { get; set; } + + public List PtrBuff { get; private set; } + public List SendBuff { get; private set; } + public List ReceiveBuff { get; private set; } + public List ExchangeBuff { get; private set; } + public List RecvListBuff { get; private set; } + + public List ObjectIds { get; private set; } + + public byte[] RawData { get; set; } + + public IpcMessage() + { + PtrBuff = new List(0); + SendBuff = new List(0); + ReceiveBuff = new List(0); + ExchangeBuff = new List(0); + RecvListBuff = new List(0); + + ObjectIds = new List(0); + } + + public IpcMessage(ReadOnlySpan data, long cmdPtr) + { + using RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data); + + BinaryReader reader = new(ms); + + int word0 = reader.ReadInt32(); + int word1 = reader.ReadInt32(); + + Type = (IpcMessageType)(word0 & 0xffff); + + int ptrBuffCount = (word0 >> 16) & 0xf; + int sendBuffCount = (word0 >> 20) & 0xf; + int recvBuffCount = (word0 >> 24) & 0xf; + int xchgBuffCount = (word0 >> 28) & 0xf; + + int rawDataSize = (word1 >> 0) & 0x3ff; + int recvListFlags = (word1 >> 10) & 0xf; + bool hndDescEnable = ((word1 >> 31) & 0x1) != 0; + + if (hndDescEnable) + { + HandleDesc = new IpcHandleDesc(reader); + } + + PtrBuff = new List(ptrBuffCount); + + for (int index = 0; index < ptrBuffCount; index++) + { + PtrBuff.Add(new IpcPtrBuffDesc(reader)); + } + + static List ReadBuff(BinaryReader reader, int count) + { + List buff = new(count); + + for (int index = 0; index < count; index++) + { + buff.Add(new IpcBuffDesc(reader)); + } + + return buff; + } + + SendBuff = ReadBuff(reader, sendBuffCount); + ReceiveBuff = ReadBuff(reader, recvBuffCount); + ExchangeBuff = ReadBuff(reader, xchgBuffCount); + + rawDataSize *= 4; + + long recvListPos = reader.BaseStream.Position + rawDataSize; + + // Only CMIF has the padding requirements. + if (Type < IpcMessageType.TipcCloseSession) + { + long pad0 = GetPadSize16(reader.BaseStream.Position + cmdPtr); + + if (rawDataSize != 0) + { + rawDataSize -= (int)pad0; + } + + reader.BaseStream.Seek(pad0, SeekOrigin.Current); + } + + int recvListCount = recvListFlags - 2; + + if (recvListCount == 0) + { + recvListCount = 1; + } + else if (recvListCount < 0) + { + recvListCount = 0; + } + + RawData = reader.ReadBytes(rawDataSize); + + reader.BaseStream.Seek(recvListPos, SeekOrigin.Begin); + + RecvListBuff = new List(recvListCount); + + for (int index = 0; index < recvListCount; index++) + { + RecvListBuff.Add(new IpcRecvListBuffDesc(reader.ReadUInt64())); + } + + ObjectIds = new List(0); + } + + public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr) + { + RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(); + + int word0; + int word1; + + word0 = (int)Type; + word0 |= (PtrBuff.Count & 0xf) << 16; + word0 |= (SendBuff.Count & 0xf) << 20; + word0 |= (ReceiveBuff.Count & 0xf) << 24; + word0 |= (ExchangeBuff.Count & 0xf) << 28; + + using RecyclableMemoryStream handleDataStream = HandleDesc?.GetStream(); + + int dataLength = RawData?.Length ?? 0; + + dataLength = (dataLength + 3) & ~3; + + int rawLength = dataLength; + + int pad0 = (int)GetPadSize16(cmdPtr + 8 + (handleDataStream?.Length ?? 0) + PtrBuff.Count * 8); + + // Apparently, padding after Raw Data is 16 bytes, however when there is + // padding before Raw Data too, we need to subtract the size of this padding. + // This is the weirdest padding I've seen so far... + int pad1 = 0x10 - pad0; + + dataLength = (dataLength + pad0 + pad1) / 4; + + word1 = (dataLength & 0x3ff) | (2 << 10); + + if (HandleDesc != null) + { + word1 |= 1 << 31; + } + + ms.Write(word0); + ms.Write(word1); + + if (handleDataStream != null) + { + ms.Write(handleDataStream); + } + + foreach (IpcPtrBuffDesc ptrBuffDesc in PtrBuff) + { + ms.Write(ptrBuffDesc.GetWord0()); + ms.Write(ptrBuffDesc.GetWord1()); + } + + ms.WriteByte(0, pad0); + + if (RawData != null) + { + ms.Write(RawData); + ms.WriteByte(0, rawLength - RawData.Length); + } + + ms.WriteByte(0, pad1); + + ms.Write(recvListAddr); + + ms.Position = 0; + + return ms; + } + + public RecyclableMemoryStream GetStreamTipc() + { + Debug.Assert(PtrBuff.Count == 0); + + RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(); + + int word0; + int word1; + + word0 = (int)Type; + word0 |= (SendBuff.Count & 0xf) << 20; + word0 |= (ReceiveBuff.Count & 0xf) << 24; + word0 |= (ExchangeBuff.Count & 0xf) << 28; + + using RecyclableMemoryStream handleDataStream = HandleDesc?.GetStream(); + + int dataLength = RawData?.Length ?? 0; + + dataLength = ((dataLength + 3) & ~3) / 4; + + word1 = (dataLength & 0x3ff); + + if (HandleDesc != null) + { + word1 |= 1 << 31; + } + + ms.Write(word0); + ms.Write(word1); + + if (handleDataStream != null) + { + ms.Write(handleDataStream); + } + + if (RawData != null) + { + ms.Write(RawData); + } + + return ms; + } + + private static long GetPadSize16(long position) + { + if ((position & 0xf) != 0) + { + return 0x10 - (position & 0xf); + } + + return 0; + } + + // ReSharper disable once InconsistentNaming + public (ulong Position, ulong Size) GetBufferType0x21(int index = 0) + { + if (PtrBuff.Count > index && PtrBuff[index].Position != 0) + { + return (PtrBuff[index].Position, PtrBuff[index].Size); + } + + if (SendBuff.Count > index) + { + return (SendBuff[index].Position, SendBuff[index].Size); + } + + return (0, 0); + } + + // ReSharper disable once InconsistentNaming + public (ulong Position, ulong Size) GetBufferType0x22(int index = 0) + { + if (RecvListBuff.Count > index && RecvListBuff[index].Position != 0) + { + return (RecvListBuff[index].Position, RecvListBuff[index].Size); + } + + if (ReceiveBuff.Count > index) + { + return (ReceiveBuff[index].Position, ReceiveBuff[index].Size); + } + + return (0, 0); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs new file mode 100644 index 00000000..1391569c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Ipc +{ + enum IpcMessageType + { + CmifResponse = 0, + CmifCloseSession = 2, + CmifRequest = 4, + CmifControl = 5, + CmifRequestWithContext = 6, + CmifControlWithContext = 7, + TipcCloseSession = 0xF, + } +} diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs new file mode 100644 index 00000000..7ea2af0a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs @@ -0,0 +1,58 @@ +using System.IO; + +namespace Ryujinx.HLE.HOS.Ipc +{ + struct IpcPtrBuffDesc + { + public ulong Position { get; private set; } + public uint Index { get; private set; } + public ulong Size { get; private set; } + + public IpcPtrBuffDesc(ulong position, uint index, ulong size) + { + Position = position; + Index = index; + Size = size; + } + + public IpcPtrBuffDesc(BinaryReader reader) + { + ulong word0 = reader.ReadUInt32(); + ulong word1 = reader.ReadUInt32(); + + Position = word1; + Position |= (word0 << 20) & 0x0f00000000; + Position |= (word0 << 30) & 0x7000000000; + + Index = ((uint)word0 >> 0) & 0x03f; + Index |= ((uint)word0 >> 3) & 0x1c0; + + Size = (ushort)(word0 >> 16); + } + + public readonly IpcPtrBuffDesc WithSize(ulong size) + { + return new IpcPtrBuffDesc(Position, Index, size); + } + + public readonly uint GetWord0() + { + uint word0; + + word0 = (uint)((Position & 0x0f00000000) >> 20); + word0 |= (uint)((Position & 0x7000000000) >> 30); + + word0 |= (Index & 0x03f) << 0; + word0 |= (Index & 0x1c0) << 3; + + word0 |= (uint)Size << 16; + + return word0; + } + + public readonly uint GetWord1() + { + return (uint)Position; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs new file mode 100644 index 00000000..f74f81c7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Ipc +{ + struct IpcRecvListBuffDesc + { + public ulong Position { get; private set; } + public ulong Size { get; private set; } + + public IpcRecvListBuffDesc(ulong position, ulong size) + { + Position = position; + Size = size; + } + + public IpcRecvListBuffDesc(ulong packedValue) + { + Position = packedValue & 0xffffffffffff; + + Size = (ushort)(packedValue >> 48); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs b/src/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs new file mode 100644 index 00000000..556b3df4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.HLE.HOS.Ipc +{ + delegate long ServiceProcessRequest(ServiceCtx context); +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs new file mode 100644 index 00000000..1550c9bf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + interface IKFutureSchedulerObject + { + void TimeUp(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs new file mode 100644 index 00000000..e5dd3d17 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs @@ -0,0 +1,73 @@ +using Ryujinx.Horizon.Common; +using System.Diagnostics; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class KAutoObject + { + protected KernelContext KernelContext; + + private int _referenceCount; + + public KAutoObject(KernelContext context) + { + KernelContext = context; + + _referenceCount = 1; + } + + public virtual Result SetName(string name) + { + if (!KernelContext.AutoObjectNames.TryAdd(name, this)) + { + return KernelResult.InvalidState; + } + + return Result.Success; + } + + public static Result RemoveName(KernelContext context, string name) + { + if (!context.AutoObjectNames.TryRemove(name, out _)) + { + return KernelResult.NotFound; + } + + return Result.Success; + } + + public static KAutoObject FindNamedObject(KernelContext context, string name) + { + if (context.AutoObjectNames.TryGetValue(name, out KAutoObject obj)) + { + return obj; + } + + return null; + } + + public void IncrementReferenceCount() + { + int newRefCount = Interlocked.Increment(ref _referenceCount); + + Debug.Assert(newRefCount >= 2); + } + + public void DecrementReferenceCount() + { + int newRefCount = Interlocked.Decrement(ref _referenceCount); + + Debug.Assert(newRefCount >= 0); + + if (newRefCount == 0) + { + Destroy(); + } + } + + protected virtual void Destroy() + { + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs new file mode 100644 index 00000000..3f16f8c2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs @@ -0,0 +1,188 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class KResourceLimit : KAutoObject + { + private const int DefaultTimeoutMs = 10000; // 10s + + private readonly long[] _current; + private readonly long[] _limit; + private readonly long[] _current2; + private readonly long[] _peak; + + private readonly object _lock; + + private readonly LinkedList _waitingThreads; + + private int _waitingThreadsCount; + + public KResourceLimit(KernelContext context) : base(context) + { + _current = new long[(int)LimitableResource.Count]; + _limit = new long[(int)LimitableResource.Count]; + _current2 = new long[(int)LimitableResource.Count]; + _peak = new long[(int)LimitableResource.Count]; + + _lock = new object(); + + _waitingThreads = new LinkedList(); + } + + public bool Reserve(LimitableResource resource, ulong amount) + { + return Reserve(resource, (long)amount); + } + + public bool Reserve(LimitableResource resource, long amount) + { + return Reserve(resource, amount, KTimeManager.ConvertMillisecondsToNanoseconds(DefaultTimeoutMs)); + } + + public bool Reserve(LimitableResource resource, long amount, long timeout) + { + long endTimePoint = KTimeManager.ConvertNanosecondsToMilliseconds(timeout); + + endTimePoint += PerformanceCounter.ElapsedMilliseconds; + + bool success = false; + + int index = GetIndex(resource); + + lock (_lock) + { + if (_current2[index] >= _limit[index]) + { + return false; + } + + long newCurrent = _current[index] + amount; + + while (newCurrent > _limit[index] && _current2[index] + amount <= _limit[index]) + { + _waitingThreadsCount++; + + KConditionVariable.Wait(KernelContext, _waitingThreads, _lock, timeout); + + _waitingThreadsCount--; + + newCurrent = _current[index] + amount; + + if (timeout >= 0 && PerformanceCounter.ElapsedMilliseconds > endTimePoint) + { + break; + } + } + + if (newCurrent <= _limit[index]) + { + _current[index] = newCurrent; + _current2[index] += amount; + + if (_current[index] > _peak[index]) + { + _peak[index] = _current[index]; + } + + success = true; + } + } + + return success; + } + + public void Release(LimitableResource resource, ulong amount) + { + Release(resource, (long)amount); + } + + public void Release(LimitableResource resource, long amount) + { + Release(resource, amount, amount); + } + + public void Release(LimitableResource resource, long amount, long amount2) + { + int index = GetIndex(resource); + + lock (_lock) + { + _current[index] -= amount; + _current2[index] -= amount2; + + if (_waitingThreadsCount > 0) + { + KConditionVariable.NotifyAll(KernelContext, _waitingThreads); + } + } + } + + public long GetRemainingValue(LimitableResource resource) + { + int index = GetIndex(resource); + + lock (_lock) + { + return _limit[index] - _current[index]; + } + } + + public long GetCurrentValue(LimitableResource resource) + { + int index = GetIndex(resource); + + lock (_lock) + { + return _current[index]; + } + } + + public long GetLimitValue(LimitableResource resource) + { + int index = GetIndex(resource); + + lock (_lock) + { + return _limit[index]; + } + } + + public long GetPeakValue(LimitableResource resource) + { + int index = GetIndex(resource); + + lock (_lock) + { + return _peak[index]; + } + } + + public Result SetLimitValue(LimitableResource resource, long limit) + { + int index = GetIndex(resource); + + lock (_lock) + { + if (_current[index] <= limit) + { + _limit[index] = limit; + _peak[index] = _current[index]; + + return Result.Success; + } + else + { + return KernelResult.InvalidState; + } + } + } + + private static int GetIndex(LimitableResource resource) + { + return (int)resource; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs new file mode 100644 index 00000000..7e725e74 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs @@ -0,0 +1,35 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class KSynchronizationObject : KAutoObject + { + public LinkedList WaitingThreads { get; } + + public KSynchronizationObject(KernelContext context) : base(context) + { + WaitingThreads = new LinkedList(); + } + + public LinkedListNode AddWaitingThread(KThread thread) + { + return WaitingThreads.AddLast(thread); + } + + public void RemoveWaitingThread(LinkedListNode node) + { + WaitingThreads.Remove(node); + } + + public virtual void Signal() + { + KernelContext.Synchronization.SignalObject(this); + } + + public virtual bool IsSignaled() + { + return false; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KSystemControl.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KSystemControl.cs new file mode 100644 index 00000000..10f0b6f7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KSystemControl.cs @@ -0,0 +1,78 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + static class KSystemControl + { + private const ulong KiB = 1024; + private const ulong MiB = 1024 * KiB; + private const ulong GiB = 1024 * MiB; + + private const ulong PageSize = 4 * KiB; + + private const ulong RequiredNonSecureSystemPoolSizeVi = 0x2238 * PageSize; + private const ulong RequiredNonSecureSystemPoolSizeNvservices = 0x710 * PageSize; + private const ulong RequiredNonSecureSystemPoolSizeOther = 0x80 * PageSize; + + private const ulong RequiredNonSecureSystemPoolSize = + RequiredNonSecureSystemPoolSizeVi + + RequiredNonSecureSystemPoolSizeNvservices + + RequiredNonSecureSystemPoolSizeOther; + + public static ulong GetApplicationPoolSize(MemoryArrange arrange) + { + return arrange switch + { + MemoryArrange.MemoryArrange4GiB or + MemoryArrange.MemoryArrange4GiBSystemDev or + MemoryArrange.MemoryArrange6GiBAppletDev => 3285 * MiB, + MemoryArrange.MemoryArrange4GiBAppletDev => 2048 * MiB, + MemoryArrange.MemoryArrange6GiB or + MemoryArrange.MemoryArrange8GiB => 4916 * MiB, + _ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\"."), + }; + } + + public static ulong GetAppletPoolSize(MemoryArrange arrange) + { + return arrange switch + { + MemoryArrange.MemoryArrange4GiB => 507 * MiB, + MemoryArrange.MemoryArrange4GiBAppletDev => 1554 * MiB, + MemoryArrange.MemoryArrange4GiBSystemDev => 448 * MiB, + MemoryArrange.MemoryArrange6GiB => 562 * MiB, + MemoryArrange.MemoryArrange6GiBAppletDev or + MemoryArrange.MemoryArrange8GiB => 2193 * MiB, + _ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\"."), + }; + } + + public static ulong GetMinimumNonSecureSystemPoolSize() + { + return RequiredNonSecureSystemPoolSize; + } + + public static ulong GetDramEndAddress(MemorySize size) + { + return DramMemoryMap.DramBase + GetDramSize(size); + } + + public static ulong GenerateRandom() + { + // TODO + return 0; + } + + public static ulong GetDramSize(MemorySize size) + { + return size switch + { + MemorySize.MemorySize4GiB => 4 * GiB, + MemorySize.MemorySize6GiB => 6 * GiB, + MemorySize.MemorySize8GiB => 8 * GiB, + _ => throw new ArgumentException($"Invalid memory size \"{size}\"."), + }; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs new file mode 100644 index 00000000..3c5fa067 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs @@ -0,0 +1,199 @@ +using Ryujinx.Common; +using Ryujinx.Common.PreciseSleep; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class KTimeManager : IDisposable + { + public static readonly long DefaultTimeIncrementNanoseconds = ConvertGuestTicksToNanoseconds(2); + + private class WaitingObject + { + public IKFutureSchedulerObject Object { get; } + public long TimePoint { get; } + + public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint) + { + Object = schedulerObj; + TimePoint = timePoint; + } + } + + private readonly KernelContext _context; + private readonly List _waitingObjects; + private IPreciseSleepEvent _waitEvent; + private bool _keepRunning; + private long _enforceWakeupFromSpinWait; + + private const long NanosecondsPerSecond = 1000000000L; + private const long NanosecondsPerMillisecond = 1000000L; + + public KTimeManager(KernelContext context) + { + _context = context; + _waitingObjects = new List(); + _keepRunning = true; + + Thread work = new(WaitAndCheckScheduledObjects) + { + Name = "HLE.TimeManager", + }; + + work.Start(); + } + + public void ScheduleFutureInvocation(IKFutureSchedulerObject schedulerObj, long timeout) + { + long startTime = PerformanceCounter.ElapsedTicks; + long timePoint = startTime + ConvertNanosecondsToHostTicks(timeout); + + if (timePoint < startTime) + { + timePoint = long.MaxValue; + } + + timePoint = _waitEvent.AdjustTimePoint(timePoint, timeout); + + lock (_context.CriticalSection.Lock) + { + _waitingObjects.Add(new WaitingObject(schedulerObj, timePoint)); + + if (timeout < NanosecondsPerMillisecond) + { + Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 1); + } + } + + _waitEvent.Signal(); + } + + public void UnscheduleFutureInvocation(IKFutureSchedulerObject schedulerObj) + { + lock (_context.CriticalSection.Lock) + { + for (int index = _waitingObjects.Count - 1; index >= 0; index--) + { + if (_waitingObjects[index].Object == schedulerObj) + { + _waitingObjects.RemoveAt(index); + } + } + } + } + + private void WaitAndCheckScheduledObjects() + { + WaitingObject next; + + using (_waitEvent = PreciseSleepHelper.CreateEvent()) + { + while (_keepRunning) + { + lock (_context.CriticalSection.Lock) + { + Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 0); + + next = GetNextWaitingObject(); + } + + if (next != null) + { + long timePoint = PerformanceCounter.ElapsedTicks; + + if (next.TimePoint > timePoint) + { + if (!_waitEvent.SleepUntil(next.TimePoint)) + { + PreciseSleepHelper.SpinWaitUntilTimePoint(next.TimePoint, ref _enforceWakeupFromSpinWait); + } + } + + bool timeUp = PerformanceCounter.ElapsedTicks >= next.TimePoint; + + if (timeUp) + { + lock (_context.CriticalSection.Lock) + { + if (_waitingObjects.Remove(next)) + { + next.Object.TimeUp(); + } + } + } + } + else + { + _waitEvent.Sleep(); + } + } + } + } + + private WaitingObject GetNextWaitingObject() + { + WaitingObject selected = null; + + long lowestTimePoint = long.MaxValue; + + for (int index = _waitingObjects.Count - 1; index >= 0; index--) + { + WaitingObject current = _waitingObjects[index]; + + if (current.TimePoint <= lowestTimePoint) + { + selected = current; + lowestTimePoint = current.TimePoint; + } + } + + return selected; + } + + public static long ConvertNanosecondsToMilliseconds(long time) + { + time /= NanosecondsPerMillisecond; + + if ((ulong)time > int.MaxValue) + { + return int.MaxValue; + } + + return time; + } + + public static long ConvertMillisecondsToNanoseconds(long time) + { + return time * NanosecondsPerMillisecond; + } + + public static long ConvertNanosecondsToHostTicks(long ns) + { + long nsDiv = ns / NanosecondsPerSecond; + long nsMod = ns % NanosecondsPerSecond; + long tickDiv = PerformanceCounter.TicksPerSecond / NanosecondsPerSecond; + long tickMod = PerformanceCounter.TicksPerSecond % NanosecondsPerSecond; + + long baseTicks = (nsMod * tickMod + PerformanceCounter.TicksPerSecond - 1) / NanosecondsPerSecond; + return (nsDiv * tickDiv) * NanosecondsPerSecond + nsDiv * tickMod + nsMod * tickDiv + baseTicks; + } + + public static long ConvertGuestTicksToNanoseconds(long ticks) + { + return (long)Math.Ceiling(ticks * (1000000000.0 / 19200000.0)); + } + + public static long ConvertHostTicksToTicks(long time) + { + return (long)((time / (double)PerformanceCounter.TicksPerSecond) * 19200000.0); + } + + public void Dispose() + { + _keepRunning = false; + _waitEvent?.Signal(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs new file mode 100644 index 00000000..8021d8da --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs @@ -0,0 +1,89 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + static class KernelInit + { + private readonly struct MemoryRegion + { + public ulong Address { get; } + public ulong Size { get; } + + public ulong EndAddress => Address + Size; + + public MemoryRegion(ulong address, ulong size) + { + Address = address; + Size = size; + } + } + + public static void InitializeResourceLimit(KResourceLimit resourceLimit, MemorySize size) + { + static void EnsureSuccess(Result result) + { + if (result != Result.Success) + { + throw new InvalidOperationException($"Unexpected result \"{result}\"."); + } + } + + ulong ramSize = KSystemControl.GetDramSize(size); + + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Memory, (long)ramSize)); + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Thread, 800)); + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Event, 700)); + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.TransferMemory, 200)); + EnsureSuccess(resourceLimit.SetLimitValue(LimitableResource.Session, 900)); + + if (!resourceLimit.Reserve(LimitableResource.Memory, 0) || + !resourceLimit.Reserve(LimitableResource.Memory, 0x60000)) + { + throw new InvalidOperationException("Unexpected failure reserving memory on resource limit."); + } + } + + public static KMemoryRegionManager[] GetMemoryRegions(MemorySize size, MemoryArrange arrange) + { + ulong poolEnd = KSystemControl.GetDramEndAddress(size); + ulong applicationPoolSize = KSystemControl.GetApplicationPoolSize(arrange); + ulong appletPoolSize = KSystemControl.GetAppletPoolSize(arrange); + + MemoryRegion servicePool; + MemoryRegion nvServicesPool; + MemoryRegion appletPool; + MemoryRegion applicationPool; + + ulong nvServicesPoolSize = KSystemControl.GetMinimumNonSecureSystemPoolSize(); + + applicationPool = new MemoryRegion(poolEnd - applicationPoolSize, applicationPoolSize); + + ulong nvServicesPoolEnd = applicationPool.Address - appletPoolSize; + + nvServicesPool = new MemoryRegion(nvServicesPoolEnd - nvServicesPoolSize, nvServicesPoolSize); + appletPool = new MemoryRegion(nvServicesPoolEnd, appletPoolSize); + + // Note: There is an extra region used by the kernel, however + // since we are doing HLE we are not going to use that memory, so give all + // the remaining memory space to services. + ulong servicePoolSize = nvServicesPool.Address - DramMemoryMap.SlabHeapEnd; + + servicePool = new MemoryRegion(DramMemoryMap.SlabHeapEnd, servicePoolSize); + + return new[] + { + GetMemoryRegion(applicationPool), + GetMemoryRegion(appletPool), + GetMemoryRegion(servicePool), + GetMemoryRegion(nvServicesPool), + }; + } + + private static KMemoryRegionManager GetMemoryRegion(MemoryRegion region) + { + return new KMemoryRegionManager(region.Address, region.Size, region.EndAddress); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs new file mode 100644 index 00000000..082d25ff --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs @@ -0,0 +1,73 @@ +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Process; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + static class KernelTransfer + { + public static bool UserToKernel(out T value, ulong address) where T : unmanaged + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (currentProcess.CpuMemory.IsRangeMapped(address, (ulong)Unsafe.SizeOf())) + { + value = currentProcess.CpuMemory.Read(address); + + return true; + } + + value = default; + + return false; + } + + public static bool UserToKernelArray(ulong address, Span values) where T : unmanaged + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + Span data = MemoryMarshal.Cast(values); + + if (currentProcess.CpuMemory.IsRangeMapped(address, (ulong)data.Length)) + { + currentProcess.CpuMemory.Read(address, data); + + return true; + } + + return false; + } + + public static bool UserToKernelString(out string value, ulong address, uint size) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (currentProcess.CpuMemory.IsRangeMapped(address, size)) + { + value = MemoryHelper.ReadAsciiString(currentProcess.CpuMemory, address, size); + + return true; + } + + value = null; + + return false; + } + + public static bool KernelToUser(ulong address, T value) where T : unmanaged + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (currentProcess.CpuMemory.IsRangeMapped(address, (ulong)Unsafe.SizeOf())) + { + currentProcess.CpuMemory.Write(address, value); + + return true; + } + + return false; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs new file mode 100644 index 00000000..8655e61c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + enum LimitableResource : byte + { + Memory = 0, + Thread = 1, + Event = 2, + TransferMemory = 3, + Session = 4, + + Count = 5, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/MemoryArrange.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/MemoryArrange.cs new file mode 100644 index 00000000..2c88d8b3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/MemoryArrange.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + enum MemoryArrange : byte + { + MemoryArrange4GiB, + MemoryArrange4GiBAppletDev, + MemoryArrange4GiBSystemDev, + MemoryArrange6GiB, + MemoryArrange6GiBAppletDev, + MemoryArrange8GiB, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/MemorySize.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/MemorySize.cs new file mode 100644 index 00000000..7cc34a72 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/MemorySize.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + enum MemorySize : byte + { + MemorySize4GiB = 0, + MemorySize6GiB = 1, + MemorySize8GiB = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs b/src/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs new file mode 100644 index 00000000..9f078b10 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs @@ -0,0 +1,128 @@ +using System.Numerics; + +namespace Ryujinx.HLE.HOS.Kernel.Common +{ + class MersenneTwister + { + private int _index; + private readonly uint[] _mt; + + public MersenneTwister(uint seed) + { + _mt = new uint[624]; + + _mt[0] = seed; + + for (int mtIdx = 1; mtIdx < _mt.Length; mtIdx++) + { + uint prev = _mt[mtIdx - 1]; + + _mt[mtIdx] = (uint)(0x6c078965 * (prev ^ (prev >> 30)) + mtIdx); + } + + _index = _mt.Length; + } + + public long GenRandomNumber(long min, long max) + { + long range = max - min; + + if (min == max) + { + return min; + } + + if (range == -1) + { + // Increment would cause a overflow, special case. + return GenRandomNumber(2, 2, 32, 0xffffffffu, 0xffffffffu); + } + + range++; + + // This is log2(Range) plus one. + int nextRangeLog2 = 64 - BitOperations.LeadingZeroCount((ulong)range); + + // If Range is already power of 2, subtract one to use log2(Range) directly. + int rangeLog2 = nextRangeLog2 - (BitOperations.IsPow2(range) ? 1 : 0); + + int parts = rangeLog2 > 32 ? 2 : 1; + int bitsPerPart = rangeLog2 / parts; + + int fullParts = parts - (rangeLog2 - parts * bitsPerPart); + + uint mask = 0xffffffffu >> (32 - bitsPerPart); + uint maskPlus1 = 0xffffffffu >> (31 - bitsPerPart); + + long randomNumber; + + do + { + randomNumber = GenRandomNumber(parts, fullParts, bitsPerPart, mask, maskPlus1); + } + while ((ulong)randomNumber >= (ulong)range); + + return min + randomNumber; + } + + private long GenRandomNumber( + int parts, + int fullParts, + int bitsPerPart, + uint mask, + uint maskPlus1) + { + long randomNumber = 0; + + int part = 0; + + for (; part < fullParts; part++) + { + randomNumber <<= bitsPerPart; + randomNumber |= GenRandomNumber() & mask; + } + + for (; part < parts; part++) + { + randomNumber <<= bitsPerPart + 1; + randomNumber |= GenRandomNumber() & maskPlus1; + } + + return randomNumber; + } + + private uint GenRandomNumber() + { + if (_index >= _mt.Length) + { + Twist(); + } + + uint value = _mt[_index++]; + + value ^= value >> 11; + value ^= (value << 7) & 0x9d2c5680; + value ^= (value << 15) & 0xefc60000; + value ^= value >> 18; + + return value; + } + + private void Twist() + { + for (int mtIdx = 0; mtIdx < _mt.Length; mtIdx++) + { + uint value = (_mt[mtIdx] & 0x80000000) + (_mt[(mtIdx + 1) % _mt.Length] & 0x7fffffff); + + _mt[mtIdx] = _mt[(mtIdx + 397) % _mt.Length] ^ (value >> 1); + + if ((value & 1) != 0) + { + _mt[mtIdx] ^= 0x9908b0df; + } + } + + _index = 0; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs new file mode 100644 index 00000000..3a943f94 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + enum ChannelState + { + NotInitialized, + Open, + ClientDisconnected, + ServerDisconnected, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs new file mode 100644 index 00000000..5fa9cbe7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KBufferDescriptor + { + public ulong ClientAddress { get; } + public ulong ServerAddress { get; } + public ulong Size { get; } + public MemoryState State { get; } + + public KBufferDescriptor(ulong src, ulong dst, ulong size, MemoryState state) + { + ClientAddress = src; + ServerAddress = dst; + Size = size; + State = state; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs new file mode 100644 index 00000000..373899b7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs @@ -0,0 +1,223 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.Horizon.Common; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KBufferDescriptorTable + { + private const int MaxInternalBuffersCount = 8; + + private readonly List _sendBufferDescriptors; + private readonly List _receiveBufferDescriptors; + private readonly List _exchangeBufferDescriptors; + + public KBufferDescriptorTable() + { + _sendBufferDescriptors = new List(MaxInternalBuffersCount); + _receiveBufferDescriptors = new List(MaxInternalBuffersCount); + _exchangeBufferDescriptors = new List(MaxInternalBuffersCount); + } + + public Result AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state) + { + return Add(_sendBufferDescriptors, src, dst, size, state); + } + + public Result AddReceiveBuffer(ulong src, ulong dst, ulong size, MemoryState state) + { + return Add(_receiveBufferDescriptors, src, dst, size, state); + } + + public Result AddExchangeBuffer(ulong src, ulong dst, ulong size, MemoryState state) + { + return Add(_exchangeBufferDescriptors, src, dst, size, state); + } + + private static Result Add(List list, ulong src, ulong dst, ulong size, MemoryState state) + { + if (list.Count < MaxInternalBuffersCount) + { + list.Add(new KBufferDescriptor(src, dst, size, state)); + + return Result.Success; + } + + return KernelResult.OutOfMemory; + } + + public Result CopyBuffersToClient(KPageTableBase memoryManager) + { + Result result = CopyToClient(memoryManager, _receiveBufferDescriptors); + + if (result != Result.Success) + { + return result; + } + + return CopyToClient(memoryManager, _exchangeBufferDescriptors); + } + + private static Result CopyToClient(KPageTableBase memoryManager, List list) + { + foreach (KBufferDescriptor desc in list) + { + MemoryState stateMask; + + switch (desc.State) + { + case MemoryState.IpcBuffer0: + stateMask = MemoryState.IpcSendAllowedType0; + break; + case MemoryState.IpcBuffer1: + stateMask = MemoryState.IpcSendAllowedType1; + break; + case MemoryState.IpcBuffer3: + stateMask = MemoryState.IpcSendAllowedType3; + break; + default: + return KernelResult.InvalidCombination; + } + + MemoryAttribute attributeMask = MemoryAttribute.Borrowed | MemoryAttribute.Uncached; + + if (desc.State == MemoryState.IpcBuffer0) + { + attributeMask |= MemoryAttribute.DeviceMapped; + } + + ulong clientAddrTruncated = BitUtils.AlignDown(desc.ClientAddress, KPageTableBase.PageSize); + ulong clientAddrRounded = BitUtils.AlignUp(desc.ClientAddress, KPageTableBase.PageSize); + + // Check if address is not aligned, in this case we need to perform 2 copies. + if (clientAddrTruncated != clientAddrRounded) + { + ulong copySize = clientAddrRounded - desc.ClientAddress; + + if (copySize > desc.Size) + { + copySize = desc.Size; + } + + Result result = memoryManager.CopyDataFromCurrentProcess( + desc.ClientAddress, + copySize, + stateMask, + stateMask, + KMemoryPermission.ReadAndWrite, + attributeMask, + MemoryAttribute.None, + desc.ServerAddress); + + if (result != Result.Success) + { + return result; + } + } + + ulong clientEndAddr = desc.ClientAddress + desc.Size; + ulong serverEndAddr = desc.ServerAddress + desc.Size; + + ulong clientEndAddrTruncated = BitUtils.AlignDown(clientEndAddr, (ulong)KPageTableBase.PageSize); + ulong clientEndAddrRounded = BitUtils.AlignUp(clientEndAddr, KPageTableBase.PageSize); + ulong serverEndAddrTruncated = BitUtils.AlignDown(serverEndAddr, (ulong)KPageTableBase.PageSize); + + if (clientEndAddrTruncated < clientEndAddrRounded && + (clientAddrTruncated == clientAddrRounded || clientAddrTruncated < clientEndAddrTruncated)) + { + Result result = memoryManager.CopyDataFromCurrentProcess( + clientEndAddrTruncated, + clientEndAddr - clientEndAddrTruncated, + stateMask, + stateMask, + KMemoryPermission.ReadAndWrite, + attributeMask, + MemoryAttribute.None, + serverEndAddrTruncated); + + if (result != Result.Success) + { + return result; + } + } + } + + return Result.Success; + } + + public Result UnmapServerBuffers(KPageTableBase memoryManager) + { + Result result = UnmapServer(memoryManager, _sendBufferDescriptors); + + if (result != Result.Success) + { + return result; + } + + result = UnmapServer(memoryManager, _receiveBufferDescriptors); + + if (result != Result.Success) + { + return result; + } + + return UnmapServer(memoryManager, _exchangeBufferDescriptors); + } + + private static Result UnmapServer(KPageTableBase memoryManager, List list) + { + foreach (KBufferDescriptor descriptor in list) + { + Result result = memoryManager.UnmapNoAttributeIfStateEquals( + descriptor.ServerAddress, + descriptor.Size, + descriptor.State); + + if (result != Result.Success) + { + return result; + } + } + + return Result.Success; + } + + public Result RestoreClientBuffers(KPageTableBase memoryManager) + { + Result result = RestoreClient(memoryManager, _sendBufferDescriptors); + + if (result != Result.Success) + { + return result; + } + + result = RestoreClient(memoryManager, _receiveBufferDescriptors); + + if (result != Result.Success) + { + return result; + } + + return RestoreClient(memoryManager, _exchangeBufferDescriptors); + } + + private static Result RestoreClient(KPageTableBase memoryManager, List list) + { + foreach (KBufferDescriptor descriptor in list) + { + Result result = memoryManager.UnmapIpcRestorePermission( + descriptor.ClientAddress, + descriptor.Size, + descriptor.State); + + if (result != Result.Success) + { + return result; + } + } + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs new file mode 100644 index 00000000..6355fb6e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs @@ -0,0 +1,144 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.Horizon.Common; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KClientPort : KSynchronizationObject + { + private int _sessionsCount; + private readonly int _maxSessions; + + private readonly KPort _parent; + + public bool IsLight => _parent.IsLight; + + public KClientPort(KernelContext context, KPort parent, int maxSessions) : base(context) + { + _maxSessions = maxSessions; + _parent = parent; + } + + public Result Connect(out KClientSession clientSession) + { + clientSession = null; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (currentProcess.ResourceLimit != null && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1)) + { + return KernelResult.ResLimitExceeded; + } + + if (!IncrementSessionsCount()) + { + currentProcess.ResourceLimit?.Release(LimitableResource.Session, 1); + + return KernelResult.SessionCountExceeded; + } + + KSession session = new(KernelContext, this); + + Result result = _parent.EnqueueIncomingSession(session.ServerSession); + + if (result != Result.Success) + { + session.ClientSession.DecrementReferenceCount(); + session.ServerSession.DecrementReferenceCount(); + + return result; + } + + clientSession = session.ClientSession; + + return result; + } + + public Result ConnectLight(out KLightClientSession clientSession) + { + clientSession = null; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (currentProcess.ResourceLimit != null && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1)) + { + return KernelResult.ResLimitExceeded; + } + + if (!IncrementSessionsCount()) + { + currentProcess.ResourceLimit?.Release(LimitableResource.Session, 1); + + return KernelResult.SessionCountExceeded; + } + + KLightSession session = new(KernelContext); + + Result result = _parent.EnqueueIncomingLightSession(session.ServerSession); + + if (result != Result.Success) + { + session.ClientSession.DecrementReferenceCount(); + session.ServerSession.DecrementReferenceCount(); + + return result; + } + + clientSession = session.ClientSession; + + return result; + } + + private bool IncrementSessionsCount() + { + while (true) + { + int currentCount = _sessionsCount; + + if (currentCount < _maxSessions) + { + if (Interlocked.CompareExchange(ref _sessionsCount, currentCount + 1, currentCount) == currentCount) + { + return true; + } + } + else + { + return false; + } + } + } + + public void Disconnect() + { + KernelContext.CriticalSection.Enter(); + + SignalIfMaximumReached(Interlocked.Decrement(ref _sessionsCount)); + + KernelContext.CriticalSection.Leave(); + } + + private void SignalIfMaximumReached(int value) + { + if (value == _maxSessions) + { + Signal(); + } + } + + public new static Result RemoveName(KernelContext context, string name) + { + KAutoObject foundObj = FindNamedObject(context, name); + + if (foundObj is not KClientPort) + { + return KernelResult.NotFound; + } + + return KAutoObject.RemoveName(context, name); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs new file mode 100644 index 00000000..385f0902 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs @@ -0,0 +1,84 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KClientSession : KSynchronizationObject + { + public KProcess CreatorProcess { get; } + + private readonly KSession _parent; + + public ChannelState State { get; set; } + + public KClientPort ParentPort { get; } + + public KClientSession(KernelContext context, KSession parent, KClientPort parentPort) : base(context) + { + _parent = parent; + ParentPort = parentPort; + + parentPort?.IncrementReferenceCount(); + + State = ChannelState.Open; + + CreatorProcess = KernelStatic.GetCurrentProcess(); + CreatorProcess.IncrementReferenceCount(); + } + + public Result SendSyncRequest(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize); + + KernelContext.CriticalSection.Enter(); + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = Result.Success; + + Result result = _parent.ServerSession.EnqueueRequest(request); + + KernelContext.CriticalSection.Leave(); + + if (result == Result.Success) + { + result = currentThread.ObjSyncResult; + } + + return result; + } + + public Result SendAsyncRequest(KWritableEvent asyncEvent, ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent); + + KernelContext.CriticalSection.Enter(); + + Result result = _parent.ServerSession.EnqueueRequest(request); + + KernelContext.CriticalSection.Leave(); + + return result; + } + + public void DisconnectFromPort() + { + if (ParentPort != null) + { + ParentPort.Disconnect(); + ParentPort.DecrementReferenceCount(); + } + } + + protected override void Destroy() + { + _parent.DisconnectClient(); + _parent.DecrementReferenceCount(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs new file mode 100644 index 00000000..1ff37282 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs @@ -0,0 +1,16 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KLightClientSession : KAutoObject + { +#pragma warning disable IDE0052 // Remove unread private member + private readonly KLightSession _parent; +#pragma warning restore IDE0052 + + public KLightClientSession(KernelContext context, KLightSession parent) : base(context) + { + _parent = parent; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs new file mode 100644 index 00000000..c355409e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs @@ -0,0 +1,16 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KLightServerSession : KAutoObject + { +#pragma warning disable IDE0052 // Remove unread private member + private readonly KLightSession _parent; +#pragma warning restore IDE0052 + + public KLightServerSession(KernelContext context, KLightSession parent) : base(context) + { + _parent = parent; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs new file mode 100644 index 00000000..16158a70 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs @@ -0,0 +1,16 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KLightSession : KAutoObject + { + public KLightServerSession ServerSession { get; } + public KLightClientSession ClientSession { get; } + + public KLightSession(KernelContext context) : base(context) + { + ServerSession = new KLightServerSession(context, this); + ClientSession = new KLightClientSession(context, this); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs new file mode 100644 index 00000000..84ebcbc3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs @@ -0,0 +1,74 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.Horizon.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KPort : KAutoObject + { + public KServerPort ServerPort { get; } + public KClientPort ClientPort { get; } + +#pragma warning disable IDE0052 // Remove unread private member + private readonly string _name; +#pragma warning restore IDE0052 + + private readonly ChannelState _state; + + public bool IsLight { get; private set; } + + public KPort(KernelContext context, int maxSessions, bool isLight, string name) : base(context) + { + ServerPort = new KServerPort(context, this); + ClientPort = new KClientPort(context, this, maxSessions); + + IsLight = isLight; + _name = name; + + _state = ChannelState.Open; + } + + public Result EnqueueIncomingSession(KServerSession session) + { + Result result; + + KernelContext.CriticalSection.Enter(); + + if (_state == ChannelState.Open) + { + ServerPort.EnqueueIncomingSession(session); + + result = Result.Success; + } + else + { + result = KernelResult.PortClosed; + } + + KernelContext.CriticalSection.Leave(); + + return result; + } + + public Result EnqueueIncomingLightSession(KLightServerSession session) + { + Result result; + + KernelContext.CriticalSection.Enter(); + + if (_state == ChannelState.Open) + { + ServerPort.EnqueueIncomingLightSession(session); + + result = Result.Success; + } + else + { + result = KernelResult.PortClosed; + } + + KernelContext.CriticalSection.Leave(); + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs new file mode 100644 index 00000000..08efa8d9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs @@ -0,0 +1,87 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KServerPort : KSynchronizationObject + { + private readonly LinkedList _incomingConnections; + private readonly LinkedList _lightIncomingConnections; + + private readonly KPort _parent; + + public bool IsLight => _parent.IsLight; + + public KServerPort(KernelContext context, KPort parent) : base(context) + { + _parent = parent; + + _incomingConnections = new LinkedList(); + _lightIncomingConnections = new LinkedList(); + } + + public void EnqueueIncomingSession(KServerSession session) + { + AcceptIncomingConnection(_incomingConnections, session); + } + + public void EnqueueIncomingLightSession(KLightServerSession session) + { + AcceptIncomingConnection(_lightIncomingConnections, session); + } + + private void AcceptIncomingConnection(LinkedList list, T session) + { + KernelContext.CriticalSection.Enter(); + + list.AddLast(session); + + if (list.Count == 1) + { + Signal(); + } + + KernelContext.CriticalSection.Leave(); + } + + public KServerSession AcceptIncomingConnection() + { + return AcceptIncomingConnection(_incomingConnections); + } + + public KLightServerSession AcceptIncomingLightConnection() + { + return AcceptIncomingConnection(_lightIncomingConnections); + } + + private T AcceptIncomingConnection(LinkedList list) + { + T session = default; + + KernelContext.CriticalSection.Enter(); + + if (list.Count != 0) + { + session = list.First.Value; + + list.RemoveFirst(); + } + + KernelContext.CriticalSection.Leave(); + + return session; + } + + public override bool IsSignaled() + { + if (_parent.IsLight) + { + return _lightIncomingConnections.Count != 0; + } + else + { + return _incomingConnections.Count != 0; + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs new file mode 100644 index 00000000..3b428085 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs @@ -0,0 +1,1247 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KServerSession : KSynchronizationObject + { + private static readonly MemoryState[] _ipcMemoryStates = { + MemoryState.IpcBuffer3, + MemoryState.IpcBuffer0, + MemoryState.IpcBuffer1, + (MemoryState)0xfffce5d4, //This is invalid, shouldn't be accessed. + }; + + private readonly struct Message + { + public ulong Address { get; } + public ulong Size { get; } + public bool IsCustom { get; } + + public Message(KThread thread, ulong customCmdBuffAddress, ulong customCmdBuffSize) + { + IsCustom = customCmdBuffAddress != 0; + + if (IsCustom) + { + Address = customCmdBuffAddress; + Size = customCmdBuffSize; + } + else + { + Address = thread.TlsAddress; + Size = 0x100; + } + } + + public Message(KSessionRequest request) : this( + request.ClientThread, + request.CustomCmdBuffAddr, + request.CustomCmdBuffSize) + { } + } + + private readonly struct MessageHeader + { + public uint Word0 { get; } + public uint Word1 { get; } + public uint Word2 { get; } + + public uint PointerBuffersCount { get; } + public uint SendBuffersCount { get; } + public uint ReceiveBuffersCount { get; } + public uint ExchangeBuffersCount { get; } + + public uint RawDataSizeInWords { get; } + + public uint ReceiveListType { get; } + + public uint MessageSizeInWords { get; } + public uint ReceiveListOffsetInWords { get; } + public uint ReceiveListOffset { get; } + + public bool HasHandles { get; } + + public bool HasPid { get; } + + public uint CopyHandlesCount { get; } + public uint MoveHandlesCount { get; } + + public MessageHeader(uint word0, uint word1, uint word2) + { + Word0 = word0; + Word1 = word1; + Word2 = word2; + + HasHandles = word1 >> 31 != 0; + + uint handleDescSizeInWords = 0; + + if (HasHandles) + { + uint pidSize = (word2 & 1) * 8; + + HasPid = pidSize != 0; + + CopyHandlesCount = (word2 >> 1) & 0xf; + MoveHandlesCount = (word2 >> 5) & 0xf; + + handleDescSizeInWords = (pidSize + CopyHandlesCount * 4 + MoveHandlesCount * 4) / 4; + } + else + { + HasPid = false; + + CopyHandlesCount = 0; + MoveHandlesCount = 0; + } + + PointerBuffersCount = (word0 >> 16) & 0xf; + SendBuffersCount = (word0 >> 20) & 0xf; + ReceiveBuffersCount = (word0 >> 24) & 0xf; + ExchangeBuffersCount = word0 >> 28; + + uint pointerDescSizeInWords = PointerBuffersCount * 2; + uint sendDescSizeInWords = SendBuffersCount * 3; + uint receiveDescSizeInWords = ReceiveBuffersCount * 3; + uint exchangeDescSizeInWords = ExchangeBuffersCount * 3; + + RawDataSizeInWords = word1 & 0x3ff; + + ReceiveListType = (word1 >> 10) & 0xf; + + ReceiveListOffsetInWords = (word1 >> 20) & 0x7ff; + + uint paddingSizeInWords = HasHandles ? 3u : 2u; + + MessageSizeInWords = pointerDescSizeInWords + + sendDescSizeInWords + + receiveDescSizeInWords + + exchangeDescSizeInWords + + RawDataSizeInWords + + paddingSizeInWords + + handleDescSizeInWords; + + if (ReceiveListOffsetInWords == 0) + { + ReceiveListOffsetInWords = MessageSizeInWords; + } + + ReceiveListOffset = ReceiveListOffsetInWords * 4; + } + } + + private struct PointerBufferDesc + { + public uint ReceiveIndex { get; } + + public uint BufferSize { get; } + public ulong BufferAddress { get; set; } + + public PointerBufferDesc(ulong dword) + { + ReceiveIndex = (uint)dword & 0xf; + BufferSize = (uint)dword >> 16; + + BufferAddress = (dword >> 2) & 0x70; + BufferAddress |= (dword >> 12) & 0xf; + + BufferAddress = (BufferAddress << 32) | (dword >> 32); + } + + public readonly ulong Pack() + { + ulong dword = (ReceiveIndex & 0xf) | ((BufferSize & 0xffff) << 16); + + dword |= BufferAddress << 32; + dword |= (BufferAddress >> 20) & 0xf000; + dword |= (BufferAddress >> 30) & 0xffc0; + + return dword; + } + } + + private readonly KSession _parent; + + private readonly LinkedList _requests; + + private KSessionRequest _activeRequest; + + public KServerSession(KernelContext context, KSession parent) : base(context) + { + _parent = parent; + + _requests = new LinkedList(); + } + + public Result EnqueueRequest(KSessionRequest request) + { + if (_parent.ClientSession.State != ChannelState.Open) + { + return KernelResult.PortRemoteClosed; + } + + if (request.AsyncEvent == null) + { + if (request.ClientThread.TerminationRequested) + { + return KernelResult.ThreadTerminating; + } + + request.ClientThread.Reschedule(ThreadSchedState.Paused); + } + + _requests.AddLast(request); + + if (_requests.Count == 1) + { + Signal(); + } + + return Result.Success; + } + + public Result Receive(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) + { + KThread serverThread = KernelStatic.GetCurrentThread(); + KProcess serverProcess = serverThread.Owner; + + KernelContext.CriticalSection.Enter(); + + if (_parent.ClientSession.State != ChannelState.Open) + { + KernelContext.CriticalSection.Leave(); + + return KernelResult.PortRemoteClosed; + } + + if (_activeRequest != null || !DequeueRequest(out KSessionRequest request)) + { + KernelContext.CriticalSection.Leave(); + + return KernelResult.NotFound; + } + + if (request.ClientThread == null) + { + KernelContext.CriticalSection.Leave(); + + return KernelResult.PortRemoteClosed; + } + + KThread clientThread = request.ClientThread; + KProcess clientProcess = clientThread.Owner; + + KernelContext.CriticalSection.Leave(); + + _activeRequest = request; + + request.ServerProcess = serverProcess; + + Message clientMsg = new(request); + Message serverMsg = new(serverThread, customCmdBuffAddr, customCmdBuffSize); + + MessageHeader clientHeader = GetClientMessageHeader(clientProcess, clientMsg); + MessageHeader serverHeader = GetServerMessageHeader(serverMsg); + + Result serverResult = KernelResult.NotFound; + Result clientResult = Result.Success; + + void CleanUpForError() + { + if (request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager) == Result.Success) + { + request.BufferDescriptorTable.RestoreClientBuffers(clientProcess.MemoryManager); + } + + CloseAllHandles(serverMsg, clientHeader, serverProcess); + + KernelContext.CriticalSection.Enter(); + + _activeRequest = null; + + if (_requests.Count != 0) + { + Signal(); + } + + KernelContext.CriticalSection.Leave(); + + WakeClientThread(request, clientResult); + } + + if (clientHeader.ReceiveListType < 2 && + clientHeader.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (clientHeader.ReceiveListType == 2 && + clientHeader.ReceiveListOffset + 8 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (clientHeader.ReceiveListType > 2 && + clientHeader.ReceiveListType * 8 - 0x10 + clientHeader.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (clientHeader.ReceiveListOffsetInWords < clientHeader.MessageSizeInWords) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (clientHeader.MessageSizeInWords * 4 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.CmdBufferTooSmall; + } + + ulong[] receiveList = GetReceiveList( + serverProcess, + serverMsg, + serverHeader.ReceiveListType, + serverHeader.ReceiveListOffset); + + serverProcess.CpuMemory.Write(serverMsg.Address + 0, clientHeader.Word0); + serverProcess.CpuMemory.Write(serverMsg.Address + 4, clientHeader.Word1); + + uint offset; + + // Copy handles. + if (clientHeader.HasHandles) + { + if (clientHeader.MoveHandlesCount != 0) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + serverProcess.CpuMemory.Write(serverMsg.Address + 8, clientHeader.Word2); + + offset = 3; + + if (clientHeader.HasPid) + { + serverProcess.CpuMemory.Write(serverMsg.Address + offset * 4, clientProcess.Pid); + + offset += 2; + } + + for (int index = 0; index < clientHeader.CopyHandlesCount; index++) + { + int newHandle = 0; + int handle = clientProcess.CpuMemory.Read(clientMsg.Address + offset * 4); + + if (clientResult == Result.Success && handle != 0) + { + clientResult = GetCopyObjectHandle(clientThread, serverProcess, handle, out newHandle); + } + + serverProcess.CpuMemory.Write(serverMsg.Address + offset * 4, newHandle); + + offset++; + } + + for (int index = 0; index < clientHeader.MoveHandlesCount; index++) + { + int newHandle = 0; + int handle = clientProcess.CpuMemory.Read(clientMsg.Address + offset * 4); + + if (handle != 0) + { + if (clientResult == Result.Success) + { + clientResult = GetMoveObjectHandle(clientProcess, serverProcess, handle, out newHandle); + } + else + { + clientProcess.HandleTable.CloseHandle(handle); + } + } + + serverProcess.CpuMemory.Write(serverMsg.Address + offset * 4, newHandle); + + offset++; + } + + if (clientResult != Result.Success) + { + CleanUpForError(); + + return serverResult; + } + } + else + { + offset = 2; + } + + // Copy pointer/receive list buffers. + uint recvListDstOffset = 0; + + for (int index = 0; index < clientHeader.PointerBuffersCount; index++) + { + ulong pointerDesc = clientProcess.CpuMemory.Read(clientMsg.Address + offset * 4); + + PointerBufferDesc descriptor = new(pointerDesc); + + if (descriptor.BufferSize != 0) + { + clientResult = GetReceiveListAddress( + descriptor, + serverMsg, + serverHeader.ReceiveListType, + clientHeader.MessageSizeInWords, + receiveList, + ref recvListDstOffset, + out ulong recvListBufferAddress); + + if (clientResult != Result.Success) + { + CleanUpForError(); + + return serverResult; + } + + clientResult = clientProcess.MemoryManager.CopyDataToCurrentProcess( + recvListBufferAddress, + descriptor.BufferSize, + descriptor.BufferAddress, + MemoryState.IsPoolAllocated, + MemoryState.IsPoolAllocated, + KMemoryPermission.Read, + MemoryAttribute.Uncached, + MemoryAttribute.None); + + if (clientResult != Result.Success) + { + CleanUpForError(); + + return serverResult; + } + + descriptor.BufferAddress = recvListBufferAddress; + } + else + { + descriptor.BufferAddress = 0; + } + + serverProcess.CpuMemory.Write(serverMsg.Address + offset * 4, descriptor.Pack()); + + offset += 2; + } + + // Copy send, receive and exchange buffers. + uint totalBuffersCount = + clientHeader.SendBuffersCount + + clientHeader.ReceiveBuffersCount + + clientHeader.ExchangeBuffersCount; + + for (int index = 0; index < totalBuffersCount; index++) + { + ulong clientDescAddress = clientMsg.Address + offset * 4; + + uint descWord0 = clientProcess.CpuMemory.Read(clientDescAddress + 0); + uint descWord1 = clientProcess.CpuMemory.Read(clientDescAddress + 4); + uint descWord2 = clientProcess.CpuMemory.Read(clientDescAddress + 8); + + bool isSendDesc = index < clientHeader.SendBuffersCount; + bool isExchangeDesc = index >= clientHeader.SendBuffersCount + clientHeader.ReceiveBuffersCount; + + bool notReceiveDesc = isSendDesc || isExchangeDesc; + bool isReceiveDesc = !notReceiveDesc; + + KMemoryPermission permission = index >= clientHeader.SendBuffersCount + ? KMemoryPermission.ReadAndWrite + : KMemoryPermission.Read; + + uint sizeHigh4 = (descWord2 >> 24) & 0xf; + + ulong bufferSize = descWord0 | (ulong)sizeHigh4 << 32; + + ulong dstAddress = 0; + + if (bufferSize != 0) + { + ulong bufferAddress; + + bufferAddress = descWord2 >> 28; + bufferAddress |= ((descWord2 >> 2) & 7) << 4; + + bufferAddress = (bufferAddress << 32) | descWord1; + + MemoryState state = _ipcMemoryStates[(descWord2 + 1) & 3]; + + clientResult = serverProcess.MemoryManager.MapBufferFromClientProcess( + bufferSize, + bufferAddress, + clientProcess.MemoryManager, + permission, + state, + notReceiveDesc, + out dstAddress); + + if (clientResult != Result.Success) + { + CleanUpForError(); + + return serverResult; + } + + if (isSendDesc) + { + clientResult = request.BufferDescriptorTable.AddSendBuffer(bufferAddress, dstAddress, bufferSize, state); + } + else if (isReceiveDesc) + { + clientResult = request.BufferDescriptorTable.AddReceiveBuffer(bufferAddress, dstAddress, bufferSize, state); + } + else /* if (isExchangeDesc) */ + { + clientResult = request.BufferDescriptorTable.AddExchangeBuffer(bufferAddress, dstAddress, bufferSize, state); + } + + if (clientResult != Result.Success) + { + CleanUpForError(); + + return serverResult; + } + } + + descWord1 = (uint)dstAddress; + + descWord2 &= 3; + + descWord2 |= sizeHigh4 << 24; + + descWord2 |= (uint)(dstAddress >> 34) & 0x3ffffffc; + descWord2 |= (uint)(dstAddress >> 4) & 0xf0000000; + + ulong serverDescAddress = serverMsg.Address + offset * 4; + + serverProcess.CpuMemory.Write(serverDescAddress + 0, descWord0); + serverProcess.CpuMemory.Write(serverDescAddress + 4, descWord1); + serverProcess.CpuMemory.Write(serverDescAddress + 8, descWord2); + + offset += 3; + } + + // Copy raw data. + if (clientHeader.RawDataSizeInWords != 0) + { + ulong copySrc = clientMsg.Address + offset * 4; + ulong copyDst = serverMsg.Address + offset * 4; + + ulong copySize = clientHeader.RawDataSizeInWords * 4; + + if (serverMsg.IsCustom || clientMsg.IsCustom) + { + KMemoryPermission permission = clientMsg.IsCustom + ? KMemoryPermission.None + : KMemoryPermission.Read; + + clientResult = clientProcess.MemoryManager.CopyDataToCurrentProcess( + copyDst, + copySize, + copySrc, + MemoryState.IsPoolAllocated, + MemoryState.IsPoolAllocated, + permission, + MemoryAttribute.Uncached, + MemoryAttribute.None); + } + else + { + serverProcess.CpuMemory.Write(copyDst, clientProcess.CpuMemory.GetReadOnlySequence(copySrc, (int)copySize)); + } + + if (clientResult != Result.Success) + { + CleanUpForError(); + + return serverResult; + } + } + + return Result.Success; + } + + public Result Reply(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0) + { + KThread serverThread = KernelStatic.GetCurrentThread(); + KProcess serverProcess = serverThread.Owner; + + KernelContext.CriticalSection.Enter(); + + if (_activeRequest == null) + { + KernelContext.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + KSessionRequest request = _activeRequest; + + _activeRequest = null; + + if (_requests.Count != 0) + { + Signal(); + } + + KernelContext.CriticalSection.Leave(); + + KThread clientThread = request.ClientThread; + KProcess clientProcess = clientThread.Owner; + + Message clientMsg = new(request); + Message serverMsg = new(serverThread, customCmdBuffAddr, customCmdBuffSize); + + MessageHeader clientHeader = GetClientMessageHeader(clientProcess, clientMsg); + MessageHeader serverHeader = GetServerMessageHeader(serverMsg); + + Result clientResult = Result.Success; + Result serverResult = Result.Success; + + void CleanUpForError() + { + CloseAllHandles(clientMsg, serverHeader, clientProcess); + + FinishRequest(request, clientResult); + } + + if (clientHeader.ReceiveListType < 2 && + clientHeader.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (clientHeader.ReceiveListType == 2 && + clientHeader.ReceiveListOffset + 8 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + else if (clientHeader.ReceiveListType > 2 && + clientHeader.ReceiveListType * 8 - 0x10 + clientHeader.ReceiveListOffset > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (clientHeader.ReceiveListOffsetInWords < clientHeader.MessageSizeInWords) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + if (serverHeader.MessageSizeInWords * 4 > clientMsg.Size) + { + CleanUpForError(); + + return KernelResult.CmdBufferTooSmall; + } + + if (serverHeader.SendBuffersCount != 0 || + serverHeader.ReceiveBuffersCount != 0 || + serverHeader.ExchangeBuffersCount != 0) + { + CleanUpForError(); + + return KernelResult.InvalidCombination; + } + + // Read receive list. + ulong[] receiveList = GetReceiveList( + clientProcess, + clientMsg, + clientHeader.ReceiveListType, + clientHeader.ReceiveListOffset); + + // Copy receive and exchange buffers. + clientResult = request.BufferDescriptorTable.CopyBuffersToClient(clientProcess.MemoryManager); + + if (clientResult != Result.Success) + { + CleanUpForError(); + + return serverResult; + } + + // Copy header. + clientProcess.CpuMemory.Write(clientMsg.Address + 0, serverHeader.Word0); + clientProcess.CpuMemory.Write(clientMsg.Address + 4, serverHeader.Word1); + + // Copy handles. + uint offset; + + if (serverHeader.HasHandles) + { + offset = 3; + + clientProcess.CpuMemory.Write(clientMsg.Address + 8, serverHeader.Word2); + + if (serverHeader.HasPid) + { + clientProcess.CpuMemory.Write(clientMsg.Address + offset * 4, serverProcess.Pid); + + offset += 2; + } + + for (int index = 0; index < serverHeader.CopyHandlesCount; index++) + { + int newHandle = 0; + + int handle = serverProcess.CpuMemory.Read(serverMsg.Address + offset * 4); + + if (handle != 0) + { + GetCopyObjectHandle(serverThread, clientProcess, handle, out newHandle); + } + + clientProcess.CpuMemory.Write(clientMsg.Address + offset * 4, newHandle); + + offset++; + } + + for (int index = 0; index < serverHeader.MoveHandlesCount; index++) + { + int newHandle = 0; + + int handle = serverProcess.CpuMemory.Read(serverMsg.Address + offset * 4); + + if (handle != 0) + { + if (clientResult == Result.Success) + { + clientResult = GetMoveObjectHandle(serverProcess, clientProcess, handle, out newHandle); + } + else + { + serverProcess.HandleTable.CloseHandle(handle); + } + } + + clientProcess.CpuMemory.Write(clientMsg.Address + offset * 4, newHandle); + + offset++; + } + } + else + { + offset = 2; + } + + // Copy pointer/receive list buffers. + uint recvListDstOffset = 0; + + for (int index = 0; index < serverHeader.PointerBuffersCount; index++) + { + ulong pointerDesc = serverProcess.CpuMemory.Read(serverMsg.Address + offset * 4); + + PointerBufferDesc descriptor = new(pointerDesc); + + ulong recvListBufferAddress = 0; + + if (descriptor.BufferSize != 0) + { + clientResult = GetReceiveListAddress( + descriptor, + clientMsg, + clientHeader.ReceiveListType, + serverHeader.MessageSizeInWords, + receiveList, + ref recvListDstOffset, + out recvListBufferAddress); + + if (clientResult != Result.Success) + { + CleanUpForError(); + + return serverResult; + } + + clientResult = clientProcess.MemoryManager.CopyDataFromCurrentProcess( + recvListBufferAddress, + descriptor.BufferSize, + MemoryState.IsPoolAllocated, + MemoryState.IsPoolAllocated, + KMemoryPermission.Read, + MemoryAttribute.Uncached, + MemoryAttribute.None, + descriptor.BufferAddress); + + if (clientResult != Result.Success) + { + CleanUpForError(); + + return serverResult; + } + } + + ulong dstDescAddress = clientMsg.Address + offset * 4; + + ulong clientPointerDesc = + (recvListBufferAddress << 32) | + ((recvListBufferAddress >> 20) & 0xf000) | + ((recvListBufferAddress >> 30) & 0xffc0); + + clientPointerDesc |= pointerDesc & 0xffff000f; + + clientProcess.CpuMemory.Write(dstDescAddress + 0, clientPointerDesc); + + offset += 2; + } + + // Set send, receive and exchange buffer descriptors to zero. + uint totalBuffersCount = + serverHeader.SendBuffersCount + + serverHeader.ReceiveBuffersCount + + serverHeader.ExchangeBuffersCount; + + for (int index = 0; index < totalBuffersCount; index++) + { + ulong dstDescAddress = clientMsg.Address + offset * 4; + + clientProcess.CpuMemory.Write(dstDescAddress + 0, 0); + clientProcess.CpuMemory.Write(dstDescAddress + 4, 0); + clientProcess.CpuMemory.Write(dstDescAddress + 8, 0); + + offset += 3; + } + + // Copy raw data. + if (serverHeader.RawDataSizeInWords != 0) + { + ulong copyDst = clientMsg.Address + offset * 4; + ulong copySrc = serverMsg.Address + offset * 4; + + ulong copySize = serverHeader.RawDataSizeInWords * 4; + + if (serverMsg.IsCustom || clientMsg.IsCustom) + { + KMemoryPermission permission = clientMsg.IsCustom + ? KMemoryPermission.None + : KMemoryPermission.Read; + + clientResult = clientProcess.MemoryManager.CopyDataFromCurrentProcess( + copyDst, + copySize, + MemoryState.IsPoolAllocated, + MemoryState.IsPoolAllocated, + permission, + MemoryAttribute.Uncached, + MemoryAttribute.None, + copySrc); + } + else + { + clientProcess.CpuMemory.Write(copyDst, serverProcess.CpuMemory.GetReadOnlySequence(copySrc, (int)copySize)); + } + } + + // Unmap buffers from server. + FinishRequest(request, clientResult); + + return serverResult; + } + + private static MessageHeader GetClientMessageHeader(KProcess clientProcess, Message clientMsg) + { + uint word0 = clientProcess.CpuMemory.Read(clientMsg.Address + 0); + uint word1 = clientProcess.CpuMemory.Read(clientMsg.Address + 4); + uint word2 = clientProcess.CpuMemory.Read(clientMsg.Address + 8); + + return new MessageHeader(word0, word1, word2); + } + + private static MessageHeader GetServerMessageHeader(Message serverMsg) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + uint word0 = currentProcess.CpuMemory.Read(serverMsg.Address + 0); + uint word1 = currentProcess.CpuMemory.Read(serverMsg.Address + 4); + uint word2 = currentProcess.CpuMemory.Read(serverMsg.Address + 8); + + return new MessageHeader(word0, word1, word2); + } + + private static Result GetCopyObjectHandle(KThread srcThread, KProcess dstProcess, int srcHandle, out int dstHandle) + { + dstHandle = 0; + + KProcess srcProcess = srcThread.Owner; + + KAutoObject obj; + + if (srcHandle == KHandleTable.SelfProcessHandle) + { + obj = srcProcess; + } + else if (srcHandle == KHandleTable.SelfThreadHandle) + { + obj = srcThread; + } + else + { + obj = srcProcess.HandleTable.GetObject(srcHandle); + } + + if (obj != null) + { + return dstProcess.HandleTable.GenerateHandle(obj, out dstHandle); + } + else + { + return KernelResult.InvalidHandle; + } + } + + private static Result GetMoveObjectHandle(KProcess srcProcess, KProcess dstProcess, int srcHandle, out int dstHandle) + { + dstHandle = 0; + + KAutoObject obj = srcProcess.HandleTable.GetObject(srcHandle); + + if (obj != null) + { + Result result = dstProcess.HandleTable.GenerateHandle(obj, out dstHandle); + + srcProcess.HandleTable.CloseHandle(srcHandle); + + return result; + } + else + { + return KernelResult.InvalidHandle; + } + } + + private static ulong[] GetReceiveList(KProcess ownerProcess, Message message, uint recvListType, uint recvListOffset) + { + int recvListSize = 0; + + if (recvListType >= 3) + { + recvListSize = (int)recvListType - 2; + } + else if (recvListType == 2) + { + recvListSize = 1; + } + + ulong[] receiveList = new ulong[recvListSize]; + + ulong recvListAddress = message.Address + recvListOffset; + + for (int index = 0; index < recvListSize; index++) + { + receiveList[index] = ownerProcess.CpuMemory.Read(recvListAddress + (ulong)index * 8); + } + + return receiveList; + } + + private static Result GetReceiveListAddress( + PointerBufferDesc descriptor, + Message message, + uint recvListType, + uint messageSizeInWords, + ulong[] receiveList, + ref uint dstOffset, + out ulong address) + { + ulong recvListBufferAddress; + address = 0; + + if (recvListType == 0) + { + return KernelResult.OutOfResource; + } + else if (recvListType == 1 || recvListType == 2) + { + ulong recvListBaseAddr; + ulong recvListEndAddr; + + if (recvListType == 1) + { + recvListBaseAddr = message.Address + messageSizeInWords * 4; + recvListEndAddr = message.Address + message.Size; + } + else /* if (recvListType == 2) */ + { + ulong packed = receiveList[0]; + + recvListBaseAddr = packed & 0x7fffffffff; + + uint size = (uint)(packed >> 48); + + if (size == 0) + { + return KernelResult.OutOfResource; + } + + recvListEndAddr = recvListBaseAddr + size; + } + + recvListBufferAddress = BitUtils.AlignUp(recvListBaseAddr + dstOffset, 0x10); + + ulong endAddress = recvListBufferAddress + descriptor.BufferSize; + + dstOffset = (uint)endAddress - (uint)recvListBaseAddr; + + if (recvListBufferAddress + descriptor.BufferSize <= recvListBufferAddress || + recvListBufferAddress + descriptor.BufferSize > recvListEndAddr) + { + return KernelResult.OutOfResource; + } + } + else /* if (recvListType > 2) */ + { + if (descriptor.ReceiveIndex >= receiveList.Length) + { + return KernelResult.OutOfResource; + } + + ulong packed = receiveList[descriptor.ReceiveIndex]; + + recvListBufferAddress = packed & 0x7fffffffff; + + uint size = (uint)(packed >> 48); + + if (recvListBufferAddress == 0 || size == 0 || size < descriptor.BufferSize) + { + return KernelResult.OutOfResource; + } + } + + address = recvListBufferAddress; + + return Result.Success; + } + + private static void CloseAllHandles(Message message, MessageHeader header, KProcess process) + { + if (header.HasHandles) + { + uint totalHandeslCount = header.CopyHandlesCount + header.MoveHandlesCount; + + uint offset = 3; + + if (header.HasPid) + { + process.CpuMemory.Write(message.Address + offset * 4, 0L); + + offset += 2; + } + + for (int index = 0; index < totalHandeslCount; index++) + { + int handle = process.CpuMemory.Read(message.Address + offset * 4); + + if (handle != 0) + { + process.HandleTable.CloseHandle(handle); + + process.CpuMemory.Write(message.Address + offset * 4, 0); + } + + offset++; + } + } + } + + public override bool IsSignaled() + { + if (_parent.ClientSession.State != ChannelState.Open) + { + return true; + } + + return _requests.Count != 0 && _activeRequest == null; + } + + protected override void Destroy() + { + _parent.DisconnectServer(); + + CancelAllRequestsServerDisconnected(); + + _parent.DecrementReferenceCount(); + } + + private void CancelAllRequestsServerDisconnected() + { + foreach (KSessionRequest request in IterateWithRemovalOfAllRequests()) + { + FinishRequest(request, KernelResult.PortRemoteClosed); + } + } + + public void CancelAllRequestsClientDisconnected() + { + foreach (KSessionRequest request in IterateWithRemovalOfAllRequests()) + { + if (request.ClientThread.TerminationRequested) + { + continue; + } + + // Client sessions can only be disconnected on async requests (because + // the client would be otherwise blocked waiting for the response), so + // we only need to handle the async case here. + if (request.AsyncEvent != null) + { + SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed); + } + } + + WakeServerThreads(KernelResult.PortRemoteClosed); + } + + private IEnumerable IterateWithRemovalOfAllRequests() + { + KernelContext.CriticalSection.Enter(); + + if (_activeRequest != null) + { + KSessionRequest request = _activeRequest; + + _activeRequest = null; + + KernelContext.CriticalSection.Leave(); + + yield return request; + } + else + { + KernelContext.CriticalSection.Leave(); + } + + while (DequeueRequest(out KSessionRequest request)) + { + yield return request; + } + } + + private bool DequeueRequest(out KSessionRequest request) + { + request = null; + + KernelContext.CriticalSection.Enter(); + + bool hasRequest = _requests.First != null; + + if (hasRequest) + { + request = _requests.First.Value; + + _requests.RemoveFirst(); + } + + KernelContext.CriticalSection.Leave(); + + return hasRequest; + } + + private void FinishRequest(KSessionRequest request, Result result) + { + KProcess clientProcess = request.ClientThread.Owner; + KProcess serverProcess = request.ServerProcess; + + Result unmapResult = Result.Success; + + if (serverProcess != null) + { + unmapResult = request.BufferDescriptorTable.UnmapServerBuffers(serverProcess.MemoryManager); + } + + if (unmapResult == Result.Success) + { + request.BufferDescriptorTable.RestoreClientBuffers(clientProcess.MemoryManager); + } + + WakeClientThread(request, result); + } + + private void WakeClientThread(KSessionRequest request, Result result) + { + // Wait client thread waiting for a response for the given request. + if (request.AsyncEvent != null) + { + SendResultToAsyncRequestClient(request, result); + } + else + { + KernelContext.CriticalSection.Enter(); + + WakeAndSetResult(request.ClientThread, result); + + KernelContext.CriticalSection.Leave(); + } + } + + private static void SendResultToAsyncRequestClient(KSessionRequest request, Result result) + { + KProcess clientProcess = request.ClientThread.Owner; + + if (result != Result.Success) + { + ulong address = request.CustomCmdBuffAddr; + + clientProcess.CpuMemory.Write(address, 0); + clientProcess.CpuMemory.Write(address + 8, result.ErrorCode); + } + + clientProcess.MemoryManager.UnborrowIpcBuffer(request.CustomCmdBuffAddr, request.CustomCmdBuffSize); + + request.AsyncEvent.Signal(); + } + + private void WakeServerThreads(Result result) + { + // Wake all server threads waiting for requests. + KernelContext.CriticalSection.Enter(); + + foreach (KThread thread in WaitingThreads) + { + WakeAndSetResult(thread, result, this); + } + + KernelContext.CriticalSection.Leave(); + } + + private static void WakeAndSetResult(KThread thread, Result result, KSynchronizationObject signaledObj = null) + { + if ((thread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) + { + thread.SignaledObj = signaledObj; + thread.ObjSyncResult = result; + + thread.Reschedule(ThreadSchedState.Running); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs new file mode 100644 index 00000000..6659d414 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs @@ -0,0 +1,54 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KSession : KAutoObject + { + public KServerSession ServerSession { get; } + public KClientSession ClientSession { get; } + + private readonly bool _hasBeenInitialized; + + public KSession(KernelContext context, KClientPort parentPort = null) : base(context) + { + IncrementReferenceCount(); + + ServerSession = new KServerSession(context, this); + ClientSession = new KClientSession(context, this, parentPort); + + _hasBeenInitialized = true; + } + + public void DisconnectClient() + { + if (ClientSession.State == ChannelState.Open) + { + ClientSession.State = ChannelState.ClientDisconnected; + + ServerSession.CancelAllRequestsClientDisconnected(); + } + } + + public void DisconnectServer() + { + if (ClientSession.State == ChannelState.Open) + { + ClientSession.State = ChannelState.ServerDisconnected; + } + } + + protected override void Destroy() + { + if (_hasBeenInitialized) + { + ClientSession.DisconnectFromPort(); + + KProcess creatorProcess = ClientSession.CreatorProcess; + + creatorProcess.ResourceLimit?.Release(LimitableResource.Session, 1); + creatorProcess.DecrementReferenceCount(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs new file mode 100644 index 00000000..bc3eef71 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs @@ -0,0 +1,33 @@ +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Ipc +{ + class KSessionRequest + { + public KBufferDescriptorTable BufferDescriptorTable { get; } + + public KThread ClientThread { get; } + + public KProcess ServerProcess { get; set; } + + public KWritableEvent AsyncEvent { get; } + + public ulong CustomCmdBuffAddr { get; } + public ulong CustomCmdBuffSize { get; } + + public KSessionRequest( + KThread clientThread, + ulong customCmdBuffAddr, + ulong customCmdBuffSize, + KWritableEvent asyncEvent = null) + { + ClientThread = clientThread; + CustomCmdBuffAddr = customCmdBuffAddr; + CustomCmdBuffSize = customCmdBuffSize; + AsyncEvent = asyncEvent; + + BufferDescriptorTable = new KBufferDescriptorTable(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/KernelConstants.cs b/src/Ryujinx.HLE/HOS/Kernel/KernelConstants.cs new file mode 100644 index 00000000..2c01efec --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/KernelConstants.cs @@ -0,0 +1,20 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; + +namespace Ryujinx.HLE.HOS.Kernel +{ + static class KernelConstants + { + public const int InitialKipId = 1; + public const int InitialProcessId = 0x51; + + public const int SupervisorCallCount = 0xC0; + + public const int MemoryBlockAllocatorSize = 0x2710; + + public const ulong UserSlabHeapBase = DramMemoryMap.SlabHeapBase; + public const ulong UserSlabHeapItemSize = KPageTableBase.PageSize; + public const ulong UserSlabHeapSize = 0x3de000; + + public const ulong CounterFrequency = 19200000; + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/KernelContext.cs b/src/Ryujinx.HLE/HOS/Kernel/KernelContext.cs new file mode 100644 index 00000000..89d788c5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/KernelContext.cs @@ -0,0 +1,160 @@ +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.SupervisorCall; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel +{ + class KernelContext : IDisposable + { + public long PrivilegedProcessLowestId { get; set; } = 1; + public long PrivilegedProcessHighestId { get; set; } = 8; + + public bool EnableVersionChecks { get; set; } + + public bool KernelInitialized { get; } + + public bool Running { get; private set; } + + public Switch Device { get; } + public MemoryBlock Memory { get; } + public ITickSource TickSource { get; } + public Syscall Syscall { get; } + public SyscallHandler SyscallHandler { get; } + + public KResourceLimit ResourceLimit { get; } + + public KMemoryManager MemoryManager { get; } + + public KMemoryBlockSlabManager LargeMemoryBlockSlabManager { get; } + public KMemoryBlockSlabManager SmallMemoryBlockSlabManager { get; } + + public KSlabHeap UserSlabHeapPages { get; } + + public KCriticalSection CriticalSection { get; } + public KScheduler[] Schedulers { get; } + public KPriorityQueue PriorityQueue { get; } + public KTimeManager TimeManager { get; } + public KSynchronization Synchronization { get; } + public KContextIdManager ContextIdManager { get; } + + public ConcurrentDictionary Processes { get; } + public ConcurrentDictionary AutoObjectNames { get; } + + public bool ThreadReselectionRequested { get; set; } + + private ulong _kipId; + private ulong _processId; + private ulong _threadUid; + + public KernelContext( + ITickSource tickSource, + Switch device, + MemoryBlock memory, + MemorySize memorySize, + MemoryArrange memoryArrange) + { + TickSource = tickSource; + Device = device; + Memory = memory; + + Running = true; + + Syscall = new Syscall(this); + + SyscallHandler = new SyscallHandler(this); + + ResourceLimit = new KResourceLimit(this); + + KernelInit.InitializeResourceLimit(ResourceLimit, memorySize); + + MemoryManager = new KMemoryManager(memorySize, memoryArrange); + + LargeMemoryBlockSlabManager = new KMemoryBlockSlabManager(KernelConstants.MemoryBlockAllocatorSize * 2); + SmallMemoryBlockSlabManager = new KMemoryBlockSlabManager(KernelConstants.MemoryBlockAllocatorSize); + + UserSlabHeapPages = new KSlabHeap( + KernelConstants.UserSlabHeapBase, + KernelConstants.UserSlabHeapItemSize, + KernelConstants.UserSlabHeapSize); + + CommitMemory(KernelConstants.UserSlabHeapBase - DramMemoryMap.DramBase, KernelConstants.UserSlabHeapSize); + + CriticalSection = new KCriticalSection(this); + Schedulers = new KScheduler[KScheduler.CpuCoresCount]; + PriorityQueue = new KPriorityQueue(); + TimeManager = new KTimeManager(this); + Synchronization = new KSynchronization(this); + ContextIdManager = new KContextIdManager(); + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + Schedulers[core] = new KScheduler(this, core); + } + + StartPreemptionThread(); + + KernelInitialized = true; + + Processes = new ConcurrentDictionary(); + AutoObjectNames = new ConcurrentDictionary(); + + _kipId = KernelConstants.InitialKipId; + _processId = KernelConstants.InitialProcessId; + } + + private void StartPreemptionThread() + { + void PreemptionThreadStart() + { + KScheduler.PreemptionThreadLoop(this); + } + + new Thread(PreemptionThreadStart) { Name = "HLE.PreemptionThread" }.Start(); + } + + public void CommitMemory(ulong address, ulong size) + { + ulong alignment = MemoryBlock.GetPageSize(); + ulong endAddress = address + size; + + address &= ~(alignment - 1); + endAddress = (endAddress + (alignment - 1)) & ~(alignment - 1); + + Memory.Commit(address, endAddress - address); + } + + public ulong NewThreadUid() + { + return Interlocked.Increment(ref _threadUid) - 1; + } + + public ulong NewKipId() + { + return Interlocked.Increment(ref _kipId) - 1; + } + + public ulong NewProcessId() + { + return Interlocked.Increment(ref _processId) - 1; + } + + public void Dispose() + { + Running = false; + + for (int i = 0; i < KScheduler.CpuCoresCount; i++) + { + Schedulers[i].Dispose(); + } + + TimeManager.Dispose(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs b/src/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs new file mode 100644 index 00000000..f5ecba75 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs @@ -0,0 +1,73 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel +{ + static class KernelStatic + { + [ThreadStatic] + private static KernelContext _context; + + [ThreadStatic] + private static KThread _currentThread; + + public static Result StartInitialProcess( + KernelContext context, + ProcessCreationInfo creationInfo, + ReadOnlySpan capabilities, + int mainThreadPriority, + ThreadStart customThreadStart) + { + KProcess process = new(context); + + Result result = process.Initialize( + creationInfo, + capabilities, + context.ResourceLimit, + MemoryRegion.Service, + null, + customThreadStart); + + if (result != Result.Success) + { + return result; + } + + process.DefaultCpuCore = 3; + + context.Processes.TryAdd(process.Pid, process); + + return process.Start(mainThreadPriority, 0x1000UL); + } + + internal static void SetKernelContext(KernelContext context, KThread thread) + { + _context = context; + _currentThread = thread; + } + + internal static KThread GetCurrentThread() + { + return _currentThread; + } + + internal static KProcess GetCurrentProcess() + { + return GetCurrentThread().Owner; + } + + internal static KProcess GetProcessByPid(ulong pid) + { + if (_context.Processes.TryGetValue(pid, out KProcess process)) + { + return process; + } + + return null; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs new file mode 100644 index 00000000..e7af2963 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + static class DramMemoryMap + { + public const ulong DramBase = 0x80000000; + + public const ulong KernelReserveBase = DramBase + 0x60000; + + public const ulong SlabHeapBase = KernelReserveBase + 0x85000; + public const ulong SlapHeapSize = 0xa21000; + public const ulong SlabHeapEnd = SlabHeapBase + SlapHeapSize; + + public static bool IsHeapPhysicalAddress(ulong address) + { + return address >= SlabHeapEnd; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs new file mode 100644 index 00000000..c725501b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs @@ -0,0 +1,169 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.Horizon.Common; +using System; +using System.Diagnostics; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KCodeMemory : KAutoObject + { + public KProcess Owner { get; private set; } + private readonly KPageList _pageList; + private readonly object _lock; + private ulong _address; + private bool _isOwnerMapped; + private bool _isMapped; + + public KCodeMemory(KernelContext context) : base(context) + { + _pageList = new KPageList(); + _lock = new object(); + } + + public Result Initialize(ulong address, ulong size) + { + Owner = KernelStatic.GetCurrentProcess(); + + Result result = Owner.MemoryManager.BorrowCodeMemory(_pageList, address, size); + + if (result != Result.Success) + { + return result; + } + + Owner.CpuMemory.Fill(address, size, 0xff); + Owner.IncrementReferenceCount(); + + _address = address; + _isMapped = false; + _isOwnerMapped = false; + + return Result.Success; + } + + public Result Map(ulong address, ulong size, KMemoryPermission perm) + { + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, (ulong)KPageTableBase.PageSize)) + { + return KernelResult.InvalidSize; + } + + lock (_lock) + { + if (_isMapped) + { + return KernelResult.InvalidState; + } + + KProcess process = KernelStatic.GetCurrentProcess(); + + Result result = process.MemoryManager.MapPages(address, _pageList, MemoryState.CodeWritable, KMemoryPermission.ReadAndWrite); + + if (result != Result.Success) + { + return result; + } + + _isMapped = true; + } + + return Result.Success; + } + + public Result MapToOwner(ulong address, ulong size, KMemoryPermission permission) + { + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, (ulong)KPageTableBase.PageSize)) + { + return KernelResult.InvalidSize; + } + + lock (_lock) + { + if (_isOwnerMapped) + { + return KernelResult.InvalidState; + } + + Debug.Assert(permission == KMemoryPermission.Read || permission == KMemoryPermission.ReadAndExecute); + + Result result = Owner.MemoryManager.MapPages(address, _pageList, MemoryState.CodeReadOnly, permission); + + if (result != Result.Success) + { + return result; + } + + _isOwnerMapped = true; + } + + return Result.Success; + } + + public Result Unmap(ulong address, ulong size) + { + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, (ulong)KPageTableBase.PageSize)) + { + return KernelResult.InvalidSize; + } + + lock (_lock) + { + KProcess process = KernelStatic.GetCurrentProcess(); + + Result result = process.MemoryManager.UnmapPages(address, _pageList, MemoryState.CodeWritable); + + if (result != Result.Success) + { + return result; + } + + Debug.Assert(_isMapped); + + _isMapped = false; + } + + return Result.Success; + } + + public Result UnmapFromOwner(ulong address, ulong size) + { + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize)) + { + return KernelResult.InvalidSize; + } + + lock (_lock) + { + Result result = Owner.MemoryManager.UnmapPages(address, _pageList, MemoryState.CodeReadOnly); + + if (result != Result.Success) + { + return result; + } + + Debug.Assert(_isOwnerMapped); + + _isOwnerMapped = false; + } + + return Result.Success; + } + + protected override void Destroy() + { + if (!_isMapped && !_isOwnerMapped) + { + ulong size = _pageList.GetPagesCount() * KPageTableBase.PageSize; + + if (Owner.MemoryManager.UnborrowCodeMemory(_address, size, _pageList) != Result.Success) + { + throw new InvalidOperationException("Unexpected failure restoring transfer memory attributes."); + } + } + + Owner.DecrementReferenceCount(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs new file mode 100644 index 00000000..d2c4aadf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs @@ -0,0 +1,156 @@ +using Ryujinx.Common.Collections; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryBlock : IntrusiveRedBlackTreeNode, IComparable, IComparable + { + public ulong BaseAddress { get; private set; } + public ulong PagesCount { get; private set; } + + public MemoryState State { get; private set; } + public KMemoryPermission Permission { get; private set; } + public MemoryAttribute Attribute { get; private set; } + public KMemoryPermission SourcePermission { get; private set; } + + public int IpcRefCount { get; private set; } + public int DeviceRefCount { get; private set; } + + public KMemoryBlock( + ulong baseAddress, + ulong pagesCount, + MemoryState state, + KMemoryPermission permission, + MemoryAttribute attribute, + int ipcRefCount = 0, + int deviceRefCount = 0) + { + BaseAddress = baseAddress; + PagesCount = pagesCount; + State = state; + Attribute = attribute; + Permission = permission; + IpcRefCount = ipcRefCount; + DeviceRefCount = deviceRefCount; + } + + public void SetState(KMemoryPermission permission, MemoryState state, MemoryAttribute attribute) + { + Permission = permission; + State = state; + Attribute &= MemoryAttribute.IpcAndDeviceMapped; + Attribute |= attribute; + } + + public void SetIpcMappingPermission(KMemoryPermission newPermission) + { + int oldIpcRefCount = IpcRefCount++; + + if ((ushort)IpcRefCount == 0) + { + throw new InvalidOperationException("IPC reference count increment overflowed."); + } + + if (oldIpcRefCount == 0) + { + SourcePermission = Permission; + + Permission &= ~KMemoryPermission.ReadAndWrite; + Permission |= KMemoryPermission.ReadAndWrite & newPermission; + } + + Attribute |= MemoryAttribute.IpcMapped; + } + + public void RestoreIpcMappingPermission() + { + int oldIpcRefCount = IpcRefCount--; + + if (oldIpcRefCount == 0) + { + throw new InvalidOperationException("IPC reference count decrement underflowed."); + } + + if (oldIpcRefCount == 1) + { + Permission = SourcePermission; + + SourcePermission = KMemoryPermission.None; + + Attribute &= ~MemoryAttribute.IpcMapped; + } + } + + public KMemoryBlock SplitRightAtAddress(ulong address) + { + ulong leftAddress = BaseAddress; + + ulong leftPagesCount = (address - leftAddress) / KPageTableBase.PageSize; + + BaseAddress = address; + + PagesCount -= leftPagesCount; + + return new KMemoryBlock( + leftAddress, + leftPagesCount, + State, + Permission, + Attribute, + IpcRefCount, + DeviceRefCount); + } + + public void AddPages(ulong pagesCount) + { + PagesCount += pagesCount; + } + + public KMemoryInfo GetInfo() + { + ulong size = PagesCount * KPageTableBase.PageSize; + + return new KMemoryInfo( + BaseAddress, + size, + State, + Permission, + Attribute, + SourcePermission, + IpcRefCount, + DeviceRefCount); + } + + public int CompareTo(KMemoryBlock other) + { + if (BaseAddress < other.BaseAddress) + { + return -1; + } + else if (BaseAddress <= other.BaseAddress + other.PagesCount * KPageTableBase.PageSize - 1UL) + { + return 0; + } + else + { + return 1; + } + } + + public int CompareTo(ulong address) + { + if (address < BaseAddress) + { + return 1; + } + else if (address <= BaseAddress + PagesCount * KPageTableBase.PageSize - 1UL) + { + return 0; + } + else + { + return -1; + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockManager.cs new file mode 100644 index 00000000..f13a3554 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockManager.cs @@ -0,0 +1,288 @@ +using Ryujinx.Common.Collections; +using Ryujinx.Horizon.Common; +using System.Diagnostics; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryBlockManager + { + private const int PageSize = KPageTableBase.PageSize; + + private readonly IntrusiveRedBlackTree _blockTree; + + public int BlocksCount => _blockTree.Count; + + private KMemoryBlockSlabManager _slabManager; + + private ulong _addrSpaceStart; + private ulong _addrSpaceEnd; + + public KMemoryBlockManager() + { + _blockTree = new IntrusiveRedBlackTree(); + } + + public Result Initialize(ulong addrSpaceStart, ulong addrSpaceEnd, KMemoryBlockSlabManager slabManager) + { + _slabManager = slabManager; + _addrSpaceStart = addrSpaceStart; + _addrSpaceEnd = addrSpaceEnd; + + // First insertion will always need only a single block, because there's nothing to split. + if (!slabManager.CanAllocate(1)) + { + return KernelResult.OutOfResource; + } + + ulong addrSpacePagesCount = (addrSpaceEnd - addrSpaceStart) / PageSize; + + _blockTree.Add(new KMemoryBlock( + addrSpaceStart, + addrSpacePagesCount, + MemoryState.Unmapped, + KMemoryPermission.None, + MemoryAttribute.None)); + + return Result.Success; + } + + public void InsertBlock( + ulong baseAddress, + ulong pagesCount, + MemoryState oldState, + KMemoryPermission oldPermission, + MemoryAttribute oldAttribute, + MemoryState newState, + KMemoryPermission newPermission, + MemoryAttribute newAttribute) + { + // Insert new block on the list only on areas where the state + // of the block matches the state specified on the old* state + // arguments, otherwise leave it as is. + + int oldCount = _blockTree.Count; + + oldAttribute |= MemoryAttribute.IpcAndDeviceMapped; + + ulong endAddr = baseAddress + pagesCount * PageSize; + + KMemoryBlock currBlock = FindBlock(baseAddress); + + while (currBlock != null) + { + ulong currBaseAddr = currBlock.BaseAddress; + ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr; + + if (baseAddress < currEndAddr && currBaseAddr < endAddr) + { + MemoryAttribute currBlockAttr = currBlock.Attribute | MemoryAttribute.IpcAndDeviceMapped; + + if (currBlock.State != oldState || + currBlock.Permission != oldPermission || + currBlockAttr != oldAttribute) + { + currBlock = currBlock.Successor; + + continue; + } + + if (baseAddress > currBaseAddr) + { + KMemoryBlock newBlock = currBlock.SplitRightAtAddress(baseAddress); + _blockTree.Add(newBlock); + } + + if (endAddr < currEndAddr) + { + KMemoryBlock newBlock = currBlock.SplitRightAtAddress(endAddr); + _blockTree.Add(newBlock); + currBlock = newBlock; + } + + currBlock.SetState(newPermission, newState, newAttribute); + + currBlock = MergeEqualStateNeighbors(currBlock); + } + + if (currEndAddr - 1 >= endAddr - 1) + { + break; + } + + currBlock = currBlock.Successor; + } + + _slabManager.Count += _blockTree.Count - oldCount; + + ValidateInternalState(); + } + + public void InsertBlock( + ulong baseAddress, + ulong pagesCount, + MemoryState state, + KMemoryPermission permission = KMemoryPermission.None, + MemoryAttribute attribute = MemoryAttribute.None) + { + // Inserts new block at the list, replacing and splitting + // existing blocks as needed. + + int oldCount = _blockTree.Count; + + ulong endAddr = baseAddress + pagesCount * PageSize; + + KMemoryBlock currBlock = FindBlock(baseAddress); + + while (currBlock != null) + { + ulong currBaseAddr = currBlock.BaseAddress; + ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr; + + if (baseAddress < currEndAddr && currBaseAddr < endAddr) + { + if (baseAddress > currBaseAddr) + { + KMemoryBlock newBlock = currBlock.SplitRightAtAddress(baseAddress); + _blockTree.Add(newBlock); + } + + if (endAddr < currEndAddr) + { + KMemoryBlock newBlock = currBlock.SplitRightAtAddress(endAddr); + _blockTree.Add(newBlock); + currBlock = newBlock; + } + + currBlock.SetState(permission, state, attribute); + + currBlock = MergeEqualStateNeighbors(currBlock); + } + + if (currEndAddr - 1 >= endAddr - 1) + { + break; + } + + currBlock = currBlock.Successor; + } + + _slabManager.Count += _blockTree.Count - oldCount; + + ValidateInternalState(); + } + + public delegate void BlockMutator(KMemoryBlock block, KMemoryPermission newPerm); + + public void InsertBlock( + ulong baseAddress, + ulong pagesCount, + BlockMutator blockMutate, + KMemoryPermission permission = KMemoryPermission.None) + { + // Inserts new block at the list, replacing and splitting + // existing blocks as needed, then calling the callback + // function on the new block. + + int oldCount = _blockTree.Count; + + ulong endAddr = baseAddress + pagesCount * PageSize; + + KMemoryBlock currBlock = FindBlock(baseAddress); + + while (currBlock != null) + { + ulong currBaseAddr = currBlock.BaseAddress; + ulong currEndAddr = currBlock.PagesCount * PageSize + currBaseAddr; + + if (baseAddress < currEndAddr && currBaseAddr < endAddr) + { + if (baseAddress > currBaseAddr) + { + KMemoryBlock newBlock = currBlock.SplitRightAtAddress(baseAddress); + _blockTree.Add(newBlock); + } + + if (endAddr < currEndAddr) + { + KMemoryBlock newBlock = currBlock.SplitRightAtAddress(endAddr); + _blockTree.Add(newBlock); + currBlock = newBlock; + } + + blockMutate(currBlock, permission); + + currBlock = MergeEqualStateNeighbors(currBlock); + } + + if (currEndAddr - 1 >= endAddr - 1) + { + break; + } + + currBlock = currBlock.Successor; + } + + _slabManager.Count += _blockTree.Count - oldCount; + + ValidateInternalState(); + } + + [Conditional("DEBUG")] + private void ValidateInternalState() + { + ulong expectedAddress = 0; + + KMemoryBlock currBlock = FindBlock(_addrSpaceStart); + + while (currBlock != null) + { + Debug.Assert(currBlock.BaseAddress == expectedAddress); + + expectedAddress = currBlock.BaseAddress + currBlock.PagesCount * PageSize; + + currBlock = currBlock.Successor; + } + + Debug.Assert(expectedAddress == _addrSpaceEnd); + } + + private KMemoryBlock MergeEqualStateNeighbors(KMemoryBlock block) + { + KMemoryBlock previousBlock = block.Predecessor; + KMemoryBlock nextBlock = block.Successor; + + if (previousBlock != null && BlockStateEquals(block, previousBlock)) + { + _blockTree.Remove(block); + + previousBlock.AddPages(block.PagesCount); + + block = previousBlock; + } + + if (nextBlock != null && BlockStateEquals(block, nextBlock)) + { + _blockTree.Remove(nextBlock); + + block.AddPages(nextBlock.PagesCount); + } + + return block; + } + + private static bool BlockStateEquals(KMemoryBlock lhs, KMemoryBlock rhs) + { + return lhs.State == rhs.State && + lhs.Permission == rhs.Permission && + lhs.Attribute == rhs.Attribute && + lhs.SourcePermission == rhs.SourcePermission && + lhs.DeviceRefCount == rhs.DeviceRefCount && + lhs.IpcRefCount == rhs.IpcRefCount; + } + + public KMemoryBlock FindBlock(ulong address) + { + return _blockTree.GetNodeByKey(address); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockSlabManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockSlabManager.cs new file mode 100644 index 00000000..b4f56699 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockSlabManager.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryBlockSlabManager + { + private readonly ulong _capacityElements; + + public int Count { get; set; } + + public KMemoryBlockSlabManager(ulong capacityElements) + { + _capacityElements = capacityElements; + } + + public bool CanAllocate(int count) + { + return (ulong)(Count + count) <= _capacityElements; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs new file mode 100644 index 00000000..4db484d0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs @@ -0,0 +1,36 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryInfo + { + public ulong Address { get; } + public ulong Size { get; } + + public MemoryState State { get; } + public KMemoryPermission Permission { get; } + public MemoryAttribute Attribute { get; } + public KMemoryPermission SourcePermission { get; } + + public int IpcRefCount { get; } + public int DeviceRefCount { get; } + + public KMemoryInfo( + ulong address, + ulong size, + MemoryState state, + KMemoryPermission permission, + MemoryAttribute attribute, + KMemoryPermission sourcePermission, + int ipcRefCount, + int deviceRefCount) + { + Address = address; + Size = size; + State = state; + Permission = permission; + Attribute = attribute; + SourcePermission = sourcePermission; + IpcRefCount = ipcRefCount; + DeviceRefCount = deviceRefCount; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs new file mode 100644 index 00000000..e56304d9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs @@ -0,0 +1,65 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryManager + { + public KMemoryRegionManager[] MemoryRegions { get; } + + public KMemoryManager(MemorySize size, MemoryArrange arrange) + { + MemoryRegions = KernelInit.GetMemoryRegions(size, arrange); + } + + private KMemoryRegionManager GetMemoryRegion(ulong address) + { + for (int i = 0; i < MemoryRegions.Length; i++) + { + var region = MemoryRegions[i]; + + if (address >= region.Address && address < region.EndAddr) + { + return region; + } + } + + return null; + } + + public void IncrementPagesReferenceCount(ulong address, ulong pagesCount) + { + IncrementOrDecrementPagesReferenceCount(address, pagesCount, true); + } + + public void DecrementPagesReferenceCount(ulong address, ulong pagesCount) + { + IncrementOrDecrementPagesReferenceCount(address, pagesCount, false); + } + + private void IncrementOrDecrementPagesReferenceCount(ulong address, ulong pagesCount, bool increment) + { + while (pagesCount != 0) + { + var region = GetMemoryRegion(address); + + ulong countToProcess = Math.Min(pagesCount, region.GetPageOffsetFromEnd(address)); + + lock (region) + { + if (increment) + { + region.IncrementPagesReferenceCount(address, countToProcess); + } + else + { + region.DecrementPagesReferenceCount(address, countToProcess); + } + } + + pagesCount -= countToProcess; + address += countToProcess * KPageTableBase.PageSize; + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs new file mode 100644 index 00000000..32734574 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs @@ -0,0 +1,46 @@ +using Ryujinx.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + [Flags] + enum KMemoryPermission : uint + { + None = 0, + UserMask = Read | Write | Execute, + Mask = uint.MaxValue, + + Read = 1 << 0, + Write = 1 << 1, + Execute = 1 << 2, + DontCare = 1 << 28, + + ReadAndWrite = Read | Write, + ReadAndExecute = Read | Execute, + } + + static class KMemoryPermissionExtensions + { + public static MemoryPermission Convert(this KMemoryPermission permission) + { + MemoryPermission output = MemoryPermission.None; + + if (permission.HasFlag(KMemoryPermission.Read)) + { + output = MemoryPermission.Read; + } + + if (permission.HasFlag(KMemoryPermission.Write)) + { + output |= MemoryPermission.Write; + } + + if (permission.HasFlag(KMemoryPermission.Execute)) + { + output |= MemoryPermission.Execute; + } + + return output; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs new file mode 100644 index 00000000..2eff616c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs @@ -0,0 +1,242 @@ +using Ryujinx.Horizon.Common; +using System.Diagnostics; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KMemoryRegionManager + { + private readonly KPageHeap _pageHeap; + + public ulong Address { get; } + public ulong Size { get; } + public ulong EndAddr => Address + Size; + + private readonly ushort[] _pageReferenceCounts; + + public KMemoryRegionManager(ulong address, ulong size, ulong endAddr) + { + Address = address; + Size = size; + + _pageReferenceCounts = new ushort[size / KPageTableBase.PageSize]; + + _pageHeap = new KPageHeap(address, size); + _pageHeap.Free(address, size / KPageTableBase.PageSize); + _pageHeap.UpdateUsedSize(); + } + + public Result AllocatePages(out KPageList pageList, ulong pagesCount) + { + if (pagesCount == 0) + { + pageList = new KPageList(); + + return Result.Success; + } + + lock (_pageHeap) + { + Result result = AllocatePagesImpl(out pageList, pagesCount, false); + + if (result == Result.Success) + { + foreach (var node in pageList) + { + IncrementPagesReferenceCount(node.Address, node.PagesCount); + } + } + + return result; + } + } + + public ulong AllocatePagesContiguous(KernelContext context, ulong pagesCount, bool backwards) + { + if (pagesCount == 0) + { + return 0; + } + + lock (_pageHeap) + { + ulong address = AllocatePagesContiguousImpl(pagesCount, 1, backwards); + + if (address != 0) + { + IncrementPagesReferenceCount(address, pagesCount); + context.CommitMemory(address - DramMemoryMap.DramBase, pagesCount * KPageTableBase.PageSize); + } + + return address; + } + } + + private Result AllocatePagesImpl(out KPageList pageList, ulong pagesCount, bool random) + { + pageList = new KPageList(); + + int heapIndex = KPageHeap.GetBlockIndex(pagesCount); + + if (heapIndex < 0) + { + return KernelResult.OutOfMemory; + } + + for (int index = heapIndex; index >= 0; index--) + { + ulong pagesPerAlloc = KPageHeap.GetBlockPagesCount(index); + + while (pagesCount >= pagesPerAlloc) + { + ulong allocatedBlock = _pageHeap.AllocateBlock(index, random); + + if (allocatedBlock == 0) + { + break; + } + + Result result = pageList.AddRange(allocatedBlock, pagesPerAlloc); + + if (result != Result.Success) + { + FreePages(pageList); + _pageHeap.Free(allocatedBlock, pagesPerAlloc); + + return result; + } + + pagesCount -= pagesPerAlloc; + } + } + + if (pagesCount != 0) + { + FreePages(pageList); + + return KernelResult.OutOfMemory; + } + + return Result.Success; + } + + private ulong AllocatePagesContiguousImpl(ulong pagesCount, ulong alignPages, bool random) + { + int heapIndex = KPageHeap.GetAlignedBlockIndex(pagesCount, alignPages); + + ulong allocatedBlock = _pageHeap.AllocateBlock(heapIndex, random); + + if (allocatedBlock == 0) + { + return 0; + } + + ulong allocatedPages = KPageHeap.GetBlockPagesCount(heapIndex); + + if (allocatedPages > pagesCount) + { + _pageHeap.Free(allocatedBlock + pagesCount * KPageTableBase.PageSize, allocatedPages - pagesCount); + } + + return allocatedBlock; + } + + public void FreePage(ulong address) + { + lock (_pageHeap) + { + _pageHeap.Free(address, 1); + } + } + + public void FreePages(KPageList pageList) + { + lock (_pageHeap) + { + foreach (KPageNode pageNode in pageList) + { + _pageHeap.Free(pageNode.Address, pageNode.PagesCount); + } + } + } + + public void FreePages(ulong address, ulong pagesCount) + { + lock (_pageHeap) + { + _pageHeap.Free(address, pagesCount); + } + } + + public ulong GetFreePages() + { + lock (_pageHeap) + { + return _pageHeap.GetFreePagesCount(); + } + } + + public void IncrementPagesReferenceCount(ulong address, ulong pagesCount) + { + ulong index = GetPageOffset(address); + ulong endIndex = index + pagesCount; + + while (index < endIndex) + { + ushort referenceCount = ++_pageReferenceCounts[index]; + Debug.Assert(referenceCount >= 1); + + index++; + } + } + + public void DecrementPagesReferenceCount(ulong address, ulong pagesCount) + { + ulong index = GetPageOffset(address); + ulong endIndex = index + pagesCount; + + ulong freeBaseIndex = 0; + ulong freePagesCount = 0; + + while (index < endIndex) + { + Debug.Assert(_pageReferenceCounts[index] > 0); + ushort referenceCount = --_pageReferenceCounts[index]; + + if (referenceCount == 0) + { + if (freePagesCount != 0) + { + freePagesCount++; + } + else + { + freeBaseIndex = index; + freePagesCount = 1; + } + } + else if (freePagesCount != 0) + { + FreePages(Address + freeBaseIndex * KPageTableBase.PageSize, freePagesCount); + freePagesCount = 0; + } + + index++; + } + + if (freePagesCount != 0) + { + FreePages(Address + freeBaseIndex * KPageTableBase.PageSize, freePagesCount); + } + } + + public ulong GetPageOffset(ulong address) + { + return (address - Address) / KPageTableBase.PageSize; + } + + public ulong GetPageOffsetFromEnd(ulong address) + { + return (EndAddr - address) / KPageTableBase.PageSize; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageBitmap.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageBitmap.cs new file mode 100644 index 00000000..947983d6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageBitmap.cs @@ -0,0 +1,298 @@ +using Ryujinx.Common; +using System; +using System.Numerics; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KPageBitmap + { + private struct RandomNumberGenerator + { + private uint _entropy; + private uint _bitsAvailable; + + private void RefreshEntropy() + { + _entropy = 0; + _bitsAvailable = sizeof(uint) * 8; + } + + private bool GenerateRandomBit() + { + if (_bitsAvailable == 0) + { + RefreshEntropy(); + } + + bool bit = (_entropy & 1) != 0; + + _entropy >>= 1; + _bitsAvailable--; + + return bit; + } + + public int SelectRandomBit(ulong bitmap) + { + int selected = 0; + + int bitsCount = UInt64BitSize / 2; + ulong mask = (1UL << bitsCount) - 1; + + while (bitsCount != 0) + { + ulong low = bitmap & mask; + ulong high = (bitmap >> bitsCount) & mask; + + bool chooseLow; + + if (high == 0) + { + chooseLow = true; + } + else if (low == 0) + { + chooseLow = false; + } + else + { + chooseLow = GenerateRandomBit(); + } + + if (chooseLow) + { + bitmap = low; + } + else + { + bitmap = high; + selected += bitsCount; + } + + bitsCount /= 2; + mask >>= bitsCount; + } + + return selected; + } + } + + private const int UInt64BitSize = sizeof(ulong) * 8; + private const int MaxDepth = 4; + + private readonly RandomNumberGenerator _rng; + private readonly ArraySegment[] _bitStorages; + private int _usedDepths; + + public int BitsCount { get; private set; } + + public int HighestDepthIndex => _usedDepths - 1; + + public KPageBitmap() + { + _rng = new RandomNumberGenerator(); + _bitStorages = new ArraySegment[MaxDepth]; + } + + public ArraySegment Initialize(ArraySegment storage, ulong size) + { + _usedDepths = GetRequiredDepth(size); + + for (int depth = HighestDepthIndex; depth >= 0; depth--) + { + _bitStorages[depth] = storage; + size = BitUtils.DivRoundUp(size, (ulong)UInt64BitSize); + storage = storage.Slice((int)size); + } + + return storage; + } + + public ulong FindFreeBlock(bool random) + { + ulong offset = 0; + int depth = 0; + + if (random) + { + do + { + ulong v = _bitStorages[depth][(int)offset]; + + if (v == 0) + { + return ulong.MaxValue; + } + + offset = offset * UInt64BitSize + (ulong)_rng.SelectRandomBit(v); + } + while (++depth < _usedDepths); + } + else + { + do + { + ulong v = _bitStorages[depth][(int)offset]; + + if (v == 0) + { + return ulong.MaxValue; + } + + offset = offset * UInt64BitSize + (ulong)BitOperations.TrailingZeroCount(v); + } + while (++depth < _usedDepths); + } + + return offset; + } + + public void SetBit(ulong offset) + { + SetBit(HighestDepthIndex, offset); + BitsCount++; + } + + public void ClearBit(ulong offset) + { + ClearBit(HighestDepthIndex, offset); + BitsCount--; + } + + public bool ClearRange(ulong offset, int count) + { + int depth = HighestDepthIndex; + var bits = _bitStorages[depth]; + + int bitInd = (int)(offset / UInt64BitSize); + + if (count < UInt64BitSize) + { + int shift = (int)(offset % UInt64BitSize); + + ulong mask = ((1UL << count) - 1) << shift; + + ulong v = bits[bitInd]; + + if ((v & mask) != mask) + { + return false; + } + + v &= ~mask; + bits[bitInd] = v; + + if (v == 0) + { + ClearBit(depth - 1, (ulong)bitInd); + } + } + else + { + int remaining = count; + int i = 0; + + do + { + if (bits[bitInd + i++] != ulong.MaxValue) + { + return false; + } + + remaining -= UInt64BitSize; + } + while (remaining > 0); + + remaining = count; + i = 0; + + do + { + bits[bitInd + i] = 0; + ClearBit(depth - 1, (ulong)(bitInd + i)); + i++; + remaining -= UInt64BitSize; + } + while (remaining > 0); + } + + BitsCount -= count; + return true; + } + + private void SetBit(int depth, ulong offset) + { + while (depth >= 0) + { + int ind = (int)(offset / UInt64BitSize); + int which = (int)(offset % UInt64BitSize); + + ulong mask = 1UL << which; + + ulong v = _bitStorages[depth][ind]; + + _bitStorages[depth][ind] = v | mask; + + if (v != 0) + { + break; + } + + offset = (ulong)ind; + depth--; + } + } + + private void ClearBit(int depth, ulong offset) + { + while (depth >= 0) + { + int ind = (int)(offset / UInt64BitSize); + int which = (int)(offset % UInt64BitSize); + + ulong mask = 1UL << which; + + ulong v = _bitStorages[depth][ind]; + + v &= ~mask; + + _bitStorages[depth][ind] = v; + + if (v != 0) + { + break; + } + + offset = (ulong)ind; + depth--; + } + } + + private static int GetRequiredDepth(ulong regionSize) + { + int depth = 0; + + do + { + regionSize /= UInt64BitSize; + depth++; + } + while (regionSize != 0); + + return depth; + } + + public static int CalculateManagementOverheadSize(ulong regionSize) + { + int overheadBits = 0; + + for (int depth = GetRequiredDepth(regionSize) - 1; depth >= 0; depth--) + { + regionSize = BitUtils.DivRoundUp(regionSize, UInt64BitSize); + overheadBits += (int)regionSize; + } + + return overheadBits * sizeof(ulong); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageHeap.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageHeap.cs new file mode 100644 index 00000000..ee5d6e2b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageHeap.cs @@ -0,0 +1,285 @@ +using Ryujinx.Common; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KPageHeap + { + private class Block + { + private readonly KPageBitmap _bitmap = new(); + private ulong _heapAddress; + private ulong _endOffset; + + public int Shift { get; private set; } + public int NextShift { get; private set; } + public ulong Size => 1UL << Shift; + public int PagesCount => (int)(Size / KPageTableBase.PageSize); + public int FreeBlocksCount => _bitmap.BitsCount; + public int FreePagesCount => FreeBlocksCount * PagesCount; + + public ArraySegment Initialize(ulong address, ulong size, int blockShift, int nextBlockShift, ArraySegment bitStorage) + { + Shift = blockShift; + NextShift = nextBlockShift; + + ulong endAddress = address + size; + + ulong align = nextBlockShift != 0 + ? 1UL << nextBlockShift + : 1UL << blockShift; + + address = BitUtils.AlignDown(address, align); + endAddress = BitUtils.AlignUp(endAddress, align); + + _heapAddress = address; + _endOffset = (endAddress - address) / (1UL << blockShift); + + return _bitmap.Initialize(bitStorage, _endOffset); + } + + public ulong PushBlock(ulong address) + { + ulong offset = (address - _heapAddress) >> Shift; + + _bitmap.SetBit(offset); + + if (NextShift != 0) + { + int diff = 1 << (NextShift - Shift); + + offset = BitUtils.AlignDown(offset, (ulong)diff); + + if (_bitmap.ClearRange(offset, diff)) + { + return _heapAddress + (offset << Shift); + } + } + + return 0; + } + + public ulong PopBlock(bool random) + { + long sOffset = (long)_bitmap.FindFreeBlock(random); + + if (sOffset < 0L) + { + return 0; + } + + ulong offset = (ulong)sOffset; + + _bitmap.ClearBit(offset); + + return _heapAddress + (offset << Shift); + } + + public static int CalculateManagementOverheadSize(ulong regionSize, int currBlockShift, int nextBlockShift) + { + ulong currBlockSize = 1UL << currBlockShift; + ulong nextBlockSize = 1UL << nextBlockShift; + ulong align = nextBlockShift != 0 ? nextBlockSize : currBlockSize; + return KPageBitmap.CalculateManagementOverheadSize((align * 2 + BitUtils.AlignUp(regionSize, align)) / currBlockSize); + } + } + + private static readonly int[] _memoryBlockPageShifts = { 12, 16, 21, 22, 25, 29, 30 }; + +#pragma warning disable IDE0052 // Remove unread private member + private readonly ulong _heapAddress; + private readonly ulong _heapSize; + private ulong _usedSize; +#pragma warning restore IDE0052 + private readonly int _blocksCount; + private readonly Block[] _blocks; + + public KPageHeap(ulong address, ulong size) : this(address, size, _memoryBlockPageShifts) + { + } + + public KPageHeap(ulong address, ulong size, int[] blockShifts) + { + _heapAddress = address; + _heapSize = size; + _blocksCount = blockShifts.Length; + _blocks = new Block[_memoryBlockPageShifts.Length]; + + var currBitmapStorage = new ArraySegment(new ulong[CalculateManagementOverheadSize(size, blockShifts)]); + + for (int i = 0; i < blockShifts.Length; i++) + { + int currBlockShift = blockShifts[i]; + int nextBlockShift = i != blockShifts.Length - 1 ? blockShifts[i + 1] : 0; + + _blocks[i] = new Block(); + + currBitmapStorage = _blocks[i].Initialize(address, size, currBlockShift, nextBlockShift, currBitmapStorage); + } + } + + public void UpdateUsedSize() + { + _usedSize = _heapSize - (GetFreePagesCount() * KPageTableBase.PageSize); + } + + public ulong GetFreePagesCount() + { + ulong freeCount = 0; + + for (int i = 0; i < _blocksCount; i++) + { + freeCount += (ulong)_blocks[i].FreePagesCount; + } + + return freeCount; + } + + public ulong AllocateBlock(int index, bool random) + { + ulong neededSize = _blocks[index].Size; + + for (int i = index; i < _blocksCount; i++) + { + ulong address = _blocks[i].PopBlock(random); + + if (address != 0) + { + ulong allocatedSize = _blocks[i].Size; + + if (allocatedSize > neededSize) + { + Free(address + neededSize, (allocatedSize - neededSize) / KPageTableBase.PageSize); + } + + return address; + } + } + + return 0; + } + + private void FreeBlock(ulong block, int index) + { + do + { + block = _blocks[index++].PushBlock(block); + } + while (block != 0); + } + + public void Free(ulong address, ulong pagesCount) + { + if (pagesCount == 0) + { + return; + } + + int bigIndex = _blocksCount - 1; + + ulong start = address; + ulong end = address + pagesCount * KPageTableBase.PageSize; + ulong beforeStart = start; + ulong beforeEnd = start; + ulong afterStart = end; + ulong afterEnd = end; + + while (bigIndex >= 0) + { + ulong blockSize = _blocks[bigIndex].Size; + + ulong bigStart = BitUtils.AlignUp(start, blockSize); + ulong bigEnd = BitUtils.AlignDown(end, blockSize); + + if (bigStart < bigEnd) + { + for (ulong block = bigStart; block < bigEnd; block += blockSize) + { + FreeBlock(block, bigIndex); + } + + beforeEnd = bigStart; + afterStart = bigEnd; + + break; + } + + bigIndex--; + } + + for (int i = bigIndex - 1; i >= 0; i--) + { + ulong blockSize = _blocks[i].Size; + + while (beforeStart + blockSize <= beforeEnd) + { + beforeEnd -= blockSize; + FreeBlock(beforeEnd, i); + } + } + + for (int i = bigIndex - 1; i >= 0; i--) + { + ulong blockSize = _blocks[i].Size; + + while (afterStart + blockSize <= afterEnd) + { + FreeBlock(afterStart, i); + afterStart += blockSize; + } + } + } + + public static int GetAlignedBlockIndex(ulong pagesCount, ulong alignPages) + { + ulong targetPages = Math.Max(pagesCount, alignPages); + + for (int i = 0; i < _memoryBlockPageShifts.Length; i++) + { + if (targetPages <= GetBlockPagesCount(i)) + { + return i; + } + } + + return -1; + } + + public static int GetBlockIndex(ulong pagesCount) + { + for (int i = _memoryBlockPageShifts.Length - 1; i >= 0; i--) + { + if (pagesCount >= GetBlockPagesCount(i)) + { + return i; + } + } + + return -1; + } + + public static ulong GetBlockSize(int index) + { + return 1UL << _memoryBlockPageShifts[index]; + } + + public static ulong GetBlockPagesCount(int index) + { + return GetBlockSize(index) / KPageTableBase.PageSize; + } + + private static int CalculateManagementOverheadSize(ulong regionSize, int[] blockShifts) + { + int overheadSize = 0; + + for (int i = 0; i < blockShifts.Length; i++) + { + int currBlockShift = blockShifts[i]; + int nextBlockShift = i != blockShifts.Length - 1 ? blockShifts[i + 1] : 0; + overheadSize += Block.CalculateManagementOverheadSize(regionSize, currBlockShift, nextBlockShift); + } + + return BitUtils.AlignUp(overheadSize, KPageTableBase.PageSize); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs new file mode 100644 index 00000000..60514824 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs @@ -0,0 +1,97 @@ +using Ryujinx.Horizon.Common; +using System.Collections; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KPageList : IEnumerable + { + public LinkedList Nodes { get; } + + public KPageList() + { + Nodes = new LinkedList(); + } + + public Result AddRange(ulong address, ulong pagesCount) + { + if (pagesCount != 0) + { + if (Nodes.Last != null) + { + KPageNode lastNode = Nodes.Last.Value; + + if (lastNode.Address + lastNode.PagesCount * KPageTableBase.PageSize == address) + { + address = lastNode.Address; + pagesCount += lastNode.PagesCount; + + Nodes.RemoveLast(); + } + } + + Nodes.AddLast(new KPageNode(address, pagesCount)); + } + + return Result.Success; + } + + public ulong GetPagesCount() + { + ulong sum = 0; + + foreach (KPageNode node in Nodes) + { + sum += node.PagesCount; + } + + return sum; + } + + public bool IsEqual(KPageList other) + { + LinkedListNode thisNode = Nodes.First; + LinkedListNode otherNode = other.Nodes.First; + + while (thisNode != null && otherNode != null) + { + if (thisNode.Value.Address != otherNode.Value.Address || + thisNode.Value.PagesCount != otherNode.Value.PagesCount) + { + return false; + } + + thisNode = thisNode.Next; + otherNode = otherNode.Next; + } + + return thisNode == null && otherNode == null; + } + + public void IncrementPagesReferenceCount(KMemoryManager manager) + { + foreach (var node in this) + { + manager.IncrementPagesReferenceCount(node.Address, node.PagesCount); + } + } + + public void DecrementPagesReferenceCount(KMemoryManager manager) + { + foreach (var node in this) + { + manager.DecrementPagesReferenceCount(node.Address, node.PagesCount); + } + } + + public IEnumerator GetEnumerator() + { + return Nodes.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs new file mode 100644 index 00000000..395c8c83 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + struct KPageNode + { + public ulong Address; + public ulong PagesCount; + + public KPageNode(ulong address, ulong pagesCount) + { + Address = address; + PagesCount = pagesCount; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs new file mode 100644 index 00000000..4ffa447d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs @@ -0,0 +1,269 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KPageTable : KPageTableBase + { + private readonly IVirtualMemoryManager _cpuMemory; + + protected override bool UsesPrivateAllocations => _cpuMemory.UsesPrivateAllocations; + + public KPageTable(KernelContext context, IVirtualMemoryManager cpuMemory, ulong reservedAddressSpaceSize) : base(context, reservedAddressSpaceSize) + { + _cpuMemory = cpuMemory; + } + + /// + protected override IEnumerable GetHostRegions(ulong va, ulong size) + { + return _cpuMemory.GetHostRegions(va, size); + } + + /// + protected override void GetPhysicalRegions(ulong va, ulong size, KPageList pageList) + { + var ranges = _cpuMemory.GetPhysicalRegions(va, size); + foreach (var range in ranges) + { + pageList.AddRange(range.Address + DramMemoryMap.DramBase, range.Size / PageSize); + } + } + + /// + protected override ReadOnlySequence GetReadOnlySequence(ulong va, int size) + { + return _cpuMemory.GetReadOnlySequence(va, size); + } + + /// + protected override ReadOnlySpan GetSpan(ulong va, int size) + { + return _cpuMemory.GetSpan(va, size); + } + + /// + protected override Result MapMemory(ulong src, ulong dst, ulong pagesCount, KMemoryPermission oldSrcPermission, KMemoryPermission newDstPermission) + { + KPageList pageList = new(); + GetPhysicalRegions(src, pagesCount * PageSize, pageList); + + Result result = Reprotect(src, pagesCount, KMemoryPermission.None); + + if (result != Result.Success) + { + return result; + } + + result = MapPages(dst, pageList, newDstPermission, MemoryMapFlags.Private, false, 0); + + if (result != Result.Success) + { + Result reprotectResult = Reprotect(src, pagesCount, oldSrcPermission); + Debug.Assert(reprotectResult == Result.Success); + } + + return result; + } + + /// + protected override Result UnmapMemory(ulong dst, ulong src, ulong pagesCount, KMemoryPermission oldDstPermission, KMemoryPermission newSrcPermission) + { + ulong size = pagesCount * PageSize; + + KPageList srcPageList = new(); + KPageList dstPageList = new(); + + GetPhysicalRegions(src, size, srcPageList); + GetPhysicalRegions(dst, size, dstPageList); + + if (!dstPageList.IsEqual(srcPageList)) + { + return KernelResult.InvalidMemRange; + } + + Result result = Unmap(dst, pagesCount); + + if (result != Result.Success) + { + return result; + } + + result = Reprotect(src, pagesCount, newSrcPermission); + + if (result != Result.Success) + { + Result mapResult = MapPages(dst, dstPageList, oldDstPermission, MemoryMapFlags.Private, false, 0); + Debug.Assert(mapResult == Result.Success); + } + + return result; + } + + /// + protected override Result MapPages( + ulong dstVa, + ulong pagesCount, + ulong srcPa, + KMemoryPermission permission, + MemoryMapFlags flags, + bool shouldFillPages, + byte fillValue) + { + ulong size = pagesCount * PageSize; + + Context.CommitMemory(srcPa - DramMemoryMap.DramBase, size); + + _cpuMemory.Map(dstVa, srcPa - DramMemoryMap.DramBase, size, flags); + + if (DramMemoryMap.IsHeapPhysicalAddress(srcPa)) + { + Context.MemoryManager.IncrementPagesReferenceCount(srcPa, pagesCount); + } + + if (shouldFillPages) + { + _cpuMemory.Fill(dstVa, size, fillValue); + } + + return Result.Success; + } + + /// + protected override Result MapPages( + ulong address, + KPageList pageList, + KMemoryPermission permission, + MemoryMapFlags flags, + bool shouldFillPages, + byte fillValue) + { + using var scopedPageList = new KScopedPageList(Context.MemoryManager, pageList); + + ulong currentVa = address; + + foreach (var pageNode in pageList) + { + ulong addr = pageNode.Address - DramMemoryMap.DramBase; + ulong size = pageNode.PagesCount * PageSize; + + Context.CommitMemory(addr, size); + + _cpuMemory.Map(currentVa, addr, size, flags); + + if (shouldFillPages) + { + _cpuMemory.Fill(currentVa, size, fillValue); + } + + currentVa += size; + } + + scopedPageList.SignalSuccess(); + + return Result.Success; + } + + /// + protected override Result MapForeign(IEnumerable regions, ulong va, ulong size) + { + ulong backingStart = (ulong)Context.Memory.Pointer; + ulong backingEnd = backingStart + Context.Memory.Size; + + KPageList pageList = new(); + + foreach (HostMemoryRange region in regions) + { + // If the range is inside the physical memory, it is shared and we should increment the page count, + // otherwise it is private and we don't need to increment the page count. + + if (region.Address >= backingStart && region.Address < backingEnd) + { + pageList.AddRange(region.Address - backingStart + DramMemoryMap.DramBase, region.Size / PageSize); + } + } + + using var scopedPageList = new KScopedPageList(Context.MemoryManager, pageList); + + foreach (var pageNode in pageList) + { + Context.CommitMemory(pageNode.Address - DramMemoryMap.DramBase, pageNode.PagesCount * PageSize); + } + + ulong offset = 0; + + foreach (var region in regions) + { + _cpuMemory.MapForeign(va + offset, region.Address, region.Size); + + offset += region.Size; + } + + scopedPageList.SignalSuccess(); + + return Result.Success; + } + + /// + protected override Result Unmap(ulong address, ulong pagesCount) + { + KPageList pagesToClose = new(); + + var regions = _cpuMemory.GetPhysicalRegions(address, pagesCount * PageSize); + + foreach (var region in regions) + { + ulong pa = region.Address + DramMemoryMap.DramBase; + if (DramMemoryMap.IsHeapPhysicalAddress(pa)) + { + pagesToClose.AddRange(pa, region.Size / PageSize); + } + } + + _cpuMemory.Unmap(address, pagesCount * PageSize); + + pagesToClose.DecrementPagesReferenceCount(Context.MemoryManager); + + return Result.Success; + } + + /// + protected override Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission) + { + _cpuMemory.Reprotect(address, pagesCount * PageSize, permission.Convert()); + + return Result.Success; + } + + /// + protected override Result ReprotectAndFlush(ulong address, ulong pagesCount, KMemoryPermission permission) + { + // TODO: Flush JIT cache. + + return Reprotect(address, pagesCount, permission); + } + + /// + protected override void SignalMemoryTracking(ulong va, ulong size, bool write) + { + _cpuMemory.SignalMemoryTracking(va, size, write); + } + + /// + protected override void Write(ulong va, ReadOnlySequence data) + { + _cpuMemory.Write(va, data); + } + + /// + protected override void Write(ulong va, ReadOnlySpan data) + { + _cpuMemory.Write(va, data); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs new file mode 100644 index 00000000..bf2bbb97 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs @@ -0,0 +1,3107 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + abstract class KPageTableBase + { + private static readonly int[] _mappingUnitSizes = { + 0x1000, + 0x10000, + 0x200000, + 0x400000, + 0x2000000, + 0x40000000, + }; + + private const ulong RegionAlignment = 0x200000; + + public const int PageSize = 0x1000; + + private const int KMemoryBlockSize = 0x40; + + // We need 2 blocks for the case where a big block + // needs to be split in 2, plus one block that will be the new one inserted. + private const int MaxBlocksNeededForInsertion = 2; + + protected readonly KernelContext Context; + protected virtual bool UsesPrivateAllocations => false; + + public ulong AddrSpaceStart { get; private set; } + public ulong AddrSpaceEnd { get; private set; } + + public ulong CodeRegionStart { get; private set; } + public ulong CodeRegionEnd { get; private set; } + + public ulong HeapRegionStart { get; private set; } + public ulong HeapRegionEnd { get; private set; } + + private ulong _currentHeapAddr; + + public ulong AliasRegionStart { get; private set; } + public ulong AliasRegionEnd { get; private set; } + + public ulong StackRegionStart { get; private set; } + public ulong StackRegionEnd { get; private set; } + + public ulong TlsIoRegionStart { get; private set; } + public ulong TlsIoRegionEnd { get; private set; } + + public ulong AslrRegionStart { get; private set; } + public ulong AslrRegionEnd { get; private set; } + + private ulong _heapCapacity; + + public ulong PhysicalMemoryUsage { get; private set; } + public ulong AliasRegionExtraSize { get; private set; } + + private readonly KMemoryBlockManager _blockManager; + + private MemoryRegion _memRegion; + + private bool _allocateFromBack; + private readonly bool _isKernel; + + private bool _aslrEnabled; + + private KMemoryBlockSlabManager _slabManager; + + private int _contextId; + + private MersenneTwister _randomNumberGenerator; + + private readonly MemoryFillValue _heapFillValue; + private readonly MemoryFillValue _ipcFillValue; + + private readonly ulong _reservedAddressSpaceSize; + + public KPageTableBase(KernelContext context, ulong reservedAddressSpaceSize) + { + Context = context; + + _blockManager = new KMemoryBlockManager(); + + _isKernel = false; + + _heapFillValue = MemoryFillValue.Zero; + _ipcFillValue = MemoryFillValue.Zero; + + _reservedAddressSpaceSize = reservedAddressSpaceSize; + } + + public Result InitializeForProcess( + ProcessCreationFlags flags, + bool fromBack, + MemoryRegion memRegion, + ulong address, + ulong size, + KMemoryBlockSlabManager slabManager) + { + _contextId = Context.ContextIdManager.GetId(); + + ulong addrSpaceBase = 0; + ulong addrSpaceSize = 1UL << GetAddressSpaceWidth(flags); + + Result result = CreateUserAddressSpace( + flags, + fromBack, + addrSpaceBase, + addrSpaceSize, + memRegion, + address, + size, + slabManager); + + if (result != Result.Success) + { + Context.ContextIdManager.PutId(_contextId); + } + + return result; + } + + private static int GetAddressSpaceWidth(ProcessCreationFlags flags) + { + switch (flags & ProcessCreationFlags.AddressSpaceMask) + { + case ProcessCreationFlags.AddressSpace32Bit: + case ProcessCreationFlags.AddressSpace32BitWithoutAlias: + return 32; + case ProcessCreationFlags.AddressSpace64BitDeprecated: + return 36; + case ProcessCreationFlags.AddressSpace64Bit: + return 39; + } + + throw new ArgumentException($"Invalid process flags {flags}", nameof(flags)); + } + + private struct Region + { + public ulong Start; + public ulong End; + public ulong Size; + public ulong AslrOffset; + } + + private Result CreateUserAddressSpace( + ProcessCreationFlags flags, + bool fromBack, + ulong addrSpaceStart, + ulong addrSpaceEnd, + MemoryRegion memRegion, + ulong address, + ulong size, + KMemoryBlockSlabManager slabManager) + { + ulong endAddr = address + size; + + Region aliasRegion = new(); + Region heapRegion = new(); + Region stackRegion = new(); + Region tlsIoRegion = new(); + + ulong codeRegionSize; + ulong stackAndTlsIoStart; + ulong stackAndTlsIoEnd; + + AliasRegionExtraSize = 0; + + switch (flags & ProcessCreationFlags.AddressSpaceMask) + { + case ProcessCreationFlags.AddressSpace32Bit: + aliasRegion.Size = 0x40000000; + heapRegion.Size = 0x40000000; + stackRegion.Size = 0; + tlsIoRegion.Size = 0; + CodeRegionStart = 0x200000; + codeRegionSize = 0x3fe00000; + AslrRegionStart = 0x200000; + AslrRegionEnd = AslrRegionStart + 0xffe00000; + stackAndTlsIoStart = 0x200000; + stackAndTlsIoEnd = 0x40000000; + break; + + case ProcessCreationFlags.AddressSpace64BitDeprecated: + aliasRegion.Size = 0x180000000; + heapRegion.Size = 0x180000000; + stackRegion.Size = 0; + tlsIoRegion.Size = 0; + CodeRegionStart = 0x8000000; + codeRegionSize = 0x78000000; + AslrRegionStart = 0x8000000; + AslrRegionEnd = AslrRegionStart + 0xff8000000; + stackAndTlsIoStart = 0x8000000; + stackAndTlsIoEnd = 0x80000000; + break; + + case ProcessCreationFlags.AddressSpace32BitWithoutAlias: + aliasRegion.Size = 0; + heapRegion.Size = 0x80000000; + stackRegion.Size = 0; + tlsIoRegion.Size = 0; + CodeRegionStart = 0x200000; + codeRegionSize = 0x3fe00000; + AslrRegionStart = 0x200000; + AslrRegionEnd = AslrRegionStart + 0xffe00000; + stackAndTlsIoStart = 0x200000; + stackAndTlsIoEnd = 0x40000000; + break; + + case ProcessCreationFlags.AddressSpace64Bit: + if (_reservedAddressSpaceSize < addrSpaceEnd) + { + int addressSpaceWidth = (int)ulong.Log2(_reservedAddressSpaceSize); + + aliasRegion.Size = 1UL << (addressSpaceWidth - 3); + heapRegion.Size = 0x180000000; + stackRegion.Size = 1UL << (addressSpaceWidth - 8); + tlsIoRegion.Size = 1UL << (addressSpaceWidth - 3); + CodeRegionStart = BitUtils.AlignDown(address, RegionAlignment); + codeRegionSize = BitUtils.AlignUp(endAddr, RegionAlignment) - CodeRegionStart; + stackAndTlsIoStart = 0; + stackAndTlsIoEnd = 0; + AslrRegionStart = 0x8000000; + addrSpaceEnd = 1UL << addressSpaceWidth; + AslrRegionEnd = addrSpaceEnd; + } + else + { + aliasRegion.Size = 0x1000000000; + heapRegion.Size = 0x180000000; + stackRegion.Size = 0x80000000; + tlsIoRegion.Size = 0x1000000000; + CodeRegionStart = BitUtils.AlignDown(address, RegionAlignment); + codeRegionSize = BitUtils.AlignUp(endAddr, RegionAlignment) - CodeRegionStart; + AslrRegionStart = 0x8000000; + AslrRegionEnd = AslrRegionStart + 0x7ff8000000; + stackAndTlsIoStart = 0; + stackAndTlsIoEnd = 0; + } + + if (flags.HasFlag(ProcessCreationFlags.EnableAliasRegionExtraSize)) + { + AliasRegionExtraSize = addrSpaceEnd / 8; + aliasRegion.Size += AliasRegionExtraSize; + } + break; + + default: + throw new ArgumentException($"Invalid process flags {flags}", nameof(flags)); + } + + CodeRegionEnd = CodeRegionStart + codeRegionSize; + + ulong mapBaseAddress; + ulong mapAvailableSize; + + if (CodeRegionStart - AslrRegionStart >= addrSpaceEnd - CodeRegionEnd) + { + // Has more space before the start of the code region. + mapBaseAddress = AslrRegionStart; + mapAvailableSize = CodeRegionStart - AslrRegionStart; + } + else + { + // Has more space after the end of the code region. + mapBaseAddress = CodeRegionEnd; + mapAvailableSize = addrSpaceEnd - CodeRegionEnd; + } + + ulong mapTotalSize = aliasRegion.Size + heapRegion.Size + stackRegion.Size + tlsIoRegion.Size; + + ulong aslrMaxOffset = mapAvailableSize - mapTotalSize; + + bool aslrEnabled = flags.HasFlag(ProcessCreationFlags.EnableAslr); + + _aslrEnabled = aslrEnabled; + + AddrSpaceStart = addrSpaceStart; + AddrSpaceEnd = addrSpaceEnd; + + _slabManager = slabManager; + + if (mapAvailableSize < mapTotalSize) + { + return KernelResult.OutOfMemory; + } + + if (aslrEnabled) + { + aliasRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset / RegionAlignment) * RegionAlignment; + heapRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset / RegionAlignment) * RegionAlignment; + stackRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset / RegionAlignment) * RegionAlignment; + tlsIoRegion.AslrOffset = GetRandomValue(0, aslrMaxOffset / RegionAlignment) * RegionAlignment; + } + + // Regions are sorted based on ASLR offset. + // When ASLR is disabled, the order is Alias, Heap, Stack and TlsIo. + aliasRegion.Start = mapBaseAddress + aliasRegion.AslrOffset; + aliasRegion.End = aliasRegion.Start + aliasRegion.Size; + heapRegion.Start = mapBaseAddress + heapRegion.AslrOffset; + heapRegion.End = heapRegion.Start + heapRegion.Size; + stackRegion.Start = mapBaseAddress + stackRegion.AslrOffset; + stackRegion.End = stackRegion.Start + stackRegion.Size; + tlsIoRegion.Start = mapBaseAddress + tlsIoRegion.AslrOffset; + tlsIoRegion.End = tlsIoRegion.Start + tlsIoRegion.Size; + + SortRegion(ref aliasRegion, ref heapRegion, true); + + if (stackRegion.Size != 0) + { + stackRegion.Start = mapBaseAddress + stackRegion.AslrOffset; + stackRegion.End = stackRegion.Start + stackRegion.Size; + + SortRegion(ref aliasRegion, ref stackRegion); + SortRegion(ref heapRegion, ref stackRegion); + } + else + { + stackRegion.Start = stackAndTlsIoStart; + stackRegion.End = stackAndTlsIoEnd; + } + + if (tlsIoRegion.Size != 0) + { + tlsIoRegion.Start = mapBaseAddress + tlsIoRegion.AslrOffset; + tlsIoRegion.End = tlsIoRegion.Start + tlsIoRegion.Size; + + SortRegion(ref aliasRegion, ref tlsIoRegion); + SortRegion(ref heapRegion, ref tlsIoRegion); + + if (stackRegion.Size != 0) + { + SortRegion(ref stackRegion, ref tlsIoRegion); + } + } + else + { + tlsIoRegion.Start = stackAndTlsIoStart; + tlsIoRegion.End = stackAndTlsIoEnd; + } + + AliasRegionStart = aliasRegion.Start; + AliasRegionEnd = aliasRegion.End; + HeapRegionStart = heapRegion.Start; + HeapRegionEnd = heapRegion.End; + StackRegionStart = stackRegion.Start; + StackRegionEnd = stackRegion.End; + TlsIoRegionStart = tlsIoRegion.Start; + TlsIoRegionEnd = tlsIoRegion.End; + + // TODO: Check kernel configuration via secure monitor call when implemented to set memory fill values. + + _currentHeapAddr = HeapRegionStart; + _heapCapacity = 0; + PhysicalMemoryUsage = 0; + + _memRegion = memRegion; + _allocateFromBack = fromBack; + + return _blockManager.Initialize(addrSpaceStart, addrSpaceEnd, slabManager); + } + + private static void SortRegion(ref Region lhs, ref Region rhs, bool checkForEquality = false) + { + bool res = checkForEquality ? lhs.AslrOffset <= rhs.AslrOffset : lhs.AslrOffset < rhs.AslrOffset; + + if (res) + { + rhs.Start += lhs.Size; + rhs.End += lhs.Size; + } + else + { + lhs.Start += rhs.Size; + lhs.End += rhs.Size; + } + } + + private ulong GetRandomValue(ulong min, ulong max) + { + return (ulong)GetRandomValue((long)min, (long)max); + } + + private long GetRandomValue(long min, long max) + { + _randomNumberGenerator ??= new MersenneTwister(0); + + return _randomNumberGenerator.GenRandomNumber(min, max); + } + + public Result MapPages(ulong address, KPageList pageList, MemoryState state, KMemoryPermission permission) + { + ulong pagesCount = pageList.GetPagesCount(); + + ulong size = pagesCount * PageSize; + + if (!CanContain(address, size, state)) + { + return KernelResult.InvalidMemState; + } + + lock (_blockManager) + { + if (!IsUnmapped(address, pagesCount * PageSize)) + { + return KernelResult.InvalidMemState; + } + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + Result result = MapPages(address, pageList, permission, MemoryMapFlags.None); + + if (result == Result.Success) + { + _blockManager.InsertBlock(address, pagesCount, state, permission); + } + + return result; + } + } + + public Result UnmapPages(ulong address, KPageList pageList, MemoryState stateExpected) + { + ulong pagesCount = pageList.GetPagesCount(); + ulong size = pagesCount * PageSize; + + ulong endAddr = address + size; + + ulong addrSpacePagesCount = (AddrSpaceEnd - AddrSpaceStart) / PageSize; + + if (AddrSpaceStart > address) + { + return KernelResult.InvalidMemState; + } + + if (addrSpacePagesCount < pagesCount) + { + return KernelResult.InvalidMemState; + } + + if (endAddr - 1 > AddrSpaceEnd - 1) + { + return KernelResult.InvalidMemState; + } + + lock (_blockManager) + { + KPageList currentPageList = new(); + + GetPhysicalRegions(address, size, currentPageList); + + if (!currentPageList.IsEqual(pageList)) + { + return KernelResult.InvalidMemRange; + } + + if (CheckRange( + address, + size, + MemoryState.Mask, + stateExpected, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState state, + out _, + out _)) + { + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + Result result = Unmap(address, pagesCount); + + if (result == Result.Success) + { + _blockManager.InsertBlock(address, pagesCount, MemoryState.Unmapped); + } + + return result; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public static Result MapNormalMemory(long address, long size, KMemoryPermission permission) + { + // TODO. + return Result.Success; + } + + public static Result MapIoMemory(long address, long size, KMemoryPermission permission) + { + // TODO. + return Result.Success; + } + + public Result MapPages( + ulong pagesCount, + int alignment, + ulong srcPa, + bool paIsValid, + ulong regionStart, + ulong regionPagesCount, + MemoryState state, + KMemoryPermission permission, + out ulong address) + { + address = 0; + + ulong regionSize = regionPagesCount * PageSize; + + if (!CanContain(regionStart, regionSize, state)) + { + return KernelResult.InvalidMemState; + } + + if (regionPagesCount <= pagesCount) + { + return KernelResult.OutOfMemory; + } + + lock (_blockManager) + { + address = AllocateVa(regionStart, regionPagesCount, pagesCount, alignment); + + if (address == 0) + { + return KernelResult.OutOfMemory; + } + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + Result result; + + if (paIsValid) + { + result = MapPages(address, pagesCount, srcPa, permission, MemoryMapFlags.Private); + } + else + { + result = AllocateAndMapPages(address, pagesCount, permission); + } + + if (result != Result.Success) + { + return result; + } + + _blockManager.InsertBlock(address, pagesCount, state, permission); + } + + return Result.Success; + } + + public Result MapPages(ulong address, ulong pagesCount, MemoryState state, KMemoryPermission permission) + { + ulong size = pagesCount * PageSize; + + if (!CanContain(address, size, state)) + { + return KernelResult.InvalidMemState; + } + + lock (_blockManager) + { + if (!IsUnmapped(address, size)) + { + return KernelResult.InvalidMemState; + } + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + Result result = AllocateAndMapPages(address, pagesCount, permission); + + if (result == Result.Success) + { + _blockManager.InsertBlock(address, pagesCount, state, permission); + } + + return result; + } + } + + private Result AllocateAndMapPages(ulong address, ulong pagesCount, KMemoryPermission permission) + { + KMemoryRegionManager region = GetMemoryRegionManager(); + + Result result = region.AllocatePages(out KPageList pageList, pagesCount); + + if (result != Result.Success) + { + return result; + } + + using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager)); + + return MapPages(address, pageList, permission, MemoryMapFlags.Private); + } + + public Result MapProcessCodeMemory(ulong dst, ulong src, ulong size) + { + lock (_blockManager) + { + bool success = CheckRange( + src, + size, + MemoryState.Mask, + MemoryState.Heap, + KMemoryPermission.Mask, + KMemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState state, + out KMemoryPermission permission, + out _); + + success &= IsUnmapped(dst, size); + + if (success) + { + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + Result result = MapMemory(src, dst, pagesCount, permission, KMemoryPermission.None); + + _blockManager.InsertBlock(src, pagesCount, state, KMemoryPermission.None, MemoryAttribute.Borrowed); + _blockManager.InsertBlock(dst, pagesCount, MemoryState.ModCodeStatic); + + return Result.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public Result UnmapProcessCodeMemory(ulong dst, ulong src, ulong size) + { + lock (_blockManager) + { + bool success = CheckRange( + src, + size, + MemoryState.Mask, + MemoryState.Heap, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _); + + success &= CheckRange( + dst, + PageSize, + MemoryState.UnmapProcessCodeMemoryAllowed, + MemoryState.UnmapProcessCodeMemoryAllowed, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask & ~MemoryAttribute.PermissionLocked, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState state, + out _, + out _); + + success &= CheckRange( + dst, + size, + MemoryState.Mask, + state, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask & ~MemoryAttribute.PermissionLocked, + MemoryAttribute.None); + + if (success) + { + ulong pagesCount = size / PageSize; + + Result result = Unmap(dst, pagesCount); + + if (result != Result.Success) + { + return result; + } + + // TODO: Missing some checks here. + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + + _blockManager.InsertBlock(dst, pagesCount, MemoryState.Unmapped); + _blockManager.InsertBlock(src, pagesCount, MemoryState.Heap, KMemoryPermission.ReadAndWrite); + + return Result.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public Result SetHeapSize(ulong size, out ulong address) + { + address = 0; + + if (size > HeapRegionEnd - HeapRegionStart || size > _heapCapacity) + { + return KernelResult.OutOfMemory; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + lock (_blockManager) + { + ulong currentHeapSize = GetHeapSize(); + + if (currentHeapSize <= size) + { + // Expand. + ulong sizeDelta = size - currentHeapSize; + + if (currentProcess.ResourceLimit != null && sizeDelta != 0 && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Memory, sizeDelta)) + { + return KernelResult.ResLimitExceeded; + } + + ulong pagesCount = sizeDelta / PageSize; + + KMemoryRegionManager region = GetMemoryRegionManager(); + + Result result = region.AllocatePages(out KPageList pageList, pagesCount); + + using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager)); + + void CleanUpForError() + { + if (currentProcess.ResourceLimit != null && sizeDelta != 0) + { + currentProcess.ResourceLimit.Release(LimitableResource.Memory, sizeDelta); + } + } + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + CleanUpForError(); + + return KernelResult.OutOfResource; + } + + if (!IsUnmapped(_currentHeapAddr, sizeDelta)) + { + CleanUpForError(); + + return KernelResult.InvalidMemState; + } + + result = MapPages(_currentHeapAddr, pageList, KMemoryPermission.ReadAndWrite, MemoryMapFlags.Private, true, (byte)_heapFillValue); + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + + _blockManager.InsertBlock(_currentHeapAddr, pagesCount, MemoryState.Heap, KMemoryPermission.ReadAndWrite); + } + else + { + // Shrink. + ulong freeAddr = HeapRegionStart + size; + ulong sizeDelta = currentHeapSize - size; + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + if (!CheckRange( + freeAddr, + sizeDelta, + MemoryState.Mask, + MemoryState.Heap, + KMemoryPermission.Mask, + KMemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _)) + { + return KernelResult.InvalidMemState; + } + + ulong pagesCount = sizeDelta / PageSize; + + Result result = Unmap(freeAddr, pagesCount); + + if (result != Result.Success) + { + return result; + } + + currentProcess.ResourceLimit?.Release(LimitableResource.Memory, sizeDelta); + + _blockManager.InsertBlock(freeAddr, pagesCount, MemoryState.Unmapped); + } + + _currentHeapAddr = HeapRegionStart + size; + } + + address = HeapRegionStart; + + return Result.Success; + } + + public Result SetMemoryPermission(ulong address, ulong size, KMemoryPermission permission) + { + lock (_blockManager) + { + if (CheckRange( + address, + size, + MemoryState.PermissionChangeAllowed, + MemoryState.PermissionChangeAllowed, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState oldState, + out KMemoryPermission oldPermission, + out _)) + { + if (permission != oldPermission) + { + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + Result result = Reprotect(address, pagesCount, permission); + + if (result != Result.Success) + { + return result; + } + + _blockManager.InsertBlock(address, pagesCount, oldState, permission); + } + + return Result.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public ulong GetTotalHeapSize() + { + lock (_blockManager) + { + return GetHeapSize() + PhysicalMemoryUsage; + } + } + + private ulong GetHeapSize() + { + return _currentHeapAddr - HeapRegionStart; + } + + public Result SetHeapCapacity(ulong capacity) + { + lock (_blockManager) + { + _heapCapacity = capacity; + } + + return Result.Success; + } + + public Result SetMemoryAttribute(ulong address, ulong size, MemoryAttribute attributeMask, MemoryAttribute attributeValue) + { + lock (_blockManager) + { + MemoryState stateCheckMask = 0; + + if (attributeMask.HasFlag(MemoryAttribute.Uncached)) + { + stateCheckMask = MemoryState.AttributeChangeAllowed; + } + + if (attributeMask.HasFlag(MemoryAttribute.PermissionLocked)) + { + stateCheckMask |= MemoryState.PermissionLockAllowed; + } + + if (CheckRange( + address, + size, + stateCheckMask, + stateCheckMask, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.BorrowedAndIpcMapped, + MemoryAttribute.None, + MemoryAttribute.DeviceMappedAndUncached, + out MemoryState state, + out KMemoryPermission permission, + out MemoryAttribute attribute)) + { + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + attribute &= ~attributeMask; + attribute |= attributeMask & attributeValue; + + _blockManager.InsertBlock(address, pagesCount, state, permission, attribute); + + return Result.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public KMemoryInfo QueryMemory(ulong address) + { + if (address >= AddrSpaceStart && + address < AddrSpaceEnd) + { + lock (_blockManager) + { + return _blockManager.FindBlock(address).GetInfo(); + } + } + else + { + return new KMemoryInfo( + AddrSpaceEnd, + ~AddrSpaceEnd + 1, + MemoryState.Reserved, + KMemoryPermission.None, + MemoryAttribute.None, + KMemoryPermission.None, + 0, + 0); + } + } + + public Result Map(ulong dst, ulong src, ulong size) + { + bool success; + + lock (_blockManager) + { + success = CheckRange( + src, + size, + MemoryState.MapAllowed, + MemoryState.MapAllowed, + KMemoryPermission.Mask, + KMemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState srcState, + out _, + out _); + + success &= IsUnmapped(dst, size); + + if (success) + { + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + Result result = MapMemory(src, dst, pagesCount, KMemoryPermission.ReadAndWrite, KMemoryPermission.ReadAndWrite); + + if (result != Result.Success) + { + return result; + } + + _blockManager.InsertBlock(src, pagesCount, srcState, KMemoryPermission.None, MemoryAttribute.Borrowed); + _blockManager.InsertBlock(dst, pagesCount, MemoryState.Stack, KMemoryPermission.ReadAndWrite); + + return Result.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public Result UnmapForKernel(ulong address, ulong pagesCount, MemoryState stateExpected) + { + ulong size = pagesCount * PageSize; + + lock (_blockManager) + { + if (CheckRange( + address, + size, + MemoryState.Mask, + stateExpected, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _)) + { + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + Result result = Unmap(address, pagesCount); + + if (result == Result.Success) + { + _blockManager.InsertBlock(address, pagesCount, MemoryState.Unmapped); + } + + return Result.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public Result Unmap(ulong dst, ulong src, ulong size) + { + bool success; + + lock (_blockManager) + { + success = CheckRange( + src, + size, + MemoryState.MapAllowed, + MemoryState.MapAllowed, + KMemoryPermission.Mask, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState srcState, + out _, + out _); + + success &= CheckRange( + dst, + size, + MemoryState.Mask, + MemoryState.Stack, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out KMemoryPermission dstPermission, + out _); + + if (success) + { + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion * 2)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + Result result = UnmapMemory(dst, src, pagesCount, dstPermission, KMemoryPermission.ReadAndWrite); + + if (result != Result.Success) + { + return result; + } + + _blockManager.InsertBlock(src, pagesCount, srcState, KMemoryPermission.ReadAndWrite); + _blockManager.InsertBlock(dst, pagesCount, MemoryState.Unmapped); + + return Result.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public Result UnmapProcessMemory(ulong dst, ulong size, KPageTableBase srcPageTable, ulong src) + { + lock (_blockManager) + { + lock (srcPageTable._blockManager) + { + bool success = CheckRange( + dst, + size, + MemoryState.Mask, + MemoryState.ProcessMemory, + KMemoryPermission.ReadAndWrite, + KMemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _); + + success &= srcPageTable.CheckRange( + src, + size, + MemoryState.MapProcessAllowed, + MemoryState.MapProcessAllowed, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _); + + if (!success) + { + return KernelResult.InvalidMemState; + } + + KPageList srcPageList = new(); + KPageList dstPageList = new(); + + srcPageTable.GetPhysicalRegions(src, size, srcPageList); + GetPhysicalRegions(dst, size, dstPageList); + + if (!dstPageList.IsEqual(srcPageList)) + { + return KernelResult.InvalidMemRange; + } + } + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + Result result = Unmap(dst, pagesCount); + + if (result != Result.Success) + { + return result; + } + + _blockManager.InsertBlock(dst, pagesCount, MemoryState.Unmapped); + + return Result.Success; + } + } + + public Result SetProcessMemoryPermission(ulong address, ulong size, KMemoryPermission permission) + { + lock (_blockManager) + { + if (CheckRange( + address, + size, + MemoryState.ProcessPermissionChangeAllowed, + MemoryState.ProcessPermissionChangeAllowed, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState oldState, + out KMemoryPermission oldPermission, + out _)) + { + MemoryState newState = oldState; + + // If writing into the code region is allowed, then we need + // to change it to mutable. + if ((permission & KMemoryPermission.Write) != 0) + { + if (oldState == MemoryState.CodeStatic) + { + newState = MemoryState.CodeMutable; + } + else if (oldState == MemoryState.ModCodeStatic) + { + newState = MemoryState.ModCodeMutable; + } + else + { + throw new InvalidOperationException($"Memory state \"{oldState}\" not valid for this operation."); + } + } + + if (newState != oldState || permission != oldPermission) + { + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong pagesCount = size / PageSize; + + Result result; + + if ((oldPermission & KMemoryPermission.Execute) != 0) + { + result = ReprotectAndFlush(address, pagesCount, permission); + } + else + { + result = Reprotect(address, pagesCount, permission); + } + + if (result != Result.Success) + { + return result; + } + + _blockManager.InsertBlock(address, pagesCount, newState, permission); + } + + return Result.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public Result MapPhysicalMemory(ulong address, ulong size) + { + ulong endAddr = address + size; + + lock (_blockManager) + { + ulong mappedSize = 0; + + foreach (KMemoryInfo info in IterateOverRange(address, endAddr)) + { + if (info.State != MemoryState.Unmapped) + { + mappedSize += GetSizeInRange(info, address, endAddr); + } + } + + if (mappedSize == size) + { + return Result.Success; + } + + ulong remainingSize = size - mappedSize; + + ulong remainingPages = remainingSize / PageSize; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (currentProcess.ResourceLimit != null && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Memory, remainingSize)) + { + return KernelResult.ResLimitExceeded; + } + + KMemoryRegionManager region = GetMemoryRegionManager(); + + Result result = region.AllocatePages(out KPageList pageList, remainingPages); + + using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager)); + + void CleanUpForError() + { + currentProcess.ResourceLimit?.Release(LimitableResource.Memory, remainingSize); + } + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + CleanUpForError(); + + return KernelResult.OutOfResource; + } + + LinkedListNode pageListNode = pageList.Nodes.First; + + KPageNode pageNode = pageListNode.Value; + + ulong srcPa = pageNode.Address; + ulong srcPaPages = pageNode.PagesCount; + + foreach (KMemoryInfo info in IterateOverRange(address, endAddr)) + { + if (info.State != MemoryState.Unmapped) + { + continue; + } + + ulong blockSize = GetSizeInRange(info, address, endAddr); + + ulong dstVaPages = blockSize / PageSize; + + ulong dstVa = GetAddrInRange(info, address); + + while (dstVaPages > 0) + { + if (srcPaPages == 0) + { + pageListNode = pageListNode.Next; + + pageNode = pageListNode.Value; + + srcPa = pageNode.Address; + srcPaPages = pageNode.PagesCount; + } + + ulong currentPagesCount = Math.Min(srcPaPages, dstVaPages); + + MapPages(dstVa, currentPagesCount, srcPa, KMemoryPermission.ReadAndWrite, MemoryMapFlags.Private); + + dstVa += currentPagesCount * PageSize; + srcPa += currentPagesCount * PageSize; + srcPaPages -= currentPagesCount; + dstVaPages -= currentPagesCount; + } + } + + PhysicalMemoryUsage += remainingSize; + + ulong pagesCount = size / PageSize; + + _blockManager.InsertBlock( + address, + pagesCount, + MemoryState.Unmapped, + KMemoryPermission.None, + MemoryAttribute.None, + MemoryState.Heap, + KMemoryPermission.ReadAndWrite, + MemoryAttribute.None); + } + + return Result.Success; + } + + public Result UnmapPhysicalMemory(ulong address, ulong size) + { + ulong endAddr = address + size; + + lock (_blockManager) + { + // Scan, ensure that the region can be unmapped (all blocks are heap or + // already unmapped), fill pages list for freeing memory. + ulong heapMappedSize = 0; + + foreach (KMemoryInfo info in IterateOverRange(address, endAddr)) + { + if (info.State == MemoryState.Heap) + { + if (info.Attribute != MemoryAttribute.None) + { + return KernelResult.InvalidMemState; + } + + ulong blockSize = GetSizeInRange(info, address, endAddr); + + heapMappedSize += blockSize; + } + else if (info.State != MemoryState.Unmapped) + { + return KernelResult.InvalidMemState; + } + } + + if (heapMappedSize == 0) + { + return Result.Success; + } + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + // Try to unmap all the heap mapped memory inside range. + Result result = Result.Success; + + foreach (KMemoryInfo info in IterateOverRange(address, endAddr)) + { + if (info.State == MemoryState.Heap) + { + ulong blockSize = GetSizeInRange(info, address, endAddr); + ulong blockAddress = GetAddrInRange(info, address); + + ulong blockPagesCount = blockSize / PageSize; + + result = Unmap(blockAddress, blockPagesCount); + + // The kernel would attempt to remap if this fails, but we don't because: + // - The implementation may not support remapping if memory aliasing is not supported on the platform. + // - Unmap can't ever fail here anyway. + Debug.Assert(result == Result.Success); + } + } + + if (result == Result.Success) + { + PhysicalMemoryUsage -= heapMappedSize; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + currentProcess.ResourceLimit?.Release(LimitableResource.Memory, heapMappedSize); + + ulong pagesCount = size / PageSize; + + _blockManager.InsertBlock(address, pagesCount, MemoryState.Unmapped); + } + + return result; + } + } + + public Result CopyDataToCurrentProcess( + ulong dst, + ulong size, + ulong src, + MemoryState stateMask, + MemoryState stateExpected, + KMemoryPermission permission, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected) + { + // Client -> server. + return CopyDataFromOrToCurrentProcess( + size, + src, + dst, + stateMask, + stateExpected, + permission, + attributeMask, + attributeExpected, + toServer: true); + } + + public Result CopyDataFromCurrentProcess( + ulong dst, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + KMemoryPermission permission, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + ulong src) + { + // Server -> client. + return CopyDataFromOrToCurrentProcess( + size, + dst, + src, + stateMask, + stateExpected, + permission, + attributeMask, + attributeExpected, + toServer: false); + } + + private Result CopyDataFromOrToCurrentProcess( + ulong size, + ulong clientAddress, + ulong serverAddress, + MemoryState stateMask, + MemoryState stateExpected, + KMemoryPermission permission, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + bool toServer) + { + if (AddrSpaceStart > clientAddress) + { + return KernelResult.InvalidMemState; + } + + ulong srcEndAddr = clientAddress + size; + + if (srcEndAddr <= clientAddress || srcEndAddr - 1 > AddrSpaceEnd - 1) + { + return KernelResult.InvalidMemState; + } + + lock (_blockManager) + { + if (CheckRange( + clientAddress, + size, + stateMask, + stateExpected, + permission, + permission, + attributeMask | MemoryAttribute.Uncached, + attributeExpected)) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + while (size > 0) + { + ulong copySize = int.MaxValue; + + if (copySize > size) + { + copySize = size; + } + + if (toServer) + { + currentProcess.CpuMemory.Write(serverAddress, GetReadOnlySequence(clientAddress, (int)copySize)); + } + else + { + Write(clientAddress, currentProcess.CpuMemory.GetReadOnlySequence(serverAddress, (int)copySize)); + } + + serverAddress += copySize; + clientAddress += copySize; + size -= copySize; + } + + return Result.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public Result MapBufferFromClientProcess( + ulong size, + ulong src, + KPageTableBase srcPageTable, + KMemoryPermission permission, + MemoryState state, + bool send, + out ulong dst) + { + dst = 0; + + lock (srcPageTable._blockManager) + { + lock (_blockManager) + { + Result result = srcPageTable.ReprotectClientProcess( + src, + size, + permission, + state, + out int blocksNeeded); + + if (result != Result.Success) + { + return result; + } + + if (!srcPageTable._slabManager.CanAllocate(blocksNeeded)) + { + return KernelResult.OutOfResource; + } + + ulong srcMapAddress = BitUtils.AlignUp(src, PageSize); + ulong srcMapEndAddr = BitUtils.AlignDown(src + size, PageSize); + ulong srcMapSize = srcMapEndAddr - srcMapAddress; + + result = MapPagesFromClientProcess(size, src, permission, state, srcPageTable, send, out ulong va); + + if (result != Result.Success) + { + if (srcMapEndAddr > srcMapAddress) + { + srcPageTable.UnmapIpcRestorePermission(src, size, state); + } + + return result; + } + + if (srcMapAddress < srcMapEndAddr) + { + KMemoryPermission permissionMask = permission == KMemoryPermission.ReadAndWrite + ? KMemoryPermission.None + : KMemoryPermission.Read; + + srcPageTable._blockManager.InsertBlock(srcMapAddress, srcMapSize / PageSize, SetIpcMappingPermissions, permissionMask); + } + + dst = va; + } + } + + return Result.Success; + } + + private Result ReprotectClientProcess( + ulong address, + ulong size, + KMemoryPermission permission, + MemoryState state, + out int blocksNeeded) + { + blocksNeeded = 0; + + if (AddrSpaceStart > address) + { + return KernelResult.InvalidMemState; + } + + ulong endAddr = address + size; + + if (endAddr <= address || endAddr - 1 > AddrSpaceEnd - 1) + { + return KernelResult.InvalidMemState; + } + + MemoryState stateMask; + + switch (state) + { + case MemoryState.IpcBuffer0: + stateMask = MemoryState.IpcSendAllowedType0; + break; + case MemoryState.IpcBuffer1: + stateMask = MemoryState.IpcSendAllowedType1; + break; + case MemoryState.IpcBuffer3: + stateMask = MemoryState.IpcSendAllowedType3; + break; + default: + return KernelResult.InvalidCombination; + } + + KMemoryPermission permissionMask = permission == KMemoryPermission.ReadAndWrite + ? KMemoryPermission.None + : KMemoryPermission.Read; + + MemoryAttribute attributeMask = MemoryAttribute.Borrowed | MemoryAttribute.Uncached; + + if (state == MemoryState.IpcBuffer0) + { + attributeMask |= MemoryAttribute.DeviceMapped; + } + + ulong addressRounded = BitUtils.AlignUp(address, PageSize); + ulong addressTruncated = BitUtils.AlignDown(address, PageSize); + ulong endAddrRounded = BitUtils.AlignUp(endAddr, PageSize); + ulong endAddrTruncated = BitUtils.AlignDown(endAddr, PageSize); + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong visitedSize = 0; + + void CleanUpForError() + { + if (visitedSize == 0) + { + return; + } + + ulong endAddrVisited = address + visitedSize; + + foreach (KMemoryInfo info in IterateOverRange(addressRounded, endAddrVisited)) + { + if ((info.Permission & KMemoryPermission.ReadAndWrite) != permissionMask && info.IpcRefCount == 0) + { + ulong blockAddress = GetAddrInRange(info, addressRounded); + ulong blockSize = GetSizeInRange(info, addressRounded, endAddrVisited); + + ulong blockPagesCount = blockSize / PageSize; + + Result reprotectResult = Reprotect(blockAddress, blockPagesCount, info.Permission); + Debug.Assert(reprotectResult == Result.Success); + } + } + } + + // Signal a read for any resources tracking reads in the region, as the other process is likely to use their data. + SignalMemoryTracking(addressTruncated, endAddrRounded - addressTruncated, false); + + // Reprotect the aligned pages range on the client to make them inaccessible from the client process. + Result result; + + if (addressRounded < endAddrTruncated) + { + foreach (KMemoryInfo info in IterateOverRange(addressRounded, endAddrTruncated)) + { + // Check if the block state matches what we expect. + if ((info.State & stateMask) != stateMask || + (info.Permission & permission) != permission || + (info.Attribute & attributeMask) != MemoryAttribute.None) + { + CleanUpForError(); + + return KernelResult.InvalidMemState; + } + + ulong blockAddress = GetAddrInRange(info, addressRounded); + ulong blockSize = GetSizeInRange(info, addressRounded, endAddrTruncated); + + ulong blockPagesCount = blockSize / PageSize; + + // If the first block starts before the aligned range, it will need to be split. + if (info.Address < addressRounded) + { + blocksNeeded++; + } + + // If the last block ends after the aligned range, it will need to be split. + if (endAddrTruncated - 1 < info.Address + info.Size - 1) + { + blocksNeeded++; + } + + if ((info.Permission & KMemoryPermission.ReadAndWrite) != permissionMask && info.IpcRefCount == 0) + { + result = Reprotect(blockAddress, blockPagesCount, permissionMask); + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + } + + visitedSize += blockSize; + } + } + + return Result.Success; + } + + private Result MapPagesFromClientProcess( + ulong size, + ulong address, + KMemoryPermission permission, + MemoryState state, + KPageTableBase srcPageTable, + bool send, + out ulong dst) + { + dst = 0; + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong endAddr = address + size; + + ulong addressTruncated = BitUtils.AlignDown(address, PageSize); + ulong addressRounded = BitUtils.AlignUp(address, PageSize); + ulong endAddrTruncated = BitUtils.AlignDown(endAddr, PageSize); + ulong endAddrRounded = BitUtils.AlignUp(endAddr, PageSize); + + ulong neededSize = endAddrRounded - addressTruncated; + + ulong neededPagesCount = neededSize / PageSize; + + ulong regionPagesCount = (AliasRegionEnd - AliasRegionStart) / PageSize; + + ulong va = 0; + + for (int unit = _mappingUnitSizes.Length - 1; unit >= 0 && va == 0; unit--) + { + int alignment = _mappingUnitSizes[unit]; + + va = AllocateVa(AliasRegionStart, regionPagesCount, neededPagesCount, alignment); + } + + if (va == 0) + { + return KernelResult.OutOfVaSpace; + } + + ulong dstFirstPagePa = 0; + ulong dstLastPagePa = 0; + ulong currentVa = va; + + using var _ = new OnScopeExit(() => + { + if (dstFirstPagePa != 0) + { + Context.MemoryManager.DecrementPagesReferenceCount(dstFirstPagePa, 1); + } + + if (dstLastPagePa != 0) + { + Context.MemoryManager.DecrementPagesReferenceCount(dstLastPagePa, 1); + } + }); + + void CleanUpForError() + { + if (currentVa != va) + { + Unmap(va, (currentVa - va) / PageSize); + } + } + + // Is the first page address aligned? + // If not, allocate a new page and copy the unaligned chunck. + if (addressTruncated < addressRounded) + { + dstFirstPagePa = GetMemoryRegionManager().AllocatePagesContiguous(Context, 1, _allocateFromBack); + + if (dstFirstPagePa == 0) + { + CleanUpForError(); + + return KernelResult.OutOfMemory; + } + } + + // Is the last page end address aligned? + // If not, allocate a new page and copy the unaligned chunck. + if (endAddrTruncated < endAddrRounded && (addressTruncated == addressRounded || addressTruncated < endAddrTruncated)) + { + dstLastPagePa = GetMemoryRegionManager().AllocatePagesContiguous(Context, 1, _allocateFromBack); + + if (dstLastPagePa == 0) + { + CleanUpForError(); + + return KernelResult.OutOfMemory; + } + } + + if (dstFirstPagePa != 0) + { + ulong firstPageFillAddress = dstFirstPagePa; + ulong unusedSizeAfter; + + if (send) + { + ulong unusedSizeBefore = address - addressTruncated; + + Context.Memory.Fill(GetDramAddressFromPa(dstFirstPagePa), unusedSizeBefore, (byte)_ipcFillValue); + + ulong copySize = addressRounded <= endAddr ? addressRounded - address : size; + var data = srcPageTable.GetReadOnlySequence(addressTruncated + unusedSizeBefore, (int)copySize); + + ((IWritableBlock)Context.Memory).Write(GetDramAddressFromPa(dstFirstPagePa + unusedSizeBefore), data); + + firstPageFillAddress += unusedSizeBefore + copySize; + + unusedSizeAfter = addressRounded > endAddr ? addressRounded - endAddr : 0; + } + else + { + unusedSizeAfter = PageSize; + } + + if (unusedSizeAfter != 0) + { + Context.Memory.Fill(GetDramAddressFromPa(firstPageFillAddress), unusedSizeAfter, (byte)_ipcFillValue); + } + + Result result = MapPages(currentVa, 1, dstFirstPagePa, permission, MemoryMapFlags.Private); + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + + currentVa += PageSize; + } + + if (endAddrTruncated > addressRounded) + { + ulong alignedSize = endAddrTruncated - addressRounded; + + Result result; + + if (srcPageTable.UsesPrivateAllocations) + { + result = MapForeign(srcPageTable.GetHostRegions(addressRounded, alignedSize), currentVa, alignedSize); + } + else + { + KPageList pageList = new(); + srcPageTable.GetPhysicalRegions(addressRounded, alignedSize, pageList); + + result = MapPages(currentVa, pageList, permission, MemoryMapFlags.None); + } + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + + currentVa += alignedSize; + } + + if (dstLastPagePa != 0) + { + ulong lastPageFillAddr = dstLastPagePa; + ulong unusedSizeAfter; + + if (send) + { + ulong copySize = endAddr - endAddrTruncated; + var data = srcPageTable.GetReadOnlySequence(endAddrTruncated, (int)copySize); + + ((IWritableBlock)Context.Memory).Write(GetDramAddressFromPa(dstLastPagePa), data); + + lastPageFillAddr += copySize; + + unusedSizeAfter = PageSize - copySize; + } + else + { + unusedSizeAfter = PageSize; + } + + Context.Memory.Fill(GetDramAddressFromPa(lastPageFillAddr), unusedSizeAfter, (byte)_ipcFillValue); + + Result result = MapPages(currentVa, 1, dstLastPagePa, permission, MemoryMapFlags.Private); + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + } + + _blockManager.InsertBlock(va, neededPagesCount, state, permission); + + dst = va + (address - addressTruncated); + + return Result.Success; + } + + public Result UnmapNoAttributeIfStateEquals(ulong address, ulong size, MemoryState state) + { + if (AddrSpaceStart > address) + { + return KernelResult.InvalidMemState; + } + + ulong endAddr = address + size; + + if (endAddr <= address || endAddr - 1 > AddrSpaceEnd - 1) + { + return KernelResult.InvalidMemState; + } + + lock (_blockManager) + { + if (CheckRange( + address, + size, + MemoryState.Mask, + state, + KMemoryPermission.Read, + KMemoryPermission.Read, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _)) + { + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + ulong addressTruncated = BitUtils.AlignDown(address, PageSize); + ulong addressRounded = BitUtils.AlignUp(address, PageSize); + ulong endAddrTruncated = BitUtils.AlignDown(endAddr, PageSize); + ulong endAddrRounded = BitUtils.AlignUp(endAddr, PageSize); + + ulong pagesCount = (endAddrRounded - addressTruncated) / PageSize; + + Result result = Unmap(addressTruncated, pagesCount); + + if (result == Result.Success) + { + _blockManager.InsertBlock(addressTruncated, pagesCount, MemoryState.Unmapped); + } + + return result; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public Result UnmapIpcRestorePermission(ulong address, ulong size, MemoryState state) + { + ulong endAddr = address + size; + + ulong addressRounded = BitUtils.AlignUp(address, PageSize); + ulong addressTruncated = BitUtils.AlignDown(address, PageSize); + ulong endAddrRounded = BitUtils.AlignUp(endAddr, PageSize); + ulong endAddrTruncated = BitUtils.AlignDown(endAddr, PageSize); + + ulong pagesCount = addressRounded < endAddrTruncated ? (endAddrTruncated - addressRounded) / PageSize : 0; + + if (pagesCount == 0) + { + return Result.Success; + } + + MemoryState stateMask; + + switch (state) + { + case MemoryState.IpcBuffer0: + stateMask = MemoryState.IpcSendAllowedType0; + break; + case MemoryState.IpcBuffer1: + stateMask = MemoryState.IpcSendAllowedType1; + break; + case MemoryState.IpcBuffer3: + stateMask = MemoryState.IpcSendAllowedType3; + break; + default: + return KernelResult.InvalidCombination; + } + + MemoryAttribute attributeMask = + MemoryAttribute.Borrowed | + MemoryAttribute.IpcMapped | + MemoryAttribute.Uncached; + + if (state == MemoryState.IpcBuffer0) + { + attributeMask |= MemoryAttribute.DeviceMapped; + } + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + // Anything on the client side should see this memory as modified. + SignalMemoryTracking(addressTruncated, endAddrRounded - addressTruncated, true); + + lock (_blockManager) + { + foreach (KMemoryInfo info in IterateOverRange(addressRounded, endAddrTruncated)) + { + // Check if the block state matches what we expect. + if ((info.State & stateMask) != stateMask || + (info.Attribute & attributeMask) != MemoryAttribute.IpcMapped) + { + return KernelResult.InvalidMemState; + } + + if (info.Permission != info.SourcePermission && info.IpcRefCount == 1) + { + ulong blockAddress = GetAddrInRange(info, addressRounded); + ulong blockSize = GetSizeInRange(info, addressRounded, endAddrTruncated); + + ulong blockPagesCount = blockSize / PageSize; + + Result result = Reprotect(blockAddress, blockPagesCount, info.SourcePermission); + + if (result != Result.Success) + { + return result; + } + } + } + + _blockManager.InsertBlock(addressRounded, pagesCount, RestoreIpcMappingPermissions); + + return Result.Success; + } + } + + private static void SetIpcMappingPermissions(KMemoryBlock block, KMemoryPermission permission) + { + block.SetIpcMappingPermission(permission); + } + + private static void RestoreIpcMappingPermissions(KMemoryBlock block, KMemoryPermission permission) + { + block.RestoreIpcMappingPermission(); + } + + public Result GetPagesIfStateEquals( + ulong address, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + KMemoryPermission permissionMask, + KMemoryPermission permissionExpected, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + KPageList pageList) + { + if (!InsideAddrSpace(address, size)) + { + return KernelResult.InvalidMemState; + } + + lock (_blockManager) + { + if (CheckRange( + address, + size, + stateMask | MemoryState.IsPoolAllocated, + stateExpected | MemoryState.IsPoolAllocated, + permissionMask, + permissionExpected, + attributeMask, + attributeExpected, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _)) + { + GetPhysicalRegions(address, size, pageList); + + return Result.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public Result BorrowIpcBuffer(ulong address, ulong size) + { + return SetAttributesAndChangePermission( + address, + size, + MemoryState.IpcBufferAllowed, + MemoryState.IpcBufferAllowed, + KMemoryPermission.Mask, + KMemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + KMemoryPermission.None, + MemoryAttribute.Borrowed); + } + + public Result BorrowTransferMemory(KPageList pageList, ulong address, ulong size, KMemoryPermission permission) + { + return SetAttributesAndChangePermission( + address, + size, + MemoryState.TransferMemoryAllowed, + MemoryState.TransferMemoryAllowed, + KMemoryPermission.Mask, + KMemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + permission, + MemoryAttribute.Borrowed, + pageList); + } + + public Result BorrowCodeMemory(KPageList pageList, ulong address, ulong size) + { + return SetAttributesAndChangePermission( + address, + size, + MemoryState.CodeMemoryAllowed, + MemoryState.CodeMemoryAllowed, + KMemoryPermission.Mask, + KMemoryPermission.ReadAndWrite, + MemoryAttribute.Mask, + MemoryAttribute.None, + KMemoryPermission.None, + MemoryAttribute.Borrowed, + pageList); + } + + private Result SetAttributesAndChangePermission( + ulong address, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + KMemoryPermission permissionMask, + KMemoryPermission permissionExpected, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + KMemoryPermission newPermission, + MemoryAttribute attributeSetMask, + KPageList pageList = null) + { + if (address + size <= address || !InsideAddrSpace(address, size)) + { + return KernelResult.InvalidMemState; + } + + lock (_blockManager) + { + if (CheckRange( + address, + size, + stateMask | MemoryState.IsPoolAllocated, + stateExpected | MemoryState.IsPoolAllocated, + permissionMask, + permissionExpected, + attributeMask, + attributeExpected, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState oldState, + out KMemoryPermission oldPermission, + out MemoryAttribute oldAttribute)) + { + ulong pagesCount = size / PageSize; + + if (pageList != null) + { + GetPhysicalRegions(address, pagesCount * PageSize, pageList); + } + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + if (newPermission == KMemoryPermission.None) + { + newPermission = oldPermission; + } + + if (newPermission != oldPermission) + { + Result result = Reprotect(address, pagesCount, newPermission); + + if (result != Result.Success) + { + return result; + } + } + + MemoryAttribute newAttribute = oldAttribute | attributeSetMask; + + _blockManager.InsertBlock(address, pagesCount, oldState, newPermission, newAttribute); + + return Result.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + public Result UnborrowIpcBuffer(ulong address, ulong size) + { + return ClearAttributesAndChangePermission( + address, + size, + MemoryState.IpcBufferAllowed, + MemoryState.IpcBufferAllowed, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + KMemoryPermission.ReadAndWrite, + MemoryAttribute.Borrowed); + } + + public Result UnborrowTransferMemory(ulong address, ulong size, KPageList pageList) + { + return ClearAttributesAndChangePermission( + address, + size, + MemoryState.TransferMemoryAllowed, + MemoryState.TransferMemoryAllowed, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + KMemoryPermission.ReadAndWrite, + MemoryAttribute.Borrowed, + pageList); + } + + public Result UnborrowCodeMemory(ulong address, ulong size, KPageList pageList) + { + return ClearAttributesAndChangePermission( + address, + size, + MemoryState.CodeMemoryAllowed, + MemoryState.CodeMemoryAllowed, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.Borrowed, + KMemoryPermission.ReadAndWrite, + MemoryAttribute.Borrowed, + pageList); + } + + private Result ClearAttributesAndChangePermission( + ulong address, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + KMemoryPermission permissionMask, + KMemoryPermission permissionExpected, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + KMemoryPermission newPermission, + MemoryAttribute attributeClearMask, + KPageList pageList = null) + { + if (address + size <= address || !InsideAddrSpace(address, size)) + { + return KernelResult.InvalidMemState; + } + + lock (_blockManager) + { + if (CheckRange( + address, + size, + stateMask | MemoryState.IsPoolAllocated, + stateExpected | MemoryState.IsPoolAllocated, + permissionMask, + permissionExpected, + attributeMask, + attributeExpected, + MemoryAttribute.IpcAndDeviceMapped, + out MemoryState oldState, + out KMemoryPermission oldPermission, + out MemoryAttribute oldAttribute)) + { + ulong pagesCount = size / PageSize; + + if (pageList != null) + { + KPageList currentPageList = new(); + + GetPhysicalRegions(address, pagesCount * PageSize, currentPageList); + + if (!currentPageList.IsEqual(pageList)) + { + return KernelResult.InvalidMemRange; + } + } + + if (!_slabManager.CanAllocate(MaxBlocksNeededForInsertion)) + { + return KernelResult.OutOfResource; + } + + if (newPermission == KMemoryPermission.None) + { + newPermission = oldPermission; + } + + if (newPermission != oldPermission) + { + Result result = Reprotect(address, pagesCount, newPermission); + + if (result != Result.Success) + { + return result; + } + } + + MemoryAttribute newAttribute = oldAttribute & ~attributeClearMask; + + _blockManager.InsertBlock(address, pagesCount, oldState, newPermission, newAttribute); + + return Result.Success; + } + else + { + return KernelResult.InvalidMemState; + } + } + } + + private static ulong GetAddrInRange(KMemoryInfo info, ulong start) + { + if (info.Address < start) + { + return start; + } + + return info.Address; + } + + private static ulong GetSizeInRange(KMemoryInfo info, ulong start, ulong end) + { + ulong endAddr = info.Size + info.Address; + ulong size = info.Size; + + if (info.Address < start) + { + size -= start - info.Address; + } + + if (endAddr > end) + { + size -= endAddr - end; + } + + return size; + } + + private bool IsUnmapped(ulong address, ulong size) + { + return CheckRange( + address, + size, + MemoryState.Mask, + MemoryState.Unmapped, + KMemoryPermission.Mask, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + MemoryAttribute.IpcAndDeviceMapped, + out _, + out _, + out _); + } + + private bool CheckRange( + ulong address, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + KMemoryPermission permissionMask, + KMemoryPermission permissionExpected, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected, + MemoryAttribute attributeIgnoreMask, + out MemoryState outState, + out KMemoryPermission outPermission, + out MemoryAttribute outAttribute) + { + ulong endAddr = address + size; + + KMemoryBlock currBlock = _blockManager.FindBlock(address); + + KMemoryInfo info = currBlock.GetInfo(); + + MemoryState firstState = info.State; + KMemoryPermission firstPermission = info.Permission; + MemoryAttribute firstAttribute = info.Attribute; + + do + { + info = currBlock.GetInfo(); + + // Check if the block state matches what we expect. + if (firstState != info.State || + firstPermission != info.Permission || + (info.Attribute & attributeMask) != attributeExpected || + (firstAttribute | attributeIgnoreMask) != (info.Attribute | attributeIgnoreMask) || + (firstState & stateMask) != stateExpected || + (firstPermission & permissionMask) != permissionExpected) + { + outState = MemoryState.Unmapped; + outPermission = KMemoryPermission.None; + outAttribute = MemoryAttribute.None; + + return false; + } + } + while (info.Address + info.Size - 1 < endAddr - 1 && (currBlock = currBlock.Successor) != null); + + outState = firstState; + outPermission = firstPermission; + outAttribute = firstAttribute & ~attributeIgnoreMask; + + return true; + } + + private bool CheckRange( + ulong address, + ulong size, + MemoryState stateMask, + MemoryState stateExpected, + KMemoryPermission permissionMask, + KMemoryPermission permissionExpected, + MemoryAttribute attributeMask, + MemoryAttribute attributeExpected) + { + foreach (KMemoryInfo info in IterateOverRange(address, address + size)) + { + // Check if the block state matches what we expect. + if ((info.State & stateMask) != stateExpected || + (info.Permission & permissionMask) != permissionExpected || + (info.Attribute & attributeMask) != attributeExpected) + { + return false; + } + } + + return true; + } + + private IEnumerable IterateOverRange(ulong start, ulong end) + { + KMemoryBlock currBlock = _blockManager.FindBlock(start); + + KMemoryInfo info; + + do + { + info = currBlock.GetInfo(); + + yield return info; + } + while (info.Address + info.Size - 1 < end - 1 && (currBlock = currBlock.Successor) != null); + } + + private ulong AllocateVa(ulong regionStart, ulong regionPagesCount, ulong neededPagesCount, int alignment) + { + ulong address = 0; + + ulong regionEndAddr = regionStart + regionPagesCount * PageSize; + + ulong reservedPagesCount = _isKernel ? 1UL : 4UL; + + if (_aslrEnabled) + { + ulong totalNeededSize = (reservedPagesCount + neededPagesCount) * PageSize; + + ulong remainingPages = regionPagesCount - neededPagesCount; + + ulong aslrMaxOffset = ((remainingPages + reservedPagesCount) * PageSize) / (ulong)alignment; + + for (int attempt = 0; attempt < 8; attempt++) + { + ulong aslrAddress = BitUtils.AlignDown(regionStart + GetRandomValue(0, aslrMaxOffset) * (ulong)alignment, (ulong)alignment); + ulong aslrEndAddr = aslrAddress + totalNeededSize; + + KMemoryInfo info = _blockManager.FindBlock(aslrAddress).GetInfo(); + + if (info.State != MemoryState.Unmapped) + { + continue; + } + + ulong currBaseAddr = info.Address + reservedPagesCount * PageSize; + ulong currEndAddr = info.Address + info.Size; + + if (aslrAddress >= regionStart && + aslrAddress >= currBaseAddr && + aslrEndAddr - 1 <= regionEndAddr - 1 && + aslrEndAddr - 1 <= currEndAddr - 1) + { + address = aslrAddress; + break; + } + } + + if (address == 0) + { + ulong aslrPage = GetRandomValue(0, aslrMaxOffset); + + address = FindFirstFit( + regionStart + aslrPage * PageSize, + regionPagesCount - aslrPage, + neededPagesCount, + alignment, + 0, + reservedPagesCount); + } + } + + if (address == 0) + { + address = FindFirstFit( + regionStart, + regionPagesCount, + neededPagesCount, + alignment, + 0, + reservedPagesCount); + } + + return address; + } + + private ulong FindFirstFit( + ulong regionStart, + ulong regionPagesCount, + ulong neededPagesCount, + int alignment, + ulong reservedStart, + ulong reservedPagesCount) + { + ulong reservedSize = reservedPagesCount * PageSize; + + ulong totalNeededSize = reservedSize + neededPagesCount * PageSize; + + ulong regionEndAddr = (regionStart + regionPagesCount * PageSize) - 1; + + KMemoryBlock currBlock = _blockManager.FindBlock(regionStart); + + KMemoryInfo info = currBlock.GetInfo(); + + while (regionEndAddr >= info.Address) + { + if (info.State == MemoryState.Unmapped) + { + ulong currBaseAddr = info.Address <= regionStart ? regionStart : info.Address; + ulong currEndAddr = info.Address + info.Size - 1; + + currBaseAddr += reservedSize; + + ulong address = BitUtils.AlignDown(currBaseAddr, (ulong)alignment) + reservedStart; + + if (currBaseAddr > address) + { + address += (ulong)alignment; + } + + ulong allocationEndAddr = address + totalNeededSize - 1; + + if (info.Address <= address && + address < allocationEndAddr && + allocationEndAddr <= regionEndAddr && + allocationEndAddr <= currEndAddr) + { + return address; + } + } + + currBlock = currBlock.Successor; + + if (currBlock == null) + { + break; + } + + info = currBlock.GetInfo(); + } + + return 0; + } + + public bool CanContain(ulong address, ulong size, MemoryState state) + { + ulong endAddr = address + size; + + ulong regionBaseAddr = GetBaseAddress(state); + ulong regionEndAddr = regionBaseAddr + GetSize(state); + + bool InsideRegion() + { + return regionBaseAddr <= address && + endAddr > address && + endAddr - 1 <= regionEndAddr - 1; + } + + bool OutsideHeapRegion() + { + return endAddr <= HeapRegionStart || address >= HeapRegionEnd; + } + + bool OutsideAliasRegion() + { + return endAddr <= AliasRegionStart || address >= AliasRegionEnd; + } + + switch (state) + { + case MemoryState.Io: + case MemoryState.Normal: + case MemoryState.CodeStatic: + case MemoryState.CodeMutable: + case MemoryState.SharedMemory: + case MemoryState.ModCodeStatic: + case MemoryState.ModCodeMutable: + case MemoryState.Stack: + case MemoryState.ThreadLocal: + case MemoryState.TransferMemoryIsolated: + case MemoryState.TransferMemory: + case MemoryState.ProcessMemory: + case MemoryState.CodeReadOnly: + case MemoryState.CodeWritable: + return InsideRegion() && OutsideHeapRegion() && OutsideAliasRegion(); + + case MemoryState.Heap: + return InsideRegion() && OutsideAliasRegion(); + + case MemoryState.IpcBuffer0: + case MemoryState.IpcBuffer1: + case MemoryState.IpcBuffer3: + return InsideRegion() && OutsideHeapRegion(); + + case MemoryState.KernelStack: + return InsideRegion(); + } + + throw new ArgumentException($"Invalid state value \"{state}\"."); + } + + private ulong GetBaseAddress(MemoryState state) + { + switch (state) + { + case MemoryState.Io: + case MemoryState.Normal: + case MemoryState.ThreadLocal: + return TlsIoRegionStart; + + case MemoryState.CodeStatic: + case MemoryState.CodeMutable: + case MemoryState.SharedMemory: + case MemoryState.ModCodeStatic: + case MemoryState.ModCodeMutable: + case MemoryState.TransferMemoryIsolated: + case MemoryState.TransferMemory: + case MemoryState.ProcessMemory: + case MemoryState.CodeReadOnly: + case MemoryState.CodeWritable: + return GetAddrSpaceBaseAddr(); + + case MemoryState.Heap: + return HeapRegionStart; + + case MemoryState.IpcBuffer0: + case MemoryState.IpcBuffer1: + case MemoryState.IpcBuffer3: + return AliasRegionStart; + + case MemoryState.Stack: + return StackRegionStart; + + case MemoryState.KernelStack: + return AddrSpaceStart; + } + + throw new ArgumentException($"Invalid state value \"{state}\"."); + } + + private ulong GetSize(MemoryState state) + { + switch (state) + { + case MemoryState.Io: + case MemoryState.Normal: + case MemoryState.ThreadLocal: + return TlsIoRegionEnd - TlsIoRegionStart; + + case MemoryState.CodeStatic: + case MemoryState.CodeMutable: + case MemoryState.SharedMemory: + case MemoryState.ModCodeStatic: + case MemoryState.ModCodeMutable: + case MemoryState.TransferMemoryIsolated: + case MemoryState.TransferMemory: + case MemoryState.ProcessMemory: + case MemoryState.CodeReadOnly: + case MemoryState.CodeWritable: + return GetAddrSpaceSize(); + + case MemoryState.Heap: + return HeapRegionEnd - HeapRegionStart; + + case MemoryState.IpcBuffer0: + case MemoryState.IpcBuffer1: + case MemoryState.IpcBuffer3: + return AliasRegionEnd - AliasRegionStart; + + case MemoryState.Stack: + return StackRegionEnd - StackRegionStart; + + case MemoryState.KernelStack: + return AddrSpaceEnd - AddrSpaceStart; + } + + throw new ArgumentException($"Invalid state value \"{state}\"."); + } + + public ulong GetAddrSpaceBaseAddr() + { + return AslrRegionStart; + } + + public ulong GetAddrSpaceSize() + { + return AslrRegionEnd - AslrRegionStart; + } + + private static ulong GetDramAddressFromPa(ulong pa) + { + return pa - DramMemoryMap.DramBase; + } + + protected KMemoryRegionManager GetMemoryRegionManager() + { + return Context.MemoryManager.MemoryRegions[(int)_memRegion]; + } + + public ulong GetMmUsedPages() + { + lock (_blockManager) + { + return BitUtils.DivRoundUp(GetMmUsedSize(), PageSize); + } + } + + private ulong GetMmUsedSize() + { + return (ulong)(_blockManager.BlocksCount * KMemoryBlockSize); + } + + public bool IsInvalidRegion(ulong address, ulong size) + { + return address + size - 1 > GetAddrSpaceBaseAddr() + GetAddrSpaceSize() - 1; + } + + public bool InsideAddrSpace(ulong address, ulong size) + { + return AddrSpaceStart <= address && address + size - 1 <= AddrSpaceEnd - 1; + } + + public bool InsideAliasRegion(ulong address, ulong size) + { + return address + size > AliasRegionStart && AliasRegionEnd > address; + } + + public bool InsideHeapRegion(ulong address, ulong size) + { + return address + size > HeapRegionStart && HeapRegionEnd > address; + } + + public bool InsideStackRegion(ulong address, ulong size) + { + return address + size > StackRegionStart && StackRegionEnd > address; + } + + public bool OutsideAliasRegion(ulong address, ulong size) + { + return AliasRegionStart > address || address + size - 1 > AliasRegionEnd - 1; + } + + public bool OutsideAddrSpace(ulong address, ulong size) + { + return AddrSpaceStart > address || address + size - 1 > AddrSpaceEnd - 1; + } + + public bool OutsideStackRegion(ulong address, ulong size) + { + return StackRegionStart > address || address + size - 1 > StackRegionEnd - 1; + } + + /// + /// Gets the host regions that make up the given virtual address region. + /// If any part of the virtual region is unmapped, null is returned. + /// + /// Virtual address of the range + /// Size of the range + /// The host regions + /// Throw for unhandled invalid or unmapped memory accesses + protected abstract IEnumerable GetHostRegions(ulong va, ulong size); + + /// + /// Gets the physical regions that make up the given virtual address region. + /// If any part of the virtual region is unmapped, null is returned. + /// + /// Virtual address of the range + /// Size of the range + /// Page list where the ranges will be added + protected abstract void GetPhysicalRegions(ulong va, ulong size, KPageList pageList); + + /// + /// Gets a read-only sequence of data from CPU mapped memory. + /// + /// + /// Allows reading non-contiguous memory without first copying it to a newly allocated single contiguous block. + /// + /// Virtual address of the data + /// Size of the data + /// A read-only sequence of the data + /// Throw for unhandled invalid or unmapped memory accesses + protected abstract ReadOnlySequence GetReadOnlySequence(ulong va, int size); + + /// + /// Gets a read-only span of data from CPU mapped memory. + /// + /// + /// This may perform a allocation if the data is not contiguous in memory. + /// For this reason, the span is read-only, you can't modify the data. + /// + /// Virtual address of the data + /// Size of the data + /// A read-only span of the data + /// Throw for unhandled invalid or unmapped memory accesses + protected abstract ReadOnlySpan GetSpan(ulong va, int size); + + /// + /// Maps a new memory region with the contents of a existing memory region. + /// + /// Source memory region where the data will be taken from + /// Destination memory region to map + /// Number of pages to map + /// Current protection of the source memory region + /// Desired protection for the destination memory region + /// Result of the mapping operation + protected abstract Result MapMemory(ulong src, ulong dst, ulong pagesCount, KMemoryPermission oldSrcPermission, KMemoryPermission newDstPermission); + + /// + /// Unmaps a region of memory that was previously mapped with . + /// + /// Destination memory region to be unmapped + /// Source memory region that was originally remapped + /// Number of pages to unmap + /// Current protection of the destination memory region + /// Desired protection of the source memory region + /// Result of the unmapping operation + protected abstract Result UnmapMemory(ulong dst, ulong src, ulong pagesCount, KMemoryPermission oldDstPermission, KMemoryPermission newSrcPermission); + + /// + /// Maps a region of memory into the specified physical memory region. + /// + /// Destination virtual address that should be mapped + /// Number of pages to map + /// Physical address where the pages should be mapped. May be ignored if aliasing is not supported + /// Permission of the region to be mapped + /// Flags controlling the memory map operation + /// Indicate if the pages should be filled with the value + /// The value used to fill pages when is set to true + /// Result of the mapping operation + protected abstract Result MapPages( + ulong dstVa, + ulong pagesCount, + ulong srcPa, + KMemoryPermission permission, + MemoryMapFlags flags, + bool shouldFillPages = false, + byte fillValue = 0); + + /// + /// Maps a region of memory into the specified physical memory region. + /// + /// Destination virtual address that should be mapped + /// List of physical memory pages where the pages should be mapped. May be ignored if aliasing is not supported + /// Permission of the region to be mapped + /// Flags controlling the memory map operation + /// Indicate if the pages should be filled with the value + /// The value used to fill pages when is set to true + /// Result of the mapping operation + protected abstract Result MapPages( + ulong address, + KPageList pageList, + KMemoryPermission permission, + MemoryMapFlags flags, + bool shouldFillPages = false, + byte fillValue = 0); + + /// + /// Maps pages into an arbitrary host memory location. + /// + /// Host regions to be mapped into the specified virtual memory region + /// Destination virtual address of the range on this page table + /// Size of the range + /// Result of the mapping operation + protected abstract Result MapForeign(IEnumerable regions, ulong va, ulong size); + + /// + /// Unmaps a region of memory that was previously mapped with one of the page mapping methods. + /// + /// Virtual address of the region to unmap + /// Number of pages to unmap + /// Result of the unmapping operation + protected abstract Result Unmap(ulong address, ulong pagesCount); + + /// + /// Changes the permissions of a given virtual memory region. + /// + /// Virtual address of the region to have the permission changes + /// Number of pages to have their permissions changed + /// New permission + /// Result of the permission change operation + protected abstract Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission); + + /// + /// Changes the permissions of a given virtual memory region, while also flushing the cache. + /// + /// Virtual address of the region to have the permission changes + /// Number of pages to have their permissions changed + /// New permission + /// Result of the permission change operation + protected abstract Result ReprotectAndFlush(ulong address, ulong pagesCount, KMemoryPermission permission); + + /// + /// Alerts the memory tracking that a given region has been read from or written to. + /// This should be called before read/write is performed. + /// + /// Virtual address of the region + /// Size of the region + protected abstract void SignalMemoryTracking(ulong va, ulong size, bool write); + + /// + /// Writes data to CPU mapped memory, with write tracking. + /// + /// Virtual address to write the data into + /// Data to be written + /// Throw for unhandled invalid or unmapped memory accesses + protected abstract void Write(ulong va, ReadOnlySequence data); + + /// + /// Writes data to CPU mapped memory, with write tracking. + /// + /// Virtual address to write the data into + /// Data to be written + /// Throw for unhandled invalid or unmapped memory accesses + protected abstract void Write(ulong va, ReadOnlySpan data); + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KScopedPageList.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KScopedPageList.cs new file mode 100644 index 00000000..d9370165 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KScopedPageList.cs @@ -0,0 +1,27 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + struct KScopedPageList : IDisposable + { + private readonly KMemoryManager _manager; + private KPageList _pageList; + + public KScopedPageList(KMemoryManager manager, KPageList pageList) + { + _manager = manager; + _pageList = pageList; + pageList.IncrementPagesReferenceCount(manager); + } + + public void SignalSuccess() + { + _pageList = null; + } + + public readonly void Dispose() + { + _pageList?.DecrementPagesReferenceCount(_manager); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs new file mode 100644 index 00000000..e593a7e1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs @@ -0,0 +1,64 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.Horizon.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KSharedMemory : KAutoObject + { + private readonly KPageList _pageList; + + private readonly ulong _ownerPid; + + private readonly KMemoryPermission _ownerPermission; + private readonly KMemoryPermission _userPermission; + + public KSharedMemory( + KernelContext context, + SharedMemoryStorage storage, + ulong ownerPid, + KMemoryPermission ownerPermission, + KMemoryPermission userPermission) : base(context) + { + _pageList = storage.GetPageList(); + _ownerPid = ownerPid; + _ownerPermission = ownerPermission; + _userPermission = userPermission; + } + + public Result MapIntoProcess( + KPageTableBase memoryManager, + ulong address, + ulong size, + KProcess process, + KMemoryPermission permission) + { + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize)) + { + return KernelResult.InvalidSize; + } + + KMemoryPermission expectedPermission = process.Pid == _ownerPid + ? _ownerPermission + : _userPermission; + + if (permission != expectedPermission) + { + return KernelResult.InvalidPermission; + } + + return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission); + } + + public Result UnmapFromProcess(KPageTableBase memoryManager, ulong address, ulong size, KProcess process) + { + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize)) + { + return KernelResult.InvalidSize; + } + + return memoryManager.UnmapPages(address, _pageList, MemoryState.SharedMemory); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs new file mode 100644 index 00000000..cd8c2e47 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KSlabHeap + { + private readonly LinkedList _items; + + public KSlabHeap(ulong pa, ulong itemSize, ulong size) + { + _items = new LinkedList(); + + int itemsCount = (int)(size / itemSize); + + for (int index = 0; index < itemsCount; index++) + { + _items.AddLast(pa); + + pa += itemSize; + } + } + + public bool TryGetItem(out ulong pa) + { + lock (_items) + { + if (_items.First != null) + { + pa = _items.First.Value; + + _items.RemoveFirst(); + + return true; + } + } + + pa = 0; + + return false; + } + + public void Free(ulong pa) + { + lock (_items) + { + _items.AddFirst(pa); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs new file mode 100644 index 00000000..9f64532e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs @@ -0,0 +1,130 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class KTransferMemory : KAutoObject + { + private KProcess _creator; + + // TODO: Remove when we no longer need to read it from the owner directly. + public KProcess Creator => _creator; + + private readonly KPageList _pageList; + + public ulong Address { get; private set; } + public ulong Size { get; private set; } + + public KMemoryPermission Permission { get; private set; } + + private bool _hasBeenInitialized; + private bool _isMapped; + + public KTransferMemory(KernelContext context) : base(context) + { + _pageList = new KPageList(); + } + + public KTransferMemory(KernelContext context, SharedMemoryStorage storage) : base(context) + { + _pageList = storage.GetPageList(); + Permission = KMemoryPermission.ReadAndWrite; + + _hasBeenInitialized = true; + _isMapped = false; + } + + public Result Initialize(ulong address, ulong size, KMemoryPermission permission) + { + KProcess creator = KernelStatic.GetCurrentProcess(); + + _creator = creator; + + Result result = creator.MemoryManager.BorrowTransferMemory(_pageList, address, size, permission); + + if (result != Result.Success) + { + return result; + } + + creator.IncrementReferenceCount(); + + Permission = permission; + Address = address; + Size = size; + _hasBeenInitialized = true; + _isMapped = false; + + return result; + } + + public Result MapIntoProcess( + KPageTableBase memoryManager, + ulong address, + ulong size, + KProcess process, + KMemoryPermission permission) + { + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, KPageTableBase.PageSize)) + { + return KernelResult.InvalidSize; + } + + if (permission != Permission || _isMapped) + { + return KernelResult.InvalidState; + } + + MemoryState state = Permission == KMemoryPermission.None ? MemoryState.TransferMemoryIsolated : MemoryState.TransferMemory; + + Result result = memoryManager.MapPages(address, _pageList, state, KMemoryPermission.ReadAndWrite); + + if (result == Result.Success) + { + _isMapped = true; + } + + return result; + } + + public Result UnmapFromProcess( + KPageTableBase memoryManager, + ulong address, + ulong size, + KProcess process) + { + if (_pageList.GetPagesCount() != BitUtils.DivRoundUp(size, (ulong)KPageTableBase.PageSize)) + { + return KernelResult.InvalidSize; + } + + MemoryState state = Permission == KMemoryPermission.None ? MemoryState.TransferMemoryIsolated : MemoryState.TransferMemory; + + Result result = memoryManager.UnmapPages(address, _pageList, state); + + if (result == Result.Success) + { + _isMapped = false; + } + + return result; + } + + protected override void Destroy() + { + if (_hasBeenInitialized) + { + if (!_isMapped && _creator.MemoryManager.UnborrowTransferMemory(Address, Size, _pageList) != Result.Success) + { + throw new InvalidOperationException("Unexpected failure restoring transfer memory attributes."); + } + + _creator.ResourceLimit?.Release(LimitableResource.TransferMemory, 1); + _creator.DecrementReferenceCount(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs new file mode 100644 index 00000000..e0fa60fa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs @@ -0,0 +1,21 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + [Flags] + enum MemoryAttribute : byte + { + None = 0, + Mask = 0xff, + + Borrowed = 1 << 0, + IpcMapped = 1 << 1, + DeviceMapped = 1 << 2, + Uncached = 1 << 3, + PermissionLocked = 1 << 4, + + IpcAndDeviceMapped = IpcMapped | DeviceMapped, + BorrowedAndIpcMapped = Borrowed | IpcMapped, + DeviceMappedAndUncached = DeviceMapped | Uncached, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryFillValue.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryFillValue.cs new file mode 100644 index 00000000..74a88b48 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryFillValue.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + enum MemoryFillValue : byte + { + Zero = 0, + Stack = 0x58, + Ipc = 0x59, + Heap = 0x5A, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs new file mode 100644 index 00000000..18784bf9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + enum MemoryRegion + { + Application = 0, + Applet = 1, + Service = 2, + NvServices = 3, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs new file mode 100644 index 00000000..da6fac63 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs @@ -0,0 +1,172 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + [Flags] + enum MemoryState : uint + { + Unmapped = 0x0, + Io = Mapped | 0x1, + Normal = Mapped | QueryPhysicalAddressAllowed | 0x2, + CodeStatic = ForceReadWritableByDebugSyscalls | + IpcSendAllowedType0 | + IpcSendAllowedType3 | + IpcSendAllowedType1 | + Mapped | + ProcessPermissionChangeAllowed | + QueryPhysicalAddressAllowed | + MapDeviceAllowed | + MapDeviceAlignedAllowed | + IsPoolAllocated | + MapProcessAllowed | + LinearMapped | + 0x3, + CodeMutable = PermissionChangeAllowed | + IpcSendAllowedType0 | + IpcSendAllowedType3 | + IpcSendAllowedType1 | + Mapped | + MapAllowed | + TransferMemoryAllowed | + QueryPhysicalAddressAllowed | + MapDeviceAllowed | + MapDeviceAlignedAllowed | + IpcBufferAllowed | + IsPoolAllocated | + MapProcessAllowed | + AttributeChangeAllowed | + CodeMemoryAllowed | + LinearMapped | + PermissionLockAllowed | + 0x4, + Heap = PermissionChangeAllowed | + IpcSendAllowedType0 | + IpcSendAllowedType3 | + IpcSendAllowedType1 | + Mapped | + MapAllowed | + TransferMemoryAllowed | + QueryPhysicalAddressAllowed | + MapDeviceAllowed | + MapDeviceAlignedAllowed | + IpcBufferAllowed | + IsPoolAllocated | + AttributeChangeAllowed | + CodeMemoryAllowed | + LinearMapped | + 0x5, + SharedMemory = Mapped | IsPoolAllocated | LinearMapped | 0x6, + ModCodeStatic = ForceReadWritableByDebugSyscalls | + IpcSendAllowedType0 | + IpcSendAllowedType3 | + IpcSendAllowedType1 | + Mapped | + ProcessPermissionChangeAllowed | + UnmapProcessCodeMemoryAllowed | + QueryPhysicalAddressAllowed | + MapDeviceAllowed | + MapDeviceAlignedAllowed | + IsPoolAllocated | + MapProcessAllowed | + LinearMapped | + 0x8, + ModCodeMutable = PermissionChangeAllowed | + IpcSendAllowedType0 | + IpcSendAllowedType3 | + IpcSendAllowedType1 | + Mapped | + MapAllowed | + UnmapProcessCodeMemoryAllowed | + TransferMemoryAllowed | + QueryPhysicalAddressAllowed | + MapDeviceAllowed | + MapDeviceAlignedAllowed | + IpcBufferAllowed | + IsPoolAllocated | + MapProcessAllowed | + AttributeChangeAllowed | + CodeMemoryAllowed | + LinearMapped | + PermissionLockAllowed | + 0x9, + IpcBuffer0 = IpcSendAllowedType0 | + IpcSendAllowedType3 | + IpcSendAllowedType1 | + Mapped | + QueryPhysicalAddressAllowed | + MapDeviceAllowed | + MapDeviceAlignedAllowed | + IsPoolAllocated | + LinearMapped | + 0xA, + Stack = IpcSendAllowedType0 | + IpcSendAllowedType3 | + IpcSendAllowedType1 | + Mapped | + QueryPhysicalAddressAllowed | + MapDeviceAllowed | + MapDeviceAlignedAllowed | + IsPoolAllocated | + LinearMapped | + 0xB, + ThreadLocal = Mapped | IsPoolAllocated | LinearMapped | 0xC, + TransferMemoryIsolated = IpcSendAllowedType0 | + IpcSendAllowedType3 | + IpcSendAllowedType1 | + Mapped | + QueryPhysicalAddressAllowed | + MapDeviceAllowed | + MapDeviceAlignedAllowed | + IsPoolAllocated | + AttributeChangeAllowed | + LinearMapped | + 0xD, + TransferMemory = IpcSendAllowedType3 | + IpcSendAllowedType1 | + Mapped | + QueryPhysicalAddressAllowed | + MapDeviceAllowed | + MapDeviceAlignedAllowed | + IsPoolAllocated | + LinearMapped | + 0xE, + ProcessMemory = IpcSendAllowedType3 | IpcSendAllowedType1 | Mapped | IsPoolAllocated | LinearMapped | 0xF, + Reserved = 0x10, + IpcBuffer1 = IpcSendAllowedType3 | + IpcSendAllowedType1 | + Mapped | + QueryPhysicalAddressAllowed | + MapDeviceAllowed | + MapDeviceAlignedAllowed | + IsPoolAllocated | + LinearMapped | + 0x11, + IpcBuffer3 = IpcSendAllowedType3 | Mapped | QueryPhysicalAddressAllowed | MapDeviceAllowed | IsPoolAllocated | LinearMapped | 0x12, + KernelStack = Mapped | 0x13, + CodeReadOnly = ForceReadWritableByDebugSyscalls | Mapped | IsPoolAllocated | LinearMapped | 0x14, + CodeWritable = Mapped | IsPoolAllocated | LinearMapped | 0x15, + UserMask = 0xFF, + Mask = 0xFFFFFFFF, + + PermissionChangeAllowed = 1 << 8, + ForceReadWritableByDebugSyscalls = 1 << 9, + IpcSendAllowedType0 = 1 << 10, + IpcSendAllowedType3 = 1 << 11, + IpcSendAllowedType1 = 1 << 12, + Mapped = 1 << 13, + ProcessPermissionChangeAllowed = 1 << 14, + MapAllowed = 1 << 15, + UnmapProcessCodeMemoryAllowed = 1 << 16, + TransferMemoryAllowed = 1 << 17, + QueryPhysicalAddressAllowed = 1 << 18, + MapDeviceAllowed = 1 << 19, + MapDeviceAlignedAllowed = 1 << 20, + IpcBufferAllowed = 1 << 21, + IsPoolAllocated = 1 << 22, + MapProcessAllowed = 1 << 23, + AttributeChangeAllowed = 1 << 24, + CodeMemoryAllowed = 1 << 25, + LinearMapped = 1 << 26, + PermissionLockAllowed = 1 << 27, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/SharedMemoryStorage.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/SharedMemoryStorage.cs new file mode 100644 index 00000000..54ccad0a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/SharedMemoryStorage.cs @@ -0,0 +1,49 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Memory +{ + class SharedMemoryStorage + { + private readonly KernelContext _context; + private readonly KPageList _pageList; + private readonly ulong _size; + + public SharedMemoryStorage(KernelContext context, KPageList pageList) + { + _context = context; + _pageList = pageList; + _size = pageList.GetPagesCount() * KPageTableBase.PageSize; + + foreach (KPageNode pageNode in pageList) + { + ulong address = pageNode.Address - DramMemoryMap.DramBase; + ulong size = pageNode.PagesCount * KPageTableBase.PageSize; + context.CommitMemory(address, size); + } + } + + public void ZeroFill() + { + for (ulong offset = 0; offset < _size; offset += sizeof(ulong)) + { + GetRef(offset) = 0; + } + } + + public ref T GetRef(ulong offset) where T : unmanaged + { + if (_pageList.Nodes.Count == 1) + { + ulong address = _pageList.Nodes.First.Value.Address - DramMemoryMap.DramBase; + return ref _context.Memory.GetRef(address + offset); + } + + throw new NotImplementedException("Non-contiguous shared memory is not yet supported."); + } + + public KPageList GetPageList() + { + return _pageList; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs new file mode 100644 index 00000000..dd133ee1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs @@ -0,0 +1,22 @@ +using System.Numerics; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + static class CapabilityExtensions + { + public static CapabilityType GetCapabilityType(this uint cap) + { + return (CapabilityType)(((cap + 1) & ~cap) - 1); + } + + public static uint GetFlag(this CapabilityType type) + { + return (uint)type + 1; + } + + public static uint GetId(this CapabilityType type) + { + return (uint)BitOperations.TrailingZeroCount(type.GetFlag()); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs new file mode 100644 index 00000000..9812eea0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + enum CapabilityType : uint + { + CorePriority = (1u << 3) - 1, + SyscallMask = (1u << 4) - 1, + MapRange = (1u << 6) - 1, + MapIoPage = (1u << 7) - 1, + MapRegion = (1u << 10) - 1, + InterruptPair = (1u << 11) - 1, + ProgramType = (1u << 13) - 1, + KernelVersion = (1u << 14) - 1, + HandleTable = (1u << 15) - 1, + DebugFlags = (1u << 16) - 1, + + Invalid = 0u, + Padding = ~0u, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs new file mode 100644 index 00000000..7578f1d2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs @@ -0,0 +1,465 @@ +using Ryujinx.HLE.HOS.Diagnostics.Demangler; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.Loaders.Elf; +using Ryujinx.Memory; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class HleProcessDebugger + { + private const int Mod0 = 'M' << 0 | 'O' << 8 | 'D' << 16 | '0' << 24; + + private readonly KProcess _owner; + + private class Image + { + public ulong BaseAddress { get; } + public ulong Size { get; } + public ulong EndAddress => BaseAddress + Size; + + public ElfSymbol[] Symbols { get; } + + public Image(ulong baseAddress, ulong size, ElfSymbol[] symbols) + { + BaseAddress = baseAddress; + Size = size; + Symbols = symbols; + } + } + + private readonly List _images; + + private int _loaded; + + public HleProcessDebugger(KProcess owner) + { + _owner = owner; + + _images = new List(); + } + + public string GetGuestStackTrace(KThread thread) + { + EnsureLoaded(); + + var context = thread.Context; + + StringBuilder trace = new(); + + trace.AppendLine($"Process: {_owner.Name}, PID: {_owner.Pid}"); + + void AppendTrace(ulong address) + { + if (AnalyzePointer(out PointerInfo info, address, thread)) + { + trace.AppendLine($" 0x{address:x16}\t{info.ImageDisplay}\t{info.SubDisplay}"); + } + else + { + trace.AppendLine($" 0x{address:x16}"); + } + } + + if (context.IsAarch32) + { + ulong framePointer = context.GetX(11); + + while (framePointer != 0) + { + if ((framePointer & 3) != 0 || + !_owner.CpuMemory.IsMapped(framePointer) || + !_owner.CpuMemory.IsMapped(framePointer + 4)) + { + break; + } + + AppendTrace(_owner.CpuMemory.Read(framePointer + 4)); + + framePointer = _owner.CpuMemory.Read(framePointer); + } + } + else + { + ulong framePointer = context.GetX(29); + + while (framePointer != 0) + { + if ((framePointer & 7) != 0 || + !_owner.CpuMemory.IsMapped(framePointer) || + !_owner.CpuMemory.IsMapped(framePointer + 8)) + { + break; + } + + AppendTrace(_owner.CpuMemory.Read(framePointer + 8)); + + framePointer = _owner.CpuMemory.Read(framePointer); + } + } + + return trace.ToString(); + } + + public string GetCpuRegisterPrintout(KThread thread) + { + EnsureLoaded(); + + var context = thread.Context; + + StringBuilder sb = new(); + + string GetReg(int x) + { + var v = x == 32 ? context.Pc : context.GetX(x); + if (!AnalyzePointer(out PointerInfo info, v, thread)) + { + return $"0x{v:x16}"; + } + else + { + if (!string.IsNullOrEmpty(info.ImageName)) + { + return $"0x{v:x16} ({info.ImageDisplay})\t=> {info.SubDisplay}"; + } + else + { + return $"0x{v:x16} ({info.SpDisplay})"; + } + } + } + + for (int i = 0; i <= 28; i++) + { + sb.AppendLine($"\tX[{i:d2}]:\t{GetReg(i)}"); + } + sb.AppendLine($"\tFP:\t{GetReg(29)}"); + sb.AppendLine($"\tLR:\t{GetReg(30)}"); + sb.AppendLine($"\tSP:\t{GetReg(31)}"); + sb.AppendLine($"\tPC:\t{GetReg(32)}"); + + return sb.ToString(); + } + + private static bool TryGetSubName(Image image, ulong address, out ElfSymbol symbol) + { + address -= image.BaseAddress; + + int left = 0; + int right = image.Symbols.Length - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + symbol = image.Symbols[middle]; + + ulong endAddr = symbol.Value + symbol.Size; + + if (address >= symbol.Value && address < endAddr) + { + return true; + } + + if (address < symbol.Value) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + symbol = default; + + return false; + } + + struct PointerInfo + { + public string ImageName; + public string SubName; + + public ulong Offset; + public ulong SubOffset; + + public readonly string ImageDisplay => $"{ImageName}:0x{Offset:x4}"; + public readonly string SubDisplay => SubOffset == 0 ? SubName : $"{SubName}:0x{SubOffset:x4}"; + public readonly string SpDisplay => SubOffset == 0 ? "SP" : $"SP:-0x{SubOffset:x4}"; + } + + private bool AnalyzePointer(out PointerInfo info, ulong address, KThread thread) + { + if (AnalyzePointerFromImages(out info, address)) + { + return true; + } + + if (AnalyzePointerFromStack(out info, address, thread)) + { + return true; + } + + return false; + } + + private bool AnalyzePointerFromImages(out PointerInfo info, ulong address) + { + info = default; + + Image image = GetImage(address, out int imageIndex); + + if (image == null) + { + // Value isn't a pointer to a known image... + return false; + } + + info.Offset = address - image.BaseAddress; + + // Try to find what this pointer is referring to + if (TryGetSubName(image, address, out ElfSymbol symbol)) + { + info.SubName = symbol.Name; + + // Demangle string if possible + if (info.SubName.StartsWith("_Z")) + { + info.SubName = Demangler.Parse(info.SubName); + } + info.SubOffset = info.Offset - symbol.Value; + } + else + { + info.SubName = ""; + } + + info.ImageName = GetGuessedNsoNameFromIndex(imageIndex); + + return true; + } + + private bool AnalyzePointerFromStack(out PointerInfo info, ulong address, KThread thread) + { + info = default; + + ulong sp = thread.Context.GetX(31); + var memoryInfo = _owner.MemoryManager.QueryMemory(address); + MemoryState memoryState = memoryInfo.State; + + if (!memoryState.HasFlag(MemoryState.Stack)) // Is this pointer within the stack? + { + return false; + } + + info.SubOffset = address - sp; + + return true; + } + + private Image GetImage(ulong address, out int index) + { + lock (_images) + { + for (index = _images.Count - 1; index >= 0; index--) + { + if (address >= _images[index].BaseAddress && address < _images[index].EndAddress) + { + return _images[index]; + } + } + } + + return null; + } + + private string GetGuessedNsoNameFromIndex(int index) + { + if ((uint)index > 11) + { + return "???"; + } + + if (index == 0) + { + return "rtld"; + } + else if (index == 1) + { + return "main"; + } + else if (index == GetImagesCount() - 1) + { + return "sdk"; + } + else + { + return "subsdk" + (index - 2); + } + } + + private int GetImagesCount() + { + lock (_images) + { + return _images.Count; + } + } + + private void EnsureLoaded() + { + if (Interlocked.CompareExchange(ref _loaded, 1, 0) == 0) + { + ScanMemoryForTextSegments(); + } + } + + private void ScanMemoryForTextSegments() + { + ulong oldAddress = 0; + ulong address = 0; + + while (address >= oldAddress) + { + KMemoryInfo info = _owner.MemoryManager.QueryMemory(address); + + if (info.State == MemoryState.Reserved) + { + break; + } + + if (info.State == MemoryState.CodeStatic && info.Permission == KMemoryPermission.ReadAndExecute) + { + LoadMod0Symbols(_owner.CpuMemory, info.Address, info.Size); + } + + oldAddress = address; + + address = info.Address + info.Size; + } + } + + private void LoadMod0Symbols(IVirtualMemoryManager memory, ulong textOffset, ulong textSize) + { + ulong mod0Offset = textOffset + memory.Read(textOffset + 4); + + if (mod0Offset < textOffset || !memory.IsMapped(mod0Offset) || (mod0Offset & 3) != 0) + { + return; + } + + Dictionary dynamic = new(); + + int mod0Magic = memory.Read(mod0Offset + 0x0); + + if (mod0Magic != Mod0) + { + return; + } + + ulong dynamicOffset = memory.Read(mod0Offset + 0x4) + mod0Offset; + ulong bssStartOffset = memory.Read(mod0Offset + 0x8) + mod0Offset; + ulong bssEndOffset = memory.Read(mod0Offset + 0xc) + mod0Offset; + ulong ehHdrStartOffset = memory.Read(mod0Offset + 0x10) + mod0Offset; + ulong ehHdrEndOffset = memory.Read(mod0Offset + 0x14) + mod0Offset; + ulong modObjOffset = memory.Read(mod0Offset + 0x18) + mod0Offset; + + bool isAArch32 = memory.Read(dynamicOffset) > 0xFFFFFFFF || memory.Read(dynamicOffset + 0x10) > 0xFFFFFFFF; + + while (true) + { + ulong tagVal; + ulong value; + + if (isAArch32) + { + tagVal = memory.Read(dynamicOffset + 0); + value = memory.Read(dynamicOffset + 4); + + dynamicOffset += 0x8; + } + else + { + tagVal = memory.Read(dynamicOffset + 0); + value = memory.Read(dynamicOffset + 8); + + dynamicOffset += 0x10; + } + + ElfDynamicTag tag = (ElfDynamicTag)tagVal; + + if (tag == ElfDynamicTag.DT_NULL) + { + break; + } + + dynamic[tag] = value; + } + + if (!dynamic.TryGetValue(ElfDynamicTag.DT_STRTAB, out ulong strTab) || + !dynamic.TryGetValue(ElfDynamicTag.DT_SYMTAB, out ulong symTab) || + !dynamic.TryGetValue(ElfDynamicTag.DT_SYMENT, out ulong symEntSize)) + { + return; + } + + ulong strTblAddr = textOffset + strTab; + ulong symTblAddr = textOffset + symTab; + + List symbols = new(); + + while (symTblAddr < strTblAddr) + { + ElfSymbol sym = isAArch32 ? GetSymbol32(memory, symTblAddr, strTblAddr) : GetSymbol64(memory, symTblAddr, strTblAddr); + + symbols.Add(sym); + + symTblAddr += symEntSize; + } + + lock (_images) + { + _images.Add(new Image(textOffset, textSize, symbols.OrderBy(x => x.Value).ToArray())); + } + } + + private static ElfSymbol GetSymbol64(IVirtualMemoryManager memory, ulong address, ulong strTblAddr) + { + ElfSymbol64 sym = memory.Read(address); + + uint nameIndex = sym.NameOffset; + + StringBuilder nameBuilder = new(); + + for (int chr; (chr = memory.Read(strTblAddr + nameIndex++)) != 0;) + { + nameBuilder.Append((char)chr); + } + + return new ElfSymbol(nameBuilder.ToString(), sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size); + } + + private static ElfSymbol GetSymbol32(IVirtualMemoryManager memory, ulong address, ulong strTblAddr) + { + ElfSymbol32 sym = memory.Read(address); + + uint nameIndex = sym.NameOffset; + + StringBuilder nameBuilder = new(); + + for (int chr; (chr = memory.Read(strTblAddr + nameIndex++)) != 0;) + { + nameBuilder.Append((char)chr); + } + + return new ElfSymbol(nameBuilder.ToString(), sym.Info, sym.Other, sym.SectionIndex, sym.ValueAddress, sym.Size); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs new file mode 100644 index 00000000..ac36b781 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs @@ -0,0 +1,17 @@ +using Ryujinx.Cpu; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + interface IProcessContext : IDisposable + { + IVirtualMemoryManager AddressSpace { get; } + + ulong AddressSpaceSize { get; } + + IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks); + void Execute(IExecutionContext context, ulong codeAddress); + void InvalidateCacheRegion(ulong address, ulong size); + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs new file mode 100644 index 00000000..93af78c1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs @@ -0,0 +1,9 @@ +using Ryujinx.Memory; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + interface IProcessContextFactory + { + IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit); + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs new file mode 100644 index 00000000..32a75132 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs @@ -0,0 +1,83 @@ +using System; +using System.Numerics; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KContextIdManager + { + private const int IdMasksCount = 8; + + private readonly int[] _idMasks; + + private int _nextFreeBitHint; + + public KContextIdManager() + { + _idMasks = new int[IdMasksCount]; + } + + public int GetId() + { + lock (_idMasks) + { + int id = 0; + + if (!TestBit(_nextFreeBitHint)) + { + id = _nextFreeBitHint; + } + else + { + for (int index = 0; index < IdMasksCount; index++) + { + int mask = _idMasks[index]; + + int firstFreeBit = BitOperations.LeadingZeroCount((uint)((mask + 1) & ~mask)); + + if (firstFreeBit < 32) + { + int baseBit = index * 32 + 31; + + id = baseBit - firstFreeBit; + + break; + } + else if (index == IdMasksCount - 1) + { + throw new InvalidOperationException("Maximum number of Ids reached!"); + } + } + } + + _nextFreeBitHint = id + 1; + + SetBit(id); + + return id; + } + } + + public void PutId(int id) + { + lock (_idMasks) + { + ClearBit(id); + } + } + + private bool TestBit(int bit) + { + return (_idMasks[bit / 32] & (1 << (bit & 31))) != 0; + } + + private void SetBit(int bit) + { + _idMasks[bit / 32] |= (1 << (bit & 31)); + } + + private void ClearBit(int bit) + { + _idMasks[bit / 32] &= ~(1 << (bit & 31)); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs new file mode 100644 index 00000000..c80423b7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Kernel.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KHandleEntry + { + public KHandleEntry Next { get; set; } + + public int Index { get; private set; } + + public ushort HandleId { get; set; } + public KAutoObject Obj { get; set; } + + public KHandleEntry(int index) + { + Index = index; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs new file mode 100644 index 00000000..21ea6bb2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs @@ -0,0 +1,278 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KHandleTable + { + public const int SelfThreadHandle = (0x1ffff << 15) | 0; + public const int SelfProcessHandle = (0x1ffff << 15) | 1; + + private KHandleEntry[] _table; + + private KHandleEntry _tableHead; + private KHandleEntry _nextFreeEntry; + + private int _activeSlotsCount; + + private uint _size; + + private ushort _idCounter; + + public Result Initialize(uint size) + { + if (size > 1024) + { + return KernelResult.OutOfMemory; + } + + if (size < 1) + { + size = 1024; + } + + _size = size; + + _idCounter = 1; + + _table = new KHandleEntry[size]; + + _tableHead = new KHandleEntry(0); + + KHandleEntry entry = _tableHead; + + for (int index = 0; index < size; index++) + { + _table[index] = entry; + + entry.Next = new KHandleEntry(index + 1); + + entry = entry.Next; + } + + _table[size - 1].Next = null; + + _nextFreeEntry = _tableHead; + + return Result.Success; + } + + public Result GenerateHandle(KAutoObject obj, out int handle) + { + handle = 0; + + lock (_table) + { + if (_activeSlotsCount >= _size) + { + return KernelResult.HandleTableFull; + } + + KHandleEntry entry = _nextFreeEntry; + + _nextFreeEntry = entry.Next; + + entry.Obj = obj; + entry.HandleId = _idCounter; + + _activeSlotsCount++; + + handle = (_idCounter << 15) | entry.Index; + + obj.IncrementReferenceCount(); + + if ((short)(_idCounter + 1) >= 0) + { + _idCounter++; + } + else + { + _idCounter = 1; + } + } + + return Result.Success; + } + + public Result ReserveHandle(out int handle) + { + handle = 0; + + lock (_table) + { + if (_activeSlotsCount >= _size) + { + return KernelResult.HandleTableFull; + } + + KHandleEntry entry = _nextFreeEntry; + + _nextFreeEntry = entry.Next; + + _activeSlotsCount++; + + handle = (_idCounter << 15) | entry.Index; + + if ((short)(_idCounter + 1) >= 0) + { + _idCounter++; + } + else + { + _idCounter = 1; + } + } + + return Result.Success; + } + + public void CancelHandleReservation(int handle) + { + int index = handle & 0x7fff; + + lock (_table) + { + KHandleEntry entry = _table[index]; + + entry.Obj = null; + entry.Next = _nextFreeEntry; + + _nextFreeEntry = entry; + + _activeSlotsCount--; + } + } + + public void SetReservedHandleObj(int handle, KAutoObject obj) + { + int index = (handle >> 0) & 0x7fff; + int handleId = (handle >> 15); + + lock (_table) + { + KHandleEntry entry = _table[index]; + + entry.Obj = obj; + entry.HandleId = (ushort)handleId; + + obj.IncrementReferenceCount(); + } + } + + public bool CloseHandle(int handle) + { + if ((handle >> 30) != 0 || + handle == SelfThreadHandle || + handle == SelfProcessHandle) + { + return false; + } + + int index = (handle >> 0) & 0x7fff; + int handleId = (handle >> 15); + + KAutoObject obj = null; + + bool result = false; + + lock (_table) + { + if (handleId != 0 && index < _size) + { + KHandleEntry entry = _table[index]; + + if ((obj = entry.Obj) != null && entry.HandleId == handleId) + { + entry.Obj = null; + entry.Next = _nextFreeEntry; + + _nextFreeEntry = entry; + + _activeSlotsCount--; + + result = true; + } + } + } + + if (result) + { + obj.DecrementReferenceCount(); + } + + return result; + } + + public T GetObject(int handle) where T : KAutoObject + { + int index = (handle >> 0) & 0x7fff; + int handleId = (handle >> 15); + + lock (_table) + { + if ((handle >> 30) == 0 && handleId != 0 && index < _size) + { + KHandleEntry entry = _table[index]; + + if (entry.HandleId == handleId && entry.Obj is T obj) + { + return obj; + } + } + } + + return default; + } + + public KThread GetKThread(int handle) + { + if (handle == SelfThreadHandle) + { + return KernelStatic.GetCurrentThread(); + } + else + { + return GetObject(handle); + } + } + + public KProcess GetKProcess(int handle) + { + if (handle == SelfProcessHandle) + { + return KernelStatic.GetCurrentProcess(); + } + else + { + return GetObject(handle); + } + } + + public void Destroy() + { + lock (_table) + { + for (int index = 0; index < _size; index++) + { + KHandleEntry entry = _table[index]; + + if (entry.Obj != null) + { + if (entry.Obj is IDisposable disposableObj) + { + disposableObj.Dispose(); + } + + entry.Obj.DecrementReferenceCount(); + entry.Obj = null; + entry.Next = _nextFreeEntry; + + _nextFreeEntry = entry; + } + } + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs new file mode 100644 index 00000000..422f03c6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -0,0 +1,1179 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KProcess : KSynchronizationObject + { + public const uint KernelVersionMajor = 10; + public const uint KernelVersionMinor = 4; + public const uint KernelVersionRevision = 0; + + public const uint KernelVersionPacked = + (KernelVersionMajor << 19) | + (KernelVersionMinor << 15) | + (KernelVersionRevision << 0); + + public KPageTableBase MemoryManager { get; private set; } + + private readonly SortedDictionary _fullTlsPages; + private readonly SortedDictionary _freeTlsPages; + + public int DefaultCpuCore { get; set; } + + public bool Debug { get; private set; } + + public KResourceLimit ResourceLimit { get; private set; } + + public ulong PersonalMmHeapPagesCount { get; private set; } + + public ProcessState State { get; private set; } + + private readonly object _processLock = new(); + private readonly object _threadingLock = new(); + + public KAddressArbiter AddressArbiter { get; private set; } + + public ulong[] RandomEntropy { get; private set; } + public KThread[] PinnedThreads { get; private set; } + + private bool _signaled; + + public string Name { get; private set; } + + private int _threadCount; + + public ProcessCreationFlags Flags { get; private set; } + + private MemoryRegion _memRegion; + + public KProcessCapabilities Capabilities { get; private set; } + + public bool AllowCodeMemoryForJit { get; private set; } + + public ulong TitleId { get; private set; } + public bool IsApplication { get; private set; } + public ulong Pid { get; private set; } + + private ulong _entrypoint; + private ThreadStart _customThreadStart; + private ulong _imageSize; + private ulong _mainThreadStackSize; + private ulong _memoryUsageCapacity; + + public KHandleTable HandleTable { get; private set; } + + public ulong UserExceptionContextAddress { get; private set; } + + private readonly LinkedList _threads; + + public bool IsPaused { get; private set; } + + private long _totalTimeRunning; + + public long TotalTimeRunning => _totalTimeRunning; + + private IProcessContextFactory _contextFactory; + public IProcessContext Context { get; private set; } + public IVirtualMemoryManager CpuMemory => Context.AddressSpace; + + public HleProcessDebugger Debugger { get; private set; } + + public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context) + { + AddressArbiter = new KAddressArbiter(context); + + _fullTlsPages = new SortedDictionary(); + _freeTlsPages = new SortedDictionary(); + + Capabilities = new KProcessCapabilities(); + + AllowCodeMemoryForJit = allowCodeMemoryForJit; + + RandomEntropy = new ulong[KScheduler.CpuCoresCount]; + PinnedThreads = new KThread[KScheduler.CpuCoresCount]; + + // TODO: Remove once we no longer need to initialize it externally. + HandleTable = new KHandleTable(); + + _threads = new LinkedList(); + + Debugger = new HleProcessDebugger(this); + } + + public Result InitializeKip( + ProcessCreationInfo creationInfo, + ReadOnlySpan capabilities, + KPageList pageList, + KResourceLimit resourceLimit, + MemoryRegion memRegion, + IProcessContextFactory contextFactory, + ThreadStart customThreadStart = null) + { + ResourceLimit = resourceLimit; + _memRegion = memRegion; + _contextFactory = contextFactory ?? new ProcessContextFactory(); + _customThreadStart = customThreadStart; + + Pid = KernelContext.NewKipId(); + + if (Pid == 0 || Pid >= KernelConstants.InitialProcessId) + { + throw new InvalidOperationException($"Invalid KIP Id {Pid}."); + } + + InitializeMemoryManager(creationInfo.Flags); + + ulong codeAddress = creationInfo.CodeAddress; + + ulong codeSize = (ulong)creationInfo.CodePagesCount * KPageTableBase.PageSize; + + KMemoryBlockSlabManager slabManager = creationInfo.Flags.HasFlag(ProcessCreationFlags.IsApplication) + ? KernelContext.LargeMemoryBlockSlabManager + : KernelContext.SmallMemoryBlockSlabManager; + + Result result = MemoryManager.InitializeForProcess( + creationInfo.Flags, + !creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr), + memRegion, + codeAddress, + codeSize, + slabManager); + + if (result != Result.Success) + { + return result; + } + + if (!MemoryManager.CanContain(codeAddress, codeSize, MemoryState.CodeStatic)) + { + return KernelResult.InvalidMemRange; + } + + result = MemoryManager.MapPages(codeAddress, pageList, MemoryState.CodeStatic, KMemoryPermission.None); + + if (result != Result.Success) + { + return result; + } + + result = Capabilities.InitializeForKernel(capabilities, MemoryManager); + + if (result != Result.Success) + { + return result; + } + + return ParseProcessInfo(creationInfo); + } + + public Result Initialize( + ProcessCreationInfo creationInfo, + ReadOnlySpan capabilities, + KResourceLimit resourceLimit, + MemoryRegion memRegion, + IProcessContextFactory contextFactory, + ThreadStart customThreadStart = null) + { + ResourceLimit = resourceLimit; + _memRegion = memRegion; + _contextFactory = contextFactory ?? new ProcessContextFactory(); + _customThreadStart = customThreadStart; + IsApplication = creationInfo.Flags.HasFlag(ProcessCreationFlags.IsApplication); + + ulong personalMmHeapSize = GetPersonalMmHeapSize((ulong)creationInfo.SystemResourcePagesCount, memRegion); + + ulong codePagesCount = (ulong)creationInfo.CodePagesCount; + + ulong neededSizeForProcess = personalMmHeapSize + codePagesCount * KPageTableBase.PageSize; + + if (neededSizeForProcess != 0 && resourceLimit != null) + { + if (!resourceLimit.Reserve(LimitableResource.Memory, neededSizeForProcess)) + { + return KernelResult.ResLimitExceeded; + } + } + + void CleanUpForError() + { + if (neededSizeForProcess != 0 && resourceLimit != null) + { + resourceLimit.Release(LimitableResource.Memory, neededSizeForProcess); + } + } + + PersonalMmHeapPagesCount = (ulong)creationInfo.SystemResourcePagesCount; + + KMemoryBlockSlabManager slabManager; + + if (PersonalMmHeapPagesCount != 0) + { + slabManager = new KMemoryBlockSlabManager(PersonalMmHeapPagesCount * KPageTableBase.PageSize); + } + else + { + slabManager = creationInfo.Flags.HasFlag(ProcessCreationFlags.IsApplication) + ? KernelContext.LargeMemoryBlockSlabManager + : KernelContext.SmallMemoryBlockSlabManager; + } + + Pid = KernelContext.NewProcessId(); + + if (Pid == ulong.MaxValue || Pid < KernelConstants.InitialProcessId) + { + throw new InvalidOperationException($"Invalid Process Id {Pid}."); + } + + InitializeMemoryManager(creationInfo.Flags); + + ulong codeAddress = creationInfo.CodeAddress; + + ulong codeSize = codePagesCount * KPageTableBase.PageSize; + + Result result = MemoryManager.InitializeForProcess( + creationInfo.Flags, + !creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr), + memRegion, + codeAddress, + codeSize, + slabManager); + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + + if (!MemoryManager.CanContain(codeAddress, codeSize, MemoryState.CodeStatic)) + { + CleanUpForError(); + + return KernelResult.InvalidMemRange; + } + + result = MemoryManager.MapPages( + codeAddress, + codePagesCount, + MemoryState.CodeStatic, + KMemoryPermission.None); + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + + result = Capabilities.InitializeForUser(capabilities, MemoryManager); + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + + result = ParseProcessInfo(creationInfo); + + if (result != Result.Success) + { + CleanUpForError(); + } + + return result; + } + + private Result ParseProcessInfo(ProcessCreationInfo creationInfo) + { + // Ensure that the current kernel version is equal or above to the minimum required. + uint requiredKernelVersionMajor = Capabilities.KernelReleaseVersion >> 19; + uint requiredKernelVersionMinor = (Capabilities.KernelReleaseVersion >> 15) & 0xf; + + if (KernelContext.EnableVersionChecks) + { + if (requiredKernelVersionMajor > KernelVersionMajor) + { + return KernelResult.InvalidCombination; + } + + if (requiredKernelVersionMajor != KernelVersionMajor && requiredKernelVersionMajor < 3) + { + return KernelResult.InvalidCombination; + } + + if (requiredKernelVersionMinor > KernelVersionMinor) + { + return KernelResult.InvalidCombination; + } + } + + Result result = AllocateThreadLocalStorage(out ulong userExceptionContextAddress); + + if (result != Result.Success) + { + return result; + } + + UserExceptionContextAddress = userExceptionContextAddress; + + MemoryHelper.FillWithZeros(CpuMemory, userExceptionContextAddress, KTlsPageInfo.TlsEntrySize); + + Name = creationInfo.Name; + + State = ProcessState.Created; + + Flags = creationInfo.Flags; + TitleId = creationInfo.TitleId; + _entrypoint = creationInfo.CodeAddress; + _imageSize = (ulong)creationInfo.CodePagesCount * KPageTableBase.PageSize; + + switch (Flags & ProcessCreationFlags.AddressSpaceMask) + { + case ProcessCreationFlags.AddressSpace32Bit: + case ProcessCreationFlags.AddressSpace64BitDeprecated: + case ProcessCreationFlags.AddressSpace64Bit: + _memoryUsageCapacity = MemoryManager.HeapRegionEnd - + MemoryManager.HeapRegionStart; + break; + + case ProcessCreationFlags.AddressSpace32BitWithoutAlias: + _memoryUsageCapacity = MemoryManager.HeapRegionEnd - + MemoryManager.HeapRegionStart + + MemoryManager.AliasRegionEnd - + MemoryManager.AliasRegionStart; + break; + default: + throw new InvalidOperationException($"Invalid MMU flags value 0x{Flags:x2}."); + } + + GenerateRandomEntropy(); + + return Result.Success; + } + + public Result AllocateThreadLocalStorage(out ulong address) + { + KernelContext.CriticalSection.Enter(); + + Result result; + + if (_freeTlsPages.Count > 0) + { + // If we have free TLS pages available, just use the first one. + KTlsPageInfo pageInfo = _freeTlsPages.Values.First(); + + if (!pageInfo.TryGetFreePage(out address)) + { + throw new InvalidOperationException("Unexpected failure getting free TLS page!"); + } + + if (pageInfo.IsFull()) + { + _freeTlsPages.Remove(pageInfo.PageVirtualAddress); + + _fullTlsPages.Add(pageInfo.PageVirtualAddress, pageInfo); + } + + result = Result.Success; + } + else + { + // Otherwise, we need to create a new one. + result = AllocateTlsPage(out KTlsPageInfo pageInfo); + + if (result == Result.Success) + { + if (!pageInfo.TryGetFreePage(out address)) + { + throw new InvalidOperationException("Unexpected failure getting free TLS page!"); + } + + _freeTlsPages.Add(pageInfo.PageVirtualAddress, pageInfo); + } + else + { + address = 0; + } + } + + KernelContext.CriticalSection.Leave(); + + return result; + } + + private Result AllocateTlsPage(out KTlsPageInfo pageInfo) + { + pageInfo = default; + + if (!KernelContext.UserSlabHeapPages.TryGetItem(out ulong tlsPagePa)) + { + return KernelResult.OutOfMemory; + } + + ulong regionStart = MemoryManager.TlsIoRegionStart; + ulong regionSize = MemoryManager.TlsIoRegionEnd - regionStart; + + ulong regionPagesCount = regionSize / KPageTableBase.PageSize; + + Result result = MemoryManager.MapPages( + 1, + KPageTableBase.PageSize, + tlsPagePa, + true, + regionStart, + regionPagesCount, + MemoryState.ThreadLocal, + KMemoryPermission.ReadAndWrite, + out ulong tlsPageVa); + + if (result != Result.Success) + { + KernelContext.UserSlabHeapPages.Free(tlsPagePa); + } + else + { + pageInfo = new KTlsPageInfo(tlsPageVa, tlsPagePa); + + MemoryHelper.FillWithZeros(CpuMemory, tlsPageVa, KPageTableBase.PageSize); + } + + return result; + } + + public Result FreeThreadLocalStorage(ulong tlsSlotAddr) + { + ulong tlsPageAddr = BitUtils.AlignDown(tlsSlotAddr, KPageTableBase.PageSize); + + KernelContext.CriticalSection.Enter(); + + Result result = Result.Success; + + + if (_fullTlsPages.TryGetValue(tlsPageAddr, out KTlsPageInfo pageInfo)) + { + // TLS page was full, free slot and move to free pages tree. + _fullTlsPages.Remove(tlsPageAddr); + + _freeTlsPages.Add(tlsPageAddr, pageInfo); + } + else if (!_freeTlsPages.TryGetValue(tlsPageAddr, out pageInfo)) + { + result = KernelResult.InvalidAddress; + } + + if (pageInfo != null) + { + pageInfo.FreeTlsSlot(tlsSlotAddr); + + if (pageInfo.IsEmpty()) + { + // TLS page is now empty, we should ensure it is removed + // from all trees, and free the memory it was using. + _freeTlsPages.Remove(tlsPageAddr); + + KernelContext.CriticalSection.Leave(); + + FreeTlsPage(pageInfo); + + return Result.Success; + } + } + + KernelContext.CriticalSection.Leave(); + + return result; + } + + private Result FreeTlsPage(KTlsPageInfo pageInfo) + { + Result result = MemoryManager.UnmapForKernel(pageInfo.PageVirtualAddress, 1, MemoryState.ThreadLocal); + + if (result == Result.Success) + { + KernelContext.UserSlabHeapPages.Free(pageInfo.PagePhysicalAddress); + } + + return result; + } + + private void GenerateRandomEntropy() + { + // TODO. + } + + public Result Start(int mainThreadPriority, ulong stackSize) + { + lock (_processLock) + { + if (State > ProcessState.CreatedAttached) + { + return KernelResult.InvalidState; + } + + if (ResourceLimit != null && !ResourceLimit.Reserve(LimitableResource.Thread, 1)) + { + return KernelResult.ResLimitExceeded; + } + + KResourceLimit threadResourceLimit = ResourceLimit; + KResourceLimit memoryResourceLimit = null; + + if (_mainThreadStackSize != 0) + { + throw new InvalidOperationException("Trying to start a process with an invalid state!"); + } + + ulong stackSizeRounded = BitUtils.AlignUp(stackSize, KPageTableBase.PageSize); + + ulong neededSize = stackSizeRounded + _imageSize; + + // Check if the needed size for the code and the stack will fit on the + // memory usage capacity of this Process. Also check for possible overflow + // on the above addition. + if (neededSize > _memoryUsageCapacity || neededSize < stackSizeRounded) + { + threadResourceLimit?.Release(LimitableResource.Thread, 1); + + return KernelResult.OutOfMemory; + } + + if (stackSizeRounded != 0 && ResourceLimit != null) + { + memoryResourceLimit = ResourceLimit; + + if (!memoryResourceLimit.Reserve(LimitableResource.Memory, stackSizeRounded)) + { + threadResourceLimit?.Release(LimitableResource.Thread, 1); + + return KernelResult.ResLimitExceeded; + } + } + + Result result; + + KThread mainThread = null; + + ulong stackTop = 0; + + void CleanUpForError() + { + HandleTable.Destroy(); + + mainThread?.DecrementReferenceCount(); + + if (_mainThreadStackSize != 0) + { + ulong stackBottom = stackTop - _mainThreadStackSize; + + ulong stackPagesCount = _mainThreadStackSize / KPageTableBase.PageSize; + + MemoryManager.UnmapForKernel(stackBottom, stackPagesCount, MemoryState.Stack); + + _mainThreadStackSize = 0; + } + + memoryResourceLimit?.Release(LimitableResource.Memory, stackSizeRounded); + threadResourceLimit?.Release(LimitableResource.Thread, 1); + } + + if (stackSizeRounded != 0) + { + ulong stackPagesCount = stackSizeRounded / KPageTableBase.PageSize; + + ulong regionStart = MemoryManager.StackRegionStart; + ulong regionSize = MemoryManager.StackRegionEnd - regionStart; + + ulong regionPagesCount = regionSize / KPageTableBase.PageSize; + + result = MemoryManager.MapPages( + stackPagesCount, + KPageTableBase.PageSize, + 0, + false, + regionStart, + regionPagesCount, + MemoryState.Stack, + KMemoryPermission.ReadAndWrite, + out ulong stackBottom); + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + + _mainThreadStackSize += stackSizeRounded; + + stackTop = stackBottom + stackSizeRounded; + } + + ulong heapCapacity = _memoryUsageCapacity - _mainThreadStackSize - _imageSize; + + result = MemoryManager.SetHeapCapacity(heapCapacity); + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + + HandleTable = new KHandleTable(); + + result = HandleTable.Initialize(Capabilities.HandleTableSize); + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + + mainThread = new KThread(KernelContext); + + result = mainThread.Initialize( + _entrypoint, + 0, + stackTop, + mainThreadPriority, + DefaultCpuCore, + this, + ThreadType.User, + _customThreadStart); + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + + result = HandleTable.GenerateHandle(mainThread, out int mainThreadHandle); + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + + mainThread.SetEntryArguments(0, mainThreadHandle); + + ProcessState oldState = State; + ProcessState newState = State != ProcessState.Created + ? ProcessState.Attached + : ProcessState.Started; + + SetState(newState); + + result = mainThread.Start(); + + if (result != Result.Success) + { + SetState(oldState); + + CleanUpForError(); + } + + if (result == Result.Success) + { + mainThread.IncrementReferenceCount(); + } + + mainThread.DecrementReferenceCount(); + + return result; + } + } + + private void SetState(ProcessState newState) + { + if (State != newState) + { + State = newState; + _signaled = true; + + Signal(); + } + } + + public Result InitializeThread( + KThread thread, + ulong entrypoint, + ulong argsPtr, + ulong stackTop, + int priority, + int cpuCore, + ThreadStart customThreadStart = null) + { + lock (_processLock) + { + return thread.Initialize(entrypoint, argsPtr, stackTop, priority, cpuCore, this, ThreadType.User, customThreadStart); + } + } + + public IExecutionContext CreateExecutionContext() + { + return Context?.CreateExecutionContext(new ExceptionCallbacks( + InterruptHandler, + null, + KernelContext.SyscallHandler.SvcCall, + UndefinedInstructionHandler)); + } + + private void InterruptHandler(IExecutionContext context) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (currentThread.Context.Running && + currentThread.Owner != null && + currentThread.GetUserDisableCount() != 0 && + currentThread.Owner.PinnedThreads[currentThread.CurrentCore] == null) + { + KernelContext.CriticalSection.Enter(); + + currentThread.Owner.PinThread(currentThread); + + currentThread.SetUserInterruptFlag(); + + KernelContext.CriticalSection.Leave(); + } + + if (currentThread.IsSchedulable) + { + KernelContext.Schedulers[currentThread.CurrentCore].Schedule(); + } + + currentThread.HandlePostSyscall(); + } + + public void IncrementThreadCount() + { + Interlocked.Increment(ref _threadCount); + } + + public void DecrementThreadCountAndTerminateIfZero() + { + if (Interlocked.Decrement(ref _threadCount) == 0) + { + Terminate(); + } + } + + public void DecrementToZeroWhileTerminatingCurrent() + { + while (Interlocked.Decrement(ref _threadCount) != 0) + { + Destroy(); + TerminateCurrentProcess(); + } + + // Nintendo panic here because if it reaches this point, the current thread should be already dead. + // As we handle the death of the thread in the post SVC handler and inside the CPU emulator, we don't panic here. + } + + public ulong GetMemoryCapacity() + { + ulong totalCapacity = (ulong)ResourceLimit.GetRemainingValue(LimitableResource.Memory); + + totalCapacity += MemoryManager.GetTotalHeapSize(); + + totalCapacity += GetPersonalMmHeapSize(); + + totalCapacity += _imageSize + _mainThreadStackSize; + + if (totalCapacity <= _memoryUsageCapacity) + { + return totalCapacity; + } + + return _memoryUsageCapacity; + } + + public ulong GetMemoryUsage() + { + return _imageSize + _mainThreadStackSize + MemoryManager.GetTotalHeapSize() + GetPersonalMmHeapSize(); + } + + public ulong GetMemoryCapacityWithoutPersonalMmHeap() + { + return GetMemoryCapacity() - GetPersonalMmHeapSize(); + } + + public ulong GetMemoryUsageWithoutPersonalMmHeap() + { + return GetMemoryUsage() - GetPersonalMmHeapSize(); + } + + private ulong GetPersonalMmHeapSize() + { + return GetPersonalMmHeapSize(PersonalMmHeapPagesCount, _memRegion); + } + + private static ulong GetPersonalMmHeapSize(ulong personalMmHeapPagesCount, MemoryRegion memRegion) + { + if (memRegion == MemoryRegion.Applet) + { + return 0; + } + + return personalMmHeapPagesCount * KPageTableBase.PageSize; + } + + public void AddCpuTime(long ticks) + { + Interlocked.Add(ref _totalTimeRunning, ticks); + } + + public void AddThread(KThread thread) + { + lock (_threadingLock) + { + thread.ProcessListNode = _threads.AddLast(thread); + } + } + + public void RemoveThread(KThread thread) + { + lock (_threadingLock) + { + _threads.Remove(thread.ProcessListNode); + } + } + + public bool IsCpuCoreAllowed(int core) + { + return (Capabilities.AllowedCpuCoresMask & (1UL << core)) != 0; + } + + public bool IsPriorityAllowed(int priority) + { + return (Capabilities.AllowedThreadPriosMask & (1UL << priority)) != 0; + } + + public override bool IsSignaled() + { + return _signaled; + } + + public Result Terminate() + { + Result result; + + bool shallTerminate = false; + + KernelContext.CriticalSection.Enter(); + + lock (_processLock) + { + if (State >= ProcessState.Started) + { + if (State == ProcessState.Started || + State == ProcessState.Crashed || + State == ProcessState.Attached || + State == ProcessState.DebugSuspended) + { + SetState(ProcessState.Exiting); + + shallTerminate = true; + } + + result = Result.Success; + } + else + { + result = KernelResult.InvalidState; + } + } + + KernelContext.CriticalSection.Leave(); + + if (shallTerminate) + { + UnpauseAndTerminateAllThreadsExcept(KernelStatic.GetCurrentThread()); + + HandleTable.Destroy(); + + SignalExitToDebugTerminated(); + SignalExit(); + } + + return result; + } + + public void TerminateCurrentProcess() + { + bool shallTerminate = false; + + KernelContext.CriticalSection.Enter(); + + lock (_processLock) + { + if (State >= ProcessState.Started) + { + if (State == ProcessState.Started || + State == ProcessState.Attached || + State == ProcessState.DebugSuspended) + { + SetState(ProcessState.Exiting); + + shallTerminate = true; + } + } + } + + KernelContext.CriticalSection.Leave(); + + if (shallTerminate) + { + UnpauseAndTerminateAllThreadsExcept(KernelStatic.GetCurrentThread()); + + HandleTable.Destroy(); + + // NOTE: this is supposed to be called in receiving of the mailbox. + SignalExitToDebugExited(); + SignalExit(); + } + + KernelStatic.GetCurrentThread().Exit(); + } + + private void UnpauseAndTerminateAllThreadsExcept(KThread currentThread) + { + lock (_threadingLock) + { + KernelContext.CriticalSection.Enter(); + + if (currentThread != null && PinnedThreads[currentThread.CurrentCore] == currentThread) + { + UnpinThread(currentThread); + } + + foreach (KThread thread in _threads) + { + if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending) + { + thread.PrepareForTermination(); + } + } + + KernelContext.CriticalSection.Leave(); + } + + while (true) + { + KThread blockedThread = null; + + lock (_threadingLock) + { + foreach (KThread thread in _threads) + { + if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending) + { + thread.IncrementReferenceCount(); + + blockedThread = thread; + break; + } + } + } + + if (blockedThread == null) + { + break; + } + + blockedThread.Terminate(); + blockedThread.DecrementReferenceCount(); + } + } + + private static void SignalExitToDebugTerminated() + { + // TODO: Debug events. + } + + private static void SignalExitToDebugExited() + { + // TODO: Debug events. + } + + private void SignalExit() + { + ResourceLimit?.Release(LimitableResource.Memory, GetMemoryUsage()); + + KernelContext.CriticalSection.Enter(); + + SetState(ProcessState.Exited); + + KernelContext.CriticalSection.Leave(); + } + + public Result ClearIfNotExited() + { + Result result; + + KernelContext.CriticalSection.Enter(); + + lock (_processLock) + { + if (State != ProcessState.Exited && _signaled) + { + _signaled = false; + + result = Result.Success; + } + else + { + result = KernelResult.InvalidState; + } + } + + KernelContext.CriticalSection.Leave(); + + return result; + } + + private void InitializeMemoryManager(ProcessCreationFlags flags) + { + int addrSpaceBits = (flags & ProcessCreationFlags.AddressSpaceMask) switch + { + ProcessCreationFlags.AddressSpace32Bit => 32, + ProcessCreationFlags.AddressSpace64BitDeprecated => 36, + ProcessCreationFlags.AddressSpace32BitWithoutAlias => 32, + ProcessCreationFlags.AddressSpace64Bit => 39, + _ => 39, + }; + + bool for64Bit = flags.HasFlag(ProcessCreationFlags.Is64Bit); + + Context = _contextFactory.Create(KernelContext, Pid, 1UL << addrSpaceBits, InvalidAccessHandler, for64Bit); + + MemoryManager = new KPageTable(KernelContext, CpuMemory, Context.AddressSpaceSize); + } + + private bool InvalidAccessHandler(ulong va) + { + KernelStatic.GetCurrentThread()?.PrintGuestStackTrace(); + KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout(); + + Logger.Error?.Print(LogClass.Cpu, $"Invalid memory access at virtual address 0x{va:X16}."); + + return false; + } + + private void UndefinedInstructionHandler(IExecutionContext context, ulong address, int opCode) + { + KernelStatic.GetCurrentThread().PrintGuestStackTrace(); + KernelStatic.GetCurrentThread()?.PrintGuestRegisterPrintout(); + + throw new UndefinedInstructionException(address, opCode); + } + + protected override void Destroy() => Context.Dispose(); + + public Result SetActivity(bool pause) + { + KernelContext.CriticalSection.Enter(); + + if (State != ProcessState.Exiting && State != ProcessState.Exited) + { + if (pause) + { + if (IsPaused) + { + KernelContext.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + lock (_threadingLock) + { + foreach (KThread thread in _threads) + { + thread.Suspend(ThreadSchedState.ProcessPauseFlag); + } + } + + IsPaused = true; + } + else + { + if (!IsPaused) + { + KernelContext.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + lock (_threadingLock) + { + foreach (KThread thread in _threads) + { + thread.Resume(ThreadSchedState.ProcessPauseFlag); + } + } + + IsPaused = false; + } + + KernelContext.CriticalSection.Leave(); + + return Result.Success; + } + + KernelContext.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + public void PinThread(KThread thread) + { + if (!thread.TerminationRequested) + { + PinnedThreads[thread.CurrentCore] = thread; + + thread.Pin(); + + KernelContext.ThreadReselectionRequested = true; + } + } + + public void UnpinThread(KThread thread) + { + if (!thread.TerminationRequested) + { + thread.Unpin(); + + PinnedThreads[thread.CurrentCore] = null; + + KernelContext.ThreadReselectionRequested = true; + } + } + + public static bool IsExceptionUserThread(KThread thread) + { + // TODO + return false; + } + + public bool IsSvcPermitted(int svcId) + { + return Capabilities.IsSvcPermitted(svcId); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs new file mode 100644 index 00000000..ebab67bb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs @@ -0,0 +1,338 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; +using System.Numerics; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KProcessCapabilities + { + private const int SvcMaskElementBits = 8; + + public byte[] SvcAccessMask { get; } + public byte[] IrqAccessMask { get; } + + public ulong AllowedCpuCoresMask { get; private set; } + public ulong AllowedThreadPriosMask { get; private set; } + + public uint DebuggingFlags { get; private set; } + public uint HandleTableSize { get; private set; } + public uint KernelReleaseVersion { get; private set; } + public uint ApplicationType { get; private set; } + + public KProcessCapabilities() + { + // length / number of bits of the underlying type + SvcAccessMask = new byte[KernelConstants.SupervisorCallCount / SvcMaskElementBits]; + IrqAccessMask = new byte[0x80]; + } + + public Result InitializeForKernel(ReadOnlySpan capabilities, KPageTableBase memoryManager) + { + AllowedCpuCoresMask = 0xf; + AllowedThreadPriosMask = ulong.MaxValue; + DebuggingFlags &= ~3u; + KernelReleaseVersion = KProcess.KernelVersionPacked; + + return Parse(capabilities, memoryManager); + } + + public Result InitializeForUser(ReadOnlySpan capabilities, KPageTableBase memoryManager) + { + return Parse(capabilities, memoryManager); + } + + private Result Parse(ReadOnlySpan capabilities, KPageTableBase memoryManager) + { + int mask0 = 0; + int mask1 = 0; + + for (int index = 0; index < capabilities.Length; index++) + { + uint cap = capabilities[index]; + + if (cap.GetCapabilityType() != CapabilityType.MapRange) + { + Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager); + + if (result != Result.Success) + { + return result; + } + } + else + { + if ((uint)index + 1 >= capabilities.Length) + { + return KernelResult.InvalidCombination; + } + + uint prevCap = cap; + + cap = capabilities[++index]; + + if (((cap + 1) & ~cap) != 0x40) + { + return KernelResult.InvalidCombination; + } + + if ((cap & 0x78000000) != 0) + { + return KernelResult.MaximumExceeded; + } + + if ((cap & 0x7ffff80) == 0) + { + return KernelResult.InvalidSize; + } + + long address = ((long)prevCap << 5) & 0xffffff000; + long size = ((long)cap << 5) & 0xfffff000; + + if (((ulong)(address + size - 1) >> 36) != 0) + { + return KernelResult.InvalidAddress; + } + + KMemoryPermission perm = (prevCap >> 31) != 0 + ? KMemoryPermission.Read + : KMemoryPermission.ReadAndWrite; + + Result result; + + if ((cap >> 31) != 0) + { + result = KPageTableBase.MapNormalMemory(address, size, perm); + } + else + { + result = KPageTableBase.MapIoMemory(address, size, perm); + } + + if (result != Result.Success) + { + return result; + } + } + } + + return Result.Success; + } + + private Result ParseCapability(uint cap, ref int mask0, ref int mask1, KPageTableBase memoryManager) + { + CapabilityType code = cap.GetCapabilityType(); + + if (code == CapabilityType.Invalid) + { + return KernelResult.InvalidCapability; + } + else if (code == CapabilityType.Padding) + { + return Result.Success; + } + + int codeMask = 1 << (32 - BitOperations.LeadingZeroCount(code.GetFlag() + 1)); + + // Check if the property was already set. + if (((mask0 & codeMask) & 0x1e008) != 0) + { + return KernelResult.InvalidCombination; + } + + mask0 |= codeMask; + + switch (code) + { + case CapabilityType.CorePriority: + { + if (AllowedCpuCoresMask != 0 || AllowedThreadPriosMask != 0) + { + return KernelResult.InvalidCapability; + } + + uint lowestCpuCore = (cap >> 16) & 0xff; + uint highestCpuCore = (cap >> 24) & 0xff; + + if (lowestCpuCore > highestCpuCore) + { + return KernelResult.InvalidCombination; + } + + uint highestThreadPrio = (cap >> 4) & 0x3f; + uint lowestThreadPrio = (cap >> 10) & 0x3f; + + if (lowestThreadPrio > highestThreadPrio) + { + return KernelResult.InvalidCombination; + } + + if (highestCpuCore >= KScheduler.CpuCoresCount) + { + return KernelResult.InvalidCpuCore; + } + + AllowedCpuCoresMask = GetMaskFromMinMax(lowestCpuCore, highestCpuCore); + AllowedThreadPriosMask = GetMaskFromMinMax(lowestThreadPrio, highestThreadPrio); + + break; + } + + case CapabilityType.SyscallMask: + { + int slot = ((int)cap >> 29) & 7; + + int svcSlotMask = 1 << slot; + + if ((mask1 & svcSlotMask) != 0) + { + return KernelResult.InvalidCombination; + } + + mask1 |= svcSlotMask; + + uint svcMask = (cap >> 5) & 0xffffff; + + int baseSvc = slot * 24; + + for (int index = 0; index < 24; index++) + { + if (((svcMask >> index) & 1) == 0) + { + continue; + } + + int svcId = baseSvc + index; + + if (svcId >= KernelConstants.SupervisorCallCount) + { + return KernelResult.MaximumExceeded; + } + + SvcAccessMask[svcId / SvcMaskElementBits] |= (byte)(1 << (svcId % SvcMaskElementBits)); + } + + break; + } + + case CapabilityType.MapIoPage: + { + long address = ((long)cap << 4) & 0xffffff000; + + KPageTableBase.MapIoMemory(address, KPageTableBase.PageSize, KMemoryPermission.ReadAndWrite); + + break; + } + + case CapabilityType.MapRegion: + { + // TODO: Implement capabilities for MapRegion + + break; + } + + case CapabilityType.InterruptPair: + { + // TODO: GIC distributor check. + int irq0 = ((int)cap >> 12) & 0x3ff; + int irq1 = ((int)cap >> 22) & 0x3ff; + + if (irq0 != 0x3ff) + { + IrqAccessMask[irq0 / 8] |= (byte)(1 << (irq0 & 7)); + } + + if (irq1 != 0x3ff) + { + IrqAccessMask[irq1 / 8] |= (byte)(1 << (irq1 & 7)); + } + + break; + } + + case CapabilityType.ProgramType: + { + uint applicationType = (cap >> 14); + + if (applicationType > 7) + { + return KernelResult.ReservedValue; + } + + ApplicationType = applicationType; + + break; + } + + case CapabilityType.KernelVersion: + { + // Note: This check is bugged on kernel too, we are just replicating the bug here. + if ((KernelReleaseVersion >> 17) != 0 || cap < 0x80000) + { + return KernelResult.ReservedValue; + } + + KernelReleaseVersion = cap; + + break; + } + + case CapabilityType.HandleTable: + { + uint handleTableSize = cap >> 26; + + if (handleTableSize > 0x3ff) + { + return KernelResult.ReservedValue; + } + + HandleTableSize = handleTableSize; + + break; + } + + case CapabilityType.DebugFlags: + { + uint debuggingFlags = cap >> 19; + + if (debuggingFlags > 3) + { + return KernelResult.ReservedValue; + } + + DebuggingFlags &= ~3u; + DebuggingFlags |= debuggingFlags; + + break; + } + default: + return KernelResult.InvalidCapability; + } + + return Result.Success; + } + + private static ulong GetMaskFromMinMax(uint min, uint max) + { + uint range = max - min + 1; + + if (range == 64) + { + return ulong.MaxValue; + } + + ulong mask = (1UL << (int)range) - 1; + + return mask << (int)min; + } + + public bool IsSvcPermitted(int svcId) + { + int index = svcId / SvcMaskElementBits; + int mask = 1 << (svcId % SvcMaskElementBits); + + return (uint)svcId < KernelConstants.SupervisorCallCount && (SvcAccessMask[index] & mask) != 0; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs new file mode 100644 index 00000000..7fcd87b6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs @@ -0,0 +1,77 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KTlsPageInfo + { + public const int TlsEntrySize = 0x200; + + public ulong PageVirtualAddress { get; } + public ulong PagePhysicalAddress { get; } + + private readonly bool[] _isSlotFree; + + public KTlsPageInfo(ulong pageVirtualAddress, ulong pagePhysicalAddress) + { + PageVirtualAddress = pageVirtualAddress; + PagePhysicalAddress = pagePhysicalAddress; + + _isSlotFree = new bool[KPageTableBase.PageSize / TlsEntrySize]; + + for (int index = 0; index < _isSlotFree.Length; index++) + { + _isSlotFree[index] = true; + } + } + + public bool TryGetFreePage(out ulong address) + { + address = PageVirtualAddress; + + for (int index = 0; index < _isSlotFree.Length; index++) + { + if (_isSlotFree[index]) + { + _isSlotFree[index] = false; + + return true; + } + + address += TlsEntrySize; + } + + address = 0; + + return false; + } + + public bool IsFull() + { + bool hasFree = false; + + for (int index = 0; index < _isSlotFree.Length; index++) + { + hasFree |= _isSlotFree[index]; + } + + return !hasFree; + } + + public bool IsEmpty() + { + bool allFree = true; + + for (int index = 0; index < _isSlotFree.Length; index++) + { + allFree &= _isSlotFree[index]; + } + + return allFree; + } + + public void FreeTlsSlot(ulong address) + { + _isSlotFree[(address - PageVirtualAddress) / TlsEntrySize] = true; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs new file mode 100644 index 00000000..279fa13e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs @@ -0,0 +1,61 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class KTlsPageManager + { + private const int TlsEntrySize = 0x200; + + private readonly long _pagePosition; + + private int _usedSlots; + + private readonly bool[] _slots; + + public bool IsEmpty => _usedSlots == 0; + public bool IsFull => _usedSlots == _slots.Length; + + public KTlsPageManager(long pagePosition) + { + _pagePosition = pagePosition; + + _slots = new bool[KPageTableBase.PageSize / TlsEntrySize]; + } + + public bool TryGetFreeTlsAddr(out long position) + { + position = _pagePosition; + + for (int index = 0; index < _slots.Length; index++) + { + if (!_slots[index]) + { + _slots[index] = true; + + _usedSlots++; + + return true; + } + + position += TlsEntrySize; + } + + position = 0; + + return false; + } + + public void FreeTlsSlot(int slot) + { + if ((uint)slot > _slots.Length) + { + throw new ArgumentOutOfRangeException(nameof(slot)); + } + + _slots[slot] = false; + + _usedSlots--; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs new file mode 100644 index 00000000..b4ae6ec4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs @@ -0,0 +1,37 @@ +using Ryujinx.Cpu; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class ProcessContext : IProcessContext + { + public IVirtualMemoryManager AddressSpace { get; } + + public ulong AddressSpaceSize { get; } + + public ProcessContext(IVirtualMemoryManager asManager, ulong addressSpaceSize) + { + AddressSpace = asManager; + AddressSpaceSize = addressSpaceSize; + } + + public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks) + { + return new ProcessExecutionContext(); + } + + public void Execute(IExecutionContext context, ulong codeAddress) + { + throw new NotSupportedException(); + } + + public void InvalidateCacheRegion(ulong address, ulong size) + { + } + + public void Dispose() + { + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs new file mode 100644 index 00000000..d2722b73 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs @@ -0,0 +1,12 @@ +using Ryujinx.Memory; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class ProcessContextFactory : IProcessContextFactory + { + public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit) + { + return new ProcessContext(new AddressSpaceManager(context.Memory, addressSpaceSize), addressSpaceSize); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs new file mode 100644 index 00000000..1b62a29d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs @@ -0,0 +1,47 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + [Flags] + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + enum ProcessCreationFlags + { + Is64Bit = 1 << 0, + + AddressSpaceShift = 1, + AddressSpace32Bit = 0 << AddressSpaceShift, + AddressSpace64BitDeprecated = 1 << AddressSpaceShift, + AddressSpace32BitWithoutAlias = 2 << AddressSpaceShift, + AddressSpace64Bit = 3 << AddressSpaceShift, + AddressSpaceMask = 7 << AddressSpaceShift, + + EnableDebug = 1 << 4, + EnableAslr = 1 << 5, + IsApplication = 1 << 6, + DeprecatedUseSecureMemory = 1 << 7, + + PoolPartitionShift = 7, + PoolPartitionApplication = 0 << PoolPartitionShift, + PoolPartitionApplet = 1 << PoolPartitionShift, + PoolPartitionSystem = 2 << PoolPartitionShift, + PoolPartitionSystemNonSecure = 3 << PoolPartitionShift, + PoolPartitionMask = 0xf << PoolPartitionShift, + + OptimizeMemoryAllocation = 1 << 11, + DisableDeviceAddressSpaceMerge = 1 << 12, + EnableAliasRegionExtraSize = 1 << 13, + + All = + Is64Bit | + AddressSpaceMask | + EnableDebug | + EnableAslr | + IsApplication | + DeprecatedUseSecureMemory | + PoolPartitionMask | + OptimizeMemoryAllocation | + DisableDeviceAddressSpaceMerge | + EnableAliasRegionExtraSize, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs new file mode 100644 index 00000000..b5e5c29b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + readonly struct ProcessCreationInfo + { + public string Name { get; } + + public int Version { get; } + public ulong TitleId { get; } + + public ulong CodeAddress { get; } + public int CodePagesCount { get; } + + public ProcessCreationFlags Flags { get; } + public int ResourceLimitHandle { get; } + public int SystemResourcePagesCount { get; } + + public ProcessCreationInfo( + string name, + int version, + ulong titleId, + ulong codeAddress, + int codePagesCount, + ProcessCreationFlags flags, + int resourceLimitHandle, + int systemResourcePagesCount) + { + Name = name; + Version = version; + TitleId = titleId; + CodeAddress = codeAddress; + CodePagesCount = codePagesCount; + Flags = flags; + ResourceLimitHandle = resourceLimitHandle; + SystemResourcePagesCount = systemResourcePagesCount; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs new file mode 100644 index 00000000..b8118fbb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs @@ -0,0 +1,43 @@ +using ARMeilleure.State; +using Ryujinx.Cpu; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class ProcessExecutionContext : IExecutionContext + { + public ulong Pc => 0UL; + + public long TpidrEl0 { get; set; } + public long TpidrroEl0 { get; set; } + + public uint Pstate { get; set; } + + public uint Fpcr { get; set; } + public uint Fpsr { get; set; } + + public bool IsAarch32 { get => false; set { } } + + public bool Running { get; private set; } = true; + + private readonly ulong[] _x = new ulong[32]; + + public ulong GetX(int index) => _x[index]; + public void SetX(int index, ulong value) => _x[index] = value; + + public V128 GetV(int index) => default; + public void SetV(int index, V128 value) { } + + public void RequestInterrupt() + { + } + + public void StopRunning() + { + Running = false; + } + + public void Dispose() + { + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs new file mode 100644 index 00000000..e6c7e33e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + enum ProcessState : byte + { + Created = 0, + CreatedAttached = 1, + Started = 2, + Crashed = 3, + Attached = 4, + Exiting = 5, + Exited = 6, + DebugSuspended = 7, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs new file mode 100644 index 00000000..8eafd96c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Process +{ + class ProcessTamperInfo + { + public KProcess Process { get; } + public IEnumerable BuildIds { get; } + public IEnumerable CodeAddresses { get; } + public ulong HeapAddress { get; } + public ulong AliasAddress { get; } + public ulong AslrAddress { get; } + + public ProcessTamperInfo(KProcess process, IEnumerable buildIds, IEnumerable codeAddresses, ulong heapAddress, ulong aliasAddress, ulong aslrAddress) + { + Process = process; + BuildIds = buildIds; + CodeAddresses = codeAddresses; + HeapAddress = heapAddress; + AliasAddress = aliasAddress; + AslrAddress = aslrAddress; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs new file mode 100644 index 00000000..09e159a8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + enum CodeMemoryOperation : uint + { + Map, + MapToOwner, + Unmap, + UnmapFromOwner, + }; +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs new file mode 100644 index 00000000..738d6b64 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs @@ -0,0 +1,25 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + readonly struct ExternalEvent : IExternalEvent + { + private readonly KWritableEvent _writableEvent; + + public ExternalEvent(KWritableEvent writableEvent) + { + _writableEvent = writableEvent; + } + + public readonly void Signal() + { + _writableEvent.Signal(); + } + + public readonly void Clear() + { + _writableEvent.Clear(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs new file mode 100644 index 00000000..cbaae878 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + enum InfoType : uint + { + CoreMask, + PriorityMask, + AliasRegionAddress, + AliasRegionSize, + HeapRegionAddress, + HeapRegionSize, + TotalMemorySize, + UsedMemorySize, + DebuggerAttached, + ResourceLimit, + IdleTickCount, + RandomEntropy, + AslrRegionAddress, + AslrRegionSize, + StackRegionAddress, + StackRegionSize, + SystemResourceSizeTotal, + SystemResourceSizeUsed, + ProgramId, + InitialProcessIdRange, // NOTE: Added in 4.0.0, removed in 5.0.0. + UserExceptionContextAddress, + TotalNonSystemMemorySize, + UsedNonSystemMemorySize, + IsApplication, + FreeThreadCount, + ThreadTickCount, + IsSvcPermitted, + IoRegionHint, + AliasRegionExtraSize, + + MesosphereCurrentProcess = 65001, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/MemoryInfo.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/MemoryInfo.cs new file mode 100644 index 00000000..beec621b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/MemoryInfo.cs @@ -0,0 +1,37 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + struct MemoryInfo + { + public ulong Address; + public ulong Size; + public MemoryState State; + public MemoryAttribute Attribute; + public KMemoryPermission Permission; + public int IpcRefCount; + public int DeviceRefCount; +#pragma warning disable CS0414, IDE0052 // Remove unread private member + private readonly int _padding; +#pragma warning restore CS0414, IDE0052 + + public MemoryInfo( + ulong address, + ulong size, + MemoryState state, + MemoryAttribute attribute, + KMemoryPermission permission, + int ipcRefCount, + int deviceRefCount) + { + Address = address; + Size = size; + State = state; + Attribute = attribute; + Permission = permission; + IpcRefCount = ipcRefCount; + DeviceRefCount = deviceRefCount; + _padding = 0; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/PointerSizedAttribute.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/PointerSizedAttribute.cs new file mode 100644 index 00000000..3d50e896 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/PointerSizedAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + class PointerSizedAttribute : Attribute + { + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcAttribute.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcAttribute.cs new file mode 100644 index 00000000..24b51fb7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + class SvcAttribute : Attribute + { + public int Id { get; } + + public SvcAttribute(int id) + { + Id = id; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcImplAttribute.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcImplAttribute.cs new file mode 100644 index 00000000..8fe74e6b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcImplAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + class SvcImplAttribute : Attribute + { + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs new file mode 100644 index 00000000..2f487243 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs @@ -0,0 +1,3113 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + [SvcImpl] + class Syscall : ISyscallApi + { + private readonly KernelContext _context; + + public Syscall(KernelContext context) + { + _context = context; + } + + // Process + + [Svc(0x24)] + public Result GetProcessId(out ulong pid, int handle) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KProcess process = currentProcess.HandleTable.GetKProcess(handle); + + if (process == null) + { + KThread thread = currentProcess.HandleTable.GetKThread(handle); + + if (thread != null) + { + process = thread.Owner; + } + + // TODO: KDebugEvent. + } + + pid = process?.Pid ?? 0; + + return process != null + ? Result.Success + : KernelResult.InvalidHandle; + } + + public Result CreateProcess( + out int handle, + ProcessCreationInfo info, + ReadOnlySpan capabilities, + IProcessContextFactory contextFactory, + ThreadStart customThreadStart = null) + { + handle = 0; + + if ((info.Flags & ~ProcessCreationFlags.All) != 0) + { + return KernelResult.InvalidEnumValue; + } + + // TODO: Address space check. + + if ((info.Flags & ProcessCreationFlags.PoolPartitionMask) > ProcessCreationFlags.PoolPartitionSystemNonSecure) + { + return KernelResult.InvalidEnumValue; + } + + if ((info.CodeAddress & 0x1fffff) != 0) + { + return KernelResult.InvalidAddress; + } + + if (info.CodePagesCount < 0 || info.SystemResourcePagesCount < 0) + { + return KernelResult.InvalidSize; + } + + if (info.Flags.HasFlag(ProcessCreationFlags.EnableAliasRegionExtraSize)) + { + if ((info.Flags & ProcessCreationFlags.AddressSpaceMask) != ProcessCreationFlags.AddressSpace64Bit || + info.SystemResourcePagesCount <= 0) + { + return KernelResult.InvalidState; + } + + // TODO: Check that we are in debug mode. + } + + if (info.Flags.HasFlag(ProcessCreationFlags.OptimizeMemoryAllocation) && + !info.Flags.HasFlag(ProcessCreationFlags.IsApplication)) + { + return KernelResult.InvalidThread; + } + + KHandleTable handleTable = KernelStatic.GetCurrentProcess().HandleTable; + + KProcess process = new(_context); + + using var _ = new OnScopeExit(process.DecrementReferenceCount); + + KResourceLimit resourceLimit; + + if (info.ResourceLimitHandle != 0) + { + resourceLimit = handleTable.GetObject(info.ResourceLimitHandle); + + if (resourceLimit == null) + { + return KernelResult.InvalidHandle; + } + } + else + { + resourceLimit = _context.ResourceLimit; + } + + MemoryRegion memRegion = (info.Flags & ProcessCreationFlags.PoolPartitionMask) switch + { + ProcessCreationFlags.PoolPartitionApplication => MemoryRegion.Application, + ProcessCreationFlags.PoolPartitionApplet => MemoryRegion.Applet, + ProcessCreationFlags.PoolPartitionSystem => MemoryRegion.Service, + ProcessCreationFlags.PoolPartitionSystemNonSecure => MemoryRegion.NvServices, + _ => MemoryRegion.NvServices, + }; + + Result result = process.Initialize( + info, + capabilities, + resourceLimit, + memRegion, + contextFactory, + customThreadStart); + + if (result != Result.Success) + { + return result; + } + + _context.Processes.TryAdd(process.Pid, process); + + return handleTable.GenerateHandle(process, out handle); + } + + public Result StartProcess(int handle, int priority, int cpuCore, ulong mainThreadStackSize) + { + KProcess process = KernelStatic.GetCurrentProcess().HandleTable.GetObject(handle); + + if (process == null) + { + return KernelResult.InvalidHandle; + } + + if ((uint)cpuCore >= KScheduler.CpuCoresCount || !process.IsCpuCoreAllowed(cpuCore)) + { + return KernelResult.InvalidCpuCore; + } + + if ((uint)priority >= KScheduler.PrioritiesCount || !process.IsPriorityAllowed(priority)) + { + return KernelResult.InvalidPriority; + } + + process.DefaultCpuCore = cpuCore; + + Result result = process.Start(priority, mainThreadStackSize); + + if (result != Result.Success) + { + return result; + } + + process.IncrementReferenceCount(); + + return Result.Success; + } + + [Svc(0x5f)] + public Result FlushProcessDataCache(int processHandle, ulong address, ulong size) + { + // FIXME: This needs to be implemented as ARMv7 doesn't have any way to do cache maintenance operations on EL0. + // As we don't support (and don't actually need) to flush the cache, this is stubbed. + return Result.Success; + } + + // IPC + + [Svc(0x1f)] + public Result ConnectToNamedPort(out int handle, [PointerSized] ulong namePtr) + { + handle = 0; + + if (!KernelTransfer.UserToKernelString(out string name, namePtr, 12)) + { + return KernelResult.UserCopyFailed; + } + + return ConnectToNamedPort(out handle, name); + } + + public Result ConnectToNamedPort(out int handle, string name) + { + handle = 0; + + if (name.Length > 11) + { + return KernelResult.MaximumExceeded; + } + + KAutoObject autoObj = KAutoObject.FindNamedObject(_context, name); + + if (autoObj is not KClientPort clientPort) + { + return KernelResult.NotFound; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + Result result = currentProcess.HandleTable.ReserveHandle(out handle); + + if (result != Result.Success) + { + return result; + } + + result = clientPort.Connect(out KClientSession clientSession); + + if (result != Result.Success) + { + currentProcess.HandleTable.CancelHandleReservation(handle); + + return result; + } + + currentProcess.HandleTable.SetReservedHandleObj(handle, clientSession); + + clientSession.DecrementReferenceCount(); + + return result; + } + + [Svc(0x21)] + public Result SendSyncRequest(int handle) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KClientSession session = currentProcess.HandleTable.GetObject(handle); + + if (session == null) + { + return KernelResult.InvalidHandle; + } + + return session.SendSyncRequest(); + } + + [Svc(0x22)] + public Result SendSyncRequestWithUserBuffer( + [PointerSized] ulong messagePtr, + [PointerSized] ulong messageSize, + int handle) + { + if (!PageAligned(messagePtr)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(messageSize) || messageSize == 0) + { + return KernelResult.InvalidSize; + } + + if (messagePtr + messageSize <= messagePtr) + { + return KernelResult.InvalidMemState; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + Result result = currentProcess.MemoryManager.BorrowIpcBuffer(messagePtr, messageSize); + + if (result != Result.Success) + { + return result; + } + + KClientSession session = currentProcess.HandleTable.GetObject(handle); + + if (session == null) + { + result = KernelResult.InvalidHandle; + } + else + { + result = session.SendSyncRequest(messagePtr, messageSize); + } + + Result result2 = currentProcess.MemoryManager.UnborrowIpcBuffer(messagePtr, messageSize); + + if (result == Result.Success) + { + result = result2; + } + + return result; + } + + [Svc(0x23)] + public Result SendAsyncRequestWithUserBuffer( + out int doneEventHandle, + [PointerSized] ulong messagePtr, + [PointerSized] ulong messageSize, + int handle) + { + doneEventHandle = 0; + + if (!PageAligned(messagePtr)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(messageSize) || messageSize == 0) + { + return KernelResult.InvalidSize; + } + + if (messagePtr + messageSize <= messagePtr) + { + return KernelResult.InvalidMemState; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + Result result = currentProcess.MemoryManager.BorrowIpcBuffer(messagePtr, messageSize); + + if (result != Result.Success) + { + return result; + } + + KResourceLimit resourceLimit = currentProcess.ResourceLimit; + + if (resourceLimit != null && !resourceLimit.Reserve(LimitableResource.Event, 1)) + { + currentProcess.MemoryManager.UnborrowIpcBuffer(messagePtr, messageSize); + + return KernelResult.ResLimitExceeded; + } + + KClientSession session = currentProcess.HandleTable.GetObject(handle); + + if (session == null) + { + result = KernelResult.InvalidHandle; + } + else + { + KEvent doneEvent = new(_context); + + result = currentProcess.HandleTable.GenerateHandle(doneEvent.ReadableEvent, out doneEventHandle); + + if (result == Result.Success) + { + result = session.SendAsyncRequest(doneEvent.WritableEvent, messagePtr, messageSize); + + if (result != Result.Success) + { + currentProcess.HandleTable.CloseHandle(doneEventHandle); + } + } + } + + if (result != Result.Success) + { + resourceLimit?.Release(LimitableResource.Event, 1); + + currentProcess.MemoryManager.UnborrowIpcBuffer(messagePtr, messageSize); + } + + return result; + } + + [Svc(0x40)] + public Result CreateSession( + out int serverSessionHandle, + out int clientSessionHandle, + bool isLight, + [PointerSized] ulong namePtr) + { + return CreateSession(out serverSessionHandle, out clientSessionHandle, isLight, null); + } + + public Result CreateSession( + out int serverSessionHandle, + out int clientSessionHandle, + bool isLight, + string name) + { + serverSessionHandle = 0; + clientSessionHandle = 0; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KResourceLimit resourceLimit = currentProcess.ResourceLimit; + + if (resourceLimit != null && !resourceLimit.Reserve(LimitableResource.Session, 1)) + { + return KernelResult.ResLimitExceeded; + } + + Result result; + + if (isLight) + { + KLightSession session = new(_context); + + result = currentProcess.HandleTable.GenerateHandle(session.ServerSession, out serverSessionHandle); + + if (result == Result.Success) + { + result = currentProcess.HandleTable.GenerateHandle(session.ClientSession, out clientSessionHandle); + + if (result != Result.Success) + { + currentProcess.HandleTable.CloseHandle(serverSessionHandle); + + serverSessionHandle = 0; + } + } + + session.ServerSession.DecrementReferenceCount(); + session.ClientSession.DecrementReferenceCount(); + } + else + { + KSession session = new(_context); + + result = currentProcess.HandleTable.GenerateHandle(session.ServerSession, out serverSessionHandle); + + if (result == Result.Success) + { + result = currentProcess.HandleTable.GenerateHandle(session.ClientSession, out clientSessionHandle); + + if (result != Result.Success) + { + currentProcess.HandleTable.CloseHandle(serverSessionHandle); + + serverSessionHandle = 0; + } + } + + session.ServerSession.DecrementReferenceCount(); + session.ClientSession.DecrementReferenceCount(); + } + + return result; + } + + [Svc(0x41)] + public Result AcceptSession(out int sessionHandle, int portHandle) + { + sessionHandle = 0; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KServerPort serverPort = currentProcess.HandleTable.GetObject(portHandle); + + if (serverPort == null) + { + return KernelResult.InvalidHandle; + } + + Result result = currentProcess.HandleTable.ReserveHandle(out int handle); + + if (result != Result.Success) + { + return result; + } + + KAutoObject session; + + if (serverPort.IsLight) + { + session = serverPort.AcceptIncomingLightConnection(); + } + else + { + session = serverPort.AcceptIncomingConnection(); + } + + if (session != null) + { + currentProcess.HandleTable.SetReservedHandleObj(handle, session); + + session.DecrementReferenceCount(); + + sessionHandle = handle; + + result = Result.Success; + } + else + { + currentProcess.HandleTable.CancelHandleReservation(handle); + + result = KernelResult.NotFound; + } + + return result; + } + + [Svc(0x43)] + public Result ReplyAndReceive( + out int handleIndex, + [PointerSized] ulong handlesPtr, + int handlesCount, + int replyTargetHandle, + long timeout) + { + handleIndex = 0; + + if ((uint)handlesCount > 0x40) + { + return KernelResult.MaximumExceeded; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + ulong copySize = (ulong)((long)handlesCount * 4); + + if (!currentProcess.MemoryManager.InsideAddrSpace(handlesPtr, copySize)) + { + return KernelResult.UserCopyFailed; + } + + if (handlesPtr + copySize < handlesPtr) + { + return KernelResult.UserCopyFailed; + } + + int[] handles = new int[handlesCount]; + + if (!KernelTransfer.UserToKernelArray(handlesPtr, handles)) + { + return KernelResult.UserCopyFailed; + } + + if (timeout > 0) + { + timeout += KTimeManager.DefaultTimeIncrementNanoseconds; + } + + return ReplyAndReceive(out handleIndex, handles, replyTargetHandle, timeout); + } + + public Result ReplyAndReceive(out int handleIndex, ReadOnlySpan handles, int replyTargetHandle, long timeout) + { + handleIndex = 0; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KSynchronizationObject[] syncObjsArray = ArrayPool.Shared.Rent(handles.Length); + + Span syncObjs = syncObjsArray.AsSpan(0, handles.Length); + + for (int index = 0; index < handles.Length; index++) + { + KSynchronizationObject obj = currentProcess.HandleTable.GetObject(handles[index]); + + if (obj == null) + { + return KernelResult.InvalidHandle; + } + + syncObjs[index] = obj; + } + + Result result = Result.Success; + + if (replyTargetHandle != 0) + { + KServerSession replyTarget = currentProcess.HandleTable.GetObject(replyTargetHandle); + + if (replyTarget == null) + { + result = KernelResult.InvalidHandle; + } + else + { + result = replyTarget.Reply(); + } + } + + if (result == Result.Success) + { + if (timeout > 0) + { + timeout += KTimeManager.DefaultTimeIncrementNanoseconds; + } + + while ((result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == Result.Success) + { + KServerSession session = currentProcess.HandleTable.GetObject(handles[handleIndex]); + + if (session == null) + { + break; + } + + if ((result = session.Receive()) != KernelResult.NotFound) + { + break; + } + } + } + + ArrayPool.Shared.Return(syncObjsArray, true); + + return result; + } + + [Svc(0x44)] + public Result ReplyAndReceiveWithUserBuffer( + out int handleIndex, + [PointerSized] ulong messagePtr, + [PointerSized] ulong messageSize, + [PointerSized] ulong handlesPtr, + int handlesCount, + int replyTargetHandle, + long timeout) + { + handleIndex = 0; + + if ((uint)handlesCount > 0x40) + { + return KernelResult.MaximumExceeded; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + ulong copySize = (ulong)((long)handlesCount * 4); + + if (!currentProcess.MemoryManager.InsideAddrSpace(handlesPtr, copySize)) + { + return KernelResult.UserCopyFailed; + } + + if (handlesPtr + copySize < handlesPtr) + { + return KernelResult.UserCopyFailed; + } + + Result result = currentProcess.MemoryManager.BorrowIpcBuffer(messagePtr, messageSize); + + if (result != Result.Success) + { + return result; + } + + int[] handles = new int[handlesCount]; + + if (!KernelTransfer.UserToKernelArray(handlesPtr, handles)) + { + currentProcess.MemoryManager.UnborrowIpcBuffer(messagePtr, messageSize); + + return KernelResult.UserCopyFailed; + } + + KSynchronizationObject[] syncObjs = new KSynchronizationObject[handlesCount]; + + for (int index = 0; index < handlesCount; index++) + { + KSynchronizationObject obj = currentProcess.HandleTable.GetObject(handles[index]); + + if (obj == null) + { + currentProcess.MemoryManager.UnborrowIpcBuffer(messagePtr, messageSize); + + return KernelResult.InvalidHandle; + } + + syncObjs[index] = obj; + } + + if (replyTargetHandle != 0) + { + KServerSession replyTarget = currentProcess.HandleTable.GetObject(replyTargetHandle); + + if (replyTarget == null) + { + result = KernelResult.InvalidHandle; + } + else + { + result = replyTarget.Reply(messagePtr, messageSize); + } + } + + if (result == Result.Success) + { + if (timeout > 0) + { + timeout += KTimeManager.DefaultTimeIncrementNanoseconds; + } + + while ((result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex)) == Result.Success) + { + KServerSession session = currentProcess.HandleTable.GetObject(handles[handleIndex]); + + if (session == null) + { + break; + } + + if ((result = session.Receive(messagePtr, messageSize)) != KernelResult.NotFound) + { + break; + } + } + } + + currentProcess.MemoryManager.UnborrowIpcBuffer(messagePtr, messageSize); + + return result; + } + + [Svc(0x70)] + public Result CreatePort( + out int serverPortHandle, + out int clientPortHandle, + int maxSessions, + bool isLight, + [PointerSized] ulong namePtr) + { + // The kernel doesn't use the name pointer, so we can just pass null as the name. + return CreatePort(out serverPortHandle, out clientPortHandle, maxSessions, isLight, null); + } + + public Result CreatePort( + out int serverPortHandle, + out int clientPortHandle, + int maxSessions, + bool isLight, + string name) + { + serverPortHandle = clientPortHandle = 0; + + if (maxSessions < 1) + { + return KernelResult.MaximumExceeded; + } + + KPort port = new(_context, maxSessions, isLight, name); + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + Result result = currentProcess.HandleTable.GenerateHandle(port.ClientPort, out clientPortHandle); + + if (result != Result.Success) + { + return result; + } + + result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out serverPortHandle); + + if (result != Result.Success) + { + currentProcess.HandleTable.CloseHandle(clientPortHandle); + } + + return result; + } + + [Svc(0x71)] + public Result ManageNamedPort(out int handle, [PointerSized] ulong namePtr, int maxSessions) + { + handle = 0; + + if (!KernelTransfer.UserToKernelString(out string name, namePtr, 12)) + { + return KernelResult.UserCopyFailed; + } + + if (name.Length > 11) + { + return KernelResult.MaximumExceeded; + } + + return ManageNamedPort(out handle, name, maxSessions); + } + + public Result ManageNamedPort(out int handle, string name, int maxSessions) + { + handle = 0; + + if (maxSessions < 0) + { + return KernelResult.MaximumExceeded; + } + + if (maxSessions == 0) + { + return KAutoObject.RemoveName(_context, name); + } + + KPort port = new(_context, maxSessions, false, null); + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + Result result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out handle); + + if (result != Result.Success) + { + return result; + } + + result = port.ClientPort.SetName(name); + + if (result != Result.Success) + { + currentProcess.HandleTable.CloseHandle(handle); + } + + return result; + } + + [Svc(0x72)] + public Result ConnectToPort(out int clientSessionHandle, int clientPortHandle) + { + clientSessionHandle = 0; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KClientPort clientPort = currentProcess.HandleTable.GetObject(clientPortHandle); + + if (clientPort == null) + { + return KernelResult.InvalidHandle; + } + + Result result = currentProcess.HandleTable.ReserveHandle(out int handle); + + if (result != Result.Success) + { + return result; + } + + KAutoObject session; + + if (clientPort.IsLight) + { + result = clientPort.ConnectLight(out KLightClientSession clientSession); + + session = clientSession; + } + else + { + result = clientPort.Connect(out KClientSession clientSession); + + session = clientSession; + } + + if (result != Result.Success) + { + currentProcess.HandleTable.CancelHandleReservation(handle); + + return result; + } + + currentProcess.HandleTable.SetReservedHandleObj(handle, session); + + session.DecrementReferenceCount(); + + clientSessionHandle = handle; + + return result; + } + + // Memory + + [Svc(1)] + public Result SetHeapSize([PointerSized] out ulong address, [PointerSized] ulong size) + { + if ((size & 0xfffffffe001fffff) != 0) + { + address = 0; + + return KernelResult.InvalidSize; + } + + KProcess process = KernelStatic.GetCurrentProcess(); + + return process.MemoryManager.SetHeapSize(size, out address); + } + + [Svc(2)] + public Result SetMemoryPermission([PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemState; + } + + if (permission != KMemoryPermission.None && (permission | KMemoryPermission.Write) != KMemoryPermission.ReadAndWrite) + { + return KernelResult.InvalidPermission; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!currentProcess.MemoryManager.InsideAddrSpace(address, size)) + { + return KernelResult.InvalidMemState; + } + + return currentProcess.MemoryManager.SetMemoryPermission(address, size, permission); + } + + [Svc(3)] + public Result SetMemoryAttribute( + [PointerSized] ulong address, + [PointerSized] ulong size, + MemoryAttribute attributeMask, + MemoryAttribute attributeValue) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + MemoryAttribute attributes = attributeMask | attributeValue; + + const MemoryAttribute SupportedAttributes = MemoryAttribute.Uncached | MemoryAttribute.PermissionLocked; + + if (attributes != attributeMask || + (attributes | SupportedAttributes) != SupportedAttributes) + { + return KernelResult.InvalidCombination; + } + + // The permission locked attribute can't be unset. + if ((attributeMask & MemoryAttribute.PermissionLocked) != (attributeValue & MemoryAttribute.PermissionLocked)) + { + return KernelResult.InvalidCombination; + } + + KProcess process = KernelStatic.GetCurrentProcess(); + + if (!process.MemoryManager.InsideAddrSpace(address, size)) + { + return KernelResult.InvalidMemState; + } + + Result result = process.MemoryManager.SetMemoryAttribute( + address, + size, + attributeMask, + attributeValue); + + return result; + } + + [Svc(4)] + public Result MapMemory([PointerSized] ulong dst, [PointerSized] ulong src, [PointerSized] ulong size) + { + if (!PageAligned(src | dst)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (src + size <= src || dst + size <= dst) + { + return KernelResult.InvalidMemState; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!currentProcess.MemoryManager.InsideAddrSpace(src, size)) + { + return KernelResult.InvalidMemState; + } + + if (currentProcess.MemoryManager.OutsideStackRegion(dst, size) || + currentProcess.MemoryManager.InsideHeapRegion(dst, size) || + currentProcess.MemoryManager.InsideAliasRegion(dst, size)) + { + return KernelResult.InvalidMemRange; + } + + KProcess process = KernelStatic.GetCurrentProcess(); + + return process.MemoryManager.Map(dst, src, size); + } + + [Svc(5)] + public Result UnmapMemory([PointerSized] ulong dst, [PointerSized] ulong src, [PointerSized] ulong size) + { + if (!PageAligned(src | dst)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (src + size <= src || dst + size <= dst) + { + return KernelResult.InvalidMemState; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!currentProcess.MemoryManager.InsideAddrSpace(src, size)) + { + return KernelResult.InvalidMemState; + } + + if (currentProcess.MemoryManager.OutsideStackRegion(dst, size) || + currentProcess.MemoryManager.InsideHeapRegion(dst, size) || + currentProcess.MemoryManager.InsideAliasRegion(dst, size)) + { + return KernelResult.InvalidMemRange; + } + + KProcess process = KernelStatic.GetCurrentProcess(); + + return process.MemoryManager.Unmap(dst, src, size); + } + + [Svc(6)] + public Result QueryMemory([PointerSized] ulong infoPtr, [PointerSized] out ulong pageInfo, [PointerSized] ulong address) + { + Result result = QueryMemory(out MemoryInfo info, out pageInfo, address); + + if (result == Result.Success) + { + return KernelTransfer.KernelToUser(infoPtr, info) + ? Result.Success + : KernelResult.InvalidMemState; + } + + return result; + } + + public Result QueryMemory(out MemoryInfo info, out ulong pageInfo, ulong address) + { + KProcess process = KernelStatic.GetCurrentProcess(); + + KMemoryInfo blockInfo = process.MemoryManager.QueryMemory(address); + + info = new MemoryInfo( + blockInfo.Address, + blockInfo.Size, + blockInfo.State & MemoryState.UserMask, + blockInfo.Attribute, + blockInfo.Permission & KMemoryPermission.UserMask, + blockInfo.IpcRefCount, + blockInfo.DeviceRefCount); + + pageInfo = 0; + + return Result.Success; + } + + [Svc(0x13)] + public Result MapSharedMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemState; + } + + if ((permission | KMemoryPermission.Write) != KMemoryPermission.ReadAndWrite) + { + return KernelResult.InvalidPermission; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KSharedMemory sharedMemory = currentProcess.HandleTable.GetObject(handle); + + if (sharedMemory == null) + { + return KernelResult.InvalidHandle; + } + + if (currentProcess.MemoryManager.IsInvalidRegion(address, size) || + currentProcess.MemoryManager.InsideHeapRegion(address, size) || + currentProcess.MemoryManager.InsideAliasRegion(address, size)) + { + return KernelResult.InvalidMemRange; + } + + return sharedMemory.MapIntoProcess( + currentProcess.MemoryManager, + address, + size, + currentProcess, + permission); + } + + [Svc(0x14)] + public Result UnmapSharedMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemState; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KSharedMemory sharedMemory = currentProcess.HandleTable.GetObject(handle); + + if (sharedMemory == null) + { + return KernelResult.InvalidHandle; + } + + if (currentProcess.MemoryManager.IsInvalidRegion(address, size) || + currentProcess.MemoryManager.InsideHeapRegion(address, size) || + currentProcess.MemoryManager.InsideAliasRegion(address, size)) + { + return KernelResult.InvalidMemRange; + } + + return sharedMemory.UnmapFromProcess( + currentProcess.MemoryManager, + address, + size, + currentProcess); + } + + [Svc(0x15)] + public Result CreateTransferMemory(out int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission) + { + handle = 0; + + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemState; + } + + if (permission > KMemoryPermission.ReadAndWrite || permission == KMemoryPermission.Write) + { + return KernelResult.InvalidPermission; + } + + KProcess process = KernelStatic.GetCurrentProcess(); + + KResourceLimit resourceLimit = process.ResourceLimit; + + if (resourceLimit != null && !resourceLimit.Reserve(LimitableResource.TransferMemory, 1)) + { + return KernelResult.ResLimitExceeded; + } + + void CleanUpForError() + { + resourceLimit?.Release(LimitableResource.TransferMemory, 1); + } + + if (!process.MemoryManager.InsideAddrSpace(address, size)) + { + CleanUpForError(); + + return KernelResult.InvalidMemState; + } + + KTransferMemory transferMemory = new(_context); + + Result result = transferMemory.Initialize(address, size, permission); + + if (result != Result.Success) + { + CleanUpForError(); + + return result; + } + + result = process.HandleTable.GenerateHandle(transferMemory, out handle); + + transferMemory.DecrementReferenceCount(); + + return result; + } + + [Svc(0x51)] + public Result MapTransferMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size, KMemoryPermission permission) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemState; + } + + if (permission > KMemoryPermission.ReadAndWrite || permission == KMemoryPermission.Write) + { + return KernelResult.InvalidPermission; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KTransferMemory transferMemory = currentProcess.HandleTable.GetObject(handle); + + if (transferMemory == null) + { + return KernelResult.InvalidHandle; + } + + if (currentProcess.MemoryManager.IsInvalidRegion(address, size) || + currentProcess.MemoryManager.InsideHeapRegion(address, size) || + currentProcess.MemoryManager.InsideAliasRegion(address, size)) + { + return KernelResult.InvalidMemRange; + } + + return transferMemory.MapIntoProcess( + currentProcess.MemoryManager, + address, + size, + currentProcess, + permission); + } + + [Svc(0x52)] + public Result UnmapTransferMemory(int handle, [PointerSized] ulong address, [PointerSized] ulong size) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemState; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KTransferMemory transferMemory = currentProcess.HandleTable.GetObject(handle); + + if (transferMemory == null) + { + return KernelResult.InvalidHandle; + } + + if (currentProcess.MemoryManager.IsInvalidRegion(address, size) || + currentProcess.MemoryManager.InsideHeapRegion(address, size) || + currentProcess.MemoryManager.InsideAliasRegion(address, size)) + { + return KernelResult.InvalidMemRange; + } + + return transferMemory.UnmapFromProcess( + currentProcess.MemoryManager, + address, + size, + currentProcess); + } + + [Svc(0x2c)] + public Result MapPhysicalMemory([PointerSized] ulong address, [PointerSized] ulong size) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemRange; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if ((currentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0) + { + return KernelResult.InvalidState; + } + + if (!currentProcess.MemoryManager.InsideAddrSpace(address, size) || + currentProcess.MemoryManager.OutsideAliasRegion(address, size)) + { + return KernelResult.InvalidMemRange; + } + + KProcess process = KernelStatic.GetCurrentProcess(); + + return process.MemoryManager.MapPhysicalMemory(address, size); + } + + [Svc(0x2d)] + public Result UnmapPhysicalMemory([PointerSized] ulong address, [PointerSized] ulong size) + { + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (address + size <= address) + { + return KernelResult.InvalidMemRange; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if ((currentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0) + { + return KernelResult.InvalidState; + } + + if (!currentProcess.MemoryManager.InsideAddrSpace(address, size) || + currentProcess.MemoryManager.OutsideAliasRegion(address, size)) + { + return KernelResult.InvalidMemRange; + } + + KProcess process = KernelStatic.GetCurrentProcess(); + + return process.MemoryManager.UnmapPhysicalMemory(address, size); + } + + [Svc(0x4b)] + public Result CreateCodeMemory(out int handle, [PointerSized] ulong address, [PointerSized] ulong size) + { + handle = 0; + + if (!PageAligned(address)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (size + address <= address) + { + return KernelResult.InvalidMemState; + } + + KCodeMemory codeMemory = new(_context); + + using var _ = new OnScopeExit(codeMemory.DecrementReferenceCount); + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!currentProcess.MemoryManager.InsideAddrSpace(address, size)) + { + return KernelResult.InvalidMemState; + } + + Result result = codeMemory.Initialize(address, size); + + if (result != Result.Success) + { + return result; + } + + return currentProcess.HandleTable.GenerateHandle(codeMemory, out handle); + } + + [Svc(0x4c)] + public Result ControlCodeMemory( + int handle, + CodeMemoryOperation op, + ulong address, + ulong size, + KMemoryPermission permission) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KCodeMemory codeMemory = currentProcess.HandleTable.GetObject(handle); + + // Newer versions of the kernel also returns an error here if the owner and process + // where the operation will happen are the same. We do not return an error here + // for homebrew because some of them requires this to be patched out to work (for JIT). + if (codeMemory == null || (!currentProcess.AllowCodeMemoryForJit && codeMemory.Owner == currentProcess)) + { + return KernelResult.InvalidHandle; + } + + switch (op) + { + case CodeMemoryOperation.Map: + if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeWritable)) + { + return KernelResult.InvalidMemRange; + } + + if (permission != KMemoryPermission.ReadAndWrite) + { + return KernelResult.InvalidPermission; + } + + return codeMemory.Map(address, size, permission); + + case CodeMemoryOperation.MapToOwner: + if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeReadOnly)) + { + return KernelResult.InvalidMemRange; + } + + if (permission != KMemoryPermission.Read && permission != KMemoryPermission.ReadAndExecute) + { + return KernelResult.InvalidPermission; + } + + return codeMemory.MapToOwner(address, size, permission); + + case CodeMemoryOperation.Unmap: + if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeWritable)) + { + return KernelResult.InvalidMemRange; + } + + if (permission != KMemoryPermission.None) + { + return KernelResult.InvalidPermission; + } + + return codeMemory.Unmap(address, size); + + case CodeMemoryOperation.UnmapFromOwner: + if (!currentProcess.MemoryManager.CanContain(address, size, MemoryState.CodeReadOnly)) + { + return KernelResult.InvalidMemRange; + } + + if (permission != KMemoryPermission.None) + { + return KernelResult.InvalidPermission; + } + + return codeMemory.UnmapFromOwner(address, size); + + default: + return KernelResult.InvalidEnumValue; + } + } + + [Svc(0x73)] + public Result SetProcessMemoryPermission( + int handle, + ulong src, + ulong size, + KMemoryPermission permission) + { + if (!PageAligned(src)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (permission != KMemoryPermission.None && + permission != KMemoryPermission.Read && + permission != KMemoryPermission.ReadAndWrite && + permission != KMemoryPermission.ReadAndExecute) + { + return KernelResult.InvalidPermission; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KProcess targetProcess = currentProcess.HandleTable.GetObject(handle); + + if (targetProcess == null) + { + return KernelResult.InvalidHandle; + } + + if (targetProcess.MemoryManager.OutsideAddrSpace(src, size)) + { + return KernelResult.InvalidMemState; + } + + return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission); + } + + [Svc(0x74)] + public Result MapProcessMemory( + [PointerSized] ulong dst, + int handle, + ulong src, + [PointerSized] ulong size) + { + if (!PageAligned(src) || !PageAligned(dst)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (dst + size <= dst || src + size <= src) + { + return KernelResult.InvalidMemRange; + } + + KProcess dstProcess = KernelStatic.GetCurrentProcess(); + KProcess srcProcess = dstProcess.HandleTable.GetObject(handle); + + if (srcProcess == null) + { + return KernelResult.InvalidHandle; + } + + if (!srcProcess.MemoryManager.InsideAddrSpace(src, size) || + !dstProcess.MemoryManager.CanContain(dst, size, MemoryState.ProcessMemory)) + { + return KernelResult.InvalidMemRange; + } + + KPageList pageList = new(); + + Result result = srcProcess.MemoryManager.GetPagesIfStateEquals( + src, + size, + MemoryState.MapProcessAllowed, + MemoryState.MapProcessAllowed, + KMemoryPermission.None, + KMemoryPermission.None, + MemoryAttribute.Mask, + MemoryAttribute.None, + pageList); + + if (result != Result.Success) + { + return result; + } + + return dstProcess.MemoryManager.MapPages(dst, pageList, MemoryState.ProcessMemory, KMemoryPermission.ReadAndWrite); + } + + [Svc(0x75)] + public Result UnmapProcessMemory( + [PointerSized] ulong dst, + int handle, + ulong src, + [PointerSized] ulong size) + { + if (!PageAligned(src) || !PageAligned(dst)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + if (dst + size <= dst || src + size <= src) + { + return KernelResult.InvalidMemRange; + } + + KProcess dstProcess = KernelStatic.GetCurrentProcess(); + KProcess srcProcess = dstProcess.HandleTable.GetObject(handle); + + if (srcProcess == null) + { + return KernelResult.InvalidHandle; + } + + if (!srcProcess.MemoryManager.InsideAddrSpace(src, size) || + !dstProcess.MemoryManager.CanContain(dst, size, MemoryState.ProcessMemory)) + { + return KernelResult.InvalidMemRange; + } + + Result result = dstProcess.MemoryManager.UnmapProcessMemory(dst, size, srcProcess.MemoryManager, src); + + if (result != Result.Success) + { + return result; + } + + return Result.Success; + } + + [Svc(0x77)] + public Result MapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size) + { + if (!PageAligned(dst) || !PageAligned(src)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KProcess targetProcess = currentProcess.HandleTable.GetObject(handle); + + if (targetProcess == null) + { + return KernelResult.InvalidHandle; + } + + if (targetProcess.MemoryManager.OutsideAddrSpace(dst, size) || + targetProcess.MemoryManager.OutsideAddrSpace(src, size) || + targetProcess.MemoryManager.InsideAliasRegion(dst, size) || + targetProcess.MemoryManager.InsideHeapRegion(dst, size)) + { + return KernelResult.InvalidMemRange; + } + + if (size + dst <= dst || size + src <= src) + { + return KernelResult.InvalidMemState; + } + + return targetProcess.MemoryManager.MapProcessCodeMemory(dst, src, size); + } + + [Svc(0x78)] + public Result UnmapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size) + { + if (!PageAligned(dst) || !PageAligned(src)) + { + return KernelResult.InvalidAddress; + } + + if (!PageAligned(size) || size == 0) + { + return KernelResult.InvalidSize; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KProcess targetProcess = currentProcess.HandleTable.GetObject(handle); + + if (targetProcess == null) + { + return KernelResult.InvalidHandle; + } + + if (targetProcess.MemoryManager.OutsideAddrSpace(dst, size) || + targetProcess.MemoryManager.OutsideAddrSpace(src, size) || + targetProcess.MemoryManager.InsideAliasRegion(dst, size) || + targetProcess.MemoryManager.InsideHeapRegion(dst, size)) + { + return KernelResult.InvalidMemRange; + } + + if (size + dst <= dst || size + src <= src) + { + return KernelResult.InvalidMemState; + } + + return targetProcess.MemoryManager.UnmapProcessCodeMemory(dst, src, size); + } + + private static bool PageAligned(ulong address) + { + return (address & (KPageTableBase.PageSize - 1)) == 0; + } + + // System + + [Svc(0x7b)] + public Result TerminateProcess(int handle) + { + KProcess process = KernelStatic.GetCurrentProcess(); + + process = process.HandleTable.GetObject(handle); + + Result result; + + if (process != null) + { + if (process == KernelStatic.GetCurrentProcess()) + { + result = Result.Success; + process.DecrementToZeroWhileTerminatingCurrent(); + } + else + { + result = process.Terminate(); + process.DecrementReferenceCount(); + } + } + else + { + result = KernelResult.InvalidHandle; + } + + return result; + } + + [Svc(7)] + public void ExitProcess() + { + KernelStatic.GetCurrentProcess().TerminateCurrentProcess(); + } + + [Svc(0x11)] + public Result SignalEvent(int handle) + { + KProcess process = KernelStatic.GetCurrentProcess(); + + KWritableEvent writableEvent = process.HandleTable.GetObject(handle); + + Result result; + + if (writableEvent != null) + { + writableEvent.Signal(); + + result = Result.Success; + } + else + { + result = KernelResult.InvalidHandle; + } + + return result; + } + + [Svc(0x12)] + public Result ClearEvent(int handle) + { + Result result; + + KProcess process = KernelStatic.GetCurrentProcess(); + + KWritableEvent writableEvent = process.HandleTable.GetObject(handle); + + if (writableEvent == null) + { + KReadableEvent readableEvent = process.HandleTable.GetObject(handle); + + result = readableEvent?.Clear() ?? KernelResult.InvalidHandle; + } + else + { + result = writableEvent.Clear(); + } + + return result; + } + + [Svc(0x16)] + public Result CloseHandle(int handle) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + return currentProcess.HandleTable.CloseHandle(handle) ? Result.Success : KernelResult.InvalidHandle; + } + + [Svc(0x17)] + public Result ResetSignal(int handle) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KReadableEvent readableEvent = currentProcess.HandleTable.GetObject(handle); + + Result result; + + if (readableEvent != null) + { + result = readableEvent.ClearIfSignaled(); + } + else + { + KProcess process = currentProcess.HandleTable.GetKProcess(handle); + + if (process != null) + { + result = process.ClearIfNotExited(); + } + else + { + result = KernelResult.InvalidHandle; + } + } + + return result; + } + + [Svc(0x1e)] + public ulong GetSystemTick() + { + return _context.TickSource.Counter; + } + + [Svc(0x26)] + public void Break(ulong reason) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + if ((reason & (1UL << 31)) == 0) + { + currentThread.PrintGuestStackTrace(); + currentThread.PrintGuestRegisterPrintout(); + + // As the process is exiting, this is probably caused by emulation termination. + if (currentThread.Owner.State == ProcessState.Exiting) + { + return; + } + + // TODO: Debug events. + currentThread.Owner.TerminateCurrentProcess(); + + throw new GuestBrokeExecutionException(); + } + else + { + Logger.Debug?.Print(LogClass.KernelSvc, "Debugger triggered."); + } + } + + [Svc(0x27)] + public void OutputDebugString([PointerSized] ulong strPtr, [PointerSized] ulong size) + { + KProcess process = KernelStatic.GetCurrentProcess(); + + string str = MemoryHelper.ReadAsciiString(process.CpuMemory, strPtr, (long)size); + + Logger.Warning?.Print(LogClass.KernelSvc, str); + } + + [Svc(0x29)] + public Result GetInfo(out ulong value, InfoType id, int handle, long subId) + { + value = 0; + + switch (id) + { + case InfoType.CoreMask: + case InfoType.PriorityMask: + case InfoType.AliasRegionAddress: + case InfoType.AliasRegionSize: + case InfoType.HeapRegionAddress: + case InfoType.HeapRegionSize: + case InfoType.TotalMemorySize: + case InfoType.UsedMemorySize: + case InfoType.AslrRegionAddress: + case InfoType.AslrRegionSize: + case InfoType.StackRegionAddress: + case InfoType.StackRegionSize: + case InfoType.SystemResourceSizeTotal: + case InfoType.SystemResourceSizeUsed: + case InfoType.ProgramId: + case InfoType.UserExceptionContextAddress: + case InfoType.TotalNonSystemMemorySize: + case InfoType.UsedNonSystemMemorySize: + case InfoType.IsApplication: + case InfoType.FreeThreadCount: + case InfoType.AliasRegionExtraSize: + { + if (subId != 0) + { + return KernelResult.InvalidCombination; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + KProcess process = currentProcess.HandleTable.GetKProcess(handle); + + if (process == null) + { + return KernelResult.InvalidHandle; + } + + switch (id) + { + case InfoType.CoreMask: + value = process.Capabilities.AllowedCpuCoresMask; + break; + case InfoType.PriorityMask: + value = process.Capabilities.AllowedThreadPriosMask; + break; + + case InfoType.AliasRegionAddress: + value = process.MemoryManager.AliasRegionStart; + break; + case InfoType.AliasRegionSize: + value = process.MemoryManager.AliasRegionEnd - process.MemoryManager.AliasRegionStart; + break; + + case InfoType.HeapRegionAddress: + value = process.MemoryManager.HeapRegionStart; + break; + case InfoType.HeapRegionSize: + value = process.MemoryManager.HeapRegionEnd - process.MemoryManager.HeapRegionStart; + break; + + case InfoType.TotalMemorySize: + value = process.GetMemoryCapacity(); + break; + case InfoType.UsedMemorySize: + value = process.GetMemoryUsage(); + break; + + case InfoType.AslrRegionAddress: + value = process.MemoryManager.GetAddrSpaceBaseAddr(); + break; + case InfoType.AslrRegionSize: + value = process.MemoryManager.GetAddrSpaceSize(); + break; + + case InfoType.StackRegionAddress: + value = process.MemoryManager.StackRegionStart; + break; + case InfoType.StackRegionSize: + value = process.MemoryManager.StackRegionEnd - process.MemoryManager.StackRegionStart; + break; + + case InfoType.SystemResourceSizeTotal: + value = process.PersonalMmHeapPagesCount * KPageTableBase.PageSize; + break; + case InfoType.SystemResourceSizeUsed: + if (process.PersonalMmHeapPagesCount != 0) + { + value = process.MemoryManager.GetMmUsedPages() * KPageTableBase.PageSize; + } + break; + + case InfoType.ProgramId: + value = process.TitleId; + break; + + case InfoType.UserExceptionContextAddress: + value = process.UserExceptionContextAddress; + break; + + case InfoType.TotalNonSystemMemorySize: + value = process.GetMemoryCapacityWithoutPersonalMmHeap(); + break; + case InfoType.UsedNonSystemMemorySize: + value = process.GetMemoryUsageWithoutPersonalMmHeap(); + break; + + case InfoType.IsApplication: + value = process.IsApplication ? 1UL : 0UL; + break; + + case InfoType.FreeThreadCount: + if (process.ResourceLimit != null) + { + value = (ulong)(process.ResourceLimit.GetLimitValue(LimitableResource.Thread) - + process.ResourceLimit.GetCurrentValue(LimitableResource.Thread)); + } + else + { + value = 0; + } + break; + + case InfoType.AliasRegionExtraSize: + value = process.MemoryManager.AliasRegionExtraSize; + break; + } + break; + } + + case InfoType.DebuggerAttached: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if (subId != 0) + { + return KernelResult.InvalidCombination; + } + + value = KernelStatic.GetCurrentProcess().Debug ? 1UL : 0UL; + break; + } + + case InfoType.ResourceLimit: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if (subId != 0) + { + return KernelResult.InvalidCombination; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (currentProcess.ResourceLimit != null) + { + KHandleTable handleTable = currentProcess.HandleTable; + KResourceLimit resourceLimit = currentProcess.ResourceLimit; + + Result result = handleTable.GenerateHandle(resourceLimit, out int resLimHandle); + + if (result != Result.Success) + { + return result; + } + + value = (uint)resLimHandle; + } + break; + } + + case InfoType.IdleTickCount: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + int currentCore = KernelStatic.GetCurrentThread().CurrentCore; + + if (subId != -1 && subId != currentCore) + { + return KernelResult.InvalidCombination; + } + + value = (ulong)KTimeManager.ConvertHostTicksToTicks(_context.Schedulers[currentCore].TotalIdleTimeTicks); + break; + } + + case InfoType.RandomEntropy: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if ((ulong)subId > 3) + { + return KernelResult.InvalidCombination; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + value = currentProcess.RandomEntropy[subId]; + break; + } + + case InfoType.ThreadTickCount: + { + if (subId < -1 || subId > 3) + { + return KernelResult.InvalidCombination; + } + + KThread thread = KernelStatic.GetCurrentProcess().HandleTable.GetKThread(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + KThread currentThread = KernelStatic.GetCurrentThread(); + + int currentCore = currentThread.CurrentCore; + + if (subId != -1 && subId != currentCore) + { + return Result.Success; + } + + KScheduler scheduler = _context.Schedulers[currentCore]; + + long timeDelta = PerformanceCounter.ElapsedTicks - scheduler.LastContextSwitchTime; + + if (subId != -1) + { + value = (ulong)KTimeManager.ConvertHostTicksToTicks(timeDelta); + } + else + { + long totalTimeRunning = thread.TotalTimeRunning; + + if (thread == currentThread) + { + totalTimeRunning += timeDelta; + } + + value = (ulong)KTimeManager.ConvertHostTicksToTicks(totalTimeRunning); + } + break; + } + + case InfoType.IsSvcPermitted: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if (subId != 0x36) + { + return KernelResult.InvalidCombination; + } + + value = KernelStatic.GetCurrentProcess().IsSvcPermitted((int)subId) ? 1UL : 0UL; + break; + } + + case InfoType.MesosphereCurrentProcess: + { + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if (subId != 0) + { + return KernelResult.InvalidCombination; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + KHandleTable handleTable = currentProcess.HandleTable; + + Result result = handleTable.GenerateHandle(currentProcess, out int outHandle); + + if (result != Result.Success) + { + return result; + } + + value = (uint)outHandle; + break; + } + + default: + return KernelResult.InvalidEnumValue; + } + + return Result.Success; + } + + [Svc(0x45)] + public Result CreateEvent(out int wEventHandle, out int rEventHandle) + { + KEvent Event = new(_context); + + KProcess process = KernelStatic.GetCurrentProcess(); + + Result result = process.HandleTable.GenerateHandle(Event.WritableEvent, out wEventHandle); + + if (result == Result.Success) + { + result = process.HandleTable.GenerateHandle(Event.ReadableEvent, out rEventHandle); + + if (result != Result.Success) + { + process.HandleTable.CloseHandle(wEventHandle); + } + } + else + { + rEventHandle = 0; + } + + return result; + } + + [Svc(0x65)] + public Result GetProcessList(out int count, [PointerSized] ulong address, int maxCount) + { + count = 0; + + if ((maxCount >> 28) != 0) + { + return KernelResult.MaximumExceeded; + } + + if (maxCount != 0) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + ulong copySize = (ulong)maxCount * 8; + + if (address + copySize <= address) + { + return KernelResult.InvalidMemState; + } + + if (currentProcess.MemoryManager.OutsideAddrSpace(address, copySize)) + { + return KernelResult.InvalidMemState; + } + } + + int copyCount = 0; + + lock (_context.Processes) + { + foreach (KProcess process in _context.Processes.Values) + { + if (copyCount < maxCount) + { + if (!KernelTransfer.KernelToUser(address + (ulong)copyCount * 8, process.Pid)) + { + return KernelResult.UserCopyFailed; + } + } + + copyCount++; + } + } + + count = copyCount; + + return Result.Success; + } + + [Svc(0x6f)] + public Result GetSystemInfo(out long value, uint id, int handle, long subId) + { + value = 0; + + if (id > 2) + { + return KernelResult.InvalidEnumValue; + } + + if (handle != 0) + { + return KernelResult.InvalidHandle; + } + + if (id < 2) + { + if ((ulong)subId > 3) + { + return KernelResult.InvalidCombination; + } + + KMemoryRegionManager region = _context.MemoryManager.MemoryRegions[subId]; + + switch (id) + { + // Memory region capacity. + case 0: + value = (long)region.Size; + break; + + // Memory region free space. + case 1: + { + ulong freePagesCount = region.GetFreePages(); + + value = (long)(freePagesCount * KPageTableBase.PageSize); + + break; + } + } + } + else /* if (Id == 2) */ + { + if ((ulong)subId > 1) + { + return KernelResult.InvalidCombination; + } + + switch (subId) + { + case 0: + value = _context.PrivilegedProcessLowestId; + break; + case 1: + value = _context.PrivilegedProcessHighestId; + break; + } + } + + return Result.Success; + } + + [Svc(0x30)] + public Result GetResourceLimitLimitValue(out long limitValue, int handle, LimitableResource resource) + { + limitValue = 0; + + if (resource >= LimitableResource.Count) + { + return KernelResult.InvalidEnumValue; + } + + KResourceLimit resourceLimit = KernelStatic.GetCurrentProcess().HandleTable.GetObject(handle); + + if (resourceLimit == null) + { + return KernelResult.InvalidHandle; + } + + limitValue = resourceLimit.GetLimitValue(resource); + + return Result.Success; + } + + [Svc(0x31)] + public Result GetResourceLimitCurrentValue(out long limitValue, int handle, LimitableResource resource) + { + limitValue = 0; + + if (resource >= LimitableResource.Count) + { + return KernelResult.InvalidEnumValue; + } + + KResourceLimit resourceLimit = KernelStatic.GetCurrentProcess().HandleTable.GetObject(handle); + + if (resourceLimit == null) + { + return KernelResult.InvalidHandle; + } + + limitValue = resourceLimit.GetCurrentValue(resource); + + return Result.Success; + } + + [Svc(0x37)] + public Result GetResourceLimitPeakValue(out long peak, int handle, LimitableResource resource) + { + peak = 0; + + if (resource >= LimitableResource.Count) + { + return KernelResult.InvalidEnumValue; + } + + KResourceLimit resourceLimit = KernelStatic.GetCurrentProcess().HandleTable.GetObject(handle); + + if (resourceLimit == null) + { + return KernelResult.InvalidHandle; + } + + peak = resourceLimit.GetPeakValue(resource); + + return Result.Success; + } + + [Svc(0x7d)] + public Result CreateResourceLimit(out int handle) + { + KResourceLimit limit = new(_context); + + KProcess process = KernelStatic.GetCurrentProcess(); + + return process.HandleTable.GenerateHandle(limit, out handle); + } + + [Svc(0x7e)] + public Result SetResourceLimitLimitValue(int handle, LimitableResource resource, long limitValue) + { + if (resource >= LimitableResource.Count) + { + return KernelResult.InvalidEnumValue; + } + + KResourceLimit resourceLimit = KernelStatic.GetCurrentProcess().HandleTable.GetObject(handle); + + if (resourceLimit == null) + { + return KernelResult.InvalidHandle; + } + + return resourceLimit.SetLimitValue(resource, limitValue); + } + + // Thread + + [Svc(8)] + public Result CreateThread( + out int handle, + [PointerSized] ulong entrypoint, + [PointerSized] ulong argsPtr, + [PointerSized] ulong stackTop, + int priority, + int cpuCore) + { + return CreateThread(out handle, entrypoint, argsPtr, stackTop, priority, cpuCore, null); + } + + public Result CreateThread( + out int handle, + ulong entrypoint, + ulong argsPtr, + ulong stackTop, + int priority, + int cpuCore, + ThreadStart customThreadStart) + { + handle = 0; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (cpuCore == -2) + { + cpuCore = currentProcess.DefaultCpuCore; + } + + if ((uint)cpuCore >= KScheduler.CpuCoresCount || !currentProcess.IsCpuCoreAllowed(cpuCore)) + { + return KernelResult.InvalidCpuCore; + } + + if ((uint)priority >= KScheduler.PrioritiesCount || !currentProcess.IsPriorityAllowed(priority)) + { + return KernelResult.InvalidPriority; + } + + long timeout = KTimeManager.ConvertMillisecondsToNanoseconds(100); + + if (currentProcess.ResourceLimit != null && + !currentProcess.ResourceLimit.Reserve(LimitableResource.Thread, 1, timeout)) + { + return KernelResult.ResLimitExceeded; + } + + KThread thread = new(_context); + + Result result = currentProcess.InitializeThread( + thread, + entrypoint, + argsPtr, + stackTop, + priority, + cpuCore, + customThreadStart); + + if (result == Result.Success) + { + KProcess process = KernelStatic.GetCurrentProcess(); + + result = process.HandleTable.GenerateHandle(thread, out handle); + } + else + { + currentProcess.ResourceLimit?.Release(LimitableResource.Thread, 1); + } + + thread.DecrementReferenceCount(); + + return result; + } + + [Svc(9)] + public Result StartThread(int handle) + { + KProcess process = KernelStatic.GetCurrentProcess(); + + KThread thread = process.HandleTable.GetKThread(handle); + + if (thread != null) + { + thread.IncrementReferenceCount(); + + Result result = thread.Start(); + + if (result == Result.Success) + { + thread.IncrementReferenceCount(); + } + + thread.DecrementReferenceCount(); + + return result; + } + else + { + return KernelResult.InvalidHandle; + } + } + + [Svc(0xa)] + public void ExitThread() + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + currentThread.Exit(); + } + + [Svc(0xb)] + public void SleepThread(long timeout) + { + if (timeout < 1) + { + switch (timeout) + { + case 0: + KScheduler.Yield(_context); + break; + case -1: + KScheduler.YieldWithLoadBalancing(_context); + break; + case -2: + KScheduler.YieldToAnyThread(_context); + break; + } + } + else + { + KernelStatic.GetCurrentThread().Sleep(timeout + KTimeManager.DefaultTimeIncrementNanoseconds); + } + } + + [Svc(0xc)] + public Result GetThreadPriority(out int priority, int handle) + { + KProcess process = KernelStatic.GetCurrentProcess(); + + KThread thread = process.HandleTable.GetKThread(handle); + + if (thread != null) + { + priority = thread.DynamicPriority; + + return Result.Success; + } + else + { + priority = 0; + + return KernelResult.InvalidHandle; + } + } + + [Svc(0xd)] + public Result SetThreadPriority(int handle, int priority) + { + // TODO: NPDM check. + + KProcess process = KernelStatic.GetCurrentProcess(); + + KThread thread = process.HandleTable.GetKThread(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + thread.SetPriority(priority); + + return Result.Success; + } + + [Svc(0xe)] + public Result GetThreadCoreMask(out int preferredCore, out ulong affinityMask, int handle) + { + KProcess process = KernelStatic.GetCurrentProcess(); + + KThread thread = process.HandleTable.GetKThread(handle); + + if (thread != null) + { + preferredCore = thread.PreferredCore; + affinityMask = thread.AffinityMask; + + return Result.Success; + } + else + { + preferredCore = 0; + affinityMask = 0; + + return KernelResult.InvalidHandle; + } + } + + [Svc(0xf)] + public Result SetThreadCoreMask(int handle, int preferredCore, ulong affinityMask) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (preferredCore == -2) + { + preferredCore = currentProcess.DefaultCpuCore; + + affinityMask = 1UL << preferredCore; + } + else + { + if ((currentProcess.Capabilities.AllowedCpuCoresMask | affinityMask) != + currentProcess.Capabilities.AllowedCpuCoresMask) + { + return KernelResult.InvalidCpuCore; + } + + if (affinityMask == 0) + { + return KernelResult.InvalidCombination; + } + + if ((uint)preferredCore > 3) + { + if ((preferredCore | 2) != -1) + { + return KernelResult.InvalidCpuCore; + } + } + else if ((affinityMask & (1UL << preferredCore)) == 0) + { + return KernelResult.InvalidCombination; + } + } + + KProcess process = KernelStatic.GetCurrentProcess(); + + KThread thread = process.HandleTable.GetKThread(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + return thread.SetCoreAndAffinityMask(preferredCore, affinityMask); + } + + [Svc(0x10)] + public int GetCurrentProcessorNumber() + { + return KernelStatic.GetCurrentThread().CurrentCore; + } + + [Svc(0x25)] + public Result GetThreadId(out ulong threadUid, int handle) + { + KProcess process = KernelStatic.GetCurrentProcess(); + + KThread thread = process.HandleTable.GetKThread(handle); + + if (thread != null) + { + threadUid = thread.ThreadUid; + + return Result.Success; + } + else + { + threadUid = 0; + + return KernelResult.InvalidHandle; + } + } + + [Svc(0x32)] + public Result SetThreadActivity(int handle, bool pause) + { + KProcess process = KernelStatic.GetCurrentProcess(); + + KThread thread = process.HandleTable.GetObject(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + if (thread.Owner != process) + { + return KernelResult.InvalidHandle; + } + + if (thread == KernelStatic.GetCurrentThread()) + { + return KernelResult.InvalidThread; + } + + return thread.SetActivity(pause); + } + + [Svc(0x33)] + public Result GetThreadContext3([PointerSized] ulong address, int handle) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + KThread currentThread = KernelStatic.GetCurrentThread(); + + KThread thread = currentProcess.HandleTable.GetObject(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + if (thread.Owner != currentProcess) + { + return KernelResult.InvalidHandle; + } + + if (currentThread == thread) + { + return KernelResult.InvalidThread; + } + + Result result = thread.GetThreadContext3(out ThreadContext context); + + if (result == Result.Success) + { + return KernelTransfer.KernelToUser(address, context) + ? Result.Success + : KernelResult.InvalidMemState; + } + + return result; + } + + // Thread synchronization + + [Svc(0x18)] + public Result WaitSynchronization(out int handleIndex, [PointerSized] ulong handlesPtr, int handlesCount, long timeout) + { + handleIndex = 0; + + if ((uint)handlesCount > KThread.MaxWaitSyncObjects) + { + return KernelResult.MaximumExceeded; + } + + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (handlesCount != 0) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (currentProcess.MemoryManager.AddrSpaceStart > handlesPtr) + { + return KernelResult.UserCopyFailed; + } + + long handlesSize = handlesCount * 4; + + if (handlesPtr + (ulong)handlesSize <= handlesPtr) + { + return KernelResult.UserCopyFailed; + } + + if (handlesPtr + (ulong)handlesSize - 1 > currentProcess.MemoryManager.AddrSpaceEnd - 1) + { + return KernelResult.UserCopyFailed; + } + + Span handles = new Span(currentThread.WaitSyncHandles)[..handlesCount]; + + if (!KernelTransfer.UserToKernelArray(handlesPtr, handles)) + { + return KernelResult.UserCopyFailed; + } + + return WaitSynchronization(out handleIndex, handles, timeout); + } + + return WaitSynchronization(out handleIndex, ReadOnlySpan.Empty, timeout); + } + + public Result WaitSynchronization(out int handleIndex, ReadOnlySpan handles, long timeout) + { + handleIndex = 0; + + if ((uint)handles.Length > KThread.MaxWaitSyncObjects) + { + return KernelResult.MaximumExceeded; + } + + KThread currentThread = KernelStatic.GetCurrentThread(); + + var syncObjs = new Span(currentThread.WaitSyncObjects)[..handles.Length]; + + if (handles.Length != 0) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + int processedHandles = 0; + + for (; processedHandles < handles.Length; processedHandles++) + { + KSynchronizationObject syncObj = currentProcess.HandleTable.GetObject(handles[processedHandles]); + + if (syncObj == null) + { + break; + } + + syncObjs[processedHandles] = syncObj; + + syncObj.IncrementReferenceCount(); + } + + if (processedHandles != handles.Length) + { + // One or more handles are invalid. + for (int index = 0; index < processedHandles; index++) + { + currentThread.WaitSyncObjects[index].DecrementReferenceCount(); + } + + return KernelResult.InvalidHandle; + } + } + + if (timeout > 0) + { + timeout += KTimeManager.DefaultTimeIncrementNanoseconds; + } + + Result result = _context.Synchronization.WaitFor(syncObjs, timeout, out handleIndex); + + if (result == KernelResult.PortRemoteClosed) + { + result = Result.Success; + } + + for (int index = 0; index < handles.Length; index++) + { + currentThread.WaitSyncObjects[index].DecrementReferenceCount(); + } + + return result; + } + + [Svc(0x19)] + public Result CancelSynchronization(int handle) + { + KProcess process = KernelStatic.GetCurrentProcess(); + + KThread thread = process.HandleTable.GetKThread(handle); + + if (thread == null) + { + return KernelResult.InvalidHandle; + } + + thread.CancelSynchronization(); + + return Result.Success; + } + + [Svc(0x1a)] + public Result ArbitrateLock(int ownerHandle, [PointerSized] ulong mutexAddress, int requesterHandle) + { + if (IsPointingInsideKernel(mutexAddress)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(mutexAddress)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + return currentProcess.AddressArbiter.ArbitrateLock(ownerHandle, mutexAddress, requesterHandle); + } + + [Svc(0x1b)] + public Result ArbitrateUnlock([PointerSized] ulong mutexAddress) + { + if (IsPointingInsideKernel(mutexAddress)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(mutexAddress)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + return currentProcess.AddressArbiter.ArbitrateUnlock(mutexAddress); + } + + [Svc(0x1c)] + public Result WaitProcessWideKeyAtomic( + [PointerSized] ulong mutexAddress, + [PointerSized] ulong condVarAddress, + int handle, + long timeout) + { + if (IsPointingInsideKernel(mutexAddress)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(mutexAddress)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (timeout > 0) + { + timeout += KTimeManager.DefaultTimeIncrementNanoseconds; + } + + return currentProcess.AddressArbiter.WaitProcessWideKeyAtomic( + mutexAddress, + condVarAddress, + handle, + timeout); + } + + [Svc(0x1d)] + public Result SignalProcessWideKey([PointerSized] ulong address, int count) + { + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + currentProcess.AddressArbiter.SignalProcessWideKey(address, count); + + return Result.Success; + } + + [Svc(0x34)] + public Result WaitForAddress([PointerSized] ulong address, ArbitrationType type, int value, long timeout) + { + if (IsPointingInsideKernel(address)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(address)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (timeout > 0) + { + timeout += KTimeManager.DefaultTimeIncrementNanoseconds; + } + + return type switch + { + ArbitrationType.WaitIfLessThan + => currentProcess.AddressArbiter.WaitForAddressIfLessThan(address, value, false, timeout), + ArbitrationType.DecrementAndWaitIfLessThan + => currentProcess.AddressArbiter.WaitForAddressIfLessThan(address, value, true, timeout), + ArbitrationType.WaitIfEqual + => currentProcess.AddressArbiter.WaitForAddressIfEqual(address, value, timeout), + _ => KernelResult.InvalidEnumValue, + }; + } + + [Svc(0x35)] + public Result SignalToAddress([PointerSized] ulong address, SignalType type, int value, int count) + { + if (IsPointingInsideKernel(address)) + { + return KernelResult.InvalidMemState; + } + + if (IsAddressNotWordAligned(address)) + { + return KernelResult.InvalidAddress; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + return type switch + { + SignalType.Signal + => currentProcess.AddressArbiter.Signal(address, count), + SignalType.SignalAndIncrementIfEqual + => currentProcess.AddressArbiter.SignalAndIncrementIfEqual(address, value, count), + SignalType.SignalAndModifyIfEqual + => currentProcess.AddressArbiter.SignalAndModifyIfEqual(address, value, count), + _ => KernelResult.InvalidEnumValue, + }; + } + + [Svc(0x36)] + public Result SynchronizePreemptionState() + { + KernelStatic.GetCurrentThread().SynchronizePreemptionState(); + + return Result.Success; + } + + // Not actual syscalls, used by HLE services and such. + + public IExternalEvent GetExternalEvent(int handle) + { + KWritableEvent writableEvent = KernelStatic.GetCurrentProcess().HandleTable.GetObject(handle); + + if (writableEvent == null) + { + return null; + } + + return new ExternalEvent(writableEvent); + } + + public IVirtualMemoryManager GetMemoryManagerByProcessHandle(int handle) + { + return KernelStatic.GetCurrentProcess().HandleTable.GetKProcess(handle).CpuMemory; + } + + public ulong GetTransferMemoryAddress(int handle) + { + KTransferMemory transferMemory = KernelStatic.GetCurrentProcess().HandleTable.GetObject(handle); + + if (transferMemory == null) + { + return 0; + } + + return transferMemory.Address; + } + + private static bool IsPointingInsideKernel(ulong address) + { + return (address + 0x1000000000) < 0xffffff000; + } + + private static bool IsAddressNotWordAligned(ulong address) + { + return (address & 3) != 0; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs new file mode 100644 index 00000000..72f90642 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs @@ -0,0 +1,44 @@ +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + partial class SyscallHandler + { + private readonly KernelContext _context; + + public SyscallHandler(KernelContext context) + { + _context = context; + } + + public void SvcCall(IExecutionContext context, ulong address, int id) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (currentThread.Owner != null && + currentThread.GetUserDisableCount() != 0 && + currentThread.Owner.PinnedThreads[currentThread.CurrentCore] == null) + { + _context.CriticalSection.Enter(); + + currentThread.Owner.PinThread(currentThread); + + currentThread.SetUserInterruptFlag(); + + _context.CriticalSection.Leave(); + } + + if (context.IsAarch32) + { + SyscallDispatch.Dispatch32(_context.Syscall, context, id); + } + else + { + SyscallDispatch.Dispatch64(_context.Syscall, context, id); + } + + currentThread.HandlePostSyscall(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ThreadContext.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ThreadContext.cs new file mode 100644 index 00000000..cca6dda0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ThreadContext.cs @@ -0,0 +1,22 @@ +using ARMeilleure.State; +using Ryujinx.Common.Memory; + +namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall +{ + struct ThreadContext + { + public Array29 Registers; + public ulong Fp; + public ulong Lr; + public ulong Sp; + public ulong Pc; + public uint Pstate; +#pragma warning disable CS0169, IDE0051 // Remove unused private member + private readonly uint _padding; +#pragma warning restore CS0169, IDE0051 + public Array32 FpuRegisters; + public uint Fpcr; + public uint Fpsr; + public ulong Tpidr; + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs new file mode 100644 index 00000000..ddcf0202 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + enum ArbitrationType + { + WaitIfLessThan = 0, + DecrementAndWaitIfLessThan = 1, + WaitIfEqual = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs new file mode 100644 index 00000000..f6b9a112 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs @@ -0,0 +1,575 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KAddressArbiter + { + private const int HasListenersMask = 0x40000000; + + private readonly KernelContext _context; + + private readonly List _condVarThreads; + private readonly List _arbiterThreads; + + public KAddressArbiter(KernelContext context) + { + _context = context; + + _condVarThreads = new List(); + _arbiterThreads = new List(); + } + + public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + _context.CriticalSection.Enter(); + + if (currentThread.TerminationRequested) + { + _context.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = Result.Success; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!KernelTransfer.UserToKernel(out int mutexValue, mutexAddress)) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + if (mutexValue != (ownerHandle | HasListenersMask)) + { + _context.CriticalSection.Leave(); + + return Result.Success; + } + + KThread mutexOwner = currentProcess.HandleTable.GetObject(ownerHandle); + + if (mutexOwner == null) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidHandle; + } + + currentThread.MutexAddress = mutexAddress; + currentThread.ThreadHandleForUserMutex = requesterHandle; + + mutexOwner.AddMutexWaiter(currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + + _context.CriticalSection.Leave(); + _context.CriticalSection.Enter(); + + currentThread.MutexOwner?.RemoveMutexWaiter(currentThread); + + _context.CriticalSection.Leave(); + + return currentThread.ObjSyncResult; + } + + public Result ArbitrateUnlock(ulong mutexAddress) + { + _context.CriticalSection.Enter(); + + KThread currentThread = KernelStatic.GetCurrentThread(); + + (int mutexValue, KThread newOwnerThread) = MutexUnlock(currentThread, mutexAddress); + + Result result = Result.Success; + + if (!KernelTransfer.KernelToUser(mutexAddress, mutexValue)) + { + result = KernelResult.InvalidMemState; + } + + if (result != Result.Success && newOwnerThread != null) + { + newOwnerThread.SignaledObj = null; + newOwnerThread.ObjSyncResult = result; + } + + _context.CriticalSection.Leave(); + + return result; + } + + public Result WaitProcessWideKeyAtomic(ulong mutexAddress, ulong condVarAddress, int threadHandle, long timeout) + { + _context.CriticalSection.Enter(); + + KThread currentThread = KernelStatic.GetCurrentThread(); + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.TimedOut; + + if (currentThread.TerminationRequested) + { + _context.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + (int mutexValue, _) = MutexUnlock(currentThread, mutexAddress); + + KernelTransfer.KernelToUser(condVarAddress, 1); + + if (!KernelTransfer.KernelToUser(mutexAddress, mutexValue)) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + currentThread.MutexAddress = mutexAddress; + currentThread.ThreadHandleForUserMutex = threadHandle; + currentThread.CondVarAddress = condVarAddress; + + _condVarThreads.Add(currentThread); + + if (timeout != 0) + { + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _context.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + } + + _context.CriticalSection.Leave(); + + if (timeout > 0) + { + _context.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _context.CriticalSection.Enter(); + + currentThread.MutexOwner?.RemoveMutexWaiter(currentThread); + + _condVarThreads.Remove(currentThread); + + _context.CriticalSection.Leave(); + + return currentThread.ObjSyncResult; + } + + private static (int, KThread) MutexUnlock(KThread currentThread, ulong mutexAddress) + { + KThread newOwnerThread = currentThread.RelinquishMutex(mutexAddress, out int count); + + int mutexValue = 0; + + if (newOwnerThread != null) + { + mutexValue = newOwnerThread.ThreadHandleForUserMutex; + + if (count >= 2) + { + mutexValue |= HasListenersMask; + } + + newOwnerThread.SignaledObj = null; + newOwnerThread.ObjSyncResult = Result.Success; + + newOwnerThread.ReleaseAndResume(); + } + + return (mutexValue, newOwnerThread); + } + + public void SignalProcessWideKey(ulong address, int count) + { + _context.CriticalSection.Enter(); + + WakeThreads(_condVarThreads, count, TryAcquireMutex, x => x.CondVarAddress == address); + + if (!_condVarThreads.Exists(x => x.CondVarAddress == address)) + { + KernelTransfer.KernelToUser(address, 0); + } + + _context.CriticalSection.Leave(); + } + + private static void TryAcquireMutex(KThread requester) + { + ulong address = requester.MutexAddress; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!currentProcess.CpuMemory.IsMapped(address)) + { + // Invalid address. + requester.SignaledObj = null; + requester.ObjSyncResult = KernelResult.InvalidMemState; + + return; + } + + ref int mutexRef = ref currentProcess.CpuMemory.GetRef(address); + + int mutexValue, newMutexValue; + + do + { + mutexValue = mutexRef; + + if (mutexValue != 0) + { + // Update value to indicate there is a mutex waiter now. + newMutexValue = mutexValue | HasListenersMask; + } + else + { + // No thread owning the mutex, assign to requesting thread. + newMutexValue = requester.ThreadHandleForUserMutex; + } + } + while (Interlocked.CompareExchange(ref mutexRef, newMutexValue, mutexValue) != mutexValue); + + if (mutexValue == 0) + { + // We now own the mutex. + requester.SignaledObj = null; + requester.ObjSyncResult = Result.Success; + + requester.ReleaseAndResume(); + + return; + } + + mutexValue &= ~HasListenersMask; + + KThread mutexOwner = currentProcess.HandleTable.GetObject(mutexValue); + + if (mutexOwner != null) + { + // Mutex already belongs to another thread, wait for it. + mutexOwner.AddMutexWaiter(requester); + } + else + { + // Invalid mutex owner. + requester.SignaledObj = null; + requester.ObjSyncResult = KernelResult.InvalidHandle; + + requester.ReleaseAndResume(); + } + } + + public Result WaitForAddressIfEqual(ulong address, int value, long timeout) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + _context.CriticalSection.Enter(); + + if (currentThread.TerminationRequested) + { + _context.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.TimedOut; + + if (!KernelTransfer.UserToKernel(out int currentValue, address)) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + if (currentValue == value) + { + if (timeout == 0) + { + _context.CriticalSection.Leave(); + + return KernelResult.TimedOut; + } + + currentThread.MutexAddress = address; + currentThread.WaitingInArbitration = true; + + _arbiterThreads.Add(currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _context.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + + _context.CriticalSection.Leave(); + + if (timeout > 0) + { + _context.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _context.CriticalSection.Enter(); + + if (currentThread.WaitingInArbitration) + { + _arbiterThreads.Remove(currentThread); + + currentThread.WaitingInArbitration = false; + } + + _context.CriticalSection.Leave(); + + return currentThread.ObjSyncResult; + } + + _context.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + public Result WaitForAddressIfLessThan(ulong address, int value, bool shouldDecrement, long timeout) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + _context.CriticalSection.Enter(); + + if (currentThread.TerminationRequested) + { + _context.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = KernelResult.TimedOut; + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!KernelTransfer.UserToKernel(out int currentValue, address)) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + if (shouldDecrement) + { + currentValue = Interlocked.Decrement(ref currentProcess.CpuMemory.GetRef(address)) + 1; + } + + if (currentValue < value) + { + if (timeout == 0) + { + _context.CriticalSection.Leave(); + + return KernelResult.TimedOut; + } + + currentThread.MutexAddress = address; + currentThread.WaitingInArbitration = true; + + _arbiterThreads.Add(currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _context.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + + _context.CriticalSection.Leave(); + + if (timeout > 0) + { + _context.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _context.CriticalSection.Enter(); + + if (currentThread.WaitingInArbitration) + { + _arbiterThreads.Remove(currentThread); + + currentThread.WaitingInArbitration = false; + } + + _context.CriticalSection.Leave(); + + return currentThread.ObjSyncResult; + } + + _context.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + public Result Signal(ulong address, int count) + { + _context.CriticalSection.Enter(); + + WakeArbiterThreads(address, count); + + _context.CriticalSection.Leave(); + + return Result.Success; + } + + public Result SignalAndIncrementIfEqual(ulong address, int value, int count) + { + _context.CriticalSection.Enter(); + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!currentProcess.CpuMemory.IsMapped(address)) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + ref int valueRef = ref currentProcess.CpuMemory.GetRef(address); + + int currentValue; + + do + { + currentValue = valueRef; + + if (currentValue != value) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + } + while (Interlocked.CompareExchange(ref valueRef, currentValue + 1, currentValue) != currentValue); + + WakeArbiterThreads(address, count); + + _context.CriticalSection.Leave(); + + return Result.Success; + } + + public Result SignalAndModifyIfEqual(ulong address, int value, int count) + { + _context.CriticalSection.Enter(); + + int addend; + + // The value is decremented if the number of threads waiting is less + // or equal to the Count of threads to be signaled, or Count is zero + // or negative. It is incremented if there are no threads waiting. + int waitingCount = 0; + + foreach (KThread thread in _arbiterThreads.Where(x => x.MutexAddress == address)) + { + if (++waitingCount >= count) + { + break; + } + } + + if (waitingCount > 0) + { + if (count <= 0) + { + addend = -2; + } + else if (waitingCount < count) + { + addend = -1; + } + else + { + addend = 0; + } + } + else + { + addend = 1; + } + + KProcess currentProcess = KernelStatic.GetCurrentProcess(); + + if (!currentProcess.CpuMemory.IsMapped(address)) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidMemState; + } + + ref int valueRef = ref currentProcess.CpuMemory.GetRef(address); + + int currentValue; + + do + { + currentValue = valueRef; + + if (currentValue != value) + { + _context.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + } + while (Interlocked.CompareExchange(ref valueRef, currentValue + addend, currentValue) != currentValue); + + WakeArbiterThreads(address, count); + + _context.CriticalSection.Leave(); + + return Result.Success; + } + + private void WakeArbiterThreads(ulong address, int count) + { + static void RemoveArbiterThread(KThread thread) + { + thread.SignaledObj = null; + thread.ObjSyncResult = Result.Success; + + thread.ReleaseAndResume(); + + thread.WaitingInArbitration = false; + } + + WakeThreads(_arbiterThreads, count, RemoveArbiterThread, x => x.MutexAddress == address); + } + + private static void WakeThreads( + List threads, + int count, + Action removeCallback, + Func predicate) + { + var candidates = threads.Where(predicate).OrderBy(x => x.DynamicPriority); + var toSignal = (count > 0 ? candidates.Take(count) : candidates).ToArray(); + + foreach (KThread thread in toSignal) + { + removeCallback(thread); + threads.Remove(thread); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs new file mode 100644 index 00000000..c6aa984c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + static class KConditionVariable + { + public static void Wait(KernelContext context, LinkedList threadList, object mutex, long timeout) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + context.CriticalSection.Enter(); + + Monitor.Exit(mutex); + + currentThread.Withholder = threadList; + + currentThread.Reschedule(ThreadSchedState.Paused); + + currentThread.WithholderNode = threadList.AddLast(currentThread); + + if (currentThread.TerminationRequested) + { + threadList.Remove(currentThread.WithholderNode); + + currentThread.Reschedule(ThreadSchedState.Running); + + currentThread.Withholder = null; + + context.CriticalSection.Leave(); + } + else + { + if (timeout > 0) + { + context.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + + context.CriticalSection.Leave(); + + if (timeout > 0) + { + context.TimeManager.UnscheduleFutureInvocation(currentThread); + } + } + + Monitor.Enter(mutex); + } + + public static void NotifyAll(KernelContext context, LinkedList threadList) + { + context.CriticalSection.Enter(); + + LinkedListNode node = threadList.First; + + for (; node != null; node = threadList.First) + { + KThread thread = node.Value; + + threadList.Remove(thread.WithholderNode); + + thread.Withholder = null; + + thread.Reschedule(ThreadSchedState.Running); + } + + context.CriticalSection.Leave(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs new file mode 100644 index 00000000..3d674488 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs @@ -0,0 +1,64 @@ +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KCriticalSection + { + private readonly KernelContext _context; + private readonly object _lock; + private int _recursionCount; + + public object Lock => _lock; + + public KCriticalSection(KernelContext context) + { + _context = context; + _lock = new object(); + } + + public void Enter() + { + Monitor.Enter(_lock); + + _recursionCount++; + } + + public void Leave() + { + if (_recursionCount == 0) + { + return; + } + + if (--_recursionCount == 0) + { + ulong scheduledCoresMask = KScheduler.SelectThreads(_context); + + Monitor.Exit(_lock); + + KThread currentThread = KernelStatic.GetCurrentThread(); + bool isCurrentThreadSchedulable = currentThread != null && currentThread.IsSchedulable; + if (isCurrentThreadSchedulable) + { + KScheduler.EnableScheduling(_context, scheduledCoresMask); + } + else + { + KScheduler.EnableSchedulingFromForeignThread(_context, scheduledCoresMask); + + // If the thread exists but is not schedulable, we still want to suspend + // it if it's not runnable. That allows the kernel to still block HLE threads + // even if they are not scheduled on guest cores. + if (currentThread != null && !currentThread.IsSchedulable && currentThread.Context.Running) + { + currentThread.SchedulerWaitEvent.WaitOne(); + } + } + } + else + { + Monitor.Exit(_lock); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs new file mode 100644 index 00000000..65169d03 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KEvent + { + public KReadableEvent ReadableEvent { get; private set; } + public KWritableEvent WritableEvent { get; private set; } + + public KEvent(KernelContext context) + { + ReadableEvent = new KReadableEvent(context); + WritableEvent = new KWritableEvent(context, this); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs new file mode 100644 index 00000000..1608db09 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs @@ -0,0 +1,286 @@ +using System.Collections.Generic; +using System.Numerics; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KPriorityQueue + { + private readonly LinkedList[][] _scheduledThreadsPerPrioPerCore; + private readonly LinkedList[][] _suggestedThreadsPerPrioPerCore; + + private readonly long[] _scheduledPrioritiesPerCore; + private readonly long[] _suggestedPrioritiesPerCore; + + public KPriorityQueue() + { + _suggestedThreadsPerPrioPerCore = new LinkedList[KScheduler.PrioritiesCount][]; + _scheduledThreadsPerPrioPerCore = new LinkedList[KScheduler.PrioritiesCount][]; + + for (int prio = 0; prio < KScheduler.PrioritiesCount; prio++) + { + _suggestedThreadsPerPrioPerCore[prio] = new LinkedList[KScheduler.CpuCoresCount]; + _scheduledThreadsPerPrioPerCore[prio] = new LinkedList[KScheduler.CpuCoresCount]; + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + _suggestedThreadsPerPrioPerCore[prio][core] = new LinkedList(); + _scheduledThreadsPerPrioPerCore[prio][core] = new LinkedList(); + } + } + + _scheduledPrioritiesPerCore = new long[KScheduler.CpuCoresCount]; + _suggestedPrioritiesPerCore = new long[KScheduler.CpuCoresCount]; + } + + public readonly ref struct KThreadEnumerable + { + readonly LinkedList[][] _listPerPrioPerCore; + readonly long[] _prios; + readonly int _core; + + public KThreadEnumerable(LinkedList[][] listPerPrioPerCore, long[] prios, int core) + { + _listPerPrioPerCore = listPerPrioPerCore; + _prios = prios; + _core = core; + } + + public Enumerator GetEnumerator() + { + return new Enumerator(_listPerPrioPerCore, _prios, _core); + } + + public ref struct Enumerator + { + private readonly LinkedList[][] _listPerPrioPerCore; + private readonly int _core; + private long _prioMask; + private int _prio; + private LinkedList _list; + private LinkedListNode _node; + + public Enumerator(LinkedList[][] listPerPrioPerCore, long[] prios, int core) + { + _listPerPrioPerCore = listPerPrioPerCore; + _core = core; + _prioMask = prios[core]; + _prio = BitOperations.TrailingZeroCount(_prioMask); + _prioMask &= ~(1L << _prio); + } + + public readonly KThread Current => _node?.Value; + + public bool MoveNext() + { + _node = _node?.Next; + + if (_node == null) + { + if (!MoveNextListAndFirstNode()) + { + return false; + } + } + + return _node != null; + } + + private bool MoveNextListAndFirstNode() + { + if (_prio < KScheduler.PrioritiesCount) + { + _list = _listPerPrioPerCore[_prio][_core]; + + _node = _list.First; + + _prio = BitOperations.TrailingZeroCount(_prioMask); + + _prioMask &= ~(1L << _prio); + + return true; + } + else + { + _list = null; + _node = null; + return false; + } + } + } + } + + public KThreadEnumerable ScheduledThreads(int core) + { + return new KThreadEnumerable(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core); + } + + public KThreadEnumerable SuggestedThreads(int core) + { + return new KThreadEnumerable(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core); + } + + public KThread ScheduledThreadsFirstOrDefault(int core) + { + return ScheduledThreadsElementAtOrDefault(core, 0); + } + + public KThread ScheduledThreadsElementAtOrDefault(int core, int index) + { + int currentIndex = 0; + foreach (var scheduledThread in ScheduledThreads(core)) + { + if (currentIndex == index) + { + return scheduledThread; + } + else + { + currentIndex++; + } + } + + return null; + } + + public KThread ScheduledThreadsWithDynamicPriorityFirstOrDefault(int core, int dynamicPriority) + { + foreach (var scheduledThread in ScheduledThreads(core)) + { + if (scheduledThread.DynamicPriority == dynamicPriority) + { + return scheduledThread; + } + } + + return null; + } + + public bool HasScheduledThreads(int core) + { + return ScheduledThreadsFirstOrDefault(core) != null; + } + + public void TransferToCore(int prio, int dstCore, KThread thread) + { + int srcCore = thread.ActiveCore; + if (srcCore == dstCore) + { + return; + } + + thread.ActiveCore = dstCore; + + if (srcCore >= 0) + { + Unschedule(prio, srcCore, thread); + } + + if (dstCore >= 0) + { + Unsuggest(prio, dstCore, thread); + Schedule(prio, dstCore, thread); + } + + if (srcCore >= 0) + { + Suggest(prio, srcCore, thread); + } + } + + public void Suggest(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + thread.SiblingsPerCore[core] = SuggestedQueue(prio, core).AddFirst(thread); + + _suggestedPrioritiesPerCore[core] |= 1L << prio; + } + + public void Unsuggest(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + LinkedList queue = SuggestedQueue(prio, core); + + queue.Remove(thread.SiblingsPerCore[core]); + + if (queue.First == null) + { + _suggestedPrioritiesPerCore[core] &= ~(1L << prio); + } + } + + public void Schedule(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddLast(thread); + + _scheduledPrioritiesPerCore[core] |= 1L << prio; + } + + public void SchedulePrepend(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddFirst(thread); + + _scheduledPrioritiesPerCore[core] |= 1L << prio; + } + + public KThread Reschedule(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return null; + } + + LinkedList queue = ScheduledQueue(prio, core); + + queue.Remove(thread.SiblingsPerCore[core]); + + thread.SiblingsPerCore[core] = queue.AddLast(thread); + + return queue.First.Value; + } + + public void Unschedule(int prio, int core, KThread thread) + { + if (prio >= KScheduler.PrioritiesCount) + { + return; + } + + LinkedList queue = ScheduledQueue(prio, core); + + queue.Remove(thread.SiblingsPerCore[core]); + + if (queue.First == null) + { + _scheduledPrioritiesPerCore[core] &= ~(1L << prio); + } + } + + private LinkedList SuggestedQueue(int prio, int core) + { + return _suggestedThreadsPerPrioPerCore[prio][core]; + } + + private LinkedList ScheduledQueue(int prio, int core) + { + return _scheduledThreadsPerPrioPerCore[prio][core]; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs new file mode 100644 index 00000000..6ed61f5a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs @@ -0,0 +1,62 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.Horizon.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KReadableEvent : KSynchronizationObject + { + private bool _signaled; + + public KReadableEvent(KernelContext context) : base(context) + { + } + + public override void Signal() + { + KernelContext.CriticalSection.Enter(); + + if (!_signaled) + { + _signaled = true; + + base.Signal(); + } + + KernelContext.CriticalSection.Leave(); + } + + public Result Clear() + { + _signaled = false; + + return Result.Success; + } + + public Result ClearIfSignaled() + { + Result result; + + KernelContext.CriticalSection.Enter(); + + if (_signaled) + { + _signaled = false; + + result = Result.Success; + } + else + { + result = KernelResult.InvalidState; + } + + KernelContext.CriticalSection.Leave(); + + return result; + } + + public override bool IsSignaled() + { + return _signaled; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs new file mode 100644 index 00000000..8ef77902 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs @@ -0,0 +1,680 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using System; +using System.Numerics; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + partial class KScheduler : IDisposable + { + public const int PrioritiesCount = 64; + public const int CpuCoresCount = 4; + + private const int RoundRobinTimeQuantumMs = 10; + + private static readonly int[] _preemptionPriorities = { 59, 59, 59, 63 }; + + private static readonly int[] _srcCoresHighestPrioThreads = new int[CpuCoresCount]; + + private readonly KernelContext _context; + private readonly int _coreId; + + private struct SchedulingState + { + public volatile bool NeedsScheduling; + public volatile KThread SelectedThread; + } + + private SchedulingState _state; + + private KThread _previousThread; + private KThread _currentThread; + + private int _coreIdleLock; + private bool _idleSignalled = true; + private bool _idleActive = true; + private long _idleTimeRunning; + + public KThread PreviousThread => _previousThread; + public KThread CurrentThread => _currentThread; + public long LastContextSwitchTime { get; private set; } + public long TotalIdleTimeTicks => _idleTimeRunning; + + public KScheduler(KernelContext context, int coreId) + { + _context = context; + _coreId = coreId; + + _currentThread = null; + } + + public static ulong SelectThreads(KernelContext context) + { + if (context.ThreadReselectionRequested) + { + return SelectThreadsImpl(context); + } + else + { + return 0UL; + } + } + + private static ulong SelectThreadsImpl(KernelContext context) + { + context.ThreadReselectionRequested = false; + + ulong scheduledCoresMask = 0UL; + + for (int core = 0; core < CpuCoresCount; core++) + { + KThread thread = context.PriorityQueue.ScheduledThreadsFirstOrDefault(core); + + if (thread != null && + thread.Owner != null && + thread.Owner.PinnedThreads[core] != null && + thread.Owner.PinnedThreads[core] != thread) + { + KThread candidate = thread.Owner.PinnedThreads[core]; + + if (candidate.KernelWaitersCount == 0 && !KProcess.IsExceptionUserThread(candidate)) + { + if (candidate.SchedFlags == ThreadSchedState.Running) + { + thread = candidate; + } + else + { + thread = null; + } + } + } + + scheduledCoresMask |= context.Schedulers[core].SelectThread(thread); + } + + for (int core = 0; core < CpuCoresCount; core++) + { + // If the core is not idle (there's already a thread running on it), + // then we don't need to attempt load balancing. + if (context.PriorityQueue.HasScheduledThreads(core)) + { + continue; + } + + Array.Fill(_srcCoresHighestPrioThreads, 0); + + int srcCoresHighestPrioThreadsCount = 0; + + KThread dst = null; + + // Select candidate threads that could run on this core. + // Give preference to threads that are not yet selected. + foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core)) + { + if (suggested.ActiveCore < 0 || suggested != context.Schedulers[suggested.ActiveCore]._state.SelectedThread) + { + dst = suggested; + break; + } + + _srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = suggested.ActiveCore; + } + + // Not yet selected candidate found. + if (dst != null) + { + // Priorities < 2 are used for the kernel message dispatching + // threads, we should skip load balancing entirely. + if (dst.DynamicPriority >= 2) + { + context.PriorityQueue.TransferToCore(dst.DynamicPriority, core, dst); + + scheduledCoresMask |= context.Schedulers[core].SelectThread(dst); + } + + continue; + } + + // All candidates are already selected, choose the best one + // (the first one that doesn't make the source core idle if moved). + for (int index = 0; index < srcCoresHighestPrioThreadsCount; index++) + { + int srcCore = _srcCoresHighestPrioThreads[index]; + + KThread src = context.PriorityQueue.ScheduledThreadsElementAtOrDefault(srcCore, 1); + + if (src != null) + { + // Run the second thread on the queue on the source core, + // move the first one to the current core. + KThread origSelectedCoreSrc = context.Schedulers[srcCore]._state.SelectedThread; + + scheduledCoresMask |= context.Schedulers[srcCore].SelectThread(src); + + context.PriorityQueue.TransferToCore(origSelectedCoreSrc.DynamicPriority, core, origSelectedCoreSrc); + + scheduledCoresMask |= context.Schedulers[core].SelectThread(origSelectedCoreSrc); + } + } + } + + return scheduledCoresMask; + } + + private ulong SelectThread(KThread nextThread) + { + KThread previousThread = _state.SelectedThread; + + if (previousThread != nextThread) + { + if (previousThread != null) + { + previousThread.LastScheduledTime = PerformanceCounter.ElapsedTicks; + } + + _state.SelectedThread = nextThread; + _state.NeedsScheduling = true; + return 1UL << _coreId; + } + else + { + return 0UL; + } + } + + public static void EnableScheduling(KernelContext context, ulong scheduledCoresMask) + { + KScheduler currentScheduler = context.Schedulers[KernelStatic.GetCurrentThread().CurrentCore]; + + // Note that "RescheduleCurrentCore" will block, so "RescheduleOtherCores" must be done first. + currentScheduler.RescheduleOtherCores(scheduledCoresMask); + currentScheduler.RescheduleCurrentCore(); + } + + public static void EnableSchedulingFromForeignThread(KernelContext context, ulong scheduledCoresMask) + { + RescheduleOtherCores(context, scheduledCoresMask); + } + + private void RescheduleCurrentCore() + { + if (_state.NeedsScheduling) + { + Schedule(); + } + } + + private void RescheduleOtherCores(ulong scheduledCoresMask) + { + RescheduleOtherCores(_context, scheduledCoresMask & ~(1UL << _coreId)); + } + + private static void RescheduleOtherCores(KernelContext context, ulong scheduledCoresMask) + { + while (scheduledCoresMask != 0) + { + int coreToSignal = BitOperations.TrailingZeroCount(scheduledCoresMask); + + KThread threadToSignal = context.Schedulers[coreToSignal]._currentThread; + + // Request the thread running on that core to stop and reschedule, if we have one. + threadToSignal?.Context.RequestInterrupt(); + + // If the core is idle, ensure that the idle thread is awaken. + context.Schedulers[coreToSignal].NotifyIdleThread(); + + scheduledCoresMask &= ~(1UL << coreToSignal); + } + } + + private void ActivateIdleThread() + { + while (Interlocked.CompareExchange(ref _coreIdleLock, 1, 0) != 0) + { + Thread.SpinWait(1); + } + + Thread.MemoryBarrier(); + + // Signals that idle thread is now active on this core. + _idleActive = true; + + TryLeaveIdle(); + + Interlocked.Exchange(ref _coreIdleLock, 0); + } + + private void NotifyIdleThread() + { + while (Interlocked.CompareExchange(ref _coreIdleLock, 1, 0) != 0) + { + Thread.SpinWait(1); + } + + Thread.MemoryBarrier(); + + // Signals that the idle core may be able to exit idle. + _idleSignalled = true; + + TryLeaveIdle(); + + Interlocked.Exchange(ref _coreIdleLock, 0); + } + + public void TryLeaveIdle() + { + if (_idleSignalled && _idleActive) + { + _state.NeedsScheduling = false; + Thread.MemoryBarrier(); + KThread nextThread = PickNextThread(null, _state.SelectedThread); + + if (nextThread != null) + { + _idleActive = false; + nextThread.SchedulerWaitEvent.Set(); + } + + _idleSignalled = false; + } + } + + public void Schedule() + { + _state.NeedsScheduling = false; + Thread.MemoryBarrier(); + KThread currentThread = KernelStatic.GetCurrentThread(); + KThread selectedThread = _state.SelectedThread; + + // If the thread is already scheduled and running on the core, we have nothing to do. + if (currentThread == selectedThread) + { + return; + } + + currentThread.SchedulerWaitEvent.Reset(); + currentThread.ThreadContext.Unlock(); + + // Wake all the threads that might be waiting until this thread context is unlocked. + for (int core = 0; core < CpuCoresCount; core++) + { + _context.Schedulers[core].NotifyIdleThread(); + } + + KThread nextThread = PickNextThread(KernelStatic.GetCurrentThread(), selectedThread); + + if (currentThread.Context.Running) + { + // Wait until this thread is scheduled again, and allow the next thread to run. + + if (nextThread == null) + { + ActivateIdleThread(); + currentThread.SchedulerWaitEvent.WaitOne(); + } + else + { + WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, currentThread.SchedulerWaitEvent); + } + } + else + { + // Allow the next thread to run. + + if (nextThread == null) + { + ActivateIdleThread(); + } + else + { + nextThread.SchedulerWaitEvent.Set(); + } + + // We don't need to wait since the thread is exiting, however we need to + // make sure this thread will never call the scheduler again, since it is + // no longer assigned to a core. + currentThread.MakeUnschedulable(); + + // Just to be sure, set the core to a invalid value. + // This will trigger a exception if it attempts to call schedule again, + // rather than leaving the scheduler in a invalid state. + currentThread.CurrentCore = -1; + } + } + + private KThread PickNextThread(KThread currentThread, KThread selectedThread) + { + while (true) + { + if (selectedThread != null) + { + // Try to run the selected thread. + // We need to acquire the context lock to be sure the thread is not + // already running on another core. If it is, then we return here + // and the caller should try again once there is something available for scheduling. + // The thread currently running on the core should have been requested to + // interrupt so this is not expected to take long. + // The idle thread must also be paused if we are scheduling a thread + // on the core, as the scheduled thread will handle the next switch. + if (selectedThread.ThreadContext.Lock()) + { + SwitchTo(currentThread, selectedThread); + + if (!_state.NeedsScheduling) + { + return selectedThread; + } + + selectedThread.ThreadContext.Unlock(); + } + else + { + return null; + } + } + else + { + // The core is idle now, make sure that the idle thread can run + // and switch the core when a thread is available. + SwitchTo(currentThread, null); + return null; + } + + _state.NeedsScheduling = false; + Thread.MemoryBarrier(); + selectedThread = _state.SelectedThread; + } + } + + private void SwitchTo(KThread currentThread, KThread nextThread) + { + KProcess currentProcess = currentThread?.Owner; + + if (currentThread != nextThread) + { + long previousTicks = LastContextSwitchTime; + long currentTicks = PerformanceCounter.ElapsedTicks; + long ticksDelta = currentTicks - previousTicks; + + if (currentThread == null) + { + Interlocked.Add(ref _idleTimeRunning, ticksDelta); + } + else + { + currentThread.AddCpuTime(ticksDelta); + } + + currentProcess?.AddCpuTime(ticksDelta); + + LastContextSwitchTime = currentTicks; + + if (currentProcess != null) + { + _previousThread = !currentThread.TerminationRequested && currentThread.ActiveCore == _coreId ? currentThread : null; + } + else if (currentThread == null) + { + _previousThread = null; + } + } + + if (nextThread != null && nextThread.CurrentCore != _coreId) + { + nextThread.CurrentCore = _coreId; + } + + _currentThread = nextThread; + } + + public static void PreemptionThreadLoop(KernelContext context) + { + while (context.Running) + { + context.CriticalSection.Enter(); + + for (int core = 0; core < CpuCoresCount; core++) + { + RotateScheduledQueue(context, core, _preemptionPriorities[core]); + } + + context.CriticalSection.Leave(); + + Thread.Sleep(RoundRobinTimeQuantumMs); + } + } + + private static void RotateScheduledQueue(KernelContext context, int core, int prio) + { + KThread selectedThread = context.PriorityQueue.ScheduledThreadsWithDynamicPriorityFirstOrDefault(core, prio); + KThread nextThread = null; + + // Yield priority queue. + if (selectedThread != null) + { + nextThread = context.PriorityQueue.Reschedule(prio, core, selectedThread); + } + + static KThread FirstSuitableCandidateOrDefault(KernelContext context, int core, KThread selectedThread, KThread nextThread, Predicate predicate) + { + foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core)) + { + int suggestedCore = suggested.ActiveCore; + if (suggestedCore >= 0) + { + KThread selectedSuggestedCore = context.PriorityQueue.ScheduledThreadsFirstOrDefault(suggestedCore); + + if (selectedSuggestedCore == suggested || (selectedSuggestedCore != null && selectedSuggestedCore.DynamicPriority < 2)) + { + continue; + } + } + + // If the candidate was scheduled after the current thread, then it's not worth it. + if (nextThread == selectedThread || + nextThread == null || + nextThread.LastScheduledTime >= suggested.LastScheduledTime) + { + if (predicate(suggested)) + { + return suggested; + } + } + } + + return null; + } + + // Select candidate threads that could run on this core. + // Only take into account threads that are not yet selected. + KThread dst = FirstSuitableCandidateOrDefault(context, core, selectedThread, nextThread, x => x.DynamicPriority == prio); + + if (dst != null) + { + context.PriorityQueue.TransferToCore(prio, core, dst); + } + + // If the priority of the currently selected thread is lower or same as the preemption priority, + // then try to migrate a thread with lower priority. + KThread bestCandidate = context.PriorityQueue.ScheduledThreadsFirstOrDefault(core); + + if (bestCandidate != null && bestCandidate.DynamicPriority >= prio) + { + dst = FirstSuitableCandidateOrDefault(context, core, selectedThread, nextThread, x => x.DynamicPriority < bestCandidate.DynamicPriority); + + if (dst != null) + { + context.PriorityQueue.TransferToCore(dst.DynamicPriority, core, dst); + } + } + + context.ThreadReselectionRequested = true; + } + + public static void Yield(KernelContext context) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (!currentThread.IsSchedulable) + { + return; + } + + context.CriticalSection.Enter(); + + if (currentThread.SchedFlags != ThreadSchedState.Running) + { + context.CriticalSection.Leave(); + return; + } + + KThread nextThread = context.PriorityQueue.Reschedule(currentThread.DynamicPriority, currentThread.ActiveCore, currentThread); + + if (nextThread != currentThread) + { + context.ThreadReselectionRequested = true; + } + + context.CriticalSection.Leave(); + } + + public static void YieldWithLoadBalancing(KernelContext context) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (!currentThread.IsSchedulable) + { + return; + } + + context.CriticalSection.Enter(); + + if (currentThread.SchedFlags != ThreadSchedState.Running) + { + context.CriticalSection.Leave(); + return; + } + + int prio = currentThread.DynamicPriority; + int core = currentThread.ActiveCore; + + // Move current thread to the end of the queue. + KThread nextThread = context.PriorityQueue.Reschedule(prio, core, currentThread); + + static KThread FirstSuitableCandidateOrDefault(KernelContext context, int core, KThread nextThread, int lessThanOrEqualPriority) + { + foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core)) + { + int suggestedCore = suggested.ActiveCore; + if (suggestedCore >= 0) + { + KThread selectedSuggestedCore = context.Schedulers[suggestedCore]._state.SelectedThread; + + if (selectedSuggestedCore == suggested || (selectedSuggestedCore != null && selectedSuggestedCore.DynamicPriority < 2)) + { + continue; + } + } + + // If the candidate was scheduled after the current thread, then it's not worth it, + // unless the priority is higher than the current one. + if (suggested.LastScheduledTime <= nextThread.LastScheduledTime || + suggested.DynamicPriority < nextThread.DynamicPriority) + { + if (suggested.DynamicPriority <= lessThanOrEqualPriority) + { + return suggested; + } + } + } + + return null; + } + + KThread dst = FirstSuitableCandidateOrDefault(context, core, nextThread, prio); + + if (dst != null) + { + context.PriorityQueue.TransferToCore(dst.DynamicPriority, core, dst); + + context.ThreadReselectionRequested = true; + } + else if (currentThread != nextThread) + { + context.ThreadReselectionRequested = true; + } + + context.CriticalSection.Leave(); + } + + public static void YieldToAnyThread(KernelContext context) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (!currentThread.IsSchedulable) + { + return; + } + + context.CriticalSection.Enter(); + + if (currentThread.SchedFlags != ThreadSchedState.Running) + { + context.CriticalSection.Leave(); + return; + } + + int core = currentThread.ActiveCore; + + context.PriorityQueue.TransferToCore(currentThread.DynamicPriority, -1, currentThread); + + if (!context.PriorityQueue.HasScheduledThreads(core)) + { + KThread selectedThread = null; + + foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core)) + { + int suggestedCore = suggested.ActiveCore; + + if (suggestedCore < 0) + { + continue; + } + + KThread firstCandidate = context.PriorityQueue.ScheduledThreadsFirstOrDefault(suggestedCore); + + if (firstCandidate == suggested) + { + continue; + } + + if (firstCandidate == null || firstCandidate.DynamicPriority >= 2) + { + context.PriorityQueue.TransferToCore(suggested.DynamicPriority, core, suggested); + } + + selectedThread = suggested; + break; + } + + if (currentThread != selectedThread) + { + context.ThreadReselectionRequested = true; + } + } + else + { + context.ThreadReselectionRequested = true; + } + + context.CriticalSection.Leave(); + } + + public void Dispose() + { + // No resources to dispose for now. + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs new file mode 100644 index 00000000..21c2730b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs @@ -0,0 +1,142 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.Horizon.Common; +using System; +using System.Buffers; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KSynchronization + { + private readonly KernelContext _context; + + public KSynchronization(KernelContext context) + { + _context = context; + } + + public Result WaitFor(Span syncObjs, long timeout, out int handleIndex) + { + handleIndex = 0; + + Result result = KernelResult.TimedOut; + + _context.CriticalSection.Enter(); + + // Check if objects are already signaled before waiting. + for (int index = 0; index < syncObjs.Length; index++) + { + if (!syncObjs[index].IsSignaled()) + { + continue; + } + + handleIndex = index; + + _context.CriticalSection.Leave(); + + return Result.Success; + } + + if (timeout == 0) + { + _context.CriticalSection.Leave(); + + return result; + } + + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (currentThread.TerminationRequested) + { + result = KernelResult.ThreadTerminating; + } + else if (currentThread.SyncCancelled) + { + currentThread.SyncCancelled = false; + + result = KernelResult.Cancelled; + } + else + { + LinkedListNode[] syncNodesArray = ArrayPool>.Shared.Rent(syncObjs.Length); + + Span> syncNodes = syncNodesArray.AsSpan(0, syncObjs.Length); + + for (int index = 0; index < syncObjs.Length; index++) + { + syncNodes[index] = syncObjs[index].AddWaitingThread(currentThread); + } + + currentThread.WaitingSync = true; + currentThread.SignaledObj = null; + currentThread.ObjSyncResult = result; + + currentThread.Reschedule(ThreadSchedState.Paused); + + if (timeout > 0) + { + _context.TimeManager.ScheduleFutureInvocation(currentThread, timeout); + } + + _context.CriticalSection.Leave(); + + currentThread.WaitingSync = false; + + if (timeout > 0) + { + _context.TimeManager.UnscheduleFutureInvocation(currentThread); + } + + _context.CriticalSection.Enter(); + + result = currentThread.ObjSyncResult; + + handleIndex = -1; + + for (int index = 0; index < syncObjs.Length; index++) + { + syncObjs[index].RemoveWaitingThread(syncNodes[index]); + + if (syncObjs[index] == currentThread.SignaledObj) + { + handleIndex = index; + } + } + + ArrayPool>.Shared.Return(syncNodesArray, true); + } + + _context.CriticalSection.Leave(); + + return result; + } + + public void SignalObject(KSynchronizationObject syncObj) + { + _context.CriticalSection.Enter(); + + if (syncObj.IsSignaled()) + { + LinkedListNode node = syncObj.WaitingThreads.First; + + while (node != null) + { + KThread thread = node.Value; + + if ((thread.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) + { + thread.SignaledObj = syncObj; + thread.ObjSyncResult = Result.Success; + + thread.Reschedule(ThreadSchedState.Running); + } + + node = node.Next; + } + } + + _context.CriticalSection.Leave(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs new file mode 100644 index 00000000..835bf5d4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -0,0 +1,1435 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.SupervisorCall; +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KThread : KSynchronizationObject, IKFutureSchedulerObject + { + private const int TlsUserDisableCountOffset = 0x100; + private const int TlsUserInterruptFlagOffset = 0x102; + + public const int MaxWaitSyncObjects = 64; + + private ManualResetEvent _schedulerWaitEvent; + + public ManualResetEvent SchedulerWaitEvent => _schedulerWaitEvent; + + public Thread HostThread { get; private set; } + + public IExecutionContext Context { get; private set; } + + public KThreadContext ThreadContext { get; private set; } + + public int DynamicPriority { get; set; } + public ulong AffinityMask { get; set; } + + public ulong ThreadUid { get; private set; } + + private long _totalTimeRunning; + + public long TotalTimeRunning => _totalTimeRunning; + + public KSynchronizationObject SignaledObj { get; set; } + + public ulong CondVarAddress { get; set; } + + private ulong _entrypoint; + private ThreadStart _customThreadStart; + private bool _forcedUnschedulable; + + public bool IsSchedulable => _customThreadStart == null && !_forcedUnschedulable; + + public ulong MutexAddress { get; set; } + public int KernelWaitersCount { get; private set; } + + public KProcess Owner { get; private set; } + + private ulong _tlsAddress; + + public ulong TlsAddress => _tlsAddress; + + public KSynchronizationObject[] WaitSyncObjects { get; } + public int[] WaitSyncHandles { get; } + + public long LastScheduledTime { get; set; } + + public LinkedListNode[] SiblingsPerCore { get; private set; } + + public LinkedList Withholder { get; set; } + public LinkedListNode WithholderNode { get; set; } + + public LinkedListNode ProcessListNode { get; set; } + + private readonly LinkedList _mutexWaiters; + private LinkedListNode _mutexWaiterNode; + + private readonly LinkedList _pinnedWaiters; + + public KThread MutexOwner { get; private set; } + + public int ThreadHandleForUserMutex { get; set; } + + private ThreadSchedState _forcePauseFlags; + private ThreadSchedState _forcePausePermissionFlags; + + public Result ObjSyncResult { get; set; } + + public int BasePriority { get; set; } + public int PreferredCore { get; set; } + + public int CurrentCore { get; set; } + public int ActiveCore { get; set; } + + public bool IsPinned { get; private set; } + + private ulong _originalAffinityMask; + private int _originalPreferredCore; + private int _originalBasePriority; + private int _coreMigrationDisableCount; + + public ThreadSchedState SchedFlags { get; private set; } + + private int _shallBeTerminated; + + private bool ShallBeTerminated => _shallBeTerminated != 0; + + public bool TerminationRequested => ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending; + + public bool SyncCancelled { get; set; } + public bool WaitingSync { get; set; } + + private int _hasExited; + private bool _hasBeenInitialized; + private bool _hasBeenReleased; + + public bool WaitingInArbitration { get; set; } + + private readonly object _activityOperationLock = new(); + + public KThread(KernelContext context) : base(context) + { + WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects]; + WaitSyncHandles = new int[MaxWaitSyncObjects]; + + SiblingsPerCore = new LinkedListNode[KScheduler.CpuCoresCount]; + + _mutexWaiters = new LinkedList(); + _pinnedWaiters = new LinkedList(); + } + + public Result Initialize( + ulong entrypoint, + ulong argsPtr, + ulong stackTop, + int priority, + int cpuCore, + KProcess owner, + ThreadType type, + ThreadStart customThreadStart = null) + { + if ((uint)type > 3) + { + throw new ArgumentException($"Invalid thread type \"{type}\"."); + } + + PreferredCore = cpuCore; + AffinityMask |= 1UL << cpuCore; + + SchedFlags = ThreadSchedState.None; + + ActiveCore = cpuCore; + ObjSyncResult = KernelResult.ThreadNotStarted; + DynamicPriority = priority; + BasePriority = priority; + CurrentCore = cpuCore; + IsPinned = false; + + _entrypoint = entrypoint; + _customThreadStart = customThreadStart; + + if (type == ThreadType.User) + { + if (owner.AllocateThreadLocalStorage(out _tlsAddress) != Result.Success) + { + return KernelResult.OutOfMemory; + } + + MemoryHelper.FillWithZeros(owner.CpuMemory, _tlsAddress, KTlsPageInfo.TlsEntrySize); + } + + bool is64Bits; + + if (owner != null) + { + Owner = owner; + + owner.IncrementReferenceCount(); + owner.IncrementThreadCount(); + + is64Bits = owner.Flags.HasFlag(ProcessCreationFlags.Is64Bit); + } + else + { + is64Bits = true; + } + + HostThread = new Thread(ThreadStart); + + Context = owner?.CreateExecutionContext() ?? new ProcessExecutionContext(); + + ThreadContext = new KThreadContext(Context); + + Context.IsAarch32 = !is64Bits; + + Context.SetX(0, argsPtr); + + if (is64Bits) + { + Context.SetX(18, KSystemControl.GenerateRandom() | 1); + Context.SetX(31, stackTop); + } + else + { + Context.SetX(13, (uint)stackTop); + } + + Context.TpidrroEl0 = (long)_tlsAddress; + + ThreadUid = KernelContext.NewThreadUid(); + + HostThread.Name = customThreadStart != null ? $"HLE.OsThread.{ThreadUid}" : $"HLE.GuestThread.{ThreadUid}"; + + _hasBeenInitialized = true; + + _forcePausePermissionFlags = ThreadSchedState.ForcePauseMask; + + if (owner != null) + { + owner.AddThread(this); + + if (owner.IsPaused) + { + KernelContext.CriticalSection.Enter(); + + if (TerminationRequested) + { + KernelContext.CriticalSection.Leave(); + + return Result.Success; + } + + _forcePauseFlags |= ThreadSchedState.ProcessPauseFlag; + + CombineForcePauseFlags(); + + KernelContext.CriticalSection.Leave(); + } + } + + return Result.Success; + } + + public Result Start() + { + if (!KernelContext.KernelInitialized) + { + KernelContext.CriticalSection.Enter(); + + if (!TerminationRequested) + { + _forcePauseFlags |= ThreadSchedState.KernelInitPauseFlag; + + CombineForcePauseFlags(); + } + + KernelContext.CriticalSection.Leave(); + } + + Result result = KernelResult.ThreadTerminating; + + KernelContext.CriticalSection.Enter(); + + if (!ShallBeTerminated) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + while (SchedFlags != ThreadSchedState.TerminationPending && (currentThread == null || !currentThread.TerminationRequested)) + { + if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.None) + { + result = KernelResult.InvalidState; + break; + } + + if (currentThread == null || currentThread._forcePauseFlags == ThreadSchedState.None) + { + if (Owner != null && _forcePauseFlags != ThreadSchedState.None) + { + CombineForcePauseFlags(); + } + + SetNewSchedFlags(ThreadSchedState.Running); + + StartHostThread(); + + result = Result.Success; + break; + } + else + { + currentThread.CombineForcePauseFlags(); + + KernelContext.CriticalSection.Leave(); + KernelContext.CriticalSection.Enter(); + + if (currentThread.ShallBeTerminated) + { + break; + } + } + } + } + + KernelContext.CriticalSection.Leave(); + + return result; + } + + public ThreadSchedState PrepareForTermination() + { + KernelContext.CriticalSection.Enter(); + + if (Owner != null && Owner.PinnedThreads[KernelStatic.GetCurrentThread().CurrentCore] == this) + { + Owner.UnpinThread(this); + } + + ThreadSchedState result; + + if (Interlocked.Exchange(ref _shallBeTerminated, 1) == 0) + { + if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.None) + { + SchedFlags = ThreadSchedState.TerminationPending; + } + else + { + if (_forcePauseFlags != ThreadSchedState.None) + { + _forcePauseFlags &= ~ThreadSchedState.ThreadPauseFlag; + + ThreadSchedState oldSchedFlags = SchedFlags; + + SchedFlags &= ThreadSchedState.LowMask; + + AdjustScheduling(oldSchedFlags); + } + + if (BasePriority >= 0x10) + { + SetPriority(0xF); + } + + if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Running) + { + // TODO: GIC distributor stuffs (sgir changes ect) + Context.RequestInterrupt(); + } + + SignaledObj = null; + ObjSyncResult = KernelResult.ThreadTerminating; + + ReleaseAndResume(); + } + } + + result = SchedFlags; + + KernelContext.CriticalSection.Leave(); + + return result & ThreadSchedState.LowMask; + } + + public void Terminate() + { + ThreadSchedState state = PrepareForTermination(); + + if (state != ThreadSchedState.TerminationPending) + { + KernelContext.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _); + } + } + + public void HandlePostSyscall() + { + ThreadSchedState state; + + do + { + if (TerminationRequested) + { + Exit(); + + // As the death of the thread is handled by the CPU emulator, we differ from the official kernel and return here. + break; + } + + KernelContext.CriticalSection.Enter(); + + if (TerminationRequested) + { + state = ThreadSchedState.TerminationPending; + } + else + { + if (_forcePauseFlags != ThreadSchedState.None) + { + CombineForcePauseFlags(); + } + + state = ThreadSchedState.Running; + } + + KernelContext.CriticalSection.Leave(); + } while (state == ThreadSchedState.TerminationPending); + } + + public void Exit() + { + // TODO: Debug event. + + if (Owner != null) + { + Owner.ResourceLimit?.Release(LimitableResource.Thread, 0, 1); + + _hasBeenReleased = true; + } + + KernelContext.CriticalSection.Enter(); + + _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask; + _forcePausePermissionFlags = 0; + + bool decRef = ExitImpl(); + + Context.StopRunning(); + + KernelContext.CriticalSection.Leave(); + + if (decRef) + { + DecrementReferenceCount(); + } + } + + private bool ExitImpl() + { + KernelContext.CriticalSection.Enter(); + + SetNewSchedFlags(ThreadSchedState.TerminationPending); + + bool decRef = Interlocked.Exchange(ref _hasExited, 1) == 0; + + Signal(); + + KernelContext.CriticalSection.Leave(); + + return decRef; + } + + private int GetEffectiveRunningCore() + { + for (int coreNumber = 0; coreNumber < KScheduler.CpuCoresCount; coreNumber++) + { + if (KernelContext.Schedulers[coreNumber].CurrentThread == this) + { + return coreNumber; + } + } + + return -1; + } + + public Result Sleep(long timeout) + { + KernelContext.CriticalSection.Enter(); + + if (TerminationRequested) + { + KernelContext.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + SetNewSchedFlags(ThreadSchedState.Paused); + + if (timeout > 0) + { + KernelContext.TimeManager.ScheduleFutureInvocation(this, timeout); + } + + KernelContext.CriticalSection.Leave(); + + if (timeout > 0) + { + KernelContext.TimeManager.UnscheduleFutureInvocation(this); + } + + return Result.Success; + } + + public void SetPriority(int priority) + { + KernelContext.CriticalSection.Enter(); + + if (IsPinned) + { + _originalBasePriority = priority; + } + else + { + BasePriority = priority; + } + + UpdatePriorityInheritance(); + + KernelContext.CriticalSection.Leave(); + } + + public void Suspend(ThreadSchedState type) + { + _forcePauseFlags |= type; + + CombineForcePauseFlags(); + } + + public void Resume(ThreadSchedState type) + { + ThreadSchedState oldForcePauseFlags = _forcePauseFlags; + + _forcePauseFlags &= ~type; + + if ((oldForcePauseFlags & ~type) == ThreadSchedState.None) + { + ThreadSchedState oldSchedFlags = SchedFlags; + + SchedFlags &= ThreadSchedState.LowMask; + + AdjustScheduling(oldSchedFlags); + } + } + + public Result SetActivity(bool pause) + { + lock (_activityOperationLock) + { + Result result = Result.Success; + + KernelContext.CriticalSection.Enter(); + + ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask; + + if (lowNibble != ThreadSchedState.Paused && lowNibble != ThreadSchedState.Running) + { + KernelContext.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + if (!TerminationRequested) + { + if (pause) + { + // Pause, the force pause flag should be clear (thread is NOT paused). + if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0) + { + Suspend(ThreadSchedState.ThreadPauseFlag); + } + else + { + result = KernelResult.InvalidState; + } + } + else + { + // Unpause, the force pause flag should be set (thread is paused). + if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0) + { + Resume(ThreadSchedState.ThreadPauseFlag); + } + else + { + result = KernelResult.InvalidState; + } + } + } + + KernelContext.CriticalSection.Leave(); + + if (result == Result.Success && pause) + { + bool isThreadRunning = true; + + while (isThreadRunning) + { + KernelContext.CriticalSection.Enter(); + + if (TerminationRequested) + { + KernelContext.CriticalSection.Leave(); + + break; + } + + isThreadRunning = false; + + if (IsPinned) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (currentThread.TerminationRequested) + { + KernelContext.CriticalSection.Leave(); + + result = KernelResult.ThreadTerminating; + + break; + } + + _pinnedWaiters.AddLast(currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + } + else + { + isThreadRunning = GetEffectiveRunningCore() >= 0; + } + + KernelContext.CriticalSection.Leave(); + } + } + + return result; + } + } + + public Result GetThreadContext3(out ThreadContext context) + { + context = default; + + lock (_activityOperationLock) + { + KernelContext.CriticalSection.Enter(); + + if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0) + { + KernelContext.CriticalSection.Leave(); + + return KernelResult.InvalidState; + } + + if (!TerminationRequested) + { + context = GetCurrentContext(); + } + + KernelContext.CriticalSection.Leave(); + } + + return Result.Success; + } + + private static uint GetPsr(IExecutionContext context) + { + return context.Pstate & 0xFF0FFE20; + } + + private ThreadContext GetCurrentContext() + { + const int MaxRegistersAArch32 = 15; + const int MaxFpuRegistersAArch32 = 16; + + ThreadContext context = new(); + + if (Owner.Flags.HasFlag(ProcessCreationFlags.Is64Bit)) + { + for (int i = 0; i < context.Registers.Length; i++) + { + context.Registers[i] = Context.GetX(i); + } + + for (int i = 0; i < context.FpuRegisters.Length; i++) + { + context.FpuRegisters[i] = Context.GetV(i); + } + + context.Fp = Context.GetX(29); + context.Lr = Context.GetX(30); + context.Sp = Context.GetX(31); + context.Pc = Context.Pc; + context.Pstate = GetPsr(Context); + context.Tpidr = (ulong)Context.TpidrroEl0; + } + else + { + for (int i = 0; i < MaxRegistersAArch32; i++) + { + context.Registers[i] = (uint)Context.GetX(i); + } + + for (int i = 0; i < MaxFpuRegistersAArch32; i++) + { + context.FpuRegisters[i] = Context.GetV(i); + } + + context.Pc = (uint)Context.Pc; + context.Pstate = GetPsr(Context); + context.Tpidr = (uint)Context.TpidrroEl0; + } + + context.Fpcr = (uint)Context.Fpcr; + context.Fpsr = (uint)Context.Fpsr; + + return context; + } + + public void CancelSynchronization() + { + KernelContext.CriticalSection.Enter(); + + if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.Paused || !WaitingSync) + { + SyncCancelled = true; + } + else if (Withholder != null) + { + Withholder.Remove(WithholderNode); + + SetNewSchedFlags(ThreadSchedState.Running); + + Withholder = null; + + SyncCancelled = true; + } + else + { + SignaledObj = null; + ObjSyncResult = KernelResult.Cancelled; + + SetNewSchedFlags(ThreadSchedState.Running); + + SyncCancelled = false; + } + + KernelContext.CriticalSection.Leave(); + } + + public Result SetCoreAndAffinityMask(int newCore, ulong newAffinityMask) + { + lock (_activityOperationLock) + { + KernelContext.CriticalSection.Enter(); + + bool isCoreMigrationDisabled = _coreMigrationDisableCount != 0; + + // The value -3 is "do not change the preferred core". + if (newCore == -3) + { + newCore = isCoreMigrationDisabled ? _originalPreferredCore : PreferredCore; + + if ((newAffinityMask & (1UL << newCore)) == 0) + { + KernelContext.CriticalSection.Leave(); + + return KernelResult.InvalidCombination; + } + } + + if (isCoreMigrationDisabled) + { + _originalPreferredCore = newCore; + _originalAffinityMask = newAffinityMask; + } + else + { + ulong oldAffinityMask = AffinityMask; + + PreferredCore = newCore; + AffinityMask = newAffinityMask; + + if (oldAffinityMask != newAffinityMask) + { + int oldCore = ActiveCore; + + if (oldCore >= 0 && ((AffinityMask >> oldCore) & 1) == 0) + { + if (PreferredCore < 0) + { + ActiveCore = sizeof(ulong) * 8 - 1 - BitOperations.LeadingZeroCount(AffinityMask); + } + else + { + ActiveCore = PreferredCore; + } + } + + AdjustSchedulingForNewAffinity(oldAffinityMask, oldCore); + } + } + + KernelContext.CriticalSection.Leave(); + + bool targetThreadPinned = true; + + while (targetThreadPinned) + { + KernelContext.CriticalSection.Enter(); + + if (TerminationRequested) + { + KernelContext.CriticalSection.Leave(); + + break; + } + + targetThreadPinned = false; + + int coreNumber = GetEffectiveRunningCore(); + bool isPinnedThreadCurrentlyRunning = coreNumber >= 0; + + if (isPinnedThreadCurrentlyRunning && ((1UL << coreNumber) & AffinityMask) == 0) + { + if (IsPinned) + { + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (currentThread.TerminationRequested) + { + KernelContext.CriticalSection.Leave(); + + return KernelResult.ThreadTerminating; + } + + _pinnedWaiters.AddLast(currentThread); + + currentThread.Reschedule(ThreadSchedState.Paused); + } + else + { + targetThreadPinned = true; + } + } + + KernelContext.CriticalSection.Leave(); + } + + return Result.Success; + } + } + + private void CombineForcePauseFlags() + { + ThreadSchedState oldFlags = SchedFlags; + ThreadSchedState lowNibble = SchedFlags & ThreadSchedState.LowMask; + + SchedFlags = lowNibble | (_forcePauseFlags & _forcePausePermissionFlags); + + AdjustScheduling(oldFlags); + } + + private void SetNewSchedFlags(ThreadSchedState newFlags) + { + KernelContext.CriticalSection.Enter(); + + ThreadSchedState oldFlags = SchedFlags; + + SchedFlags = (oldFlags & ThreadSchedState.HighMask) | newFlags; + + if ((oldFlags & ThreadSchedState.LowMask) != newFlags) + { + AdjustScheduling(oldFlags); + } + + KernelContext.CriticalSection.Leave(); + } + + public void ReleaseAndResume() + { + KernelContext.CriticalSection.Enter(); + + if ((SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused) + { + if (Withholder != null) + { + Withholder.Remove(WithholderNode); + + SetNewSchedFlags(ThreadSchedState.Running); + + Withholder = null; + } + else + { + SetNewSchedFlags(ThreadSchedState.Running); + } + } + + KernelContext.CriticalSection.Leave(); + } + + public void Reschedule(ThreadSchedState newFlags) + { + KernelContext.CriticalSection.Enter(); + + ThreadSchedState oldFlags = SchedFlags; + + SchedFlags = (oldFlags & ThreadSchedState.HighMask) | + (newFlags & ThreadSchedState.LowMask); + + AdjustScheduling(oldFlags); + + KernelContext.CriticalSection.Leave(); + } + + public void AddMutexWaiter(KThread requester) + { + AddToMutexWaitersList(requester); + + requester.MutexOwner = this; + + UpdatePriorityInheritance(); + } + + public void RemoveMutexWaiter(KThread thread) + { + if (thread._mutexWaiterNode?.List != null) + { + _mutexWaiters.Remove(thread._mutexWaiterNode); + } + + thread.MutexOwner = null; + + UpdatePriorityInheritance(); + } + + public KThread RelinquishMutex(ulong mutexAddress, out int count) + { + count = 0; + + if (_mutexWaiters.First == null) + { + return null; + } + + KThread newMutexOwner = null; + + LinkedListNode currentNode = _mutexWaiters.First; + + do + { + // Skip all threads that are not waiting for this mutex. + while (currentNode != null && currentNode.Value.MutexAddress != mutexAddress) + { + currentNode = currentNode.Next; + } + + if (currentNode == null) + { + break; + } + + LinkedListNode nextNode = currentNode.Next; + + _mutexWaiters.Remove(currentNode); + + currentNode.Value.MutexOwner = newMutexOwner; + + if (newMutexOwner != null) + { + // New owner was already selected, re-insert on new owner list. + newMutexOwner.AddToMutexWaitersList(currentNode.Value); + } + else + { + // New owner not selected yet, use current thread. + newMutexOwner = currentNode.Value; + } + + count++; + + currentNode = nextNode; + } + while (currentNode != null); + + if (newMutexOwner != null) + { + UpdatePriorityInheritance(); + + newMutexOwner.UpdatePriorityInheritance(); + } + + return newMutexOwner; + } + + private void UpdatePriorityInheritance() + { + // If any of the threads waiting for the mutex has + // higher priority than the current thread, then + // the current thread inherits that priority. + int highestPriority = BasePriority; + + if (_mutexWaiters.First != null) + { + int waitingDynamicPriority = _mutexWaiters.First.Value.DynamicPriority; + + if (waitingDynamicPriority < highestPriority) + { + highestPriority = waitingDynamicPriority; + } + } + + if (highestPriority != DynamicPriority) + { + int oldPriority = DynamicPriority; + + DynamicPriority = highestPriority; + + AdjustSchedulingForNewPriority(oldPriority); + + if (MutexOwner != null) + { + // Remove and re-insert to ensure proper sorting based on new priority. + MutexOwner._mutexWaiters.Remove(_mutexWaiterNode); + + MutexOwner.AddToMutexWaitersList(this); + + MutexOwner.UpdatePriorityInheritance(); + } + } + } + + private void AddToMutexWaitersList(KThread thread) + { + LinkedListNode nextPrio = _mutexWaiters.First; + + int currentPriority = thread.DynamicPriority; + + while (nextPrio != null && nextPrio.Value.DynamicPriority <= currentPriority) + { + nextPrio = nextPrio.Next; + } + + if (nextPrio != null) + { + thread._mutexWaiterNode = _mutexWaiters.AddBefore(nextPrio, thread); + } + else + { + thread._mutexWaiterNode = _mutexWaiters.AddLast(thread); + } + } + + private void AdjustScheduling(ThreadSchedState oldFlags) + { + if (oldFlags == SchedFlags) + { + return; + } + + if (!IsSchedulable) + { + if (!_forcedUnschedulable) + { + // Ensure our thread is running and we have an event. + StartHostThread(); + + // If the thread is not schedulable, we want to just run or pause + // it directly as we don't care about priority or the core it is + // running on in this case. + + if (SchedFlags == ThreadSchedState.Running) + { + _schedulerWaitEvent.Set(); + } + else + { + _schedulerWaitEvent.Reset(); + } + } + + return; + } + + if (oldFlags == ThreadSchedState.Running) + { + // Was running, now it's stopped. + if (ActiveCore >= 0) + { + KernelContext.PriorityQueue.Unschedule(DynamicPriority, ActiveCore, this); + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0) + { + KernelContext.PriorityQueue.Unsuggest(DynamicPriority, core, this); + } + } + } + else if (SchedFlags == ThreadSchedState.Running) + { + // Was stopped, now it's running. + if (ActiveCore >= 0) + { + KernelContext.PriorityQueue.Schedule(DynamicPriority, ActiveCore, this); + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0) + { + KernelContext.PriorityQueue.Suggest(DynamicPriority, core, this); + } + } + } + + KernelContext.ThreadReselectionRequested = true; + } + + private void AdjustSchedulingForNewPriority(int oldPriority) + { + if (SchedFlags != ThreadSchedState.Running || !IsSchedulable) + { + return; + } + + // Remove thread from the old priority queues. + if (ActiveCore >= 0) + { + KernelContext.PriorityQueue.Unschedule(oldPriority, ActiveCore, this); + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0) + { + KernelContext.PriorityQueue.Unsuggest(oldPriority, core, this); + } + } + + // Add thread to the new priority queues. + KThread currentThread = KernelStatic.GetCurrentThread(); + + if (ActiveCore >= 0) + { + if (currentThread == this) + { + KernelContext.PriorityQueue.SchedulePrepend(DynamicPriority, ActiveCore, this); + } + else + { + KernelContext.PriorityQueue.Schedule(DynamicPriority, ActiveCore, this); + } + } + + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0) + { + KernelContext.PriorityQueue.Suggest(DynamicPriority, core, this); + } + } + + KernelContext.ThreadReselectionRequested = true; + } + + private void AdjustSchedulingForNewAffinity(ulong oldAffinityMask, int oldCore) + { + if (SchedFlags != ThreadSchedState.Running || DynamicPriority >= KScheduler.PrioritiesCount || !IsSchedulable) + { + return; + } + + // Remove thread from the old priority queues. + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (((oldAffinityMask >> core) & 1) != 0) + { + if (core == oldCore) + { + KernelContext.PriorityQueue.Unschedule(DynamicPriority, core, this); + } + else + { + KernelContext.PriorityQueue.Unsuggest(DynamicPriority, core, this); + } + } + } + + // Add thread to the new priority queues. + for (int core = 0; core < KScheduler.CpuCoresCount; core++) + { + if (((AffinityMask >> core) & 1) != 0) + { + if (core == ActiveCore) + { + KernelContext.PriorityQueue.Schedule(DynamicPriority, core, this); + } + else + { + KernelContext.PriorityQueue.Suggest(DynamicPriority, core, this); + } + } + } + + KernelContext.ThreadReselectionRequested = true; + } + + public void SetEntryArguments(long argsPtr, int threadHandle) + { + Context.SetX(0, (ulong)argsPtr); + Context.SetX(1, (ulong)threadHandle); + } + + public void TimeUp() + { + ReleaseAndResume(); + } + + public string GetGuestStackTrace() + { + return Owner.Debugger.GetGuestStackTrace(this); + } + + public string GetGuestRegisterPrintout() + { + return Owner.Debugger.GetCpuRegisterPrintout(this); + } + + public void PrintGuestStackTrace() + { + Logger.Info?.Print(LogClass.Cpu, $"Guest stack trace:\n{GetGuestStackTrace()}\n"); + } + + public void PrintGuestRegisterPrintout() + { + Logger.Info?.Print(LogClass.Cpu, $"Guest CPU registers:\n{GetGuestRegisterPrintout()}\n"); + } + + public void AddCpuTime(long ticks) + { + Interlocked.Add(ref _totalTimeRunning, ticks); + } + + public void StartHostThread() + { + if (_schedulerWaitEvent == null) + { + var schedulerWaitEvent = new ManualResetEvent(false); + + if (Interlocked.Exchange(ref _schedulerWaitEvent, schedulerWaitEvent) == null) + { + HostThread.Start(); + } + else + { + schedulerWaitEvent.Dispose(); + } + } + } + + private void ThreadStart() + { + _schedulerWaitEvent.WaitOne(); + KernelStatic.SetKernelContext(KernelContext, this); + + if (_customThreadStart != null) + { + _customThreadStart(); + + // Ensure that anything trying to join the HLE thread is unblocked. + Exit(); + HandlePostSyscall(); + } + else + { + Owner.Context.Execute(Context, _entrypoint); + } + + Context.Dispose(); + _schedulerWaitEvent.Dispose(); + } + + public void MakeUnschedulable() + { + _forcedUnschedulable = true; + } + + public override bool IsSignaled() + { + return _hasExited != 0; + } + + protected override void Destroy() + { + if (_hasBeenInitialized) + { + FreeResources(); + + bool released = Owner != null || _hasBeenReleased; + + if (Owner != null) + { + Owner.ResourceLimit?.Release(LimitableResource.Thread, 1, released ? 0 : 1); + + Owner.DecrementReferenceCount(); + } + else + { + KernelContext.ResourceLimit.Release(LimitableResource.Thread, 1, released ? 0 : 1); + } + } + } + + private void FreeResources() + { + Owner?.RemoveThread(this); + + if (_tlsAddress != 0 && Owner.FreeThreadLocalStorage(_tlsAddress) != Result.Success) + { + throw new InvalidOperationException("Unexpected failure freeing thread local storage."); + } + + KernelContext.CriticalSection.Enter(); + + // Wake up all threads that may be waiting for a mutex being held by this thread. + foreach (KThread thread in _mutexWaiters) + { + thread.MutexOwner = null; + thread._originalPreferredCore = 0; + thread.ObjSyncResult = KernelResult.InvalidState; + + thread.ReleaseAndResume(); + } + + KernelContext.CriticalSection.Leave(); + + Owner?.DecrementThreadCountAndTerminateIfZero(); + } + + public void Pin() + { + IsPinned = true; + _coreMigrationDisableCount++; + + int activeCore = ActiveCore; + + _originalPreferredCore = PreferredCore; + _originalAffinityMask = AffinityMask; + + ActiveCore = CurrentCore; + PreferredCore = CurrentCore; + AffinityMask = 1UL << CurrentCore; + + if (activeCore != CurrentCore || _originalAffinityMask != AffinityMask) + { + AdjustSchedulingForNewAffinity(_originalAffinityMask, activeCore); + } + + _originalBasePriority = BasePriority; + BasePriority = Math.Min(_originalBasePriority, BitOperations.TrailingZeroCount(Owner.Capabilities.AllowedThreadPriosMask) - 1); + UpdatePriorityInheritance(); + + // Disallows thread pausing + _forcePausePermissionFlags &= ~ThreadSchedState.ThreadPauseFlag; + CombineForcePauseFlags(); + + // TODO: Assign reduced SVC permissions + } + + public void Unpin() + { + IsPinned = false; + _coreMigrationDisableCount--; + + ulong affinityMask = AffinityMask; + int activeCore = ActiveCore; + + PreferredCore = _originalPreferredCore; + AffinityMask = _originalAffinityMask; + + if (AffinityMask != affinityMask) + { + if ((AffinityMask & 1UL << ActiveCore) != 0) + { + if (PreferredCore >= 0) + { + ActiveCore = PreferredCore; + } + else + { + ActiveCore = sizeof(ulong) * 8 - 1 - BitOperations.LeadingZeroCount((ulong)AffinityMask); + } + + AdjustSchedulingForNewAffinity(affinityMask, activeCore); + } + } + + BasePriority = _originalBasePriority; + UpdatePriorityInheritance(); + + if (!TerminationRequested) + { + // Allows thread pausing + _forcePausePermissionFlags |= ThreadSchedState.ThreadPauseFlag; + CombineForcePauseFlags(); + + // TODO: Restore SVC permissions + } + + // Wake up waiters + foreach (KThread waiter in _pinnedWaiters) + { + waiter.ReleaseAndResume(); + } + + _pinnedWaiters.Clear(); + } + + public void SynchronizePreemptionState() + { + KernelContext.CriticalSection.Enter(); + + if (Owner != null && Owner.PinnedThreads[CurrentCore] == this) + { + ClearUserInterruptFlag(); + + Owner.UnpinThread(this); + } + + KernelContext.CriticalSection.Leave(); + } + + public ushort GetUserDisableCount() + { + return Owner.CpuMemory.Read(_tlsAddress + TlsUserDisableCountOffset); + } + + public void SetUserInterruptFlag() + { + Owner.CpuMemory.Write(_tlsAddress + TlsUserInterruptFlagOffset, 1); + } + + public void ClearUserInterruptFlag() + { + Owner.CpuMemory.Write(_tlsAddress + TlsUserInterruptFlagOffset, 0); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs new file mode 100644 index 00000000..9a616a13 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs @@ -0,0 +1,33 @@ +using Ryujinx.Cpu; +using Ryujinx.Horizon.Common; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KThreadContext : IThreadContext + { + private readonly IExecutionContext _context; + + public bool Running => _context.Running; + public ulong TlsAddress => (ulong)_context.TpidrroEl0; + + public ulong GetX(int index) => _context.GetX(index); + + private int _locked; + + public KThreadContext(IExecutionContext context) + { + _context = context; + } + + public bool Lock() + { + return Interlocked.Exchange(ref _locked, 1) == 0; + } + + public void Unlock() + { + Interlocked.Exchange(ref _locked, 0); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs new file mode 100644 index 00000000..fd65361a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs @@ -0,0 +1,25 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.Horizon.Common; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + class KWritableEvent : KAutoObject + { + private readonly KEvent _parent; + + public KWritableEvent(KernelContext context, KEvent parent) : base(context) + { + _parent = parent; + } + + public void Signal() + { + _parent.ReadableEvent.Signal(); + } + + public Result Clear() + { + return _parent.ReadableEvent.Clear(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs new file mode 100644 index 00000000..9f257d98 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + enum SignalType + { + Signal = 0, + SignalAndIncrementIfEqual = 1, + SignalAndModifyIfEqual = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs new file mode 100644 index 00000000..eca412d0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs @@ -0,0 +1,23 @@ +using System; + +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + [Flags] + enum ThreadSchedState : ushort + { + LowMask = 0xf, + HighMask = 0xfff0, + ForcePauseMask = 0x1f0, + + ProcessPauseFlag = 1 << 4, + ThreadPauseFlag = 1 << 5, + ProcessDebugPauseFlag = 1 << 6, + BacktracePauseFlag = 1 << 7, + KernelInitPauseFlag = 1 << 8, + + None = 0, + Paused = 1, + Running = 2, + TerminationPending = 3, + } +} diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs new file mode 100644 index 00000000..e2dfd2ff --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Kernel.Threading +{ + enum ThreadType + { + Kernel, + Kernel2, + User, + } +} diff --git a/src/Ryujinx.HLE/HOS/LibHacHorizonManager.cs b/src/Ryujinx.HLE/HOS/LibHacHorizonManager.cs new file mode 100644 index 00000000..e8ef15dc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/LibHacHorizonManager.cs @@ -0,0 +1,119 @@ +using LibHac; +using LibHac.Bcat; +using LibHac.Common; +using LibHac.FsSrv.Impl; +using LibHac.Loader; +using LibHac.Ncm; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Arp; +using System; + +namespace Ryujinx.HLE.HOS +{ + public class LibHacHorizonManager + { + private LibHac.Horizon Server { get; set; } + + public HorizonClient RyujinxClient { get; private set; } + public HorizonClient ApplicationClient { get; private set; } + public HorizonClient AccountClient { get; private set; } + public HorizonClient AmClient { get; private set; } + public HorizonClient BcatClient { get; private set; } + public HorizonClient FsClient { get; private set; } + public HorizonClient NsClient { get; private set; } + public HorizonClient PmClient { get; private set; } + public HorizonClient SdbClient { get; private set; } + + private SharedRef _arpIReader; + internal LibHacIReader ArpIReader => _arpIReader.Get; + + public LibHacHorizonManager() + { + InitializeServer(); + } + + private void InitializeServer() + { + Server = new LibHac.Horizon(new HorizonConfiguration()); + + RyujinxClient = Server.CreatePrivilegedHorizonClient(); + } + + public void InitializeArpServer() + { + _arpIReader.Reset(new LibHacIReader()); + RyujinxClient.Sm.RegisterService(new LibHacArpServiceObject(ref _arpIReader), "arp:r").ThrowIfFailure(); + } + + public void InitializeBcatServer() + { + BcatClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Bcat, StorageId.BuiltInSystem), BcatFsPermissions); + + _ = new BcatServer(BcatClient); + } + + public void InitializeFsServer(VirtualFileSystem virtualFileSystem) + { + virtualFileSystem.InitializeFsServer(Server, out var fsClient); + + FsClient = fsClient; + } + + public void InitializeSystemClients() + { +#pragma warning disable IDE0055 // Disable formatting + PmClient = Server.CreatePrivilegedHorizonClient(); + AccountClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Account, StorageId.BuiltInSystem), AccountFsPermissions); + AmClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Am, StorageId.BuiltInSystem), AmFsPermissions); + NsClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Ns, StorageId.BuiltInSystem), NsFsPermissions); + SdbClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Sdb, StorageId.BuiltInSystem), SdbFacData, SdbFacDescriptor); +#pragma warning restore IDE0055 + } + + public void InitializeApplicationClient(ProgramId programId, in Npdm npdm) + { + ApplicationClient = Server.CreateHorizonClient(new ProgramLocation(programId, StorageId.BuiltInUser), npdm.FsAccessControlData, npdm.FsAccessControlDescriptor); + } + + private static AccessControlBits.Bits AccountFsPermissions => AccessControlBits.Bits.SystemSaveData | + AccessControlBits.Bits.GameCard | + AccessControlBits.Bits.SaveDataMeta | + AccessControlBits.Bits.GetRightsId; + + private static AccessControlBits.Bits AmFsPermissions => AccessControlBits.Bits.SaveDataManagement | + AccessControlBits.Bits.CreateSaveData | + AccessControlBits.Bits.SystemData; + private static AccessControlBits.Bits BcatFsPermissions => AccessControlBits.Bits.SystemSaveData; + + private static AccessControlBits.Bits NsFsPermissions => AccessControlBits.Bits.ApplicationInfo | + AccessControlBits.Bits.SystemSaveData | + AccessControlBits.Bits.GameCard | + AccessControlBits.Bits.SaveDataManagement | + AccessControlBits.Bits.ContentManager | + AccessControlBits.Bits.ImageManager | + AccessControlBits.Bits.SystemSaveDataManagement | + AccessControlBits.Bits.SystemUpdate | + AccessControlBits.Bits.SdCard | + AccessControlBits.Bits.FormatSdCard | + AccessControlBits.Bits.GetRightsId | + AccessControlBits.Bits.RegisterProgramIndexMapInfo | + AccessControlBits.Bits.MoveCacheStorage; + + // Sdb has save data access control info so we can't store just its access control bits + private static ReadOnlySpan SdbFacData => new byte[] + { + 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x03, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + }; + + private static ReadOnlySpan SdbFacDescriptor => new byte[] + { + 0x01, 0x00, 0x02, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x09, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }; + } +} diff --git a/src/Ryujinx.HLE/HOS/ModLoader.cs b/src/Ryujinx.HLE/HOS/ModLoader.cs new file mode 100644 index 00000000..ee179c92 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/ModLoader.cs @@ -0,0 +1,831 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Loader; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.RomFs; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Loaders.Mods; +using Ryujinx.HLE.Loaders.Processes; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.IO; +using System.Linq; +using LazyFile = Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy.LazyFile; +using Path = System.IO.Path; + +namespace Ryujinx.HLE.HOS +{ + public class ModLoader + { + private const string RomfsDir = "romfs"; + private const string ExefsDir = "exefs"; + private const string CheatDir = "cheats"; + private const string RomfsContainer = "romfs.bin"; + private const string ExefsContainer = "exefs.nsp"; + private const string StubExtension = ".stub"; + private const string CheatExtension = ".txt"; + private const string DefaultCheatName = ""; + + private const string AmsContentsDir = "contents"; + private const string AmsNsoPatchDir = "exefs_patches"; + private const string AmsNroPatchDir = "nro_patches"; + private const string AmsKipPatchDir = "kip_patches"; + + private static readonly ModMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public readonly struct Mod where T : FileSystemInfo + { + public readonly string Name; + public readonly T Path; + public readonly bool Enabled; + + public Mod(string name, T path, bool enabled) + { + Name = name; + Path = path; + Enabled = enabled; + } + } + + public struct Cheat + { + // Atmosphere identifies the executables with the first 8 bytes + // of the build id, which is equivalent to 16 hex digits. + public const int CheatIdSize = 16; + + public readonly string Name; + public readonly FileInfo Path; + public readonly IEnumerable Instructions; + + public Cheat(string name, FileInfo path, IEnumerable instructions) + { + Name = name; + Path = path; + Instructions = instructions; + } + } + + // Application dependent mods + public class ModCache + { + public List> RomfsContainers { get; } + public List> ExefsContainers { get; } + + public List> RomfsDirs { get; } + public List> ExefsDirs { get; } + + public List Cheats { get; } + + public ModCache() + { + RomfsContainers = new List>(); + ExefsContainers = new List>(); + RomfsDirs = new List>(); + ExefsDirs = new List>(); + Cheats = new List(); + } + } + + // Application independent mods + private class PatchCache + { + public List> NsoPatches { get; } + public List> NroPatches { get; } + public List> KipPatches { get; } + + internal bool Initialized { get; set; } + + public PatchCache() + { + NsoPatches = new List>(); + NroPatches = new List>(); + KipPatches = new List>(); + + Initialized = false; + } + } + + private readonly Dictionary _appMods; // key is ApplicationId + private PatchCache _patches; + + private static readonly EnumerationOptions _dirEnumOptions; + + static ModLoader() + { + _dirEnumOptions = new EnumerationOptions + { + MatchCasing = MatchCasing.CaseInsensitive, + MatchType = MatchType.Simple, + RecurseSubdirectories = false, + ReturnSpecialDirectories = false, + }; + } + + public ModLoader() + { + _appMods = new Dictionary(); + _patches = new PatchCache(); + } + + private void Clear() + { + _appMods.Clear(); + _patches = new PatchCache(); + } + + private static bool StrEquals(string s1, string s2) => string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase); + + public static string GetModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetModsPath()); + public static string GetSdModsBasePath() => EnsureBaseDirStructure(AppDataManager.GetSdModsPath()); + + private static string EnsureBaseDirStructure(string modsBasePath) + { + var modsDir = new DirectoryInfo(modsBasePath); + + modsDir.CreateSubdirectory(AmsContentsDir); + modsDir.CreateSubdirectory(AmsNsoPatchDir); + modsDir.CreateSubdirectory(AmsNroPatchDir); + // TODO: uncomment when KIPs are supported + // modsDir.CreateSubdirectory(AmsKipPatchDir); + + return modsDir.FullName; + } + + private static DirectoryInfo FindApplicationDir(DirectoryInfo contentsDir, string applicationId) + => contentsDir.EnumerateDirectories(applicationId, _dirEnumOptions).FirstOrDefault(); + + private static void AddModsFromDirectory(ModCache mods, DirectoryInfo dir, ModMetadata modMetadata) + { + System.Text.StringBuilder types = new(); + + foreach (var modDir in dir.EnumerateDirectories()) + { + types.Clear(); + Mod mod = new("", null, true); + + if (StrEquals(RomfsDir, modDir.Name)) + { + var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path)); + var enabled = modData?.Enabled ?? true; + + mods.RomfsDirs.Add(mod = new Mod(dir.Name, modDir, enabled)); + types.Append('R'); + } + else if (StrEquals(ExefsDir, modDir.Name)) + { + var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path)); + var enabled = modData?.Enabled ?? true; + + mods.ExefsDirs.Add(mod = new Mod(dir.Name, modDir, enabled)); + types.Append('E'); + } + else if (StrEquals(CheatDir, modDir.Name)) + { + types.Append('C', QueryCheatsDir(mods, modDir)); + } + else + { + AddModsFromDirectory(mods, modDir, modMetadata); + } + + if (types.Length > 0) + { + Logger.Info?.Print(LogClass.ModLoader, $"Found {(mod.Enabled ? "enabled" : "disabled")} mod '{mod.Name}' [{types}]"); + } + } + } + + public static string GetApplicationDir(string modsBasePath, string applicationId) + { + var contentsDir = new DirectoryInfo(Path.Combine(modsBasePath, AmsContentsDir)); + var applicationModsPath = FindApplicationDir(contentsDir, applicationId); + + if (applicationModsPath == null) + { + Logger.Info?.Print(LogClass.ModLoader, $"Creating mods directory for Application {applicationId.ToUpper()}"); + applicationModsPath = contentsDir.CreateSubdirectory(applicationId); + } + + return applicationModsPath.FullName; + } + + // Static Query Methods + private static void QueryPatchDirs(PatchCache cache, DirectoryInfo patchDir) + { + if (cache.Initialized || !patchDir.Exists) + { + return; + } + + List> patches; + string type; + + if (StrEquals(AmsNsoPatchDir, patchDir.Name)) + { + patches = cache.NsoPatches; + type = "NSO"; + } + else if (StrEquals(AmsNroPatchDir, patchDir.Name)) + { + patches = cache.NroPatches; + type = "NRO"; + } + else if (StrEquals(AmsKipPatchDir, patchDir.Name)) + { + patches = cache.KipPatches; + type = "KIP"; + } + else + { + return; + } + + foreach (var modDir in patchDir.EnumerateDirectories()) + { + patches.Add(new Mod(modDir.Name, modDir, true)); + Logger.Info?.Print(LogClass.ModLoader, $"Found {type} patch '{modDir.Name}'"); + } + } + + private static void QueryApplicationDir(ModCache mods, DirectoryInfo applicationDir, ulong applicationId) + { + if (!applicationDir.Exists) + { + return; + } + + string modJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationId.ToString("x16"), "mods.json"); + ModMetadata modMetadata = new(); + + if (File.Exists(modJsonPath)) + { + try + { + modMetadata = JsonHelper.DeserializeFromFile(modJsonPath, _serializerContext.ModMetadata); + } + catch + { + Logger.Warning?.Print(LogClass.ModLoader, $"Failed to deserialize mod data for {applicationId:X16} at {modJsonPath}"); + } + } + + var fsFile = new FileInfo(Path.Combine(applicationDir.FullName, RomfsContainer)); + if (fsFile.Exists) + { + var modData = modMetadata.Mods.Find(x => fsFile.FullName.Contains(x.Path)); + var enabled = modData == null || modData.Enabled; + + mods.RomfsContainers.Add(new Mod($"<{applicationDir.Name} RomFs>", fsFile, enabled)); + } + + fsFile = new FileInfo(Path.Combine(applicationDir.FullName, ExefsContainer)); + if (fsFile.Exists) + { + var modData = modMetadata.Mods.Find(x => fsFile.FullName.Contains(x.Path)); + var enabled = modData == null || modData.Enabled; + + mods.ExefsContainers.Add(new Mod($"<{applicationDir.Name} ExeFs>", fsFile, enabled)); + } + + AddModsFromDirectory(mods, applicationDir, modMetadata); + } + + public static void QueryContentsDir(ModCache mods, DirectoryInfo contentsDir, ulong applicationId) + { + if (!contentsDir.Exists) + { + return; + } + + Logger.Info?.Print(LogClass.ModLoader, $"Searching mods for {((applicationId & 0x1000) != 0 ? "DLC" : "Application")} {applicationId:X16} in \"{contentsDir.FullName}\""); + + var applicationDir = FindApplicationDir(contentsDir, $"{applicationId:x16}"); + + if (applicationDir != null) + { + QueryApplicationDir(mods, applicationDir, applicationId); + } + } + + private static int QueryCheatsDir(ModCache mods, DirectoryInfo cheatsDir) + { + if (!cheatsDir.Exists) + { + return 0; + } + + int numMods = 0; + + foreach (FileInfo file in cheatsDir.EnumerateFiles()) + { + if (!StrEquals(CheatExtension, file.Extension)) + { + continue; + } + + string cheatId = Path.GetFileNameWithoutExtension(file.Name); + + if (cheatId.Length != Cheat.CheatIdSize) + { + continue; + } + + if (!ulong.TryParse(cheatId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _)) + { + continue; + } + + int oldCheatsCount = mods.Cheats.Count; + + // A cheat file can contain several cheats for the same executable, so the file must be parsed in + // order to properly enumerate them. + mods.Cheats.AddRange(GetCheatsInFile(file)); + + if (mods.Cheats.Count - oldCheatsCount > 0) + { + numMods++; + } + } + + return numMods; + } + + private static IEnumerable GetCheatsInFile(FileInfo cheatFile) + { + string cheatName = DefaultCheatName; + List instructions = new(); + List cheats = new(); + + using StreamReader cheatData = cheatFile.OpenText(); + while (cheatData.ReadLine() is { } line) + { + line = line.Trim(); + + if (line.StartsWith('[')) + { + // This line starts a new cheat section. + if (!line.EndsWith(']') || line.Length < 3) + { + // Skip the entire file if there's any error while parsing the cheat file. + + Logger.Warning?.Print(LogClass.ModLoader, $"Ignoring cheat '{cheatFile.FullName}' because it is malformed"); + + return Array.Empty(); + } + + // Add the previous section to the list. + if (instructions.Count > 0) + { + cheats.Add(new Cheat($"<{cheatName} Cheat>", cheatFile, instructions)); + } + + // Start a new cheat section. + cheatName = line[1..^1]; + instructions = new List(); + } + else if (line.Length > 0) + { + // The line contains an instruction. + instructions.Add(line); + } + } + + // Add the last section being processed. + if (instructions.Count > 0) + { + cheats.Add(new Cheat($"<{cheatName} Cheat>", cheatFile, instructions)); + } + + return cheats; + } + + // Assumes searchDirPaths don't overlap + private static void CollectMods(Dictionary modCaches, PatchCache patches, params string[] searchDirPaths) + { + static bool IsPatchesDir(string name) => StrEquals(AmsNsoPatchDir, name) || + StrEquals(AmsNroPatchDir, name) || + StrEquals(AmsKipPatchDir, name); + + static bool IsContentsDir(string name) => StrEquals(AmsContentsDir, name); + + static bool TryQuery(DirectoryInfo searchDir, PatchCache patches, Dictionary modCaches) + { + if (IsContentsDir(searchDir.Name)) + { + foreach ((ulong applicationId, ModCache cache) in modCaches) + { + QueryContentsDir(cache, searchDir, applicationId); + } + + return true; + } + else if (IsPatchesDir(searchDir.Name)) + { + QueryPatchDirs(patches, searchDir); + + return true; + } + + return false; + } + + foreach (var path in searchDirPaths) + { + var searchDir = new DirectoryInfo(path); + if (!searchDir.Exists) + { + Logger.Warning?.Print(LogClass.ModLoader, $"Mod Search Dir '{searchDir.FullName}' doesn't exist"); + return; + } + + if (!TryQuery(searchDir, patches, modCaches)) + { + foreach (var subdir in searchDir.EnumerateDirectories()) + { + TryQuery(subdir, patches, modCaches); + } + } + } + + patches.Initialized = true; + } + + public void CollectMods(IEnumerable applications, params string[] searchDirPaths) + { + Clear(); + + foreach (ulong applicationId in applications) + { + _appMods[applicationId] = new ModCache(); + } + + CollectMods(_appMods, _patches, searchDirPaths); + } + + internal IStorage ApplyRomFsMods(ulong applicationId, IStorage baseStorage) + { + if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.RomfsDirs.Count + mods.RomfsContainers.Count == 0) + { + return baseStorage; + } + + var fileSet = new HashSet(); + var builder = new RomFsBuilder(); + int count = 0; + + Logger.Info?.Print(LogClass.ModLoader, $"Applying RomFS mods for Application {applicationId:X16}"); + + // Prioritize loose files first + foreach (var mod in mods.RomfsDirs) + { + if (!mod.Enabled) + { + continue; + } + + using (IFileSystem fs = new LocalFileSystem(mod.Path.FullName)) + { + AddFiles(fs, mod.Name, mod.Path.FullName, fileSet, builder); + } + count++; + } + + // Then files inside images + foreach (var mod in mods.RomfsContainers) + { + if (!mod.Enabled) + { + continue; + } + + Logger.Info?.Print(LogClass.ModLoader, $"Found 'romfs.bin' for Application {applicationId:X16}"); + using (IFileSystem fs = new RomFsFileSystem(mod.Path.OpenRead().AsStorage())) + { + AddFiles(fs, mod.Name, mod.Path.FullName, fileSet, builder); + } + count++; + } + + if (fileSet.Count == 0) + { + Logger.Info?.Print(LogClass.ModLoader, "No files found. Using base RomFS"); + + return baseStorage; + } + + Logger.Info?.Print(LogClass.ModLoader, $"Replaced {fileSet.Count} file(s) over {count} mod(s). Processing base storage..."); + + // And finally, the base romfs + var baseRom = new RomFsFileSystem(baseStorage); + foreach (var entry in baseRom.EnumerateEntries() + .Where(f => f.Type == DirectoryEntryType.File && !fileSet.Contains(f.FullPath)) + .OrderBy(f => f.FullPath, StringComparer.Ordinal)) + { + using var file = new UniqueRef(); + + baseRom.OpenFile(ref file.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + builder.AddFile(entry.FullPath, file.Release()); + } + + Logger.Info?.Print(LogClass.ModLoader, "Building new RomFS..."); + IStorage newStorage = builder.Build(); + Logger.Info?.Print(LogClass.ModLoader, "Using modded RomFS"); + + return newStorage; + } + + private static void AddFiles(IFileSystem fs, string modName, string rootPath, ISet fileSet, RomFsBuilder builder) + { + foreach (var entry in fs.EnumerateEntries() + .AsParallel() + .Where(f => f.Type == DirectoryEntryType.File) + .OrderBy(f => f.FullPath, StringComparer.Ordinal)) + { + var file = new LazyFile(entry.FullPath, rootPath, fs); + + if (fileSet.Add(entry.FullPath)) + { + builder.AddFile(entry.FullPath, file); + } + else + { + Logger.Warning?.Print(LogClass.ModLoader, $" Skipped duplicate file '{entry.FullPath}' from '{modName}'", "ApplyRomFsMods"); + } + } + } + + internal bool ReplaceExefsPartition(ulong applicationId, ref IFileSystem exefs) + { + if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.ExefsContainers.Count == 0) + { + return false; + } + + if (mods.ExefsContainers.Count > 1) + { + Logger.Warning?.Print(LogClass.ModLoader, "Multiple ExeFS partition replacements detected"); + } + + Logger.Info?.Print(LogClass.ModLoader, "Using replacement ExeFS partition"); + + var pfs = new PartitionFileSystem(); + pfs.Initialize(mods.ExefsContainers[0].Path.OpenRead().AsStorage()).ThrowIfFailure(); + exefs = pfs; + + return true; + } + + public struct ModLoadResult + { + public BitVector32 Stubs; + public BitVector32 Replaces; + public MetaLoader Npdm; + + public bool Modified => (Stubs.Data | Replaces.Data) != 0; + } + + internal ModLoadResult ApplyExefsMods(ulong applicationId, NsoExecutable[] nsos) + { + ModLoadResult modLoadResult = new() + { + Stubs = new BitVector32(), + Replaces = new BitVector32(), + }; + + if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.ExefsDirs.Count == 0) + { + return modLoadResult; + } + + if (nsos.Length != ProcessConst.ExeFsPrefixes.Length) + { + throw new ArgumentOutOfRangeException(nameof(nsos), nsos.Length, "NSO Count is incorrect"); + } + + var exeMods = mods.ExefsDirs; + + foreach (var mod in exeMods) + { + if (!mod.Enabled) + { + continue; + } + + for (int i = 0; i < ProcessConst.ExeFsPrefixes.Length; ++i) + { + var nsoName = ProcessConst.ExeFsPrefixes[i]; + + FileInfo nsoFile = new(Path.Combine(mod.Path.FullName, nsoName)); + if (nsoFile.Exists) + { + if (modLoadResult.Replaces[1 << i]) + { + Logger.Warning?.Print(LogClass.ModLoader, $"Multiple replacements to '{nsoName}'"); + + continue; + } + + modLoadResult.Replaces[1 << i] = true; + + nsos[i] = new NsoExecutable(nsoFile.OpenRead().AsStorage(), nsoName); + Logger.Info?.Print(LogClass.ModLoader, $"NSO '{nsoName}' replaced"); + } + + modLoadResult.Stubs[1 << i] |= File.Exists(Path.Combine(mod.Path.FullName, nsoName + StubExtension)); + } + + FileInfo npdmFile = new(Path.Combine(mod.Path.FullName, "main.npdm")); + if (npdmFile.Exists) + { + if (modLoadResult.Npdm != null) + { + Logger.Warning?.Print(LogClass.ModLoader, "Multiple replacements to 'main.npdm'"); + + continue; + } + + modLoadResult.Npdm = new MetaLoader(); + modLoadResult.Npdm.Load(File.ReadAllBytes(npdmFile.FullName)); + + Logger.Info?.Print(LogClass.ModLoader, "main.npdm replaced"); + } + } + + for (int i = ProcessConst.ExeFsPrefixes.Length - 1; i >= 0; --i) + { + if (modLoadResult.Stubs[1 << i] && !modLoadResult.Replaces[1 << i]) // Prioritizes replacements over stubs + { + Logger.Info?.Print(LogClass.ModLoader, $" NSO '{nsos[i].Name}' stubbed"); + nsos[i] = null; + } + } + + return modLoadResult; + } + + internal void ApplyNroPatches(NroExecutable nro) + { + var nroPatches = _patches.NroPatches; + + if (nroPatches.Count == 0) + { + return; + } + + // NRO patches aren't offset relative to header unlike NSO + // according to Atmosphere's ro patcher module + ApplyProgramPatches(nroPatches, 0, nro); + } + + internal bool ApplyNsoPatches(ulong applicationId, params IExecutable[] programs) + { + IEnumerable> nsoMods = _patches.NsoPatches; + + if (_appMods.TryGetValue(applicationId, out ModCache mods)) + { + nsoMods = nsoMods.Concat(mods.ExefsDirs); + } + + // NSO patches are created with offset 0 according to Atmosphere's patcher module + // But `Program` doesn't contain the header which is 0x100 bytes. So, we adjust for that here + return ApplyProgramPatches(nsoMods, 0x100, programs); + } + + internal void LoadCheats(ulong applicationId, ProcessTamperInfo tamperInfo, TamperMachine tamperMachine) + { + if (tamperInfo?.BuildIds == null || tamperInfo.CodeAddresses == null) + { + Logger.Error?.Print(LogClass.ModLoader, "Unable to install cheat because the associated process is invalid"); + + return; + } + + Logger.Info?.Print(LogClass.ModLoader, $"Build ids found for application {applicationId:X16}:\n {String.Join("\n ", tamperInfo.BuildIds)}"); + + if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.Cheats.Count == 0) + { + return; + } + + var cheats = mods.Cheats; + var processExes = tamperInfo.BuildIds.Zip(tamperInfo.CodeAddresses, (k, v) => new { k, v }) + .ToDictionary(x => x.k[..Math.Min(Cheat.CheatIdSize, x.k.Length)], x => x.v); + + foreach (var cheat in cheats) + { + string cheatId = Path.GetFileNameWithoutExtension(cheat.Path.Name).ToUpper(); + + if (!processExes.TryGetValue(cheatId, out ulong exeAddress)) + { + Logger.Warning?.Print(LogClass.ModLoader, $"Skipping cheat '{cheat.Name}' because no executable matches its BuildId {cheatId} (check if the game title and version are correct)"); + + continue; + } + + Logger.Info?.Print(LogClass.ModLoader, $"Installing cheat '{cheat.Name}'"); + + tamperMachine.InstallAtmosphereCheat(cheat.Name, cheatId, cheat.Instructions, tamperInfo, exeAddress); + } + + EnableCheats(applicationId, tamperMachine); + } + + internal static void EnableCheats(ulong applicationId, TamperMachine tamperMachine) + { + var contentDirectory = FindApplicationDir(new DirectoryInfo(Path.Combine(GetModsBasePath(), AmsContentsDir)), $"{applicationId:x16}"); + string enabledCheatsPath = Path.Combine(contentDirectory.FullName, CheatDir, "enabled.txt"); + + if (File.Exists(enabledCheatsPath)) + { + tamperMachine.EnableCheats(File.ReadAllLines(enabledCheatsPath)); + } + } + + private static bool ApplyProgramPatches(IEnumerable> mods, int protectedOffset, params IExecutable[] programs) + { + int count = 0; + + MemPatch[] patches = new MemPatch[programs.Length]; + + for (int i = 0; i < patches.Length; ++i) + { + patches[i] = new MemPatch(); + } + + var buildIds = programs.Select(p => p switch + { + NsoExecutable nso => Convert.ToHexString(nso.BuildId.ItemsRo.ToArray()).TrimEnd('0'), + NroExecutable nro => Convert.ToHexString(nro.Header.BuildId).TrimEnd('0'), + _ => string.Empty, + }).ToList(); + + int GetIndex(string buildId) => buildIds.FindIndex(id => id == buildId); // O(n) but list is small + + // Collect patches + foreach (var mod in mods) + { + if (!mod.Enabled) + { + continue; + } + + var patchDir = mod.Path; + foreach (var patchFile in patchDir.EnumerateFiles()) + { + if (StrEquals(".ips", patchFile.Extension)) // IPS|IPS32 + { + string filename = Path.GetFileNameWithoutExtension(patchFile.FullName).Split('.')[0]; + string buildId = filename.TrimEnd('0'); + + int index = GetIndex(buildId); + if (index == -1) + { + continue; + } + + Logger.Info?.Print(LogClass.ModLoader, $"Matching IPS patch '{patchFile.Name}' in '{mod.Name}' bid={buildId}"); + + using var fs = patchFile.OpenRead(); + using var reader = new BinaryReader(fs); + + var patcher = new IpsPatcher(reader); + patcher.AddPatches(patches[index]); + } + else if (StrEquals(".pchtxt", patchFile.Extension)) // IPSwitch + { + using var fs = patchFile.OpenRead(); + using var reader = new StreamReader(fs); + + var patcher = new IPSwitchPatcher(reader); + + int index = GetIndex(patcher.BuildId); + if (index == -1) + { + continue; + } + + Logger.Info?.Print(LogClass.ModLoader, $"Matching IPSwitch patch '{patchFile.Name}' in '{mod.Name}' bid={patcher.BuildId}"); + + patcher.AddPatches(patches[index]); + } + } + } + + // Apply patches + for (int i = 0; i < programs.Length; ++i) + { + count += patches[i].Patch(programs[i].Program, protectedOffset); + } + + return count > 0; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/ResultCode.cs b/src/Ryujinx.HLE/HOS/ResultCode.cs new file mode 100644 index 00000000..f1309460 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/ResultCode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS +{ + public enum ResultCode + { + OsModuleId = 3, + ErrorCodeShift = 9, + + Success = 0, + + NotAllocated = (1023 << ErrorCodeShift) | OsModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/ServiceCtx.cs b/src/Ryujinx.HLE/HOS/ServiceCtx.cs new file mode 100644 index 00000000..ba27e12e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/ServiceCtx.cs @@ -0,0 +1,40 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Memory; +using System.IO; + +namespace Ryujinx.HLE.HOS +{ + class ServiceCtx + { + public Switch Device { get; } + public KProcess Process { get; } + public IVirtualMemoryManager Memory { get; } + public KThread Thread { get; } + public IpcMessage Request { get; } + public IpcMessage Response { get; } + public BinaryReader RequestData { get; } + public BinaryWriter ResponseData { get; } + + public ServiceCtx( + Switch device, + KProcess process, + IVirtualMemoryManager memory, + KThread thread, + IpcMessage request, + IpcMessage response, + BinaryReader requestData, + BinaryWriter responseData) + { + Device = device; + Process = process; + Memory = memory; + Thread = thread; + Request = request; + Response = response; + RequestData = requestData; + ResponseData = responseData; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs new file mode 100644 index 00000000..c724660e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs @@ -0,0 +1,254 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Sdk.Account; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + public class AccountManager : IEmulatorAccountManager + { + public static readonly UserId DefaultUserId = new("00000000000000010000000000000000"); + + private readonly AccountSaveDataManager _accountSaveDataManager; + + // Todo: The account service doesn't have the permissions to delete save data. Qlaunch takes care of deleting + // save data, so we're currently passing a client with full permissions. Consider moving save data deletion + // outside of the AccountManager. + private readonly HorizonClient _horizonClient; + + private readonly ConcurrentDictionary _profiles; + private UserProfile[] _storedOpenedUsers; + + public UserProfile LastOpenedUser { get; private set; } + + public AccountManager(HorizonClient horizonClient, string initialProfileName = null) + { + _horizonClient = horizonClient; + + _profiles = new ConcurrentDictionary(); + _storedOpenedUsers = Array.Empty(); + + _accountSaveDataManager = new AccountSaveDataManager(_profiles); + + if (!_profiles.TryGetValue(DefaultUserId.ToString(), out _)) + { + byte[] defaultUserImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg"); + + AddUser("RyuPlayer", defaultUserImage, DefaultUserId); + + OpenUser(DefaultUserId); + } + else + { + UserId commandLineUserProfileOverride = default; + if (!string.IsNullOrEmpty(initialProfileName)) + { + commandLineUserProfileOverride = _profiles.Values.FirstOrDefault(x => x.Name == initialProfileName)?.UserId ?? default; + if (commandLineUserProfileOverride.IsNull) + { + Logger.Warning?.Print(LogClass.Application, $"The command line specified profile named '{initialProfileName}' was not found"); + } + } + OpenUser(commandLineUserProfileOverride.IsNull ? _accountSaveDataManager.LastOpened : commandLineUserProfileOverride); + } + } + + public void AddUser(string name, byte[] image, UserId userId = new UserId()) + { + if (userId.IsNull) + { + userId = new UserId(Guid.NewGuid().ToString().Replace("-", "")); + } + + UserProfile profile = new(userId, name, image); + + _profiles.AddOrUpdate(userId.ToString(), profile, (key, old) => profile); + + _accountSaveDataManager.Save(_profiles); + } + + public void OpenUser(UserId userId) + { + if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile)) + { + // TODO: Support multiple open users ? + foreach (UserProfile userProfile in GetAllUsers()) + { + if (userProfile == LastOpenedUser) + { + userProfile.AccountState = AccountState.Closed; + + break; + } + } + + (LastOpenedUser = profile).AccountState = AccountState.Open; + + _accountSaveDataManager.LastOpened = userId; + } + + _accountSaveDataManager.Save(_profiles); + } + + public void CloseUser(UserId userId) + { + if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile)) + { + profile.AccountState = AccountState.Closed; + } + + _accountSaveDataManager.Save(_profiles); + } + + public void OpenUserOnlinePlay(Uid userId) + { + OpenUserOnlinePlay(new UserId((long)userId.Low, (long)userId.High)); + } + + public void OpenUserOnlinePlay(UserId userId) + { + if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile)) + { + // TODO: Support multiple open online users ? + foreach (UserProfile userProfile in GetAllUsers()) + { + if (userProfile == LastOpenedUser) + { + userProfile.OnlinePlayState = AccountState.Closed; + + break; + } + } + + profile.OnlinePlayState = AccountState.Open; + } + + _accountSaveDataManager.Save(_profiles); + } + + public void CloseUserOnlinePlay(Uid userId) + { + CloseUserOnlinePlay(new UserId((long)userId.Low, (long)userId.High)); + } + + public void CloseUserOnlinePlay(UserId userId) + { + if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile)) + { + profile.OnlinePlayState = AccountState.Closed; + } + + _accountSaveDataManager.Save(_profiles); + } + + public void SetUserImage(UserId userId, byte[] image) + { + foreach (UserProfile userProfile in GetAllUsers()) + { + if (userProfile.UserId == userId) + { + userProfile.Image = image; + + break; + } + } + + _accountSaveDataManager.Save(_profiles); + } + + public void SetUserName(UserId userId, string name) + { + foreach (UserProfile userProfile in GetAllUsers()) + { + if (userProfile.UserId == userId) + { + userProfile.Name = name; + + break; + } + } + + _accountSaveDataManager.Save(_profiles); + } + + public void DeleteUser(UserId userId) + { + DeleteSaveData(userId); + + _profiles.Remove(userId.ToString(), out _); + + OpenUser(DefaultUserId); + + _accountSaveDataManager.Save(_profiles); + } + + private void DeleteSaveData(UserId userId) + { + var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: default, + new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low), saveDataId: default, index: default); + + using var saveDataIterator = new UniqueRef(); + + _horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure(); + + Span saveDataInfo = stackalloc SaveDataInfo[10]; + + while (true) + { + saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure(); + + if (readCount == 0) + { + break; + } + + for (int i = 0; i < readCount; i++) + { + _horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, saveDataInfo[i].SaveDataId).ThrowIfFailure(); + } + } + } + + internal int GetUserCount() + { + return _profiles.Count; + } + + internal bool TryGetUser(UserId userId, out UserProfile profile) + { + return _profiles.TryGetValue(userId.ToString(), out profile); + } + + public IEnumerable GetAllUsers() + { + return _profiles.Values; + } + + internal IEnumerable GetOpenedUsers() + { + return _profiles.Values.Where(x => x.AccountState == AccountState.Open); + } + + internal IEnumerable GetStoredOpenedUsers() + { + return _storedOpenedUsers; + } + + internal void StoreOpenedUsers() + { + _storedOpenedUsers = _profiles.Values.Where(x => x.AccountState == AccountState.Open).ToArray(); + } + + internal UserProfile GetFirst() + { + return _profiles.First().Value; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs new file mode 100644 index 00000000..b1ef0761 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs @@ -0,0 +1,76 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS.Services.Account.Acc.Types; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + class AccountSaveDataManager + { + private readonly string _profilesJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "Profiles.json"); + + private static readonly ProfilesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public UserId LastOpened { get; set; } + + public AccountSaveDataManager(ConcurrentDictionary profiles) + { + // TODO: Use 0x8000000000000010 system savedata instead of a JSON file if needed. + + if (File.Exists(_profilesJsonPath)) + { + try + { + ProfilesJson profilesJson = JsonHelper.DeserializeFromFile(_profilesJsonPath, _serializerContext.ProfilesJson); + + foreach (var profile in profilesJson.Profiles) + { + UserProfile addedProfile = new(new UserId(profile.UserId), profile.Name, profile.Image, profile.LastModifiedTimestamp); + + profiles.AddOrUpdate(profile.UserId, addedProfile, (key, old) => addedProfile); + } + + LastOpened = new UserId(profilesJson.LastOpened); + } + catch (Exception ex) + { + Logger.Error?.Print(LogClass.Application, $"Failed to parse {_profilesJsonPath}: {ex.Message} Loading default profile!"); + + LastOpened = AccountManager.DefaultUserId; + } + } + else + { + LastOpened = AccountManager.DefaultUserId; + } + } + + public void Save(ConcurrentDictionary profiles) + { + ProfilesJson profilesJson = new() + { + Profiles = new List(), + LastOpened = LastOpened.ToString(), + }; + + foreach (var profile in profiles) + { + profilesJson.Profiles.Add(new UserProfileJson() + { + UserId = profile.Value.UserId.ToString(), + Name = profile.Value.Name, + AccountState = profile.Value.AccountState, + OnlinePlayState = profile.Value.OnlinePlayState, + LastModifiedTimestamp = profile.Value.LastModifiedTimestamp, + Image = profile.Value.Image, + }); + } + + JsonHelper.SerializeToFile(_profilesJsonPath, profilesJson, _serializerContext.ProfilesJson); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForApplication.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForApplication.cs new file mode 100644 index 00000000..3cb46d20 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForApplication.cs @@ -0,0 +1,75 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService +{ + class IManagerForApplication : IpcService + { + private readonly ManagerServer _managerServer; + + public IManagerForApplication(UserId userId) + { + _managerServer = new ManagerServer(userId); + } + + [CommandCmif(0)] + // CheckAvailability() + public ResultCode CheckAvailability(ServiceCtx context) + { + return _managerServer.CheckAvailability(context); + } + + [CommandCmif(1)] + // GetAccountId() -> nn::account::NetworkServiceAccountId + public ResultCode GetAccountId(ServiceCtx context) + { + return _managerServer.GetAccountId(context); + } + + [CommandCmif(2)] + // EnsureIdTokenCacheAsync() -> object + public ResultCode EnsureIdTokenCacheAsync(ServiceCtx context) + { + ResultCode resultCode = _managerServer.EnsureIdTokenCacheAsync(context, out IAsyncContext asyncContext); + + if (resultCode == ResultCode.Success) + { + MakeObject(context, asyncContext); + } + + return resultCode; + } + + [CommandCmif(3)] + // LoadIdTokenCache() -> (u32 id_token_cache_size, buffer) + public ResultCode LoadIdTokenCache(ServiceCtx context) + { + return _managerServer.LoadIdTokenCache(context); + } + + [CommandCmif(130)] + // GetNintendoAccountUserResourceCacheForApplication() -> (nn::account::NintendoAccountId, nn::account::nas::NasUserBaseForApplication, buffer) + public ResultCode GetNintendoAccountUserResourceCacheForApplication(ServiceCtx context) + { + return _managerServer.GetNintendoAccountUserResourceCacheForApplication(context); + } + + [CommandCmif(160)] // 5.0.0+ + // StoreOpenContext() + public ResultCode StoreOpenContext(ServiceCtx context) + { + return _managerServer.StoreOpenContext(context); + } + + [CommandCmif(170)] // 6.0.0+ + // LoadNetworkServiceLicenseKindAsync() -> object + public ResultCode LoadNetworkServiceLicenseKindAsync(ServiceCtx context) + { + ResultCode resultCode = _managerServer.LoadNetworkServiceLicenseKindAsync(context, out IAsyncNetworkServiceLicenseKindContext asyncContext); + + if (resultCode == ResultCode.Success) + { + MakeObject(context, asyncContext); + } + + return resultCode; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForSystemService.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForSystemService.cs new file mode 100644 index 00000000..8510837b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForSystemService.cs @@ -0,0 +1,47 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService +{ + class IManagerForSystemService : IpcService + { + private readonly ManagerServer _managerServer; + + public IManagerForSystemService(UserId userId) + { + _managerServer = new ManagerServer(userId); + } + + [CommandCmif(0)] + // CheckAvailability() + public ResultCode CheckAvailability(ServiceCtx context) + { + return _managerServer.CheckAvailability(context); + } + + [CommandCmif(1)] + // GetAccountId() -> nn::account::NetworkServiceAccountId + public ResultCode GetAccountId(ServiceCtx context) + { + return _managerServer.GetAccountId(context); + } + + [CommandCmif(2)] + // EnsureIdTokenCacheAsync() -> object + public ResultCode EnsureIdTokenCacheAsync(ServiceCtx context) + { + ResultCode resultCode = _managerServer.EnsureIdTokenCacheAsync(context, out IAsyncContext asyncContext); + + if (resultCode == ResultCode.Success) + { + MakeObject(context, asyncContext); + } + + return resultCode; + } + + [CommandCmif(3)] + // LoadIdTokenCache() -> (u32 id_token_cache_size, buffer) + public ResultCode LoadIdTokenCache(ServiceCtx context) + { + return _managerServer.LoadIdTokenCache(context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfile.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfile.cs new file mode 100644 index 00000000..a0021917 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfile.cs @@ -0,0 +1,40 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService +{ + class IProfile : IpcService + { + private readonly ProfileServer _profileServer; + + public IProfile(UserProfile profile) + { + _profileServer = new ProfileServer(profile); + } + + [CommandCmif(0)] + // Get() -> (nn::account::profile::ProfileBase, buffer) + public ResultCode Get(ServiceCtx context) + { + return _profileServer.Get(context); + } + + [CommandCmif(1)] + // GetBase() -> nn::account::profile::ProfileBase + public ResultCode GetBase(ServiceCtx context) + { + return _profileServer.GetBase(context); + } + + [CommandCmif(10)] + // GetImageSize() -> u32 + public ResultCode GetImageSize(ServiceCtx context) + { + return _profileServer.GetImageSize(context); + } + + [CommandCmif(11)] + // LoadImage() -> (u32, buffer) + public ResultCode LoadImage(ServiceCtx context) + { + return _profileServer.LoadImage(context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfileEditor.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfileEditor.cs new file mode 100644 index 00000000..5d5d0dd6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfileEditor.cs @@ -0,0 +1,54 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService +{ + class IProfileEditor : IpcService + { + private readonly ProfileServer _profileServer; + + public IProfileEditor(UserProfile profile) + { + _profileServer = new ProfileServer(profile); + } + + [CommandCmif(0)] + // Get() -> (nn::account::profile::ProfileBase, buffer) + public ResultCode Get(ServiceCtx context) + { + return _profileServer.Get(context); + } + + [CommandCmif(1)] + // GetBase() -> nn::account::profile::ProfileBase + public ResultCode GetBase(ServiceCtx context) + { + return _profileServer.GetBase(context); + } + + [CommandCmif(10)] + // GetImageSize() -> u32 + public ResultCode GetImageSize(ServiceCtx context) + { + return _profileServer.GetImageSize(context); + } + + [CommandCmif(11)] + // LoadImage() -> (u32, buffer) + public ResultCode LoadImage(ServiceCtx context) + { + return _profileServer.LoadImage(context); + } + + [CommandCmif(100)] + // Store(nn::account::profile::ProfileBase, buffer) + public ResultCode Store(ServiceCtx context) + { + return _profileServer.Store(context); + } + + [CommandCmif(101)] + // StoreWithImage(nn::account::profile::ProfileBase, buffer, buffer) + public ResultCode StoreWithImage(ServiceCtx context) + { + return _profileServer.StoreWithImage(context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs new file mode 100644 index 00000000..75bad0e3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs @@ -0,0 +1,199 @@ +using Microsoft.IdentityModel.JsonWebTokens; +using Microsoft.IdentityModel.Tokens; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Account.Acc.AsyncContext; +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Security.Principal; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService +{ + class ManagerServer + { + // TODO: Determine where and how NetworkServiceAccountId is set. + private const long NetworkServiceAccountId = 0xcafe; + +#pragma warning disable IDE0052 // Remove unread private member + private readonly UserId _userId; +#pragma warning restore IDE0052 + + private byte[] _cachedTokenData; + private DateTime _cachedTokenExpiry; + + public ManagerServer(UserId userId) + { + _userId = userId; + } + + private static string GenerateIdToken() + { + using RSA provider = RSA.Create(2048); + + RSAParameters parameters = provider.ExportParameters(true); + + RsaSecurityKey secKey = new(parameters); + + SigningCredentials credentials = new(secKey, "RS256"); + + credentials.Key.KeyId = parameters.ToString(); + + byte[] rawUserId = new byte[0x10]; + RandomNumberGenerator.Fill(rawUserId); + + byte[] deviceId = new byte[0x10]; + RandomNumberGenerator.Fill(deviceId); + + byte[] deviceAccountId = new byte[0x10]; + RandomNumberGenerator.Fill(deviceId); + + var descriptor = new SecurityTokenDescriptor + { + Subject = new GenericIdentity(Convert.ToHexString(rawUserId).ToLower()), + SigningCredentials = credentials, + Audience = "ed9e2f05d286f7b8", + Issuer = "https://e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com", + TokenType = "id_token", + IssuedAt = DateTime.UtcNow, + Expires = DateTime.UtcNow + TimeSpan.FromHours(3), + Claims = new Dictionary + { + { "jku", "https://e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com/1.0.0/certificates" }, + { "di", Convert.ToHexString(deviceId).ToLower() }, + { "sn", "XAW10000000000" }, + { "bs:did", Convert.ToHexString(deviceAccountId).ToLower() } + } + }; + + return new JsonWebTokenHandler().CreateToken(descriptor); + } + + public ResultCode CheckAvailability(ServiceCtx context) + { + // NOTE: This opens the file at "su/baas/USERID_IN_UUID_STRING.dat" where USERID_IN_UUID_STRING is formatted as "%08x-%04x-%04x-%02x%02x-%08x%04x". + // Then it searches the Availability of Online Services related to the UserId in this file and returns it. + + Logger.Stub?.PrintStub(LogClass.ServiceAcc); + + // NOTE: Even if we try to return different error codes here, the guest still needs other calls. + return ResultCode.Success; + } + + public ResultCode GetAccountId(ServiceCtx context) + { + // NOTE: This opens the file at "su/baas/USERID_IN_UUID_STRING.dat" (where USERID_IN_UUID_STRING is formatted + // as "%08x-%04x-%04x-%02x%02x-%08x%04x") in the account:/ savedata. + // Then it searches the NetworkServiceAccountId related to the UserId in this file and returns it. + + Logger.Stub?.PrintStub(LogClass.ServiceAcc, new { NetworkServiceAccountId }); + + context.ResponseData.Write(NetworkServiceAccountId); + + return ResultCode.Success; + } + + public ResultCode EnsureIdTokenCacheAsync(ServiceCtx context, out IAsyncContext asyncContext) + { + KEvent asyncEvent = new(context.Device.System.KernelContext); + AsyncExecution asyncExecution = new(asyncEvent); + + asyncExecution.Initialize(1000, EnsureIdTokenCacheAsyncImpl); + + asyncContext = new IAsyncContext(asyncExecution); + + // return ResultCode.NullObject if the IAsyncContext pointer is null. Doesn't occur in our case. + + return ResultCode.Success; + } + + private async Task EnsureIdTokenCacheAsyncImpl(CancellationToken token) + { + // NOTE: This open the file at "su/baas/USERID_IN_UUID_STRING.dat" (where USERID_IN_UUID_STRING is formatted as "%08x-%04x-%04x-%02x%02x-%08x%04x") + // in the "account:/" savedata. + // Then its read data, use dauth API with this data to get the Token Id and probably store the dauth response + // in "su/cache/USERID_IN_UUID_STRING.dat" (where USERID_IN_UUID_STRING is formatted as "%08x-%04x-%04x-%02x%02x-%08x%04x") in the "account:/" savedata. + // Since we don't support online services, we can stub it. + + Logger.Stub?.PrintStub(LogClass.ServiceAcc); + + // TODO: Use a real function instead, with the CancellationToken. + await Task.CompletedTask; + } + + public ResultCode LoadIdTokenCache(ServiceCtx context) + { + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong bufferSize = context.Request.ReceiveBuff[0].Size; +#pragma warning restore IDE0059 + + // NOTE: This opens the file at "su/cache/USERID_IN_UUID_STRING.dat" (where USERID_IN_UUID_STRING is formatted as "%08x-%04x-%04x-%02x%02x-%08x%04x") + // in the "account:/" savedata and writes some data in the buffer. + // Since we don't support online services, we can stub it. + + Logger.Stub?.PrintStub(LogClass.ServiceAcc); + + /* + if (internal_object != null) + { + if (bufferSize > 0xC00) + { + return ResultCode.InvalidIdTokenCacheBufferSize; + } + } + */ + + if (_cachedTokenData == null || DateTime.UtcNow > _cachedTokenExpiry) + { + _cachedTokenExpiry = DateTime.UtcNow + TimeSpan.FromHours(3); + _cachedTokenData = Encoding.ASCII.GetBytes(GenerateIdToken()); + } + + byte[] tokenData = _cachedTokenData; + + context.Memory.Write(bufferPosition, tokenData); + context.ResponseData.Write(tokenData.Length); + + return ResultCode.Success; + } + + public ResultCode GetNintendoAccountUserResourceCacheForApplication(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAcc, new { NetworkServiceAccountId }); + + context.ResponseData.Write(NetworkServiceAccountId); + + // TODO: determine and fill the output IPC buffer. + + return ResultCode.Success; + } + + public ResultCode StoreOpenContext(ServiceCtx context) + { + context.Device.System.AccountManager.StoreOpenedUsers(); + + return ResultCode.Success; + } + + public ResultCode LoadNetworkServiceLicenseKindAsync(ServiceCtx context, out IAsyncNetworkServiceLicenseKindContext asyncContext) + { + KEvent asyncEvent = new(context.Device.System.KernelContext); + AsyncExecution asyncExecution = new(asyncEvent); + + Logger.Stub?.PrintStub(LogClass.ServiceAcc); + + // NOTE: This is an extension of the data retrieved from the id token cache. + asyncExecution.Initialize(1000, EnsureIdTokenCacheAsyncImpl); + + asyncContext = new IAsyncNetworkServiceLicenseKindContext(asyncExecution, NetworkServiceLicenseKind.Subscribed); + + // return ResultCode.NullObject if the IAsyncNetworkServiceLicenseKindContext pointer is null. Doesn't occur in our case. + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs new file mode 100644 index 00000000..72560618 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs @@ -0,0 +1,114 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.Utilities; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService +{ + class ProfileServer + { + private readonly UserProfile _profile; + + public ProfileServer(UserProfile profile) + { + _profile = profile; + } + + public ResultCode Get(ServiceCtx context) + { + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(0x80UL); + + ulong bufferPosition = context.Request.RecvListBuff[0].Position; + + MemoryHelper.FillWithZeros(context.Memory, bufferPosition, 0x80); + + // TODO: Determine the struct. + context.Memory.Write(bufferPosition, 0); // Unknown + context.Memory.Write(bufferPosition + 4, 1); // Icon ID. 0 = Mii, the rest are character icon IDs. + context.Memory.Write(bufferPosition + 8, (byte)1); // Profile icon background color ID + // 0x07 bytes - Unknown + // 0x10 bytes - Some ID related to the Mii? All zeros when a character icon is used. + // 0x60 bytes - Usually zeros? + + Logger.Stub?.PrintStub(LogClass.ServiceAcc); + + return GetBase(context); + } + + public ResultCode GetBase(ServiceCtx context) + { + _profile.UserId.Write(context.ResponseData); + + context.ResponseData.Write(_profile.LastModifiedTimestamp); + + byte[] username = StringUtils.GetFixedLengthBytes(_profile.Name, 0x20, Encoding.UTF8); + + context.ResponseData.Write(username); + + return ResultCode.Success; + } + + public ResultCode GetImageSize(ServiceCtx context) + { + context.ResponseData.Write(_profile.Image.Length); + + return ResultCode.Success; + } + + public ResultCode LoadImage(ServiceCtx context) + { + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + if ((ulong)_profile.Image.Length > bufferLen) + { + return ResultCode.InvalidBufferSize; + } + + context.Memory.Write(bufferPosition, _profile.Image); + + context.ResponseData.Write(_profile.Image.Length); + + return ResultCode.Success; + } + + public ResultCode Store(ServiceCtx context) + { + ulong userDataPosition = context.Request.PtrBuff[0].Position; + ulong userDataSize = context.Request.PtrBuff[0].Size; + + byte[] userData = new byte[userDataSize]; + + context.Memory.Read(userDataPosition, userData); + + // TODO: Read the nn::account::profile::ProfileBase and store everything in the savedata. + + Logger.Stub?.PrintStub(LogClass.ServiceAcc, new { userDataSize }); + + return ResultCode.Success; + } + + public ResultCode StoreWithImage(ServiceCtx context) + { + ulong userDataPosition = context.Request.PtrBuff[0].Position; + ulong userDataSize = context.Request.PtrBuff[0].Size; + + byte[] userData = new byte[userDataSize]; + + context.Memory.Read(userDataPosition, userData); + + ulong profileImagePosition = context.Request.SendBuff[0].Position; + ulong profileImageSize = context.Request.SendBuff[0].Size; + + byte[] profileImageData = new byte[profileImageSize]; + + context.Memory.Read(profileImagePosition, profileImageData); + + // TODO: Read the nn::account::profile::ProfileBase and store everything in the savedata. + + Logger.Stub?.PrintStub(LogClass.ServiceAcc, new { userDataSize, profileImageSize }); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs new file mode 100644 index 00000000..da3555c8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs @@ -0,0 +1,254 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Account.Acc.AccountService; +using Ryujinx.HLE.HOS.Services.Account.Acc.AsyncContext; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + class ApplicationServiceServer + { + readonly AccountServiceFlag _serviceFlag; + + public ApplicationServiceServer(AccountServiceFlag serviceFlag) + { + _serviceFlag = serviceFlag; + } + + public ResultCode GetUserCountImpl(ServiceCtx context) + { + context.ResponseData.Write(context.Device.System.AccountManager.GetUserCount()); + + return ResultCode.Success; + } + + public ResultCode GetUserExistenceImpl(ServiceCtx context) + { + ResultCode resultCode = CheckUserId(context, out UserId userId); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + context.ResponseData.Write(context.Device.System.AccountManager.TryGetUser(userId, out _)); + + return ResultCode.Success; + } + + public ResultCode ListAllUsers(ServiceCtx context) + { + return WriteUserList(context, context.Device.System.AccountManager.GetAllUsers()); + } + + public ResultCode ListOpenUsers(ServiceCtx context) + { + return WriteUserList(context, context.Device.System.AccountManager.GetOpenedUsers()); + } + + private ResultCode WriteUserList(ServiceCtx context, IEnumerable profiles) + { + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.InvalidBuffer; + } + + ulong outputPosition = context.Request.RecvListBuff[0].Position; + ulong outputSize = context.Request.RecvListBuff[0].Size; + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + ulong offset = 0; + + foreach (UserProfile userProfile in profiles) + { + if (offset + 0x10 > outputSize) + { + break; + } + + context.Memory.Write(outputPosition + offset, userProfile.UserId.High); + context.Memory.Write(outputPosition + offset + 8, userProfile.UserId.Low); + + offset += 0x10; + } + + return ResultCode.Success; + } + + public ResultCode GetLastOpenedUser(ServiceCtx context) + { + context.Device.System.AccountManager.LastOpenedUser.UserId.Write(context.ResponseData); + + return ResultCode.Success; + } + + public ResultCode GetProfile(ServiceCtx context, out IProfile profile) + { + profile = default; + + ResultCode resultCode = CheckUserId(context, out UserId userId); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (!context.Device.System.AccountManager.TryGetUser(userId, out UserProfile userProfile)) + { + Logger.Warning?.Print(LogClass.ServiceAcc, $"User 0x{userId} not found!"); + + return ResultCode.UserNotFound; + } + + profile = new IProfile(userProfile); + + // Doesn't occur in our case. + // return ResultCode.NullObject; + + return ResultCode.Success; + } + + public ResultCode IsUserRegistrationRequestPermitted(ServiceCtx context) + { + context.ResponseData.Write(_serviceFlag != AccountServiceFlag.Application); + + return ResultCode.Success; + } + + public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context) + { + if (context.Device.System.AccountManager.GetUserCount() < 1) + { + // Invalid UserId. + UserId.Null.Write(context.ResponseData); + + return ResultCode.UserNotFound; + } + + bool isNetworkServiceAccountRequired = context.RequestData.ReadBoolean(); + + if (isNetworkServiceAccountRequired) + { + // NOTE: This checks something related to baas (online), and then return an invalid UserId if the check in baas returns an error code. + // In our case, we can just log it for now. + + Logger.Stub?.PrintStub(LogClass.ServiceAcc, new { isNetworkServiceAccountRequired }); + } + + // NOTE: As we returned an invalid UserId if there is more than one user earlier, now we can return only the first one. + context.Device.System.AccountManager.GetFirst().UserId.Write(context.ResponseData); + + return ResultCode.Success; + } + + public ResultCode CheckNetworkServiceAvailabilityAsync(ServiceCtx context, out IAsyncContext asyncContext) + { + KEvent asyncEvent = new(context.Device.System.KernelContext); + AsyncExecution asyncExecution = new(asyncEvent); + + asyncExecution.Initialize(1000, CheckNetworkServiceAvailabilityAsyncImpl); + + asyncContext = new IAsyncContext(asyncExecution); + + // return ResultCode.NullObject if the IAsyncContext pointer is null. Doesn't occur in our case. + + return ResultCode.Success; + } + + private async Task CheckNetworkServiceAvailabilityAsyncImpl(CancellationToken token) + { + Logger.Stub?.PrintStub(LogClass.ServiceAcc); + + // TODO: Use a real function instead, with the CancellationToken. + await Task.CompletedTask; + } + + public ResultCode StoreSaveDataThumbnail(ServiceCtx context) + { + ResultCode resultCode = CheckUserId(context, out UserId _); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (context.Request.SendBuff.Count == 0) + { + return ResultCode.InvalidBuffer; + } + + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + if (inputSize != 0x24000) + { + return ResultCode.InvalidBufferSize; + } + + byte[] thumbnailBuffer = new byte[inputSize]; + + context.Memory.Read(inputPosition, thumbnailBuffer); + + // NOTE: Account service call nn::fs::WriteSaveDataThumbnailFile(). + // TODO: Store thumbnailBuffer somewhere, in save data 0x8000000000000010 ? + + Logger.Stub?.PrintStub(LogClass.ServiceAcc); + + return ResultCode.Success; + } + + public ResultCode ClearSaveDataThumbnail(ServiceCtx context) + { + ResultCode resultCode = CheckUserId(context, out UserId _); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + /* + // NOTE: Doesn't occur in our case. + if (userId == null) + { + return ResultCode.InvalidArgument; + } + */ + + // NOTE: Account service call nn::fs::WriteSaveDataThumbnailFileHeader(); + // TODO: Clear the Thumbnail somewhere, in save data 0x8000000000000010 ? + + Logger.Stub?.PrintStub(LogClass.ServiceAcc); + + return ResultCode.Success; + } + + public ResultCode ListOpenContextStoredUsers(ServiceCtx context) + { + return WriteUserList(context, context.Device.System.AccountManager.GetStoredOpenedUsers()); + } + + public ResultCode ListQualifiedUsers(ServiceCtx context) + { + // TODO: Determine how users are "qualified". We assume all users are "qualified" for now. + + return WriteUserList(context, context.Device.System.AccountManager.GetAllUsers()); + } + + public ResultCode CheckUserId(ServiceCtx context, out UserId userId) + { + userId = context.RequestData.ReadStruct(); + + if (userId.IsNull) + { + return ResultCode.NullArgument; + } + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AsyncContext/AsyncExecution.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AsyncContext/AsyncExecution.cs new file mode 100644 index 00000000..9e329b8d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AsyncContext/AsyncExecution.cs @@ -0,0 +1,56 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc.AsyncContext +{ + class AsyncExecution + { + private readonly CancellationTokenSource _tokenSource; + private readonly CancellationToken _token; + + public KEvent SystemEvent { get; } + public bool IsInitialized { get; private set; } + public bool IsRunning { get; private set; } + + public AsyncExecution(KEvent asyncEvent) + { + SystemEvent = asyncEvent; + + _tokenSource = new CancellationTokenSource(); + _token = _tokenSource.Token; + } + + public void Initialize(int timeout, Func taskAsync) + { + Task.Run(async () => + { + IsRunning = true; + + _tokenSource.CancelAfter(timeout); + + try + { + await taskAsync(_token); + } + catch (Exception ex) + { + Logger.Warning?.Print(LogClass.ServiceAcc, $"Exception: {ex.Message}"); + } + + SystemEvent.ReadableEvent.Signal(); + + IsRunning = false; + }, _token); + + IsInitialized = true; + } + + public void Cancel() + { + _tokenSource.Cancel(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg b/src/Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..64c4e8ec7526418d1848c01ab0a676229a4fad61 GIT binary patch literal 49000 zcmeFabzD_V*D$;RX`~Sp1f;uLx&4AH`ERQG&KPZ003|RY&22;1I(S*qg??o&+`CajYj=De+P~2Ck{G@$Pa*5fC2ou zp$VMl$-#V7Fci4(vu!qb?+5Ts0s(-P_}5*|7H$op*KvTm!x0W}7kUK+dI3IuF?u#5 zD9je_gU6 z3zYMBU(gu->I)jj-}{1&_M3(P`fu8oWY4JqfGT(gwf5tV97e7I1n0f5RL}&_k?R2A zc?qx$MByRFfJXoU4h{}34goGM0SP`HJ_!X80Ra&O6&V=?85tD`!Fl?5`%(D!E80bT z{ELJai3tgb$q5Mw$x#LBk+HLr#F5#6x~UlL4i~ zL_1HQz^H@)prK#D#KOkG#lr^;;9o1z0CbFBmBawr1vCuw3+R~GSU8v%1cG2C3C4wM zr2Lq2H?7DRT<;5Dk-tnSmS<$5(0y+$DCG8_M**8TLT__s%LXBQDYfJwi(;g{t7$Q*9$$BqS))WO9{jd1s$yjC6&#yY z*)p_nKmwqngVJ6&mkApSQ}|qjYy6}aKnYwK$S?)&za&RV@LsoPQ;^Ae#_d6bf*yqp zLTHN_3nc(Hi|~hDP>3{2TO|V#cgXB-5&nA#SpO0MISLS9obx0BWPn}U2(=42Oc848 zYD^Kj5o%0P=@Dwo?EeJNMt1+ij)tZ5^Vh(rcif%@3CsG}XnY>`45dD7Y)MZ+mK4zNqVCAh@xAPb0PzRg#3SVBEv699wV=$#M1N)q%qbFMpCA-hY zN2j2ls;89mD~_1HJC%>9W!9v+j)QaQA6}4CbE$f5d@9f~@aW*HL?q>Hvx$MSB$tV&2ckWDqJBId z8sZ90+Ih^0G>*C+#vgHod~7Afx7g?rd$Nmm?z|8zX=r(3r`4s`7jCYUc43M&e}Z$ znKw5$+ORR^zLHq0od*Ag1h^dt?iPm=Hwo~R9dN&k<=+yUqniIR^ZdE@x(X0_w^0=P z%ld9%&atA2Kb=Z1H=V}a^@)=h=OeuBrlX2xqKddy4~sNY)5H{d-*_*g$>CcS(Og^4 z-P-smNc5${S{Nf)v`lHXN!Z`;u;bzqFBYMw*BO+HXl|ED!1AWDM$d=IGFM>b~Q>;#f;KyP<~! z0*6yDL?^=0Y^epY3)Rr)0~aUaKPE+b#{z#<5=U##j(@t0r3iq z9IN4j8q(vo!u!%lz+er2_wJCfU!mR9rm26@41aIU>eH5$XosnkD8P^Yl%pg__TGc8 z&G6k^B%rZ)6wT2z820csk;(e2XGOVLDmfarpp|&<2!!Z7C~hGE&vD0*#!JtVmWGf3 zv>t3!L$N{Obsn3_ zA%s$Sgl&6(9D-*5*y5XW-qhh@OgHyHp|B~nQo)?uct^*Ph;aBpdC?KwbiL5xT$JX6 zme`o47zV)*dg{~B(}ixTqg5NHoDXf27_atUJI2jahdTy;tIru?-*e2b2{yX#C)-^8 zt%gQu7sevpW`EW*Z@lrr>pMQzu1s7-k#?Su6~EZ9Pp5rs48dd4ohPS@IrbjZTRX0> z_f0jmZu!^pvn6o3{4Dm{_e)1dC(Gh@+bFSJ4kC=B{0*pPUNjJyb*m6pVT+0i?C--}u#^)4z z>&?Pdh*%N=XO-@44!ywI@S_<#t_TmvjA2#kYEQOQy@rF0_x>jHL+YQ*po$e)n zN*PM&glF|KTJ>WUjjjXX`3jzAI|JjdA9;t@h@}#xmdz51^0p=fQ%R;j=8AZ7;N3;; z+P>T96?}rNoq78?ft>R6K~?PN44>I;Kl`0q&E-!=)wo(!+muywHDfbd!o+5%Wc6=f zu$DvzC-H#|Wbcnrg3rqG{Dz-^8!cyMO7(^W5@0TH43KWA9Wih|7_%>?usyYFZYs@8 z$d5I_!mq!Rm+?Nc-AuYmNehuz3p?f>G0%tRXd!`{xTI)|$(D~9d>AjAtKMIt>V0dAuf$nSl+#%%7Yc(yIuDiY9&)qj8l zC>C1?iuYHYO~25HYm{7Wk)4LlLju4OSw|j=Y?~*TQPD5L4&yjE|5HJgKc*UfZws&mv-1kN{42 zA3~tU4j~blyQiJ%(4KuA5h`1;!jsZ5+*P(-G(08)&U6oStyNr_l%S*8?E<$_e8)1Y06E$={>uOTd23Tr7jsRggTzEy!=ymn#8 z(T)afcs;vRZ%0l-2Fnt6PFm-kn zqYCu1I2N!hx^rbTmjnr1jb26qgR!zXcDoiy{cgoY9ae8`+WonEOh*?4I;tsPFuvDyP0YN6|!M*$0UDx;_JrcXb<#JU)`mQMG5r?wvn z`skabAJZ#*|t<5XV={~Al z{ZcVgB|QAvQ@GQCAENzsC`5o-y4^LuvInLhlTg?$<_YP!*~EuNIy_Tn>>iL;pIvyGm; zOTiz%FL38u^S_p<#rFyS-o`f*6!}s5jyS)vbdd#&PW!;SYT()6ktWrE-o95mRrf~E zAf#u*b3taJ{%jtBpYMK>AJe0x4GAXZra6&!?`Gcel_hp?9VxXOM6W)K=70Ho&R+4^>|tY*&zaeYzBY}UbqkH`C#^6U zEtyR>iB_hF%oWtjo;I@o=W8~YryFu@=ol=aSMmvk8Mx5H?uW)qt%!^+gKr-42F#^< zwMUjWzC-Ta@OYkWM-gFgQm}MJCn#-1B2^XeDYimpF^4(}3ACLGB7uvQNI+`xL1>_Y zZ>1*Uxe%m8?d$lMqy6E&6gVlH69h=!{m9FI(rmd8j+@l|uR$$)i>HlX6pxct?k<(ivjrSMCqxh?ihrgY2O!@H+7a&->CC4ovDZa5*NxqK+ zGRWDJ>V#_EUbT@M#tjW9I?U~U-!UmjQa#Vrqu-g|K@qgpE6 z*M|hwiOz`Z4>-j{XD-<}e;lJK;-M|={I;GqlD!{b?l9N%T=C_j(DEuTB;c?k*(mx& zgG+9~hQBDY-oOf%4l<3Lif~-OQ-a+UNBJG9uE28d4o0$bxF$M@-!V& z=^l}3Uk|&|${lmX-(9x7;H|R0?|FE+?QVs*x(!!|=nKodfm4T%XHUT|H8Lo2;4gC# z1r5UJr$JQQs|-+IN+Kv~4J(eO0Y|w8_|mVmBz^g8uH}sKEGjNi>bW02ZTB}Zg}52| zaK1$D!je&Tn|i^Q{zCA;wbY@CEHUB9iOG4YOVDZtfN2#or}MLiT`Xi?BKTrq`kcuv z6GbP1(_@*J%S=DM!U1C73RW3r2@=RP4kZ-Y!;-we<~+QKyHk4QTXp09Xxr!JwokRI zhaN|U&EM8M_oV_?_Z%uN*Vi^C*o>HiQ|P0jMb<7_SsvM;(ZTl}ZX-m5>?$F91w{jS2f-YwZjz_t@<-&LUaL$p;PFWiJWIKw~1?RZo^D zrS=*RJ*owmFtXovy-@uWqVZS}${?0^LRr4_o!tH8X84Vl%=@<%ryyZRUZ#~h6Lfnu zpn@!m74m&$N_qDlcDPJ@D8Xom7g?P>G&5%ezOH3Q`&Fu86D&8v5%fYkF z0L$TcctN+7t*vPOvz-*{afy%Gk__tQ2xfo;(ka%%wY6GnPJACLNSS$+clxbJPYkS& zm1O#zJq1UxFdGuk^g1k%UlQw|-uv?0e16?oshkSkzOQ<%^o%6`bZWfVp-V*kYtn^G z2uV{BrB}lH)*ULujCirtb5?2mkkF$vUhVH%Z?HAfvMQQ;4;lyPYx}wT9O#{g2k&i{ zMW{bYx4n_>^rmoM(xrt2M^Tx7l<{_YgqpA)a`1oC)}nIG;vXV`VAp`19diA@704KO zi1yk)V8|dnn?K zh{KRy#TuegN+ZkPmaC|*leg5-uJ(0aBArsqox$p<>VVV6v)5dNIkEi`9oOf}-rkzg zz~#oU8Efb}Bbz91$Vl3&e`d*5`fBdUBP$Xz#Ochb<%T4jGyG+kg(VZhwmm{G9E-7z z^>{>Pq;`oZqQh4hTsYW~L;w7AYQm~}3YX!{CGjH)TlI36_`4i~Qr`x|`@T#3Z{4ER{~&AI|0z7izp-Z5k!Zzhmy7O1LT0B~cii~YF=M2ED<`)$USYAyVn`}z zjj!E-3vufxaThjTFn8gDEKDJ&O-pR&YeI*zK#7GKGx+)e_6QZ)itWBj_?$F$`NQvuiYT9H_R^UhcdiS^Go-nt6e06TL!P#OPh|BEUAvr~S% z5pU{Lnlb6{$^(i*LmG)2Is%lZlZ7(P;vwJC+S|i!wp_a^7GQitCxhQQEFckMP|*`6 z`ohWIT~t=a5595z9kRvP%{dWBbiR>yEE$n$M2C0>rwq&PW`zUqAZFWk&(X z7>%pT@RD^*f46fIw|XP4Skh+Mi-z&QC87S_7?deOf5PM3YJPe3{xcuoh=aSkiv%w( z48dcC@`Ur)z@2%$tz3BddH8q%Nf~b!D;r0MJH0i;9tx9U*r;t}poiK@F&GJJ@@cv# zKpdc|zHSgbUoCwbUq>5pTLu|vdP#2yZ)X>0h`SZNx3d!rA>l2>a86tT%%i}(4D{zH z?v7Fn=e}zCTberb3UD_Fy)cgmw++9L0KKR<55EYXxPTBB${#Pv%O}dqFUZZuk8;2Z ziqrp03?Mf*TRRC|MdhEoz>*Zh&rW%HdGUA&^1$8fdHKb~#d-Mzcm)KwK@4t$56s=l zn;V8;{6#?#g0OLey0}B(FnSb4D{HuiyA%T`>5ncryZk2mkGlGWXlwHu&&9*d>72Q( z4KKtA;tYYgBS5|Kqq_f#sHWy`^nWDl?ED)W;jZKfO7uTHFoeF33xrn}f`EIt*+7&$ zL5_^Sm?7MCA%CgnADR7Ya}fP^W0zn1Bmd0*clEj1pg6inD7Zna+~IEeaJbX2r~E(c zviz+LMA6C}BE^8(cH!m|=N91CM{Tnwk=7Foe4m%m$*SD8&Hwj|U31l@JrL z7UzcuiEvw4+lq2q@e5gVTZ`NAaogHJgoUjHg~Wvgg@3hIgxh$aOaRsX$7ryH+kiBF z>rTQ>P(aWc0@AP*5)xU{ZHlEI9S2#A>jPW%kba!#|94bgt-0f ziMUv~S%K3o#0??EVCM#Rrnho&ae~@dp&lQvC(QQe%>UaU=-uJ;f3yGpmXR&Q4f;3L z{hiHk8R-ANWca^)Qvca_{2%P>|Jsw{MXf161`+SSkFOtP#UI*KMm?bOxmx0%M$&(3 zi{Ruf0nRVKXPp0cbHfk*@c+{}24y+t9sk?n`U?cE*r@F9R|V-`OZuPt9}WD`z#k3# z(ZC-K{L#Swk2LU?vjGAF-*CM^R|0a4PWi8oMnN7vKulgu^8)j^gPQoKoBH?dyiVf0 zxB*^K^{6m80Kj)~gTmakJ=|S9+`%F$mJrZExa(Pi-=^mnYA|;Q1m@w4szBvYcLgUm zRGH|!tsc}Hg&_}hN3}s!|DXwR(*j?u^_6bvgPEHUUeeT{sfc2CpOa&Kr*stz@-L-c6UC^~hX#OrJ zxWN?uD!|N=sG^6w2>kZdLD?!4X8%ilgs2K#P$qe}yF1+32@bRW*@WO%4N8Q+ z!7u)T>q6}v{$7Fqs{)kmH$H0AV66R+;b&+m+OPZhh+;XfBmBt}yvYOtKkV(|ISdtE zYjTcx`FEN2oa)Yb8Tb4OUeL~4fuXmkg!gl}fZ%i7y>qB4z-wm*);NJzFjy8$Xg?t- zuj7701kT|m;Pn@d@Hqr!`9C4&G{1vo032fg@*fk%BC7fytC0UEb?3UkLDBv-4$)=7 zaS8r@>WvnaI}h>wp~owzB8C)r`5`^3c&?uF5pn^z0nQxu;O__Uy!LOGpTh_RL1q6| z{E75mA-@GfVZc0`P%|1TdKwFKdctfG|295t-1)&e)MExvM*Z7z!1BLY9w7hQLVqyT z{cV8fWh?~fcYq)aoo=9p5Zb@+!91@6!KC01`@Ep0_%~kI_HJ+wmwy3a{rE!tH9IQl zp{W1xjDa*RT6ws`RUj~k8_GQe0sxeE^d~aj4`dVsRiox?PY>SyZ}%Sq^a%gUVsWng ze}!lw?EiH^!Lo94*SE6&7c4>>h?A2(#M@mBp{lQWH&{@j81H|3K2pf9iTy8>(iXu)A9 zUE{jLUBIOn0r~sPgy#fKVgCh6V0}L2{TDbs=$LT$cl7g7$A)4A5P&~)na%V3j}b<4 zo=5vJ16?3LFQE(?@4OWXAGl-&UQkw={Nsl<4WMy>*-Nqj6&ObUC(Qm&nEjtH`#)j! zf5PnlgxUWIv;Pxj|0m4;Pni9mF#A7Y_J6|c|Ag893A6tbX8$M5{!f_wpD_DBVfKH* z?Ei$>tNq*A8`{W`b3gMn(47nb=HL(XM1xz)fE>6D3i&0l&z2XFxM?%)q`o&&e7 zz-`+98G{~B0RQL#0e}zS2gFc>fB3t9>?2`=2kD&i`rDx}vM306D2xIq!+)I6|4JCY z6N<>+D4>o(LH}L(pJtFBz=L5>hx}lmfhVc_I2h&=a1{igT>y_Y`77a~&Vxb2xPXrQ z1ki%&`*AAF?|N)BbWAK9j0?DUzYatp#sD`O(c-TA2mVI-pE86^^^WJ5{>F>?ggiU5 zWgs@jl4^Ze&Dg-Uors>}BhQDnA@}lRL8=qZTkL~K;AK6X1^LC*g~smF!0v@)u%gxJ z?&0;K#U{rvbiE$2_O~_gopCjcnEeIlqqQ!A=#Lv=03f|&ZW4L;xVfvP3<+dWx>{G{ z#}>3!AIm3!9I@l3ZteF-AWVeCYZ-iP))OkC12;*79A`R*cDBk%)~fx?sB27PrSEb8 zS1Tqi*Yh66!|lp^v9wK&D|=OZg7@%QR!UHYfbrDGu)O-AzEIe+rlXT+drtoZ-G)28 zFQqsB8eMOmM*&F^mR&k#8#JnYiH5mW7AvvY`V7U5aKy$p}*1}UIDEgplFE+Zb7 zIF@8Ge^8}}_s{d#$>|$huk3BAZ4rkNJ0++kcx+~89^WOA8$tr%l`+-VMviF^d#1Lr z(T#@N%J_aD9l*07xGKDkE$$@xR)*8)h_SgfMYKR@!-t;QcEntR@_0uxFb&e+mqF3+ z>EvHpTYu;m<#*CBmGKU2?v@$maLSlpJY?`)ztj<;9BU$6$)MZtw2<20H>w!6HT$Ob zqv$;k+s4LQTfTRTuMf+F)i+ogr7g4~Ol`eh1?X(Eka>Z0Fv!1FEEWbIHx6ulTM8;R zF~@wbene@+?u8fi(O{0}-qF3oA0e>PfMfZ8m_P%oQ&(C1H?wl3*8u%7YmV#teX zM<3Bn`=NaD*i@YZ?*mSyqNIq5p@O}didQhb3?8m0TJKK0508pz1|uM?0fnN4yigRO zVpy0%J!j#~DFXnjN!6^l&U8U&l#a)O^>$KkZOq0sJEQjZCU&c9j7>cf$-E~;^W7Hl$*;S5U25_9L2BnakOEFkh+oO@&^+<)_A7?< z97~Ey<4y1MR@BH|H_9MbY~;f41)dzP9JC-7_U7y5E!yLv-p6?OsDcF0C~p#ZnqE0c z1-@5#Ppb9E?g0CU@J-(rpWp8EK|4$z1fq>UB+#;uOp zJ_MBd6((Yu8`%N@Krat&*K$cCs`#V-fzN^IT_X?CdAQ0@@az;+Z=PsSX%K~f!CX;l z#L00x1id&h%&pT?_@l&b&g@5cfck?srw@dqM&Lnryu9x`%4wUTZKS0bpLs&Pz}qtw zUw~@-S8tsreMV)OEHq}LpDqU1Wmn;{5$ca#7JN~8-8Ham*6M3vBRm*?!$tBw!fQ!t zEPOZ+Y>m#7O1oMie5i;PFZ#yw1ukX8Ctu}6ui&!I2Y{XV(3^AtmAUNRCCd)Nq&y1G z887qGg{kg0Z5AlbBp$>Ekm8~?dV-OE4Ao2Mp=$({=3&0B5=tydQx8IkeyJ89ipW~bfCku@%m@3&{p4T z*In)rycBQS`mnyu-XW7IToix4yZaBk+XPp>$Yt*0V2Er;uF&hUvMeedyc;&Uw=mX@ zTc;W;d=_ii;FOHwtVFIbF?b1G4{);B>U5)H*wzWd=__)Mu)sd?MxbJzT1x;kr~H?byln^_`=PdyaCg z?@%26mk9%0zy!dh2@4G!0}BgW#?D)S1@KTa0Got@o{yM}{E~<$={0@gnTAJcbM{ zhz-OlneS0>pU!A?uQ_=_3U~5iCFdf2UViVL8Cn&IaEWcH@h|QI&xDobsHp2}GShb! z+3}fW^e$0e$3c*N^WYkk-kVF03IXoFb&G3KJ?heVr9%A@T|$azuoM2|#5g)R`GxC+Pz1p-m7jwfsvn}i6 z@WIK|eU*o$&WM>Cr9u4f=vl66$~H)vj~N!cstjOwbO);KD!DZ%={EQ-t>k*-diR~2 z1glT#+AFlWhQj4R(E{)<9$aoWugm*C+#%d^!Zc`m^88Mv)@FTZxv?#4GELO}wIC@rl8-2k2*)lv)5pRwAV>Cn%z3p zBdwhwLyAKx_RM{pvsZ*4%5EEdI}`%>L2pqHq=v97dA>-$Q6ms?5b4^kpnli3>-p=g ztcLVgDUN+sX!(2G_h#H2Hr50U=hS;&zQtzDQsZ~ba?u!qw#Z?N`9&4AEQmYdClnq$ zWBlHFq#v>U)?CA~>TYO)`lnVhLmox0=Z`ub>y>!|DFfjyFG)y9?>_ep%B$NB%9BjF zGkJg}%4nV1QXd+)Y;yW>v0AVs%UD=b#UYV`_=EqlYAaryvI>^627GUz{5ZJPGBBb- zn>&4fx5Q4M)rjatu>Da{*XQm8Ai*D?+TGc2|S0;h-I)yr3 zE2pKQ@RIGyrhT>i#JutSdJ^AtnK zr_X-y6L<`kXTtuwnTJ?6ow<(gVnnC+D_0K~q={n9y!83Vu=7}Hf#BIE(@NOYrwk8f zrWs4$sn`dGb)j8-lKvF4;{ZJ10-Td?lYpmAVqCz$#v{hX#li+>!< z-hNP0#;jobF!jUi`3#PMh9-lC1fH}7(sMF+7;!wze!7Ak1(`UA_0OpM+b=32QiMGwaU2HvS@Yk(VF93APxdmT@ZmOO|GtWH{(E;z0IHx;zd0EVvRnITjZ8eFL5k%dV?+v_^QT0fV(OjLC zN5cUj7i+3oY1}&A=|jr+Nxs|Uc3OAui#VGL6b1%NG(qB?mZu#D(o5rqdo$MlS3+Ld zxia44-hddiUa-0ly`}f2M0mO4+Y9BVDI8)wT}dg$$JgVDYVk>kC~CV!wF(Z+P9~3hAXe^R+3A_$mU4yW^_kuG50EsU+?d8?IvEcvRV~;^(^Rs zB6$eoi=To0gI9Q)(WYuwDBZNi4W_F*3R(rQSgr0DuHf#k(X{7>rioHt-i$O8zHDDq zA)DJxm^Nxkr~Zq=`$^6w3QB^Y0XCCXEpFMF zfoBpJB^7oI0%-G8UgMD62to?-xMkstR69Mke&(vGg+xs^Jd-HSZp|_!ugRJt(VB=? z=~mAt@e7S^=`HijFj}eWN4-E?@X}}3tO|}Z)@T*RQ@bX+!=@9CS=brBkq{zEBxu4? zvU^Oq?d?^JJ9Bs1ys$8a2eQnqAk4QSp4+(1;P60uA9kd>Kd;l^h#*ONQ#9=)IEXl^ zJ6ydae7N&nx#xBS=cDe^yV1u!PIwV1H)8kP-NJ63#XRqk`uvLDm-vYuchil))Xnh4 zBEY4PX2qGqJ^0eg*vpa@IN^bz(JIR7-?(DF&W!89SYN6M3C&}hXr2M3%Z0tw(v$or5~IYwCj~l&`xkI+|0jdsn+WjkXxh*4w{ZrJI5&9bnq=(+W`-|)PhCk+ZN;nLz^{r0aJrt{n@FJf%g0RGtJw~QW5l1y z)n!v~Tw$7SN@6u5X0I7)Kt7y$T%qjxT_0{X@Th#28e0kl` zc`wG4&MJ*~=k||t-|6b(w#D=m%-2(9s$gH1sr~Z4U8hawLvKcq%p-t2#3tfOav{h4 zJb+!8DrDb6vhKvN)vT>pwLl+>$2>Tz>!Vd+g28Ci$0t|96mJoVkS+R)J+0`A{&XYt_QZPBM_tP+GdJW(1z;?%io5S9%;t)_zGvF3Db?OL>pKx_&@@y_ zS64l-JllU#E%5r@;%j}Owe-T}FR8vuLE@TAYg_eOI9<%4XQAW?(VunY-o8}NW@mbw z_09CSVwJpTQWgFRzCOa85|xoTh9j>8pa&A(7WUFKPBiSf^d#<;Fs@&b8IKr+ONy02 z&ZNo|?bq^(uFS3IcgeS?)pF}j6yiQD$YO@z4yNB<=(3rjlfevByjnD#Mw ziTw4^H$)Gn+VZ+KAB!%;?vEJd?#vkNJr*^pDO#%tweVzA84^F3(492Hu+33Uf1|7I zUgdIdhhtxzx_-;SZX<|7%*YInjq1hdXKcJnxtLe(OwXID7F|f)dSA^Sp&UD{kWsKH zl73BH^^0(*urBFEezeEcjs=YiMoAAo2DPb(z5U!rYZkYr?GPb(c(M3pnZIFE+r|6X zd~VAmI!Yx2Irr|#6voVJvd8w6c?*YAa)S;;FKfGA$41GRwyPmwk6j%+oSE`duc|fk zr_g67i{aw;PvMm{Is1f~Ys^%Sezh2>yRk*2_^w4dH48&bL;dDpJ{}DP+e+%&Z6;Gy zUx6N;(3nq5quZJccAaUYx2(8rREM+3(}HlXi?t2B{~%w`u(+{(xvNXzY`x7(#a*V` zUPg-OxYkVMT1uf(Hw>q!fS@{v@hbnL0yJ5U*QA{4VTN5;$eF~@Btnm1`t0CwazjCyg|o$6G%k^1?bMc(ii*OfFF9uSlSw?xR_hulVhBiFR8C z6KmJ3nT7w|m~(o8-zcs+DUc&Zt$G!G>Olwd-3xJN`3n=pJ$4b<0oe%4frR6V>h6vA z<0~oHm)hc=-7zKzN|Apm-`gu6k@EK8%d36zZtfjr!JIz*gS2Rs4AH&!gBo|paTecB zr&|vlJLyBs6_Qpf?CNZG{bO(jEO=SR+WYCRTKB0XWU67!H9KGV#D#tlKa4o4e_3@j zgYM1?@Lbrn%N1{@?2{~;6*)7m3e#V4$5+cLacshR5*1_pMV~a3zrus=PFWmxIf4!U z_8e6q5R^rE_2X z!b4z7<;l;lo1Jgk-ipbR^HpS7@ZEH_IB7IN^Xn?@QW)ZDf-r)OB$ z-9>sQTyZg}m)l=m(!}``^F1@mBH8)7YLzA7d_aNdLBU_y{Pxu*fiL=kt!|h5gHoWv z>b)uYIjjykU#`~oA;$Gf2%YcJzEa`)n0R}Vbom3;#obs9yNQi5Gr1xl1-u-C(%^o)b6d93nY2aW017vJ-Z znmVJGsT1>MFqDg<-Ds5>QrelDXg~I{BjP2X4%;9+2oGJ)dd*57I+kpYMJwl(UQi(y z8pgwQa?47VeJiT?NV1}?dJ-2rDR=-p4AZ#$;{>aFI;Ff4W1?Ex@im-x4jb3X=^t(B zsE8(RvKA9MN;kA_*^SFXkgEMA(F;Q0HlP4w8Z__^$65&G;&yzUHXj0$^* z@7%6%^bMt++P;P0-PoDMg$0xAeNPu}YVi!YhkrF<+~K#l*^n`0`^n;2G2u%C9vcFg zPPx+D=AC6~!=j=xT#U@I)U+}E?S*$tn zwQVNwl$Ac=Q0sS-E{cBI&oPCDFgMe|J#b?vK$kQV0Jmi z;>CWMGV@9?eTPC5oUX#8qrM4u{uO%jmF_hvQ@vt;MF}_<7q{BFJO^5yedmx(ZFj1^e-mu1}cCcl_@bhmC(&^U_B$W5(Y=%}0XVlfBfnCaoi z$;O9n+5?TJCYCkmyNWIE6$;l{`3m~8www*ex)|ZaK1I76mo-o?^EF^h!w-h7}Y*=outih!QA}p3% zqq}Y&44d7G(9I_lsnMf$9QKjRH7GM)>(phKx7u)WyE3PDk?ZV*uH2T%!uac}pJS_e zBPtpdb@U3Wqq{5gOYXvpb%0rXirz%*6?#R#S*U2$c_3C2Lm&e)p ztfk=BO4N)>1x9vF8(b8)p^(ca#PXCOn8AnWudc7F7jUOTmc-<0;)93?tlGpJ<@0DU z3He58(xBx1rW}Y!+{+EYV<90hZbBA5u8sR&Tjrp|)1w`r8N7hto4W;T7S*P=p7Dw%0y|D)- z%FYdJ=%nV@oN;RE{Yir!pTL{Cm8`+RtGngF=<<0w(T3%jo$>Op&`7qhh8q6R(2UA4 z%Mfn%qOc)LHBsqc(~1+R2J*tTz1QU&{*Q%tVvS%5ZmFUPR7mBF`dcJ#}@P`$Pq)6DOxv+G%DkYf#Np6_vooUDGX7h zXtt6bkaPC8`9lMl{iQW8<)r1NLApsT zAAHfJkfrjDzNjIUp-eC4_zc%rg`zjpMm^V-XL4V&+V%EKN19l1aFm7lvDZapL&w9U zNJv{$Fri2C_d7VKb#qCad0`>J5qtCFRlF&O1$bu9X;?vn`%8HuPEBWlfeMeX~D)T+ZP4Nh=62FSaQcmkdzDOX}(y>Ogf3NpY4hb+w)Fzy& zS!hS=&5@0+hsq4JXUf!BY%{#;e(21%h~>)u+`t{WZb8BQhHqyVi5D^v0<3;V8rLB{m6Zmu}_ z^tIwP>H~ul{WV4sRpGKQ{r;zFtS?KJTwjUVjo^t>VD*xPghpo;uBC*VUeB9soF4)u z+LLl`&|<8W+%^xy1NPnxU3u)Oa|@@+ zRHh=S6nFhA(mP77q!%yLtWI7oD}jo~w9e8+zYnzVU}t74V&97VjCTEEnMWr6Kw7MB zKiMh5t1Y+62$%cBdZ?N=QJfTuFPR-mGmx_yp)F~ZI>!H^qmC58PRPWGdmU=-KPLa# zwd3-HTOZ^$_R5V#ue~{+iSYckP7EjL^i|g% z!U%XgR^?KLG?&EtB07$&C7coN6he)$nFz@w_zN$UB6Ip>Pu3m^oo-3}RUQ)cIrI=6 zQYcsMhWbo;$t`k)wh?j-=s>6D9sRV`sNBf1F^%{{D|-fkEi)|QPlG{Pa?j(v*SJ=l z14gzeR$aQ8jV{qL5YCw`s#fTa>*;=W=f)$~)sK0{t|R_{@U34d|I4>aY>&hID_RSP zXL~u@6<=_|+BE(5pB*mT+pA?4J{aX)NIV`~O&^+aXnf7RP$GgWV&%3!T- zKt{Tfi~jIRe~r5}%NxQx?N5CAR@6E!e};}5fnr~_nqld{&}%UMf(!|O zzHsexUpU4EJp7B;KQ<_T`NF~fT1oCEF&UqLmFxZDk{)bwdIm-zVO_+_2#QOB^7;nW zZVw_;-uF^6>A8QH*<=<`xNVbq?)pam;rcG^^J>?sg=CqR^c9Xkhi=Pt)U-^DL>0i4 z+8E6B403=M#jRV_Qu>#I>UVA-B1wsl#FW+RZFVj_sMpP|VIGLNDnS`PO69wBTRu=% zQ`ddnaWd~|?>^OmH1>h}T|8;F9Tm)qkbua_)v!EgVkE!{AGvBcdhJYWszjn-gqdAw z(M0D3+34Cg@|{Y>rVm^_B+g{-%JznBo@%OKW*&GF+DEO4WOs={k3MSC-n)K-*)^iY zGdnXUdqKS_{JS%6d`r8*ao!xGx)!^n%}y88eEDH$s~yktPf1p0Dk>y-h zyR&*4RK>zH-#vZ6)dFEpBM^8Ot?=T}qmAff-65|Mf=(Za`_YBB+DzROQX^7jaakIJ zibB7~nYZTNwo1OjcdTP*`r3R;xKNXgZ*Np?gw|Cgp?$tnme0h)UHr1Ktj|cN)53#^ zh!D}7QHNmoQKfJS&o$?K1H22gjQ7hRtxEcHH7vm$mMe6x=F4te=+v#LYY%mK;HFJN zW&Gr1nFi=@YV+sd&9L%&;~%Z?W%`Q$!+mW74-y539$u#F=(F3nlU!b{vy7Om{odTV z+&p!W?B38QE*`481~!kA5n&gV`@~+^TkDC&a+NI)7_gM-QZ8JyGTB?my!3MNQIoSv zB*leK-|)YXdt6mNA;O|qkA0x;&5hyC^YMiaj7RysNt~J(l-|UWAyVD&YwwcdjhYns z^ykprn*4O3Bfu5c^e#Qg_mL`Z&roA49ElII-^$ltF+hD-dYn1(*Iwh!%C;73mS?AlSP8B@g6lkpBBt>Ls- zcq$QHL?B+={H1POL9?MPxA*@c?5%_12)g#s#exQRcZZ;h`{M3S@ZbRg1b252E{nUn zy9IZ54ek<>yLsR5yH&q?|M*qS&YbD(nd+M9>Up})InOgB{W85lLt`U{)lCgG?1-yv zw!Wbz-DbvfL-ekd__-~(uX(4aMRR$>0#TNt7m!!XNKS3JY~ASk8XM?|34K9JV5^8U zaZ&33?I6HkP$Z-f={$rs*aqqa%SmOl561&hPdYP2U4=`-q69v8e>HsEXX{QO zp|HrH`ErtDYe?YbyxzrZQn$@QWEz{o;B!Nl=4evO8D$jrM1A*?;!pt1A`;;5AE`${ zrLlkT)a&o;zFY|5`6g0ZZLKoYI%;8QvR$qAGURKEG@jVQ+sz=^ z?Fwhv6kM;NspfLL`ITmm8D}C7Z}%IE+1@+$JIfa-lUP&Rx}1$ne49F{<`x_fXpEOMmvK4}DAM7wL|Y>XQ`X zCycVAnPZX*^sr9L)~gz`TRLbVDYN-bytLai84D5w8BpCYNZ0}jnUE?8jg@eUV+1of zVn>t46sS}f?Mp+6uw@O!{bx8r9l12LLzms|j3!2jJ#n=W76>)P_r_0@3Iig;=hGaQ zR(XP+uZ;$J%KLZ%3kLeUQ2$gYXF07N?0z-B=&El&I4b4pZ=L@*Yb#-;A4>>~m)*n9 zlpd-U=AdMsyo;=%4HyNw+mF>v)XA$`wqJbzgjtLPYNu%j(RAYI9UdGH7_uJ-AaTh8 zlepn2I}(?+JU&Wr^*bkn0pnWKiLRyV+mh=quUJ=DCZ4RD(y#Y)TeS0G==Xbeck)Qq zPORusrbsaeI|4u@yof+;%Xb?)EHoRslW?;SbdQ())v68O0V#5q* zYDp0*AXLeAtNG^ClB8b3&}~-dzLP-25OL+rDa$0gsPLz*Aj-0XNAjZDW!`Vl-ueNh zW_)7nQ{;^11-%F;HoQr*y0^T5NLjE=;E(-Q`X-aYQujIL&u)54BsDZvs=LeTPkoc! z4c;TBJcDLkNUyY`TU1f4)3)_!9r4uO1`U4!eT^i;?Q!Dbya(BEa`x0u6LWCB+$BG% zZ$*`89ofEp$3U1ELQq7vN=P%!*!}AJ)N(u`lnT)Z=z81Q&Lw`-e+POjQd0Vw{x_u3wi8pTA*&BFKi}0SmsyiLmL~Z%GN)# zXIda`>c0Rl!-z`eEm>_$vDCyTSdbIm_1pYIrt6g#N> z+V^~ry+>TTgpJaCV*M{2fVcJ!nMVS6V0sBzuo_y3Tevc--#{B~LLcL2#^5lW>&9_y z!nzXjyjrJW=PZjM8kZTFqehnA6d`1k+hX=rd3)+-L(}%EH-TjDL$arywdqIUtV17I*YzX6xQe$(r;of!rA8pWU-^5kJ{g1c3CcfsvVlKTKu7-^&P+hDIQxt<85l zOYzbls%(+cm*LzpA|w*8QrYl8-Z{wF>OUA1?*lFp>EV-Na zEppX;yKprK3m5;Qc!*7Jsa4SDFwzn$O&4E{`GrkhRlqY=w%P{85w`~mpP_2cm*X=! zNhnL6#Ybba-2ZLqMo6K!L^JiIR#$y1v>+aw%;4>eZD~Cc61qNllLA9V7IuFo5(IJ0 za1apY87G%$Ca;OyV%K*4hq;oC2Y%lP+J2?fl_s+{y{+ zku>d-#q!!kb_E6bm2-u)X3wss`h)g@$GIXDFzj4w{rX(w zaxYX5x*NqB=dS9F#`gHoXXRb1ec6=oT)YEy{>*h*JGm)A?ga1=fHvHmng4N$_M+w5 zI?@G|xuL@-_mhI}i0F47s^Nln^i#X-IQm{cR?jbOZLp8OJ#DuxV2UhEQ&5q`J_}TC zU=!J9bNzeS-aBUCt#^l#>F7T~T3L@$31a;nm-_Dwq|%-MKEN-v7&gLCAfwts32u3n zRN*f=%uWL_Q8qa98wJn>MC6iol2Dzdo38M0m+3GT!kr5_bf{X!%z za20v%$@VQwRA}I6e9X!N!Bnx)8Q8?Z-blH!Z+N{if5~`;l`O_;1dFHX^gAsgqB)$!NF7#a@{S z`%cGy0Tgp}IIwyrpHFy)cMsLQQ>ieaRXQiz6acSCJG>+Wr&bXzDEilRswy#U=zZnb zl5*}nKcCmGw^lVbq_0@*6Ar#t(RsUT9?=EgYR%Tk#l}R(>OpJWmFvQ=jdK_Of8TL~ z&+j6b-#rgGM+fdd{R)U5Y5g+|k;7mB3tUTIk}yaML#X|RH=XcFZd;Y?VHCl%Z0HGW zwNC6YV21ZS*?diIzbxENZE;QaS)I_wGJld^W!sK$9;Z8k!FoU(752J#CLOg^R~c$c zKH8Mc8kxU<{|re@Eb?j#O`~L;BWG(P@|JHwm&q8#BsK4r3;+IECilHzySF~Be~R2| zJbM?Hv50q6A^4;O3^utTypm#CawT`3o2^Sw$1gE6%T-U1=PmuC=RflSdRe}nD&8X9 zbw}5L#p*{x88NEI*sJbv#&P4#pnyOY|6G5DhI}waIC%v$YSpSDSA%9_3Mu->4kNOI zi#LX|7TbZ}**z9QS{LsBrb{K|n>A>+Ztuwty7c0VMB-|^0OEBKlKLi$?a} zuwGQ<>k?$W(vX?d|9M)OoDUr?3gfSyX~y$j0hvDscjf)W)^-0e&Hd=}a{g?1 z;-Lk}=L-f(R+0))C%3QGW2~+^(fLwkjC9=P?>rd`*G5DO6H5f z3r3ov)$nkP2FLI&=3UGJDZ4!=SZ_?~zOieGt?%4oHrR;BLu#5BdzXA&w~PVBRDElq zx~t{Mfl}Zp0%ST%#%k7ZcE%9Xl2Lsxl*)>k;8utf)UjHL9xLr!{vyL|dGds3zM`qR zl44lqb^-W!zIb`O(|!xXGxeE6?tDRY?bo%CX9S11#o_Fh4MX zvxrat*|)J`CTcJOs^vD&vgUZvInw;^V5Q$Kq zw-f12mB8lhp(56ccjA3yHmypbn41{=RX13EZ;v@ZT7!~XY)`_D+{e62(>N32zH8AN z-xu8cvR4(H;R^B@T*4idxBpZS*u`5i$M8*u-;;l*pZ@HV`7c&*RAoE#FV@Ht@V*iS zaL;2NRW46}&a51jF#(4H?o8mQ=a&WpV;#$(g&s3eQ#psGb-;*~n+a+1y2;G9QzP!c zh`G|(=4^Kct~R^wi5yHQ@(9v;s{6+ zbJudMw%ZQ(u9Ri0a_b}P5+5cx7sU-F{>EPVgQf*{7P>H35Z&bTT;)s{G?!!DuZ`Z% ziR3^JCN#89eYSor@&qjrI2s(ppQL=ic!2v!{x+Q$-;~ZRDxYZWEdcze=fko}tt32B znn*29H|g+|Yb8l(WD9d61V{W8OiQ78u;8N|dJ>&Bn;CvUf3@-w#X{zI5RVvI^-ZPS zA;a|7kk_T$q;7mh*+v<9f(`eN1HwFrWJ`#(fo(=&lGy8 zm)e;=xMz!m-c94jO|gFg0%Dp&KO+5(_a)D#Utq5;t9RiyxxY@~4z9*J@nEEmXi60f zV8@UooB=-6+@xrikdBfSi?kqa61}s(x30d~{&Iu}!~F&L;DFlmDZ#E)_sSy?$&)cv zDGpvqpOXwNy%EB2BkNJNfOPiMriki{L<994;k%LUZU%Zj6oX_Bk!Axz6|PCh?WrPQ z9pGE^=#+t>)$r*;>AqCpzIl#@|U!Zld>OOkmWdj^sBf%0k^z$Yt+FctUU^6m0pBI|50Ql#4TMiQ-DH0**RP_0^9TQRH#I) zsBSV(QaMf=_S1`gGiCu&5@x2#EU8aqrUNH;k7p*fzrNSLm2AM)tSs5YwA2~fi?Cmy zWyj#xTTRELVS$zu$hk`GS5I49!$n* z5eo&nH5LZnj~ke0PW{YRjYP!t`Z5|h)tGqSiHTVsrjFO^!3F(O^O&s1p1K`k0{61!qnai>5Y-qE)n^P$Ve&EJ!oJvLQ zL8wmhX`YZ8y+IyfE?*zjXkl@uwt_){%0Q)u#@@s+*B4ffDbJM7rws2l4)dDyglNAS zqGuvbw$G0#(xh^T9+zuZQ(LObWcCXTxA#Z&Z(p%;Of$xT?cU-e>l$s4JdZJi^-}KR zS|(>W5h?vc*k37T3{R77kn^aLbEC zq~CO@QE%XTC%wym)bjwbLks(JcA8(Ctvb@18$Do2iR7tRPX{%W+%$)0XKk={=gLx? zWU+^fg_-MAtA?`2wq3(=R7|-JqsWPX?mZ^dJFWF6Wkc*Pds%Gft#d_cV|R2$CZTBw zIELJ6j;ru0$_e4IKw6wRcLBZxw_`uvjYxkU>WyI6O z4G=jpc5afm!7cS$iaOjrDQ59e(nnzwJx6GdMW?ppQQ^5?9Rh3b-iwm`9r8*1@n>&V zu~Girk~X5PslVOvh|7A*zRG_mq`Ee6C}7|Aj2lDzUb0-2RDw88Pk%UzcY&0IioPA5 zimuMUi)jvb~?K2EFxcOL? zZd8~7O74QuEno{A9TBM~Wzo6z0K0i(>xNkb#Oj?AfD$16p-mwijY=lf0H} zo?V*xeP|~jQ!)QV)uxK4o8LmVuA}ZM#k}9A-kC<6)gF6|rv7HcHH1WdGstS9><~|9 zvpqMLU_Y}7e{8ou-by`_G$n+&{%R5HQt9S|Y2Sxn)`j0bzqu2e)Y@(z73l;l6B)Hq zvdF+VuW2KxnK6jtA*17Zf9bq8N-jwqrxFgDU9Z7fan89QY?0CVQ?8cnPTkU;95Pli zUlfVP=zkj+vjFOy8_!}Bb{W!r%{qzC!k-`{x+NY*GH4r2V-_g?LSe`2E_N_-oA~3d z=gsUzR{mxCy>|V)6^-K>xw3Qhi~Z6hCyc*rK2(Xa_sS1~5S46#$V1hU%o32B*#z6E zK~BchX#$Njb?Z&I(SWyjk-c(N%v8Z^^CoUK6<>iY*KVa;<@M~UVthV+V>wL5ip&kZ z4JTD!vlx^JQ{nQo@AA37o8poEk1s+={G6kfq=(3n+@1RCA#XT{F@c^H4Igu6W(%#m zEJn4Qy;X$;a|)n7w!rkU;6>6Bd35P})Oz%UpXA5=E?U{PprxS;0x~$z#AWx*9^CQ< zGlc~3Fo=}UrLB84(|s2@Ys@blDFtrxW_CqzlNP!bsbRae5UQb}sP?L;kp=V26R{13 z@k}Y@Yd?BCjVNg(=-U)POBBpc#?0>|R>MyRJzx46)Qu23L6(<)Ui;N6y-d;&-PD)+ zR@zD0bHOHQt+p{;s4}wmdvmi^j0S^qeAmwdp5D{$y}O5J!Pka4)t;BJC}! zqn9eq`fnGz-6s`{OX|L6h+QE2$4q&o*tQ6kFxh*LSDC!lM6#s<#2`w5r)XZjY-l-S zTMADeVbwel0?gDHWgEM*?FJ#ZlkvMEk6d+ew*gtr{CoOf(@Imq9Ar*00;`}#r%;k~ zdpICnDA-^b*m}x;F$p3=mtW7vB4zxXyCC8*5*c;;fVSm0DAOAw6*# z&PNIkAAu44kG?G@Z90X1+`WI!x4(E=Z98Kco`!GcDLF9`7WH8+>zjf+-Eq`3MH7r{ z*mXm}U~;aXV~-os(6-vlg>Kv+`Mtc~%LVP3pClZpv18Z$5jy0VJQa&w^?9HLaqJsU z;S~6uj(eH}MT_N%6+K2-m|Z-XW>rh{rBT+o@qVHZ*R3RlW|$2=e0^T{3#jh*oYj&q zr+KQyUDh{RzpxK1mI>fj&l^BXs+*$cP8G-WFMjXirWRc;AT1p>dA_&}+pAF1Y;yV- zot}E003ART;qX#UK_^oKjWZMC9h_li^=QsFd*W&)1DpG1q1af~XW~mNb>Of(8nN~s zFAUMoC5`*BvK9f`Eam3hY*R7&%Pv6qlv!<^1*hEw<1@e1*M_BR`cAyjZUriPe;{Hn z5tJR)Pe3^DdSph4vs;^NG0^DOLb)DsFllG?&z4*)V>!iD9*RBl=?qLW-d#RY3F>BP zsNo1+M<(!D+N0E8fP$ZcP(sf<*?RD0z%E(~nM;m=OdFEtru?2v?l-R*WDCiMt%7ov z`Cy0Yx0cV$`LC!vwvVDp*_RX9?as`Ifng4O*on)(&XtP;d^#F$DecZ_-|Grn0?wjJ z1)ZfzILzhySkr2sDZ{C5B$-8KvI`?>sxl&HJAG_Z`0^O$lt@CbinOuaIVEV6sFO4iEP;zVezDs(?q7LyIZ8< zh}qy2n${Rz&LioG5A@<@RQh1dgNtt%Lt8doLQf9e+7kD~4vX!J(D`Q;8u$mX6_wQE zK2o?o$CPFLlppVmkMc~x`!?;Y99{M6EcCBNrcD~*tDo981aR)qfEK-4&5;UUibgC} z7FyAXfMpsoHRa6y;jv{xlmuoz(-v0NBeoKLA7uX*$p^)A} zS{-BxGKdEWFq46aJXv>~ZgZJye_5co_ap8g~~B zpVGYOeFk0L6W3$EJbvOE6tlL#fc}eFRj))fe-Uw+!Ay;AO5=FX;bqS3G8m)cepicj z@qA<3d1H-69Q7+@7p_KhF1U5;LI?${ewXLjAs3(GNzPammeM{0+wP3{_9avFb{7W+ zK^8qYL?t-HRUA_hK~sP+OI2+=svOf5lQlB!P$S<>Ymz8)?PvmNv(DNlqpIQL^PmlT zSe9K(Mm&Qp<*E~|H{!>MAbtV0-ErHM11WEnH5X5{y>{?fRPle8*zQuPIG55G%d!?K z%abs0mq+5JPwL*^(03KHGjiF)wl(zPHua*5D`h#tYxF%ZCK&Hh5WCb3*&>F}B{9@B zsKr3ylBeQ|UXPpJ)nO;Ro7HoF5axuqKh+wWq5^lYlp1#mV)$o^wFzD8TU%EnS5;jM zG=aX9<^^52+`j(fi9e`9_(lja6`}PL>yYy1F0c9;KqnWUV!*4WMu%dhoGOKZ)m3Ut zdk2BB3|H(n_WVJMIV+%F8VdMDn7JlSeeyrky`bWY&Npj{1!a|94z??L$5ZE-VN~Zh zF?7^vGi=$3DmuUA>~`)NYAa%3VbhVcx4J}nX4K#qDIr95JAtm&|J#k+I^>_JWbPNb zC-JUn{sJJ;=tkH^)^_HUTu-1)mxQLzk)b`gFgHo4uYH;t+D00xFTLfK zGZQ(9^z9N}PmZ%2t>re}?n93i#Dy0qN`~#+9V}YJ?}Uk~D+T;(?lNvIq=dWUZd2_c zLv1(-i;&r3ks-7EOg$dxE&DgKilNKovO$p~9f{-E2r&(b?8rxrpFgTx|Nl>e>Y8R- znQ!d^FUg$2Z(cv)jV)@Sv?miyx#TM&b`zbE+dzgrA42kUax>{PHm4U`3AZ4ZGBPHC z^;4#$F^uz?_56pTX5{qRc<8pzP)2y~I%9BIl~9y2Ie&~ph^i3gwYNb#^WTL#%c{xM z#roaJL6-ELrv6Hoq3c7inqoh@NJ!rPXS7?k_Jg%1&G@le_y;z(#Nfg$^I9(MG~FH2 z8W+KZaE8bL8so?V{oDy7*tgT zVULeyT=$#}?R4Y_gk(qxOPPjHcq&uck~}E4x0wF=nPCbiLqsd?EoFgBD`UQN|FuIM z1QM*6pWkj?`JxzukevVmCaU-Mw{MpwB)MvW*eP|dvU7M*oW^7tQ{BOSDBMxj6}R^NF_qAj8;r@N@~fey*=-)^Baur zym^hkEa$7{z(Fzm)ax-BsCoP5;iA?H?N0}MYTVpKlcDPFl%2_J_D~!8^WJaoBUdQ2}q=2xd}I*oxoqhoT3~IY9Lo1%Nd|uw#z} z#`8@fd3h;y+WDxc=Bx%!ug9l(JNM@Fdpokyktpk2sXoXm&i{EzUpwf&Cz%#1fHH!~ zq|aVJjsS{SURj>}v*-u$CNZExgcl$VBnbJ}|4@QKVNd|r#n4qv$l07k0}`(D`;6;m zcmJ0pMT7$K?Me{KU0{?58TGm^`b3GiOMohc$(9f-bKyowHAEfrmPmUpqt1$`$D$h@ zCYM0JDoFE%nr_mF)FH<37%$$bYj}ChBsPdQ(Zj0l;!jBfN?`&zvIhO-agZRv&Ln{t zq71HhX-uEmbjvfa+#gvLpx21+j}u(?BNj0Lb#{#{VC3f^^oiLjKQ zlTMtNprY{Xjm5ITXAkNGLY@o;U&wW}oPe%knzH}65V>)ngx}C^@G@E`RPU+CtDsjD5HQ=Pj#Y8q^ zRM5mSAyPK4&_G=Ci7&|@+6I|bf#m=BmS~%ThgmyniRg+QUV-Ms?8JW8e)AR+4hW>s@c!Qcd z7lraaN&P4@XYo-u1z~`bVi?B=CY83Z|6=6_JkiG+N*bsHhEFr7Y*pRcuk|)n%)y_{ zgwJy_f7gl#t2BOqHTObP?vOce@>kJkU5=2q%%9bN=AhRF@$7Twrc(4}@aeUEA(L^a z6`VuXw@8$q2TllZ&3N-IqP2x+W2nPI2}sf4Yi>G<|A^&6`~|MWXrJk0^hn$=5bU8a z2yX7)Sma8Mp|n%NDs}oMkvN7kc~}S6yd{1@K%=!A%Sax+#~m9epB`v0DVvAphy&0R zh1RqJG#O|UrQmdTqmQ+O;YyC;d3Wl2ic$9?#9|_&tP@!G=Xm3>JNkrYfWBOI7Y?dy^3zG2iXzK^dCPmtU#ba3P(`fb%F9oI0l16;LpKTDp&N?)5n zf($)~0yBYxDli`djtFz`mN&wPftv#n4oF4kYSm;Q$83tO^B1t0CRehjGmV86ozP$} z#YUT7h~^m1V?~-;aJMp(fe@;1F0y6X(%31GM8?Y&iwR|l=`DeFLZ2dP1dLcifl}gH zf%kub^FD+^gGD{TyNlXc8e1*BMH(E?J0fMwFO~Q?R?R{{hLG`-pMs5#kS_QNB^pPj zU@wc^gV-1(FN%Z}daz?!eo~i(knjPMPqp+>iHn4Wgd6=cIRr^ZyfQ}9{Q+IK(?+f! zJ2!%}byCK01)d1;?PQSLLLc`?+06YV&Z&CMk}RSR!Ups|Q4S*lHjQ*^`P_HHT|V() zH*bD~^=1gy3wS3+D6*lM&Z&XPQ_nk`l8C?V2r!0GC2re^QCHKr$rd<4_}!*e7!(k) z@K%q4#E3uz7D?+L4F%9ETjG_#Ied>g#iAa4`ZR%W73nILNpgV2+F)t0?eChE&gYfV zA^mUXDj0<0~-C?a2Kb1KQ9fZQ}^n&R1Y&CgYhfMd}N(^YBuDH zJe5So!GJ+`H^#v3g-x0QWsRG;JvyUCu8QlQn=1P&cx7BuFrsqf_`2tc5LEIYnInDJ zFTT#_Gp|-5jy<-TK#T7)W29tYEwnZ4h!UUF7Bjo8Yp1)_3dx%_$0*n3jop=$;~q0H z&MVC%X*LN678RYOCnpy$u@1si_zUnSVD9~ zmbCb4w#hkX6%itSamsAp)Q|Fn5u{$xF_XGlVUDfFE%^Nf0Nq0;S9_YbV|_X(i7hXr zVzXYnhew&tCU}fWXihJM;1K(^*W;%|H%Ej&TzF%aRJ4sKss1j)j@Md}^>9OEn7-j+ zaAM~#Gb7{<{qYL!8oWoObhHXX##ePR;#4!@Wf|V>m~y4~0Q|#84hn3mF-kR=wZxw9 z=y@jglMAE@$^l7L5Tz0_j^Pu&JGn0 zxnWre`%8|f`DmRA@Nj{pi%0>IbAJKj+bE8zmL_lFXaIQeK+&0eHbiO1UAqf9m$2mN z+3;^vADv*s6I_>hU8I|T<0-p0D4)2fOVn8^g!7X_(^nQsVO7r}qcF+JO-2wjI#vJ= zbkjJ4=-mu0Sx^x^R1g;&(c74$Wzkafg3>HFU>{F{rg-+zi_q645JW*;Fa%-&3nFq9 zn36)y1DZkP`%h`mCy_EAz+1VLR<$NO{m@JNDzF@h%0mC!Y&3RSS3%v@3lq=AUI6UQ-W^uT-+ zJSy*GLy75m1j*;^iBrk!++(x*>{gr?=r+*9F{6Q$$LSqxjT+EVjHO^L(n7~Du$(DY z(i&Nh>Bre1C7b_BBHL!)~A<(@f1JsUR1K&iGPxYF1< z06M-5+-IWp&KCT+>UjRr4Q;|tXCyDa9S3uSKzxX{mcoQm-V`fq@||pnz9OGD8r;|l zE7xfw6N;LK&VXJ>4vh!j$yIWn$SCBH;%PDvhPuQAG{h{cZ_z24(m+xG)QVq;=>55S zsQKRmTK#<%8Nb~{Zt=w+y-}Ya`d;rWlH#PekBZ@?kO0S|)7($$kLA{hJ{OK9(VE3h#1aZoQS&pI_o6}N zox5skTG;DrPyA7aAV(Akw1obT2q}6ZgunOyzESIQI~`)nQk7&ZVuBoL{@FAYlN?}qKhJ+QI@t^YzO2%JY+WiX#hJ<3Ib0!j66N{ejCf92$mALS@9;eX2$ zaA|aHWB+SsvQc*<{A&ZGP9`07W(LRq*fi8B?0WxWLvDrlKi5L)B*L%#E2pB)rdqcE z9{scF{sP{~Wht;z|DF8bHwflu#DLHpLKubNVBjDe!~ZA}A^gA;|8kEYiAM?KY;~gf zeY4j$#{UKV|9G_^$v*SG98qba+5o5%`u0!TdEA^+U$Un8GT7e2=~PJqRf|YyfQ|I0 zeNvdWa5!S~DvX?U_tMNB7)xI41F(_pFzqa!td^3{@zr74@xoh!HzHX~YB15TuY7mM z-YPDtm_;yk+!Na+ODa=#D;cDB(EAgTW z^0<3CA{m5>;~Q|nI9OdnONr@Hw?ZA#OJ$>>>zYEBe)Lw_6Tm7R{1vpa&`nyVogp34 zQ=3mDaT7+6w-Ze0>P4E)k0GJ%1X2ua#ilz{y2Q2~YhTh@8#G^{^Ok;?FzYDu5f+@& znND>SO$+8C4fmAP-w^cV(s3V($SVDDX z6mxgFeMY4T_0s>YBFYO0M)h}N3v6F`jrWhI7b_YQ0<)P&n8#eKfBaP52XQ9D82nTV zRPnd?>OdYX`y_E3%w)PqG8^3GuTf(CRgh%Ov?0E=RIbUYQc=syx?%^C(X_sdtU6|u zU1P|*YR3Gr?lf@Am0&=C-}U2oD!y3YRT^rT7H2t0K6_o>3||$nZ_9AlRxG8|E^vkx zzZ~84a*EVSFu|q|E(E1cJjuZ3l)om5`ia->H}{Ei z%xv(qHUr(C{b3$nG-`g8H3{(Q7SY|-Gj}w%7494+=RB#ne7P)>_O?FiT#6AUZ4#;G zAG`BM#WV|Z*-awEQD?;3T{%cte?2r5v%0yctoGX~2_035A4vNE@bW>0*JaSDI*aIL z`sM4s;B)sUg_${u5>;5~dckbrb7ihiruL70^LO|7ofrBYYzq_{b3ewCUcOHrV(c#R z?XKKdzLgMEf$oiZ`FUbqh@)vk4pO5pT(WnBj_N%E7py%j=axdCk7cakoiy%e1RF_N zfNnvF&H@!BfEwP+OYKICyt=?^Q4mG}Br*@ga`EB4o@83Cwz(EczmAei@4}A^j#fM% zoC^TE19*#s1K@ZWO9wANa!m2z0eFE--zMmY0apAU$-0GSrYe_HGZT#lWIP@D&XEr6 zEME{`Ohc=KeMRYR<_O2*8@jssm`?B)>B}r;khuWpz=8M;l}~tgl~APQT^_Sl@${$b zo)}oCO8w{3@(IGy!9yRyWYp+T%;ZYGBK|VlP~Vim!ta#{mx+}<4cG=gpu7CM${Se@+gm>l3BskM+>YaHF%PYJ6IE7jra~V%`0Ci80 zTs_l7CNp|bQUDby#*CEwAuXBQQIy7jmp)>Ot-8aE1e>_`&yzLtBGOO8q=<=x)k9+T z{QO2F3W&~k=3Dj%Z1*1AyX6obsC#vjG-8kwEbSzcFgWD-(d)t6n7Xft*sxNt%{9rL zCDF*jdn*6ndnKH*hu`xbCqyiVgzodO4QIG+7)j#W4Cz|Z)-INFiB)W|e&v&pLexDn z1`Y(Y33^Ca&kNa>hpArgZaK{te;kq@Tvb*`@hCbW0RSayCYZy~*qAOdD6;**^bDsg zRyuGyy2Z*kkW$1kJQFkY_1=5s#R)-d@ofFJS_5i({!LlKyG|Tnlqf`M)X^(SzMtY% zU6Xiamp)Ka7DNx-M{gz|lP~GzSt@ctl|>y8e}4D!dhcyjK^h zN4pMpv_wAX@6&z7UnT7Maju5YUm42et!b8Elr+?IWW>}p2J|CIzZ)1DTC2)1?8mQO8rgZ9G9A=#fT8Hgy9{ zf6v`fsA_t-DK?KZ?_MCwv#954G2a!wL_7Cugafr*>LyAPiXFyq=GzqP0~U121o=eX z_1$JDPO>{iIW-D4jpkeBA@0r=?VdV!N2PWS?iGXeMqJO51)?LZ-|>@spv2mEdv@mV zHExi?GE)qw^ z5lPwkNsDVH4DQ*U)9EBtT!~WAy)a{N!?EfV{8ImL{%*O62EmC!rKkbyJ@L877vu-T zwqPCYC~4GU$Ei-XXJJs+E;GVx!+H2J_xsxd^&1?}8}p&C5vlck40}56$+Q1VOnBPW zFsDf!&3rK;usZ3@GddP#RmrudyqXU%Heg6WA zK8DyEe)04$R-rT}U4~;1{%8m7k02-!LHl)M)Gk8s$_KspYXr!f;R#&y?Tr_^pyt zX&4}z+ogXJ%O6TnwD;V%5=h$J>=*KWeBqZR>IkFF0M+DDS|?L*c2CZZBg==#6NdH~ z6=o5}yd{muh{L|HT7$94Pm2gaM2~Ld$t-hpJDSL2v5v~5e9J+-jt+~GP}&B{aJIrh zEbv#}w4@kw1W|4uIuc8K{rKH$?TVL>N8EU-_wG+TZQGoab98~UVs_t|@MTfPFcn|V zl909XV+U9dG0ta702~;40#*1XEmT-s7~a?5%R8&z-0{ENUaiVk%8*8ShJ3Mu8>R9E z=8SS`F$@rp^Y_)2$kd;^{~m64@~L@ED`eFIgRnV4UVeO)oV5TLk|L){No6B#InWb! zcWE1K2Fx@j{u~F18A8vglXfajUVT-Yd5du{IlV#6<0g z;{Gqjg+SbEbaJ)?2(9dH-;MEq*0ulT&;NtCW7w^hpAZwGX(!)(QCsaLZU{8Z@Y3M-u(O`8U;wQ51qdDS`nFF9|QzM*6fr+#my6114D5sLPkH? zUVmR@BMOh1rqHD$$|I%`w>GD&DIz#2{_=so8a(zvHllcmN!LFBHc2N_1tT3c3qVxK&m?B~8j?i}Udm&l=r@KU0U{)1 z;u(cXd&_)Tqqo*b?hQ6i;rC~FQO!iEtlrYpsspkwh z6lL_bjN52?tx>0i`1@GlSD%i#q9P>Vq{6{p03!{+7 z(&ArC2$xP?frWJPQ7ezWigwoujh|ocdX)#6kA^zdq?-IH1;g@lPTcc-5gzOJi(S%y z?97cFJq%k}Z7N<9d|g@e1V&m(TIr~>9P!MZAWUQOV3c%aOjvA;DK&9C^kB#J1@BZW zN_4UE&|eqYpiCu0CPWj_7<24(_~cZJ!e98u4y351t_n545V&;l2y6zaBciwM-p$;c zmpNp+_rCylw`gue(%AqZQRkYh*AjBQSjq;VTvyg>26Kd#)LyWX;uM;wZoJdyYY`)w z^RvHzSn8`+MIgebTJ1T(tZG06L|X zh_o`4tIWCQ~3+k!=4h-Wayt2rgmu7SQ?p%hujTxJ5_0+N$XSjUqojCLJcRA8HsglmTK8M&I*O&|tAIAvkj#pr@r&LPGFs_wK(q|X+Sfno?WzpL`RqV*gEyG)FnOPWFmAZk=D zgD=et2q0jb-F4I|+VBq`7ZlEj0UAg(T$`B4Rb^K+z$GuLH^i6%PE_z>(W3|@NIKef zx(L~sa6*o>6R&~_fneHAJztgS2u(b=;7OA5w3h(Q$z)9g*u+|R1|r@K7;i7CZ^tFo zQCB5$_+9}g7mg&S*!{k@%JPK@DVS&r!Sl+ts?q&ucQ7G8xSF;iOpsoZ7<@%A)$FJn z1DSB@NEWaf81Th_NbYePjE(B_WQr{kn4~F0;$sr{5F<39F@XSJKnP(Jj3bZBj+phn zSCn^Mo(K*CB1nyq8zNa6k5FAQ5=}uvSc#QiAi^a#xa8t6ZVIkt)vdua{{UoE(G({r zp8{Z+i+! zt|_ASryA(&GHP@?bkJ}%;UEIQECr8S<#|VS+2ZVZK=-^T5W+<(lbo;$TPy^V#N-CD zL1{=->4X^o3xiH_!o{>qZbqQR5lzTSR74i&OiP^l-z&;HuFo7!KPlh#&@q6T6T&3b+iNP_R8dUoq%AaHTQ|x~#eUIfJ@ndsaec_Ga zj5YFKRcK|hnIc1)iA$i1EEYEQzz8&i&?-56n=Lg99@tRdcHuDs$}T2PSdr-xq-`j% z;pH@F(WMB?patUsd;ky-cz_TV{{Ykf0RO}QDi8qy z00IF60|NvD0RaF20003I03k6DK~W$uVL)+_fuYgxV8QXR;s4qI2mt{A0Y4D`04Cai zDxd(WtN<#i=r-g%N6Z@##JSP$ts#s=JAnsr+34@S(k%iFs+GhMj-PXYAVr`_2KAHd z3^S0^MU~+S+pmQ!22vsJq>zJ&QfIURYe8L2z|0Yxky<=k&$zH1I+?7f4XCTmd{4{# z=fwQK&U{bH{N)nNTSSp6f=H^q(f0rXSy2sE1gZk&suUFf-==G6tPxWMn>n!%4sxn( z{{U6$*oPnyII*B;P|csnILbZmV`W29n+7on0$l7$M8ZXdhyoMQ)T`DMh~b(?BYEHI z5MLKNIQ6-qgw#brP-i$pl-0gB7DK%gofshqK z=D`cg>Ko2AKq1^tbAaJse<>_500unq3>Z8j&>;ZY3uu?Ra8L#0xu&ook~m<}yNE4i4a?Wj*4xDVtwas+Kf%x}>LgyWWs!&gckQs63b<{wLJkve5Vc+UYfz9CFcL@O6C zbO9)pA-5W093%!R0M?W{cE|~kj*Pf8VT-yb+_~v$K?8Mb{FT@h!kY5)N2e(J(+W zpcY>#aO8j$18j>1h-WKg8pa7j5GW0VWQj#UQ6ERObwGJk%M8yFr^>fQ`WyjV@QAU|NFfdk~ z>)ff$jdVyO2vA&AHcd&e(PLkHR8Z=niV7C6;t4oc8SMW6%8cR(>tRJ8p#eaM0)iv4 z>OaR83cNv82D{=*7CQTgUY39uO|2%31OOle4&5z`*ncvk^#-&z?jmPA{2o^T0;@1j zn~`wV*}6(xh!Lb{j6F-v1TsZZupId*j_}%QBwB2yH78BY708sLG&CO$oRv5_wYwp& zu{I?9@%Sj(`3aX`s=fLNa<&?tDgYZ)Gg48LkuM(W(sCrgCn36X;Ug&Ncb|a|j39l6EY=c51g4Xb zj4tHiX;npl90#~|pP<8v5QJeRiYR{-xT0OU4KC0Qems%#!C^>7YMb)ac=L&eSK5l% zumBsQiAFH7a53mm71M_zSa(ZIu27T9acFJCCDWMk$;rZjR0G2R$DpG_eu+}m+Kf&x z844f}=HLqzfZnl9DS@L`bk#u_`ff}7u`qieNqRUSM0j!RAyIK|z^Bd!y!?pqB6WcY zW}WF8;yaAHe^u9SzFx;bX9@@Nkw%RDNKM;>FUZzbr%EDDj5mXlJlr z#3RCEYH++D)TX0OQIgJ&8ToLcT<|dPx42@42bCd^iPEGll9Ath-q6NlxX2p@VkNg= z6!74uXkVCE>JI90Dnl&3uVVrp&iM`h0O=W1zBDi{Afjoc&L9B?=88`eZNZ&Y?}B?rE+YUC%~BfCNxxYLtJ@>afEZ&+vE zFTQ!wV2(`Q*%3e`2{Vo*BVDB55{_5_05lGBnGPk5Nh5l$WO^YSy+V>n-w#97@dD3o zhL9=~u#H}k7!_D!=V6G0k^*2Y=Sm?MjRAscuq8{_Ene799cK>=*}>T0{{XGZLbErf zy7&^~oDkRFAZB2F#Jbg(Q6rZuV5H2tmbH|bd4**iXqy17cC~x@R1iHuRP79gjCSc4 zpc#*etF+hx*f-qYF7`fp5i%|56chk)@LaA}z|mxw1m>AHXJ3G}6KzS9m{Kv(tyN37 z{^!Fuge@tdjdl(*`X~F(`(zKC03ZPnAZ*Zl`-xb@!&Gzv@Z-~qBpU^*k;9NaxW}=N z*p?hh1x&9i%Jp1hfQ__)o-Wi>Ln;ai@@zl@B(9VslY&(vX@C{cLLD9K0GI-XsIDWL zH?Zk1#7|@-e2)~sVht&%!k5aT`@EP@2haIf#<8G82x4}D+3SoZ(mx;v>f@+z zsn?%%2J>|csNJx`6~Hfko~Wn~2ODUbG2#l%OvT(PpJ&c@{uDkhVRWct8PrRT@^jcB zyjoRGgds$TKvZyWoRPh?Aw+-)?r%6xLM{!EiwTAO5+7(;c>zESJyC_V__=TeA>~89 z8d54OqqCvv38aE(n(8P-V~Wq~(5q1eG%q zA7;ZL9#A-|5;0@7V|Ny_Ku_|aMLvotnmqG>G-BOUVSGEHDGjq}JSI*c+FaW5#pyb! zvh=MP=x{dUNX@g#7HY1W4o)X39{{Mj0~W&V1gPvAaq5n+*U>uB1nDHK*aO}tMVn9s zSYg3`r=ZSIz=@L9uksCIphdt-^wCK6(Q}bv`x6DDpfbI^g*XJrA1P>tRn}Akj)WQr zk^m0s(;im`#biOU$OrXt{(wkq39qG!vAn8V*sq2Vn2e^1 z{`*-*OAFRyL@7#kM@hnPnIffwvhp}iItCkPI06KabfEXe@j_BX46_Z4*GG^zro|)( zDV97|S=y|)?VxwJ>7uNOfFh`DFfQaX#rgEQgWh=|7q5`;C|$kzSQF0zx#KjuY(K z0!;D2Y%+k(nm?=DAyIS813Td8JcJB*MpC?0=~aT@kXPwqto#q&evyBx@Abvf1qcJM z!tQS_(N=cj5(jJ+bF4A7gimZj#TeD7_?Y?=?ttswd%g+GQ1RZNUr51&1}9SV*#=4*NP%xcG_f|8 yk1LV1Jv3<1qI2kP$ i32 + public ResultCode GetUserCount(ServiceCtx context) + { + return _applicationServiceServer.GetUserCountImpl(context); + } + + [CommandCmif(1)] + // GetUserExistence(nn::account::Uid) -> bool + public ResultCode GetUserExistence(ServiceCtx context) + { + return _applicationServiceServer.GetUserExistenceImpl(context); + } + + [CommandCmif(2)] + // ListAllUsers() -> array + public ResultCode ListAllUsers(ServiceCtx context) + { + return _applicationServiceServer.ListAllUsers(context); + } + + [CommandCmif(3)] + // ListOpenUsers() -> array + public ResultCode ListOpenUsers(ServiceCtx context) + { + return _applicationServiceServer.ListOpenUsers(context); + } + + [CommandCmif(4)] + // GetLastOpenedUser() -> nn::account::Uid + public ResultCode GetLastOpenedUser(ServiceCtx context) + { + return _applicationServiceServer.GetLastOpenedUser(context); + } + + [CommandCmif(5)] + // GetProfile(nn::account::Uid) -> object + public ResultCode GetProfile(ServiceCtx context) + { + ResultCode resultCode = _applicationServiceServer.GetProfile(context, out IProfile iProfile); + + if (resultCode == ResultCode.Success) + { + MakeObject(context, iProfile); + } + + return resultCode; + } + + [CommandCmif(50)] + // IsUserRegistrationRequestPermitted(pid) -> bool + public ResultCode IsUserRegistrationRequestPermitted(ServiceCtx context) + { + // NOTE: pid is unused. + + return _applicationServiceServer.IsUserRegistrationRequestPermitted(context); + } + + [CommandCmif(51)] + // TrySelectUserWithoutInteraction(bool) -> nn::account::Uid + public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context) + { + return _applicationServiceServer.TrySelectUserWithoutInteraction(context); + } + + [CommandCmif(102)] + // GetBaasAccountManagerForSystemService(nn::account::Uid) -> object + public ResultCode GetBaasAccountManagerForSystemService(ServiceCtx context) + { + ResultCode resultCode = _applicationServiceServer.CheckUserId(context, out UserId userId); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + MakeObject(context, new IManagerForSystemService(userId)); + + // Doesn't occur in our case. + // return ResultCode.NullObject; + + return ResultCode.Success; + } + + [CommandCmif(140)] // 6.0.0+ + // ListQualifiedUsers() -> array + public ResultCode ListQualifiedUsers(ServiceCtx context) + { + return _applicationServiceServer.ListQualifiedUsers(context); + } + + [CommandCmif(205)] + // GetProfileEditor(nn::account::Uid) -> object + public ResultCode GetProfileEditor(ServiceCtx context) + { + UserId userId = context.RequestData.ReadStruct(); + + if (!context.Device.System.AccountManager.TryGetUser(userId, out UserProfile userProfile)) + { + Logger.Warning?.Print(LogClass.ServiceAcc, $"User 0x{userId} not found!"); + + return ResultCode.UserNotFound; + } + + MakeObject(context, new IProfileEditor(userProfile)); + + // Doesn't occur in our case. + // return ResultCode.NullObject; + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs new file mode 100644 index 00000000..98af1069 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs @@ -0,0 +1,200 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Account.Acc.AccountService; +using Ryujinx.HLE.HOS.Services.Arp; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + [Service("acc:u0", AccountServiceFlag.Application)] // Max Sessions: 4 + class IAccountServiceForApplication : IpcService + { + private readonly ApplicationServiceServer _applicationServiceServer; + + public IAccountServiceForApplication(ServiceCtx context, AccountServiceFlag serviceFlag) + { + _applicationServiceServer = new ApplicationServiceServer(serviceFlag); + } + + [CommandCmif(0)] + // GetUserCount() -> i32 + public ResultCode GetUserCount(ServiceCtx context) + { + return _applicationServiceServer.GetUserCountImpl(context); + } + + [CommandCmif(1)] + // GetUserExistence(nn::account::Uid) -> bool + public ResultCode GetUserExistence(ServiceCtx context) + { + return _applicationServiceServer.GetUserExistenceImpl(context); + } + + [CommandCmif(2)] + // ListAllUsers() -> array + public ResultCode ListAllUsers(ServiceCtx context) + { + return _applicationServiceServer.ListAllUsers(context); + } + + [CommandCmif(3)] + // ListOpenUsers() -> array + public ResultCode ListOpenUsers(ServiceCtx context) + { + return _applicationServiceServer.ListOpenUsers(context); + } + + [CommandCmif(4)] + // GetLastOpenedUser() -> nn::account::Uid + public ResultCode GetLastOpenedUser(ServiceCtx context) + { + return _applicationServiceServer.GetLastOpenedUser(context); + } + + [CommandCmif(5)] + // GetProfile(nn::account::Uid) -> object + public ResultCode GetProfile(ServiceCtx context) + { + ResultCode resultCode = _applicationServiceServer.GetProfile(context, out IProfile iProfile); + + if (resultCode == ResultCode.Success) + { + MakeObject(context, iProfile); + } + + return resultCode; + } + + [CommandCmif(50)] + // IsUserRegistrationRequestPermitted(pid) -> bool + public ResultCode IsUserRegistrationRequestPermitted(ServiceCtx context) + { + // NOTE: pid is unused. + return _applicationServiceServer.IsUserRegistrationRequestPermitted(context); + } + + [CommandCmif(51)] + // TrySelectUserWithoutInteraction(bool) -> nn::account::Uid + public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context) + { + return _applicationServiceServer.TrySelectUserWithoutInteraction(context); + } + + [CommandCmif(100)] + [CommandCmif(140)] // 6.0.0+ + [CommandCmif(160)] // 13.0.0+ + // InitializeApplicationInfo(u64 pid_placeholder, pid) + public ResultCode InitializeApplicationInfo(ServiceCtx context) + { + // NOTE: In call 100, account service use the pid_placeholder instead of the real pid, which is wrong, call 140 fix that. + + /* + + // TODO: Account actually calls nn::arp::detail::IReader::GetApplicationLaunchProperty() with the current PID and store the result (ApplicationLaunchProperty) internally. + // For now we can hardcode values, and fix it after GetApplicationLaunchProperty is implemented. + if (nn::arp::detail::IReader::GetApplicationLaunchProperty() == 0xCC9D) // ResultCode.InvalidProcessId + { + return ResultCode.InvalidArgument; + } + + */ + + // TODO: Determine where ApplicationLaunchProperty is used. + ApplicationLaunchProperty applicationLaunchProperty = ApplicationLaunchProperty.GetByPid(context); + + Logger.Stub?.PrintStub(LogClass.ServiceAcc, new { applicationLaunchProperty.TitleId }); + + return ResultCode.Success; + } + + [CommandCmif(101)] + // GetBaasAccountManagerForApplication(nn::account::Uid) -> object + public ResultCode GetBaasAccountManagerForApplication(ServiceCtx context) + { + ResultCode resultCode = _applicationServiceServer.CheckUserId(context, out UserId userId); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + MakeObject(context, new IManagerForApplication(userId)); + + // Doesn't occur in our case. + // return ResultCode.NullObject; + + return ResultCode.Success; + } + + [CommandCmif(103)] // 4.0.0+ + // CheckNetworkServiceAvailabilityAsync() -> object + public ResultCode CheckNetworkServiceAvailabilityAsync(ServiceCtx context) + { + ResultCode resultCode = _applicationServiceServer.CheckNetworkServiceAvailabilityAsync(context, out IAsyncContext asyncContext); + + if (resultCode == ResultCode.Success) + { + MakeObject(context, asyncContext); + } + + return resultCode; + } + + [CommandCmif(110)] + // StoreSaveDataThumbnail(nn::account::Uid, buffer) + public ResultCode StoreSaveDataThumbnail(ServiceCtx context) + { + return _applicationServiceServer.StoreSaveDataThumbnail(context); + } + + [CommandCmif(111)] + // ClearSaveDataThumbnail(nn::account::Uid) + public ResultCode ClearSaveDataThumbnail(ServiceCtx context) + { + return _applicationServiceServer.ClearSaveDataThumbnail(context); + } + + [CommandCmif(130)] // 5.0.0+ + // LoadOpenContext(nn::account::Uid) -> object + public ResultCode LoadOpenContext(ServiceCtx context) + { + ResultCode resultCode = _applicationServiceServer.CheckUserId(context, out UserId userId); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + MakeObject(context, new IManagerForApplication(userId)); + + return ResultCode.Success; + } + + [CommandCmif(60)] // 5.0.0-5.1.0 + [CommandCmif(131)] // 6.0.0+ + // ListOpenContextStoredUsers() -> array + public ResultCode ListOpenContextStoredUsers(ServiceCtx context) + { + return _applicationServiceServer.ListOpenContextStoredUsers(context); + } + + [CommandCmif(141)] // 6.0.0+ + // ListQualifiedUsers() -> array + public ResultCode ListQualifiedUsers(ServiceCtx context) + { + return _applicationServiceServer.ListQualifiedUsers(context); + } + + [CommandCmif(150)] // 6.0.0+ + // IsUserAccountSwitchLocked() -> bool + public ResultCode IsUserAccountSwitchLocked(ServiceCtx context) + { + // TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally. + // But since we use LibHac and we load one Application at a time, it's not necessary. + + context.ResponseData.Write((byte)context.Device.Processes.ActiveApplication.ApplicationControlProperties.UserAccountSwitchLock); + + Logger.Stub?.PrintStub(LogClass.ServiceAcc); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForSystemService.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForSystemService.cs new file mode 100644 index 00000000..682b5327 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForSystemService.cs @@ -0,0 +1,107 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Account.Acc.AccountService; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + [Service("acc:u1", AccountServiceFlag.SystemService)] // Max Sessions: 16 + class IAccountServiceForSystemService : IpcService + { + private readonly ApplicationServiceServer _applicationServiceServer; + + public IAccountServiceForSystemService(ServiceCtx context, AccountServiceFlag serviceFlag) + { + _applicationServiceServer = new ApplicationServiceServer(serviceFlag); + } + + [CommandCmif(0)] + // GetUserCount() -> i32 + public ResultCode GetUserCount(ServiceCtx context) + { + return _applicationServiceServer.GetUserCountImpl(context); + } + + [CommandCmif(1)] + // GetUserExistence(nn::account::Uid) -> bool + public ResultCode GetUserExistence(ServiceCtx context) + { + return _applicationServiceServer.GetUserExistenceImpl(context); + } + + [CommandCmif(2)] + // ListAllUsers() -> array + public ResultCode ListAllUsers(ServiceCtx context) + { + return _applicationServiceServer.ListAllUsers(context); + } + + [CommandCmif(3)] + // ListOpenUsers() -> array + public ResultCode ListOpenUsers(ServiceCtx context) + { + return _applicationServiceServer.ListOpenUsers(context); + } + + [CommandCmif(4)] + // GetLastOpenedUser() -> nn::account::Uid + public ResultCode GetLastOpenedUser(ServiceCtx context) + { + return _applicationServiceServer.GetLastOpenedUser(context); + } + + [CommandCmif(5)] + // GetProfile(nn::account::Uid) -> object + public ResultCode GetProfile(ServiceCtx context) + { + ResultCode resultCode = _applicationServiceServer.GetProfile(context, out IProfile iProfile); + + if (resultCode == ResultCode.Success) + { + MakeObject(context, iProfile); + } + + return resultCode; + } + + [CommandCmif(50)] + // IsUserRegistrationRequestPermitted(pid) -> bool + public ResultCode IsUserRegistrationRequestPermitted(ServiceCtx context) + { + // NOTE: pid is unused. + + return _applicationServiceServer.IsUserRegistrationRequestPermitted(context); + } + + [CommandCmif(51)] + // TrySelectUserWithoutInteraction(bool) -> nn::account::Uid + public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context) + { + return _applicationServiceServer.TrySelectUserWithoutInteraction(context); + } + + [CommandCmif(102)] + // GetBaasAccountManagerForSystemService(nn::account::Uid) -> object + public ResultCode GetBaasAccountManagerForSystemService(ServiceCtx context) + { + UserId userId = context.RequestData.ReadStruct(); + + if (userId.IsNull) + { + return ResultCode.NullArgument; + } + + MakeObject(context, new IManagerForSystemService(userId)); + + // Doesn't occur in our case. + // return ResultCode.NullObject; + + return ResultCode.Success; + } + + [CommandCmif(140)] // 6.0.0+ + // ListQualifiedUsers() -> array + public ResultCode ListQualifiedUsers(ServiceCtx context) + { + return _applicationServiceServer.ListQualifiedUsers(context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncContext.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncContext.cs new file mode 100644 index 00000000..91daba5f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncContext.cs @@ -0,0 +1,79 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Account.Acc.AsyncContext; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + class IAsyncContext : IpcService + { + protected AsyncExecution AsyncExecution; + + public IAsyncContext(AsyncExecution asyncExecution) + { + AsyncExecution = asyncExecution; + } + + [CommandCmif(0)] + // GetSystemEvent() -> handle + public ResultCode GetSystemEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(AsyncExecution.SystemEvent.ReadableEvent, out int systemEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(systemEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // Cancel() + public ResultCode Cancel(ServiceCtx context) + { + if (!AsyncExecution.IsInitialized) + { + return ResultCode.AsyncExecutionNotInitialized; + } + + if (AsyncExecution.IsRunning) + { + AsyncExecution.Cancel(); + } + + return ResultCode.Success; + } + + [CommandCmif(2)] + // HasDone() -> b8 + public ResultCode HasDone(ServiceCtx context) + { + if (!AsyncExecution.IsInitialized) + { + return ResultCode.AsyncExecutionNotInitialized; + } + + context.ResponseData.Write(AsyncExecution.SystemEvent.ReadableEvent.IsSignaled()); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetResult() + public ResultCode GetResult(ServiceCtx context) + { + if (!AsyncExecution.IsInitialized) + { + return ResultCode.AsyncExecutionNotInitialized; + } + + if (!AsyncExecution.SystemEvent.ReadableEvent.IsSignaled()) + { + return ResultCode.Unknown41; + } + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncNetworkServiceLicenseKindContext.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncNetworkServiceLicenseKindContext.cs new file mode 100644 index 00000000..17583850 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncNetworkServiceLicenseKindContext.cs @@ -0,0 +1,38 @@ +using Ryujinx.HLE.HOS.Services.Account.Acc.AsyncContext; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + class IAsyncNetworkServiceLicenseKindContext : IAsyncContext + { + private readonly NetworkServiceLicenseKind? _serviceLicenseKind; + + public IAsyncNetworkServiceLicenseKindContext(AsyncExecution asyncExecution, NetworkServiceLicenseKind? serviceLicenseKind) : base(asyncExecution) + { + _serviceLicenseKind = serviceLicenseKind; + } + + [CommandCmif(100)] + // GetNetworkServiceLicenseKind() -> nn::account::NetworkServiceLicenseKind + public ResultCode GetNetworkServiceLicenseKind(ServiceCtx context) + { + if (!AsyncExecution.IsInitialized) + { + return ResultCode.AsyncExecutionNotInitialized; + } + + if (!AsyncExecution.SystemEvent.ReadableEvent.IsSignaled()) + { + return ResultCode.Unknown41; + } + + if (!_serviceLicenseKind.HasValue) + { + return ResultCode.MissingNetworkServiceLicenseKind; + } + + context.ResponseData.Write((uint)_serviceLicenseKind.Value); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/IBaasAccessTokenAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IBaasAccessTokenAccessor.cs new file mode 100644 index 00000000..b8881577 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IBaasAccessTokenAccessor.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + [Service("acc:aa", AccountServiceFlag.BaasAccessTokenAccessor)] // Max Sessions: 4 + class IBaasAccessTokenAccessor : IpcService + { + public IBaasAccessTokenAccessor(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/ProfilesJsonSerializerContext.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/ProfilesJsonSerializerContext.cs new file mode 100644 index 00000000..7b5be953 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/ProfilesJsonSerializerContext.cs @@ -0,0 +1,11 @@ +using Ryujinx.HLE.HOS.Services.Account.Acc.Types; +using System.Text.Json.Serialization; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(ProfilesJson))] + internal partial class ProfilesJsonSerializerContext : JsonSerializerContext + { + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountServiceFlag.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountServiceFlag.cs new file mode 100644 index 00000000..44478e93 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountServiceFlag.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + enum AccountServiceFlag + { + Administrator = 100, + SystemService = 101, + Application = 102, + BaasAccessTokenAccessor = 200, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs new file mode 100644 index 00000000..0e35b481 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs @@ -0,0 +1,12 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum AccountState + { + Closed, + Open, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/NetworkServiceLicenseKind.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/NetworkServiceLicenseKind.cs new file mode 100644 index 00000000..5fe0e866 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/NetworkServiceLicenseKind.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + enum NetworkServiceLicenseKind : uint + { + NoSubscription, + Subscribed, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs new file mode 100644 index 00000000..01c1482c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc.Types +{ + internal struct ProfilesJson + { + public List Profiles { get; set; } + public string LastOpened { get; set; } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs new file mode 100644 index 00000000..c9c78af9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs @@ -0,0 +1,64 @@ +using LibHac.Account; +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + [StructLayout(LayoutKind.Sequential)] + public readonly record struct UserId + { + public readonly long High; + public readonly long Low; + + public bool IsNull => (Low | High) == 0; + + public static UserId Null => new(0, 0); + + public UserId(long low, long high) + { + Low = low; + High = high; + } + + public UserId(byte[] bytes) + { + High = BitConverter.ToInt64(bytes, 0); + Low = BitConverter.ToInt64(bytes, 8); + } + + public UserId(string hex) + { + if (hex == null || hex.Length != 32 || !hex.All("0123456789abcdefABCDEF".Contains)) + { + throw new ArgumentException("Invalid Hex value!", nameof(hex)); + } + + Low = long.Parse(hex.AsSpan(16), NumberStyles.HexNumber); + High = long.Parse(hex.AsSpan(0, 16), NumberStyles.HexNumber); + } + + public void Write(BinaryWriter binaryWriter) + { + binaryWriter.Write(High); + binaryWriter.Write(Low); + } + + public override string ToString() + { + return High.ToString("x16") + Low.ToString("x16"); + } + + public Uid ToLibHacUid() + { + return new Uid((ulong)High, (ulong)Low); + } + + public UInt128 ToUInt128() + { + return new UInt128((ulong)High, (ulong)Low); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs new file mode 100644 index 00000000..e6484822 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs @@ -0,0 +1,87 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Account.Acc +{ + public class UserProfile + { + public UserId UserId { get; } + + public long LastModifiedTimestamp { get; set; } + + private string _name; + + public string Name + { + get => _name; + set + { + _name = value; + + UpdateLastModifiedTimestamp(); + } + } + + private byte[] _image; + + public byte[] Image + { + get => _image; + set + { + _image = value; + + UpdateLastModifiedTimestamp(); + } + } + + private AccountState _accountState; + + public AccountState AccountState + { + get => _accountState; + set + { + _accountState = value; + + UpdateLastModifiedTimestamp(); + } + } + + private AccountState _onlinePlayState; + + public AccountState OnlinePlayState + { + get => _onlinePlayState; + set + { + _onlinePlayState = value; + + UpdateLastModifiedTimestamp(); + } + } + + public UserProfile(UserId userId, string name, byte[] image, long lastModifiedTimestamp = 0) + { + UserId = userId; + Name = name; + Image = image; + + AccountState = AccountState.Closed; + OnlinePlayState = AccountState.Closed; + + if (lastModifiedTimestamp != 0) + { + LastModifiedTimestamp = lastModifiedTimestamp; + } + else + { + UpdateLastModifiedTimestamp(); + } + } + + private void UpdateLastModifiedTimestamp() + { + LastModifiedTimestamp = (long)(DateTime.Now - DateTime.UnixEpoch).TotalSeconds; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfileJson.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfileJson.cs new file mode 100644 index 00000000..2e0e8823 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfileJson.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Acc.Types +{ + internal struct UserProfileJson + { + public string UserId { get; set; } + public string Name { get; set; } + public AccountState AccountState { get; set; } + public AccountState OnlinePlayState { get; set; } + public long LastModifiedTimestamp { get; set; } + public byte[] Image { get; set; } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Dauth/IService.cs b/src/Ryujinx.HLE/HOS/Services/Account/Dauth/IService.cs new file mode 100644 index 00000000..24fc7aad --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/Dauth/IService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Account.Dauth +{ + [Service("dauth:0")] // 5.0.0+ + class IService : IpcService + { + public IService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Account/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Account/ResultCode.cs new file mode 100644 index 00000000..6bd3cce8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Account/ResultCode.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.HLE.HOS.Services.Account +{ + enum ResultCode + { + ModuleId = 124, + ErrorCodeShift = 9, + + Success = 0, + + NullArgument = (20 << ErrorCodeShift) | ModuleId, + InvalidArgument = (22 << ErrorCodeShift) | ModuleId, + NullInputBuffer = (30 << ErrorCodeShift) | ModuleId, + InvalidBufferSize = (31 << ErrorCodeShift) | ModuleId, + InvalidBuffer = (32 << ErrorCodeShift) | ModuleId, + AsyncExecutionNotInitialized = (40 << ErrorCodeShift) | ModuleId, + Unknown41 = (41 << ErrorCodeShift) | ModuleId, + InternetRequestDenied = (59 << ErrorCodeShift) | ModuleId, + UserNotFound = (100 << ErrorCodeShift) | ModuleId, + NullObject = (302 << ErrorCodeShift) | ModuleId, + Unknown341 = (341 << ErrorCodeShift) | ModuleId, + MissingNetworkServiceLicenseKind = (400 << ErrorCodeShift) | ModuleId, + InvalidIdTokenCacheBufferSize = (451 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs new file mode 100644 index 00000000..7700cac0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs @@ -0,0 +1,105 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService +{ + class ILibraryAppletProxy : IpcService + { + private readonly ulong _pid; + + public ILibraryAppletProxy(ulong pid) + { + _pid = pid; + } + + [CommandCmif(0)] + // GetCommonStateGetter() -> object + public ResultCode GetCommonStateGetter(ServiceCtx context) + { + MakeObject(context, new ICommonStateGetter(context)); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetSelfController() -> object + public ResultCode GetSelfController(ServiceCtx context) + { + MakeObject(context, new ISelfController(context, _pid)); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetWindowController() -> object + public ResultCode GetWindowController(ServiceCtx context) + { + MakeObject(context, new IWindowController(_pid)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetAudioController() -> object + public ResultCode GetAudioController(ServiceCtx context) + { + MakeObject(context, new IAudioController()); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetDisplayController() -> object + public ResultCode GetDisplayController(ServiceCtx context) + { + MakeObject(context, new IDisplayController(context)); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // GetProcessWindingController() -> object + public ResultCode GetProcessWindingController(ServiceCtx context) + { + MakeObject(context, new IProcessWindingController()); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // GetLibraryAppletCreator() -> object + public ResultCode GetLibraryAppletCreator(ServiceCtx context) + { + MakeObject(context, new ILibraryAppletCreator()); + + return ResultCode.Success; + } + + [CommandCmif(20)] + // OpenLibraryAppletSelfAccessor() -> object + public ResultCode OpenLibraryAppletSelfAccessor(ServiceCtx context) + { + MakeObject(context, new ILibraryAppletSelfAccessor(context)); + + return ResultCode.Success; + } + + [CommandCmif(21)] + // GetAppletCommonFunctions() -> object + public ResultCode GetAppletCommonFunctions(ServiceCtx context) + { + MakeObject(context, new IAppletCommonFunctions()); + + return ResultCode.Success; + } + + [CommandCmif(1000)] + // GetDebugFunctions() -> object + public ResultCode GetDebugFunctions(ServiceCtx context) + { + MakeObject(context, new IDebugFunctions()); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs new file mode 100644 index 00000000..dd015fd8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs @@ -0,0 +1,113 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService +{ + class ISystemAppletProxy : IpcService + { + private readonly ulong _pid; + + public ISystemAppletProxy(ulong pid) + { + _pid = pid; + } + + [CommandCmif(0)] + // GetCommonStateGetter() -> object + public ResultCode GetCommonStateGetter(ServiceCtx context) + { + MakeObject(context, new ICommonStateGetter(context)); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetSelfController() -> object + public ResultCode GetSelfController(ServiceCtx context) + { + MakeObject(context, new ISelfController(context, _pid)); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetWindowController() -> object + public ResultCode GetWindowController(ServiceCtx context) + { + MakeObject(context, new IWindowController(_pid)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetAudioController() -> object + public ResultCode GetAudioController(ServiceCtx context) + { + MakeObject(context, new IAudioController()); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetDisplayController() -> object + public ResultCode GetDisplayController(ServiceCtx context) + { + MakeObject(context, new IDisplayController(context)); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // GetLibraryAppletCreator() -> object + public ResultCode GetLibraryAppletCreator(ServiceCtx context) + { + MakeObject(context, new ILibraryAppletCreator()); + + return ResultCode.Success; + } + + [CommandCmif(20)] + // GetHomeMenuFunctions() -> object + public ResultCode GetHomeMenuFunctions(ServiceCtx context) + { + MakeObject(context, new IHomeMenuFunctions(context.Device.System)); + + return ResultCode.Success; + } + + [CommandCmif(21)] + // GetGlobalStateController() -> object + public ResultCode GetGlobalStateController(ServiceCtx context) + { + MakeObject(context, new IGlobalStateController()); + + return ResultCode.Success; + } + + [CommandCmif(22)] + // GetApplicationCreator() -> object + public ResultCode GetApplicationCreator(ServiceCtx context) + { + MakeObject(context, new IApplicationCreator()); + + return ResultCode.Success; + } + + [CommandCmif(23)] + // GetAppletCommonFunctions() -> object + public ResultCode GetAppletCommonFunctions(ServiceCtx context) + { + MakeObject(context, new IAppletCommonFunctions()); + + return ResultCode.Success; + } + + [CommandCmif(1000)] + // GetDebugFunctions() -> object + public ResultCode GetDebugFunctions(ServiceCtx context) + { + MakeObject(context, new IDebugFunctions()); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs new file mode 100644 index 00000000..14862813 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs @@ -0,0 +1,261 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletCreator +{ + class ILibraryAppletAccessor : DisposableIpcService + { + private readonly KernelContext _kernelContext; + + private readonly IApplet _applet; + + private readonly AppletSession _normalSession; + private readonly AppletSession _interactiveSession; + + private readonly KEvent _stateChangedEvent; + private readonly KEvent _normalOutDataEvent; + private readonly KEvent _interactiveOutDataEvent; + + private int _stateChangedEventHandle; + private int _normalOutDataEventHandle; + private int _interactiveOutDataEventHandle; + + private int _indirectLayerHandle; + + public ILibraryAppletAccessor(AppletId appletId, Horizon system) + { + _kernelContext = system.KernelContext; + + _stateChangedEvent = new KEvent(system.KernelContext); + _normalOutDataEvent = new KEvent(system.KernelContext); + _interactiveOutDataEvent = new KEvent(system.KernelContext); + + _applet = AppletManager.Create(appletId, system); + + _normalSession = new AppletSession(); + _interactiveSession = new AppletSession(); + + _applet.AppletStateChanged += OnAppletStateChanged; + _normalSession.DataAvailable += OnNormalOutData; + _interactiveSession.DataAvailable += OnInteractiveOutData; + + Logger.Info?.Print(LogClass.ServiceAm, $"Applet '{appletId}' created."); + } + + private void OnAppletStateChanged(object sender, EventArgs e) + { + _stateChangedEvent.WritableEvent.Signal(); + } + + private void OnNormalOutData(object sender, EventArgs e) + { + _normalOutDataEvent.WritableEvent.Signal(); + } + + private void OnInteractiveOutData(object sender, EventArgs e) + { + _interactiveOutDataEvent.WritableEvent.Signal(); + } + + [CommandCmif(0)] + // GetAppletStateChangedEvent() -> handle + public ResultCode GetAppletStateChangedEvent(ServiceCtx context) + { + if (_stateChangedEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_stateChangedEvent.ReadableEvent, out _stateChangedEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangedEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // Start() + public ResultCode Start(ServiceCtx context) + { + return (ResultCode)_applet.Start(_normalSession.GetConsumer(), _interactiveSession.GetConsumer()); + } + + [CommandCmif(20)] + // RequestExit() + public ResultCode RequestExit(ServiceCtx context) + { + // TODO: Since we don't support software Applet for now, we can just signals the changed state of the applet. + _stateChangedEvent.ReadableEvent.Signal(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(30)] + // GetResult() + public ResultCode GetResult(ServiceCtx context) + { + return (ResultCode)_applet.GetResult(); + } + + [CommandCmif(60)] + // PresetLibraryAppletGpuTimeSliceZero() + public ResultCode PresetLibraryAppletGpuTimeSliceZero(ServiceCtx context) + { + // NOTE: This call reset two internal fields to 0 and one internal field to "true". + // It seems to be used only with software keyboard inline. + // Since we doesn't support applets for now, it's fine to stub it. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(100)] + // PushInData(object) + public ResultCode PushInData(ServiceCtx context) + { + IStorage data = GetObject(context, 0); + + _normalSession.Push(data.Data); + + return ResultCode.Success; + } + + [CommandCmif(101)] + // PopOutData() -> object + public ResultCode PopOutData(ServiceCtx context) + { + if (_normalSession.TryPop(out byte[] data)) + { + MakeObject(context, new IStorage(data)); + + _normalOutDataEvent.WritableEvent.Clear(); + + return ResultCode.Success; + } + + return ResultCode.NotAvailable; + } + + [CommandCmif(103)] + // PushInteractiveInData(object) + public ResultCode PushInteractiveInData(ServiceCtx context) + { + IStorage data = GetObject(context, 0); + + _interactiveSession.Push(data.Data); + + return ResultCode.Success; + } + + [CommandCmif(104)] + // PopInteractiveOutData() -> object + public ResultCode PopInteractiveOutData(ServiceCtx context) + { + if (_interactiveSession.TryPop(out byte[] data)) + { + MakeObject(context, new IStorage(data)); + + _interactiveOutDataEvent.WritableEvent.Clear(); + + return ResultCode.Success; + } + + return ResultCode.NotAvailable; + } + + [CommandCmif(105)] + // GetPopOutDataEvent() -> handle + public ResultCode GetPopOutDataEvent(ServiceCtx context) + { + if (_normalOutDataEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_normalOutDataEvent.ReadableEvent, out _normalOutDataEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_normalOutDataEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(106)] + // GetPopInteractiveOutDataEvent() -> handle + public ResultCode GetPopInteractiveOutDataEvent(ServiceCtx context) + { + if (_interactiveOutDataEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_interactiveOutDataEvent.ReadableEvent, out _interactiveOutDataEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_interactiveOutDataEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(110)] + // NeedsToExitProcess() + public ResultCode NeedsToExitProcess(ServiceCtx context) + { + return ResultCode.Stubbed; + } + + [CommandCmif(150)] + // RequestForAppletToGetForeground() + public ResultCode RequestForAppletToGetForeground(ServiceCtx context) + { + return ResultCode.Stubbed; + } + + [CommandCmif(160)] // 2.0.0+ + // GetIndirectLayerConsumerHandle() -> u64 indirect_layer_consumer_handle + public ResultCode GetIndirectLayerConsumerHandle(ServiceCtx context) + { + Horizon horizon = _kernelContext.Device.System; + + _indirectLayerHandle = horizon.AppletState.IndirectLayerHandles.Add(_applet); + + context.ResponseData.Write((ulong)_indirectLayerHandle); + + return ResultCode.Success; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + if (_stateChangedEventHandle != 0) + { + _kernelContext.Syscall.CloseHandle(_stateChangedEventHandle); + } + + if (_normalOutDataEventHandle != 0) + { + _kernelContext.Syscall.CloseHandle(_normalOutDataEventHandle); + } + + if (_interactiveOutDataEventHandle != 0) + { + _kernelContext.Syscall.CloseHandle(_interactiveOutDataEventHandle); + } + } + + Horizon horizon = _kernelContext.Device.System; + + horizon.AppletState.IndirectLayerHandles.Delete(_indirectLayerHandle); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/AppletStandalone.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/AppletStandalone.cs new file mode 100644 index 00000000..c996ed3b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/AppletStandalone.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy +{ + class AppletStandalone + { + public AppletId AppletId; + public LibraryAppletMode LibraryAppletMode; + public Queue InputData; + + public AppletStandalone() + { + InputData = new Queue(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs new file mode 100644 index 00000000..fc02ea17 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs @@ -0,0 +1,78 @@ +using Ryujinx.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy +{ + class ILibraryAppletSelfAccessor : IpcService + { + private readonly AppletStandalone _appletStandalone = new(); + + public ILibraryAppletSelfAccessor(ServiceCtx context) + { + if (context.Device.Processes.ActiveApplication.ProgramId == 0x0100000000001009) + { + // Create MiiEdit data. + _appletStandalone = new AppletStandalone() + { + AppletId = AppletId.MiiEdit, + LibraryAppletMode = LibraryAppletMode.AllForeground, + }; + + byte[] miiEditInputData = new byte[0x100]; + miiEditInputData[0] = 0x03; // Hardcoded unknown value. + + _appletStandalone.InputData.Enqueue(miiEditInputData); + } + else + { + throw new NotImplementedException($"{context.Device.Processes.ActiveApplication.ProgramId} applet is not implemented."); + } + } + + [CommandCmif(0)] + // PopInData() -> object + public ResultCode PopInData(ServiceCtx context) + { + byte[] appletData = _appletStandalone.InputData.Dequeue(); + + if (appletData.Length == 0) + { + return ResultCode.NotAvailable; + } + + MakeObject(context, new IStorage(appletData)); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // GetLibraryAppletInfo() -> nn::am::service::LibraryAppletInfo + public ResultCode GetLibraryAppletInfo(ServiceCtx context) + { + LibraryAppletInfo libraryAppletInfo = new() + { + AppletId = _appletStandalone.AppletId, + LibraryAppletMode = _appletStandalone.LibraryAppletMode, + }; + + context.ResponseData.WriteStruct(libraryAppletInfo); + + return ResultCode.Success; + } + + [CommandCmif(14)] + // GetCallerAppletIdentityInfo() -> nn::am::service::AppletIdentityInfo + public ResultCode GetCallerAppletIdentityInfo(ServiceCtx context) + { + AppletIdentifyInfo appletIdentifyInfo = new() + { + AppletId = AppletId.QLaunch, + TitleId = 0x0100000000001000, + }; + + context.ResponseData.WriteStruct(appletIdentifyInfo); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/IProcessWindingController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/IProcessWindingController.cs new file mode 100644 index 00000000..d86a896d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/IProcessWindingController.cs @@ -0,0 +1,24 @@ +using Ryujinx.Common; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy +{ + class IProcessWindingController : IpcService + { + public IProcessWindingController() { } + + [CommandCmif(0)] + // GetLaunchReason() -> nn::am::service::AppletProcessLaunchReason + public ResultCode GetLaunchReason(ServiceCtx context) + { + // NOTE: Flag is set by using an internal field. + AppletProcessLaunchReason appletProcessLaunchReason = new() + { + Flag = 0, + }; + + context.ResponseData.WriteStruct(appletProcessLaunchReason); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAppletCommonFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAppletCommonFunctions.cs new file mode 100644 index 00000000..13cdd8f1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAppletCommonFunctions.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IAppletCommonFunctions : IpcService + { + public IAppletCommonFunctions() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs new file mode 100644 index 00000000..502324ea --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IApplicationCreator : IpcService + { + public IApplicationCreator() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs new file mode 100644 index 00000000..05a4b0a6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs @@ -0,0 +1,72 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IAudioController : IpcService + { + public IAudioController() { } + + [CommandCmif(0)] + // SetExpectedMasterVolume(f32, f32) + public ResultCode SetExpectedMasterVolume(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + float appletVolume = context.RequestData.ReadSingle(); + float libraryAppletVolume = context.RequestData.ReadSingle(); +#pragma warning restore IDE0059 + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetMainAppletExpectedMasterVolume() -> f32 + public ResultCode GetMainAppletExpectedMasterVolume(ServiceCtx context) + { + context.ResponseData.Write(1f); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetLibraryAppletExpectedMasterVolume() -> f32 + public ResultCode GetLibraryAppletExpectedMasterVolume(ServiceCtx context) + { + context.ResponseData.Write(1f); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // ChangeMainAppletMasterVolume(f32, u64) + public ResultCode ChangeMainAppletMasterVolume(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + float unknown0 = context.RequestData.ReadSingle(); + long unknown1 = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // SetTransparentVolumeRate(f32) + public ResultCode SetTransparentVolumeRate(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + float unknown0 = context.RequestData.ReadSingle(); +#pragma warning restore IDE0059 + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs new file mode 100644 index 00000000..602fc2c4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs @@ -0,0 +1,335 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Settings.Types; +using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Lbl; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class ICommonStateGetter : DisposableIpcService + { + private readonly ServiceCtx _context; + + private readonly Apm.ManagerServer _apmManagerServer; + private readonly Apm.SystemManagerServer _apmSystemManagerServer; + + private bool _vrModeEnabled; +#pragma warning disable CS0414, IDE0052 // Remove unread private member + private bool _lcdBacklighOffEnabled; + private bool _requestExitToLibraryAppletAtExecuteNextProgramEnabled; +#pragma warning restore CS0414, IDE0052 + private int _messageEventHandle; + private int _displayResolutionChangedEventHandle; + + private readonly KEvent _acquiredSleepLockEvent; + private int _acquiredSleepLockEventHandle; + + public ICommonStateGetter(ServiceCtx context) + { + _context = context; + + _apmManagerServer = new Apm.ManagerServer(context); + _apmSystemManagerServer = new Apm.SystemManagerServer(context); + + _acquiredSleepLockEvent = new KEvent(context.Device.System.KernelContext); + } + + [CommandCmif(0)] + // GetEventHandle() -> handle + public ResultCode GetEventHandle(ServiceCtx context) + { + KEvent messageEvent = context.Device.System.AppletState.MessageEvent; + + if (_messageEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(messageEvent.ReadableEvent, out _messageEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_messageEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // ReceiveMessage() -> nn::am::AppletMessage + public ResultCode ReceiveMessage(ServiceCtx context) + { + if (!context.Device.System.AppletState.Messages.TryDequeue(out AppletMessage message)) + { + return ResultCode.NoMessages; + } + + KEvent messageEvent = context.Device.System.AppletState.MessageEvent; + + // NOTE: Service checks if current states are different than the stored ones. + // Since we don't support any states for now, it's fine to check if there is still messages available. + + if (context.Device.System.AppletState.Messages.IsEmpty) + { + messageEvent.ReadableEvent.Clear(); + } + else + { + messageEvent.ReadableEvent.Signal(); + } + + context.ResponseData.Write((int)message); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // GetOperationMode() -> u8 + public ResultCode GetOperationMode(ServiceCtx context) + { + OperationMode mode = context.Device.System.State.DockedMode + ? OperationMode.Docked + : OperationMode.Handheld; + + context.ResponseData.Write((byte)mode); + + return ResultCode.Success; + } + + [CommandCmif(6)] + // GetPerformanceMode() -> nn::apm::PerformanceMode + public ResultCode GetPerformanceMode(ServiceCtx context) + { + return (ResultCode)_apmManagerServer.GetPerformanceMode(context); + } + + [CommandCmif(8)] + // GetBootMode() -> u8 + public ResultCode GetBootMode(ServiceCtx context) + { + context.ResponseData.Write((byte)0); //Unknown value. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // GetCurrentFocusState() -> u8 + public ResultCode GetCurrentFocusState(ServiceCtx context) + { + context.ResponseData.Write((byte)context.Device.System.AppletState.FocusState); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // RequestToAcquireSleepLock() + public ResultCode RequestToAcquireSleepLock(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(13)] + // GetAcquiredSleepLockEvent() -> handle + public ResultCode GetAcquiredSleepLockEvent(ServiceCtx context) + { + if (_acquiredSleepLockEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_acquiredSleepLockEvent.ReadableEvent, out _acquiredSleepLockEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_acquiredSleepLockEventHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(50)] // 3.0.0+ + // IsVrModeEnabled() -> b8 + public ResultCode IsVrModeEnabled(ServiceCtx context) + { + context.ResponseData.Write(_vrModeEnabled); + + return ResultCode.Success; + } + + [CommandCmif(51)] // 3.0.0+ + // SetVrModeEnabled(b8) + public ResultCode SetVrModeEnabled(ServiceCtx context) + { + bool vrModeEnabled = context.RequestData.ReadBoolean(); + + UpdateVrMode(vrModeEnabled); + + return ResultCode.Success; + } + + [CommandCmif(52)] // 4.0.0+ + // SetLcdBacklighOffEnabled(b8) + public ResultCode SetLcdBacklighOffEnabled(ServiceCtx context) + { + // NOTE: Service sets a private field here, maybe this field is used somewhere else to turned off the backlight. + // Since we don't support backlight, it's fine to do nothing. + + _lcdBacklighOffEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(53)] // 7.0.0+ + // BeginVrModeEx() + public ResultCode BeginVrModeEx(ServiceCtx context) + { + UpdateVrMode(true); + + return ResultCode.Success; + } + + [CommandCmif(54)] // 7.0.0+ + // EndVrModeEx() + public ResultCode EndVrModeEx(ServiceCtx context) + { + UpdateVrMode(false); + + return ResultCode.Success; + } + + private void UpdateVrMode(bool vrModeEnabled) + { + if (_vrModeEnabled == vrModeEnabled) + { + return; + } + + _vrModeEnabled = vrModeEnabled; + + using var lblApi = new LblApi(); + + if (vrModeEnabled) + { + lblApi.EnableVrMode().AbortOnFailure(); + } + else + { + lblApi.DisableVrMode().AbortOnFailure(); + } + + // TODO: It signals an internal event of ICommonStateGetter. We have to determine where this event is used. + } + + [CommandCmif(60)] // 3.0.0+ + // GetDefaultDisplayResolution() -> (u32, u32) + public ResultCode GetDefaultDisplayResolution(ServiceCtx context) + { + // NOTE: Original service calls IOperationModeManager::GetDefaultDisplayResolution of omm service. + // IOperationModeManager::GetDefaultDisplayResolution of omm service call IManagerDisplayService::GetDisplayResolution of vi service. + (ulong width, ulong height) = AndroidSurfaceComposerClient.GetDisplayInfo(context); + + context.ResponseData.Write((uint)width); + context.ResponseData.Write((uint)height); + + return ResultCode.Success; + } + + [CommandCmif(61)] // 3.0.0+ + // GetDefaultDisplayResolutionChangeEvent() -> handle + public ResultCode GetDefaultDisplayResolutionChangeEvent(ServiceCtx context) + { + // NOTE: Original service calls IOperationModeManager::GetDefaultDisplayResolutionChangeEvent of omm service. + if (_displayResolutionChangedEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(context.Device.System.DisplayResolutionChangeEvent.ReadableEvent, out _displayResolutionChangedEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_displayResolutionChangedEventHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(62)] // 4.0.0+ + // GetHdcpAuthenticationState() -> s32 state + public ResultCode GetHdcpAuthenticationState(ServiceCtx context) + { + context.ResponseData.Write(0); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(66)] // 6.0.0+ + // SetCpuBoostMode(u32 cpu_boost_mode) + public ResultCode SetCpuBoostMode(ServiceCtx context) + { + uint cpuBoostMode = context.RequestData.ReadUInt32(); + + if (cpuBoostMode > 1) + { + return ResultCode.InvalidParameters; + } + + _apmSystemManagerServer.SetCpuBoostMode((Apm.CpuBoostMode)cpuBoostMode); + + // TODO: It signals an internal event of ICommonStateGetter. We have to determine where this event is used. + + return ResultCode.Success; + } + + [CommandCmif(91)] // 7.0.0+ + // GetCurrentPerformanceConfiguration() -> nn::apm::PerformanceConfiguration + public ResultCode GetCurrentPerformanceConfiguration(ServiceCtx context) + { + return (ResultCode)_apmSystemManagerServer.GetCurrentPerformanceConfiguration(context); + } + + [CommandCmif(300)] // 9.0.0+ + // GetSettingsPlatformRegion() -> u8 + public ResultCode GetSettingsPlatformRegion(ServiceCtx context) + { + PlatformRegion platformRegion = context.Device.System.State.DesiredRegionCode == (uint)RegionCode.China ? PlatformRegion.China : PlatformRegion.Global; + + // FIXME: Call set:sys GetPlatformRegion + context.ResponseData.Write((byte)platformRegion); + + return ResultCode.Success; + } + + [CommandCmif(900)] // 11.0.0+ + // SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled() + public ResultCode SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(ServiceCtx context) + { + // TODO : Find where the field is used. + _requestExitToLibraryAppletAtExecuteNextProgramEnabled = true; + + return ResultCode.Success; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + if (_acquiredSleepLockEventHandle != 0) + { + _context.Process.HandleTable.CloseHandle(_acquiredSleepLockEventHandle); + _acquiredSleepLockEventHandle = 0; + } + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs new file mode 100644 index 00000000..61cef13b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IDebugFunctions : IpcService + { + public IDebugFunctions() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs new file mode 100644 index 00000000..6bd35a77 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs @@ -0,0 +1,106 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IDisplayController : IpcService + { + private readonly KTransferMemory _transferMem; + private bool _lastApplicationCaptureBufferAcquired; + private bool _callerAppletCaptureBufferAcquired; + + public IDisplayController(ServiceCtx context) + { + _transferMem = context.Device.System.AppletCaptureBufferTransfer; + } + + [CommandCmif(8)] // 2.0.0+ + // TakeScreenShotOfOwnLayer(b8, s32) + public ResultCode TakeScreenShotOfOwnLayer(ServiceCtx context) + { + bool unknown1 = context.RequestData.ReadBoolean(); + int unknown2 = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { unknown1, unknown2 }); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // ReleaseLastApplicationCaptureBuffer() + public ResultCode ReleaseLastApplicationCaptureBuffer(ServiceCtx context) + { + if (!_lastApplicationCaptureBufferAcquired) + { + return ResultCode.BufferNotAcquired; + } + + _lastApplicationCaptureBufferAcquired = false; + + return ResultCode.Success; + } + + [CommandCmif(15)] + // ReleaseCallerAppletCaptureBuffer() + public ResultCode ReleaseCallerAppletCaptureBuffer(ServiceCtx context) + { + if (!_callerAppletCaptureBufferAcquired) + { + return ResultCode.BufferNotAcquired; + } + + _callerAppletCaptureBufferAcquired = false; + + return ResultCode.Success; + } + + [CommandCmif(16)] + // AcquireLastApplicationCaptureBufferEx() -> (b8, handle) + public ResultCode AcquireLastApplicationCaptureBufferEx(ServiceCtx context) + { + if (_lastApplicationCaptureBufferAcquired) + { + return ResultCode.BufferAlreadyAcquired; + } + + if (context.Process.HandleTable.GenerateHandle(_transferMem, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + _lastApplicationCaptureBufferAcquired = true; + + context.ResponseData.Write(_lastApplicationCaptureBufferAcquired); + + return ResultCode.Success; + } + + [CommandCmif(18)] + // AcquireCallerAppletCaptureBufferEx() -> (b8, handle) + public ResultCode AcquireCallerAppletCaptureBufferEx(ServiceCtx context) + { + if (_callerAppletCaptureBufferAcquired) + { + return ResultCode.BufferAlreadyAcquired; + } + + if (context.Process.HandleTable.GenerateHandle(_transferMem, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + _callerAppletCaptureBufferAcquired = true; + + context.ResponseData.Write(_callerAppletCaptureBufferAcquired); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs new file mode 100644 index 00000000..9e46d1cd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IGlobalStateController : IpcService + { + public IGlobalStateController() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs new file mode 100644 index 00000000..78f47e0e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs @@ -0,0 +1,48 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IHomeMenuFunctions : IpcService + { + private readonly KEvent _channelEvent; + private int _channelEventHandle; + + public IHomeMenuFunctions(Horizon system) + { + // TODO: Signal this Event somewhere in future. + _channelEvent = new KEvent(system.KernelContext); + } + + [CommandCmif(10)] + // RequestToGetForeground() + public ResultCode RequestToGetForeground(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(21)] + // GetPopFromGeneralChannelEvent() -> handle + public ResultCode GetPopFromGeneralChannelEvent(ServiceCtx context) + { + if (_channelEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_channelEvent.ReadableEvent, out _channelEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_channelEventHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs new file mode 100644 index 00000000..23ba99b0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs @@ -0,0 +1,93 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletCreator; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class ILibraryAppletCreator : IpcService + { + public ILibraryAppletCreator() { } + + [CommandCmif(0)] + // CreateLibraryApplet(u32, u32) -> object + public ResultCode CreateLibraryApplet(ServiceCtx context) + { + AppletId appletId = (AppletId)context.RequestData.ReadInt32(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + int libraryAppletMode = context.RequestData.ReadInt32(); +#pragma warning restore IDE0059 + + MakeObject(context, new ILibraryAppletAccessor(appletId, context.Device.System)); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // CreateStorage(u64) -> object + public ResultCode CreateStorage(ServiceCtx context) + { + long size = context.RequestData.ReadInt64(); + + if (size <= 0) + { + return ResultCode.ObjectInvalid; + } + + MakeObject(context, new IStorage(new byte[size])); + + // NOTE: Returns ResultCode.MemoryAllocationFailed if IStorage is null, it doesn't occur in our case. + + return ResultCode.Success; + } + + [CommandCmif(11)] + // CreateTransferMemoryStorage(b8, u64, handle) -> object + public ResultCode CreateTransferMemoryStorage(ServiceCtx context) + { + bool isReadOnly = (context.RequestData.ReadInt64() & 1) == 0; + long size = context.RequestData.ReadInt64(); + int handle = context.Request.HandleDesc.ToCopy[0]; + + KTransferMemory transferMem = context.Process.HandleTable.GetObject(handle); + + if (size <= 0) + { + return ResultCode.ObjectInvalid; + } + + byte[] data = new byte[transferMem.Size]; + + transferMem.Creator.CpuMemory.Read(transferMem.Address, data); + + context.Device.System.KernelContext.Syscall.CloseHandle(handle); + + MakeObject(context, new IStorage(data, isReadOnly)); + + return ResultCode.Success; + } + + [CommandCmif(12)] // 2.0.0+ + // CreateHandleStorage(u64, handle) -> object + public ResultCode CreateHandleStorage(ServiceCtx context) + { + long size = context.RequestData.ReadInt64(); + int handle = context.Request.HandleDesc.ToCopy[0]; + + KTransferMemory transferMem = context.Process.HandleTable.GetObject(handle); + + if (size <= 0) + { + return ResultCode.ObjectInvalid; + } + + byte[] data = new byte[transferMem.Size]; + + transferMem.Creator.CpuMemory.Read(transferMem.Address, data); + + context.Device.System.KernelContext.Syscall.CloseHandle(handle); + + MakeObject(context, new IStorage(data)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs new file mode 100644 index 00000000..85898f13 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs @@ -0,0 +1,436 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.Types; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class ISelfController : IpcService + { + private readonly ulong _pid; + + private readonly KEvent _libraryAppletLaunchableEvent; + private int _libraryAppletLaunchableEventHandle; + + private KEvent _accumulatedSuspendedTickChangedEvent; + private int _accumulatedSuspendedTickChangedEventHandle; + + private readonly object _fatalSectionLock = new(); + private int _fatalSectionCount; + + // TODO: Set this when the game goes in suspension (go back to home menu ect), we currently don't support that so we can keep it set to 0. + private readonly ulong _accumulatedSuspendedTickValue = 0; + + // TODO: Determine where those fields are used. +#pragma warning disable IDE0052 // Remove unread private member + private bool _screenShotPermission = false; + private bool _operationModeChangedNotification = false; + private bool _performanceModeChangedNotification = false; + private bool _restartMessageEnabled = false; + private bool _outOfFocusSuspendingEnabled = false; + private bool _handlesRequestToDisplay = false; +#pragma warning restore IDE0052 + private bool _autoSleepDisabled = false; +#pragma warning disable IDE0052 // Remove unread private member + private bool _albumImageTakenNotificationEnabled = false; + private bool _recordVolumeMuted = false; + + private uint _screenShotImageOrientation = 0; +#pragma warning restore IDE0052 + private uint _idleTimeDetectionExtension = 0; + + public ISelfController(ServiceCtx context, ulong pid) + { + _libraryAppletLaunchableEvent = new KEvent(context.Device.System.KernelContext); + _pid = pid; + } + + [CommandCmif(0)] + // Exit() + public ResultCode Exit(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // LockExit() + public ResultCode LockExit(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // UnlockExit() + public ResultCode UnlockExit(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(3)] // 2.0.0+ + // EnterFatalSection() + public ResultCode EnterFatalSection(ServiceCtx context) + { + lock (_fatalSectionLock) + { + _fatalSectionCount++; + } + + return ResultCode.Success; + } + + [CommandCmif(4)] // 2.0.0+ + // LeaveFatalSection() + public ResultCode LeaveFatalSection(ServiceCtx context) + { + ResultCode result = ResultCode.Success; + + lock (_fatalSectionLock) + { + if (_fatalSectionCount != 0) + { + _fatalSectionCount--; + } + else + { + result = ResultCode.UnbalancedFatalSection; + } + } + + return result; + } + + [CommandCmif(9)] + // GetLibraryAppletLaunchableEvent() -> handle + public ResultCode GetLibraryAppletLaunchableEvent(ServiceCtx context) + { + _libraryAppletLaunchableEvent.ReadableEvent.Signal(); + + if (_libraryAppletLaunchableEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_libraryAppletLaunchableEvent.ReadableEvent, out _libraryAppletLaunchableEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_libraryAppletLaunchableEventHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // SetScreenShotPermission(u32) + public ResultCode SetScreenShotPermission(ServiceCtx context) + { + bool screenShotPermission = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { screenShotPermission }); + + _screenShotPermission = screenShotPermission; + + return ResultCode.Success; + } + + [CommandCmif(11)] + // SetOperationModeChangedNotification(b8) + public ResultCode SetOperationModeChangedNotification(ServiceCtx context) + { + bool operationModeChangedNotification = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { operationModeChangedNotification }); + + _operationModeChangedNotification = operationModeChangedNotification; + + return ResultCode.Success; + } + + [CommandCmif(12)] + // SetPerformanceModeChangedNotification(b8) + public ResultCode SetPerformanceModeChangedNotification(ServiceCtx context) + { + bool performanceModeChangedNotification = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { performanceModeChangedNotification }); + + _performanceModeChangedNotification = performanceModeChangedNotification; + + return ResultCode.Success; + } + + [CommandCmif(13)] + // SetFocusHandlingMode(b8, b8, b8) + public ResultCode SetFocusHandlingMode(ServiceCtx context) + { + bool unknownFlag1 = context.RequestData.ReadBoolean(); + bool unknownFlag2 = context.RequestData.ReadBoolean(); + bool unknownFlag3 = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { unknownFlag1, unknownFlag2, unknownFlag3 }); + + return ResultCode.Success; + } + + [CommandCmif(14)] + // SetRestartMessageEnabled(b8) + public ResultCode SetRestartMessageEnabled(ServiceCtx context) + { + bool restartMessageEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { restartMessageEnabled }); + + _restartMessageEnabled = restartMessageEnabled; + + return ResultCode.Success; + } + + [CommandCmif(16)] // 2.0.0+ + // SetOutOfFocusSuspendingEnabled(b8) + public ResultCode SetOutOfFocusSuspendingEnabled(ServiceCtx context) + { + bool outOfFocusSuspendingEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { outOfFocusSuspendingEnabled }); + + _outOfFocusSuspendingEnabled = outOfFocusSuspendingEnabled; + + return ResultCode.Success; + } + + [CommandCmif(19)] // 3.0.0+ + // SetScreenShotImageOrientation(u32) + public ResultCode SetScreenShotImageOrientation(ServiceCtx context) + { + uint screenShotImageOrientation = context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { screenShotImageOrientation }); + + _screenShotImageOrientation = screenShotImageOrientation; + + return ResultCode.Success; + } + + [CommandCmif(40)] + // CreateManagedDisplayLayer() -> u64 + public ResultCode CreateManagedDisplayLayer(ServiceCtx context) + { + context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, _pid); + context.Device.System.SurfaceFlinger.SetRenderLayer(layerId); + + context.ResponseData.Write(layerId); + + return ResultCode.Success; + } + + [CommandCmif(41)] // 4.0.0+ + // IsSystemBufferSharingEnabled() + public ResultCode IsSystemBufferSharingEnabled(ServiceCtx context) + { + // NOTE: Service checks a private field and return an error if the SystemBufferSharing is disabled. + + return ResultCode.NotImplemented; + } + + [CommandCmif(44)] // 10.0.0+ + // CreateManagedDisplaySeparableLayer() -> (u64, u64) + public ResultCode CreateManagedDisplaySeparableLayer(ServiceCtx context) + { + context.Device.System.SurfaceFlinger.CreateLayer(out long displayLayerId, _pid); + context.Device.System.SurfaceFlinger.CreateLayer(out long recordingLayerId, _pid); + context.Device.System.SurfaceFlinger.SetRenderLayer(displayLayerId); + + context.ResponseData.Write(displayLayerId); + context.ResponseData.Write(recordingLayerId); + + return ResultCode.Success; + } + + [CommandCmif(50)] + // SetHandlesRequestToDisplay(b8) + public ResultCode SetHandlesRequestToDisplay(ServiceCtx context) + { + bool handlesRequestToDisplay = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { handlesRequestToDisplay }); + + _handlesRequestToDisplay = handlesRequestToDisplay; + + return ResultCode.Success; + } + + [CommandCmif(62)] + // SetIdleTimeDetectionExtension(u32) + public ResultCode SetIdleTimeDetectionExtension(ServiceCtx context) + { + uint idleTimeDetectionExtension = context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { idleTimeDetectionExtension }); + + _idleTimeDetectionExtension = idleTimeDetectionExtension; + + return ResultCode.Success; + } + + [CommandCmif(63)] + // GetIdleTimeDetectionExtension() -> u32 + public ResultCode GetIdleTimeDetectionExtension(ServiceCtx context) + { + context.ResponseData.Write(_idleTimeDetectionExtension); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { _idleTimeDetectionExtension }); + + return ResultCode.Success; + } + + [CommandCmif(65)] + // ReportUserIsActive() + public ResultCode ReportUserIsActive(ServiceCtx context) + { + // TODO: Call idle:sys ReportUserIsActive when implemented. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(67)] //3.0.0+ + // IsIlluminanceAvailable() -> bool + public ResultCode IsIlluminanceAvailable(ServiceCtx context) + { + // NOTE: This should call IsAmbientLightSensorAvailable through to Lbl, but there's no situation where we'd want false. + context.ResponseData.Write(true); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(68)] + // SetAutoSleepDisabled(u8) + public ResultCode SetAutoSleepDisabled(ServiceCtx context) + { + bool autoSleepDisabled = context.RequestData.ReadBoolean(); + + _autoSleepDisabled = autoSleepDisabled; + + return ResultCode.Success; + } + + [CommandCmif(69)] + // IsAutoSleepDisabled() -> u8 + public ResultCode IsAutoSleepDisabled(ServiceCtx context) + { + context.ResponseData.Write(_autoSleepDisabled); + + return ResultCode.Success; + } + + [CommandCmif(71)] //5.0.0+ + // GetCurrentIlluminanceEx() -> (bool, f32) + public ResultCode GetCurrentIlluminanceEx(ServiceCtx context) + { + // TODO: The light value should be configurable - presumably users using software that takes advantage will want control. + context.ResponseData.Write(1); // OverLimit + context.ResponseData.Write(10000f); // Lux - 10K lux is ambient light. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(80)] // 4.0.0+ + // SetWirelessPriorityMode(s32 wireless_priority_mode) + public ResultCode SetWirelessPriorityMode(ServiceCtx context) + { + WirelessPriorityMode wirelessPriorityMode = (WirelessPriorityMode)context.RequestData.ReadInt32(); + + if (wirelessPriorityMode > WirelessPriorityMode.Unknown2) + { + return ResultCode.InvalidParameters; + } + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { wirelessPriorityMode }); + + return ResultCode.Success; + } + + [CommandCmif(90)] // 6.0.0+ + // GetAccumulatedSuspendedTickValue() -> u64 + public ResultCode GetAccumulatedSuspendedTickValue(ServiceCtx context) + { + context.ResponseData.Write(_accumulatedSuspendedTickValue); + + return ResultCode.Success; + } + + [CommandCmif(91)] // 6.0.0+ + // GetAccumulatedSuspendedTickChangedEvent() -> handle + public ResultCode GetAccumulatedSuspendedTickChangedEvent(ServiceCtx context) + { + if (_accumulatedSuspendedTickChangedEventHandle == 0) + { + _accumulatedSuspendedTickChangedEvent = new KEvent(context.Device.System.KernelContext); + + _accumulatedSuspendedTickChangedEvent.ReadableEvent.Signal(); + + if (context.Process.HandleTable.GenerateHandle(_accumulatedSuspendedTickChangedEvent.ReadableEvent, out _accumulatedSuspendedTickChangedEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_accumulatedSuspendedTickChangedEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(100)] // 7.0.0+ + // SetAlbumImageTakenNotificationEnabled(u8) + public ResultCode SetAlbumImageTakenNotificationEnabled(ServiceCtx context) + { + bool albumImageTakenNotificationEnabled = context.RequestData.ReadBoolean(); + + _albumImageTakenNotificationEnabled = albumImageTakenNotificationEnabled; + + return ResultCode.Success; + } + + [CommandCmif(120)] // 11.0.0+ + // SaveCurrentScreenshot(s32 album_report_option) + public ResultCode SaveCurrentScreenshot(ServiceCtx context) + { + AlbumReportOption albumReportOption = (AlbumReportOption)context.RequestData.ReadInt32(); + + if (albumReportOption > AlbumReportOption.Unknown3) + { + return ResultCode.InvalidParameters; + } + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { albumReportOption }); + + return ResultCode.Success; + } + + [CommandCmif(130)] // 13.0.0+ + // SetRecordVolumeMuted(b8) + public ResultCode SetRecordVolumeMuted(ServiceCtx context) + { + bool recordVolumeMuted = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { recordVolumeMuted }); + + _recordVolumeMuted = recordVolumeMuted; + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs new file mode 100644 index 00000000..46dc4916 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs @@ -0,0 +1,36 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IWindowController : IpcService + { + private readonly ulong _pid; + + public IWindowController(ulong pid) + { + _pid = pid; + } + + [CommandCmif(1)] + // GetAppletResourceUserId() -> nn::applet::AppletResourceUserId + public ResultCode GetAppletResourceUserId(ServiceCtx context) + { + long appletResourceUserId = context.Device.System.AppletState.AppletResourceUserIds.Add(_pid); + + context.ResponseData.Write(appletResourceUserId); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // AcquireForegroundRights() + public ResultCode AcquireForegroundRights(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AlbumReportOption.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AlbumReportOption.cs new file mode 100644 index 00000000..b0b58232 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AlbumReportOption.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.Types +{ + enum AlbumReportOption + { + OverlayNotDisplayed, + OverlayDisplayed, + Unknown2, + Unknown3, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AppletMessage.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AppletMessage.cs new file mode 100644 index 00000000..3f4600fa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AppletMessage.cs @@ -0,0 +1,36 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + enum AppletMessage + { + None = 0, + ChangeIntoForeground = 1, + ChangeIntoBackground = 2, + Exit = 4, + ApplicationExited = 6, + FocusStateChanged = 15, + Resume = 16, + DetectShortPressingHomeButton = 20, + DetectLongPressingHomeButton = 21, + DetectShortPressingPowerButton = 22, + DetectMiddlePressingPowerButton = 23, + DetectLongPressingPowerButton = 24, + RequestToPrepareSleep = 25, + FinishedSleepSequence = 26, + SleepRequiredByHighTemperature = 27, + SleepRequiredByLowBattery = 28, + AutoPowerDown = 29, + OperationModeChanged = 30, + PerformanceModeChanged = 31, + DetectReceivingCecSystemStandby = 32, + SdCardRemoved = 33, + LaunchApplicationRequested = 50, + RequestToDisplay = 51, + ShowApplicationLogo = 55, + HideApplicationLogo = 56, + ForceHideApplicationLogo = 57, + FloatingApplicationDetected = 60, + DetectShortPressingCaptureButton = 90, + AlbumScreenShotTaken = 92, + AlbumRecordingSaved = 93, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs new file mode 100644 index 00000000..afb7d6b4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + enum FocusState + { + InFocus = 1, + OutOfFocus = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs new file mode 100644 index 00000000..86125978 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + enum OperationMode + { + Handheld = 0, + Docked = 1, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/WirelessPriorityMode.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/WirelessPriorityMode.cs new file mode 100644 index 00000000..b06057b5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/WirelessPriorityMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.Types +{ + enum WirelessPriorityMode + { + Default, + OptimizedForWlan, + Unknown2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs new file mode 100644 index 00000000..92663253 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + internal class AppletFifo : IAppletFifo + { + private readonly ConcurrentQueue _dataQueue; + + public event EventHandler DataAvailable; + + public bool IsSynchronized + { + get { return ((ICollection)_dataQueue).IsSynchronized; } + } + + public object SyncRoot + { + get { return ((ICollection)_dataQueue).SyncRoot; } + } + + public int Count + { + get { return _dataQueue.Count; } + } + + public AppletFifo() + { + _dataQueue = new ConcurrentQueue(); + } + + public void Push(T item) + { + _dataQueue.Enqueue(item); + + DataAvailable?.Invoke(this, null); + } + + public bool TryAdd(T item) + { + try + { + this.Push(item); + + return true; + } + catch + { + return false; + } + } + + public T Pop() + { + if (_dataQueue.TryDequeue(out T result)) + { + return result; + } + + throw new InvalidOperationException("FIFO empty."); + } + + public bool TryPop(out T result) + { + return _dataQueue.TryDequeue(out result); + } + + public bool TryTake(out T item) + { + return this.TryPop(out item); + } + + public T Peek() + { + if (_dataQueue.TryPeek(out T result)) + { + return result; + } + + throw new InvalidOperationException("FIFO empty."); + } + + public bool TryPeek(out T result) + { + return _dataQueue.TryPeek(out result); + } + + public void Clear() + { + _dataQueue.Clear(); + } + + public T[] ToArray() + { + return _dataQueue.ToArray(); + } + + public void CopyTo(T[] array, int arrayIndex) + { + _dataQueue.CopyTo(array, arrayIndex); + } + + public void CopyTo(Array array, int index) + { + this.CopyTo((T[])array, index); + } + + public IEnumerator GetEnumerator() + { + return _dataQueue.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _dataQueue.GetEnumerator(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs new file mode 100644 index 00000000..a3f44a45 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs @@ -0,0 +1,77 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + internal class AppletSession + { + private readonly IAppletFifo _inputData; + private readonly IAppletFifo _outputData; + + public event EventHandler DataAvailable; + + public int Length + { + get { return _inputData.Count; } + } + + public AppletSession() + : this(new AppletFifo(), + new AppletFifo()) + { } + + public AppletSession( + IAppletFifo inputData, + IAppletFifo outputData) + { + _inputData = inputData; + _outputData = outputData; + + _inputData.DataAvailable += OnDataAvailable; + } + + private void OnDataAvailable(object sender, EventArgs e) + { + DataAvailable?.Invoke(this, null); + } + + public void Push(byte[] item) + { + if (!this.TryPush(item)) + { + // TODO(jduncanator): Throw a proper exception + throw new InvalidOperationException(); + } + } + + public bool TryPush(byte[] item) + { + return _outputData.TryAdd(item); + } + + public byte[] Pop() + { + if (this.TryPop(out byte[] item)) + { + return item; + } + + throw new InvalidOperationException("Input data empty."); + } + + public bool TryPop(out byte[] item) + { + return _inputData.TryTake(out item); + } + + /// + /// This returns an AppletSession that can be used at the + /// other end of the pipe. Pushing data into this new session + /// will put it in the first session's input buffer, and vice + /// versa. + /// + public AppletSession GetConsumer() + { + return new AppletSession(this._outputData, this._inputData); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs new file mode 100644 index 00000000..0a032562 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs @@ -0,0 +1,29 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + [Service("appletAE")] + class IAllSystemAppletProxiesService : IpcService + { + public IAllSystemAppletProxiesService(ServiceCtx context) { } + + [CommandCmif(100)] + // OpenSystemAppletProxy(u64, pid, handle) -> object + public ResultCode OpenSystemAppletProxy(ServiceCtx context) + { + MakeObject(context, new ISystemAppletProxy(context.Request.HandleDesc.PId)); + + return ResultCode.Success; + } + + [CommandCmif(200)] + [CommandCmif(201)] // 3.0.0+ + // OpenLibraryAppletProxy(u64, pid, handle) -> object + public ResultCode OpenLibraryAppletProxy(ServiceCtx context) + { + MakeObject(context, new ILibraryAppletProxy(context.Request.HandleDesc.PId)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs new file mode 100644 index 00000000..24f183d2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Concurrent; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + interface IAppletFifo : IProducerConsumerCollection + { + event EventHandler DataAvailable; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs new file mode 100644 index 00000000..311084aa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + class IStorage : IpcService + { + public bool IsReadOnly { get; private set; } + public byte[] Data { get; private set; } + + public IStorage(byte[] data, bool isReadOnly = false) + { + IsReadOnly = isReadOnly; + Data = data; + } + + [CommandCmif(0)] + // Open() -> object + public ResultCode Open(ServiceCtx context) + { + MakeObject(context, new IStorageAccessor(this)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs new file mode 100644 index 00000000..54c7b69e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs @@ -0,0 +1,86 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + class IStorageAccessor : IpcService + { + private readonly IStorage _storage; + + public IStorageAccessor(IStorage storage) + { + _storage = storage; + } + + [CommandCmif(0)] + // GetSize() -> u64 + public ResultCode GetSize(ServiceCtx context) + { + context.ResponseData.Write((long)_storage.Data.Length); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // Write(u64, buffer) + public ResultCode Write(ServiceCtx context) + { + if (_storage.IsReadOnly) + { + return ResultCode.ObjectInvalid; + } + + ulong writePosition = context.RequestData.ReadUInt64(); + + if (writePosition > (ulong)_storage.Data.Length) + { + return ResultCode.OutOfBounds; + } + + (ulong position, ulong size) = context.Request.GetBufferType0x21(); + + size = Math.Min(size, (ulong)_storage.Data.Length - writePosition); + + if (size > 0) + { + ulong maxSize = (ulong)_storage.Data.Length - writePosition; + + if (size > maxSize) + { + size = maxSize; + } + + byte[] data = new byte[size]; + + context.Memory.Read(position, data); + + Buffer.BlockCopy(data, 0, _storage.Data, (int)writePosition, (int)size); + } + + return ResultCode.Success; + } + + [CommandCmif(11)] + // Read(u64) -> buffer + public ResultCode Read(ServiceCtx context) + { + ulong readPosition = context.RequestData.ReadUInt64(); + + if (readPosition > (ulong)_storage.Data.Length) + { + return ResultCode.OutOfBounds; + } + + (ulong position, ulong size) = context.Request.GetBufferType0x22(); + + size = Math.Min(size, (ulong)_storage.Data.Length - readPosition); + + byte[] data = new byte[size]; + + Buffer.BlockCopy(_storage.Data, (int)readPosition, data, 0, (int)size); + + context.Memory.Write(position, data); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs new file mode 100644 index 00000000..6c23720e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs @@ -0,0 +1,26 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage +{ + class StorageHelper + { + private const uint LaunchParamsMagic = 0xc79497ca; + + public static byte[] MakeLaunchParams(UserProfile userProfile) + { + // Size needs to be at least 0x88 bytes otherwise application errors. + using MemoryStream ms = MemoryStreamManager.Shared.GetStream(); + BinaryWriter writer = new(ms); + + ms.SetLength(0x88); + + writer.Write(LaunchParamsMagic); + writer.Write(1); // IsAccountSelected? Only lower 8 bits actually used. + userProfile.UserId.Write(writer); + + return ms.ToArray(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs new file mode 100644 index 00000000..d9970341 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + enum AppletId + { + Application = 0x01, + OverlayDisplay = 0x02, + QLaunch = 0x03, + Starter = 0x04, + Auth = 0x0A, + Cabinet = 0x0B, + Controller = 0x0C, + DataErase = 0x0D, + Error = 0x0E, + NetConnect = 0x0F, + PlayerSelect = 0x10, + SoftwareKeyboard = 0x11, + MiiEdit = 0x12, + LibAppletWeb = 0x13, + LibAppletShop = 0x14, + PhotoViewer = 0x15, + Settings = 0x16, + LibAppletOff = 0x17, + LibAppletWhitelisted = 0x18, + LibAppletAuth = 0x19, + MyPage = 0x1A, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletIdentityInfo.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletIdentityInfo.cs new file mode 100644 index 00000000..1f04c7eb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletIdentityInfo.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + struct AppletIdentifyInfo + { + public AppletId AppletId; + public uint Padding; + public ulong TitleId; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletProcessLaunchReason.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletProcessLaunchReason.cs new file mode 100644 index 00000000..461c84ea --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletProcessLaunchReason.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + [StructLayout(LayoutKind.Sequential, Size = 0x4)] + struct AppletProcessLaunchReason + { + public byte Flag; + public ushort Unknown1; + public byte Unknown2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletInfo.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletInfo.cs new file mode 100644 index 00000000..f8a911d4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletInfo.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8)] + struct LibraryAppletInfo + { + public AppletId AppletId; + public LibraryAppletMode LibraryAppletMode; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletMode.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletMode.cs new file mode 100644 index 00000000..629aee07 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletMode.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + [Flags] + enum LibraryAppletMode : uint + { + AllForeground, + PartialForeground, + NoUi, + PartialForegroundWithIndirectDisplay, + AllForegroundInitiallyHidden, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs new file mode 100644 index 00000000..9a7fdcc1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs @@ -0,0 +1,677 @@ +using LibHac.Account; +using LibHac.Fs; +using LibHac.Ncm; +using LibHac.Ns; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage; +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; +using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.Horizon.Common; +using System; +using System.Numerics; +using System.Threading; +using AccountUid = Ryujinx.HLE.HOS.Services.Account.Acc.UserId; +using ApplicationId = LibHac.Ncm.ApplicationId; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy +{ + class IApplicationFunctions : IpcService + { + private long _defaultSaveDataSize = 200000000; + private long _defaultJournalSaveDataSize = 200000000; + + private readonly KEvent _gpuErrorDetectedSystemEvent; + private readonly KEvent _friendInvitationStorageChannelEvent; + private readonly KEvent _notificationStorageChannelEvent; + private readonly KEvent _healthWarningDisappearedSystemEvent; + + private int _gpuErrorDetectedSystemEventHandle; + private int _friendInvitationStorageChannelEventHandle; + private int _notificationStorageChannelEventHandle; + private int _healthWarningDisappearedSystemEventHandle; + + private bool _gamePlayRecordingState; + + private int _jitLoaded; + + private readonly LibHac.HorizonClient _horizon; + + public IApplicationFunctions(Horizon system) + { + // TODO: Find where they are signaled. + _gpuErrorDetectedSystemEvent = new KEvent(system.KernelContext); + _friendInvitationStorageChannelEvent = new KEvent(system.KernelContext); + _notificationStorageChannelEvent = new KEvent(system.KernelContext); + _healthWarningDisappearedSystemEvent = new KEvent(system.KernelContext); + + _horizon = system.LibHacHorizonManager.AmClient; + } + + [CommandCmif(1)] + // PopLaunchParameter(LaunchParameterKind kind) -> object + public ResultCode PopLaunchParameter(ServiceCtx context) + { + LaunchParameterKind kind = (LaunchParameterKind)context.RequestData.ReadUInt32(); + + byte[] storageData; + + switch (kind) + { + case LaunchParameterKind.UserChannel: + storageData = context.Device.Configuration.UserChannelPersistence.Pop(); + break; + case LaunchParameterKind.PreselectedUser: + // Only the first 0x18 bytes of the Data seems to be actually used. + storageData = StorageHelper.MakeLaunchParams(context.Device.System.AccountManager.LastOpenedUser); + break; + case LaunchParameterKind.Unknown: + throw new NotImplementedException("Unknown LaunchParameterKind."); + default: + return ResultCode.ObjectInvalid; + } + + if (storageData == null) + { + return ResultCode.NotAvailable; + } + + MakeObject(context, new AppletAE.IStorage(storageData)); + + return ResultCode.Success; + } + + [CommandCmif(12)] // 4.0.0+ + // CreateApplicationAndRequestToStart(u64 title_id) + public ResultCode CreateApplicationAndRequestToStart(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { titleId }); + + if (titleId == 0) + { + context.Device.UIHandler.ExecuteProgram(context.Device, ProgramSpecifyKind.RestartProgram, titleId); + } + else + { + throw new NotImplementedException(); + } + + return ResultCode.Success; + } + + [CommandCmif(20)] + // EnsureSaveData(nn::account::Uid) -> u64 + public ResultCode EnsureSaveData(ServiceCtx context) + { + Uid userId = context.RequestData.ReadStruct().ToLibHacUid(); + + // Mask out the low nibble of the program ID to get the application ID + ApplicationId applicationId = new(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul); + + ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; + + LibHac.HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient; + LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in nacp, in userId); + + context.ResponseData.Write(requiredSize); + + return (ResultCode)result.Value; + } + + [CommandCmif(21)] + // GetDesiredLanguage() -> nn::settings::LanguageCode + public ResultCode GetDesiredLanguage(ServiceCtx context) + { + // This seems to be calling ns:am GetApplicationDesiredLanguage followed by ConvertApplicationLanguageToLanguageCode + // Calls are from a IReadOnlyApplicationControlDataInterface object + // ConvertApplicationLanguageToLanguageCode compares language code strings and returns the index + // TODO: When above calls are implemented, switch to using ns:am + + long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode; + int supportedLanguages = (int)context.Device.Processes.ActiveApplication.ApplicationControlProperties.SupportedLanguageFlag; + int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages); + + if (firstSupported > (int)TitleLanguage.BrazilianPortuguese) + { + Logger.Warning?.Print(LogClass.ServiceAm, "Application has zero supported languages"); + + context.ResponseData.Write(desiredLanguageCode); + + return ResultCode.Success; + } + + // If desired language is not supported by application, use first supported language from TitleLanguage. + // TODO: In the future, a GUI could enable user-specified search priority + if (((1 << (int)context.Device.System.State.DesiredTitleLanguage) & supportedLanguages) == 0) + { + SystemLanguage newLanguage = Enum.Parse(Enum.GetName(typeof(TitleLanguage), firstSupported)); + desiredLanguageCode = SystemStateMgr.GetLanguageCode((int)newLanguage); + + Logger.Info?.Print(LogClass.ServiceAm, $"Application doesn't support configured language. Using {newLanguage}"); + } + + context.ResponseData.Write(desiredLanguageCode); + + return ResultCode.Success; + } + + [CommandCmif(22)] + // SetTerminateResult(u32) + public ResultCode SetTerminateResult(ServiceCtx context) + { + LibHac.Result result = new(context.RequestData.ReadUInt32()); + + Logger.Info?.Print(LogClass.ServiceAm, $"Result = 0x{result.Value:x8} ({result.ToStringWithName()})."); + + return ResultCode.Success; + } + + [CommandCmif(23)] + // GetDisplayVersion() -> nn::oe::DisplayVersion + public ResultCode GetDisplayVersion(ServiceCtx context) + { + // If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation. + context.ResponseData.Write(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion); + + return ResultCode.Success; + } + + [CommandCmif(25)] // 3.0.0+ + // ExtendSaveData(u8 save_data_type, nn::account::Uid, s64 save_size, s64 journal_size) -> u64 result_code + public ResultCode ExtendSaveData(ServiceCtx context) + { + SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64(); + Uid userId = context.RequestData.ReadStruct(); + long saveDataSize = context.RequestData.ReadInt64(); + long journalSize = context.RequestData.ReadInt64(); + + // NOTE: Service calls nn::fs::ExtendApplicationSaveData. + // Since LibHac currently doesn't support this method, we can stub it for now. + + _defaultSaveDataSize = saveDataSize; + _defaultJournalSaveDataSize = journalSize; + + context.ResponseData.Write((uint)ResultCode.Success); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { saveDataType, userId, saveDataSize, journalSize }); + + return ResultCode.Success; + } + + [CommandCmif(26)] // 3.0.0+ + // GetSaveDataSize(u8 save_data_type, nn::account::Uid) -> (s64 save_size, s64 journal_size) + public ResultCode GetSaveDataSize(ServiceCtx context) + { + SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64(); + Uid userId = context.RequestData.ReadStruct(); + + // NOTE: Service calls nn::fs::FindSaveDataWithFilter with SaveDataType = 1 hardcoded. + // Then it calls nn::fs::GetSaveDataAvailableSize and nn::fs::GetSaveDataJournalSize to get the sizes. + // Since LibHac currently doesn't support the 2 last methods, we can hardcode the values to 200mb. + + context.ResponseData.Write(_defaultSaveDataSize); + context.ResponseData.Write(_defaultJournalSaveDataSize); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { saveDataType, userId }); + + return ResultCode.Success; + } + + [CommandCmif(27)] // 5.0.0+ + // CreateCacheStorage(u16 index, s64 save_size, s64 journal_size) -> (u32 storageTarget, u64 requiredSize) + public ResultCode CreateCacheStorage(ServiceCtx context) + { + ushort index = (ushort)context.RequestData.ReadUInt64(); + long saveSize = context.RequestData.ReadInt64(); + long journalSize = context.RequestData.ReadInt64(); + + // Mask out the low nibble of the program ID to get the application ID + ApplicationId applicationId = new(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul); + + ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; + + LibHac.Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize, + out CacheStorageTargetMedia storageTarget, applicationId, in nacp, index, saveSize, journalSize); + + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.Write((ulong)storageTarget); + context.ResponseData.Write(requiredSize); + + return ResultCode.Success; + } + + [CommandCmif(28)] // 11.0.0+ + // GetSaveDataSizeMax() -> (s64 save_size_max, s64 journal_size_max) + public ResultCode GetSaveDataSizeMax(ServiceCtx context) + { + // NOTE: We are currently using a stub for GetSaveDataSize() which returns the default values. + // For this method we shouldn't return anything lower than that, but since we aren't interacting + // with fs to get the actual sizes, we return the default values here as well. + // This also helps in case ExtendSaveData() has been executed and the default values were modified. + + context.ResponseData.Write(_defaultSaveDataSize); + context.ResponseData.Write(_defaultJournalSaveDataSize); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(30)] + // BeginBlockingHomeButtonShortAndLongPressed() + public ResultCode BeginBlockingHomeButtonShortAndLongPressed(ServiceCtx context) + { + // NOTE: This set two internal fields at offsets 0x89 and 0x8B to value 1 then it signals an internal event. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(31)] + // EndBlockingHomeButtonShortAndLongPressed() + public ResultCode EndBlockingHomeButtonShortAndLongPressed(ServiceCtx context) + { + // NOTE: This set two internal fields at offsets 0x89 and 0x8B to value 0 then it signals an internal event. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(32)] // 2.0.0+ + // BeginBlockingHomeButton(u64 nano_second) + public ResultCode BeginBlockingHomeButton(ServiceCtx context) + { + ulong nanoSeconds = context.RequestData.ReadUInt64(); + + // NOTE: This set two internal fields at offsets 0x89 to value 1 and 0x90 to value of "nanoSeconds" then it signals an internal event. + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { nanoSeconds }); + + return ResultCode.Success; + } + + [CommandCmif(33)] // 2.0.0+ + // EndBlockingHomeButton() + public ResultCode EndBlockingHomeButton(ServiceCtx context) + { + // NOTE: This set two internal fields at offsets 0x89 and 0x90 to value 0 then it signals an internal event. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(40)] + // NotifyRunning() -> b8 + public ResultCode NotifyRunning(ServiceCtx context) + { + context.ResponseData.Write(true); + + return ResultCode.Success; + } + + [CommandCmif(50)] // 2.0.0+ + // GetPseudoDeviceId() -> nn::util::Uuid + public ResultCode GetPseudoDeviceId(ServiceCtx context) + { + context.ResponseData.Write(0L); + context.ResponseData.Write(0L); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(60)] // 2.0.0+ + // SetMediaPlaybackStateForApplication(bool enabled) + public ResultCode SetMediaPlaybackStateForApplication(ServiceCtx context) + { + bool enabled = context.RequestData.ReadBoolean(); + + // NOTE: Service stores the "enabled" value in a private field, when enabled is false, it stores nn::os::GetSystemTick() too. + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { enabled }); + + return ResultCode.Success; + } + + [CommandCmif(65)] // 3.0.0+ + // IsGamePlayRecordingSupported() -> u8 + public ResultCode IsGamePlayRecordingSupported(ServiceCtx context) + { + context.ResponseData.Write(_gamePlayRecordingState); + + return ResultCode.Success; + } + + [CommandCmif(66)] // 3.0.0+ + // InitializeGamePlayRecording(u64, handle) + public ResultCode InitializeGamePlayRecording(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(67)] // 3.0.0+ + // SetGamePlayRecordingState(u32) + public ResultCode SetGamePlayRecordingState(ServiceCtx context) + { + _gamePlayRecordingState = context.RequestData.ReadInt32() != 0; + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { _gamePlayRecordingState }); + + return ResultCode.Success; + } + + [CommandCmif(90)] // 4.0.0+ + // EnableApplicationCrashReport(u8) + public ResultCode EnableApplicationCrashReport(ServiceCtx context) + { + bool applicationCrashReportEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { applicationCrashReportEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(100)] // 5.0.0+ + // InitializeApplicationCopyrightFrameBuffer(s32 width, s32 height, handle transfer_memory, u64 transfer_memory_size) + public ResultCode InitializeApplicationCopyrightFrameBuffer(ServiceCtx context) + { + int width = context.RequestData.ReadInt32(); + int height = context.RequestData.ReadInt32(); + ulong transferMemorySize = context.RequestData.ReadUInt64(); + int transferMemoryHandle = context.Request.HandleDesc.ToCopy[0]; + ulong transferMemoryAddress = context.Process.HandleTable.GetObject(transferMemoryHandle).Address; + + ResultCode resultCode = ResultCode.InvalidParameters; + + if (((transferMemorySize & 0x3FFFF) == 0) && width <= 1280 && height <= 720) + { + resultCode = InitializeApplicationCopyrightFrameBufferImpl(transferMemoryAddress, transferMemorySize, width, height); + } + + if (transferMemoryHandle != 0) + { + context.Device.System.KernelContext.Syscall.CloseHandle(transferMemoryHandle); + } + + return resultCode; + } + + private ResultCode InitializeApplicationCopyrightFrameBufferImpl(ulong transferMemoryAddress, ulong transferMemorySize, int width, int height) + { + if ((transferMemorySize & 0x3FFFF) != 0) + { + return ResultCode.InvalidParameters; + } + + ResultCode resultCode; + + // if (_copyrightBuffer == null) + { + // TODO: Initialize buffer and object. + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { transferMemoryAddress, transferMemorySize, width, height }); + + resultCode = ResultCode.Success; + } + + return resultCode; + } + + [CommandCmif(101)] // 5.0.0+ + // SetApplicationCopyrightImage(buffer frame_buffer, s32 x, s32 y, s32 width, s32 height, s32 window_origin_mode) + public ResultCode SetApplicationCopyrightImage(ServiceCtx context) + { + ulong frameBufferPos = context.Request.SendBuff[0].Position; + ulong frameBufferSize = context.Request.SendBuff[0].Size; + int x = context.RequestData.ReadInt32(); + int y = context.RequestData.ReadInt32(); + int width = context.RequestData.ReadInt32(); + int height = context.RequestData.ReadInt32(); + uint windowOriginMode = context.RequestData.ReadUInt32(); + + ResultCode resultCode = ResultCode.InvalidParameters; + + if (((y | x) >= 0) && width >= 1 && height >= 1) + { + ResultCode result = SetApplicationCopyrightImageImpl(x, y, width, height, frameBufferPos, frameBufferSize, windowOriginMode); + + if (result != ResultCode.Success) + { + resultCode = result; + } + else + { + resultCode = ResultCode.Success; + } + } + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { frameBufferPos, frameBufferSize, x, y, width, height, windowOriginMode }); + + return resultCode; + } + + private ResultCode SetApplicationCopyrightImageImpl(int x, int y, int width, int height, ulong frameBufferPos, ulong frameBufferSize, uint windowOriginMode) + { + /* + if (_copyrightBuffer == null) + { + return ResultCode.NullCopyrightObject; + } + */ + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { x, y, width, height, frameBufferPos, frameBufferSize, windowOriginMode }); + + return ResultCode.Success; + } + + [CommandCmif(102)] // 5.0.0+ + // SetApplicationCopyrightVisibility(bool visible) + public ResultCode SetApplicationCopyrightVisibility(ServiceCtx context) + { + bool visible = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { visible }); + + // NOTE: It sets an internal field and return ResultCode.Success in all case. + + return ResultCode.Success; + } + + [CommandCmif(110)] // 5.0.0+ + // QueryApplicationPlayStatistics(buffer title_id_list) -> (buffer entries, s32 entries_count) + public ResultCode QueryApplicationPlayStatistics(ServiceCtx context) + { + // TODO: Call pdm:qry cmd 13 when IPC call between services will be implemented. + return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context); + } + + [CommandCmif(111)] // 6.0.0+ + // QueryApplicationPlayStatisticsByUid(nn::account::Uid, buffer title_id_list) -> (buffer entries, s32 entries_count) + public ResultCode QueryApplicationPlayStatisticsByUid(ServiceCtx context) + { + // TODO: Call pdm:qry cmd 16 when IPC call between services will be implemented. + return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context, true); + } + + [CommandCmif(120)] // 5.0.0+ + // ExecuteProgram(ProgramSpecifyKind kind, u64 value) + public ResultCode ExecuteProgram(ServiceCtx context) + { + ProgramSpecifyKind kind = (ProgramSpecifyKind)context.RequestData.ReadUInt32(); + + // padding + context.RequestData.ReadUInt32(); + + ulong value = context.RequestData.ReadUInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { kind, value }); + + context.Device.UIHandler.ExecuteProgram(context.Device, kind, value); + + return ResultCode.Success; + } + + [CommandCmif(121)] // 5.0.0+ + // ClearUserChannel() + public ResultCode ClearUserChannel(ServiceCtx context) + { + context.Device.Configuration.UserChannelPersistence.Clear(); + + return ResultCode.Success; + } + + [CommandCmif(122)] // 5.0.0+ + // UnpopToUserChannel(object input_storage) + public ResultCode UnpopToUserChannel(ServiceCtx context) + { + AppletAE.IStorage data = GetObject(context, 0); + + context.Device.Configuration.UserChannelPersistence.Push(data.Data); + + return ResultCode.Success; + } + + [CommandCmif(123)] // 5.0.0+ + // GetPreviousProgramIndex() -> s32 program_index + public ResultCode GetPreviousProgramIndex(ServiceCtx context) + { + int previousProgramIndex = context.Device.Configuration.UserChannelPersistence.PreviousIndex; + + context.ResponseData.Write(previousProgramIndex); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { previousProgramIndex }); + + return ResultCode.Success; + } + + [CommandCmif(130)] // 8.0.0+ + // GetGpuErrorDetectedSystemEvent() -> handle + public ResultCode GetGpuErrorDetectedSystemEvent(ServiceCtx context) + { + if (_gpuErrorDetectedSystemEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_gpuErrorDetectedSystemEvent.ReadableEvent, out _gpuErrorDetectedSystemEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_gpuErrorDetectedSystemEventHandle); + + // NOTE: This is used by "sdk" NSO during applet-application initialization. + // A separate thread is setup where event-waiting is handled. + // When the Event is signaled, official sw will assert. + + return ResultCode.Success; + } + + [CommandCmif(140)] // 9.0.0+ + // GetFriendInvitationStorageChannelEvent() -> handle + public ResultCode GetFriendInvitationStorageChannelEvent(ServiceCtx context) + { + if (_friendInvitationStorageChannelEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_friendInvitationStorageChannelEvent.ReadableEvent, out _friendInvitationStorageChannelEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_friendInvitationStorageChannelEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(141)] // 9.0.0+ + // TryPopFromFriendInvitationStorageChannel() -> object + public ResultCode TryPopFromFriendInvitationStorageChannel(ServiceCtx context) + { + // NOTE: IStorage are pushed in the channel with IApplicationAccessor PushToFriendInvitationStorageChannel + // If _friendInvitationStorageChannelEvent is signaled, the event is cleared. + // If an IStorage is available, returns it with ResultCode.Success. + // If not, just returns ResultCode.NotAvailable. Since we don't support friend feature for now, it's fine to do the same. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.NotAvailable; + } + + [CommandCmif(150)] // 9.0.0+ + // GetNotificationStorageChannelEvent() -> handle + public ResultCode GetNotificationStorageChannelEvent(ServiceCtx context) + { + if (_notificationStorageChannelEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_notificationStorageChannelEvent.ReadableEvent, out _notificationStorageChannelEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_notificationStorageChannelEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(160)] // 9.0.0+ + // GetHealthWarningDisappearedSystemEvent() -> handle + public ResultCode GetHealthWarningDisappearedSystemEvent(ServiceCtx context) + { + if (_healthWarningDisappearedSystemEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_healthWarningDisappearedSystemEvent.ReadableEvent, out _healthWarningDisappearedSystemEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_healthWarningDisappearedSystemEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(1001)] // 10.0.0+ + // PrepareForJit() + public ResultCode PrepareForJit(ServiceCtx context) + { + if (Interlocked.Exchange(ref _jitLoaded, 1) == 0) + { + string jitPath = context.Device.System.ContentManager.GetInstalledContentPath(0x010000000000003B, StorageId.BuiltInSystem, NcaContentType.Program); + string filePath = FileSystem.VirtualFileSystem.SwitchPathToSystemPath(jitPath); + + if (string.IsNullOrWhiteSpace(filePath)) + { + throw new InvalidSystemResourceException("JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)"); + } + + context.Device.LoadNca(filePath); + + // FIXME: Most likely not how this should be done? + while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u")) + { + context.Device.System.SmRegistry.WaitForServiceRegistration(); + } + } + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs new file mode 100644 index 00000000..e3c90dc5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types +{ + public enum LaunchParameterKind : uint + { + UserChannel = 1, + PreselectedUser, + Unknown, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs new file mode 100644 index 00000000..47a755f9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types +{ + public enum ProgramSpecifyKind : uint + { + ExecuteProgram, + SubApplicationProgram, + RestartProgram, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs new file mode 100644 index 00000000..b24e1bf4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs @@ -0,0 +1,87 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService +{ + class IApplicationProxy : IpcService + { + private readonly ulong _pid; + + public IApplicationProxy(ulong pid) + { + _pid = pid; + } + + [CommandCmif(0)] + // GetCommonStateGetter() -> object + public ResultCode GetCommonStateGetter(ServiceCtx context) + { + MakeObject(context, new ICommonStateGetter(context)); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetSelfController() -> object + public ResultCode GetSelfController(ServiceCtx context) + { + MakeObject(context, new ISelfController(context, _pid)); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetWindowController() -> object + public ResultCode GetWindowController(ServiceCtx context) + { + MakeObject(context, new IWindowController(_pid)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetAudioController() -> object + public ResultCode GetAudioController(ServiceCtx context) + { + MakeObject(context, new IAudioController()); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetDisplayController() -> object + public ResultCode GetDisplayController(ServiceCtx context) + { + MakeObject(context, new IDisplayController(context)); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // GetLibraryAppletCreator() -> object + public ResultCode GetLibraryAppletCreator(ServiceCtx context) + { + MakeObject(context, new ILibraryAppletCreator()); + + return ResultCode.Success; + } + + [CommandCmif(20)] + // GetApplicationFunctions() -> object + public ResultCode GetApplicationFunctions(ServiceCtx context) + { + MakeObject(context, new IApplicationFunctions(context.Device.System)); + + return ResultCode.Success; + } + + [CommandCmif(1000)] + // GetDebugFunctions() -> object + public ResultCode GetDebugFunctions(ServiceCtx context) + { + MakeObject(context, new IDebugFunctions()); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs new file mode 100644 index 00000000..9814976f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService; + +namespace Ryujinx.HLE.HOS.Services.Am +{ + [Service("appletOE")] + class IApplicationProxyService : IpcService + { + public IApplicationProxyService(ServiceCtx context) { } + + [CommandCmif(0)] + // OpenApplicationProxy(u64, pid, handle) -> object + public ResultCode OpenApplicationProxy(ServiceCtx context) + { + MakeObject(context, new IApplicationProxy(context.Request.HandleDesc.PId)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs b/src/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs new file mode 100644 index 00000000..1ff0e6d2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Idle +{ + [Service("idle:sys")] + class IPolicyManagerSystem : IpcService + { + public IPolicyManagerSystem(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs b/src/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs new file mode 100644 index 00000000..c9a402e7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Omm +{ + [Service("omm")] + class IOperationModeManager : IpcService + { + public IOperationModeManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs new file mode 100644 index 00000000..9142f65e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.HLE.HOS.Services.Am +{ + enum ResultCode + { + ModuleId = 128, + ErrorCodeShift = 9, + + Success = 0, + + NotAvailable = (2 << ErrorCodeShift) | ModuleId, + NoMessages = (3 << ErrorCodeShift) | ModuleId, + AppletLaunchFailed = (35 << ErrorCodeShift) | ModuleId, + TitleIdNotFound = (37 << ErrorCodeShift) | ModuleId, + ObjectInvalid = (500 << ErrorCodeShift) | ModuleId, + IStorageInUse = (502 << ErrorCodeShift) | ModuleId, + OutOfBounds = (503 << ErrorCodeShift) | ModuleId, + BufferNotAcquired = (504 << ErrorCodeShift) | ModuleId, + BufferAlreadyAcquired = (505 << ErrorCodeShift) | ModuleId, + InvalidParameters = (506 << ErrorCodeShift) | ModuleId, + OpenedAsWrongType = (511 << ErrorCodeShift) | ModuleId, + UnbalancedFatalSection = (512 << ErrorCodeShift) | ModuleId, + NullObject = (518 << ErrorCodeShift) | ModuleId, + MemoryAllocationFailed = (600 << ErrorCodeShift) | ModuleId, + StackPoolExhausted = (712 << ErrorCodeShift) | ModuleId, + DebugModeNotEnabled = (974 << ErrorCodeShift) | ModuleId, + DevFunctionNotEnabled = (980 << ErrorCodeShift) | ModuleId, + NotImplemented = (998 << ErrorCodeShift) | ModuleId, + Stubbed = (999 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs b/src/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs new file mode 100644 index 00000000..b24acb24 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Spsm +{ + [Service("spsm")] + class IPowerStateInterface : IpcService + { + public IPowerStateInterface(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs new file mode 100644 index 00000000..6c6af3db --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Tcap +{ + [Service("tcap")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Apm/IManager.cs new file mode 100644 index 00000000..83215bef --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/IManager.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + abstract class IManager : IpcService + { + public IManager(ServiceCtx context) { } + + protected abstract ResultCode OpenSession(out SessionServer sessionServer); + protected abstract PerformanceMode GetPerformanceMode(); + protected abstract bool IsCpuOverclockEnabled(); + + [CommandCmif(0)] + // OpenSession() -> object + public ResultCode OpenSession(ServiceCtx context) + { + ResultCode resultCode = OpenSession(out SessionServer sessionServer); + + if (resultCode == ResultCode.Success) + { + MakeObject(context, sessionServer); + } + + return resultCode; + } + + [CommandCmif(1)] + // GetPerformanceMode() -> nn::apm::PerformanceMode + public ResultCode GetPerformanceMode(ServiceCtx context) + { + context.ResponseData.Write((uint)GetPerformanceMode()); + + return ResultCode.Success; + } + + [CommandCmif(6)] // 7.0.0+ + // IsCpuOverclockEnabled() -> bool + public ResultCode IsCpuOverclockEnabled(ServiceCtx context) + { + context.ResponseData.Write(IsCpuOverclockEnabled()); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/IManagerPrivileged.cs b/src/Ryujinx.HLE/HOS/Services/Apm/IManagerPrivileged.cs new file mode 100644 index 00000000..bb0049d1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/IManagerPrivileged.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + // NOTE: This service doesn’t exist anymore after firmware 7.0.1. But some outdated homebrew still uses it. + + [Service("apm:p")] // 1.0.0-7.0.1 + class IManagerPrivileged : IpcService + { + public IManagerPrivileged(ServiceCtx context) { } + + [CommandCmif(0)] + // OpenSession() -> object + public ResultCode OpenSession(ServiceCtx context) + { + MakeObject(context, new SessionServer(context)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/ISession.cs b/src/Ryujinx.HLE/HOS/Services/Apm/ISession.cs new file mode 100644 index 00000000..6ee69605 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/ISession.cs @@ -0,0 +1,45 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + abstract class ISession : IpcService + { + public ISession(ServiceCtx context) { } + + protected abstract ResultCode SetPerformanceConfiguration(PerformanceMode performanceMode, PerformanceConfiguration performanceConfiguration); + protected abstract ResultCode GetPerformanceConfiguration(PerformanceMode performanceMode, out PerformanceConfiguration performanceConfiguration); + protected abstract void SetCpuOverclockEnabled(bool enabled); + + [CommandCmif(0)] + // SetPerformanceConfiguration(nn::apm::PerformanceMode, nn::apm::PerformanceConfiguration) + public ResultCode SetPerformanceConfiguration(ServiceCtx context) + { + PerformanceMode performanceMode = (PerformanceMode)context.RequestData.ReadInt32(); + PerformanceConfiguration performanceConfiguration = (PerformanceConfiguration)context.RequestData.ReadInt32(); + + return SetPerformanceConfiguration(performanceMode, performanceConfiguration); + } + + [CommandCmif(1)] + // GetPerformanceConfiguration(nn::apm::PerformanceMode) -> nn::apm::PerformanceConfiguration + public ResultCode GetPerformanceConfiguration(ServiceCtx context) + { + PerformanceMode performanceMode = (PerformanceMode)context.RequestData.ReadInt32(); + + ResultCode resultCode = GetPerformanceConfiguration(performanceMode, out PerformanceConfiguration performanceConfiguration); + + context.ResponseData.Write((uint)performanceConfiguration); + + return resultCode; + } + + [CommandCmif(2)] // 8.0.0+ + // SetCpuOverclockEnabled(bool) + public ResultCode SetCpuOverclockEnabled(ServiceCtx context) + { + bool enabled = context.RequestData.ReadBoolean(); + + SetCpuOverclockEnabled(enabled); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/ISystemManager.cs b/src/Ryujinx.HLE/HOS/Services/Apm/ISystemManager.cs new file mode 100644 index 00000000..375423cf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/ISystemManager.cs @@ -0,0 +1,42 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + abstract class ISystemManager : IpcService + { + public ISystemManager(ServiceCtx context) { } + + protected abstract void RequestPerformanceMode(PerformanceMode performanceMode); + internal abstract void SetCpuBoostMode(CpuBoostMode cpuBoostMode); + protected abstract PerformanceConfiguration GetCurrentPerformanceConfiguration(); + + [CommandCmif(0)] + // RequestPerformanceMode(nn::apm::PerformanceMode) + public ResultCode RequestPerformanceMode(ServiceCtx context) + { + RequestPerformanceMode((PerformanceMode)context.RequestData.ReadInt32()); + + // NOTE: This call seems to overclock the system related to the PerformanceMode, since we emulate it, it's fine to do nothing instead. + + return ResultCode.Success; + } + + [CommandCmif(6)] // 7.0.0+ + // SetCpuBoostMode(nn::apm::CpuBootMode) + public ResultCode SetCpuBoostMode(ServiceCtx context) + { + SetCpuBoostMode((CpuBoostMode)context.RequestData.ReadUInt32()); + + // NOTE: This call seems to overclock the system related to the CpuBoostMode, since we emulate it, it's fine to do nothing instead. + + return ResultCode.Success; + } + + [CommandCmif(7)] // 7.0.0+ + // GetCurrentPerformanceConfiguration() -> nn::apm::PerformanceConfiguration + public ResultCode GetCurrentPerformanceConfiguration(ServiceCtx context) + { + context.ResponseData.Write((uint)GetCurrentPerformanceConfiguration()); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/ManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Apm/ManagerServer.cs new file mode 100644 index 00000000..16bc4407 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/ManagerServer.cs @@ -0,0 +1,31 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + [Service("apm")] + [Service("apm:am")] // 8.0.0+ + class ManagerServer : IManager + { + private readonly ServiceCtx _context; + + public ManagerServer(ServiceCtx context) : base(context) + { + _context = context; + } + + protected override ResultCode OpenSession(out SessionServer sessionServer) + { + sessionServer = new SessionServer(_context); + + return ResultCode.Success; + } + + protected override PerformanceMode GetPerformanceMode() + { + return _context.Device.System.PerformanceState.PerformanceMode; + } + + protected override bool IsCpuOverclockEnabled() + { + return _context.Device.System.PerformanceState.CpuOverclockEnabled; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/PerformanceState.cs b/src/Ryujinx.HLE/HOS/Services/Apm/PerformanceState.cs new file mode 100644 index 00000000..86a1491d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/PerformanceState.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + class PerformanceState + { + public PerformanceState() { } + + public bool CpuOverclockEnabled = false; + + public PerformanceMode PerformanceMode = PerformanceMode.Default; + public CpuBoostMode CpuBoostMode = CpuBoostMode.Disabled; + + public PerformanceConfiguration DefaultPerformanceConfiguration = PerformanceConfiguration.PerformanceConfiguration7; + public PerformanceConfiguration BoostPerformanceConfiguration = PerformanceConfiguration.PerformanceConfiguration8; + + public PerformanceConfiguration GetCurrentPerformanceConfiguration(PerformanceMode performanceMode) + { + return performanceMode switch + { + PerformanceMode.Default => DefaultPerformanceConfiguration, + PerformanceMode.Boost => BoostPerformanceConfiguration, + _ => PerformanceConfiguration.PerformanceConfiguration7, + }; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Apm/ResultCode.cs new file mode 100644 index 00000000..3cbfbffb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/ResultCode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + enum ResultCode + { + ModuleId = 148, + ErrorCodeShift = 9, + + Success = 0, + + InvalidParameters = (1 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/SessionServer.cs b/src/Ryujinx.HLE/HOS/Services/Apm/SessionServer.cs new file mode 100644 index 00000000..d238e422 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/SessionServer.cs @@ -0,0 +1,58 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Apm +{ + class SessionServer : ISession + { + private readonly ServiceCtx _context; + + public SessionServer(ServiceCtx context) : base(context) + { + _context = context; + } + + protected override ResultCode SetPerformanceConfiguration(PerformanceMode performanceMode, PerformanceConfiguration performanceConfiguration) + { + if (performanceMode > PerformanceMode.Boost) + { + return ResultCode.InvalidParameters; + } + + switch (performanceMode) + { + case PerformanceMode.Default: + _context.Device.System.PerformanceState.DefaultPerformanceConfiguration = performanceConfiguration; + break; + case PerformanceMode.Boost: + _context.Device.System.PerformanceState.BoostPerformanceConfiguration = performanceConfiguration; + break; + default: + Logger.Error?.Print(LogClass.ServiceApm, $"PerformanceMode isn't supported: {performanceMode}"); + break; + } + + return ResultCode.Success; + } + + protected override ResultCode GetPerformanceConfiguration(PerformanceMode performanceMode, out PerformanceConfiguration performanceConfiguration) + { + if (performanceMode > PerformanceMode.Boost) + { + performanceConfiguration = 0; + + return ResultCode.InvalidParameters; + } + + performanceConfiguration = _context.Device.System.PerformanceState.GetCurrentPerformanceConfiguration(performanceMode); + + return ResultCode.Success; + } + + protected override void SetCpuOverclockEnabled(bool enabled) + { + _context.Device.System.PerformanceState.CpuOverclockEnabled = enabled; + + // NOTE: This call seems to overclock the system, since we emulate it, it's fine to do nothing instead. + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/SystemManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Apm/SystemManagerServer.cs new file mode 100644 index 00000000..e8b7d12a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/SystemManagerServer.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + [Service("apm:sys")] + class SystemManagerServer : ISystemManager + { + private readonly ServiceCtx _context; + + public SystemManagerServer(ServiceCtx context) : base(context) + { + _context = context; + } + + protected override void RequestPerformanceMode(PerformanceMode performanceMode) + { + _context.Device.System.PerformanceState.PerformanceMode = performanceMode; + } + + internal override void SetCpuBoostMode(CpuBoostMode cpuBoostMode) + { + _context.Device.System.PerformanceState.CpuBoostMode = cpuBoostMode; + } + + protected override PerformanceConfiguration GetCurrentPerformanceConfiguration() + { + return _context.Device.System.PerformanceState.GetCurrentPerformanceConfiguration(_context.Device.System.PerformanceState.PerformanceMode); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/Types/CpuBoostMode.cs b/src/Ryujinx.HLE/HOS/Services/Apm/Types/CpuBoostMode.cs new file mode 100644 index 00000000..26350157 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/Types/CpuBoostMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + enum CpuBoostMode + { + Disabled = 0, + BoostCPU = 1, // Uses PerformanceConfiguration13 and PerformanceConfiguration14, or PerformanceConfiguration15 and PerformanceConfiguration16 + ConservePower = 2, // Uses PerformanceConfiguration15 and PerformanceConfiguration16. + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceConfiguration.cs b/src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceConfiguration.cs new file mode 100644 index 00000000..650e4f4c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceConfiguration.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + enum PerformanceConfiguration : uint // Clocks are all in MHz. + { // CPU | GPU | RAM | NOTE + PerformanceConfiguration1 = 0x00010000, // 1020 | 384 | 1600 | Only available while docked. + PerformanceConfiguration2 = 0x00010001, // 1020 | 768 | 1600 | Only available while docked. + PerformanceConfiguration3 = 0x00010002, // 1224 | 691.2 | 1600 | Only available for SDEV units. + PerformanceConfiguration4 = 0x00020000, // 1020 | 230.4 | 1600 | Only available for SDEV units. + PerformanceConfiguration5 = 0x00020001, // 1020 | 307.2 | 1600 | + PerformanceConfiguration6 = 0x00020002, // 1224 | 230.4 | 1600 | + PerformanceConfiguration7 = 0x00020003, // 1020 | 307 | 1331.2 | + PerformanceConfiguration8 = 0x00020004, // 1020 | 384 | 1331.2 | + PerformanceConfiguration9 = 0x00020005, // 1020 | 307.2 | 1065.6 | + PerformanceConfiguration10 = 0x00020006, // 1020 | 384 | 1065.6 | + PerformanceConfiguration11 = 0x92220007, // 1020 | 460.8 | 1600 | + PerformanceConfiguration12 = 0x92220008, // 1020 | 460.8 | 1331.2 | + PerformanceConfiguration13 = 0x92220009, // 1785 | 768 | 1600 | 7.0.0+ + PerformanceConfiguration14 = 0x9222000A, // 1785 | 768 | 1331.2 | 7.0.0+ + PerformanceConfiguration15 = 0x9222000B, // 1020 | 768 | 1600 | 7.0.0+ + PerformanceConfiguration16 = 0x9222000C, // 1020 | 768 | 1331.2 | 7.0.0+ + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceMode.cs b/src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceMode.cs new file mode 100644 index 00000000..74f54c44 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Apm +{ + enum PerformanceMode : uint + { + Default = 0, + Boost = 1, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs b/src/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs new file mode 100644 index 00000000..26089bdd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs @@ -0,0 +1,43 @@ +using LibHac.Ncm; + +namespace Ryujinx.HLE.HOS.Services.Arp +{ + class ApplicationLaunchProperty + { + public ulong TitleId; + public int Version; + public byte BaseGameStorageId; + public byte UpdateGameStorageId; +#pragma warning disable CS0649 // Field is never assigned to + public short Padding; +#pragma warning restore CS0649 + + public static ApplicationLaunchProperty Default + { + get + { + return new ApplicationLaunchProperty + { + TitleId = 0x00, + Version = 0x00, + BaseGameStorageId = (byte)StorageId.BuiltInSystem, + UpdateGameStorageId = (byte)StorageId.None, + }; + } + } + + public static ApplicationLaunchProperty GetByPid(ServiceCtx context) + { + // TODO: Handle ApplicationLaunchProperty as array when pid will be supported and return the right item. + // For now we can hardcode values, and fix it after GetApplicationLaunchProperty is implemented. + + return new ApplicationLaunchProperty + { + TitleId = context.Device.Processes.ActiveApplication.ProgramId, + Version = 0x00, + BaseGameStorageId = (byte)StorageId.BuiltInSystem, + UpdateGameStorageId = (byte)StorageId.None, + }; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs b/src/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs new file mode 100644 index 00000000..f69d8500 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs @@ -0,0 +1,75 @@ +using LibHac; +using LibHac.Common; +using LibHac.Ncm; +using LibHac.Ns; +using System; +using ApplicationId = LibHac.ApplicationId; + +namespace Ryujinx.HLE.HOS.Services.Arp +{ + class LibHacIReader : LibHac.Arp.Impl.IReader + { + public ApplicationId ApplicationId { get; set; } + + public Result GetApplicationLaunchProperty(out LibHac.Arp.ApplicationLaunchProperty launchProperty, ulong processId) + { + launchProperty = new LibHac.Arp.ApplicationLaunchProperty + { + StorageId = StorageId.BuiltInUser, + ApplicationId = ApplicationId, + }; + + return Result.Success; + } + + public void Dispose() { } + + public Result GetApplicationLaunchPropertyWithApplicationId(out LibHac.Arp.ApplicationLaunchProperty launchProperty, ApplicationId applicationId) + { + launchProperty = new LibHac.Arp.ApplicationLaunchProperty + { + StorageId = StorageId.BuiltInUser, + ApplicationId = applicationId, + }; + + return Result.Success; + } + + public Result GetApplicationControlProperty(out ApplicationControlProperty controlProperty, ulong processId) + { + throw new NotImplementedException(); + } + + public Result GetApplicationControlPropertyWithApplicationId(out ApplicationControlProperty controlProperty, ApplicationId applicationId) + { + throw new NotImplementedException(); + } + + public Result GetServiceObject(out object serviceObject) + { + throw new NotImplementedException(); + } + } + + internal class LibHacArpServiceObject : LibHac.Sm.IServiceObject + { + private SharedRef _serviceObject; + + public LibHacArpServiceObject(ref SharedRef serviceObject) + { + _serviceObject = SharedRef.CreateCopy(in serviceObject); + } + + public void Dispose() + { + _serviceObject.Destroy(); + } + + public Result GetServiceObject(ref SharedRef serviceObject) + { + serviceObject.SetByCopy(in _serviceObject); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Bgtc/IStateControlService.cs b/src/Ryujinx.HLE/HOS/Services/Bgtc/IStateControlService.cs new file mode 100644 index 00000000..3045712e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bgtc/IStateControlService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Bgct +{ + [Service("bgtc:sc")] + class IStateControlService : IpcService + { + public IStateControlService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Bgtc/ITaskService.cs b/src/Ryujinx.HLE/HOS/Services/Bgtc/ITaskService.cs new file mode 100644 index 00000000..138c10af --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bgtc/ITaskService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Bgct +{ + [Service("bgtc:t")] + class ITaskService : IpcService + { + public ITaskService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothDriver/BluetoothEventManager.cs b/src/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothDriver/BluetoothEventManager.cs new file mode 100644 index 00000000..2c727527 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothDriver/BluetoothEventManager.cs @@ -0,0 +1,25 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Bluetooth.BluetoothDriver +{ + static class BluetoothEventManager + { + public static KEvent InitializeBleDebugEvent; + public static int InitializeBleDebugEventHandle; + + public static KEvent UnknownBleDebugEvent; + public static int UnknownBleDebugEventHandle; + + public static KEvent RegisterBleDebugEvent; + public static int RegisterBleDebugEventHandle; + + public static KEvent InitializeBleEvent; + public static int InitializeBleEventHandle; + + public static KEvent UnknownBleEvent; + public static int UnknownBleEventHandle; + + public static KEvent RegisterBleEvent; + public static int RegisterBleEventHandle; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs b/src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs new file mode 100644 index 00000000..8f264269 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs @@ -0,0 +1,103 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Bluetooth.BluetoothDriver; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Bluetooth +{ + [Service("btdrv")] + class IBluetoothDriver : IpcService + { +#pragma warning disable CS0414, IDE0052 // Remove unread private member + private string _unknownLowEnergy; +#pragma warning restore CS0414, IDE0052 + + public IBluetoothDriver(ServiceCtx context) { } + + [CommandCmif(46)] + // InitializeBluetoothLe() -> handle + public ResultCode InitializeBluetoothLe(ServiceCtx context) + { + NxSettings.Settings.TryGetValue("bluetooth_debug!skip_boot", out object debugMode); + + int initializeEventHandle; + + if ((bool)debugMode) + { + if (BluetoothEventManager.InitializeBleDebugEventHandle == 0) + { + BluetoothEventManager.InitializeBleDebugEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.InitializeBleDebugEvent.ReadableEvent, out BluetoothEventManager.InitializeBleDebugEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.UnknownBleDebugEventHandle == 0) + { + BluetoothEventManager.UnknownBleDebugEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.UnknownBleDebugEvent.ReadableEvent, out BluetoothEventManager.UnknownBleDebugEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.RegisterBleDebugEventHandle == 0) + { + BluetoothEventManager.RegisterBleDebugEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.RegisterBleDebugEvent.ReadableEvent, out BluetoothEventManager.RegisterBleDebugEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + initializeEventHandle = BluetoothEventManager.InitializeBleDebugEventHandle; + } + else + { + _unknownLowEnergy = "low_energy"; + + if (BluetoothEventManager.InitializeBleEventHandle == 0) + { + BluetoothEventManager.InitializeBleEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.InitializeBleEvent.ReadableEvent, out BluetoothEventManager.InitializeBleEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.UnknownBleEventHandle == 0) + { + BluetoothEventManager.UnknownBleEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.UnknownBleEvent.ReadableEvent, out BluetoothEventManager.UnknownBleEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.RegisterBleEventHandle == 0) + { + BluetoothEventManager.RegisterBleEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.RegisterBleEvent.ReadableEvent, out BluetoothEventManager.RegisterBleEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + initializeEventHandle = BluetoothEventManager.InitializeBleEventHandle; + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(initializeEventHandle); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs b/src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs new file mode 100644 index 00000000..ea4a46f9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs @@ -0,0 +1,30 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Bluetooth.BluetoothDriver; +using Ryujinx.HLE.HOS.Services.Settings; + +namespace Ryujinx.HLE.HOS.Services.Bluetooth +{ + [Service("bt")] + class IBluetoothUser : IpcService + { + public IBluetoothUser(ServiceCtx context) { } + + [CommandCmif(9)] + // RegisterBleEvent(pid) -> handle + public ResultCode RegisterBleEvent(ServiceCtx context) + { + NxSettings.Settings.TryGetValue("bluetooth_debug!skip_boot", out object debugMode); + + if ((bool)debugMode) + { + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(BluetoothEventManager.RegisterBleDebugEventHandle); + } + else + { + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(BluetoothEventManager.RegisterBleEventHandle); + } + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmUser/IBtmUserCore.cs b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmUser/IBtmUserCore.cs new file mode 100644 index 00000000..d4e23b93 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmUser/IBtmUserCore.cs @@ -0,0 +1,128 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; + +namespace Ryujinx.HLE.HOS.Services.BluetoothManager.BtmUser +{ + class IBtmUserCore : IpcService + { + public KEvent _bleScanEvent; + public int _bleScanEventHandle; + + public KEvent _bleConnectionEvent; + public int _bleConnectionEventHandle; + + public KEvent _bleServiceDiscoveryEvent; + public int _bleServiceDiscoveryEventHandle; + + public KEvent _bleMtuConfigEvent; + public int _bleMtuConfigEventHandle; + + public IBtmUserCore() { } + + [CommandCmif(0)] // 5.0.0+ + // AcquireBleScanEvent() -> (byte<1>, handle) + public ResultCode AcquireBleScanEvent(ServiceCtx context) + { + Result result = Result.Success; + + if (_bleScanEventHandle == 0) + { + _bleScanEvent = new KEvent(context.Device.System.KernelContext); + + result = context.Process.HandleTable.GenerateHandle(_bleScanEvent.ReadableEvent, out _bleScanEventHandle); + + if (result != Result.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.Error?.Print(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleScanEventHandle); + + context.ResponseData.Write(result == Result.Success ? 1 : 0); + + return ResultCode.Success; + } + + [CommandCmif(17)] // 5.0.0+ + // AcquireBleConnectionEvent() -> (byte<1>, handle) + public ResultCode AcquireBleConnectionEvent(ServiceCtx context) + { + Result result = Result.Success; + + if (_bleConnectionEventHandle == 0) + { + _bleConnectionEvent = new KEvent(context.Device.System.KernelContext); + + result = context.Process.HandleTable.GenerateHandle(_bleConnectionEvent.ReadableEvent, out _bleConnectionEventHandle); + + if (result != Result.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.Error?.Print(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleConnectionEventHandle); + + context.ResponseData.Write(result == Result.Success ? 1 : 0); + + return ResultCode.Success; + } + + [CommandCmif(26)] // 5.0.0+ + // AcquireBleServiceDiscoveryEvent() -> (byte<1>, handle) + public ResultCode AcquireBleServiceDiscoveryEvent(ServiceCtx context) + { + Result result = Result.Success; + + if (_bleServiceDiscoveryEventHandle == 0) + { + _bleServiceDiscoveryEvent = new KEvent(context.Device.System.KernelContext); + + result = context.Process.HandleTable.GenerateHandle(_bleServiceDiscoveryEvent.ReadableEvent, out _bleServiceDiscoveryEventHandle); + + if (result != Result.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.Error?.Print(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleServiceDiscoveryEventHandle); + + context.ResponseData.Write(result == Result.Success ? 1 : 0); + + return ResultCode.Success; + } + + [CommandCmif(33)] // 5.0.0+ + // AcquireBleMtuConfigEvent() -> (byte<1>, handle) + public ResultCode AcquireBleMtuConfigEvent(ServiceCtx context) + { + Result result = Result.Success; + + if (_bleMtuConfigEventHandle == 0) + { + _bleMtuConfigEvent = new KEvent(context.Device.System.KernelContext); + + result = context.Process.HandleTable.GenerateHandle(_bleMtuConfigEvent.ReadableEvent, out _bleMtuConfigEventHandle); + + if (result != Result.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.Error?.Print(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleMtuConfigEventHandle); + + context.ResponseData.Write(result == Result.Success ? 1 : 0); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtm.cs b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtm.cs new file mode 100644 index 00000000..87f4706a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtm.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + [Service("btm")] + class IBtm : IpcService + { + public IBtm(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmDebug.cs b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmDebug.cs new file mode 100644 index 00000000..20e2b04e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmDebug.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + [Service("btm:dbg")] + class IBtmDebug : IpcService + { + public IBtmDebug(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs new file mode 100644 index 00000000..0efab876 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + [Service("btm:sys")] + class IBtmSystem : IpcService + { + public IBtmSystem(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmUser.cs b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmUser.cs new file mode 100644 index 00000000..78f8fd8f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmUser.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.BluetoothManager.BtmUser; + +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + [Service("btm:u")] // 5.0.0+ + class IBtmUser : IpcService + { + public IBtmUser(ServiceCtx context) { } + + [CommandCmif(0)] // 5.0.0+ + // GetCore() -> object + public ResultCode GetCore(ServiceCtx context) + { + MakeObject(context, new IBtmUserCore()); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/ResultCode.cs new file mode 100644 index 00000000..f729a688 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/ResultCode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.BluetoothManager +{ + enum ResultCode + { + ModuleId = 143, + ErrorCodeShift = 9, + + Success = 0, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs b/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs new file mode 100644 index 00000000..bf0c7e9d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs @@ -0,0 +1,140 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Caps.Types; +using SkiaSharp; +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; + +namespace Ryujinx.HLE.HOS.Services.Caps +{ + class CaptureManager + { + private readonly string _sdCardPath; + + private uint _shimLibraryVersion; + + public CaptureManager(Switch device) + { + _sdCardPath = FileSystem.VirtualFileSystem.GetSdCardPath(); + } + + public ResultCode SetShimLibraryVersion(ServiceCtx context) + { + ulong shimLibraryVersion = context.RequestData.ReadUInt64(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong appletResourceUserId = context.RequestData.ReadUInt64(); +#pragma warning restore IDE0059 + + // TODO: Service checks if the pid is present in an internal list and returns ResultCode.BlacklistedPid if it is. + // The list contents needs to be determined. + + ResultCode resultCode = ResultCode.OutOfRange; + + if (shimLibraryVersion != 0) + { + if (_shimLibraryVersion == shimLibraryVersion) + { + resultCode = ResultCode.Success; + } + else if (_shimLibraryVersion != 0) + { + resultCode = ResultCode.ShimLibraryVersionAlreadySet; + } + else if (shimLibraryVersion == 1) + { + resultCode = ResultCode.Success; + + _shimLibraryVersion = 1; + } + } + + return resultCode; + } + + public ResultCode SaveScreenShot(byte[] screenshotData, ulong appletResourceUserId, ulong titleId, out ApplicationAlbumEntry applicationAlbumEntry) + { + applicationAlbumEntry = default; + + if (screenshotData.Length == 0) + { + return ResultCode.NullInputBuffer; + } + + /* + // NOTE: On our current implementation, appletResourceUserId starts at 0, disable it for now. + if (appletResourceUserId == 0) + { + return ResultCode.InvalidArgument; + } + */ + + /* + // Doesn't occur in our case. + if (applicationAlbumEntry == null) + { + return ResultCode.NullOutputBuffer; + } + */ + + if (screenshotData.Length >= 0x384000) + { + DateTime currentDateTime = DateTime.Now; + + applicationAlbumEntry = new ApplicationAlbumEntry() + { + Size = (ulong)Unsafe.SizeOf(), + TitleId = titleId, + AlbumFileDateTime = new AlbumFileDateTime() + { + Year = (ushort)currentDateTime.Year, + Month = (byte)currentDateTime.Month, + Day = (byte)currentDateTime.Day, + Hour = (byte)currentDateTime.Hour, + Minute = (byte)currentDateTime.Minute, + Second = (byte)currentDateTime.Second, + UniqueId = 0, + }, + AlbumStorage = AlbumStorage.Sd, + ContentType = ContentType.Screenshot, + Padding = new Array5(), + Unknown0x1f = 1, + }; + + // NOTE: The hex hash is a HMAC-SHA256 (first 32 bytes) using a hardcoded secret key over the titleId, we can simulate it by hashing the titleId instead. + string hash = Convert.ToHexString(SHA256.HashData(BitConverter.GetBytes(titleId))).Remove(0x20); + string folderPath = Path.Combine(_sdCardPath, "Nintendo", "Album", currentDateTime.Year.ToString("00"), currentDateTime.Month.ToString("00"), currentDateTime.Day.ToString("00")); + string filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash); + + // TODO: Handle that using the FS service implementation and return the right error code instead of throwing exceptions. + Directory.CreateDirectory(folderPath); + + while (File.Exists(filePath)) + { + applicationAlbumEntry.AlbumFileDateTime.UniqueId++; + + filePath = GenerateFilePath(folderPath, applicationAlbumEntry, currentDateTime, hash); + } + + // NOTE: The saved JPEG file doesn't have the limitation in the extra EXIF data. + using var bitmap = new SKBitmap(new SKImageInfo(1280, 720, SKColorType.Rgba8888)); + Marshal.Copy(screenshotData, 0, bitmap.GetPixels(), screenshotData.Length); + using var data = bitmap.Encode(SKEncodedImageFormat.Jpeg, 80); + using var file = File.OpenWrite(filePath); + data.SaveTo(file); + + return ResultCode.Success; + } + + return ResultCode.NullInputBuffer; + } + + private string GenerateFilePath(string folderPath, ApplicationAlbumEntry applicationAlbumEntry, DateTime currentDateTime, string hash) + { + string fileName = $"{currentDateTime:yyyyMMddHHmmss}{applicationAlbumEntry.AlbumFileDateTime.UniqueId:00}-{hash}.jpg"; + + return Path.Combine(folderPath, fileName); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs new file mode 100644 index 00000000..de62b08d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:a")] + class IAlbumAccessorService : IpcService + { + public IAlbumAccessorService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs new file mode 100644 index 00000000..5fbba310 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs @@ -0,0 +1,69 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Caps.Types; + +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:u")] + class IAlbumApplicationService : IpcService + { + public IAlbumApplicationService(ServiceCtx context) { } + + [CommandCmif(32)] // 7.0.0+ + // SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId) + public ResultCode SetShimLibraryVersion(ServiceCtx context) + { + return context.Device.System.CaptureManager.SetShimLibraryVersion(context); + } + + [CommandCmif(102)] + // GetAlbumFileList0AafeAruidDeprecated(pid, u16 content_type, u64 start_time, u64 end_time, nn::applet::AppletResourceUserId) -> (u64 count, buffer) + public ResultCode GetAlbumFileList0AafeAruidDeprecated(ServiceCtx context) + { + // NOTE: ApplicationAlbumFileEntry size is 0x30. + return GetAlbumFileList(context); + } + + [CommandCmif(142)] + // GetAlbumFileList3AaeAruid(pid, u16 content_type, u64 start_time, u64 end_time, nn::applet::AppletResourceUserId) -> (u64 count, buffer) + public ResultCode GetAlbumFileList3AaeAruid(ServiceCtx context) + { + // NOTE: ApplicationAlbumFileEntry size is 0x20. + return GetAlbumFileList(context); + } + + private ResultCode GetAlbumFileList(ServiceCtx context) + { + ResultCode resultCode = ResultCode.Success; + ulong count = 0; + + ContentType contentType = (ContentType)context.RequestData.ReadUInt16(); + ulong startTime = context.RequestData.ReadUInt64(); + ulong endTime = context.RequestData.ReadUInt64(); + + context.RequestData.ReadUInt16(); // Alignment. + + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + ulong applicationAlbumFileEntryPosition = context.Request.ReceiveBuff[0].Position; + ulong applicationAlbumFileEntrySize = context.Request.ReceiveBuff[0].Size; + + MemoryHelper.FillWithZeros(context.Memory, applicationAlbumFileEntryPosition, (int)applicationAlbumFileEntrySize); + + if (contentType > ContentType.Unknown || contentType == ContentType.ExtraMovie) + { + resultCode = ResultCode.InvalidContentType; + } + + // TODO: Service checks if the pid is present in an internal list and returns ResultCode.BlacklistedPid if it is. + // The list contents needs to be determined. + // Service populate the buffer with a ApplicationAlbumFileEntry related to the pid. + + Logger.Stub?.PrintStub(LogClass.ServiceCaps, new { contentType, startTime, endTime, appletResourceUserId }); + + context.ResponseData.Write(count); + + return resultCode; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs new file mode 100644 index 00000000..4376c4d1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:c")] + class IAlbumControlService : IpcService + { + public IAlbumControlService(ServiceCtx context) { } + + [CommandCmif(33)] // 7.0.0+ + // SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId) + public ResultCode SetShimLibraryVersion(ServiceCtx context) + { + return context.Device.System.CaptureManager.SetShimLibraryVersion(context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs new file mode 100644 index 00000000..0dad46c1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs @@ -0,0 +1,105 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Caps.Types; + +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:su")] // 6.0.0+ + class IScreenShotApplicationService : IpcService + { + public IScreenShotApplicationService(ServiceCtx context) { } + + [CommandCmif(32)] // 7.0.0+ + // SetShimLibraryVersion(pid, u64, nn::applet::AppletResourceUserId) + public ResultCode SetShimLibraryVersion(ServiceCtx context) + { + return context.Device.System.CaptureManager.SetShimLibraryVersion(context); + } + + [CommandCmif(203)] + // SaveScreenShotEx0(bytes<0x40> ScreenShotAttribute, u32 unknown, u64 AppletResourceUserId, pid, buffer ScreenshotData) -> bytes<0x20> ApplicationAlbumEntry + public ResultCode SaveScreenShotEx0(ServiceCtx context) + { + // TODO: Use the ScreenShotAttribute. +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ScreenShotAttribute screenShotAttribute = context.RequestData.ReadStruct(); + + uint unknown = context.RequestData.ReadUInt32(); +#pragma warning restore IDE0059 + ulong appletResourceUserId = context.RequestData.ReadUInt64(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong pidPlaceholder = context.RequestData.ReadUInt64(); +#pragma warning restore IDE0059 + + ulong screenshotDataPosition = context.Request.SendBuff[0].Position; + ulong screenshotDataSize = context.Request.SendBuff[0].Size; + + byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); + + ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); + + context.ResponseData.WriteStruct(applicationAlbumEntry); + + return resultCode; + } + + [CommandCmif(205)] // 8.0.0+ + // SaveScreenShotEx1(bytes<0x40> ScreenShotAttribute, u32 unknown, u64 AppletResourceUserId, pid, buffer ApplicationData, buffer ScreenshotData) -> bytes<0x20> ApplicationAlbumEntry + public ResultCode SaveScreenShotEx1(ServiceCtx context) + { + // TODO: Use the ScreenShotAttribute. + _ = context.RequestData.ReadStruct(); + + _ = context.RequestData.ReadUInt32(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + _ = context.RequestData.ReadUInt64(); + + ulong applicationDataPosition = context.Request.SendBuff[0].Position; + ulong applicationDataSize = context.Request.SendBuff[0].Size; + + ulong screenshotDataPosition = context.Request.SendBuff[1].Position; + ulong screenshotDataSize = context.Request.SendBuff[1].Size; + + + // TODO: Parse the application data: At 0x00 it's UserData (Size of 0x400), at 0x404 it's a uint UserDataSize (Always empty for now). + _ = context.Memory.GetSpan(applicationDataPosition, (int)applicationDataSize).ToArray(); + + byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); + + ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); + + context.ResponseData.WriteStruct(applicationAlbumEntry); + + return resultCode; + } + + [CommandCmif(210)] + // SaveScreenShotEx2(bytes<0x40> ScreenShotAttribute, u32 unknown, u64 AppletResourceUserId, buffer UserIdList, buffer ScreenshotData) -> bytes<0x20> ApplicationAlbumEntry + public ResultCode SaveScreenShotEx2(ServiceCtx context) + { + // TODO: Use the ScreenShotAttribute. + _ = context.RequestData.ReadStruct(); + + _ = context.RequestData.ReadUInt32(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + ulong userIdListPosition = context.Request.SendBuff[0].Position; + ulong userIdListSize = context.Request.SendBuff[0].Size; + + ulong screenshotDataPosition = context.Request.SendBuff[1].Position; + ulong screenshotDataSize = context.Request.SendBuff[1].Size; + + + // TODO: Parse the UserIdList. + _ = context.Memory.GetSpan(userIdListPosition, (int)userIdListSize).ToArray(); + + byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); + + ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); + + context.ResponseData.WriteStruct(applicationAlbumEntry); + + return resultCode; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs new file mode 100644 index 00000000..77876b49 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:sc")] + class IScreenShotControlService : IpcService + { + public IScreenShotControlService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs new file mode 100644 index 00000000..ab921e08 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + [Service("caps:ss")] // 2.0.0+ + class IScreenshotService : IpcService + { + public IScreenshotService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs new file mode 100644 index 00000000..7617ae9d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Services.Caps +{ + enum ResultCode + { + ModuleId = 206, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArgument = (2 << ErrorCodeShift) | ModuleId, + ShimLibraryVersionAlreadySet = (7 << ErrorCodeShift) | ModuleId, + OutOfRange = (8 << ErrorCodeShift) | ModuleId, + InvalidContentType = (14 << ErrorCodeShift) | ModuleId, + NullOutputBuffer = (141 << ErrorCodeShift) | ModuleId, + NullInputBuffer = (142 << ErrorCodeShift) | ModuleId, + BlacklistedPid = (822 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs new file mode 100644 index 00000000..00c51452 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8)] + struct AlbumFileDateTime + { + public ushort Year; + public byte Month; + public byte Day; + public byte Hour; + public byte Minute; + public byte Second; + public byte UniqueId; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs new file mode 100644 index 00000000..26afbd38 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + enum AlbumImageOrientation : uint + { + Degrees0, + Degrees90, + Degrees180, + Degrees270, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs new file mode 100644 index 00000000..9ae9767f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + enum AlbumStorage : byte + { + Nand, + Sd, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs new file mode 100644 index 00000000..fac5e58f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs @@ -0,0 +1,17 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20)] + struct ApplicationAlbumEntry + { + public ulong Size; + public ulong TitleId; + public AlbumFileDateTime AlbumFileDateTime; + public AlbumStorage AlbumStorage; + public ContentType ContentType; + public Array5 Padding; + public byte Unknown0x1f; // Always 1 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs new file mode 100644 index 00000000..7b1d4062 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + enum ContentType : byte + { + Screenshot, + Movie, + ExtraMovie, + Unknown, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs new file mode 100644 index 00000000..d932f1b8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Caps.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40)] + struct ScreenShotAttribute + { + public uint Unknown0x00; // Always 0 + public AlbumImageOrientation AlbumImageOrientation; + public uint Unknown0x08; // Always 0 + public uint Unknown0x0C; // Always 1 + public Array30 Unknown0x10; // Always 0 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Cec/ICecManager.cs b/src/Ryujinx.HLE/HOS/Services/Cec/ICecManager.cs new file mode 100644 index 00000000..044f5b1e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Cec/ICecManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Cec +{ + [Service("cec-mgr")] + class ICecManager : IpcService + { + public ICecManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/CommandCmifAttribute.cs b/src/Ryujinx.HLE/HOS/Services/CommandCmifAttribute.cs new file mode 100644 index 00000000..17a5960e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/CommandCmifAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + class CommandCmifAttribute : Attribute + { + public readonly int Id; + + public CommandCmifAttribute(int id) => Id = id; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/CommandTIpcAttribute.cs b/src/Ryujinx.HLE/HOS/Services/CommandTIpcAttribute.cs new file mode 100644 index 00000000..b9c60354 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/CommandTIpcAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + class CommandTipcAttribute : Attribute + { + public readonly int Id; + + public CommandTipcAttribute(int id) => Id = id; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/DisposableIpcService.cs b/src/Ryujinx.HLE/HOS/Services/DisposableIpcService.cs new file mode 100644 index 00000000..b06158a8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/DisposableIpcService.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services +{ + abstract class DisposableIpcService : IpcService, IDisposable + { + private int _disposeState; + + public DisposableIpcService(ServerBase server = null) : base(server) { } + + protected abstract void Dispose(bool isDisposing); + + public void Dispose() + { + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/DummyService.cs b/src/Ryujinx.HLE/HOS/Services/DummyService.cs new file mode 100644 index 00000000..d890f574 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/DummyService.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services +{ + class DummyService : IpcService + { + public string ServiceName { get; set; } + + public DummyService(string serviceName) + { + ServiceName = serviceName; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ectx/IReaderForSystem.cs b/src/Ryujinx.HLE/HOS/Services/Ectx/IReaderForSystem.cs new file mode 100644 index 00000000..fc2b35aa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ectx/IReaderForSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ectx +{ + [Service("ectx:r")] // 11.0.0+ + class IReaderForSystem : IpcService + { + public IReaderForSystem(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForApplication.cs b/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForApplication.cs new file mode 100644 index 00000000..cf2cdddc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForApplication.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ectx +{ + [Service("ectx:aw")] // 11.0.0+ + class IWriterForApplication : IpcService + { + public IWriterForApplication(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForSystem.cs b/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForSystem.cs new file mode 100644 index 00000000..468a5a5b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ectx +{ + [Service("ectx:w")] // 11.0.0+ + class IWriterForSystem : IpcService + { + public IWriterForSystem(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs b/src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs new file mode 100644 index 00000000..bd0880db --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Erpt +{ + [Service("erpt:c")] + class IContext : IpcService + { + public IContext(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Erpt/ISession.cs b/src/Ryujinx.HLE/HOS/Services/Erpt/ISession.cs new file mode 100644 index 00000000..cd9006a2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Erpt/ISession.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Erpt +{ + [Service("erpt:r")] + class ISession : IpcService + { + public ISession(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs b/src/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs new file mode 100644 index 00000000..48656552 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Es +{ + [Service("es")] + class IETicketService : IpcService + { + public IETicketService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Eupld/IControl.cs b/src/Ryujinx.HLE/HOS/Services/Eupld/IControl.cs new file mode 100644 index 00000000..db79d471 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Eupld/IControl.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Eupld +{ + [Service("eupld:c")] + class IControl : IpcService + { + public IControl(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Eupld/IRequest.cs b/src/Ryujinx.HLE/HOS/Services/Eupld/IRequest.cs new file mode 100644 index 00000000..7c6f9c81 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Eupld/IRequest.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Eupld +{ + [Service("eupld:r")] + class IRequest : IpcService + { + public IRequest(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fatal/IPrivateService.cs b/src/Ryujinx.HLE/HOS/Services/Fatal/IPrivateService.cs new file mode 100644 index 00000000..3c48f519 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fatal/IPrivateService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Fatal +{ + [Service("fatal:p")] + class IPrivateService : IpcService + { + public IPrivateService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs b/src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs new file mode 100644 index 00000000..15507774 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs @@ -0,0 +1,147 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Fatal.Types; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Fatal +{ + [Service("fatal:u")] + class IService : IpcService + { + public IService(ServiceCtx context) { } + + [CommandCmif(0)] + // ThrowFatal(u64 result_code, u64 pid) + public ResultCode ThrowFatal(ServiceCtx context) + { + ResultCode resultCode = (ResultCode)context.RequestData.ReadUInt64(); + ulong pid = context.Request.HandleDesc.PId; + + return ThrowFatalWithCpuContextImpl(context, resultCode, pid, FatalPolicy.ErrorReportAndErrorScreen, null); + } + + [CommandCmif(1)] + // ThrowFatalWithPolicy(u64 result_code, u32 fatal_policy, u64 pid) + public ResultCode ThrowFatalWithPolicy(ServiceCtx context) + { + ResultCode resultCode = (ResultCode)context.RequestData.ReadUInt64(); + FatalPolicy fatalPolicy = (FatalPolicy)context.RequestData.ReadUInt32(); + ulong pid = context.Request.HandleDesc.PId; + + return ThrowFatalWithCpuContextImpl(context, resultCode, pid, fatalPolicy, null); + } + + [CommandCmif(2)] + // ThrowFatalWithCpuContext(u64 result_code, u32 fatal_policy, u64 pid, buffer cpu_context) + public ResultCode ThrowFatalWithCpuContext(ServiceCtx context) + { + ResultCode resultCode = (ResultCode)context.RequestData.ReadUInt64(); + FatalPolicy fatalPolicy = (FatalPolicy)context.RequestData.ReadUInt32(); + ulong pid = context.Request.HandleDesc.PId; + + ulong cpuContextPosition = context.Request.SendBuff[0].Position; + ulong cpuContextSize = context.Request.SendBuff[0].Size; + + ReadOnlySpan cpuContextData = context.Memory.GetSpan(cpuContextPosition, (int)cpuContextSize); + + return ThrowFatalWithCpuContextImpl(context, resultCode, pid, fatalPolicy, cpuContextData); + } + + private ResultCode ThrowFatalWithCpuContextImpl(ServiceCtx context, ResultCode resultCode, ulong pid, FatalPolicy fatalPolicy, ReadOnlySpan cpuContext) + { + StringBuilder errorReport = new(); + + errorReport.AppendLine(); + errorReport.AppendLine("ErrorReport log:"); + + errorReport.AppendLine($"\tTitleId: {context.Device.Processes.ActiveApplication.ProgramIdText}"); + errorReport.AppendLine($"\tPid: {pid}"); + errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}"); + errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}"); + + if (!cpuContext.IsEmpty) + { + errorReport.AppendLine("CPU Context:"); + + if (context.Device.Processes.ActiveApplication.Is64Bit) + { + CpuContext64 cpuContext64 = MemoryMarshal.Cast(cpuContext)[0]; + + errorReport.AppendLine($"\tStartAddress: 0x{cpuContext64.StartAddress:x16}"); + errorReport.AppendLine($"\tRegisterSetFlags: {cpuContext64.RegisterSetFlags}"); + + if (cpuContext64.StackTraceSize > 0) + { + errorReport.AppendLine("\tStackTrace:"); + + for (int i = 0; i < cpuContext64.StackTraceSize; i++) + { + errorReport.AppendLine($"\t\t0x{cpuContext64.StackTrace[i]:x16}"); + } + } + + errorReport.AppendLine("\tRegisters:"); + + for (int i = 0; i < cpuContext64.X.Length; i++) + { + errorReport.AppendLine($"\t\tX[{i:d2}]:\t0x{cpuContext64.X[i]:x16}"); + } + + errorReport.AppendLine(); + errorReport.AppendLine($"\t\tFP:\t0x{cpuContext64.FP:x16}"); + errorReport.AppendLine($"\t\tLR:\t0x{cpuContext64.LR:x16}"); + errorReport.AppendLine($"\t\tSP:\t0x{cpuContext64.SP:x16}"); + errorReport.AppendLine($"\t\tPC:\t0x{cpuContext64.PC:x16}"); + errorReport.AppendLine($"\t\tPState:\t0x{cpuContext64.PState:x16}"); + errorReport.AppendLine($"\t\tAfsr0:\t0x{cpuContext64.Afsr0:x16}"); + errorReport.AppendLine($"\t\tAfsr1:\t0x{cpuContext64.Afsr1:x16}"); + errorReport.AppendLine($"\t\tEsr:\t0x{cpuContext64.Esr:x16}"); + errorReport.AppendLine($"\t\tFar:\t0x{cpuContext64.Far:x16}"); + } + else + { + CpuContext32 cpuContext32 = MemoryMarshal.Cast(cpuContext)[0]; + + errorReport.AppendLine($"\tStartAddress: 0x{cpuContext32.StartAddress:16}"); + errorReport.AppendLine($"\tRegisterSetFlags: {cpuContext32.RegisterSetFlags}"); + + if (cpuContext32.StackTraceSize > 0) + { + errorReport.AppendLine("\tStackTrace:"); + + for (int i = 0; i < cpuContext32.StackTraceSize; i++) + { + errorReport.AppendLine($"\t\t0x{cpuContext32.StackTrace[i]:x16}"); + } + } + + errorReport.AppendLine("\tRegisters:"); + + for (int i = 0; i < cpuContext32.X.Length; i++) + { + errorReport.AppendLine($"\t\tX[{i:d2}]:\t0x{cpuContext32.X[i]:x16}"); + } + + errorReport.AppendLine(); + errorReport.AppendLine($"\t\tFP:\t0x{cpuContext32.FP:x16}"); + errorReport.AppendLine($"\t\tFP:\t0x{cpuContext32.IP:x16}"); + errorReport.AppendLine($"\t\tSP:\t0x{cpuContext32.SP:x16}"); + errorReport.AppendLine($"\t\tLR:\t0x{cpuContext32.LR:x16}"); + errorReport.AppendLine($"\t\tPC:\t0x{cpuContext32.PC:x16}"); + errorReport.AppendLine($"\t\tPState:\t0x{cpuContext32.PState:x16}"); + errorReport.AppendLine($"\t\tAfsr0:\t0x{cpuContext32.Afsr0:x16}"); + errorReport.AppendLine($"\t\tAfsr1:\t0x{cpuContext32.Afsr1:x16}"); + errorReport.AppendLine($"\t\tEsr:\t0x{cpuContext32.Esr:x16}"); + errorReport.AppendLine($"\t\tFar:\t0x{cpuContext32.Far:x16}"); + } + } + + Logger.Info?.Print(LogClass.ServiceFatal, errorReport.ToString()); + + context.Device.System.KernelContext.Syscall.Break((ulong)resultCode); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext32.cs b/src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext32.cs new file mode 100644 index 00000000..8429774e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext32.cs @@ -0,0 +1,25 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.HLE.HOS.Services.Fatal.Types +{ + public struct CpuContext32 + { + public Array11 X; + public uint FP; + public uint IP; + public uint SP; + public uint LR; + public uint PC; + + public uint PState; + public uint Afsr0; + public uint Afsr1; + public uint Esr; + public uint Far; + + public Array32 StackTrace; + public uint StackTraceSize; + public uint StartAddress; + public uint RegisterSetFlags; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext64.cs b/src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext64.cs new file mode 100644 index 00000000..17b5036d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext64.cs @@ -0,0 +1,24 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.HLE.HOS.Services.Fatal.Types +{ + public struct CpuContext64 + { + public Array29 X; + public ulong FP; + public ulong LR; + public ulong SP; + public ulong PC; + + public ulong PState; + public ulong Afsr0; + public ulong Afsr1; + public ulong Esr; + public ulong Far; + + public Array32 StackTrace; + public ulong StartAddress; + public ulong RegisterSetFlags; + public uint StackTraceSize; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fatal/Types/FatalPolicy.cs b/src/Ryujinx.HLE/HOS/Services/Fatal/Types/FatalPolicy.cs new file mode 100644 index 00000000..b0da0e37 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fatal/Types/FatalPolicy.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Fatal.Types +{ + enum FatalPolicy + { + ErrorReportAndErrorScreen, + ErrorReport, + ErrorScreen, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs new file mode 100644 index 00000000..20ffb996 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs @@ -0,0 +1,164 @@ +using LibHac; +using LibHac.Common; +using LibHac.Common.Keys; +using LibHac.Fs; +using LibHac.FsSrv.Impl; +using LibHac.FsSrv.Sf; +using LibHac.FsSystem; +using LibHac.Spl; +using LibHac.Tools.Es; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using System; +using System.IO; +using System.Runtime.InteropServices; +using Path = System.IO.Path; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + static class FileSystemProxyHelper + { + public static ResultCode OpenNsp(ServiceCtx context, string pfsPath, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + try + { + LocalStorage storage = new(pfsPath, FileAccess.Read, FileMode.Open); + var pfs = new PartitionFileSystem(); + using SharedRef nsp = new(pfs); + pfs.Initialize(storage).ThrowIfFailure(); + + ImportTitleKeysFromNsp(nsp.Get, context.Device.System.KeySet); + + using SharedRef adapter = FileSystemInterfaceAdapter.CreateShared(ref nsp.Ref, true); + + openedFileSystem = new IFileSystem(ref adapter.Ref); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + + return ResultCode.Success; + } + + public static ResultCode OpenNcaFs(ServiceCtx context, string ncaPath, LibHac.Fs.IStorage ncaStorage, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + try + { + Nca nca = new(context.Device.System.KeySet, ncaStorage); + + if (!nca.SectionExists(NcaSectionType.Data)) + { + return ResultCode.PartitionNotFound; + } + + LibHac.Fs.Fsa.IFileSystem fileSystem = nca.OpenFileSystem(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); + using var sharedFs = new SharedRef(fileSystem); + + using SharedRef adapter = FileSystemInterfaceAdapter.CreateShared(ref sharedFs.Ref, true); + + openedFileSystem = new IFileSystem(ref adapter.Ref); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + + return ResultCode.Success; + } + + public static ResultCode OpenFileSystemFromInternalFile(ServiceCtx context, string fullPath, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + DirectoryInfo archivePath = new DirectoryInfo(fullPath).Parent; + + while (string.IsNullOrWhiteSpace(archivePath.Extension)) + { + archivePath = archivePath.Parent; + } + + if (archivePath.Extension == ".nsp" && File.Exists(archivePath.FullName)) + { + FileStream pfsFile = new( + archivePath.FullName.TrimEnd(Path.DirectorySeparatorChar), + FileMode.Open, + FileAccess.Read); + + try + { + PartitionFileSystem nsp = new(); + nsp.Initialize(pfsFile.AsStorage()).ThrowIfFailure(); + + ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet); + + string filename = fullPath.Replace(archivePath.FullName, string.Empty).TrimStart('\\'); + + using var ncaFile = new UniqueRef(); + + Result result = nsp.OpenFile(ref ncaFile.Ref, filename.ToU8Span(), OpenMode.Read); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + return OpenNcaFs(context, fullPath, ncaFile.Release().AsStorage(), out openedFileSystem); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + } + + return ResultCode.PathDoesNotExist; + } + + public static void ImportTitleKeysFromNsp(LibHac.Fs.Fsa.IFileSystem nsp, KeySet keySet) + { + foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik")) + { + using var ticketFile = new UniqueRef(); + + Result result = nsp.OpenFile(ref ticketFile.Ref, ticketEntry.FullPath.ToU8Span(), OpenMode.Read); + + if (result.IsSuccess()) + { + Ticket ticket = new(ticketFile.Get.AsStream()); + var titleKey = ticket.GetTitleKey(keySet); + + if (titleKey != null) + { + keySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(titleKey)); + } + } + } + } + + public static ref readonly FspPath GetFspPath(ServiceCtx context, int index = 0) + { + ulong position = context.Request.PtrBuff[index].Position; + ulong size = context.Request.PtrBuff[index].Size; + + ReadOnlySpan buffer = context.Memory.GetSpan(position, (int)size); + ReadOnlySpan fspBuffer = MemoryMarshal.Cast(buffer); + + return ref fspBuffer[0]; + } + + public static ref readonly LibHac.FsSrv.Sf.Path GetSfPath(ServiceCtx context, int index = 0) + { + ulong position = context.Request.PtrBuff[index].Position; + ulong size = context.Request.PtrBuff[index].Size; + + ReadOnlySpan buffer = context.Memory.GetSpan(position, (int)size); + ReadOnlySpan pathBuffer = MemoryMarshal.Cast(buffer); + + return ref pathBuffer[0]; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs new file mode 100644 index 00000000..70d3a6bd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs @@ -0,0 +1,50 @@ +using LibHac; +using LibHac.Common; +using LibHac.Sf; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + class IDirectory : DisposableIpcService + { + private SharedRef _baseDirectory; + + public IDirectory(ref SharedRef directory) + { + _baseDirectory = SharedRef.CreateMove(ref directory); + } + + [CommandCmif(0)] + // Read() -> (u64 count, buffer entries) + public ResultCode Read(ServiceCtx context) + { + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + using var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true); + Result result = _baseDirectory.Get.Read(out long entriesRead, new OutBuffer(region.Memory.Span)); + + context.ResponseData.Write(entriesRead); + + return (ResultCode)result.Value; + } + + [CommandCmif(1)] + // GetEntryCount() -> u64 + public ResultCode GetEntryCount(ServiceCtx context) + { + Result result = _baseDirectory.Get.GetEntryCount(out long entryCount); + + context.ResponseData.Write(entryCount); + + return (ResultCode)result.Value; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseDirectory.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs new file mode 100644 index 00000000..dcc34a75 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs @@ -0,0 +1,93 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Sf; +using Ryujinx.Common; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + class IFile : DisposableIpcService + { + private SharedRef _baseFile; + + public IFile(ref SharedRef baseFile) + { + _baseFile = SharedRef.CreateMove(ref baseFile); + } + + [CommandCmif(0)] + // Read(u32 readOption, u64 offset, u64 size) -> (u64 out_size, buffer out_buf) + public ResultCode Read(ServiceCtx context) + { + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + ReadOption readOption = context.RequestData.ReadStruct(); + context.RequestData.BaseStream.Position += 4; + + long offset = context.RequestData.ReadInt64(); + long size = context.RequestData.ReadInt64(); + + using var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true); + Result result = _baseFile.Get.Read(out long bytesRead, offset, new OutBuffer(region.Memory.Span), size, readOption); + + context.ResponseData.Write(bytesRead); + + return (ResultCode)result.Value; + } + + [CommandCmif(1)] + // Write(u32 writeOption, u64 offset, u64 size, buffer) + public ResultCode Write(ServiceCtx context) + { + ulong position = context.Request.SendBuff[0].Position; + + WriteOption writeOption = context.RequestData.ReadStruct(); + context.RequestData.BaseStream.Position += 4; + + long offset = context.RequestData.ReadInt64(); + long size = context.RequestData.ReadInt64(); + + byte[] data = new byte[context.Request.SendBuff[0].Size]; + + context.Memory.Read(position, data); + + return (ResultCode)_baseFile.Get.Write(offset, new InBuffer(data), size, writeOption).Value; + } + + [CommandCmif(2)] + // Flush() + public ResultCode Flush(ServiceCtx context) + { + return (ResultCode)_baseFile.Get.Flush().Value; + } + + [CommandCmif(3)] + // SetSize(u64 size) + public ResultCode SetSize(ServiceCtx context) + { + long size = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFile.Get.SetSize(size).Value; + } + + [CommandCmif(4)] + // GetSize() -> u64 fileSize + public ResultCode GetSize(ServiceCtx context) + { + Result result = _baseFile.Get.GetSize(out long size); + + context.ResponseData.Write(size); + + return (ResultCode)result.Value; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseFile.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs new file mode 100644 index 00000000..e19d1791 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs @@ -0,0 +1,231 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using Ryujinx.Common.Logging; +using Path = LibHac.FsSrv.Sf.Path; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + class IFileSystem : DisposableIpcService + { + private SharedRef _fileSystem; + + public IFileSystem(ref SharedRef provider) + { + _fileSystem = SharedRef.CreateMove(ref provider); + } + + public SharedRef GetBaseFileSystem() + { + return SharedRef.CreateCopy(in _fileSystem); + } + + [CommandCmif(0)] + // CreateFile(u32 createOption, u64 size, buffer, 0x19, 0x301> path) + public ResultCode CreateFile(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + int createOption = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; + + long size = context.RequestData.ReadInt64(); + + return (ResultCode)_fileSystem.Get.CreateFile(in name, size, createOption).Value; + } + + [CommandCmif(1)] + // DeleteFile(buffer, 0x19, 0x301> path) + public ResultCode DeleteFile(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + return (ResultCode)_fileSystem.Get.DeleteFile(in name).Value; + } + + [CommandCmif(2)] + // CreateDirectory(buffer, 0x19, 0x301> path) + public ResultCode CreateDirectory(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + return (ResultCode)_fileSystem.Get.CreateDirectory(in name).Value; + } + + [CommandCmif(3)] + // DeleteDirectory(buffer, 0x19, 0x301> path) + public ResultCode DeleteDirectory(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + return (ResultCode)_fileSystem.Get.DeleteDirectory(in name).Value; + } + + [CommandCmif(4)] + // DeleteDirectoryRecursively(buffer, 0x19, 0x301> path) + public ResultCode DeleteDirectoryRecursively(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + return (ResultCode)_fileSystem.Get.DeleteDirectoryRecursively(in name).Value; + } + + [CommandCmif(5)] + // RenameFile(buffer, 0x19, 0x301> oldPath, buffer, 0x19, 0x301> newPath) + public ResultCode RenameFile(ServiceCtx context) + { + ref readonly Path currentName = ref FileSystemProxyHelper.GetSfPath(context, index: 0); + ref readonly Path newName = ref FileSystemProxyHelper.GetSfPath(context, index: 1); + + return (ResultCode)_fileSystem.Get.RenameFile(in currentName, in newName).Value; + } + + [CommandCmif(6)] + // RenameDirectory(buffer, 0x19, 0x301> oldPath, buffer, 0x19, 0x301> newPath) + public ResultCode RenameDirectory(ServiceCtx context) + { + ref readonly Path currentName = ref FileSystemProxyHelper.GetSfPath(context, index: 0); + ref readonly Path newName = ref FileSystemProxyHelper.GetSfPath(context, index: 1); + + return (ResultCode)_fileSystem.Get.RenameDirectory(in currentName, in newName).Value; + } + + [CommandCmif(7)] + // GetEntryType(buffer, 0x19, 0x301> path) -> nn::fssrv::sf::DirectoryEntryType + public ResultCode GetEntryType(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + Result result = _fileSystem.Get.GetEntryType(out uint entryType, in name); + + context.ResponseData.Write((int)entryType); + + return (ResultCode)result.Value; + } + + [CommandCmif(8)] + // OpenFile(u32 mode, buffer, 0x19, 0x301> path) -> object file + public ResultCode OpenFile(ServiceCtx context) + { + uint mode = context.RequestData.ReadUInt32(); + + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + using var file = new SharedRef(); + + Result result = _fileSystem.Get.OpenFile(ref file.Ref, in name, mode); + + if (result.IsSuccess()) + { + IFile fileInterface = new(ref file.Ref); + + MakeObject(context, fileInterface); + } + + return (ResultCode)result.Value; + } + + [CommandCmif(9)] + // OpenDirectory(u32 filter_flags, buffer, 0x19, 0x301> path) -> object directory + public ResultCode OpenDirectory(ServiceCtx context) + { + uint mode = context.RequestData.ReadUInt32(); + + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + using var dir = new SharedRef(); + + Result result = _fileSystem.Get.OpenDirectory(ref dir.Ref, name, mode); + + if (result.IsSuccess()) + { + IDirectory dirInterface = new(ref dir.Ref); + + MakeObject(context, dirInterface); + } + + return (ResultCode)result.Value; + } + + [CommandCmif(10)] + // Commit() + public ResultCode Commit(ServiceCtx context) + { + ResultCode resultCode = (ResultCode)_fileSystem.Get.Commit().Value; + if (resultCode == ResultCode.PathAlreadyInUse) + { + Logger.Warning?.Print(LogClass.ServiceFs, "The file system is already in use by another process."); + } + + return resultCode; + } + + [CommandCmif(11)] + // GetFreeSpaceSize(buffer, 0x19, 0x301> path) -> u64 totalFreeSpace + public ResultCode GetFreeSpaceSize(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + Result result = _fileSystem.Get.GetFreeSpaceSize(out long size, in name); + + context.ResponseData.Write(size); + + return (ResultCode)result.Value; + } + + [CommandCmif(12)] + // GetTotalSpaceSize(buffer, 0x19, 0x301> path) -> u64 totalSize + public ResultCode GetTotalSpaceSize(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + Result result = _fileSystem.Get.GetTotalSpaceSize(out long size, in name); + + context.ResponseData.Write(size); + + return (ResultCode)result.Value; + } + + [CommandCmif(13)] + // CleanDirectoryRecursively(buffer, 0x19, 0x301> path) + public ResultCode CleanDirectoryRecursively(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + return (ResultCode)_fileSystem.Get.CleanDirectoryRecursively(in name).Value; + } + + [CommandCmif(14)] + // GetFileTimeStampRaw(buffer, 0x19, 0x301> path) -> bytes<0x20> timestamp + public ResultCode GetFileTimeStampRaw(ServiceCtx context) + { + ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context); + + Result result = _fileSystem.Get.GetFileTimeStampRaw(out FileTimeStampRaw timestamp, in name); + + context.ResponseData.Write(timestamp.Created); + context.ResponseData.Write(timestamp.Modified); + context.ResponseData.Write(timestamp.Accessed); + context.ResponseData.Write(1L); // Is valid? + + return (ResultCode)result.Value; + } + + [CommandCmif(16)] + public ResultCode GetFileSystemAttribute(ServiceCtx context) + { + Result result = _fileSystem.Get.GetFileSystemAttribute(out FileSystemAttribute attribute); + + context.ResponseData.Write(SpanHelpers.AsReadOnlyByteSpan(in attribute)); + + return (ResultCode)result.Value; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _fileSystem.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs new file mode 100644 index 00000000..4299a6c7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs @@ -0,0 +1,62 @@ +using LibHac; +using LibHac.Common; +using LibHac.Sf; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + class IStorage : DisposableIpcService + { + private SharedRef _baseStorage; + + public IStorage(ref SharedRef baseStorage) + { + _baseStorage = SharedRef.CreateMove(ref baseStorage); + } + + [CommandCmif(0)] + // Read(u64 offset, u64 length) -> buffer buffer + public ResultCode Read(ServiceCtx context) + { + ulong offset = context.RequestData.ReadUInt64(); + ulong size = context.RequestData.ReadUInt64(); + + if (context.Request.ReceiveBuff.Count > 0) + { + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + // Use smaller length to avoid overflows. + if (size > bufferLen) + { + size = bufferLen; + } + + using var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true); + Result result = _baseStorage.Get.Read((long)offset, new OutBuffer(region.Memory.Span), (long)size); + + return (ResultCode)result.Value; + } + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetSize() -> u64 size + public ResultCode GetSize(ServiceCtx context) + { + Result result = _baseStorage.Get.GetSize(out long size); + + context.ResponseData.Write(size); + + return (ResultCode)result.Value; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseStorage.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/LazyFile.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/LazyFile.cs new file mode 100644 index 00000000..a179e8e3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/LazyFile.cs @@ -0,0 +1,65 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy +{ + class LazyFile : LibHac.Fs.Fsa.IFile + { + private readonly LibHac.Fs.Fsa.IFileSystem _fs; + private readonly string _filePath; + private readonly UniqueRef _fileReference = new(); + private readonly FileInfo _fileInfo; + + public LazyFile(string filePath, string prefix, LibHac.Fs.Fsa.IFileSystem fs) + { + _fs = fs; + _filePath = filePath; + _fileInfo = new FileInfo(prefix + "/" + filePath); + } + + private void PrepareFile() + { + if (_fileReference.Get == null) + { + _fs.OpenFile(ref _fileReference.Ref, _filePath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + } + } + + protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) + { + PrepareFile(); + + return _fileReference.Get!.Read(out bytesRead, offset, destination); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + throw new NotSupportedException(); + } + + protected override Result DoFlush() + { + throw new NotSupportedException(); + } + + protected override Result DoSetSize(long size) + { + throw new NotSupportedException(); + } + + protected override Result DoGetSize(out long size) + { + size = _fileInfo.Length; + + return Result.Success; + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs b/src/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs new file mode 100644 index 00000000..49453e83 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs @@ -0,0 +1,58 @@ +using LibHac; +using LibHac.Common; + +using GameCardHandle = System.UInt32; + +namespace Ryujinx.HLE.HOS.Services.Fs +{ + class IDeviceOperator : DisposableIpcService + { + private SharedRef _baseOperator; + + public IDeviceOperator(ref SharedRef baseOperator) + { + _baseOperator = SharedRef.CreateMove(ref baseOperator); + } + + [CommandCmif(0)] + // IsSdCardInserted() -> b8 is_inserted + public ResultCode IsSdCardInserted(ServiceCtx context) + { + Result result = _baseOperator.Get.IsSdCardInserted(out bool isInserted); + + context.ResponseData.Write(isInserted); + + return (ResultCode)result.Value; + } + + [CommandCmif(200)] + // IsGameCardInserted() -> b8 is_inserted + public ResultCode IsGameCardInserted(ServiceCtx context) + { + Result result = _baseOperator.Get.IsGameCardInserted(out bool isInserted); + + context.ResponseData.Write(isInserted); + + return (ResultCode)result.Value; + } + + [CommandCmif(202)] + // GetGameCardHandle() -> u32 gamecard_handle + public ResultCode GetGameCardHandle(ServiceCtx context) + { + Result result = _baseOperator.Get.GetGameCardHandle(out GameCardHandle handle); + + context.ResponseData.Write(handle); + + return (ResultCode)result.Value; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseOperator.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs b/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs new file mode 100644 index 00000000..24dd1e9b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs @@ -0,0 +1,1441 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.FsSrv.Impl; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Sf; +using LibHac.Spl; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy; +using System; +using System.IO; +using static Ryujinx.HLE.Utilities.StringUtils; +using GameCardHandle = System.UInt32; +using IFileSystem = LibHac.FsSrv.Sf.IFileSystem; +using IStorage = LibHac.FsSrv.Sf.IStorage; + +namespace Ryujinx.HLE.HOS.Services.Fs +{ + [Service("fsp-srv")] + class IFileSystemProxy : DisposableIpcService + { + private SharedRef _baseFileSystemProxy; + private ulong _pid; + + public IFileSystemProxy(ServiceCtx context) : base(context.Device.System.FsServer) + { + var applicationClient = context.Device.System.LibHacHorizonManager.ApplicationClient; + _baseFileSystemProxy = applicationClient.Fs.Impl.GetFileSystemProxyServiceObject(); + } + + [CommandCmif(1)] + // SetCurrentProcess(u64, pid) + public ResultCode SetCurrentProcess(ServiceCtx context) + { + _pid = context.Request.HandleDesc.PId; + + return ResultCode.Success; + } + + [CommandCmif(8)] + // OpenFileSystemWithId(nn::fssrv::sf::FileSystemType filesystem_type, nn::ApplicationId tid, buffer, 0x19, 0x301> path) + // -> object contentFs + public ResultCode OpenFileSystemWithId(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + FileSystemType fileSystemType = (FileSystemType)context.RequestData.ReadInt32(); + ulong titleId = context.RequestData.ReadUInt64(); +#pragma warning restore IDE0059 + string switchPath = ReadUtf8String(context); + string fullPath = FileSystem.VirtualFileSystem.SwitchPathToSystemPath(switchPath); + + if (!File.Exists(fullPath)) + { + if (fullPath.Contains('.')) + { + ResultCode result = FileSystemProxyHelper.OpenFileSystemFromInternalFile(context, fullPath, out FileSystemProxy.IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; + } + + return ResultCode.PathDoesNotExist; + } + + FileStream fileStream = new(fullPath, FileMode.Open, FileAccess.Read); + string extension = System.IO.Path.GetExtension(fullPath); + + if (extension == ".nca") + { + ResultCode result = FileSystemProxyHelper.OpenNcaFs(context, fullPath, fileStream.AsStorage(), out FileSystemProxy.IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; + } + else if (extension == ".nsp") + { + ResultCode result = FileSystemProxyHelper.OpenNsp(context, fullPath, out FileSystemProxy.IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; + } + + return ResultCode.InvalidInput; + } + + [CommandCmif(11)] + // OpenBisFileSystem(nn::fssrv::sf::Partition partitionID, buffer, 0x19, 0x301>) -> object Bis + public ResultCode OpenBisFileSystem(ServiceCtx context) + { + BisPartitionId bisPartitionId = (BisPartitionId)context.RequestData.ReadInt32(); + + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); + using var fileSystem = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenBisFileSystem(ref fileSystem.Ref, in path, bisPartitionId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(12)] + // OpenBisStorage(u32 partitionId) -> object bisStorage + public ResultCode OpenBisStorage(ServiceCtx context) + { + BisPartitionId bisPartitionId = (BisPartitionId)context.RequestData.ReadInt32(); + using var storage = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenBisStorage(ref storage.Ref, bisPartitionId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IStorage(ref storage.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(13)] + // InvalidateBisCache() -> () + public ResultCode InvalidateBisCache(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.InvalidateBisCache().Value; + } + + [CommandCmif(18)] + // OpenSdCardFileSystem() -> object + public ResultCode OpenSdCardFileSystem(ServiceCtx context) + { + using var fileSystem = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenSdCardFileSystem(ref fileSystem.Ref); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(19)] + // FormatSdCardFileSystem() -> () + public ResultCode FormatSdCardFileSystem(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.FormatSdCardFileSystem().Value; + } + + [CommandCmif(21)] + // DeleteSaveDataFileSystem(u64 saveDataId) -> () + public ResultCode DeleteSaveDataFileSystem(ServiceCtx context) + { + ulong saveDataId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.DeleteSaveDataFileSystem(saveDataId).Value; + } + + [CommandCmif(22)] + // CreateSaveDataFileSystem(nn::fs::SaveDataAttribute attribute, nn::fs::SaveDataCreationInfo creationInfo, nn::fs::SaveDataMetaInfo metaInfo) -> () + public ResultCode CreateSaveDataFileSystem(ServiceCtx context) + { + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + SaveDataCreationInfo creationInfo = context.RequestData.ReadStruct(); + SaveDataMetaInfo metaInfo = context.RequestData.ReadStruct(); + + return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo).Value; + } + + [CommandCmif(23)] + // CreateSaveDataFileSystemBySystemSaveDataId(nn::fs::SaveDataAttribute attribute, nn::fs::SaveDataCreationInfo creationInfo) -> () + public ResultCode CreateSaveDataFileSystemBySystemSaveDataId(ServiceCtx context) + { + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + SaveDataCreationInfo creationInfo = context.RequestData.ReadStruct(); + + return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystemBySystemSaveDataId(in attribute, in creationInfo).Value; + } + + [CommandCmif(24)] + // RegisterSaveDataFileSystemAtomicDeletion(buffer saveDataIds) -> () + public ResultCode RegisterSaveDataFileSystemAtomicDeletion(ServiceCtx context) + { + byte[] saveIdBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, saveIdBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.RegisterSaveDataFileSystemAtomicDeletion(new InBuffer(saveIdBuffer)).Value; + } + + [CommandCmif(25)] + // DeleteSaveDataFileSystemBySaveDataSpaceId(u8 spaceId, u64 saveDataId) -> () + public ResultCode DeleteSaveDataFileSystemBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.DeleteSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId).Value; + } + + [CommandCmif(26)] + // FormatSdCardDryRun() -> () + public ResultCode FormatSdCardDryRun(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.FormatSdCardDryRun().Value; + } + + [CommandCmif(27)] + // IsExFatSupported() -> (u8 isSupported) + public ResultCode IsExFatSupported(ServiceCtx context) + { + Result result = _baseFileSystemProxy.Get.IsExFatSupported(out bool isSupported); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.Write(isSupported); + + return ResultCode.Success; + } + + [CommandCmif(28)] + // DeleteSaveDataFileSystemBySaveDataAttribute(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> () + public ResultCode DeleteSaveDataFileSystemBySaveDataAttribute(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + + return (ResultCode)_baseFileSystemProxy.Get.DeleteSaveDataFileSystemBySaveDataAttribute(spaceId, in attribute).Value; + } + + [CommandCmif(30)] + // OpenGameCardStorage(u32 handle, u32 partitionId) -> object + public ResultCode OpenGameCardStorage(ServiceCtx context) + { + GameCardHandle handle = context.RequestData.ReadUInt32(); + GameCardPartitionRaw partitionId = (GameCardPartitionRaw)context.RequestData.ReadInt32(); + using var storage = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenGameCardStorage(ref storage.Ref, handle, partitionId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IStorage(ref storage.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(31)] + // OpenGameCardFileSystem(u32 handle, u32 partitionId) -> object + public ResultCode OpenGameCardFileSystem(ServiceCtx context) + { + GameCardHandle handle = context.RequestData.ReadUInt32(); + GameCardPartition partitionId = (GameCardPartition)context.RequestData.ReadInt32(); + using var fileSystem = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenGameCardFileSystem(ref fileSystem.Ref, handle, partitionId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(32)] + // ExtendSaveDataFileSystem(u8 spaceId, u64 saveDataId, s64 dataSize, s64 journalSize) -> () + public ResultCode ExtendSaveDataFileSystem(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + long dataSize = context.RequestData.ReadInt64(); + long journalSize = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.ExtendSaveDataFileSystem(spaceId, saveDataId, dataSize, journalSize).Value; + } + + [CommandCmif(33)] + // DeleteCacheStorage(u16 index) -> () + public ResultCode DeleteCacheStorage(ServiceCtx context) + { + ushort index = context.RequestData.ReadUInt16(); + + return (ResultCode)_baseFileSystemProxy.Get.DeleteCacheStorage(index).Value; + } + + [CommandCmif(34)] + // GetCacheStorageSize(u16 index) -> (s64 dataSize, s64 journalSize) + public ResultCode GetCacheStorageSize(ServiceCtx context) + { + ushort index = context.RequestData.ReadUInt16(); + + Result result = _baseFileSystemProxy.Get.GetCacheStorageSize(out long dataSize, out long journalSize, index); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.Write(dataSize); + context.ResponseData.Write(journalSize); + + return ResultCode.Success; + } + + [CommandCmif(35)] + // CreateSaveDataFileSystemWithHashSalt(nn::fs::SaveDataAttribute attribute, nn::fs::SaveDataCreationInfo creationInfo, nn::fs::SaveDataMetaInfo metaInfo nn::fs::HashSalt hashSalt) -> () + public ResultCode CreateSaveDataFileSystemWithHashSalt(ServiceCtx context) + { + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + SaveDataCreationInfo creationInfo = context.RequestData.ReadStruct(); + SaveDataMetaInfo metaCreateInfo = context.RequestData.ReadStruct(); + HashSalt hashSalt = context.RequestData.ReadStruct(); + + return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, in metaCreateInfo, in hashSalt).Value; + } + + [CommandCmif(37)] // 14.0.0+ + // CreateSaveDataFileSystemWithCreationInfo2(buffer creationInfo) -> () + public ResultCode CreateSaveDataFileSystemWithCreationInfo2(ServiceCtx context) + { + byte[] creationInfoBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, creationInfoBuffer); + ref readonly SaveDataCreationInfo2 creationInfo = ref SpanHelpers.AsReadOnlyStruct(creationInfoBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystemWithCreationInfo2(in creationInfo).Value; + } + + [CommandCmif(51)] + // OpenSaveDataFileSystem(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> object saveDataFs + public ResultCode OpenSaveDataFileSystem(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + using var fileSystem = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataFileSystem(ref fileSystem.Ref, spaceId, in attribute); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(52)] + // OpenSaveDataFileSystemBySystemSaveDataId(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> object systemSaveDataFs + public ResultCode OpenSaveDataFileSystemBySystemSaveDataId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + using var fileSystem = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataFileSystemBySystemSaveDataId(ref fileSystem.Ref, spaceId, in attribute); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(53)] + // OpenReadOnlySaveDataFileSystem(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> object + public ResultCode OpenReadOnlySaveDataFileSystem(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + using var fileSystem = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenReadOnlySaveDataFileSystem(ref fileSystem.Ref, spaceId, in attribute); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(57)] + // ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(u8 spaceId, u64 saveDataId) -> (buffer extraData) + public ResultCode ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] extraDataBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, extraDataBuffer); + + Result result = _baseFileSystemProxy.Get.ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(new OutBuffer(extraDataBuffer), spaceId, saveDataId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, extraDataBuffer); + + return ResultCode.Success; + } + + [CommandCmif(58)] + // ReadSaveDataFileSystemExtraData(u64 saveDataId) -> (buffer extraData) + public ResultCode ReadSaveDataFileSystemExtraData(ServiceCtx context) + { + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] extraDataBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, extraDataBuffer); + + Result result = _baseFileSystemProxy.Get.ReadSaveDataFileSystemExtraData(new OutBuffer(extraDataBuffer), saveDataId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, extraDataBuffer); + + return ResultCode.Success; + } + + [CommandCmif(59)] + // WriteSaveDataFileSystemExtraData(u8 spaceId, u64 saveDataId, buffer extraData) -> () + public ResultCode WriteSaveDataFileSystemExtraData(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] extraDataBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, extraDataBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.WriteSaveDataFileSystemExtraData(saveDataId, spaceId, new InBuffer(extraDataBuffer)).Value; + } + + [CommandCmif(60)] + // OpenSaveDataInfoReader() -> object + public ResultCode OpenSaveDataInfoReader(ServiceCtx context) + { + using var infoReader = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataInfoReader(ref infoReader.Ref); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new ISaveDataInfoReader(ref infoReader.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(61)] + // OpenSaveDataInfoReaderBySaveDataSpaceId(u8 spaceId) -> object + public ResultCode OpenSaveDataInfoReaderBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadByte(); + using var infoReader = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataInfoReaderBySaveDataSpaceId(ref infoReader.Ref, spaceId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new ISaveDataInfoReader(ref infoReader.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(62)] + // OpenSaveDataInfoReaderOnlyCacheStorage() -> object + public ResultCode OpenSaveDataInfoReaderOnlyCacheStorage(ServiceCtx context) + { + using var infoReader = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataInfoReaderOnlyCacheStorage(ref infoReader.Ref); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new ISaveDataInfoReader(ref infoReader.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(64)] + // OpenSaveDataInternalStorageFileSystem(u8 spaceId, u64 saveDataId) -> object + public ResultCode OpenSaveDataInternalStorageFileSystem(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + using var fileSystem = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataInternalStorageFileSystem(ref fileSystem.Ref, spaceId, saveDataId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(65)] + // UpdateSaveDataMacForDebug(u8 spaceId, u64 saveDataId) -> () + public ResultCode UpdateSaveDataMacForDebug(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.UpdateSaveDataMacForDebug(spaceId, saveDataId).Value; + } + + [CommandCmif(66)] + public ResultCode WriteSaveDataFileSystemExtraDataWithMask(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] extraDataBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, extraDataBuffer); + + byte[] maskBuffer = new byte[context.Request.SendBuff[1].Size]; + context.Memory.Read(context.Request.SendBuff[1].Position, maskBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.WriteSaveDataFileSystemExtraDataWithMask(saveDataId, spaceId, new InBuffer(extraDataBuffer), new InBuffer(maskBuffer)).Value; + } + + [CommandCmif(67)] + public ResultCode FindSaveDataWithFilter(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataFilter filter = context.RequestData.ReadStruct(); + + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + using var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true); + Result result = _baseFileSystemProxy.Get.FindSaveDataWithFilter(out long count, new OutBuffer(region.Memory.Span), spaceId, in filter); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.Write(count); + + return ResultCode.Success; + } + + [CommandCmif(68)] + public ResultCode OpenSaveDataInfoReaderWithFilter(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataFilter filter = context.RequestData.ReadStruct(); + using var infoReader = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataInfoReaderWithFilter(ref infoReader.Ref, spaceId, in filter); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new ISaveDataInfoReader(ref infoReader.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(69)] + public ResultCode ReadSaveDataFileSystemExtraDataBySaveDataAttribute(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + + byte[] outputBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, outputBuffer); + + Result result = _baseFileSystemProxy.Get.ReadSaveDataFileSystemExtraDataBySaveDataAttribute(new OutBuffer(outputBuffer), spaceId, in attribute); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, outputBuffer); + + return ResultCode.Success; + } + + [CommandCmif(70)] + public ResultCode WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + + byte[] extraDataBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, extraDataBuffer); + + byte[] maskBuffer = new byte[context.Request.SendBuff[1].Size]; + context.Memory.Read(context.Request.SendBuff[1].Position, maskBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in attribute, spaceId, new InBuffer(extraDataBuffer), new InBuffer(maskBuffer)).Value; + } + + [CommandCmif(71)] + public ResultCode ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + + byte[] maskBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, maskBuffer); + + byte[] outputBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, outputBuffer); + + Result result = _baseFileSystemProxy.Get.ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(new OutBuffer(outputBuffer), spaceId, in attribute, new InBuffer(maskBuffer)); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, outputBuffer); + + return ResultCode.Success; + } + + [CommandCmif(80)] + public ResultCode OpenSaveDataMetaFile(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt32(); + SaveDataMetaType metaType = (SaveDataMetaType)context.RequestData.ReadInt32(); + SaveDataAttribute attribute = context.RequestData.ReadStruct(); + using var file = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenSaveDataMetaFile(ref file.Ref, spaceId, in attribute, metaType); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new IFile(ref file.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(84)] + public ResultCode ListAccessibleSaveDataOwnerId(ServiceCtx context) + { + int startIndex = context.RequestData.ReadInt32(); + int bufferCount = context.RequestData.ReadInt32(); + ProgramId programId = context.RequestData.ReadStruct(); + + byte[] outputBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, outputBuffer); + + Result result = _baseFileSystemProxy.Get.ListAccessibleSaveDataOwnerId(out int readCount, new OutBuffer(outputBuffer), programId, startIndex, bufferCount); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.Write(readCount); + + return ResultCode.Success; + } + + [CommandCmif(100)] + public ResultCode OpenImageDirectoryFileSystem(ServiceCtx context) + { + ImageDirectoryId directoryId = (ImageDirectoryId)context.RequestData.ReadInt32(); + using var fileSystem = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenImageDirectoryFileSystem(ref fileSystem.Ref, directoryId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(101)] + public ResultCode OpenBaseFileSystem(ServiceCtx context) + { + BaseFileSystemId fileSystemId = (BaseFileSystemId)context.RequestData.ReadInt32(); + using var fileSystem = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenBaseFileSystem(ref fileSystem.Ref, fileSystemId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(110)] + public ResultCode OpenContentStorageFileSystem(ServiceCtx context) + { + ContentStorageId contentStorageId = (ContentStorageId)context.RequestData.ReadInt32(); + using var fileSystem = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenContentStorageFileSystem(ref fileSystem.Ref, contentStorageId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(120)] + public ResultCode OpenCloudBackupWorkStorageFileSystem(ServiceCtx context) + { + CloudBackupWorkStorageId storageId = (CloudBackupWorkStorageId)context.RequestData.ReadInt32(); + using var fileSystem = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenCloudBackupWorkStorageFileSystem(ref fileSystem.Ref, storageId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(130)] + public ResultCode OpenCustomStorageFileSystem(ServiceCtx context) + { + CustomStorageId customStorageId = (CustomStorageId)context.RequestData.ReadInt32(); + using var fileSystem = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenCustomStorageFileSystem(ref fileSystem.Ref, customStorageId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(200)] + // OpenDataStorageByCurrentProcess() -> object dataStorage + public ResultCode OpenDataStorageByCurrentProcess(ServiceCtx context) + { + var storage = context.Device.FileSystem.GetRomFs(_pid).AsStorage(true); + using var sharedStorage = new SharedRef(storage); + using var sfStorage = new SharedRef(new StorageInterfaceAdapter(ref sharedStorage.Ref)); + + MakeObject(context, new FileSystemProxy.IStorage(ref sfStorage.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(202)] + // OpenDataStorageByDataId(u8 storageId, nn::ncm::DataId dataId) -> object dataStorage + public ResultCode OpenDataStorageByDataId(ServiceCtx context) + { + StorageId storageId = (StorageId)context.RequestData.ReadByte(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + byte[] padding = context.RequestData.ReadBytes(7); +#pragma warning restore IDE0059 + ulong titleId = context.RequestData.ReadUInt64(); + + // We do a mitm here to find if the request is for an AOC. + // This is because AOC can be distributed over multiple containers in the emulator. + if (context.Device.System.ContentManager.GetAocDataStorage(titleId, out LibHac.Fs.IStorage aocStorage, context.Device.Configuration.FsIntegrityCheckLevel)) + { + Logger.Info?.Print(LogClass.Loader, $"Opened AddOnContent Data TitleID={titleId:X16}"); + + var storage = context.Device.FileSystem.ModLoader.ApplyRomFsMods(titleId, aocStorage); + using var sharedStorage = new SharedRef(storage); + using var sfStorage = new SharedRef(new StorageInterfaceAdapter(ref sharedStorage.Ref)); + + MakeObject(context, new FileSystemProxy.IStorage(ref sfStorage.Ref)); + + return ResultCode.Success; + } + + NcaContentType contentType = NcaContentType.Data; + + StorageId installedStorage = context.Device.System.ContentManager.GetInstalledStorage(titleId, contentType, storageId); + + if (installedStorage == StorageId.None) + { + contentType = NcaContentType.PublicData; + + installedStorage = context.Device.System.ContentManager.GetInstalledStorage(titleId, contentType, storageId); + } + + if (installedStorage != StorageId.None) + { + string contentPath = context.Device.System.ContentManager.GetInstalledContentPath(titleId, storageId, contentType); + string installPath = FileSystem.VirtualFileSystem.SwitchPathToSystemPath(contentPath); + + if (!string.IsNullOrWhiteSpace(installPath)) + { + string ncaPath = installPath; + + if (File.Exists(ncaPath)) + { + try + { + LibHac.Fs.IStorage ncaStorage = new LocalStorage(ncaPath, FileAccess.Read, FileMode.Open); + Nca nca = new(context.Device.System.KeySet, ncaStorage); + LibHac.Fs.IStorage romfsStorage = nca.OpenStorage(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); + using var sharedStorage = new SharedRef(romfsStorage); + using var sfStorage = new SharedRef(new StorageInterfaceAdapter(ref sharedStorage.Ref)); + + MakeObject(context, new FileSystemProxy.IStorage(ref sfStorage.Ref)); + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + + return ResultCode.Success; + } + else + { + throw new FileNotFoundException($"No Nca found in Path `{ncaPath}`."); + } + } + else + { + throw new DirectoryNotFoundException($"Path for title id {titleId:x16} on Storage {storageId} was not found in Path {installPath}."); + } + } + + throw new FileNotFoundException($"System archive with titleid {titleId:x16} was not found on Storage {storageId}. Found in {installedStorage}."); + } + + [CommandCmif(203)] + // OpenPatchDataStorageByCurrentProcess() -> object + public ResultCode OpenPatchDataStorageByCurrentProcess(ServiceCtx context) + { + var storage = context.Device.FileSystem.GetRomFs(_pid).AsStorage(true); + using var sharedStorage = new SharedRef(storage); + using var sfStorage = new SharedRef(new StorageInterfaceAdapter(ref sharedStorage.Ref)); + + MakeObject(context, new FileSystemProxy.IStorage(ref sfStorage.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(205)] + // OpenDataStorageWithProgramIndex(u8 program_index) -> object + public ResultCode OpenDataStorageWithProgramIndex(ServiceCtx context) + { + byte programIndex = context.RequestData.ReadByte(); + + if ((context.Device.Processes.ActiveApplication.ProgramId & 0xf) != programIndex) + { + throw new NotImplementedException($"Accessing storage from other programs is not supported (program index = {programIndex})."); + } + + var storage = context.Device.FileSystem.GetRomFs(_pid).AsStorage(true); + using var sharedStorage = new SharedRef(storage); + using var sfStorage = new SharedRef(new StorageInterfaceAdapter(ref sharedStorage.Ref)); + + MakeObject(context, new FileSystemProxy.IStorage(ref sfStorage.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(400)] + // OpenDataStorageByCurrentProcess() -> object dataStorage + public ResultCode OpenDeviceOperator(ServiceCtx context) + { + using var deviceOperator = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenDeviceOperator(ref deviceOperator.Ref); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new IDeviceOperator(ref deviceOperator.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(601)] + public ResultCode QuerySaveDataTotalSize(ServiceCtx context) + { + long dataSize = context.RequestData.ReadInt64(); + long journalSize = context.RequestData.ReadInt64(); + + Result result = _baseFileSystemProxy.Get.QuerySaveDataTotalSize(out long totalSize, dataSize, journalSize); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.Write(totalSize); + + return ResultCode.Success; + } + + [CommandCmif(511)] + public ResultCode NotifySystemDataUpdateEvent(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.NotifySystemDataUpdateEvent().Value; + } + + [CommandCmif(523)] + public ResultCode SimulateDeviceDetectionEvent(ServiceCtx context) + { + bool signalEvent = context.RequestData.ReadBoolean(); + context.RequestData.BaseStream.Seek(3, SeekOrigin.Current); + SdmmcPort port = context.RequestData.ReadStruct(); + SimulatingDeviceDetectionMode mode = context.RequestData.ReadStruct(); + + return (ResultCode)_baseFileSystemProxy.Get.SimulateDeviceDetectionEvent(port, mode, signalEvent).Value; + } + + [CommandCmif(602)] + public ResultCode VerifySaveDataFileSystem(ServiceCtx context) + { + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] readBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, readBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.VerifySaveDataFileSystem(saveDataId, new OutBuffer(readBuffer)).Value; + } + + [CommandCmif(603)] + public ResultCode CorruptSaveDataFileSystem(ServiceCtx context) + { + ulong saveDataId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.CorruptSaveDataFileSystem(saveDataId).Value; + } + + [CommandCmif(604)] + public ResultCode CreatePaddingFile(ServiceCtx context) + { + long size = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.CreatePaddingFile(size).Value; + } + + [CommandCmif(605)] + public ResultCode DeleteAllPaddingFiles(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.DeleteAllPaddingFiles().Value; + } + + [CommandCmif(606)] + public ResultCode GetRightsId(ServiceCtx context) + { + StorageId storageId = (StorageId)context.RequestData.ReadInt64(); + ProgramId programId = context.RequestData.ReadStruct(); + + Result result = _baseFileSystemProxy.Get.GetRightsId(out RightsId rightsId, programId, storageId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.WriteStruct(rightsId); + + return ResultCode.Success; + } + + [CommandCmif(607)] + public ResultCode RegisterExternalKey(ServiceCtx context) + { + RightsId rightsId = context.RequestData.ReadStruct(); + AccessKey accessKey = context.RequestData.ReadStruct(); + + return (ResultCode)_baseFileSystemProxy.Get.RegisterExternalKey(in rightsId, in accessKey).Value; + } + + [CommandCmif(608)] + public ResultCode UnregisterAllExternalKey(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.UnregisterAllExternalKey().Value; + } + + [CommandCmif(609)] + public ResultCode GetRightsIdByPath(ServiceCtx context) + { + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); + + Result result = _baseFileSystemProxy.Get.GetRightsIdByPath(out RightsId rightsId, in path); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.WriteStruct(rightsId); + + return ResultCode.Success; + } + + [CommandCmif(610)] + public ResultCode GetRightsIdAndKeyGenerationByPath(ServiceCtx context) + { + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); + + Result result = _baseFileSystemProxy.Get.GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in path); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.Write(keyGeneration); + context.ResponseData.BaseStream.Seek(7, SeekOrigin.Current); + context.ResponseData.WriteStruct(rightsId); + + return ResultCode.Success; + } + + [CommandCmif(611)] + public ResultCode SetCurrentPosixTimeWithTimeDifference(ServiceCtx context) + { + int timeDifference = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Seek(4, SeekOrigin.Current); + long time = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.SetCurrentPosixTimeWithTimeDifference(time, timeDifference).Value; + } + + [CommandCmif(612)] + public ResultCode GetFreeSpaceSizeForSaveData(ServiceCtx context) + { + SaveDataSpaceId spaceId = context.RequestData.ReadStruct(); + + Result result = _baseFileSystemProxy.Get.GetFreeSpaceSizeForSaveData(out long freeSpaceSize, spaceId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.Write(freeSpaceSize); + + return ResultCode.Success; + } + + [CommandCmif(613)] + public ResultCode VerifySaveDataFileSystemBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + byte[] readBuffer = new byte[context.Request.ReceiveBuff[0].Size]; + context.Memory.Read(context.Request.ReceiveBuff[0].Position, readBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.VerifySaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId, new OutBuffer(readBuffer)).Value; + } + + [CommandCmif(614)] + public ResultCode CorruptSaveDataFileSystemBySaveDataSpaceId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.CorruptSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId).Value; + } + + [CommandCmif(615)] + public ResultCode QuerySaveDataInternalStorageTotalSize(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + Result result = _baseFileSystemProxy.Get.QuerySaveDataInternalStorageTotalSize(out long size, spaceId, saveDataId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.Write(size); + + return ResultCode.Success; + } + + [CommandCmif(616)] + public ResultCode GetSaveDataCommitId(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + + Result result = _baseFileSystemProxy.Get.GetSaveDataCommitId(out long commitId, spaceId, saveDataId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.Write(commitId); + + return ResultCode.Success; + } + + [CommandCmif(617)] + public ResultCode UnregisterExternalKey(ServiceCtx context) + { + RightsId rightsId = context.RequestData.ReadStruct(); + + return (ResultCode)_baseFileSystemProxy.Get.UnregisterExternalKey(in rightsId).Value; + } + + [CommandCmif(620)] + public ResultCode SetSdCardEncryptionSeed(ServiceCtx context) + { + EncryptionSeed encryptionSeed = context.RequestData.ReadStruct(); + + return (ResultCode)_baseFileSystemProxy.Get.SetSdCardEncryptionSeed(in encryptionSeed).Value; + } + + [CommandCmif(630)] + // SetSdCardAccessibility(u8 isAccessible) + public ResultCode SetSdCardAccessibility(ServiceCtx context) + { + bool isAccessible = context.RequestData.ReadBoolean(); + + return (ResultCode)_baseFileSystemProxy.Get.SetSdCardAccessibility(isAccessible).Value; + } + + [CommandCmif(631)] + // IsSdCardAccessible() -> u8 isAccessible + public ResultCode IsSdCardAccessible(ServiceCtx context) + { + Result result = _baseFileSystemProxy.Get.IsSdCardAccessible(out bool isAccessible); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.Write(isAccessible); + + return ResultCode.Success; + } + + [CommandCmif(702)] + public ResultCode IsAccessFailureDetected(ServiceCtx context) + { + ulong processId = context.RequestData.ReadUInt64(); + + Result result = _baseFileSystemProxy.Get.IsAccessFailureDetected(out bool isDetected, processId); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.Write(isDetected); + + return ResultCode.Success; + } + + [CommandCmif(710)] + public ResultCode ResolveAccessFailure(ServiceCtx context) + { + ulong processId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.ResolveAccessFailure(processId).Value; + } + + [CommandCmif(720)] + public ResultCode AbandonAccessFailure(ServiceCtx context) + { + ulong processId = context.RequestData.ReadUInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.AbandonAccessFailure(processId).Value; + } + + [CommandCmif(800)] + public ResultCode GetAndClearErrorInfo(ServiceCtx context) + { + Result result = _baseFileSystemProxy.Get.GetAndClearErrorInfo(out FileSystemProxyErrorInfo errorInfo); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.WriteStruct(errorInfo); + + return ResultCode.Success; + } + + [CommandCmif(810)] + public ResultCode RegisterProgramIndexMapInfo(ServiceCtx context) + { + int programCount = context.RequestData.ReadInt32(); + + byte[] mapInfoBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, mapInfoBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.RegisterProgramIndexMapInfo(new InBuffer(mapInfoBuffer), programCount).Value; + } + + [CommandCmif(1000)] + public ResultCode SetBisRootForHost(ServiceCtx context) + { + BisPartitionId partitionId = (BisPartitionId)context.RequestData.ReadInt32(); + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); + + return (ResultCode)_baseFileSystemProxy.Get.SetBisRootForHost(partitionId, in path).Value; + } + + [CommandCmif(1001)] + public ResultCode SetSaveDataSize(ServiceCtx context) + { + long dataSize = context.RequestData.ReadInt64(); + long journalSize = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.SetSaveDataSize(dataSize, journalSize).Value; + } + + [CommandCmif(1002)] + public ResultCode SetSaveDataRootPath(ServiceCtx context) + { + ref readonly var path = ref FileSystemProxyHelper.GetFspPath(context); + + return (ResultCode)_baseFileSystemProxy.Get.SetSaveDataRootPath(in path).Value; + } + + [CommandCmif(1003)] + public ResultCode DisableAutoSaveDataCreation(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.DisableAutoSaveDataCreation().Value; + } + + [CommandCmif(1004)] + // SetGlobalAccessLogMode(u32 mode) + public ResultCode SetGlobalAccessLogMode(ServiceCtx context) + { + int mode = context.RequestData.ReadInt32(); + + context.Device.System.GlobalAccessLogMode = mode; + + return ResultCode.Success; + } + + [CommandCmif(1005)] + // GetGlobalAccessLogMode() -> u32 logMode + public ResultCode GetGlobalAccessLogMode(ServiceCtx context) + { + int mode = context.Device.System.GlobalAccessLogMode; + + context.ResponseData.Write(mode); + + return ResultCode.Success; + } + + [CommandCmif(1006)] + // OutputAccessLogToSdCard(buffer log_text) + public ResultCode OutputAccessLogToSdCard(ServiceCtx context) + { + string message = ReadUtf8StringSend(context); + + // FS ends each line with a newline. Remove it because Ryujinx logging adds its own newline + Logger.AccessLog?.PrintMsg(LogClass.ServiceFs, message.TrimEnd('\n')); + + return ResultCode.Success; + } + + [CommandCmif(1007)] + public ResultCode RegisterUpdatePartition(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.RegisterUpdatePartition().Value; + } + + [CommandCmif(1008)] + public ResultCode OpenRegisteredUpdatePartition(ServiceCtx context) + { + using var fileSystem = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenRegisteredUpdatePartition(ref fileSystem.Ref); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new FileSystemProxy.IFileSystem(ref fileSystem.Ref)); + + return ResultCode.Success; + } + + [CommandCmif(1009)] + public ResultCode GetAndClearMemoryReportInfo(ServiceCtx context) + { + Result result = _baseFileSystemProxy.Get.GetAndClearMemoryReportInfo(out MemoryReportInfo reportInfo); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.WriteStruct(reportInfo); + + return ResultCode.Success; + } + + [CommandCmif(1011)] + public ResultCode GetProgramIndexForAccessLog(ServiceCtx context) + { + Result result = _baseFileSystemProxy.Get.GetProgramIndexForAccessLog(out int programIndex, out int programCount); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.Write(programIndex); + context.ResponseData.Write(programCount); + + return ResultCode.Success; + } + + [CommandCmif(1012)] + public ResultCode GetFsStackUsage(ServiceCtx context) + { + FsStackUsageThreadType threadType = context.RequestData.ReadStruct(); + + Result result = _baseFileSystemProxy.Get.GetFsStackUsage(out uint usage, threadType); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + context.ResponseData.Write(usage); + + return ResultCode.Success; + } + + [CommandCmif(1013)] + public ResultCode UnsetSaveDataRootPath(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.UnsetSaveDataRootPath().Value; + } + + [CommandCmif(1014)] + public ResultCode OutputMultiProgramTagAccessLog(ServiceCtx context) + { + return (ResultCode)_baseFileSystemProxy.Get.OutputMultiProgramTagAccessLog().Value; + } + + [CommandCmif(1016)] + public ResultCode FlushAccessLogOnSdCard(ServiceCtx context) + { + // Logging the access log to the SD card isn't implemented, meaning this function will be a no-op since + // there's nothing to flush. Return success until it's implemented. + // return (ResultCode)_baseFileSystemProxy.Get.FlushAccessLogOnSdCard().Value; + return ResultCode.Success; + } + + [CommandCmif(1017)] + public ResultCode OutputApplicationInfoAccessLog(ServiceCtx context) + { + ApplicationInfo info = context.RequestData.ReadStruct(); + + return (ResultCode)_baseFileSystemProxy.Get.OutputApplicationInfoAccessLog(in info).Value; + } + + [CommandCmif(1100)] + public ResultCode OverrideSaveDataTransferTokenSignVerificationKey(ServiceCtx context) + { + byte[] keyBuffer = new byte[context.Request.SendBuff[0].Size]; + context.Memory.Read(context.Request.SendBuff[0].Position, keyBuffer); + + return (ResultCode)_baseFileSystemProxy.Get.OverrideSaveDataTransferTokenSignVerificationKey(new InBuffer(keyBuffer)).Value; + } + + [CommandCmif(1110)] + public ResultCode CorruptSaveDataFileSystemByOffset(ServiceCtx context) + { + SaveDataSpaceId spaceId = (SaveDataSpaceId)context.RequestData.ReadInt64(); + ulong saveDataId = context.RequestData.ReadUInt64(); + long offset = context.RequestData.ReadInt64(); + + return (ResultCode)_baseFileSystemProxy.Get.CorruptSaveDataFileSystemByOffset(spaceId, saveDataId, offset).Value; + } + + [CommandCmif(1200)] // 6.0.0+ + // OpenMultiCommitManager() -> object + public ResultCode OpenMultiCommitManager(ServiceCtx context) + { + using var commitManager = new SharedRef(); + + Result result = _baseFileSystemProxy.Get.OpenMultiCommitManager(ref commitManager.Ref); + if (result.IsFailure()) + { + return (ResultCode)result.Value; + } + + MakeObject(context, new IMultiCommitManager(ref commitManager.Ref)); + + return ResultCode.Success; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseFileSystemProxy.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxyForLoader.cs b/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxyForLoader.cs new file mode 100644 index 00000000..82c59384 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxyForLoader.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Fs +{ + [Service("fsp-ldr")] + class IFileSystemProxyForLoader : IpcService + { + public IFileSystemProxyForLoader(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs b/src/Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs new file mode 100644 index 00000000..134e2ed8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs @@ -0,0 +1,44 @@ +using LibHac; +using LibHac.Common; +using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy; + +namespace Ryujinx.HLE.HOS.Services.Fs +{ + class IMultiCommitManager : DisposableIpcService // 6.0.0+ + { + private SharedRef _baseCommitManager; + + public IMultiCommitManager(ref SharedRef baseCommitManager) + { + _baseCommitManager = SharedRef.CreateMove(ref baseCommitManager); + } + + [CommandCmif(1)] // 6.0.0+ + // Add(object) + public ResultCode Add(ServiceCtx context) + { + using SharedRef fileSystem = GetObject(context, 0).GetBaseFileSystem(); + + Result result = _baseCommitManager.Get.Add(ref fileSystem.Ref); + + return (ResultCode)result.Value; + } + + [CommandCmif(2)] // 6.0.0+ + // Commit() + public ResultCode Commit(ServiceCtx context) + { + Result result = _baseCommitManager.Get.Commit(); + + return (ResultCode)result.Value; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseCommitManager.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/IProgramRegistry.cs b/src/Ryujinx.HLE/HOS/Services/Fs/IProgramRegistry.cs new file mode 100644 index 00000000..9383a1d3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/IProgramRegistry.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Fs +{ + [Service("fsp-pr")] + class IProgramRegistry : IpcService + { + public IProgramRegistry(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs b/src/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs new file mode 100644 index 00000000..7ed6dadc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs @@ -0,0 +1,39 @@ +using LibHac; +using LibHac.Common; +using LibHac.Sf; + +namespace Ryujinx.HLE.HOS.Services.Fs +{ + class ISaveDataInfoReader : DisposableIpcService + { + private SharedRef _baseReader; + + public ISaveDataInfoReader(ref SharedRef baseReader) + { + _baseReader = SharedRef.CreateMove(ref baseReader); + } + + [CommandCmif(0)] + // ReadSaveDataInfo() -> (u64, buffer) + public ResultCode ReadSaveDataInfo(ServiceCtx context) + { + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + using var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true); + Result result = _baseReader.Get.Read(out long readCount, new OutBuffer(region.Memory.Span)); + + context.ResponseData.Write(readCount); + + return (ResultCode)result.Value; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _baseReader.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Fs/ResultCode.cs new file mode 100644 index 00000000..e04bd7be --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/ResultCode.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Services.Fs +{ + enum ResultCode + { + ModuleId = 2, + ErrorCodeShift = 9, + + Success = 0, + + PathDoesNotExist = (1 << ErrorCodeShift) | ModuleId, + PathAlreadyExists = (2 << ErrorCodeShift) | ModuleId, + PathAlreadyInUse = (7 << ErrorCodeShift) | ModuleId, + PartitionNotFound = (1001 << ErrorCodeShift) | ModuleId, + InvalidInput = (6001 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/Types/FileSystemType.cs b/src/Ryujinx.HLE/HOS/Services/Fs/Types/FileSystemType.cs new file mode 100644 index 00000000..17756e12 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Fs/Types/FileSystemType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Fs +{ + enum FileSystemType + { + Logo = 2, + ContentControl = 3, + ContentManual = 4, + ContentMeta = 5, + ContentData = 6, + ApplicationPackage = 7, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Grc/IGrcService.cs b/src/Ryujinx.HLE/HOS/Services/Grc/IGrcService.cs new file mode 100644 index 00000000..da01f64b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Grc/IGrcService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Grc +{ + [Service("grc:c")] // 4.0.0+ + class IGrcService : IpcService + { + public IGrcService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Grc/IRemoteVideoTransfer.cs b/src/Ryujinx.HLE/HOS/Services/Grc/IRemoteVideoTransfer.cs new file mode 100644 index 00000000..08e7365c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Grc/IRemoteVideoTransfer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Grc +{ + [Service("grc:d")] // 6.0.0+ + class IRemoteVideoTransfer : IpcService + { + public IRemoteVideoTransfer(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs new file mode 100644 index 00000000..dbcbe187 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs @@ -0,0 +1,109 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class Hid + { + private readonly Switch _device; + + private readonly SharedMemoryStorage _storage; + + internal ref SharedMemory SharedMemory => ref _storage.GetRef(0); + + internal const int SharedMemEntryCount = 17; + + public DebugPadDevice DebugPad; + public TouchDevice Touchscreen; + public MouseDevice Mouse; + public KeyboardDevice Keyboard; + public NpadDevices Npads; + + private static void CheckTypeSizeOrThrow(int expectedSize) + { + if (Unsafe.SizeOf() != expectedSize) + { + throw new InvalidStructLayoutException(expectedSize); + } + } + + static Hid() + { + CheckTypeSizeOrThrow>(0x2c8); + CheckTypeSizeOrThrow>(0x2C38); + CheckTypeSizeOrThrow>(0x350); + CheckTypeSizeOrThrow>(0x3D8); + CheckTypeSizeOrThrow>(0x32000); + CheckTypeSizeOrThrow(Horizon.HidSize); + } + + internal Hid(in Switch device, SharedMemoryStorage storage) + { + _device = device; + _storage = storage; + + SharedMemory = SharedMemory.Create(); + + InitDevices(); + } + + private void InitDevices() + { + DebugPad = new DebugPadDevice(_device, true); + Touchscreen = new TouchDevice(_device, true); + Mouse = new MouseDevice(_device, false); + Keyboard = new KeyboardDevice(_device, false); + Npads = new NpadDevices(_device, true); + } + + public void RefreshInputConfig(List inputConfig) + { + ControllerConfig[] npadConfig = new ControllerConfig[inputConfig.Count]; + + for (int i = 0; i < npadConfig.Length; ++i) + { + npadConfig[i].Player = (PlayerIndex)inputConfig[i].PlayerIndex; + npadConfig[i].Type = (ControllerType)inputConfig[i].ControllerType; + } + + _device.Hid.Npads.Configure(npadConfig); + } + + public ControllerKeys UpdateStickButtons(JoystickPosition leftStick, JoystickPosition rightStick) + { + const int StickButtonThreshold = short.MaxValue / 2; + ControllerKeys result = 0; + +#pragma warning disable IDE0055 // Disable formatting + result |= (leftStick.Dx < -StickButtonThreshold) ? ControllerKeys.LStickLeft : result; + result |= (leftStick.Dx > StickButtonThreshold) ? ControllerKeys.LStickRight : result; + result |= (leftStick.Dy < -StickButtonThreshold) ? ControllerKeys.LStickDown : result; + result |= (leftStick.Dy > StickButtonThreshold) ? ControllerKeys.LStickUp : result; + + result |= (rightStick.Dx < -StickButtonThreshold) ? ControllerKeys.RStickLeft : result; + result |= (rightStick.Dx > StickButtonThreshold) ? ControllerKeys.RStickRight : result; + result |= (rightStick.Dy < -StickButtonThreshold) ? ControllerKeys.RStickDown : result; + result |= (rightStick.Dy > StickButtonThreshold) ? ControllerKeys.RStickUp : result; +#pragma warning restore IDE0055 + + return result; + } + + internal ulong GetTimestampTicks() + { + return (ulong)PerformanceCounter.ElapsedMilliseconds * 19200; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs new file mode 100644 index 00000000..a6c21fda --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public abstract class BaseDevice + { + protected readonly Switch _device; + public bool Active; + + public BaseDevice(Switch device, bool active) + { + _device = device; + Active = active; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs new file mode 100644 index 00000000..6b1d7af5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs @@ -0,0 +1,28 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class DebugPadDevice : BaseDevice + { + public DebugPadDevice(Switch device, bool active) : base(device, active) { } + + public void Update() + { + ref RingLifo lifo = ref _device.Hid.SharedMemory.DebugPad; + + ref DebugPadState previousEntry = ref lifo.GetCurrentEntryRef(); + + DebugPadState newState = new(); + + if (Active) + { + // TODO: This is a debug device only present in dev environment, do we want to support it? + } + + newState.SamplingNumber = previousEntry.SamplingNumber + 1; + + lifo.Write(ref newState); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs new file mode 100644 index 00000000..0e3630f2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs @@ -0,0 +1,35 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard; +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class KeyboardDevice : BaseDevice + { + public KeyboardDevice(Switch device, bool active) : base(device, active) { } + + public void Update(KeyboardInput keyState) + { + ref RingLifo lifo = ref _device.Hid.SharedMemory.Keyboard; + + if (!Active) + { + lifo.Clear(); + + return; + } + + ref KeyboardState previousEntry = ref lifo.GetCurrentEntryRef(); + + KeyboardState newState = new() + { + SamplingNumber = previousEntry.SamplingNumber + 1, + }; + + keyState.Keys.AsSpan().CopyTo(newState.Keys.RawData.AsSpan()); + newState.Modifiers = (KeyboardModifier)keyState.Modifier; + + lifo.Write(ref newState); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs new file mode 100644 index 00000000..2e62d206 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs @@ -0,0 +1,36 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class MouseDevice : BaseDevice + { + public MouseDevice(Switch device, bool active) : base(device, active) { } + + public void Update(int mouseX, int mouseY, uint buttons = 0, int scrollX = 0, int scrollY = 0, bool connected = false) + { + ref RingLifo lifo = ref _device.Hid.SharedMemory.Mouse; + + ref MouseState previousEntry = ref lifo.GetCurrentEntryRef(); + + MouseState newState = new() + { + SamplingNumber = previousEntry.SamplingNumber + 1, + }; + + if (Active) + { + newState.Buttons = (MouseButton)buttons; + newState.X = mouseX; + newState.Y = mouseY; + newState.DeltaX = mouseX - previousEntry.X; + newState.DeltaY = mouseY - previousEntry.Y; + newState.WheelDeltaX = scrollX; + newState.WheelDeltaY = scrollY; + newState.Attributes = connected ? MouseAttribute.IsConnected : MouseAttribute.None; + } + + lifo.Write(ref newState); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs new file mode 100644 index 00000000..86c6a825 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs @@ -0,0 +1,643 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Hid.Types; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class NpadDevices : BaseDevice + { + private const int NoMatchNotifyFrequencyMs = 2000; + private int _activeCount; + private long _lastNotifyTimestamp; + + public const int MaxControllers = 9; // Players 1-8 and Handheld + private ControllerType[] _configuredTypes; + private readonly KEvent[] _styleSetUpdateEvents; + private readonly bool[] _supportedPlayers; + private VibrationValue _neutralVibrationValue = new() + { + AmplitudeLow = 0f, + FrequencyLow = 160f, + AmplitudeHigh = 0f, + FrequencyHigh = 320f, + }; + + internal NpadJoyHoldType JoyHold { get; set; } + internal bool SixAxisActive = false; // TODO: link to hidserver when implemented + internal ControllerType SupportedStyleSets { get; set; } + + public Dictionary> RumbleQueues = new(); + public Dictionary LastVibrationValues = new(); + + public NpadDevices(Switch device, bool active = true) : base(device, active) + { + _configuredTypes = new ControllerType[MaxControllers]; + + SupportedStyleSets = ControllerType.Handheld | ControllerType.JoyconPair | + ControllerType.JoyconLeft | ControllerType.JoyconRight | + ControllerType.ProController; + + _supportedPlayers = new bool[MaxControllers]; + _supportedPlayers.AsSpan().Fill(true); + + _styleSetUpdateEvents = new KEvent[MaxControllers]; + for (int i = 0; i < _styleSetUpdateEvents.Length; ++i) + { + _styleSetUpdateEvents[i] = new KEvent(_device.System.KernelContext); + } + + _activeCount = 0; + + JoyHold = NpadJoyHoldType.Vertical; + } + + internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player) + { + return ref _styleSetUpdateEvents[(int)player]; + } + + internal void ClearSupportedPlayers() + { + _supportedPlayers.AsSpan().Clear(); + } + + internal void SetSupportedPlayer(PlayerIndex player, bool supported = true) + { + if ((uint)player >= _supportedPlayers.Length) + { + return; + } + + _supportedPlayers[(int)player] = supported; + } + + internal IEnumerable GetSupportedPlayers() + { + for (int i = 0; i < _supportedPlayers.Length; ++i) + { + if (_supportedPlayers[i]) + { + yield return (PlayerIndex)i; + } + } + } + + public bool Validate(int playerMin, int playerMax, ControllerType acceptedTypes, out int configuredCount, out PlayerIndex primaryIndex) + { + primaryIndex = PlayerIndex.Unknown; + configuredCount = 0; + + for (int i = 0; i < MaxControllers; ++i) + { + ControllerType npad = _configuredTypes[i]; + + if (npad == ControllerType.Handheld && _device.System.State.DockedMode) + { + continue; + } + + ControllerType currentType = (ControllerType)_device.Hid.SharedMemory.Npads[i].InternalState.StyleSet; + + if (currentType != ControllerType.None && (npad & acceptedTypes) != 0 && _supportedPlayers[i]) + { + configuredCount++; + if (primaryIndex == PlayerIndex.Unknown) + { + primaryIndex = (PlayerIndex)i; + } + } + } + + if (configuredCount < playerMin || configuredCount > playerMax || primaryIndex == PlayerIndex.Unknown) + { + return false; + } + + return true; + } + + public void Configure(params ControllerConfig[] configs) + { + _configuredTypes = new ControllerType[MaxControllers]; + + for (int i = 0; i < configs.Length; ++i) + { + PlayerIndex player = configs[i].Player; + ControllerType controllerType = configs[i].Type; + + if (player > PlayerIndex.Handheld) + { + throw new InvalidOperationException("Player must be Player1-8 or Handheld"); + } + + if (controllerType == ControllerType.Handheld) + { + player = PlayerIndex.Handheld; + } + + _configuredTypes[(int)player] = controllerType; + + Logger.Info?.Print(LogClass.Hid, $"Configured Controller {controllerType} to {player}"); + } + } + + public void Update(IList states) + { + Remap(); + + Span updated = stackalloc bool[10]; + + // Update configured inputs + for (int i = 0; i < states.Count; ++i) + { + GamepadInput state = states[i]; + + updated[(int)state.PlayerId] = true; + + UpdateInput(state); + } + + for (int i = 0; i < updated.Length; i++) + { + if (!updated[i]) + { + UpdateDisconnectedInput((PlayerIndex)i); + } + } + } + + private void Remap() + { + // Remap/Init if necessary + for (int i = 0; i < MaxControllers; ++i) + { + ControllerType config = _configuredTypes[i]; + + // Remove Handheld config when Docked + if (config == ControllerType.Handheld && _device.System.State.DockedMode) + { + config = ControllerType.None; + } + + // Auto-remap ProController and JoyconPair + if (config == ControllerType.JoyconPair && (SupportedStyleSets & ControllerType.JoyconPair) == 0 && (SupportedStyleSets & ControllerType.ProController) != 0) + { + config = ControllerType.ProController; + } + else if (config == ControllerType.ProController && (SupportedStyleSets & ControllerType.ProController) == 0 && (SupportedStyleSets & ControllerType.JoyconPair) != 0) + { + config = ControllerType.JoyconPair; + } + + // Check StyleSet and PlayerSet + if ((config & SupportedStyleSets) == 0 || !_supportedPlayers[i]) + { + config = ControllerType.None; + } + + SetupNpad((PlayerIndex)i, config); + } + + if (_activeCount == 0 && PerformanceCounter.ElapsedMilliseconds > _lastNotifyTimestamp + NoMatchNotifyFrequencyMs) + { + Logger.Warning?.Print(LogClass.Hid, $"No matching controllers found. Application requests '{SupportedStyleSets}' on '{string.Join(", ", GetSupportedPlayers())}'"); + _lastNotifyTimestamp = PerformanceCounter.ElapsedMilliseconds; + } + } + + private void SetupNpad(PlayerIndex player, ControllerType type) + { + ref NpadInternalState controller = ref _device.Hid.SharedMemory.Npads[(int)player].InternalState; + + ControllerType oldType = (ControllerType)controller.StyleSet; + + if (oldType == type) + { + return; // Already configured + } + + controller = NpadInternalState.Create(); // Reset it + + if (type == ControllerType.None) + { + _styleSetUpdateEvents[(int)player].ReadableEvent.Signal(); // Signal disconnect + _activeCount--; + + Logger.Info?.Print(LogClass.Hid, $"Disconnected Controller {oldType} from {player}"); + + return; + } + + // TODO: Allow customizing colors at config + controller.JoyAssignmentMode = NpadJoyAssignmentMode.Dual; + controller.FullKeyColor.FullKeyBody = (uint)NpadColor.BodyGray; + controller.FullKeyColor.FullKeyButtons = (uint)NpadColor.ButtonGray; + controller.JoyColor.LeftBody = (uint)NpadColor.BodyNeonBlue; + controller.JoyColor.LeftButtons = (uint)NpadColor.ButtonGray; + controller.JoyColor.RightBody = (uint)NpadColor.BodyNeonRed; + controller.JoyColor.RightButtons = (uint)NpadColor.ButtonGray; + + controller.SystemProperties = NpadSystemProperties.IsPoweredJoyDual | + NpadSystemProperties.IsPoweredJoyLeft | + NpadSystemProperties.IsPoweredJoyRight; + + controller.BatteryLevelJoyDual = NpadBatteryLevel.Percent100; + controller.BatteryLevelJoyLeft = NpadBatteryLevel.Percent100; + controller.BatteryLevelJoyRight = NpadBatteryLevel.Percent100; + + switch (type) + { +#pragma warning disable IDE0055 // Disable formatting + case ControllerType.ProController: + controller.StyleSet = NpadStyleTag.FullKey; + controller.DeviceType = DeviceType.FullKey; + controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented | + NpadSystemProperties.IsPlusAvailable | + NpadSystemProperties.IsMinusAvailable; + controller.AppletFooterUiType = AppletFooterUiType.SwitchProController; + break; + case ControllerType.Handheld: + controller.StyleSet = NpadStyleTag.Handheld; + controller.DeviceType = DeviceType.HandheldLeft | + DeviceType.HandheldRight; + controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented | + NpadSystemProperties.IsPlusAvailable | + NpadSystemProperties.IsMinusAvailable; + controller.AppletFooterUiType = AppletFooterUiType.HandheldJoyConLeftJoyConRight; + break; + case ControllerType.JoyconPair: + controller.StyleSet = NpadStyleTag.JoyDual; + controller.DeviceType = DeviceType.JoyLeft | + DeviceType.JoyRight; + controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented | + NpadSystemProperties.IsPlusAvailable | + NpadSystemProperties.IsMinusAvailable; + controller.AppletFooterUiType = _device.System.State.DockedMode ? AppletFooterUiType.JoyDual : AppletFooterUiType.HandheldJoyConLeftJoyConRight; + break; + case ControllerType.JoyconLeft: + controller.StyleSet = NpadStyleTag.JoyLeft; + controller.JoyAssignmentMode = NpadJoyAssignmentMode.Single; + controller.DeviceType = DeviceType.JoyLeft; + controller.SystemProperties |= NpadSystemProperties.IsSlSrButtonOriented | + NpadSystemProperties.IsMinusAvailable; + controller.AppletFooterUiType = _device.System.State.DockedMode ? AppletFooterUiType.JoyDualLeftOnly : AppletFooterUiType.HandheldJoyConLeftOnly; + break; + case ControllerType.JoyconRight: + controller.StyleSet = NpadStyleTag.JoyRight; + controller.JoyAssignmentMode = NpadJoyAssignmentMode.Single; + controller.DeviceType = DeviceType.JoyRight; + controller.SystemProperties |= NpadSystemProperties.IsSlSrButtonOriented | + NpadSystemProperties.IsPlusAvailable; + controller.AppletFooterUiType = _device.System.State.DockedMode ? AppletFooterUiType.JoyDualRightOnly : AppletFooterUiType.HandheldJoyConRightOnly; + break; + case ControllerType.Pokeball: + controller.StyleSet = NpadStyleTag.Palma; + controller.DeviceType = DeviceType.Palma; + controller.AppletFooterUiType = AppletFooterUiType.None; + break; +#pragma warning restore IDE0055 + } + + _styleSetUpdateEvents[(int)player].ReadableEvent.Signal(); + _activeCount++; + + Logger.Info?.Print(LogClass.Hid, $"Connected Controller {type} to {player}"); + } + + private ref RingLifo GetCommonStateLifo(ref NpadInternalState npad) + { + switch (npad.StyleSet) + { + case NpadStyleTag.FullKey: + return ref npad.FullKey; + case NpadStyleTag.Handheld: + return ref npad.Handheld; + case NpadStyleTag.JoyDual: + return ref npad.JoyDual; + case NpadStyleTag.JoyLeft: + return ref npad.JoyLeft; + case NpadStyleTag.JoyRight: + return ref npad.JoyRight; + case NpadStyleTag.Palma: + return ref npad.Palma; + default: + return ref npad.SystemExt; + } + } + + private void UpdateUnusedInputIfNotEqual(ref RingLifo currentlyUsed, ref RingLifo possiblyUnused) + { + if (!Unsafe.AreSame(ref currentlyUsed, ref possiblyUnused)) + { + NpadCommonState newState = new(); + + WriteNewInputEntry(ref possiblyUnused, ref newState); + } + } + + private void WriteNewInputEntry(ref RingLifo lifo, ref NpadCommonState state) + { + ref NpadCommonState previousEntry = ref lifo.GetCurrentEntryRef(); + + state.SamplingNumber = previousEntry.SamplingNumber + 1; + + lifo.Write(ref state); + } + + private void UpdateUnusedSixInputIfNotEqual(ref RingLifo currentlyUsed, ref RingLifo possiblyUnused) + { + if (!Unsafe.AreSame(ref currentlyUsed, ref possiblyUnused)) + { + SixAxisSensorState newState = new(); + + WriteNewSixInputEntry(ref possiblyUnused, ref newState); + } + } + + private void WriteNewSixInputEntry(ref RingLifo lifo, ref SixAxisSensorState state) + { + ref SixAxisSensorState previousEntry = ref lifo.GetCurrentEntryRef(); + + state.SamplingNumber = previousEntry.SamplingNumber + 1; + + lifo.Write(ref state); + } + + private void UpdateInput(GamepadInput state) + { + if (state.PlayerId == PlayerIndex.Unknown) + { + return; + } + + ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId].InternalState; + + if (currentNpad.StyleSet == NpadStyleTag.None) + { + return; + } + + ref RingLifo lifo = ref GetCommonStateLifo(ref currentNpad); + + NpadCommonState newState = new() + { + Buttons = (NpadButton)state.Buttons, + AnalogStickL = new AnalogStickState + { + X = state.LStick.Dx, + Y = state.LStick.Dy, + }, + AnalogStickR = new AnalogStickState + { + X = state.RStick.Dx, + Y = state.RStick.Dy, + }, + Attributes = NpadAttribute.IsConnected, + }; + + switch (currentNpad.StyleSet) + { + case NpadStyleTag.Handheld: + case NpadStyleTag.FullKey: + newState.Attributes |= NpadAttribute.IsWired; + break; + case NpadStyleTag.JoyDual: + newState.Attributes |= NpadAttribute.IsLeftConnected | + NpadAttribute.IsRightConnected; + break; + case NpadStyleTag.JoyLeft: + newState.Attributes |= NpadAttribute.IsLeftConnected; + break; + case NpadStyleTag.JoyRight: + newState.Attributes |= NpadAttribute.IsRightConnected; + break; + } + + WriteNewInputEntry(ref lifo, ref newState); + + // Mirror data to Default layout just in case + if (!currentNpad.StyleSet.HasFlag(NpadStyleTag.SystemExt)) + { + WriteNewInputEntry(ref currentNpad.SystemExt, ref newState); + } + + UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.FullKey); + UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.Handheld); + UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyDual); + UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyLeft); + UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyRight); + UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.Palma); + } + + private void UpdateDisconnectedInput(PlayerIndex index) + { + ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)index].InternalState; + + NpadCommonState newState = new(); + + WriteNewInputEntry(ref currentNpad.FullKey, ref newState); + WriteNewInputEntry(ref currentNpad.Handheld, ref newState); + WriteNewInputEntry(ref currentNpad.JoyDual, ref newState); + WriteNewInputEntry(ref currentNpad.JoyLeft, ref newState); + WriteNewInputEntry(ref currentNpad.JoyRight, ref newState); + WriteNewInputEntry(ref currentNpad.Palma, ref newState); + } + + public void UpdateSixAxis(IList states) + { + Span updated = stackalloc bool[10]; + + for (int i = 0; i < states.Count; ++i) + { + updated[(int)states[i].PlayerId] = true; + + if (SetSixAxisState(states[i])) + { + i++; + + if (i >= states.Count) + { + return; + } + + SetSixAxisState(states[i], true); + } + } + + for (int i = 0; i < updated.Length; i++) + { + if (!updated[i]) + { + UpdateDisconnectedInputSixAxis((PlayerIndex)i); + } + } + } + + private ref RingLifo GetSixAxisSensorLifo(ref NpadInternalState npad, bool isRightPair) + { + switch (npad.StyleSet) + { + case NpadStyleTag.FullKey: + return ref npad.FullKeySixAxisSensor; + case NpadStyleTag.Handheld: + return ref npad.HandheldSixAxisSensor; + case NpadStyleTag.JoyDual: + if (isRightPair) + { + return ref npad.JoyDualRightSixAxisSensor; + } + else + { + return ref npad.JoyDualSixAxisSensor; + } + case NpadStyleTag.JoyLeft: + return ref npad.JoyLeftSixAxisSensor; + case NpadStyleTag.JoyRight: + return ref npad.JoyRightSixAxisSensor; + default: + throw new NotImplementedException($"{npad.StyleSet}"); + } + } + + private bool SetSixAxisState(SixAxisInput state, bool isRightPair = false) + { + if (state.PlayerId == PlayerIndex.Unknown) + { + return false; + } + + ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId].InternalState; + + if (currentNpad.StyleSet == NpadStyleTag.None) + { + return false; + } + + HidVector accel = new() + { + X = state.Accelerometer.X, + Y = state.Accelerometer.Y, + Z = state.Accelerometer.Z, + }; + + HidVector gyro = new() + { + X = state.Gyroscope.X, + Y = state.Gyroscope.Y, + Z = state.Gyroscope.Z, + }; + + HidVector rotation = new() + { + X = state.Rotation.X, + Y = state.Rotation.Y, + Z = state.Rotation.Z, + }; + + SixAxisSensorState newState = new() + { + Acceleration = accel, + AngularVelocity = gyro, + Angle = rotation, + Attributes = SixAxisSensorAttribute.IsConnected, + }; + + state.Orientation.AsSpan().CopyTo(newState.Direction.AsSpan()); + + ref RingLifo lifo = ref GetSixAxisSensorLifo(ref currentNpad, isRightPair); + + WriteNewSixInputEntry(ref lifo, ref newState); + + bool needUpdateRight = currentNpad.StyleSet == NpadStyleTag.JoyDual && !isRightPair; + + if (!isRightPair) + { + UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.FullKeySixAxisSensor); + UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.HandheldSixAxisSensor); + UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyDualSixAxisSensor); + UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyLeftSixAxisSensor); + UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyRightSixAxisSensor); + } + + if (!needUpdateRight && !isRightPair) + { + SixAxisSensorState emptyState = new() + { + Attributes = SixAxisSensorAttribute.IsConnected, + }; + + WriteNewSixInputEntry(ref currentNpad.JoyDualRightSixAxisSensor, ref emptyState); + } + + return needUpdateRight; + } + + private void UpdateDisconnectedInputSixAxis(PlayerIndex index) + { + ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)index].InternalState; + + SixAxisSensorState newState = new() + { + Attributes = SixAxisSensorAttribute.IsConnected, + }; + + WriteNewSixInputEntry(ref currentNpad.FullKeySixAxisSensor, ref newState); + WriteNewSixInputEntry(ref currentNpad.HandheldSixAxisSensor, ref newState); + WriteNewSixInputEntry(ref currentNpad.JoyDualSixAxisSensor, ref newState); + WriteNewSixInputEntry(ref currentNpad.JoyDualRightSixAxisSensor, ref newState); + WriteNewSixInputEntry(ref currentNpad.JoyLeftSixAxisSensor, ref newState); + WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState); + } + + public void UpdateRumbleQueue(PlayerIndex index, Dictionary dualVibrationValues) + { + if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> currentQueue)) + { + if (!dualVibrationValues.TryGetValue(0, out VibrationValue leftVibrationValue)) + { + leftVibrationValue = _neutralVibrationValue; + } + + if (!dualVibrationValues.TryGetValue(1, out VibrationValue rightVibrationValue)) + { + rightVibrationValue = _neutralVibrationValue; + } + + if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2)) + { + currentQueue.Enqueue((leftVibrationValue, rightVibrationValue)); + + LastVibrationValues[index] = (leftVibrationValue, rightVibrationValue); + } + } + } + + public VibrationValue GetLastVibrationValue(PlayerIndex index, byte position) + { + if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue)) + { + return _neutralVibrationValue; + } + + return (position == 0) ? dualVibrationValue.Item1 : dualVibrationValue.Item2; + } + + public ConcurrentQueue<(VibrationValue, VibrationValue)> GetRumbleQueue(PlayerIndex index) + { + if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> rumbleQueue)) + { + rumbleQueue = new ConcurrentQueue<(VibrationValue, VibrationValue)>(); + _device.Hid.Npads.RumbleQueues[index] = rumbleQueue; + } + + return rumbleQueue; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs new file mode 100644 index 00000000..35ac1a16 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs @@ -0,0 +1,48 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen; +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public class TouchDevice : BaseDevice + { + public TouchDevice(Switch device, bool active) : base(device, active) { } + + public void Update(params TouchPoint[] points) + { + ref RingLifo lifo = ref _device.Hid.SharedMemory.TouchScreen; + + ref TouchScreenState previousEntry = ref lifo.GetCurrentEntryRef(); + + TouchScreenState newState = new() + { + SamplingNumber = previousEntry.SamplingNumber + 1, + }; + + if (Active) + { + newState.TouchesCount = points.Length; + + int pointsLength = Math.Min(points.Length, newState.Touches.Length); + + for (int i = 0; i < pointsLength; ++i) + { + TouchPoint pi = points[i]; + newState.Touches[i] = new TouchState + { + DeltaTime = newState.SamplingNumber, + Attribute = pi.Attribute, + X = pi.X, + Y = pi.Y, + FingerId = (uint)i, + DiameterX = pi.DiameterX, + DiameterY = pi.DiameterY, + RotationAngle = pi.Angle, + }; + } + } + + lifo.Write(ref newState); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs new file mode 100644 index 00000000..cba5c7b1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct ControllerConfig + { + public PlayerIndex Player; + public ControllerType Type; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs new file mode 100644 index 00000000..452901a0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct GamepadInput + { + public PlayerIndex PlayerId; + public ControllerKeys Buttons; + public JoystickPosition LStick; + public JoystickPosition RStick; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs new file mode 100644 index 00000000..47be8d41 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct JoystickPosition + { + public int Dx; + public int Dy; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs new file mode 100644 index 00000000..26fe980f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct KeyboardInput + { + public int Modifier; + public ulong[] Keys; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/SixAxisInput.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/SixAxisInput.cs new file mode 100644 index 00000000..5c5c5d8c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/SixAxisInput.cs @@ -0,0 +1,13 @@ +using System.Numerics; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct SixAxisInput + { + public PlayerIndex PlayerId; + public Vector3 Accelerometer; + public Vector3 Gyroscope; + public Vector3 Rotation; + public float[] Orientation; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs new file mode 100644 index 00000000..ab0c8526 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs @@ -0,0 +1,14 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct TouchPoint + { + public TouchAttribute Attribute; + public uint X; + public uint Y; + public uint DiameterX; + public uint DiameterY; + public uint Angle; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs new file mode 100644 index 00000000..8e2e854f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs @@ -0,0 +1,50 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.HidServer +{ + static class HidUtils + { + public static PlayerIndex GetIndexFromNpadIdType(NpadIdType npadIdType) + => npadIdType switch + { +#pragma warning disable IDE0055 // Disable formatting + NpadIdType.Player1 => PlayerIndex.Player1, + NpadIdType.Player2 => PlayerIndex.Player2, + NpadIdType.Player3 => PlayerIndex.Player3, + NpadIdType.Player4 => PlayerIndex.Player4, + NpadIdType.Player5 => PlayerIndex.Player5, + NpadIdType.Player6 => PlayerIndex.Player6, + NpadIdType.Player7 => PlayerIndex.Player7, + NpadIdType.Player8 => PlayerIndex.Player8, + NpadIdType.Handheld => PlayerIndex.Handheld, + NpadIdType.Unknown => PlayerIndex.Unknown, + _ => throw new ArgumentOutOfRangeException(nameof(npadIdType)), +#pragma warning restore IDE0055 + }; + + public static NpadIdType GetNpadIdTypeFromIndex(PlayerIndex index) + => index switch + { +#pragma warning disable IDE0055 // Disable formatting + PlayerIndex.Player1 => NpadIdType.Player1, + PlayerIndex.Player2 => NpadIdType.Player2, + PlayerIndex.Player3 => NpadIdType.Player3, + PlayerIndex.Player4 => NpadIdType.Player4, + PlayerIndex.Player5 => NpadIdType.Player5, + PlayerIndex.Player6 => NpadIdType.Player6, + PlayerIndex.Player7 => NpadIdType.Player7, + PlayerIndex.Player8 => NpadIdType.Player8, + PlayerIndex.Handheld => NpadIdType.Handheld, + PlayerIndex.Unknown => NpadIdType.Unknown, + _ => throw new ArgumentOutOfRangeException(nameof(index)), +#pragma warning restore IDE0055 + }; + + public static bool IsValidNpadIdType(NpadIdType npadIdType) + { + return (npadIdType >= NpadIdType.Player1 && npadIdType <= NpadIdType.Player8) || + npadIdType == NpadIdType.Handheld || + npadIdType == NpadIdType.Unknown; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs new file mode 100644 index 00000000..93f19c91 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.HidServer +{ + class IActiveApplicationDeviceList : IpcService + { + public IActiveApplicationDeviceList() { } + + [CommandCmif(0)] + // ActivateVibrationDevice(nn::hid::VibrationDeviceHandle) + public ResultCode ActivateVibrationDevice(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + int vibrationDeviceHandle = context.RequestData.ReadInt32(); +#pragma warning restore IDE0059 + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs new file mode 100644 index 00000000..56eb345a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs @@ -0,0 +1,35 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.HidServer +{ + class IAppletResource : IpcService + { + private readonly KSharedMemory _hidSharedMem; + private int _hidSharedMemHandle; + + public IAppletResource(KSharedMemory hidSharedMem) + { + _hidSharedMem = hidSharedMem; + } + + [CommandCmif(0)] + // GetSharedMemoryHandle() -> handle + public ResultCode GetSharedMemoryHandle(ServiceCtx context) + { + if (_hidSharedMemHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_hidSharedMem, out _hidSharedMemHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_hidSharedMemHandle); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs new file mode 100644 index 00000000..ba56e93c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum NpadHandheldActivationMode + { + Dual, + Single, + None, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs new file mode 100644 index 00000000..ac0bb357 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum NpadJoyDeviceType + { + Left, + Right, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs new file mode 100644 index 00000000..baabffa0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct AccelerometerParameters + { + public float X; + public float Y; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs new file mode 100644 index 00000000..d73c6025 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum GyroscopeZeroDriftMode + { + Loose, + Standard, + Tight, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs new file mode 100644 index 00000000..c7421456 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct SensorFusionParameters + { + public float RevisePower; + public float ReviseRange; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs new file mode 100644 index 00000000..02f4b684 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct VibrationDeviceHandle + { + public byte DeviceType; + public byte PlayerId; + public byte Position; + public byte Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs new file mode 100644 index 00000000..90f4abcc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum VibrationDevicePosition + { + None, + Left, + Right, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs new file mode 100644 index 00000000..db586bb3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum VibrationDeviceType + { + None, + LinearResonantActuator, + GcErm, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs new file mode 100644 index 00000000..feed8764 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct VibrationDeviceValue + { + public VibrationDeviceType DeviceType; + public VibrationDevicePosition Position; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs new file mode 100644 index 00000000..490d1e6a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs @@ -0,0 +1,34 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public struct VibrationValue + { + public float AmplitudeLow; + public float FrequencyLow; + public float AmplitudeHigh; + public float FrequencyHigh; + + public readonly override bool Equals(object obj) + { + return obj is VibrationValue value && + AmplitudeLow == value.AmplitudeLow && + AmplitudeHigh == value.AmplitudeHigh; + } + + public readonly override int GetHashCode() + { + return HashCode.Combine(AmplitudeLow, AmplitudeHigh); + } + + public static bool operator ==(VibrationValue left, VibrationValue right) + { + return left.Equals(right); + } + + public static bool operator !=(VibrationValue left, VibrationValue right) + { + return !(left == right); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs new file mode 100644 index 00000000..2444c443 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("hid:dbg")] + class IHidDebugServer : IpcService + { + public IHidDebugServer(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs new file mode 100644 index 00000000..e3f505f3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs @@ -0,0 +1,1852 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Hid.HidServer; +using Ryujinx.HLE.HOS.Services.Hid.Types; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("hid")] + class IHidServer : IpcService + { + private readonly KEvent _xpadIdEvent; + private readonly KEvent _palmaOperationCompleteEvent; + + private int _xpadIdEventHandle; + + private bool _sixAxisSensorFusionEnabled; + private bool _unintendedHomeButtonInputProtectionEnabled; + private bool _npadAnalogStickCenterClampEnabled; + private bool _vibrationPermitted; + private bool _usbFullKeyControllerEnabled; + private readonly bool _isFirmwareUpdateAvailableForSixAxisSensor; + private bool _isSixAxisSensorUnalteredPassthroughEnabled; + + private NpadHandheldActivationMode _npadHandheldActivationMode; + private GyroscopeZeroDriftMode _gyroscopeZeroDriftMode; + + private long _npadCommunicationMode; + private uint _accelerometerPlayMode; +#pragma warning disable CS0649 // Field is never assigned to + private readonly long _vibrationGcErmCommand; +#pragma warning restore CS0649 + private float _sevenSixAxisSensorFusionStrength; + + private SensorFusionParameters _sensorFusionParams; + private AccelerometerParameters _accelerometerParams; + + public IHidServer(ServiceCtx context) : base(context.Device.System.HidServer) + { + _xpadIdEvent = new KEvent(context.Device.System.KernelContext); + _palmaOperationCompleteEvent = new KEvent(context.Device.System.KernelContext); + + _npadHandheldActivationMode = NpadHandheldActivationMode.Dual; + _gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard; + + _isFirmwareUpdateAvailableForSixAxisSensor = false; + + _sensorFusionParams = new SensorFusionParameters(); + _accelerometerParams = new AccelerometerParameters(); + + // TODO: signal event at right place + _xpadIdEvent.ReadableEvent.Signal(); + + _vibrationPermitted = true; + } + + [CommandCmif(0)] + // CreateAppletResource(nn::applet::AppletResourceUserId) -> object + public ResultCode CreateAppletResource(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long appletResourceUserId = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + + MakeObject(context, new IAppletResource(context.Device.System.HidSharedMem)); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // ActivateDebugPad(nn::applet::AppletResourceUserId) + public ResultCode ActivateDebugPad(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + // Initialize entries to avoid issues with some games. + + for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++) + { + context.Device.Hid.DebugPad.Update(); + } + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // ActivateTouchScreen(nn::applet::AppletResourceUserId) + public ResultCode ActivateTouchScreen(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.Device.Hid.Touchscreen.Active = true; + + // Initialize entries to avoid issues with some games. + + for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++) + { + context.Device.Hid.Touchscreen.Update(); + } + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(21)] + // ActivateMouse(nn::applet::AppletResourceUserId) + public ResultCode ActivateMouse(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.Device.Hid.Mouse.Active = true; + + // Initialize entries to avoid issues with some games. + + for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++) + { + context.Device.Hid.Mouse.Update(0, 0); + } + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(31)] + // ActivateKeyboard(nn::applet::AppletResourceUserId) + public ResultCode ActivateKeyboard(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.Device.Hid.Keyboard.Active = true; + + // Initialize entries to avoid issues with some games. + + KeyboardInput emptyInput = new() + { + Keys = new ulong[4], + }; + + for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++) + { + context.Device.Hid.Keyboard.Update(emptyInput); + } + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(32)] + // SendKeyboardLockKeyEvent(uint flags, pid) + public ResultCode SendKeyboardLockKeyEvent(ServiceCtx context) + { + uint flags = context.RequestData.ReadUInt32(); + + // NOTE: This signal the keyboard driver about lock events. + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { flags }); + + return ResultCode.Success; + } + + [CommandCmif(40)] + // AcquireXpadIdEventHandle(ulong XpadId) -> nn::sf::NativeHandle + public ResultCode AcquireXpadIdEventHandle(ServiceCtx context) + { + long xpadId = context.RequestData.ReadInt64(); + + if (context.Process.HandleTable.GenerateHandle(_xpadIdEvent.ReadableEvent, out _xpadIdEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_xpadIdEventHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { xpadId }); + + return ResultCode.Success; + } + + [CommandCmif(41)] + // ReleaseXpadIdEventHandle(ulong XpadId) + public ResultCode ReleaseXpadIdEventHandle(ServiceCtx context) + { + long xpadId = context.RequestData.ReadInt64(); + + context.Process.HandleTable.CloseHandle(_xpadIdEventHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { xpadId }); + + return ResultCode.Success; + } + + [CommandCmif(51)] + // ActivateXpad(nn::hid::BasicXpadId, nn::applet::AppletResourceUserId) + public ResultCode ActivateXpad(ServiceCtx context) + { + int basicXpadId = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, basicXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(55)] + // GetXpadIds() -> long IdsCount, buffer, type: 0xa> + public ResultCode GetXpadIds(ServiceCtx context) + { + // There is any Xpad, so we return 0 and write nothing inside the type-0xa buffer. + context.ResponseData.Write(0L); + + Logger.Stub?.PrintStub(LogClass.ServiceHid); + + return ResultCode.Success; + } + + [CommandCmif(56)] + // ActivateJoyXpad(nn::hid::JoyXpadId) + public ResultCode ActivateJoyXpad(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(58)] + // GetJoyXpadLifoHandle(nn::hid::JoyXpadId) -> nn::sf::NativeHandle + public ResultCode GetJoyXpadLifoHandle(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + int handle = 0; + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(59)] + // GetJoyXpadIds() -> long IdsCount, buffer, type: 0xa> + public ResultCode GetJoyXpadIds(ServiceCtx context) + { + // There is any JoyXpad, so we return 0 and write nothing inside the type-0xa buffer. + context.ResponseData.Write(0L); + + Logger.Stub?.PrintStub(LogClass.ServiceHid); + + return ResultCode.Success; + } + + [CommandCmif(60)] + // ActivateSixAxisSensor(nn::hid::BasicXpadId) + public ResultCode ActivateSixAxisSensor(ServiceCtx context) + { + int basicXpadId = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { basicXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(61)] + // DeactivateSixAxisSensor(nn::hid::BasicXpadId) + public ResultCode DeactivateSixAxisSensor(ServiceCtx context) + { + int basicXpadId = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { basicXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(62)] + // GetSixAxisSensorLifoHandle(nn::hid::BasicXpadId) -> nn::sf::NativeHandle + public ResultCode GetSixAxisSensorLifoHandle(ServiceCtx context) + { + int basicXpadId = context.RequestData.ReadInt32(); + + int handle = 0; + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { basicXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(63)] + // ActivateJoySixAxisSensor(nn::hid::JoyXpadId) + public ResultCode ActivateJoySixAxisSensor(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(64)] + // DeactivateJoySixAxisSensor(nn::hid::JoyXpadId) + public ResultCode DeactivateJoySixAxisSensor(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(65)] + // GetJoySixAxisSensorLifoHandle(nn::hid::JoyXpadId) -> nn::sf::NativeHandle + public ResultCode GetJoySixAxisSensorLifoHandle(ServiceCtx context) + { + int joyXpadId = context.RequestData.ReadInt32(); + + int handle = 0; + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId }); + + return ResultCode.Success; + } + + [CommandCmif(66)] + // StartSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode StartSixAxisSensor(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle }); + + return ResultCode.Success; + } + + [CommandCmif(67)] + // StopSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode StopSixAxisSensor(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle }); + + return ResultCode.Success; + } + + [CommandCmif(68)] + // IsSixAxisSensorFusionEnabled(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsEnabled + public ResultCode IsSixAxisSensorFusionEnabled(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_sixAxisSensorFusionEnabled); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sixAxisSensorFusionEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(69)] + // EnableSixAxisSensorFusion(bool Enabled, nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode EnableSixAxisSensorFusion(ServiceCtx context) + { + _sixAxisSensorFusionEnabled = context.RequestData.ReadUInt32() != 0; + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sixAxisSensorFusionEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(70)] + // SetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, float RevisePower, float ReviseRange, nn::applet::AppletResourceUserId) + public ResultCode SetSixAxisSensorFusionParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + + _sensorFusionParams = new SensorFusionParameters + { + RevisePower = context.RequestData.ReadInt32(), + ReviseRange = context.RequestData.ReadInt32(), + }; + + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sensorFusionParams.RevisePower, _sensorFusionParams.ReviseRange }); + + return ResultCode.Success; + } + + [CommandCmif(71)] + // GetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float RevisePower, float ReviseRange) + public ResultCode GetSixAxisSensorFusionParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_sensorFusionParams.RevisePower); + context.ResponseData.Write(_sensorFusionParams.ReviseRange); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sensorFusionParams.RevisePower, _sensorFusionParams.ReviseRange }); + + return ResultCode.Success; + } + + [CommandCmif(72)] + // ResetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode ResetSixAxisSensorFusionParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + _sensorFusionParams.RevisePower = 0; + _sensorFusionParams.ReviseRange = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sensorFusionParams.RevisePower, _sensorFusionParams.ReviseRange }); + + return ResultCode.Success; + } + + [CommandCmif(73)] + // SetAccelerometerParameters(nn::hid::SixAxisSensorHandle, float X, float Y, nn::applet::AppletResourceUserId) + public ResultCode SetAccelerometerParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + + _accelerometerParams = new AccelerometerParameters + { + X = context.RequestData.ReadInt32(), + Y = context.RequestData.ReadInt32(), + }; + + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerParams.X, _accelerometerParams.Y }); + + return ResultCode.Success; + } + + [CommandCmif(74)] + // GetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float X, float Y + public ResultCode GetAccelerometerParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_accelerometerParams.X); + context.ResponseData.Write(_accelerometerParams.Y); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerParams.X, _accelerometerParams.Y }); + + return ResultCode.Success; + } + + [CommandCmif(75)] + // ResetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode ResetAccelerometerParameters(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + _accelerometerParams.X = 0; + _accelerometerParams.Y = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerParams.X, _accelerometerParams.Y }); + + return ResultCode.Success; + } + + [CommandCmif(76)] + // SetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, uint PlayMode, nn::applet::AppletResourceUserId) + public ResultCode SetAccelerometerPlayMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + _accelerometerPlayMode = context.RequestData.ReadUInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerPlayMode }); + + return ResultCode.Success; + } + + [CommandCmif(77)] + // GetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> uint PlayMode + public ResultCode GetAccelerometerPlayMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_accelerometerPlayMode); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerPlayMode }); + + return ResultCode.Success; + } + + [CommandCmif(78)] + // ResetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode ResetAccelerometerPlayMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + _accelerometerPlayMode = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerPlayMode }); + + return ResultCode.Success; + } + + [CommandCmif(79)] + // SetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, uint GyroscopeZeroDriftMode, nn::applet::AppletResourceUserId) + public ResultCode SetGyroscopeZeroDriftMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + _gyroscopeZeroDriftMode = (GyroscopeZeroDriftMode)context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode }); + + return ResultCode.Success; + } + + [CommandCmif(80)] + // GetGyroscopeZeroDriftMode(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> int GyroscopeZeroDriftMode + public ResultCode GetGyroscopeZeroDriftMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write((int)_gyroscopeZeroDriftMode); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode }); + + return ResultCode.Success; + } + + [CommandCmif(81)] + // ResetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode ResetGyroscopeZeroDriftMode(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + _gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard; + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode }); + + return ResultCode.Success; + } + + [CommandCmif(82)] + // IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest + public ResultCode IsSixAxisSensorAtRest(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + bool isAtRest = true; + + context.ResponseData.Write(isAtRest); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, isAtRest }); + + return ResultCode.Success; + } + + [CommandCmif(83)] // 6.0.0+ + // IsFirmwareUpdateAvailableForSixAxisSensor(nn::hid::AppletResourceUserId, nn::hid::SixAxisSensorHandle, pid) -> bool UpdateAvailable + public ResultCode IsFirmwareUpdateAvailableForSixAxisSensor(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor }); + + return ResultCode.Success; + } + + [CommandCmif(84)] // 13.0.0+ + // EnableSixAxisSensorUnalteredPassthrough(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle, u8 enabled) + public ResultCode EnableSixAxisSensorUnalteredPassthrough(ServiceCtx context) + { + _isSixAxisSensorUnalteredPassthroughEnabled = context.RequestData.ReadUInt32() != 0; + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isSixAxisSensorUnalteredPassthroughEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(85)] // 13.0.0+ + // IsSixAxisSensorUnalteredPassthroughEnabled(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> u8 enabled + public ResultCode IsSixAxisSensorUnalteredPassthroughEnabled(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_isSixAxisSensorUnalteredPassthroughEnabled); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle }); + + return ResultCode.Success; + } + + [CommandCmif(87)] // 13.0.0+ + // LoadSixAxisSensorCalibrationParameter(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle, u64 unknown) + public ResultCode LoadSixAxisSensorCalibrationParameter(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + // TODO: CalibrationParameter have to be determined. + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle }); + + return ResultCode.Success; + } + + [CommandCmif(88)] // 13.0.0+ + // GetSixAxisSensorIcInformation(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> u64 unknown + public ResultCode GetSixAxisSensorIcInformation(ServiceCtx context) + { + int sixAxisSensorHandle = context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + // TODO: IcInformation have to be determined. + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle }); + + return ResultCode.Success; + } + + [CommandCmif(91)] + // ActivateGesture(nn::applet::AppletResourceUserId, int Unknown0) + public ResultCode ActivateGesture(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + int unknown0 = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0 }); + + return ResultCode.Success; + } + + [CommandCmif(100)] + // SetSupportedNpadStyleSet(pid, nn::applet::AppletResourceUserId, nn::hid::NpadStyleTag) + public ResultCode SetSupportedNpadStyleSet(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + ControllerType type = (ControllerType)context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { pid, appletResourceUserId, type }); + + context.Device.Hid.Npads.SupportedStyleSets = type; + + return ResultCode.Success; + } + + [CommandCmif(101)] + // GetSupportedNpadStyleSet(pid, nn::applet::AppletResourceUserId) -> uint nn::hid::NpadStyleTag + public ResultCode GetSupportedNpadStyleSet(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong pid = context.Request.HandleDesc.PId; +#pragma warning restore IDE0059 + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write((int)context.Device.Hid.Npads.SupportedStyleSets); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, context.Device.Hid.Npads.SupportedStyleSets }); + + return ResultCode.Success; + } + + [CommandCmif(102)] + // SetSupportedNpadIdType(nn::applet::AppletResourceUserId, array) + public ResultCode SetSupportedNpadIdType(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long appletResourceUserId = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + ulong arrayPosition = context.Request.PtrBuff[0].Position; + ulong arraySize = context.Request.PtrBuff[0].Size; + + ReadOnlySpan supportedPlayerIds = MemoryMarshal.Cast(context.Memory.GetSpan(arrayPosition, (int)arraySize)); + + context.Device.Hid.Npads.ClearSupportedPlayers(); + + for (int i = 0; i < supportedPlayerIds.Length; ++i) + { + if (HidUtils.IsValidNpadIdType(supportedPlayerIds[i])) + { + context.Device.Hid.Npads.SetSupportedPlayer(HidUtils.GetIndexFromNpadIdType(supportedPlayerIds[i])); + } + } + + Logger.Stub?.PrintStub(LogClass.ServiceHid, $"{supportedPlayerIds.Length} Players: " + string.Join(",", supportedPlayerIds.ToArray())); + + return ResultCode.Success; + } + + [CommandCmif(103)] + // ActivateNpad(nn::applet::AppletResourceUserId) + public ResultCode ActivateNpad(ServiceCtx context) + { + return ActiveNpadImpl(context); + } + + [CommandCmif(104)] + // DeactivateNpad(nn::applet::AppletResourceUserId) + public ResultCode DeactivateNpad(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.Device.Hid.Npads.Active = false; + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(106)] + // AcquireNpadStyleSetUpdateEventHandle(nn::applet::AppletResourceUserId, uint, ulong) -> nn::sf::NativeHandle + public ResultCode AcquireNpadStyleSetUpdateEventHandle(ServiceCtx context) + { + PlayerIndex npadId = HidUtils.GetIndexFromNpadIdType((NpadIdType)context.RequestData.ReadInt32()); + long appletResourceUserId = context.RequestData.ReadInt64(); + long npadStyleSet = context.RequestData.ReadInt64(); + + KEvent evnt = context.Device.Hid.Npads.GetStyleSetUpdateEvent(npadId); + if (context.Process.HandleTable.GenerateHandle(evnt.ReadableEvent, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + // Games expect this event to be signaled after calling this function + evnt.ReadableEvent.Signal(); + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadId, npadStyleSet }); + + return ResultCode.Success; + } + + [CommandCmif(107)] + // DisconnectNpad(nn::applet::AppletResourceUserId, uint NpadIdType) + public ResultCode DisconnectNpad(ServiceCtx context) + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType }); + + return ResultCode.Success; + } + + [CommandCmif(108)] + // GetPlayerLedPattern(u32 npad_id) -> u64 led_pattern + public ResultCode GetPlayerLedPattern(ServiceCtx context) + { + NpadIdType npadId = (NpadIdType)context.RequestData.ReadUInt32(); + + ulong ledPattern = npadId switch + { + NpadIdType.Player1 => 0b0001, + NpadIdType.Player2 => 0b0011, + NpadIdType.Player3 => 0b0111, + NpadIdType.Player4 => 0b1111, + NpadIdType.Player5 => 0b1001, + NpadIdType.Player6 => 0b0101, + NpadIdType.Player7 => 0b1101, + NpadIdType.Player8 => 0b0110, + NpadIdType.Unknown => 0b0000, + NpadIdType.Handheld => 0b0000, + _ => throw new InvalidOperationException($"{nameof(npadId)} contains an invalid value: {npadId}"), + }; + + context.ResponseData.Write(ledPattern); + + return ResultCode.Success; + } + + [CommandCmif(109)] // 5.0.0+ + // ActivateNpadWithRevision(nn::applet::AppletResourceUserId, ulong revision) + public ResultCode ActivateNpadWithRevision(ServiceCtx context) + { + ulong revision = context.RequestData.ReadUInt64(); + + return ActiveNpadImpl(context, revision); + } + + private ResultCode ActiveNpadImpl(ServiceCtx context, ulong revision = 0) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.Device.Hid.Npads.Active = true; + + // Initialize entries to avoid issues with some games. + + List emptyGamepadInputs = new(); + List emptySixAxisInputs = new(); + + for (int player = 0; player < NpadDevices.MaxControllers; player++) + { + GamepadInput gamepadInput = new(); + SixAxisInput sixaxisInput = new(); + + gamepadInput.PlayerId = (PlayerIndex)player; + sixaxisInput.PlayerId = (PlayerIndex)player; + + sixaxisInput.Orientation = new float[9]; + + emptyGamepadInputs.Add(gamepadInput); + emptySixAxisInputs.Add(sixaxisInput); + } + + for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++) + { + context.Device.Hid.Npads.Update(emptyGamepadInputs); + context.Device.Hid.Npads.UpdateSixAxis(emptySixAxisInputs); + } + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, revision }); + + return ResultCode.Success; + } + + [CommandCmif(120)] + // SetNpadJoyHoldType(nn::applet::AppletResourceUserId, ulong NpadJoyHoldType) + public ResultCode SetNpadJoyHoldType(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long appletResourceUserId = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + + NpadJoyHoldType npadJoyHoldType = (NpadJoyHoldType)context.RequestData.ReadUInt64(); + + if (npadJoyHoldType > NpadJoyHoldType.Horizontal) + { + throw new InvalidOperationException($"{nameof(npadJoyHoldType)} contains an invalid value: {npadJoyHoldType}"); + } + + foreach (PlayerIndex playerIndex in context.Device.Hid.Npads.GetSupportedPlayers()) + { + if (HidUtils.GetNpadIdTypeFromIndex(playerIndex) > NpadIdType.Handheld) + { + return ResultCode.InvalidNpadIdType; + } + } + + context.Device.Hid.Npads.JoyHold = npadJoyHoldType; + + return ResultCode.Success; + } + + [CommandCmif(121)] + // GetNpadJoyHoldType(nn::applet::AppletResourceUserId) -> ulong NpadJoyHoldType + public ResultCode GetNpadJoyHoldType(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long appletResourceUserId = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + + foreach (PlayerIndex playerIndex in context.Device.Hid.Npads.GetSupportedPlayers()) + { + if (HidUtils.GetNpadIdTypeFromIndex(playerIndex) > NpadIdType.Handheld) + { + return ResultCode.InvalidNpadIdType; + } + } + + context.ResponseData.Write((ulong)context.Device.Hid.Npads.JoyHold); + + return ResultCode.Success; + } + + [CommandCmif(122)] + // SetNpadJoyAssignmentModeSingleByDefault(uint HidControllerId, nn::applet::AppletResourceUserId) + public ResultCode SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx context) + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32(); + context.RequestData.BaseStream.Position += 4; // Padding +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long appletResourceUserId = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + + if (HidUtils.IsValidNpadIdType(npadIdType)) + { + context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single; + } + + return ResultCode.Success; + } + + [CommandCmif(123)] + // SetNpadJoyAssignmentModeSingle(uint npadIdType, nn::applet::AppletResourceUserId, uint npadJoyDeviceType) + public ResultCode SetNpadJoyAssignmentModeSingle(ServiceCtx context) + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadUInt32(); + + if (HidUtils.IsValidNpadIdType(npadIdType)) + { + SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out _, out _); + } + + return ResultCode.Success; + } + + [CommandCmif(124)] + // SetNpadJoyAssignmentModeDual(uint npadIdType, nn::applet::AppletResourceUserId) + public ResultCode SetNpadJoyAssignmentModeDual(ServiceCtx context) + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32(); + context.RequestData.BaseStream.Position += 4; // Padding +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long appletResourceUserId = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + + if (HidUtils.IsValidNpadIdType(npadIdType)) + { + context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Dual; + } + + return ResultCode.Success; + } + + [CommandCmif(125)] + // MergeSingleJoyAsDualJoy(uint npadIdType0, uint npadIdType1, nn::applet::AppletResourceUserId) + public ResultCode MergeSingleJoyAsDualJoy(ServiceCtx context) + { + NpadIdType npadIdType0 = (NpadIdType)context.RequestData.ReadUInt32(); + NpadIdType npadIdType1 = (NpadIdType)context.RequestData.ReadUInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + if (HidUtils.IsValidNpadIdType(npadIdType0) && HidUtils.IsValidNpadIdType(npadIdType1)) + { + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType0, npadIdType1 }); + } + + return ResultCode.Success; + } + + [CommandCmif(126)] + // StartLrAssignmentMode(nn::applet::AppletResourceUserId) + public ResultCode StartLrAssignmentMode(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(127)] + // StopLrAssignmentMode(nn::applet::AppletResourceUserId) + public ResultCode StopLrAssignmentMode(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(128)] + // SetNpadHandheldActivationMode(nn::applet::AppletResourceUserId, long HidNpadHandheldActivationMode) + public ResultCode SetNpadHandheldActivationMode(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + _npadHandheldActivationMode = (NpadHandheldActivationMode)context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadHandheldActivationMode }); + + return ResultCode.Success; + } + + [CommandCmif(129)] + // GetNpadHandheldActivationMode(nn::applet::AppletResourceUserId) -> long HidNpadHandheldActivationMode + public ResultCode GetNpadHandheldActivationMode(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write((long)_npadHandheldActivationMode); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadHandheldActivationMode }); + + return ResultCode.Success; + } + + [CommandCmif(130)] + // SwapNpadAssignment(uint OldNpadAssignment, uint NewNpadAssignment, nn::applet::AppletResourceUserId) + public ResultCode SwapNpadAssignment(ServiceCtx context) + { + int oldNpadAssignment = context.RequestData.ReadInt32(); + int newNpadAssignment = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, oldNpadAssignment, newNpadAssignment }); + + return ResultCode.Success; + } + + [CommandCmif(131)] + // IsUnintendedHomeButtonInputProtectionEnabled(uint Unknown0, nn::applet::AppletResourceUserId) -> bool IsEnabled + public ResultCode IsUnintendedHomeButtonInputProtectionEnabled(ServiceCtx context) + { + uint unknown0 = context.RequestData.ReadUInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_unintendedHomeButtonInputProtectionEnabled); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0, _unintendedHomeButtonInputProtectionEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(132)] + // EnableUnintendedHomeButtonInputProtection(bool Enable, uint Unknown0, nn::applet::AppletResourceUserId) + public ResultCode EnableUnintendedHomeButtonInputProtection(ServiceCtx context) + { + _unintendedHomeButtonInputProtectionEnabled = context.RequestData.ReadBoolean(); + uint unknown0 = context.RequestData.ReadUInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0, _unintendedHomeButtonInputProtectionEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(133)] // 5.0.0+ + // SetNpadJoyAssignmentModeSingleWithDestination(uint npadIdType, uint npadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool npadIdTypeIsSet, uint npadIdTypeSet + public ResultCode SetNpadJoyAssignmentModeSingleWithDestination(ServiceCtx context) + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadInt32(); + NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + long appletResourceUserId = context.RequestData.ReadInt64(); + + if (HidUtils.IsValidNpadIdType(npadIdType)) + { + SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet); + + if (npadIdTypeIsSet) + { + context.ResponseData.Write(npadIdTypeIsSet); + context.ResponseData.Write((uint)npadIdTypeSet); + } + } + + return ResultCode.Success; + } + + private void SetNpadJoyAssignmentModeSingleWithDestinationImpl(ServiceCtx context, NpadIdType npadIdType, long appletResourceUserId, NpadJoyDeviceType npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet) + { + npadIdTypeSet = default; + npadIdTypeIsSet = false; + + context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single; + + // TODO: Service seems to use the npadJoyDeviceType to find the nearest other Npad available and merge them to dual. + // If one is found, it returns the npadIdType of the other Npad and a bool. + // If not, it returns nothing. + } + + [CommandCmif(134)] // 6.1.0+ + // SetNpadUseAnalogStickUseCenterClamp(bool Enable, nn::applet::AppletResourceUserId) + public ResultCode SetNpadUseAnalogStickUseCenterClamp(ServiceCtx context) + { + ulong pid = context.RequestData.ReadUInt64(); + _npadAnalogStickCenterClampEnabled = context.RequestData.ReadUInt32() != 0; + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { pid, appletResourceUserId, _npadAnalogStickCenterClampEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(200)] + // GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo + public ResultCode GetVibrationDeviceInfo(ServiceCtx context) + { + VibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct(); + NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType; + NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId; + + if (deviceType < NpadStyleIndex.System || deviceType >= NpadStyleIndex.FullKey) + { + if (!HidUtils.IsValidNpadIdType(npadIdType)) + { + return ResultCode.InvalidNpadIdType; + } + + if (deviceHandle.Position > 1) + { + return ResultCode.InvalidDeviceIndex; + } + + VibrationDeviceType vibrationDeviceType = VibrationDeviceType.None; + + if (Enum.IsDefined(deviceType)) + { + vibrationDeviceType = VibrationDeviceType.LinearResonantActuator; + } + else if ((uint)deviceType == 8) + { + vibrationDeviceType = VibrationDeviceType.GcErm; + } + + VibrationDevicePosition vibrationDevicePosition = VibrationDevicePosition.None; + + if (vibrationDeviceType == VibrationDeviceType.LinearResonantActuator) + { + if (deviceHandle.Position == 0) + { + vibrationDevicePosition = VibrationDevicePosition.Left; + } + else if (deviceHandle.Position == 1) + { + vibrationDevicePosition = VibrationDevicePosition.Right; + } + else + { + throw new InvalidOperationException($"{nameof(deviceHandle.Position)} contains an invalid value: {deviceHandle.Position}"); + } + } + + VibrationDeviceValue deviceInfo = new() + { + DeviceType = vibrationDeviceType, + Position = vibrationDevicePosition, + }; + + context.ResponseData.WriteStruct(deviceInfo); + + return ResultCode.Success; + } + + return ResultCode.InvalidNpadDeviceType; + } + + [CommandCmif(201)] + // SendVibrationValue(nn::hid::VibrationDeviceHandle, nn::hid::VibrationValue, nn::applet::AppletResourceUserId) + public ResultCode SendVibrationValue(ServiceCtx context) + { + VibrationDeviceHandle deviceHandle = new() + { + DeviceType = context.RequestData.ReadByte(), + PlayerId = context.RequestData.ReadByte(), + Position = context.RequestData.ReadByte(), + Reserved = context.RequestData.ReadByte(), + }; + + VibrationValue vibrationValue = new() + { + AmplitudeLow = context.RequestData.ReadSingle(), + FrequencyLow = context.RequestData.ReadSingle(), + AmplitudeHigh = context.RequestData.ReadSingle(), + FrequencyHigh = context.RequestData.ReadSingle(), + }; + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long appletResourceUserId = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + + Dictionary dualVibrationValues = new() + { + [deviceHandle.Position] = vibrationValue, + }; + + context.Device.Hid.Npads.UpdateRumbleQueue((PlayerIndex)deviceHandle.PlayerId, dualVibrationValues); + + return ResultCode.Success; + } + + [CommandCmif(202)] + // GetActualVibrationValue(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationValue + public ResultCode GetActualVibrationValue(ServiceCtx context) + { + VibrationDeviceHandle deviceHandle = new() + { + DeviceType = context.RequestData.ReadByte(), + PlayerId = context.RequestData.ReadByte(), + Position = context.RequestData.ReadByte(), + Reserved = context.RequestData.ReadByte(), + }; + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long appletResourceUserId = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + + VibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position); + + context.ResponseData.Write(vibrationValue.AmplitudeLow); + context.ResponseData.Write(vibrationValue.FrequencyLow); + context.ResponseData.Write(vibrationValue.AmplitudeHigh); + context.ResponseData.Write(vibrationValue.FrequencyHigh); + + return ResultCode.Success; + } + + [CommandCmif(203)] + // CreateActiveVibrationDeviceList() -> object + public ResultCode CreateActiveVibrationDeviceList(ServiceCtx context) + { + MakeObject(context, new IActiveApplicationDeviceList()); + + return ResultCode.Success; + } + + [CommandCmif(204)] + // PermitVibration(bool Enable) + public ResultCode PermitVibration(ServiceCtx context) + { + _vibrationPermitted = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { _vibrationPermitted }); + + return ResultCode.Success; + } + + [CommandCmif(205)] + // IsVibrationPermitted() -> bool IsEnabled + public ResultCode IsVibrationPermitted(ServiceCtx context) + { + context.ResponseData.Write(_vibrationPermitted); + + return ResultCode.Success; + } + + [CommandCmif(206)] + // SendVibrationValues(nn::applet::AppletResourceUserId, buffer, type: 9>, buffer, type: 9>) + public ResultCode SendVibrationValues(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long appletResourceUserId = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + + byte[] vibrationDeviceHandleBuffer = new byte[context.Request.PtrBuff[0].Size]; + + context.Memory.Read(context.Request.PtrBuff[0].Position, vibrationDeviceHandleBuffer); + + byte[] vibrationValueBuffer = new byte[context.Request.PtrBuff[1].Size]; + + context.Memory.Read(context.Request.PtrBuff[1].Position, vibrationValueBuffer); + + Span deviceHandles = MemoryMarshal.Cast(vibrationDeviceHandleBuffer); + Span vibrationValues = MemoryMarshal.Cast(vibrationValueBuffer); + + if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length) + { + Dictionary dualVibrationValues = new(); + PlayerIndex currentIndex = (PlayerIndex)deviceHandles[0].PlayerId; + + for (int deviceCounter = 0; deviceCounter < deviceHandles.Length; deviceCounter++) + { + PlayerIndex index = (PlayerIndex)deviceHandles[deviceCounter].PlayerId; + byte position = deviceHandles[deviceCounter].Position; + + if (index != currentIndex || dualVibrationValues.Count == 2) + { + context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues); + dualVibrationValues = new Dictionary(); + } + + dualVibrationValues[position] = vibrationValues[deviceCounter]; + currentIndex = index; + } + + context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues); + } + + return ResultCode.Success; + } + + [CommandCmif(207)] // 4.0.0+ + // SendVibrationGcErmCommand(nn::hid::VibrationDeviceHandle, nn::hid::VibrationGcErmCommand, nn::applet::AppletResourceUserId) + public ResultCode SendVibrationGcErmCommand(ServiceCtx context) + { + int vibrationDeviceHandle = context.RequestData.ReadInt32(); + long vibrationGcErmCommand = context.RequestData.ReadInt64(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, vibrationDeviceHandle, vibrationGcErmCommand }); + + return ResultCode.Success; + } + + [CommandCmif(208)] // 4.0.0+ + // GetActualVibrationGcErmCommand(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationGcErmCommand + public ResultCode GetActualVibrationGcErmCommand(ServiceCtx context) + { + int vibrationDeviceHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_vibrationGcErmCommand); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, vibrationDeviceHandle, _vibrationGcErmCommand }); + + return ResultCode.Success; + } + + [CommandCmif(209)] // 4.0.0+ + // BeginPermitVibrationSession(nn::applet::AppletResourceUserId) + public ResultCode BeginPermitVibrationSession(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(210)] // 4.0.0+ + // EndPermitVibrationSession() + public ResultCode EndPermitVibrationSession(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceHid); + + return ResultCode.Success; + } + + [CommandCmif(211)] // 7.0.0+ + // IsVibrationDeviceMounted(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) + public ResultCode IsVibrationDeviceMounted(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + int vibrationDeviceHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + + // NOTE: Service use vibrationDeviceHandle to get the PlayerIndex. + // And return false if (npadIdType >= (NpadIdType)8 && npadIdType != NpadIdType.Handheld && npadIdType != NpadIdType.Unknown) + + context.ResponseData.Write(true); + + return ResultCode.Success; + } + + [CommandCmif(300)] + // ActivateConsoleSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode ActivateConsoleSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(301)] + // StartConsoleSixAxisSensor(nn::hid::ConsoleSixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode StartConsoleSixAxisSensor(ServiceCtx context) + { + int consoleSixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, consoleSixAxisSensorHandle }); + + return ResultCode.Success; + } + + [CommandCmif(302)] + // StopConsoleSixAxisSensor(nn::hid::ConsoleSixAxisSensorHandle, nn::applet::AppletResourceUserId) + public ResultCode StopConsoleSixAxisSensor(ServiceCtx context) + { + int consoleSixAxisSensorHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, consoleSixAxisSensorHandle }); + + return ResultCode.Success; + } + + [CommandCmif(303)] // 5.0.0+ + // ActivateSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode ActivateSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(304)] // 5.0.0+ + // StartSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode StartSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(305)] // 5.0.0+ + // StopSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode StopSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(306)] // 5.0.0+ + // InitializeSevenSixAxisSensor(array, ulong Counter0, array, ulong Counter1, nn::applet::AppletResourceUserId) + public ResultCode InitializeSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + long counter0 = context.RequestData.ReadInt64(); + long counter1 = context.RequestData.ReadInt64(); + + // TODO: Determine if array is a buffer or not... + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, counter0, counter1 }); + + return ResultCode.Success; + } + + [CommandCmif(307)] // 5.0.0+ + // FinalizeSevenSixAxisSensor(nn::applet::AppletResourceUserId) + public ResultCode FinalizeSevenSixAxisSensor(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(308)] // 5.0.0+ + // SetSevenSixAxisSensorFusionStrength(float Strength, nn::applet::AppletResourceUserId) + public ResultCode SetSevenSixAxisSensorFusionStrength(ServiceCtx context) + { + _sevenSixAxisSensorFusionStrength = context.RequestData.ReadSingle(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _sevenSixAxisSensorFusionStrength }); + + return ResultCode.Success; + } + + [CommandCmif(309)] // 5.0.0+ + // GetSevenSixAxisSensorFusionStrength(nn::applet::AppletResourceUserId) -> float Strength + public ResultCode GetSevenSixAxisSensorFusionStrength(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(_sevenSixAxisSensorFusionStrength); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _sevenSixAxisSensorFusionStrength }); + + return ResultCode.Success; + } + + [CommandCmif(310)] // 6.0.0+ + // ResetSevenSixAxisSensorTimestamp(pid, nn::applet::AppletResourceUserId) + public ResultCode ResetSevenSixAxisSensorTimestamp(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(400)] + // IsUsbFullKeyControllerEnabled() -> bool IsEnabled + public ResultCode IsUsbFullKeyControllerEnabled(ServiceCtx context) + { + context.ResponseData.Write(_usbFullKeyControllerEnabled); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { _usbFullKeyControllerEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(401)] + // EnableUsbFullKeyController(bool Enable) + public ResultCode EnableUsbFullKeyController(ServiceCtx context) + { + _usbFullKeyControllerEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { _usbFullKeyControllerEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(402)] + // IsUsbFullKeyControllerConnected(uint Unknown0) -> bool Connected + public ResultCode IsUsbFullKeyControllerConnected(ServiceCtx context) + { + int unknown0 = context.RequestData.ReadInt32(); + + context.ResponseData.Write(true); //FullKeyController is always connected ? + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { unknown0, Connected = true }); + + return ResultCode.Success; + } + + [CommandCmif(403)] // 4.0.0+ + // HasBattery(uint NpadId) -> bool HasBattery + public ResultCode HasBattery(ServiceCtx context) + { + int npadId = context.RequestData.ReadInt32(); + + context.ResponseData.Write(true); //Npad always got a battery ? + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadId, HasBattery = true }); + + return ResultCode.Success; + } + + [CommandCmif(404)] // 4.0.0+ + // HasLeftRightBattery(uint NpadId) -> bool HasLeftBattery, bool HasRightBattery + public ResultCode HasLeftRightBattery(ServiceCtx context) + { + int npadId = context.RequestData.ReadInt32(); + + context.ResponseData.Write(true); //Npad always got a left battery ? + context.ResponseData.Write(true); //Npad always got a right battery ? + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadId, HasLeftBattery = true, HasRightBattery = true }); + + return ResultCode.Success; + } + + [CommandCmif(405)] // 4.0.0+ + // GetNpadInterfaceType(uint NpadId) -> uchar InterfaceType + public ResultCode GetNpadInterfaceType(ServiceCtx context) + { + int npadId = context.RequestData.ReadInt32(); + + context.ResponseData.Write((byte)0); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadId, NpadInterfaceType = 0 }); + + return ResultCode.Success; + } + + [CommandCmif(406)] // 4.0.0+ + // GetNpadLeftRightInterfaceType(uint NpadId) -> uchar LeftInterfaceType, uchar RightInterfaceType + public ResultCode GetNpadLeftRightInterfaceType(ServiceCtx context) + { + int npadId = context.RequestData.ReadInt32(); + + context.ResponseData.Write((byte)0); + context.ResponseData.Write((byte)0); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadId, LeftInterfaceType = 0, RightInterfaceType = 0 }); + + return ResultCode.Success; + } + + [CommandCmif(500)] // 5.0.0+ + // GetPalmaConnectionHandle(uint Unknown0, nn::applet::AppletResourceUserId) -> nn::hid::PalmaConnectionHandle + public ResultCode GetPalmaConnectionHandle(ServiceCtx context) + { + int unknown0 = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + int palmaConnectionHandle = 0; + + context.ResponseData.Write(palmaConnectionHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0, palmaConnectionHandle }); + + return ResultCode.Success; + } + + [CommandCmif(501)] // 5.0.0+ + // InitializePalma(nn::hid::PalmaConnectionHandle) + public ResultCode InitializePalma(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [CommandCmif(502)] // 5.0.0+ + // AcquirePalmaOperationCompleteEvent(nn::hid::PalmaConnectionHandle) -> nn::sf::NativeHandle + public ResultCode AcquirePalmaOperationCompleteEvent(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + if (context.Process.HandleTable.GenerateHandle(_palmaOperationCompleteEvent.ReadableEvent, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + return ResultCode.Success; + } + + [CommandCmif(503)] // 5.0.0+ + // GetPalmaOperationInfo(nn::hid::PalmaConnectionHandle) -> long Unknown0, buffer + public ResultCode GetPalmaOperationInfo(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + long unknown0 = 0; //Counter? + + context.ResponseData.Write(unknown0); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0 }); + + return ResultCode.Success; + } + + [CommandCmif(504)] // 5.0.0+ + // PlayPalmaActivity(nn::hid::PalmaConnectionHandle, ulong Unknown0) + public ResultCode PlayPalmaActivity(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + long unknown0 = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0 }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [CommandCmif(505)] // 5.0.0+ + // SetPalmaFrModeType(nn::hid::PalmaConnectionHandle, ulong FrModeType) + public ResultCode SetPalmaFrModeType(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + long frModeType = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, frModeType }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [CommandCmif(506)] // 5.0.0+ + // ReadPalmaStep(nn::hid::PalmaConnectionHandle) + public ResultCode ReadPalmaStep(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + return ResultCode.Success; + } + + [CommandCmif(507)] // 5.0.0+ + // EnablePalmaStep(nn::hid::PalmaConnectionHandle, bool Enable) + public ResultCode EnablePalmaStep(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + bool enabledPalmaStep = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, enabledPalmaStep }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [CommandCmif(508)] // 5.0.0+ + // ResetPalmaStep(nn::hid::PalmaConnectionHandle) + public ResultCode ResetPalmaStep(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [CommandCmif(509)] // 5.0.0+ + // ReadPalmaApplicationSection(nn::hid::PalmaConnectionHandle, ulong Unknown0, ulong Unknown1) + public ResultCode ReadPalmaApplicationSection(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + long unknown0 = context.RequestData.ReadInt64(); + long unknown1 = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0, unknown1 }); + + return ResultCode.Success; + } + + [CommandCmif(510)] // 5.0.0+ + // WritePalmaApplicationSection(nn::hid::PalmaConnectionHandle, ulong Unknown0, ulong Unknown1, nn::hid::PalmaApplicationSectionAccessBuffer) + public ResultCode WritePalmaApplicationSection(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + long unknown0 = context.RequestData.ReadInt64(); + long unknown1 = context.RequestData.ReadInt64(); + // nn::hid::PalmaApplicationSectionAccessBuffer cast is unknown + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0, unknown1 }); + + _palmaOperationCompleteEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + [CommandCmif(511)] // 5.0.0+ + // ReadPalmaUniqueCode(nn::hid::PalmaConnectionHandle) + public ResultCode ReadPalmaUniqueCode(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + return ResultCode.Success; + } + + [CommandCmif(512)] // 5.0.0+ + // SetPalmaUniqueCodeInvalid(nn::hid::PalmaConnectionHandle) + public ResultCode SetPalmaUniqueCodeInvalid(ServiceCtx context) + { + int palmaConnectionHandle = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle }); + + return ResultCode.Success; + } + + [CommandCmif(522)] // 5.1.0+ + // SetIsPalmaAllConnectable(nn::applet::AppletResourceUserId, bool, pid) + public ResultCode SetIsPalmaAllConnectable(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + long unknownBool = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknownBool }); + + return ResultCode.Success; + } + + [CommandCmif(525)] // 5.1.0+ + // SetPalmaBoostMode(bool) + public ResultCode SetPalmaBoostMode(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + return ResultCode.Success; + } + + [CommandCmif(1000)] + // SetNpadCommunicationMode(long CommunicationMode, nn::applet::AppletResourceUserId) + public ResultCode SetNpadCommunicationMode(ServiceCtx context) + { + _npadCommunicationMode = context.RequestData.ReadInt64(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadCommunicationMode }); + + return ResultCode.Success; + } + + [CommandCmif(1001)] + // GetNpadCommunicationMode() -> long CommunicationMode + public ResultCode GetNpadCommunicationMode(ServiceCtx context) + { + context.ResponseData.Write(_npadCommunicationMode); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { _npadCommunicationMode }); + + return ResultCode.Success; + } + + [CommandCmif(1002)] // 9.0.0+ + // SetTouchScreenConfiguration(nn::hid::TouchScreenConfigurationForNx, nn::applet::AppletResourceUserId) + public ResultCode SetTouchScreenConfiguration(ServiceCtx context) + { + long touchScreenConfigurationForNx = context.RequestData.ReadInt64(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, touchScreenConfigurationForNx }); + + return ResultCode.Success; + } + + [CommandCmif(1004)] // 17.0.0+ + // SetTouchScreenResolution(int width, int height, nn::applet::AppletResourceUserId) + public ResultCode SetTouchScreenResolution(ServiceCtx context) + { + int width = context.RequestData.ReadInt32(); + int height = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { width, height, appletResourceUserId }); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs new file mode 100644 index 00000000..0b4eba94 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs @@ -0,0 +1,76 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Hid.HidServer; +using Ryujinx.HLE.HOS.Services.Hid.Types; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("hid:sys")] + class IHidSystemServer : IpcService + { + public IHidSystemServer(ServiceCtx context) { } + + [CommandCmif(303)] + // ApplyNpadSystemCommonPolicy(u64) + public ResultCode ApplyNpadSystemCommonPolicy(ServiceCtx context) + { + ulong commonPolicy = context.RequestData.ReadUInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { commonPolicy }); + + return ResultCode.Success; + } + + [CommandCmif(306)] + // GetLastActiveNpad(u32) -> u8, u8 + public ResultCode GetLastActiveNpad(ServiceCtx context) + { + // TODO: RequestData seems to have garbage data, reading an extra uint seems to fix the issue. + context.RequestData.ReadUInt32(); + + ResultCode resultCode = GetAppletFooterUiTypeImpl(context, out AppletFooterUiType appletFooterUiType); + + context.ResponseData.Write((byte)appletFooterUiType); + context.ResponseData.Write((byte)0); + + return resultCode; + } + + [CommandCmif(307)] + // GetNpadSystemExtStyle() -> u64 + public ResultCode GetNpadSystemExtStyle(ServiceCtx context) + { + foreach (PlayerIndex playerIndex in context.Device.Hid.Npads.GetSupportedPlayers()) + { + if (HidUtils.GetNpadIdTypeFromIndex(playerIndex) > NpadIdType.Handheld) + { + return ResultCode.InvalidNpadIdType; + } + } + + context.ResponseData.Write((ulong)context.Device.Hid.Npads.SupportedStyleSets); + + return ResultCode.Success; + } + + [CommandCmif(314)] // 9.0.0+ + // GetAppletFooterUiType(u32) -> u8 + public ResultCode GetAppletFooterUiType(ServiceCtx context) + { + ResultCode resultCode = GetAppletFooterUiTypeImpl(context, out AppletFooterUiType appletFooterUiType); + + context.ResponseData.Write((byte)appletFooterUiType); + + return resultCode; + } + + private ResultCode GetAppletFooterUiTypeImpl(ServiceCtx context, out AppletFooterUiType appletFooterUiType) + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32(); + PlayerIndex playerIndex = HidUtils.GetIndexFromNpadIdType(npadIdType); + + appletFooterUiType = context.Device.Hid.SharedMemory.Npads[(int)playerIndex].InternalState.AppletFooterUiType; + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs new file mode 100644 index 00000000..d6531cc1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs @@ -0,0 +1,31 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("hidbus")] + class IHidbusServer : IpcService + { + public IHidbusServer(ServiceCtx context) { } + + [CommandCmif(1)] + // GetBusHandle(nn::hid::NpadIdType, nn::hidbus::BusType, nn::applet::AppletResourceUserId) -> (bool HasHandle, nn::hidbus::BusHandle) +#pragma warning disable CA1822 // Mark member as static + public ResultCode GetBusHandle(ServiceCtx context) +#pragma warning restore CA1822 + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + BusType busType = (BusType)context.RequestData.ReadInt64(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + context.ResponseData.Write(false); + context.ResponseData.BaseStream.Position += 7; // Padding + context.ResponseData.WriteStruct(new BusHandle()); + + Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadIdType, busType, appletResourceUserId }); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs new file mode 100644 index 00000000..c00b083a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Service("xcd:sys")] + class ISystemServer : IpcService + { + public ISystemServer(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs new file mode 100644 index 00000000..a13e77e7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs @@ -0,0 +1,240 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Hid.HidServer; +using Ryujinx.HLE.HOS.Services.Hid.Irs.Types; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs +{ + [Service("irs")] + class IIrSensorServer : IpcService + { + private int _irsensorSharedMemoryHandle = 0; + + public IIrSensorServer(ServiceCtx context) { } + + [CommandCmif(302)] + // ActivateIrsensor(nn::applet::AppletResourceUserId, pid) + public ResultCode ActivateIrsensor(ServiceCtx context) + { + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + // NOTE: This seems to initialize the shared memory for irs service. + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(303)] + // DeactivateIrsensor(nn::applet::AppletResourceUserId, pid) + public ResultCode DeactivateIrsensor(ServiceCtx context) + { + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + // NOTE: This seems to deinitialize the shared memory for irs service. + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(304)] + // GetIrsensorSharedMemoryHandle(nn::applet::AppletResourceUserId, pid) -> handle + public ResultCode GetIrsensorSharedMemoryHandle(ServiceCtx context) + { + // NOTE: Shared memory should use the appletResourceUserId. + // ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + if (_irsensorSharedMemoryHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(context.Device.System.IirsSharedMem, out _irsensorSharedMemoryHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_irsensorSharedMemoryHandle); + + return ResultCode.Success; + } + + [CommandCmif(305)] + // StopImageProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId) + public ResultCode StopImageProcessor(ServiceCtx context) + { + IrCameraHandle irCameraHandle = context.RequestData.ReadStruct(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + CheckCameraHandle(irCameraHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType }); + + return ResultCode.Success; + } + + [CommandCmif(306)] + // RunMomentProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, PackedMomentProcessorConfig) + public ResultCode RunMomentProcessor(ServiceCtx context) + { + IrCameraHandle irCameraHandle = context.RequestData.ReadStruct(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + var packedMomentProcessorConfig = context.RequestData.ReadStruct(); + + CheckCameraHandle(irCameraHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType, packedMomentProcessorConfig.ExposureTime }); + + return ResultCode.Success; + } + + [CommandCmif(307)] + // RunClusteringProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, PackedClusteringProcessorConfig) + public ResultCode RunClusteringProcessor(ServiceCtx context) + { + IrCameraHandle irCameraHandle = context.RequestData.ReadStruct(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + var packedClusteringProcessorConfig = context.RequestData.ReadStruct(); + + CheckCameraHandle(irCameraHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType, packedClusteringProcessorConfig.ExposureTime }); + + return ResultCode.Success; + } + + [CommandCmif(308)] + // RunImageTransferProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, PackedImageTransferProcessorConfig, u64 TransferMemorySize, TransferMemoryHandle) + public ResultCode RunImageTransferProcessor(ServiceCtx context) + { + IrCameraHandle irCameraHandle = context.RequestData.ReadStruct(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + var packedImageTransferProcessorConfig = context.RequestData.ReadStruct(); + + CheckCameraHandle(irCameraHandle); + + // TODO: Handle the Transfer Memory. + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType, packedImageTransferProcessorConfig.ExposureTime }); + + return ResultCode.Success; + } + + [CommandCmif(309)] + // GetImageTransferProcessorState(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId) + public ResultCode GetImageTransferProcessorState(ServiceCtx context) + { + IrCameraHandle irCameraHandle = context.RequestData.ReadStruct(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + // ulong imageTransferBufferAddress = context.Request.ReceiveBuff[0].Position; + ulong imageTransferBufferSize = context.Request.ReceiveBuff[0].Size; + + if (imageTransferBufferSize == 0) + { + return ResultCode.InvalidBufferSize; + } + + CheckCameraHandle(irCameraHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType }); + + // TODO: Uses the buffer to copy the JoyCon IR data (by using a JoyCon driver) and update the following struct. + context.ResponseData.WriteStruct(new ImageTransferProcessorState() + { + SamplingNumber = 0, + AmbientNoiseLevel = 0, + }); + + return ResultCode.Success; + } + + [CommandCmif(310)] + // RunTeraPluginProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, PackedTeraPluginProcessorConfig) + public ResultCode RunTeraPluginProcessor(ServiceCtx context) + { + IrCameraHandle irCameraHandle = context.RequestData.ReadStruct(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + var packedTeraPluginProcessorConfig = context.RequestData.ReadStruct(); + + CheckCameraHandle(irCameraHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType, packedTeraPluginProcessorConfig.RequiredMcuVersion }); + + return ResultCode.Success; + } + + [CommandCmif(311)] + // GetNpadIrCameraHandle(u32) -> nn::irsensor::IrCameraHandle + public ResultCode GetNpadIrCameraHandle(ServiceCtx context) + { + NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32(); + + if (npadIdType > NpadIdType.Player8 && + npadIdType != NpadIdType.Unknown && + npadIdType != NpadIdType.Handheld) + { + return ResultCode.NpadIdOutOfRange; + } + + PlayerIndex irCameraHandle = HidUtils.GetIndexFromNpadIdType(npadIdType); + + context.ResponseData.Write((int)irCameraHandle); + + // NOTE: If the irCameraHandle pointer is null this error is returned, Doesn't occur in our case. + // return ResultCode.HandlePointerIsNull; + + return ResultCode.Success; + } + + [CommandCmif(314)] // 3.0.0+ + // CheckFirmwareVersion(nn::irsensor::IrCameraHandle, nn::irsensor::PackedMcuVersion, nn::applet::AppletResourceUserId, pid) + public ResultCode CheckFirmwareVersion(ServiceCtx context) + { + int irCameraHandle = context.RequestData.ReadInt32(); + short packedMcuVersionMajor = context.RequestData.ReadInt16(); + short packedMcuVersionMinor = context.RequestData.ReadInt16(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle, packedMcuVersionMajor, packedMcuVersionMinor }); + + return ResultCode.Success; + } + + [CommandCmif(318)] // 4.0.0+ + // StopImageProcessorAsync(nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, pid) + public ResultCode StopImageProcessorAsync(ServiceCtx context) + { + int irCameraHandle = context.RequestData.ReadInt32(); + long appletResourceUserId = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle }); + + return ResultCode.Success; + } + + [CommandCmif(319)] // 4.0.0+ + // ActivateIrsensorWithFunctionLevel(nn::applet::AppletResourceUserId, nn::irsensor::PackedFunctionLevel, pid) + public ResultCode ActivateIrsensorWithFunctionLevel(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + long packedFunctionLevel = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, packedFunctionLevel }); + + return ResultCode.Success; + } + + private ResultCode CheckCameraHandle(IrCameraHandle irCameraHandle) + { + if (irCameraHandle.DeviceType == 1 || (PlayerIndex)irCameraHandle.PlayerNumber >= PlayerIndex.Unknown) + { + return ResultCode.InvalidCameraHandle; + } + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs new file mode 100644 index 00000000..5046268c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Irs +{ + [Service("irs:sys")] + class IIrSensorSystemServer : IpcService + { + public IIrSensorSystemServer(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs new file mode 100644 index 00000000..8cee32cd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Irs +{ + public enum ResultCode + { + ModuleId = 205, + ErrorCodeShift = 9, + + Success = 0, + + InvalidCameraHandle = (204 << ErrorCodeShift) | ModuleId, + InvalidBufferSize = (207 << ErrorCodeShift) | ModuleId, + HandlePointerIsNull = (212 << ErrorCodeShift) | ModuleId, + NpadIdOutOfRange = (709 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/ImageTransferProcessorState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/ImageTransferProcessorState.cs new file mode 100644 index 00000000..76a07f79 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/ImageTransferProcessorState.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + struct ImageTransferProcessorState + { + public ulong SamplingNumber; + public uint AmbientNoiseLevel; + public uint Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/IrCameraHandle.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/IrCameraHandle.cs new file mode 100644 index 00000000..a15e445c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/IrCameraHandle.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x4)] + struct IrCameraHandle + { + public byte PlayerNumber; + public byte DeviceType; + public ushort Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedClusteringProcessorConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedClusteringProcessorConfig.cs new file mode 100644 index 00000000..d12513fa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedClusteringProcessorConfig.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x28)] + struct PackedClusteringProcessorConfig + { + public long ExposureTime; + public byte LightTarget; + public byte Gain; + public byte IsNegativeImageUsed; + public byte Reserved1; + public uint Reserved2; + public ushort WindowOfInterestX; + public ushort WindowOfInterestY; + public ushort WindowOfInterestWidth; + public ushort WindowOfInterestHeight; + public uint RequiredMcuVersion; + public uint ObjectPixelCountMin; + public uint ObjectPixelCountMax; + public byte ObjectIntensityMin; + public byte IsExternalLightFilterEnabled; + public ushort Reserved3; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedImageTransferProcessorConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedImageTransferProcessorConfig.cs new file mode 100644 index 00000000..0bc709f2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedImageTransferProcessorConfig.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18)] + struct PackedImageTransferProcessorConfig + { + public long ExposureTime; + public byte LightTarget; + public byte Gain; + public byte IsNegativeImageUsed; + public byte Reserved1; + public uint Reserved2; + public uint RequiredMcuVersion; + public byte Format; + public byte Reserved3; + public ushort Reserved4; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedMomentProcessorConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedMomentProcessorConfig.cs new file mode 100644 index 00000000..f1905ee3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedMomentProcessorConfig.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20)] + struct PackedMomentProcessorConfig + { + public long ExposureTime; + public byte LightTarget; + public byte Gain; + public byte IsNegativeImageUsed; + public byte Reserved1; + public uint Reserved2; + public ushort WindowOfInterestX; + public ushort WindowOfInterestY; + public ushort WindowOfInterestWidth; + public ushort WindowOfInterestHeight; + public uint RequiredMcuVersion; + public byte Preprocess; + public byte PreprocessIntensityThreshold; + public ushort Reserved3; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedTeraPluginProcessorConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedTeraPluginProcessorConfig.cs new file mode 100644 index 00000000..1cc2533a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedTeraPluginProcessorConfig.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8)] + struct PackedTeraPluginProcessorConfig + { + public uint RequiredMcuVersion; + public byte Mode; + public byte Unknown1; + public byte Unknown2; + public byte Unknown3; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs new file mode 100644 index 00000000..488356d4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + enum ResultCode + { + ModuleId = 202, + ErrorCodeShift = 9, + + Success = 0, + + InvalidNpadDeviceType = (122 << ErrorCodeShift) | ModuleId, + InvalidNpadIdType = (123 << ErrorCodeShift) | ModuleId, + InvalidDeviceIndex = (124 << ErrorCodeShift) | ModuleId, + InvalidBufferSize = (131 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/AppletFooterUiType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/AppletFooterUiType.cs new file mode 100644 index 00000000..b9b0c5f9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/AppletFooterUiType.cs @@ -0,0 +1,30 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types +{ + [Flags] + enum AppletFooterUiType : byte + { + None, + HandheldNone, + HandheldJoyConLeftOnly, + HandheldJoyConRightOnly, + HandheldJoyConLeftJoyConRight, + JoyDual, + JoyDualLeftOnly, + JoyDualRightOnly, + JoyLeftHorizontal, + JoyLeftVertical, + JoyRightHorizontal, + JoyRightVertical, + SwitchProController, + CompatibleProController, + CompatibleJoyCon, + LarkHvc1, + LarkHvc2, + LarkNesLeft, + LarkNesRight, + Lucia, + Verification, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/HidVector.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/HidVector.cs new file mode 100644 index 00000000..8d667796 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/HidVector.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types +{ + struct HidVector + { + public float X; + public float Y; + public float Z; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/BusHandle.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/BusHandle.cs new file mode 100644 index 00000000..8e061351 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/BusHandle.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [StructLayout(LayoutKind.Sequential)] + struct BusHandle + { + public int AbstractedPadId; + public byte InternalIndex; + public byte PlayerNumber; + public byte BusTypeId; + public byte IsValid; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/BusType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/BusType.cs new file mode 100644 index 00000000..6c70ac25 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/BusType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum BusType : long + { + LeftJoyRail = 0, + RightJoyRail = 1, + InternalBus = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerKeys.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerKeys.cs new file mode 100644 index 00000000..b43381e6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerKeys.cs @@ -0,0 +1,45 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Flags] + public enum ControllerKeys : long + { + A = 1 << 0, + B = 1 << 1, + X = 1 << 2, + Y = 1 << 3, + LStick = 1 << 4, + RStick = 1 << 5, + L = 1 << 6, + R = 1 << 7, + Zl = 1 << 8, + Zr = 1 << 9, + Plus = 1 << 10, + Minus = 1 << 11, + DpadLeft = 1 << 12, + DpadUp = 1 << 13, + DpadRight = 1 << 14, + DpadDown = 1 << 15, + LStickLeft = 1 << 16, + LStickUp = 1 << 17, + LStickRight = 1 << 18, + LStickDown = 1 << 19, + RStickLeft = 1 << 20, + RStickUp = 1 << 21, + RStickRight = 1 << 22, + RStickDown = 1 << 23, + SlLeft = 1 << 24, + SrLeft = 1 << 25, + SlRight = 1 << 26, + SrRight = 1 << 27, + + // Generic Catch-all + Up = DpadUp | LStickUp | RStickUp, + Down = DpadDown | LStickDown | RStickDown, + Left = DpadLeft | LStickLeft | RStickLeft, + Right = DpadRight | LStickRight | RStickRight, + Sl = SlLeft | SlRight, + Sr = SrLeft | SrRight, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerType.cs new file mode 100644 index 00000000..1f5da317 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerType.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid +{ + [Flags] + public enum ControllerType + { + None, + ProController = 1 << 0, + Handheld = 1 << 1, + JoyconPair = 1 << 2, + JoyconLeft = 1 << 3, + JoyconRight = 1 << 4, + Invalid = 1 << 5, + Pokeball = 1 << 6, + SystemExternal = 1 << 29, + System = 1 << 30, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadColor.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadColor.cs new file mode 100644 index 00000000..010cffbd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadColor.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum NpadColor : uint + { + BodyGray = 0x828282, + BodyNeonRed = 0xFF3C28, + BodyNeonBlue = 0x0AB9E6, + BodyNeonYellow = 0xE6FF00, + BodyNeonGreen = 0x1EDC00, + BodyNeonPink = 0xFF3278, + BodyRed = 0xE10F00, + BodyBlue = 0x4655F5, + BodyNeonPurple = 0xB400E6, + BodyNeonOrange = 0xFAA005, + BodyPokemonLetsGoPikachu = 0xFFDC00, + BodyPokemonLetsGoEevee = 0xC88C32, + BodyNintendoLaboCreatorsContestEdition = 0xD7AA73, + BodyAnimalCrossingSpecialEditionLeftJoyCon = 0x82FF96, + BodyAnimalCrossingSpecialEditionRightJoyCon = 0x96F5F5, + + ButtonGray = 0x0F0F0F, + ButtonNeonRed = 0x1E0A0A, + ButtonNeonBlue = 0x001E1E, + ButtonNeonYellow = 0x142800, + ButtonNeonGreen = 0x002800, + ButtonNeonPink = 0x28001E, + ButtonRed = 0x280A0A, + ButtonBlue = 0x00000A, + ButtonNeonPurple = 0x140014, + ButtonNeonOrange = 0x0F0A00, + ButtonPokemonLetsGoPikachu = 0x322800, + ButtonPokemonLetsGoEevee = 0x281900, + ButtonNintendoLaboCreatorsContestEdition = 0x1E1914, + ButtonAnimalCrossingSpecialEditionLeftJoyCon = 0x0A1E0A, + ButtonAnimalCrossingSpecialEditionRightJoyCon = 0x0A1E28, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadIdType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadIdType.cs new file mode 100644 index 00000000..8f08481c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadIdType.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum NpadIdType + { + Player1 = 0, + Player2 = 1, + Player3 = 2, + Player4 = 3, + Player5 = 4, + Player6 = 5, + Player7 = 6, + Player8 = 7, + Unknown = 16, + Handheld = 32, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs new file mode 100644 index 00000000..c42a5bc5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum NpadStyleIndex : byte + { + FullKey = 3, + Handheld = 4, + JoyDual = 5, + JoyLeft = 6, + JoyRight = 7, + SystemExt = 32, + System = 33, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/PlayerIndex.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/PlayerIndex.cs new file mode 100644 index 00000000..d68b6d93 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/PlayerIndex.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Services.Hid +{ + public enum PlayerIndex + { + Player1 = 0, + Player2 = 1, + Player3 = 2, + Player4 = 3, + Player5 = 4, + Player6 = 5, + Player7 = 6, + Player8 = 7, + Handheld = 8, + Unknown = 9, + Auto = 10, // Shouldn't be used directly + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/NpadJoyHoldType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/NpadJoyHoldType.cs new file mode 100644 index 00000000..cd61dc92 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/NpadJoyHoldType.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types +{ + enum NpadJoyHoldType + { + Vertical, + Horizontal, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AnalogStickState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AnalogStickState.cs new file mode 100644 index 00000000..b9af211c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AnalogStickState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common +{ + struct AnalogStickState + { + public int X; + public int Y; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs new file mode 100644 index 00000000..c9f59ce0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs @@ -0,0 +1,26 @@ +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common +{ + struct AtomicStorage where T : unmanaged, ISampledDataStruct + { + public ulong SamplingNumber; + public T Object; + + public ulong ReadSamplingNumberAtomic() + { + return Interlocked.Read(ref SamplingNumber); + } + + public void SetObject(ref T obj) + { + ulong samplingNumber = ISampledDataStruct.GetSamplingNumber(ref obj); + + Interlocked.Exchange(ref SamplingNumber, samplingNumber); + + Thread.MemoryBarrier(); + + Object = obj; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs new file mode 100644 index 00000000..312075bc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs @@ -0,0 +1,65 @@ +using System; +using System.Buffers.Binary; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common +{ + /// + /// This is a "marker interface" to add some compile-time safety to a convention-based optimization. + /// + /// Any struct implementing this interface should: + /// - use StructLayoutAttribute (and related attributes) to explicity control how the struct is laid out in memory. + /// - ensure that the method ISampledDataStruct.GetSamplingNumberFieldOffset() correctly returns the offset, in bytes, + /// to the ulong "Sampling Number" field within the struct. Most types have it as the first field, so the default offset is 0. + /// + /// Example: + /// + /// + /// [StructLayout(LayoutKind.Sequential, Pack = 8)] + /// struct DebugPadState : ISampledDataStruct + /// { + /// public ulong SamplingNumber; // 1st field, so no need to add special handling to GetSamplingNumberFieldOffset() + /// // other members... + /// } + /// + /// [StructLayout(LayoutKind.Sequential, Pack = 8)] + /// struct SixAxisSensorState : ISampledDataStruct + /// { + /// public ulong DeltaTime; + /// public ulong SamplingNumber; // Not the first field - needs special handling in GetSamplingNumberFieldOffset() + /// // other members... + /// } + /// + /// + internal interface ISampledDataStruct + { + // No Instance Members - marker interface only + + public static ulong GetSamplingNumber(ref T sampledDataStruct) where T : unmanaged, ISampledDataStruct + { + ReadOnlySpan structSpan = MemoryMarshal.CreateReadOnlySpan(ref sampledDataStruct, 1); + + ReadOnlySpan byteSpan = MemoryMarshal.Cast(structSpan); + + int fieldOffset = GetSamplingNumberFieldOffset(ref sampledDataStruct); + + if (fieldOffset > 0) + { + byteSpan = byteSpan[fieldOffset..]; + } + + ulong value = BinaryPrimitives.ReadUInt64LittleEndian(byteSpan); + + return value; + } + + private static int GetSamplingNumberFieldOffset(ref T sampledDataStruct) where T : unmanaged, ISampledDataStruct + { + return sampledDataStruct switch + { + Npad.SixAxisSensorState _ => sizeof(ulong), + _ => 0, + }; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs new file mode 100644 index 00000000..99f2f59e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs @@ -0,0 +1,149 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common +{ + struct RingLifo where T : unmanaged, ISampledDataStruct + { + private const ulong MaxEntries = 17; + +#pragma warning disable IDE0051, CS0169 // Remove unused private member + private readonly ulong _unused; +#pragma warning restore IDE0051, CS0169 +#pragma warning disable CS0414, IDE0052 // Remove unread private member + private ulong _bufferCount; +#pragma warning restore CS0414, IDE0052 + private ulong _index; + private ulong _count; + private Array17> _storage; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ulong ReadCurrentIndex() + { + return Interlocked.Read(ref _index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ulong ReadCurrentCount() + { + return Interlocked.Read(ref _count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private readonly ulong GetNextIndexForWrite(ulong index) + { + return (index + 1) % MaxEntries; + } + + public ref AtomicStorage GetCurrentAtomicEntryRef() + { + ulong countAvailaible = Math.Min(Math.Max(0, ReadCurrentCount()), 1); + + if (countAvailaible == 0) + { + _storage[0] = default; + + return ref _storage[0]; + } + + ulong index = ReadCurrentIndex(); + + while (true) + { + int inputEntryIndex = (int)((index + MaxEntries + 1 - countAvailaible) % MaxEntries); + + ref AtomicStorage result = ref _storage[inputEntryIndex]; + + ulong samplingNumber0 = result.ReadSamplingNumberAtomic(); + ulong samplingNumber1 = result.ReadSamplingNumberAtomic(); + + if (samplingNumber0 != samplingNumber1 && (result.SamplingNumber - result.SamplingNumber) != 1) + { + ulong tempCount = Math.Min(ReadCurrentCount(), countAvailaible); + + countAvailaible = Math.Min(tempCount, 1); + index = ReadCurrentIndex(); + + continue; + } + + return ref result; + } + } + + public ref T GetCurrentEntryRef() + { + return ref GetCurrentAtomicEntryRef().Object; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan> ReadEntries(uint maxCount) + { + ulong countAvailaible = Math.Min(Math.Max(0, ReadCurrentCount()), maxCount); + + if (countAvailaible == 0) + { + return ReadOnlySpan>.Empty; + } + + ulong index = ReadCurrentIndex(); + + AtomicStorage[] result = new AtomicStorage[countAvailaible]; + + for (ulong i = 0; i < countAvailaible; i++) + { + int inputEntryIndex = (int)((index + MaxEntries + 1 - countAvailaible + i) % MaxEntries); + int outputEntryIndex = (int)(countAvailaible - i - 1); + + ulong samplingNumber0 = _storage[inputEntryIndex].ReadSamplingNumberAtomic(); + result[outputEntryIndex] = _storage[inputEntryIndex]; + ulong samplingNumber1 = _storage[inputEntryIndex].ReadSamplingNumberAtomic(); + + if (samplingNumber0 != samplingNumber1 && (i > 0 && (result[outputEntryIndex].SamplingNumber - result[outputEntryIndex].SamplingNumber) != 1)) + { + ulong tempCount = Math.Min(ReadCurrentCount(), countAvailaible); + + countAvailaible = Math.Min(tempCount, maxCount); + index = ReadCurrentIndex(); + + i -= 1; + } + } + + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ref T value) + { + ulong targetIndex = GetNextIndexForWrite(ReadCurrentIndex()); + + _storage[(int)targetIndex].SetObject(ref value); + + Interlocked.Exchange(ref _index, targetIndex); + + ulong count = ReadCurrentCount(); + + if (count < (MaxEntries - 1)) + { + Interlocked.Increment(ref _count); + } + } + + public void Clear() + { + Interlocked.Exchange(ref _count, 0); + Interlocked.Exchange(ref _index, 0); + } + + public static RingLifo Create() + { + return new RingLifo + { + _bufferCount = MaxEntries, + }; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadAttribute.cs new file mode 100644 index 00000000..dadaf6b2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad +{ + [Flags] + enum DebugPadAttribute : uint + { + None = 0, + Connected = 1 << 0, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadButton.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadButton.cs new file mode 100644 index 00000000..b0bb9b95 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadButton.cs @@ -0,0 +1,24 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad +{ + [Flags] + enum DebugPadButton : uint + { + None = 0, + A = 1 << 0, + B = 1 << 1, + X = 1 << 2, + Y = 1 << 3, + L = 1 << 4, + R = 1 << 5, + ZL = 1 << 6, + ZR = 1 << 7, + Start = 1 << 8, + Select = 1 << 9, + Left = 1 << 10, + Up = 1 << 11, + Right = 1 << 12, + Down = 1 << 13, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs new file mode 100644 index 00000000..f26440c4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs @@ -0,0 +1,15 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct DebugPadState : ISampledDataStruct + { + public ulong SamplingNumber; + public DebugPadAttribute Attributes; + public DebugPadButton Buttons; + public AnalogStickState AnalogStickR; + public AnalogStickState AnalogStickL; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKey.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKey.cs new file mode 100644 index 00000000..7f75bc3f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKey.cs @@ -0,0 +1,29 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard +{ + struct KeyboardKey + { + public Array4 RawData; + + public bool this[KeyboardKeyShift index] + { + get + { + return (RawData[(int)index / 64] & (1UL << ((int)index & 63))) != 0; + } + set + { + int arrayIndex = (int)index / 64; + ulong mask = 1UL << ((int)index & 63); + + RawData[arrayIndex] &= ~mask; + + if (value) + { + RawData[arrayIndex] |= mask; + } + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKeyShift.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKeyShift.cs new file mode 100644 index 00000000..c19b5e61 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKeyShift.cs @@ -0,0 +1,138 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard +{ + enum KeyboardKeyShift + { + A = 4, + B = 5, + C = 6, + D = 7, + E = 8, + F = 9, + G = 10, + H = 11, + I = 12, + J = 13, + K = 14, + L = 15, + M = 16, + N = 17, + O = 18, + P = 19, + Q = 20, + R = 21, + S = 22, + T = 23, + U = 24, + V = 25, + W = 26, + X = 27, + Y = 28, + Z = 29, + D1 = 30, + D2 = 31, + D3 = 32, + D4 = 33, + D5 = 34, + D6 = 35, + D7 = 36, + D8 = 37, + D9 = 38, + D0 = 39, + Return = 40, + Escape = 41, + Backspace = 42, + Tab = 43, + Space = 44, + Minus = 45, + Plus = 46, + OpenBracket = 47, + CloseBracket = 48, + Pipe = 49, + Tilde = 50, + Semicolon = 51, + Quote = 52, + Backquote = 53, + Comma = 54, + Period = 55, + Slash = 56, + CapsLock = 57, + F1 = 58, + F2 = 59, + F3 = 60, + F4 = 61, + F5 = 62, + F6 = 63, + F7 = 64, + F8 = 65, + F9 = 66, + F10 = 67, + F11 = 68, + F12 = 69, + PrintScreen = 70, + ScrollLock = 71, + Pause = 72, + Insert = 73, + Home = 74, + PageUp = 75, + Delete = 76, + End = 77, + PageDown = 78, + RightArrow = 79, + LeftArrow = 80, + DownArrow = 81, + UpArrow = 82, + NumLock = 83, + NumPadDivide = 84, + NumPadMultiply = 85, + NumPadSubtract = 86, + NumPadAdd = 87, + NumPadEnter = 88, + NumPad1 = 89, + NumPad2 = 90, + NumPad3 = 91, + NumPad4 = 92, + NumPad5 = 93, + NumPad6 = 94, + NumPad7 = 95, + NumPad8 = 96, + NumPad9 = 97, + NumPad0 = 98, + NumPadDot = 99, + Backslash = 100, + Application = 101, + Power = 102, + NumPadEquals = 103, + F13 = 104, + F14 = 105, + F15 = 106, + F16 = 107, + F17 = 108, + F18 = 109, + F19 = 110, + F20 = 111, + F21 = 112, + F22 = 113, + F23 = 114, + F24 = 115, + NumPadComma = 133, + Ro = 135, + KatakanaHiragana = 136, + Yen = 137, + Henkan = 138, + Muhenkan = 139, + NumPadCommaPc98 = 140, + HangulEnglish = 144, + Hanja = 145, + Katakana = 146, + Hiragana = 147, + ZenkakuHankaku = 148, + LeftControl = 224, + LeftShift = 225, + LeftAlt = 226, + LeftGui = 227, + RightControl = 228, + RightShift = 229, + RightAlt = 230, + RightGui = 231, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs new file mode 100644 index 00000000..890daa0d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs @@ -0,0 +1,20 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard +{ + [Flags] + enum KeyboardModifier : ulong + { + None = 0, + Control = 1 << 0, + Shift = 1 << 1, + LeftAlt = 1 << 2, + RightAlt = 1 << 3, + Gui = 1 << 4, + CapsLock = 1 << 8, + ScrollLock = 1 << 9, + NumLock = 1 << 10, + Katakana = 1 << 11, + Hiragana = 1 << 12, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs new file mode 100644 index 00000000..3f7d9409 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs @@ -0,0 +1,13 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct KeyboardState : ISampledDataStruct + { + public ulong SamplingNumber; + public KeyboardModifier Modifiers; + public KeyboardKey Keys; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseAttribute.cs new file mode 100644 index 00000000..048dd357 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse +{ + [Flags] + enum MouseAttribute : uint + { + None = 0, + Transferable = 1 << 0, + IsConnected = 1 << 1, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseButton.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseButton.cs new file mode 100644 index 00000000..4a6f73b8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseButton.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse +{ + [Flags] + enum MouseButton : uint + { + None = 0, + Left = 1 << 0, + Right = 1 << 1, + Middle = 1 << 2, + Forward = 1 << 3, + Back = 1 << 4, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs new file mode 100644 index 00000000..41fe68b8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct MouseState : ISampledDataStruct + { + public ulong SamplingNumber; + public int X; + public int Y; + public int DeltaX; + public int DeltaY; + public int WheelDeltaX; + public int WheelDeltaY; + public MouseButton Buttons; + public MouseAttribute Attributes; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/DeviceType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/DeviceType.cs new file mode 100644 index 00000000..d56f849a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/DeviceType.cs @@ -0,0 +1,29 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [Flags] + enum DeviceType + { + None = 0, + + FullKey = 1 << 0, + DebugPad = 1 << 1, + HandheldLeft = 1 << 2, + HandheldRight = 1 << 3, + JoyLeft = 1 << 4, + JoyRight = 1 << 5, + Palma = 1 << 6, + LarkHvcLeft = 1 << 7, + LarkHvcRight = 1 << 8, + LarkNesLeft = 1 << 9, + LarkNesRight = 1 << 10, + HandheldLarkHvcLeft = 1 << 11, + HandheldLarkHvcRight = 1 << 12, + HandheldLarkNesLeft = 1 << 13, + HandheldLarkNesRight = 1 << 14, + Lucia = 1 << 15, + + System = 1 << 31, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadAttribute.cs new file mode 100644 index 00000000..6d7719e8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadAttribute.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [Flags] + enum NpadAttribute : uint + { + None = 0, + IsConnected = 1 << 0, + IsWired = 1 << 1, + IsLeftConnected = 1 << 2, + IsLeftWired = 1 << 3, + IsRightConnected = 1 << 4, + IsRightWired = 1 << 5, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadBatteryLevel.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadBatteryLevel.cs new file mode 100644 index 00000000..7f5b17ff --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadBatteryLevel.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + enum NpadBatteryLevel + { + Percent0, + Percent25, + Percent50, + Percent75, + Percent100, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadButton.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadButton.cs new file mode 100644 index 00000000..c20db8e0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadButton.cs @@ -0,0 +1,44 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [Flags] + enum NpadButton : ulong + { + None = 0, + A = 1 << 0, + B = 1 << 1, + X = 1 << 2, + Y = 1 << 3, + StickL = 1 << 4, + StickR = 1 << 5, + L = 1 << 6, + R = 1 << 7, + ZL = 1 << 8, + ZR = 1 << 9, + Plus = 1 << 10, + Minus = 1 << 11, + Left = 1 << 12, + Up = 1 << 13, + Right = 1 << 14, + Down = 1 << 15, + StickLLeft = 1 << 16, + StickLUp = 1 << 17, + StickLRight = 1 << 18, + StickLDown = 1 << 19, + StickRLeft = 1 << 20, + StickRUp = 1 << 21, + StickRRight = 1 << 22, + StickRDown = 1 << 23, + LeftSL = 1 << 24, + LeftSR = 1 << 25, + RightSL = 1 << 26, + RightSR = 1 << 27, + Palma = 1 << 28, + + // FIXME: Probably a button on Lark. + Unknown29 = 1 << 29, + + HandheldLeftB = 1 << 30, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadColorAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadColorAttribute.cs new file mode 100644 index 00000000..e8140459 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadColorAttribute.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + enum NpadColorAttribute : uint + { + Ok, + ReadError, + NoController, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs new file mode 100644 index 00000000..d3caf717 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs @@ -0,0 +1,16 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct NpadCommonState : ISampledDataStruct + { + public ulong SamplingNumber; + public NpadButton Buttons; + public AnalogStickState AnalogStickL; + public AnalogStickState AnalogStickR; + public NpadAttribute Attributes; + private readonly uint _reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadFullKeyColorState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadFullKeyColorState.cs new file mode 100644 index 00000000..77127c2c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadFullKeyColorState.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + struct NpadFullKeyColorState + { + public NpadColorAttribute Attribute; + public uint FullKeyBody; + public uint FullKeyButtons; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs new file mode 100644 index 00000000..41e84134 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs @@ -0,0 +1,15 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct NpadGcTriggerState : ISampledDataStruct + { +#pragma warning disable CS0649 // Field is never assigned to + public ulong SamplingNumber; + public uint TriggerL; + public uint TriggerR; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadInternalState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadInternalState.cs new file mode 100644 index 00000000..f79a2657 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadInternalState.cs @@ -0,0 +1,69 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + struct NpadInternalState + { + public NpadStyleTag StyleSet; + public NpadJoyAssignmentMode JoyAssignmentMode; + public NpadFullKeyColorState FullKeyColor; + public NpadJoyColorState JoyColor; + public RingLifo FullKey; + public RingLifo Handheld; + public RingLifo JoyDual; + public RingLifo JoyLeft; + public RingLifo JoyRight; + public RingLifo Palma; + public RingLifo SystemExt; + public RingLifo FullKeySixAxisSensor; + public RingLifo HandheldSixAxisSensor; + public RingLifo JoyDualSixAxisSensor; + public RingLifo JoyDualRightSixAxisSensor; + public RingLifo JoyLeftSixAxisSensor; + public RingLifo JoyRightSixAxisSensor; + public DeviceType DeviceType; +#pragma warning disable IDE0051 // Remove unused private member + private readonly uint _reserved1; +#pragma warning restore IDE0051 + public NpadSystemProperties SystemProperties; + public NpadSystemButtonProperties SystemButtonProperties; + public NpadBatteryLevel BatteryLevelJoyDual; + public NpadBatteryLevel BatteryLevelJoyLeft; + public NpadBatteryLevel BatteryLevelJoyRight; + public uint AppletFooterUiAttributes; + public AppletFooterUiType AppletFooterUiType; +#pragma warning disable IDE0051 // Remove unused private member + private readonly Reserved2Struct _reserved2; +#pragma warning restore IDE0051 + public RingLifo GcTrigger; + public NpadLarkType LarkTypeLeftAndMain; + public NpadLarkType LarkTypeRight; + public NpadLuciaType LuciaType; + public uint Unknown43EC; + + [StructLayout(LayoutKind.Sequential, Size = 123, Pack = 1)] + private struct Reserved2Struct { } + + public static NpadInternalState Create() + { + return new NpadInternalState + { + FullKey = RingLifo.Create(), + Handheld = RingLifo.Create(), + JoyDual = RingLifo.Create(), + JoyLeft = RingLifo.Create(), + JoyRight = RingLifo.Create(), + Palma = RingLifo.Create(), + SystemExt = RingLifo.Create(), + FullKeySixAxisSensor = RingLifo.Create(), + HandheldSixAxisSensor = RingLifo.Create(), + JoyDualSixAxisSensor = RingLifo.Create(), + JoyDualRightSixAxisSensor = RingLifo.Create(), + JoyLeftSixAxisSensor = RingLifo.Create(), + JoyRightSixAxisSensor = RingLifo.Create(), + GcTrigger = RingLifo.Create(), + }; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyAssignmentMode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyAssignmentMode.cs new file mode 100644 index 00000000..b8a4623f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyAssignmentMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + enum NpadJoyAssignmentMode : uint + { + Dual, + Single, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyColorState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyColorState.cs new file mode 100644 index 00000000..4e00b5ae --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyColorState.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + struct NpadJoyColorState + { + public NpadColorAttribute Attribute; + public uint LeftBody; + public uint LeftButtons; + public uint RightBody; + public uint RightButtons; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLarkType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLarkType.cs new file mode 100644 index 00000000..013c5fee --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLarkType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + enum NpadLarkType : uint + { + Invalid, + H1, + H2, + NL, + NR + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLuciaType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLuciaType.cs new file mode 100644 index 00000000..18756bc3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLuciaType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + enum NpadLuciaType + { + Invalid, + J, + E, + U, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadState.cs new file mode 100644 index 00000000..fb32e716 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadState.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [StructLayout(LayoutKind.Sequential, Size = 0x5000)] + struct NpadState + { + public NpadInternalState InternalState; + + public static NpadState Create() + { + return new NpadState + { + InternalState = NpadInternalState.Create(), + }; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadStyleTag.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadStyleTag.cs new file mode 100644 index 00000000..bf4d9187 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadStyleTag.cs @@ -0,0 +1,76 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + /// + /// Nintendo pad style + /// + [Flags] + enum NpadStyleTag : uint + { + /// + /// No type. + /// + None = 0, + + /// + /// Pro controller. + /// + FullKey = 1 << 0, + + /// + /// Joy-Con controller in handheld mode. + /// + Handheld = 1 << 1, + + /// + /// Joy-Con controller in dual mode. + /// + JoyDual = 1 << 2, + + /// + /// Joy-Con left controller in single mode. + /// + JoyLeft = 1 << 3, + + /// + /// Joy-Con right controller in single mode. + /// + JoyRight = 1 << 4, + + /// + /// GameCube controller. + /// + Gc = 1 << 5, + + /// + /// Poké Ball Plus controller. + /// + Palma = 1 << 6, + + /// + /// NES and Famicom controller. + /// + Lark = 1 << 7, + + /// + /// NES and Famicom controller in handheld mode. + /// + HandheldLark = 1 << 8, + + /// + /// SNES controller. + /// + Lucia = 1 << 9, + + /// + /// Generic external controller. + /// + SystemExt = 1 << 29, + + /// + /// Generic controller. + /// + System = 1 << 30, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemButtonProperties.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemButtonProperties.cs new file mode 100644 index 00000000..6f820ef6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemButtonProperties.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [Flags] + enum NpadSystemButtonProperties : uint + { + None = 0, + IsUnintendedHomeButtonInputProtectionEnabled = 1 << 0, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemProperties.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemProperties.cs new file mode 100644 index 00000000..79110fe4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemProperties.cs @@ -0,0 +1,24 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [Flags] + enum NpadSystemProperties : ulong + { + None = 0, + + IsChargingJoyDual = 1 << 0, + IsChargingJoyLeft = 1 << 1, + IsChargingJoyRight = 1 << 2, + IsPoweredJoyDual = 1 << 3, + IsPoweredJoyLeft = 1 << 4, + IsPoweredJoyRight = 1 << 5, + IsUnsuportedButtonPressedOnNpadSystem = 1 << 9, + IsUnsuportedButtonPressedOnNpadSystemExt = 1 << 10, + IsAbxyButtonOriented = 1 << 11, + IsSlSrButtonOriented = 1 << 12, + IsPlusAvailable = 1 << 13, + IsMinusAvailable = 1 << 14, + IsDirectionalButtonsAvailable = 1 << 15, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorAttribute.cs new file mode 100644 index 00000000..b6e57496 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [Flags] + enum SixAxisSensorAttribute : uint + { + None = 0, + IsConnected = 1 << 0, + IsInterpolated = 1 << 1, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs new file mode 100644 index 00000000..f3c2b208 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SixAxisSensorState : ISampledDataStruct + { + public ulong DeltaTime; + public ulong SamplingNumber; + public HidVector Acceleration; + public HidVector AngularVelocity; + public HidVector Angle; + public Array9 Direction; + public SixAxisSensorAttribute Attributes; + private readonly uint _reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs new file mode 100644 index 00000000..d6283eb5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs @@ -0,0 +1,66 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory +{ + /// + /// Represent the shared memory shared between applications for input. + /// + [StructLayout(LayoutKind.Explicit, Size = 0x40000)] + struct SharedMemory + { + /// + /// Debug controller. + /// + [FieldOffset(0)] + public RingLifo DebugPad; + + /// + /// Touchscreen. + /// + [FieldOffset(0x400)] + public RingLifo TouchScreen; + + /// + /// Mouse. + /// + [FieldOffset(0x3400)] + public RingLifo Mouse; + + /// + /// Keyboard. + /// + [FieldOffset(0x3800)] + public RingLifo Keyboard; + + /// + /// Nintendo Pads. + /// + [FieldOffset(0x9A00)] + public Array10 Npads; + + public static SharedMemory Create() + { + SharedMemory result = new() + { + DebugPad = RingLifo.Create(), + TouchScreen = RingLifo.Create(), + Mouse = RingLifo.Create(), + Keyboard = RingLifo.Create(), + }; + + for (int i = 0; i < result.Npads.Length; i++) + { + result.Npads[i] = NpadState.Create(); + } + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchAttribute.cs new file mode 100644 index 00000000..44d7c22a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen +{ + [Flags] + public enum TouchAttribute : uint + { + None = 0, + Start = 1 << 0, + End = 1 << 1, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs new file mode 100644 index 00000000..d256da0d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct TouchScreenState : ISampledDataStruct + { + public ulong SamplingNumber; + public int TouchesCount; + private readonly int _reserved; + public Array16 Touches; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchState.cs new file mode 100644 index 00000000..06be9b24 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchState.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen +{ + struct TouchState + { + public ulong DeltaTime; +#pragma warning disable CS0649 // Field is never assigned to + public TouchAttribute Attribute; +#pragma warning restore CS0649 + public uint FingerId; + public uint X; + public uint Y; + public uint DiameterX; + public uint DiameterY; + public uint RotationAngle; +#pragma warning disable CS0169, IDE0051 // Remove unused private member + private readonly uint _reserved; +#pragma warning restore CS0169, IDE0051 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/IpcService.cs b/src/Ryujinx.HLE/HOS/Services/IpcService.cs new file mode 100644 index 00000000..808f21c0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/IpcService.cs @@ -0,0 +1,284 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace Ryujinx.HLE.HOS.Services +{ + abstract class IpcService + { + public IReadOnlyDictionary CmifCommands { get; } + public IReadOnlyDictionary TipcCommands { get; } + + public ServerBase Server { get; private set; } + + private IpcService _parent; + private readonly IdDictionary _domainObjects; + private int _selfId; + private bool _isDomain; + + public IpcService(ServerBase server = null) + { + CmifCommands = typeof(IpcService).Assembly.GetTypes() + .Where(type => type == GetType()) + .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)) + .SelectMany(methodInfo => methodInfo.GetCustomAttributes(typeof(CommandCmifAttribute)) + .Select(command => (((CommandCmifAttribute)command).Id, methodInfo))) + .ToDictionary(command => command.Id, command => command.methodInfo); + + TipcCommands = typeof(IpcService).Assembly.GetTypes() + .Where(type => type == GetType()) + .SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public)) + .SelectMany(methodInfo => methodInfo.GetCustomAttributes(typeof(CommandTipcAttribute)) + .Select(command => (((CommandTipcAttribute)command).Id, methodInfo))) + .ToDictionary(command => command.Id, command => command.methodInfo); + + Server = server; + + _parent = this; + _domainObjects = new IdDictionary(); + _selfId = -1; + } + + public int ConvertToDomain() + { + if (_selfId == -1) + { + _selfId = _domainObjects.Add(this); + } + + _isDomain = true; + + return _selfId; + } + + public void ConvertToSession() + { + _isDomain = false; + } + + public void CallCmifMethod(ServiceCtx context) + { + IpcService service = this; + + if (_isDomain) + { + int domainWord0 = context.RequestData.ReadInt32(); + int domainObjId = context.RequestData.ReadInt32(); + + int domainCmd = (domainWord0 >> 0) & 0xff; + int inputObjCount = (domainWord0 >> 8) & 0xff; + int dataPayloadSize = (domainWord0 >> 16) & 0xffff; + + context.RequestData.BaseStream.Seek(0x10 + dataPayloadSize, SeekOrigin.Begin); + + context.Request.ObjectIds.EnsureCapacity(inputObjCount); + + for (int index = 0; index < inputObjCount; index++) + { + context.Request.ObjectIds.Add(context.RequestData.ReadInt32()); + } + + context.RequestData.BaseStream.Seek(0x10, SeekOrigin.Begin); + + if (domainCmd == 1) + { + service = GetObject(domainObjId); + + context.ResponseData.Write(0L); + context.ResponseData.Write(0L); + } + else if (domainCmd == 2) + { + Delete(domainObjId); + + context.ResponseData.Write(0L); + + return; + } + else + { + throw new NotImplementedException($"Domain command: {domainCmd}"); + } + } + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long sfciMagic = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + int commandId = (int)context.RequestData.ReadInt64(); + + bool serviceExists = service.CmifCommands.TryGetValue(commandId, out MethodInfo processRequest); + + if (context.Device.Configuration.IgnoreMissingServices || serviceExists) + { + ResultCode result = ResultCode.Success; + + context.ResponseData.BaseStream.Seek(_isDomain ? 0x20 : 0x10, SeekOrigin.Begin); + + if (serviceExists) + { + Logger.Trace?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}"); + + result = (ResultCode)processRequest.Invoke(service, new object[] { context }); + } + else + { + string serviceName; + + + serviceName = (service is not DummyService dummyService) ? service.GetType().FullName : dummyService.ServiceName; + + Logger.Warning?.Print(LogClass.KernelIpc, $"Missing service {serviceName}: {commandId} ignored"); + } + + if (_isDomain) + { + foreach (int id in context.Response.ObjectIds) + { + context.ResponseData.Write(id); + } + + context.ResponseData.BaseStream.Seek(0, SeekOrigin.Begin); + + context.ResponseData.Write(context.Response.ObjectIds.Count); + } + + context.ResponseData.BaseStream.Seek(_isDomain ? 0x10 : 0, SeekOrigin.Begin); + + context.ResponseData.Write(IpcMagic.Sfco); + context.ResponseData.Write((long)result); + } + else + { + string dbgMessage = $"{service.GetType().FullName}: {commandId}"; + + throw new ServiceNotImplementedException(service, context, dbgMessage); + } + } + + public void CallTipcMethod(ServiceCtx context) + { + int commandId = (int)context.Request.Type - 0x10; + + bool serviceExists = TipcCommands.TryGetValue(commandId, out MethodInfo processRequest); + + if (context.Device.Configuration.IgnoreMissingServices || serviceExists) + { + ResultCode result = ResultCode.Success; + + context.ResponseData.BaseStream.Seek(0x4, SeekOrigin.Begin); + + if (serviceExists) + { + Logger.Debug?.Print(LogClass.KernelIpc, $"{GetType().Name}: {processRequest.Name}"); + + result = (ResultCode)processRequest.Invoke(this, new object[] { context }); + } + else + { + string serviceName; + + + serviceName = (this is not DummyService dummyService) ? GetType().FullName : dummyService.ServiceName; + + Logger.Warning?.Print(LogClass.KernelIpc, $"Missing service {serviceName}: {commandId} ignored"); + } + + context.ResponseData.BaseStream.Seek(0, SeekOrigin.Begin); + + context.ResponseData.Write((uint)result); + } + else + { + string dbgMessage = $"{GetType().FullName}: {commandId}"; + + throw new ServiceNotImplementedException(this, context, dbgMessage); + } + } + + protected void MakeObject(ServiceCtx context, IpcService obj) + { + obj.TrySetServer(_parent.Server); + + if (_parent._isDomain) + { + obj._parent = _parent; + + context.Response.ObjectIds.Add(_parent.Add(obj)); + } + else + { + context.Device.System.KernelContext.Syscall.CreateSession(out int serverSessionHandle, out int clientSessionHandle, false, 0); + + obj.Server.AddSessionObj(serverSessionHandle, obj); + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(clientSessionHandle); + } + } + + protected T GetObject(ServiceCtx context, int index) where T : IpcService + { + int objId = context.Request.ObjectIds[index]; + + IpcService obj = _parent.GetObject(objId); + + return obj is T t ? t : null; + } + + public bool TrySetServer(ServerBase newServer) + { + if (Server == null) + { + Server = newServer; + + return true; + } + + return false; + } + + private int Add(IpcService obj) + { + return _domainObjects.Add(obj); + } + + private bool Delete(int id) + { + object obj = _domainObjects.Delete(id); + + if (obj is IDisposable disposableObj) + { + disposableObj.Dispose(); + } + + return obj != null; + } + + private IpcService GetObject(int id) + { + return _domainObjects.GetData(id); + } + + public void SetParent(IpcService parent) + { + _parent = parent._parent; + } + + public virtual void DestroyAtExit() + { + foreach (object domainObject in _domainObjects.Values) + { + if (domainObject != this && domainObject is IDisposable disposableObj) + { + disposableObj.Dispose(); + } + } + + _domainObjects.Clear(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs new file mode 100644 index 00000000..4bea3945 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + [Service("ldn:m")] + class IMonitorServiceCreator : IpcService + { + public IMonitorServiceCreator(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/ISystemServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/ISystemServiceCreator.cs new file mode 100644 index 00000000..535013b0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/ISystemServiceCreator.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + [Service("ldn:s")] + class ISystemServiceCreator : IpcService + { + public ISystemServiceCreator(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs new file mode 100644 index 00000000..633d5f73 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator; + +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + [Service("ldn:u")] + class IUserServiceCreator : IpcService + { + public IUserServiceCreator(ServiceCtx context) : base(context.Device.System.LdnServer) { } + + [CommandCmif(0)] + // CreateUserLocalCommunicationService() -> object + public ResultCode CreateUserLocalCommunicationService(ServiceCtx context) + { + MakeObject(context, new IUserLocalCommunicationService(context)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/LdnConst.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/LdnConst.cs new file mode 100644 index 00000000..80ea2c9d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/LdnConst.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + static class LdnConst + { + public const int SsidLengthMax = 0x20; + public const int AdvertiseDataSizeMax = 0x180; + public const int UserNameBytesMax = 0x20; + public const int NodeCountMax = 8; + public const int StationCountMax = NodeCountMax - 1; + public const int PassphraseLengthMax = 0x40; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs new file mode 100644 index 00000000..797a7a9b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p +{ + [Service("lp2p:app")] // 9.0.0+ + [Service("lp2p:sys")] // 9.0.0+ + class IServiceCreator : IpcService + { + public IServiceCreator(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs new file mode 100644 index 00000000..e4a16dbb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs @@ -0,0 +1,59 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using System.Net; + +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + internal class NetworkInterface + { + public ResultCode NifmState { get; set; } + public KEvent StateChangeEvent { get; private set; } + + private NetworkState _state; + + public NetworkInterface(Horizon system) + { + // TODO(Ac_K): Determine where the internal state is set. + NifmState = ResultCode.Success; + StateChangeEvent = new KEvent(system.KernelContext); + + _state = NetworkState.None; + } + + public ResultCode Initialize(int unknown, int version, IPAddress ipv4Address, IPAddress subnetMaskAddress) + { + // TODO(Ac_K): Call nn::nifm::InitializeSystem(). + // If the call failed, it returns the result code. + // If the call succeed, it signal and clear an event then start a new thread named nn.ldn.NetworkInterfaceMonitor. + + Logger.Stub?.PrintStub(LogClass.ServiceLdn, new { version }); + + // NOTE: Since we don't support ldn for now, we can return this following result code to make it disabled. + return ResultCode.DeviceDisabled; + } + + public ResultCode GetState(out NetworkState state) + { + // Return ResultCode.InvalidArgument if _state is null, doesn't occur in our case. + + state = _state; + + return ResultCode.Success; + } + + public ResultCode Finalize() + { + // TODO(Ac_K): Finalize nifm service then kill the thread named nn.ldn.NetworkInterfaceMonitor. + + _state = NetworkState.None; + + StateChangeEvent.WritableEvent.Signal(); + StateChangeEvent.WritableEvent.Clear(); + + Logger.Stub?.PrintStub(LogClass.ServiceLdn); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs new file mode 100644 index 00000000..d550ea57 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn +{ + enum ResultCode + { + ModuleId = 203, + ErrorCodeShift = 9, + + Success = 0, + + DeviceNotAvailable = (16 << ErrorCodeShift) | ModuleId, + DeviceDisabled = (22 << ErrorCodeShift) | ModuleId, + InvalidState = (32 << ErrorCodeShift) | ModuleId, + NodeNotFound = (48 << ErrorCodeShift) | ModuleId, + ConnectFailure = (64 << ErrorCodeShift) | ModuleId, + ConnectNotFound = (65 << ErrorCodeShift) | ModuleId, + ConnectTimeout = (66 << ErrorCodeShift) | ModuleId, + ConnectRejected = (67 << ErrorCodeShift) | ModuleId, + InvalidArgument = (96 << ErrorCodeShift) | ModuleId, + InvalidObject = (97 << ErrorCodeShift) | ModuleId, + VersionTooLow = (113 << ErrorCodeShift) | ModuleId, + VersionTooHigh = (114 << ErrorCodeShift) | ModuleId, + TooManyPlayers = (144 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/AcceptPolicy.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/AcceptPolicy.cs new file mode 100644 index 00000000..f49b7bdd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/AcceptPolicy.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + enum AcceptPolicy : byte + { + AcceptAll, + RejectAll, + BlackList, + WhiteList, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/AddressEntry.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/AddressEntry.cs new file mode 100644 index 00000000..a458c521 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/AddressEntry.cs @@ -0,0 +1,13 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0xC)] + struct AddressEntry + { + public uint Ipv4Address; + public Array6 MacAddress; + public ushort Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/AddressList.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/AddressList.cs new file mode 100644 index 00000000..cf4d0e20 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/AddressList.cs @@ -0,0 +1,11 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x60)] + struct AddressList + { + public Array8 Addresses; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/CommonNetworkInfo.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/CommonNetworkInfo.cs new file mode 100644 index 00000000..217bca4f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/CommonNetworkInfo.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x30)] + struct CommonNetworkInfo + { + public Array6 MacAddress; + public Ssid Ssid; + public ushort Channel; + public byte LinkLevel; + public byte NetworkType; + public uint Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/DisconnectReason.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/DisconnectReason.cs new file mode 100644 index 00000000..00a70f22 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/DisconnectReason.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + enum DisconnectReason : uint + { + None, + DisconnectedByUser, + DisconnectedBySystem, + DestroyedByUser, + DestroyedBySystem, + Rejected, + SignalLost, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/IntentId.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/IntentId.cs new file mode 100644 index 00000000..2c5cdd95 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/IntentId.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + struct IntentId + { + public long LocalCommunicationId; + public ushort Reserved1; + public ushort SceneId; + public uint Reserved2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/LdnNetworkInfo.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/LdnNetworkInfo.cs new file mode 100644 index 00000000..5fb2aca0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/LdnNetworkInfo.cs @@ -0,0 +1,23 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x430)] + struct LdnNetworkInfo + { + public Array16 SecurityParameter; + public ushort SecurityMode; + public AcceptPolicy StationAcceptPolicy; + public byte Reserved1; + public ushort Reserved2; + public byte NodeCountMax; + public byte NodeCount; + public Array8 Nodes; + public ushort Reserved3; + public ushort AdvertiseDataSize; + public Array384 AdvertiseData; + public Array140 Reserved4; + public ulong AuthenticationId; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkConfig.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkConfig.cs new file mode 100644 index 00000000..4da5fe42 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkConfig.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20)] + struct NetworkConfig + { + public IntentId IntentId; + public ushort Channel; + public byte NodeCountMax; + public byte Reserved1; + public ushort LocalCommunicationVersion; + public Array10 Reserved2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkId.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkId.cs new file mode 100644 index 00000000..7039256f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkId.cs @@ -0,0 +1,12 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20)] + struct NetworkId + { + public IntentId IntentId; + public Array16 SessionId; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkInfo.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkInfo.cs new file mode 100644 index 00000000..98434b69 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkInfo.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x480)] + struct NetworkInfo + { + public NetworkId NetworkId; + public CommonNetworkInfo Common; + public LdnNetworkInfo Ldn; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs new file mode 100644 index 00000000..5b613552 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + enum NetworkState + { + None, + Initialized, + AccessPoint, + AccessPointCreated, + Station, + StationConnected, + Error, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkType.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkType.cs new file mode 100644 index 00000000..5db23efb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + enum NetworkType : uint + { + None, + General, + Ldn, + All, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeInfo.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeInfo.cs new file mode 100644 index 00000000..9d547793 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeInfo.cs @@ -0,0 +1,18 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40)] + struct NodeInfo + { + public uint Ipv4Address; + public Array6 MacAddress; + public byte NodeId; + public byte IsConnected; + public Array33 UserName; + public byte Reserved1; + public ushort LocalCommunicationVersion; + public Array16 Reserved2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeLatestUpdate.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeLatestUpdate.cs new file mode 100644 index 00000000..0461e783 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeLatestUpdate.cs @@ -0,0 +1,62 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 8)] + struct NodeLatestUpdate + { + public NodeLatestUpdateFlags State; + public Array7 Reserved; + } + + static class NodeLatestUpdateHelper + { + private static readonly object _lock = new(); + + public static void CalculateLatestUpdate(this Array8 array, Array8 beforeNodes, Array8 afterNodes) + { + lock (_lock) + { + for (int i = 0; i < 8; i++) + { + if (beforeNodes[i].IsConnected == 0) + { + if (afterNodes[i].IsConnected != 0) + { + array[i].State |= NodeLatestUpdateFlags.Connect; + } + } + else + { + if (afterNodes[i].IsConnected == 0) + { + array[i].State |= NodeLatestUpdateFlags.Disconnect; + } + } + } + } + } + + public static NodeLatestUpdate[] ConsumeLatestUpdate(this Array8 array, int number) + { + NodeLatestUpdate[] result = new NodeLatestUpdate[number]; + + lock (_lock) + { + for (int i = 0; i < number; i++) + { + result[i].Reserved = new Array7(); + + if (i < LdnConst.NodeCountMax) + { + result[i].State = array[i].State; + array[i].State = NodeLatestUpdateFlags.None; + } + } + } + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeLatestUpdateFlags.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeLatestUpdateFlags.cs new file mode 100644 index 00000000..66442f13 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/NodeLatestUpdateFlags.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [Flags] + enum NodeLatestUpdateFlags : byte + { + None = 0, + Connect = 1 << 0, + Disconnect = 1 << 1, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/ScanFilter.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/ScanFilter.cs new file mode 100644 index 00000000..449c923c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/ScanFilter.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x60)] + struct ScanFilter + { + public NetworkId NetworkId; + public NetworkType NetworkType; + public Array6 MacAddress; + public Ssid Ssid; + public Array16 Reserved; + public ScanFilterFlag Flag; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/ScanFilterFlag.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/ScanFilterFlag.cs new file mode 100644 index 00000000..c59224cb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/ScanFilterFlag.cs @@ -0,0 +1,18 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [Flags] + enum ScanFilterFlag : byte + { + LocalCommunicationId = 1 << 0, + SessionId = 1 << 1, + NetworkType = 1 << 2, + MacAddress = 1 << 3, + Ssid = 1 << 4, + SceneId = 1 << 5, + IntentId = LocalCommunicationId | SceneId, + NetworkId = IntentId | SessionId, + All = NetworkType | IntentId | SessionId | MacAddress | Ssid, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityConfig.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityConfig.cs new file mode 100644 index 00000000..5939a139 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityConfig.cs @@ -0,0 +1,13 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x44)] + struct SecurityConfig + { + public SecurityMode SecurityMode; + public ushort PassphraseSize; + public Array64 Passphrase; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityMode.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityMode.cs new file mode 100644 index 00000000..1cdb7957 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + enum SecurityMode : ushort + { + All, + Retail, + Debug, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityParameter.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityParameter.cs new file mode 100644 index 00000000..dbcaa9ee --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/SecurityParameter.cs @@ -0,0 +1,12 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20)] + struct SecurityParameter + { + public Array16 Data; + public Array16 SessionId; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/Ssid.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/Ssid.cs new file mode 100644 index 00000000..76486250 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/Ssid.cs @@ -0,0 +1,12 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x22)] + struct Ssid + { + public byte Length; + public Array33 Name; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/UserConfig.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/UserConfig.cs new file mode 100644 index 00000000..3820f936 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/UserConfig.cs @@ -0,0 +1,12 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x30)] + struct UserConfig + { + public Array33 UserName; + public Array15 Unknown1; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/AccessPoint.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/AccessPoint.cs new file mode 100644 index 00000000..78ebcac8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/AccessPoint.cs @@ -0,0 +1,103 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator +{ + class AccessPoint : IDisposable + { + private byte[] _advertiseData; + + private readonly IUserLocalCommunicationService _parent; + + public NetworkInfo NetworkInfo; + public Array8 LatestUpdates = new(); + public bool Connected { get; private set; } + + public AccessPoint(IUserLocalCommunicationService parent) + { + _parent = parent; + + _parent.NetworkClient.NetworkChange += NetworkChanged; + } + + public void Dispose() + { + _parent.NetworkClient.DisconnectNetwork(); + + _parent.NetworkClient.NetworkChange -= NetworkChanged; + } + + private void NetworkChanged(object sender, NetworkChangeEventArgs e) + { + LatestUpdates.CalculateLatestUpdate(NetworkInfo.Ldn.Nodes, e.Info.Ldn.Nodes); + + NetworkInfo = e.Info; + + if (Connected != e.Connected) + { + Connected = e.Connected; + + if (Connected) + { + _parent.SetState(NetworkState.AccessPointCreated); + } + else + { + _parent.SetDisconnectReason(e.DisconnectReasonOrDefault(DisconnectReason.DestroyedBySystem)); + } + } + else + { + _parent.SetState(); + } + } + + public ResultCode SetAdvertiseData(byte[] advertiseData) + { + _advertiseData = advertiseData; + + _parent.NetworkClient.SetAdvertiseData(_advertiseData); + + return ResultCode.Success; + } + + public ResultCode SetStationAcceptPolicy(AcceptPolicy acceptPolicy) + { + _parent.NetworkClient.SetStationAcceptPolicy(acceptPolicy); + + return ResultCode.Success; + } + + public ResultCode CreateNetwork(SecurityConfig securityConfig, UserConfig userConfig, NetworkConfig networkConfig) + { + CreateAccessPointRequest request = new() + { + SecurityConfig = securityConfig, + UserConfig = userConfig, + NetworkConfig = networkConfig, + }; + + bool success = _parent.NetworkClient.CreateNetwork(request, _advertiseData ?? Array.Empty()); + + return success ? ResultCode.Success : ResultCode.InvalidState; + } + + public ResultCode CreateNetworkPrivate(SecurityConfig securityConfig, SecurityParameter securityParameter, UserConfig userConfig, NetworkConfig networkConfig, AddressList addressList) + { + CreateAccessPointPrivateRequest request = new() + { + SecurityConfig = securityConfig, + SecurityParameter = securityParameter, + UserConfig = userConfig, + NetworkConfig = networkConfig, + AddressList = addressList, + }; + + bool success = _parent.NetworkClient.CreateNetworkPrivate(request, _advertiseData); + + return success ? ResultCode.Success : ResultCode.InvalidState; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/INetworkClient.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/INetworkClient.cs new file mode 100644 index 00000000..7ad6de51 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/INetworkClient.cs @@ -0,0 +1,25 @@ +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator +{ + interface INetworkClient : IDisposable + { + bool NeedsRealId { get; } + + event EventHandler NetworkChange; + + void DisconnectNetwork(); + void DisconnectAndStop(); + NetworkError Connect(ConnectRequest request); + NetworkError ConnectPrivate(ConnectPrivateRequest request); + ResultCode Reject(DisconnectReason disconnectReason, uint nodeId); + NetworkInfo[] Scan(ushort channel, ScanFilter scanFilter); + void SetGameVersion(byte[] versionString); + void SetStationAcceptPolicy(AcceptPolicy acceptPolicy); + void SetAdvertiseData(byte[] data); + bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData); + bool CreateNetworkPrivate(CreateAccessPointPrivateRequest request, byte[] advertiseData); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs new file mode 100644 index 00000000..1d4b5485 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs @@ -0,0 +1,1110 @@ +using LibHac.Ns; +using Ryujinx.Common; +using Ryujinx.Common.Configuration.Multiplayer; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm; +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; +using System.IO; +using System.Net; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator +{ + class IUserLocalCommunicationService : IpcService, IDisposable + { + public INetworkClient NetworkClient { get; private set; } + + private const int NifmRequestID = 90; + private const string DefaultIPAddress = "127.0.0.1"; + private const string DefaultSubnetMask = "255.255.255.0"; + private const bool IsDevelopment = false; + + private readonly KEvent _stateChangeEvent; + private int _stateChangeEventHandle; + + private NetworkState _state; + private DisconnectReason _disconnectReason; + private ResultCode _nifmResultCode; + + private AccessPoint _accessPoint; + private Station _station; + + public IUserLocalCommunicationService(ServiceCtx context) + { + _stateChangeEvent = new KEvent(context.Device.System.KernelContext); + _state = NetworkState.None; + _disconnectReason = DisconnectReason.None; + } + + private ushort CheckDevelopmentChannel(ushort channel) + { + return (ushort)(!IsDevelopment ? 0 : channel); + } + + private SecurityMode CheckDevelopmentSecurityMode(SecurityMode securityMode) + { + return !IsDevelopment ? SecurityMode.Retail : securityMode; + } + + private bool CheckLocalCommunicationIdPermission(ServiceCtx context, ulong localCommunicationIdChecked) + { + // TODO: Call nn::arp::GetApplicationControlProperty here when implemented. + ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties; + + foreach (var localCommunicationId in controlProperty.LocalCommunicationId.ItemsRo) + { + if (localCommunicationId == localCommunicationIdChecked) + { + return true; + } + } + + return false; + } + + [CommandCmif(0)] + // GetState() -> s32 state + public ResultCode GetState(ServiceCtx context) + { + if (_nifmResultCode != ResultCode.Success) + { + context.ResponseData.Write((int)NetworkState.Error); + + return ResultCode.Success; + } + + // NOTE: Returns ResultCode.InvalidArgument if _state is null, doesn't occur in our case. + context.ResponseData.Write((int)_state); + + return ResultCode.Success; + } + + public void SetState() + { + _stateChangeEvent.WritableEvent.Signal(); + } + + public void SetState(NetworkState state) + { + _state = state; + + SetState(); + } + + [CommandCmif(1)] + // GetNetworkInfo() -> buffer, 0x1a> + public ResultCode GetNetworkInfo(ServiceCtx context) + { + ulong bufferPosition = context.Request.RecvListBuff[0].Position; + + MemoryHelper.FillWithZeros(context.Memory, bufferPosition, 0x480); + + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo); + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + ulong infoSize = MemoryHelper.Write(context.Memory, bufferPosition, networkInfo); + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(infoSize); + + return ResultCode.Success; + } + + private ResultCode GetNetworkInfoImpl(out NetworkInfo networkInfo) + { + if (_state == NetworkState.StationConnected) + { + networkInfo = _station.NetworkInfo; + } + else if (_state == NetworkState.AccessPointCreated) + { + networkInfo = _accessPoint.NetworkInfo; + } + else + { + networkInfo = new NetworkInfo(); + + return ResultCode.InvalidState; + } + + return ResultCode.Success; + } + + private NodeLatestUpdate[] GetNodeLatestUpdateImpl(int count) + { + if (_state == NetworkState.StationConnected) + { + return _station.LatestUpdates.ConsumeLatestUpdate(count); + } + else if (_state == NetworkState.AccessPointCreated) + { + return _accessPoint.LatestUpdates.ConsumeLatestUpdate(count); + } + else + { + return Array.Empty(); + } + } + + [CommandCmif(2)] + // GetIpv4Address() -> (u32 ip_address, u32 subnet_mask) + public ResultCode GetIpv4Address(ServiceCtx context) + { + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + // NOTE: Return ResultCode.InvalidArgument if ip_address and subnet_mask are null, doesn't occur in our case. + + if (_state == NetworkState.AccessPointCreated || _state == NetworkState.StationConnected) + { + (_, UnicastIPAddressInformation unicastAddress) = NetworkHelpers.GetLocalInterface(context.Device.Configuration.MultiplayerLanInterfaceId); + + if (unicastAddress == null) + { + context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(DefaultIPAddress)); + context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(DefaultSubnetMask)); + } + else + { + Logger.Info?.Print(LogClass.ServiceLdn, $"Console's LDN IP is \"{unicastAddress.Address}\"."); + + context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.Address)); + context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.IPv4Mask)); + } + } + else + { + return ResultCode.InvalidArgument; + } + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetDisconnectReason() -> u16 disconnect_reason + public ResultCode GetDisconnectReason(ServiceCtx context) + { + // NOTE: Returns ResultCode.InvalidArgument if _disconnectReason is null, doesn't occur in our case. + + context.ResponseData.Write((short)_disconnectReason); + + return ResultCode.Success; + } + + public void SetDisconnectReason(DisconnectReason reason) + { + if (_state != NetworkState.Initialized) + { + _disconnectReason = reason; + + SetState(NetworkState.Initialized); + } + } + + [CommandCmif(4)] + // GetSecurityParameter() -> bytes<0x20, 1> security_parameter + public ResultCode GetSecurityParameter(ServiceCtx context) + { + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo); + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + SecurityParameter securityParameter = new() + { + Data = new Array16(), + SessionId = networkInfo.NetworkId.SessionId, + }; + + context.ResponseData.WriteStruct(securityParameter); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // GetNetworkConfig() -> bytes<0x20, 8> network_config + public ResultCode GetNetworkConfig(ServiceCtx context) + { + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo); + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + NetworkConfig networkConfig = new() + { + IntentId = networkInfo.NetworkId.IntentId, + Channel = networkInfo.Common.Channel, + NodeCountMax = networkInfo.Ldn.NodeCountMax, + LocalCommunicationVersion = networkInfo.Ldn.Nodes[0].LocalCommunicationVersion, + Reserved2 = new Array10(), + }; + + context.ResponseData.WriteStruct(networkConfig); + + return ResultCode.Success; + } + + [CommandCmif(100)] + // AttachStateChangeEvent() -> handle + public ResultCode AttachStateChangeEvent(ServiceCtx context) + { + if (_stateChangeEventHandle == 0 && context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out _stateChangeEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle); + + // Returns ResultCode.InvalidArgument if handle is null, doesn't occur in our case since we already throw an Exception. + + return ResultCode.Success; + } + + [CommandCmif(101)] + // GetNetworkInfoLatestUpdate() -> (buffer, 0x1a>, buffer) + public ResultCode GetNetworkInfoLatestUpdate(ServiceCtx context) + { + ulong bufferPosition = context.Request.RecvListBuff[0].Position; + + MemoryHelper.FillWithZeros(context.Memory, bufferPosition, 0x480); + + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo); + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + ulong outputPosition = context.Request.RecvListBuff[0].Position; + ulong outputSize = context.Request.RecvListBuff[0].Size; + + ulong latestUpdateSize = (ulong)Marshal.SizeOf(); + int count = (int)(outputSize / latestUpdateSize); + + NodeLatestUpdate[] latestUpdate = GetNodeLatestUpdateImpl(count); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + foreach (NodeLatestUpdate node in latestUpdate) + { + MemoryHelper.Write(context.Memory, outputPosition, node); + + outputPosition += latestUpdateSize; + } + + ulong infoSize = MemoryHelper.Write(context.Memory, bufferPosition, networkInfo); + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(infoSize); + + return ResultCode.Success; + } + + [CommandCmif(102)] + // Scan(u16 channel, bytes<0x60, 8> scan_filter) -> (u16 count, buffer) + public ResultCode Scan(ServiceCtx context) + { + return ScanImpl(context); + } + + [CommandCmif(103)] + // ScanPrivate(u16 channel, bytes<0x60, 8> scan_filter) -> (u16 count, buffer) + public ResultCode ScanPrivate(ServiceCtx context) + { + return ScanImpl(context, true); + } + + private ResultCode ScanImpl(ServiceCtx context, bool isPrivate = false) + { + ushort channel = (ushort)context.RequestData.ReadUInt64(); + ScanFilter scanFilter = context.RequestData.ReadStruct(); + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(0); + + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + if (!isPrivate) + { + channel = CheckDevelopmentChannel(channel); + } + + ResultCode resultCode = ResultCode.InvalidArgument; + + if (bufferSize != 0) + { + if (bufferPosition != 0) + { + ScanFilterFlag scanFilterFlag = scanFilter.Flag; + + if (!scanFilterFlag.HasFlag(ScanFilterFlag.NetworkType) || scanFilter.NetworkType <= NetworkType.All) + { + if (scanFilterFlag.HasFlag(ScanFilterFlag.Ssid)) + { + if (scanFilter.Ssid.Length <= 31) + { + return resultCode; + } + } + + if (!scanFilterFlag.HasFlag(ScanFilterFlag.MacAddress)) + { + if (scanFilterFlag > ScanFilterFlag.All) + { + return resultCode; + } + + if (_state - 3 >= NetworkState.AccessPoint) + { + resultCode = ResultCode.InvalidState; + } + else + { + if (scanFilter.NetworkId.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId) + { + // TODO: Call nn::arp::GetApplicationControlProperty here when implemented. + ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties; + + scanFilter.NetworkId.IntentId.LocalCommunicationId = (long)controlProperty.LocalCommunicationId[0]; + } + + resultCode = ScanInternal(context.Memory, channel, scanFilter, bufferPosition, bufferSize, out ulong counter); + + context.ResponseData.Write(counter); + } + } + else + { + throw new NotSupportedException(); + } + } + } + } + + return resultCode; + } + + private ResultCode ScanInternal(IVirtualMemoryManager memory, ushort channel, ScanFilter scanFilter, ulong bufferPosition, ulong bufferSize, out ulong counter) + { + ulong networkInfoSize = (ulong)Marshal.SizeOf(typeof(NetworkInfo)); + ulong maxGames = bufferSize / networkInfoSize; + + MemoryHelper.FillWithZeros(memory, bufferPosition, (int)bufferSize); + + NetworkInfo[] availableGames = NetworkClient.Scan(channel, scanFilter); + + counter = 0; + + foreach (NetworkInfo networkInfo in availableGames) + { + MemoryHelper.Write(memory, bufferPosition + (networkInfoSize * counter), networkInfo); + + if (++counter >= maxGames) + { + break; + } + } + + return ResultCode.Success; + } + + [CommandCmif(104)] // 5.0.0+ + // SetWirelessControllerRestriction(u32 wireless_controller_restriction) + public ResultCode SetWirelessControllerRestriction(ServiceCtx context) + { + // NOTE: Return ResultCode.InvalidArgument if an internal IPAddress is null, doesn't occur in our case. + + uint wirelessControllerRestriction = context.RequestData.ReadUInt32(); + + if (wirelessControllerRestriction > 1) + { + return ResultCode.InvalidArgument; + } + + if (_state != NetworkState.Initialized) + { + return ResultCode.InvalidState; + } + + // NOTE: WirelessControllerRestriction value is used for the btm service in SetWlanMode call. + // Since we use our own implementation we can do nothing here. + + return ResultCode.Success; + } + + [CommandCmif(200)] + // OpenAccessPoint() + public ResultCode OpenAccessPoint(ServiceCtx context) + { + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + if (_state != NetworkState.Initialized) + { + return ResultCode.InvalidState; + } + + CloseStation(); + + SetState(NetworkState.AccessPoint); + + _accessPoint = new AccessPoint(this); + + // NOTE: Calls nifm service and return related result codes. + // Since we use our own implementation we can return ResultCode.Success. + + return ResultCode.Success; + } + + [CommandCmif(201)] + // CloseAccessPoint() + public ResultCode CloseAccessPoint(ServiceCtx context) + { + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + if (_state == NetworkState.AccessPoint || _state == NetworkState.AccessPointCreated) + { + DestroyNetworkImpl(DisconnectReason.DestroyedByUser); + } + else + { + return ResultCode.InvalidState; + } + + SetState(NetworkState.Initialized); + + return ResultCode.Success; + } + + private void CloseAccessPoint() + { + _accessPoint?.Dispose(); + _accessPoint = null; + } + + [CommandCmif(202)] + // CreateNetwork(bytes<0x44, 2> security_config, bytes<0x30, 1> user_config, bytes<0x20, 8> network_config) + public ResultCode CreateNetwork(ServiceCtx context) + { + return CreateNetworkImpl(context); + } + + [CommandCmif(203)] + // CreateNetworkPrivate(bytes<0x44, 2> security_config, bytes<0x20, 1> security_parameter, bytes<0x30, 1>, bytes<0x20, 8> network_config, buffer address_entry, int count) + public ResultCode CreateNetworkPrivate(ServiceCtx context) + { + return CreateNetworkImpl(context, true); + } + + public ResultCode CreateNetworkImpl(ServiceCtx context, bool isPrivate = false) + { + SecurityConfig securityConfig = context.RequestData.ReadStruct(); + SecurityParameter securityParameter = isPrivate ? context.RequestData.ReadStruct() : new SecurityParameter(); + + UserConfig userConfig = context.RequestData.ReadStruct(); + + context.RequestData.BaseStream.Seek(4, SeekOrigin.Current); // Alignment? + NetworkConfig networkConfig = context.RequestData.ReadStruct(); + + if (networkConfig.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId) + { + // TODO: Call nn::arp::GetApplicationControlProperty here when implemented. + ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties; + + networkConfig.IntentId.LocalCommunicationId = (long)controlProperty.LocalCommunicationId[0]; + } + + bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkConfig.IntentId.LocalCommunicationId); + if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId) + { + return ResultCode.InvalidObject; + } + + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + networkConfig.Channel = CheckDevelopmentChannel(networkConfig.Channel); + securityConfig.SecurityMode = CheckDevelopmentSecurityMode(securityConfig.SecurityMode); + + if (networkConfig.NodeCountMax <= LdnConst.NodeCountMax) + { + if ((((ulong)networkConfig.LocalCommunicationVersion) & 0x80000000) == 0) + { + if (securityConfig.SecurityMode <= SecurityMode.Retail) + { + if (securityConfig.Passphrase.Length <= LdnConst.PassphraseLengthMax) + { + if (_state == NetworkState.AccessPoint) + { + if (isPrivate) + { + ulong bufferPosition = context.Request.PtrBuff[0].Position; + ulong bufferSize = context.Request.PtrBuff[0].Size; + + byte[] addressListBytes = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, addressListBytes); + + AddressList addressList = MemoryMarshal.Cast(addressListBytes)[0]; + + _accessPoint.CreateNetworkPrivate(securityConfig, securityParameter, userConfig, networkConfig, addressList); + } + else + { + _accessPoint.CreateNetwork(securityConfig, userConfig, networkConfig); + } + + return ResultCode.Success; + } + else + { + return ResultCode.InvalidState; + } + } + } + } + } + + return ResultCode.InvalidArgument; + } + + [CommandCmif(204)] + // DestroyNetwork() + public ResultCode DestroyNetwork(ServiceCtx context) + { + return DestroyNetworkImpl(DisconnectReason.DestroyedByUser); + } + + private ResultCode DestroyNetworkImpl(DisconnectReason disconnectReason) + { + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + if (disconnectReason - 3 <= DisconnectReason.DisconnectedByUser) + { + if (_state == NetworkState.AccessPointCreated) + { + CloseAccessPoint(); + + SetState(NetworkState.AccessPoint); + + return ResultCode.Success; + } + + CloseAccessPoint(); + + return ResultCode.InvalidState; + } + + return ResultCode.InvalidArgument; + } + + [CommandCmif(205)] + // Reject(u32 node_id) + public ResultCode Reject(ServiceCtx context) + { + uint nodeId = context.RequestData.ReadUInt32(); + + return RejectImpl(DisconnectReason.Rejected, nodeId); + } + + private ResultCode RejectImpl(DisconnectReason disconnectReason, uint nodeId) + { + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + if (_state != NetworkState.AccessPointCreated) + { + return ResultCode.InvalidState; // Must be network host to reject nodes. + } + + return NetworkClient.Reject(disconnectReason, nodeId); + } + + [CommandCmif(206)] + // SetAdvertiseData(buffer) + public ResultCode SetAdvertiseData(ServiceCtx context) + { + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(0); + + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + if (bufferSize == 0 || bufferSize > LdnConst.AdvertiseDataSizeMax) + { + return ResultCode.InvalidArgument; + } + + if (_state == NetworkState.AccessPoint || _state == NetworkState.AccessPointCreated) + { + byte[] advertiseData = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, advertiseData); + + return _accessPoint.SetAdvertiseData(advertiseData); + } + else + { + return ResultCode.InvalidState; + } + } + + [CommandCmif(207)] + // SetStationAcceptPolicy(u8 accept_policy) + public ResultCode SetStationAcceptPolicy(ServiceCtx context) + { + AcceptPolicy acceptPolicy = (AcceptPolicy)context.RequestData.ReadByte(); + + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + if (acceptPolicy > AcceptPolicy.WhiteList) + { + return ResultCode.InvalidArgument; + } + + if (_state == NetworkState.AccessPoint || _state == NetworkState.AccessPointCreated) + { + return _accessPoint.SetStationAcceptPolicy(acceptPolicy); + } + else + { + return ResultCode.InvalidState; + } + } + + [CommandCmif(208)] + // AddAcceptFilterEntry(bytes<6, 1> mac_address) + public ResultCode AddAcceptFilterEntry(ServiceCtx context) + { + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + // TODO + + return ResultCode.Success; + } + + [CommandCmif(209)] + // ClearAcceptFilter() + public ResultCode ClearAcceptFilter(ServiceCtx context) + { + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + // TODO + + return ResultCode.Success; + } + + [CommandCmif(300)] + // OpenStation() + public ResultCode OpenStation(ServiceCtx context) + { + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + if (_state != NetworkState.Initialized) + { + return ResultCode.InvalidState; + } + + CloseAccessPoint(); + + SetState(NetworkState.Station); + + _station?.Dispose(); + _station = new Station(this); + + // NOTE: Calls nifm service and returns related result codes. + // Since we use our own implementation we can return ResultCode.Success. + + return ResultCode.Success; + } + + [CommandCmif(301)] + // CloseStation() + public ResultCode CloseStation(ServiceCtx context) + { + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + if (_state == NetworkState.Station || _state == NetworkState.StationConnected) + { + DisconnectImpl(DisconnectReason.DisconnectedByUser); + } + else + { + return ResultCode.InvalidState; + } + + SetState(NetworkState.Initialized); + + return ResultCode.Success; + } + + private void CloseStation() + { + _station?.Dispose(); + _station = null; + } + + [CommandCmif(302)] + // Connect(bytes<0x44, 2> security_config, bytes<0x30, 1> user_config, u32 local_communication_version, u32 option_unknown, buffer, 0x19>) + public ResultCode Connect(ServiceCtx context) + { + return ConnectImpl(context); + } + + [CommandCmif(303)] + // ConnectPrivate(bytes<0x44, 2> security_config, bytes<0x20, 1> security_parameter, bytes<0x30, 1> user_config, u32 local_communication_version, u32 option_unknown, bytes<0x20, 8> network_config) + public ResultCode ConnectPrivate(ServiceCtx context) + { + return ConnectImpl(context, true); + } + + private ResultCode ConnectImpl(ServiceCtx context, bool isPrivate = false) + { + SecurityConfig securityConfig = context.RequestData.ReadStruct(); + SecurityParameter securityParameter = isPrivate ? context.RequestData.ReadStruct() : new SecurityParameter(); + + UserConfig userConfig = context.RequestData.ReadStruct(); + uint localCommunicationVersion = context.RequestData.ReadUInt32(); + uint optionUnknown = context.RequestData.ReadUInt32(); + + NetworkConfig networkConfig = new(); + NetworkInfo networkInfo = new(); + + if (isPrivate) + { + context.RequestData.ReadUInt32(); // Padding. + + networkConfig = context.RequestData.ReadStruct(); + } + else + { + ulong bufferPosition = context.Request.PtrBuff[0].Position; + ulong bufferSize = context.Request.PtrBuff[0].Size; + + byte[] networkInfoBytes = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, networkInfoBytes); + + networkInfo = MemoryMarshal.Read(networkInfoBytes); + } + + if (networkInfo.NetworkId.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId) + { + // TODO: Call nn::arp::GetApplicationControlProperty here when implemented. + ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties; + + networkInfo.NetworkId.IntentId.LocalCommunicationId = (long)controlProperty.LocalCommunicationId[0]; + } + + bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkInfo.NetworkId.IntentId.LocalCommunicationId); + if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId) + { + return ResultCode.InvalidObject; + } + + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + securityConfig.SecurityMode = CheckDevelopmentSecurityMode(securityConfig.SecurityMode); + + ResultCode resultCode = ResultCode.InvalidArgument; + + if (securityConfig.SecurityMode - 1 <= SecurityMode.Debug) + { + if (optionUnknown <= 1 && (localCommunicationVersion >> 15) == 0 && securityConfig.PassphraseSize <= 64) + { + resultCode = ResultCode.VersionTooLow; + if (localCommunicationVersion >= 0) + { + resultCode = ResultCode.VersionTooHigh; + if (localCommunicationVersion <= short.MaxValue) + { + if (_state != NetworkState.Station) + { + resultCode = ResultCode.InvalidState; + } + else + { + if (isPrivate) + { + resultCode = _station.ConnectPrivate(securityConfig, securityParameter, userConfig, localCommunicationVersion, optionUnknown, networkConfig); + } + else + { + resultCode = _station.Connect(securityConfig, userConfig, localCommunicationVersion, optionUnknown, networkInfo); + } + } + } + } + } + } + + return resultCode; + } + + [CommandCmif(304)] + // Disconnect() + public ResultCode Disconnect(ServiceCtx context) + { + return DisconnectImpl(DisconnectReason.DisconnectedByUser); + } + + private ResultCode DisconnectImpl(DisconnectReason disconnectReason) + { + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + if (disconnectReason <= DisconnectReason.DisconnectedBySystem) + { + if (_state == NetworkState.StationConnected) + { + SetState(NetworkState.Station); + + CloseStation(); + + _disconnectReason = disconnectReason; + + return ResultCode.Success; + } + + CloseStation(); + + return ResultCode.InvalidState; + } + + return ResultCode.InvalidArgument; + } + + [CommandCmif(400)] + // InitializeOld(pid) + public ResultCode InitializeOld(ServiceCtx context) + { + return InitializeImpl(context, context.Process.Pid, NifmRequestID); + } + + [CommandCmif(401)] + // Finalize() + public ResultCode Finalize(ServiceCtx context) + { + if (_nifmResultCode != ResultCode.Success) + { + return _nifmResultCode; + } + + // NOTE: Use true when its called in nn::ldn::detail::ISystemLocalCommunicationService + ResultCode resultCode = FinalizeImpl(false); + if (resultCode == ResultCode.Success) + { + SetDisconnectReason(DisconnectReason.None); + } + + if (_stateChangeEventHandle != 0) + { + context.Process.HandleTable.CloseHandle(_stateChangeEventHandle); + _stateChangeEventHandle = 0; + } + + return resultCode; + } + + private ResultCode FinalizeImpl(bool isCausedBySystem) + { + DisconnectReason disconnectReason; + + switch (_state) + { + case NetworkState.None: + return ResultCode.Success; + case NetworkState.AccessPoint: + { + CloseAccessPoint(); + + break; + } + case NetworkState.AccessPointCreated: + { + if (isCausedBySystem) + { + disconnectReason = DisconnectReason.DestroyedBySystem; + } + else + { + disconnectReason = DisconnectReason.DestroyedByUser; + } + + DestroyNetworkImpl(disconnectReason); + + break; + } + case NetworkState.Station: + { + CloseStation(); + + break; + } + case NetworkState.StationConnected: + { + if (isCausedBySystem) + { + disconnectReason = DisconnectReason.DisconnectedBySystem; + } + else + { + disconnectReason = DisconnectReason.DisconnectedByUser; + } + + DisconnectImpl(disconnectReason); + + break; + } + } + + SetState(NetworkState.None); + + NetworkClient?.Dispose(); + NetworkClient = null; + + return ResultCode.Success; + } + + [CommandCmif(402)] // 7.0.0+ + // Initialize(u64 ip_addresses, pid) + public ResultCode Initialize(ServiceCtx context) + { + _ = new IPAddress(context.RequestData.ReadUInt32()); + _ = new IPAddress(context.RequestData.ReadUInt32()); + + // NOTE: It seems the guest can get ip_address and subnet_mask from nifm service and pass it through the initialize. + // This calls InitializeImpl() twice: The first time with NIFM_REQUEST_ID, and if it fails, a second time with nifm_request_id = 1. + + return InitializeImpl(context, context.Process.Pid, NifmRequestID); + } + + public ResultCode InitializeImpl(ServiceCtx context, ulong pid, int nifmRequestId) + { + ResultCode resultCode = ResultCode.InvalidArgument; + + if (nifmRequestId <= 255) + { + if (_state != NetworkState.Initialized) + { + // NOTE: Service calls nn::ldn::detail::NetworkInterfaceManager::NetworkInterfaceMonitor::Initialize() with nifmRequestId as argument, + // then it stores the result code of it in a global variable. Since we use our own implementation, we can just check the connection + // and return related error codes. + if (System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable()) + { + MultiplayerMode mode = context.Device.Configuration.MultiplayerMode; + + Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Initializing with multiplayer mode: {mode}"); + + switch (mode) + { + case MultiplayerMode.LdnMitm: + NetworkClient = new LdnMitmClient(context.Device.Configuration); + break; + case MultiplayerMode.Disabled: + NetworkClient = new LdnDisabledClient(); + break; + } + + // TODO: Call nn::arp::GetApplicationLaunchProperty here when implemented. + NetworkClient.SetGameVersion(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion.Items.ToArray()); + + resultCode = ResultCode.Success; + + _nifmResultCode = resultCode; + + SetState(NetworkState.Initialized); + } + else + { + // NOTE: Service returns different ResultCode here related to the nifm ResultCode. + resultCode = ResultCode.DeviceDisabled; + _nifmResultCode = resultCode; + } + } + } + + return resultCode; + } + + public void Dispose() + { + _station?.Dispose(); + _station = null; + + _accessPoint?.Dispose(); + _accessPoint = null; + + NetworkClient?.Dispose(); + NetworkClient = null; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnDisabledClient.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnDisabledClient.cs new file mode 100644 index 00000000..e3385a1e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnDisabledClient.cs @@ -0,0 +1,63 @@ +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator +{ + class LdnDisabledClient : INetworkClient + { + public bool NeedsRealId => true; + + public event EventHandler NetworkChange; + + public NetworkError Connect(ConnectRequest request) + { + NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false)); + + return NetworkError.None; + } + + public NetworkError ConnectPrivate(ConnectPrivateRequest request) + { + NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false)); + + return NetworkError.None; + } + + public bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData) + { + NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false)); + + return true; + } + + public bool CreateNetworkPrivate(CreateAccessPointPrivateRequest request, byte[] advertiseData) + { + NetworkChange?.Invoke(this, new NetworkChangeEventArgs(new NetworkInfo(), false)); + + return true; + } + + public void DisconnectAndStop() { } + + public void DisconnectNetwork() { } + + public ResultCode Reject(DisconnectReason disconnectReason, uint nodeId) + { + return ResultCode.Success; + } + + public NetworkInfo[] Scan(ushort channel, ScanFilter scanFilter) + { + return Array.Empty(); + } + + public void SetAdvertiseData(byte[] data) { } + + public void SetGameVersion(byte[] versionString) { } + + public void SetStationAcceptPolicy(AcceptPolicy acceptPolicy) { } + + public void Dispose() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/LanDiscovery.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/LanDiscovery.cs new file mode 100644 index 00000000..b5f643d2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/LanDiscovery.cs @@ -0,0 +1,611 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy; +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types; +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm +{ + internal class LanDiscovery : IDisposable + { + private const int DefaultPort = 11452; + private const ushort CommonChannel = 6; + private const byte CommonLinkLevel = 3; + private const byte CommonNetworkType = 2; + + private const int FailureTimeout = 4000; + + private readonly LdnMitmClient _parent; + private readonly LanProtocol _protocol; + private bool _initialized; + private readonly Ssid _fakeSsid; + private ILdnTcpSocket _tcp; + private LdnProxyUdpServer _udp, _udp2; + private readonly List _stations = new(); + private readonly object _lock = new(); + + private readonly AutoResetEvent _apConnected = new(false); + + internal readonly IPAddress LocalAddr; + internal readonly IPAddress LocalBroadcastAddr; + internal NetworkInfo NetworkInfo; + + public bool IsHost => _tcp is LdnProxyTcpServer; + + private readonly Random _random = new(); + + // NOTE: Credit to https://stackoverflow.com/a/39338188 + private static IPAddress GetBroadcastAddress(IPAddress address, IPAddress mask) + { + uint ipAddress = BitConverter.ToUInt32(address.GetAddressBytes(), 0); + uint ipMaskV4 = BitConverter.ToUInt32(mask.GetAddressBytes(), 0); + uint broadCastIpAddress = ipAddress | ~ipMaskV4; + + return new IPAddress(BitConverter.GetBytes(broadCastIpAddress)); + } + + private static NetworkInfo GetEmptyNetworkInfo() + { + NetworkInfo networkInfo = new() + { + NetworkId = new NetworkId + { + SessionId = new Array16(), + }, + Common = new CommonNetworkInfo + { + MacAddress = new Array6(), + Ssid = new Ssid + { + Name = new Array33(), + }, + }, + Ldn = new LdnNetworkInfo + { + NodeCountMax = LdnConst.NodeCountMax, + SecurityParameter = new Array16(), + Nodes = new Array8(), + AdvertiseData = new Array384(), + Reserved4 = new Array140(), + }, + }; + + for (int i = 0; i < LdnConst.NodeCountMax; i++) + { + networkInfo.Ldn.Nodes[i] = new NodeInfo + { + MacAddress = new Array6(), + UserName = new Array33(), + Reserved2 = new Array16(), + }; + } + + return networkInfo; + } + + public LanDiscovery(LdnMitmClient parent, IPAddress ipAddress, IPAddress ipv4Mask) + { + Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Initialize LanDiscovery using IP: {ipAddress}"); + + _parent = parent; + LocalAddr = ipAddress; + LocalBroadcastAddr = GetBroadcastAddress(ipAddress, ipv4Mask); + + _fakeSsid = new Ssid + { + Length = LdnConst.SsidLengthMax, + }; + _random.NextBytes(_fakeSsid.Name.AsSpan()[..32]); + + _protocol = new LanProtocol(this); + _protocol.Accept += OnConnect; + _protocol.SyncNetwork += OnSyncNetwork; + _protocol.DisconnectStation += DisconnectStation; + + NetworkInfo = GetEmptyNetworkInfo(); + + ResetStations(); + + if (!InitUdp()) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, "LanDiscovery Initialize: InitUdp failed."); + + return; + } + + _initialized = true; + } + + protected void OnSyncNetwork(NetworkInfo info) + { + bool updated = false; + + lock (_lock) + { + if (!NetworkInfo.Equals(info)) + { + NetworkInfo = info; + updated = true; + + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"Host IP: {NetworkHelpers.ConvertUint(info.Ldn.Nodes[0].Ipv4Address)}"); + } + } + + if (updated) + { + _parent.InvokeNetworkChange(info, true); + } + + _apConnected.Set(); + } + + protected void OnConnect(LdnProxyTcpSession station) + { + lock (_lock) + { + station.NodeId = LocateEmptyNode(); + + if (_stations.Count > LdnConst.StationCountMax || station.NodeId == -1) + { + station.Disconnect(); + station.Dispose(); + + return; + } + + _stations.Add(station); + + UpdateNodes(); + } + } + + public void DisconnectStation(LdnProxyTcpSession station) + { + if (!station.IsDisposed) + { + if (station.IsConnected) + { + station.Disconnect(); + } + + station.Dispose(); + } + + lock (_lock) + { + if (_stations.Remove(station)) + { + NetworkInfo.Ldn.Nodes[station.NodeId] = new NodeInfo() + { + MacAddress = new Array6(), + UserName = new Array33(), + Reserved2 = new Array16(), + }; + + UpdateNodes(); + } + } + } + + public bool SetAdvertiseData(byte[] data) + { + if (data.Length > LdnConst.AdvertiseDataSizeMax) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, "AdvertiseData exceeds size limit."); + + return false; + } + + data.CopyTo(NetworkInfo.Ldn.AdvertiseData.AsSpan()); + NetworkInfo.Ldn.AdvertiseDataSize = (ushort)data.Length; + + // NOTE: Otherwise this results in SessionKeepFailed or MasterDisconnected + lock (_lock) + { + if (NetworkInfo.Ldn.Nodes[0].IsConnected == 1) + { + UpdateNodes(true); + } + } + + return true; + } + + public void InitNetworkInfo() + { + lock (_lock) + { + NetworkInfo.Common.MacAddress = GetFakeMac(); + NetworkInfo.Common.Channel = CommonChannel; + NetworkInfo.Common.LinkLevel = CommonLinkLevel; + NetworkInfo.Common.NetworkType = CommonNetworkType; + NetworkInfo.Common.Ssid = _fakeSsid; + + NetworkInfo.Ldn.Nodes = new Array8(); + + for (int i = 0; i < LdnConst.NodeCountMax; i++) + { + NetworkInfo.Ldn.Nodes[i].NodeId = (byte)i; + NetworkInfo.Ldn.Nodes[i].IsConnected = 0; + } + } + } + + protected Array6 GetFakeMac(IPAddress address = null) + { + address ??= LocalAddr; + + byte[] ip = address.GetAddressBytes(); + + var macAddress = new Array6(); + new byte[] { 0x02, 0x00, ip[0], ip[1], ip[2], ip[3] }.CopyTo(macAddress.AsSpan()); + + return macAddress; + } + + public bool InitTcp(bool listening, IPAddress address = null, int port = DefaultPort) + { + Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LanDiscovery InitTcp: IP: {address}, listening: {listening}"); + + if (_tcp != null) + { + _tcp.DisconnectAndStop(); + _tcp.Dispose(); + _tcp = null; + } + + ILdnTcpSocket tcpSocket; + + if (listening) + { + try + { + address ??= LocalAddr; + + tcpSocket = new LdnProxyTcpServer(_protocol, address, port); + } + catch (Exception ex) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to create LdnProxyTcpServer: {ex}"); + + return false; + } + + if (!tcpSocket.Start()) + { + return false; + } + } + else + { + if (address == null) + { + return false; + } + + try + { + tcpSocket = new LdnProxyTcpClient(_protocol, address, port); + } + catch (Exception ex) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to create LdnProxyTcpClient: {ex}"); + + return false; + } + } + + _tcp = tcpSocket; + + return true; + } + + public bool InitUdp() + { + _udp?.Stop(); + _udp2?.Stop(); + + try + { + // NOTE: Linux won't receive any broadcast packets if the socket is not bound to the broadcast address. + // Windows only works if bound to localhost or the local address. + // See this discussion: https://stackoverflow.com/questions/13666789/receiving-udp-broadcast-packets-on-linux + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + _udp2 = new LdnProxyUdpServer(_protocol, LocalBroadcastAddr, DefaultPort); + } + + _udp = new LdnProxyUdpServer(_protocol, LocalAddr, DefaultPort); + } + catch (Exception ex) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to create LdnProxyUdpServer: {ex}"); + + return false; + } + + return true; + } + + public NetworkInfo[] Scan(ushort channel, ScanFilter filter) + { + _udp.ClearScanResults(); + + if (_protocol.SendBroadcast(_udp, LanPacketType.Scan, DefaultPort) < 0) + { + return Array.Empty(); + } + + List outNetworkInfo = new(); + + foreach (KeyValuePair item in _udp.GetScanResults()) + { + bool copy = true; + + if (filter.Flag.HasFlag(ScanFilterFlag.LocalCommunicationId)) + { + copy &= filter.NetworkId.IntentId.LocalCommunicationId == item.Value.NetworkId.IntentId.LocalCommunicationId; + } + + if (filter.Flag.HasFlag(ScanFilterFlag.SessionId)) + { + copy &= filter.NetworkId.SessionId.AsSpan().SequenceEqual(item.Value.NetworkId.SessionId.AsSpan()); + } + + if (filter.Flag.HasFlag(ScanFilterFlag.NetworkType)) + { + copy &= filter.NetworkType == (NetworkType)item.Value.Common.NetworkType; + } + + if (filter.Flag.HasFlag(ScanFilterFlag.Ssid)) + { + Span gameSsid = item.Value.Common.Ssid.Name.AsSpan()[item.Value.Common.Ssid.Length..]; + Span scanSsid = filter.Ssid.Name.AsSpan()[filter.Ssid.Length..]; + copy &= gameSsid.SequenceEqual(scanSsid); + } + + if (filter.Flag.HasFlag(ScanFilterFlag.SceneId)) + { + copy &= filter.NetworkId.IntentId.SceneId == item.Value.NetworkId.IntentId.SceneId; + } + + if (copy) + { + if (item.Value.Ldn.Nodes[0].UserName[0] != 0) + { + outNetworkInfo.Add(item.Value); + } + else + { + Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "LanDiscovery Scan: Got empty Username. There might be a timing issue somewhere..."); + } + } + } + + return outNetworkInfo.ToArray(); + } + + protected void ResetStations() + { + lock (_lock) + { + foreach (LdnProxyTcpSession station in _stations) + { + station.Disconnect(); + station.Dispose(); + } + + _stations.Clear(); + } + } + + private int LocateEmptyNode() + { + Array8 nodes = NetworkInfo.Ldn.Nodes; + + for (int i = 1; i < nodes.Length; i++) + { + if (nodes[i].IsConnected == 0) + { + return i; + } + } + + return -1; + } + + protected void UpdateNodes(bool forceUpdate = false) + { + int countConnected = 1; + + foreach (LdnProxyTcpSession station in _stations.Where(station => station.IsConnected)) + { + countConnected++; + + station.OverrideInfo(); + + // NOTE: This is not part of the original implementation. + NetworkInfo.Ldn.Nodes[station.NodeId] = station.NodeInfo; + } + + byte nodeCount = (byte)countConnected; + + bool networkInfoChanged = forceUpdate || NetworkInfo.Ldn.NodeCount != nodeCount; + + NetworkInfo.Ldn.NodeCount = nodeCount; + + foreach (LdnProxyTcpSession station in _stations) + { + if (station.IsConnected) + { + if (_protocol.SendPacket(station, LanPacketType.SyncNetwork, SpanHelpers.AsSpan(ref NetworkInfo).ToArray()) < 0) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to send {LanPacketType.SyncNetwork} to station {station.NodeId}"); + } + } + } + + if (networkInfoChanged) + { + _parent.InvokeNetworkChange(NetworkInfo, true); + } + } + + protected NodeInfo GetNodeInfo(NodeInfo node, UserConfig userConfig, ushort localCommunicationVersion) + { + uint ipAddress = NetworkHelpers.ConvertIpv4Address(LocalAddr); + + node.MacAddress = GetFakeMac(); + node.IsConnected = 1; + node.UserName = userConfig.UserName; + node.LocalCommunicationVersion = localCommunicationVersion; + node.Ipv4Address = ipAddress; + + return node; + } + + public bool CreateNetwork(SecurityConfig securityConfig, UserConfig userConfig, NetworkConfig networkConfig) + { + if (!InitTcp(true)) + { + return false; + } + + InitNetworkInfo(); + + NetworkInfo.Ldn.NodeCountMax = networkConfig.NodeCountMax; + NetworkInfo.Ldn.SecurityMode = (ushort)securityConfig.SecurityMode; + + NetworkInfo.Common.Channel = networkConfig.Channel == 0 ? (ushort)6 : networkConfig.Channel; + + NetworkInfo.NetworkId.SessionId = new Array16(); + _random.NextBytes(NetworkInfo.NetworkId.SessionId.AsSpan()); + NetworkInfo.NetworkId.IntentId = networkConfig.IntentId; + + NetworkInfo.Ldn.Nodes[0] = GetNodeInfo(NetworkInfo.Ldn.Nodes[0], userConfig, networkConfig.LocalCommunicationVersion); + NetworkInfo.Ldn.Nodes[0].IsConnected = 1; + NetworkInfo.Ldn.NodeCount++; + + _parent.InvokeNetworkChange(NetworkInfo, true); + + return true; + } + + public void DestroyNetwork() + { + if (_tcp != null) + { + try + { + _tcp.DisconnectAndStop(); + } + finally + { + _tcp.Dispose(); + _tcp = null; + } + } + + ResetStations(); + } + + public NetworkError Connect(NetworkInfo networkInfo, UserConfig userConfig, uint localCommunicationVersion) + { + _apConnected.Reset(); + + if (networkInfo.Ldn.NodeCount == 0) + { + return NetworkError.Unknown; + } + + IPAddress address = NetworkHelpers.ConvertUint(networkInfo.Ldn.Nodes[0].Ipv4Address); + + Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Connecting to host: {address}"); + + if (!InitTcp(false, address)) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, "Could not initialize TCPClient"); + + return NetworkError.ConnectNotFound; + } + + if (!_tcp.Connect()) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, "Failed to connect."); + + return NetworkError.ConnectFailure; + } + + NodeInfo myNode = GetNodeInfo(new NodeInfo(), userConfig, (ushort)localCommunicationVersion); + if (_protocol.SendPacket(_tcp, LanPacketType.Connect, SpanHelpers.AsSpan(ref myNode).ToArray()) < 0) + { + return NetworkError.Unknown; + } + + return _apConnected.WaitOne(FailureTimeout) ? NetworkError.None : NetworkError.ConnectTimeout; + } + + public void Dispose() + { + if (_initialized) + { + DisconnectAndStop(); + ResetStations(); + _initialized = false; + } + + _protocol.Accept -= OnConnect; + _protocol.SyncNetwork -= OnSyncNetwork; + _protocol.DisconnectStation -= DisconnectStation; + } + + public void DisconnectAndStop() + { + if (_udp != null) + { + try + { + _udp.Stop(); + } + finally + { + _udp.Dispose(); + _udp = null; + } + } + + if (_udp2 != null) + { + try + { + _udp2.Stop(); + } + finally + { + _udp2.Dispose(); + _udp2 = null; + } + } + + if (_tcp != null) + { + try + { + _tcp.DisconnectAndStop(); + } + finally + { + _tcp.Dispose(); + _tcp = null; + } + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/LanProtocol.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/LanProtocol.cs new file mode 100644 index 00000000..4b01bfe3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/LanProtocol.cs @@ -0,0 +1,314 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy; +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm +{ + internal class LanProtocol + { + private const uint LanMagic = 0x11451400; + + public const int BufferSize = 2048; + public const int TcpTxBufferSize = 0x800; + public const int TcpRxBufferSize = 0x1000; + public const int TxBufferSizeMax = 0x2000; + public const int RxBufferSizeMax = 0x2000; + + private readonly int _headerSize = Marshal.SizeOf(); + + private readonly LanDiscovery _discovery; + + public event Action Accept; + public event Action Scan; + public event Action ScanResponse; + public event Action SyncNetwork; + public event Action Connect; + public event Action DisconnectStation; + + public LanProtocol(LanDiscovery parent) + { + _discovery = parent; + } + + public void InvokeAccept(LdnProxyTcpSession session) + { + Accept?.Invoke(session); + } + + public void InvokeDisconnectStation(LdnProxyTcpSession session) + { + DisconnectStation?.Invoke(session); + } + + private void DecodeAndHandle(LanPacketHeader header, byte[] data, EndPoint endPoint = null) + { + switch (header.Type) + { + case LanPacketType.Scan: + // UDP + if (_discovery.IsHost) + { + Scan?.Invoke(endPoint, LanPacketType.ScanResponse, SpanHelpers.AsSpan(ref _discovery.NetworkInfo).ToArray()); + } + break; + case LanPacketType.ScanResponse: + // UDP + ScanResponse?.Invoke(MemoryMarshal.Cast(data)[0]); + break; + case LanPacketType.SyncNetwork: + // TCP + SyncNetwork?.Invoke(MemoryMarshal.Cast(data)[0]); + break; + case LanPacketType.Connect: + // TCP Session / Station + Connect?.Invoke(MemoryMarshal.Cast(data)[0], endPoint); + break; + default: + Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decode error: Unhandled type {header.Type}"); + break; + } + } + + public void Read(scoped ref byte[] buffer, scoped ref int bufferEnd, byte[] data, int offset, int size, EndPoint endPoint = null) + { + if (endPoint != null && _discovery.LocalAddr.Equals(((IPEndPoint)endPoint).Address)) + { + return; + } + + int index = 0; + while (index < size) + { + if (bufferEnd < _headerSize) + { + int copyable2 = Math.Min(size - index, Math.Min(size, _headerSize - bufferEnd)); + + Array.Copy(data, index + offset, buffer, bufferEnd, copyable2); + + index += copyable2; + bufferEnd += copyable2; + } + + if (bufferEnd >= _headerSize) + { + LanPacketHeader header = MemoryMarshal.Cast(buffer)[0]; + if (header.Magic != LanMagic) + { + bufferEnd = 0; + + Logger.Warning?.PrintMsg(LogClass.ServiceLdn, $"Invalid magic number in received packet. [magic: {header.Magic}] [EP: {endPoint}]"); + + return; + } + + int totalSize = _headerSize + header.Length; + if (totalSize > BufferSize) + { + bufferEnd = 0; + + Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Max packet size {BufferSize} exceeded."); + + return; + } + + int copyable = Math.Min(size - index, Math.Min(size, totalSize - bufferEnd)); + + Array.Copy(data, index + offset, buffer, bufferEnd, copyable); + + index += copyable; + bufferEnd += copyable; + + if (totalSize == bufferEnd) + { + byte[] ldnData = new byte[totalSize - _headerSize]; + Array.Copy(buffer, _headerSize, ldnData, 0, ldnData.Length); + + if (header.Compressed == 1) + { + if (Decompress(ldnData, out byte[] decompressedLdnData) != 0) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error:\n {header}, {_headerSize}\n {ldnData}, {ldnData.Length}"); + + return; + } + + if (decompressedLdnData.Length != header.DecompressLength) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error: length does not match. ({decompressedLdnData.Length} != {header.DecompressLength})"); + Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error data: '{string.Join("", decompressedLdnData.Select(x => (int)x).ToArray())}'"); + + return; + } + + ldnData = decompressedLdnData; + } + + DecodeAndHandle(header, ldnData, endPoint); + + bufferEnd = 0; + } + } + } + } + + public int SendBroadcast(ILdnSocket s, LanPacketType type, int port) + { + return SendPacket(s, type, Array.Empty(), new IPEndPoint(_discovery.LocalBroadcastAddr, port)); + } + + public int SendPacket(ILdnSocket s, LanPacketType type, byte[] data, EndPoint endPoint = null) + { + byte[] buf = PreparePacket(type, data); + + return s.SendPacketAsync(endPoint, buf) ? 0 : -1; + } + + public int SendPacket(LdnProxyTcpSession s, LanPacketType type, byte[] data) + { + byte[] buf = PreparePacket(type, data); + + return s.SendAsync(buf) ? 0 : -1; + } + + private LanPacketHeader PrepareHeader(LanPacketHeader header, LanPacketType type) + { + header.Magic = LanMagic; + header.Type = type; + header.Compressed = 0; + header.Length = 0; + header.DecompressLength = 0; + header.Reserved = new Array2(); + + return header; + } + + private byte[] PreparePacket(LanPacketType type, byte[] data) + { + LanPacketHeader header = PrepareHeader(new LanPacketHeader(), type); + header.Length = (ushort)data.Length; + + byte[] buf; + if (data.Length > 0) + { + if (Compress(data, out byte[] compressed) == 0) + { + header.DecompressLength = header.Length; + header.Length = (ushort)compressed.Length; + header.Compressed = 1; + + buf = new byte[compressed.Length + _headerSize]; + + SpanHelpers.AsSpan(ref header).ToArray().CopyTo(buf, 0); + compressed.CopyTo(buf, _headerSize); + } + else + { + buf = new byte[data.Length + _headerSize]; + + Logger.Error?.PrintMsg(LogClass.ServiceLdn, "Compressing packet data failed."); + + SpanHelpers.AsSpan(ref header).ToArray().CopyTo(buf, 0); + data.CopyTo(buf, _headerSize); + } + } + else + { + buf = new byte[_headerSize]; + SpanHelpers.AsSpan(ref header).ToArray().CopyTo(buf, 0); + } + + return buf; + } + + private int Compress(byte[] input, out byte[] output) + { + List outputList = new(); + int i = 0; + int maxCount = 0xFF; + + while (i < input.Length) + { + byte inputByte = input[i++]; + int count = 0; + + if (inputByte == 0) + { + while (i < input.Length && input[i] == 0 && count < maxCount) + { + count += 1; + i++; + } + } + + if (inputByte == 0) + { + outputList.Add(0); + + if (outputList.Count == BufferSize) + { + output = null; + + return -1; + } + + outputList.Add((byte)count); + } + else + { + outputList.Add(inputByte); + } + } + + output = outputList.ToArray(); + + return i == input.Length ? 0 : -1; + } + + private int Decompress(byte[] input, out byte[] output) + { + List outputList = new(); + int i = 0; + + while (i < input.Length && outputList.Count < BufferSize) + { + byte inputByte = input[i++]; + + outputList.Add(inputByte); + + if (inputByte == 0) + { + if (i == input.Length) + { + output = null; + + return -1; + } + + int count = input[i++]; + + for (int j = 0; j < count; j++) + { + if (outputList.Count == BufferSize) + { + break; + } + + outputList.Add(inputByte); + } + } + } + + output = outputList.ToArray(); + + return i == input.Length ? 0 : -1; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/LdnMitmClient.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/LdnMitmClient.cs new file mode 100644 index 00000000..273acdd5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/LdnMitmClient.cs @@ -0,0 +1,104 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types; +using System; +using System.Net.NetworkInformation; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm +{ + /// + /// Client implementation for ldn_mitm + /// + internal class LdnMitmClient : INetworkClient + { + public bool NeedsRealId => false; + + public event EventHandler NetworkChange; + + private readonly LanDiscovery _lanDiscovery; + + public LdnMitmClient(HLEConfiguration config) + { + UnicastIPAddressInformation localIpInterface = NetworkHelpers.GetLocalInterface(config.MultiplayerLanInterfaceId).Item2; + + _lanDiscovery = new LanDiscovery(this, localIpInterface.Address, localIpInterface.IPv4Mask); + } + + internal void InvokeNetworkChange(NetworkInfo info, bool connected, DisconnectReason reason = DisconnectReason.None) + { + NetworkChange?.Invoke(this, new NetworkChangeEventArgs(info, connected: connected, disconnectReason: reason)); + } + + public NetworkError Connect(ConnectRequest request) + { + return _lanDiscovery.Connect(request.NetworkInfo, request.UserConfig, request.LocalCommunicationVersion); + } + + public NetworkError ConnectPrivate(ConnectPrivateRequest request) + { + // NOTE: This method is not implemented in ldn_mitm + Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient ConnectPrivate"); + + return NetworkError.None; + } + + public bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData) + { + return _lanDiscovery.CreateNetwork(request.SecurityConfig, request.UserConfig, request.NetworkConfig); + } + + public bool CreateNetworkPrivate(CreateAccessPointPrivateRequest request, byte[] advertiseData) + { + // NOTE: This method is not implemented in ldn_mitm + Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient CreateNetworkPrivate"); + + return true; + } + + public void DisconnectAndStop() + { + _lanDiscovery.DisconnectAndStop(); + } + + public void DisconnectNetwork() + { + _lanDiscovery.DestroyNetwork(); + } + + public ResultCode Reject(DisconnectReason disconnectReason, uint nodeId) + { + // NOTE: This method is not implemented in ldn_mitm + Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient Reject"); + + return ResultCode.Success; + } + + public NetworkInfo[] Scan(ushort channel, ScanFilter scanFilter) + { + return _lanDiscovery.Scan(channel, scanFilter); + } + + public void SetAdvertiseData(byte[] data) + { + _lanDiscovery.SetAdvertiseData(data); + } + + public void SetGameVersion(byte[] versionString) + { + // NOTE: This method is not implemented in ldn_mitm + Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient SetGameVersion"); + } + + public void SetStationAcceptPolicy(AcceptPolicy acceptPolicy) + { + // NOTE: This method is not implemented in ldn_mitm + Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient SetStationAcceptPolicy"); + } + + public void Dispose() + { + _lanDiscovery.Dispose(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnSocket.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnSocket.cs new file mode 100644 index 00000000..9427cc94 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnSocket.cs @@ -0,0 +1,12 @@ +using System; +using System.Net; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy +{ + internal interface ILdnSocket : IDisposable + { + bool SendPacketAsync(EndPoint endpoint, byte[] buffer); + bool Start(); + bool Stop(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnTcpSocket.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnTcpSocket.cs new file mode 100644 index 00000000..dfdd5fe7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/ILdnTcpSocket.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy +{ + internal interface ILdnTcpSocket : ILdnSocket + { + bool Connect(); + void DisconnectAndStop(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpClient.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpClient.cs new file mode 100644 index 00000000..7f755b40 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpClient.cs @@ -0,0 +1,99 @@ +using Ryujinx.Common.Logging; +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy +{ + internal class LdnProxyTcpClient : NetCoreServer.TcpClient, ILdnTcpSocket + { + private readonly LanProtocol _protocol; + private byte[] _buffer; + private int _bufferEnd; + + public LdnProxyTcpClient(LanProtocol protocol, IPAddress address, int port) : base(address, port) + { + _protocol = protocol; + _buffer = new byte[LanProtocol.BufferSize]; + OptionSendBufferSize = LanProtocol.TcpTxBufferSize; + OptionReceiveBufferSize = LanProtocol.TcpRxBufferSize; + OptionSendBufferLimit = LanProtocol.TxBufferSizeMax; + OptionReceiveBufferLimit = LanProtocol.RxBufferSizeMax; + } + + protected override void OnConnected() + { + Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPClient connected!"); + } + + protected override void OnReceived(byte[] buffer, long offset, long size) + { + _protocol.Read(ref _buffer, ref _bufferEnd, buffer, (int)offset, (int)size); + } + + public void DisconnectAndStop() + { + DisconnectAsync(); + + while (IsConnected) + { + Thread.Yield(); + } + } + + public bool SendPacketAsync(EndPoint endPoint, byte[] data) + { + if (endPoint != null) + { + Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTcpClient is sending a packet but endpoint is not null."); + } + + if (IsConnecting && !IsConnected) + { + Logger.Info?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTCPClient needs to connect before sending packets. Waiting..."); + + while (IsConnecting && !IsConnected) + { + Thread.Yield(); + } + } + + return SendAsync(data); + } + + protected override void OnError(SocketError error) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPClient caught an error with code {error}"); + } + + protected override void Dispose(bool disposingManagedResources) + { + DisconnectAndStop(); + base.Dispose(disposingManagedResources); + } + + public override bool Connect() + { + // TODO: NetCoreServer has a Connect() method, but it currently leads to weird issues. + base.ConnectAsync(); + + while (IsConnecting) + { + Thread.Sleep(1); + } + + return IsConnected; + } + + public bool Start() + { + throw new InvalidOperationException("Start was called."); + } + + public bool Stop() + { + throw new InvalidOperationException("Stop was called."); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpServer.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpServer.cs new file mode 100644 index 00000000..d15e2b55 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpServer.cs @@ -0,0 +1,54 @@ +using NetCoreServer; +using Ryujinx.Common.Logging; +using System; +using System.Net; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy +{ + internal class LdnProxyTcpServer : TcpServer, ILdnTcpSocket + { + private readonly LanProtocol _protocol; + + public LdnProxyTcpServer(LanProtocol protocol, IPAddress address, int port) : base(address, port) + { + _protocol = protocol; + OptionReuseAddress = true; + OptionSendBufferSize = LanProtocol.TcpTxBufferSize; + OptionReceiveBufferSize = LanProtocol.TcpRxBufferSize; + + Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPServer created a server for this address: {address}:{port}"); + } + + protected override TcpSession CreateSession() + { + return new LdnProxyTcpSession(this, _protocol); + } + + protected override void OnError(SocketError error) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPServer caught an error with code {error}"); + } + + protected override void Dispose(bool disposingManagedResources) + { + Stop(); + base.Dispose(disposingManagedResources); + } + + public bool Connect() + { + throw new InvalidOperationException("Connect was called."); + } + + public void DisconnectAndStop() + { + Stop(); + } + + public bool SendPacketAsync(EndPoint endpoint, byte[] buffer) + { + throw new InvalidOperationException("SendPacketAsync was called."); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpSession.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpSession.cs new file mode 100644 index 00000000..667230b8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyTcpSession.cs @@ -0,0 +1,83 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using System.Net; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy +{ + internal class LdnProxyTcpSession : NetCoreServer.TcpSession + { + private readonly LanProtocol _protocol; + + internal int NodeId; + internal NodeInfo NodeInfo; + + private byte[] _buffer; + private int _bufferEnd; + + public LdnProxyTcpSession(LdnProxyTcpServer server, LanProtocol protocol) : base(server) + { + _protocol = protocol; + _protocol.Connect += OnConnect; + _buffer = new byte[LanProtocol.BufferSize]; + OptionSendBufferSize = LanProtocol.TcpTxBufferSize; + OptionReceiveBufferSize = LanProtocol.TcpRxBufferSize; + OptionSendBufferLimit = LanProtocol.TxBufferSizeMax; + OptionReceiveBufferLimit = LanProtocol.RxBufferSizeMax; + } + + public void OverrideInfo() + { + NodeInfo.NodeId = (byte)NodeId; + NodeInfo.IsConnected = (byte)(IsConnected ? 1 : 0); + } + + protected override void OnConnected() + { + Logger.Info?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTCPSession connected!"); + } + + protected override void OnDisconnected() + { + Logger.Info?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTCPSession disconnected!"); + + _protocol.InvokeDisconnectStation(this); + } + + protected override void OnReceived(byte[] buffer, long offset, long size) + { + _protocol.Read(ref _buffer, ref _bufferEnd, buffer, (int)offset, (int)size, this.Socket.RemoteEndPoint); + } + + protected override void OnError(SocketError error) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPSession caught an error with code {error}"); + + Dispose(); + } + + protected override void Dispose(bool disposingManagedResources) + { + _protocol.Connect -= OnConnect; + base.Dispose(disposingManagedResources); + } + + private void OnConnect(NodeInfo info, EndPoint endPoint) + { + try + { + if (endPoint.Equals(this.Socket.RemoteEndPoint)) + { + NodeInfo = info; + _protocol.InvokeAccept(this); + } + } + catch (System.ObjectDisposedException) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPSession was disposed. [IP: {NodeInfo.Ipv4Address}]"); + + _protocol.InvokeDisconnectStation(this); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyUdpServer.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyUdpServer.cs new file mode 100644 index 00000000..0f5875a1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Proxy/LdnProxyUdpServer.cs @@ -0,0 +1,157 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy +{ + internal class LdnProxyUdpServer : NetCoreServer.UdpServer, ILdnSocket + { + private const long ScanFrequency = 1000; + + private readonly LanProtocol _protocol; + private byte[] _buffer; + private int _bufferEnd; + + private readonly object _scanLock = new(); + + private Dictionary _scanResultsLast = new(); + private Dictionary _scanResults = new(); + private readonly AutoResetEvent _scanResponse = new(false); + private long _lastScanTime; + + public LdnProxyUdpServer(LanProtocol protocol, IPAddress address, int port) : base(address, port) + { + _protocol = protocol; + _protocol.Scan += HandleScan; + _protocol.ScanResponse += HandleScanResponse; + _buffer = new byte[LanProtocol.BufferSize]; + OptionReuseAddress = true; + OptionReceiveBufferSize = LanProtocol.RxBufferSizeMax; + OptionSendBufferSize = LanProtocol.TxBufferSizeMax; + + Start(); + } + + protected override Socket CreateSocket() + { + return new Socket(Endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp) + { + EnableBroadcast = true, + }; + } + + protected override void OnStarted() + { + ReceiveAsync(); + } + + protected override void OnReceived(EndPoint endpoint, byte[] buffer, long offset, long size) + { + _protocol.Read(ref _buffer, ref _bufferEnd, buffer, (int)offset, (int)size, endpoint); + ReceiveAsync(); + } + + protected override void OnError(SocketError error) + { + Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyUdpServer caught an error with code {error}"); + } + + protected override void Dispose(bool disposingManagedResources) + { + _protocol.Scan -= HandleScan; + _protocol.ScanResponse -= HandleScanResponse; + + _scanResponse.Dispose(); + + base.Dispose(disposingManagedResources); + } + + public bool SendPacketAsync(EndPoint endpoint, byte[] data) + { + return SendAsync(endpoint, data); + } + + private void HandleScan(EndPoint endpoint, LanPacketType type, byte[] data) + { + _protocol.SendPacket(this, type, data, endpoint); + } + + private void HandleScanResponse(NetworkInfo info) + { + Span mac = stackalloc byte[8]; + + info.Common.MacAddress.AsSpan().CopyTo(mac); + + lock (_scanLock) + { + _scanResults[BitConverter.ToUInt64(mac)] = info; + + _scanResponse.Set(); + } + } + + public void ClearScanResults() + { + // Rate limit scans. + + long timeMs = Stopwatch.GetTimestamp() / (Stopwatch.Frequency / 1000); + long delay = ScanFrequency - (timeMs - _lastScanTime); + + if (delay > 0) + { + Thread.Sleep((int)delay); + } + + _lastScanTime = timeMs; + + lock (_scanLock) + { + var newResults = _scanResultsLast; + newResults.Clear(); + + _scanResultsLast = _scanResults; + _scanResults = newResults; + + _scanResponse.Reset(); + } + } + + public Dictionary GetScanResults() + { + // NOTE: Try to minimize waiting time for scan results. + // After we receive the first response, wait a short time for follow-ups and return. + // Responses that were too late to catch will appear in the next scan. + + // ldn_mitm does not do this, but this improves latency for games that expect it to be low (it is on console). + + if (_scanResponse.WaitOne(1000)) + { + // Wait a short while longer in case there are some other responses. + Thread.Sleep(33); + } + + lock (_scanLock) + { + var results = new Dictionary(); + + foreach (KeyValuePair last in _scanResultsLast) + { + results[last.Key] = last.Value; + } + + foreach (KeyValuePair scan in _scanResults) + { + results[scan.Key] = scan.Value; + } + + return results; + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Types/LanPacketHeader.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Types/LanPacketHeader.cs new file mode 100644 index 00000000..1eca1bb8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Types/LanPacketHeader.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 12)] + internal struct LanPacketHeader + { + public uint Magic; + public LanPacketType Type; + public byte Compressed; + public ushort Length; + public ushort DecompressLength; + public Array2 Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Types/LanPacketType.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Types/LanPacketType.cs new file mode 100644 index 00000000..826ea723 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnMitm/Types/LanPacketType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types +{ + internal enum LanPacketType : byte + { + Scan, + ScanResponse, + Connect, + SyncNetwork, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/NetworkChangeEventArgs.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/NetworkChangeEventArgs.cs new file mode 100644 index 00000000..a23e110f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/NetworkChangeEventArgs.cs @@ -0,0 +1,24 @@ +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator +{ + class NetworkChangeEventArgs : EventArgs + { + public NetworkInfo Info; + public bool Connected; + public DisconnectReason DisconnectReason; + + public NetworkChangeEventArgs(NetworkInfo info, bool connected, DisconnectReason disconnectReason = DisconnectReason.None) + { + Info = info; + Connected = connected; + DisconnectReason = disconnectReason; + } + + public DisconnectReason DisconnectReasonOrDefault(DisconnectReason defaultReason) + { + return DisconnectReason == DisconnectReason.None ? defaultReason : DisconnectReason; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Station.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Station.cs new file mode 100644 index 00000000..e39c0197 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Station.cs @@ -0,0 +1,114 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator +{ + class Station : IDisposable + { + public NetworkInfo NetworkInfo; + public Array8 LatestUpdates = new(); + + private readonly IUserLocalCommunicationService _parent; + + public bool Connected { get; private set; } + + public Station(IUserLocalCommunicationService parent) + { + _parent = parent; + + _parent.NetworkClient.NetworkChange += NetworkChanged; + } + + private void NetworkChanged(object sender, NetworkChangeEventArgs e) + { + LatestUpdates.CalculateLatestUpdate(NetworkInfo.Ldn.Nodes, e.Info.Ldn.Nodes); + + NetworkInfo = e.Info; + + if (Connected != e.Connected) + { + Connected = e.Connected; + + if (Connected) + { + _parent.SetState(NetworkState.StationConnected); + } + else + { + _parent.SetDisconnectReason(e.DisconnectReasonOrDefault(DisconnectReason.DestroyedByUser)); + } + } + else + { + _parent.SetState(); + } + } + + public void Dispose() + { + _parent.NetworkClient.DisconnectNetwork(); + + _parent.NetworkClient.NetworkChange -= NetworkChanged; + } + + private ResultCode NetworkErrorToResult(NetworkError error) + { + return error switch + { + NetworkError.None => ResultCode.Success, + NetworkError.VersionTooLow => ResultCode.VersionTooLow, + NetworkError.VersionTooHigh => ResultCode.VersionTooHigh, + NetworkError.TooManyPlayers => ResultCode.TooManyPlayers, + + NetworkError.ConnectFailure => ResultCode.ConnectFailure, + NetworkError.ConnectNotFound => ResultCode.ConnectNotFound, + NetworkError.ConnectTimeout => ResultCode.ConnectTimeout, + NetworkError.ConnectRejected => ResultCode.ConnectRejected, + + _ => ResultCode.DeviceNotAvailable, + }; + } + + public ResultCode Connect( + SecurityConfig securityConfig, + UserConfig userConfig, + uint localCommunicationVersion, + uint optionUnknown, + NetworkInfo networkInfo) + { + ConnectRequest request = new() + { + SecurityConfig = securityConfig, + UserConfig = userConfig, + LocalCommunicationVersion = localCommunicationVersion, + OptionUnknown = optionUnknown, + NetworkInfo = networkInfo, + }; + + return NetworkErrorToResult(_parent.NetworkClient.Connect(request)); + } + + public ResultCode ConnectPrivate( + SecurityConfig securityConfig, + SecurityParameter securityParameter, + UserConfig userConfig, + uint localCommunicationVersion, + uint optionUnknown, + NetworkConfig networkConfig) + { + ConnectPrivateRequest request = new() + { + SecurityConfig = securityConfig, + SecurityParameter = securityParameter, + UserConfig = userConfig, + LocalCommunicationVersion = localCommunicationVersion, + OptionUnknown = optionUnknown, + NetworkConfig = networkConfig, + }; + + return NetworkErrorToResult(_parent.NetworkClient.ConnectPrivate(request)); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/ConnectPrivateRequest.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/ConnectPrivateRequest.cs new file mode 100644 index 00000000..a402f653 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/ConnectPrivateRequest.cs @@ -0,0 +1,16 @@ +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0xBC)] + struct ConnectPrivateRequest + { + public SecurityConfig SecurityConfig; + public SecurityParameter SecurityParameter; + public UserConfig UserConfig; + public uint LocalCommunicationVersion; + public uint OptionUnknown; + public NetworkConfig NetworkConfig; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/ConnectRequest.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/ConnectRequest.cs new file mode 100644 index 00000000..4ea2fceb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/ConnectRequest.cs @@ -0,0 +1,15 @@ +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x4FC)] + struct ConnectRequest + { + public SecurityConfig SecurityConfig; + public UserConfig UserConfig; + public uint LocalCommunicationVersion; + public uint OptionUnknown; + public NetworkInfo NetworkInfo; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/CreateAccessPointPrivateRequest.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/CreateAccessPointPrivateRequest.cs new file mode 100644 index 00000000..ac0ff7d9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/CreateAccessPointPrivateRequest.cs @@ -0,0 +1,18 @@ +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types +{ + /// + /// Advertise data is appended separately (remaining data in the buffer). + /// + [StructLayout(LayoutKind.Sequential, Size = 0x13C, Pack = 1)] + struct CreateAccessPointPrivateRequest + { + public SecurityConfig SecurityConfig; + public SecurityParameter SecurityParameter; + public UserConfig UserConfig; + public NetworkConfig NetworkConfig; + public AddressList AddressList; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/CreateAccessPointRequest.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/CreateAccessPointRequest.cs new file mode 100644 index 00000000..f67f0aac --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/CreateAccessPointRequest.cs @@ -0,0 +1,16 @@ +using Ryujinx.HLE.HOS.Services.Ldn.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types +{ + /// + /// Advertise data is appended separately (remaining data in the buffer). + /// + [StructLayout(LayoutKind.Sequential, Size = 0x94, CharSet = CharSet.Ansi)] + struct CreateAccessPointRequest + { + public SecurityConfig SecurityConfig; + public UserConfig UserConfig; + public NetworkConfig NetworkConfig; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/NetworkError.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/NetworkError.cs new file mode 100644 index 00000000..cd576e05 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/NetworkError.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types +{ + enum NetworkError : int + { + None, + + PortUnreachable, + + TooManyPlayers, + VersionTooLow, + VersionTooHigh, + + ConnectFailure, + ConnectNotFound, + ConnectTimeout, + ConnectRejected, + + RejectFailed, + + Unknown = -1, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/NetworkErrorMessage.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/NetworkErrorMessage.cs new file mode 100644 index 00000000..7e0c2a43 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/Types/NetworkErrorMessage.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x4)] + struct NetworkErrorMessage + { + public NetworkError Error; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Loader/IDebugMonitorInterface.cs b/src/Ryujinx.HLE/HOS/Services/Loader/IDebugMonitorInterface.cs new file mode 100644 index 00000000..5e4e5975 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Loader/IDebugMonitorInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Loader +{ + [Service("ldr:dmnt")] + class IDebugMonitorInterface : IpcService + { + public IDebugMonitorInterface(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Loader/IProcessManagerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Loader/IProcessManagerInterface.cs new file mode 100644 index 00000000..cb5a26d1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Loader/IProcessManagerInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Loader +{ + [Service("ldr:pm")] + class IProcessManagerInterface : IpcService + { + public IProcessManagerInterface(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Loader/IShellInterface.cs b/src/Ryujinx.HLE/HOS/Services/Loader/IShellInterface.cs new file mode 100644 index 00000000..f9cf44d9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Loader/IShellInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Loader +{ + [Service("ldr:shel")] + class IShellInterface : IpcService + { + public IShellInterface(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Loader/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Loader/ResultCode.cs new file mode 100644 index 00000000..6744563d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Loader/ResultCode.cs @@ -0,0 +1,46 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.HOS.Services.Loader +{ + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + enum ResultCode + { + ModuleId = 9, + ErrorCodeShift = 9, + + Success = 0, + + ArgsTooLong = (1 << ErrorCodeShift) | ModuleId, + MaximumProcessesLoaded = (2 << ErrorCodeShift) | ModuleId, + NPDMTooBig = (3 << ErrorCodeShift) | ModuleId, + InvalidNPDM = (4 << ErrorCodeShift) | ModuleId, + InvalidNSO = (5 << ErrorCodeShift) | ModuleId, + InvalidPath = (6 << ErrorCodeShift) | ModuleId, + AlreadyRegistered = (7 << ErrorCodeShift) | ModuleId, + TitleNotFound = (8 << ErrorCodeShift) | ModuleId, + ACI0TitleIdNotMatchingRangeInACID = (9 << ErrorCodeShift) | ModuleId, + InvalidVersionInNPDM = (10 << ErrorCodeShift) | ModuleId, + InsufficientAddressSpace = (51 << ErrorCodeShift) | ModuleId, + InsufficientNRO = (52 << ErrorCodeShift) | ModuleId, + InvalidNRR = (53 << ErrorCodeShift) | ModuleId, + InvalidSignature = (54 << ErrorCodeShift) | ModuleId, + InsufficientNRORegistrations = (55 << ErrorCodeShift) | ModuleId, + InsufficientNRRRegistrations = (56 << ErrorCodeShift) | ModuleId, + NROAlreadyLoaded = (57 << ErrorCodeShift) | ModuleId, + UnalignedNRRAddress = (81 << ErrorCodeShift) | ModuleId, + BadNRRSize = (82 << ErrorCodeShift) | ModuleId, + NRRNotLoaded = (84 << ErrorCodeShift) | ModuleId, + BadNRRAddress = (85 << ErrorCodeShift) | ModuleId, + BadInitialization = (87 << ErrorCodeShift) | ModuleId, + UnknownACI0Descriptor = (100 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingKernelFlagsDescriptor = (103 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingSyscallMaskDescriptor = (104 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingMapIoOrNormalRangeDescriptor = (106 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingMapNormalPageDescriptor = (107 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingInterruptPairDescriptor = (111 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingApplicationTypeDescriptor = (113 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingKernelReleaseVersionDescriptor = (114 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingHandleTableSizeDescriptor = (115 << ErrorCodeShift) | ModuleId, + ACI0NotMatchingDebugFlagsDescriptor = (116 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mig/IService.cs b/src/Ryujinx.HLE/HOS/Services/Mig/IService.cs new file mode 100644 index 00000000..a4d43284 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mig/IService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Mig +{ + [Service("mig:usr")] // 4.0.0+ + class IService : IpcService + { + public IService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs b/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs new file mode 100644 index 00000000..4bb73699 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs @@ -0,0 +1,325 @@ +using LibHac; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.Mii +{ + class DatabaseImpl + { + private static DatabaseImpl _instance; + + public static DatabaseImpl Instance + { + get + { + _instance ??= new DatabaseImpl(); + + return _instance; + } + } + + private UtilityImpl _utilityImpl; + private readonly MiiDatabaseManager _miiDatabase; + private bool _isBroken; + + public DatabaseImpl() + { + _miiDatabase = new MiiDatabaseManager(); + } + + public bool IsUpdated(DatabaseSessionMetadata metadata, SourceFlag flag) + { + if (flag.HasFlag(SourceFlag.Database)) + { + return _miiDatabase.IsUpdated(metadata); + } + + return false; + } + + public bool IsBrokenDatabaseWithClearFlag() + { + bool result = _isBroken; + + if (_isBroken) + { + _isBroken = false; + + Format(new DatabaseSessionMetadata(0, new SpecialMiiKeyCode())); + } + + return result; + } + + public bool IsFullDatabase() + { + return _miiDatabase.IsFullDatabase(); + } + + private ResultCode GetDefault(SourceFlag flag, ref int count, Span elements) where T : struct, IElement + { + if (!flag.HasFlag(SourceFlag.Default)) + { + return ResultCode.Success; + } + + for (uint i = 0; i < DefaultMii.TableLength; i++) + { + if (count >= elements.Length) + { + return ResultCode.BufferTooSmall; + } + + elements[count] = default; + elements[count].SetFromStoreData(StoreData.BuildDefault(_utilityImpl, i)); + elements[count].SetSource(Source.Default); + + count++; + } + + return ResultCode.Success; + } + + public ResultCode UpdateLatest(DatabaseSessionMetadata metadata, IStoredData oldMiiData, SourceFlag flag, IStoredData newMiiData) where T : unmanaged + { + if (!flag.HasFlag(SourceFlag.Database)) + { + return ResultCode.NotFound; + } + + if (metadata.IsInterfaceVersionSupported(1) && !oldMiiData.IsValid()) + { + return oldMiiData.InvalidData; + } + + ResultCode result = _miiDatabase.FindIndex(metadata, out int index, oldMiiData.CreateId); + + if (result == ResultCode.Success) + { + _miiDatabase.Get(metadata, index, out StoreData storeData); + + if (storeData.Type != oldMiiData.Type) + { + return ResultCode.NotFound; + } + + newMiiData.SetFromStoreData(storeData); + + if (oldMiiData == newMiiData) + { + return ResultCode.NotUpdated; + } + } + + return result; + } + + public ResultCode Get(DatabaseSessionMetadata metadata, SourceFlag flag, out int count, Span elements) where T : struct, IElement + { + count = 0; + + if (!flag.HasFlag(SourceFlag.Database)) + { + return GetDefault(flag, ref count, elements); + } + + int databaseCount = _miiDatabase.GetCount(metadata); + + for (int i = 0; i < databaseCount; i++) + { + if (count >= elements.Length) + { + return ResultCode.BufferTooSmall; + } + + _miiDatabase.Get(metadata, i, out StoreData storeData); + + elements[count] = default; + elements[count].SetFromStoreData(storeData); + elements[count].SetSource(Source.Database); + + count++; + } + + return GetDefault(flag, ref count, elements); + } + + public ResultCode InitializeDatabase(ITickSource tickSource, HorizonClient horizonClient) + { + _utilityImpl = new UtilityImpl(tickSource); + _miiDatabase.InitializeDatabase(horizonClient); + _miiDatabase.LoadFromFile(out _isBroken); + + // Nintendo ignores any error code from before. + return ResultCode.Success; + } + + public DatabaseSessionMetadata CreateSessionMetadata(SpecialMiiKeyCode miiKeyCode) + { + return _miiDatabase.CreateSessionMetadata(miiKeyCode); + } + + public void SetInterfaceVersion(DatabaseSessionMetadata metadata, uint interfaceVersion) + { + _miiDatabase.SetInterfaceVersion(metadata, interfaceVersion); + } + + public void Format(DatabaseSessionMetadata metadata) + { + _miiDatabase.FormatDatabase(metadata); + _miiDatabase.SaveDatabase(); + } + + public ResultCode DestroyFile(DatabaseSessionMetadata metadata) + { + _isBroken = true; + + return _miiDatabase.DestroyFile(metadata); + } + + public void BuildDefault(uint index, out CharInfo charInfo) + { + StoreData storeData = StoreData.BuildDefault(_utilityImpl, index); + + charInfo = default; + + charInfo.SetFromStoreData(storeData); + } + + public void BuildRandom(Age age, Gender gender, Race race, out CharInfo charInfo) + { + StoreData storeData = StoreData.BuildRandom(_utilityImpl, age, gender, race); + + charInfo = default; + + charInfo.SetFromStoreData(storeData); + } + + public ResultCode DeleteFile() + { + return _miiDatabase.DeleteFile(); + } + + public ResultCode ConvertCoreDataToCharInfo(CoreData coreData, out CharInfo charInfo) + { + charInfo = new CharInfo(); + + if (!coreData.IsValid()) + { + return ResultCode.InvalidCoreData; + } + + StoreData storeData = StoreData.BuildFromCoreData(_utilityImpl, coreData); + + if (!storeData.CoreData.Nickname.IsValidForFontRegion(storeData.CoreData.FontRegion)) + { + storeData.CoreData.Nickname = Nickname.Question; + storeData.UpdateCrc(); + } + + charInfo.SetFromStoreData(storeData); + + return ResultCode.Success; + } + + public int FindIndex(CreateId createId, bool isSpecial) + { + if (_miiDatabase.FindIndex(out int index, createId, isSpecial) == ResultCode.Success) + { + return index; + } + + return -1; + } + + public uint GetCount(DatabaseSessionMetadata metadata, SourceFlag flag) + { + int count = 0; + + if (flag.HasFlag(SourceFlag.Default)) + { + count += DefaultMii.TableLength; + } + + if (flag.HasFlag(SourceFlag.Database)) + { + count += _miiDatabase.GetCount(metadata); + } + + return (uint)count; + } + + public ResultCode Move(DatabaseSessionMetadata metadata, int index, CreateId createId) + { + ResultCode result = _miiDatabase.Move(metadata, index, createId); + + if (result == ResultCode.Success) + { + result = _miiDatabase.SaveDatabase(); + } + + return result; + } + + public ResultCode Delete(DatabaseSessionMetadata metadata, CreateId createId) + { + ResultCode result = _miiDatabase.Delete(metadata, createId); + + if (result == ResultCode.Success) + { + result = _miiDatabase.SaveDatabase(); + } + + return result; + } + + public ResultCode AddOrReplace(DatabaseSessionMetadata metadata, StoreData storeData) + { + ResultCode result = _miiDatabase.AddOrReplace(metadata, storeData); + + if (result == ResultCode.Success) + { + result = _miiDatabase.SaveDatabase(); + } + + return result; + } + + public ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData) + { + coreData = new CoreData(); + + if (!charInfo.IsValid()) + { + return ResultCode.InvalidCharInfo; + } + + coreData.SetFromCharInfo(charInfo); + + if (!coreData.Nickname.IsValidForFontRegion(coreData.FontRegion)) + { + coreData.Nickname = Nickname.Question; + } + + return ResultCode.Success; + } + + public ResultCode GetIndex(DatabaseSessionMetadata metadata, CharInfo charInfo, out int index) + { + if (!charInfo.IsValid()) + { + index = -1; + + return ResultCode.InvalidCharInfo; + } + + if (_miiDatabase.FindIndex(out index, charInfo.CreateId, metadata.MiiKeyCode.IsEnabledSpecialMii()) != ResultCode.Success) + { + return ResultCode.NotFound; + } + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseSessionMetadata.cs b/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseSessionMetadata.cs new file mode 100644 index 00000000..b01295d8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/DatabaseSessionMetadata.cs @@ -0,0 +1,24 @@ +using Ryujinx.HLE.HOS.Services.Mii.Types; + +namespace Ryujinx.HLE.HOS.Services.Mii +{ + class DatabaseSessionMetadata + { + public uint InterfaceVersion; + public ulong UpdateCounter; + + public SpecialMiiKeyCode MiiKeyCode { get; private set; } + + public DatabaseSessionMetadata(ulong updateCounter, SpecialMiiKeyCode miiKeyCode) + { + InterfaceVersion = 0; + UpdateCounter = updateCounter; + MiiKeyCode = miiKeyCode; + } + + public bool IsInterfaceVersionSupported(uint interfaceVersion) + { + return InterfaceVersion >= interfaceVersion; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Helper.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Helper.cs new file mode 100644 index 00000000..f4d288e9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Helper.cs @@ -0,0 +1,50 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Buffers.Binary; + +namespace Ryujinx.HLE.HOS.Services.Mii +{ + static class Helper + { + public static ushort CalculateCrc16(ReadOnlySpan data, int crc, bool reverseEndianess) + { + const ushort Poly = 0x1021; + + for (int i = 0; i < data.Length; i++) + { + crc ^= data[i] << 8; + + for (int j = 0; j < 8; j++) + { + crc <<= 1; + + if ((crc & 0x10000) != 0) + { + crc = (crc ^ Poly) & 0xFFFF; + } + } + } + + if (reverseEndianess) + { + return (ushort)(BinaryPrimitives.ReverseEndianness(crc) >> 16); + } + + return (ushort)crc; + } + + public static UInt128 GetDeviceId() + { + // FIXME: call set:sys GetMiiAuthorId + return UInt128Utils.FromHex("5279754d69694e780000000000000000"); // RyuMiiNx + } + +#pragma warning disable IDE0055 // Disable formatting + public static ReadOnlySpan Ver3FacelineColorTable => new byte[] { 0, 1, 2, 3, 4, 5 }; + public static ReadOnlySpan Ver3HairColorTable => new byte[] { 8, 1, 2, 3, 4, 5, 6, 7 }; + public static ReadOnlySpan Ver3EyeColorTable => new byte[] { 8, 9, 10, 11, 12, 13 }; + public static ReadOnlySpan Ver3MouthColorTable => new byte[] { 19, 20, 21, 22, 23 }; + public static ReadOnlySpan Ver3GlassColorTable => new byte[] { 8, 14, 15, 16, 17, 18, 0 }; +#pragma warning restore IDE0055 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/IImageDatabaseService.cs b/src/Ryujinx.HLE/HOS/Services/Mii/IImageDatabaseService.cs new file mode 100644 index 00000000..88d7d7b3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/IImageDatabaseService.cs @@ -0,0 +1,41 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Mii +{ + [Service("miiimg")] // 5.0.0+ + class IImageDatabaseService : IpcService + { + private uint _imageCount; + private bool _isDirty; + + public IImageDatabaseService(ServiceCtx context) { } + + [CommandCmif(0)] + // Initialize(b8) -> b8 + public ResultCode Initialize(ServiceCtx context) + { + // TODO: Service uses MiiImage:/database.dat if true, seems to use hardcoded data if false. + bool useHardcodedData = context.RequestData.ReadBoolean(); + + _imageCount = 0; + _isDirty = false; + + context.ResponseData.Write(_isDirty); + + Logger.Stub?.PrintStub(LogClass.ServiceMii, new { useHardcodedData }); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // GetCount() -> u32 + public ResultCode GetCount(ServiceCtx context) + { + context.ResponseData.Write(_imageCount); + + Logger.Stub?.PrintStub(LogClass.ServiceMii); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/IStaticService.cs b/src/Ryujinx.HLE/HOS/Services/Mii/IStaticService.cs new file mode 100644 index 00000000..8a680002 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/IStaticService.cs @@ -0,0 +1,32 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Mii.StaticService; +using Ryujinx.HLE.HOS.Services.Mii.Types; + +namespace Ryujinx.HLE.HOS.Services.Mii +{ + [Service("mii:e", true)] + [Service("mii:u", false)] + class IStaticService : IpcService + { + private readonly DatabaseImpl _databaseImpl; + + private readonly bool _isSystem; + + public IStaticService(ServiceCtx context, bool isSystem) + { + _isSystem = isSystem; + _databaseImpl = DatabaseImpl.Instance; + } + + [CommandCmif(0)] + // GetDatabaseService(u32 mii_key_code) -> object + public ResultCode GetDatabaseService(ServiceCtx context) + { + SpecialMiiKeyCode miiKeyCode = context.RequestData.ReadStruct(); + + MakeObject(context, new DatabaseServiceImpl(_databaseImpl, _isSystem, miiKeyCode)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs b/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs new file mode 100644 index 00000000..23a52d90 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs @@ -0,0 +1,514 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Fs.Shim; +using LibHac.Ncm; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.Mii +{ + class MiiDatabaseManager + { + private readonly bool _isTestModeEnabled = false; + private uint _mountCounter = 0; + + private const ulong DatabaseTestSaveDataId = 0x8000000000000031; + private const ulong DatabaseSaveDataId = 0x8000000000000030; + + private readonly U8String _databasePath = new("mii:/MiiDatabase.dat"); + private readonly U8String _mountName = new("mii"); + + private NintendoFigurineDatabase _database; + private bool _isDirty; + + private HorizonClient _horizonClient; + + protected ulong UpdateCounter { get; private set; } + + public MiiDatabaseManager() + { + _database = new NintendoFigurineDatabase(); + _isDirty = false; + UpdateCounter = 0; + } + + private void ResetDatabase() + { + _database = new NintendoFigurineDatabase(); + _database.Format(); + } + + private void MarkDirty(DatabaseSessionMetadata metadata) + { + _isDirty = true; + + UpdateCounter++; + + metadata.UpdateCounter = UpdateCounter; + } + + private bool GetAtVirtualIndex(int index, out int realIndex, out StoreData storeData) + { + realIndex = -1; + storeData = new StoreData(); + + int virtualIndex = 0; + + for (int i = 0; i < _database.Length; i++) + { + StoreData tmp = _database.Get(i); + + if (!tmp.IsSpecial()) + { + if (index == virtualIndex) + { + realIndex = i; + storeData = tmp; + + return true; + } + + virtualIndex++; + } + } + + return false; + } + + private int ConvertRealIndexToVirtualIndex(int realIndex) + { + int virtualIndex = 0; + + for (int i = 0; i < realIndex; i++) + { + StoreData tmp = _database.Get(i); + + if (!tmp.IsSpecial()) + { + virtualIndex++; + } + } + + return virtualIndex; + } + + public void InitializeDatabase(HorizonClient horizonClient) + { + _horizonClient = horizonClient; + + // Ensure we have valid data in the database + _database.Format(); + + MountSave(); + } + + private Result MountSave() + { + if (_mountCounter != 0) + { + _mountCounter++; + return Result.Success; + } + + ulong saveDataId = _isTestModeEnabled ? DatabaseTestSaveDataId : DatabaseSaveDataId; + + Result result = _horizonClient.Fs.MountSystemSaveData(_mountName, SaveDataSpaceId.System, saveDataId); + + if (result.IsFailure()) + { + if (!ResultFs.TargetNotFound.Includes(result)) + { + return result; + } + + if (_isTestModeEnabled) +#pragma warning disable CS0162 + { + result = _horizonClient.Fs.CreateSystemSaveData(saveDataId, 0x10000, 0x10000, + SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData); + if (result.IsFailure()) + { + return result; + } + } +#pragma warning restore CS0162 + else + { + result = _horizonClient.Fs.CreateSystemSaveData(saveDataId, SystemProgramId.Ns.Value, 0x10000, + 0x10000, SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData); + if (result.IsFailure()) + { + return result; + } + } + + result = _horizonClient.Fs.MountSystemSaveData(_mountName, SaveDataSpaceId.System, saveDataId); + if (result.IsFailure()) + { + return result; + } + } + + if (result == Result.Success) + { + _mountCounter++; + } + return result; + } + + public ResultCode DeleteFile() + { + ResultCode result = (ResultCode)_horizonClient.Fs.DeleteFile(_databasePath).Value; + + _horizonClient.Fs.Commit(_mountName); + + return result; + } + + public ResultCode LoadFromFile(out bool isBroken) + { + isBroken = false; + + if (_mountCounter == 0) + { + return ResultCode.InvalidArgument; + } + + UpdateCounter++; + + ResetDatabase(); + + Result result = _horizonClient.Fs.OpenFile(out FileHandle handle, _databasePath, OpenMode.Read); + + if (result.IsSuccess()) + { + result = _horizonClient.Fs.GetFileSize(out long fileSize, handle); + + if (result.IsSuccess()) + { + if (fileSize == Unsafe.SizeOf()) + { + result = _horizonClient.Fs.ReadFile(handle, 0, _database.AsSpan()); + + if (result.IsSuccess()) + { + if (_database.Verify() != ResultCode.Success) + { + ResetDatabase(); + + isBroken = true; + } + else + { + isBroken = _database.FixDatabase(); + } + } + } + else + { + isBroken = true; + } + } + + _horizonClient.Fs.CloseFile(handle); + + return (ResultCode)result.Value; + } + else if (ResultFs.PathNotFound.Includes(result)) + { + return (ResultCode)ForceSaveDatabase().Value; + } + + return ResultCode.Success; + } + + private Result ForceSaveDatabase() + { + Result result = _horizonClient.Fs.CreateFile(_databasePath, Unsafe.SizeOf()); + + if (result.IsSuccess() || ResultFs.PathAlreadyExists.Includes(result)) + { + result = _horizonClient.Fs.OpenFile(out FileHandle handle, _databasePath, OpenMode.Write); + + if (result.IsSuccess()) + { + result = _horizonClient.Fs.GetFileSize(out long fileSize, handle); + + if (result.IsSuccess()) + { + // If the size doesn't match, recreate the file + if (fileSize != Unsafe.SizeOf()) + { + _horizonClient.Fs.CloseFile(handle); + + result = _horizonClient.Fs.DeleteFile(_databasePath); + + if (result.IsSuccess()) + { + result = _horizonClient.Fs.CreateFile(_databasePath, Unsafe.SizeOf()); + + if (result.IsSuccess()) + { + result = _horizonClient.Fs.OpenFile(out handle, _databasePath, OpenMode.Write); + } + } + + if (result.IsFailure()) + { + return result; + } + } + + result = _horizonClient.Fs.WriteFile(handle, 0, _database.AsReadOnlySpan(), WriteOption.Flush); + } + + _horizonClient.Fs.CloseFile(handle); + } + } + + if (result.IsSuccess()) + { + _isDirty = false; + + result = _horizonClient.Fs.Commit(_mountName); + } + + return result; + } + + public DatabaseSessionMetadata CreateSessionMetadata(SpecialMiiKeyCode miiKeyCode) + { + return new DatabaseSessionMetadata(UpdateCounter, miiKeyCode); + } + + public void SetInterfaceVersion(DatabaseSessionMetadata metadata, uint interfaceVersion) + { + metadata.InterfaceVersion = interfaceVersion; + } + + public bool IsUpdated(DatabaseSessionMetadata metadata) + { + bool result = metadata.UpdateCounter != UpdateCounter; + + metadata.UpdateCounter = UpdateCounter; + + return result; + } + + public int GetCount(DatabaseSessionMetadata metadata) + { + if (!metadata.MiiKeyCode.IsEnabledSpecialMii()) + { + int count = 0; + + for (int i = 0; i < _database.Length; i++) + { + StoreData tmp = _database.Get(i); + + if (!tmp.IsSpecial()) + { + count++; + } + } + + return count; + } + else + { + return _database.Length; + } + } + + public void Get(DatabaseSessionMetadata metadata, int index, out StoreData storeData) + { + if (!metadata.MiiKeyCode.IsEnabledSpecialMii()) + { + if (GetAtVirtualIndex(index, out int realIndex, out _)) + { + index = realIndex; + } + else + { + index = 0; + } + } + + storeData = _database.Get(index); + } + + public ResultCode FindIndex(DatabaseSessionMetadata metadata, out int index, CreateId createId) + { + return FindIndex(out index, createId, metadata.MiiKeyCode.IsEnabledSpecialMii()); + } + + public ResultCode FindIndex(out int index, CreateId createId, bool isSpecial) + { + if (_database.GetIndexByCreatorId(out int realIndex, createId)) + { + if (isSpecial) + { + index = realIndex; + + return ResultCode.Success; + } + + StoreData storeData = _database.Get(realIndex); + + if (!storeData.IsSpecial()) + { + if (realIndex < 1) + { + index = 0; + } + else + { + index = ConvertRealIndexToVirtualIndex(realIndex); + } + + return ResultCode.Success; + } + } + + index = -1; + + return ResultCode.NotFound; + } + + public ResultCode Move(DatabaseSessionMetadata metadata, int newIndex, CreateId createId) + { + if (!metadata.MiiKeyCode.IsEnabledSpecialMii()) + { + if (GetAtVirtualIndex(newIndex, out int realIndex, out _)) + { + newIndex = realIndex; + } + else + { + newIndex = 0; + } + } + + if (_database.GetIndexByCreatorId(out int oldIndex, createId)) + { + StoreData realStoreData = _database.Get(oldIndex); + + if (!metadata.MiiKeyCode.IsEnabledSpecialMii() && realStoreData.IsSpecial()) + { + return ResultCode.InvalidOperationOnSpecialMii; + } + + ResultCode result = _database.Move(newIndex, oldIndex); + + if (result == ResultCode.Success) + { + MarkDirty(metadata); + } + + return result; + } + + return ResultCode.NotFound; + } + + public ResultCode AddOrReplace(DatabaseSessionMetadata metadata, StoreData storeData) + { + if (!storeData.IsValid()) + { + return ResultCode.InvalidStoreData; + } + + if (!metadata.MiiKeyCode.IsEnabledSpecialMii() && storeData.IsSpecial()) + { + return ResultCode.InvalidOperationOnSpecialMii; + } + + if (_database.GetIndexByCreatorId(out int index, storeData.CreateId)) + { + StoreData oldStoreData = _database.Get(index); + + if (oldStoreData.IsSpecial()) + { + return ResultCode.InvalidOperationOnSpecialMii; + } + + _database.Replace(index, storeData); + } + else + { + if (_database.IsFull()) + { + return ResultCode.DatabaseFull; + } + + _database.Add(storeData); + } + + MarkDirty(metadata); + + return ResultCode.Success; + } + + public ResultCode Delete(DatabaseSessionMetadata metadata, CreateId createId) + { + if (!_database.GetIndexByCreatorId(out int index, createId)) + { + return ResultCode.NotFound; + } + + if (!metadata.MiiKeyCode.IsEnabledSpecialMii()) + { + StoreData storeData = _database.Get(index); + + if (storeData.IsSpecial()) + { + return ResultCode.InvalidOperationOnSpecialMii; + } + } + + _database.Delete(index); + + MarkDirty(metadata); + + return ResultCode.Success; + } + + public ResultCode DestroyFile(DatabaseSessionMetadata metadata) + { + _database.CorruptDatabase(); + + MarkDirty(metadata); + + ResultCode result = SaveDatabase(); + + ResetDatabase(); + + return result; + } + + public ResultCode SaveDatabase() + { + if (_isDirty) + { + return (ResultCode)ForceSaveDatabase().Value; + } + else + { + return ResultCode.NotUpdated; + } + } + + public void FormatDatabase(DatabaseSessionMetadata metadata) + { + _database.Format(); + + MarkDirty(metadata); + } + + public bool IsFullDatabase() + { + return _database.IsFull(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Mii/ResultCode.cs new file mode 100644 index 00000000..cccc519e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/ResultCode.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.HLE.HOS.Services.Mii +{ + public enum ResultCode + { + ModuleId = 126, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArgument = (1 << ErrorCodeShift) | ModuleId, + BufferTooSmall = (2 << ErrorCodeShift) | ModuleId, + NotUpdated = (3 << ErrorCodeShift) | ModuleId, + NotFound = (4 << ErrorCodeShift) | ModuleId, + DatabaseFull = (5 << ErrorCodeShift) | ModuleId, + InvalidDatabaseSignatureValue = (67 << ErrorCodeShift) | ModuleId, + InvalidDatabaseEntryCount = (69 << ErrorCodeShift) | ModuleId, + InvalidCharInfo = (100 << ErrorCodeShift) | ModuleId, + InvalidCrc = (101 << ErrorCodeShift) | ModuleId, + InvalidDeviceCrc = (102 << ErrorCodeShift) | ModuleId, + InvalidDatabaseMagic = (103 << ErrorCodeShift) | ModuleId, + InvalidDatabaseVersion = (104 << ErrorCodeShift) | ModuleId, + InvalidDatabaseSize = (105 << ErrorCodeShift) | ModuleId, + InvalidCreateId = (106 << ErrorCodeShift) | ModuleId, + InvalidCoreData = (108 << ErrorCodeShift) | ModuleId, + InvalidStoreData = (109 << ErrorCodeShift) | ModuleId, + InvalidOperationOnSpecialMii = (202 << ErrorCodeShift) | ModuleId, + PermissionDenied = (203 << ErrorCodeShift) | ModuleId, + TestModeNotEnabled = (204 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs new file mode 100644 index 00000000..fc12e253 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs @@ -0,0 +1,266 @@ +using Ryujinx.HLE.HOS.Services.Mii.Types; +using Ryujinx.HLE.HOS.Services.Settings; +using System; + +namespace Ryujinx.HLE.HOS.Services.Mii.StaticService +{ + class DatabaseServiceImpl : IDatabaseService + { + private readonly DatabaseImpl _database; + private readonly DatabaseSessionMetadata _metadata; + private readonly bool _isSystem; + + public DatabaseServiceImpl(DatabaseImpl database, bool isSystem, SpecialMiiKeyCode miiKeyCode) + { + _database = database; + _metadata = _database.CreateSessionMetadata(miiKeyCode); + _isSystem = isSystem; + } + + public bool IsDatabaseTestModeEnabled() + { + if (NxSettings.Settings.TryGetValue("mii!is_db_test_mode_enabled", out object isDatabaseTestModeEnabled)) + { + return (bool)isDatabaseTestModeEnabled; + } + + return false; + } + + protected override bool IsUpdated(SourceFlag flag) + { + return _database.IsUpdated(_metadata, flag); + } + + protected override bool IsFullDatabase() + { + return _database.IsFullDatabase(); + } + + protected override uint GetCount(SourceFlag flag) + { + return _database.GetCount(_metadata, flag); + } + + protected override ResultCode Get(SourceFlag flag, out int count, Span elements) + { + return _database.Get(_metadata, flag, out count, elements); + } + + protected override ResultCode Get1(SourceFlag flag, out int count, Span elements) + { + return _database.Get(_metadata, flag, out count, elements); + } + + protected override ResultCode UpdateLatest(CharInfo oldCharInfo, SourceFlag flag, out CharInfo newCharInfo) + { + newCharInfo = default; + + return _database.UpdateLatest(_metadata, oldCharInfo, flag, newCharInfo); + } + + protected override ResultCode BuildRandom(Age age, Gender gender, Race race, out CharInfo charInfo) + { + if (age > Age.All || gender > Gender.All || race > Race.All) + { + charInfo = default; + + return ResultCode.InvalidArgument; + } + + _database.BuildRandom(age, gender, race, out charInfo); + + return ResultCode.Success; + } + + protected override ResultCode BuildDefault(uint index, out CharInfo charInfo) + { + if (index >= DefaultMii.TableLength) + { + charInfo = default; + + return ResultCode.InvalidArgument; + } + + _database.BuildDefault(index, out charInfo); + + return ResultCode.Success; + } + + protected override ResultCode Get2(SourceFlag flag, out int count, Span elements) + { + if (!_isSystem) + { + count = -1; + + return ResultCode.PermissionDenied; + } + + return _database.Get(_metadata, flag, out count, elements); + } + + protected override ResultCode Get3(SourceFlag flag, out int count, Span elements) + { + if (!_isSystem) + { + count = -1; + + return ResultCode.PermissionDenied; + } + + return _database.Get(_metadata, flag, out count, elements); + } + + protected override ResultCode UpdateLatest1(StoreData oldStoreData, SourceFlag flag, out StoreData newStoreData) + { + newStoreData = default; + + if (!_isSystem) + { + return ResultCode.PermissionDenied; + } + + return _database.UpdateLatest(_metadata, oldStoreData, flag, newStoreData); + } + + protected override ResultCode FindIndex(CreateId createId, bool isSpecial, out int index) + { + if (!_isSystem) + { + index = -1; + + return ResultCode.PermissionDenied; + } + + index = _database.FindIndex(createId, isSpecial); + + return ResultCode.Success; + } + + protected override ResultCode Move(CreateId createId, int newIndex) + { + if (!_isSystem) + { + return ResultCode.PermissionDenied; + } + + if (newIndex > 0 && _database.GetCount(_metadata, SourceFlag.Database) > newIndex) + { + return _database.Move(_metadata, newIndex, createId); + } + + return ResultCode.InvalidArgument; + } + + protected override ResultCode AddOrReplace(StoreData storeData) + { + if (!_isSystem) + { + return ResultCode.PermissionDenied; + } + + return _database.AddOrReplace(_metadata, storeData); + } + + protected override ResultCode Delete(CreateId createId) + { + if (!_isSystem) + { + return ResultCode.PermissionDenied; + } + + return _database.Delete(_metadata, createId); + } + + protected override ResultCode DestroyFile() + { + if (!IsDatabaseTestModeEnabled()) + { + return ResultCode.TestModeNotEnabled; + } + + return _database.DestroyFile(_metadata); + } + + protected override ResultCode DeleteFile() + { + if (!IsDatabaseTestModeEnabled()) + { + return ResultCode.TestModeNotEnabled; + } + + return _database.DeleteFile(); + } + + protected override ResultCode Format() + { + if (!IsDatabaseTestModeEnabled()) + { + return ResultCode.TestModeNotEnabled; + } + + _database.Format(_metadata); + + return ResultCode.Success; + } + + protected override ResultCode Import(ReadOnlySpan data) + { + if (!IsDatabaseTestModeEnabled()) + { + return ResultCode.TestModeNotEnabled; + } + + throw new NotImplementedException(); + } + + protected override ResultCode Export(Span data) + { + if (!IsDatabaseTestModeEnabled()) + { + return ResultCode.TestModeNotEnabled; + } + + throw new NotImplementedException(); + } + + protected override ResultCode IsBrokenDatabaseWithClearFlag(out bool isBrokenDatabase) + { + if (!_isSystem) + { + isBrokenDatabase = false; + + return ResultCode.PermissionDenied; + } + + isBrokenDatabase = _database.IsBrokenDatabaseWithClearFlag(); + + return ResultCode.Success; + } + + protected override ResultCode GetIndex(CharInfo charInfo, out int index) + { + return _database.GetIndex(_metadata, charInfo, out index); + } + + protected override void SetInterfaceVersion(uint interfaceVersion) + { + _database.SetInterfaceVersion(_metadata, interfaceVersion); + } + + protected override ResultCode Convert(Ver3StoreData ver3StoreData, out CharInfo charInfo) + { + throw new NotImplementedException(); + } + + protected override ResultCode ConvertCoreDataToCharInfo(CoreData coreData, out CharInfo charInfo) + { + return _database.ConvertCoreDataToCharInfo(coreData, out charInfo); + } + + protected override ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData) + { + return _database.ConvertCharInfoToCoreData(charInfo, out coreData); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs new file mode 100644 index 00000000..1a1c20d6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs @@ -0,0 +1,425 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.StaticService +{ + abstract class IDatabaseService : IpcService + { + [CommandCmif(0)] + // IsUpdated(SourceFlag flag) -> bool + public ResultCode IsUpdated(ServiceCtx context) + { + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + context.ResponseData.Write(IsUpdated(flag)); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // IsFullDatabase() -> bool + public ResultCode IsFullDatabase(ServiceCtx context) + { + context.ResponseData.Write(IsFullDatabase()); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetCount(SourceFlag flag) -> u32 + public ResultCode GetCount(ServiceCtx context) + { + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + context.ResponseData.Write(GetCount(flag)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // Get(SourceFlag flag) -> (s32 count, buffer) + public ResultCode Get(ServiceCtx context) + { + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0]; + + Span elementsSpan = CreateSpanFromBuffer(context, outputBuffer, true); + + ResultCode result = Get(flag, out int count, elementsSpan); + + elementsSpan = elementsSpan[..count]; + + context.ResponseData.Write(count); + + WriteSpanToBuffer(context, outputBuffer, elementsSpan); + + return result; + } + + [CommandCmif(4)] + // Get1(SourceFlag flag) -> (s32 count, buffer) + public ResultCode Get1(ServiceCtx context) + { + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0]; + + Span elementsSpan = CreateSpanFromBuffer(context, outputBuffer, true); + + ResultCode result = Get1(flag, out int count, elementsSpan); + + elementsSpan = elementsSpan[..count]; + + context.ResponseData.Write(count); + + WriteSpanToBuffer(context, outputBuffer, elementsSpan); + + return result; + } + + [CommandCmif(5)] + // UpdateLatest(nn::mii::CharInfo old_char_info, SourceFlag flag) -> nn::mii::CharInfo + public ResultCode UpdateLatest(ServiceCtx context) + { + CharInfo oldCharInfo = context.RequestData.ReadStruct(); + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + ResultCode result = UpdateLatest(oldCharInfo, flag, out CharInfo newCharInfo); + + context.ResponseData.WriteStruct(newCharInfo); + + return result; + } + + [CommandCmif(6)] + // BuildRandom(Age age, Gender gender, Race race) -> nn::mii::CharInfo + public ResultCode BuildRandom(ServiceCtx context) + { + Age age = (Age)context.RequestData.ReadInt32(); + Gender gender = (Gender)context.RequestData.ReadInt32(); + Race race = (Race)context.RequestData.ReadInt32(); + + ResultCode result = BuildRandom(age, gender, race, out CharInfo charInfo); + + context.ResponseData.WriteStruct(charInfo); + + return result; + } + + [CommandCmif(7)] + // BuildDefault(u32 index) -> nn::mii::CharInfoRaw + public ResultCode BuildDefault(ServiceCtx context) + { + uint index = context.RequestData.ReadUInt32(); + + ResultCode result = BuildDefault(index, out CharInfo charInfo); + + context.ResponseData.WriteStruct(charInfo); + + return result; + } + + [CommandCmif(8)] + // Get2(SourceFlag flag) -> (u32 count, buffer) + public ResultCode Get2(ServiceCtx context) + { + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0]; + + Span elementsSpan = CreateSpanFromBuffer(context, outputBuffer, true); + + ResultCode result = Get2(flag, out int count, elementsSpan); + + elementsSpan = elementsSpan[..count]; + + context.ResponseData.Write(count); + + WriteSpanToBuffer(context, outputBuffer, elementsSpan); + + return result; + } + + [CommandCmif(9)] + // Get3(SourceFlag flag) -> (u32 count, buffer) + public ResultCode Get3(ServiceCtx context) + { + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0]; + + Span elementsSpan = CreateSpanFromBuffer(context, outputBuffer, true); + + ResultCode result = Get3(flag, out int count, elementsSpan); + + elementsSpan = elementsSpan[..count]; + + context.ResponseData.Write(count); + + WriteSpanToBuffer(context, outputBuffer, elementsSpan); + + return result; + } + + [CommandCmif(10)] + // UpdateLatest1(nn::mii::StoreData old_store_data, SourceFlag flag) -> nn::mii::StoreData + public ResultCode UpdateLatest1(ServiceCtx context) + { + StoreData oldStoreData = context.RequestData.ReadStruct(); + SourceFlag flag = (SourceFlag)context.RequestData.ReadInt32(); + + ResultCode result = UpdateLatest1(oldStoreData, flag, out StoreData newStoreData); + + context.ResponseData.WriteStruct(newStoreData); + + return result; + } + + [CommandCmif(11)] + // FindIndex(nn::mii::CreateId create_id, bool is_special) -> s32 + public ResultCode FindIndex(ServiceCtx context) + { + CreateId createId = context.RequestData.ReadStruct(); + bool isSpecial = context.RequestData.ReadBoolean(); + + ResultCode result = FindIndex(createId, isSpecial, out int index); + + context.ResponseData.Write(index); + + return result; + } + + [CommandCmif(12)] + // Move(nn::mii::CreateId create_id, s32 new_index) + public ResultCode Move(ServiceCtx context) + { + CreateId createId = context.RequestData.ReadStruct(); + int newIndex = context.RequestData.ReadInt32(); + + return Move(createId, newIndex); + } + + [CommandCmif(13)] + // AddOrReplace(nn::mii::StoreData store_data) + public ResultCode AddOrReplace(ServiceCtx context) + { + StoreData storeData = context.RequestData.ReadStruct(); + + return AddOrReplace(storeData); + } + + [CommandCmif(14)] + // Delete(nn::mii::CreateId create_id) + public ResultCode Delete(ServiceCtx context) + { + CreateId createId = context.RequestData.ReadStruct(); + + return Delete(createId); + } + + [CommandCmif(15)] + // DestroyFile() + public ResultCode DestroyFile(ServiceCtx context) + { + return DestroyFile(); + } + + [CommandCmif(16)] + // DeleteFile() + public ResultCode DeleteFile(ServiceCtx context) + { + return DeleteFile(); + } + + [CommandCmif(17)] + // Format() + public ResultCode Format(ServiceCtx context) + { + return Format(); + } + + [CommandCmif(18)] + // Import(buffer) + public ResultCode Import(ServiceCtx context) + { + ReadOnlySpan data = CreateByteSpanFromBuffer(context, context.Request.SendBuff[0], false); + + return Import(data); + } + + [CommandCmif(19)] + // Export() -> buffer + public ResultCode Export(ServiceCtx context) + { + IpcBuffDesc outputBuffer = context.Request.ReceiveBuff[0]; + + Span data = CreateByteSpanFromBuffer(context, outputBuffer, true); + + ResultCode result = Export(data); + + context.Memory.Write(outputBuffer.Position, data.ToArray()); + + return result; + } + + [CommandCmif(20)] + // IsBrokenDatabaseWithClearFlag() -> bool + public ResultCode IsBrokenDatabaseWithClearFlag(ServiceCtx context) + { + ResultCode result = IsBrokenDatabaseWithClearFlag(out bool isBrokenDatabase); + + context.ResponseData.Write(isBrokenDatabase); + + return result; + } + + [CommandCmif(21)] + // GetIndex(nn::mii::CharInfo char_info) -> s32 + public ResultCode GetIndex(ServiceCtx context) + { + CharInfo charInfo = context.RequestData.ReadStruct(); + + ResultCode result = GetIndex(charInfo, out int index); + + context.ResponseData.Write(index); + + return result; + } + + [CommandCmif(22)] // 5.0.0+ + // SetInterfaceVersion(u32 version) + public ResultCode SetInterfaceVersion(ServiceCtx context) + { + uint interfaceVersion = context.RequestData.ReadUInt32(); + + SetInterfaceVersion(interfaceVersion); + + return ResultCode.Success; + } + + [CommandCmif(23)] // 5.0.0+ + // Convert(nn::mii::Ver3StoreData ver3_store_data) -> nn::mii::CharInfo + public ResultCode Convert(ServiceCtx context) + { + Ver3StoreData ver3StoreData = context.RequestData.ReadStruct(); + + ResultCode result = Convert(ver3StoreData, out CharInfo charInfo); + + context.ResponseData.WriteStruct(charInfo); + + return result; + } + + [CommandCmif(24)] // 7.0.0+ + // ConvertCoreDataToCharInfo(nn::mii::CoreData core_data) -> nn::mii::CharInfo + public ResultCode ConvertCoreDataToCharInfo(ServiceCtx context) + { + CoreData coreData = context.RequestData.ReadStruct(); + + ResultCode result = ConvertCoreDataToCharInfo(coreData, out CharInfo charInfo); + + context.ResponseData.WriteStruct(charInfo); + + return result; + } + + [CommandCmif(25)] // 7.0.0+ + // ConvertCharInfoToCoreData(nn::mii::CharInfo char_info) -> nn::mii::CoreData + public ResultCode ConvertCharInfoToCoreData(ServiceCtx context) + { + CharInfo charInfo = context.RequestData.ReadStruct(); + + ResultCode result = ConvertCharInfoToCoreData(charInfo, out CoreData coreData); + + context.ResponseData.WriteStruct(coreData); + + return result; + } + + private Span CreateByteSpanFromBuffer(ServiceCtx context, IpcBuffDesc ipcBuff, bool isOutput) + { + byte[] rawData; + + if (isOutput) + { + rawData = new byte[ipcBuff.Size]; + } + else + { + rawData = new byte[ipcBuff.Size]; + + context.Memory.Read(ipcBuff.Position, rawData); + } + + return new Span(rawData); + } + + private Span CreateSpanFromBuffer(ServiceCtx context, IpcBuffDesc ipcBuff, bool isOutput) where T : unmanaged + { + return MemoryMarshal.Cast(CreateByteSpanFromBuffer(context, ipcBuff, isOutput)); + } + + private void WriteSpanToBuffer(ServiceCtx context, IpcBuffDesc ipcBuff, Span span) where T : unmanaged + { + Span rawData = MemoryMarshal.Cast(span); + + context.Memory.Write(ipcBuff.Position, rawData); + } + + protected abstract bool IsUpdated(SourceFlag flag); + + protected abstract bool IsFullDatabase(); + + protected abstract uint GetCount(SourceFlag flag); + + protected abstract ResultCode Get(SourceFlag flag, out int count, Span elements); + + protected abstract ResultCode Get1(SourceFlag flag, out int count, Span elements); + + protected abstract ResultCode UpdateLatest(CharInfo oldCharInfo, SourceFlag flag, out CharInfo newCharInfo); + + protected abstract ResultCode BuildRandom(Age age, Gender gender, Race race, out CharInfo charInfo); + + protected abstract ResultCode BuildDefault(uint index, out CharInfo charInfo); + + protected abstract ResultCode Get2(SourceFlag flag, out int count, Span elements); + + protected abstract ResultCode Get3(SourceFlag flag, out int count, Span elements); + + protected abstract ResultCode UpdateLatest1(StoreData oldStoreData, SourceFlag flag, out StoreData newStoreData); + + protected abstract ResultCode FindIndex(CreateId createId, bool isSpecial, out int index); + + protected abstract ResultCode Move(CreateId createId, int newIndex); + + protected abstract ResultCode AddOrReplace(StoreData storeData); + + protected abstract ResultCode Delete(CreateId createId); + + protected abstract ResultCode DestroyFile(); + + protected abstract ResultCode DeleteFile(); + + protected abstract ResultCode Format(); + + protected abstract ResultCode Import(ReadOnlySpan data); + + protected abstract ResultCode Export(Span data); + + protected abstract ResultCode IsBrokenDatabaseWithClearFlag(out bool isBrokenDatabase); + + protected abstract ResultCode GetIndex(CharInfo charInfo, out int index); + + protected abstract void SetInterfaceVersion(uint interfaceVersion); + + protected abstract ResultCode Convert(Ver3StoreData ver3StoreData, out CharInfo charInfo); + + protected abstract ResultCode ConvertCoreDataToCharInfo(CoreData coreData, out CharInfo charInfo); + + protected abstract ResultCode ConvertCharInfoToCoreData(CharInfo charInfo, out CoreData coreData); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/Age.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Age.cs new file mode 100644 index 00000000..9c89ffd3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Age.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum Age : uint + { + Young, + Normal, + Old, + All, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/BeardType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/BeardType.cs new file mode 100644 index 00000000..8f362c15 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/BeardType.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum BeardType : byte + { + None, + Goatee, + GoateeLong, + LionsManeLong, + LionsMane, + Full, + + Min = None, + Max = Full, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfo.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfo.cs new file mode 100644 index 00000000..63f44694 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfo.cs @@ -0,0 +1,482 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x58)] + struct CharInfo : IStoredData + { + public CreateId CreateId; + public Nickname Nickname; + public FontRegion FontRegion; + public byte FavoriteColor; + public Gender Gender; + public byte Height; + public byte Build; + public byte Type; + public byte RegionMove; + public FacelineType FacelineType; + public FacelineColor FacelineColor; + public FacelineWrinkle FacelineWrinkle; + public FacelineMake FacelineMake; + public HairType HairType; + public CommonColor HairColor; + public HairFlip HairFlip; + public EyeType EyeType; + public CommonColor EyeColor; + public byte EyeScale; + public byte EyeAspect; + public byte EyeRotate; + public byte EyeX; + public byte EyeY; + public EyebrowType EyebrowType; + public CommonColor EyebrowColor; + public byte EyebrowScale; + public byte EyebrowAspect; + public byte EyebrowRotate; + public byte EyebrowX; + public byte EyebrowY; + public NoseType NoseType; + public byte NoseScale; + public byte NoseY; + public MouthType MouthType; + public CommonColor MouthColor; + public byte MouthScale; + public byte MouthAspect; + public byte MouthY; + public CommonColor BeardColor; + public BeardType BeardType; + public MustacheType MustacheType; + public byte MustacheScale; + public byte MustacheY; + public GlassType GlassType; + public CommonColor GlassColor; + public byte GlassScale; + public byte GlassY; + public MoleType MoleType; + public byte MoleScale; + public byte MoleX; + public byte MoleY; + public byte Reserved; + + readonly byte IStoredData.Type => Type; + + readonly CreateId IStoredData.CreateId => CreateId; + + public readonly ResultCode InvalidData => ResultCode.InvalidCharInfo; + + public bool IsValid() + { + return Verify() == 0; + } + + public uint Verify() + { + if (!CreateId.IsValid) + { + return 50; + } + if (!Nickname.IsValid()) + { + return 51; + } + if ((byte)FontRegion > 3) + { + return 23; + } + if (FavoriteColor > 11) + { + return 22; + } + if (Gender > Gender.Max) + { + return 24; + } + if ((sbyte)Height < 0) + { + return 32; + } + if ((sbyte)Build < 0) + { + return 3; + } + if (Type > 1) + { + return 53; + } + if (RegionMove > 3) + { + return 49; + } + if (FacelineType > FacelineType.Max) + { + return 21; + } + if (FacelineColor > FacelineColor.Max) + { + return 18; + } + if (FacelineWrinkle > FacelineWrinkle.Max) + { + return 20; + } + if (FacelineMake > FacelineMake.Max) + { + return 19; + } + if (HairType > HairType.Max) + { + return 31; + } + if (HairColor > CommonColor.Max) + { + return 29; + } + if (HairFlip > HairFlip.Max) + { + return 30; + } + if (EyeType > EyeType.Max) + { + return 8; + } + if (EyeColor > CommonColor.Max) + { + return 5; + } + if (EyeScale > 7) + { + return 7; + } + if (EyeAspect > 6) + { + return 4; + } + if (EyeRotate > 7) + { + return 6; + } + if (EyeX > 12) + { + return 9; + } + if (EyeY > 18) + { + return 10; + } + if (EyebrowType > EyebrowType.Max) + { + return 15; + } + if (EyebrowColor > CommonColor.Max) + { + return 12; + } + if (EyebrowScale > 8) + { + return 14; + } + if (EyebrowAspect > 6) + { + return 11; + } + if (EyebrowRotate > 11) + { + return 13; + } + if (EyebrowX > 12) + { + return 16; + } + if (EyebrowY - 3 > 15) + { + return 17; + } + if (NoseType > NoseType.Max) + { + return 47; + } + if (NoseScale > 8) + { + return 46; + } + if (NoseY > 18) + { + return 48; + } + if (MouthType > MouthType.Max) + { + return 40; + } + if (MouthColor > CommonColor.Max) + { + return 38; + } + if (MouthScale > 8) + { + return 39; + } + if (MouthAspect > 6) + { + return 37; + } + if (MouthY > 18) + { + return 41; + } + if (BeardColor > CommonColor.Max) + { + return 1; + } + if (BeardType > BeardType.Max) + { + return 2; + } + if (MustacheType > MustacheType.Max) + { + return 43; + } + if (MustacheScale > 8) + { + return 42; + } + if (MustacheY > 16) + { + return 44; + } + if (GlassType > GlassType.Max) + { + return 27; + } + if (GlassColor > CommonColor.Max) + { + return 25; + } + if (GlassScale > 7) + { + return 26; + } + if (GlassY > 20) + { + return 28; + } + if (MoleType > MoleType.Max) + { + return 34; + } + if (MoleScale > 8) + { + return 33; + } + if (MoleX > 16) + { + return 35; + } + if (MoleY >= 31) + { + return 36; + } + + return 0; + } + + public void SetFromStoreData(StoreData storeData) + { + Nickname = storeData.CoreData.Nickname; + CreateId = storeData.CreateId; + FontRegion = storeData.CoreData.FontRegion; + FavoriteColor = storeData.CoreData.FavoriteColor; + Gender = storeData.CoreData.Gender; + Height = storeData.CoreData.Height; + Build = storeData.CoreData.Build; + Type = storeData.CoreData.Type; + RegionMove = storeData.CoreData.RegionMove; + FacelineType = storeData.CoreData.FacelineType; + FacelineColor = storeData.CoreData.FacelineColor; + FacelineWrinkle = storeData.CoreData.FacelineWrinkle; + FacelineMake = storeData.CoreData.FacelineMake; + HairType = storeData.CoreData.HairType; + HairColor = storeData.CoreData.HairColor; + HairFlip = storeData.CoreData.HairFlip; + EyeType = storeData.CoreData.EyeType; + EyeColor = storeData.CoreData.EyeColor; + EyeScale = storeData.CoreData.EyeScale; + EyeAspect = storeData.CoreData.EyeAspect; + EyeRotate = storeData.CoreData.EyeRotate; + EyeX = storeData.CoreData.EyeX; + EyeY = storeData.CoreData.EyeY; + EyebrowType = storeData.CoreData.EyebrowType; + EyebrowColor = storeData.CoreData.EyebrowColor; + EyebrowScale = storeData.CoreData.EyebrowScale; + EyebrowAspect = storeData.CoreData.EyebrowAspect; + EyebrowRotate = storeData.CoreData.EyebrowRotate; + EyebrowX = storeData.CoreData.EyebrowX; + EyebrowY = storeData.CoreData.EyebrowY; + NoseType = storeData.CoreData.NoseType; + NoseScale = storeData.CoreData.NoseScale; + NoseY = storeData.CoreData.NoseY; + MouthType = storeData.CoreData.MouthType; + MouthColor = storeData.CoreData.MouthColor; + MouthScale = storeData.CoreData.MouthScale; + MouthAspect = storeData.CoreData.MouthAspect; + MouthY = storeData.CoreData.MouthY; + BeardColor = storeData.CoreData.BeardColor; + BeardType = storeData.CoreData.BeardType; + MustacheType = storeData.CoreData.MustacheType; + MustacheScale = storeData.CoreData.MustacheScale; + MustacheY = storeData.CoreData.MustacheY; + GlassType = storeData.CoreData.GlassType; + GlassColor = storeData.CoreData.GlassColor; + GlassScale = storeData.CoreData.GlassScale; + GlassY = storeData.CoreData.GlassY; + MoleType = storeData.CoreData.MoleType; + MoleScale = storeData.CoreData.MoleScale; + MoleX = storeData.CoreData.MoleX; + MoleY = storeData.CoreData.MoleY; + Reserved = 0; + } + + public readonly void SetSource(Source source) + { + // Only implemented for Element variants. + } + + public static bool operator ==(CharInfo x, CharInfo y) + { + return x.Equals(y); + } + + public static bool operator !=(CharInfo x, CharInfo y) + { + return !x.Equals(y); + } + + public readonly override bool Equals(object obj) + { + return obj is CharInfo charInfo && Equals(charInfo); + } + + public readonly bool Equals(CharInfo cmpObj) + { + if (!cmpObj.IsValid()) + { + return false; + } + + bool result = true; + + result &= Nickname == cmpObj.Nickname; + result &= CreateId == cmpObj.CreateId; + result &= FontRegion == cmpObj.FontRegion; + result &= FavoriteColor == cmpObj.FavoriteColor; + result &= Gender == cmpObj.Gender; + result &= Height == cmpObj.Height; + result &= Build == cmpObj.Build; + result &= Type == cmpObj.Type; + result &= RegionMove == cmpObj.RegionMove; + result &= FacelineType == cmpObj.FacelineType; + result &= FacelineColor == cmpObj.FacelineColor; + result &= FacelineWrinkle == cmpObj.FacelineWrinkle; + result &= FacelineMake == cmpObj.FacelineMake; + result &= HairType == cmpObj.HairType; + result &= HairColor == cmpObj.HairColor; + result &= HairFlip == cmpObj.HairFlip; + result &= EyeType == cmpObj.EyeType; + result &= EyeColor == cmpObj.EyeColor; + result &= EyeScale == cmpObj.EyeScale; + result &= EyeAspect == cmpObj.EyeAspect; + result &= EyeRotate == cmpObj.EyeRotate; + result &= EyeX == cmpObj.EyeX; + result &= EyeY == cmpObj.EyeY; + result &= EyebrowType == cmpObj.EyebrowType; + result &= EyebrowColor == cmpObj.EyebrowColor; + result &= EyebrowScale == cmpObj.EyebrowScale; + result &= EyebrowAspect == cmpObj.EyebrowAspect; + result &= EyebrowRotate == cmpObj.EyebrowRotate; + result &= EyebrowX == cmpObj.EyebrowX; + result &= EyebrowY == cmpObj.EyebrowY; + result &= NoseType == cmpObj.NoseType; + result &= NoseScale == cmpObj.NoseScale; + result &= NoseY == cmpObj.NoseY; + result &= MouthType == cmpObj.MouthType; + result &= MouthColor == cmpObj.MouthColor; + result &= MouthScale == cmpObj.MouthScale; + result &= MouthAspect == cmpObj.MouthAspect; + result &= MouthY == cmpObj.MouthY; + result &= BeardColor == cmpObj.BeardColor; + result &= BeardType == cmpObj.BeardType; + result &= MustacheType == cmpObj.MustacheType; + result &= MustacheScale == cmpObj.MustacheScale; + result &= MustacheY == cmpObj.MustacheY; + result &= GlassType == cmpObj.GlassType; + result &= GlassColor == cmpObj.GlassColor; + result &= GlassScale == cmpObj.GlassScale; + result &= GlassY == cmpObj.GlassY; + result &= MoleType == cmpObj.MoleType; + result &= MoleScale == cmpObj.MoleScale; + result &= MoleX == cmpObj.MoleX; + result &= MoleY == cmpObj.MoleY; + + return result; + } + + public readonly override int GetHashCode() + { + HashCode hashCode = new(); + + hashCode.Add(Nickname); + hashCode.Add(CreateId); + hashCode.Add(FontRegion); + hashCode.Add(FavoriteColor); + hashCode.Add(Gender); + hashCode.Add(Height); + hashCode.Add(Build); + hashCode.Add(Type); + hashCode.Add(RegionMove); + hashCode.Add(FacelineType); + hashCode.Add(FacelineColor); + hashCode.Add(FacelineWrinkle); + hashCode.Add(FacelineMake); + hashCode.Add(HairType); + hashCode.Add(HairColor); + hashCode.Add(HairFlip); + hashCode.Add(EyeType); + hashCode.Add(EyeColor); + hashCode.Add(EyeScale); + hashCode.Add(EyeAspect); + hashCode.Add(EyeRotate); + hashCode.Add(EyeX); + hashCode.Add(EyeY); + hashCode.Add(EyebrowType); + hashCode.Add(EyebrowColor); + hashCode.Add(EyebrowScale); + hashCode.Add(EyebrowAspect); + hashCode.Add(EyebrowRotate); + hashCode.Add(EyebrowX); + hashCode.Add(EyebrowY); + hashCode.Add(NoseType); + hashCode.Add(NoseScale); + hashCode.Add(NoseY); + hashCode.Add(MouthType); + hashCode.Add(MouthColor); + hashCode.Add(MouthScale); + hashCode.Add(MouthAspect); + hashCode.Add(MouthY); + hashCode.Add(BeardColor); + hashCode.Add(BeardType); + hashCode.Add(MustacheType); + hashCode.Add(MustacheScale); + hashCode.Add(MustacheY); + hashCode.Add(GlassType); + hashCode.Add(GlassColor); + hashCode.Add(GlassScale); + hashCode.Add(GlassY); + hashCode.Add(MoleType); + hashCode.Add(MoleScale); + hashCode.Add(MoleX); + hashCode.Add(MoleY); + + return hashCode.ToHashCode(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfoElement.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfoElement.cs new file mode 100644 index 00000000..974fc239 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfoElement.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x5C)] + struct CharInfoElement : IElement + { + public CharInfo CharInfo; + public Source Source; + + public void SetFromStoreData(StoreData storeData) + { + CharInfo.SetFromStoreData(storeData); + } + + public void SetSource(Source source) + { + Source = source; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/CommonColor.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CommonColor.cs new file mode 100644 index 00000000..a21eb822 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CommonColor.cs @@ -0,0 +1,9 @@ + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum CommonColor : byte + { + Min = 0, + Max = 99, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs new file mode 100644 index 00000000..aaeb8aa1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs @@ -0,0 +1,912 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.HLE.HOS.Services.Mii.Types.RandomMiiConstants; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = Size)] + struct CoreData : IEquatable + { + public const int Size = 0x30; + + private Array48 _storage; + + public Span Storage => _storage.AsSpan(); + + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x18)] + public struct ElementInfo + { + public int ByteOffset; + public int BitOffset; + public int BitWidth; + public int MinValue; + public int MaxValue; + public int Unknown; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetValue(ElementInfoIndex index) + { + ElementInfo info = ElementInfos[(int)index]; + + return ((Storage[info.ByteOffset] >> info.BitOffset) & ~(-1 << info.BitWidth)) + info.MinValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetValue(ElementInfoIndex index, int value) + { + ElementInfo info = ElementInfos[(int)index]; + + int newValue = Storage[info.ByteOffset] & ~(~(-1 << info.BitWidth) << info.BitOffset) | (((value - info.MinValue) & ~(-1 << info.BitWidth)) << info.BitOffset); + + Storage[info.ByteOffset] = (byte)newValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsElementValid(ElementInfoIndex index) + { + ElementInfo info = ElementInfos[(int)index]; + + int value = GetValue(index); + + return value >= info.MinValue && value <= info.MaxValue; + } + + public bool IsValid(bool acceptEmptyNickname = false) + { + if (!Nickname.IsValid() || (!acceptEmptyNickname && Nickname.IsEmpty())) + { + return false; + } + + for (int i = 0; i < ElementInfos.Length; i++) + { + if (!IsElementValid((ElementInfoIndex)i)) + { + return false; + } + } + + return true; + } + + public void SetDefault() + { + Storage.Clear(); + + Nickname = Nickname.Default; + } + + public HairType HairType + { + get => (HairType)GetValue(ElementInfoIndex.HairType); + set => SetValue(ElementInfoIndex.HairType, (int)value); + } + + public byte Height + { + get => (byte)GetValue(ElementInfoIndex.Height); + set => SetValue(ElementInfoIndex.Height, value); + } + + public MoleType MoleType + { + get => (MoleType)GetValue(ElementInfoIndex.MoleType); + set => SetValue(ElementInfoIndex.MoleType, (byte)value); + } + + public byte Build + { + get => (byte)GetValue(ElementInfoIndex.Build); + set => SetValue(ElementInfoIndex.Build, value); + } + + public HairFlip HairFlip + { + get => (HairFlip)GetValue(ElementInfoIndex.HairFlip); + set => SetValue(ElementInfoIndex.HairFlip, (byte)value); + } + + public CommonColor HairColor + { + get => (CommonColor)GetValue(ElementInfoIndex.HairColor); + set => SetValue(ElementInfoIndex.HairColor, (int)value); + } + + public byte Type + { + get => (byte)GetValue(ElementInfoIndex.Type); + set => SetValue(ElementInfoIndex.Type, value); + } + + public CommonColor EyeColor + { + get => (CommonColor)GetValue(ElementInfoIndex.EyeColor); + set => SetValue(ElementInfoIndex.EyeColor, (int)value); + } + + public Gender Gender + { + get => (Gender)GetValue(ElementInfoIndex.Gender); + set => SetValue(ElementInfoIndex.Gender, (int)value); + } + + public CommonColor EyebrowColor + { + get => (CommonColor)GetValue(ElementInfoIndex.EyebrowColor); + set => SetValue(ElementInfoIndex.EyebrowColor, (int)value); + } + + public CommonColor MouthColor + { + get => (CommonColor)GetValue(ElementInfoIndex.MouthColor); + set => SetValue(ElementInfoIndex.MouthColor, (int)value); + } + + public CommonColor BeardColor + { + get => (CommonColor)GetValue(ElementInfoIndex.BeardColor); + set => SetValue(ElementInfoIndex.BeardColor, (byte)value); + } + + public CommonColor GlassColor + { + get => (CommonColor)GetValue(ElementInfoIndex.GlassColor); + set => SetValue(ElementInfoIndex.GlassColor, (int)value); + } + + public EyeType EyeType + { + get => (EyeType)GetValue(ElementInfoIndex.EyeType); + set => SetValue(ElementInfoIndex.EyeType, (int)value); + } + + public byte RegionMove + { + get => (byte)GetValue(ElementInfoIndex.RegionMove); + set => SetValue(ElementInfoIndex.RegionMove, value); + } + + public MouthType MouthType + { + get => (MouthType)GetValue(ElementInfoIndex.MouthType); + set => SetValue(ElementInfoIndex.MouthType, (int)value); + } + + public FontRegion FontRegion + { + get => (FontRegion)GetValue(ElementInfoIndex.FontRegion); + set => SetValue(ElementInfoIndex.FontRegion, (byte)value); + } + + public byte EyeY + { + get => (byte)GetValue(ElementInfoIndex.EyeY); + set => SetValue(ElementInfoIndex.EyeY, value); + } + + public byte GlassScale + { + get => (byte)GetValue(ElementInfoIndex.GlassScale); + set => SetValue(ElementInfoIndex.GlassScale, value); + } + + public EyebrowType EyebrowType + { + get => (EyebrowType)GetValue(ElementInfoIndex.EyebrowType); + set => SetValue(ElementInfoIndex.EyebrowType, (int)value); + } + + public MustacheType MustacheType + { + get => (MustacheType)GetValue(ElementInfoIndex.MustacheType); + set => SetValue(ElementInfoIndex.MustacheType, (int)value); + } + + public NoseType NoseType + { + get => (NoseType)GetValue(ElementInfoIndex.NoseType); + set => SetValue(ElementInfoIndex.NoseType, (int)value); + } + + public BeardType BeardType + { + get => (BeardType)GetValue(ElementInfoIndex.BeardType); + set => SetValue(ElementInfoIndex.BeardType, (int)value); + } + + public byte NoseY + { + get => (byte)GetValue(ElementInfoIndex.NoseY); + set => SetValue(ElementInfoIndex.NoseY, value); + } + + public byte MouthAspect + { + get => (byte)GetValue(ElementInfoIndex.MouthAspect); + set => SetValue(ElementInfoIndex.MouthAspect, value); + } + + public byte MouthY + { + get => (byte)GetValue(ElementInfoIndex.MouthY); + set => SetValue(ElementInfoIndex.MouthY, value); + } + + public byte EyebrowAspect + { + get => (byte)GetValue(ElementInfoIndex.EyebrowAspect); + set => SetValue(ElementInfoIndex.EyebrowAspect, value); + } + + public byte MustacheY + { + get => (byte)GetValue(ElementInfoIndex.MustacheY); + set => SetValue(ElementInfoIndex.MustacheY, value); + } + + public byte EyeRotate + { + get => (byte)GetValue(ElementInfoIndex.EyeRotate); + set => SetValue(ElementInfoIndex.EyeRotate, value); + } + + public byte GlassY + { + get => (byte)GetValue(ElementInfoIndex.GlassY); + set => SetValue(ElementInfoIndex.GlassY, value); + } + + public byte EyeAspect + { + get => (byte)GetValue(ElementInfoIndex.EyeAspect); + set => SetValue(ElementInfoIndex.EyeAspect, value); + } + + public byte MoleX + { + get => (byte)GetValue(ElementInfoIndex.MoleX); + set => SetValue(ElementInfoIndex.MoleX, value); + } + + public byte EyeScale + { + get => (byte)GetValue(ElementInfoIndex.EyeScale); + set => SetValue(ElementInfoIndex.EyeScale, value); + } + + public byte MoleY + { + get => (byte)GetValue(ElementInfoIndex.MoleY); + set => SetValue(ElementInfoIndex.MoleY, value); + } + + public GlassType GlassType + { + get => (GlassType)GetValue(ElementInfoIndex.GlassType); + set => SetValue(ElementInfoIndex.GlassType, (int)value); + } + + public byte FavoriteColor + { + get => (byte)GetValue(ElementInfoIndex.FavoriteColor); + set => SetValue(ElementInfoIndex.FavoriteColor, value); + } + + public FacelineType FacelineType + { + get => (FacelineType)GetValue(ElementInfoIndex.FacelineType); + set => SetValue(ElementInfoIndex.FacelineType, (int)value); + } + + public FacelineColor FacelineColor + { + get => (FacelineColor)GetValue(ElementInfoIndex.FacelineColor); + set => SetValue(ElementInfoIndex.FacelineColor, (int)value); + } + + public FacelineWrinkle FacelineWrinkle + { + get => (FacelineWrinkle)GetValue(ElementInfoIndex.FacelineWrinkle); + set => SetValue(ElementInfoIndex.FacelineWrinkle, (int)value); + } + + public FacelineMake FacelineMake + { + get => (FacelineMake)GetValue(ElementInfoIndex.FacelineMake); + set => SetValue(ElementInfoIndex.FacelineMake, (int)value); + } + + public byte EyeX + { + get => (byte)GetValue(ElementInfoIndex.EyeX); + set => SetValue(ElementInfoIndex.EyeX, value); + } + + public byte EyebrowScale + { + get => (byte)GetValue(ElementInfoIndex.EyebrowScale); + set => SetValue(ElementInfoIndex.EyebrowScale, value); + } + + public byte EyebrowRotate + { + get => (byte)GetValue(ElementInfoIndex.EyebrowRotate); + set => SetValue(ElementInfoIndex.EyebrowRotate, value); + } + + public byte EyebrowX + { + get => (byte)GetValue(ElementInfoIndex.EyebrowX); + set => SetValue(ElementInfoIndex.EyebrowX, value); + } + + public byte EyebrowY + { + get => (byte)GetValue(ElementInfoIndex.EyebrowY); + set => SetValue(ElementInfoIndex.EyebrowY, value); + } + + public byte NoseScale + { + get => (byte)GetValue(ElementInfoIndex.NoseScale); + set => SetValue(ElementInfoIndex.NoseScale, value); + } + + public byte MouthScale + { + get => (byte)GetValue(ElementInfoIndex.MouthScale); + set => SetValue(ElementInfoIndex.MouthScale, value); + } + + public byte MustacheScale + { + get => (byte)GetValue(ElementInfoIndex.MustacheScale); + set => SetValue(ElementInfoIndex.MustacheScale, value); + } + + public byte MoleScale + { + get => (byte)GetValue(ElementInfoIndex.MoleScale); + set => SetValue(ElementInfoIndex.MoleScale, value); + } + + public Span GetNicknameStorage() + { + return Storage[0x1c..]; + } + + public Nickname Nickname + { + get => Nickname.FromBytes(GetNicknameStorage()); + set => value.Raw[..20].CopyTo(GetNicknameStorage()); + } + + public static CoreData BuildRandom(UtilityImpl utilImpl, Age age, Gender gender, Race race) + { + CoreData coreData = new(); + + coreData.SetDefault(); + + if (gender == Gender.All) + { + gender = (Gender)utilImpl.GetRandom((int)gender); + } + + if (age == Age.All) + { + int ageDecade = utilImpl.GetRandom(10); + + if (ageDecade >= 8) + { + age = Age.Old; + } + else if (ageDecade >= 4) + { + age = Age.Normal; + } + else + { + age = Age.Young; + } + } + + if (race == Race.All) + { + int raceTempValue = utilImpl.GetRandom(10); + + if (raceTempValue >= 8) + { + race = Race.Black; + } + else if (raceTempValue >= 4) + { + race = Race.White; + } + else + { + race = Race.Asian; + } + } + + int axisY = 0; + + if (gender == Gender.Female && age == Age.Young) + { + axisY = utilImpl.GetRandom(3); + } + + int indexFor4 = 3 * (int)age + 9 * (int)gender + (int)race; + + var facelineTypeInfo = RandomMiiFacelineArray[indexFor4]; + var facelineColorInfo = RandomMiiFacelineColorArray[3 * (int)gender + (int)race]; + var facelineWrinkleInfo = RandomMiiFacelineWrinkleArray[indexFor4]; + var facelineMakeInfo = RandomMiiFacelineMakeArray[indexFor4]; + var hairTypeInfo = RandomMiiHairTypeArray[indexFor4]; + var hairColorInfo = RandomMiiHairColorArray[3 * (int)race + (int)age]; + var eyeTypeInfo = RandomMiiEyeTypeArray[indexFor4]; + var eyeColorInfo = RandomMiiEyeColorArray[(int)race]; + var eyebrowTypeInfo = RandomMiiEyebrowTypeArray[indexFor4]; + var noseTypeInfo = RandomMiiNoseTypeArray[indexFor4]; + var mouthTypeInfo = RandomMiiMouthTypeArray[indexFor4]; + var glassTypeInfo = RandomMiiGlassTypeArray[(int)age]; + + // Faceline + coreData.FacelineType = (FacelineType)facelineTypeInfo.Values[utilImpl.GetRandom(facelineTypeInfo.ValuesCount)]; + coreData.FacelineColor = (FacelineColor)Helper.Ver3FacelineColorTable[facelineColorInfo.Values[utilImpl.GetRandom(facelineColorInfo.ValuesCount)]]; + coreData.FacelineWrinkle = (FacelineWrinkle)facelineWrinkleInfo.Values[utilImpl.GetRandom(facelineWrinkleInfo.ValuesCount)]; + coreData.FacelineMake = (FacelineMake)facelineMakeInfo.Values[utilImpl.GetRandom(facelineMakeInfo.ValuesCount)]; + + // Hair + coreData.HairType = (HairType)hairTypeInfo.Values[utilImpl.GetRandom(hairTypeInfo.ValuesCount)]; + coreData.HairColor = (CommonColor)Helper.Ver3HairColorTable[hairColorInfo.Values[utilImpl.GetRandom(hairColorInfo.ValuesCount)]]; + coreData.HairFlip = (HairFlip)utilImpl.GetRandom((int)HairFlip.Max + 1); + + // Eye + coreData.EyeType = (EyeType)eyeTypeInfo.Values[utilImpl.GetRandom(eyeTypeInfo.ValuesCount)]; + + int eyeRotateKey1 = gender != Gender.Male ? 4 : 2; + int eyeRotateKey2 = gender != Gender.Male ? 3 : 4; + + byte eyeRotateOffset = (byte)(32 - EyeRotateTable[eyeRotateKey1] + eyeRotateKey2); + byte eyeRotate = (byte)(32 - EyeRotateTable[(int)coreData.EyeType]); + + coreData.EyeColor = (CommonColor)Helper.Ver3EyeColorTable[eyeColorInfo.Values[utilImpl.GetRandom(eyeColorInfo.ValuesCount)]]; + coreData.EyeScale = 4; + coreData.EyeAspect = 3; + coreData.EyeRotate = (byte)(eyeRotateOffset - eyeRotate); + coreData.EyeX = 2; + coreData.EyeY = (byte)(axisY + 12); + + // Eyebrow + coreData.EyebrowType = (EyebrowType)eyebrowTypeInfo.Values[utilImpl.GetRandom(eyebrowTypeInfo.ValuesCount)]; + + int eyebrowRotateKey = race == Race.Asian ? 6 : 0; + int eyebrowY = race == Race.Asian ? 9 : 10; + + byte eyebrowRotateOffset = (byte)(32 - EyebrowRotateTable[eyebrowRotateKey] + 6); + byte eyebrowRotate = (byte)(32 - EyebrowRotateTable[(int)coreData.EyebrowType]); + + coreData.EyebrowColor = coreData.HairColor; + coreData.EyebrowScale = 4; + coreData.EyebrowAspect = 3; + coreData.EyebrowRotate = (byte)(eyebrowRotateOffset - eyebrowRotate); + coreData.EyebrowX = 2; + coreData.EyebrowY = (byte)(axisY + eyebrowY); + + // Nose + int noseScale = gender == Gender.Female ? 3 : 4; + + coreData.NoseType = (NoseType)noseTypeInfo.Values[utilImpl.GetRandom(noseTypeInfo.ValuesCount)]; + coreData.NoseScale = (byte)noseScale; + coreData.NoseY = (byte)(axisY + 9); + + // Mouth + int mouthColor = gender == Gender.Female ? utilImpl.GetRandom(0, 4) : 0; + + coreData.MouthType = (MouthType)mouthTypeInfo.Values[utilImpl.GetRandom(mouthTypeInfo.ValuesCount)]; + coreData.MouthColor = (CommonColor)Helper.Ver3MouthColorTable[mouthColor]; + coreData.MouthScale = 4; + coreData.MouthAspect = 3; + coreData.MouthY = (byte)(axisY + 13); + + // Beard & Mustache + coreData.BeardColor = coreData.HairColor; + coreData.MustacheScale = 4; + + if (gender == Gender.Male && age != Age.Young && utilImpl.GetRandom(10) < 2) + { + BeardAndMustacheFlag mustacheAndBeardFlag = (BeardAndMustacheFlag)utilImpl.GetRandom(3); + + BeardType beardType = BeardType.None; + MustacheType mustacheType = MustacheType.None; + + if ((mustacheAndBeardFlag & BeardAndMustacheFlag.Beard) == BeardAndMustacheFlag.Beard) + { + beardType = (BeardType)utilImpl.GetRandom((int)BeardType.Goatee, (int)BeardType.Full); + } + + if ((mustacheAndBeardFlag & BeardAndMustacheFlag.Mustache) == BeardAndMustacheFlag.Mustache) + { + mustacheType = (MustacheType)utilImpl.GetRandom((int)MustacheType.Walrus, (int)MustacheType.Toothbrush); + } + + coreData.MustacheType = mustacheType; + coreData.BeardType = beardType; + coreData.MustacheY = 10; + } + else + { + coreData.MustacheType = MustacheType.None; + coreData.BeardType = BeardType.None; + coreData.MustacheY = (byte)(axisY + 10); + } + + // Glass + int glassTypeStart = utilImpl.GetRandom(100); + GlassType glassType = GlassType.None; + + while (glassTypeStart < glassTypeInfo.Values[(int)glassType]) + { + glassType++; + + if ((int)glassType >= glassTypeInfo.ValuesCount) + { + throw new InvalidOperationException("glassTypeStart shouldn't exceed glassTypeInfo.ValuesCount"); + } + } + + coreData.GlassType = glassType; + coreData.GlassColor = (CommonColor)Helper.Ver3GlassColorTable[0]; + coreData.GlassScale = 4; + coreData.GlassY = (byte)(axisY + 10); + + // Mole + coreData.MoleType = 0; + coreData.MoleScale = 4; + coreData.MoleX = 2; + coreData.MoleY = 20; + + // Body sizing + coreData.Height = 64; + coreData.Build = 64; + + // Misc + coreData.Nickname = Nickname.Default; + coreData.Gender = gender; + coreData.FavoriteColor = (byte)utilImpl.GetRandom(0, 11); + coreData.RegionMove = 0; + coreData.FontRegion = 0; + coreData.Type = 0; + + return coreData; + } + + public void SetFromCharInfo(CharInfo charInfo) + { + Nickname = charInfo.Nickname; + FontRegion = charInfo.FontRegion; + FavoriteColor = charInfo.FavoriteColor; + Gender = charInfo.Gender; + Height = charInfo.Height; + Build = charInfo.Build; + Type = charInfo.Type; + RegionMove = charInfo.RegionMove; + FacelineType = charInfo.FacelineType; + FacelineColor = charInfo.FacelineColor; + FacelineWrinkle = charInfo.FacelineWrinkle; + FacelineMake = charInfo.FacelineMake; + HairType = charInfo.HairType; + HairColor = charInfo.HairColor; + HairFlip = charInfo.HairFlip; + EyeType = charInfo.EyeType; + EyeColor = charInfo.EyeColor; + EyeScale = charInfo.EyeScale; + EyeAspect = charInfo.EyeAspect; + EyeRotate = charInfo.EyeRotate; + EyeX = charInfo.EyeX; + EyeY = charInfo.EyeY; + EyebrowType = charInfo.EyebrowType; + EyebrowColor = charInfo.EyebrowColor; + EyebrowScale = charInfo.EyebrowScale; + EyebrowAspect = charInfo.EyebrowAspect; + EyebrowRotate = charInfo.EyebrowRotate; + EyebrowX = charInfo.EyebrowX; + EyebrowY = charInfo.EyebrowY; + NoseType = charInfo.NoseType; + NoseScale = charInfo.NoseScale; + NoseY = charInfo.NoseY; + MouthType = charInfo.MouthType; + MouthColor = charInfo.MouthColor; + MouthScale = charInfo.MouthScale; + MouthAspect = charInfo.MouthAspect; + MouthY = charInfo.MouthY; + BeardColor = charInfo.BeardColor; + BeardType = charInfo.BeardType; + MustacheType = charInfo.MustacheType; + MustacheScale = charInfo.MustacheScale; + MustacheY = charInfo.MustacheY; + GlassType = charInfo.GlassType; + GlassColor = charInfo.GlassColor; + GlassScale = charInfo.GlassScale; + GlassY = charInfo.GlassY; + MoleType = charInfo.MoleType; + MoleScale = charInfo.MoleScale; + MoleX = charInfo.MoleX; + MoleY = charInfo.MoleY; + } + + public static bool operator ==(CoreData x, CoreData y) + { + return x.Equals(y); + } + + public static bool operator !=(CoreData x, CoreData y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is CoreData coreData && Equals(coreData); + } + + public bool Equals(CoreData cmpObj) + { + if (!cmpObj.IsValid()) + { + return false; + } + + bool result = true; + + result &= Nickname == cmpObj.Nickname; + result &= FontRegion == cmpObj.FontRegion; + result &= FavoriteColor == cmpObj.FavoriteColor; + result &= Gender == cmpObj.Gender; + result &= Height == cmpObj.Height; + result &= Build == cmpObj.Build; + result &= Type == cmpObj.Type; + result &= RegionMove == cmpObj.RegionMove; + result &= FacelineType == cmpObj.FacelineType; + result &= FacelineColor == cmpObj.FacelineColor; + result &= FacelineWrinkle == cmpObj.FacelineWrinkle; + result &= FacelineMake == cmpObj.FacelineMake; + result &= HairType == cmpObj.HairType; + result &= HairColor == cmpObj.HairColor; + result &= HairFlip == cmpObj.HairFlip; + result &= EyeType == cmpObj.EyeType; + result &= EyeColor == cmpObj.EyeColor; + result &= EyeScale == cmpObj.EyeScale; + result &= EyeAspect == cmpObj.EyeAspect; + result &= EyeRotate == cmpObj.EyeRotate; + result &= EyeX == cmpObj.EyeX; + result &= EyeY == cmpObj.EyeY; + result &= EyebrowType == cmpObj.EyebrowType; + result &= EyebrowColor == cmpObj.EyebrowColor; + result &= EyebrowScale == cmpObj.EyebrowScale; + result &= EyebrowAspect == cmpObj.EyebrowAspect; + result &= EyebrowRotate == cmpObj.EyebrowRotate; + result &= EyebrowX == cmpObj.EyebrowX; + result &= EyebrowY == cmpObj.EyebrowY; + result &= NoseType == cmpObj.NoseType; + result &= NoseScale == cmpObj.NoseScale; + result &= NoseY == cmpObj.NoseY; + result &= MouthType == cmpObj.MouthType; + result &= MouthColor == cmpObj.MouthColor; + result &= MouthScale == cmpObj.MouthScale; + result &= MouthAspect == cmpObj.MouthAspect; + result &= MouthY == cmpObj.MouthY; + result &= BeardColor == cmpObj.BeardColor; + result &= BeardType == cmpObj.BeardType; + result &= MustacheType == cmpObj.MustacheType; + result &= MustacheScale == cmpObj.MustacheScale; + result &= MustacheY == cmpObj.MustacheY; + result &= GlassType == cmpObj.GlassType; + result &= GlassColor == cmpObj.GlassColor; + result &= GlassScale == cmpObj.GlassScale; + result &= GlassY == cmpObj.GlassY; + result &= MoleType == cmpObj.MoleType; + result &= MoleScale == cmpObj.MoleScale; + result &= MoleX == cmpObj.MoleX; + result &= MoleY == cmpObj.MoleY; + + return result; + } + + public override int GetHashCode() + { + HashCode hashCode = new(); + + hashCode.Add(Nickname); + hashCode.Add(FontRegion); + hashCode.Add(FavoriteColor); + hashCode.Add(Gender); + hashCode.Add(Height); + hashCode.Add(Build); + hashCode.Add(Type); + hashCode.Add(RegionMove); + hashCode.Add(FacelineType); + hashCode.Add(FacelineColor); + hashCode.Add(FacelineWrinkle); + hashCode.Add(FacelineMake); + hashCode.Add(HairType); + hashCode.Add(HairColor); + hashCode.Add(HairFlip); + hashCode.Add(EyeType); + hashCode.Add(EyeColor); + hashCode.Add(EyeScale); + hashCode.Add(EyeAspect); + hashCode.Add(EyeRotate); + hashCode.Add(EyeX); + hashCode.Add(EyeY); + hashCode.Add(EyebrowType); + hashCode.Add(EyebrowColor); + hashCode.Add(EyebrowScale); + hashCode.Add(EyebrowAspect); + hashCode.Add(EyebrowRotate); + hashCode.Add(EyebrowX); + hashCode.Add(EyebrowY); + hashCode.Add(NoseType); + hashCode.Add(NoseScale); + hashCode.Add(NoseY); + hashCode.Add(MouthType); + hashCode.Add(MouthColor); + hashCode.Add(MouthScale); + hashCode.Add(MouthAspect); + hashCode.Add(MouthY); + hashCode.Add(BeardColor); + hashCode.Add(BeardType); + hashCode.Add(MustacheType); + hashCode.Add(MustacheScale); + hashCode.Add(MustacheY); + hashCode.Add(GlassType); + hashCode.Add(GlassColor); + hashCode.Add(GlassScale); + hashCode.Add(GlassY); + hashCode.Add(MoleType); + hashCode.Add(MoleScale); + hashCode.Add(MoleX); + hashCode.Add(MoleY); + + return hashCode.ToHashCode(); + } + + private readonly ReadOnlySpan ElementInfos => MemoryMarshal.Cast(ElementInfoArray); + + private enum ElementInfoIndex + { + HairType, + Height, + MoleType, + Build, + HairFlip, + HairColor, + Type, + EyeColor, + Gender, + EyebrowColor, + MouthColor, + BeardColor, + GlassColor, + EyeType, + RegionMove, + MouthType, + FontRegion, + EyeY, + GlassScale, + EyebrowType, + MustacheType, + NoseType, + BeardType, + NoseY, + MouthAspect, + MouthY, + EyebrowAspect, + MustacheY, + EyeRotate, + GlassY, + EyeAspect, + MoleX, + EyeScale, + MoleY, + GlassType, + FavoriteColor, + FacelineType, + FacelineColor, + FacelineWrinkle, + FacelineMake, + EyeX, + EyebrowScale, + EyebrowRotate, + EyebrowX, + EyebrowY, + NoseScale, + MouthScale, + MustacheScale, + MoleScale, + } + + #region "Element Info Array" + private readonly ReadOnlySpan ElementInfoArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x83, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + }; + #endregion + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/CreateId.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CreateId.cs new file mode 100644 index 00000000..96533a04 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CreateId.cs @@ -0,0 +1,45 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + readonly struct CreateId : IEquatable + { + public readonly UInt128 Raw; + + public readonly bool IsNull => Raw == UInt128.Zero; + public readonly bool IsValid => !IsNull && ((Raw >> 64) & 0xC0) == 0x80; + + public CreateId(UInt128 raw) + { + Raw = raw; + } + + public static bool operator ==(CreateId x, CreateId y) + { + return x.Equals(y); + } + + public static bool operator !=(CreateId x, CreateId y) + { + return !x.Equals(y); + } + + public readonly override bool Equals(object obj) + { + return obj is CreateId createId && Equals(createId); + } + + public readonly bool Equals(CreateId cmpObj) + { + // Nintendo additionally check that the CreatorId is valid before doing the actual comparison. + return IsValid && Raw == cmpObj.Raw; + } + + public readonly override int GetHashCode() + { + return Raw.GetHashCode(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/DefaultMii.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/DefaultMii.cs new file mode 100644 index 00000000..b7a63d16 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/DefaultMii.cs @@ -0,0 +1,197 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = Size)] + struct DefaultMii + { + public const int Size = 0xD8; + + public int FacelineType; + public int FacelineColorVer3; + public int FacelineWrinkle; + public int FacelineMake; + public int HairType; + public int HairColorVer3; + public int HairFlip; + public int EyeType; + public int EyeColorVer3; + public int EyeScale; + public int EyeAspect; + public int EyeRotate; + public int EyeX; + public int EyeY; + public int EyebrowType; + public int EyebrowColorVer3; + public int EyebrowScale; + public int EyebrowAspect; + public int EyebrowRotate; + public int EyebrowX; + public int EyebrowY; + public int NoseType; + public int NoseScale; + public int NoseY; + public int MouthType; + public int MouthColorVer3; + public int MouthScale; + public int MouthAspect; + public int MouthY; + public int MustacheType; + public int BeardType; + public int BeardColorVer3; + public int MustacheScale; + public int MustacheY; + public int GlassType; + public int GlassColorVer3; + public int GlassScale; + public int GlassY; + public int MoleType; + public int MoleScale; + public int MoleX; + public int MoleY; + public int Height; + public int Build; + public int Gender; + public int FavoriteColor; + public int RegionMove; + public int FontRegion; + public int Type; + + private byte _nicknameFirstByte; + + public Span NicknameStorage => MemoryMarshal.CreateSpan(ref _nicknameFirstByte, 20); + + public Nickname Nickname + { + get => Nickname.FromBytes(NicknameStorage); + set => value.Raw[..20].CopyTo(NicknameStorage); + } + + public static ReadOnlySpan Table => MemoryMarshal.Cast(TableRawArray); + + // The first 2 Mii in the default table are used as base for Male/Female in editor but not exposed via IPC. + public static int TableLength => _fromIndex.Length; + + private static readonly int[] _fromIndex = { 2, 3, 4, 5, 6, 7 }; + + public static DefaultMii GetDefaultMii(uint index) + { + return Table[_fromIndex[index]]; + } + + #region "Raw Table Array" + private static ReadOnlySpan TableRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, + 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, + 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, + 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + #endregion + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/EyeType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/EyeType.cs new file mode 100644 index 00000000..0d385c45 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/EyeType.cs @@ -0,0 +1,69 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum EyeType : byte + { + Normal, + NormalLash, + WhiteLash, + WhiteNoBottom, + OvalAngledWhite, + AngryWhite, + DotLashType1, + Line, + DotLine, + OvalWhite, + RoundedWhite, + NormalShadow, + CircleWhite, + Circle, + CircleWhiteStroke, + NormalOvalNoBottom, + NormalOvalLarge, + NormalRoundedNoBottom, + SmallLash, + Small, + TwoSmall, + NormalLongLash, + WhiteTwoLashes, + WhiteThreeLashes, + DotAngry, + DotAngled, + Oval, + SmallWhite, + WhiteAngledNoBottom, + WhiteAngledNoLeft, + SmallWhiteTwoLashes, + LeafWhiteLash, + WhiteLargeNoBottom, + Dot, + DotLashType2, + DotThreeLashes, + WhiteOvalTop, + WhiteOvalBottom, + WhiteOvalBottomFlat, + WhiteOvalTwoLashes, + WhiteOvalThreeLashes, + WhiteOvalNoBottomTwoLashes, + DotWhite, + WhiteOvalTopFlat, + WhiteThinLeaf, + StarThreeLashes, + LineTwoLashes, + CrowsFeet, + WhiteNoBottomFlat, + WhiteNoBottomRounded, + WhiteSmallBottomLine, + WhiteNoBottomLash, + WhiteNoPartialBottomLash, + WhiteOvalBottomLine, + WhiteNoBottomLashTopLine, + WhiteNoPartialBottomTwoLashes, + NormalTopLine, + WhiteOvalLash, + RoundTired, + WhiteLarge, + + Min = Normal, + Max = WhiteLarge, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/EyebrowType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/EyebrowType.cs new file mode 100644 index 00000000..1c9f3436 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/EyebrowType.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum EyebrowType : byte + { + FlatAngledLarge, + LowArchRoundedThin, + SoftAngledLarge, + MediumArchRoundedThin, + RoundedMedium, + LowArchMedium, + RoundedThin, + UpThin, + MediumArchRoundedMedium, + RoundedLarge, + UpLarge, + FlatAngledLargeInverted, + MediumArchFlat, + AngledThin, + HorizontalLarge, + HighArchFlat, + Flat, + MediumArchLarge, + LowArchThin, + RoundedThinInverted, + HighArchLarge, + Hairy, + Dotted, + None, + + Min = FlatAngledLarge, + Max = None, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineColor.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineColor.cs new file mode 100644 index 00000000..dc432eac --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineColor.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum FacelineColor : byte + { + Beige, + WarmBeige, + Natural, + Honey, + Chestnut, + Porcelain, + Ivory, + WarmIvory, + Almond, + Espresso, + + Min = Beige, + Max = Espresso, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineMake.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineMake.cs new file mode 100644 index 00000000..fdd7160e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineMake.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum FacelineMake : byte + { + None, + CheekPorcelain, + CheekNatural, + EyeShadowBlue, + CheekBlushPorcelain, + CheekBlushNatural, + CheekPorcelainEyeShadowBlue, + CheekPorcelainEyeShadowNatural, + CheekBlushPorcelainEyeShadowEspresso, + Freckles, + LionsManeBeard, + StubbleBeard, + + Min = None, + Max = StubbleBeard, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineType.cs new file mode 100644 index 00000000..82d87dd4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineType.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum FacelineType : byte + { + Sharp, + Rounded, + SharpRounded, + SharpRoundedSmall, + Large, + LargeRounded, + SharpSmall, + Flat, + Bump, + Angular, + FlatRounded, + AngularSmall, + + Min = Sharp, + Max = AngularSmall, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineWrinkle.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineWrinkle.cs new file mode 100644 index 00000000..6b45e27a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineWrinkle.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum FacelineWrinkle : byte + { + None, + TearTroughs, + FacialPain, + Cheeks, + Folds, + UnderTheEyes, + SplitChin, + Chin, + BrowDroop, + MouthFrown, + CrowsFeet, + FoldsCrowsFrown, + + Min = None, + Max = FoldsCrowsFrown, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/FontRegion.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FontRegion.cs new file mode 100644 index 00000000..b10a5d73 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/FontRegion.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum FontRegion : byte + { + Standard, + China, + Korea, + Taiwan, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/Gender.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Gender.cs new file mode 100644 index 00000000..b7833cb5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Gender.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum Gender : byte + { + Male, + Female, + All, + + Min = Male, + Max = Female, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/GlassType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/GlassType.cs new file mode 100644 index 00000000..3bf28dc5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/GlassType.cs @@ -0,0 +1,29 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum GlassType : byte + { + None, + Oval, + Wayfarer, + Rectangle, + TopRimless, + Rounded, + Oversized, + CatEye, + Square, + BottomRimless, + SemiOpaqueRounded, + SemiOpaqueCatEye, + SemiOpaqueOval, + SemiOpaqueRectangle, + SemiOpaqueAviator, + OpaqueRounded, + OpaqueCatEye, + OpaqueOval, + OpaqueRectangle, + OpaqueAviator, + + Min = None, + Max = OpaqueAviator, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/HairFlip.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/HairFlip.cs new file mode 100644 index 00000000..4e719e15 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/HairFlip.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum HairFlip : byte + { + Left, + Right, + + Min = Left, + Max = Right, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/HairType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/HairType.cs new file mode 100644 index 00000000..37b7f6bc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/HairType.cs @@ -0,0 +1,141 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum HairType : byte + { + NormalLong, + NormalShort, + NormalMedium, + NormalExtraLong, + NormalLongBottom, + NormalTwoPeaks, + PartingLong, + FrontLock, + PartingShort, + PartingExtraLongCurved, + PartingExtraLong, + PartingMiddleLong, + PartingSquared, + PartingLongBottom, + PeaksTop, + PeaksSquared, + PartingPeaks, + PeaksLongBottom, + Peaks, + PeaksRounded, + PeaksSide, + PeaksMedium, + PeaksLong, + PeaksRoundedLong, + PartingFrontPeaks, + PartingLongFront, + PartingLongRounded, + PartingFrontPeaksLong, + PartingExtraLongRounded, + LongRounded, + NormalUnknown1, + NormalUnknown2, + NormalUnknown3, + NormalUnknown4, + NormalUnknown5, + NormalUnknown6, + DreadLocks, + PlatedMats, + Caps, + Afro, + PlatedMatsLong, + Beanie, + Short, + ShortTopLongSide, + ShortUnknown1, + ShortUnknown2, + MilitaryParting, + Military, + ShortUnknown3, + ShortUnknown4, + ShortUnknown5, + ShortUnknown6, + NoneTop, + None, + LongUnknown1, + LongUnknown2, + LongUnknown3, + LongUnknown4, + LongUnknown5, + LongUnknown6, + LongUnknown7, + LongUnknown8, + LongUnknown9, + LongUnknown10, + LongUnknown11, + LongUnknown12, + LongUnknown13, + LongUnknown14, + LongUnknown15, + LongUnknown16, + LongUnknown17, + LongUnknown18, + LongUnknown19, + LongUnknown20, + LongUnknown21, + LongUnknown22, + LongUnknown23, + LongUnknown24, + LongUnknown25, + LongUnknown26, + LongUnknown27, + LongUnknown28, + LongUnknown29, + LongUnknown30, + LongUnknown31, + LongUnknown32, + LongUnknown33, + LongUnknown34, + LongUnknown35, + LongUnknown36, + LongUnknown37, + LongUnknown38, + LongUnknown39, + LongUnknown40, + LongUnknown41, + LongUnknown42, + LongUnknown43, + LongUnknown44, + LongUnknown45, + LongUnknown46, + LongUnknown47, + LongUnknown48, + LongUnknown49, + LongUnknown50, + LongUnknown51, + LongUnknown52, + LongUnknown53, + LongUnknown54, + LongUnknown55, + LongUnknown56, + LongUnknown57, + LongUnknown58, + LongUnknown59, + LongUnknown60, + LongUnknown61, + LongUnknown62, + LongUnknown63, + LongUnknown64, + LongUnknown65, + LongUnknown66, + TwoMediumFrontStrandsOneLongBackPonyTail, + TwoFrontStrandsLongBackPonyTail, + PartingFrontTwoLongBackPonyTails, + TwoFrontStrandsOneLongBackPonyTail, + LongBackPonyTail, + LongFrontTwoLongBackPonyTails, + StrandsTwoShortSidedPonyTails, + TwoMediumSidedPonyTails, + ShortFrontTwoBackPonyTails, + TwoShortSidedPonyTails, + TwoLongSidedPonyTails, + LongFrontTwoBackPonyTails, + + Min = NormalLong, + Max = LongFrontTwoBackPonyTails, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/IElement.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/IElement.cs new file mode 100644 index 00000000..0f604e89 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/IElement.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + interface IElement + { + void SetFromStoreData(StoreData storeData); + + void SetSource(Source source); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/IStoredData.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/IStoredData.cs new file mode 100644 index 00000000..804e0343 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/IStoredData.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + interface IStoredData : IElement, IEquatable where T : notnull + { + byte Type { get; } + + CreateId CreateId { get; } + + ResultCode InvalidData { get; } + + bool IsValid(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/MoleType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/MoleType.cs new file mode 100644 index 00000000..6aa9517c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/MoleType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum MoleType : byte + { + None, + OneDot, + + Min = None, + Max = OneDot, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/MouthType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/MouthType.cs new file mode 100644 index 00000000..89447b08 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/MouthType.cs @@ -0,0 +1,45 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum MouthType : byte + { + Neutral, + NeutralLips, + Smile, + SmileStroke, + SmileTeeth, + LipsSmall, + LipsLarge, + Wave, + WaveAngrySmall, + NeutralStrokeLarge, + TeethSurprised, + LipsExtraLarge, + LipsUp, + NeutralDown, + Surprised, + TeethMiddle, + NeutralStroke, + LipsExtraSmall, + Malicious, + LipsDual, + NeutralComma, + NeutralUp, + TeethLarge, + WaveAngry, + LipsSexy, + SmileInverted, + LipsSexyOutline, + SmileRounded, + LipsTeeth, + NeutralOpen, + TeethRounded, + WaveAngrySmallInverted, + NeutralCommaInverted, + TeethFull, + SmileDownLine, + Kiss, + + Min = Neutral, + Max = Kiss, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/MustacheType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/MustacheType.cs new file mode 100644 index 00000000..6f13c1a6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/MustacheType.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum MustacheType : byte + { + None, + Walrus, + Pencil, + Horseshoe, + Normal, + Toothbrush, + + Min = None, + Max = Toothbrush, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/Nickname.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Nickname.cs new file mode 100644 index 00000000..4cebfe55 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Nickname.cs @@ -0,0 +1,121 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 2, Size = SizeConst)] + struct Nickname : IEquatable + { + public const int CharCount = 10; + private const int SizeConst = (CharCount + 1) * 2; + + private Array22 _storage; + + public static Nickname Default => FromString("no name"); + public static Nickname Question => FromString("???"); + + public Span Raw => _storage.AsSpan(); + + private ReadOnlySpan Characters => MemoryMarshal.Cast(Raw); + + private int GetEndCharacterIndex() + { + for (int i = 0; i < Characters.Length; i++) + { + if (Characters[i] == 0) + { + return i; + } + } + + return -1; + } + + public bool IsEmpty() + { + for (int i = 0; i < Characters.Length - 1; i++) + { + if (Characters[i] != 0) + { + return false; + } + } + + return true; + } + + public bool IsValid() + { + // Create a new unicode encoding instance with error checking enabled + UnicodeEncoding unicodeEncoding = new(false, false, true); + + try + { + unicodeEncoding.GetString(Raw); + + return true; + } + catch (ArgumentException) + { + return false; + } + } + + public bool IsValidForFontRegion(FontRegion fontRegion) + { + // TODO: We need to extract the character tables used here, for now just assume that if it's valid Unicode, it will be valid for any font. + return IsValid(); + } + + public override string ToString() + { + return Encoding.Unicode.GetString(Raw); + } + + public static Nickname FromBytes(ReadOnlySpan data) + { + if (data.Length > SizeConst) + { + data = data[..SizeConst]; + } + + Nickname result = new(); + + data.CopyTo(result.Raw); + + return result; + } + + public static Nickname FromString(string nickname) + { + return FromBytes(Encoding.Unicode.GetBytes(nickname)); + } + + public static bool operator ==(Nickname x, Nickname y) + { + return x.Equals(y); + } + + public static bool operator !=(Nickname x, Nickname y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is Nickname nickname && Equals(nickname); + } + + public bool Equals(Nickname cmpObj) + { + return Raw.SequenceEqual(cmpObj.Raw); + } + + public override int GetHashCode() + { + return HashCode.Combine(Raw.ToArray()); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/NintendoFigurineDatabase.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/NintendoFigurineDatabase.cs new file mode 100644 index 00000000..5865fb11 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/NintendoFigurineDatabase.cs @@ -0,0 +1,254 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 8, Size = 0x1A98)] + struct NintendoFigurineDatabase + { + private const int DatabaseMagic = ('N' << 0) | ('F' << 8) | ('D' << 16) | ('B' << 24); + private const byte MaxMii = 100; + private const byte CurrentVersion = 1; + + private const int FigurineArraySize = MaxMii * StoreData.Size; + + private uint _magic; + + private FigurineStorageStruct _figurineStorage; + + private byte _version; + private byte _figurineCount; + private ushort _crc; + + // Set to true to allow fixing database with invalid storedata device crc instead of deleting them. + private const bool AcceptInvalidDeviceCrc = true; + + public readonly int Length => _figurineCount; + + [StructLayout(LayoutKind.Sequential, Size = FigurineArraySize)] + private struct FigurineStorageStruct { } + + private Span Figurines => SpanHelpers.AsSpan(ref _figurineStorage); + + public StoreData Get(int index) + { + return Figurines[index]; + } + + public readonly bool IsFull() + { + return Length >= MaxMii; + } + + public bool GetIndexByCreatorId(out int index, CreateId createId) + { + for (int i = 0; i < Length; i++) + { + if (Figurines[i].CreateId == createId) + { + index = i; + + return true; + } + } + + index = -1; + + return false; + } + + public ResultCode Move(int newIndex, int oldIndex) + { + if (newIndex == oldIndex) + { + return ResultCode.NotUpdated; + } + + StoreData tmp = Figurines[oldIndex]; + + int targetLength; + int sourceIndex; + int destinationIndex; + + if (newIndex < oldIndex) + { + targetLength = oldIndex - newIndex; + sourceIndex = newIndex; + destinationIndex = newIndex + 1; + } + else + { + targetLength = newIndex - oldIndex; + sourceIndex = oldIndex + 1; + destinationIndex = oldIndex; + } + + Figurines.Slice(sourceIndex, targetLength).CopyTo(Figurines.Slice(destinationIndex, targetLength)); + + Figurines[newIndex] = tmp; + + UpdateCrc(); + + return ResultCode.Success; + } + + public void Replace(int index, StoreData storeData) + { + Figurines[index] = storeData; + + UpdateCrc(); + } + + public void Add(StoreData storeData) + { + Replace(_figurineCount++, storeData); + } + + public void Delete(int index) + { + int newCount = _figurineCount - 1; + + // If this isn't the only element in the list, move the data in it. + if (index < newCount) + { + int targetLength = newCount - index; + int sourceIndex = index + 1; + int destinationIndex = index; + + Figurines.Slice(sourceIndex, targetLength).CopyTo(Figurines.Slice(destinationIndex, targetLength)); + } + + _figurineCount = (byte)newCount; + + UpdateCrc(); + } + + public bool FixDatabase() + { + bool isBroken = false; + int i = 0; + + while (i < Length) + { + ref StoreData figurine = ref Figurines[i]; + + if (!figurine.IsValid()) + { + if (AcceptInvalidDeviceCrc && figurine.CoreData.IsValid() && figurine.IsValidDataCrc()) + { + figurine.UpdateCrc(); + } + else + { + Delete(i); + isBroken = true; + } + } + else + { + bool hasDuplicate = false; + CreateId createId = figurine.CreateId; + + for (int j = 0; j < i; j++) + { + if (Figurines[j].CreateId == createId) + { + hasDuplicate = true; + break; + } + } + + if (hasDuplicate) + { + Delete(i); + isBroken = true; + } + else + { + i++; + } + } + } + + UpdateCrc(); + + return isBroken; + } + + public ResultCode Verify() + { + if (_magic != DatabaseMagic) + { + return ResultCode.InvalidDatabaseMagic; + } + + if (_version != CurrentVersion) + { + return ResultCode.InvalidDatabaseVersion; + } + + if (!IsValidCrc()) + { + return ResultCode.InvalidCrc; + } + + if (_figurineCount > 100) + { + return ResultCode.InvalidDatabaseSize; + } + + return ResultCode.Success; + } + + public void Format() + { + _magic = DatabaseMagic; + _version = CurrentVersion; + _figurineCount = 0; + + // Fill with empty data + Figurines.Clear(); + + UpdateCrc(); + } + + public void CorruptDatabase() + { + UpdateCrc(); + + _crc = (ushort)~_crc; + } + + private void UpdateCrc() + { + _crc = CalculateCrc(); + } + + public bool IsValidCrc() + { + return _crc == CalculateCrc(); + } + + private ushort CalculateCrc() + { + return Helper.CalculateCrc16(AsSpanWithoutCrc(), 0, true); + } + + public Span AsSpan() + { + return SpanHelpers.AsByteSpan(ref this); + } + + public ReadOnlySpan AsReadOnlySpan() + { + return SpanHelpers.AsReadOnlyByteSpan(ref this); + } + + private ReadOnlySpan AsSpanWithoutCrc() + { + return AsReadOnlySpan()[..(Unsafe.SizeOf() - 2)]; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/NoseType.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/NoseType.cs new file mode 100644 index 00000000..00f0ed12 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/NoseType.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum NoseType : byte + { + Normal, + Rounded, + Dot, + Arrow, + Roman, + Triangle, + Button, + RoundedInverted, + Potato, + Grecian, + Snub, + Aquiline, + ArrowLeft, + RoundedLarge, + Hooked, + Fat, + Droopy, + ArrowLarge, + + Min = Normal, + Max = ArrowLarge, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/Race.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Race.cs new file mode 100644 index 00000000..953858b3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Race.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum Race : uint + { + Black, + White, + Asian, + All, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/RandomMiiConstants.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/RandomMiiConstants.cs new file mode 100644 index 00000000..092733f7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/RandomMiiConstants.cs @@ -0,0 +1,2243 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + static class RandomMiiConstants + { + public static readonly int[] EyeRotateTable = { + 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04, + 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, + 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, + }; + + public static readonly int[] EyebrowRotateTable = { + 0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, 0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06, + 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05, + }; + + [Flags] + public enum BeardAndMustacheFlag + { + Beard = 1, + Mustache, + } + + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0xCC)] + public struct RandomMiiData4 + { + public readonly int Gender; + public readonly int Age; + public readonly int Race; + public readonly int ValuesCount; + + private Array47 _values; + + public ReadOnlySpan Values => _values.AsSpan()[..ValuesCount]; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0xC8)] + public struct RandomMiiData3 + { + private readonly int _argument1; + private readonly int _argument2; + + public readonly int ValuesCount; + + private Array47 _values; + + public ReadOnlySpan Values => _values.AsSpan()[..ValuesCount]; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0xC4)] + public struct RandomMiiData2 + { + private readonly int _argument; + + public readonly int ValuesCount; + + private Array47 _values; + + public ReadOnlySpan Values => _values.AsSpan()[..ValuesCount]; + } + + public static ReadOnlySpan RandomMiiFacelineArray => MemoryMarshal.Cast(RandomMiiFacelineRawArray); + + public static ReadOnlySpan RandomMiiFacelineColorArray => MemoryMarshal.Cast(RandomMiiFacelineColorRawArray); + + public static ReadOnlySpan RandomMiiFacelineWrinkleArray => MemoryMarshal.Cast(RandomMiiFacelineWrinkleRawArray); + + public static ReadOnlySpan RandomMiiFacelineMakeArray => MemoryMarshal.Cast(RandomMiiFacelineMakeRawArray); + + public static ReadOnlySpan RandomMiiHairTypeArray => MemoryMarshal.Cast(RandomMiiHairTypeRawArray); + + public static ReadOnlySpan RandomMiiHairColorArray => MemoryMarshal.Cast(RandomMiiHairColorRawArray); + + public static ReadOnlySpan RandomMiiEyeTypeArray => MemoryMarshal.Cast(RandomMiiEyeTypeRawArray); + + public static ReadOnlySpan RandomMiiEyeColorArray => MemoryMarshal.Cast(RandomMiiEyeColorRawArray); + + public static ReadOnlySpan RandomMiiEyebrowTypeArray => MemoryMarshal.Cast(RandomMiiEyebrowTypeRawArray); + + public static ReadOnlySpan RandomMiiNoseTypeArray => MemoryMarshal.Cast(RandomMiiNoseTypeRawArray); + + public static ReadOnlySpan RandomMiiMouthTypeArray => MemoryMarshal.Cast(RandomMiiMouthTypeRawArray); + + public static ReadOnlySpan RandomMiiGlassTypeArray => MemoryMarshal.Cast(RandomMiiGlassTypeRawArray); + + #region "Random Mii Data Arrays" + + private static ReadOnlySpan RandomMiiFacelineRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static ReadOnlySpan RandomMiiFacelineColorRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static ReadOnlySpan RandomMiiFacelineWrinkleRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static ReadOnlySpan RandomMiiFacelineMakeRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static ReadOnlySpan RandomMiiHairTypeRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x56, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x42, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, + 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, + 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, + 0x51, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, + 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, + 0x41, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, + 0x41, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, + 0x4a, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, + 0x53, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x4d, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, + 0x4a, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, + 0x53, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x45, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x4f, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x51, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x45, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, + 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static ReadOnlySpan RandomMiiHairColorRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static ReadOnlySpan RandomMiiEyeTypeRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x2b, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, + 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x29, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x39, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, + 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, + 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, + 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, + 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static ReadOnlySpan RandomMiiEyeColorRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static ReadOnlySpan RandomMiiEyebrowTypeRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static ReadOnlySpan RandomMiiNoseTypeRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static ReadOnlySpan RandomMiiMouthTypeRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static ReadOnlySpan RandomMiiGlassTypeRawArray => new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x56, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x4e, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + #endregion + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/Source.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Source.cs new file mode 100644 index 00000000..0980564f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Source.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + enum Source + { + Database, + Default, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/SourceFlag.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/SourceFlag.cs new file mode 100644 index 00000000..7f6ddf47 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/SourceFlag.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [Flags] + enum SourceFlag + { + Database = 1 << Source.Database, + Default = 1 << Source.Default, + All = Database | Default, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/SpecialMiiKeyCode.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/SpecialMiiKeyCode.cs new file mode 100644 index 00000000..efa83605 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/SpecialMiiKeyCode.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 4)] + struct SpecialMiiKeyCode + { + private const uint SpecialMiiMagic = 0xA523B78F; + + public uint RawValue; + + public readonly bool IsEnabledSpecialMii() + { + return RawValue == SpecialMiiMagic; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreData.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreData.cs new file mode 100644 index 00000000..44054e6b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreData.cs @@ -0,0 +1,237 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)] + struct StoreData : IStoredData + { + public const int Size = 0x44; + + public CoreData CoreData; + private CreateId _createId; + public ushort DataCrc; + public ushort DeviceCrc; + + public byte Type => CoreData.Type; + + public readonly CreateId CreateId => _createId; + + public readonly ResultCode InvalidData => ResultCode.InvalidStoreData; + + private void UpdateDataCrc() + { + DataCrc = CalculateDataCrc(); + } + + private void UpdateDeviceCrc() + { + DeviceCrc = CalculateDeviceCrc(); + } + + public void UpdateCrc() + { + UpdateDataCrc(); + UpdateDeviceCrc(); + } + + public bool IsSpecial() + { + return CoreData.Type == 1; + } + + public bool IsValid() + { + return CoreData.IsValid() && IsValidDataCrc() && IsValidDeviceCrc(); + } + + public bool IsValidDataCrc() + { + return Helper.CalculateCrc16(AsSpanWithoutDeviceCrc(), 0, false) == 0; + } + + public bool IsValidDeviceCrc() + { + UInt128 deviceId = Helper.GetDeviceId(); + + ushort deviceIdCrc16 = Helper.CalculateCrc16(SpanHelpers.AsByteSpan(ref deviceId), 0, false); + + return Helper.CalculateCrc16(AsSpan(), deviceIdCrc16, false) == 0; + } + + private ushort CalculateDataCrc() + { + return Helper.CalculateCrc16(AsSpanWithoutCrcs(), 0, true); + } + + private ushort CalculateDeviceCrc() + { + UInt128 deviceId = Helper.GetDeviceId(); + + ushort deviceIdCrc16 = Helper.CalculateCrc16(SpanHelpers.AsByteSpan(ref deviceId), 0, false); + + return Helper.CalculateCrc16(AsSpanWithoutDeviceCrc(), deviceIdCrc16, true); + } + + private ReadOnlySpan AsSpan() + { + return SpanHelpers.AsReadOnlyByteSpan(ref this); + } + + private ReadOnlySpan AsSpanWithoutDeviceCrc() + { + return AsSpan()[..(Size - 2)]; + } + + private ReadOnlySpan AsSpanWithoutCrcs() + { + return AsSpan()[..(Size - 4)]; + } + + public static StoreData BuildDefault(UtilityImpl utilImpl, uint index) + { + StoreData result = new() + { + _createId = utilImpl.MakeCreateId(), + }; + + CoreData coreData = new(); + + DefaultMii template = DefaultMii.GetDefaultMii(index); + + coreData.SetDefault(); + +#pragma warning disable IDE0055 // Disable formatting + coreData.Nickname = template.Nickname; + coreData.FontRegion = (FontRegion)template.FontRegion; + coreData.FavoriteColor = (byte)template.FavoriteColor; + coreData.Gender = (Gender)template.Gender; + coreData.Height = (byte)template.Height; + coreData.Build = (byte)template.Build; + coreData.Type = (byte)template.Type; + coreData.RegionMove = (byte)template.RegionMove; + coreData.FacelineType = (FacelineType)template.FacelineType; + coreData.FacelineColor = (FacelineColor)Helper.Ver3FacelineColorTable[template.FacelineColorVer3]; + coreData.FacelineWrinkle = (FacelineWrinkle)template.FacelineWrinkle; + coreData.FacelineMake = (FacelineMake)template.FacelineMake; + coreData.HairType = (HairType)template.HairType; + coreData.HairColor = (CommonColor)Helper.Ver3HairColorTable[template.HairColorVer3]; + coreData.HairFlip = (HairFlip)template.HairFlip; + coreData.EyeType = (EyeType)template.EyeType; + coreData.EyeColor = (CommonColor)Helper.Ver3EyeColorTable[template.EyeColorVer3]; + coreData.EyeScale = (byte)template.EyeScale; + coreData.EyeAspect = (byte)template.EyeAspect; + coreData.EyeRotate = (byte)template.EyeRotate; + coreData.EyeX = (byte)template.EyeX; + coreData.EyeY = (byte)template.EyeY; + coreData.EyebrowType = (EyebrowType)template.EyebrowType; + coreData.EyebrowColor = (CommonColor)Helper.Ver3HairColorTable[template.EyebrowColorVer3]; + coreData.EyebrowScale = (byte)template.EyebrowScale; + coreData.EyebrowAspect = (byte)template.EyebrowAspect; + coreData.EyebrowRotate = (byte)template.EyebrowRotate; + coreData.EyebrowX = (byte)template.EyebrowX; + coreData.EyebrowY = (byte)template.EyebrowY; + coreData.NoseType = (NoseType)template.NoseType; + coreData.NoseScale = (byte)template.NoseScale; + coreData.NoseY = (byte)template.NoseY; + coreData.MouthType = (MouthType)template.MouthType; + coreData.MouthColor = (CommonColor)Helper.Ver3MouthColorTable[template.MouthColorVer3]; + coreData.MouthScale = (byte)template.MouthScale; + coreData.MouthAspect = (byte)template.MouthAspect; + coreData.MouthY = (byte)template.MouthY; + coreData.BeardColor = (CommonColor)Helper.Ver3HairColorTable[template.BeardColorVer3]; + coreData.BeardType = (BeardType)template.BeardType; + coreData.MustacheType = (MustacheType)template.MustacheType; + coreData.MustacheScale = (byte)template.MustacheScale; + coreData.MustacheY = (byte)template.MustacheY; + coreData.GlassType = (GlassType)template.GlassType; + coreData.GlassColor = (CommonColor)Helper.Ver3GlassColorTable[template.GlassColorVer3]; + coreData.GlassScale = (byte)template.GlassScale; + coreData.GlassY = (byte)template.GlassY; + coreData.MoleType = (MoleType)template.MoleType; + coreData.MoleScale = (byte)template.MoleScale; + coreData.MoleX = (byte)template.MoleX; + coreData.MoleY = (byte)template.MoleY; +#pragma warning restore IDE0055 + + result.CoreData = coreData; + + result.UpdateCrc(); + + return result; + } + + public static StoreData BuildRandom(UtilityImpl utilImpl, Age age, Gender gender, Race race) + { + return BuildFromCoreData(utilImpl, CoreData.BuildRandom(utilImpl, age, gender, race)); + } + + public static StoreData BuildFromCoreData(UtilityImpl utilImpl, CoreData coreData) + { + StoreData result = new() + { + CoreData = coreData, + _createId = utilImpl.MakeCreateId(), + }; + + result.UpdateCrc(); + + return result; + } + + public void SetFromStoreData(StoreData storeData) + { + this = storeData; + } + + public readonly void SetSource(Source source) + { + // Only implemented for Element variants. + } + + public static bool operator ==(StoreData x, StoreData y) + { + return x.Equals(y); + } + + public static bool operator !=(StoreData x, StoreData y) + { + return !x.Equals(y); + } + + public readonly override bool Equals(object obj) + { + return obj is StoreData storeData && Equals(storeData); + } + + public readonly bool Equals(StoreData cmpObj) + { + if (!cmpObj.IsValid()) + { + return false; + } + + bool result = true; + + result &= CreateId == cmpObj.CreateId; + result &= CoreData == cmpObj.CoreData; + result &= DataCrc == cmpObj.DataCrc; + result &= DeviceCrc == cmpObj.DeviceCrc; + + return result; + } + + public readonly override int GetHashCode() + { + HashCode hashCode = new(); + + hashCode.Add(CreateId); + hashCode.Add(CoreData); + hashCode.Add(DataCrc); + hashCode.Add(DeviceCrc); + + return hashCode.ToHashCode(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreDataElement.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreDataElement.cs new file mode 100644 index 00000000..629c76f5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreDataElement.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x48)] + struct StoreDataElement : IElement + { + public StoreData StoreData; + public Source Source; + + public void SetFromStoreData(StoreData storeData) + { + StoreData = storeData; + } + + public void SetSource(Source source) + { + Source = source; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/Ver3StoreData.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Ver3StoreData.cs new file mode 100644 index 00000000..3930b331 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/Ver3StoreData.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Mii.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = Size)] + struct Ver3StoreData + { + public const int Size = 0x60; + + private Array96 _storage; + + public Span Storage => _storage.AsSpan(); + + // TODO: define all getters/setters + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/UtilityImpl.cs b/src/Ryujinx.HLE/HOS/Services/Mii/UtilityImpl.cs new file mode 100644 index 00000000..32dbb494 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mii/UtilityImpl.cs @@ -0,0 +1,75 @@ +using Ryujinx.Common.Utilities; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using Ryujinx.HLE.HOS.Services.Time; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using System; + +namespace Ryujinx.HLE.HOS.Services.Mii +{ + class UtilityImpl + { + private uint _x; + private uint _y; + private uint _z; + private uint _w; + + public UtilityImpl(ITickSource tickSource) + { + _x = 123456789; + _y = 362436069; + + TimeSpanType time = TimeManager.Instance.TickBasedSteadyClock.GetCurrentRawTimePoint(tickSource); + + _w = (uint)(time.NanoSeconds & uint.MaxValue); + _z = (uint)((time.NanoSeconds >> 32) & uint.MaxValue); + } + + private uint GetRandom() + { + uint t = (_x ^ (_x << 11)); + + _x = _y; + _y = _z; + _z = _w; + _w = (_w ^ (_w >> 19)) ^ (t ^ (t >> 8)); + + return _w; + } + + public int GetRandom(int end) + { + return (int)GetRandom((uint)end); + } + + public uint GetRandom(uint end) + { + uint random = GetRandom(); + + return random - random / end * end; + } + + public uint GetRandom(uint start, uint end) + { + uint random = GetRandom(); + + return random - random / (1 - start + end) * (1 - start + end) + start; + } + + public int GetRandom(int start, int end) + { + return (int)GetRandom((uint)start, (uint)end); + } + + public CreateId MakeCreateId() + { + UInt128 value = UInt128Utils.CreateRandom(); + + // Ensure the random ID generated is valid as a create id. + value &= ~new UInt128(0xC0, 0); + value |= new UInt128(0x80, 0); + + return new CreateId(value); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mnpp/IServiceForApplication.cs b/src/Ryujinx.HLE/HOS/Services/Mnpp/IServiceForApplication.cs new file mode 100644 index 00000000..8cdab641 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mnpp/IServiceForApplication.cs @@ -0,0 +1,63 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Account.Acc; + +namespace Ryujinx.HLE.HOS.Services.Mnpp +{ + [Service("mnpp:app")] // 13.0.0+ + class IServiceForApplication : IpcService + { + public IServiceForApplication(ServiceCtx context) { } + + [CommandCmif(0)] + // Initialize(pid) + public ResultCode Initialize(ServiceCtx context) + { + // Pid placeholder + context.RequestData.ReadInt64(); + ulong pid = context.Request.HandleDesc.PId; + + // TODO: Service calls set:sys GetPlatformRegion. + // If the result == 1 (China) it calls arp:r GetApplicationInstanceId and GetApplicationLaunchProperty to get the title id and store it internally. + // If not, it does nothing. + + Logger.Stub?.PrintStub(LogClass.ServiceMnpp, new { pid }); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // SendRawTelemetryData(nn::account::Uid user_id, buffer title_id) + public ResultCode SendRawTelemetryData(ServiceCtx context) + { + ulong titleIdInputPosition = context.Request.SendBuff[0].Position; + ulong titleIdInputSize = context.Request.SendBuff[0].Size; + + UserId userId = context.RequestData.ReadStruct(); + + // TODO: Service calls set:sys GetPlatformRegion. + // If the result != 1 (China) it returns ResultCode.Success. + + if (userId.IsNull) + { + return ResultCode.InvalidArgument; + } + + if (titleIdInputSize <= 64) + { + string titleId = MemoryHelper.ReadAsciiString(context.Memory, titleIdInputPosition, (long)titleIdInputSize); + + // TODO: The service stores the titleId internally and seems proceed to some telemetry for China, which is not needed here. + + Logger.Stub?.PrintStub(LogClass.ServiceMnpp, new { userId, titleId }); + + return ResultCode.Success; + } + + Logger.Stub?.PrintStub(LogClass.ServiceMnpp, new { userId }); + + return ResultCode.InvalidBufferSize; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Mnpp/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Mnpp/ResultCode.cs new file mode 100644 index 00000000..af3acf0a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Mnpp/ResultCode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Mnpp +{ + enum ResultCode + { + ModuleId = 239, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArgument = (100 << ErrorCodeShift) | ModuleId, + InvalidBufferSize = (101 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs b/src/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs new file mode 100644 index 00000000..7c3cc4cb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ncm +{ + [Service("ncm")] + class IContentManager : IpcService + { + public IContentManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs b/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs new file mode 100644 index 00000000..35e311c4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs @@ -0,0 +1,22 @@ +using LibHac.Ncm; +using Ryujinx.HLE.HOS.Services.Ncm.Lr.LocationResolverManager; + +namespace Ryujinx.HLE.HOS.Services.Ncm.Lr +{ + [Service("lr")] + class ILocationResolverManager : IpcService + { + public ILocationResolverManager(ServiceCtx context) { } + + [CommandCmif(0)] + // OpenLocationResolver() + public ResultCode OpenLocationResolver(ServiceCtx context) + { + StorageId storageId = (StorageId)context.RequestData.ReadByte(); + + MakeObject(context, new ILocationResolver(storageId)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs b/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs new file mode 100644 index 00000000..71ed5638 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs @@ -0,0 +1,257 @@ +using LibHac.Ncm; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.HLE.FileSystem; +using System.Text; +using static Ryujinx.HLE.Utilities.StringUtils; + +namespace Ryujinx.HLE.HOS.Services.Ncm.Lr.LocationResolverManager +{ + class ILocationResolver : IpcService + { + private readonly StorageId _storageId; + + public ILocationResolver(StorageId storageId) + { + _storageId = storageId; + } + + [CommandCmif(0)] + // ResolveProgramPath(u64 titleId) + public ResultCode ResolveProgramPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Program)) + { + return ResultCode.Success; + } + else + { + return ResultCode.ProgramLocationEntryNotFound; + } + } + + [CommandCmif(1)] + // RedirectProgramPath(u64 titleId) + public ResultCode RedirectProgramPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + RedirectPath(context, titleId, 0, NcaContentType.Program); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // ResolveApplicationControlPath(u64 titleId) + public ResultCode ResolveApplicationControlPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Control)) + { + return ResultCode.Success; + } + else + { + return ResultCode.AccessDenied; + } + } + + [CommandCmif(3)] + // ResolveApplicationHtmlDocumentPath(u64 titleId) + public ResultCode ResolveApplicationHtmlDocumentPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Manual)) + { + return ResultCode.Success; + } + else + { + return ResultCode.AccessDenied; + } + } + + [CommandCmif(4)] + // ResolveDataPath(u64 titleId) + public ResultCode ResolveDataPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Data) || ResolvePath(context, titleId, NcaContentType.PublicData)) + { + return ResultCode.Success; + } + else + { + return ResultCode.AccessDenied; + } + } + + [CommandCmif(5)] + // RedirectApplicationControlPath(u64 titleId) + public ResultCode RedirectApplicationControlPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + RedirectPath(context, titleId, 1, NcaContentType.Control); + + return ResultCode.Success; + } + + [CommandCmif(6)] + // RedirectApplicationHtmlDocumentPath(u64 titleId) + public ResultCode RedirectApplicationHtmlDocumentPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + RedirectPath(context, titleId, 1, NcaContentType.Manual); + + return ResultCode.Success; + } + + [CommandCmif(7)] + // ResolveApplicationLegalInformationPath(u64 titleId) + public ResultCode ResolveApplicationLegalInformationPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + if (ResolvePath(context, titleId, NcaContentType.Manual)) + { + return ResultCode.Success; + } + else + { + return ResultCode.AccessDenied; + } + } + + [CommandCmif(8)] + // RedirectApplicationLegalInformationPath(u64 titleId) + public ResultCode RedirectApplicationLegalInformationPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + RedirectPath(context, titleId, 1, NcaContentType.Manual); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // Refresh() + public ResultCode Refresh(ServiceCtx context) + { + context.Device.System.ContentManager.RefreshEntries(_storageId, 1); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // SetProgramNcaPath2(u64 titleId) + public ResultCode SetProgramNcaPath2(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + RedirectPath(context, titleId, 1, NcaContentType.Program); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // ClearLocationResolver2() + public ResultCode ClearLocationResolver2(ServiceCtx context) + { + context.Device.System.ContentManager.RefreshEntries(_storageId, 1); + + return ResultCode.Success; + } + + [CommandCmif(12)] + // DeleteProgramNcaPath(u64 titleId) + public ResultCode DeleteProgramNcaPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + DeleteContentPath(context, titleId, NcaContentType.Program); + + return ResultCode.Success; + } + + [CommandCmif(13)] + // DeleteControlNcaPath(u64 titleId) + public ResultCode DeleteControlNcaPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + DeleteContentPath(context, titleId, NcaContentType.Control); + + return ResultCode.Success; + } + + [CommandCmif(14)] + // DeleteDocHtmlNcaPath(u64 titleId) + public ResultCode DeleteDocHtmlNcaPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + DeleteContentPath(context, titleId, NcaContentType.Manual); + + return ResultCode.Success; + } + + [CommandCmif(15)] + // DeleteInfoHtmlNcaPath(u64 titleId) + public ResultCode DeleteInfoHtmlNcaPath(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + DeleteContentPath(context, titleId, NcaContentType.Manual); + + return ResultCode.Success; + } + + private void RedirectPath(ServiceCtx context, ulong titleId, int flag, NcaContentType contentType) + { + string contentPath = ReadUtf8String(context); + LocationEntry newLocation = new(contentPath, flag, titleId, contentType); + + context.Device.System.ContentManager.RedirectLocation(newLocation, _storageId); + } + + private bool ResolvePath(ServiceCtx context, ulong titleId, NcaContentType contentType) + { + ContentManager contentManager = context.Device.System.ContentManager; + string contentPath = contentManager.GetInstalledContentPath(titleId, _storageId, NcaContentType.Program); + + if (!string.IsNullOrWhiteSpace(contentPath)) + { + ulong position = context.Request.RecvListBuff[0].Position; +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong size = context.Request.RecvListBuff[0].Size; +#pragma warning restore IDE0059 + + byte[] contentPathBuffer = Encoding.UTF8.GetBytes(contentPath); + + context.Memory.Write(position, contentPathBuffer); + } + else + { + return false; + } + + return true; + } + + private void DeleteContentPath(ServiceCtx context, ulong titleId, NcaContentType contentType) + { + ContentManager contentManager = context.Device.System.ContentManager; +#pragma warning disable IDE0059 // Remove unnecessary value assignment + string contentPath = contentManager.GetInstalledContentPath(titleId, _storageId, NcaContentType.Manual); +#pragma warning restore IDE0059 + + contentManager.ClearEntry(titleId, NcaContentType.Manual, _storageId); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ResultCode.cs new file mode 100644 index 00000000..66b9217e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ResultCode.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.HLE.HOS.Services.Ncm.Lr +{ + enum ResultCode + { + ModuleId = 8, + ErrorCodeShift = 9, + + Success = 0, + + ProgramLocationEntryNotFound = (2 << ErrorCodeShift) | ModuleId, + InvalidContextForControlLocation = (3 << ErrorCodeShift) | ModuleId, + StorageNotFound = (4 << ErrorCodeShift) | ModuleId, + AccessDenied = (5 << ErrorCodeShift) | ModuleId, + OfflineManualHTMLLocationEntryNotFound = (6 << ErrorCodeShift) | ModuleId, + TitleIsNotRegistered = (7 << ErrorCodeShift) | ModuleId, + ControlLocationEntryForHostNotFound = (8 << ErrorCodeShift) | ModuleId, + LegalInfoHTMLLocationEntryNotFound = (9 << ErrorCodeShift) | ModuleId, + ProgramLocationForDebugEntryNotFound = (10 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs new file mode 100644 index 00000000..3d14629c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.News +{ + [Service("news:a")] + [Service("news:c")] + [Service("news:m")] + [Service("news:p")] + [Service("news:v")] + class IServiceCreator : IpcService + { + public IServiceCreator(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs new file mode 100644 index 00000000..770d423f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc +{ + [Service("nfc:am")] + class IAmManager : IpcService + { + public IAmManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs new file mode 100644 index 00000000..be23d2cd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Nfc.NfcManager; + +namespace Ryujinx.HLE.HOS.Services.Nfc +{ + [Service("nfc:sys")] + class ISystemManager : IpcService + { + public ISystemManager(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateSystemInterface() -> object + public ResultCode CreateSystemInterface(ServiceCtx context) + { + MakeObject(context, new INfc(NfcPermissionLevel.System)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs new file mode 100644 index 00000000..5b8afce7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Nfc.NfcManager; + +namespace Ryujinx.HLE.HOS.Services.Nfc +{ + [Service("nfc:user")] + class IUserManager : IpcService + { + public IUserManager(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateUserInterface() -> object + public ResultCode CreateUserInterface(ServiceCtx context) + { + MakeObject(context, new INfc(NfcPermissionLevel.User)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs new file mode 100644 index 00000000..295c7e71 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare +{ + [Service("nfc:mf:u")] + class IUserManager : IpcService + { + public IUserManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/INfc.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/INfc.cs new file mode 100644 index 00000000..72027cf4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/INfc.cs @@ -0,0 +1,63 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Nfc.NfcManager +{ + class INfc : IpcService + { + private readonly NfcPermissionLevel _permissionLevel; + private State _state; + + public INfc(NfcPermissionLevel permissionLevel) + { + _permissionLevel = permissionLevel; + _state = State.NonInitialized; + } + + [CommandCmif(0)] + [CommandCmif(400)] // 4.0.0+ + // Initialize(u64, u64, pid, buffer) + public ResultCode Initialize(ServiceCtx context) + { + _state = State.Initialized; + + Logger.Stub?.PrintStub(LogClass.ServiceNfc, new { _permissionLevel }); + + return ResultCode.Success; + } + + [CommandCmif(1)] + [CommandCmif(401)] // 4.0.0+ + // Finalize() + public ResultCode Finalize(ServiceCtx context) + { + _state = State.NonInitialized; + + Logger.Stub?.PrintStub(LogClass.ServiceNfc, new { _permissionLevel }); + + return ResultCode.Success; + } + + [CommandCmif(2)] + [CommandCmif(402)] // 4.0.0+ + // GetState() -> u32 + public ResultCode GetState(ServiceCtx context) + { + context.ResponseData.Write((int)_state); + + return ResultCode.Success; + } + + [CommandCmif(3)] + [CommandCmif(403)] // 4.0.0+ + // IsNfcEnabled() -> b8 + public ResultCode IsNfcEnabled(ServiceCtx context) + { + // NOTE: Write false value here could make nfp service not called. + context.ResponseData.Write(true); + + Logger.Stub?.PrintStub(LogClass.ServiceNfc, new { _permissionLevel }); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/NfcPermissionLevel.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/NfcPermissionLevel.cs new file mode 100644 index 00000000..11ed5678 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/NfcPermissionLevel.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.NfcManager +{ + enum NfcPermissionLevel + { + User, + System, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs new file mode 100644 index 00000000..cfc86dfa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.NfcManager +{ + enum State + { + NonInitialized, + Initialized, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs new file mode 100644 index 00000000..94dde5b8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs @@ -0,0 +1,10 @@ +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; +using System.Text.Json.Serialization; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + [JsonSerializable(typeof(VirtualAmiiboFile))] + internal partial class AmiiboJsonSerializerContext : JsonSerializerContext + { + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs new file mode 100644 index 00000000..b3eff0c2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + [Service("nfp:dbg")] + class IAmManager : IpcService + { + public IAmManager(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateDebugInterface() -> object + public ResultCode CreateDebugInterface(ServiceCtx context) + { + MakeObject(context, new INfp(NfpPermissionLevel.Debug)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs new file mode 100644 index 00000000..4cb541a6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + [Service("nfp:sys")] + class ISystemManager : IpcService + { + public ISystemManager(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateSystemInterface() -> object + public ResultCode CreateSystemInterface(ServiceCtx context) + { + MakeObject(context, new INfp(NfpPermissionLevel.System)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs new file mode 100644 index 00000000..229386c0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + [Service("nfp:user")] + class IUserManager : IpcService + { + public IUserManager(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateUserInterface() -> object + public ResultCode CreateUserInterface(ServiceCtx context) + { + MakeObject(context, new INfp(NfpPermissionLevel.User)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs new file mode 100644 index 00000000..20f67a4e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs @@ -0,0 +1,997 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Hid; +using Ryujinx.HLE.HOS.Services.Hid.HidServer; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; +using Ryujinx.Horizon.Common; +using System; +using System.Buffers.Binary; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + class INfp : IpcService + { +#pragma warning disable IDE0052 // Remove unread private member + private ulong _appletResourceUserId; + private ulong _mcuVersionData; +#pragma warning restore IDE0052 + private byte[] _mcuData; + + private State _state = State.NonInitialized; + + private KEvent _availabilityChangeEvent; + + private CancellationTokenSource _cancelTokenSource; + + private readonly NfpPermissionLevel _permissionLevel; + + public INfp(NfpPermissionLevel permissionLevel) + { + _permissionLevel = permissionLevel; + } + + [CommandCmif(0)] + // Initialize(u64, u64, pid, buffer) + public ResultCode Initialize(ServiceCtx context) + { + _appletResourceUserId = context.RequestData.ReadUInt64(); + _mcuVersionData = context.RequestData.ReadUInt64(); + + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + _mcuData = new byte[inputSize]; + + context.Memory.Read(inputPosition, _mcuData); + + // TODO: The mcuData buffer seems to contains entries with a size of 0x40 bytes each. Usage of the data needs to be determined. + + // TODO: Handle this in a controller class directly. + // Every functions which use the Handle call nn::hid::system::GetXcdHandleForNpadWithNfc(). + NfpDevice devicePlayer1 = new() + { + NpadIdType = NpadIdType.Player1, + Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1), + State = NfpDeviceState.Initialized, + }; + + context.Device.System.NfpDevices.Add(devicePlayer1); + + // TODO: It mounts 0x8000000000000020 save data and stores a random generate value inside. Usage of the data needs to be determined. + + _state = State.Initialized; + + return ResultCode.Success; + } + + [CommandCmif(1)] + // Finalize() + public ResultCode Finalize(ServiceCtx context) + { + if (_state == State.Initialized) + { + _cancelTokenSource?.Cancel(); + + // NOTE: All events are destroyed here. + context.Device.System.NfpDevices.Clear(); + + _state = State.NonInitialized; + } + + return ResultCode.Success; + } + + [CommandCmif(2)] + // ListDevices() -> (u32, buffer) + public ResultCode ListDevices(ServiceCtx context) + { + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + ulong outputPosition = context.Request.RecvListBuff[0].Position; + ulong outputSize = context.Request.RecvListBuff[0].Size; + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + if (CheckNfcIsEnabled() == ResultCode.Success) + { + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + context.Memory.Write(outputPosition + ((uint)i * sizeof(long)), (uint)context.Device.System.NfpDevices[i].Handle); + } + + context.ResponseData.Write(context.Device.System.NfpDevices.Count); + } + else + { + context.ResponseData.Write(0); + } + + return ResultCode.Success; + } + + [CommandCmif(3)] + // StartDetection(bytes<8, 4>) + public ResultCode StartDetection(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + context.Device.System.NfpDevices[i].State = NfpDeviceState.SearchingForTag; + + break; + } + } + + _cancelTokenSource = new CancellationTokenSource(); + + Task.Run(() => + { + while (true) + { + if (_cancelTokenSource.Token.IsCancellationRequested) + { + break; + } + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound) + { + context.Device.System.NfpDevices[i].SignalActivate(); + Thread.Sleep(125); // NOTE: Simulate amiibo scanning delay. + context.Device.System.NfpDevices[i].SignalDeactivate(); + + break; + } + } + } + }, _cancelTokenSource.Token); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // StopDetection(bytes<8, 4>) + public ResultCode StopDetection(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + _cancelTokenSource?.Cancel(); + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + context.Device.System.NfpDevices[i].State = NfpDeviceState.Initialized; + + break; + } + } + + return ResultCode.Success; + } + + [CommandCmif(5)] + // Mount(bytes<8, 4>, u32, u32) + public ResultCode Mount(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + DeviceType deviceType = (DeviceType)context.RequestData.ReadUInt32(); + MountTarget mountTarget = (MountTarget)context.RequestData.ReadUInt32(); + + if (deviceType != 0) + { + return ResultCode.WrongArgument; + } + + if (((uint)mountTarget & 3) == 0) + { + return ResultCode.WrongArgument; + } + + // TODO: Found how the MountTarget is handled. + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound) + { + // NOTE: This mount the amiibo data, which isn't needed in our case. + + context.Device.System.NfpDevices[i].State = NfpDeviceState.TagMounted; + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(6)] + // Unmount(bytes<8, 4>) + public ResultCode Unmount(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + // NOTE: This mount the amiibo data, which isn't needed in our case. + + context.Device.System.NfpDevices[i].State = NfpDeviceState.TagFound; + + resultCode = ResultCode.Success; + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(7)] + // OpenApplicationArea(bytes<8, 4>, u32) + public ResultCode OpenApplicationArea(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + uint applicationAreaId = context.RequestData.ReadUInt32(); + + bool isOpened = false; + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted) + { + isOpened = VirtualAmiibo.OpenApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationAreaId); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + if (!isOpened) + { + resultCode = ResultCode.ApplicationAreaIsNull; + } + + return resultCode; + } + + [CommandCmif(8)] + // GetApplicationArea(bytes<8, 4>) -> (u32, buffer) + public ResultCode GetApplicationArea(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + ulong outputPosition = context.Request.ReceiveBuff[0].Position; + ulong outputSize = context.Request.ReceiveBuff[0].Size; + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + uint size = 0; + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted) + { + byte[] applicationArea = VirtualAmiibo.GetApplicationArea(context.Device.System.NfpDevices[i].AmiiboId); + + context.Memory.Write(outputPosition, applicationArea); + + size = (uint)applicationArea.Length; + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + } + } + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (size == 0) + { + return ResultCode.ApplicationAreaIsNull; + } + + context.ResponseData.Write(size); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // SetApplicationArea(bytes<8, 4>, buffer) + public ResultCode SetApplicationArea(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + byte[] applicationArea = new byte[inputSize]; + + context.Memory.Read(inputPosition, applicationArea); + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted) + { + VirtualAmiibo.SetApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationArea); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(10)] + // Flush(bytes<8, 4>) + public ResultCode Flush(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); +#pragma warning restore IDE0059 + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + // NOTE: Since we handle amiibo through VirtualAmiibo, we don't have to flush anything in our case. + + return ResultCode.Success; + } + + [CommandCmif(11)] + // Restore(bytes<8, 4>) + public ResultCode Restore(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(12)] + // CreateApplicationArea(bytes<8, 4>, u32, buffer) + public ResultCode CreateApplicationArea(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + uint applicationAreaId = context.RequestData.ReadUInt32(); + + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + byte[] applicationArea = new byte[inputSize]; + + context.Memory.Read(inputPosition, applicationArea); + + bool isCreated = false; + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted) + { + isCreated = VirtualAmiibo.CreateApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationAreaId, applicationArea); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + if (!isCreated) + { + resultCode = ResultCode.ApplicationAreaIsNull; + } + + return resultCode; + } + + [CommandCmif(13)] + // GetTagInfo(bytes<8, 4>) -> buffer, 0x1a> + public ResultCode GetTagInfo(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + ulong outputPosition = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf()); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf()); + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted || context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound) + { + byte[] uuid = VirtualAmiibo.GenerateUuid(context.Device.System.NfpDevices[i].AmiiboId, context.Device.System.NfpDevices[i].UseRandomUuid); + + if (uuid.Length > AmiiboConstants.UuidMaxLength) + { + throw new InvalidOperationException($"{nameof(uuid)} is too long: {uuid.Length}"); + } + + TagInfo tagInfo = new() + { + UuidLength = (byte)uuid.Length, + Reserved1 = new Array21(), + Protocol = uint.MaxValue, // All Protocol + TagType = uint.MaxValue, // All Type + Reserved2 = new Array6(), + }; + + uuid.CopyTo(tagInfo.Uuid.AsSpan()); + + context.Memory.Write(outputPosition, tagInfo); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(14)] + // GetRegisterInfo(bytes<8, 4>) -> buffer, 0x1a> + public ResultCode GetRegisterInfo(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + ulong outputPosition = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf()); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf()); + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted) + { + RegisterInfo registerInfo = VirtualAmiibo.GetRegisterInfo( + context.Device.System.TickSource, + context.Device.System.NfpDevices[i].AmiiboId, + context.Device.System.AccountManager.LastOpenedUser.Name); + + context.Memory.Write(outputPosition, registerInfo); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(15)] + // GetCommonInfo(bytes<8, 4>) -> buffer, 0x1a> + public ResultCode GetCommonInfo(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + ulong outputPosition = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf()); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf()); + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted) + { + CommonInfo commonInfo = VirtualAmiibo.GetCommonInfo(context.Device.System.NfpDevices[i].AmiiboId); + + context.Memory.Write(outputPosition, commonInfo); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(16)] + // GetModelInfo(bytes<8, 4>) -> buffer, 0x1a> + public ResultCode GetModelInfo(ServiceCtx context) + { + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + ulong outputPosition = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf()); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf()); + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfpDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted) + { + ModelInfo modelInfo = new() + { + Reserved = new Array57(), + CharacterId = BinaryPrimitives.ReverseEndianness(ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(0, 4), NumberStyles.HexNumber)), + CharacterVariant = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(4, 2), NumberStyles.HexNumber), + Series = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(12, 2), NumberStyles.HexNumber), + ModelNumber = ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(8, 4), NumberStyles.HexNumber), + Type = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(6, 2), NumberStyles.HexNumber), + }; + + context.Memory.Write(outputPosition, modelInfo); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(17)] + // AttachActivateEvent(bytes<8, 4>) -> handle + public ResultCode AttachActivateEvent(ServiceCtx context) + { + uint deviceHandle = context.RequestData.ReadUInt32(); + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle) + { + context.Device.System.NfpDevices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfpDevices[i].ActivateEvent.ReadableEvent, out int activateEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(activateEventHandle); + + return ResultCode.Success; + } + } + + return ResultCode.DeviceNotFound; + } + + [CommandCmif(18)] + // AttachDeactivateEvent(bytes<8, 4>) -> handle + public ResultCode AttachDeactivateEvent(ServiceCtx context) + { + uint deviceHandle = context.RequestData.ReadUInt32(); + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle) + { + context.Device.System.NfpDevices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfpDevices[i].DeactivateEvent.ReadableEvent, out int deactivateEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(deactivateEventHandle); + + return ResultCode.Success; + } + } + + return ResultCode.DeviceNotFound; + } + + [CommandCmif(19)] + // GetState() -> u32 + public ResultCode GetState(ServiceCtx context) + { + context.ResponseData.Write((int)_state); + + return ResultCode.Success; + } + + [CommandCmif(20)] + // GetDeviceState(bytes<8, 4>) -> u32 + public ResultCode GetDeviceState(ServiceCtx context) + { + uint deviceHandle = context.RequestData.ReadUInt32(); + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle) + { + if (context.Device.System.NfpDevices[i].State > NfpDeviceState.Finalized) + { + throw new InvalidOperationException($"{nameof(context.Device.System.NfpDevices)} contains an invalid state for device {i}: {context.Device.System.NfpDevices[i].State}"); + } + + context.ResponseData.Write((uint)context.Device.System.NfpDevices[i].State); + + return ResultCode.Success; + } + } + + context.ResponseData.Write((uint)NfpDeviceState.Unavailable); + + return ResultCode.DeviceNotFound; + } + + [CommandCmif(21)] + // GetNpadId(bytes<8, 4>) -> u32 + public ResultCode GetNpadId(ServiceCtx context) + { + uint deviceHandle = context.RequestData.ReadUInt32(); + + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle) + { + context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(context.Device.System.NfpDevices[i].Handle)); + + return ResultCode.Success; + } + } + + return ResultCode.DeviceNotFound; + } + + [CommandCmif(22)] + // GetApplicationAreaSize() -> u32 + public ResultCode GetApplicationAreaSize(ServiceCtx context) + { + context.ResponseData.Write(AmiiboConstants.ApplicationAreaSize); + + return ResultCode.Success; + } + + [CommandCmif(23)] // 3.0.0+ + // AttachAvailabilityChangeEvent() -> handle + public ResultCode AttachAvailabilityChangeEvent(ServiceCtx context) + { + _availabilityChangeEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out int availabilityChangeEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(availabilityChangeEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(24)] // 3.0.0+ + // RecreateApplicationArea(bytes<8, 4>, u32, buffer) + public ResultCode RecreateApplicationArea(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(102)] + // GetRegisterInfo2(bytes<8, 4>) -> buffer, 0x1a> + public ResultCode GetRegisterInfo2(ServiceCtx context) + { + // TODO: Find the differencies between IUser and ISystem/IDebug. + + if (_permissionLevel == NfpPermissionLevel.Debug || _permissionLevel == NfpPermissionLevel.System) + { + return GetRegisterInfo(context); + } + + return ResultCode.DeviceNotFound; + } + + private ResultCode CheckNfcIsEnabled() + { + // TODO: Call nn::settings::detail::GetNfcEnableFlag when it will be implemented. + return true ? ResultCode.Success : ResultCode.NfcDisabled; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/AmiiboConstants.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/AmiiboConstants.cs new file mode 100644 index 00000000..9139ab0b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/AmiiboConstants.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + static class AmiiboConstants + { + public const int UuidMaxLength = 10; + public const int ApplicationAreaSize = 0xD8; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/CommonInfo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/CommonInfo.cs new file mode 100644 index 00000000..80bf4077 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/CommonInfo.cs @@ -0,0 +1,17 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40)] + struct CommonInfo + { + public ushort LastWriteYear; + public byte LastWriteMonth; + public byte LastWriteDay; + public ushort WriteCounter; + public ushort Version; + public uint ApplicationAreaSize; + public Array52 Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/DeviceType.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/DeviceType.cs new file mode 100644 index 00000000..f75c2f16 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/DeviceType.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + enum DeviceType : uint + { + Amiibo, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/ModelInfo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/ModelInfo.cs new file mode 100644 index 00000000..cddd8707 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/ModelInfo.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40)] + struct ModelInfo + { + public ushort CharacterId; + public byte CharacterVariant; + public byte Series; + public ushort ModelNumber; + public byte Type; + public Array57 Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/MountTarget.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/MountTarget.cs new file mode 100644 index 00000000..8e5da97b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/MountTarget.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + enum MountTarget : uint + { + Rom = 1, + Ram = 2, + All = 3, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDevice.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDevice.cs new file mode 100644 index 00000000..3320238a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDevice.cs @@ -0,0 +1,23 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Hid; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + class NfpDevice + { + public KEvent ActivateEvent; + public KEvent DeactivateEvent; + + public void SignalActivate() => ActivateEvent.ReadableEvent.Signal(); + public void SignalDeactivate() => DeactivateEvent.ReadableEvent.Signal(); + + public NfpDeviceState State = NfpDeviceState.Unavailable; + + public PlayerIndex Handle; + public NpadIdType NpadIdType; + + public string AmiiboId; + + public bool UseRandomUuid; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDeviceState.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDeviceState.cs new file mode 100644 index 00000000..9e92e848 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDeviceState.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + enum NfpDeviceState + { + Initialized = 0, + SearchingForTag = 1, + TagFound = 2, + TagRemoved = 3, + TagMounted = 4, + Unavailable = 5, + Finalized = 6, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpPermissionLevel.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpPermissionLevel.cs new file mode 100644 index 00000000..9c440c45 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpPermissionLevel.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + enum NfpPermissionLevel + { + Debug, + User, + System, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/RegisterInfo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/RegisterInfo.cs new file mode 100644 index 00000000..424b0193 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/RegisterInfo.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x100)] + struct RegisterInfo + { + public CharInfo MiiCharInfo; + public ushort FirstWriteYear; + public byte FirstWriteMonth; + public byte FirstWriteDay; + public Array41 Nickname; + public byte FontRegion; + public Array64 Reserved1; + public Array58 Reserved2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/State.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/State.cs new file mode 100644 index 00000000..71a85b44 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/State.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + enum State + { + NonInitialized = 0, + Initialized = 1, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/TagInfo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/TagInfo.cs new file mode 100644 index 00000000..d6d20600 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/TagInfo.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x58)] + struct TagInfo + { + public Array10 Uuid; + public byte UuidLength; + public Array21 Reserved1; + public uint Protocol; + public uint TagType; + public Array6 Reserved2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs new file mode 100644 index 00000000..65d38097 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager +{ + struct VirtualAmiiboFile + { + public uint FileVersion { get; set; } + public byte[] TagUuid { get; set; } + public string AmiiboId { get; set; } + public DateTime FirstWriteDate { get; set; } + public DateTime LastWriteDate { get; set; } + public ushort WriteCounter { get; set; } + public List ApplicationAreas { get; set; } + } + + struct VirtualAmiiboApplicationArea + { + public uint ApplicationAreaId { get; set; } + public byte[] ApplicationArea { get; set; } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs new file mode 100644 index 00000000..92184fb4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + public enum ResultCode + { + ModuleId = 115, + ErrorCodeShift = 9, + + Success = 0, + + DeviceNotFound = (64 << ErrorCodeShift) | ModuleId, + WrongArgument = (65 << ErrorCodeShift) | ModuleId, + WrongDeviceState = (73 << ErrorCodeShift) | ModuleId, + NfcDisabled = (80 << ErrorCodeShift) | ModuleId, + TagNotFound = (97 << ErrorCodeShift) | ModuleId, + ApplicationAreaIsNull = (128 << ErrorCodeShift) | ModuleId, + ApplicationAreaAlreadyCreated = (168 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs new file mode 100644 index 00000000..ba4a81e0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs @@ -0,0 +1,203 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Mii; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + static class VirtualAmiibo + { + private static uint _openedApplicationAreaId; + + private static readonly AmiiboJsonSerializerContext _serializerContext = AmiiboJsonSerializerContext.Default; + + public static byte[] GenerateUuid(string amiiboId, bool useRandomUuid) + { + if (useRandomUuid) + { + return GenerateRandomUuid(); + } + + VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); + + if (virtualAmiiboFile.TagUuid.Length == 0) + { + virtualAmiiboFile.TagUuid = GenerateRandomUuid(); + + SaveAmiiboFile(virtualAmiiboFile); + } + + return virtualAmiiboFile.TagUuid; + } + + private static byte[] GenerateRandomUuid() + { + byte[] uuid = new byte[9]; + + Random.Shared.NextBytes(uuid); + + uuid[3] = (byte)(0x88 ^ uuid[0] ^ uuid[1] ^ uuid[2]); + uuid[8] = (byte)(uuid[3] ^ uuid[4] ^ uuid[5] ^ uuid[6]); + + return uuid; + } + + public static CommonInfo GetCommonInfo(string amiiboId) + { + VirtualAmiiboFile amiiboFile = LoadAmiiboFile(amiiboId); + + return new CommonInfo() + { + LastWriteYear = (ushort)amiiboFile.LastWriteDate.Year, + LastWriteMonth = (byte)amiiboFile.LastWriteDate.Month, + LastWriteDay = (byte)amiiboFile.LastWriteDate.Day, + WriteCounter = amiiboFile.WriteCounter, + Version = 1, + ApplicationAreaSize = AmiiboConstants.ApplicationAreaSize, + Reserved = new Array52(), + }; + } + + public static RegisterInfo GetRegisterInfo(ITickSource tickSource, string amiiboId, string nickname) + { + VirtualAmiiboFile amiiboFile = LoadAmiiboFile(amiiboId); + + UtilityImpl utilityImpl = new(tickSource); + CharInfo charInfo = new(); + + charInfo.SetFromStoreData(StoreData.BuildDefault(utilityImpl, 0)); + + charInfo.Nickname = Nickname.FromString(nickname); + + RegisterInfo registerInfo = new() + { + MiiCharInfo = charInfo, + FirstWriteYear = (ushort)amiiboFile.FirstWriteDate.Year, + FirstWriteMonth = (byte)amiiboFile.FirstWriteDate.Month, + FirstWriteDay = (byte)amiiboFile.FirstWriteDate.Day, + FontRegion = 0, + Reserved1 = new Array64(), + Reserved2 = new Array58(), + }; + "Ryujinx"u8.CopyTo(registerInfo.Nickname.AsSpan()); + + return registerInfo; + } + + public static bool OpenApplicationArea(string amiiboId, uint applicationAreaId) + { + VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); + + if (virtualAmiiboFile.ApplicationAreas.Exists(item => item.ApplicationAreaId == applicationAreaId)) + { + _openedApplicationAreaId = applicationAreaId; + + return true; + } + + return false; + } + + public static byte[] GetApplicationArea(string amiiboId) + { + VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); + + foreach (VirtualAmiiboApplicationArea applicationArea in virtualAmiiboFile.ApplicationAreas) + { + if (applicationArea.ApplicationAreaId == _openedApplicationAreaId) + { + return applicationArea.ApplicationArea; + } + } + + return Array.Empty(); + } + + public static bool CreateApplicationArea(string amiiboId, uint applicationAreaId, byte[] applicationAreaData) + { + VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); + + if (virtualAmiiboFile.ApplicationAreas.Exists(item => item.ApplicationAreaId == applicationAreaId)) + { + return false; + } + + virtualAmiiboFile.ApplicationAreas.Add(new VirtualAmiiboApplicationArea() + { + ApplicationAreaId = applicationAreaId, + ApplicationArea = applicationAreaData, + }); + + SaveAmiiboFile(virtualAmiiboFile); + + return true; + } + + public static void SetApplicationArea(string amiiboId, byte[] applicationAreaData) + { + VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); + + if (virtualAmiiboFile.ApplicationAreas.Exists(item => item.ApplicationAreaId == _openedApplicationAreaId)) + { + for (int i = 0; i < virtualAmiiboFile.ApplicationAreas.Count; i++) + { + if (virtualAmiiboFile.ApplicationAreas[i].ApplicationAreaId == _openedApplicationAreaId) + { + virtualAmiiboFile.ApplicationAreas[i] = new VirtualAmiiboApplicationArea() + { + ApplicationAreaId = _openedApplicationAreaId, + ApplicationArea = applicationAreaData, + }; + + break; + } + } + + SaveAmiiboFile(virtualAmiiboFile); + } + } + + private static VirtualAmiiboFile LoadAmiiboFile(string amiiboId) + { + Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo")); + + string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{amiiboId}.json"); + + VirtualAmiiboFile virtualAmiiboFile; + + if (File.Exists(filePath)) + { + virtualAmiiboFile = JsonHelper.DeserializeFromFile(filePath, _serializerContext.VirtualAmiiboFile); + } + else + { + virtualAmiiboFile = new VirtualAmiiboFile() + { + FileVersion = 0, + TagUuid = Array.Empty(), + AmiiboId = amiiboId, + FirstWriteDate = DateTime.Now, + LastWriteDate = DateTime.Now, + WriteCounter = 0, + ApplicationAreas = new List(), + }; + + SaveAmiiboFile(virtualAmiiboFile); + } + + return virtualAmiiboFile; + } + + private static void SaveAmiiboFile(VirtualAmiiboFile virtualAmiiboFile) + { + string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json"); + JsonHelper.SerializeToFile(filePath, virtualAmiiboFile, _serializerContext.VirtualAmiiboFile); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ngct/IService.cs b/src/Ryujinx.HLE/HOS/Services/Ngct/IService.cs new file mode 100644 index 00000000..d9ca7004 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ngct/IService.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Ngct +{ + [Service("ngct:u")] // 9.0.0+ + class IService : IpcService + { + public IService(ServiceCtx context) { } + + [CommandCmif(0)] + // Match(buffer) -> b8 + public ResultCode Match(ServiceCtx context) + { + return NgctServer.Match(context); + } + + [CommandCmif(1)] + // Filter(buffer) -> buffer + public ResultCode Filter(ServiceCtx context) + { + return NgctServer.Filter(context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ngct/IServiceWithManagementApi.cs b/src/Ryujinx.HLE/HOS/Services/Ngct/IServiceWithManagementApi.cs new file mode 100644 index 00000000..88995335 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ngct/IServiceWithManagementApi.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Ngct +{ + [Service("ngct:s")] // 9.0.0+ + class IServiceWithManagementApi : IpcService + { + public IServiceWithManagementApi(ServiceCtx context) { } + + [CommandCmif(0)] + // Match(buffer) -> b8 + public ResultCode Match(ServiceCtx context) + { + return NgctServer.Match(context); + } + + [CommandCmif(1)] + // Filter(buffer) -> buffer + public ResultCode Filter(ServiceCtx context) + { + return NgctServer.Filter(context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ngct/NgctServer.cs b/src/Ryujinx.HLE/HOS/Services/Ngct/NgctServer.cs new file mode 100644 index 00000000..f652ecda --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ngct/NgctServer.cs @@ -0,0 +1,92 @@ +using Ryujinx.Common.Logging; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Ngct +{ + static class NgctServer + { + public static ResultCode Match(ServiceCtx context) + { + // NOTE: Service load the values of sys:set ngc.t!functionality_override_enabled and ngc.t!auto_reload_enabled in internal fields. + // Then it checks if ngc.t!functionality_override_enabled is enabled and if sys:set GetT is == 2. + // If both conditions are true, it does this following code. Since we currently stub it, it's fine to don't check settings service values. + + ulong bufferPosition = context.Request.PtrBuff[0].Position; + ulong bufferSize = context.Request.PtrBuff[0].Size; + + bool isMatch = false; + string text = ""; + + if (bufferSize != 0) + { + if (bufferSize > 1024) + { + isMatch = true; + } + else + { + byte[] buffer = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, buffer); + + text = Encoding.ASCII.GetString(buffer); + + // NOTE: Ngct use the archive 0100000000001034 which contains a words table. This is pushed on Chinese Switchs using Bcat service. + // This call check if the string match with entries in the table and return the result if there is one (or more). + // Since we don't want to hide bad words. It's fine to returns false here. + + isMatch = false; + } + } + + Logger.Stub?.PrintStub(LogClass.ServiceNgct, new { isMatch, text }); + + context.ResponseData.Write(isMatch); + + return ResultCode.Success; + } + + public static ResultCode Filter(ServiceCtx context) + { + // NOTE: Service load the values of sys:set ngc.t!functionality_override_enabled and ngc.t!auto_reload_enabled in internal fields. + // Then it checks if ngc.t!functionality_override_enabled is enabled and if sys:set GetT is == 2. + // If both conditions are true, it does this following code. Since we currently stub it, it's fine to don't check settings service values. + + ulong bufferPosition = context.Request.PtrBuff[0].Position; + ulong bufferSize = context.Request.PtrBuff[0].Size; + + ulong bufferFilteredPosition = context.Request.RecvListBuff[0].Position; + + string text = ""; + string textFiltered = ""; + + if (bufferSize != 0) + { + if (bufferSize > 1024) + { + textFiltered = new string('*', text.Length); + + context.Memory.Write(bufferFilteredPosition, Encoding.ASCII.GetBytes(textFiltered)); + } + else + { + byte[] buffer = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, buffer); + + // NOTE: Ngct use the archive 0100000000001034 which contains a words table. This is pushed on Chinese Switchs using Bcat service. + // This call check if the string contains words which are in the table then returns the same string with each matched words replaced by '*'. + // Since we don't want to hide bad words. It's fine to returns the same string. + + textFiltered = text = Encoding.ASCII.GetString(buffer); + + context.Memory.Write(bufferFilteredPosition, buffer); + } + } + + Logger.Stub?.PrintStub(LogClass.ServiceNgct, new { text, textFiltered }); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs new file mode 100644 index 00000000..96e71cd0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs @@ -0,0 +1,30 @@ +using Ryujinx.HLE.HOS.Services.Nifm.StaticService; + +namespace Ryujinx.HLE.HOS.Services.Nifm +{ + [Service("nifm:a")] // Max sessions: 2 + [Service("nifm:s")] // Max sessions: 16 + [Service("nifm:u")] // Max sessions: 5 + class IStaticService : IpcService + { + public IStaticService(ServiceCtx context) { } + + [CommandCmif(4)] + // CreateGeneralServiceOld() -> object + public ResultCode CreateGeneralServiceOld(ServiceCtx context) + { + MakeObject(context, new IGeneralService()); + + return ResultCode.Success; + } + + [CommandCmif(5)] // 3.0.0+ + // CreateGeneralService(u64, pid) -> object + public ResultCode CreateGeneralService(ServiceCtx context) + { + MakeObject(context, new IGeneralService()); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/ResultCode.cs new file mode 100644 index 00000000..91a3d0af --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/ResultCode.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Nifm +{ + enum ResultCode + { + ModuleId = 110, + ErrorCodeShift = 9, + + Success = 0, + + Unknown112 = (112 << ErrorCodeShift) | ModuleId, // IRequest::GetResult + Unknown180 = (180 << ErrorCodeShift) | ModuleId, // IRequest::GetAppletInfo + NoInternetConnection = (300 << ErrorCodeShift) | ModuleId, + ObjectIsNull = (350 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/GeneralServiceManager.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/GeneralServiceManager.cs new file mode 100644 index 00000000..16eefc87 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/GeneralServiceManager.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.GeneralService +{ + static class GeneralServiceManager + { + private static readonly List _generalServices = new(); + + public static int Count + { + get => _generalServices.Count; + } + + public static void Add(GeneralServiceDetail generalServiceDetail) + { + _generalServices.Add(generalServiceDetail); + } + + public static void Remove(int index) + { + _generalServices.RemoveAt(index); + } + + public static GeneralServiceDetail Get(int clientId) + { + return _generalServices.First(item => item.ClientId == clientId); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/Types/GeneralServiceDetail.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/Types/GeneralServiceDetail.cs new file mode 100644 index 00000000..73853a83 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/Types/GeneralServiceDetail.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.GeneralService +{ + class GeneralServiceDetail + { + public int ClientId; + public bool IsAnyInternetRequestAccepted; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs new file mode 100644 index 00000000..581a2906 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs @@ -0,0 +1,205 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS.Services.Nifm.StaticService.GeneralService; +using Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types; +using System; +using System.Net.NetworkInformation; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService +{ + class IGeneralService : DisposableIpcService + { + private readonly GeneralServiceDetail _generalServiceDetail; + + private IPInterfaceProperties _targetPropertiesCache = null; + private UnicastIPAddressInformation _targetAddressInfoCache = null; + private string _cacheChosenInterface = null; + + public IGeneralService() + { + _generalServiceDetail = new GeneralServiceDetail + { + ClientId = GeneralServiceManager.Count, + IsAnyInternetRequestAccepted = true, // NOTE: Why not accept any internet request? + }; + + NetworkChange.NetworkAddressChanged += LocalInterfaceCacheHandler; + + GeneralServiceManager.Add(_generalServiceDetail); + } + + [CommandCmif(1)] + // GetClientId() -> buffer + public ResultCode GetClientId(ServiceCtx context) + { + ulong position = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(sizeof(int)); + + context.Memory.Write(position, _generalServiceDetail.ClientId); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // CreateRequest(u32 version) -> object + public ResultCode CreateRequest(ServiceCtx context) + { + uint version = context.RequestData.ReadUInt32(); + + MakeObject(context, new IRequest(context.Device.System, version)); + + // Doesn't occur in our case. + // return ResultCode.ObjectIsNull; + + Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { version }); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // GetCurrentNetworkProfile() -> buffer + public ResultCode GetCurrentNetworkProfile(ServiceCtx context) + { + ulong networkProfileDataPosition = context.Request.RecvListBuff[0].Position; + + (IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context); + + if (interfaceProperties == null || unicastAddress == null) + { + return ResultCode.NoInternetConnection; + } + + Logger.Info?.Print(LogClass.ServiceNifm, $"Console's local IP is \"{unicastAddress.Address}\"."); + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Unsafe.SizeOf()); + + NetworkProfileData networkProfile = new() + { + Uuid = UInt128Utils.CreateRandom(), + }; + + networkProfile.IpSettingData.IpAddressSetting = new IpAddressSetting(interfaceProperties, unicastAddress); + networkProfile.IpSettingData.DnsSetting = new DnsSetting(interfaceProperties); + + "RyujinxNetwork"u8.CopyTo(networkProfile.Name.AsSpan()); + + context.Memory.Write(networkProfileDataPosition, networkProfile); + + return ResultCode.Success; + } + + [CommandCmif(12)] + // GetCurrentIpAddress() -> nn::nifm::IpV4Address + public ResultCode GetCurrentIpAddress(ServiceCtx context) + { + (_, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context); + + if (unicastAddress == null) + { + return ResultCode.NoInternetConnection; + } + + context.ResponseData.WriteStruct(new IpV4Address(unicastAddress.Address)); + + Logger.Info?.Print(LogClass.ServiceNifm, $"Console's local IP is \"{unicastAddress.Address}\"."); + + return ResultCode.Success; + } + + [CommandCmif(15)] + // GetCurrentIpConfigInfo() -> (nn::nifm::IpAddressSetting, nn::nifm::DnsSetting) + public ResultCode GetCurrentIpConfigInfo(ServiceCtx context) + { + (IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context); + + if (interfaceProperties == null || unicastAddress == null) + { + return ResultCode.NoInternetConnection; + } + + Logger.Info?.Print(LogClass.ServiceNifm, $"Console's local IP is \"{unicastAddress.Address}\"."); + + context.ResponseData.WriteStruct(new IpAddressSetting(interfaceProperties, unicastAddress)); + context.ResponseData.WriteStruct(new DnsSetting(interfaceProperties)); + + return ResultCode.Success; + } + + [CommandCmif(18)] + // GetInternetConnectionStatus() -> nn::nifm::detail::sf::InternetConnectionStatus + public ResultCode GetInternetConnectionStatus(ServiceCtx context) + { + if (!NetworkInterface.GetIsNetworkAvailable()) + { + return ResultCode.NoInternetConnection; + } + + InternetConnectionStatus internetConnectionStatus = new() + { + Type = InternetConnectionType.WiFi, + WifiStrength = 3, + State = InternetConnectionState.Connected, + }; + + context.ResponseData.WriteStruct(internetConnectionStatus); + + return ResultCode.Success; + } + + [CommandCmif(21)] + // IsAnyInternetRequestAccepted(buffer) -> bool + public ResultCode IsAnyInternetRequestAccepted(ServiceCtx context) + { + ulong position = context.Request.PtrBuff[0].Position; +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong size = context.Request.PtrBuff[0].Size; +#pragma warning restore IDE0059 + + int clientId = context.Memory.Read(position); + + context.ResponseData.Write(GeneralServiceManager.Get(clientId).IsAnyInternetRequestAccepted); + + return ResultCode.Success; + } + + private (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(ServiceCtx context) + { + if (!NetworkInterface.GetIsNetworkAvailable()) + { + return (null, null); + } + + string chosenInterface = context.Device.Configuration.MultiplayerLanInterfaceId; + + if (_targetPropertiesCache == null || _targetAddressInfoCache == null || _cacheChosenInterface != chosenInterface) + { + _cacheChosenInterface = chosenInterface; + + (_targetPropertiesCache, _targetAddressInfoCache) = NetworkHelpers.GetLocalInterface(chosenInterface); + } + + return (_targetPropertiesCache, _targetAddressInfoCache); + } + + private void LocalInterfaceCacheHandler(object sender, EventArgs e) + { + Logger.Info?.Print(LogClass.ServiceNifm, "NetworkAddress changed, invalidating cached data."); + + _targetPropertiesCache = null; + _targetAddressInfoCache = null; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + NetworkChange.NetworkAddressChanged -= LocalInterfaceCacheHandler; + + GeneralServiceManager.Remove(_generalServiceDetail.ClientId); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IRequest.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IRequest.cs new file mode 100644 index 00000000..577d0382 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IRequest.cs @@ -0,0 +1,146 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService +{ + class IRequest : IpcService + { + private enum RequestState + { + Error = 1, + OnHold = 2, + Available = 3, + } + + private readonly KEvent _event0; + private readonly KEvent _event1; + + private int _event0Handle; + private int _event1Handle; + +#pragma warning disable IDE0052 // Remove unread private member + private readonly uint _version; +#pragma warning restore IDE0052 + + public IRequest(Horizon system, uint version) + { + _event0 = new KEvent(system.KernelContext); + _event1 = new KEvent(system.KernelContext); + + _version = version; + } + + [CommandCmif(0)] + // GetRequestState() -> u32 + public ResultCode GetRequestState(ServiceCtx context) + { + RequestState requestState = context.Device.Configuration.EnableInternetAccess + ? RequestState.Available + : RequestState.Error; + + context.ResponseData.Write((int)requestState); + + Logger.Stub?.PrintStub(LogClass.ServiceNifm); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetResult() + public ResultCode GetResult(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNifm); + + return GetResultImpl(); + } + + private ResultCode GetResultImpl() + { + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetSystemEventReadableHandles() -> (handle, handle) + public ResultCode GetSystemEventReadableHandles(ServiceCtx context) + { + if (_event0Handle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_event0.ReadableEvent, out _event0Handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (_event1Handle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_event1.ReadableEvent, out _event1Handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_event0Handle, _event1Handle); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // Cancel() + public ResultCode Cancel(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNifm); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // Submit() + public ResultCode Submit(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNifm); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // SetConnectionConfirmationOption(i8) + public ResultCode SetConnectionConfirmationOption(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNifm); + + return ResultCode.Success; + } + + [CommandCmif(21)] + // GetAppletInfo(u32) -> (u32, u32, u32, buffer) + public ResultCode GetAppletInfo(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + uint themeColor = context.RequestData.ReadUInt32(); +#pragma warning restore IDE0059 + + Logger.Stub?.PrintStub(LogClass.ServiceNifm); + + ResultCode result = GetResultImpl(); + + if (result == ResultCode.Success || (ResultCode)((int)result & 0x3fffff) == ResultCode.Unknown112) + { + return ResultCode.Unknown180; + } + + // Returns appletId, libraryAppletMode, outSize and a buffer. + // Returned applet ids- (0x19, 0xf, 0xe) + // libraryAppletMode seems to be 0 for all applets supported. + + // TODO: check order + context.ResponseData.Write(0xe); // Use error applet as default for now + context.ResponseData.Write(0); // libraryAppletMode + context.ResponseData.Write(0); // outSize + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/DnsSetting.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/DnsSetting.cs new file mode 100644 index 00000000..af80db48 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/DnsSetting.cs @@ -0,0 +1,31 @@ +using System; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 9)] + struct DnsSetting + { + [MarshalAs(UnmanagedType.U1)] + public bool IsDynamicDnsEnabled; + public IpV4Address PrimaryDns; + public IpV4Address SecondaryDns; + + public DnsSetting(IPInterfaceProperties interfaceProperties) + { + IsDynamicDnsEnabled = OperatingSystem.IsWindows() && interfaceProperties.IsDynamicDnsEnabled; + + if (interfaceProperties.DnsAddresses.Count == 0) + { + PrimaryDns = new IpV4Address(); + SecondaryDns = new IpV4Address(); + } + else + { + PrimaryDns = new IpV4Address(interfaceProperties.DnsAddresses[0]); + SecondaryDns = new IpV4Address(interfaceProperties.DnsAddresses[interfaceProperties.DnsAddresses.Count > 1 ? 1 : 0]); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionState.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionState.cs new file mode 100644 index 00000000..ba8efd19 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionState.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + enum InternetConnectionState : byte + { + ConnectingType0 = 0, + ConnectingType1 = 1, + ConnectingType2 = 2, + ConnectingType3 = 3, + Connected = 4, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionStatus.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionStatus.cs new file mode 100644 index 00000000..fa780fd2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionStatus.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct InternetConnectionStatus + { + public InternetConnectionType Type; + public byte WifiStrength; + public InternetConnectionState State; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionType.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionType.cs new file mode 100644 index 00000000..4917b8f8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + enum InternetConnectionType : byte + { + Invalid = 0, + WiFi = 1, + Ethernet = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpAddressSetting.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpAddressSetting.cs new file mode 100644 index 00000000..4fa674de --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpAddressSetting.cs @@ -0,0 +1,24 @@ +using System; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0xd)] + struct IpAddressSetting + { + [MarshalAs(UnmanagedType.U1)] + public bool IsDhcpEnabled; + public IpV4Address Address; + public IpV4Address IPv4Mask; + public IpV4Address GatewayAddress; + + public IpAddressSetting(IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastIPAddressInformation) + { + IsDhcpEnabled = OperatingSystem.IsMacOS() || interfaceProperties.DhcpServerAddresses.Count != 0; + Address = new IpV4Address(unicastIPAddressInformation.Address); + IPv4Mask = new IpV4Address(unicastIPAddressInformation.IPv4Mask); + GatewayAddress = (interfaceProperties.GatewayAddresses.Count == 0) ? new IpV4Address() : new IpV4Address(interfaceProperties.GatewayAddresses[0].Address); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpSettingData.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpSettingData.cs new file mode 100644 index 00000000..c966999b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpSettingData.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0xc2)] + struct IpSettingData + { + public IpAddressSetting IpAddressSetting; + public DnsSetting DnsSetting; + public ProxySetting ProxySetting; + public short Mtu; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpV4Address.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpV4Address.cs new file mode 100644 index 00000000..9d902a1a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpV4Address.cs @@ -0,0 +1,24 @@ +using System; +using System.Net; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct IpV4Address + { + public uint Address; + + public IpV4Address(IPAddress address) + { + if (address == null) + { + Address = 0; + } + else + { + Address = BitConverter.ToUInt32(address.GetAddressBytes()); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/NetworkProfileData.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/NetworkProfileData.cs new file mode 100644 index 00000000..fe69fef1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/NetworkProfileData.cs @@ -0,0 +1,17 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x17C)] + struct NetworkProfileData + { + public IpSettingData IpSettingData; + public UInt128 Uuid; + public Array64 Name; + public Array4 Unknown; + public WirelessSettingData WirelessSettingData; + public byte Padding; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/ProxySetting.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/ProxySetting.cs new file mode 100644 index 00000000..4d872d34 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/ProxySetting.cs @@ -0,0 +1,27 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0xaa)] + public struct ProxySetting + { + [MarshalAs(UnmanagedType.I1)] + public bool Enabled; + private readonly byte _padding; + public short Port; + private NameStruct _name; + [MarshalAs(UnmanagedType.I1)] + public bool AutoAuthEnabled; + public Array32 User; + public Array32 Pass; + private readonly byte _padding2; + + [StructLayout(LayoutKind.Sequential, Size = 0x64)] + private struct NameStruct { } + + public Span Name => SpanHelpers.AsSpan(ref _name); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/WirelessSettingData.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/WirelessSettingData.cs new file mode 100644 index 00000000..4bbc69ec --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/WirelessSettingData.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x65)] + struct WirelessSettingData + { + public byte SsidLength; + public Array32 Ssid; + public Array3 Unknown; + public Array64 Passphrase1; + public byte Passphrase2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/INetworkInstallManager.cs b/src/Ryujinx.HLE/HOS/Services/Nim/INetworkInstallManager.cs new file mode 100644 index 00000000..4e62b97b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/INetworkInstallManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nim +{ + [Service("nim")] + class INetworkInstallManager : IpcService + { + public INetworkInstallManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServer.cs b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServer.cs new file mode 100644 index 00000000..4deecc53 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServer.cs @@ -0,0 +1,21 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer; + +namespace Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface +{ + class IShopServiceAccessServer : IpcService + { + public IShopServiceAccessServer() { } + + [CommandCmif(0)] + // CreateAccessorInterface(u8) -> object + public ResultCode CreateAccessorInterface(ServiceCtx context) + { + MakeObject(context, new IShopServiceAccessor(context.Device.System)); + + Logger.Stub?.PrintStub(LogClass.ServiceNim); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs new file mode 100644 index 00000000..d7e276ea --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs @@ -0,0 +1,51 @@ +using LibHac.Ncm; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Arp; +using Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface; + +namespace Ryujinx.HLE.HOS.Services.Nim +{ + [Service("nim:eca")] // 5.0.0+ + class IShopServiceAccessServerInterface : IpcService + { + public IShopServiceAccessServerInterface(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateServerInterface(pid, handle, u64) -> object + public ResultCode CreateServerInterface(ServiceCtx context) + { + // Close transfer memory immediately as we don't use it. + context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); + + MakeObject(context, new IShopServiceAccessServer()); + + Logger.Stub?.PrintStub(LogClass.ServiceNim); + + return ResultCode.Success; + } + + [CommandCmif(4)] // 10.0.0+ + // IsLargeResourceAvailable(pid) -> b8 + public ResultCode IsLargeResourceAvailable(ServiceCtx context) + { + // TODO: Service calls arp:r GetApplicationInstanceId (10.0.0+) then if it fails it calls arp:r GetMicroApplicationInstanceId (10.0.0+) + // then if it fails it returns the arp:r result code. + + // NOTE: Firmare 10.0.0+ don't use the Pid here anymore, but the returned InstanceId. We don't support that for now so we can just use the Pid instead. + StorageId baseStorageId = (StorageId)ApplicationLaunchProperty.GetByPid(context).BaseGameStorageId; + + // NOTE: Service returns ResultCode.InvalidArgument if baseStorageId is null, doesn't occur in our case. + + context.ResponseData.Write(baseStorageId == StorageId.Host); + + return ResultCode.Success; + } + + [CommandCmif(5)] // 17.0.0+ + // CreateServerInterface2(pid, handle, u64) -> object + public ResultCode CreateServerInterface2(ServiceCtx context) + { + return CreateServerInterface(context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessSystemInterface.cs b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessSystemInterface.cs new file mode 100644 index 00000000..dfec2efd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessSystemInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nim +{ + [Service("nim:ecas")] // 7.0.0+ + class IShopServiceAccessSystemInterface : IpcService + { + public IShopServiceAccessSystemInterface(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessor.cs new file mode 100644 index 00000000..c1f58a07 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessor.cs @@ -0,0 +1,42 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer.ShopServiceAccessor; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer +{ + class IShopServiceAccessor : IpcService + { + private readonly KEvent _event; + + private int _eventHandle; + + public IShopServiceAccessor(Horizon system) + { + _event = new KEvent(system.KernelContext); + } + + [CommandCmif(0)] + // CreateAsyncInterface(u64) -> (handle, object) + public ResultCode CreateAsyncInterface(ServiceCtx context) + { + MakeObject(context, new IShopServiceAsync()); + + if (_eventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_event.ReadableEvent, out _eventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_eventHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceNim); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAsync.cs b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAsync.cs new file mode 100644 index 00000000..225b9a70 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAsync.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer.ShopServiceAccessor +{ + class IShopServiceAsync : IpcService + { + public IShopServiceAsync() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceManager.cs b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceManager.cs new file mode 100644 index 00000000..8f2e0703 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nim +{ + [Service("nim:shp")] + class IShopServiceManager : IpcService + { + public IShopServiceManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs b/src/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs new file mode 100644 index 00000000..ed6e4472 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs @@ -0,0 +1,24 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Nim.Ntc.StaticService; + +namespace Ryujinx.HLE.HOS.Services.Nim.Ntc +{ + [Service("ntc")] + class IStaticService : IpcService + { + public IStaticService(ServiceCtx context) { } + + [CommandCmif(0)] + // OpenEnsureNetworkClockAvailabilityService(u64) -> object + public ResultCode CreateAsyncInterface(ServiceCtx context) + { + ulong unknown = context.RequestData.ReadUInt64(); + + MakeObject(context, new IEnsureNetworkClockAvailabilityService(context)); + + Logger.Stub?.PrintStub(LogClass.ServiceNtc, new { unknown }); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs b/src/Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs new file mode 100644 index 00000000..2bd6f4f0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs @@ -0,0 +1,77 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Nim.Ntc.StaticService +{ + class IEnsureNetworkClockAvailabilityService : IpcService + { + private readonly KEvent _finishNotificationEvent; + private ResultCode _taskResultCode; + + public IEnsureNetworkClockAvailabilityService(ServiceCtx context) + { + _finishNotificationEvent = new KEvent(context.Device.System.KernelContext); + _taskResultCode = ResultCode.Success; + + // NOTE: The service starts a thread that polls Nintendo NTP server and syncs the time with it. + // Additionnally it gets and uses some settings too: + // autonomic_correction_interval_seconds, autonomic_correction_failed_retry_interval_seconds, + // autonomic_correction_immediate_try_count_max, autonomic_correction_immediate_try_interval_milliseconds + } + + [CommandCmif(0)] + // StartTask() + public ResultCode StartTask(ServiceCtx context) + { + if (!context.Device.Configuration.EnableInternetAccess) + { + return (ResultCode)Time.ResultCode.NetworkTimeNotAvailable; + } + + // NOTE: Since we don't support the Nintendo NTP server, we can signal the event now to confirm the update task is done. + _finishNotificationEvent.ReadableEvent.Signal(); + + Logger.Stub?.PrintStub(LogClass.ServiceNtc); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetFinishNotificationEvent() -> handle + public ResultCode GetFinishNotificationEvent(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_finishNotificationEvent.ReadableEvent, out int finishNotificationEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(finishNotificationEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetResult() + public ResultCode GetResult(ServiceCtx context) + { + return _taskResultCode; + } + + [CommandCmif(3)] + // Cancel() + public ResultCode Cancel(ServiceCtx context) + { + // NOTE: The update task should be canceled here. + _finishNotificationEvent.ReadableEvent.Signal(); + + _taskResultCode = (ResultCode)Time.ResultCode.NetworkTimeTaskCanceled; + + Logger.Stub?.PrintStub(LogClass.ServiceNtc); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nim/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Nim/ResultCode.cs new file mode 100644 index 00000000..3fcb9024 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nim/ResultCode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Nim +{ + enum ResultCode + { + ModuleId = 137, + ErrorCodeShift = 9, + + Success = 0, + + NullArgument = (90 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs new file mode 100644 index 00000000..29f8bfa8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Notification +{ + [Service("notif:a")] // 9.0.0+ + class INotificationServicesForApplication : IpcService + { + public INotificationServicesForApplication(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs new file mode 100644 index 00000000..c5946be8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Notification +{ + [Service("notif:s")] // 9.0.0+ + class INotificationServicesForSystem : IpcService + { + public INotificationServicesForSystem(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs b/src/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs new file mode 100644 index 00000000..dc707e6f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Npns +{ + [Service("npns:s")] + class INpnsSystem : IpcService + { + public INpnsSystem(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Npns/INpnsUser.cs b/src/Ryujinx.HLE/HOS/Services/Npns/INpnsUser.cs new file mode 100644 index 00000000..9a794709 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Npns/INpnsUser.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Npns +{ + [Service("npns:u")] + class INpnsUser : IpcService + { + public INpnsUser(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs new file mode 100644 index 00000000..083a8321 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs @@ -0,0 +1,362 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Ns.Aoc +{ + [Service("aoc:u")] + class IAddOnContentManager : IpcService + { + private readonly KEvent _addOnContentListChangedEvent; + private int _addOnContentListChangedEventHandle; + + private ulong _addOnContentBaseId; + + private readonly List _mountedAocTitleIds = new(); + + public IAddOnContentManager(ServiceCtx context) + { + _addOnContentListChangedEvent = new KEvent(context.Device.System.KernelContext); + } + + [CommandCmif(0)] // 1.0.0-6.2.0 + // CountAddOnContentByApplicationId(u64 title_id) -> u32 + public ResultCode CountAddOnContentByApplicationId(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + return CountAddOnContentImpl(context, titleId); + } + + [CommandCmif(1)] // 1.0.0-6.2.0 + // ListAddOnContentByApplicationId(u64 title_id, u32 start_index, u32 buffer_size) -> (u32 count, buffer) + public ResultCode ListAddOnContentByApplicationId(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + return ListAddContentImpl(context, titleId); + } + + [CommandCmif(2)] + // CountAddOnContent(pid) -> u32 + public ResultCode CountAddOnContent(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong pid = context.Request.HandleDesc.PId; +#pragma warning restore IDE0059 + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + + return CountAddOnContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId); + } + + [CommandCmif(3)] + // ListAddOnContent(u32 start_index, u32 buffer_size, pid) -> (u32 count, buffer) + public ResultCode ListAddOnContent(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong pid = context.Request.HandleDesc.PId; +#pragma warning restore IDE0059 + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + + return ListAddContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId); + } + + [CommandCmif(4)] // 1.0.0-6.2.0 + // GetAddOnContentBaseIdByApplicationId(u64 title_id) -> u64 + public ResultCode GetAddOnContentBaseIdByApplicationId(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + return GetAddOnContentBaseIdImpl(context, titleId); + } + + [CommandCmif(5)] + // GetAddOnContentBaseId(pid) -> u64 + public ResultCode GetAddOnContentBaseId(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong pid = context.Request.HandleDesc.PId; +#pragma warning restore IDE0059 + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + + return GetAddOnContentBaseIdImpl(context, context.Device.Processes.ActiveApplication.ProgramId); + } + + [CommandCmif(6)] // 1.0.0-6.2.0 + // PrepareAddOnContentByApplicationId(u64 title_id, u32 index) + public ResultCode PrepareAddOnContentByApplicationId(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + return PrepareAddOnContentImpl(context, titleId); + } + + [CommandCmif(7)] + // PrepareAddOnContent(u32 index, pid) + public ResultCode PrepareAddOnContent(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong pid = context.Request.HandleDesc.PId; +#pragma warning restore IDE0059 + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + + return PrepareAddOnContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId); + } + + [CommandCmif(8)] // 4.0.0+ + // GetAddOnContentListChangedEvent() -> handle + public ResultCode GetAddOnContentListChangedEvent(ServiceCtx context) + { + return GetAddOnContentListChangedEventImpl(context); + } + + [CommandCmif(9)] // 10.0.0+ + // GetAddOnContentLostErrorCode() -> u64 + public ResultCode GetAddOnContentLostErrorCode(ServiceCtx context) + { + // NOTE: 0x7D0A4 -> 2164-1000 + context.ResponseData.Write(GetAddOnContentLostErrorCodeImpl(0x7D0A4)); + + return ResultCode.Success; + } + + [CommandCmif(10)] // 11.0.0+ + // GetAddOnContentListChangedEventWithProcessId(pid) -> handle + public ResultCode GetAddOnContentListChangedEventWithProcessId(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong pid = context.Request.HandleDesc.PId; +#pragma warning restore IDE0059 + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + + // TODO: Found where stored value is used. + ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.ActiveApplication.ProgramId); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + return GetAddOnContentListChangedEventImpl(context); + } + + [CommandCmif(11)] // 13.0.0+ + // NotifyMountAddOnContent(pid, u64 title_id) + public ResultCode NotifyMountAddOnContent(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong pid = context.Request.HandleDesc.PId; +#pragma warning restore IDE0059 + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + + ulong aocTitleId = context.RequestData.ReadUInt64(); + + if (_mountedAocTitleIds.Count <= 0x7F) + { + _mountedAocTitleIds.Add(aocTitleId); + } + + return ResultCode.Success; + } + + [CommandCmif(12)] // 13.0.0+ + // NotifyUnmountAddOnContent(pid, u64 title_id) + public ResultCode NotifyUnmountAddOnContent(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong pid = context.Request.HandleDesc.PId; +#pragma warning restore IDE0059 + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + + ulong aocTitleId = context.RequestData.ReadUInt64(); + + _mountedAocTitleIds.Remove(aocTitleId); + + return ResultCode.Success; + } + + [CommandCmif(50)] // 13.0.0+ + // CheckAddOnContentMountStatus(pid) + public ResultCode CheckAddOnContentMountStatus(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong pid = context.Request.HandleDesc.PId; +#pragma warning restore IDE0059 + + // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. + // Then it does some internal checks and returns InvalidBufferSize if they fail. + + Logger.Stub?.PrintStub(LogClass.ServiceNs); + + return ResultCode.Success; + } + + [CommandCmif(100)] // 7.0.0+ + // CreateEcPurchasedEventManager() -> object + public ResultCode CreateEcPurchasedEventManager(ServiceCtx context) + { + MakeObject(context, new IPurchaseEventManager(context.Device.System)); + + return ResultCode.Success; + } + + [CommandCmif(101)] // 9.0.0+ + // CreatePermanentEcPurchasedEventManager() -> object + public ResultCode CreatePermanentEcPurchasedEventManager(ServiceCtx context) + { + // NOTE: Service call arp:r to get the TitleId, do some extra checks and pass it to returned interface. + + MakeObject(context, new IPurchaseEventManager(context.Device.System)); + + return ResultCode.Success; + } + + [CommandCmif(110)] // 12.0.0+ + // CreateContentsServiceManager() -> object + public ResultCode CreateContentsServiceManager(ServiceCtx context) + { + MakeObject(context, new IContentsServiceManager()); + + return ResultCode.Success; + } + + private ResultCode CountAddOnContentImpl(ServiceCtx context, ulong titleId) + { + // NOTE: Service call sys:set GetQuestFlag and store it internally. + // If QuestFlag is true, counts some extra titles. + + ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, titleId); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + // TODO: This should use _addOnContentBaseId; + uint aocCount = (uint)context.Device.System.ContentManager.GetAocCount(); + + context.ResponseData.Write(aocCount); + + return ResultCode.Success; + } + + private ResultCode ListAddContentImpl(ServiceCtx context, ulong titleId) + { + // NOTE: Service call sys:set GetQuestFlag and store it internally. + // If QuestFlag is true, counts some extra titles. + + uint startIndex = context.RequestData.ReadUInt32(); + uint indexNumber = context.RequestData.ReadUInt32(); + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; + + // TODO: This should use _addOnContentBaseId; + uint aocTotalCount = (uint)context.Device.System.ContentManager.GetAocCount(); + + if (indexNumber > bufferSize / sizeof(uint)) + { + return ResultCode.InvalidBufferSize; + } + + if (aocTotalCount <= startIndex) + { + context.ResponseData.Write(0); + + return ResultCode.Success; + } + + IList aocTitleIds = context.Device.System.ContentManager.GetAocTitleIds(); + + GetAddOnContentBaseIdFromTitleId(context, titleId); + + uint indexCounter = 0; + + for (int i = 0; i < indexNumber; i++) + { + if (i + (int)startIndex < aocTitleIds.Count) + { + context.Memory.Write(bufferPosition + (ulong)i * sizeof(uint), (uint)(aocTitleIds[i + (int)startIndex] - _addOnContentBaseId)); + + indexCounter++; + } + } + + context.ResponseData.Write(indexCounter); + + return ResultCode.Success; + } + + private ResultCode GetAddOnContentBaseIdImpl(ServiceCtx context, ulong titleId) + { + ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, titleId); + + context.ResponseData.Write(_addOnContentBaseId); + + return resultCode; + } + + private ResultCode GetAddOnContentBaseIdFromTitleId(ServiceCtx context, ulong titleId) + { + // NOTE: Service calls arp:r GetApplicationControlProperty to get AddOnContentBaseId using TitleId, + // If the call fails, it returns ResultCode.InvalidPid. + + _addOnContentBaseId = context.Device.Processes.ActiveApplication.ApplicationControlProperties.AddOnContentBaseId; + + if (_addOnContentBaseId == 0) + { + _addOnContentBaseId = titleId + 0x1000; + } + + return ResultCode.Success; + } + + private ResultCode PrepareAddOnContentImpl(ServiceCtx context, ulong titleId) + { + uint index = context.RequestData.ReadUInt32(); + + ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.ActiveApplication.ProgramId); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + // TODO: Service calls ns:am RegisterContentsExternalKey?, GetOwnedApplicationContentMetaStatus? etc... + // Ideally, this should probably initialize the AocData values for the specified index + + Logger.Stub?.PrintStub(LogClass.ServiceNs, new { index }); + + return ResultCode.Success; + } + + private ResultCode GetAddOnContentListChangedEventImpl(ServiceCtx context) + { + if (_addOnContentListChangedEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_addOnContentListChangedEvent.ReadableEvent, out _addOnContentListChangedEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_addOnContentListChangedEventHandle); + + return ResultCode.Success; + } + + private static ulong GetAddOnContentLostErrorCodeImpl(int errorCode) + { + return ((ulong)errorCode & 0x1FF | ((((ulong)errorCode >> 9) & 0x1FFF) << 32)) + 2000; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IContentsServiceManager.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IContentsServiceManager.cs new file mode 100644 index 00000000..40b0b2a8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IContentsServiceManager.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Ns.Aoc +{ + class IContentsServiceManager : IpcService + { + public IContentsServiceManager() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IPurchaseEventManager.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IPurchaseEventManager.cs new file mode 100644 index 00000000..c92a10d6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IPurchaseEventManager.cs @@ -0,0 +1,68 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Ns.Aoc +{ + class IPurchaseEventManager : IpcService + { + private readonly KEvent _purchasedEvent; + + public IPurchaseEventManager(Horizon system) + { + _purchasedEvent = new KEvent(system.KernelContext); + } + + [CommandCmif(0)] + // SetDefaultDeliveryTarget(pid, buffer unknown) + public ResultCode SetDefaultDeliveryTarget(ServiceCtx context) + { + ulong inBufferPosition = context.Request.SendBuff[0].Position; + ulong inBufferSize = context.Request.SendBuff[0].Size; + byte[] buffer = new byte[inBufferSize]; + + context.Memory.Read(inBufferPosition, buffer); + + // NOTE: Service uses the pid to call arp:r GetApplicationLaunchProperty and store it in internal field. + // Then it seems to use the buffer content and compare it with a stored linked instrusive list. + // Since we don't support purchase from eShop, we can stub it. + + Logger.Stub?.PrintStub(LogClass.ServiceNs); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetPurchasedEventReadableHandle() -> handle + public ResultCode GetPurchasedEventReadableHandle(ServiceCtx context) + { + if (context.Process.HandleTable.GenerateHandle(_purchasedEvent.ReadableEvent, out int purchasedEventReadableHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(purchasedEventReadableHandle); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // PopPurchasedProductInfo(nn::ec::detail::PurchasedProductInfo) + public ResultCode PopPurchasedProductInfo(ServiceCtx context) + { + byte[] purchasedProductInfo = new byte[0x80]; + + context.ResponseData.Write(purchasedProductInfo); + + // NOTE: Service finds info using internal array then convert it into nn::ec::detail::PurchasedProductInfo. + // Returns 0x320A4 if the internal array size is null. + // Since we don't support purchase from eShop, we can stub it. + + Logger.Debug?.PrintStub(LogClass.ServiceNs); // NOTE: Uses Debug to avoid spamming. + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/ResultCode.cs new file mode 100644 index 00000000..b795a756 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/Aoc/ResultCode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Ns.Aoc +{ + enum ResultCode + { + ModuleId = 166, + ErrorCodeShift = 9, + + Success = 0, + + InvalidBufferSize = (200 << ErrorCodeShift) | ModuleId, + InvalidPid = (300 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs new file mode 100644 index 00000000..f510da59 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs @@ -0,0 +1,29 @@ +using LibHac.Ns; +using Ryujinx.Common.Utilities; + +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:am")] + class IApplicationManagerInterface : IpcService + { + public IApplicationManagerInterface(ServiceCtx context) { } + + [CommandCmif(400)] + // GetApplicationControlData(u8, u64) -> (unknown<4>, buffer) + public ResultCode GetApplicationControlData(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + byte source = (byte)context.RequestData.ReadInt64(); + ulong titleId = context.RequestData.ReadUInt64(); +#pragma warning restore IDE0059 + + ulong position = context.Request.ReceiveBuff[0].Position; + + ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; + + context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray()); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IDevelopInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IDevelopInterface.cs new file mode 100644 index 00000000..8f0f6cbe --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/IDevelopInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:dev")] + class IDevelopInterface : IpcService + { + public IDevelopInterface(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs new file mode 100644 index 00000000..ca7d42b4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs @@ -0,0 +1,28 @@ +using LibHac.Common; +using LibHac.Ns; + +namespace Ryujinx.HLE.HOS.Services.Ns +{ + class IReadOnlyApplicationControlDataInterface : IpcService + { + public IReadOnlyApplicationControlDataInterface(ServiceCtx context) { } + + [CommandCmif(0)] + // GetApplicationControlData(u8, u64) -> (unknown<4>, buffer) + public ResultCode GetApplicationControlData(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + byte source = (byte)context.RequestData.ReadInt64(); + ulong titleId = context.RequestData.ReadUInt64(); +#pragma warning restore IDE0059 + + ulong position = context.Request.ReceiveBuff[0].Position; + + ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; + + context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray()); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs new file mode 100644 index 00000000..e45c6750 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:am2")] + [Service("ns:ec")] + [Service("ns:rid")] + [Service("ns:rt")] + [Service("ns:web")] + class IServiceGetterInterface : IpcService + { + public IServiceGetterInterface(ServiceCtx context) { } + + [CommandCmif(7996)] + // GetApplicationManagerInterface() -> object + public ResultCode GetApplicationManagerInterface(ServiceCtx context) + { + MakeObject(context, new IApplicationManagerInterface(context)); + + return ResultCode.Success; + } + + [CommandCmif(7989)] + // GetReadOnlyApplicationControlDataInterface() -> object + public ResultCode GetReadOnlyApplicationControlDataInterface(ServiceCtx context) + { + MakeObject(context, new IReadOnlyApplicationControlDataInterface(context)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs new file mode 100644 index 00000000..1108778c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:su")] + class ISystemUpdateInterface : IpcService + { + public ISystemUpdateInterface(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs new file mode 100644 index 00000000..c1ec50bc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ns +{ + [Service("ns:vm")] + class IVulnerabilityManagerInterface : IpcService + { + public IVulnerabilityManagerInterface(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs new file mode 100644 index 00000000..7c7ebf22 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs @@ -0,0 +1,32 @@ +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Host1x; +using Ryujinx.Graphics.Nvdec; +using Ryujinx.Graphics.Vic; +using System; +using GpuContext = Ryujinx.Graphics.Gpu.GpuContext; + +namespace Ryujinx.HLE.HOS.Services.Nv +{ + class Host1xContext : IDisposable + { + public DeviceMemoryManager Smmu { get; } + public NvMemoryAllocator MemoryAllocator { get; } + public Host1xDevice Host1x { get; } + + public Host1xContext(GpuContext gpu, ulong pid) + { + MemoryAllocator = new NvMemoryAllocator(); + Host1x = new Host1xDevice(gpu.Synchronization); + Smmu = gpu.CreateDeviceMemoryManager(pid); + var nvdec = new NvdecDevice(Smmu); + var vic = new VicDevice(Smmu); + Host1x.RegisterDevice(ClassId.Nvdec, nvdec); + Host1x.RegisterDevice(ClassId.Vic, vic); + } + + public void Dispose() + { + Host1x.Dispose(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs new file mode 100644 index 00000000..a00de31e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvdrvdbg")] + class INvDrvDebugFSServices : IpcService + { + public INvDrvDebugFSServices(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs new file mode 100644 index 00000000..a0df66d0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs @@ -0,0 +1,580 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostDbgGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostProfGpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvdrv")] + [Service("nvdrv:a")] + [Service("nvdrv:s")] + [Service("nvdrv:t")] + class INvDrvServices : IpcService + { + private static readonly List _deviceFileDebugRegistry = new() + { + "/dev/nvhost-dbg-gpu", + "/dev/nvhost-prof-gpu", + }; + + private static readonly Dictionary _deviceFileRegistry = new() + { + { "/dev/nvmap", typeof(NvMapDeviceFile) }, + { "/dev/nvhost-ctrl", typeof(NvHostCtrlDeviceFile) }, + { "/dev/nvhost-ctrl-gpu", typeof(NvHostCtrlGpuDeviceFile) }, + { "/dev/nvhost-as-gpu", typeof(NvHostAsGpuDeviceFile) }, + { "/dev/nvhost-gpu", typeof(NvHostGpuDeviceFile) }, + //{ "/dev/nvhost-msenc", typeof(NvHostChannelDeviceFile) }, + { "/dev/nvhost-nvdec", typeof(NvHostChannelDeviceFile) }, + //{ "/dev/nvhost-nvjpg", typeof(NvHostChannelDeviceFile) }, + { "/dev/nvhost-vic", typeof(NvHostChannelDeviceFile) }, + //{ "/dev/nvhost-display", typeof(NvHostChannelDeviceFile) }, + { "/dev/nvhost-dbg-gpu", typeof(NvHostDbgGpuDeviceFile) }, + { "/dev/nvhost-prof-gpu", typeof(NvHostProfGpuDeviceFile) }, + }; + + public static IdDictionary DeviceFileIdRegistry = new(); + + private IVirtualMemoryManager _clientMemory; + private ulong _owner; + + private bool _transferMemInitialized = false; + + // TODO: This should call set:sys::GetDebugModeFlag + private readonly bool _debugModeEnabled = false; + + public INvDrvServices(ServiceCtx context) : base(context.Device.System.NvDrvServer) + { + _owner = 0; + } + + private NvResult Open(ServiceCtx context, string path, out int fd) + { + fd = -1; + + if (!_debugModeEnabled && _deviceFileDebugRegistry.Contains(path)) + { + return NvResult.NotSupported; + } + + if (_deviceFileRegistry.TryGetValue(path, out Type deviceFileClass)) + { + ConstructorInfo constructor = deviceFileClass.GetConstructor(new[] { typeof(ServiceCtx), typeof(IVirtualMemoryManager), typeof(ulong) }); + + NvDeviceFile deviceFile = (NvDeviceFile)constructor.Invoke(new object[] { context, _clientMemory, _owner }); + + deviceFile.Path = path; + + fd = DeviceFileIdRegistry.Add(deviceFile); + + return NvResult.Success; + } + + Logger.Warning?.Print(LogClass.ServiceNv, $"Cannot find file device \"{path}\"!"); + + return NvResult.FileOperationFailed; + } + + private NvResult GetIoctlArgument(ServiceCtx context, NvIoctl ioctlCommand, out Span arguments) + { + (ulong inputDataPosition, ulong inputDataSize) = context.Request.GetBufferType0x21(0); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + (ulong outputDataPosition, ulong outputDataSize) = context.Request.GetBufferType0x22(0); +#pragma warning restore IDE0059 + + NvIoctl.Direction ioctlDirection = ioctlCommand.DirectionValue; + uint ioctlSize = ioctlCommand.Size; + + bool isRead = (ioctlDirection & NvIoctl.Direction.Read) != 0; + bool isWrite = (ioctlDirection & NvIoctl.Direction.Write) != 0; + + if ((isWrite && ioctlSize > outputDataSize) || (isRead && ioctlSize > inputDataSize)) + { + arguments = null; + + Logger.Warning?.Print(LogClass.ServiceNv, "Ioctl size inconsistency found!"); + + return NvResult.InvalidSize; + } + + if (isRead && isWrite) + { + if (outputDataSize < inputDataSize) + { + arguments = null; + + Logger.Warning?.Print(LogClass.ServiceNv, "Ioctl size inconsistency found!"); + + return NvResult.InvalidSize; + } + + byte[] outputData = new byte[outputDataSize]; + + byte[] temp = new byte[inputDataSize]; + + context.Memory.Read(inputDataPosition, temp); + + Buffer.BlockCopy(temp, 0, outputData, 0, temp.Length); + + arguments = new Span(outputData); + } + else if (isWrite) + { + byte[] outputData = new byte[outputDataSize]; + + arguments = new Span(outputData); + } + else + { + byte[] temp = new byte[inputDataSize]; + + context.Memory.Read(inputDataPosition, temp); + + arguments = new Span(temp); + } + + return NvResult.Success; + } + + private NvResult GetDeviceFileFromFd(int fd, out NvDeviceFile deviceFile) + { + deviceFile = null; + + if (fd < 0) + { + return NvResult.InvalidParameter; + } + + deviceFile = DeviceFileIdRegistry.GetData(fd); + + if (deviceFile == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid file descriptor {fd}"); + + return NvResult.NotImplemented; + } + + if (deviceFile.Owner != _owner) + { + return NvResult.AccessDenied; + } + + return NvResult.Success; + } + + private NvResult EnsureInitialized() + { + if (_owner == 0) + { + Logger.Warning?.Print(LogClass.ServiceNv, "INvDrvServices is not initialized!"); + + return NvResult.NotInitialized; + } + + return NvResult.Success; + } + + private NvResult ConvertInternalErrorCode(NvInternalResult errorCode) + { + return errorCode switch + { + NvInternalResult.Success => NvResult.Success, + NvInternalResult.Unknown0x72 => NvResult.AlreadyAllocated, + NvInternalResult.TimedOut or NvInternalResult.TryAgain or NvInternalResult.Interrupted => NvResult.Timeout, + NvInternalResult.InvalidAddress => NvResult.InvalidAddress, + NvInternalResult.NotSupported or NvInternalResult.Unknown0x18 => NvResult.NotSupported, + NvInternalResult.InvalidState => NvResult.InvalidState, + NvInternalResult.ReadOnlyAttribute => NvResult.ReadOnlyAttribute, + NvInternalResult.NoSpaceLeft or NvInternalResult.FileTooBig => NvResult.InvalidSize, + NvInternalResult.FileTableOverflow or NvInternalResult.BadFileNumber => NvResult.FileOperationFailed, + NvInternalResult.InvalidInput => NvResult.InvalidValue, + NvInternalResult.NotADirectory => NvResult.DirectoryOperationFailed, + NvInternalResult.Busy => NvResult.Busy, + NvInternalResult.BadAddress => NvResult.InvalidAddress, + NvInternalResult.AccessDenied or NvInternalResult.OperationNotPermitted => NvResult.AccessDenied, + NvInternalResult.OutOfMemory => NvResult.InsufficientMemory, + NvInternalResult.DeviceNotFound => NvResult.ModuleNotPresent, + NvInternalResult.IoError => NvResult.ResourceError, + _ => NvResult.IoctlFailed, + }; + } + + [CommandCmif(0)] + // Open(buffer path) -> (s32 fd, u32 error_code) + public ResultCode Open(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + int fd = -1; + + if (errorCode == NvResult.Success) + { + ulong pathPtr = context.Request.SendBuff[0].Position; + ulong pathSize = context.Request.SendBuff[0].Size; + + string path = MemoryHelper.ReadAsciiString(context.Memory, pathPtr, (long)pathSize); + + errorCode = Open(context, path, out fd); + } + + context.ResponseData.Write(fd); + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // Ioctl(s32 fd, u32 ioctl_cmd, buffer in_args) -> (u32 error_code, buffer out_args) + public ResultCode Ioctl(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + NvIoctl ioctlCommand = context.RequestData.ReadStruct(); + + errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments); + + if (errorCode == NvResult.Success) + { + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.Ioctl(ioctlCommand, arguments); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) + { + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + } + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // Close(s32 fd) -> u32 error_code + public ResultCode Close(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + deviceFile.Close(); + + DeviceFileIdRegistry.Delete(fd); + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // Initialize(u32 transfer_memory_size, handle current_process, handle transfer_memory) -> u32 error_code + public ResultCode Initialize(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long transferMemSize = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + int transferMemHandle = context.Request.HandleDesc.ToCopy[1]; + + // TODO: When transfer memory will be implemented, this could be removed. + _transferMemInitialized = true; + + int clientHandle = context.Request.HandleDesc.ToCopy[0]; + + _clientMemory = context.Process.HandleTable.GetKProcess(clientHandle).CpuMemory; + + context.Device.System.KernelContext.Syscall.GetProcessId(out _owner, clientHandle); + + context.ResponseData.Write((uint)NvResult.Success); + + // Close the process and transfer memory handles immediately as we don't use them. + context.Device.System.KernelContext.Syscall.CloseHandle(clientHandle); + context.Device.System.KernelContext.Syscall.CloseHandle(transferMemHandle); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // QueryEvent(s32 fd, u32 event_id) -> (u32, handle) + public ResultCode QueryEvent(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + uint eventId = context.RequestData.ReadUInt32(); + + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.QueryEvent(out int eventHandle, eventId); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvQueryEventNotImplementedException(context, deviceFile, eventId); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if (errorCode == NvResult.Success) + { + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(eventHandle); + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // MapSharedMemory(s32 fd, u32 argument, handle) -> u32 error_code + public ResultCode MapSharedMemory(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + uint argument = context.RequestData.ReadUInt32(); + int sharedMemoryHandle = context.Request.HandleDesc.ToCopy[0]; + + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + errorCode = ConvertInternalErrorCode(deviceFile.MapSharedMemory(sharedMemoryHandle, argument)); + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(6)] + // GetStatus() -> (unknown<0x20>, u32 error_code) + public ResultCode GetStatus(ServiceCtx context) + { + // TODO: When transfer memory will be implemented, check if it's mapped instead. + if (_transferMemInitialized) + { + // TODO: Populate values when more RE will be done. + NvStatus nvStatus = new() + { + MemoryValue1 = 0, // GetMemStats(transfer_memory + 0x60, 3) + MemoryValue2 = 0, // GetMemStats(transfer_memory + 0x60, 5) + MemoryValue3 = 0, // transfer_memory + 0x78 + MemoryValue4 = 0, // transfer_memory + 0x80 + }; + + context.ResponseData.WriteStruct(nvStatus); + context.ResponseData.Write((uint)NvResult.Success); + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + } + else + { + context.ResponseData.Write((uint)NvResult.NotInitialized); + } + + return ResultCode.Success; + } + + [CommandCmif(7)] + // ForceSetClientPid(u64) -> u32 error_code + public ResultCode ForceSetClientPid(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(8)] + // SetClientPID(u64, pid) -> u32 error_code + public ResultCode SetClientPid(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long pid = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + + context.ResponseData.Write(0); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // DumpGraphicsMemoryInfo() + public ResultCode DumpGraphicsMemoryInfo(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return ResultCode.Success; + } + + [CommandCmif(10)] // 3.0.0+ + // InitializeDevtools(u32, handle) -> u32 error_code; + public ResultCode InitializeDevtools(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(11)] // 3.0.0+ + // Ioctl2(s32 fd, u32 ioctl_cmd, buffer in_args, buffer inline_in_buffer) -> (u32 error_code, buffer out_args) + public ResultCode Ioctl2(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + NvIoctl ioctlCommand = context.RequestData.ReadStruct(); + + (ulong inlineInBufferPosition, ulong inlineInBufferSize) = context.Request.GetBufferType0x21(1); + + errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments); + + byte[] temp = new byte[inlineInBufferSize]; + + context.Memory.Read(inlineInBufferPosition, temp); + + Span inlineInBuffer = new(temp); + + if (errorCode == NvResult.Success) + { + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBuffer); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) + { + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + } + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(12)] // 3.0.0+ + // Ioctl3(s32 fd, u32 ioctl_cmd, buffer in_args) -> (u32 error_code, buffer out_args, buffer inline_out_buffer) + public ResultCode Ioctl3(ServiceCtx context) + { + NvResult errorCode = EnsureInitialized(); + + if (errorCode == NvResult.Success) + { + int fd = context.RequestData.ReadInt32(); + NvIoctl ioctlCommand = context.RequestData.ReadStruct(); + + (ulong inlineOutBufferPosition, ulong inlineOutBufferSize) = context.Request.GetBufferType0x22(1); + + errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments); + + byte[] temp = new byte[inlineOutBufferSize]; + + context.Memory.Read(inlineOutBufferPosition, temp); + + Span inlineOutBuffer = new(temp); + + if (errorCode == NvResult.Success) + { + errorCode = GetDeviceFileFromFd(fd, out NvDeviceFile deviceFile); + + if (errorCode == NvResult.Success) + { + NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBuffer); + + if (internalResult == NvInternalResult.NotImplemented) + { + throw new NvIoctlNotImplementedException(context, deviceFile, ioctlCommand); + } + + errorCode = ConvertInternalErrorCode(internalResult); + + if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) + { + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + context.Memory.Write(inlineOutBufferPosition, inlineOutBuffer.ToArray()); + } + } + } + } + + context.ResponseData.Write((uint)errorCode); + + return ResultCode.Success; + } + + [CommandCmif(13)] // 3.0.0+ + // FinishInitialize(unknown<8>) + public ResultCode FinishInitialize(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return ResultCode.Success; + } + + public static void Destroy() + { + NvHostChannelDeviceFile.Destroy(); + + foreach (object entry in DeviceFileIdRegistry.Values) + { + NvDeviceFile deviceFile = (NvDeviceFile)entry; + + deviceFile.Close(); + } + + DeviceFileIdRegistry.Clear(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs new file mode 100644 index 00000000..9cb2c7e3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvgem:c")] + class INvGemControl : IpcService + { + public INvGemControl(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs new file mode 100644 index 00000000..e79e5900 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [Service("nvgem:cd")] + class INvGemCoreDump : IpcService + { + public INvGemCoreDump(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs new file mode 100644 index 00000000..ef0bac45 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs @@ -0,0 +1,94 @@ +using Ryujinx.Common.Logging; +using System; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices +{ + abstract class NvDeviceFile + { + public readonly ServiceCtx Context; + public readonly ulong Owner; + + public string Path; + + public NvDeviceFile(ServiceCtx context, ulong owner) + { + Context = context; + Owner = owner; + } + + public virtual NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + eventHandle = 0; + + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult MapSharedMemory(int sharedMemoryHandle, uint argument) + { + // Close shared memory immediately as we don't use it. + Context.Device.System.KernelContext.Syscall.CloseHandle(sharedMemoryHandle); + + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult Ioctl(NvIoctl command, Span arguments) + { + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult Ioctl2(NvIoctl command, Span arguments, Span inlineInBuffer) + { + return NvInternalResult.NotImplemented; + } + + public virtual NvInternalResult Ioctl3(NvIoctl command, Span arguments, Span inlineOutBuffer) + { + return NvInternalResult.NotImplemented; + } + + protected delegate NvInternalResult IoctlProcessor(ref T arguments); + protected delegate NvInternalResult IoctlProcessorSpan(Span arguments); + protected delegate NvInternalResult IoctlProcessorInline(ref T arguments, ref T1 inlineData); + protected delegate NvInternalResult IoctlProcessorInlineSpan(ref T arguments, Span inlineData); + + private static NvInternalResult PrintResult(MethodInfo info, NvInternalResult result) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"{info.Name} returned result {result}"); + + return result; + } + + protected static NvInternalResult CallIoctlMethod(IoctlProcessor callback, Span arguments) where T : struct + { + Debug.Assert(arguments.Length == Unsafe.SizeOf()); + + return PrintResult(callback.Method, callback(ref MemoryMarshal.Cast(arguments)[0])); + } + + protected static NvInternalResult CallIoctlMethod(IoctlProcessorInline callback, Span arguments, Span inlineBuffer) where T : struct where T1 : struct + { + Debug.Assert(arguments.Length == Unsafe.SizeOf()); + Debug.Assert(inlineBuffer.Length == Unsafe.SizeOf()); + + return PrintResult(callback.Method, callback(ref MemoryMarshal.Cast(arguments)[0], ref MemoryMarshal.Cast(inlineBuffer)[0])); + } + + protected static NvInternalResult CallIoctlMethod(IoctlProcessorSpan callback, Span arguments) where T : struct + { + return PrintResult(callback.Method, callback(MemoryMarshal.Cast(arguments))); + } + + protected static NvInternalResult CallIoctlMethod(IoctlProcessorInlineSpan callback, Span arguments, Span inlineBuffer) where T : struct where T1 : struct + { + Debug.Assert(arguments.Length == Unsafe.SizeOf()); + + return PrintResult(callback.Method, callback(ref MemoryMarshal.Cast(arguments)[0], MemoryMarshal.Cast(inlineBuffer))); + } + + public abstract void Close(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs new file mode 100644 index 00000000..ff9a6764 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs @@ -0,0 +1,400 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu +{ + class NvHostAsGpuDeviceFile : NvDeviceFile + { + private const uint SmallPageSize = 0x1000; + private const uint BigPageSize = 0x10000; + + private static readonly uint[] _pageSizes = { SmallPageSize, BigPageSize }; + + private const ulong SmallRegionLimit = 0x400000000UL; // 16 GiB + private const ulong DefaultUserSize = 1UL << 37; + + private readonly struct VmRegion + { + public ulong Start { get; } + public ulong Limit { get; } + + public VmRegion(ulong start, ulong limit) + { + Start = start; + Limit = limit; + } + } + + private static readonly VmRegion[] _vmRegions = { + new VmRegion((ulong)BigPageSize << 16, SmallRegionLimit), + new VmRegion(SmallRegionLimit, DefaultUserSize), + }; + + private readonly AddressSpaceContext _asContext; + private readonly NvMemoryAllocator _memoryAllocator; + + public NvHostAsGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + _asContext = new AddressSpaceContext(context.Device.Gpu.CreateMemoryManager(owner)); + _memoryAllocator = new NvMemoryAllocator(); + } + + public override NvInternalResult Ioctl(NvIoctl command, Span arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuAsMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod(BindChannel, arguments); + break; + case 0x02: + result = CallIoctlMethod(AllocSpace, arguments); + break; + case 0x03: + result = CallIoctlMethod(FreeSpace, arguments); + break; + case 0x05: + result = CallIoctlMethod(UnmapBuffer, arguments); + break; + case 0x06: + result = CallIoctlMethod(MapBufferEx, arguments); + break; + case 0x08: + result = CallIoctlMethod(GetVaRegions, arguments); + break; + case 0x09: + result = CallIoctlMethod(InitializeEx, arguments); + break; + case 0x14: + result = CallIoctlMethod(Remap, arguments); + break; + } + } + + return result; + } + + public override NvInternalResult Ioctl3(NvIoctl command, Span arguments, Span inlineOutBuffer) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuAsMagic) + { + switch (command.Number) + { + case 0x08: + // This is the same as the one in ioctl as inlineOutBuffer is empty. + result = CallIoctlMethod(GetVaRegions, arguments); + break; + } + } + + return result; + } + + private NvInternalResult BindChannel(ref BindChannelArguments arguments) + { + var channelDeviceFile = INvDrvServices.DeviceFileIdRegistry.GetData(arguments.Fd); + if (channelDeviceFile == null) + { + // TODO: Return invalid Fd error. + } + + channelDeviceFile.Channel.BindMemory(_asContext.Gmm); + + return NvInternalResult.Success; + } + + private NvInternalResult AllocSpace(ref AllocSpaceArguments arguments) + { + ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize; + + NvInternalResult result = NvInternalResult.Success; + + lock (_asContext) + { + // Note: When the fixed offset flag is not set, + // the Offset field holds the alignment size instead. + if ((arguments.Flags & AddressSpaceFlags.FixedOffset) != 0) + { + bool regionInUse = _memoryAllocator.IsRegionInUse(arguments.Offset, size, out ulong freeAddressStartPosition); + ulong address; + + if (!regionInUse) + { + _memoryAllocator.AllocateRange(arguments.Offset, size, freeAddressStartPosition); + address = freeAddressStartPosition; + } + else + { + address = NvMemoryAllocator.PteUnmapped; + } + + arguments.Offset = address; + } + else + { + ulong address = _memoryAllocator.GetFreeAddress(size, out ulong freeAddressStartPosition, arguments.Offset); + if (address != NvMemoryAllocator.PteUnmapped) + { + _memoryAllocator.AllocateRange(address, size, freeAddressStartPosition); + } + + arguments.Offset = address; + } + + if (arguments.Offset == NvMemoryAllocator.PteUnmapped) + { + arguments.Offset = 0; + + Logger.Warning?.Print(LogClass.ServiceNv, $"Failed to allocate size {size:x16}!"); + + result = NvInternalResult.OutOfMemory; + } + else + { + _asContext.AddReservation(arguments.Offset, size); + } + } + + return result; + } + + private NvInternalResult FreeSpace(ref FreeSpaceArguments arguments) + { + ulong size = (ulong)arguments.Pages * (ulong)arguments.PageSize; + + NvInternalResult result = NvInternalResult.Success; + + lock (_asContext) + { + if (_asContext.RemoveReservation(arguments.Offset)) + { + _memoryAllocator.DeallocateRange(arguments.Offset, size); + _asContext.Gmm.Unmap(arguments.Offset, size); + } + else + { + Logger.Warning?.Print(LogClass.ServiceNv, + $"Failed to free offset 0x{arguments.Offset:x16} size 0x{size:x16}!"); + + result = NvInternalResult.InvalidInput; + } + } + + return result; + } + + private NvInternalResult UnmapBuffer(ref UnmapBufferArguments arguments) + { + lock (_asContext) + { + if (_asContext.RemoveMap(arguments.Offset, out ulong size)) + { + if (size != 0) + { + _memoryAllocator.DeallocateRange(arguments.Offset, size); + _asContext.Gmm.Unmap(arguments.Offset, size); + } + } + else + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid buffer offset {arguments.Offset:x16}!"); + } + } + + return NvInternalResult.Success; + } + + private NvInternalResult MapBufferEx(ref MapBufferExArguments arguments) + { + const string MapErrorMsg = "Failed to map fixed buffer with offset 0x{0:x16}, size 0x{1:x16} and alignment 0x{2:x16}!"; + + ulong physicalAddress; + + if ((arguments.Flags & AddressSpaceFlags.RemapSubRange) != 0) + { + lock (_asContext) + { + if (_asContext.TryGetMapPhysicalAddress(arguments.Offset, out physicalAddress)) + { + ulong virtualAddress = arguments.Offset + arguments.BufferOffset; + + physicalAddress += arguments.BufferOffset; + _asContext.Gmm.Map(physicalAddress, virtualAddress, arguments.MappingSize, (PteKind)arguments.Kind); + + return NvInternalResult.Success; + } + else + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Address 0x{arguments.Offset:x16} not mapped!"); + + return NvInternalResult.InvalidInput; + } + } + } + + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, arguments.NvMapHandle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{arguments.NvMapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + ulong pageSize = (ulong)arguments.PageSize; + + if (pageSize == 0) + { + pageSize = (ulong)map.Align; + } + + physicalAddress = map.Address + arguments.BufferOffset; + + ulong size = arguments.MappingSize; + + if (size == 0) + { + size = map.Size; + } + + NvInternalResult result = NvInternalResult.Success; + + lock (_asContext) + { + // Note: When the fixed offset flag is not set, + // the Offset field holds the alignment size instead. + bool virtualAddressAllocated = (arguments.Flags & AddressSpaceFlags.FixedOffset) == 0; + + if (!virtualAddressAllocated) + { + if (_asContext.ValidateFixedBuffer(arguments.Offset, size, pageSize)) + { + _asContext.Gmm.Map(physicalAddress, arguments.Offset, size, (PteKind)arguments.Kind); + } + else + { + string message = string.Format(MapErrorMsg, arguments.Offset, size, pageSize); + + Logger.Warning?.Print(LogClass.ServiceNv, message); + + result = NvInternalResult.InvalidInput; + } + } + else + { + ulong va = _memoryAllocator.GetFreeAddress(size, out ulong freeAddressStartPosition, pageSize); + if (va != NvMemoryAllocator.PteUnmapped) + { + _memoryAllocator.AllocateRange(va, size, freeAddressStartPosition); + } + + _asContext.Gmm.Map(physicalAddress, va, size, (PteKind)arguments.Kind); + arguments.Offset = va; + } + + if (arguments.Offset == NvMemoryAllocator.PteUnmapped) + { + arguments.Offset = 0; + + Logger.Warning?.Print(LogClass.ServiceNv, $"Failed to map size 0x{size:x16}!"); + + result = NvInternalResult.InvalidInput; + } + else + { + _asContext.AddMap(arguments.Offset, size, physicalAddress, virtualAddressAllocated); + } + } + + return result; + } + + private NvInternalResult GetVaRegions(ref GetVaRegionsArguments arguments) + { + int vaRegionStructSize = Unsafe.SizeOf(); + + Debug.Assert(vaRegionStructSize == 0x18); + Debug.Assert(_pageSizes.Length == 2); + + uint writeEntries = (uint)(arguments.BufferSize / vaRegionStructSize); + if (writeEntries > _pageSizes.Length) + { + writeEntries = (uint)_pageSizes.Length; + } + + for (uint i = 0; i < writeEntries; i++) + { + ref var region = ref arguments.Regions[(int)i]; + + var vmRegion = _vmRegions[i]; + uint pageSize = _pageSizes[i]; + + region.PageSize = pageSize; + region.Offset = vmRegion.Start; + region.Pages = (vmRegion.Limit - vmRegion.Start) / pageSize; + region.Padding = 0; + } + + arguments.BufferSize = (uint)(_pageSizes.Length * vaRegionStructSize); + + return NvInternalResult.Success; + } + + private NvInternalResult InitializeEx(ref InitializeExArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult Remap(Span arguments) + { + MemoryManager gmm = _asContext.Gmm; + + for (int index = 0; index < arguments.Length; index++) + { + ref RemapArguments argument = ref arguments[index]; + ulong gpuVa = (ulong)argument.GpuOffset << 16; + ulong size = (ulong)argument.Pages << 16; + int nvmapHandle = argument.NvMapHandle; + + if (nvmapHandle == 0) + { + gmm.Unmap(gpuVa, size); + } + else + { + ulong mapOffs = (ulong)argument.MapOffset << 16; + PteKind kind = (PteKind)argument.Kind; + + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, nvmapHandle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid NvMap handle 0x{nvmapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + gmm.Map(mapOffs + map.Address, gpuVa, size, kind); + } + } + + return NvInternalResult.Success; + } + + public override void Close() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs new file mode 100644 index 00000000..9dd52e6d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs @@ -0,0 +1,190 @@ +using Ryujinx.Graphics.Gpu.Memory; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + class AddressSpaceContext + { + private class Range + { + public ulong Start { get; } + public ulong End { get; } + + public Range(ulong address, ulong size) + { + Start = address; + End = size + Start; + } + } + + private class MappedMemory : Range + { + public ulong PhysicalAddress { get; } + public bool VaAllocated { get; } + + public MappedMemory(ulong address, ulong size, ulong physicalAddress, bool vaAllocated) : base(address, size) + { + PhysicalAddress = physicalAddress; + VaAllocated = vaAllocated; + } + } + + public MemoryManager Gmm { get; } + + private readonly SortedList _maps; + private readonly SortedList _reservations; + + public AddressSpaceContext(MemoryManager gmm) + { + Gmm = gmm; + + _maps = new SortedList(); + _reservations = new SortedList(); + } + + public bool ValidateFixedBuffer(ulong address, ulong size, ulong alignment) + { + ulong mapEnd = address + size; + + // Check if size is valid (0 is also not allowed). + if (mapEnd <= address) + { + return false; + } + + // Check if address is aligned. + if ((address & (alignment - 1)) != 0) + { + return false; + } + + // Check if region is reserved. + if (BinarySearch(_reservations, address) == null) + { + return false; + } + + // Check for overlap with already mapped buffers. + Range map = BinarySearchLt(_maps, mapEnd); + + if (map != null && map.End > address) + { + return false; + } + + return true; + } + + public void AddMap(ulong gpuVa, ulong size, ulong physicalAddress, bool vaAllocated) + { + _maps.Add(gpuVa, new MappedMemory(gpuVa, size, physicalAddress, vaAllocated)); + } + + public bool RemoveMap(ulong gpuVa, out ulong size) + { + size = 0; + + if (_maps.Remove(gpuVa, out Range value)) + { + MappedMemory map = (MappedMemory)value; + + if (map.VaAllocated) + { + size = (map.End - map.Start); + } + + return true; + } + + return false; + } + + public bool TryGetMapPhysicalAddress(ulong gpuVa, out ulong physicalAddress) + { + Range map = BinarySearch(_maps, gpuVa); + + if (map != null) + { + physicalAddress = ((MappedMemory)map).PhysicalAddress; + return true; + } + + physicalAddress = 0; + return false; + } + + public void AddReservation(ulong gpuVa, ulong size) + { + _reservations.Add(gpuVa, new Range(gpuVa, size)); + } + + public bool RemoveReservation(ulong gpuVa) + { + return _reservations.Remove(gpuVa); + } + + private Range BinarySearch(SortedList list, ulong address) + { + int left = 0; + int right = list.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Range rg = list.Values[middle]; + + if (address >= rg.Start && address < rg.End) + { + return rg; + } + + if (address < rg.Start) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return null; + } + + private Range BinarySearchLt(SortedList list, ulong address) + { + Range ltRg = null; + + int left = 0; + int right = list.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Range rg = list.Values[middle]; + + if (address < rg.Start) + { + right = middle - 1; + } + else + { + left = middle + 1; + + if (address > rg.Start) + { + ltRg = rg; + } + } + } + + return ltRg; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs new file mode 100644 index 00000000..0d0ae4ca --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [Flags] + enum AddressSpaceFlags : uint + { + FixedOffset = 1, + RemapSubRange = 0x100, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs new file mode 100644 index 00000000..231fa7c1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct AllocSpaceArguments + { + public uint Pages; + public uint PageSize; + public AddressSpaceFlags Flags; + public uint Padding; + public ulong Offset; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs new file mode 100644 index 00000000..51fac8d8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct BindChannelArguments + { + public int Fd; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs new file mode 100644 index 00000000..f14b751d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct FreeSpaceArguments + { + public ulong Offset; + public uint Pages; + public uint PageSize; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs new file mode 100644 index 00000000..44747782 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs @@ -0,0 +1,23 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct VaRegion + { + public ulong Offset; + public uint PageSize; + public uint Padding; + public ulong Pages; + } + + [StructLayout(LayoutKind.Sequential)] + struct GetVaRegionsArguments + { + public ulong Unused; + public uint BufferSize; + public uint Padding; + public Array2 Regions; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs new file mode 100644 index 00000000..e86932bb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct InitializeExArguments + { + public uint Flags; + public int AsFd; + public uint BigPageSize; + public uint Reserved; + public ulong Unknown0; + public ulong Unknown1; + public ulong Unknown2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs new file mode 100644 index 00000000..c0f6d40e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct MapBufferExArguments + { + public AddressSpaceFlags Flags; + public int Kind; + public int NvMapHandle; + public int PageSize; + public ulong BufferOffset; + public ulong MappingSize; + public ulong Offset; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs new file mode 100644 index 00000000..99afa4ba --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct RemapArguments + { + public ushort Flags; + public ushort Kind; + public int NvMapHandle; + public uint MapOffset; + public uint GpuOffset; + public uint Pages; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs new file mode 100644 index 00000000..d6bc1bc5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types +{ + struct UnmapBufferArguments + { +#pragma warning disable CS0649 // Field is never assigned to + public ulong Offset; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/ChannelInitialization.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/ChannelInitialization.cs new file mode 100644 index 00000000..59578775 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/ChannelInitialization.cs @@ -0,0 +1,1361 @@ +using Ryujinx.Graphics.Gpu; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + static class ChannelInitialization + { + public static void InitializeState(GpuChannel channel) + { + channel.Write(ClassId.Threed, 0x800, 0x0); + channel.Write(ClassId.Threed, 0x840, 0x0); + channel.Write(ClassId.Threed, 0x880, 0x0); + channel.Write(ClassId.Threed, 0x8C0, 0x0); + channel.Write(ClassId.Threed, 0x900, 0x0); + channel.Write(ClassId.Threed, 0x940, 0x0); + channel.Write(ClassId.Threed, 0x980, 0x0); + channel.Write(ClassId.Threed, 0x9C0, 0x0); + channel.Write(ClassId.Threed, 0x804, 0x0); + channel.Write(ClassId.Threed, 0x844, 0x0); + channel.Write(ClassId.Threed, 0x884, 0x0); + channel.Write(ClassId.Threed, 0x8C4, 0x0); + channel.Write(ClassId.Threed, 0x904, 0x0); + channel.Write(ClassId.Threed, 0x944, 0x0); + channel.Write(ClassId.Threed, 0x984, 0x0); + channel.Write(ClassId.Threed, 0x9C4, 0x0); + channel.Write(ClassId.Threed, 0x808, 0x400); + channel.Write(ClassId.Threed, 0x848, 0x400); + channel.Write(ClassId.Threed, 0x888, 0x400); + channel.Write(ClassId.Threed, 0x8C8, 0x400); + channel.Write(ClassId.Threed, 0x908, 0x400); + channel.Write(ClassId.Threed, 0x948, 0x400); + channel.Write(ClassId.Threed, 0x988, 0x400); + channel.Write(ClassId.Threed, 0x9C8, 0x400); + channel.Write(ClassId.Threed, 0x80C, 0x300); + channel.Write(ClassId.Threed, 0x84C, 0x300); + channel.Write(ClassId.Threed, 0x88C, 0x300); + channel.Write(ClassId.Threed, 0x8CC, 0x300); + channel.Write(ClassId.Threed, 0x90C, 0x300); + channel.Write(ClassId.Threed, 0x94C, 0x300); + channel.Write(ClassId.Threed, 0x98C, 0x300); + channel.Write(ClassId.Threed, 0x9CC, 0x300); + channel.Write(ClassId.Threed, 0x810, 0xCF); + channel.Write(ClassId.Threed, 0x850, 0x0); + channel.Write(ClassId.Threed, 0x890, 0x0); + channel.Write(ClassId.Threed, 0x8D0, 0x0); + channel.Write(ClassId.Threed, 0x910, 0x0); + channel.Write(ClassId.Threed, 0x950, 0x0); + channel.Write(ClassId.Threed, 0x990, 0x0); + channel.Write(ClassId.Threed, 0x9D0, 0x0); + channel.Write(ClassId.Threed, 0x814, 0x40); + channel.Write(ClassId.Threed, 0x854, 0x40); + channel.Write(ClassId.Threed, 0x894, 0x40); + channel.Write(ClassId.Threed, 0x8D4, 0x40); + channel.Write(ClassId.Threed, 0x914, 0x40); + channel.Write(ClassId.Threed, 0x954, 0x40); + channel.Write(ClassId.Threed, 0x994, 0x40); + channel.Write(ClassId.Threed, 0x9D4, 0x40); + channel.Write(ClassId.Threed, 0x818, 0x1); + channel.Write(ClassId.Threed, 0x858, 0x1); + channel.Write(ClassId.Threed, 0x898, 0x1); + channel.Write(ClassId.Threed, 0x8D8, 0x1); + channel.Write(ClassId.Threed, 0x918, 0x1); + channel.Write(ClassId.Threed, 0x958, 0x1); + channel.Write(ClassId.Threed, 0x998, 0x1); + channel.Write(ClassId.Threed, 0x9D8, 0x1); + channel.Write(ClassId.Threed, 0x81C, 0x0); + channel.Write(ClassId.Threed, 0x85C, 0x0); + channel.Write(ClassId.Threed, 0x89C, 0x0); + channel.Write(ClassId.Threed, 0x8DC, 0x0); + channel.Write(ClassId.Threed, 0x91C, 0x0); + channel.Write(ClassId.Threed, 0x95C, 0x0); + channel.Write(ClassId.Threed, 0x99C, 0x0); + channel.Write(ClassId.Threed, 0x9DC, 0x0); + channel.Write(ClassId.Threed, 0x820, 0x0); + channel.Write(ClassId.Threed, 0x860, 0x0); + channel.Write(ClassId.Threed, 0x8A0, 0x0); + channel.Write(ClassId.Threed, 0x8E0, 0x0); + channel.Write(ClassId.Threed, 0x920, 0x0); + channel.Write(ClassId.Threed, 0x960, 0x0); + channel.Write(ClassId.Threed, 0x9A0, 0x0); + channel.Write(ClassId.Threed, 0x9E0, 0x0); + channel.Write(ClassId.Threed, 0x1C00, 0x0); + channel.Write(ClassId.Threed, 0x1C10, 0x0); + channel.Write(ClassId.Threed, 0x1C20, 0x0); + channel.Write(ClassId.Threed, 0x1C30, 0x0); + channel.Write(ClassId.Threed, 0x1C40, 0x0); + channel.Write(ClassId.Threed, 0x1C50, 0x0); + channel.Write(ClassId.Threed, 0x1C60, 0x0); + channel.Write(ClassId.Threed, 0x1C70, 0x0); + channel.Write(ClassId.Threed, 0x1C80, 0x0); + channel.Write(ClassId.Threed, 0x1C90, 0x0); + channel.Write(ClassId.Threed, 0x1CA0, 0x0); + channel.Write(ClassId.Threed, 0x1CB0, 0x0); + channel.Write(ClassId.Threed, 0x1CC0, 0x0); + channel.Write(ClassId.Threed, 0x1CD0, 0x0); + channel.Write(ClassId.Threed, 0x1CE0, 0x0); + channel.Write(ClassId.Threed, 0x1CF0, 0x0); + channel.Write(ClassId.Threed, 0x1C04, 0x0); + channel.Write(ClassId.Threed, 0x1C14, 0x0); + channel.Write(ClassId.Threed, 0x1C24, 0x0); + channel.Write(ClassId.Threed, 0x1C34, 0x0); + channel.Write(ClassId.Threed, 0x1C44, 0x0); + channel.Write(ClassId.Threed, 0x1C54, 0x0); + channel.Write(ClassId.Threed, 0x1C64, 0x0); + channel.Write(ClassId.Threed, 0x1C74, 0x0); + channel.Write(ClassId.Threed, 0x1C84, 0x0); + channel.Write(ClassId.Threed, 0x1C94, 0x0); + channel.Write(ClassId.Threed, 0x1CA4, 0x0); + channel.Write(ClassId.Threed, 0x1CB4, 0x0); + channel.Write(ClassId.Threed, 0x1CC4, 0x0); + channel.Write(ClassId.Threed, 0x1CD4, 0x0); + channel.Write(ClassId.Threed, 0x1CE4, 0x0); + channel.Write(ClassId.Threed, 0x1CF4, 0x0); + channel.Write(ClassId.Threed, 0x1C08, 0x0); + channel.Write(ClassId.Threed, 0x1C18, 0x0); + channel.Write(ClassId.Threed, 0x1C28, 0x0); + channel.Write(ClassId.Threed, 0x1C38, 0x0); + channel.Write(ClassId.Threed, 0x1C48, 0x0); + channel.Write(ClassId.Threed, 0x1C58, 0x0); + channel.Write(ClassId.Threed, 0x1C68, 0x0); + channel.Write(ClassId.Threed, 0x1C78, 0x0); + channel.Write(ClassId.Threed, 0x1C88, 0x0); + channel.Write(ClassId.Threed, 0x1C98, 0x0); + channel.Write(ClassId.Threed, 0x1CA8, 0x0); + channel.Write(ClassId.Threed, 0x1CB8, 0x0); + channel.Write(ClassId.Threed, 0x1CC8, 0x0); + channel.Write(ClassId.Threed, 0x1CD8, 0x0); + channel.Write(ClassId.Threed, 0x1CE8, 0x0); + channel.Write(ClassId.Threed, 0x1CF8, 0x0); + channel.Write(ClassId.Threed, 0x1C0C, 0x0); + channel.Write(ClassId.Threed, 0x1C1C, 0x0); + channel.Write(ClassId.Threed, 0x1C2C, 0x0); + channel.Write(ClassId.Threed, 0x1C3C, 0x0); + channel.Write(ClassId.Threed, 0x1C4C, 0x0); + channel.Write(ClassId.Threed, 0x1C5C, 0x0); + channel.Write(ClassId.Threed, 0x1C6C, 0x0); + channel.Write(ClassId.Threed, 0x1C7C, 0x0); + channel.Write(ClassId.Threed, 0x1C8C, 0x0); + channel.Write(ClassId.Threed, 0x1C9C, 0x0); + channel.Write(ClassId.Threed, 0x1CAC, 0x0); + channel.Write(ClassId.Threed, 0x1CBC, 0x0); + channel.Write(ClassId.Threed, 0x1CCC, 0x0); + channel.Write(ClassId.Threed, 0x1CDC, 0x0); + channel.Write(ClassId.Threed, 0x1CEC, 0x0); + channel.Write(ClassId.Threed, 0x1CFC, 0x0); + channel.Write(ClassId.Threed, 0x1D00, 0x0); + channel.Write(ClassId.Threed, 0x1D10, 0x0); + channel.Write(ClassId.Threed, 0x1D20, 0x0); + channel.Write(ClassId.Threed, 0x1D30, 0x0); + channel.Write(ClassId.Threed, 0x1D40, 0x0); + channel.Write(ClassId.Threed, 0x1D50, 0x0); + channel.Write(ClassId.Threed, 0x1D60, 0x0); + channel.Write(ClassId.Threed, 0x1D70, 0x0); + channel.Write(ClassId.Threed, 0x1D80, 0x0); + channel.Write(ClassId.Threed, 0x1D90, 0x0); + channel.Write(ClassId.Threed, 0x1DA0, 0x0); + channel.Write(ClassId.Threed, 0x1DB0, 0x0); + channel.Write(ClassId.Threed, 0x1DC0, 0x0); + channel.Write(ClassId.Threed, 0x1DD0, 0x0); + channel.Write(ClassId.Threed, 0x1DE0, 0x0); + channel.Write(ClassId.Threed, 0x1DF0, 0x0); + channel.Write(ClassId.Threed, 0x1D04, 0x0); + channel.Write(ClassId.Threed, 0x1D14, 0x0); + channel.Write(ClassId.Threed, 0x1D24, 0x0); + channel.Write(ClassId.Threed, 0x1D34, 0x0); + channel.Write(ClassId.Threed, 0x1D44, 0x0); + channel.Write(ClassId.Threed, 0x1D54, 0x0); + channel.Write(ClassId.Threed, 0x1D64, 0x0); + channel.Write(ClassId.Threed, 0x1D74, 0x0); + channel.Write(ClassId.Threed, 0x1D84, 0x0); + channel.Write(ClassId.Threed, 0x1D94, 0x0); + channel.Write(ClassId.Threed, 0x1DA4, 0x0); + channel.Write(ClassId.Threed, 0x1DB4, 0x0); + channel.Write(ClassId.Threed, 0x1DC4, 0x0); + channel.Write(ClassId.Threed, 0x1DD4, 0x0); + channel.Write(ClassId.Threed, 0x1DE4, 0x0); + channel.Write(ClassId.Threed, 0x1DF4, 0x0); + channel.Write(ClassId.Threed, 0x1D08, 0x0); + channel.Write(ClassId.Threed, 0x1D18, 0x0); + channel.Write(ClassId.Threed, 0x1D28, 0x0); + channel.Write(ClassId.Threed, 0x1D38, 0x0); + channel.Write(ClassId.Threed, 0x1D48, 0x0); + channel.Write(ClassId.Threed, 0x1D58, 0x0); + channel.Write(ClassId.Threed, 0x1D68, 0x0); + channel.Write(ClassId.Threed, 0x1D78, 0x0); + channel.Write(ClassId.Threed, 0x1D88, 0x0); + channel.Write(ClassId.Threed, 0x1D98, 0x0); + channel.Write(ClassId.Threed, 0x1DA8, 0x0); + channel.Write(ClassId.Threed, 0x1DB8, 0x0); + channel.Write(ClassId.Threed, 0x1DC8, 0x0); + channel.Write(ClassId.Threed, 0x1DD8, 0x0); + channel.Write(ClassId.Threed, 0x1DE8, 0x0); + channel.Write(ClassId.Threed, 0x1DF8, 0x0); + channel.Write(ClassId.Threed, 0x1D0C, 0x0); + channel.Write(ClassId.Threed, 0x1D1C, 0x0); + channel.Write(ClassId.Threed, 0x1D2C, 0x0); + channel.Write(ClassId.Threed, 0x1D3C, 0x0); + channel.Write(ClassId.Threed, 0x1D4C, 0x0); + channel.Write(ClassId.Threed, 0x1D5C, 0x0); + channel.Write(ClassId.Threed, 0x1D6C, 0x0); + channel.Write(ClassId.Threed, 0x1D7C, 0x0); + channel.Write(ClassId.Threed, 0x1D8C, 0x0); + channel.Write(ClassId.Threed, 0x1D9C, 0x0); + channel.Write(ClassId.Threed, 0x1DAC, 0x0); + channel.Write(ClassId.Threed, 0x1DBC, 0x0); + channel.Write(ClassId.Threed, 0x1DCC, 0x0); + channel.Write(ClassId.Threed, 0x1DDC, 0x0); + channel.Write(ClassId.Threed, 0x1DEC, 0x0); + channel.Write(ClassId.Threed, 0x1DFC, 0x0); + channel.Write(ClassId.Threed, 0x1F00, 0x0); + channel.Write(ClassId.Threed, 0x1F08, 0x0); + channel.Write(ClassId.Threed, 0x1F10, 0x0); + channel.Write(ClassId.Threed, 0x1F18, 0x0); + channel.Write(ClassId.Threed, 0x1F20, 0x0); + channel.Write(ClassId.Threed, 0x1F28, 0x0); + channel.Write(ClassId.Threed, 0x1F30, 0x0); + channel.Write(ClassId.Threed, 0x1F38, 0x0); + channel.Write(ClassId.Threed, 0x1F40, 0x0); + channel.Write(ClassId.Threed, 0x1F48, 0x0); + channel.Write(ClassId.Threed, 0x1F50, 0x0); + channel.Write(ClassId.Threed, 0x1F58, 0x0); + channel.Write(ClassId.Threed, 0x1F60, 0x0); + channel.Write(ClassId.Threed, 0x1F68, 0x0); + channel.Write(ClassId.Threed, 0x1F70, 0x0); + channel.Write(ClassId.Threed, 0x1F78, 0x0); + channel.Write(ClassId.Threed, 0x1F04, 0x0); + channel.Write(ClassId.Threed, 0x1F0C, 0x0); + channel.Write(ClassId.Threed, 0x1F14, 0x0); + channel.Write(ClassId.Threed, 0x1F1C, 0x0); + channel.Write(ClassId.Threed, 0x1F24, 0x0); + channel.Write(ClassId.Threed, 0x1F2C, 0x0); + channel.Write(ClassId.Threed, 0x1F34, 0x0); + channel.Write(ClassId.Threed, 0x1F3C, 0x0); + channel.Write(ClassId.Threed, 0x1F44, 0x0); + channel.Write(ClassId.Threed, 0x1F4C, 0x0); + channel.Write(ClassId.Threed, 0x1F54, 0x0); + channel.Write(ClassId.Threed, 0x1F5C, 0x0); + channel.Write(ClassId.Threed, 0x1F64, 0x0); + channel.Write(ClassId.Threed, 0x1F6C, 0x0); + channel.Write(ClassId.Threed, 0x1F74, 0x0); + channel.Write(ClassId.Threed, 0x1F7C, 0x0); + channel.Write(ClassId.Threed, 0x1F80, 0x0); + channel.Write(ClassId.Threed, 0x1F88, 0x0); + channel.Write(ClassId.Threed, 0x1F90, 0x0); + channel.Write(ClassId.Threed, 0x1F98, 0x0); + channel.Write(ClassId.Threed, 0x1FA0, 0x0); + channel.Write(ClassId.Threed, 0x1FA8, 0x0); + channel.Write(ClassId.Threed, 0x1FB0, 0x0); + channel.Write(ClassId.Threed, 0x1FB8, 0x0); + channel.Write(ClassId.Threed, 0x1FC0, 0x0); + channel.Write(ClassId.Threed, 0x1FC8, 0x0); + channel.Write(ClassId.Threed, 0x1FD0, 0x0); + channel.Write(ClassId.Threed, 0x1FD8, 0x0); + channel.Write(ClassId.Threed, 0x1FE0, 0x0); + channel.Write(ClassId.Threed, 0x1FE8, 0x0); + channel.Write(ClassId.Threed, 0x1FF0, 0x0); + channel.Write(ClassId.Threed, 0x1FF8, 0x0); + channel.Write(ClassId.Threed, 0x1F84, 0x0); + channel.Write(ClassId.Threed, 0x1F8C, 0x0); + channel.Write(ClassId.Threed, 0x1F94, 0x0); + channel.Write(ClassId.Threed, 0x1F9C, 0x0); + channel.Write(ClassId.Threed, 0x1FA4, 0x0); + channel.Write(ClassId.Threed, 0x1FAC, 0x0); + channel.Write(ClassId.Threed, 0x1FB4, 0x0); + channel.Write(ClassId.Threed, 0x1FBC, 0x0); + channel.Write(ClassId.Threed, 0x1FC4, 0x0); + channel.Write(ClassId.Threed, 0x1FCC, 0x0); + channel.Write(ClassId.Threed, 0x1FD4, 0x0); + channel.Write(ClassId.Threed, 0x1FDC, 0x0); + channel.Write(ClassId.Threed, 0x1FE4, 0x0); + channel.Write(ClassId.Threed, 0x1FEC, 0x0); + channel.Write(ClassId.Threed, 0x1FF4, 0x0); + channel.Write(ClassId.Threed, 0x1FFC, 0x0); + channel.Write(ClassId.Threed, 0x2000, 0x0); + channel.Write(ClassId.Threed, 0x2040, 0x11); + channel.Write(ClassId.Threed, 0x2080, 0x20); + channel.Write(ClassId.Threed, 0x20C0, 0x30); + channel.Write(ClassId.Threed, 0x2100, 0x40); + channel.Write(ClassId.Threed, 0x2140, 0x51); + channel.Write(ClassId.Threed, 0x200C, 0x1); + channel.Write(ClassId.Threed, 0x204C, 0x1); + channel.Write(ClassId.Threed, 0x208C, 0x1); + channel.Write(ClassId.Threed, 0x20CC, 0x1); + channel.Write(ClassId.Threed, 0x210C, 0x1); + channel.Write(ClassId.Threed, 0x214C, 0x1); + channel.Write(ClassId.Threed, 0x2010, 0x0); + channel.Write(ClassId.Threed, 0x2050, 0x0); + channel.Write(ClassId.Threed, 0x2090, 0x1); + channel.Write(ClassId.Threed, 0x20D0, 0x2); + channel.Write(ClassId.Threed, 0x2110, 0x3); + channel.Write(ClassId.Threed, 0x2150, 0x4); + channel.Write(ClassId.Threed, 0x380, 0x0); + channel.Write(ClassId.Threed, 0x3A0, 0x0); + channel.Write(ClassId.Threed, 0x3C0, 0x0); + channel.Write(ClassId.Threed, 0x3E0, 0x0); + channel.Write(ClassId.Threed, 0x384, 0x0); + channel.Write(ClassId.Threed, 0x3A4, 0x0); + channel.Write(ClassId.Threed, 0x3C4, 0x0); + channel.Write(ClassId.Threed, 0x3E4, 0x0); + channel.Write(ClassId.Threed, 0x388, 0x0); + channel.Write(ClassId.Threed, 0x3A8, 0x0); + channel.Write(ClassId.Threed, 0x3C8, 0x0); + channel.Write(ClassId.Threed, 0x3E8, 0x0); + channel.Write(ClassId.Threed, 0x38C, 0x0); + channel.Write(ClassId.Threed, 0x3AC, 0x0); + channel.Write(ClassId.Threed, 0x3CC, 0x0); + channel.Write(ClassId.Threed, 0x3EC, 0x0); + channel.Write(ClassId.Threed, 0x700, 0x0); + channel.Write(ClassId.Threed, 0x710, 0x0); + channel.Write(ClassId.Threed, 0x720, 0x0); + channel.Write(ClassId.Threed, 0x730, 0x0); + channel.Write(ClassId.Threed, 0x704, 0x0); + channel.Write(ClassId.Threed, 0x714, 0x0); + channel.Write(ClassId.Threed, 0x724, 0x0); + channel.Write(ClassId.Threed, 0x734, 0x0); + channel.Write(ClassId.Threed, 0x708, 0x0); + channel.Write(ClassId.Threed, 0x718, 0x0); + channel.Write(ClassId.Threed, 0x728, 0x0); + channel.Write(ClassId.Threed, 0x738, 0x0); + channel.Write(ClassId.Threed, 0x2800, 0x0); + channel.Write(ClassId.Threed, 0x2804, 0x0); + channel.Write(ClassId.Threed, 0x2808, 0x0); + channel.Write(ClassId.Threed, 0x280C, 0x0); + channel.Write(ClassId.Threed, 0x2810, 0x0); + channel.Write(ClassId.Threed, 0x2814, 0x0); + channel.Write(ClassId.Threed, 0x2818, 0x0); + channel.Write(ClassId.Threed, 0x281C, 0x0); + channel.Write(ClassId.Threed, 0x2820, 0x0); + channel.Write(ClassId.Threed, 0x2824, 0x0); + channel.Write(ClassId.Threed, 0x2828, 0x0); + channel.Write(ClassId.Threed, 0x282C, 0x0); + channel.Write(ClassId.Threed, 0x2830, 0x0); + channel.Write(ClassId.Threed, 0x2834, 0x0); + channel.Write(ClassId.Threed, 0x2838, 0x0); + channel.Write(ClassId.Threed, 0x283C, 0x0); + channel.Write(ClassId.Threed, 0x2840, 0x0); + channel.Write(ClassId.Threed, 0x2844, 0x0); + channel.Write(ClassId.Threed, 0x2848, 0x0); + channel.Write(ClassId.Threed, 0x284C, 0x0); + channel.Write(ClassId.Threed, 0x2850, 0x0); + channel.Write(ClassId.Threed, 0x2854, 0x0); + channel.Write(ClassId.Threed, 0x2858, 0x0); + channel.Write(ClassId.Threed, 0x285C, 0x0); + channel.Write(ClassId.Threed, 0x2860, 0x0); + channel.Write(ClassId.Threed, 0x2864, 0x0); + channel.Write(ClassId.Threed, 0x2868, 0x0); + channel.Write(ClassId.Threed, 0x286C, 0x0); + channel.Write(ClassId.Threed, 0x2870, 0x0); + channel.Write(ClassId.Threed, 0x2874, 0x0); + channel.Write(ClassId.Threed, 0x2878, 0x0); + channel.Write(ClassId.Threed, 0x287C, 0x0); + channel.Write(ClassId.Threed, 0x2880, 0x0); + channel.Write(ClassId.Threed, 0x2884, 0x0); + channel.Write(ClassId.Threed, 0x2888, 0x0); + channel.Write(ClassId.Threed, 0x288C, 0x0); + channel.Write(ClassId.Threed, 0x2890, 0x0); + channel.Write(ClassId.Threed, 0x2894, 0x0); + channel.Write(ClassId.Threed, 0x2898, 0x0); + channel.Write(ClassId.Threed, 0x289C, 0x0); + channel.Write(ClassId.Threed, 0x28A0, 0x0); + channel.Write(ClassId.Threed, 0x28A4, 0x0); + channel.Write(ClassId.Threed, 0x28A8, 0x0); + channel.Write(ClassId.Threed, 0x28AC, 0x0); + channel.Write(ClassId.Threed, 0x28B0, 0x0); + channel.Write(ClassId.Threed, 0x28B4, 0x0); + channel.Write(ClassId.Threed, 0x28B8, 0x0); + channel.Write(ClassId.Threed, 0x28BC, 0x0); + channel.Write(ClassId.Threed, 0x28C0, 0x0); + channel.Write(ClassId.Threed, 0x28C4, 0x0); + channel.Write(ClassId.Threed, 0x28C8, 0x0); + channel.Write(ClassId.Threed, 0x28CC, 0x0); + channel.Write(ClassId.Threed, 0x28D0, 0x0); + channel.Write(ClassId.Threed, 0x28D4, 0x0); + channel.Write(ClassId.Threed, 0x28D8, 0x0); + channel.Write(ClassId.Threed, 0x28DC, 0x0); + channel.Write(ClassId.Threed, 0x28E0, 0x0); + channel.Write(ClassId.Threed, 0x28E4, 0x0); + channel.Write(ClassId.Threed, 0x28E8, 0x0); + channel.Write(ClassId.Threed, 0x28EC, 0x0); + channel.Write(ClassId.Threed, 0x28F0, 0x0); + channel.Write(ClassId.Threed, 0x28F4, 0x0); + channel.Write(ClassId.Threed, 0x28F8, 0x0); + channel.Write(ClassId.Threed, 0x28FC, 0x0); + channel.Write(ClassId.Threed, 0x2900, 0x0); + channel.Write(ClassId.Threed, 0x2904, 0x0); + channel.Write(ClassId.Threed, 0x2908, 0x0); + channel.Write(ClassId.Threed, 0x290C, 0x0); + channel.Write(ClassId.Threed, 0x2910, 0x0); + channel.Write(ClassId.Threed, 0x2914, 0x0); + channel.Write(ClassId.Threed, 0x2918, 0x0); + channel.Write(ClassId.Threed, 0x291C, 0x0); + channel.Write(ClassId.Threed, 0x2920, 0x0); + channel.Write(ClassId.Threed, 0x2924, 0x0); + channel.Write(ClassId.Threed, 0x2928, 0x0); + channel.Write(ClassId.Threed, 0x292C, 0x0); + channel.Write(ClassId.Threed, 0x2930, 0x0); + channel.Write(ClassId.Threed, 0x2934, 0x0); + channel.Write(ClassId.Threed, 0x2938, 0x0); + channel.Write(ClassId.Threed, 0x293C, 0x0); + channel.Write(ClassId.Threed, 0x2940, 0x0); + channel.Write(ClassId.Threed, 0x2944, 0x0); + channel.Write(ClassId.Threed, 0x2948, 0x0); + channel.Write(ClassId.Threed, 0x294C, 0x0); + channel.Write(ClassId.Threed, 0x2950, 0x0); + channel.Write(ClassId.Threed, 0x2954, 0x0); + channel.Write(ClassId.Threed, 0x2958, 0x0); + channel.Write(ClassId.Threed, 0x295C, 0x0); + channel.Write(ClassId.Threed, 0x2960, 0x0); + channel.Write(ClassId.Threed, 0x2964, 0x0); + channel.Write(ClassId.Threed, 0x2968, 0x0); + channel.Write(ClassId.Threed, 0x296C, 0x0); + channel.Write(ClassId.Threed, 0x2970, 0x0); + channel.Write(ClassId.Threed, 0x2974, 0x0); + channel.Write(ClassId.Threed, 0x2978, 0x0); + channel.Write(ClassId.Threed, 0x297C, 0x0); + channel.Write(ClassId.Threed, 0x2980, 0x0); + channel.Write(ClassId.Threed, 0x2984, 0x0); + channel.Write(ClassId.Threed, 0x2988, 0x0); + channel.Write(ClassId.Threed, 0x298C, 0x0); + channel.Write(ClassId.Threed, 0x2990, 0x0); + channel.Write(ClassId.Threed, 0x2994, 0x0); + channel.Write(ClassId.Threed, 0x2998, 0x0); + channel.Write(ClassId.Threed, 0x299C, 0x0); + channel.Write(ClassId.Threed, 0x29A0, 0x0); + channel.Write(ClassId.Threed, 0x29A4, 0x0); + channel.Write(ClassId.Threed, 0x29A8, 0x0); + channel.Write(ClassId.Threed, 0x29AC, 0x0); + channel.Write(ClassId.Threed, 0x29B0, 0x0); + channel.Write(ClassId.Threed, 0x29B4, 0x0); + channel.Write(ClassId.Threed, 0x29B8, 0x0); + channel.Write(ClassId.Threed, 0x29BC, 0x0); + channel.Write(ClassId.Threed, 0x29C0, 0x0); + channel.Write(ClassId.Threed, 0x29C4, 0x0); + channel.Write(ClassId.Threed, 0x29C8, 0x0); + channel.Write(ClassId.Threed, 0x29CC, 0x0); + channel.Write(ClassId.Threed, 0x29D0, 0x0); + channel.Write(ClassId.Threed, 0x29D4, 0x0); + channel.Write(ClassId.Threed, 0x29D8, 0x0); + channel.Write(ClassId.Threed, 0x29DC, 0x0); + channel.Write(ClassId.Threed, 0x29E0, 0x0); + channel.Write(ClassId.Threed, 0x29E4, 0x0); + channel.Write(ClassId.Threed, 0x29E8, 0x0); + channel.Write(ClassId.Threed, 0x29EC, 0x0); + channel.Write(ClassId.Threed, 0x29F0, 0x0); + channel.Write(ClassId.Threed, 0x29F4, 0x0); + channel.Write(ClassId.Threed, 0x29F8, 0x0); + channel.Write(ClassId.Threed, 0x29FC, 0x0); + channel.Write(ClassId.Threed, 0xA00, 0x0); + channel.Write(ClassId.Threed, 0xA20, 0x0); + channel.Write(ClassId.Threed, 0xA40, 0x0); + channel.Write(ClassId.Threed, 0xA60, 0x0); + channel.Write(ClassId.Threed, 0xA80, 0x0); + channel.Write(ClassId.Threed, 0xAA0, 0x0); + channel.Write(ClassId.Threed, 0xAC0, 0x0); + channel.Write(ClassId.Threed, 0xAE0, 0x0); + channel.Write(ClassId.Threed, 0xB00, 0x0); + channel.Write(ClassId.Threed, 0xB20, 0x0); + channel.Write(ClassId.Threed, 0xB40, 0x0); + channel.Write(ClassId.Threed, 0xB60, 0x0); + channel.Write(ClassId.Threed, 0xB80, 0x0); + channel.Write(ClassId.Threed, 0xBA0, 0x0); + channel.Write(ClassId.Threed, 0xBC0, 0x0); + channel.Write(ClassId.Threed, 0xBE0, 0x0); + channel.Write(ClassId.Threed, 0xA04, 0x0); + channel.Write(ClassId.Threed, 0xA24, 0x0); + channel.Write(ClassId.Threed, 0xA44, 0x0); + channel.Write(ClassId.Threed, 0xA64, 0x0); + channel.Write(ClassId.Threed, 0xA84, 0x0); + channel.Write(ClassId.Threed, 0xAA4, 0x0); + channel.Write(ClassId.Threed, 0xAC4, 0x0); + channel.Write(ClassId.Threed, 0xAE4, 0x0); + channel.Write(ClassId.Threed, 0xB04, 0x0); + channel.Write(ClassId.Threed, 0xB24, 0x0); + channel.Write(ClassId.Threed, 0xB44, 0x0); + channel.Write(ClassId.Threed, 0xB64, 0x0); + channel.Write(ClassId.Threed, 0xB84, 0x0); + channel.Write(ClassId.Threed, 0xBA4, 0x0); + channel.Write(ClassId.Threed, 0xBC4, 0x0); + channel.Write(ClassId.Threed, 0xBE4, 0x0); + channel.Write(ClassId.Threed, 0xA08, 0x0); + channel.Write(ClassId.Threed, 0xA28, 0x0); + channel.Write(ClassId.Threed, 0xA48, 0x0); + channel.Write(ClassId.Threed, 0xA68, 0x0); + channel.Write(ClassId.Threed, 0xA88, 0x0); + channel.Write(ClassId.Threed, 0xAA8, 0x0); + channel.Write(ClassId.Threed, 0xAC8, 0x0); + channel.Write(ClassId.Threed, 0xAE8, 0x0); + channel.Write(ClassId.Threed, 0xB08, 0x0); + channel.Write(ClassId.Threed, 0xB28, 0x0); + channel.Write(ClassId.Threed, 0xB48, 0x0); + channel.Write(ClassId.Threed, 0xB68, 0x0); + channel.Write(ClassId.Threed, 0xB88, 0x0); + channel.Write(ClassId.Threed, 0xBA8, 0x0); + channel.Write(ClassId.Threed, 0xBC8, 0x0); + channel.Write(ClassId.Threed, 0xBE8, 0x0); + channel.Write(ClassId.Threed, 0xA0C, 0x0); + channel.Write(ClassId.Threed, 0xA2C, 0x0); + channel.Write(ClassId.Threed, 0xA4C, 0x0); + channel.Write(ClassId.Threed, 0xA6C, 0x0); + channel.Write(ClassId.Threed, 0xA8C, 0x0); + channel.Write(ClassId.Threed, 0xAAC, 0x0); + channel.Write(ClassId.Threed, 0xACC, 0x0); + channel.Write(ClassId.Threed, 0xAEC, 0x0); + channel.Write(ClassId.Threed, 0xB0C, 0x0); + channel.Write(ClassId.Threed, 0xB2C, 0x0); + channel.Write(ClassId.Threed, 0xB4C, 0x0); + channel.Write(ClassId.Threed, 0xB6C, 0x0); + channel.Write(ClassId.Threed, 0xB8C, 0x0); + channel.Write(ClassId.Threed, 0xBAC, 0x0); + channel.Write(ClassId.Threed, 0xBCC, 0x0); + channel.Write(ClassId.Threed, 0xBEC, 0x0); + channel.Write(ClassId.Threed, 0xA10, 0x0); + channel.Write(ClassId.Threed, 0xA30, 0x0); + channel.Write(ClassId.Threed, 0xA50, 0x0); + channel.Write(ClassId.Threed, 0xA70, 0x0); + channel.Write(ClassId.Threed, 0xA90, 0x0); + channel.Write(ClassId.Threed, 0xAB0, 0x0); + channel.Write(ClassId.Threed, 0xAD0, 0x0); + channel.Write(ClassId.Threed, 0xAF0, 0x0); + channel.Write(ClassId.Threed, 0xB10, 0x0); + channel.Write(ClassId.Threed, 0xB30, 0x0); + channel.Write(ClassId.Threed, 0xB50, 0x0); + channel.Write(ClassId.Threed, 0xB70, 0x0); + channel.Write(ClassId.Threed, 0xB90, 0x0); + channel.Write(ClassId.Threed, 0xBB0, 0x0); + channel.Write(ClassId.Threed, 0xBD0, 0x0); + channel.Write(ClassId.Threed, 0xBF0, 0x0); + channel.Write(ClassId.Threed, 0xA14, 0x0); + channel.Write(ClassId.Threed, 0xA34, 0x0); + channel.Write(ClassId.Threed, 0xA54, 0x0); + channel.Write(ClassId.Threed, 0xA74, 0x0); + channel.Write(ClassId.Threed, 0xA94, 0x0); + channel.Write(ClassId.Threed, 0xAB4, 0x0); + channel.Write(ClassId.Threed, 0xAD4, 0x0); + channel.Write(ClassId.Threed, 0xAF4, 0x0); + channel.Write(ClassId.Threed, 0xB14, 0x0); + channel.Write(ClassId.Threed, 0xB34, 0x0); + channel.Write(ClassId.Threed, 0xB54, 0x0); + channel.Write(ClassId.Threed, 0xB74, 0x0); + channel.Write(ClassId.Threed, 0xB94, 0x0); + channel.Write(ClassId.Threed, 0xBB4, 0x0); + channel.Write(ClassId.Threed, 0xBD4, 0x0); + channel.Write(ClassId.Threed, 0xBF4, 0x0); + channel.Write(ClassId.Threed, 0xA18, 0x6420); + channel.Write(ClassId.Threed, 0xA38, 0x6420); + channel.Write(ClassId.Threed, 0xA58, 0x6420); + channel.Write(ClassId.Threed, 0xA78, 0x6420); + channel.Write(ClassId.Threed, 0xA98, 0x6420); + channel.Write(ClassId.Threed, 0xAB8, 0x6420); + channel.Write(ClassId.Threed, 0xAD8, 0x6420); + channel.Write(ClassId.Threed, 0xAF8, 0x6420); + channel.Write(ClassId.Threed, 0xB18, 0x6420); + channel.Write(ClassId.Threed, 0xB38, 0x6420); + channel.Write(ClassId.Threed, 0xB58, 0x6420); + channel.Write(ClassId.Threed, 0xB78, 0x6420); + channel.Write(ClassId.Threed, 0xB98, 0x6420); + channel.Write(ClassId.Threed, 0xBB8, 0x6420); + channel.Write(ClassId.Threed, 0xBD8, 0x6420); + channel.Write(ClassId.Threed, 0xBF8, 0x6420); + channel.Write(ClassId.Threed, 0xA1C, 0x0); + channel.Write(ClassId.Threed, 0xA3C, 0x0); + channel.Write(ClassId.Threed, 0xA5C, 0x0); + channel.Write(ClassId.Threed, 0xA7C, 0x0); + channel.Write(ClassId.Threed, 0xA9C, 0x0); + channel.Write(ClassId.Threed, 0xABC, 0x0); + channel.Write(ClassId.Threed, 0xADC, 0x0); + channel.Write(ClassId.Threed, 0xAFC, 0x0); + channel.Write(ClassId.Threed, 0xB1C, 0x0); + channel.Write(ClassId.Threed, 0xB3C, 0x0); + channel.Write(ClassId.Threed, 0xB5C, 0x0); + channel.Write(ClassId.Threed, 0xB7C, 0x0); + channel.Write(ClassId.Threed, 0xB9C, 0x0); + channel.Write(ClassId.Threed, 0xBBC, 0x0); + channel.Write(ClassId.Threed, 0xBDC, 0x0); + channel.Write(ClassId.Threed, 0xBFC, 0x0); + channel.Write(ClassId.Threed, 0xC00, 0x0); + channel.Write(ClassId.Threed, 0xC10, 0x0); + channel.Write(ClassId.Threed, 0xC20, 0x0); + channel.Write(ClassId.Threed, 0xC30, 0x0); + channel.Write(ClassId.Threed, 0xC40, 0x0); + channel.Write(ClassId.Threed, 0xC50, 0x0); + channel.Write(ClassId.Threed, 0xC60, 0x0); + channel.Write(ClassId.Threed, 0xC70, 0x0); + channel.Write(ClassId.Threed, 0xC80, 0x0); + channel.Write(ClassId.Threed, 0xC90, 0x0); + channel.Write(ClassId.Threed, 0xCA0, 0x0); + channel.Write(ClassId.Threed, 0xCB0, 0x0); + channel.Write(ClassId.Threed, 0xCC0, 0x0); + channel.Write(ClassId.Threed, 0xCD0, 0x0); + channel.Write(ClassId.Threed, 0xCE0, 0x0); + channel.Write(ClassId.Threed, 0xCF0, 0x0); + channel.Write(ClassId.Threed, 0xC04, 0x0); + channel.Write(ClassId.Threed, 0xC14, 0x0); + channel.Write(ClassId.Threed, 0xC24, 0x0); + channel.Write(ClassId.Threed, 0xC34, 0x0); + channel.Write(ClassId.Threed, 0xC44, 0x0); + channel.Write(ClassId.Threed, 0xC54, 0x0); + channel.Write(ClassId.Threed, 0xC64, 0x0); + channel.Write(ClassId.Threed, 0xC74, 0x0); + channel.Write(ClassId.Threed, 0xC84, 0x0); + channel.Write(ClassId.Threed, 0xC94, 0x0); + channel.Write(ClassId.Threed, 0xCA4, 0x0); + channel.Write(ClassId.Threed, 0xCB4, 0x0); + channel.Write(ClassId.Threed, 0xCC4, 0x0); + channel.Write(ClassId.Threed, 0xCD4, 0x0); + channel.Write(ClassId.Threed, 0xCE4, 0x0); + channel.Write(ClassId.Threed, 0xCF4, 0x0); + channel.Write(ClassId.Threed, 0xC08, 0x0); + channel.Write(ClassId.Threed, 0xC18, 0x0); + channel.Write(ClassId.Threed, 0xC28, 0x0); + channel.Write(ClassId.Threed, 0xC38, 0x0); + channel.Write(ClassId.Threed, 0xC48, 0x0); + channel.Write(ClassId.Threed, 0xC58, 0x0); + channel.Write(ClassId.Threed, 0xC68, 0x0); + channel.Write(ClassId.Threed, 0xC78, 0x0); + channel.Write(ClassId.Threed, 0xC88, 0x0); + channel.Write(ClassId.Threed, 0xC98, 0x0); + channel.Write(ClassId.Threed, 0xCA8, 0x0); + channel.Write(ClassId.Threed, 0xCB8, 0x0); + channel.Write(ClassId.Threed, 0xCC8, 0x0); + channel.Write(ClassId.Threed, 0xCD8, 0x0); + channel.Write(ClassId.Threed, 0xCE8, 0x0); + channel.Write(ClassId.Threed, 0xCF8, 0x0); + channel.Write(ClassId.Threed, 0xC0C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC1C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC2C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC3C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC4C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC5C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC6C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC7C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC8C, 0x3F800000); + channel.Write(ClassId.Threed, 0xC9C, 0x3F800000); + channel.Write(ClassId.Threed, 0xCAC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCBC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCCC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCDC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCEC, 0x3F800000); + channel.Write(ClassId.Threed, 0xCFC, 0x3F800000); + channel.Write(ClassId.Threed, 0xD00, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD08, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD10, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD18, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD20, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD28, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD30, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD38, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD04, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD0C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD14, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD1C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD24, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD2C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD34, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD3C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE00, 0x0); + channel.Write(ClassId.Threed, 0xE10, 0x0); + channel.Write(ClassId.Threed, 0xE20, 0x0); + channel.Write(ClassId.Threed, 0xE30, 0x0); + channel.Write(ClassId.Threed, 0xE40, 0x0); + channel.Write(ClassId.Threed, 0xE50, 0x0); + channel.Write(ClassId.Threed, 0xE60, 0x0); + channel.Write(ClassId.Threed, 0xE70, 0x0); + channel.Write(ClassId.Threed, 0xE80, 0x0); + channel.Write(ClassId.Threed, 0xE90, 0x0); + channel.Write(ClassId.Threed, 0xEA0, 0x0); + channel.Write(ClassId.Threed, 0xEB0, 0x0); + channel.Write(ClassId.Threed, 0xEC0, 0x0); + channel.Write(ClassId.Threed, 0xED0, 0x0); + channel.Write(ClassId.Threed, 0xEE0, 0x0); + channel.Write(ClassId.Threed, 0xEF0, 0x0); + channel.Write(ClassId.Threed, 0xE04, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE14, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE24, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE34, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE44, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE54, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE64, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE74, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE84, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE94, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEA4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEB4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEC4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xED4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEE4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEF4, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE08, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE18, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE28, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE38, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE48, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE58, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE68, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE78, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE88, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xE98, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEA8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEB8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEC8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xED8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEE8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xEF8, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD40, 0x0); + channel.Write(ClassId.Threed, 0xD48, 0x0); + channel.Write(ClassId.Threed, 0xD50, 0x0); + channel.Write(ClassId.Threed, 0xD58, 0x0); + channel.Write(ClassId.Threed, 0xD44, 0x0); + channel.Write(ClassId.Threed, 0xD4C, 0x0); + channel.Write(ClassId.Threed, 0xD54, 0x0); + channel.Write(ClassId.Threed, 0xD5C, 0x0); + channel.Write(ClassId.Threed, 0x1E00, 0x1); + channel.Write(ClassId.Threed, 0x1E20, 0x1); + channel.Write(ClassId.Threed, 0x1E40, 0x1); + channel.Write(ClassId.Threed, 0x1E60, 0x1); + channel.Write(ClassId.Threed, 0x1E80, 0x1); + channel.Write(ClassId.Threed, 0x1EA0, 0x1); + channel.Write(ClassId.Threed, 0x1EC0, 0x1); + channel.Write(ClassId.Threed, 0x1EE0, 0x1); + channel.Write(ClassId.Threed, 0x1E04, 0x1); + channel.Write(ClassId.Threed, 0x1E24, 0x1); + channel.Write(ClassId.Threed, 0x1E44, 0x1); + channel.Write(ClassId.Threed, 0x1E64, 0x1); + channel.Write(ClassId.Threed, 0x1E84, 0x1); + channel.Write(ClassId.Threed, 0x1EA4, 0x1); + channel.Write(ClassId.Threed, 0x1EC4, 0x1); + channel.Write(ClassId.Threed, 0x1EE4, 0x1); + channel.Write(ClassId.Threed, 0x1E08, 0x2); + channel.Write(ClassId.Threed, 0x1E28, 0x2); + channel.Write(ClassId.Threed, 0x1E48, 0x2); + channel.Write(ClassId.Threed, 0x1E68, 0x2); + channel.Write(ClassId.Threed, 0x1E88, 0x2); + channel.Write(ClassId.Threed, 0x1EA8, 0x2); + channel.Write(ClassId.Threed, 0x1EC8, 0x2); + channel.Write(ClassId.Threed, 0x1EE8, 0x2); + channel.Write(ClassId.Threed, 0x1E0C, 0x1); + channel.Write(ClassId.Threed, 0x1E2C, 0x1); + channel.Write(ClassId.Threed, 0x1E4C, 0x1); + channel.Write(ClassId.Threed, 0x1E6C, 0x1); + channel.Write(ClassId.Threed, 0x1E8C, 0x1); + channel.Write(ClassId.Threed, 0x1EAC, 0x1); + channel.Write(ClassId.Threed, 0x1ECC, 0x1); + channel.Write(ClassId.Threed, 0x1EEC, 0x1); + channel.Write(ClassId.Threed, 0x1E10, 0x1); + channel.Write(ClassId.Threed, 0x1E30, 0x1); + channel.Write(ClassId.Threed, 0x1E50, 0x1); + channel.Write(ClassId.Threed, 0x1E70, 0x1); + channel.Write(ClassId.Threed, 0x1E90, 0x1); + channel.Write(ClassId.Threed, 0x1EB0, 0x1); + channel.Write(ClassId.Threed, 0x1ED0, 0x1); + channel.Write(ClassId.Threed, 0x1EF0, 0x1); + channel.Write(ClassId.Threed, 0x1E14, 0x2); + channel.Write(ClassId.Threed, 0x1E34, 0x2); + channel.Write(ClassId.Threed, 0x1E54, 0x2); + channel.Write(ClassId.Threed, 0x1E74, 0x2); + channel.Write(ClassId.Threed, 0x1E94, 0x2); + channel.Write(ClassId.Threed, 0x1EB4, 0x2); + channel.Write(ClassId.Threed, 0x1ED4, 0x2); + channel.Write(ClassId.Threed, 0x1EF4, 0x2); + channel.Write(ClassId.Threed, 0x1E18, 0x1); + channel.Write(ClassId.Threed, 0x1E38, 0x1); + channel.Write(ClassId.Threed, 0x1E58, 0x1); + channel.Write(ClassId.Threed, 0x1E78, 0x1); + channel.Write(ClassId.Threed, 0x1E98, 0x1); + channel.Write(ClassId.Threed, 0x1EB8, 0x1); + channel.Write(ClassId.Threed, 0x1ED8, 0x1); + channel.Write(ClassId.Threed, 0x1EF8, 0x1); + channel.Write(ClassId.Threed, 0x1480, 0x0); + channel.Write(ClassId.Threed, 0x1490, 0x0); + channel.Write(ClassId.Threed, 0x14A0, 0x0); + channel.Write(ClassId.Threed, 0x14B0, 0x0); + channel.Write(ClassId.Threed, 0x14C0, 0x0); + channel.Write(ClassId.Threed, 0x14D0, 0x0); + channel.Write(ClassId.Threed, 0x14E0, 0x0); + channel.Write(ClassId.Threed, 0x14F0, 0x0); + channel.Write(ClassId.Threed, 0x1484, 0x0); + channel.Write(ClassId.Threed, 0x1494, 0x0); + channel.Write(ClassId.Threed, 0x14A4, 0x0); + channel.Write(ClassId.Threed, 0x14B4, 0x0); + channel.Write(ClassId.Threed, 0x14C4, 0x0); + channel.Write(ClassId.Threed, 0x14D4, 0x0); + channel.Write(ClassId.Threed, 0x14E4, 0x0); + channel.Write(ClassId.Threed, 0x14F4, 0x0); + channel.Write(ClassId.Threed, 0x1488, 0x0); + channel.Write(ClassId.Threed, 0x1498, 0x0); + channel.Write(ClassId.Threed, 0x14A8, 0x0); + channel.Write(ClassId.Threed, 0x14B8, 0x0); + channel.Write(ClassId.Threed, 0x14C8, 0x0); + channel.Write(ClassId.Threed, 0x14D8, 0x0); + channel.Write(ClassId.Threed, 0x14E8, 0x0); + channel.Write(ClassId.Threed, 0x14F8, 0x0); + channel.Write(ClassId.Threed, 0x3400, 0x0); + channel.Write(ClassId.Threed, 0x3404, 0x0); + channel.Write(ClassId.Threed, 0x3408, 0x0); + channel.Write(ClassId.Threed, 0x340C, 0x0); + channel.Write(ClassId.Threed, 0x3410, 0x0); + channel.Write(ClassId.Threed, 0x3414, 0x0); + channel.Write(ClassId.Threed, 0x3418, 0x0); + channel.Write(ClassId.Threed, 0x341C, 0x0); + channel.Write(ClassId.Threed, 0x3420, 0x0); + channel.Write(ClassId.Threed, 0x3424, 0x0); + channel.Write(ClassId.Threed, 0x3428, 0x0); + channel.Write(ClassId.Threed, 0x342C, 0x0); + channel.Write(ClassId.Threed, 0x3430, 0x0); + channel.Write(ClassId.Threed, 0x3434, 0x0); + channel.Write(ClassId.Threed, 0x3438, 0x0); + channel.Write(ClassId.Threed, 0x343C, 0x0); + channel.Write(ClassId.Threed, 0x3440, 0x0); + channel.Write(ClassId.Threed, 0x3444, 0x0); + channel.Write(ClassId.Threed, 0x3448, 0x0); + channel.Write(ClassId.Threed, 0x344C, 0x0); + channel.Write(ClassId.Threed, 0x3450, 0x0); + channel.Write(ClassId.Threed, 0x3454, 0x0); + channel.Write(ClassId.Threed, 0x3458, 0x0); + channel.Write(ClassId.Threed, 0x345C, 0x0); + channel.Write(ClassId.Threed, 0x3460, 0x0); + channel.Write(ClassId.Threed, 0x3464, 0x0); + channel.Write(ClassId.Threed, 0x3468, 0x0); + channel.Write(ClassId.Threed, 0x346C, 0x0); + channel.Write(ClassId.Threed, 0x3470, 0x0); + channel.Write(ClassId.Threed, 0x3474, 0x0); + channel.Write(ClassId.Threed, 0x3478, 0x0); + channel.Write(ClassId.Threed, 0x347C, 0x0); + channel.Write(ClassId.Threed, 0x3480, 0x0); + channel.Write(ClassId.Threed, 0x3484, 0x0); + channel.Write(ClassId.Threed, 0x3488, 0x0); + channel.Write(ClassId.Threed, 0x348C, 0x0); + channel.Write(ClassId.Threed, 0x3490, 0x0); + channel.Write(ClassId.Threed, 0x3494, 0x0); + channel.Write(ClassId.Threed, 0x3498, 0x0); + channel.Write(ClassId.Threed, 0x349C, 0x0); + channel.Write(ClassId.Threed, 0x34A0, 0x0); + channel.Write(ClassId.Threed, 0x34A4, 0x0); + channel.Write(ClassId.Threed, 0x34A8, 0x0); + channel.Write(ClassId.Threed, 0x34AC, 0x0); + channel.Write(ClassId.Threed, 0x34B0, 0x0); + channel.Write(ClassId.Threed, 0x34B4, 0x0); + channel.Write(ClassId.Threed, 0x34B8, 0x0); + channel.Write(ClassId.Threed, 0x34BC, 0x0); + channel.Write(ClassId.Threed, 0x34C0, 0x0); + channel.Write(ClassId.Threed, 0x34C4, 0x0); + channel.Write(ClassId.Threed, 0x34C8, 0x0); + channel.Write(ClassId.Threed, 0x34CC, 0x0); + channel.Write(ClassId.Threed, 0x34D0, 0x0); + channel.Write(ClassId.Threed, 0x34D4, 0x0); + channel.Write(ClassId.Threed, 0x34D8, 0x0); + channel.Write(ClassId.Threed, 0x34DC, 0x0); + channel.Write(ClassId.Threed, 0x34E0, 0x0); + channel.Write(ClassId.Threed, 0x34E4, 0x0); + channel.Write(ClassId.Threed, 0x34E8, 0x0); + channel.Write(ClassId.Threed, 0x34EC, 0x0); + channel.Write(ClassId.Threed, 0x34F0, 0x0); + channel.Write(ClassId.Threed, 0x34F4, 0x0); + channel.Write(ClassId.Threed, 0x34F8, 0x0); + channel.Write(ClassId.Threed, 0x34FC, 0x0); + channel.Write(ClassId.Threed, 0x3500, 0x0); + channel.Write(ClassId.Threed, 0x3504, 0x0); + channel.Write(ClassId.Threed, 0x3508, 0x0); + channel.Write(ClassId.Threed, 0x350C, 0x0); + channel.Write(ClassId.Threed, 0x3510, 0x0); + channel.Write(ClassId.Threed, 0x3514, 0x0); + channel.Write(ClassId.Threed, 0x3518, 0x0); + channel.Write(ClassId.Threed, 0x351C, 0x0); + channel.Write(ClassId.Threed, 0x3520, 0x0); + channel.Write(ClassId.Threed, 0x3524, 0x0); + channel.Write(ClassId.Threed, 0x3528, 0x0); + channel.Write(ClassId.Threed, 0x352C, 0x0); + channel.Write(ClassId.Threed, 0x3530, 0x0); + channel.Write(ClassId.Threed, 0x3534, 0x0); + channel.Write(ClassId.Threed, 0x3538, 0x0); + channel.Write(ClassId.Threed, 0x353C, 0x0); + channel.Write(ClassId.Threed, 0x3540, 0x0); + channel.Write(ClassId.Threed, 0x3544, 0x0); + channel.Write(ClassId.Threed, 0x3548, 0x0); + channel.Write(ClassId.Threed, 0x354C, 0x0); + channel.Write(ClassId.Threed, 0x3550, 0x0); + channel.Write(ClassId.Threed, 0x3554, 0x0); + channel.Write(ClassId.Threed, 0x3558, 0x0); + channel.Write(ClassId.Threed, 0x355C, 0x0); + channel.Write(ClassId.Threed, 0x3560, 0x0); + channel.Write(ClassId.Threed, 0x3564, 0x0); + channel.Write(ClassId.Threed, 0x3568, 0x0); + channel.Write(ClassId.Threed, 0x356C, 0x0); + channel.Write(ClassId.Threed, 0x3570, 0x0); + channel.Write(ClassId.Threed, 0x3574, 0x0); + channel.Write(ClassId.Threed, 0x3578, 0x0); + channel.Write(ClassId.Threed, 0x357C, 0x0); + channel.Write(ClassId.Threed, 0x3580, 0x0); + channel.Write(ClassId.Threed, 0x3584, 0x0); + channel.Write(ClassId.Threed, 0x3588, 0x0); + channel.Write(ClassId.Threed, 0x358C, 0x0); + channel.Write(ClassId.Threed, 0x3590, 0x0); + channel.Write(ClassId.Threed, 0x3594, 0x0); + channel.Write(ClassId.Threed, 0x3598, 0x0); + channel.Write(ClassId.Threed, 0x359C, 0x0); + channel.Write(ClassId.Threed, 0x35A0, 0x0); + channel.Write(ClassId.Threed, 0x35A4, 0x0); + channel.Write(ClassId.Threed, 0x35A8, 0x0); + channel.Write(ClassId.Threed, 0x35AC, 0x0); + channel.Write(ClassId.Threed, 0x35B0, 0x0); + channel.Write(ClassId.Threed, 0x35B4, 0x0); + channel.Write(ClassId.Threed, 0x35B8, 0x0); + channel.Write(ClassId.Threed, 0x35BC, 0x0); + channel.Write(ClassId.Threed, 0x35C0, 0x0); + channel.Write(ClassId.Threed, 0x35C4, 0x0); + channel.Write(ClassId.Threed, 0x35C8, 0x0); + channel.Write(ClassId.Threed, 0x35CC, 0x0); + channel.Write(ClassId.Threed, 0x35D0, 0x0); + channel.Write(ClassId.Threed, 0x35D4, 0x0); + channel.Write(ClassId.Threed, 0x35D8, 0x0); + channel.Write(ClassId.Threed, 0x35DC, 0x0); + channel.Write(ClassId.Threed, 0x35E0, 0x0); + channel.Write(ClassId.Threed, 0x35E4, 0x0); + channel.Write(ClassId.Threed, 0x35E8, 0x0); + channel.Write(ClassId.Threed, 0x35EC, 0x0); + channel.Write(ClassId.Threed, 0x35F0, 0x0); + channel.Write(ClassId.Threed, 0x35F4, 0x0); + channel.Write(ClassId.Threed, 0x35F8, 0x0); + channel.Write(ClassId.Threed, 0x35FC, 0x0); + channel.Write(ClassId.Threed, 0x30C, 0x1); + channel.Write(ClassId.Threed, 0x1944, 0x0); + channel.Write(ClassId.Threed, 0x1514, 0x0); + channel.Write(ClassId.Threed, 0xD68, 0xFFFF); + channel.Write(ClassId.Threed, 0x121C, 0xFAC6881); + channel.Write(ClassId.Threed, 0xFAC, 0x1); + channel.Write(ClassId.Threed, 0x1538, 0x1); + channel.Write(ClassId.Threed, 0xFE0, 0x0); + channel.Write(ClassId.Threed, 0xFE4, 0x0); + channel.Write(ClassId.Threed, 0xFE8, 0x14); + channel.Write(ClassId.Threed, 0xFEC, 0x40); + channel.Write(ClassId.Threed, 0xFF0, 0x0); + channel.Write(ClassId.Threed, 0x179C, 0x0); + channel.Write(ClassId.Threed, 0x1228, 0x400); + channel.Write(ClassId.Threed, 0x122C, 0x300); + channel.Write(ClassId.Threed, 0x1230, 0x10001); + channel.Write(ClassId.Threed, 0x7F8, 0x0); + channel.Write(ClassId.Threed, 0x1208, 0x0); + channel.Write(ClassId.Threed, 0x15B4, 0x1); + channel.Write(ClassId.Threed, 0x15CC, 0x0); + channel.Write(ClassId.Threed, 0x1534, 0x0); + channel.Write(ClassId.Threed, 0x754, 0x1); + channel.Write(ClassId.Threed, 0xFB0, 0x0); + channel.Write(ClassId.Threed, 0x15D0, 0x0); + channel.Write(ClassId.Threed, 0x11E0, 0x88888888); + channel.Write(ClassId.Threed, 0x11E4, 0x88888888); + channel.Write(ClassId.Threed, 0x11E8, 0x88888888); + channel.Write(ClassId.Threed, 0x11EC, 0x88888888); + channel.Write(ClassId.Threed, 0x153C, 0x0); + channel.Write(ClassId.Threed, 0x16B4, 0x3); + channel.Write(ClassId.Threed, 0xFA4, 0x1); + channel.Write(ClassId.Threed, 0xFBC, 0xFFFF); + channel.Write(ClassId.Threed, 0xFC0, 0xFFFF); + channel.Write(ClassId.Threed, 0xFC4, 0xFFFF); + channel.Write(ClassId.Threed, 0xFC8, 0xFFFF); + channel.Write(ClassId.Threed, 0xFA8, 0xFFFF); + channel.Write(ClassId.Threed, 0xDF8, 0x0); + channel.Write(ClassId.Threed, 0xDFC, 0x0); + channel.Write(ClassId.Threed, 0x1948, 0x0); + channel.Write(ClassId.Threed, 0x1970, 0x1); + channel.Write(ClassId.Threed, 0x161C, 0x9F0); + channel.Write(ClassId.Threed, 0xDCC, 0x10); + channel.Write(ClassId.Threed, 0x15E4, 0x0); + channel.Write(ClassId.Threed, 0x1160, 0x25E00040); + channel.Write(ClassId.Threed, 0x1164, 0x25E00040); + channel.Write(ClassId.Threed, 0x1168, 0x25E00040); + channel.Write(ClassId.Threed, 0x116C, 0x25E00040); + channel.Write(ClassId.Threed, 0x1170, 0x25E00040); + channel.Write(ClassId.Threed, 0x1174, 0x25E00040); + channel.Write(ClassId.Threed, 0x1178, 0x25E00040); + channel.Write(ClassId.Threed, 0x117C, 0x25E00040); + channel.Write(ClassId.Threed, 0x1180, 0x25E00040); + channel.Write(ClassId.Threed, 0x1184, 0x25E00040); + channel.Write(ClassId.Threed, 0x1188, 0x25E00040); + channel.Write(ClassId.Threed, 0x118C, 0x25E00040); + channel.Write(ClassId.Threed, 0x1190, 0x25E00040); + channel.Write(ClassId.Threed, 0x1194, 0x25E00040); + channel.Write(ClassId.Threed, 0x1198, 0x25E00040); + channel.Write(ClassId.Threed, 0x119C, 0x25E00040); + channel.Write(ClassId.Threed, 0x11A0, 0x25E00040); + channel.Write(ClassId.Threed, 0x11A4, 0x25E00040); + channel.Write(ClassId.Threed, 0x11A8, 0x25E00040); + channel.Write(ClassId.Threed, 0x11AC, 0x25E00040); + channel.Write(ClassId.Threed, 0x11B0, 0x25E00040); + channel.Write(ClassId.Threed, 0x11B4, 0x25E00040); + channel.Write(ClassId.Threed, 0x11B8, 0x25E00040); + channel.Write(ClassId.Threed, 0x11BC, 0x25E00040); + channel.Write(ClassId.Threed, 0x11C0, 0x25E00040); + channel.Write(ClassId.Threed, 0x11C4, 0x25E00040); + channel.Write(ClassId.Threed, 0x11C8, 0x25E00040); + channel.Write(ClassId.Threed, 0x11CC, 0x25E00040); + channel.Write(ClassId.Threed, 0x11D0, 0x25E00040); + channel.Write(ClassId.Threed, 0x11D4, 0x25E00040); + channel.Write(ClassId.Threed, 0x11D8, 0x25E00040); + channel.Write(ClassId.Threed, 0x11DC, 0x25E00040); + channel.Write(ClassId.Threed, 0x1880, 0x0); + channel.Write(ClassId.Threed, 0x1884, 0x0); + channel.Write(ClassId.Threed, 0x1888, 0x0); + channel.Write(ClassId.Threed, 0x188C, 0x0); + channel.Write(ClassId.Threed, 0x1890, 0x0); + channel.Write(ClassId.Threed, 0x1894, 0x0); + channel.Write(ClassId.Threed, 0x1898, 0x0); + channel.Write(ClassId.Threed, 0x189C, 0x0); + channel.Write(ClassId.Threed, 0x18A0, 0x0); + channel.Write(ClassId.Threed, 0x18A4, 0x0); + channel.Write(ClassId.Threed, 0x18A8, 0x0); + channel.Write(ClassId.Threed, 0x18AC, 0x0); + channel.Write(ClassId.Threed, 0x18B0, 0x0); + channel.Write(ClassId.Threed, 0x18B4, 0x0); + channel.Write(ClassId.Threed, 0x18B8, 0x0); + channel.Write(ClassId.Threed, 0x18BC, 0x0); + channel.Write(ClassId.Threed, 0x18C0, 0x0); + channel.Write(ClassId.Threed, 0x18C4, 0x0); + channel.Write(ClassId.Threed, 0x18C8, 0x0); + channel.Write(ClassId.Threed, 0x18CC, 0x0); + channel.Write(ClassId.Threed, 0x18D0, 0x0); + channel.Write(ClassId.Threed, 0x18D4, 0x0); + channel.Write(ClassId.Threed, 0x18D8, 0x0); + channel.Write(ClassId.Threed, 0x18DC, 0x0); + channel.Write(ClassId.Threed, 0x18E0, 0x0); + channel.Write(ClassId.Threed, 0x18E4, 0x0); + channel.Write(ClassId.Threed, 0x18E8, 0x0); + channel.Write(ClassId.Threed, 0x18EC, 0x0); + channel.Write(ClassId.Threed, 0x18F0, 0x0); + channel.Write(ClassId.Threed, 0x18F4, 0x0); + channel.Write(ClassId.Threed, 0x18F8, 0x0); + channel.Write(ClassId.Threed, 0x18FC, 0x0); + channel.Write(ClassId.Threed, 0xF84, 0x0); + channel.Write(ClassId.Threed, 0xF88, 0x0); + channel.Write(ClassId.Threed, 0x17C8, 0x0); + channel.Write(ClassId.Threed, 0x17CC, 0x0); + channel.Write(ClassId.Threed, 0x17D0, 0xFF); + channel.Write(ClassId.Threed, 0x17D4, 0xFFFFFFFF); + channel.Write(ClassId.Threed, 0x17D8, 0x2); + channel.Write(ClassId.Threed, 0x17DC, 0x0); + channel.Write(ClassId.Threed, 0x15F4, 0x0); + channel.Write(ClassId.Threed, 0x15F8, 0x0); + channel.Write(ClassId.Threed, 0x1434, 0x0); + channel.Write(ClassId.Threed, 0x1438, 0x0); + channel.Write(ClassId.Threed, 0xD74, 0x0); + channel.Write(ClassId.Threed, 0x13A4, 0x0); + channel.Write(ClassId.Threed, 0x1318, 0x1); + channel.Write(ClassId.Threed, 0x1080, 0x0); + channel.Write(ClassId.Threed, 0x1084, 0x0); + channel.Write(ClassId.Threed, 0x1088, 0x1); + channel.Write(ClassId.Threed, 0x108C, 0x1); + channel.Write(ClassId.Threed, 0x1090, 0x0); + channel.Write(ClassId.Threed, 0x1094, 0x1); + channel.Write(ClassId.Threed, 0x1098, 0x0); + channel.Write(ClassId.Threed, 0x109C, 0x1); + channel.Write(ClassId.Threed, 0x10A0, 0x0); + channel.Write(ClassId.Threed, 0x10A4, 0x0); + channel.Write(ClassId.Threed, 0x1644, 0x0); + channel.Write(ClassId.Threed, 0x748, 0x0); + channel.Write(ClassId.Threed, 0xDE8, 0x0); + channel.Write(ClassId.Threed, 0x1648, 0x0); + channel.Write(ClassId.Threed, 0x12A4, 0x0); + channel.Write(ClassId.Threed, 0x1120, 0x0); + channel.Write(ClassId.Threed, 0x1124, 0x0); + channel.Write(ClassId.Threed, 0x1128, 0x0); + channel.Write(ClassId.Threed, 0x112C, 0x0); + channel.Write(ClassId.Threed, 0x1118, 0x0); + channel.Write(ClassId.Threed, 0x164C, 0x0); + channel.Write(ClassId.Threed, 0x1658, 0x0); + channel.Write(ClassId.Threed, 0x1910, 0x290); + channel.Write(ClassId.Threed, 0x1518, 0x0); + channel.Write(ClassId.Threed, 0x165C, 0x1); + channel.Write(ClassId.Threed, 0x1520, 0x0); + channel.Write(ClassId.Threed, 0x1604, 0x0); + channel.Write(ClassId.Threed, 0x1570, 0x0); + channel.Write(ClassId.Threed, 0x13B0, 0x3F800000); + channel.Write(ClassId.Threed, 0x13B4, 0x3F800000); + channel.Write(ClassId.Threed, 0x20C, 0x0); + channel.Write(ClassId.Threed, 0x1670, 0x30201000); + channel.Write(ClassId.Threed, 0x1674, 0x70605040); + channel.Write(ClassId.Threed, 0x1678, 0xB8A89888); + channel.Write(ClassId.Threed, 0x167C, 0xF8E8D8C8); + channel.Write(ClassId.Threed, 0x166C, 0x0); + channel.Write(ClassId.Threed, 0x1680, 0xFFFF00); + channel.Write(ClassId.Threed, 0x12D0, 0x3); + channel.Write(ClassId.Threed, 0x113C, 0x0); + channel.Write(ClassId.Threed, 0x12D4, 0x2); + channel.Write(ClassId.Threed, 0x1684, 0x0); + channel.Write(ClassId.Threed, 0x1688, 0x0); + channel.Write(ClassId.Threed, 0xDAC, 0x1B02); + channel.Write(ClassId.Threed, 0xDB0, 0x1B02); + channel.Write(ClassId.Threed, 0xDB4, 0x0); + channel.Write(ClassId.Threed, 0x168C, 0x0); + channel.Write(ClassId.Threed, 0x15BC, 0x0); + channel.Write(ClassId.Threed, 0x156C, 0x0); + channel.Write(ClassId.Threed, 0x187C, 0x0); + channel.Write(ClassId.Threed, 0x1110, 0x1); + channel.Write(ClassId.Threed, 0xDC0, 0x0); + channel.Write(ClassId.Threed, 0xDC4, 0x0); + channel.Write(ClassId.Threed, 0xDC8, 0x0); + channel.Write(ClassId.Threed, 0xF40, 0x0); + channel.Write(ClassId.Threed, 0xF44, 0x0); + channel.Write(ClassId.Threed, 0xF48, 0x0); + channel.Write(ClassId.Threed, 0xF4C, 0x0); + channel.Write(ClassId.Threed, 0xF50, 0x0); + channel.Write(ClassId.Threed, 0x1234, 0x0); + channel.Write(ClassId.Threed, 0x1690, 0x0); + channel.Write(ClassId.Threed, 0x790, 0x0); + channel.Write(ClassId.Threed, 0x794, 0x0); + channel.Write(ClassId.Threed, 0x798, 0x0); + channel.Write(ClassId.Threed, 0x79C, 0x0); + channel.Write(ClassId.Threed, 0x7A0, 0x0); + channel.Write(ClassId.Threed, 0x77C, 0x0); + channel.Write(ClassId.Threed, 0x1000, 0x10); + channel.Write(ClassId.Threed, 0x10FC, 0x0); + channel.Write(ClassId.Threed, 0x1290, 0x0); + channel.Write(ClassId.Threed, 0x218, 0x10); + channel.Write(ClassId.Threed, 0x12D8, 0x0); + channel.Write(ClassId.Threed, 0x12DC, 0x10); + channel.Write(ClassId.Threed, 0xD94, 0x1); + channel.Write(ClassId.Threed, 0x155C, 0x0); + channel.Write(ClassId.Threed, 0x1560, 0x0); + channel.Write(ClassId.Threed, 0x1564, 0xFFF); + channel.Write(ClassId.Threed, 0x1574, 0x0); + channel.Write(ClassId.Threed, 0x1578, 0x0); + channel.Write(ClassId.Threed, 0x157C, 0xFFFFF); + channel.Write(ClassId.Threed, 0x1354, 0x0); + channel.Write(ClassId.Threed, 0x1610, 0x12); + channel.Write(ClassId.Threed, 0x1608, 0x0); + channel.Write(ClassId.Threed, 0x160C, 0x0); + channel.Write(ClassId.Threed, 0x260C, 0x0); + channel.Write(ClassId.Threed, 0x7AC, 0x0); + channel.Write(ClassId.Threed, 0x162C, 0x3); + channel.Write(ClassId.Threed, 0x210, 0x0); + channel.Write(ClassId.Threed, 0x320, 0x0); + channel.Write(ClassId.Threed, 0x324, 0x3F800000); + channel.Write(ClassId.Threed, 0x328, 0x3F800000); + channel.Write(ClassId.Threed, 0x32C, 0x3F800000); + channel.Write(ClassId.Threed, 0x330, 0x3F800000); + channel.Write(ClassId.Threed, 0x334, 0x3F800000); + channel.Write(ClassId.Threed, 0x338, 0x3F800000); + channel.Write(ClassId.Threed, 0x750, 0x0); + channel.Write(ClassId.Threed, 0x760, 0x39291909); + channel.Write(ClassId.Threed, 0x764, 0x79695949); + channel.Write(ClassId.Threed, 0x768, 0xB9A99989); + channel.Write(ClassId.Threed, 0x76C, 0xF9E9D9C9); + channel.Write(ClassId.Threed, 0x770, 0x30201000); + channel.Write(ClassId.Threed, 0x774, 0x70605040); + channel.Write(ClassId.Threed, 0x778, 0x9080); + channel.Write(ClassId.Threed, 0x780, 0x39291909); + channel.Write(ClassId.Threed, 0x784, 0x79695949); + channel.Write(ClassId.Threed, 0x788, 0xB9A99989); + channel.Write(ClassId.Threed, 0x78C, 0xF9E9D9C9); + channel.Write(ClassId.Threed, 0x7D0, 0x30201000); + channel.Write(ClassId.Threed, 0x7D4, 0x70605040); + channel.Write(ClassId.Threed, 0x7D8, 0x9080); + channel.Write(ClassId.Threed, 0x1004, 0x0); + channel.Write(ClassId.Threed, 0x1240, 0x0); + channel.Write(ClassId.Threed, 0x1244, 0x0); + channel.Write(ClassId.Threed, 0x1248, 0x0); + channel.Write(ClassId.Threed, 0x124C, 0x0); + channel.Write(ClassId.Threed, 0x1250, 0x0); + channel.Write(ClassId.Threed, 0x1254, 0x0); + channel.Write(ClassId.Threed, 0x1258, 0x0); + channel.Write(ClassId.Threed, 0x125C, 0x0); + channel.Write(ClassId.Threed, 0x37C, 0x1); + channel.Write(ClassId.Threed, 0x740, 0x0); + channel.Write(ClassId.Threed, 0x1148, 0x0); + channel.Write(ClassId.Threed, 0xFB4, 0x0); + channel.Write(ClassId.Threed, 0xFB8, 0x2); + channel.Write(ClassId.Threed, 0x1130, 0x2); + channel.Write(ClassId.Threed, 0xFD4, 0x0); + channel.Write(ClassId.Threed, 0xFD8, 0x0); + channel.Write(ClassId.Threed, 0x1030, 0x20181008); + channel.Write(ClassId.Threed, 0x1034, 0x40383028); + channel.Write(ClassId.Threed, 0x1038, 0x60585048); + channel.Write(ClassId.Threed, 0x103C, 0x80787068); + channel.Write(ClassId.Threed, 0x744, 0x0); + channel.Write(ClassId.Threed, 0x2600, 0x0); + channel.Write(ClassId.Threed, 0x1918, 0x0); + channel.Write(ClassId.Threed, 0x191C, 0x900); + channel.Write(ClassId.Threed, 0x1920, 0x405); + channel.Write(ClassId.Threed, 0x1308, 0x1); + channel.Write(ClassId.Threed, 0x1924, 0x0); + channel.Write(ClassId.Threed, 0x13AC, 0x0); + channel.Write(ClassId.Threed, 0x192C, 0x1); + channel.Write(ClassId.Threed, 0x193C, 0x2C1C); + channel.Write(ClassId.Threed, 0xD7C, 0x0); + channel.Write(ClassId.Threed, 0xF8C, 0x0); + channel.Write(ClassId.Threed, 0x2C0, 0x1); + channel.Write(ClassId.Threed, 0x1510, 0x0); + channel.Write(ClassId.Threed, 0x1940, 0x0); + channel.Write(ClassId.Threed, 0xFF4, 0x0); + channel.Write(ClassId.Threed, 0xFF8, 0x0); + channel.Write(ClassId.Threed, 0x194C, 0x0); + channel.Write(ClassId.Threed, 0x1950, 0x0); + channel.Write(ClassId.Threed, 0x1968, 0x0); + channel.Write(ClassId.Threed, 0x1590, 0x3F); + channel.Write(ClassId.Threed, 0x7E8, 0x0); + channel.Write(ClassId.Threed, 0x7EC, 0x0); + channel.Write(ClassId.Threed, 0x7F0, 0x0); + channel.Write(ClassId.Threed, 0x7F4, 0x0); + channel.Write(ClassId.Threed, 0x196C, 0x11); + channel.Write(ClassId.Threed, 0x2E4, 0xB001); + channel.Write(ClassId.Threed, 0x36C, 0x0); + channel.Write(ClassId.Threed, 0x370, 0x0); + channel.Write(ClassId.Threed, 0x197C, 0x0); + channel.Write(ClassId.Threed, 0xFCC, 0x0); + channel.Write(ClassId.Threed, 0xFD0, 0x0); + channel.Write(ClassId.Threed, 0x2D8, 0x40); + channel.Write(ClassId.Threed, 0x1980, 0x80); + channel.Write(ClassId.Threed, 0x1504, 0x80); + channel.Write(ClassId.Threed, 0x1984, 0x0); + channel.Write(ClassId.Threed, 0xF60, 0x0); + channel.Write(ClassId.Threed, 0xF64, 0x400040); + channel.Write(ClassId.Threed, 0xF68, 0x2212); + channel.Write(ClassId.Threed, 0xF6C, 0x8080203); + channel.Write(ClassId.Threed, 0x1108, 0x8); + channel.Write(ClassId.Threed, 0xF70, 0x80001); + channel.Write(ClassId.Threed, 0xFFC, 0x0); + channel.Write(ClassId.Threed, 0x1134, 0x0); + channel.Write(ClassId.Threed, 0xF1C, 0x0); + channel.Write(ClassId.Threed, 0x11F8, 0x0); + channel.Write(ClassId.Threed, 0x1138, 0x1); + channel.Write(ClassId.Threed, 0x300, 0x1); + channel.Write(ClassId.Threed, 0x13A8, 0x0); + channel.Write(ClassId.Threed, 0x1224, 0x0); + channel.Write(ClassId.Threed, 0x12EC, 0x0); + channel.Write(ClassId.Threed, 0x1310, 0x0); + channel.Write(ClassId.Threed, 0x1314, 0x1); + channel.Write(ClassId.Threed, 0x1380, 0x0); + channel.Write(ClassId.Threed, 0x1384, 0x1); + channel.Write(ClassId.Threed, 0x1388, 0x1); + channel.Write(ClassId.Threed, 0x138C, 0x1); + channel.Write(ClassId.Threed, 0x1390, 0x1); + channel.Write(ClassId.Threed, 0x1394, 0x0); + channel.Write(ClassId.Threed, 0x139C, 0x0); + channel.Write(ClassId.Threed, 0x1398, 0x0); + channel.Write(ClassId.Threed, 0x1594, 0x0); + channel.Write(ClassId.Threed, 0x1598, 0x1); + channel.Write(ClassId.Threed, 0x159C, 0x1); + channel.Write(ClassId.Threed, 0x15A0, 0x1); + channel.Write(ClassId.Threed, 0x15A4, 0x1); + channel.Write(ClassId.Threed, 0xF54, 0x0); + channel.Write(ClassId.Threed, 0xF58, 0x0); + channel.Write(ClassId.Threed, 0xF5C, 0x0); + channel.Write(ClassId.Threed, 0x19BC, 0x0); + channel.Write(ClassId.Threed, 0xF9C, 0x0); + channel.Write(ClassId.Threed, 0xFA0, 0x0); + channel.Write(ClassId.Threed, 0x12CC, 0x0); + channel.Write(ClassId.Threed, 0x12E8, 0x0); + channel.Write(ClassId.Threed, 0x130C, 0x1); + channel.Write(ClassId.Threed, 0x1360, 0x0); + channel.Write(ClassId.Threed, 0x1364, 0x0); + channel.Write(ClassId.Threed, 0x1368, 0x0); + channel.Write(ClassId.Threed, 0x136C, 0x0); + channel.Write(ClassId.Threed, 0x1370, 0x0); + channel.Write(ClassId.Threed, 0x1374, 0x0); + channel.Write(ClassId.Threed, 0x1378, 0x0); + channel.Write(ClassId.Threed, 0x137C, 0x0); + channel.Write(ClassId.Threed, 0x133C, 0x1); + channel.Write(ClassId.Threed, 0x1340, 0x1); + channel.Write(ClassId.Threed, 0x1344, 0x2); + channel.Write(ClassId.Threed, 0x1348, 0x1); + channel.Write(ClassId.Threed, 0x134C, 0x1); + channel.Write(ClassId.Threed, 0x1350, 0x2); + channel.Write(ClassId.Threed, 0x1358, 0x1); + channel.Write(ClassId.Threed, 0x12E4, 0x0); + channel.Write(ClassId.Threed, 0x131C, 0x0); + channel.Write(ClassId.Threed, 0x1320, 0x0); + channel.Write(ClassId.Threed, 0x1324, 0x0); + channel.Write(ClassId.Threed, 0x1328, 0x0); + channel.Write(ClassId.Threed, 0x19C0, 0x0); + channel.Write(ClassId.Threed, 0x1140, 0x0); + channel.Write(ClassId.Threed, 0xDD0, 0x0); + channel.Write(ClassId.Threed, 0xDD4, 0x1); + channel.Write(ClassId.Threed, 0x2F4, 0x0); + channel.Write(ClassId.Threed, 0x19C4, 0x0); + channel.Write(ClassId.Threed, 0x19C8, 0x1500); + channel.Write(ClassId.Threed, 0x135C, 0x0); + channel.Write(ClassId.Threed, 0xF90, 0x0); + channel.Write(ClassId.Threed, 0x19E0, 0x1); + channel.Write(ClassId.Threed, 0x19E4, 0x1); + channel.Write(ClassId.Threed, 0x19E8, 0x1); + channel.Write(ClassId.Threed, 0x19EC, 0x1); + channel.Write(ClassId.Threed, 0x19F0, 0x1); + channel.Write(ClassId.Threed, 0x19F4, 0x1); + channel.Write(ClassId.Threed, 0x19F8, 0x1); + channel.Write(ClassId.Threed, 0x19FC, 0x1); + channel.Write(ClassId.Threed, 0x19CC, 0x1); + channel.Write(ClassId.Threed, 0x111C, 0x1); + channel.Write(ClassId.Threed, 0x15B8, 0x0); + channel.Write(ClassId.Threed, 0x1A00, 0x1111); + channel.Write(ClassId.Threed, 0x1A04, 0x0); + channel.Write(ClassId.Threed, 0x1A08, 0x0); + channel.Write(ClassId.Threed, 0x1A0C, 0x0); + channel.Write(ClassId.Threed, 0x1A10, 0x0); + channel.Write(ClassId.Threed, 0x1A14, 0x0); + channel.Write(ClassId.Threed, 0x1A18, 0x0); + channel.Write(ClassId.Threed, 0x1A1C, 0x0); + channel.Write(ClassId.Threed, 0xD6C, 0xFFFF0000); + channel.Write(ClassId.Threed, 0xD70, 0xFFFF0000); + channel.Write(ClassId.Threed, 0x10F8, 0x1010); + channel.Write(ClassId.Threed, 0xD80, 0x0); + channel.Write(ClassId.Threed, 0xD84, 0x0); + channel.Write(ClassId.Threed, 0xD88, 0x0); + channel.Write(ClassId.Threed, 0xD8C, 0x0); + channel.Write(ClassId.Threed, 0xD90, 0x0); + channel.Write(ClassId.Threed, 0xDA0, 0x0); + channel.Write(ClassId.Threed, 0x7A4, 0x0); + channel.Write(ClassId.Threed, 0x7A8, 0x0); + channel.Write(ClassId.Threed, 0x1508, 0x80000000); + channel.Write(ClassId.Threed, 0x150C, 0x40000000); + channel.Write(ClassId.Threed, 0x1668, 0x0); + channel.Write(ClassId.Threed, 0x318, 0x8); + channel.Write(ClassId.Threed, 0x31C, 0x8); + channel.Write(ClassId.Threed, 0xD9C, 0x1); + channel.Write(ClassId.Threed, 0xF14, 0x0); + channel.Write(ClassId.Threed, 0x374, 0x0); + channel.Write(ClassId.Threed, 0x378, 0xC); + channel.Write(ClassId.Threed, 0x7DC, 0x0); + channel.Write(ClassId.Threed, 0x74C, 0x55); + channel.Write(ClassId.Threed, 0x1420, 0x3); + channel.Write(ClassId.Threed, 0x1008, 0x8); + channel.Write(ClassId.Threed, 0x100C, 0x40); + channel.Write(ClassId.Threed, 0x1010, 0x12C); + channel.Write(ClassId.Threed, 0xD60, 0x40); + channel.Write(ClassId.Threed, 0x1018, 0x20); + channel.Write(ClassId.Threed, 0x101C, 0x1); + channel.Write(ClassId.Threed, 0x1020, 0x20); + channel.Write(ClassId.Threed, 0x1024, 0x1); + channel.Write(ClassId.Threed, 0x1444, 0x0); + channel.Write(ClassId.Threed, 0x1448, 0x0); + channel.Write(ClassId.Threed, 0x144C, 0x0); + channel.Write(ClassId.Threed, 0x360, 0x20164010); + channel.Write(ClassId.Threed, 0x364, 0x20); + channel.Write(ClassId.Threed, 0x368, 0x0); + channel.Write(ClassId.Threed, 0xDA8, 0x30); + channel.Write(ClassId.Threed, 0xDE4, 0x0); + channel.Write(ClassId.Threed, 0x204, 0x6); + channel.Write(ClassId.Threed, 0x2D0, 0x3FFFFF); + channel.Write(ClassId.Threed, 0x1220, 0x5); + channel.Write(ClassId.Threed, 0xFDC, 0x0); + channel.Write(ClassId.Threed, 0xF98, 0x400008); + channel.Write(ClassId.Threed, 0x1284, 0x8000080); + channel.Write(ClassId.Threed, 0x1450, 0x400008); + channel.Write(ClassId.Threed, 0x1454, 0x8000080); + channel.Write(ClassId.Threed, 0x214, 0x0); + channel.Write(ClassId.Twod, 0x200, 0xCF); + channel.Write(ClassId.Twod, 0x204, 0x1); + channel.Write(ClassId.Twod, 0x208, 0x20); + channel.Write(ClassId.Twod, 0x20C, 0x1); + channel.Write(ClassId.Twod, 0x210, 0x0); + channel.Write(ClassId.Twod, 0x214, 0x80); + channel.Write(ClassId.Twod, 0x218, 0x100); + channel.Write(ClassId.Twod, 0x21C, 0x100); + channel.Write(ClassId.Twod, 0x220, 0x0); + channel.Write(ClassId.Twod, 0x224, 0x0); + channel.Write(ClassId.Twod, 0x230, 0xCF); + channel.Write(ClassId.Twod, 0x234, 0x1); + channel.Write(ClassId.Twod, 0x238, 0x20); + channel.Write(ClassId.Twod, 0x23C, 0x1); + channel.Write(ClassId.Twod, 0x244, 0x80); + channel.Write(ClassId.Twod, 0x248, 0x100); + channel.Write(ClassId.Twod, 0x24C, 0x100); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs new file mode 100644 index 00000000..bc70b05c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs @@ -0,0 +1,578 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + class NvHostChannelDeviceFile : NvDeviceFile + { + private static readonly ConcurrentDictionary _host1xContextRegistry = new(); + + private const uint MaxModuleSyncpoint = 16; + +#pragma warning disable IDE0052 // Remove unread private member + private uint _timeout; + private uint _submitTimeout; + private uint _timeslice; +#pragma warning restore IDE0052 + + private readonly Switch _device; + + private readonly IVirtualMemoryManager _memory; + private readonly Host1xContext _host1xContext; + private readonly long _contextId; + + public GpuChannel Channel { get; } + + public enum ResourcePolicy + { + Device, + Channel, + } + + protected static uint[] DeviceSyncpoints = new uint[MaxModuleSyncpoint]; + + protected uint[] ChannelSyncpoints; + + protected static ResourcePolicy ChannelResourcePolicy = ResourcePolicy.Device; + + private NvFence _channelSyncpoint; + + public NvHostChannelDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + _device = context.Device; + _memory = memory; + _timeout = 3000; + _submitTimeout = 0; + _timeslice = 0; + _host1xContext = GetHost1XContext(context.Device.Gpu, owner); + _contextId = _host1xContext.Host1x.CreateContext(); + Channel = _device.Gpu.CreateChannel(); + + ChannelInitialization.InitializeState(Channel); + + ChannelSyncpoints = new uint[MaxModuleSyncpoint]; + + _channelSyncpoint.Id = _device.System.HostSyncpoint.AllocateSyncpoint(false); + _channelSyncpoint.UpdateValue(_device.System.HostSyncpoint); + } + + public override NvInternalResult Ioctl(NvIoctl command, Span arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvHostCustomMagic) + { + switch (command.Number) + { + case 0x01: + result = Submit(arguments); + break; + case 0x02: + result = CallIoctlMethod(GetSyncpoint, arguments); + break; + case 0x03: + result = CallIoctlMethod(GetWaitBase, arguments); + break; + case 0x07: + result = CallIoctlMethod(SetSubmitTimeout, arguments); + break; + case 0x09: + result = MapCommandBuffer(arguments); + break; + case 0x0a: + result = UnmapCommandBuffer(arguments); + break; + } + } + else if (command.Type == NvIoctl.NvHostMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod(SetNvMapFd, arguments); + break; + case 0x03: + result = CallIoctlMethod(SetTimeout, arguments); + break; + case 0x08: + result = SubmitGpfifo(arguments); + break; + case 0x09: + result = CallIoctlMethod(AllocObjCtx, arguments); + break; + case 0x0b: + result = CallIoctlMethod(ZcullBind, arguments); + break; + case 0x0c: + result = CallIoctlMethod(SetErrorNotifier, arguments); + break; + case 0x0d: + result = CallIoctlMethod(SetPriority, arguments); + break; + case 0x18: + result = CallIoctlMethod(AllocGpfifoEx, arguments); + break; + case 0x1a: + result = CallIoctlMethod(AllocGpfifoEx2, arguments); + break; + case 0x1d: + result = CallIoctlMethod(SetTimeslice, arguments); + break; + } + } + else if (command.Type == NvIoctl.NvGpuMagic) + { + switch (command.Number) + { + case 0x14: + result = CallIoctlMethod(SetUserData, arguments); + break; + } + } + + return result; + } + + private NvInternalResult Submit(Span arguments) + { + SubmitArguments submitHeader = GetSpanAndSkip(ref arguments, 1)[0]; + Span commandBuffers = GetSpanAndSkip(ref arguments, submitHeader.CmdBufsCount); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + Span relocs = GetSpanAndSkip(ref arguments, submitHeader.RelocsCount); + Span relocShifts = GetSpanAndSkip(ref arguments, submitHeader.RelocsCount); +#pragma warning restore IDE0059 + Span syncptIncrs = GetSpanAndSkip(ref arguments, submitHeader.SyncptIncrsCount); + Span fenceThresholds = GetSpanAndSkip(ref arguments, submitHeader.FencesCount); + + lock (_device) + { + for (int i = 0; i < syncptIncrs.Length; i++) + { + SyncptIncr syncptIncr = syncptIncrs[i]; + + uint id = syncptIncr.Id; + + fenceThresholds[i] = Context.Device.System.HostSyncpoint.IncrementSyncpointMax(id, syncptIncr.Incrs); + } + + foreach (CommandBuffer commandBuffer in commandBuffers) + { + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBuffer.Mem); + + var data = _memory.GetSpan(map.Address + commandBuffer.Offset, commandBuffer.WordsCount * 4); + + _host1xContext.Host1x.Submit(MemoryMarshal.Cast(data), _contextId); + } + } + + return NvInternalResult.Success; + } + + private Span GetSpanAndSkip(ref Span arguments, int count) where T : unmanaged + { + Span output = MemoryMarshal.Cast(arguments)[..count]; + + arguments = arguments[(Unsafe.SizeOf() * count)..]; + + return output; + } + + private NvInternalResult GetSyncpoint(ref GetParameterArguments arguments) + { + if (arguments.Parameter >= MaxModuleSyncpoint) + { + return NvInternalResult.InvalidInput; + } + + if (ChannelResourcePolicy == ResourcePolicy.Device) + { + arguments.Value = GetSyncpointDevice(_device.System.HostSyncpoint, arguments.Parameter, false); + } + else + { + arguments.Value = GetSyncpointChannel(arguments.Parameter, false); + } + + if (arguments.Value == 0) + { + return NvInternalResult.TryAgain; + } + + return NvInternalResult.Success; + } + + private NvInternalResult GetWaitBase(ref GetParameterArguments arguments) + { + arguments.Value = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetSubmitTimeout(ref uint submitTimeout) + { + _submitTimeout = submitTimeout; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult MapCommandBuffer(Span arguments) + { + int headerSize = Unsafe.SizeOf(); + MapCommandBufferArguments commandBufferHeader = MemoryMarshal.Cast(arguments)[0]; + Span commandBufferEntries = MemoryMarshal.Cast(arguments[headerSize..])[..commandBufferHeader.NumEntries]; + + foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries) + { + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + lock (map) + { + if (map.DmaMapAddress == 0) + { + ulong va = _host1xContext.MemoryAllocator.GetFreeAddress(map.Size, out ulong freeAddressStartPosition, 1, MemoryManager.PageSize); + + if (va != NvMemoryAllocator.PteUnmapped && va <= uint.MaxValue && (va + map.Size) <= uint.MaxValue) + { + _host1xContext.MemoryAllocator.AllocateRange(va, map.Size, freeAddressStartPosition); + _host1xContext.Smmu.Map(map.Address, va, map.Size); + map.DmaMapAddress = va; + } + else + { + map.DmaMapAddress = NvMemoryAllocator.PteUnmapped; + } + } + + commandBufferEntry.MapAddress = (int)map.DmaMapAddress; + } + } + + return NvInternalResult.Success; + } + + private NvInternalResult UnmapCommandBuffer(Span arguments) + { + int headerSize = Unsafe.SizeOf(); + MapCommandBufferArguments commandBufferHeader = MemoryMarshal.Cast(arguments)[0]; + Span commandBufferEntries = MemoryMarshal.Cast(arguments[headerSize..])[..commandBufferHeader.NumEntries]; + + foreach (ref CommandBufferHandle commandBufferEntry in commandBufferEntries) + { + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(Owner, commandBufferEntry.MapHandle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{commandBufferEntry.MapHandle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + lock (map) + { + if (map.DmaMapAddress != 0) + { + // FIXME: + // To make unmapping work, we need separate address space per channel. + // Right now NVDEC and VIC share the GPU address space which is not correct at all. + + // _host1xContext.MemoryAllocator.Free((ulong)map.DmaMapAddress, (uint)map.Size); + + // map.DmaMapAddress = 0; + } + } + } + + return NvInternalResult.Success; + } + + private NvInternalResult SetNvMapFd(ref int nvMapFd) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetTimeout(ref uint timeout) + { + _timeout = timeout; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SubmitGpfifo(Span arguments) + { + int headerSize = Unsafe.SizeOf(); + SubmitGpfifoArguments gpfifoSubmissionHeader = MemoryMarshal.Cast(arguments)[0]; + Span gpfifoEntries = MemoryMarshal.Cast(arguments[headerSize..])[..gpfifoSubmissionHeader.NumEntries]; + + return SubmitGpfifo(ref gpfifoSubmissionHeader, gpfifoEntries); + } + + private NvInternalResult AllocObjCtx(ref AllocObjCtxArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult ZcullBind(ref ZcullBindArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetErrorNotifier(ref SetErrorNotifierArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetPriority(ref NvChannelPriority priority) + { + switch (priority) + { + case NvChannelPriority.Low: + _timeslice = 1300; // Timeslice low priority in micro-seconds + break; + case NvChannelPriority.Medium: + _timeslice = 2600; // Timeslice medium priority in micro-seconds + break; + case NvChannelPriority.High: + _timeslice = 5200; // Timeslice high priority in micro-seconds + break; + default: + return NvInternalResult.InvalidInput; + } + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + // TODO: disable and preempt channel when GPU scheduler will be implemented. + + return NvInternalResult.Success; + } + + private NvInternalResult AllocGpfifoEx(ref AllocGpfifoExArguments arguments) + { + _channelSyncpoint.UpdateValue(_device.System.HostSyncpoint); + + arguments.Fence = _channelSyncpoint; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult AllocGpfifoEx2(ref AllocGpfifoExArguments arguments) + { + _channelSyncpoint.UpdateValue(_device.System.HostSyncpoint); + + arguments.Fence = _channelSyncpoint; + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult SetTimeslice(ref uint timeslice) + { + if (timeslice < 1000 || timeslice > 50000) + { + return NvInternalResult.InvalidInput; + } + + _timeslice = timeslice; // in micro-seconds + + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + // TODO: disable and preempt channel when GPU scheduler will be implemented. + + return NvInternalResult.Success; + } + + private NvInternalResult SetUserData(ref ulong userData) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + protected NvInternalResult SubmitGpfifo(ref SubmitGpfifoArguments header, Span entries) + { + if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceWait) && header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue)) + { + return NvInternalResult.InvalidInput; + } + + if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceWait) && !_device.System.HostSyncpoint.IsSyncpointExpired(header.Fence.Id, header.Fence.Value)) + { + Channel.PushHostCommandBuffer(CreateWaitCommandBuffer(header.Fence)); + } + + header.Fence.Id = _channelSyncpoint.Id; + + if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement) || header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue)) + { + uint incrementCount = header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement) ? 2u : 0u; + + if (header.Flags.HasFlag(SubmitGpfifoFlags.IncrementWithValue)) + { + incrementCount += header.Fence.Value; + } + + header.Fence.Value = _device.System.HostSyncpoint.IncrementSyncpointMaxExt(header.Fence.Id, (int)incrementCount); + } + else + { + header.Fence.Value = _device.System.HostSyncpoint.ReadSyncpointMaxValue(header.Fence.Id); + } + + Channel.PushEntries(entries); + + if (header.Flags.HasFlag(SubmitGpfifoFlags.FenceIncrement)) + { + Channel.PushHostCommandBuffer(CreateIncrementCommandBuffer(ref header.Fence, header.Flags)); + } + + header.Flags = SubmitGpfifoFlags.None; + + _device.Gpu.GPFifo.SignalNewEntries(); + + return NvInternalResult.Success; + } + + public uint GetSyncpointChannel(uint index, bool isClientManaged) + { + if (ChannelSyncpoints[index] != 0) + { + return ChannelSyncpoints[index]; + } + + ChannelSyncpoints[index] = _device.System.HostSyncpoint.AllocateSyncpoint(isClientManaged); + + return ChannelSyncpoints[index]; + } + + public uint GetSyncpointDevice(NvHostSyncpt syncpointManager, uint index, bool isClientManaged) + { + if (DeviceSyncpoints[index] != 0) + { + return DeviceSyncpoints[index]; + } + + DeviceSyncpoints[index] = syncpointManager.AllocateSyncpoint(isClientManaged); + + return DeviceSyncpoints[index]; + } + + private static int[] CreateWaitCommandBuffer(NvFence fence) + { + int[] commandBuffer = new int[4]; + + // SyncpointValue = fence.Value; + commandBuffer[0] = 0x2001001C; + commandBuffer[1] = (int)fence.Value; + + // SyncpointAction(fence.id, increment: false, switch_en: true); + commandBuffer[2] = 0x2001001D; + commandBuffer[3] = (((int)fence.Id << 8) | (0 << 0) | (1 << 4)); + + return commandBuffer; + } + + private int[] CreateIncrementCommandBuffer(ref NvFence fence, SubmitGpfifoFlags flags) + { + bool hasWfi = !flags.HasFlag(SubmitGpfifoFlags.SuppressWfi); + + int[] commandBuffer; + + int offset = 0; + + if (hasWfi) + { + commandBuffer = new int[8]; + + // WaitForInterrupt(handle) + commandBuffer[offset++] = 0x2001001E; + commandBuffer[offset++] = 0x0; + } + else + { + commandBuffer = new int[6]; + } + + // SyncpointValue = 0x0; + commandBuffer[offset++] = 0x2001001C; + commandBuffer[offset++] = 0x0; + + // Increment the syncpoint 2 times. (mitigate a hardware bug) + + // SyncpointAction(fence.id, increment: true, switch_en: false); + commandBuffer[offset++] = 0x2001001D; + commandBuffer[offset++] = (((int)fence.Id << 8) | (1 << 0) | (0 << 4)); + + // SyncpointAction(fence.id, increment: true, switch_en: false); + commandBuffer[offset++] = 0x2001001D; + commandBuffer[offset++] = (((int)fence.Id << 8) | (1 << 0) | (0 << 4)); + + return commandBuffer; + } + + public override void Close() + { + _host1xContext.Host1x.DestroyContext(_contextId); + Channel.Dispose(); + + for (int i = 0; i < MaxModuleSyncpoint; i++) + { + if (ChannelSyncpoints[i] != 0) + { + _device.System.HostSyncpoint.ReleaseSyncpoint(ChannelSyncpoints[i]); + ChannelSyncpoints[i] = 0; + } + } + + _device.System.HostSyncpoint.ReleaseSyncpoint(_channelSyncpoint.Id); + _channelSyncpoint.Id = 0; + } + + private static Host1xContext GetHost1XContext(GpuContext gpu, ulong pid) + { + return _host1xContextRegistry.GetOrAdd(pid, (ulong key) => new Host1xContext(gpu, key)); + } + + public static void Destroy() + { + foreach (Host1xContext host1xContext in _host1xContextRegistry.Values) + { + host1xContext.Dispose(); + } + + _host1xContextRegistry.Clear(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs new file mode 100644 index 00000000..34663e9d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs @@ -0,0 +1,98 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types; +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + internal class NvHostGpuDeviceFile : NvHostChannelDeviceFile + { +#pragma warning disable IDE0052 // Remove unread private member + private readonly KEvent _smExceptionBptIntReportEvent; + private readonly KEvent _smExceptionBptPauseReportEvent; + private readonly KEvent _errorNotifierEvent; +#pragma warning restore IDE0052 + + private int _smExceptionBptIntReportEventHandle; + private int _smExceptionBptPauseReportEventHandle; + private int _errorNotifierEventHandle; + + public NvHostGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, memory, owner) + { + _smExceptionBptIntReportEvent = CreateEvent(context, out _smExceptionBptIntReportEventHandle); + _smExceptionBptPauseReportEvent = CreateEvent(context, out _smExceptionBptPauseReportEventHandle); + _errorNotifierEvent = CreateEvent(context, out _errorNotifierEventHandle); + } + + private KEvent CreateEvent(ServiceCtx context, out int handle) + { + KEvent evnt = new(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(evnt.ReadableEvent, out handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + return evnt; + } + + public override NvInternalResult Ioctl2(NvIoctl command, Span arguments, Span inlineInBuffer) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvHostMagic) + { + switch (command.Number) + { + case 0x1b: + result = CallIoctlMethod(SubmitGpfifoEx, arguments, inlineInBuffer); + break; + } + } + + return result; + } + + public override NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + // TODO: accurately represent and implement those events. + eventHandle = eventId switch + { + 0x1 => _smExceptionBptIntReportEventHandle, + 0x2 => _smExceptionBptPauseReportEventHandle, + 0x3 => _errorNotifierEventHandle, + _ => 0, + }; + return eventHandle != 0 ? NvInternalResult.Success : NvInternalResult.InvalidInput; + } + + private NvInternalResult SubmitGpfifoEx(ref SubmitGpfifoArguments arguments, Span inlineData) + { + return SubmitGpfifo(ref arguments, inlineData); + } + + public override void Close() + { + if (_smExceptionBptIntReportEventHandle != 0) + { + Context.Process.HandleTable.CloseHandle(_smExceptionBptIntReportEventHandle); + _smExceptionBptIntReportEventHandle = 0; + } + + if (_smExceptionBptPauseReportEventHandle != 0) + { + Context.Process.HandleTable.CloseHandle(_smExceptionBptPauseReportEventHandle); + _smExceptionBptPauseReportEventHandle = 0; + } + + if (_errorNotifierEventHandle != 0) + { + Context.Process.HandleTable.CloseHandle(_errorNotifierEventHandle); + _errorNotifierEventHandle = 0; + } + + base.Close(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs new file mode 100644 index 00000000..9a06fbeb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs @@ -0,0 +1,17 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct AllocGpfifoExArguments + { + public uint NumEntries; + public uint NumJobs; + public uint Flags; + public NvFence Fence; + public uint Reserved1; + public uint Reserved2; + public uint Reserved3; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs new file mode 100644 index 00000000..4578ec6a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct AllocObjCtxArguments + { + public uint ClassNumber; + public uint Flags; + public ulong ObjectId; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs new file mode 100644 index 00000000..5e74f6f2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GetParameterArguments + { + public uint Parameter; + public uint Value; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs new file mode 100644 index 00000000..88cd6bd9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct CommandBufferHandle + { + public int MapHandle; + public int MapAddress; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct MapCommandBufferArguments + { + public int NumEntries; + public int DataAddress; // Ignored by the driver. + public bool AttachHostChDas; + public byte Padding1; + public short Padding2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs new file mode 100644 index 00000000..177f483f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + class NvChannel + { +#pragma warning disable CS0649 // Field is never assigned to + public int Timeout; + public int SubmitTimeout; + public int Timeslice; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs new file mode 100644 index 00000000..ca9a791b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel +{ + enum NvChannelPriority : uint + { + Low = 50, + Medium = 100, + High = 150, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs new file mode 100644 index 00000000..f285123a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SetErrorNotifierArguments + { + public ulong Offset; + public ulong Size; + public uint Mem; + public uint Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs new file mode 100644 index 00000000..0c4ea762 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs @@ -0,0 +1,40 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct CommandBuffer + { + public int Mem; + public uint Offset; + public int WordsCount; + } + + [StructLayout(LayoutKind.Sequential)] + struct Reloc + { + public int CmdbufMem; + public int CmdbufOffset; + public int Target; + public int TargetOffset; + } + + [StructLayout(LayoutKind.Sequential)] + struct SyncptIncr + { + public uint Id; + public uint Incrs; + public uint Reserved1; + public uint Reserved2; + public uint Reserved3; + } + + [StructLayout(LayoutKind.Sequential)] + struct SubmitArguments + { + public int CmdBufsCount; + public int RelocsCount; + public int SyncptIncrsCount; + public int FencesCount; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs new file mode 100644 index 00000000..b6bd1e4e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs @@ -0,0 +1,14 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SubmitGpfifoArguments + { + public long Address; + public int NumEntries; + public SubmitGpfifoFlags Flags; + public NvFence Fence; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoFlags.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoFlags.cs new file mode 100644 index 00000000..23ead473 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoFlags.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [Flags] + enum SubmitGpfifoFlags : uint + { + None, + FenceWait = 1 << 0, + FenceIncrement = 1 << 1, + HwFormat = 1 << 2, + SuppressWfi = 1 << 4, + IncrementWithValue = 1 << 8, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs new file mode 100644 index 00000000..fe4c74d6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZcullBindArguments + { + public ulong GpuVirtualAddress; + public uint Mode; + public uint Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs new file mode 100644 index 00000000..471bca73 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs @@ -0,0 +1,540 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Synchronization; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.Memory; +using System; +using System.Text; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + internal class NvHostCtrlDeviceFile : NvDeviceFile + { + public const int EventsCount = 64; + + private readonly bool _isProductionMode; + private readonly Switch _device; + private readonly NvHostEvent[] _events; + + public NvHostCtrlDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + if (NxSettings.Settings.TryGetValue("nv!rmos_set_production_mode", out object productionModeSetting)) + { + _isProductionMode = ((string)productionModeSetting) != "0"; // Default value is "" + } + else + { + _isProductionMode = true; + } + + _device = context.Device; + + _events = new NvHostEvent[EventsCount]; + } + + public override NvInternalResult Ioctl(NvIoctl command, Span arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvHostCustomMagic) + { + switch (command.Number) + { + case 0x14: + result = CallIoctlMethod(SyncptRead, arguments); + break; + case 0x15: + result = CallIoctlMethod(SyncptIncr, arguments); + break; + case 0x16: + result = CallIoctlMethod(SyncptWait, arguments); + break; + case 0x19: + result = CallIoctlMethod(SyncptWaitEx, arguments); + break; + case 0x1a: + result = CallIoctlMethod(SyncptReadMax, arguments); + break; + case 0x1b: + // As Marshal cannot handle unaligned arrays, we do everything by hand here. + GetConfigurationArguments configArgument = GetConfigurationArguments.FromSpan(arguments); + result = GetConfig(configArgument); + + if (result == NvInternalResult.Success) + { + configArgument.CopyTo(arguments); + } + break; + case 0x1c: + result = CallIoctlMethod(EventSignal, arguments); + break; + case 0x1d: + result = CallIoctlMethod(EventWait, arguments); + break; + case 0x1e: + result = CallIoctlMethod(EventWaitAsync, arguments); + break; + case 0x1f: + result = CallIoctlMethod(EventRegister, arguments); + break; + case 0x20: + result = CallIoctlMethod(EventUnregister, arguments); + break; + case 0x21: + result = CallIoctlMethod(EventKill, arguments); + break; + } + } + + return result; + } + + private int QueryEvent(uint eventId) + { + lock (_events) + { + uint eventSlot; + uint syncpointId; + + if ((eventId >> 28) == 1) + { + eventSlot = eventId & 0xFFFF; + syncpointId = (eventId >> 16) & 0xFFF; + } + else + { + eventSlot = eventId & 0xFF; + syncpointId = eventId >> 4; + } + + if (eventSlot >= EventsCount || _events[eventSlot] == null || _events[eventSlot].Fence.Id != syncpointId) + { + return 0; + } + + return _events[eventSlot].EventHandle; + } + } + + public override NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + eventHandle = QueryEvent(eventId); + + return eventHandle != 0 ? NvInternalResult.Success : NvInternalResult.InvalidInput; + } + + private NvInternalResult SyncptRead(ref NvFence arguments) + { + return SyncptReadMinOrMax(ref arguments, max: false); + } + + private NvInternalResult SyncptIncr(ref uint id) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return NvInternalResult.InvalidInput; + } + + _device.System.HostSyncpoint.Increment(id); + + return NvInternalResult.Success; + } + + private NvInternalResult SyncptWait(ref SyncptWaitArguments arguments) + { + uint dummyValue = 0; + + return EventWait(ref arguments.Fence, ref dummyValue, arguments.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: false); + } + + private NvInternalResult SyncptWaitEx(ref SyncptWaitExArguments arguments) + { + return EventWait(ref arguments.Input.Fence, ref arguments.Value, arguments.Input.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: false); + } + + private NvInternalResult SyncptReadMax(ref NvFence arguments) + { + return SyncptReadMinOrMax(ref arguments, max: true); + } + + private NvInternalResult GetConfig(GetConfigurationArguments arguments) + { + if (!_isProductionMode && NxSettings.Settings.TryGetValue($"{arguments.Domain}!{arguments.Parameter}".ToLower(), out object nvSetting)) + { + byte[] settingBuffer = new byte[0x101]; + + if (nvSetting is string stringValue) + { + if (stringValue.Length > 0x100) + { + Logger.Error?.Print(LogClass.ServiceNv, $"{arguments.Domain}!{arguments.Parameter} String value size is too big!"); + } + else + { + settingBuffer = Encoding.ASCII.GetBytes(stringValue + "\0"); + } + } + else if (nvSetting is int intValue) + { + settingBuffer = BitConverter.GetBytes(intValue); + } + else if (nvSetting is bool boolValue) + { + settingBuffer[0] = boolValue ? (byte)1 : (byte)0; + } + else + { + throw new NotImplementedException(nvSetting.GetType().Name); + } + + Logger.Debug?.Print(LogClass.ServiceNv, $"Got setting {arguments.Domain}!{arguments.Parameter}"); + + arguments.Configuration = settingBuffer; + + return NvInternalResult.Success; + } + + // NOTE: This actually return NotAvailableInProduction but this is directly translated as a InvalidInput before returning the ioctl. + //return NvInternalResult.NotAvailableInProduction; + return NvInternalResult.InvalidInput; + } + + private NvInternalResult EventWait(ref EventWaitArguments arguments) + { + return EventWait(ref arguments.Fence, ref arguments.Value, arguments.Timeout, isWaitEventAsyncCmd: false, isWaitEventCmd: true); + } + + private NvInternalResult EventWaitAsync(ref EventWaitArguments arguments) + { + return EventWait(ref arguments.Fence, ref arguments.Value, arguments.Timeout, isWaitEventAsyncCmd: true, isWaitEventCmd: false); + } + + private NvInternalResult EventRegister(ref uint userEventId) + { + lock (_events) + { + NvInternalResult result = EventUnregister(ref userEventId); + + if (result == NvInternalResult.Success) + { + _events[userEventId] = new NvHostEvent(_device.System.HostSyncpoint, userEventId, _device.System); + } + + return result; + } + + } + + private NvInternalResult EventUnregister(ref uint userEventId) + { + lock (_events) + { + if (userEventId >= EventsCount) + { + return NvInternalResult.InvalidInput; + } + + NvHostEvent hostEvent = _events[userEventId]; + + if (hostEvent == null) + { + return NvInternalResult.Success; + } + + if (hostEvent.State == NvHostEventState.Available || + hostEvent.State == NvHostEventState.Cancelled || + hostEvent.State == NvHostEventState.Signaled) + { + _events[userEventId].CloseEvent(Context); + _events[userEventId] = null; + + return NvInternalResult.Success; + } + + return NvInternalResult.Busy; + } + } + + private NvInternalResult EventKill(ref ulong eventMask) + { + lock (_events) + { + NvInternalResult result = NvInternalResult.Success; + + for (uint eventId = 0; eventId < EventsCount; eventId++) + { + if ((eventMask & (1UL << (int)eventId)) != 0) + { + NvInternalResult tmp = EventUnregister(ref eventId); + + if (tmp != NvInternalResult.Success) + { + result = tmp; + } + } + } + + return result; + } + } + + private NvInternalResult EventSignal(ref uint userEventId) + { + uint eventId = userEventId & ushort.MaxValue; + + if (eventId >= EventsCount) + { + return NvInternalResult.InvalidInput; + } + + lock (_events) + { + NvHostEvent hostEvent = _events[eventId]; + + if (hostEvent == null) + { + return NvInternalResult.InvalidInput; + } + + hostEvent.Cancel(_device.Gpu); + + _device.System.HostSyncpoint.UpdateMin(hostEvent.Fence.Id); + + return NvInternalResult.Success; + } + } + + private NvInternalResult SyncptReadMinOrMax(ref NvFence arguments, bool max) + { + if (arguments.Id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return NvInternalResult.InvalidInput; + } + + if (max) + { + arguments.Value = _device.System.HostSyncpoint.ReadSyncpointMaxValue(arguments.Id); + } + else + { + arguments.Value = _device.System.HostSyncpoint.ReadSyncpointValue(arguments.Id); + } + + return NvInternalResult.Success; + } + + private NvInternalResult EventWait(ref NvFence fence, ref uint value, int timeout, bool isWaitEventAsyncCmd, bool isWaitEventCmd) + { + if (fence.Id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return NvInternalResult.InvalidInput; + } + + // First try to check if the syncpoint is already expired on the CPU side + if (_device.System.HostSyncpoint.IsSyncpointExpired(fence.Id, fence.Value)) + { + value = _device.System.HostSyncpoint.ReadSyncpointMinValue(fence.Id); + + return NvInternalResult.Success; + } + + // Try to invalidate the CPU cache and check for expiration again. + uint newCachedSyncpointValue = _device.System.HostSyncpoint.UpdateMin(fence.Id); + + // Has the fence already expired? + if (_device.System.HostSyncpoint.IsSyncpointExpired(fence.Id, fence.Value)) + { + value = newCachedSyncpointValue; + + return NvInternalResult.Success; + } + + // If the timeout is 0, directly return. + if (timeout == 0) + { + return NvInternalResult.TryAgain; + } + + // The syncpoint value isn't at the fence yet, we need to wait. + + if (!isWaitEventAsyncCmd) + { + value = 0; + } + + NvHostEvent hostEvent; + + NvInternalResult result; + + uint eventIndex; + + lock (_events) + { + if (isWaitEventAsyncCmd) + { + eventIndex = value; + + if (eventIndex >= EventsCount) + { + return NvInternalResult.InvalidInput; + } + + hostEvent = _events[eventIndex]; + } + else + { + hostEvent = GetFreeEventLocked(fence.Id, out eventIndex); + } + + if (hostEvent != null) + { + lock (hostEvent.Lock) + { + if (hostEvent.State == NvHostEventState.Available || + hostEvent.State == NvHostEventState.Signaled || + hostEvent.State == NvHostEventState.Cancelled) + { + bool timedOut = hostEvent.Wait(_device.Gpu, fence); + + if (timedOut) + { + if (isWaitEventCmd) + { + value = ((fence.Id & 0xfff) << 16) | 0x10000000; + } + else + { + value = fence.Id << 4; + } + + value |= eventIndex; + + result = NvInternalResult.TryAgain; + } + else + { + value = fence.Value; + + return NvInternalResult.Success; + } + } + else + { + Logger.Error?.Print(LogClass.ServiceNv, $"Invalid Event at index {eventIndex} (isWaitEventAsyncCmd: {isWaitEventAsyncCmd}, isWaitEventCmd: {isWaitEventCmd})"); + + if (hostEvent != null) + { + Logger.Error?.Print(LogClass.ServiceNv, hostEvent.DumpState(_device.Gpu)); + } + + result = NvInternalResult.InvalidInput; + } + } + } + else + { + Logger.Error?.Print(LogClass.ServiceNv, $"Invalid Event at index {eventIndex} (isWaitEventAsyncCmd: {isWaitEventAsyncCmd}, isWaitEventCmd: {isWaitEventCmd})"); + + result = NvInternalResult.InvalidInput; + } + } + + return result; + } + + private NvHostEvent GetFreeEventLocked(uint id, out uint eventIndex) + { + eventIndex = EventsCount; + + uint nullIndex = EventsCount; + + for (uint index = 0; index < EventsCount; index++) + { + NvHostEvent Event = _events[index]; + + if (Event != null) + { + if (Event.State == NvHostEventState.Available || + Event.State == NvHostEventState.Signaled || + Event.State == NvHostEventState.Cancelled) + { + eventIndex = index; + + if (Event.Fence.Id == id) + { + return Event; + } + } + } + else if (nullIndex == EventsCount) + { + nullIndex = index; + } + } + + if (nullIndex < EventsCount) + { + eventIndex = nullIndex; + + EventRegister(ref eventIndex); + + return _events[nullIndex]; + } + + if (eventIndex < EventsCount) + { + return _events[eventIndex]; + } + + return null; + } + + public override void Close() + { + Logger.Warning?.Print(LogClass.ServiceNv, "Closing channel"); + + lock (_events) + { + // If the device file need to be closed, cancel all user events and dispose events. + for (int i = 0; i < _events.Length; i++) + { + NvHostEvent evnt = _events[i]; + + if (evnt != null) + { + lock (evnt.Lock) + { + if (evnt.State == NvHostEventState.Waiting) + { + evnt.State = NvHostEventState.Cancelling; + + evnt.Cancel(_device.Gpu); + } + else if (evnt.State == NvHostEventState.Signaling) + { + // Wait at max 9ms if the guest app is trying to signal the event while closing it.. + int retryCount = 0; + do + { + if (retryCount++ > 9) + { + break; + } + + // TODO: This should be handled by the kernel (reschedule the current thread ect), waiting for Kernel decoupling work. + Thread.Sleep(1); + } while (evnt.State != NvHostEventState.Signaled); + } + + evnt.CloseEvent(Context); + + _events[i] = null; + } + } + } + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs new file mode 100644 index 00000000..0b2133a7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs @@ -0,0 +1,13 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct EventWaitArguments + { + public NvFence Fence; + public int Timeout; + public uint Value; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs new file mode 100644 index 00000000..c94bb4ff --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs @@ -0,0 +1,34 @@ +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + class GetConfigurationArguments + { + public string Domain; + public string Parameter; + public byte[] Configuration; + + public static GetConfigurationArguments FromSpan(Span span) + { + string domain = Encoding.ASCII.GetString(span[..0x41]); + string parameter = Encoding.ASCII.GetString(span.Slice(0x41, 0x41)); + + GetConfigurationArguments result = new() + { + Domain = domain[..domain.IndexOf('\0')], + Parameter = parameter[..parameter.IndexOf('\0')], + Configuration = span.Slice(0x82, 0x101).ToArray(), + }; + + return result; + } + + public void CopyTo(Span span) + { + Encoding.ASCII.GetBytes(Domain + '\0').CopyTo(span[..0x41]); + Encoding.ASCII.GetBytes(Parameter + '\0').CopyTo(span.Slice(0x41, 0x41)); + Configuration.CopyTo(span.Slice(0x82, 0x101)); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs new file mode 100644 index 00000000..8f851f37 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs @@ -0,0 +1,187 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Gpu.Synchronization; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using Ryujinx.Horizon.Common; +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + class NvHostEvent + { + public NvFence Fence; + public NvHostEventState State; + public KEvent Event; + public int EventHandle; + + private readonly uint _eventId; +#pragma warning disable IDE0052 // Remove unread private member + private readonly NvHostSyncpt _syncpointManager; +#pragma warning restore IDE0052 + private SyncpointWaiterHandle _waiterInformation; + + private NvFence _previousFailingFence; + private uint _failingCount; + + public readonly object Lock = new(); + + /// + /// Max failing count until waiting on CPU. + /// FIXME: This seems enough for most of the cases, reduce if needed. + /// + private const uint FailingCountMax = 2; + + public NvHostEvent(NvHostSyncpt syncpointManager, uint eventId, Horizon system) + { + Fence.Id = 0; + + State = NvHostEventState.Available; + + Event = new KEvent(system.KernelContext); + + if (KernelStatic.GetCurrentProcess().HandleTable.GenerateHandle(Event.ReadableEvent, out EventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + _eventId = eventId; + + _syncpointManager = syncpointManager; + + ResetFailingState(); + } + + private void ResetFailingState() + { + _previousFailingFence.Id = NvFence.InvalidSyncPointId; + _previousFailingFence.Value = 0; + _failingCount = 0; + } + + private void Signal() + { + lock (Lock) + { + NvHostEventState oldState = State; + + State = NvHostEventState.Signaling; + + if (oldState == NvHostEventState.Waiting) + { + Event.WritableEvent.Signal(); + } + + State = NvHostEventState.Signaled; + } + } + + private void GpuSignaled(SyncpointWaiterHandle waiterInformation) + { + lock (Lock) + { + // If the signal does not match our current waiter, + // then it is from a past fence and we should just ignore it. + if (waiterInformation != null && waiterInformation != _waiterInformation) + { + return; + } + + ResetFailingState(); + + Signal(); + } + } + + public void Cancel(GpuContext gpuContext) + { + lock (Lock) + { + NvHostEventState oldState = State; + + State = NvHostEventState.Cancelling; + + if (oldState == NvHostEventState.Waiting && _waiterInformation != null) + { + gpuContext.Synchronization.UnregisterCallback(Fence.Id, _waiterInformation); + _waiterInformation = null; + + if (_previousFailingFence.Id == Fence.Id && _previousFailingFence.Value == Fence.Value) + { + _failingCount++; + } + else + { + _failingCount = 1; + + _previousFailingFence = Fence; + } + } + + State = NvHostEventState.Cancelled; + + Event.WritableEvent.Clear(); + } + } + + public bool Wait(GpuContext gpuContext, NvFence fence) + { + lock (Lock) + { + // NOTE: nvservices code should always wait on the GPU side. + // If we do this, we may get an abort or undefined behaviour when the GPU processing thread is blocked for a long period (for example, during shader compilation). + // The reason for this is that the NVN code will try to wait until giving up. + // This is done by trying to wait and signal multiple times until aborting after you are past the timeout. + // As such, if it fails too many time, we enforce a wait on the CPU side indefinitely. + // This allows to keep GPU and CPU in sync when we are slow. + if (_failingCount == FailingCountMax) + { + Logger.Warning?.Print(LogClass.ServiceNv, "GPU processing thread is too slow, waiting on CPU..."); + + Fence.Wait(gpuContext, Timeout.InfiniteTimeSpan); + + ResetFailingState(); + + return false; + } + else + { + Fence = fence; + State = NvHostEventState.Waiting; + + _waiterInformation = gpuContext.Synchronization.RegisterCallbackOnSyncpoint(Fence.Id, Fence.Value, GpuSignaled); + + return true; + } + } + } + + public string DumpState(GpuContext gpuContext) + { + string res = $"\nNvHostEvent {_eventId}:\n"; + res += $"\tState: {State}\n"; + + if (State == NvHostEventState.Waiting) + { + res += "\tFence:\n"; + res += $"\t\tId : {Fence.Id}\n"; + res += $"\t\tThreshold : {Fence.Value}\n"; + res += $"\t\tCurrent Value : {gpuContext.Synchronization.GetSyncpointValue(Fence.Id)}\n"; + res += $"\t\tWaiter Valid : {_waiterInformation != null}\n"; + } + + return res; + } + + public void CloseEvent(ServiceCtx context) + { + if (EventHandle != 0) + { + context.Process.HandleTable.CloseHandle(EventHandle); + EventHandle = 0; + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs new file mode 100644 index 00000000..57c99d61 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + enum NvHostEventState + { + Available = 0, + Waiting = 1, + Cancelling = 2, + Signaling = 3, + Signaled = 4, + Cancelled = 5, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs new file mode 100644 index 00000000..b83c642e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs @@ -0,0 +1,196 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Synchronization; +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl +{ + class NvHostSyncpt + { + public const int VBlank0SyncpointId = 26; + public const int VBlank1SyncpointId = 27; + + private readonly int[] _counterMin; + private readonly int[] _counterMax; + private readonly bool[] _clientManaged; + private readonly bool[] _assigned; + + private readonly Switch _device; + + private readonly object _syncpointAllocatorLock = new(); + + public NvHostSyncpt(Switch device) + { + _device = device; + _counterMin = new int[SynchronizationManager.MaxHardwareSyncpoints]; + _counterMax = new int[SynchronizationManager.MaxHardwareSyncpoints]; + _clientManaged = new bool[SynchronizationManager.MaxHardwareSyncpoints]; + _assigned = new bool[SynchronizationManager.MaxHardwareSyncpoints]; + + // Reserve VBLANK syncpoints + ReserveSyncpointLocked(VBlank0SyncpointId, true); + ReserveSyncpointLocked(VBlank1SyncpointId, true); + } + + private void ReserveSyncpointLocked(uint id, bool isClientManaged) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints || _assigned[id]) + { + throw new ArgumentOutOfRangeException(nameof(id)); + } + + _assigned[id] = true; + _clientManaged[id] = isClientManaged; + } + + public uint AllocateSyncpoint(bool isClientManaged) + { + lock (_syncpointAllocatorLock) + { + for (uint i = 1; i < SynchronizationManager.MaxHardwareSyncpoints; i++) + { + if (!_assigned[i]) + { + ReserveSyncpointLocked(i, isClientManaged); + return i; + } + } + } + + Logger.Error?.Print(LogClass.ServiceNv, "Cannot allocate a new syncpoint!"); + + return 0; + } + + public void ReleaseSyncpoint(uint id) + { + if (id == 0) + { + return; + } + + lock (_syncpointAllocatorLock) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints || !_assigned[id]) + { + throw new ArgumentOutOfRangeException(nameof(id)); + } + + _assigned[id] = false; + _clientManaged[id] = false; + + SetSyncpointMinEqualSyncpointMax(id); + } + } + + public void SetSyncpointMinEqualSyncpointMax(uint id) + { + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)SynchronizationManager.MaxHardwareSyncpoints); + + int value = (int)ReadSyncpointValue(id); + + Interlocked.Exchange(ref _counterMax[id], value); + } + + public uint ReadSyncpointValue(uint id) + { + return UpdateMin(id); + } + + public uint ReadSyncpointMinValue(uint id) + { + return (uint)_counterMin[id]; + } + + public uint ReadSyncpointMaxValue(uint id) + { + return (uint)_counterMax[id]; + } + + private bool IsClientManaged(uint id) + { + if (id >= SynchronizationManager.MaxHardwareSyncpoints) + { + return false; + } + + return _clientManaged[id]; + } + + public void Increment(uint id) + { + if (IsClientManaged(id)) + { + IncrementSyncpointMax(id); + } + + IncrementSyncpointGPU(id); + } + + public uint UpdateMin(uint id) + { + uint newValue = _device.Gpu.Synchronization.GetSyncpointValue(id); + + Interlocked.Exchange(ref _counterMin[id], (int)newValue); + + return newValue; + } + + private void IncrementSyncpointGPU(uint id) + { + _device.Gpu.Synchronization.IncrementSyncpoint(id); + } + + public void IncrementSyncpointMin(uint id) + { + Interlocked.Increment(ref _counterMin[id]); + } + + public uint IncrementSyncpointMaxExt(uint id, int count) + { + if (count == 0) + { + return ReadSyncpointMaxValue(id); + } + + uint result = 0; + + for (int i = 0; i < count; i++) + { + result = IncrementSyncpointMax(id); + } + + return result; + } + + private uint IncrementSyncpointMax(uint id) + { + return (uint)Interlocked.Increment(ref _counterMax[id]); + } + + public uint IncrementSyncpointMax(uint id, uint incrs) + { + return (uint)Interlocked.Add(ref _counterMax[id], (int)incrs); + } + + public bool IsSyncpointExpired(uint id, uint threshold) + { + return MinCompare(id, _counterMin[id], _counterMax[id], (int)threshold); + } + + private bool MinCompare(uint id, int min, int max, int threshold) + { + int minDiff = min - threshold; + int maxDiff = max - threshold; + + if (IsClientManaged(id)) + { + return minDiff >= 0; + } + else + { + return (uint)maxDiff >= (uint)minDiff; + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs new file mode 100644 index 00000000..5138db9e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs @@ -0,0 +1,12 @@ +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SyncptWaitArguments + { + public NvFence Fence; + public int Timeout; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs new file mode 100644 index 00000000..7bcd38c7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct SyncptWaitExArguments + { + public SyncptWaitArguments Input; + public uint Value; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs new file mode 100644 index 00000000..29198617 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs @@ -0,0 +1,276 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types; +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; +using System.Diagnostics; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu +{ + class NvHostCtrlGpuDeviceFile : NvDeviceFile + { + private static readonly Stopwatch _pTimer = new(); + private static readonly double _ticksToNs = (1.0 / Stopwatch.Frequency) * 1_000_000_000; + + private readonly KEvent _errorEvent; + private readonly KEvent _unknownEvent; + + public NvHostCtrlGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + _errorEvent = new KEvent(context.Device.System.KernelContext); + _unknownEvent = new KEvent(context.Device.System.KernelContext); + } + + static NvHostCtrlGpuDeviceFile() + { + _pTimer.Start(); + } + + public override NvInternalResult Ioctl(NvIoctl command, Span arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod(ZcullGetCtxSize, arguments); + break; + case 0x02: + result = CallIoctlMethod(ZcullGetInfo, arguments); + break; + case 0x03: + result = CallIoctlMethod(ZbcSetTable, arguments); + break; + case 0x05: + result = CallIoctlMethod(GetCharacteristics, arguments); + break; + case 0x06: + result = CallIoctlMethod(GetTpcMasks, arguments); + break; + case 0x12: + result = CallIoctlMethod(NumVsms, arguments); + break; + case 0x13: + result = CallIoctlMethod(VsmsMapping, arguments); + break; + case 0x14: + result = CallIoctlMethod(GetActiveSlotMask, arguments); + break; + case 0x1c: + result = CallIoctlMethod(GetGpuTime, arguments); + break; + } + } + + return result; + } + + public override NvInternalResult Ioctl3(NvIoctl command, Span arguments, Span inlineOutBuffer) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvGpuMagic) + { + switch (command.Number) + { + case 0x05: + result = CallIoctlMethod(GetCharacteristics, arguments, inlineOutBuffer); + break; + case 0x06: + result = CallIoctlMethod(GetTpcMasks, arguments, inlineOutBuffer); + break; + case 0x12: + result = CallIoctlMethod(NumVsms, arguments); + break; + case 0x13: + result = CallIoctlMethod(VsmsMapping, arguments); + break; + } + } + + return result; + } + + public override NvInternalResult QueryEvent(out int eventHandle, uint eventId) + { + // TODO: accurately represent and implement those events. + KEvent targetEvent = null; + + switch (eventId) + { + case 0x1: + targetEvent = _errorEvent; + break; + case 0x2: + targetEvent = _unknownEvent; + break; + } + + if (targetEvent != null) + { + if (Context.Process.HandleTable.GenerateHandle(targetEvent.ReadableEvent, out eventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + else + { + eventHandle = 0; + + return NvInternalResult.InvalidInput; + } + + return NvInternalResult.Success; + } + + public override void Close() { } + + private NvInternalResult ZcullGetCtxSize(ref ZcullGetCtxSizeArguments arguments) + { + arguments.Size = 1; + + return NvInternalResult.Success; + } + + private NvInternalResult ZcullGetInfo(ref ZcullGetInfoArguments arguments) + { +#pragma warning disable IDE0055 // Disable formatting + arguments.WidthAlignPixels = 0x20; + arguments.HeightAlignPixels = 0x20; + arguments.PixelSquaresByAliquots = 0x400; + arguments.AliquotTotal = 0x800; + arguments.RegionByteMultiplier = 0x20; + arguments.RegionHeaderSize = 0x20; + arguments.SubregionHeaderSize = 0xc0; + arguments.SubregionWidthAlignPixels = 0x20; + arguments.SubregionHeightAlignPixels = 0x40; + arguments.SubregionCount = 0x10; +#pragma warning restore IDE0055 + + return NvInternalResult.Success; + } + + private NvInternalResult ZbcSetTable(ref ZbcSetTableArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + return NvInternalResult.Success; + } + + private NvInternalResult GetCharacteristics(ref GetCharacteristicsArguments arguments) + { + return GetCharacteristics(ref arguments, ref arguments.Characteristics); + } + + private NvInternalResult GetCharacteristics(ref GetCharacteristicsArguments arguments, ref GpuCharacteristics characteristics) + { + arguments.Header.BufferSize = 0xa0; + +#pragma warning disable IDE0055 // Disable formatting + characteristics.Arch = 0x120; + characteristics.Impl = 0xb; + characteristics.Rev = 0xa1; + characteristics.NumGpc = 0x1; + characteristics.L2CacheSize = 0x40000; + characteristics.OnBoardVideoMemorySize = 0x0; + characteristics.NumTpcPerGpc = 0x2; + characteristics.BusType = 0x20; + characteristics.BigPageSize = 0x20000; + characteristics.CompressionPageSize = 0x20000; + characteristics.PdeCoverageBitCount = 0x1b; + characteristics.AvailableBigPageSizes = 0x30000; + characteristics.GpcMask = 0x1; + characteristics.SmArchSmVersion = 0x503; + characteristics.SmArchSpaVersion = 0x503; + characteristics.SmArchWarpCount = 0x80; + characteristics.GpuVaBitCount = 0x28; + characteristics.Reserved = 0x0; + characteristics.Flags = 0x55; + characteristics.TwodClass = 0x902d; + characteristics.ThreedClass = 0xb197; + characteristics.ComputeClass = 0xb1c0; + characteristics.GpfifoClass = 0xb06f; + characteristics.InlineToMemoryClass = 0xa140; + characteristics.DmaCopyClass = 0xb0b5; + characteristics.MaxFbpsCount = 0x1; + characteristics.FbpEnMask = 0x0; + characteristics.MaxLtcPerFbp = 0x2; + characteristics.MaxLtsPerLtc = 0x1; + characteristics.MaxTexPerTpc = 0x0; + characteristics.MaxGpcCount = 0x1; + characteristics.RopL2EnMask0 = 0x21d70; + characteristics.RopL2EnMask1 = 0x0; + characteristics.ChipName = 0x6230326d67; + characteristics.GrCompbitStoreBaseHw = 0x0; +#pragma warning restore IDE0055 + + arguments.Characteristics = characteristics; + + return NvInternalResult.Success; + } + + private NvInternalResult GetTpcMasks(ref GetTpcMasksArguments arguments) + { + return GetTpcMasks(ref arguments, ref arguments.TpcMask); + } + + private NvInternalResult GetTpcMasks(ref GetTpcMasksArguments arguments, ref int tpcMask) + { + if (arguments.MaskBufferSize != 0) + { + tpcMask = 3; + arguments.TpcMask = tpcMask; + } + + return NvInternalResult.Success; + } + + private NvInternalResult NumVsms(ref NumVsmsArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + arguments.NumVsms = 2; + + return NvInternalResult.Success; + } + + private NvInternalResult VsmsMapping(ref VsmsMappingArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + arguments.Sm0GpcIndex = 0; + arguments.Sm0TpcIndex = 0; + arguments.Sm1GpcIndex = 0; + arguments.Sm1TpcIndex = 1; + + return NvInternalResult.Success; + } + + private NvInternalResult GetActiveSlotMask(ref GetActiveSlotMaskArguments arguments) + { + Logger.Stub?.PrintStub(LogClass.ServiceNv); + + arguments.Slot = 0x07; + arguments.Mask = 0x01; + + return NvInternalResult.Success; + } + + private NvInternalResult GetGpuTime(ref GetGpuTimeArguments arguments) + { + arguments.Timestamp = GetPTimerNanoSeconds(); + + return NvInternalResult.Success; + } + + private static ulong GetPTimerNanoSeconds() + { + double ticks = _pTimer.ElapsedTicks; + + return (ulong)(ticks * _ticksToNs) & 0xff_ffff_ffff_ffff; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs new file mode 100644 index 00000000..4f447f48 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GetActiveSlotMaskArguments + { + public int Slot; + public int Mask; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs new file mode 100644 index 00000000..556786de --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs @@ -0,0 +1,59 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GpuCharacteristics + { + public int Arch; + public int Impl; + public int Rev; + public int NumGpc; + public long L2CacheSize; + public long OnBoardVideoMemorySize; + public int NumTpcPerGpc; + public int BusType; + public int BigPageSize; + public int CompressionPageSize; + public int PdeCoverageBitCount; + public int AvailableBigPageSizes; + public int GpcMask; + public int SmArchSmVersion; + public int SmArchSpaVersion; + public int SmArchWarpCount; + public int GpuVaBitCount; + public int Reserved; + public long Flags; + public int TwodClass; + public int ThreedClass; + public int ComputeClass; + public int GpfifoClass; + public int InlineToMemoryClass; + public int DmaCopyClass; + public int MaxFbpsCount; + public int FbpEnMask; + public int MaxLtcPerFbp; + public int MaxLtsPerLtc; + public int MaxTexPerTpc; + public int MaxGpcCount; + public int RopL2EnMask0; + public int RopL2EnMask1; + public long ChipName; + public long GrCompbitStoreBaseHw; + } + + struct CharacteristicsHeader + { +#pragma warning disable CS0649 // Field is never assigned to + public long BufferSize; + public long BufferAddress; +#pragma warning restore CS0649 + } + + [StructLayout(LayoutKind.Sequential)] + struct GetCharacteristicsArguments + { + public CharacteristicsHeader Header; + public GpuCharacteristics Characteristics; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs new file mode 100644 index 00000000..c1c1a6f9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct GetGpuTimeArguments + { + public ulong Timestamp; + public ulong Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs new file mode 100644 index 00000000..83ff4f09 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + + [StructLayout(LayoutKind.Sequential)] + struct GetTpcMasksArguments + { + public int MaskBufferSize; + public int Reserved; + public long MaskBufferAddress; + public int TpcMask; + public int Padding; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/NumVsmsArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/NumVsmsArguments.cs new file mode 100644 index 00000000..fb5013a7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/NumVsmsArguments.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct NumVsmsArguments + { + public uint NumVsms; + public uint Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs new file mode 100644 index 00000000..baada919 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/VsmsMappingArguments.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct VsmsMappingArguments + { + public byte Sm0GpcIndex; + public byte Sm0TpcIndex; + public byte Sm1GpcIndex; + public byte Sm1TpcIndex; + public uint Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs new file mode 100644 index 00000000..cee339ba --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZbcSetTableArguments + { + public Array4 ColorDs; + public Array4 ColorL2; + public uint Depth; + public uint Format; + public uint Type; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs new file mode 100644 index 00000000..16ffacda --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZcullGetCtxSizeArguments + { + public int Size; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs new file mode 100644 index 00000000..343643ab --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types +{ + [StructLayout(LayoutKind.Sequential)] + struct ZcullGetInfoArguments + { + public int WidthAlignPixels; + public int HeightAlignPixels; + public int PixelSquaresByAliquots; + public int AliquotTotal; + public int RegionByteMultiplier; + public int RegionHeaderSize; + public int SubregionHeaderSize; + public int SubregionWidthAlignPixels; + public int SubregionHeightAlignPixels; + public int SubregionCount; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs new file mode 100644 index 00000000..44f01f8a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs @@ -0,0 +1,11 @@ +using Ryujinx.Memory; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostDbgGpu +{ + class NvHostDbgGpuDeviceFile : NvDeviceFile + { + public NvHostDbgGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) { } + + public override void Close() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostProfGpu/NvHostProfGpuDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostProfGpu/NvHostProfGpuDeviceFile.cs new file mode 100644 index 00000000..db4112af --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostProfGpu/NvHostProfGpuDeviceFile.cs @@ -0,0 +1,11 @@ +using Ryujinx.Memory; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostProfGpu +{ + class NvHostProfGpuDeviceFile : NvDeviceFile + { + public NvHostProfGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) { } + + public override void Close() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs new file mode 100644 index 00000000..c100c817 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs @@ -0,0 +1,32 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices +{ + enum NvInternalResult + { + Success = 0, + OperationNotPermitted = -1, + NoEntry = -2, + Interrupted = -4, + IoError = -5, + DeviceNotFound = -6, + BadFileNumber = -9, + TryAgain = -11, + OutOfMemory = -12, + AccessDenied = -13, + BadAddress = -14, + Busy = -16, + NotADirectory = -20, + InvalidInput = -22, + FileTableOverflow = -23, + Unknown0x18 = -24, + NotSupported = -25, + FileTooBig = -27, + NoSpaceLeft = -28, + ReadOnlyAttribute = -30, + NotImplemented = -38, + InvalidState = -40, + Restart = -85, + InvalidAddress = -99, + TimedOut = -110, + Unknown0x72 = -114, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs new file mode 100644 index 00000000..6a0ac58b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs @@ -0,0 +1,283 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + internal class NvMapDeviceFile : NvDeviceFile + { + private const int FlagNotFreedYet = 1; + + private static readonly NvMapIdDictionary _maps = new(); + + public NvMapDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, ulong owner) : base(context, owner) + { + } + + public override NvInternalResult Ioctl(NvIoctl command, Span arguments) + { + NvInternalResult result = NvInternalResult.NotImplemented; + + if (command.Type == NvIoctl.NvMapCustomMagic) + { + switch (command.Number) + { + case 0x01: + result = CallIoctlMethod(Create, arguments); + break; + case 0x03: + result = CallIoctlMethod(FromId, arguments); + break; + case 0x04: + result = CallIoctlMethod(Alloc, arguments); + break; + case 0x05: + result = CallIoctlMethod(Free, arguments); + break; + case 0x09: + result = CallIoctlMethod(Param, arguments); + break; + case 0x0e: + result = CallIoctlMethod(GetId, arguments); + break; + case 0x02: + case 0x06: + case 0x07: + case 0x08: + case 0x0a: + case 0x0c: + case 0x0d: + case 0x0f: + case 0x10: + case 0x11: + result = NvInternalResult.NotSupported; + break; + } + } + + return result; + } + + private NvInternalResult Create(ref NvMapCreate arguments) + { + if (arguments.Size == 0) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid size 0x{arguments.Size:x8}!"); + + return NvInternalResult.InvalidInput; + } + + uint size = BitUtils.AlignUp(arguments.Size, (uint)MemoryManager.PageSize); + + arguments.Handle = CreateHandleFromMap(new NvMapHandle(size)); + + Logger.Debug?.Print(LogClass.ServiceNv, $"Created map {arguments.Handle} with size 0x{size:x8}!"); + + return NvInternalResult.Success; + } + + private NvInternalResult FromId(ref NvMapFromId arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Id); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + map.IncrementRefCount(); + + arguments.Handle = arguments.Id; + + return NvInternalResult.Success; + } + + private NvInternalResult Alloc(ref NvMapAlloc arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + if ((arguments.Align & (arguments.Align - 1)) != 0) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid alignment 0x{arguments.Align:x8}!"); + + return NvInternalResult.InvalidInput; + } + + if ((uint)arguments.Align < MemoryManager.PageSize) + { + arguments.Align = (int)MemoryManager.PageSize; + } + + NvInternalResult result = NvInternalResult.Success; + + if (!map.Allocated) + { + map.Allocated = true; + + map.Align = arguments.Align; + map.Kind = (byte)arguments.Kind; + + uint size = BitUtils.AlignUp(map.Size, (uint)MemoryManager.PageSize); + + ulong address = arguments.Address; + + if (address == 0) + { + // When the address is zero, we need to allocate + // our own backing memory for the NvMap. + // TODO: Is this allocation inside the transfer memory? + result = NvInternalResult.OutOfMemory; + } + + if (result == NvInternalResult.Success) + { + map.Size = size; + map.Address = address; + } + } + + return result; + } + + private NvInternalResult Free(ref NvMapFree arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + if (DecrementMapRefCount(Owner, arguments.Handle)) + { + arguments.Address = map.Address; + arguments.Flags = 0; + } + else + { + arguments.Address = 0; + arguments.Flags = FlagNotFreedYet; + } + + arguments.Size = map.Size; + + return NvInternalResult.Success; + } + + private NvInternalResult Param(ref NvMapParam arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + switch (arguments.Param) + { + case NvMapHandleParam.Size: + arguments.Result = (int)map.Size; + break; + case NvMapHandleParam.Align: + arguments.Result = map.Align; + break; + case NvMapHandleParam.Heap: + arguments.Result = 0x40000000; + break; + case NvMapHandleParam.Kind: + arguments.Result = map.Kind; + break; + case NvMapHandleParam.Compr: + arguments.Result = 0; + break; + + // Note: Base is not supported and returns an error. + // Any other value also returns an error. + default: + return NvInternalResult.InvalidInput; + } + + return NvInternalResult.Success; + } + + private NvInternalResult GetId(ref NvMapGetId arguments) + { + NvMapHandle map = GetMapFromHandle(Owner, arguments.Handle); + + if (map == null) + { + Logger.Warning?.Print(LogClass.ServiceNv, $"Invalid handle 0x{arguments.Handle:x8}!"); + + return NvInternalResult.InvalidInput; + } + + arguments.Id = arguments.Handle; + + return NvInternalResult.Success; + } + + public override void Close() + { + // TODO: refcount NvMapDeviceFile instances and remove when closing + // _maps.TryRemove(GetOwner(), out _); + } + + private int CreateHandleFromMap(NvMapHandle map) + { + return _maps.Add(map); + } + + private static bool DeleteMapWithHandle(ulong pid, int handle) + { + return _maps.Delete(handle) != null; + } + + public static void IncrementMapRefCount(ulong pid, int handle) + { + GetMapFromHandle(pid, handle)?.IncrementRefCount(); + } + + public static bool DecrementMapRefCount(ulong pid, int handle) + { + NvMapHandle map = GetMapFromHandle(pid, handle); + + if (map == null) + { + return false; + } + + if (map.DecrementRefCount() <= 0) + { + DeleteMapWithHandle(pid, handle); + + Logger.Debug?.Print(LogClass.ServiceNv, $"Deleted map {handle}!"); + + return true; + } + else + { + return false; + } + } + + public static NvMapHandle GetMapFromHandle(ulong pid, int handle) + { + return _maps.Get(handle); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs new file mode 100644 index 00000000..dc4f5d60 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapAlloc + { + public int Handle; + public int HeapMask; + public int Flags; + public int Align; + public long Kind; + public ulong Address; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs new file mode 100644 index 00000000..f4047497 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapCreate + { + public uint Size; + public int Handle; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs new file mode 100644 index 00000000..ce93e9e5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapFree + { + public int Handle; + public int Padding; + public ulong Address; + public uint Size; + public int Flags; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs new file mode 100644 index 00000000..9ec81f9f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapFromId + { + public int Id; + public int Handle; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs new file mode 100644 index 00000000..8306ae4c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapGetId + { + public int Id; + public int Handle; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs new file mode 100644 index 00000000..e821b571 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs @@ -0,0 +1,40 @@ +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + class NvMapHandle + { +#pragma warning disable CS0649 // Field is never assigned to + public int Handle; + public int Id; +#pragma warning restore CS0649 + public uint Size; + public int Align; + public int Kind; + public ulong Address; + public bool Allocated; + public ulong DmaMapAddress; + + private long _dupes; + + public NvMapHandle() + { + _dupes = 1; + } + + public NvMapHandle(uint size) : this() + { + Size = size; + } + + public void IncrementRefCount() + { + Interlocked.Increment(ref _dupes); + } + + public long DecrementRefCount() + { + return Interlocked.Decrement(ref _dupes); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs new file mode 100644 index 00000000..21393e7a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + enum NvMapHandleParam + { + Size = 1, + Align = 2, + Base = 3, + Heap = 4, + Kind = 5, + Compr = 6, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapIdDictionary.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapIdDictionary.cs new file mode 100644 index 00000000..1b4d8dd4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapIdDictionary.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + class NvMapIdDictionary + { + private readonly ConcurrentDictionary _nvmapHandles; + private int _id; + + public ICollection Values => _nvmapHandles.Values; + + public NvMapIdDictionary() + { + _nvmapHandles = new ConcurrentDictionary(); + } + + public int Add(NvMapHandle handle) + { + int id = Interlocked.Add(ref _id, 4); + + if (id != 0 && _nvmapHandles.TryAdd(id, handle)) + { + return id; + } + + throw new InvalidOperationException("NvMap ID overflow."); + } + + public NvMapHandle Get(int id) + { + if (_nvmapHandles.TryGetValue(id, out NvMapHandle handle)) + { + return handle; + } + + return null; + } + + public NvMapHandle Delete(int id) + { + if (_nvmapHandles.TryRemove(id, out NvMapHandle handle)) + { + return handle; + } + + return null; + } + + public ICollection Clear() + { + ICollection values = _nvmapHandles.Values; + + _nvmapHandles.Clear(); + + return values; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs new file mode 100644 index 00000000..16fd7804 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap +{ + [StructLayout(LayoutKind.Sequential)] + struct NvMapParam + { + public int Handle; + public NvMapHandleParam Param; + public int Result; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs new file mode 100644 index 00000000..40e35fa7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs @@ -0,0 +1,45 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv +{ + [StructLayout(LayoutKind.Sequential)] + struct NvIoctl + { + public const int NvHostCustomMagic = 0x00; + public const int NvMapCustomMagic = 0x01; + public const int NvGpuAsMagic = 0x41; + public const int NvGpuMagic = 0x47; + public const int NvHostMagic = 0x48; + + private const int NumberBits = 8; + private const int TypeBits = 8; + private const int SizeBits = 14; + private const int DirectionBits = 2; + + private const int NumberShift = 0; + private const int TypeShift = NumberShift + NumberBits; + private const int SizeShift = TypeShift + TypeBits; + private const int DirectionShift = SizeShift + SizeBits; + + private const int NumberMask = (1 << NumberBits) - 1; + private const int TypeMask = (1 << TypeBits) - 1; + private const int SizeMask = (1 << SizeBits) - 1; + private const int DirectionMask = (1 << DirectionBits) - 1; + + [Flags] + public enum Direction : uint + { + None = 0, + Read = 1, + Write = 2, + } + + public uint RawValue; + + public readonly uint Number => (RawValue >> NumberShift) & NumberMask; + public readonly uint Type => (RawValue >> TypeShift) & TypeMask; + public readonly uint Size => (RawValue >> SizeShift) & SizeMask; + public readonly Direction DirectionValue => (Direction)((RawValue >> DirectionShift) & DirectionMask); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs new file mode 100644 index 00000000..4e819330 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs @@ -0,0 +1,310 @@ +using Ryujinx.Common.Collections; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu.Memory; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Nv +{ + class NvMemoryAllocator + { + private const ulong AddressSpaceSize = 1UL << 40; + + private const ulong DefaultStart = 1UL << 32; + private const ulong InvalidAddress = 0; + + private const ulong PageSize = MemoryManager.PageSize; + private const ulong PageMask = MemoryManager.PageMask; + + public const ulong PteUnmapped = MemoryManager.PteUnmapped; + + // Key --> Start Address of Region + // Value --> End Address of Region + private readonly TreeDictionary _tree = new(); + + private readonly Dictionary> _dictionary = new(); + private readonly LinkedList _list = new(); + + public NvMemoryAllocator() + { + _tree.Add(PageSize, AddressSpaceSize); + LinkedListNode node = _list.AddFirst(PageSize); + _dictionary[PageSize] = node; + } + + /// + /// Marks a range of memory as consumed by removing it from the tree. + /// This function will split memory regions if there is available space. + /// + /// Virtual address at which to allocate + /// Size of the allocation in bytes + /// Reference to the address of memory where the allocation can take place + #region Memory Allocation + public void AllocateRange(ulong va, ulong size, ulong referenceAddress = InvalidAddress) + { + lock (_tree) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Allocating range from 0x{va:X} to 0x{(va + size):X}."); + if (referenceAddress != InvalidAddress) + { + ulong endAddress = va + size; + ulong referenceEndAddress = _tree.Get(referenceAddress); + if (va >= referenceAddress) + { + // Need Left Node + if (va > referenceAddress) + { + ulong leftEndAddress = va; + + // Overwrite existing block with its new smaller range. + _tree.Add(referenceAddress, leftEndAddress); + Logger.Debug?.Print(LogClass.ServiceNv, $"Created smaller address range from 0x{referenceAddress:X} to 0x{leftEndAddress:X}."); + } + else + { + // We need to get rid of the large chunk. + _tree.Remove(referenceAddress); + } + + ulong rightSize = referenceEndAddress - endAddress; + // If leftover space, create a right node. + if (rightSize > 0) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Created smaller address range from 0x{endAddress:X} to 0x{referenceEndAddress:X}."); + _tree.Add(endAddress, referenceEndAddress); + + LinkedListNode node = _list.AddAfter(_dictionary[referenceAddress], endAddress); + _dictionary[endAddress] = node; + } + + if (va == referenceAddress) + { + _list.Remove(_dictionary[referenceAddress]); + _dictionary.Remove(referenceAddress); + } + } + } + } + } + + /// + /// Marks a range of memory as free by adding it to the tree. + /// This function will automatically compact the tree when it determines there are multiple ranges of free memory adjacent to each other. + /// + /// Virtual address at which to deallocate + /// Size of the allocation in bytes + public void DeallocateRange(ulong va, ulong size) + { + lock (_tree) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Deallocating address range from 0x{va:X} to 0x{(va + size):X}."); + + ulong freeAddressStartPosition = _tree.Floor(va); + if (freeAddressStartPosition != InvalidAddress) + { + LinkedListNode node = _dictionary[freeAddressStartPosition]; + ulong targetPrevAddress = _dictionary[freeAddressStartPosition].Previous != null ? _dictionary[_dictionary[freeAddressStartPosition].Previous.Value].Value : InvalidAddress; + ulong targetNextAddress = _dictionary[freeAddressStartPosition].Next != null ? _dictionary[_dictionary[freeAddressStartPosition].Next.Value].Value : InvalidAddress; + ulong expandedStart = va; + ulong expandedEnd = va + size; + + while (targetPrevAddress != InvalidAddress) + { + ulong prevAddress = targetPrevAddress; + ulong prevEndAddress = _tree.Get(targetPrevAddress); + if (prevEndAddress >= expandedStart) + { + expandedStart = targetPrevAddress; + LinkedListNode prevPtr = _dictionary[prevAddress]; + if (prevPtr.Previous != null) + { + targetPrevAddress = prevPtr.Previous.Value; + } + else + { + targetPrevAddress = InvalidAddress; + } + node = node.Previous; + _tree.Remove(prevAddress); + _list.Remove(_dictionary[prevAddress]); + _dictionary.Remove(prevAddress); + } + else + { + break; + } + } + + while (targetNextAddress != InvalidAddress) + { + ulong nextAddress = targetNextAddress; + ulong nextEndAddress = _tree.Get(targetNextAddress); + if (nextAddress <= expandedEnd) + { + expandedEnd = Math.Max(expandedEnd, nextEndAddress); + LinkedListNode nextPtr = _dictionary[nextAddress]; + if (nextPtr.Next != null) + { + targetNextAddress = nextPtr.Next.Value; + } + else + { + targetNextAddress = InvalidAddress; + } + _tree.Remove(nextAddress); + _list.Remove(_dictionary[nextAddress]); + _dictionary.Remove(nextAddress); + } + else + { + break; + } + } + + Logger.Debug?.Print(LogClass.ServiceNv, $"Deallocation resulted in new free range from 0x{expandedStart:X} to 0x{expandedEnd:X}."); + + _tree.Add(expandedStart, expandedEnd); + LinkedListNode nodePtr = _list.AddAfter(node, expandedStart); + _dictionary[expandedStart] = nodePtr; + } + } + } + + /// + /// Gets the address of an unused (free) region of the specified size. + /// + /// Size of the region in bytes + /// Position at which memory can be allocated + /// Required alignment of the region address in bytes + /// Start address of the search on the address space + /// GPU virtual address of the allocation, or an all ones mask in case of failure + public ulong GetFreeAddress(ulong size, out ulong freeAddressStartPosition, ulong alignment = 1, ulong start = DefaultStart) + { + // Note: Address 0 is not considered valid by the driver, + // when 0 is returned it's considered a mapping error. + lock (_tree) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Searching for a free address @ 0x{start:X} of size 0x{size:X}."); + ulong address = start; + + if (alignment == 0) + { + alignment = 1; + } + + alignment = (alignment + PageMask) & ~PageMask; + if (address < AddressSpaceSize) + { + bool reachedEndOfAddresses = false; + ulong targetAddress; + if (start == DefaultStart) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Target address set to start of the last available range: 0x{_list.Last.Value:X}."); + targetAddress = _list.Last.Value; + } + else + { + targetAddress = _tree.Floor(address); + Logger.Debug?.Print(LogClass.ServiceNv, $"Target address set to floor of 0x{address:X}; resulted in 0x{targetAddress:X}."); + if (targetAddress == InvalidAddress) + { + targetAddress = _tree.Ceiling(address); + Logger.Debug?.Print(LogClass.ServiceNv, $"Target address was invalid, set to ceiling of 0x{address:X}; resulted in 0x{targetAddress:X}"); + } + } + while (address < AddressSpaceSize) + { + if (targetAddress != InvalidAddress) + { + if (address >= targetAddress) + { + if (address + size <= _tree.Get(targetAddress)) + { + Logger.Debug?.Print(LogClass.ServiceNv, $"Found a suitable free address range from 0x{targetAddress:X} to 0x{_tree.Get(targetAddress):X} for 0x{address:X}."); + freeAddressStartPosition = targetAddress; + return address; + } + else + { + Logger.Debug?.Print(LogClass.ServiceNv, "Address requirements exceeded the available space in the target range."); + LinkedListNode nextPtr = _dictionary[targetAddress]; + if (nextPtr.Next != null) + { + targetAddress = nextPtr.Next.Value; + Logger.Debug?.Print(LogClass.ServiceNv, $"Moved search to successor range starting at 0x{targetAddress:X}."); + } + else + { + if (reachedEndOfAddresses) + { + Logger.Debug?.Print(LogClass.ServiceNv, "Exiting loop, a full pass has already been completed w/ no suitable free address range."); + break; + } + else + { + reachedEndOfAddresses = true; + address = start; + targetAddress = _tree.Floor(address); + Logger.Debug?.Print(LogClass.ServiceNv, $"Reached the end of the available free ranges, restarting loop @ 0x{targetAddress:X} for 0x{address:X}."); + } + } + } + } + else + { + address += PageSize * (targetAddress / PageSize - (address / PageSize)); + + ulong remainder = address % alignment; + + if (remainder != 0) + { + address = (address - remainder) + alignment; + } + + Logger.Debug?.Print(LogClass.ServiceNv, $"Reset and aligned address to {address:X}."); + + if (address + size > AddressSpaceSize && !reachedEndOfAddresses) + { + reachedEndOfAddresses = true; + address = start; + targetAddress = _tree.Floor(address); + Logger.Debug?.Print(LogClass.ServiceNv, $"Address requirements exceeded the capacity of available address space, restarting loop @ 0x{targetAddress:X} for 0x{address:X}."); + } + } + } + else + { + break; + } + } + } + Logger.Debug?.Print(LogClass.ServiceNv, $"No suitable address range found; returning: 0x{InvalidAddress:X}."); + freeAddressStartPosition = InvalidAddress; + } + + return PteUnmapped; + } + + /// + /// Checks if a given memory region is mapped or reserved. + /// + /// GPU virtual address of the page + /// Size of the allocation in bytes + /// Nearest lower address that memory can be allocated + /// True if the page is mapped or reserved, false otherwise + public bool IsRegionInUse(ulong gpuVa, ulong size, out ulong freeAddressStartPosition) + { + lock (_tree) + { + ulong floorAddress = _tree.Floor(gpuVa); + freeAddressStartPosition = floorAddress; + if (floorAddress != InvalidAddress) + { + return !(gpuVa >= floorAddress && ((gpuVa + size) <= _tree.Get(floorAddress))); + } + } + return true; + } + #endregion + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs new file mode 100644 index 00000000..214987e9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs @@ -0,0 +1,41 @@ +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8)] + internal struct NvFence + { + public const uint InvalidSyncPointId = uint.MaxValue; + + public uint Id; + public uint Value; + + public readonly bool IsValid() + { + return Id != InvalidSyncPointId; + } + + public void UpdateValue(NvHostSyncpt hostSyncpt) + { + Value = hostSyncpt.ReadSyncpointValue(Id); + } + + public void Increment(GpuContext gpuContext) + { + Value = gpuContext.Synchronization.IncrementSyncpoint(Id); + } + + public readonly bool Wait(GpuContext gpuContext, TimeSpan timeout) + { + if (IsValid()) + { + return gpuContext.Synchronization.WaitOnSyncpoint(Id, Value, timeout); + } + + return false; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs new file mode 100644 index 00000000..e05e32aa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs @@ -0,0 +1,55 @@ +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + class NvIoctlNotImplementedException : Exception + { + public ServiceCtx Context { get; } + public NvDeviceFile DeviceFile { get; } + public NvIoctl Command { get; } + + public NvIoctlNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, NvIoctl command) + : this(context, deviceFile, command, "The ioctl is not implemented.") + { } + + public NvIoctlNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, NvIoctl command, string message) + : base(message) + { + Context = context; + DeviceFile = deviceFile; + Command = command; + } + + public override string Message + { + get + { + return base.Message + + Environment.NewLine + + Environment.NewLine + + BuildMessage(); + } + } + + private string BuildMessage() + { + StringBuilder sb = new(); + + sb.AppendLine($"Device File: {DeviceFile.GetType().Name}"); + sb.AppendLine(); + + sb.AppendLine($"Ioctl (0x{Command.RawValue:x8})"); + sb.AppendLine($"\tNumber: 0x{Command.Number:x8}"); + sb.AppendLine($"\tType: 0x{Command.Type:x8}"); + sb.AppendLine($"\tSize: 0x{Command.Size:x8}"); + sb.AppendLine($"\tDirection: {Command.DirectionValue}"); + + sb.AppendLine("Guest Stack Trace:"); + sb.AppendLine(Context.Thread.GetGuestStackTrace()); + + return sb.ToString(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs new file mode 100644 index 00000000..f9e826fc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs @@ -0,0 +1,51 @@ +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + class NvQueryEventNotImplementedException : Exception + { + public ServiceCtx Context { get; } + public NvDeviceFile DeviceFile { get; } + public uint EventId { get; } + + public NvQueryEventNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, uint eventId) + : this(context, deviceFile, eventId, "This query event is not implemented.") + { } + + public NvQueryEventNotImplementedException(ServiceCtx context, NvDeviceFile deviceFile, uint eventId, string message) + : base(message) + { + Context = context; + DeviceFile = deviceFile; + EventId = eventId; + } + + public override string Message + { + get + { + return base.Message + + Environment.NewLine + + Environment.NewLine + + BuildMessage(); + } + } + + private string BuildMessage() + { + StringBuilder sb = new(); + + sb.AppendLine($"Device File: {DeviceFile.GetType().Name}"); + sb.AppendLine(); + + sb.AppendLine($"Event ID: (0x{EventId:x8})"); + + sb.AppendLine("Guest Stack Trace:"); + sb.AppendLine(Context.Thread.GetGuestStackTrace()); + + return sb.ToString(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs new file mode 100644 index 00000000..6f7e09a6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.HLE.HOS.Services.Nv +{ + enum NvResult : uint + { + Success = 0, + NotImplemented = 1, + NotSupported = 2, + NotInitialized = 3, + InvalidParameter = 4, + Timeout = 5, + InsufficientMemory = 6, + ReadOnlyAttribute = 7, + InvalidState = 8, + InvalidAddress = 9, + InvalidSize = 10, + InvalidValue = 11, + AlreadyAllocated = 13, + Busy = 14, + ResourceError = 15, + CountMismatch = 16, + SharedMemoryTooSmall = 0x1000, + FileOperationFailed = 0x30003, + DirectoryOperationFailed = 0x30004, + NotAvailableInProduction = 0x30006, + IoctlFailed = 0x3000F, + AccessDenied = 0x30010, + FileNotFound = 0x30013, + ModuleNotPresent = 0xA000E, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs new file mode 100644 index 00000000..645a8433 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nv.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20)] + struct NvStatus + { + public uint MemoryValue1; + public uint MemoryValue2; + public uint MemoryValue3; + public uint MemoryValue4; + public long Padding1; + public long Padding2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForApplication.cs b/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForApplication.cs new file mode 100644 index 00000000..1513d6fe --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForApplication.cs @@ -0,0 +1,90 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Olsc +{ + [Service("olsc:u")] // 10.0.0+ + class IOlscServiceForApplication : IpcService + { + private bool _initialized; + private Dictionary _saveDataBackupSettingDatabase; + + public IOlscServiceForApplication(ServiceCtx context) { } + + [CommandCmif(0)] + // Initialize(pid) + public ResultCode Initialize(ServiceCtx context) + { + // NOTE: Service call arp:r GetApplicationInstanceUnregistrationNotifier with the pid and initialize some internal struct. + // Since we will not support online savedata backup, it's fine to stub it for now. + + _saveDataBackupSettingDatabase = new Dictionary(); + + _initialized = true; + + Logger.Stub?.PrintStub(LogClass.ServiceOlsc); + + return ResultCode.Success; + } + + [CommandCmif(13)] + // GetSaveDataBackupSetting(nn::account::Uid) -> u8 + public ResultCode GetSaveDataBackupSetting(ServiceCtx context) + { + UserId userId = context.RequestData.ReadStruct(); + + if (!_initialized) + { + return ResultCode.NotInitialized; + } + + if (userId.IsNull) + { + return ResultCode.NullArgument; + } + + if (_saveDataBackupSettingDatabase.TryGetValue(userId, out bool enabled) && enabled) + { + context.ResponseData.Write((byte)1); // TODO: Determine value. + } + else + { + context.ResponseData.Write((byte)2); // TODO: Determine value. + } + + // NOTE: Since we will not support online savedata backup, it's fine to stub it for now. + + Logger.Stub?.PrintStub(LogClass.ServiceOlsc, new { userId }); + + return ResultCode.Success; + } + + [CommandCmif(14)] + // SetSaveDataBackupSettingEnabled(nn::account::Uid, bool) + public ResultCode SetSaveDataBackupSettingEnabled(ServiceCtx context) + { + bool saveDataBackupSettingEnabled = context.RequestData.ReadUInt64() != 0; + UserId userId = context.RequestData.ReadStruct(); + + if (!_initialized) + { + return ResultCode.NotInitialized; + } + + if (userId.IsNull) + { + return ResultCode.NullArgument; + } + + _saveDataBackupSettingDatabase[userId] = saveDataBackupSettingEnabled; + + // NOTE: Since we will not support online savedata backup, it's fine to stub it for now. + + Logger.Stub?.PrintStub(LogClass.ServiceOlsc, new { userId, saveDataBackupSettingEnabled }); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs b/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs new file mode 100644 index 00000000..c6e37205 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Olsc +{ + [Service("olsc:s")] // 4.0.0+ + class IOlscServiceForSystemService : IpcService + { + public IOlscServiceForSystemService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Olsc/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Olsc/ResultCode.cs new file mode 100644 index 00000000..78392f6e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Olsc/ResultCode.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Olsc +{ + enum ResultCode + { + ModuleId = 179, + ErrorCodeShift = 9, + + Success = 0, + + NullArgument = (100 << ErrorCodeShift) | ModuleId, + NotInitialized = (101 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pcie/ILogManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcie/ILogManager.cs new file mode 100644 index 00000000..4812727b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcie/ILogManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcie +{ + [Service("pcie:log")] + class ILogManager : IpcService + { + public ILogManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pcie/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcie/IManager.cs new file mode 100644 index 00000000..42e48035 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcie/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcie +{ + [Service("pcie")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs b/src/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs new file mode 100644 index 00000000..707f6423 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs @@ -0,0 +1,40 @@ +using Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory; + +namespace Ryujinx.HLE.HOS.Services.Pctl +{ + [Service("pctl", 0x303)] + [Service("pctl:a", 0x83BE)] + [Service("pctl:r", 0x8040)] + [Service("pctl:s", 0x838E)] + class IParentalControlServiceFactory : IpcService + { + private readonly int _permissionFlag; + + public IParentalControlServiceFactory(ServiceCtx context, int permissionFlag) + { + _permissionFlag = permissionFlag; + } + + [CommandCmif(0)] + // CreateService(u64, pid) -> object + public ResultCode CreateService(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + + MakeObject(context, new IParentalControlService(context, pid, true, _permissionFlag)); + + return ResultCode.Success; + } + + [CommandCmif(1)] // 4.0.0+ + // CreateServiceWithoutInitialize(u64, pid) -> object + public ResultCode CreateServiceWithoutInitialize(ServiceCtx context) + { + ulong pid = context.Request.HandleDesc.PId; + + MakeObject(context, new IParentalControlService(context, pid, false, _permissionFlag)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs b/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs new file mode 100644 index 00000000..9b026a1c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs @@ -0,0 +1,260 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Arp; +using System; +using static LibHac.Ns.ApplicationControlProperty; + +namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory +{ + class IParentalControlService : IpcService + { + private readonly ulong _pid; + private readonly int _permissionFlag; + private ulong _titleId; + private ParentalControlFlagValue _parentalControlFlag; +#pragma warning disable IDE0052, CS0414 // Remove unread private member + private int[] _ratingAge; + + // TODO: Find where they are set. + private readonly bool _restrictionEnabled = false; + private readonly bool _featuresRestriction = false; + private bool _freeCommunicationEnabled = false; + private readonly bool _stereoVisionRestrictionConfigurable = true; + private bool _stereoVisionRestriction = false; +#pragma warning restore IDE0052, CS0414 + + public IParentalControlService(ServiceCtx context, ulong pid, bool withInitialize, int permissionFlag) + { + _pid = pid; + _permissionFlag = permissionFlag; + + if (withInitialize) + { + Initialize(context); + } + } + + [CommandCmif(1)] // 4.0.0+ + // Initialize() + public ResultCode Initialize(ServiceCtx context) + { + if ((_permissionFlag & 0x8001) == 0) + { + return ResultCode.PermissionDenied; + } + + ResultCode resultCode = ResultCode.InvalidPid; + + if (_pid != 0) + { + if ((_permissionFlag & 0x40) == 0) + { + ulong titleId = ApplicationLaunchProperty.GetByPid(context).TitleId; + + if (titleId != 0) + { + _titleId = titleId; + + // TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields. + _ratingAge = Array.ConvertAll(context.Device.Processes.ActiveApplication.ApplicationControlProperties.RatingAge.ItemsRo.ToArray(), Convert.ToInt32); + _parentalControlFlag = context.Device.Processes.ActiveApplication.ApplicationControlProperties.ParentalControlFlag; + } + } + + if (_titleId != 0) + { + // TODO: Service store some private fields in another object. + + if ((_permissionFlag & 0x8040) == 0) + { + // TODO: Service store TitleId and FreeCommunicationEnabled in another object. + // When it's done it signal an event in this object. + Logger.Stub?.PrintStub(LogClass.ServicePctl); + } + } + + resultCode = ResultCode.Success; + } + + return resultCode; + } + + [CommandCmif(1001)] + // CheckFreeCommunicationPermission() + public ResultCode CheckFreeCommunicationPermission(ServiceCtx context) + { + if (_parentalControlFlag == ParentalControlFlagValue.FreeCommunication && _restrictionEnabled) + { + // TODO: It seems to checks if an entry exists in the FreeCommunicationApplicationList using the TitleId. + // Then it returns FreeCommunicationDisabled if the entry doesn't exist. + + return ResultCode.FreeCommunicationDisabled; + } + + _freeCommunicationEnabled = true; + + Logger.Stub?.PrintStub(LogClass.ServicePctl); + + return ResultCode.Success; + } + + [CommandCmif(1017)] // 10.0.0+ + // EndFreeCommunication() + public ResultCode EndFreeCommunication(ServiceCtx context) + { + _freeCommunicationEnabled = false; + + return ResultCode.Success; + } + + [CommandCmif(1013)] // 4.0.0+ + // ConfirmStereoVisionPermission() + public ResultCode ConfirmStereoVisionPermission(ServiceCtx context) + { + return IsStereoVisionPermittedImpl(); + } + + [CommandCmif(1018)] + // IsFreeCommunicationAvailable() + public ResultCode IsFreeCommunicationAvailable(ServiceCtx context) + { + if (_parentalControlFlag == ParentalControlFlagValue.FreeCommunication && _restrictionEnabled) + { + // TODO: It seems to checks if an entry exists in the FreeCommunicationApplicationList using the TitleId. + // Then it returns FreeCommunicationDisabled if the entry doesn't exist. + + return ResultCode.FreeCommunicationDisabled; + } + + Logger.Stub?.PrintStub(LogClass.ServicePctl); + + return ResultCode.Success; + } + + [CommandCmif(1031)] + // IsRestrictionEnabled() -> b8 + public ResultCode IsRestrictionEnabled(ServiceCtx context) + { + if ((_permissionFlag & 0x140) == 0) + { + return ResultCode.PermissionDenied; + } + + context.ResponseData.Write(_restrictionEnabled); + + return ResultCode.Success; + } + + [CommandCmif(1061)] // 4.0.0+ + // ConfirmStereoVisionRestrictionConfigurable() + public ResultCode ConfirmStereoVisionRestrictionConfigurable(ServiceCtx context) + { + if ((_permissionFlag & 2) == 0) + { + return ResultCode.PermissionDenied; + } + + if (_stereoVisionRestrictionConfigurable) + { + return ResultCode.Success; + } + else + { + return ResultCode.StereoVisionRestrictionConfigurableDisabled; + } + } + + [CommandCmif(1062)] // 4.0.0+ + // GetStereoVisionRestriction() -> bool + public ResultCode GetStereoVisionRestriction(ServiceCtx context) + { + if ((_permissionFlag & 0x200) == 0) + { + return ResultCode.PermissionDenied; + } + +#pragma warning disable // Remove unnecessary value assignment + bool stereoVisionRestriction = false; +#pragma warning restore IDE0059 + + if (_stereoVisionRestrictionConfigurable) + { + stereoVisionRestriction = _stereoVisionRestriction; + } + + context.ResponseData.Write(stereoVisionRestriction); + + return ResultCode.Success; + } + + [CommandCmif(1063)] // 4.0.0+ + // SetStereoVisionRestriction(bool) + public ResultCode SetStereoVisionRestriction(ServiceCtx context) + { + if ((_permissionFlag & 0x200) == 0) + { + return ResultCode.PermissionDenied; + } + + bool stereoVisionRestriction = context.RequestData.ReadBoolean(); + + if (!_featuresRestriction) + { + if (_stereoVisionRestrictionConfigurable) + { + _stereoVisionRestriction = stereoVisionRestriction; + + // TODO: It signals an internal event of service. We have to determine where this event is used. + } + } + + return ResultCode.Success; + } + + [CommandCmif(1064)] // 5.0.0+ + // ResetConfirmedStereoVisionPermission() + public ResultCode ResetConfirmedStereoVisionPermission(ServiceCtx context) + { + return ResultCode.Success; + } + + [CommandCmif(1065)] // 5.0.0+ + // IsStereoVisionPermitted() -> bool + public ResultCode IsStereoVisionPermitted(ServiceCtx context) + { + bool isStereoVisionPermitted = false; + + ResultCode resultCode = IsStereoVisionPermittedImpl(); + + if (resultCode == ResultCode.Success) + { + isStereoVisionPermitted = true; + } + + context.ResponseData.Write(isStereoVisionPermitted); + + return resultCode; + } + + private ResultCode IsStereoVisionPermittedImpl() + { + /* + // TODO: Application Exemptions are read from file "appExemptions.dat" in the service savedata. + // Since we don't support the pctl savedata for now, this can be implemented later. + + if (appExemption) + { + return ResultCode.Success; + } + */ + + if (_stereoVisionRestrictionConfigurable && _stereoVisionRestriction) + { + return ResultCode.StereoVisionDenied; + } + else + { + return ResultCode.Success; + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs new file mode 100644 index 00000000..c3d157ed --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.HLE.HOS.Services.Pctl +{ + enum ResultCode + { + ModuleId = 142, + ErrorCodeShift = 9, + + Success = 0, + + FreeCommunicationDisabled = (101 << ErrorCodeShift) | ModuleId, + StereoVisionDenied = (104 << ErrorCodeShift) | ModuleId, + InvalidPid = (131 << ErrorCodeShift) | ModuleId, + PermissionDenied = (133 << ErrorCodeShift) | ModuleId, + StereoVisionRestrictionConfigurableDisabled = (181 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IBoardPowerControlManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IBoardPowerControlManager.cs new file mode 100644 index 00000000..26fd8690 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IBoardPowerControlManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Bpc +{ + [Service("bpc")] + class IBoardPowerControlManager : IpcService + { + public IBoardPowerControlManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IRtcManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IRtcManager.cs new file mode 100644 index 00000000..c3717684 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IRtcManager.cs @@ -0,0 +1,32 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Pcv.Bpc +{ + [Service("bpc:r")] // 1.0.0 - 8.1.0 + class IRtcManager : IpcService + { + public IRtcManager(ServiceCtx context) { } + + [CommandCmif(0)] + // GetRtcTime() -> u64 + public ResultCode GetRtcTime(ServiceCtx context) + { + ResultCode result = GetExternalRtcValue(out ulong rtcValue); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(rtcValue); + } + + return result; + } + + public static ResultCode GetExternalRtcValue(out ulong rtcValue) + { + // TODO: emulate MAX77620/MAX77812 RTC + rtcValue = (ulong)(DateTime.Now.ToUniversalTime() - DateTime.UnixEpoch).TotalSeconds; + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/ClkrstManager/IClkrstSession.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/ClkrstManager/IClkrstSession.cs new file mode 100644 index 00000000..ddc4e5c7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/ClkrstManager/IClkrstSession.cs @@ -0,0 +1,63 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Pcv.Types; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Services.Pcv.Clkrst.ClkrstManager +{ + class IClkrstSession : IpcService + { + private readonly DeviceCode _deviceCode; +#pragma warning disable IDE0052 // Remove unread private member + private readonly uint _unknown; +#pragma warning restore IDE0052 + private uint _clockRate; + + private readonly DeviceCode[] _allowedDeviceCodeTable = { + DeviceCode.Cpu, DeviceCode.Gpu, DeviceCode.Disp1, DeviceCode.Disp2, + DeviceCode.Tsec, DeviceCode.Mselect, DeviceCode.Sor1, DeviceCode.Host1x, + DeviceCode.Vic, DeviceCode.Nvenc, DeviceCode.Nvjpg, DeviceCode.Nvdec, + DeviceCode.Ape, DeviceCode.AudioDsp, DeviceCode.Emc, DeviceCode.Dsi, + DeviceCode.SysBus, DeviceCode.XusbSs, DeviceCode.XusbHost, DeviceCode.XusbDevice, + DeviceCode.Gpuaux, DeviceCode.Pcie, DeviceCode.Apbdma, DeviceCode.Sdmmc1, + DeviceCode.Sdmmc2, DeviceCode.Sdmmc4, + }; + + public IClkrstSession(DeviceCode deviceCode, uint unknown) + { + _deviceCode = deviceCode; + _unknown = unknown; + } + + [CommandCmif(7)] + // SetClockRate(u32 hz) + public ResultCode SetClockRate(ServiceCtx context) + { + if (!_allowedDeviceCodeTable.Contains(_deviceCode)) + { + return ResultCode.InvalidArgument; + } + + _clockRate = context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServicePcv, new { _clockRate }); + + return ResultCode.Success; + } + + [CommandCmif(8)] + // GetClockRate() -> u32 hz + public ResultCode GetClockRate(ServiceCtx context) + { + if (!_allowedDeviceCodeTable.Contains(_deviceCode)) + { + return ResultCode.InvalidArgument; + } + + context.ResponseData.Write(_clockRate); + + Logger.Stub?.PrintStub(LogClass.ServicePcv, new { _clockRate }); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IArbitrationManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IArbitrationManager.cs new file mode 100644 index 00000000..4c7e3300 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IArbitrationManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Clkrst +{ + [Service("clkrst:a")] // 8.0.0+ + class IArbitrationManager : IpcService + { + public IArbitrationManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IClkrstManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IClkrstManager.cs new file mode 100644 index 00000000..c7c45919 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IClkrstManager.cs @@ -0,0 +1,57 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Pcv.Clkrst.ClkrstManager; +using Ryujinx.HLE.HOS.Services.Pcv.Types; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Pcv.Clkrst +{ + [Service("clkrst")] // 8.0.0+ + [Service("clkrst:i")] // 8.0.0+ + class IClkrstManager : IpcService + { + private int _moduleStateTableEventHandle = 0; + + public IClkrstManager(ServiceCtx context) { } + + [CommandCmif(0)] + // OpenSession(u32 device_code, u32 unk) -> object + public ResultCode OpenSession(ServiceCtx context) + { + DeviceCode deviceCode = (DeviceCode)context.RequestData.ReadUInt32(); + uint unknown = context.RequestData.ReadUInt32(); + + // TODO: Service checks the deviceCode and the unk value. + + MakeObject(context, new IClkrstSession(deviceCode, unknown)); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetModuleStateTableEvent() -> handle + public ResultCode GetModuleStateTableEvent(ServiceCtx context) + { + if (_moduleStateTableEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(context.Device.System.IirsSharedMem, out _moduleStateTableEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_moduleStateTableEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // GetModuleStateTableMaxCount() -> u32 max_count + public ResultCode GetModuleStateTableMaxCount(ServiceCtx context) + { + context.ResponseData.Write(26u); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/IPcvService.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/IPcvService.cs new file mode 100644 index 00000000..d25a71b8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/IPcvService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv +{ + [Service("pcv")] + class IPcvService : IpcService + { + public IPcvService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/ResultCode.cs new file mode 100644 index 00000000..b1694b34 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/ResultCode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv +{ + enum ResultCode + { + ModuleId = 30, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArgument = (5 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Rgltr/IRegulatorManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Rgltr/IRegulatorManager.cs new file mode 100644 index 00000000..5a290e42 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Rgltr/IRegulatorManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Rgltr +{ + [Service("rgltr")] // 8.0.0+ + class IRegulatorManager : IpcService + { + public IRegulatorManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Rtc/IRtcManager.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Rtc/IRtcManager.cs new file mode 100644 index 00000000..460a7cc7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Rtc/IRtcManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Rtc +{ + [Service("rtc")] // 8.0.0+ + class IRtcManager : IpcService + { + public IRtcManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pcv/Types/DeviceCode.cs b/src/Ryujinx.HLE/HOS/Services/Pcv/Types/DeviceCode.cs new file mode 100644 index 00000000..7aa95b21 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pcv/Types/DeviceCode.cs @@ -0,0 +1,94 @@ +namespace Ryujinx.HLE.HOS.Services.Pcv.Types +{ + enum DeviceCode + { + Cpu = 0x40000001, + Gpu = 0x40000002, + I2s1 = 0x40000003, + I2s2 = 0x40000004, + I2s3 = 0x40000005, + Pwm = 0x40000006, + I2c1 = 0x02000001, + I2c2 = 0x02000002, + I2c3 = 0x02000003, + I2c4 = 0x02000004, + I2c5 = 0x02000005, + I2c6 = 0x02000006, + Spi1 = 0x07000000, + Spi2 = 0x07000001, + Spi3 = 0x07000002, + Spi4 = 0x07000003, + Disp1 = 0x40000011, + Disp2 = 0x40000012, + Isp = 0x40000013, + Vi = 0x40000014, + Sdmmc1 = 0x40000015, + Sdmmc2 = 0x40000016, + Sdmmc3 = 0x40000017, + Sdmmc4 = 0x40000018, + Owr = 0x40000019, + Csite = 0x4000001A, + Tsec = 0x4000001B, + Mselect = 0x4000001C, + Hda2codec2x = 0x4000001D, + Actmon = 0x4000001E, + I2cSlow = 0x4000001F, + Sor1 = 0x40000020, + Sata = 0x40000021, + Hda = 0x40000022, + XusbCoreHostSrc = 0x40000023, + XusbFalconSrc = 0x40000024, + XusbFsSrc = 0x40000025, + XusbCoreDevSrc = 0x40000026, + XusbSsSrc = 0x40000027, + UartA = 0x03000001, + UartB = 0x35000405, + UartC = 0x3500040F, + UartD = 0x37000001, + Host1x = 0x4000002C, + Entropy = 0x4000002D, + SocTherm = 0x4000002E, + Vic = 0x4000002F, + Nvenc = 0x40000030, + Nvjpg = 0x40000031, + Nvdec = 0x40000032, + Qspi = 0x40000033, + ViI2c = 0x40000034, + Tsecb = 0x40000035, + Ape = 0x40000036, + AudioDsp = 0x40000037, + AudioUart = 0x40000038, + Emc = 0x40000039, + Plle = 0x4000003A, + PlleHwSeq = 0x4000003B, + Dsi = 0x4000003C, + Maud = 0x4000003D, + Dpaux1 = 0x4000003E, + MipiCal = 0x4000003F, + UartFstMipiCal = 0x40000040, + Osc = 0x40000041, + SysBus = 0x40000042, + SorSafe = 0x40000043, + XusbSs = 0x40000044, + XusbHost = 0x40000045, + XusbDevice = 0x40000046, + Extperiph1 = 0x40000047, + Ahub = 0x40000048, + Hda2hdmicodec = 0x40000049, + Gpuaux = 0x4000004A, + UsbD = 0x4000004B, + Usb2 = 0x4000004C, + Pcie = 0x4000004D, + Afi = 0x4000004E, + PciExClk = 0x4000004F, + PExUsbPhy = 0x40000050, + XUsbPadCtl = 0x40000051, + Apbdma = 0x40000052, + Usb2TrkClk = 0x40000053, + XUsbIoPll = 0x40000054, + XUsbIoPllHwSeq = 0x40000055, + Cec = 0x40000056, + Extperiph2 = 0x40000057, + OscClk = 0x40000080, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pm/IBootModeInterface.cs b/src/Ryujinx.HLE/HOS/Services/Pm/IBootModeInterface.cs new file mode 100644 index 00000000..1f1aafc1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pm/IBootModeInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Pm +{ + [Service("pm:bm")] + class IBootModeInterface : IpcService + { + public IBootModeInterface(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs b/src/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs new file mode 100644 index 00000000..9becc6e4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs @@ -0,0 +1,49 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.Horizon.Common; + +namespace Ryujinx.HLE.HOS.Services.Pm +{ + [Service("pm:dmnt")] + class IDebugMonitorInterface : IpcService + { + public IDebugMonitorInterface(ServiceCtx context) { } + + [CommandCmif(4)] + // GetProgramId() -> sf::Out out_process_id + public ResultCode GetApplicationProcessId(ServiceCtx context) + { + // TODO: Not correct as it shouldn't be directly using kernel objects here + foreach (KProcess process in context.Device.System.KernelContext.Processes.Values) + { + if (process.IsApplication) + { + context.ResponseData.Write(process.Pid); + + return ResultCode.Success; + } + } + + return ResultCode.ProcessNotFound; + } + + [CommandCmif(65000)] + // AtmosphereGetProcessInfo(os::ProcessId process_id) -> sf::OutCopyHandle out_process_handle, sf::Out out_loc, sf::Out out_status + public ResultCode GetProcessInfo(ServiceCtx context) + { + ulong pid = context.RequestData.ReadUInt64(); + + KProcess process = KernelStatic.GetProcessByPid(pid); + + if (context.Process.HandleTable.GenerateHandle(process, out int processHandle) != Result.Success) + { + throw new System.Exception("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(processHandle); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pm/IInformationInterface.cs b/src/Ryujinx.HLE/HOS/Services/Pm/IInformationInterface.cs new file mode 100644 index 00000000..d6440a4d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pm/IInformationInterface.cs @@ -0,0 +1,27 @@ +using Ryujinx.HLE.HOS.Kernel.Process; + +namespace Ryujinx.HLE.HOS.Services.Pm +{ + [Service("pm:info")] + class IInformationInterface : IpcService + { + public IInformationInterface(ServiceCtx context) { } + + [CommandCmif(0)] + // GetProgramId(os::ProcessId process_id) -> sf::Out out + public ResultCode GetProgramId(ServiceCtx context) + { + ulong pid = context.RequestData.ReadUInt64(); + + // TODO: Not correct as it shouldn't be directly using kernel objects here + if (context.Device.System.KernelContext.Processes.TryGetValue(pid, out KProcess process)) + { + context.ResponseData.Write(process.TitleId); + + return ResultCode.Success; + } + + return ResultCode.ProcessNotFound; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs b/src/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs new file mode 100644 index 00000000..1ee2c377 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Services.Pm +{ + [Service("pm:shell")] + class IShellInterface : IpcService + { + public IShellInterface(ServiceCtx context) { } + + [CommandCmif(6)] + // GetApplicationPid() -> u64 + public ResultCode GetApplicationPid(ServiceCtx context) + { + // FIXME: This is wrong but needed to make hb loader works + // TODO: Change this when we will have a way to process via a PM like interface. + ulong pid = context.Process.Pid; + + context.ResponseData.Write(pid); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Pm/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Pm/ResultCode.cs new file mode 100644 index 00000000..9894cd20 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Pm/ResultCode.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Services.Pm +{ + enum ResultCode + { + ModuleId = 15, + ErrorCodeShift = 9, + + Success = 0, + + ProcessNotFound = (1 << ErrorCodeShift) | ModuleId, + AlreadyStarted = (2 << ErrorCodeShift) | ModuleId, + NotTerminated = (3 << ErrorCodeShift) | ModuleId, + DebugHookInUse = (4 << ErrorCodeShift) | ModuleId, + ApplicationRunning = (5 << ErrorCodeShift) | ModuleId, + InvalidSize = (6 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Fan/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Fan/IManager.cs new file mode 100644 index 00000000..4498fe0d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Fan/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Fan +{ + [Service("fan")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/IDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/IDebugger.cs new file mode 100644 index 00000000..a9ce42e7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/IDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Fgm +{ + [Service("fgm:dbg")] // 9.0.0+ + class IDebugger : IpcService + { + public IDebugger(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/ISession.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/ISession.cs new file mode 100644 index 00000000..6373ab2d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/ISession.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Fgm +{ + [Service("fgm")] // 9.0.0+ + [Service("fgm:0")] // 9.0.0+ + [Service("fgm:9")] // 9.0.0+ + class ISession : IpcService + { + public ISession(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Pcm/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Pcm/IManager.cs new file mode 100644 index 00000000..e05035ac --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Pcm/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Pcm +{ + [Service("pcm")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmServer.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmServer.cs new file mode 100644 index 00000000..3ce50297 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmServer.cs @@ -0,0 +1,45 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Ptm.Psm +{ + [Service("psm")] + class IPsmServer : IpcService + { + public IPsmServer(ServiceCtx context) { } + + [CommandCmif(0)] + // GetBatteryChargePercentage() -> u32 + public static ResultCode GetBatteryChargePercentage(ServiceCtx context) + { + int chargePercentage = 100; + + context.ResponseData.Write(chargePercentage); + + Logger.Stub?.PrintStub(LogClass.ServicePsm, new { chargePercentage }); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetChargerType() -> u32 + public static ResultCode GetChargerType(ServiceCtx context) + { + ChargerType chargerType = ChargerType.ChargerOrDock; + + context.ResponseData.Write((int)chargerType); + + Logger.Stub?.PrintStub(LogClass.ServicePsm, new { chargerType }); + + return ResultCode.Success; + } + + [CommandCmif(7)] + // OpenSession() -> IPsmSession + public ResultCode OpenSession(ServiceCtx context) + { + MakeObject(context, new IPsmSession(context.Device.System)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmSession.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmSession.cs new file mode 100644 index 00000000..edfa60af --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmSession.cs @@ -0,0 +1,88 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; + +namespace Ryujinx.HLE.HOS.Services.Ptm.Psm +{ + class IPsmSession : IpcService + { + private readonly KEvent _stateChangeEvent; + private int _stateChangeEventHandle; + + public IPsmSession(Horizon system) + { + _stateChangeEvent = new KEvent(system.KernelContext); + _stateChangeEventHandle = -1; + } + + [CommandCmif(0)] + // BindStateChangeEvent() -> KObject + public ResultCode BindStateChangeEvent(ServiceCtx context) + { + if (_stateChangeEventHandle == -1) + { + Result resultCode = context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out _stateChangeEventHandle); + + if (resultCode != Result.Success) + { + return (ResultCode)resultCode.ErrorCode; + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle); + + Logger.Stub?.PrintStub(LogClass.ServicePsm); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // UnbindStateChangeEvent() + public ResultCode UnbindStateChangeEvent(ServiceCtx context) + { + if (_stateChangeEventHandle != -1) + { + context.Process.HandleTable.CloseHandle(_stateChangeEventHandle); + _stateChangeEventHandle = -1; + } + + Logger.Stub?.PrintStub(LogClass.ServicePsm); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // SetChargerTypeChangeEventEnabled(u8) + public ResultCode SetChargerTypeChangeEventEnabled(ServiceCtx context) + { + bool chargerTypeChangeEventEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServicePsm, new { chargerTypeChangeEventEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // SetPowerSupplyChangeEventEnabled(u8) + public ResultCode SetPowerSupplyChangeEventEnabled(ServiceCtx context) + { + bool powerSupplyChangeEventEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServicePsm, new { powerSupplyChangeEventEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // SetBatteryVoltageStateChangeEventEnabled(u8) + public ResultCode SetBatteryVoltageStateChangeEventEnabled(ServiceCtx context) + { + bool batteryVoltageStateChangeEventEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServicePsm, new { batteryVoltageStateChangeEventEnabled }); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/Types/ChargerType.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/Types/ChargerType.cs new file mode 100644 index 00000000..5b6ee42d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Psm/Types/ChargerType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Psm +{ + enum ChargerType + { + None, + ChargerOrDock, + UsbC, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ptm/Tc/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Ptm/Tc/IManager.cs new file mode 100644 index 00000000..0342a15b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ptm/Tc/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ptm.Tc +{ + [Service("tc")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs new file mode 100644 index 00000000..5b5b3bf8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs @@ -0,0 +1,600 @@ +using LibHac.Tools.FsSystem; +using Ryujinx.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; + +namespace Ryujinx.HLE.HOS.Services.Ro +{ + [Service("ldr:ro")] + [Service("ro:1")] // 7.0.0+ + class IRoInterface : DisposableIpcService + { + private const int MaxNrr = 0x40; + private const int MaxNro = 0x40; + private const int MaxMapRetries = 0x200; + private const int GuardPagesSize = 0x4000; + + private const uint NrrMagic = 0x3052524E; + private const uint NroMagic = 0x304F524E; + + private readonly List _nrrInfos; + private readonly List _nroInfos; + + private KProcess _owner; + private IVirtualMemoryManager _ownerMm; + + public IRoInterface(ServiceCtx context) + { + _nrrInfos = new List(MaxNrr); + _nroInfos = new List(MaxNro); + _owner = null; + _ownerMm = null; + } + + private ResultCode ParseNrr(out NrrInfo nrrInfo, ServiceCtx context, ulong nrrAddress, ulong nrrSize) + { + nrrInfo = null; + + if (nrrSize == 0 || nrrAddress + nrrSize <= nrrAddress || (nrrSize & 0xFFF) != 0) + { + return ResultCode.InvalidSize; + } + else if ((nrrAddress & 0xFFF) != 0) + { + return ResultCode.InvalidAddress; + } + + NrrHeader header = _owner.CpuMemory.Read(nrrAddress); + + if (header.Magic != NrrMagic) + { + return ResultCode.InvalidNrr; + } + else if (header.Size != nrrSize) + { + return ResultCode.InvalidSize; + } + + List hashes = new(); + + for (int i = 0; i < header.HashesCount; i++) + { + byte[] hash = new byte[0x20]; + + _owner.CpuMemory.Read(nrrAddress + header.HashesOffset + (uint)(i * 0x20), hash); + + hashes.Add(hash); + } + + nrrInfo = new NrrInfo(nrrAddress, header, hashes); + + return ResultCode.Success; + } + + public bool IsNroHashPresent(byte[] nroHash) + { + foreach (NrrInfo info in _nrrInfos) + { + foreach (byte[] hash in info.Hashes) + { + if (hash.SequenceEqual(nroHash)) + { + return true; + } + } + } + + return false; + } + + public bool IsNroLoaded(byte[] nroHash) + { + foreach (NroInfo info in _nroInfos) + { + if (info.Hash.SequenceEqual(nroHash)) + { + return true; + } + } + + return false; + } + + public ResultCode ParseNro(out NroInfo res, ServiceCtx context, ulong nroAddress, ulong nroSize, ulong bssAddress, ulong bssSize) + { + res = null; + + if (_nroInfos.Count >= MaxNro) + { + return ResultCode.TooManyNro; + } + else if (nroSize == 0 || nroAddress + nroSize <= nroAddress || (nroSize & 0xFFF) != 0) + { + return ResultCode.InvalidSize; + } + else if (bssSize != 0 && bssAddress + bssSize <= bssAddress) + { + return ResultCode.InvalidSize; + } + else if ((nroAddress & 0xFFF) != 0) + { + return ResultCode.InvalidAddress; + } + + uint magic = _owner.CpuMemory.Read(nroAddress + 0x10); + uint nroFileSize = _owner.CpuMemory.Read(nroAddress + 0x18); + + if (magic != NroMagic || nroSize != nroFileSize) + { + return ResultCode.InvalidNro; + } + + byte[] nroData = new byte[nroSize]; + + _owner.CpuMemory.Read(nroAddress, nroData); + + MemoryStream stream = new(nroData); + + byte[] nroHash = SHA256.HashData(stream); + + if (!IsNroHashPresent(nroHash)) + { + return ResultCode.NotRegistered; + } + + if (IsNroLoaded(nroHash)) + { + return ResultCode.AlreadyLoaded; + } + + stream.Position = 0; + + NroExecutable nro = new(stream.AsStorage(), nroAddress, bssAddress); + + // Check if everything is page align. + if ((nro.Text.Length & 0xFFF) != 0 || (nro.Ro.Length & 0xFFF) != 0 || + (nro.Data.Length & 0xFFF) != 0 || (nro.BssSize & 0xFFF) != 0) + { + return ResultCode.InvalidNro; + } + + // Check if everything is contiguous. + if (nro.RoOffset != nro.TextOffset + nro.Text.Length || + nro.DataOffset != nro.RoOffset + nro.Ro.Length || + nroFileSize != nro.DataOffset + nro.Data.Length) + { + return ResultCode.InvalidNro; + } + + // Check the bss size match. + if ((ulong)nro.BssSize != bssSize) + { + return ResultCode.InvalidNro; + } + + uint totalSize = (uint)nro.Text.Length + (uint)nro.Ro.Length + (uint)nro.Data.Length + nro.BssSize; + + // Apply patches + context.Device.FileSystem.ModLoader.ApplyNroPatches(nro); + + res = new NroInfo( + nro, + nroHash, + nroAddress, + nroSize, + bssAddress, + bssSize, + (ulong)totalSize); + + return ResultCode.Success; + } + + private ResultCode MapNro(KProcess process, NroInfo info, out ulong nroMappedAddress) + { + KPageTableBase memMgr = process.MemoryManager; + + int retryCount = 0; + + nroMappedAddress = 0; + + while (retryCount++ < MaxMapRetries) + { + ResultCode result = MapCodeMemoryInProcess(process, info.NroAddress, info.NroSize, out nroMappedAddress); + + if (result != ResultCode.Success) + { + return result; + } + + if (info.BssSize > 0) + { + Result bssMappingResult = memMgr.MapProcessCodeMemory(nroMappedAddress + info.NroSize, info.BssAddress, info.BssSize); + + if (bssMappingResult == KernelResult.InvalidMemState) + { + memMgr.UnmapProcessCodeMemory(nroMappedAddress + info.NroSize, info.BssAddress, info.BssSize); + memMgr.UnmapProcessCodeMemory(nroMappedAddress, info.NroAddress, info.NroSize); + + continue; + } + else if (bssMappingResult != Result.Success) + { + memMgr.UnmapProcessCodeMemory(nroMappedAddress + info.NroSize, info.BssAddress, info.BssSize); + memMgr.UnmapProcessCodeMemory(nroMappedAddress, info.NroAddress, info.NroSize); + + return (ResultCode)bssMappingResult.ErrorCode; + } + } + + if (CanAddGuardRegionsInProcess(process, nroMappedAddress, info.TotalSize)) + { + return ResultCode.Success; + } + } + + return ResultCode.InsufficientAddressSpace; + } + + private bool CanAddGuardRegionsInProcess(KProcess process, ulong baseAddress, ulong size) + { + KPageTableBase memMgr = process.MemoryManager; + + KMemoryInfo memInfo = memMgr.QueryMemory(baseAddress - 1); + + if (memInfo.State == MemoryState.Unmapped && baseAddress - GuardPagesSize >= memInfo.Address) + { + memInfo = memMgr.QueryMemory(baseAddress + size); + + if (memInfo.State == MemoryState.Unmapped) + { + return baseAddress + size + GuardPagesSize <= memInfo.Address + memInfo.Size; + } + } + return false; + } + + private ResultCode MapCodeMemoryInProcess(KProcess process, ulong baseAddress, ulong size, out ulong targetAddress) + { + KPageTableBase memMgr = process.MemoryManager; + + targetAddress = 0; + + int retryCount; + + ulong addressSpacePageLimit = (memMgr.GetAddrSpaceSize() - size) >> 12; + + for (retryCount = 0; retryCount < MaxMapRetries; retryCount++) + { + while (true) + { + ulong randomOffset = (ulong)(uint)Random.Shared.Next(0, (int)addressSpacePageLimit) << 12; + + targetAddress = memMgr.GetAddrSpaceBaseAddr() + randomOffset; + + if (memMgr.InsideAddrSpace(targetAddress, size) && !memMgr.InsideHeapRegion(targetAddress, size) && !memMgr.InsideAliasRegion(targetAddress, size)) + { + break; + } + } + + Result result = memMgr.MapProcessCodeMemory(targetAddress, baseAddress, size); + + if (result == KernelResult.InvalidMemState) + { + continue; + } + else if (result != Result.Success) + { + return (ResultCode)result.ErrorCode; + } + + if (!CanAddGuardRegionsInProcess(process, targetAddress, size)) + { + continue; + } + + return ResultCode.Success; + } + + if (retryCount == MaxMapRetries) + { + return ResultCode.InsufficientAddressSpace; + } + + return ResultCode.Success; + } + + private Result SetNroMemoryPermissions(KProcess process, IExecutable relocatableObject, ulong baseAddress) + { + ulong textStart = baseAddress + relocatableObject.TextOffset; + ulong roStart = baseAddress + relocatableObject.RoOffset; + ulong dataStart = baseAddress + relocatableObject.DataOffset; + + ulong bssStart = dataStart + (ulong)relocatableObject.Data.Length; + + ulong bssEnd = BitUtils.AlignUp(bssStart + relocatableObject.BssSize, KPageTableBase.PageSize); + + process.CpuMemory.Write(textStart, relocatableObject.Text); + process.CpuMemory.Write(roStart, relocatableObject.Ro); + process.CpuMemory.Write(dataStart, relocatableObject.Data); + + MemoryHelper.FillWithZeros(process.CpuMemory, bssStart, (int)(bssEnd - bssStart)); + + Result result; + + result = process.MemoryManager.SetProcessMemoryPermission(textStart, roStart - textStart, KMemoryPermission.ReadAndExecute); + + if (result != Result.Success) + { + return result; + } + + result = process.MemoryManager.SetProcessMemoryPermission(roStart, dataStart - roStart, KMemoryPermission.Read); + + if (result != Result.Success) + { + return result; + } + + return process.MemoryManager.SetProcessMemoryPermission(dataStart, bssEnd - dataStart, KMemoryPermission.ReadAndWrite); + } + + private ResultCode RemoveNrrInfo(ulong nrrAddress) + { + foreach (NrrInfo info in _nrrInfos) + { + if (info.NrrAddress == nrrAddress) + { + _nrrInfos.Remove(info); + + return ResultCode.Success; + } + } + + return ResultCode.NotLoaded; + } + + private ResultCode RemoveNroInfo(ulong nroMappedAddress) + { + foreach (NroInfo info in _nroInfos) + { + if (info.NroMappedAddress == nroMappedAddress) + { + _nroInfos.Remove(info); + + return UnmapNroFromInfo(info); + } + } + + return ResultCode.NotLoaded; + } + + private ResultCode UnmapNroFromInfo(NroInfo info) + { + ulong textSize = (ulong)info.Executable.Text.Length; + ulong roSize = (ulong)info.Executable.Ro.Length; + ulong dataSize = (ulong)info.Executable.Data.Length; + ulong bssSize = (ulong)info.Executable.BssSize; + + Result result = Result.Success; + + if (info.Executable.BssSize != 0) + { + result = _owner.MemoryManager.UnmapProcessCodeMemory( + info.NroMappedAddress + textSize + roSize + dataSize, + info.Executable.BssAddress, + bssSize); + } + + if (result == Result.Success) + { + result = _owner.MemoryManager.UnmapProcessCodeMemory( + info.NroMappedAddress + textSize + roSize, + info.Executable.SourceAddress + textSize + roSize, + dataSize); + + if (result == Result.Success) + { + result = _owner.MemoryManager.UnmapProcessCodeMemory( + info.NroMappedAddress, + info.Executable.SourceAddress, + textSize + roSize); + } + } + + return (ResultCode)result.ErrorCode; + } + + private ResultCode IsInitialized(ulong pid) + { + if (_owner != null && _owner.Pid == pid) + { + return ResultCode.Success; + } + + return ResultCode.InvalidProcess; + } + + [CommandCmif(0)] + // LoadNro(u64, u64, u64, u64, u64, pid) -> u64 + public ResultCode LoadNro(ServiceCtx context) + { + ResultCode result = IsInitialized(_owner.Pid); + + // Zero + context.RequestData.ReadUInt64(); + + ulong nroHeapAddress = context.RequestData.ReadUInt64(); + ulong nroSize = context.RequestData.ReadUInt64(); + ulong bssHeapAddress = context.RequestData.ReadUInt64(); + ulong bssSize = context.RequestData.ReadUInt64(); + + ulong nroMappedAddress = 0; + + if (result == ResultCode.Success) + { + + result = ParseNro(out NroInfo info, context, nroHeapAddress, nroSize, bssHeapAddress, bssSize); + + if (result == ResultCode.Success) + { + result = MapNro(_owner, info, out nroMappedAddress); + + if (result == ResultCode.Success) + { + result = (ResultCode)SetNroMemoryPermissions(_owner, info.Executable, nroMappedAddress).ErrorCode; + + if (result == ResultCode.Success) + { + info.NroMappedAddress = nroMappedAddress; + + _nroInfos.Add(info); + } + } + } + } + + context.ResponseData.Write(nroMappedAddress); + + return result; + } + + [CommandCmif(1)] + // UnloadNro(u64, u64, pid) + public ResultCode UnloadNro(ServiceCtx context) + { + ResultCode result = IsInitialized(_owner.Pid); + + // Zero + context.RequestData.ReadUInt64(); + + ulong nroMappedAddress = context.RequestData.ReadUInt64(); + + if (result == ResultCode.Success) + { + if ((nroMappedAddress & 0xFFF) != 0) + { + return ResultCode.InvalidAddress; + } + + result = RemoveNroInfo(nroMappedAddress); + } + + return result; + } + + [CommandCmif(2)] + // LoadNrr(u64, u64, u64, pid) + public ResultCode LoadNrr(ServiceCtx context) + { + ResultCode result = IsInitialized(_owner.Pid); + + // pid placeholder, zero + context.RequestData.ReadUInt64(); + + ulong nrrAddress = context.RequestData.ReadUInt64(); + ulong nrrSize = context.RequestData.ReadUInt64(); + + if (result == ResultCode.Success) + { + result = ParseNrr(out NrrInfo info, context, nrrAddress, nrrSize); + + if (result == ResultCode.Success) + { + if (_nrrInfos.Count >= MaxNrr) + { + result = ResultCode.NotLoaded; + } + else + { + _nrrInfos.Add(info); + } + } + } + + return result; + } + + [CommandCmif(3)] + // UnloadNrr(u64, u64, pid) + public ResultCode UnloadNrr(ServiceCtx context) + { + ResultCode result = IsInitialized(_owner.Pid); + + // pid placeholder, zero + context.RequestData.ReadUInt64(); + + ulong nrrHeapAddress = context.RequestData.ReadUInt64(); + + if (result == ResultCode.Success) + { + if ((nrrHeapAddress & 0xFFF) != 0) + { + return ResultCode.InvalidAddress; + } + + result = RemoveNrrInfo(nrrHeapAddress); + } + + return result; + } + + [CommandCmif(4)] + // Initialize(u64, pid, KObject) + public ResultCode Initialize(ServiceCtx context) + { + if (_owner != null) + { + return ResultCode.InvalidSession; + } + + int processHandle = context.Request.HandleDesc.ToCopy[0]; + _owner = context.Process.HandleTable.GetKProcess(processHandle); + _ownerMm = _owner?.CpuMemory; + context.Device.System.KernelContext.Syscall.CloseHandle(processHandle); + + if (_ownerMm is IRefCounted rc) + { + rc.IncrementReferenceCount(); + } + + return ResultCode.Success; + } + + [CommandCmif(10)] + // LoadNrr2(u64, u64, u64, pid) + public ResultCode LoadNrr2(ServiceCtx context) + { + context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); + + return LoadNrr(context); + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + foreach (NroInfo info in _nroInfos) + { + UnmapNroFromInfo(info); + } + + _nroInfos.Clear(); + + if (_ownerMm is IRefCounted rc) + { + rc.DecrementReferenceCount(); + } + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ro/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Ro/ResultCode.cs new file mode 100644 index 00000000..91114034 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ro/ResultCode.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.HLE.HOS.Services.Ro +{ + enum ResultCode + { + ModuleId = 22, + ErrorCodeShift = 9, + + Success = 0, + + InsufficientAddressSpace = (2 << ErrorCodeShift) | ModuleId, + AlreadyLoaded = (3 << ErrorCodeShift) | ModuleId, + InvalidNro = (4 << ErrorCodeShift) | ModuleId, + InvalidNrr = (6 << ErrorCodeShift) | ModuleId, + TooManyNro = (7 << ErrorCodeShift) | ModuleId, + TooManyNrr = (8 << ErrorCodeShift) | ModuleId, + NotAuthorized = (9 << ErrorCodeShift) | ModuleId, + + InvalidNrrType = (10 << ErrorCodeShift) | ModuleId, + + InvalidAddress = (1025 << ErrorCodeShift) | ModuleId, + InvalidSize = (1026 << ErrorCodeShift) | ModuleId, + NotLoaded = (1028 << ErrorCodeShift) | ModuleId, + NotRegistered = (1029 << ErrorCodeShift) | ModuleId, + InvalidSession = (1030 << ErrorCodeShift) | ModuleId, + InvalidProcess = (1031 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ro/Types/NRRCertification.cs b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NRRCertification.cs new file mode 100644 index 00000000..8c56adb9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NRRCertification.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ro +{ + [StructLayout(LayoutKind.Sequential, Size = 0x220)] + struct NRRCertification + { + public ulong ApplicationIdMask; + public ulong ApplicationIdPattern; + private Array16 _reserved; + public ByteArray256 Modulus; + public ByteArray256 Signature; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ro/Types/NroInfo.cs b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NroInfo.cs new file mode 100644 index 00000000..4df2793f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NroInfo.cs @@ -0,0 +1,35 @@ +using Ryujinx.HLE.Loaders.Executables; + +namespace Ryujinx.HLE.HOS.Services.Ro +{ + class NroInfo + { + public NroExecutable Executable { get; private set; } + + public byte[] Hash { get; private set; } + public ulong NroAddress { get; private set; } + public ulong NroSize { get; private set; } + public ulong BssAddress { get; private set; } + public ulong BssSize { get; private set; } + public ulong TotalSize { get; private set; } + public ulong NroMappedAddress { get; set; } + + public NroInfo( + NroExecutable executable, + byte[] hash, + ulong nroAddress, + ulong nroSize, + ulong bssAddress, + ulong bssSize, + ulong totalSize) + { + Executable = executable; + Hash = hash; + NroAddress = nroAddress; + NroSize = nroSize; + BssAddress = bssAddress; + BssSize = bssSize; + TotalSize = totalSize; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrHeader.cs b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrHeader.cs new file mode 100644 index 00000000..dbbcb151 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrHeader.cs @@ -0,0 +1,22 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ro +{ + [StructLayout(LayoutKind.Sequential, Size = 0x350)] + struct NrrHeader + { + public uint Magic; + public uint KeyGeneration; // 9.0.0+ + private Array8 _reserved; + public NRRCertification Certification; + public ByteArray256 Signature; + public ulong TitleId; + public uint Size; + public byte Kind; // 7.0.0+ + private Array3 _reserved2; + public uint HashesOffset; + public uint HashesCount; + private Array8 _reserved3; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrInfo.cs b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrInfo.cs new file mode 100644 index 00000000..1f394d92 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrInfo.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Ro +{ + class NrrInfo + { + public NrrHeader Header { get; private set; } + public List Hashes { get; private set; } + public ulong NrrAddress { get; private set; } + + public NrrInfo(ulong nrrAddress, NrrHeader header, List hashes) + { + NrrAddress = nrrAddress; + Header = header; + Hashes = hashes; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Avm/IAvmService.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Avm/IAvmService.cs new file mode 100644 index 00000000..9c6ed5c3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Avm/IAvmService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Tcap +{ + [Service("avm")] // 6.0.0+ + class IAvmService : IpcService + { + public IAvmService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/INotifyService.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/INotifyService.cs new file mode 100644 index 00000000..208847cc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/INotifyService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm +{ + [Service("pdm:ntfy")] + class INotifyService : IpcService + { + public INotifyService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs new file mode 100644 index 00000000..6508794a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs @@ -0,0 +1,24 @@ +using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService; + +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm +{ + [Service("pdm:qry")] + class IQueryService : IpcService + { + public IQueryService(ServiceCtx context) { } + + [CommandCmif(13)] // 5.0.0+ + // QueryApplicationPlayStatisticsForSystem(buffer title_id_list) -> (buffer entries, s32 entries_count) + public ResultCode QueryApplicationPlayStatisticsForSystem(ServiceCtx context) + { + return QueryPlayStatisticsManager.GetPlayStatistics(context); + } + + [CommandCmif(16)] // 6.0.0+ + // QueryApplicationPlayStatisticsByUserAccountIdForSystem(nn::account::Uid, buffer title_id_list) -> (buffer entries, s32 entries_count) + public ResultCode QueryApplicationPlayStatisticsByUserAccountIdForSystem(ServiceCtx context) + { + return QueryPlayStatisticsManager.GetPlayStatistics(context, true); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs new file mode 100644 index 00000000..56d389cd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs @@ -0,0 +1,84 @@ +using Ryujinx.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService +{ + class QueryPlayStatisticsManager + { + private static readonly Dictionary _applicationPlayStatistics = new(); + + internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false) + { + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + ulong outputPosition = context.Request.ReceiveBuff[0].Position; + ulong outputSize = context.Request.ReceiveBuff[0].Size; + + UserId userId = byUserId ? context.RequestData.ReadStruct() : new UserId(); + + if (byUserId) + { + if (!context.Device.System.AccountManager.TryGetUser(userId, out _)) + { + return ResultCode.UserNotFound; + } + } + + PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryCapability; + + List titleIds = new(); + + for (ulong i = 0; i < inputSize / sizeof(ulong); i++) + { + titleIds.Add(context.Memory.Read(inputPosition)); + } + + if (queryCapability == PlayLogQueryCapability.WhiteList) + { + // Check if input title ids are in the whitelist. + foreach (ulong titleId in titleIds) + { + if (!context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId)) + { + return (ResultCode)Am.ResultCode.ObjectInvalid; + } + } + } + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + // Return ResultCode.ServiceUnavailable if data is locked by another process. + var filteredApplicationPlayStatistics = _applicationPlayStatistics.AsEnumerable(); + + if (queryCapability == PlayLogQueryCapability.None) + { + filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => kv.Value.TitleId == context.Process.TitleId); + } + else // PlayLogQueryCapability.All + { + filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => titleIds.Contains(kv.Value.TitleId)); + } + + if (byUserId) + { + filteredApplicationPlayStatistics = filteredApplicationPlayStatistics.Where(kv => kv.Key == userId); + } + + for (int i = 0; i < filteredApplicationPlayStatistics.Count(); i++) + { + MemoryHelper.Write(context.Memory, outputPosition + (ulong)(i * Unsafe.SizeOf()), filteredApplicationPlayStatistics.ElementAt(i).Value); + } + + context.ResponseData.Write(filteredApplicationPlayStatistics.Count()); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs new file mode 100644 index 00000000..8bfb75c7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18)] + struct ApplicationPlayStatistics + { + public ulong TitleId; + public long TotalPlayTime; // In nanoseconds. + public long TotalLaunchCount; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs new file mode 100644 index 00000000..a4fd3ca9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types +{ + enum PlayLogQueryCapability + { + None, + WhiteList, + All, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs new file mode 100644 index 00000000..85167227 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm +{ + enum ResultCode + { + ModuleId = 178, + ErrorCodeShift = 9, + + Success = 0, + + InvalidUserID = (100 << ErrorCodeShift) | ModuleId, + UserNotFound = (101 << ErrorCodeShift) | ModuleId, + ServiceUnavailable = (150 << ErrorCodeShift) | ModuleId, + FileStorageFailure = (200 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs new file mode 100644 index 00000000..45c4ce7e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs @@ -0,0 +1,146 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Sdb.Pl.Types; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Sdb.Pl +{ + [Service("pl:u")] + [Service("pl:s")] // 9.0.0+ + class ISharedFontManager : IpcService + { + private int _fontSharedMemHandle; + + public ISharedFontManager(ServiceCtx context) { } + + [CommandCmif(0)] + // RequestLoad(u32) + public ResultCode RequestLoad(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + SharedFontType fontType = (SharedFontType)context.RequestData.ReadInt32(); +#pragma warning restore IDE0059 + + // We don't need to do anything here because we do lazy initialization + // on SharedFontManager (the font is loaded when necessary). + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetLoadState(u32) -> u32 + public ResultCode GetLoadState(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + SharedFontType fontType = (SharedFontType)context.RequestData.ReadInt32(); +#pragma warning restore IDE0059 + + // 1 (true) indicates that the font is already loaded. + // All fonts are already loaded. + context.ResponseData.Write(1); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetFontSize(u32) -> u32 + public ResultCode GetFontSize(ServiceCtx context) + { + SharedFontType fontType = (SharedFontType)context.RequestData.ReadInt32(); + + context.ResponseData.Write(context.Device.System.SharedFontManager.GetFontSize(fontType)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetSharedMemoryAddressOffset(u32) -> u32 + public ResultCode GetSharedMemoryAddressOffset(ServiceCtx context) + { + SharedFontType fontType = (SharedFontType)context.RequestData.ReadInt32(); + + context.ResponseData.Write(context.Device.System.SharedFontManager.GetSharedMemoryAddressOffset(fontType)); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetSharedMemoryNativeHandle() -> handle + public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context) + { + context.Device.System.SharedFontManager.EnsureInitialized(context.Device.System.ContentManager); + + if (_fontSharedMemHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(context.Device.System.FontSharedMem, out _fontSharedMemHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_fontSharedMemHandle); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // GetSharedFontInOrderOfPriority(bytes<8, 1>) -> (u8, u32, buffer, buffer, buffer) + public ResultCode GetSharedFontInOrderOfPriority(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long languageCode = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + int loadedCount = 0; + + for (SharedFontType type = 0; type < SharedFontType.Count; type++) + { + uint offset = (uint)type * 4; + + if (!AddFontToOrderOfPriorityList(context, type, offset)) + { + break; + } + + loadedCount++; + } + + context.ResponseData.Write(loadedCount); + context.ResponseData.Write((int)SharedFontType.Count); + + return ResultCode.Success; + } + + [CommandCmif(6)] // 4.0.0+ + // GetSharedFontInOrderOfPriorityForSystem(bytes<8, 1>) -> (u8, u32, buffer, buffer, buffer) + public ResultCode GetSharedFontInOrderOfPriorityForSystem(ServiceCtx context) + { + // TODO: Check the differencies with GetSharedFontInOrderOfPriority. + + return GetSharedFontInOrderOfPriority(context); + } + + private bool AddFontToOrderOfPriorityList(ServiceCtx context, SharedFontType fontType, uint offset) + { + ulong typesPosition = context.Request.ReceiveBuff[0].Position; + ulong typesSize = context.Request.ReceiveBuff[0].Size; + + ulong offsetsPosition = context.Request.ReceiveBuff[1].Position; + ulong offsetsSize = context.Request.ReceiveBuff[1].Size; + + ulong fontSizeBufferPosition = context.Request.ReceiveBuff[2].Position; + ulong fontSizeBufferSize = context.Request.ReceiveBuff[2].Size; + + if (offset + 4 > (uint)typesSize || + offset + 4 > (uint)offsetsSize || + offset + 4 > (uint)fontSizeBufferSize) + { + return false; + } + + context.Memory.Write(typesPosition + offset, (int)fontType); + context.Memory.Write(offsetsPosition + offset, context.Device.System.SharedFontManager.GetSharedMemoryAddressOffset(fontType)); + context.Memory.Write(fontSizeBufferPosition + offset, context.Device.System.SharedFontManager.GetFontSize(fontType)); + + return true; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs new file mode 100644 index 00000000..64179589 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs @@ -0,0 +1,182 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Sdb.Pl.Types; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Sdb.Pl +{ + class SharedFontManager + { + private const uint FontKey = 0x06186249; + private const uint BFTTFMagic = 0x18029a7f; + + private readonly Switch _device; + private readonly SharedMemoryStorage _storage; + + private struct FontInfo + { + public int Offset; + public int Size; + + public FontInfo(int offset, int size) + { + Offset = offset; + Size = size; + } + } + + private Dictionary _fontData; + + public SharedFontManager(Switch device, SharedMemoryStorage storage) + { + _device = device; + _storage = storage; + } + + public void Initialize() + { + _fontData?.Clear(); + _fontData = null; + + } + + public void EnsureInitialized(ContentManager contentManager) + { + if (_fontData == null) + { + _storage.ZeroFill(); + + uint fontOffset = 0; + + FontInfo CreateFont(string name) + { + if (contentManager.TryGetFontTitle(name, out ulong fontTitle) && contentManager.TryGetFontFilename(name, out string fontFilename)) + { + string contentPath = contentManager.GetInstalledContentPath(fontTitle, StorageId.BuiltInSystem, NcaContentType.Data); + string fontPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath); + + if (!string.IsNullOrWhiteSpace(fontPath)) + { + byte[] data; + + using (IStorage ncaFileStream = new LocalStorage(fontPath, FileAccess.Read, FileMode.Open)) + { + Nca nca = new(_device.System.KeySet, ncaFileStream); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); + + using var fontFile = new UniqueRef(); + + romfs.OpenFile(ref fontFile.Ref, ("/" + fontFilename).ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + data = DecryptFont(fontFile.Get.AsStream()); + } + + FontInfo info = new((int)fontOffset, data.Length); + + WriteMagicAndSize(fontOffset, data.Length); + + fontOffset += 8; + + uint start = fontOffset; + + for (; fontOffset - start < data.Length; fontOffset++) + { + _storage.GetRef(fontOffset) = data[fontOffset - start]; + } + + return info; + } + else + { + if (!contentManager.TryGetSystemTitlesName(fontTitle, out string titleName)) + { + titleName = "Unknown"; + } + + throw new InvalidSystemResourceException($"{titleName} ({fontTitle:x8}) system title not found! This font will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)"); + } + } + else + { + throw new ArgumentException($"Unknown font \"{name}\"!"); + } + } + + _fontData = new Dictionary + { + { SharedFontType.JapanUsEurope, CreateFont("FontStandard") }, + { SharedFontType.SimplifiedChinese, CreateFont("FontChineseSimplified") }, + { SharedFontType.SimplifiedChineseEx, CreateFont("FontExtendedChineseSimplified") }, + { SharedFontType.TraditionalChinese, CreateFont("FontChineseTraditional") }, + { SharedFontType.Korean, CreateFont("FontKorean") }, + { SharedFontType.NintendoEx, CreateFont("FontNintendoExtended") }, + }; + + if (fontOffset > Horizon.FontSize) + { + throw new InvalidSystemResourceException("The sum of all fonts size exceed the shared memory size. " + + $"Please make sure that the fonts don't exceed {Horizon.FontSize} bytes in total. (actual size: {fontOffset} bytes)."); + } + } + } + + private void WriteMagicAndSize(ulong offset, int size) + { + const int Key = 0x49621806; + + int encryptedSize = BinaryPrimitives.ReverseEndianness(size ^ Key); + + _storage.GetRef(offset + 0) = (int)BFTTFMagic; + _storage.GetRef(offset + 4) = encryptedSize; + } + + public int GetFontSize(SharedFontType fontType) + { + EnsureInitialized(_device.System.ContentManager); + + return _fontData[fontType].Size; + } + + public int GetSharedMemoryAddressOffset(SharedFontType fontType) + { + EnsureInitialized(_device.System.ContentManager); + + return _fontData[fontType].Offset + 8; + } + + private byte[] DecryptFont(Stream bfttfStream) + { + static uint KXor(uint data) => data ^ FontKey; + + using BinaryReader reader = new(bfttfStream); + using MemoryStream ttfStream = MemoryStreamManager.Shared.GetStream(); + using BinaryWriter output = new(ttfStream); + + if (KXor(reader.ReadUInt32()) != BFTTFMagic) + { + throw new InvalidDataException("Error: Input file is not in BFTTF format!"); + } + + bfttfStream.Position += 4; + + for (int i = 0; i < (bfttfStream.Length - 8) / 4; i++) + { + output.Write(KXor(reader.ReadUInt32())); + } + + return ttfStream.ToArray(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/Types/SharedFontType.cs b/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/Types/SharedFontType.cs new file mode 100644 index 00000000..273fcafb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sdb/Pl/Types/SharedFontType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Sdb.Pl.Types +{ + public enum SharedFontType + { + JapanUsEurope = 0, + SimplifiedChinese = 1, + SimplifiedChineseEx = 2, + TraditionalChinese = 3, + Korean = 4, + NintendoEx = 5, + Count, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs new file mode 100644 index 00000000..f67699b9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs @@ -0,0 +1,555 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon; +using Ryujinx.Horizon.Common; +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services +{ + class ServerBase : IDisposable + { + // Must be the maximum value used by services (highest one know is the one used by nvservices = 0x8000). + // Having a size that is too low will cause failures as data copy will fail if the receiving buffer is + // not large enough. + private const int PointerBufferSize = 0x8000; + + private readonly static uint[] _defaultCapabilities = { + 0x030363F7, + 0x1FFFFFCF, + 0x207FFFEF, + 0x47E0060F, + 0x0048BFFF, + 0x01007FFF, + }; + + // The amount of time Dispose() will wait to Join() the thread executing the ServerLoop() + private static readonly TimeSpan _threadJoinTimeout = TimeSpan.FromSeconds(3); + + private readonly KernelContext _context; + private KProcess _selfProcess; + private KThread _selfThread; + private KEvent _wakeEvent; + private int _wakeHandle = 0; + + private readonly ReaderWriterLockSlim _handleLock = new(); + private readonly Dictionary _sessions = new(); + private readonly Dictionary> _ports = new(); + + private readonly MemoryStream _requestDataStream; + private readonly BinaryReader _requestDataReader; + + private readonly MemoryStream _responseDataStream; + private readonly BinaryWriter _responseDataWriter; + + private int _isDisposed = 0; + + public ManualResetEvent InitDone { get; } + public string Name { get; } + public Func SmObjectFactory { get; } + + public ServerBase(KernelContext context, string name, Func smObjectFactory = null) + { + _context = context; + + _requestDataStream = MemoryStreamManager.Shared.GetStream(); + _requestDataReader = new BinaryReader(_requestDataStream); + + _responseDataStream = MemoryStreamManager.Shared.GetStream(); + _responseDataWriter = new BinaryWriter(_responseDataStream); + + InitDone = new ManualResetEvent(false); + Name = name; + SmObjectFactory = smObjectFactory; + + const ProcessCreationFlags Flags = + ProcessCreationFlags.EnableAslr | + ProcessCreationFlags.AddressSpace64Bit | + ProcessCreationFlags.Is64Bit | + ProcessCreationFlags.PoolPartitionSystem; + + ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0); + + KernelStatic.StartInitialProcess(context, creationInfo, _defaultCapabilities, 44, Main); + } + + private void AddPort(int serverPortHandle, Func objectFactory) + { + bool lockTaken = false; + try + { + lockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite); + + _ports.Add(serverPortHandle, objectFactory); + } + finally + { + if (lockTaken) + { + _handleLock.ExitWriteLock(); + } + } + } + + public void AddSessionObj(KServerSession serverSession, IpcService obj) + { + // Ensure that the sever loop is running. + InitDone.WaitOne(); + + _selfProcess.HandleTable.GenerateHandle(serverSession, out int serverSessionHandle); + + AddSessionObj(serverSessionHandle, obj); + } + + public void AddSessionObj(int serverSessionHandle, IpcService obj) + { + bool lockTaken = false; + try + { + lockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite); + + _sessions.Add(serverSessionHandle, obj); + } + finally + { + if (lockTaken) + { + _handleLock.ExitWriteLock(); + } + } + + _wakeEvent.WritableEvent.Signal(); + } + + private IpcService GetSessionObj(int serverSessionHandle) + { + bool lockTaken = false; + try + { + lockTaken = _handleLock.TryEnterReadLock(Timeout.Infinite); + + return _sessions[serverSessionHandle]; + } + finally + { + if (lockTaken) + { + _handleLock.ExitReadLock(); + } + } + } + + private bool RemoveSessionObj(int serverSessionHandle, out IpcService obj) + { + bool lockTaken = false; + try + { + lockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite); + + return _sessions.Remove(serverSessionHandle, out obj); + } + finally + { + if (lockTaken) + { + _handleLock.ExitWriteLock(); + } + } + } + + private void Main() + { + ServerLoop(); + } + + private void ServerLoop() + { + _selfProcess = KernelStatic.GetCurrentProcess(); + _selfThread = KernelStatic.GetCurrentThread(); + + HorizonStatic.Register( + default, + _context.Syscall, + _selfProcess.CpuMemory, + _selfThread.ThreadContext, + (int)_selfThread.ThreadContext.GetX(1)); + + if (SmObjectFactory != null) + { + _context.Syscall.ManageNamedPort(out int serverPortHandle, "sm:", 50); + + AddPort(serverPortHandle, SmObjectFactory); + } + + _wakeEvent = new KEvent(_context); + Result result = _selfProcess.HandleTable.GenerateHandle(_wakeEvent.ReadableEvent, out _wakeHandle); + + InitDone.Set(); + + ulong messagePtr = _selfThread.TlsAddress; + _context.Syscall.SetHeapSize(out ulong heapAddr, 0x200000); + + _selfProcess.CpuMemory.Write(messagePtr + 0x0, 0); + _selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10); + _selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48)); + int replyTargetHandle = 0; + + while (true) + { + int portHandleCount; + int handleCount; + int[] handles; + + bool handleLockTaken = false; + try + { + handleLockTaken = _handleLock.TryEnterReadLock(Timeout.Infinite); + + portHandleCount = _ports.Count; + + handleCount = portHandleCount + _sessions.Count + 1; + + handles = ArrayPool.Shared.Rent(handleCount); + + handles[0] = _wakeHandle; + + _ports.Keys.CopyTo(handles, 1); + + _sessions.Keys.CopyTo(handles, portHandleCount + 1); + } + finally + { + if (handleLockTaken) + { + _handleLock.ExitReadLock(); + } + } + + var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, -1); + + _selfThread.HandlePostSyscall(); + + if (!_selfThread.Context.Running) + { + break; + } + + replyTargetHandle = 0; + + if (rc == Result.Success && signaledIndex >= portHandleCount + 1) + { + // We got a IPC request, process it, pass to the appropriate service if needed. + int signaledHandle = handles[signaledIndex]; + + if (Process(signaledHandle, heapAddr)) + { + replyTargetHandle = signaledHandle; + } + } + else + { + if (rc == Result.Success) + { + if (signaledIndex > 0) + { + // We got a new connection, accept the session to allow servicing future requests. + if (_context.Syscall.AcceptSession(out int serverSessionHandle, handles[signaledIndex]) == Result.Success) + { + bool handleWriteLockTaken = false; + try + { + handleWriteLockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite); + IpcService obj = _ports[handles[signaledIndex]].Invoke(); + _sessions.Add(serverSessionHandle, obj); + } + finally + { + if (handleWriteLockTaken) + { + _handleLock.ExitWriteLock(); + } + } + } + } + else + { + // The _wakeEvent signalled, which means we have a new session. + _wakeEvent.WritableEvent.Clear(); + } + } + else if (rc == KernelResult.PortRemoteClosed && signaledIndex >= 0 && SmObjectFactory != null) + { + DestroySession(handles[signaledIndex]); + } + + _selfProcess.CpuMemory.Write(messagePtr + 0x0, 0); + _selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10); + _selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48)); + } + + ArrayPool.Shared.Return(handles); + } + + Dispose(); + } + + private void DestroySession(int serverSessionHandle) + { + _context.Syscall.CloseHandle(serverSessionHandle); + + if (RemoveSessionObj(serverSessionHandle, out var session)) + { + (session as IDisposable)?.Dispose(); + } + } + + private bool Process(int serverSessionHandle, ulong recvListAddr) + { + IpcMessage request = ReadRequest(); + + IpcMessage response = new(); + + ulong tempAddr = recvListAddr; + int sizesOffset = request.RawData.Length - ((request.RecvListBuff.Count * 2 + 3) & ~3); + + bool noReceive = true; + + for (int i = 0; i < request.ReceiveBuff.Count; i++) + { + noReceive &= (request.ReceiveBuff[i].Position == 0); + } + + if (noReceive) + { + response.PtrBuff.EnsureCapacity(request.RecvListBuff.Count); + + for (int i = 0; i < request.RecvListBuff.Count; i++) + { + ulong size = (ulong)BinaryPrimitives.ReadInt16LittleEndian(request.RawData.AsSpan(sizesOffset + i * 2, 2)); + + response.PtrBuff.Add(new IpcPtrBuffDesc(tempAddr, (uint)i, size)); + + request.RecvListBuff[i] = new IpcRecvListBuffDesc(tempAddr, size); + + tempAddr += size; + } + } + + bool shouldReply = true; + bool isTipcCommunication = false; + + _requestDataStream.SetLength(0); + _requestDataStream.Write(request.RawData); + _requestDataStream.Position = 0; + + if (request.Type == IpcMessageType.CmifRequest || + request.Type == IpcMessageType.CmifRequestWithContext) + { + response.Type = IpcMessageType.CmifResponse; + + _responseDataStream.SetLength(0); + + ServiceCtx context = new( + _context.Device, + _selfProcess, + _selfProcess.CpuMemory, + _selfThread, + request, + response, + _requestDataReader, + _responseDataWriter); + + GetSessionObj(serverSessionHandle).CallCmifMethod(context); + + response.RawData = _responseDataStream.ToArray(); + } + else if (request.Type == IpcMessageType.CmifControl || + request.Type == IpcMessageType.CmifControlWithContext) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + uint magic = (uint)_requestDataReader.ReadUInt64(); +#pragma warning restore IDE0059 + uint cmdId = (uint)_requestDataReader.ReadUInt64(); + + switch (cmdId) + { + case 0: + FillHipcResponse(response, 0, GetSessionObj(serverSessionHandle).ConvertToDomain()); + break; + + case 3: + FillHipcResponse(response, 0, PointerBufferSize); + break; + + // TODO: Whats the difference between IpcDuplicateSession/Ex? + case 2: + case 4: + { + _ = _requestDataReader.ReadInt32(); + + _context.Syscall.CreateSession(out int dupServerSessionHandle, out int dupClientSessionHandle, false, 0); + + bool writeLockTaken = false; + try + { + writeLockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite); + _sessions[dupServerSessionHandle] = _sessions[serverSessionHandle]; + } + finally + { + if (writeLockTaken) + { + _handleLock.ExitWriteLock(); + } + } + + response.HandleDesc = IpcHandleDesc.MakeMove(dupClientSessionHandle); + + FillHipcResponse(response, 0); + + break; + } + + default: + throw new NotImplementedException(cmdId.ToString()); + } + } + else if (request.Type == IpcMessageType.CmifCloseSession || request.Type == IpcMessageType.TipcCloseSession) + { + DestroySession(serverSessionHandle); + shouldReply = false; + } + // If the type is past 0xF, we are using TIPC + else if (request.Type > IpcMessageType.TipcCloseSession) + { + isTipcCommunication = true; + + // Response type is always the same as request on TIPC. + response.Type = request.Type; + + _responseDataStream.SetLength(0); + + ServiceCtx context = new( + _context.Device, + _selfProcess, + _selfProcess.CpuMemory, + _selfThread, + request, + response, + _requestDataReader, + _responseDataWriter); + + GetSessionObj(serverSessionHandle).CallTipcMethod(context); + + response.RawData = _responseDataStream.ToArray(); + + using var responseStream = response.GetStreamTipc(); + _selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence()); + } + else + { + throw new NotImplementedException(request.Type.ToString()); + } + + if (!isTipcCommunication) + { + using var responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48)); + _selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence()); + } + + return shouldReply; + } + + private IpcMessage ReadRequest() + { + const int MessageSize = 0x100; + + using SpanOwner reqDataOwner = SpanOwner.Rent(MessageSize); + + Span reqDataSpan = reqDataOwner.Span; + + _selfProcess.CpuMemory.Read(_selfThread.TlsAddress, reqDataSpan); + + IpcMessage request = new(reqDataSpan, (long)_selfThread.TlsAddress); + + return request; + } + + private void FillHipcResponse(IpcMessage response, long result) + { + FillHipcResponse(response, result, ReadOnlySpan.Empty); + } + + private void FillHipcResponse(IpcMessage response, long result, int value) + { + Span span = stackalloc byte[sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(span, value); + FillHipcResponse(response, result, span); + } + + private void FillHipcResponse(IpcMessage response, long result, ReadOnlySpan data) + { + response.Type = IpcMessageType.CmifResponse; + + _responseDataStream.SetLength(0); + + _responseDataStream.Write(IpcMagic.Sfco); + _responseDataStream.Write(result); + + _responseDataStream.Write(data); + + response.RawData = _responseDataStream.ToArray(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && _selfThread != null) + { + if (_selfThread.HostThread.ManagedThreadId != Environment.CurrentManagedThreadId && _selfThread.HostThread.Join(_threadJoinTimeout) == false) + { + Logger.Warning?.Print(LogClass.Service, $"The ServerBase thread didn't terminate within {_threadJoinTimeout:g}, waiting longer."); + + _selfThread.HostThread.Join(Timeout.Infinite); + } + + if (Interlocked.Exchange(ref _isDisposed, 1) == 0) + { + _selfProcess.HandleTable.CloseHandle(_wakeHandle); + + foreach (IpcService service in _sessions.Values) + { + (service as IDisposable)?.Dispose(); + + service.DestroyAtExit(); + } + + _sessions.Clear(); + _ports.Clear(); + _handleLock.Dispose(); + + _requestDataReader.Dispose(); + _requestDataStream.Dispose(); + _responseDataWriter.Dispose(); + _responseDataStream.Dispose(); + + InitDone.Dispose(); + } + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/ServiceAttributes.cs b/src/Ryujinx.HLE/HOS/Services/ServiceAttributes.cs new file mode 100644 index 00000000..112563eb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/ServiceAttributes.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + class ServiceAttribute : Attribute + { + public readonly string Name; + public readonly object Parameter; + + public ServiceAttribute(string name, object parameter = null) + { + Name = name; + Parameter = parameter; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/IFactorySettingsServer.cs b/src/Ryujinx.HLE/HOS/Services/Settings/IFactorySettingsServer.cs new file mode 100644 index 00000000..aceb28c1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/IFactorySettingsServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Tcap +{ + [Service("set:cal")] + class IFactorySettingsServer : IpcService + { + public IFactorySettingsServer(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/IFirmwareDebugSettingsServer.cs b/src/Ryujinx.HLE/HOS/Services/Settings/IFirmwareDebugSettingsServer.cs new file mode 100644 index 00000000..61e366da --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/IFirmwareDebugSettingsServer.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Settings +{ + [Service("set:fd")] + class IFirmwareDebugSettingsServer : IpcService + { + public IFirmwareDebugSettingsServer(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/ISettingsServer.cs b/src/Ryujinx.HLE/HOS/Services/Settings/ISettingsServer.cs new file mode 100644 index 00000000..abb9b6d8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/ISettingsServer.cs @@ -0,0 +1,247 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.SystemState; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Settings +{ + [Service("set")] + class ISettingsServer : IpcService + { + public ISettingsServer(ServiceCtx context) { } + + [CommandCmif(0)] + // GetLanguageCode() -> nn::settings::LanguageCode + public ResultCode GetLanguageCode(ServiceCtx context) + { + context.ResponseData.Write(context.Device.System.State.DesiredLanguageCode); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetAvailableLanguageCodes() -> (u32, buffer) + public ResultCode GetAvailableLanguageCodes(ServiceCtx context) + { + return GetAvailableLanguagesCodesImpl( + context, + context.Request.RecvListBuff[0].Position, + context.Request.RecvListBuff[0].Size, + 0xF); + } + + [CommandCmif(2)] // 4.0.0+ + // MakeLanguageCode(nn::settings::Language language_index) -> nn::settings::LanguageCode + public ResultCode MakeLanguageCode(ServiceCtx context) + { + int languageIndex = context.RequestData.ReadInt32(); + + if ((uint)languageIndex >= (uint)SystemStateMgr.LanguageCodes.Length) + { + return ResultCode.LanguageOutOfRange; + } + + context.ResponseData.Write(SystemStateMgr.GetLanguageCode(languageIndex)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetAvailableLanguageCodeCount() -> u32 + public ResultCode GetAvailableLanguageCodeCount(ServiceCtx context) + { + context.ResponseData.Write(Math.Min(SystemStateMgr.LanguageCodes.Length, 0xF)); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetRegionCode() -> u32 nn::settings::RegionCode + public ResultCode GetRegionCode(ServiceCtx context) + { + // NOTE: Service mount 0x8000000000000050 savedata and read the region code here. + + RegionCode regionCode = (RegionCode)context.Device.System.State.DesiredRegionCode; + + if (regionCode < RegionCode.Min || regionCode > RegionCode.Max) + { + regionCode = RegionCode.USA; + } + + context.ResponseData.Write((uint)regionCode); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // GetAvailableLanguageCodes2() -> (u32, buffer) + public ResultCode GetAvailableLanguageCodes2(ServiceCtx context) + { + return GetAvailableLanguagesCodesImpl( + context, + context.Request.ReceiveBuff[0].Position, + context.Request.ReceiveBuff[0].Size, + SystemStateMgr.LanguageCodes.Length); + } + + [CommandCmif(6)] + // GetAvailableLanguageCodeCount2() -> u32 + public ResultCode GetAvailableLanguageCodeCount2(ServiceCtx context) + { + context.ResponseData.Write(SystemStateMgr.LanguageCodes.Length); + + return ResultCode.Success; + } + + [CommandCmif(7)] // 4.0.0+ + // GetKeyCodeMap() -> buffer + public ResultCode GetKeyCodeMap(ServiceCtx context) + { + return GetKeyCodeMapImpl(context, 1); + } + + [CommandCmif(8)] // 5.0.0+ + // GetQuestFlag() -> bool + public ResultCode GetQuestFlag(ServiceCtx context) + { + context.ResponseData.Write(false); + + Logger.Stub?.PrintStub(LogClass.ServiceSet); + + return ResultCode.Success; + } + + [CommandCmif(9)] // 6.0.0+ + // GetKeyCodeMap2() -> buffer + public ResultCode GetKeyCodeMap2(ServiceCtx context) + { + return GetKeyCodeMapImpl(context, 2); + } + + [CommandCmif(11)] // 10.1.0+ + // GetDeviceNickName() -> buffer + public ResultCode GetDeviceNickName(ServiceCtx context) + { + ulong deviceNickNameBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong deviceNickNameBufferSize = context.Request.ReceiveBuff[0].Size; + + if (deviceNickNameBufferPosition == 0) + { + return ResultCode.NullDeviceNicknameBuffer; + } + + if (deviceNickNameBufferSize != 0x80) + { + Logger.Warning?.Print(LogClass.ServiceSet, "Wrong buffer size"); + } + + context.Memory.Write(deviceNickNameBufferPosition, Encoding.ASCII.GetBytes(context.Device.System.State.DeviceNickName + '\0')); + + return ResultCode.Success; + } + + private ResultCode GetKeyCodeMapImpl(ServiceCtx context, int version) + { + if (context.Request.ReceiveBuff[0].Size != 0x1000) + { + Logger.Warning?.Print(LogClass.ServiceSet, "Wrong buffer size"); + } + + byte[] keyCodeMap; + + switch ((KeyboardLayout)context.Device.System.State.DesiredKeyboardLayout) + { + case KeyboardLayout.EnglishUs: + + long langCode = context.Device.System.State.DesiredLanguageCode; + + if (langCode == 0x736e61482d687a) // Zh-Hans + { + keyCodeMap = KeyCodeMaps.ChineseSimplified; + } + else if (langCode == 0x746e61482d687a) // Zh-Hant + { + keyCodeMap = KeyCodeMaps.ChineseTraditional; + } + else + { + keyCodeMap = KeyCodeMaps.EnglishUk; + } + + break; + case KeyboardLayout.EnglishUsInternational: + keyCodeMap = KeyCodeMaps.EnglishUsInternational; + break; + case KeyboardLayout.EnglishUk: + keyCodeMap = KeyCodeMaps.EnglishUk; + break; + case KeyboardLayout.French: + keyCodeMap = KeyCodeMaps.French; + break; + case KeyboardLayout.FrenchCa: + keyCodeMap = KeyCodeMaps.FrenchCa; + break; + case KeyboardLayout.Spanish: + keyCodeMap = KeyCodeMaps.Spanish; + break; + case KeyboardLayout.SpanishLatin: + keyCodeMap = KeyCodeMaps.SpanishLatin; + break; + case KeyboardLayout.German: + keyCodeMap = KeyCodeMaps.German; + break; + case KeyboardLayout.Italian: + keyCodeMap = KeyCodeMaps.Italian; + break; + case KeyboardLayout.Portuguese: + keyCodeMap = KeyCodeMaps.Portuguese; + break; + case KeyboardLayout.Russian: + keyCodeMap = KeyCodeMaps.Russian; + break; + case KeyboardLayout.Korean: + keyCodeMap = KeyCodeMaps.Korean; + break; + case KeyboardLayout.ChineseSimplified: + keyCodeMap = KeyCodeMaps.ChineseSimplified; + break; + case KeyboardLayout.ChineseTraditional: + keyCodeMap = KeyCodeMaps.ChineseTraditional; + break; + default: // KeyboardLayout.Default + keyCodeMap = KeyCodeMaps.Default; + break; + } + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, keyCodeMap); + + if (version == 1 && context.Device.System.State.DesiredKeyboardLayout == (long)KeyboardLayout.Default) + { + context.Memory.Write(context.Request.ReceiveBuff[0].Position, (byte)0x01); + } + + return ResultCode.Success; + } + + private ResultCode GetAvailableLanguagesCodesImpl(ServiceCtx context, ulong position, ulong size, int maxSize) + { + int count = (int)(size / 8); + + if (count > maxSize) + { + count = maxSize; + } + + for (int index = 0; index < count; index++) + { + context.Memory.Write(position, SystemStateMgr.GetLanguageCode(index)); + + position += 8; + } + + context.ResponseData.Write(count); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs b/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs new file mode 100644 index 00000000..65748be3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs @@ -0,0 +1,345 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.SystemState; +using System; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Settings +{ + [Service("set:sys")] + class ISystemSettingsServer : IpcService + { + public ISystemSettingsServer(ServiceCtx context) { } + + [CommandCmif(3)] + // GetFirmwareVersion() -> buffer + public ResultCode GetFirmwareVersion(ServiceCtx context) + { + return GetFirmwareVersion2(context); + } + + [CommandCmif(4)] + // GetFirmwareVersion2() -> buffer + public ResultCode GetFirmwareVersion2(ServiceCtx context) + { + ulong replyPos = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(0x100L); + + byte[] firmwareData = GetFirmwareData(context.Device); + + if (firmwareData != null) + { + context.Memory.Write(replyPos, firmwareData); + + return ResultCode.Success; + } + + const byte MajorFwVersion = 0x03; + const byte MinorFwVersion = 0x00; + const byte MicroFwVersion = 0x00; + const byte Unknown = 0x00; //Build? + + const int RevisionNumber = 0x0A; + + const string Platform = "NX"; + const string UnknownHex = "7fbde2b0bba4d14107bf836e4643043d9f6c8e47"; + const string Version = "3.0.0"; + const string Build = "NintendoSDK Firmware for NX 3.0.0-10.0"; + + // http://switchbrew.org/index.php?title=System_Version_Title + using MemoryStream ms = new(0x100); + + BinaryWriter writer = new(ms); + + writer.Write(MajorFwVersion); + writer.Write(MinorFwVersion); + writer.Write(MicroFwVersion); + writer.Write(Unknown); + + writer.Write(RevisionNumber); + + writer.Write(Encoding.ASCII.GetBytes(Platform)); + + ms.Seek(0x28, SeekOrigin.Begin); + + writer.Write(Encoding.ASCII.GetBytes(UnknownHex)); + + ms.Seek(0x68, SeekOrigin.Begin); + + writer.Write(Encoding.ASCII.GetBytes(Version)); + + ms.Seek(0x80, SeekOrigin.Begin); + + writer.Write(Encoding.ASCII.GetBytes(Build)); + + context.Memory.Write(replyPos, ms.ToArray()); + + return ResultCode.Success; + } + + [CommandCmif(23)] + // GetColorSetId() -> i32 + public ResultCode GetColorSetId(ServiceCtx context) + { + context.ResponseData.Write((int)context.Device.System.State.ThemeColor); + + return ResultCode.Success; + } + + [CommandCmif(24)] + // GetColorSetId() -> i32 + public ResultCode SetColorSetId(ServiceCtx context) + { + int colorSetId = context.RequestData.ReadInt32(); + + context.Device.System.State.ThemeColor = (ColorSet)colorSetId; + + return ResultCode.Success; + } + + [CommandCmif(37)] + // GetSettingsItemValueSize(buffer, buffer) -> u64 + public ResultCode GetSettingsItemValueSize(ServiceCtx context) + { + ulong classPos = context.Request.PtrBuff[0].Position; + ulong classSize = context.Request.PtrBuff[0].Size; + + ulong namePos = context.Request.PtrBuff[1].Position; + ulong nameSize = context.Request.PtrBuff[1].Size; + + byte[] classBuffer = new byte[classSize]; + + context.Memory.Read(classPos, classBuffer); + + byte[] nameBuffer = new byte[nameSize]; + + context.Memory.Read(namePos, nameBuffer); + + string askedSetting = Encoding.ASCII.GetString(classBuffer).Trim('\0') + "!" + Encoding.ASCII.GetString(nameBuffer).Trim('\0'); + + NxSettings.Settings.TryGetValue(askedSetting, out object nxSetting); + + if (nxSetting != null) + { + ulong settingSize; + + if (nxSetting is string stringValue) + { + settingSize = (ulong)stringValue.Length + 1; + } + else if (nxSetting is int) + { + settingSize = sizeof(int); + } + else if (nxSetting is bool) + { + settingSize = 1; + } + else + { + throw new NotImplementedException(nxSetting.GetType().Name); + } + + context.ResponseData.Write(settingSize); + } + + return ResultCode.Success; + } + + [CommandCmif(38)] + // GetSettingsItemValue(buffer, buffer) -> (u64, buffer) + public ResultCode GetSettingsItemValue(ServiceCtx context) + { + ulong classPos = context.Request.PtrBuff[0].Position; + ulong classSize = context.Request.PtrBuff[0].Size; + + ulong namePos = context.Request.PtrBuff[1].Position; + ulong nameSize = context.Request.PtrBuff[1].Size; + + ulong replyPos = context.Request.ReceiveBuff[0].Position; + ulong replySize = context.Request.ReceiveBuff[0].Size; + + byte[] classBuffer = new byte[classSize]; + + context.Memory.Read(classPos, classBuffer); + + byte[] nameBuffer = new byte[nameSize]; + + context.Memory.Read(namePos, nameBuffer); + + string askedSetting = Encoding.ASCII.GetString(classBuffer).Trim('\0') + "!" + Encoding.ASCII.GetString(nameBuffer).Trim('\0'); + + NxSettings.Settings.TryGetValue(askedSetting, out object nxSetting); + + if (nxSetting != null) + { + byte[] settingBuffer = new byte[replySize]; + + if (nxSetting is string stringValue) + { + if ((ulong)(stringValue.Length + 1) > replySize) + { + Logger.Error?.Print(LogClass.ServiceSet, $"{askedSetting} String value size is too big!"); + } + else + { + settingBuffer = Encoding.ASCII.GetBytes(stringValue + "\0"); + } + } + + if (nxSetting is int intValue) + { + settingBuffer = BitConverter.GetBytes(intValue); + } + else if (nxSetting is bool boolValue) + { + settingBuffer[0] = boolValue ? (byte)1 : (byte)0; + } + else + { + throw new NotImplementedException(nxSetting.GetType().Name); + } + + context.Memory.Write(replyPos, settingBuffer); + + Logger.Debug?.Print(LogClass.ServiceSet, $"{askedSetting} set value: {nxSetting} as {nxSetting.GetType()}"); + } + else + { + Logger.Error?.Print(LogClass.ServiceSet, $"{askedSetting} not found!"); + } + + return ResultCode.Success; + } + + [CommandCmif(60)] + // IsUserSystemClockAutomaticCorrectionEnabled() -> bool + public ResultCode IsUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) + { + // NOTE: When set to true, is automatically synced with the internet. + context.ResponseData.Write(true); + + Logger.Stub?.PrintStub(LogClass.ServiceSet); + + return ResultCode.Success; + } + + [CommandCmif(62)] + // GetDebugModeFlag() -> bool + public ResultCode GetDebugModeFlag(ServiceCtx context) + { + context.ResponseData.Write(false); + + Logger.Stub?.PrintStub(LogClass.ServiceSet); + + return ResultCode.Success; + } + + [CommandCmif(77)] + // GetDeviceNickName() -> buffer + public ResultCode GetDeviceNickName(ServiceCtx context) + { + ulong deviceNickNameBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong deviceNickNameBufferSize = context.Request.ReceiveBuff[0].Size; + + if (deviceNickNameBufferPosition == 0) + { + return ResultCode.NullDeviceNicknameBuffer; + } + + if (deviceNickNameBufferSize != 0x80) + { + Logger.Warning?.Print(LogClass.ServiceSet, "Wrong buffer size"); + } + + context.Memory.Write(deviceNickNameBufferPosition, Encoding.ASCII.GetBytes(context.Device.System.State.DeviceNickName + '\0')); + + return ResultCode.Success; + } + + [CommandCmif(78)] + // SetDeviceNickName(buffer) + public ResultCode SetDeviceNickName(ServiceCtx context) + { + ulong deviceNickNameBufferPosition = context.Request.SendBuff[0].Position; + ulong deviceNickNameBufferSize = context.Request.SendBuff[0].Size; + + byte[] deviceNickNameBuffer = new byte[deviceNickNameBufferSize]; + + context.Memory.Read(deviceNickNameBufferPosition, deviceNickNameBuffer); + + context.Device.System.State.DeviceNickName = Encoding.ASCII.GetString(deviceNickNameBuffer); + + return ResultCode.Success; + } + + [CommandCmif(90)] + // GetMiiAuthorId() -> nn::util::Uuid + public ResultCode GetMiiAuthorId(ServiceCtx context) + { + // NOTE: If miiAuthorId is null ResultCode.NullMiiAuthorIdBuffer is returned. + // Doesn't occur in our case. + + context.ResponseData.Write(Mii.Helper.GetDeviceId()); + + return ResultCode.Success; + } + + public byte[] GetFirmwareData(Switch device) + { + const ulong SystemVersionTitleId = 0x0100000000000809; + + string contentPath = device.System.ContentManager.GetInstalledContentPath(SystemVersionTitleId, StorageId.BuiltInSystem, NcaContentType.Data); + + if (string.IsNullOrWhiteSpace(contentPath)) + { + return null; + } + + string firmwareTitlePath = FileSystem.VirtualFileSystem.SwitchPathToSystemPath(contentPath); + + using IStorage firmwareStorage = new LocalStorage(firmwareTitlePath, FileAccess.Read); + Nca firmwareContent = new(device.System.KeySet, firmwareStorage); + + if (!firmwareContent.CanOpenSection(NcaSectionType.Data)) + { + return null; + } + + IFileSystem firmwareRomFs = firmwareContent.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel); + + using var firmwareFile = new UniqueRef(); + + Result result = firmwareRomFs.OpenFile(ref firmwareFile.Ref, "/file".ToU8Span(), OpenMode.Read); + if (result.IsFailure()) + { + return null; + } + + result = firmwareFile.Get.GetSize(out long fileSize); + if (result.IsFailure()) + { + return null; + } + + byte[] data = new byte[fileSize]; + + result = firmwareFile.Get.Read(out _, 0, data); + if (result.IsFailure()) + { + return null; + } + + return data; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/KeyCodeMaps.cs b/src/Ryujinx.HLE/HOS/Services/Settings/KeyCodeMaps.cs new file mode 100644 index 00000000..981fc18e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/KeyCodeMaps.cs @@ -0,0 +1,4849 @@ +namespace Ryujinx.HLE.HOS.Services.Settings +{ + static class KeyCodeMaps + { + public static byte[] Default = + { + 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x61, 0x30, + 0x61, 0x30, 0xc1, 0x30, 0xc1, 0x30, 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, + 0x53, 0x30, 0x53, 0x30, 0xb3, 0x30, 0xb3, 0x30, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x5d, 0x30, 0x5d, 0x30, 0xbd, 0x30, 0xbd, 0x30, 0x01, 0x10, + 0x64, 0x00, 0x44, 0x00, 0x57, 0x30, 0x57, 0x30, 0xb7, 0x30, 0xb7, 0x30, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0x44, 0x30, 0x43, 0x30, 0xa4, 0x30, + 0xa3, 0x30, 0x01, 0x10, 0x66, 0x00, 0x46, 0x00, 0x6f, 0x30, 0x6f, 0x30, + 0xcf, 0x30, 0xcf, 0x30, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x4d, 0x30, + 0x4d, 0x30, 0xad, 0x30, 0xad, 0x30, 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, + 0x4f, 0x30, 0x4f, 0x30, 0xaf, 0x30, 0xaf, 0x30, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x6b, 0x30, 0x6b, 0x30, 0xcb, 0x30, 0xcb, 0x30, 0x01, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x7e, 0x30, 0x7e, 0x30, 0xde, 0x30, 0xde, 0x30, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x6e, 0x30, 0x6e, 0x30, 0xce, 0x30, + 0xce, 0x30, 0x01, 0x10, 0x6c, 0x00, 0x4c, 0x00, 0x8a, 0x30, 0x8a, 0x30, + 0xea, 0x30, 0xea, 0x30, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x82, 0x30, + 0x82, 0x30, 0xe2, 0x30, 0xe2, 0x30, 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, + 0x7f, 0x30, 0x7f, 0x30, 0xdf, 0x30, 0xdf, 0x30, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0x89, 0x30, 0x89, 0x30, 0xe9, 0x30, 0xe9, 0x30, 0x01, 0x10, + 0x70, 0x00, 0x50, 0x00, 0x5b, 0x30, 0x5b, 0x30, 0xbb, 0x30, 0xbb, 0x30, + 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x5f, 0x30, 0x5f, 0x30, 0xbf, 0x30, + 0xbf, 0x30, 0x01, 0x10, 0x72, 0x00, 0x52, 0x00, 0x59, 0x30, 0x59, 0x30, + 0xb9, 0x30, 0xb9, 0x30, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x68, 0x30, + 0x68, 0x30, 0xc8, 0x30, 0xc8, 0x30, 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, + 0x4b, 0x30, 0x4b, 0x30, 0xab, 0x30, 0xab, 0x30, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x6a, 0x30, 0x6a, 0x30, 0xca, 0x30, 0xca, 0x30, 0x01, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x72, 0x30, 0x72, 0x30, 0xd2, 0x30, 0xd2, 0x30, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x66, 0x30, 0x66, 0x30, 0xc6, 0x30, + 0xc6, 0x30, 0x01, 0x10, 0x78, 0x00, 0x58, 0x00, 0x55, 0x30, 0x55, 0x30, + 0xb5, 0x30, 0xb5, 0x30, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x93, 0x30, + 0x93, 0x30, 0xf3, 0x30, 0xf3, 0x30, 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, + 0x64, 0x30, 0x63, 0x30, 0xc4, 0x30, 0xc3, 0x30, 0x00, 0x10, 0x31, 0x00, + 0x21, 0x00, 0x6c, 0x30, 0x6c, 0x30, 0xcc, 0x30, 0xcc, 0x30, 0x00, 0x10, + 0x32, 0x00, 0x22, 0x00, 0x75, 0x30, 0x75, 0x30, 0xd5, 0x30, 0xd5, 0x30, + 0x00, 0x10, 0x33, 0x00, 0x23, 0x00, 0x42, 0x30, 0x41, 0x30, 0xa2, 0x30, + 0xa1, 0x30, 0x00, 0x10, 0x34, 0x00, 0x24, 0x00, 0x46, 0x30, 0x45, 0x30, + 0xa6, 0x30, 0xa5, 0x30, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x48, 0x30, + 0x47, 0x30, 0xa8, 0x30, 0xa7, 0x30, 0x00, 0x10, 0x36, 0x00, 0x26, 0x00, + 0x4a, 0x30, 0x49, 0x30, 0xaa, 0x30, 0xa9, 0x30, 0x00, 0x10, 0x37, 0x00, + 0x27, 0x00, 0x84, 0x30, 0x83, 0x30, 0xe4, 0x30, 0xe3, 0x30, 0x00, 0x10, + 0x38, 0x00, 0x28, 0x00, 0x86, 0x30, 0x85, 0x30, 0xe6, 0x30, 0xe5, 0x30, + 0x00, 0x10, 0x39, 0x00, 0x29, 0x00, 0x88, 0x30, 0x87, 0x30, 0xe8, 0x30, + 0xe7, 0x30, 0x00, 0x10, 0x30, 0x00, 0x00, 0x00, 0x8f, 0x30, 0x92, 0x30, + 0xef, 0x30, 0xf2, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x00, 0x10, 0x2d, 0x00, 0x3d, 0x00, 0x7b, 0x30, 0x7b, 0x30, + 0xdb, 0x30, 0xdb, 0x30, 0x00, 0x10, 0x5e, 0x00, 0x7e, 0x00, 0x78, 0x30, + 0x78, 0x30, 0xd8, 0x30, 0xd8, 0x30, 0x00, 0x10, 0x40, 0x00, 0x60, 0x00, + 0x9e, 0xff, 0x9e, 0xff, 0x9e, 0xff, 0x9e, 0xff, 0x00, 0x10, 0x5b, 0x00, + 0x7b, 0x00, 0x9f, 0xff, 0x62, 0xff, 0x9f, 0xff, 0x62, 0xff, 0x00, 0x10, + 0x5d, 0x00, 0x7d, 0x00, 0x80, 0x30, 0x63, 0xff, 0xe0, 0x30, 0x63, 0xff, + 0x00, 0x10, 0x5d, 0x00, 0x7d, 0x00, 0x80, 0x30, 0x63, 0xff, 0xe0, 0x30, + 0x63, 0xff, 0x00, 0x10, 0x3b, 0x00, 0x2b, 0x00, 0x8c, 0x30, 0x8c, 0x30, + 0xec, 0x30, 0xec, 0x30, 0x00, 0x10, 0x3a, 0x00, 0x2a, 0x00, 0x51, 0x30, + 0x51, 0x30, 0xb1, 0x30, 0xb1, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2c, 0x00, + 0x3c, 0x00, 0x6d, 0x30, 0x64, 0xff, 0xcd, 0x30, 0x64, 0xff, 0x00, 0x10, + 0x2e, 0x00, 0x3e, 0x00, 0x8b, 0x30, 0x61, 0xff, 0xeb, 0x30, 0x61, 0xff, + 0x00, 0x10, 0x2f, 0x00, 0x3f, 0x00, 0x81, 0x30, 0x65, 0xff, 0xe1, 0x30, + 0x65, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, + 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, 0x2b, 0x00, 0x2b, 0x00, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, + 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x34, 0x00, 0xff, 0x20, 0x20, 0x00, 0x35, 0x00, 0x20, 0x00, 0x35, 0x00, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x36, 0x00, 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, + 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x37, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x30, 0x00, 0xff, 0x20, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2e, 0x00, + 0x00, 0x00, 0x2e, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x5f, 0x00, 0x8d, 0x30, 0x8d, 0x30, + 0xed, 0x30, 0xed, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, + 0x70, 0xff, 0x70, 0xff, 0x70, 0xff, 0x70, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static byte[] EnglishUsInternational = + { + 0x01, 0x00, 0x00, 0x03, 0x05, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0x61, 0x00, 0x41, 0x00, 0xe1, 0x00, + 0xc1, 0x00, 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x63, 0x00, 0x43, 0x00, 0xa9, 0x00, 0xa2, 0x00, 0x03, 0x10, + 0x64, 0x00, 0x44, 0x00, 0xf0, 0x00, 0xd0, 0x00, 0x03, 0x10, 0x65, 0x00, + 0x45, 0x00, 0xe9, 0x00, 0xc9, 0x00, 0x01, 0x10, 0x66, 0x00, 0x46, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x10, 0x69, 0x00, 0x49, 0x00, 0xed, 0x00, 0xcd, 0x00, 0x01, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6b, 0x00, + 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0x6c, 0x00, 0x4c, 0x00, + 0xf8, 0x00, 0xd8, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0xb5, 0x00, + 0x00, 0x00, 0x03, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0xf1, 0x00, 0xd1, 0x00, + 0x03, 0x10, 0x6f, 0x00, 0x4f, 0x00, 0xf3, 0x00, 0xd3, 0x00, 0x03, 0x10, + 0x70, 0x00, 0x50, 0x00, 0xf6, 0x00, 0xd6, 0x00, 0x03, 0x10, 0x71, 0x00, + 0x51, 0x00, 0xe4, 0x00, 0xc4, 0x00, 0x01, 0x10, 0x72, 0x00, 0x52, 0x00, + 0xae, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0xdf, 0x00, + 0xa7, 0x00, 0x03, 0x10, 0x74, 0x00, 0x54, 0x00, 0xfe, 0x00, 0xde, 0x00, + 0x03, 0x10, 0x75, 0x00, 0x55, 0x00, 0xfa, 0x00, 0xda, 0x00, 0x01, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0x77, 0x00, + 0x57, 0x00, 0xe5, 0x00, 0xc5, 0x00, 0x01, 0x10, 0x78, 0x00, 0x58, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0x79, 0x00, 0x59, 0x00, 0xfc, 0x00, + 0xdc, 0x00, 0x03, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0xe6, 0x00, 0xc6, 0x00, + 0x00, 0x10, 0x31, 0x00, 0x21, 0x00, 0xa1, 0x00, 0xb9, 0x00, 0x00, 0x10, + 0x32, 0x00, 0x40, 0x00, 0xb2, 0x00, 0x00, 0x00, 0x00, 0x10, 0x33, 0x00, + 0x23, 0x00, 0xb3, 0x00, 0x00, 0x00, 0x00, 0x10, 0x34, 0x00, 0x24, 0x00, + 0xa4, 0x00, 0xa3, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0xac, 0x20, + 0x00, 0x00, 0x00, 0x10, 0x36, 0x00, 0x02, 0x03, 0xbc, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x37, 0x00, 0x26, 0x00, 0xbd, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x38, 0x00, 0x2a, 0x00, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x10, 0x39, 0x00, + 0x28, 0x00, 0x18, 0x20, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, 0x29, 0x00, + 0x19, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, + 0xa5, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0xd7, 0x00, + 0xf7, 0x00, 0x00, 0x10, 0x5b, 0x00, 0x7b, 0x00, 0xab, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x5d, 0x00, 0x7d, 0x00, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x5c, 0x00, 0x7c, 0x00, 0xac, 0x00, 0xa6, 0x00, 0x00, 0x10, 0x5c, 0x00, + 0x7c, 0x00, 0xac, 0x00, 0xa6, 0x00, 0x00, 0x10, 0x3b, 0x00, 0x3a, 0x00, + 0xb6, 0x00, 0xb0, 0x00, 0x00, 0x10, 0x0d, 0x03, 0x0e, 0x03, 0xb4, 0x00, + 0xa8, 0x00, 0x00, 0x10, 0x00, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x10, 0x2c, 0x00, 0x3c, 0x00, 0xe7, 0x00, 0xc7, 0x00, 0x00, 0x10, + 0x2e, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x3f, 0x00, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x2d, 0x00, + 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x34, 0x00, 0xff, 0x20, 0x20, 0x00, 0x35, 0x00, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x36, 0x00, 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x37, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xff, 0x20, 0x00, 0x00, 0x2e, 0x00, + 0x00, 0x00, 0x2e, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static byte[] EnglishUk = + { + 0x01, 0x00, 0x00, 0x03, 0x05, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0x61, 0x00, 0x41, 0x00, 0xe1, 0x00, + 0xc1, 0x00, 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x63, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0x65, 0x00, + 0x45, 0x00, 0xe9, 0x00, 0xc9, 0x00, 0x01, 0x10, 0x66, 0x00, 0x46, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x10, 0x69, 0x00, 0x49, 0x00, 0xed, 0x00, 0xcd, 0x00, 0x01, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6b, 0x00, + 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, 0x4c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x10, 0x6f, 0x00, 0x4f, 0x00, 0xf3, 0x00, 0xd3, 0x00, 0x01, 0x10, + 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x71, 0x00, + 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x72, 0x00, 0x52, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x10, 0x75, 0x00, 0x55, 0x00, 0xfa, 0x00, 0xda, 0x00, 0x01, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x77, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, 0x58, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x31, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x32, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x33, 0x00, + 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x34, 0x00, 0x24, 0x00, + 0xac, 0x20, 0x00, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x36, 0x00, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x37, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x38, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x39, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, 0x29, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x5b, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x5d, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x23, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x23, 0x00, + 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3b, 0x00, 0x3a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x60, 0x00, 0xac, 0x00, 0xa6, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2c, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x2e, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x2d, 0x00, + 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x34, 0x00, 0xff, 0x20, 0x20, 0x00, 0x35, 0x00, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x36, 0x00, 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x37, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xff, 0x20, 0x00, 0x00, 0x2e, 0x00, + 0x00, 0x00, 0x2e, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static byte[] French = + { + 0x01, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x01, 0x10, 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0xac, 0x20, 0x01, 0x10, 0x66, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x01, 0x10, 0x2c, 0x00, 0x3f, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0x00, 0x00, 0x01, 0x10, 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x00, 0x00, 0x01, 0x10, 0x72, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x00, 0x00, 0x01, 0x10, 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x00, 0x00, 0x01, 0x10, 0x26, 0x00, + 0x31, 0x00, 0x00, 0x00, 0x01, 0x10, 0xe9, 0x00, 0x32, 0x00, 0x03, 0x03, + 0x01, 0x10, 0x22, 0x00, 0x33, 0x00, 0x23, 0x00, 0x01, 0x10, 0x27, 0x00, + 0x34, 0x00, 0x7b, 0x00, 0x01, 0x10, 0x28, 0x00, 0x35, 0x00, 0x5b, 0x00, + 0x01, 0x10, 0x2d, 0x00, 0x36, 0x00, 0x7c, 0x00, 0x01, 0x10, 0xe8, 0x00, + 0x37, 0x00, 0x00, 0x03, 0x01, 0x10, 0x5f, 0x00, 0x38, 0x00, 0x5c, 0x00, + 0x01, 0x10, 0xe7, 0x00, 0x39, 0x00, 0x5e, 0x00, 0x01, 0x10, 0xe0, 0x00, + 0x30, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x01, 0x10, 0x29, 0x00, + 0xb0, 0x00, 0x5d, 0x00, 0x01, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0x7d, 0x00, + 0x01, 0x10, 0x02, 0x03, 0x08, 0x03, 0x00, 0x00, 0x01, 0x10, 0x24, 0x00, + 0xa3, 0x00, 0xa4, 0x00, 0x01, 0x10, 0x2a, 0x00, 0xb5, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x2a, 0x00, 0xb5, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, + 0x4d, 0x00, 0x00, 0x00, 0x01, 0x10, 0xf9, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x00, 0x10, 0xb2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x3b, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x01, 0x10, 0x3a, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x21, 0x00, 0xa7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0xff, 0x20, 0x20, 0x00, + 0x35, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static byte[] FrenchCa = + { + 0x01, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x01, 0x10, 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0x00, 0x00, 0x01, 0x10, 0x66, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0xb5, 0x00, + 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0xa7, 0x00, 0x01, 0x10, 0x70, 0x00, 0x50, 0x00, 0xb6, 0x00, + 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x00, 0x00, 0x01, 0x10, 0x72, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x00, 0x00, 0x01, 0x10, 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x10, 0x31, 0x00, + 0x21, 0x00, 0xb1, 0x00, 0x00, 0x10, 0x32, 0x00, 0x22, 0x00, 0x40, 0x00, + 0x00, 0x10, 0x33, 0x00, 0x2f, 0x00, 0xa3, 0x00, 0x00, 0x10, 0x34, 0x00, + 0x24, 0x00, 0xa2, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0xa4, 0x00, + 0x00, 0x10, 0x36, 0x00, 0x3f, 0x00, 0xac, 0x00, 0x00, 0x10, 0x37, 0x00, + 0x26, 0x00, 0xa6, 0x00, 0x00, 0x10, 0x38, 0x00, 0x2a, 0x00, 0xb2, 0x00, + 0x00, 0x10, 0x39, 0x00, 0x28, 0x00, 0xb3, 0x00, 0x00, 0x10, 0x30, 0x00, + 0x29, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x2d, 0x00, + 0x5f, 0x00, 0xbd, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0xbe, 0x00, + 0x00, 0x10, 0x02, 0x03, 0x02, 0x03, 0x5b, 0x00, 0x00, 0x10, 0x27, 0x03, + 0x08, 0x03, 0x5d, 0x00, 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x7d, 0x00, + 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x7d, 0x00, 0x00, 0x10, 0x3b, 0x00, + 0x3a, 0x00, 0x7e, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x03, 0x7b, 0x00, + 0x00, 0x10, 0x23, 0x00, 0x7c, 0x00, 0x5c, 0x00, 0x00, 0x10, 0x2c, 0x00, + 0x27, 0x00, 0xaf, 0x00, 0x00, 0x10, 0x2e, 0x00, 0x2e, 0x00, 0x2d, 0x00, + 0x01, 0x10, 0xe9, 0x00, 0xc9, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0xff, 0x20, 0x20, 0x00, + 0x35, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x00, 0x10, 0xab, 0x00, 0xbb, 0x00, 0xb0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static byte[] Spanish = + { + 0x01, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x01, 0x10, 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0xac, 0x20, 0x01, 0x10, 0x66, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0x00, 0x00, 0x01, 0x10, 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x00, 0x00, 0x01, 0x10, 0x72, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x00, 0x00, 0x01, 0x10, 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x10, 0x31, 0x00, + 0x21, 0x00, 0x7c, 0x00, 0x00, 0x10, 0x32, 0x00, 0x22, 0x00, 0x40, 0x00, + 0x00, 0x10, 0x33, 0x00, 0xb7, 0x00, 0x23, 0x00, 0x00, 0x10, 0x34, 0x00, + 0x24, 0x00, 0x03, 0x03, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0xac, 0x20, + 0x00, 0x10, 0x36, 0x00, 0x26, 0x00, 0xac, 0x00, 0x00, 0x10, 0x37, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x39, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, + 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x27, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x00, 0x10, 0xa1, 0x00, 0xbf, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x03, 0x02, 0x03, 0x5b, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2a, 0x00, 0x5d, 0x00, 0x01, 0x10, 0xe7, 0x00, 0xc7, 0x00, 0x7d, 0x00, + 0x01, 0x10, 0xe7, 0x00, 0xc7, 0x00, 0x7d, 0x00, 0x01, 0x10, 0xf1, 0x00, + 0xd1, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x03, 0x08, 0x03, 0x7b, 0x00, + 0x00, 0x10, 0xba, 0x00, 0xaa, 0x00, 0x5c, 0x00, 0x00, 0x10, 0x2c, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2e, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0xff, 0x20, 0x20, 0x00, + 0x35, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static byte[] SpanishLatin = + { + 0x01, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x01, 0x10, 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0x00, 0x00, 0x01, 0x10, 0x66, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0x00, 0x00, 0x01, 0x10, 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x40, 0x00, 0x01, 0x10, 0x72, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x00, 0x00, 0x01, 0x10, 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x10, 0x31, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x10, 0x32, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x33, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x10, 0x34, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x36, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x10, 0x37, 0x00, + 0x2f, 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x39, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, + 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x27, 0x00, + 0x3f, 0x00, 0x5c, 0x00, 0x00, 0x10, 0xbf, 0x00, 0xa1, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x01, 0x03, 0x08, 0x03, 0x00, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2a, 0x00, 0x7e, 0x00, 0x00, 0x10, 0x7d, 0x00, 0x5d, 0x00, 0x00, 0x03, + 0x00, 0x10, 0x7d, 0x00, 0x5d, 0x00, 0x00, 0x03, 0x01, 0x10, 0xf1, 0x00, + 0xd1, 0x00, 0x00, 0x00, 0x00, 0x10, 0x7b, 0x00, 0x5b, 0x00, 0x02, 0x03, + 0x00, 0x10, 0x7c, 0x00, 0xb0, 0x00, 0xac, 0x00, 0x00, 0x10, 0x2c, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2e, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0xff, 0x20, 0x20, 0x00, + 0x35, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static byte[] German = + { + 0x01, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x01, 0x10, 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0xac, 0x20, 0x01, 0x10, 0x66, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0xb5, 0x00, + 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0x00, 0x00, 0x01, 0x10, 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x40, 0x00, 0x01, 0x10, 0x72, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x00, 0x00, 0x01, 0x10, 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, 0x01, 0x10, 0x31, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x01, 0x10, 0x32, 0x00, 0x22, 0x00, 0xb2, 0x00, + 0x01, 0x10, 0x33, 0x00, 0xa7, 0x00, 0xb3, 0x00, 0x01, 0x10, 0x34, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x01, 0x10, 0x35, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x36, 0x00, 0x26, 0x00, 0x00, 0x00, 0x01, 0x10, 0x37, 0x00, + 0x2f, 0x00, 0x7b, 0x00, 0x01, 0x10, 0x38, 0x00, 0x28, 0x00, 0x5b, 0x00, + 0x01, 0x10, 0x39, 0x00, 0x29, 0x00, 0x5d, 0x00, 0x01, 0x10, 0x30, 0x00, + 0x3d, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x01, 0x10, 0xdf, 0x00, + 0x3f, 0x00, 0x5c, 0x00, 0x00, 0x10, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, + 0x01, 0x10, 0xfc, 0x00, 0xdc, 0x00, 0x00, 0x00, 0x01, 0x10, 0x2b, 0x00, + 0x2a, 0x00, 0x7e, 0x00, 0x01, 0x10, 0x23, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x23, 0x00, 0x27, 0x00, 0x00, 0x00, 0x01, 0x10, 0xf6, 0x00, + 0xd6, 0x00, 0x00, 0x00, 0x01, 0x10, 0xe4, 0x00, 0xc4, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x02, 0x03, 0xb0, 0x00, 0x00, 0x00, 0x01, 0x10, 0x2c, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x01, 0x10, 0x2e, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0xff, 0x20, 0x20, 0x00, + 0x35, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x7c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static byte[] Italian = + { + 0x01, 0x00, 0x00, 0x03, 0x05, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x63, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x65, 0x00, + 0x45, 0x00, 0xac, 0x20, 0x00, 0x00, 0x01, 0x10, 0x66, 0x00, 0x46, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x69, 0x00, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6b, 0x00, + 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, 0x4c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6f, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x71, 0x00, + 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x72, 0x00, 0x52, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x75, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x77, 0x00, + 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, 0x58, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x31, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x32, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x33, 0x00, + 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x34, 0x00, 0x24, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0xac, 0x20, + 0x00, 0x00, 0x00, 0x10, 0x36, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x37, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x38, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x39, 0x00, + 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x27, 0x00, 0x3f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xec, 0x00, 0x5e, 0x00, 0x7e, 0x00, + 0x00, 0x00, 0x00, 0x10, 0xe8, 0x00, 0xe9, 0x00, 0x5b, 0x00, 0x7b, 0x00, + 0x00, 0x10, 0x2b, 0x00, 0x2a, 0x00, 0x5d, 0x00, 0x7d, 0x00, 0x00, 0x10, + 0xf9, 0x00, 0xa7, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0xf9, 0x00, + 0xa7, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0xf2, 0x00, 0xe7, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x10, 0xe0, 0x00, 0xb0, 0x00, 0x23, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2c, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x2e, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2d, 0x00, + 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x2d, 0x00, + 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x34, 0x00, 0xff, 0x20, 0x20, 0x00, 0x35, 0x00, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x36, 0x00, 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x37, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xff, 0x20, 0x00, 0x00, 0x2c, 0x00, + 0x00, 0x00, 0x2c, 0x00, 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static byte[] Portuguese = + { + 0x01, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x00, 0x00, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x00, 0x00, 0x01, 0x10, 0x64, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0xac, 0x20, 0x01, 0x10, 0x66, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6a, 0x00, 0x4a, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0x00, 0x00, 0x01, 0x10, 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x00, 0x00, 0x01, 0x10, 0x72, 0x00, + 0x52, 0x00, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x00, 0x00, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x00, 0x00, 0x01, 0x10, 0x76, 0x00, 0x56, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x10, 0x31, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x10, 0x32, 0x00, 0x22, 0x00, 0x40, 0x00, + 0x00, 0x10, 0x33, 0x00, 0x23, 0x00, 0xa3, 0x00, 0x00, 0x10, 0x34, 0x00, + 0x24, 0x00, 0xa7, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x36, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x10, 0x37, 0x00, + 0x2f, 0x00, 0x7b, 0x00, 0x00, 0x10, 0x38, 0x00, 0x28, 0x00, 0x5b, 0x00, + 0x00, 0x10, 0x39, 0x00, 0x29, 0x00, 0x5d, 0x00, 0x00, 0x10, 0x30, 0x00, + 0x3d, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x27, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x00, 0x10, 0xab, 0x00, 0xbb, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2b, 0x00, 0x2a, 0x00, 0x08, 0x03, 0x00, 0x10, 0x01, 0x03, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, 0x03, 0x03, 0x02, 0x03, 0x00, 0x00, + 0x00, 0x10, 0x03, 0x03, 0x02, 0x03, 0x00, 0x00, 0x01, 0x10, 0xe7, 0x00, + 0xc7, 0x00, 0x00, 0x00, 0x00, 0x10, 0xba, 0x00, 0xaa, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2c, 0x00, + 0x3b, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2e, 0x00, 0x3a, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0xff, 0x20, 0x20, 0x00, + 0x35, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3c, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static byte[] Russian = + { + 0x09, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x11, 0x10, 0x61, 0x00, 0x41, 0x00, 0x44, 0x04, + 0x24, 0x04, 0x11, 0x10, 0x62, 0x00, 0x42, 0x00, 0x38, 0x04, 0x18, 0x04, + 0x11, 0x10, 0x63, 0x00, 0x43, 0x00, 0x41, 0x04, 0x21, 0x04, 0x11, 0x10, + 0x64, 0x00, 0x44, 0x00, 0x32, 0x04, 0x12, 0x04, 0x11, 0x10, 0x65, 0x00, + 0x45, 0x00, 0x43, 0x04, 0x23, 0x04, 0x11, 0x10, 0x66, 0x00, 0x46, 0x00, + 0x30, 0x04, 0x10, 0x04, 0x11, 0x10, 0x67, 0x00, 0x47, 0x00, 0x3f, 0x04, + 0x1f, 0x04, 0x11, 0x10, 0x68, 0x00, 0x48, 0x00, 0x40, 0x04, 0x20, 0x04, + 0x11, 0x10, 0x69, 0x00, 0x49, 0x00, 0x48, 0x04, 0x28, 0x04, 0x11, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x3e, 0x04, 0x1e, 0x04, 0x11, 0x10, 0x6b, 0x00, + 0x4b, 0x00, 0x3b, 0x04, 0x1b, 0x04, 0x11, 0x10, 0x6c, 0x00, 0x4c, 0x00, + 0x34, 0x04, 0x14, 0x04, 0x11, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x4c, 0x04, + 0x2c, 0x04, 0x11, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x42, 0x04, 0x22, 0x04, + 0x11, 0x10, 0x6f, 0x00, 0x4f, 0x00, 0x49, 0x04, 0x29, 0x04, 0x11, 0x10, + 0x70, 0x00, 0x50, 0x00, 0x37, 0x04, 0x17, 0x04, 0x11, 0x10, 0x71, 0x00, + 0x51, 0x00, 0x39, 0x04, 0x19, 0x04, 0x11, 0x10, 0x72, 0x00, 0x52, 0x00, + 0x3a, 0x04, 0x1a, 0x04, 0x11, 0x10, 0x73, 0x00, 0x53, 0x00, 0x4b, 0x04, + 0x2b, 0x04, 0x11, 0x10, 0x74, 0x00, 0x54, 0x00, 0x35, 0x04, 0x15, 0x04, + 0x11, 0x10, 0x75, 0x00, 0x55, 0x00, 0x33, 0x04, 0x13, 0x04, 0x11, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x3c, 0x04, 0x1c, 0x04, 0x11, 0x10, 0x77, 0x00, + 0x57, 0x00, 0x46, 0x04, 0x26, 0x04, 0x11, 0x10, 0x78, 0x00, 0x58, 0x00, + 0x47, 0x04, 0x27, 0x04, 0x11, 0x10, 0x79, 0x00, 0x59, 0x00, 0x3d, 0x04, + 0x1d, 0x04, 0x11, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x4f, 0x04, 0x2f, 0x04, + 0x00, 0x10, 0x31, 0x00, 0x21, 0x00, 0x31, 0x00, 0x21, 0x00, 0x00, 0x10, + 0x32, 0x00, 0x40, 0x00, 0x32, 0x00, 0x22, 0x00, 0x00, 0x10, 0x33, 0x00, + 0x23, 0x00, 0x33, 0x00, 0x16, 0x21, 0x00, 0x10, 0x34, 0x00, 0x24, 0x00, + 0x34, 0x00, 0x3b, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x35, 0x00, + 0x25, 0x00, 0x00, 0x10, 0x36, 0x00, 0x5e, 0x00, 0x36, 0x00, 0x3a, 0x00, + 0x00, 0x10, 0x37, 0x00, 0x26, 0x00, 0x37, 0x00, 0x3f, 0x00, 0x00, 0x10, + 0x38, 0x00, 0x2a, 0x00, 0x38, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x39, 0x00, + 0x28, 0x00, 0x39, 0x00, 0x28, 0x00, 0x00, 0x10, 0x30, 0x00, 0x29, 0x00, + 0x30, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, + 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0x3d, 0x00, + 0x2b, 0x00, 0x10, 0x10, 0x5b, 0x00, 0x7b, 0x00, 0x45, 0x04, 0x25, 0x04, + 0x10, 0x10, 0x5d, 0x00, 0x7d, 0x00, 0x4a, 0x04, 0x2a, 0x04, 0x00, 0x10, + 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, 0x2f, 0x00, 0x00, 0x10, 0x5c, 0x00, + 0x7c, 0x00, 0x5c, 0x00, 0x2f, 0x00, 0x10, 0x10, 0x3b, 0x00, 0x3a, 0x00, + 0x36, 0x04, 0x16, 0x04, 0x10, 0x10, 0x27, 0x00, 0x22, 0x00, 0x4d, 0x04, + 0x2d, 0x04, 0x10, 0x10, 0x60, 0x00, 0x7e, 0x00, 0x51, 0x04, 0x01, 0x04, + 0x10, 0x10, 0x2c, 0x00, 0x3c, 0x00, 0x31, 0x04, 0x11, 0x04, 0x10, 0x10, + 0x2e, 0x00, 0x3e, 0x00, 0x4e, 0x04, 0x2e, 0x04, 0x00, 0x10, 0x2f, 0x00, + 0x3f, 0x00, 0x2e, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x2d, 0x00, + 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x34, 0x00, 0xff, 0x20, 0x20, 0x00, 0x35, 0x00, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x36, 0x00, 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x37, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xff, 0x20, 0x00, 0x00, 0x2c, 0x00, + 0x00, 0x00, 0x2c, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static byte[] Korean = + { + 0x11, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x41, 0x31, + 0x41, 0x31, 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, 0x60, 0x31, 0x60, 0x31, + 0x01, 0x10, 0x63, 0x00, 0x43, 0x00, 0x4a, 0x31, 0x4a, 0x31, 0x01, 0x10, + 0x64, 0x00, 0x44, 0x00, 0x47, 0x31, 0x47, 0x31, 0x01, 0x10, 0x65, 0x00, + 0x45, 0x00, 0x37, 0x31, 0x38, 0x31, 0x01, 0x10, 0x66, 0x00, 0x46, 0x00, + 0x39, 0x31, 0x39, 0x31, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x4e, 0x31, + 0x4e, 0x31, 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, 0x57, 0x31, 0x57, 0x31, + 0x01, 0x10, 0x69, 0x00, 0x49, 0x00, 0x51, 0x31, 0x51, 0x31, 0x01, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x53, 0x31, 0x53, 0x31, 0x01, 0x10, 0x6b, 0x00, + 0x4b, 0x00, 0x4f, 0x31, 0x4f, 0x31, 0x01, 0x10, 0x6c, 0x00, 0x4c, 0x00, + 0x63, 0x31, 0x63, 0x31, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x61, 0x31, + 0x61, 0x31, 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, 0x5c, 0x31, 0x5c, 0x31, + 0x01, 0x10, 0x6f, 0x00, 0x4f, 0x00, 0x50, 0x31, 0x52, 0x31, 0x01, 0x10, + 0x70, 0x00, 0x50, 0x00, 0x54, 0x31, 0x56, 0x31, 0x01, 0x10, 0x71, 0x00, + 0x51, 0x00, 0x42, 0x31, 0x43, 0x31, 0x01, 0x10, 0x72, 0x00, 0x52, 0x00, + 0x31, 0x31, 0x32, 0x31, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x34, 0x31, + 0x34, 0x31, 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, 0x45, 0x31, 0x46, 0x31, + 0x01, 0x10, 0x75, 0x00, 0x55, 0x00, 0x55, 0x31, 0x55, 0x31, 0x01, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x4d, 0x31, 0x4d, 0x31, 0x01, 0x10, 0x77, 0x00, + 0x57, 0x00, 0x48, 0x31, 0x49, 0x31, 0x01, 0x10, 0x78, 0x00, 0x58, 0x00, + 0x4c, 0x31, 0x4c, 0x31, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x5b, 0x31, + 0x5b, 0x31, 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, 0x4b, 0x31, 0x4b, 0x31, + 0x00, 0x10, 0x31, 0x00, 0x21, 0x00, 0x31, 0x00, 0x21, 0x00, 0x00, 0x10, + 0x32, 0x00, 0x40, 0x00, 0x32, 0x00, 0x40, 0x00, 0x00, 0x10, 0x33, 0x00, + 0x23, 0x00, 0x33, 0x00, 0x23, 0x00, 0x00, 0x10, 0x34, 0x00, 0x24, 0x00, + 0x34, 0x00, 0x24, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x35, 0x00, + 0x25, 0x00, 0x00, 0x10, 0x36, 0x00, 0x5e, 0x00, 0x36, 0x00, 0x5e, 0x00, + 0x00, 0x10, 0x37, 0x00, 0x26, 0x00, 0x37, 0x00, 0x26, 0x00, 0x00, 0x10, + 0x38, 0x00, 0x2a, 0x00, 0x38, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x39, 0x00, + 0x28, 0x00, 0x39, 0x00, 0x28, 0x00, 0x00, 0x10, 0x30, 0x00, 0x29, 0x00, + 0x30, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, + 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, + 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0x3d, 0x00, + 0x2b, 0x00, 0x00, 0x10, 0x5b, 0x00, 0x7b, 0x00, 0x5b, 0x00, 0x7b, 0x00, + 0x00, 0x10, 0x5d, 0x00, 0x7d, 0x00, 0x5d, 0x00, 0x7d, 0x00, 0x00, 0x10, + 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x10, 0x5c, 0x00, + 0x7c, 0x00, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x10, 0x3b, 0x00, 0x3a, 0x00, + 0x3b, 0x00, 0x3a, 0x00, 0x00, 0x10, 0x27, 0x00, 0x22, 0x00, 0x27, 0x00, + 0x22, 0x00, 0x00, 0x10, 0x60, 0x00, 0x7e, 0x00, 0x60, 0x00, 0x7e, 0x00, + 0x00, 0x10, 0x2c, 0x00, 0x3c, 0x00, 0x2c, 0x00, 0x3c, 0x00, 0x00, 0x10, + 0x2e, 0x00, 0x3e, 0x00, 0x2e, 0x00, 0x3e, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x3f, 0x00, 0x2f, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x2d, 0x00, + 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x34, 0x00, 0xff, 0x20, 0x20, 0x00, 0x35, 0x00, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x36, 0x00, 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x37, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0xff, 0x20, 0x00, 0x00, 0x2e, 0x00, + 0x00, 0x00, 0x2e, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static byte[] ChineseSimplified = + { + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x01, 0x10, + 0x62, 0x00, 0x42, 0x00, 0x01, 0x10, 0x63, 0x00, 0x43, 0x00, 0x01, 0x10, + 0x64, 0x00, 0x44, 0x00, 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0x01, 0x10, + 0x66, 0x00, 0x46, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x01, 0x10, + 0x68, 0x00, 0x48, 0x00, 0x01, 0x10, 0x69, 0x00, 0x49, 0x00, 0x01, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x01, 0x10, + 0x6c, 0x00, 0x4c, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x01, 0x10, + 0x6e, 0x00, 0x4e, 0x00, 0x01, 0x10, 0x6f, 0x00, 0x4f, 0x00, 0x01, 0x10, + 0x70, 0x00, 0x50, 0x00, 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x01, 0x10, + 0x72, 0x00, 0x52, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x01, 0x10, + 0x74, 0x00, 0x54, 0x00, 0x01, 0x10, 0x75, 0x00, 0x55, 0x00, 0x01, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x01, 0x10, + 0x78, 0x00, 0x58, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x01, 0x10, + 0x7a, 0x00, 0x5a, 0x00, 0x00, 0x10, 0x31, 0x00, 0x21, 0x00, 0x00, 0x10, + 0x32, 0x00, 0x40, 0x00, 0x00, 0x10, 0x33, 0x00, 0x23, 0x00, 0x00, 0x10, + 0x34, 0x00, 0x24, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x00, 0x10, + 0x36, 0x00, 0x5e, 0x00, 0x00, 0x10, 0x37, 0x00, 0x26, 0x00, 0x00, 0x10, + 0x38, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x39, 0x00, 0x28, 0x00, 0x00, 0x10, + 0x30, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, + 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0x00, 0x10, + 0x5b, 0x00, 0x7b, 0x00, 0x00, 0x10, 0x5d, 0x00, 0x7d, 0x00, 0x00, 0x10, + 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x10, + 0x3b, 0x00, 0x3a, 0x00, 0x00, 0x10, 0x27, 0x00, 0x22, 0x00, 0x00, 0x10, + 0x60, 0x00, 0x7e, 0x00, 0x00, 0x10, 0x2c, 0x00, 0x3c, 0x00, 0x00, 0x10, + 0x2e, 0x00, 0x3e, 0x00, 0x00, 0x10, 0x2f, 0x00, 0x3f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x00, 0x10, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x31, 0x00, 0xff, 0x20, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0xff, 0x20, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x37, 0x00, 0xff, 0x20, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x2e, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static byte[] ChineseTraditional = + { + 0x61, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x61, 0x00, 0x41, 0x00, 0x07, 0x31, + 0x00, 0x00, 0xe5, 0x65, 0x00, 0x00, 0x01, 0x10, 0x62, 0x00, 0x42, 0x00, + 0x16, 0x31, 0x00, 0x00, 0x08, 0x67, 0x00, 0x00, 0x01, 0x10, 0x63, 0x00, + 0x43, 0x00, 0x0f, 0x31, 0x00, 0x00, 0xd1, 0x91, 0x00, 0x00, 0x01, 0x10, + 0x64, 0x00, 0x44, 0x00, 0x0e, 0x31, 0x00, 0x00, 0x28, 0x67, 0x00, 0x00, + 0x01, 0x10, 0x65, 0x00, 0x45, 0x00, 0x0d, 0x31, 0x00, 0x00, 0x34, 0x6c, + 0x00, 0x00, 0x01, 0x10, 0x66, 0x00, 0x46, 0x00, 0x11, 0x31, 0x00, 0x00, + 0x6b, 0x70, 0x00, 0x00, 0x01, 0x10, 0x67, 0x00, 0x47, 0x00, 0x15, 0x31, + 0x00, 0x00, 0x1f, 0x57, 0x00, 0x00, 0x01, 0x10, 0x68, 0x00, 0x48, 0x00, + 0x18, 0x31, 0x00, 0x00, 0xf9, 0x7a, 0x00, 0x00, 0x01, 0x10, 0x69, 0x00, + 0x49, 0x00, 0x1b, 0x31, 0x00, 0x00, 0x08, 0x62, 0x00, 0x00, 0x01, 0x10, + 0x6a, 0x00, 0x4a, 0x00, 0x28, 0x31, 0x00, 0x00, 0x41, 0x53, 0x00, 0x00, + 0x01, 0x10, 0x6b, 0x00, 0x4b, 0x00, 0x1c, 0x31, 0x00, 0x00, 0x27, 0x59, + 0x00, 0x00, 0x01, 0x10, 0x6c, 0x00, 0x4c, 0x00, 0x20, 0x31, 0x00, 0x00, + 0x2d, 0x4e, 0x00, 0x00, 0x01, 0x10, 0x6d, 0x00, 0x4d, 0x00, 0x29, 0x31, + 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x01, 0x10, 0x6e, 0x00, 0x4e, 0x00, + 0x19, 0x31, 0x00, 0x00, 0x13, 0x5f, 0x00, 0x00, 0x01, 0x10, 0x6f, 0x00, + 0x4f, 0x00, 0x1f, 0x31, 0x00, 0x00, 0xba, 0x4e, 0x00, 0x00, 0x01, 0x10, + 0x70, 0x00, 0x50, 0x00, 0x23, 0x31, 0x00, 0x00, 0xc3, 0x5f, 0x00, 0x00, + 0x01, 0x10, 0x71, 0x00, 0x51, 0x00, 0x06, 0x31, 0x00, 0x00, 0x4b, 0x62, + 0x00, 0x00, 0x01, 0x10, 0x72, 0x00, 0x52, 0x00, 0x10, 0x31, 0x00, 0x00, + 0xe3, 0x53, 0x00, 0x00, 0x01, 0x10, 0x73, 0x00, 0x53, 0x00, 0x0b, 0x31, + 0x00, 0x00, 0x38, 0x5c, 0x00, 0x00, 0x01, 0x10, 0x74, 0x00, 0x54, 0x00, + 0x14, 0x31, 0x00, 0x00, 0xff, 0x5e, 0x00, 0x00, 0x01, 0x10, 0x75, 0x00, + 0x55, 0x00, 0x27, 0x31, 0x00, 0x00, 0x71, 0x5c, 0x00, 0x00, 0x01, 0x10, + 0x76, 0x00, 0x56, 0x00, 0x12, 0x31, 0x00, 0x00, 0x73, 0x59, 0x00, 0x00, + 0x01, 0x10, 0x77, 0x00, 0x57, 0x00, 0x0a, 0x31, 0x00, 0x00, 0x30, 0x75, + 0x00, 0x00, 0x01, 0x10, 0x78, 0x00, 0x58, 0x00, 0x0c, 0x31, 0x00, 0x00, + 0xe3, 0x96, 0x00, 0x00, 0x01, 0x10, 0x79, 0x00, 0x59, 0x00, 0x17, 0x31, + 0x00, 0x00, 0x5c, 0x53, 0x00, 0x00, 0x01, 0x10, 0x7a, 0x00, 0x5a, 0x00, + 0x08, 0x31, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x10, 0x31, 0x00, + 0x21, 0x00, 0x05, 0x31, 0x00, 0x00, 0x31, 0x00, 0x21, 0x00, 0x00, 0x10, + 0x32, 0x00, 0x40, 0x00, 0x09, 0x31, 0x00, 0x00, 0x32, 0x00, 0x40, 0x00, + 0x00, 0x10, 0x33, 0x00, 0x23, 0x00, 0xc7, 0x02, 0x00, 0x00, 0x33, 0x00, + 0x23, 0x00, 0x00, 0x10, 0x34, 0x00, 0x24, 0x00, 0xcb, 0x02, 0x00, 0x00, + 0x34, 0x00, 0x24, 0x00, 0x00, 0x10, 0x35, 0x00, 0x25, 0x00, 0x13, 0x31, + 0x00, 0x00, 0x35, 0x00, 0x25, 0x00, 0x00, 0x10, 0x36, 0x00, 0x5e, 0x00, + 0xca, 0x02, 0x00, 0x00, 0x36, 0x00, 0x5e, 0x00, 0x00, 0x10, 0x37, 0x00, + 0x26, 0x00, 0xd9, 0x02, 0x00, 0x00, 0x37, 0x00, 0x26, 0x00, 0x00, 0x10, + 0x38, 0x00, 0x2a, 0x00, 0x1a, 0x31, 0x00, 0x00, 0x38, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x39, 0x00, 0x28, 0x00, 0x1e, 0x31, 0x00, 0x00, 0x39, 0x00, + 0x28, 0x00, 0x00, 0x10, 0x30, 0x00, 0x29, 0x00, 0x22, 0x31, 0x00, 0x00, + 0x30, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x00, 0x10, 0x2d, 0x00, 0x5f, 0x00, 0x26, 0x31, 0x00, 0x00, + 0x2d, 0x00, 0x5f, 0x00, 0x00, 0x10, 0x3d, 0x00, 0x2b, 0x00, 0x3d, 0x00, + 0x2b, 0x00, 0x3d, 0x00, 0x2b, 0x00, 0x00, 0x10, 0x5b, 0x00, 0x7b, 0x00, + 0x5b, 0x00, 0x7b, 0x00, 0x5b, 0x00, 0x7b, 0x00, 0x00, 0x10, 0x5d, 0x00, + 0x7d, 0x00, 0x5d, 0x00, 0x7d, 0x00, 0x5d, 0x00, 0x7d, 0x00, 0x00, 0x10, + 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, 0x7c, 0x00, + 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, + 0x7c, 0x00, 0x00, 0x10, 0x3b, 0x00, 0x3a, 0x00, 0x24, 0x31, 0x00, 0x00, + 0x3b, 0x00, 0x3a, 0x00, 0x00, 0x10, 0x27, 0x00, 0x22, 0x00, 0x27, 0x00, + 0x22, 0x00, 0x27, 0x00, 0x22, 0x00, 0x00, 0x10, 0x60, 0x00, 0x7e, 0x00, + 0x60, 0x00, 0x7e, 0x00, 0x60, 0x00, 0x7e, 0x00, 0x00, 0x10, 0x2c, 0x00, + 0x3c, 0x00, 0x1d, 0x31, 0x00, 0x00, 0x2c, 0x00, 0x3c, 0x00, 0x00, 0x10, + 0x2e, 0x00, 0x3e, 0x00, 0x21, 0x31, 0x00, 0x00, 0x2e, 0x00, 0x3e, 0x00, + 0x00, 0x10, 0x2f, 0x00, 0x3f, 0x00, 0x25, 0x31, 0x00, 0x00, 0x2f, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x00, + 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x2f, 0x00, 0x00, 0x10, + 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, + 0x00, 0x10, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, 0x2d, 0x00, + 0x2d, 0x00, 0x00, 0x10, 0x2b, 0x00, 0x2b, 0x00, 0x2b, 0x00, 0x2b, 0x00, + 0x2b, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x20, 0x00, 0x00, 0x31, 0x00, + 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x31, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x33, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x34, 0x00, 0xff, 0x20, 0x20, 0x00, 0x35, 0x00, 0x20, 0x00, 0x35, 0x00, + 0x20, 0x00, 0x35, 0x00, 0xff, 0x20, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, + 0x36, 0x00, 0x00, 0x00, 0x36, 0x00, 0xff, 0x20, 0x00, 0x00, 0x37, 0x00, + 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x37, 0x00, 0xff, 0x20, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x00, 0xff, 0x20, + 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, + 0xff, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x30, 0x00, 0xff, 0x20, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2e, 0x00, + 0x00, 0x00, 0x2e, 0x00, 0x00, 0x10, 0x5c, 0x00, 0x7c, 0x00, 0x5c, 0x00, + 0x7c, 0x00, 0x5c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + }; +} diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs b/src/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs new file mode 100644 index 00000000..b2d4d55c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs @@ -0,0 +1,1712 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Settings +{ + static class NxSettings + { + // Generated automatically from a Switch 3.0 config file (Tid: 0100000000000818). + public static Dictionary Settings = new() + { + { "account!na_required_for_network_service", true }, + { "account.daemon!background_awaking_periodicity", 10800 }, + { "account.daemon!schedule_periodicity", 3600 }, + { "account.daemon!profile_sync_interval", 18000 }, + { "account.daemon!na_info_refresh_interval", 46800 }, + { "am.display!transition_layer_enabled", true }, + { "am.gpu!gpu_scheduling_enabled", true }, + { "am.gpu!gpu_scheduling_frame_time_us", 116666 }, + { "am.gpu!gpu_scheduling_fg_app_us", 116166 }, + { "am.gpu!gpu_scheduling_bg_app_us", 104500 }, + { "am.gpu!gpu_scheduling_oa_us", 500 }, + { "am.gpu!gpu_scheduling_fg_sa_us", 11666 }, + { "am.gpu!gpu_scheduling_bg_sa_us", 0 }, + { "am.gpu!gpu_scheduling_fg_la_us", 11666 }, + { "am.gpu!gpu_scheduling_partial_fg_la_us", 2000 }, + { "am.gpu!gpu_scheduling_bg_la_us", 0 }, + { "audio!audren_log_enabled", false }, + { "audio!audout_log_enabled", false }, + { "audio!audin_log_enabled", false }, + { "audio!hwopus_log_enabled", false }, + { "audio!adsp_log_enabled", false }, + { "audio!suspend_for_debugger_enabled", false }, + { "audio!uac_speaker_enabled", false }, + { "bgtc!enable_halfawake", 1 }, + { "bgtc!enable_battery_saver", 1 }, + { "bgtc!leaving_halfawake_margin", 3 }, + { "bgtc!battery_threshold_save", 20 }, + { "bgtc!battery_threshold_stop", 20 }, + { "bgtc!minimum_interval_normal", 1800 }, + { "bgtc!minimum_interval_save", 86400 }, + { "boot!force_maintenance", false }, + { "capsrv!screenshot_layerstack", "screenshot" }, + { "capsrv!enable_album_screenshot_filedata_verification", true }, + { "devmenu!enable_application_update", true }, + { "devmenu!enable_exhibition_mode", false }, + { "eclct!analytics_override", false }, + { "eclct!analytics_pollperiod", 86400 }, + { "err!applet_auto_close", false }, + { "friends!background_processing", true }, + { "htc!disconnection_emulation", false }, + { "idle!dim_level_percent_lcd", 10 }, + { "idle!dim_level_percent_tv", 70 }, + { "lbl!force_disable_als", false }, + { "lm!enable_sd_card_logging", false }, + { "lm!sd_card_log_output_directory", "NxBinLogs" }, + { "mii!is_db_test_mode_enabled", false }, + { "news!system_version", 2 }, + { "nfp!not_locked_tag", true }, + { "nfp!play_report", false }, + { "nifm!is_communication_control_enabled_for_test", false }, + { "nifm!connection_test_timeout", 45000 }, + { "nifm!apply_config_timeout", 30000 }, + { "nifm!ethernet_adapter_standby_time", 10000 }, + { "nim.install!prefer_delta_evenif_inefficient", false }, + { "nim.install!apply_delta_stress_storage", 0 }, + { "ns.notification!retry_interval", 60 }, + { "ns.notification!enable_network_update", true }, + { "ns.notification!enable_download_task_list", true }, + { "ns.notification!enable_version_list", true }, + { "ns.notification!enable_random_wait", true }, + { "ns.notification!debug_waiting_limit", 0 }, + { "ns.notification!enable_request_on_cold_boot", true }, + { "ns.sdcard!mount_sdcard", true }, + { "ns.sdcard!compare_sdcard", 0 }, + { "ns.gamecard!mount_gamecard_result_value", 0 }, + { "ns.gamecard!try_gamecard_access_result_value", 0 }, + { "nv!00008600", "" }, + { "nv!0007b25e", "" }, + { "nv!0083e1", "" }, + { "nv!01621887", "" }, + { "nv!03134743", "" }, + { "nv!0356afd0", "" }, + { "nv!0356afd1", "" }, + { "nv!0356afd2", "" }, + { "nv!0356afd3", "" }, + { "nv!094313", "" }, + { "nv!0x04dc09", "" }, + { "nv!0x111133", "" }, + { "nv!0x1aa483", "" }, + { "nv!0x1cb1cf", "" }, + { "nv!0x1cb1d0", "" }, + { "nv!0x1e3221", "" }, + { "nv!0x300fc8", "" }, + { "nv!0x301fc8", "" }, + { "nv!0x302fc8", "" }, + { "nv!0x3eec59", "" }, + { "nv!0x46b3ed", "" }, + { "nv!0x523dc0", "" }, + { "nv!0x523dc1", "" }, + { "nv!0x523dc2", "" }, + { "nv!0x523dc3", "" }, + { "nv!0x523dc4", "" }, + { "nv!0x523dc5", "" }, + { "nv!0x523dc6", "" }, + { "nv!0x523dd0", "" }, + { "nv!0x523dd1", "" }, + { "nv!0x523dd3", "" }, + { "nv!0x5344bb", "" }, + { "nv!0x555237", "" }, + { "nv!0x58a234", "" }, + { "nv!0x7b4428", "" }, + { "nv!0x923dc0", "" }, + { "nv!0x923dc1", "" }, + { "nv!0x923dc2", "" }, + { "nv!0x923dc3", "" }, + { "nv!0x923dc4", "" }, + { "nv!0x923dd3", "" }, + { "nv!0x9abdc5", "" }, + { "nv!0x9abdc6", "" }, + { "nv!0xaaa36c", "" }, + { "nv!0xb09da0", "" }, + { "nv!0xb09da1", "" }, + { "nv!0xb09da2", "" }, + { "nv!0xb09da3", "" }, + { "nv!0xb09da4", "" }, + { "nv!0xb09da5", "" }, + { "nv!0xb0b348", "" }, + { "nv!0xb0b349", "" }, + { "nv!0xbb558f", "" }, + { "nv!0xbd10fb", "" }, + { "nv!0xc32ad3", "" }, + { "nv!0xce2348", "" }, + { "nv!0xcfd81f", "" }, + { "nv!0xe0036b", "" }, + { "nv!0xe01f2d", "" }, + { "nv!0xe17212", "" }, + { "nv!0xeae966", "" }, + { "nv!0xed4f82", "" }, + { "nv!0xf12335", "" }, + { "nv!0xf12336", "" }, + { "nv!10261989", "" }, + { "nv!1042d483", "" }, + { "nv!10572898", "" }, + { "nv!115631", "" }, + { "nv!12950094", "" }, + { "nv!1314f311", "" }, + { "nv!1314f312", "" }, + { "nv!13279512", "" }, + { "nv!13813496", "" }, + { "nv!14507179", "" }, + { "nv!15694569", "" }, + { "nv!16936964", "" }, + { "nv!17aa230c", "" }, + { "nv!182054", "" }, + { "nv!18273275", "" }, + { "nv!18273276", "" }, + { "nv!1854d03b", "" }, + { "nv!18add00d", "" }, + { "nv!19156670", "" }, + { "nv!19286545", "" }, + { "nv!1a298e9f", "" }, + { "nv!1acf43fe", "" }, + { "nv!1bda43fe", "" }, + { "nv!1c3b92", "" }, + { "nv!21509920", "" }, + { "nv!215323457", "" }, + { "nv!2165ad", "" }, + { "nv!2165ae", "" }, + { "nv!21be9c", "" }, + { "nv!233264316", "" }, + { "nv!234557580", "" }, + { "nv!23cd0e", "" }, + { "nv!24189123", "" }, + { "nv!2443266", "" }, + { "nv!25025519", "" }, + { "nv!255e39", "" }, + { "nv!2583364", "" }, + { "nv!2888c1", "" }, + { "nv!28ca3e", "" }, + { "nv!29871243", "" }, + { "nv!2a1f64", "" }, + { "nv!2dc432", "" }, + { "nv!2de437", "" }, + { "nv!2f3bb89c", "" }, + { "nv!2fd652", "" }, + { "nv!3001ac", "" }, + { "nv!31298772", "" }, + { "nv!313233", "" }, + { "nv!31f7d603", "" }, + { "nv!320ce4", "" }, + { "nv!32153248", "" }, + { "nv!32153249", "" }, + { "nv!335bca", "" }, + { "nv!342abb", "" }, + { "nv!34dfe6", "" }, + { "nv!34dfe7", "" }, + { "nv!34dfe8", "" }, + { "nv!34dfe9", "" }, + { "nv!35201578", "" }, + { "nv!359278", "" }, + { "nv!37f53a", "" }, + { "nv!38144972", "" }, + { "nv!38542646", "" }, + { "nv!3b74c9", "" }, + { "nv!3c136f", "" }, + { "nv!3cf72823", "" }, + { "nv!3d7af029", "" }, + { "nv!3ff34782", "" }, + { "nv!4129618", "" }, + { "nv!4189fac3", "" }, + { "nv!420bd4", "" }, + { "nv!42a699", "" }, + { "nv!441369", "" }, + { "nv!4458713e", "" }, + { "nv!4554b6", "" }, + { "nv!457425", "" }, + { "nv!4603b207", "" }, + { "nv!46574957", "" }, + { "nv!46574958", "" }, + { "nv!46813529", "" }, + { "nv!46f1e13d", "" }, + { "nv!47534c43", "" }, + { "nv!48550336", "" }, + { "nv!48576893", "" }, + { "nv!48576894", "" }, + { "nv!4889ac02", "" }, + { "nv!49005740", "" }, + { "nv!49867584", "" }, + { "nv!49960973", "" }, + { "nv!4a5341", "" }, + { "nv!4f4e48", "" }, + { "nv!4f8a0a", "" }, + { "nv!50299698", "" }, + { "nv!50299699", "" }, + { "nv!50361291", "" }, + { "nv!5242ae", "" }, + { "nv!53d30c", "" }, + { "nv!56347a", "" }, + { "nv!563a95f1", "" }, + { "nv!573823", "" }, + { "nv!58027529", "" }, + { "nv!5d2d63", "" }, + { "nv!5f7e3b", "" }, + { "nv!60461793", "" }, + { "nv!60d355", "" }, + { "nv!616627aa", "" }, + { "nv!62317182", "" }, + { "nv!6253fa2e", "" }, + { "nv!64100768", "" }, + { "nv!64100769", "" }, + { "nv!64100770", "" }, + { "nv!647395", "" }, + { "nv!66543234", "" }, + { "nv!67674763", "" }, + { "nv!67739784", "" }, + { "nv!68fb9c", "" }, + { "nv!69801276", "" }, + { "nv!6af9fa2f", "" }, + { "nv!6af9fa3f", "" }, + { "nv!6af9fa4f", "" }, + { "nv!6bd8c7", "" }, + { "nv!6c7691", "" }, + { "nv!6d4296ce", "" }, + { "nv!6dd7e7", "" }, + { "nv!6dd7e8", "" }, + { "nv!6fe11ec1", "" }, + { "nv!716511763", "" }, + { "nv!72504593", "" }, + { "nv!73304097", "" }, + { "nv!73314098", "" }, + { "nv!74095213", "" }, + { "nv!74095213a", "" }, + { "nv!74095213b", "" }, + { "nv!74095214", "" }, + { "nv!748f9649", "" }, + { "nv!75494732", "" }, + { "nv!78452832", "" }, + { "nv!784561", "" }, + { "nv!78e16b9c", "" }, + { "nv!79251225", "" }, + { "nv!7c128b", "" }, + { "nv!7ccd93", "" }, + { "nv!7df8d1", "" }, + { "nv!800c2310", "" }, + { "nv!80546710", "" }, + { "nv!80772310", "" }, + { "nv!808ee280", "" }, + { "nv!81131154", "" }, + { "nv!81274457", "" }, + { "nv!8292291f", "" }, + { "nv!83498426", "" }, + { "nv!84993794", "" }, + { "nv!84995585", "" }, + { "nv!84a0a0", "" }, + { "nv!852142", "" }, + { "nv!85612309", "" }, + { "nv!85612310", "" }, + { "nv!85612311", "" }, + { "nv!85612312", "" }, + { "nv!8623ff27", "" }, + { "nv!87364952", "" }, + { "nv!87f6275666", "" }, + { "nv!886748", "" }, + { "nv!89894423", "" }, + { "nv!8ad8a75", "" }, + { "nv!8ad8ad00", "" }, + { "nv!8bb815", "" }, + { "nv!8bb817", "" }, + { "nv!8bb818", "" }, + { "nv!8bb819", "" }, + { "nv!8e640cd1", "" }, + { "nv!8f34971a", "" }, + { "nv!8f773984", "" }, + { "nv!8f7a7d", "" }, + { "nv!902486209", "" }, + { "nv!90482571", "" }, + { "nv!91214835", "" }, + { "nv!912848290", "" }, + { "nv!915e56", "" }, + { "nv!92179063", "" }, + { "nv!92179064", "" }, + { "nv!92179065", "" }, + { "nv!92179066", "" }, + { "nv!92350358", "" }, + { "nv!92809063", "" }, + { "nv!92809064", "" }, + { "nv!92809065", "" }, + { "nv!92809066", "" }, + { "nv!92920143", "" }, + { "nv!93a89b12", "" }, + { "nv!93a89c0b", "" }, + { "nv!94812574", "" }, + { "nv!95282304", "" }, + { "nv!95394027", "" }, + { "nv!959b1f", "" }, + { "nv!9638af", "" }, + { "nv!96fd59", "" }, + { "nv!97f6275666", "" }, + { "nv!97f6275667", "" }, + { "nv!97f6275668", "" }, + { "nv!97f6275669", "" }, + { "nv!97f627566a", "" }, + { "nv!97f627566b", "" }, + { "nv!97f627566d", "" }, + { "nv!97f627566e", "" }, + { "nv!97f627566f", "" }, + { "nv!97f6275670", "" }, + { "nv!97f6275671", "" }, + { "nv!97f727566e", "" }, + { "nv!98480775", "" }, + { "nv!98480776", "" }, + { "nv!98480777", "" }, + { "nv!992431", "" }, + { "nv!9aa29065", "" }, + { "nv!9af32c", "" }, + { "nv!9af32d", "" }, + { "nv!9af32e", "" }, + { "nv!9c108b71", "" }, + { "nv!9f279065", "" }, + { "nv!a01bc728", "" }, + { "nv!a13b46c80", "" }, + { "nv!a22eb0", "" }, + { "nv!a2fb451e", "" }, + { "nv!a3456abe", "" }, + { "nv!a7044887", "" }, + { "nv!a7149200", "" }, + { "nv!a766215670", "" }, + { "nv!aac_drc_boost", "" }, + { "nv!aac_drc_cut", "" }, + { "nv!aac_drc_enc_target_level", "" }, + { "nv!aac_drc_heavy", "" }, + { "nv!aac_drc_reference_level", "" }, + { "nv!aalinegamma", "" }, + { "nv!aalinetweaks", "" }, + { "nv!ab34ee01", "" }, + { "nv!ab34ee02", "" }, + { "nv!ab34ee03", "" }, + { "nv!ac0274", "" }, + { "nv!af73c63e", "" }, + { "nv!af73c63f", "" }, + { "nv!af9927", "" }, + { "nv!afoverride", "" }, + { "nv!allocdeviceevents", "" }, + { "nv!applicationkey", "" }, + { "nv!appreturnonlybasicglsltype", "" }, + { "nv!app_softimage", "" }, + { "nv!app_supportbits2", "" }, + { "nv!assumetextureismipmappedatcreation", "" }, + { "nv!b1fb0f01", "" }, + { "nv!b3edd5", "" }, + { "nv!b40d9e03d", "" }, + { "nv!b7f6275666", "" }, + { "nv!b812c1", "" }, + { "nv!ba14ba1a", "" }, + { "nv!ba14ba1b", "" }, + { "nv!bd7559", "" }, + { "nv!bd755a", "" }, + { "nv!bd755c", "" }, + { "nv!bd755d", "" }, + { "nv!be58bb", "" }, + { "nv!be92cb", "" }, + { "nv!beefcba3", "" }, + { "nv!beefcba4", "" }, + { "nv!c023777f", "" }, + { "nv!c09dc8", "" }, + { "nv!c0d340", "" }, + { "nv!c2ff374c", "" }, + { "nv!c5e9d7a3", "" }, + { "nv!c5e9d7a4", "" }, + { "nv!c5e9d7b4", "" }, + { "nv!c618f9", "" }, + { "nv!ca345840", "" }, + { "nv!cachedisable", "" }, + { "nv!cast.on", "" }, + { "nv!cde", "" }, + { "nv!channelpriorityoverride", "" }, + { "nv!cleardatastorevidmem", "" }, + { "nv!cmdbufmemoryspaceenables", "" }, + { "nv!cmdbufminwords", "" }, + { "nv!cmdbufsizewords", "" }, + { "nv!conformantblitframebufferscissor", "" }, + { "nv!conformantincompletetextures", "" }, + { "nv!copybuffermethod", "" }, + { "nv!cubemapaniso", "" }, + { "nv!cubemapfiltering", "" }, + { "nv!d0e9a4d7", "" }, + { "nv!d13733f12", "" }, + { "nv!d1b399", "" }, + { "nv!d2983c32", "" }, + { "nv!d2983c33", "" }, + { "nv!d2e71b", "" }, + { "nv!d377dc", "" }, + { "nv!d377dd", "" }, + { "nv!d489f4", "" }, + { "nv!d4bce1", "" }, + { "nv!d518cb", "" }, + { "nv!d518cd", "" }, + { "nv!d518ce", "" }, + { "nv!d518d0", "" }, + { "nv!d518d1", "" }, + { "nv!d518d2", "" }, + { "nv!d518d3", "" }, + { "nv!d518d4", "" }, + { "nv!d518d5", "" }, + { "nv!d59eda", "" }, + { "nv!d83cbd", "" }, + { "nv!d8e777", "" }, + { "nv!debug_level", "" }, + { "nv!debug_mask", "" }, + { "nv!debug_options", "" }, + { "nv!devshmpageableallocations", "" }, + { "nv!df1f9812", "" }, + { "nv!df783c", "" }, + { "nv!diagenable", "" }, + { "nv!disallowcemask", "" }, + { "nv!disallowz16", "" }, + { "nv!dlmemoryspaceenables", "" }, + { "nv!e0bfec", "" }, + { "nv!e433456d", "" }, + { "nv!e435563f", "" }, + { "nv!e4cd9c", "" }, + { "nv!e5c972", "" }, + { "nv!e639ef", "" }, + { "nv!e802af", "" }, + { "nv!eae964", "" }, + { "nv!earlytexturehwallocation", "" }, + { "nv!eb92a3", "" }, + { "nv!ebca56", "" }, + { "nv!enable-noaud", "" }, + { "nv!enable-noavs", "" }, + { "nv!enable-prof", "" }, + { "nv!enable-sxesmode", "" }, + { "nv!enable-ulld", "" }, + { "nv!expert_detail_level", "" }, + { "nv!expert_output_mask", "" }, + { "nv!expert_report_mask", "" }, + { "nv!extensionstringnvarch", "" }, + { "nv!extensionstringversion", "" }, + { "nv!f00f1938", "" }, + { "nv!f10736", "" }, + { "nv!f1846870", "" }, + { "nv!f33bc370", "" }, + { "nv!f392a874", "" }, + { "nv!f49ae8", "" }, + { "nv!fa345cce", "" }, + { "nv!fa35cc4", "" }, + { "nv!faa14a", "" }, + { "nv!faf8a723", "" }, + { "nv!fastgs", "" }, + { "nv!fbf4ac45", "" }, + { "nv!fbo_blit_ignore_srgb", "" }, + { "nv!fc64c7", "" }, + { "nv!ff54ec97", "" }, + { "nv!ff54ec98", "" }, + { "nv!forceexitprocessdetach", "" }, + { "nv!forcerequestedesversion", "" }, + { "nv!__gl_", "" }, + { "nv!__gl_00008600", "" }, + { "nv!__gl_0007b25e", "" }, + { "nv!__gl_0083e1", "" }, + { "nv!__gl_01621887", "" }, + { "nv!__gl_03134743", "" }, + { "nv!__gl_0356afd0", "" }, + { "nv!__gl_0356afd1", "" }, + { "nv!__gl_0356afd2", "" }, + { "nv!__gl_0356afd3", "" }, + { "nv!__gl_094313", "" }, + { "nv!__gl_0x04dc09", "" }, + { "nv!__gl_0x111133", "" }, + { "nv!__gl_0x1aa483", "" }, + { "nv!__gl_0x1cb1cf", "" }, + { "nv!__gl_0x1cb1d0", "" }, + { "nv!__gl_0x1e3221", "" }, + { "nv!__gl_0x300fc8", "" }, + { "nv!__gl_0x301fc8", "" }, + { "nv!__gl_0x302fc8", "" }, + { "nv!__gl_0x3eec59", "" }, + { "nv!__gl_0x46b3ed", "" }, + { "nv!__gl_0x523dc0", "" }, + { "nv!__gl_0x523dc1", "" }, + { "nv!__gl_0x523dc2", "" }, + { "nv!__gl_0x523dc3", "" }, + { "nv!__gl_0x523dc4", "" }, + { "nv!__gl_0x523dc5", "" }, + { "nv!__gl_0x523dc6", "" }, + { "nv!__gl_0x523dd0", "" }, + { "nv!__gl_0x523dd1", "" }, + { "nv!__gl_0x523dd3", "" }, + { "nv!__gl_0x5344bb", "" }, + { "nv!__gl_0x555237", "" }, + { "nv!__gl_0x58a234", "" }, + { "nv!__gl_0x7b4428", "" }, + { "nv!__gl_0x923dc0", "" }, + { "nv!__gl_0x923dc1", "" }, + { "nv!__gl_0x923dc2", "" }, + { "nv!__gl_0x923dc3", "" }, + { "nv!__gl_0x923dc4", "" }, + { "nv!__gl_0x923dd3", "" }, + { "nv!__gl_0x9abdc5", "" }, + { "nv!__gl_0x9abdc6", "" }, + { "nv!__gl_0xaaa36c", "" }, + { "nv!__gl_0xb09da0", "" }, + { "nv!__gl_0xb09da1", "" }, + { "nv!__gl_0xb09da2", "" }, + { "nv!__gl_0xb09da3", "" }, + { "nv!__gl_0xb09da4", "" }, + { "nv!__gl_0xb09da5", "" }, + { "nv!__gl_0xb0b348", "" }, + { "nv!__gl_0xb0b349", "" }, + { "nv!__gl_0xbb558f", "" }, + { "nv!__gl_0xbd10fb", "" }, + { "nv!__gl_0xc32ad3", "" }, + { "nv!__gl_0xce2348", "" }, + { "nv!__gl_0xcfd81f", "" }, + { "nv!__gl_0xe0036b", "" }, + { "nv!__gl_0xe01f2d", "" }, + { "nv!__gl_0xe17212", "" }, + { "nv!__gl_0xeae966", "" }, + { "nv!__gl_0xed4f82", "" }, + { "nv!__gl_0xf12335", "" }, + { "nv!__gl_0xf12336", "" }, + { "nv!__gl_10261989", "" }, + { "nv!__gl_1042d483", "" }, + { "nv!__gl_10572898", "" }, + { "nv!__gl_115631", "" }, + { "nv!__gl_12950094", "" }, + { "nv!__gl_1314f311", "" }, + { "nv!__gl_1314f312", "" }, + { "nv!__gl_13279512", "" }, + { "nv!__gl_13813496", "" }, + { "nv!__gl_14507179", "" }, + { "nv!__gl_15694569", "" }, + { "nv!__gl_16936964", "" }, + { "nv!__gl_17aa230c", "" }, + { "nv!__gl_182054", "" }, + { "nv!__gl_18273275", "" }, + { "nv!__gl_18273276", "" }, + { "nv!__gl_1854d03b", "" }, + { "nv!__gl_18add00d", "" }, + { "nv!__gl_19156670", "" }, + { "nv!__gl_19286545", "" }, + { "nv!__gl_1a298e9f", "" }, + { "nv!__gl_1acf43fe", "" }, + { "nv!__gl_1bda43fe", "" }, + { "nv!__gl_1c3b92", "" }, + { "nv!__gl_21509920", "" }, + { "nv!__gl_215323457", "" }, + { "nv!__gl_2165ad", "" }, + { "nv!__gl_2165ae", "" }, + { "nv!__gl_21be9c", "" }, + { "nv!__gl_233264316", "" }, + { "nv!__gl_234557580", "" }, + { "nv!__gl_23cd0e", "" }, + { "nv!__gl_24189123", "" }, + { "nv!__gl_2443266", "" }, + { "nv!__gl_25025519", "" }, + { "nv!__gl_255e39", "" }, + { "nv!__gl_2583364", "" }, + { "nv!__gl_2888c1", "" }, + { "nv!__gl_28ca3e", "" }, + { "nv!__gl_29871243", "" }, + { "nv!__gl_2a1f64", "" }, + { "nv!__gl_2dc432", "" }, + { "nv!__gl_2de437", "" }, + { "nv!__gl_2f3bb89c", "" }, + { "nv!__gl_2fd652", "" }, + { "nv!__gl_3001ac", "" }, + { "nv!__gl_31298772", "" }, + { "nv!__gl_313233", "" }, + { "nv!__gl_31f7d603", "" }, + { "nv!__gl_320ce4", "" }, + { "nv!__gl_32153248", "" }, + { "nv!__gl_32153249", "" }, + { "nv!__gl_335bca", "" }, + { "nv!__gl_342abb", "" }, + { "nv!__gl_34dfe6", "" }, + { "nv!__gl_34dfe7", "" }, + { "nv!__gl_34dfe8", "" }, + { "nv!__gl_34dfe9", "" }, + { "nv!__gl_35201578", "" }, + { "nv!__gl_359278", "" }, + { "nv!__gl_37f53a", "" }, + { "nv!__gl_38144972", "" }, + { "nv!__gl_38542646", "" }, + { "nv!__gl_3b74c9", "" }, + { "nv!__gl_3c136f", "" }, + { "nv!__gl_3cf72823", "" }, + { "nv!__gl_3d7af029", "" }, + { "nv!__gl_3ff34782", "" }, + { "nv!__gl_4129618", "" }, + { "nv!__gl_4189fac3", "" }, + { "nv!__gl_420bd4", "" }, + { "nv!__gl_42a699", "" }, + { "nv!__gl_441369", "" }, + { "nv!__gl_4458713e", "" }, + { "nv!__gl_4554b6", "" }, + { "nv!__gl_457425", "" }, + { "nv!__gl_4603b207", "" }, + { "nv!__gl_46574957", "" }, + { "nv!__gl_46574958", "" }, + { "nv!__gl_46813529", "" }, + { "nv!__gl_46f1e13d", "" }, + { "nv!__gl_47534c43", "" }, + { "nv!__gl_48550336", "" }, + { "nv!__gl_48576893", "" }, + { "nv!__gl_48576894", "" }, + { "nv!__gl_4889ac02", "" }, + { "nv!__gl_49005740", "" }, + { "nv!__gl_49867584", "" }, + { "nv!__gl_49960973", "" }, + { "nv!__gl_4a5341", "" }, + { "nv!__gl_4f4e48", "" }, + { "nv!__gl_4f8a0a", "" }, + { "nv!__gl_50299698", "" }, + { "nv!__gl_50299699", "" }, + { "nv!__gl_50361291", "" }, + { "nv!__gl_5242ae", "" }, + { "nv!__gl_53d30c", "" }, + { "nv!__gl_56347a", "" }, + { "nv!__gl_563a95f1", "" }, + { "nv!__gl_573823", "" }, + { "nv!__gl_58027529", "" }, + { "nv!__gl_5d2d63", "" }, + { "nv!__gl_5f7e3b", "" }, + { "nv!__gl_60461793", "" }, + { "nv!__gl_60d355", "" }, + { "nv!__gl_616627aa", "" }, + { "nv!__gl_62317182", "" }, + { "nv!__gl_6253fa2e", "" }, + { "nv!__gl_64100768", "" }, + { "nv!__gl_64100769", "" }, + { "nv!__gl_64100770", "" }, + { "nv!__gl_647395", "" }, + { "nv!__gl_66543234", "" }, + { "nv!__gl_67674763", "" }, + { "nv!__gl_67739784", "" }, + { "nv!__gl_68fb9c", "" }, + { "nv!__gl_69801276", "" }, + { "nv!__gl_6af9fa2f", "" }, + { "nv!__gl_6af9fa3f", "" }, + { "nv!__gl_6af9fa4f", "" }, + { "nv!__gl_6bd8c7", "" }, + { "nv!__gl_6c7691", "" }, + { "nv!__gl_6d4296ce", "" }, + { "nv!__gl_6dd7e7", "" }, + { "nv!__gl_6dd7e8", "" }, + { "nv!__gl_6fe11ec1", "" }, + { "nv!__gl_716511763", "" }, + { "nv!__gl_72504593", "" }, + { "nv!__gl_73304097", "" }, + { "nv!__gl_73314098", "" }, + { "nv!__gl_74095213", "" }, + { "nv!__gl_74095213a", "" }, + { "nv!__gl_74095213b", "" }, + { "nv!__gl_74095214", "" }, + { "nv!__gl_748f9649", "" }, + { "nv!__gl_75494732", "" }, + { "nv!__gl_78452832", "" }, + { "nv!__gl_784561", "" }, + { "nv!__gl_78e16b9c", "" }, + { "nv!__gl_79251225", "" }, + { "nv!__gl_7c128b", "" }, + { "nv!__gl_7ccd93", "" }, + { "nv!__gl_7df8d1", "" }, + { "nv!__gl_800c2310", "" }, + { "nv!__gl_80546710", "" }, + { "nv!__gl_80772310", "" }, + { "nv!__gl_808ee280", "" }, + { "nv!__gl_81131154", "" }, + { "nv!__gl_81274457", "" }, + { "nv!__gl_8292291f", "" }, + { "nv!__gl_83498426", "" }, + { "nv!__gl_84993794", "" }, + { "nv!__gl_84995585", "" }, + { "nv!__gl_84a0a0", "" }, + { "nv!__gl_852142", "" }, + { "nv!__gl_85612309", "" }, + { "nv!__gl_85612310", "" }, + { "nv!__gl_85612311", "" }, + { "nv!__gl_85612312", "" }, + { "nv!__gl_8623ff27", "" }, + { "nv!__gl_87364952", "" }, + { "nv!__gl_87f6275666", "" }, + { "nv!__gl_886748", "" }, + { "nv!__gl_89894423", "" }, + { "nv!__gl_8ad8a75", "" }, + { "nv!__gl_8ad8ad00", "" }, + { "nv!__gl_8bb815", "" }, + { "nv!__gl_8bb817", "" }, + { "nv!__gl_8bb818", "" }, + { "nv!__gl_8bb819", "" }, + { "nv!__gl_8e640cd1", "" }, + { "nv!__gl_8f34971a", "" }, + { "nv!__gl_8f773984", "" }, + { "nv!__gl_8f7a7d", "" }, + { "nv!__gl_902486209", "" }, + { "nv!__gl_90482571", "" }, + { "nv!__gl_91214835", "" }, + { "nv!__gl_912848290", "" }, + { "nv!__gl_915e56", "" }, + { "nv!__gl_92179063", "" }, + { "nv!__gl_92179064", "" }, + { "nv!__gl_92179065", "" }, + { "nv!__gl_92179066", "" }, + { "nv!__gl_92350358", "" }, + { "nv!__gl_92809063", "" }, + { "nv!__gl_92809064", "" }, + { "nv!__gl_92809065", "" }, + { "nv!__gl_92809066", "" }, + { "nv!__gl_92920143", "" }, + { "nv!__gl_93a89b12", "" }, + { "nv!__gl_93a89c0b", "" }, + { "nv!__gl_94812574", "" }, + { "nv!__gl_95282304", "" }, + { "nv!__gl_95394027", "" }, + { "nv!__gl_959b1f", "" }, + { "nv!__gl_9638af", "" }, + { "nv!__gl_96fd59", "" }, + { "nv!__gl_97f6275666", "" }, + { "nv!__gl_97f6275667", "" }, + { "nv!__gl_97f6275668", "" }, + { "nv!__gl_97f6275669", "" }, + { "nv!__gl_97f627566a", "" }, + { "nv!__gl_97f627566b", "" }, + { "nv!__gl_97f627566d", "" }, + { "nv!__gl_97f627566e", "" }, + { "nv!__gl_97f627566f", "" }, + { "nv!__gl_97f6275670", "" }, + { "nv!__gl_97f6275671", "" }, + { "nv!__gl_97f727566e", "" }, + { "nv!__gl_98480775", "" }, + { "nv!__gl_98480776", "" }, + { "nv!__gl_98480777", "" }, + { "nv!__gl_992431", "" }, + { "nv!__gl_9aa29065", "" }, + { "nv!__gl_9af32c", "" }, + { "nv!__gl_9af32d", "" }, + { "nv!__gl_9af32e", "" }, + { "nv!__gl_9c108b71", "" }, + { "nv!__gl_9f279065", "" }, + { "nv!__gl_a01bc728", "" }, + { "nv!__gl_a13b46c80", "" }, + { "nv!__gl_a22eb0", "" }, + { "nv!__gl_a2fb451e", "" }, + { "nv!__gl_a3456abe", "" }, + { "nv!__gl_a7044887", "" }, + { "nv!__gl_a7149200", "" }, + { "nv!__gl_a766215670", "" }, + { "nv!__gl_aalinegamma", "" }, + { "nv!__gl_aalinetweaks", "" }, + { "nv!__gl_ab34ee01", "" }, + { "nv!__gl_ab34ee02", "" }, + { "nv!__gl_ab34ee03", "" }, + { "nv!__gl_ac0274", "" }, + { "nv!__gl_af73c63e", "" }, + { "nv!__gl_af73c63f", "" }, + { "nv!__gl_af9927", "" }, + { "nv!__gl_afoverride", "" }, + { "nv!__gl_allocdeviceevents", "" }, + { "nv!__gl_applicationkey", "" }, + { "nv!__gl_appreturnonlybasicglsltype", "" }, + { "nv!__gl_app_softimage", "" }, + { "nv!__gl_app_supportbits2", "" }, + { "nv!__gl_assumetextureismipmappedatcreation", "" }, + { "nv!__gl_b1fb0f01", "" }, + { "nv!__gl_b3edd5", "" }, + { "nv!__gl_b40d9e03d", "" }, + { "nv!__gl_b7f6275666", "" }, + { "nv!__gl_b812c1", "" }, + { "nv!__gl_ba14ba1a", "" }, + { "nv!__gl_ba14ba1b", "" }, + { "nv!__gl_bd7559", "" }, + { "nv!__gl_bd755a", "" }, + { "nv!__gl_bd755c", "" }, + { "nv!__gl_bd755d", "" }, + { "nv!__gl_be58bb", "" }, + { "nv!__gl_be92cb", "" }, + { "nv!__gl_beefcba3", "" }, + { "nv!__gl_beefcba4", "" }, + { "nv!__gl_c023777f", "" }, + { "nv!__gl_c09dc8", "" }, + { "nv!__gl_c0d340", "" }, + { "nv!__gl_c2ff374c", "" }, + { "nv!__gl_c5e9d7a3", "" }, + { "nv!__gl_c5e9d7a4", "" }, + { "nv!__gl_c5e9d7b4", "" }, + { "nv!__gl_c618f9", "" }, + { "nv!__gl_ca345840", "" }, + { "nv!__gl_cachedisable", "" }, + { "nv!__gl_channelpriorityoverride", "" }, + { "nv!__gl_cleardatastorevidmem", "" }, + { "nv!__gl_cmdbufmemoryspaceenables", "" }, + { "nv!__gl_cmdbufminwords", "" }, + { "nv!__gl_cmdbufsizewords", "" }, + { "nv!__gl_conformantblitframebufferscissor", "" }, + { "nv!__gl_conformantincompletetextures", "" }, + { "nv!__gl_copybuffermethod", "" }, + { "nv!__gl_cubemapaniso", "" }, + { "nv!__gl_cubemapfiltering", "" }, + { "nv!__gl_d0e9a4d7", "" }, + { "nv!__gl_d13733f12", "" }, + { "nv!__gl_d1b399", "" }, + { "nv!__gl_d2983c32", "" }, + { "nv!__gl_d2983c33", "" }, + { "nv!__gl_d2e71b", "" }, + { "nv!__gl_d377dc", "" }, + { "nv!__gl_d377dd", "" }, + { "nv!__gl_d489f4", "" }, + { "nv!__gl_d4bce1", "" }, + { "nv!__gl_d518cb", "" }, + { "nv!__gl_d518cd", "" }, + { "nv!__gl_d518ce", "" }, + { "nv!__gl_d518d0", "" }, + { "nv!__gl_d518d1", "" }, + { "nv!__gl_d518d2", "" }, + { "nv!__gl_d518d3", "" }, + { "nv!__gl_d518d4", "" }, + { "nv!__gl_d518d5", "" }, + { "nv!__gl_d59eda", "" }, + { "nv!__gl_d83cbd", "" }, + { "nv!__gl_d8e777", "" }, + { "nv!__gl_debug_level", "" }, + { "nv!__gl_debug_mask", "" }, + { "nv!__gl_debug_options", "" }, + { "nv!__gl_devshmpageableallocations", "" }, + { "nv!__gl_df1f9812", "" }, + { "nv!__gl_df783c", "" }, + { "nv!__gl_diagenable", "" }, + { "nv!__gl_disallowcemask", "" }, + { "nv!__gl_disallowz16", "" }, + { "nv!__gl_dlmemoryspaceenables", "" }, + { "nv!__gl_e0bfec", "" }, + { "nv!__gl_e433456d", "" }, + { "nv!__gl_e435563f", "" }, + { "nv!__gl_e4cd9c", "" }, + { "nv!__gl_e5c972", "" }, + { "nv!__gl_e639ef", "" }, + { "nv!__gl_e802af", "" }, + { "nv!__gl_eae964", "" }, + { "nv!__gl_earlytexturehwallocation", "" }, + { "nv!__gl_eb92a3", "" }, + { "nv!__gl_ebca56", "" }, + { "nv!__gl_expert_detail_level", "" }, + { "nv!__gl_expert_output_mask", "" }, + { "nv!__gl_expert_report_mask", "" }, + { "nv!__gl_extensionstringnvarch", "" }, + { "nv!__gl_extensionstringversion", "" }, + { "nv!__gl_f00f1938", "" }, + { "nv!__gl_f10736", "" }, + { "nv!__gl_f1846870", "" }, + { "nv!__gl_f33bc370", "" }, + { "nv!__gl_f392a874", "" }, + { "nv!__gl_f49ae8", "" }, + { "nv!__gl_fa345cce", "" }, + { "nv!__gl_fa35cc4", "" }, + { "nv!__gl_faa14a", "" }, + { "nv!__gl_faf8a723", "" }, + { "nv!__gl_fastgs", "" }, + { "nv!__gl_fbf4ac45", "" }, + { "nv!__gl_fbo_blit_ignore_srgb", "" }, + { "nv!__gl_fc64c7", "" }, + { "nv!__gl_ff54ec97", "" }, + { "nv!__gl_ff54ec98", "" }, + { "nv!__gl_forceexitprocessdetach", "" }, + { "nv!__gl_forcerequestedesversion", "" }, + { "nv!__gl_glsynctovblank", "" }, + { "nv!__gl_gvitimeoutcontrol", "" }, + { "nv!__gl_hcctrl", "" }, + { "nv!__gl_hwstate_per_ctx", "" }, + { "nv!__gl_machinecachelimit", "" }, + { "nv!__gl_maxframesallowed", "" }, + { "nv!__gl_memmgrcachedalloclimit", "" }, + { "nv!__gl_memmgrcachedalloclimitratio", "" }, + { "nv!__gl_memmgrsysheapalloclimit", "" }, + { "nv!__gl_memmgrsysheapalloclimitratio", "" }, + { "nv!__gl_memmgrvidheapalloclimit", "" }, + { "nv!__gl_mosaic_clip_to_subdev", "" }, + { "nv!__gl_mosaic_clip_to_subdev_h_overlap", "" }, + { "nv!__gl_mosaic_clip_to_subdev_v_overlap", "" }, + { "nv!__gl_overlaymergeblittimerms", "" }, + { "nv!__gl_perfmon_mode", "" }, + { "nv!__gl_pixbar_mode", "" }, + { "nv!__gl_qualityenhancements", "" }, + { "nv!__gl_r27s18q28", "" }, + { "nv!__gl_r2d7c1d8", "" }, + { "nv!__gl_renderer", "" }, + { "nv!__gl_renderqualityflags", "" }, + { "nv!__gl_s3tcquality", "" }, + { "nv!__gl_shaderatomics", "" }, + { "nv!__gl_shadercacheinitsize", "" }, + { "nv!__gl_shader_disk_cache_path", "" }, + { "nv!__gl_shader_disk_cache_read_only", "" }, + { "nv!__gl_shaderobjects", "" }, + { "nv!__gl_shaderportabilitywarnings", "" }, + { "nv!__gl_shaderwarningsaserrors", "" }, + { "nv!__gl_skiptexturehostcopies", "" }, + { "nv!__glslc_debug_level", "" }, + { "nv!__glslc_debug_mask", "" }, + { "nv!__glslc_debug_options", "" }, + { "nv!__glslc_debug_filename", "" }, + { "nv!__gl_sli_dli_control", "" }, + { "nv!__gl_sparsetexture", "" }, + { "nv!__gl_spinlooptimeout", "" }, + { "nv!__gl_sync_to_vblank", "" }, + { "nv!glsynctovblank", "" }, + { "nv!__gl_sysheapreuseratio", "" }, + { "nv!__gl_sysmemtexturepromotion", "" }, + { "nv!__gl_targetflushcount", "" }, + { "nv!__gl_tearingfreeswappresent", "" }, + { "nv!__gl_texclampbehavior", "" }, + { "nv!__gl_texlodbias", "" }, + { "nv!__gl_texmemoryspaceenables", "" }, + { "nv!__gl_textureprecache", "" }, + { "nv!__gl_threadcontrol", "" }, + { "nv!__gl_threadcontrol2", "" }, + { "nv!__gl_usegvievents", "" }, + { "nv!__gl_vbomemoryspaceenables", "" }, + { "nv!__gl_vertexlimit", "" }, + { "nv!__gl_vidheapreuseratio", "" }, + { "nv!__gl_vpipe", "" }, + { "nv!__gl_vpipeformatbloatlimit", "" }, + { "nv!__gl_wglmessageboxonabort", "" }, + { "nv!__gl_writeinfolog", "" }, + { "nv!__gl_writeprogramobjectassembly", "" }, + { "nv!__gl_writeprogramobjectsource", "" }, + { "nv!__gl_xnvadapterpresent", "" }, + { "nv!__gl_yield", "" }, + { "nv!__gl_yieldfunction", "" }, + { "nv!__gl_yieldfunctionfast", "" }, + { "nv!__gl_yieldfunctionslow", "" }, + { "nv!__gl_yieldfunctionwaitfordcqueue", "" }, + { "nv!__gl_yieldfunctionwaitforframe", "" }, + { "nv!__gl_yieldfunctionwaitforgpu", "" }, + { "nv!__gl_zbctableaddhysteresis", "" }, + { "nv!gpu_debug_mode", "" }, + { "nv!gpu_stay_on", "" }, + { "nv!gpu_timeout_ms_max", "" }, + { "nv!gvitimeoutcontrol", "" }, + { "nv!hcctrl", "" }, + { "nv!hwstate_per_ctx", "" }, + { "nv!libandroid_enable_log", "" }, + { "nv!machinecachelimit", "" }, + { "nv!maxframesallowed", "" }, + { "nv!media.aac_51_output_enabled", "" }, + { "nv!memmgrcachedalloclimit", "" }, + { "nv!memmgrcachedalloclimitratio", "" }, + { "nv!memmgrsysheapalloclimit", "" }, + { "nv!memmgrsysheapalloclimitratio", "" }, + { "nv!memmgrvidheapalloclimit", "" }, + { "nv!mosaic_clip_to_subdev", "" }, + { "nv!mosaic_clip_to_subdev_h_overlap", "" }, + { "nv!mosaic_clip_to_subdev_v_overlap", "" }, + { "nv!nvblit.dump", "" }, + { "nv!nvblit.profile", "" }, + { "nv!nvblit.twod", "" }, + { "nv!nvblit.vic", "" }, + { "nv!nvddk_vic_prevent_use", "" }, + { "nv!nv_decompression", "" }, + { "nv!nvdisp_bl_ctrl", "0" }, + { "nv!nvdisp_debug_mask", "" }, + { "nv!nvdisp_enable_ts", "0" }, + { "nv!nvhdcp_timeout_ms", "12000" }, + { "nv!nvhdcp_max_retries", "5" }, + { "nv!nv_emc_dvfs_test", "" }, + { "nv!nv_emc_init_rate_hz", "" }, + { "nv!nv_gmmu_va_page_split", "" }, + { "nv!nv_gmmu_va_range", "" }, + { "nv!nvhost_debug_mask", "" }, + { "nv!nvidia.hwc.dump_config", "" }, + { "nv!nvidia.hwc.dump_layerlist", "" }, + { "nv!nvidia.hwc.dump_windows", "" }, + { "nv!nvidia.hwc.enable_disp_trans", "" }, + { "nv!nvidia.hwc.ftrace_enable", "" }, + { "nv!nvidia.hwc.hdcp_enable", "" }, + { "nv!nvidia.hwc.hidden_window_mask0", "" }, + { "nv!nvidia.hwc.hidden_window_mask1", "" }, + { "nv!nvidia.hwc.immediate_modeset", "" }, + { "nv!nvidia.hwc.imp_enable", "" }, + { "nv!nvidia.hwc.no_egl", "" }, + { "nv!nvidia.hwc.no_scratchblit", "" }, + { "nv!nvidia.hwc.no_vic", "" }, + { "nv!nvidia.hwc.null_display", "" }, + { "nv!nvidia.hwc.scan_props", "" }, + { "nv!nvidia.hwc.swap_interval", "" }, + { "nv!nvidia.hwc.war_1515812", "0" }, + { "nv!nvmap_debug_mask", "" }, + { "nv!nv_memory_profiler", "" }, + { "nv!nvnflinger_enable_log", "" }, + { "nv!nvnflinger_flip_policy", "" }, + { "nv!nvnflinger_hotplug_autoswitch", "0" }, + { "nv!nvnflinger_prefer_primary_layer", "0" }, + { "nv!nvnflinger_service_priority", "" }, + { "nv!nvnflinger_service_threads", "" }, + { "nv!nvnflinger_swap_interval", "" }, + { "nv!nvnflinger_track_perf", "" }, + { "nv!nvnflinger_virtualdisplay_policy", "60hz" }, + { "nv!nvn_no_vsync_capability", false }, + { "nv!nvn_through_opengl", "" }, + { "nv!nv_pllcx_always_on", "" }, + { "nv!nv_pllcx_safe_div", "" }, + { "nv!nvrm_gpu_channel_interleave", "" }, + { "nv!nvrm_gpu_channel_priority", "" }, + { "nv!nvrm_gpu_channel_timeslice", "" }, + { "nv!nvrm_gpu_default_device_index", "" }, + { "nv!nvrm_gpu_dummy", "" }, + { "nv!nvrm_gpu_help", "" }, + { "nv!nvrm_gpu_nvgpu_disable", "" }, + { "nv!nvrm_gpu_nvgpu_do_nfa_partial_map", "" }, + { "nv!nvrm_gpu_nvgpu_ecc_overrides", "" }, + { "nv!nvrm_gpu_nvgpu_no_as_get_va_regions", "" }, + { "nv!nvrm_gpu_nvgpu_no_channel_abort", "" }, + { "nv!nvrm_gpu_nvgpu_no_cyclestats", "" }, + { "nv!nvrm_gpu_nvgpu_no_fixed", "" }, + { "nv!nvrm_gpu_nvgpu_no_gpu_characteristics", "" }, + { "nv!nvrm_gpu_nvgpu_no_ioctl_mutex", "" }, + { "nv!nvrm_gpu_nvgpu_no_map_buffer_ex", "" }, + { "nv!nvrm_gpu_nvgpu_no_robustness", "" }, + { "nv!nvrm_gpu_nvgpu_no_sparse", "" }, + { "nv!nvrm_gpu_nvgpu_no_syncpoints", "" }, + { "nv!nvrm_gpu_nvgpu_no_tsg", "" }, + { "nv!nvrm_gpu_nvgpu_no_zbc", "" }, + { "nv!nvrm_gpu_nvgpu_no_zcull", "" }, + { "nv!nvrm_gpu_nvgpu_wrap_channels_in_tsgs", "" }, + { "nv!nvrm_gpu_prevent_use", "" }, + { "nv!nvrm_gpu_trace", "" }, + { "nv!nvsched_debug_mask", "" }, + { "nv!nvsched_force_enable", "" }, + { "nv!nvsched_force_log", "" }, + { "nv!nv_usb_plls_hw_ctrl", "" }, + { "nv!nv_winsys", "" }, + { "nv!nvwsi_dump", "" }, + { "nv!nvwsi_fill", "" }, + { "nv!ogl_", "" }, + { "nv!ogl_0356afd0", "" }, + { "nv!ogl_0356afd1", "" }, + { "nv!ogl_0356afd2", "" }, + { "nv!ogl_0356afd3", "" }, + { "nv!ogl_0x923dc0", "" }, + { "nv!ogl_0x923dc1", "" }, + { "nv!ogl_0x923dc2", "" }, + { "nv!ogl_0x923dc3", "" }, + { "nv!ogl_0x923dc4", "" }, + { "nv!ogl_0x923dd3", "" }, + { "nv!ogl_0x9abdc5", "" }, + { "nv!ogl_0x9abdc6", "" }, + { "nv!ogl_0xbd10fb", "" }, + { "nv!ogl_0xce2348", "" }, + { "nv!ogl_10261989", "" }, + { "nv!ogl_1042d483", "" }, + { "nv!ogl_10572898", "" }, + { "nv!ogl_115631", "" }, + { "nv!ogl_12950094", "" }, + { "nv!ogl_1314f311", "" }, + { "nv!ogl_1314f312", "" }, + { "nv!ogl_13279512", "" }, + { "nv!ogl_13813496", "" }, + { "nv!ogl_14507179", "" }, + { "nv!ogl_15694569", "" }, + { "nv!ogl_16936964", "" }, + { "nv!ogl_17aa230c", "" }, + { "nv!ogl_182054", "" }, + { "nv!ogl_18273275", "" }, + { "nv!ogl_18273276", "" }, + { "nv!ogl_1854d03b", "" }, + { "nv!ogl_18add00d", "" }, + { "nv!ogl_19156670", "" }, + { "nv!ogl_19286545", "" }, + { "nv!ogl_1a298e9f", "" }, + { "nv!ogl_1acf43fe", "" }, + { "nv!ogl_1bda43fe", "" }, + { "nv!ogl_1c3b92", "" }, + { "nv!ogl_21509920", "" }, + { "nv!ogl_215323457", "" }, + { "nv!ogl_2165ad", "" }, + { "nv!ogl_2165ae", "" }, + { "nv!ogl_21be9c", "" }, + { "nv!ogl_233264316", "" }, + { "nv!ogl_234557580", "" }, + { "nv!ogl_23cd0e", "" }, + { "nv!ogl_24189123", "" }, + { "nv!ogl_2443266", "" }, + { "nv!ogl_25025519", "" }, + { "nv!ogl_255e39", "" }, + { "nv!ogl_2583364", "" }, + { "nv!ogl_2888c1", "" }, + { "nv!ogl_28ca3e", "" }, + { "nv!ogl_29871243", "" }, + { "nv!ogl_2a1f64", "" }, + { "nv!ogl_2dc432", "" }, + { "nv!ogl_2de437", "" }, + { "nv!ogl_2f3bb89c", "" }, + { "nv!ogl_2fd652", "" }, + { "nv!ogl_3001ac", "" }, + { "nv!ogl_31298772", "" }, + { "nv!ogl_313233", "" }, + { "nv!ogl_31f7d603", "" }, + { "nv!ogl_320ce4", "" }, + { "nv!ogl_32153248", "" }, + { "nv!ogl_32153249", "" }, + { "nv!ogl_335bca", "" }, + { "nv!ogl_342abb", "" }, + { "nv!ogl_34dfe6", "" }, + { "nv!ogl_34dfe7", "" }, + { "nv!ogl_34dfe8", "" }, + { "nv!ogl_34dfe9", "" }, + { "nv!ogl_35201578", "" }, + { "nv!ogl_359278", "" }, + { "nv!ogl_37f53a", "" }, + { "nv!ogl_38144972", "" }, + { "nv!ogl_38542646", "" }, + { "nv!ogl_3b74c9", "" }, + { "nv!ogl_3c136f", "" }, + { "nv!ogl_3cf72823", "" }, + { "nv!ogl_3d7af029", "" }, + { "nv!ogl_3ff34782", "" }, + { "nv!ogl_4129618", "" }, + { "nv!ogl_4189fac3", "" }, + { "nv!ogl_420bd4", "" }, + { "nv!ogl_42a699", "" }, + { "nv!ogl_441369", "" }, + { "nv!ogl_4458713e", "" }, + { "nv!ogl_4554b6", "" }, + { "nv!ogl_457425", "" }, + { "nv!ogl_4603b207", "" }, + { "nv!ogl_46574957", "" }, + { "nv!ogl_46574958", "" }, + { "nv!ogl_46813529", "" }, + { "nv!ogl_46f1e13d", "" }, + { "nv!ogl_47534c43", "" }, + { "nv!ogl_48550336", "" }, + { "nv!ogl_48576893", "" }, + { "nv!ogl_48576894", "" }, + { "nv!ogl_4889ac02", "" }, + { "nv!ogl_49005740", "" }, + { "nv!ogl_49867584", "" }, + { "nv!ogl_49960973", "" }, + { "nv!ogl_4a5341", "" }, + { "nv!ogl_4f4e48", "" }, + { "nv!ogl_4f8a0a", "" }, + { "nv!ogl_50299698", "" }, + { "nv!ogl_50299699", "" }, + { "nv!ogl_50361291", "" }, + { "nv!ogl_5242ae", "" }, + { "nv!ogl_53d30c", "" }, + { "nv!ogl_56347a", "" }, + { "nv!ogl_563a95f1", "" }, + { "nv!ogl_573823", "" }, + { "nv!ogl_58027529", "" }, + { "nv!ogl_5d2d63", "" }, + { "nv!ogl_5f7e3b", "" }, + { "nv!ogl_60461793", "" }, + { "nv!ogl_60d355", "" }, + { "nv!ogl_616627aa", "" }, + { "nv!ogl_62317182", "" }, + { "nv!ogl_6253fa2e", "" }, + { "nv!ogl_64100768", "" }, + { "nv!ogl_64100769", "" }, + { "nv!ogl_64100770", "" }, + { "nv!ogl_647395", "" }, + { "nv!ogl_66543234", "" }, + { "nv!ogl_67674763", "" }, + { "nv!ogl_67739784", "" }, + { "nv!ogl_68fb9c", "" }, + { "nv!ogl_69801276", "" }, + { "nv!ogl_6af9fa2f", "" }, + { "nv!ogl_6af9fa3f", "" }, + { "nv!ogl_6af9fa4f", "" }, + { "nv!ogl_6bd8c7", "" }, + { "nv!ogl_6c7691", "" }, + { "nv!ogl_6d4296ce", "" }, + { "nv!ogl_6dd7e7", "" }, + { "nv!ogl_6dd7e8", "" }, + { "nv!ogl_6fe11ec1", "" }, + { "nv!ogl_716511763", "" }, + { "nv!ogl_72504593", "" }, + { "nv!ogl_73304097", "" }, + { "nv!ogl_73314098", "" }, + { "nv!ogl_74095213", "" }, + { "nv!ogl_74095213a", "" }, + { "nv!ogl_74095213b", "" }, + { "nv!ogl_74095214", "" }, + { "nv!ogl_748f9649", "" }, + { "nv!ogl_75494732", "" }, + { "nv!ogl_78452832", "" }, + { "nv!ogl_784561", "" }, + { "nv!ogl_78e16b9c", "" }, + { "nv!ogl_79251225", "" }, + { "nv!ogl_7c128b", "" }, + { "nv!ogl_7ccd93", "" }, + { "nv!ogl_7df8d1", "" }, + { "nv!ogl_800c2310", "" }, + { "nv!ogl_80546710", "" }, + { "nv!ogl_80772310", "" }, + { "nv!ogl_808ee280", "" }, + { "nv!ogl_81131154", "" }, + { "nv!ogl_81274457", "" }, + { "nv!ogl_8292291f", "" }, + { "nv!ogl_83498426", "" }, + { "nv!ogl_84993794", "" }, + { "nv!ogl_84995585", "" }, + { "nv!ogl_84a0a0", "" }, + { "nv!ogl_852142", "" }, + { "nv!ogl_85612309", "" }, + { "nv!ogl_85612310", "" }, + { "nv!ogl_85612311", "" }, + { "nv!ogl_85612312", "" }, + { "nv!ogl_8623ff27", "" }, + { "nv!ogl_87364952", "" }, + { "nv!ogl_87f6275666", "" }, + { "nv!ogl_886748", "" }, + { "nv!ogl_89894423", "" }, + { "nv!ogl_8ad8a75", "" }, + { "nv!ogl_8ad8ad00", "" }, + { "nv!ogl_8bb815", "" }, + { "nv!ogl_8bb817", "" }, + { "nv!ogl_8bb818", "" }, + { "nv!ogl_8bb819", "" }, + { "nv!ogl_8e640cd1", "" }, + { "nv!ogl_8f34971a", "" }, + { "nv!ogl_8f773984", "" }, + { "nv!ogl_8f7a7d", "" }, + { "nv!ogl_902486209", "" }, + { "nv!ogl_90482571", "" }, + { "nv!ogl_91214835", "" }, + { "nv!ogl_912848290", "" }, + { "nv!ogl_915e56", "" }, + { "nv!ogl_92179063", "" }, + { "nv!ogl_92179064", "" }, + { "nv!ogl_92179065", "" }, + { "nv!ogl_92179066", "" }, + { "nv!ogl_92350358", "" }, + { "nv!ogl_92809063", "" }, + { "nv!ogl_92809064", "" }, + { "nv!ogl_92809065", "" }, + { "nv!ogl_92809066", "" }, + { "nv!ogl_92920143", "" }, + { "nv!ogl_93a89b12", "" }, + { "nv!ogl_93a89c0b", "" }, + { "nv!ogl_94812574", "" }, + { "nv!ogl_95282304", "" }, + { "nv!ogl_95394027", "" }, + { "nv!ogl_959b1f", "" }, + { "nv!ogl_9638af", "" }, + { "nv!ogl_96fd59", "" }, + { "nv!ogl_97f6275666", "" }, + { "nv!ogl_97f6275667", "" }, + { "nv!ogl_97f6275668", "" }, + { "nv!ogl_97f6275669", "" }, + { "nv!ogl_97f627566a", "" }, + { "nv!ogl_97f627566b", "" }, + { "nv!ogl_97f627566d", "" }, + { "nv!ogl_97f627566e", "" }, + { "nv!ogl_97f627566f", "" }, + { "nv!ogl_97f6275670", "" }, + { "nv!ogl_97f6275671", "" }, + { "nv!ogl_97f727566e", "" }, + { "nv!ogl_98480775", "" }, + { "nv!ogl_98480776", "" }, + { "nv!ogl_98480777", "" }, + { "nv!ogl_992431", "" }, + { "nv!ogl_9aa29065", "" }, + { "nv!ogl_9af32c", "" }, + { "nv!ogl_9af32d", "" }, + { "nv!ogl_9af32e", "" }, + { "nv!ogl_9c108b71", "" }, + { "nv!ogl_9f279065", "" }, + { "nv!ogl_a01bc728", "" }, + { "nv!ogl_a13b46c80", "" }, + { "nv!ogl_a22eb0", "" }, + { "nv!ogl_a2fb451e", "" }, + { "nv!ogl_a3456abe", "" }, + { "nv!ogl_a7044887", "" }, + { "nv!ogl_a7149200", "" }, + { "nv!ogl_a766215670", "" }, + { "nv!ogl_aalinegamma", "" }, + { "nv!ogl_aalinetweaks", "" }, + { "nv!ogl_ab34ee01", "" }, + { "nv!ogl_ab34ee02", "" }, + { "nv!ogl_ab34ee03", "" }, + { "nv!ogl_ac0274", "" }, + { "nv!ogl_af73c63e", "" }, + { "nv!ogl_af73c63f", "" }, + { "nv!ogl_af9927", "" }, + { "nv!ogl_afoverride", "" }, + { "nv!ogl_allocdeviceevents", "" }, + { "nv!ogl_applicationkey", "" }, + { "nv!ogl_appreturnonlybasicglsltype", "" }, + { "nv!ogl_app_softimage", "" }, + { "nv!ogl_app_supportbits2", "" }, + { "nv!ogl_assumetextureismipmappedatcreation", "" }, + { "nv!ogl_b1fb0f01", "" }, + { "nv!ogl_b3edd5", "" }, + { "nv!ogl_b40d9e03d", "" }, + { "nv!ogl_b7f6275666", "" }, + { "nv!ogl_b812c1", "" }, + { "nv!ogl_ba14ba1a", "" }, + { "nv!ogl_ba14ba1b", "" }, + { "nv!ogl_bd7559", "" }, + { "nv!ogl_bd755a", "" }, + { "nv!ogl_bd755c", "" }, + { "nv!ogl_bd755d", "" }, + { "nv!ogl_be58bb", "" }, + { "nv!ogl_be92cb", "" }, + { "nv!ogl_beefcba3", "" }, + { "nv!ogl_beefcba4", "" }, + { "nv!ogl_c023777f", "" }, + { "nv!ogl_c09dc8", "" }, + { "nv!ogl_c0d340", "" }, + { "nv!ogl_c2ff374c", "" }, + { "nv!ogl_c5e9d7a3", "" }, + { "nv!ogl_c5e9d7a4", "" }, + { "nv!ogl_c5e9d7b4", "" }, + { "nv!ogl_c618f9", "" }, + { "nv!ogl_ca345840", "" }, + { "nv!ogl_cachedisable", "" }, + { "nv!ogl_channelpriorityoverride", "" }, + { "nv!ogl_cleardatastorevidmem", "" }, + { "nv!ogl_cmdbufmemoryspaceenables", "" }, + { "nv!ogl_cmdbufminwords", "" }, + { "nv!ogl_cmdbufsizewords", "" }, + { "nv!ogl_conformantblitframebufferscissor", "" }, + { "nv!ogl_conformantincompletetextures", "" }, + { "nv!ogl_copybuffermethod", "" }, + { "nv!ogl_cubemapaniso", "" }, + { "nv!ogl_cubemapfiltering", "" }, + { "nv!ogl_d0e9a4d7", "" }, + { "nv!ogl_d13733f12", "" }, + { "nv!ogl_d1b399", "" }, + { "nv!ogl_d2983c32", "" }, + { "nv!ogl_d2983c33", "" }, + { "nv!ogl_d2e71b", "" }, + { "nv!ogl_d377dc", "" }, + { "nv!ogl_d377dd", "" }, + { "nv!ogl_d489f4", "" }, + { "nv!ogl_d4bce1", "" }, + { "nv!ogl_d518cb", "" }, + { "nv!ogl_d518cd", "" }, + { "nv!ogl_d518ce", "" }, + { "nv!ogl_d518d0", "" }, + { "nv!ogl_d518d1", "" }, + { "nv!ogl_d518d2", "" }, + { "nv!ogl_d518d3", "" }, + { "nv!ogl_d518d4", "" }, + { "nv!ogl_d518d5", "" }, + { "nv!ogl_d59eda", "" }, + { "nv!ogl_d83cbd", "" }, + { "nv!ogl_d8e777", "" }, + { "nv!ogl_debug_level", "" }, + { "nv!ogl_debug_mask", "" }, + { "nv!ogl_debug_options", "" }, + { "nv!ogl_devshmpageableallocations", "" }, + { "nv!ogl_df1f9812", "" }, + { "nv!ogl_df783c", "" }, + { "nv!ogl_diagenable", "" }, + { "nv!ogl_disallowcemask", "" }, + { "nv!ogl_disallowz16", "" }, + { "nv!ogl_dlmemoryspaceenables", "" }, + { "nv!ogl_e0bfec", "" }, + { "nv!ogl_e433456d", "" }, + { "nv!ogl_e435563f", "" }, + { "nv!ogl_e4cd9c", "" }, + { "nv!ogl_e5c972", "" }, + { "nv!ogl_e639ef", "" }, + { "nv!ogl_e802af", "" }, + { "nv!ogl_eae964", "" }, + { "nv!ogl_earlytexturehwallocation", "" }, + { "nv!ogl_eb92a3", "" }, + { "nv!ogl_ebca56", "" }, + { "nv!ogl_expert_detail_level", "" }, + { "nv!ogl_expert_output_mask", "" }, + { "nv!ogl_expert_report_mask", "" }, + { "nv!ogl_extensionstringnvarch", "" }, + { "nv!ogl_extensionstringversion", "" }, + { "nv!ogl_f00f1938", "" }, + { "nv!ogl_f10736", "" }, + { "nv!ogl_f1846870", "" }, + { "nv!ogl_f33bc370", "" }, + { "nv!ogl_f392a874", "" }, + { "nv!ogl_f49ae8", "" }, + { "nv!ogl_fa345cce", "" }, + { "nv!ogl_fa35cc4", "" }, + { "nv!ogl_faa14a", "" }, + { "nv!ogl_faf8a723", "" }, + { "nv!ogl_fastgs", "" }, + { "nv!ogl_fbf4ac45", "" }, + { "nv!ogl_fbo_blit_ignore_srgb", "" }, + { "nv!ogl_fc64c7", "" }, + { "nv!ogl_ff54ec97", "" }, + { "nv!ogl_ff54ec98", "" }, + { "nv!ogl_forceexitprocessdetach", "" }, + { "nv!ogl_forcerequestedesversion", "" }, + { "nv!ogl_glsynctovblank", "" }, + { "nv!ogl_gvitimeoutcontrol", "" }, + { "nv!ogl_hcctrl", "" }, + { "nv!ogl_hwstate_per_ctx", "" }, + { "nv!ogl_machinecachelimit", "" }, + { "nv!ogl_maxframesallowed", "" }, + { "nv!ogl_memmgrcachedalloclimit", "" }, + { "nv!ogl_memmgrcachedalloclimitratio", "" }, + { "nv!ogl_memmgrsysheapalloclimit", "" }, + { "nv!ogl_memmgrsysheapalloclimitratio", "" }, + { "nv!ogl_memmgrvidheapalloclimit", "" }, + { "nv!ogl_mosaic_clip_to_subdev", "" }, + { "nv!ogl_mosaic_clip_to_subdev_h_overlap", "" }, + { "nv!ogl_mosaic_clip_to_subdev_v_overlap", "" }, + { "nv!ogl_overlaymergeblittimerms", "" }, + { "nv!ogl_perfmon_mode", "" }, + { "nv!ogl_pixbar_mode", "" }, + { "nv!ogl_qualityenhancements", "" }, + { "nv!ogl_r27s18q28", "" }, + { "nv!ogl_r2d7c1d8", "" }, + { "nv!ogl_renderer", "" }, + { "nv!ogl_renderqualityflags", "" }, + { "nv!ogl_s3tcquality", "" }, + { "nv!ogl_shaderatomics", "" }, + { "nv!ogl_shadercacheinitsize", "" }, + { "nv!ogl_shader_disk_cache_path", "" }, + { "nv!ogl_shader_disk_cache_read_only", "" }, + { "nv!ogl_shaderobjects", "" }, + { "nv!ogl_shaderportabilitywarnings", "" }, + { "nv!ogl_shaderwarningsaserrors", "" }, + { "nv!ogl_skiptexturehostcopies", "" }, + { "nv!ogl_sli_dli_control", "" }, + { "nv!ogl_sparsetexture", "" }, + { "nv!ogl_spinlooptimeout", "" }, + { "nv!ogl_sync_to_vblank", "" }, + { "nv!ogl_sysheapreuseratio", "" }, + { "nv!ogl_sysmemtexturepromotion", "" }, + { "nv!ogl_targetflushcount", "" }, + { "nv!ogl_tearingfreeswappresent", "" }, + { "nv!ogl_texclampbehavior", "" }, + { "nv!ogl_texlodbias", "" }, + { "nv!ogl_texmemoryspaceenables", "" }, + { "nv!ogl_textureprecache", "" }, + { "nv!ogl_threadcontrol", "" }, + { "nv!ogl_threadcontrol2", "" }, + { "nv!ogl_usegvievents", "" }, + { "nv!ogl_vbomemoryspaceenables", "" }, + { "nv!ogl_vertexlimit", "" }, + { "nv!ogl_vidheapreuseratio", "" }, + { "nv!ogl_vpipe", "" }, + { "nv!ogl_vpipeformatbloatlimit", "" }, + { "nv!ogl_wglmessageboxonabort", "" }, + { "nv!ogl_writeinfolog", "" }, + { "nv!ogl_writeprogramobjectassembly", "" }, + { "nv!ogl_writeprogramobjectsource", "" }, + { "nv!ogl_xnvadapterpresent", "" }, + { "nv!ogl_yield", "" }, + { "nv!ogl_yieldfunction", "" }, + { "nv!ogl_yieldfunctionfast", "" }, + { "nv!ogl_yieldfunctionslow", "" }, + { "nv!ogl_yieldfunctionwaitfordcqueue", "" }, + { "nv!ogl_yieldfunctionwaitforframe", "" }, + { "nv!ogl_yieldfunctionwaitforgpu", "" }, + { "nv!ogl_zbctableaddhysteresis", "" }, + { "nv!overlaymergeblittimerms", "" }, + { "nv!perfmon_mode", "" }, + { "nv!persist.sys.display.resolution", "" }, + { "nv!persist.tegra.composite.fallb", "" }, + { "nv!persist.tegra.composite.policy", "" }, + { "nv!persist.tegra.composite.range", "" }, + { "nv!persist.tegra.compositor", "" }, + { "nv!persist.tegra.compositor.virt", "" }, + { "nv!persist.tegra.compression", "" }, + { "nv!persist.tegra.cursor.enable", "" }, + { "nv!persist.tegra.didim.enable", "" }, + { "nv!persist.tegra.didim.normal", "" }, + { "nv!persist.tegra.didim.video", "" }, + { "nv!persist.tegra.disp.heads", "" }, + { "nv!persist.tegra.gamma_correction", "" }, + { "nv!persist.tegra.gpu_mapping_cache", "" }, + { "nv!persist.tegra.grlayout", "" }, + { "nv!persist.tegra.hdmi.2020.10", "" }, + { "nv!persist.tegra.hdmi.2020.fake", "" }, + { "nv!persist.tegra.hdmi.2020.force", "" }, + { "nv!persist.tegra.hdmi.autorotate", "" }, + { "nv!persist.tegra.hdmi.hdr.fake", "" }, + { "nv!persist.tegra.hdmi.ignore_ratio", "" }, + { "nv!persist.tegra.hdmi.limit.clock", "" }, + { "nv!persist.tegra.hdmi.only_16_9", "" }, + { "nv!persist.tegra.hdmi.range", "" }, + { "nv!persist.tegra.hdmi.resolution", "" }, + { "nv!persist.tegra.hdmi.underscan", "" }, + { "nv!persist.tegra.hdmi.yuv.422", "" }, + { "nv!persist.tegra.hdmi.yuv.444", "" }, + { "nv!persist.tegra.hdmi.yuv.enable", "" }, + { "nv!persist.tegra.hdmi.yuv.force", "" }, + { "nv!persist.tegra.hwc.nvdc", "" }, + { "nv!persist.tegra.idle.minimum_fps", "" }, + { "nv!persist.tegra.panel.rotation", "" }, + { "nv!persist.tegra.scan_props", "" }, + { "nv!persist.tegra.stb.mode", "" }, + { "nv!persist.tegra.zbc_override", "" }, + { "nv!pixbar_mode", "" }, + { "nv!qualityenhancements", "" }, + { "nv!r27s18q28", "" }, + { "nv!r2d7c1d8", "" }, + { "nv!renderer", "" }, + { "nv!renderqualityflags", "" }, + { "nv!rmos_debug_mask", "" }, + { "nv!rmos_set_production_mode", "" }, + { "nv!s3tcquality", "" }, + { "nv!shaderatomics", "" }, + { "nv!shadercacheinitsize", "" }, + { "nv!shader_disk_cache_path", "" }, + { "nv!shader_disk_cache_read_only", "" }, + { "nv!shaderobjects", "" }, + { "nv!shaderportabilitywarnings", "" }, + { "nv!shaderwarningsaserrors", "" }, + { "nv!skiptexturehostcopies", "" }, + { "nv!sli_dli_control", "" }, + { "nv!sparsetexture", "" }, + { "nv!spinlooptimeout", "" }, + { "nv!sync_to_vblank", "" }, + { "nv!sysheapreuseratio", "" }, + { "nv!sysmemtexturepromotion", "" }, + { "nv!targetflushcount", "" }, + { "nv!tearingfreeswappresent", "" }, + { "nv!tegra.refresh", "" }, + { "nv!texclampbehavior", "" }, + { "nv!texlodbias", "" }, + { "nv!texmemoryspaceenables", "" }, + { "nv!textureprecache", "" }, + { "nv!threadcontrol", "" }, + { "nv!threadcontrol2", "" }, + { "nv!tvmr.avp.logs", "" }, + { "nv!tvmr.buffer.logs", "" }, + { "nv!tvmr.dec.prof", "" }, + { "nv!tvmr.deint.logs", "" }, + { "nv!tvmr.dfs.logs", "" }, + { "nv!tvmr.ffprof.logs", "" }, + { "nv!tvmr.game.stream", "" }, + { "nv!tvmr.general.logs", "" }, + { "nv!tvmr.input.dump", "" }, + { "nv!tvmr.seeking.logs", "" }, + { "nv!tvmr.ts_pulldown", "" }, + { "nv!usegvievents", "" }, + { "nv!vbomemoryspaceenables", "" }, + { "nv!vcc_debug_ip", "" }, + { "nv!vcc_verbose_level", "" }, + { "nv!vertexlimit", "" }, + { "nv!viccomposer.filter", "" }, + { "nv!videostats-enable", "" }, + { "nv!vidheapreuseratio", "" }, + { "nv!vpipe", "" }, + { "nv!vpipeformatbloatlimit", "" }, + { "nv!wglmessageboxonabort", "" }, + { "nv!writeinfolog", "" }, + { "nv!writeprogramobjectassembly", "" }, + { "nv!writeprogramobjectsource", "" }, + { "nv!xnvadapterpresent", "" }, + { "nv!yield", "" }, + { "nv!yieldfunction", "" }, + { "nv!yieldfunctionfast", "" }, + { "nv!yieldfunctionslow", "" }, + { "nv!yieldfunctionwaitfordcqueue", "" }, + { "nv!yieldfunctionwaitforframe", "" }, + { "nv!yieldfunctionwaitforgpu", "" }, + { "nv!zbctableaddhysteresis", "" }, + { "pcm!enable", true }, + { "pctl!intermittent_task_interval_seconds", 21600 }, + { "prepo!devmenu_prepo_page_view", false }, + { "prepo!background_processing", true }, + { "prepo!transmission_interval_min", 10 }, + { "prepo!transmission_retry_interval", 3600 }, + { "psm!evaluation_log_enabled", false }, + { "snap_shot_dump!auto_dump", false }, + { "snap_shot_dump!output_dir", "%USERPROFILE%/Documents/Nintendo/NXDMP" }, + { "snap_shot_dump!full_dump", false }, + { "systemconfig!field_testing", false }, + { "systemconfig!exhivision", false }, + { "systempowerstate!always_reboot", false }, + { "systempowerstate!power_state_message_emulation_trigger_time", 0 }, + { "systempowerstate!power_state_message_to_emulate", 0 }, + { "target_manager!device_name", "" }, + { "vulnerability!needs_update_vulnerability_policy", 0 }, + { "apm!performance_mode_policy", "auto" }, + { "apm!sdev_throttling_enabled", true }, + { "apm!sdev_throttling_additional_delay_us", 16000 }, + { "apm!battery_draining_enabled", false }, + { "apm!sdev_cpu_overclock_enabled", false }, + { "bcat!production_mode", true }, + { "bpc!enable_quasi_off", true }, + { "bsp0!usb", "UDS" }, + { "bsp0!tm_transport", "USB" }, + { "bluetooth_debug!skip_boot", false }, + { "contents_delivery!enable_debug_api", false }, + { "eupld!upload_enabled", true }, + { "fatal!transition_to_fatal", true }, + { "fatal!show_extra_info", false }, + { "gpu_core_dump!auto_dump", false }, + { "hid_debug!enables_debugpad", false }, + { "hid_debug!manages_devices", true }, + { "hid_debug!emulate_future_device", false }, + { "hid_debug!emulate_firmware_update_failure", false }, + { "hid_debug!emulate_mcu_hardware_error", false }, + { "hid_debug!firmware_update_failure_emulation_mode", 0 }, + { "jit_debug!enable_jit_debug", false }, + { "npns!background_processing", true }, + { "npns!logmanager_redirection", true }, + { "npns!sleep_processing_timeout", 30 }, + { "npns!sleep_periodic_interval", 10800 }, + { "npns!sleep_max_try_count", 5 }, + { "npns!test_mode", false }, + { "ns.applet!overlay_applet_id", "0x010000000000100c" }, + { "ns.applet!system_applet_id", "0x0100000000001000" }, + { "ns.applet!shop_applet_id", "0x010000000000100b" }, + { "ns.autoboot!enabled", true }, + { "ns.pseudodeviceid!reset_pseudo_device_id", false }, + { "nsd!environment_identifier", "lp1" }, + { "nsd!test_mode", false }, + { "ntc!is_autonomic_correction_enabled", true }, + { "ntc!autonomic_correction_interval_seconds", 432000 }, + { "ntc!autonomic_correction_failed_retry_interval_seconds", 1800 }, + { "ntc!autonomic_correction_immediate_try_count_max", 4 }, + { "ntc!autonomic_correction_immediate_try_interval_milliseconds", 5000 }, + { "nv!nv_graphics_firmware_memory_margin", false }, + { "omm!operation_mode_policy", "auto" }, + { "omm!sleep_fade_in_ms", 50 }, + { "omm!sleep_fade_out_ms", 100 }, + { "omm!charging_sign_ms", 3000 }, + { "omm!low_battery_sign_ms", 3000 }, + { "omm!sign_fade_in_ms", 0 }, + { "omm!sign_fade_out_ms", 400 }, + { "omm!sign_wait_layer_visible_ms", 100 }, + { "omm!startup_fade_in_ms", 200 }, + { "omm!startup_fade_out_ms", 400 }, + { "omm!backlight_off_ms_on_handheld_switch", 150 }, + { "omm!sleep_on_ac_ok_boot", true }, + { "pdm!save_playlog", true }, + { "productinfo!product_name", "Nintendo Switch" }, + { "productinfo!cec_osd_name", "NintendoSwitch" }, + { "ro!ease_nro_restriction", false }, + { "settings_debug!is_debug_mode_enabled", false }, + { "systemreport!enabled", true }, + { "systemsleep!enter_sleep", true }, + { "systemsleep!enter_sc7", true }, + { "systemsleep!keep_vdd_core", true }, + { "systemsleep!disable_tma_sleep", false }, + { "systemsleep!disable_auto_sleep", false }, + { "systemsleep!override_auto_sleep_time", 0 }, + { "systemsleep!sleep_pending_time_ms", 15000 }, + { "systemsleep!hush_time_after_brief_power_button_press_ms", 1000 }, + { "systemsleep!transition_timeout_sec", 60 }, + { "systemsleep!dummy_event_auto_wake", false }, + { "systemupdate!debug_id", "0x0000000000000000" }, + { "systemupdate!debug_version", 0 }, + { "systemupdate!bgnup_retry_seconds", 60 }, + { "systemupdate!enable_background_download_stress_testing", false }, + { "systemupdate!debug_id_for_content_delivery", "0x0000000000000000" }, + { "systemupdate!debug_version_for_content_delivery", 0 }, + { "systemupdate!assumed_system_applet_version", 0 }, + { "tc!iir_filter_gain_soc", 100 }, + { "tc!iir_filter_gain_pcb", 100 }, + { "tc!tskin_soc_coefficients_handheld", "[5464, 174190]" }, + { "tc!tskin_soc_coefficients_console", "[6182, 112480]" }, + { "tc!tskin_pcb_coefficients_handheld", "[5464, 174190]" }, + { "tc!tskin_pcb_coefficients_console", "[6182, 112480]" }, + { "tc!tskin_select", "both" }, + { "tc!tskin_rate_table_handheld", "[[-1000000, 40000, 0, 0], [36000, 43000, 51, 51], [43000, 48000, 51, 102], [48000, 53000, 102, 153], [53000, 1000000, 153, 153], [48000, 1000000, 153, 153]]" }, + { "tc!tskin_rate_table_console", "[[-1000000, 43000, 51, 51], [43000, 53000, 51, 153], [53000, 58000, 153, 255], [58000, 1000000, 255, 255]]" }, + { "tc!rate_select", "both" }, + { "tc!log_enabled", false }, + { "tc!sleep_enabled", true }, + { "time!standard_steady_clock_test_offset_minutes", 0 }, + { "time!standard_steady_clock_rtc_update_interval_minutes", 5 }, + { "time!standard_network_clock_sufficient_accuracy_minutes", 43200 }, + { "time!standard_user_clock_initial_year", 2019 }, + { "usb!usb30_force_enabled", false }, + { "wlan_debug!skip_wlan_boot", false }, + }; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Settings/ResultCode.cs new file mode 100644 index 00000000..e925361e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/ResultCode.cs @@ -0,0 +1,126 @@ +namespace Ryujinx.HLE.HOS.Services.Settings +{ + enum ResultCode + { + ModuleId = 105, + ErrorCodeShift = 9, + + Success = 0, + + NullSettingsName = (201 << ErrorCodeShift) | ModuleId, + NullSettingsKey = (202 << ErrorCodeShift) | ModuleId, + NullSettingsValue = (203 << ErrorCodeShift) | ModuleId, + NullSettingsValueBuffer = (205 << ErrorCodeShift) | ModuleId, + NullSettingValueSizeBuffer = (208 << ErrorCodeShift) | ModuleId, + NullDebugModeFlagBuffer = (209 << ErrorCodeShift) | ModuleId, + SettingGroupNameHasZeroLength = (221 << ErrorCodeShift) | ModuleId, + EmptySettingsItemKey = (222 << ErrorCodeShift) | ModuleId, + SettingGroupNameIsTooLong = (241 << ErrorCodeShift) | ModuleId, + SettingNameIsTooLong = (242 << ErrorCodeShift) | ModuleId, + SettingGroupNameEndsWithDotOrContainsInvalidCharacters = (261 << ErrorCodeShift) | ModuleId, + SettingNameEndsWithDotOrContainsInvalidCharacters = (262 << ErrorCodeShift) | ModuleId, + NullLanguageCodeBuffer = (621 << ErrorCodeShift) | ModuleId, + LanguageOutOfRange = (625 << ErrorCodeShift) | ModuleId, + NullNetworkSettingsBuffer = (631 << ErrorCodeShift) | ModuleId, + NullNetworkSettingsOutputCountBuffer = (632 << ErrorCodeShift) | ModuleId, + NullBacklightSettingsBuffer = (641 << ErrorCodeShift) | ModuleId, + NullBluetoothDeviceSettingBuffer = (651 << ErrorCodeShift) | ModuleId, + NullBluetoothDeviceSettingOutputCountBuffer = (652 << ErrorCodeShift) | ModuleId, + NullBluetoothEnableFlagBuffer = (653 << ErrorCodeShift) | ModuleId, + NullBluetoothAFHEnableFlagBuffer = (654 << ErrorCodeShift) | ModuleId, + NullBluetoothBoostEnableFlagBuffer = (655 << ErrorCodeShift) | ModuleId, + NullBLEPairingSettingsBuffer = (656 << ErrorCodeShift) | ModuleId, + NullBLEPairingSettingsEntryCountBuffer = (657 << ErrorCodeShift) | ModuleId, + NullExternalSteadyClockSourceIDBuffer = (661 << ErrorCodeShift) | ModuleId, + NullUserSystemClockContextBuffer = (662 << ErrorCodeShift) | ModuleId, + NullNetworkSystemClockContextBuffer = (663 << ErrorCodeShift) | ModuleId, + NullUserSystemClockAutomaticCorrectionEnabledFlagBuffer = (664 << ErrorCodeShift) | ModuleId, + NullShutdownRTCValueBuffer = (665 << ErrorCodeShift) | ModuleId, + NullExternalSteadyClockInternalOffsetBuffer = (666 << ErrorCodeShift) | ModuleId, + NullAccountSettingsBuffer = (671 << ErrorCodeShift) | ModuleId, + NullAudioVolumeBuffer = (681 << ErrorCodeShift) | ModuleId, + NullForceMuteOnHeadphoneRemovedBuffer = (683 << ErrorCodeShift) | ModuleId, + NullHeadphoneVolumeWarningCountBuffer = (684 << ErrorCodeShift) | ModuleId, + InvalidAudioOutputMode = (687 << ErrorCodeShift) | ModuleId, + NullHeadphoneVolumeUpdateFlagBuffer = (688 << ErrorCodeShift) | ModuleId, + NullConsoleInformationUploadFlagBuffer = (691 << ErrorCodeShift) | ModuleId, + NullAutomaticApplicationDownloadFlagBuffer = (701 << ErrorCodeShift) | ModuleId, + NullNotificationSettingsBuffer = (702 << ErrorCodeShift) | ModuleId, + NullAccountNotificationSettingsEntryCountBuffer = (703 << ErrorCodeShift) | ModuleId, + NullAccountNotificationSettingsBuffer = (704 << ErrorCodeShift) | ModuleId, + NullVibrationMasterVolumeBuffer = (711 << ErrorCodeShift) | ModuleId, + NullNXControllerSettingsBuffer = (712 << ErrorCodeShift) | ModuleId, + NullNXControllerSettingsEntryCountBuffer = (713 << ErrorCodeShift) | ModuleId, + NullUSBFullKeyEnableFlagBuffer = (714 << ErrorCodeShift) | ModuleId, + NullTVSettingsBuffer = (721 << ErrorCodeShift) | ModuleId, + NullEDIDBuffer = (722 << ErrorCodeShift) | ModuleId, + NullDataDeletionSettingsBuffer = (731 << ErrorCodeShift) | ModuleId, + NullInitialSystemAppletProgramIDBuffer = (741 << ErrorCodeShift) | ModuleId, + NullOverlayDispProgramIDBuffer = (742 << ErrorCodeShift) | ModuleId, + NullIsInRepairProcessBuffer = (743 << ErrorCodeShift) | ModuleId, + NullRequiresRunRepairTimeReviserBuffer = (744 << ErrorCodeShift) | ModuleId, + NullDeviceTimezoneLocationNameBuffer = (751 << ErrorCodeShift) | ModuleId, + NullPrimaryAlbumStorageBuffer = (761 << ErrorCodeShift) | ModuleId, + NullUSB30EnableFlagBuffer = (771 << ErrorCodeShift) | ModuleId, + NullUSBTypeCPowerSourceCircuitVersionBuffer = (772 << ErrorCodeShift) | ModuleId, + NullBatteryLotBuffer = (781 << ErrorCodeShift) | ModuleId, + NullSerialNumberBuffer = (791 << ErrorCodeShift) | ModuleId, + NullLockScreenFlagBuffer = (801 << ErrorCodeShift) | ModuleId, + NullColorSetIDBuffer = (803 << ErrorCodeShift) | ModuleId, + NullQuestFlagBuffer = (804 << ErrorCodeShift) | ModuleId, + NullWirelessCertificationFileSizeBuffer = (805 << ErrorCodeShift) | ModuleId, + NullWirelessCertificationFileBuffer = (806 << ErrorCodeShift) | ModuleId, + NullInitialLaunchSettingsBuffer = (807 << ErrorCodeShift) | ModuleId, + NullDeviceNicknameBuffer = (808 << ErrorCodeShift) | ModuleId, + NullBatteryPercentageFlagBuffer = (809 << ErrorCodeShift) | ModuleId, + NullAppletLaunchFlagsBuffer = (810 << ErrorCodeShift) | ModuleId, + NullWirelessLANEnableFlagBuffer = (1012 << ErrorCodeShift) | ModuleId, + NullProductModelBuffer = (1021 << ErrorCodeShift) | ModuleId, + NullNFCEnableFlagBuffer = (1031 << ErrorCodeShift) | ModuleId, + NullECIDeviceCertificateBuffer = (1041 << ErrorCodeShift) | ModuleId, + NullETicketDeviceCertificateBuffer = (1042 << ErrorCodeShift) | ModuleId, + NullSleepSettingsBuffer = (1051 << ErrorCodeShift) | ModuleId, + NullEULAVersionBuffer = (1061 << ErrorCodeShift) | ModuleId, + NullEULAVersionEntryCountBuffer = (1062 << ErrorCodeShift) | ModuleId, + NullLDNChannelBuffer = (1071 << ErrorCodeShift) | ModuleId, + NullSSLKeyBuffer = (1081 << ErrorCodeShift) | ModuleId, + NullSSLCertificateBuffer = (1082 << ErrorCodeShift) | ModuleId, + NullTelemetryFlagsBuffer = (1091 << ErrorCodeShift) | ModuleId, + NullGamecardKeyBuffer = (1101 << ErrorCodeShift) | ModuleId, + NullGamecardCertificateBuffer = (1102 << ErrorCodeShift) | ModuleId, + NullPTMBatteryLotBuffer = (1111 << ErrorCodeShift) | ModuleId, + NullPTMFuelGaugeParameterBuffer = (1112 << ErrorCodeShift) | ModuleId, + NullECIDeviceKeyBuffer = (1121 << ErrorCodeShift) | ModuleId, + NullETicketDeviceKeyBuffer = (1122 << ErrorCodeShift) | ModuleId, + NullSpeakerParameterBuffer = (1131 << ErrorCodeShift) | ModuleId, + NullFirmwareVersionBuffer = (1141 << ErrorCodeShift) | ModuleId, + NullFirmwareVersionDigestBuffer = (1142 << ErrorCodeShift) | ModuleId, + NullRebootlessSystemUpdateVersionBuffer = (1143 << ErrorCodeShift) | ModuleId, + NullMiiAuthorIDBuffer = (1151 << ErrorCodeShift) | ModuleId, + NullFatalFlagsBuffer = (1161 << ErrorCodeShift) | ModuleId, + NullAutoUpdateEnableFlagBuffer = (1171 << ErrorCodeShift) | ModuleId, + NullExternalRTCResetFlagBuffer = (1181 << ErrorCodeShift) | ModuleId, + NullPushNotificationActivityModeBuffer = (1191 << ErrorCodeShift) | ModuleId, + NullServiceDiscoveryControlSettingBuffer = (1201 << ErrorCodeShift) | ModuleId, + NullErrorReportSharePermissionBuffer = (1211 << ErrorCodeShift) | ModuleId, + NullLCDVendorIDBuffer = (1221 << ErrorCodeShift) | ModuleId, + NullConsoleSixAxisSensorAccelerationBiasBuffer = (1231 << ErrorCodeShift) | ModuleId, + NullConsoleSixAxisSensorAngularVelocityBiasBuffer = (1232 << ErrorCodeShift) | ModuleId, + NullConsoleSixAxisSensorAccelerationGainBuffer = (1233 << ErrorCodeShift) | ModuleId, + NullConsoleSixAxisSensorAngularVelocityGainBuffer = (1234 << ErrorCodeShift) | ModuleId, + NullConsoleSixAxisSensorAngularVelocityTimeBiasBuffer = (1235 << ErrorCodeShift) | ModuleId, + NullConsoleSixAxisSensorAngularAccelerationBuffer = (1236 << ErrorCodeShift) | ModuleId, + NullKeyboardLayoutBuffer = (1241 << ErrorCodeShift) | ModuleId, + InvalidKeyboardLayout = (1245 << ErrorCodeShift) | ModuleId, + NullWebInspectorFlagBuffer = (1251 << ErrorCodeShift) | ModuleId, + NullAllowedSSLHostsBuffer = (1252 << ErrorCodeShift) | ModuleId, + NullAllowedSSLHostsEntryCountBuffer = (1253 << ErrorCodeShift) | ModuleId, + NullHostFSMountPointBuffer = (1254 << ErrorCodeShift) | ModuleId, + NullAmiiboKeyBuffer = (1271 << ErrorCodeShift) | ModuleId, + NullAmiiboECQVCertificateBuffer = (1272 << ErrorCodeShift) | ModuleId, + NullAmiiboECDSACertificateBuffer = (1273 << ErrorCodeShift) | ModuleId, + NullAmiiboECQVBLSKeyBuffer = (1274 << ErrorCodeShift) | ModuleId, + NullAmiiboECQVBLSCertificateBuffer = (1275 << ErrorCodeShift) | ModuleId, + NullAmiiboECQVBLSRootCertificateBuffer = (1276 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/PlatformRegion.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/PlatformRegion.cs new file mode 100644 index 00000000..9bddc484 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/PlatformRegion.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Settings.Types +{ + enum PlatformRegion + { + Global = 1, + China = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sm/IManagerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Sm/IManagerInterface.cs new file mode 100644 index 00000000..5733a49b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sm/IManagerInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sm +{ + [Service("sm:m")] + class IManagerInterface : IpcService + { + public IManagerInterface(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs b/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs new file mode 100644 index 00000000..7a90c664 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs @@ -0,0 +1,262 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Ipc; +using Ryujinx.HLE.HOS.Services.Apm; +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sm +{ + partial class IUserInterface : IpcService + { + private static readonly Dictionary _services; + + private readonly SmRegistry _registry; + private readonly ServerBase _commonServer; + + private bool _isInitialized; + + public IUserInterface(KernelContext context, SmRegistry registry) + { + _commonServer = new ServerBase(context, "CommonServer"); + _registry = registry; + } + + static IUserInterface() + { + _services = typeof(IUserInterface).Assembly.GetTypes() + .SelectMany(type => type.GetCustomAttributes(typeof(ServiceAttribute), true) + .Select(service => (((ServiceAttribute)service).Name, type))) + .ToDictionary(service => service.Name, service => service.type); + } + + [CommandCmif(0)] + [CommandTipc(0)] // 12.0.0+ + // Initialize(pid, u64 reserved) + public ResultCode Initialize(ServiceCtx context) + { + _isInitialized = true; + + return ResultCode.Success; + } + + [CommandTipc(1)] // 12.0.0+ + // GetService(ServiceName name) -> handle + public ResultCode GetServiceTipc(ServiceCtx context) + { + context.Response.HandleDesc = IpcHandleDesc.MakeMove(0); + + return GetService(context); + } + + [CommandCmif(1)] + public ResultCode GetService(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.NotInitialized; + } + + string name = ReadName(context); + + if (name == string.Empty) + { + return ResultCode.InvalidName; + } + + KSession session = new(context.Device.System.KernelContext); + + if (_registry.TryGetService(name, out KPort port)) + { + Result result = port.EnqueueIncomingSession(session.ServerSession); + + if (result != Result.Success) + { + throw new InvalidOperationException($"Session enqueue on port returned error \"{result}\"."); + } + + if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + session.ClientSession.DecrementReferenceCount(); + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + } + else + { + if (_services.TryGetValue(name, out Type type)) + { + ServiceAttribute serviceAttribute = (ServiceAttribute)type.GetCustomAttributes(typeof(ServiceAttribute)).First(service => ((ServiceAttribute)service).Name == name); + + IpcService service = GetServiceInstance(type, context, serviceAttribute.Parameter); + + service.TrySetServer(_commonServer); + service.Server.AddSessionObj(session.ServerSession, service); + } + else + { + if (context.Device.Configuration.IgnoreMissingServices) + { + Logger.Warning?.Print(LogClass.Service, $"Missing service {name} ignored"); + } + else + { + throw new NotImplementedException(name); + } + } + + if (context.Process.HandleTable.GenerateHandle(session.ClientSession, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + session.ServerSession.DecrementReferenceCount(); + session.ClientSession.DecrementReferenceCount(); + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + } + + return ResultCode.Success; + } + + [CommandCmif(2)] + // RegisterService(ServiceName name, u8 isLight, u32 maxHandles) -> handle + public ResultCode RegisterServiceCmif(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.NotInitialized; + } + + long namePosition = context.RequestData.BaseStream.Position; + + string name = ReadName(context); + + context.RequestData.BaseStream.Seek(namePosition + 8, SeekOrigin.Begin); + + bool isLight = (context.RequestData.ReadInt32() & 1) != 0; + + int maxSessions = context.RequestData.ReadInt32(); + + return RegisterService(context, name, isLight, maxSessions); + } + + [CommandTipc(2)] // 12.0.0+ + // RegisterService(ServiceName name, u32 maxHandles, u8 isLight) -> handle + public ResultCode RegisterServiceTipc(ServiceCtx context) + { + if (!_isInitialized) + { + context.Response.HandleDesc = IpcHandleDesc.MakeMove(0); + + return ResultCode.NotInitialized; + } + + long namePosition = context.RequestData.BaseStream.Position; + + string name = ReadName(context); + + context.RequestData.BaseStream.Seek(namePosition + 8, SeekOrigin.Begin); + + int maxSessions = context.RequestData.ReadInt32(); + + bool isLight = (context.RequestData.ReadInt32() & 1) != 0; + + return RegisterService(context, name, isLight, maxSessions); + } + + private ResultCode RegisterService(ServiceCtx context, string name, bool isLight, int maxSessions) + { + if (string.IsNullOrEmpty(name)) + { + return ResultCode.InvalidName; + } + + Logger.Debug?.Print(LogClass.ServiceSm, $"Register \"{name}\"."); + + KPort port = new(context.Device.System.KernelContext, maxSessions, isLight, null); + + if (!_registry.TryRegister(name, port)) + { + return ResultCode.AlreadyRegistered; + } + + if (context.Process.HandleTable.GenerateHandle(port.ServerPort, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + + return ResultCode.Success; + } + + [CommandCmif(3)] + [CommandTipc(3)] // 12.0.0+ + // UnregisterService(ServiceName name) + public ResultCode UnregisterService(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.NotInitialized; + } + + long namePosition = context.RequestData.BaseStream.Position; + + string name = ReadName(context); + + context.RequestData.BaseStream.Seek(namePosition + 8, SeekOrigin.Begin); + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + bool isLight = (context.RequestData.ReadInt32() & 1) != 0; + int maxSessions = context.RequestData.ReadInt32(); +#pragma warning restore IDE0059 + + if (string.IsNullOrEmpty(name)) + { + return ResultCode.InvalidName; + } + + if (!_registry.Unregister(name)) + { + return ResultCode.NotRegistered; + } + + return ResultCode.Success; + } + + private static string ReadName(ServiceCtx context) + { + StringBuilder nameBuilder = new(); + + for (int index = 0; index < 8 && + context.RequestData.BaseStream.Position < + context.RequestData.BaseStream.Length; index++) + { + byte chr = context.RequestData.ReadByte(); + + if (chr >= 0x20 && chr < 0x7f) + { + nameBuilder.Append((char)chr); + } + } + + return nameBuilder.ToString(); + } + + public override void DestroyAtExit() + { + _commonServer.Dispose(); + + base.DestroyAtExit(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sm/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Sm/ResultCode.cs new file mode 100644 index 00000000..6db33d2a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sm/ResultCode.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Sm +{ + enum ResultCode + { + ModuleId = 21, + ErrorCodeShift = 9, + + Success = 0, + + NotInitialized = (2 << ErrorCodeShift) | ModuleId, + AlreadyRegistered = (4 << ErrorCodeShift) | ModuleId, + InvalidName = (6 << ErrorCodeShift) | ModuleId, + NotRegistered = (7 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs b/src/Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs new file mode 100644 index 00000000..3919eaae --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs @@ -0,0 +1,49 @@ +using Ryujinx.HLE.HOS.Kernel.Ipc; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Sm +{ + class SmRegistry + { + private readonly ConcurrentDictionary _registeredServices; + private readonly AutoResetEvent _serviceRegistrationEvent; + + public SmRegistry() + { + _registeredServices = new ConcurrentDictionary(); + _serviceRegistrationEvent = new AutoResetEvent(false); + } + + public bool TryGetService(string name, out KPort port) + { + return _registeredServices.TryGetValue(name, out port); + } + + public bool TryRegister(string name, KPort port) + { + if (_registeredServices.TryAdd(name, port)) + { + _serviceRegistrationEvent.Set(); + return true; + } + + return false; + } + + public bool Unregister(string name) + { + return _registeredServices.TryRemove(name, out _); + } + + public bool IsServiceRegistered(string name) + { + return _registeredServices.TryGetValue(name, out _); + } + + public void WaitForServiceRegistration() + { + _serviceRegistrationEvent.WaitOne(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs new file mode 100644 index 00000000..7ecd6835 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs @@ -0,0 +1,184 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Numerics; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + class BsdContext + { + private static readonly ConcurrentDictionary _registry = new(); + + private readonly object _lock = new(); + + private readonly List _fds; + + private BsdContext() + { + _fds = new List(); + } + + public ISocket RetrieveSocket(int socketFd) + { + IFileDescriptor file = RetrieveFileDescriptor(socketFd); + + if (file is ISocket socket) + { + return socket; + } + + return null; + } + + public IFileDescriptor RetrieveFileDescriptor(int fd) + { + lock (_lock) + { + if (fd >= 0 && _fds.Count > fd) + { + return _fds[fd]; + } + } + + return null; + } + + public List RetrieveFileDescriptorsFromMask(ReadOnlySpan mask) + { + List fds = new(); + + for (int i = 0; i < mask.Length; i++) + { + byte current = mask[i]; + + while (current != 0) + { + int bit = BitOperations.TrailingZeroCount(current); + current &= (byte)~(1 << bit); + int fd = i * 8 + bit; + + fds.Add(RetrieveFileDescriptor(fd)); + } + } + + return fds; + } + + public int RegisterFileDescriptor(IFileDescriptor file) + { + lock (_lock) + { + for (int fd = 0; fd < _fds.Count; fd++) + { + if (_fds[fd] == null) + { + _fds[fd] = file; + + return fd; + } + } + + _fds.Add(file); + + return _fds.Count - 1; + } + } + + public void BuildMask(List fds, Span mask) + { + foreach (IFileDescriptor descriptor in fds) + { + int fd = _fds.IndexOf(descriptor); + + mask[fd >> 3] |= (byte)(1 << (fd & 7)); + } + } + + public int DuplicateFileDescriptor(int fd) + { + IFileDescriptor oldFile = RetrieveFileDescriptor(fd); + + if (oldFile != null) + { + lock (_lock) + { + oldFile.Refcount++; + + return RegisterFileDescriptor(oldFile); + } + } + + return -1; + } + + public bool CloseFileDescriptor(int fd) + { + IFileDescriptor file = RetrieveFileDescriptor(fd); + + if (file != null) + { + file.Refcount--; + + if (file.Refcount <= 0) + { + file.Dispose(); + } + + lock (_lock) + { + _fds[fd] = null; + } + + return true; + } + + return false; + } + + public LinuxError ShutdownAllSockets(BsdSocketShutdownFlags how) + { + lock (_lock) + { + foreach (IFileDescriptor file in _fds) + { + if (file is ISocket socket) + { + LinuxError errno = socket.Shutdown(how); + + if (errno != LinuxError.SUCCESS) + { + return errno; + } + } + } + } + + return LinuxError.SUCCESS; + } + + public static BsdContext GetOrRegister(ulong processId) + { + BsdContext context = GetContext(processId); + + if (context == null) + { + context = new BsdContext(); + + _registry.TryAdd(processId, context); + } + + return context; + } + + public static BsdContext GetContext(ulong processId) + { + if (!_registry.TryGetValue(processId, out BsdContext processContext)) + { + return null; + } + + return processContext; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs new file mode 100644 index 00000000..21d48288 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs @@ -0,0 +1,1147 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + [Service("bsd:s", true)] + [Service("bsd:u", false)] + class IClient : IpcService + { + private static readonly List _pollManagers = new() + { + EventFileDescriptorPollManager.Instance, + ManagedSocketPollManager.Instance, + }; + + private BsdContext _context; + private readonly bool _isPrivileged; + + public IClient(ServiceCtx context, bool isPrivileged) : base(context.Device.System.BsdServer) + { + _isPrivileged = isPrivileged; + } + + private ResultCode WriteBsdResult(ServiceCtx context, int result, LinuxError errorCode = LinuxError.SUCCESS) + { + if (errorCode != LinuxError.SUCCESS) + { + result = -1; + } + + context.ResponseData.Write(result); + context.ResponseData.Write((int)errorCode); + + return ResultCode.Success; + } + + private static AddressFamily ConvertBsdAddressFamily(BsdAddressFamily family) + { + return family switch + { + BsdAddressFamily.Unspecified => AddressFamily.Unspecified, + BsdAddressFamily.InterNetwork => AddressFamily.InterNetwork, + BsdAddressFamily.InterNetworkV6 => AddressFamily.InterNetworkV6, + BsdAddressFamily.Unknown => AddressFamily.Unknown, + _ => throw new NotImplementedException(family.ToString()), + }; + } + + private LinuxError SetResultErrno(IFileDescriptor socket, int result) + { + return result == 0 && !socket.Blocking ? LinuxError.EWOULDBLOCK : LinuxError.SUCCESS; + } + + private ResultCode SocketInternal(ServiceCtx context, bool exempt) + { + BsdAddressFamily domain = (BsdAddressFamily)context.RequestData.ReadInt32(); + BsdSocketType type = (BsdSocketType)context.RequestData.ReadInt32(); + ProtocolType protocol = (ProtocolType)context.RequestData.ReadInt32(); + + BsdSocketCreationFlags creationFlags = (BsdSocketCreationFlags)((int)type >> (int)BsdSocketCreationFlags.FlagsShift); + type &= BsdSocketType.TypeMask; + + if (domain == BsdAddressFamily.Unknown) + { + return WriteBsdResult(context, -1, LinuxError.EPROTONOSUPPORT); + } + else if ((type == BsdSocketType.Seqpacket || type == BsdSocketType.Raw) && !_isPrivileged) + { + if (domain != BsdAddressFamily.InterNetwork || type != BsdSocketType.Raw || protocol != ProtocolType.Icmp) + { + return WriteBsdResult(context, -1, LinuxError.ENOENT); + } + } + + AddressFamily netDomain = ConvertBsdAddressFamily(domain); + + if (protocol == ProtocolType.IP) + { + if (type == BsdSocketType.Stream) + { + protocol = ProtocolType.Tcp; + } + else if (type == BsdSocketType.Dgram) + { + protocol = ProtocolType.Udp; + } + } + + ISocket newBsdSocket = new ManagedSocket(netDomain, (SocketType)type, protocol) + { + Blocking = !creationFlags.HasFlag(BsdSocketCreationFlags.NonBlocking), + }; + + LinuxError errno = LinuxError.SUCCESS; + + int newSockFd = _context.RegisterFileDescriptor(newBsdSocket); + + if (newSockFd == -1) + { + errno = LinuxError.EBADF; + } + + if (exempt) + { + newBsdSocket.Disconnect(); + } + + return WriteBsdResult(context, newSockFd, errno); + } + + private void WriteSockAddr(ServiceCtx context, ulong bufferPosition, ISocket socket, bool isRemote) + { + IPEndPoint endPoint = isRemote ? socket.RemoteEndPoint : socket.LocalEndPoint; + + if (endPoint != null) + { + context.Memory.Write(bufferPosition, BsdSockAddr.FromIPEndPoint(endPoint)); + } + else + { + context.Memory.Write(bufferPosition, new BsdSockAddr()); + } + } + + [CommandCmif(0)] + // Initialize(nn::socket::BsdBufferConfig config, u64 pid, u64 transferMemorySize, KObject, pid) -> u32 bsd_errno + public ResultCode RegisterClient(ServiceCtx context) + { + _context = BsdContext.GetOrRegister(context.Request.HandleDesc.PId); + + /* + typedef struct { + u32 version; // Observed 1 on 2.0 LibAppletWeb, 2 on 3.0. + u32 tcp_tx_buf_size; // Size of the TCP transfer (send) buffer (initial or fixed). + u32 tcp_rx_buf_size; // Size of the TCP recieve buffer (initial or fixed). + u32 tcp_tx_buf_max_size; // Maximum size of the TCP transfer (send) buffer. If it is 0, the size of the buffer is fixed to its initial value. + u32 tcp_rx_buf_max_size; // Maximum size of the TCP receive buffer. If it is 0, the size of the buffer is fixed to its initial value. + u32 udp_tx_buf_size; // Size of the UDP transfer (send) buffer (typically 0x2400 bytes). + u32 udp_rx_buf_size; // Size of the UDP receive buffer (typically 0xA500 bytes). + u32 sb_efficiency; // Number of buffers for each socket (standard values range from 1 to 8). + } BsdBufferConfig; + */ + + // bsd_error + context.ResponseData.Write(0); + + Logger.Stub?.PrintStub(LogClass.ServiceBsd); + + // Close transfer memory immediately as we don't use it. + context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // StartMonitoring(u64, pid) + public ResultCode StartMonitoring(ServiceCtx context) + { + ulong unknown0 = context.RequestData.ReadUInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceBsd, new { unknown0 }); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // Socket(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) + public ResultCode Socket(ServiceCtx context) + { + return SocketInternal(context, false); + } + + [CommandCmif(3)] + // SocketExempt(u32 domain, u32 type, u32 protocol) -> (i32 ret, u32 bsd_errno) + public ResultCode SocketExempt(ServiceCtx context) + { + return SocketInternal(context, true); + } + + [CommandCmif(4)] + // Open(u32 flags, array path) -> (i32 ret, u32 bsd_errno) + public ResultCode Open(ServiceCtx context) + { + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); + + int flags = context.RequestData.ReadInt32(); + + byte[] rawPath = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, rawPath); + + string path = Encoding.ASCII.GetString(rawPath); + + WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + + Logger.Stub?.PrintStub(LogClass.ServiceBsd, new { path, flags }); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // Select(u32 nfds, nn::socket::timeval timeout, buffer readfds_in, buffer writefds_in, buffer errorfds_in) + // -> (i32 ret, u32 bsd_errno, buffer readfds_out, buffer writefds_out, buffer errorfds_out) + public ResultCode Select(ServiceCtx context) + { + int fdsCount = context.RequestData.ReadInt32(); + int timeout = context.RequestData.ReadInt32(); + + (ulong readFdsInBufferPosition, ulong readFdsInBufferSize) = context.Request.GetBufferType0x21(0); + (ulong writeFdsInBufferPosition, ulong writeFdsInBufferSize) = context.Request.GetBufferType0x21(1); + (ulong errorFdsInBufferPosition, ulong errorFdsInBufferSize) = context.Request.GetBufferType0x21(2); + + (ulong readFdsOutBufferPosition, ulong readFdsOutBufferSize) = context.Request.GetBufferType0x22(0); + (ulong writeFdsOutBufferPosition, ulong writeFdsOutBufferSize) = context.Request.GetBufferType0x22(1); + (ulong errorFdsOutBufferPosition, ulong errorFdsOutBufferSize) = context.Request.GetBufferType0x22(2); + + List readFds = _context.RetrieveFileDescriptorsFromMask(context.Memory.GetSpan(readFdsInBufferPosition, (int)readFdsInBufferSize)); + List writeFds = _context.RetrieveFileDescriptorsFromMask(context.Memory.GetSpan(writeFdsInBufferPosition, (int)writeFdsInBufferSize)); + List errorFds = _context.RetrieveFileDescriptorsFromMask(context.Memory.GetSpan(errorFdsInBufferPosition, (int)errorFdsInBufferSize)); + + int actualFdsCount = readFds.Count + writeFds.Count + errorFds.Count; + + if (fdsCount == 0 || actualFdsCount == 0) + { + WriteBsdResult(context, 0); + + return ResultCode.Success; + } + + PollEvent[] events = new PollEvent[actualFdsCount]; + + int index = 0; + + foreach (IFileDescriptor fd in readFds) + { + events[index] = new PollEvent(new PollEventData { InputEvents = PollEventTypeMask.Input }, fd); + + index++; + } + + foreach (IFileDescriptor fd in writeFds) + { + events[index] = new PollEvent(new PollEventData { InputEvents = PollEventTypeMask.Output }, fd); + + index++; + } + + foreach (IFileDescriptor fd in errorFds) + { + events[index] = new PollEvent(new PollEventData { InputEvents = PollEventTypeMask.Error }, fd); + + index++; + } + + List[] eventsByPollManager = new List[_pollManagers.Count]; + + for (int i = 0; i < eventsByPollManager.Length; i++) + { + eventsByPollManager[i] = new List(); + + foreach (PollEvent evnt in events) + { + if (_pollManagers[i].IsCompatible(evnt)) + { + eventsByPollManager[i].Add(evnt); + } + } + } + + int updatedCount = 0; + + for (int i = 0; i < _pollManagers.Count; i++) + { + if (eventsByPollManager[i].Count > 0) + { + _pollManagers[i].Select(eventsByPollManager[i], timeout, out int updatedPollCount); + updatedCount += updatedPollCount; + } + } + + readFds.Clear(); + writeFds.Clear(); + errorFds.Clear(); + + foreach (PollEvent pollEvent in events) + { + for (int i = 0; i < _pollManagers.Count; i++) + { + if (eventsByPollManager[i].Contains(pollEvent)) + { + if (pollEvent.Data.OutputEvents.HasFlag(PollEventTypeMask.Input)) + { + readFds.Add(pollEvent.FileDescriptor); + } + + if (pollEvent.Data.OutputEvents.HasFlag(PollEventTypeMask.Output)) + { + writeFds.Add(pollEvent.FileDescriptor); + } + + if (pollEvent.Data.OutputEvents.HasFlag(PollEventTypeMask.Error)) + { + errorFds.Add(pollEvent.FileDescriptor); + } + } + } + } + + using var readFdsOut = context.Memory.GetWritableRegion(readFdsOutBufferPosition, (int)readFdsOutBufferSize); + using var writeFdsOut = context.Memory.GetWritableRegion(writeFdsOutBufferPosition, (int)writeFdsOutBufferSize); + using var errorFdsOut = context.Memory.GetWritableRegion(errorFdsOutBufferPosition, (int)errorFdsOutBufferSize); + + _context.BuildMask(readFds, readFdsOut.Memory.Span); + _context.BuildMask(writeFds, writeFdsOut.Memory.Span); + _context.BuildMask(errorFds, errorFdsOut.Memory.Span); + + WriteBsdResult(context, updatedCount); + + return ResultCode.Success; + } + + [CommandCmif(6)] + // Poll(u32 nfds, u32 timeout, buffer fds) -> (i32 ret, u32 bsd_errno, buffer) + public ResultCode Poll(ServiceCtx context) + { + int fdsCount = context.RequestData.ReadInt32(); + int timeout = context.RequestData.ReadInt32(); + + (ulong inputBufferPosition, ulong inputBufferSize) = context.Request.GetBufferType0x21(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + (ulong outputBufferPosition, ulong outputBufferSize) = context.Request.GetBufferType0x22(); +#pragma warning restore IDE0059 + + if (timeout < -1 || fdsCount < 0 || (ulong)(fdsCount * 8) > inputBufferSize) + { + return WriteBsdResult(context, -1, LinuxError.EINVAL); + } + + PollEvent[] events = new PollEvent[fdsCount]; + + for (int i = 0; i < fdsCount; i++) + { + PollEventData pollEventData = context.Memory.Read(inputBufferPosition + (ulong)(i * Unsafe.SizeOf())); + + IFileDescriptor fileDescriptor = _context.RetrieveFileDescriptor(pollEventData.SocketFd); + + if (fileDescriptor == null) + { + return WriteBsdResult(context, -1, LinuxError.EBADF); + } + + events[i] = new PollEvent(pollEventData, fileDescriptor); + } + + List discoveredEvents = new(); + List[] eventsByPollManager = new List[_pollManagers.Count]; + + for (int i = 0; i < eventsByPollManager.Length; i++) + { + eventsByPollManager[i] = new List(); + + foreach (PollEvent evnt in events) + { + if (_pollManagers[i].IsCompatible(evnt)) + { + eventsByPollManager[i].Add(evnt); + discoveredEvents.Add(evnt); + } + } + } + + foreach (PollEvent evnt in events) + { + if (!discoveredEvents.Contains(evnt)) + { + Logger.Error?.Print(LogClass.ServiceBsd, $"Poll operation is not supported for {evnt.FileDescriptor.GetType().Name}!"); + + return WriteBsdResult(context, -1, LinuxError.EBADF); + } + } + + int updateCount = 0; + + LinuxError errno = LinuxError.SUCCESS; + + if (fdsCount != 0) + { + static bool IsUnexpectedLinuxError(LinuxError error) + { + return error != LinuxError.SUCCESS && error != LinuxError.ETIMEDOUT; + } + + // Hybrid approach + long budgetLeftMilliseconds; + + if (timeout == -1) + { + budgetLeftMilliseconds = PerformanceCounter.ElapsedMilliseconds + uint.MaxValue; + } + else + { + budgetLeftMilliseconds = PerformanceCounter.ElapsedMilliseconds + timeout; + } + + do + { + for (int i = 0; i < eventsByPollManager.Length; i++) + { + if (eventsByPollManager[i].Count == 0) + { + continue; + } + + errno = _pollManagers[i].Poll(eventsByPollManager[i], 0, out updateCount); + + if (IsUnexpectedLinuxError(errno)) + { + break; + } + + if (updateCount > 0) + { + break; + } + } + + if (updateCount > 0) + { + break; + } + + // If we are here, that mean nothing was available, sleep for 50ms + context.Device.System.KernelContext.Syscall.SleepThread(50 * 1000000); + context.Thread.HandlePostSyscall(); + } + while (context.Thread.Context.Running && PerformanceCounter.ElapsedMilliseconds < budgetLeftMilliseconds); + } + else if (timeout == -1) + { + // FIXME: If we get a timeout of -1 and there is no fds to wait on, this should kill the KProcess. (need to check that with re) + throw new InvalidOperationException(); + } + else + { + context.Device.System.KernelContext.Syscall.SleepThread(timeout); + } + + // TODO: Spanify + for (int i = 0; i < fdsCount; i++) + { + context.Memory.Write(outputBufferPosition + (ulong)(i * Unsafe.SizeOf()), events[i].Data); + } + + // In case of non blocking call timeout should not be returned. + if (timeout == 0 && errno == LinuxError.ETIMEDOUT) + { + errno = LinuxError.SUCCESS; + } + + return WriteBsdResult(context, updateCount, errno); + } + + [CommandCmif(7)] + // Sysctl(buffer, buffer) -> (i32 ret, u32 bsd_errno, u32, buffer) + public ResultCode Sysctl(ServiceCtx context) + { + WriteBsdResult(context, -1, LinuxError.EOPNOTSUPP); + + Logger.Stub?.PrintStub(LogClass.ServiceBsd); + + return ResultCode.Success; + } + + [CommandCmif(8)] + // Recv(u32 socket, u32 flags) -> (i32 ret, u32 bsd_errno, array message) + public ResultCode Recv(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + (ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22(); + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = socket.Receive(out result, receiveRegion.Memory.Span, socketFlags); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + + receiveRegion.Dispose(); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(9)] + // RecvFrom(u32 sock, u32 flags) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer message, buffer) + public ResultCode RecvFrom(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + (ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22(0); + (ulong sockAddrOutPosition, ulong sockAddrOutSize) = context.Request.GetBufferType0x22(1); + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = socket.ReceiveFrom(out result, receiveRegion.Memory.Span, receiveRegion.Memory.Span.Length, socketFlags, out IPEndPoint endPoint); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + + receiveRegion.Dispose(); + + if (sockAddrOutSize != 0 && sockAddrOutSize >= (ulong)Unsafe.SizeOf()) + { + context.Memory.Write(sockAddrOutPosition, BsdSockAddr.FromIPEndPoint(endPoint)); + } + else + { + errno = LinuxError.ENOMEM; + } + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(10)] + // Send(u32 socket, u32 flags, buffer) -> (i32 ret, u32 bsd_errno) + public ResultCode Send(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + (ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21(); + + ReadOnlySpan sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = socket.Send(out result, sendBuffer, socketFlags); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(11)] + // SendTo(u32 socket, u32 flags, buffer, buffer) -> (i32 ret, u32 bsd_errno) + public ResultCode SendTo(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + (ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21(0); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(1); +#pragma warning restore IDE0059 + + ReadOnlySpan sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + IPEndPoint endPoint = context.Memory.Read(bufferPosition).ToIPEndPoint(); + + errno = socket.SendTo(out result, sendBuffer, sendBuffer.Length, socketFlags, endPoint); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(12)] + // Accept(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer addr) + public ResultCode Accept(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x22(); +#pragma warning restore IDE0059 + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = socket.Accept(out ISocket newSocket); + + if (newSocket == null && errno == LinuxError.SUCCESS) + { + errno = LinuxError.EWOULDBLOCK; + } + else if (errno == LinuxError.SUCCESS) + { + int newSockFd = _context.RegisterFileDescriptor(newSocket); + + if (newSockFd == -1) + { + errno = LinuxError.EBADF; + } + else + { + WriteSockAddr(context, bufferPos, newSocket, true); + } + + WriteBsdResult(context, newSockFd, errno); + + context.ResponseData.Write(0x10); + + return ResultCode.Success; + } + } + + return WriteBsdResult(context, -1, errno); + } + + [CommandCmif(13)] + // Bind(u32 socket, buffer addr) -> (i32 ret, u32 bsd_errno) + public ResultCode Bind(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); +#pragma warning restore IDE0059 + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + IPEndPoint endPoint = context.Memory.Read(bufferPosition).ToIPEndPoint(); + + errno = socket.Bind(endPoint); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(14)] + // Connect(u32 socket, buffer) -> (i32 ret, u32 bsd_errno) + public ResultCode Connect(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); +#pragma warning restore IDE0059 + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + IPEndPoint endPoint = context.Memory.Read(bufferPosition).ToIPEndPoint(); + + errno = socket.Connect(endPoint); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(15)] + // GetPeerName(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer addr) + public ResultCode GetPeerName(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(); +#pragma warning restore IDE0059 + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + if (socket != null) + { + errno = LinuxError.ENOTCONN; + + if (socket.RemoteEndPoint != null) + { + errno = LinuxError.SUCCESS; + + WriteSockAddr(context, bufferPosition, socket, true); + WriteBsdResult(context, 0, errno); + context.ResponseData.Write(Unsafe.SizeOf()); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(16)] + // GetSockName(u32 socket) -> (i32 ret, u32 bsd_errno, u32 addrlen, buffer addr) + public ResultCode GetSockName(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x22(); +#pragma warning restore IDE0059 + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + WriteSockAddr(context, bufferPos, socket, false); + WriteBsdResult(context, 0, errno); + context.ResponseData.Write(Unsafe.SizeOf()); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(17)] + // GetSockOpt(u32 socket, u32 level, u32 option_name) -> (i32 ret, u32 bsd_errno, u32, buffer) + public ResultCode GetSockOpt(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32(); + BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32(); + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(); + WritableRegion optionValue = context.Memory.GetWritableRegion(bufferPosition, (int)bufferSize); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = socket.GetSocketOption(option, level, optionValue.Memory.Span); + + if (errno == LinuxError.SUCCESS) + { + optionValue.Dispose(); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(18)] + // Listen(u32 socket, u32 backlog) -> (i32 ret, u32 bsd_errno) + public ResultCode Listen(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int backlog = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = socket.Listen(backlog); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(19)] + // Ioctl(u32 fd, u32 request, u32 bufcount, buffer, buffer, buffer, buffer) -> (i32 ret, u32 bsd_errno, buffer, buffer, buffer, buffer) + public ResultCode Ioctl(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + BsdIoctl cmd = (BsdIoctl)context.RequestData.ReadInt32(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + int bufferCount = context.RequestData.ReadInt32(); +#pragma warning restore IDE0059 + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + switch (cmd) + { + case BsdIoctl.AtMark: + errno = LinuxError.SUCCESS; + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(); +#pragma warning restore IDE0059 + + // FIXME: OOB not implemented. + context.Memory.Write(bufferPosition, 0); + break; + + default: + errno = LinuxError.EOPNOTSUPP; + + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Ioctl Cmd: {cmd}"); + break; + } + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(20)] + // Fcntl(u32 socket, u32 cmd, u32 arg) -> (i32 ret, u32 bsd_errno) + public ResultCode Fcntl(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int cmd = context.RequestData.ReadInt32(); + int arg = context.RequestData.ReadInt32(); + + int result = 0; + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.SUCCESS; + + if (cmd == 0x3) + { + result = !socket.Blocking ? 0x800 : 0; + } + else if (cmd == 0x4 && arg == 0x800) + { + socket.Blocking = false; + result = 0; + } + else + { + errno = LinuxError.EOPNOTSUPP; + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(21)] + // SetSockOpt(u32 socket, u32 level, u32 option_name, buffer option_value) -> (i32 ret, u32 bsd_errno) + public ResultCode SetSockOpt(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + SocketOptionLevel level = (SocketOptionLevel)context.RequestData.ReadInt32(); + BsdSocketOption option = (BsdSocketOption)context.RequestData.ReadInt32(); + + (ulong bufferPos, ulong bufferSize) = context.Request.GetBufferType0x21(); + + ReadOnlySpan optionValue = context.Memory.GetSpan(bufferPos, (int)bufferSize); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = socket.SetSocketOption(option, level, optionValue); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(22)] + // Shutdown(u32 socket, u32 how) -> (i32 ret, u32 bsd_errno) + public ResultCode Shutdown(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int how = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + + if (socket != null) + { + errno = LinuxError.EINVAL; + + if (how >= 0 && how <= 2) + { + errno = socket.Shutdown((BsdSocketShutdownFlags)how); + } + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(23)] + // ShutdownAllSockets(u32 how) -> (i32 ret, u32 bsd_errno) + public ResultCode ShutdownAllSockets(ServiceCtx context) + { + int how = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EINVAL; + + if (how >= 0 && how <= 2) + { + errno = _context.ShutdownAllSockets((BsdSocketShutdownFlags)how); + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(24)] + // Write(u32 fd, buffer message) -> (i32 ret, u32 bsd_errno) + public ResultCode Write(ServiceCtx context) + { + int fd = context.RequestData.ReadInt32(); + + (ulong sendPosition, ulong sendSize) = context.Request.GetBufferType0x21(); + + ReadOnlySpan sendBuffer = context.Memory.GetSpan(sendPosition, (int)sendSize); + + LinuxError errno = LinuxError.EBADF; + IFileDescriptor file = _context.RetrieveFileDescriptor(fd); + int result = -1; + + if (file != null) + { + errno = file.Write(out result, sendBuffer); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(file, result); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(25)] + // Read(u32 fd) -> (i32 ret, u32 bsd_errno, buffer message) + public ResultCode Read(ServiceCtx context) + { + int fd = context.RequestData.ReadInt32(); + + (ulong receivePosition, ulong receiveLength) = context.Request.GetBufferType0x22(); + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + IFileDescriptor file = _context.RetrieveFileDescriptor(fd); + int result = -1; + + if (file != null) + { + errno = file.Read(out result, receiveRegion.Memory.Span); + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(file, result); + + receiveRegion.Dispose(); + } + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(26)] + // Close(u32 fd) -> (i32 ret, u32 bsd_errno) + public ResultCode Close(ServiceCtx context) + { + int fd = context.RequestData.ReadInt32(); + + LinuxError errno = LinuxError.EBADF; + + if (_context.CloseFileDescriptor(fd)) + { + errno = LinuxError.SUCCESS; + } + + return WriteBsdResult(context, 0, errno); + } + + [CommandCmif(27)] + // DuplicateSocket(u32 fd, u64 reserved) -> (i32 ret, u32 bsd_errno) + public ResultCode DuplicateSocket(ServiceCtx context) + { + int fd = context.RequestData.ReadInt32(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong reserved = context.RequestData.ReadUInt64(); +#pragma warning restore IDE0059 + + LinuxError errno = LinuxError.ENOENT; + int newSockFd = -1; + + if (_isPrivileged) + { + errno = LinuxError.SUCCESS; + + newSockFd = _context.DuplicateFileDescriptor(fd); + + if (newSockFd == -1) + { + errno = LinuxError.EBADF; + } + } + + return WriteBsdResult(context, newSockFd, errno); + } + + + [CommandCmif(29)] // 7.0.0+ + // RecvMMsg(u32 fd, u32 vlen, u32 flags, u32 reserved, nn::socket::TimeVal timeout) -> (i32 ret, u32 bsd_errno, buffer message); + public ResultCode RecvMMsg(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int vlen = context.RequestData.ReadInt32(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + uint reserved = context.RequestData.ReadUInt32(); +#pragma warning restore IDE0059 + TimeVal timeout = context.RequestData.ReadStruct(); + + ulong receivePosition = context.Request.ReceiveBuff[0].Position; + ulong receiveLength = context.Request.ReceiveBuff[0].Size; + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = BsdMMsgHdr.Deserialize(out BsdMMsgHdr message, receiveRegion.Memory.Span, vlen); + + if (errno == LinuxError.SUCCESS) + { + errno = socket.RecvMMsg(out result, message, socketFlags, timeout); + + if (errno == LinuxError.SUCCESS) + { + errno = BsdMMsgHdr.Serialize(receiveRegion.Memory.Span, message); + } + } + } + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + receiveRegion.Dispose(); + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(30)] // 7.0.0+ + // SendMMsg(u32 fd, u32 vlen, u32 flags) -> (i32 ret, u32 bsd_errno, buffer message); + public ResultCode SendMMsg(ServiceCtx context) + { + int socketFd = context.RequestData.ReadInt32(); + int vlen = context.RequestData.ReadInt32(); + BsdSocketFlags socketFlags = (BsdSocketFlags)context.RequestData.ReadInt32(); + + ulong receivePosition = context.Request.ReceiveBuff[0].Position; + ulong receiveLength = context.Request.ReceiveBuff[0].Size; + + WritableRegion receiveRegion = context.Memory.GetWritableRegion(receivePosition, (int)receiveLength); + + LinuxError errno = LinuxError.EBADF; + ISocket socket = _context.RetrieveSocket(socketFd); + int result = -1; + + if (socket != null) + { + errno = BsdMMsgHdr.Deserialize(out BsdMMsgHdr message, receiveRegion.Memory.Span, vlen); + + if (errno == LinuxError.SUCCESS) + { + errno = socket.SendMMsg(out result, message, socketFlags); + + if (errno == LinuxError.SUCCESS) + { + errno = BsdMMsgHdr.Serialize(receiveRegion.Memory.Span, message); + } + } + } + + if (errno == LinuxError.SUCCESS) + { + SetResultErrno(socket, result); + receiveRegion.Dispose(); + } + + return WriteBsdResult(context, result, errno); + } + + [CommandCmif(31)] // 7.0.0+ + // EventFd(nn::socket::EventFdFlags flags, u64 initval) -> (i32 ret, u32 bsd_errno) + public ResultCode EventFd(ServiceCtx context) + { + EventFdFlags flags = (EventFdFlags)context.RequestData.ReadUInt32(); + context.RequestData.BaseStream.Position += 4; // Padding + ulong initialValue = context.RequestData.ReadUInt64(); + + EventFileDescriptor newEventFile = new(initialValue, flags); + + LinuxError errno = LinuxError.SUCCESS; + + int newSockFd = _context.RegisterFileDescriptor(newEventFile); + + if (newSockFd == -1) + { + errno = LinuxError.EBADF; + } + + return WriteBsdResult(context, newSockFd, errno); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs new file mode 100644 index 00000000..6c00d5e1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs @@ -0,0 +1,15 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + interface IFileDescriptor : IDisposable + { + bool Blocking { get; set; } + int Refcount { get; set; } + + LinuxError Read(out int readSize, Span buffer); + + LinuxError Write(out int writeSize, ReadOnlySpan buffer); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs new file mode 100644 index 00000000..fe2f8477 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs @@ -0,0 +1,53 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Net; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + interface ISocket : IFileDescriptor + { + IPEndPoint RemoteEndPoint { get; } + IPEndPoint LocalEndPoint { get; } + + AddressFamily AddressFamily { get; } + + SocketType SocketType { get; } + + ProtocolType ProtocolType { get; } + + IntPtr Handle { get; } + + LinuxError Receive(out int receiveSize, Span buffer, BsdSocketFlags flags); + + LinuxError ReceiveFrom(out int receiveSize, Span buffer, int size, BsdSocketFlags flags, out IPEndPoint remoteEndPoint); + + LinuxError Send(out int sendSize, ReadOnlySpan buffer, BsdSocketFlags flags); + + LinuxError SendTo(out int sendSize, ReadOnlySpan buffer, int size, BsdSocketFlags flags, IPEndPoint remoteEndPoint); + + LinuxError RecvMMsg(out int vlen, BsdMMsgHdr message, BsdSocketFlags flags, TimeVal timeout); + + LinuxError SendMMsg(out int vlen, BsdMMsgHdr message, BsdSocketFlags flags); + + LinuxError GetSocketOption(BsdSocketOption option, SocketOptionLevel level, Span optionValue); + + LinuxError SetSocketOption(BsdSocketOption option, SocketOptionLevel level, ReadOnlySpan optionValue); + + bool Poll(int microSeconds, SelectMode mode); + + LinuxError Bind(IPEndPoint localEndPoint); + + LinuxError Connect(IPEndPoint remoteEndPoint); + + LinuxError Listen(int backlog); + + LinuxError Accept(out ISocket newSocket); + + void Disconnect(); + + LinuxError Shutdown(BsdSocketShutdownFlags how); + + void Close(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs new file mode 100644 index 00000000..5b9e6811 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs @@ -0,0 +1,153 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + class EventFileDescriptor : IFileDescriptor + { + private ulong _value; + private readonly EventFdFlags _flags; + + private readonly object _lock = new(); + + public bool Blocking { get => !_flags.HasFlag(EventFdFlags.NonBlocking); set => throw new NotSupportedException(); } + + public ManualResetEvent WriteEvent { get; } + public ManualResetEvent ReadEvent { get; } + + public EventFileDescriptor(ulong value, EventFdFlags flags) + { + // FIXME: We should support blocking operations. + // Right now they can't be supported because it would cause the + // service to lock up as we only have one thread processing requests. + flags |= EventFdFlags.NonBlocking; + + _value = value; + _flags = flags; + + WriteEvent = new ManualResetEvent(false); + ReadEvent = new ManualResetEvent(false); + UpdateEventStates(); + } + + public int Refcount { get; set; } + + public void Dispose() + { + WriteEvent.Dispose(); + ReadEvent.Dispose(); + } + + private void ResetEventStates() + { + WriteEvent.Reset(); + ReadEvent.Reset(); + } + + private void UpdateEventStates() + { + if (_value > 0) + { + ReadEvent.Set(); + } + + if (_value != uint.MaxValue - 1) + { + WriteEvent.Set(); + } + } + + public LinuxError Read(out int readSize, Span buffer) + { + if (buffer.Length < sizeof(ulong)) + { + readSize = 0; + + return LinuxError.EINVAL; + } + + lock (_lock) + { + ResetEventStates(); + + ref ulong count = ref MemoryMarshal.Cast(buffer)[0]; + + if (_value == 0) + { + if (Blocking) + { + while (_value == 0) + { + Monitor.Wait(_lock); + } + } + else + { + readSize = 0; + + UpdateEventStates(); + return LinuxError.EAGAIN; + } + } + + readSize = sizeof(ulong); + + if (_flags.HasFlag(EventFdFlags.Semaphore)) + { + --_value; + + count = 1; + } + else + { + count = _value; + + _value = 0; + } + + UpdateEventStates(); + return LinuxError.SUCCESS; + } + } + + public LinuxError Write(out int writeSize, ReadOnlySpan buffer) + { + if (!MemoryMarshal.TryRead(buffer, out ulong count) || count == ulong.MaxValue) + { + writeSize = 0; + + return LinuxError.EINVAL; + } + + lock (_lock) + { + ResetEventStates(); + + if (_value > _value + count) + { + if (Blocking) + { + Monitor.Wait(_lock); + } + else + { + writeSize = 0; + + UpdateEventStates(); + return LinuxError.EAGAIN; + } + } + + writeSize = sizeof(ulong); + + _value += count; + Monitor.Pulse(_lock); + + UpdateEventStates(); + return LinuxError.SUCCESS; + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs new file mode 100644 index 00000000..a29554c3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs @@ -0,0 +1,119 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + class EventFileDescriptorPollManager : IPollManager + { + private static EventFileDescriptorPollManager _instance; + + public static EventFileDescriptorPollManager Instance + { + get + { + _instance ??= new EventFileDescriptorPollManager(); + + return _instance; + } + } + + public bool IsCompatible(PollEvent evnt) + { + return evnt.FileDescriptor is EventFileDescriptor; + } + + public LinuxError Poll(List events, int timeoutMilliseconds, out int updatedCount) + { + updatedCount = 0; + + List waiters = new(); + + for (int i = 0; i < events.Count; i++) + { + PollEvent evnt = events[i]; + + EventFileDescriptor socket = (EventFileDescriptor)evnt.FileDescriptor; + + bool isValidEvent = false; + + if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Input) || + evnt.Data.InputEvents.HasFlag(PollEventTypeMask.UrgentInput)) + { + waiters.Add(socket.ReadEvent); + + isValidEvent = true; + } + if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Output)) + { + waiters.Add(socket.WriteEvent); + + isValidEvent = true; + } + + if (!isValidEvent) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Poll input event type: {evnt.Data.InputEvents}"); + + return LinuxError.EINVAL; + } + } + + int index = WaitHandle.WaitAny(waiters.ToArray(), timeoutMilliseconds); + + if (index != WaitHandle.WaitTimeout) + { + for (int i = 0; i < events.Count; i++) + { + PollEventTypeMask outputEvents = 0; + + PollEvent evnt = events[i]; + + EventFileDescriptor socket = (EventFileDescriptor)evnt.FileDescriptor; + + if (socket.ReadEvent.WaitOne(0)) + { + if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Input)) + { + outputEvents |= PollEventTypeMask.Input; + } + + if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.UrgentInput)) + { + outputEvents |= PollEventTypeMask.UrgentInput; + } + } + + if ((evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Output)) + && socket.WriteEvent.WaitOne(0)) + { + outputEvents |= PollEventTypeMask.Output; + } + + + if (outputEvents != 0) + { + evnt.Data.OutputEvents = outputEvents; + + updatedCount++; + } + } + } + else + { + return LinuxError.ETIMEDOUT; + } + + return LinuxError.SUCCESS; + } + + public LinuxError Select(List events, int timeout, out int updatedCount) + { + // TODO: Implement Select for event file descriptors + updatedCount = 0; + + return LinuxError.EOPNOTSUPP; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs new file mode 100644 index 00000000..c42b7201 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs @@ -0,0 +1,549 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + class ManagedSocket : ISocket + { + public int Refcount { get; set; } + + public AddressFamily AddressFamily => Socket.AddressFamily; + + public SocketType SocketType => Socket.SocketType; + + public ProtocolType ProtocolType => Socket.ProtocolType; + + public bool Blocking { get => Socket.Blocking; set => Socket.Blocking = value; } + + public IntPtr Handle => Socket.Handle; + + public IPEndPoint RemoteEndPoint => Socket.RemoteEndPoint as IPEndPoint; + + public IPEndPoint LocalEndPoint => Socket.LocalEndPoint as IPEndPoint; + + public Socket Socket { get; } + + public ManagedSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) + { + Socket = new Socket(addressFamily, socketType, protocolType); + Refcount = 1; + } + + private ManagedSocket(Socket socket) + { + Socket = socket; + Refcount = 1; + } + + private static SocketFlags ConvertBsdSocketFlags(BsdSocketFlags bsdSocketFlags) + { + SocketFlags socketFlags = SocketFlags.None; + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.Oob)) + { + socketFlags |= SocketFlags.OutOfBand; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.Peek)) + { + socketFlags |= SocketFlags.Peek; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.DontRoute)) + { + socketFlags |= SocketFlags.DontRoute; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.Trunc)) + { + socketFlags |= SocketFlags.Truncated; + } + + if (bsdSocketFlags.HasFlag(BsdSocketFlags.CTrunc)) + { + socketFlags |= SocketFlags.ControlDataTruncated; + } + + bsdSocketFlags &= ~(BsdSocketFlags.Oob | + BsdSocketFlags.Peek | + BsdSocketFlags.DontRoute | + BsdSocketFlags.DontWait | + BsdSocketFlags.Trunc | + BsdSocketFlags.CTrunc); + + if (bsdSocketFlags != BsdSocketFlags.None) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported socket flags: {bsdSocketFlags}"); + } + + return socketFlags; + } + + public LinuxError Accept(out ISocket newSocket) + { + try + { + newSocket = new ManagedSocket(Socket.Accept()); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + newSocket = null; + + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError Bind(IPEndPoint localEndPoint) + { + try + { + Socket.Bind(localEndPoint); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public void Close() + { + Socket.Close(); + } + + public LinuxError Connect(IPEndPoint remoteEndPoint) + { + try + { + Socket.Connect(remoteEndPoint); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + if (!Blocking && exception.ErrorCode == (int)WsaError.WSAEWOULDBLOCK) + { + return LinuxError.EINPROGRESS; + } + else + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + } + + public void Disconnect() + { + Socket.Disconnect(true); + } + + public void Dispose() + { + Socket.Close(); + Socket.Dispose(); + } + + public LinuxError Listen(int backlog) + { + try + { + Socket.Listen(backlog); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public bool Poll(int microSeconds, SelectMode mode) + { + return Socket.Poll(microSeconds, mode); + } + + public LinuxError Shutdown(BsdSocketShutdownFlags how) + { + try + { + Socket.Shutdown((SocketShutdown)how); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError Receive(out int receiveSize, Span buffer, BsdSocketFlags flags) + { + LinuxError result; + + bool shouldBlockAfterOperation = false; + + try + { + if (Blocking && flags.HasFlag(BsdSocketFlags.DontWait)) + { + Blocking = false; + shouldBlockAfterOperation = true; + } + + receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags)); + + result = LinuxError.SUCCESS; + } + catch (SocketException exception) + { + receiveSize = -1; + + result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + + if (shouldBlockAfterOperation) + { + Blocking = true; + } + + return result; + } + + public LinuxError ReceiveFrom(out int receiveSize, Span buffer, int size, BsdSocketFlags flags, out IPEndPoint remoteEndPoint) + { + remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); + + LinuxError result; + + bool shouldBlockAfterOperation = false; + + try + { + EndPoint temp = new IPEndPoint(IPAddress.Any, 0); + + if (Blocking && flags.HasFlag(BsdSocketFlags.DontWait)) + { + Blocking = false; + shouldBlockAfterOperation = true; + } + + if (!Socket.IsBound) + { + receiveSize = -1; + + return LinuxError.EOPNOTSUPP; + } + + receiveSize = Socket.ReceiveFrom(buffer[..size], ConvertBsdSocketFlags(flags), ref temp); + + remoteEndPoint = (IPEndPoint)temp; + result = LinuxError.SUCCESS; + } + catch (SocketException exception) + { + receiveSize = -1; + + result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + + if (shouldBlockAfterOperation) + { + Blocking = true; + } + + return result; + } + + public LinuxError Send(out int sendSize, ReadOnlySpan buffer, BsdSocketFlags flags) + { + try + { + sendSize = Socket.Send(buffer, ConvertBsdSocketFlags(flags)); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + sendSize = -1; + + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError SendTo(out int sendSize, ReadOnlySpan buffer, int size, BsdSocketFlags flags, IPEndPoint remoteEndPoint) + { + try + { + sendSize = Socket.SendTo(buffer[..size], ConvertBsdSocketFlags(flags), remoteEndPoint); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + sendSize = -1; + + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError GetSocketOption(BsdSocketOption option, SocketOptionLevel level, Span optionValue) + { + try + { + LinuxError result = WinSockHelper.ValidateSocketOption(option, level, write: false); + + if (result != LinuxError.SUCCESS) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Invalid GetSockOpt Option: {option} Level: {level}"); + + return result; + } + + if (!WinSockHelper.TryConvertSocketOption(option, level, out SocketOptionName optionName)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported GetSockOpt Option: {option} Level: {level}"); + optionValue.Clear(); + + return LinuxError.SUCCESS; + } + + byte[] tempOptionValue = new byte[optionValue.Length]; + + Socket.GetSocketOption(level, optionName, tempOptionValue); + + tempOptionValue.AsSpan().CopyTo(optionValue); + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError SetSocketOption(BsdSocketOption option, SocketOptionLevel level, ReadOnlySpan optionValue) + { + try + { + LinuxError result = WinSockHelper.ValidateSocketOption(option, level, write: true); + + if (result != LinuxError.SUCCESS) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Invalid SetSockOpt Option: {option} Level: {level}"); + + return result; + } + + if (!WinSockHelper.TryConvertSocketOption(option, level, out SocketOptionName optionName)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported SetSockOpt Option: {option} Level: {level}"); + + return LinuxError.SUCCESS; + } + + int value = optionValue.Length >= 4 ? MemoryMarshal.Read(optionValue) : MemoryMarshal.Read(optionValue); + + if (level == SocketOptionLevel.Socket && option == BsdSocketOption.SoLinger) + { + int value2 = 0; + + if (optionValue.Length >= 8) + { + value2 = MemoryMarshal.Read(optionValue[4..]); + } + + Socket.SetSocketOption(level, SocketOptionName.Linger, new LingerOption(value != 0, value2)); + } + else + { + Socket.SetSocketOption(level, optionName, value); + } + + return LinuxError.SUCCESS; + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError Read(out int readSize, Span buffer) + { + return Receive(out readSize, buffer, BsdSocketFlags.None); + } + + public LinuxError Write(out int writeSize, ReadOnlySpan buffer) + { + return Send(out writeSize, buffer, BsdSocketFlags.None); + } + + private bool CanSupportMMsgHdr(BsdMMsgHdr message) + { + for (int i = 0; i < message.Messages.Length; i++) + { + if (message.Messages[i].Name != null || + message.Messages[i].Control != null) + { + return false; + } + } + + return true; + } + + private static IList> ConvertMessagesToBuffer(BsdMMsgHdr message) + { + int segmentCount = 0; + int index = 0; + + foreach (BsdMsgHdr msgHeader in message.Messages) + { + segmentCount += msgHeader.Iov.Length; + } + + ArraySegment[] buffers = new ArraySegment[segmentCount]; + + foreach (BsdMsgHdr msgHeader in message.Messages) + { + foreach (byte[] iov in msgHeader.Iov) + { + buffers[index++] = new ArraySegment(iov); + } + + // Clear the length + msgHeader.Length = 0; + } + + return buffers; + } + + private static void UpdateMessages(out int vlen, BsdMMsgHdr message, int transferedSize) + { + int bytesLeft = transferedSize; + int index = 0; + + while (bytesLeft > 0) + { + // First ensure we haven't finished all buffers + if (index >= message.Messages.Length) + { + break; + } + + BsdMsgHdr msgHeader = message.Messages[index]; + + int possiblyTransferedBytes = 0; + + foreach (byte[] iov in msgHeader.Iov) + { + possiblyTransferedBytes += iov.Length; + } + + int storedBytes; + + if (bytesLeft > possiblyTransferedBytes) + { + storedBytes = possiblyTransferedBytes; + index++; + } + else + { + storedBytes = bytesLeft; + } + + msgHeader.Length = (uint)storedBytes; + bytesLeft -= storedBytes; + } + + Debug.Assert(bytesLeft == 0); + + vlen = index + 1; + } + + // TODO: Find a way to support passing the timeout somehow without changing the socket ReceiveTimeout. + public LinuxError RecvMMsg(out int vlen, BsdMMsgHdr message, BsdSocketFlags flags, TimeVal timeout) + { + vlen = 0; + + if (message.Messages.Length == 0) + { + return LinuxError.SUCCESS; + } + + if (!CanSupportMMsgHdr(message)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, "Unsupported BsdMMsgHdr"); + + return LinuxError.EOPNOTSUPP; + } + + if (message.Messages.Length == 0) + { + return LinuxError.SUCCESS; + } + + try + { + int receiveSize = Socket.Receive(ConvertMessagesToBuffer(message), ConvertBsdSocketFlags(flags), out SocketError socketError); + + if (receiveSize > 0) + { + UpdateMessages(out vlen, message, receiveSize); + } + + return WinSockHelper.ConvertError((WsaError)socketError); + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + + public LinuxError SendMMsg(out int vlen, BsdMMsgHdr message, BsdSocketFlags flags) + { + vlen = 0; + + if (message.Messages.Length == 0) + { + return LinuxError.SUCCESS; + } + + if (!CanSupportMMsgHdr(message)) + { + Logger.Warning?.Print(LogClass.ServiceBsd, "Unsupported BsdMMsgHdr"); + + return LinuxError.EOPNOTSUPP; + } + + if (message.Messages.Length == 0) + { + return LinuxError.SUCCESS; + } + + try + { + int sendSize = Socket.Send(ConvertMessagesToBuffer(message), ConvertBsdSocketFlags(flags), out SocketError socketError); + + if (sendSize > 0) + { + UpdateMessages(out vlen, message, sendSize); + } + + return WinSockHelper.ConvertError((WsaError)socketError); + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs new file mode 100644 index 00000000..d0db4408 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs @@ -0,0 +1,174 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System.Collections.Generic; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + class ManagedSocketPollManager : IPollManager + { + private static ManagedSocketPollManager _instance; + + public static ManagedSocketPollManager Instance + { + get + { + _instance ??= new ManagedSocketPollManager(); + + return _instance; + } + } + + public bool IsCompatible(PollEvent evnt) + { + return evnt.FileDescriptor is ManagedSocket; + } + + public LinuxError Poll(List events, int timeoutMilliseconds, out int updatedCount) + { + List readEvents = new(); + List writeEvents = new(); + List errorEvents = new(); + + updatedCount = 0; + + foreach (PollEvent evnt in events) + { + ManagedSocket socket = (ManagedSocket)evnt.FileDescriptor; + + bool isValidEvent = evnt.Data.InputEvents == 0; + + errorEvents.Add(socket.Socket); + + if ((evnt.Data.InputEvents & PollEventTypeMask.Input) != 0) + { + readEvents.Add(socket.Socket); + + isValidEvent = true; + } + + if ((evnt.Data.InputEvents & PollEventTypeMask.UrgentInput) != 0) + { + readEvents.Add(socket.Socket); + + isValidEvent = true; + } + + if ((evnt.Data.InputEvents & PollEventTypeMask.Output) != 0) + { + writeEvents.Add(socket.Socket); + + isValidEvent = true; + } + + if (!isValidEvent) + { + Logger.Warning?.Print(LogClass.ServiceBsd, $"Unsupported Poll input event type: {evnt.Data.InputEvents}"); + return LinuxError.EINVAL; + } + } + + try + { + int actualTimeoutMicroseconds = timeoutMilliseconds == -1 ? -1 : timeoutMilliseconds * 1000; + + Socket.Select(readEvents, writeEvents, errorEvents, actualTimeoutMicroseconds); + } + catch (SocketException exception) + { + return WinSockHelper.ConvertError((WsaError)exception.ErrorCode); + } + + foreach (PollEvent evnt in events) + { + Socket socket = ((ManagedSocket)evnt.FileDescriptor).Socket; + + PollEventTypeMask outputEvents = evnt.Data.OutputEvents & ~evnt.Data.InputEvents; + + if (errorEvents.Contains(socket)) + { + outputEvents |= PollEventTypeMask.Error; + + if (!socket.Connected || !socket.IsBound) + { + outputEvents |= PollEventTypeMask.Disconnected; + } + } + + if (readEvents.Contains(socket)) + { + if ((evnt.Data.InputEvents & PollEventTypeMask.Input) != 0) + { + outputEvents |= PollEventTypeMask.Input; + } + } + + if (writeEvents.Contains(socket)) + { + outputEvents |= PollEventTypeMask.Output; + } + + evnt.Data.OutputEvents = outputEvents; + } + + updatedCount = readEvents.Count + writeEvents.Count + errorEvents.Count; + + return LinuxError.SUCCESS; + } + + public LinuxError Select(List events, int timeout, out int updatedCount) + { + List readEvents = new(); + List writeEvents = new(); + List errorEvents = new(); + + updatedCount = 0; + + foreach (PollEvent pollEvent in events) + { + ManagedSocket socket = (ManagedSocket)pollEvent.FileDescriptor; + + if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Input)) + { + readEvents.Add(socket.Socket); + } + + if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Output)) + { + writeEvents.Add(socket.Socket); + } + + if (pollEvent.Data.InputEvents.HasFlag(PollEventTypeMask.Error)) + { + errorEvents.Add(socket.Socket); + } + } + + Socket.Select(readEvents, writeEvents, errorEvents, timeout); + + updatedCount = readEvents.Count + writeEvents.Count + errorEvents.Count; + + foreach (PollEvent pollEvent in events) + { + ManagedSocket socket = (ManagedSocket)pollEvent.FileDescriptor; + + if (readEvents.Contains(socket.Socket)) + { + pollEvent.Data.OutputEvents |= PollEventTypeMask.Input; + } + + if (writeEvents.Contains(socket.Socket)) + { + pollEvent.Data.OutputEvents |= PollEventTypeMask.Output; + } + + if (errorEvents.Contains(socket.Socket)) + { + pollEvent.Data.OutputEvents |= PollEventTypeMask.Error; + } + } + + return LinuxError.SUCCESS; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WSAError.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WSAError.cs new file mode 100644 index 00000000..cd8e530c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WSAError.cs @@ -0,0 +1,134 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum WsaError + { + /* + * All Windows Sockets error constants are biased by WSABASEERR from + * the "normal" + */ + WSABASEERR = 10000, + + /* + * Windows Sockets definitions of regular Microsoft C error constants + */ + WSAEINTR = (WSABASEERR + 4), + WSAEBADF = (WSABASEERR + 9), + WSAEACCES = (WSABASEERR + 13), + WSAEFAULT = (WSABASEERR + 14), + WSAEINVAL = (WSABASEERR + 22), + WSAEMFILE = (WSABASEERR + 24), + + /* + * Windows Sockets definitions of regular Berkeley error constants + */ + WSAEWOULDBLOCK = (WSABASEERR + 35), + WSAEINPROGRESS = (WSABASEERR + 36), + WSAEALREADY = (WSABASEERR + 37), + WSAENOTSOCK = (WSABASEERR + 38), + WSAEDESTADDRREQ = (WSABASEERR + 39), + WSAEMSGSIZE = (WSABASEERR + 40), + WSAEPROTOTYPE = (WSABASEERR + 41), + WSAENOPROTOOPT = (WSABASEERR + 42), + WSAEPROTONOSUPPORT = (WSABASEERR + 43), + WSAESOCKTNOSUPPORT = (WSABASEERR + 44), + WSAEOPNOTSUPP = (WSABASEERR + 45), + WSAEPFNOSUPPORT = (WSABASEERR + 46), + WSAEAFNOSUPPORT = (WSABASEERR + 47), + WSAEADDRINUSE = (WSABASEERR + 48), + WSAEADDRNOTAVAIL = (WSABASEERR + 49), + WSAENETDOWN = (WSABASEERR + 50), + WSAENETUNREACH = (WSABASEERR + 51), + WSAENETRESET = (WSABASEERR + 52), + WSAECONNABORTED = (WSABASEERR + 53), + WSAECONNRESET = (WSABASEERR + 54), + WSAENOBUFS = (WSABASEERR + 55), + WSAEISCONN = (WSABASEERR + 56), + WSAENOTCONN = (WSABASEERR + 57), + WSAESHUTDOWN = (WSABASEERR + 58), + WSAETOOMANYREFS = (WSABASEERR + 59), + WSAETIMEDOUT = (WSABASEERR + 60), + WSAECONNREFUSED = (WSABASEERR + 61), + WSAELOOP = (WSABASEERR + 62), + WSAENAMETOOLONG = (WSABASEERR + 63), + WSAEHOSTDOWN = (WSABASEERR + 64), + WSAEHOSTUNREACH = (WSABASEERR + 65), + WSAENOTEMPTY = (WSABASEERR + 66), + WSAEPROCLIM = (WSABASEERR + 67), + WSAEUSERS = (WSABASEERR + 68), + WSAEDQUOT = (WSABASEERR + 69), + WSAESTALE = (WSABASEERR + 70), + WSAEREMOTE = (WSABASEERR + 71), + + /* + * Extended Windows Sockets error constant definitions + */ + WSASYSNOTREADY = (WSABASEERR + 91), + WSAVERNOTSUPPORTED = (WSABASEERR + 92), + WSANOTINITIALISED = (WSABASEERR + 93), + WSAEDISCON = (WSABASEERR + 101), + WSAENOMORE = (WSABASEERR + 102), + WSAECANCELLED = (WSABASEERR + 103), + WSAEINVALIDPROCTABLE = (WSABASEERR + 104), + WSAEINVALIDPROVIDER = (WSABASEERR + 105), + WSAEPROVIDERFAILEDINIT = (WSABASEERR + 106), + WSASYSCALLFAILURE = (WSABASEERR + 107), + WSASERVICE_NOT_FOUND = (WSABASEERR + 108), + WSATYPE_NOT_FOUND = (WSABASEERR + 109), + WSA_E_NO_MORE = (WSABASEERR + 110), + WSA_E_CANCELLED = (WSABASEERR + 111), + WSAEREFUSED = (WSABASEERR + 112), + + /* + * Error return codes from gethostbyname() and gethostbyaddr() + * (when using the resolver). Note that these errors are + * retrieved via WSAGetLastError() and must therefore follow + * the rules for avoiding clashes with error numbers from + * specific implementations or language run-time systems. + * For this reason the codes are based at WSABASEERR+1001. + * Note also that [WSA]NO_ADDRESS is defined only for + * compatibility purposes. + */ + + /* Authoritative Answer: Host not found */ + WSAHOST_NOT_FOUND = (WSABASEERR + 1001), + + /* Non-Authoritative: Host not found, or SERVERFAIL */ + WSATRY_AGAIN = (WSABASEERR + 1002), + + /* Non-recoverable errors, FORMERR, REFUSED, NOTIMP */ + WSANO_RECOVERY = (WSABASEERR + 1003), + + /* Valid name, no data record of requested type */ + WSANO_DATA = (WSABASEERR + 1004), + + /* + * Define QOS related error return codes + * + */ + WSA_QOS_RECEIVERS = (WSABASEERR + 1005), + /* at least one Reserve has arrived */ + WSA_QOS_SENDERS = (WSABASEERR + 1006), + /* at least one Path has arrived */ + WSA_QOS_NO_SENDERS = (WSABASEERR + 1007), + /* there are no senders */ + WSA_QOS_NO_RECEIVERS = (WSABASEERR + 1008), + /* there are no receivers */ + WSA_QOS_REQUEST_CONFIRMED = (WSABASEERR + 1009), + /* Reserve has been confirmed */ + WSA_QOS_ADMISSION_FAILURE = (WSABASEERR + 1010), + /* error due to lack of resources */ + WSA_QOS_POLICY_FAILURE = (WSABASEERR + 1011), + /* rejected for administrative reasons - bad credentials */ + WSA_QOS_BAD_STYLE = (WSABASEERR + 1012), + /* unknown or conflicting style */ + WSA_QOS_BAD_OBJECT = (WSABASEERR + 1013), + /* problem with some part of the filterspec or providerspecific + * buffer in general */ + WSA_QOS_TRAFFIC_CTRL_ERROR = (WSABASEERR + 1014), + /* problem with some part of the flowspec */ + WSA_QOS_GENERIC_ERROR = (WSABASEERR + 1015), + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs new file mode 100644 index 00000000..e2ef75f8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs @@ -0,0 +1,347 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types; +using System; +using System.Collections.Generic; +using System.Net.Sockets; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl +{ + static class WinSockHelper + { + private static readonly Dictionary _errorMap = new() + { + // WSAEINTR + { WsaError.WSAEINTR, LinuxError.EINTR }, + // WSAEWOULDBLOCK + { WsaError.WSAEWOULDBLOCK, LinuxError.EWOULDBLOCK }, + // WSAEINPROGRESS + { WsaError.WSAEINPROGRESS, LinuxError.EINPROGRESS }, + // WSAEALREADY + { WsaError.WSAEALREADY, LinuxError.EALREADY }, + // WSAENOTSOCK + { WsaError.WSAENOTSOCK, LinuxError.ENOTSOCK }, + // WSAEDESTADDRREQ + { WsaError.WSAEDESTADDRREQ, LinuxError.EDESTADDRREQ }, + // WSAEMSGSIZE + { WsaError.WSAEMSGSIZE, LinuxError.EMSGSIZE }, + // WSAEPROTOTYPE + { WsaError.WSAEPROTOTYPE, LinuxError.EPROTOTYPE }, + // WSAENOPROTOOPT + { WsaError.WSAENOPROTOOPT, LinuxError.ENOPROTOOPT }, + // WSAEPROTONOSUPPORT + { WsaError.WSAEPROTONOSUPPORT, LinuxError.EPROTONOSUPPORT }, + // WSAESOCKTNOSUPPORT + { WsaError.WSAESOCKTNOSUPPORT, LinuxError.ESOCKTNOSUPPORT }, + // WSAEOPNOTSUPP + { WsaError.WSAEOPNOTSUPP, LinuxError.EOPNOTSUPP }, + // WSAEPFNOSUPPORT + { WsaError.WSAEPFNOSUPPORT, LinuxError.EPFNOSUPPORT }, + // WSAEAFNOSUPPORT + { WsaError.WSAEAFNOSUPPORT, LinuxError.EAFNOSUPPORT }, + // WSAEADDRINUSE + { WsaError.WSAEADDRINUSE, LinuxError.EADDRINUSE }, + // WSAEADDRNOTAVAIL + { WsaError.WSAEADDRNOTAVAIL, LinuxError.EADDRNOTAVAIL }, + // WSAENETDOWN + { WsaError.WSAENETDOWN, LinuxError.ENETDOWN }, + // WSAENETUNREACH + { WsaError.WSAENETUNREACH, LinuxError.ENETUNREACH }, + // WSAENETRESET + { WsaError.WSAENETRESET, LinuxError.ENETRESET }, + // WSAECONNABORTED + { WsaError.WSAECONNABORTED, LinuxError.ECONNABORTED }, + // WSAECONNRESET + { WsaError.WSAECONNRESET, LinuxError.ECONNRESET }, + // WSAENOBUFS + { WsaError.WSAENOBUFS, LinuxError.ENOBUFS }, + // WSAEISCONN + { WsaError.WSAEISCONN, LinuxError.EISCONN }, + // WSAENOTCONN + { WsaError.WSAENOTCONN, LinuxError.ENOTCONN }, + // WSAESHUTDOWN + { WsaError.WSAESHUTDOWN, LinuxError.ESHUTDOWN }, + // WSAETOOMANYREFS + { WsaError.WSAETOOMANYREFS, LinuxError.ETOOMANYREFS }, + // WSAETIMEDOUT + { WsaError.WSAETIMEDOUT, LinuxError.ETIMEDOUT }, + // WSAECONNREFUSED + { WsaError.WSAECONNREFUSED, LinuxError.ECONNREFUSED }, + // WSAELOOP + { WsaError.WSAELOOP, LinuxError.ELOOP }, + // WSAENAMETOOLONG + { WsaError.WSAENAMETOOLONG, LinuxError.ENAMETOOLONG }, + // WSAEHOSTDOWN + { WsaError.WSAEHOSTDOWN, LinuxError.EHOSTDOWN }, + // WSAEHOSTUNREACH + { WsaError.WSAEHOSTUNREACH, LinuxError.EHOSTUNREACH }, + // WSAENOTEMPTY + { WsaError.WSAENOTEMPTY, LinuxError.ENOTEMPTY }, + // WSAEUSERS + { WsaError.WSAEUSERS, LinuxError.EUSERS }, + // WSAEDQUOT + { WsaError.WSAEDQUOT, LinuxError.EDQUOT }, + // WSAESTALE + { WsaError.WSAESTALE, LinuxError.ESTALE }, + // WSAEREMOTE + { WsaError.WSAEREMOTE, LinuxError.EREMOTE }, + // WSAEINVAL + { WsaError.WSAEINVAL, LinuxError.EINVAL }, + // WSAEFAULT + { WsaError.WSAEFAULT, LinuxError.EFAULT }, + // NOERROR + { 0, 0 }, + }; + + private static readonly Dictionary _errorMapMacOs = new() + { + { 35, LinuxError.EAGAIN }, + { 11, LinuxError.EDEADLOCK }, + { 91, LinuxError.ENOMSG }, + { 90, LinuxError.EIDRM }, + { 77, LinuxError.ENOLCK }, + { 70, LinuxError.ESTALE }, + { 36, LinuxError.EINPROGRESS }, + { 37, LinuxError.EALREADY }, + { 38, LinuxError.ENOTSOCK }, + { 39, LinuxError.EDESTADDRREQ }, + { 40, LinuxError.EMSGSIZE }, + { 41, LinuxError.EPROTOTYPE }, + { 42, LinuxError.ENOPROTOOPT }, + { 43, LinuxError.EPROTONOSUPPORT }, + { 44, LinuxError.ESOCKTNOSUPPORT }, + { 45, LinuxError.EOPNOTSUPP }, + { 46, LinuxError.EPFNOSUPPORT }, + { 47, LinuxError.EAFNOSUPPORT }, + { 48, LinuxError.EADDRINUSE }, + { 49, LinuxError.EADDRNOTAVAIL }, + { 50, LinuxError.ENETDOWN }, + { 51, LinuxError.ENETUNREACH }, + { 52, LinuxError.ENETRESET }, + { 53, LinuxError.ECONNABORTED }, + { 54, LinuxError.ECONNRESET }, + { 55, LinuxError.ENOBUFS }, + { 56, LinuxError.EISCONN }, + { 57, LinuxError.ENOTCONN }, + { 58, LinuxError.ESHUTDOWN }, + { 60, LinuxError.ETIMEDOUT }, + { 61, LinuxError.ECONNREFUSED }, + { 64, LinuxError.EHOSTDOWN }, + { 65, LinuxError.EHOSTUNREACH }, + { 68, LinuxError.EUSERS }, + { 62, LinuxError.ELOOP }, + { 63, LinuxError.ENAMETOOLONG }, + { 66, LinuxError.ENOTEMPTY }, + { 69, LinuxError.EDQUOT }, + { 71, LinuxError.EREMOTE }, + { 78, LinuxError.ENOSYS }, + { 59, LinuxError.ETOOMANYREFS }, + { 92, LinuxError.EILSEQ }, + { 89, LinuxError.ECANCELED }, + { 84, LinuxError.EOVERFLOW }, + }; + + private static readonly Dictionary _soSocketOptionMap = new() + { + { BsdSocketOption.SoDebug, SocketOptionName.Debug }, + { BsdSocketOption.SoReuseAddr, SocketOptionName.ReuseAddress }, + { BsdSocketOption.SoKeepAlive, SocketOptionName.KeepAlive }, + { BsdSocketOption.SoDontRoute, SocketOptionName.DontRoute }, + { BsdSocketOption.SoBroadcast, SocketOptionName.Broadcast }, + { BsdSocketOption.SoUseLoopBack, SocketOptionName.UseLoopback }, + { BsdSocketOption.SoLinger, SocketOptionName.Linger }, + { BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline }, + { BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress }, + { BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer }, + { BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer }, + { BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater }, + { BsdSocketOption.SoRcvLoWat, SocketOptionName.ReceiveLowWater }, + { BsdSocketOption.SoSndTimeo, SocketOptionName.SendTimeout }, + { BsdSocketOption.SoRcvTimeo, SocketOptionName.ReceiveTimeout }, + { BsdSocketOption.SoError, SocketOptionName.Error }, + { BsdSocketOption.SoType, SocketOptionName.Type }, + }; + + private static readonly Dictionary _ipSocketOptionMap = new() + { + { BsdSocketOption.IpOptions, SocketOptionName.IPOptions }, + { BsdSocketOption.IpHdrIncl, SocketOptionName.HeaderIncluded }, + { BsdSocketOption.IpTtl, SocketOptionName.IpTimeToLive }, + { BsdSocketOption.IpMulticastIf, SocketOptionName.MulticastInterface }, + { BsdSocketOption.IpMulticastTtl, SocketOptionName.MulticastTimeToLive }, + { BsdSocketOption.IpMulticastLoop, SocketOptionName.MulticastLoopback }, + { BsdSocketOption.IpAddMembership, SocketOptionName.AddMembership }, + { BsdSocketOption.IpDropMembership, SocketOptionName.DropMembership }, + { BsdSocketOption.IpDontFrag, SocketOptionName.DontFragment }, + { BsdSocketOption.IpAddSourceMembership, SocketOptionName.AddSourceMembership }, + { BsdSocketOption.IpDropSourceMembership, SocketOptionName.DropSourceMembership }, + }; + + private static readonly Dictionary _tcpSocketOptionMap = new() + { + { BsdSocketOption.TcpNoDelay, SocketOptionName.NoDelay }, + { BsdSocketOption.TcpKeepIdle, SocketOptionName.TcpKeepAliveTime }, + { BsdSocketOption.TcpKeepIntvl, SocketOptionName.TcpKeepAliveInterval }, + { BsdSocketOption.TcpKeepCnt, SocketOptionName.TcpKeepAliveRetryCount }, + }; + + [Flags] + private enum OptionDir + { + Get = 1 << 0, + Set = 1 << 1, + GetSet = Get | Set, + } + + private static readonly Dictionary _validSoSocketOptionMap = new() + { + { BsdSocketOption.SoDebug, OptionDir.GetSet }, + { BsdSocketOption.SoAcceptConn, OptionDir.Get }, + { BsdSocketOption.SoReuseAddr, OptionDir.GetSet }, + { BsdSocketOption.SoKeepAlive, OptionDir.GetSet }, + { BsdSocketOption.SoDontRoute, OptionDir.GetSet }, + { BsdSocketOption.SoBroadcast, OptionDir.GetSet }, + { BsdSocketOption.SoUseLoopBack, OptionDir.GetSet }, + { BsdSocketOption.SoLinger, OptionDir.GetSet }, + { BsdSocketOption.SoOobInline, OptionDir.GetSet }, + { BsdSocketOption.SoReusePort, OptionDir.GetSet }, + { BsdSocketOption.SoTimestamp, OptionDir.GetSet }, + { BsdSocketOption.SoNoSigpipe, OptionDir.GetSet }, + { BsdSocketOption.SoAcceptFilter, OptionDir.GetSet }, + { BsdSocketOption.SoSndBuf, OptionDir.GetSet }, + { BsdSocketOption.SoRcvBuf, OptionDir.GetSet }, + { BsdSocketOption.SoSndLoWat, OptionDir.GetSet }, + { BsdSocketOption.SoRcvLoWat, OptionDir.GetSet }, + { BsdSocketOption.SoSndTimeo, OptionDir.GetSet }, + { BsdSocketOption.SoRcvTimeo, OptionDir.GetSet }, + { BsdSocketOption.SoError, OptionDir.Get }, + { BsdSocketOption.SoType, OptionDir.Get }, + { BsdSocketOption.SoLabel, OptionDir.Get }, + { BsdSocketOption.SoPeerLabel, OptionDir.Get }, + { BsdSocketOption.SoListenQLimit, OptionDir.Get }, + { BsdSocketOption.SoListenQLen, OptionDir.Get }, + { BsdSocketOption.SoListenIncQLen, OptionDir.Get }, + { BsdSocketOption.SoSetFib, OptionDir.Set }, + { BsdSocketOption.SoUserCookie, OptionDir.Set }, + { BsdSocketOption.SoProtocol, OptionDir.Get }, + { BsdSocketOption.SoBinTime, OptionDir.GetSet }, + { BsdSocketOption.SoNoOffload, OptionDir.Set }, + { BsdSocketOption.SoNoDdp, OptionDir.Set }, + { BsdSocketOption.SoReusePortLb, OptionDir.GetSet }, + }; + + private static readonly Dictionary _validIpSocketOptionMap = new() + { + { BsdSocketOption.IpOptions, OptionDir.GetSet }, + { BsdSocketOption.IpHdrIncl, OptionDir.GetSet }, + { BsdSocketOption.IpTos, OptionDir.GetSet }, + { BsdSocketOption.IpTtl, OptionDir.GetSet }, + { BsdSocketOption.IpRecvOpts, OptionDir.GetSet }, + { BsdSocketOption.IpRecvRetOpts, OptionDir.GetSet }, + { BsdSocketOption.IpRecvDstAddr, OptionDir.GetSet }, + { BsdSocketOption.IpRetOpts, OptionDir.GetSet }, + { BsdSocketOption.IpMulticastIf, OptionDir.GetSet }, + { BsdSocketOption.IpMulticastTtl, OptionDir.GetSet }, + { BsdSocketOption.IpMulticastLoop, OptionDir.GetSet }, + { BsdSocketOption.IpAddMembership, OptionDir.GetSet }, + { BsdSocketOption.IpDropMembership, OptionDir.GetSet }, + { BsdSocketOption.IpMulticastVif, OptionDir.GetSet }, + { BsdSocketOption.IpRsvpOn, OptionDir.GetSet }, + { BsdSocketOption.IpRsvpOff, OptionDir.GetSet }, + { BsdSocketOption.IpRsvpVifOn, OptionDir.GetSet }, + { BsdSocketOption.IpRsvpVifOff, OptionDir.GetSet }, + { BsdSocketOption.IpPortRange, OptionDir.GetSet }, + { BsdSocketOption.IpRecvIf, OptionDir.GetSet }, + { BsdSocketOption.IpIpsecPolicy, OptionDir.GetSet }, + { BsdSocketOption.IpOnesBcast, OptionDir.GetSet }, + { BsdSocketOption.IpBindany, OptionDir.GetSet }, + { BsdSocketOption.IpBindMulti, OptionDir.GetSet }, + { BsdSocketOption.IpRssListenBucket, OptionDir.GetSet }, + { BsdSocketOption.IpOrigDstAddr, OptionDir.GetSet }, + { BsdSocketOption.IpRecvTtl, OptionDir.GetSet }, + { BsdSocketOption.IpMinTtl, OptionDir.GetSet }, + { BsdSocketOption.IpDontFrag, OptionDir.GetSet }, + { BsdSocketOption.IpRecvTos, OptionDir.GetSet }, + { BsdSocketOption.IpAddSourceMembership, OptionDir.GetSet }, + { BsdSocketOption.IpDropSourceMembership, OptionDir.GetSet }, + { BsdSocketOption.IpBlockSource, OptionDir.GetSet }, + { BsdSocketOption.IpUnblockSource, OptionDir.GetSet }, + }; + + private static readonly Dictionary _validTcpSocketOptionMap = new() + { + { BsdSocketOption.TcpNoDelay, OptionDir.GetSet }, + { BsdSocketOption.TcpMaxSeg, OptionDir.GetSet }, + { BsdSocketOption.TcpNoPush, OptionDir.GetSet }, + { BsdSocketOption.TcpNoOpt, OptionDir.GetSet }, + { BsdSocketOption.TcpMd5Sig, OptionDir.GetSet }, + { BsdSocketOption.TcpInfo, OptionDir.GetSet }, + { BsdSocketOption.TcpCongestion, OptionDir.GetSet }, + { BsdSocketOption.TcpKeepInit, OptionDir.GetSet }, + { BsdSocketOption.TcpKeepIdle, OptionDir.GetSet }, + { BsdSocketOption.TcpKeepIntvl, OptionDir.GetSet }, + { BsdSocketOption.TcpKeepCnt, OptionDir.GetSet }, + }; + + public static LinuxError ConvertError(WsaError errorCode) + { + if (OperatingSystem.IsMacOS()) + { + if (_errorMapMacOs.TryGetValue((int)errorCode, out LinuxError errno)) + { + return errno; + } + } + else + { + if (_errorMap.TryGetValue(errorCode, out LinuxError errno)) + { + return errno; + } + } + + return (LinuxError)errorCode; + } + + public static bool TryConvertSocketOption(BsdSocketOption option, SocketOptionLevel level, out SocketOptionName name) + { + var table = level switch + { + SocketOptionLevel.Socket => _soSocketOptionMap, + SocketOptionLevel.IP => _ipSocketOptionMap, + SocketOptionLevel.Tcp => _tcpSocketOptionMap, + _ => null, + }; + + if (table == null) + { + name = default; + return false; + } + + return table.TryGetValue(option, out name); + } + + public static LinuxError ValidateSocketOption(BsdSocketOption option, SocketOptionLevel level, bool write) + { + var table = level switch + { + SocketOptionLevel.Socket => _validSoSocketOptionMap, + SocketOptionLevel.IP => _validIpSocketOptionMap, + SocketOptionLevel.Tcp => _validTcpSocketOptionMap, + _ => null, + }; + + OptionDir dir = write ? OptionDir.Set : OptionDir.Get; + + if (table == null || !table.TryGetValue(option, out OptionDir validDir)) + { + return LinuxError.ENOPROTOOPT; + } + else if ((validDir & dir) != dir) + { + return LinuxError.EOPNOTSUPP; + } + + return LinuxError.SUCCESS; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs new file mode 100644 index 00000000..edf9d351 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd +{ + [Service("bsdcfg")] + class ServerInterface : IpcService + { + public ServerInterface(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs new file mode 100644 index 00000000..6bc27ffb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdAddressFamily : uint + { + Unspecified, + InterNetwork = 2, + InterNetworkV6 = 28, + + Unknown = uint.MaxValue, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs new file mode 100644 index 00000000..d38bd02b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdIoctl + { + AtMark = 0x40047307, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMMsgHdr.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMMsgHdr.cs new file mode 100644 index 00000000..75f869b6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMMsgHdr.cs @@ -0,0 +1,56 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + class BsdMMsgHdr + { + public BsdMsgHdr[] Messages { get; } + + private BsdMMsgHdr(BsdMsgHdr[] messages) + { + Messages = messages; + } + + public static LinuxError Serialize(Span rawData, BsdMMsgHdr message) + { + rawData[0] = 0x8; + rawData = rawData[1..]; + + for (int index = 0; index < message.Messages.Length; index++) + { + LinuxError res = BsdMsgHdr.Serialize(ref rawData, message.Messages[index]); + + if (res != LinuxError.SUCCESS) + { + return res; + } + } + + return LinuxError.SUCCESS; + } + + public static LinuxError Deserialize(out BsdMMsgHdr message, ReadOnlySpan rawData, int vlen) + { + message = null; + + BsdMsgHdr[] messages = new BsdMsgHdr[vlen]; + + // Skip "header" byte (Nintendo also ignore it) + rawData = rawData[1..]; + + for (int index = 0; index < messages.Length; index++) + { + LinuxError res = BsdMsgHdr.Deserialize(out messages[index], ref rawData); + + if (res != LinuxError.SUCCESS) + { + return res; + } + } + + message = new BsdMMsgHdr(messages); + + return LinuxError.SUCCESS; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs new file mode 100644 index 00000000..6b6617e2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs @@ -0,0 +1,212 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + class BsdMsgHdr + { + public byte[] Name { get; } + public byte[][] Iov { get; } + public byte[] Control { get; } + public BsdSocketFlags Flags { get; } + public uint Length; + + private BsdMsgHdr(byte[] name, byte[][] iov, byte[] control, BsdSocketFlags flags, uint length) + { + Name = name; + Iov = iov; + Control = control; + Flags = flags; + Length = length; + } + + public static LinuxError Serialize(ref Span rawData, BsdMsgHdr message) + { + int msgNameLength = message.Name == null ? 0 : message.Name.Length; + int iovCount = message.Iov == null ? 0 : message.Iov.Length; + int controlLength = message.Control == null ? 0 : message.Control.Length; + BsdSocketFlags flags = message.Flags; + + if (!MemoryMarshal.TryWrite(rawData, in msgNameLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (msgNameLength > 0) + { + if (rawData.Length < msgNameLength) + { + return LinuxError.EFAULT; + } + + message.Name.CopyTo(rawData); + rawData = rawData[msgNameLength..]; + } + + if (!MemoryMarshal.TryWrite(rawData, in iovCount)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (iovCount > 0) + { + for (int index = 0; index < iovCount; index++) + { + ulong iovLength = (ulong)message.Iov[index].Length; + + if (!MemoryMarshal.TryWrite(rawData, in iovLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(ulong)..]; + + if (iovLength > 0) + { + if ((ulong)rawData.Length < iovLength) + { + return LinuxError.EFAULT; + } + + message.Iov[index].CopyTo(rawData); + rawData = rawData[(int)iovLength..]; + } + } + } + + if (!MemoryMarshal.TryWrite(rawData, in controlLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (controlLength > 0) + { + if (rawData.Length < controlLength) + { + return LinuxError.EFAULT; + } + + message.Control.CopyTo(rawData); + rawData = rawData[controlLength..]; + } + + if (!MemoryMarshal.TryWrite(rawData, in flags)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(BsdSocketFlags)..]; + + if (!MemoryMarshal.TryWrite(rawData, in message.Length)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + return LinuxError.SUCCESS; + } + + public static LinuxError Deserialize(out BsdMsgHdr message, ref ReadOnlySpan rawData) + { + byte[] name = null; + byte[][] iov = null; + byte[] control = null; + + message = null; + + if (!MemoryMarshal.TryRead(rawData, out uint msgNameLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (msgNameLength > 0) + { + if (rawData.Length < msgNameLength) + { + return LinuxError.EFAULT; + } + + name = rawData[..(int)msgNameLength].ToArray(); + rawData = rawData[(int)msgNameLength..]; + } + + if (!MemoryMarshal.TryRead(rawData, out uint iovCount)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (iovCount > 0) + { + iov = new byte[iovCount][]; + + for (int index = 0; index < iov.Length; index++) + { + if (!MemoryMarshal.TryRead(rawData, out ulong iovLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(ulong)..]; + + if (iovLength > 0) + { + if ((ulong)rawData.Length < iovLength) + { + return LinuxError.EFAULT; + } + + iov[index] = rawData[..(int)iovLength].ToArray(); + rawData = rawData[(int)iovLength..]; + } + } + } + + if (!MemoryMarshal.TryRead(rawData, out uint controlLength)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + if (controlLength > 0) + { + if (rawData.Length < controlLength) + { + return LinuxError.EFAULT; + } + + control = rawData[..(int)controlLength].ToArray(); + rawData = rawData[(int)controlLength..]; + } + + if (!MemoryMarshal.TryRead(rawData, out BsdSocketFlags flags)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(BsdSocketFlags)..]; + + if (!MemoryMarshal.TryRead(rawData, out uint length)) + { + return LinuxError.EFAULT; + } + + rawData = rawData[sizeof(uint)..]; + + message = new BsdMsgHdr(name, iov, control, flags, length); + + return LinuxError.SUCCESS; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs new file mode 100644 index 00000000..0dc76fc7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs @@ -0,0 +1,39 @@ +using Ryujinx.Common.Memory; +using System; +using System.Net; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + struct BsdSockAddr + { + public byte Length; + public byte Family; + public ushort Port; + public Array4 Address; + private Array8 _reserved; + + public IPEndPoint ToIPEndPoint() + { + IPAddress address = new(Address.AsSpan()); + int port = (ushort)IPAddress.NetworkToHostOrder((short)Port); + + return new IPEndPoint(address, port); + } + + public static BsdSockAddr FromIPEndPoint(IPEndPoint endpoint) + { + BsdSockAddr result = new() + { + Length = 0, + Family = (byte)endpoint.AddressFamily, + Port = (ushort)IPAddress.HostToNetworkOrder((short)endpoint.Port), + }; + + endpoint.Address.GetAddressBytes().AsSpan().CopyTo(result.Address.AsSpan()); + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs new file mode 100644 index 00000000..0abd424e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [Flags] + enum BsdSocketCreationFlags + { + None = 0, + CloseOnExecution = 1, + NonBlocking = 2, + + FlagsShift = 28, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs new file mode 100644 index 00000000..63998129 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdSocketFlags + { + None = 0, + Oob = 0x1, + Peek = 0x2, + DontRoute = 0x4, + Eor = 0x8, + Trunc = 0x10, + CTrunc = 0x20, + WaitAll = 0x40, + DontWait = 0x80, + Eof = 0x100, + Notification = 0x2000, + Nbio = 0x4000, + Compat = 0x8000, + SoCallbck = 0x10000, + NoSignal = 0x20000, + CMsgCloExec = 0x40000, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs new file mode 100644 index 00000000..5bc3e81f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs @@ -0,0 +1,122 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + enum BsdSocketOption + { + SoDebug = 0x1, + SoAcceptConn = 0x2, + SoReuseAddr = 0x4, + SoKeepAlive = 0x8, + SoDontRoute = 0x10, + SoBroadcast = 0x20, + SoUseLoopBack = 0x40, + SoLinger = 0x80, + SoOobInline = 0x100, + SoReusePort = 0x200, + SoTimestamp = 0x400, + SoNoSigpipe = 0x800, + SoAcceptFilter = 0x1000, + SoBinTime = 0x2000, + SoNoOffload = 0x4000, + SoNoDdp = 0x8000, + SoReusePortLb = 0x10000, + SoRError = 0x20000, + + SoSndBuf = 0x1001, + SoRcvBuf = 0x1002, + SoSndLoWat = 0x1003, + SoRcvLoWat = 0x1004, + SoSndTimeo = 0x1005, + SoRcvTimeo = 0x1006, + SoError = 0x1007, + SoType = 0x1008, + SoLabel = 0x1009, + SoPeerLabel = 0x1010, + SoListenQLimit = 0x1011, + SoListenQLen = 0x1012, + SoListenIncQLen = 0x1013, + SoSetFib = 0x1014, + SoUserCookie = 0x1015, + SoProtocol = 0x1016, + SoTsClock = 0x1017, + SoMaxPacingRate = 0x1018, + SoDomain = 0x1019, + + IpOptions = 1, + IpHdrIncl = 2, + IpTos = 3, + IpTtl = 4, + IpRecvOpts = 5, + IpRecvRetOpts = 6, + IpRecvDstAddr = 7, + IpRetOpts = 8, + IpMulticastIf = 9, + IpMulticastTtl = 10, + IpMulticastLoop = 11, + IpAddMembership = 12, + IpDropMembership = 13, + IpMulticastVif = 14, + IpRsvpOn = 15, + IpRsvpOff = 16, + IpRsvpVifOn = 17, + IpRsvpVifOff = 18, + IpPortRange = 19, + IpRecvIf = 20, + IpIpsecPolicy = 21, + IpOnesBcast = 23, + IpBindany = 24, + IpBindMulti = 25, + IpRssListenBucket = 26, + IpOrigDstAddr = 27, + + IpFwTableAdd = 40, + IpFwTableDel = 41, + IpFwTableFlush = 42, + IpFwTableGetSize = 43, + IpFwTableList = 44, + + IpFw3 = 48, + IpDummyNet3 = 49, + + IpFwAdd = 50, + IpFwDel = 51, + IpFwFlush = 52, + IpFwZero = 53, + IpFwGet = 54, + IpFwResetLog = 55, + + IpFwNatCfg = 56, + IpFwNatDel = 57, + IpFwNatGetConfig = 58, + IpFwNatGetLog = 59, + + IpDummyNetConfigure = 60, + IpDummyNetDel = 61, + IpDummyNetFlush = 62, + IpDummyNetGet = 64, + + IpRecvTtl = 65, + IpMinTtl = 66, + IpDontFrag = 67, + IpRecvTos = 68, + + IpAddSourceMembership = 70, + IpDropSourceMembership = 71, + IpBlockSource = 72, + IpUnblockSource = 73, + + TcpNoDelay = 1, + TcpMaxSeg = 2, + TcpNoPush = 4, + TcpNoOpt = 8, + TcpMd5Sig = 16, + TcpInfo = 32, + TcpCongestion = 64, + TcpKeepInit = 128, + TcpKeepIdle = 256, + TcpKeepIntvl = 512, + TcpKeepCnt = 1024, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs new file mode 100644 index 00000000..4a047897 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdSocketShutdownFlags + { + Receive, + Send, + ReceiveAndSend, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs new file mode 100644 index 00000000..2938ebe7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + enum BsdSocketType + { + Stream = 1, + Dgram, + Raw, + Rdm, + Seqpacket, + + TypeMask = 0xFFFFFFF, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs new file mode 100644 index 00000000..c21a997b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [Flags] + enum EventFdFlags : uint + { + None = 0, + Semaphore = 1 << 0, + NonBlocking = 1 << 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs new file mode 100644 index 00000000..f6f47daf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + interface IPollManager + { + bool IsCompatible(PollEvent evnt); + + LinuxError Poll(List events, int timeoutMilliseconds, out int updatedCount); + + LinuxError Select(List events, int timeout, out int updatedCount); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/LinuxError.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/LinuxError.cs new file mode 100644 index 00000000..aaeee44d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/LinuxError.cs @@ -0,0 +1,155 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + enum LinuxError + { + SUCCESS = 0, + EPERM = 1 /* Operation not permitted */, + ENOENT = 2 /* No such file or directory */, + ESRCH = 3 /* No such process */, + EINTR = 4 /* Interrupted system call */, + EIO = 5 /* I/O error */, + ENXIO = 6 /* No such device or address */, + E2BIG = 7 /* Argument list too long */, + ENOEXEC = 8 /* Exec format error */, + EBADF = 9 /* Bad file number */, + ECHILD = 10 /* No child processes */, + EAGAIN = 11 /* Try again */, + ENOMEM = 12 /* Out of memory */, + EACCES = 13 /* Permission denied */, + EFAULT = 14 /* Bad address */, + ENOTBLK = 15 /* Block device required */, + EBUSY = 16 /* Device or resource busy */, + EEXIST = 17 /* File exists */, + EXDEV = 18 /* Cross-device link */, + ENODEV = 19 /* No such device */, + ENOTDIR = 20 /* Not a directory */, + EISDIR = 21 /* Is a directory */, + EINVAL = 22 /* Invalid argument */, + ENFILE = 23 /* File table overflow */, + EMFILE = 24 /* Too many open files */, + ENOTTY = 25 /* Not a typewriter */, + ETXTBSY = 26 /* Text file busy */, + EFBIG = 27 /* File too large */, + ENOSPC = 28 /* No space left on device */, + ESPIPE = 29 /* Illegal seek */, + EROFS = 30 /* Read-only file system */, + EMLINK = 31 /* Too many links */, + EPIPE = 32 /* Broken pipe */, + EDOM = 33 /* Math argument out of domain of func */, + ERANGE = 34 /* Math result not representable */, + EDEADLK = 35 /* Resource deadlock would occur */, + ENAMETOOLONG = 36 /* File name too long */, + ENOLCK = 37 /* No record locks available */, + + /* + * This error code is special: arch syscall entry code will return + * -ENOSYS if users try to call a syscall that doesn't exist. To keep + * failures of syscalls that really do exist distinguishable from + * failures due to attempts to use a nonexistent syscall, syscall + * implementations should refrain from returning -ENOSYS. + */ + ENOSYS = 38 /* Invalid system call number */, + ENOTEMPTY = 39 /* Directory not empty */, + ELOOP = 40 /* Too many symbolic links encountered */, + EWOULDBLOCK = EAGAIN /* Operation would block */, + ENOMSG = 42 /* No message of desired type */, + EIDRM = 43 /* Identifier removed */, + ECHRNG = 44 /* Channel number out of range */, + EL2NSYNC = 45 /* Level 2 not synchronized */, + EL3HLT = 46 /* Level 3 halted */, + EL3RST = 47 /* Level 3 reset */, + ELNRNG = 48 /* Link number out of range */, + EUNATCH = 49 /* Protocol driver not attached */, + ENOCSI = 50 /* No CSI structure available */, + EL2HLT = 51 /* Level 2 halted */, + EBADE = 52 /* Invalid exchange */, + EBADR = 53 /* Invalid request descriptor */, + EXFULL = 54 /* Exchange full */, + ENOANO = 55 /* No anode */, + EBADRQC = 56 /* Invalid request code */, + EBADSLT = 57 /* Invalid slot */, + EDEADLOCK = EDEADLK, + EBFONT = 59 /* Bad font file format */, + ENOSTR = 60 /* Device not a stream */, + ENODATA = 61 /* No data available */, + ETIME = 62 /* Timer expired */, + ENOSR = 63 /* Out of streams resources */, + ENONET = 64 /* Machine is not on the network */, + ENOPKG = 65 /* Package not installed */, + EREMOTE = 66 /* Object is remote */, + ENOLINK = 67 /* Link has been severed */, + EADV = 68 /* Advertise error */, + ESRMNT = 69 /* Srmount error */, + ECOMM = 70 /* Communication error on send */, + EPROTO = 71 /* Protocol error */, + EMULTIHOP = 72 /* Multihop attempted */, + EDOTDOT = 73 /* RFS specific error */, + EBADMSG = 74 /* Not a data message */, + EOVERFLOW = 75 /* Value too large for defined data type */, + ENOTUNIQ = 76 /* Name not unique on network */, + EBADFD = 77 /* File descriptor in bad state */, + EREMCHG = 78 /* Remote address changed */, + ELIBACC = 79 /* Can not access a needed shared library */, + ELIBBAD = 80 /* Accessing a corrupted shared library */, + ELIBSCN = 81 /* .lib section in a.out corrupted */, + ELIBMAX = 82 /* Attempting to link in too many shared libraries */, + ELIBEXEC = 83 /* Cannot exec a shared library directly */, + EILSEQ = 84 /* Illegal byte sequence */, + ERESTART = 85 /* Interrupted system call should be restarted */, + ESTRPIPE = 86 /* Streams pipe error */, + EUSERS = 87 /* Too many users */, + ENOTSOCK = 88 /* Socket operation on non-socket */, + EDESTADDRREQ = 89 /* Destination address required */, + EMSGSIZE = 90 /* Message too long */, + EPROTOTYPE = 91 /* Protocol wrong type for socket */, + ENOPROTOOPT = 92 /* Protocol not available */, + EPROTONOSUPPORT = 93 /* Protocol not supported */, + ESOCKTNOSUPPORT = 94 /* Socket type not supported */, + EOPNOTSUPP = 95 /* Operation not supported on transport endpoint */, + EPFNOSUPPORT = 96 /* Protocol family not supported */, + EAFNOSUPPORT = 97 /* Address family not supported by protocol */, + EADDRINUSE = 98 /* Address already in use */, + EADDRNOTAVAIL = 99 /* Cannot assign requested address */, + ENETDOWN = 100 /* Network is down */, + ENETUNREACH = 101 /* Network is unreachable */, + ENETRESET = 102 /* Network dropped connection because of reset */, + ECONNABORTED = 103 /* Software caused connection abort */, + ECONNRESET = 104 /* Connection reset by peer */, + ENOBUFS = 105 /* No buffer space available */, + EISCONN = 106 /* Transport endpoint is already connected */, + ENOTCONN = 107 /* Transport endpoint is not connected */, + ESHUTDOWN = 108 /* Cannot send after transport endpoint shutdown */, + ETOOMANYREFS = 109 /* Too many references: cannot splice */, + ETIMEDOUT = 110 /* Connection timed out */, + ECONNREFUSED = 111 /* Connection refused */, + EHOSTDOWN = 112 /* Host is down */, + EHOSTUNREACH = 113 /* No route to host */, + EALREADY = 114 /* Operation already in progress */, + EINPROGRESS = 115 /* Operation now in progress */, + ESTALE = 116 /* Stale file handle */, + EUCLEAN = 117 /* Structure needs cleaning */, + ENOTNAM = 118 /* Not a XENIX named type file */, + ENAVAIL = 119 /* No XENIX semaphores available */, + EISNAM = 120 /* Is a named type file */, + EREMOTEIO = 121 /* Remote I/O error */, + EDQUOT = 122 /* Quota exceeded */, + ENOMEDIUM = 123 /* No medium found */, + EMEDIUMTYPE = 124 /* Wrong medium type */, + ECANCELED = 125 /* Operation Canceled */, + ENOKEY = 126 /* Required key not available */, + EKEYEXPIRED = 127 /* Key has expired */, + EKEYREVOKED = 128 /* Key has been revoked */, + EKEYREJECTED = 129 /* Key was rejected by service */, + + /* for robust mutexes */ + EOWNERDEAD = 130 /* Owner died */, + ENOTRECOVERABLE = 131 /* State not recoverable */, + + ERFKILL = 132 /* Operation not possible due to RF-kill */, + + EHWPOISON = 133, /* Memory page has hardware error */ + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs new file mode 100644 index 00000000..b6fd3e32 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + class PollEvent + { + public PollEventData Data; + public IFileDescriptor FileDescriptor { get; } + + public PollEvent(PollEventData data, IFileDescriptor fileDescriptor) + { + Data = data; + FileDescriptor = fileDescriptor; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs new file mode 100644 index 00000000..3babb001 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + struct PollEventData + { +#pragma warning disable CS0649 // Field is never assigned to + public int SocketFd; + public PollEventTypeMask InputEvents; +#pragma warning restore CS0649 + public PollEventTypeMask OutputEvents; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs new file mode 100644 index 00000000..da47d60a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + [Flags] + enum PollEventTypeMask : ushort + { + Input = 1, + UrgentInput = 2, + Output = 4, + Error = 8, + Disconnected = 0x10, + Invalid = 0x20, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/TimeVal.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/TimeVal.cs new file mode 100644 index 00000000..bff30b04 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/TimeVal.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types +{ + public struct TimeVal + { + public ulong TvSec; + public ulong TvUsec; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs new file mode 100644 index 00000000..f5766264 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Ethc +{ + [Service("ethc:c")] + class IEthInterface : IpcService + { + public IEthInterface(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs new file mode 100644 index 00000000..527b6dd0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Ethc +{ + [Service("ethc:i")] + class IEthInterfaceGroup : IpcService + { + public IEthInterfaceGroup(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs new file mode 100644 index 00000000..0c1fa3a9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs @@ -0,0 +1,404 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd.Manager; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd.Types; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd +{ + [Service("nsd:a")] // Max sessions: 5 + [Service("nsd:u")] // Max sessions: 20 + class IManager : IpcService + { + public static readonly NsdSettings NsdSettings; +#pragma warning disable IDE0052 // Remove unread private member + private readonly FqdnResolver _fqdnResolver; +#pragma warning restore IDE0052 + + private readonly bool _isInitialized = false; + + public IManager(ServiceCtx context) + { + _fqdnResolver = new FqdnResolver(); + + _isInitialized = true; + } + + static IManager() + { + // TODO: Load nsd settings through the savedata 0x80000000000000B0 (nsdsave:/). + + if (!NxSettings.Settings.TryGetValue("nsd!test_mode", out object testMode)) + { + // return ResultCode.InvalidSettingsValue; + } + + if (!NxSettings.Settings.TryGetValue("nsd!environment_identifier", out object environmentIdentifier)) + { + // return ResultCode.InvalidSettingsValue; + } + + NsdSettings = new NsdSettings + { + Initialized = true, + TestMode = (bool)testMode, + Environment = (string)environmentIdentifier, + }; + } + + [CommandCmif(5)] // 11.0.0+ + // GetSettingUrl() -> buffer, 0x16> + public ResultCode GetSettingUrl(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(10)] + // GetSettingName() -> buffer, 0x16> + public ResultCode GetSettingName(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(11)] + // GetEnvironmentIdentifier() -> buffer environment_identifier, 0x16> + public ResultCode GetEnvironmentIdentifier(ServiceCtx context) + { + (ulong outputPosition, ulong outputSize) = context.Request.GetBufferType0x22(); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + ResultCode result = _fqdnResolver.GetEnvironmentIdentifier(out string identifier); + + if (result == ResultCode.Success) + { + byte[] identifierBuffer = Encoding.UTF8.GetBytes(identifier); + + context.Memory.Write(outputPosition, identifierBuffer); + } + + return result; + } + + [CommandCmif(12)] + // GetDeviceId() -> bytes<0x10, 1> + public ResultCode GetDeviceId(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + return ResultCode.Success; + } + + [CommandCmif(13)] + // DeleteSettings(u32) + public ResultCode DeleteSettings(ServiceCtx context) + { + uint unknown = context.RequestData.ReadUInt32(); + + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + if (unknown > 1) + { + return ResultCode.InvalidArgument; + } + + if (unknown == 1) + { + NxSettings.Settings.TryGetValue("nsd!environment_identifier", out object environmentIdentifier); + + if ((string)environmentIdentifier == NsdSettings.Environment) + { + // TODO: Call nn::fs::DeleteSystemFile() to delete the savedata file and return ResultCode. + } + else + { + // TODO: Mount the savedata file and return ResultCode. + } + } + else + { + // TODO: Call nn::fs::DeleteSystemFile() to delete the savedata file and return ResultCode. + } + + return ResultCode.Success; + } + + [CommandCmif(14)] + // ImportSettings(u32, buffer) -> buffer + public ResultCode ImportSettings(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(15)] // 4.0.0+ + // SetChangeEnvironmentIdentifierDisabled(bytes<1>) + public ResultCode SetChangeEnvironmentIdentifierDisabled(ServiceCtx context) + { + byte disabled = context.RequestData.ReadByte(); + + // TODO: When sys:set service calls will be implemented + /* + if (((nn::settings::detail::GetServiceDiscoveryControlSettings() ^ disabled) & 1) != 0 ) + { + nn::settings::detail::SetServiceDiscoveryControlSettings(disabled & 1); + } + */ + + Logger.Stub?.PrintStub(LogClass.ServiceNsd, new { disabled }); + + return ResultCode.Success; + } + + [CommandCmif(20)] + // Resolve(buffer, 0x15>) -> buffer, 0x16> + public ResultCode Resolve(ServiceCtx context) + { + ulong outputPosition = context.Request.ReceiveBuff[0].Position; + ulong outputSize = context.Request.ReceiveBuff[0].Size; + + ResultCode result = _fqdnResolver.ResolveEx(context, out _, out string resolvedAddress); + + if ((ulong)resolvedAddress.Length > outputSize) + { + return ResultCode.InvalidArgument; + } + + byte[] resolvedAddressBuffer = Encoding.UTF8.GetBytes(resolvedAddress); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + context.Memory.Write(outputPosition, resolvedAddressBuffer); + + return result; + } + + [CommandCmif(21)] + // ResolveEx(buffer, 0x15>) -> (u32, buffer, 0x16>) + public ResultCode ResolveEx(ServiceCtx context) + { + ulong outputPosition = context.Request.ReceiveBuff[0].Position; + ulong outputSize = context.Request.ReceiveBuff[0].Size; + + ResultCode result = _fqdnResolver.ResolveEx(context, out ResultCode errorCode, out string resolvedAddress); + + if ((ulong)resolvedAddress.Length > outputSize) + { + return ResultCode.InvalidArgument; + } + + byte[] resolvedAddressBuffer = Encoding.UTF8.GetBytes(resolvedAddress); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + context.Memory.Write(outputPosition, resolvedAddressBuffer); + + context.ResponseData.Write((int)errorCode); + + return result; + } + + [CommandCmif(30)] + // GetNasServiceSetting(buffer, 0x15>) -> buffer, 0x16> + public ResultCode GetNasServiceSetting(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(31)] + // GetNasServiceSettingEx(buffer, 0x15>) -> (u32, buffer, 0x16>) + public ResultCode GetNasServiceSettingEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(40)] + // GetNasRequestFqdn() -> buffer, 0x16> + public ResultCode GetNasRequestFqdn(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(41)] + // GetNasRequestFqdnEx() -> (u32, buffer, 0x16>) + public ResultCode GetNasRequestFqdnEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(42)] + // GetNasApiFqdn() -> buffer, 0x16> + public ResultCode GetNasApiFqdn(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(43)] + // GetNasApiFqdnEx() -> (u32, buffer, 0x16>) + public ResultCode GetNasApiFqdnEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(50)] + // GetCurrentSetting() -> buffer, 0x16> + public ResultCode GetCurrentSetting(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(51)] // 9.0.0+ + // WriteTestParameter(buffer) + public ResultCode WriteTestParameter(ServiceCtx context) + { + // TODO: Write test parameter through the savedata 0x80000000000000B0 (nsdsave:/test_parameter). + + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(52)] // 9.0.0+ + // ReadTestParameter() -> buffer + public ResultCode ReadTestParameter(ServiceCtx context) + { + // TODO: Read test parameter through the savedata 0x80000000000000B0 (nsdsave:/test_parameter). + + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(60)] + // ReadSaveDataFromFsForTest() -> buffer, 0x16> + public ResultCode ReadSaveDataFromFsForTest(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + // TODO: Read the savedata 0x80000000000000B0 (nsdsave:/file) and write it inside the buffer. + + Logger.Stub?.PrintStub(LogClass.ServiceNsd); + + return ResultCode.Success; + } + + [CommandCmif(61)] + // WriteSaveDataToFsForTest(buffer, 0x15>) + public ResultCode WriteSaveDataToFsForTest(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + // TODO: When sys:set service calls will be implemented + /* + if (nn::settings::detail::GetSettingsItemValueSize("nsd", "test_mode") != 1) + { + return ResultCode.InvalidSettingsValue; + } + */ + + if (!NsdSettings.TestMode) + { + return ResultCode.InvalidSettingsValue; + } + + // TODO: Write the buffer inside the savedata 0x80000000000000B0 (nsdsave:/file). + + Logger.Stub?.PrintStub(LogClass.ServiceNsd); + + return ResultCode.Success; + } + + [CommandCmif(62)] + // DeleteSaveDataOfFsForTest() + public ResultCode DeleteSaveDataOfFsForTest(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + // TODO: When sys:set service calls will be implemented + /* + if (nn::settings::detail::GetSettingsItemValueSize("nsd", "test_mode") != 1) + { + return ResultCode.InvalidSettingsValue; + } + */ + + if (!NsdSettings.TestMode) + { + return ResultCode.InvalidSettingsValue; + } + + // TODO: Delete the savedata 0x80000000000000B0. + + Logger.Stub?.PrintStub(LogClass.ServiceNsd); + + return ResultCode.Success; + } + + [CommandCmif(63)] // 4.0.0+ + // IsChangeEnvironmentIdentifierDisabled() -> bytes<1> + public ResultCode IsChangeEnvironmentIdentifierDisabled(ServiceCtx context) + { + // TODO: When sys:set service calls will be implemented use nn::settings::detail::GetServiceDiscoveryControlSettings() + + bool disabled = false; + + context.ResponseData.Write(disabled); + + Logger.Stub?.PrintStub(LogClass.ServiceNsd, new { disabled }); + + return ResultCode.Success; + } + + [CommandCmif(100)] // 10.0.0+ + // GetApplicationServerEnvironmentType() -> bytes<1> + public ResultCode GetApplicationServerEnvironmentType(ServiceCtx context) + { + // TODO: Mount the savedata 0x80000000000000B0 (nsdsave:/test_parameter) and returns the environment type stored inside if the mount succeed. + // Returns ResultCode.NullOutputObject if failed. + + ResultCode result = _fqdnResolver.GetEnvironmentIdentifier(out string identifier); + + if (result != ResultCode.Success) + { + return result; + } + + byte environmentType = identifier.AsSpan(0, 2) switch + { + "lp" => (byte)ApplicationServerEnvironmentType.Lp, + "sd" => (byte)ApplicationServerEnvironmentType.Sd, + "sp" => (byte)ApplicationServerEnvironmentType.Sp, + "dp" => (byte)ApplicationServerEnvironmentType.Dp, + _ => (byte)ApplicationServerEnvironmentType.None, + }; + + context.ResponseData.Write(environmentType); + + return ResultCode.Success; + } + + [CommandCmif(101)] // 10.0.0+ + // SetApplicationServerEnvironmentType(bytes<1>) + public ResultCode SetApplicationServerEnvironmentType(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(102)] // 10.0.0+ + // DeleteApplicationServerEnvironmentType() + public ResultCode DeleteApplicationServerEnvironmentType(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs new file mode 100644 index 00000000..2ec0f744 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs @@ -0,0 +1,99 @@ +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd.Manager +{ + class FqdnResolver + { + private const string DummyAddress = "unknown.dummy.nintendo.net"; + + public ResultCode GetEnvironmentIdentifier(out string identifier) + { + if (IManager.NsdSettings.TestMode) + { + identifier = "err"; + + return ResultCode.InvalidSettingsValue; + } + else + { + identifier = IManager.NsdSettings.Environment; + } + + return ResultCode.Success; + } + + public static ResultCode Resolve(string address, out string resolvedAddress) + { + if (address == "api.sect.srv.nintendo.net" || + address == "ctest.cdn.nintendo.net" || + address == "ctest.cdn.n.nintendoswitch.cn" || + address == "unknown.dummy.nintendo.net") + { + resolvedAddress = address; + } + else + { + // TODO: Load Environment from the savedata. + address = address.Replace("%", IManager.NsdSettings.Environment); + + resolvedAddress = ""; + + if (IManager.NsdSettings == null) + { + return ResultCode.SettingsNotInitialized; + } + + if (!IManager.NsdSettings.Initialized) + { + return ResultCode.SettingsNotLoaded; + } + + resolvedAddress = address switch + { +#pragma warning disable IDE0055 // Disable formatting + "e97b8a9d672e4ce4845ec6947cd66ef6-sb-api.accounts.nintendo.com" => "e97b8a9d672e4ce4845ec6947cd66ef6-sb.baas.nintendo.com", // dp1 environment + "api.accounts.nintendo.com" => "e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com", // dp1 environment + "e97b8a9d672e4ce4845ec6947cd66ef6-sb.accounts.nintendo.com" => "e97b8a9d672e4ce4845ec6947cd66ef6-sb.baas.nintendo.com", // lp1 environment + "accounts.nintendo.com" => "e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com", // lp1 environment + /* + // TODO: Determine fields of the struct. + this + 0xEB8 => this + 0xEB8 + 0x300 + this + 0x2BE8 => this + 0x2BE8 + 0x300 + */ + _ => address, +#pragma warning restore IDE0055 + }; + } + + return ResultCode.Success; + } + + public ResultCode ResolveEx(ServiceCtx context, out ResultCode resultCode, out string resolvedAddress) + { + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + byte[] addressBuffer = new byte[inputSize]; + + context.Memory.Read(inputPosition, addressBuffer); + + string address = Encoding.UTF8.GetString(addressBuffer).TrimEnd('\0'); + + resultCode = Resolve(address, out resolvedAddress); + + if (resultCode != ResultCode.Success) + { + resolvedAddress = DummyAddress; + } + + if (IManager.NsdSettings.TestMode) + { + return ResultCode.Success; + } + else + { + return resultCode; + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs new file mode 100644 index 00000000..5a62922f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd +{ + enum ResultCode + { + ModuleId = 141, + ErrorCodeShift = 9, + + Success = 0, + + InvalidSettingsValue = (1 << ErrorCodeShift) | ModuleId, + InvalidObject1 = (3 << ErrorCodeShift) | ModuleId, + InvalidObject2 = (4 << ErrorCodeShift) | ModuleId, + NullOutputObject = (5 << ErrorCodeShift) | ModuleId, + SettingsNotLoaded = (6 << ErrorCodeShift) | ModuleId, + InvalidArgument = (8 << ErrorCodeShift) | ModuleId, + SettingsNotInitialized = (10 << ErrorCodeShift) | ModuleId, + ServiceNotInitialized = (400 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/ApplicationServerEnvironmentType.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/ApplicationServerEnvironmentType.cs new file mode 100644 index 00000000..bc10499c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/ApplicationServerEnvironmentType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd.Types +{ + enum ApplicationServerEnvironmentType : byte + { + None, + Lp, + Sd, + Sp, + Dp, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs new file mode 100644 index 00000000..8b37c9fc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd +{ + class NsdSettings + { + public bool Initialized; + public bool TestMode; + public string Environment; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs new file mode 100644 index 00000000..39af9038 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs @@ -0,0 +1,712 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd.Manager; +using Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy; +using Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types; +using Ryujinx.Memory; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres +{ + [Service("sfdnsres")] + class IResolver : IpcService + { + public IResolver(ServiceCtx context) + { + DnsMitmResolver.Instance.ReloadEntries(context); + } + + [CommandCmif(0)] + // SetDnsAddressesPrivateRequest(u32, buffer) + public ResultCode SetDnsAddressesPrivateRequest(ServiceCtx context) + { + uint cancelHandleRequest = context.RequestData.ReadUInt32(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong bufferPosition = context.Request.SendBuff[0].Position; + ulong bufferSize = context.Request.SendBuff[0].Size; +#pragma warning restore IDE0059 + + // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake of completeness. + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); + + return ResultCode.NotAllocated; + } + + [CommandCmif(1)] + // GetDnsAddressPrivateRequest(u32) -> buffer + public ResultCode GetDnsAddressPrivateRequest(ServiceCtx context) + { + uint cancelHandleRequest = context.RequestData.ReadUInt32(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; +#pragma warning restore IDE0059 + + // TODO: This is stubbed in 2.0.0+, reverse 1.0.0 version for the sake of completeness. + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); + + return ResultCode.NotAllocated; + } + + [CommandCmif(2)] + // GetHostByNameRequest(u8, u32, u64, pid, buffer) -> (u32, u32, u32, buffer) + public ResultCode GetHostByNameRequest(ServiceCtx context) + { + ulong inputBufferPosition = context.Request.SendBuff[0].Position; + ulong inputBufferSize = context.Request.SendBuff[0].Size; + + ulong outputBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong outputBufferSize = context.Request.ReceiveBuff[0].Size; + + return GetHostByNameRequestImpl(context, inputBufferPosition, inputBufferSize, outputBufferPosition, outputBufferSize, false, 0, 0); + } + + [CommandCmif(3)] + // GetHostByAddrRequest(u32, u32, u32, u64, pid, buffer) -> (u32, u32, u32, buffer) + public ResultCode GetHostByAddrRequest(ServiceCtx context) + { + ulong inputBufferPosition = context.Request.SendBuff[0].Position; + ulong inputBufferSize = context.Request.SendBuff[0].Size; + + ulong outputBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong outputBufferSize = context.Request.ReceiveBuff[0].Size; + + return GetHostByAddrRequestImpl(context, inputBufferPosition, inputBufferSize, outputBufferPosition, outputBufferSize, false, 0, 0); + } + + [CommandCmif(4)] + // GetHostStringErrorRequest(u32) -> buffer + public ResultCode GetHostStringErrorRequest(ServiceCtx context) + { + ResultCode resultCode = ResultCode.NotAllocated; + NetDbError errorCode = (NetDbError)context.RequestData.ReadInt32(); + + string errorString = errorCode switch + { + NetDbError.Success => "Resolver Error 0 (no error)", + NetDbError.HostNotFound => "Unknown host", + NetDbError.TryAgain => "Host name lookup failure", + NetDbError.NoRecovery => "Unknown server error", + NetDbError.NoData => "No address associated with name", + _ => (errorCode <= NetDbError.Internal) ? "Resolver internal error" : "Unknown resolver error", + }; + + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; + + if ((ulong)(errorString.Length + 1) <= bufferSize) + { + context.Memory.Write(bufferPosition, Encoding.ASCII.GetBytes(errorString + '\0')); + + resultCode = ResultCode.Success; + } + + return resultCode; + } + + [CommandCmif(5)] + // GetGaiStringErrorRequest(u32) -> buffer + public ResultCode GetGaiStringErrorRequest(ServiceCtx context) + { + ResultCode resultCode = ResultCode.NotAllocated; + GaiError errorCode = (GaiError)context.RequestData.ReadInt32(); + + if (errorCode > GaiError.Max) + { + errorCode = GaiError.Max; + } + + string errorString = errorCode switch + { + GaiError.AddressFamily => "Address family for hostname not supported", + GaiError.Again => "Temporary failure in name resolution", + GaiError.BadFlags => "Invalid value for ai_flags", + GaiError.Fail => "Non-recoverable failure in name resolution", + GaiError.Family => "ai_family not supported", + GaiError.Memory => "Memory allocation failure", + GaiError.NoData => "No address associated with hostname", + GaiError.NoName => "hostname nor servname provided, or not known", + GaiError.Service => "servname not supported for ai_socktype", + GaiError.SocketType => "ai_socktype not supported", + GaiError.System => "System error returned in errno", + GaiError.BadHints => "Invalid value for hints", + GaiError.Protocol => "Resolved protocol is unknown", + GaiError.Overflow => "Argument buffer overflow", + GaiError.Max => "Unknown error", + _ => "Success", + }; + + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; + + if ((ulong)(errorString.Length + 1) <= bufferSize) + { + context.Memory.Write(bufferPosition, Encoding.ASCII.GetBytes(errorString + '\0')); + + resultCode = ResultCode.Success; + } + + return resultCode; + } + + [CommandCmif(6)] + // GetAddrInfoRequest(bool enable_nsd_resolve, u32, u64 pid_placeholder, pid, buffer host, buffer service, buffer hints) -> (i32 ret, u32 bsd_errno, u32 packed_addrinfo_size, buffer response) + public ResultCode GetAddrInfoRequest(ServiceCtx context) + { + ulong responseBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong responseBufferSize = context.Request.ReceiveBuff[0].Size; + + return GetAddrInfoRequestImpl(context, responseBufferPosition, responseBufferSize, false, 0, 0); + } + + [CommandCmif(8)] + // GetCancelHandleRequest(u64, pid) -> u32 + public ResultCode GetCancelHandleRequest(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong pidPlaceHolder = context.RequestData.ReadUInt64(); +#pragma warning restore IDE0059 + uint cancelHandleRequest = 0; + + context.ResponseData.Write(cancelHandleRequest); + + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // CancelRequest(u32, u64, pid) + public ResultCode CancelRequest(ServiceCtx context) + { + uint cancelHandleRequest = context.RequestData.ReadUInt32(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong pidPlaceHolder = context.RequestData.ReadUInt64(); +#pragma warning restore IDE0059 + + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { cancelHandleRequest }); + + return ResultCode.Success; + } + + [CommandCmif(10)] // 5.0.0+ + // GetHostByNameRequestWithOptions(u8, u32, u64, pid, buffer, buffer) -> (u32, u32, u32, buffer) + public ResultCode GetHostByNameRequestWithOptions(ServiceCtx context) + { + (ulong inputBufferPosition, ulong inputBufferSize) = context.Request.GetBufferType0x21(); + (ulong outputBufferPosition, ulong outputBufferSize) = context.Request.GetBufferType0x22(); + (ulong optionsBufferPosition, ulong optionsBufferSize) = context.Request.GetBufferType0x21(); + + return GetHostByNameRequestImpl( + context, + inputBufferPosition, + inputBufferSize, + outputBufferPosition, + outputBufferSize, + true, + optionsBufferPosition, + optionsBufferSize); + } + + [CommandCmif(11)] // 5.0.0+ + // GetHostByAddrRequestWithOptions(u32, u32, u32, u64, pid, buffer, buffer) -> (u32, u32, u32, buffer) + public ResultCode GetHostByAddrRequestWithOptions(ServiceCtx context) + { + (ulong inputBufferPosition, ulong inputBufferSize) = context.Request.GetBufferType0x21(); + (ulong outputBufferPosition, ulong outputBufferSize) = context.Request.GetBufferType0x22(); + (ulong optionsBufferPosition, ulong optionsBufferSize) = context.Request.GetBufferType0x21(); + + return GetHostByAddrRequestImpl( + context, + inputBufferPosition, + inputBufferSize, + outputBufferPosition, + outputBufferSize, + true, + optionsBufferPosition, + optionsBufferSize); + } + + [CommandCmif(12)] // 5.0.0+ + // GetAddrInfoRequestWithOptions(bool enable_nsd_resolve, u32, u64 pid_placeholder, pid, buffer host, buffer service, buffer hints, buffer) -> (i32 ret, u32 bsd_errno, u32 unknown, u32 packed_addrinfo_size, buffer response) + public ResultCode GetAddrInfoRequestWithOptions(ServiceCtx context) + { + (ulong outputBufferPosition, ulong outputBufferSize) = context.Request.GetBufferType0x22(); + (ulong optionsBufferPosition, ulong optionsBufferSize) = context.Request.GetBufferType0x21(); + + return GetAddrInfoRequestImpl(context, outputBufferPosition, outputBufferSize, true, optionsBufferPosition, optionsBufferSize); + } + + [CommandCmif(14)] // 5.0.0+ + // ResolverSetOptionRequest(buffer, u64 unknown, u64 pid_placeholder, pid) -> (i32 ret, u32 bsd_errno) + public ResultCode ResolverSetOptionRequest(ServiceCtx context) + { + ulong bufferPosition = context.Request.SendBuff[0].Position; + ulong bufferSize = context.Request.SendBuff[0].Size; + + ulong unknown = context.RequestData.ReadUInt64(); + + byte[] buffer = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, buffer); + + // TODO: Parse and use options. + + Logger.Stub?.PrintStub(LogClass.ServiceSfdnsres, new { unknown }); + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.Success; + + context.ResponseData.Write((int)errno); + context.ResponseData.Write((int)netDbErrorCode); + + return ResultCode.Success; + } + + // Atmosphère extension for dns_mitm + [CommandCmif(65000)] + // AtmosphereReloadHostsFile() + public ResultCode AtmosphereReloadHostsFile(ServiceCtx context) + { + DnsMitmResolver.Instance.ReloadEntries(context); + + return ResultCode.Success; + } + + private static ResultCode GetHostByNameRequestImpl( + ServiceCtx context, + ulong inputBufferPosition, + ulong inputBufferSize, + ulong outputBufferPosition, + ulong outputBufferSize, + bool withOptions, + ulong optionsBufferPosition, + ulong optionsBufferSize) + { + string host = MemoryHelper.ReadAsciiString(context.Memory, inputBufferPosition, (int)inputBufferSize); + + if (!context.Device.Configuration.EnableInternetAccess) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Guest network access disabled, DNS Blocked: {host}"); + + WriteResponse(context, withOptions, 0, GaiError.NoData, NetDbError.HostNotFound); + + return ResultCode.Success; + } + + // TODO: Use params. + bool enableNsdResolve = (context.RequestData.ReadInt32() & 1) != 0; +#pragma warning disable IDE0059 // Remove unnecessary value assignment + int timeOut = context.RequestData.ReadInt32(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); +#pragma warning restore IDE0059 + + if (withOptions) + { + // TODO: Parse and use options. + } + + IPHostEntry hostEntry = null; + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.Overflow; + int serializedSize = 0; + + if (host.Length <= byte.MaxValue) + { + if (enableNsdResolve) + { + if (FqdnResolver.Resolve(host, out string newAddress) == Nsd.ResultCode.Success) + { + host = newAddress; + } + } + + string targetHost = host; + + if (DnsBlacklist.IsHostBlocked(host)) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"DNS Blocked: {host}"); + + netDbErrorCode = NetDbError.HostNotFound; + errno = GaiError.NoData; + } + else + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Trying to resolve: {host}"); + + try + { + hostEntry = DnsMitmResolver.Instance.ResolveAddress(targetHost); + } + catch (SocketException exception) + { + netDbErrorCode = ConvertSocketErrorCodeToNetDbError(exception.ErrorCode); + errno = ConvertSocketErrorCodeToGaiError(exception.ErrorCode, errno); + } + } + } + else + { + netDbErrorCode = NetDbError.HostNotFound; + } + + if (hostEntry != null) + { + IEnumerable addresses = GetIpv4Addresses(hostEntry); + + if (!addresses.Any()) + { + errno = GaiError.NoData; + netDbErrorCode = NetDbError.NoAddress; + } + else + { + errno = GaiError.Success; + serializedSize = SerializeHostEntries(context, outputBufferPosition, outputBufferSize, hostEntry, addresses); + } + } + + WriteResponse(context, withOptions, serializedSize, errno, netDbErrorCode); + + return ResultCode.Success; + } + + private static ResultCode GetHostByAddrRequestImpl( + ServiceCtx context, + ulong inputBufferPosition, + ulong inputBufferSize, + ulong outputBufferPosition, + ulong outputBufferSize, + bool withOptions, + ulong optionsBufferPosition, + ulong optionsBufferSize) + { + if (!context.Device.Configuration.EnableInternetAccess) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, "Guest network access disabled, DNS Blocked."); + + WriteResponse(context, withOptions, 0, GaiError.NoData, NetDbError.HostNotFound); + + return ResultCode.Success; + } + + byte[] rawIp = new byte[inputBufferSize]; + + context.Memory.Read(inputBufferPosition, rawIp); + + // TODO: Use params. +#pragma warning disable IDE0059 // Remove unnecessary value assignment + uint socketLength = context.RequestData.ReadUInt32(); + uint type = context.RequestData.ReadUInt32(); + int timeOut = context.RequestData.ReadInt32(); + ulong pidPlaceholder = context.RequestData.ReadUInt64(); +#pragma warning restore IDE0059 + + if (withOptions) + { + // TODO: Parse and use options. + } + + IPHostEntry hostEntry = null; + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.AddressFamily; + int serializedSize = 0; + + if (rawIp.Length == 4) + { + try + { + IPAddress address = new(rawIp); + + hostEntry = Dns.GetHostEntry(address); + } + catch (SocketException exception) + { + netDbErrorCode = ConvertSocketErrorCodeToNetDbError(exception.ErrorCode); + errno = ConvertSocketErrorCodeToGaiError(exception.ErrorCode, errno); + } + } + else + { + netDbErrorCode = NetDbError.NoAddress; + } + + if (hostEntry != null) + { + errno = GaiError.Success; + serializedSize = SerializeHostEntries(context, outputBufferPosition, outputBufferSize, hostEntry, GetIpv4Addresses(hostEntry)); + } + + WriteResponse(context, withOptions, serializedSize, errno, netDbErrorCode); + + return ResultCode.Success; + } + + private static int SerializeHostEntries(ServiceCtx context, ulong outputBufferPosition, ulong outputBufferSize, IPHostEntry hostEntry, IEnumerable addresses = null) + { + ulong originalBufferPosition = outputBufferPosition; + ulong bufferPosition = originalBufferPosition; + + string hostName = hostEntry.HostName + '\0'; + + // h_name + context.Memory.Write(bufferPosition, Encoding.ASCII.GetBytes(hostName)); + bufferPosition += (ulong)hostName.Length; + + // h_aliases list size + context.Memory.Write(bufferPosition, BinaryPrimitives.ReverseEndianness(hostEntry.Aliases.Length)); + bufferPosition += sizeof(int); + + // Actual aliases + foreach (string alias in hostEntry.Aliases) + { + context.Memory.Write(bufferPosition, Encoding.ASCII.GetBytes(alias + '\0')); + bufferPosition += (ulong)(alias.Length + 1); + } + + // h_addrtype but it's a short (also only support IPv4) + context.Memory.Write(bufferPosition, BinaryPrimitives.ReverseEndianness((short)AddressFamily.InterNetwork)); + bufferPosition += sizeof(short); + + // h_length but it's a short + context.Memory.Write(bufferPosition, BinaryPrimitives.ReverseEndianness((short)4)); + bufferPosition += sizeof(short); + + // Ip address count, we can only support ipv4 (blame Nintendo) + context.Memory.Write(bufferPosition, addresses != null ? BinaryPrimitives.ReverseEndianness(addresses.Count()) : 0); + bufferPosition += sizeof(int); + + if (addresses != null) + { + foreach (IPAddress ip in addresses) + { + context.Memory.Write(bufferPosition, BinaryPrimitives.ReverseEndianness(BitConverter.ToInt32(ip.GetAddressBytes(), 0))); + bufferPosition += sizeof(int); + } + } + + return (int)(bufferPosition - originalBufferPosition); + } + + private static ResultCode GetAddrInfoRequestImpl( + ServiceCtx context, + ulong responseBufferPosition, + ulong responseBufferSize, + bool withOptions, + ulong optionsBufferPosition, + ulong optionsBufferSize) + { + bool enableNsdResolve = (context.RequestData.ReadInt32() & 1) != 0; +#pragma warning disable IDE0059 // Remove unnecessary value assignment + uint cancelHandle = context.RequestData.ReadUInt32(); +#pragma warning restore IDE0059 + + string host = MemoryHelper.ReadAsciiString(context.Memory, context.Request.SendBuff[0].Position, (long)context.Request.SendBuff[0].Size); + string service = MemoryHelper.ReadAsciiString(context.Memory, context.Request.SendBuff[1].Position, (long)context.Request.SendBuff[1].Size); + + if (!context.Device.Configuration.EnableInternetAccess) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Guest network access disabled, DNS Blocked: {host}"); + + WriteResponse(context, withOptions, 0, GaiError.NoData, NetDbError.HostNotFound); + + return ResultCode.Success; + } + + // NOTE: We ignore hints for now. +#pragma warning disable IDE0059 // Remove unnecessary value assignment + List hints = DeserializeAddrInfos(context.Memory, context.Request.SendBuff[2].Position, context.Request.SendBuff[2].Size); +#pragma warning restore IDE0059 + + if (withOptions) + { + // TODO: Find unknown, Parse and use options. +#pragma warning disable IDE0059 // Remove unnecessary value assignment + uint unknown = context.RequestData.ReadUInt32(); +#pragma warning restore IDE0059 + } + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong pidPlaceHolder = context.RequestData.ReadUInt64(); +#pragma warning restore IDE0059 + + IPHostEntry hostEntry = null; + + NetDbError netDbErrorCode = NetDbError.Success; + GaiError errno = GaiError.AddressFamily; + int serializedSize = 0; + + if (host.Length <= byte.MaxValue) + { + if (enableNsdResolve) + { + if (FqdnResolver.Resolve(host, out string newAddress) == Nsd.ResultCode.Success) + { + host = newAddress; + } + } + + string targetHost = host; + + if (DnsBlacklist.IsHostBlocked(host)) + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"DNS Blocked: {host}"); + + netDbErrorCode = NetDbError.HostNotFound; + errno = GaiError.NoData; + } + else + { + Logger.Info?.Print(LogClass.ServiceSfdnsres, $"Trying to resolve: {host}"); + + try + { + hostEntry = DnsMitmResolver.Instance.ResolveAddress(targetHost); + } + catch (SocketException exception) + { + netDbErrorCode = ConvertSocketErrorCodeToNetDbError(exception.ErrorCode); + errno = ConvertSocketErrorCodeToGaiError(exception.ErrorCode, errno); + } + } + } + else + { + netDbErrorCode = NetDbError.NoAddress; + } + + if (hostEntry != null) + { + if (int.TryParse(service, out int port) || string.IsNullOrEmpty(service)) + { + errno = GaiError.Success; + serializedSize = SerializeAddrInfos(context, responseBufferPosition, responseBufferSize, hostEntry, port); + } + else + { + errno = GaiError.Service; + } + } + + WriteResponse(context, withOptions, serializedSize, errno, netDbErrorCode); + + return ResultCode.Success; + } + + private static List DeserializeAddrInfos(IVirtualMemoryManager memory, ulong address, ulong size) + { + List result = new(); + + ReadOnlySpan data = memory.GetSpan(address, (int)size); + + while (!data.IsEmpty) + { + AddrInfoSerialized info = AddrInfoSerialized.Read(data, out data); + + if (info == null) + { + break; + } + + result.Add(info); + } + + return result; + } + + private static int SerializeAddrInfos(ServiceCtx context, ulong responseBufferPosition, ulong responseBufferSize, IPHostEntry hostEntry, int port) + { + ulong originalBufferPosition = responseBufferPosition; +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong bufferPosition = originalBufferPosition; + + byte[] hostName = Encoding.ASCII.GetBytes(hostEntry.HostName + '\0'); +#pragma warning restore IDE0059 + + using WritableRegion region = context.Memory.GetWritableRegion(responseBufferPosition, (int)responseBufferSize); + + Span data = region.Memory.Span; + + for (int i = 0; i < hostEntry.AddressList.Length; i++) + { + IPAddress ip = hostEntry.AddressList[i]; + + if (ip.AddressFamily != AddressFamily.InterNetwork) + { + continue; + } + + // NOTE: 0 = Any + AddrInfoSerializedHeader header = new(ip, 0); + AddrInfo4 addr = new(ip, (short)port); + AddrInfoSerialized info = new(header, addr, null, hostEntry.HostName); + + data = info.Write(data); + } + + uint sentinel = 0; + MemoryMarshal.Write(data, in sentinel); + data = data[sizeof(uint)..]; + + return region.Memory.Span.Length - data.Length; + } + + private static void WriteResponse( + ServiceCtx context, + bool withOptions, + int serializedSize, + GaiError errno, + NetDbError netDbErrorCode) + { + if (withOptions) + { + context.ResponseData.Write(serializedSize); + context.ResponseData.Write((int)errno); + context.ResponseData.Write((int)netDbErrorCode); + context.ResponseData.Write(0); + } + else + { + context.ResponseData.Write((int)netDbErrorCode); + context.ResponseData.Write((int)errno); + context.ResponseData.Write(serializedSize); + } + } + + private static IEnumerable GetIpv4Addresses(IPHostEntry hostEntry) + { + return hostEntry.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetwork); + } + + private static NetDbError ConvertSocketErrorCodeToNetDbError(int errorCode) + { + return errorCode switch + { + 11001 => NetDbError.HostNotFound, + 11002 => NetDbError.TryAgain, + 11003 => NetDbError.NoRecovery, + 11004 => NetDbError.NoData, + _ => NetDbError.Internal, + }; + } + + private static GaiError ConvertSocketErrorCodeToGaiError(int errorCode, GaiError errno) + { + return errorCode switch + { + 11001 => GaiError.NoData, + 10060 => GaiError.Again, + _ => errno, + }; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs new file mode 100644 index 00000000..608ea2f5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs @@ -0,0 +1,44 @@ +using System.Text.RegularExpressions; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy +{ + static partial class DnsBlacklist + { + const RegexOptions RegexOpts = RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture; + + [GeneratedRegex(@"^(.*)\-lp1\.(n|s)\.n\.srv\.nintendo\.net$", RegexOpts)] + private static partial Regex BlockedHost1(); + [GeneratedRegex(@"^(.*)\-lp1\.lp1\.t\.npln\.srv\.nintendo\.net$", RegexOpts)] + private static partial Regex BlockedHost2(); + [GeneratedRegex(@"^(.*)\-lp1\.(znc|p)\.srv\.nintendo\.net$", RegexOpts)] + private static partial Regex BlockedHost3(); + [GeneratedRegex(@"^(.*)\-sb\-api\.accounts\.nintendo\.com$", RegexOpts)] + private static partial Regex BlockedHost4(); + [GeneratedRegex(@"^(.*)\-sb\.accounts\.nintendo\.com$", RegexOpts)] + private static partial Regex BlockedHost5(); + [GeneratedRegex(@"^accounts\.nintendo\.com$", RegexOpts)] + private static partial Regex BlockedHost6(); + + private static readonly Regex[] _blockedHosts = { + BlockedHost1(), + BlockedHost2(), + BlockedHost3(), + BlockedHost4(), + BlockedHost5(), + BlockedHost6(), + }; + + public static bool IsHostBlocked(string host) + { + foreach (Regex regex in _blockedHosts) + { + if (regex.IsMatch(host)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs new file mode 100644 index 00000000..d17a999d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs @@ -0,0 +1,106 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Sockets.Nsd; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Enumeration; +using System.Net; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy +{ + class DnsMitmResolver + { + private const string HostsFilePath = "/atmosphere/hosts/default.txt"; + + private static DnsMitmResolver _instance; + public static DnsMitmResolver Instance => _instance ??= new DnsMitmResolver(); + + private readonly Dictionary _mitmHostEntries = new(); + + public void ReloadEntries(ServiceCtx context) + { + string sdPath = FileSystem.VirtualFileSystem.GetSdCardPath(); + string filePath = FileSystem.VirtualFileSystem.GetFullPath(sdPath, HostsFilePath); + + _mitmHostEntries.Clear(); + + if (File.Exists(filePath)) + { + using FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read); + using StreamReader reader = new(fileStream); + + while (!reader.EndOfStream) + { + string line = reader.ReadLine(); + + if (line == null) + { + break; + } + + // Ignore comments and empty lines + if (line.StartsWith('#') || line.Trim().Length == 0) + { + continue; + } + + string[] entry = line.Split(new[] { ' ', '\t' }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + // Hosts file example entry: + // 127.0.0.1 localhost loopback + + // 0. Check the size of the array + if (entry.Length < 2) + { + Logger.Warning?.PrintMsg(LogClass.ServiceBsd, $"Invalid entry in hosts file: {line}"); + + continue; + } + + // 1. Parse the address + if (!IPAddress.TryParse(entry[0], out IPAddress address)) + { + Logger.Warning?.PrintMsg(LogClass.ServiceBsd, $"Failed to parse IP address in hosts file: {entry[0]}"); + + continue; + } + + // 2. Check for AMS hosts file extension: "%" + for (int i = 1; i < entry.Length; i++) + { + entry[i] = entry[i].Replace("%", IManager.NsdSettings.Environment); + } + + // 3. Add hostname to entry dictionary (updating duplicate entries) + foreach (string hostname in entry[1..]) + { + _mitmHostEntries[hostname] = address; + } + } + } + } + + public IPHostEntry ResolveAddress(string host) + { + foreach (var hostEntry in _mitmHostEntries) + { + // Check for AMS hosts file extension: "*" + // NOTE: MatchesSimpleExpression also allows "?" as a wildcard + if (FileSystemName.MatchesSimpleExpression(hostEntry.Key, host)) + { + Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Redirecting '{host}' to: {hostEntry.Value}"); + + return new IPHostEntry + { + AddressList = new[] { hostEntry.Value }, + HostName = hostEntry.Key, + Aliases = Array.Empty(), + }; + } + } + + // No match has been found, resolve the host using regular dns + return Dns.GetHostEntry(host); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs new file mode 100644 index 00000000..de839e3a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs @@ -0,0 +1,51 @@ +using Ryujinx.Common.Memory; +using System; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + struct AddrInfo4 + { + public byte Length; + public byte Family; + public short Port; + public Array4 Address; + public Array8 Padding; + + public AddrInfo4(IPAddress address, short port) + { + Length = (byte)Unsafe.SizeOf>(); + Family = (byte)AddressFamily.InterNetwork; + Port = IPAddress.HostToNetworkOrder(port); + Address = new Array4(); + + address.TryWriteBytes(Address.AsSpan(), out _); + } + + public void ToNetworkOrder() + { + Port = IPAddress.HostToNetworkOrder(Port); + + RawIpv4AddressNetworkEndianSwap(ref Address); + } + + public void ToHostOrder() + { + Port = IPAddress.NetworkToHostOrder(Port); + + RawIpv4AddressNetworkEndianSwap(ref Address); + } + + public static void RawIpv4AddressNetworkEndianSwap(ref Array4 address) + { + if (BitConverter.IsLittleEndian) + { + address.AsSpan().Reverse(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs new file mode 100644 index 00000000..471c274c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs @@ -0,0 +1,143 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.Utilities; +using System; +using System.Diagnostics; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + class AddrInfoSerialized + { + public AddrInfoSerializedHeader Header; + public AddrInfo4? SocketAddress; + public Array4? RawIPv4Address; + public string CanonicalName; + + public AddrInfoSerialized(AddrInfoSerializedHeader header, AddrInfo4? address, Array4? rawIPv4Address, string canonicalName) + { + Header = header; + SocketAddress = address; + RawIPv4Address = rawIPv4Address; + CanonicalName = canonicalName; + } + + public static AddrInfoSerialized Read(ReadOnlySpan buffer, out ReadOnlySpan rest) + { + if (!MemoryMarshal.TryRead(buffer, out AddrInfoSerializedHeader header)) + { + rest = buffer; + + return null; + } + + AddrInfo4? socketAddress = null; + Array4? rawIPv4Address = null; + string canonicalName; + + buffer = buffer[Unsafe.SizeOf()..]; + + header.ToHostOrder(); + + if (header.Magic != SfdnsresContants.AddrInfoMagic) + { + rest = buffer; + + return null; + } + + Debug.Assert(header.Magic == SfdnsresContants.AddrInfoMagic); + + if (header.AddressLength == 0) + { + rest = buffer; + + return null; + } + + if (header.Family == (int)AddressFamily.InterNetwork) + { + socketAddress = MemoryMarshal.Read(buffer); + socketAddress.Value.ToHostOrder(); + + buffer = buffer[Unsafe.SizeOf()..]; + } + // AF_INET6 + else if (header.Family == 28) + { + throw new NotImplementedException(); + } + else + { + // Nintendo hardcode 4 bytes in that case here. + Array4 address = MemoryMarshal.Read>(buffer); + AddrInfo4.RawIpv4AddressNetworkEndianSwap(ref address); + + rawIPv4Address = address; + + buffer = buffer[Unsafe.SizeOf>()..]; + } + + canonicalName = StringUtils.ReadUtf8String(buffer, out int dataRead); + buffer = buffer[dataRead..]; + + rest = buffer; + + return new AddrInfoSerialized(header, socketAddress, rawIPv4Address, canonicalName); + } + + public Span Write(Span buffer) + { + int familly = Header.Family; + + Header.ToNetworkOrder(); + + MemoryMarshal.Write(buffer, in Header); + + buffer = buffer[Unsafe.SizeOf()..]; + + if (familly == (int)AddressFamily.InterNetwork) + { + AddrInfo4 socketAddress = SocketAddress.Value; + socketAddress.ToNetworkOrder(); + + MemoryMarshal.Write(buffer, in socketAddress); + + buffer = buffer[Unsafe.SizeOf()..]; + } + // AF_INET6 + else if (familly == 28) + { + throw new NotImplementedException(); + } + else + { + Array4 rawIPv4Address = RawIPv4Address.Value; + AddrInfo4.RawIpv4AddressNetworkEndianSwap(ref rawIPv4Address); + + MemoryMarshal.Write(buffer, in rawIPv4Address); + + buffer = buffer[Unsafe.SizeOf>()..]; + } + + if (CanonicalName == null) + { + buffer[0] = 0; + + buffer = buffer[1..]; + } + else + { + byte[] canonicalName = Encoding.ASCII.GetBytes(CanonicalName + '\0'); + + canonicalName.CopyTo(buffer); + + buffer = buffer[canonicalName.Length..]; + } + + return buffer; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs new file mode 100644 index 00000000..8415382a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs @@ -0,0 +1,57 @@ +using Ryujinx.Common.Memory; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 6 * sizeof(int))] + struct AddrInfoSerializedHeader + { + public uint Magic; + public int Flags; + public int Family; + public int SocketType; + public int Protocol; + public uint AddressLength; + + public AddrInfoSerializedHeader(IPAddress address, SocketType socketType) + { + Magic = SfdnsresContants.AddrInfoMagic; + Flags = 0; + Family = (int)address.AddressFamily; + SocketType = (int)socketType; + Protocol = 0; + + if (address.AddressFamily == AddressFamily.InterNetwork) + { + AddressLength = (uint)Unsafe.SizeOf(); + } + else + { + AddressLength = (uint)Unsafe.SizeOf>(); + } + } + + public void ToNetworkOrder() + { + Magic = (uint)IPAddress.HostToNetworkOrder((int)Magic); + Flags = IPAddress.HostToNetworkOrder(Flags); + Family = IPAddress.HostToNetworkOrder(Family); + SocketType = IPAddress.HostToNetworkOrder(SocketType); + Protocol = IPAddress.HostToNetworkOrder(Protocol); + AddressLength = (uint)IPAddress.HostToNetworkOrder((int)AddressLength); + } + + public void ToHostOrder() + { + Magic = (uint)IPAddress.NetworkToHostOrder((int)Magic); + Flags = IPAddress.NetworkToHostOrder(Flags); + Family = IPAddress.NetworkToHostOrder(Family); + SocketType = IPAddress.NetworkToHostOrder(SocketType); + Protocol = IPAddress.NetworkToHostOrder(Protocol); + AddressLength = (uint)IPAddress.NetworkToHostOrder((int)AddressLength); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs new file mode 100644 index 00000000..81326c7f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres +{ + enum GaiError + { + Success, + AddressFamily, + Again, + BadFlags, + Fail, + Family, + Memory, + NoData, + NoName, + Service, + SocketType, + System, + BadHints, + Protocol, + Overflow, + Max, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs new file mode 100644 index 00000000..8fc30261 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres +{ + enum NetDbError + { + Internal = -1, + Success, + HostNotFound, + TryAgain, + NoRecovery, + NoData, + NoAddress = NoData, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs new file mode 100644 index 00000000..8af70224 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types +{ + class SfdnsresContants + { + public const uint AddrInfoMagic = 0xBEEFCAFE; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs b/src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs new file mode 100644 index 00000000..4a2a910f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs @@ -0,0 +1,128 @@ +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Services.Spl.Types; + +namespace Ryujinx.HLE.HOS.Services.Spl +{ + [Service("spl:")] + [Service("spl:es")] + [Service("spl:fs")] + [Service("spl:manu")] + [Service("spl:mig")] + [Service("spl:ssl")] + class IGeneralInterface : IpcService + { + public IGeneralInterface(ServiceCtx context) { } + + [CommandCmif(0)] + // GetConfig(u32 config_item) -> u64 config_value + public ResultCode GetConfig(ServiceCtx context) + { + ConfigItem configItem = (ConfigItem)context.RequestData.ReadUInt32(); + + // NOTE: Nintendo explicitly blacklists package2 hash here, amusingly. + // This is not blacklisted in safemode, but we're never in safe mode... + if (configItem == ConfigItem.Package2Hash) + { + return ResultCode.InvalidArguments; + } + + // TODO: This should call svcCallSecureMonitor using arg 0xC3000002. + // Since it's currently not implemented we can use a private method for now. + SmcResult result = SmcGetConfig(context, out ulong configValue, configItem); + + // Nintendo has some special handling here for hardware type/is_retail. + if (result == SmcResult.InvalidArgument) + { + switch (configItem) + { + case ConfigItem.HardwareType: + configValue = (ulong)HardwareType.Icosa; + result = SmcResult.Success; + break; + case ConfigItem.HardwareState: + configValue = (ulong)HardwareState.Development; + result = SmcResult.Success; + break; + default: + break; + } + } + + context.ResponseData.Write(configValue); + + return (ResultCode)((int)result << 9) | ResultCode.ModuleId; + } + + private SmcResult SmcGetConfig(ServiceCtx context, out ulong configValue, ConfigItem configItem) + { + configValue = default; + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + SystemVersion version = context.Device.System.ContentManager.GetCurrentFirmwareVersion(); +#pragma warning restore IDE0059 + MemorySize memorySize = context.Device.Configuration.MemoryConfiguration.ToKernelMemorySize(); + + switch (configItem) + { + case ConfigItem.DisableProgramVerification: + configValue = 0; + break; + case ConfigItem.DramId: + if (memorySize == MemorySize.MemorySize8GiB) + { + configValue = (ulong)DramId.IowaSamsung8GiB; + } + else if (memorySize == MemorySize.MemorySize6GiB) + { + configValue = (ulong)DramId.IcosaSamsung6GiB; + } + else + { + configValue = (ulong)DramId.IcosaSamsung4GiB; + } + break; + case ConfigItem.SecurityEngineInterruptNumber: + return SmcResult.NotImplemented; + case ConfigItem.FuseVersion: + return SmcResult.NotImplemented; + case ConfigItem.HardwareType: + configValue = (ulong)HardwareType.Icosa; + break; + case ConfigItem.HardwareState: + configValue = (ulong)HardwareState.Production; + break; + case ConfigItem.IsRecoveryBoot: + configValue = 0; + break; + case ConfigItem.DeviceId: + return SmcResult.NotImplemented; + case ConfigItem.BootReason: + // This was removed in firmware 4.0.0. + return SmcResult.InvalidArgument; + case ConfigItem.MemoryMode: + configValue = (ulong)context.Device.Configuration.MemoryConfiguration; + break; + case ConfigItem.IsDevelopmentFunctionEnabled: + configValue = 0; + break; + case ConfigItem.KernelConfiguration: + return SmcResult.NotImplemented; + case ConfigItem.IsChargerHiZModeEnabled: + return SmcResult.NotImplemented; + case ConfigItem.QuestState: + return SmcResult.NotImplemented; + case ConfigItem.RegulatorType: + return SmcResult.NotImplemented; + case ConfigItem.DeviceUniqueKeyGeneration: + return SmcResult.NotImplemented; + case ConfigItem.Package2Hash: + return SmcResult.NotImplemented; + default: + return SmcResult.InvalidArgument; + } + + return SmcResult.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs b/src/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs new file mode 100644 index 00000000..fc58613f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs @@ -0,0 +1,36 @@ +using System.Security.Cryptography; + +namespace Ryujinx.HLE.HOS.Services.Spl +{ + [Service("csrng")] + class IRandomInterface : DisposableIpcService + { + private readonly RandomNumberGenerator _rng; + + public IRandomInterface(ServiceCtx context) + { + _rng = RandomNumberGenerator.Create(); + } + + [CommandCmif(0)] + // GetRandomBytes() -> buffer + public ResultCode GetRandomBytes(ServiceCtx context) + { + byte[] randomBytes = new byte[context.Request.ReceiveBuff[0].Size]; + + _rng.GetBytes(randomBytes); + + context.Memory.Write(context.Request.ReceiveBuff[0].Position, randomBytes); + + return ResultCode.Success; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + _rng.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Spl/ResultCode.cs new file mode 100644 index 00000000..fe77c753 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/ResultCode.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Spl +{ + enum ResultCode + { + ModuleId = 26, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArguments = (101 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/Types/ConfigItem.cs b/src/Ryujinx.HLE/HOS/Services/Spl/Types/ConfigItem.cs new file mode 100644 index 00000000..ec5da24a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/Types/ConfigItem.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.HLE.HOS.Services.Spl.Types +{ + enum ConfigItem + { + // Standard config items. + DisableProgramVerification = 1, + DramId = 2, + SecurityEngineInterruptNumber = 3, + FuseVersion = 4, + HardwareType = 5, + HardwareState = 6, + IsRecoveryBoot = 7, + DeviceId = 8, + BootReason = 9, + MemoryMode = 10, + IsDevelopmentFunctionEnabled = 11, + KernelConfiguration = 12, + IsChargerHiZModeEnabled = 13, + QuestState = 14, + RegulatorType = 15, + DeviceUniqueKeyGeneration = 16, + Package2Hash = 17, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/Types/DramId.cs b/src/Ryujinx.HLE/HOS/Services/Spl/Types/DramId.cs new file mode 100644 index 00000000..7a53ee11 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/Types/DramId.cs @@ -0,0 +1,35 @@ +namespace Ryujinx.HLE.HOS.Services.Spl.Types +{ + enum DramId + { + IcosaSamsung4GiB, + IcosaHynix4GiB, + IcosaMicron4GiB, + IowaHynix1y4GiB, + IcosaSamsung6GiB, + HoagHynix1y4GiB, + AulaHynix1y4GiB, + IowaX1X2Samsung4GiB, + IowaSansung4GiB, + IowaSamsung8GiB, + IowaHynix4GiB, + IowaMicron4GiB, + HoagSamsung4GiB, + HoagSamsung8GiB, + HoagHynix4GiB, + HoagMicron4GiB, + IowaSamsung4GiBY, + IowaSamsung1y4GiBX, + IowaSamsung1y8GiBX, + HoagSamsung1y4GiBX, + IowaSamsung1y4GiBY, + IowaSamsung1y8GiBY, + AulaSamsung1y4GiB, + HoagSamsung1y8GiBX, + AulaSamsung1y4GiBX, + IowaMicron1y4GiB, + HoagMicron1y4GiB, + AulaMicron1y4GiB, + AulaSamsung1y8GiBX, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareState.cs b/src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareState.cs new file mode 100644 index 00000000..de37f591 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Spl.Types +{ + enum HardwareState + { + Development, + Production, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareType.cs b/src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareType.cs new file mode 100644 index 00000000..2a1e0d8c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareType.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Spl.Types +{ + enum HardwareType + { + Icosa, + Copper, + Hoag, + Iowa, + Calcio, + Aula + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Spl/Types/SmcResult.cs b/src/Ryujinx.HLE/HOS/Services/Spl/Types/SmcResult.cs new file mode 100644 index 00000000..13bdeccc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Spl/Types/SmcResult.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Spl.Types +{ + enum SmcResult + { + Success = 0, + NotImplemented = 1, + InvalidArgument = 2, + Busy = 3, + NoAsyncOperation = 4, + InvalidAsyncOperation = 5, + NotPermitted = 6, + NotInitialized = 7, + + PsciNotSupported = -1, + PsciInvalidParameters = -2, + PsciDenied = -3, + PsciAlreadyOn = -4, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs new file mode 100644 index 00000000..5d2e06a4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs @@ -0,0 +1,243 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Ssl.Types; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ssl +{ + class BuiltInCertificateManager + { + private const long CertStoreTitleId = 0x0100000000000800; + + private const string CertStoreTitleMissingErrorMessage = "CertStore system title not found! SSL CA retrieving will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide#initial-setup-continued---installation-of-firmware for more information)"; + + private static BuiltInCertificateManager _instance; + + public static BuiltInCertificateManager Instance + { + get + { + _instance ??= new BuiltInCertificateManager(); + + return _instance; + } + } + + private VirtualFileSystem _virtualFileSystem; + private IntegrityCheckLevel _fsIntegrityCheckLevel; + private ContentManager _contentManager; + private bool _initialized; + private Dictionary _certificates; + + private readonly object _lock = new(); + + private struct CertStoreFileHeader + { + private const uint ValidMagic = 0x546C7373; + +#pragma warning disable CS0649 // Field is never assigned to + public uint Magic; + public uint EntriesCount; +#pragma warning restore CS0649 + + public readonly bool IsValid() + { + return Magic == ValidMagic; + } + } + + private struct CertStoreFileEntry + { +#pragma warning disable CS0649 // Field is never assigned to + public CaCertificateId Id; + public TrustedCertStatus Status; + public uint DataSize; + public uint DataOffset; +#pragma warning restore CS0649 + } + + public class CertStoreEntry + { + public CaCertificateId Id; + public TrustedCertStatus Status; + public byte[] Data; + } + + public string GetCertStoreTitleContentPath() + { + return _contentManager.GetInstalledContentPath(CertStoreTitleId, StorageId.BuiltInSystem, NcaContentType.Data); + } + + public bool HasCertStoreTitle() + { + return !string.IsNullOrEmpty(GetCertStoreTitleContentPath()); + } + + private CertStoreEntry ReadCertStoreEntry(ReadOnlySpan buffer, CertStoreFileEntry entry) + { + string customCertificatePath = System.IO.Path.Join(AppDataManager.BaseDirPath, "system", "ssl", $"{entry.Id}.der"); + + byte[] data; + + if (File.Exists(customCertificatePath)) + { + data = File.ReadAllBytes(customCertificatePath); + } + else + { + data = buffer.Slice((int)entry.DataOffset, (int)entry.DataSize).ToArray(); + } + + return new CertStoreEntry + { + Id = entry.Id, + Status = entry.Status, + Data = data, + }; + } + + public void Initialize(Switch device) + { + lock (_lock) + { + _certificates = new Dictionary(); + _initialized = false; + _contentManager = device.System.ContentManager; + _virtualFileSystem = device.FileSystem; + _fsIntegrityCheckLevel = device.System.FsIntegrityCheckLevel; + + if (HasCertStoreTitle()) + { + using LocalStorage ncaFile = new(VirtualFileSystem.SwitchPathToSystemPath(GetCertStoreTitleContentPath()), FileAccess.Read, FileMode.Open); + + Nca nca = new(_virtualFileSystem.KeySet, ncaFile); + + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel); + + using var trustedCertsFileRef = new UniqueRef(); + + Result result = romfs.OpenFile(ref trustedCertsFileRef.Ref, "/ssl_TrustedCerts.bdf".ToU8Span(), OpenMode.Read); + + if (!result.IsSuccess()) + { + // [1.0.0 - 2.3.0] + if (ResultFs.PathNotFound.Includes(result)) + { + result = romfs.OpenFile(ref trustedCertsFileRef.Ref, "/ssl_TrustedCerts.tcf".ToU8Span(), OpenMode.Read); + } + + if (result.IsFailure()) + { + Logger.Error?.Print(LogClass.ServiceSsl, CertStoreTitleMissingErrorMessage); + + return; + } + } + + using IFile trustedCertsFile = trustedCertsFileRef.Release(); + + trustedCertsFile.GetSize(out long fileSize).ThrowIfFailure(); + + Span trustedCertsRaw = new byte[fileSize]; + + trustedCertsFile.Read(out _, 0, trustedCertsRaw).ThrowIfFailure(); + + CertStoreFileHeader header = MemoryMarshal.Read(trustedCertsRaw); + + if (!header.IsValid()) + { + Logger.Error?.Print(LogClass.ServiceSsl, "Invalid CertStore data found, skipping!"); + + return; + } + + ReadOnlySpan trustedCertsData = trustedCertsRaw[Unsafe.SizeOf()..]; + ReadOnlySpan trustedCertsEntries = MemoryMarshal.Cast(trustedCertsData)[..(int)header.EntriesCount]; + + foreach (CertStoreFileEntry entry in trustedCertsEntries) + { + _certificates.Add(entry.Id, ReadCertStoreEntry(trustedCertsData, entry)); + } + + _initialized = true; + } + } + } + + public bool TryGetCertificates( + ReadOnlySpan ids, + out CertStoreEntry[] entries, + out bool hasAllCertificates, + out int requiredSize) + { + lock (_lock) + { + if (!_initialized) + { + throw new InvalidSystemResourceException(CertStoreTitleMissingErrorMessage); + } + + requiredSize = 0; + hasAllCertificates = false; + + foreach (CaCertificateId id in ids) + { + if (id == CaCertificateId.All) + { + hasAllCertificates = true; + + break; + } + } + + if (hasAllCertificates) + { + entries = new CertStoreEntry[_certificates.Count]; + requiredSize = (_certificates.Count + 1) * Unsafe.SizeOf(); + + int i = 0; + + foreach (CertStoreEntry entry in _certificates.Values) + { + entries[i++] = entry; + requiredSize += (entry.Data.Length + 3) & ~3; + } + + return true; + } + else + { + entries = new CertStoreEntry[ids.Length]; + requiredSize = ids.Length * Unsafe.SizeOf(); + + for (int i = 0; i < ids.Length; i++) + { + if (!_certificates.TryGetValue(ids[i], out CertStoreEntry entry)) + { + return false; + } + + entries[i] = entry; + requiredSize += (entry.Data.Length + 3) & ~3; + } + + return true; + } + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs new file mode 100644 index 00000000..5e4a0c53 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs @@ -0,0 +1,127 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Ssl.SslService; +using Ryujinx.HLE.HOS.Services.Ssl.Types; +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Ssl +{ + [Service("ssl")] + class ISslService : IpcService + { + // NOTE: The SSL service is used by games to connect it to various official online services, which we do not intend to support. + // In this case it is acceptable to stub all calls of the service. + public ISslService(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateContext(nn::ssl::sf::SslVersion, u64, pid) -> object + public ResultCode CreateContext(ServiceCtx context) + { + SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong pidPlaceholder = context.RequestData.ReadUInt64(); +#pragma warning restore IDE0059 + + MakeObject(context, new ISslContext(context.Request.HandleDesc.PId, sslVersion)); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { sslVersion }); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetCertificates(buffer ids) -> (u32 certificates_count, buffer certificates) + public ResultCode GetCertificates(ServiceCtx context) + { + ReadOnlySpan ids = MemoryMarshal.Cast(context.Memory.GetSpan(context.Request.SendBuff[0].Position, (int)context.Request.SendBuff[0].Size)); + + if (!BuiltInCertificateManager.Instance.TryGetCertificates( + ids, + out BuiltInCertificateManager.CertStoreEntry[] entries, + out bool hasAllCertificates, + out int requiredSize)) + { + throw new InvalidOperationException(); + } + + if ((uint)requiredSize > (uint)context.Request.ReceiveBuff[0].Size) + { + return ResultCode.InvalidCertBufSize; + } + + int infosCount = entries.Length; + + if (hasAllCertificates) + { + infosCount++; + } + + using (WritableRegion region = context.Memory.GetWritableRegion(context.Request.ReceiveBuff[0].Position, (int)context.Request.ReceiveBuff[0].Size)) + { + Span rawData = region.Memory.Span; + Span infos = MemoryMarshal.Cast(rawData)[..infosCount]; + Span certificatesData = rawData[(Unsafe.SizeOf() * infosCount)..]; + + for (int i = 0; i < entries.Length; i++) + { + entries[i].Data.CopyTo(certificatesData); + + infos[i] = new BuiltInCertificateInfo + { + Id = entries[i].Id, + Status = entries[i].Status, + CertificateDataSize = (ulong)entries[i].Data.Length, + CertificateDataOffset = (ulong)(rawData.Length - certificatesData.Length), + }; + + certificatesData = certificatesData[entries[i].Data.Length..]; + } + + if (hasAllCertificates) + { + infos[entries.Length] = new BuiltInCertificateInfo + { + Id = CaCertificateId.All, + Status = TrustedCertStatus.Invalid, + CertificateDataSize = 0, + CertificateDataOffset = 0, + }; + } + } + + context.ResponseData.Write(entries.Length); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetCertificateBufSize(buffer ids) -> u32 buffer_size; + public ResultCode GetCertificateBufSize(ServiceCtx context) + { + ReadOnlySpan ids = MemoryMarshal.Cast(context.Memory.GetSpan(context.Request.SendBuff[0].Position, (int)context.Request.SendBuff[0].Size)); + + if (!BuiltInCertificateManager.Instance.TryGetCertificates(ids, out _, out _, out int requiredSize)) + { + throw new InvalidOperationException(); + } + + context.ResponseData.Write(requiredSize); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // SetInterfaceVersion(u32) + public ResultCode SetInterfaceVersion(ServiceCtx context) + { + // 1 = 3.0.0+, 2 = 5.0.0+, 3 = 6.0.0+ + uint interfaceVersion = context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { interfaceVersion }); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/ResultCode.cs new file mode 100644 index 00000000..e23621d0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/ResultCode.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl +{ + public enum ResultCode + { + OsModuleId = 123, + ErrorCodeShift = 9, + + Success = 0, + NoSocket = (103 << ErrorCodeShift) | OsModuleId, + InvalidSocket = (106 << ErrorCodeShift) | OsModuleId, + InvalidCertBufSize = (112 << ErrorCodeShift) | OsModuleId, + InvalidOption = (126 << ErrorCodeShift) | OsModuleId, + CertBufferTooSmall = (202 << ErrorCodeShift) | OsModuleId, + AlreadyInUse = (203 << ErrorCodeShift) | OsModuleId, + WouldBlock = (204 << ErrorCodeShift) | OsModuleId, + Timeout = (205 << ErrorCodeShift) | OsModuleId, + ConnectionReset = (209 << ErrorCodeShift) | OsModuleId, + ConnectionAbort = (210 << ErrorCodeShift) | OsModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnection.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnection.cs new file mode 100644 index 00000000..b5c608d3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnection.cs @@ -0,0 +1,516 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd; +using Ryujinx.HLE.HOS.Services.Ssl.Types; +using Ryujinx.Memory; +using System; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Ssl.SslService +{ + class ISslConnection : IpcService, IDisposable + { + private bool _doNotClockSocket; + private bool _getServerCertChain; + private bool _skipDefaultVerify; + private bool _enableAlpn; + + private readonly SslVersion _sslVersion; + private IoMode _ioMode; + private VerifyOption _verifyOption; + private SessionCacheMode _sessionCacheMode; + private string _hostName; + + private ISslConnectionBase _connection; + private BsdContext _bsdContext; + private readonly ulong _processId; + + private byte[] _nextAplnProto; + + public ISslConnection(ulong processId, SslVersion sslVersion) + { + _processId = processId; + _sslVersion = sslVersion; + _ioMode = IoMode.Blocking; + _sessionCacheMode = SessionCacheMode.None; + _verifyOption = VerifyOption.PeerCa | VerifyOption.HostName; + } + + [CommandCmif(0)] + // SetSocketDescriptor(u32) -> u32 + public ResultCode SetSocketDescriptor(ServiceCtx context) + { + if (_connection != null) + { + return ResultCode.AlreadyInUse; + } + + _bsdContext = BsdContext.GetContext(_processId); + + if (_bsdContext == null) + { + return ResultCode.InvalidSocket; + } + + int inputFd = context.RequestData.ReadInt32(); + + int internalFd = _bsdContext.DuplicateFileDescriptor(inputFd); + + if (internalFd == -1) + { + return ResultCode.InvalidSocket; + } + + InitializeConnection(internalFd); + + int outputFd = inputFd; + + if (_doNotClockSocket) + { + outputFd = -1; + } + + context.ResponseData.Write(outputFd); + + return ResultCode.Success; + } + + private void InitializeConnection(int socketFd) + { + ISocket bsdSocket = _bsdContext.RetrieveSocket(socketFd); + + _connection = new SslManagedSocketConnection(_bsdContext, _sslVersion, socketFd, bsdSocket); + } + + [CommandCmif(1)] + // SetHostName(buffer) + public ResultCode SetHostName(ServiceCtx context) + { + ulong hostNameDataPosition = context.Request.SendBuff[0].Position; + ulong hostNameDataSize = context.Request.SendBuff[0].Size; + + byte[] hostNameData = new byte[hostNameDataSize]; + + context.Memory.Read(hostNameDataPosition, hostNameData); + + _hostName = Encoding.ASCII.GetString(hostNameData).Trim('\0'); + + Logger.Info?.Print(LogClass.ServiceSsl, _hostName); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // SetVerifyOption(nn::ssl::sf::VerifyOption) + public ResultCode SetVerifyOption(ServiceCtx context) + { + _verifyOption = (VerifyOption)context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { _verifyOption }); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // SetIoMode(nn::ssl::sf::IoMode) + public ResultCode SetIoMode(ServiceCtx context) + { + if (_connection == null) + { + return ResultCode.NoSocket; + } + + _ioMode = (IoMode)context.RequestData.ReadUInt32(); + + _connection.Socket.Blocking = _ioMode == IoMode.Blocking; + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { _ioMode }); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetSocketDescriptor() -> u32 + public ResultCode GetSocketDescriptor(ServiceCtx context) + { + context.ResponseData.Write(_connection.SocketFd); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // GetHostName(buffer) -> u32 + public ResultCode GetHostName(ServiceCtx context) + { + ulong bufferAddress = context.Request.ReceiveBuff[0].Position; + ulong bufferLen = context.Request.ReceiveBuff[0].Size; + + using (var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true)) + { + Encoding.ASCII.GetBytes(_hostName, region.Memory.Span); + } + + context.ResponseData.Write((uint)_hostName.Length); + + Logger.Info?.Print(LogClass.ServiceSsl, _hostName); + + return ResultCode.Success; + } + + [CommandCmif(6)] + // GetVerifyOption() -> nn::ssl::sf::VerifyOption + public ResultCode GetVerifyOption(ServiceCtx context) + { + context.ResponseData.Write((uint)_verifyOption); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { _verifyOption }); + + return ResultCode.Success; + } + + [CommandCmif(7)] + // GetIoMode() -> nn::ssl::sf::IoMode + public ResultCode GetIoMode(ServiceCtx context) + { + context.ResponseData.Write((uint)_ioMode); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { _ioMode }); + + return ResultCode.Success; + } + + [CommandCmif(8)] + // DoHandshake() + public ResultCode DoHandshake(ServiceCtx context) + { + if (_connection == null) + { + return ResultCode.NoSocket; + } + + return _connection.Handshake(_hostName); + } + + [CommandCmif(9)] + // DoHandshakeGetServerCert() -> (u32, u32, buffer) + public ResultCode DoHandshakeGetServerCert(ServiceCtx context) + { + if (_connection == null) + { + return ResultCode.NoSocket; + } + + ResultCode result = _connection.Handshake(_hostName); + + if (result == ResultCode.Success) + { + if (_getServerCertChain) + { + using WritableRegion region = context.Memory.GetWritableRegion(context.Request.ReceiveBuff[0].Position, (int)context.Request.ReceiveBuff[0].Size); + + result = _connection.GetServerCertificate(_hostName, region.Memory.Span, out uint bufferSize, out uint certificateCount); + + context.ResponseData.Write(bufferSize); + context.ResponseData.Write(certificateCount); + } + else + { + context.ResponseData.Write(0); + context.ResponseData.Write(0); + } + } + + return result; + } + + [CommandCmif(10)] + // Read() -> (u32, buffer) + public ResultCode Read(ServiceCtx context) + { + if (_connection == null) + { + return ResultCode.NoSocket; + } + + ResultCode result; + + using WritableRegion region = context.Memory.GetWritableRegion(context.Request.ReceiveBuff[0].Position, (int)context.Request.ReceiveBuff[0].Size); + // TODO: Better error management. + result = _connection.Read(out int readCount, region.Memory); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(readCount); + } + + return result; + } + + [CommandCmif(11)] + // Write(buffer) -> s32 + public ResultCode Write(ServiceCtx context) + { + if (_connection == null) + { + return ResultCode.NoSocket; + } + + // We don't dispose as this isn't supposed to be modified + WritableRegion region = context.Memory.GetWritableRegion(context.Request.SendBuff[0].Position, (int)context.Request.SendBuff[0].Size); + + // TODO: Better error management. + ResultCode result = _connection.Write(out int writtenCount, region.Memory); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(writtenCount); + } + + return result; + } + + [CommandCmif(12)] + // Pending() -> s32 + public ResultCode Pending(ServiceCtx context) + { + if (_connection == null) + { + return ResultCode.NoSocket; + } + + context.ResponseData.Write(_connection.Pending()); + + return ResultCode.Success; + } + + [CommandCmif(13)] + // Peek() -> (s32, buffer) + public ResultCode Peek(ServiceCtx context) + { + if (_connection == null) + { + return ResultCode.NoSocket; + } + + ResultCode result; + + using WritableRegion region = context.Memory.GetWritableRegion(context.Request.ReceiveBuff[0].Position, (int)context.Request.ReceiveBuff[0].Size); + + + // TODO: Better error management. + result = _connection.Peek(out int peekCount, region.Memory); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(peekCount); + } + + return result; + } + + [CommandCmif(14)] + // Poll(nn::ssl::sf::PollEvent poll_event, u32 timeout) -> nn::ssl::sf::PollEvent + public ResultCode Poll(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(15)] + // GetVerifyCertError() + public ResultCode GetVerifyCertError(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(16)] + // GetNeededServerCertBufferSize() -> u32 + public ResultCode GetNeededServerCertBufferSize(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(17)] + // SetSessionCacheMode(nn::ssl::sf::SessionCacheMode) + public ResultCode SetSessionCacheMode(ServiceCtx context) + { + SessionCacheMode sessionCacheMode = (SessionCacheMode)context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { sessionCacheMode }); + + _sessionCacheMode = sessionCacheMode; + + return ResultCode.Success; + } + + [CommandCmif(18)] + // GetSessionCacheMode() -> nn::ssl::sf::SessionCacheMode + public ResultCode GetSessionCacheMode(ServiceCtx context) + { + context.ResponseData.Write((uint)_sessionCacheMode); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { _sessionCacheMode }); + + return ResultCode.Success; + } + + [CommandCmif(19)] + // FlushSessionCache() + public ResultCode FlushSessionCache(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(20)] + // SetRenegotiationMode(nn::ssl::sf::RenegotiationMode) + public ResultCode SetRenegotiationMode(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(21)] + // GetRenegotiationMode() -> nn::ssl::sf::RenegotiationMode + public ResultCode GetRenegotiationMode(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(22)] + // SetOption(b8 value, nn::ssl::sf::OptionType option) + public ResultCode SetOption(ServiceCtx context) + { + bool value = context.RequestData.ReadUInt32() != 0; + OptionType option = (OptionType)context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { option, value }); + + return SetOption(option, value); + } + + [CommandCmif(23)] + // GetOption(nn::ssl::sf::OptionType) -> b8 + public ResultCode GetOption(ServiceCtx context) + { + OptionType option = (OptionType)context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { option }); + + ResultCode result = GetOption(option, out bool value); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(value); + } + + return result; + } + + [CommandCmif(24)] + // GetVerifyCertErrors() -> (u32, u32, buffer) + public ResultCode GetVerifyCertErrors(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(25)] // 4.0.0+ + // GetCipherInfo(u32) -> buffer + public ResultCode GetCipherInfo(ServiceCtx context) + { + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(26)] + // SetNextAlpnProto(buffer) -> u32 + public ResultCode SetNextAlpnProto(ServiceCtx context) + { + ulong inputDataPosition = context.Request.SendBuff[0].Position; + ulong inputDataSize = context.Request.SendBuff[0].Size; + + _nextAplnProto = new byte[inputDataSize]; + + context.Memory.Read(inputDataPosition, _nextAplnProto); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { inputDataSize }); + + return ResultCode.Success; + } + + [CommandCmif(27)] + // GetNextAlpnProto(buffer) -> u32 + public ResultCode GetNextAlpnProto(ServiceCtx context) + { + ulong outputDataPosition = context.Request.ReceiveBuff[0].Position; + ulong outputDataSize = context.Request.ReceiveBuff[0].Size; + + context.Memory.Write(outputDataPosition, _nextAplnProto); + + context.ResponseData.Write(_nextAplnProto.Length); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { outputDataSize }); + + return ResultCode.Success; + } + + private ResultCode SetOption(OptionType option, bool value) + { + switch (option) + { + case OptionType.DoNotCloseSocket: + _doNotClockSocket = value; + break; + + case OptionType.GetServerCertChain: + _getServerCertChain = value; + break; + + case OptionType.SkipDefaultVerify: + _skipDefaultVerify = value; + break; + + case OptionType.EnableAlpn: + _enableAlpn = value; + break; + + default: + Logger.Warning?.Print(LogClass.ServiceSsl, $"Unsupported option {option}"); + return ResultCode.InvalidOption; + } + + return ResultCode.Success; + } + + private ResultCode GetOption(OptionType option, out bool value) + { + switch (option) + { + case OptionType.DoNotCloseSocket: + value = _doNotClockSocket; + break; + + case OptionType.GetServerCertChain: + value = _getServerCertChain; + break; + + case OptionType.SkipDefaultVerify: + value = _skipDefaultVerify; + break; + + case OptionType.EnableAlpn: + value = _enableAlpn; + break; + + default: + Logger.Warning?.Print(LogClass.ServiceSsl, $"Unsupported option {option}"); + + value = false; + return ResultCode.InvalidOption; + } + + return ResultCode.Success; + } + + public void Dispose() + { + _connection?.Dispose(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnectionBase.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnectionBase.cs new file mode 100644 index 00000000..93d85a26 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnectionBase.cs @@ -0,0 +1,24 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd; +using System; + +namespace Ryujinx.HLE.HOS.Services.Ssl.SslService +{ + interface ISslConnectionBase : IDisposable + { + int SocketFd { get; } + + ISocket Socket { get; } + + ResultCode Handshake(string hostName); + + ResultCode GetServerCertificate(string hostname, Span certificates, out uint storageSize, out uint certificateCount); + + ResultCode Write(out int writtenCount, ReadOnlyMemory buffer); + + ResultCode Read(out int readCount, Memory buffer); + + ResultCode Peek(out int peekCount, Memory buffer); + + int Pending(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslContext.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslContext.cs new file mode 100644 index 00000000..7b371d29 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslContext.cs @@ -0,0 +1,87 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Ssl.Types; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Ssl.SslService +{ + class ISslContext : IpcService + { + private uint _connectionCount; + + private readonly ulong _processId; + private readonly SslVersion _sslVersion; + private ulong _serverCertificateId; + private ulong _clientCertificateId; + + public ISslContext(ulong processId, SslVersion sslVersion) + { + _processId = processId; + _sslVersion = sslVersion; + } + + [CommandCmif(2)] + // CreateConnection() -> object + public ResultCode CreateConnection(ServiceCtx context) + { + MakeObject(context, new ISslConnection(_processId, _sslVersion)); + + _connectionCount++; + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetConnectionCount() -> u32 count + public ResultCode GetConnectionCount(ServiceCtx context) + { + context.ResponseData.Write(_connectionCount); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { _connectionCount }); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // ImportServerPki(nn::ssl::sf::CertificateFormat certificateFormat, buffer certificate) -> u64 certificateId + public ResultCode ImportServerPki(ServiceCtx context) + { + CertificateFormat certificateFormat = (CertificateFormat)context.RequestData.ReadUInt32(); + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong certificateDataPosition = context.Request.SendBuff[0].Position; + ulong certificateDataSize = context.Request.SendBuff[0].Size; +#pragma warning restore IDE0059 + + context.ResponseData.Write(_serverCertificateId++); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { certificateFormat }); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // ImportClientPki(buffer certificate, buffer ascii_password) -> u64 certificateId + public ResultCode ImportClientPki(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong certificateDataPosition = context.Request.SendBuff[0].Position; + ulong certificateDataSize = context.Request.SendBuff[0].Size; +#pragma warning restore IDE0059 + + ulong asciiPasswordDataPosition = context.Request.SendBuff[1].Position; + ulong asciiPasswordDataSize = context.Request.SendBuff[1].Size; + + byte[] asciiPasswordData = new byte[asciiPasswordDataSize]; + + context.Memory.Read(asciiPasswordDataPosition, asciiPasswordData); + + string asciiPassword = Encoding.ASCII.GetString(asciiPasswordData).Trim('\0'); + + context.ResponseData.Write(_clientCertificateId++); + + Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { asciiPassword }); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs new file mode 100644 index 00000000..8cc761ba --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs @@ -0,0 +1,276 @@ +using Ryujinx.HLE.HOS.Services.Sockets.Bsd; +using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl; +using Ryujinx.HLE.HOS.Services.Ssl.Types; +using System; +using System.IO; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; + +namespace Ryujinx.HLE.HOS.Services.Ssl.SslService +{ + class SslManagedSocketConnection : ISslConnectionBase + { + public int SocketFd { get; } + + public ISocket Socket { get; } + + private readonly BsdContext _bsdContext; + private readonly SslVersion _sslVersion; + private SslStream _stream; + private bool _isBlockingSocket; + private int _previousReadTimeout; + + public SslManagedSocketConnection(BsdContext bsdContext, SslVersion sslVersion, int socketFd, ISocket socket) + { + _bsdContext = bsdContext; + _sslVersion = sslVersion; + + SocketFd = socketFd; + Socket = socket; + } + + private void StartSslOperation() + { + // Save blocking state + _isBlockingSocket = Socket.Blocking; + + // Force blocking for SslStream + Socket.Blocking = true; + } + + private void EndSslOperation() + { + // Restore blocking state + Socket.Blocking = _isBlockingSocket; + } + + private void StartSslReadOperation() + { + StartSslOperation(); + + if (!_isBlockingSocket) + { + _previousReadTimeout = _stream.ReadTimeout; + + _stream.ReadTimeout = 1; + } + } + + private void EndSslReadOperation() + { + if (!_isBlockingSocket) + { + _stream.ReadTimeout = _previousReadTimeout; + } + + EndSslOperation(); + } + + // NOTE: We silence warnings about TLS 1.0 and 1.1 as games will likely use it. +#pragma warning disable SYSLIB0039 + private SslProtocols TranslateSslVersion(SslVersion version) + { + return (version & SslVersion.VersionMask) switch + { + SslVersion.Auto => SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13, + SslVersion.TlsV10 => SslProtocols.Tls, + SslVersion.TlsV11 => SslProtocols.Tls11, + SslVersion.TlsV12 => SslProtocols.Tls12, + SslVersion.TlsV13 => SslProtocols.Tls13, + _ => throw new NotImplementedException(version.ToString()), + }; + } +#pragma warning restore SYSLIB0039 + + /// + /// Retrieve the hostname of the current remote in case the provided hostname is null or empty. + /// + /// The current hostname + /// Either the resolved or provided hostname + /// + /// This is done to avoid getting an + /// as the remote certificate will be rejected with RemoteCertificateNameMismatch due to an empty hostname. + /// This is not what the switch does! + /// It might just skip remote hostname verification if the hostname wasn't set with before. + /// TODO: Remove this as soon as we know how the switch deals with empty hostnames + /// + private string RetrieveHostName(string hostName) + { + if (!string.IsNullOrEmpty(hostName)) + { + return hostName; + } + + try + { + return Dns.GetHostEntry(Socket.RemoteEndPoint.Address).HostName; + } + catch (SocketException) + { + return hostName; + } + } + + public ResultCode Handshake(string hostName) + { + StartSslOperation(); + _stream = new SslStream(new NetworkStream(((ManagedSocket)Socket).Socket, false), false, null, null); + hostName = RetrieveHostName(hostName); + _stream.AuthenticateAsClient(hostName, null, TranslateSslVersion(_sslVersion), false); + EndSslOperation(); + + return ResultCode.Success; + } + + public ResultCode Peek(out int peekCount, Memory buffer) + { + // NOTE: We cannot support that on .NET SSL API. + // As Nintendo's curl implementation detail check if a connection is alive via Peek, we just return that it would block to let it know that it's alive. + peekCount = -1; + + return ResultCode.WouldBlock; + } + + public int Pending() + { + // Unsupported + return 0; + } + + private bool TryTranslateWinSockError(bool isBlocking, WsaError error, out ResultCode resultCode) + { + switch (error) + { + case WsaError.WSAETIMEDOUT: + resultCode = isBlocking ? ResultCode.Timeout : ResultCode.WouldBlock; + return true; + case WsaError.WSAECONNABORTED: + resultCode = ResultCode.ConnectionAbort; + return true; + case WsaError.WSAECONNRESET: + resultCode = ResultCode.ConnectionReset; + return true; + default: + resultCode = ResultCode.Success; + return false; + } + } + + public ResultCode Read(out int readCount, Memory buffer) + { + if (!Socket.Poll(0, SelectMode.SelectRead)) + { + readCount = -1; + + return ResultCode.WouldBlock; + } + + StartSslReadOperation(); + + try + { + readCount = _stream.Read(buffer.Span); + } + catch (IOException exception) + { + readCount = -1; + + if (exception.InnerException is SocketException socketException) + { + WsaError socketErrorCode = (WsaError)socketException.SocketErrorCode; + + if (TryTranslateWinSockError(_isBlockingSocket, socketErrorCode, out ResultCode result)) + { + return result; + } + else + { + throw socketException; + } + } + else + { + throw; + } + } + finally + { + EndSslReadOperation(); + } + + return ResultCode.Success; + } + + public ResultCode Write(out int writtenCount, ReadOnlyMemory buffer) + { + if (!Socket.Poll(0, SelectMode.SelectWrite)) + { + writtenCount = 0; + + return ResultCode.WouldBlock; + } + + StartSslOperation(); + + try + { + _stream.Write(buffer.Span); + } + catch (IOException exception) + { + writtenCount = -1; + + if (exception.InnerException is SocketException socketException) + { + WsaError socketErrorCode = (WsaError)socketException.SocketErrorCode; + + if (TryTranslateWinSockError(_isBlockingSocket, socketErrorCode, out ResultCode result)) + { + return result; + } + else + { + throw socketException; + } + } + else + { + throw; + } + } + finally + { + EndSslOperation(); + } + + // .NET API doesn't provide the size written, assume all written. + writtenCount = buffer.Length; + + return ResultCode.Success; + } + + public ResultCode GetServerCertificate(string hostname, Span certificates, out uint storageSize, out uint certificateCount) + { + byte[] rawCertData = _stream.RemoteCertificate.GetRawCertData(); + + storageSize = (uint)rawCertData.Length; + certificateCount = 1; + + if (rawCertData.Length > certificates.Length) + { + return ResultCode.CertBufferTooSmall; + } + + rawCertData.CopyTo(certificates); + + return ResultCode.Success; + } + + public void Dispose() + { + _bsdContext.CloseFileDescriptor(SocketFd); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/BuiltInCertificateInfo.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/BuiltInCertificateInfo.cs new file mode 100644 index 00000000..1d7029f7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/BuiltInCertificateInfo.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + struct BuiltInCertificateInfo + { + public CaCertificateId Id; + public TrustedCertStatus Status; + public ulong CertificateDataSize; + public ulong CertificateDataOffset; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/CaCertificateId.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/CaCertificateId.cs new file mode 100644 index 00000000..2bf06171 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/CaCertificateId.cs @@ -0,0 +1,68 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + enum CaCertificateId : uint + { + // Nintendo CAs + NintendoCAG3 = 1, + NintendoClass2CAG3, + + // External CAs + AmazonRootCA1 = 1000, + StarfieldServicesRootCertificateAuthorityG2, + AddTrustExternalCARoot, + COMODOCertificationAuthority, + UTNDATACorpSGC, + UTNUSERFirstHardware, + BaltimoreCyberTrustRoot, + CybertrustGlobalRoot, + VerizonGlobalRootCA, + DigiCertAssuredIDRootCA, + DigiCertAssuredIDRootG2, + DigiCertGlobalRootCA, + DigiCertGlobalRootG2, + DigiCertHighAssuranceEVRootCA, + EntrustnetCertificationAuthority2048, + EntrustRootCertificationAuthority, + EntrustRootCertificationAuthorityG2, + GeoTrustGlobalCA2, + GeoTrustGlobalCA, + GeoTrustPrimaryCertificationAuthorityG3, + GeoTrustPrimaryCertificationAuthority, + GlobalSignRootCA, + GlobalSignRootCAR2, + GlobalSignRootCAR3, + GoDaddyClass2CertificationAuthority, + GoDaddyRootCertificateAuthorityG2, + StarfieldClass2CertificationAuthority, + StarfieldRootCertificateAuthorityG2, + ThawtePrimaryRootCAG3, + ThawtePrimaryRootCA, + VeriSignClass3PublicPrimaryCertificationAuthorityG3, + VeriSignClass3PublicPrimaryCertificationAuthorityG5, + VeriSignUniversalRootCertificationAuthority, + DSTRootCAX3, + USERTrustRSACertificationAuthority, + ISRGRootX10, + USERTrustECCCertificationAuthority, + COMODORSACertificationAuthority, + COMODOECCCertificationAuthority, + AmazonRootCA2, + AmazonRootCA3, + AmazonRootCA4, + DigiCertAssuredIDRootG3, + DigiCertGlobalRootG3, + DigiCertTrustedRootG4, + EntrustRootCertificationAuthorityEC1, + EntrustRootCertificationAuthorityG4, + GlobalSignECCRootCAR4, + GlobalSignECCRootCAR5, + GlobalSignECCRootCAR6, + GTSRootR1, + GTSRootR2, + GTSRootR3, + GTSRootR4, + SecurityCommunicationRootCA, + + All = uint.MaxValue, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/CertificateFormat.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/CertificateFormat.cs new file mode 100644 index 00000000..0b3d4c3b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/CertificateFormat.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + enum CertificateFormat : uint + { + Pem = 1, + Der = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/IoMode.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/IoMode.cs new file mode 100644 index 00000000..b42bccaa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/IoMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + enum IoMode : uint + { + Blocking = 1, + NonBlocking = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/OptionType.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/OptionType.cs new file mode 100644 index 00000000..8d0e0b57 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/OptionType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + enum OptionType : uint + { + DoNotCloseSocket, + GetServerCertChain, // 3.0.0+ + SkipDefaultVerify, // 5.0.0+ + EnableAlpn, // 9.0.0+ + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/SessionCacheMode.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/SessionCacheMode.cs new file mode 100644 index 00000000..d2f236b0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/SessionCacheMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + enum SessionCacheMode : uint + { + None, + SessionId, + SessionTicket, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/SslVersion.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/SslVersion.cs new file mode 100644 index 00000000..2cbb0089 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/SslVersion.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + [Flags] + enum SslVersion : uint + { + Auto = 1 << 0, + TlsV10 = 1 << 3, + TlsV11 = 1 << 4, + TlsV12 = 1 << 5, + TlsV13 = 1 << 6, // 11.0.0+ + + VersionMask = 0xFFFFFF, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/TrustedCertStatus.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/TrustedCertStatus.cs new file mode 100644 index 00000000..b6f855f2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/TrustedCertStatus.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + enum TrustedCertStatus : uint + { + Removed, + EnabledTrusted, + EnabledNotTrusted, + Revoked, + + Invalid = uint.MaxValue, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/Types/VerifyOption.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/VerifyOption.cs new file mode 100644 index 00000000..215e72b0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Ssl/Types/VerifyOption.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Ssl.Types +{ + [Flags] + enum VerifyOption : uint + { + PeerCa = 1 << 0, + HostName = 1 << 1, + DateCheck = 1 << 2, + EvCertPartial = 1 << 3, + EvPolicyOid = 1 << 4, // 6.0.0+ + EvCertFingerprint = 1 << 5, // 6.0.0+ + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs new file mode 100644 index 00000000..17045e4e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs @@ -0,0 +1,95 @@ +using Ryujinx.Graphics.Gpu; +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferItemConsumer : ConsumerBase + { + private readonly GpuContext _gpuContext; + + public BufferItemConsumer(Switch device, + BufferQueueConsumer consumer, + uint consumerUsage, + int bufferCount, + bool controlledByApp, + IConsumerListener listener = null) : base(consumer, controlledByApp, listener) + { + _gpuContext = device.Gpu; + + Status status = Consumer.SetConsumerUsageBits(consumerUsage); + + if (status != Status.Success) + { + throw new InvalidOperationException(); + } + + if (bufferCount != -1) + { + status = Consumer.SetMaxAcquiredBufferCount(bufferCount); + + if (status != Status.Success) + { + throw new InvalidOperationException(); + } + } + } + + public Status AcquireBuffer(out BufferItem bufferItem, ulong expectedPresent, bool waitForFence = false) + { + lock (Lock) + { + Status status = AcquireBufferLocked(out BufferItem tmp, expectedPresent); + + if (status != Status.Success) + { + bufferItem = null; + + return status; + } + + // Make sure to clone the object to not temper the real instance. + bufferItem = (BufferItem)tmp.Clone(); + + if (waitForFence) + { + bufferItem.Fence.WaitForever(_gpuContext); + } + + bufferItem.GraphicBuffer.Set(Slots[bufferItem.Slot].GraphicBuffer); + + return Status.Success; + } + } + + public Status ReleaseBuffer(BufferItem bufferItem, ref AndroidFence fence) + { + lock (Lock) + { + Status result = AddReleaseFenceLocked(bufferItem.Slot, ref bufferItem.GraphicBuffer, ref fence); + + if (result == Status.Success) + { + result = ReleaseBufferLocked(bufferItem.Slot, ref bufferItem.GraphicBuffer); + } + + return result; + } + } + + public Status SetDefaultBufferSize(uint width, uint height) + { + lock (Lock) + { + return Consumer.SetDefaultBufferSize(width, height); + } + } + + public Status SetDefaultBufferFormat(PixelFormat defaultFormat) + { + lock (Lock) + { + return Consumer.SetDefaultBufferFormat(defaultFormat); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs new file mode 100644 index 00000000..e95dcc69 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + static class BufferQueue + { + public static BufferQueueCore CreateBufferQueue(Switch device, ulong pid, out BufferQueueProducer producer, out BufferQueueConsumer consumer) + { + BufferQueueCore core = new(device, pid); + + producer = new BufferQueueProducer(core, device.System.TickSource); + consumer = new BufferQueueConsumer(core); + + return core; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs new file mode 100644 index 00000000..74afa989 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs @@ -0,0 +1,420 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferQueueConsumer + { + public BufferQueueCore Core { get; } + + public BufferQueueConsumer(BufferQueueCore core) + { + Core = core; + } + + public Status AcquireBuffer(out BufferItem bufferItem, ulong expectedPresent) + { + lock (Core.Lock) + { + int numAcquiredBuffers = 0; + + for (int i = 0; i < Core.MaxBufferCountCached; i++) + { + if (Core.Slots[i].BufferState == BufferState.Acquired) + { + numAcquiredBuffers++; + } + } + + if (numAcquiredBuffers > Core.MaxAcquiredBufferCount) + { + bufferItem = null; + + Logger.Debug?.Print(LogClass.SurfaceFlinger, $"Max acquired buffer count reached: {numAcquiredBuffers} (max: {Core.MaxAcquiredBufferCount})"); + + return Status.InvalidOperation; + } + + if (Core.Queue.Count == 0) + { + bufferItem = null; + + return Status.NoBufferAvailaible; + } + + if (expectedPresent != 0) + { + // TODO: support this for advanced presenting. + throw new NotImplementedException(); + } + + bufferItem = Core.Queue[0]; + + if (Core.StillTracking(ref bufferItem)) + { + Core.Slots[bufferItem.Slot].AcquireCalled = true; + Core.Slots[bufferItem.Slot].NeedsCleanupOnRelease = true; + Core.Slots[bufferItem.Slot].BufferState = BufferState.Acquired; + Core.Slots[bufferItem.Slot].Fence = AndroidFence.NoFence; + + ulong targetFrameNumber = Core.Slots[bufferItem.Slot].FrameNumber; + + for (int i = 0; i < Core.BufferHistory.Length; i++) + { + if (Core.BufferHistory[i].FrameNumber == targetFrameNumber) + { + Core.BufferHistory[i].State = BufferState.Acquired; + + break; + } + } + } + + if (bufferItem.AcquireCalled) + { + bufferItem.GraphicBuffer.Reset(); + } + + Core.Queue.RemoveAt(0); + + Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(true)); + Core.SignalDequeueEvent(); + } + + return Status.Success; + } + + public Status DetachBuffer(int slot) + { + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByConsumerLocked(slot)) + { + return Status.BadValue; + } + + if (!Core.Slots[slot].RequestBufferCalled) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} was detached without requesting a buffer"); + + return Status.BadValue; + } + + Core.FreeBufferLocked(slot); + Core.SignalDequeueEvent(); + + return Status.Success; + } + } + + public Status AttachBuffer(out int slot, ref AndroidStrongPointer graphicBuffer) + { + lock (Core.Lock) + { + int numAcquiredBuffers = 0; + + int freeSlot = BufferSlotArray.InvalidBufferSlot; + + for (int i = 0; i < Core.Slots.Length; i++) + { + if (Core.Slots[i].BufferState == BufferState.Acquired) + { + numAcquiredBuffers++; + } + else if (Core.Slots[i].BufferState == BufferState.Free) + { + if (freeSlot == BufferSlotArray.InvalidBufferSlot || Core.Slots[i].FrameNumber < Core.Slots[freeSlot].FrameNumber) + { + freeSlot = i; + } + } + } + + if (numAcquiredBuffers > Core.MaxAcquiredBufferCount + 1) + { + slot = BufferSlotArray.InvalidBufferSlot; + + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Max acquired buffer count reached: {numAcquiredBuffers} (max: {Core.MaxAcquiredBufferCount})"); + + return Status.InvalidOperation; + } + + if (freeSlot == BufferSlotArray.InvalidBufferSlot) + { + slot = BufferSlotArray.InvalidBufferSlot; + + return Status.NoMemory; + } + + Core.UpdateMaxBufferCountCachedLocked(freeSlot); + + slot = freeSlot; + + Core.Slots[slot].GraphicBuffer.Set(graphicBuffer); + + Core.Slots[slot].BufferState = BufferState.Acquired; + Core.Slots[slot].AttachedByConsumer = true; + Core.Slots[slot].NeedsCleanupOnRelease = false; + Core.Slots[slot].Fence = AndroidFence.NoFence; + Core.Slots[slot].FrameNumber = 0; + Core.Slots[slot].AcquireCalled = false; + } + + return Status.Success; + } + + public Status ReleaseBuffer(int slot, ulong frameNumber, ref AndroidFence fence) + { + if (slot < 0 || slot >= Core.Slots.Length) + { + return Status.BadValue; + } + + IProducerListener listener = null; + + lock (Core.Lock) + { + if (Core.Slots[slot].FrameNumber != frameNumber) + { + return Status.StaleBufferSlot; + } + + foreach (BufferItem item in Core.Queue) + { + if (item.Slot == slot) + { + return Status.BadValue; + } + } + + if (Core.Slots[slot].BufferState == BufferState.Acquired) + { + Core.Slots[slot].BufferState = BufferState.Free; + Core.Slots[slot].Fence = fence; + + listener = Core.ProducerListener; + } + else if (Core.Slots[slot].NeedsCleanupOnRelease) + { + Core.Slots[slot].NeedsCleanupOnRelease = false; + + return Status.StaleBufferSlot; + } + else + { + return Status.BadValue; + } + + Core.Slots[slot].GraphicBuffer.Object.DecrementNvMapHandleRefCount(Core.Owner); + + Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(true)); + Core.SignalDequeueEvent(); + } + + listener?.OnBufferReleased(); + + return Status.Success; + } + + public Status Connect(IConsumerListener consumerListener, bool controlledByApp) + { + if (consumerListener == null) + { + return Status.BadValue; + } + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + Core.ConsumerListener = consumerListener; + Core.ConsumerControlledByApp = controlledByApp; + } + + return Status.Success; + } + + public Status Disconnect() + { + lock (Core.Lock) + { + if (!Core.IsConsumerConnectedLocked()) + { + return Status.BadValue; + } + + Core.IsAbandoned = true; + Core.ConsumerListener = null; + + Core.Queue.Clear(); + Core.FreeAllBuffersLocked(); + Core.SignalDequeueEvent(); + } + + return Status.Success; + } + + public Status GetReleasedBuffers(out ulong slotMask) + { + slotMask = 0; + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.BadValue; + } + + for (int slot = 0; slot < Core.Slots.Length; slot++) + { + if (!Core.Slots[slot].AcquireCalled) + { + slotMask |= 1UL << slot; + } + } + + for (int i = 0; i < Core.Queue.Count; i++) + { + if (Core.Queue[i].AcquireCalled) + { + slotMask &= ~(1UL << i); + } + } + } + + return Status.Success; + } + + public Status SetDefaultBufferSize(uint width, uint height) + { + if (width == 0 || height == 0) + { + return Status.BadValue; + } + + lock (Core.Lock) + { + Core.DefaultWidth = (int)width; + Core.DefaultHeight = (int)height; + } + + return Status.Success; + } + + public Status SetDefaultMaxBufferCount(int bufferMaxCount) + { + lock (Core.Lock) + { + return Core.SetDefaultMaxBufferCountLocked(bufferMaxCount); + } + } + + public Status DisableAsyncBuffer() + { + lock (Core.Lock) + { + if (Core.IsConsumerConnectedLocked()) + { + return Status.InvalidOperation; + } + + Core.UseAsyncBuffer = false; + } + + return Status.Success; + } + + public Status SetMaxAcquiredBufferCount(int maxAcquiredBufferCount) + { + if (maxAcquiredBufferCount < 0 || maxAcquiredBufferCount > BufferSlotArray.MaxAcquiredBuffers) + { + return Status.BadValue; + } + + lock (Core.Lock) + { + if (Core.IsProducerConnectedLocked()) + { + return Status.InvalidOperation; + } + + Core.MaxAcquiredBufferCount = maxAcquiredBufferCount; + } + + return Status.Success; + } + + public Status SetDefaultBufferFormat(PixelFormat defaultFormat) + { + lock (Core.Lock) + { + Core.DefaultBufferFormat = defaultFormat; + } + + return Status.Success; + } + + public Status SetConsumerUsageBits(uint usage) + { + lock (Core.Lock) + { + Core.ConsumerUsageBits = usage; + } + + return Status.Success; + } + + public Status SetTransformHint(NativeWindowTransform transformHint) + { + lock (Core.Lock) + { + Core.TransformHint = transformHint; + } + + return Status.Success; + } + + public Status SetPresentTime(int slot, ulong frameNumber, TimeSpanType presentationTime) + { + if (slot < 0 || slot >= Core.Slots.Length) + { + return Status.BadValue; + } + + lock (Core.Lock) + { + if (Core.Slots[slot].FrameNumber != frameNumber) + { + return Status.StaleBufferSlot; + } + + if (Core.Slots[slot].PresentationTime.NanoSeconds == 0) + { + Core.Slots[slot].PresentationTime = presentationTime; + } + + for (int i = 0; i < Core.BufferHistory.Length; i++) + { + if (Core.BufferHistory[i].FrameNumber == frameNumber) + { + Core.BufferHistory[i].PresentationTime = presentationTime; + + break; + } + } + } + + return Status.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs new file mode 100644 index 00000000..5dc02a07 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs @@ -0,0 +1,341 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferQueueCore + { + public BufferSlotArray Slots; + public int OverrideMaxBufferCount; + public bool UseAsyncBuffer; + public bool DequeueBufferCannotBlock; + public PixelFormat DefaultBufferFormat; + public int DefaultWidth; + public int DefaultHeight; + public int DefaultMaxBufferCount; + public int MaxAcquiredBufferCount; + public bool BufferHasBeenQueued; + public ulong FrameCounter; + public NativeWindowTransform TransformHint; + public bool IsAbandoned; + public NativeWindowApi ConnectedApi; + public bool IsAllocating; + public IProducerListener ProducerListener; + public IConsumerListener ConsumerListener; + public bool ConsumerControlledByApp; + public uint ConsumerUsageBits; + public List Queue; + public BufferInfo[] BufferHistory; + public uint BufferHistoryPosition; + public bool EnableExternalEvent; + public int MaxBufferCountCached; + + public readonly object Lock = new(); + + private readonly KEvent _waitBufferFreeEvent; + private readonly KEvent _frameAvailableEvent; + + public ulong Owner { get; } + + public bool Active { get; private set; } + + public const int BufferHistoryArraySize = 8; + + public event Action BufferQueued; + + public BufferQueueCore(Switch device, ulong pid) + { + Slots = new BufferSlotArray(); + IsAbandoned = false; + OverrideMaxBufferCount = 0; + DequeueBufferCannotBlock = false; + UseAsyncBuffer = false; + DefaultWidth = 1; + DefaultHeight = 1; + DefaultMaxBufferCount = 2; + MaxAcquiredBufferCount = 1; + FrameCounter = 0; + TransformHint = 0; + DefaultBufferFormat = PixelFormat.Rgba8888; + IsAllocating = false; + ProducerListener = null; + ConsumerListener = null; + ConsumerUsageBits = 0; + + Queue = new List(); + + // TODO: CreateGraphicBufferAlloc? + + _waitBufferFreeEvent = new KEvent(device.System.KernelContext); + _frameAvailableEvent = new KEvent(device.System.KernelContext); + + Owner = pid; + + Active = true; + + BufferHistory = new BufferInfo[BufferHistoryArraySize]; + EnableExternalEvent = true; + MaxBufferCountCached = 0; + } + + public int GetMinUndequeuedBufferCountLocked(bool async) + { + if (!UseAsyncBuffer) + { + return 0; + } + + if (DequeueBufferCannotBlock || async) + { + return MaxAcquiredBufferCount + 1; + } + + return MaxAcquiredBufferCount; + } + + public int GetMinMaxBufferCountLocked(bool async) + { + return GetMinUndequeuedBufferCountLocked(async); + } + + public void UpdateMaxBufferCountCachedLocked(int slot) + { + if (MaxBufferCountCached <= slot) + { + MaxBufferCountCached = slot + 1; + } + } + + public int GetMaxBufferCountLocked(bool async) + { + int minMaxBufferCount = GetMinMaxBufferCountLocked(async); + + int maxBufferCount = Math.Max(DefaultMaxBufferCount, minMaxBufferCount); + + if (OverrideMaxBufferCount != 0) + { + return OverrideMaxBufferCount; + } + + // Preserve all buffers already in control of the producer and the consumer. + for (int slot = maxBufferCount; slot < Slots.Length; slot++) + { + BufferState state = Slots[slot].BufferState; + + if (state == BufferState.Queued || state == BufferState.Dequeued) + { + maxBufferCount = slot + 1; + } + } + + return maxBufferCount; + } + + public Status SetDefaultMaxBufferCountLocked(int count) + { + int minBufferCount = UseAsyncBuffer ? 2 : 1; + + if (count < minBufferCount || count > Slots.Length) + { + return Status.BadValue; + } + + DefaultMaxBufferCount = count; + + SignalDequeueEvent(); + + return Status.Success; + } + + public void SignalWaitBufferFreeEvent() + { + if (EnableExternalEvent) + { + _waitBufferFreeEvent.WritableEvent.Signal(); + } + } + + public void SignalFrameAvailableEvent() + { + if (EnableExternalEvent) + { + _frameAvailableEvent.WritableEvent.Signal(); + } + } + + public void PrepareForExit() + { + lock (Lock) + { + Active = false; + + Monitor.PulseAll(Lock); + } + } + + // TODO: Find an accurate way to handle a regular condvar here as this will wake up unwanted threads in some edge cases. + public void SignalDequeueEvent() + { + Monitor.PulseAll(Lock); + } + + public void WaitDequeueEvent() + { + WaitForLock(); + } + + public void SignalIsAllocatingEvent() + { + Monitor.PulseAll(Lock); + } + + public void WaitIsAllocatingEvent() + { + WaitForLock(); + } + + public void SignalQueueEvent() + { + BufferQueued?.Invoke(); + } + + private void WaitForLock() + { + if (Active) + { + Monitor.Wait(Lock); + } + } + + public void FreeBufferLocked(int slot) + { + Slots[slot].GraphicBuffer.Reset(); + + if (Slots[slot].BufferState == BufferState.Acquired) + { + Slots[slot].NeedsCleanupOnRelease = true; + } + + Slots[slot].BufferState = BufferState.Free; + Slots[slot].FrameNumber = uint.MaxValue; + Slots[slot].AcquireCalled = false; + Slots[slot].Fence.FenceCount = 0; + } + + public void FreeAllBuffersLocked() + { + BufferHasBeenQueued = false; + + for (int slot = 0; slot < Slots.Length; slot++) + { + FreeBufferLocked(slot); + } + } + + public bool StillTracking(ref BufferItem item) + { + BufferSlot slot = Slots[item.Slot]; + + // TODO: Check this. On Android, this checks the "handle". I assume NvMapHandle is the handle, but it might not be. + return !slot.GraphicBuffer.IsNull && slot.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle == item.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle; + } + + public void WaitWhileAllocatingLocked() + { + while (IsAllocating) + { + WaitIsAllocatingEvent(); + } + } + + public void CheckSystemEventsLocked(int maxBufferCount) + { + if (!EnableExternalEvent) + { + return; + } + + bool needBufferReleaseSignal = false; + bool needFrameAvailableSignal = false; + + if (maxBufferCount > 1) + { + for (int i = 0; i < maxBufferCount; i++) + { + if (Slots[i].BufferState == BufferState.Queued) + { + needFrameAvailableSignal = true; + } + else if (Slots[i].BufferState == BufferState.Free) + { + needBufferReleaseSignal = true; + } + } + } + + if (needBufferReleaseSignal) + { + SignalWaitBufferFreeEvent(); + } + else + { + _waitBufferFreeEvent.WritableEvent.Clear(); + } + + if (needFrameAvailableSignal) + { + SignalFrameAvailableEvent(); + } + else + { + _frameAvailableEvent.WritableEvent.Clear(); + } + } + + public bool IsProducerConnectedLocked() + { + return ConnectedApi != NativeWindowApi.NoApi; + } + + public bool IsConsumerConnectedLocked() + { + return ConsumerListener != null; + } + + public KReadableEvent GetWaitBufferFreeEvent() + { + lock (Lock) + { + return _waitBufferFreeEvent.ReadableEvent; + } + } + + public bool IsOwnedByConsumerLocked(int slot) + { + if (Slots[slot].BufferState != BufferState.Acquired) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} is not owned by the consumer (state = {Slots[slot].BufferState})"); + + return false; + } + + return true; + } + + public bool IsOwnedByProducerLocked(int slot) + { + if (Slots[slot].BufferState != BufferState.Dequeued) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} is not owned by the producer (state = {Slots[slot].BufferState})"); + + return false; + } + + return true; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs new file mode 100644 index 00000000..ae61d0bf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs @@ -0,0 +1,879 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using System; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferQueueProducer : IGraphicBufferProducer + { + public BufferQueueCore Core { get; } + + private readonly ITickSource _tickSource; + +#pragma warning disable IDE0052 // Remove unread private member + private uint _stickyTransform; +#pragma warning restore IDE0052 + + private uint _nextCallbackTicket; + private uint _currentCallbackTicket; + private uint _callbackTicket; + + private readonly object _callbackLock = new(); + + public BufferQueueProducer(BufferQueueCore core, ITickSource tickSource) + { + Core = core; + _tickSource = tickSource; + + _stickyTransform = 0; + _callbackTicket = 0; + _nextCallbackTicket = 0; + _currentCallbackTicket = 0; + } + + public override Status RequestBuffer(int slot, out AndroidStrongPointer graphicBuffer) + { + graphicBuffer = new AndroidStrongPointer(); + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot)) + { + return Status.BadValue; + } + + graphicBuffer.Set(Core.Slots[slot].GraphicBuffer); + + Core.Slots[slot].RequestBufferCalled = true; + + return Status.Success; + } + } + + public override Status SetBufferCount(int bufferCount) + { + IConsumerListener listener = null; + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + if (bufferCount > BufferSlotArray.NumBufferSlots) + { + return Status.BadValue; + } + + for (int slot = 0; slot < Core.Slots.Length; slot++) + { + if (Core.Slots[slot].BufferState == BufferState.Dequeued) + { + return Status.BadValue; + } + } + + if (bufferCount == 0) + { + Core.OverrideMaxBufferCount = 0; + Core.SignalDequeueEvent(); + + return Status.Success; + } + + int minBufferSlots = Core.GetMinMaxBufferCountLocked(false); + + if (bufferCount < minBufferSlots) + { + return Status.BadValue; + } + + int preallocatedBufferCount = GetPreallocatedBufferCountLocked(); + + if (preallocatedBufferCount <= 0) + { + Core.Queue.Clear(); + Core.FreeAllBuffersLocked(); + } + else if (preallocatedBufferCount < bufferCount) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, "Not enough buffers. Try with more pre-allocated buffers"); + + return Status.Success; + } + + Core.OverrideMaxBufferCount = bufferCount; + + Core.SignalDequeueEvent(); + Core.SignalWaitBufferFreeEvent(); + + listener = Core.ConsumerListener; + } + + listener?.OnBuffersReleased(); + + return Status.Success; + } + + public override Status DequeueBuffer(out int slot, + out AndroidFence fence, + bool async, + uint width, + uint height, + PixelFormat format, + uint usage) + { + if ((width == 0 && height != 0) || (height == 0 && width != 0)) + { + slot = BufferSlotArray.InvalidBufferSlot; + fence = AndroidFence.NoFence; + + return Status.BadValue; + } + + Status returnFlags = Status.Success; + + bool attachedByConsumer = false; + + lock (Core.Lock) + { + if (format == PixelFormat.Unknown) + { + format = Core.DefaultBufferFormat; + } + + usage |= Core.ConsumerUsageBits; + + Status status = WaitForFreeSlotThenRelock(async, out slot, out returnFlags); + + if (status != Status.Success) + { + slot = BufferSlotArray.InvalidBufferSlot; + fence = AndroidFence.NoFence; + + return status; + } + + if (slot == BufferSlotArray.InvalidBufferSlot) + { + fence = AndroidFence.NoFence; + + Logger.Error?.Print(LogClass.SurfaceFlinger, "No available buffer slots"); + + return Status.Busy; + } + + attachedByConsumer = Core.Slots[slot].AttachedByConsumer; + + if (width == 0 || height == 0) + { + width = (uint)Core.DefaultWidth; + height = (uint)Core.DefaultHeight; + } + + GraphicBuffer graphicBuffer = Core.Slots[slot].GraphicBuffer.Object; + + if (Core.Slots[slot].GraphicBuffer.IsNull + || graphicBuffer.Width != width + || graphicBuffer.Height != height + || graphicBuffer.Format != format + || (graphicBuffer.Usage & usage) != usage) + { + if (!Core.Slots[slot].IsPreallocated) + { + slot = BufferSlotArray.InvalidBufferSlot; + fence = AndroidFence.NoFence; + + return Status.NoMemory; + } + else + { + Logger.Error?.Print(LogClass.SurfaceFlinger, + $"Preallocated buffer mismatch - slot {slot}\n" + + $"available: Width = {graphicBuffer.Width} Height = {graphicBuffer.Height} Format = {graphicBuffer.Format} Usage = {graphicBuffer.Usage:x} " + + $"requested: Width = {width} Height = {height} Format = {format} Usage = {usage:x}"); + + slot = BufferSlotArray.InvalidBufferSlot; + fence = AndroidFence.NoFence; + + return Status.NoInit; + } + } + + Core.Slots[slot].BufferState = BufferState.Dequeued; + + Core.UpdateMaxBufferCountCachedLocked(slot); + + fence = Core.Slots[slot].Fence; + + Core.Slots[slot].Fence = AndroidFence.NoFence; + Core.Slots[slot].QueueTime = TimeSpanType.Zero; + Core.Slots[slot].PresentationTime = TimeSpanType.Zero; + + Core.CheckSystemEventsLocked(Core.GetMaxBufferCountLocked(async)); + } + + if (attachedByConsumer) + { + returnFlags |= Status.BufferNeedsReallocation; + } + + return returnFlags; + } + + public override Status DetachBuffer(int slot) + { + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot)) + { + return Status.BadValue; + } + + if (!Core.Slots[slot].RequestBufferCalled) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} was detached without requesting a buffer"); + + return Status.BadValue; + } + + Core.FreeBufferLocked(slot); + Core.SignalDequeueEvent(); + + return Status.Success; + } + } + + public override Status DetachNextBuffer(out AndroidStrongPointer graphicBuffer, out AndroidFence fence) + { + lock (Core.Lock) + { + Core.WaitWhileAllocatingLocked(); + + if (Core.IsAbandoned) + { + graphicBuffer = default; + fence = AndroidFence.NoFence; + + return Status.NoInit; + } + + int nextBufferSlot = BufferSlotArray.InvalidBufferSlot; + + for (int slot = 0; slot < Core.Slots.Length; slot++) + { + if (Core.Slots[slot].BufferState == BufferState.Free && !Core.Slots[slot].GraphicBuffer.IsNull) + { + if (nextBufferSlot == BufferSlotArray.InvalidBufferSlot || Core.Slots[slot].FrameNumber < Core.Slots[nextBufferSlot].FrameNumber) + { + nextBufferSlot = slot; + } + } + } + + if (nextBufferSlot == BufferSlotArray.InvalidBufferSlot) + { + graphicBuffer = default; + fence = AndroidFence.NoFence; + + return Status.NoMemory; + } + + graphicBuffer = Core.Slots[nextBufferSlot].GraphicBuffer; + fence = Core.Slots[nextBufferSlot].Fence; + + Core.FreeBufferLocked(nextBufferSlot); + + return Status.Success; + } + } + + public override Status AttachBuffer(out int slot, AndroidStrongPointer graphicBuffer) + { + lock (Core.Lock) + { + Core.WaitWhileAllocatingLocked(); + + Status status = WaitForFreeSlotThenRelock(false, out slot, out Status returnFlags); + + if (status != Status.Success) + { + return status; + } + + if (slot == BufferSlotArray.InvalidBufferSlot) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, "No available buffer slots"); + + return Status.Busy; + } + + Core.UpdateMaxBufferCountCachedLocked(slot); + + Core.Slots[slot].GraphicBuffer.Set(graphicBuffer); + + Core.Slots[slot].BufferState = BufferState.Dequeued; + Core.Slots[slot].Fence = AndroidFence.NoFence; + Core.Slots[slot].RequestBufferCalled = true; + + return returnFlags; + } + } + + public override Status QueueBuffer(int slot, ref QueueBufferInput input, out QueueBufferOutput output) + { + output = default; + + switch (input.ScalingMode) + { + case NativeWindowScalingMode.Freeze: + case NativeWindowScalingMode.ScaleToWindow: + case NativeWindowScalingMode.ScaleCrop: + case NativeWindowScalingMode.Unknown: + case NativeWindowScalingMode.NoScaleCrop: + break; + default: + return Status.BadValue; + } + + BufferItem item = new(); + + IConsumerListener frameAvailableListener = null; + IConsumerListener frameReplaceListener = null; + + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + return Status.NoInit; + } + + int maxBufferCount = Core.GetMaxBufferCountLocked(input.Async != 0); + + if (input.Async != 0 && Core.OverrideMaxBufferCount != 0 && Core.OverrideMaxBufferCount < maxBufferCount) + { + return Status.BadValue; + } + + if (slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot)) + { + return Status.BadValue; + } + + if (!Core.Slots[slot].RequestBufferCalled) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Slot {slot} was queued without requesting a buffer"); + + return Status.BadValue; + } + + input.Crop.Intersect(Core.Slots[slot].GraphicBuffer.Object.ToRect(), out Rect croppedRect); + + if (croppedRect != input.Crop) + { + return Status.BadValue; + } + + Core.Slots[slot].Fence = input.Fence; + Core.Slots[slot].BufferState = BufferState.Queued; + Core.FrameCounter++; + Core.Slots[slot].FrameNumber = Core.FrameCounter; + Core.Slots[slot].QueueTime = TimeSpanType.FromTimeSpan(_tickSource.ElapsedTime); + Core.Slots[slot].PresentationTime = TimeSpanType.Zero; + + item.AcquireCalled = Core.Slots[slot].AcquireCalled; + item.Crop = input.Crop; + item.Transform = input.Transform; + item.TransformToDisplayInverse = (input.Transform & NativeWindowTransform.InverseDisplay) == NativeWindowTransform.InverseDisplay; + item.ScalingMode = input.ScalingMode; + item.Timestamp = input.Timestamp; + item.IsAutoTimestamp = input.IsAutoTimestamp != 0; + item.SwapInterval = input.SwapInterval; + item.FrameNumber = Core.FrameCounter; + item.Slot = slot; + item.Fence = input.Fence; + item.IsDroppable = Core.DequeueBufferCannotBlock || input.Async != 0; + + item.GraphicBuffer.Set(Core.Slots[slot].GraphicBuffer); + item.GraphicBuffer.Object.IncrementNvMapHandleRefCount(Core.Owner); + + Core.BufferHistoryPosition = (Core.BufferHistoryPosition + 1) % BufferQueueCore.BufferHistoryArraySize; + + Core.BufferHistory[Core.BufferHistoryPosition] = new BufferInfo + { + FrameNumber = Core.FrameCounter, + QueueTime = Core.Slots[slot].QueueTime, + State = BufferState.Queued, + }; + + _stickyTransform = input.StickyTransform; + + if (Core.Queue.Count == 0) + { + Core.Queue.Add(item); + + frameAvailableListener = Core.ConsumerListener; + } + else + { + BufferItem frontItem = Core.Queue[0]; + + if (frontItem.IsDroppable) + { + if (Core.StillTracking(ref frontItem)) + { + Core.Slots[slot].BufferState = BufferState.Free; + Core.Slots[slot].FrameNumber = 0; + } + + Core.Queue.RemoveAt(0); + Core.Queue.Insert(0, item); + + frameReplaceListener = Core.ConsumerListener; + } + else + { + Core.Queue.Add(item); + + frameAvailableListener = Core.ConsumerListener; + } + } + + Core.BufferHasBeenQueued = true; + Core.SignalDequeueEvent(); + + Core.CheckSystemEventsLocked(maxBufferCount); + + output = new QueueBufferOutput + { + Width = (uint)Core.DefaultWidth, + Height = (uint)Core.DefaultHeight, + TransformHint = Core.TransformHint, + NumPendingBuffers = (uint)Core.Queue.Count, + }; + + if ((input.StickyTransform & 8) != 0) + { + output.TransformHint |= NativeWindowTransform.ReturnFrameNumber; + output.FrameNumber = Core.Slots[slot].FrameNumber; + } + + _callbackTicket = _nextCallbackTicket++; + } + + lock (_callbackLock) + { + while (_callbackTicket != _currentCallbackTicket) + { + Monitor.Wait(_callbackLock); + } + + frameAvailableListener?.OnFrameAvailable(ref item); + frameReplaceListener?.OnFrameReplaced(ref item); + + _currentCallbackTicket++; + + Monitor.PulseAll(_callbackLock); + } + + Core.SignalQueueEvent(); + + return Status.Success; + } + + public override void CancelBuffer(int slot, ref AndroidFence fence) + { + lock (Core.Lock) + { + if (Core.IsAbandoned || slot < 0 || slot >= Core.Slots.Length || !Core.IsOwnedByProducerLocked(slot)) + { + return; + } + + Core.Slots[slot].BufferState = BufferState.Free; + Core.Slots[slot].FrameNumber = 0; + Core.Slots[slot].Fence = fence; + Core.SignalDequeueEvent(); + Core.SignalWaitBufferFreeEvent(); + } + } + + public override Status Query(NativeWindowAttribute what, out int outValue) + { + lock (Core.Lock) + { + if (Core.IsAbandoned) + { + outValue = 0; + return Status.NoInit; + } + + switch (what) + { + case NativeWindowAttribute.Width: + outValue = Core.DefaultWidth; + return Status.Success; + case NativeWindowAttribute.Height: + outValue = Core.DefaultHeight; + return Status.Success; + case NativeWindowAttribute.Format: + outValue = (int)Core.DefaultBufferFormat; + return Status.Success; + case NativeWindowAttribute.MinUnqueuedBuffers: + outValue = Core.GetMinUndequeuedBufferCountLocked(false); + return Status.Success; + case NativeWindowAttribute.ConsumerRunningBehind: + outValue = Core.Queue.Count > 1 ? 1 : 0; + return Status.Success; + case NativeWindowAttribute.ConsumerUsageBits: + outValue = (int)Core.ConsumerUsageBits; + return Status.Success; + case NativeWindowAttribute.MaxBufferCountAsync: + outValue = Core.GetMaxBufferCountLocked(true); + return Status.Success; + default: + outValue = 0; + return Status.BadValue; + } + } + } + + public override Status Connect(IProducerListener listener, NativeWindowApi api, bool producerControlledByApp, out QueueBufferOutput output) + { + output = new QueueBufferOutput(); + + lock (Core.Lock) + { + if (Core.IsAbandoned || Core.ConsumerListener == null) + { + return Status.NoInit; + } + + if (Core.ConnectedApi != NativeWindowApi.NoApi) + { + return Status.BadValue; + } + + Core.BufferHasBeenQueued = false; + Core.DequeueBufferCannotBlock = Core.ConsumerControlledByApp && producerControlledByApp; + + switch (api) + { + case NativeWindowApi.NVN: + case NativeWindowApi.CPU: + case NativeWindowApi.Media: + case NativeWindowApi.Camera: + Core.ProducerListener = listener; + Core.ConnectedApi = api; + + output.Width = (uint)Core.DefaultWidth; + output.Height = (uint)Core.DefaultHeight; + output.TransformHint = Core.TransformHint; + output.NumPendingBuffers = (uint)Core.Queue.Count; + + if (NxSettings.Settings.TryGetValue("nv!nvn_no_vsync_capability", out object noVSyncCapability) && (bool)noVSyncCapability) + { + output.TransformHint |= NativeWindowTransform.NoVSyncCapability; + } + + return Status.Success; + default: + return Status.BadValue; + } + } + } + + public override Status Disconnect(NativeWindowApi api) + { + IProducerListener producerListener = null; + + Status status = Status.BadValue; + + lock (Core.Lock) + { + Core.WaitWhileAllocatingLocked(); + + if (Core.IsAbandoned) + { + return Status.Success; + } + + switch (api) + { + case NativeWindowApi.NVN: + case NativeWindowApi.CPU: + case NativeWindowApi.Media: + case NativeWindowApi.Camera: + if (Core.ConnectedApi == api) + { + Core.Queue.Clear(); + Core.FreeAllBuffersLocked(); + Core.SignalDequeueEvent(); + + producerListener = Core.ProducerListener; + + Core.ProducerListener = null; + Core.ConnectedApi = NativeWindowApi.NoApi; + + Core.SignalWaitBufferFreeEvent(); + Core.SignalFrameAvailableEvent(); + + status = Status.Success; + } + break; + } + } + + producerListener?.OnBufferReleased(); + + return status; + } + + private int GetPreallocatedBufferCountLocked() + { + int bufferCount = 0; + + for (int i = 0; i < Core.Slots.Length; i++) + { + if (Core.Slots[i].IsPreallocated) + { + bufferCount++; + } + } + + return bufferCount; + } + + public override Status SetPreallocatedBuffer(int slot, AndroidStrongPointer graphicBuffer) + { + if (slot < 0 || slot >= Core.Slots.Length) + { + return Status.BadValue; + } + + lock (Core.Lock) + { + // If we are replacing a buffer that has already been queued, make sure we release the references. + if (Core.Slots[slot].BufferState == BufferState.Queued) + { + Core.Slots[slot].GraphicBuffer.Object.DecrementNvMapHandleRefCount(Core.Owner); + } + + Core.Slots[slot].BufferState = BufferState.Free; + Core.Slots[slot].Fence = AndroidFence.NoFence; + Core.Slots[slot].RequestBufferCalled = false; + Core.Slots[slot].AcquireCalled = false; + Core.Slots[slot].NeedsCleanupOnRelease = false; + Core.Slots[slot].IsPreallocated = !graphicBuffer.IsNull; + Core.Slots[slot].FrameNumber = 0; + + Core.Slots[slot].GraphicBuffer.Set(graphicBuffer); + + if (!Core.Slots[slot].GraphicBuffer.IsNull) + { + Core.Slots[slot].GraphicBuffer.Object.Buffer.Usage &= (int)Core.ConsumerUsageBits; + } + + Core.OverrideMaxBufferCount = GetPreallocatedBufferCountLocked(); + Core.UseAsyncBuffer = false; + + if (!graphicBuffer.IsNull) + { + // NOTE: Nintendo set the default width, height and format from the GraphicBuffer.. + // This is entirely wrong and should only be controlled by the consumer... + Core.DefaultWidth = graphicBuffer.Object.Width; + Core.DefaultHeight = graphicBuffer.Object.Height; + Core.DefaultBufferFormat = graphicBuffer.Object.Format; + } + else + { + bool allBufferFreed = true; + + for (int i = 0; i < Core.Slots.Length; i++) + { + if (!Core.Slots[i].GraphicBuffer.IsNull) + { + allBufferFreed = false; + break; + } + } + + if (allBufferFreed) + { + Core.Queue.Clear(); + Core.FreeAllBuffersLocked(); + Core.SignalDequeueEvent(); + Core.SignalWaitBufferFreeEvent(); + Core.SignalFrameAvailableEvent(); + + return Status.Success; + } + } + + Core.SignalDequeueEvent(); + Core.SignalWaitBufferFreeEvent(); + + return Status.Success; + } + } + + private Status WaitForFreeSlotThenRelock(bool async, out int freeSlot, out Status returnStatus) + { + bool tryAgain = true; + + freeSlot = BufferSlotArray.InvalidBufferSlot; + returnStatus = Status.Success; + + while (tryAgain) + { + if (Core.IsAbandoned) + { + freeSlot = BufferSlotArray.InvalidBufferSlot; + + return Status.NoInit; + } + + int maxBufferCount = Core.GetMaxBufferCountLocked(async); + + if (async && Core.OverrideMaxBufferCount != 0 && Core.OverrideMaxBufferCount < maxBufferCount) + { + freeSlot = BufferSlotArray.InvalidBufferSlot; + + return Status.BadValue; + } + + + if (maxBufferCount < Core.MaxBufferCountCached) + { + for (int slot = maxBufferCount; slot < Core.MaxBufferCountCached; slot++) + { + if (Core.Slots[slot].BufferState == BufferState.Free && !Core.Slots[slot].GraphicBuffer.IsNull && !Core.Slots[slot].IsPreallocated) + { + Core.FreeBufferLocked(slot); + returnStatus |= Status.ReleaseAllBuffers; + } + } + } + + freeSlot = BufferSlotArray.InvalidBufferSlot; + + int dequeuedCount = 0; + int acquiredCount = 0; + + for (int slot = 0; slot < maxBufferCount; slot++) + { + switch (Core.Slots[slot].BufferState) + { + case BufferState.Acquired: + acquiredCount++; + break; + case BufferState.Dequeued: + dequeuedCount++; + break; + case BufferState.Free: + if (freeSlot == BufferSlotArray.InvalidBufferSlot || Core.Slots[slot].FrameNumber < Core.Slots[freeSlot].FrameNumber) + { + freeSlot = slot; + } + break; + default: + break; + } + } + + // The producer SHOULD call SetBufferCount otherwise it's not allowed to dequeue multiple buffers. + if (Core.OverrideMaxBufferCount == 0 && dequeuedCount > 0) + { + return Status.InvalidOperation; + } + + if (Core.BufferHasBeenQueued) + { + int newUndequeuedCount = maxBufferCount - (dequeuedCount + 1); + int minUndequeuedCount = Core.GetMinUndequeuedBufferCountLocked(async); + + if (newUndequeuedCount < minUndequeuedCount) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Min undequeued buffer count ({minUndequeuedCount}) exceeded (dequeued = {dequeuedCount} undequeued = {newUndequeuedCount})"); + + return Status.InvalidOperation; + } + } + + bool tooManyBuffers = Core.Queue.Count > maxBufferCount; + + tryAgain = freeSlot == BufferSlotArray.InvalidBufferSlot || tooManyBuffers; + + if (tryAgain) + { + if (async || (Core.DequeueBufferCannotBlock && acquiredCount < Core.MaxAcquiredBufferCount)) + { + Core.CheckSystemEventsLocked(maxBufferCount); + + return Status.WouldBlock; + } + + Core.WaitDequeueEvent(); + + if (!Core.Active) + { + break; + } + } + } + + return Status.Success; + } + + protected override KReadableEvent GetWaitBufferFreeEvent() + { + return Core.GetWaitBufferFreeEvent(); + } + + public override Status GetBufferHistory(int bufferHistoryCount, out Span bufferInfos) + { + if (bufferHistoryCount <= 0) + { + bufferInfos = Span.Empty; + + return Status.BadValue; + } + + lock (Core.Lock) + { + bufferHistoryCount = Math.Min(bufferHistoryCount, Core.BufferHistory.Length); + + BufferInfo[] result = new BufferInfo[bufferHistoryCount]; + + uint position = Core.BufferHistoryPosition; + + for (uint i = 0; i < bufferHistoryCount; i++) + { + result[i] = Core.BufferHistory[(position - i) % Core.BufferHistory.Length]; + + position--; + } + + bufferInfos = result; + + return Status.Success; + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs new file mode 100644 index 00000000..07d6ab7c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs @@ -0,0 +1,29 @@ +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using Ryujinx.HLE.HOS.Services.Time.Clock; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferSlot + { + public AndroidStrongPointer GraphicBuffer; + public BufferState BufferState; + public bool RequestBufferCalled; + public ulong FrameNumber; + public AndroidFence Fence; + public bool AcquireCalled; + public bool NeedsCleanupOnRelease; + public bool AttachedByConsumer; + public TimeSpanType QueueTime; + public TimeSpanType PresentationTime; + public bool IsPreallocated; + + public BufferSlot() + { + GraphicBuffer = new AndroidStrongPointer(); + BufferState = BufferState.Free; + QueueTime = TimeSpanType.Zero; + PresentationTime = TimeSpanType.Zero; + IsPreallocated = false; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs new file mode 100644 index 00000000..19c1bb4e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferSlotArray + { + // TODO: move to BufferQueue + public const int NumBufferSlots = 0x40; + public const int MaxAcquiredBuffers = NumBufferSlots - 2; + public const int InvalidBufferSlot = -1; + + private readonly BufferSlot[] _raw = new BufferSlot[NumBufferSlots]; + + public BufferSlotArray() + { + for (int i = 0; i < _raw.Length; i++) + { + _raw[i] = new BufferSlot(); + } + } + + public BufferSlot this[int index] + { + get => _raw[index]; + set => _raw[index] = value; + } + + public int Length => NumBufferSlots; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs new file mode 100644 index 00000000..f8ee8484 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs @@ -0,0 +1,175 @@ +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class ConsumerBase : IConsumerListener + { + public class Slot + { + public AndroidStrongPointer GraphicBuffer; + public AndroidFence Fence; + public ulong FrameNumber; + + public Slot() + { + GraphicBuffer = new AndroidStrongPointer(); + } + } + + protected Slot[] Slots = new Slot[BufferSlotArray.NumBufferSlots]; + + protected bool IsAbandoned; + + protected BufferQueueConsumer Consumer; + + protected readonly object Lock = new(); + + private readonly IConsumerListener _listener; + + public ConsumerBase(BufferQueueConsumer consumer, bool controlledByApp, IConsumerListener listener) + { + for (int i = 0; i < Slots.Length; i++) + { + Slots[i] = new Slot(); + } + + IsAbandoned = false; + Consumer = consumer; + _listener = listener; + + Status connectStatus = consumer.Connect(this, controlledByApp); + + if (connectStatus != Status.Success) + { + throw new InvalidOperationException(); + } + } + + public virtual void OnBuffersReleased() + { + lock (Lock) + { + if (IsAbandoned) + { + return; + } + + Consumer.GetReleasedBuffers(out ulong slotMask); + + for (int i = 0; i < Slots.Length; i++) + { + if ((slotMask & (1UL << i)) != 0) + { + FreeBufferLocked(i); + } + } + } + } + + public virtual void OnFrameAvailable(ref BufferItem item) + { + _listener?.OnFrameAvailable(ref item); + } + + public virtual void OnFrameReplaced(ref BufferItem item) + { + _listener?.OnFrameReplaced(ref item); + } + + protected virtual void FreeBufferLocked(int slotIndex) + { + Slots[slotIndex].GraphicBuffer.Reset(); + + Slots[slotIndex].Fence = AndroidFence.NoFence; + Slots[slotIndex].FrameNumber = 0; + } + + public void Abandon() + { + lock (Lock) + { + if (!IsAbandoned) + { + AbandonLocked(); + + IsAbandoned = true; + } + } + } + + protected virtual void AbandonLocked() + { + for (int i = 0; i < Slots.Length; i++) + { + FreeBufferLocked(i); + } + + Consumer.Disconnect(); + } + + protected virtual Status AcquireBufferLocked(out BufferItem bufferItem, ulong expectedPresent) + { + Status acquireStatus = Consumer.AcquireBuffer(out bufferItem, expectedPresent); + + if (acquireStatus != Status.Success) + { + return acquireStatus; + } + + if (!bufferItem.GraphicBuffer.IsNull) + { + Slots[bufferItem.Slot].GraphicBuffer.Set(bufferItem.GraphicBuffer.Object); + } + + Slots[bufferItem.Slot].FrameNumber = bufferItem.FrameNumber; + Slots[bufferItem.Slot].Fence = bufferItem.Fence; + + return Status.Success; + } + + protected virtual Status AddReleaseFenceLocked(int slot, ref AndroidStrongPointer graphicBuffer, ref AndroidFence fence) + { + if (!StillTracking(slot, ref graphicBuffer)) + { + return Status.Success; + } + + Slots[slot].Fence = fence; + + return Status.Success; + } + + protected virtual Status ReleaseBufferLocked(int slot, ref AndroidStrongPointer graphicBuffer) + { + if (!StillTracking(slot, ref graphicBuffer)) + { + return Status.Success; + } + + Status result = Consumer.ReleaseBuffer(slot, Slots[slot].FrameNumber, ref Slots[slot].Fence); + + if (result == Status.StaleBufferSlot) + { + FreeBufferLocked(slot); + } + + Slots[slot].Fence = AndroidFence.NoFence; + + return result; + } + + protected virtual bool StillTracking(int slotIndex, ref AndroidStrongPointer graphicBuffer) + { + if (slotIndex < 0 || slotIndex >= Slots.Length) + { + return false; + } + + Slot slot = Slots[slotIndex]; + + // TODO: Check this. On Android, this checks the "handle". I assume NvMapHandle is the handle, but it might not be. + return !slot.GraphicBuffer.IsNull && slot.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle == graphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs new file mode 100644 index 00000000..bc7bffcb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs @@ -0,0 +1,109 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class HOSBinderDriverServer : IHOSBinderDriver + { + private static readonly Dictionary _registeredBinderObjects = new(); + + private static int _lastBinderId = 0; + + private static readonly object _lock = new(); + + public static int RegisterBinderObject(IBinder binder) + { + lock (_lock) + { + _lastBinderId++; + + _registeredBinderObjects.Add(_lastBinderId, binder); + + return _lastBinderId; + } + } + + public static void UnregisterBinderObject(int binderId) + { + lock (_lock) + { + _registeredBinderObjects.Remove(binderId); + } + } + + public static int GetBinderId(IBinder binder) + { + lock (_lock) + { + foreach (KeyValuePair pair in _registeredBinderObjects) + { + if (ReferenceEquals(binder, pair.Value)) + { + return pair.Key; + } + } + + return -1; + } + } + + private static IBinder GetBinderObjectById(int binderId) + { + lock (_lock) + { + if (_registeredBinderObjects.TryGetValue(binderId, out IBinder binder)) + { + return binder; + } + + return null; + } + } + + protected override ResultCode AdjustRefcount(int binderId, int addVal, int type) + { + IBinder binder = GetBinderObjectById(binderId); + + if (binder == null) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}"); + + return ResultCode.Success; + } + + return binder.AdjustRefcount(addVal, type); + } + + protected override void GetNativeHandle(int binderId, uint typeId, out KReadableEvent readableEvent) + { + IBinder binder = GetBinderObjectById(binderId); + + if (binder == null) + { + readableEvent = null; + + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}"); + + return; + } + + binder.GetNativeHandle(typeId, out readableEvent); + } + + protected override ResultCode OnTransact(int binderId, uint code, uint flags, ReadOnlySpan inputParcel, Span outputParcel) + { + IBinder binder = GetBinderObjectById(binderId); + + if (binder == null) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Invalid binder id {binderId}"); + + return ResultCode.Success; + } + + return binder.OnTransact(code, flags, inputParcel, outputParcel); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs new file mode 100644 index 00000000..54aac48a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs @@ -0,0 +1,41 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + interface IBinder + { + ResultCode AdjustRefcount(int addVal, int type); + + void GetNativeHandle(uint typeId, out KReadableEvent readableEvent); + + ResultCode OnTransact(uint code, uint flags, ReadOnlySpan inputParcel, Span outputParcel) + { + using Parcel inputParcelReader = new(inputParcel); + + // TODO: support objects? + using Parcel outputParcelWriter = new((uint)(outputParcel.Length - Unsafe.SizeOf()), 0); + + string inputInterfaceToken = inputParcelReader.ReadInterfaceToken(); + + if (!InterfaceToken.Equals(inputInterfaceToken)) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Invalid interface token {inputInterfaceToken} (expected: {InterfaceToken}"); + + return ResultCode.Success; + } + + OnTransact(code, flags, inputParcelReader, outputParcelWriter); + + outputParcelWriter.Finish().CopyTo(outputParcel); + + return ResultCode.Success; + } + + void OnTransact(uint code, uint flags, Parcel inputParcel, Parcel outputParcel); + + string InterfaceToken { get; } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs new file mode 100644 index 00000000..34a02ca9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + interface IConsumerListener + { + void OnFrameAvailable(ref BufferItem item); + void OnFrameReplaced(ref BufferItem item); + void OnBuffersReleased(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs new file mode 100644 index 00000000..7d7e7257 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + interface IFlattenable + { + uint GetFlattenedSize(); + + uint GetFdCount(); + + void Flatten(Parcel parcel); + + void Unflatten(Parcel parcel); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs new file mode 100644 index 00000000..3c01b92f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs @@ -0,0 +1,304 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + abstract class IGraphicBufferProducer : IBinder + { + public string InterfaceToken => "android.gui.IGraphicBufferProducer"; + + enum TransactionCode : uint + { + RequestBuffer = 1, + SetBufferCount, + DequeueBuffer, + DetachBuffer, + DetachNextBuffer, + AttachBuffer, + QueueBuffer, + CancelBuffer, + Query, + Connect, + Disconnect, + SetSidebandStream, + AllocateBuffers, + SetPreallocatedBuffer, + Reserved15, + GetBufferInfo, + GetBufferHistory, + } + + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x54)] + public struct QueueBufferInput : IFlattenable + { + public long Timestamp; + public int IsAutoTimestamp; + public Rect Crop; + public NativeWindowScalingMode ScalingMode; + public NativeWindowTransform Transform; + public uint StickyTransform; + public int Async; + public int SwapInterval; + public AndroidFence Fence; + + public void Flatten(Parcel parcel) + { + parcel.WriteUnmanagedType(ref this); + } + + public readonly uint GetFdCount() + { + return 0; + } + + public readonly uint GetFlattenedSize() + { + return (uint)Unsafe.SizeOf(); + } + + public void Unflatten(Parcel parcel) + { + this = parcel.ReadUnmanagedType(); + } + } + + public struct QueueBufferOutput + { + public uint Width; + public uint Height; + public NativeWindowTransform TransformHint; + public uint NumPendingBuffers; + public ulong FrameNumber; + + public void WriteToParcel(Parcel parcel) + { + parcel.WriteUInt32(Width); + parcel.WriteUInt32(Height); + parcel.WriteUnmanagedType(ref TransformHint); + parcel.WriteUInt32(NumPendingBuffers); + + if (TransformHint.HasFlag(NativeWindowTransform.ReturnFrameNumber)) + { + parcel.WriteUInt64(FrameNumber); + } + } + } + + public ResultCode AdjustRefcount(int addVal, int type) + { + // TODO? + return ResultCode.Success; + } + + public void GetNativeHandle(uint typeId, out KReadableEvent readableEvent) + { + if (typeId == 0xF) + { + readableEvent = GetWaitBufferFreeEvent(); + } + else + { + throw new NotImplementedException($"Unimplemented native event type {typeId}!"); + } + } + + public void OnTransact(uint code, uint flags, Parcel inputParcel, Parcel outputParcel) + { + Status status = Status.Success; + int slot; + AndroidFence fence; + QueueBufferInput queueInput; + QueueBufferOutput queueOutput; + NativeWindowApi api; + + AndroidStrongPointer graphicBuffer; + AndroidStrongPointer strongFence; + + switch ((TransactionCode)code) + { + case TransactionCode.RequestBuffer: + slot = inputParcel.ReadInt32(); + + status = RequestBuffer(slot, out graphicBuffer); + + outputParcel.WriteStrongPointer(ref graphicBuffer); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.SetBufferCount: + int bufferCount = inputParcel.ReadInt32(); + + status = SetBufferCount(bufferCount); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.DequeueBuffer: + bool async = inputParcel.ReadBoolean(); + uint width = inputParcel.ReadUInt32(); + uint height = inputParcel.ReadUInt32(); + PixelFormat format = inputParcel.ReadUnmanagedType(); + uint usage = inputParcel.ReadUInt32(); + + status = DequeueBuffer(out int dequeueSlot, out fence, async, width, height, format, usage); + strongFence = new AndroidStrongPointer(fence); + + outputParcel.WriteInt32(dequeueSlot); + outputParcel.WriteStrongPointer(ref strongFence); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.DetachBuffer: + slot = inputParcel.ReadInt32(); + + status = DetachBuffer(slot); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.DetachNextBuffer: + status = DetachNextBuffer(out graphicBuffer, out fence); + strongFence = new AndroidStrongPointer(fence); + + outputParcel.WriteStrongPointer(ref graphicBuffer); + outputParcel.WriteStrongPointer(ref strongFence); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.AttachBuffer: + graphicBuffer = inputParcel.ReadStrongPointer(); + + status = AttachBuffer(out slot, graphicBuffer); + + outputParcel.WriteInt32(slot); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.QueueBuffer: + slot = inputParcel.ReadInt32(); + queueInput = inputParcel.ReadFlattenable(); + + status = QueueBuffer(slot, ref queueInput, out queueOutput); + + queueOutput.WriteToParcel(outputParcel); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.CancelBuffer: + slot = inputParcel.ReadInt32(); + fence = inputParcel.ReadFlattenable(); + + CancelBuffer(slot, ref fence); + + outputParcel.WriteStatus(Status.Success); + + break; + case TransactionCode.Query: + NativeWindowAttribute what = inputParcel.ReadUnmanagedType(); + + status = Query(what, out int outValue); + + outputParcel.WriteInt32(outValue); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.Connect: + bool hasListener = inputParcel.ReadBoolean(); + + IProducerListener listener = null; + + if (hasListener) + { + throw new NotImplementedException("Connect with a strong binder listener isn't implemented"); + } + + api = inputParcel.ReadUnmanagedType(); + + bool producerControlledByApp = inputParcel.ReadBoolean(); + + status = Connect(listener, api, producerControlledByApp, out queueOutput); + + queueOutput.WriteToParcel(outputParcel); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.Disconnect: + api = inputParcel.ReadUnmanagedType(); + + status = Disconnect(api); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.SetPreallocatedBuffer: + slot = inputParcel.ReadInt32(); + + graphicBuffer = inputParcel.ReadStrongPointer(); + + status = SetPreallocatedBuffer(slot, graphicBuffer); + + outputParcel.WriteStatus(status); + + break; + case TransactionCode.GetBufferHistory: + int bufferHistoryCount = inputParcel.ReadInt32(); + + status = GetBufferHistory(bufferHistoryCount, out Span bufferInfos); + + outputParcel.WriteStatus(status); + + outputParcel.WriteInt32(bufferInfos.Length); + + outputParcel.WriteUnmanagedSpan(bufferInfos); + + break; + default: + throw new NotImplementedException($"Transaction {(TransactionCode)code} not implemented"); + } + + if (status != Status.Success) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Error returned by transaction {(TransactionCode)code}: {status}"); + } + } + + protected abstract KReadableEvent GetWaitBufferFreeEvent(); + + public abstract Status RequestBuffer(int slot, out AndroidStrongPointer graphicBuffer); + + public abstract Status SetBufferCount(int bufferCount); + + public abstract Status DequeueBuffer(out int slot, out AndroidFence fence, bool async, uint width, uint height, PixelFormat format, uint usage); + + public abstract Status DetachBuffer(int slot); + + public abstract Status DetachNextBuffer(out AndroidStrongPointer graphicBuffer, out AndroidFence fence); + + public abstract Status AttachBuffer(out int slot, AndroidStrongPointer graphicBuffer); + + public abstract Status QueueBuffer(int slot, ref QueueBufferInput input, out QueueBufferOutput output); + + public abstract void CancelBuffer(int slot, ref AndroidFence fence); + + public abstract Status Query(NativeWindowAttribute what, out int outValue); + + public abstract Status Connect(IProducerListener listener, NativeWindowApi api, bool producerControlledByApp, out QueueBufferOutput output); + + public abstract Status Disconnect(NativeWindowApi api); + + public abstract Status SetPreallocatedBuffer(int slot, AndroidStrongPointer graphicBuffer); + + public abstract Status GetBufferHistory(int bufferHistoryCount, out Span bufferInfos); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs new file mode 100644 index 00000000..2ffa961c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs @@ -0,0 +1,108 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; +using System.Buffers; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + abstract class IHOSBinderDriver : IpcService + { + public IHOSBinderDriver() { } + + [CommandCmif(0)] + // TransactParcel(s32, u32, u32, buffer) -> buffer + public ResultCode TransactParcel(ServiceCtx context) + { + int binderId = context.RequestData.ReadInt32(); + + uint code = context.RequestData.ReadUInt32(); + uint flags = context.RequestData.ReadUInt32(); + + ulong dataPos = context.Request.SendBuff[0].Position; + ulong dataSize = context.Request.SendBuff[0].Size; + + ulong replyPos = context.Request.ReceiveBuff[0].Position; + ulong replySize = context.Request.ReceiveBuff[0].Size; + + ReadOnlySpan inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize); + + Span outputParcel = new(new byte[replySize]); + + ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel); + + if (result == ResultCode.Success) + { + context.Memory.Write(replyPos, outputParcel); + } + + return result; + } + + [CommandCmif(1)] + // AdjustRefcount(s32, s32, s32) + public ResultCode AdjustRefcount(ServiceCtx context) + { + int binderId = context.RequestData.ReadInt32(); + int addVal = context.RequestData.ReadInt32(); + int type = context.RequestData.ReadInt32(); + + return AdjustRefcount(binderId, addVal, type); + } + + [CommandCmif(2)] + // GetNativeHandle(s32, s32) -> handle + public ResultCode GetNativeHandle(ServiceCtx context) + { + int binderId = context.RequestData.ReadInt32(); + + uint typeId = context.RequestData.ReadUInt32(); + + GetNativeHandle(binderId, typeId, out KReadableEvent readableEvent); + + if (context.Process.HandleTable.GenerateHandle(readableEvent, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle); + + return ResultCode.Success; + } + + [CommandCmif(3)] // 3.0.0+ + // TransactParcelAuto(s32, u32, u32, buffer) -> buffer + public ResultCode TransactParcelAuto(ServiceCtx context) + { + int binderId = context.RequestData.ReadInt32(); + + uint code = context.RequestData.ReadUInt32(); + uint flags = context.RequestData.ReadUInt32(); + + (ulong dataPos, ulong dataSize) = context.Request.GetBufferType0x21(); + (ulong replyPos, ulong replySize) = context.Request.GetBufferType0x22(); + + ReadOnlySpan inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize); + + using SpanOwner outputParcelOwner = SpanOwner.RentCleared(checked((int)replySize)); + + Span outputParcel = outputParcelOwner.Span; + + ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel); + + if (result == ResultCode.Success) + { + context.Memory.Write(replyPos, outputParcel); + } + + return result; + } + + protected abstract ResultCode AdjustRefcount(int binderId, int addVal, int type); + + protected abstract void GetNativeHandle(int binderId, uint typeId, out KReadableEvent readableEvent); + + protected abstract ResultCode OnTransact(int binderId, uint code, uint flags, ReadOnlySpan inputParcel, Span outputParcel); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs new file mode 100644 index 00000000..945d9d82 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + interface IProducerListener + { + void OnBufferReleased(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs new file mode 100644 index 00000000..af32be3d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum LayerState + { + NotInitialized, + ManagedClosed, + ManagedOpened, + Stray, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs new file mode 100644 index 00000000..45c352ab --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum NativeWindowApi + { + NoApi = 0, + NVN = 1, + CPU = 2, + Media = 3, + Camera = 4, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs new file mode 100644 index 00000000..d4994226 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum NativeWindowAttribute : uint + { + Width = 0, + Height = 1, + Format = 2, + MinUnqueuedBuffers = 3, + ConsumerRunningBehind = 9, + ConsumerUsageBits = 10, + MaxBufferCountAsync = 12, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs new file mode 100644 index 00000000..9f521ca2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum NativeWindowScalingMode : uint + { + Freeze = 0, + ScaleToWindow = 1, + ScaleCrop = 2, + Unknown = 3, + NoScaleCrop = 4, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs new file mode 100644 index 00000000..d568b743 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs @@ -0,0 +1,18 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [Flags] + enum NativeWindowTransform : uint + { + None = 0, + FlipX = 1, + FlipY = 2, + Rotate90 = 4, + Rotate180 = FlipX | FlipY, + Rotate270 = Rotate90 | Rotate180, + InverseDisplay = 8, + NoVSyncCapability = 0x10, + ReturnFrameNumber = 0x20, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs new file mode 100644 index 00000000..2ca0e1aa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs @@ -0,0 +1,238 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + sealed class Parcel : IDisposable + { + private readonly MemoryOwner _rawDataOwner; + + private Span Raw => _rawDataOwner.Memory.Span; + + private ref ParcelHeader Header => ref MemoryMarshal.Cast(Raw)[0]; + + private Span Payload => Raw.Slice((int)Header.PayloadOffset, (int)Header.PayloadSize); + + private Span Objects => Raw.Slice((int)Header.ObjectOffset, (int)Header.ObjectsSize); + + private int _payloadPosition; + private int _objectPosition; + + private bool _isDisposed; + + public Parcel(ReadOnlySpan data) + { + _rawDataOwner = MemoryOwner.RentCopy(data); + + _payloadPosition = 0; + _objectPosition = 0; + } + + public Parcel(uint payloadSize, uint objectsSize) + { + uint headerSize = (uint)Unsafe.SizeOf(); + + _rawDataOwner = MemoryOwner.RentCleared(checked((int)BitUtils.AlignUp(headerSize + payloadSize + objectsSize, 4))); + + Header.PayloadSize = payloadSize; + Header.ObjectsSize = objectsSize; + Header.PayloadOffset = headerSize; + Header.ObjectOffset = Header.PayloadOffset + Header.ObjectsSize; + } + + public string ReadInterfaceToken() + { + // Ignore the policy flags +#pragma warning disable IDE0059 // Remove unnecessary value assignment + int strictPolicy = ReadInt32(); +#pragma warning restore IDE0059 + + return ReadString16(); + } + + public string ReadString16() + { + int size = ReadInt32(); + + if (size < 0) + { + return ""; + } + + ReadOnlySpan data = ReadInPlace((size + 1) * 2); + + // Return the unicode string without the last character (null terminator) + return Encoding.Unicode.GetString(data[..(size * 2)]); + } + + public int ReadInt32() => ReadUnmanagedType(); + public uint ReadUInt32() => ReadUnmanagedType(); + public bool ReadBoolean() => ReadUnmanagedType() != 0; + public long ReadInt64() => ReadUnmanagedType(); + public ulong ReadUInt64() => ReadUnmanagedType(); + + public T ReadFlattenable() where T : unmanaged, IFlattenable + { + long flattenableSize = ReadInt64(); + + T result = new(); + + Debug.Assert(flattenableSize == result.GetFlattenedSize()); + + result.Unflatten(this); + + return result; + } + + public T ReadUnmanagedType() where T : unmanaged + { + ReadOnlySpan data = ReadInPlace(Unsafe.SizeOf()); + + return MemoryMarshal.Cast(data)[0]; + } + + public ReadOnlySpan ReadInPlace(int size) + { + ReadOnlySpan result = Payload.Slice(_payloadPosition, size); + + _payloadPosition += BitUtils.AlignUp(size, 4); + + return result; + } + + [StructLayout(LayoutKind.Sequential, Size = 0x28)] + private struct FlatBinderObject + { + public int Type; + public int Flags; + public long BinderId; + public long Cookie; + + private byte _serviceNameStart; + + public Span ServiceName => MemoryMarshal.CreateSpan(ref _serviceNameStart, 0x8); + } + + public void WriteObject(T obj, string serviceName) where T : IBinder + { + FlatBinderObject flatBinderObject = new() + { + Type = 2, + Flags = 0, + BinderId = HOSBinderDriverServer.GetBinderId(obj), + }; + + Encoding.ASCII.GetBytes(serviceName).CopyTo(flatBinderObject.ServiceName); + + WriteUnmanagedType(ref flatBinderObject); + + // TODO: figure out what this value is + + Span fourBytes = stackalloc byte[4]; + + WriteInplaceObject(fourBytes); + } + + public AndroidStrongPointer ReadStrongPointer() where T : unmanaged, IFlattenable + { + bool hasObject = ReadBoolean(); + + if (hasObject) + { + T obj = ReadFlattenable(); + + return new AndroidStrongPointer(obj); + } + else + { + return new AndroidStrongPointer(); + } + } + + public void WriteStrongPointer(ref AndroidStrongPointer value) where T : unmanaged, IFlattenable + { + WriteBoolean(!value.IsNull); + + if (!value.IsNull) + { + WriteFlattenable(ref value.Object); + } + } + + public void WriteFlattenable(ref T value) where T : unmanaged, IFlattenable + { + WriteInt64(value.GetFlattenedSize()); + + value.Flatten(this); + } + + public void WriteStatus(Status status) => WriteUnmanagedType(ref status); + public void WriteBoolean(bool value) => WriteUnmanagedType(ref value); + public void WriteInt32(int value) => WriteUnmanagedType(ref value); + public void WriteUInt32(uint value) => WriteUnmanagedType(ref value); + public void WriteInt64(long value) => WriteUnmanagedType(ref value); + public void WriteUInt64(ulong value) => WriteUnmanagedType(ref value); + + public void WriteUnmanagedSpan(ReadOnlySpan value) where T : unmanaged + { + WriteInplace(MemoryMarshal.Cast(value)); + } + + public void WriteUnmanagedType(ref T value) where T : unmanaged + { + WriteInplace(SpanHelpers.AsByteSpan(ref value)); + } + + public void WriteInplace(ReadOnlySpan data) + { + Span result = Payload.Slice(_payloadPosition, data.Length); + + data.CopyTo(result); + + _payloadPosition += BitUtils.AlignUp(data.Length, 4); + } + + public void WriteInplaceObject(ReadOnlySpan data) + { + Span result = Objects.Slice(_objectPosition, data.Length); + + data.CopyTo(result); + + _objectPosition += BitUtils.AlignUp(data.Length, 4); + } + + private void UpdateHeader() + { + uint headerSize = (uint)Unsafe.SizeOf(); + + Header.PayloadSize = (uint)_payloadPosition; + Header.ObjectsSize = (uint)_objectPosition; + Header.PayloadOffset = headerSize; + Header.ObjectOffset = Header.PayloadOffset + Header.PayloadSize; + } + + public ReadOnlySpan Finish() + { + UpdateHeader(); + + return Raw[..(int)(Header.PayloadSize + Header.ObjectsSize + Unsafe.SizeOf())]; + } + + public void Dispose() + { + if (!_isDisposed) + { + _isDisposed = true; + + _rawDataOwner.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs new file mode 100644 index 00000000..1937204a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + struct ParcelHeader + { + public uint PayloadSize; + public uint PayloadOffset; + public uint ObjectsSize; + public uint ObjectOffset; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs new file mode 100644 index 00000000..8febb7fb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum PixelFormat : uint + { + Unknown, + Rgba8888, + Rgbx8888, + Rgb888, + Rgb565, + Bgra8888, + Rgba5551, + Rgba4444, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs new file mode 100644 index 00000000..14f80fa9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs @@ -0,0 +1,25 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + enum Status + { + Success = 0, + WouldBlock = -11, + NoMemory = -12, + Busy = -16, + NoInit = -19, + BadValue = -22, + InvalidOperation = -37, + + // Producer flags + BufferNeedsReallocation = 1, + ReleaseAllBuffers = 2, + + // Consumer errors + StaleBufferSlot = 1, + NoBufferAvailaible = 2, + PresentLater = 3, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs new file mode 100644 index 00000000..fd517b1a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs @@ -0,0 +1,539 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.PreciseSleep; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class SurfaceFlinger : IConsumerListener, IDisposable + { + private const int TargetFps = 60; + + private readonly Switch _device; + + private readonly Dictionary _layers; + + private bool _isRunning; + + private readonly Thread _composerThread; + + private readonly AutoResetEvent _event = new(false); + private readonly AutoResetEvent _nextFrameEvent = new(true); + private long _ticks; + private long _ticksPerFrame; + private readonly long _spinTicks; + private readonly long _1msTicks; + + private int _swapInterval; + private int _swapIntervalDelay; + + private readonly object _lock = new(); + + public long RenderLayerId { get; private set; } + + private class Layer + { + public int ProducerBinderId; + public IGraphicBufferProducer Producer; + public BufferItemConsumer Consumer; + public BufferQueueCore Core; + public ulong Owner; + public LayerState State; + } + + private class TextureCallbackInformation + { + public Layer Layer; + public BufferItem Item; + } + + public SurfaceFlinger(Switch device) + { + _device = device; + _layers = new Dictionary(); + RenderLayerId = 0; + + _composerThread = new Thread(HandleComposition) + { + Name = "SurfaceFlinger.Composer", + Priority = ThreadPriority.AboveNormal + }; + + _ticks = 0; + _spinTicks = Stopwatch.Frequency / 500; + _1msTicks = Stopwatch.Frequency / 1000; + + UpdateSwapInterval(1); + + _composerThread.Start(); + } + + private void UpdateSwapInterval(int swapInterval) + { + _swapInterval = swapInterval; + + // If the swap interval is 0, Game VSync is disabled. + if (_swapInterval == 0) + { + _nextFrameEvent.Set(); + _ticksPerFrame = 1; + } + else + { + _ticksPerFrame = Stopwatch.Frequency / TargetFps; + } + } + + public IGraphicBufferProducer CreateLayer(out long layerId, ulong pid, LayerState initialState = LayerState.ManagedClosed) + { + layerId = 1; + + lock (_lock) + { + foreach (KeyValuePair pair in _layers) + { + if (pair.Key >= layerId) + { + layerId = pair.Key + 1; + } + } + } + + CreateLayerFromId(pid, layerId, initialState); + + return GetProducerByLayerId(layerId); + } + + private void CreateLayerFromId(ulong pid, long layerId, LayerState initialState) + { + lock (_lock) + { + Logger.Info?.Print(LogClass.SurfaceFlinger, $"Creating layer {layerId}"); + + BufferQueueCore core = BufferQueue.CreateBufferQueue(_device, pid, out BufferQueueProducer producer, out BufferQueueConsumer consumer); + + core.BufferQueued += () => + { + _nextFrameEvent.Set(); + }; + + _layers.Add(layerId, new Layer + { + ProducerBinderId = HOSBinderDriverServer.RegisterBinderObject(producer), + Producer = producer, + Consumer = new BufferItemConsumer(_device, consumer, 0, -1, false, this), + Core = core, + Owner = pid, + State = initialState, + }); + } + } + + public Vi.ResultCode OpenLayer(ulong pid, long layerId, out IBinder producer) + { + Layer layer = GetLayerByIdLocked(layerId); + + if (layer == null || layer.State != LayerState.ManagedClosed) + { + producer = null; + + return Vi.ResultCode.InvalidArguments; + } + + layer.State = LayerState.ManagedOpened; + producer = layer.Producer; + + return Vi.ResultCode.Success; + } + + public Vi.ResultCode CloseLayer(long layerId) + { + lock (_lock) + { + Layer layer = GetLayerByIdLocked(layerId); + + if (layer == null) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to close layer {layerId}"); + + return Vi.ResultCode.InvalidValue; + } + + CloseLayer(layerId, layer); + + return Vi.ResultCode.Success; + } + } + + public Vi.ResultCode DestroyManagedLayer(long layerId) + { + lock (_lock) + { + Layer layer = GetLayerByIdLocked(layerId); + + if (layer == null) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy managed layer {layerId} (not found)"); + + return Vi.ResultCode.InvalidValue; + } + + if (layer.State != LayerState.ManagedClosed && layer.State != LayerState.ManagedOpened) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy managed layer {layerId} (permission denied)"); + + return Vi.ResultCode.PermissionDenied; + } + + HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId); + + if (_layers.Remove(layerId) && layer.State == LayerState.ManagedOpened) + { + CloseLayer(layerId, layer); + } + + return Vi.ResultCode.Success; + } + } + + public Vi.ResultCode DestroyStrayLayer(long layerId) + { + lock (_lock) + { + Layer layer = GetLayerByIdLocked(layerId); + + if (layer == null) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy stray layer {layerId} (not found)"); + + return Vi.ResultCode.InvalidValue; + } + + if (layer.State != LayerState.Stray) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy stray layer {layerId} (permission denied)"); + + return Vi.ResultCode.PermissionDenied; + } + + HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId); + + if (_layers.Remove(layerId)) + { + CloseLayer(layerId, layer); + } + + return Vi.ResultCode.Success; + } + } + + private void CloseLayer(long layerId, Layer layer) + { + // If the layer was removed and the current in use, we need to change the current layer in use. + if (RenderLayerId == layerId) + { + // If no layer is availaible, reset to default value. + if (_layers.Count == 0) + { + SetRenderLayer(0); + } + else + { + SetRenderLayer(_layers.Last().Key); + } + } + + if (layer.State == LayerState.ManagedOpened) + { + layer.State = LayerState.ManagedClosed; + } + } + + public void SetRenderLayer(long layerId) + { + lock (_lock) + { + RenderLayerId = layerId; + } + } + + private Layer GetLayerByIdLocked(long layerId) + { + foreach (KeyValuePair pair in _layers) + { + if (pair.Key == layerId) + { + return pair.Value; + } + } + + return null; + } + + public IGraphicBufferProducer GetProducerByLayerId(long layerId) + { + lock (_lock) + { + Layer layer = GetLayerByIdLocked(layerId); + + if (layer != null) + { + return layer.Producer; + } + } + + return null; + } + + private void HandleComposition() + { + _isRunning = true; + + long lastTicks = PerformanceCounter.ElapsedTicks; + + while (_isRunning) + { + long ticks = PerformanceCounter.ElapsedTicks; + + if (_swapInterval == 0) + { + Compose(); + + _device.System?.SignalVsync(); + + _nextFrameEvent.WaitOne(17); + lastTicks = ticks; + } + else + { + _ticks += ticks - lastTicks; + lastTicks = ticks; + + if (_ticks >= _ticksPerFrame) + { + if (_swapIntervalDelay-- == 0) + { + Compose(); + + // When a frame is presented, delay the next one by its swap interval value. + _swapIntervalDelay = Math.Max(0, _swapInterval - 1); + } + + _device.System?.SignalVsync(); + + // Apply a maximum bound of 3 frames to the tick remainder, in case some event causes Ryujinx to pause for a long time or messes with the timer. + _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame * 3); + } + + // Sleep if possible. If the time til the next frame is too low, spin wait instead. + long diff = _ticksPerFrame - (_ticks + PerformanceCounter.ElapsedTicks - ticks); + if (diff > 0) + { + PreciseSleepHelper.SleepUntilTimePoint(_event, PerformanceCounter.ElapsedTicks + diff); + + diff = _ticksPerFrame - (_ticks + PerformanceCounter.ElapsedTicks - ticks); + + if (diff < _spinTicks) + { + PreciseSleepHelper.SpinWaitUntilTimePoint(PerformanceCounter.ElapsedTicks + diff); + } + else + { + _event.WaitOne((int)(diff / _1msTicks)); + } + } + } + } + } + + public void Compose() + { + lock (_lock) + { + // TODO: support multilayers (& multidisplay ?) + if (RenderLayerId == 0) + { + return; + } + + Layer layer = GetLayerByIdLocked(RenderLayerId); + + Status acquireStatus = layer.Consumer.AcquireBuffer(out BufferItem item, 0); + + if (acquireStatus == Status.Success) + { + // If device vsync is disabled, reflect the change. + if (!_device.EnableDeviceVsync) + { + if (_swapInterval != 0) + { + UpdateSwapInterval(0); + } + } + else if (item.SwapInterval != _swapInterval) + { + UpdateSwapInterval(item.SwapInterval); + } + + PostFrameBuffer(layer, item); + } + else if (acquireStatus != Status.NoBufferAvailaible && acquireStatus != Status.InvalidOperation) + { + throw new InvalidOperationException(); + } + } + } + + private void PostFrameBuffer(Layer layer, BufferItem item) + { + int frameBufferWidth = item.GraphicBuffer.Object.Width; + int frameBufferHeight = item.GraphicBuffer.Object.Height; + + int nvMapHandle = item.GraphicBuffer.Object.Buffer.Surfaces[0].NvMapHandle; + + if (nvMapHandle == 0) + { + nvMapHandle = item.GraphicBuffer.Object.Buffer.NvMapId; + } + + ulong bufferOffset = (ulong)item.GraphicBuffer.Object.Buffer.Surfaces[0].Offset; + + NvMapHandle map = NvMapDeviceFile.GetMapFromHandle(layer.Owner, nvMapHandle); + + ulong frameBufferAddress = map.Address + bufferOffset; + + Format format = ConvertColorFormat(item.GraphicBuffer.Object.Buffer.Surfaces[0].ColorFormat); + + int bytesPerPixel = + format == Format.B5G6R5Unorm || + format == Format.R4G4B4A4Unorm ? 2 : 4; + + int gobBlocksInY = 1 << item.GraphicBuffer.Object.Buffer.Surfaces[0].BlockHeightLog2; + + // Note: Rotation is being ignored. + Rect cropRect = item.Crop; + + bool flipX = item.Transform.HasFlag(NativeWindowTransform.FlipX); + bool flipY = item.Transform.HasFlag(NativeWindowTransform.FlipY); + + AspectRatio aspectRatio = _device.Configuration.AspectRatio; + bool isStretched = aspectRatio == AspectRatio.Stretched; + + ImageCrop crop = new( + cropRect.Left, + cropRect.Right, + cropRect.Top, + cropRect.Bottom, + flipX, + flipY, + isStretched, + aspectRatio.ToFloatX(), + aspectRatio.ToFloatY()); + + TextureCallbackInformation textureCallbackInformation = new() + { + Layer = layer, + Item = item, + }; + + if (_device.Gpu.Window.EnqueueFrameThreadSafe( + layer.Owner, + frameBufferAddress, + frameBufferWidth, + frameBufferHeight, + 0, + false, + gobBlocksInY, + format, + bytesPerPixel, + crop, + AcquireBuffer, + ReleaseBuffer, + textureCallbackInformation)) + { + if (item.Fence.FenceCount == 0) + { + _device.Gpu.Window.SignalFrameReady(); + _device.Gpu.GPFifo.Interrupt(); + } + else + { + item.Fence.RegisterCallback(_device.Gpu, (x) => + { + _device.Gpu.Window.SignalFrameReady(); + _device.Gpu.GPFifo.Interrupt(); + }); + } + } + else + { + ReleaseBuffer(textureCallbackInformation); + } + } + + private void ReleaseBuffer(object obj) + { + ReleaseBuffer((TextureCallbackInformation)obj); + } + + private void ReleaseBuffer(TextureCallbackInformation information) + { + AndroidFence fence = AndroidFence.NoFence; + + information.Layer.Consumer.ReleaseBuffer(information.Item, ref fence); + } + + private void AcquireBuffer(GpuContext ignored, object obj) + { + AcquireBuffer((TextureCallbackInformation)obj); + } + + private void AcquireBuffer(TextureCallbackInformation information) + { + information.Item.Fence.WaitForever(_device.Gpu); + } + + public static Format ConvertColorFormat(ColorFormat colorFormat) + { + return colorFormat switch + { + ColorFormat.A8B8G8R8 => Format.R8G8B8A8Unorm, + ColorFormat.X8B8G8R8 => Format.R8G8B8A8Unorm, + ColorFormat.R5G6B5 => Format.B5G6R5Unorm, + ColorFormat.A8R8G8B8 => Format.B8G8R8A8Unorm, + ColorFormat.A4B4G4R4 => Format.R4G4B4A4Unorm, + _ => throw new NotImplementedException($"Color Format \"{colorFormat}\" not implemented!"), + }; + } + + public void Dispose() + { + _isRunning = false; + + foreach (Layer layer in _layers.Values) + { + layer.Core.PrepareForExit(); + } + } + + public void OnFrameAvailable(ref BufferItem item) + { + _device.Statistics.RecordGameFrameTime(); + } + + public void OnFrameReplaced(ref BufferItem item) + { + _device.Statistics.RecordGameFrameTime(); + } + + public void OnBuffersReleased() { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs new file mode 100644 index 00000000..ebc6501a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs @@ -0,0 +1,104 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Gpu.Synchronization; +using Ryujinx.HLE.HOS.Services.Nv.Types; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x24)] + struct AndroidFence : IFlattenable + { + public int FenceCount; + + private byte _fenceStorageStart; + + private Span Storage => MemoryMarshal.CreateSpan(ref _fenceStorageStart, Unsafe.SizeOf() * 4); + + public Span NvFences => MemoryMarshal.Cast(Storage); + + public static AndroidFence NoFence + { + get + { + AndroidFence fence = new() + { + FenceCount = 0, + }; + + fence.NvFences[0].Id = NvFence.InvalidSyncPointId; + + return fence; + } + } + + public void AddFence(NvFence fence) + { + NvFences[FenceCount++] = fence; + } + + public void WaitForever(GpuContext gpuContext) + { + bool hasTimeout = Wait(gpuContext, TimeSpan.FromMilliseconds(3000)); + + if (hasTimeout) + { + Logger.Error?.Print(LogClass.SurfaceFlinger, "Android fence didn't signal in 3000 ms"); + Wait(gpuContext, Timeout.InfiniteTimeSpan); + } + + } + + public bool Wait(GpuContext gpuContext, TimeSpan timeout) + { + for (int i = 0; i < FenceCount; i++) + { + bool hasTimeout = NvFences[i].Wait(gpuContext, timeout); + + if (hasTimeout) + { + return true; + } + } + + return false; + } + + public void RegisterCallback(GpuContext gpuContext, Action callback) + { + ref NvFence fence = ref NvFences[FenceCount - 1]; + + if (fence.IsValid()) + { + gpuContext.Synchronization.RegisterCallbackOnSyncpoint(fence.Id, fence.Value, callback); + } + else + { + callback(null); + } + } + + public readonly uint GetFlattenedSize() + { + return (uint)Unsafe.SizeOf(); + } + + public readonly uint GetFdCount() + { + return 0; + } + + public void Flatten(Parcel parcel) + { + parcel.WriteUnmanagedType(ref this); + } + + public void Unflatten(Parcel parcel) + { + this = parcel.ReadUnmanagedType(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs new file mode 100644 index 00000000..85db34d4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs @@ -0,0 +1,38 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types +{ + class AndroidStrongPointer where T : unmanaged, IFlattenable + { + public T Object; + + private bool _hasObject; + + public bool IsNull => !_hasObject; + + public AndroidStrongPointer() + { + _hasObject = false; + } + + public AndroidStrongPointer(T obj) + { + Set(obj); + } + + public void Set(AndroidStrongPointer other) + { + Object = other.Object; + _hasObject = other._hasObject; + } + + public void Set(T obj) + { + Object = obj; + _hasObject = true; + } + + public void Reset() + { + _hasObject = false; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs new file mode 100644 index 00000000..243a88fe --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs @@ -0,0 +1,14 @@ +using Ryujinx.HLE.HOS.Services.Time.Clock; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x1C, Pack = 1)] + struct BufferInfo + { + public ulong FrameNumber; + public TimeSpanType QueueTime; + public TimeSpanType PresentationTime; + public BufferState State; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferItem.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferItem.cs new file mode 100644 index 00000000..dc752daa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferItem.cs @@ -0,0 +1,63 @@ +using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types; +using System; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class BufferItem : ICloneable + { + public AndroidStrongPointer GraphicBuffer; + public AndroidFence Fence; + public Rect Crop; + public NativeWindowTransform Transform; + public NativeWindowScalingMode ScalingMode; + public long Timestamp; + public bool IsAutoTimestamp; + public int SwapInterval; + public ulong FrameNumber; + public int Slot; + public bool IsDroppable; + public bool AcquireCalled; + public bool TransformToDisplayInverse; + + public BufferItem() + { + GraphicBuffer = new AndroidStrongPointer(); + Transform = NativeWindowTransform.None; + ScalingMode = NativeWindowScalingMode.Freeze; + Timestamp = 0; + IsAutoTimestamp = false; + FrameNumber = 0; + Slot = BufferSlotArray.InvalidBufferSlot; + IsDroppable = false; + AcquireCalled = false; + TransformToDisplayInverse = false; + SwapInterval = 1; + Fence = AndroidFence.NoFence; + + Crop = new Rect(); + Crop.MakeInvalid(); + } + + public object Clone() + { + BufferItem item = new() + { + Transform = Transform, + ScalingMode = ScalingMode, + IsAutoTimestamp = IsAutoTimestamp, + FrameNumber = FrameNumber, + Slot = Slot, + IsDroppable = IsDroppable, + AcquireCalled = AcquireCalled, + TransformToDisplayInverse = TransformToDisplayInverse, + SwapInterval = SwapInterval, + Fence = Fence, + Crop = Crop, + }; + + item.GraphicBuffer.Set(GraphicBuffer); + + return item; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs new file mode 100644 index 00000000..89e6dfc6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + internal enum BufferState + { + Free = 0, + Dequeued = 1, + Queued = 2, + Acquired = 3, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs new file mode 100644 index 00000000..12539032 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorBytePerPixel + { + Bpp1 = 1, + Bpp2 = 2, + Bpp4 = 4, + Bpp8 = 8, + Bpp16 = 16, + Bpp24 = 24, + Bpp32 = 32, + Bpp48 = 48, + Bpp64 = 64, + Bpp96 = 96, + Bpp128 = 128, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs new file mode 100644 index 00000000..6dbeb11b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs @@ -0,0 +1,44 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorComponent : uint + { +#pragma warning disable IDE0055 // Disable formatting + X1 = (0x01 << ColorShift.Component) | ColorBytePerPixel.Bpp1, + X2 = (0x02 << ColorShift.Component) | ColorBytePerPixel.Bpp2, + X4 = (0x03 << ColorShift.Component) | ColorBytePerPixel.Bpp4, + X8 = (0x04 << ColorShift.Component) | ColorBytePerPixel.Bpp8, + Y4X4 = (0x05 << ColorShift.Component) | ColorBytePerPixel.Bpp8, + X3Y3Z2 = (0x06 << ColorShift.Component) | ColorBytePerPixel.Bpp8, + X8Y8 = (0x07 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X8Y8X8Z8 = (0x08 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y8X8Z8X8 = (0x09 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X16 = (0x0A << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y2X14 = (0x0B << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y4X12 = (0x0C << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y6X10 = (0x0D << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Y8X8 = (0x0E << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X10 = (0x0F << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X12 = (0x10 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + Z5Y5X6 = (0x11 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X5Y6Z5 = (0x12 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X6Y5Z5 = (0x13 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X1Y5Z5W5 = (0x14 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X4Y4Z4W4 = (0x15 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X5Y1Z5W5 = (0x16 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X5Y5Z1W5 = (0x17 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X5Y5Z5W1 = (0x18 << ColorShift.Component) | ColorBytePerPixel.Bpp16, + X8Y8Z8 = (0x19 << ColorShift.Component) | ColorBytePerPixel.Bpp24, + X24 = (0x1A << ColorShift.Component) | ColorBytePerPixel.Bpp24, + X32 = (0x1C << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X16Y16 = (0x1D << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X11Y11Z10 = (0x1E << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X2Y10Z10W10 = (0x20 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X8Y8Z8W8 = (0x21 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + Y10X10 = (0x22 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X10Y10Z10W2 = (0x23 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + Y12X12 = (0x24 << ColorShift.Component) | ColorBytePerPixel.Bpp32, + X20Y20Z20 = (0x26 << ColorShift.Component) | ColorBytePerPixel.Bpp64, + X16Y16Z16W16 = (0x27 << ColorShift.Component) | ColorBytePerPixel.Bpp64, +#pragma warning restore IDE0055 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs new file mode 100644 index 00000000..d30171cb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorDataType + { + Integer = 0x0 << ColorShift.DataType, + Float = 0x1 << ColorShift.DataType, + Stencil = 0x2 << ColorShift.DataType, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs new file mode 100644 index 00000000..2512393c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs @@ -0,0 +1,240 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + enum ColorFormat : ulong + { +#pragma warning disable IDE0055 // Disable formatting + NonColor8 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + NonColor16 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + NonColor24 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X24 | ColorDataType.Integer, + NonColor32 = ColorSpace.NonColor | ColorSwizzle.X000 | ColorComponent.X32 | ColorDataType.Integer, + X4C4 = ColorSpace.NonColor | ColorSwizzle.Y000 | ColorComponent.Y4X4 | ColorDataType.Integer, + A4L4 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.Y4X4 | ColorDataType.Integer, + A8L8 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.Y8X8 | ColorDataType.Integer, + Float_A16L16 = ColorSpace.LinearRGBA | ColorSwizzle.YYYX | ColorComponent.X16Y16 | ColorDataType.Float, + A1B5G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + A4B4G4R4 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer, + A5B5G5R1 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + A2B10G10R10 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_A16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + A1R5G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + A4R4G4B4 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer, + A5R1G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X5Y1Z5W5 | ColorDataType.Integer, + A2R10G10B10 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8R8G8B8 = ColorSpace.LinearRGBA | ColorSwizzle.YZWX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A1 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X1 | ColorDataType.Integer, + A2 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X2 | ColorDataType.Integer, + A4 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X4 | ColorDataType.Integer, + A8 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X8 | ColorDataType.Integer, + A16 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X16 | ColorDataType.Integer, + A32 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X32 | ColorDataType.Integer, + Float_A16 = ColorSpace.LinearRGBA | ColorSwizzle._000X | ColorComponent.X16 | ColorDataType.Float, + L4A4 = ColorSpace.LinearRGBA | ColorSwizzle.XXXY | ColorComponent.Y4X4 | ColorDataType.Integer, + L8A8 = ColorSpace.LinearRGBA | ColorSwizzle.XXXY | ColorComponent.Y8X8 | ColorDataType.Integer, + B4G4R4A4 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer, + B5G5R1A5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X5Y5Z1W5 | ColorDataType.Integer, + B5G5R5A1 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + B8G8R8A8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + B10G10R10A2 = ColorSpace.LinearRGBA | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R1G5B5A5 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + R4G4B4A4 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X4Y4Z4W4 | ColorDataType.Integer, + R5G5B5A1 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + R8G8B8A8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + R10G10B10A2 = ColorSpace.LinearRGBA | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + L1 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X1 | ColorDataType.Integer, + L2 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X2 | ColorDataType.Integer, + L4 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X4 | ColorDataType.Integer, + L8 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X8 | ColorDataType.Integer, + L16 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X16 | ColorDataType.Integer, + L32 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X32 | ColorDataType.Integer, + Float_L16 = ColorSpace.LinearRGBA | ColorSwizzle.XXX1 | ColorComponent.X16 | ColorDataType.Float, + B5G6R5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X5Y6Z5 | ColorDataType.Integer, + B6G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X6Y5Z5 | ColorDataType.Integer, + B5G5R5X1 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + B8_G8_R8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer, + B8G8R8X8 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + Float_B10G11R11 = ColorSpace.LinearRGBA | ColorSwizzle.ZYX1 | ColorComponent.X11Y11Z10 | ColorDataType.Float, + X1B5G5R5 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + X8B8G8R8 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_X16B16G16R16 = ColorSpace.LinearRGBA | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + R3G3B2 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X3Y3Z2 | ColorDataType.Integer, + R5G5B6 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.Z5Y5X6 | ColorDataType.Integer, + R5G6B5 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X5Y6Z5 | ColorDataType.Integer, + R5G5B5X1 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X5Y5Z5W1 | ColorDataType.Integer, + R8_G8_B8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer, + R8G8B8X8 = ColorSpace.LinearRGBA | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X1R5G5B5 = ColorSpace.LinearRGBA | ColorSwizzle.YZW1 | ColorComponent.X1Y5Z5W5 | ColorDataType.Integer, + X8R8G8B8 = ColorSpace.LinearRGBA | ColorSwizzle.YZW1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + RG8 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.Y8X8 | ColorDataType.Integer, + R16G16 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.X16Y16 | ColorDataType.Integer, + Float_R16G16 = ColorSpace.LinearRGBA | ColorSwizzle.XY01 | ColorComponent.X16Y16 | ColorDataType.Float, + R8 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X8 | ColorDataType.Integer, + R16 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X16 | ColorDataType.Integer, + Float_R16 = ColorSpace.LinearRGBA | ColorSwizzle.X001 | ColorComponent.X16 | ColorDataType.Float, + A2B10G10R10_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_sRGB = ColorSpace.SRGB | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2R10G10B10_sRGB = ColorSpace.SRGB | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_sRGB = ColorSpace.SRGB | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_sRGB = ColorSpace.SRGB | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_sRGB = ColorSpace.SRGB | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_sRGB = ColorSpace.SRGB | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2B10G10R10_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_709 = ColorSpace.RGB709 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2R10G10B10_709 = ColorSpace.RGB709 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_709 = ColorSpace.RGB709 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_709 = ColorSpace.RGB709 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_709 = ColorSpace.RGB709 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_709 = ColorSpace.RGB709 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2B10G10R10_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2R10G10B10_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_709_Linear = ColorSpace.LinearRGB709 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_A16B16G16R16_scRGB_Linear = ColorSpace.LinearScRGB | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + A2B10G10R10_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2R10G10B10_2020 = ColorSpace.RGB2020 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_2020 = ColorSpace.RGB2020 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_2020 = ColorSpace.RGB2020 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_2020 = ColorSpace.RGB2020 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + A2B10G10R10_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + A8B8G8R8_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_A16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + A2R10G10B10_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.YZWX | ColorComponent.X2Y10Z10W10 | ColorDataType.Integer, + B10G10R10A2_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.ZYXW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + R10G10B10A2_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.XYZW | ColorComponent.X10Y10Z10W2 | ColorDataType.Integer, + X8B8G8R8_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZY1 | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + X16B16G16R16_2020_Linear = ColorSpace.LinearRGB2020 | ColorSwizzle.WZY1 | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Float_A16B16G16R16_2020_PQ = ColorSpace.RGB2020_PQ | ColorSwizzle.WZYX | ColorComponent.X16Y16Z16W16 | ColorDataType.Float, + A4I4 = ColorSpace.ColorIndex | ColorSwizzle.X00X | ColorComponent.Y4X4 | ColorDataType.Integer, + A8I8 = ColorSpace.ColorIndex | ColorSwizzle.X00X | ColorComponent.Y8X8 | ColorDataType.Integer, + I4A4 = ColorSpace.ColorIndex | ColorSwizzle.X00Y | ColorComponent.Y4X4 | ColorDataType.Integer, + I8A8 = ColorSpace.ColorIndex | ColorSwizzle.X00Y | ColorComponent.Y8X8 | ColorDataType.Integer, + I1 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X1 | ColorDataType.Integer, + I2 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X2 | ColorDataType.Integer, + I4 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X4 | ColorDataType.Integer, + I8 = ColorSpace.ColorIndex | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + A8Y8U8V8 = ColorSpace.YCbCr601 | ColorSwizzle.YZWX | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + A16Y16U16V16 = ColorSpace.YCbCr601 | ColorSwizzle.YZWX | ColorComponent.X16Y16Z16W16 | ColorDataType.Integer, + Y8U8V8A8 = ColorSpace.YCbCr601 | ColorSwizzle.XYZW | ColorComponent.X8Y8Z8W8 | ColorDataType.Integer, + V8_U8 = ColorSpace.YCbCr601 | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8 = ColorSpace.YCbCr601 | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V10U10 = ColorSpace.YCbCr601 | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer, + V12U12 = ColorSpace.YCbCr601 | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer, + V8 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + V10 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer, + V12 = ColorSpace.YCbCr601 | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer, + U8_V8 = ColorSpace.YCbCr601 | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8 = ColorSpace.YCbCr601 | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U10V10 = ColorSpace.YCbCr601 | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer, + U12V12 = ColorSpace.YCbCr601 | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer, + U8 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + U10 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer, + U12 = ColorSpace.YCbCr601 | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer, + Y8 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Y10 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer, + Y12 = ColorSpace.YCbCr601 | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer, + YVYU = ColorSpace.YCbCr601 | ColorSwizzle.XZY1 | ColorComponent.X8Y8X8Z8 | ColorDataType.Integer, + VYUY = ColorSpace.YCbCr601 | ColorSwizzle.XZY1 | ColorComponent.Y8X8Z8X8 | ColorDataType.Integer, + UYVY = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.Y8X8Z8X8 | ColorDataType.Integer, + YUYV = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.X8Y8X8Z8 | ColorDataType.Integer, + Y8_U8_V8 = ColorSpace.YCbCr601 | ColorSwizzle.XYZ1 | ColorComponent.X8Y8Z8 | ColorDataType.Integer, + V8_U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + U8_V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + Y8_RR = ColorSpace.YCbCr601_RR | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + V8_U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + U8_V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + Y8_ER = ColorSpace.YCbCr601_ER | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + V8_U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V10U10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer, + V12U12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer, + V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + V10_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer, + V12_709 = ColorSpace.YCbCr709 | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer, + U8_V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U10V10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer, + U12V12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer, + U8_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + U10_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer, + U12_709 = ColorSpace.YCbCr709 | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer, + Y8_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Y10_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer, + Y12_709 = ColorSpace.YCbCr709 | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer, + V8_U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0YX0 | ColorComponent.X8Y8 | ColorDataType.Integer, + V8U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0YX0 | ColorComponent.Y8X8 | ColorDataType.Integer, + V10U10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer, + V12U12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer, + V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X8 | ColorDataType.Integer, + V10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer, + V12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer, + U8_V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XY0 | ColorComponent.X8Y8 | ColorDataType.Integer, + U8V8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XY0 | ColorComponent.Y8X8 | ColorDataType.Integer, + U10V10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer, + U12V12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer, + U8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X8 | ColorDataType.Integer, + U10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer, + U12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer, + Y8_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Y10_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer, + Y12_709_ER = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer, + V10U10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y10X10 | ColorDataType.Integer, + V12U12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0ZY0 | ColorComponent.Y12X12 | ColorDataType.Integer, + V10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X10 | ColorDataType.Integer, + V12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._00X0 | ColorComponent.X12 | ColorDataType.Integer, + U10V10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y10X10 | ColorDataType.Integer, + U12V12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0XZ0 | ColorComponent.Y12X12 | ColorDataType.Integer, + U10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X10 | ColorDataType.Integer, + U12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle._0X00 | ColorComponent.X12 | ColorDataType.Integer, + Y10_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X10 | ColorDataType.Integer, + Y12_2020 = ColorSpace.YCbCr709_ER | ColorSwizzle.X000 | ColorComponent.X12 | ColorDataType.Integer, + Bayer8RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Bayer16RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + BayerS16RGGB = ColorSpace.BayerRGGB | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil, + X2Bayer14RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer, + X4Bayer12RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer, + X6Bayer10RGGB = ColorSpace.BayerRGGB | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer, + Bayer8BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Bayer16BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + BayerS16BGGR = ColorSpace.BayerBGGR | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil, + X2Bayer14BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer, + X4Bayer12BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer, + X6Bayer10BGGR = ColorSpace.BayerBGGR | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer, + Bayer8GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Bayer16GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + BayerS16GRBG = ColorSpace.BayerGRBG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil, + X2Bayer14GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer, + X4Bayer12GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer, + X6Bayer10GRBG = ColorSpace.BayerGRBG | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer, + Bayer8GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X8 | ColorDataType.Integer, + Bayer16GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Integer, + BayerS16GBRG = ColorSpace.BayerGBRG | ColorSwizzle.X000 | ColorComponent.X16 | ColorDataType.Stencil, + X2Bayer14GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y2X14 | ColorDataType.Integer, + X4Bayer12GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y4X12 | ColorDataType.Integer, + X6Bayer10GBRG = ColorSpace.BayerGBRG | ColorSwizzle.Y000 | ColorComponent.Y6X10 | ColorDataType.Integer, + XYZ = ColorSpace.XYZ | ColorSwizzle.XYZ1 | ColorComponent.X20Y20Z20 | ColorDataType.Float, +#pragma warning restore IDE0055 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs new file mode 100644 index 00000000..1cc8fecb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + class ColorShift + { + public const int Swizzle = 16; + public const int DataType = 14; + public const int Space = 32; + public const int Component = 8; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs new file mode 100644 index 00000000..7f3f0220 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorSpace : ulong + { + NonColor = 0x0L << ColorShift.Space, + LinearRGBA = 0x1L << ColorShift.Space, + SRGB = 0x2L << ColorShift.Space, + + RGB709 = 0x3L << ColorShift.Space, + LinearRGB709 = 0x4L << ColorShift.Space, + + LinearScRGB = 0x5L << ColorShift.Space, + + RGB2020 = 0x6L << ColorShift.Space, + LinearRGB2020 = 0x7L << ColorShift.Space, + RGB2020_PQ = 0x8L << ColorShift.Space, + + ColorIndex = 0x9L << ColorShift.Space, + + YCbCr601 = 0xAL << ColorShift.Space, + YCbCr601_RR = 0xBL << ColorShift.Space, + YCbCr601_ER = 0xCL << ColorShift.Space, + YCbCr709 = 0xDL << ColorShift.Space, + YCbCr709_ER = 0xEL << ColorShift.Space, + + BayerRGGB = 0x10L << ColorShift.Space, + BayerBGGR = 0x11L << ColorShift.Space, + BayerGRBG = 0x12L << ColorShift.Space, + BayerGBRG = 0x13L << ColorShift.Space, + + XYZ = 0x14L << ColorShift.Space, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs new file mode 100644 index 00000000..0cac6dab --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + enum ColorSwizzle + { +#pragma warning disable IDE0055 // Disable formatting + XYZW = 0x688 << ColorShift.Swizzle, + ZYXW = 0x60a << ColorShift.Swizzle, + WZYX = 0x053 << ColorShift.Swizzle, + YZWX = 0x0d1 << ColorShift.Swizzle, + XYZ1 = 0xa88 << ColorShift.Swizzle, + YZW1 = 0xad1 << ColorShift.Swizzle, + XXX1 = 0xa00 << ColorShift.Swizzle, + XZY1 = 0xa50 << ColorShift.Swizzle, + ZYX1 = 0xa0a << ColorShift.Swizzle, + WZY1 = 0xa53 << ColorShift.Swizzle, + X000 = 0x920 << ColorShift.Swizzle, + Y000 = 0x921 << ColorShift.Swizzle, + XY01 = 0xb08 << ColorShift.Swizzle, + X001 = 0xb20 << ColorShift.Swizzle, + X00X = 0x121 << ColorShift.Swizzle, + X00Y = 0x320 << ColorShift.Swizzle, + _0YX0 = 0x80c << ColorShift.Swizzle, + _0ZY0 = 0x814 << ColorShift.Swizzle, + _0XZ0 = 0x884 << ColorShift.Swizzle, + _0X00 = 0x904 << ColorShift.Swizzle, + _00X0 = 0x824 << ColorShift.Swizzle, + _000X = 0x124 << ColorShift.Swizzle, + _0XY0 = 0x844 << ColorShift.Swizzle, + XXXY = 0x200 << ColorShift.Swizzle, + YYYX = 0x049 << ColorShift.Swizzle, +#pragma warning restore IDE0055 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs new file mode 100644 index 00000000..f7bd3b24 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs @@ -0,0 +1,74 @@ +using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct GraphicBuffer : IFlattenable + { + public GraphicBufferHeader Header; + public NvGraphicBuffer Buffer; + + public readonly int Width => Header.Width; + public readonly int Height => Header.Height; + public readonly PixelFormat Format => Header.Format; + public readonly int Usage => Header.Usage; + + public readonly Rect ToRect() + { + return new Rect(Width, Height); + } + + public void Flatten(Parcel parcel) + { + parcel.WriteUnmanagedType(ref Header); + parcel.WriteUnmanagedType(ref Buffer); + } + + public void Unflatten(Parcel parcel) + { + Header = parcel.ReadUnmanagedType(); + + int expectedSize = Unsafe.SizeOf() / 4; + + if (Header.IntsCount != expectedSize) + { + throw new NotImplementedException($"Unexpected Graphic Buffer ints count (expected 0x{expectedSize:x}, found 0x{Header.IntsCount:x})"); + } + + Buffer = parcel.ReadUnmanagedType(); + } + + public readonly void IncrementNvMapHandleRefCount(ulong pid) + { + NvMapDeviceFile.IncrementMapRefCount(pid, Buffer.NvMapId); + + for (int i = 0; i < NvGraphicBufferSurfaceArray.Length; i++) + { + NvMapDeviceFile.IncrementMapRefCount(pid, Buffer.Surfaces[i].NvMapHandle); + } + } + + public readonly void DecrementNvMapHandleRefCount(ulong pid) + { + NvMapDeviceFile.DecrementMapRefCount(pid, Buffer.NvMapId); + + for (int i = 0; i < NvGraphicBufferSurfaceArray.Length; i++) + { + NvMapDeviceFile.DecrementMapRefCount(pid, Buffer.Surfaces[i].NvMapHandle); + } + } + + public readonly uint GetFlattenedSize() + { + return (uint)Unsafe.SizeOf(); + } + + public readonly uint GetFdCount() + { + return 0; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs new file mode 100644 index 00000000..30cb5c37 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Sequential, Size = 0x28, Pack = 1)] + struct GraphicBufferHeader + { + public int Magic; + public int Width; + public int Height; + public int Stride; + public PixelFormat Format; + public int Usage; + + public int Pid; + public int RefCount; + + public int FdsCount; + public int IntsCount; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs new file mode 100644 index 00000000..51daac81 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs @@ -0,0 +1,41 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Explicit, Size = 0x144, Pack = 1)] + struct NvGraphicBuffer + { + [FieldOffset(0x4)] + public int NvMapId; + + [FieldOffset(0xC)] + public int Magic; + + [FieldOffset(0x10)] + public int Pid; + + [FieldOffset(0x14)] + public int Type; + + [FieldOffset(0x18)] + public int Usage; + + [FieldOffset(0x1C)] + public int PixelFormat; + + [FieldOffset(0x20)] + public int ExternalPixelFormat; + + [FieldOffset(0x24)] + public int Stride; + + [FieldOffset(0x28)] + public int FrameBufferSize; + + [FieldOffset(0x2C)] + public int PlanesCount; + + [FieldOffset(0x34)] + public NvGraphicBufferSurfaceArray Surfaces; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs new file mode 100644 index 00000000..4a427b42 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs @@ -0,0 +1,44 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Explicit, Size = 0x58)] + struct NvGraphicBufferSurface + { + [FieldOffset(0)] + public uint Width; + + [FieldOffset(0x4)] + public uint Height; + + [FieldOffset(0x8)] + public ColorFormat ColorFormat; + + [FieldOffset(0x10)] + public int Layout; + + [FieldOffset(0x14)] + public int Pitch; + + [FieldOffset(0x18)] + public int NvMapHandle; + + [FieldOffset(0x1C)] + public int Offset; + + [FieldOffset(0x20)] + public int Kind; + + [FieldOffset(0x24)] + public int BlockHeightLog2; + + [FieldOffset(0x28)] + public int ScanFormat; + + [FieldOffset(0x30)] + public long Flags; + + [FieldOffset(0x38)] + public long Size; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs new file mode 100644 index 00000000..94d780a9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Explicit)] + struct NvGraphicBufferSurfaceArray + { + [FieldOffset(0x0)] + private NvGraphicBufferSurface Surface0; + + [FieldOffset(0x58)] + private NvGraphicBufferSurface Surface1; + + [FieldOffset(0xb0)] + private NvGraphicBufferSurface Surface2; + + public readonly NvGraphicBufferSurface this[int index] + { + get + { + return index switch + { + 0 => Surface0, + 1 => Surface1, + 2 => Surface2, + _ => throw new IndexOutOfRangeException(), + }; + } + } + + public static int Length => 3; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs new file mode 100644 index 00000000..d287fab0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs @@ -0,0 +1,71 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + struct Rect : IEquatable + { + public int Left; + public int Top; + public int Right; + public int Bottom; + + public readonly int Width => Right - Left; + public readonly int Height => Bottom - Top; + + public Rect(int width, int height) + { + Left = 0; + Top = 0; + Right = width; + Bottom = height; + } + + public readonly bool IsEmpty() + { + return Width <= 0 || Height <= 0; + } + + public bool Intersect(Rect other, out Rect result) + { + result = new Rect + { + Left = Math.Max(Left, other.Left), + Top = Math.Max(Top, other.Top), + Right = Math.Min(Right, other.Right), + Bottom = Math.Min(Bottom, other.Bottom), + }; + + return !result.IsEmpty(); + } + + public void MakeInvalid() + { + Right = -1; + Bottom = -1; + } + + public static bool operator ==(Rect x, Rect y) + { + return x.Equals(y); + } + + public static bool operator !=(Rect x, Rect y) + { + return !x.Equals(y); + } + + public readonly override bool Equals(object obj) + { + return obj is Rect rect && Equals(rect); + } + + public readonly bool Equals(Rect cmpObj) + { + return Left == cmpObj.Left && Top == cmpObj.Top && Right == cmpObj.Right && Bottom == cmpObj.Bottom; + } + + public readonly override int GetHashCode() => HashCode.Combine(Left, Top, Right, Bottom); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs new file mode 100644 index 00000000..4684d4dc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class EphemeralNetworkSystemClockContextWriter : SystemClockContextUpdateCallback + { + protected override ResultCode Update() + { + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs new file mode 100644 index 00000000..5aa907cd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class EphemeralNetworkSystemClockCore : SystemClockCore + { + public EphemeralNetworkSystemClockCore(SteadyClockCore steadyClockCore) : base(steadyClockCore) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs new file mode 100644 index 00000000..7b1c1ada --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class LocalSystemClockContextWriter : SystemClockContextUpdateCallback + { + private readonly TimeSharedMemory _sharedMemory; + + public LocalSystemClockContextWriter(TimeSharedMemory sharedMemory) + { + _sharedMemory = sharedMemory; + } + + protected override ResultCode Update() + { + _sharedMemory.UpdateLocalSystemClockContext(Context); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs new file mode 100644 index 00000000..99a192f4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class NetworkSystemClockContextWriter : SystemClockContextUpdateCallback + { + private readonly TimeSharedMemory _sharedMemory; + + public NetworkSystemClockContextWriter(TimeSharedMemory sharedMemory) + { + _sharedMemory = sharedMemory; + } + + protected override ResultCode Update() + { + _sharedMemory.UpdateNetworkSystemClockContext(Context); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs new file mode 100644 index 00000000..cf1e03f3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardLocalSystemClockCore : SystemClockCore + { + public StandardLocalSystemClockCore(StandardSteadyClockCore steadyClockCore) : base(steadyClockCore) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs new file mode 100644 index 00000000..12059633 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs @@ -0,0 +1,36 @@ +using Ryujinx.Cpu; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardNetworkSystemClockCore : SystemClockCore + { + private TimeSpanType _standardNetworkClockSufficientAccuracy; + + public StandardNetworkSystemClockCore(StandardSteadyClockCore steadyClockCore) : base(steadyClockCore) + { + _standardNetworkClockSufficientAccuracy = new TimeSpanType(0); + } + + public bool IsStandardNetworkSystemClockAccuracySufficient(ITickSource tickSource) + { + SteadyClockCore steadyClockCore = GetSteadyClockCore(); + SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(tickSource); + + bool isStandardNetworkClockSufficientAccuracy = false; + + ResultCode result = GetClockContext(tickSource, out SystemClockContext context); + + if (result == ResultCode.Success && context.SteadyTimePoint.GetSpanBetween(currentTimePoint, out long outSpan) == ResultCode.Success) + { + isStandardNetworkClockSufficientAccuracy = outSpan * 1000000000 < _standardNetworkClockSufficientAccuracy.NanoSeconds; + } + + return isStandardNetworkClockSufficientAccuracy; + } + + public void SetStandardNetworkClockSufficientAccuracy(TimeSpanType standardNetworkClockSufficientAccuracy) + { + _standardNetworkClockSufficientAccuracy = standardNetworkClockSufficientAccuracy; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs new file mode 100644 index 00000000..bc785ca7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs @@ -0,0 +1,72 @@ +using Ryujinx.Cpu; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardSteadyClockCore : SteadyClockCore + { + private TimeSpanType _setupValue; + private TimeSpanType _testOffset; + private TimeSpanType _internalOffset; + private TimeSpanType _cachedRawTimePoint; + + public StandardSteadyClockCore() + { + _setupValue = TimeSpanType.Zero; + _testOffset = TimeSpanType.Zero; + _internalOffset = TimeSpanType.Zero; + _cachedRawTimePoint = TimeSpanType.Zero; + } + + public override SteadyClockTimePoint GetTimePoint(ITickSource tickSource) + { + SteadyClockTimePoint result = new() + { + TimePoint = GetCurrentRawTimePoint(tickSource).ToSeconds(), + ClockSourceId = GetClockSourceId(), + }; + + return result; + } + + public override TimeSpanType GetTestOffset() + { + return _testOffset; + } + + public override void SetTestOffset(TimeSpanType testOffset) + { + _testOffset = testOffset; + } + + public override TimeSpanType GetInternalOffset() + { + return _internalOffset; + } + + public override void SetInternalOffset(TimeSpanType internalOffset) + { + _internalOffset = internalOffset; + } + + public override TimeSpanType GetCurrentRawTimePoint(ITickSource tickSource) + { + TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(tickSource.Counter, tickSource.Frequency); + + TimeSpanType rawTimePoint = new(_setupValue.NanoSeconds + ticksTimeSpan.NanoSeconds); + + if (rawTimePoint.NanoSeconds < _cachedRawTimePoint.NanoSeconds) + { + rawTimePoint.NanoSeconds = _cachedRawTimePoint.NanoSeconds; + } + + _cachedRawTimePoint = rawTimePoint; + + return rawTimePoint; + } + + public void SetSetupValue(TimeSpanType setupValue) + { + _setupValue = setupValue; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs new file mode 100644 index 00000000..147faa84 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs @@ -0,0 +1,108 @@ +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class StandardUserSystemClockCore : SystemClockCore + { + private readonly StandardLocalSystemClockCore _localSystemClockCore; + private readonly StandardNetworkSystemClockCore _networkSystemClockCore; + private bool _autoCorrectionEnabled; + private SteadyClockTimePoint _autoCorrectionTime; + private KEvent _autoCorrectionEvent; + + public StandardUserSystemClockCore(StandardLocalSystemClockCore localSystemClockCore, StandardNetworkSystemClockCore networkSystemClockCore) : base(localSystemClockCore.GetSteadyClockCore()) + { + _localSystemClockCore = localSystemClockCore; + _networkSystemClockCore = networkSystemClockCore; + _autoCorrectionEnabled = false; + _autoCorrectionTime = SteadyClockTimePoint.GetRandom(); + _autoCorrectionEvent = null; + } + + protected override ResultCode Flush(SystemClockContext context) + { + // As UserSystemClock isn't a real system clock, this shouldn't happens. + throw new NotImplementedException(); + } + + public override ResultCode GetClockContext(ITickSource tickSource, out SystemClockContext context) + { + ResultCode result = ApplyAutomaticCorrection(tickSource, false); + + context = new SystemClockContext(); + + if (result == ResultCode.Success) + { + return _localSystemClockCore.GetClockContext(tickSource, out context); + } + + return result; + } + + public override ResultCode SetClockContext(SystemClockContext context) + { + return ResultCode.NotImplemented; + } + + private ResultCode ApplyAutomaticCorrection(ITickSource tickSource, bool autoCorrectionEnabled) + { + ResultCode result = ResultCode.Success; + + if (_autoCorrectionEnabled != autoCorrectionEnabled && _networkSystemClockCore.IsClockSetup(tickSource)) + { + result = _networkSystemClockCore.GetClockContext(tickSource, out SystemClockContext context); + + if (result == ResultCode.Success) + { + _localSystemClockCore.SetClockContext(context); + } + } + + return result; + } + + internal void CreateAutomaticCorrectionEvent(Horizon system) + { + _autoCorrectionEvent = new KEvent(system.KernelContext); + } + + public ResultCode SetAutomaticCorrectionEnabled(ITickSource tickSource, bool autoCorrectionEnabled) + { + ResultCode result = ApplyAutomaticCorrection(tickSource, autoCorrectionEnabled); + + if (result == ResultCode.Success) + { + _autoCorrectionEnabled = autoCorrectionEnabled; + } + + return result; + } + + public bool IsAutomaticCorrectionEnabled() + { + return _autoCorrectionEnabled; + } + + public KReadableEvent GetAutomaticCorrectionReadableEvent() + { + return _autoCorrectionEvent.ReadableEvent; + } + + public void SetAutomaticCorrectionUpdatedTime(SteadyClockTimePoint steadyClockTimePoint) + { + _autoCorrectionTime = steadyClockTimePoint; + } + + public SteadyClockTimePoint GetAutomaticCorrectionUpdatedTime() + { + return _autoCorrectionTime; + } + + public void SignalAutomaticCorrectionEvent() + { + _autoCorrectionEvent.WritableEvent.Signal(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs new file mode 100644 index 00000000..95cf2a89 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs @@ -0,0 +1,98 @@ +using Ryujinx.Common.Utilities; +using Ryujinx.Cpu; +using System; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + abstract class SteadyClockCore + { + private UInt128 _clockSourceId; + private bool _isRtcResetDetected; + private bool _isInitialized; + + public SteadyClockCore() + { + _clockSourceId = UInt128Utils.CreateRandom(); + _isRtcResetDetected = false; + _isInitialized = false; + } + + public UInt128 GetClockSourceId() + { + return _clockSourceId; + } + + public void SetClockSourceId(UInt128 clockSourceId) + { + _clockSourceId = clockSourceId; + } + + public void SetRtcReset() + { + _isRtcResetDetected = true; + } + + public virtual TimeSpanType GetTestOffset() + { + return new TimeSpanType(0); + } + + public virtual void SetTestOffset(TimeSpanType testOffset) { } + + public ResultCode GetRtcValue(out ulong rtcValue) + { + rtcValue = 0; + + return ResultCode.NotImplemented; + } + + public bool IsRtcResetDetected() + { + return _isRtcResetDetected; + } + + public ResultCode GetSetupResultValue() + { + return ResultCode.Success; + } + + public virtual TimeSpanType GetInternalOffset() + { + return new TimeSpanType(0); + } + + public virtual void SetInternalOffset(TimeSpanType internalOffset) { } + + public virtual SteadyClockTimePoint GetTimePoint(ITickSource tickSource) + { + throw new NotImplementedException(); + } + + public virtual TimeSpanType GetCurrentRawTimePoint(ITickSource tickSource) + { + SteadyClockTimePoint timePoint = GetTimePoint(tickSource); + + return TimeSpanType.FromSeconds(timePoint.TimePoint); + } + + public SteadyClockTimePoint GetCurrentTimePoint(ITickSource tickSource) + { + SteadyClockTimePoint result = GetTimePoint(tickSource); + + result.TimePoint += GetTestOffset().ToSeconds(); + result.TimePoint += GetInternalOffset().ToSeconds(); + + return result; + } + + public bool IsInitialized() + { + return _isInitialized; + } + + public void MarkInitialized() + { + _isInitialized = true; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs new file mode 100644 index 00000000..119096dc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs @@ -0,0 +1,71 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + abstract class SystemClockContextUpdateCallback + { + private readonly List _operationEventList; + protected SystemClockContext Context; + private bool _hasContext; + + public SystemClockContextUpdateCallback() + { + _operationEventList = new List(); + Context = new SystemClockContext(); + _hasContext = false; + } + + private bool NeedUpdate(SystemClockContext context) + { + if (_hasContext) + { + return Context.Offset != context.Offset || Context.SteadyTimePoint.ClockSourceId != context.SteadyTimePoint.ClockSourceId; + } + + return true; + } + + public void RegisterOperationEvent(KWritableEvent writableEvent) + { + Monitor.Enter(_operationEventList); + _operationEventList.Add(writableEvent); + Monitor.Exit(_operationEventList); + } + + private void BroadcastOperationEvent() + { + Monitor.Enter(_operationEventList); + + foreach (KWritableEvent e in _operationEventList) + { + e.Signal(); + } + + Monitor.Exit(_operationEventList); + } + + protected abstract ResultCode Update(); + + public ResultCode Update(SystemClockContext context) + { + ResultCode result = ResultCode.Success; + + if (NeedUpdate(context)) + { + Context = context; + _hasContext = true; + + result = Update(); + + if (result == ResultCode.Success) + { + BroadcastOperationEvent(); + } + } + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs new file mode 100644 index 00000000..c4638051 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs @@ -0,0 +1,141 @@ +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + abstract class SystemClockCore + { + private readonly SteadyClockCore _steadyClockCore; + private SystemClockContext _context; + private bool _isInitialized; + private SystemClockContextUpdateCallback _systemClockContextUpdateCallback; + + public SystemClockCore(SteadyClockCore steadyClockCore) + { + _steadyClockCore = steadyClockCore; + _context = new SystemClockContext(); + _isInitialized = false; + + _context.SteadyTimePoint.ClockSourceId = steadyClockCore.GetClockSourceId(); + _systemClockContextUpdateCallback = null; + } + + public virtual SteadyClockCore GetSteadyClockCore() + { + return _steadyClockCore; + } + + public ResultCode GetCurrentTime(ITickSource tickSource, out long posixTime) + { + posixTime = 0; + + SteadyClockTimePoint currentTimePoint = _steadyClockCore.GetCurrentTimePoint(tickSource); + + ResultCode result = GetClockContext(tickSource, out SystemClockContext clockContext); + + if (result == ResultCode.Success) + { + result = ResultCode.TimeMismatch; + + if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId) + { + posixTime = clockContext.Offset + currentTimePoint.TimePoint; + + result = 0; + } + } + + return result; + } + + public ResultCode SetCurrentTime(ITickSource tickSource, long posixTime) + { + SteadyClockTimePoint currentTimePoint = _steadyClockCore.GetCurrentTimePoint(tickSource); + + SystemClockContext clockContext = new() + { + Offset = posixTime - currentTimePoint.TimePoint, + SteadyTimePoint = currentTimePoint, + }; + + ResultCode result = SetClockContext(clockContext); + + if (result == ResultCode.Success) + { + result = Flush(clockContext); + } + + return result; + } + + public virtual ResultCode GetClockContext(ITickSource tickSource, out SystemClockContext context) + { + context = _context; + + return ResultCode.Success; + } + + public virtual ResultCode SetClockContext(SystemClockContext context) + { + _context = context; + + return ResultCode.Success; + } + + protected virtual ResultCode Flush(SystemClockContext context) + { + if (_systemClockContextUpdateCallback == null) + { + return ResultCode.Success; + } + + return _systemClockContextUpdateCallback.Update(context); + } + + public void SetUpdateCallbackInstance(SystemClockContextUpdateCallback systemClockContextUpdateCallback) + { + _systemClockContextUpdateCallback = systemClockContextUpdateCallback; + } + + public void RegisterOperationEvent(KWritableEvent writableEvent) + { + _systemClockContextUpdateCallback?.RegisterOperationEvent(writableEvent); + } + + public ResultCode SetSystemClockContext(SystemClockContext context) + { + ResultCode result = SetClockContext(context); + + if (result == ResultCode.Success) + { + result = Flush(context); + } + + return result; + } + + public bool IsInitialized() + { + return _isInitialized; + } + + public void MarkInitialized() + { + _isInitialized = true; + } + + public bool IsClockSetup(ITickSource tickSource) + { + ResultCode result = GetClockContext(tickSource, out SystemClockContext context); + + if (result == ResultCode.Success) + { + SteadyClockTimePoint steadyClockTimePoint = _steadyClockCore.GetCurrentTimePoint(tickSource); + + return steadyClockTimePoint.ClockSourceId == context.SteadyTimePoint.ClockSourceId; + } + + return false; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs new file mode 100644 index 00000000..5c10c693 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs @@ -0,0 +1,24 @@ +using Ryujinx.Cpu; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + class TickBasedSteadyClockCore : SteadyClockCore + { + public TickBasedSteadyClockCore() { } + + public override SteadyClockTimePoint GetTimePoint(ITickSource tickSource) + { + SteadyClockTimePoint result = new() + { + TimePoint = 0, + ClockSourceId = GetClockSourceId(), + }; + + TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(tickSource.Counter, tickSource.Frequency); + + result.TimePoint = ticksTimeSpan.ToSeconds(); + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs new file mode 100644 index 00000000..4383fc82 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs @@ -0,0 +1,50 @@ +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + [StructLayout(LayoutKind.Sequential, Size = 0xD0)] + struct ClockSnapshot + { + public SystemClockContext UserContext; + public SystemClockContext NetworkContext; + public long UserTime; + public long NetworkTime; + public CalendarTime UserCalendarTime; + public CalendarTime NetworkCalendarTime; + public CalendarAdditionalInfo UserCalendarAdditionalTime; + public CalendarAdditionalInfo NetworkCalendarAdditionalTime; + public SteadyClockTimePoint SteadyClockTimePoint; + + private LocationNameStorageHolder _locationName; + + public Span LocationName => MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref _locationName, LocationNameStorageHolder.Size)); + + [MarshalAs(UnmanagedType.I1)] + public bool IsAutomaticCorrectionEnabled; + public byte Type; + public ushort Unknown; + + + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)] + private struct LocationNameStorageHolder + { + public const int Size = 0x24; + } + + public static ResultCode GetCurrentTime(out long currentTime, SteadyClockTimePoint steadyClockTimePoint, SystemClockContext context) + { + currentTime = 0; + + if (steadyClockTimePoint.ClockSourceId == context.SteadyTimePoint.ClockSourceId) + { + currentTime = steadyClockTimePoint.TimePoint + context.Offset; + + return ResultCode.Success; + } + + return ResultCode.TimeMismatch; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ContinuousAdjustmentTimePoint.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ContinuousAdjustmentTimePoint.cs new file mode 100644 index 00000000..df7ee823 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ContinuousAdjustmentTimePoint.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ContinuousAdjustmentTimePoint + { + public ulong ClockOffset; + public long Multiplier; + public long DivisorLog2; + public SystemClockContext Context; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs new file mode 100644 index 00000000..e4878483 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs @@ -0,0 +1,43 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SteadyClockTimePoint + { + public long TimePoint; + public UInt128 ClockSourceId; + + public readonly ResultCode GetSpanBetween(SteadyClockTimePoint other, out long outSpan) + { + outSpan = 0; + + if (ClockSourceId == other.ClockSourceId) + { + try + { + outSpan = checked(other.TimePoint - TimePoint); + + return ResultCode.Success; + } + catch (OverflowException) + { + return ResultCode.Overflow; + } + } + + return ResultCode.Overflow; + } + + public static SteadyClockTimePoint GetRandom() + { + return new SteadyClockTimePoint + { + TimePoint = 0, + ClockSourceId = UInt128Utils.CreateRandom(), + }; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs new file mode 100644 index 00000000..cb5f05e4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SystemClockContext + { + public long Offset; + public SteadyClockTimePoint SteadyTimePoint; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs new file mode 100644 index 00000000..c0b4ad29 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Clock +{ + [StructLayout(LayoutKind.Sequential)] + struct TimeSpanType + { + private const long NanoSecondsPerSecond = 1000000000; + + public static readonly TimeSpanType Zero = new(0); + + public long NanoSeconds; + + public TimeSpanType(long nanoSeconds) + { + NanoSeconds = nanoSeconds; + } + + public readonly long ToSeconds() + { + return NanoSeconds / NanoSecondsPerSecond; + } + + public readonly TimeSpanType AddSeconds(long seconds) + { + return new TimeSpanType(NanoSeconds + (seconds * NanoSecondsPerSecond)); + } + + public readonly bool IsDaylightSavingTime() + { + return DateTime.UnixEpoch.AddSeconds(ToSeconds()).ToLocalTime().IsDaylightSavingTime(); + } + + public static TimeSpanType FromSeconds(long seconds) + { + return new TimeSpanType(seconds * NanoSecondsPerSecond); + } + + public static TimeSpanType FromTimeSpan(TimeSpan timeSpan) + { + return new TimeSpanType((long)(timeSpan.TotalMilliseconds * 1000000)); + } + + public static TimeSpanType FromTicks(ulong ticks, ulong frequency) + { + return FromSeconds((long)ticks / (long)frequency); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/IAlarmService.cs b/src/Ryujinx.HLE/HOS/Services/Time/IAlarmService.cs new file mode 100644 index 00000000..38cd3eb8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/IAlarmService.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:al")] // 9.0.0+ + class IAlarmService : IpcService + { + public IAlarmService(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs b/src/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs new file mode 100644 index 00000000..2f67918b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:p")] // 9.0.0+ + class IPowerStateRequestHandler : IpcService + { + public IPowerStateRequestHandler(ServiceCtx context) { } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs b/src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs new file mode 100644 index 00000000..eaddf9cf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs @@ -0,0 +1,186 @@ +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Services.Pcv.Bpc; +using Ryujinx.HLE.HOS.Services.Settings; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.StaticService; +using System; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:a", TimePermissions.Admin)] + [Service("time:r", TimePermissions.Repair)] + [Service("time:u", TimePermissions.User)] + class IStaticServiceForGlue : IpcService + { + private readonly IStaticServiceForPsc _inner; + private readonly TimePermissions _permissions; + + public IStaticServiceForGlue(ServiceCtx context, TimePermissions permissions) : base(context.Device.System.TimeServer) + { + _permissions = permissions; + _inner = new IStaticServiceForPsc(context, permissions); + _inner.TrySetServer(Server); + _inner.SetParent(this); + } + + [CommandCmif(0)] + // GetStandardUserSystemClock() -> object + public ResultCode GetStandardUserSystemClock(ServiceCtx context) + { + return _inner.GetStandardUserSystemClock(context); + } + + [CommandCmif(1)] + // GetStandardNetworkSystemClock() -> object + public ResultCode GetStandardNetworkSystemClock(ServiceCtx context) + { + return _inner.GetStandardNetworkSystemClock(context); + } + + [CommandCmif(2)] + // GetStandardSteadyClock() -> object + public ResultCode GetStandardSteadyClock(ServiceCtx context) + { + return _inner.GetStandardSteadyClock(context); + } + + [CommandCmif(3)] + // GetTimeZoneService() -> object + public ResultCode GetTimeZoneService(ServiceCtx context) + { + MakeObject(context, new ITimeZoneServiceForGlue(TimeManager.Instance.TimeZone, (_permissions & TimePermissions.TimeZoneWritableMask) != 0)); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetStandardLocalSystemClock() -> object + public ResultCode GetStandardLocalSystemClock(ServiceCtx context) + { + return _inner.GetStandardLocalSystemClock(context); + } + + [CommandCmif(5)] // 4.0.0+ + // GetEphemeralNetworkSystemClock() -> object + public ResultCode GetEphemeralNetworkSystemClock(ServiceCtx context) + { + return _inner.GetEphemeralNetworkSystemClock(context); + } + + [CommandCmif(20)] // 6.0.0+ + // GetSharedMemoryNativeHandle() -> handle + public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context) + { + return _inner.GetSharedMemoryNativeHandle(context); + } + + [CommandCmif(50)] // 4.0.0+ + // SetStandardSteadyClockInternalOffset(nn::TimeSpanType internal_offset) + public ResultCode SetStandardSteadyClockInternalOffset(ServiceCtx context) + { + if ((_permissions & TimePermissions.SteadyClockWritableMask) == 0) + { + return ResultCode.PermissionDenied; + } + +#pragma warning disable IDE0059 // Remove unnecessary value assignment + TimeSpanType internalOffset = context.RequestData.ReadStruct(); +#pragma warning restore IDE0059 + + // TODO: set:sys SetExternalSteadyClockInternalOffset(internalOffset.ToSeconds()) + + return ResultCode.Success; + } + + [CommandCmif(51)] // 9.0.0+ + // GetStandardSteadyClockRtcValue() -> u64 + public ResultCode GetStandardSteadyClockRtcValue(ServiceCtx context) + { + ResultCode result = (ResultCode)IRtcManager.GetExternalRtcValue(out ulong rtcValue); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(rtcValue); + } + + return result; + } + + [CommandCmif(100)] + // IsStandardUserSystemClockAutomaticCorrectionEnabled() -> bool + public ResultCode IsStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) + { + return _inner.IsStandardUserSystemClockAutomaticCorrectionEnabled(context); + } + + [CommandCmif(101)] + // SetStandardUserSystemClockAutomaticCorrectionEnabled(b8) + public ResultCode SetStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) + { + return _inner.SetStandardUserSystemClockAutomaticCorrectionEnabled(context); + } + + [CommandCmif(102)] // 5.0.0+ + // GetStandardUserSystemClockInitialYear() -> u32 + public ResultCode GetStandardUserSystemClockInitialYear(ServiceCtx context) + { + if (!NxSettings.Settings.TryGetValue("time!standard_user_clock_initial_year", out object standardUserSystemClockInitialYear)) + { + throw new InvalidOperationException("standard_user_clock_initial_year isn't defined in system settings!"); + } + + context.ResponseData.Write((int)standardUserSystemClockInitialYear); + + return ResultCode.Success; + } + + [CommandCmif(200)] // 3.0.0+ + // IsStandardNetworkSystemClockAccuracySufficient() -> bool + public ResultCode IsStandardNetworkSystemClockAccuracySufficient(ServiceCtx context) + { + return _inner.IsStandardNetworkSystemClockAccuracySufficient(context); + } + + [CommandCmif(201)] // 6.0.0+ + // GetStandardUserSystemClockAutomaticCorrectionUpdatedTime() -> nn::time::SteadyClockTimePoint + public ResultCode GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(ServiceCtx context) + { + return _inner.GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(context); + } + + [CommandCmif(300)] // 4.0.0+ + // CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> s64 + public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context) + { + return _inner.CalculateMonotonicSystemClockBaseTimePoint(context); + } + + [CommandCmif(400)] // 4.0.0+ + // GetClockSnapshot(u8) -> buffer + public ResultCode GetClockSnapshot(ServiceCtx context) + { + return _inner.GetClockSnapshot(context); + } + + [CommandCmif(401)] // 4.0.0+ + // GetClockSnapshotFromSystemClockContext(u8, nn::time::SystemClockContext, nn::time::SystemClockContext) -> buffer + public ResultCode GetClockSnapshotFromSystemClockContext(ServiceCtx context) + { + return _inner.GetClockSnapshotFromSystemClockContext(context); + } + + [CommandCmif(500)] // 4.0.0+ + // CalculateStandardUserSystemClockDifferenceByUser(buffer, buffer) -> nn::TimeSpanType + public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context) + { + return _inner.CalculateStandardUserSystemClockDifferenceByUser(context); + } + + [CommandCmif(501)] // 4.0.0+ + // CalculateSpanBetween(buffer, buffer) -> nn::TimeSpanType + public ResultCode CalculateSpanBetween(ServiceCtx context) + { + return _inner.CalculateSpanBetween(context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs b/src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs new file mode 100644 index 00000000..a6b33e4a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs @@ -0,0 +1,432 @@ +using Ryujinx.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.StaticService; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using Ryujinx.Horizon.Common; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:s", TimePermissions.System)] + [Service("time:su", TimePermissions.SystemUpdate)] + class IStaticServiceForPsc : IpcService + { + private readonly TimeManager _timeManager; + private readonly TimePermissions _permissions; + + private int _timeSharedMemoryNativeHandle = 0; + + public IStaticServiceForPsc(ServiceCtx context, TimePermissions permissions) : this(TimeManager.Instance, permissions) { } + + public IStaticServiceForPsc(TimeManager manager, TimePermissions permissions) + { + _permissions = permissions; + _timeManager = manager; + } + + [CommandCmif(0)] + // GetStandardUserSystemClock() -> object + public ResultCode GetStandardUserSystemClock(ServiceCtx context) + { + MakeObject(context, new ISystemClock(_timeManager.StandardUserSystemClock, + (_permissions & TimePermissions.UserSystemClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetStandardNetworkSystemClock() -> object + public ResultCode GetStandardNetworkSystemClock(ServiceCtx context) + { + MakeObject(context, new ISystemClock(_timeManager.StandardNetworkSystemClock, + (_permissions & TimePermissions.NetworkSystemClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetStandardSteadyClock() -> object + public ResultCode GetStandardSteadyClock(ServiceCtx context) + { + MakeObject(context, new ISteadyClock(_timeManager.StandardSteadyClock, + (_permissions & TimePermissions.SteadyClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetTimeZoneService() -> object + public ResultCode GetTimeZoneService(ServiceCtx context) + { + MakeObject(context, new ITimeZoneServiceForPsc(_timeManager.TimeZone.Manager, + (_permissions & TimePermissions.TimeZoneWritableMask) != 0)); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetStandardLocalSystemClock() -> object + public ResultCode GetStandardLocalSystemClock(ServiceCtx context) + { + MakeObject(context, new ISystemClock(_timeManager.StandardLocalSystemClock, + (_permissions & TimePermissions.LocalSystemClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [CommandCmif(5)] // 4.0.0+ + // GetEphemeralNetworkSystemClock() -> object + public ResultCode GetEphemeralNetworkSystemClock(ServiceCtx context) + { + MakeObject(context, new ISystemClock(_timeManager.StandardNetworkSystemClock, + (_permissions & TimePermissions.NetworkSystemClockWritableMask) != 0, + (_permissions & TimePermissions.BypassUninitialized) != 0)); + + return ResultCode.Success; + } + + [CommandCmif(20)] // 6.0.0+ + // GetSharedMemoryNativeHandle() -> handle + public ResultCode GetSharedMemoryNativeHandle(ServiceCtx context) + { + if (_timeSharedMemoryNativeHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_timeManager.SharedMemory.GetSharedMemory(), out _timeSharedMemoryNativeHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_timeSharedMemoryNativeHandle); + + return ResultCode.Success; + } + + [CommandCmif(50)] // 4.0.0+ + // SetStandardSteadyClockInternalOffset(nn::TimeSpanType internal_offset) + public ResultCode SetStandardSteadyClockInternalOffset(ServiceCtx context) + { + // This is only implemented in glue's StaticService. + return ResultCode.NotImplemented; + } + + [CommandCmif(51)] // 9.0.0+ + // GetStandardSteadyClockRtcValue() -> u64 + public ResultCode GetStandardSteadyClockRtcValue(ServiceCtx context) + { + // This is only implemented in glue's StaticService. + return ResultCode.NotImplemented; + } + + [CommandCmif(100)] + // IsStandardUserSystemClockAutomaticCorrectionEnabled() -> bool + public ResultCode IsStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) + { + StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock; + + if (!userClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.Write(userClock.IsAutomaticCorrectionEnabled()); + + return ResultCode.Success; + } + + [CommandCmif(101)] + // SetStandardUserSystemClockAutomaticCorrectionEnabled(b8) + public ResultCode SetStandardUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context) + { + SteadyClockCore steadyClock = _timeManager.StandardSteadyClock; + StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock; + + if (!userClock.IsInitialized() || !steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + if ((_permissions & TimePermissions.UserSystemClockWritableMask) == 0) + { + return ResultCode.PermissionDenied; + } + + bool autoCorrectionEnabled = context.RequestData.ReadBoolean(); + + ITickSource tickSource = context.Device.System.TickSource; + + ResultCode result = userClock.SetAutomaticCorrectionEnabled(tickSource, autoCorrectionEnabled); + + if (result == ResultCode.Success) + { + _timeManager.SharedMemory.SetAutomaticCorrectionEnabled(autoCorrectionEnabled); + + SteadyClockTimePoint currentTimePoint = userClock.GetSteadyClockCore().GetCurrentTimePoint(tickSource); + + userClock.SetAutomaticCorrectionUpdatedTime(currentTimePoint); + userClock.SignalAutomaticCorrectionEvent(); + } + + return result; + } + + [CommandCmif(102)] // 5.0.0+ + // GetStandardUserSystemClockInitialYear() -> u32 + public ResultCode GetStandardUserSystemClockInitialYear(ServiceCtx context) + { + // This is only implemented in glue's StaticService. + return ResultCode.NotImplemented; + } + + [CommandCmif(200)] // 3.0.0+ + // IsStandardNetworkSystemClockAccuracySufficient() -> bool + public ResultCode IsStandardNetworkSystemClockAccuracySufficient(ServiceCtx context) + { + ITickSource tickSource = context.Device.System.TickSource; + + context.ResponseData.Write(_timeManager.StandardNetworkSystemClock.IsStandardNetworkSystemClockAccuracySufficient(tickSource)); + + return ResultCode.Success; + } + + [CommandCmif(201)] // 6.0.0+ + // GetStandardUserSystemClockAutomaticCorrectionUpdatedTime() -> nn::time::SteadyClockTimePoint + public ResultCode GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(ServiceCtx context) + { + StandardUserSystemClockCore userClock = _timeManager.StandardUserSystemClock; + + if (!userClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.WriteStruct(userClock.GetAutomaticCorrectionUpdatedTime()); + + return ResultCode.Success; + } + + [CommandCmif(300)] // 4.0.0+ + // CalculateMonotonicSystemClockBaseTimePoint(nn::time::SystemClockContext) -> s64 + public ResultCode CalculateMonotonicSystemClockBaseTimePoint(ServiceCtx context) + { + SteadyClockCore steadyClock = _timeManager.StandardSteadyClock; + + if (!steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + ITickSource tickSource = context.Device.System.TickSource; + + SystemClockContext otherContext = context.RequestData.ReadStruct(); + SteadyClockTimePoint currentTimePoint = steadyClock.GetCurrentTimePoint(tickSource); + + ResultCode result = ResultCode.TimeMismatch; + + if (currentTimePoint.ClockSourceId == otherContext.SteadyTimePoint.ClockSourceId) + { + TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(tickSource.Counter, tickSource.Frequency); + long baseTimePoint = otherContext.Offset + currentTimePoint.TimePoint - ticksTimeSpan.ToSeconds(); + + context.ResponseData.Write(baseTimePoint); + + result = ResultCode.Success; + } + + return result; + } + + [CommandCmif(400)] // 4.0.0+ + // GetClockSnapshot(u8) -> buffer + public ResultCode GetClockSnapshot(ServiceCtx context) + { + byte type = context.RequestData.ReadByte(); + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf()); + + ITickSource tickSource = context.Device.System.TickSource; + + ResultCode result = _timeManager.StandardUserSystemClock.GetClockContext(tickSource, out SystemClockContext userContext); + + if (result == ResultCode.Success) + { + result = _timeManager.StandardNetworkSystemClock.GetClockContext(tickSource, out SystemClockContext networkContext); + + if (result == ResultCode.Success) + { + result = GetClockSnapshotFromSystemClockContextInternal(tickSource, userContext, networkContext, type, out ClockSnapshot clockSnapshot); + + if (result == ResultCode.Success) + { + WriteClockSnapshotFromBuffer(context, context.Request.RecvListBuff[0], clockSnapshot); + } + } + } + + return result; + } + + [CommandCmif(401)] // 4.0.0+ + // GetClockSnapshotFromSystemClockContext(u8, nn::time::SystemClockContext, nn::time::SystemClockContext) -> buffer + public ResultCode GetClockSnapshotFromSystemClockContext(ServiceCtx context) + { + byte type = context.RequestData.ReadByte(); + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Unsafe.SizeOf()); + + context.RequestData.BaseStream.Position += 7; + + SystemClockContext userContext = context.RequestData.ReadStruct(); + SystemClockContext networkContext = context.RequestData.ReadStruct(); + + ITickSource tickSource = context.Device.System.TickSource; + + ResultCode result = GetClockSnapshotFromSystemClockContextInternal(tickSource, userContext, networkContext, type, out ClockSnapshot clockSnapshot); + + if (result == ResultCode.Success) + { + WriteClockSnapshotFromBuffer(context, context.Request.RecvListBuff[0], clockSnapshot); + } + + return result; + } + + [CommandCmif(500)] // 4.0.0+ + // CalculateStandardUserSystemClockDifferenceByUser(buffer, buffer) -> nn::TimeSpanType + public ResultCode CalculateStandardUserSystemClockDifferenceByUser(ServiceCtx context) + { + ClockSnapshot clockSnapshotA = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[0]); + ClockSnapshot clockSnapshotB = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[1]); + TimeSpanType difference = TimeSpanType.FromSeconds(clockSnapshotB.UserContext.Offset - clockSnapshotA.UserContext.Offset); + + if (clockSnapshotB.UserContext.SteadyTimePoint.ClockSourceId != clockSnapshotA.UserContext.SteadyTimePoint.ClockSourceId || (clockSnapshotB.IsAutomaticCorrectionEnabled && clockSnapshotA.IsAutomaticCorrectionEnabled)) + { + difference = new TimeSpanType(0); + } + + context.ResponseData.Write(difference.NanoSeconds); + + return ResultCode.Success; + } + + [CommandCmif(501)] // 4.0.0+ + // CalculateSpanBetween(buffer, buffer) -> nn::TimeSpanType + public ResultCode CalculateSpanBetween(ServiceCtx context) + { + ClockSnapshot clockSnapshotA = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[0]); + ClockSnapshot clockSnapshotB = ReadClockSnapshotFromBuffer(context, context.Request.PtrBuff[1]); + + TimeSpanType result; + + ResultCode resultCode = clockSnapshotA.SteadyClockTimePoint.GetSpanBetween(clockSnapshotB.SteadyClockTimePoint, out long timeSpan); + + if (resultCode != ResultCode.Success) + { + resultCode = ResultCode.TimeNotFound; + + if (clockSnapshotA.NetworkTime != 0 && clockSnapshotB.NetworkTime != 0) + { + result = TimeSpanType.FromSeconds(clockSnapshotB.NetworkTime - clockSnapshotA.NetworkTime); + resultCode = ResultCode.Success; + } + else + { + return resultCode; + } + } + else + { + result = TimeSpanType.FromSeconds(timeSpan); + } + + context.ResponseData.Write(result.NanoSeconds); + + return resultCode; + } + + private ResultCode GetClockSnapshotFromSystemClockContextInternal(ITickSource tickSource, SystemClockContext userContext, SystemClockContext networkContext, byte type, out ClockSnapshot clockSnapshot) + { + clockSnapshot = new ClockSnapshot(); + + SteadyClockCore steadyClockCore = _timeManager.StandardSteadyClock; + SteadyClockTimePoint currentTimePoint = steadyClockCore.GetCurrentTimePoint(tickSource); + + clockSnapshot.IsAutomaticCorrectionEnabled = _timeManager.StandardUserSystemClock.IsAutomaticCorrectionEnabled(); + clockSnapshot.UserContext = userContext; + clockSnapshot.NetworkContext = networkContext; + clockSnapshot.SteadyClockTimePoint = currentTimePoint; + + ResultCode result = _timeManager.TimeZone.Manager.GetDeviceLocationName(out string deviceLocationName); + + if (result != ResultCode.Success) + { + return result; + } + + ReadOnlySpan tzName = Encoding.ASCII.GetBytes(deviceLocationName); + + tzName.CopyTo(clockSnapshot.LocationName); + + result = ClockSnapshot.GetCurrentTime(out clockSnapshot.UserTime, currentTimePoint, clockSnapshot.UserContext); + + if (result == ResultCode.Success) + { + result = _timeManager.TimeZone.Manager.ToCalendarTimeWithMyRules(clockSnapshot.UserTime, out CalendarInfo userCalendarInfo); + + if (result == ResultCode.Success) + { + clockSnapshot.UserCalendarTime = userCalendarInfo.Time; + clockSnapshot.UserCalendarAdditionalTime = userCalendarInfo.AdditionalInfo; + + if (ClockSnapshot.GetCurrentTime(out clockSnapshot.NetworkTime, currentTimePoint, clockSnapshot.NetworkContext) != ResultCode.Success) + { + clockSnapshot.NetworkTime = 0; + } + + result = _timeManager.TimeZone.Manager.ToCalendarTimeWithMyRules(clockSnapshot.NetworkTime, out CalendarInfo networkCalendarInfo); + + if (result == ResultCode.Success) + { + clockSnapshot.NetworkCalendarTime = networkCalendarInfo.Time; + clockSnapshot.NetworkCalendarAdditionalTime = networkCalendarInfo.AdditionalInfo; + clockSnapshot.Type = type; + + // Probably a version field? + clockSnapshot.Unknown = 0; + } + } + } + + return result; + } + + private ClockSnapshot ReadClockSnapshotFromBuffer(ServiceCtx context, IpcPtrBuffDesc ipcDesc) + { + Debug.Assert(ipcDesc.Size == (ulong)Unsafe.SizeOf()); + + byte[] temp = new byte[ipcDesc.Size]; + + context.Memory.Read(ipcDesc.Position, temp); + + using BinaryReader bufferReader = new(new MemoryStream(temp)); + + return bufferReader.ReadStruct(); + } + + private void WriteClockSnapshotFromBuffer(ServiceCtx context, IpcRecvListBuffDesc ipcDesc, ClockSnapshot clockSnapshot) + { + MemoryHelper.Write(context.Memory, ipcDesc.Position, clockSnapshot); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs b/src/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs new file mode 100644 index 00000000..1648aa3e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs @@ -0,0 +1,230 @@ +using Ryujinx.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.Utilities; +using Ryujinx.Horizon.Common; +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Service("time:m")] // 9.0.0+ + class ITimeServiceManager : IpcService + { + private readonly TimeManager _timeManager; + private int _automaticCorrectionEvent; + + public ITimeServiceManager(ServiceCtx context) + { + _timeManager = TimeManager.Instance; + _automaticCorrectionEvent = 0; + } + + [CommandCmif(0)] + // GetUserStaticService() -> object + public ResultCode GetUserStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.User)); + + return ResultCode.Success; + } + + [CommandCmif(5)] + // GetAdminStaticService() -> object + public ResultCode GetAdminStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Admin)); + + return ResultCode.Success; + } + + [CommandCmif(6)] + // GetRepairStaticService() -> object + public ResultCode GetRepairStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Repair)); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // GetManufactureStaticService() -> object + public ResultCode GetManufactureStaticService(ServiceCtx context) + { + MakeObject(context, new IStaticServiceForPsc(_timeManager, TimePermissions.Manufacture)); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // SetupStandardSteadyClock(nn::util::Uuid clock_source_id, nn::TimeSpanType setup_value, nn::TimeSpanType internal_offset, nn::TimeSpanType test_offset, bool is_rtc_reset_detected) + public ResultCode SetupStandardSteadyClock(ServiceCtx context) + { + UInt128 clockSourceId = context.RequestData.ReadStruct(); + TimeSpanType setupValue = context.RequestData.ReadStruct(); + TimeSpanType internalOffset = context.RequestData.ReadStruct(); + TimeSpanType testOffset = context.RequestData.ReadStruct(); + bool isRtcResetDetected = context.RequestData.ReadBoolean(); + + ITickSource tickSource = context.Device.System.TickSource; + + _timeManager.SetupStandardSteadyClock(tickSource, clockSourceId, setupValue, internalOffset, testOffset, isRtcResetDetected); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // SetupStandardLocalSystemClock(nn::time::SystemClockContext context, nn::time::PosixTime posix_time) + public ResultCode SetupStandardLocalSystemClock(ServiceCtx context) + { + SystemClockContext clockContext = context.RequestData.ReadStruct(); + long posixTime = context.RequestData.ReadInt64(); + + ITickSource tickSource = context.Device.System.TickSource; + + _timeManager.SetupStandardLocalSystemClock(tickSource, clockContext, posixTime); + + return ResultCode.Success; + } + + [CommandCmif(12)] + // SetupStandardNetworkSystemClock(nn::time::SystemClockContext context, nn::TimeSpanType sufficient_accuracy) + public ResultCode SetupStandardNetworkSystemClock(ServiceCtx context) + { + SystemClockContext clockContext = context.RequestData.ReadStruct(); + TimeSpanType sufficientAccuracy = context.RequestData.ReadStruct(); + + _timeManager.SetupStandardNetworkSystemClock(clockContext, sufficientAccuracy); + + return ResultCode.Success; + } + + [CommandCmif(13)] + // SetupStandardUserSystemClock(bool automatic_correction_enabled, nn::time::SteadyClockTimePoint steady_clock_timepoint) + public ResultCode SetupStandardUserSystemClock(ServiceCtx context) + { + bool isAutomaticCorrectionEnabled = context.RequestData.ReadBoolean(); + + context.RequestData.BaseStream.Position += 7; + + SteadyClockTimePoint steadyClockTimePoint = context.RequestData.ReadStruct(); + + ITickSource tickSource = context.Device.System.TickSource; + + _timeManager.SetupStandardUserSystemClock(tickSource, isAutomaticCorrectionEnabled, steadyClockTimePoint); + + return ResultCode.Success; + } + + [CommandCmif(14)] + // SetupTimeZoneManager(nn::time::LocationName location_name, nn::time::SteadyClockTimePoint timezone_update_timepoint, u32 total_location_name_count, nn::time::TimeZoneRuleVersion timezone_rule_version, buffer timezone_binary) + public ResultCode SetupTimeZoneManager(ServiceCtx context) + { + string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24); + SteadyClockTimePoint timeZoneUpdateTimePoint = context.RequestData.ReadStruct(); + uint totalLocationNameCount = context.RequestData.ReadUInt32(); + UInt128 timeZoneRuleVersion = context.RequestData.ReadStruct(); + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); + + byte[] temp = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, temp); + + using MemoryStream timeZoneBinaryStream = new(temp); + + _timeManager.SetupTimeZoneManager(locationName, timeZoneUpdateTimePoint, totalLocationNameCount, timeZoneRuleVersion, timeZoneBinaryStream); + + return ResultCode.Success; + } + + [CommandCmif(15)] + // SetupEphemeralNetworkSystemClock() + public ResultCode SetupEphemeralNetworkSystemClock(ServiceCtx context) + { + _timeManager.SetupEphemeralNetworkSystemClock(); + + return ResultCode.Success; + } + + [CommandCmif(50)] + // Unknown50() -> handle + public ResultCode Unknown50(ServiceCtx context) + { + // TODO: figure out the usage of this event + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(51)] + // Unknown51() -> handle + public ResultCode Unknown51(ServiceCtx context) + { + // TODO: figure out the usage of this event + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(52)] + // Unknown52() -> handle + public ResultCode Unknown52(ServiceCtx context) + { + // TODO: figure out the usage of this event + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(60)] + // GetStandardUserSystemClockAutomaticCorrectionEvent() -> handle + public ResultCode GetStandardUserSystemClockAutomaticCorrectionEvent(ServiceCtx context) + { + if (_automaticCorrectionEvent == 0) + { + if (context.Process.HandleTable.GenerateHandle(_timeManager.StandardUserSystemClock.GetAutomaticCorrectionReadableEvent(), out _automaticCorrectionEvent) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_automaticCorrectionEvent); + + return ResultCode.Success; + } + + [CommandCmif(100)] + // SetStandardSteadyClockRtcOffset(nn::TimeSpanType rtc_offset) + public ResultCode SetStandardSteadyClockRtcOffset(ServiceCtx context) + { + TimeSpanType rtcOffset = context.RequestData.ReadStruct(); + + ITickSource tickSource = context.Device.System.TickSource; + + _timeManager.SetStandardSteadyClockRtcOffset(tickSource, rtcOffset); + + return ResultCode.Success; + } + + [CommandCmif(200)] + // GetAlarmRegistrationEvent() -> handle + public ResultCode GetAlarmRegistrationEvent(ServiceCtx context) + { + // TODO + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(201)] + // UpdateSteadyAlarms() + public ResultCode UpdateSteadyAlarms(ServiceCtx context) + { + // TODO + throw new ServiceNotImplementedException(this, context); + } + + [CommandCmif(202)] + // TryGetNextSteadyClockAlarmSnapshot() -> (bool, nn::time::SteadyClockAlarmSnapshot) + public ResultCode TryGetNextSteadyClockAlarmSnapshot(ServiceCtx context) + { + // TODO + throw new ServiceNotImplementedException(this, context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs new file mode 100644 index 00000000..74a57f20 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs @@ -0,0 +1,27 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + public enum ResultCode + { + ModuleId = 116, + ErrorCodeShift = 9, + + Success = 0, + + TimeServiceNotInitialized = (0 << ErrorCodeShift) | ModuleId, + PermissionDenied = (1 << ErrorCodeShift) | ModuleId, + TimeMismatch = (102 << ErrorCodeShift) | ModuleId, + UninitializedClock = (103 << ErrorCodeShift) | ModuleId, + TimeNotFound = (200 << ErrorCodeShift) | ModuleId, + Overflow = (201 << ErrorCodeShift) | ModuleId, + LocationNameTooLong = (801 << ErrorCodeShift) | ModuleId, + OutOfRange = (902 << ErrorCodeShift) | ModuleId, + TimeZoneConversionFailed = (903 << ErrorCodeShift) | ModuleId, + TimeZoneNotFound = (989 << ErrorCodeShift) | ModuleId, + NotImplemented = (990 << ErrorCodeShift) | ModuleId, + NetworkTimeNotAvailable = (1000 << ErrorCodeShift) | ModuleId, + NetworkTimeTaskCanceled = (1003 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs new file mode 100644 index 00000000..8ddb646b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs @@ -0,0 +1,155 @@ +using Ryujinx.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Time.Clock; + +namespace Ryujinx.HLE.HOS.Services.Time.StaticService +{ + class ISteadyClock : IpcService + { + private readonly SteadyClockCore _steadyClock; + private readonly bool _writePermission; + private readonly bool _bypassUninitializedClock; + + public ISteadyClock(SteadyClockCore steadyClock, bool writePermission, bool bypassUninitializedClock) + { + _steadyClock = steadyClock; + _writePermission = writePermission; + _bypassUninitializedClock = bypassUninitializedClock; + } + + [CommandCmif(0)] + // GetCurrentTimePoint() -> nn::time::SteadyClockTimePoint + public ResultCode GetCurrentTimePoint(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + ITickSource tickSource = context.Device.System.TickSource; + + SteadyClockTimePoint currentTimePoint = _steadyClock.GetCurrentTimePoint(tickSource); + + context.ResponseData.WriteStruct(currentTimePoint); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetTestOffset() -> nn::TimeSpanType + public ResultCode GetTestOffset(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.WriteStruct(_steadyClock.GetTestOffset()); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // SetTestOffset(nn::TimeSpanType) + public ResultCode SetTestOffset(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + TimeSpanType testOffset = context.RequestData.ReadStruct(); + + _steadyClock.SetTestOffset(testOffset); + + return ResultCode.Success; + } + + [CommandCmif(100)] // 2.0.0+ + // GetRtcValue() -> u64 + public ResultCode GetRtcValue(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + ResultCode result = _steadyClock.GetRtcValue(out ulong rtcValue); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(rtcValue); + } + + return result; + } + + [CommandCmif(101)] // 2.0.0+ + // IsRtcResetDetected() -> bool + public ResultCode IsRtcResetDetected(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.Write(_steadyClock.IsRtcResetDetected()); + + return ResultCode.Success; + } + + [CommandCmif(102)] // 2.0.0+ + // GetSetupResultValue() -> u32 + public ResultCode GetSetupResultValue(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.Write((uint)_steadyClock.GetSetupResultValue()); + + return ResultCode.Success; + } + + [CommandCmif(200)] // 3.0.0+ + // GetInternalOffset() -> nn::TimeSpanType + public ResultCode GetInternalOffset(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + context.ResponseData.WriteStruct(_steadyClock.GetInternalOffset()); + + return ResultCode.Success; + } + + [CommandCmif(201)] // 3.0.0-3.0.2 + // SetInternalOffset(nn::TimeSpanType) + public ResultCode SetInternalOffset(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + if (!_bypassUninitializedClock && !_steadyClock.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + TimeSpanType internalOffset = context.RequestData.ReadStruct(); + + _steadyClock.SetInternalOffset(internalOffset); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs new file mode 100644 index 00000000..ada5f057 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs @@ -0,0 +1,131 @@ +using Ryujinx.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Time.StaticService +{ + class ISystemClock : IpcService + { + private readonly SystemClockCore _clockCore; + private readonly bool _writePermission; + private readonly bool _bypassUninitializedClock; + private int _operationEventReadableHandle; + + public ISystemClock(SystemClockCore clockCore, bool writePermission, bool bypassUninitializedClock) + { + _clockCore = clockCore; + _writePermission = writePermission; + _bypassUninitializedClock = bypassUninitializedClock; + _operationEventReadableHandle = 0; + } + + [CommandCmif(0)] + // GetCurrentTime() -> nn::time::PosixTime + public ResultCode GetCurrentTime(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_clockCore.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + ITickSource tickSource = context.Device.System.TickSource; + + ResultCode result = _clockCore.GetCurrentTime(tickSource, out long posixTime); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(posixTime); + } + + return result; + } + + [CommandCmif(1)] + // SetCurrentTime(nn::time::PosixTime) + public ResultCode SetCurrentTime(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + if (!_bypassUninitializedClock && !_clockCore.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + long posixTime = context.RequestData.ReadInt64(); + + ITickSource tickSource = context.Device.System.TickSource; + + return _clockCore.SetCurrentTime(tickSource, posixTime); + } + + [CommandCmif(2)] + // GetClockContext() -> nn::time::SystemClockContext + public ResultCode GetSystemClockContext(ServiceCtx context) + { + if (!_bypassUninitializedClock && !_clockCore.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + ITickSource tickSource = context.Device.System.TickSource; + + ResultCode result = _clockCore.GetClockContext(tickSource, out SystemClockContext clockContext); + + if (result == ResultCode.Success) + { + context.ResponseData.WriteStruct(clockContext); + } + + return result; + } + + [CommandCmif(3)] + // SetClockContext(nn::time::SystemClockContext) + public ResultCode SetSystemClockContext(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + if (!_bypassUninitializedClock && !_clockCore.IsInitialized()) + { + return ResultCode.UninitializedClock; + } + + SystemClockContext clockContext = context.RequestData.ReadStruct(); + + ResultCode result = _clockCore.SetSystemClockContext(clockContext); + + return result; + } + + [CommandCmif(4)] // 9.0.0+ + // GetOperationEventReadableHandle() -> handle + public ResultCode GetOperationEventReadableHandle(ServiceCtx context) + { + if (_operationEventReadableHandle == 0) + { + KEvent kEvent = new(context.Device.System.KernelContext); + + _clockCore.RegisterOperationEvent(kEvent.WritableEvent); + + if (context.Process.HandleTable.GenerateHandle(kEvent.ReadableEvent, out _operationEventReadableHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_operationEventReadableHandle); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs new file mode 100644 index 00000000..81944c83 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs @@ -0,0 +1,141 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using Ryujinx.HLE.Utilities; +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Time.StaticService +{ + class ITimeZoneServiceForGlue : IpcService + { + private readonly TimeZoneContentManager _timeZoneContentManager; + private readonly ITimeZoneServiceForPsc _inner; + private readonly bool _writePermission; + + public ITimeZoneServiceForGlue(TimeZoneContentManager timeZoneContentManager, bool writePermission) + { + _timeZoneContentManager = timeZoneContentManager; + _writePermission = writePermission; + _inner = new ITimeZoneServiceForPsc(timeZoneContentManager.Manager, writePermission); + } + + [CommandCmif(0)] + // GetDeviceLocationName() -> nn::time::LocationName + public ResultCode GetDeviceLocationName(ServiceCtx context) + { + return _inner.GetDeviceLocationName(context); + } + + [CommandCmif(1)] + // SetDeviceLocationName(nn::time::LocationName) + public ResultCode SetDeviceLocationName(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24); + + return _timeZoneContentManager.SetDeviceLocationName(locationName); + } + + [CommandCmif(2)] + // GetTotalLocationNameCount() -> u32 + public ResultCode GetTotalLocationNameCount(ServiceCtx context) + { + return _inner.GetTotalLocationNameCount(context); + } + + [CommandCmif(3)] + // LoadLocationNameList(u32 index) -> (u32 outCount, buffer) + public ResultCode LoadLocationNameList(ServiceCtx context) + { + uint index = context.RequestData.ReadUInt32(); + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; + + ResultCode errorCode = _timeZoneContentManager.LoadLocationNameList(index, out string[] locationNameArray, (uint)bufferSize / 0x24); + + if (errorCode == 0) + { + uint offset = 0; + + foreach (string locationName in locationNameArray) + { + int padding = 0x24 - locationName.Length; + + if (padding < 0) + { + return ResultCode.LocationNameTooLong; + } + + context.Memory.Write(bufferPosition + offset, Encoding.ASCII.GetBytes(locationName)); + MemoryHelper.FillWithZeros(context.Memory, bufferPosition + offset + (ulong)locationName.Length, padding); + + offset += 0x24; + } + + context.ResponseData.Write((uint)locationNameArray.Length); + } + + return errorCode; + } + + [CommandCmif(4)] + // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer + public ResultCode LoadTimeZoneRule(ServiceCtx context) + { + ulong bufferPosition = context.Request.ReceiveBuff[0].Position; + ulong bufferSize = context.Request.ReceiveBuff[0].Size; + + if (bufferSize != 0x4000) + { + // TODO: find error code here + Logger.Error?.Print(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)"); + + throw new InvalidOperationException(); + } + + string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24); + + using WritableRegion region = context.Memory.GetWritableRegion(bufferPosition, Unsafe.SizeOf()); + + ref TimeZoneRule rules = ref MemoryMarshal.Cast(region.Memory.Span)[0]; + + return _timeZoneContentManager.LoadTimeZoneRule(ref rules, locationName); + } + + [CommandCmif(100)] + // ToCalendarTime(nn::time::PosixTime time, buffer rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) + public ResultCode ToCalendarTime(ServiceCtx context) + { + return _inner.ToCalendarTime(context); + } + + [CommandCmif(101)] + // ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) + public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context) + { + return _inner.ToCalendarTimeWithMyRule(context); + } + + [CommandCmif(201)] + // ToPosixTime(nn::time::CalendarTime calendarTime, buffer rules) -> (u32 outCount, buffer) + public ResultCode ToPosixTime(ServiceCtx context) + { + return _inner.ToPosixTime(context); + } + + [CommandCmif(202)] + // ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer) + public ResultCode ToPosixTimeWithMyRule(ServiceCtx context) + { + return _inner.ToPosixTimeWithMyRule(context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs new file mode 100644 index 00000000..b18a4f76 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs @@ -0,0 +1,302 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using Ryujinx.HLE.Utilities; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.StaticService +{ + class ITimeZoneServiceForPsc : IpcService + { + private readonly TimeZoneManager _timeZoneManager; + private readonly bool _writePermission; + + public ITimeZoneServiceForPsc(TimeZoneManager timeZoneManager, bool writePermission) + { + _timeZoneManager = timeZoneManager; + _writePermission = writePermission; + } + + [CommandCmif(0)] + // GetDeviceLocationName() -> nn::time::LocationName + public ResultCode GetDeviceLocationName(ServiceCtx context) + { + ResultCode result = _timeZoneManager.GetDeviceLocationName(out string deviceLocationName); + + if (result == ResultCode.Success) + { + WriteLocationName(context, deviceLocationName); + } + + return result; + } + + [CommandCmif(1)] + // SetDeviceLocationName(nn::time::LocationName) + public ResultCode SetDeviceLocationName(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + return ResultCode.NotImplemented; + } + + [CommandCmif(2)] + // GetTotalLocationNameCount() -> u32 + public ResultCode GetTotalLocationNameCount(ServiceCtx context) + { + ResultCode result = _timeZoneManager.GetTotalLocationNameCount(out uint totalLocationNameCount); + + if (result == ResultCode.Success) + { + context.ResponseData.Write(totalLocationNameCount); + } + + return ResultCode.Success; + } + + [CommandCmif(3)] + // LoadLocationNameList(u32 index) -> (u32 outCount, buffer) + public ResultCode LoadLocationNameList(ServiceCtx context) + { + return ResultCode.NotImplemented; + } + + [CommandCmif(4)] + // LoadTimeZoneRule(nn::time::LocationName locationName) -> buffer + public ResultCode LoadTimeZoneRule(ServiceCtx context) + { + return ResultCode.NotImplemented; + } + + [CommandCmif(5)] // 2.0.0+ + // GetTimeZoneRuleVersion() -> nn::time::TimeZoneRuleVersion + public ResultCode GetTimeZoneRuleVersion(ServiceCtx context) + { + ResultCode result = _timeZoneManager.GetTimeZoneRuleVersion(out UInt128 timeZoneRuleVersion); + + if (result == ResultCode.Success) + { + context.ResponseData.WriteStruct(timeZoneRuleVersion); + } + + return result; + } + + [CommandCmif(6)] // 5.0.0+ + // GetDeviceLocationNameAndUpdatedTime() -> (nn::time::LocationName, nn::time::SteadyClockTimePoint) + public ResultCode GetDeviceLocationNameAndUpdatedTime(ServiceCtx context) + { + ResultCode result = _timeZoneManager.GetDeviceLocationName(out string deviceLocationName); + + if (result == ResultCode.Success) + { + result = _timeZoneManager.GetUpdatedTime(out SteadyClockTimePoint timeZoneUpdateTimePoint); + + if (result == ResultCode.Success) + { + WriteLocationName(context, deviceLocationName); + + // Skip padding + context.ResponseData.BaseStream.Position += 0x4; + + context.ResponseData.WriteStruct(timeZoneUpdateTimePoint); + } + } + + return result; + } + + [CommandCmif(7)] // 9.0.0+ + // SetDeviceLocationNameWithTimeZoneRule(nn::time::LocationName locationName, buffer timeZoneBinary) + public ResultCode SetDeviceLocationNameWithTimeZoneRule(ServiceCtx context) + { + if (!_writePermission) + { + return ResultCode.PermissionDenied; + } + + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); + + string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24); + + ResultCode result; + + byte[] temp = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, temp); + + using MemoryStream timeZoneBinaryStream = new(temp); + result = _timeZoneManager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream); + + return result; + } + + [CommandCmif(8)] // 9.0.0+ + // ParseTimeZoneBinary(buffer timeZoneBinary) -> buffer + public ResultCode ParseTimeZoneBinary(ServiceCtx context) + { + (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(); + + ulong timeZoneRuleBufferPosition = context.Request.ReceiveBuff[0].Position; + ulong timeZoneRuleBufferSize = context.Request.ReceiveBuff[0].Size; + + if (timeZoneRuleBufferSize != 0x4000) + { + // TODO: find error code here + Logger.Error?.Print(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{timeZoneRuleBufferSize:x} (expected 0x4000)"); + + throw new InvalidOperationException(); + } + + ResultCode result; + + byte[] temp = new byte[bufferSize]; + + context.Memory.Read(bufferPosition, temp); + + using MemoryStream timeZoneBinaryStream = new(temp); + using WritableRegion region = context.Memory.GetWritableRegion(timeZoneRuleBufferPosition, Unsafe.SizeOf()); + + ref TimeZoneRule rule = ref MemoryMarshal.Cast(region.Memory.Span)[0]; + + result = _timeZoneManager.ParseTimeZoneRuleBinary(ref rule, timeZoneBinaryStream); + + return result; + } + + [CommandCmif(20)] // 9.0.0+ + // GetDeviceLocationNameOperationEventReadableHandle() -> handle + public ResultCode GetDeviceLocationNameOperationEventReadableHandle(ServiceCtx context) + { + return ResultCode.NotImplemented; + } + + [CommandCmif(100)] + // ToCalendarTime(nn::time::PosixTime time, buffer rules) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) + public ResultCode ToCalendarTime(ServiceCtx context) + { + long posixTime = context.RequestData.ReadInt64(); + ulong bufferPosition = context.Request.SendBuff[0].Position; + ulong bufferSize = context.Request.SendBuff[0].Size; + + if (bufferSize != 0x4000) + { + // TODO: find error code here + Logger.Error?.Print(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{bufferSize:x} (expected 0x4000)"); + + throw new InvalidOperationException(); + } + + ReadOnlySpan rules = MemoryMarshal.Cast(context.Memory.GetSpan(bufferPosition, (int)bufferSize)); + + ResultCode resultCode = _timeZoneManager.ToCalendarTime(in rules[0], posixTime, out CalendarInfo calendar); + + if (resultCode == 0) + { + context.ResponseData.WriteStruct(calendar); + } + + return resultCode; + } + + [CommandCmif(101)] + // ToCalendarTimeWithMyRule(nn::time::PosixTime) -> (nn::time::CalendarTime, nn::time::sf::CalendarAdditionalInfo) + public ResultCode ToCalendarTimeWithMyRule(ServiceCtx context) + { + long posixTime = context.RequestData.ReadInt64(); + + ResultCode resultCode = _timeZoneManager.ToCalendarTimeWithMyRules(posixTime, out CalendarInfo calendar); + + if (resultCode == ResultCode.Success) + { + context.ResponseData.WriteStruct(calendar); + } + + return resultCode; + } + + [CommandCmif(201)] + // ToPosixTime(nn::time::CalendarTime calendarTime, buffer rules) -> (u32 outCount, buffer) + public ResultCode ToPosixTime(ServiceCtx context) + { + ulong inBufferPosition = context.Request.SendBuff[0].Position; + ulong inBufferSize = context.Request.SendBuff[0].Size; + + CalendarTime calendarTime = context.RequestData.ReadStruct(); + + if (inBufferSize != 0x4000) + { + // TODO: find error code here + Logger.Error?.Print(LogClass.ServiceTime, $"TimeZoneRule buffer size is 0x{inBufferSize:x} (expected 0x4000)"); + + throw new InvalidOperationException(); + } + + ReadOnlySpan rules = MemoryMarshal.Cast(context.Memory.GetSpan(inBufferPosition, (int)inBufferSize)); + + ResultCode resultCode = _timeZoneManager.ToPosixTime(in rules[0], calendarTime, out long posixTime); + + if (resultCode == ResultCode.Success) + { + ulong outBufferPosition = context.Request.RecvListBuff[0].Position; +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong outBufferSize = context.Request.RecvListBuff[0].Size; +#pragma warning restore IDE0059 + + context.Memory.Write(outBufferPosition, posixTime); + context.ResponseData.Write(1); + } + + return resultCode; + } + + [CommandCmif(202)] + // ToPosixTimeWithMyRule(nn::time::CalendarTime calendarTime) -> (u32 outCount, buffer) + public ResultCode ToPosixTimeWithMyRule(ServiceCtx context) + { + CalendarTime calendarTime = context.RequestData.ReadStruct(); + + ResultCode resultCode = _timeZoneManager.ToPosixTimeWithMyRules(calendarTime, out long posixTime); + + if (resultCode == ResultCode.Success) + { + ulong outBufferPosition = context.Request.RecvListBuff[0].Position; +#pragma warning disable IDE0059 // Remove unnecessary value assignment + ulong outBufferSize = context.Request.RecvListBuff[0].Size; +#pragma warning restore IDE0059 + + context.Memory.Write(outBufferPosition, posixTime); + + // There could be only one result on one calendar as leap seconds aren't supported. + context.ResponseData.Write(1); + } + + return resultCode; + } + + private void WriteLocationName(ServiceCtx context, string locationName) + { + char[] locationNameArray = locationName.ToCharArray(); + + int padding = 0x24 - locationNameArray.Length; + + Debug.Assert(padding >= 0, "LocationName exceeded limit (0x24 bytes)"); + + context.ResponseData.Write(locationNameArray); + + for (int index = 0; index < padding; index++) + { + context.ResponseData.Write((byte)0); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs new file mode 100644 index 00000000..f8e36bcd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs @@ -0,0 +1,179 @@ +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + class TimeManager + { + private static TimeManager _instance; + + public static TimeManager Instance + { + get + { + _instance ??= new TimeManager(); + + return _instance; + } + } + + public StandardSteadyClockCore StandardSteadyClock { get; } + public TickBasedSteadyClockCore TickBasedSteadyClock { get; } + public StandardLocalSystemClockCore StandardLocalSystemClock { get; } + public StandardNetworkSystemClockCore StandardNetworkSystemClock { get; } + public StandardUserSystemClockCore StandardUserSystemClock { get; } + public TimeZoneContentManager TimeZone { get; } + public EphemeralNetworkSystemClockCore EphemeralNetworkSystemClock { get; } + public TimeSharedMemory SharedMemory { get; } + public LocalSystemClockContextWriter LocalClockContextWriter { get; } + public NetworkSystemClockContextWriter NetworkClockContextWriter { get; } + public EphemeralNetworkSystemClockContextWriter EphemeralClockContextWriter { get; } + + // TODO: 9.0.0+ power states and alarms + + public TimeManager() + { + StandardSteadyClock = new StandardSteadyClockCore(); + TickBasedSteadyClock = new TickBasedSteadyClockCore(); + StandardLocalSystemClock = new StandardLocalSystemClockCore(StandardSteadyClock); + StandardNetworkSystemClock = new StandardNetworkSystemClockCore(StandardSteadyClock); + StandardUserSystemClock = new StandardUserSystemClockCore(StandardLocalSystemClock, StandardNetworkSystemClock); + TimeZone = new TimeZoneContentManager(); + EphemeralNetworkSystemClock = new EphemeralNetworkSystemClockCore(TickBasedSteadyClock); + SharedMemory = new TimeSharedMemory(); + LocalClockContextWriter = new LocalSystemClockContextWriter(SharedMemory); + NetworkClockContextWriter = new NetworkSystemClockContextWriter(SharedMemory); + EphemeralClockContextWriter = new EphemeralNetworkSystemClockContextWriter(); + } + + public void Initialize(Switch device, Horizon system, KSharedMemory sharedMemory, SharedMemoryStorage timeSharedMemoryStorage, int timeSharedMemorySize) + { + SharedMemory.Initialize(device, sharedMemory, timeSharedMemoryStorage, timeSharedMemorySize); + + // Here we use system on purpose as device. System isn't initialized at this point. + StandardUserSystemClock.CreateAutomaticCorrectionEvent(system); + } + + public void InitializeTimeZone(Switch device) + { + TimeZone.Initialize(this, device); + } + + public void SetupStandardSteadyClock(ITickSource tickSource, UInt128 clockSourceId, TimeSpanType setupValue, TimeSpanType internalOffset, TimeSpanType testOffset, bool isRtcResetDetected) + { + SetupInternalStandardSteadyClock(clockSourceId, setupValue, internalOffset, testOffset, isRtcResetDetected); + + TimeSpanType currentTimePoint = StandardSteadyClock.GetCurrentRawTimePoint(tickSource); + + SharedMemory.SetupStandardSteadyClock(tickSource, clockSourceId, currentTimePoint); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + private void SetupInternalStandardSteadyClock(UInt128 clockSourceId, TimeSpanType setupValue, TimeSpanType internalOffset, TimeSpanType testOffset, bool isRtcResetDetected) + { + StandardSteadyClock.SetClockSourceId(clockSourceId); + StandardSteadyClock.SetSetupValue(setupValue); + StandardSteadyClock.SetInternalOffset(internalOffset); + StandardSteadyClock.SetTestOffset(testOffset); + + if (isRtcResetDetected) + { + StandardSteadyClock.SetRtcReset(); + } + + StandardSteadyClock.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupStandardLocalSystemClock(ITickSource tickSource, SystemClockContext clockContext, long posixTime) + { + StandardLocalSystemClock.SetUpdateCallbackInstance(LocalClockContextWriter); + + SteadyClockTimePoint currentTimePoint = StandardLocalSystemClock.GetSteadyClockCore().GetCurrentTimePoint(tickSource); + if (currentTimePoint.ClockSourceId == clockContext.SteadyTimePoint.ClockSourceId) + { + StandardLocalSystemClock.SetSystemClockContext(clockContext); + } + else + { + if (StandardLocalSystemClock.SetCurrentTime(tickSource, posixTime) != ResultCode.Success) + { + throw new InternalServiceException("Cannot set current local time"); + } + } + + StandardLocalSystemClock.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupStandardNetworkSystemClock(SystemClockContext clockContext, TimeSpanType sufficientAccuracy) + { + StandardNetworkSystemClock.SetUpdateCallbackInstance(NetworkClockContextWriter); + + if (StandardNetworkSystemClock.SetSystemClockContext(clockContext) != ResultCode.Success) + { + throw new InternalServiceException("Cannot set network SystemClockContext"); + } + + StandardNetworkSystemClock.SetStandardNetworkClockSufficientAccuracy(sufficientAccuracy); + StandardNetworkSystemClock.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupTimeZoneManager(string locationName, SteadyClockTimePoint timeZoneUpdatedTimePoint, uint totalLocationNameCount, UInt128 timeZoneRuleVersion, Stream timeZoneBinaryStream) + { + if (TimeZone.Manager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream) != ResultCode.Success) + { + throw new InternalServiceException("Cannot set DeviceLocationName with a given TimeZoneBinary"); + } + + TimeZone.Manager.SetUpdatedTime(timeZoneUpdatedTimePoint, true); + TimeZone.Manager.SetTotalLocationNameCount(totalLocationNameCount); + TimeZone.Manager.SetTimeZoneRuleVersion(timeZoneRuleVersion); + TimeZone.Manager.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupEphemeralNetworkSystemClock() + { + EphemeralNetworkSystemClock.SetUpdateCallbackInstance(EphemeralClockContextWriter); + EphemeralNetworkSystemClock.MarkInitialized(); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetupStandardUserSystemClock(ITickSource tickSource, bool isAutomaticCorrectionEnabled, SteadyClockTimePoint steadyClockTimePoint) + { + if (StandardUserSystemClock.SetAutomaticCorrectionEnabled(tickSource, isAutomaticCorrectionEnabled) != ResultCode.Success) + { + throw new InternalServiceException("Cannot set automatic user time correction state"); + } + + StandardUserSystemClock.SetAutomaticCorrectionUpdatedTime(steadyClockTimePoint); + StandardUserSystemClock.MarkInitialized(); + + SharedMemory.SetAutomaticCorrectionEnabled(isAutomaticCorrectionEnabled); + + // TODO: propagate IPC late binding of "time:s" and "time:p" + } + + public void SetStandardSteadyClockRtcOffset(ITickSource tickSource, TimeSpanType rtcOffset) + { + StandardSteadyClock.SetSetupValue(rtcOffset); + + TimeSpanType currentTimePoint = StandardSteadyClock.GetCurrentRawTimePoint(tickSource); + + SharedMemory.SetSteadyClockRawTimePoint(tickSource, currentTimePoint); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs new file mode 100644 index 00000000..34648703 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs @@ -0,0 +1,137 @@ +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using Ryujinx.HLE.HOS.Services.Time.Clock.Types; +using Ryujinx.HLE.HOS.Services.Time.Types; +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + class TimeSharedMemory + { + private Switch _device; + private KSharedMemory _sharedMemory; + private SharedMemoryStorage _timeSharedMemoryStorage; +#pragma warning disable IDE0052 // Remove unread private member + private int _timeSharedMemorySize; +#pragma warning restore IDE0052 + + private const uint SteadyClockContextOffset = 0x00; + private const uint LocalSystemClockContextOffset = 0x38; + private const uint NetworkSystemClockContextOffset = 0x80; + private const uint AutomaticCorrectionEnabledOffset = 0xC8; + private const uint ContinuousAdjustmentTimePointOffset = 0xD0; + + public void Initialize(Switch device, KSharedMemory sharedMemory, SharedMemoryStorage timeSharedMemoryStorage, int timeSharedMemorySize) + { + _device = device; + _sharedMemory = sharedMemory; + _timeSharedMemoryStorage = timeSharedMemoryStorage; + _timeSharedMemorySize = timeSharedMemorySize; + + // Clean the shared memory + timeSharedMemoryStorage.ZeroFill(); + } + + public KSharedMemory GetSharedMemory() + { + return _sharedMemory; + } + + public void SetupStandardSteadyClock(ITickSource tickSource, UInt128 clockSourceId, TimeSpanType currentTimePoint) + { + UpdateSteadyClock(tickSource, clockSourceId, currentTimePoint); + } + + public void SetAutomaticCorrectionEnabled(bool isAutomaticCorrectionEnabled) + { + // We convert the bool to byte here as a bool in C# takes 4 bytes... + WriteObjectToSharedMemory(AutomaticCorrectionEnabledOffset, 0, Convert.ToByte(isAutomaticCorrectionEnabled)); + } + + public void SetSteadyClockRawTimePoint(ITickSource tickSource, TimeSpanType currentTimePoint) + { + SteadyClockContext context = ReadObjectFromSharedMemory(SteadyClockContextOffset, 4); + + UpdateSteadyClock(tickSource, context.ClockSourceId, currentTimePoint); + } + + private void UpdateSteadyClock(ITickSource tickSource, UInt128 clockSourceId, TimeSpanType currentTimePoint) + { + TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(tickSource.Counter, tickSource.Frequency); + + ContinuousAdjustmentTimePoint adjustmentTimePoint = new() + { + ClockOffset = (ulong)ticksTimeSpan.NanoSeconds, + Multiplier = 1, + DivisorLog2 = 0, + Context = new SystemClockContext + { + Offset = 0, + SteadyTimePoint = new SteadyClockTimePoint + { + ClockSourceId = clockSourceId, + TimePoint = 0, + }, + }, + }; + + WriteObjectToSharedMemory(ContinuousAdjustmentTimePointOffset, 4, adjustmentTimePoint); + + SteadyClockContext context = new() + { + InternalOffset = (ulong)(currentTimePoint.NanoSeconds - ticksTimeSpan.NanoSeconds), + ClockSourceId = clockSourceId, + }; + + WriteObjectToSharedMemory(SteadyClockContextOffset, 4, context); + } + + public void UpdateLocalSystemClockContext(SystemClockContext context) + { + WriteObjectToSharedMemory(LocalSystemClockContextOffset, 4, context); + } + + public void UpdateNetworkSystemClockContext(SystemClockContext context) + { + WriteObjectToSharedMemory(NetworkSystemClockContextOffset, 4, context); + } + + private T ReadObjectFromSharedMemory(ulong offset, ulong padding) where T : unmanaged + { + T result; + uint index; + uint possiblyNewIndex; + + do + { + index = _timeSharedMemoryStorage.GetRef(offset); + + ulong objectOffset = offset + 4 + padding + (ulong)((index & 1) * Unsafe.SizeOf()); + + result = _timeSharedMemoryStorage.GetRef(objectOffset); + + Thread.MemoryBarrier(); + + possiblyNewIndex = _device.Memory.Read(offset); + } while (index != possiblyNewIndex); + + return result; + } + + private void WriteObjectToSharedMemory(ulong offset, ulong padding, T value) where T : unmanaged + { + uint newIndex = _timeSharedMemoryStorage.GetRef(offset) + 1; + + ulong objectOffset = offset + 4 + padding + (ulong)((newIndex & 1) * Unsafe.SizeOf()); + + _timeSharedMemoryStorage.GetRef(objectOffset) = value; + + Thread.MemoryBarrier(); + + _timeSharedMemoryStorage.GetRef(offset) = newIndex; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs new file mode 100644 index 00000000..cdd11b58 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs @@ -0,0 +1,1704 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.Utilities; +using System; +using System.Buffers.Binary; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + public class TimeZone + { + private const int TimeTypeSize = 8; + private const int EpochYear = 1970; + private const int YearBase = 1900; + private const int EpochWeekDay = 4; + private const int SecondsPerMinute = 60; + private const int MinutesPerHour = 60; + private const int HoursPerDays = 24; + private const int DaysPerWeek = 7; + private const int DaysPerNYear = 365; + private const int DaysPerLYear = 366; + private const int MonthsPerYear = 12; + private const int SecondsPerHour = SecondsPerMinute * MinutesPerHour; + private const int SecondsPerDay = SecondsPerHour * HoursPerDays; + + private const int YearsPerRepeat = 400; + private const long AverageSecondsPerYear = 31556952; + private const long SecondsPerRepeat = YearsPerRepeat * AverageSecondsPerYear; + + private static readonly int[] _yearLengths = { DaysPerNYear, DaysPerLYear }; + private static readonly int[][] _monthsLengths = { + new[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + new[] { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + }; + + private static ReadOnlySpan TimeZoneDefaultRule => ",M4.1.0,M10.5.0"u8; + + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x10)] + private struct CalendarTimeInternal + { + // NOTE: On the IPC side this is supposed to be a 16 bits value but internally this need to be a 64 bits value for ToPosixTime. + public long Year; + public sbyte Month; + public sbyte Day; + public sbyte Hour; + public sbyte Minute; + public sbyte Second; + + public readonly int CompareTo(CalendarTimeInternal other) + { + if (Year != other.Year) + { + if (Year < other.Year) + { + return -1; + } + + return 1; + } + + if (Month != other.Month) + { + return Month - other.Month; + } + + if (Day != other.Day) + { + return Day - other.Day; + } + + if (Hour != other.Hour) + { + return Hour - other.Hour; + } + + if (Minute != other.Minute) + { + return Minute - other.Minute; + } + + if (Second != other.Second) + { + return Second - other.Second; + } + + return 0; + } + } + + private enum RuleType + { + JulianDay, + DayOfYear, + MonthNthDayOfWeek, + } + + private struct Rule + { + public RuleType Type; + public int Day; + public int Week; + public int Month; + public int TransitionTime; + } + + private static int Detzcode32(ReadOnlySpan bytes) + { + return BinaryPrimitives.ReadInt32BigEndian(bytes); + } + + private static int Detzcode32(int value) + { + if (BitConverter.IsLittleEndian) + { + return BinaryPrimitives.ReverseEndianness(value); + } + + return value; + } + + private static long Detzcode64(ReadOnlySpan bytes) + { + return BinaryPrimitives.ReadInt64BigEndian(bytes); + } + + private static bool DifferByRepeat(long t1, long t0) + { + return (t1 - t0) == SecondsPerRepeat; + } + + private static bool TimeTypeEquals(in TimeZoneRule outRules, byte aIndex, byte bIndex) + { + if (aIndex < 0 || aIndex >= outRules.TypeCount || bIndex < 0 || bIndex >= outRules.TypeCount) + { + return false; + } + + TimeTypeInfo a = outRules.Ttis[aIndex]; + TimeTypeInfo b = outRules.Ttis[bIndex]; + + return a.GmtOffset == b.GmtOffset && + a.IsDaySavingTime == b.IsDaySavingTime && + a.IsStandardTimeDaylight == b.IsStandardTimeDaylight && + a.IsGMT == b.IsGMT && + StringUtils.CompareCStr(outRules.Chars[a.AbbreviationListIndex..], outRules.Chars[b.AbbreviationListIndex..]) == 0; + } + + private static int GetQZName(ReadOnlySpan name, int namePosition, char delimiter) + { + int i = namePosition; + + while (name[i] != '\0' && name[i] != delimiter) + { + i++; + } + + return i; + } + + private static int GetTZName(ReadOnlySpan name, int namePosition) + { + int i = namePosition; + + char c; + + while ((c = (char)name[i]) != '\0' && !char.IsDigit(c) && c != ',' && c != '-' && c != '+') + { + i++; + } + + return i; + } + + private static bool GetNum(ReadOnlySpan name, ref int namePosition, out int num, int min, int max) + { + num = 0; + + if (namePosition >= name.Length) + { + return false; + } + + char c = (char)name[namePosition]; + + if (!char.IsDigit(c)) + { + return false; + } + + do + { + num = num * 10 + (c - '0'); + if (num > max) + { + return false; + } + + if (++namePosition >= name.Length) + { + return false; + } + + c = (char)name[namePosition]; + } + while (char.IsDigit(c)); + + if (num < min) + { + return false; + } + + return true; + } + + private static bool GetSeconds(ReadOnlySpan name, ref int namePosition, out int seconds) + { + seconds = 0; + + + bool isValid = GetNum(name, ref namePosition, out int num, 0, HoursPerDays * DaysPerWeek - 1); + if (!isValid) + { + return false; + } + + seconds = num * SecondsPerHour; + + if (namePosition >= name.Length) + { + return false; + } + + if (name[namePosition] == ':') + { + namePosition++; + isValid = GetNum(name, ref namePosition, out num, 0, MinutesPerHour - 1); + if (!isValid) + { + return false; + } + + seconds += num * SecondsPerMinute; + + if (namePosition >= name.Length) + { + return false; + } + + if (name[namePosition] == ':') + { + namePosition++; + isValid = GetNum(name, ref namePosition, out num, 0, SecondsPerMinute); + if (!isValid) + { + return false; + } + + seconds += num; + } + } + return true; + } + + private static bool GetOffset(ReadOnlySpan name, ref int namePosition, ref int offset) + { + bool isNegative = false; + + if (namePosition >= name.Length) + { + return false; + } + + if (name[namePosition] == '-') + { + isNegative = true; + namePosition++; + } + else if (name[namePosition] == '+') + { + namePosition++; + } + + if (namePosition >= name.Length) + { + return false; + } + + bool isValid = GetSeconds(name, ref namePosition, out offset); + if (!isValid) + { + return false; + } + + if (isNegative) + { + offset = -offset; + } + + return true; + } + + private static bool GetRule(ReadOnlySpan name, ref int namePosition, out Rule rule) + { + rule = new Rule(); + + bool isValid; + + if (name[namePosition] == 'J') + { + namePosition++; + + rule.Type = RuleType.JulianDay; + isValid = GetNum(name, ref namePosition, out rule.Day, 1, DaysPerNYear); + } + else if (name[namePosition] == 'M') + { + namePosition++; + + rule.Type = RuleType.MonthNthDayOfWeek; + isValid = GetNum(name, ref namePosition, out rule.Month, 1, MonthsPerYear); + + if (!isValid) + { + return false; + } + + if (name[namePosition++] != '.') + { + return false; + } + + isValid = GetNum(name, ref namePosition, out rule.Week, 1, 5); + if (!isValid) + { + return false; + } + + if (name[namePosition++] != '.') + { + return false; + } + + isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerWeek - 1); + } + else if (char.IsDigit((char)name[namePosition])) + { + rule.Type = RuleType.DayOfYear; + isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerLYear - 1); + } + else + { + return false; + } + + if (!isValid) + { + return false; + } + + if (name[namePosition] == '/') + { + namePosition++; + return GetOffset(name, ref namePosition, ref rule.TransitionTime); + } + else + { + rule.TransitionTime = 2 * SecondsPerHour; + } + + return true; + } + + private static int IsLeap(int year) + { + if (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0)) + { + return 1; + } + + return 0; + } + + private static bool ParsePosixName(ReadOnlySpan name, ref TimeZoneRule outRules, bool lastDitch) + { + outRules = new TimeZoneRule(); + + int stdLen; + + ReadOnlySpan stdName = name; + int namePosition = 0; + int stdOffset = 0; + + if (lastDitch) + { + stdLen = 3; + namePosition += stdLen; + } + else + { + if (name[namePosition] == '<') + { + namePosition++; + + stdName = name[namePosition..]; + + int stdNamePosition = namePosition; + + namePosition = GetQZName(name, namePosition, '>'); + + if (name[namePosition] != '>') + { + return false; + } + + stdLen = namePosition - stdNamePosition; + namePosition++; + } + else + { + namePosition = GetTZName(name, namePosition); + stdLen = namePosition; + } + + if (stdLen == 0) + { + return false; + } + + bool isValid = GetOffset(name.ToArray(), ref namePosition, ref stdOffset); + + if (!isValid) + { + return false; + } + } + + int charCount = stdLen + 1; + int destLen = 0; + int dstOffset = 0; + + ReadOnlySpan destName = name[namePosition..]; + + if (TzCharsArraySize < charCount) + { + return false; + } + + if (name[namePosition] != '\0') + { + if (name[namePosition] == '<') + { + destName = name[++namePosition..]; + int destNamePosition = namePosition; + + namePosition = GetQZName(name.ToArray(), namePosition, '>'); + + if (name[namePosition] != '>') + { + return false; + } + + destLen = namePosition - destNamePosition; + namePosition++; + } + else + { + destName = name[namePosition..]; + namePosition = GetTZName(name, namePosition); + destLen = namePosition; + } + + if (destLen == 0) + { + return false; + } + + charCount += destLen + 1; + if (TzCharsArraySize < charCount) + { + return false; + } + + if (name[namePosition] != '\0' && name[namePosition] != ',' && name[namePosition] != ';') + { + bool isValid = GetOffset(name.ToArray(), ref namePosition, ref dstOffset); + + if (!isValid) + { + return false; + } + } + else + { + dstOffset = stdOffset - SecondsPerHour; + } + + if (name[namePosition] == '\0') + { + name = TimeZoneDefaultRule; + namePosition = 0; + } + + if (name[namePosition] == ',' || name[namePosition] == ';') + { + namePosition++; + + bool isRuleValid = GetRule(name, ref namePosition, out Rule start); + if (!isRuleValid) + { + return false; + } + + if (name[namePosition++] != ',') + { + return false; + } + + isRuleValid = GetRule(name, ref namePosition, out Rule end); + if (!isRuleValid) + { + return false; + } + + if (name[namePosition] != '\0') + { + return false; + } + + outRules.TypeCount = 2; + + outRules.Ttis[0] = new TimeTypeInfo + { + GmtOffset = -dstOffset, + IsDaySavingTime = true, + AbbreviationListIndex = stdLen + 1, + }; + + outRules.Ttis[1] = new TimeTypeInfo + { + GmtOffset = -stdOffset, + IsDaySavingTime = false, + AbbreviationListIndex = 0, + }; + + outRules.DefaultType = 0; + + int timeCount = 0; + long janFirst = 0; + int janOffset = 0; + int yearBegining = EpochYear; + + do + { + int yearSeconds = _yearLengths[IsLeap(yearBegining - 1)] * SecondsPerDay; + yearBegining--; + if (IncrementOverflow64(ref janFirst, -yearSeconds)) + { + janOffset = -yearSeconds; + break; + } + } + while (EpochYear - YearsPerRepeat / 2 < yearBegining); + + int yearLimit = yearBegining + YearsPerRepeat + 1; + int year; + for (year = yearBegining; year < yearLimit; year++) + { + int startTime = TransitionTime(year, start, stdOffset); + int endTime = TransitionTime(year, end, dstOffset); + + int yearSeconds = _yearLengths[IsLeap(year)] * SecondsPerDay; + + bool isReversed = endTime < startTime; + if (isReversed) + { + (endTime, startTime) = (startTime, endTime); + } + + if (isReversed || (startTime < endTime && (endTime - startTime < (yearSeconds + (stdOffset - dstOffset))))) + { + if (TzMaxTimes - 2 < timeCount) + { + break; + } + + outRules.Ats[timeCount] = janFirst; + if (!IncrementOverflow64(ref outRules.Ats[timeCount], janOffset + startTime)) + { + outRules.Types[timeCount++] = isReversed ? (byte)1 : (byte)0; + } + else if (janOffset != 0) + { + outRules.DefaultType = isReversed ? 1 : 0; + } + + outRules.Ats[timeCount] = janFirst; + if (!IncrementOverflow64(ref outRules.Ats[timeCount], janOffset + endTime)) + { + outRules.Types[timeCount++] = isReversed ? (byte)0 : (byte)1; + yearLimit = year + YearsPerRepeat + 1; + } + else if (janOffset != 0) + { + outRules.DefaultType = isReversed ? 0 : 1; + } + } + + if (IncrementOverflow64(ref janFirst, janOffset + yearSeconds)) + { + break; + } + + janOffset = 0; + } + + outRules.TimeCount = timeCount; + + // There is no time variation, this is then a perpetual DST rule + if (timeCount == 0) + { + outRules.TypeCount = 1; + } + else if (YearsPerRepeat < year - yearBegining) + { + outRules.GoBack = true; + outRules.GoAhead = true; + } + } + else + { + if (name[namePosition] == '\0') + { + return false; + } + + long theirStdOffset = 0; + for (int i = 0; i < outRules.TimeCount; i++) + { + int j = outRules.Types[i]; + if (outRules.Ttis[j].IsStandardTimeDaylight) + { + theirStdOffset = -outRules.Ttis[j].GmtOffset; + } + } + + long theirDstOffset; + for (int i = 0; i < outRules.TimeCount; i++) + { + int j = outRules.Types[i]; + if (outRules.Ttis[j].IsDaySavingTime) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + theirDstOffset = -outRules.Ttis[j].GmtOffset; +#pragma warning restore IDE0059 + } + } + + bool isDaySavingTime = false; +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long theirOffset = theirStdOffset; +#pragma warning restore IDE0059 + for (int i = 0; i < outRules.TimeCount; i++) + { + int j = outRules.Types[i]; + outRules.Types[i] = outRules.Ttis[j].IsDaySavingTime ? (byte)1 : (byte)0; + if (!outRules.Ttis[j].IsGMT) + { + if (isDaySavingTime && !outRules.Ttis[j].IsStandardTimeDaylight) + { + outRules.Ats[i] += dstOffset - theirStdOffset; + } + else + { + outRules.Ats[i] += stdOffset - theirStdOffset; + } + } + + theirOffset = -outRules.Ttis[j].GmtOffset; + if (outRules.Ttis[j].IsDaySavingTime) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + theirDstOffset = theirOffset; +#pragma warning restore IDE0059 + } + else + { + theirStdOffset = theirOffset; + } + } + + outRules.Ttis[0] = new TimeTypeInfo + { + GmtOffset = -stdOffset, + IsDaySavingTime = false, + AbbreviationListIndex = 0, + }; + + outRules.Ttis[1] = new TimeTypeInfo + { + GmtOffset = -dstOffset, + IsDaySavingTime = true, + AbbreviationListIndex = stdLen + 1, + }; + + outRules.TypeCount = 2; + outRules.DefaultType = 0; + } + } + else + { + // default is perpetual standard time + outRules.TypeCount = 1; + outRules.TimeCount = 0; + outRules.DefaultType = 0; + outRules.Ttis[0] = new TimeTypeInfo + { + GmtOffset = -stdOffset, + IsDaySavingTime = false, + AbbreviationListIndex = 0, + }; + } + + outRules.CharCount = charCount; + + int charsPosition = 0; + + for (int i = 0; i < stdLen; i++) + { + outRules.Chars[i] = stdName[i]; + } + + charsPosition += stdLen; + outRules.Chars[charsPosition++] = 0; + + if (destLen != 0) + { + for (int i = 0; i < destLen; i++) + { + outRules.Chars[charsPosition + i] = destName[i]; + } + outRules.Chars[charsPosition + destLen] = 0; + } + + return true; + } + + private static int TransitionTime(int year, Rule rule, int offset) + { + int leapYear = IsLeap(year); + + int value; + switch (rule.Type) + { + case RuleType.JulianDay: + value = (rule.Day - 1) * SecondsPerDay; + if (leapYear == 1 && rule.Day >= 60) + { + value += SecondsPerDay; + } + break; + + case RuleType.DayOfYear: + value = rule.Day * SecondsPerDay; + break; + + case RuleType.MonthNthDayOfWeek: + // Here we use Zeller's Congruence to get the day of week of the first month. + + int m1 = (rule.Month + 9) % 12 + 1; + int yy0 = (rule.Month <= 2) ? (year - 1) : year; + int yy1 = yy0 / 100; + int yy2 = yy0 % 100; + + int dayOfWeek = ((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7; + + if (dayOfWeek < 0) + { + dayOfWeek += DaysPerWeek; + } + + // Get the zero origin + int d = rule.Day - dayOfWeek; + + if (d < 0) + { + d += DaysPerWeek; + } + + for (int i = 1; i < rule.Week; i++) + { + if (d + DaysPerWeek >= _monthsLengths[leapYear][rule.Month - 1]) + { + break; + } + + d += DaysPerWeek; + } + + value = d * SecondsPerDay; + for (int i = 0; i < rule.Month - 1; i++) + { + value += _monthsLengths[leapYear][i] * SecondsPerDay; + } + + break; + default: + throw new NotImplementedException("Unknown time transition!"); + } + + return value + rule.TransitionTime + offset; + } + + private static bool NormalizeOverflow32(ref int ip, ref int unit, int baseValue) + { + int delta; + + if (unit >= 0) + { + delta = unit / baseValue; + } + else + { + delta = -1 - (-1 - unit) / baseValue; + } + + unit -= delta * baseValue; + + return IncrementOverflow32(ref ip, delta); + } + + private static bool NormalizeOverflow64(ref long ip, ref long unit, long baseValue) + { + long delta; + + if (unit >= 0) + { + delta = unit / baseValue; + } + else + { + delta = -1 - (-1 - unit) / baseValue; + } + + unit -= delta * baseValue; + + return IncrementOverflow64(ref ip, delta); + } + + private static bool IncrementOverflow32(ref int time, int j) + { + try + { + time = checked(time + j); + + return false; + } + catch (OverflowException) + { + return true; + } + } + + private static bool IncrementOverflow64(ref long time, long j) + { + try + { + time = checked(time + j); + + return false; + } + catch (OverflowException) + { + return true; + } + } + + internal static bool ParsePosixName(string name, ref TimeZoneRule outRules) + { + return ParsePosixName(Encoding.ASCII.GetBytes(name), ref outRules, false); + } + + internal static bool ParseTimeZoneBinary(ref TimeZoneRule outRules, Stream inputData) + { + outRules = new TimeZoneRule(); + + BinaryReader reader = new(inputData); + + long streamLength = reader.BaseStream.Length; + + if (streamLength < Unsafe.SizeOf()) + { + return false; + } + + TzifHeader header = reader.ReadStruct(); + + streamLength -= Unsafe.SizeOf(); + + int ttisGMTCount = Detzcode32(header.TtisGMTCount); + int ttisSTDCount = Detzcode32(header.TtisSTDCount); + int leapCount = Detzcode32(header.LeapCount); + int timeCount = Detzcode32(header.TimeCount); + int typeCount = Detzcode32(header.TypeCount); + int charCount = Detzcode32(header.CharCount); + + if (!(0 <= leapCount + && leapCount < TzMaxLeaps + && 0 < typeCount + && typeCount < TzMaxTypes + && 0 <= timeCount + && timeCount < TzMaxTimes + && 0 <= charCount + && charCount < TzMaxChars + && (ttisSTDCount == typeCount || ttisSTDCount == 0) + && (ttisGMTCount == typeCount || ttisGMTCount == 0))) + { + return false; + } + + + if (streamLength < (timeCount * TimeTypeSize + + timeCount + + typeCount * 6 + + charCount + + leapCount * (TimeTypeSize + 4) + + ttisSTDCount + + ttisGMTCount)) + { + return false; + } + + outRules.TimeCount = timeCount; + outRules.TypeCount = typeCount; + outRules.CharCount = charCount; + + byte[] workBuffer = StreamUtils.StreamToBytes(inputData); + + timeCount = 0; + + { + Span p = workBuffer; + for (int i = 0; i < outRules.TimeCount; i++) + { + long at = Detzcode64(p); + outRules.Types[i] = 1; + + if (timeCount != 0 && at <= outRules.Ats[timeCount - 1]) + { + if (at < outRules.Ats[timeCount - 1]) + { + return false; + } + + outRules.Types[i - 1] = 0; + timeCount--; + } + + outRules.Ats[timeCount++] = at; + + p = p[TimeTypeSize..]; + } + + timeCount = 0; + for (int i = 0; i < outRules.TimeCount; i++) + { + byte type = p[0]; + p = p[1..]; + + if (outRules.TypeCount <= type) + { + return false; + } + + if (outRules.Types[i] != 0) + { + outRules.Types[timeCount++] = type; + } + } + + outRules.TimeCount = timeCount; + + for (int i = 0; i < outRules.TypeCount; i++) + { + TimeTypeInfo ttis = outRules.Ttis[i]; + ttis.GmtOffset = Detzcode32(p); + p = p[sizeof(int)..]; + + if (p[0] >= 2) + { + return false; + } + + ttis.IsDaySavingTime = p[0] != 0; + p = p[1..]; + + int abbreviationListIndex = p[0]; + p = p[1..]; + + if (abbreviationListIndex >= outRules.CharCount) + { + return false; + } + + ttis.AbbreviationListIndex = abbreviationListIndex; + + outRules.Ttis[i] = ttis; + } + + p[..outRules.CharCount].CopyTo(outRules.Chars); + + p = p[outRules.CharCount..]; + outRules.Chars[outRules.CharCount] = 0; + + for (int i = 0; i < outRules.TypeCount; i++) + { + if (ttisSTDCount == 0) + { + outRules.Ttis[i].IsStandardTimeDaylight = false; + } + else + { + if (p[0] >= 2) + { + return false; + } + + outRules.Ttis[i].IsStandardTimeDaylight = p[0] != 0; + p = p[1..]; + } + } + + for (int i = 0; i < outRules.TypeCount; i++) + { + if (ttisSTDCount == 0) + { + outRules.Ttis[i].IsGMT = false; + } + else + { + if (p[0] >= 2) + { + return false; + } + + outRules.Ttis[i].IsGMT = p[0] != 0; + p = p[1..]; + } + + } + + long position = (workBuffer.Length - p.Length); + long nRead = streamLength - position; + + if (nRead < 0) + { + return false; + } + + // Nintendo abort in case of a TzIf file with a POSIX TZ Name too long to fit inside a TimeZoneRule. + // As it's impossible in normal usage to achive this, we also force a crash. + if (nRead > (TzNameMax + 1)) + { + throw new InvalidOperationException(); + } + + byte[] tempName = new byte[TzNameMax + 1]; + Array.Copy(workBuffer, position, tempName, 0, nRead); + + if (nRead > 2 && tempName[0] == '\n' && tempName[nRead - 1] == '\n' && outRules.TypeCount + 2 <= TzMaxTypes) + { + tempName[nRead - 1] = 0; + + byte[] name = new byte[TzNameMax]; + Array.Copy(tempName, 1, name, 0, nRead - 1); + + Box tempRulesBox = new(); + ref TimeZoneRule tempRules = ref tempRulesBox.Data; + + if (ParsePosixName(name, ref tempRulesBox.Data, false)) + { + int abbreviationCount = 0; + charCount = outRules.CharCount; + + Span chars = outRules.Chars; + + for (int i = 0; i < tempRules.TypeCount; i++) + { + ReadOnlySpan tempChars = tempRules.Chars; + ReadOnlySpan tempAbbreviation = tempChars[tempRules.Ttis[i].AbbreviationListIndex..]; + + int j; + + for (j = 0; j < charCount; j++) + { + if (StringUtils.CompareCStr(chars[j..], tempAbbreviation) == 0) + { + tempRules.Ttis[i].AbbreviationListIndex = j; + abbreviationCount++; + break; + } + } + + if (j >= charCount) + { + int abbreviationLength = StringUtils.LengthCstr(tempAbbreviation); + if (j + abbreviationLength < TzMaxChars) + { + for (int x = 0; x < abbreviationLength; x++) + { + chars[j + x] = tempAbbreviation[x]; + } + + charCount = j + abbreviationLength + 1; + + tempRules.Ttis[i].AbbreviationListIndex = j; + abbreviationCount++; + } + } + } + + if (abbreviationCount == tempRules.TypeCount) + { + outRules.CharCount = charCount; + + // Remove trailing + while (1 < outRules.TimeCount && (outRules.Types[outRules.TimeCount - 1] == outRules.Types[outRules.TimeCount - 2])) + { + outRules.TimeCount--; + } + + int i; + + for (i = 0; i < tempRules.TimeCount; i++) + { + if (outRules.TimeCount == 0 || outRules.Ats[outRules.TimeCount - 1] < tempRules.Ats[i]) + { + break; + } + } + + while (i < tempRules.TimeCount && outRules.TimeCount < TzMaxTimes) + { + outRules.Ats[outRules.TimeCount] = tempRules.Ats[i]; + outRules.Types[outRules.TimeCount] = (byte)(outRules.TypeCount + (byte)tempRules.Types[i]); + + outRules.TimeCount++; + i++; + } + + for (i = 0; i < tempRules.TypeCount; i++) + { + outRules.Ttis[outRules.TypeCount++] = tempRules.Ttis[i]; + } + } + } + } + + if (outRules.TypeCount == 0) + { + return false; + } + + if (outRules.TimeCount > 1) + { + for (int i = 1; i < outRules.TimeCount; i++) + { + if (TimeTypeEquals(in outRules, outRules.Types[i], outRules.Types[0]) && DifferByRepeat(outRules.Ats[i], outRules.Ats[0])) + { + outRules.GoBack = true; + break; + } + } + + for (int i = outRules.TimeCount - 2; i >= 0; i--) + { + if (TimeTypeEquals(in outRules, outRules.Types[outRules.TimeCount - 1], outRules.Types[i]) && DifferByRepeat(outRules.Ats[outRules.TimeCount - 1], outRules.Ats[i])) + { + outRules.GoAhead = true; + break; + } + } + } + + int defaultType; + + for (defaultType = 0; defaultType < outRules.TimeCount; defaultType++) + { + if (outRules.Types[defaultType] == 0) + { + break; + } + } + + defaultType = defaultType < outRules.TimeCount ? -1 : 0; + + if (defaultType < 0 && outRules.TimeCount > 0 && outRules.Ttis[outRules.Types[0]].IsDaySavingTime) + { + defaultType = outRules.Types[0]; + while (--defaultType >= 0) + { + if (!outRules.Ttis[defaultType].IsDaySavingTime) + { + break; + } + } + } + + if (defaultType < 0) + { + defaultType = 0; + while (outRules.Ttis[defaultType].IsDaySavingTime) + { + if (++defaultType >= outRules.TypeCount) + { + defaultType = 0; + break; + } + } + } + + outRules.DefaultType = defaultType; + } + + return true; + } + + private static long GetLeapDaysNotNeg(long year) + { + return year / 4 - year / 100 + year / 400; + } + + private static long GetLeapDays(long year) + { + if (year < 0) + { + return -1 - GetLeapDaysNotNeg(-1 - year); + } + else + { + return GetLeapDaysNotNeg(year); + } + } + + private static ResultCode CreateCalendarTime(long time, int gmtOffset, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo) + { + long year = EpochYear; + long timeDays = time / SecondsPerDay; + long remainingSeconds = time % SecondsPerDay; + + calendarTime = new CalendarTimeInternal(); + calendarAdditionalInfo = new CalendarAdditionalInfo(); + + while (timeDays < 0 || timeDays >= _yearLengths[IsLeap((int)year)]) + { + long timeDelta = timeDays / DaysPerLYear; + long delta = timeDelta; + + if (delta == 0) + { + delta = timeDays < 0 ? -1 : 1; + } + + long newYear = year; + + if (IncrementOverflow64(ref newYear, delta)) + { + return ResultCode.OutOfRange; + } + + long leapDays = GetLeapDays(newYear - 1) - GetLeapDays(year - 1); + timeDays -= (newYear - year) * DaysPerNYear; + timeDays -= leapDays; + year = newYear; + } + + long dayOfYear = timeDays; + remainingSeconds += gmtOffset; + while (remainingSeconds < 0) + { + remainingSeconds += SecondsPerDay; + dayOfYear -= 1; + } + + while (remainingSeconds >= SecondsPerDay) + { + remainingSeconds -= SecondsPerDay; + dayOfYear += 1; + } + + while (dayOfYear < 0) + { + if (IncrementOverflow64(ref year, -1)) + { + return ResultCode.OutOfRange; + } + + dayOfYear += _yearLengths[IsLeap((int)year)]; + } + + while (dayOfYear >= _yearLengths[IsLeap((int)year)]) + { + dayOfYear -= _yearLengths[IsLeap((int)year)]; + + if (IncrementOverflow64(ref year, 1)) + { + return ResultCode.OutOfRange; + } + } + + calendarTime.Year = year; + calendarAdditionalInfo.DayOfYear = (uint)dayOfYear; + + long dayOfWeek = (EpochWeekDay + ((year - EpochYear) % DaysPerWeek) * (DaysPerNYear % DaysPerWeek) + GetLeapDays(year - 1) - GetLeapDays(EpochYear - 1) + dayOfYear) % DaysPerWeek; + if (dayOfWeek < 0) + { + dayOfWeek += DaysPerWeek; + } + + calendarAdditionalInfo.DayOfWeek = (uint)dayOfWeek; + + calendarTime.Hour = (sbyte)((remainingSeconds / SecondsPerHour) % SecondsPerHour); + remainingSeconds %= SecondsPerHour; + + calendarTime.Minute = (sbyte)(remainingSeconds / SecondsPerMinute); + calendarTime.Second = (sbyte)(remainingSeconds % SecondsPerMinute); + + int[] ip = _monthsLengths[IsLeap((int)year)]; + + for (calendarTime.Month = 0; dayOfYear >= ip[calendarTime.Month]; ++calendarTime.Month) + { + dayOfYear -= ip[calendarTime.Month]; + } + + calendarTime.Day = (sbyte)(dayOfYear + 1); + + calendarAdditionalInfo.IsDaySavingTime = false; + calendarAdditionalInfo.GmtOffset = gmtOffset; + + return 0; + } + + private static ResultCode ToCalendarTimeInternal(in TimeZoneRule rules, long time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo) + { + calendarTime = new CalendarTimeInternal(); + calendarAdditionalInfo = new CalendarAdditionalInfo(); + + ResultCode result; + + if ((rules.GoAhead && time < rules.Ats[0]) || (rules.GoBack && time > rules.Ats[rules.TimeCount - 1])) + { + long newTime = time; + + long seconds; + long years; + + if (time < rules.Ats[0]) + { + seconds = rules.Ats[0] - time; + } + else + { + seconds = time - rules.Ats[rules.TimeCount - 1]; + } + + seconds -= 1; + + years = (seconds / SecondsPerRepeat + 1) * YearsPerRepeat; + seconds = years * AverageSecondsPerYear; + + if (time < rules.Ats[0]) + { + newTime += seconds; + } + else + { + newTime -= seconds; + } + + if (newTime < rules.Ats[0] && newTime > rules.Ats[rules.TimeCount - 1]) + { + return ResultCode.TimeNotFound; + } + + result = ToCalendarTimeInternal(in rules, newTime, out calendarTime, out calendarAdditionalInfo); + if (result != 0) + { + return result; + } + + if (time < rules.Ats[0]) + { + calendarTime.Year -= years; + } + else + { + calendarTime.Year += years; + } + + return ResultCode.Success; + } + + int ttiIndex; + + if (rules.TimeCount == 0 || time < rules.Ats[0]) + { + ttiIndex = rules.DefaultType; + } + else + { + int low = 1; + int high = rules.TimeCount; + + while (low < high) + { + int mid = (low + high) >> 1; + + if (time < rules.Ats[mid]) + { + high = mid; + } + else + { + low = mid + 1; + } + } + + ttiIndex = rules.Types[low - 1]; + } + + result = CreateCalendarTime(time, rules.Ttis[ttiIndex].GmtOffset, out calendarTime, out calendarAdditionalInfo); + + if (result == 0) + { + calendarAdditionalInfo.IsDaySavingTime = rules.Ttis[ttiIndex].IsDaySavingTime; + + ReadOnlySpan timeZoneAbbreviation = rules.Chars[rules.Ttis[ttiIndex].AbbreviationListIndex..]; + + int timeZoneSize = Math.Min(StringUtils.LengthCstr(timeZoneAbbreviation), 8); + + timeZoneAbbreviation[..timeZoneSize].CopyTo(calendarAdditionalInfo.TimezoneName.AsSpan()); + } + + return result; + } + + private static ResultCode ToPosixTimeInternal(in TimeZoneRule rules, CalendarTimeInternal calendarTime, out long posixTime) + { + posixTime = 0; + + int hour = calendarTime.Hour; + int minute = calendarTime.Minute; + + if (NormalizeOverflow32(ref hour, ref minute, MinutesPerHour)) + { + return ResultCode.Overflow; + } + + calendarTime.Minute = (sbyte)minute; + + int day = calendarTime.Day; + if (NormalizeOverflow32(ref day, ref hour, HoursPerDays)) + { + return ResultCode.Overflow; + } + + calendarTime.Day = (sbyte)day; + calendarTime.Hour = (sbyte)hour; + + long year = calendarTime.Year; + long month = calendarTime.Month; + + if (NormalizeOverflow64(ref year, ref month, MonthsPerYear)) + { + return ResultCode.Overflow; + } + + calendarTime.Month = (sbyte)month; + + if (IncrementOverflow64(ref year, YearBase)) + { + return ResultCode.Overflow; + } + + while (day <= 0) + { + if (IncrementOverflow64(ref year, -1)) + { + return ResultCode.Overflow; + } + + long li = year; + + if (1 < calendarTime.Month) + { + li++; + } + + day += _yearLengths[IsLeap((int)li)]; + } + + while (day > DaysPerLYear) + { + long li = year; + + if (1 < calendarTime.Month) + { + li++; + } + + day -= _yearLengths[IsLeap((int)li)]; + + if (IncrementOverflow64(ref year, 1)) + { + return ResultCode.Overflow; + } + } + + while (true) + { + int i = _monthsLengths[IsLeap((int)year)][calendarTime.Month]; + + if (day <= i) + { + break; + } + + day -= i; + calendarTime.Month += 1; + + if (calendarTime.Month >= MonthsPerYear) + { + calendarTime.Month = 0; + if (IncrementOverflow64(ref year, 1)) + { + return ResultCode.Overflow; + } + } + } + + calendarTime.Day = (sbyte)day; + + if (IncrementOverflow64(ref year, -YearBase)) + { + return ResultCode.Overflow; + } + + calendarTime.Year = year; + + int savedSeconds; + + if (calendarTime.Second >= 0 && calendarTime.Second < SecondsPerMinute) + { + savedSeconds = 0; + } + else if (year + YearBase < EpochYear) + { + int second = calendarTime.Second; + if (IncrementOverflow32(ref second, 1 - SecondsPerMinute)) + { + return ResultCode.Overflow; + } + + savedSeconds = second; + calendarTime.Second = 1 - SecondsPerMinute; + } + else + { + savedSeconds = calendarTime.Second; + calendarTime.Second = 0; + } + + long low = long.MinValue; + long high = long.MaxValue; + + while (true) + { + long pivot = low / 2 + high / 2; + + if (pivot < low) + { + pivot = low; + } + else if (pivot > high) + { + pivot = high; + } + + int direction; + + ResultCode result = ToCalendarTimeInternal(in rules, pivot, out CalendarTimeInternal candidateCalendarTime, out _); + if (result != 0) + { + if (pivot > 0) + { + direction = 1; + } + else + { + direction = -1; + } + } + else + { + direction = candidateCalendarTime.CompareTo(calendarTime); + } + + if (direction == 0) + { + long timeResult = pivot + savedSeconds; + + if ((timeResult < pivot) != (savedSeconds < 0)) + { + return ResultCode.Overflow; + } + + posixTime = timeResult; + break; + } + else + { + if (pivot == low) + { + if (pivot == long.MaxValue) + { + return ResultCode.TimeNotFound; + } + + pivot += 1; + low += 1; + } + else if (pivot == high) + { + if (pivot == long.MinValue) + { + return ResultCode.TimeNotFound; + } + + pivot -= 1; + high -= 1; + } + + if (low > high) + { + return ResultCode.TimeNotFound; + } + + if (direction > 0) + { + high = pivot; + } + else + { + low = pivot; + } + } + } + + return ResultCode.Success; + } + + internal static ResultCode ToCalendarTime(in TimeZoneRule rules, long time, out CalendarInfo calendar) + { + ResultCode result = ToCalendarTimeInternal(in rules, time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo); + + calendar = new CalendarInfo() + { + Time = new CalendarTime() + { + Year = (short)calendarTime.Year, + // NOTE: Nintendo's month range is 1-12, internal range is 0-11. + Month = (sbyte)(calendarTime.Month + 1), + Day = calendarTime.Day, + Hour = calendarTime.Hour, + Minute = calendarTime.Minute, + Second = calendarTime.Second, + }, + AdditionalInfo = calendarAdditionalInfo, + }; + + return result; + } + + internal static ResultCode ToPosixTime(in TimeZoneRule rules, CalendarTime calendarTime, out long posixTime) + { + CalendarTimeInternal calendarTimeInternal = new() + { + Year = calendarTime.Year, + // NOTE: Nintendo's month range is 1-12, internal range is 0-11. + Month = (sbyte)(calendarTime.Month - 1), + Day = calendarTime.Day, + Hour = calendarTime.Hour, + Minute = calendarTime.Minute, + Second = calendarTime.Second, + }; + + return ToPosixTimeInternal(in rules, calendarTimeInternal, out posixTime); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs new file mode 100644 index 00000000..abf3cd7d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs @@ -0,0 +1,302 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using TimeZoneRuleBox = Ryujinx.Common.Memory.Box; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + public class TimeZoneContentManager + { + private const long TimeZoneBinaryTitleId = 0x010000000000080E; + + private const string TimeZoneSystemTitleMissingErrorMessage = "TimeZoneBinary system title not found! TimeZone conversions will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide#initial-setup-continued---installation-of-firmware for more information)"; + + private VirtualFileSystem _virtualFileSystem; + private IntegrityCheckLevel _fsIntegrityCheckLevel; + private ContentManager _contentManager; + + public string[] LocationNameCache { get; private set; } + + internal TimeZoneManager Manager { get; private set; } + + public TimeZoneContentManager() + { + Manager = new TimeZoneManager(); + } + + public void InitializeInstance(VirtualFileSystem virtualFileSystem, ContentManager contentManager, IntegrityCheckLevel fsIntegrityCheckLevel) + { + _virtualFileSystem = virtualFileSystem; + _contentManager = contentManager; + _fsIntegrityCheckLevel = fsIntegrityCheckLevel; + + InitializeLocationNameCache(); + } + + public string SanityCheckDeviceLocationName(string locationName) + { + if (IsLocationNameValid(locationName)) + { + return locationName; + } + + Logger.Warning?.Print(LogClass.ServiceTime, $"Invalid device TimeZone {locationName}, switching back to UTC"); + + return "UTC"; + } + + internal void Initialize(TimeManager timeManager, Switch device) + { + InitializeInstance(device.FileSystem, device.System.ContentManager, device.System.FsIntegrityCheckLevel); + + ITickSource tickSource = device.System.TickSource; + + SteadyClockTimePoint timeZoneUpdatedTimePoint = timeManager.StandardSteadyClock.GetCurrentTimePoint(tickSource); + + string deviceLocationName = SanityCheckDeviceLocationName(device.Configuration.TimeZone); + + ResultCode result = GetTimeZoneBinary(deviceLocationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile); + + if (result == ResultCode.Success) + { + // TODO: Read TimeZoneVersion from sysarchive. + timeManager.SetupTimeZoneManager(deviceLocationName, timeZoneUpdatedTimePoint, (uint)LocationNameCache.Length, new UInt128(), timeZoneBinaryStream); + + ncaFile.Dispose(); + } + else + { + // In the case the user don't have the timezone system archive, we just mark the manager as initialized. + Manager.MarkInitialized(); + } + } + + private void InitializeLocationNameCache() + { + if (HasTimeZoneBinaryTitle()) + { + using IStorage ncaFileStream = new LocalStorage(VirtualFileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open); + + Nca nca = new(_virtualFileSystem.KeySet, ncaFileStream); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel); + + using var binaryListFile = new UniqueRef(); + + romfs.OpenFile(ref binaryListFile.Ref, "/binaryList.txt".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + StreamReader reader = new(binaryListFile.Get.AsStream()); + + List locationNameList = new(); + + string locationName; + while ((locationName = reader.ReadLine()) != null) + { + locationNameList.Add(locationName); + } + + LocationNameCache = locationNameList.ToArray(); + } + else + { + LocationNameCache = new[] { "UTC" }; + + Logger.Error?.Print(LogClass.ServiceTime, TimeZoneSystemTitleMissingErrorMessage); + } + } + + public IEnumerable<(int Offset, string Location, string Abbr)> ParseTzOffsets() + { + var tzBinaryContentPath = GetTimeZoneBinaryTitleContentPath(); + + if (string.IsNullOrEmpty(tzBinaryContentPath)) + { + return new[] { (0, "UTC", "UTC") }; + } + + List<(int Offset, string Location, string Abbr)> outList = new(); + var now = DateTimeOffset.Now.ToUnixTimeSeconds(); + using (IStorage ncaStorage = new LocalStorage(VirtualFileSystem.SwitchPathToSystemPath(tzBinaryContentPath), FileAccess.Read, FileMode.Open)) + using (IFileSystem romfs = new Nca(_virtualFileSystem.KeySet, ncaStorage).OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel)) + { + foreach (string locName in LocationNameCache) + { + if (locName.StartsWith("Etc")) + { + continue; + } + + using var tzif = new UniqueRef(); + + if (romfs.OpenFile(ref tzif.Ref, $"/zoneinfo/{locName}".ToU8Span(), OpenMode.Read).IsFailure()) + { + Logger.Error?.Print(LogClass.ServiceTime, $"Error opening /zoneinfo/{locName}"); + continue; + } + + TimeZoneRuleBox tzRuleBox = new(); + ref TimeZoneRule tzRule = ref tzRuleBox.Data; + + TimeZone.ParseTimeZoneBinary(ref tzRule, tzif.Get.AsStream()); + + + TimeTypeInfo ttInfo; + if (tzRule.TimeCount > 0) // Find the current transition period + { + int fin = 0; + for (int i = 0; i < tzRule.TimeCount; ++i) + { + if (tzRule.Ats[i] <= now) + { + fin = i; + } + } + ttInfo = tzRule.Ttis[tzRule.Types[fin]]; + } + else if (tzRule.TypeCount >= 1) // Otherwise, use the first offset in TTInfo + { + ttInfo = tzRule.Ttis[0]; + } + else + { + Logger.Error?.Print(LogClass.ServiceTime, $"Couldn't find UTC offset for zone {locName}"); + continue; + } + + var abbrStart = tzRule.Chars[ttInfo.AbbreviationListIndex..]; + int abbrEnd = abbrStart.IndexOf((byte)0); + + outList.Add((ttInfo.GmtOffset, locName, Encoding.UTF8.GetString(abbrStart[..abbrEnd]))); + } + } + + outList.Sort(); + + return outList; + } + + private bool IsLocationNameValid(string locationName) + { + foreach (string cachedLocationName in LocationNameCache) + { + if (cachedLocationName.Equals(locationName)) + { + return true; + } + } + + return false; + } + + public ResultCode SetDeviceLocationName(string locationName) + { + ResultCode result = GetTimeZoneBinary(locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile); + + if (result == ResultCode.Success) + { + result = Manager.SetDeviceLocationNameWithTimeZoneRule(locationName, timeZoneBinaryStream); + + ncaFile.Dispose(); + } + + return result; + } + + public ResultCode LoadLocationNameList(uint index, out string[] outLocationNameArray, uint maxLength) + { + List locationNameList = new(); + + for (int i = 0; i < LocationNameCache.Length && i < maxLength; i++) + { + if (i < index) + { + continue; + } + + string locationName = LocationNameCache[i]; + + // If the location name is too long, error out. + if (locationName.Length > 0x24) + { + outLocationNameArray = Array.Empty(); + + return ResultCode.LocationNameTooLong; + } + + locationNameList.Add(locationName); + } + + outLocationNameArray = locationNameList.ToArray(); + + return ResultCode.Success; + } + + public string GetTimeZoneBinaryTitleContentPath() + { + return _contentManager.GetInstalledContentPath(TimeZoneBinaryTitleId, StorageId.BuiltInSystem, NcaContentType.Data); + } + + public bool HasTimeZoneBinaryTitle() + { + return !string.IsNullOrEmpty(GetTimeZoneBinaryTitleContentPath()); + } + + internal ResultCode GetTimeZoneBinary(string locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile) + { + timeZoneBinaryStream = null; + ncaFile = null; + + if (!HasTimeZoneBinaryTitle() || !IsLocationNameValid(locationName)) + { + return ResultCode.TimeZoneNotFound; + } + + ncaFile = new LocalStorage(VirtualFileSystem.SwitchPathToSystemPath(GetTimeZoneBinaryTitleContentPath()), FileAccess.Read, FileMode.Open); + + Nca nca = new(_virtualFileSystem.KeySet, ncaFile); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _fsIntegrityCheckLevel); + + using var timeZoneBinaryFile = new UniqueRef(); + + Result result = romfs.OpenFile(ref timeZoneBinaryFile.Ref, $"/zoneinfo/{locationName}".ToU8Span(), OpenMode.Read); + + timeZoneBinaryStream = timeZoneBinaryFile.Release().AsStream(); + + return (ResultCode)result.Value; + } + + internal ResultCode LoadTimeZoneRule(ref TimeZoneRule rules, string locationName) + { + rules = default; + + if (!HasTimeZoneBinaryTitle()) + { + throw new InvalidSystemResourceException(TimeZoneSystemTitleMissingErrorMessage); + } + + ResultCode result = GetTimeZoneBinary(locationName, out Stream timeZoneBinaryStream, out LocalStorage ncaFile); + + if (result == ResultCode.Success) + { + result = Manager.ParseTimeZoneRuleBinary(ref rules, timeZoneBinaryStream); + + ncaFile.Dispose(); + } + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs new file mode 100644 index 00000000..3b57b180 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs @@ -0,0 +1,260 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Time.Clock; +using System; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + class TimeZoneManager + { + private bool _isInitialized; + private Box _myRules; + private string _deviceLocationName; + private UInt128 _timeZoneRuleVersion; + private uint _totalLocationNameCount; + private SteadyClockTimePoint _timeZoneUpdateTimePoint; + private readonly object _lock = new(); + + public TimeZoneManager() + { + _isInitialized = false; + _deviceLocationName = "UTC"; + _timeZoneRuleVersion = new UInt128(); + _myRules = new Box(); + + _timeZoneUpdateTimePoint = SteadyClockTimePoint.GetRandom(); + } + + public bool IsInitialized() + { + bool res; + + lock (_lock) + { + res = _isInitialized; + } + + return res; + } + + public void MarkInitialized() + { + lock (_lock) + { + _isInitialized = true; + } + } + + public ResultCode GetDeviceLocationName(out string deviceLocationName) + { + ResultCode result = ResultCode.UninitializedClock; + + deviceLocationName = null; + + lock (_lock) + { + if (_isInitialized) + { + deviceLocationName = _deviceLocationName; + result = ResultCode.Success; + } + } + + return result; + } + + public ResultCode SetDeviceLocationNameWithTimeZoneRule(string locationName, Stream timeZoneBinaryStream) + { + ResultCode result = ResultCode.TimeZoneConversionFailed; + + lock (_lock) + { + Box rules = new(); + + bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(ref rules.Data, timeZoneBinaryStream); + + if (timeZoneConversionSuccess) + { + _deviceLocationName = locationName; + _myRules = rules; + result = ResultCode.Success; + } + } + + return result; + } + + public void SetTotalLocationNameCount(uint totalLocationNameCount) + { + lock (_lock) + { + _totalLocationNameCount = totalLocationNameCount; + } + } + + public ResultCode GetTotalLocationNameCount(out uint totalLocationNameCount) + { + ResultCode result = ResultCode.UninitializedClock; + + totalLocationNameCount = 0; + + lock (_lock) + { + if (_isInitialized) + { + totalLocationNameCount = _totalLocationNameCount; + result = ResultCode.Success; + } + } + + return result; + } + + public ResultCode SetUpdatedTime(SteadyClockTimePoint timeZoneUpdatedTimePoint, bool bypassUninitialized = false) + { + ResultCode result = ResultCode.UninitializedClock; + + lock (_lock) + { + if (_isInitialized || bypassUninitialized) + { + _timeZoneUpdateTimePoint = timeZoneUpdatedTimePoint; + result = ResultCode.Success; + } + } + + return result; + } + + public ResultCode GetUpdatedTime(out SteadyClockTimePoint timeZoneUpdatedTimePoint) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + timeZoneUpdatedTimePoint = _timeZoneUpdateTimePoint; + result = ResultCode.Success; + } + else + { + timeZoneUpdatedTimePoint = SteadyClockTimePoint.GetRandom(); + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ParseTimeZoneRuleBinary(ref TimeZoneRule outRules, Stream timeZoneBinaryStream) + { + ResultCode result = ResultCode.Success; + + lock (_lock) + { + bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(ref outRules, timeZoneBinaryStream); + + if (!timeZoneConversionSuccess) + { + result = ResultCode.TimeZoneConversionFailed; + } + } + + return result; + } + + public void SetTimeZoneRuleVersion(UInt128 timeZoneRuleVersion) + { + lock (_lock) + { + _timeZoneRuleVersion = timeZoneRuleVersion; + } + } + + public ResultCode GetTimeZoneRuleVersion(out UInt128 timeZoneRuleVersion) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + timeZoneRuleVersion = _timeZoneRuleVersion; + result = ResultCode.Success; + } + else + { + timeZoneRuleVersion = new UInt128(); + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ToCalendarTimeWithMyRules(long time, out CalendarInfo calendar) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + result = ToCalendarTime(in _myRules.Data, time, out calendar); + } + else + { + calendar = new CalendarInfo(); + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ToCalendarTime(in TimeZoneRule rules, long time, out CalendarInfo calendar) + { + ResultCode result; + + lock (_lock) + { + result = TimeZone.ToCalendarTime(in rules, time, out calendar); + } + + return result; + } + + public ResultCode ToPosixTimeWithMyRules(CalendarTime calendarTime, out long posixTime) + { + ResultCode result; + + lock (_lock) + { + if (_isInitialized) + { + result = ToPosixTime(in _myRules.Data, calendarTime, out posixTime); + } + else + { + posixTime = 0; + result = ResultCode.UninitializedClock; + } + } + + return result; + } + + public ResultCode ToPosixTime(in TimeZoneRule rules, CalendarTime calendarTime, out long posixTime) + { + ResultCode result; + + lock (_lock) + { + result = TimeZone.ToPosixTime(in rules, calendarTime, out posixTime); + } + + return result; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarAdditionalInfo.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarAdditionalInfo.cs new file mode 100644 index 00000000..331690cc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarAdditionalInfo.cs @@ -0,0 +1,21 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x18, CharSet = CharSet.Ansi)] + struct CalendarAdditionalInfo + { + public uint DayOfWeek; + public uint DayOfYear; + + public Array8 TimezoneName; + + [MarshalAs(UnmanagedType.I1)] + public bool IsDaySavingTime; + + public Array3 Padding; + + public int GmtOffset; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarInfo.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarInfo.cs new file mode 100644 index 00000000..c4c1ca00 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarInfo.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x20, CharSet = CharSet.Ansi)] + struct CalendarInfo + { + public CalendarTime Time; + public CalendarAdditionalInfo AdditionalInfo; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarTime.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarTime.cs new file mode 100644 index 00000000..16769ebf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarTime.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x8)] + struct CalendarTime + { + public short Year; + public sbyte Month; + public sbyte Day; + public sbyte Hour; + public sbyte Minute; + public sbyte Second; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeTypeInfo.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeTypeInfo.cs new file mode 100644 index 00000000..4ec58a68 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeTypeInfo.cs @@ -0,0 +1,28 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 4)] + public struct TimeTypeInfo + { + public const int Size = 0x10; + + public int GmtOffset; + + [MarshalAs(UnmanagedType.I1)] + public bool IsDaySavingTime; + + public Array3 Padding1; + + public int AbbreviationListIndex; + + [MarshalAs(UnmanagedType.I1)] + public bool IsStandardTimeDaylight; + + [MarshalAs(UnmanagedType.I1)] + public bool IsGMT; + + public ushort Padding2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeZoneRule.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeZoneRule.cs new file mode 100644 index 00000000..9f5c3410 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeZoneRule.cs @@ -0,0 +1,56 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x4000, CharSet = CharSet.Ansi)] + public struct TimeZoneRule + { + public const int TzMaxTypes = 128; + public const int TzMaxChars = 50; + public const int TzMaxLeaps = 50; + public const int TzMaxTimes = 1000; + public const int TzNameMax = 255; + public const int TzCharsArraySize = 2 * (TzNameMax + 1); + + public int TimeCount; + public int TypeCount; + public int CharCount; + + [MarshalAs(UnmanagedType.I1)] + public bool GoBack; + + [MarshalAs(UnmanagedType.I1)] + public bool GoAhead; + + [StructLayout(LayoutKind.Sequential, Size = sizeof(long) * TzMaxTimes)] + private struct AtsStorageStruct { } + + private AtsStorageStruct _ats; + + public Span Ats => SpanHelpers.AsSpan(ref _ats); + + [StructLayout(LayoutKind.Sequential, Size = sizeof(byte) * TzMaxTimes)] + private struct TypesStorageStruct { } + + private TypesStorageStruct _types; + + public Span Types => SpanHelpers.AsByteSpan(ref _types); + + [StructLayout(LayoutKind.Sequential, Size = TimeTypeInfo.Size * TzMaxTypes)] + private struct TimeTypeInfoStorageStruct { } + + private TimeTypeInfoStorageStruct _ttis; + + public Span Ttis => SpanHelpers.AsSpan(ref _ttis); + + [StructLayout(LayoutKind.Sequential, Size = sizeof(byte) * TzCharsArraySize)] + private struct CharsStorageStruct { } + + private CharsStorageStruct _chars; + public Span Chars => SpanHelpers.AsByteSpan(ref _chars); + + public int DefaultType; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TzifHeader.cs b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TzifHeader.cs new file mode 100644 index 00000000..33dca605 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TzifHeader.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.TimeZone +{ + [StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x2C)] + struct TzifHeader + { + public Array4 Magic; + public byte Version; + private Array15 _reserved; + public int TtisGMTCount; + public int TtisSTDCount; + public int LeapCount; + public int TimeCount; + public int TypeCount; + public int CharCount; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs b/src/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs new file mode 100644 index 00000000..580b6476 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs @@ -0,0 +1,12 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Time.Types +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SteadyClockContext + { + public ulong InternalOffset; + public UInt128 ClockSourceId; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs b/src/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs new file mode 100644 index 00000000..7591c3e9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs @@ -0,0 +1,22 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Time +{ + [Flags] + enum TimePermissions + { + LocalSystemClockWritableMask = 0x1, + UserSystemClockWritableMask = 0x2, + NetworkSystemClockWritableMask = 0x4, + TimeZoneWritableMask = 0x8, + SteadyClockWritableMask = 0x10, + BypassUninitialized = 0x20, + + User = 0, + Admin = LocalSystemClockWritableMask | UserSystemClockWritableMask | TimeZoneWritableMask, + System = NetworkSystemClockWritableMask, + SystemUpdate = BypassUninitialized, + Repair = SteadyClockWritableMask, + Manufacture = LocalSystemClockWritableMask | UserSystemClockWritableMask | NetworkSystemClockWritableMask | TimeZoneWritableMask | SteadyClockWritableMask, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs new file mode 100644 index 00000000..9cc9d421 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs @@ -0,0 +1,27 @@ +using Ryujinx.HLE.HOS.Services.Vi.RootService; +using Ryujinx.HLE.HOS.Services.Vi.Types; + +namespace Ryujinx.HLE.HOS.Services.Vi +{ + [Service("vi:u")] + class IApplicationRootService : IpcService + { + public IApplicationRootService(ServiceCtx context) : base(context.Device.System.ViServer) { } + + [CommandCmif(0)] + // GetDisplayService(u32) -> object + public ResultCode GetDisplayService(ServiceCtx context) + { + ViServiceType serviceType = (ViServiceType)context.RequestData.ReadInt32(); + + if (serviceType != ViServiceType.Application) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new IApplicationDisplayService(serviceType)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs new file mode 100644 index 00000000..dd4b25a0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs @@ -0,0 +1,28 @@ +using Ryujinx.HLE.HOS.Services.Vi.RootService; +using Ryujinx.HLE.HOS.Services.Vi.Types; + +namespace Ryujinx.HLE.HOS.Services.Vi +{ + [Service("vi:m")] + class IManagerRootService : IpcService + { + // vi:u/m/s aren't on 3 separate threads but we can't put them together with the current ServerBase + public IManagerRootService(ServiceCtx context) : base(context.Device.System.ViServerM) { } + + [CommandCmif(2)] + // GetDisplayService(u32) -> object + public ResultCode GetDisplayService(ServiceCtx context) + { + ViServiceType serviceType = (ViServiceType)context.RequestData.ReadInt32(); + + if (serviceType != ViServiceType.Manager) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new IApplicationDisplayService(serviceType)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs new file mode 100644 index 00000000..b2415f2e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs @@ -0,0 +1,28 @@ +using Ryujinx.HLE.HOS.Services.Vi.RootService; +using Ryujinx.HLE.HOS.Services.Vi.Types; + +namespace Ryujinx.HLE.HOS.Services.Vi +{ + [Service("vi:s")] + class ISystemRootService : IpcService + { + // vi:u/m/s aren't on 3 separate threads but we can't put them together with the current ServerBase + public ISystemRootService(ServiceCtx context) : base(context.Device.System.ViServerS) { } + + [CommandCmif(1)] + // GetDisplayService(u32) -> object + public ResultCode GetDisplayService(ServiceCtx context) + { + ViServiceType serviceType = (ViServiceType)context.RequestData.ReadInt32(); + + if (serviceType != ViServiceType.System) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new IApplicationDisplayService(serviceType)); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Vi/ResultCode.cs new file mode 100644 index 00000000..3bed7e57 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/ResultCode.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Services.Vi +{ + enum ResultCode + { + ModuleId = 114, + ErrorCodeShift = 9, + + Success = 0, + + InvalidArguments = (1 << ErrorCodeShift) | ModuleId, + InvalidLayerSize = (4 << ErrorCodeShift) | ModuleId, + PermissionDenied = (5 << ErrorCodeShift) | ModuleId, + InvalidScalingMode = (6 << ErrorCodeShift) | ModuleId, + InvalidValue = (7 << ErrorCodeShift) | ModuleId, + AlreadyOpened = (9 << ErrorCodeShift) | ModuleId, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/AndroidSurfaceComposerClient.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/AndroidSurfaceComposerClient.cs new file mode 100644 index 00000000..c4503098 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/AndroidSurfaceComposerClient.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + class AndroidSurfaceComposerClient + { + // NOTE: This is android::SurfaceComposerClient::getDisplayInfo. + public static (ulong, ulong) GetDisplayInfo(ServiceCtx context, ulong displayId = 0) + { + // TODO: This need to be REd, it should returns the driver resolution and more. + if (context.Device.System.State.DockedMode) + { + return (1920, 1080); + } + else + { + return (1280, 720); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs new file mode 100644 index 00000000..3a08cdd7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs @@ -0,0 +1,84 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + class IManagerDisplayService : IpcService + { +#pragma warning disable IDE0052 // Remove unread private member + private readonly IApplicationDisplayService _applicationDisplayService; +#pragma warning restore IDE0052 + + public IManagerDisplayService(IApplicationDisplayService applicationDisplayService) + { + _applicationDisplayService = applicationDisplayService; + } + + [CommandCmif(1102)] + // GetDisplayResolution(u64 display_id) -> (u64 width, u64 height) + public ResultCode GetDisplayResolution(ServiceCtx context) + { + ulong displayId = context.RequestData.ReadUInt64(); + + (ulong width, ulong height) = AndroidSurfaceComposerClient.GetDisplayInfo(context, displayId); + + context.ResponseData.Write(width); + context.ResponseData.Write(height); + + return ResultCode.Success; + } + + [CommandCmif(2010)] + // CreateManagedLayer(u32, u64, nn::applet::AppletResourceUserId) -> u64 + public ResultCode CreateManagedLayer(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long layerFlags = context.RequestData.ReadInt64(); + long displayId = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + long appletResourceUserId = context.RequestData.ReadInt64(); + + ulong pid = context.Device.System.AppletState.AppletResourceUserIds.GetData((int)appletResourceUserId); + + context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, pid); + context.Device.System.SurfaceFlinger.SetRenderLayer(layerId); + + context.ResponseData.Write(layerId); + + return ResultCode.Success; + } + + [CommandCmif(2011)] + // DestroyManagedLayer(u64) + public ResultCode DestroyManagedLayer(ServiceCtx context) + { + long layerId = context.RequestData.ReadInt64(); + + return context.Device.System.SurfaceFlinger.DestroyManagedLayer(layerId); + } + + [CommandCmif(2012)] // 7.0.0+ + // CreateStrayLayer(u32, u64) -> (u64, u64, buffer) + public ResultCode CreateStrayLayer(ServiceCtx context) + { + return _applicationDisplayService.CreateStrayLayer(context); + } + + [CommandCmif(6000)] + // AddToLayerStack(u32, u64) + public ResultCode AddToLayerStack(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + + [CommandCmif(6002)] + // SetLayerVisibility(b8, u64) + public ResultCode SetLayerVisibility(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs new file mode 100644 index 00000000..12ac2cf4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs @@ -0,0 +1,61 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + class ISystemDisplayService : IpcService + { +#pragma warning disable IDE0052 // Remove unread private member + private readonly IApplicationDisplayService _applicationDisplayService; +#pragma warning restore IDE0052 + + public ISystemDisplayService(IApplicationDisplayService applicationDisplayService) + { + _applicationDisplayService = applicationDisplayService; + } + + [CommandCmif(2205)] + // SetLayerZ(u64, u64) + public ResultCode SetLayerZ(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + + [CommandCmif(2207)] + // SetLayerVisibility(b8, u64) + public ResultCode SetLayerVisibility(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + + [CommandCmif(2312)] // 1.0.0-6.2.0 + // CreateStrayLayer(u32, u64) -> (u64, u64, buffer) + public ResultCode CreateStrayLayer(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceVi); + + return _applicationDisplayService.CreateStrayLayer(context); + } + + [CommandCmif(3200)] + // GetDisplayMode(u64) -> nn::vi::DisplayModeInfo + public ResultCode GetDisplayMode(ServiceCtx context) + { + ulong displayId = context.RequestData.ReadUInt64(); + + (ulong width, ulong height) = AndroidSurfaceComposerClient.GetDisplayInfo(context, displayId); + + context.ResponseData.Write((uint)width); + context.ResponseData.Write((uint)height); + context.ResponseData.Write(60.0f); + context.ResponseData.Write(0); + + Logger.Stub?.PrintStub(LogClass.ServiceVi); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DestinationScalingMode.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DestinationScalingMode.cs new file mode 100644 index 00000000..066b9ccf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DestinationScalingMode.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + enum DestinationScalingMode + { + Freeze, + ScaleToWindow, + ScaleAndCrop, + None, + PreserveAspectRatio, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DisplayInfo.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DisplayInfo.cs new file mode 100644 index 00000000..a3231692 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DisplayInfo.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x60)] + struct DisplayInfo + { + public Array64 Name; + public bool LayerLimitEnabled; + public Array7 Padding; + public ulong LayerLimitMax; + public ulong Width; + public ulong Height; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/SourceScalingMode.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/SourceScalingMode.cs new file mode 100644 index 00000000..e950d74b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/SourceScalingMode.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService +{ + enum SourceScalingMode + { + None, + Freeze, + ScaleToWindow, + ScaleAndCrop, + PreserveAspectRatio, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs new file mode 100644 index 00000000..a2b1fb52 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs @@ -0,0 +1,492 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.SurfaceFlinger; +using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService; +using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService.Types; +using Ryujinx.HLE.HOS.Services.Vi.Types; +using Ryujinx.HLE.UI; +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Vi.RootService +{ + class IApplicationDisplayService : IpcService + { + private readonly ViServiceType _serviceType; + + private class DisplayState + { + public int RetrievedEventsCount; + } + + private readonly List _displayInfo; + private readonly Dictionary _openDisplays; + + private int _vsyncEventHandle; + + public IApplicationDisplayService(ViServiceType serviceType) + { + _serviceType = serviceType; + _displayInfo = new List(); + _openDisplays = new Dictionary(); + + void AddDisplayInfo(string name, bool layerLimitEnabled, ulong layerLimitMax, ulong width, ulong height) + { + DisplayInfo displayInfo = new() + { + Name = new Array64(), + LayerLimitEnabled = layerLimitEnabled, + Padding = new Array7(), + LayerLimitMax = layerLimitMax, + Width = width, + Height = height, + }; + + Encoding.ASCII.GetBytes(name).AsSpan().CopyTo(displayInfo.Name.AsSpan()); + + _displayInfo.Add(displayInfo); + } + + AddDisplayInfo("Default", true, 1, 1920, 1080); + AddDisplayInfo("External", true, 1, 1920, 1080); + AddDisplayInfo("Edid", true, 1, 0, 0); + AddDisplayInfo("Internal", true, 1, 1920, 1080); + AddDisplayInfo("Null", false, 0, 1920, 1080); + } + + [CommandCmif(100)] + // GetRelayService() -> object + public ResultCode GetRelayService(ServiceCtx context) + { + // FIXME: Should be _serviceType != ViServiceType.Application but guests crashes if we do this check. + if (_serviceType > ViServiceType.System) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new HOSBinderDriverServer()); + + return ResultCode.Success; + } + + [CommandCmif(101)] + // GetSystemDisplayService() -> object + public ResultCode GetSystemDisplayService(ServiceCtx context) + { + // FIXME: Should be _serviceType == ViServiceType.System but guests crashes if we do this check. + if (_serviceType > ViServiceType.System) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new ISystemDisplayService(this)); + + return ResultCode.Success; + } + + [CommandCmif(102)] + // GetManagerDisplayService() -> object + public ResultCode GetManagerDisplayService(ServiceCtx context) + { + if (_serviceType > ViServiceType.System) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new IManagerDisplayService(this)); + + return ResultCode.Success; + } + + [CommandCmif(103)] // 2.0.0+ + // GetIndirectDisplayTransactionService() -> object + public ResultCode GetIndirectDisplayTransactionService(ServiceCtx context) + { + if (_serviceType > ViServiceType.System) + { + return ResultCode.PermissionDenied; + } + + MakeObject(context, new HOSBinderDriverServer()); + + return ResultCode.Success; + } + + [CommandCmif(1000)] + // ListDisplays() -> (u64 count, buffer) + public ResultCode ListDisplays(ServiceCtx context) + { + ulong displayInfoBuffer = context.Request.ReceiveBuff[0].Position; + + // TODO: Determine when more than one display is needed. + ulong displayCount = 1; + + for (int i = 0; i < (int)displayCount; i++) + { + context.Memory.Write(displayInfoBuffer + (ulong)(i * Unsafe.SizeOf()), _displayInfo[i]); + } + + context.ResponseData.Write(displayCount); + + return ResultCode.Success; + } + + [CommandCmif(1010)] + // OpenDisplay(nn::vi::DisplayName) -> u64 display_id + public ResultCode OpenDisplay(ServiceCtx context) + { + StringBuilder nameBuilder = new(); + + for (int index = 0; index < 8 && context.RequestData.BaseStream.Position < context.RequestData.BaseStream.Length; index++) + { + byte chr = context.RequestData.ReadByte(); + + if (chr >= 0x20 && chr < 0x7f) + { + nameBuilder.Append((char)chr); + } + } + + return OpenDisplayImpl(context, nameBuilder.ToString()); + } + + [CommandCmif(1011)] + // OpenDefaultDisplay() -> u64 display_id + public ResultCode OpenDefaultDisplay(ServiceCtx context) + { + return OpenDisplayImpl(context, "Default"); + } + + private ResultCode OpenDisplayImpl(ServiceCtx context, string name) + { + if (name == "") + { + return ResultCode.InvalidValue; + } + + int displayId = _displayInfo.FindIndex(display => Encoding.ASCII.GetString(display.Name.AsSpan()).Trim('\0') == name); + + if (displayId == -1) + { + return ResultCode.InvalidValue; + } + + if (!_openDisplays.TryAdd((ulong)displayId, new DisplayState())) + { + return ResultCode.AlreadyOpened; + } + + context.ResponseData.Write((ulong)displayId); + + return ResultCode.Success; + } + + [CommandCmif(1020)] + // CloseDisplay(u64 display_id) + public ResultCode CloseDisplay(ServiceCtx context) + { + ulong displayId = context.RequestData.ReadUInt64(); + + if (!_openDisplays.Remove(displayId)) + { + return ResultCode.InvalidValue; + } + + return ResultCode.Success; + } + + [CommandCmif(1101)] + // SetDisplayEnabled(u32 enabled_bool, u64 display_id) + public ResultCode SetDisplayEnabled(ServiceCtx context) + { + // NOTE: Stubbed in original service. + return ResultCode.Success; + } + + [CommandCmif(1102)] + // GetDisplayResolution(u64 display_id) -> (u64 width, u64 height) + public ResultCode GetDisplayResolution(ServiceCtx context) + { + // NOTE: Not used in original service. + // ulong displayId = context.RequestData.ReadUInt64(); + + // NOTE: Returns ResultCode.InvalidArguments if width and height pointer are null, doesn't occur in our case. + + // NOTE: Values are hardcoded in original service. + context.ResponseData.Write(1280UL); // Width + context.ResponseData.Write(720UL); // Height + + return ResultCode.Success; + } + + [CommandCmif(2020)] + // OpenLayer(nn::vi::DisplayName, u64, nn::applet::AppletResourceUserId, pid) -> (u64, buffer) + public ResultCode OpenLayer(ServiceCtx context) + { + // TODO: support multi display. +#pragma warning disable IDE0059 // Remove unnecessary value assignment + byte[] displayName = context.RequestData.ReadBytes(0x40); +#pragma warning restore IDE0059 + + long layerId = context.RequestData.ReadInt64(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long userId = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + ulong parcelPtr = context.Request.ReceiveBuff[0].Position; + + ResultCode result = context.Device.System.SurfaceFlinger.OpenLayer(context.Request.HandleDesc.PId, layerId, out IBinder producer); + + if (result != ResultCode.Success) + { + return result; + } + + context.Device.System.SurfaceFlinger.SetRenderLayer(layerId); + + using Parcel parcel = new(0x28, 0x4); + + parcel.WriteObject(producer, "dispdrv\0"); + + ReadOnlySpan parcelData = parcel.Finish(); + + context.Memory.Write(parcelPtr, parcelData); + + context.ResponseData.Write((long)parcelData.Length); + + return ResultCode.Success; + } + + [CommandCmif(2021)] + // CloseLayer(u64) + public ResultCode CloseLayer(ServiceCtx context) + { + long layerId = context.RequestData.ReadInt64(); + + return context.Device.System.SurfaceFlinger.CloseLayer(layerId); + } + + [CommandCmif(2030)] + // CreateStrayLayer(u32, u64) -> (u64, u64, buffer) + public ResultCode CreateStrayLayer(ServiceCtx context) + { +#pragma warning disable IDE0059 // Remove unnecessary value assignment + long layerFlags = context.RequestData.ReadInt64(); + long displayId = context.RequestData.ReadInt64(); +#pragma warning restore IDE0059 + + ulong parcelPtr = context.Request.ReceiveBuff[0].Position; + + // TODO: support multi display. + IBinder producer = context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, 0, LayerState.Stray); + + context.Device.System.SurfaceFlinger.SetRenderLayer(layerId); + + using Parcel parcel = new(0x28, 0x4); + + parcel.WriteObject(producer, "dispdrv\0"); + + ReadOnlySpan parcelData = parcel.Finish(); + + context.Memory.Write(parcelPtr, parcelData); + + context.ResponseData.Write(layerId); + context.ResponseData.Write((long)parcelData.Length); + + return ResultCode.Success; + } + + [CommandCmif(2031)] + // DestroyStrayLayer(u64) + public ResultCode DestroyStrayLayer(ServiceCtx context) + { + long layerId = context.RequestData.ReadInt64(); + + return context.Device.System.SurfaceFlinger.DestroyStrayLayer(layerId); + } + + [CommandCmif(2101)] + // SetLayerScalingMode(u32, u64) + public ResultCode SetLayerScalingMode(ServiceCtx context) + { + /* + uint sourceScalingMode = context.RequestData.ReadUInt32(); + ulong layerId = context.RequestData.ReadUInt64(); + */ + // NOTE: Original service converts SourceScalingMode to DestinationScalingMode but does nothing with the converted value. + + return ResultCode.Success; + } + + [CommandCmif(2102)] // 5.0.0+ + // ConvertScalingMode(u32 source_scaling_mode) -> u64 destination_scaling_mode + public ResultCode ConvertScalingMode(ServiceCtx context) + { + SourceScalingMode scalingMode = (SourceScalingMode)context.RequestData.ReadInt32(); + + DestinationScalingMode? convertedScalingMode = scalingMode switch + { + SourceScalingMode.None => DestinationScalingMode.None, + SourceScalingMode.Freeze => DestinationScalingMode.Freeze, + SourceScalingMode.ScaleAndCrop => DestinationScalingMode.ScaleAndCrop, + SourceScalingMode.ScaleToWindow => DestinationScalingMode.ScaleToWindow, + SourceScalingMode.PreserveAspectRatio => DestinationScalingMode.PreserveAspectRatio, + _ => null, + }; + + if (!convertedScalingMode.HasValue) + { + // Scaling mode out of the range of valid values. + return ResultCode.InvalidArguments; + } + + if (scalingMode != SourceScalingMode.ScaleToWindow && scalingMode != SourceScalingMode.PreserveAspectRatio) + { + // Invalid scaling mode specified. + return ResultCode.InvalidScalingMode; + } + + context.ResponseData.Write((ulong)convertedScalingMode); + + return ResultCode.Success; + } + + private ulong GetA8B8G8R8LayerSize(int width, int height, out int pitch, out int alignment) + { + const int DefaultAlignment = 0x1000; + const ulong DefaultSize = 0x20000; + + alignment = DefaultAlignment; + pitch = BitUtils.AlignUp(BitUtils.DivRoundUp(width * 32, 8), 64); + + int memorySize = pitch * BitUtils.AlignUp(height, 64); + ulong requiredMemorySize = (ulong)BitUtils.AlignUp(memorySize, alignment); + + return (requiredMemorySize + DefaultSize - 1) / DefaultSize * DefaultSize; + } + + [CommandCmif(2450)] + // GetIndirectLayerImageMap(s64 width, s64 height, u64 handle, nn::applet::AppletResourceUserId, pid) -> (s64, s64, buffer) + public ResultCode GetIndirectLayerImageMap(ServiceCtx context) + { + // The size of the layer buffer should be an aligned multiple of width * height + // because it was created using GetIndirectLayerImageRequiredMemoryInfo as a guide. + + long layerWidth = context.RequestData.ReadInt64(); + long layerHeight = context.RequestData.ReadInt64(); + long layerHandle = context.RequestData.ReadInt64(); + ulong layerBuffPosition = context.Request.ReceiveBuff[0].Position; + ulong layerBuffSize = context.Request.ReceiveBuff[0].Size; + + // Get the pitch of the layer that is necessary to render correctly. + ulong size = GetA8B8G8R8LayerSize((int)layerWidth, (int)layerHeight, out int pitch, out _); + + Debug.Assert(layerBuffSize == size); + + RenderingSurfaceInfo surfaceInfo = new(ColorFormat.A8B8G8R8, (uint)layerWidth, (uint)layerHeight, (uint)pitch, (uint)layerBuffSize); + + // Get the applet associated with the handle. + object appletObject = context.Device.System.AppletState.IndirectLayerHandles.GetData((int)layerHandle); + + if (appletObject == null) + { + Logger.Error?.Print(LogClass.ServiceVi, $"Indirect layer handle {layerHandle} does not match any applet"); + + return ResultCode.Success; + } + + Debug.Assert(appletObject is IApplet); + + IApplet applet = appletObject as IApplet; + + if (!applet.DrawTo(surfaceInfo, context.Memory, layerBuffPosition)) + { + Logger.Warning?.Print(LogClass.ServiceVi, $"Applet did not draw on indirect layer handle {layerHandle}"); + + return ResultCode.Success; + } + + context.ResponseData.Write(layerWidth); + context.ResponseData.Write(layerHeight); + + return ResultCode.Success; + } + + [CommandCmif(2460)] + // GetIndirectLayerImageRequiredMemoryInfo(u64 width, u64 height) -> (u64 size, u64 alignment) + public ResultCode GetIndirectLayerImageRequiredMemoryInfo(ServiceCtx context) + { + /* + // Doesn't occur in our case. + if (sizePtr == null || address_alignmentPtr == null) + { + return ResultCode.InvalidArguments; + } + */ + + int width = (int)context.RequestData.ReadUInt64(); + int height = (int)context.RequestData.ReadUInt64(); + + if (height < 0 || width < 0) + { + return ResultCode.InvalidLayerSize; + } + else + { + /* + // Doesn't occur in our case. + if (!service_initialized) + { + return ResultCode.InvalidArguments; + } + */ + + // NOTE: The official service setup a A8B8G8R8 texture with a linear layout and then query its size. + // As we don't need this texture on the emulator, we can just simplify this logic and directly + // do a linear layout size calculation. (stride * height * bytePerPixel) + ulong size = GetA8B8G8R8LayerSize(width, height, out _, out int alignment); + + context.ResponseData.Write(size); + context.ResponseData.Write(alignment); + } + + return ResultCode.Success; + } + + [CommandCmif(5202)] + // GetDisplayVsyncEvent(u64) -> handle + public ResultCode GetDisplayVSyncEvent(ServiceCtx context) + { + ulong displayId = context.RequestData.ReadUInt64(); + + if (!_openDisplays.TryGetValue(displayId, out DisplayState displayState)) + { + return ResultCode.InvalidValue; + } + + if (displayState.RetrievedEventsCount > 0) + { + return ResultCode.PermissionDenied; + } + + if (_vsyncEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(context.Device.System.VsyncEvent.ReadableEvent, out _vsyncEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + displayState.RetrievedEventsCount++; + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_vsyncEventHandle); + + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/Types/ViServiceType.cs b/src/Ryujinx.HLE/HOS/Services/Vi/Types/ViServiceType.cs new file mode 100644 index 00000000..3e6e7c24 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Vi/Types/ViServiceType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Vi.Types +{ + enum ViServiceType + { + Application, + Manager, + System, + } +} diff --git a/src/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs b/src/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs new file mode 100644 index 00000000..a2837e20 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs @@ -0,0 +1,42 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; +using System.Collections.Concurrent; + +namespace Ryujinx.HLE.HOS.SystemState +{ + class AppletStateMgr + { + public ConcurrentQueue Messages { get; } + + public FocusState FocusState { get; private set; } + + public KEvent MessageEvent { get; } + + public IdDictionary AppletResourceUserIds { get; } + + public IdDictionary IndirectLayerHandles { get; } + + public AppletStateMgr(Horizon system) + { + Messages = new ConcurrentQueue(); + MessageEvent = new KEvent(system.KernelContext); + + AppletResourceUserIds = new IdDictionary(); + IndirectLayerHandles = new IdDictionary(); + } + + public void SetFocus(bool isFocused) + { + FocusState = isFocused ? FocusState.InFocus : FocusState.OutOfFocus; + + Messages.Enqueue(AppletMessage.FocusStateChanged); + + if (isFocused) + { + Messages.Enqueue(AppletMessage.ChangeIntoForeground); + } + + MessageEvent.ReadableEvent.Signal(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/SystemState/ColorSet.cs b/src/Ryujinx.HLE/HOS/SystemState/ColorSet.cs new file mode 100644 index 00000000..b1924206 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/SystemState/ColorSet.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.SystemState +{ + public enum ColorSet + { + BasicWhite = 0, + BasicBlack = 1, + } +} diff --git a/src/Ryujinx.HLE/HOS/SystemState/KeyboardLayout.cs b/src/Ryujinx.HLE/HOS/SystemState/KeyboardLayout.cs new file mode 100644 index 00000000..e3cb68a2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/SystemState/KeyboardLayout.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.HLE.HOS.SystemState +{ + // nn::settings::KeyboardLayout + public enum KeyboardLayout + { + Default = 0, + EnglishUs, + EnglishUsInternational, + EnglishUk, + French, + FrenchCa, + Spanish, + SpanishLatin, + German, + Italian, + Portuguese, + Russian, + Korean, + ChineseSimplified, + ChineseTraditional, + + Min = Default, + Max = ChineseTraditional, + } +} diff --git a/src/Ryujinx.HLE/HOS/SystemState/RegionCode.cs b/src/Ryujinx.HLE/HOS/SystemState/RegionCode.cs new file mode 100644 index 00000000..11122723 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/SystemState/RegionCode.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.SystemState +{ + // nn::settings::RegionCode + public enum RegionCode + { + Japan, + USA, + Europe, + Australia, + China, + Korea, + Taiwan, + + Min = Japan, + Max = Taiwan, + } +} diff --git a/src/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs b/src/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs new file mode 100644 index 00000000..f5b7fc0f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.HLE.HOS.SystemState +{ + public enum SystemLanguage + { + Japanese, + AmericanEnglish, + French, + German, + Italian, + Spanish, + Chinese, + Korean, + Dutch, + Portuguese, + Russian, + Taiwanese, + BritishEnglish, + CanadianFrench, + LatinAmericanSpanish, + SimplifiedChinese, + TraditionalChinese, + BrazilianPortuguese, + } +} diff --git a/src/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs b/src/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs new file mode 100644 index 00000000..c650fe03 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs @@ -0,0 +1,89 @@ +using System; + +namespace Ryujinx.HLE.HOS.SystemState +{ + public class SystemStateMgr + { + internal static string[] LanguageCodes = { + "ja", + "en-US", + "fr", + "de", + "it", + "es", + "zh-CN", + "ko", + "nl", + "pt", + "ru", + "zh-TW", + "en-GB", + "fr-CA", + "es-419", + "zh-Hans", + "zh-Hant", + "pt-BR", + }; + + internal long DesiredKeyboardLayout { get; private set; } + + internal SystemLanguage DesiredSystemLanguage { get; private set; } + + internal long DesiredLanguageCode { get; private set; } + + internal uint DesiredRegionCode { get; private set; } + + public TitleLanguage DesiredTitleLanguage { get; private set; } + + public bool DockedMode { get; set; } + + public ColorSet ThemeColor { get; set; } + + public string DeviceNickName { get; set; } + + public SystemStateMgr() + { + // TODO: Let user specify fields. + DesiredKeyboardLayout = (long)KeyboardLayout.Default; + DeviceNickName = "Ryujinx's Switch"; + } + + public void SetLanguage(SystemLanguage language) + { + DesiredSystemLanguage = language; + DesiredLanguageCode = GetLanguageCode((int)DesiredSystemLanguage); + + DesiredTitleLanguage = language switch + { + SystemLanguage.Taiwanese or + SystemLanguage.TraditionalChinese => TitleLanguage.TraditionalChinese, + SystemLanguage.Chinese or + SystemLanguage.SimplifiedChinese => TitleLanguage.SimplifiedChinese, + _ => Enum.Parse(Enum.GetName(language)), + }; + } + + public void SetRegion(RegionCode region) + { + DesiredRegionCode = (uint)region; + } + + internal static long GetLanguageCode(int index) + { + if ((uint)index >= LanguageCodes.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + long code = 0; + int shift = 0; + + foreach (char chr in LanguageCodes[index]) + { + code |= (long)(byte)chr << shift++ * 8; + } + + return code; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs b/src/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs new file mode 100644 index 00000000..e3bfb916 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.HLE.HOS.SystemState +{ + public enum TitleLanguage + { + AmericanEnglish, + BritishEnglish, + Japanese, + French, + German, + LatinAmericanSpanish, + Spanish, + Italian, + Dutch, + CanadianFrench, + Portuguese, + Russian, + Korean, + TraditionalChinese, + SimplifiedChinese, + BrazilianPortuguese, + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs b/src/Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs new file mode 100644 index 00000000..e25ba7a5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs @@ -0,0 +1,151 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Tamper.CodeEmitters; +using Ryujinx.HLE.HOS.Tamper.Operations; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Tamper +{ + class AtmosphereCompiler + { + private readonly ulong _exeAddress; + private readonly ulong _heapAddress; + private readonly ulong _aliasAddress; + private readonly ulong _aslrAddress; + private readonly ITamperedProcess _process; + + public AtmosphereCompiler(ulong exeAddress, ulong heapAddress, ulong aliasAddress, ulong aslrAddress, ITamperedProcess process) + { + _exeAddress = exeAddress; + _heapAddress = heapAddress; + _aliasAddress = aliasAddress; + _aslrAddress = aslrAddress; + _process = process; + } + + public ITamperProgram Compile(string name, IEnumerable rawInstructions) + { + string[] addresses = { + $" Executable address: 0x{_exeAddress:X16}", + $" Heap address : 0x{_heapAddress:X16}", + $" Alias address : 0x{_aliasAddress:X16}", + $" Aslr address : 0x{_aslrAddress:X16}", + }; + + Logger.Debug?.Print(LogClass.TamperMachine, $"Compiling Atmosphere cheat {name}...\n{string.Join('\n', addresses)}"); + + try + { + return CompileImpl(name, rawInstructions); + } + catch (TamperCompilationException ex) + { + // Just print the message without the stack trace. + Logger.Error?.Print(LogClass.TamperMachine, ex.Message); + } + catch (Exception ex) + { + Logger.Error?.Print(LogClass.TamperMachine, ex.ToString()); + } + + Logger.Error?.Print(LogClass.TamperMachine, "There was a problem while compiling the Atmosphere cheat"); + + return null; + } + + private ITamperProgram CompileImpl(string name, IEnumerable rawInstructions) + { + CompilationContext context = new(_exeAddress, _heapAddress, _aliasAddress, _aslrAddress, _process); + context.BlockStack.Push(new OperationBlock(null)); + + // Parse the instructions. + + foreach (string rawInstruction in rawInstructions) + { + Logger.Debug?.Print(LogClass.TamperMachine, $"Compiling instruction {rawInstruction}"); + + byte[] instruction = InstructionHelper.ParseRawInstruction(rawInstruction); + CodeType codeType = InstructionHelper.GetCodeType(instruction); + + switch (codeType) + { + case CodeType.StoreConstantToAddress: + StoreConstantToAddress.Emit(instruction, context); + break; + case CodeType.BeginMemoryConditionalBlock: + BeginConditionalBlock.Emit(instruction, context); + break; + case CodeType.EndConditionalBlock: + EndConditionalBlock.Emit(instruction, context); + break; + case CodeType.StartEndLoop: + StartEndLoop.Emit(instruction, context); + break; + case CodeType.LoadRegisterWithContant: + LoadRegisterWithConstant.Emit(instruction, context); + break; + case CodeType.LoadRegisterWithMemory: + LoadRegisterWithMemory.Emit(instruction, context); + break; + case CodeType.StoreConstantToMemory: + StoreConstantToMemory.Emit(instruction, context); + break; + case CodeType.LegacyArithmetic: + LegacyArithmetic.Emit(instruction, context); + break; + case CodeType.BeginKeypressConditionalBlock: + BeginConditionalBlock.Emit(instruction, context); + break; + case CodeType.Arithmetic: + Arithmetic.Emit(instruction, context); + break; + case CodeType.StoreRegisterToMemory: + StoreRegisterToMemory.Emit(instruction, context); + break; + case CodeType.BeginRegisterConditionalBlock: + BeginConditionalBlock.Emit(instruction, context); + break; + case CodeType.SaveOrRestoreRegister: + SaveOrRestoreRegister.Emit(instruction, context); + break; + case CodeType.SaveOrRestoreRegisterWithMask: + SaveOrRestoreRegisterWithMask.Emit(instruction, context); + break; + case CodeType.ReadOrWriteStaticRegister: + ReadOrWriteStaticRegister.Emit(instruction, context); + break; + case CodeType.PauseProcess: + PauseProcess.Emit(instruction, context); + break; + case CodeType.ResumeProcess: + ResumeProcess.Emit(instruction, context); + break; + case CodeType.DebugLog: + DebugLog.Emit(instruction, context); + break; + default: + throw new TamperCompilationException($"Code type {codeType} not implemented in Atmosphere cheat"); + } + } + + // Initialize only the registers used. + + Value zero = new(0UL); + int position = 0; + + foreach (Register register in context.Registers.Values) + { + context.CurrentOperations.Insert(position, new OpMov(register, zero)); + position++; + } + + if (context.BlockStack.Count != 1) + { + throw new TamperCompilationException("Reached end of compilation with unmatched conditional(s) or loop(s)"); + } + + return new AtmosphereProgram(name, _process, context.PressedKeys, new Block(context.CurrentOperations)); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs b/src/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs new file mode 100644 index 00000000..8171d217 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs @@ -0,0 +1,33 @@ +using Ryujinx.HLE.HOS.Services.Hid; +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper +{ + class AtmosphereProgram : ITamperProgram + { + private readonly Parameter _pressedKeys; + private readonly IOperation _entryPoint; + + public string Name { get; } + public bool TampersCodeMemory { get; set; } = false; + public ITamperedProcess Process { get; } + public bool IsEnabled { get; set; } + + public AtmosphereProgram(string name, ITamperedProcess process, Parameter pressedKeys, IOperation entryPoint) + { + Name = name; + Process = process; + _pressedKeys = pressedKeys; + _entryPoint = entryPoint; + } + + public void Execute(ControllerKeys pressedKeys) + { + if (IsEnabled) + { + _pressedKeys.Value = (long)pressedKeys; + _entryPoint.Execute(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/Arithmetic.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/Arithmetic.cs new file mode 100644 index 00000000..5b296def --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/Arithmetic.cs @@ -0,0 +1,127 @@ +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Tamper.Operations; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 9 allows performing arithmetic on registers. + /// + class Arithmetic + { + private const int OperationWidthIndex = 1; + private const int OperationTypeIndex = 2; + private const int DestinationRegisterIndex = 3; + private const int LeftHandSideRegisterIndex = 4; + private const int UseImmediateAsRhsIndex = 5; + private const int RightHandSideRegisterIndex = 6; + private const int RightHandSideImmediateIndex = 8; + + private const int RightHandSideImmediate8 = 8; + private const int RightHandSideImmediate16 = 16; + + private const byte Add = 0; // lhs + rhs + private const byte Sub = 1; // lhs - rhs + private const byte Mul = 2; // lhs * rhs + private const byte Lsh = 3; // lhs << rhs + private const byte Rsh = 4; // lhs >> rhs + private const byte And = 5; // lhs & rhs + private const byte Or = 6; // lhs | rhs + private const byte Not = 7; // ~lhs (discards right-hand operand) + private const byte Xor = 8; // lhs ^ rhs + private const byte Mov = 9; // lhs (discards right-hand operand) + + public static void Emit(byte[] instruction, CompilationContext context) + { + // 9TCRS0s0 + // T: Width of arithmetic operation(1, 2, 4, or 8 bytes). + // C: Arithmetic operation to apply, see below. + // R: Register to store result in. + // S: Register to use as left - hand operand. + // s: Register to use as right - hand operand. + + // 9TCRS100 VVVVVVVV (VVVVVVVV) + // T: Width of arithmetic operation(1, 2, 4, or 8 bytes). + // C: Arithmetic operation to apply, see below. + // R: Register to store result in. + // S: Register to use as left - hand operand. + // V: Value to use as right - hand operand. + + byte operationWidth = instruction[OperationWidthIndex]; + byte operation = instruction[OperationTypeIndex]; + Register destinationRegister = context.GetRegister(instruction[DestinationRegisterIndex]); + Register leftHandSideRegister = context.GetRegister(instruction[LeftHandSideRegisterIndex]); + byte rightHandSideIsImmediate = instruction[UseImmediateAsRhsIndex]; + IOperand rightHandSideOperand; + + switch (rightHandSideIsImmediate) + { + case 0: + // Use a register as right-hand side. + rightHandSideOperand = context.GetRegister(instruction[RightHandSideRegisterIndex]); + break; + case 1: + // Use an immediate as right-hand side. + int immediateSize = operationWidth <= 4 ? RightHandSideImmediate8 : RightHandSideImmediate16; + ulong immediate = InstructionHelper.GetImmediate(instruction, RightHandSideImmediateIndex, immediateSize); + rightHandSideOperand = new Value(immediate); + break; + default: + throw new TamperCompilationException($"Invalid right-hand side switch {rightHandSideIsImmediate} in Atmosphere cheat"); + } + + void Emit(Type operationType, IOperand rhs = null) + { + List operandList = new() + { + destinationRegister, + leftHandSideRegister, + }; + + if (rhs != null) + { + operandList.Add(rhs); + } + + InstructionHelper.Emit(operationType, operationWidth, context, operandList.ToArray()); + } + + switch (operation) + { + case Add: + Emit(typeof(OpAdd<>), rightHandSideOperand); + break; + case Sub: + Emit(typeof(OpSub<>), rightHandSideOperand); + break; + case Mul: + Emit(typeof(OpMul<>), rightHandSideOperand); + break; + case Lsh: + Emit(typeof(OpLsh<>), rightHandSideOperand); + break; + case Rsh: + Emit(typeof(OpRsh<>), rightHandSideOperand); + break; + case And: + Emit(typeof(OpAnd<>), rightHandSideOperand); + break; + case Or: + Emit(typeof(OpOr<>), rightHandSideOperand); + break; + case Not: + Emit(typeof(OpNot<>)); + break; + case Xor: + Emit(typeof(OpXor<>), rightHandSideOperand); + break; + case Mov: + Emit(typeof(OpMov<>)); + break; + default: + throw new TamperCompilationException($"Invalid arithmetic operation {operation} in Atmosphere cheat"); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/BeginConditionalBlock.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/BeginConditionalBlock.cs new file mode 100644 index 00000000..c8aa4e0e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/BeginConditionalBlock.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Marks the begin of a conditional block (started by Code Type 1, Code Type 8 or Code Type C0). + /// + class BeginConditionalBlock + { + public static void Emit(byte[] instruction, CompilationContext context) + { + // Just start a new compilation block and parse the instruction itself at the end. + context.BlockStack.Push(new OperationBlock(instruction)); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/DebugLog.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/DebugLog.cs new file mode 100644 index 00000000..d74a998f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/DebugLog.cs @@ -0,0 +1,87 @@ +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 0xFFF writes a debug log. + /// + class DebugLog + { + private const int OperationWidthIndex = 3; + private const int LogIdIndex = 4; + private const int OperandTypeIndex = 5; + private const int RegisterOrMemoryRegionIndex = 6; + private const int OffsetRegisterOrImmediateIndex = 7; + + private const int MemoryRegionWithOffsetImmediate = 0; + private const int MemoryRegionWithOffsetRegister = 1; + private const int AddressRegisterWithOffsetImmediate = 2; + private const int AddressRegisterWithOffsetRegister = 3; + private const int ValueRegister = 4; + + private const int OffsetImmediateSize = 9; + + public static void Emit(byte[] instruction, CompilationContext context) + { + // FFFTIX## + // FFFTI0Ma aaaaaaaa + // FFFTI1Mr + // FFFTI2Ra aaaaaaaa + // FFFTI3Rr + // FFFTI4V0 + // T: Width of memory write (1, 2, 4, or 8 bytes). + // I: Log id. + // X: Operand Type, see below. + // M: Memory Type (operand types 0 and 1). + // R: Address Register (operand types 2 and 3). + // a: Relative Address (operand types 0 and 2). + // r: Offset Register (operand types 1 and 3). + // V: Value Register (operand type 4). + + byte operationWidth = instruction[OperationWidthIndex]; + byte logId = instruction[LogIdIndex]; + byte operandType = instruction[OperandTypeIndex]; + byte registerOrMemoryRegion = instruction[RegisterOrMemoryRegionIndex]; + byte offsetRegisterIndex = instruction[OffsetRegisterOrImmediateIndex]; + ulong immediate; + Register addressRegister; + Register offsetRegister; + IOperand sourceOperand; + + switch (operandType) + { + case MemoryRegionWithOffsetImmediate: + // *(?x + #a) + immediate = InstructionHelper.GetImmediate(instruction, OffsetRegisterOrImmediateIndex, OffsetImmediateSize); + sourceOperand = MemoryHelper.EmitPointer((MemoryRegion)registerOrMemoryRegion, immediate, context); + break; + case MemoryRegionWithOffsetRegister: + // *(?x + $r) + offsetRegister = context.GetRegister(offsetRegisterIndex); + sourceOperand = MemoryHelper.EmitPointer((MemoryRegion)registerOrMemoryRegion, offsetRegister, context); + break; + case AddressRegisterWithOffsetImmediate: + // *($R + #a) + addressRegister = context.GetRegister(registerOrMemoryRegion); + immediate = InstructionHelper.GetImmediate(instruction, OffsetRegisterOrImmediateIndex, OffsetImmediateSize); + sourceOperand = MemoryHelper.EmitPointer(addressRegister, immediate, context); + break; + case AddressRegisterWithOffsetRegister: + // *($R + $r) + addressRegister = context.GetRegister(registerOrMemoryRegion); + offsetRegister = context.GetRegister(offsetRegisterIndex); + sourceOperand = MemoryHelper.EmitPointer(addressRegister, offsetRegister, context); + break; + case ValueRegister: + // $V + sourceOperand = context.GetRegister(registerOrMemoryRegion); + break; + default: + throw new TamperCompilationException($"Invalid operand type {operandType} in Atmosphere cheat"); + } + + InstructionHelper.Emit(typeof(OpLog<>), operationWidth, context, logId, sourceOperand); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/EndConditionalBlock.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/EndConditionalBlock.cs new file mode 100644 index 00000000..fc7edd62 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/EndConditionalBlock.cs @@ -0,0 +1,91 @@ +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Tamper.Conditions; +using Ryujinx.HLE.HOS.Tamper.Operations; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 2 marks the end of a conditional block (started by Code Type 1, Code Type 8 or Code Type C0). + /// + class EndConditionalBlock + { + const int TerminationTypeIndex = 1; + + private const byte End = 0; // True end of the conditional. + private const byte Else = 1; // End of the 'then' block and beginning of 'else' block. + + public static void Emit(byte[] instruction, CompilationContext context) + { + Emit(instruction, context, null); + } + + private static void Emit(byte[] instruction, CompilationContext context, IEnumerable operationsElse) + { + // 2X000000 + // X: End type (0 = End, 1 = Else). + + byte terminationType = instruction[TerminationTypeIndex]; + + switch (terminationType) + { + case End: + break; + case Else: + // Start a new operation block with the 'else' instruction to signal that there is the 'then' block just above it. + context.BlockStack.Push(new OperationBlock(instruction)); + return; + default: + throw new TamperCompilationException($"Unknown conditional termination type {terminationType}"); + } + + // Use the conditional begin instruction stored in the stack. + var upperInstruction = context.CurrentBlock.BaseInstruction; + CodeType codeType = InstructionHelper.GetCodeType(upperInstruction); + + // Pop the current block of operations from the stack so control instructions + // for the conditional can be emitted in the upper block. + IEnumerable operations = context.CurrentOperations; + context.BlockStack.Pop(); + + // If the else operations are already set, then the upper block must not be another end. + if (operationsElse != null && codeType == CodeType.EndConditionalBlock) + { + throw new TamperCompilationException("Expected an upper 'if' conditional instead of 'end conditional'"); + } + + ICondition condition; + + switch (codeType) + { + case CodeType.BeginMemoryConditionalBlock: + condition = MemoryConditional.Emit(upperInstruction, context); + break; + case CodeType.BeginKeypressConditionalBlock: + condition = KeyPressConditional.Emit(upperInstruction, context); + break; + case CodeType.BeginRegisterConditionalBlock: + condition = RegisterConditional.Emit(upperInstruction, context); + break; + case CodeType.EndConditionalBlock: + terminationType = upperInstruction[TerminationTypeIndex]; + // If there is an end instruction above then it must be an else. + if (terminationType != Else) + { + throw new TamperCompilationException($"Expected an upper 'else' conditional instead of {terminationType}"); + } + // Re-run the Emit with the else operations set. + Emit(instruction, context, operations); + return; + default: + throw new TamperCompilationException($"Conditional end does not match code type {codeType} in Atmosphere cheat"); + } + + // Create a conditional block with the current operations and nest it in the upper + // block of the stack. + + IfBlock block = new(condition, operations, operationsElse); + context.CurrentOperations.Add(block); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/KeyPressConditional.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/KeyPressConditional.cs new file mode 100644 index 00000000..2b0e2c81 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/KeyPressConditional.cs @@ -0,0 +1,26 @@ +using Ryujinx.HLE.HOS.Tamper.Conditions; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 8 enters or skips a conditional block based on whether a key combination is pressed. + /// + class KeyPressConditional + { + private const int InputMaskIndex = 1; + + private const int InputMaskSize = 7; + + public static ICondition Emit(byte[] instruction, CompilationContext context) + { + // 8kkkkkkk + // k: Keypad mask to check against, see below. + // Note that for multiple button combinations, the bitmasks should be ORd together. + // The Keypad Values are the direct output of hidKeysDown(). + + ulong inputMask = InstructionHelper.GetImmediate(instruction, InputMaskIndex, InputMaskSize); + + return new InputMask((long)inputMask, context.PressedKeys); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LegacyArithmetic.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LegacyArithmetic.cs new file mode 100644 index 00000000..3f222864 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LegacyArithmetic.cs @@ -0,0 +1,67 @@ +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Tamper.Operations; +using System; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 7 allows performing arithmetic on registers. However, it has been deprecated by Code + /// type 9, and is only kept for backwards compatibility. + /// + class LegacyArithmetic + { + const int OperationWidthIndex = 1; + const int DestinationRegisterIndex = 3; + const int OperationTypeIndex = 4; + const int ValueImmediateIndex = 8; + + const int ValueImmediateSize = 8; + + private const byte Add = 0; // reg += rhs + private const byte Sub = 1; // reg -= rhs + private const byte Mul = 2; // reg *= rhs + private const byte Lsh = 3; // reg <<= rhs + private const byte Rsh = 4; // reg >>= rhs + + public static void Emit(byte[] instruction, CompilationContext context) + { + // 7T0RC000 VVVVVVVV + // T: Width of arithmetic operation(1, 2, 4, or 8 bytes). + // R: Register to apply arithmetic to. + // C: Arithmetic operation to apply, see below. + // V: Value to use for arithmetic operation. + + byte operationWidth = instruction[OperationWidthIndex]; + Register register = context.GetRegister(instruction[DestinationRegisterIndex]); + byte operation = instruction[OperationTypeIndex]; + ulong immediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, ValueImmediateSize); + Value rightHandSideValue = new(immediate); + + void Emit(Type operationType) + { + InstructionHelper.Emit(operationType, operationWidth, context, register, register, rightHandSideValue); + } + + switch (operation) + { + case Add: + Emit(typeof(OpAdd<>)); + break; + case Sub: + Emit(typeof(OpSub<>)); + break; + case Mul: + Emit(typeof(OpMul<>)); + break; + case Lsh: + Emit(typeof(OpLsh<>)); + break; + case Rsh: + Emit(typeof(OpRsh<>)); + break; + default: + throw new TamperCompilationException($"Invalid arithmetic operation {operation} in Atmosphere cheat"); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithConstant.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithConstant.cs new file mode 100644 index 00000000..501936b8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithConstant.cs @@ -0,0 +1,28 @@ +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 4 allows setting a register to a constant value. + /// + class LoadRegisterWithConstant + { + const int RegisterIndex = 3; + const int ValueImmediateIndex = 8; + + const int ValueImmediateSize = 16; + + public static void Emit(byte[] instruction, CompilationContext context) + { + // 400R0000 VVVVVVVV VVVVVVVV + // R: Register to use. + // V: Value to load. + + Register destinationRegister = context.GetRegister(instruction[RegisterIndex]); + ulong immediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, ValueImmediateSize); + Value sourceValue = new(immediate); + + context.CurrentOperations.Add(new OpMov(destinationRegister, sourceValue)); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithMemory.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithMemory.cs new file mode 100644 index 00000000..3440efce --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithMemory.cs @@ -0,0 +1,58 @@ +using Ryujinx.HLE.Exceptions; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 5 allows loading a value from memory into a register, either using a fixed address or by + /// dereferencing the destination register. + /// + class LoadRegisterWithMemory + { + private const int OperationWidthIndex = 1; + private const int MemoryRegionIndex = 2; + private const int DestinationRegisterIndex = 3; + private const int UseDestinationAsSourceIndex = 4; + private const int OffsetImmediateIndex = 6; + + private const int OffsetImmediateSize = 10; + + public static void Emit(byte[] instruction, CompilationContext context) + { + // 5TMR00AA AAAAAAAA + // T: Width of memory read (1, 2, 4, or 8 bytes). + // M: Memory region to write to (0 = Main NSO, 1 = Heap). + // R: Register to load value into. + // A: Immediate offset to use from memory region base. + + // 5TMR10AA AAAAAAAA + // T: Width of memory read(1, 2, 4, or 8 bytes). + // M: Ignored. + // R: Register to use as base address and to load value into. + // A: Immediate offset to use from register R. + + byte operationWidth = instruction[OperationWidthIndex]; + MemoryRegion memoryRegion = (MemoryRegion)instruction[MemoryRegionIndex]; + Register destinationRegister = context.GetRegister(instruction[DestinationRegisterIndex]); + byte useDestinationAsSourceIndex = instruction[UseDestinationAsSourceIndex]; + ulong address = InstructionHelper.GetImmediate(instruction, OffsetImmediateIndex, OffsetImmediateSize); + + Pointer sourceMemory; + + switch (useDestinationAsSourceIndex) + { + case 0: + // Don't use the source register as an additional address offset. + sourceMemory = MemoryHelper.EmitPointer(memoryRegion, address, context); + break; + case 1: + // Use the source register as the base address. + sourceMemory = MemoryHelper.EmitPointer(destinationRegister, address, context); + break; + default: + throw new TamperCompilationException($"Invalid source mode {useDestinationAsSourceIndex} in Atmosphere cheat"); + } + + InstructionHelper.EmitMov(operationWidth, context, destinationRegister, sourceMemory); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/MemoryConditional.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/MemoryConditional.cs new file mode 100644 index 00000000..72b6e6cd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/MemoryConditional.cs @@ -0,0 +1,45 @@ +using Ryujinx.HLE.HOS.Tamper.Conditions; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 1 performs a comparison of the contents of memory to a static value. + /// If the condition is not met, all instructions until the appropriate conditional block terminator + /// are skipped. + /// + class MemoryConditional + { + private const int OperationWidthIndex = 1; + private const int MemoryRegionIndex = 2; + private const int ComparisonTypeIndex = 3; + private const int OffsetImmediateIndex = 6; + private const int ValueImmediateIndex = 16; + + private const int OffsetImmediateSize = 10; + private const int ValueImmediateSize4 = 8; + private const int ValueImmediateSize8 = 16; + + public static ICondition Emit(byte[] instruction, CompilationContext context) + { + // 1TMC00AA AAAAAAAA VVVVVVVV (VVVVVVVV) + // T: Width of memory write (1, 2, 4, or 8 bytes). + // M: Memory region to write to (0 = Main NSO, 1 = Heap). + // C: Condition to use, see below. + // A: Immediate offset to use from memory region base. + // V: Value to compare to. + + byte operationWidth = instruction[OperationWidthIndex]; + MemoryRegion memoryRegion = (MemoryRegion)instruction[MemoryRegionIndex]; + Comparison comparison = (Comparison)instruction[ComparisonTypeIndex]; + + ulong address = InstructionHelper.GetImmediate(instruction, OffsetImmediateIndex, OffsetImmediateSize); + Pointer sourceMemory = MemoryHelper.EmitPointer(memoryRegion, address, context); + + int valueSize = operationWidth <= 4 ? ValueImmediateSize4 : ValueImmediateSize8; + ulong value = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, valueSize); + Value compareToValue = new(value); + + return InstructionHelper.CreateCondition(comparison, operationWidth, sourceMemory, compareToValue); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/PauseProcess.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/PauseProcess.cs new file mode 100644 index 00000000..8bf91ef5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/PauseProcess.cs @@ -0,0 +1,17 @@ +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 0xFF0 pauses the current process. + /// + class PauseProcess + { + // FF0????? + + public static void Emit(byte[] instruction, CompilationContext context) + { + context.CurrentOperations.Add(new OpProcCtrl(context.Process, true)); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ReadOrWriteStaticRegister.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ReadOrWriteStaticRegister.cs new file mode 100644 index 00000000..73fbbde4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ReadOrWriteStaticRegister.cs @@ -0,0 +1,47 @@ +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 0xC3 reads or writes a static register with a given register. + /// NOTE: Registers are saved and restored to a different set of registers than the ones used + /// for the other opcodes (Static Registers). + /// + class ReadOrWriteStaticRegister + { + private const int StaticRegisterIndex = 5; + private const int RegisterIndex = 7; + + private const byte FirstWriteRegister = 0x80; + + private const int StaticRegisterSize = 2; + + public static void Emit(byte[] instruction, CompilationContext context) + { + // C3000XXx + // XX: Static register index, 0x00 to 0x7F for reading or 0x80 to 0xFF for writing. + // x: Register index. + + ulong staticRegisterIndex = InstructionHelper.GetImmediate(instruction, StaticRegisterIndex, StaticRegisterSize); + Register register = context.GetRegister(instruction[RegisterIndex]); + + IOperand sourceRegister; + IOperand destinationRegister; + + if (staticRegisterIndex < FirstWriteRegister) + { + // Read from static register. + sourceRegister = context.GetStaticRegister((byte)staticRegisterIndex); + destinationRegister = register; + } + else + { + // Write to static register. + sourceRegister = register; + destinationRegister = context.GetStaticRegister((byte)(staticRegisterIndex - FirstWriteRegister)); + } + + context.CurrentOperations.Add(new OpMov(destinationRegister, sourceRegister)); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/RegisterConditional.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/RegisterConditional.cs new file mode 100644 index 00000000..4e4d70b0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/RegisterConditional.cs @@ -0,0 +1,106 @@ +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Tamper.Conditions; +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 0xC0 performs a comparison of the contents of a register and another value. + /// This code support multiple operand types, see below. If the condition is not met, + /// all instructions until the appropriate conditional block terminator are skipped. + /// + class RegisterConditional + { + private const int OperationWidthIndex = 2; + private const int ComparisonTypeIndex = 3; + private const int SourceRegisterIndex = 4; + private const int OperandTypeIndex = 5; + private const int RegisterOrMemoryRegionIndex = 6; + private const int OffsetImmediateIndex = 7; + private const int ValueImmediateIndex = 8; + + private const int MemoryRegionWithOffsetImmediate = 0; + private const int MemoryRegionWithOffsetRegister = 1; + private const int AddressRegisterWithOffsetImmediate = 2; + private const int AddressRegisterWithOffsetRegister = 3; + private const int OffsetImmediate = 4; + private const int AddressRegister = 5; + + private const int OffsetImmediateSize = 9; + private const int ValueImmediateSize8 = 8; + private const int ValueImmediateSize16 = 16; + + public static ICondition Emit(byte[] instruction, CompilationContext context) + { + // C0TcSX## + // C0TcS0Ma aaaaaaaa + // C0TcS1Mr + // C0TcS2Ra aaaaaaaa + // C0TcS3Rr + // C0TcS400 VVVVVVVV (VVVVVVVV) + // C0TcS5X0 + // T: Width of memory write(1, 2, 4, or 8 bytes). + // c: Condition to use, see below. + // S: Source Register. + // X: Operand Type, see below. + // M: Memory Type(operand types 0 and 1). + // R: Address Register(operand types 2 and 3). + // a: Relative Address(operand types 0 and 2). + // r: Offset Register(operand types 1 and 3). + // X: Other Register(operand type 5). + // V: Value to compare to(operand type 4). + + byte operationWidth = instruction[OperationWidthIndex]; + Comparison comparison = (Comparison)instruction[ComparisonTypeIndex]; + Register sourceRegister = context.GetRegister(instruction[SourceRegisterIndex]); + byte operandType = instruction[OperandTypeIndex]; + byte registerOrMemoryRegion = instruction[RegisterOrMemoryRegionIndex]; + byte offsetRegisterIndex = instruction[OffsetImmediateIndex]; + ulong offsetImmediate; + ulong valueImmediate; + int valueImmediateSize; + Register addressRegister; + Register offsetRegister; + IOperand sourceOperand; + + switch (operandType) + { + case MemoryRegionWithOffsetImmediate: + // *(?x + #a) + offsetImmediate = InstructionHelper.GetImmediate(instruction, OffsetImmediateIndex, OffsetImmediateSize); + sourceOperand = MemoryHelper.EmitPointer((MemoryRegion)registerOrMemoryRegion, offsetImmediate, context); + break; + case MemoryRegionWithOffsetRegister: + // *(?x + $r) + offsetRegister = context.GetRegister(offsetRegisterIndex); + sourceOperand = MemoryHelper.EmitPointer((MemoryRegion)registerOrMemoryRegion, offsetRegister, context); + break; + case AddressRegisterWithOffsetImmediate: + // *($R + #a) + addressRegister = context.GetRegister(registerOrMemoryRegion); + offsetImmediate = InstructionHelper.GetImmediate(instruction, OffsetImmediateIndex, OffsetImmediateSize); + sourceOperand = MemoryHelper.EmitPointer(addressRegister, offsetImmediate, context); + break; + case AddressRegisterWithOffsetRegister: + // *($R + $r) + addressRegister = context.GetRegister(registerOrMemoryRegion); + offsetRegister = context.GetRegister(offsetRegisterIndex); + sourceOperand = MemoryHelper.EmitPointer(addressRegister, offsetRegister, context); + break; + case OffsetImmediate: + valueImmediateSize = operationWidth <= 4 ? ValueImmediateSize8 : ValueImmediateSize16; + valueImmediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, valueImmediateSize); + sourceOperand = new Value(valueImmediate); + break; + case AddressRegister: + // $V + sourceOperand = context.GetRegister(registerOrMemoryRegion); + break; + default: + throw new TamperCompilationException($"Invalid operand type {operandType} in Atmosphere cheat"); + } + + return InstructionHelper.CreateCondition(comparison, operationWidth, sourceRegister, sourceOperand); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ResumeProcess.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ResumeProcess.cs new file mode 100644 index 00000000..b052d810 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ResumeProcess.cs @@ -0,0 +1,16 @@ +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 0xFF1 resumes the current process. + /// + class ResumeProcess + { + // FF1????? + public static void Emit(byte[] instruction, CompilationContext context) + { + context.CurrentOperations.Add(new OpProcCtrl(context.Process, false)); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegister.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegister.cs new file mode 100644 index 00000000..59cbdb00 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegister.cs @@ -0,0 +1,65 @@ +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 0xC1 performs saving or restoring of registers. + /// NOTE: Registers are saved and restored to a different set of registers than the ones used + /// for the other opcodes (Save Registers). + /// + class SaveOrRestoreRegister + { + private const int DestinationRegisterIndex = 3; + private const int SourceRegisterIndex = 5; + private const int OperationTypeIndex = 6; + + private const int RestoreRegister = 0; + private const int SaveRegister = 1; + private const int ClearSavedValue = 2; + private const int ClearRegister = 3; + + public static void Emit(byte[] instruction, CompilationContext context) + { + // C10D0Sx0 + // D: Destination index. + // S: Source index. + // x: Operand Type, see below. + + byte destinationRegIndex = instruction[DestinationRegisterIndex]; + byte sourceRegIndex = instruction[SourceRegisterIndex]; + byte operationType = instruction[OperationTypeIndex]; + Impl(operationType, destinationRegIndex, sourceRegIndex, context); + } + + public static void Impl(byte operationType, byte destinationRegIndex, byte sourceRegIndex, CompilationContext context) + { + IOperand destinationOperand; + IOperand sourceOperand; + + switch (operationType) + { + case RestoreRegister: + destinationOperand = context.GetRegister(destinationRegIndex); + sourceOperand = context.GetSavedRegister(sourceRegIndex); + break; + case SaveRegister: + destinationOperand = context.GetSavedRegister(destinationRegIndex); + sourceOperand = context.GetRegister(sourceRegIndex); + break; + case ClearSavedValue: + destinationOperand = new Value(0); + sourceOperand = context.GetSavedRegister(sourceRegIndex); + break; + case ClearRegister: + destinationOperand = new Value(0); + sourceOperand = context.GetRegister(sourceRegIndex); + break; + default: + throw new TamperCompilationException($"Invalid register operation type {operationType} in Atmosphere cheat"); + } + + context.CurrentOperations.Add(new OpMov(destinationOperand, sourceOperand)); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegisterWithMask.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegisterWithMask.cs new file mode 100644 index 00000000..6d1c3b3b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegisterWithMask.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 0xC2 performs saving or restoring of multiple registers using a bitmask. + /// NOTE: Registers are saved and restored to a different set of registers than the ones used + /// for the other opcodes (Save Registers). + /// + class SaveOrRestoreRegisterWithMask + { + private const int OperationTypeIndex = 2; + private const int RegisterMaskIndex = 4; + + private const int RegisterMaskSize = 4; + + public static void Emit(byte[] instruction, CompilationContext context) + { + // C2x0XXXX + // x: Operand Type, see below. + // X: 16-bit bitmask, bit i == save or restore register i. + + byte operationType = instruction[OperationTypeIndex]; + ulong mask = InstructionHelper.GetImmediate(instruction, RegisterMaskIndex, RegisterMaskSize); + + for (byte regIndex = 0; mask != 0; mask >>= 1, regIndex++) + { + if ((mask & 0x1) != 0) + { + SaveOrRestoreRegister.Impl(operationType, regIndex, regIndex, context); + } + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StartEndLoop.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StartEndLoop.cs new file mode 100644 index 00000000..22b13ead --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StartEndLoop.cs @@ -0,0 +1,72 @@ +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 3 allows for iterating in a loop a fixed number of times. + /// + class StartEndLoop + { + private const int StartOrEndIndex = 1; + private const int IterationRegisterIndex = 3; + private const int IterationsImmediateIndex = 8; + + private const int IterationsImmediateSize = 8; + + private const byte LoopBegin = 0; + private const byte LoopEnd = 1; + + public static void Emit(byte[] instruction, CompilationContext context) + { + // 300R0000 VVVVVVVV + // R: Register to use as loop counter. + // V: Number of iterations to loop. + + // 310R0000 + + byte mode = instruction[StartOrEndIndex]; + byte iterationRegisterIndex = instruction[IterationRegisterIndex]; + + switch (mode) + { + case LoopBegin: + // Just start a new compilation block and parse the instruction itself at the end. + context.BlockStack.Push(new OperationBlock(instruction)); + return; + case LoopEnd: + break; + default: + throw new TamperCompilationException($"Invalid loop {mode} in Atmosphere cheat"); + } + + // Use the loop begin instruction stored in the stack. + instruction = context.CurrentBlock.BaseInstruction; + CodeType codeType = InstructionHelper.GetCodeType(instruction); + + if (codeType != CodeType.StartEndLoop) + { + throw new TamperCompilationException($"Loop end does not match code type {codeType} in Atmosphere cheat"); + } + + // Validate if the register in the beginning and end are the same. + + byte oldIterationRegisterIndex = instruction[IterationRegisterIndex]; + + if (iterationRegisterIndex != oldIterationRegisterIndex) + { + throw new TamperCompilationException($"The register used for the loop changed from {oldIterationRegisterIndex} to {iterationRegisterIndex} in Atmosphere cheat"); + } + + Register iterationRegister = context.GetRegister(iterationRegisterIndex); + ulong immediate = InstructionHelper.GetImmediate(instruction, IterationsImmediateIndex, IterationsImmediateSize); + + // Create a loop block with the current operations and nest it in the upper + // block of the stack. + + ForBlock block = new(immediate, iterationRegister, context.CurrentOperations); + context.BlockStack.Pop(); + context.CurrentOperations.Add(block); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToAddress.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToAddress.cs new file mode 100644 index 00000000..53716679 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToAddress.cs @@ -0,0 +1,41 @@ +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 0 allows writing a static value to a memory address. + /// + class StoreConstantToAddress + { + private const int OperationWidthIndex = 1; + private const int MemoryRegionIndex = 2; + private const int OffsetRegisterIndex = 3; + private const int OffsetImmediateIndex = 6; + private const int ValueImmediateIndex = 16; + + private const int OffsetImmediateSize = 10; + private const int ValueImmediateSize8 = 8; + private const int ValueImmediateSize16 = 16; + + public static void Emit(byte[] instruction, CompilationContext context) + { + // 0TMR00AA AAAAAAAA VVVVVVVV (VVVVVVVV) + // T: Width of memory write(1, 2, 4, or 8 bytes). + // M: Memory region to write to(0 = Main NSO, 1 = Heap). + // R: Register to use as an offset from memory region base. + // A: Immediate offset to use from memory region base. + // V: Value to write. + + byte operationWidth = instruction[OperationWidthIndex]; + MemoryRegion memoryRegion = (MemoryRegion)instruction[MemoryRegionIndex]; + Register offsetRegister = context.GetRegister(instruction[OffsetRegisterIndex]); + ulong offsetImmediate = InstructionHelper.GetImmediate(instruction, OffsetImmediateIndex, OffsetImmediateSize); + + Pointer dstMem = MemoryHelper.EmitPointer(memoryRegion, offsetRegister, offsetImmediate, context); + + int valueImmediateSize = operationWidth <= 4 ? ValueImmediateSize8 : ValueImmediateSize16; + ulong valueImmediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, valueImmediateSize); + Value storeValue = new(valueImmediate); + + InstructionHelper.EmitMov(operationWidth, context, dstMem, storeValue); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs new file mode 100644 index 00000000..27a99bb6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs @@ -0,0 +1,71 @@ +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 6 allows writing a fixed value to a memory address specified by a register. + /// + class StoreConstantToMemory + { + private const int OperationWidthIndex = 1; + private const int AddressRegisterIndex = 3; + private const int IncrementAddressRegisterIndex = 4; + private const int UseOffsetRegisterIndex = 5; + private const int OffsetRegisterIndex = 6; + private const int ValueImmediateIndex = 8; + + private const int ValueImmediateSize = 16; + + public static void Emit(byte[] instruction, CompilationContext context) + { + // 6T0RIor0 VVVVVVVV VVVVVVVV + // T: Width of memory write(1, 2, 4, or 8 bytes). + // R: Register used as base memory address. + // I: Increment register flag(0 = do not increment R, 1 = increment R by T). + // o: Offset register enable flag(0 = do not add r to address, 1 = add r to address). + // r: Register used as offset when o is 1. + // V: Value to write to memory. + + byte operationWidth = instruction[OperationWidthIndex]; + Register sourceRegister = context.GetRegister(instruction[AddressRegisterIndex]); + byte incrementAddressRegister = instruction[IncrementAddressRegisterIndex]; + byte useOffsetRegister = instruction[UseOffsetRegisterIndex]; + ulong immediate = InstructionHelper.GetImmediate(instruction, ValueImmediateIndex, ValueImmediateSize); + Value storeValue = new(immediate); + + Pointer destinationMemory; + + switch (useOffsetRegister) + { + case 0: + // Don't offset the address register by another register. + destinationMemory = MemoryHelper.EmitPointer(sourceRegister, context); + break; + case 1: + // Replace the source address by the sum of the base and offset registers. + Register offsetRegister = context.GetRegister(instruction[OffsetRegisterIndex]); + destinationMemory = MemoryHelper.EmitPointer(sourceRegister, offsetRegister, context); + break; + default: + throw new TamperCompilationException($"Invalid offset mode {useOffsetRegister} in Atmosphere cheat"); + } + + InstructionHelper.EmitMov(operationWidth, context, destinationMemory, storeValue); + + switch (incrementAddressRegister) + { + case 0: + // Don't increment the address register by operationWidth. + break; + case 1: + // Increment the address register by operationWidth. + IOperand increment = new Value(operationWidth); + context.CurrentOperations.Add(new OpAdd(sourceRegister, sourceRegister, increment)); + break; + default: + throw new TamperCompilationException($"Invalid increment mode {incrementAddressRegister} in Atmosphere cheat"); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreRegisterToMemory.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreRegisterToMemory.cs new file mode 100644 index 00000000..d7f3045b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreRegisterToMemory.cs @@ -0,0 +1,99 @@ +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters +{ + /// + /// Code type 10 allows writing a register to memory. + /// + class StoreRegisterToMemory + { + private const int OperationWidthIndex = 1; + private const int SourceRegisterIndex = 2; + private const int AddressRegisterIndex = 3; + private const int IncrementAddressRegisterIndex = 4; + private const int AddressingTypeIndex = 5; + private const int RegisterOrMemoryRegionIndex = 6; + private const int OffsetImmediateIndex = 7; + + private const int AddressRegister = 0; + private const int AddressRegisterWithOffsetRegister = 1; + private const int OffsetImmediate = 2; + private const int MemoryRegionWithOffsetRegister = 3; + private const int MemoryRegionWithOffsetImmediate = 4; + private const int MemoryRegionWithOffsetRegisterAndImmediate = 5; + + private const int OffsetImmediateSize1 = 1; + private const int OffsetImmediateSize9 = 9; + + public static void Emit(byte[] instruction, CompilationContext context) + { + // ATSRIOxa (aaaaaaaa) + // T: Width of memory write (1, 2, 4, or 8 bytes). + // S: Register to write to memory. + // R: Register to use as base address. + // I: Increment register flag (0 = do not increment R, 1 = increment R by T). + // O: Offset type, see below. + // x: Register used as offset when O is 1, Memory type when O is 3, 4 or 5. + // a: Value used as offset when O is 2, 4 or 5. + + byte operationWidth = instruction[OperationWidthIndex]; + Register sourceRegister = context.GetRegister(instruction[SourceRegisterIndex]); + Register addressRegister = context.GetRegister(instruction[AddressRegisterIndex]); + byte incrementAddressRegister = instruction[IncrementAddressRegisterIndex]; + byte offsetType = instruction[AddressingTypeIndex]; + byte registerOrMemoryRegion = instruction[RegisterOrMemoryRegionIndex]; + int immediateSize = instruction.Length <= 8 ? OffsetImmediateSize1 : OffsetImmediateSize9; + ulong immediate = InstructionHelper.GetImmediate(instruction, OffsetImmediateIndex, immediateSize); + + Pointer destinationMemory; + + switch (offsetType) + { + case AddressRegister: + // *($R) = $S + destinationMemory = MemoryHelper.EmitPointer(addressRegister, context); + break; + case AddressRegisterWithOffsetRegister: + // *($R + $x) = $S + Register offsetRegister = context.GetRegister(registerOrMemoryRegion); + destinationMemory = MemoryHelper.EmitPointer(addressRegister, offsetRegister, context); + break; + case OffsetImmediate: + // *(#a) = $S + destinationMemory = MemoryHelper.EmitPointer(addressRegister, immediate, context); + break; + case MemoryRegionWithOffsetRegister: + // *(?x + $R) = $S + destinationMemory = MemoryHelper.EmitPointer((MemoryRegion)registerOrMemoryRegion, addressRegister, context); + break; + case MemoryRegionWithOffsetImmediate: + // *(?x + #a) = $S + destinationMemory = MemoryHelper.EmitPointer((MemoryRegion)registerOrMemoryRegion, immediate, context); + break; + case MemoryRegionWithOffsetRegisterAndImmediate: + // *(?x + #a + $R) = $S + destinationMemory = MemoryHelper.EmitPointer((MemoryRegion)registerOrMemoryRegion, addressRegister, immediate, context); + break; + default: + throw new TamperCompilationException($"Invalid offset type {offsetType} in Atmosphere cheat"); + } + + InstructionHelper.EmitMov(operationWidth, context, destinationMemory, sourceRegister); + + switch (incrementAddressRegister) + { + case 0: + // Don't increment the address register by operationWidth. + break; + case 1: + // Increment the address register by operationWidth. + IOperand increment = new Value(operationWidth); + context.CurrentOperations.Add(new OpAdd(addressRegister, addressRegister, increment)); + break; + default: + throw new TamperCompilationException($"Invalid increment mode {incrementAddressRegister} in Atmosphere cheat"); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CodeType.cs b/src/Ryujinx.HLE/HOS/Tamper/CodeType.cs new file mode 100644 index 00000000..704233da --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CodeType.cs @@ -0,0 +1,110 @@ +namespace Ryujinx.HLE.HOS.Tamper +{ + /// + /// The opcodes specified for the Atmosphere Cheat VM. + /// + enum CodeType + { + /// + /// Code type 0 allows writing a static value to a memory address. + /// + StoreConstantToAddress = 0x0, + + /// + /// Code type 1 performs a comparison of the contents of memory to a static value. + /// If the condition is not met, all instructions until the appropriate conditional block terminator + /// are skipped. + /// + BeginMemoryConditionalBlock = 0x1, + + /// + /// Code type 2 marks the end of a conditional block (started by Code Type 1 or Code Type 8). + /// + EndConditionalBlock = 0x2, + + /// + /// Code type 3 allows for iterating in a loop a fixed number of times. + /// + StartEndLoop = 0x3, + + /// + /// Code type 4 allows setting a register to a constant value. + /// + LoadRegisterWithContant = 0x4, + + /// + /// Code type 5 allows loading a value from memory into a register, either using a fixed address or by + /// dereferencing the destination register. + /// + LoadRegisterWithMemory = 0x5, + + /// + /// Code type 6 allows writing a fixed value to a memory address specified by a register. + /// + StoreConstantToMemory = 0x6, + + /// + /// Code type 7 allows performing arithmetic on registers. However, it has been deprecated by Code + /// type 9, and is only kept for backwards compatibility. + /// + LegacyArithmetic = 0x7, + + /// + /// Code type 8 enters or skips a conditional block based on whether a key combination is pressed. + /// + BeginKeypressConditionalBlock = 0x8, + + /// + /// Code type 9 allows performing arithmetic on registers. + /// + Arithmetic = 0x9, + + /// + /// Code type 10 allows writing a register to memory. + /// + StoreRegisterToMemory = 0xA, + + /// + /// Code type 0xC0 performs a comparison of the contents of a register and another value. + /// This code support multiple operand types, see below. If the condition is not met, + /// all instructions until the appropriate conditional block terminator are skipped. + /// + BeginRegisterConditionalBlock = 0xC0, + + /// + /// Code type 0xC1 performs saving or restoring of registers. + /// NOTE: Registers are saved and restored to a different set of registers than the ones used + /// for the other opcodes (Save Registers). + /// + SaveOrRestoreRegister = 0xC1, + + /// + /// Code type 0xC2 performs saving or restoring of multiple registers using a bitmask. + /// NOTE: Registers are saved and restored to a different set of registers than the ones used + /// for the other opcodes (Save Registers). + /// + SaveOrRestoreRegisterWithMask = 0xC2, + + /// + /// Code type 0xC3 reads or writes a static register with a given register. + /// NOTE: Registers are saved and restored to a different set of registers than the ones used + /// for the other opcodes (Static Registers). + /// + ReadOrWriteStaticRegister = 0xC3, + + /// + /// Code type 0xFF0 pauses the current process. + /// + PauseProcess = 0xFF0, + + /// + /// Code type 0xFF1 resumes the current process. + /// + ResumeProcess = 0xFF1, + + /// + /// Code type 0xFFF writes a debug log. + /// + DebugLog = 0xFFF, + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Comparison.cs b/src/Ryujinx.HLE/HOS/Tamper/Comparison.cs new file mode 100644 index 00000000..656a62c3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Comparison.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Tamper +{ + /// + /// The comparisons used by conditional operations. + /// + enum Comparison + { + Greater = 1, + GreaterOrEqual = 2, + Less = 3, + LessOrEqual = 4, + Equal = 5, + NotEqual = 6, + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/CompilationContext.cs b/src/Ryujinx.HLE/HOS/Tamper/CompilationContext.cs new file mode 100644 index 00000000..2a8810ad --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/CompilationContext.cs @@ -0,0 +1,75 @@ +using Ryujinx.HLE.HOS.Tamper.Operations; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Tamper +{ + class CompilationContext + { + public OperationBlock CurrentBlock => BlockStack.Peek(); + public List CurrentOperations => CurrentBlock.Operations; + + public ITamperedProcess Process { get; } + public Parameter PressedKeys { get; } + public Stack BlockStack { get; } + public Dictionary Registers { get; } + public Dictionary SavedRegisters { get; } + public Dictionary StaticRegisters { get; } + public ulong ExeAddress { get; } + public ulong HeapAddress { get; } + public ulong AliasAddress { get; } + public ulong AslrAddress { get; } + + public CompilationContext(ulong exeAddress, ulong heapAddress, ulong aliasAddress, ulong aslrAddress, ITamperedProcess process) + { + Process = process; + PressedKeys = new Parameter(0); + BlockStack = new Stack(); + Registers = new Dictionary(); + SavedRegisters = new Dictionary(); + StaticRegisters = new Dictionary(); + ExeAddress = exeAddress; + HeapAddress = heapAddress; + AliasAddress = aliasAddress; + AslrAddress = aslrAddress; + } + + public Register GetRegister(byte index) + { + if (Registers.TryGetValue(index, out Register register)) + { + return register; + } + + register = new Register($"R_{index:X2}"); + Registers.Add(index, register); + + return register; + } + + public Register GetSavedRegister(byte index) + { + if (SavedRegisters.TryGetValue(index, out Register register)) + { + return register; + } + + register = new Register($"S_{index:X2}"); + SavedRegisters.Add(index, register); + + return register; + } + + public Register GetStaticRegister(byte index) + { + if (SavedRegisters.TryGetValue(index, out Register register)) + { + return register; + } + + register = new Register($"T_{index:X2}"); + SavedRegisters.Add(index, register); + + return register; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondEQ.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondEQ.cs new file mode 100644 index 00000000..529ed25b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondEQ.cs @@ -0,0 +1,21 @@ +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.Conditions +{ + class CondEQ : ICondition where T : unmanaged + { + private readonly IOperand _lhs; + private readonly IOperand _rhs; + + public CondEQ(IOperand lhs, IOperand rhs) + { + _lhs = lhs; + _rhs = rhs; + } + + public bool Evaluate() + { + return (dynamic)_lhs.Get() == (dynamic)_rhs.Get(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGE.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGE.cs new file mode 100644 index 00000000..94877c2a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGE.cs @@ -0,0 +1,21 @@ +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.Conditions +{ + class CondGE : ICondition where T : unmanaged + { + private readonly IOperand _lhs; + private readonly IOperand _rhs; + + public CondGE(IOperand lhs, IOperand rhs) + { + _lhs = lhs; + _rhs = rhs; + } + + public bool Evaluate() + { + return (dynamic)_lhs.Get() >= (dynamic)_rhs.Get(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGT.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGT.cs new file mode 100644 index 00000000..35068816 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGT.cs @@ -0,0 +1,21 @@ +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.Conditions +{ + class CondGT : ICondition where T : unmanaged + { + private readonly IOperand _lhs; + private readonly IOperand _rhs; + + public CondGT(IOperand lhs, IOperand rhs) + { + _lhs = lhs; + _rhs = rhs; + } + + public bool Evaluate() + { + return (dynamic)_lhs.Get() > (dynamic)_rhs.Get(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLE.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLE.cs new file mode 100644 index 00000000..dd9cf70c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLE.cs @@ -0,0 +1,21 @@ +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.Conditions +{ + class CondLE : ICondition where T : unmanaged + { + private readonly IOperand _lhs; + private readonly IOperand _rhs; + + public CondLE(IOperand lhs, IOperand rhs) + { + _lhs = lhs; + _rhs = rhs; + } + + public bool Evaluate() + { + return (dynamic)_lhs.Get() <= (dynamic)_rhs.Get(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLT.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLT.cs new file mode 100644 index 00000000..0c85f5e4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLT.cs @@ -0,0 +1,21 @@ +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.Conditions +{ + class CondLT : ICondition where T : unmanaged + { + private readonly IOperand _lhs; + private readonly IOperand _rhs; + + public CondLT(IOperand lhs, IOperand rhs) + { + _lhs = lhs; + _rhs = rhs; + } + + public bool Evaluate() + { + return (dynamic)_lhs.Get() < (dynamic)_rhs.Get(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondNE.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondNE.cs new file mode 100644 index 00000000..b649ecce --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/CondNE.cs @@ -0,0 +1,21 @@ +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper.Conditions +{ + class CondNE : ICondition where T : unmanaged + { + private readonly IOperand _lhs; + private readonly IOperand _rhs; + + public CondNE(IOperand lhs, IOperand rhs) + { + _lhs = lhs; + _rhs = rhs; + } + + public bool Evaluate() + { + return (dynamic)_lhs.Get() != (dynamic)_rhs.Get(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/ICondition.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/ICondition.cs new file mode 100644 index 00000000..f15ceffe --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/ICondition.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Tamper.Conditions +{ + interface ICondition + { + bool Evaluate(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Conditions/InputMask.cs b/src/Ryujinx.HLE/HOS/Tamper/Conditions/InputMask.cs new file mode 100644 index 00000000..6c72eb5c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Conditions/InputMask.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Tamper.Conditions +{ + class InputMask : ICondition + { + private readonly long _mask; + private readonly Parameter _input; + + public InputMask(long mask, Parameter input) + { + _mask = mask; + _input = input; + } + + public bool Evaluate() + { + return (_input.Value & _mask) == _mask; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs b/src/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs new file mode 100644 index 00000000..8458d95d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs @@ -0,0 +1,13 @@ +using Ryujinx.HLE.HOS.Services.Hid; + +namespace Ryujinx.HLE.HOS.Tamper +{ + interface ITamperProgram + { + bool IsEnabled { get; set; } + string Name { get; } + bool TampersCodeMemory { get; set; } + ITamperedProcess Process { get; } + void Execute(ControllerKeys pressedKeys); + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs b/src/Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs new file mode 100644 index 00000000..c86e1021 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs @@ -0,0 +1,16 @@ +using Ryujinx.HLE.HOS.Kernel.Process; + +namespace Ryujinx.HLE.HOS.Tamper +{ + interface ITamperedProcess + { + ProcessState State { get; } + + bool TamperedCodeMemory { get; set; } + + T ReadMemory(ulong va) where T : unmanaged; + void WriteMemory(ulong va, T value) where T : unmanaged; + void PauseProcess(); + void ResumeProcess(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/InstructionHelper.cs b/src/Ryujinx.HLE/HOS/Tamper/InstructionHelper.cs new file mode 100644 index 00000000..759ba5f9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/InstructionHelper.cs @@ -0,0 +1,128 @@ +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Tamper.Conditions; +using Ryujinx.HLE.HOS.Tamper.Operations; +using System; +using System.Globalization; + +namespace Ryujinx.HLE.HOS.Tamper +{ + class InstructionHelper + { + private const int CodeTypeIndex = 0; + + public static void Emit(IOperation operation, CompilationContext context) + { + context.CurrentOperations.Add(operation); + } + + public static void Emit(Type instruction, byte width, CompilationContext context, params Object[] operands) + { + Emit((IOperation)Create(instruction, width, operands), context); + } + + public static void EmitMov(byte width, CompilationContext context, IOperand destination, IOperand source) + { + Emit(typeof(OpMov<>), width, context, destination, source); + } + + public static ICondition CreateCondition(Comparison comparison, byte width, IOperand lhs, IOperand rhs) + { + ICondition Create(Type conditionType) + { + return (ICondition)InstructionHelper.Create(conditionType, width, lhs, rhs); + } + + return comparison switch + { + Comparison.Greater => Create(typeof(CondGT<>)), + Comparison.GreaterOrEqual => Create(typeof(CondGE<>)), + Comparison.Less => Create(typeof(CondLT<>)), + Comparison.LessOrEqual => Create(typeof(CondLE<>)), + Comparison.Equal => Create(typeof(CondEQ<>)), + Comparison.NotEqual => Create(typeof(CondNE<>)), + _ => throw new TamperCompilationException($"Invalid comparison {comparison} in Atmosphere cheat"), + }; + } + + public static Object Create(Type instruction, byte width, params Object[] operands) + { + Type realType = width switch + { + 1 => instruction.MakeGenericType(typeof(byte)), + 2 => instruction.MakeGenericType(typeof(ushort)), + 4 => instruction.MakeGenericType(typeof(uint)), + 8 => instruction.MakeGenericType(typeof(ulong)), + _ => throw new TamperCompilationException($"Invalid instruction width {width} in Atmosphere cheat"), + }; + return Activator.CreateInstance(realType, operands); + } + + public static ulong GetImmediate(byte[] instruction, int index, int nybbleCount) + { + ulong value = 0; + + for (int i = 0; i < nybbleCount; i++) + { + value <<= 4; + value |= instruction[index + i]; + } + + return value; + } + + public static CodeType GetCodeType(byte[] instruction) + { + int codeType = instruction[CodeTypeIndex]; + + if (codeType >= 0xC) + { + byte extension = instruction[CodeTypeIndex + 1]; + codeType = (codeType << 4) | extension; + + if (extension == 0xF) + { + extension = instruction[CodeTypeIndex + 2]; + codeType = (codeType << 4) | extension; + } + } + + return (CodeType)codeType; + } + + public static byte[] ParseRawInstruction(string rawInstruction) + { + const int WordSize = 2 * sizeof(uint); + + // Instructions are multi-word, with 32bit words. Split the raw instruction + // and parse each word into individual nybbles of bits. + + var words = rawInstruction.Split((char[])null, StringSplitOptions.RemoveEmptyEntries); + + byte[] instruction = new byte[WordSize * words.Length]; + + if (words.Length == 0) + { + throw new TamperCompilationException("Empty instruction in Atmosphere cheat"); + } + + for (int wordIndex = 0; wordIndex < words.Length; wordIndex++) + { + string word = words[wordIndex]; + + if (word.Length != WordSize) + { + throw new TamperCompilationException($"Invalid word length for {word} in Atmosphere cheat"); + } + + for (int nybbleIndex = 0; nybbleIndex < WordSize; nybbleIndex++) + { + int index = wordIndex * WordSize + nybbleIndex; + + instruction[index] = byte.Parse(word.AsSpan(nybbleIndex, 1), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + } + } + + return instruction; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs b/src/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs new file mode 100644 index 00000000..b9e649cf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs @@ -0,0 +1,90 @@ +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper +{ + class MemoryHelper + { + public static ulong GetAddressShift(MemoryRegion source, CompilationContext context) + { + return source switch + { + // Memory address is relative to the code start. + MemoryRegion.NSO => context.ExeAddress, + // Memory address is relative to the heap. + MemoryRegion.Heap => context.HeapAddress, + // Memory address is relative to the alias region. + MemoryRegion.Alias => context.AliasAddress, + // Memory address is relative to the asrl region, which matches the code region. + MemoryRegion.Asrl => context.AslrAddress, + _ => throw new TamperCompilationException($"Invalid memory source {source} in Atmosphere cheat"), + }; + } + + private static void EmitAdd(Value finalValue, IOperand firstOperand, IOperand secondOperand, CompilationContext context) + { + context.CurrentOperations.Add(new OpAdd(finalValue, firstOperand, secondOperand)); + } + + public static Pointer EmitPointer(ulong addressImmediate, CompilationContext context) + { + Value addressImmediateValue = new(addressImmediate); + + return new Pointer(addressImmediateValue, context.Process); + } + + public static Pointer EmitPointer(Register addressRegister, CompilationContext context) + { + return new Pointer(addressRegister, context.Process); + } + + public static Pointer EmitPointer(Register addressRegister, ulong offsetImmediate, CompilationContext context) + { + Value offsetImmediateValue = new(offsetImmediate); + Value finalAddressValue = new(0); + EmitAdd(finalAddressValue, addressRegister, offsetImmediateValue, context); + + return new Pointer(finalAddressValue, context.Process); + } + + public static Pointer EmitPointer(Register addressRegister, Register offsetRegister, CompilationContext context) + { + Value finalAddressValue = new(0); + EmitAdd(finalAddressValue, addressRegister, offsetRegister, context); + + return new Pointer(finalAddressValue, context.Process); + } + + public static Pointer EmitPointer(Register addressRegister, Register offsetRegister, ulong offsetImmediate, CompilationContext context) + { + Value offsetImmediateValue = new(offsetImmediate); + Value finalOffsetValue = new(0); + EmitAdd(finalOffsetValue, offsetRegister, offsetImmediateValue, context); + Value finalAddressValue = new(0); + EmitAdd(finalAddressValue, addressRegister, finalOffsetValue, context); + + return new Pointer(finalAddressValue, context.Process); + } + + public static Pointer EmitPointer(MemoryRegion memoryRegion, ulong offsetImmediate, CompilationContext context) + { + offsetImmediate += GetAddressShift(memoryRegion, context); + + return EmitPointer(offsetImmediate, context); + } + + public static Pointer EmitPointer(MemoryRegion memoryRegion, Register offsetRegister, CompilationContext context) + { + ulong offsetImmediate = GetAddressShift(memoryRegion, context); + + return EmitPointer(offsetRegister, offsetImmediate, context); + } + + public static Pointer EmitPointer(MemoryRegion memoryRegion, Register offsetRegister, ulong offsetImmediate, CompilationContext context) + { + offsetImmediate += GetAddressShift(memoryRegion, context); + + return EmitPointer(offsetRegister, offsetImmediate, context); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs b/src/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs new file mode 100644 index 00000000..b6e4ed0a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs @@ -0,0 +1,29 @@ +namespace Ryujinx.HLE.HOS.Tamper +{ + /// + /// The regions in the virtual address space of the process that are used as base address of memory operations. + /// + enum MemoryRegion + { + /// + /// The position of the NSO associated with the cheat in the virtual address space. + /// NOTE: A game can have several NSOs, but the cheat only associates itself with one. + /// + NSO = 0x0, + + /// + /// The address of the heap, as determined by the kernel. + /// + Heap = 0x1, + + /// + /// The address of the alias region, as determined by the kernel. + /// + Alias = 0x2, + + /// + /// The address of the code region with address space layout randomization included. + /// + Asrl = 0x3, + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/OperationBlock.cs b/src/Ryujinx.HLE/HOS/Tamper/OperationBlock.cs new file mode 100644 index 00000000..5a0bccc5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/OperationBlock.cs @@ -0,0 +1,17 @@ +using Ryujinx.HLE.HOS.Tamper.Operations; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Tamper +{ + readonly struct OperationBlock + { + public byte[] BaseInstruction { get; } + public List Operations { get; } + + public OperationBlock(byte[] baseInstruction) + { + BaseInstruction = baseInstruction; + Operations = new List(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/Block.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/Block.cs new file mode 100644 index 00000000..6d4b1637 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/Block.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class Block : IOperation + { + private readonly IEnumerable _operations; + + public Block(IEnumerable operations) + { + _operations = operations; + } + + public Block(params IOperation[] operations) + { + _operations = operations; + } + + public void Execute() + { + foreach (IOperation op in _operations) + { + op.Execute(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/ForBlock.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/ForBlock.cs new file mode 100644 index 00000000..1e8cafa8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/ForBlock.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class ForBlock : IOperation + { + private readonly ulong _count; + private readonly Register _register; + private readonly IEnumerable _operations; + + public ForBlock(ulong count, Register register, IEnumerable operations) + { + _count = count; + _register = register; + _operations = operations; + } + + public ForBlock(ulong count, Register register, params IOperation[] operations) + { + _count = count; + _register = register; + _operations = operations; + } + + public void Execute() + { + for (ulong i = 0; i < _count; i++) + { + // Set the register and execute the operations so that changing the + // register during runtime does not break iteration. + + _register.Set(i); + + foreach (IOperation op in _operations) + { + op.Execute(); + } + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/IOperand.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/IOperand.cs new file mode 100644 index 00000000..1aadda0b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/IOperand.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + interface IOperand + { + public T Get() where T : unmanaged; + public void Set(T value) where T : unmanaged; + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/IOperation.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/IOperation.cs new file mode 100644 index 00000000..a4474979 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/IOperation.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + interface IOperation + { + void Execute(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs new file mode 100644 index 00000000..a6b31f25 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs @@ -0,0 +1,34 @@ +using Ryujinx.HLE.HOS.Tamper.Conditions; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class IfBlock : IOperation + { + private readonly ICondition _condition; + private readonly IEnumerable _operationsThen; + private readonly IEnumerable _operationsElse; + + public IfBlock(ICondition condition, IEnumerable operationsThen, IEnumerable operationsElse) + { + _condition = condition; + _operationsThen = operationsThen; + _operationsElse = operationsElse; + } + + public void Execute() + { + IEnumerable operations = _condition.Evaluate() ? _operationsThen : _operationsElse; + + if (operations == null) + { + return; + } + + foreach (IOperation op in operations) + { + op.Execute(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpAdd.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpAdd.cs new file mode 100644 index 00000000..855245e3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpAdd.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class OpAdd : IOperation where T : unmanaged + { + readonly IOperand _destination; + readonly IOperand _lhs; + readonly IOperand _rhs; + + public OpAdd(IOperand destination, IOperand lhs, IOperand rhs) + { + _destination = destination; + _lhs = lhs; + _rhs = rhs; + } + + public void Execute() + { + _destination.Set((T)((dynamic)_lhs.Get() + (dynamic)_rhs.Get())); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpAnd.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpAnd.cs new file mode 100644 index 00000000..7d1fa10b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpAnd.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class OpAnd : IOperation where T : unmanaged + { + readonly IOperand _destination; + readonly IOperand _lhs; + readonly IOperand _rhs; + + public OpAnd(IOperand destination, IOperand lhs, IOperand rhs) + { + _destination = destination; + _lhs = lhs; + _rhs = rhs; + } + + public void Execute() + { + _destination.Set((T)((dynamic)_lhs.Get() & (dynamic)_rhs.Get())); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpLog.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpLog.cs new file mode 100644 index 00000000..4017e5f7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpLog.cs @@ -0,0 +1,21 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class OpLog : IOperation where T : unmanaged + { + readonly int _logId; + readonly IOperand _source; + + public OpLog(int logId, IOperand source) + { + _logId = logId; + _source = source; + } + + public void Execute() + { + Logger.Debug?.Print(LogClass.TamperMachine, $"Tamper debug log id={_logId} value={(dynamic)_source.Get():X}"); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpLsh.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpLsh.cs new file mode 100644 index 00000000..6c846425 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpLsh.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class OpLsh : IOperation where T : unmanaged + { + readonly IOperand _destination; + readonly IOperand _lhs; + readonly IOperand _rhs; + + public OpLsh(IOperand destination, IOperand lhs, IOperand rhs) + { + _destination = destination; + _lhs = lhs; + _rhs = rhs; + } + + public void Execute() + { + _destination.Set((T)((dynamic)_lhs.Get() << (dynamic)_rhs.Get())); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpMov.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpMov.cs new file mode 100644 index 00000000..af82f18e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpMov.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class OpMov : IOperation where T : unmanaged + { + readonly IOperand _destination; + readonly IOperand _source; + + public OpMov(IOperand destination, IOperand source) + { + _destination = destination; + _source = source; + } + + public void Execute() + { + _destination.Set(_source.Get()); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpMul.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpMul.cs new file mode 100644 index 00000000..a1b080f0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpMul.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class OpMul : IOperation where T : unmanaged + { + readonly IOperand _destination; + readonly IOperand _lhs; + readonly IOperand _rhs; + + public OpMul(IOperand destination, IOperand lhs, IOperand rhs) + { + _destination = destination; + _lhs = lhs; + _rhs = rhs; + } + + public void Execute() + { + _destination.Set((T)((dynamic)_lhs.Get() * (dynamic)_rhs.Get())); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpNot.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpNot.cs new file mode 100644 index 00000000..034e2200 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpNot.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class OpNot : IOperation where T : unmanaged + { + readonly IOperand _destination; + readonly IOperand _source; + + public OpNot(IOperand destination, IOperand source) + { + _destination = destination; + _source = source; + } + + public void Execute() + { + _destination.Set((T)(~(dynamic)_source.Get())); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpOr.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpOr.cs new file mode 100644 index 00000000..0afdc3f4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpOr.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class OpOr : IOperation where T : unmanaged + { + readonly IOperand _destination; + readonly IOperand _lhs; + readonly IOperand _rhs; + + public OpOr(IOperand destination, IOperand lhs, IOperand rhs) + { + _destination = destination; + _lhs = lhs; + _rhs = rhs; + } + + public void Execute() + { + _destination.Set((T)((dynamic)_lhs.Get() | (dynamic)_rhs.Get())); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpProcCtrl.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpProcCtrl.cs new file mode 100644 index 00000000..5de225a1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpProcCtrl.cs @@ -0,0 +1,26 @@ +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class OpProcCtrl : IOperation + { + private readonly ITamperedProcess _process; + private readonly bool _pause; + + public OpProcCtrl(ITamperedProcess process, bool pause) + { + _process = process; + _pause = pause; + } + + public void Execute() + { + if (_pause) + { + _process.PauseProcess(); + } + else + { + _process.ResumeProcess(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpRsh.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpRsh.cs new file mode 100644 index 00000000..e7e0f870 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpRsh.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class OpRsh : IOperation where T : unmanaged + { + readonly IOperand _destination; + readonly IOperand _lhs; + readonly IOperand _rhs; + + public OpRsh(IOperand destination, IOperand lhs, IOperand rhs) + { + _destination = destination; + _lhs = lhs; + _rhs = rhs; + } + + public void Execute() + { + _destination.Set((T)((dynamic)_lhs.Get() >> (dynamic)_rhs.Get())); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpSub.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpSub.cs new file mode 100644 index 00000000..d860d66f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpSub.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class OpSub : IOperation where T : unmanaged + { + readonly IOperand _destination; + readonly IOperand _lhs; + readonly IOperand _rhs; + + public OpSub(IOperand destination, IOperand lhs, IOperand rhs) + { + _destination = destination; + _lhs = lhs; + _rhs = rhs; + } + + public void Execute() + { + _destination.Set((T)((dynamic)_lhs.Get() - (dynamic)_rhs.Get())); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Operations/OpXor.cs b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpXor.cs new file mode 100644 index 00000000..07ba6b33 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Operations/OpXor.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.Tamper.Operations +{ + class OpXor : IOperation where T : unmanaged + { + readonly IOperand _destination; + readonly IOperand _lhs; + readonly IOperand _rhs; + + public OpXor(IOperand destination, IOperand lhs, IOperand rhs) + { + _destination = destination; + _lhs = lhs; + _rhs = rhs; + } + + public void Execute() + { + _destination.Set((T)((dynamic)_lhs.Get() ^ (dynamic)_rhs.Get())); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Parameter.cs b/src/Ryujinx.HLE/HOS/Tamper/Parameter.cs new file mode 100644 index 00000000..824c62fe --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Parameter.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.HLE.HOS.Tamper +{ + class Parameter + { + public T Value { get; set; } + + public Parameter(T value) + { + Value = value; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Pointer.cs b/src/Ryujinx.HLE/HOS/Tamper/Pointer.cs new file mode 100644 index 00000000..c961e1a7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Pointer.cs @@ -0,0 +1,32 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Tamper.Operations; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Tamper +{ + class Pointer : IOperand + { + private readonly IOperand _position; + private readonly ITamperedProcess _process; + + public Pointer(IOperand position, ITamperedProcess process) + { + _position = position; + _process = process; + } + + public T Get() where T : unmanaged + { + return _process.ReadMemory(_position.Get()); + } + + public void Set(T value) where T : unmanaged + { + ulong position = _position.Get(); + + Logger.Debug?.Print(LogClass.TamperMachine, $"0x{position:X16}@{Unsafe.SizeOf()}: {value:X}"); + + _process.WriteMemory(position, value); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Register.cs b/src/Ryujinx.HLE/HOS/Tamper/Register.cs new file mode 100644 index 00000000..cce13ee6 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Register.cs @@ -0,0 +1,28 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper +{ + class Register : IOperand + { + private ulong _register = 0; + private readonly string _alias; + + public Register(string alias) + { + _alias = alias; + } + + public T Get() where T : unmanaged + { + return (T)(dynamic)_register; + } + + public void Set(T value) where T : unmanaged + { + Logger.Debug?.Print(LogClass.TamperMachine, $"{_alias}: {value}"); + + _register = (ulong)(dynamic)value; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs b/src/Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs new file mode 100644 index 00000000..422e4ef0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs @@ -0,0 +1,68 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Kernel.Process; +using System.Runtime.CompilerServices; + +namespace Ryujinx.HLE.HOS.Tamper +{ + class TamperedKProcess : ITamperedProcess + { + private readonly KProcess _process; + + public ProcessState State => _process.State; + + public bool TamperedCodeMemory { get; set; } = false; + + public TamperedKProcess(KProcess process) + { + _process = process; + } + + private void AssertMemoryRegion(ulong va, bool isWrite) where T : unmanaged + { + ulong size = (ulong)Unsafe.SizeOf(); + + // TODO (Caian): This double check is workaround because CpuMemory.IsRangeMapped reports + // some addresses as mapped even though they are not, i. e. 4 bytes from 0xffffffffffffff70. + if (!_process.CpuMemory.IsMapped(va) || !_process.CpuMemory.IsRangeMapped(va, size)) + { + throw new TamperExecutionException($"Unmapped memory access of {size} bytes at 0x{va:X16}"); + } + + if (!isWrite) + { + return; + } + + // TODO (Caian): The JIT does not support invalidating a code region so writing to code memory may not work + // as intended, so taint the operation to issue a warning later. + if (isWrite && (va >= _process.MemoryManager.CodeRegionStart) && (va + size <= _process.MemoryManager.CodeRegionEnd)) + { + TamperedCodeMemory = true; + } + } + + public T ReadMemory(ulong va) where T : unmanaged + { + AssertMemoryRegion(va, false); + + return _process.CpuMemory.Read(va); + } + + public void WriteMemory(ulong va, T value) where T : unmanaged + { + AssertMemoryRegion(va, true); + _process.CpuMemory.Write(va, value); + } + + public void PauseProcess() + { + Logger.Warning?.Print(LogClass.TamperMachine, "Process pausing is not supported!"); + } + + public void ResumeProcess() + { + Logger.Warning?.Print(LogClass.TamperMachine, "Process resuming is not supported!"); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Tamper/Value.cs b/src/Ryujinx.HLE/HOS/Tamper/Value.cs new file mode 100644 index 00000000..436fc13d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Tamper/Value.cs @@ -0,0 +1,24 @@ +using Ryujinx.HLE.HOS.Tamper.Operations; + +namespace Ryujinx.HLE.HOS.Tamper +{ + class Value : IOperand where TP : unmanaged + { + private TP _value; + + public Value(TP value) + { + _value = value; + } + + public T Get() where T : unmanaged + { + return (T)(dynamic)_value; + } + + public void Set(T value) where T : unmanaged + { + _value = (TP)(dynamic)value; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/TamperMachine.cs b/src/Ryujinx.HLE/HOS/TamperMachine.cs new file mode 100644 index 00000000..60922153 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/TamperMachine.cs @@ -0,0 +1,188 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Services.Hid; +using Ryujinx.HLE.HOS.Tamper; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS +{ + public class TamperMachine + { + // Atmosphere specifies a delay of 83 milliseconds between the execution of the last + // cheat and the re-execution of the first one. + private const int TamperMachineSleepMs = 1000 / 12; + + private Thread _tamperThread = null; + private readonly ConcurrentQueue _programs = new(); + private long _pressedKeys = 0; + private readonly Dictionary _programDictionary = new(); + + private void Activate() + { + if (_tamperThread == null || !_tamperThread.IsAlive) + { + _tamperThread = new Thread(this.TamperRunner) + { + Name = "HLE.TamperMachine", + }; + _tamperThread.Start(); + } + } + + internal void InstallAtmosphereCheat(string name, string buildId, IEnumerable rawInstructions, ProcessTamperInfo info, ulong exeAddress) + { + if (!CanInstallOnPid(info.Process.Pid)) + { + return; + } + + ITamperedProcess tamperedProcess = new TamperedKProcess(info.Process); + AtmosphereCompiler compiler = new(exeAddress, info.HeapAddress, info.AliasAddress, info.AslrAddress, tamperedProcess); + ITamperProgram program = compiler.Compile(name, rawInstructions); + + if (program != null) + { + program.TampersCodeMemory = false; + + _programs.Enqueue(program); + _programDictionary.TryAdd($"{buildId}-{name}", program); + } + + Activate(); + } + + private static bool CanInstallOnPid(ulong pid) + { + // Do not allow tampering of kernel processes. + if (pid < KernelConstants.InitialProcessId) + { + Logger.Warning?.Print(LogClass.TamperMachine, $"Refusing to tamper kernel process {pid}"); + + return false; + } + + return true; + } + + public void EnableCheats(string[] enabledCheats) + { + foreach (var program in _programDictionary.Values) + { + program.IsEnabled = false; + } + + foreach (var cheat in enabledCheats) + { + if (_programDictionary.TryGetValue(cheat, out var program)) + { + program.IsEnabled = true; + } + } + } + + private static bool IsProcessValid(ITamperedProcess process) + { + return process.State != ProcessState.Crashed && process.State != ProcessState.Exiting && process.State != ProcessState.Exited; + } + + private void TamperRunner() + { + Logger.Info?.Print(LogClass.TamperMachine, "TamperMachine thread running"); + + int sleepCounter = 0; + + while (true) + { + // Sleep to not consume too much CPU. + if (sleepCounter == 0) + { + sleepCounter = _programs.Count; + Thread.Sleep(TamperMachineSleepMs); + } + else + { + sleepCounter--; + } + + if (!AdvanceTamperingsQueue()) + { + // No more work to be done. + + Logger.Info?.Print(LogClass.TamperMachine, "TamperMachine thread exiting"); + + return; + } + } + } + + private bool AdvanceTamperingsQueue() + { + if (!_programs.TryDequeue(out ITamperProgram program)) + { + // No more programs in the queue. + _programDictionary.Clear(); + + return false; + } + + // Check if the process is still suitable for running the tamper program. + if (!IsProcessValid(program.Process)) + { + // Exit without re-enqueuing the program because the process is no longer valid. + return true; + } + + // Re-enqueue the tampering program because the process is still valid. + _programs.Enqueue(program); + + Logger.Debug?.Print(LogClass.TamperMachine, $"Running tampering program {program.Name}"); + + try + { + ControllerKeys pressedKeys = (ControllerKeys)Volatile.Read(ref _pressedKeys); + program.Process.TamperedCodeMemory = false; + program.Execute(pressedKeys); + + // Detect the first attempt to tamper memory and log it. + if (!program.TampersCodeMemory && program.Process.TamperedCodeMemory) + { + program.TampersCodeMemory = true; + + Logger.Warning?.Print(LogClass.TamperMachine, $"Tampering program {program.Name} modifies code memory so it may not work properly"); + } + } + catch (Exception ex) + { + Logger.Debug?.Print(LogClass.TamperMachine, $"The tampering program {program.Name} crashed, this can happen while the game is starting"); + + if (!string.IsNullOrEmpty(ex.Message)) + { + Logger.Debug?.Print(LogClass.TamperMachine, ex.Message); + } + } + + return true; + } + + public void UpdateInput(List gamepadInputs) + { + // Look for the input of the player one or the handheld. + foreach (GamepadInput input in gamepadInputs) + { + if (input.PlayerId == PlayerIndex.Player1 || input.PlayerId == PlayerIndex.Handheld) + { + Volatile.Write(ref _pressedKeys, (long)input.Buttons); + + return; + } + } + + // Clear the input because player one is not conected. + Volatile.Write(ref _pressedKeys, 0); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/UserChannelPersistence.cs b/src/Ryujinx.HLE/HOS/UserChannelPersistence.cs new file mode 100644 index 00000000..64e5ee4b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/UserChannelPersistence.cs @@ -0,0 +1,60 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS +{ + public class UserChannelPersistence + { + private readonly Stack _userChannelStorages; + public int PreviousIndex { get; private set; } + public int Index { get; private set; } + public ProgramSpecifyKind Kind { get; private set; } + public bool ShouldRestart { get; set; } + + public UserChannelPersistence() + { + _userChannelStorages = new Stack(); + Kind = ProgramSpecifyKind.ExecuteProgram; + PreviousIndex = -1; + Index = 0; + } + + public void Clear() + { + _userChannelStorages.Clear(); + } + + public void Push(byte[] data) + { + _userChannelStorages.Push(data); + } + + public byte[] Pop() + { + _userChannelStorages.TryPop(out byte[] result); + + return result; + } + + public bool IsEmpty => _userChannelStorages.Count == 0; + + public void ExecuteProgram(ProgramSpecifyKind kind, ulong value) + { + Kind = kind; + PreviousIndex = Index; + ShouldRestart = true; + + switch (kind) + { + case ProgramSpecifyKind.ExecuteProgram: + Index = (int)value; + break; + case ProgramSpecifyKind.RestartProgram: + break; + default: + throw new NotImplementedException($"{kind} not implemented"); + } + } + } +} diff --git a/src/Ryujinx.HLE/Homebrew.npdm b/src/Ryujinx.HLE/Homebrew.npdm new file mode 100644 index 0000000000000000000000000000000000000000..814116146521574a55b96b6614fb0c55d132058f GIT binary patch literal 972 zcmeZu4RK_E0%iss7!yhhFgO+z0K{vU$TMS9*YF7e zM`ur$HH;AdfGA!8Fq;uZI{?!NC{-2!X%-;e0Of;K0SSZ5x6jvSU|8^99%#0`1jGM&2B!c2*}&R>?lnNx4|69>%mK)d c0pbK82D!HYh>>#@vU@@5KmbOA*p#>r082ehU;qFB literal 0 HcmV?d00001 diff --git a/src/Ryujinx.HLE/Loaders/Elf/ElfDynamic.cs b/src/Ryujinx.HLE/Loaders/Elf/ElfDynamic.cs new file mode 100644 index 00000000..4cc1a9fe --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Elf/ElfDynamic.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + struct ElfDynamic + { + public ElfDynamicTag Tag { get; private set; } + + public long Value { get; private set; } + + public ElfDynamic(ElfDynamicTag tag, long value) + { + Tag = tag; + Value = value; + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Elf/ElfDynamicTag.cs b/src/Ryujinx.HLE/Loaders/Elf/ElfDynamicTag.cs new file mode 100644 index 00000000..6505e17d --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Elf/ElfDynamicTag.cs @@ -0,0 +1,76 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Ryujinx.HLE.Loaders.Elf +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + enum ElfDynamicTag + { + DT_NULL = 0, + DT_NEEDED = 1, + DT_PLTRELSZ = 2, + DT_PLTGOT = 3, + DT_HASH = 4, + DT_STRTAB = 5, + DT_SYMTAB = 6, + DT_RELA = 7, + DT_RELASZ = 8, + DT_RELAENT = 9, + DT_STRSZ = 10, + DT_SYMENT = 11, + DT_INIT = 12, + DT_FINI = 13, + DT_SONAME = 14, + DT_RPATH = 15, + DT_SYMBOLIC = 16, + DT_REL = 17, + DT_RELSZ = 18, + DT_RELENT = 19, + DT_PLTREL = 20, + DT_DEBUG = 21, + DT_TEXTREL = 22, + DT_JMPREL = 23, + DT_BIND_NOW = 24, + DT_INIT_ARRAY = 25, + DT_FINI_ARRAY = 26, + DT_INIT_ARRAYSZ = 27, + DT_FINI_ARRAYSZ = 28, + DT_RUNPATH = 29, + DT_FLAGS = 30, + DT_ENCODING = 32, + DT_PREINIT_ARRAY = 32, + DT_PREINIT_ARRAYSZ = 33, + DT_GNU_PRELINKED = 0x6ffffdf5, + DT_GNU_CONFLICTSZ = 0x6ffffdf6, + DT_GNU_LIBLISTSZ = 0x6ffffdf7, + DT_CHECKSUM = 0x6ffffdf8, + DT_PLTPADSZ = 0x6ffffdf9, + DT_MOVEENT = 0x6ffffdfa, + DT_MOVESZ = 0x6ffffdfb, + DT_FEATURE_1 = 0x6ffffdfc, + DT_POSFLAG_1 = 0x6ffffdfd, + DT_SYMINSZ = 0x6ffffdfe, + DT_SYMINENT = 0x6ffffdff, + DT_GNU_HASH = 0x6ffffef5, + DT_TLSDESC_PLT = 0x6ffffef6, + DT_TLSDESC_GOT = 0x6ffffef7, + DT_GNU_CONFLICT = 0x6ffffef8, + DT_GNU_LIBLIST = 0x6ffffef9, + DT_CONFIG = 0x6ffffefa, + DT_DEPAUDIT = 0x6ffffefb, + DT_AUDIT = 0x6ffffefc, + DT_PLTPAD = 0x6ffffefd, + DT_MOVETAB = 0x6ffffefe, + DT_SYMINFO = 0x6ffffeff, + DT_VERSYM = 0x6ffffff0, + DT_RELACOUNT = 0x6ffffff9, + DT_RELCOUNT = 0x6ffffffa, + DT_FLAGS_1 = 0x6ffffffb, + DT_VERDEF = 0x6ffffffc, + DT_VERDEFNUM = 0x6ffffffd, + DT_VERNEED = 0x6ffffffe, + DT_VERNEEDNUM = 0x6fffffff, + DT_AUXILIARY = 0x7ffffffd, + DT_FILTER = 0x7fffffff, + } +} diff --git a/src/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs b/src/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs new file mode 100644 index 00000000..1ed61b2c --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs @@ -0,0 +1,35 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + struct ElfSymbol + { + public string Name { get; private set; } + + public ElfSymbolType Type { get; private set; } + public ElfSymbolBinding Binding { get; private set; } + public ElfSymbolVisibility Visibility { get; private set; } + + public readonly bool IsFuncOrObject => Type == ElfSymbolType.SttFunc || Type == ElfSymbolType.SttObject; + public readonly bool IsGlobalOrWeak => Binding == ElfSymbolBinding.StbGlobal || Binding == ElfSymbolBinding.StbWeak; + + public int ShIdx { get; private set; } + public ulong Value { get; private set; } + public ulong Size { get; private set; } + + public ElfSymbol( + string name, + int info, + int other, + int shIdx, + ulong value, + ulong size) + { + Name = name; + Type = (ElfSymbolType)(info & 0xf); + Binding = (ElfSymbolBinding)(info >> 4); + Visibility = (ElfSymbolVisibility)other; + ShIdx = shIdx; + Value = value; + Size = size; + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs b/src/Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs new file mode 100644 index 00000000..8d4df894 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + struct ElfSymbol32 + { +#pragma warning disable CS0649 // Field is never assigned to + public uint NameOffset; + public uint ValueAddress; + public uint Size; + public byte Info; + public byte Other; + public ushort SectionIndex; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs b/src/Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs new file mode 100644 index 00000000..45a75361 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + struct ElfSymbol64 + { +#pragma warning disable CS0649 // Field is never assigned to + public uint NameOffset; + public byte Info; + public byte Other; + public ushort SectionIndex; + public ulong ValueAddress; + public ulong Size; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.HLE/Loaders/Elf/ElfSymbolBinding.cs b/src/Ryujinx.HLE/Loaders/Elf/ElfSymbolBinding.cs new file mode 100644 index 00000000..cf504ae2 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Elf/ElfSymbolBinding.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + enum ElfSymbolBinding + { + StbLocal = 0, + StbGlobal = 1, + StbWeak = 2, + } +} diff --git a/src/Ryujinx.HLE/Loaders/Elf/ElfSymbolType.cs b/src/Ryujinx.HLE/Loaders/Elf/ElfSymbolType.cs new file mode 100644 index 00000000..9dc41436 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Elf/ElfSymbolType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + enum ElfSymbolType + { + SttNoType = 0, + SttObject = 1, + SttFunc = 2, + SttSection = 3, + SttFile = 4, + SttCommon = 5, + SttTls = 6, + } +} diff --git a/src/Ryujinx.HLE/Loaders/Elf/ElfSymbolVisibility.cs b/src/Ryujinx.HLE/Loaders/Elf/ElfSymbolVisibility.cs new file mode 100644 index 00000000..6305ab1c --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Elf/ElfSymbolVisibility.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.Loaders.Elf +{ + enum ElfSymbolVisibility + { + StvDefault = 0, + StvInternal = 1, + StvHidden = 2, + StvProtected = 3, + } +} diff --git a/src/Ryujinx.HLE/Loaders/Executables/IExecutable.cs b/src/Ryujinx.HLE/Loaders/Executables/IExecutable.cs new file mode 100644 index 00000000..06f6d969 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Executables/IExecutable.cs @@ -0,0 +1,18 @@ +using System; + +namespace Ryujinx.HLE.Loaders.Executables +{ + interface IExecutable + { + byte[] Program { get; } + Span Text { get; } + Span Ro { get; } + Span Data { get; } + + uint TextOffset { get; } + uint RoOffset { get; } + uint DataOffset { get; } + uint BssOffset { get; } + uint BssSize { get; } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Executables/KipExecutable.cs b/src/Ryujinx.HLE/Loaders/Executables/KipExecutable.cs new file mode 100644 index 00000000..83380ff4 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Executables/KipExecutable.cs @@ -0,0 +1,86 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Kernel; +using System; + +namespace Ryujinx.HLE.Loaders.Executables +{ + class KipExecutable : IExecutable + { + public byte[] Program { get; } + public Span Text => Program.AsSpan((int)TextOffset, (int)TextSize); + public Span Ro => Program.AsSpan((int)RoOffset, (int)RoSize); + public Span Data => Program.AsSpan((int)DataOffset, (int)DataSize); + + public uint TextOffset { get; } + public uint RoOffset { get; } + public uint DataOffset { get; } + public uint BssOffset { get; } + + public uint TextSize { get; } + public uint RoSize { get; } + public uint DataSize { get; } + public uint BssSize { get; } + + public uint[] Capabilities { get; } + public bool UsesSecureMemory { get; } + public bool Is64BitAddressSpace { get; } + public bool Is64Bit { get; } + public ulong ProgramId { get; } + public byte Priority { get; } + public int StackSize { get; } + public byte IdealCoreId { get; } + public int Version { get; } + public string Name { get; } + + public KipExecutable(in SharedRef inStorage) + { + KipReader reader = new(); + + reader.Initialize(in inStorage).ThrowIfFailure(); + + TextOffset = (uint)reader.Segments[0].MemoryOffset; + RoOffset = (uint)reader.Segments[1].MemoryOffset; + DataOffset = (uint)reader.Segments[2].MemoryOffset; + BssOffset = (uint)reader.Segments[3].MemoryOffset; + BssSize = (uint)reader.Segments[3].Size; + + StackSize = reader.StackSize; + + UsesSecureMemory = reader.UsesSecureMemory; + Is64BitAddressSpace = reader.Is64BitAddressSpace; + Is64Bit = reader.Is64Bit; + + ProgramId = reader.ProgramId; + Priority = reader.Priority; + IdealCoreId = reader.IdealCoreId; + Version = reader.Version; + Name = reader.Name.ToString(); + + Capabilities = new uint[32]; + + for (int index = 0; index < Capabilities.Length; index++) + { + Capabilities[index] = reader.Capabilities[index]; + } + + reader.GetSegmentSize(KipReader.SegmentType.Data, out int uncompressedSize).ThrowIfFailure(); + Program = new byte[DataOffset + uncompressedSize]; + + TextSize = DecompressSection(reader, KipReader.SegmentType.Text, TextOffset, Program); + RoSize = DecompressSection(reader, KipReader.SegmentType.Ro, RoOffset, Program); + DataSize = DecompressSection(reader, KipReader.SegmentType.Data, DataOffset, Program); + } + + private static uint DecompressSection(KipReader reader, KipReader.SegmentType segmentType, uint offset, byte[] program) + { + reader.GetSegmentSize(segmentType, out int uncompressedSize).ThrowIfFailure(); + + var span = program.AsSpan((int)offset, uncompressedSize); + + reader.ReadSegment(segmentType, span).ThrowIfFailure(); + + return (uint)uncompressedSize; + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Executables/NroExecutable.cs b/src/Ryujinx.HLE/Loaders/Executables/NroExecutable.cs new file mode 100644 index 00000000..6b5a8c83 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Executables/NroExecutable.cs @@ -0,0 +1,38 @@ +using LibHac.Fs; +using LibHac.Tools.Ro; +using System; + +namespace Ryujinx.HLE.Loaders.Executables +{ + class NroExecutable : Nro, IExecutable + { + public byte[] Program { get; } + public Span Text => Program.AsSpan((int)TextOffset, (int)Header.NroSegments[0].Size); + public Span Ro => Program.AsSpan((int)RoOffset, (int)Header.NroSegments[1].Size); + public Span Data => Program.AsSpan((int)DataOffset, (int)Header.NroSegments[2].Size); + + public uint TextOffset => Header.NroSegments[0].FileOffset; + public uint RoOffset => Header.NroSegments[1].FileOffset; + public uint DataOffset => Header.NroSegments[2].FileOffset; + public uint BssOffset => DataOffset + (uint)Data.Length; + public uint BssSize => Header.BssSize; + + public uint Mod0Offset => (uint)Start.Mod0Offset; + public uint FileSize => Header.Size; + + public ulong SourceAddress { get; private set; } + public ulong BssAddress { get; private set; } + + public NroExecutable(IStorage inStorage, ulong sourceAddress = 0, ulong bssAddress = 0) : base(inStorage) + { + Program = new byte[FileSize]; + + SourceAddress = sourceAddress; + BssAddress = bssAddress; + + OpenNroSegment(NroSegmentType.Text, false).Read(0, Text); + OpenNroSegment(NroSegmentType.Ro, false).Read(0, Ro); + OpenNroSegment(NroSegmentType.Data, false).Read(0, Data); + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Executables/NsoExecutable.cs b/src/Ryujinx.HLE/Loaders/Executables/NsoExecutable.cs new file mode 100644 index 00000000..83ad5d7e --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Executables/NsoExecutable.cs @@ -0,0 +1,123 @@ +using LibHac.Common.FixedArrays; +using LibHac.Fs; +using LibHac.Loader; +using LibHac.Tools.FsSystem; +using Ryujinx.Common.Logging; +using System; +using System.Text; +using System.Text.RegularExpressions; + +namespace Ryujinx.HLE.Loaders.Executables +{ + partial class NsoExecutable : IExecutable + { + public byte[] Program { get; } + public Span Text => Program.AsSpan((int)TextOffset, (int)TextSize); + public Span Ro => Program.AsSpan((int)RoOffset, (int)RoSize); + public Span Data => Program.AsSpan((int)DataOffset, (int)DataSize); + + public uint TextOffset { get; } + public uint RoOffset { get; } + public uint DataOffset { get; } + public uint BssOffset => DataOffset + (uint)Data.Length; + + public uint TextSize { get; } + public uint RoSize { get; } + public uint DataSize { get; } + public uint BssSize { get; } + + public string Name; + public Array32 BuildId; + + [GeneratedRegex(@"[a-z]:[\\/][ -~]{5,}\.nss", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] + private static partial Regex ModuleRegex(); + [GeneratedRegex(@"sdk_version: ([0-9.]*)")] + private static partial Regex FsSdkRegex(); + [GeneratedRegex(@"SDK MW[ -~]*")] + private static partial Regex SdkMwRegex(); + + public NsoExecutable(IStorage inStorage, string name = null) + { + NsoReader reader = new(); + + reader.Initialize(inStorage.AsFile(OpenMode.Read)).ThrowIfFailure(); + + TextOffset = reader.Header.Segments[0].MemoryOffset; + RoOffset = reader.Header.Segments[1].MemoryOffset; + DataOffset = reader.Header.Segments[2].MemoryOffset; + BssSize = reader.Header.BssSize; + + reader.GetSegmentSize(NsoReader.SegmentType.Data, out uint uncompressedSize).ThrowIfFailure(); + + Program = new byte[DataOffset + uncompressedSize]; + + TextSize = DecompressSection(reader, NsoReader.SegmentType.Text, TextOffset); + RoSize = DecompressSection(reader, NsoReader.SegmentType.Ro, RoOffset); + DataSize = DecompressSection(reader, NsoReader.SegmentType.Data, DataOffset); + + Name = name; + BuildId = reader.Header.ModuleId; + + PrintRoSectionInfo(); + } + + private uint DecompressSection(NsoReader reader, NsoReader.SegmentType segmentType, uint offset) + { + reader.GetSegmentSize(segmentType, out uint uncompressedSize).ThrowIfFailure(); + + var span = Program.AsSpan((int)offset, (int)uncompressedSize); + + reader.ReadSegment(segmentType, span).ThrowIfFailure(); + + return uncompressedSize; + } + + private void PrintRoSectionInfo() + { + string rawTextBuffer = Encoding.ASCII.GetString(Ro); + StringBuilder stringBuilder = new(); + + string modulePath = null; + + if (BitConverter.ToInt32(Ro[..4]) == 0) + { + int length = BitConverter.ToInt32(Ro.Slice(4, 4)); + if (length > 0) + { + modulePath = Encoding.UTF8.GetString(Ro.Slice(8, length)); + } + } + + if (string.IsNullOrEmpty(modulePath)) + { + Match moduleMatch = ModuleRegex().Match(rawTextBuffer); + if (moduleMatch.Success) + { + modulePath = moduleMatch.Value; + } + } + + stringBuilder.AppendLine($" Module: {modulePath}"); + + Match fsSdkMatch = FsSdkRegex().Match(rawTextBuffer); + if (fsSdkMatch.Success) + { + stringBuilder.AppendLine($" FS SDK Version: {fsSdkMatch.Value.Replace("sdk_version: ", "")}"); + } + + MatchCollection sdkMwMatches = SdkMwRegex().Matches(rawTextBuffer); + if (sdkMwMatches.Count != 0) + { + string libHeader = " SDK Libraries: "; + string libContent = string.Join($"\n{new string(' ', libHeader.Length)}", sdkMwMatches); + + stringBuilder.AppendLine($"{libHeader}{libContent}"); + } + + if (stringBuilder.Length > 0) + { + Logger.Info?.Print(LogClass.Loader, $"{Name}:\n{stringBuilder.ToString().TrimEnd('\r', '\n')}"); + } + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Mods/IPSPatcher.cs b/src/Ryujinx.HLE/Loaders/Mods/IPSPatcher.cs new file mode 100644 index 00000000..cf316b56 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Mods/IPSPatcher.cs @@ -0,0 +1,116 @@ +using Ryujinx.Common.Logging; +using System; +using System.IO; + +namespace Ryujinx.HLE.Loaders.Mods +{ + class IpsPatcher + { + readonly MemPatch _patches; + + public IpsPatcher(BinaryReader reader) + { + _patches = ParseIps(reader); + if (_patches != null) + { + Logger.Info?.Print(LogClass.ModLoader, "IPS patch loaded successfully"); + } + } + + private static MemPatch ParseIps(BinaryReader reader) + { + ReadOnlySpan ipsHeaderMagic = "PATCH"u8; + ReadOnlySpan ipsTailMagic = "EOF"u8; + ReadOnlySpan ips32HeaderMagic = "IPS32"u8; + ReadOnlySpan ips32TailMagic = "EEOF"u8; + + MemPatch patches = new(); + var header = reader.ReadBytes(ipsHeaderMagic.Length).AsSpan(); + + if (header.Length != ipsHeaderMagic.Length) + { + return null; + } + + bool is32; + ReadOnlySpan tailSpan; + + if (header.SequenceEqual(ipsHeaderMagic)) + { + is32 = false; + tailSpan = ipsTailMagic; + } + else if (header.SequenceEqual(ips32HeaderMagic)) + { + is32 = true; + tailSpan = ips32TailMagic; + } + else + { + return null; + } + + byte[] buf = new byte[tailSpan.Length]; + + bool ReadNext(int size) => reader.Read(buf, 0, size) != size; + + while (true) + { + if (ReadNext(buf.Length)) + { + return null; + } + + if (buf.AsSpan().SequenceEqual(tailSpan)) + { + break; + } + + int patchOffset = is32 ? buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3] + : buf[0] << 16 | buf[1] << 8 | buf[2]; + + if (ReadNext(2)) + { + return null; + } + + int patchSize = buf[0] << 8 | buf[1]; + + if (patchSize == 0) // RLE/Fill mode + { + if (ReadNext(2)) + { + return null; + } + + int fillLength = buf[0] << 8 | buf[1]; + + if (ReadNext(1)) + { + return null; + } + + patches.AddFill((uint)patchOffset, fillLength, buf[0]); + } + else // Copy mode + { + var patch = reader.ReadBytes(patchSize); + + if (patch.Length != patchSize) + { + return null; + } + + patches.Add((uint)patchOffset, patch); + } + } + + return patches; + } + + public void AddPatches(MemPatch patches) + { + patches.AddFrom(_patches); + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Mods/IPSwitchPatcher.cs b/src/Ryujinx.HLE/Loaders/Mods/IPSwitchPatcher.cs new file mode 100644 index 00000000..693e0388 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Mods/IPSwitchPatcher.cs @@ -0,0 +1,281 @@ +using Ryujinx.Common.Logging; +using System; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.Loaders.Mods +{ + class IPSwitchPatcher + { + const string BidHeader = "@nsobid-"; + + private enum Token + { + Normal, + String, + EscapeChar, + Comment, + } + + private readonly StreamReader _reader; + public string BuildId { get; } + + public IPSwitchPatcher(StreamReader reader) + { + string header = reader.ReadLine(); + if (header == null || !header.StartsWith(BidHeader)) + { + Logger.Error?.Print(LogClass.ModLoader, "IPSwitch: Malformed PCHTXT file. Skipping..."); + + return; + } + + _reader = reader; + BuildId = header[BidHeader.Length..].TrimEnd().TrimEnd('0'); + } + + // Uncomments line and unescapes C style strings within + private static string PreprocessLine(string line) + { + StringBuilder str = new(); + Token state = Token.Normal; + + for (int i = 0; i < line.Length; ++i) + { + char c = line[i]; + char la = i + 1 != line.Length ? line[i + 1] : '\0'; + + switch (state) + { + case Token.Normal: + state = c == '"' ? Token.String : + c == '/' && la == '/' ? Token.Comment : + c == '/' && la != '/' ? Token.Comment : // Ignore error and stop parsing + Token.Normal; + break; + case Token.String: + state = c switch + { + '"' => Token.Normal, + '\\' => Token.EscapeChar, + _ => Token.String, + }; + break; + case Token.EscapeChar: + state = Token.String; + c = c switch + { + 'a' => '\a', + 'b' => '\b', + 'f' => '\f', + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + 'v' => '\v', + '\\' => '\\', + _ => '?', + }; + break; + } + + if (state == Token.Comment) + { + break; + } + + if (state < Token.EscapeChar) + { + str.Append(c); + } + } + + return str.ToString().Trim(); + } + + static int ParseHexByte(byte c) + { + if (c >= '0' && c <= '9') + { + return c - '0'; + } + else if (c >= 'A' && c <= 'F') + { + return c - 'A' + 10; + } + else if (c >= 'a' && c <= 'f') + { + return c - 'a' + 10; + } + else + { + return 0; + } + } + + // Big Endian + static byte[] Hex2ByteArrayBE(string hexstr) + { + if ((hexstr.Length & 1) == 1) + { + return null; + } + + byte[] bytes = new byte[hexstr.Length >> 1]; + + for (int i = 0; i < hexstr.Length; i += 2) + { + int high = ParseHexByte((byte)hexstr[i]); + int low = ParseHexByte((byte)hexstr[i + 1]); + + bytes[i >> 1] = (byte)((high << 4) | low); + } + + return bytes; + } + + // Auto base discovery + private static bool ParseInt(string str, out int value) + { + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) + { + return int.TryParse(str.AsSpan(2), System.Globalization.NumberStyles.HexNumber, null, out value); + } + else + { + return int.TryParse(str, System.Globalization.NumberStyles.Integer, null, out value); + } + } + + private MemPatch Parse() + { + if (_reader == null) + { + return null; + } + + MemPatch patches = new(); + + bool enabled = false; + bool printValues = false; + int offsetShift = 0; + + string line; + int lineNum = 0; + + static void Print(string s) => Logger.Info?.Print(LogClass.ModLoader, $"IPSwitch: {s}"); + + void ParseWarn() => Logger.Warning?.Print(LogClass.ModLoader, $"IPSwitch: Parse error at line {lineNum} for bid={BuildId}"); + + while ((line = _reader.ReadLine()) != null) + { + if (string.IsNullOrWhiteSpace(line)) + { + enabled = false; + + continue; + } + + line = PreprocessLine(line); + lineNum += 1; + + if (line.Length == 0) + { + continue; + } + else if (line.StartsWith('#')) + { + Print(line); + } + else if (line.StartsWith("@stop")) + { + break; + } + else if (line.StartsWith("@enabled")) + { + enabled = true; + } + else if (line.StartsWith("@disabled")) + { + enabled = false; + } + else if (line.StartsWith("@flag")) + { + var tokens = line.Split(' ', 3, StringSplitOptions.RemoveEmptyEntries); + + if (tokens.Length < 2) + { + ParseWarn(); + + continue; + } + + if (tokens[1] == "offset_shift") + { + if (tokens.Length != 3 || !ParseInt(tokens[2], out offsetShift)) + { + ParseWarn(); + + continue; + } + } + else if (tokens[1] == "print_values") + { + printValues = true; + } + } + else if (line.StartsWith('@')) + { + // Ignore + } + else + { + if (!enabled) + { + continue; + } + + var tokens = line.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries); + + if (tokens.Length < 2) + { + ParseWarn(); + + continue; + } + + if (!int.TryParse(tokens[0], System.Globalization.NumberStyles.HexNumber, null, out int offset)) + { + ParseWarn(); + + continue; + } + + offset += offsetShift; + + if (printValues) + { + Print($"print_values 0x{offset:x} <= {tokens[1]}"); + } + + if (tokens[1][0] == '"') + { + var patch = Encoding.ASCII.GetBytes(tokens[1].Trim('"') + "\0"); + patches.Add((uint)offset, patch); + } + else + { + var patch = Hex2ByteArrayBE(tokens[1]); + patches.Add((uint)offset, patch); + } + } + } + + return patches; + } + + public void AddPatches(MemPatch patches) + { + patches.AddFrom(Parse()); + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Mods/MemPatch.cs b/src/Ryujinx.HLE/Loaders/Mods/MemPatch.cs new file mode 100644 index 00000000..0a1f12b1 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Mods/MemPatch.cs @@ -0,0 +1,96 @@ +using Ryujinx.Common.Logging; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.HLE.Loaders.Mods +{ + public class MemPatch + { + readonly Dictionary _patches = new(); + + /// + /// Adds a patch to specified offset. Overwrites if already present. + /// + /// Memory offset + /// The patch to add + public void Add(uint offset, byte[] patch) + { + _patches[offset] = patch; + } + + /// + /// Adds a patch in the form of an RLE (Fill mode). + /// + /// Memory offset + /// + /// The byte to fill + public void AddFill(uint offset, int length, byte filler) + { + // TODO: Can be made space efficient by changing `_patches` + // Should suffice for now + byte[] patch = new byte[length]; + patch.AsSpan().Fill(filler); + + _patches[offset] = patch; + } + + /// + /// Adds all patches from an existing MemPatch + /// + /// The patches to add + public void AddFrom(MemPatch patches) + { + if (patches == null) + { + return; + } + + foreach (var (patchOffset, patch) in patches._patches) + { + _patches[patchOffset] = patch; + } + } + + /// + /// Applies all the patches added to this instance. + /// + /// + /// Patches are applied in ascending order of offsets to guarantee + /// overlapping patches always apply the same way. + /// + /// The span of bytes to patch + /// The maximum size of the slice of patchable memory + /// A secondary offset used in special cases (NSO header) + /// Successful patches count + public int Patch(Span memory, int protectedOffset = 0) + { + int count = 0; + foreach (var (offset, patch) in _patches.OrderBy(item => item.Key)) + { + int patchOffset = (int)offset; + int patchSize = patch.Length; + + if (patchOffset < protectedOffset || patchOffset > memory.Length) + { + continue; // Add warning? + } + + patchOffset -= protectedOffset; + + if (patchOffset + patchSize > memory.Length) + { + patchSize = memory.Length - patchOffset; // Add warning? + } + + Logger.Info?.Print(LogClass.ModLoader, $"Patching address offset {patchOffset:x} <= {BitConverter.ToString(patch).Replace('-', ' ')} len={patchSize}"); + + patch.AsSpan(0, patchSize).CopyTo(memory.Slice(patchOffset, patchSize)); + + count++; + } + + return count; + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs b/src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs new file mode 100644 index 00000000..8d828e8e --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs @@ -0,0 +1,59 @@ +using Ryujinx.HLE.Exceptions; +using System.IO; + +namespace Ryujinx.HLE.Loaders.Npdm +{ + public class Aci0 + { + private const int Aci0Magic = 'A' << 0 | 'C' << 8 | 'I' << 16 | '0' << 24; + + public ulong TitleId { get; set; } + + public int FsVersion { get; private set; } + public ulong FsPermissionsBitmask { get; private set; } + + public ServiceAccessControl ServiceAccessControl { get; private set; } + public KernelAccessControl KernelAccessControl { get; private set; } + + /// The stream doesn't contain valid ACI0 data. + /// The stream does not support reading, is , or is already closed. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurred. + /// The FsAccessHeader.ContentOwnerId section is not implemented. + public Aci0(Stream stream, int offset) + { + stream.Seek(offset, SeekOrigin.Begin); + + BinaryReader reader = new(stream); + + if (reader.ReadInt32() != Aci0Magic) + { + throw new InvalidNpdmException("ACI0 Stream doesn't contain ACI0 section!"); + } + + stream.Seek(0xc, SeekOrigin.Current); + + TitleId = reader.ReadUInt64(); + + // Reserved. + stream.Seek(8, SeekOrigin.Current); + + int fsAccessHeaderOffset = reader.ReadInt32(); + int fsAccessHeaderSize = reader.ReadInt32(); + int serviceAccessControlOffset = reader.ReadInt32(); + int serviceAccessControlSize = reader.ReadInt32(); + int kernelAccessControlOffset = reader.ReadInt32(); + int kernelAccessControlSize = reader.ReadInt32(); + + FsAccessHeader fsAccessHeader = new(stream, offset + fsAccessHeaderOffset, fsAccessHeaderSize); + + FsVersion = fsAccessHeader.Version; + FsPermissionsBitmask = fsAccessHeader.PermissionsBitmask; + + ServiceAccessControl = new ServiceAccessControl(stream, offset + serviceAccessControlOffset, serviceAccessControlSize); + + KernelAccessControl = new KernelAccessControl(stream, offset + kernelAccessControlOffset, kernelAccessControlSize); + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Npdm/ACID.cs b/src/Ryujinx.HLE/Loaders/Npdm/ACID.cs new file mode 100644 index 00000000..57d0ee27 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Npdm/ACID.cs @@ -0,0 +1,66 @@ +using Ryujinx.HLE.Exceptions; +using System.IO; + +namespace Ryujinx.HLE.Loaders.Npdm +{ + public class Acid + { + private const int AcidMagic = 'A' << 0 | 'C' << 8 | 'I' << 16 | 'D' << 24; + + public byte[] Rsa2048Signature { get; private set; } + public byte[] Rsa2048Modulus { get; private set; } + public int Unknown1 { get; private set; } + public int Flags { get; private set; } + + public long TitleIdRangeMin { get; private set; } + public long TitleIdRangeMax { get; private set; } + + public FsAccessControl FsAccessControl { get; private set; } + public ServiceAccessControl ServiceAccessControl { get; private set; } + public KernelAccessControl KernelAccessControl { get; private set; } + + /// The stream doesn't contain valid ACID data. + /// The stream does not support reading, is , or is already closed. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurred. + public Acid(Stream stream, int offset) + { + stream.Seek(offset, SeekOrigin.Begin); + + BinaryReader reader = new(stream); + + Rsa2048Signature = reader.ReadBytes(0x100); + Rsa2048Modulus = reader.ReadBytes(0x100); + + if (reader.ReadInt32() != AcidMagic) + { + throw new InvalidNpdmException("ACID Stream doesn't contain ACID section!"); + } + + // Size field used with the above signature (?). + Unknown1 = reader.ReadInt32(); + + reader.ReadInt32(); + + // Bit0 must be 1 on retail, on devunit 0 is also allowed. Bit1 is unknown. + Flags = reader.ReadInt32(); + + TitleIdRangeMin = reader.ReadInt64(); + TitleIdRangeMax = reader.ReadInt64(); + + int fsAccessControlOffset = reader.ReadInt32(); + int fsAccessControlSize = reader.ReadInt32(); + int serviceAccessControlOffset = reader.ReadInt32(); + int serviceAccessControlSize = reader.ReadInt32(); + int kernelAccessControlOffset = reader.ReadInt32(); + int kernelAccessControlSize = reader.ReadInt32(); + + FsAccessControl = new FsAccessControl(stream, offset + fsAccessControlOffset, fsAccessControlSize); + + ServiceAccessControl = new ServiceAccessControl(stream, offset + serviceAccessControlOffset, serviceAccessControlSize); + + KernelAccessControl = new KernelAccessControl(stream, offset + kernelAccessControlOffset, kernelAccessControlSize); + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs b/src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs new file mode 100644 index 00000000..a369f9f2 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs @@ -0,0 +1,32 @@ +using System.IO; + +namespace Ryujinx.HLE.Loaders.Npdm +{ + public class FsAccessControl + { + public int Version { get; private set; } + public ulong PermissionsBitmask { get; private set; } + public int Unknown1 { get; private set; } + public int Unknown2 { get; private set; } + public int Unknown3 { get; private set; } + public int Unknown4 { get; private set; } + + /// The stream does not support reading, is , or is already closed. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurred. + public FsAccessControl(Stream stream, int offset, int size) + { + stream.Seek(offset, SeekOrigin.Begin); + + BinaryReader reader = new(stream); + + Version = reader.ReadInt32(); + PermissionsBitmask = reader.ReadUInt64(); + Unknown1 = reader.ReadInt32(); + Unknown2 = reader.ReadInt32(); + Unknown3 = reader.ReadInt32(); + Unknown4 = reader.ReadInt32(); + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs b/src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs new file mode 100644 index 00000000..249f8dd9 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs @@ -0,0 +1,44 @@ +using Ryujinx.HLE.Exceptions; +using System; +using System.IO; + +namespace Ryujinx.HLE.Loaders.Npdm +{ + class FsAccessHeader + { + public int Version { get; private set; } + public ulong PermissionsBitmask { get; private set; } + + /// The stream contains invalid data. + /// The ContentOwnerId section is not implemented. + /// The stream does not support reading, is , or is already closed. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurred. + public FsAccessHeader(Stream stream, int offset, int size) + { + stream.Seek(offset, SeekOrigin.Begin); + + BinaryReader reader = new(stream); + + Version = reader.ReadInt32(); + PermissionsBitmask = reader.ReadUInt64(); + + int dataSize = reader.ReadInt32(); + + if (dataSize != 0x1c) + { + throw new InvalidNpdmException("FsAccessHeader is corrupted!"); + } +#pragma warning disable IDE0059 // Remove unnecessary value assignment + int contentOwnerIdSize = reader.ReadInt32(); +#pragma warning restore IDE0059 + int dataAndContentOwnerIdSize = reader.ReadInt32(); + + if (dataAndContentOwnerIdSize != 0x1c) + { + throw new NotImplementedException("ContentOwnerId section is not implemented!"); + } + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs b/src/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs new file mode 100644 index 00000000..979c6f66 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs @@ -0,0 +1,27 @@ +using System.IO; + +namespace Ryujinx.HLE.Loaders.Npdm +{ + public class KernelAccessControl + { + public int[] Capabilities { get; private set; } + + /// The stream does not support reading, is , or is already closed. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurred. + public KernelAccessControl(Stream stream, int offset, int size) + { + stream.Seek(offset, SeekOrigin.Begin); + + Capabilities = new int[size / 4]; + + BinaryReader reader = new(stream); + + for (int index = 0; index < Capabilities.Length; index++) + { + Capabilities[index] = reader.ReadInt32(); + } + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs b/src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs new file mode 100644 index 00000000..4a99de98 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs @@ -0,0 +1,81 @@ +using Ryujinx.HLE.Exceptions; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.Loaders.Npdm +{ + // https://github.com/SciresM/hactool/blob/master/npdm.c + // https://github.com/SciresM/hactool/blob/master/npdm.h + // http://switchbrew.org/index.php?title=NPDM + public class Npdm + { + private const int MetaMagic = 'M' << 0 | 'E' << 8 | 'T' << 16 | 'A' << 24; + + public byte ProcessFlags { get; private set; } + public bool Is64Bit { get; private set; } + public byte MainThreadPriority { get; private set; } + public byte DefaultCpuId { get; private set; } + public int PersonalMmHeapSize { get; private set; } + public int Version { get; private set; } + public int MainThreadStackSize { get; private set; } + public string TitleName { get; set; } + public byte[] ProductCode { get; private set; } + + public Aci0 Aci0 { get; private set; } + public Acid Acid { get; private set; } + + /// The stream doesn't contain valid NPDM data. + /// The FsAccessHeader.ContentOwnerId section is not implemented. + /// The stream does not support reading, is , or is already closed. + /// An error occured while reading bytes from the stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurred. + public Npdm(Stream stream) + { + BinaryReader reader = new(stream); + + if (reader.ReadInt32() != MetaMagic) + { + throw new InvalidNpdmException("NPDM Stream doesn't contain NPDM file!"); + } + + reader.ReadInt64(); + + ProcessFlags = reader.ReadByte(); + + Is64Bit = (ProcessFlags & 1) != 0; + + reader.ReadByte(); + + MainThreadPriority = reader.ReadByte(); + DefaultCpuId = reader.ReadByte(); + + reader.ReadInt32(); + + PersonalMmHeapSize = reader.ReadInt32(); + + Version = reader.ReadInt32(); + + MainThreadStackSize = reader.ReadInt32(); + + byte[] tempTitleName = reader.ReadBytes(0x10); + + TitleName = Encoding.UTF8.GetString(tempTitleName, 0, tempTitleName.Length).Trim('\0'); + + ProductCode = reader.ReadBytes(0x10); + + stream.Seek(0x30, SeekOrigin.Current); + + int aci0Offset = reader.ReadInt32(); +#pragma warning disable IDE0059 // Remove unnecessary value assignment + int aci0Size = reader.ReadInt32(); + int acidOffset = reader.ReadInt32(); + int acidSize = reader.ReadInt32(); +#pragma warning restore IDE0059 + + Aci0 = new Aci0(stream, aci0Offset); + Acid = new Acid(stream, acidOffset); + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs b/src/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs new file mode 100644 index 00000000..b6bc6492 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Text; + +namespace Ryujinx.HLE.Loaders.Npdm +{ + public class ServiceAccessControl + { + public IReadOnlyDictionary Services { get; private set; } + + /// The stream does not support reading, is , or is already closed. + /// An error occured while reading bytes from the stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurred. + public ServiceAccessControl(Stream stream, int offset, int size) + { + stream.Seek(offset, SeekOrigin.Begin); + + BinaryReader reader = new(stream); + + int bytesRead = 0; + + Dictionary services = new(); + + while (bytesRead != size) + { + byte controlByte = reader.ReadByte(); + + if (controlByte == 0) + { + break; + } + + int length = (controlByte & 0x07) + 1; + bool registerAllowed = (controlByte & 0x80) != 0; + + services[Encoding.ASCII.GetString(reader.ReadBytes(length))] = registerAllowed; + + bytesRead += length + 1; + } + + Services = new ReadOnlyDictionary(services); + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs new file mode 100644 index 00000000..3904d660 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs @@ -0,0 +1,132 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Loader; +using LibHac.Ns; +using LibHac.Tools.FsSystem; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.Memory; +using System; +using System.Linq; +using static Ryujinx.HLE.HOS.ModLoader; + +namespace Ryujinx.HLE.Loaders.Processes.Extensions +{ + static class FileSystemExtensions + { + public static MetaLoader GetNpdm(this IFileSystem fileSystem) + { + MetaLoader metaLoader = new(); + + if (fileSystem == null || !fileSystem.FileExists(ProcessConst.MainNpdmPath)) + { + Logger.Warning?.Print(LogClass.Loader, "NPDM file not found, using default values!"); + + metaLoader.LoadDefault(); + } + else + { + metaLoader.LoadFromFile(fileSystem); + } + + return metaLoader; + } + + public static ProcessResult Load(this IFileSystem exeFs, Switch device, BlitStruct nacpData, MetaLoader metaLoader, byte programIndex, bool isHomebrew = false) + { + ulong programId = metaLoader.GetProgramId(); + + // Replace the whole ExeFs partition by the modded one. + if (device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(programId, ref exeFs)) + { + metaLoader = null; + } + + // Reload the MetaLoader in case of ExeFs partition replacement. + metaLoader ??= exeFs.GetNpdm(); + + NsoExecutable[] nsoExecutables = new NsoExecutable[ProcessConst.ExeFsPrefixes.Length]; + + for (int i = 0; i < nsoExecutables.Length; i++) + { + string name = ProcessConst.ExeFsPrefixes[i]; + + if (!exeFs.FileExists($"/{name}")) + { + continue; // File doesn't exist, skip. + } + + Logger.Info?.Print(LogClass.Loader, $"Loading {name}..."); + + using var nsoFile = new UniqueRef(); + + exeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + nsoExecutables[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name); + } + + // ExeFs file replacements. + ModLoadResult modLoadResult = device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(programId, nsoExecutables); + + // Take the Npdm from mods if present. + if (modLoadResult.Npdm != null) + { + metaLoader = modLoadResult.Npdm; + } + + // Collect the Nsos, ignoring ones that aren't used. + nsoExecutables = nsoExecutables.Where(x => x != null).ToArray(); + + // Apply Nsos patches. + device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(programId, nsoExecutables); + + // Don't use PTC if ExeFS files have been replaced. + bool enablePtc = device.System.EnablePtc && !modLoadResult.Modified; + if (!enablePtc) + { + Logger.Warning?.Print(LogClass.Ptc, "Detected unsupported ExeFs modifications. PTC disabled."); + } + + string programName = ""; + + if (!isHomebrew && programId > 0x010000000000FFFF) + { + programName = nacpData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString(); + + if (string.IsNullOrWhiteSpace(programName)) + { + programName = Array.Find(nacpData.Value.Title.ItemsRo.ToArray(), x => x.Name[0] != 0).NameString.ToString(); + } + } + + // Initialize GPU. + Graphics.Gpu.GraphicsConfig.TitleId = $"{programId:x16}"; + device.Gpu.HostInitalized.Set(); + + if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible)) + { + device.Configuration.MemoryManagerMode = MemoryManagerMode.SoftwarePageTable; + } + + ProcessResult processResult = ProcessLoaderHelper.LoadNsos( + device, + device.System.KernelContext, + metaLoader, + nacpData, + enablePtc, + true, + programName, + metaLoader.GetProgramId(), + programIndex, + null, + nsoExecutables); + + // TODO: This should be stored using ProcessId instead. + device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(metaLoader.GetProgramId()); + + return processResult; + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs new file mode 100644 index 00000000..6c2a1989 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs @@ -0,0 +1,36 @@ +using LibHac.Common; +using LibHac.FsSystem; +using LibHac.Loader; +using LibHac.Ncm; +using LibHac.Ns; +using Ryujinx.HLE.Loaders.Processes.Extensions; + +namespace Ryujinx.HLE.Loaders.Processes +{ + static class LocalFileSystemExtensions + { + public static ProcessResult Load(this LocalFileSystem exeFs, Switch device, string romFsPath = "") + { + MetaLoader metaLoader = exeFs.GetNpdm(); + var nacpData = new BlitStruct(1); + ulong programId = metaLoader.GetProgramId(); + + device.Configuration.VirtualFileSystem.ModLoader.CollectMods(new[] { programId }); + + if (programId != 0) + { + ProcessLoaderHelper.EnsureSaveData(device, new ApplicationId(programId), nacpData); + } + + ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader, 0); + + // Load RomFS. + if (!string.IsNullOrEmpty(romFsPath)) + { + device.Configuration.VirtualFileSystem.LoadRomFs(processResult.ProcessId, romFsPath); + } + + return processResult; + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs new file mode 100644 index 00000000..92e71cb5 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs @@ -0,0 +1,61 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Loader; +using LibHac.Util; +using Ryujinx.Common; +using System; + +namespace Ryujinx.HLE.Loaders.Processes.Extensions +{ + public static class MetaLoaderExtensions + { + public static ulong GetProgramId(this MetaLoader metaLoader) + { + metaLoader.GetNpdm(out var npdm).ThrowIfFailure(); + + return npdm.Aci.ProgramId.Value; + } + + public static string GetProgramName(this MetaLoader metaLoader) + { + metaLoader.GetNpdm(out var npdm).ThrowIfFailure(); + + return StringUtils.Utf8ZToString(npdm.Meta.ProgramName); + } + + public static bool IsProgram64Bit(this MetaLoader metaLoader) + { + metaLoader.GetNpdm(out var npdm).ThrowIfFailure(); + + return (npdm.Meta.Flags & 1) != 0; + } + + public static void LoadDefault(this MetaLoader metaLoader) + { + byte[] npdmBuffer = EmbeddedResources.Read("Ryujinx.HLE/Homebrew.npdm"); + + metaLoader.Load(npdmBuffer).ThrowIfFailure(); + } + + public static void LoadFromFile(this MetaLoader metaLoader, IFileSystem fileSystem, string path = "") + { + if (string.IsNullOrEmpty(path)) + { + path = ProcessConst.MainNpdmPath; + } + + using var npdmFile = new UniqueRef(); + + fileSystem.OpenFile(ref npdmFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure(); + + Span npdmBuffer = new byte[fileSize]; + + npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure(); + + metaLoader.Load(npdmBuffer).ThrowIfFailure(); + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs new file mode 100644 index 00000000..2928ac7f --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs @@ -0,0 +1,258 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Loader; +using LibHac.Ncm; +using LibHac.Ns; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using LibHac.Tools.Ncm; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.Utilities; +using System.IO; +using System.Linq; +using ApplicationId = LibHac.Ncm.ApplicationId; +using ContentType = LibHac.Ncm.ContentType; +using Path = System.IO.Path; + +namespace Ryujinx.HLE.Loaders.Processes.Extensions +{ + public static class NcaExtensions + { + private static readonly TitleUpdateMetadataJsonSerializerContext _applicationSerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca) + { + // Extract RomFs and ExeFs from NCA. + IStorage romFs = nca.GetRomFs(device, patchNca); + IFileSystem exeFs = nca.GetExeFs(device, patchNca); + + if (exeFs == null) + { + Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA"); + + return ProcessResult.Failed; + } + + // Load Npdm file. + MetaLoader metaLoader = exeFs.GetNpdm(); + + // Collecting mods related to AocTitleIds and ProgramId. + device.Configuration.VirtualFileSystem.ModLoader.CollectMods( + device.Configuration.ContentManager.GetAocTitleIds().Prepend(metaLoader.GetProgramId()), + ModLoader.GetModsBasePath(), + ModLoader.GetSdModsBasePath()); + + // Load Nacp file. + var nacpData = new BlitStruct(1); + + if (controlNca != null) + { + nacpData = controlNca.GetNacp(device); + } + + /* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" non-existent update. + + // Load program 0 control NCA as we are going to need it for display version. + (_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _); + + // NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application. + // As such, to avoid PTC cache confusion, we only trust the program 0 display version when launching a sub program. + if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0) + { + nacpData.Value.DisplayVersion = updateProgram0ControlNca.GetNacp(_device).Value.DisplayVersion; + } + + */ + + ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader, (byte)nca.GetProgramIndex()); + + // Load RomFS. + if (romFs == null) + { + Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA"); + } + else + { + romFs = device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(processResult.ProgramId, romFs); + + device.Configuration.VirtualFileSystem.SetRomFs(processResult.ProcessId, romFs.AsStream(FileAccess.Read)); + } + + // Don't create save data for system programs. + if (processResult.ProgramId != 0 && (processResult.ProgramId < SystemProgramId.Start.Value || processResult.ProgramId > SystemAppletId.End.Value)) + { + // Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble. + // We'll know if this changes in the future because applications will get errors when trying to mount the correct save. + ProcessLoaderHelper.EnsureSaveData(device, new ApplicationId(processResult.ProgramId & ~0xFul), nacpData); + } + + return processResult; + } + + public static ulong GetProgramIdBase(this Nca nca) + { + return nca.Header.TitleId & ~0x1FFFUL; + } + + public static int GetProgramIndex(this Nca nca) + { + return (int)(nca.Header.TitleId & 0xF); + } + + public static bool IsProgram(this Nca nca) + { + return nca.Header.ContentType == NcaContentType.Program; + } + + public static bool IsMain(this Nca nca) + { + return nca.IsProgram() && !nca.IsPatch(); + } + + public static bool IsPatch(this Nca nca) + { + int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); + + return nca.IsProgram() && nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection(); + } + + public static bool IsControl(this Nca nca) + { + return nca.Header.ContentType == NcaContentType.Control; + } + + public static (Nca, Nca) GetUpdateData(this Nca mainNca, VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel, int programIndex, out string updatePath) + { + updatePath = null; + + // Load Update NCAs. + Nca updatePatchNca = null; + Nca updateControlNca = null; + + // Clear the program index part. + ulong titleIdBase = mainNca.GetProgramIdBase(); + + // Load update information if exists. + string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json"); + if (File.Exists(titleUpdateMetadataPath)) + { + updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _applicationSerializerContext.TitleUpdateMetadata).Selected; + if (File.Exists(updatePath)) + { + IFileSystem updatePartitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(updatePath, fileSystem); + + foreach ((ulong applicationTitleId, ContentMetaData content) in updatePartitionFileSystem.GetContentData(ContentMetaType.Patch, fileSystem, checkLevel)) + { + if ((applicationTitleId & ~0x1FFFUL) != titleIdBase) + { + continue; + } + + updatePatchNca = content.GetNcaByType(fileSystem.KeySet, ContentType.Program, programIndex); + updateControlNca = content.GetNcaByType(fileSystem.KeySet, ContentType.Control, programIndex); + break; + } + } + } + + return (updatePatchNca, updateControlNca); + } + + public static IFileSystem GetExeFs(this Nca nca, Switch device, Nca patchNca = null) + { + IFileSystem exeFs = null; + + if (patchNca == null) + { + if (nca.CanOpenSection(NcaSectionType.Code)) + { + exeFs = nca.OpenFileSystem(NcaSectionType.Code, device.System.FsIntegrityCheckLevel); + } + } + else + { + if (patchNca.CanOpenSection(NcaSectionType.Code)) + { + exeFs = nca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, device.System.FsIntegrityCheckLevel); + } + } + + return exeFs; + } + + public static IStorage GetRomFs(this Nca nca, Switch device, Nca patchNca = null) + { + IStorage romFs = null; + + if (patchNca == null) + { + if (nca.CanOpenSection(NcaSectionType.Data)) + { + romFs = nca.OpenStorage(NcaSectionType.Data, device.System.FsIntegrityCheckLevel); + } + } + else + { + if (patchNca.CanOpenSection(NcaSectionType.Data)) + { + romFs = nca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, device.System.FsIntegrityCheckLevel); + } + } + + return romFs; + } + + public static BlitStruct GetNacp(this Nca controlNca, Switch device) + { + var nacpData = new BlitStruct(1); + + using var controlFile = new UniqueRef(); + + Result result = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel) + .OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read); + + if (result.IsSuccess()) + { + result = controlFile.Get.Read(out long bytesRead, 0, nacpData.ByteSpan, ReadOption.None); + } + else + { + nacpData.ByteSpan.Clear(); + } + + return nacpData; + } + + public static Cnmt GetCnmt(this Nca cnmtNca, IntegrityCheckLevel checkLevel, ContentMetaType metaType) + { + string path = $"/{metaType}_{cnmtNca.Header.TitleId:x16}.cnmt"; + using var cnmtFile = new UniqueRef(); + + try + { + Result result = cnmtNca.OpenFileSystem(0, checkLevel) + .OpenFile(ref cnmtFile.Ref, path.ToU8Span(), OpenMode.Read); + + if (result.IsSuccess()) + { + return new Cnmt(cnmtFile.Release().AsStream()); + } + } + catch (HorizonResultException ex) + { + if (!ResultFs.PathNotFound.Includes(ex.ResultValue)) + { + Logger.Warning?.Print(LogClass.Application, $"Failed get CNMT for '{cnmtNca.Header.TitleId:x16}' from NCA: {ex.Message}"); + } + } + + return null; + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs new file mode 100644 index 00000000..b3590d9b --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs @@ -0,0 +1,162 @@ +using LibHac.Common; +using LibHac.Common.Keys; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using LibHac.Tools.Ncm; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; +using System; +using System.Collections.Generic; +using System.IO; +using ContentType = LibHac.Ncm.ContentType; + +namespace Ryujinx.HLE.Loaders.Processes.Extensions +{ + public static class PartitionFileSystemExtensions + { + private static readonly DownloadableContentJsonSerializerContext _contentSerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public static Dictionary GetContentData(this IFileSystem partitionFileSystem, + ContentMetaType contentType, VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel) + { + fileSystem.ImportTickets(partitionFileSystem); + + var programs = new Dictionary(); + + foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.cnmt.nca")) + { + Cnmt cnmt = partitionFileSystem.GetNca(fileSystem.KeySet, fileEntry.FullPath).GetCnmt(checkLevel, contentType); + + if (cnmt == null) + { + continue; + } + + ContentMetaData content = new(partitionFileSystem, cnmt); + + if (content.Type != contentType) + { + continue; + } + + programs.TryAdd(content.ApplicationId, content); + } + + return programs; + } + + internal static (bool, ProcessResult) TryLoad(this PartitionFileSystemCore partitionFileSystem, Switch device, string path, ulong applicationId, out string errorMessage) + where TMetaData : PartitionFileSystemMetaCore, new() + where TFormat : IPartitionFileSystemFormat + where THeader : unmanaged, IPartitionFileSystemHeader + where TEntry : unmanaged, IPartitionFileSystemEntry + { + errorMessage = null; + + // Load required NCAs. + Nca mainNca = null; + Nca patchNca = null; + Nca controlNca = null; + + try + { + Dictionary applications = partitionFileSystem.GetContentData(ContentMetaType.Application, device.FileSystem, device.System.FsIntegrityCheckLevel); + + if (applicationId == 0) + { + foreach ((ulong _, ContentMetaData content) in applications) + { + mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index); + controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index); + break; + } + } + else if (applications.TryGetValue(applicationId, out ContentMetaData content)) + { + mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index); + controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index); + } + + ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure(); + } + catch (Exception ex) + { + errorMessage = $"Unable to load: {ex.Message}"; + + return (false, ProcessResult.Failed); + } + + if (mainNca != null) + { + if (mainNca.Header.ContentType != NcaContentType.Program) + { + errorMessage = "Selected NCA file is not a \"Program\" NCA"; + + return (false, ProcessResult.Failed); + } + + (Nca updatePatchNca, Nca updateControlNca) = mainNca.GetUpdateData(device.FileSystem, device.System.FsIntegrityCheckLevel, device.Configuration.UserChannelPersistence.Index, out string _); + + if (updatePatchNca != null) + { + patchNca = updatePatchNca; + } + + if (updateControlNca != null) + { + controlNca = updateControlNca; + } + + // TODO: If we want to support multi-processes in future, we shouldn't clear AddOnContent data here. + device.Configuration.ContentManager.ClearAocData(); + + // Load DownloadableContents. + string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.GetProgramIdBase().ToString("x16"), "dlc.json"); + if (File.Exists(addOnContentMetadataPath)) + { + List dlcContainerList = JsonHelper.DeserializeFromFile(addOnContentMetadataPath, _contentSerializerContext.ListDownloadableContentContainer); + + foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList) + { + foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) + { + if (File.Exists(downloadableContentContainer.ContainerPath)) + { + if (downloadableContentNca.Enabled) + { + device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath); + } + } + else + { + Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed."); + } + } + } + } + + return (true, mainNca.Load(device, patchNca, controlNca)); + } + + errorMessage = $"Unable to load: Could not find Main NCA for title \"{applicationId:X16}\""; + + return (false, ProcessResult.Failed); + } + + public static Nca GetNca(this IFileSystem fileSystem, KeySet keySet, string path) + { + using var ncaFile = new UniqueRef(); + + fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + return new Nca(keySet, ncaFile.Release().AsStorage()); + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs new file mode 100644 index 00000000..778f9ce6 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.HLE.Loaders.Processes +{ + static class ProcessConst + { + // Binaries from exefs are loaded into mem in this order. Do not change. + public static readonly string[] ExeFsPrefixes = + { + "rtld", + "main", + "subsdk0", + "subsdk1", + "subsdk2", + "subsdk3", + "subsdk4", + "subsdk5", + "subsdk6", + "subsdk7", + "subsdk8", + "subsdk9", + "sdk", + }; + + public const string MainNpdmPath = "/main.npdm"; + + public const int NroAsetMagic = ('A' << 0) | ('S' << 8) | ('E' << 16) | ('T' << 24); + + public const bool AslrEnabled = true; + + public const int NsoArgsHeaderSize = 8; + public const int NsoArgsDataSize = 0x9000; + public const int NsoArgsTotalSize = NsoArgsHeaderSize + NsoArgsDataSize; + } +} diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs new file mode 100644 index 00000000..12d9c8bd --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs @@ -0,0 +1,246 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ns; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Loaders.Processes.Extensions; +using System; +using System.Collections.Concurrent; +using System.IO; +using Path = System.IO.Path; + +namespace Ryujinx.HLE.Loaders.Processes +{ + public class ProcessLoader + { + private readonly Switch _device; + + private readonly ConcurrentDictionary _processesByPid; + + private ulong _latestPid; + + public ProcessResult ActiveApplication => _processesByPid[_latestPid]; + + public ProcessLoader(Switch device) + { + _device = device; + _processesByPid = new ConcurrentDictionary(); + } + + public bool LoadXci(string path, ulong applicationId) + { + FileStream stream = new(path, FileMode.Open, FileAccess.Read); + Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage()); + + if (!xci.HasPartition(XciPartitionType.Secure)) + { + Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI Secure partition"); + + return false; + } + + (bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, applicationId, out string errorMessage); + + if (!success) + { + Logger.Error?.Print(LogClass.Loader, errorMessage, nameof(PartitionFileSystemExtensions.TryLoad)); + + return false; + } + + if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) + { + if (processResult.Start(_device)) + { + _latestPid = processResult.ProcessId; + + return true; + } + } + + return false; + } + + public bool LoadNsp(string path, ulong applicationId) + { + FileStream file = new(path, FileMode.Open, FileAccess.Read); + PartitionFileSystem partitionFileSystem = new(); + partitionFileSystem.Initialize(file.AsStorage()).ThrowIfFailure(); + + (bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, applicationId, out string errorMessage); + + if (processResult.ProcessId == 0) + { + // This is not a normal NSP, it's actually a ExeFS as a NSP + processResult = partitionFileSystem.Load(_device, new BlitStruct(1), partitionFileSystem.GetNpdm(), 0, true); + } + + if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) + { + if (processResult.Start(_device)) + { + _latestPid = processResult.ProcessId; + + return true; + } + } + + if (!success) + { + Logger.Error?.Print(LogClass.Loader, errorMessage, nameof(PartitionFileSystemExtensions.TryLoad)); + } + + return false; + } + + public bool LoadNca(string path) + { + FileStream file = new(path, FileMode.Open, FileAccess.Read); + Nca nca = new(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false)); + + ProcessResult processResult = nca.Load(_device, null, null); + + if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) + { + if (processResult.Start(_device)) + { + // NOTE: Check if process is SystemApplicationId or ApplicationId + if (processResult.ProgramId > 0x01000000000007FF) + { + _latestPid = processResult.ProcessId; + } + + return true; + } + } + + return false; + } + + public bool LoadUnpackedNca(string exeFsDirPath, string romFsPath = null) + { + ProcessResult processResult = new LocalFileSystem(exeFsDirPath).Load(_device, romFsPath); + + if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) + { + if (processResult.Start(_device)) + { + _latestPid = processResult.ProcessId; + + return true; + } + } + + return false; + } + + public bool LoadNxo(string path) + { + var nacpData = new BlitStruct(1); + IFileSystem dummyExeFs = null; + Stream romfsStream = null; + + string programName = ""; + ulong programId = 0000000000000000; + + // Load executable. + IExecutable executable; + + if (Path.GetExtension(path).ToLower() == ".nro") + { + FileStream input = new(path, FileMode.Open); + NroExecutable nro = new(input.AsStorage()); + + executable = nro; + + // Open RomFS if exists. + IStorage romFsStorage = nro.OpenNroAssetSection(LibHac.Tools.Ro.NroAssetType.RomFs, false); + romFsStorage.GetSize(out long romFsSize).ThrowIfFailure(); + if (romFsSize != 0) + { + romfsStream = romFsStorage.AsStream(); + } + + // Load Nacp if exists. + IStorage nacpStorage = nro.OpenNroAssetSection(LibHac.Tools.Ro.NroAssetType.Nacp, false); + nacpStorage.GetSize(out long nacpSize).ThrowIfFailure(); + if (nacpSize != 0) + { + nacpStorage.Read(0, nacpData.ByteSpan); + + programName = nacpData.Value.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString(); + + if (string.IsNullOrWhiteSpace(programName)) + { + programName = Array.Find(nacpData.Value.Title.ItemsRo.ToArray(), x => x.Name[0] != 0).NameString.ToString(); + } + + if (nacpData.Value.PresenceGroupId != 0) + { + programId = nacpData.Value.PresenceGroupId; + } + else if (nacpData.Value.SaveDataOwnerId != 0) + { + programId = nacpData.Value.SaveDataOwnerId; + } + else if (nacpData.Value.AddOnContentBaseId != 0) + { + programId = nacpData.Value.AddOnContentBaseId - 0x1000; + } + } + + // TODO: Add icon maybe ? + } + else + { + programName = Path.GetFileNameWithoutExtension(path); + + executable = new NsoExecutable(new LocalStorage(path, FileAccess.Read), programName); + } + + // Explicitly null TitleId to disable the shader cache. + Graphics.Gpu.GraphicsConfig.TitleId = null; + _device.Gpu.HostInitalized.Set(); + + ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device, + _device.System.KernelContext, + dummyExeFs.GetNpdm(), + nacpData, + diskCacheEnabled: false, + allowCodeMemoryForJit: true, + programName, + programId, + 0, + null, + executable); + + // Make sure the process id is valid. + if (processResult.ProcessId != 0) + { + // Load RomFS. + if (romfsStream != null) + { + _device.Configuration.VirtualFileSystem.SetRomFs(processResult.ProcessId, romfsStream); + } + + // Start process. + if (_processesByPid.TryAdd(processResult.ProcessId, processResult)) + { + if (processResult.Start(_device)) + { + _latestPid = processResult.ProcessId; + + return true; + } + } + } + + return false; + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs new file mode 100644 index 00000000..cf4eb416 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs @@ -0,0 +1,503 @@ +using LibHac.Account; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Fs.Shim; +using LibHac.Loader; +using LibHac.Ncm; +using LibHac.Ns; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Loaders.Processes.Extensions; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using System; +using System.Linq; +using System.Runtime.InteropServices; +using ApplicationId = LibHac.Ncm.ApplicationId; + +namespace Ryujinx.HLE.Loaders.Processes +{ + static class ProcessLoaderHelper + { + // NOTE: If you want to change this value make sure to increment the InternalVersion of Ptc and PtcProfiler. + // You also need to add a new migration path and adjust the existing ones. + // TODO: Remove this workaround when ASLR is implemented. + private const ulong CodeStartOffset = 0x500000UL; + + public static LibHac.Result RegisterProgramMapInfo(Switch device, IFileSystem partitionFileSystem) + { + ulong applicationId = 0; + int programCount = 0; + + Span hasIndex = stackalloc bool[0x10]; + + foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca")) + { + Nca nca = partitionFileSystem.GetNca(device.FileSystem.KeySet, fileEntry.FullPath); + + if (!nca.IsProgram()) + { + continue; + } + + ulong currentMainProgramId = nca.GetProgramIdBase(); + + if (applicationId == 0 && currentMainProgramId != 0) + { + applicationId = currentMainProgramId; + } + + if (applicationId != currentMainProgramId) + { + // Currently there aren't any known multi-application game cards containing multi-program applications, + // so because multi-application game cards are the only way we could run into multiple applications + // we'll just return that there's a single program. + programCount = 1; + + break; + } + + hasIndex[nca.GetProgramIndex()] = true; + } + + if (programCount == 0) + { + for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++) + { + programCount++; + } + } + + if (programCount <= 0) + { + return LibHac.Result.Success; + } + + Span mapInfo = stackalloc ProgramIndexMapInfo[0x10]; + + for (int i = 0; i < programCount; i++) + { + mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i); + mapInfo[i].MainProgramId = new ApplicationId(applicationId); + mapInfo[i].ProgramIndex = (byte)i; + } + + return device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo[..programCount]); + } + + public static LibHac.Result EnsureSaveData(Switch device, ApplicationId applicationId, BlitStruct applicationControlProperty) + { + Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists."); + + ref ApplicationControlProperty control = ref applicationControlProperty.Value; + + if (LibHac.Common.Utilities.IsZeros(applicationControlProperty.ByteSpan)) + { + // If the current application doesn't have a loaded control property, create a dummy one and set the savedata sizes so a user savedata will be created. + control = ref new BlitStruct(1).Value; + + // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. + control.UserAccountSaveDataSize = 0x4000; + control.UserAccountSaveDataJournalSize = 0x4000; + control.SaveDataOwnerId = applicationId.Value; + + Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); + } + + LibHac.Result resultCode = device.System.LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control); + if (resultCode.IsFailure()) + { + Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}"); + + return resultCode; + } + + Uid userId = device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid(); + + resultCode = device.System.LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in userId); + if (resultCode.IsFailure()) + { + Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}"); + } + + return resultCode; + } + + public static bool LoadKip(KernelContext context, KipExecutable kip) + { + uint endOffset = kip.DataOffset + (uint)kip.Data.Length; + + if (kip.BssSize != 0) + { + endOffset = kip.BssOffset + kip.BssSize; + } + + uint codeSize = BitUtils.AlignUp(kip.TextOffset + endOffset, KPageTableBase.PageSize); + int codePagesCount = (int)(codeSize / KPageTableBase.PageSize); + ulong codeBaseAddress = kip.Is64BitAddressSpace ? 0x8000000UL : 0x200000UL; + ulong codeAddress = codeBaseAddress + kip.TextOffset; + + ProcessCreationFlags flags = 0; + + if (ProcessConst.AslrEnabled) + { + // TODO: Randomization. + + flags |= ProcessCreationFlags.EnableAslr; + } + + if (kip.Is64BitAddressSpace) + { + flags |= ProcessCreationFlags.AddressSpace64Bit; + } + + if (kip.Is64Bit) + { + flags |= ProcessCreationFlags.Is64Bit; + } + + ProcessCreationInfo creationInfo = new(kip.Name, kip.Version, kip.ProgramId, codeAddress, codePagesCount, flags, 0, 0); + MemoryRegion memoryRegion = kip.UsesSecureMemory ? MemoryRegion.Service : MemoryRegion.Application; + KMemoryRegionManager region = context.MemoryManager.MemoryRegions[(int)memoryRegion]; + + Result result = region.AllocatePages(out KPageList pageList, (ulong)codePagesCount); + if (result != Result.Success) + { + Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); + + return false; + } + + KProcess process = new(context); + + var processContextFactory = new ArmProcessContextFactory( + context.Device.System.TickSource, + context.Device.Gpu, + string.Empty, + string.Empty, + false, + codeAddress, + codeSize); + + result = process.InitializeKip(creationInfo, kip.Capabilities, pageList, context.ResourceLimit, memoryRegion, processContextFactory); + if (result != Result.Success) + { + Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); + + return false; + } + + result = LoadIntoMemory(process, kip, codeBaseAddress); + if (result != Result.Success) + { + Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); + + return false; + } + + process.DefaultCpuCore = kip.IdealCoreId; + + result = process.Start(kip.Priority, (ulong)kip.StackSize); + if (result != Result.Success) + { + Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\"."); + + return false; + } + + context.Processes.TryAdd(process.Pid, process); + + return true; + } + + public static ProcessResult LoadNsos( + Switch device, + KernelContext context, + MetaLoader metaLoader, + BlitStruct applicationControlProperties, + bool diskCacheEnabled, + bool allowCodeMemoryForJit, + string name, + ulong programId, + byte programIndex, + byte[] arguments = null, + params IExecutable[] executables) + { + context.Device.System.ServiceTable.WaitServicesReady(); + + LibHac.Result resultCode = metaLoader.GetNpdm(out var npdm); + + if (resultCode.IsFailure()) + { + Logger.Error?.Print(LogClass.Loader, $"Process initialization failed getting npdm. Result Code {resultCode.ToStringWithName()}"); + + return ProcessResult.Failed; + } + + ref readonly var meta = ref npdm.Meta; + + ulong argsStart = 0; + uint argsSize = 0; + ulong codeStart = ((meta.Flags & 1) != 0 ? 0x8000000UL : 0x200000UL) + CodeStartOffset; + uint codeSize = 0; + + var buildIds = executables.Select(e => (e switch + { + NsoExecutable nso => Convert.ToHexString(nso.BuildId.ItemsRo.ToArray()), + NroExecutable nro => Convert.ToHexString(nro.Header.BuildId), + _ => "", + }).ToUpper()); + + ulong[] nsoBase = new ulong[executables.Length]; + + for (int index = 0; index < executables.Length; index++) + { + IExecutable nso = executables[index]; + + uint textEnd = nso.TextOffset + (uint)nso.Text.Length; + uint roEnd = nso.RoOffset + (uint)nso.Ro.Length; + uint dataEnd = nso.DataOffset + (uint)nso.Data.Length + nso.BssSize; + + uint nsoSize = textEnd; + + if (nsoSize < roEnd) + { + nsoSize = roEnd; + } + + if (nsoSize < dataEnd) + { + nsoSize = dataEnd; + } + + nsoSize = BitUtils.AlignUp(nsoSize, KPageTableBase.PageSize); + + nsoBase[index] = codeStart + codeSize; + + codeSize += nsoSize; + + if (arguments != null && argsSize == 0) + { + argsStart = codeSize; + + argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ProcessConst.NsoArgsTotalSize - 1, KPageTableBase.PageSize); + + codeSize += argsSize; + } + } + + int codePagesCount = (int)(codeSize / KPageTableBase.PageSize); + int personalMmHeapPagesCount = (int)(meta.SystemResourceSize / KPageTableBase.PageSize); + + ProcessCreationInfo creationInfo = new( + name, + (int)meta.Version, + programId, + codeStart, + codePagesCount, + (ProcessCreationFlags)meta.Flags | ProcessCreationFlags.IsApplication, + 0, + personalMmHeapPagesCount); + + context.Device.System.LibHacHorizonManager.InitializeApplicationClient(new ProgramId(programId), in npdm); + + Result result; + + KResourceLimit resourceLimit = new(context); + + long applicationRgSize = (long)context.MemoryManager.MemoryRegions[(int)MemoryRegion.Application].Size; + + result = resourceLimit.SetLimitValue(LimitableResource.Memory, applicationRgSize); + + if (result.IsSuccess) + { + result = resourceLimit.SetLimitValue(LimitableResource.Thread, 608); + } + + if (result.IsSuccess) + { + result = resourceLimit.SetLimitValue(LimitableResource.Event, 700); + } + + if (result.IsSuccess) + { + result = resourceLimit.SetLimitValue(LimitableResource.TransferMemory, 128); + } + + if (result.IsSuccess) + { + result = resourceLimit.SetLimitValue(LimitableResource.Session, 894); + } + + if (result != Result.Success) + { + Logger.Error?.Print(LogClass.Loader, "Process initialization failed setting resource limit values."); + + return ProcessResult.Failed; + } + + KProcess process = new(context, allowCodeMemoryForJit); + + // NOTE: This field doesn't exists one firmware pre-5.0.0, a workaround have to be found. + MemoryRegion memoryRegion = (MemoryRegion)(npdm.Acid.Flags >> 2 & 0xf); + if (memoryRegion > MemoryRegion.NvServices) + { + Logger.Error?.Print(LogClass.Loader, "Process initialization failed due to invalid ACID flags."); + + return ProcessResult.Failed; + } + + string displayVersion; + + if (metaLoader.GetProgramId() > 0x0100000000007FFF) + { + displayVersion = applicationControlProperties.Value.DisplayVersionString.ToString(); + } + else + { + displayVersion = device.System.ContentManager.GetCurrentFirmwareVersion()?.VersionString ?? string.Empty; + } + + var processContextFactory = new ArmProcessContextFactory( + context.Device.System.TickSource, + context.Device.Gpu, + $"{programId:x16}", + displayVersion, + diskCacheEnabled, + codeStart, + codeSize); + + result = process.Initialize( + creationInfo, + MemoryMarshal.Cast(npdm.KernelCapabilityData), + resourceLimit, + memoryRegion, + processContextFactory); + + if (result != Result.Success) + { + Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); + + return ProcessResult.Failed; + } + + for (int index = 0; index < executables.Length; index++) + { + Logger.Info?.Print(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}..."); + + result = LoadIntoMemory(process, executables[index], nsoBase[index]); + if (result != Result.Success) + { + Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); + + return ProcessResult.Failed; + } + } + + process.DefaultCpuCore = meta.DefaultCpuId; + + context.Processes.TryAdd(process.Pid, process); + + // Keep the build ids because the tamper machine uses them to know which process to associate a + // tamper to and also keep the starting address of each executable inside a process because some + // memory modifications are relative to this address. + ProcessTamperInfo tamperInfo = new( + process, + buildIds, + nsoBase, + process.MemoryManager.HeapRegionStart, + process.MemoryManager.AliasRegionStart, + process.MemoryManager.CodeRegionStart); + + // Once everything is loaded, we can load cheats. + device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(programId, tamperInfo, device.TamperMachine); + + ProcessResult processResult = new( + metaLoader, + applicationControlProperties, + diskCacheEnabled, + allowCodeMemoryForJit, + processContextFactory.DiskCacheLoadState, + process.Pid, + meta.MainThreadPriority, + meta.MainThreadStackSize, + device.System.State.DesiredTitleLanguage); + + // Register everything in arp service. + device.System.ServiceTable.ArpWriter.AcquireRegistrar(out IRegistrar registrar); + registrar.SetApplicationControlProperty(MemoryMarshal.Cast(applicationControlProperties.ByteSpan)[0]); + // TODO: Handle Version and StorageId when it will be needed. + registrar.SetApplicationLaunchProperty(new ApplicationLaunchProperty() + { + ApplicationId = new Horizon.Sdk.Ncm.ApplicationId(programId), + Version = 0x00, + Storage = Horizon.Sdk.Ncm.StorageId.BuiltInUser, + PatchStorage = Horizon.Sdk.Ncm.StorageId.None, + ApplicationKind = ApplicationKind.Application, + }); + + device.System.ServiceTable.ArpReader.GetApplicationInstanceId(out ulong applicationInstanceId, process.Pid); + device.System.ServiceTable.ArpWriter.AcquireApplicationProcessPropertyUpdater(out IUpdater updater, applicationInstanceId); + updater.SetApplicationProcessProperty(process.Pid, new ApplicationProcessProperty() { ProgramIndex = programIndex }); + + return processResult; + } + + public static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress) + { + ulong textStart = baseAddress + image.TextOffset; + ulong roStart = baseAddress + image.RoOffset; + ulong dataStart = baseAddress + image.DataOffset; + ulong bssStart = baseAddress + image.BssOffset; + + ulong end = dataStart + (ulong)image.Data.Length; + + if (image.BssSize != 0) + { + end = bssStart + image.BssSize; + } + + process.CpuMemory.Write(textStart, image.Text); + process.CpuMemory.Write(roStart, image.Ro); + process.CpuMemory.Write(dataStart, image.Data); + + process.CpuMemory.Fill(bssStart, image.BssSize, 0); + + Result SetProcessMemoryPermission(ulong address, ulong size, KMemoryPermission permission) + { + if (size == 0) + { + return Result.Success; + } + + size = BitUtils.AlignUp(size, KPageTableBase.PageSize); + + return process.MemoryManager.SetProcessMemoryPermission(address, size, permission); + } + + Result result = SetProcessMemoryPermission(textStart, (ulong)image.Text.Length, KMemoryPermission.ReadAndExecute); + if (result != Result.Success) + { + return result; + } + + result = SetProcessMemoryPermission(roStart, (ulong)image.Ro.Length, KMemoryPermission.Read); + if (result != Result.Success) + { + return result; + } + + return SetProcessMemoryPermission(dataStart, end - dataStart, KMemoryPermission.ReadAndWrite); + } + } +} diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs new file mode 100644 index 00000000..1804d045 --- /dev/null +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs @@ -0,0 +1,95 @@ +using LibHac.Common; +using LibHac.Loader; +using LibHac.Ns; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.HLE.Loaders.Processes.Extensions; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.Loaders.Processes +{ + public class ProcessResult + { + public static ProcessResult Failed => new(null, new BlitStruct(1), false, false, null, 0, 0, 0, TitleLanguage.AmericanEnglish); + + private readonly byte _mainThreadPriority; + private readonly uint _mainThreadStackSize; + + public readonly IDiskCacheLoadState DiskCacheLoadState; + + public readonly MetaLoader MetaLoader; + public readonly ApplicationControlProperty ApplicationControlProperties; + + public readonly ulong ProcessId; + public readonly string Name; + public readonly string DisplayVersion; + public readonly ulong ProgramId; + public readonly string ProgramIdText; + public readonly bool Is64Bit; + public readonly bool DiskCacheEnabled; + public readonly bool AllowCodeMemoryForJit; + + public ProcessResult( + MetaLoader metaLoader, + BlitStruct applicationControlProperties, + bool diskCacheEnabled, + bool allowCodeMemoryForJit, + IDiskCacheLoadState diskCacheLoadState, + ulong pid, + byte mainThreadPriority, + uint mainThreadStackSize, + TitleLanguage titleLanguage) + { + _mainThreadPriority = mainThreadPriority; + _mainThreadStackSize = mainThreadStackSize; + + DiskCacheLoadState = diskCacheLoadState; + ProcessId = pid; + + MetaLoader = metaLoader; + ApplicationControlProperties = applicationControlProperties.Value; + + if (metaLoader is not null) + { + ulong programId = metaLoader.GetProgramId(); + + Name = ApplicationControlProperties.Title[(int)titleLanguage].NameString.ToString(); + + if (string.IsNullOrWhiteSpace(Name)) + { + Name = Array.Find(ApplicationControlProperties.Title.ItemsRo.ToArray(), x => x.Name[0] != 0).NameString.ToString(); + } + + DisplayVersion = ApplicationControlProperties.DisplayVersionString.ToString(); + ProgramId = programId; + ProgramIdText = $"{programId:x16}"; + Is64Bit = metaLoader.IsProgram64Bit(); + } + + DiskCacheEnabled = diskCacheEnabled; + AllowCodeMemoryForJit = allowCodeMemoryForJit; + } + + public bool Start(Switch device) + { + device.Configuration.ContentManager.LoadEntries(device); + + Result result = device.System.KernelContext.Processes[ProcessId].Start(_mainThreadPriority, _mainThreadStackSize); + if (result != Result.Success) + { + Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\"."); + + return false; + } + + // TODO: LibHac npdm currently doesn't support version field. + string version = ProgramId > 0x0100000000007FFF ? DisplayVersion : device.System.ContentManager.GetCurrentFirmwareVersion()?.VersionString ?? "?"; + + Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {Name} v{version} [{ProgramIdText}] [{(Is64Bit ? "64-bit" : "32-bit")}]"); + + return true; + } + } +} diff --git a/src/Ryujinx.HLE/MemoryConfiguration.cs b/src/Ryujinx.HLE/MemoryConfiguration.cs new file mode 100644 index 00000000..45e8927d --- /dev/null +++ b/src/Ryujinx.HLE/MemoryConfiguration.cs @@ -0,0 +1,64 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using System; + +namespace Ryujinx.HLE +{ + public enum MemoryConfiguration + { + MemoryConfiguration4GiB = 0, + MemoryConfiguration4GiBAppletDev = 1, + MemoryConfiguration4GiBSystemDev = 2, + MemoryConfiguration6GiB = 3, + MemoryConfiguration6GiBAppletDev = 4, + MemoryConfiguration8GiB = 5, + } + + static class MemoryConfigurationExtensions + { + private const ulong GiB = 1024 * 1024 * 1024; + +#pragma warning disable IDE0055 // Disable formatting + public static MemoryArrange ToKernelMemoryArrange(this MemoryConfiguration configuration) + { + return configuration switch + { + MemoryConfiguration.MemoryConfiguration4GiB => MemoryArrange.MemoryArrange4GiB, + MemoryConfiguration.MemoryConfiguration4GiBAppletDev => MemoryArrange.MemoryArrange4GiBAppletDev, + MemoryConfiguration.MemoryConfiguration4GiBSystemDev => MemoryArrange.MemoryArrange4GiBSystemDev, + MemoryConfiguration.MemoryConfiguration6GiB => MemoryArrange.MemoryArrange6GiB, + MemoryConfiguration.MemoryConfiguration6GiBAppletDev => MemoryArrange.MemoryArrange6GiBAppletDev, + MemoryConfiguration.MemoryConfiguration8GiB => MemoryArrange.MemoryArrange8GiB, + _ => throw new AggregateException($"Invalid memory configuration \"{configuration}\"."), + }; + } + + public static MemorySize ToKernelMemorySize(this MemoryConfiguration configuration) + { + return configuration switch + { + MemoryConfiguration.MemoryConfiguration4GiB or + MemoryConfiguration.MemoryConfiguration4GiBAppletDev or + MemoryConfiguration.MemoryConfiguration4GiBSystemDev => MemorySize.MemorySize4GiB, + MemoryConfiguration.MemoryConfiguration6GiB or + MemoryConfiguration.MemoryConfiguration6GiBAppletDev => MemorySize.MemorySize6GiB, + MemoryConfiguration.MemoryConfiguration8GiB => MemorySize.MemorySize8GiB, + _ => throw new AggregateException($"Invalid memory configuration \"{configuration}\"."), + }; + } + + public static ulong ToDramSize(this MemoryConfiguration configuration) + { + return configuration switch + { + MemoryConfiguration.MemoryConfiguration4GiB or + MemoryConfiguration.MemoryConfiguration4GiBAppletDev or + MemoryConfiguration.MemoryConfiguration4GiBSystemDev => 4 * GiB, + MemoryConfiguration.MemoryConfiguration6GiB or + MemoryConfiguration.MemoryConfiguration6GiBAppletDev => 6 * GiB, + MemoryConfiguration.MemoryConfiguration8GiB => 8 * GiB, + _ => throw new AggregateException($"Invalid memory configuration \"{configuration}\"."), + }; + } +#pragma warning restore IDE0055 + } +} diff --git a/src/Ryujinx.HLE/PerformanceStatistics.cs b/src/Ryujinx.HLE/PerformanceStatistics.cs new file mode 100644 index 00000000..3767a7fb --- /dev/null +++ b/src/Ryujinx.HLE/PerformanceStatistics.cs @@ -0,0 +1,167 @@ +using Ryujinx.Common; +using System.Timers; + +namespace Ryujinx.HLE +{ + public class PerformanceStatistics + { + private const int FrameTypeGame = 0; + private const int PercentTypeFifo = 0; + + private readonly double[] _frameRate; + private readonly double[] _accumulatedFrameTime; + private readonly double[] _previousFrameTime; + + private readonly double[] _averagePercent; + private readonly double[] _accumulatedActiveTime; + private readonly double[] _percentLastEndTime; + private readonly double[] _percentStartTime; + + private readonly long[] _framesRendered; + private readonly double[] _percentTime; + + private readonly object[] _frameLock; + private readonly object[] _percentLock; + + private readonly double _ticksToSeconds; + + private readonly Timer _resetTimer; + + public PerformanceStatistics() + { + _frameRate = new double[1]; + _accumulatedFrameTime = new double[1]; + _previousFrameTime = new double[1]; + + _averagePercent = new double[1]; + _accumulatedActiveTime = new double[1]; + _percentLastEndTime = new double[1]; + _percentStartTime = new double[1]; + + _framesRendered = new long[1]; + _percentTime = new double[1]; + + _frameLock = new[] { new object() }; + _percentLock = new[] { new object() }; + + _resetTimer = new Timer(750); + + _resetTimer.Elapsed += ResetTimerElapsed; + _resetTimer.AutoReset = true; + + _resetTimer.Start(); + + _ticksToSeconds = 1.0 / PerformanceCounter.TicksPerSecond; + } + + private void ResetTimerElapsed(object sender, ElapsedEventArgs e) + { + CalculateFrameRate(FrameTypeGame); + CalculateAveragePercent(PercentTypeFifo); + } + + private void CalculateFrameRate(int frameType) + { + double frameRate = 0; + + lock (_frameLock[frameType]) + { + if (_accumulatedFrameTime[frameType] > 0) + { + frameRate = _framesRendered[frameType] / _accumulatedFrameTime[frameType]; + } + + _frameRate[frameType] = frameRate; + _framesRendered[frameType] = 0; + _accumulatedFrameTime[frameType] = 0; + } + } + + private void CalculateAveragePercent(int percentType) + { + // If start time is non-zero, a percent reading is still being measured. + // If there aren't any readings, the default should be 100% if still being measured, or 0% if not. + double percent = (_percentStartTime[percentType] == 0) ? 0 : 100; + + lock (_percentLock[percentType]) + { + if (_percentTime[percentType] > 0) + { + percent = (_accumulatedActiveTime[percentType] / _percentTime[percentType]) * 100; + } + + _averagePercent[percentType] = percent; + _percentTime[percentType] = 0; + _accumulatedActiveTime[percentType] = 0; + } + } + + public void RecordGameFrameTime() + { + RecordFrameTime(FrameTypeGame); + } + + public void RecordFifoStart() + { + StartPercentTime(PercentTypeFifo); + } + + public void RecordFifoEnd() + { + EndPercentTime(PercentTypeFifo); + } + + private void StartPercentTime(int percentType) + { + double currentTime = PerformanceCounter.ElapsedTicks * _ticksToSeconds; + + _percentStartTime[percentType] = currentTime; + } + + private void EndPercentTime(int percentType) + { + double currentTime = PerformanceCounter.ElapsedTicks * _ticksToSeconds; + double elapsedTime = currentTime - _percentLastEndTime[percentType]; + double elapsedActiveTime = currentTime - _percentStartTime[percentType]; + + lock (_percentLock[percentType]) + { + _accumulatedActiveTime[percentType] += elapsedActiveTime; + _percentTime[percentType] += elapsedTime; + } + + _percentLastEndTime[percentType] = currentTime; + _percentStartTime[percentType] = 0; + } + + private void RecordFrameTime(int frameType) + { + double currentFrameTime = PerformanceCounter.ElapsedTicks * _ticksToSeconds; + double elapsedFrameTime = currentFrameTime - _previousFrameTime[frameType]; + + _previousFrameTime[frameType] = currentFrameTime; + + lock (_frameLock[frameType]) + { + _accumulatedFrameTime[frameType] += elapsedFrameTime; + + _framesRendered[frameType]++; + } + } + + public double GetGameFrameRate() + { + return _frameRate[FrameTypeGame]; + } + + public double GetFifoPercent() + { + return _averagePercent[PercentTypeFifo]; + } + + public double GetGameFrameTime() + { + return 1000 / _frameRate[FrameTypeGame]; + } + } +} diff --git a/src/Ryujinx.HLE/Ryujinx.HLE.csproj b/src/Ryujinx.HLE/Ryujinx.HLE.csproj new file mode 100644 index 00000000..a7bb3cd7 --- /dev/null +++ b/src/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -0,0 +1,52 @@ + + + + net8.0 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs new file mode 100644 index 00000000..9dfc6989 --- /dev/null +++ b/src/Ryujinx.HLE/Switch.cs @@ -0,0 +1,160 @@ +using Ryujinx.Audio.Backends.CompatLayer; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Configuration; +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Apm; +using Ryujinx.HLE.HOS.Services.Hid; +using Ryujinx.HLE.Loaders.Processes; +using Ryujinx.HLE.UI; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.HLE +{ + public class Switch : IDisposable + { + public HLEConfiguration Configuration { get; } + public IHardwareDeviceDriver AudioDeviceDriver { get; } + public MemoryBlock Memory { get; } + public GpuContext Gpu { get; } + public VirtualFileSystem FileSystem { get; } + public HOS.Horizon System { get; } + public ProcessLoader Processes { get; } + public PerformanceStatistics Statistics { get; } + public Hid Hid { get; } + public TamperMachine TamperMachine { get; } + public IHostUIHandler UIHandler { get; } + + public bool EnableDeviceVsync { get; set; } = true; + + public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable; + + public Switch(HLEConfiguration configuration) + { + ArgumentNullException.ThrowIfNull(configuration.GpuRenderer); + ArgumentNullException.ThrowIfNull(configuration.AudioDeviceDriver); + ArgumentNullException.ThrowIfNull(configuration.UserChannelPersistence); + + Configuration = configuration; + FileSystem = Configuration.VirtualFileSystem; + UIHandler = Configuration.HostUIHandler; + + MemoryAllocationFlags memoryAllocationFlags = configuration.MemoryManagerMode == MemoryManagerMode.SoftwarePageTable + ? MemoryAllocationFlags.Reserve + : MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Mirrorable; + +#pragma warning disable IDE0055 // Disable formatting + AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver); + Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags); + Gpu = new GpuContext(Configuration.GpuRenderer); + System = new HOS.Horizon(this); + Statistics = new PerformanceStatistics(); + Hid = new Hid(this, System.HidStorage); + Processes = new ProcessLoader(this); + TamperMachine = new TamperMachine(); + + System.InitializeServices(); + System.State.SetLanguage(Configuration.SystemLanguage); + System.State.SetRegion(Configuration.Region); + + EnableDeviceVsync = Configuration.EnableVsync; + System.State.DockedMode = Configuration.EnableDockedMode; + System.PerformanceState.PerformanceMode = System.State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default; + System.EnablePtc = Configuration.EnablePtc; + System.FsIntegrityCheckLevel = Configuration.FsIntegrityCheckLevel; + System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode; +#pragma warning restore IDE0055 + } + + public bool LoadCart(string exeFsDir, string romFsFile = null) + { + return Processes.LoadUnpackedNca(exeFsDir, romFsFile); + } + + public bool LoadXci(string xciFile, ulong applicationId = 0) + { + return Processes.LoadXci(xciFile, applicationId); + } + + public bool LoadNca(string ncaFile) + { + return Processes.LoadNca(ncaFile); + } + + public bool LoadNsp(string nspFile, ulong applicationId = 0) + { + return Processes.LoadNsp(nspFile, applicationId); + } + + public bool LoadProgram(string fileName) + { + return Processes.LoadNxo(fileName); + } + + public bool WaitFifo() + { + return Gpu.GPFifo.WaitForCommands(); + } + + public void ProcessFrame() + { + Gpu.ProcessShaderCacheQueue(); + Gpu.Renderer.PreFrame(); + Gpu.GPFifo.DispatchCalls(); + } + + public bool ConsumeFrameAvailable() + { + return Gpu.Window.ConsumeFrameAvailable(); + } + + public void PresentFrame(Action swapBuffersCallback) + { + Gpu.Window.Present(swapBuffersCallback); + } + + public void SetVolume(float volume) + { + AudioDeviceDriver.Volume = Math.Clamp(volume, 0f, 1f); + } + + public float GetVolume() + { + return AudioDeviceDriver.Volume; + } + + public void EnableCheats() + { + ModLoader.EnableCheats(Processes.ActiveApplication.ProgramId, TamperMachine); + } + + public bool IsAudioMuted() + { + return AudioDeviceDriver.Volume == 0; + } + + public void DisposeGpu() + { + Gpu.Dispose(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + System.Dispose(); + AudioDeviceDriver.Dispose(); + FileSystem.Dispose(); + Memory.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.HLE/UI/DynamicTextChangedHandler.cs b/src/Ryujinx.HLE/UI/DynamicTextChangedHandler.cs new file mode 100644 index 00000000..c0945259 --- /dev/null +++ b/src/Ryujinx.HLE/UI/DynamicTextChangedHandler.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.HLE.UI +{ + public delegate void DynamicTextChangedHandler(string text, int cursorBegin, int cursorEnd, bool overwriteMode); +} diff --git a/src/Ryujinx.HLE/UI/IDynamicTextInputHandler.cs b/src/Ryujinx.HLE/UI/IDynamicTextInputHandler.cs new file mode 100644 index 00000000..1ff451d1 --- /dev/null +++ b/src/Ryujinx.HLE/UI/IDynamicTextInputHandler.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.HLE.UI +{ + public interface IDynamicTextInputHandler : IDisposable + { + event DynamicTextChangedHandler TextChangedEvent; + event KeyPressedHandler KeyPressedEvent; + event KeyReleasedHandler KeyReleasedEvent; + + bool TextProcessingEnabled { get; set; } + + void SetText(string text, int cursorBegin); + void SetText(string text, int cursorBegin, int cursorEnd); + } +} diff --git a/src/Ryujinx.HLE/UI/IHostUIHandler.cs b/src/Ryujinx.HLE/UI/IHostUIHandler.cs new file mode 100644 index 00000000..3b3a430e --- /dev/null +++ b/src/Ryujinx.HLE/UI/IHostUIHandler.cs @@ -0,0 +1,51 @@ +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; + +namespace Ryujinx.HLE.UI +{ + public interface IHostUIHandler + { + /// + /// Displays an Input Dialog box to the user and blocks until text is entered. + /// + /// Text that the user entered. Set to `null` on internal errors + /// True when OK is pressed, False otherwise. Also returns True on internal errors + bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText); + + /// + /// Displays a Message Dialog box to the user and blocks until it is closed. + /// + /// True when OK is pressed, False otherwise. + bool DisplayMessageDialog(string title, string message); + + /// + /// Displays a Message Dialog box specific to Controller Applet and blocks until it is closed. + /// + /// True when OK is pressed, False otherwise. + bool DisplayMessageDialog(ControllerAppletUIArgs args); + + /// + /// Tell the UI that we need to transisition to another program. + /// + /// The device instance. + /// The program kind. + /// The value associated to the . + void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value); + + /// Displays a Message Dialog box specific to Error Applet and blocks until it is closed. + /// + /// False when OK is pressed, True when another button (Details) is pressed. + bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText); + + /// + /// Creates a handler to process keyboard inputs into text strings. + /// + /// An instance of the text handler. + IDynamicTextInputHandler CreateDynamicTextInputHandler(); + + /// + /// Gets fonts and colors used by the host. + /// + IHostUITheme HostUITheme { get; } + } +} diff --git a/src/Ryujinx.HLE/UI/IHostUITheme.cs b/src/Ryujinx.HLE/UI/IHostUITheme.cs new file mode 100644 index 00000000..3b054400 --- /dev/null +++ b/src/Ryujinx.HLE/UI/IHostUITheme.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.UI +{ + public interface IHostUITheme + { + string FontFamily { get; } + + ThemeColor DefaultBackgroundColor { get; } + ThemeColor DefaultForegroundColor { get; } + ThemeColor DefaultBorderColor { get; } + ThemeColor SelectionBackgroundColor { get; } + ThemeColor SelectionForegroundColor { get; } + } +} diff --git a/src/Ryujinx.HLE/UI/Input/NpadButtonHandler.cs b/src/Ryujinx.HLE/UI/Input/NpadButtonHandler.cs new file mode 100644 index 00000000..73c30661 --- /dev/null +++ b/src/Ryujinx.HLE/UI/Input/NpadButtonHandler.cs @@ -0,0 +1,6 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; + +namespace Ryujinx.HLE.UI.Input +{ + delegate void NpadButtonHandler(int npadIndex, NpadButton button); +} diff --git a/src/Ryujinx.HLE/UI/Input/NpadReader.cs b/src/Ryujinx.HLE/UI/Input/NpadReader.cs new file mode 100644 index 00000000..8276d616 --- /dev/null +++ b/src/Ryujinx.HLE/UI/Input/NpadReader.cs @@ -0,0 +1,140 @@ +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; + +namespace Ryujinx.HLE.UI.Input +{ + /// + /// Class that converts Hid entries for the Npad into pressed / released events. + /// + class NpadReader + { + private readonly Switch _device; + private readonly NpadCommonState[] _lastStates; + + public event NpadButtonHandler NpadButtonUpEvent; + public event NpadButtonHandler NpadButtonDownEvent; + + public NpadReader(Switch device) + { + _device = device; + _lastStates = new NpadCommonState[_device.Hid.SharedMemory.Npads.Length]; + } + + public NpadButton GetCurrentButtonsOfNpad(int npadIndex) + { + return _lastStates[npadIndex].Buttons; + } + + public NpadButton GetCurrentButtonsOfAllNpads() + { + NpadButton buttons = 0; + + foreach (var state in _lastStates) + { + buttons |= state.Buttons; + } + + return buttons; + } + + private static ref RingLifo GetCommonStateLifo(ref NpadInternalState npad) + { + switch (npad.StyleSet) + { + case NpadStyleTag.FullKey: + return ref npad.FullKey; + case NpadStyleTag.Handheld: + return ref npad.Handheld; + case NpadStyleTag.JoyDual: + return ref npad.JoyDual; + case NpadStyleTag.JoyLeft: + return ref npad.JoyLeft; + case NpadStyleTag.JoyRight: + return ref npad.JoyRight; + case NpadStyleTag.Palma: + return ref npad.Palma; + default: + return ref npad.SystemExt; + } + } + + public void Update(bool supressEvents = false) + { + ref var npads = ref _device.Hid.SharedMemory.Npads; + + // Process each input individually. + for (int npadIndex = 0; npadIndex < npads.Length; npadIndex++) + { + UpdateNpad(npadIndex, supressEvents); + } + } + + private void UpdateNpad(int npadIndex, bool supressEvents) + { + const int MaxEntries = 1024; + + ref var npadState = ref _device.Hid.SharedMemory.Npads[npadIndex]; + ref var lastEntry = ref _lastStates[npadIndex]; + + var fullKeyEntries = GetCommonStateLifo(ref npadState.InternalState).ReadEntries(MaxEntries); + + int firstEntryNum; + + // Scan the LIFO for the first entry that is newer that what's already processed. + for (firstEntryNum = fullKeyEntries.Length - 1; + firstEntryNum >= 0 && fullKeyEntries[firstEntryNum].Object.SamplingNumber <= lastEntry.SamplingNumber; + firstEntryNum--) + { + } + + if (firstEntryNum == -1) + { + return; + } + + for (; firstEntryNum >= 0; firstEntryNum--) + { + var entry = fullKeyEntries[firstEntryNum]; + + // The interval of valid entries should be contiguous. + if (entry.SamplingNumber < lastEntry.SamplingNumber) + { + break; + } + + if (!supressEvents) + { + ProcessNpadButtons(npadIndex, entry.Object.Buttons); + } + + lastEntry = entry.Object; + } + } + + private void ProcessNpadButtons(int npadIndex, NpadButton buttons) + { + NpadButton lastButtons = _lastStates[npadIndex].Buttons; + + for (ulong buttonMask = 1; buttonMask != 0; buttonMask <<= 1) + { + NpadButton currentButton = (NpadButton)buttonMask & buttons; + NpadButton lastButton = (NpadButton)buttonMask & lastButtons; + + if (lastButton != 0) + { + if (currentButton == 0) + { + NpadButtonUpEvent?.Invoke(npadIndex, lastButton); + } + } + else + { + if (currentButton != 0) + { + NpadButtonDownEvent?.Invoke(npadIndex, currentButton); + } + } + } + } + } +} diff --git a/src/Ryujinx.HLE/UI/KeyPressedHandler.cs b/src/Ryujinx.HLE/UI/KeyPressedHandler.cs new file mode 100644 index 00000000..6feb11bd --- /dev/null +++ b/src/Ryujinx.HLE/UI/KeyPressedHandler.cs @@ -0,0 +1,6 @@ +using Ryujinx.Common.Configuration.Hid; + +namespace Ryujinx.HLE.UI +{ + public delegate bool KeyPressedHandler(Key key); +} diff --git a/src/Ryujinx.HLE/UI/KeyReleasedHandler.cs b/src/Ryujinx.HLE/UI/KeyReleasedHandler.cs new file mode 100644 index 00000000..3de89d0c --- /dev/null +++ b/src/Ryujinx.HLE/UI/KeyReleasedHandler.cs @@ -0,0 +1,6 @@ +using Ryujinx.Common.Configuration.Hid; + +namespace Ryujinx.HLE.UI +{ + public delegate bool KeyReleasedHandler(Key key); +} diff --git a/src/Ryujinx.HLE/UI/RenderingSurfaceInfo.cs b/src/Ryujinx.HLE/UI/RenderingSurfaceInfo.cs new file mode 100644 index 00000000..af0a0d44 --- /dev/null +++ b/src/Ryujinx.HLE/UI/RenderingSurfaceInfo.cs @@ -0,0 +1,45 @@ +using Ryujinx.HLE.HOS.Services.SurfaceFlinger; +using System; + +namespace Ryujinx.HLE.UI +{ + /// + /// Information about the indirect layer that is being drawn to. + /// + class RenderingSurfaceInfo : IEquatable + { + public ColorFormat ColorFormat { get; } + public uint Width { get; } + public uint Height { get; } + public uint Pitch { get; } + public uint Size { get; } + + public RenderingSurfaceInfo(ColorFormat colorFormat, uint width, uint height, uint pitch, uint size) + { + ColorFormat = colorFormat; + Width = width; + Height = height; + Pitch = pitch; + Size = size; + } + + public bool Equals(RenderingSurfaceInfo other) + { + return ColorFormat == other.ColorFormat && + Width == other.Width && + Height == other.Height && + Pitch == other.Pitch && + Size == other.Size; + } + + public override bool Equals(object obj) + { + return obj is RenderingSurfaceInfo info && Equals(info); + } + + public override int GetHashCode() + { + return BitConverter.ToInt32(BitConverter.GetBytes(((ulong)ColorFormat) ^ Width ^ Height ^ Pitch ^ Size)); + } + } +} diff --git a/src/Ryujinx.HLE/UI/ThemeColor.cs b/src/Ryujinx.HLE/UI/ThemeColor.cs new file mode 100644 index 00000000..c5cfb147 --- /dev/null +++ b/src/Ryujinx.HLE/UI/ThemeColor.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.HLE.UI +{ + public readonly struct ThemeColor + { + public float A { get; } + public float R { get; } + public float G { get; } + public float B { get; } + + public ThemeColor(float a, float r, float g, float b) + { + A = a; + R = r; + G = g; + B = b; + } + } +} diff --git a/src/Ryujinx.HLE/Utilities/PartitionFileSystemUtils.cs b/src/Ryujinx.HLE/Utilities/PartitionFileSystemUtils.cs new file mode 100644 index 00000000..3c4ce085 --- /dev/null +++ b/src/Ryujinx.HLE/Utilities/PartitionFileSystemUtils.cs @@ -0,0 +1,45 @@ +using LibHac; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using Ryujinx.HLE.FileSystem; +using System.IO; + +namespace Ryujinx.HLE.Utilities +{ + public static class PartitionFileSystemUtils + { + public static IFileSystem OpenApplicationFileSystem(string path, VirtualFileSystem fileSystem, bool throwOnFailure = true) + { + FileStream file = File.OpenRead(path); + + IFileSystem partitionFileSystem; + + if (Path.GetExtension(path).ToLower() == ".xci") + { + partitionFileSystem = new Xci(fileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure); + } + else + { + var pfsTemp = new PartitionFileSystem(); + Result initResult = pfsTemp.Initialize(file.AsStorage()); + + if (throwOnFailure) + { + initResult.ThrowIfFailure(); + } + else if (initResult.IsFailure()) + { + return null; + } + + partitionFileSystem = pfsTemp; + } + + fileSystem.ImportTickets(partitionFileSystem); + + return partitionFileSystem; + } + } +} diff --git a/src/Ryujinx.HLE/Utilities/StringUtils.cs b/src/Ryujinx.HLE/Utilities/StringUtils.cs new file mode 100644 index 00000000..55fb06e4 --- /dev/null +++ b/src/Ryujinx.HLE/Utilities/StringUtils.cs @@ -0,0 +1,156 @@ +using LibHac.Common; +using Microsoft.IO; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS; +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; + +namespace Ryujinx.HLE.Utilities +{ + static class StringUtils + { + public static byte[] GetFixedLengthBytes(string inputString, int size, Encoding encoding) + { + inputString += "\0"; + + int bytesCount = encoding.GetByteCount(inputString); + + byte[] output = new byte[size]; + + if (bytesCount < size) + { + encoding.GetBytes(inputString, 0, inputString.Length, output, 0); + } + else + { + int nullSize = encoding.GetByteCount("\0"); + + output = encoding.GetBytes(inputString); + + Array.Resize(ref output, size - nullSize); + + output = output.Concat(encoding.GetBytes("\0")).ToArray(); + } + + return output; + } + + public static string ReadInlinedAsciiString(BinaryReader reader, int maxSize) + { + byte[] data = reader.ReadBytes(maxSize); + + int stringSize = Array.IndexOf(data, 0); + + return Encoding.ASCII.GetString(data, 0, stringSize < 0 ? maxSize : stringSize); + } + + public static byte[] HexToBytes(string hexString) + { + // Ignore last character if HexLength % 2 != 0. + int bytesInHex = hexString.Length / 2; + + byte[] output = new byte[bytesInHex]; + + for (int index = 0; index < bytesInHex; index++) + { + output[index] = byte.Parse(hexString.AsSpan(index * 2, 2), NumberStyles.HexNumber); + } + + return output; + } + + public static string ReadUtf8String(ReadOnlySpan data, out int dataRead) + { + dataRead = data.IndexOf((byte)0) + 1; + + if (dataRead <= 1) + { + return string.Empty; + } + + return Encoding.UTF8.GetString(data[..dataRead]); + } + + public static string ReadUtf8String(ServiceCtx context, int index = 0) + { + ulong position = context.Request.PtrBuff[index].Position; + ulong size = context.Request.PtrBuff[index].Size; + + using RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(); + while (size-- > 0) + { + byte value = context.Memory.Read(position++); + + if (value == 0) + { + break; + } + + ms.WriteByte(value); + } + + return Encoding.UTF8.GetString(ms.GetReadOnlySequence()); + } + + public static U8Span ReadUtf8Span(ServiceCtx context, int index = 0) + { + ulong position = context.Request.PtrBuff[index].Position; + ulong size = context.Request.PtrBuff[index].Size; + + ReadOnlySpan buffer = context.Memory.GetSpan(position, (int)size); + + return new U8Span(buffer); + } + + public static string ReadUtf8StringSend(ServiceCtx context, int index = 0) + { + ulong position = context.Request.SendBuff[index].Position; + ulong size = context.Request.SendBuff[index].Size; + + using RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(); + + while (size-- > 0) + { + byte value = context.Memory.Read(position++); + + if (value == 0) + { + break; + } + + ms.WriteByte(value); + } + + return Encoding.UTF8.GetString(ms.GetReadOnlySequence()); + } + + public static int CompareCStr(ReadOnlySpan s1, ReadOnlySpan s2) + { + int s1Index = 0; + int s2Index = 0; + + while (s1[s1Index] != 0 && s2[s2Index] != 0 && s1[s1Index] == s2[s2Index]) + { + s1Index += 1; + s2Index += 1; + } + + return s2[s2Index] - s1[s1Index]; + } + + public static int LengthCstr(ReadOnlySpan s) + { + int i = 0; + + while (s[i] != 0) + { + i++; + } + + return i; + } + } +} diff --git a/src/Ryujinx.Headless.SDL2/HeadlessDynamicTextInputHandler.cs b/src/Ryujinx.Headless.SDL2/HeadlessDynamicTextInputHandler.cs new file mode 100644 index 00000000..503874ff --- /dev/null +++ b/src/Ryujinx.Headless.SDL2/HeadlessDynamicTextInputHandler.cs @@ -0,0 +1,51 @@ +using Ryujinx.HLE.UI; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.Headless.SDL2 +{ + /// + /// Headless text processing class, right now there is no way to forward the input to it. + /// + internal class HeadlessDynamicTextInputHandler : IDynamicTextInputHandler + { + private bool _canProcessInput; + + public event DynamicTextChangedHandler TextChangedEvent; + public event KeyPressedHandler KeyPressedEvent { add { } remove { } } + public event KeyReleasedHandler KeyReleasedEvent { add { } remove { } } + + public bool TextProcessingEnabled + { + get + { + return Volatile.Read(ref _canProcessInput); + } + + set + { + Volatile.Write(ref _canProcessInput, value); + + // Launch a task to update the text. + Task.Run(() => + { + Thread.Sleep(100); + TextChangedEvent?.Invoke("Ryujinx", 7, 7, false); + }); + } + } + + public HeadlessDynamicTextInputHandler() + { + // Start with input processing turned off so the text box won't accumulate text + // if the user is playing on the keyboard. + _canProcessInput = false; + } + + public void SetText(string text, int cursorBegin) { } + + public void SetText(string text, int cursorBegin, int cursorEnd) { } + + public void Dispose() { } + } +} diff --git a/src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs b/src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs new file mode 100644 index 00000000..78cd43ae --- /dev/null +++ b/src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs @@ -0,0 +1,15 @@ +using Ryujinx.HLE.UI; + +namespace Ryujinx.Headless.SDL2 +{ + internal class HeadlessHostUiTheme : IHostUITheme + { + public string FontFamily => "sans-serif"; + + public ThemeColor DefaultBackgroundColor => new(1, 0, 0, 0); + public ThemeColor DefaultForegroundColor => new(1, 1, 1, 1); + public ThemeColor DefaultBorderColor => new(1, 1, 1, 1); + public ThemeColor SelectionBackgroundColor => new(1, 1, 1, 1); + public ThemeColor SelectionForegroundColor => new(1, 0, 0, 0); + } +} diff --git a/src/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs b/src/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs new file mode 100644 index 00000000..7ea6e148 --- /dev/null +++ b/src/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs @@ -0,0 +1,202 @@ +using OpenTK; +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.OpenGL; +using Ryujinx.Input.HLE; +using System; +using static SDL2.SDL; + +namespace Ryujinx.Headless.SDL2.OpenGL +{ + class OpenGLWindow : WindowBase + { + private static void CheckResult(int result) + { + if (result < 0) + { + throw new InvalidOperationException($"SDL_GL function returned an error: {SDL_GetError()}"); + } + } + + private static void SetupOpenGLAttributes(bool sharedContext, GraphicsDebugLevel debugLevel) + { + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3)); + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 3)); + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_COMPATIBILITY)); + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_FLAGS, debugLevel != GraphicsDebugLevel.None ? (int)SDL_GLcontext.SDL_GL_CONTEXT_DEBUG_FLAG : 0)); + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, sharedContext ? 1 : 0)); + + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_ACCELERATED_VISUAL, 1)); + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_RED_SIZE, 8)); + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_GREEN_SIZE, 8)); + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_BLUE_SIZE, 8)); + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_ALPHA_SIZE, 8)); + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DEPTH_SIZE, 16)); + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_STENCIL_SIZE, 0)); + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1)); + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_STEREO, 0)); + } + + private class OpenToolkitBindingsContext : IBindingsContext + { + public IntPtr GetProcAddress(string procName) + { + return SDL_GL_GetProcAddress(procName); + } + } + + private class SDL2OpenGLContext : IOpenGLContext + { + private readonly IntPtr _context; + private readonly IntPtr _window; + private readonly bool _shouldDisposeWindow; + + public SDL2OpenGLContext(IntPtr context, IntPtr window, bool shouldDisposeWindow = true) + { + _context = context; + _window = window; + _shouldDisposeWindow = shouldDisposeWindow; + } + + public static SDL2OpenGLContext CreateBackgroundContext(SDL2OpenGLContext sharedContext) + { + sharedContext.MakeCurrent(); + + // Ensure we share our contexts. + SetupOpenGLAttributes(true, GraphicsDebugLevel.None); + IntPtr windowHandle = SDL_CreateWindow("Ryujinx background context window", 0, 0, 1, 1, SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_HIDDEN); + IntPtr context = SDL_GL_CreateContext(windowHandle); + + GL.LoadBindings(new OpenToolkitBindingsContext()); + + CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0)); + + CheckResult(SDL_GL_MakeCurrent(windowHandle, IntPtr.Zero)); + + return new SDL2OpenGLContext(context, windowHandle); + } + + public void MakeCurrent() + { + if (SDL_GL_GetCurrentContext() == _context || SDL_GL_GetCurrentWindow() == _window) + { + return; + } + + int res = SDL_GL_MakeCurrent(_window, _context); + + if (res != 0) + { + string errorMessage = $"SDL_GL_CreateContext failed with error \"{SDL_GetError()}\""; + + Logger.Error?.Print(LogClass.Application, errorMessage); + + throw new Exception(errorMessage); + } + } + + public bool HasContext() => SDL_GL_GetCurrentContext() != IntPtr.Zero; + + public void Dispose() + { + SDL_GL_DeleteContext(_context); + + if (_shouldDisposeWindow) + { + SDL_DestroyWindow(_window); + } + } + } + + private readonly GraphicsDebugLevel _glLogLevel; + private SDL2OpenGLContext _openGLContext; + + public OpenGLWindow( + InputManager inputManager, + GraphicsDebugLevel glLogLevel, + AspectRatio aspectRatio, + bool enableMouse, + HideCursorMode hideCursorMode) + : base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode) + { + _glLogLevel = glLogLevel; + } + + public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_OPENGL; + + protected override void InitializeWindowRenderer() + { + // Ensure to not share this context with other contexts before this point. + SetupOpenGLAttributes(false, _glLogLevel); + IntPtr context = SDL_GL_CreateContext(WindowHandle); + CheckResult(SDL_GL_SetSwapInterval(1)); + + if (context == IntPtr.Zero) + { + string errorMessage = $"SDL_GL_CreateContext failed with error \"{SDL_GetError()}\""; + + Logger.Error?.Print(LogClass.Application, errorMessage); + + throw new Exception(errorMessage); + } + + // NOTE: The window handle needs to be disposed by the thread that created it and is handled separately. + _openGLContext = new SDL2OpenGLContext(context, WindowHandle, false); + + // First take exclusivity on the OpenGL context. + ((OpenGLRenderer)Renderer).InitializeBackgroundContext(SDL2OpenGLContext.CreateBackgroundContext(_openGLContext)); + + _openGLContext.MakeCurrent(); + + GL.ClearColor(0, 0, 0, 1.0f); + GL.Clear(ClearBufferMask.ColorBufferBit); + SwapBuffers(); + + if (IsExclusiveFullscreen) + { + Renderer?.Window.SetSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight); + MouseDriver.SetClientSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight); + } + else if (IsFullscreen) + { + // NOTE: grabbing the main display's dimensions directly as OpenGL doesn't scale along like the VulkanWindow. + if (SDL_GetDisplayBounds(DisplayId, out SDL_Rect displayBounds) < 0) + { + Logger.Warning?.Print(LogClass.Application, $"Could not retrieve display bounds: {SDL_GetError()}"); + + // Fallback to defaults + displayBounds.w = DefaultWidth; + displayBounds.h = DefaultHeight; + } + + Renderer?.Window.SetSize(displayBounds.w, displayBounds.h); + MouseDriver.SetClientSize(displayBounds.w, displayBounds.h); + } + else + { + Renderer?.Window.SetSize(DefaultWidth, DefaultHeight); + MouseDriver.SetClientSize(DefaultWidth, DefaultHeight); + } + } + + protected override void InitializeRenderer() { } + + protected override void FinalizeWindowRenderer() + { + // Try to bind the OpenGL context before calling the gpu disposal. + _openGLContext.MakeCurrent(); + + Device.DisposeGpu(); + + // Unbind context and destroy everything + CheckResult(SDL_GL_MakeCurrent(WindowHandle, IntPtr.Zero)); + _openGLContext.Dispose(); + } + + protected override void SwapBuffers() + { + SDL_GL_SwapWindow(WindowHandle); + } + } +} diff --git a/src/Ryujinx.Headless.SDL2/Options.cs b/src/Ryujinx.Headless.SDL2/Options.cs new file mode 100644 index 00000000..ea206375 --- /dev/null +++ b/src/Ryujinx.Headless.SDL2/Options.cs @@ -0,0 +1,233 @@ +using CommandLine; +using Ryujinx.Common.Configuration; +using Ryujinx.HLE.HOS.SystemState; + +namespace Ryujinx.Headless.SDL2 +{ + public class Options + { + // General + + [Option("root-data-dir", Required = false, HelpText = "Set the custom folder path for Ryujinx data.")] + public string BaseDataDir { get; set; } + + [Option("profile", Required = false, HelpText = "Set the user profile to launch the game with.")] + public string UserProfile { get; set; } + + [Option("display-id", Required = false, Default = 0, HelpText = "Set the display to use - especially helpful for fullscreen mode. [0-n]")] + public int DisplayId { get; set; } + + [Option("fullscreen", Required = false, Default = false, HelpText = "Launch the game in fullscreen mode.")] + public bool IsFullscreen { get; set; } + + [Option("exclusive-fullscreen", Required = false, Default = false, HelpText = "Launch the game in exclusive fullscreen mode.")] + public bool IsExclusiveFullscreen { get; set; } + + [Option("exclusive-fullscreen-width", Required = false, Default = 1920, HelpText = "Set horizontal resolution for exclusive fullscreen mode.")] + public int ExclusiveFullscreenWidth { get; set; } + + [Option("exclusive-fullscreen-height", Required = false, Default = 1080, HelpText = "Set vertical resolution for exclusive fullscreen mode.")] + public int ExclusiveFullscreenHeight { get; set; } + + // Input + + [Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")] + public string InputProfile1Name { get; set; } + + [Option("input-profile-2", Required = false, HelpText = "Set the input profile in use for Player 2.")] + public string InputProfile2Name { get; set; } + + [Option("input-profile-3", Required = false, HelpText = "Set the input profile in use for Player 3.")] + public string InputProfile3Name { get; set; } + + [Option("input-profile-4", Required = false, HelpText = "Set the input profile in use for Player 4.")] + public string InputProfile4Name { get; set; } + + [Option("input-profile-5", Required = false, HelpText = "Set the input profile in use for Player 5.")] + public string InputProfile5Name { get; set; } + + [Option("input-profile-6", Required = false, HelpText = "Set the input profile in use for Player 6.")] + public string InputProfile6Name { get; set; } + + [Option("input-profile-7", Required = false, HelpText = "Set the input profile in use for Player 7.")] + public string InputProfile7Name { get; set; } + + [Option("input-profile-8", Required = false, HelpText = "Set the input profile in use for Player 8.")] + public string InputProfile8Name { get; set; } + + [Option("input-profile-handheld", Required = false, HelpText = "Set the input profile in use for the Handheld Player.")] + public string InputProfileHandheldName { get; set; } + + [Option("input-id-1", Required = false, HelpText = "Set the input id in use for Player 1.")] + public string InputId1 { get; set; } + + [Option("input-id-2", Required = false, HelpText = "Set the input id in use for Player 2.")] + public string InputId2 { get; set; } + + [Option("input-id-3", Required = false, HelpText = "Set the input id in use for Player 3.")] + public string InputId3 { get; set; } + + [Option("input-id-4", Required = false, HelpText = "Set the input id in use for Player 4.")] + public string InputId4 { get; set; } + + [Option("input-id-5", Required = false, HelpText = "Set the input id in use for Player 5.")] + public string InputId5 { get; set; } + + [Option("input-id-6", Required = false, HelpText = "Set the input id in use for Player 6.")] + public string InputId6 { get; set; } + + [Option("input-id-7", Required = false, HelpText = "Set the input id in use for Player 7.")] + public string InputId7 { get; set; } + + [Option("input-id-8", Required = false, HelpText = "Set the input id in use for Player 8.")] + public string InputId8 { get; set; } + + [Option("input-id-handheld", Required = false, HelpText = "Set the input id in use for the Handheld Player.")] + public string InputIdHandheld { get; set; } + + [Option("enable-keyboard", Required = false, Default = false, HelpText = "Enable or disable keyboard support (Independent from controllers binding).")] + public bool EnableKeyboard { get; set; } + + [Option("enable-mouse", Required = false, Default = false, HelpText = "Enable or disable mouse support.")] + public bool EnableMouse { get; set; } + + [Option("hide-cursor", Required = false, Default = HideCursorMode.OnIdle, HelpText = "Change when the cursor gets hidden.")] + public HideCursorMode HideCursorMode { get; set; } + + [Option("list-input-profiles", Required = false, HelpText = "List inputs profiles.")] + public bool ListInputProfiles { get; set; } + + [Option("list-inputs-ids", Required = false, HelpText = "List inputs ids.")] + public bool ListInputIds { get; set; } + + // System + + [Option("disable-ptc", Required = false, HelpText = "Disables profiled persistent translation cache.")] + public bool DisablePTC { get; set; } + + [Option("enable-internet-connection", Required = false, Default = false, HelpText = "Enables guest Internet connection.")] + public bool EnableInternetAccess { get; set; } + + [Option("disable-fs-integrity-checks", Required = false, HelpText = "Disables integrity checks on Game content files.")] + public bool DisableFsIntegrityChecks { get; set; } + + [Option("fs-global-access-log-mode", Required = false, Default = 0, HelpText = "Enables FS access log output to the console.")] + public int FsGlobalAccessLogMode { get; set; } + + [Option("disable-vsync", Required = false, HelpText = "Disables Vertical Sync.")] + public bool DisableVSync { get; set; } + + [Option("disable-shader-cache", Required = false, HelpText = "Disables Shader cache.")] + public bool DisableShaderCache { get; set; } + + [Option("enable-texture-recompression", Required = false, Default = false, HelpText = "Enables Texture recompression.")] + public bool EnableTextureRecompression { get; set; } + + [Option("disable-docked-mode", Required = false, HelpText = "Disables Docked Mode.")] + public bool DisableDockedMode { get; set; } + + [Option("system-language", Required = false, Default = SystemLanguage.AmericanEnglish, HelpText = "Change System Language.")] + public SystemLanguage SystemLanguage { get; set; } + + [Option("system-region", Required = false, Default = RegionCode.USA, HelpText = "Change System Region.")] + public RegionCode SystemRegion { get; set; } + + [Option("system-timezone", Required = false, Default = "UTC", HelpText = "Change System TimeZone.")] + public string SystemTimeZone { get; set; } + + [Option("system-time-offset", Required = false, Default = 0, HelpText = "Change System Time Offset in seconds.")] + public long SystemTimeOffset { get; set; } + + [Option("memory-manager-mode", Required = false, Default = MemoryManagerMode.HostMappedUnsafe, HelpText = "The selected memory manager mode.")] + public MemoryManagerMode MemoryManagerMode { get; set; } + + [Option("audio-volume", Required = false, Default = 1.0f, HelpText = "The audio level (0 to 1).")] + public float AudioVolume { get; set; } + + [Option("use-hypervisor", Required = false, Default = true, HelpText = "Uses Hypervisor over JIT if available.")] + public bool? UseHypervisor { get; set; } + + [Option("lan-interface-id", Required = false, Default = "0", HelpText = "GUID for the network interface used by LAN.")] + public string MultiplayerLanInterfaceId { get; set; } + + // Logging + + [Option("disable-file-logging", Required = false, Default = false, HelpText = "Disables logging to a file on disk.")] + public bool DisableFileLog { get; set; } + + [Option("enable-debug-logs", Required = false, Default = false, HelpText = "Enables printing debug log messages.")] + public bool LoggingEnableDebug { get; set; } + + [Option("disable-stub-logs", Required = false, HelpText = "Disables printing stub log messages.")] + public bool LoggingDisableStub { get; set; } + + [Option("disable-info-logs", Required = false, HelpText = "Disables printing info log messages.")] + public bool LoggingDisableInfo { get; set; } + + [Option("disable-warning-logs", Required = false, HelpText = "Disables printing warning log messages.")] + public bool LoggingDisableWarning { get; set; } + + [Option("disable-error-logs", Required = false, HelpText = "Disables printing error log messages.")] + public bool LoggingEnableError { get; set; } + + [Option("enable-trace-logs", Required = false, Default = false, HelpText = "Enables printing trace log messages.")] + public bool LoggingEnableTrace { get; set; } + + [Option("disable-guest-logs", Required = false, HelpText = "Disables printing guest log messages.")] + public bool LoggingDisableGuest { get; set; } + + [Option("enable-fs-access-logs", Required = false, Default = false, HelpText = "Enables printing FS access log messages.")] + public bool LoggingEnableFsAccessLog { get; set; } + + [Option("graphics-debug-level", Required = false, Default = GraphicsDebugLevel.None, HelpText = "Change Graphics API debug log level.")] + public GraphicsDebugLevel LoggingGraphicsDebugLevel { get; set; } + + // Graphics + + [Option("resolution-scale", Required = false, Default = 1, HelpText = "Resolution Scale. A floating point scale applied to applicable render targets.")] + public float ResScale { get; set; } + + [Option("max-anisotropy", Required = false, Default = -1, HelpText = "Max Anisotropy. Values range from 0 - 16. Set to -1 to let the game decide.")] + public float MaxAnisotropy { get; set; } + + [Option("aspect-ratio", Required = false, Default = AspectRatio.Fixed16x9, HelpText = "Aspect Ratio applied to the renderer window.")] + public AspectRatio AspectRatio { get; set; } + + [Option("backend-threading", Required = false, Default = BackendThreading.Auto, HelpText = "Whether or not backend threading is enabled. The \"Auto\" setting will determine whether threading should be enabled at runtime.")] + public BackendThreading BackendThreading { get; set; } + + [Option("disable-macro-hle", Required = false, HelpText = "Disables high-level emulation of Macro code. Leaving this enabled improves performance but may cause graphical glitches in some games.")] + public bool DisableMacroHLE { get; set; } + + [Option("graphics-shaders-dump-path", Required = false, HelpText = "Dumps shaders in this local directory. (Developer only)")] + public string GraphicsShadersDumpPath { get; set; } + + [Option("graphics-backend", Required = false, Default = GraphicsBackend.OpenGl, HelpText = "Change Graphics Backend to use.")] + public GraphicsBackend GraphicsBackend { get; set; } + + [Option("preferred-gpu-vendor", Required = false, Default = "", HelpText = "When using the Vulkan backend, prefer using the GPU from the specified vendor.")] + public string PreferredGPUVendor { get; set; } + + [Option("anti-aliasing", Required = false, Default = AntiAliasing.None, HelpText = "Set the type of anti aliasing being used. [None|Fxaa|SmaaLow|SmaaMedium|SmaaHigh|SmaaUltra]")] + public AntiAliasing AntiAliasing { get; set; } + + [Option("scaling-filter", Required = false, Default = ScalingFilter.Bilinear, HelpText = "Set the scaling filter. [Bilinear|Nearest|Fsr]")] + public ScalingFilter ScalingFilter { get; set; } + + [Option("scaling-filter-level", Required = false, Default = 0, HelpText = "Set the scaling filter intensity (currently only applies to FSR). [0-100]")] + public int ScalingFilterLevel { get; set; } + + // Hacks + + [Option("expand-ram", Required = false, Default = false, HelpText = "Expands the RAM amount on the emulated system from 4GiB to 6GiB.")] + public bool ExpandRAM { get; set; } + + [Option("ignore-missing-services", Required = false, Default = false, HelpText = "Enable ignoring missing services.")] + public bool IgnoreMissingServices { get; set; } + + // Values + + [Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)] + public string InputPath { get; set; } + } +} diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs new file mode 100644 index 00000000..07995dbd --- /dev/null +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -0,0 +1,741 @@ +using CommandLine; +using LibHac.Tools.FsSystem; +using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using Ryujinx.Common.GraphicsDriver; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Logging.Targets; +using Ryujinx.Common.SystemInterop; +using Ryujinx.Common.Utilities; +using Ryujinx.Cpu; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.Gpu.Shader; +using Ryujinx.Graphics.OpenGL; +using Ryujinx.Graphics.Vulkan; +using Ryujinx.Graphics.Vulkan.MoltenVK; +using Ryujinx.Headless.SDL2.OpenGL; +using Ryujinx.Headless.SDL2.Vulkan; +using Ryujinx.HLE; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.Input; +using Ryujinx.Input.HLE; +using Ryujinx.Input.SDL2; +using Ryujinx.SDL2.Common; +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using System.Threading; +using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; +using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; +using Key = Ryujinx.Common.Configuration.Hid.Key; + +namespace Ryujinx.Headless.SDL2 +{ + class Program + { + public static string Version { get; private set; } + + private static VirtualFileSystem _virtualFileSystem; + private static ContentManager _contentManager; + private static AccountManager _accountManager; + private static LibHacHorizonManager _libHacHorizonManager; + private static UserChannelPersistence _userChannelPersistence; + private static InputManager _inputManager; + private static Switch _emulationContext; + private static WindowBase _window; + private static WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; + private static List _inputConfiguration; + private static bool _enableKeyboard; + private static bool _enableMouse; + + private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + static void Main(string[] args) + { + Version = ReleaseInformation.Version; + + // Make process DPI aware for proper window sizing on high-res screens. + ForceDpiAware.Windows(); + + Console.Title = $"Ryujinx Console {Version} (Headless SDL2)"; + + if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) + { + AutoResetEvent invoked = new(false); + + // MacOS must perform SDL polls from the main thread. + SDL2Driver.MainThreadDispatcher = action => + { + invoked.Reset(); + + WindowBase.QueueMainThreadAction(() => + { + action(); + + invoked.Set(); + }); + + invoked.WaitOne(); + }; + } + + if (OperatingSystem.IsMacOS()) + { + MVKInitialization.InitializeResolver(); + } + + Parser.Default.ParseArguments(args) + .WithParsed(Load) + .WithNotParsed(errors => errors.Output()); + } + + private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index) + { + if (inputId == null) + { + if (index == PlayerIndex.Player1) + { + Logger.Info?.Print(LogClass.Application, $"{index} not configured, defaulting to default keyboard."); + + // Default to keyboard + inputId = "0"; + } + else + { + Logger.Info?.Print(LogClass.Application, $"{index} not configured"); + + return null; + } + } + + IGamepad gamepad; + + bool isKeyboard = true; + + gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId); + + if (gamepad == null) + { + gamepad = _inputManager.GamepadDriver.GetGamepad(inputId); + isKeyboard = false; + + if (gamepad == null) + { + Logger.Error?.Print(LogClass.Application, $"{index} gamepad not found (\"{inputId}\")"); + + return null; + } + } + + string gamepadName = gamepad.Name; + + gamepad.Dispose(); + + InputConfig config; + + if (inputProfileName == null || inputProfileName.Equals("default")) + { + if (isKeyboard) + { + config = new StandardKeyboardInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.WindowKeyboard, + Id = null, + ControllerType = ControllerType.JoyconPair, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = Key.Up, + DpadDown = Key.Down, + DpadLeft = Key.Left, + DpadRight = Key.Right, + ButtonMinus = Key.Minus, + ButtonL = Key.E, + ButtonZl = Key.Q, + ButtonSl = Key.Unbound, + ButtonSr = Key.Unbound, + }, + + LeftJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = Key.W, + StickDown = Key.S, + StickLeft = Key.A, + StickRight = Key.D, + StickButton = Key.F, + }, + + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = Key.Z, + ButtonB = Key.X, + ButtonX = Key.C, + ButtonY = Key.V, + ButtonPlus = Key.Plus, + ButtonR = Key.U, + ButtonZr = Key.O, + ButtonSl = Key.Unbound, + ButtonSr = Key.Unbound, + }, + + RightJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = Key.I, + StickDown = Key.K, + StickLeft = Key.J, + StickRight = Key.L, + StickButton = Key.H, + }, + }; + } + else + { + bool isNintendoStyle = gamepadName.Contains("Nintendo"); + + config = new StandardControllerInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.GamepadSDL2, + Id = null, + ControllerType = ControllerType.JoyconPair, + DeadzoneLeft = 0.1f, + DeadzoneRight = 0.1f, + RangeLeft = 1.0f, + RangeRight = 1.0f, + TriggerThreshold = 0.5f, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = ConfigGamepadInputId.DpadUp, + DpadDown = ConfigGamepadInputId.DpadDown, + DpadLeft = ConfigGamepadInputId.DpadLeft, + DpadRight = ConfigGamepadInputId.DpadRight, + ButtonMinus = ConfigGamepadInputId.Minus, + ButtonL = ConfigGamepadInputId.LeftShoulder, + ButtonZl = ConfigGamepadInputId.LeftTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + + LeftJoyconStick = new JoyconConfigControllerStick + { + Joystick = ConfigStickInputId.Left, + StickButton = ConfigGamepadInputId.LeftStick, + InvertStickX = false, + InvertStickY = false, + Rotate90CW = false, + }, + + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B, + ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A, + ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y, + ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X, + ButtonPlus = ConfigGamepadInputId.Plus, + ButtonR = ConfigGamepadInputId.RightShoulder, + ButtonZr = ConfigGamepadInputId.RightTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + + RightJoyconStick = new JoyconConfigControllerStick + { + Joystick = ConfigStickInputId.Right, + StickButton = ConfigGamepadInputId.RightStick, + InvertStickX = false, + InvertStickY = false, + Rotate90CW = false, + }, + + Motion = new StandardMotionConfigController + { + MotionBackend = MotionInputBackendType.GamepadDriver, + EnableMotion = true, + Sensitivity = 100, + GyroDeadzone = 1, + }, + Rumble = new RumbleConfigController + { + StrongRumble = 1f, + WeakRumble = 1f, + EnableRumble = false, + }, + }; + } + } + else + { + string profileBasePath; + + if (isKeyboard) + { + profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "keyboard"); + } + else + { + profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "controller"); + } + + string path = Path.Combine(profileBasePath, inputProfileName + ".json"); + + if (!File.Exists(path)) + { + Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" not found for \"{inputId}\""); + + return null; + } + + try + { + config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig); + } + catch (JsonException) + { + Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" parsing failed for \"{inputId}\""); + + return null; + } + } + + config.Id = inputId; + config.PlayerIndex = index; + + string inputTypeName = isKeyboard ? "Keyboard" : "Gamepad"; + + Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} configured with {inputTypeName} \"{config.Id}\""); + + // If both stick ranges are 0 (usually indicative of an outdated profile load) then both sticks will be set to 1.0. + if (config is StandardControllerInputConfig controllerConfig) + { + if (controllerConfig.RangeLeft <= 0.0f && controllerConfig.RangeRight <= 0.0f) + { + controllerConfig.RangeLeft = 1.0f; + controllerConfig.RangeRight = 1.0f; + + Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration"); + } + } + + return config; + } + + static void Load(Options option) + { + AppDataManager.Initialize(option.BaseDataDir); + + _virtualFileSystem = VirtualFileSystem.CreateInstance(); + _libHacHorizonManager = new LibHacHorizonManager(); + + _libHacHorizonManager.InitializeFsServer(_virtualFileSystem); + _libHacHorizonManager.InitializeArpServer(); + _libHacHorizonManager.InitializeBcatServer(); + _libHacHorizonManager.InitializeSystemClients(); + + _contentManager = new ContentManager(_virtualFileSystem); + _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile); + _userChannelPersistence = new UserChannelPersistence(); + + _inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver()); + + GraphicsConfig.EnableShaderCache = true; + + if (OperatingSystem.IsMacOS()) + { + if (option.GraphicsBackend == GraphicsBackend.OpenGl) + { + option.GraphicsBackend = GraphicsBackend.Vulkan; + Logger.Warning?.Print(LogClass.Application, "OpenGL is not supported on macOS, switching to Vulkan!"); + } + } + + IGamepad gamepad; + + if (option.ListInputIds) + { + Logger.Info?.Print(LogClass.Application, "Input Ids:"); + + foreach (string id in _inputManager.KeyboardDriver.GamepadsIds) + { + gamepad = _inputManager.KeyboardDriver.GetGamepad(id); + + Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")"); + + gamepad.Dispose(); + } + + foreach (string id in _inputManager.GamepadDriver.GamepadsIds) + { + gamepad = _inputManager.GamepadDriver.GetGamepad(id); + + Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")"); + + gamepad.Dispose(); + } + + return; + } + + if (option.InputPath == null) + { + Logger.Error?.Print(LogClass.Application, "Please provide a file to load"); + + return; + } + + _inputConfiguration = new List(); + _enableKeyboard = option.EnableKeyboard; + _enableMouse = option.EnableMouse; + + static void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index) + { + InputConfig inputConfig = HandlePlayerConfiguration(inputProfileName, inputId, index); + + if (inputConfig != null) + { + _inputConfiguration.Add(inputConfig); + } + } + + LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1); + LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2); + LoadPlayerConfiguration(option.InputProfile3Name, option.InputId3, PlayerIndex.Player3); + LoadPlayerConfiguration(option.InputProfile4Name, option.InputId4, PlayerIndex.Player4); + LoadPlayerConfiguration(option.InputProfile5Name, option.InputId5, PlayerIndex.Player5); + LoadPlayerConfiguration(option.InputProfile6Name, option.InputId6, PlayerIndex.Player6); + LoadPlayerConfiguration(option.InputProfile7Name, option.InputId7, PlayerIndex.Player7); + LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8); + LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld); + + if (_inputConfiguration.Count == 0) + { + return; + } + + // Setup logging level + Logger.SetEnable(LogLevel.Debug, option.LoggingEnableDebug); + Logger.SetEnable(LogLevel.Stub, !option.LoggingDisableStub); + Logger.SetEnable(LogLevel.Info, !option.LoggingDisableInfo); + Logger.SetEnable(LogLevel.Warning, !option.LoggingDisableWarning); + Logger.SetEnable(LogLevel.Error, option.LoggingEnableError); + Logger.SetEnable(LogLevel.Trace, option.LoggingEnableTrace); + Logger.SetEnable(LogLevel.Guest, !option.LoggingDisableGuest); + Logger.SetEnable(LogLevel.AccessLog, option.LoggingEnableFsAccessLog); + + if (!option.DisableFileLog) + { + string logDir = AppDataManager.LogsDirPath; + FileStream logFile = null; + + if (!string.IsNullOrEmpty(logDir)) + { + logFile = FileLogTarget.PrepareLogFile(logDir); + } + + if (logFile != null) + { + Logger.AddTarget(new AsyncLogTargetWrapper( + new FileLogTarget("file", logFile), + 1000, + AsyncLogTargetOverflowAction.Block + )); + } + else + { + Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the Logs directory, Application Data, or the Ryujinx directory is writable."); + } + } + + // Setup graphics configuration + GraphicsConfig.EnableShaderCache = !option.DisableShaderCache; + GraphicsConfig.EnableTextureRecompression = option.EnableTextureRecompression; + GraphicsConfig.ResScale = option.ResScale; + GraphicsConfig.MaxAnisotropy = option.MaxAnisotropy; + GraphicsConfig.ShadersDumpPath = option.GraphicsShadersDumpPath; + GraphicsConfig.EnableMacroHLE = !option.DisableMacroHLE; + + DriverUtilities.InitDriverConfig(option.BackendThreading == BackendThreading.Off); + + while (true) + { + LoadApplication(option); + + if (_userChannelPersistence.PreviousIndex == -1 || !_userChannelPersistence.ShouldRestart) + { + break; + } + + _userChannelPersistence.ShouldRestart = false; + } + + _inputManager.Dispose(); + } + + private static void SetupProgressHandler() + { + if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null) + { + _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler; + _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler; + } + + _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; + _emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler; + } + + private static void ProgressHandler(T state, int current, int total) where T : Enum + { + string label = state switch + { + LoadState => $"PTC : {current}/{total}", + ShaderCacheState => $"Shaders : {current}/{total}", + _ => throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}"), + }; + + Logger.Info?.Print(LogClass.Application, label); + } + + private static WindowBase CreateWindow(Options options) + { + return options.GraphicsBackend == GraphicsBackend.Vulkan + ? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode) + : new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode); + } + + private static IRenderer CreateRenderer(Options options, WindowBase window) + { + if (options.GraphicsBackend == GraphicsBackend.Vulkan && window is VulkanWindow vulkanWindow) + { + string preferredGpuId = string.Empty; + Vk api = Vk.GetApi(); + + if (!string.IsNullOrEmpty(options.PreferredGPUVendor)) + { + string preferredGpuVendor = options.PreferredGPUVendor.ToLowerInvariant(); + var devices = VulkanRenderer.GetPhysicalDevices(api); + + foreach (var device in devices) + { + if (device.Vendor.ToLowerInvariant() == preferredGpuVendor) + { + preferredGpuId = device.Id; + break; + } + } + } + + return new VulkanRenderer( + api, + (instance, vk) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))), + vulkanWindow.GetRequiredInstanceExtensions, + preferredGpuId); + } + + return new OpenGLRenderer(); + } + + private static Switch InitializeEmulationContext(WindowBase window, IRenderer renderer, Options options) + { + BackendThreading threadingMode = options.BackendThreading; + + bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); + + if (threadedGAL) + { + renderer = new ThreadedRenderer(renderer); + } + + HLEConfiguration configuration = new(_virtualFileSystem, + _libHacHorizonManager, + _contentManager, + _accountManager, + _userChannelPersistence, + renderer, + new SDL2HardwareDeviceDriver(), + options.ExpandRAM ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB, + window, + options.SystemLanguage, + options.SystemRegion, + !options.DisableVSync, + !options.DisableDockedMode, + !options.DisablePTC, + options.EnableInternetAccess, + !options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, + options.FsGlobalAccessLogMode, + options.SystemTimeOffset, + options.SystemTimeZone, + options.MemoryManagerMode, + options.IgnoreMissingServices, + options.AspectRatio, + options.AudioVolume, + options.UseHypervisor ?? true, + options.MultiplayerLanInterfaceId, + Common.Configuration.Multiplayer.MultiplayerMode.Disabled); + + return new Switch(configuration); + } + + private static void ExecutionEntrypoint() + { + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1); + } + + DisplaySleep.Prevent(); + + _window.Initialize(_emulationContext, _inputConfiguration, _enableKeyboard, _enableMouse); + + _window.Execute(); + + _emulationContext.Dispose(); + _window.Dispose(); + + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution?.Dispose(); + _windowsMultimediaTimerResolution = null; + } + } + + private static bool LoadApplication(Options options) + { + string path = options.InputPath; + + Logger.RestartTime(); + + WindowBase window = CreateWindow(options); + IRenderer renderer = CreateRenderer(options, window); + + _window = window; + + _window.IsFullscreen = options.IsFullscreen; + _window.DisplayId = options.DisplayId; + _window.IsExclusiveFullscreen = options.IsExclusiveFullscreen; + _window.ExclusiveFullscreenWidth = options.ExclusiveFullscreenWidth; + _window.ExclusiveFullscreenHeight = options.ExclusiveFullscreenHeight; + _window.AntiAliasing = options.AntiAliasing; + _window.ScalingFilter = options.ScalingFilter; + _window.ScalingFilterLevel = options.ScalingFilterLevel; + + _emulationContext = InitializeEmulationContext(window, renderer, options); + + SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion(); + + Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); + + if (Directory.Exists(path)) + { + string[] romFsFiles = Directory.GetFiles(path, "*.istorage"); + + if (romFsFiles.Length == 0) + { + romFsFiles = Directory.GetFiles(path, "*.romfs"); + } + + if (romFsFiles.Length > 0) + { + Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); + + if (!_emulationContext.LoadCart(path, romFsFiles[0])) + { + _emulationContext.Dispose(); + + return false; + } + } + else + { + Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); + + if (!_emulationContext.LoadCart(path)) + { + _emulationContext.Dispose(); + + return false; + } + } + } + else if (File.Exists(path)) + { + switch (Path.GetExtension(path).ToLowerInvariant()) + { + case ".xci": + Logger.Info?.Print(LogClass.Application, "Loading as XCI."); + + if (!_emulationContext.LoadXci(path)) + { + _emulationContext.Dispose(); + + return false; + } + break; + case ".nca": + Logger.Info?.Print(LogClass.Application, "Loading as NCA."); + + if (!_emulationContext.LoadNca(path)) + { + _emulationContext.Dispose(); + + return false; + } + break; + case ".nsp": + case ".pfs0": + Logger.Info?.Print(LogClass.Application, "Loading as NSP."); + + if (!_emulationContext.LoadNsp(path)) + { + _emulationContext.Dispose(); + + return false; + } + break; + default: + Logger.Info?.Print(LogClass.Application, "Loading as Homebrew."); + try + { + if (!_emulationContext.LoadProgram(path)) + { + _emulationContext.Dispose(); + + return false; + } + } + catch (ArgumentOutOfRangeException) + { + Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); + + _emulationContext.Dispose(); + + return false; + } + break; + } + } + else + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't load '{options.InputPath}'. Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); + + _emulationContext.Dispose(); + + return false; + } + + SetupProgressHandler(); + ExecutionEntrypoint(); + + return true; + } + } +} diff --git a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj new file mode 100644 index 00000000..61022954 --- /dev/null +++ b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj @@ -0,0 +1,72 @@ + + + + net8.0 + win-x64;osx-x64;linux-x64 + Exe + true + 1.0.0-dirty + $(DefineConstants);$(ExtraDefineConstants) + - + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + THIRDPARTY.md + + + Always + LICENSE.txt + + + + + + Always + + + + + + + + + + false + ..\Ryujinx\Ryujinx.ico + + + + true + true + partial + + diff --git a/src/Ryujinx.Headless.SDL2/Ryujinx.bmp b/src/Ryujinx.Headless.SDL2/Ryujinx.bmp new file mode 100644 index 0000000000000000000000000000000000000000..1daa7ce9406ae1ad56b4e697582c3e746a52da7e GIT binary patch literal 9354 zcmdU!eN2^A9LLY)lBHX$!m=%_+j6d?ISFlR(`2l-^|BHWB)J5LjMbtf%yhNUAC}W) z%RgHFk+YY%_3E0gmswW8r9W_sxm9MO754&8)Us7->C@-?-21@e1>pj&=ef`PaGt~E znV)l>-#O=Z&Mkak{S0GY>-qUC%t;V1ArsUmeBPQBN7eXc8594fs#SP*(NoPWf8JR2 z>9tpe)*cBIZ_b@S%Q^QZg}oKf-eEs2NiBGX;*_5|0L&Z>UTmFR3}kG zeJM$v4ui$-j>BB=!e4D!=J?AY!6eFO z-~`BVB)s;om4CE7;J63yZG@Afcsp(Kg@4ar-xuQDpQ5`T;>YfPeAWMx;iECQ`^|qI z?#I&D|6$xU&p5H2O6iOE$64`HJ4W{*@pHJ}cgO#P{iE#}%w<6nP0RZe?wofl!Sx`763$NS`_W%**aSQKJ4{AnQ->UHf?wcXpZZ^cZyl_F z2G|a4bTV+!7u$U|IeTYh<&^KH`)@JFpY*?zv*B!yuI;0Z8G>V2{P4mm&Rjtra!EW};8+`Ro?TqS?Sy2kG|^12MQA%W-Xm%IOUkF()i zKx4WIa#h!PF~0Xe9VjpLuRnX4n7yB2PZJ56?ntI7z{|}t&YnTh throw new NotImplementedException(); + + public string Id => "0"; + + public string Name => "SDL2Mouse"; + + public bool IsConnected => true; + + public bool[] Buttons => _driver.PressedButtons; + + Size IMouse.ClientSize => _driver.GetClientSize(); + + public SDL2Mouse(SDL2MouseDriver driver) + { + _driver = driver; + } + + public Vector2 GetPosition() + { + return _driver.CurrentPosition; + } + + public Vector2 GetScroll() + { + return _driver.Scroll; + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + throw new NotImplementedException(); + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + throw new NotImplementedException(); + } + + public GamepadStateSnapshot GetStateSnapshot() + { + throw new NotImplementedException(); + } + + public (float, float) GetStick(StickInputId inputId) + { + throw new NotImplementedException(); + } + + public bool IsButtonPressed(MouseButton button) + { + return _driver.IsButtonPressed(button); + } + + public bool IsPressed(GamepadButtonInputId inputId) + { + throw new NotImplementedException(); + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + throw new NotImplementedException(); + } + + public void SetConfiguration(InputConfig configuration) + { + throw new NotImplementedException(); + } + + public void SetTriggerThreshold(float triggerThreshold) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + _driver = null; + } + } +} diff --git a/src/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs b/src/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs new file mode 100644 index 00000000..8983091f --- /dev/null +++ b/src/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs @@ -0,0 +1,178 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Input; +using System; +using System.Diagnostics; +using System.Drawing; +using System.Numerics; +using System.Runtime.CompilerServices; +using static SDL2.SDL; + +namespace Ryujinx.Headless.SDL2 +{ + class SDL2MouseDriver : IGamepadDriver + { + private const int CursorHideIdleTime = 5; // seconds + + private bool _isDisposed; + private readonly HideCursorMode _hideCursorMode; + private bool _isHidden; + private long _lastCursorMoveTime; + + public bool[] PressedButtons { get; } + + public Vector2 CurrentPosition { get; private set; } + public Vector2 Scroll { get; private set; } + public Size ClientSize; + + public SDL2MouseDriver(HideCursorMode hideCursorMode) + { + PressedButtons = new bool[(int)MouseButton.Count]; + _hideCursorMode = hideCursorMode; + + if (_hideCursorMode == HideCursorMode.Always) + { + if (SDL_ShowCursor(SDL_DISABLE) != SDL_DISABLE) + { + Logger.Error?.PrintMsg(LogClass.Application, "Failed to disable the cursor."); + } + + _isHidden = true; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static MouseButton DriverButtonToMouseButton(uint rawButton) + { + Debug.Assert(rawButton > 0 && rawButton <= (int)MouseButton.Count); + + return (MouseButton)(rawButton - 1); + } + + public void UpdatePosition() + { + _ = SDL_GetMouseState(out int posX, out int posY); + Vector2 position = new(posX, posY); + + if (CurrentPosition != position) + { + CurrentPosition = position; + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + } + + CheckIdle(); + } + + private void CheckIdle() + { + if (_hideCursorMode != HideCursorMode.OnIdle) + { + return; + } + + long cursorMoveDelta = Stopwatch.GetTimestamp() - _lastCursorMoveTime; + + if (cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency) + { + if (!_isHidden) + { + if (SDL_ShowCursor(SDL_DISABLE) != SDL_DISABLE) + { + Logger.Error?.PrintMsg(LogClass.Application, "Failed to disable the cursor."); + } + + _isHidden = true; + } + } + else + { + if (_isHidden) + { + if (SDL_ShowCursor(SDL_ENABLE) != SDL_ENABLE) + { + Logger.Error?.PrintMsg(LogClass.Application, "Failed to enable the cursor."); + } + + _isHidden = false; + } + } + } + + public void Update(SDL_Event evnt) + { + switch (evnt.type) + { + case SDL_EventType.SDL_MOUSEBUTTONDOWN: + case SDL_EventType.SDL_MOUSEBUTTONUP: + uint rawButton = evnt.button.button; + + if (rawButton > 0 && rawButton <= (int)MouseButton.Count) + { + PressedButtons[(int)DriverButtonToMouseButton(rawButton)] = evnt.type == SDL_EventType.SDL_MOUSEBUTTONDOWN; + + CurrentPosition = new Vector2(evnt.button.x, evnt.button.y); + } + + break; + + // NOTE: On Linux using Wayland mouse motion events won't be received at all. + case SDL_EventType.SDL_MOUSEMOTION: + CurrentPosition = new Vector2(evnt.motion.x, evnt.motion.y); + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + + break; + + case SDL_EventType.SDL_MOUSEWHEEL: + Scroll = new Vector2(evnt.wheel.x, evnt.wheel.y); + + break; + } + } + + public void SetClientSize(int width, int height) + { + ClientSize = new Size(width, height); + } + + public bool IsButtonPressed(MouseButton button) + { + return PressedButtons[(int)button]; + } + + public Size GetClientSize() + { + return ClientSize; + } + + public string DriverName => "SDL2"; + + public event Action OnGamepadConnected + { + add { } + remove { } + } + + public event Action OnGamepadDisconnected + { + add { } + remove { } + } + + public ReadOnlySpan GamepadsIds => new[] { "0" }; + + public IGamepad GetGamepad(string id) + { + return new SDL2Mouse(this); + } + + public void Dispose() + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + } + } +} diff --git a/src/Ryujinx.Headless.SDL2/StatusUpdatedEventArgs.cs b/src/Ryujinx.Headless.SDL2/StatusUpdatedEventArgs.cs new file mode 100644 index 00000000..0b199d12 --- /dev/null +++ b/src/Ryujinx.Headless.SDL2/StatusUpdatedEventArgs.cs @@ -0,0 +1,24 @@ +using System; + +namespace Ryujinx.Headless.SDL2 +{ + class StatusUpdatedEventArgs : EventArgs + { + public bool VSyncEnabled; + public string DockedMode; + public string AspectRatio; + public string GameStatus; + public string FifoStatus; + public string GpuName; + + public StatusUpdatedEventArgs(bool vSyncEnabled, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus, string gpuName) + { + VSyncEnabled = vSyncEnabled; + DockedMode = dockedMode; + AspectRatio = aspectRatio; + GameStatus = gameStatus; + FifoStatus = fifoStatus; + GpuName = gpuName; + } + } +} diff --git a/src/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs b/src/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs new file mode 100644 index 00000000..e5572c93 --- /dev/null +++ b/src/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs @@ -0,0 +1,112 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Input.HLE; +using Ryujinx.SDL2.Common; +using System; +using System.Runtime.InteropServices; +using static SDL2.SDL; + +namespace Ryujinx.Headless.SDL2.Vulkan +{ + class VulkanWindow : WindowBase + { + private readonly GraphicsDebugLevel _glLogLevel; + + public VulkanWindow( + InputManager inputManager, + GraphicsDebugLevel glLogLevel, + AspectRatio aspectRatio, + bool enableMouse, + HideCursorMode hideCursorMode) + : base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode) + { + _glLogLevel = glLogLevel; + } + + public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_VULKAN; + + protected override void InitializeWindowRenderer() { } + + protected override void InitializeRenderer() + { + if (IsExclusiveFullscreen) + { + Renderer?.Window.SetSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight); + MouseDriver.SetClientSize(ExclusiveFullscreenWidth, ExclusiveFullscreenHeight); + } + else + { + Renderer?.Window.SetSize(DefaultWidth, DefaultHeight); + MouseDriver.SetClientSize(DefaultWidth, DefaultHeight); + } + } + + private static void BasicInvoke(Action action) + { + action(); + } + + public IntPtr CreateWindowSurface(IntPtr instance) + { + ulong surfaceHandle = 0; + + void CreateSurface() + { + if (SDL_Vulkan_CreateSurface(WindowHandle, instance, out surfaceHandle) == SDL_bool.SDL_FALSE) + { + string errorMessage = $"SDL_Vulkan_CreateSurface failed with error \"{SDL_GetError()}\""; + + Logger.Error?.Print(LogClass.Application, errorMessage); + + throw new Exception(errorMessage); + } + } + + if (SDL2Driver.MainThreadDispatcher != null) + { + SDL2Driver.MainThreadDispatcher(CreateSurface); + } + else + { + CreateSurface(); + } + + return (IntPtr)surfaceHandle; + } + + public unsafe string[] GetRequiredInstanceExtensions() + { + if (SDL_Vulkan_GetInstanceExtensions(WindowHandle, out uint extensionsCount, IntPtr.Zero) == SDL_bool.SDL_TRUE) + { + IntPtr[] rawExtensions = new IntPtr[(int)extensionsCount]; + string[] extensions = new string[(int)extensionsCount]; + + fixed (IntPtr* rawExtensionsPtr = rawExtensions) + { + if (SDL_Vulkan_GetInstanceExtensions(WindowHandle, out extensionsCount, (IntPtr)rawExtensionsPtr) == SDL_bool.SDL_TRUE) + { + for (int i = 0; i < extensions.Length; i++) + { + extensions[i] = Marshal.PtrToStringUTF8(rawExtensions[i]); + } + + return extensions; + } + } + } + + string errorMessage = $"SDL_Vulkan_GetInstanceExtensions failed with error \"{SDL_GetError()}\""; + + Logger.Error?.Print(LogClass.Application, errorMessage); + + throw new Exception(errorMessage); + } + + protected override void FinalizeWindowRenderer() + { + Device.DisposeGpu(); + } + + protected override void SwapBuffers() { } + } +} diff --git a/src/Ryujinx.Headless.SDL2/WindowBase.cs b/src/Ryujinx.Headless.SDL2/WindowBase.cs new file mode 100644 index 00000000..8768913f --- /dev/null +++ b/src/Ryujinx.Headless.SDL2/WindowBase.cs @@ -0,0 +1,554 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.OpenGL; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; +using Ryujinx.HLE.UI; +using Ryujinx.Input; +using Ryujinx.Input.HLE; +using Ryujinx.SDL2.Common; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using static SDL2.SDL; +using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing; +using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter; +using Switch = Ryujinx.HLE.Switch; + +namespace Ryujinx.Headless.SDL2 +{ + abstract partial class WindowBase : IHostUIHandler, IDisposable + { + protected const int DefaultWidth = 1280; + protected const int DefaultHeight = 720; + private const int TargetFps = 60; + private SDL_WindowFlags DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS | SDL_WindowFlags.SDL_WINDOW_SHOWN; + private SDL_WindowFlags FullscreenFlag = 0; + + private static readonly ConcurrentQueue _mainThreadActions = new(); + + [LibraryImport("SDL2")] + // TODO: Remove this as soon as SDL2-CS was updated to expose this method publicly + private static partial IntPtr SDL_LoadBMP_RW(IntPtr src, int freesrc); + + public static void QueueMainThreadAction(Action action) + { + _mainThreadActions.Enqueue(action); + } + + public NpadManager NpadManager { get; } + public TouchScreenManager TouchScreenManager { get; } + public Switch Device { get; private set; } + public IRenderer Renderer { get; private set; } + + public event EventHandler StatusUpdatedEvent; + + protected IntPtr WindowHandle { get; set; } + + public IHostUITheme HostUITheme { get; } + public int Width { get; private set; } + public int Height { get; private set; } + public int DisplayId { get; set; } + public bool IsFullscreen { get; set; } + public bool IsExclusiveFullscreen { get; set; } + public int ExclusiveFullscreenWidth { get; set; } + public int ExclusiveFullscreenHeight { get; set; } + public AntiAliasing AntiAliasing { get; set; } + public ScalingFilter ScalingFilter { get; set; } + public int ScalingFilterLevel { get; set; } + + protected SDL2MouseDriver MouseDriver; + private readonly InputManager _inputManager; + private readonly IKeyboard _keyboardInterface; + private readonly GraphicsDebugLevel _glLogLevel; + private readonly Stopwatch _chrono; + private readonly long _ticksPerFrame; + private readonly CancellationTokenSource _gpuCancellationTokenSource; + private readonly ManualResetEvent _exitEvent; + private readonly ManualResetEvent _gpuDoneEvent; + + private long _ticks; + private bool _isActive; + private bool _isStopped; + private uint _windowId; + + private string _gpuDriverName; + + private readonly AspectRatio _aspectRatio; + private readonly bool _enableMouse; + + public WindowBase( + InputManager inputManager, + GraphicsDebugLevel glLogLevel, + AspectRatio aspectRatio, + bool enableMouse, + HideCursorMode hideCursorMode) + { + MouseDriver = new SDL2MouseDriver(hideCursorMode); + _inputManager = inputManager; + _inputManager.SetMouseDriver(MouseDriver); + NpadManager = _inputManager.CreateNpadManager(); + TouchScreenManager = _inputManager.CreateTouchScreenManager(); + _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); + _glLogLevel = glLogLevel; + _chrono = new Stopwatch(); + _ticksPerFrame = Stopwatch.Frequency / TargetFps; + _gpuCancellationTokenSource = new CancellationTokenSource(); + _exitEvent = new ManualResetEvent(false); + _gpuDoneEvent = new ManualResetEvent(false); + _aspectRatio = aspectRatio; + _enableMouse = enableMouse; + HostUITheme = new HeadlessHostUiTheme(); + + SDL2Driver.Instance.Initialize(); + } + + public void Initialize(Switch device, List inputConfigs, bool enableKeyboard, bool enableMouse) + { + Device = device; + + IRenderer renderer = Device.Gpu.Renderer; + + if (renderer is ThreadedRenderer tr) + { + renderer = tr.BaseRenderer; + } + + Renderer = renderer; + + NpadManager.Initialize(device, inputConfigs, enableKeyboard, enableMouse); + TouchScreenManager.Initialize(device); + } + + private void SetWindowIcon() + { + Stream iconStream = typeof(WindowBase).Assembly.GetManifestResourceStream("Ryujinx.Headless.SDL2.Ryujinx.bmp"); + byte[] iconBytes = new byte[iconStream!.Length]; + + if (iconStream.Read(iconBytes, 0, iconBytes.Length) != iconBytes.Length) + { + Logger.Error?.Print(LogClass.Application, "Failed to read icon to byte array."); + iconStream.Close(); + + return; + } + + iconStream.Close(); + + unsafe + { + fixed (byte* iconPtr = iconBytes) + { + IntPtr rwOpsStruct = SDL_RWFromConstMem((IntPtr)iconPtr, iconBytes.Length); + IntPtr iconHandle = SDL_LoadBMP_RW(rwOpsStruct, 1); + + SDL_SetWindowIcon(WindowHandle, iconHandle); + SDL_FreeSurface(iconHandle); + } + } + } + + private void InitializeWindow() + { + 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)"; + + Width = DefaultWidth; + Height = DefaultHeight; + + if (IsExclusiveFullscreen) + { + Width = ExclusiveFullscreenWidth; + Height = ExclusiveFullscreenHeight; + + DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI; + FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN; + } + else if (IsFullscreen) + { + DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI; + FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP; + } + + WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), Width, Height, DefaultFlags | FullscreenFlag | GetWindowFlags()); + + if (WindowHandle == IntPtr.Zero) + { + string errorMessage = $"SDL_CreateWindow failed with error \"{SDL_GetError()}\""; + + Logger.Error?.Print(LogClass.Application, errorMessage); + + throw new Exception(errorMessage); + } + + SetWindowIcon(); + + _windowId = SDL_GetWindowID(WindowHandle); + SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent); + } + + private void HandleWindowEvent(SDL_Event evnt) + { + if (evnt.type == SDL_EventType.SDL_WINDOWEVENT) + { + switch (evnt.window.windowEvent) + { + case SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED: + // Unlike on Windows, this event fires on macOS when triggering fullscreen mode. + // And promptly crashes the process because `Renderer?.window.SetSize` is undefined. + // As we don't need this to fire in either case we can test for fullscreen. + if (!IsFullscreen && !IsExclusiveFullscreen) + { + Width = evnt.window.data1; + Height = evnt.window.data2; + Renderer?.Window.SetSize(Width, Height); + MouseDriver.SetClientSize(Width, Height); + } + break; + + case SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE: + Exit(); + break; + } + } + else + { + MouseDriver.Update(evnt); + } + } + + protected abstract void InitializeWindowRenderer(); + + protected abstract void InitializeRenderer(); + + protected abstract void FinalizeWindowRenderer(); + + protected abstract void SwapBuffers(); + + public abstract SDL_WindowFlags GetWindowFlags(); + + private string GetGpuDriverName() + { + return Renderer.GetHardwareInfo().GpuDriver; + } + + private void SetAntiAliasing() + { + Renderer?.Window.SetAntiAliasing((Graphics.GAL.AntiAliasing)AntiAliasing); + } + + private void SetScalingFilter() + { + Renderer?.Window.SetScalingFilter((Graphics.GAL.ScalingFilter)ScalingFilter); + Renderer?.Window.SetScalingFilterLevel(ScalingFilterLevel); + } + + public void Render() + { + InitializeWindowRenderer(); + + Device.Gpu.Renderer.Initialize(_glLogLevel); + + InitializeRenderer(); + + SetAntiAliasing(); + + SetScalingFilter(); + + _gpuDriverName = GetGpuDriverName(); + + Device.Gpu.Renderer.RunLoop(() => + { + Device.Gpu.SetGpuThread(); + Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); + + while (_isActive) + { + if (_isStopped) + { + return; + } + + _ticks += _chrono.ElapsedTicks; + + _chrono.Restart(); + + if (Device.WaitFifo()) + { + Device.Statistics.RecordFifoStart(); + Device.ProcessFrame(); + Device.Statistics.RecordFifoEnd(); + } + + while (Device.ConsumeFrameAvailable()) + { + Device.PresentFrame(SwapBuffers); + } + + if (_ticks >= _ticksPerFrame) + { + string dockedMode = Device.System.State.DockedMode ? "Docked" : "Handheld"; + float scale = GraphicsConfig.ResScale; + if (scale != 1) + { + dockedMode += $" ({scale}x)"; + } + + StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( + Device.EnableDeviceVsync, + dockedMode, + Device.Configuration.AspectRatio.ToText(), + $"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", + $"FIFO: {Device.Statistics.GetFifoPercent():0.00} %", + $"GPU: {_gpuDriverName}")); + + _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame); + } + } + + // Make sure all commands in the run loop are fully executed before leaving the loop. + if (Device.Gpu.Renderer is ThreadedRenderer threaded) + { + threaded.FlushThreadedCommands(); + } + + _gpuDoneEvent.Set(); + }); + + FinalizeWindowRenderer(); + } + + public void Exit() + { + TouchScreenManager?.Dispose(); + NpadManager?.Dispose(); + + if (_isStopped) + { + return; + } + + _gpuCancellationTokenSource.Cancel(); + + _isStopped = true; + _isActive = false; + + _exitEvent.WaitOne(); + _exitEvent.Dispose(); + } + + public static void ProcessMainThreadQueue() + { + while (_mainThreadActions.TryDequeue(out Action action)) + { + action(); + } + } + + public void MainLoop() + { + while (_isActive) + { + UpdateFrame(); + + SDL_PumpEvents(); + + ProcessMainThreadQueue(); + + // Polling becomes expensive if it's not slept + Thread.Sleep(1); + } + + _exitEvent.Set(); + } + + private void NvidiaStutterWorkaround() + { + while (_isActive) + { + // When NVIDIA Threaded Optimization is on, the driver will snapshot all threads in the system whenever the application creates any new ones. + // The ThreadPool has something called a "GateThread" which terminates itself after some inactivity. + // However, it immediately starts up again, since the rules regarding when to terminate and when to start differ. + // This creates a new thread every second or so. + // The main problem with this is that the thread snapshot can take 70ms, is on the OpenGL thread and will delay rendering any graphics. + // This is a little over budget on a frame time of 16ms, so creates a large stutter. + // The solution is to keep the ThreadPool active so that it never has a reason to terminate the GateThread. + + // TODO: This should be removed when the issue with the GateThread is resolved. + + ThreadPool.QueueUserWorkItem(state => { }); + Thread.Sleep(300); + } + } + + private bool UpdateFrame() + { + if (!_isActive) + { + return true; + } + + if (_isStopped) + { + return false; + } + + NpadManager.Update(); + + // Touchscreen + bool hasTouch = false; + + // Get screen touch position + if (!_enableMouse) + { + hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as SDL2MouseDriver).IsButtonPressed(MouseButton.Button1), _aspectRatio.ToFloat()); + } + + if (!hasTouch) + { + TouchScreenManager.Update(false); + } + + Device.Hid.DebugPad.Update(); + + // TODO: Replace this with MouseDriver.CheckIdle() when mouse motion events are received on every supported platform. + MouseDriver.UpdatePosition(); + + return true; + } + + public void Execute() + { + _chrono.Restart(); + _isActive = true; + + InitializeWindow(); + + Thread renderLoopThread = new(Render) + { + Name = "GUI.RenderLoop", + }; + renderLoopThread.Start(); + + Thread nvidiaStutterWorkaround = null; + if (Renderer is OpenGLRenderer) + { + nvidiaStutterWorkaround = new Thread(NvidiaStutterWorkaround) + { + Name = "GUI.NvidiaStutterWorkaround", + }; + nvidiaStutterWorkaround.Start(); + } + + MainLoop(); + + // NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose. + // We only need to wait for all commands submitted during the main gpu loop to be processed. + _gpuDoneEvent.WaitOne(); + _gpuDoneEvent.Dispose(); + nvidiaStutterWorkaround?.Join(); + + Exit(); + } + + public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText) + { + // SDL2 doesn't support input dialogs + userText = "Ryujinx"; + + return true; + } + + public bool DisplayMessageDialog(string title, string message) + { + SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_INFORMATION, title, message, WindowHandle); + + return true; + } + + public bool DisplayMessageDialog(ControllerAppletUIArgs args) + { + string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}"; + + string message = $"Application requests {playerCount} player(s) with:\n\n" + + $"TYPES: {args.SupportedStyles}\n\n" + + $"PLAYERS: {string.Join(", ", args.SupportedPlayers)}\n\n" + + (args.IsDocked ? "Docked mode set. Handheld is also invalid.\n\n" : "") + + "Please reconfigure Input now and then press OK."; + + return DisplayMessageDialog("Controller Applet", message); + } + + public IDynamicTextInputHandler CreateDynamicTextInputHandler() + { + return new HeadlessDynamicTextInputHandler(); + } + + public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value) + { + device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value); + + Exit(); + } + + public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText) + { + SDL_MessageBoxData data = new() + { + title = title, + message = message, + buttons = new SDL_MessageBoxButtonData[buttonsText.Length], + numbuttons = buttonsText.Length, + window = WindowHandle, + }; + + for (int i = 0; i < buttonsText.Length; i++) + { + data.buttons[i] = new SDL_MessageBoxButtonData + { + buttonid = i, + text = buttonsText[i], + }; + } + + SDL_ShowMessageBox(ref data, out int _); + + return true; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _isActive = false; + TouchScreenManager?.Dispose(); + NpadManager.Dispose(); + + SDL2Driver.Instance.UnregisterWindow(_windowId); + + SDL_DestroyWindow(WindowHandle); + + SDL2Driver.Instance.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Horizon.Common/IExternalEvent.cs b/src/Ryujinx.Horizon.Common/IExternalEvent.cs new file mode 100644 index 00000000..dedf4c72 --- /dev/null +++ b/src/Ryujinx.Horizon.Common/IExternalEvent.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ryujinx.Horizon.Common +{ + public interface IExternalEvent + { + void Signal(); + void Clear(); + } +} diff --git a/src/Ryujinx.Horizon.Common/ISyscallApi.cs b/src/Ryujinx.Horizon.Common/ISyscallApi.cs new file mode 100644 index 00000000..3d6da041 --- /dev/null +++ b/src/Ryujinx.Horizon.Common/ISyscallApi.cs @@ -0,0 +1,38 @@ +using Ryujinx.Memory; +using System; + +namespace Ryujinx.Horizon.Common +{ + public interface ISyscallApi + { + Result SetHeapSize(out ulong address, ulong size); + + void SleepThread(long timeout); + + Result CloseHandle(int handle); + + Result WaitSynchronization(out int handleIndex, ReadOnlySpan handles, long timeout); + Result CancelSynchronization(int handle); + + Result GetProcessId(out ulong pid, int handle); + + Result ConnectToNamedPort(out int handle, string name); + Result SendSyncRequest(int handle); + Result CreateSession(out int serverSessionHandle, out int clientSessionHandle, bool isLight, string name); + Result AcceptSession(out int sessionHandle, int portHandle); + Result ReplyAndReceive(out int handleIndex, ReadOnlySpan handles, int replyTargetHandle, long timeout); + + Result CreateEvent(out int writableHandle, out int readableHandle); + Result SignalEvent(int handle); + Result ClearEvent(int handle); + Result ResetSignal(int handle); + + Result CreatePort(out int serverPortHandle, out int clientPortHandle, int maxSessions, bool isLight, string name); + Result ManageNamedPort(out int handle, string name, int maxSessions); + Result ConnectToPort(out int clientSessionHandle, int clientPortHandle); + + IExternalEvent GetExternalEvent(int handle); + IVirtualMemoryManager GetMemoryManagerByProcessHandle(int handle); + ulong GetTransferMemoryAddress(int handle); + } +} diff --git a/src/Ryujinx.Horizon.Common/IThreadContext.cs b/src/Ryujinx.Horizon.Common/IThreadContext.cs new file mode 100644 index 00000000..6a2b71b6 --- /dev/null +++ b/src/Ryujinx.Horizon.Common/IThreadContext.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Horizon.Common +{ + public interface IThreadContext + { + bool Running { get; } + + ulong TlsAddress { get; } + + ulong GetX(int index); + } +} diff --git a/src/Ryujinx.Horizon.Common/InvalidResultException.cs b/src/Ryujinx.Horizon.Common/InvalidResultException.cs new file mode 100644 index 00000000..80621f45 --- /dev/null +++ b/src/Ryujinx.Horizon.Common/InvalidResultException.cs @@ -0,0 +1,23 @@ +using System; + +namespace Ryujinx.Horizon.Common +{ + public class InvalidResultException : Exception + { + public InvalidResultException() + { + } + + public InvalidResultException(Result result) : base($"Unexpected result code {result} returned.") + { + } + + public InvalidResultException(string message) : base(message) + { + } + + public InvalidResultException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/Ryujinx.Horizon.Common/KernelResult.cs b/src/Ryujinx.Horizon.Common/KernelResult.cs new file mode 100644 index 00000000..37d9fb0a --- /dev/null +++ b/src/Ryujinx.Horizon.Common/KernelResult.cs @@ -0,0 +1,39 @@ +namespace Ryujinx.Horizon.Common +{ + public static class KernelResult + { + private const int ModuleId = 1; + + public static Result SessionCountExceeded => new(ModuleId, 7); + public static Result InvalidCapability => new(ModuleId, 14); + public static Result ThreadNotStarted => new(ModuleId, 57); + public static Result ThreadTerminating => new(ModuleId, 59); + public static Result InvalidSize => new(ModuleId, 101); + public static Result InvalidAddress => new(ModuleId, 102); + public static Result OutOfResource => new(ModuleId, 103); + public static Result OutOfMemory => new(ModuleId, 104); + public static Result HandleTableFull => new(ModuleId, 105); + public static Result InvalidMemState => new(ModuleId, 106); + public static Result InvalidPermission => new(ModuleId, 108); + public static Result InvalidMemRange => new(ModuleId, 110); + public static Result InvalidPriority => new(ModuleId, 112); + public static Result InvalidCpuCore => new(ModuleId, 113); + public static Result InvalidHandle => new(ModuleId, 114); + public static Result UserCopyFailed => new(ModuleId, 115); + public static Result InvalidCombination => new(ModuleId, 116); + public static Result TimedOut => new(ModuleId, 117); + public static Result Cancelled => new(ModuleId, 118); + public static Result MaximumExceeded => new(ModuleId, 119); + public static Result InvalidEnumValue => new(ModuleId, 120); + public static Result NotFound => new(ModuleId, 121); + public static Result InvalidThread => new(ModuleId, 122); + public static Result PortRemoteClosed => new(ModuleId, 123); + public static Result InvalidState => new(ModuleId, 125); + public static Result ReservedValue => new(ModuleId, 126); + public static Result PortClosed => new(ModuleId, 131); + public static Result ResLimitExceeded => new(ModuleId, 132); + public static Result ReceiveListBroken => new(ModuleId, 258); + public static Result OutOfVaSpace => new(ModuleId, 259); + public static Result CmdBufferTooSmall => new(ModuleId, 260); + } +} diff --git a/src/Ryujinx.Horizon.Common/OnScopeExit.cs b/src/Ryujinx.Horizon.Common/OnScopeExit.cs new file mode 100644 index 00000000..aebaf3ba --- /dev/null +++ b/src/Ryujinx.Horizon.Common/OnScopeExit.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ryujinx.Horizon.Common +{ + public readonly struct OnScopeExit : IDisposable + { + private readonly Action _action; + + public OnScopeExit(Action action) + { + _action = action; + } + + public void Dispose() + { + _action(); + } + } +} diff --git a/src/Ryujinx.Horizon.Common/Result.cs b/src/Ryujinx.Horizon.Common/Result.cs new file mode 100644 index 00000000..4b120b84 --- /dev/null +++ b/src/Ryujinx.Horizon.Common/Result.cs @@ -0,0 +1,123 @@ +using System; + +namespace Ryujinx.Horizon.Common +{ + public struct Result : IEquatable + { + private const int ModuleBits = 9; + private const int DescriptionBits = 13; + private const int ModuleMax = 1 << ModuleBits; + private const int DescriptionMax = 1 << DescriptionBits; + + public static Result Success { get; } = new Result(0, 0); + + public int ErrorCode { get; } + + public readonly bool IsSuccess => ErrorCode == 0; + public readonly bool IsFailure => ErrorCode != 0; + + public readonly int Module => ErrorCode & (ModuleMax - 1); + public readonly int Description => (ErrorCode >> ModuleBits) & (DescriptionMax - 1); + + public readonly string PrintableResult => $"{2000 + Module:D4}-{Description:D4}"; + + public Result(int module, int description) + { + if ((uint)module >= ModuleMax) + { + throw new ArgumentOutOfRangeException(nameof(module)); + } + + if ((uint)description >= DescriptionMax) + { + throw new ArgumentOutOfRangeException(nameof(description)); + } + + ErrorCode = module | (description << ModuleBits); + } + + public Result(int errorCode) + { + ErrorCode = errorCode; + } + + public readonly override bool Equals(object obj) + { + return obj is Result result && result.Equals(this); + } + + public readonly bool Equals(Result other) + { + return other.ErrorCode == ErrorCode; + } + + public readonly override int GetHashCode() + { + return ErrorCode; + } + + public static bool operator ==(Result lhs, Result rhs) + { + return lhs.Equals(rhs); + } + + public static bool operator !=(Result lhs, Result rhs) + { + return !lhs.Equals(rhs); + } + + public readonly bool InRange(int minInclusive, int maxInclusive) + { + return (uint)(Description - minInclusive) <= (uint)(maxInclusive - minInclusive); + } + + public void AbortOnSuccess() + { + if (IsSuccess) + { + ThrowInvalidResult(); + } + } + + public void AbortOnFailure() + { + if (this == KernelResult.ThreadTerminating) + { + throw new ThreadTerminatedException(); + } + + AbortUnless(Success); + } + + public void AbortUnless(Result result) + { + if (this != result) + { + ThrowInvalidResult(); + } + } + + public void AbortUnless(Result result, Result result2) + { + if (this != result && this != result2) + { + ThrowInvalidResult(); + } + } + + private void ThrowInvalidResult() + { + throw new InvalidResultException(this); + } + + public readonly override string ToString() + { + if (ResultNames.TryGet(ErrorCode, out string name)) + { + return name; + } + + return PrintableResult; + } + } +} diff --git a/src/Ryujinx.Horizon.Common/ResultNames.cs b/src/Ryujinx.Horizon.Common/ResultNames.cs new file mode 100644 index 00000000..55a33d68 --- /dev/null +++ b/src/Ryujinx.Horizon.Common/ResultNames.cs @@ -0,0 +1,1701 @@ +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Common +{ + static class ResultNames + { + // Reference: https://github.com/Thealexbarney/LibHac/blob/master/build/CodeGen/results.csv + private static readonly IReadOnlyDictionary _names = new Dictionary() + { + { 0x0, "Success" }, + { 0xE01, "OutOfSessions" }, + { 0x1C01, "InvalidArgument" }, + { 0x4201, "NotImplemented" }, + { 0x6C01, "StopProcessingException" }, + { 0x7201, "NoSynchronizationObject" }, + { 0x7601, "TerminationRequested" }, + { 0x8C01, "NoEvent" }, + { 0xCA01, "InvalidSize" }, + { 0xCC01, "InvalidAddress" }, + { 0xCE01, "OutOfResource" }, + { 0xD001, "OutOfMemory" }, + { 0xD201, "OutOfHandles" }, + { 0xD401, "InvalidCurrentMemory" }, + { 0xD801, "InvalidNewMemoryPermission" }, + { 0xDC01, "InvalidMemoryRegion" }, + { 0xE001, "InvalidPriority" }, + { 0xE201, "InvalidCoreId" }, + { 0xE401, "InvalidHandle" }, + { 0xE601, "InvalidPointer" }, + { 0xE801, "InvalidCombination" }, + { 0xEA01, "TimedOut" }, + { 0xEC01, "Cancelled" }, + { 0xEE01, "OutOfRange" }, + { 0xF001, "InvalidEnumValue" }, + { 0xF201, "NotFound" }, + { 0xF401, "Busy" }, + { 0xF601, "SessionClosed" }, + { 0xF801, "NotHandled" }, + { 0xFA01, "InvalidState" }, + { 0xFC01, "ReservedUsed" }, + { 0xFE01, "NotSupported" }, + { 0x10001, "Debug" }, + { 0x10201, "NoThread" }, + { 0x10401, "UnknownThread" }, + { 0x10601, "PortClosed" }, + { 0x10801, "LimitReached" }, + { 0x10A01, "InvalidMemoryPool" }, + { 0x20401, "ReceiveListBroken" }, + { 0x20601, "OutOfAddressSpace" }, + { 0x20801, "MessageTooLarge" }, + { 0x40A01, "InvalidProcessId" }, + { 0x40C01, "InvalidThreadId" }, + { 0x40E01, "InvalidId" }, + { 0x41001, "ProcessTerminated" }, + { 0x2, "HandledByAllProcess" }, + { 0x202, "PathNotFound" }, + { 0x402, "PathAlreadyExists" }, + { 0x1002, "DirectoryNotEmpty" }, + { 0x1A02, "DirectoryStatusChanged" }, + { 0x3C02, "UsableSpaceNotEnough" }, + { 0x3E02, "UsableSpaceNotEnoughForSaveData" }, + { 0x4002, "UsableSpaceNotEnoughForSaveDataEvenAssistanceSuccess" }, + { 0x4202, "UsableSpaceNotEnoughForCacheStorage" }, + { 0x4402, "UsableSpaceNotEnoughMmc" }, + { 0x4602, "UsableSpaceNotEnoughMmcCalibration" }, + { 0x4802, "UsableSpaceNotEnoughMmcSafe" }, + { 0x4A02, "UsableSpaceNotEnoughMmcUser" }, + { 0x4C02, "UsableSpaceNotEnoughMmcSystem" }, + { 0x4E02, "UsableSpaceNotEnoughSdCard" }, + { 0x6402, "UnsupportedSdkVersion" }, + { 0x7802, "MountNameAlreadyExists" }, + { 0x8C02, "IndividualFileDataCacheAlreadyEnabled" }, + { 0x7D002, "HandledBySystemProcess" }, + { 0x7D202, "PartitionNotFound" }, + { 0x7D402, "TargetNotFound" }, + { 0x7D602, "HasNotGottenPatrolCount" }, + { 0x7D802, "NcaExternalKeyUnregistered" }, + { 0xFA002, "SdCardAccessFailed" }, + { 0xFA202, "PortSdCardNoDevice" }, + { 0xFA402, "PortSdCardNotActivated" }, + { 0xFA602, "PortSdCardDeviceRemoved" }, + { 0xFA802, "PortSdCardNotAwakened" }, + { 0xFE002, "PortSdCardCommunicationError" }, + { 0xFE202, "PortSdCardCommunicationNotAttained" }, + { 0xFE402, "PortSdCardResponseIndexError" }, + { 0xFE602, "PortSdCardResponseEndBitError" }, + { 0xFE802, "PortSdCardResponseCrcError" }, + { 0xFEA02, "PortSdCardResponseTimeoutError" }, + { 0xFEC02, "PortSdCardDataEndBitError" }, + { 0xFEE02, "PortSdCardDataCrcError" }, + { 0xFF002, "PortSdCardDataTimeoutError" }, + { 0xFF202, "PortSdCardAutoCommandResponseIndexError" }, + { 0xFF402, "PortSdCardAutoCommandResponseEndBitError" }, + { 0xFF602, "PortSdCardAutoCommandResponseCrcError" }, + { 0xFF802, "PortSdCardAutoCommandResponseTimeoutError" }, + { 0xFFA02, "PortSdCardCommandCompleteSwTimeout" }, + { 0xFFC02, "PortSdCardTransferCompleteSwTimeout" }, + { 0x100002, "PortSdCardDeviceStatusHasError" }, + { 0x100202, "PortSdCardDeviceStatusAddressOutOfRange" }, + { 0x100402, "PortSdCardDeviceStatusAddressMisalign" }, + { 0x100602, "PortSdCardDeviceStatusBlockLenError" }, + { 0x100802, "PortSdCardDeviceStatusEraseSeqError" }, + { 0x100A02, "PortSdCardDeviceStatusEraseParam" }, + { 0x100C02, "PortSdCardDeviceStatusWpViolation" }, + { 0x100E02, "PortSdCardDeviceStatusLockUnlockFailed" }, + { 0x101002, "PortSdCardDeviceStatusComCrcError" }, + { 0x101202, "PortSdCardDeviceStatusIllegalCommand" }, + { 0x101402, "PortSdCardDeviceStatusDeviceEccFailed" }, + { 0x101602, "PortSdCardDeviceStatusCcError" }, + { 0x101802, "PortSdCardDeviceStatusError" }, + { 0x101A02, "PortSdCardDeviceStatusCidCsdOverwrite" }, + { 0x101C02, "PortSdCardDeviceStatusWpEraseSkip" }, + { 0x101E02, "PortSdCardDeviceStatusEraseReset" }, + { 0x102002, "PortSdCardDeviceStatusSwitchError" }, + { 0x103002, "PortSdCardUnexpectedDeviceState" }, + { 0x103202, "PortSdCardUnexpectedDeviceCsdValue" }, + { 0x103402, "PortSdCardAbortTransactionSwTimeout" }, + { 0x103602, "PortSdCardCommandInhibitCmdSwTimeout" }, + { 0x103802, "PortSdCardCommandInhibitDatSwTimeout" }, + { 0x103A02, "PortSdCardBusySwTimeout" }, + { 0x103C02, "PortSdCardIssueTuningCommandSwTimeout" }, + { 0x103E02, "PortSdCardTuningFailed" }, + { 0x104002, "PortSdCardMmcInitializationSwTimeout" }, + { 0x104202, "PortSdCardMmcNotSupportExtendedCsd" }, + { 0x104402, "PortSdCardUnexpectedMmcExtendedCsdValue" }, + { 0x104602, "PortSdCardMmcEraseSwTimeout" }, + { 0x104802, "PortSdCardSdCardValidationError" }, + { 0x104A02, "PortSdCardSdCardInitializationSwTimeout" }, + { 0x104C02, "PortSdCardSdCardGetValidRcaSwTimeout" }, + { 0x104E02, "PortSdCardUnexpectedSdCardAcmdDisabled" }, + { 0x105002, "PortSdCardSdCardNotSupportSwitchFunctionStatus" }, + { 0x105202, "PortSdCardUnexpectedSdCardSwitchFunctionStatus" }, + { 0x105402, "PortSdCardSdCardNotSupportAccessMode" }, + { 0x105602, "PortSdCardSdCardNot4BitBusWidthAtUhsIMode" }, + { 0x105802, "PortSdCardSdCardNotSupportSdr104AndSdr50" }, + { 0x105A02, "PortSdCardSdCardCannotSwitchedAccessMode" }, + { 0x105C02, "PortSdCardSdCardFailedSwitchedAccessMode" }, + { 0x105E02, "PortSdCardSdCardUnacceptableCurrentConsumption" }, + { 0x106002, "PortSdCardSdCardNotReadyToVoltageSwitch" }, + { 0x106202, "PortSdCardSdCardNotCompleteVoltageSwitch" }, + { 0x10A002, "PortSdCardHostControllerUnexpected" }, + { 0x10A202, "PortSdCardInternalClockStableSwTimeout" }, + { 0x10A402, "PortSdCardSdHostStandardUnknownAutoCmdError" }, + { 0x10A602, "PortSdCardSdHostStandardUnknownError" }, + { 0x10A802, "PortSdCardSdmmcDllCalibrationSwTimeout" }, + { 0x10AA02, "PortSdCardSdmmcDllApplicationSwTimeout" }, + { 0x10AC02, "PortSdCardSdHostStandardFailSwitchTo18V" }, + { 0x10E002, "PortSdCardInternalError" }, + { 0x10E202, "PortSdCardNoWaitedInterrupt" }, + { 0x10E402, "PortSdCardWaitInterruptSwTimeout" }, + { 0x112002, "PortSdCardAbortCommandIssued" }, + { 0x113002, "PortSdCardNotSupported" }, + { 0x113202, "PortSdCardNotImplemented" }, + { 0x138002, "PortSdCardStorageDeviceInvalidated" }, + { 0x138202, "PortSdCardWriteVerifyError" }, + { 0x138402, "PortSdCardFileSystemInvalidatedByRemoved" }, + { 0x138602, "PortSdCardUnexpected" }, + { 0x138802, "GameCardAccessFailed" }, + { 0x138A02, "GameCardUnknown" }, + { 0x138C02, "GameCardUnexpectedDeadCode" }, + { 0x138E02, "GameCardPreconditionViolation" }, + { 0x139002, "GameCardNotImplemented" }, + { 0x139C02, "GameCardQueueFullFailure" }, + { 0x139E02, "GameCardLockerOutOfRange" }, + { 0x13A802, "GameCardFailedIoMappingForGpio" }, + { 0x13B002, "GameCardCardNotInserted" }, + { 0x13B202, "GameCardCardIdMismatch" }, + { 0x13B402, "GameCardCardNotActivated" }, + { 0x13B602, "GameCardNotAwakened" }, + { 0x13C402, "GameCardCardAccessFailure" }, + { 0x13C602, "GameCardCardAccessTimeout" }, + { 0x13C802, "GameCardCardFatal" }, + { 0x13CA02, "GameCardCardNeedRetry" }, + { 0x13CC02, "GameCardCardRetryFailure" }, + { 0x13D002, "GameCardRetryLimitOut" }, + { 0x13D202, "GameCardNeedRefresh" }, + { 0x13D402, "GameCardNeedRefreshAndCardNeedRetry" }, + { 0x13D802, "GameCardInvalidSecureAccess" }, + { 0x13DA02, "GameCardInvalidNormalAccess" }, + { 0x13DC02, "GameCardInvalidAccessAcrossMode" }, + { 0x13DE02, "GameCardWrongCard" }, + { 0x13E002, "GameCardInitialDataMismatch" }, + { 0x13E202, "GameCardInitialNotFilledWithZero" }, + { 0x13E402, "GameCardKekIndexMismatch" }, + { 0x13E802, "GameCardInvalidGetCardDeviceCertificate" }, + { 0x13EA02, "GameCardUnregisteredCardSecureMethod" }, + { 0x13EC02, "GameCardCardNeedRetryAfterAsicReinitialize" }, + { 0x13EE02, "GameCardCardHeaderReadFailure" }, + { 0x13F002, "GameCardCardReinitializeFailure" }, + { 0x13F202, "GameCardInvalidChallengeCardExistenceMode" }, + { 0x13F402, "GameCardInvalidCardHeader" }, + { 0x13F602, "GameCardInvalidT1CardCertificate" }, + { 0x13FA02, "GameCardInvalidCa10Certificate" }, + { 0x13FC02, "GameCardInvalidCa10CardHeader" }, + { 0x140A02, "GameCardCommunicationFailure" }, + { 0x140C02, "GameCardFinishOperationFailed" }, + { 0x144A02, "GameCardStateTransitionFailure" }, + { 0x144C02, "GameCardAlreadyTransitionedState" }, + { 0x144E02, "GameCardShouldTransitFromAsicInitialToSecure" }, + { 0x145002, "GameCardShouldTransitFromInitialToNormal" }, + { 0x145202, "GameCardShouldTransitFromNormalModeToSecure" }, + { 0x145402, "GameCardShouldTransitFromNormalModeToDebug" }, + { 0x148A02, "GameCardInitializeAsicFailure" }, + { 0x148C02, "GameCardAlreadyInitializedAsic" }, + { 0x148E02, "GameCardActivateAsicFailure" }, + { 0x149002, "GameCardAsicBootFailure" }, + { 0x149402, "GameCardSendFirmwareFailure" }, + { 0x149802, "GameCardVerifyCertificateFailure" }, + { 0x149A02, "GameCardReceiveCertificateFailure" }, + { 0x149C02, "GameCardParseCertificateFailure" }, + { 0x149E02, "GameCardInvalidCertificate" }, + { 0x14A002, "GameCardSendSocCertificateFailure" }, + { 0x14A802, "GameCardGenerateCommonKeyFailure" }, + { 0x14AA02, "GameCardReceiveRandomValueFailure" }, + { 0x14AC02, "GameCardSendRandomValueFailure" }, + { 0x14AE02, "GameCardDecryptRandomValueFailure" }, + { 0x14B402, "GameCardAuthenticateMutuallyFailure" }, + { 0x14B602, "GameCardReceiveDeviceChallengeFailure" }, + { 0x14B802, "GameCardRespondDeviceChallengeFailure" }, + { 0x14BA02, "GameCardSendHostChallengeFailure" }, + { 0x14BC02, "GameCardReceiveChallengeResponseFailure" }, + { 0x14BE02, "GameCardChallengeAndResponseFailure" }, + { 0x14C402, "GameCardChangeModeToSecureFailure" }, + { 0x14C602, "GameCardExchangeRandomValuesFailure" }, + { 0x14C802, "GameCardAsicChallengeCardExistenceFailure" }, + { 0x14CE02, "GameCardInitializeAsicTimeOut" }, + { 0x14D202, "GameCardSplFailure" }, + { 0x14D402, "GameCardSplDecryptAesKeyFailure" }, + { 0x14D602, "GameCardSplDecryptAndStoreGcKeyFailure" }, + { 0x14D802, "GameCardSplGenerateRandomBytesFailure" }, + { 0x14DA02, "GameCardSplDecryptGcMessageFailure" }, + { 0x14DE02, "GameCardReadRegisterFailure" }, + { 0x14E002, "GameCardWriteRegisterFailure" }, + { 0x14E202, "GameCardEnableCardBusFailure" }, + { 0x14E402, "GameCardGetCardHeaderFailure" }, + { 0x14E602, "GameCardAsicStatusError" }, + { 0x14E802, "GameCardChangeGcModeToSecureFailure" }, + { 0x14EA02, "GameCardChangeGcModeToDebugFailure" }, + { 0x14EC02, "GameCardReadRmaInfoFailure" }, + { 0x14F002, "GameCardUpdateKeyFailure" }, + { 0x14F202, "GameCardKeySourceNotFound" }, + { 0x150402, "GameCardStateFailure" }, + { 0x150602, "GameCardStateCardNormalModeRequired" }, + { 0x150802, "GameCardStateCardSecureModeRequired" }, + { 0x150A02, "GameCardStateCardDebugModeRequired" }, + { 0x150C02, "GameCardStateAsicInitialRequired" }, + { 0x150E02, "GameCardStateAsicSecureRequired" }, + { 0x151802, "GameCardGeneralIoFailure" }, + { 0x151A02, "GameCardGeneralIoReleaseAsicResetFailure" }, + { 0x151C02, "GameCardGeneralIoHoldAsicResetFailure" }, + { 0x151E02, "GameCardSetVoltageFailure" }, + { 0x152C02, "GameCardDataIoFailure" }, + { 0x152E02, "GameCardDataIoActivateFailure" }, + { 0x155402, "GameCardCardCommandFailure" }, + { 0x155602, "GameCardCommandReadId1Failure" }, + { 0x155802, "GameCardCommandReadId2Failure" }, + { 0x155A02, "GameCardCommandReadId3Failure" }, + { 0x155C02, "GameCardSendCardReadUidFailure" }, + { 0x155E02, "GameCardCommandReadPageFailure" }, + { 0x156002, "GameCardCommandReadPageUnalignedFailure" }, + { 0x156202, "GameCardCommandWritePageFailure" }, + { 0x156402, "GameCardCommandRefreshFailure" }, + { 0x156602, "GameCardCommandUpdateKeyFailure" }, + { 0x156802, "GameCardSendCardSelfRefreshFailure" }, + { 0x156A02, "GameCardSendCardReadRefreshStatusFailure" }, + { 0x156C02, "GameCardCommandReadCrcFailure" }, + { 0x156E02, "GameCardCommandEraseFailure" }, + { 0x157002, "GameCardCommandReadDevParamFailure" }, + { 0x157202, "GameCardCommandWriteDevParamFailure" }, + { 0x157402, "GameCardSendCardReadErrorCountFailure" }, + { 0x16A802, "GameCardDevCardUnexpectedFailure" }, + { 0x16AA02, "GameCardDebugParameterMismatch" }, + { 0x16AC02, "GameCardDebugEraseFailure" }, + { 0x16AE02, "GameCardDebugWriteCrcMismatch" }, + { 0x16B002, "GameCardDebugCardReceivedIdMismatch" }, + { 0x16B202, "GameCardDebugCardId1Mismatch" }, + { 0x16B402, "GameCardDebugCardId2Mismatch" }, + { 0x170C02, "GameCardFsFailure" }, + { 0x170E02, "GameCardFsGetHandleFailure" }, + { 0x171002, "GameCardFsCheckHandleInReadFailure" }, + { 0x171202, "GameCardFsCheckHandleInWriteFailure" }, + { 0x171402, "GameCardFsCheckHandleInGetStatusFailure" }, + { 0x171602, "GameCardFsCheckHandleInGetDeviceCertFailure" }, + { 0x171802, "GameCardFsCheckHandleInGetCardImageHashFailure" }, + { 0x171A02, "GameCardFsCheckHandleInChallengeCardExistence" }, + { 0x171C02, "GameCardFsCheckHandleInOnAcquireLock" }, + { 0x171E02, "GameCardFsCheckModeInOnAcquireSecureLock" }, + { 0x172002, "GameCardFsCheckHandleInCreateReadOnlyFailure" }, + { 0x172202, "GameCardFsCheckHandleInCreateSecureReadOnlyFailure" }, + { 0x172402, "GameCardFsInvalidCompatibilityType" }, + { 0x172602, "GameCardNotSupportedOnDeviceModel" }, + { 0x177002, "Internal" }, + { 0x177202, "NotImplemented" }, + { 0x177402, "UnsupportedVersion" }, + { 0x177602, "AlreadyExists" }, + { 0x177A02, "OutOfRange" }, + { 0x183602, "StorageDeviceInvalidOperation" }, + { 0x183802, "SystemPartitionNotReady" }, + { 0x183A02, "StorageDeviceNotReady" }, + { 0x190002, "AllocationMemoryFailed" }, + { 0x190202, "AllocationMemoryFailedInFatFileSystemA" }, + { 0x190602, "AllocationMemoryFailedInFatFileSystemC" }, + { 0x190802, "AllocationMemoryFailedInFatFileSystemD" }, + { 0x190A02, "AllocationMemoryFailedInFatFileSystemE" }, + { 0x190C02, "AllocationMemoryFailedInFatFileSystemF" }, + { 0x191002, "AllocationMemoryFailedInFatFileSystemH" }, + { 0x191602, "AllocationMemoryFailedInFileSystemAccessorA" }, + { 0x191802, "AllocationMemoryFailedInFileSystemAccessorB" }, + { 0x191A02, "AllocationMemoryFailedInApplicationA" }, + { 0x191C02, "AllocationMemoryFailedInBcatSaveDataA" }, + { 0x191E02, "AllocationMemoryFailedInBisA" }, + { 0x192002, "AllocationMemoryFailedInBisB" }, + { 0x192202, "AllocationMemoryFailedInBisC" }, + { 0x192402, "AllocationMemoryFailedInCodeA" }, + { 0x192602, "AllocationMemoryFailedInContentA" }, + { 0x192802, "AllocationMemoryFailedInContentStorageA" }, + { 0x192A02, "AllocationMemoryFailedInContentStorageB" }, + { 0x192C02, "AllocationMemoryFailedInDataA" }, + { 0x192E02, "AllocationMemoryFailedInDataB" }, + { 0x193002, "AllocationMemoryFailedInDeviceSaveDataA" }, + { 0x193202, "AllocationMemoryFailedInGameCardA" }, + { 0x193402, "AllocationMemoryFailedInGameCardB" }, + { 0x193602, "AllocationMemoryFailedInGameCardC" }, + { 0x193802, "AllocationMemoryFailedInGameCardD" }, + { 0x193A02, "AllocationMemoryFailedInHostA" }, + { 0x193C02, "AllocationMemoryFailedInHostB" }, + { 0x193E02, "AllocationMemoryFailedInHostC" }, + { 0x194002, "AllocationMemoryFailedInImageDirectoryA" }, + { 0x194202, "AllocationMemoryFailedInLogoA" }, + { 0x194402, "AllocationMemoryFailedInRomA" }, + { 0x194602, "AllocationMemoryFailedInRomB" }, + { 0x194802, "AllocationMemoryFailedInRomC" }, + { 0x194A02, "AllocationMemoryFailedInRomD" }, + { 0x194C02, "AllocationMemoryFailedInRomE" }, + { 0x194E02, "AllocationMemoryFailedInRomF" }, + { 0x195402, "AllocationMemoryFailedInSaveDataManagementA" }, + { 0x195602, "AllocationMemoryFailedInSaveDataThumbnailA" }, + { 0x195802, "AllocationMemoryFailedInSdCardA" }, + { 0x195A02, "AllocationMemoryFailedInSdCardB" }, + { 0x195C02, "AllocationMemoryFailedInSystemSaveDataA" }, + { 0x195E02, "AllocationMemoryFailedInRomFsFileSystemA" }, + { 0x196002, "AllocationMemoryFailedInRomFsFileSystemB" }, + { 0x196202, "AllocationMemoryFailedInRomFsFileSystemC" }, + { 0x196602, "AllocationMemoryFailedInGuidPartitionTableA" }, + { 0x196802, "AllocationMemoryFailedInDeviceDetectionEventManagerA" }, + { 0x196A02, "AllocationMemoryFailedInSaveDataFileSystemServiceImplA" }, + { 0x196C02, "AllocationMemoryFailedInFileSystemProxyCoreImplB" }, + { 0x196E02, "AllocationMemoryFailedInSdCardProxyFileSystemCreatorA" }, + { 0x197002, "AllocationMemoryFailedInNcaFileSystemServiceImplA" }, + { 0x197202, "AllocationMemoryFailedInNcaFileSystemServiceImplB" }, + { 0x197402, "AllocationMemoryFailedInProgramRegistryManagerA" }, + { 0x197602, "AllocationMemoryFailedInSdmmcStorageServiceA" }, + { 0x197802, "AllocationMemoryFailedInBuiltInStorageCreatorA" }, + { 0x197A02, "AllocationMemoryFailedInBuiltInStorageCreatorB" }, + { 0x197C02, "AllocationMemoryFailedInBuiltInStorageCreatorC" }, + { 0x198002, "AllocationMemoryFailedFatFileSystemWithBufferA" }, + { 0x198202, "AllocationMemoryFailedInFatFileSystemCreatorA" }, + { 0x198402, "AllocationMemoryFailedInFatFileSystemCreatorB" }, + { 0x198602, "AllocationMemoryFailedInGameCardFileSystemCreatorA" }, + { 0x198802, "AllocationMemoryFailedInGameCardFileSystemCreatorB" }, + { 0x198A02, "AllocationMemoryFailedInGameCardFileSystemCreatorC" }, + { 0x198C02, "AllocationMemoryFailedInGameCardFileSystemCreatorD" }, + { 0x198E02, "AllocationMemoryFailedInGameCardFileSystemCreatorE" }, + { 0x199002, "AllocationMemoryFailedInGameCardFileSystemCreatorF" }, + { 0x199202, "AllocationMemoryFailedInGameCardManagerA" }, + { 0x199402, "AllocationMemoryFailedInGameCardManagerB" }, + { 0x199602, "AllocationMemoryFailedInGameCardManagerC" }, + { 0x199802, "AllocationMemoryFailedInGameCardManagerD" }, + { 0x199A02, "AllocationMemoryFailedInGameCardManagerE" }, + { 0x199C02, "AllocationMemoryFailedInGameCardManagerF" }, + { 0x199E02, "AllocationMemoryFailedInLocalFileSystemCreatorA" }, + { 0x19A002, "AllocationMemoryFailedInPartitionFileSystemCreatorA" }, + { 0x19A202, "AllocationMemoryFailedInRomFileSystemCreatorA" }, + { 0x19A402, "AllocationMemoryFailedInSaveDataFileSystemCreatorA" }, + { 0x19A602, "AllocationMemoryFailedInSaveDataFileSystemCreatorB" }, + { 0x19A802, "AllocationMemoryFailedInSaveDataFileSystemCreatorC" }, + { 0x19AA02, "AllocationMemoryFailedInSaveDataFileSystemCreatorD" }, + { 0x19AC02, "AllocationMemoryFailedInSaveDataFileSystemCreatorE" }, + { 0x19B002, "AllocationMemoryFailedInStorageOnNcaCreatorA" }, + { 0x19B202, "AllocationMemoryFailedInStorageOnNcaCreatorB" }, + { 0x19B402, "AllocationMemoryFailedInSubDirectoryFileSystemCreatorA" }, + { 0x19B602, "AllocationMemoryFailedInTargetManagerFileSystemCreatorA" }, + { 0x19B802, "AllocationMemoryFailedInSaveDataIndexerA" }, + { 0x19BA02, "AllocationMemoryFailedInSaveDataIndexerB" }, + { 0x19BC02, "AllocationMemoryFailedInFileSystemBuddyHeapA" }, + { 0x19BE02, "AllocationMemoryFailedInFileSystemBufferManagerA" }, + { 0x19C002, "AllocationMemoryFailedInBlockCacheBufferedStorageA" }, + { 0x19C202, "AllocationMemoryFailedInBlockCacheBufferedStorageB" }, + { 0x19C402, "AllocationMemoryFailedInDuplexStorageA" }, + { 0x19D002, "AllocationMemoryFailedInIntegrityVerificationStorageA" }, + { 0x19D202, "AllocationMemoryFailedInIntegrityVerificationStorageB" }, + { 0x19D402, "AllocationMemoryFailedInJournalStorageA" }, + { 0x19D602, "AllocationMemoryFailedInJournalStorageB" }, + { 0x19DC02, "AllocationMemoryFailedInSaveDataFileSystemCoreA" }, + { 0x19DE02, "AllocationMemoryFailedInSaveDataFileSystemCoreB" }, + { 0x19E002, "AllocationMemoryFailedInAesXtsFileA" }, + { 0x19E202, "AllocationMemoryFailedInAesXtsFileB" }, + { 0x19E402, "AllocationMemoryFailedInAesXtsFileC" }, + { 0x19E602, "AllocationMemoryFailedInAesXtsFileD" }, + { 0x19E802, "AllocationMemoryFailedInAesXtsFileSystemA" }, + { 0x19EE02, "AllocationMemoryFailedInConcatenationFileSystemA" }, + { 0x19F002, "AllocationMemoryFailedInConcatenationFileSystemB" }, + { 0x19F202, "AllocationMemoryFailedInDirectorySaveDataFileSystemA" }, + { 0x19F402, "AllocationMemoryFailedInLocalFileSystemA" }, + { 0x19F602, "AllocationMemoryFailedInLocalFileSystemB" }, + { 0x1A1A02, "AllocationMemoryFailedInNcaFileSystemDriverI" }, + { 0x1A2602, "AllocationMemoryFailedInPartitionFileSystemA" }, + { 0x1A2802, "AllocationMemoryFailedInPartitionFileSystemB" }, + { 0x1A2A02, "AllocationMemoryFailedInPartitionFileSystemC" }, + { 0x1A2C02, "AllocationMemoryFailedInPartitionFileSystemMetaA" }, + { 0x1A2E02, "AllocationMemoryFailedInPartitionFileSystemMetaB" }, + { 0x1A3002, "AllocationMemoryFailedInRomFsFileSystemD" }, + { 0x1A3602, "AllocationMemoryFailedInSubdirectoryFileSystemA" }, + { 0x1A3802, "AllocationMemoryFailedInTmFileSystemA" }, + { 0x1A3A02, "AllocationMemoryFailedInTmFileSystemB" }, + { 0x1A3E02, "AllocationMemoryFailedInProxyFileSystemA" }, + { 0x1A4002, "AllocationMemoryFailedInProxyFileSystemB" }, + { 0x1A4402, "AllocationMemoryFailedInSaveDataExtraDataAccessorCacheManagerA" }, + { 0x1A4602, "AllocationMemoryFailedInNcaReaderA" }, + { 0x1A4A02, "AllocationMemoryFailedInRegisterA" }, + { 0x1A4C02, "AllocationMemoryFailedInRegisterB" }, + { 0x1A4E02, "AllocationMemoryFailedInPathNormalizer" }, + { 0x1A5E02, "AllocationMemoryFailedInDbmRomKeyValueStorage" }, + { 0x1A6002, "AllocationMemoryFailedInDbmHierarchicalRomFileTable" }, + { 0x1A6202, "AllocationMemoryFailedInRomFsFileSystemE" }, + { 0x1A6402, "AllocationMemoryFailedInISaveFileSystemA" }, + { 0x1A6602, "AllocationMemoryFailedInISaveFileSystemB" }, + { 0x1A6802, "AllocationMemoryFailedInRomOnFileA" }, + { 0x1A6A02, "AllocationMemoryFailedInRomOnFileB" }, + { 0x1A6C02, "AllocationMemoryFailedInRomOnFileC" }, + { 0x1A6E02, "AllocationMemoryFailedInAesXtsFileE" }, + { 0x1A7002, "AllocationMemoryFailedInAesXtsFileF" }, + { 0x1A7202, "AllocationMemoryFailedInAesXtsFileG" }, + { 0x1A7402, "AllocationMemoryFailedInReadOnlyFileSystemA" }, + { 0x1A8402, "AllocationMemoryFailedInEncryptedFileSystemCreatorA" }, + { 0x1A8E02, "AllocationMemoryFailedInAesCtrCounterExtendedStorageA" }, + { 0x1A9002, "AllocationMemoryFailedInAesCtrCounterExtendedStorageB" }, + { 0x1A9C02, "AllocationMemoryFailedInSdmmcStorageServiceB" }, + { 0x1A9E02, "AllocationMemoryFailedInFileSystemInterfaceAdapterA" }, + { 0x1AA002, "AllocationMemoryFailedInGameCardFileSystemCreatorG" }, + { 0x1AA202, "AllocationMemoryFailedInGameCardFileSystemCreatorH" }, + { 0x1AA402, "AllocationMemoryFailedInAesXtsFileSystemB" }, + { 0x1AA602, "AllocationMemoryFailedInBufferedStorageA" }, + { 0x1AA802, "AllocationMemoryFailedInIntegrityRomFsStorageA" }, + { 0x1AB002, "AllocationMemoryFailedInSaveDataFileSystemServiceImplB" }, + { 0x1AB802, "AllocationMemoryFailedNew" }, + { 0x1ABA02, "AllocationMemoryFailedInFileSystemProxyImplA" }, + { 0x1ABC02, "AllocationMemoryFailedMakeUnique" }, + { 0x1ABE02, "AllocationMemoryFailedAllocateShared" }, + { 0x1AC002, "AllocationPooledBufferNotEnoughSize" }, + { 0x1AC802, "AllocationMemoryFailedInWriteThroughCacheStorageA" }, + { 0x1ACA02, "AllocationMemoryFailedInSaveDataTransferManagerA" }, + { 0x1ACC02, "AllocationMemoryFailedInSaveDataTransferManagerB" }, + { 0x1ACE02, "AllocationMemoryFailedInHtcFileSystemA" }, + { 0x1AD002, "AllocationMemoryFailedInHtcFileSystemB" }, + { 0x1AD202, "AllocationMemoryFailedInGameCardManagerG" }, + { 0x1B5802, "MmcAccessFailed" }, + { 0x1B5A02, "PortMmcNoDevice" }, + { 0x1B5C02, "PortMmcNotActivated" }, + { 0x1B5E02, "PortMmcDeviceRemoved" }, + { 0x1B6002, "PortMmcNotAwakened" }, + { 0x1B9802, "PortMmcCommunicationError" }, + { 0x1B9A02, "PortMmcCommunicationNotAttained" }, + { 0x1B9C02, "PortMmcResponseIndexError" }, + { 0x1B9E02, "PortMmcResponseEndBitError" }, + { 0x1BA002, "PortMmcResponseCrcError" }, + { 0x1BA202, "PortMmcResponseTimeoutError" }, + { 0x1BA402, "PortMmcDataEndBitError" }, + { 0x1BA602, "PortMmcDataCrcError" }, + { 0x1BA802, "PortMmcDataTimeoutError" }, + { 0x1BAA02, "PortMmcAutoCommandResponseIndexError" }, + { 0x1BAC02, "PortMmcAutoCommandResponseEndBitError" }, + { 0x1BAE02, "PortMmcAutoCommandResponseCrcError" }, + { 0x1BB002, "PortMmcAutoCommandResponseTimeoutError" }, + { 0x1BB202, "PortMmcCommandCompleteSwTimeout" }, + { 0x1BB402, "PortMmcTransferCompleteSwTimeout" }, + { 0x1BB802, "PortMmcDeviceStatusHasError" }, + { 0x1BBA02, "PortMmcDeviceStatusAddressOutOfRange" }, + { 0x1BBC02, "PortMmcDeviceStatusAddressMisalign" }, + { 0x1BBE02, "PortMmcDeviceStatusBlockLenError" }, + { 0x1BC002, "PortMmcDeviceStatusEraseSeqError" }, + { 0x1BC202, "PortMmcDeviceStatusEraseParam" }, + { 0x1BC402, "PortMmcDeviceStatusWpViolation" }, + { 0x1BC602, "PortMmcDeviceStatusLockUnlockFailed" }, + { 0x1BC802, "PortMmcDeviceStatusComCrcError" }, + { 0x1BCA02, "PortMmcDeviceStatusIllegalCommand" }, + { 0x1BCC02, "PortMmcDeviceStatusDeviceEccFailed" }, + { 0x1BCE02, "PortMmcDeviceStatusCcError" }, + { 0x1BD002, "PortMmcDeviceStatusError" }, + { 0x1BD202, "PortMmcDeviceStatusCidCsdOverwrite" }, + { 0x1BD402, "PortMmcDeviceStatusWpEraseSkip" }, + { 0x1BD602, "PortMmcDeviceStatusEraseReset" }, + { 0x1BD802, "PortMmcDeviceStatusSwitchError" }, + { 0x1BE802, "PortMmcUnexpectedDeviceState" }, + { 0x1BEA02, "PortMmcUnexpectedDeviceCsdValue" }, + { 0x1BEC02, "PortMmcAbortTransactionSwTimeout" }, + { 0x1BEE02, "PortMmcCommandInhibitCmdSwTimeout" }, + { 0x1BF002, "PortMmcCommandInhibitDatSwTimeout" }, + { 0x1BF202, "PortMmcBusySwTimeout" }, + { 0x1BF402, "PortMmcIssueTuningCommandSwTimeout" }, + { 0x1BF602, "PortMmcTuningFailed" }, + { 0x1BF802, "PortMmcMmcInitializationSwTimeout" }, + { 0x1BFA02, "PortMmcMmcNotSupportExtendedCsd" }, + { 0x1BFC02, "PortMmcUnexpectedMmcExtendedCsdValue" }, + { 0x1BFE02, "PortMmcMmcEraseSwTimeout" }, + { 0x1C0002, "PortMmcSdCardValidationError" }, + { 0x1C0202, "PortMmcSdCardInitializationSwTimeout" }, + { 0x1C0402, "PortMmcSdCardGetValidRcaSwTimeout" }, + { 0x1C0602, "PortMmcUnexpectedSdCardAcmdDisabled" }, + { 0x1C0802, "PortMmcSdCardNotSupportSwitchFunctionStatus" }, + { 0x1C0A02, "PortMmcUnexpectedSdCardSwitchFunctionStatus" }, + { 0x1C0C02, "PortMmcSdCardNotSupportAccessMode" }, + { 0x1C0E02, "PortMmcSdCardNot4BitBusWidthAtUhsIMode" }, + { 0x1C1002, "PortMmcSdCardNotSupportSdr104AndSdr50" }, + { 0x1C1202, "PortMmcSdCardCannotSwitchedAccessMode" }, + { 0x1C1402, "PortMmcSdCardFailedSwitchedAccessMode" }, + { 0x1C1602, "PortMmcSdCardUnacceptableCurrentConsumption" }, + { 0x1C1802, "PortMmcSdCardNotReadyToVoltageSwitch" }, + { 0x1C1A02, "PortMmcSdCardNotCompleteVoltageSwitch" }, + { 0x1C5802, "PortMmcHostControllerUnexpected" }, + { 0x1C5A02, "PortMmcInternalClockStableSwTimeout" }, + { 0x1C5C02, "PortMmcSdHostStandardUnknownAutoCmdError" }, + { 0x1C5E02, "PortMmcSdHostStandardUnknownError" }, + { 0x1C6002, "PortMmcSdmmcDllCalibrationSwTimeout" }, + { 0x1C6202, "PortMmcSdmmcDllApplicationSwTimeout" }, + { 0x1C6402, "PortMmcSdHostStandardFailSwitchTo18V" }, + { 0x1C9802, "PortMmcInternalError" }, + { 0x1C9A02, "PortMmcNoWaitedInterrupt" }, + { 0x1C9C02, "PortMmcWaitInterruptSwTimeout" }, + { 0x1CD802, "PortMmcAbortCommandIssued" }, + { 0x1CE802, "PortMmcNotSupported" }, + { 0x1CEA02, "PortMmcNotImplemented" }, + { 0x1F3C02, "PortMmcStorageDeviceInvalidated" }, + { 0x1F3E02, "PortMmcUnexpected" }, + { 0x1F4002, "DataCorrupted" }, + { 0x1F4202, "RomCorrupted" }, + { 0x1F4402, "UnsupportedRomVersion" }, + { 0x1F5602, "AesCtrCounterExtendedStorageCorrupted" }, + { 0x1F5802, "InvalidAesCtrCounterExtendedEntryOffset" }, + { 0x1F5A02, "InvalidAesCtrCounterExtendedTableSize" }, + { 0x1F5C02, "InvalidAesCtrCounterExtendedGeneration" }, + { 0x1F5E02, "InvalidAesCtrCounterExtendedOffset" }, + { 0x1F6002, "InvalidAesCtrCounterExtendedDataStorageSize" }, + { 0x1F6202, "InvalidAesCtrCounterExtendedMetaStorageSize" }, + { 0x1F6A02, "IndirectStorageCorrupted" }, + { 0x1F6C02, "InvalidIndirectEntryOffset" }, + { 0x1F6E02, "InvalidIndirectEntryStorageIndex" }, + { 0x1F7002, "InvalidIndirectStorageSize" }, + { 0x1F7202, "InvalidIndirectVirtualOffset" }, + { 0x1F7402, "InvalidIndirectPhysicalOffset" }, + { 0x1F7602, "InvalidIndirectStorageIndex" }, + { 0x1F7802, "InvalidIndirectStorageBucketTreeSize" }, + { 0x1F7E02, "BucketTreeCorrupted" }, + { 0x1F8002, "InvalidBucketTreeSignature" }, + { 0x1F8202, "InvalidBucketTreeEntryCount" }, + { 0x1F8402, "InvalidBucketTreeNodeEntryCount" }, + { 0x1F8602, "InvalidBucketTreeNodeOffset" }, + { 0x1F8802, "InvalidBucketTreeEntryOffset" }, + { 0x1F8A02, "InvalidBucketTreeEntrySetOffset" }, + { 0x1F8C02, "InvalidBucketTreeNodeIndex" }, + { 0x1F8E02, "InvalidBucketTreeVirtualOffset" }, + { 0x1F9202, "RomNcaCorrupted" }, + { 0x1FA602, "RomNcaFileSystemCorrupted" }, + { 0x1FA802, "InvalidRomNcaFileSystemType" }, + { 0x1FAA02, "InvalidRomAcidFileSize" }, + { 0x1FAC02, "InvalidRomAcidSize" }, + { 0x1FAE02, "InvalidRomAcid" }, + { 0x1FB002, "RomAcidVerificationFailed" }, + { 0x1FB202, "InvalidRomNcaSignature" }, + { 0x1FB402, "RomNcaHeaderSignature1VerificationFailed" }, + { 0x1FB602, "RomNcaHeaderSignature2VerificationFailed" }, + { 0x1FB802, "RomNcaFsHeaderHashVerificationFailed" }, + { 0x1FBA02, "InvalidRomNcaKeyIndex" }, + { 0x1FBC02, "InvalidRomNcaFsHeaderHashType" }, + { 0x1FBE02, "InvalidRomNcaFsHeaderEncryptionType" }, + { 0x1FC002, "InvalidRomNcaPatchInfoIndirectSize" }, + { 0x1FC202, "InvalidRomNcaPatchInfoAesCtrExSize" }, + { 0x1FC402, "InvalidRomNcaPatchInfoAesCtrExOffset" }, + { 0x1FC602, "InvalidRomNcaId" }, + { 0x1FC802, "InvalidRomNcaHeader" }, + { 0x1FCA02, "InvalidRomNcaFsHeader" }, + { 0x1FCC02, "InvalidRomNcaPatchInfoIndirectOffset" }, + { 0x1FCE02, "RomNcaHierarchicalSha256StorageCorrupted" }, + { 0x1FD002, "InvalidRomHierarchicalSha256BlockSize" }, + { 0x1FD202, "InvalidRomHierarchicalSha256LayerCount" }, + { 0x1FD402, "RomHierarchicalSha256BaseStorageTooLarge" }, + { 0x1FD602, "RomHierarchicalSha256HashVerificationFailed" }, + { 0x1FE202, "InvalidRomHierarchicalIntegrityVerificationLayerCount" }, + { 0x1FE402, "RomNcaIndirectStorageOutOfRange" }, + { 0x1FE602, "RomNcaInvalidCompressionInfo" }, + { 0x205A02, "RomIntegrityVerificationStorageCorrupted" }, + { 0x205C02, "IncorrectRomIntegrityVerificationMagicCode" }, + { 0x205E02, "InvalidRomZeroSignature" }, + { 0x206002, "RomNonRealDataVerificationFailed" }, + { 0x206E02, "RomRealDataVerificationFailed" }, + { 0x207002, "ClearedRomRealDataVerificationFailed" }, + { 0x207202, "UnclearedRomRealDataVerificationFailed" }, + { 0x20AA02, "RomPartitionFileSystemCorrupted" }, + { 0x20AC02, "InvalidRomSha256PartitionHashTarget" }, + { 0x20AE02, "RomSha256PartitionHashVerificationFailed" }, + { 0x20B002, "RomPartitionSignatureVerificationFailed" }, + { 0x20B202, "RomSha256PartitionSignatureVerificationFailed" }, + { 0x20B402, "InvalidRomPartitionEntryOffset" }, + { 0x20B602, "InvalidRomSha256PartitionMetaDataSize" }, + { 0x20D202, "RomBuiltInStorageCorrupted" }, + { 0x20D402, "RomGptHeaderSignatureVerificationFailed" }, + { 0x212202, "RomHostFileSystemCorrupted" }, + { 0x212402, "RomHostEntryCorrupted" }, + { 0x212602, "RomHostFileDataCorrupted" }, + { 0x212802, "RomHostFileCorrupted" }, + { 0x212A02, "InvalidRomHostHandle" }, + { 0x214A02, "RomDatabaseCorrupted" }, + { 0x214C02, "InvalidRomAllocationTableBlock" }, + { 0x214E02, "InvalidRomKeyValueListElementIndex" }, + { 0x217002, "RomStorageCorrupted" }, + { 0x217202, "InvalidRomStorageSize" }, + { 0x219A02, "SaveDataCorrupted" }, + { 0x219C02, "UnsupportedSaveDataVersion" }, + { 0x219E02, "InvalidSaveDataEntryType" }, + { 0x21A002, "ReconstructibleSaveDataCorrupted" }, + { 0x21AE02, "SaveDataFileSystemCorrupted" }, + { 0x21B002, "InvalidJournalIntegritySaveDataHashSize" }, + { 0x21B202, "InvalidJournalIntegritySaveDataCommitState" }, + { 0x21B402, "InvalidJournalIntegritySaveDataControlAreaSize" }, + { 0x21B602, "JournalIntegritySaveDataControlAreaVerificationFailed" }, + { 0x21B802, "JournalIntegritySaveDataMasterSignatureVerificationFailed" }, + { 0x21BA02, "IncorrectJournalIntegritySaveDataMagicCode" }, + { 0x21C202, "SaveDataDuplexStorageCorrupted" }, + { 0x21C402, "IncorrectDuplexMagicCode" }, + { 0x21C602, "DuplexStorageAccessOutOfRange" }, + { 0x21D602, "SaveDataMapCorrupted" }, + { 0x21D802, "InvalidMapEntryCount" }, + { 0x21DA02, "InvalidMapOffset" }, + { 0x21DC02, "InvalidMapSize" }, + { 0x21DE02, "InvalidMapAlignment" }, + { 0x21E002, "InvalidMapStorageType" }, + { 0x21E202, "MapAddressAlreadyRegistered" }, + { 0x21E402, "MapStorageNotFound" }, + { 0x21E602, "InvalidMapStorageSize" }, + { 0x21EA02, "SaveDataLogCorrupted" }, + { 0x21EC02, "InvalidLogBlockSize" }, + { 0x21EE02, "InvalidLogOffset" }, + { 0x21F002, "UnexpectedEndOfLog" }, + { 0x21F202, "LogNotFound" }, + { 0x220002, "ThumbnailHashVerificationFailed" }, + { 0x220A02, "InvalidSaveDataInternalStorageIntegritySeedSize" }, + { 0x220C02, "InvalidSaveDataInternalStorageAllocationTableFreeBitmapSizeA" }, + { 0x220E02, "InvalidSaveDataInternalStorageAllocationTableFreeBitmapSizeB" }, + { 0x221202, "SaveDataIntegrityVerificationStorageCorrupted" }, + { 0x221402, "IncorrectSaveDataIntegrityVerificationMagicCode" }, + { 0x221602, "InvalidSaveDataZeroHash" }, + { 0x221802, "SaveDataNonRealDataVerificationFailed" }, + { 0x222602, "SaveDataRealDataVerificationFailed" }, + { 0x222802, "ClearedSaveDataRealDataVerificationFailed" }, + { 0x222A02, "UnclearedSaveDataRealDataVerificationFailed" }, + { 0x226202, "SaveDataBuiltInStorageCorrupted" }, + { 0x226402, "SaveDataGptHeaderSignatureVerificationFailed" }, + { 0x227602, "SaveDataCoreFileSystemCorrupted" }, + { 0x227802, "IncorrectSaveDataFileSystemMagicCode" }, + { 0x227A02, "InvalidSaveDataFileReadOffset" }, + { 0x227C02, "InvalidSaveDataCoreDataStorageSize" }, + { 0x229602, "IncompleteBlockInZeroBitmapHashStorageFileSaveData" }, + { 0x229E02, "JournalStorageCorrupted" }, + { 0x22A002, "JournalStorageAccessOutOfRange" }, + { 0x22A202, "InvalidJournalStorageDataStorageSize" }, + { 0x22B202, "SaveDataHostFileSystemCorrupted" }, + { 0x22B402, "SaveDataHostEntryCorrupted" }, + { 0x22B602, "SaveDataHostFileDataCorrupted" }, + { 0x22B802, "SaveDataHostFileCorrupted" }, + { 0x22BA02, "InvalidSaveDataHostHandle" }, + { 0x22C602, "MappingTableCorrupted" }, + { 0x22C802, "InvalidMappingTableEntryCount" }, + { 0x22CA02, "InvalidMappingTablePhysicalIndex" }, + { 0x22CC02, "InvalidMappingTableVirtualIndex" }, + { 0x22DA02, "SaveDataDatabaseCorrupted" }, + { 0x22DC02, "InvalidSaveDataAllocationTableBlock" }, + { 0x22DE02, "InvalidSaveDataKeyValueListElementIndex" }, + { 0x22E002, "InvalidSaveDataAllocationTableChainEntry" }, + { 0x22E202, "InvalidSaveDataAllocationTableOffset" }, + { 0x22E402, "InvalidSaveDataAllocationTableBlockCount" }, + { 0x22E602, "InvalidSaveDataKeyValueListEntryIndex" }, + { 0x22E802, "InvalidSaveDataBitmapIndex" }, + { 0x230202, "SaveDataExtensionContextCorrupted" }, + { 0x230402, "IncorrectSaveDataExtensionContextMagicCode" }, + { 0x230602, "InvalidSaveDataExtensionContextState" }, + { 0x230802, "DifferentSaveDataExtensionContextParameter" }, + { 0x230A02, "InvalidSaveDataExtensionContextParameter" }, + { 0x231602, "IntegritySaveDataCorrupted" }, + { 0x231802, "InvalidIntegritySaveDataHashSize" }, + { 0x231C02, "InvalidIntegritySaveDataControlAreaSize" }, + { 0x231E02, "IntegritySaveDataControlAreaVerificationFailed" }, + { 0x232002, "IntegritySaveDataMasterSignatureVerificationFailed" }, + { 0x232202, "IncorrectIntegritySaveDataMagicCode" }, + { 0x232A02, "NcaCorrupted" }, + { 0x233802, "NcaBaseStorageOutOfRangeA" }, + { 0x233A02, "NcaBaseStorageOutOfRangeB" }, + { 0x233C02, "NcaBaseStorageOutOfRangeC" }, + { 0x233E02, "NcaFileSystemCorrupted" }, + { 0x234002, "InvalidNcaFileSystemType" }, + { 0x234202, "InvalidAcidFileSize" }, + { 0x234402, "InvalidAcidSize" }, + { 0x234602, "InvalidAcid" }, + { 0x234802, "AcidVerificationFailed" }, + { 0x234A02, "InvalidNcaSignature" }, + { 0x234C02, "NcaHeaderSignature1VerificationFailed" }, + { 0x234E02, "NcaHeaderSignature2VerificationFailed" }, + { 0x235002, "NcaFsHeaderHashVerificationFailed" }, + { 0x235202, "InvalidNcaKeyIndex" }, + { 0x235402, "InvalidNcaFsHeaderHashType" }, + { 0x235602, "InvalidNcaFsHeaderEncryptionType" }, + { 0x235802, "InvalidNcaPatchInfoIndirectSize" }, + { 0x235A02, "InvalidNcaPatchInfoAesCtrExSize" }, + { 0x235C02, "InvalidNcaPatchInfoAesCtrExOffset" }, + { 0x235E02, "InvalidNcaId" }, + { 0x236002, "InvalidNcaHeader" }, + { 0x236202, "InvalidNcaFsHeader" }, + { 0x236402, "InvalidNcaPatchInfoIndirectOffset" }, + { 0x236602, "NcaHierarchicalSha256StorageCorrupted" }, + { 0x236802, "InvalidHierarchicalSha256BlockSize" }, + { 0x236A02, "InvalidHierarchicalSha256LayerCount" }, + { 0x236C02, "HierarchicalSha256BaseStorageTooLarge" }, + { 0x236E02, "HierarchicalSha256HashVerificationFailed" }, + { 0x237A02, "InvalidHierarchicalIntegrityVerificationLayerCount" }, + { 0x237C02, "NcaIndirectStorageOutOfRange" }, + { 0x237E02, "InvalidNcaHeader1SignatureKeyGeneration" }, + { 0x238202, "InvalidNspdVerificationData" }, + { 0x238402, "MissingNspdVerificationData" }, + { 0x238602, "NcaInvalidCompressionInfo" }, + { 0x23F202, "IntegrityVerificationStorageCorrupted" }, + { 0x23F402, "IncorrectIntegrityVerificationMagicCode" }, + { 0x23F602, "InvalidZeroHash" }, + { 0x23F802, "NonRealDataVerificationFailed" }, + { 0x240602, "RealDataVerificationFailed" }, + { 0x240802, "ClearedRealDataVerificationFailed" }, + { 0x240A02, "UnclearedRealDataVerificationFailed" }, + { 0x244202, "PartitionFileSystemCorrupted" }, + { 0x244402, "InvalidSha256PartitionHashTarget" }, + { 0x244602, "Sha256PartitionHashVerificationFailed" }, + { 0x244802, "PartitionSignatureVerificationFailed" }, + { 0x244A02, "Sha256PartitionSignatureVerificationFailed" }, + { 0x244C02, "InvalidPartitionEntryOffset" }, + { 0x244E02, "InvalidSha256PartitionMetaDataSize" }, + { 0x246A02, "BuiltInStorageCorrupted" }, + { 0x246C02, "GptHeaderSignatureVerificationFailed" }, + { 0x247002, "GptHeaderInvalidPartitionSize" }, + { 0x249202, "FatFileSystemCorrupted" }, + { 0x249602, "InvalidFatFormat" }, + { 0x249802, "InvalidFatFileNumber" }, + { 0x249A02, "ExFatUnavailable" }, + { 0x249C02, "InvalidFatFormatBisUser" }, + { 0x249E02, "InvalidFatFormatBisSystem" }, + { 0x24A002, "InvalidFatFormatBisSafe" }, + { 0x24A202, "InvalidFatFormatBisCalibration" }, + { 0x24A402, "InvalidFatFormatSd" }, + { 0x24BA02, "HostFileSystemCorrupted" }, + { 0x24BC02, "HostEntryCorrupted" }, + { 0x24BE02, "HostFileDataCorrupted" }, + { 0x24C002, "HostFileCorrupted" }, + { 0x24C202, "InvalidHostHandle" }, + { 0x24E202, "DatabaseCorrupted" }, + { 0x24E402, "InvalidAllocationTableBlock" }, + { 0x24E602, "InvalidKeyValueListElementIndex" }, + { 0x24E802, "InvalidAllocationTableChainEntry" }, + { 0x24EA02, "InvalidAllocationTableOffset" }, + { 0x24EC02, "InvalidAllocationTableBlockCount" }, + { 0x24EE02, "InvalidKeyValueListEntryIndex" }, + { 0x24F002, "InvalidBitmapIndex" }, + { 0x250A02, "AesXtsFileSystemCorrupted" }, + { 0x250C02, "AesXtsFileSystemFileHeaderSizeCorruptedOnFileOpen" }, + { 0x250E02, "AesXtsFileSystemFileHeaderCorruptedOnFileOpen" }, + { 0x251002, "AesXtsFileSystemFileNoHeaderOnFileOpen" }, + { 0x251202, "AesXtsFileSystemFileSizeCorruptedOnFileOpen" }, + { 0x251402, "AesXtsFileSystemFileSizeCorruptedOnFileSetSize" }, + { 0x251602, "AesXtsFileSystemFileHeaderCorruptedOnRename" }, + { 0x251802, "AesXtsFileSystemFileHeaderCorruptedOnFileSetSize" }, + { 0x253202, "SaveDataTransferDataCorrupted" }, + { 0x253402, "SaveDataTransferTokenMacVerificationFailed" }, + { 0x253602, "SaveDataTransferTokenSignatureVerificationFailed" }, + { 0x253802, "SaveDataTransferTokenChallengeVerificationFailed" }, + { 0x253A02, "SaveDataTransferImportMacVerificationFailed" }, + { 0x253C02, "SaveDataTransferInitialDataMacVerificationFailed" }, + { 0x253E02, "SaveDataTransferInitialDataVersionVerificationFailed" }, + { 0x254602, "SignedSystemPartitionDataCorrupted" }, + { 0x254802, "SignedSystemPartitionInvalidSize" }, + { 0x254A02, "SignedSystemPartitionSignatureVerificationFailed" }, + { 0x254C02, "SignedSystemPartitionHashVerificationFailed" }, + { 0x254E02, "SignedSystemPartitionPackage2HashVerificationFailed" }, + { 0x255002, "SignedSystemPartitionInvalidAppendHashCount" }, + { 0x255A02, "GameCardLogoDataCorrupted" }, + { 0x256202, "SimulatedDeviceDataCorrupted" }, + { 0x256C02, "MultiCommitContextCorrupted" }, + { 0x256E02, "InvalidMultiCommitContextVersion" }, + { 0x257002, "InvalidMultiCommitContextState" }, + { 0x258402, "ConcatenationFsInvalidInternalFileCount" }, + { 0x259602, "ZeroBitmapFileCorrupted" }, + { 0x259802, "IncompleteBlockInZeroBitmapHashStorageFile" }, + { 0x271002, "Unexpected" }, + { 0x271202, "FatFsUnexpected" }, + { 0x271402, "FatFsUnclassified" }, + { 0x271602, "FatFsStorageStateMissmatch" }, + { 0x274002, "FatFsTooManyFilesOpenedS" }, + { 0x274202, "FatFsTooManyFilesOpenedU" }, + { 0x274402, "FatFsNotAFile" }, + { 0x274802, "FatFsLockError" }, + { 0x274A02, "FatFsInternalError" }, + { 0x277E02, "FatFsModuleSafeError" }, + { 0x27EC02, "FatFsUnexpectedSystemError" }, + { 0x280002, "FatFsFormatUnexpected" }, + { 0x280202, "FatFsFormatUnsupportedSize" }, + { 0x280402, "FatFsFormatInvalidBpb" }, + { 0x280602, "FatFsFormatInvalidParameter" }, + { 0x280802, "FatFsFormatIllegalSectorsA" }, + { 0x280A02, "FatFsFormatIllegalSectorsB" }, + { 0x280C02, "FatFsFormatIllegalSectorsC" }, + { 0x280E02, "FatFsFormatIllegalSectorsD" }, + { 0x281602, "FatFsWriteVerifyError" }, + { 0x296A02, "UnexpectedInMountTableA" }, + { 0x296C02, "UnexpectedInJournalIntegritySaveDataFileSystemA" }, + { 0x296E02, "UnexpectedInJournalIntegritySaveDataFileSystemB" }, + { 0x297002, "UnexpectedInJournalIntegritySaveDataFileSystemC" }, + { 0x297202, "UnexpectedInLocalFileSystemA" }, + { 0x297402, "UnexpectedInLocalFileSystemB" }, + { 0x297602, "UnexpectedInLocalFileSystemC" }, + { 0x297802, "UnexpectedInLocalFileSystemD" }, + { 0x297A02, "UnexpectedInLocalFileSystemE" }, + { 0x297C02, "UnexpectedInLocalFileSystemF" }, + { 0x297E02, "UnexpectedInPathToolA" }, + { 0x298002, "UnexpectedInPathOnExecutionDirectoryA" }, + { 0x298202, "UnexpectedInPathOnExecutionDirectoryB" }, + { 0x298402, "UnexpectedInPathOnExecutionDirectoryC" }, + { 0x298602, "UnexpectedInAesCtrStorageA" }, + { 0x298802, "UnexpectedInAesXtsStorageA" }, + { 0x298A02, "UnexpectedInSaveDataInternalStorageFileSystemA" }, + { 0x298C02, "UnexpectedInSaveDataInternalStorageFileSystemB" }, + { 0x298E02, "UnexpectedInMountUtilityA" }, + { 0x299002, "UnexpectedInNcaFileSystemServiceImplA" }, + { 0x299202, "UnexpectedInRamDiskFileSystemA" }, + { 0x299402, "UnexpectedInBisWiperA" }, + { 0x299602, "UnexpectedInBisWiperB" }, + { 0x299802, "UnexpectedInCompressedStorageA" }, + { 0x299A02, "UnexpectedInCompressedStorageB" }, + { 0x299C02, "UnexpectedInCompressedStorageC" }, + { 0x299E02, "UnexpectedInCompressedStorageD" }, + { 0x29A002, "UnexpectedInPathA" }, + { 0x2EE002, "PreconditionViolation" }, + { 0x2EE202, "InvalidArgument" }, + { 0x2EE402, "InvalidPath" }, + { 0x2EE602, "TooLongPath" }, + { 0x2EE802, "InvalidCharacter" }, + { 0x2EEA02, "InvalidPathFormat" }, + { 0x2EEC02, "DirectoryUnobtainable" }, + { 0x2EEE02, "NotNormalized" }, + { 0x2F1C02, "InvalidPathForOperation" }, + { 0x2F1E02, "DirectoryUndeletable" }, + { 0x2F2002, "DirectoryUnrenamable" }, + { 0x2F2202, "IncompatiblePath" }, + { 0x2F2402, "RenameToOtherFileSystem" }, + { 0x2F5A02, "InvalidOffset" }, + { 0x2F5C02, "InvalidSize" }, + { 0x2F5E02, "NullptrArgument" }, + { 0x2F6002, "InvalidAlignment" }, + { 0x2F6202, "InvalidMountName" }, + { 0x2F6402, "ExtensionSizeTooLarge" }, + { 0x2F6602, "ExtensionSizeInvalid" }, + { 0x2F6802, "InvalidHandle" }, + { 0x2F6A02, "CacheStorageSizeTooLarge" }, + { 0x2F6C02, "CacheStorageIndexTooLarge" }, + { 0x2F6E02, "InvalidCommitNameCount" }, + { 0x2F7002, "InvalidModeForFileOpen" }, + { 0x2F7202, "InvalidFileSize" }, + { 0x2F7402, "InvalidModeForDirectoryOpen" }, + { 0x2F7602, "InvalidCommitOption" }, + { 0x2F8002, "InvalidEnumValue" }, + { 0x2F8202, "InvalidSaveDataState" }, + { 0x2F8402, "InvalidSaveDataSpaceId" }, + { 0x2FAA02, "GameCardLogoDataTooLarge" }, + { 0x2FAC02, "FileDataCacheMemorySizeTooSmall" }, + { 0x307002, "InvalidOperationForOpenMode" }, + { 0x307202, "FileExtensionWithoutOpenModeAllowAppend" }, + { 0x307402, "ReadUnpermitted" }, + { 0x307602, "WriteUnpermitted" }, + { 0x313802, "UnsupportedOperation" }, + { 0x313A02, "UnsupportedCommitTarget" }, + { 0x313C02, "UnsupportedSetSizeForNotResizableSubStorage" }, + { 0x313E02, "UnsupportedSetSizeForResizableSubStorage" }, + { 0x314002, "UnsupportedSetSizeForMemoryStorage" }, + { 0x314202, "UnsupportedOperateRangeForMemoryStorage" }, + { 0x314402, "UnsupportedOperateRangeForFileStorage" }, + { 0x314602, "UnsupportedOperateRangeForFileHandleStorage" }, + { 0x314802, "UnsupportedOperateRangeForSwitchStorage" }, + { 0x314A02, "UnsupportedOperateRangeForStorageServiceObjectAdapter" }, + { 0x314C02, "UnsupportedWriteForAesCtrCounterExtendedStorage" }, + { 0x314E02, "UnsupportedSetSizeForAesCtrCounterExtendedStorage" }, + { 0x315002, "UnsupportedOperateRangeForAesCtrCounterExtendedStorage" }, + { 0x315202, "UnsupportedWriteForAesCtrStorageExternal" }, + { 0x315402, "UnsupportedSetSizeForAesCtrStorageExternal" }, + { 0x315602, "UnsupportedSetSizeForAesCtrStorage" }, + { 0x315802, "UnsupportedSetSizeForHierarchicalIntegrityVerificationStorage" }, + { 0x315A02, "UnsupportedOperateRangeForHierarchicalIntegrityVerificationStorage" }, + { 0x315C02, "UnsupportedSetSizeForIntegrityVerificationStorage" }, + { 0x315E02, "UnsupportedOperateRangeForWritableIntegrityVerificationStorage" }, + { 0x316002, "UnsupportedOperateRangeForIntegrityVerificationStorage" }, + { 0x316202, "UnsupportedSetSizeForBlockCacheBufferedStorage" }, + { 0x316402, "UnsupportedOperateRangeForWritableBlockCacheBufferedStorage" }, + { 0x316602, "UnsupportedOperateRangeForBlockCacheBufferedStorage" }, + { 0x316802, "UnsupportedWriteForIndirectStorage" }, + { 0x316A02, "UnsupportedSetSizeForIndirectStorage" }, + { 0x316C02, "UnsupportedOperateRangeForIndirectStorage" }, + { 0x316E02, "UnsupportedWriteForZeroStorage" }, + { 0x317002, "UnsupportedSetSizeForZeroStorage" }, + { 0x317202, "UnsupportedSetSizeForHierarchicalSha256Storage" }, + { 0x317402, "UnsupportedWriteForReadOnlyBlockCacheStorage" }, + { 0x317602, "UnsupportedSetSizeForReadOnlyBlockCacheStorage" }, + { 0x317802, "UnsupportedSetSizeForIntegrityRomFsStorage" }, + { 0x317A02, "UnsupportedSetSizeForDuplexStorage" }, + { 0x317C02, "UnsupportedOperateRangeForDuplexStorage" }, + { 0x317E02, "UnsupportedSetSizeForHierarchicalDuplexStorage" }, + { 0x318002, "UnsupportedGetSizeForRemapStorage" }, + { 0x318202, "UnsupportedSetSizeForRemapStorage" }, + { 0x318402, "UnsupportedOperateRangeForRemapStorage" }, + { 0x318602, "UnsupportedSetSizeForIntegritySaveDataStorage" }, + { 0x318802, "UnsupportedOperateRangeForIntegritySaveDataStorage" }, + { 0x318A02, "UnsupportedSetSizeForJournalIntegritySaveDataStorage" }, + { 0x318C02, "UnsupportedOperateRangeForJournalIntegritySaveDataStorage" }, + { 0x318E02, "UnsupportedGetSizeForJournalStorage" }, + { 0x319002, "UnsupportedSetSizeForJournalStorage" }, + { 0x319202, "UnsupportedOperateRangeForJournalStorage" }, + { 0x319402, "UnsupportedSetSizeForUnionStorage" }, + { 0x319602, "UnsupportedSetSizeForAllocationTableStorage" }, + { 0x319802, "UnsupportedReadForWriteOnlyGameCardStorage" }, + { 0x319A02, "UnsupportedSetSizeForWriteOnlyGameCardStorage" }, + { 0x319C02, "UnsupportedWriteForReadOnlyGameCardStorage" }, + { 0x319E02, "UnsupportedSetSizeForReadOnlyGameCardStorage" }, + { 0x31A002, "UnsupportedOperateRangeForReadOnlyGameCardStorage" }, + { 0x31A202, "UnsupportedSetSizeForSdmmcStorage" }, + { 0x31A402, "UnsupportedOperateRangeForSdmmcStorage" }, + { 0x31A602, "UnsupportedOperateRangeForFatFile" }, + { 0x31A802, "UnsupportedOperateRangeForStorageFile" }, + { 0x31AA02, "UnsupportedSetSizeForInternalStorageConcatenationFile" }, + { 0x31AC02, "UnsupportedOperateRangeForInternalStorageConcatenationFile" }, + { 0x31AE02, "UnsupportedQueryEntryForConcatenationFileSystem" }, + { 0x31B002, "UnsupportedOperateRangeForConcatenationFile" }, + { 0x31B202, "UnsupportedSetSizeForZeroBitmapFile" }, + { 0x31B402, "UnsupportedOperateRangeForFileServiceObjectAdapter" }, + { 0x31B602, "UnsupportedOperateRangeForAesXtsFile" }, + { 0x31B802, "UnsupportedWriteForRomFsFileSystem" }, + { 0x31BA02, "UnsupportedCommitProvisionallyForRomFsFileSystem" }, + { 0x31BC02, "UnsupportedGetTotalSpaceSizeForRomFsFileSystem" }, + { 0x31BE02, "UnsupportedWriteForRomFsFile" }, + { 0x31C002, "UnsupportedOperateRangeForRomFsFile" }, + { 0x31C202, "UnsupportedWriteForReadOnlyFileSystem" }, + { 0x31C402, "UnsupportedCommitProvisionallyForReadOnlyFileSystem" }, + { 0x31C602, "UnsupportedGetTotalSpaceSizeForReadOnlyFileSystem" }, + { 0x31C802, "UnsupportedWriteForReadOnlyFile" }, + { 0x31CA02, "UnsupportedOperateRangeForReadOnlyFile" }, + { 0x31CC02, "UnsupportedWriteForPartitionFileSystem" }, + { 0x31CE02, "UnsupportedCommitProvisionallyForPartitionFileSystem" }, + { 0x31D002, "UnsupportedWriteForPartitionFile" }, + { 0x31D202, "UnsupportedOperateRangeForPartitionFile" }, + { 0x31D402, "UnsupportedOperateRangeForTmFileSystemFile" }, + { 0x31D602, "UnsupportedWriteForSaveDataInternalStorageFileSystem" }, + { 0x31DC02, "UnsupportedCommitProvisionallyForApplicationTemporaryFileSystem" }, + { 0x31DE02, "UnsupportedCommitProvisionallyForSaveDataFileSystem" }, + { 0x31E002, "UnsupportedCommitProvisionallyForDirectorySaveDataFileSystem" }, + { 0x31E202, "UnsupportedWriteForZeroBitmapHashStorageFile" }, + { 0x31E402, "UnsupportedSetSizeForZeroBitmapHashStorageFile" }, + { 0x31E602, "UnsupportedWriteForCompressedStorage" }, + { 0x31E802, "UnsupportedOperateRangeForCompressedStorage" }, + { 0x31F602, "UnsupportedRollbackOnlyModifiedForApplicationTemporaryFileSystem" }, + { 0x31F802, "UnsupportedRollbackOnlyModifiedForDirectorySaveDataFileSystem" }, + { 0x31FA02, "UnsupportedOperateRangeForRegionSwitchStorage" }, + { 0x320002, "PermissionDenied" }, + { 0x320602, "HostFileSystemOperationDisabled" }, + { 0x326402, "PortAcceptableCountLimited" }, + { 0x326802, "NcaExternalKeyInconsistent" }, + { 0x326C02, "NeedFlush" }, + { 0x326E02, "FileNotClosed" }, + { 0x327002, "DirectoryNotClosed" }, + { 0x327202, "WriteModeFileNotClosed" }, + { 0x327402, "AllocatorAlreadyRegistered" }, + { 0x327602, "DefaultAllocatorAlreadyUsed" }, + { 0x327802, "GameCardLogoDataSizeInvalid" }, + { 0x327A02, "AllocatorAlignmentViolation" }, + { 0x327C02, "GlobalFileDataCacheAlreadyEnabled" }, + { 0x327E02, "MultiCommitHasOverlappingTargets" }, + { 0x328002, "MultiCommitAlreadyInProgress" }, + { 0x328202, "UserNotExist" }, + { 0x328402, "DefaultGlobalFileDataCacheEnabled" }, + { 0x328602, "SaveDataRootPathUnavailable" }, + { 0x339002, "NotFound" }, + { 0x339402, "FileNotFound" }, + { 0x339602, "DirectoryNotFound" }, + { 0x339802, "DatabaseKeyNotFound" }, + { 0x339A02, "ProgramInfoNotFound" }, + { 0x339C02, "ProgramIndexNotFound" }, + { 0x345802, "OutOfResource" }, + { 0x346202, "BufferAllocationFailed" }, + { 0x346402, "MappingTableFull" }, + { 0x346602, "AllocationTableFull" }, + { 0x346A02, "OpenCountLimit" }, + { 0x346C02, "MultiCommitFileSystemLimit" }, + { 0x352002, "MappingFailed" }, + { 0x353602, "MapFull" }, + { 0x35E802, "BadState" }, + { 0x35EC02, "NotInitialized" }, + { 0x35EE02, "BisProxyInvalidated" }, + { 0x35F002, "NcaDigestInconsistent" }, + { 0x35F202, "NotMounted" }, + { 0x35F402, "SaveDataExtending" }, + { 0x35F602, "SaveDataToExpandIsProvisionallyCommitted" }, + { 0x36B402, "SaveDataTransferV2KeySeedPackageMacVerificationFailed" }, + { 0x36B602, "SaveDataTransferV2KeySeedPackageSignatureVerificationFailed" }, + { 0x36B802, "SaveDataTransferV2KeySeedPackageChallengeVerificationFailed" }, + { 0x36BA02, "SaveDataTransferV2ImportDataVerificationFailed" }, + { 0x36BC02, "SaveDataTransferV2InitialDataGcmMacVerificationFailed" }, + { 0x36C202, "SaveDataTransferV2InitialDataMacVerificationFailed" }, + { 0x36C402, "SaveDataTransferV2ImportDataDecompressionFailed" }, + { 0x36C602, "SaveDataTransferV2PortContextMacVerificationFailed" }, + { 0x36EE02, "SaveDataPorterInvalidated" }, + { 0x36F002, "SaveDataDivisionExporterChunkExportIncomplete" }, + { 0x36F202, "SaveDataDivisionImporterChunkImportIncomplete" }, + { 0x36F402, "SaveDataPorterInitialDataVersionVerificationFailed" }, + { 0x36F602, "SaveDataChunkDecryptorGcmStreamVersionVerificationFailed" }, + { 0x36F802, "SaveDataPorterSaveDataModified" }, + { 0x36FA02, "SaveDataPorterVersionUnsupported" }, + { 0x36FC02, "SaveDataTransferV2SecondarySaveCorrupted" }, + { 0x372C02, "SaveDataTransferForSaveDataRepairKeyPackageMacVerificationFailed" }, + { 0x372E02, "SaveDataTransferForSaveDataRepairKeyPackageSignatureVerificationFailed" }, + { 0x373002, "SaveDataTransferForSaveDataRepairKeyPackageChallengeVerificationFailed" }, + { 0x373202, "SaveDataTransferForSaveDataRepairUnsupportedKeyGeneration" }, + { 0x373402, "SaveDataTransferForSaveDataRepairInitialDataMacVerificationFailed" }, + { 0x373A02, "SaveDataTransferForSaveDataRepairIncorrectInitialData" }, + { 0x373C02, "SaveDataTransferForSaveDataRepairInconsistentInitialData" }, + { 0x373E02, "SaveDataTransferForSaveDataRepairInitialDataIncorrectUserId" }, + { 0x377802, "RamDiskCorrupted" }, + { 0x377A02, "RamDiskVerifiedStorageVerificationFailed" }, + { 0x378E02, "RamDiskSaveDataCoreFileSystemCorrupted" }, + { 0x379002, "IncorrectRamDiskSaveDataFileSystemMagicCode" }, + { 0x379202, "InvalidRamDiskSaveDataFileReadOffset" }, + { 0x379402, "InvalidRamDiskSaveDataCoreDataStorageSize" }, + { 0x37A202, "RamDiskDatabaseCorrupted" }, + { 0x37A402, "InvalidRamDiskAllocationTableBlock" }, + { 0x37A602, "InvalidRamDiskKeyValueListElementIndex" }, + { 0x37A802, "InvalidRamDiskAllocationTableChainEntry" }, + { 0x37AA02, "InvalidRamDiskAllocationTableOffset" }, + { 0x37AC02, "InvalidRamDiskAllocationTableBlockCount" }, + { 0x37AE02, "InvalidRamDiskKeyValueListEntryIndex" }, + { 0x37CC02, "SaveDataTransferForRepairInitialDataMacVerificationFailed" }, + { 0x3DB802, "Unknown" }, + { 0x3DBA02, "DbmNotFound" }, + { 0x3DBC02, "DbmKeyNotFound" }, + { 0x3DBE02, "DbmFileNotFound" }, + { 0x3DC002, "DbmDirectoryNotFound" }, + { 0x3DC402, "DbmAlreadyExists" }, + { 0x3DC602, "DbmKeyFull" }, + { 0x3DC802, "DbmDirectoryEntryFull" }, + { 0x3DCA02, "DbmFileEntryFull" }, + { 0x3DCC02, "DbmFindFinished" }, + { 0x3DCE02, "DbmFindKeyFinished" }, + { 0x3DD002, "DbmIterationFinished" }, + { 0x3DD402, "DbmInvalidOperation" }, + { 0x3DD602, "DbmInvalidPathFormat" }, + { 0x3DD802, "DbmDirectoryNameTooLong" }, + { 0x3DDA02, "DbmFileNameTooLong" }, + { 0x803, "Busy" }, + { 0x1003, "OutOfMemory" }, + { 0x1203, "OutOfResource" }, + { 0x1803, "OutOfVirtualAddressSpace" }, + { 0x1A03, "ResourceLimit" }, + { 0x3E803, "OutOfHandles" }, + { 0x3EA03, "InvalidHandle" }, + { 0x3EC03, "InvalidCurrentMemoryState" }, + { 0x3EE03, "InvalidTransferMemoryState" }, + { 0x3F003, "InvalidTransferMemorySize" }, + { 0x3F203, "OutOfTransferMemory" }, + { 0x3F403, "OutOfAddressSpace" }, + { 0x3FC03, "SessionClosedForReceive" }, + { 0x3FE03, "SessionClosedForReply" }, + { 0x40003, "ReceiveListBroken" }, + { 0x1204, "InvalidHandle" }, + { 0xFA204, "InvalidArgument" }, + { 0xFA604, "InvalidServerHandle" }, + { 0xFBC04, "InvalidSize" }, + { 0xFCA04, "Cancelled" }, + { 0xFCE04, "Completed" }, + { 0x106E04, "InvalidTask" }, + { 0x205, "InvalidContentStorageBase" }, + { 0x405, "PlaceHolderAlreadyExists" }, + { 0x605, "PlaceHolderNotFound" }, + { 0x805, "ContentAlreadyExists" }, + { 0xA05, "ContentNotFound" }, + { 0xE05, "ContentMetaNotFound" }, + { 0x1005, "AllocationFailed" }, + { 0x1805, "UnknownStorage" }, + { 0xC805, "InvalidContentStorage" }, + { 0xDC05, "InvalidContentMetaDatabase" }, + { 0x10405, "InvalidPackageFormat" }, + { 0x11805, "InvalidContentHash" }, + { 0x14005, "InvalidInstallTaskState" }, + { 0x15405, "InvalidPlaceHolderFile" }, + { 0x16805, "BufferInsufficient" }, + { 0x17C05, "WriteToReadOnlyContentStorage" }, + { 0x19005, "NotEnoughInstallSpace" }, + { 0x1A405, "SystemUpdateNotFoundInPackage" }, + { 0x1B805, "ContentInfoNotFound" }, + { 0x1DA05, "DeltaNotFound" }, + { 0x1E005, "InvalidContentMetaKey" }, + { 0x1F405, "ContentStorageNotActive" }, + { 0x1F605, "GameCardContentStorageNotActive" }, + { 0x1F805, "BuiltInSystemContentStorageNotActive" }, + { 0x1FA05, "BuiltInUserContentStorageNotActive" }, + { 0x1FC05, "SdCardContentStorageNotActive" }, + { 0x20405, "UnknownContentStorageNotActive" }, + { 0x20805, "ContentMetaDatabaseNotActive" }, + { 0x20A05, "GameCardContentMetaDatabaseNotActive" }, + { 0x20C05, "BuiltInSystemContentMetaDatabaseNotActive" }, + { 0x20E05, "BuiltInUserContentMetaDatabaseNotActive" }, + { 0x21005, "SdCardContentMetaDatabaseNotActive" }, + { 0x21805, "UnknownContentMetaDatabaseNotActive" }, + { 0x23005, "IgnorableInstallTicketFailure" }, + { 0x24405, "InstallTaskCancelled" }, + { 0x24605, "CreatePlaceHolderCancelled" }, + { 0x24805, "WritePlaceHolderCancelled" }, + { 0x26C05, "ContentStorageBaseNotFound" }, + { 0x29405, "ListPartiallyNotCommitted" }, + { 0x2D005, "UnexpectedContentMetaPrepared" }, + { 0x2F805, "InvalidFirmwareVariation" }, + { 0x3FEA05, "InvalidArgument" }, + { 0x3FEC05, "InvalidOffset" }, + { 0x206, "EndOfQuery" }, + { 0x406, "InvalidCurrentMemory" }, + { 0x606, "NotSingleRegion" }, + { 0x806, "InvalidMemoryState" }, + { 0xA06, "OutOfMemory" }, + { 0xC06, "OutOfResource" }, + { 0xE06, "NotSupported" }, + { 0x1006, "InvalidHandle" }, + { 0x7FE06, "InternalError" }, + { 0x208, "ResolverNotFound" }, + { 0x408, "ProgramNotFound" }, + { 0x608, "DataNotFound" }, + { 0x808, "UnknownResolver" }, + { 0xA08, "ApplicationNotFound" }, + { 0xC08, "HtmlDocumentNotFound" }, + { 0xE08, "AddOnContentNotFound" }, + { 0x1008, "ControlNotFound" }, + { 0x1208, "LegalInformationNotFound" }, + { 0x1408, "DebugProgramNotFound" }, + { 0xB408, "TooManyRegisteredPaths" }, + { 0x209, "TooLongArgument" }, + { 0x409, "TooManyArguments" }, + { 0x609, "TooLargeMeta" }, + { 0x809, "InvalidMeta" }, + { 0xA09, "InvalidNso" }, + { 0xC09, "InvalidPath" }, + { 0xE09, "TooManyProcesses" }, + { 0x1009, "NotPinned" }, + { 0x1209, "InvalidProgramId" }, + { 0x1409, "InvalidVersion" }, + { 0x1609, "InvalidAcidSignature" }, + { 0x1809, "InvalidNcaSignature" }, + { 0x6609, "InsufficientAddressSpace" }, + { 0x6809, "InvalidNro" }, + { 0x6A09, "InvalidNrr" }, + { 0x6C09, "InvalidSignature" }, + { 0x6E09, "InsufficientNroRegistrations" }, + { 0x7009, "InsufficientNrrRegistrations" }, + { 0x7209, "NroAlreadyLoaded" }, + { 0xA209, "InvalidAddress" }, + { 0xA409, "InvalidSize" }, + { 0xA809, "NotLoaded" }, + { 0xAA09, "NotRegistered" }, + { 0xAC09, "InvalidSession" }, + { 0xAE09, "InvalidProcess" }, + { 0xC809, "UnknownCapability" }, + { 0xCE09, "InvalidCapabilityKernelFlags" }, + { 0xD009, "InvalidCapabilitySyscallMask" }, + { 0xD409, "InvalidCapabilityMapRange" }, + { 0xD609, "InvalidCapabilityMapPage" }, + { 0xDE09, "InvalidCapabilityInterruptPair" }, + { 0xE209, "InvalidCapabilityApplicationType" }, + { 0xE409, "InvalidCapabilityKernelVersion" }, + { 0xE609, "InvalidCapabilityHandleTable" }, + { 0xE809, "InvalidCapabilityDebugFlags" }, + { 0x19009, "InternalError" }, + { 0x20A, "NotSupported" }, + { 0x60A, "PreconditionViolation" }, + { 0x140A, "MemoryAllocationFailed" }, + { 0x160A, "CmifProxyAllocationFailed" }, + { 0x1940A, "InvalidCmifHeaderSize" }, + { 0x1A60A, "InvalidCmifInHeader" }, + { 0x1A80A, "InvalidCmifOutHeader" }, + { 0x1BA0A, "UnknownMethodId" }, + { 0x1CE0A, "InvalidInRawSize" }, + { 0x1D00A, "InvalidOutRawSize" }, + { 0x1D60A, "InvalidInObjectCount" }, + { 0x1D80A, "InvalidOutObjectCount" }, + { 0x1DE0A, "InvalidInObject" }, + { 0x20A0A, "TargetObjectNotFound" }, + { 0x25A0A, "OutOfDomainEntry" }, + { 0x6400A, "RequestContextChanged" }, + { 0x6420A, "RequestInvalidated" }, + { 0x6440A, "RequestInvalidatedByUser" }, + { 0x6560A, "RequestDeferred" }, + { 0x6580A, "RequestDeferredByUser" }, + { 0x20B, "NotSupported" }, + { 0xC80B, "OutOfResource" }, + { 0xCC0B, "OutOfSessionMemory" }, + { 0x1060B, "OutOfSessions" }, + { 0x11A0B, "InsufficientPointerTransferBuffer" }, + { 0x1900B, "OutOfDomains" }, + { 0x2580B, "CommunicationError" }, + { 0x25A0B, "SessionClosed" }, + { 0x3240B, "InvalidRequestSize" }, + { 0x3260B, "UnknownCommandType" }, + { 0x3480B, "InvalidCmifRequest" }, + { 0x3D60B, "TargetNotDomain" }, + { 0x3D80B, "DomainObjectNotFound" }, + { 0x20C, "Unknown" }, + { 0x20D, "Unknown" }, + { 0x40D, "DebuggingDisabled" }, + { 0x20F, "ProcessNotFound" }, + { 0x40F, "AlreadyStarted" }, + { 0x60F, "NotTerminated" }, + { 0x80F, "DebugHookInUse" }, + { 0xA0F, "ApplicationRunning" }, + { 0xC0F, "InvalidSize" }, + { 0xB410, "Canceled" }, + { 0xDC10, "OutOfMaxRunningTask" }, + { 0x21C10, "CardUpdateNotSetup" }, + { 0x23010, "CardUpdateNotPrepared" }, + { 0x24410, "CardUpdateAlreadySetup" }, + { 0x39810, "PrepareCardUpdateAlreadyRequested" }, + { 0x212, "ConnectionFailure" }, + { 0x412, "NotFound" }, + { 0x612, "NotEnoughBuffer" }, + { 0xCA12, "Cancelled" }, + { 0x7FE12, "" }, + { 0xFA212, "" }, + { 0xFA612, "InvalidTaskId" }, + { 0xFB612, "InvalidSize" }, + { 0xFCA12, "TaskCancelled" }, + { 0xFCC12, "TaskNotCompleted" }, + { 0xFCE12, "TaskQueueNotAvailable" }, + { 0x106A12, "" }, + { 0x106C12, "OutOfRpcTask" }, + { 0x109612, "InvalidCategory" }, + { 0x214, "OutOfKeyResource" }, + { 0x414, "KeyNotFound" }, + { 0x814, "AllocationFailed" }, + { 0xA14, "InvalidKeyValue" }, + { 0xC14, "BufferInsufficient" }, + { 0x1014, "InvalidFileSystemState" }, + { 0x1214, "NotCreated" }, + { 0x215, "OutOfProcesses" }, + { 0x415, "InvalidClient" }, + { 0x615, "OutOfSessions" }, + { 0x815, "AlreadyRegistered" }, + { 0xA15, "OutOfServices" }, + { 0xC15, "InvalidServiceName" }, + { 0xE15, "NotRegistered" }, + { 0x1015, "NotAllowed" }, + { 0x1215, "TooLargeAccessControl" }, + { 0x216, "RoError" }, + { 0x416, "OutOfAddressSpace" }, + { 0x616, "AlreadyLoaded" }, + { 0x816, "InvalidNro" }, + { 0xC16, "InvalidNrr" }, + { 0xE16, "TooManyNro" }, + { 0x1016, "TooManyNrr" }, + { 0x1216, "NotAuthorized" }, + { 0x1416, "InvalidNrrKind" }, + { 0x7FE16, "InternalError" }, + { 0x80216, "InvalidAddress" }, + { 0x80416, "InvalidSize" }, + { 0x80816, "NotLoaded" }, + { 0x80A16, "NotRegistered" }, + { 0x80C16, "InvalidSession" }, + { 0x80E16, "InvalidProcess" }, + { 0x218, "NoDevice" }, + { 0x418, "NotActivated" }, + { 0x618, "DeviceRemoved" }, + { 0x818, "NotAwakened" }, + { 0x4018, "CommunicationError" }, + { 0x4218, "CommunicationNotAttained" }, + { 0x4418, "ResponseIndexError" }, + { 0x4618, "ResponseEndBitError" }, + { 0x4818, "ResponseCrcError" }, + { 0x4A18, "ResponseTimeoutError" }, + { 0x4C18, "DataEndBitError" }, + { 0x4E18, "DataCrcError" }, + { 0x5018, "DataTimeoutError" }, + { 0x5218, "AutoCommandResponseIndexError" }, + { 0x5418, "AutoCommandResponseEndBitError" }, + { 0x5618, "AutoCommandResponseCrcError" }, + { 0x5818, "AutoCommandResponseTimeoutError" }, + { 0x5A18, "CommandCompleteSoftwareTimeout" }, + { 0x5C18, "TransferCompleteSoftwareTimeout" }, + { 0x6018, "DeviceStatusHasError" }, + { 0x6218, "DeviceStatusAddressOutOfRange" }, + { 0x6418, "DeviceStatusAddressMisaligned" }, + { 0x6618, "DeviceStatusBlockLenError" }, + { 0x6818, "DeviceStatusEraseSeqError" }, + { 0x6A18, "DeviceStatusEraseParam" }, + { 0x6C18, "DeviceStatusWpViolation" }, + { 0x6E18, "DeviceStatusLockUnlockFailed" }, + { 0x7018, "DeviceStatusComCrcError" }, + { 0x7218, "DeviceStatusIllegalCommand" }, + { 0x7418, "DeviceStatusDeviceEccFailed" }, + { 0x7618, "DeviceStatusCcError" }, + { 0x7818, "DeviceStatusError" }, + { 0x7A18, "DeviceStatusCidCsdOverwrite" }, + { 0x7C18, "DeviceStatusWpEraseSkip" }, + { 0x7E18, "DeviceStatusEraseReset" }, + { 0x8018, "DeviceStatusSwitchError" }, + { 0x9018, "UnexpectedDeviceState" }, + { 0x9218, "UnexpectedDeviceCsdValue" }, + { 0x9418, "AbortTransactionSoftwareTimeout" }, + { 0x9618, "CommandInhibitCmdSoftwareTimeout" }, + { 0x9818, "CommandInhibitDatSoftwareTimeout" }, + { 0x9A18, "BusySoftwareTimeout" }, + { 0x9C18, "IssueTuningCommandSoftwareTimeout" }, + { 0x9E18, "TuningFailed" }, + { 0xA018, "MmcInitializationSoftwareTimeout" }, + { 0xA218, "MmcNotSupportExtendedCsd" }, + { 0xA418, "UnexpectedMmcExtendedCsdValue" }, + { 0xA618, "MmcEraseSoftwareTimeout" }, + { 0xA818, "SdCardValidationError" }, + { 0xAA18, "SdCardInitializationSoftwareTimeout" }, + { 0xAC18, "SdCardGetValidRcaSoftwareTimeout" }, + { 0xAE18, "UnexpectedSdCardAcmdDisabled" }, + { 0xB018, "SdCardNotSupportSwitchFunctionStatus" }, + { 0xB218, "UnexpectedSdCardSwitchFunctionStatus" }, + { 0xB418, "SdCardNotSupportAccessMode" }, + { 0xB618, "SdCardNot4BitBusWidthAtUhsIMode" }, + { 0xB818, "SdCardNotSupportSdr104AndSdr50" }, + { 0xBA18, "SdCardCannotSwitchAccessMode" }, + { 0xBC18, "SdCardFailedSwitchAccessMode" }, + { 0xBE18, "SdCardUnacceptableCurrentConsumption" }, + { 0xC018, "SdCardNotReadyToVoltageSwitch" }, + { 0xC218, "SdCardNotCompleteVoltageSwitch" }, + { 0x10018, "HostControllerUnexpected" }, + { 0x10218, "InternalClockStableSoftwareTimeout" }, + { 0x10418, "SdHostStandardUnknownAutoCmdError" }, + { 0x10618, "SdHostStandardUnknownError" }, + { 0x10818, "SdmmcDllCalibrationSoftwareTimeout" }, + { 0x10A18, "SdmmcDllApplicationSoftwareTimeout" }, + { 0x10C18, "SdHostStandardFailSwitchTo18V" }, + { 0x10E18, "DriveStrengthCalibrationNotCompleted" }, + { 0x11018, "DriveStrengthCalibrationSoftwareTimeout" }, + { 0x11218, "SdmmcCompShortToGnd" }, + { 0x11418, "SdmmcCompOpen" }, + { 0x14018, "InternalError" }, + { 0x14218, "NoWaitedInterrupt" }, + { 0x14418, "WaitInterruptSoftwareTimeout" }, + { 0x18018, "AbortCommandIssued" }, + { 0x19018, "NotSupported" }, + { 0x19218, "NotImplemented" }, + { 0x1A, "SecureMonitorError" }, + { 0x21A, "SecureMonitorNotImplemented" }, + { 0x41A, "SecureMonitorInvalidArgument" }, + { 0x61A, "SecureMonitorBusy" }, + { 0x81A, "SecureMonitorNoAsyncOperation" }, + { 0xA1A, "SecureMonitorInvalidAsyncOperation" }, + { 0xC1A, "SecureMonitorNotPermitted" }, + { 0xE1A, "SecureMonitorNotInitialized" }, + { 0xC81A, "InvalidSize" }, + { 0xCA1A, "UnknownSecureMonitorError" }, + { 0xCC1A, "DecryptionFailed" }, + { 0xD01A, "OutOfKeySlots" }, + { 0xD21A, "InvalidKeySlot" }, + { 0xD41A, "BootReasonAlreadySet" }, + { 0xD61A, "BootReasonNotSet" }, + { 0xD81A, "InvalidArgument" }, + { 0x21B, "InsufficientProvidedMemory" }, + { 0x21D, "ConnectionFailure" }, + { 0x61D, "UnknownDriverType" }, + { 0xA1D, "NonBlockingReceiveFailed" }, + { 0x101D, "ChannelWaitCancelled" }, + { 0x121D, "ChannelAlreadyExist" }, + { 0x141D, "ChannelNotExist" }, + { 0x12E1D, "OutOfChannel" }, + { 0x1301D, "OutOfTask" }, + { 0x1901D, "InvalidChannelState" }, + { 0x1921D, "InvalidChannelStateDisconnected" }, + { 0x7D01D, "InternalError" }, + { 0x7D21D, "Overflow" }, + { 0x7D41D, "OutOfMemory" }, + { 0x7D61D, "InvalidArgument" }, + { 0x7D81D, "ProtocolError" }, + { 0x7DA1D, "Cancelled" }, + { 0x8981D, "MuxError" }, + { 0x89A1D, "ChannelBufferOverflow" }, + { 0x89C1D, "ChannelBufferHasNotEnoughData" }, + { 0x89E1D, "ChannelVersionNotMatched" }, + { 0x8A01D, "ChannelStateTransitionError" }, + { 0x8A41D, "ChannelReceiveBufferEmpty" }, + { 0x8A61D, "ChannelSequenceIdNotMatched" }, + { 0x8A81D, "ChannelCannotDiscard" }, + { 0x9601D, "DriverError" }, + { 0x9621D, "DriverOpened" }, + { 0xA281D, "SocketDriverError" }, + { 0xA2A1D, "SocketSocketExemptError" }, + { 0xA2C1D, "SocketBindError" }, + { 0xA301D, "SocketListenError" }, + { 0xA321D, "SocketAcceptError" }, + { 0xA341D, "SocketReceiveError" }, + { 0xA361D, "SocketSendError" }, + { 0xA381D, "SocketReceiveFromError" }, + { 0xA3A1D, "SocketSendToError" }, + { 0xA3C1D, "SocketSetSockOptError" }, + { 0xA3E1D, "SocketGetSockNameError" }, + { 0xAF01D, "UsbDriverError" }, + { 0xAF21D, "UsbDriverUnknownError" }, + { 0xAF41D, "UsbDriverBusyError" }, + { 0xAF61D, "UsbDriverReceiveError" }, + { 0xAF81D, "UsbDriverSendError" }, + { 0xFA01D, "HtcctrlError" }, + { 0xFA21D, "HtcctrlStateTransitionNotAllowed" }, + { 0xFA41D, "HtcctrlReceiveUnexpectedPacket" }, + { 0x21E, "OutOfResource" }, + { 0x41E, "NotSupported" }, + { 0x61E, "InvalidArgument" }, + { 0x81E, "PermissionDenied" }, + { 0xA1E, "AccessModeDenied" }, + { 0xC1E, "DeviceCodeNotFound" }, + { 0x61F, "InvalidArgument" }, + { 0xC81F, "ConnectionFailure" }, + { 0xCA1F, "HtclowChannelClosed" }, + { 0xDC1F, "UnexpectedResponse" }, + { 0xDE1F, "UnexpectedResponseProtocolId" }, + { 0xE01F, "UnexpectedResponseProtocolVersion" }, + { 0xE21F, "UnexpectedResponsePacketCategory" }, + { 0xE41F, "UnexpectedResponsePacketType" }, + { 0xE61F, "UnexpectedResponseBodySize" }, + { 0xE81F, "UnexpectedResponseBody" }, + { 0x1901F, "InternalError" }, + { 0x1921F, "InvalidSize" }, + { 0x1A61F, "UnknownError" }, + { 0x1A81F, "UnsupportedProtocolVersion" }, + { 0x1AA1F, "InvalidRequest" }, + { 0x1AC1F, "InvalidHandle" }, + { 0x1AE1F, "OutOfHandle" }, + { 0x265, "NoAck" }, + { 0x465, "BusBusy" }, + { 0x665, "CommandListFull" }, + { 0xA65, "UnknownDevice" }, + { 0x1FA65, "Timeout" }, + { 0x266, "AlreadyBound" }, + { 0x466, "AlreadyOpen" }, + { 0x666, "DeviceNotFound" }, + { 0x866, "InvalidArgument" }, + { 0xC66, "NotOpen" }, + { 0x1669, "SettingsItemNotFound" }, + { 0xC869, "InternalError" }, + { 0xCA69, "SettingsItemKeyAllocationFailed" }, + { 0xCC69, "SettingsItemValueAllocationFailed" }, + { 0x19069, "InvalidArgument" }, + { 0x19269, "SettingsNameNull" }, + { 0x19469, "SettingsItemKeyNull" }, + { 0x19669, "SettingsItemValueNull" }, + { 0x19869, "SettingsItemKeyBufferNull" }, + { 0x19A69, "SettingsItemValueBufferNull" }, + { 0x1BA69, "SettingsNameEmpty" }, + { 0x1BC69, "SettingsItemKeyEmpty" }, + { 0x1E269, "SettingsNameTooLong" }, + { 0x1E469, "SettingsItemKeyTooLong" }, + { 0x20A69, "SettingsNameInvalidFormat" }, + { 0x20C69, "SettingsItemKeyInvalidFormat" }, + { 0x20E69, "SettingsItemValueInvalidFormat" }, + { 0x48869, "CalibrationDataError" }, + { 0x48A69, "CalibrationDataFileSystemCorrupted" }, + { 0x48C69, "CalibrationDataCrcError" }, + { 0x48E69, "CalibrationDataShaError" }, + { 0x272, "OperationFailed" }, + { 0xC72, "NotSupported" }, + { 0xE72, "NotFound" }, + { 0x74, "NotInitialized" }, + { 0x274, "NoCapability" }, + { 0xCC74, "OffsetInvalid" }, + { 0xCE74, "UninitializedClock" }, + { 0x19074, "NotComparable" }, + { 0x19274, "Overflowed" }, + { 0x64274, "OutOfMemory" }, + { 0x70874, "InvalidArgument" }, + { 0x70A74, "InvalidPointer" }, + { 0x70C74, "OutOfRange" }, + { 0x70E74, "InvalidTimeZoneBinary" }, + { 0x7BA74, "NotFound" }, + { 0x7BC74, "NotImplemented" }, + { 0x27A, "InvalidArgument" }, + { 0x47A, "NotFound" }, + { 0x67A, "TargetLocked" }, + { 0x87A, "TargetAlreadyMounted" }, + { 0xA7A, "TargetNotMounted" }, + { 0xC7A, "AlreadyOpen" }, + { 0xE7A, "NotOpen" }, + { 0x107A, "InternetRequestDenied" }, + { 0x127A, "ServiceOpenLimitReached" }, + { 0x147A, "SaveDataNotFound" }, + { 0x3E7A, "NetworkServiceAccountNotAvailable" }, + { 0xA07A, "PassphrasePathNotFound" }, + { 0xA27A, "DataVerificationFailed" }, + { 0xB47A, "PermissionDenied" }, + { 0xB67A, "AllocationFailed" }, + { 0xC47A, "InvalidOperation" }, + { 0x1987A, "InvalidDeliveryCacheStorageFile" }, + { 0x19A7A, "StorageOpenLimitReached" }, + { 0x7B, "SslService" }, + { 0x7C, "Cancelled" }, + { 0x27C, "CancelledByUser" }, + { 0xC87C, "UserNotExist" }, + { 0x1907C, "NetworkServiceAccountUnavailable" }, + { 0x35C7C, "TokenCacheUnavailable" }, + { 0x17707C, "NetworkCommunicationError" }, + { 0x2085, "IllegalRequest" }, + { 0x8C89, "HttpConnectionCanceled" }, + { 0x48A, "AlreadyInitialized" }, + { 0x68A, "NotInitialized" }, + { 0x8C, "NotInitialized" }, + { 0x28C, "AlreadyInitialized" }, + { 0xC88C, "InvalidParameter" }, + { 0xCE8C, "AlignmentError" }, + { 0x1928C, "OperationDenied" }, + { 0x1948C, "MemAllocFailure" }, + { 0x19C8C, "ResourceBusy" }, + { 0x19E8C, "InternalStateError" }, + { 0x3228C, "TransactionError" }, + { 0x3328C, "Interrupted" }, + { 0x293, "NotInitialized" }, + { 0x493, "AlreadyInitialized" }, + { 0x693, "OutOfArraySpace" }, + { 0x893, "OutOfFieldSpace" }, + { 0xA93, "OutOfMemory" }, + { 0xC93, "NotSupported" }, + { 0xE93, "InvalidArgument" }, + { 0x1093, "NotFound" }, + { 0x1293, "FieldCategoryMismatch" }, + { 0x1493, "FieldTypeMismatch" }, + { 0x1693, "AlreadyExists" }, + { 0x1893, "CorruptJournal" }, + { 0x1A93, "CategoryNotFound" }, + { 0x1C93, "RequiredContextMissing" }, + { 0x1E93, "RequiredFieldMissing" }, + { 0x2093, "FormatterError" }, + { 0x2293, "InvalidPowerState" }, + { 0x2493, "ArrayFieldTooLarge" }, + { 0x2693, "AlreadyOwned" }, + { 0x49E, "BootImagePackageNotFound" }, + { 0x69E, "InvalidBootImagePackage" }, + { 0x89E, "TooSmallWorkBuffer" }, + { 0xA9E, "NotAlignedWorkBuffer" }, + { 0xC9E, "NeedsRepairBootImages" }, + { 0x2A2, "ApplicationAborted" }, + { 0x4A2, "SystemModuleAborted" }, + { 0x2A3, "AllocationFailed" }, + { 0x4A3, "NullGraphicsBuffer" }, + { 0x6A3, "AlreadyThrown" }, + { 0x8A3, "TooManyEvents" }, + { 0xAA3, "InRepairWithoutVolHeld" }, + { 0xCA3, "InRepairWithoutTimeReviserCartridge" }, + { 0xA8, "UndefinedInstruction" }, + { 0x2A8, "InstructionAbort" }, + { 0x4A8, "DataAbort" }, + { 0x6A8, "AlignmentFault" }, + { 0x8A8, "DebuggerAttached" }, + { 0xAA8, "BreakPoint" }, + { 0xCA8, "UserBreak" }, + { 0xEA8, "DebuggerBreak" }, + { 0x10A8, "UndefinedSystemCall" }, + { 0x12A8, "MemorySystemError" }, + { 0xC6A8, "IncompleteReport" }, + { 0x2B7, "CannotDebug" }, + { 0x4B7, "AlreadyAttached" }, + { 0x6B7, "Cancelled" }, + { 0x4BD, "InvalidArgument" }, + { 0x2C6, "NotSupported" }, + { 0x4C6, "InvalidArgument" }, + { 0x6C6, "NotAvailable" }, + { 0xCAC6, "CalibrationDataCrcError" }, + { 0x118CA, "Invalid" }, + { 0x4B2CA, "DualConnected" }, + { 0x4B4CA, "SameJoyTypeConnected" }, + { 0x4B6CA, "ColorNotAvailable" }, + { 0x4B8CA, "ControllerNotConnected" }, + { 0x183ACA, "Canceled" }, + { 0x183CCA, "NotSupportedNpadStyle" }, + { 0x1900CA, "ControllerFirmwareUpdateError" }, + { 0x1902CA, "ControllerFirmwareUpdateFailed" }, + { 0x4CC, "UnknownCommand" }, + { 0x8CC, "OutOfResource" }, + { 0xECC, "NoSocket" }, + { 0xDCCD, "IrsensorUnavailable" }, + { 0xDECD, "IrsensorUnsupported" }, + { 0xF0CD, "IrsensorNotReady" }, + { 0xF4CD, "IrsensorDeviceError" }, + { 0x4CE, "AlbumError" }, + { 0x6CE, "AlbumWorkMemoryError" }, + { 0xECE, "AlbumAlreadyOpened" }, + { 0x10CE, "AlbumOutOfRange" }, + { 0x14CE, "AlbumInvalidFileId" }, + { 0x16CE, "AlbumInvalidApplicationId" }, + { 0x18CE, "AlbumInvalidTimestamp" }, + { 0x1ACE, "AlbumInvalidStorage" }, + { 0x1CCE, "AlbumInvalidFileContents" }, + { 0x2ACE, "AlbumIsNotMounted" }, + { 0x2CCE, "AlbumIsFull" }, + { 0x2ECE, "AlbumFileNotFound" }, + { 0x30CE, "AlbumInvalidFileData" }, + { 0x32CE, "AlbumFileCountLimit" }, + { 0x34CE, "AlbumFileNoThumbnail" }, + { 0x3CCE, "AlbumReadBufferShortage" }, + { 0xB4CE, "AlbumFileSystemError" }, + { 0xBCCE, "AlbumAccessCorrupted" }, + { 0xC0CE, "AlbumDestinationAccessCorrupted" }, + { 0x640CE, "ControlError" }, + { 0x668CE, "ControlResourceLimit" }, + { 0x66CCE, "ControlNotOpened" }, + { 0x7FECE, "NotSupported" }, + { 0x800CE, "InternalError" }, + { 0x974CE, "InternalJpegEncoderError" }, + { 0x978CE, "InternalJpegWorkMemoryShortage" }, + { 0xA28CE, "InternalFileDataVerificationError" }, + { 0xA2ACE, "InternalFileDataVerificationEmptyFileData" }, + { 0xA2CCE, "InternalFileDataVerificationExifExtractionFailed" }, + { 0xA2ECE, "InternalFileDataVerificationExifAnalyzationFailed" }, + { 0xA30CE, "InternalFileDataVerificationDateTimeExtractionFailed" }, + { 0xA32CE, "InternalFileDataVerificationInvalidDateTimeLength" }, + { 0xA34CE, "InternalFileDataVerificationInconsistentDateTime" }, + { 0xA36CE, "InternalFileDataVerificationMakerNoteExtractionFailed" }, + { 0xA38CE, "InternalFileDataVerificationInconsistentApplicationId" }, + { 0xA3ACE, "InternalFileDataVerificationInconsistentSignature" }, + { 0xA3CCE, "InternalFileDataVerificationUnsupportedOrientation" }, + { 0xA3ECE, "InternalFileDataVerificationInvalidDataDimension" }, + { 0xA40CE, "InternalFileDataVerificationInconsistentOrientation" }, + { 0xAF0CE, "InternalAlbumLimitationError" }, + { 0xAF2CE, "InternalAlbumLimitationFileCountLimit" }, + { 0xBB8CE, "InternalSignatureError" }, + { 0xBBACE, "InternalSignatureExifExtractionFailed" }, + { 0xBBCCE, "InternalSignatureMakerNoteExtractionFailed" }, + { 0xD48CE, "InternalAlbumSessionError" }, + { 0xD4ACE, "InternalAlbumLimitationSessionCountLimit" }, + { 0xED8CE, "InternalAlbumTemporaryFileError" }, + { 0xEDACE, "InternalAlbumTemporaryFileCountLimit" }, + { 0xEDCCE, "InternalAlbumTemporaryFileCreateError" }, + { 0xEDECE, "InternalAlbumTemporaryFileCreateRetryCountLimit" }, + { 0xEE0CE, "InternalAlbumTemporaryFileOpenError" }, + { 0xEE2CE, "InternalAlbumTemporaryFileGetFileSizeError" }, + { 0xEE4CE, "InternalAlbumTemporaryFileSetFileSizeError" }, + { 0xEE6CE, "InternalAlbumTemporaryFileReadFileError" }, + { 0xEE8CE, "InternalAlbumTemporaryFileWriteFileError" }, + { 0x2E4, "NotImplemented" }, + { 0x4E4, "NotAvailable" }, + { 0x6E4, "ApplicationNotRunning" }, + { 0x8E4, "BufferNotEnough" }, + { 0xAE4, "ApplicationContentNotFound" }, + { 0xCE4, "ContentMetaNotFound" }, + { 0xEE4, "OutOfMemory" }, + { 0x3AC, "InvalidArgument" }, + { 0x5AC, "NullArgument" }, + { 0x7AC, "ArgumentOutOfRange" }, + { 0x9AC, "BufferTooSmall" }, + { 0x67AC, "ServiceNotInitialized" }, + { 0xCBAC, "NotImplemented" }, + { 0x7D1AC, "InvalidData" }, + { 0x7D3AC, "InvalidInitialProcessData" }, + { 0x7D5AC, "InvalidKip" }, + { 0x7D7AC, "InvalidKipFileSize" }, + { 0x7D9AC, "InvalidKipMagic" }, + { 0x7DBAC, "InvalidKipSegmentSize" }, + { 0x7DDAC, "KipSegmentDecompressionFailed" }, + { 0x7E5AC, "InvalidIni" }, + { 0x7E7AC, "InvalidIniFileSize" }, + { 0x7E9AC, "InvalidIniMagic" }, + { 0x7EBAC, "InvalidIniProcessCount" }, + { 0x7F9AC, "InvalidPackage2" }, + { 0x7FBAC, "InvalidPackage2HeaderSignature" }, + { 0x7FDAC, "InvalidPackage2MetaSizeA" }, + { 0x7FFAC, "InvalidPackage2MetaSizeB" }, + { 0x801AC, "InvalidPackage2MetaKeyGeneration" }, + { 0x803AC, "InvalidPackage2MetaMagic" }, + { 0x805AC, "InvalidPackage2MetaEntryPointAlignment" }, + { 0x807AC, "InvalidPackage2MetaPayloadAlignment" }, + { 0x809AC, "InvalidPackage2MetaPayloadSizeAlignment" }, + { 0x80BAC, "InvalidPackage2MetaTotalSize" }, + { 0x80DAC, "InvalidPackage2MetaPayloadSize" }, + { 0x80FAC, "InvalidPackage2MetaPayloadsOverlap" }, + { 0x811AC, "InvalidPackage2MetaEntryPointNotFound" }, + { 0x813AC, "InvalidPackage2PayloadCorrupted" }, + { 0x821AC, "InvalidPackage1" }, + { 0x823AC, "InvalidPackage1SectionSize" }, + { 0x825AC, "InvalidPackage1MarikoBodySize" }, + { 0x827AC, "InvalidPackage1Pk11Size" }, + }; + + public static bool TryGet(int errorCode, out string name) + { + return _names.TryGetValue(errorCode, out name); + } + } +} diff --git a/src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj b/src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj new file mode 100644 index 00000000..fa1544c4 --- /dev/null +++ b/src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + + + + + + + diff --git a/src/Ryujinx.Horizon.Common/ThreadTerminatedException.cs b/src/Ryujinx.Horizon.Common/ThreadTerminatedException.cs new file mode 100644 index 00000000..c86cb05f --- /dev/null +++ b/src/Ryujinx.Horizon.Common/ThreadTerminatedException.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ryujinx.Horizon.Common +{ + public class ThreadTerminatedException : Exception + { + public ThreadTerminatedException() : base("The thread has been terminated.") + { + } + + public ThreadTerminatedException(string message) : base(message) + { + } + + public ThreadTerminatedException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/Ryujinx.Horizon.Generators/CodeGenerator.cs b/src/Ryujinx.Horizon.Generators/CodeGenerator.cs new file mode 100644 index 00000000..9d117f55 --- /dev/null +++ b/src/Ryujinx.Horizon.Generators/CodeGenerator.cs @@ -0,0 +1,63 @@ +using System.Text; + +namespace Ryujinx.Horizon.Generators +{ + class CodeGenerator + { + private const int IndentLength = 4; + + private readonly StringBuilder _sb; + private int _currentIndentCount; + + public CodeGenerator() + { + _sb = new StringBuilder(); + } + + public void EnterScope(string header = null) + { + if (header != null) + { + AppendLine(header); + } + + AppendLine("{"); + IncreaseIndentation(); + } + + public void LeaveScope(string suffix = "") + { + DecreaseIndentation(); + AppendLine($"}}{suffix}"); + } + + public void IncreaseIndentation() + { + _currentIndentCount++; + } + + public void DecreaseIndentation() + { + if (_currentIndentCount - 1 >= 0) + { + _currentIndentCount--; + } + } + + public void AppendLine() + { + _sb.AppendLine(); + } + + public void AppendLine(string text) + { + _sb.Append(' ', IndentLength * _currentIndentCount); + _sb.AppendLine(text); + } + + public override string ToString() + { + return _sb.ToString(); + } + } +} diff --git a/src/Ryujinx.Horizon.Generators/Hipc/CommandArgType.cs b/src/Ryujinx.Horizon.Generators/Hipc/CommandArgType.cs new file mode 100644 index 00000000..f26eeec1 --- /dev/null +++ b/src/Ryujinx.Horizon.Generators/Hipc/CommandArgType.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Horizon.Generators.Hipc +{ + enum CommandArgType : byte + { + Invalid, + + Buffer, + InArgument, + InCopyHandle, + InMoveHandle, + InObject, + OutArgument, + OutCopyHandle, + OutMoveHandle, + OutObject, + ProcessId, + } +} diff --git a/src/Ryujinx.Horizon.Generators/Hipc/CommandInterface.cs b/src/Ryujinx.Horizon.Generators/Hipc/CommandInterface.cs new file mode 100644 index 00000000..2dcdfe5a --- /dev/null +++ b/src/Ryujinx.Horizon.Generators/Hipc/CommandInterface.cs @@ -0,0 +1,17 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Generators.Hipc +{ + class CommandInterface + { + public ClassDeclarationSyntax ClassDeclarationSyntax { get; } + public List CommandImplementations { get; } + + public CommandInterface(ClassDeclarationSyntax classDeclarationSyntax) + { + ClassDeclarationSyntax = classDeclarationSyntax; + CommandImplementations = new List(); + } + } +} diff --git a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs new file mode 100644 index 00000000..d1be8298 --- /dev/null +++ b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs @@ -0,0 +1,798 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Horizon.Generators.Hipc +{ + [Generator] + class HipcGenerator : ISourceGenerator + { + private const string ArgVariablePrefix = "arg"; + private const string ResultVariableName = "result"; + private const string IsBufferMapAliasVariableName = "isBufferMapAlias"; + private const string InObjectsVariableName = "inObjects"; + private const string OutObjectsVariableName = "outObjects"; + private const string ResponseVariableName = "response"; + private const string OutRawDataVariableName = "outRawData"; + + private const string TypeSystemBuffersReadOnlySequence = "System.Buffers.ReadOnlySequence"; + private const string TypeSystemMemory = "System.Memory"; + private const string TypeSystemReadOnlySpan = "System.ReadOnlySpan"; + private const string TypeSystemSpan = "System.Span"; + private const string TypeStructLayoutAttribute = "System.Runtime.InteropServices.StructLayoutAttribute"; + + public const string CommandAttributeName = "CmifCommandAttribute"; + + private const string TypeResult = "Ryujinx.Horizon.Common.Result"; + private const string TypeBufferAttribute = "Ryujinx.Horizon.Sdk.Sf.BufferAttribute"; + private const string TypeCopyHandleAttribute = "Ryujinx.Horizon.Sdk.Sf.CopyHandleAttribute"; + private const string TypeMoveHandleAttribute = "Ryujinx.Horizon.Sdk.Sf.MoveHandleAttribute"; + private const string TypeClientProcessIdAttribute = "Ryujinx.Horizon.Sdk.Sf.ClientProcessIdAttribute"; + private const string TypeCommandAttribute = "Ryujinx.Horizon.Sdk.Sf." + CommandAttributeName; + private const string TypeIServiceObject = "Ryujinx.Horizon.Sdk.Sf.IServiceObject"; + + private enum Modifier + { + None, + Ref, + Out, + In, + } + + private readonly struct OutParameter + { + public readonly string Name; + public readonly string TypeName; + public readonly int Index; + public readonly CommandArgType Type; + + public OutParameter(string name, string typeName, int index, CommandArgType type) + { + Name = name; + TypeName = typeName; + Index = index; + Type = type; + } + } + + public void Execute(GeneratorExecutionContext context) + { + HipcSyntaxReceiver syntaxReceiver = (HipcSyntaxReceiver)context.SyntaxReceiver; + + foreach (var commandInterface in syntaxReceiver.CommandInterfaces) + { + if (!NeedsIServiceObjectImplementation(context.Compilation, commandInterface.ClassDeclarationSyntax)) + { + continue; + } + + CodeGenerator generator = new CodeGenerator(); + string className = commandInterface.ClassDeclarationSyntax.Identifier.ToString(); + + generator.AppendLine("using Ryujinx.Horizon.Common;"); + generator.AppendLine("using Ryujinx.Horizon.Sdk.Sf;"); + generator.AppendLine("using Ryujinx.Horizon.Sdk.Sf.Cmif;"); + generator.AppendLine("using Ryujinx.Horizon.Sdk.Sf.Hipc;"); + generator.AppendLine("using System;"); + generator.AppendLine("using System.Collections.Frozen;"); + generator.AppendLine("using System.Collections.Generic;"); + generator.AppendLine("using System.Runtime.CompilerServices;"); + generator.AppendLine("using System.Runtime.InteropServices;"); + generator.AppendLine(); + generator.EnterScope($"namespace {GetNamespaceName(commandInterface.ClassDeclarationSyntax)}"); + generator.EnterScope($"partial class {className}"); + + GenerateMethodTable(generator, context.Compilation, commandInterface); + + foreach (var method in commandInterface.CommandImplementations) + { + generator.AppendLine(); + + GenerateMethod(generator, context.Compilation, method); + } + + generator.LeaveScope(); + generator.LeaveScope(); + + context.AddSource($"{GetNamespaceName(commandInterface.ClassDeclarationSyntax)}.{className}.g.cs", generator.ToString()); + } + } + + private static string GetNamespaceName(SyntaxNode syntaxNode) + { + while (syntaxNode != null && !(syntaxNode is NamespaceDeclarationSyntax)) + { + syntaxNode = syntaxNode.Parent; + } + + if (syntaxNode == null) + { + return string.Empty; + } + + return ((NamespaceDeclarationSyntax)syntaxNode).Name.ToString(); + } + + private static void GenerateMethodTable(CodeGenerator generator, Compilation compilation, CommandInterface commandInterface) + { + generator.EnterScope($"public IReadOnlyDictionary GetCommandHandlers()"); + + if (commandInterface.CommandImplementations.Count == 0) + { + generator.AppendLine("return FrozenDictionary.Empty;"); + } + else + { + generator.EnterScope($"return FrozenDictionary.ToFrozenDictionary(new []"); + + foreach (var method in commandInterface.CommandImplementations) + { + foreach (var commandId in GetAttributeArguments(compilation, method, TypeCommandAttribute, 0)) + { + string[] args = new string[method.ParameterList.Parameters.Count]; + + if (args.Length == 0) + { + generator.AppendLine($"KeyValuePair.Create({commandId}, new CommandHandler({method.Identifier.Text}, Array.Empty())),"); + } + else + { + int index = 0; + + foreach (var parameter in method.ParameterList.Parameters) + { + string canonicalTypeName = GetCanonicalTypeNameWithGenericArguments(compilation, parameter.Type); + CommandArgType argType = GetCommandArgType(compilation, parameter); + + string arg; + + if (argType == CommandArgType.Buffer) + { + string bufferFlags = GetFirstAttributeArgument(compilation, parameter, TypeBufferAttribute, 0); + string bufferFixedSize = GetFirstAttributeArgument(compilation, parameter, TypeBufferAttribute, 1); + + if (bufferFixedSize != null) + { + arg = $"new CommandArg({bufferFlags} | HipcBufferFlags.FixedSize, {bufferFixedSize})"; + } + else + { + arg = $"new CommandArg({bufferFlags})"; + } + } + else if (argType == CommandArgType.InArgument || argType == CommandArgType.OutArgument) + { + string alignment = GetTypeAlignmentExpression(compilation, parameter.Type); + + arg = $"new CommandArg(CommandArgType.{argType}, Unsafe.SizeOf<{canonicalTypeName}>(), {alignment})"; + } + else + { + arg = $"new CommandArg(CommandArgType.{argType})"; + } + + args[index++] = arg; + } + + generator.AppendLine($"KeyValuePair.Create({commandId}, new CommandHandler({method.Identifier.Text}, {string.Join(", ", args)})),"); + } + } + } + + generator.LeaveScope(");"); + } + + generator.LeaveScope(); + } + + private static IEnumerable GetAttributeArguments(Compilation compilation, SyntaxNode syntaxNode, string attributeName, int argIndex) + { + ISymbol symbol = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetDeclaredSymbol(syntaxNode); + + foreach (var attribute in symbol.GetAttributes()) + { + if (attribute.AttributeClass.ToDisplayString() == attributeName && (uint)argIndex < (uint)attribute.ConstructorArguments.Length) + { + yield return attribute.ConstructorArguments[argIndex].ToCSharpString(); + } + } + } + + private static string GetFirstAttributeArgument(Compilation compilation, SyntaxNode syntaxNode, string attributeName, int argIndex) + { + return GetAttributeArguments(compilation, syntaxNode, attributeName, argIndex).FirstOrDefault(); + } + + private static void GenerateMethod(CodeGenerator generator, Compilation compilation, MethodDeclarationSyntax method) + { + int inObjectsCount = 0; + int outObjectsCount = 0; + int buffersCount = 0; + + foreach (var parameter in method.ParameterList.Parameters) + { + if (IsObject(compilation, parameter)) + { + if (IsIn(parameter)) + { + inObjectsCount++; + } + else + { + outObjectsCount++; + } + } + else if (IsBuffer(compilation, parameter)) + { + buffersCount++; + } + } + + generator.EnterScope($"private Result {method.Identifier.Text}(" + + "ref ServiceDispatchContext context, " + + "HipcCommandProcessor processor, " + + "ServerMessageRuntimeMetadata runtimeMetadata, " + + "ReadOnlySpan inRawData, " + + "ref Span outHeader)"); + + bool returnsResult = method.ReturnType != null && GetCanonicalTypeName(compilation, method.ReturnType) == TypeResult; + + if (returnsResult || buffersCount != 0 || inObjectsCount != 0) + { + generator.AppendLine($"Result {ResultVariableName};"); + + if (buffersCount != 0) + { + generator.AppendLine($"Span {IsBufferMapAliasVariableName} = stackalloc bool[{method.ParameterList.Parameters.Count}];"); + generator.AppendLine(); + + generator.AppendLine($"{ResultVariableName} = processor.ProcessBuffers(ref context, {IsBufferMapAliasVariableName}, runtimeMetadata);"); + generator.EnterScope($"if ({ResultVariableName}.IsFailure)"); + generator.AppendLine($"return {ResultVariableName};"); + generator.LeaveScope(); + } + + generator.AppendLine(); + } + + List outParameters = new List(); + + string[] args = new string[method.ParameterList.Parameters.Count]; + + if (inObjectsCount != 0) + { + generator.AppendLine($"var {InObjectsVariableName} = new IServiceObject[{inObjectsCount}];"); + generator.AppendLine(); + + generator.AppendLine($"{ResultVariableName} = processor.GetInObjects(context.Processor, {InObjectsVariableName});"); + generator.EnterScope($"if ({ResultVariableName}.IsFailure)"); + generator.AppendLine($"return {ResultVariableName};"); + generator.LeaveScope(); + generator.AppendLine(); + } + + if (outObjectsCount != 0) + { + generator.AppendLine($"var {OutObjectsVariableName} = new IServiceObject[{outObjectsCount}];"); + } + + int index = 0; + int inArgIndex = 0; + int outArgIndex = 0; + int inCopyHandleIndex = 0; + int inMoveHandleIndex = 0; + int inObjectIndex = 0; + + foreach (var parameter in method.ParameterList.Parameters) + { + string name = parameter.Identifier.Text; + string argName = GetPrefixedArgName(name); + string canonicalTypeName = GetCanonicalTypeNameWithGenericArguments(compilation, parameter.Type); + CommandArgType argType = GetCommandArgType(compilation, parameter); + Modifier modifier = GetModifier(parameter); + bool isNonSpanBuffer = false; + + if (modifier == Modifier.Out) + { + if (IsNonSpanOutBuffer(compilation, parameter)) + { + generator.AppendLine($"using var {argName} = CommandSerialization.GetWritableRegion(processor.GetBufferRange({index}));"); + + argName = $"out {GenerateSpanCastElement0(canonicalTypeName, $"{argName}.Memory.Span")}"; + } + else + { + outParameters.Add(new OutParameter(argName, canonicalTypeName, outArgIndex++, argType)); + + argName = $"out {canonicalTypeName} {argName}"; + } + } + else + { + string value = $"default({canonicalTypeName})"; + + switch (argType) + { + case CommandArgType.InArgument: + value = $"CommandSerialization.DeserializeArg<{canonicalTypeName}>(inRawData, processor.GetInArgOffset({inArgIndex++}))"; + break; + case CommandArgType.InCopyHandle: + value = $"CommandSerialization.DeserializeCopyHandle(ref context, {inCopyHandleIndex++})"; + break; + case CommandArgType.InMoveHandle: + value = $"CommandSerialization.DeserializeMoveHandle(ref context, {inMoveHandleIndex++})"; + break; + case CommandArgType.ProcessId: + value = "CommandSerialization.DeserializeClientProcessId(ref context)"; + break; + case CommandArgType.InObject: + value = $"{InObjectsVariableName}[{inObjectIndex++}]"; + break; + case CommandArgType.Buffer: + if (IsMemory(compilation, parameter)) + { + value = $"CommandSerialization.GetWritableRegion(processor.GetBufferRange({index}))"; + } + else if (IsReadOnlySequence(compilation, parameter)) + { + value = $"CommandSerialization.GetReadOnlySequence(processor.GetBufferRange({index}))"; + } + else if (IsReadOnlySpan(compilation, parameter)) + { + string spanGenericTypeName = GetCanonicalTypeNameOfGenericArgument(compilation, parameter.Type, 0); + value = GenerateSpanCast(spanGenericTypeName, $"CommandSerialization.GetReadOnlySpan(processor.GetBufferRange({index}))"); + } + else if (IsSpan(compilation, parameter)) + { + value = $"CommandSerialization.GetWritableRegion(processor.GetBufferRange({index}))"; + } + else + { + value = $"CommandSerialization.GetRef<{canonicalTypeName}>(processor.GetBufferRange({index}))"; + isNonSpanBuffer = true; + } + break; + } + + if (IsMemory(compilation, parameter)) + { + generator.AppendLine($"using var {argName} = {value};"); + + argName = $"{argName}.Memory"; + } + else if (IsSpan(compilation, parameter)) + { + generator.AppendLine($"using var {argName} = {value};"); + + string spanGenericTypeName = GetCanonicalTypeNameOfGenericArgument(compilation, parameter.Type, 0); + argName = GenerateSpanCast(spanGenericTypeName, $"{argName}.Memory.Span"); + } + else if (isNonSpanBuffer) + { + generator.AppendLine($"ref var {argName} = ref {value};"); + } + else if (argType == CommandArgType.InObject) + { + generator.EnterScope($"if (!({value} is {canonicalTypeName} {argName}))"); + generator.AppendLine("return SfResult.InvalidInObject;"); + generator.LeaveScope(); + } + else + { + generator.AppendLine($"var {argName} = {value};"); + } + } + + if (modifier == Modifier.Ref) + { + argName = $"ref {argName}"; + } + else if (modifier == Modifier.In) + { + argName = $"in {argName}"; + } + + args[index++] = argName; + } + + if (args.Length - outParameters.Count > 0) + { + generator.AppendLine(); + } + + if (returnsResult) + { + generator.AppendLine($"{ResultVariableName} = {method.Identifier.Text}({string.Join(", ", args)});"); + generator.AppendLine(); + + generator.AppendLine($"Span {OutRawDataVariableName};"); + generator.AppendLine(); + + generator.EnterScope($"if ({ResultVariableName}.IsFailure)"); + generator.AppendLine($"context.Processor.PrepareForErrorReply(ref context, out {OutRawDataVariableName}, runtimeMetadata);"); + generator.AppendLine($"CommandHandler.GetCmifOutHeaderPointer(ref outHeader, ref {OutRawDataVariableName});"); + generator.AppendLine($"return {ResultVariableName};"); + generator.LeaveScope(); + } + else + { + generator.AppendLine($"{method.Identifier.Text}({string.Join(", ", args)});"); + + generator.AppendLine(); + generator.AppendLine($"Span {OutRawDataVariableName};"); + } + + generator.AppendLine(); + + generator.AppendLine($"var {ResponseVariableName} = context.Processor.PrepareForReply(ref context, out {OutRawDataVariableName}, runtimeMetadata);"); + generator.AppendLine($"CommandHandler.GetCmifOutHeaderPointer(ref outHeader, ref {OutRawDataVariableName});"); + generator.AppendLine(); + + generator.EnterScope($"if ({OutRawDataVariableName}.Length < processor.OutRawDataSize)"); + generator.AppendLine("return SfResult.InvalidOutRawSize;"); + generator.LeaveScope(); + + if (outParameters.Count != 0) + { + generator.AppendLine(); + + int outCopyHandleIndex = 0; + int outMoveHandleIndex = outObjectsCount; + int outObjectIndex = 0; + + for (int outIndex = 0; outIndex < outParameters.Count; outIndex++) + { + OutParameter outParameter = outParameters[outIndex]; + + switch (outParameter.Type) + { + case CommandArgType.OutArgument: + generator.AppendLine($"CommandSerialization.SerializeArg<{outParameter.TypeName}>({OutRawDataVariableName}, processor.GetOutArgOffset({outParameter.Index}), {outParameter.Name});"); + break; + case CommandArgType.OutCopyHandle: + generator.AppendLine($"CommandSerialization.SerializeCopyHandle({ResponseVariableName}, {outCopyHandleIndex++}, {outParameter.Name});"); + break; + case CommandArgType.OutMoveHandle: + generator.AppendLine($"CommandSerialization.SerializeMoveHandle({ResponseVariableName}, {outMoveHandleIndex++}, {outParameter.Name});"); + break; + case CommandArgType.OutObject: + generator.AppendLine($"{OutObjectsVariableName}[{outObjectIndex++}] = {outParameter.Name};"); + break; + } + } + } + + generator.AppendLine(); + + if (outObjectsCount != 0 || buffersCount != 0) + { + if (outObjectsCount != 0) + { + generator.AppendLine($"processor.SetOutObjects(ref context, {ResponseVariableName}, {OutObjectsVariableName});"); + } + + if (buffersCount != 0) + { + generator.AppendLine($"processor.SetOutBuffers({ResponseVariableName}, {IsBufferMapAliasVariableName});"); + } + + generator.AppendLine(); + } + + generator.AppendLine("return Result.Success;"); + generator.LeaveScope(); + } + + private static string GetPrefixedArgName(string name) + { + return ArgVariablePrefix + name[0].ToString().ToUpperInvariant() + name.Substring(1); + } + + private static string GetCanonicalTypeNameOfGenericArgument(Compilation compilation, SyntaxNode syntaxNode, int argIndex) + { + if (syntaxNode is GenericNameSyntax genericNameSyntax) + { + if ((uint)argIndex < (uint)genericNameSyntax.TypeArgumentList.Arguments.Count) + { + return GetCanonicalTypeNameWithGenericArguments(compilation, genericNameSyntax.TypeArgumentList.Arguments[argIndex]); + } + } + + return GetCanonicalTypeName(compilation, syntaxNode); + } + + private static string GetCanonicalTypeNameWithGenericArguments(Compilation compilation, SyntaxNode syntaxNode) + { + TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode); + + return typeInfo.Type.ToDisplayString(); + } + + private static string GetCanonicalTypeName(Compilation compilation, SyntaxNode syntaxNode) + { + TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode); + string typeName = typeInfo.Type.ToDisplayString(); + + int genericArgsStartIndex = typeName.IndexOf('<'); + if (genericArgsStartIndex >= 0) + { + return typeName.Substring(0, genericArgsStartIndex); + } + + return typeName; + } + + private static SpecialType GetSpecialTypeName(Compilation compilation, SyntaxNode syntaxNode) + { + TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode); + + return typeInfo.Type.SpecialType; + } + + private static string GetTypeAlignmentExpression(Compilation compilation, SyntaxNode syntaxNode) + { + TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode); + + // Since there's no way to get the alignment for a arbitrary type here, let's assume that all + // "special" types are primitive types aligned to their own length. + // Otherwise, assume that the type is a custom struct, that either defines an explicit alignment + // or has an alignment of 1 which is the lowest possible value. + if (typeInfo.Type.SpecialType == SpecialType.None) + { + string pack = GetTypeFirstNamedAttributeAgument(compilation, syntaxNode, TypeStructLayoutAttribute, "Pack"); + + return pack ?? "1"; + } + else + { + return $"Unsafe.SizeOf<{typeInfo.Type.ToDisplayString()}>()"; + } + } + + private static string GetTypeFirstNamedAttributeAgument(Compilation compilation, SyntaxNode syntaxNode, string attributeName, string argName) + { + ISymbol symbol = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode).Type; + + foreach (var attribute in symbol.GetAttributes()) + { + if (attribute.AttributeClass.ToDisplayString() == attributeName) + { + foreach (var kv in attribute.NamedArguments) + { + if (kv.Key == argName) + { + return kv.Value.ToCSharpString(); + } + } + } + } + + return null; + } + + private static CommandArgType GetCommandArgType(Compilation compilation, ParameterSyntax parameter) + { + CommandArgType type = CommandArgType.Invalid; + + if (IsIn(parameter)) + { + if (IsArgument(compilation, parameter)) + { + type = CommandArgType.InArgument; + } + else if (IsBuffer(compilation, parameter)) + { + type = CommandArgType.Buffer; + } + else if (IsCopyHandle(compilation, parameter)) + { + type = CommandArgType.InCopyHandle; + } + else if (IsMoveHandle(compilation, parameter)) + { + type = CommandArgType.InMoveHandle; + } + else if (IsObject(compilation, parameter)) + { + type = CommandArgType.InObject; + } + else if (IsProcessId(compilation, parameter)) + { + type = CommandArgType.ProcessId; + } + } + else if (IsOut(parameter)) + { + if (IsArgument(compilation, parameter)) + { + type = CommandArgType.OutArgument; + } + else if (IsNonSpanOutBuffer(compilation, parameter)) + { + type = CommandArgType.Buffer; + } + else if (IsCopyHandle(compilation, parameter)) + { + type = CommandArgType.OutCopyHandle; + } + else if (IsMoveHandle(compilation, parameter)) + { + type = CommandArgType.OutMoveHandle; + } + else if (IsObject(compilation, parameter)) + { + type = CommandArgType.OutObject; + } + } + + return type; + } + + private static bool IsArgument(Compilation compilation, ParameterSyntax parameter) + { + return !IsBuffer(compilation, parameter) && + !IsHandle(compilation, parameter) && + !IsObject(compilation, parameter) && + !IsProcessId(compilation, parameter) && + IsUnmanagedType(compilation, parameter.Type); + } + + private static bool IsBuffer(Compilation compilation, ParameterSyntax parameter) + { + return HasAttribute(compilation, parameter, TypeBufferAttribute) && + IsValidTypeForBuffer(compilation, parameter); + } + + private static bool IsNonSpanOutBuffer(Compilation compilation, ParameterSyntax parameter) + { + return HasAttribute(compilation, parameter, TypeBufferAttribute) && + IsUnmanagedType(compilation, parameter.Type); + } + + private static bool IsValidTypeForBuffer(Compilation compilation, ParameterSyntax parameter) + { + return IsMemory(compilation, parameter) || + IsReadOnlySequence(compilation, parameter) || + IsReadOnlySpan(compilation, parameter) || + IsSpan(compilation, parameter) || + IsUnmanagedType(compilation, parameter.Type); + } + + private static bool IsUnmanagedType(Compilation compilation, SyntaxNode syntaxNode) + { + TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode); + + return typeInfo.Type.IsUnmanagedType; + } + + private static bool IsMemory(Compilation compilation, ParameterSyntax parameter) + { + return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemMemory; + } + + private static bool IsReadOnlySequence(Compilation compilation, ParameterSyntax parameter) + { + return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemBuffersReadOnlySequence; + } + + private static bool IsReadOnlySpan(Compilation compilation, ParameterSyntax parameter) + { + return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemReadOnlySpan; + } + + private static bool IsSpan(Compilation compilation, ParameterSyntax parameter) + { + return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemSpan; + } + + private static bool IsHandle(Compilation compilation, ParameterSyntax parameter) + { + return IsCopyHandle(compilation, parameter) || IsMoveHandle(compilation, parameter); + } + + private static bool IsCopyHandle(Compilation compilation, ParameterSyntax parameter) + { + return HasAttribute(compilation, parameter, TypeCopyHandleAttribute) && + GetSpecialTypeName(compilation, parameter.Type) == SpecialType.System_Int32; + } + + private static bool IsMoveHandle(Compilation compilation, ParameterSyntax parameter) + { + return HasAttribute(compilation, parameter, TypeMoveHandleAttribute) && + GetSpecialTypeName(compilation, parameter.Type) == SpecialType.System_Int32; + } + + private static bool IsObject(Compilation compilation, ParameterSyntax parameter) + { + SyntaxNode syntaxNode = parameter.Type; + TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode); + + return typeInfo.Type.ToDisplayString() == TypeIServiceObject || + typeInfo.Type.AllInterfaces.Any(x => x.ToDisplayString() == TypeIServiceObject); + } + + private static bool IsProcessId(Compilation compilation, ParameterSyntax parameter) + { + return HasAttribute(compilation, parameter, TypeClientProcessIdAttribute) && + GetSpecialTypeName(compilation, parameter.Type) == SpecialType.System_UInt64; + } + + private static bool IsIn(ParameterSyntax parameter) + { + return !IsOut(parameter); + } + + private static bool IsOut(ParameterSyntax parameter) + { + return parameter.Modifiers.Any(SyntaxKind.OutKeyword); + } + + private static Modifier GetModifier(ParameterSyntax parameter) + { + foreach (SyntaxToken syntaxToken in parameter.Modifiers) + { + if (syntaxToken.IsKind(SyntaxKind.RefKeyword)) + { + return Modifier.Ref; + } + else if (syntaxToken.IsKind(SyntaxKind.OutKeyword)) + { + return Modifier.Out; + } + else if (syntaxToken.IsKind(SyntaxKind.InKeyword)) + { + return Modifier.In; + } + } + + return Modifier.None; + } + + private static string GenerateSpanCastElement0(string targetType, string input) + { + return $"{GenerateSpanCast(targetType, input)}[0]"; + } + + private static string GenerateSpanCast(string targetType, string input) + { + return targetType == "byte" + ? input + : $"MemoryMarshal.Cast({input})"; + } + + private static bool HasAttribute(Compilation compilation, ParameterSyntax parameterSyntax, string fullAttributeName) + { + foreach (var attributeList in parameterSyntax.AttributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + if (GetCanonicalTypeName(compilation, attribute) == fullAttributeName) + { + return true; + } + } + } + + return false; + } + + private static bool NeedsIServiceObjectImplementation(Compilation compilation, ClassDeclarationSyntax classDeclarationSyntax) + { + ITypeSymbol type = compilation.GetSemanticModel(classDeclarationSyntax.SyntaxTree).GetDeclaredSymbol(classDeclarationSyntax); + var serviceObjectInterface = type.AllInterfaces.FirstOrDefault(x => x.ToDisplayString() == TypeIServiceObject); + var interfaceMember = serviceObjectInterface?.GetMembers().FirstOrDefault(x => x.Name == "GetCommandHandlers"); + + // Return true only if the class implements IServiceObject but does not actually implement the method + // that the interface defines, since this is the only case we want to handle, if the method already exists + // we have nothing to do. + return serviceObjectInterface != null && type.FindImplementationForInterfaceMember(interfaceMember) == null; + } + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new HipcSyntaxReceiver()); + } + } +} diff --git a/src/Ryujinx.Horizon.Generators/Hipc/HipcSyntaxReceiver.cs b/src/Ryujinx.Horizon.Generators/Hipc/HipcSyntaxReceiver.cs new file mode 100644 index 00000000..9b342107 --- /dev/null +++ b/src/Ryujinx.Horizon.Generators/Hipc/HipcSyntaxReceiver.cs @@ -0,0 +1,58 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Horizon.Generators.Hipc +{ + class HipcSyntaxReceiver : ISyntaxReceiver + { + public List CommandInterfaces { get; } + + public HipcSyntaxReceiver() + { + CommandInterfaces = new List(); + } + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is ClassDeclarationSyntax classDeclaration) + { + if (!classDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword) || classDeclaration.BaseList == null) + { + return; + } + + CommandInterface commandInterface = new CommandInterface(classDeclaration); + + foreach (var memberDeclaration in classDeclaration.Members) + { + if (memberDeclaration is MethodDeclarationSyntax methodDeclaration) + { + VisitMethod(commandInterface, methodDeclaration); + } + } + + CommandInterfaces.Add(commandInterface); + } + } + + private void VisitMethod(CommandInterface commandInterface, MethodDeclarationSyntax methodDeclaration) + { + string attributeName = HipcGenerator.CommandAttributeName.Replace("Attribute", string.Empty); + + if (methodDeclaration.AttributeLists.Count != 0) + { + foreach (var attributeList in methodDeclaration.AttributeLists) + { + if (attributeList.Attributes.Any(x => x.Name.ToString().Contains(attributeName))) + { + commandInterface.CommandImplementations.Add(methodDeclaration); + break; + } + } + } + } + } +} diff --git a/src/Ryujinx.Horizon.Generators/Ryujinx.Horizon.Generators.csproj b/src/Ryujinx.Horizon.Generators/Ryujinx.Horizon.Generators.csproj new file mode 100644 index 00000000..d5880399 --- /dev/null +++ b/src/Ryujinx.Horizon.Generators/Ryujinx.Horizon.Generators.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.0 + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/src/Ryujinx.Horizon.Kernel.Generators/CodeGenerator.cs b/src/Ryujinx.Horizon.Kernel.Generators/CodeGenerator.cs new file mode 100644 index 00000000..393a36c3 --- /dev/null +++ b/src/Ryujinx.Horizon.Kernel.Generators/CodeGenerator.cs @@ -0,0 +1,58 @@ +using System.Text; + +namespace Ryujinx.Horizon.Kernel.Generators +{ + class CodeGenerator + { + private const string Indent = " "; + private readonly StringBuilder _sb; + private string _currentIndent; + + public CodeGenerator() + { + _sb = new StringBuilder(); + } + + public void EnterScope(string header = null) + { + if (header != null) + { + AppendLine(header); + } + + AppendLine("{"); + IncreaseIndentation(); + } + + public void LeaveScope() + { + DecreaseIndentation(); + AppendLine("}"); + } + + public void IncreaseIndentation() + { + _currentIndent += Indent; + } + + public void DecreaseIndentation() + { + _currentIndent = _currentIndent.Substring(0, _currentIndent.Length - Indent.Length); + } + + public void AppendLine() + { + _sb.AppendLine(); + } + + public void AppendLine(string text) + { + _sb.AppendLine(_currentIndent + text); + } + + public override string ToString() + { + return _sb.ToString(); + } + } +} diff --git a/src/Ryujinx.Horizon.Kernel.Generators/Ryujinx.Horizon.Kernel.Generators.csproj b/src/Ryujinx.Horizon.Kernel.Generators/Ryujinx.Horizon.Kernel.Generators.csproj new file mode 100644 index 00000000..d5880399 --- /dev/null +++ b/src/Ryujinx.Horizon.Kernel.Generators/Ryujinx.Horizon.Kernel.Generators.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.0 + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/src/Ryujinx.Horizon.Kernel.Generators/SyscallGenerator.cs b/src/Ryujinx.Horizon.Kernel.Generators/SyscallGenerator.cs new file mode 100644 index 00000000..7419a839 --- /dev/null +++ b/src/Ryujinx.Horizon.Kernel.Generators/SyscallGenerator.cs @@ -0,0 +1,520 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Ryujinx.Horizon.Kernel.Generators +{ + [Generator] + class SyscallGenerator : ISourceGenerator + { + private const string ClassNamespace = "Ryujinx.HLE.HOS.Kernel.SupervisorCall"; + private const string ClassName = "SyscallDispatch"; + private const string A32Suffix = "32"; + private const string A64Suffix = "64"; + private const string ResultVariableName = "result"; + private const string ArgVariablePrefix = "arg"; + private const string ResultCheckHelperName = "LogResultAsTrace"; + + private const string TypeSystemBoolean = "System.Boolean"; + private const string TypeSystemInt32 = "System.Int32"; + private const string TypeSystemInt64 = "System.Int64"; + private const string TypeSystemUInt32 = "System.UInt32"; + private const string TypeSystemUInt64 = "System.UInt64"; + + private const string NamespaceKernel = "Ryujinx.HLE.HOS.Kernel"; + private const string NamespaceHorizonCommon = "Ryujinx.Horizon.Common"; + private const string TypeSvcAttribute = NamespaceKernel + ".SupervisorCall.SvcAttribute"; + private const string TypePointerSizedAttribute = NamespaceKernel + ".SupervisorCall.PointerSizedAttribute"; + private const string TypeResultName = "Result"; + private const string TypeKernelResultName = "KernelResult"; + private const string TypeResult = NamespaceHorizonCommon + "." + TypeResultName; + private const string TypeExecutionContext = "IExecutionContext"; + + private static readonly string[] _expectedResults = new string[] + { + $"{TypeResultName}.Success", + $"{TypeKernelResultName}.TimedOut", + $"{TypeKernelResultName}.Cancelled", + $"{TypeKernelResultName}.PortRemoteClosed", + $"{TypeKernelResultName}.InvalidState", + }; + + private readonly struct OutParameter + { + public readonly string Identifier; + public readonly bool NeedsSplit; + + public OutParameter(string identifier, bool needsSplit = false) + { + Identifier = identifier; + NeedsSplit = needsSplit; + } + } + + private struct RegisterAllocatorA32 + { + private uint _useSet; + private int _linearIndex; + + public int AllocateSingle() + { + return Allocate(); + } + + public (int, int) AllocatePair() + { + _linearIndex += _linearIndex & 1; + + return (Allocate(), Allocate()); + } + + private int Allocate() + { + int regIndex; + + if (_linearIndex < 4) + { + regIndex = _linearIndex++; + } + else + { + regIndex = -1; + + for (int i = 0; i < 32; i++) + { + if ((_useSet & (1 << i)) == 0) + { + regIndex = i; + break; + } + } + + Debug.Assert(regIndex != -1); + } + + _useSet |= 1u << regIndex; + + return regIndex; + } + + public void AdvanceLinearIndex() + { + _linearIndex++; + } + } + + private readonly struct SyscallIdAndName : IComparable + { + public readonly int Id; + public readonly string Name; + + public SyscallIdAndName(int id, string name) + { + Id = id; + Name = name; + } + + public int CompareTo(SyscallIdAndName other) + { + return Id.CompareTo(other.Id); + } + } + + public void Execute(GeneratorExecutionContext context) + { + SyscallSyntaxReceiver syntaxReceiver = (SyscallSyntaxReceiver)context.SyntaxReceiver; + + CodeGenerator generator = new CodeGenerator(); + + generator.AppendLine("using Ryujinx.Common.Logging;"); + generator.AppendLine("using Ryujinx.Cpu;"); + generator.AppendLine($"using {NamespaceKernel}.Common;"); + generator.AppendLine($"using {NamespaceKernel}.Memory;"); + generator.AppendLine($"using {NamespaceKernel}.Process;"); + generator.AppendLine($"using {NamespaceKernel}.Threading;"); + generator.AppendLine($"using {NamespaceHorizonCommon};"); + generator.AppendLine("using System;"); + generator.AppendLine(); + generator.EnterScope($"namespace {ClassNamespace}"); + generator.EnterScope($"static class {ClassName}"); + + GenerateResultCheckHelper(generator); + generator.AppendLine(); + + List syscalls = new List(); + + foreach (var method in syntaxReceiver.SvcImplementations) + { + GenerateMethod32(generator, context.Compilation, method); + GenerateMethod64(generator, context.Compilation, method); + + foreach (AttributeSyntax attribute in method.AttributeLists.SelectMany(attributeList => + attributeList.Attributes.Where(attribute => + GetCanonicalTypeName(context.Compilation, attribute) == TypeSvcAttribute))) + { + syscalls.AddRange(from attributeArg in attribute.ArgumentList.Arguments + where attributeArg.Expression.Kind() == SyntaxKind.NumericLiteralExpression + select (LiteralExpressionSyntax)attributeArg.Expression + into numericLiteral + select new SyscallIdAndName((int)numericLiteral.Token.Value, method.Identifier.Text)); + } + } + + syscalls.Sort(); + + GenerateDispatch(generator, syscalls, A32Suffix); + generator.AppendLine(); + GenerateDispatch(generator, syscalls, A64Suffix); + + generator.LeaveScope(); + generator.LeaveScope(); + + context.AddSource($"{ClassName}.g.cs", generator.ToString()); + } + + private static void GenerateResultCheckHelper(CodeGenerator generator) + { + generator.EnterScope($"private static bool {ResultCheckHelperName}({TypeResultName} {ResultVariableName})"); + + string[] expectedChecks = new string[_expectedResults.Length]; + + for (int i = 0; i < expectedChecks.Length; i++) + { + expectedChecks[i] = $"{ResultVariableName} == {_expectedResults[i]}"; + } + + string checks = string.Join(" || ", expectedChecks); + + generator.AppendLine($"return {checks};"); + generator.LeaveScope(); + } + + private static void GenerateMethod32(CodeGenerator generator, Compilation compilation, MethodDeclarationSyntax method) + { + generator.EnterScope($"private static void {method.Identifier.Text}{A32Suffix}(Syscall syscall, {TypeExecutionContext} context)"); + + string[] args = new string[method.ParameterList.Parameters.Count]; + int index = 0; + + RegisterAllocatorA32 regAlloc = new RegisterAllocatorA32(); + + List outParameters = new List(); + List logInArgs = new List(); + List logOutArgs = new List(); + + foreach (var methodParameter in method.ParameterList.Parameters) + { + string name = methodParameter.Identifier.Text; + string argName = GetPrefixedArgName(name); + string typeName = methodParameter.Type.ToString(); + string canonicalTypeName = GetCanonicalTypeName(compilation, methodParameter.Type); + + if (methodParameter.Modifiers.Any(SyntaxKind.OutKeyword)) + { + bool needsSplit = Is64BitInteger(canonicalTypeName) && !IsPointerSized(compilation, methodParameter); + outParameters.Add(new OutParameter(argName, needsSplit)); + logOutArgs.Add($"{name}: {GetFormattedLogValue(argName, canonicalTypeName)}"); + + argName = $"out {typeName} {argName}"; + + regAlloc.AdvanceLinearIndex(); + } + else + { + if (Is64BitInteger(canonicalTypeName)) + { + if (IsPointerSized(compilation, methodParameter)) + { + int registerIndex = regAlloc.AllocateSingle(); + + generator.AppendLine($"var {argName} = (uint)context.GetX({registerIndex});"); + } + else + { + (int registerIndex, int registerIndex2) = regAlloc.AllocatePair(); + + string valueLow = $"(ulong)(uint)context.GetX({registerIndex})"; + string valueHigh = $"(ulong)(uint)context.GetX({registerIndex2})"; + string value = $"{valueLow} | ({valueHigh} << 32)"; + + generator.AppendLine($"var {argName} = ({typeName})({value});"); + } + } + else + { + int registerIndex = regAlloc.AllocateSingle(); + + string value = GenerateCastFromUInt64($"context.GetX({registerIndex})", canonicalTypeName, typeName); + + generator.AppendLine($"var {argName} = {value};"); + } + + logInArgs.Add($"{name}: {GetFormattedLogValue(argName, canonicalTypeName)}"); + } + + args[index++] = argName; + } + + GenerateLogPrintBeforeCall(generator, method.Identifier.Text, logInArgs); + + string argsList = string.Join(", ", args); + int returnRegisterIndex = 0; + string result = null; + string canonicalReturnTypeName = null; + + if (method.ReturnType.ToString() != "void") + { + generator.AppendLine($"var {ResultVariableName} = syscall.{method.Identifier.Text}({argsList});"); + canonicalReturnTypeName = GetCanonicalTypeName(compilation, method.ReturnType); + + if (canonicalReturnTypeName == TypeResult) + { + generator.AppendLine($"context.SetX({returnRegisterIndex++}, (uint){ResultVariableName}.ErrorCode);"); + } + else + { + generator.AppendLine($"context.SetX({returnRegisterIndex++}, (uint){ResultVariableName});"); + } + + if (Is64BitInteger(canonicalReturnTypeName)) + { + generator.AppendLine($"context.SetX({returnRegisterIndex++}, (uint)({ResultVariableName} >> 32));"); + } + + result = GetFormattedLogValue(ResultVariableName, canonicalReturnTypeName); + } + else + { + generator.AppendLine($"syscall.{method.Identifier.Text}({argsList});"); + } + + foreach (OutParameter outParameter in outParameters) + { + generator.AppendLine($"context.SetX({returnRegisterIndex++}, (uint){outParameter.Identifier});"); + + if (outParameter.NeedsSplit) + { + generator.AppendLine($"context.SetX({returnRegisterIndex++}, (uint)({outParameter.Identifier} >> 32));"); + } + } + + while (returnRegisterIndex < 4) + { + generator.AppendLine($"context.SetX({returnRegisterIndex++}, 0);"); + } + + GenerateLogPrintAfterCall(generator, method.Identifier.Text, logOutArgs, result, canonicalReturnTypeName); + + generator.LeaveScope(); + generator.AppendLine(); + } + + private static void GenerateMethod64(CodeGenerator generator, Compilation compilation, MethodDeclarationSyntax method) + { + generator.EnterScope($"private static void {method.Identifier.Text}{A64Suffix}(Syscall syscall, {TypeExecutionContext} context)"); + + string[] args = new string[method.ParameterList.Parameters.Count]; + int registerIndex = 0; + int index = 0; + + List outParameters = new List(); + List logInArgs = new List(); + List logOutArgs = new List(); + + foreach (var methodParameter in method.ParameterList.Parameters) + { + string name = methodParameter.Identifier.Text; + string argName = GetPrefixedArgName(name); + string typeName = methodParameter.Type.ToString(); + string canonicalTypeName = GetCanonicalTypeName(compilation, methodParameter.Type); + + if (methodParameter.Modifiers.Any(SyntaxKind.OutKeyword)) + { + outParameters.Add(new OutParameter(argName)); + logOutArgs.Add($"{name}: {GetFormattedLogValue(argName, canonicalTypeName)}"); + argName = $"out {typeName} {argName}"; + registerIndex++; + } + else + { + string value = GenerateCastFromUInt64($"context.GetX({registerIndex++})", canonicalTypeName, typeName); + generator.AppendLine($"var {argName} = {value};"); + logInArgs.Add($"{name}: {GetFormattedLogValue(argName, canonicalTypeName)}"); + } + + args[index++] = argName; + } + + GenerateLogPrintBeforeCall(generator, method.Identifier.Text, logInArgs); + + string argsList = string.Join(", ", args); + int returnRegisterIndex = 0; + string result = null; + string canonicalReturnTypeName = null; + + if (method.ReturnType.ToString() != "void") + { + generator.AppendLine($"var {ResultVariableName} = syscall.{method.Identifier.Text}({argsList});"); + canonicalReturnTypeName = GetCanonicalTypeName(compilation, method.ReturnType); + + if (canonicalReturnTypeName == TypeResult) + { + generator.AppendLine($"context.SetX({returnRegisterIndex++}, (ulong){ResultVariableName}.ErrorCode);"); + } + else + { + generator.AppendLine($"context.SetX({returnRegisterIndex++}, (ulong){ResultVariableName});"); + } + + result = GetFormattedLogValue(ResultVariableName, canonicalReturnTypeName); + } + else + { + generator.AppendLine($"syscall.{method.Identifier.Text}({argsList});"); + } + + foreach (OutParameter outParameter in outParameters) + { + generator.AppendLine($"context.SetX({returnRegisterIndex++}, (ulong){outParameter.Identifier});"); + } + + while (returnRegisterIndex < 8) + { + generator.AppendLine($"context.SetX({returnRegisterIndex++}, 0);"); + } + + GenerateLogPrintAfterCall(generator, method.Identifier.Text, logOutArgs, result, canonicalReturnTypeName); + + generator.LeaveScope(); + generator.AppendLine(); + } + + private static string GetFormattedLogValue(string value, string canonicalTypeName) + { + if (Is32BitInteger(canonicalTypeName)) + { + return $"0x{{{value}:X8}}"; + } + else if (Is64BitInteger(canonicalTypeName)) + { + return $"0x{{{value}:X16}}"; + } + + return $"{{{value}}}"; + } + + private static string GetPrefixedArgName(string name) + { + return ArgVariablePrefix + char.ToUpperInvariant(name[0]) + name.Substring(1); + } + + private static string GetCanonicalTypeName(Compilation compilation, SyntaxNode syntaxNode) + { + TypeInfo typeInfo = compilation.GetSemanticModel(syntaxNode.SyntaxTree).GetTypeInfo(syntaxNode); + if (typeInfo.Type.ContainingNamespace == null) + { + return typeInfo.Type.Name; + } + + return $"{typeInfo.Type.ContainingNamespace.ToDisplayString()}.{typeInfo.Type.Name}"; + } + + private static void GenerateLogPrintBeforeCall(CodeGenerator generator, string methodName, List argList) + { + string log = $"{methodName}({string.Join(", ", argList)})"; + GenerateLogPrint(generator, "Trace", "KernelSvc", log); + } + + private static void GenerateLogPrintAfterCall( + CodeGenerator generator, + string methodName, + List argList, + string result, + string canonicalResultTypeName) + { + string log = $"{methodName}({string.Join(", ", argList)})"; + + if (result != null) + { + log += $" = {result}"; + } + + if (canonicalResultTypeName == TypeResult) + { + generator.EnterScope($"if ({ResultCheckHelperName}({ResultVariableName}))"); + GenerateLogPrint(generator, "Trace", "KernelSvc", log); + generator.LeaveScope(); + generator.EnterScope("else"); + GenerateLogPrint(generator, "Warning", "KernelSvc", log); + generator.LeaveScope(); + } + else + { + GenerateLogPrint(generator, "Trace", "KernelSvc", log); + } + } + + private static void GenerateLogPrint(CodeGenerator generator, string logLevel, string logClass, string log) + { + generator.AppendLine($"Logger.{logLevel}?.PrintMsg(LogClass.{logClass}, $\"{log}\");"); + } + + private static void GenerateDispatch(CodeGenerator generator, List syscalls, string suffix) + { + generator.EnterScope($"public static void Dispatch{suffix}(Syscall syscall, {TypeExecutionContext} context, int id)"); + generator.EnterScope("switch (id)"); + + foreach (var syscall in syscalls) + { + generator.AppendLine($"case {syscall.Id}:"); + generator.IncreaseIndentation(); + + generator.AppendLine($"{syscall.Name}{suffix}(syscall, context);"); + generator.AppendLine("break;"); + + generator.DecreaseIndentation(); + } + + generator.AppendLine($"default:"); + generator.IncreaseIndentation(); + + generator.AppendLine("throw new NotImplementedException($\"SVC 0x{id:X4} is not implemented.\");"); + + generator.DecreaseIndentation(); + + generator.LeaveScope(); + generator.LeaveScope(); + } + + private static bool Is32BitInteger(string canonicalTypeName) + { + return canonicalTypeName == TypeSystemInt32 || canonicalTypeName == TypeSystemUInt32; + } + + private static bool Is64BitInteger(string canonicalTypeName) + { + return canonicalTypeName == TypeSystemInt64 || canonicalTypeName == TypeSystemUInt64; + } + + private static string GenerateCastFromUInt64(string value, string canonicalTargetTypeName, string targetTypeName) + { + return canonicalTargetTypeName == TypeSystemBoolean ? $"({value} & 1) != 0" : $"({targetTypeName}){value}"; + } + + private static bool IsPointerSized(Compilation compilation, ParameterSyntax parameterSyntax) + { + return parameterSyntax.AttributeLists.Any(attributeList => + attributeList.Attributes.Any(attribute => + GetCanonicalTypeName(compilation, attribute) == TypePointerSizedAttribute)); + } + + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new SyscallSyntaxReceiver()); + } + } +} diff --git a/src/Ryujinx.Horizon.Kernel.Generators/SyscallSyntaxReceiver.cs b/src/Ryujinx.Horizon.Kernel.Generators/SyscallSyntaxReceiver.cs new file mode 100644 index 00000000..1542fed6 --- /dev/null +++ b/src/Ryujinx.Horizon.Kernel.Generators/SyscallSyntaxReceiver.cs @@ -0,0 +1,53 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Horizon.Kernel.Generators +{ + class SyscallSyntaxReceiver : ISyntaxReceiver + { + public List SvcImplementations { get; } + + public SyscallSyntaxReceiver() + { + SvcImplementations = new List(); + } + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (!(syntaxNode is ClassDeclarationSyntax classDeclaration) || classDeclaration.AttributeLists.Count == 0) + { + return; + } + + if (!classDeclaration.AttributeLists.Any(attributeList => + attributeList.Attributes.Any(x => x.Name.GetText().ToString() == "SvcImpl"))) + { + return; + } + + foreach (var memberDeclaration in classDeclaration.Members) + { + if (memberDeclaration is MethodDeclarationSyntax methodDeclaration) + { + VisitMethod(methodDeclaration); + } + } + } + + private void VisitMethod(MethodDeclarationSyntax methodDeclaration) + { + if (methodDeclaration.AttributeLists.Count == 0) + { + return; + } + + if (methodDeclaration.AttributeLists.Any(attributeList => + attributeList.Attributes.Any(x => x.Name.GetText().ToString() == "Svc"))) + { + SvcImplementations.Add(methodDeclaration); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs b/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs new file mode 100644 index 00000000..a6017b8a --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs @@ -0,0 +1,62 @@ +using Ryujinx.Horizon.Arp.Ipc; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Arp +{ + class ArpIpcServer + { + private const int ArpRMaxSessionsCount = 16; + private const int ArpWMaxSessionsCount = 8; + private const int MaxSessionsCount = ArpRMaxSessionsCount + ArpWMaxSessionsCount; + + private const int PointerBufferSize = 0x1000; + private const int MaxDomains = 24; + private const int MaxDomainObjects = 32; + private const int MaxPortsCount = 2; + + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + private ApplicationInstanceManager _applicationInstanceManager; + + public IReader Reader { get; private set; } + public IWriter Writer { get; private set; } + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount); + + _applicationInstanceManager = new ApplicationInstanceManager(); + + Reader reader = new(_applicationInstanceManager); + Reader = reader; + + Writer writer = new(_applicationInstanceManager); + Writer = writer; + + _serverManager.RegisterObjectForServer(reader, ServiceName.Encode("arp:r"), ArpRMaxSessionsCount); + _serverManager.RegisterObjectForServer(writer, ServiceName.Encode("arp:w"), ArpWMaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _applicationInstanceManager.Dispose(); + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/ArpMain.cs b/src/Ryujinx.Horizon/Arp/ArpMain.cs new file mode 100644 index 00000000..a28baa71 --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/ArpMain.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Horizon.Arp +{ + class ArpMain : IService + { + public static void Main(ServiceTable serviceTable) + { + ArpIpcServer arpIpcServer = new(); + + arpIpcServer.Initialize(); + + serviceTable.ArpReader = arpIpcServer.Reader; + serviceTable.ArpWriter = arpIpcServer.Writer; + + serviceTable.SignalServiceReady(); + + arpIpcServer.ServiceRequests(); + arpIpcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/Reader.cs b/src/Ryujinx.Horizon/Arp/Ipc/Reader.cs new file mode 100644 index 00000000..de99c2ad --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/Reader.cs @@ -0,0 +1,135 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Ns; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class Reader : IReader, IServiceObject + { + private readonly ApplicationInstanceManager _applicationInstanceManager; + + public Reader(ApplicationInstanceManager applicationInstanceManager) + { + _applicationInstanceManager = applicationInstanceManager; + } + + [CmifCommand(0)] + public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] == null || !_applicationInstanceManager.Entries[applicationInstanceId].LaunchProperty.HasValue) + { + applicationLaunchProperty = default; + + return ArpResult.InvalidInstanceId; + } + + applicationLaunchProperty = _applicationInstanceManager.Entries[applicationInstanceId].LaunchProperty.Value; + + return Result.Success; + } + + [CmifCommand(1)] + public Result GetApplicationControlProperty([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias, 0x4000)] out ApplicationControlProperty applicationControlProperty, ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] == null || !_applicationInstanceManager.Entries[applicationInstanceId].ControlProperty.HasValue) + { + applicationControlProperty = default; + + return ArpResult.InvalidInstanceId; + } + + applicationControlProperty = _applicationInstanceManager.Entries[applicationInstanceId].ControlProperty.Value; + + return Result.Success; + } + + [CmifCommand(2)] + public Result GetApplicationProcessProperty(out ApplicationProcessProperty applicationProcessProperty, ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] == null || !_applicationInstanceManager.Entries[applicationInstanceId].ProcessProperty.HasValue) + { + applicationProcessProperty = default; + + return ArpResult.InvalidInstanceId; + } + + applicationProcessProperty = _applicationInstanceManager.Entries[applicationInstanceId].ProcessProperty.Value; + + return Result.Success; + } + + [CmifCommand(3)] + public Result GetApplicationInstanceId(out ulong applicationInstanceId, ulong pid) + { + applicationInstanceId = 0; + + if (pid == 0) + { + return ArpResult.InvalidPid; + } + + for (int i = 0; i < _applicationInstanceManager.Entries.Length; i++) + { + if (_applicationInstanceManager.Entries[i] != null && _applicationInstanceManager.Entries[i].Pid == pid) + { + applicationInstanceId = (ulong)i; + + return Result.Success; + } + } + + return ArpResult.InvalidPid; + } + + [CmifCommand(4)] + public Result GetApplicationInstanceUnregistrationNotifier(out IUnregistrationNotifier unregistrationNotifier) + { + unregistrationNotifier = new UnregistrationNotifier(_applicationInstanceManager); + + return Result.Success; + } + + [CmifCommand(5)] + public Result ListApplicationInstanceId(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span applicationInstanceIdList) + { + count = 0; + + if (_applicationInstanceManager.Entries[0] != null) + { + applicationInstanceIdList[count++] = 0; + } + + if (_applicationInstanceManager.Entries[1] != null) + { + applicationInstanceIdList[count++] = 1; + } + + return Result.Success; + } + + [CmifCommand(6)] + public Result GetMicroApplicationInstanceId(out ulong microApplicationInstanceId, [ClientProcessId] ulong pid) + { + return GetApplicationInstanceId(out microApplicationInstanceId, pid); + } + + [CmifCommand(7)] + public Result GetApplicationCertificate([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.FixedSize, 0x528)] out ApplicationCertificate applicationCertificate, ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] == null) + { + applicationCertificate = default; + + return ArpResult.InvalidInstanceId; + } + + applicationCertificate = _applicationInstanceManager.Entries[applicationInstanceId].Certificate.Value; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/Registrar.cs b/src/Ryujinx.Horizon/Arp/Ipc/Registrar.cs new file mode 100644 index 00000000..841ab760 --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/Registrar.cs @@ -0,0 +1,52 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Ns; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class Registrar : IRegistrar, IServiceObject + { + private readonly ApplicationInstance _applicationInstance; + + public Registrar(ApplicationInstance applicationInstance) + { + _applicationInstance = applicationInstance; + } + + [CmifCommand(0)] + public Result Issue(out ulong applicationInstanceId) + { + throw new NotImplementedException(); + } + + [CmifCommand(1)] + public Result SetApplicationLaunchProperty(ApplicationLaunchProperty applicationLaunchProperty) + { + if (_applicationInstance.LaunchProperty != null) + { + return ArpResult.DataAlreadyBound; + } + + _applicationInstance.LaunchProperty = applicationLaunchProperty; + + return Result.Success; + } + + [CmifCommand(2)] + public Result SetApplicationControlProperty([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.FixedSize, 0x4000)] in ApplicationControlProperty applicationControlProperty) + { + if (_applicationInstance.ControlProperty != null) + { + return ArpResult.DataAlreadyBound; + } + + _applicationInstance.ControlProperty = applicationControlProperty; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/UnregistrationNotifier.cs b/src/Ryujinx.Horizon/Arp/Ipc/UnregistrationNotifier.cs new file mode 100644 index 00000000..49f2b1cc --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/UnregistrationNotifier.cs @@ -0,0 +1,25 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class UnregistrationNotifier : IUnregistrationNotifier, IServiceObject + { + private readonly ApplicationInstanceManager _applicationInstanceManager; + + public UnregistrationNotifier(ApplicationInstanceManager applicationInstanceManager) + { + _applicationInstanceManager = applicationInstanceManager; + } + + [CmifCommand(0)] + public Result GetReadableHandle([CopyHandle] out int readableHandle) + { + readableHandle = _applicationInstanceManager.EventHandle; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/Updater.cs b/src/Ryujinx.Horizon/Arp/Ipc/Updater.cs new file mode 100644 index 00000000..f7531d71 --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/Updater.cs @@ -0,0 +1,74 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class Updater : IUpdater, IServiceObject + { + private readonly ApplicationInstanceManager _applicationInstanceManager; + private readonly ulong _applicationInstanceId; + private readonly bool _forCertificate; + + public Updater(ApplicationInstanceManager applicationInstanceManager, ulong applicationInstanceId, bool forCertificate) + { + _applicationInstanceManager = applicationInstanceManager; + _applicationInstanceId = applicationInstanceId; + _forCertificate = forCertificate; + } + + [CmifCommand(0)] + public Result Issue() + { + throw new NotImplementedException(); + } + + [CmifCommand(1)] + public Result SetApplicationProcessProperty(ulong pid, ApplicationProcessProperty applicationProcessProperty) + { + if (_forCertificate) + { + return ArpResult.DataAlreadyBound; + } + + if (pid == 0) + { + return ArpResult.InvalidPid; + } + + _applicationInstanceManager.Entries[_applicationInstanceId].Pid = pid; + _applicationInstanceManager.Entries[_applicationInstanceId].ProcessProperty = applicationProcessProperty; + + return Result.Success; + } + + [CmifCommand(2)] + public Result DeleteApplicationProcessProperty() + { + if (_forCertificate) + { + return ArpResult.DataAlreadyBound; + } + + _applicationInstanceManager.Entries[_applicationInstanceId].ProcessProperty = new ApplicationProcessProperty(); + + return Result.Success; + } + + [CmifCommand(3)] + public Result SetApplicationCertificate([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ApplicationCertificate applicationCertificate) + { + if (!_forCertificate) + { + return ArpResult.DataAlreadyBound; + } + + _applicationInstanceManager.Entries[_applicationInstanceId].Certificate = applicationCertificate; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/Writer.cs b/src/Ryujinx.Horizon/Arp/Ipc/Writer.cs new file mode 100644 index 00000000..29c98b77 --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/Writer.cs @@ -0,0 +1,75 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class Writer : IWriter, IServiceObject + { + private readonly ApplicationInstanceManager _applicationInstanceManager; + + public Writer(ApplicationInstanceManager applicationInstanceManager) + { + _applicationInstanceManager = applicationInstanceManager; + } + + [CmifCommand(0)] + public Result AcquireRegistrar(out IRegistrar registrar) + { + if (_applicationInstanceManager.Entries[0] != null) + { + if (_applicationInstanceManager.Entries[1] != null) + { + registrar = null; + + return ArpResult.NoFreeInstance; + } + else + { + _applicationInstanceManager.Entries[1] = new ApplicationInstance(); + + registrar = new Registrar(_applicationInstanceManager.Entries[1]); + } + } + else + { + _applicationInstanceManager.Entries[0] = new ApplicationInstance(); + + registrar = new Registrar(_applicationInstanceManager.Entries[0]); + } + + return Result.Success; + } + + [CmifCommand(1)] + public Result UnregisterApplicationInstance(ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] != null) + { + _applicationInstanceManager.Entries[applicationInstanceId] = null; + } + + Os.SignalSystemEvent(ref _applicationInstanceManager.SystemEvent); + + return Result.Success; + } + + [CmifCommand(2)] + public Result AcquireApplicationProcessPropertyUpdater(out IUpdater updater, ulong applicationInstanceId) + { + updater = new Updater(_applicationInstanceManager, applicationInstanceId, false); + + return Result.Success; + } + + [CmifCommand(3)] + public Result AcquireApplicationCertificateUpdater(out IUpdater updater, ulong applicationInstanceId) + { + updater = new Updater(_applicationInstanceManager, applicationInstanceId, true); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Audio/AudioMain.cs b/src/Ryujinx.Horizon/Audio/AudioMain.cs new file mode 100644 index 00000000..92c9e804 --- /dev/null +++ b/src/Ryujinx.Horizon/Audio/AudioMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Audio +{ + class AudioMain : IService + { + public static void Main(ServiceTable serviceTable) + { + AudioUserIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Audio/AudioManagers.cs b/src/Ryujinx.Horizon/Audio/AudioManagers.cs new file mode 100644 index 00000000..493a6f9b --- /dev/null +++ b/src/Ryujinx.Horizon/Audio/AudioManagers.cs @@ -0,0 +1,78 @@ +using Ryujinx.Audio; +using Ryujinx.Audio.Input; +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Output; +using Ryujinx.Audio.Renderer.Device; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Cpu; +using Ryujinx.Horizon.Sdk.Audio; +using System; + +namespace Ryujinx.Horizon.Audio +{ + class AudioManagers : IDisposable + { + public AudioManager AudioManager { get; } + public AudioOutputManager AudioOutputManager { get; } + public AudioInputManager AudioInputManager { get; } + public AudioRendererManager AudioRendererManager { get; } + public VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; } + + public AudioManagers(IHardwareDeviceDriver audioDeviceDriver, ITickSource tickSource) + { + AudioManager = new AudioManager(); + AudioOutputManager = new AudioOutputManager(); + AudioInputManager = new AudioInputManager(); + AudioRendererManager = new AudioRendererManager(tickSource); + AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(audioDeviceDriver); + + IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax]; + + for (int i = 0; i < audioOutputRegisterBufferEvents.Length; i++) + { + audioOutputRegisterBufferEvents[i] = new AudioEvent(); + } + + AudioOutputManager.Initialize(audioDeviceDriver, audioOutputRegisterBufferEvents); + + IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax]; + + for (int i = 0; i < audioInputRegisterBufferEvents.Length; i++) + { + audioInputRegisterBufferEvents[i] = new AudioEvent(); + } + + AudioInputManager.Initialize(audioDeviceDriver, audioInputRegisterBufferEvents); + + IWritableEvent[] systemEvents = new IWritableEvent[Constants.AudioRendererSessionCountMax]; + + for (int i = 0; i < systemEvents.Length; i++) + { + systemEvents[i] = new AudioEvent(); + } + + AudioManager.Initialize(audioDeviceDriver.GetUpdateRequiredEvent(), AudioOutputManager.Update, AudioInputManager.Update); + + AudioRendererManager.Initialize(systemEvents, audioDeviceDriver); + + AudioManager.Start(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + AudioManager.Dispose(); + AudioOutputManager.Dispose(); + AudioInputManager.Dispose(); + AudioRendererManager.Dispose(); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs b/src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs new file mode 100644 index 00000000..20c824e1 --- /dev/null +++ b/src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs @@ -0,0 +1,55 @@ +using Ryujinx.Horizon.Sdk.Audio.Detail; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Audio +{ + class AudioUserIpcServer + { + private const int MaxSessionsCount = 30; + + private const int PointerBufferSize = 0xB40; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 1; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + private AudioManagers _managers; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, MaxSessionsCount); + _managers = new AudioManagers(HorizonStatic.Options.AudioDeviceDriver, HorizonStatic.Options.TickSource); + + AudioRendererManager audioRendererManager = new(_managers.AudioRendererManager, _managers.AudioDeviceSessionRegistry); + AudioOutManager audioOutManager = new(_managers.AudioOutputManager); + AudioInManager audioInManager = new(_managers.AudioInputManager); + FinalOutputRecorderManager finalOutputRecorderManager = new(); + + _serverManager.RegisterObjectForServer(audioRendererManager, ServiceName.Encode("audren:u"), MaxSessionsCount); + _serverManager.RegisterObjectForServer(audioOutManager, ServiceName.Encode("audout:u"), MaxSessionsCount); + _serverManager.RegisterObjectForServer(audioInManager, ServiceName.Encode("audin:u"), MaxSessionsCount); + _serverManager.RegisterObjectForServer(finalOutputRecorderManager, ServiceName.Encode("audrec:u"), MaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _managers.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs b/src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs new file mode 100644 index 00000000..e60e033c --- /dev/null +++ b/src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs @@ -0,0 +1,46 @@ +using Ryujinx.Horizon.Sdk.Codec.Detail; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Audio +{ + class HwopusIpcServer + { + private const int MaxSessionsCount = 24; + + private const int PointerBufferSize = 0x1000; + private const int MaxDomains = 8; + private const int MaxDomainObjects = 256; + private const int MaxPortsCount = 1; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, MaxSessionsCount); + + HardwareOpusDecoderManager hardwareOpusDecoderManager = new(); + + _serverManager.RegisterObjectForServer(hardwareOpusDecoderManager, ServiceName.Encode("hwopus"), MaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Audio/HwopusMain.cs b/src/Ryujinx.Horizon/Audio/HwopusMain.cs new file mode 100644 index 00000000..04eee3fa --- /dev/null +++ b/src/Ryujinx.Horizon/Audio/HwopusMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Audio +{ + class HwopusMain : IService + { + public static void Main(ServiceTable serviceTable) + { + HwopusIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs b/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs new file mode 100644 index 00000000..8da3971c --- /dev/null +++ b/src/Ryujinx.Horizon/Bcat/BcatIpcServer.cs @@ -0,0 +1,50 @@ +using Ryujinx.Horizon.Bcat.Types; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Bcat +{ + internal class BcatIpcServer + { + private const int MaxSessionsCount = 8; + private const int TotalMaxSessionsCount = MaxSessionsCount * 4; + + private const int PointerBufferSize = 0x400; + private const int MaxDomains = 64; + private const int MaxDomainObjects = 64; + private const int MaxPortsCount = 4; + + private SmApi _sm; + private BcatServerManager _serverManager; + + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + internal void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new BcatServerManager(allocator, _sm, MaxPortsCount, _managerOptions, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterServer((int)BcatPortIndex.Admin, ServiceName.Encode("bcat:a"), MaxSessionsCount); + _serverManager.RegisterServer((int)BcatPortIndex.Manager, ServiceName.Encode("bcat:m"), MaxSessionsCount); + _serverManager.RegisterServer((int)BcatPortIndex.User, ServiceName.Encode("bcat:u"), MaxSessionsCount); + _serverManager.RegisterServer((int)BcatPortIndex.System, ServiceName.Encode("bcat:s"), MaxSessionsCount); +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Bcat/BcatMain.cs b/src/Ryujinx.Horizon/Bcat/BcatMain.cs new file mode 100644 index 00000000..ac10d235 --- /dev/null +++ b/src/Ryujinx.Horizon/Bcat/BcatMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Bcat +{ + internal class BcatMain : IService + { + public static void Main(ServiceTable serviceTable) + { + BcatIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Bcat/BcatResult.cs b/src/Ryujinx.Horizon/Bcat/BcatResult.cs new file mode 100644 index 00000000..014c52e7 --- /dev/null +++ b/src/Ryujinx.Horizon/Bcat/BcatResult.cs @@ -0,0 +1,29 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Bcat +{ + class BcatResult + { + private const int ModuleId = 122; + + public static Result Success => new(ModuleId, 0); + public static Result InvalidArgument => new(ModuleId, 1); + public static Result NotFound => new(ModuleId, 2); + public static Result TargetLocked => new(ModuleId, 3); + public static Result TargetAlreadyMounted => new(ModuleId, 4); + public static Result TargetNotMounted => new(ModuleId, 5); + public static Result AlreadyOpen => new(ModuleId, 6); + public static Result NotOpen => new(ModuleId, 7); + public static Result InternetRequestDenied => new(ModuleId, 8); + public static Result ServiceOpenLimitReached => new(ModuleId, 9); + public static Result SaveDataNotFound => new(ModuleId, 10); + public static Result NetworkServiceAccountNotAvailable => new(ModuleId, 31); + public static Result PassphrasePathNotFound => new(ModuleId, 80); + public static Result DataVerificationFailed => new(ModuleId, 81); + public static Result PermissionDenied => new(ModuleId, 90); + public static Result AllocationFailed => new(ModuleId, 91); + public static Result InvalidOperation => new(ModuleId, 98); + public static Result InvalidDeliveryCacheStorageFile => new(ModuleId, 204); + public static Result StorageOpenLimitReached => new(ModuleId, 205); + } +} diff --git a/src/Ryujinx.Horizon/Bcat/BcatServerManager.cs b/src/Ryujinx.Horizon/Bcat/BcatServerManager.cs new file mode 100644 index 00000000..31ac967b --- /dev/null +++ b/src/Ryujinx.Horizon/Bcat/BcatServerManager.cs @@ -0,0 +1,28 @@ +using Ryujinx.Horizon.Bcat.Ipc; +using Ryujinx.Horizon.Bcat.Types; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using System; + +namespace Ryujinx.Horizon.Bcat +{ + class BcatServerManager : ServerManager + { + public BcatServerManager(HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions) + { + } + + protected override Result OnNeedsToAccept(int portIndex, Server server) + { + return (BcatPortIndex)portIndex switch + { + BcatPortIndex.Admin => AcceptImpl(server, new ServiceCreator("bcat:a", BcatServicePermissionLevel.Admin)), + BcatPortIndex.Manager => AcceptImpl(server, new ServiceCreator("bcat:m", BcatServicePermissionLevel.Manager)), + BcatPortIndex.User => AcceptImpl(server, new ServiceCreator("bcat:u", BcatServicePermissionLevel.User)), + BcatPortIndex.System => AcceptImpl(server, new ServiceCreator("bcat:s", BcatServicePermissionLevel.System)), + _ => throw new ArgumentOutOfRangeException(nameof(portIndex)), + }; + } + } +} diff --git a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator.cs b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator.cs new file mode 100644 index 00000000..78114c51 --- /dev/null +++ b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator.cs @@ -0,0 +1,85 @@ +using LibHac.Common; +using Ryujinx.Horizon.Bcat.Types; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Bcat; +using Ryujinx.Horizon.Sdk.Sf; +using System; +using System.Threading; +using ApplicationId = Ryujinx.Horizon.Sdk.Ncm.ApplicationId; + +namespace Ryujinx.Horizon.Bcat.Ipc +{ + partial class ServiceCreator : IServiceCreator, IDisposable + { + private readonly BcatServicePermissionLevel _permissionLevel; + private SharedRef _libHacService; + + private int _disposalState; + + public ServiceCreator(string serviceName, BcatServicePermissionLevel permissionLevel) + { + HorizonStatic.Options.BcatClient.Sm.GetService(ref _libHacService, serviceName).ThrowIfFailure(); + _permissionLevel = permissionLevel; + } + + [CmifCommand(0)] + public Result CreateBcatService(out IBcatService bcatService, [ClientProcessId] ulong pid) + { + // TODO: Call arp:r GetApplicationLaunchProperty with the pid to get the TitleId. + // Add an instance of nn::bcat::detail::service::core::PassphraseManager. + // Add an instance of nn::bcat::detail::service::ServiceMemoryManager. + // Add an instance of nn::bcat::detail::service::core::TaskManager who loads "bcat-sys:/" system save data and opens "dc/task.bin". + // If the file don't exist, create a new one (with a size of 0x800 bytes) and write 2 empty structs with a size of 0x400 bytes. + + bcatService = new BcatService(_permissionLevel); + + return Result.Success; + } + + [CmifCommand(1)] + public Result CreateDeliveryCacheStorageService(out IDeliveryCacheStorageService service, [ClientProcessId] ulong pid) + { + using var libHacService = new SharedRef(); + + var resultCode = _libHacService.Get.CreateDeliveryCacheStorageService(ref libHacService.Ref, pid); + + if (resultCode.IsSuccess()) + { + service = new DeliveryCacheStorageService(ref libHacService.Ref); + } + else + { + service = null; + } + + return resultCode.ToHorizonResult(); + } + + [CmifCommand(2)] + public Result CreateDeliveryCacheStorageServiceWithApplicationId(out IDeliveryCacheStorageService service, ApplicationId applicationId) + { + using var libHacService = new SharedRef(); + + var resultCode = _libHacService.Get.CreateDeliveryCacheStorageServiceWithApplicationId(ref libHacService.Ref, new LibHac.ApplicationId(applicationId.Id)); + + if (resultCode.IsSuccess()) + { + service = new DeliveryCacheStorageService(ref libHacService.Ref); + } + else + { + service = null; + } + + return resultCode.ToHorizonResult(); + } + + public void Dispose() + { + if (Interlocked.Exchange(ref _disposalState, 1) == 0) + { + _libHacService.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/BcatService.cs b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/BcatService.cs new file mode 100644 index 00000000..91beec20 --- /dev/null +++ b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/BcatService.cs @@ -0,0 +1,20 @@ +using Ryujinx.Horizon.Bcat.Types; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Bcat; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Bcat.Ipc +{ + partial class BcatService : IBcatService + { + public BcatService(BcatServicePermissionLevel permissionLevel) { } + + [CmifCommand(10100)] + public Result RequestSyncDeliveryCache(out IDeliveryCacheProgressService deliveryCacheProgressService) + { + deliveryCacheProgressService = new DeliveryCacheProgressService(); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheDirectoryService.cs b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheDirectoryService.cs new file mode 100644 index 00000000..1559c833 --- /dev/null +++ b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheDirectoryService.cs @@ -0,0 +1,48 @@ +using LibHac.Bcat; +using LibHac.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Bcat; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Threading; + +namespace Ryujinx.Horizon.Bcat.Ipc +{ + partial class DeliveryCacheDirectoryService : IDeliveryCacheDirectoryService, IDisposable + { + private SharedRef _libHacService; + private int _disposalState; + + public DeliveryCacheDirectoryService(ref SharedRef libHacService) + { + _libHacService = SharedRef.CreateMove(ref libHacService); + } + + [CmifCommand(0)] + public Result Open(DirectoryName directoryName) + { + return _libHacService.Get.Open(ref directoryName).ToHorizonResult(); + } + + [CmifCommand(1)] + public Result Read(out int entriesRead, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span entriesBuffer) + { + return _libHacService.Get.Read(out entriesRead, entriesBuffer).ToHorizonResult(); + } + + [CmifCommand(2)] + public Result GetCount(out int count) + { + return _libHacService.Get.GetCount(out count).ToHorizonResult(); + } + + public void Dispose() + { + if (Interlocked.Exchange(ref _disposalState, 1) == 0) + { + _libHacService.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheFileService.cs b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheFileService.cs new file mode 100644 index 00000000..bd5c418d --- /dev/null +++ b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheFileService.cs @@ -0,0 +1,54 @@ +using LibHac.Bcat; +using LibHac.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Bcat; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Threading; + +namespace Ryujinx.Horizon.Bcat.Ipc +{ + partial class DeliveryCacheFileService : IDeliveryCacheFileService, IDisposable + { + private SharedRef _libHacService; + private int _disposalState; + + public DeliveryCacheFileService(ref SharedRef libHacService) + { + _libHacService = SharedRef.CreateMove(ref libHacService); + } + + [CmifCommand(0)] + public Result Open(DirectoryName directoryName, FileName fileName) + { + return _libHacService.Get.Open(ref directoryName, ref fileName).ToHorizonResult(); + } + + [CmifCommand(1)] + public Result Read(long offset, out long bytesRead, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span data) + { + return _libHacService.Get.Read(out bytesRead, offset, data).ToHorizonResult(); + } + + [CmifCommand(2)] + public Result GetSize(out long size) + { + return _libHacService.Get.GetSize(out size).ToHorizonResult(); + } + + [CmifCommand(3)] + public Result GetDigest(out Digest digest) + { + return _libHacService.Get.GetDigest(out digest).ToHorizonResult(); + } + + public void Dispose() + { + if (Interlocked.Exchange(ref _disposalState, 1) == 0) + { + _libHacService.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheProgressService.cs b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheProgressService.cs new file mode 100644 index 00000000..a21d140e --- /dev/null +++ b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheProgressService.cs @@ -0,0 +1,58 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Bcat.Ipc.Types; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Bcat; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Threading; + +namespace Ryujinx.Horizon.Bcat.Ipc +{ + partial class DeliveryCacheProgressService : IDeliveryCacheProgressService, IDisposable + { + private int _handle; + private SystemEventType _systemEvent; + private int _disposalState; + + [CmifCommand(0)] + public Result GetEvent([CopyHandle] out int handle) + { + if (_handle == 0) + { + Os.CreateSystemEvent(out _systemEvent, EventClearMode.ManualClear, true).AbortOnFailure(); + + _handle = Os.GetReadableHandleOfSystemEvent(ref _systemEvent); + } + + handle = _handle; + + Logger.Stub?.PrintStub(LogClass.ServiceBcat); + + return Result.Success; + } + + [CmifCommand(1)] + public Result GetImpl([Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x200)] out DeliveryCacheProgressImpl deliveryCacheProgressImpl) + { + deliveryCacheProgressImpl = new DeliveryCacheProgressImpl + { + State = DeliveryCacheProgressImpl.Status.Done, + Result = 0, + }; + + Logger.Stub?.PrintStub(LogClass.ServiceBcat); + + return Result.Success; + } + + public void Dispose() + { + if (_handle != 0 && Interlocked.Exchange(ref _disposalState, 1) == 0) + { + Os.DestroySystemEvent(ref _systemEvent); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheStorageService.cs b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheStorageService.cs new file mode 100644 index 00000000..ecbc4bdb --- /dev/null +++ b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/DeliveryCacheStorageService.cs @@ -0,0 +1,74 @@ +using LibHac.Bcat; +using LibHac.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Bcat; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Threading; + +namespace Ryujinx.Horizon.Bcat.Ipc +{ + partial class DeliveryCacheStorageService : IDeliveryCacheStorageService, IDisposable + { + private SharedRef _libHacService; + private int _disposalState; + + public DeliveryCacheStorageService(ref SharedRef libHacService) + { + _libHacService = SharedRef.CreateMove(ref libHacService); + } + + [CmifCommand(0)] + public Result CreateFileService(out IDeliveryCacheFileService service) + { + using var libHacService = new SharedRef(); + + var resultCode = _libHacService.Get.CreateFileService(ref libHacService.Ref); + + if (resultCode.IsSuccess()) + { + service = new DeliveryCacheFileService(ref libHacService.Ref); + } + else + { + service = null; + } + + return resultCode.ToHorizonResult(); + } + + [CmifCommand(1)] + public Result CreateDirectoryService(out IDeliveryCacheDirectoryService service) + { + using var libHacService = new SharedRef(); + + var resultCode = _libHacService.Get.CreateDirectoryService(ref libHacService.Ref); + + if (resultCode.IsSuccess()) + { + service = new DeliveryCacheDirectoryService(ref libHacService.Ref); + } + else + { + service = null; + } + + return resultCode.ToHorizonResult(); + } + + [CmifCommand(10)] + public Result EnumerateDeliveryCacheDirectory(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span directoryNames) + { + return _libHacService.Get.EnumerateDeliveryCacheDirectory(out count, directoryNames).ToHorizonResult(); + } + + public void Dispose() + { + if (Interlocked.Exchange(ref _disposalState, 1) == 0) + { + _libHacService.Destroy(); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/Types/DeliveryCacheProgressImpl.cs b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/Types/DeliveryCacheProgressImpl.cs new file mode 100644 index 00000000..dcd86cbf --- /dev/null +++ b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/Types/DeliveryCacheProgressImpl.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Bcat.Ipc.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x200)] + public struct DeliveryCacheProgressImpl + { + public enum Status + { + // TODO: determine other values + Done = 9, + } + + public Status State; + public uint Result; + // TODO: reverse the rest of the structure + } +} diff --git a/src/Ryujinx.Horizon/Bcat/Types/BcatPortIndex.cs b/src/Ryujinx.Horizon/Bcat/Types/BcatPortIndex.cs new file mode 100644 index 00000000..942cacdf --- /dev/null +++ b/src/Ryujinx.Horizon/Bcat/Types/BcatPortIndex.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Horizon.Bcat.Types +{ + enum BcatPortIndex + { + Admin, + Manager, + User, + System, + } +} diff --git a/src/Ryujinx.Horizon/Bcat/Types/BcatServicePermissionLevel.cs b/src/Ryujinx.Horizon/Bcat/Types/BcatServicePermissionLevel.cs new file mode 100644 index 00000000..e45f986a --- /dev/null +++ b/src/Ryujinx.Horizon/Bcat/Types/BcatServicePermissionLevel.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Horizon.Bcat.Types +{ + enum BcatServicePermissionLevel + { + Admin = -1, + User = 1, + System = 2, + Manager = 6, + } +} diff --git a/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs b/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs new file mode 100644 index 00000000..a12c0cae --- /dev/null +++ b/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs @@ -0,0 +1,50 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Friends +{ + class FriendsIpcServer + { + private const int MaxSessionsCount = 8; + private const int TotalMaxSessionsCount = MaxSessionsCount * 5; + + private const int PointerBufferSize = 0xA00; + private const int MaxDomains = 64; + private const int MaxDomainObjects = 16; + private const int MaxPortsCount = 5; + + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private FriendsServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new FriendsServerManager(allocator, _sm, MaxPortsCount, _managerOptions, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterServer((int)FriendsPortIndex.Admin, ServiceName.Encode("friend:a"), MaxSessionsCount); + _serverManager.RegisterServer((int)FriendsPortIndex.User, ServiceName.Encode("friend:u"), MaxSessionsCount); + _serverManager.RegisterServer((int)FriendsPortIndex.Viewer, ServiceName.Encode("friend:v"), MaxSessionsCount); + _serverManager.RegisterServer((int)FriendsPortIndex.Manager, ServiceName.Encode("friend:m"), MaxSessionsCount); + _serverManager.RegisterServer((int)FriendsPortIndex.System, ServiceName.Encode("friend:s"), MaxSessionsCount); +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Friends/FriendsMain.cs b/src/Ryujinx.Horizon/Friends/FriendsMain.cs new file mode 100644 index 00000000..0f119cf0 --- /dev/null +++ b/src/Ryujinx.Horizon/Friends/FriendsMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Friends +{ + class FriendsMain : IService + { + public static void Main(ServiceTable serviceTable) + { + FriendsIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs b/src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs new file mode 100644 index 00000000..f567db30 --- /dev/null +++ b/src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Horizon.Friends +{ + enum FriendsPortIndex + { + Admin, + User, + Viewer, + Manager, + System, + } +} diff --git a/src/Ryujinx.Horizon/Friends/FriendsServerManager.cs b/src/Ryujinx.Horizon/Friends/FriendsServerManager.cs new file mode 100644 index 00000000..5026206b --- /dev/null +++ b/src/Ryujinx.Horizon/Friends/FriendsServerManager.cs @@ -0,0 +1,36 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.Friends.Detail.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using System; + +namespace Ryujinx.Horizon.Friends +{ + class FriendsServerManager : ServerManager + { + private readonly IEmulatorAccountManager _accountManager; + private readonly NotificationEventHandler _notificationEventHandler; + + public FriendsServerManager(HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions) + { + _accountManager = HorizonStatic.Options.AccountManager; + _notificationEventHandler = new(); + } + + protected override Result OnNeedsToAccept(int portIndex, Server server) + { + return (FriendsPortIndex)portIndex switch + { +#pragma warning disable IDE0055 // Disable formatting + FriendsPortIndex.Admin => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.Admin)), + FriendsPortIndex.User => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.User)), + FriendsPortIndex.Viewer => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.Viewer)), + FriendsPortIndex.Manager => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.Manager)), + FriendsPortIndex.System => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.System)), + _ => throw new ArgumentOutOfRangeException(nameof(portIndex)), +#pragma warning restore IDE0055 + }; + } + } +} diff --git a/src/Ryujinx.Horizon/HeapAllocator.cs b/src/Ryujinx.Horizon/HeapAllocator.cs new file mode 100644 index 00000000..fc125387 --- /dev/null +++ b/src/Ryujinx.Horizon/HeapAllocator.cs @@ -0,0 +1,143 @@ +using Ryujinx.Common; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Horizon +{ + class HeapAllocator + { + private const ulong InvalidAddress = ulong.MaxValue; + + private readonly struct Range : IComparable + { + public ulong Offset { get; } + public ulong Size { get; } + + public Range(ulong offset, ulong size) + { + Offset = offset; + Size = size; + } + + public int CompareTo(Range other) + { + return Offset.CompareTo(other.Offset); + } + } + + private readonly List _freeRanges; + private ulong _currentHeapSize; + + public HeapAllocator() + { + _freeRanges = new List(); + _currentHeapSize = 0; + } + + public ulong Allocate(ulong size, ulong alignment = 1UL) + { + ulong address = AllocateImpl(size, alignment); + + if (address == InvalidAddress) + { + ExpandHeap(size + alignment - 1UL); + + address = AllocateImpl(size, alignment); + + Debug.Assert(address != InvalidAddress); + } + + return address; + } + + private void ExpandHeap(ulong expansionSize) + { + ulong oldHeapSize = _currentHeapSize; + ulong newHeapSize = BitUtils.AlignUp(oldHeapSize + expansionSize, 0x200000UL); + + _currentHeapSize = newHeapSize; + + HorizonStatic.Syscall.SetHeapSize(out ulong heapAddress, newHeapSize).AbortOnFailure(); + + Free(heapAddress + oldHeapSize, newHeapSize - oldHeapSize); + } + + private ulong AllocateImpl(ulong size, ulong alignment) + { + for (int i = 0; i < _freeRanges.Count; i++) + { + var range = _freeRanges[i]; + + ulong alignedOffset = BitUtils.AlignUp(range.Offset, alignment); + ulong sizeDelta = alignedOffset - range.Offset; + ulong usableSize = range.Size - sizeDelta; + + if (sizeDelta < range.Size && usableSize >= size) + { + _freeRanges.RemoveAt(i); + + if (sizeDelta != 0) + { + InsertFreeRange(range.Offset, sizeDelta); + } + + ulong endOffset = range.Offset + range.Size; + ulong remainingSize = endOffset - (alignedOffset + size); + if (remainingSize != 0) + { + InsertFreeRange(endOffset - remainingSize, remainingSize); + } + + return alignedOffset; + } + } + + return InvalidAddress; + } + + public void Free(ulong offset, ulong size) + { + InsertFreeRangeComingled(offset, size); + } + + private void InsertFreeRange(ulong offset, ulong size) + { + var range = new Range(offset, size); + int index = _freeRanges.BinarySearch(range); + if (index < 0) + { + index = ~index; + } + + _freeRanges.Insert(index, range); + } + + private void InsertFreeRangeComingled(ulong offset, ulong size) + { + ulong endOffset = offset + size; + var range = new Range(offset, size); + int index = _freeRanges.BinarySearch(range); + if (index < 0) + { + index = ~index; + } + + if (index < _freeRanges.Count && _freeRanges[index].Offset == endOffset) + { + endOffset = _freeRanges[index].Offset + _freeRanges[index].Size; + _freeRanges.RemoveAt(index); + } + + if (index > 0 && _freeRanges[index - 1].Offset + _freeRanges[index - 1].Size == offset) + { + offset = _freeRanges[index - 1].Offset; + _freeRanges.RemoveAt(--index); + } + + range = new Range(offset, endOffset - offset); + + _freeRanges.Insert(index, range); + } + } +} diff --git a/src/Ryujinx.Horizon/HorizonOptions.cs b/src/Ryujinx.Horizon/HorizonOptions.cs new file mode 100644 index 00000000..a24ce7f6 --- /dev/null +++ b/src/Ryujinx.Horizon/HorizonOptions.cs @@ -0,0 +1,37 @@ +using LibHac; +using Ryujinx.Audio.Integration; +using Ryujinx.Cpu; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.Fs; + +namespace Ryujinx.Horizon +{ + public readonly struct HorizonOptions + { + public bool IgnoreMissingServices { get; } + public bool ThrowOnInvalidCommandIds { get; } + + public HorizonClient BcatClient { get; } + public IFsClient FsClient { get; } + public IEmulatorAccountManager AccountManager { get; } + public IHardwareDeviceDriver AudioDeviceDriver { get; } + public ITickSource TickSource { get; } + + public HorizonOptions( + bool ignoreMissingServices, + HorizonClient bcatClient, + IFsClient fsClient, + IEmulatorAccountManager accountManager, + IHardwareDeviceDriver audioDeviceDriver, + ITickSource tickSource) + { + IgnoreMissingServices = ignoreMissingServices; + ThrowOnInvalidCommandIds = true; + BcatClient = bcatClient; + FsClient = fsClient; + AccountManager = accountManager; + AudioDeviceDriver = audioDeviceDriver; + TickSource = tickSource; + } + } +} diff --git a/src/Ryujinx.Horizon/HorizonStatic.cs b/src/Ryujinx.Horizon/HorizonStatic.cs new file mode 100644 index 00000000..305d54bd --- /dev/null +++ b/src/Ryujinx.Horizon/HorizonStatic.cs @@ -0,0 +1,44 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.Horizon +{ + public static class HorizonStatic + { + [ThreadStatic] + private static HorizonOptions _options; + + [ThreadStatic] + private static ISyscallApi _syscall; + + [ThreadStatic] + private static IVirtualMemoryManager _addressSpace; + + [ThreadStatic] + private static IThreadContext _threadContext; + + [ThreadStatic] + private static int _threadHandle; + + public static HorizonOptions Options => _options; + public static ISyscallApi Syscall => _syscall; + public static IVirtualMemoryManager AddressSpace => _addressSpace; + public static IThreadContext ThreadContext => _threadContext; + public static int CurrentThreadHandle => _threadHandle; + + public static void Register( + HorizonOptions options, + ISyscallApi syscallApi, + IVirtualMemoryManager addressSpace, + IThreadContext threadContext, + int threadHandle) + { + _options = options; + _syscall = syscallApi; + _addressSpace = addressSpace; + _threadContext = threadContext; + _threadHandle = threadHandle; + } + } +} diff --git a/src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs b/src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs new file mode 100644 index 00000000..b1cc7259 --- /dev/null +++ b/src/Ryujinx.Horizon/Hshl/HshlIpcServer.cs @@ -0,0 +1,48 @@ +using Ryujinx.Horizon.Hshl.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Hshl +{ + class HshlIpcServer + { + private const int HshlMaxSessionsCount = 10; + private const int TotalMaxSessionsCount = HshlMaxSessionsCount * 2; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 2; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterObjectForServer(new SetterManager(), ServiceName.Encode("hshl:set"), HshlMaxSessionsCount); // 11.0.0+ + _serverManager.RegisterObjectForServer(new Manager(), ServiceName.Encode("hshl:sys"), HshlMaxSessionsCount); // 11.0.0+ +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Hshl/HshlMain.cs b/src/Ryujinx.Horizon/Hshl/HshlMain.cs new file mode 100644 index 00000000..9c65f1ff --- /dev/null +++ b/src/Ryujinx.Horizon/Hshl/HshlMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Hshl +{ + class HshlMain : IService + { + public static void Main(ServiceTable serviceTable) + { + HshlIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Hshl/Ipc/Manager.cs b/src/Ryujinx.Horizon/Hshl/Ipc/Manager.cs new file mode 100644 index 00000000..26e901c4 --- /dev/null +++ b/src/Ryujinx.Horizon/Hshl/Ipc/Manager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Hshl; + +namespace Ryujinx.Horizon.Hshl.Ipc +{ + partial class Manager : IManager + { + } +} diff --git a/src/Ryujinx.Horizon/Hshl/Ipc/SetterManager.cs b/src/Ryujinx.Horizon/Hshl/Ipc/SetterManager.cs new file mode 100644 index 00000000..d85a7064 --- /dev/null +++ b/src/Ryujinx.Horizon/Hshl/Ipc/SetterManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Hshl; + +namespace Ryujinx.Horizon.Hshl.Ipc +{ + partial class SetterManager : ISetterManager + { + } +} diff --git a/src/Ryujinx.Horizon/IService.cs b/src/Ryujinx.Horizon/IService.cs new file mode 100644 index 00000000..2bc15b12 --- /dev/null +++ b/src/Ryujinx.Horizon/IService.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Horizon +{ + interface IService + { + abstract static void Main(ServiceTable serviceTable); + } +} diff --git a/src/Ryujinx.Horizon/Ins/InsIpcServer.cs b/src/Ryujinx.Horizon/Ins/InsIpcServer.cs new file mode 100644 index 00000000..4e06dcad --- /dev/null +++ b/src/Ryujinx.Horizon/Ins/InsIpcServer.cs @@ -0,0 +1,48 @@ +using Ryujinx.Horizon.Ins.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Ins +{ + class InsIpcServer + { + private const int InsMaxSessionsCount = 8; + private const int TotalMaxSessionsCount = InsMaxSessionsCount * 2; + + private const int PointerBufferSize = 0x200; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 2; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterObjectForServer(new ReceiverManager(), ServiceName.Encode("ins:r"), InsMaxSessionsCount); // 9.0.0+ + _serverManager.RegisterObjectForServer(new SenderManager(), ServiceName.Encode("ins:s"), InsMaxSessionsCount); // 9.0.0+ +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Ins/InsMain.cs b/src/Ryujinx.Horizon/Ins/InsMain.cs new file mode 100644 index 00000000..101bf9b0 --- /dev/null +++ b/src/Ryujinx.Horizon/Ins/InsMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Ins +{ + class InsMain : IService + { + public static void Main(ServiceTable serviceTable) + { + InsIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Ins/Ipc/ReceiverManager.cs b/src/Ryujinx.Horizon/Ins/Ipc/ReceiverManager.cs new file mode 100644 index 00000000..b5f44801 --- /dev/null +++ b/src/Ryujinx.Horizon/Ins/Ipc/ReceiverManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Ins; + +namespace Ryujinx.Horizon.Ins.Ipc +{ + partial class ReceiverManager : IReceiverManager + { + } +} diff --git a/src/Ryujinx.Horizon/Ins/Ipc/SenderManager.cs b/src/Ryujinx.Horizon/Ins/Ipc/SenderManager.cs new file mode 100644 index 00000000..7407ada7 --- /dev/null +++ b/src/Ryujinx.Horizon/Ins/Ipc/SenderManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Ins; + +namespace Ryujinx.Horizon.Ins.Ipc +{ + partial class SenderManager : ISenderManager + { + } +} diff --git a/src/Ryujinx.Horizon/Lbl/Ipc/LblController.cs b/src/Ryujinx.Horizon/Lbl/Ipc/LblController.cs new file mode 100644 index 00000000..2c97476c --- /dev/null +++ b/src/Ryujinx.Horizon/Lbl/Ipc/LblController.cs @@ -0,0 +1,130 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Lbl; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Lbl.Ipc +{ + partial class LblController : ILblController + { + private bool _vrModeEnabled; + private float _currentBrightnessSettingForVrMode; + + [CmifCommand(17)] + public Result SetBrightnessReflectionDelayLevel(float unknown0, float unknown1) + { + // NOTE: Stubbed in system module. + + return Result.Success; + } + + [CmifCommand(18)] + public Result GetBrightnessReflectionDelayLevel(out float unknown1, float unknown0) + { + // NOTE: Stubbed in system module. + + unknown1 = 0.0f; + + return Result.Success; + } + + [CmifCommand(19)] + public Result SetCurrentBrightnessMapping(float unknown0, float unknown1, float unknown2) + { + // NOTE: Stubbed in system module. + + return Result.Success; + } + + [CmifCommand(20)] + public Result GetCurrentBrightnessMapping(out float unknown0, out float unknown1, out float unknown2) + { + // NOTE: Stubbed in system module. + + unknown0 = 0.0f; + unknown1 = 0.0f; + unknown2 = 0.0f; + + return Result.Success; + } + + [CmifCommand(21)] + public Result SetCurrentAmbientLightSensorMapping(float unknown0, float unknown1, float unknown2) + { + // NOTE: Stubbed in system module. + + return Result.Success; + } + + [CmifCommand(22)] + public Result GetCurrentAmbientLightSensorMapping(out float unknown0, out float unknown1, out float unknown2) + { + // NOTE: Stubbed in system module. + + unknown0 = 0.0f; + unknown1 = 0.0f; + unknown2 = 0.0f; + + return Result.Success; + } + + [CmifCommand(24)] + public Result SetCurrentBrightnessSettingForVrMode(float currentBrightnessSettingForVrMode) + { + if (float.IsNaN(currentBrightnessSettingForVrMode) || float.IsInfinity(currentBrightnessSettingForVrMode)) + { + _currentBrightnessSettingForVrMode = 0.0f; + } + else + { + _currentBrightnessSettingForVrMode = currentBrightnessSettingForVrMode; + } + + return Result.Success; + } + + [CmifCommand(25)] + public Result GetCurrentBrightnessSettingForVrMode(out float currentBrightnessSettingForVrMode) + { + if (float.IsNaN(_currentBrightnessSettingForVrMode) || float.IsInfinity(_currentBrightnessSettingForVrMode)) + { + currentBrightnessSettingForVrMode = 0.0f; + } + else + { + currentBrightnessSettingForVrMode = _currentBrightnessSettingForVrMode; + } + + return Result.Success; + } + + [CmifCommand(26)] + public Result EnableVrMode() + { + _vrModeEnabled = true; + + // NOTE: The service checks _vrModeEnabled field value in a thread and then changes the screen brightness. + // Since we don't support that, it's fine to do nothing. + + return Result.Success; + } + + [CmifCommand(27)] + public Result DisableVrMode() + { + _vrModeEnabled = false; + + // NOTE: The service checks _vrModeEnabled field value in a thread and then changes the screen brightness. + // Since we don't support that, it's fine to do nothing. + + return Result.Success; + } + + [CmifCommand(28)] + public Result IsVrModeEnabled(out bool vrModeEnabled) + { + vrModeEnabled = _vrModeEnabled; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Lbl/LblIpcServer.cs b/src/Ryujinx.Horizon/Lbl/LblIpcServer.cs new file mode 100644 index 00000000..f25fc54b --- /dev/null +++ b/src/Ryujinx.Horizon/Lbl/LblIpcServer.cs @@ -0,0 +1,44 @@ +using Ryujinx.Horizon.Lbl.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Lbl +{ + class LblIpcServer + { + private const int MaxSessionsCount = 5; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 1; + + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount); + + _serverManager.RegisterObjectForServer(new LblController(), ServiceName.Encode("lbl"), MaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Lbl/LblMain.cs b/src/Ryujinx.Horizon/Lbl/LblMain.cs new file mode 100644 index 00000000..ab1e67ac --- /dev/null +++ b/src/Ryujinx.Horizon/Lbl/LblMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Lbl +{ + class LblMain : IService + { + public static void Main(ServiceTable serviceTable) + { + LblIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/LibHacResultExtensions.cs b/src/Ryujinx.Horizon/LibHacResultExtensions.cs new file mode 100644 index 00000000..2abed197 --- /dev/null +++ b/src/Ryujinx.Horizon/LibHacResultExtensions.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon +{ + public static class LibHacResultExtensions + { + public static Result ToHorizonResult(this LibHac.Result result) + { + return new Result((int)result.Module, (int)result.Description); + } + } +} diff --git a/src/Ryujinx.Horizon/LogManager/Ipc/LmLogger.cs b/src/Ryujinx.Horizon/LogManager/Ipc/LmLogger.cs new file mode 100644 index 00000000..b6460a4b --- /dev/null +++ b/src/Ryujinx.Horizon/LogManager/Ipc/LmLogger.cs @@ -0,0 +1,171 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.LogManager.Types; +using Ryujinx.Horizon.Sdk.Lm; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.LogManager.Ipc +{ + partial class LmLogger : ILmLogger + { + private const int MessageLengthLimit = 5000; + + private readonly LogService _log; + private readonly ulong _pid; + + private LogPacket _logPacket; + + public LmLogger(LogService log, ulong pid) + { + _log = log; + _pid = pid; + + _logPacket = new LogPacket(); + } + + [CmifCommand(0)] + public Result Log([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] Span message) + { + if (!SetProcessId(message, _pid)) + { + return Result.Success; + } + + if (LogImpl(message)) + { + Logger.Guest?.Print(LogClass.ServiceLm, _logPacket.ToString()); + + _logPacket = new LogPacket(); + } + + return Result.Success; + } + + [CmifCommand(1)] // 3.0.0+ + public Result SetDestination(LogDestination destination) + { + _log.LogDestination = destination; + + return Result.Success; + } + + private static bool SetProcessId(Span message, ulong processId) + { + ref LogPacketHeader header = ref MemoryMarshal.Cast(message)[0]; + + uint expectedMessageSize = (uint)Unsafe.SizeOf() + header.PayloadSize; + if (expectedMessageSize != (uint)message.Length) + { + Logger.Warning?.Print(LogClass.ServiceLm, $"Invalid message size (expected 0x{expectedMessageSize:X} but got 0x{message.Length:X})."); + + return false; + } + + header.ProcessId = processId; + + return true; + } + + private bool LogImpl(ReadOnlySpan message) + { + SpanReader reader = new(message); + + if (!reader.TryRead(out LogPacketHeader header)) + { + return true; + } + + bool isHeadPacket = (header.Flags & LogPacketFlags.IsHead) != 0; + bool isTailPacket = (header.Flags & LogPacketFlags.IsTail) != 0; + + _logPacket.Severity = header.Severity; + + while (reader.Length > 0) + { + if (!TryReadUleb128(ref reader, out int type) || !TryReadUleb128(ref reader, out int size)) + { + return true; + } + + LogDataChunkKey key = (LogDataChunkKey)type; + + switch (key) + { + case LogDataChunkKey.Start: + reader.Skip(size); + continue; + case LogDataChunkKey.Stop: + break; + case LogDataChunkKey.Line when !reader.TryRead(out _logPacket.Line): + case LogDataChunkKey.DropCount when !reader.TryRead(out _logPacket.DropCount): + case LogDataChunkKey.Time when !reader.TryRead(out _logPacket.Time): + return true; + case LogDataChunkKey.Message: + { + string text = Encoding.UTF8.GetString(reader.GetSpanSafe(size)).TrimEnd(); + + if (isHeadPacket && isTailPacket) + { + _logPacket.Message = text; + } + else + { + _logPacket.Message += text; + + if (_logPacket.Message.Length >= MessageLengthLimit) + { + isTailPacket = true; + } + } + + break; + } + case LogDataChunkKey.Filename: + _logPacket.Filename = Encoding.UTF8.GetString(reader.GetSpanSafe(size)).TrimEnd(); + break; + case LogDataChunkKey.Function: + _logPacket.Function = Encoding.UTF8.GetString(reader.GetSpanSafe(size)).TrimEnd(); + break; + case LogDataChunkKey.Module: + _logPacket.Module = Encoding.UTF8.GetString(reader.GetSpanSafe(size)).TrimEnd(); + break; + case LogDataChunkKey.Thread: + _logPacket.Thread = Encoding.UTF8.GetString(reader.GetSpanSafe(size)).TrimEnd(); + break; + case LogDataChunkKey.ProgramName: + _logPacket.ProgramName = Encoding.UTF8.GetString(reader.GetSpanSafe(size)).TrimEnd(); + break; + } + } + + return isTailPacket; + } + + private static bool TryReadUleb128(ref SpanReader reader, out int result) + { + result = 0; + int count = 0; + byte encoded; + + do + { + if (!reader.TryRead(out encoded)) + { + return false; + } + + result += (encoded & 0x7F) << (7 * count); + + count++; + } while ((encoded & 0x80) != 0); + + return true; + } + } +} diff --git a/src/Ryujinx.Horizon/LogManager/Ipc/LogService.cs b/src/Ryujinx.Horizon/LogManager/Ipc/LogService.cs new file mode 100644 index 00000000..8ed5d99d --- /dev/null +++ b/src/Ryujinx.Horizon/LogManager/Ipc/LogService.cs @@ -0,0 +1,20 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Lm; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.LogManager.Ipc +{ + partial class LogService : ILogService + { + public LogDestination LogDestination { get; set; } = LogDestination.TargetManager; + + [CmifCommand(0)] + public Result OpenLogger(out LmLogger logger, [ClientProcessId] ulong pid) + { + // NOTE: Internal name is Logger, but we rename it to LmLogger to avoid name clash with Ryujinx.Common.Logging logger. + logger = new LmLogger(this, pid); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs b/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs new file mode 100644 index 00000000..6bb4e11c --- /dev/null +++ b/src/Ryujinx.Horizon/LogManager/LmIpcServer.cs @@ -0,0 +1,44 @@ +using Ryujinx.Horizon.LogManager.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.LogManager +{ + class LmIpcServer + { + private const int MaxSessionsCount = 42; + + private const int PointerBufferSize = 0x400; + private const int MaxDomains = 31; + private const int MaxDomainObjects = 61; + private const int MaxPortsCount = 1; + + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount); + + _serverManager.RegisterObjectForServer(new LogService(), ServiceName.Encode("lm"), MaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/LogManager/LmMain.cs b/src/Ryujinx.Horizon/LogManager/LmMain.cs new file mode 100644 index 00000000..ac3cd4bb --- /dev/null +++ b/src/Ryujinx.Horizon/LogManager/LmMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.LogManager +{ + class LmMain : IService + { + public static void Main(ServiceTable serviceTable) + { + LmIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/LogManager/Types/LogPacket.cs b/src/Ryujinx.Horizon/LogManager/Types/LogPacket.cs new file mode 100644 index 00000000..b4294d66 --- /dev/null +++ b/src/Ryujinx.Horizon/LogManager/Types/LogPacket.cs @@ -0,0 +1,72 @@ +using Ryujinx.Horizon.Sdk.Diag; +using System.Text; + +namespace Ryujinx.Horizon.LogManager.Types +{ + struct LogPacket + { + public string Message; + public int Line; + public string Filename; + public string Function; + public string Module; + public string Thread; + public long DropCount; + public long Time; + public string ProgramName; + public LogSeverity Severity; + + public override string ToString() + { + StringBuilder builder = new(); + builder.AppendLine($"Guest Log:\n Log level: {Severity}"); + + if (Time > 0) + { + builder.AppendLine($" Time: {Time}s"); + } + + if (DropCount > 0) + { + builder.AppendLine($" DropCount: {DropCount}"); + } + + if (!string.IsNullOrEmpty(ProgramName)) + { + builder.AppendLine($" ProgramName: {ProgramName}"); + } + + if (!string.IsNullOrEmpty(Module)) + { + builder.AppendLine($" Module: {Module}"); + } + + if (!string.IsNullOrEmpty(Thread)) + { + builder.AppendLine($" Thread: {Thread}"); + } + + if (!string.IsNullOrEmpty(Filename)) + { + builder.AppendLine($" Filename: {Filename}"); + } + + if (Line > 0) + { + builder.AppendLine($" Line: {Line}"); + } + + if (!string.IsNullOrEmpty(Function)) + { + builder.AppendLine($" Function: {Function}"); + } + + if (!string.IsNullOrEmpty(Message)) + { + builder.AppendLine($" Message: {Message}"); + } + + return builder.ToString(); + } + } +} diff --git a/src/Ryujinx.Horizon/MmNv/Ipc/Request.cs b/src/Ryujinx.Horizon/MmNv/Ipc/Request.cs new file mode 100644 index 00000000..c53ca186 --- /dev/null +++ b/src/Ryujinx.Horizon/MmNv/Ipc/Request.cs @@ -0,0 +1,160 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.MmNv; +using Ryujinx.Horizon.Sdk.Sf; +using System.Collections.Generic; + +namespace Ryujinx.Horizon.MmNv.Ipc +{ + partial class Request : IRequest + { + private readonly List _sessionList = new(); + + private uint _uniqueId = 1; + + [CmifCommand(0)] + public Result InitializeOld(Module module, uint fgmPriority, uint autoClearEvent) + { + bool isAutoClearEvent = autoClearEvent != 0; + + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module, fgmPriority, isAutoClearEvent }); + + Register(module, fgmPriority, isAutoClearEvent); + + return Result.Success; + } + + [CmifCommand(1)] + public Result FinalizeOld(Module module) + { + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module }); + + lock (_sessionList) + { + _sessionList.Remove(GetSessionByModule(module)); + } + + return Result.Success; + } + + [CmifCommand(2)] + public Result SetAndWaitOld(Module module, uint clockRateMin, int clockRateMax) + { + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module, clockRateMin, clockRateMax }); + + lock (_sessionList) + { + GetSessionByModule(module)?.SetAndWait(clockRateMin, clockRateMax); + } + + return Result.Success; + } + + [CmifCommand(3)] + public Result GetOld(out uint clockRateActual, Module module) + { + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module }); + + lock (_sessionList) + { + Session session = GetSessionByModule(module); + + clockRateActual = session == null ? 0 : session.ClockRateMin; + } + + return Result.Success; + } + + [CmifCommand(4)] + public Result Initialize(out uint requestId, Module module, uint fgmPriority, uint autoClearEvent) + { + bool isAutoClearEvent = autoClearEvent != 0; + + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { module, fgmPriority, isAutoClearEvent }); + + requestId = Register(module, fgmPriority, isAutoClearEvent); + + return Result.Success; + } + + [CmifCommand(5)] + public Result Finalize(uint requestId) + { + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { requestId }); + + lock (_sessionList) + { + _sessionList.Remove(GetSessionById(requestId)); + } + + return Result.Success; + } + + [CmifCommand(6)] + public Result SetAndWait(uint requestId, uint clockRateMin, int clockRateMax) + { + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { requestId, clockRateMin, clockRateMax }); + + lock (_sessionList) + { + GetSessionById(requestId)?.SetAndWait(clockRateMin, clockRateMax); + } + + return Result.Success; + } + + [CmifCommand(7)] + public Result Get(out uint clockRateActual, uint requestId) + { + Logger.Stub?.PrintStub(LogClass.ServiceMm, new { requestId }); + + lock (_sessionList) + { + Session session = GetSessionById(requestId); + + clockRateActual = session == null ? 0 : session.ClockRateMin; + } + + return Result.Success; + } + + private Session GetSessionById(uint id) + { + foreach (Session session in _sessionList) + { + if (session.Id == id) + { + return session; + } + } + + return null; + } + + private Session GetSessionByModule(Module module) + { + foreach (Session session in _sessionList) + { + if (session.Module == module) + { + return session; + } + } + + return null; + } + + private uint Register(Module module, uint fgmPriority, bool isAutoClearEvent) + { + lock (_sessionList) + { + // Nintendo ignores the fgm priority as the other services were deprecated. + Session session = new(_uniqueId++, module, isAutoClearEvent); + + _sessionList.Add(session); + + return session.Id; + } + } + } +} diff --git a/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs b/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs new file mode 100644 index 00000000..b3ce8118 --- /dev/null +++ b/src/Ryujinx.Horizon/MmNv/MmNvIpcServer.cs @@ -0,0 +1,44 @@ +using Ryujinx.Horizon.MmNv.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.MmNv +{ + class MmNvIpcServer + { + private const int MaxSessionsCount = 40; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 1; + + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount); + + _serverManager.RegisterObjectForServer(new Request(), ServiceName.Encode("mm:u"), MaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/MmNv/MmNvMain.cs b/src/Ryujinx.Horizon/MmNv/MmNvMain.cs new file mode 100644 index 00000000..a5a7b630 --- /dev/null +++ b/src/Ryujinx.Horizon/MmNv/MmNvMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.MmNv +{ + class MmNvMain : IService + { + public static void Main(ServiceTable serviceTable) + { + MmNvIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Ngc/Ipc/Service.cs b/src/Ryujinx.Horizon/Ngc/Ipc/Service.cs new file mode 100644 index 00000000..740f893f --- /dev/null +++ b/src/Ryujinx.Horizon/Ngc/Ipc/Service.cs @@ -0,0 +1,68 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Ngc; +using Ryujinx.Horizon.Sdk.Ngc.Detail; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Ngc.Ipc +{ + partial class Service : INgcService + { + private readonly ProfanityFilter _profanityFilter; + + public Service(ProfanityFilter profanityFilter) + { + _profanityFilter = profanityFilter; + } + + [CmifCommand(0)] + public Result GetContentVersion(out uint version) + { + lock (_profanityFilter) + { + return _profanityFilter.GetContentVersion(out version); + } + } + + [CmifCommand(1)] + public Result Check( + out uint checkMask, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan text, + uint regionMask, + ProfanityFilterOption option) + { + lock (_profanityFilter) + { + return _profanityFilter.CheckProfanityWords(out checkMask, text, regionMask, option); + } + } + + [CmifCommand(2)] + public Result Mask( + out int maskedWordsCount, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span filteredText, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan text, + uint regionMask, + ProfanityFilterOption option) + { + lock (_profanityFilter) + { + int length = Math.Min(filteredText.Length, text.Length); + + text[..length].CopyTo(filteredText[..length]); + + return _profanityFilter.MaskProfanityWordsInText(out maskedWordsCount, filteredText, regionMask, option); + } + } + + [CmifCommand(3)] + public Result Reload() + { + lock (_profanityFilter) + { + return _profanityFilter.Reload(); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs b/src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs new file mode 100644 index 00000000..ec73f96a --- /dev/null +++ b/src/Ryujinx.Horizon/Ngc/NgcIpcServer.cs @@ -0,0 +1,51 @@ +using Ryujinx.Horizon.Ngc.Ipc; +using Ryujinx.Horizon.Sdk.Fs; +using Ryujinx.Horizon.Sdk.Ngc.Detail; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Ngc +{ + class NgcIpcServer + { + private const int MaxSessionsCount = 4; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 1; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + private ProfanityFilter _profanityFilter; + + public void Initialize(IFsClient fsClient) + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _profanityFilter = new(fsClient); + _profanityFilter.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, MaxSessionsCount); + + _serverManager.RegisterObjectForServer(new Service(_profanityFilter), ServiceName.Encode("ngc:u"), MaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _profanityFilter.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Ngc/NgcMain.cs b/src/Ryujinx.Horizon/Ngc/NgcMain.cs new file mode 100644 index 00000000..1a584d66 --- /dev/null +++ b/src/Ryujinx.Horizon/Ngc/NgcMain.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.Horizon.Ngc +{ + class NgcMain : IService + { + public static void Main(ServiceTable serviceTable) + { + NgcIpcServer ipcServer = new(); + + ipcServer.Initialize(HorizonStatic.Options.FsClient); + + // TODO: Notification thread, requires implementing OpenSystemDataUpdateEventNotifier on FS. + // The notification thread seems to wait until the event returned by OpenSystemDataUpdateEventNotifier is signalled + // in a loop. When it receives the signal, it calls ContentsReader.Reload and then waits for the next signal. + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Ovln/Ipc/ReceiverService.cs b/src/Ryujinx.Horizon/Ovln/Ipc/ReceiverService.cs new file mode 100644 index 00000000..b74d3cd2 --- /dev/null +++ b/src/Ryujinx.Horizon/Ovln/Ipc/ReceiverService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Ovln; + +namespace Ryujinx.Horizon.Ovln.Ipc +{ + partial class ReceiverService : IReceiverService + { + } +} diff --git a/src/Ryujinx.Horizon/Ovln/Ipc/SenderService.cs b/src/Ryujinx.Horizon/Ovln/Ipc/SenderService.cs new file mode 100644 index 00000000..04ef5d4b --- /dev/null +++ b/src/Ryujinx.Horizon/Ovln/Ipc/SenderService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Ovln; + +namespace Ryujinx.Horizon.Ovln.Ipc +{ + partial class SenderService : ISenderService + { + } +} diff --git a/src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs b/src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs new file mode 100644 index 00000000..d4257be8 --- /dev/null +++ b/src/Ryujinx.Horizon/Ovln/OvlnIpcServer.cs @@ -0,0 +1,49 @@ +using Ryujinx.Horizon.Ovln.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Ovln +{ + class OvlnIpcServer + { + private const int OvlnRcvMaxSessionsCount = 2; + private const int OvlnSndMaxSessionsCount = 20; + private const int TotalMaxSessionsCount = OvlnRcvMaxSessionsCount + OvlnSndMaxSessionsCount; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 21; + private const int MaxDomainObjects = 60; + private const int MaxPortsCount = 2; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterObjectForServer(new ReceiverService(), ServiceName.Encode("ovln:rcv"), OvlnRcvMaxSessionsCount); // 8.0.0+ + _serverManager.RegisterObjectForServer(new SenderService(), ServiceName.Encode("ovln:snd"), OvlnSndMaxSessionsCount); // 8.0.0+ +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Ovln/OvlnMain.cs b/src/Ryujinx.Horizon/Ovln/OvlnMain.cs new file mode 100644 index 00000000..79afad20 --- /dev/null +++ b/src/Ryujinx.Horizon/Ovln/OvlnMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Ovln +{ + class OvlnMain : IService + { + public static void Main(ServiceTable serviceTable) + { + OvlnIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs b/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs new file mode 100644 index 00000000..4ed7dd48 --- /dev/null +++ b/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs @@ -0,0 +1,239 @@ +using MsgPack; +using MsgPack.Serialization; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Prepo.Types; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Prepo; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Text; +using ApplicationId = Ryujinx.Horizon.Sdk.Ncm.ApplicationId; + +namespace Ryujinx.Horizon.Prepo.Ipc +{ + partial class PrepoService : IPrepoService + { + enum PlayReportKind + { + Normal, + System, + } + + private readonly ArpApi _arp; + private readonly PrepoServicePermissionLevel _permissionLevel; + private ulong _systemSessionId; + + private bool _immediateTransmissionEnabled; + private bool _userAgreementCheckEnabled = true; + + public PrepoService(ArpApi arp, PrepoServicePermissionLevel permissionLevel) + { + _arp = arp; + _permissionLevel = permissionLevel; + } + + [CmifCommand(10100)] // 1.0.0-5.1.0 + [CmifCommand(10102)] // 6.0.0-9.2.0 + [CmifCommand(10104)] // 10.0.0+ + public Result SaveReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan reportBuffer, [ClientProcessId] ulong pid) + { + if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0) + { + return PrepoResult.PermissionDenied; + } + + ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, Uid.Null); + + return Result.Success; + } + + [CmifCommand(10101)] // 1.0.0-5.1.0 + [CmifCommand(10103)] // 6.0.0-9.2.0 + [CmifCommand(10105)] // 10.0.0+ + public Result SaveReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan reportBuffer, [ClientProcessId] ulong pid) + { + if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0) + { + return PrepoResult.PermissionDenied; + } + + ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, userId, true); + + return Result.Success; + } + + [CmifCommand(10200)] + public Result RequestImmediateTransmission() + { + _immediateTransmissionEnabled = true; + + // It signals an event of nn::prepo::detail::service::core::TransmissionStatusManager that requests the transmission of the report. + // Since we don't use reports, it's fine to do nothing. + + return Result.Success; + } + + [CmifCommand(10300)] + public Result GetTransmissionStatus(out int status) + { + status = 0; + + if (_immediateTransmissionEnabled && _userAgreementCheckEnabled) + { + status = 1; + } + + return Result.Success; + } + + [CmifCommand(10400)] // 9.0.0+ + public Result GetSystemSessionId(out ulong systemSessionId) + { + systemSessionId = default; + + if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0) + { + return PrepoResult.PermissionDenied; + } + + if (_systemSessionId == 0) + { + _systemSessionId = (ulong)Random.Shared.NextInt64(); + } + + systemSessionId = _systemSessionId; + + return Result.Success; + } + + [CmifCommand(20100)] + public Result SaveSystemReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan gameRoomBuffer, ApplicationId applicationId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan reportBuffer) + { + if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0) + { + return PrepoResult.PermissionDenied; + } + + return ProcessPlayReport(PlayReportKind.System, gameRoomBuffer, reportBuffer, 0, Uid.Null, false, applicationId); + } + + [CmifCommand(20101)] + public Result SaveSystemReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan gameRoomBuffer, ApplicationId applicationId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan reportBuffer) + { + if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0) + { + return PrepoResult.PermissionDenied; + } + + return ProcessPlayReport(PlayReportKind.System, gameRoomBuffer, reportBuffer, 0, userId, true, applicationId); + } + + [CmifCommand(40100)] // 2.0.0+ + public Result IsUserAgreementCheckEnabled(out bool enabled) + { + enabled = false; + + if (_permissionLevel == PrepoServicePermissionLevel.User || _permissionLevel == PrepoServicePermissionLevel.System) + { + enabled = _userAgreementCheckEnabled; + + // If "enabled" is false, it sets some internal fields to 0. + // Then, it mounts "prepo-sys:/is_user_agreement_check_enabled.bin" and returns the contained bool. + // We can return the private bool instead, we don't care about the agreement since we don't send reports. + + return Result.Success; + } + + return PrepoResult.PermissionDenied; + } + + [CmifCommand(40101)] // 2.0.0+ + public Result SetUserAgreementCheckEnabled(bool enabled) + { + if (_permissionLevel == PrepoServicePermissionLevel.User || _permissionLevel == PrepoServicePermissionLevel.System) + { + _userAgreementCheckEnabled = enabled; + + // If "enabled" is false, it sets some internal fields to 0. + // Then, it mounts "prepo-sys:/is_user_agreement_check_enabled.bin" and stores the "enabled" value. + // We can store in the private bool instead, we don't care about the agreement since we don't send reports. + + return Result.Success; + } + + return PrepoResult.PermissionDenied; + } + + private Result ProcessPlayReport(PlayReportKind playReportKind, ReadOnlySpan gameRoomBuffer, ReadOnlySpan reportBuffer, ulong pid, Uid userId, bool withUserId = false, ApplicationId applicationId = default) + { + if (withUserId) + { + if (userId.IsNull) + { + return PrepoResult.InvalidArgument; + } + } + + if (gameRoomBuffer.Length > 31) + { + return PrepoResult.InvalidArgument; + } + + string gameRoom = Encoding.UTF8.GetString(gameRoomBuffer).TrimEnd(); + + if (gameRoom == string.Empty) + { + return PrepoResult.InvalidState; + } + + if (reportBuffer.Length == 0) + { + return PrepoResult.InvalidBufferSize; + } + + StringBuilder builder = new(); + MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(reportBuffer.ToArray()); + + builder.AppendLine(); + builder.AppendLine("PlayReport log:"); + builder.AppendLine($" Kind: {playReportKind}"); + + // NOTE: Reports are stored internally and an event is signaled to transmit them. + + if (pid != 0) + { + builder.AppendLine($" Pid: {pid}"); + } + else + { + builder.AppendLine($" ApplicationId: {applicationId}"); + } + + Result result = _arp.GetApplicationInstanceId(out ulong applicationInstanceId, pid); + if (result.IsFailure) + { + return PrepoResult.InvalidPid; + } + + _arp.GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, applicationInstanceId).AbortOnFailure(); + + builder.AppendLine($" ApplicationVersion: {applicationLaunchProperty.Version}"); + + if (!userId.IsNull) + { + builder.AppendLine($" UserId: {userId}"); + } + + builder.AppendLine($" Room: {gameRoom}"); + builder.AppendLine($" Report: {MessagePackObjectFormatter.Format(deserializedReport)}"); + + Logger.Info?.Print(LogClass.ServicePrepo, builder.ToString()); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs b/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs new file mode 100644 index 00000000..669a6459 --- /dev/null +++ b/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs @@ -0,0 +1,57 @@ +using Ryujinx.Horizon.Prepo.Types; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Prepo +{ + class PrepoIpcServer + { + private const int MaxSessionsCount = 12; + private const int TotalMaxSessionsCount = MaxSessionsCount * 6; + + private const int PointerBufferSize = 0x80; + private const int MaxDomains = 64; + private const int MaxDomainObjects = 16; + private const int MaxPortsCount = 6; + + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ArpApi _arp; + private PrepoServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _arp = new ArpApi(allocator); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new PrepoServerManager(allocator, _sm, _arp, MaxPortsCount, _managerOptions, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterServer((int)PrepoPortIndex.Admin, ServiceName.Encode("prepo:a"), MaxSessionsCount); // 1.0.0-5.1.0 + _serverManager.RegisterServer((int)PrepoPortIndex.Admin2, ServiceName.Encode("prepo:a2"), MaxSessionsCount); // 6.0.0+ + _serverManager.RegisterServer((int)PrepoPortIndex.Manager, ServiceName.Encode("prepo:m"), MaxSessionsCount); + _serverManager.RegisterServer((int)PrepoPortIndex.User, ServiceName.Encode("prepo:u"), MaxSessionsCount); + _serverManager.RegisterServer((int)PrepoPortIndex.System, ServiceName.Encode("prepo:s"), MaxSessionsCount); + _serverManager.RegisterServer((int)PrepoPortIndex.Debug, ServiceName.Encode("prepo:d"), MaxSessionsCount); // 1.0.0 +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _arp.Dispose(); + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Prepo/PrepoMain.cs b/src/Ryujinx.Horizon/Prepo/PrepoMain.cs new file mode 100644 index 00000000..8d54af9d --- /dev/null +++ b/src/Ryujinx.Horizon/Prepo/PrepoMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Prepo +{ + class PrepoMain : IService + { + public static void Main(ServiceTable serviceTable) + { + PrepoIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Prepo/PrepoResult.cs b/src/Ryujinx.Horizon/Prepo/PrepoResult.cs new file mode 100644 index 00000000..24e46d0c --- /dev/null +++ b/src/Ryujinx.Horizon/Prepo/PrepoResult.cs @@ -0,0 +1,17 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Prepo +{ + static class PrepoResult + { + private const int ModuleId = 129; + +#pragma warning disable IDE0055 // Disable formatting + public static Result InvalidArgument => new(ModuleId, 1); + public static Result InvalidState => new(ModuleId, 5); + public static Result InvalidBufferSize => new(ModuleId, 9); + public static Result PermissionDenied => new(ModuleId, 90); + public static Result InvalidPid => new(ModuleId, 101); +#pragma warning restore IDE0055 + } +} diff --git a/src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs b/src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs new file mode 100644 index 00000000..8cac44c8 --- /dev/null +++ b/src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs @@ -0,0 +1,36 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Prepo.Ipc; +using Ryujinx.Horizon.Prepo.Types; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using System; + +namespace Ryujinx.Horizon.Prepo +{ + class PrepoServerManager : ServerManager + { + private readonly ArpApi _arp; + + public PrepoServerManager(HeapAllocator allocator, SmApi sm, ArpApi arp, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions) + { + _arp = arp; + } + + protected override Result OnNeedsToAccept(int portIndex, Server server) + { + return (PrepoPortIndex)portIndex switch + { +#pragma warning disable IDE0055 // Disable formatting + PrepoPortIndex.Admin => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.Admin)), + PrepoPortIndex.Admin2 => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.Admin)), + PrepoPortIndex.Manager => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.Manager)), + PrepoPortIndex.User => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.User)), + PrepoPortIndex.System => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.System)), + PrepoPortIndex.Debug => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.Debug)), + _ => throw new ArgumentOutOfRangeException(nameof(portIndex)), +#pragma warning restore IDE0055 + }; + } + } +} diff --git a/src/Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs b/src/Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs new file mode 100644 index 00000000..b8242bae --- /dev/null +++ b/src/Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Horizon.Prepo.Types +{ + enum PrepoPortIndex + { + Admin, + Admin2, + Manager, + User, + System, + Debug, + } +} diff --git a/src/Ryujinx.Horizon/Prepo/Types/PrepoServicePermissionLevel.cs b/src/Ryujinx.Horizon/Prepo/Types/PrepoServicePermissionLevel.cs new file mode 100644 index 00000000..7a807354 --- /dev/null +++ b/src/Ryujinx.Horizon/Prepo/Types/PrepoServicePermissionLevel.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Horizon.Prepo.Types +{ + enum PrepoServicePermissionLevel + { + Admin = -1, + User = 1, + System = 2, + Manager = 6, + Debug = unchecked((int)0x80000006), + } +} diff --git a/src/Ryujinx.Horizon/Psc/Ipc/PmControl.cs b/src/Ryujinx.Horizon/Psc/Ipc/PmControl.cs new file mode 100644 index 00000000..112a702f --- /dev/null +++ b/src/Ryujinx.Horizon/Psc/Ipc/PmControl.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Psc; + +namespace Ryujinx.Horizon.Psc.Ipc +{ + partial class PmControl : IPmControl + { + } +} diff --git a/src/Ryujinx.Horizon/Psc/Ipc/PmService.cs b/src/Ryujinx.Horizon/Psc/Ipc/PmService.cs new file mode 100644 index 00000000..cb2ea8d4 --- /dev/null +++ b/src/Ryujinx.Horizon/Psc/Ipc/PmService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Psc; + +namespace Ryujinx.Horizon.Psc.Ipc +{ + partial class PmService : IPmService + { + } +} diff --git a/src/Ryujinx.Horizon/Psc/Ipc/PmStateLock.cs b/src/Ryujinx.Horizon/Psc/Ipc/PmStateLock.cs new file mode 100644 index 00000000..5c796180 --- /dev/null +++ b/src/Ryujinx.Horizon/Psc/Ipc/PmStateLock.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Psc; + +namespace Ryujinx.Horizon.Psc.Ipc +{ + partial class PmStateLock : IPmStateLock + { + } +} diff --git a/src/Ryujinx.Horizon/Psc/PscIpcServer.cs b/src/Ryujinx.Horizon/Psc/PscIpcServer.cs new file mode 100644 index 00000000..8e574ddd --- /dev/null +++ b/src/Ryujinx.Horizon/Psc/PscIpcServer.cs @@ -0,0 +1,51 @@ +using Ryujinx.Horizon.Psc.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Psc +{ + class PscIpcServer + { + private const int PscCMaxSessionsCount = 1; + private const int PscMMaxSessionsCount = 50; + private const int PscLMaxSessionsCount = 5; + private const int TotalMaxSessionsCount = PscCMaxSessionsCount + PscMMaxSessionsCount + PscLMaxSessionsCount; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 3; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterObjectForServer(new PmControl(), ServiceName.Encode("psc:c"), PscCMaxSessionsCount); + _serverManager.RegisterObjectForServer(new PmService(), ServiceName.Encode("psc:m"), PscMMaxSessionsCount); + _serverManager.RegisterObjectForServer(new PmStateLock(), ServiceName.Encode("psc:l"), PscLMaxSessionsCount); // 9.0.0+ +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Psc/PscMain.cs b/src/Ryujinx.Horizon/Psc/PscMain.cs new file mode 100644 index 00000000..0b531667 --- /dev/null +++ b/src/Ryujinx.Horizon/Psc/PscMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Psc +{ + class PscMain : IService + { + public static void Main(ServiceTable serviceTable) + { + PscIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Ptm/Ipc/MeasurementServer.cs b/src/Ryujinx.Horizon/Ptm/Ipc/MeasurementServer.cs new file mode 100644 index 00000000..ce7c0474 --- /dev/null +++ b/src/Ryujinx.Horizon/Ptm/Ipc/MeasurementServer.cs @@ -0,0 +1,63 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Ts; +using Ryujinx.Horizon.Ts.Ipc; + +namespace Ryujinx.Horizon.Ptm.Ipc +{ + partial class MeasurementServer : IMeasurementServer + { + // NOTE: Values are randomly choosen. + public const int DefaultTemperature = 42; + public const int MinimumTemperature = 0; + public const int MaximumTemperature = 100; + + [CmifCommand(0)] // 1.0.0-16.1.0 + public Result GetTemperatureRange(out int minimumTemperature, out int maximumTemperature, Location location) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); + + minimumTemperature = MinimumTemperature; + maximumTemperature = MaximumTemperature; + + return Result.Success; + } + + [CmifCommand(1)] // 1.0.0-16.1.0 + public Result GetTemperature(out int temperature, Location location) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); + + temperature = DefaultTemperature; + + return Result.Success; + } + + [CmifCommand(2)] // 1.0.0-13.2.1 + public Result SetMeasurementMode(Location location, byte measurementMode) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location, measurementMode }); + + return Result.Success; + } + + [CmifCommand(3)] // 1.0.0-13.2.1 + public Result GetTemperatureMilliC(out int temperatureMilliC, Location location) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location }); + + temperatureMilliC = DefaultTemperature * 1000; + + return Result.Success; + } + + [CmifCommand(4)] // 8.0.0+ + public Result OpenSession(out ISession session, DeviceCode deviceCode) + { + session = new Session(deviceCode); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Ptm/Ipc/Session.cs b/src/Ryujinx.Horizon/Ptm/Ipc/Session.cs new file mode 100644 index 00000000..191a4b3a --- /dev/null +++ b/src/Ryujinx.Horizon/Ptm/Ipc/Session.cs @@ -0,0 +1,47 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Ptm.Ipc; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Ts; + +namespace Ryujinx.Horizon.Ts.Ipc +{ + partial class Session : ISession + { + private readonly DeviceCode _deviceCode; + + public Session(DeviceCode deviceCode) + { + _deviceCode = deviceCode; + } + + [CmifCommand(0)] + public Result GetTemperatureRange(out int minimumTemperature, out int maximumTemperature) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { _deviceCode }); + + minimumTemperature = MeasurementServer.MinimumTemperature; + maximumTemperature = MeasurementServer.MaximumTemperature; + + return Result.Success; + } + + [CmifCommand(2)] + public Result SetMeasurementMode(byte measurementMode) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { _deviceCode, measurementMode }); + + return Result.Success; + } + + [CmifCommand(4)] + public Result GetTemperature(out int temperature) + { + Logger.Stub?.PrintStub(LogClass.ServicePtm, new { _deviceCode }); + + temperature = MeasurementServer.DefaultTemperature; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Ptm/TsIpcServer.cs b/src/Ryujinx.Horizon/Ptm/TsIpcServer.cs new file mode 100644 index 00000000..db25d8e2 --- /dev/null +++ b/src/Ryujinx.Horizon/Ptm/TsIpcServer.cs @@ -0,0 +1,44 @@ +using Ryujinx.Horizon.Ptm.Ipc; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Ptm +{ + class TsIpcServer + { + private const int MaxSessionsCount = 4; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 1; + + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount); + + _serverManager.RegisterObjectForServer(new MeasurementServer(), ServiceName.Encode("ts"), MaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Ptm/TsMain.cs b/src/Ryujinx.Horizon/Ptm/TsMain.cs new file mode 100644 index 00000000..237d52cd --- /dev/null +++ b/src/Ryujinx.Horizon/Ptm/TsMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Ptm +{ + class TsMain : IService + { + public static void Main(ServiceTable serviceTable) + { + TsIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj new file mode 100644 index 00000000..bf34ddd1 --- /dev/null +++ b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.Horizon/Sdk/Account/IEmulatorAccountManager.cs b/src/Ryujinx.Horizon/Sdk/Account/IEmulatorAccountManager.cs new file mode 100644 index 00000000..af02cc8e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Account/IEmulatorAccountManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.Account +{ + public interface IEmulatorAccountManager + { + void OpenUserOnlinePlay(Uid userId); + void CloseUserOnlinePlay(Uid userId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs b/src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs new file mode 100644 index 00000000..2512975e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Account +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)] + readonly record struct NetworkServiceAccountId + { + public readonly ulong Id; + + public NetworkServiceAccountId(ulong id) + { + Id = id; + } + + public override readonly string ToString() + { + return Id.ToString("x16"); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Account/Nickname.cs b/src/Ryujinx.Horizon/Sdk/Account/Nickname.cs new file mode 100644 index 00000000..1f351ee3 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Account/Nickname.cs @@ -0,0 +1,29 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Account +{ + [StructLayout(LayoutKind.Sequential, Size = 0x21, Pack = 0x1)] + readonly struct Nickname + { + public readonly Array33 Name; + + public Nickname(in Array33 name) + { + Name = name; + } + + public override string ToString() + { + int length = ((ReadOnlySpan)Name.AsSpan()).IndexOf((byte)0); + if (length < 0) + { + length = 33; + } + + return Encoding.UTF8.GetString(Name.AsSpan()[..length]); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Account/Uid.cs b/src/Ryujinx.Horizon/Sdk/Account/Uid.cs new file mode 100644 index 00000000..d612f479 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Account/Uid.cs @@ -0,0 +1,62 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Account +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + public readonly record struct Uid + { + public readonly ulong High; + public readonly ulong Low; + + public bool IsNull => (Low | High) == 0; + + public static Uid Null => new(0, 0); + + public Uid(ulong low, ulong high) + { + Low = low; + High = high; + } + + public Uid(byte[] bytes) + { + High = BitConverter.ToUInt64(bytes, 0); + Low = BitConverter.ToUInt64(bytes, 8); + } + + public Uid(string hex) + { + if (hex == null || hex.Length != 32 || !hex.All("0123456789abcdefABCDEF".Contains)) + { + throw new ArgumentException("Invalid Hex value!", nameof(hex)); + } + + Low = Convert.ToUInt64(hex[16..], 16); + High = Convert.ToUInt64(hex[..16], 16); + } + + public void Write(BinaryWriter binaryWriter) + { + binaryWriter.Write(High); + binaryWriter.Write(Low); + } + + public override string ToString() + { + return High.ToString("x16") + Low.ToString("x16"); + } + + public LibHac.Account.Uid ToLibHacUid() + { + return new LibHac.Account.Uid((ulong)High, (ulong)Low); + } + + public UInt128 ToUInt128() + { + return new UInt128((ulong)High, (ulong)Low); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs b/src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs new file mode 100644 index 00000000..2b81fbf6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs @@ -0,0 +1,71 @@ +namespace Ryujinx.Horizon.Sdk.Applet +{ + enum AppletId : uint + { + None = 0x00, + Application = 0x01, + OverlayApplet = 0x02, + SystemAppletMenu = 0x03, + SystemApplication = 0x04, + LibraryAppletAuth = 0x0A, + LibraryAppletCabinet = 0x0B, + LibraryAppletController = 0x0C, + LibraryAppletDataErase = 0x0D, + LibraryAppletError = 0x0E, + LibraryAppletNetConnect = 0x0F, + LibraryAppletPlayerSelect = 0x10, + LibraryAppletSwkbd = 0x11, + LibraryAppletMiiEdit = 0x12, + LibraryAppletWeb = 0x13, + LibraryAppletShop = 0x14, + LibraryAppletPhotoViewer = 0x15, + LibraryAppletSet = 0x16, + LibraryAppletOfflineWeb = 0x17, + LibraryAppletLoginShare = 0x18, + LibraryAppletWifiWebAuth = 0x19, + LibraryAppletMyPage = 0x1A, + LibraryAppletGift = 0x1B, + LibraryAppletUserMigration = 0x1C, + LibraryAppletPreomiaSys = 0x1D, + LibraryAppletStory = 0x1E, + LibraryAppletPreomiaUsr = 0x1F, + LibraryAppletPreomiaUsrDummy = 0x20, + LibraryAppletSample = 0x21, + LibraryAppletPromoteQualification = 0x22, + LibraryAppletOfflineWebFw17 = 0x32, + LibraryAppletOfflineWeb2Fw17 = 0x33, + LibraryAppletLoginShareFw17 = 0x35, + LibraryAppletLoginShare2Fw17 = 0x36, + LibraryAppletLoginShare3Fw17 = 0x37, + Unknown38 = 0x38, + DevlopmentTool = 0x3E8, + CombinationLA = 0x3F1, + AeSystemApplet = 0x3F2, + AeOverlayApplet = 0x3F3, + AeStarter = 0x3F4, + AeLibraryAppletAlone = 0x3F5, + AeLibraryApplet1 = 0x3F6, + AeLibraryApplet2 = 0x3F7, + AeLibraryApplet3 = 0x3F8, + AeLibraryApplet4 = 0x3F9, + AppletISA = 0x3FA, + AppletIOA = 0x3FB, + AppletISTA = 0x3FC, + AppletILA1 = 0x3FD, + AppletILA2 = 0x3FE, + CombinationLAFw17 = 0x700000DC, + AeSystemAppletFw17 = 0x700000E6, + AeOverlayAppletFw17 = 0x700000E7, + AeStarterFw17 = 0x700000E8, + AeLibraryAppletAloneFw17 = 0x700000E9, + AeLibraryApplet1Fw17 = 0x700000EA, + AeLibraryApplet2Fw17 = 0x700000EB, + AeLibraryApplet3Fw17 = 0x700000EC, + AeLibraryApplet4Fw17 = 0x700000ED, + AppletISAFw17 = 0x700000F0, + AppletIOAFw17 = 0x700000F1, + AppletISTAFw17 = 0x700000F2, + AppletILA1Fw17 = 0x700000F3, + AppletILA2Fw17 = 0x700000F4, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs b/src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs new file mode 100644 index 00000000..00e2ad36 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Applet +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)] + readonly struct AppletResourceUserId + { + public readonly ulong Id; + + public AppletResourceUserId(ulong id) + { + Id = id; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ApplicationCertificate.cs b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationCertificate.cs new file mode 100644 index 00000000..d60d337d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + [StructLayout(LayoutKind.Sequential, Size = 0x528)] + public struct ApplicationCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ApplicationKind.cs b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationKind.cs new file mode 100644 index 00000000..586e6a98 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationKind.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.Arp +{ + public enum ApplicationKind : byte + { + Application, + MicroApplication, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ApplicationLaunchProperty.cs b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationLaunchProperty.cs new file mode 100644 index 00000000..00b81832 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationLaunchProperty.cs @@ -0,0 +1,14 @@ +using Ryujinx.Horizon.Sdk.Ncm; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public struct ApplicationLaunchProperty + { + public ApplicationId ApplicationId; + public uint Version; + public StorageId Storage; + public StorageId PatchStorage; + public ApplicationKind ApplicationKind; + public byte Padding; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ApplicationProcessProperty.cs b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationProcessProperty.cs new file mode 100644 index 00000000..13d222a1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationProcessProperty.cs @@ -0,0 +1,10 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public struct ApplicationProcessProperty + { + public byte ProgramIndex; + public Array15 Unknown; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ArpApi.cs b/src/Ryujinx.Horizon/Sdk/Arp/ArpApi.cs new file mode 100644 index 00000000..b0acc006 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ArpApi.cs @@ -0,0 +1,130 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Ns; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + class ArpApi : IDisposable + { + private const string ArpRName = "arp:r"; + + private readonly HeapAllocator _allocator; + private int _sessionHandle; + + public ArpApi(HeapAllocator allocator) + { + _allocator = allocator; + } + + private void InitializeArpRService() + { + if (_sessionHandle == 0) + { + using var smApi = new SmApi(); + + smApi.Initialize(); + smApi.GetServiceHandle(out _sessionHandle, ServiceName.Encode(ArpRName)).AbortOnFailure(); + } + } + + public Result GetApplicationInstanceId(out ulong applicationInstanceId, ulong applicationPid) + { + Span data = stackalloc byte[8]; + SpanWriter writer = new(data); + + writer.Write(applicationPid); + + InitializeArpRService(); + + Result result = ServiceUtil.SendRequest(out CmifResponse response, _sessionHandle, 3, sendPid: false, data); + if (result.IsFailure) + { + applicationInstanceId = 0; + + return result; + } + + SpanReader reader = new(response.Data); + + applicationInstanceId = reader.Read(); + + return Result.Success; + } + + public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, ulong applicationInstanceId) + { + applicationLaunchProperty = default; + + Span data = stackalloc byte[8]; + SpanWriter writer = new(data); + + writer.Write(applicationInstanceId); + + InitializeArpRService(); + + Result result = ServiceUtil.SendRequest(out CmifResponse response, _sessionHandle, 0, sendPid: false, data); + if (result.IsFailure) + { + return result; + } + + SpanReader reader = new(response.Data); + + applicationLaunchProperty = reader.Read(); + + return Result.Success; + } + + public Result GetApplicationControlProperty(out ApplicationControlProperty applicationControlProperty, ulong applicationInstanceId) + { + applicationControlProperty = default; + + Span data = stackalloc byte[8]; + SpanWriter writer = new(data); + + writer.Write(applicationInstanceId); + + ulong bufferSize = (ulong)Unsafe.SizeOf(); + ulong bufferAddress = _allocator.Allocate(bufferSize); + + InitializeArpRService(); + + Result result = ServiceUtil.SendRequest( + out CmifResponse response, + _sessionHandle, + 1, + sendPid: false, + data, + stackalloc[] { HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.FixedSize }, + stackalloc[] { new PointerAndSize(bufferAddress, bufferSize) }); + + if (result.IsFailure) + { + return result; + } + + applicationControlProperty = HorizonStatic.AddressSpace.Read(bufferAddress); + + _allocator.Free(bufferAddress, bufferSize); + + return Result.Success; + } + + public void Dispose() + { + if (_sessionHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_sessionHandle); + + _sessionHandle = 0; + } + + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ArpResult.cs b/src/Ryujinx.Horizon/Sdk/Arp/ArpResult.cs new file mode 100644 index 00000000..5de07871 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ArpResult.cs @@ -0,0 +1,17 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + static class ArpResult + { + private const int ModuleId = 157; + + public static Result InvalidArgument => new(ModuleId, 30); + public static Result InvalidPid => new(ModuleId, 31); + public static Result InvalidPointer => new(ModuleId, 32); + public static Result DataAlreadyBound => new(ModuleId, 42); + public static Result AllocationFailed => new(ModuleId, 63); + public static Result NoFreeInstance => new(ModuleId, 101); + public static Result InvalidInstanceId => new(ModuleId, 102); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstance.cs b/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstance.cs new file mode 100644 index 00000000..5eb0ab18 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstance.cs @@ -0,0 +1,13 @@ +using Ryujinx.Horizon.Sdk.Ns; + +namespace Ryujinx.Horizon.Sdk.Arp.Detail +{ + class ApplicationInstance + { + public ulong Pid { get; set; } + public ApplicationLaunchProperty? LaunchProperty { get; set; } + public ApplicationProcessProperty? ProcessProperty { get; set; } + public ApplicationControlProperty? ControlProperty { get; set; } + public ApplicationCertificate? Certificate { get; set; } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstanceManager.cs b/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstanceManager.cs new file mode 100644 index 00000000..18c993ce --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstanceManager.cs @@ -0,0 +1,31 @@ +using Ryujinx.Horizon.Sdk.OsTypes; +using System; +using System.Threading; + +namespace Ryujinx.Horizon.Sdk.Arp.Detail +{ + class ApplicationInstanceManager : IDisposable + { + private int _disposalState; + + public SystemEventType SystemEvent; + public int EventHandle; + + public readonly ApplicationInstance[] Entries = new ApplicationInstance[2]; + + public ApplicationInstanceManager() + { + Os.CreateSystemEvent(out SystemEvent, EventClearMode.ManualClear, true).AbortOnFailure(); + + EventHandle = Os.GetReadableHandleOfSystemEvent(ref SystemEvent); + } + + public void Dispose() + { + if (EventHandle != 0 && Interlocked.Exchange(ref _disposalState, 1) == 0) + { + Os.DestroySystemEvent(ref SystemEvent); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IReader.cs b/src/Ryujinx.Horizon/Sdk/Arp/IReader.cs new file mode 100644 index 00000000..ef78f7fd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IReader.cs @@ -0,0 +1,18 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Ns; +using System; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IReader + { + public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, ulong applicationInstanceId); + public Result GetApplicationControlProperty(out ApplicationControlProperty applicationControlProperty, ulong applicationInstanceId); + public Result GetApplicationProcessProperty(out ApplicationProcessProperty applicationControlProperty, ulong applicationInstanceId); + public Result GetApplicationInstanceId(out ulong applicationInstanceId, ulong pid); + public Result GetApplicationInstanceUnregistrationNotifier(out IUnregistrationNotifier unregistrationNotifier); + public Result ListApplicationInstanceId(out int count, Span applicationInstanceIdList); + public Result GetMicroApplicationInstanceId(out ulong MicroApplicationInstanceId, ulong pid); + public Result GetApplicationCertificate(out ApplicationCertificate applicationCertificate, ulong applicationInstanceId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IRegistrar.cs b/src/Ryujinx.Horizon/Sdk/Arp/IRegistrar.cs new file mode 100644 index 00000000..467f3dbd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IRegistrar.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Ns; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IRegistrar + { + public Result Issue(out ulong applicationInstanceId); + public Result SetApplicationLaunchProperty(ApplicationLaunchProperty applicationLaunchProperty); + public Result SetApplicationControlProperty(in ApplicationControlProperty applicationControlProperty); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IUnregistrationNotifier.cs b/src/Ryujinx.Horizon/Sdk/Arp/IUnregistrationNotifier.cs new file mode 100644 index 00000000..24b9807d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IUnregistrationNotifier.cs @@ -0,0 +1,9 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IUnregistrationNotifier + { + public Result GetReadableHandle(out int readableHandle); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IUpdater.cs b/src/Ryujinx.Horizon/Sdk/Arp/IUpdater.cs new file mode 100644 index 00000000..f9beeb69 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IUpdater.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IUpdater + { + public Result Issue(); + public Result SetApplicationProcessProperty(ulong pid, ApplicationProcessProperty applicationProcessProperty); + public Result DeleteApplicationProcessProperty(); + public Result SetApplicationCertificate(ApplicationCertificate applicationCertificate); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IWriter.cs b/src/Ryujinx.Horizon/Sdk/Arp/IWriter.cs new file mode 100644 index 00000000..b3e000e1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IWriter.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IWriter + { + public Result AcquireRegistrar(out IRegistrar registrar); + public Result UnregisterApplicationInstance(ulong applicationInstanceId); + public Result AcquireApplicationProcessPropertyUpdater(out IUpdater updater, ulong applicationInstanceId); + public Result AcquireApplicationCertificateUpdater(out IUpdater updater, ulong applicationInstanceId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs b/src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs new file mode 100644 index 00000000..efa8d5bc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs @@ -0,0 +1,50 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.OsTypes; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio +{ + class AudioEvent : IWritableEvent, IDisposable + { + private SystemEventType _systemEvent; + private readonly IExternalEvent _externalEvent; + + public AudioEvent() + { + Os.CreateSystemEvent(out _systemEvent, EventClearMode.ManualClear, interProcess: true); + + // We need to do this because the event will be signalled from a different thread. + _externalEvent = HorizonStatic.Syscall.GetExternalEvent(Os.GetWritableHandleOfSystemEvent(ref _systemEvent)); + } + + public void Signal() + { + _externalEvent.Signal(); + } + + public void Clear() + { + _externalEvent.Clear(); + } + + public int GetReadableHandle() + { + return Os.GetReadableHandleOfSystemEvent(ref _systemEvent); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Os.DestroySystemEvent(ref _systemEvent); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs b/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs new file mode 100644 index 00000000..c18bfee9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Audio +{ + static class AudioResult + { + private const int ModuleId = 153; + + public static Result DeviceNotFound => new(ModuleId, 1); + public static Result UnsupportedRevision => new(ModuleId, 2); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs new file mode 100644 index 00000000..f67ea729 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs @@ -0,0 +1,252 @@ +using Ryujinx.Audio.Renderer.Device; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioDevice : IAudioDevice, IDisposable + { + private readonly VirtualDeviceSessionRegistry _registry; + private readonly VirtualDeviceSession[] _sessions; + private readonly bool _isUsbDeviceSupported; + + private SystemEventType _audioEvent; + private SystemEventType _audioInputEvent; + private SystemEventType _audioOutputEvent; + + public AudioDevice(VirtualDeviceSessionRegistry registry, AppletResourceUserId appletResourceId, uint revision) + { + _registry = registry; + + BehaviourContext behaviourContext = new(); + behaviourContext.SetUserRevision((int)revision); + + _isUsbDeviceSupported = behaviourContext.IsAudioUsbDeviceOutputSupported(); + _sessions = registry.GetSessionByAppletResourceId(appletResourceId.Id); + + Os.CreateSystemEvent(out _audioEvent, EventClearMode.AutoClear, interProcess: true); + Os.CreateSystemEvent(out _audioInputEvent, EventClearMode.AutoClear, interProcess: true); + Os.CreateSystemEvent(out _audioOutputEvent, EventClearMode.AutoClear, interProcess: true); + } + + private bool TryGetDeviceByName(out VirtualDeviceSession result, string name, bool ignoreRevLimitation = false) + { + result = null; + + foreach (VirtualDeviceSession session in _sessions) + { + if (session.Device.Name.Equals(name)) + { + if (!ignoreRevLimitation && !_isUsbDeviceSupported && session.Device.IsUsbDevice()) + { + return false; + } + + result = session; + + return true; + } + } + + return false; + } + + [CmifCommand(0)] + public Result ListAudioDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span names, out int nameCount) + { + int count = 0; + + foreach (VirtualDeviceSession session in _sessions) + { + if (!_isUsbDeviceSupported && session.Device.IsUsbDevice()) + { + continue; + } + + if (count >= names.Length) + { + break; + } + + names[count] = new DeviceName(session.Device.Name); + + count++; + } + + nameCount = count; + + return Result.Success; + } + + [CmifCommand(1)] + public Result SetAudioDeviceOutputVolume([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan name, float volume) + { + if (name.Length > 0 && TryGetDeviceByName(out VirtualDeviceSession result, name[0].ToString(), ignoreRevLimitation: true)) + { + if (!_isUsbDeviceSupported && result.Device.IsUsbDevice()) + { + result = _sessions[0]; + } + + result.Volume = volume; + } + + return Result.Success; + } + + [CmifCommand(2)] + public Result GetAudioDeviceOutputVolume([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan name, out float volume) + { + if (name.Length > 0 && TryGetDeviceByName(out VirtualDeviceSession result, name[0].ToString())) + { + volume = result.Volume; + } + else + { + volume = 0f; + } + + return Result.Success; + } + + [CmifCommand(3)] + public Result GetActiveAudioDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span name) + { + VirtualDevice device = _registry.ActiveDevice; + + if (!_isUsbDeviceSupported && device.IsUsbDevice()) + { + device = _registry.DefaultDevice; + } + + if (name.Length > 0) + { + name[0] = new DeviceName(device.Name); + } + + return Result.Success; + } + + [CmifCommand(4)] + public Result QueryAudioDeviceSystemEvent([CopyHandle] out int eventHandle) + { + eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioEvent); + + return Result.Success; + } + + [CmifCommand(5)] + public Result GetActiveChannelCount(out int channelCount) + { + VirtualDevice device = _registry.ActiveDevice; + + if (!_isUsbDeviceSupported && device.IsUsbDevice()) + { + device = _registry.DefaultDevice; + } + + channelCount = (int)device.ChannelCount; + + return Result.Success; + } + + [CmifCommand(6)] // 3.0.0+ + public Result ListAudioDeviceNameAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span names, out int nameCount) + { + return ListAudioDeviceName(names, out nameCount); + } + + [CmifCommand(7)] // 3.0.0+ + public Result SetAudioDeviceOutputVolumeAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan name, float volume) + { + return SetAudioDeviceOutputVolume(name, volume); + } + + [CmifCommand(8)] // 3.0.0+ + public Result GetAudioDeviceOutputVolumeAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan name, out float volume) + { + return GetAudioDeviceOutputVolume(name, out volume); + } + + [CmifCommand(10)] // 3.0.0+ + public Result GetActiveAudioDeviceNameAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span name) + { + return GetActiveAudioDeviceName(name); + } + + [CmifCommand(11)] // 3.0.0+ + public Result QueryAudioDeviceInputEvent([CopyHandle] out int eventHandle) + { + eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioInputEvent); + + return Result.Success; + } + + [CmifCommand(12)] // 3.0.0+ + public Result QueryAudioDeviceOutputEvent([CopyHandle] out int eventHandle) + { + eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioOutputEvent); + + return Result.Success; + } + + [CmifCommand(13)] // 13.0.0+ + public Result GetActiveAudioOutputDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span name) + { + if (name.Length > 0) + { + name[0] = new DeviceName(_registry.ActiveDevice.GetOutputDeviceName()); + } + + return Result.Success; + } + + [CmifCommand(14)] // 13.0.0+ + public Result ListAudioOutputDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span names, out int nameCount) + { + int count = 0; + + foreach (VirtualDeviceSession session in _sessions) + { + if (!_isUsbDeviceSupported && session.Device.IsUsbDevice()) + { + continue; + } + + if (count >= names.Length) + { + break; + } + + names[count] = new DeviceName(session.Device.GetOutputDeviceName()); + + count++; + } + + nameCount = count; + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Os.DestroySystemEvent(ref _audioEvent); + Os.DestroySystemEvent(ref _audioInputEvent); + Os.DestroySystemEvent(ref _audioOutputEvent); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs new file mode 100644 index 00000000..464ede58 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs @@ -0,0 +1,171 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Input; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioIn : IAudioIn, IDisposable + { + private readonly AudioInputSystem _impl; + private int _processHandle; + + public AudioIn(AudioInputSystem impl, int processHandle) + { + _impl = impl; + _processHandle = processHandle; + } + + [CmifCommand(0)] + public Result GetAudioInState(out AudioDeviceState state) + { + state = _impl.GetState(); + + return Result.Success; + } + + [CmifCommand(1)] + public Result Start() + { + return new Result((int)_impl.Start()); + } + + [CmifCommand(2)] + public Result Stop() + { + return new Result((int)_impl.Stop()); + } + + [CmifCommand(3)] + public Result AppendAudioInBuffer(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan buffer) + { + AudioUserBuffer userBuffer = default; + + if (buffer.Length > 0) + { + userBuffer = buffer[0]; + } + + return new Result((int)_impl.AppendBuffer(bufferTag, ref userBuffer)); + } + + [CmifCommand(4)] + public Result RegisterBufferEvent([CopyHandle] out int eventHandle) + { + eventHandle = 0; + + if (_impl.RegisterBufferEvent() is AudioEvent audioEvent) + { + eventHandle = audioEvent.GetReadableHandle(); + } + + return Result.Success; + } + + [CmifCommand(5)] + public Result GetReleasedAudioInBuffers(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span bufferTags) + { + return new Result((int)_impl.GetReleasedBuffers(bufferTags, out count)); + } + + [CmifCommand(6)] + public Result ContainsAudioInBuffer(out bool contains, ulong bufferTag) + { + contains = _impl.ContainsBuffer(bufferTag); + + return Result.Success; + } + + [CmifCommand(7)] // 3.0.0+ + public Result AppendUacInBuffer( + ulong bufferTag, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan buffer, + [CopyHandle] int eventHandle) + { + AudioUserBuffer userBuffer = default; + + if (buffer.Length > 0) + { + userBuffer = buffer[0]; + } + + return new Result((int)_impl.AppendUacBuffer(bufferTag, ref userBuffer, (uint)eventHandle)); + } + + [CmifCommand(8)] // 3.0.0+ + public Result AppendAudioInBufferAuto(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan buffer) + { + return AppendAudioInBuffer(bufferTag, buffer); + } + + [CmifCommand(9)] // 3.0.0+ + public Result GetReleasedAudioInBuffersAuto(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span bufferTags) + { + return GetReleasedAudioInBuffers(out count, bufferTags); + } + + [CmifCommand(10)] // 3.0.0+ + public Result AppendUacInBufferAuto( + ulong bufferTag, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan buffer, + [CopyHandle] int eventHandle) + { + return AppendUacInBuffer(bufferTag, buffer, eventHandle); + } + + [CmifCommand(11)] // 4.0.0+ + public Result GetAudioInBufferCount(out uint bufferCount) + { + bufferCount = _impl.GetBufferCount(); + + return Result.Success; + } + + [CmifCommand(12)] // 4.0.0+ + public Result SetDeviceGain(float gain) + { + _impl.SetVolume(gain); + + return Result.Success; + } + + [CmifCommand(13)] // 4.0.0+ + public Result GetDeviceGain(out float gain) + { + gain = _impl.GetVolume(); + + return Result.Success; + } + + [CmifCommand(14)] // 6.0.0+ + public Result FlushAudioInBuffers(out bool pending) + { + pending = _impl.FlushBuffers(); + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _impl.Dispose(); + + if (_processHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_processHandle); + + _processHandle = 0; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs new file mode 100644 index 00000000..d5d04720 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs @@ -0,0 +1,130 @@ +using Ryujinx.Audio; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Input; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioInManager : IAudioInManager + { + private readonly AudioInputManager _impl; + + public AudioInManager(AudioInputManager impl) + { + _impl = impl; + } + + [CmifCommand(0)] + public Result ListAudioIns(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span names) + { + string[] deviceNames = _impl.ListAudioIns(filtered: false); + + count = 0; + + foreach (string deviceName in deviceNames) + { + if (count >= names.Length) + { + break; + } + + names[count++] = new DeviceName(deviceName); + } + + return Result.Success; + } + + [CmifCommand(1)] + public Result OpenAudioIn( + out AudioOutputConfiguration outputConfiguration, + out IAudioIn audioIn, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan name, + [ClientProcessId] ulong pid) + { + var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle); + + ResultCode rc = _impl.OpenAudioIn( + out string outputDeviceName, + out outputConfiguration, + out AudioInputSystem inSystem, + clientMemoryManager, + name.Length > 0 ? name[0].ToString() : string.Empty, + SampleFormat.PcmInt16, + ref parameter); + + if (rc == ResultCode.Success && outName.Length > 0) + { + outName[0] = new DeviceName(outputDeviceName); + } + + audioIn = new AudioIn(inSystem, processHandle); + + return new Result((int)rc); + } + + [CmifCommand(2)] // 3.0.0+ + public Result ListAudioInsAuto(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span names) + { + return ListAudioIns(out count, names); + } + + [CmifCommand(3)] // 3.0.0+ + public Result OpenAudioInAuto( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan name, + [ClientProcessId] ulong pid) + { + return OpenAudioIn(out outputConfig, out audioIn, outName, parameter, appletResourceId, processHandle, name, pid); + } + + [CmifCommand(4)] // 3.0.0+ + public Result ListAudioInsAutoFiltered(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span names) + { + string[] deviceNames = _impl.ListAudioIns(filtered: true); + + count = 0; + + foreach (string deviceName in deviceNames) + { + if (count >= names.Length) + { + break; + } + + names[count++] = new DeviceName(deviceName); + } + + return Result.Success; + } + + [CmifCommand(5)] // 5.0.0+ + public Result OpenAudioInProtocolSpecified( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span outName, + AudioInProtocol protocol, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan name, + [ClientProcessId] ulong pid) + { + // NOTE: We always assume that only the default device will be plugged (we never report any USB Audio Class type devices). + + return OpenAudioIn(out outputConfig, out audioIn, outName, parameter, appletResourceId, processHandle, name, pid); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs new file mode 100644 index 00000000..48785f1c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs @@ -0,0 +1,23 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)] + struct AudioInProtocol + { + public AudioInProtocolName Name; + public Array7 Padding; + + public AudioInProtocol(AudioInProtocolName name) + { + Name = name; + Padding = new(); + } + + public override readonly string ToString() + { + return Name.ToString(); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocolName.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocolName.cs new file mode 100644 index 00000000..68d283cc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocolName.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + enum AudioInProtocolName : byte + { + DeviceIn = 0, + UacIn = 1, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs new file mode 100644 index 00000000..7607e264 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs @@ -0,0 +1,154 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Output; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioOut : IAudioOut, IDisposable + { + private readonly AudioOutputSystem _impl; + private int _processHandle; + + public AudioOut(AudioOutputSystem impl, int processHandle) + { + _impl = impl; + _processHandle = processHandle; + } + + [CmifCommand(0)] + public Result GetAudioOutState(out AudioDeviceState state) + { + state = _impl.GetState(); + + return Result.Success; + } + + [CmifCommand(1)] + public Result Start() + { + return new Result((int)_impl.Start()); + } + + [CmifCommand(2)] + public Result Stop() + { + return new Result((int)_impl.Stop()); + } + + [CmifCommand(3)] + public Result AppendAudioOutBuffer(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan buffer) + { + AudioUserBuffer userBuffer = default; + + if (buffer.Length > 0) + { + userBuffer = buffer[0]; + } + + return new Result((int)_impl.AppendBuffer(bufferTag, ref userBuffer)); + } + + [CmifCommand(4)] + public Result RegisterBufferEvent([CopyHandle] out int eventHandle) + { + eventHandle = 0; + + if (_impl.RegisterBufferEvent() is AudioEvent audioEvent) + { + eventHandle = audioEvent.GetReadableHandle(); + } + + return Result.Success; + } + + [CmifCommand(5)] + public Result GetReleasedAudioOutBuffers(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span bufferTags) + { + return new Result((int)_impl.GetReleasedBuffer(bufferTags, out count)); + } + + [CmifCommand(6)] + public Result ContainsAudioOutBuffer(out bool contains, ulong bufferTag) + { + contains = _impl.ContainsBuffer(bufferTag); + + return Result.Success; + } + + [CmifCommand(7)] // 3.0.0+ + public Result AppendAudioOutBufferAuto(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan buffer) + { + return AppendAudioOutBuffer(bufferTag, buffer); + } + + [CmifCommand(8)] // 3.0.0+ + public Result GetReleasedAudioOutBuffersAuto(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span bufferTags) + { + return GetReleasedAudioOutBuffers(out count, bufferTags); + } + + [CmifCommand(9)] // 4.0.0+ + public Result GetAudioOutBufferCount(out uint bufferCount) + { + bufferCount = _impl.GetBufferCount(); + + return Result.Success; + } + + [CmifCommand(10)] // 4.0.0+ + public Result GetAudioOutPlayedSampleCount(out ulong sampleCount) + { + sampleCount = _impl.GetPlayedSampleCount(); + + return Result.Success; + } + + [CmifCommand(11)] // 4.0.0+ + public Result FlushAudioOutBuffers(out bool pending) + { + pending = _impl.FlushBuffers(); + + return Result.Success; + } + + [CmifCommand(12)] // 6.0.0+ + public Result SetAudioOutVolume(float volume) + { + _impl.SetVolume(volume); + + return Result.Success; + } + + [CmifCommand(13)] // 6.0.0+ + public Result GetAudioOutVolume(out float volume) + { + volume = _impl.GetVolume(); + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _impl.Dispose(); + + if (_processHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_processHandle); + + _processHandle = 0; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs new file mode 100644 index 00000000..3d129470 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs @@ -0,0 +1,93 @@ +using Ryujinx.Audio; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Output; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioOutManager : IAudioOutManager + { + private readonly AudioOutputManager _impl; + + public AudioOutManager(AudioOutputManager impl) + { + _impl = impl; + } + + [CmifCommand(0)] + public Result ListAudioOuts(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span names) + { + string[] deviceNames = _impl.ListAudioOuts(); + + count = 0; + + foreach (string deviceName in deviceNames) + { + if (count >= names.Length) + { + break; + } + + names[count++] = new DeviceName(deviceName); + } + + return Result.Success; + } + + [CmifCommand(1)] + public Result OpenAudioOut( + out AudioOutputConfiguration outputConfig, + out IAudioOut audioOut, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan name, + [ClientProcessId] ulong pid) + { + var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle); + + ResultCode rc = _impl.OpenAudioOut( + out string outputDeviceName, + out outputConfig, + out AudioOutputSystem outSystem, + clientMemoryManager, + name.Length > 0 ? name[0].ToString() : string.Empty, + SampleFormat.PcmInt16, + ref parameter); + + if (rc == ResultCode.Success && outName.Length > 0) + { + outName[0] = new DeviceName(outputDeviceName); + } + + audioOut = new AudioOut(outSystem, processHandle); + + return new Result((int)rc); + } + + [CmifCommand(2)] // 3.0.0+ + public Result ListAudioOutsAuto(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span names) + { + return ListAudioOuts(out count, names); + } + + [CmifCommand(3)] // 3.0.0+ + public Result OpenAudioOutAuto( + out AudioOutputConfiguration outputConfig, + out IAudioOut audioOut, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan name, + [ClientProcessId] ulong pid) + { + return OpenAudioOut(out outputConfig, out audioOut, outName, parameter, appletResourceId, processHandle, name, pid); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs new file mode 100644 index 00000000..4d446bba --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs @@ -0,0 +1,178 @@ +using Ryujinx.Audio; +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Buffers; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioRenderer : IAudioRenderer, IDisposable + { + private readonly AudioRenderSystem _renderSystem; + private int _workBufferHandle; + private int _processHandle; + + public AudioRenderer(AudioRenderSystem renderSystem, int workBufferHandle, int processHandle) + { + _renderSystem = renderSystem; + _workBufferHandle = workBufferHandle; + _processHandle = processHandle; + } + + [CmifCommand(0)] + public Result GetSampleRate(out int sampleRate) + { + sampleRate = (int)_renderSystem.GetSampleRate(); + + return Result.Success; + } + + [CmifCommand(1)] + public Result GetSampleCount(out int sampleCount) + { + sampleCount = (int)_renderSystem.GetSampleCount(); + + return Result.Success; + } + + [CmifCommand(2)] + public Result GetMixBufferCount(out int mixBufferCount) + { + mixBufferCount = (int)_renderSystem.GetMixBufferCount(); + + return Result.Success; + } + + [CmifCommand(3)] + public Result GetState(out int state) + { + state = _renderSystem.IsActive() ? 0 : 1; + + return Result.Success; + } + + [CmifCommand(4)] + public Result RequestUpdate( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Memory output, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Memory performanceOutput, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySequence input) + { + using MemoryHandle outputHandle = output.Pin(); + using MemoryHandle performanceOutputHandle = performanceOutput.Pin(); + + Result result = new Result((int)_renderSystem.Update(output, performanceOutput, input)); + + return result; + } + + [CmifCommand(5)] + public Result Start() + { + _renderSystem.Start(); + + return Result.Success; + } + + [CmifCommand(6)] + public Result Stop() + { + _renderSystem.Stop(); + + return Result.Success; + } + + [CmifCommand(7)] + public Result QuerySystemEvent([CopyHandle] out int eventHandle) + { + ResultCode rc = _renderSystem.QuerySystemEvent(out IWritableEvent systemEvent); + + eventHandle = 0; + + if (rc == ResultCode.Success && systemEvent is AudioEvent audioEvent) + { + eventHandle = audioEvent.GetReadableHandle(); + } + + return new Result((int)rc); + } + + [CmifCommand(8)] + public Result SetRenderingTimeLimit(int percent) + { + _renderSystem.SetRenderingTimeLimitPercent((uint)percent); + + return Result.Success; + } + + [CmifCommand(9)] + public Result GetRenderingTimeLimit(out int percent) + { + percent = (int)_renderSystem.GetRenderingTimeLimit(); + + return Result.Success; + } + + [CmifCommand(10)] // 3.0.0+ + public Result RequestUpdateAuto( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Memory output, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Memory performanceOutput, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySequence input) + { + return RequestUpdate(output, performanceOutput, input); + } + + [CmifCommand(11)] // 3.0.0+ + public Result ExecuteAudioRendererRendering() + { + return new Result((int)_renderSystem.ExecuteAudioRendererRendering()); + } + + [CmifCommand(12)] // 15.0.0+ + public Result SetVoiceDropParameter(float voiceDropParameter) + { + _renderSystem.SetVoiceDropParameter(voiceDropParameter); + + return Result.Success; + } + + [CmifCommand(13)] // 15.0.0+ + public Result GetVoiceDropParameter(out float voiceDropParameter) + { + voiceDropParameter = _renderSystem.GetVoiceDropParameter(); + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _renderSystem.Dispose(); + + if (_workBufferHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_workBufferHandle); + + _workBufferHandle = 0; + } + + if (_processHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_processHandle); + + _processHandle = 0; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs new file mode 100644 index 00000000..7138d27c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs @@ -0,0 +1,132 @@ +using Ryujinx.Audio.Renderer.Device; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioRendererManager : IAudioRendererManager + { + private const uint InitialRevision = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('1' << 24); + + private readonly Ryujinx.Audio.Renderer.Server.AudioRendererManager _impl; + private readonly VirtualDeviceSessionRegistry _registry; + + public AudioRendererManager(Ryujinx.Audio.Renderer.Server.AudioRendererManager impl, VirtualDeviceSessionRegistry registry) + { + _impl = impl; + _registry = registry; + } + + [CmifCommand(0)] + public Result OpenAudioRenderer( + out IAudioRenderer renderer, + AudioRendererParameterInternal parameter, + [CopyHandle] int workBufferHandle, + [CopyHandle] int processHandle, + ulong workBufferSize, + AppletResourceUserId appletResourceId, + [ClientProcessId] ulong pid) + { + var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle); + ulong workBufferAddress = HorizonStatic.Syscall.GetTransferMemoryAddress(workBufferHandle); + + Result result = new Result((int)_impl.OpenAudioRenderer( + out var renderSystem, + clientMemoryManager, + ref parameter.Configuration, + appletResourceId.Id, + workBufferAddress, + workBufferSize, + (uint)processHandle)); + + if (result.IsSuccess) + { + renderer = new AudioRenderer(renderSystem, workBufferHandle, processHandle); + } + else + { + renderer = null; + + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + HorizonStatic.Syscall.CloseHandle(processHandle); + } + + return result; + } + + [CmifCommand(1)] + public Result GetWorkBufferSize(out long workBufferSize, AudioRendererParameterInternal parameter) + { + if (BehaviourContext.CheckValidRevision(parameter.Configuration.Revision)) + { + workBufferSize = (long)Ryujinx.Audio.Renderer.Server.AudioRendererManager.GetWorkBufferSize(ref parameter.Configuration); + + Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{workBufferSize:x16}."); + + return Result.Success; + } + else + { + workBufferSize = 0; + + Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{BehaviourContext.GetRevisionNumber(parameter.Configuration.Revision)} is not supported!"); + + return AudioResult.UnsupportedRevision; + } + } + + [CmifCommand(2)] + public Result GetAudioDeviceService(out IAudioDevice audioDevice, AppletResourceUserId appletResourceId) + { + audioDevice = new AudioDevice(_registry, appletResourceId, InitialRevision); + + return Result.Success; + } + + [CmifCommand(3)] // 3.0.0+ + public Result OpenAudioRendererForManualExecution( + out IAudioRenderer renderer, + AudioRendererParameterInternal parameter, + ulong workBufferAddress, + [CopyHandle] int processHandle, + ulong workBufferSize, + AppletResourceUserId appletResourceId, + [ClientProcessId] ulong pid) + { + var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle); + + Result result = new Result((int)_impl.OpenAudioRenderer( + out var renderSystem, + clientMemoryManager, + ref parameter.Configuration, + appletResourceId.Id, + workBufferAddress, + workBufferSize, + (uint)processHandle)); + + if (result.IsSuccess) + { + renderer = new AudioRenderer(renderSystem, 0, processHandle); + } + else + { + renderer = null; + + HorizonStatic.Syscall.CloseHandle(processHandle); + } + + return result; + } + + [CmifCommand(4)] // 4.0.0+ + public Result GetAudioDeviceServiceWithRevisionInfo(out IAudioDevice audioDevice, AppletResourceUserId appletResourceId, uint revision) + { + audioDevice = new AudioDevice(_registry, appletResourceId, revision); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererParameterInternal.cs new file mode 100644 index 00000000..e5fcf7b3 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererParameterInternal.cs @@ -0,0 +1,14 @@ +using Ryujinx.Audio.Renderer.Parameter; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + struct AudioRendererParameterInternal + { + public AudioRendererConfiguration Configuration; + + public AudioRendererParameterInternal(AudioRendererConfiguration configuration) + { + Configuration = configuration; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs new file mode 100644 index 00000000..cf1fe3d1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs @@ -0,0 +1,30 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioSnoopManager : IAudioSnoopManager + { + // Note: The interface changed completely on firmware 17.0.0, this implementation is for older firmware. + + [CmifCommand(0)] + public Result EnableDspUsageMeasurement() + { + return Result.Success; + } + + [CmifCommand(1)] + public Result DisableDspUsageMeasurement() + { + return Result.Success; + } + + [CmifCommand(6)] + public Result GetDspUsage(out uint usage) + { + usage = 0; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs new file mode 100644 index 00000000..b77e2f40 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs @@ -0,0 +1,30 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x100, Pack = 1)] + struct DeviceName + { + public Array256 Name; + + public DeviceName(string name) + { + Name = new(); + Encoding.ASCII.GetBytes(name, Name.AsSpan()); + } + + public override string ToString() + { + int length = Name.AsSpan().IndexOf((byte)0); + if (length < 0) + { + length = 0x100; + } + + return Encoding.ASCII.GetString(Name.AsSpan()[..length]); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs new file mode 100644 index 00000000..39391437 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs @@ -0,0 +1,147 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class FinalOutputRecorder : IFinalOutputRecorder, IDisposable + { + private int _processHandle; + private SystemEventType _event; + + public FinalOutputRecorder(int processHandle) + { + _processHandle = processHandle; + Os.CreateSystemEvent(out _event, EventClearMode.ManualClear, interProcess: true); + } + + [CmifCommand(0)] + public Result GetFinalOutputRecorderState(out uint state) + { + state = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(1)] + public Result Start() + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(2)] + public Result Stop() + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(3)] + public Result AppendFinalOutputRecorderBuffer([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan buffer, ulong bufferClientPtr) + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferClientPtr }); + + return Result.Success; + } + + [CmifCommand(4)] + public Result RegisterBufferEvent([CopyHandle] out int eventHandle) + { + eventHandle = Os.GetReadableHandleOfSystemEvent(ref _event); + + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(5)] + public Result GetReleasedFinalOutputRecorderBuffers([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span buffer, out uint count, out ulong released) + { + count = 0; + released = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(6)] + public Result ContainsFinalOutputRecorderBuffer(ulong bufferPointer, out bool contains) + { + contains = false; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferPointer }); + + return Result.Success; + } + + [CmifCommand(7)] + public Result GetFinalOutputRecorderBufferEndTime(ulong bufferPointer, out ulong released) + { + released = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferPointer }); + + return Result.Success; + } + + [CmifCommand(8)] // 3.0.0+ + public Result AppendFinalOutputRecorderBufferAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan buffer, ulong bufferClientPtr) + { + return AppendFinalOutputRecorderBuffer(buffer, bufferClientPtr); + } + + [CmifCommand(9)] // 3.0.0+ + public Result GetReleasedFinalOutputRecorderBuffersAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span buffer, out uint count, out ulong released) + { + return GetReleasedFinalOutputRecorderBuffers(buffer, out count, out released); + } + + [CmifCommand(10)] // 6.0.0+ + public Result FlushFinalOutputRecorderBuffers(out bool pending) + { + pending = false; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(11)] // 9.0.0+ + public Result AttachWorkBuffer(FinalOutputRecorderParameterInternal parameter) + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { parameter }); + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Os.DestroySystemEvent(ref _event); + + if (_processHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_processHandle); + + _processHandle = 0; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderManager.cs new file mode 100644 index 00000000..76491bb7 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderManager.cs @@ -0,0 +1,23 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class FinalOutputRecorderManager : IFinalOutputRecorderManager + { + [CmifCommand(0)] + public Result OpenFinalOutputRecorder( + out IFinalOutputRecorder recorder, + FinalOutputRecorderParameter parameter, + [CopyHandle] int processHandle, + out FinalOutputRecorderParameterInternal outParameter, + AppletResourceUserId appletResourceId) + { + recorder = new FinalOutputRecorder(processHandle); + outParameter = new(parameter.SampleRate, 2, 0); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameter.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameter.cs new file mode 100644 index 00000000..afa060fc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameter.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)] + readonly struct FinalOutputRecorderParameter + { + public readonly uint SampleRate; + public readonly uint Padding; + + public FinalOutputRecorderParameter(uint sampleRate) + { + SampleRate = sampleRate; + Padding = 0; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameterInternal.cs new file mode 100644 index 00000000..e88398eb --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameterInternal.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)] + readonly struct FinalOutputRecorderParameterInternal + { + public readonly uint SampleRate; + public readonly uint ChannelCount; + public readonly uint UseLargeFrameSize; + public readonly uint Padding; + + public FinalOutputRecorderParameterInternal(uint sampleRate, uint channelCount, uint useLargeFrameSize) + { + SampleRate = sampleRate; + ChannelCount = channelCount; + UseLargeFrameSize = useLargeFrameSize; + Padding = 0; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs new file mode 100644 index 00000000..3df1fe22 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs @@ -0,0 +1,24 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioDevice : IServiceObject + { + Result ListAudioDeviceName(Span names, out int nameCount); + Result SetAudioDeviceOutputVolume(ReadOnlySpan name, float volume); + Result GetAudioDeviceOutputVolume(ReadOnlySpan name, out float volume); + Result GetActiveAudioDeviceName(Span name); + Result QueryAudioDeviceSystemEvent(out int eventHandle); + Result GetActiveChannelCount(out int channelCount); + Result ListAudioDeviceNameAuto(Span names, out int nameCount); + Result SetAudioDeviceOutputVolumeAuto(ReadOnlySpan name, float volume); + Result GetAudioDeviceOutputVolumeAuto(ReadOnlySpan name, out float volume); + Result GetActiveAudioDeviceNameAuto(Span name); + Result QueryAudioDeviceInputEvent(out int eventHandle); + Result QueryAudioDeviceOutputEvent(out int eventHandle); + Result GetActiveAudioOutputDeviceName(Span name); + Result ListAudioOutputDeviceName(Span names, out int nameCount); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs new file mode 100644 index 00000000..bdc3bcf6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs @@ -0,0 +1,26 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioIn : IServiceObject + { + Result GetAudioInState(out AudioDeviceState state); + Result Start(); + Result Stop(); + Result AppendAudioInBuffer(ulong bufferTag, ReadOnlySpan buffer); + Result RegisterBufferEvent(out int eventHandle); + Result GetReleasedAudioInBuffers(out uint count, Span bufferTags); + Result ContainsAudioInBuffer(out bool contains, ulong bufferTag); + Result AppendUacInBuffer(ulong bufferTag, ReadOnlySpan buffer, int eventHandle); + Result AppendAudioInBufferAuto(ulong bufferTag, ReadOnlySpan buffer); + Result GetReleasedAudioInBuffersAuto(out uint count, Span bufferTags); + Result AppendUacInBufferAuto(ulong bufferTag, ReadOnlySpan buffer, int eventHandle); + Result GetAudioInBufferCount(out uint bufferCount); + Result SetDeviceGain(float gain); + Result GetDeviceGain(out float gain); + Result FlushAudioInBuffers(out bool pending); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs new file mode 100644 index 00000000..e7f32fbd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs @@ -0,0 +1,43 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioInManager : IServiceObject + { + Result ListAudioIns(out int count, Span names); + Result OpenAudioIn( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + Span outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan name, + ulong pid); + Result ListAudioInsAuto(out int count, Span names); + Result OpenAudioInAuto( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + Span outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan name, + ulong pid); + Result ListAudioInsAutoFiltered(out int count, Span names); + Result OpenAudioInProtocolSpecified( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + Span outName, + AudioInProtocol protocol, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan name, + ulong pid); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs new file mode 100644 index 00000000..1b200926 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs @@ -0,0 +1,25 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioOut : IServiceObject + { + Result GetAudioOutState(out AudioDeviceState state); + Result Start(); + Result Stop(); + Result AppendAudioOutBuffer(ulong bufferTag, ReadOnlySpan buffer); + Result RegisterBufferEvent(out int eventHandle); + Result GetReleasedAudioOutBuffers(out uint count, Span bufferTags); + Result ContainsAudioOutBuffer(out bool contains, ulong bufferTag); + Result AppendAudioOutBufferAuto(ulong bufferTag, ReadOnlySpan buffer); + Result GetReleasedAudioOutBuffersAuto(out uint count, Span bufferTags); + Result GetAudioOutBufferCount(out uint bufferCount); + Result GetAudioOutPlayedSampleCount(out ulong sampleCount); + Result FlushAudioOutBuffers(out bool pending); + Result SetAudioOutVolume(float volume); + Result GetAudioOutVolume(out float volume); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs new file mode 100644 index 00000000..40d62836 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs @@ -0,0 +1,32 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioOutManager : IServiceObject + { + Result ListAudioOuts(out int count, Span names); + Result OpenAudioOut( + out AudioOutputConfiguration outputConfig, + out IAudioOut audioOut, + Span outName, + AudioInputConfiguration inputConfig, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan name, + ulong pid); + Result ListAudioOutsAuto(out int count, Span names); + Result OpenAudioOutAuto( + out AudioOutputConfiguration outputConfig, + out IAudioOut audioOut, + Span outName, + AudioInputConfiguration inputConfig, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan name, + ulong pid); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs new file mode 100644 index 00000000..b766bd73 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs @@ -0,0 +1,25 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; +using System.Buffers; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioRenderer : IServiceObject + { + Result GetSampleRate(out int sampleRate); + Result GetSampleCount(out int sampleCount); + Result GetMixBufferCount(out int mixBufferCount); + Result GetState(out int state); + Result RequestUpdate(Memory output, Memory performanceOutput, ReadOnlySequence input); + Result Start(); + Result Stop(); + Result QuerySystemEvent(out int eventHandle); + Result SetRenderingTimeLimit(int percent); + Result GetRenderingTimeLimit(out int percent); + Result RequestUpdateAuto(Memory output, Memory performanceOutput, ReadOnlySequence input); + Result ExecuteAudioRendererRendering(); + Result SetVoiceDropParameter(float voiceDropParameter); + Result GetVoiceDropParameter(out float voiceDropParameter); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRendererManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRendererManager.cs new file mode 100644 index 00000000..fe95a208 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRendererManager.cs @@ -0,0 +1,29 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioRendererManager : IServiceObject + { + Result OpenAudioRenderer( + out IAudioRenderer renderer, + AudioRendererParameterInternal parameter, + int processHandle, + int workBufferHandle, + ulong workBufferSize, + AppletResourceUserId appletUserId, + ulong pid); + Result GetWorkBufferSize(out long workBufferSize, AudioRendererParameterInternal parameter); + Result GetAudioDeviceService(out IAudioDevice audioDevice, AppletResourceUserId appletUserId); + Result OpenAudioRendererForManualExecution( + out IAudioRenderer renderer, + AudioRendererParameterInternal parameter, + ulong workBufferAddress, + int processHandle, + ulong workBufferSize, + AppletResourceUserId appletUserId, + ulong pid); + Result GetAudioDeviceServiceWithRevisionInfo(out IAudioDevice audioDevice, AppletResourceUserId appletUserId, uint revision); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs new file mode 100644 index 00000000..72853886 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioSnoopManager : IServiceObject + { + Result EnableDspUsageMeasurement(); + Result DisableDspUsageMeasurement(); + Result GetDspUsage(out uint usage); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs new file mode 100644 index 00000000..be21c38b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs @@ -0,0 +1,22 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IFinalOutputRecorder : IServiceObject + { + Result GetFinalOutputRecorderState(out uint state); + Result Start(); + Result Stop(); + Result AppendFinalOutputRecorderBuffer(ReadOnlySpan buffer, ulong bufferClientPtr); + Result RegisterBufferEvent(out int eventHandle); + Result GetReleasedFinalOutputRecorderBuffers(Span buffer, out uint count, out ulong released); + Result ContainsFinalOutputRecorderBuffer(ulong bufferPointer, out bool contains); + Result GetFinalOutputRecorderBufferEndTime(ulong bufferPointer, out ulong released); + Result AppendFinalOutputRecorderBufferAuto(ReadOnlySpan buffer, ulong bufferClientPtr); + Result GetReleasedFinalOutputRecorderBuffersAuto(Span buffer, out uint count, out ulong released); + Result FlushFinalOutputRecorderBuffers(out bool pending); + Result AttachWorkBuffer(FinalOutputRecorderParameterInternal parameter); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorderManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorderManager.cs new file mode 100644 index 00000000..bac41ca9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorderManager.cs @@ -0,0 +1,16 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IFinalOutputRecorderManager : IServiceObject + { + Result OpenFinalOutputRecorder( + out IFinalOutputRecorder recorder, + FinalOutputRecorderParameter parameter, + int processHandle, + out FinalOutputRecorderParameterInternal outParameter, + AppletResourceUserId appletResourceId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Bcat/IBcatService.cs b/src/Ryujinx.Horizon/Sdk/Bcat/IBcatService.cs new file mode 100644 index 00000000..9201c4b2 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Bcat/IBcatService.cs @@ -0,0 +1,10 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Bcat +{ + internal interface IBcatService : IServiceObject + { + Result RequestSyncDeliveryCache(out IDeliveryCacheProgressService deliveryCacheProgressService); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheDirectoryService.cs b/src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheDirectoryService.cs new file mode 100644 index 00000000..f2f6c5b5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheDirectoryService.cs @@ -0,0 +1,14 @@ +using LibHac.Bcat; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Bcat +{ + internal interface IDeliveryCacheDirectoryService : IServiceObject + { + Result GetCount(out int count); + Result Open(DirectoryName directoryName); + Result Read(out int entriesRead, Span entriesBuffer); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheFileService.cs b/src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheFileService.cs new file mode 100644 index 00000000..1cba63ce --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheFileService.cs @@ -0,0 +1,15 @@ +using LibHac.Bcat; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Bcat +{ + internal interface IDeliveryCacheFileService : IServiceObject + { + Result GetDigest(out Digest digest); + Result GetSize(out long size); + Result Open(DirectoryName directoryName, FileName fileName); + Result Read(long offset, out long bytesRead, Span data); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheProgressService.cs b/src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheProgressService.cs new file mode 100644 index 00000000..bafcb9e1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheProgressService.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Bcat.Ipc.Types; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Bcat +{ + internal interface IDeliveryCacheProgressService : IServiceObject + { + Result GetEvent(out int handle); + Result GetImpl(out DeliveryCacheProgressImpl deliveryCacheProgressImpl); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheStorageService.cs b/src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheStorageService.cs new file mode 100644 index 00000000..f83114d9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Bcat/IDeliveryCacheStorageService.cs @@ -0,0 +1,14 @@ +using LibHac.Bcat; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Bcat +{ + internal interface IDeliveryCacheStorageService : IServiceObject + { + Result CreateDirectoryService(out IDeliveryCacheDirectoryService service); + Result CreateFileService(out IDeliveryCacheFileService service); + Result EnumerateDeliveryCacheDirectory(out int count, Span directoryNames); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Bcat/IServiceCreator.cs b/src/Ryujinx.Horizon/Sdk/Bcat/IServiceCreator.cs new file mode 100644 index 00000000..f31ff3e9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Bcat/IServiceCreator.cs @@ -0,0 +1,13 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Ncm; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Bcat +{ + internal interface IServiceCreator : IServiceObject + { + Result CreateBcatService(out IBcatService service, ulong pid); + Result CreateDeliveryCacheStorageService(out IDeliveryCacheStorageService service, ulong pid); + Result CreateDeliveryCacheStorageServiceWithApplicationId(out IDeliveryCacheStorageService service, ApplicationId applicationId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs b/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs new file mode 100644 index 00000000..21508b7f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs @@ -0,0 +1,16 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Codec +{ + static class CodecResult + { + private const int ModuleId = 111; + + public static Result InvalidLength => new(ModuleId, 3); + public static Result OpusBadArg => new(ModuleId, 130); + public static Result OpusInvalidPacket => new(ModuleId, 133); + public static Result InvalidNumberOfStreams => new(ModuleId, 1000); + public static Result InvalidSampleRate => new(ModuleId, 1001); + public static Result InvalidChannelCount => new(ModuleId, 1002); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs new file mode 100644 index 00000000..2146362d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs @@ -0,0 +1,375 @@ +using Concentus; +using Concentus.Enums; +using Concentus.Structs; +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + partial class HardwareOpusDecoder : IHardwareOpusDecoder, IDisposable + { + static HardwareOpusDecoder() + { + OpusCodecFactory.AttemptToUseNativeLibrary = false; + } + + [StructLayout(LayoutKind.Sequential)] + private struct OpusPacketHeader + { + public uint Length; + public uint FinalRange; + + public static OpusPacketHeader FromSpan(ReadOnlySpan data) + { + return new() + { + Length = BinaryPrimitives.ReadUInt32BigEndian(data), + FinalRange = BinaryPrimitives.ReadUInt32BigEndian(data[sizeof(uint)..]), + }; + } + } + + private interface IDecoder : IDisposable + { + int SampleRate { get; } + int ChannelsCount { get; } + + int Decode(ReadOnlySpan inData, Span outPcm, int frameSize); + void ResetState(); + } + + private class Decoder : IDecoder + { + private readonly IOpusDecoder _decoder; + + public int SampleRate => _decoder.SampleRate; + public int ChannelsCount => _decoder.NumChannels; + + public Decoder(int sampleRate, int channelsCount) + { + _decoder = OpusCodecFactory.CreateDecoder(sampleRate, channelsCount); + } + + public int Decode(ReadOnlySpan inData, Span outPcm, int frameSize) + { + return _decoder.Decode(inData, outPcm, frameSize); + } + + public void ResetState() + { + _decoder.ResetState(); + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _decoder?.Dispose(); + } + } + } + + private class MultiSampleDecoder : IDecoder + { + private readonly IOpusMultiStreamDecoder _decoder; + + public int SampleRate => _decoder.SampleRate; + public int ChannelsCount => _decoder.NumChannels; + + public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping) + { + _decoder = OpusCodecFactory.CreateMultiStreamDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); + } + + public int Decode(ReadOnlySpan inData, Span outPcm, int frameSize) + { + return _decoder.DecodeMultistream(inData, outPcm, frameSize, false); + } + + public void ResetState() + { + _decoder.ResetState(); + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _decoder?.Dispose(); + } + } + } + + private readonly IDecoder _decoder; + private int _workBufferHandle; + + private HardwareOpusDecoder(int workBufferHandle) + { + _workBufferHandle = workBufferHandle; + } + + public HardwareOpusDecoder(int sampleRate, int channelsCount, int workBufferHandle) : this(workBufferHandle) + { + _decoder = new Decoder(sampleRate, channelsCount); + } + + public HardwareOpusDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping, int workBufferHandle) : this(workBufferHandle) + { + _decoder = new MultiSampleDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); + } + + [CmifCommand(0)] + public Result DecodeInterleavedOld( + out int outConsumed, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out _, output, input, reset: false, withPerf: false); + } + + [CmifCommand(1)] + public Result SetContext(ReadOnlySpan context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(2)] // 3.0.0+ + public Result DecodeInterleavedForMultiStreamOld( + out int outConsumed, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out _, output, input, reset: false, withPerf: false); + } + + [CmifCommand(3)] // 3.0.0+ + public Result SetContextForMultiStream(ReadOnlySpan arg0) + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(4)] // 4.0.0+ + public Result DecodeInterleavedWithPerfOld( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset: false, withPerf: true); + } + + [CmifCommand(5)] // 4.0.0+ + public Result DecodeInterleavedForMultiStreamWithPerfOld( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset: false, withPerf: true); + } + + [CmifCommand(6)] // 6.0.0+ + public Result DecodeInterleavedWithPerfAndResetOld( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input, + bool reset) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true); + } + + [CmifCommand(7)] // 6.0.0+ + public Result DecodeInterleavedForMultiStreamWithPerfAndResetOld( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan input, + bool reset) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true); + } + + [CmifCommand(8)] // 7.0.0+ + public Result DecodeInterleaved( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] ReadOnlySpan input, + bool reset) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true); + } + + [CmifCommand(9)] // 7.0.0+ + public Result DecodeInterleavedForMultiStream( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] ReadOnlySpan input, + bool reset) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true); + } + + private Result DecodeInterleavedInternal( + out int outConsumed, + out int outSamples, + out long timeTaken, + Span output, + ReadOnlySpan input, + bool reset, + bool withPerf) + { + timeTaken = 0; + + Span outPcmSpace = MemoryMarshal.Cast(output); + Result result = DecodeInterleaved(_decoder, reset, input, outPcmSpace, output.Length, out outConsumed, out outSamples); + + if (withPerf) + { + // This is the time the DSP took to process the request, TODO: fill this. + timeTaken = 0; + } + + return result; + } + + private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, ReadOnlySpan packet) + { + int result = OpusPacketInfo.GetNumSamples(packet, decoder.SampleRate); + + numSamples = result; + + if (result == OpusError.OPUS_INVALID_PACKET) + { + return CodecResult.OpusInvalidPacket; + } + else if (result == OpusError.OPUS_BAD_ARG) + { + return CodecResult.OpusBadArg; + } + + return Result.Success; + } + + private static Result DecodeInterleaved( + IDecoder decoder, + bool reset, + ReadOnlySpan input, + Span outPcmData, + int outputSize, + out int outConsumed, + out int outSamples) + { + outConsumed = 0; + outSamples = 0; + + int streamSize = input.Length; + + if (streamSize < Unsafe.SizeOf()) + { + return CodecResult.InvalidLength; + } + + OpusPacketHeader header = OpusPacketHeader.FromSpan(input); + int headerSize = Unsafe.SizeOf(); + uint totalSize = header.Length + (uint)headerSize; + + if (totalSize > streamSize) + { + return CodecResult.InvalidLength; + } + + ReadOnlySpan opusData = input.Slice(headerSize, (int)header.Length); + + Result result = GetPacketNumSamples(decoder, out int numSamples, opusData); + + if (result.IsSuccess) + { + if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize) + { + return CodecResult.InvalidLength; + } + + if (reset) + { + decoder.ResetState(); + } + + try + { + outSamples = decoder.Decode(opusData, outPcmData, numSamples); + outConsumed = (int)totalSize; + } + catch (OpusException e) + { + switch (e.OpusErrorCode) + { + case OpusError.OPUS_BUFFER_TOO_SMALL: + return CodecResult.InvalidLength; + case OpusError.OPUS_BAD_ARG: + return CodecResult.OpusBadArg; + case OpusError.OPUS_INVALID_PACKET: + return CodecResult.OpusInvalidPacket; + default: + return CodecResult.InvalidLength; + } + } + } + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (_workBufferHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_workBufferHandle); + + _workBufferHandle = 0; + } + + _decoder?.Dispose(); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs new file mode 100644 index 00000000..acec66e8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs @@ -0,0 +1,386 @@ +using Ryujinx.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + partial class HardwareOpusDecoderManager : IHardwareOpusDecoderManager + { + [CmifCommand(0)] + public Result OpenHardwareOpusDecoder( + out IHardwareOpusDecoder decoder, + HardwareOpusDecoderParameterInternal parameter, + [CopyHandle] int workBufferHandle, + int workBufferSize) + { + decoder = null; + + if (!IsValidSampleRate(parameter.SampleRate)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidSampleRate; + } + + if (!IsValidChannelCount(parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidChannelCount; + } + + decoder = new HardwareOpusDecoder(parameter.SampleRate, parameter.ChannelsCount, workBufferHandle); + + return Result.Success; + } + + [CmifCommand(1)] + public Result GetWorkBufferSize(out int size, HardwareOpusDecoderParameterInternal parameter) + { + size = 0; + + if (!IsValidChannelCount(parameter.ChannelsCount)) + { + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + return CodecResult.InvalidSampleRate; + } + + int opusDecoderSize = GetOpusDecoderSize(parameter.ChannelsCount); + + int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0; + int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * 1920 / sampleRateRatio : 0, 64); + size = opusDecoderSize + 1536 + frameSize; + + return Result.Success; + } + + [CmifCommand(2)] // 3.0.0+ + public Result OpenHardwareOpusDecoderForMultiStream( + out IHardwareOpusDecoder decoder, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x110)] in HardwareOpusMultiStreamDecoderParameterInternal parameter, + [CopyHandle] int workBufferHandle, + int workBufferSize) + { + decoder = null; + + if (!IsValidSampleRate(parameter.SampleRate)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidSampleRate; + } + + if (!IsValidMultiChannelCount(parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidChannelCount; + } + + if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidNumberOfStreams; + } + + decoder = new HardwareOpusDecoder( + parameter.SampleRate, + parameter.ChannelsCount, + parameter.NumberOfStreams, + parameter.NumberOfStereoStreams, + parameter.ChannelMappings.AsSpan().ToArray(), + workBufferHandle); + + return Result.Success; + } + + [CmifCommand(3)] // 3.0.0+ + public Result GetWorkBufferSizeForMultiStream( + out int size, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x110)] in HardwareOpusMultiStreamDecoderParameterInternal parameter) + { + size = 0; + + if (!IsValidMultiChannelCount(parameter.ChannelsCount)) + { + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + return CodecResult.InvalidSampleRate; + } + + if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount)) + { + return CodecResult.InvalidSampleRate; + } + + int opusDecoderSize = GetOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams); + + int streamSize = BitUtils.AlignUp(parameter.NumberOfStreams * 1500, 64); + int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0; + int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * 1920 / sampleRateRatio : 0, 64); + size = opusDecoderSize + streamSize + frameSize; + + return Result.Success; + } + + [CmifCommand(4)] // 12.0.0+ + public Result OpenHardwareOpusDecoderEx( + out IHardwareOpusDecoder decoder, + HardwareOpusDecoderParameterInternalEx parameter, + [CopyHandle] int workBufferHandle, + int workBufferSize) + { + decoder = null; + + if (!IsValidChannelCount(parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidSampleRate; + } + + decoder = new HardwareOpusDecoder(parameter.SampleRate, parameter.ChannelsCount, workBufferHandle); + + return Result.Success; + } + + [CmifCommand(5)] // 12.0.0+ + public Result GetWorkBufferSizeEx(out int size, HardwareOpusDecoderParameterInternalEx parameter) + { + return GetWorkBufferSizeExImpl(out size, in parameter, fromDsp: false); + } + + [CmifCommand(6)] // 12.0.0+ + public Result OpenHardwareOpusDecoderForMultiStreamEx( + out IHardwareOpusDecoder decoder, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, + [CopyHandle] int workBufferHandle, + int workBufferSize) + { + decoder = null; + + if (!IsValidSampleRate(parameter.SampleRate)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidSampleRate; + } + + if (!IsValidMultiChannelCount(parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidChannelCount; + } + + if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidNumberOfStreams; + } + + decoder = new HardwareOpusDecoder( + parameter.SampleRate, + parameter.ChannelsCount, + parameter.NumberOfStreams, + parameter.NumberOfStereoStreams, + parameter.ChannelMappings.AsSpan().ToArray(), + workBufferHandle); + + return Result.Success; + } + + [CmifCommand(7)] // 12.0.0+ + public Result GetWorkBufferSizeForMultiStreamEx( + out int size, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter) + { + return GetWorkBufferSizeForMultiStreamExImpl(out size, in parameter, fromDsp: false); + } + + [CmifCommand(8)] // 16.0.0+ + public Result GetWorkBufferSizeExEx(out int size, HardwareOpusDecoderParameterInternalEx parameter) + { + return GetWorkBufferSizeExImpl(out size, in parameter, fromDsp: true); + } + + [CmifCommand(9)] // 16.0.0+ + public Result GetWorkBufferSizeForMultiStreamExEx( + out int size, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter) + { + return GetWorkBufferSizeForMultiStreamExImpl(out size, in parameter, fromDsp: true); + } + + private Result GetWorkBufferSizeExImpl(out int size, in HardwareOpusDecoderParameterInternalEx parameter, bool fromDsp) + { + size = 0; + + if (!IsValidChannelCount(parameter.ChannelsCount)) + { + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + return CodecResult.InvalidSampleRate; + } + + int opusDecoderSize = fromDsp ? GetDspOpusDecoderSize(parameter.ChannelsCount) : GetOpusDecoderSize(parameter.ChannelsCount); + + int frameSizeMono48KHz = parameter.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920; + int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0; + int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * frameSizeMono48KHz / sampleRateRatio : 0, 64); + size = opusDecoderSize + 1536 + frameSize; + + return Result.Success; + } + + private Result GetWorkBufferSizeForMultiStreamExImpl(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, bool fromDsp) + { + size = 0; + + if (!IsValidMultiChannelCount(parameter.ChannelsCount)) + { + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + return CodecResult.InvalidSampleRate; + } + + if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount)) + { + return CodecResult.InvalidSampleRate; + } + + int opusDecoderSize = fromDsp + ? GetDspOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams) + : GetOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams); + + int frameSizeMono48KHz = parameter.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920; + int streamSize = BitUtils.AlignUp(parameter.NumberOfStreams * 1500, 64); + int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0; + int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * frameSizeMono48KHz / sampleRateRatio : 0, 64); + size = opusDecoderSize + streamSize + frameSize; + + return Result.Success; + } + + private static int GetDspOpusDecoderSize(int channelsCount) + { + // TODO: Figure out the size returned here. + // Not really important because we don't use the work buffer, and the size being lower is fine. + + return 0; + } + + private static int GetDspOpusMultistreamDecoderSize(int streams, int coupledStreams) + { + // TODO: Figure out the size returned here. + // Not really important because we don't use the work buffer, and the size being lower is fine. + + return 0; + } + + private static int GetOpusDecoderSize(int channelsCount) + { + const int SilkDecoderSize = 0x2160; + + if (channelsCount < 1 || channelsCount > 2) + { + return 0; + } + + int celtDecoderSize = GetCeltDecoderSize(channelsCount); + int opusDecoderSize = GetOpusDecoderAllocSize(channelsCount) | 0x50; + + return opusDecoderSize + SilkDecoderSize + celtDecoderSize; + } + + private static int GetOpusMultistreamDecoderSize(int streams, int coupledStreams) + { + if (streams < 1 || coupledStreams > streams || coupledStreams < 0) + { + return 0; + } + + int coupledSize = GetOpusDecoderSize(2); + int monoSize = GetOpusDecoderSize(1); + + return Align4(monoSize - GetOpusDecoderAllocSize(1)) * (streams - coupledStreams) + + Align4(coupledSize - GetOpusDecoderAllocSize(2)) * coupledStreams + 0xb920; + } + + private static int Align4(int value) + { + return BitUtils.AlignUp(value, 4); + } + + private static int GetOpusDecoderAllocSize(int channelsCount) + { + return channelsCount * 0x800 + 0x4800; + } + + private static int GetCeltDecoderSize(int channelsCount) + { + const int DecodeBufferSize = 0x2030; + const int Overlap = 120; + const int EBandsCount = 21; + + return (DecodeBufferSize + Overlap * 4) * channelsCount + EBandsCount * 16 + 0x54; + } + + private static bool IsValidChannelCount(int channelsCount) + { + return channelsCount > 0 && channelsCount <= 2; + } + + private static bool IsValidMultiChannelCount(int channelsCount) + { + return channelsCount > 0 && channelsCount <= 255; + } + + private static bool IsValidSampleRate(int sampleRate) + { + switch (sampleRate) + { + case 8000: + case 12000: + case 16000: + case 24000: + case 48000: + return true; + } + + return false; + } + + private static bool IsValidNumberOfStreams(int numberOfStreams, int numberOfStereoStreams, int channelsCount) + { + return numberOfStreams > 0 && + numberOfStreams + numberOfStereoStreams <= channelsCount && + numberOfStereoStreams >= 0 && + numberOfStereoStreams <= numberOfStreams; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs new file mode 100644 index 00000000..271a592c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)] + struct HardwareOpusDecoderParameterInternal + { + public int SampleRate; + public int ChannelsCount; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs new file mode 100644 index 00000000..e2b81c77 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)] + struct HardwareOpusDecoderParameterInternalEx + { + public int SampleRate; + public int ChannelsCount; + public OpusDecoderFlags Flags; + public uint Reserved; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs new file mode 100644 index 00000000..98536a4f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x110)] + struct HardwareOpusMultiStreamDecoderParameterInternal + { + public int SampleRate; + public int ChannelsCount; + public int NumberOfStreams; + public int NumberOfStereoStreams; + public Array256 ChannelMappings; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs new file mode 100644 index 00000000..8f8615df --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs @@ -0,0 +1,17 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x118)] + struct HardwareOpusMultiStreamDecoderParameterInternalEx + { + public int SampleRate; + public int ChannelsCount; + public int NumberOfStreams; + public int NumberOfStereoStreams; + public OpusDecoderFlags Flags; + public uint Reserved; + public Array256 ChannelMappings; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs new file mode 100644 index 00000000..ae09ad15 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs @@ -0,0 +1,20 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + interface IHardwareOpusDecoder : IServiceObject + { + Result DecodeInterleavedOld(out int outConsumed, out int outSamples, Span output, ReadOnlySpan input); + Result SetContext(ReadOnlySpan context); + Result DecodeInterleavedForMultiStreamOld(out int outConsumed, out int outSamples, Span output, ReadOnlySpan input); + Result SetContextForMultiStream(ReadOnlySpan context); + Result DecodeInterleavedWithPerfOld(out int outConsumed, out long timeTaken, out int outSamples, Span output, ReadOnlySpan input); + Result DecodeInterleavedForMultiStreamWithPerfOld(out int outConsumed, out long timeTaken, out int outSamples, Span output, ReadOnlySpan input); + Result DecodeInterleavedWithPerfAndResetOld(out int outConsumed, out long timeTaken, out int outSamples, Span output, ReadOnlySpan input, bool reset); + Result DecodeInterleavedForMultiStreamWithPerfAndResetOld(out int outConsumed, out long timeTaken, out int outSamples, Span output, ReadOnlySpan input, bool reset); + Result DecodeInterleaved(out int outConsumed, out long timeTaken, out int outSamples, Span output, ReadOnlySpan input, bool reset); + Result DecodeInterleavedForMultiStream(out int outConsumed, out long timeTaken, out int outSamples, Span output, ReadOnlySpan input, bool reset); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs new file mode 100644 index 00000000..fb6c787b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + interface IHardwareOpusDecoderManager : IServiceObject + { + Result OpenHardwareOpusDecoder(out IHardwareOpusDecoder decoder, HardwareOpusDecoderParameterInternal parameter, int workBufferHandle, int workBufferSize); + Result GetWorkBufferSize(out int size, HardwareOpusDecoderParameterInternal parameter); + Result OpenHardwareOpusDecoderForMultiStream(out IHardwareOpusDecoder decoder, in HardwareOpusMultiStreamDecoderParameterInternal parameter, int workBufferHandle, int workBufferSize); + Result GetWorkBufferSizeForMultiStream(out int size, in HardwareOpusMultiStreamDecoderParameterInternal parameter); + Result OpenHardwareOpusDecoderEx(out IHardwareOpusDecoder decoder, HardwareOpusDecoderParameterInternalEx parameter, int workBufferHandle, int workBufferSize); + Result GetWorkBufferSizeEx(out int size, HardwareOpusDecoderParameterInternalEx parameter); + Result OpenHardwareOpusDecoderForMultiStreamEx(out IHardwareOpusDecoder decoder, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, int workBufferHandle, int workBufferSize); + Result GetWorkBufferSizeForMultiStreamEx(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter); + Result GetWorkBufferSizeExEx(out int size, HardwareOpusDecoderParameterInternalEx parameter); + Result GetWorkBufferSizeForMultiStreamExEx(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs new file mode 100644 index 00000000..d630b10f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + [Flags] + enum OpusDecoderFlags : uint + { + None, + LargeFrameSize = 1 << 0, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/DebugUtil.cs b/src/Ryujinx.Horizon/Sdk/DebugUtil.cs new file mode 100644 index 00000000..a88c99a7 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/DebugUtil.cs @@ -0,0 +1,12 @@ +using System.Diagnostics; + +namespace Ryujinx.Horizon.Sdk +{ + static class DebugUtil + { + public static void Assert(bool condition) + { + Debug.Assert(condition); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs b/src/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs new file mode 100644 index 00000000..ecac00bb --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Horizon.Sdk.Diag +{ + enum LogSeverity : byte + { + Trace = 0, + Info = 1, + Warn = 2, + Error = 3, + Fatal = 4, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs b/src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs new file mode 100644 index 00000000..23bad3d1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Sdk.Ncm; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + struct ApplicationInfo + { + public ApplicationId ApplicationId; + public ulong PresenceGroupId; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/BlockedUserImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/BlockedUserImpl.cs new file mode 100644 index 00000000..d5f8a031 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/BlockedUserImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct BlockedUserImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendCandidateImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendCandidateImpl.cs new file mode 100644 index 00000000..21e99c75 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendCandidateImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct FriendCandidateImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendDetailedInfoImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendDetailedInfoImpl.cs new file mode 100644 index 00000000..1b46dccd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendDetailedInfoImpl.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x800)] + struct FriendDetailedInfoImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs new file mode 100644 index 00000000..d22ca4b9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Sdk.Account; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x200, Pack = 0x8)] + struct FriendImpl + { + public Uid UserId; + public NetworkServiceAccountId NetworkUserId; + public Nickname Nickname; + public UserPresenceImpl Presence; + public bool IsFavourite; + public bool IsNew; + public Array6 Unknown; + public bool IsValid; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationForViewerImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationForViewerImpl.cs new file mode 100644 index 00000000..416ba365 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationForViewerImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct FriendInvitationForViewerImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationGroupImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationGroupImpl.cs new file mode 100644 index 00000000..ef923834 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationGroupImpl.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x1400)] + struct FriendInvitationGroupImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendRequestImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendRequestImpl.cs new file mode 100644 index 00000000..ba567169 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendRequestImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct FriendRequestImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendSettingImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendSettingImpl.cs new file mode 100644 index 00000000..f711d31f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendSettingImpl.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40)] + struct FriendSettingImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/DaemonSuspendSessionService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/DaemonSuspendSessionService.cs new file mode 100644 index 00000000..aaf88ed0 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/DaemonSuspendSessionService.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + partial class DaemonSuspendSessionService : IDaemonSuspendSessionService + { + // NOTE: This service has no commands. + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendService.cs new file mode 100644 index 00000000..1b4c8c30 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendService.cs @@ -0,0 +1,1015 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Settings; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + partial class FriendService : IFriendService, IDisposable + { + private readonly IEmulatorAccountManager _accountManager; + private SystemEventType _completionEvent; + + public FriendService(IEmulatorAccountManager accountManager, FriendsServicePermissionLevel permissionLevel) + { + _accountManager = accountManager; + + Os.CreateSystemEvent(out _completionEvent, EventClearMode.ManualClear, interProcess: true).AbortOnFailure(); + Os.SignalSystemEvent(ref _completionEvent); // TODO: Figure out where we are supposed to signal this. + } + + [CmifCommand(0)] + public Result GetCompletionEvent([CopyHandle] out int completionEventHandle) + { + completionEventHandle = Os.GetReadableHandleOfSystemEvent(ref _completionEvent); + + return Result.Success; + } + + [CmifCommand(1)] + public Result Cancel() + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend); + + return Result.Success; + } + + [CmifCommand(10100)] + public Result GetFriendListIds( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer)] Span friendIds, + Uid userId, + int offset, + SizedFriendFilter filter, + ulong pidPlaceholder, + [ClientProcessId] ulong pid) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, offset, filter, pidPlaceholder, pid }); + + if (userId.IsNull) + { + return FriendResult.InvalidArgument; + } + + return Result.Success; + } + + [CmifCommand(10101)] + public Result GetFriendList( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span friendList, + Uid userId, + int offset, + SizedFriendFilter filter, + ulong pidPlaceholder, + [ClientProcessId] ulong pid) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, offset, filter, pidPlaceholder, pid }); + + if (userId.IsNull) + { + return FriendResult.InvalidArgument; + } + + return Result.Success; + } + + [CmifCommand(10102)] + public Result UpdateFriendInfo( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span info, + Uid userId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan friendIds, + ulong pidPlaceholder, + [ClientProcessId] ulong pid) + { + string friendIdList = string.Join(", ", friendIds.ToArray()); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendIdList, pidPlaceholder, pid }); + + return Result.Success; + } + + [CmifCommand(10110)] + public Result GetFriendProfileImage( + out int size, + Uid userId, + NetworkServiceAccountId friendId, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span profileImage) + { + size = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(10120)] + public Result CheckFriendListAvailability(out bool listAvailable, Uid userId) + { + listAvailable = true; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(10121)] + public Result EnsureFriendListAvailable(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(10200)] + public Result SendFriendRequestForApplication( + Uid userId, + NetworkServiceAccountId friendId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg2, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg3, + ulong pidPlaceholder, + [ClientProcessId] ulong pid) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, arg3, pidPlaceholder, pid }); + + return Result.Success; + } + + [CmifCommand(10211)] + public Result AddFacedFriendRequestForApplication( + Uid userId, + FacedFriendRequestRegistrationKey key, + Nickname nickname, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan arg3, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg4, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg5, + ulong pidPlaceholder, + [ClientProcessId] ulong pid) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, key, nickname, arg4, arg5, pidPlaceholder, pid }); + + return Result.Success; + } + + [CmifCommand(10400)] + public Result GetBlockedUserListIds( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer)] Span blockedIds, + Uid userId, + int offset) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, offset }); + + return Result.Success; + } + + [CmifCommand(10420)] + public Result CheckBlockedUserListAvailability(out bool listAvailable, Uid userId) + { + listAvailable = true; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(10421)] + public Result EnsureBlockedUserListAvailable(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(10500)] + public Result GetProfileList( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span profileList, + Uid userId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan friendIds) + { + string friendIdList = string.Join(", ", friendIds.ToArray()); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendIdList }); + + return Result.Success; + } + + [CmifCommand(10600)] + public Result DeclareOpenOnlinePlaySession(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + if (userId.IsNull) + { + return FriendResult.InvalidArgument; + } + + _accountManager.OpenUserOnlinePlay(userId); + + return Result.Success; + } + + [CmifCommand(10601)] + public Result DeclareCloseOnlinePlaySession(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + if (userId.IsNull) + { + return FriendResult.InvalidArgument; + } + + _accountManager.CloseUserOnlinePlay(userId); + + return Result.Success; + } + + [CmifCommand(10610)] + public Result UpdateUserPresence( + Uid userId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0xE0)] in UserPresenceImpl userPresence, + ulong pidPlaceholder, + [ClientProcessId] ulong pid) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, userPresence, pidPlaceholder, pid }); + + return Result.Success; + } + + [CmifCommand(10700)] + public Result GetPlayHistoryRegistrationKey( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x40)] out PlayHistoryRegistrationKey registrationKey, + Uid userId, + bool arg2) + { + if (userId.IsNull) + { + registrationKey = default; + + return FriendResult.InvalidArgument; + } + + // NOTE: Calls nn::friends::detail::service::core::PlayHistoryManager::GetInstance and stores the instance. + + // NOTE: Calls nn::friends::detail::service::core::UuidManager::GetInstance and stores the instance. + // Then calls nn::friends::detail::service::core::AccountStorageManager::GetInstance and stores the instance. + // Then it checks if an Uuid is already stored for the UserId, if not it generates a random Uuid, + // and stores it in the savedata 8000000000000080 in the friends:/uid.bin file. + + /* + + NOTE: The service uses the KeyIndex to get a random key from a keys buffer (since the key index is stored in the returned buffer). + We currently don't support play history and online services so we can use a blank key for now. + Code for reference: + + byte[] hmacKey = new byte[0x20]; + + HMACSHA256 hmacSha256 = new HMACSHA256(hmacKey); + byte[] hmacHash = hmacSha256.ComputeHash(playHistoryRegistrationKeyBuffer); + + */ + + Uid randomGuid = new(); + + Guid.NewGuid().TryWriteBytes(MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref randomGuid, 1))); + + registrationKey = new() + { + Type = 0x101, + KeyIndex = (byte)(Random.Shared.Next() & 7), + UserIdBool = 0, // TODO: Find it. + UnknownBool = (byte)(arg2 ? 1 : 0), // TODO: Find it. + Reserved = new(), + Uuid = randomGuid, + HmacHash = new(), + }; + + return Result.Success; + } + + [CmifCommand(10701)] + public Result GetPlayHistoryRegistrationKeyWithNetworkServiceAccountId( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x40)] out PlayHistoryRegistrationKey registrationKey, + NetworkServiceAccountId friendId, + bool arg2) + { + registrationKey = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { friendId, arg2 }); + + return Result.Success; + } + + [CmifCommand(10702)] + public Result AddPlayHistory( + Uid userId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x40)] in PlayHistoryRegistrationKey registrationKey, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg2, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg3, + ulong pidPlaceholder, + [ClientProcessId] ulong pid) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, registrationKey, arg2, arg3, pidPlaceholder, pid }); + + return Result.Success; + } + + [CmifCommand(11000)] + public Result GetProfileImageUrl(out Url imageUrl, Url url, int arg2) + { + imageUrl = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { url, arg2 }); + + return Result.Success; + } + + [CmifCommand(20100)] + public Result GetFriendCount(out int count, Uid userId, SizedFriendFilter filter, ulong pidPlaceholder, [ClientProcessId] ulong pid) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, filter, pidPlaceholder, pid }); + + return Result.Success; + } + + [CmifCommand(20101)] + public Result GetNewlyFriendCount(out int count, Uid userId) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20102)] + public Result GetFriendDetailedInfo( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x800)] out FriendDetailedInfoImpl detailedInfo, + Uid userId, + NetworkServiceAccountId friendId) + { + detailedInfo = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(20103)] + public Result SyncFriendList(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20104)] + public Result RequestSyncFriendList(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20110)] + public Result LoadFriendSetting( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x40)] out FriendSettingImpl friendSetting, + Uid userId, + NetworkServiceAccountId friendId) + { + friendSetting = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(20200)] + public Result GetReceivedFriendRequestCount(out int count, out int count2, Uid userId) + { + count = 0; + count2 = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20201)] + public Result GetFriendRequestList( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span requestList, + Uid userId, + int arg3, + int arg4) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3, arg4 }); + + return Result.Success; + } + + [CmifCommand(20300)] + public Result GetFriendCandidateList( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span candidateList, + Uid userId, + int arg3) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3 }); + + return Result.Success; + } + + [CmifCommand(20301)] + public Result GetNintendoNetworkIdInfo( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x38)] out NintendoNetworkIdUserInfo networkIdInfo, + out int arg1, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span friendInfo, + Uid userId, + int arg4) + { + networkIdInfo = default; + arg1 = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg4 }); + + return Result.Success; + } + + [CmifCommand(20302)] + public Result GetSnsAccountLinkage(out SnsAccountLinkage accountLinkage, Uid userId) + { + accountLinkage = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20303)] + public Result GetSnsAccountProfile( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x380)] out SnsAccountProfile accountProfile, + Uid userId, + NetworkServiceAccountId friendId, + int arg3) + { + accountProfile = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg3 }); + + return Result.Success; + } + + [CmifCommand(20304)] + public Result GetSnsAccountFriendList( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span friendList, + Uid userId, + int arg3) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3 }); + + return Result.Success; + } + + [CmifCommand(20400)] + public Result GetBlockedUserList( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span blockedUsers, + Uid userId, + int arg3) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3 }); + + return Result.Success; + } + + [CmifCommand(20401)] + public Result SyncBlockedUserList(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20500)] + public Result GetProfileExtraList( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span extraList, + Uid userId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan friendIds) + { + string friendIdList = string.Join(", ", friendIds.ToArray()); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendIdList }); + + return Result.Success; + } + + [CmifCommand(20501)] + public Result GetRelationship(out Relationship relationship, Uid userId, NetworkServiceAccountId friendId) + { + relationship = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(20600)] + public Result GetUserPresenceView([Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0xE0)] out UserPresenceViewImpl userPresenceView, Uid userId) + { + userPresenceView = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20700)] + public Result GetPlayHistoryList(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span playHistoryList, Uid userId, int arg3) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3 }); + + return Result.Success; + } + + [CmifCommand(20701)] + public Result GetPlayHistoryStatistics(out PlayHistoryStatistics statistics, Uid userId) + { + statistics = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20800)] + public Result LoadUserSetting([Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x800)] out UserSettingImpl userSetting, Uid userId) + { + userSetting = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20801)] + public Result SyncUserSetting(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(20900)] + public Result RequestListSummaryOverlayNotification() + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend); + + return Result.Success; + } + + [CmifCommand(21000)] + public Result GetExternalApplicationCatalog( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x4B8)] out ExternalApplicationCatalog catalog, + ExternalApplicationCatalogId catalogId, + LanguageCode language) + { + catalog = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { catalogId, language }); + + return Result.Success; + } + + [CmifCommand(22000)] + public Result GetReceivedFriendInvitationList( + out int count, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span invitationList, + Uid userId) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(22001)] + public Result GetReceivedFriendInvitationDetailedInfo( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias, 0x1400)] out FriendInvitationGroupImpl invicationGroup, + Uid userId, + FriendInvitationGroupId groupId) + { + invicationGroup = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, groupId }); + + return Result.Success; + } + + [CmifCommand(22010)] + public Result GetReceivedFriendInvitationCountCache(out int count, Uid userId) + { + count = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(30100)] + public Result DropFriendNewlyFlags(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(30101)] + public Result DeleteFriend(Uid userId, NetworkServiceAccountId friendId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(30110)] + public Result DropFriendNewlyFlag(Uid userId, NetworkServiceAccountId friendId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(30120)] + public Result ChangeFriendFavoriteFlag(Uid userId, NetworkServiceAccountId friendId, bool favoriteFlag) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, favoriteFlag }); + + return Result.Success; + } + + [CmifCommand(30121)] + public Result ChangeFriendOnlineNotificationFlag(Uid userId, NetworkServiceAccountId friendId, bool onlineNotificationFlag) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, onlineNotificationFlag }); + + return Result.Success; + } + + [CmifCommand(30200)] + public Result SendFriendRequest(Uid userId, NetworkServiceAccountId friendId, int arg2) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2 }); + + return Result.Success; + } + + [CmifCommand(30201)] + public Result SendFriendRequestWithApplicationInfo( + Uid userId, + NetworkServiceAccountId friendId, + int arg2, + ApplicationInfo applicationInfo, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg4, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg5) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, applicationInfo, arg4, arg5 }); + + return Result.Success; + } + + [CmifCommand(30202)] + public Result CancelFriendRequest(Uid userId, RequestId requestId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, requestId }); + + return Result.Success; + } + + [CmifCommand(30203)] + public Result AcceptFriendRequest(Uid userId, RequestId requestId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, requestId }); + + return Result.Success; + } + + [CmifCommand(30204)] + public Result RejectFriendRequest(Uid userId, RequestId requestId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, requestId }); + + return Result.Success; + } + + [CmifCommand(30205)] + public Result ReadFriendRequest(Uid userId, RequestId requestId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, requestId }); + + return Result.Success; + } + + [CmifCommand(30210)] + public Result GetFacedFriendRequestRegistrationKey(out FacedFriendRequestRegistrationKey registrationKey, Uid userId) + { + registrationKey = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(30211)] + public Result AddFacedFriendRequest( + Uid userId, + FacedFriendRequestRegistrationKey registrationKey, + Nickname nickname, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan arg3) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, registrationKey, nickname }); + + return Result.Success; + } + + [CmifCommand(30212)] + public Result CancelFacedFriendRequest(Uid userId, NetworkServiceAccountId friendId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(30213)] + public Result GetFacedFriendRequestProfileImage( + out int size, + Uid userId, + NetworkServiceAccountId friendId, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span profileImage) + { + size = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(30214)] + public Result GetFacedFriendRequestProfileImageFromPath( + out int size, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan path, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span profileImage) + { + size = 0; + + string pathString = Encoding.UTF8.GetString(path); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { pathString }); + + return Result.Success; + } + + [CmifCommand(30215)] + public Result SendFriendRequestWithExternalApplicationCatalogId( + Uid userId, + NetworkServiceAccountId friendId, + int arg2, + ExternalApplicationCatalogId catalogId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg4, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg5) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, catalogId, arg4, arg5 }); + + return Result.Success; + } + + [CmifCommand(30216)] + public Result ResendFacedFriendRequest(Uid userId, NetworkServiceAccountId friendId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(30217)] + public Result SendFriendRequestWithNintendoNetworkIdInfo( + Uid userId, + NetworkServiceAccountId friendId, + int arg2, + MiiName arg3, + MiiImageUrlParam arg4, + MiiName arg5, + MiiImageUrlParam arg6) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, arg3, arg4, arg5, arg6 }); + + return Result.Success; + } + + [CmifCommand(30300)] + public Result GetSnsAccountLinkPageUrl([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias, 0x1000)] out WebPageUrl url, Uid userId, int arg2) + { + url = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg2 }); + + return Result.Success; + } + + [CmifCommand(30301)] + public Result UnlinkSnsAccount(Uid userId, int arg1) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg1 }); + + return Result.Success; + } + + [CmifCommand(30400)] + public Result BlockUser(Uid userId, NetworkServiceAccountId friendId, int arg2) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2 }); + + return Result.Success; + } + + [CmifCommand(30401)] + public Result BlockUserWithApplicationInfo( + Uid userId, + NetworkServiceAccountId friendId, + int arg2, + ApplicationInfo applicationInfo, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg4) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, applicationInfo, arg4 }); + + return Result.Success; + } + + [CmifCommand(30402)] + public Result UnblockUser(Uid userId, NetworkServiceAccountId friendId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId }); + + return Result.Success; + } + + [CmifCommand(30500)] + public Result GetProfileExtraFromFriendCode( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x400)] out ProfileExtraImpl profileExtra, + Uid userId, + FriendCode friendCode) + { + profileExtra = default; + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendCode }); + + return Result.Success; + } + + [CmifCommand(30700)] + public Result DeletePlayHistory(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(30810)] + public Result ChangePresencePermission(Uid userId, int permission) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, permission }); + + return Result.Success; + } + + [CmifCommand(30811)] + public Result ChangeFriendRequestReception(Uid userId, bool reception) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, reception }); + + return Result.Success; + } + + [CmifCommand(30812)] + public Result ChangePlayLogPermission(Uid userId, int permission) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, permission }); + + return Result.Success; + } + + [CmifCommand(30820)] + public Result IssueFriendCode(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(30830)] + public Result ClearPlayLog(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(30900)] + public Result SendFriendInvitation( + Uid userId, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan friendIds, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias, 0xC00)] in FriendInvitationGameModeDescription description, + ApplicationInfo applicationInfo, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan arg4, + bool arg5) + { + string friendIdList = string.Join(", ", friendIds.ToArray()); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendIdList, description, applicationInfo, arg5 }); + + return Result.Success; + } + + [CmifCommand(30910)] + public Result ReadFriendInvitation(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan invitationIds) + { + string invitationIdList = string.Join(", ", invitationIds.ToArray()); + + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, invitationIdList }); + + return Result.Success; + } + + [CmifCommand(30911)] + public Result ReadAllFriendInvitations(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(40100)] + public Result DeleteFriendListCache(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(40400)] + public Result DeleteBlockedUserListCache(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + [CmifCommand(49900)] + public Result DeleteNetworkServiceAccountCache(Uid userId) + { + Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId }); + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Os.DestroySystemEvent(ref _completionEvent); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendsServicePermissionLevel.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendsServicePermissionLevel.cs new file mode 100644 index 00000000..f4bbe100 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendsServicePermissionLevel.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + enum FriendsServicePermissionLevel + { + UserMask = 1, + ViewerMask = 2, + ManagerMask = 4, + SystemMask = 8, + + Admin = -1, + User = UserMask, + Viewer = UserMask | ViewerMask, + Manager = UserMask | ViewerMask | ManagerMask, + System = UserMask | SystemMask, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IDaemonSuspendSessionService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IDaemonSuspendSessionService.cs new file mode 100644 index 00000000..2bb0434e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IDaemonSuspendSessionService.cs @@ -0,0 +1,9 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + interface IDaemonSuspendSessionService : IServiceObject + { + // NOTE: This service has no commands. + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IFriendService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IFriendService.cs new file mode 100644 index 00000000..c19d0b78 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IFriendService.cs @@ -0,0 +1,97 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.Settings; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + interface IFriendService : IServiceObject + { + Result GetCompletionEvent(out int completionEventHandle); + Result Cancel(); + Result GetFriendListIds(out int count, Span friendIds, Uid userId, int offset, SizedFriendFilter filter, ulong pidPlaceholder, ulong pid); + Result GetFriendList(out int count, Span friendList, Uid userId, int offset, SizedFriendFilter filter, ulong pidPlaceholder, ulong pid); + Result UpdateFriendInfo(Span info, Uid userId, ReadOnlySpan friendIds, ulong pidPlaceholder, ulong pid); + Result GetFriendProfileImage(out int size, Uid userId, NetworkServiceAccountId friendId, Span profileImage); + Result CheckFriendListAvailability(out bool listAvailable, Uid userId); + Result EnsureFriendListAvailable(Uid userId); + Result SendFriendRequestForApplication(Uid userId, NetworkServiceAccountId friendId, in InAppScreenName arg2, in InAppScreenName arg3, ulong pidPlaceholder, ulong pid); + Result AddFacedFriendRequestForApplication(Uid userId, FacedFriendRequestRegistrationKey key, Nickname nickname, ReadOnlySpan arg3, in InAppScreenName arg4, in InAppScreenName arg5, ulong pidPlaceholder, ulong pid); + Result GetBlockedUserListIds(out int count, Span blockedIds, Uid userId, int offset); + Result CheckBlockedUserListAvailability(out bool listAvailable, Uid userId); + Result EnsureBlockedUserListAvailable(Uid userId); + Result GetProfileList(Span profileList, Uid userId, ReadOnlySpan friendIds); + Result DeclareOpenOnlinePlaySession(Uid userId); + Result DeclareCloseOnlinePlaySession(Uid userId); + Result UpdateUserPresence(Uid userId, in UserPresenceImpl userPresence, ulong pidPlaceholder, ulong pid); + Result GetPlayHistoryRegistrationKey(out PlayHistoryRegistrationKey registrationKey, Uid userId, bool arg2); + Result GetPlayHistoryRegistrationKeyWithNetworkServiceAccountId(out PlayHistoryRegistrationKey registrationKey, NetworkServiceAccountId friendId, bool arg2); + Result AddPlayHistory(Uid userId, in PlayHistoryRegistrationKey registrationKey, in InAppScreenName arg2, in InAppScreenName arg3, ulong pidPlaceholder, ulong pid); + Result GetProfileImageUrl(out Url imageUrl, Url url, int arg2); + Result GetFriendCount(out int count, Uid userId, SizedFriendFilter filter, ulong pidPlaceholder, ulong pid); + Result GetNewlyFriendCount(out int count, Uid userId); + Result GetFriendDetailedInfo(out FriendDetailedInfoImpl detailedInfo, Uid userId, NetworkServiceAccountId friendId); + Result SyncFriendList(Uid userId); + Result RequestSyncFriendList(Uid userId); + Result LoadFriendSetting(out FriendSettingImpl friendSetting, Uid userId, NetworkServiceAccountId friendId); + Result GetReceivedFriendRequestCount(out int count, out int count2, Uid userId); + Result GetFriendRequestList(out int count, Span requestList, Uid userId, int arg3, int arg4); + Result GetFriendCandidateList(out int count, Span candidateList, Uid userId, int arg3); + Result GetNintendoNetworkIdInfo(out NintendoNetworkIdUserInfo networkIdInfo, out int arg1, Span friendInfo, Uid userId, int arg4); + Result GetSnsAccountLinkage(out SnsAccountLinkage accountLinkage, Uid userId); + Result GetSnsAccountProfile(out SnsAccountProfile accountProfile, Uid userId, NetworkServiceAccountId friendId, int arg3); + Result GetSnsAccountFriendList(out int count, Span friendList, Uid userId, int arg3); + Result GetBlockedUserList(out int count, Span blockedUsers, Uid userId, int arg3); + Result SyncBlockedUserList(Uid userId); + Result GetProfileExtraList(Span extraList, Uid userId, ReadOnlySpan friendIds); + Result GetRelationship(out Relationship relationship, Uid userId, NetworkServiceAccountId friendId); + Result GetUserPresenceView(out UserPresenceViewImpl userPresenceView, Uid userId); + Result GetPlayHistoryList(out int count, Span playHistoryList, Uid userId, int arg3); + Result GetPlayHistoryStatistics(out PlayHistoryStatistics statistics, Uid userId); + Result LoadUserSetting(out UserSettingImpl userSetting, Uid userId); + Result SyncUserSetting(Uid userId); + Result RequestListSummaryOverlayNotification(); + Result GetExternalApplicationCatalog(out ExternalApplicationCatalog catalog, ExternalApplicationCatalogId catalogId, LanguageCode language); + Result GetReceivedFriendInvitationList(out int count, Span invitationList, Uid userId); + Result GetReceivedFriendInvitationDetailedInfo(out FriendInvitationGroupImpl invicationGroup, Uid userId, FriendInvitationGroupId groupId); + Result GetReceivedFriendInvitationCountCache(out int count, Uid userId); + Result DropFriendNewlyFlags(Uid userId); + Result DeleteFriend(Uid userId, NetworkServiceAccountId friendId); + Result DropFriendNewlyFlag(Uid userId, NetworkServiceAccountId friendId); + Result ChangeFriendFavoriteFlag(Uid userId, NetworkServiceAccountId friendId, bool favoriteFlag); + Result ChangeFriendOnlineNotificationFlag(Uid userId, NetworkServiceAccountId friendId, bool onlineNotificationFlag); + Result SendFriendRequest(Uid userId, NetworkServiceAccountId friendId, int arg2); + Result SendFriendRequestWithApplicationInfo(Uid userId, NetworkServiceAccountId friendId, int arg2, ApplicationInfo applicationInfo, in InAppScreenName arg4, in InAppScreenName arg5); + Result CancelFriendRequest(Uid userId, RequestId requestId); + Result AcceptFriendRequest(Uid userId, RequestId requestId); + Result RejectFriendRequest(Uid userId, RequestId requestId); + Result ReadFriendRequest(Uid userId, RequestId requestId); + Result GetFacedFriendRequestRegistrationKey(out FacedFriendRequestRegistrationKey registrationKey, Uid userId); + Result AddFacedFriendRequest(Uid userId, FacedFriendRequestRegistrationKey registrationKey, Nickname nickname, ReadOnlySpan arg3); + Result CancelFacedFriendRequest(Uid userId, NetworkServiceAccountId friendId); + Result GetFacedFriendRequestProfileImage(out int size, Uid userId, NetworkServiceAccountId friendId, Span profileImage); + Result GetFacedFriendRequestProfileImageFromPath(out int size, ReadOnlySpan path, Span profileImage); + Result SendFriendRequestWithExternalApplicationCatalogId(Uid userId, NetworkServiceAccountId friendId, int arg2, ExternalApplicationCatalogId catalogId, in InAppScreenName arg4, in InAppScreenName arg5); + Result ResendFacedFriendRequest(Uid userId, NetworkServiceAccountId friendId); + Result SendFriendRequestWithNintendoNetworkIdInfo(Uid userId, NetworkServiceAccountId friendId, int arg2, MiiName arg3, MiiImageUrlParam arg4, MiiName arg5, MiiImageUrlParam arg6); + Result GetSnsAccountLinkPageUrl(out WebPageUrl url, Uid userId, int arg2); + Result UnlinkSnsAccount(Uid userId, int arg1); + Result BlockUser(Uid userId, NetworkServiceAccountId friendId, int arg2); + Result BlockUserWithApplicationInfo(Uid userId, NetworkServiceAccountId friendId, int arg2, ApplicationInfo applicationInfo, in InAppScreenName arg4); + Result UnblockUser(Uid userId, NetworkServiceAccountId friendId); + Result GetProfileExtraFromFriendCode(out ProfileExtraImpl profileExtra, Uid userId, FriendCode friendCode); + Result DeletePlayHistory(Uid userId); + Result ChangePresencePermission(Uid userId, int permission); + Result ChangeFriendRequestReception(Uid userId, bool reception); + Result ChangePlayLogPermission(Uid userId, int permission); + Result IssueFriendCode(Uid userId); + Result ClearPlayLog(Uid userId); + Result SendFriendInvitation(Uid userId, ReadOnlySpan friendIds, in FriendInvitationGameModeDescription description, ApplicationInfo applicationInfo, ReadOnlySpan arg4, bool arg5); + Result ReadFriendInvitation(Uid userId, ReadOnlySpan invitationIds); + Result ReadAllFriendInvitations(Uid userId); + Result DeleteFriendListCache(Uid userId); + Result DeleteBlockedUserListCache(Uid userId); + Result DeleteNetworkServiceAccountCache(Uid userId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/INotificationService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/INotificationService.cs new file mode 100644 index 00000000..a3a28e8c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/INotificationService.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + interface INotificationService : IServiceObject + { + Result GetEvent(out int eventHandle); + Result Clear(); + Result Pop(out SizedNotificationInfo sizedNotificationInfo); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IServiceCreator.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IServiceCreator.cs new file mode 100644 index 00000000..58e2569b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IServiceCreator.cs @@ -0,0 +1,13 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + interface IServiceCreator : IServiceObject + { + Result CreateFriendService(out IFriendService friendService); + Result CreateNotificationService(out INotificationService notificationService, Uid userId); + Result CreateDaemonSuspendSessionService(out IDaemonSuspendSessionService daemonSuspendSessionService); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventHandler.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventHandler.cs new file mode 100644 index 00000000..61c692a6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventHandler.cs @@ -0,0 +1,58 @@ +using Ryujinx.Horizon.Sdk.Account; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + sealed class NotificationEventHandler + { + private readonly NotificationService[] _registry; + + public NotificationEventHandler() + { + _registry = new NotificationService[0x20]; + } + + public void RegisterNotificationService(NotificationService service) + { + // NOTE: When there's no enough space in the registry array, Nintendo doesn't return any errors. + for (int i = 0; i < _registry.Length; i++) + { + if (_registry[i] == null) + { + _registry[i] = service; + break; + } + } + } + + public void UnregisterNotificationService(NotificationService service) + { + // NOTE: When there's no enough space in the registry array, Nintendo doesn't return any errors. + for (int i = 0; i < _registry.Length; i++) + { + if (_registry[i] == service) + { + _registry[i] = null; + break; + } + } + } + + // TODO: Use this when we have enough things to go online. + public void SignalFriendListUpdate(Uid targetId) + { + for (int i = 0; i < _registry.Length; i++) + { + _registry[i]?.SignalFriendListUpdate(targetId); + } + } + + // TODO: Use this when we have enough things to go online. + public void SignalNewFriendRequest(Uid targetId) + { + for (int i = 0; i < _registry.Length; i++) + { + _registry[i]?.SignalNewFriendRequest(targetId); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventType.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventType.cs new file mode 100644 index 00000000..e46fc9b7 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + enum NotificationEventType : uint + { + Invalid = 0x0, + FriendListUpdate = 0x1, + NewFriendRequest = 0x65, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationService.cs new file mode 100644 index 00000000..534bf63e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationService.cs @@ -0,0 +1,172 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + partial class NotificationService : INotificationService, IDisposable + { + private readonly NotificationEventHandler _notificationEventHandler; + private readonly Uid _userId; + private readonly FriendsServicePermissionLevel _permissionLevel; + + private readonly object _lock = new(); + + private SystemEventType _notificationEvent; + + private readonly LinkedList _notifications; + + private bool _hasNewFriendRequest; + private bool _hasFriendListUpdate; + + public NotificationService(NotificationEventHandler notificationEventHandler, Uid userId, FriendsServicePermissionLevel permissionLevel) + { + _notificationEventHandler = notificationEventHandler; + _userId = userId; + _permissionLevel = permissionLevel; + _notifications = new LinkedList(); + Os.CreateSystemEvent(out _notificationEvent, EventClearMode.AutoClear, interProcess: true).AbortOnFailure(); + + _hasNewFriendRequest = false; + _hasFriendListUpdate = false; + + notificationEventHandler.RegisterNotificationService(this); + } + + [CmifCommand(0)] + public Result GetEvent([CopyHandle] out int eventHandle) + { + eventHandle = Os.GetReadableHandleOfSystemEvent(ref _notificationEvent); + + return Result.Success; + } + + [CmifCommand(1)] + public Result Clear() + { + lock (_lock) + { + _hasNewFriendRequest = false; + _hasFriendListUpdate = false; + + _notifications.Clear(); + } + + return Result.Success; + } + + [CmifCommand(2)] + public Result Pop(out SizedNotificationInfo sizedNotificationInfo) + { + lock (_lock) + { + if (_notifications.Count >= 1) + { + sizedNotificationInfo = _notifications.First.Value; + _notifications.RemoveFirst(); + + if (sizedNotificationInfo.Type == NotificationEventType.FriendListUpdate) + { + _hasFriendListUpdate = false; + } + else if (sizedNotificationInfo.Type == NotificationEventType.NewFriendRequest) + { + _hasNewFriendRequest = false; + } + + return Result.Success; + } + } + + sizedNotificationInfo = default; + + return FriendResult.NotificationQueueEmpty; + } + + public void SignalFriendListUpdate(Uid targetId) + { + lock (_lock) + { + if (_userId == targetId) + { + if (!_hasFriendListUpdate) + { + SizedNotificationInfo friendListNotification = new(); + + if (_notifications.Count != 0) + { + friendListNotification = _notifications.First.Value; + _notifications.RemoveFirst(); + } + + friendListNotification.Type = NotificationEventType.FriendListUpdate; + _hasFriendListUpdate = true; + + if (_hasNewFriendRequest) + { + SizedNotificationInfo newFriendRequestNotification = new(); + + if (_notifications.Count != 0) + { + newFriendRequestNotification = _notifications.First.Value; + _notifications.RemoveFirst(); + } + + newFriendRequestNotification.Type = NotificationEventType.NewFriendRequest; + _notifications.AddFirst(newFriendRequestNotification); + } + + // We defer this to make sure we are on top of the queue. + _notifications.AddFirst(friendListNotification); + } + + Os.SignalSystemEvent(ref _notificationEvent); + } + } + } + + public void SignalNewFriendRequest(Uid targetId) + { + lock (_lock) + { + if (_permissionLevel.HasFlag(FriendsServicePermissionLevel.ViewerMask) && _userId == targetId) + { + if (!_hasNewFriendRequest) + { + if (_notifications.Count == 100) + { + SignalFriendListUpdate(targetId); + } + + SizedNotificationInfo newFriendRequestNotification = new() + { + Type = NotificationEventType.NewFriendRequest, + }; + + _notifications.AddLast(newFriendRequestNotification); + _hasNewFriendRequest = true; + } + + Os.SignalSystemEvent(ref _notificationEvent); + } + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _notificationEventHandler.UnregisterNotificationService(this); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/PresenceStatusFilter.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/PresenceStatusFilter.cs new file mode 100644 index 00000000..3ea10587 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/PresenceStatusFilter.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + enum PresenceStatusFilter : uint + { + None, + Online, + OnlinePlay, + OnlineOrOnlinePlay, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/ServiceCreator.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/ServiceCreator.cs new file mode 100644 index 00000000..1be804df --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/ServiceCreator.cs @@ -0,0 +1,51 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + partial class ServiceCreator : IServiceCreator + { + private readonly IEmulatorAccountManager _accountManager; + private readonly NotificationEventHandler _notificationEventHandler; + private readonly FriendsServicePermissionLevel _permissionLevel; + + public ServiceCreator(IEmulatorAccountManager accountManager, NotificationEventHandler notificationEventHandler, FriendsServicePermissionLevel permissionLevel) + { + _accountManager = accountManager; + _notificationEventHandler = notificationEventHandler; + _permissionLevel = permissionLevel; + } + + [CmifCommand(0)] + public Result CreateFriendService(out IFriendService friendService) + { + friendService = new FriendService(_accountManager, _permissionLevel); + + return Result.Success; + } + + [CmifCommand(1)] // 2.0.0+ + public Result CreateNotificationService(out INotificationService notificationService, Uid userId) + { + if (userId.IsNull) + { + notificationService = null; + + return FriendResult.InvalidArgument; + } + + notificationService = new NotificationService(_notificationEventHandler, userId, _permissionLevel); + + return Result.Success; + } + + [CmifCommand(2)] // 4.0.0+ + public Result CreateDaemonSuspendSessionService(out IDaemonSuspendSessionService daemonSuspendSessionService) + { + daemonSuspendSessionService = new DaemonSuspendSessionService(); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedFriendFilter.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedFriendFilter.cs new file mode 100644 index 00000000..d93a2ae2 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedFriendFilter.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + struct SizedFriendFilter + { + public PresenceStatusFilter PresenceStatus; + public bool IsFavoriteOnly; + public bool IsSameAppPresenceOnly; + public bool IsSameAppPlayedOnly; + public bool IsArbitraryAppPlayedOnly; + public ulong PresenceGroupId; + + public readonly override string ToString() + { + return $"{{ PresenceStatus: {PresenceStatus}, " + + $"IsFavoriteOnly: {IsFavoriteOnly}, " + + $"IsSameAppPresenceOnly: {IsSameAppPresenceOnly}, " + + $"IsSameAppPlayedOnly: {IsSameAppPlayedOnly}, " + + $"IsArbitraryAppPlayedOnly: {IsArbitraryAppPlayedOnly}, " + + $"PresenceGroupId: {PresenceGroupId} }}"; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedNotificationInfo.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedNotificationInfo.cs new file mode 100644 index 00000000..0da26a1a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedNotificationInfo.cs @@ -0,0 +1,13 @@ +using Ryujinx.Horizon.Sdk.Account; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + struct SizedNotificationInfo + { + public NotificationEventType Type; + public uint Padding; + public NetworkServiceAccountId NetworkUserIdPlaceholder; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/NintendoNetworkIdFriendImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/NintendoNetworkIdFriendImpl.cs new file mode 100644 index 00000000..66d61e4c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/NintendoNetworkIdFriendImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct NintendoNetworkIdFriendImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/PlayHistoryImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/PlayHistoryImpl.cs new file mode 100644 index 00000000..9f90f0c8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/PlayHistoryImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct PlayHistoryImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/PresenceStatus.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/PresenceStatus.cs new file mode 100644 index 00000000..5ddbe14e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/PresenceStatus.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + enum PresenceStatus : uint + { + Offline, + Online, + OnlinePlay, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileExtraImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileExtraImpl.cs new file mode 100644 index 00000000..1548d725 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileExtraImpl.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x400)] + struct ProfileExtraImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileImpl.cs new file mode 100644 index 00000000..f779d93c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct ProfileImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/SnsAccountFriendImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/SnsAccountFriendImpl.cs new file mode 100644 index 00000000..dc6adf03 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/SnsAccountFriendImpl.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + struct SnsAccountFriendImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceImpl.cs new file mode 100644 index 00000000..cf4520cf --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceImpl.cs @@ -0,0 +1,29 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Sdk.Account; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0xE0)] + struct UserPresenceImpl + { + public Uid UserId; + public long LastTimeOnlineTimestamp; + public PresenceStatus Status; + public bool SamePresenceGroupApplication; + public Array3 Unknown; + public AppKeyValueStorageHolder AppKeyValueStorage; + + [InlineArray(0xC0)] + public struct AppKeyValueStorageHolder + { + public byte Value; + } + + public readonly override string ToString() + { + return $"{{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status} }}"; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceViewImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceViewImpl.cs new file mode 100644 index 00000000..04c09260 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceViewImpl.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0xE0)] + struct UserPresenceViewImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserSettingImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserSettingImpl.cs new file mode 100644 index 00000000..9d057fb1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserSettingImpl.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x800)] + struct UserSettingImpl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalog.cs b/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalog.cs new file mode 100644 index 00000000..0d9c157d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalog.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x4B8)] + struct ExternalApplicationCatalog + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalogId.cs b/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalogId.cs new file mode 100644 index 00000000..7ed36cd9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalogId.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + struct ExternalApplicationCatalogId + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FacedFriendRequestRegistrationKey.cs b/src/Ryujinx.Horizon/Sdk/Friends/FacedFriendRequestRegistrationKey.cs new file mode 100644 index 00000000..6b5812f6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/FacedFriendRequestRegistrationKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40, Pack = 0x1)] + struct FacedFriendRequestRegistrationKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs new file mode 100644 index 00000000..d78497a1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 0x1)] + struct FriendCode + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGameModeDescription.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGameModeDescription.cs new file mode 100644 index 00000000..29b4a097 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGameModeDescription.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0xC00)] + struct FriendInvitationGameModeDescription + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGroupId.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGroupId.cs new file mode 100644 index 00000000..ef53882b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGroupId.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)] + struct FriendInvitationGroupId + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationId.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationId.cs new file mode 100644 index 00000000..7be19d57 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationId.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + struct FriendInvitationId + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendResult.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendResult.cs new file mode 100644 index 00000000..5965d508 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendResult.cs @@ -0,0 +1,13 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + static class FriendResult + { + private const int ModuleId = 121; + + public static Result InvalidArgument => new(ModuleId, 2); + public static Result InternetRequestDenied => new(ModuleId, 6); + public static Result NotificationQueueEmpty => new(ModuleId, 15); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/InAppScreenName.cs b/src/Ryujinx.Horizon/Sdk/Friends/InAppScreenName.cs new file mode 100644 index 00000000..22574a5c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/InAppScreenName.cs @@ -0,0 +1,26 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Sdk.Settings; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x48)] + struct InAppScreenName + { + public Array64 Name; + public LanguageCode LanguageCode; + + public override readonly string ToString() + { + int length = Name.AsSpan().IndexOf((byte)0); + if (length < 0) + { + length = 64; + } + + return Encoding.UTF8.GetString(Name.AsSpan()[..length]); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/MiiImageUrlParam.cs b/src/Ryujinx.Horizon/Sdk/Friends/MiiImageUrlParam.cs new file mode 100644 index 00000000..8790bb93 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/MiiImageUrlParam.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x1)] + struct MiiImageUrlParam + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs b/src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs new file mode 100644 index 00000000..e73c0d83 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 0x1)] + struct MiiName + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/NintendoNetworkIdUserInfo.cs b/src/Ryujinx.Horizon/Sdk/Friends/NintendoNetworkIdUserInfo.cs new file mode 100644 index 00000000..a2a9e046 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/NintendoNetworkIdUserInfo.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x38)] + struct NintendoNetworkIdUserInfo + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryRegistrationKey.cs b/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryRegistrationKey.cs new file mode 100644 index 00000000..bb672a79 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryRegistrationKey.cs @@ -0,0 +1,18 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Sdk.Account; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40)] + struct PlayHistoryRegistrationKey + { + public ushort Type; + public byte KeyIndex; + public byte UserIdBool; + public byte UnknownBool; + public Array11 Reserved; + public Uid Uuid; + public Array32 HmacHash; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryStatistics.cs b/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryStatistics.cs new file mode 100644 index 00000000..ea3e3d99 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryStatistics.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + struct PlayHistoryStatistics + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Relationship.cs b/src/Ryujinx.Horizon/Sdk/Friends/Relationship.cs new file mode 100644 index 00000000..efba09a8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Relationship.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)] + struct Relationship + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs b/src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs new file mode 100644 index 00000000..3236a1d7 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)] + struct RequestId + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountLinkage.cs b/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountLinkage.cs new file mode 100644 index 00000000..b4660d9e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountLinkage.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)] + struct SnsAccountLinkage + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountProfile.cs b/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountProfile.cs new file mode 100644 index 00000000..d872b3da --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountProfile.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x380)] + struct SnsAccountProfile + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Url.cs b/src/Ryujinx.Horizon/Sdk/Friends/Url.cs new file mode 100644 index 00000000..833ee123 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/Url.cs @@ -0,0 +1,30 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 0x1)] + struct Url + { + public UrlStorage Path; + + [InlineArray(0xA0)] + public struct UrlStorage + { + public byte Value; + } + + public override readonly string ToString() + { + int length = ((ReadOnlySpan)Path).IndexOf((byte)0); + if (length < 0) + { + length = 33; + } + + return Encoding.UTF8.GetString(((ReadOnlySpan)Path)[..length]); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs b/src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs new file mode 100644 index 00000000..85488af6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Friends +{ + [StructLayout(LayoutKind.Sequential, Size = 0x1000)] + struct WebPageUrl + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Fs/FileHandle.cs b/src/Ryujinx.Horizon/Sdk/Fs/FileHandle.cs new file mode 100644 index 00000000..1993577d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Fs/FileHandle.cs @@ -0,0 +1,13 @@ + +namespace Ryujinx.Horizon.Sdk.Fs +{ + public readonly struct FileHandle + { + public object Value { get; } + + public FileHandle(object value) + { + Value = value; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Fs/FsResult.cs b/src/Ryujinx.Horizon/Sdk/Fs/FsResult.cs new file mode 100644 index 00000000..a4b70bd5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Fs/FsResult.cs @@ -0,0 +1,13 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Fs +{ + static class FsResult + { + private const int ModuleId = 2; + + public static Result PathNotFound => new(ModuleId, 1); + public static Result PathAlreadyExists => new(ModuleId, 2); + public static Result TargetNotFound => new(ModuleId, 1002); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Fs/IFsClient.cs b/src/Ryujinx.Horizon/Sdk/Fs/IFsClient.cs new file mode 100644 index 00000000..caf6b03e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Fs/IFsClient.cs @@ -0,0 +1,16 @@ +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.Horizon.Sdk.Fs +{ + public interface IFsClient + { + Result QueryMountSystemDataCacheSize(out long size, ulong dataId); + Result MountSystemData(string mountName, ulong dataId); + Result OpenFile(out FileHandle handle, string path, OpenMode openMode); + Result ReadFile(FileHandle handle, long offset, Span destination); + Result GetFileSize(out long size, FileHandle handle); + void CloseFile(FileHandle handle); + void Unmount(string mountName); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Fs/OpenMode.cs b/src/Ryujinx.Horizon/Sdk/Fs/OpenMode.cs new file mode 100644 index 00000000..add2ca48 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Fs/OpenMode.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Fs +{ + [Flags] + public enum OpenMode + { + Read = 1, + Write = 2, + AllowAppend = 4, + ReadWrite = 3, + All = 7, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Hshl/IManager.cs b/src/Ryujinx.Horizon/Sdk/Hshl/IManager.cs new file mode 100644 index 00000000..52695ce6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Hshl/IManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Hshl +{ + interface IManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Hshl/ISetterManager.cs b/src/Ryujinx.Horizon/Sdk/Hshl/ISetterManager.cs new file mode 100644 index 00000000..fc668b05 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Hshl/ISetterManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Hshl +{ + interface ISetterManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ins/IReceiverManager.cs b/src/Ryujinx.Horizon/Sdk/Ins/IReceiverManager.cs new file mode 100644 index 00000000..f9fb3a44 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ins/IReceiverManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Ins +{ + interface IReceiverManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ins/ISenderManager.cs b/src/Ryujinx.Horizon/Sdk/Ins/ISenderManager.cs new file mode 100644 index 00000000..03c22c6d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ins/ISenderManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Ins +{ + interface ISenderManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Lbl/ILblController.cs b/src/Ryujinx.Horizon/Sdk/Lbl/ILblController.cs new file mode 100644 index 00000000..19a8154e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Lbl/ILblController.cs @@ -0,0 +1,20 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Lbl +{ + interface ILblController : IServiceObject + { + Result SetBrightnessReflectionDelayLevel(float unknown0, float unknown1); + Result GetBrightnessReflectionDelayLevel(out float unknown1, float unknown0); + Result SetCurrentBrightnessMapping(float unknown0, float unknown1, float unknown2); + Result GetCurrentBrightnessMapping(out float unknown0, out float unknown1, out float unknown2); + Result SetCurrentAmbientLightSensorMapping(float unknown0, float unknown1, float unknown2); + Result GetCurrentAmbientLightSensorMapping(out float unknown0, out float unknown1, out float unknown2); + Result SetCurrentBrightnessSettingForVrMode(float currentBrightnessSettingForVrMode); + Result GetCurrentBrightnessSettingForVrMode(out float currentBrightnessSettingForVrMode); + Result EnableVrMode(); + Result DisableVrMode(); + Result IsVrModeEnabled(out bool vrModeEnabled); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Lbl/LblApi.cs b/src/Ryujinx.Horizon/Sdk/Lbl/LblApi.cs new file mode 100644 index 00000000..a5622d4a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Lbl/LblApi.cs @@ -0,0 +1,43 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sm; +using System; + +namespace Ryujinx.Horizon.Sdk.Lbl +{ + public class LblApi : IDisposable + { + private const string LblName = "lbl"; + + private int _sessionHandle; + + public LblApi() + { + using var smApi = new SmApi(); + + smApi.Initialize(); + smApi.GetServiceHandle(out _sessionHandle, ServiceName.Encode(LblName)).AbortOnFailure(); + } + + public Result EnableVrMode() + { + return ServiceUtil.SendRequest(out _, _sessionHandle, 26, sendPid: false, ReadOnlySpan.Empty); + } + + public Result DisableVrMode() + { + return ServiceUtil.SendRequest(out _, _sessionHandle, 27, sendPid: false, ReadOnlySpan.Empty); + } + + public void Dispose() + { + if (_sessionHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_sessionHandle); + + _sessionHandle = 0; + } + + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Lm/ILmLogger.cs b/src/Ryujinx.Horizon/Sdk/Lm/ILmLogger.cs new file mode 100644 index 00000000..2b32a7a0 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Lm/ILmLogger.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Lm +{ + interface ILmLogger : IServiceObject + { + Result Log(Span message); + Result SetDestination(LogDestination destination); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Lm/ILogService.cs b/src/Ryujinx.Horizon/Sdk/Lm/ILogService.cs new file mode 100644 index 00000000..ae29267a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Lm/ILogService.cs @@ -0,0 +1,11 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.LogManager.Ipc; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Lm +{ + interface ILogService : IServiceObject + { + Result OpenLogger(out LmLogger logger, ulong pid); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs b/src/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs new file mode 100644 index 00000000..6905db0c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.Horizon.Sdk.Lm +{ + enum LogDataChunkKey + { + Start = 0, + Stop = 1, + Message = 2, + Line = 3, + Filename = 4, + Function = 5, + Module = 6, + Thread = 7, + DropCount = 8, + Time = 9, + ProgramName = 10, + + Count, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs b/src/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs new file mode 100644 index 00000000..d9078629 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Lm +{ + [Flags] + enum LogDestination + { + TargetManager = 1 << 0, + Uart = 1 << 1, + UartIfSleep = 1 << 2, + + All = 0xffff, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs b/src/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs new file mode 100644 index 00000000..e7d5d664 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Lm +{ + [Flags] + enum LogPacketFlags : byte + { + IsHead = 1 << 0, + IsTail = 1 << 1, + IsLittleEndian = 1 << 2, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs b/src/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs new file mode 100644 index 00000000..022ba8da --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs @@ -0,0 +1,15 @@ +using Ryujinx.Horizon.Sdk.Diag; + +namespace Ryujinx.Horizon.Sdk.Lm +{ + struct LogPacketHeader + { + public ulong ProcessId; + public ulong ThreadId; + public LogPacketFlags Flags; + public byte Padding; + public LogSeverity Severity; + public byte Verbosity; + public uint PayloadSize; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/MmNv/IRequest.cs b/src/Ryujinx.Horizon/Sdk/MmNv/IRequest.cs new file mode 100644 index 00000000..1c2ce655 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/MmNv/IRequest.cs @@ -0,0 +1,17 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.MmNv +{ + interface IRequest : IServiceObject + { + Result InitializeOld(Module module, uint fgmPriority, uint autoClearEvent); + Result FinalizeOld(Module module); + Result SetAndWaitOld(Module module, uint clockRateMin, int clockRateMax); + Result GetOld(out uint clockRateActual, Module module); + Result Initialize(out uint requestId, Module module, uint fgmPriority, uint autoClearEvent); + Result Finalize(uint requestId); + Result SetAndWait(uint requestId, uint clockRateMin, int clockRateMax); + Result Get(out uint clockRateActual, uint requestId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/MmNv/Module.cs b/src/Ryujinx.Horizon/Sdk/MmNv/Module.cs new file mode 100644 index 00000000..a2379ee6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/MmNv/Module.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.Horizon.Sdk.MmNv +{ + enum Module : uint + { + Cpu, + Gpu, + Emc, + SysBus, + MSelect, + NvDec, + NvEnc, + NvJpg, + Test, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/MmNv/Session.cs b/src/Ryujinx.Horizon/Sdk/MmNv/Session.cs new file mode 100644 index 00000000..b67a9d22 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/MmNv/Session.cs @@ -0,0 +1,26 @@ +namespace Ryujinx.Horizon.Sdk.MmNv +{ + class Session + { + public Module Module { get; } + public uint Id { get; } + public bool IsAutoClearEvent { get; } + public uint ClockRateMin { get; private set; } + public int ClockRateMax { get; private set; } + + public Session(uint id, Module module, bool isAutoClearEvent) + { + Module = module; + Id = id; + IsAutoClearEvent = isAutoClearEvent; + ClockRateMin = 0; + ClockRateMax = -1; + } + + public void SetAndWait(uint clockRateMin, int clockRateMax) + { + ClockRateMin = clockRateMin; + ClockRateMax = clockRateMax; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs b/src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs new file mode 100644 index 00000000..24b7d9ca --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs @@ -0,0 +1,52 @@ +namespace Ryujinx.Horizon.Sdk.Ncm +{ + public readonly struct ApplicationId + { + public readonly ulong Id; + + public static int Length => sizeof(ulong); + + public static ApplicationId First => new(0x0100000000010000); + + public static ApplicationId Last => new(0x01FFFFFFFFFFFFFF); + + public static ApplicationId Invalid => new(0); + + public bool IsValid => Id >= First.Id && Id <= Last.Id; + + public ApplicationId(ulong id) + { + Id = id; + } + + public override bool Equals(object obj) + { + return obj is ApplicationId applicationId && applicationId.Equals(this); + } + + public bool Equals(ApplicationId other) + { + return other.Id == Id; + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + public static bool operator ==(ApplicationId lhs, ApplicationId rhs) + { + return lhs.Equals(rhs); + } + + public static bool operator !=(ApplicationId lhs, ApplicationId rhs) + { + return !lhs.Equals(rhs); + } + + public override string ToString() + { + return $"0x{Id:x}"; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ncm/StorageId.cs b/src/Ryujinx.Horizon/Sdk/Ncm/StorageId.cs new file mode 100644 index 00000000..e2fb3250 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ncm/StorageId.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Horizon.Sdk.Ncm +{ + public enum StorageId : byte + { + None, + Host, + GameCard, + BuiltInSystem, + BuiltInUser, + SdCard, + Any, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/AhoCorasick.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/AhoCorasick.cs new file mode 100644 index 00000000..6acb9be9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/AhoCorasick.cs @@ -0,0 +1,251 @@ +using System; +using System.Diagnostics; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + class AhoCorasick + { + public delegate bool MatchCallback(ReadOnlySpan text, int matchStartOffset, int matchEndOffset, int nodeId, ref MatchState state); + public delegate bool MatchCallback(ReadOnlySpan text, int matchStartOffset, int matchEndOffset, int nodeId, ref T state); + + private readonly SparseSet _wordMap = new(); + private readonly CompressedArray _wordLengths = new(); + private readonly SparseSet _multiWordMap = new(); + private readonly CompressedArray _multiWordIndices = new(); + private readonly SparseSet _nodeMap = new(); + private uint _nodesPerCharacter; + private readonly Bp _bp = new(); + + public bool Import(ref BinaryReader reader) + { + if (!_wordLengths.Import(ref reader) || + !_wordMap.Import(ref reader) || + !_multiWordIndices.Import(ref reader) || + !_multiWordMap.Import(ref reader)) + { + return false; + } + + if (!reader.Read(out _nodesPerCharacter)) + { + return false; + } + + return _nodeMap.Import(ref reader) && _bp.Import(ref reader); + } + + public void Match(ReadOnlySpan utf8Text, MatchCallback callback, ref MatchState state) + { + int nodeId = 0; + + for (int index = 0; index < utf8Text.Length; index++) + { + long c = utf8Text[index]; + + while (true) + { + long nodeSparseIndex = _nodesPerCharacter * c + (uint)nodeId; + int nodePlainIndex = _nodeMap.Rank1(nodeSparseIndex); + + if (nodePlainIndex != 0) + { + long foundNodeSparseIndex = _nodeMap.Select1Ex(nodePlainIndex - 1); + + if (foundNodeSparseIndex > 0 && foundNodeSparseIndex == nodeSparseIndex) + { + nodeId = nodePlainIndex; + + if (callback != null) + { + // Match full word. + if (_wordMap.Has(nodePlainIndex)) + { + int wordLength = _wordLengths[_wordMap.Rank1((uint)nodePlainIndex) - 1]; + int startIndex = index + 1 - wordLength; + + if (!callback(utf8Text, startIndex, index + 1, nodeId, ref state)) + { + return; + } + } + + // If this is a phrase composed of multiple words, also match each sub-word. + while (_multiWordMap.Has(nodePlainIndex)) + { + nodePlainIndex = _multiWordIndices[_multiWordMap.Rank1((uint)nodePlainIndex) - 1]; + + int wordLength = _wordMap.Has(nodePlainIndex) ? _wordLengths[_wordMap.Rank1(nodePlainIndex) - 1] : 0; + int startIndex = index + 1 - wordLength; + + if (!callback(utf8Text, startIndex, index + 1, nodePlainIndex, ref state)) + { + return; + } + } + } + + break; + } + } + + if (nodeId == 0) + { + break; + } + + int nodePos = _bp.ToPos(nodeId); + nodePos = _bp.Enclose(nodePos); + if (nodePos < 0) + { + return; + } + + nodeId = _bp.ToNodeId(nodePos); + } + } + } + + public void Match(ReadOnlySpan utf8Text, MatchCallback callback, ref T state) + { + int nodeId = 0; + + for (int index = 0; index < utf8Text.Length; index++) + { + long c = utf8Text[index]; + + while (true) + { + long nodeSparseIndex = _nodesPerCharacter * c + (uint)nodeId; + int nodePlainIndex = _nodeMap.Rank1(nodeSparseIndex); + + if (nodePlainIndex != 0) + { + long foundNodeSparseIndex = _nodeMap.Select1Ex(nodePlainIndex - 1); + + if (foundNodeSparseIndex > 0 && foundNodeSparseIndex == nodeSparseIndex) + { + nodeId = nodePlainIndex; + + if (callback != null) + { + // Match full word. + if (_wordMap.Has(nodePlainIndex)) + { + int wordLength = _wordLengths[_wordMap.Rank1((uint)nodePlainIndex) - 1]; + int startIndex = index + 1 - wordLength; + + if (!callback(utf8Text, startIndex, index + 1, nodeId, ref state)) + { + return; + } + } + + // If this is a phrase composed of multiple words, also match each sub-word. + while (_multiWordMap.Has(nodePlainIndex)) + { + nodePlainIndex = _multiWordIndices[_multiWordMap.Rank1((uint)nodePlainIndex) - 1]; + + int wordLength = _wordMap.Has(nodePlainIndex) ? _wordLengths[_wordMap.Rank1(nodePlainIndex) - 1] : 0; + int startIndex = index + 1 - wordLength; + + if (!callback(utf8Text, startIndex, index + 1, nodePlainIndex, ref state)) + { + return; + } + } + } + + break; + } + } + + if (nodeId == 0) + { + break; + } + + int nodePos = _bp.ToPos(nodeId); + nodePos = _bp.Enclose(nodePos); + if (nodePos < 0) + { + return; + } + + nodeId = _bp.ToNodeId(nodePos); + } + } + } + + public string GetWordList(bool includeMultiWord = true) + { + // Storage must be large enough to fit the largest word in the dictionary. + // Since this is only used for debugging, it's fine to increase the size manually if needed. + StringBuilder sb = new(); + Span storage = new byte[1024]; + + // Traverse trie from the root. + GetWord(sb, storage, 0, 0, includeMultiWord); + + return sb.ToString(); + } + + private void GetWord(StringBuilder sb, Span storage, int storageOffset, int nodeId, bool includeMultiWord) + { + int characters = (int)((_nodeMap.RangeEndValue + _nodesPerCharacter - 1) / _nodesPerCharacter); + + for (int c = 0; c < characters; c++) + { + long nodeSparseIndex = _nodesPerCharacter * c + (uint)nodeId; + int nodePlainIndex = _nodeMap.Rank1(nodeSparseIndex); + + if (nodePlainIndex != 0) + { + long foundNodeSparseIndex = _nodeMap.Select1Ex(nodePlainIndex - 1); + + if (foundNodeSparseIndex > 0 && foundNodeSparseIndex == nodeSparseIndex) + { + storage[storageOffset] = (byte)c; + int nextNodeId = nodePlainIndex; + + if (_wordMap.Has(nodePlainIndex)) + { + sb.AppendLine(Encoding.UTF8.GetString(storage[..(storageOffset + 1)])); + + // Some basic validation to ensure we imported the dictionary properly. + int wordLength = _wordLengths[_wordMap.Rank1((uint)nodePlainIndex) - 1]; + + Debug.Assert(storageOffset + 1 == wordLength); + } + + if (includeMultiWord) + { + int lastMultiWordIndex = 0; + string multiWord = ""; + + while (_multiWordMap.Has(nodePlainIndex)) + { + nodePlainIndex = _multiWordIndices[_multiWordMap.Rank1((uint)nodePlainIndex) - 1]; + + int wordLength = _wordMap.Has(nodePlainIndex) ? _wordLengths[_wordMap.Rank1(nodePlainIndex) - 1] : 0; + int startIndex = storageOffset + 1 - wordLength; + + multiWord += Encoding.UTF8.GetString(storage[lastMultiWordIndex..startIndex]) + " "; + lastMultiWordIndex = startIndex; + } + + if (lastMultiWordIndex != 0) + { + multiWord += Encoding.UTF8.GetString(storage[lastMultiWordIndex..(storageOffset + 1)]); + + sb.AppendLine(multiWord); + } + } + + GetWord(sb, storage, storageOffset + 1, nextNodeId, includeMultiWord); + } + } + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/BinaryReader.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/BinaryReader.cs new file mode 100644 index 00000000..17bfcabb --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/BinaryReader.cs @@ -0,0 +1,63 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + ref struct BinaryReader + { + private readonly ReadOnlySpan _data; + private int _offset; + + public BinaryReader(ReadOnlySpan data) + { + _data = data; + } + + public bool Read(out T value) where T : unmanaged + { + int byteLength = Unsafe.SizeOf(); + + if ((uint)(_offset + byteLength) <= (uint)_data.Length) + { + value = MemoryMarshal.Cast(_data[_offset..])[0]; + _offset += byteLength; + + return true; + } + + value = default; + + return false; + } + + public int AllocateAndReadArray(ref T[] array, int length, int maxLengthExclusive) where T : unmanaged + { + return AllocateAndReadArray(ref array, Math.Min(length, maxLengthExclusive)); + } + + public int AllocateAndReadArray(ref T[] array, int length) where T : unmanaged + { + array = new T[length]; + + return ReadArray(array); + } + + public int ReadArray(T[] array) where T : unmanaged + { + if (array != null) + { + int byteLength = array.Length * Unsafe.SizeOf(); + byteLength = Math.Min(byteLength, _data.Length - _offset); + + MemoryMarshal.Cast(_data.Slice(_offset, byteLength)).CopyTo(array); + + _offset += byteLength; + + return byteLength / Unsafe.SizeOf(); + } + + return 0; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/BitVector32.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/BitVector32.cs new file mode 100644 index 00000000..484c1c07 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/BitVector32.cs @@ -0,0 +1,78 @@ +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + class BitVector32 + { + private const int BitsPerWord = Set.BitsPerWord; + + private int _bitLength; + private uint[] _array; + + public int BitLength => _bitLength; + public uint[] Array => _array; + + public BitVector32() + { + _bitLength = 0; + _array = null; + } + + public BitVector32(int length) + { + _bitLength = length; + _array = new uint[(length + BitsPerWord - 1) / BitsPerWord]; + } + + public bool Has(int index) + { + if ((uint)index < (uint)_bitLength) + { + int wordIndex = index / BitsPerWord; + int wordBitOffset = index % BitsPerWord; + + return ((_array[wordIndex] >> wordBitOffset) & 1u) != 0; + } + + return false; + } + + public bool TurnOn(int index, int count) + { + for (int bit = 0; bit < count; bit++) + { + if (!TurnOn(index + bit)) + { + return false; + } + } + + return true; + } + + public bool TurnOn(int index) + { + if ((uint)index < (uint)_bitLength) + { + int wordIndex = index / BitsPerWord; + int wordBitOffset = index % BitsPerWord; + + _array[wordIndex] |= 1u << wordBitOffset; + + return true; + } + + return false; + } + + public bool Import(ref BinaryReader reader) + { + if (!reader.Read(out _bitLength)) + { + return false; + } + + int arrayLength = (_bitLength + BitsPerWord - 1) / BitsPerWord; + + return reader.AllocateAndReadArray(ref _array, arrayLength) == arrayLength; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Bp.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Bp.cs new file mode 100644 index 00000000..d24fab63 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Bp.cs @@ -0,0 +1,54 @@ +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + class Bp + { + private readonly BpNode _firstNode = new(); + private readonly SbvSelect _sbvSelect = new(); + + public bool Import(ref BinaryReader reader) + { + return _firstNode.Import(ref reader) && _sbvSelect.Import(ref reader); + } + + public int ToPos(int index) + { + return _sbvSelect.Select(_firstNode.Set, index); + } + + public int Enclose(int index) + { + if ((uint)index < (uint)_firstNode.Set.BitVector.BitLength) + { + if (!_firstNode.Set.Has(index)) + { + index = _firstNode.FindOpen(index); + } + + if (index > 0) + { + return _firstNode.Enclose(index); + } + } + + return -1; + } + + public int ToNodeId(int index) + { + if ((uint)index < (uint)_firstNode.Set.BitVector.BitLength) + { + if (!_firstNode.Set.Has(index)) + { + index = _firstNode.FindOpen(index); + } + + if (index >= 0) + { + return _firstNode.Set.Rank1(index) - 1; + } + } + + return -1; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/BpNode.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/BpNode.cs new file mode 100644 index 00000000..2f502cae --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/BpNode.cs @@ -0,0 +1,241 @@ +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + class BpNode + { + private readonly Set _set = new(); + private SparseSet _sparseSet; + private BpNode _nextNode; + + public Set Set => _set; + + public bool Import(ref BinaryReader reader) + { + if (!_set.Import(ref reader)) + { + return false; + } + + if (!reader.Read(out byte hasNext)) + { + return false; + } + + if (hasNext == 0) + { + return true; + } + + _sparseSet = new(); + _nextNode = new(); + + return _sparseSet.Import(ref reader) && _nextNode.Import(ref reader); + } + + public int FindOpen(int index) + { + uint membershipBits = _set.BitVector.Array[index / Set.BitsPerWord]; + + int wordBitOffset = index % Set.BitsPerWord; + int unsetBits = 1; + + for (int bit = wordBitOffset - 1; bit >= 0; bit--) + { + if (((membershipBits >> bit) & 1) != 0) + { + if (--unsetBits == 0) + { + return (index & ~(Set.BitsPerWord - 1)) | bit; + } + } + else + { + unsetBits++; + } + } + + int plainIndex = _sparseSet.Rank1(index); + if (plainIndex == 0) + { + return -1; + } + + int newIndex = index; + + if (!_sparseSet.Has(index)) + { + if (plainIndex == 0 || _nextNode == null) + { + return -1; + } + + newIndex = _sparseSet.Select1(plainIndex); + if (newIndex < 0) + { + return -1; + } + } + else + { + plainIndex--; + } + + int openIndex = _nextNode.FindOpen(plainIndex); + if (openIndex < 0) + { + return -1; + } + + int openSparseIndex = _sparseSet.Select1(openIndex); + if (openSparseIndex < 0) + { + return -1; + } + + if (newIndex != index) + { + unsetBits = 1; + + for (int bit = newIndex % Set.BitsPerWord - 1; bit > wordBitOffset; bit--) + { + unsetBits += ((membershipBits >> bit) & 1) != 0 ? -1 : 1; + } + + int bestCandidate = -1; + + membershipBits = _set.BitVector.Array[openSparseIndex / Set.BitsPerWord]; + + for (int bit = openSparseIndex % Set.BitsPerWord + 1; bit < Set.BitsPerWord; bit++) + { + if (unsetBits - 1 == 0) + { + bestCandidate = bit; + } + + unsetBits += ((membershipBits >> bit) & 1) != 0 ? -1 : 1; + } + + return (openSparseIndex & ~(Set.BitsPerWord - 1)) | bestCandidate; + } + else + { + return openSparseIndex; + } + } + + public int Enclose(int index) + { + uint membershipBits = _set.BitVector.Array[index / Set.BitsPerWord]; + + int unsetBits = 1; + + for (int bit = index % Set.BitsPerWord - 1; bit >= 0; bit--) + { + if (((membershipBits >> bit) & 1) != 0) + { + if (--unsetBits == 0) + { + return (index & ~(Set.BitsPerWord - 1)) + bit; + } + } + else + { + unsetBits++; + } + } + + int setBits = 2; + + for (int bit = index % Set.BitsPerWord + 1; bit < Set.BitsPerWord; bit++) + { + if (((membershipBits >> bit) & 1) != 0) + { + setBits++; + } + else + { + if (--setBits == 0) + { + return FindOpen((index & ~(Set.BitsPerWord - 1)) + bit); + } + } + } + + int newIndex = index; + + if (!_sparseSet.Has(index)) + { + newIndex = _sparseSet.Select1(_sparseSet.Rank1(index)); + if (newIndex < 0) + { + return -1; + } + } + + if (!_set.Has(newIndex)) + { + newIndex = FindOpen(newIndex); + if (newIndex < 0) + { + return -1; + } + } + else + { + newIndex = _nextNode.Enclose(_sparseSet.Rank1(newIndex) - 1); + if (newIndex < 0) + { + return -1; + } + + newIndex = _sparseSet.Select1(newIndex); + } + + int nearestIndex = _sparseSet.Select1(_sparseSet.Rank1(newIndex)); + if (nearestIndex < 0) + { + return -1; + } + + setBits = 0; + + membershipBits = _set.BitVector.Array[newIndex / Set.BitsPerWord]; + + if ((newIndex / Set.BitsPerWord) == (nearestIndex / Set.BitsPerWord)) + { + for (int bit = nearestIndex % Set.BitsPerWord - 1; bit >= newIndex % Set.BitsPerWord; bit--) + { + if (((membershipBits >> bit) & 1) != 0) + { + if (++setBits > 0) + { + return (newIndex & ~(Set.BitsPerWord - 1)) + bit; + } + } + else + { + setBits--; + } + } + } + else + { + for (int bit = Set.BitsPerWord - 1; bit >= newIndex % Set.BitsPerWord; bit--) + { + if (((membershipBits >> bit) & 1) != 0) + { + if (++setBits > 0) + { + return (newIndex & ~(Set.BitsPerWord - 1)) + bit; + } + } + else + { + setBits--; + } + } + } + + return -1; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/CompressedArray.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/CompressedArray.cs new file mode 100644 index 00000000..fc5cd683 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/CompressedArray.cs @@ -0,0 +1,100 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + class CompressedArray + { + private const int MaxUncompressedEntries = 64; + private const int CompressedEntriesPerBlock = 64; + private const int BitsPerWord = Set.BitsPerWord; + + private readonly struct BitfieldRange + { + private readonly uint _range; + private readonly int _baseValue; + + public int BitfieldIndex => (int)(_range & 0x7ffffff); + public int BitfieldLength => (int)(_range >> 27) + 1; + public int BaseValue => _baseValue; + + public BitfieldRange(uint range, int baseValue) + { + _range = range; + _baseValue = baseValue; + } + } + + private uint[] _bitfieldRanges; + private uint[] _bitfields; + private int[] _uncompressedArray; + + public int Length => (_bitfieldRanges.Length / 2) * CompressedEntriesPerBlock + _uncompressedArray.Length; + + public int this[int index] + { + get + { + var ranges = GetBitfieldRanges(); + + int rangeBlockIndex = index / CompressedEntriesPerBlock; + + if (rangeBlockIndex < ranges.Length) + { + var range = ranges[rangeBlockIndex]; + + int bitfieldLength = range.BitfieldLength; + int bitfieldOffset = (index % CompressedEntriesPerBlock) * bitfieldLength; + int bitfieldIndex = range.BitfieldIndex + (bitfieldOffset / BitsPerWord); + int bitOffset = bitfieldOffset % BitsPerWord; + + ulong bitfieldValue = _bitfields[bitfieldIndex]; + + // If the bit fields crosses the word boundary, let's load the next one to ensure we + // have access to the full value. + if (bitOffset + bitfieldLength > BitsPerWord) + { + bitfieldValue |= (ulong)_bitfields[bitfieldIndex + 1] << 32; + } + + int value = (int)(bitfieldValue >> bitOffset) & ((1 << bitfieldLength) - 1); + + // Sign-extend. + int remainderBits = BitsPerWord - bitfieldLength; + value <<= remainderBits; + value >>= remainderBits; + + return value + range.BaseValue; + } + else if (rangeBlockIndex < _uncompressedArray.Length + _bitfieldRanges.Length * BitsPerWord) + { + return _uncompressedArray[index % MaxUncompressedEntries]; + } + + return 0; + } + } + + private ReadOnlySpan GetBitfieldRanges() + { + return MemoryMarshal.Cast(_bitfieldRanges); + } + + public bool Import(ref BinaryReader reader) + { + if (!reader.Read(out int bitfieldRangesCount) || + reader.AllocateAndReadArray(ref _bitfieldRanges, bitfieldRangesCount) != bitfieldRangesCount) + { + return false; + } + + if (!reader.Read(out int bitfieldsCount) || reader.AllocateAndReadArray(ref _bitfields, bitfieldsCount) != bitfieldsCount) + { + return false; + } + + return reader.Read(out byte uncompressedArrayLength) && + reader.AllocateAndReadArray(ref _uncompressedArray, uncompressedArrayLength, MaxUncompressedEntries) == uncompressedArrayLength; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/ContentsReader.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/ContentsReader.cs new file mode 100644 index 00000000..6a0fc4f9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/ContentsReader.cs @@ -0,0 +1,404 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Fs; +using System; +using System.IO; +using System.IO.Compression; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + class ContentsReader : IDisposable + { + private const string MountName = "NgWord"; + private const string VersionFilePath = $"{MountName}:/version.dat"; + private const ulong DataId = 0x100000000000823UL; + + private enum AcType + { + AcNotB, + AcB1, + AcB2, + AcSimilarForm, + TableSimilarForm, + } + + private readonly IFsClient _fsClient; + private readonly object _lock; + private bool _intialized; + private ulong _cacheSize; + + public ContentsReader(IFsClient fsClient) + { + _lock = new(); + _fsClient = fsClient; + } + + private static void MakeMountPoint(out string path, AcType type, int regionIndex) + { + path = null; + + switch (type) + { + case AcType.AcNotB: + if (regionIndex < 0) + { + path = $"{MountName}:/ac_common_not_b_nx"; + } + else + { + path = $"{MountName}:/ac_{regionIndex}_not_b_nx"; + } + break; + case AcType.AcB1: + if (regionIndex < 0) + { + path = $"{MountName}:/ac_common_b1_nx"; + } + else + { + path = $"{MountName}:/ac_{regionIndex}_b1_nx"; + } + break; + case AcType.AcB2: + if (regionIndex < 0) + { + path = $"{MountName}:/ac_common_b2_nx"; + } + else + { + path = $"{MountName}:/ac_{regionIndex}_b2_nx"; + } + break; + case AcType.AcSimilarForm: + path = $"{MountName}:/ac_similar_form_nx"; + break; + case AcType.TableSimilarForm: + path = $"{MountName}:/table_similar_form_nx"; + break; + } + } + + public Result Initialize(ulong cacheSize) + { + lock (_lock) + { + if (_intialized) + { + return Result.Success; + } + + Result result = _fsClient.QueryMountSystemDataCacheSize(out long dataCacheSize, DataId); + if (result.IsFailure) + { + return result; + } + + if (cacheSize < (ulong)dataCacheSize) + { + return NgcResult.InvalidSize; + } + + result = _fsClient.MountSystemData(MountName, DataId); + if (result.IsFailure) + { + // Official firmware would return the result here, + // we don't to support older firmware where the archive didn't exist yet. + return Result.Success; + } + + _cacheSize = cacheSize; + _intialized = true; + + return Result.Success; + } + } + + public Result Reload() + { + lock (_lock) + { + if (!_intialized) + { + return Result.Success; + } + + _fsClient.Unmount(MountName); + + Result result = Result.Success; + + try + { + result = _fsClient.QueryMountSystemDataCacheSize(out long cacheSize, DataId); + if (result.IsFailure) + { + return result; + } + + if (_cacheSize < (ulong)cacheSize) + { + result = NgcResult.InvalidSize; + return NgcResult.InvalidSize; + } + + result = _fsClient.MountSystemData(MountName, DataId); + if (result.IsFailure) + { + return result; + } + } + finally + { + if (result.IsFailure) + { + _intialized = false; + _cacheSize = 0; + } + } + } + + return Result.Success; + } + + private Result GetFileSize(out long size, string filePath) + { + size = 0; + + lock (_lock) + { + Result result = _fsClient.OpenFile(out FileHandle handle, filePath, OpenMode.Read); + if (result.IsFailure) + { + return result; + } + + try + { + result = _fsClient.GetFileSize(out size, handle); + if (result.IsFailure) + { + return result; + } + } + finally + { + _fsClient.CloseFile(handle); + } + } + + return Result.Success; + } + + private Result GetFileContent(Span destination, string filePath) + { + lock (_lock) + { + Result result = _fsClient.OpenFile(out FileHandle handle, filePath, OpenMode.Read); + if (result.IsFailure) + { + return result; + } + + try + { + result = _fsClient.ReadFile(handle, 0, destination); + if (result.IsFailure) + { + return result; + } + } + finally + { + _fsClient.CloseFile(handle); + } + } + + return Result.Success; + } + + public Result GetVersionDataSize(out long size) + { + return GetFileSize(out size, VersionFilePath); + } + + public Result GetVersionData(Span destination) + { + return GetFileContent(destination, VersionFilePath); + } + + public Result ReadDictionaries(out AhoCorasick partialWordsTrie, out AhoCorasick completeWordsTrie, out AhoCorasick delimitedWordsTrie, int regionIndex) + { + completeWordsTrie = null; + delimitedWordsTrie = null; + + MakeMountPoint(out string partialWordsTriePath, AcType.AcNotB, regionIndex); + MakeMountPoint(out string completeWordsTriePath, AcType.AcB1, regionIndex); + MakeMountPoint(out string delimitedWordsTriePath, AcType.AcB2, regionIndex); + + Result result = ReadDictionary(out partialWordsTrie, partialWordsTriePath); + if (result.IsFailure) + { + return NgcResult.DataAccessError; + } + + result = ReadDictionary(out completeWordsTrie, completeWordsTriePath); + if (result.IsFailure) + { + return NgcResult.DataAccessError; + } + + return ReadDictionary(out delimitedWordsTrie, delimitedWordsTriePath); + } + + public Result ReadSimilarFormDictionary(out AhoCorasick similarFormTrie) + { + MakeMountPoint(out string similarFormTriePath, AcType.AcSimilarForm, 0); + + return ReadDictionary(out similarFormTrie, similarFormTriePath); + } + + public Result ReadSimilarFormTable(out SimilarFormTable similarFormTable) + { + similarFormTable = null; + + MakeMountPoint(out string similarFormTablePath, AcType.TableSimilarForm, 0); + + Result result = ReadGZipCompressedArchive(out byte[] data, similarFormTablePath); + if (result.IsFailure) + { + return result; + } + + BinaryReader reader = new(data); + SimilarFormTable table = new(); + + if (!table.Import(ref reader)) + { + // Official firmware doesn't return an error here and just assumes the import was successful. + return NgcResult.DataAccessError; + } + + similarFormTable = table; + + return Result.Success; + } + + public static Result ReadNotSeparatorDictionary(out AhoCorasick notSeparatorTrie) + { + notSeparatorTrie = null; + + BinaryReader reader = new(EmbeddedTries.NotSeparatorTrie); + AhoCorasick ac = new(); + + if (!ac.Import(ref reader)) + { + // Official firmware doesn't return an error here and just assumes the import was successful. + return NgcResult.DataAccessError; + } + + notSeparatorTrie = ac; + + return Result.Success; + } + + private Result ReadDictionary(out AhoCorasick trie, string path) + { + trie = null; + + Result result = ReadGZipCompressedArchive(out byte[] data, path); + if (result.IsFailure) + { + return result; + } + + BinaryReader reader = new(data); + AhoCorasick ac = new(); + + if (!ac.Import(ref reader)) + { + // Official firmware doesn't return an error here and just assumes the import was successful. + return NgcResult.DataAccessError; + } + + trie = ac; + + return Result.Success; + } + + private Result ReadGZipCompressedArchive(out byte[] data, string filePath) + { + data = null; + + Result result = _fsClient.OpenFile(out FileHandle handle, filePath, OpenMode.Read); + if (result.IsFailure) + { + return result; + } + + try + { + result = _fsClient.GetFileSize(out long fileSize, handle); + if (result.IsFailure) + { + return result; + } + + data = new byte[fileSize]; + + result = _fsClient.ReadFile(handle, 0, data.AsSpan()); + if (result.IsFailure) + { + return result; + } + } + finally + { + _fsClient.CloseFile(handle); + } + + try + { + data = DecompressGZipCompressedStream(data); + } + catch (InvalidDataException) + { + // Official firmware returns a different error, but it is translated to this error on the caller. + return NgcResult.DataAccessError; + } + + return Result.Success; + } + + private static byte[] DecompressGZipCompressedStream(byte[] data) + { + using MemoryStream input = new(data); + using GZipStream gZipStream = new(input, CompressionMode.Decompress); + using MemoryStream output = new(); + + gZipStream.CopyTo(output); + + return output.ToArray(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + lock (_lock) + { + if (!_intialized) + { + return; + } + + _fsClient.Unmount(MountName); + _intialized = false; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/EmbeddedTries.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/EmbeddedTries.cs new file mode 100644 index 00000000..37ee43fa --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/EmbeddedTries.cs @@ -0,0 +1,266 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + static class EmbeddedTries + { + public static ReadOnlySpan NotSeparatorTrie => new byte[] + { + 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0xE9, 0xFF, 0xE9, 0xFF, 0xF4, 0xFF, 0xFA, 0xBF, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x5F, 0xFF, 0xAF, + 0xFF, 0xEB, 0xFF, 0xFA, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFB, 0x7F, 0xFF, 0xEF, 0xFF, 0xFD, + 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xF7, 0xFF, 0xE8, 0xFF, 0xE9, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFC, 0x3F, 0xFF, 0xCF, 0xFF, 0xF3, 0xFF, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xE7, 0xFF, + 0xFC, 0x9F, 0xFF, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x9F, 0xFF, 0xE7, 0xFF, 0xF9, 0x7F, + 0x00, 0x00, 0x00, 0x00, 0xFE, 0x5F, 0xFF, 0xCF, 0xFF, 0xF3, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0xFF, 0xCF, 0xFF, 0xF3, 0xFF, 0xFC, 0x3F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xCF, 0xFF, 0xF3, + 0xFF, 0xFC, 0x3F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xCF, 0xFF, 0xFB, 0x7F, 0xFE, 0x9F, 0xFF, 0xF3, + 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x9F, 0xFF, 0xF3, 0xFF, 0xFC, 0x9F, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xF3, 0x7F, 0xFE, 0xCF, 0xFF, 0xF5, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x03, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x8A, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0x54, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0xA9, 0x52, 0x55, 0x55, 0xA9, 0xAA, 0xAA, 0xAA, 0x54, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0xAA, 0x54, 0x55, 0xA5, 0x4A, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0x52, 0x55, 0x55, + 0x95, 0xAA, 0xAA, 0xAA, 0x54, 0x55, 0x55, 0xA5, 0xAA, 0xAA, 0x2A, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xA5, 0xAA, 0xAA, 0xAA, 0x54, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x4A, + 0x55, 0x55, 0x55, 0xA9, 0xAA, 0xAA, 0x52, 0x55, 0x55, 0xA5, 0xAA, 0xAA, 0x4A, 0x55, 0x55, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7D, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x77, 0x01, 0x00, + 0x00, 0xF7, 0x01, 0x00, 0x00, 0x77, 0x02, 0x00, 0x00, 0xF7, 0x02, 0x00, 0x00, 0x6E, 0x03, 0x00, + 0x00, 0x6E, 0x03, 0x00, 0x00, 0x6E, 0x03, 0x00, 0x00, 0x6E, 0x03, 0x00, 0x00, 0x6E, 0x03, 0x00, + 0x00, 0x6E, 0x03, 0x00, 0x00, 0x6E, 0x03, 0x00, 0x00, 0x6E, 0x03, 0x00, 0x00, 0x6E, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x1F, 0x2F, 0x3F, 0x4E, 0x5E, 0x6D, 0x00, 0x0F, 0x1E, + 0x2E, 0x3D, 0x4C, 0x5C, 0x6B, 0x00, 0x10, 0x20, 0x2F, 0x3F, 0x4F, 0x5F, 0x6F, 0x00, 0x10, 0x20, + 0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20, + 0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20, 0x30, 0x3F, 0x4F, 0x5E, 0x6D, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x03, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F, + 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, + 0x00, 0x02, 0x04, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8A, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8A, 0x03, 0x00, 0x00, 0x00, 0x8A, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0xC5, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x51, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x03, 0x00, + 0x00, 0x07, 0x00, 0x00, 0x00, 0xC6, 0x00, 0x00, 0x00, 0xCF, 0xED, 0x81, 0x61, 0xD9, 0xDC, 0x8A, + 0xD3, 0xF0, 0xBB, 0x05, 0x6E, 0xEB, 0x0D, 0x88, 0x6C, 0x39, 0x62, 0x01, 0x95, 0x82, 0xCF, 0xEE, + 0x3A, 0x7F, 0x53, 0xDF, 0x09, 0x90, 0xF7, 0x06, 0xA4, 0x7A, 0x2D, 0xB3, 0xE7, 0xFA, 0x20, 0x48, + 0x0F, 0x38, 0x34, 0xED, 0xBC, 0x8A, 0x96, 0xAB, 0x8E, 0xE3, 0xFF, 0xC6, 0xD2, 0xBF, 0xC0, 0x90, + 0x06, 0x34, 0xDF, 0xF0, 0xDB, 0xDE, 0x27, 0x2E, 0xD5, 0x3C, 0xA2, 0x22, 0x72, 0xBD, 0x02, 0x0D, + 0x1F, 0xB2, 0x99, 0xBE, 0x17, 0x26, 0xA1, 0xEF, 0x40, 0xF2, 0x61, 0xE1, 0x16, 0x17, 0xA4, 0xF4, + 0x3A, 0x0F, 0x3C, 0x3A, 0xAB, 0x74, 0x83, 0x93, 0xB2, 0x09, 0x43, 0x52, 0x6E, 0xB8, 0xBF, 0xC8, + 0x9C, 0x6A, 0x73, 0xD3, 0x0C, 0xC8, 0x5C, 0x71, 0xCD, 0x87, 0xCA, 0x28, 0xF6, 0xEB, 0x87, 0x60, + 0x3D, 0xA5, 0x15, 0x9B, 0xAA, 0x99, 0x23, 0x9F, 0xD6, 0x2E, 0x79, 0x58, 0xE9, 0x8E, 0x54, 0xB0, + 0xF8, 0x07, 0x6F, 0x6C, 0x52, 0xB7, 0xE2, 0x34, 0x42, 0x8C, 0x7A, 0xD5, 0xEC, 0xA4, 0xFE, 0x52, + 0x9A, 0x05, 0x9F, 0xDD, 0x8D, 0x73, 0x8B, 0xA6, 0xDB, 0xA7, 0x84, 0xD0, 0xAB, 0xB7, 0xCC, 0x9E, + 0x4B, 0xD8, 0xB2, 0xDC, 0x0F, 0xE8, 0x3A, 0x56, 0xB9, 0x63, 0x75, 0x1C, 0x7F, 0x89, 0xDF, 0x7C, + 0x84, 0xE2, 0x8C, 0xA9, 0x0D, 0xA3, 0xDF, 0xF6, 0x3E, 0xC7, 0xCE, 0x1B, 0x24, 0x94, 0xB8, 0xE8, + 0xD7, 0xDC, 0xA6, 0xEF, 0x85, 0xA1, 0x7D, 0x00, 0xE1, 0x78, 0xD4, 0x8B, 0x13, 0xCB, 0xB6, 0x4B, + 0x5E, 0xCB, 0xF3, 0xC0, 0xA3, 0x09, 0x68, 0x68, 0x4C, 0xF4, 0x98, 0x0D, 0x38, 0x0D, 0xBF, 0xFB, + 0x8B, 0xCC, 0x55, 0x71, 0x21, 0xC1, 0xFC, 0x3B, 0x60, 0x77, 0x9D, 0x3F, 0x54, 0x46, 0x61, 0x4A, + 0xC8, 0xA5, 0xDB, 0x21, 0x8A, 0xCA, 0x73, 0x7D, 0x10, 0xF9, 0xB4, 0xD6, 0x9E, 0x15, 0x8E, 0x58, + 0x94, 0x3C, 0xA9, 0xF1, 0x7F, 0x63, 0x93, 0xBA, 0xD5, 0x51, 0x35, 0xA1, 0x93, 0x93, 0xF5, 0xEE, + 0x13, 0x97, 0xD2, 0x2C, 0xF8, 0x97, 0xFD, 0x98, 0x58, 0xD3, 0x6A, 0x8C, 0x2E, 0x4C, 0x42, 0xAF, + 0xDE, 0x32, 0xC1, 0x4B, 0x5A, 0x61, 0x6D, 0xF9, 0xA3, 0xB3, 0xCA, 0x1D, 0xAB, 0x13, 0xE3, 0x14, + 0xAC, 0xBB, 0xF3, 0x33, 0xA7, 0xDA, 0x30, 0xFA, 0xED, 0x40, 0xBB, 0x6A, 0x62, 0xC0, 0x30, 0x8A, + 0xFD, 0x9A, 0xDB, 0xF4, 0x49, 0x7B, 0xA6, 0x3B, 0x17, 0x90, 0xD6, 0x2E, 0x79, 0x2D, 0xCF, 0x63, + 0xE4, 0xB8, 0x1F, 0x5B, 0xD1, 0xDC, 0x8A, 0xD3, 0xF0, 0xBB, 0xBF, 0x73, 0xEF, 0x11, 0xE2, 0x0F, + 0x29, 0xF8, 0xEC, 0xAE, 0xF3, 0x07, 0x5B, 0x11, 0x5F, 0x90, 0xB0, 0x53, 0xAE, 0x65, 0xF6, 0x5C, + 0x1F, 0x44, 0x80, 0x4F, 0xC1, 0x83, 0x63, 0x9F, 0xE1, 0xAA, 0xE3, 0xF8, 0xBF, 0xB1, 0x51, 0x66, + 0x19, 0x19, 0x13, 0xA0, 0xF7, 0x6D, 0xEF, 0x13, 0x97, 0x12, 0x75, 0xAC, 0xB7, 0x8C, 0x60, 0x3F, + 0xC5, 0x71, 0x9B, 0xBE, 0x17, 0x26, 0xA1, 0x97, 0xB7, 0x0D, 0x6A, 0xE9, 0x28, 0x99, 0x68, 0x79, + 0x1E, 0x78, 0x74, 0x56, 0x39, 0xF4, 0x5D, 0x75, 0x23, 0x7A, 0xB6, 0xEF, 0xFE, 0x22, 0x73, 0xAA, + 0x0D, 0xE5, 0x01, 0x5A, 0xD0, 0x89, 0x2A, 0xE7, 0x0F, 0x95, 0x51, 0xEC, 0xD7, 0xE4, 0x2F, 0x7C, + 0x4B, 0xAC, 0xEC, 0x3D, 0x88, 0x7C, 0x5A, 0xBB, 0xE4, 0xD5, 0x50, 0x41, 0x56, 0xC5, 0xBC, 0x7C, + 0x63, 0x93, 0xBA, 0x15, 0xA7, 0x61, 0xC8, 0x47, 0xFA, 0x65, 0x1B, 0x07, 0x97, 0xD2, 0x2C, 0xF8, + 0xEC, 0xAE, 0x35, 0x29, 0x6E, 0xDA, 0x0E, 0x6D, 0x84, 0x5E, 0xBD, 0x65, 0xF6, 0x5C, 0x27, 0xCD, + 0xCC, 0x73, 0x80, 0xF6, 0xB2, 0xCA, 0x1D, 0xAB, 0xE3, 0xF8, 0xDF, 0xD5, 0x83, 0xF7, 0x15, 0xE4, + 0x50, 0x6D, 0x18, 0xFD, 0xB6, 0xF7, 0x09, 0xDC, 0x51, 0x7F, 0xA0, 0xB8, 0x57, 0xB0, 0x5F, 0x73, + 0x9B, 0xBE, 0x17, 0x26, 0x42, 0x42, 0xC4, 0x83, 0xAF, 0xE9, 0x92, 0xD7, 0xF2, 0x3C, 0xF0, 0xE8, + 0x30, 0x1D, 0x1B, 0x94, 0xE0, 0x47, 0x9C, 0x86, 0xDF, 0xFD, 0x45, 0xE6, 0x64, 0xC5, 0x94, 0x64, + 0x8C, 0xA4, 0xB3, 0xBB, 0xCE, 0x1F, 0x2A, 0xA3, 0x18, 0x58, 0xF4, 0xE2, 0x59, 0xA6, 0xD8, 0x73, + 0x7D, 0x10, 0xF9, 0xB4, 0x76, 0x6A, 0x56, 0xCE, 0xD8, 0x15, 0xC7, 0xFF, 0x8D, 0x4D, 0xEA, 0x56, + 0xA4, 0xDB, 0x86, 0x50, 0xD5, 0x99, 0xBD, 0x4F, 0x5C, 0x4A, 0xB3, 0xE0, 0xD3, 0x0F, 0x6C, 0x6A, + 0x69, 0x71, 0x7B, 0x21, 0xF4, 0xEA, 0x2D, 0xB3, 0x08, 0xE5, 0x95, 0xEC, 0xDB, 0x03, 0x1E, 0xAB, + 0xDC, 0xB1, 0x3A, 0x96, 0x50, 0xC3, 0x6E, 0x64, 0x41, 0x91, 0xA9, 0x0D, 0xA3, 0xDF, 0x36, 0x27, + 0xEA, 0x5D, 0xE3, 0xA5, 0x0F, 0xCA, 0xE8, 0xD7, 0xDC, 0xA6, 0xEF, 0x26, 0x74, 0x5D, 0xC0, 0xCD, + 0x78, 0x5A, 0xC9, 0x6B, 0x79, 0x1E, 0x80, 0xC9, 0xFF, 0x8C, 0x96, 0x79, 0x84, 0xBA, 0x4D, 0xC3, + 0xEF, 0xFE, 0x42, 0xC7, 0x4F, 0x58, 0xE0, 0x2D, 0x59, 0xB0, 0xBB, 0xCE, 0x1F, 0x2A, 0x44, 0xC3, + 0x04, 0xA4, 0xBF, 0xF1, 0x96, 0xE7, 0xFA, 0x20, 0xF2, 0x71, 0x42, 0x3A, 0x2A, 0x42, 0xD0, 0x58, + 0x8D, 0xFF, 0x1B, 0x9B, 0x14, 0x56, 0x73, 0xA2, 0x39, 0x96, 0xD0, 0xEF, 0x3E, 0x71, 0x29, 0xCD, + 0xC4, 0xA4, 0x98, 0x6F, 0x89, 0xE9, 0x54, 0xB5, 0xE9, 0xC2, 0x24, 0xF4, 0xEA, 0xB1, 0x5D, 0x3B, + 0x64, 0x55, 0x44, 0x9E, 0x3F, 0x3A, 0xAB, 0xDC, 0xD1, 0x8E, 0x2B, 0x4A, 0xBF, 0x2C, 0x77, 0x3F, + 0x73, 0xAA, 0x0D, 0xA3, 0x00, 0xE1, 0x93, 0x9B, 0xB6, 0xE1, 0x0F, 0xA3, 0xD8, 0xAF, 0xB9, 0x55, + 0x30, 0xB3, 0xE6, 0x39, 0x50, 0xD0, 0xDA, 0x25, 0xAF, 0x65, 0x8A, 0x75, 0x0C, 0xEF, 0x53, 0xBD, + 0x60, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0xBF, 0x70, 0xEF, 0xBF, 0xB0, 0xFB, 0x37, 0xF4, 0xFD, + 0x0D, 0xDD, 0xDF, 0x85, 0xEF, 0xEF, 0x89, 0xF7, 0xFB, 0xC4, 0xFB, 0x3E, 0x78, 0xF7, 0x13, 0xDF, + 0x7D, 0xC5, 0xB7, 0x5F, 0xF8, 0xF6, 0x0B, 0x5F, 0x7F, 0xE1, 0xED, 0x2F, 0xDC, 0xFD, 0x85, 0xBD, + 0xDF, 0xD0, 0xF7, 0xDF, 0xC1, 0xF7, 0x77, 0xF0, 0x7D, 0x0F, 0xBE, 0xEF, 0x83, 0xEF, 0xFB, 0xE0, + 0xBD, 0x1F, 0xBC, 0xF7, 0x0B, 0x77, 0xBF, 0x70, 0xF7, 0x0B, 0xD7, 0xBF, 0x70, 0xFD, 0x0B, 0xD7, + 0xBF, 0xB0, 0xFD, 0x1D, 0xBA, 0xDF, 0x83, 0xF7, 0x7B, 0x70, 0xDF, 0x87, 0xDE, 0xF7, 0x83, 0xFB, + 0xFE, 0xE0, 0xDE, 0x2F, 0xDC, 0xFD, 0x85, 0xDB, 0xDF, 0x70, 0xFB, 0x1B, 0xAE, 0x7F, 0xC3, 0xF5, + 0x6F, 0xD8, 0xFE, 0x0D, 0xDB, 0xDF, 0xA1, 0xFB, 0x3B, 0x78, 0xBF, 0x07, 0xF7, 0xF7, 0xE0, 0x7E, + 0x1F, 0xDC, 0xF7, 0x83, 0x7B, 0x3F, 0xB8, 0xF7, 0x07, 0x77, 0xBF, 0x70, 0xFB, 0x0B, 0xD7, 0xBF, + 0xF0, 0xFA, 0x17, 0xB6, 0xBF, 0x61, 0xF7, 0x37, 0x74, 0xBF, 0x83, 0xF7, 0x3D, 0xB8, 0xDF, 0x83, + 0xFB, 0x3E, 0x78, 0xDF, 0x0F, 0xDE, 0xFD, 0xE0, 0xDD, 0x17, 0xDE, 0x7E, 0xE1, 0xF5, 0x0B, 0x0F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0xA8, 0x01, 0x00, + 0x00, 0x53, 0x02, 0x00, 0x00, 0xFD, 0x02, 0x00, 0x00, 0x86, 0x03, 0x00, 0x00, 0x88, 0x03, 0x00, + 0x00, 0x89, 0x03, 0x00, 0x00, 0x89, 0x03, 0x00, 0x00, 0x89, 0x03, 0x00, 0x00, 0x89, 0x03, 0x00, + 0x00, 0x89, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x27, 0x3B, 0x00, 0x18, 0x2B, 0x42, 0x57, 0x6D, 0x81, + 0x98, 0x00, 0x17, 0x2B, 0x42, 0x56, 0x6D, 0x80, 0x97, 0x00, 0x17, 0x2B, 0x43, 0x56, 0x6D, 0x80, + 0x97, 0x00, 0x16, 0x2B, 0x40, 0x55, 0x69, 0x80, 0x94, 0x00, 0x13, 0x29, 0x3E, 0x52, 0x68, 0x7C, + 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1C, 0x00, + 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2D, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x89, 0x03, 0x00, 0x00, 0x01, 0x80, + 0x00, 0x00, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x02, 0x00, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x40, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x40, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x40, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00, 0x02, 0x00, 0x40, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x80, 0x00, 0x00, 0x20, 0x00, 0x00, + 0x01, 0x00, 0x40, 0x00, 0x00, 0x08, 0x00, 0x80, 0x00, 0x00, 0x20, 0x00, 0x00, 0x02, 0x40, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x05, 0x07, 0x08, 0x0A, 0x0B, + 0x00, 0x01, 0x02, 0x04, 0x06, 0x07, 0x09, 0x0A, 0x00, 0x01, 0x03, 0x04, 0x06, 0x07, 0x09, 0x0A, + 0x00, 0x01, 0x03, 0x04, 0x06, 0x14, 0x07, 0x00, 0x00, 0xAB, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x81, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x81, 0x01, 0x00, 0x00, 0x01, 0x02, 0x00, + 0x00, 0x81, 0x02, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x81, 0x03, 0x00, 0x00, 0x00, 0x11, 0x21, + 0x31, 0x41, 0x51, 0x61, 0x71, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20, + 0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20, + 0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x10, 0x20, + 0x30, 0x40, 0x50, 0x60, 0x70, 0x00, 0x01, 0x14, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x38, 0x8E, 0xE3, 0x38, 0x8E, + 0xE3, 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, + 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x38, + 0x8E, 0xE3, 0x38, 0x8E, 0xE3, 0x18, 0x00, 0x00, 0x02, 0x00, 0x00, 0x51, 0x14, 0x45, 0x51, 0x14, + 0x45, 0x51, 0x14, 0x45, 0x51, 0x14, 0x45, 0x51, 0x14, 0x45, 0x51, 0x14, 0x45, 0x51, 0x14, 0x45, + 0x51, 0x14, 0x45, 0x51, 0x14, 0x45, 0x51, 0x14, 0x45, 0x51, 0x14, 0x45, 0x51, 0x14, 0x45, 0x51, + 0x14, 0x45, 0x51, 0x14, 0x45, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x15, 0x20, 0x2B, 0x35, 0x40, 0x4B, 0x00, + 0x0B, 0x16, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, + 0x01, 0x08, 0x20, 0x00, 0x01, 0x08, 0x20, 0x00, 0x01, 0x08, 0x20, 0x00, 0x01, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x09, 0x72, 0x00, 0x00, + 0x00, 0xAB, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x21, 0x31, 0x01, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x38, 0x8E, + 0x23, 0x00, 0x20, 0x00, 0x00, 0x00, 0x51, 0x14, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x8A, 0x03, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, + 0x0C, 0x0E, 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x00, 0x02, 0x04, 0x06, 0x08, + }; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchCheckState.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchCheckState.cs new file mode 100644 index 00000000..b1a409ea --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchCheckState.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + struct MatchCheckState + { + public uint CheckMask; + public readonly uint RegionMask; + public readonly ProfanityFilterOption Option; + + public MatchCheckState(uint checkMask, uint regionMask, ProfanityFilterOption option) + { + CheckMask = checkMask; + RegionMask = regionMask; + Option = option; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchDelimitedState.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchDelimitedState.cs new file mode 100644 index 00000000..30965819 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchDelimitedState.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + struct MatchDelimitedState + { + public bool Matched; + public readonly bool PrevCharIsWordSeparator; + public readonly bool NextCharIsWordSeparator; + public readonly Sbv NoSeparatorMap; + public readonly AhoCorasick DelimitedWordsTrie; + + public MatchDelimitedState( + bool prevCharIsWordSeparator, + bool nextCharIsWordSeparator, + Sbv noSeparatorMap, + AhoCorasick delimitedWordsTrie) + { + Matched = false; + PrevCharIsWordSeparator = prevCharIsWordSeparator; + NextCharIsWordSeparator = nextCharIsWordSeparator; + NoSeparatorMap = noSeparatorMap; + DelimitedWordsTrie = delimitedWordsTrie; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchRangeList.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchRangeList.cs new file mode 100644 index 00000000..91600f98 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchRangeList.cs @@ -0,0 +1,113 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + readonly struct MatchRange + { + public readonly int StartOffset; + public readonly int EndOffset; + + public MatchRange(int startOffset, int endOffset) + { + StartOffset = startOffset; + EndOffset = endOffset; + } + } + + struct MatchRangeList + { + private int _capacity; + private int _count; + private MatchRange[] _ranges; + + public readonly int Count => _count; + + public readonly MatchRange this[int index] => _ranges[index]; + + public MatchRangeList() + { + _capacity = 0; + _count = 0; + _ranges = Array.Empty(); + } + + public void Add(int startOffset, int endOffset) + { + if (_count == _capacity) + { + int newCapacity = _count * 2; + + if (newCapacity == 0) + { + newCapacity = 1; + } + + Array.Resize(ref _ranges, newCapacity); + + _capacity = newCapacity; + } + + _ranges[_count++] = new(startOffset, endOffset); + } + + public readonly MatchRangeList Deduplicate() + { + MatchRangeList output = new(); + + if (_count != 0) + { + int prevStartOffset = _ranges[0].StartOffset; + int prevEndOffset = _ranges[0].EndOffset; + + for (int index = 1; index < _count; index++) + { + int currStartOffset = _ranges[index].StartOffset; + int currEndOffset = _ranges[index].EndOffset; + + if (prevStartOffset == currStartOffset) + { + if (prevEndOffset <= currEndOffset) + { + prevEndOffset = currEndOffset; + } + } + else if (prevEndOffset <= currStartOffset) + { + output.Add(prevStartOffset, prevEndOffset); + + prevStartOffset = currStartOffset; + prevEndOffset = currEndOffset; + } + } + + output.Add(prevStartOffset, prevEndOffset); + } + + return output; + } + + public readonly int Find(int startOffset, int endOffset) + { + int baseIndex = 0; + int range = _count; + + while (range != 0) + { + MatchRange currRange = _ranges[baseIndex + (range / 2)]; + + if (currRange.StartOffset < startOffset || (currRange.StartOffset == startOffset && currRange.EndOffset < endOffset)) + { + int nextHalf = (range / 2) + 1; + baseIndex += nextHalf; + range -= nextHalf; + } + else + { + range /= 2; + } + } + + return baseIndex; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchRangeListState.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchRangeListState.cs new file mode 100644 index 00000000..1a3851fc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchRangeListState.cs @@ -0,0 +1,21 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + struct MatchRangeListState + { + public MatchRangeList MatchRanges; + + public MatchRangeListState() + { + MatchRanges = new(); + } + + public static bool AddMatch(ReadOnlySpan text, int startOffset, int endOffset, int nodeId, ref MatchRangeListState state) + { + state.MatchRanges.Add(startOffset, endOffset); + + return true; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchSimilarFormState.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchSimilarFormState.cs new file mode 100644 index 00000000..b0711f72 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchSimilarFormState.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + struct MatchSimilarFormState + { + public MatchRangeList MatchRanges; + public SimilarFormTable SimilarFormTable; + public Utf8Text CanonicalText; + public int ReplaceEndOffset; + + public MatchSimilarFormState(MatchRangeList matchRanges, SimilarFormTable similarFormTable) + { + MatchRanges = matchRanges; + SimilarFormTable = similarFormTable; + CanonicalText = new(); + ReplaceEndOffset = 0; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchState.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchState.cs new file mode 100644 index 00000000..2a701e05 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/MatchState.cs @@ -0,0 +1,49 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + readonly ref struct MatchState + { + public readonly Span OriginalText; + public readonly Span ConvertedText; + public readonly ReadOnlySpan DeltaTable; + public readonly ref int MaskedCount; + public readonly MaskMode MaskMode; + public readonly Sbv NoSeparatorMap; + public readonly AhoCorasick DelimitedWordsTrie; + + public MatchState( + Span originalText, + Span convertedText, + ReadOnlySpan deltaTable, + ref int maskedCount, + MaskMode maskMode, + Sbv noSeparatorMap = null, + AhoCorasick delimitedWordsTrie = null) + { + OriginalText = originalText; + ConvertedText = convertedText; + DeltaTable = deltaTable; + MaskedCount = ref maskedCount; + MaskMode = maskMode; + NoSeparatorMap = noSeparatorMap; + DelimitedWordsTrie = delimitedWordsTrie; + } + + public readonly (int, int) GetOriginalRange(int convertedStartOffest, int convertedEndOffset) + { + int originalStartOffset = 0; + int originalEndOffset = 0; + + for (int index = 0; index < convertedEndOffset; index++) + { + int byteLength = Math.Abs(DeltaTable[index]); + + originalStartOffset += index < convertedStartOffest ? byteLength : 0; + originalEndOffset += byteLength; + } + + return (originalStartOffset, originalEndOffset); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/ProfanityFilter.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/ProfanityFilter.cs new file mode 100644 index 00000000..20adf16a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/ProfanityFilter.cs @@ -0,0 +1,886 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Fs; +using System; +using System.Buffers.Binary; +using System.Numerics; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + class ProfanityFilter : ProfanityFilterBase, IDisposable + { + private const int MaxBufferLength = 0x800; + private const int MaxUtf8CharacterLength = 4; + private const int MaxUtf8Characters = MaxBufferLength / MaxUtf8CharacterLength; + private const int RegionsCount = 16; + private const int MountCacheSize = 0x2000; + + private readonly ContentsReader _contentsReader; + + public ProfanityFilter(IFsClient fsClient) + { + _contentsReader = new(fsClient); + } + + public Result Initialize() + { + return _contentsReader.Initialize(MountCacheSize); + } + + public override Result Reload() + { + return _contentsReader.Reload(); + } + + public override Result GetContentVersion(out uint version) + { + version = 0; + + Result result = _contentsReader.GetVersionDataSize(out long size); + if (result.IsFailure && size != 4) + { + return Result.Success; + } + + Span data = stackalloc byte[4]; + result = _contentsReader.GetVersionData(data); + if (result.IsFailure) + { + return Result.Success; + } + + version = BinaryPrimitives.ReadUInt32BigEndian(data); + + return Result.Success; + } + + public override Result CheckProfanityWords(out uint checkMask, ReadOnlySpan word, uint regionMask, ProfanityFilterOption option) + { + checkMask = 0; + + int length = word.IndexOf((byte)0); + if (length >= 0) + { + word = word[..length]; + } + + UTF8Encoding encoding = new(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + + string decodedWord; + + try + { + decodedWord = encoding.GetString(word); + } + catch (ArgumentException) + { + return NgcResult.InvalidUtf8Encoding; + } + + return CheckProfanityWordsMultiRegionImpl(ref checkMask, decodedWord, regionMask, option); + } + + private Result CheckProfanityWordsMultiRegionImpl(ref uint checkMask, string word, uint regionMask, ProfanityFilterOption option) + { + // Check using common dictionary. + Result result = CheckProfanityWordsImpl(ref checkMask, word, 0, option); + if (result.IsFailure) + { + return result; + } + + if (checkMask != 0) + { + checkMask = (ushort)(regionMask | option.SystemRegionMask); + } + + // Check using region specific dictionaries if needed. + for (int regionIndex = 0; regionIndex < RegionsCount; regionIndex++) + { + if (((regionMask | option.SystemRegionMask) & (1 << regionIndex)) != 0) + { + result = CheckProfanityWordsImpl(ref checkMask, word, 1u << regionIndex, option); + if (result.IsFailure) + { + return result; + } + } + } + + return Result.Success; + } + + private Result CheckProfanityWordsImpl(ref uint checkMask, string word, uint regionMask, ProfanityFilterOption option) + { + ConvertUserInputForWord(out string convertedWord, word); + + if (IsIncludesAtSign(convertedWord)) + { + checkMask |= regionMask != 0 ? regionMask : option.SystemRegionMask; + } + + byte[] utf8Text = Encoding.UTF8.GetBytes(convertedWord); + byte[] convertedText = new byte[utf8Text.Length + 5]; + + utf8Text.CopyTo(convertedText.AsSpan().Slice(2, utf8Text.Length)); + + convertedText[0] = (byte)'\\'; + convertedText[1] = (byte)'b'; + convertedText[2 + utf8Text.Length] = (byte)'\\'; + convertedText[3 + utf8Text.Length] = (byte)'b'; + convertedText[4 + utf8Text.Length] = 0; + + int regionIndex = (ushort)regionMask != 0 ? BitOperations.TrailingZeroCount(regionMask) : -1; + + Result result = _contentsReader.ReadDictionaries(out AhoCorasick partialWordsTrie, out _, out AhoCorasick delimitedWordsTrie, regionIndex); + if (result.IsFailure) + { + return result; + } + + if ((checkMask & regionMask) == 0) + { + MatchCheckState state = new(checkMask, regionMask, option); + + partialWordsTrie.Match(convertedText, MatchCheck, ref state); + delimitedWordsTrie.Match(convertedText, MatchCheck, ref state); + + checkMask = state.CheckMask; + } + + return Result.Success; + } + + public override Result MaskProfanityWordsInText(out int maskedWordsCount, Span text, uint regionMask, ProfanityFilterOption option) + { + maskedWordsCount = 0; + + Span output = text; + Span convertedText = new byte[MaxBufferLength]; + Span deltaTable = new sbyte[MaxBufferLength]; + + int nullTerminatorIndex = GetUtf8Length(out _, text, MaxUtf8Characters); + + // Ensure that the text has a null terminator if we can. + // If the text is too long, it will be truncated. + byte replacedCharacter = 0; + + if (nullTerminatorIndex > 0 && nullTerminatorIndex < text.Length) + { + replacedCharacter = text[nullTerminatorIndex]; + text[nullTerminatorIndex] = 0; + } + + // Truncate the text if needed. + int length = text.IndexOf((byte)0); + if (length >= 0) + { + text = text[..length]; + } + + // If requested, mask e-mail addresses. + if (option.SkipAtSignCheck == SkipMode.DoNotSkip) + { + maskedWordsCount += FilterAtSign(text, option.MaskMode); + text = MaskText(text); + } + + // Convert the text to lower case, required for string matching. + ConvertUserInputForText(convertedText, deltaTable, text); + + // Mask words for common and requested regions. + Result result = MaskProfanityWordsInTextMultiRegion(ref maskedWordsCount, ref text, ref convertedText, deltaTable, regionMask, option); + if (result.IsFailure) + { + return result; + } + + // If requested, also try to match and mask the canonicalized string. + if (option.Flags != ProfanityFilterFlags.None) + { + result = MaskProfanityWordsInTextCanonicalizedMultiRegion(ref maskedWordsCount, text, regionMask, option); + if (result.IsFailure) + { + return result; + } + } + + // If we received more text than we can process, copy unprocessed portion to the end of the new text. + if (replacedCharacter != 0) + { + length = text.IndexOf((byte)0); + + if (length < 0) + { + length = text.Length; + } + + output[length++] = replacedCharacter; + int unprocessedLength = output.Length - nullTerminatorIndex - 1; + output.Slice(nullTerminatorIndex + 1, unprocessedLength).CopyTo(output.Slice(length, unprocessedLength)); + } + + return Result.Success; + } + + private Result MaskProfanityWordsInTextMultiRegion( + ref int maskedWordsCount, + ref Span originalText, + ref Span convertedText, + Span deltaTable, + uint regionMask, + ProfanityFilterOption option) + { + // Filter using common dictionary. + Result result = MaskProfanityWordsInTextImpl(ref maskedWordsCount, ref originalText, ref convertedText, deltaTable, -1, option); + if (result.IsFailure) + { + return result; + } + + // Filter using region specific dictionaries if needed. + for (int regionIndex = 0; regionIndex < RegionsCount; regionIndex++) + { + if (((regionMask | option.SystemRegionMask) & (1 << regionIndex)) != 0) + { + result = MaskProfanityWordsInTextImpl(ref maskedWordsCount, ref originalText, ref convertedText, deltaTable, regionIndex, option); + if (result.IsFailure) + { + return result; + } + } + } + + return Result.Success; + } + + private Result MaskProfanityWordsInTextImpl( + ref int maskedWordsCount, + ref Span originalText, + ref Span convertedText, + Span deltaTable, + int regionIndex, + ProfanityFilterOption option) + { + Result result = _contentsReader.ReadDictionaries( + out AhoCorasick partialWordsTrie, + out AhoCorasick completeWordsTrie, + out AhoCorasick delimitedWordsTrie, + regionIndex); + + if (result.IsFailure) + { + return result; + } + + // Match single words. + + MatchState state = new(originalText, convertedText, deltaTable, ref maskedWordsCount, option.MaskMode); + + partialWordsTrie.Match(convertedText, MatchSingleWord, ref state); + + MaskText(ref originalText, ref convertedText, deltaTable); + + // Match single words and phrases. + // We remove word separators on the string used for the match. + + Span noSeparatorText = new byte[originalText.Length]; + Sbv noSeparatorMap = new(convertedText.Length); + noSeparatorText = RemoveWordSeparators(noSeparatorText, convertedText, noSeparatorMap); + + state = new( + originalText, + convertedText, + deltaTable, + ref maskedWordsCount, + option.MaskMode, + noSeparatorMap, + delimitedWordsTrie); + + partialWordsTrie.Match(noSeparatorText, MatchMultiWord, ref state); + + MaskText(ref originalText, ref convertedText, deltaTable); + + // Match whole words, which must be surrounded by word separators. + + noSeparatorText = new byte[originalText.Length]; + noSeparatorMap = new(convertedText.Length); + noSeparatorText = RemoveWordSeparators(noSeparatorText, convertedText, noSeparatorMap); + + state = new( + originalText, + convertedText, + deltaTable, + ref maskedWordsCount, + option.MaskMode, + noSeparatorMap, + delimitedWordsTrie); + + completeWordsTrie.Match(noSeparatorText, MatchDelimitedWord, ref state); + + MaskText(ref originalText, ref convertedText, deltaTable); + + return Result.Success; + } + + private static void MaskText(ref Span originalText, ref Span convertedText, Span deltaTable) + { + originalText = MaskText(originalText); + UpdateDeltaTable(deltaTable, convertedText); + convertedText = MaskText(convertedText); + } + + private Result MaskProfanityWordsInTextCanonicalizedMultiRegion(ref int maskedWordsCount, Span text, uint regionMask, ProfanityFilterOption option) + { + // Filter using common dictionary. + Result result = MaskProfanityWordsInTextCanonicalized(ref maskedWordsCount, text, 0, option); + if (result.IsFailure) + { + return result; + } + + // Filter using region specific dictionaries if needed. + for (int index = 0; index < RegionsCount; index++) + { + if ((((regionMask | option.SystemRegionMask) >> index) & 1) != 0) + { + result = MaskProfanityWordsInTextCanonicalized(ref maskedWordsCount, text, 1u << index, option); + if (result.IsFailure) + { + return result; + } + } + } + + return Result.Success; + } + + private Result MaskProfanityWordsInTextCanonicalized(ref int maskedWordsCount, Span text, uint regionMask, ProfanityFilterOption option) + { + Utf8Text maskedText = new(); + Utf8ParseResult parseResult = Utf8Text.Create(out Utf8Text inputText, text); + if (parseResult != Utf8ParseResult.Success) + { + return NgcResult.InvalidUtf8Encoding; + } + + ReadOnlySpan prevCharacter = ReadOnlySpan.Empty; + + int charStartIndex = 0; + + for (int charEndIndex = 1; charStartIndex < inputText.CharacterCount;) + { + ReadOnlySpan nextCharacter = charEndIndex < inputText.CharacterCount + ? inputText.AsSubstring(charEndIndex, charEndIndex + 1) + : ReadOnlySpan.Empty; + + Result result = CheckProfanityWordsInTextCanonicalized( + out bool matched, + inputText.AsSubstring(charStartIndex, charEndIndex), + prevCharacter, + nextCharacter, + regionMask, + option); + + if (result.IsFailure && result != NgcResult.InvalidSize) + { + return result; + } + + if (matched) + { + // We had a match, we know where it ends, now we need to find where it starts. + + int previousCharStartIndex = charStartIndex; + + for (; charStartIndex < charEndIndex; charStartIndex++) + { + result = CheckProfanityWordsInTextCanonicalized( + out matched, + inputText.AsSubstring(charStartIndex, charEndIndex), + prevCharacter, + nextCharacter, + regionMask, + option); + + if (result.IsFailure && result != NgcResult.InvalidSize) + { + return result; + } + + // When we get past the start of the matched substring, the match will fail, + // so that's when we know we found the start. + if (!matched) + { + break; + } + } + + // Append substring before the match start. + maskedText = maskedText.Append(inputText.AsSubstring(previousCharStartIndex, charStartIndex - 1)); + + // Mask matched substring with asterisks. + if (option.MaskMode == MaskMode.ReplaceByOneCharacter) + { + maskedText = maskedText.Append("*"u8); + prevCharacter = "*"u8; + } + else if (option.MaskMode == MaskMode.Overwrite && charStartIndex <= charEndIndex) + { + int maskLength = charEndIndex - charStartIndex + 1; + + while (maskLength-- > 0) + { + maskedText = maskedText.Append("*"u8); + } + + prevCharacter = "*"u8; + } + + charStartIndex = charEndIndex; + maskedWordsCount++; + } + + if (charEndIndex < inputText.CharacterCount) + { + charEndIndex++; + } + else if (charStartIndex < inputText.CharacterCount) + { + prevCharacter = inputText.AsSubstring(charStartIndex, charStartIndex + 1); + maskedText = maskedText.Append(prevCharacter); + charStartIndex++; + } + } + + // Replace text with the masked text. + maskedText.CopyTo(text); + + return Result.Success; + } + + private Result CheckProfanityWordsInTextCanonicalized( + out bool matched, + ReadOnlySpan text, + ReadOnlySpan prevCharacter, + ReadOnlySpan nextCharacter, + uint regionMask, + ProfanityFilterOption option) + { + matched = false; + + Span convertedText = new byte[MaxBufferLength + 1]; + text.CopyTo(convertedText[..text.Length]); + + Result result; + + if (text.Length > 0) + { + // If requested, normalize. + // This will convert different encodings for the same character in their canonical encodings. + if (option.Flags.HasFlag(ProfanityFilterFlags.MatchNormalizedFormKC)) + { + Utf8ParseResult parseResult = Utf8Util.NormalizeFormKC(convertedText, convertedText); + + if (parseResult != Utf8ParseResult.Success) + { + return NgcResult.InvalidUtf8Encoding; + } + } + + // Convert to lower case. + ConvertUserInputForText(convertedText, Span.Empty, convertedText); + + // If requested, also try to replace similar characters with their canonical form. + // For example, vv is similar to w, and 1 or | is similar to i. + if (option.Flags.HasFlag(ProfanityFilterFlags.MatchSimilarForm)) + { + result = ConvertInputTextFromSimilarForm(convertedText, convertedText); + if (result.IsFailure) + { + return result; + } + } + + int length = convertedText.IndexOf((byte)0); + if (length >= 0) + { + convertedText = convertedText[..length]; + } + } + + int regionIndex = (ushort)regionMask != 0 ? BitOperations.TrailingZeroCount(regionMask) : -1; + + result = _contentsReader.ReadDictionaries( + out AhoCorasick partialWordsTrie, + out AhoCorasick completeWordsTrie, + out AhoCorasick delimitedWordsTrie, + regionIndex); + + if (result.IsFailure) + { + return result; + } + + result = ContentsReader.ReadNotSeparatorDictionary(out AhoCorasick notSeparatorTrie); + if (result.IsFailure) + { + return result; + } + + // Match single words. + + bool trieMatched = false; + + partialWordsTrie.Match(convertedText, MatchSimple, ref trieMatched); + + if (trieMatched) + { + matched = true; + + return Result.Success; + } + + // Match single words and phrases. + // We remove word separators on the string used for the match. + + Span noSeparatorText = new byte[text.Length]; + Sbv noSeparatorMap = new(convertedText.Length); + noSeparatorText = RemoveWordSeparators(noSeparatorText, convertedText, noSeparatorMap, notSeparatorTrie); + + trieMatched = false; + + partialWordsTrie.Match(noSeparatorText, MatchSimple, ref trieMatched); + + if (trieMatched) + { + matched = true; + + return Result.Success; + } + + // Match whole words, which must be surrounded by word separators. + + bool prevCharIsWordSeparator = prevCharacter.Length == 0 || IsWordSeparator(prevCharacter, notSeparatorTrie); + bool nextCharIsWordSeparator = nextCharacter.Length == 0 || IsWordSeparator(nextCharacter, notSeparatorTrie); + + MatchDelimitedState state = new(prevCharIsWordSeparator, nextCharIsWordSeparator, noSeparatorMap, delimitedWordsTrie); + + completeWordsTrie.Match(noSeparatorText, MatchDelimitedWordSimple, ref state); + + if (state.Matched) + { + matched = true; + } + + return Result.Success; + } + + private Result ConvertInputTextFromSimilarForm(Span convertedText, ReadOnlySpan text) + { + int length = text.IndexOf((byte)0); + if (length >= 0) + { + text = text[..length]; + } + + Result result = _contentsReader.ReadSimilarFormDictionary(out AhoCorasick similarFormTrie); + if (result.IsFailure) + { + return result; + } + + result = _contentsReader.ReadSimilarFormTable(out SimilarFormTable similarFormTable); + if (result.IsFailure) + { + return result; + } + + // Find all characters that have a similar form. + MatchRangeListState listState = new(); + + similarFormTrie.Match(text, MatchRangeListState.AddMatch, ref listState); + + // Filter found match ranges. + // Because some similar form strings are a subset of others, we need to remove overlapping matches. + // For example, | can be replaced with i, but |-| can be replaced with h. + // We prefer the latter match (|-|) because it is more specific. + MatchRangeList deduplicatedMatches = listState.MatchRanges.Deduplicate(); + + MatchSimilarFormState state = new(deduplicatedMatches, similarFormTable); + + similarFormTrie.Match(text, MatchAndReplace, ref state); + + // Append remaining characters. + state.CanonicalText = state.CanonicalText.Append(text[state.ReplaceEndOffset..]); + + // Set canonical text to output. + ReadOnlySpan canonicalText = state.CanonicalText.AsSpan(); + canonicalText.CopyTo(convertedText[..canonicalText.Length]); + convertedText[canonicalText.Length] = 0; + + return Result.Success; + } + + private static bool MatchCheck(ReadOnlySpan text, int matchStartOffset, int matchEndOffset, int nodeId, ref MatchCheckState state) + { + state.CheckMask |= state.RegionMask != 0 ? state.RegionMask : state.Option.SystemRegionMask; + + return true; + } + + private static bool MatchSingleWord(ReadOnlySpan text, int matchStartOffset, int matchEndOffset, int nodeId, ref MatchState state) + { + MatchCommon(ref state, matchStartOffset, matchEndOffset); + + return true; + } + + private static bool MatchMultiWord(ReadOnlySpan text, int matchStartOffset, int matchEndOffset, int nodeId, ref MatchState state) + { + int convertedStartOffset = state.NoSeparatorMap.Set.Select0(matchStartOffset); + int convertedEndOffset = state.NoSeparatorMap.Set.Select0(matchEndOffset); + + if (convertedEndOffset < 0) + { + convertedEndOffset = state.NoSeparatorMap.Set.BitVector.BitLength; + } + + int endOffsetBeforeSeparator = TrimEnd(state.ConvertedText, convertedEndOffset); + + MatchCommon(ref state, convertedStartOffset, endOffsetBeforeSeparator); + + return true; + } + + private static bool MatchDelimitedWord(ReadOnlySpan text, int matchStartOffset, int matchEndOffset, int nodeId, ref MatchState state) + { + int convertedStartOffset = state.NoSeparatorMap.Set.Select0(matchStartOffset); + int convertedEndOffset = state.NoSeparatorMap.Set.Select0(matchEndOffset); + + if (convertedEndOffset < 0) + { + convertedEndOffset = state.NoSeparatorMap.Set.BitVector.BitLength; + } + + int endOffsetBeforeSeparator = TrimEnd(state.ConvertedText, convertedEndOffset); + + Span delimitedText = new byte[64]; + + // If the word is prefixed by a word separator, insert "\b" delimiter, otherwise insert "a" delimitar. + // The start of the string is also considered a "word separator". + + bool startIsPrefixedByWordSeparator = + convertedStartOffset == 0 || + IsPrefixedByWordSeparator(state.ConvertedText, convertedStartOffset); + + int delimitedTextOffset = 0; + + if (startIsPrefixedByWordSeparator) + { + delimitedText[delimitedTextOffset++] = (byte)'\\'; + delimitedText[delimitedTextOffset++] = (byte)'b'; + } + else + { + delimitedText[delimitedTextOffset++] = (byte)'a'; + } + + // Copy the word to our temporary buffer used for the next match. + + int matchLength = matchEndOffset - matchStartOffset; + + text.Slice(matchStartOffset, matchLength).CopyTo(delimitedText.Slice(delimitedTextOffset, matchLength)); + + delimitedTextOffset += matchLength; + + // If the word is suffixed by a word separator, insert "\b" delimiter, otherwise insert "a" delimiter. + // The end of the string is also considered a "word separator". + + bool endIsSuffixedByWordSeparator = + endOffsetBeforeSeparator == state.NoSeparatorMap.Set.BitVector.BitLength || + state.ConvertedText[endOffsetBeforeSeparator] == 0 || + IsWordSeparator(state.ConvertedText, endOffsetBeforeSeparator); + + if (endIsSuffixedByWordSeparator) + { + delimitedText[delimitedTextOffset++] = (byte)'\\'; + delimitedText[delimitedTextOffset++] = (byte)'b'; + } + else + { + delimitedText[delimitedTextOffset++] = (byte)'a'; + } + + // Create our temporary match state for the next match. + bool matched = false; + + // Insert the null terminator. + delimitedText[delimitedTextOffset] = 0; + + // Check if the delimited word is on the dictionary. + state.DelimitedWordsTrie.Match(delimitedText, MatchSimple, ref matched); + + // If we have a match, mask the word. + if (matched) + { + MatchCommon(ref state, convertedStartOffset, endOffsetBeforeSeparator); + } + + return true; + } + + private static void MatchCommon(ref MatchState state, int matchStartOffset, int matchEndOffset) + { + // If length is zero or negative, there was no match. + if (matchStartOffset >= matchEndOffset) + { + return; + } + + Span convertedText = state.ConvertedText; + Span originalText = state.OriginalText; + + int matchLength = matchEndOffset - matchStartOffset; + int characterCount = Encoding.UTF8.GetCharCount(state.ConvertedText.Slice(matchStartOffset, matchLength)); + + // Exit early if there are no character, or if we matched past the end of the string. + if (characterCount == 0 || + (matchStartOffset > 0 && convertedText[matchStartOffset - 1] == 0) || + (matchStartOffset > 1 && convertedText[matchStartOffset - 2] == 0)) + { + return; + } + + state.MaskedCount++; + + (int originalStartOffset, int originalEndOffset) = state.GetOriginalRange(matchStartOffset, matchEndOffset); + + PreMaskCharacterRange(convertedText, matchStartOffset, matchEndOffset, state.MaskMode, characterCount); + PreMaskCharacterRange(originalText, originalStartOffset, originalEndOffset, state.MaskMode, characterCount); + } + + private static bool MatchDelimitedWordSimple(ReadOnlySpan text, int matchStartOffset, int matchEndOffset, int nodeId, ref MatchDelimitedState state) + { + int convertedStartOffset = state.NoSeparatorMap.Set.Select0(matchStartOffset); + + Span delimitedText = new byte[64]; + + // If the word is prefixed by a word separator, insert "\b" delimiter, otherwise insert "a" delimitar. + // The start of the string is also considered a "word separator". + + bool startIsPrefixedByWordSeparator = + (convertedStartOffset == 0 && state.PrevCharIsWordSeparator) || + state.NoSeparatorMap.Set.Has(convertedStartOffset - 1); + + int delimitedTextOffset = 0; + + if (startIsPrefixedByWordSeparator) + { + delimitedText[delimitedTextOffset++] = (byte)'\\'; + delimitedText[delimitedTextOffset++] = (byte)'b'; + } + else + { + delimitedText[delimitedTextOffset++] = (byte)'a'; + } + + // Copy the word to our temporary buffer used for the next match. + + int matchLength = matchEndOffset - matchStartOffset; + + text.Slice(matchStartOffset, matchLength).CopyTo(delimitedText.Slice(delimitedTextOffset, matchLength)); + + delimitedTextOffset += matchLength; + + // If the word is suffixed by a word separator, insert "\b" delimiter, otherwise insert "a" delimiter. + // The end of the string is also considered a "word separator". + + int convertedEndOffset = state.NoSeparatorMap.Set.Select0(matchEndOffset); + + bool endIsSuffixedByWordSeparator = + (convertedEndOffset < 0 && state.NextCharIsWordSeparator) || + state.NoSeparatorMap.Set.Has(convertedEndOffset - 1); + + if (endIsSuffixedByWordSeparator) + { + delimitedText[delimitedTextOffset++] = (byte)'\\'; + delimitedText[delimitedTextOffset++] = (byte)'b'; + } + else + { + delimitedText[delimitedTextOffset++] = (byte)'a'; + } + + // Create our temporary match state for the next match. + bool matched = false; + + // Insert the null terminator. + delimitedText[delimitedTextOffset] = 0; + + // Check if the delimited word is on the dictionary. + state.DelimitedWordsTrie.Match(delimitedText, MatchSimple, ref matched); + + // If we have a match, mask the word. + if (matched) + { + state.Matched = true; + } + + return !matched; + } + + private static bool MatchAndReplace(ReadOnlySpan text, int matchStartOffset, int matchEndOffset, int nodeId, ref MatchSimilarFormState state) + { + if (matchStartOffset < state.ReplaceEndOffset || state.MatchRanges.Count == 0) + { + return true; + } + + // Check if the match range exists on our list of ranges. + int rangeIndex = state.MatchRanges.Find(matchStartOffset, matchEndOffset); + + if ((uint)rangeIndex >= (uint)state.MatchRanges.Count) + { + return true; + } + + MatchRange range = state.MatchRanges[rangeIndex]; + + // We only replace if the match has the same size or is larger than an existing match on the list. + if (range.StartOffset <= matchStartOffset && + (range.StartOffset != matchStartOffset || range.EndOffset <= matchEndOffset)) + { + // Copy all characters since the last match to the output. + int endOffset = state.ReplaceEndOffset; + + if (endOffset < matchStartOffset) + { + state.CanonicalText = state.CanonicalText.Append(text[endOffset..matchStartOffset]); + } + + // Get canonical character from the similar one, and append it. + // For example, |-| is replaced with h, vv is replaced with w, etc. + ReadOnlySpan matchText = text[matchStartOffset..matchEndOffset]; + state.CanonicalText = state.CanonicalText.AppendNullTerminated(state.SimilarFormTable.FindCanonicalString(matchText)); + state.ReplaceEndOffset = matchEndOffset; + } + + return true; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _contentsReader.Dispose(); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/ProfanityFilterBase.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/ProfanityFilterBase.cs new file mode 100644 index 00000000..acfdd904 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/ProfanityFilterBase.cs @@ -0,0 +1,789 @@ +using Ryujinx.Horizon.Common; +using System; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + abstract class ProfanityFilterBase + { +#pragma warning disable IDE0230 // Use UTF-8 string literal + private static readonly byte[][] _wordSeparators = { + new byte[] { 0x0D }, + new byte[] { 0x0A }, + new byte[] { 0xC2, 0x85 }, + new byte[] { 0xE2, 0x80, 0xA8 }, + new byte[] { 0xE2, 0x80, 0xA9 }, + new byte[] { 0x09 }, + new byte[] { 0x0B }, + new byte[] { 0x0C }, + new byte[] { 0x20 }, + new byte[] { 0xEF, 0xBD, 0xA1 }, + new byte[] { 0xEF, 0xBD, 0xA4 }, + new byte[] { 0x2E }, + new byte[] { 0x2C }, + new byte[] { 0x5B }, + new byte[] { 0x21 }, + new byte[] { 0x22 }, + new byte[] { 0x23 }, + new byte[] { 0x24 }, + new byte[] { 0x25 }, + new byte[] { 0x26 }, + new byte[] { 0x27 }, + new byte[] { 0x28 }, + new byte[] { 0x29 }, + new byte[] { 0x2A }, + new byte[] { 0x2B }, + new byte[] { 0x2F }, + new byte[] { 0x3A }, + new byte[] { 0x3B }, + new byte[] { 0x3C }, + new byte[] { 0x3D }, + new byte[] { 0x3E }, + new byte[] { 0x3F }, + new byte[] { 0x5C }, + new byte[] { 0x40 }, + new byte[] { 0x5E }, + new byte[] { 0x5F }, + new byte[] { 0x60 }, + new byte[] { 0x7B }, + new byte[] { 0x7C }, + new byte[] { 0x7D }, + new byte[] { 0x7E }, + new byte[] { 0x2D }, + new byte[] { 0x5D }, + new byte[] { 0xE3, 0x80, 0x80 }, + new byte[] { 0xE3, 0x80, 0x82 }, + new byte[] { 0xE3, 0x80, 0x81 }, + new byte[] { 0xEF, 0xBC, 0x8E }, + new byte[] { 0xEF, 0xBC, 0x8C }, + new byte[] { 0xEF, 0xBC, 0xBB }, + new byte[] { 0xEF, 0xBC, 0x81 }, + new byte[] { 0xE2, 0x80, 0x9C }, + new byte[] { 0xE2, 0x80, 0x9D }, + new byte[] { 0xEF, 0xBC, 0x83 }, + new byte[] { 0xEF, 0xBC, 0x84 }, + new byte[] { 0xEF, 0xBC, 0x85 }, + new byte[] { 0xEF, 0xBC, 0x86 }, + new byte[] { 0xE2, 0x80, 0x98 }, + new byte[] { 0xE2, 0x80, 0x99 }, + new byte[] { 0xEF, 0xBC, 0x88 }, + new byte[] { 0xEF, 0xBC, 0x89 }, + new byte[] { 0xEF, 0xBC, 0x8A }, + new byte[] { 0xEF, 0xBC, 0x8B }, + new byte[] { 0xEF, 0xBC, 0x8F }, + new byte[] { 0xEF, 0xBC, 0x9A }, + new byte[] { 0xEF, 0xBC, 0x9B }, + new byte[] { 0xEF, 0xBC, 0x9C }, + new byte[] { 0xEF, 0xBC, 0x9D }, + new byte[] { 0xEF, 0xBC, 0x9E }, + new byte[] { 0xEF, 0xBC, 0x9F }, + new byte[] { 0xEF, 0xBC, 0xA0 }, + new byte[] { 0xEF, 0xBF, 0xA5 }, + new byte[] { 0xEF, 0xBC, 0xBE }, + new byte[] { 0xEF, 0xBC, 0xBF }, + new byte[] { 0xEF, 0xBD, 0x80 }, + new byte[] { 0xEF, 0xBD, 0x9B }, + new byte[] { 0xEF, 0xBD, 0x9C }, + new byte[] { 0xEF, 0xBD, 0x9D }, + new byte[] { 0xEF, 0xBD, 0x9E }, + new byte[] { 0xEF, 0xBC, 0x8D }, + new byte[] { 0xEF, 0xBC, 0xBD }, + }; +#pragma warning restore IDE0230 + + private enum SignFilterStep + { + DetectEmailStart, + DetectEmailUserAtSign, + DetectEmailDomain, + DetectEmailEnd, + } + + public abstract Result GetContentVersion(out uint version); + public abstract Result CheckProfanityWords(out uint checkMask, ReadOnlySpan word, uint regionMask, ProfanityFilterOption option); + public abstract Result MaskProfanityWordsInText(out int maskedWordsCount, Span text, uint regionMask, ProfanityFilterOption option); + public abstract Result Reload(); + + protected static bool IsIncludesAtSign(string word) + { + for (int index = 0; index < word.Length; index++) + { + if (word[index] == '\0') + { + break; + } + else if (word[index] == '@' || word[index] == '\uFF20') + { + return true; + } + } + + return false; + } + + protected static int FilterAtSign(Span text, MaskMode maskMode) + { + SignFilterStep step = SignFilterStep.DetectEmailStart; + int matchStart = 0; + int matchCount = 0; + + for (int index = 0; index < text.Length; index++) + { + byte character = text[index]; + + switch (step) + { + case SignFilterStep.DetectEmailStart: + if (char.IsAsciiLetterOrDigit((char)character)) + { + step = SignFilterStep.DetectEmailUserAtSign; + matchStart = index; + } + break; + case SignFilterStep.DetectEmailUserAtSign: + bool hasMatch = false; + + while (IsValidEmailAddressCharacter(character)) + { + hasMatch = true; + + if (index + 1 >= text.Length) + { + break; + } + + character = text[++index]; + } + + step = hasMatch && character == '@' ? SignFilterStep.DetectEmailDomain : SignFilterStep.DetectEmailStart; + break; + case SignFilterStep.DetectEmailDomain: + step = char.IsAsciiLetterOrDigit((char)character) ? SignFilterStep.DetectEmailEnd : SignFilterStep.DetectEmailStart; + break; + case SignFilterStep.DetectEmailEnd: + int domainIndex = index; + + while (index + 1 < text.Length && IsValidEmailAddressCharacter(text[++index])) + { + } + + int addressLastIndex = index - 1; + int lastIndex = 0; + bool lastIndexSet = false; + + while (matchStart < addressLastIndex) + { + character = text[addressLastIndex]; + + if (char.IsAsciiLetterOrDigit((char)character)) + { + if (!lastIndexSet) + { + lastIndexSet = true; + lastIndex = addressLastIndex; + } + } + else if (lastIndexSet) + { + break; + } + + addressLastIndex--; + } + + step = SignFilterStep.DetectEmailStart; + + if (domainIndex < addressLastIndex && character == '.') + { + PreMaskCharacterRange(text, matchStart, lastIndex + 1, maskMode, (lastIndex - matchStart) + 1); + matchCount++; + } + else + { + index = domainIndex - 1; + } + break; + } + } + + return matchCount; + } + + private static bool IsValidEmailAddressCharacter(byte character) + { + return char.IsAsciiLetterOrDigit((char)character) || character == '-' || character == '.' || character == '_'; + } + + protected static void PreMaskCharacterRange(Span text, int startOffset, int endOffset, MaskMode maskMode, int characterCount) + { + int byteLength = endOffset - startOffset; + + if (byteLength == 1) + { + text[startOffset] = 0xc1; + } + else if (byteLength == 2) + { + if (maskMode == MaskMode.Overwrite && Encoding.UTF8.GetCharCount(text.Slice(startOffset, 2)) != 1) + { + text[startOffset] = 0xc1; + text[startOffset + 1] = 0xc1; + } + else if (maskMode == MaskMode.Overwrite || maskMode == MaskMode.ReplaceByOneCharacter) + { + text[startOffset] = 0xc0; + text[startOffset + 1] = 0xc0; + } + } + else + { + text[startOffset++] = 0; + + if (byteLength >= 0xff) + { + int fillLength = (byteLength - 0xff) / 0xff + 1; + + text.Slice(startOffset++, fillLength).Fill(0xff); + + byteLength -= fillLength * 0xff; + startOffset += fillLength; + } + + text[startOffset++] = (byte)byteLength; + + if (maskMode == MaskMode.ReplaceByOneCharacter) + { + text[startOffset++] = 1; + } + else if (maskMode == MaskMode.Overwrite) + { + if (characterCount >= 0xff) + { + int fillLength = (characterCount - 0xff) / 0xff + 1; + + text.Slice(startOffset, fillLength).Fill(0xff); + + characterCount -= fillLength * 0xff; + startOffset += fillLength; + } + + text[startOffset++] = (byte)characterCount; + } + + if (startOffset < endOffset) + { + text[startOffset..endOffset].Fill(0xc1); + } + } + } + + protected static void ConvertUserInputForWord(out string outputText, string inputText) + { + outputText = inputText.ToLowerInvariant(); + } + + protected static void ConvertUserInputForText(Span outputText, Span deltaTable, ReadOnlySpan inputText) + { + int outputIndex = 0; + int deltaTableIndex = 0; + + for (int index = 0; index < inputText.Length;) + { + byte character = inputText[index]; + bool isInvalid = false; + int characterByteLength = 1; + + if (character == 0xef && index + 4 < inputText.Length) + { + if (((inputText[index + 1] == 0xbd && inputText[index + 2] >= 0xa6 && inputText[index + 2] < 0xe6) || + (inputText[index + 1] == 0xbe && inputText[index + 2] >= 0x80 && inputText[index + 2] < 0xa0)) && + inputText[index + 3] == 0xef && + inputText[index + 4] == 0xbe) + { + characterByteLength = 6; + } + else + { + characterByteLength = 3; + } + } + else if ((character & 0x80) != 0) + { + if (character >= 0xc2 && character < 0xe0) + { + characterByteLength = 2; + } + else if ((character & 0xf0) == 0xe0) + { + characterByteLength = 3; + } + else if ((character & 0xf8) == 0xf0) + { + characterByteLength = 4; + } + else + { + isInvalid = true; + } + } + + isInvalid |= index + characterByteLength > inputText.Length; + + string str = null; + + if (!isInvalid) + { + str = Encoding.UTF8.GetString(inputText.Slice(index, characterByteLength)); + + foreach (char chr in str) + { + if (chr == '\uFFFD') + { + isInvalid = true; + break; + } + } + } + + int convertedByteLength = 1; + + if (isInvalid) + { + characterByteLength = 1; + outputText[outputIndex++] = inputText[index]; + } + else + { + convertedByteLength = Encoding.UTF8.GetBytes(str.ToLowerInvariant().AsSpan(), outputText[outputIndex..]); + outputIndex += convertedByteLength; + } + + if (deltaTable.Length != 0 && convertedByteLength != 0) + { + // Calculate how many bytes we need to advance for each converted byte to match + // the character on the original text. + // The official service does this as part of the conversion (to lower case) process, + // but since we use .NET for that here, this is done separately. + + int distribution = characterByteLength / convertedByteLength; + + deltaTable[deltaTableIndex++] = (sbyte)(characterByteLength - distribution * convertedByteLength + distribution); + + for (int byteIndex = 1; byteIndex < convertedByteLength; byteIndex++) + { + deltaTable[deltaTableIndex++] = (sbyte)distribution; + } + } + + index += characterByteLength; + } + + if (outputIndex < outputText.Length) + { + outputText[outputIndex] = 0; + } + } + + protected static Span MaskText(Span text) + { + if (text.Length == 0) + { + return text; + } + + for (int index = 0; index < text.Length; index++) + { + byte character = text[index]; + + if (character == 0xc1) + { + text[index] = (byte)'*'; + } + else if (character == 0xc0) + { + if (index + 1 < text.Length && text[index + 1] == 0xc0) + { + text[index++] = (byte)'*'; + text[index] = 0; + } + } + else if (character == 0 && index + 1 < text.Length) + { + // There are two sequences of 0xFF followed by another value. + // The first indicates the length of the sub-string to replace in bytes. + // The second indicates the character count. + + int lengthSequenceIndex = index + 1; + int byteLength = CountMaskLengthBytes(text, ref lengthSequenceIndex); + int characterCount = CountMaskLengthBytes(text, ref lengthSequenceIndex); + + if (byteLength != 0) + { + for (int replaceIndex = 0; replaceIndex < byteLength; replaceIndex++) + { + text[index++] = (byte)(replaceIndex < characterCount ? '*' : '\0'); + } + + index--; + } + } + } + + // Move null-terminators to the end. + MoveZeroValuesToEnd(text); + + // Find new length of the text. + int length = text.IndexOf((byte)0); + + if (length >= 0) + { + return text[..length]; + } + + return text; + } + + protected static void UpdateDeltaTable(Span deltaTable, ReadOnlySpan text) + { + if (text.Length == 0) + { + return; + } + + // Update values to account for the characters that will be removed. + for (int index = 0; index < text.Length; index++) + { + byte character = text[index]; + + if (character == 0 && index + 1 < text.Length) + { + // There are two sequences of 0xFF followed by another value. + // The first indicates the length of the sub-string to replace in bytes. + // The second indicates the character count. + + int lengthSequenceIndex = index + 1; + int byteLength = CountMaskLengthBytes(text, ref lengthSequenceIndex); + int characterCount = CountMaskLengthBytes(text, ref lengthSequenceIndex); + + if (byteLength != 0) + { + for (int replaceIndex = 0; replaceIndex < byteLength; replaceIndex++) + { + deltaTable[index++] = (sbyte)(replaceIndex < characterCount ? 1 : 0); + } + } + } + } + + // Move zero values of the removed bytes to the end. + MoveZeroValuesToEnd(MemoryMarshal.Cast(deltaTable)); + } + + private static int CountMaskLengthBytes(ReadOnlySpan text, ref int index) + { + int totalLength = 0; + + for (; index < text.Length; index++) + { + int length = text[index]; + totalLength += length; + + if (length != 0xff) + { + index++; + break; + } + } + + return totalLength; + } + + private static void MoveZeroValuesToEnd(Span text) + { + for (int index = 0; index < text.Length; index++) + { + int nullCount = 0; + + for (; index + nullCount < text.Length; nullCount++) + { + byte character = text[index + nullCount]; + if (character != 0) + { + break; + } + } + + if (nullCount != 0) + { + int fillLength = text.Length - (index + nullCount); + + text[(index + nullCount)..].CopyTo(text.Slice(index, fillLength)); + text.Slice(index + fillLength, nullCount).Clear(); + } + } + } + + protected static Span RemoveWordSeparators(Span output, ReadOnlySpan input, Sbv map) + { + int outputIndex = 0; + + if (map.Set.BitVector.BitLength != 0) + { + for (int index = 0; index < input.Length; index++) + { + bool isWordSeparator = false; + + for (int separatorIndex = 0; separatorIndex < _wordSeparators.Length; separatorIndex++) + { + ReadOnlySpan separator = _wordSeparators[separatorIndex]; + + if (index + separator.Length < input.Length && input.Slice(index, separator.Length).SequenceEqual(separator)) + { + map.Set.TurnOn(index, separator.Length); + + index += separator.Length - 1; + isWordSeparator = true; + break; + } + } + + if (!isWordSeparator) + { + output[outputIndex++] = input[index]; + } + } + } + + map.Build(); + + return output[..outputIndex]; + } + + protected static int TrimEnd(ReadOnlySpan text, int offset) + { + for (int separatorIndex = 0; separatorIndex < _wordSeparators.Length; separatorIndex++) + { + ReadOnlySpan separator = _wordSeparators[separatorIndex]; + + if (offset >= separator.Length && text.Slice(offset - separator.Length, separator.Length).SequenceEqual(separator)) + { + offset -= separator.Length; + separatorIndex = -1; + } + } + + return offset; + } + + protected static bool IsPrefixedByWordSeparator(ReadOnlySpan text, int offset) + { + for (int separatorIndex = 0; separatorIndex < _wordSeparators.Length; separatorIndex++) + { + ReadOnlySpan separator = _wordSeparators[separatorIndex]; + + if (offset >= separator.Length && text.Slice(offset - separator.Length, separator.Length).SequenceEqual(separator)) + { + return true; + } + } + + return false; + } + + protected static bool IsWordSeparator(ReadOnlySpan text, int offset) + { + for (int separatorIndex = 0; separatorIndex < _wordSeparators.Length; separatorIndex++) + { + ReadOnlySpan separator = _wordSeparators[separatorIndex]; + + if (offset + separator.Length <= text.Length && text.Slice(offset, separator.Length).SequenceEqual(separator)) + { + return true; + } + } + + return false; + } + + protected static Span RemoveWordSeparators(Span output, ReadOnlySpan input, Sbv map, AhoCorasick notSeparatorTrie) + { + int outputIndex = 0; + + if (map.Set.BitVector.BitLength != 0) + { + for (int index = 0; index < input.Length;) + { + byte character = input[index]; + int characterByteLength = 1; + + if ((character & 0x80) != 0) + { + if (character >= 0xc2 && character < 0xe0) + { + characterByteLength = 2; + } + else if ((character & 0xf0) == 0xe0) + { + characterByteLength = 3; + } + else if ((character & 0xf8) == 0xf0) + { + characterByteLength = 4; + } + } + + characterByteLength = Math.Min(characterByteLength, input.Length - index); + + bool isWordSeparator = IsWordSeparator(input.Slice(index, characterByteLength), notSeparatorTrie); + if (isWordSeparator) + { + map.Set.TurnOn(index, characterByteLength); + } + else + { + output[outputIndex++] = input[index]; + } + + index += characterByteLength; + } + } + + map.Build(); + + return output[..outputIndex]; + } + + protected static bool IsWordSeparator(ReadOnlySpan text, AhoCorasick notSeparatorTrie) + { + string str = Encoding.UTF8.GetString(text); + + if (str.Length == 0) + { + return false; + } + + char character = str[0]; + + switch (character) + { + case '\0': + case '\uD800': + case '\uDB7F': + case '\uDB80': + case '\uDBFF': + case '\uDC00': + case '\uDFFF': + return false; + case '\u02E4': + case '\u02EC': + case '\u02EE': + case '\u0374': + case '\u037A': + case '\u0559': + case '\u0640': + case '\u06E5': + case '\u06E6': + case '\u07F4': + case '\u07F5': + case '\u07FA': + case '\u1C78': + case '\u1C79': + case '\u1C7A': + case '\u1C7B': + case '\u1C7C': + case '\uA4F8': + case '\uA4F9': + case '\uA4FA': + case '\uA4FB': + case '\uA4FC': + case '\uA4FD': + case '\uFF70': + case '\uFF9A': + case '\uFF9B': + return true; + } + + bool matched = false; + + notSeparatorTrie.Match(text, MatchSimple, ref matched); + + if (!matched) + { + switch (char.GetUnicodeCategory(character)) + { + case UnicodeCategory.NonSpacingMark: + case UnicodeCategory.SpacingCombiningMark: + case UnicodeCategory.EnclosingMark: + case UnicodeCategory.SpaceSeparator: + case UnicodeCategory.LineSeparator: + case UnicodeCategory.ParagraphSeparator: + case UnicodeCategory.Control: + case UnicodeCategory.Format: + case UnicodeCategory.Surrogate: + case UnicodeCategory.PrivateUse: + case UnicodeCategory.ConnectorPunctuation: + case UnicodeCategory.DashPunctuation: + case UnicodeCategory.OpenPunctuation: + case UnicodeCategory.ClosePunctuation: + case UnicodeCategory.InitialQuotePunctuation: + case UnicodeCategory.FinalQuotePunctuation: + case UnicodeCategory.OtherPunctuation: + case UnicodeCategory.MathSymbol: + case UnicodeCategory.CurrencySymbol: + return true; + } + } + + return false; + } + + protected static int GetUtf8Length(out int characterCount, ReadOnlySpan text, int maxCharacters) + { + int index; + + for (index = 0, characterCount = 0; index < text.Length && characterCount < maxCharacters; characterCount++) + { + byte character = text[index]; + int characterByteLength; + + if ((character & 0x80) != 0 || character == 0) + { + if (character >= 0xc2 && character < 0xe0) + { + characterByteLength = 2; + } + else if ((character & 0xf0) == 0xe0) + { + characterByteLength = 3; + } + else if ((character & 0xf8) == 0xf0) + { + characterByteLength = 4; + } + else + { + index = 0; + break; + } + } + else + { + characterByteLength = 1; + } + + index += characterByteLength; + } + + return index; + } + + protected static bool MatchSimple(ReadOnlySpan text, int matchStartOffset, int matchEndOffset, int nodeId, ref bool matched) + { + matched = true; + + return false; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Sbv.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Sbv.cs new file mode 100644 index 00000000..7a003744 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Sbv.cs @@ -0,0 +1,34 @@ +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + class Sbv + { + private readonly SbvSelect _sbvSelect; + private readonly Set _set; + + public SbvSelect SbvSelect => _sbvSelect; + public Set Set => _set; + + public Sbv() + { + _sbvSelect = new(); + _set = new(); + } + + public Sbv(int length) + { + _sbvSelect = new(); + _set = new(length); + } + + public void Build() + { + _set.Build(); + _sbvSelect.Build(_set.BitVector.Array, _set.BitVector.BitLength); + } + + public bool Import(ref BinaryReader reader) + { + return _set.Import(ref reader) && _sbvSelect.Import(ref reader); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/SbvRank.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/SbvRank.cs new file mode 100644 index 00000000..4ed450f3 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/SbvRank.cs @@ -0,0 +1,162 @@ +using System; +using System.Numerics; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + class SbvRank + { + private const int BitsPerWord = Set.BitsPerWord; + private const int Rank1Entries = 8; + private const int BitsPerRank0Entry = BitsPerWord * Rank1Entries; + + private uint[] _rank0; + private byte[] _rank1; + + public SbvRank() + { + } + + public SbvRank(ReadOnlySpan bitmap, int setCapacity) + { + Build(bitmap, setCapacity); + } + + public void Build(ReadOnlySpan bitmap, int setCapacity) + { + _rank0 = new uint[CalculateRank0Length(setCapacity)]; + _rank1 = new byte[CalculateRank1Length(setCapacity)]; + + BuildRankDictionary(_rank0, _rank1, (setCapacity + BitsPerWord - 1) / BitsPerWord, bitmap); + } + + private static void BuildRankDictionary(Span rank0, Span rank1, int length, ReadOnlySpan bitmap) + { + uint rank0Count; + uint rank1Count = 0; + + for (int index = 0; index < length; index++) + { + if ((index % Rank1Entries) != 0) + { + rank0Count = rank0[index / Rank1Entries]; + } + else + { + rank0[index / Rank1Entries] = rank1Count; + rank0Count = rank1Count; + } + + rank1[index] = (byte)(rank1Count - rank0Count); + + rank1Count += (uint)BitOperations.PopCount(bitmap[index]); + } + } + + public bool Import(ref BinaryReader reader, int setCapacity) + { + if (setCapacity == 0) + { + return true; + } + + int rank0Length = CalculateRank0Length(setCapacity); + int rank1Length = CalculateRank1Length(setCapacity); + + return reader.AllocateAndReadArray(ref _rank0, rank0Length) == rank0Length && + reader.AllocateAndReadArray(ref _rank1, rank1Length) == rank1Length; + } + + public int CalcRank1(int index, uint[] membershipBitmap) + { + int rank0Index = index / BitsPerRank0Entry; + int rank1Index = index / BitsPerWord; + + uint membershipBits = membershipBitmap[rank1Index] & (uint.MaxValue >> (BitsPerWord - 1 - (index % BitsPerWord))); + + return (int)_rank0[rank0Index] + _rank1[rank1Index] + BitOperations.PopCount(membershipBits); + } + + public int CalcSelect0(int index, int length, uint[] membershipBitmap) + { + int rank0Index; + + if (length > BitsPerRank0Entry) + { + int left = 0; + int right = (length + BitsPerRank0Entry - 1) / BitsPerRank0Entry; + + while (true) + { + int range = right - left; + if (range < 0) + { + range++; + } + + int middle = left + (range / 2); + + int foundIndex = middle * BitsPerRank0Entry - (int)_rank0[middle]; + + if ((uint)foundIndex <= (uint)index) + { + left = middle; + } + else + { + right = middle; + } + + if (right <= left + 1) + { + break; + } + } + + rank0Index = left; + } + else + { + rank0Index = 0; + } + + int lengthInWords = (length + BitsPerWord - 1) / BitsPerWord; + int rank1WordsCount = rank0Index == (length / BitsPerRank0Entry) && (lengthInWords % Rank1Entries) != 0 + ? lengthInWords % Rank1Entries + : Rank1Entries; + + int baseIndex = (int)_rank0[rank0Index] + rank0Index * -BitsPerRank0Entry + index; + int plainIndex; + int count; + int remainingBits; + uint membershipBits; + + for (plainIndex = rank0Index * Rank1Entries - 1, count = 0; count < rank1WordsCount; plainIndex++, count++) + { + int currentIndex = baseIndex + count * -BitsPerWord; + + if (_rank1[plainIndex + 1] + currentIndex < 0) + { + remainingBits = _rank1[plainIndex] + currentIndex + BitsPerWord; + membershipBits = ~membershipBitmap[plainIndex]; + + return plainIndex * BitsPerWord + SbvSelect.SelectPos(membershipBits, remainingBits); + } + } + + remainingBits = _rank1[plainIndex] + baseIndex + (rank1WordsCount - 1) * -BitsPerWord; + membershipBits = ~membershipBitmap[plainIndex]; + + return plainIndex * BitsPerWord + SbvSelect.SelectPos(membershipBits, remainingBits); + } + + private static int CalculateRank0Length(int setCapacity) + { + return (setCapacity / (BitsPerWord * Rank1Entries)) + 1; + } + + private static int CalculateRank1Length(int setCapacity) + { + return (setCapacity / BitsPerWord) + 1; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/SbvSelect.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/SbvSelect.cs new file mode 100644 index 00000000..c3a69fdd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/SbvSelect.cs @@ -0,0 +1,156 @@ +using System; +using System.Numerics; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + class SbvSelect + { + private uint[] _array; + private BitVector32 _bv1; + private BitVector32 _bv2; + private SbvRank _sbvRank1; + private SbvRank _sbvRank2; + + public bool Import(ref BinaryReader reader) + { + if (!reader.Read(out int arrayLength) || + reader.AllocateAndReadArray(ref _array, arrayLength) != arrayLength) + { + return false; + } + + _bv1 = new(); + _bv2 = new(); + _sbvRank1 = new(); + _sbvRank2 = new(); + + return _bv1.Import(ref reader) && + _bv2.Import(ref reader) && + _sbvRank1.Import(ref reader, _bv1.BitLength) && + _sbvRank2.Import(ref reader, _bv2.BitLength); + } + + public void Build(ReadOnlySpan bitmap, int length) + { + int lengthInWords = (length + Set.BitsPerWord - 1) / Set.BitsPerWord; + + int rank0Length = 0; + int rank1Length = 0; + + if (lengthInWords != 0) + { + for (int index = 0; index < bitmap.Length; index++) + { + uint value = bitmap[index]; + + if (value != 0) + { + rank0Length++; + rank1Length += BitOperations.PopCount(value); + } + } + } + + _bv1 = new(rank0Length); + _bv2 = new(rank1Length); + _array = new uint[rank0Length]; + + bool setSequence = false; + int arrayIndex = 0; + uint unsetCount = 0; + rank0Length = 0; + rank1Length = 0; + + if (lengthInWords != 0) + { + for (int index = 0; index < bitmap.Length; index++) + { + uint value = bitmap[index]; + + if (value != 0) + { + if (!setSequence) + { + _bv1.TurnOn(rank0Length); + _array[arrayIndex++] = unsetCount; + setSequence = true; + } + + _bv2.TurnOn(rank1Length); + + rank0Length++; + rank1Length += BitOperations.PopCount(value); + } + else + { + unsetCount++; + setSequence = false; + } + } + } + + _sbvRank1 = new(_bv1.Array, _bv1.BitLength); + _sbvRank2 = new(_bv2.Array, _bv2.BitLength); + } + + public int Select(Set set, int index) + { + if (index < _bv2.BitLength) + { + int rank1PlainIndex = _sbvRank2.CalcRank1(index, _bv2.Array); + int rank0PlainIndex = _sbvRank1.CalcRank1(rank1PlainIndex - 1, _bv1.Array); + + int value = (int)_array[rank0PlainIndex - 1] + (rank1PlainIndex - 1); + + int baseBitIndex = 0; + + if (value != 0) + { + baseBitIndex = value * 32; + + int setBvLength = set.BitVector.BitLength; + int bitIndexBounded = baseBitIndex - 1; + + if (bitIndexBounded >= setBvLength) + { + bitIndexBounded = setBvLength - 1; + } + + index -= set.SbvRank.CalcRank1(bitIndexBounded, set.BitVector.Array); + } + + return SelectPos(set.BitVector.Array[value], index) + baseBitIndex; + } + + return -1; + } + + public static int SelectPos(uint membershipBits, int bitIndex) + { + // Skips "bitIndex" set bits, and returns the bit index of the next set bit. + // If there is no set bit after skipping the specified amount, returns 32. + + int bit; + int bitCount = bitIndex; + + for (bit = 0; bit < sizeof(uint) * 8;) + { + if (((membershipBits >> bit) & 1) != 0) + { + if (bitCount-- == 0) + { + break; + } + + bit++; + } + else + { + bit += BitOperations.TrailingZeroCount(membershipBits >> bit); + } + } + + return bit; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Set.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Set.cs new file mode 100644 index 00000000..2627a51b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Set.cs @@ -0,0 +1,73 @@ +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + class Set + { + public const int BitsPerWord = 32; + + private readonly BitVector32 _bitVector; + private readonly SbvRank _sbvRank; + + public BitVector32 BitVector => _bitVector; + public SbvRank SbvRank => _sbvRank; + + public Set() + { + _bitVector = new(); + _sbvRank = new(); + } + + public Set(int length) + { + _bitVector = new(length); + _sbvRank = new(); + } + + public void Build() + { + _sbvRank.Build(_bitVector.Array, _bitVector.BitLength); + } + + public bool Import(ref BinaryReader reader) + { + return _bitVector.Import(ref reader) && _sbvRank.Import(ref reader, _bitVector.BitLength); + } + + public bool Has(int index) + { + return _bitVector.Has(index); + } + + public bool TurnOn(int index, int count) + { + return _bitVector.TurnOn(index, count); + } + + public bool TurnOn(int index) + { + return _bitVector.TurnOn(index); + } + + public int Rank1(int index) + { + if ((uint)index >= (uint)_bitVector.BitLength) + { + index = _bitVector.BitLength - 1; + } + + return _sbvRank.CalcRank1(index, _bitVector.Array); + } + + public int Select0(int index) + { + int length = _bitVector.BitLength; + int rankIndex = _sbvRank.CalcRank1(length - 1, _bitVector.Array); + + if ((uint)index < (uint)(length - rankIndex)) + { + return _sbvRank.CalcSelect0(index, length, _bitVector.Array); + } + + return -1; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/SimilarFormTable.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/SimilarFormTable.cs new file mode 100644 index 00000000..828d7a24 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/SimilarFormTable.cs @@ -0,0 +1,132 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + class SimilarFormTable + { + private int _similarTableStringLength; + private int _canonicalTableStringLength; + private int _count; + private byte[][] _similarTable; + private byte[][] _canonicalTable; + + public bool Import(ref BinaryReader reader) + { + if (!reader.Read(out _similarTableStringLength) || + !reader.Read(out _canonicalTableStringLength) || + !reader.Read(out _count)) + { + return false; + } + + _similarTable = new byte[_count][]; + _canonicalTable = new byte[_count][]; + + if (_count < 1) + { + return true; + } + + for (int tableIndex = 0; tableIndex < _count; tableIndex++) + { + if (reader.AllocateAndReadArray(ref _similarTable[tableIndex], _similarTableStringLength) != _similarTableStringLength || + reader.AllocateAndReadArray(ref _canonicalTable[tableIndex], _canonicalTableStringLength) != _canonicalTableStringLength) + { + return false; + } + } + + return true; + } + + public ReadOnlySpan FindCanonicalString(ReadOnlySpan similarFormString) + { + int lowerBound = 0; + int upperBound = _count; + + for (int charIndex = 0; charIndex < similarFormString.Length; charIndex++) + { + byte character = similarFormString[charIndex]; + + int newLowerBound = GetLowerBound(character, charIndex, lowerBound - 1, upperBound - 1); + if (newLowerBound < 0 || _similarTable[newLowerBound][charIndex] != character) + { + return ReadOnlySpan.Empty; + } + + int newUpperBound = GetUpperBound(character, charIndex, lowerBound - 1, upperBound - 1); + if (newUpperBound < 0) + { + newUpperBound = upperBound; + } + + lowerBound = newLowerBound; + upperBound = newUpperBound; + } + + return _canonicalTable[lowerBound]; + } + + private int GetLowerBound(byte character, int charIndex, int left, int right) + { + while (right - left > 1) + { + int range = right + left; + + if (range < 0) + { + range++; + } + + int middle = range / 2; + + if (character <= _similarTable[middle][charIndex]) + { + right = middle; + } + else + { + left = middle; + } + } + + if (_similarTable[right][charIndex] < character) + { + return -1; + } + + return right; + } + + private int GetUpperBound(byte character, int charIndex, int left, int right) + { + while (right - left > 1) + { + int range = right + left; + + if (range < 0) + { + range++; + } + + int middle = range / 2; + + if (_similarTable[middle][charIndex] <= character) + { + left = middle; + } + else + { + right = middle; + } + } + + if (_similarTable[right][charIndex] <= character) + { + return -1; + } + + return right; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/SparseSet.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/SparseSet.cs new file mode 100644 index 00000000..72404908 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/SparseSet.cs @@ -0,0 +1,125 @@ +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + class SparseSet + { + private const int BitsPerWord = Set.BitsPerWord; + + private ulong _rangeValuesCount; + private ulong _rangeStartValue; + private ulong _rangeEndValue; + private uint _count; + private uint _bitfieldLength; + private uint[] _bitfields; + private readonly Sbv _sbv = new(); + + public ulong RangeValuesCount => _rangeValuesCount; + public ulong RangeEndValue => _rangeEndValue; + + public bool Import(ref BinaryReader reader) + { + if (!reader.Read(out _rangeValuesCount) || + !reader.Read(out _rangeStartValue) || + !reader.Read(out _rangeEndValue) || + !reader.Read(out _count) || + !reader.Read(out _bitfieldLength) || + !reader.Read(out int arrayLength) || + reader.AllocateAndReadArray(ref _bitfields, arrayLength) != arrayLength) + { + return false; + } + + return _sbv.Import(ref reader); + } + + public bool Has(long index) + { + int plainIndex = Rank1(index); + + return plainIndex != 0 && Select1Ex(plainIndex - 1) == index; + } + + public int Rank1(long index) + { + uint count = _count; + + if ((ulong)index < _rangeStartValue || count == 0) + { + return 0; + } + + if (_rangeStartValue == (ulong)index || count < 3) + { + return 1; + } + + if (_rangeEndValue <= (ulong)index) + { + return (int)count; + } + + int left = 0; + int right = (int)count - 1; + + while (true) + { + int range = right - left; + if (range < 0) + { + range++; + } + + int middle = left + (range / 2); + + long foundIndex = Select1Ex(middle); + + if ((ulong)foundIndex <= (ulong)index) + { + left = middle; + } + else + { + right = middle; + } + + if (right <= left + 1) + { + break; + } + } + + return left + 1; + } + + public int Select1(int index) + { + return (int)Select1Ex(index); + } + + public long Select1Ex(int index) + { + if ((uint)index >= _count) + { + return -1L; + } + + int indexOffset = _sbv.SbvSelect.Select(_sbv.Set, index); + int bitfieldLength = (int)_bitfieldLength; + + int currentBitIndex = index * bitfieldLength; + int wordIndex = currentBitIndex / BitsPerWord; + int wordBitOffset = currentBitIndex % BitsPerWord; + + ulong value = _bitfields[wordIndex]; + + if (wordBitOffset + bitfieldLength > BitsPerWord) + { + value |= (ulong)_bitfields[wordIndex + 1] << 32; + } + + value >>= wordBitOffset; + value &= uint.MaxValue >> (BitsPerWord - bitfieldLength); + + return ((indexOffset - (uint)index) << bitfieldLength) + (int)value; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Utf8ParseResult.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Utf8ParseResult.cs new file mode 100644 index 00000000..54a7e73e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Utf8ParseResult.cs @@ -0,0 +1,27 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + enum Utf8ParseResult + { + Success = 0, + InvalidCharacter = 2, + InvalidPointer = 0x16, + InvalidSize = 0x22, + InvalidString = 0x54, + } + + static class Utf8ParseResultExtensions + { + public static Result ToHorizonResult(this Utf8ParseResult result) + { + return result switch + { + Utf8ParseResult.Success => Result.Success, + Utf8ParseResult.InvalidSize => NgcResult.InvalidSize, + Utf8ParseResult.InvalidString => NgcResult.InvalidUtf8Encoding, + _ => NgcResult.InvalidPointer, + }; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Utf8Text.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Utf8Text.cs new file mode 100644 index 00000000..34978fdc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Utf8Text.cs @@ -0,0 +1,104 @@ +using System; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + readonly struct Utf8Text + { + private readonly byte[] _text; + private readonly int[] _charOffsets; + + public int CharacterCount => _charOffsets.Length - 1; + + public Utf8Text() + { + _text = Array.Empty(); + _charOffsets = Array.Empty(); + } + + public Utf8Text(byte[] text) + { + _text = text; + + UTF8Encoding encoding = new(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + + string str = encoding.GetString(text); + + _charOffsets = new int[str.Length + 1]; + + int offset = 0; + + for (int index = 0; index < str.Length; index++) + { + _charOffsets[index] = offset; + offset += encoding.GetByteCount(str.AsSpan().Slice(index, 1)); + } + + _charOffsets[str.Length] = offset; + } + + public Utf8Text(ReadOnlySpan text) : this(text.ToArray()) + { + } + + public static Utf8ParseResult Create(out Utf8Text utf8Text, ReadOnlySpan text) + { + try + { + utf8Text = new(text); + } + catch (ArgumentException) + { + utf8Text = default; + + return Utf8ParseResult.InvalidCharacter; + } + + return Utf8ParseResult.Success; + } + + public ReadOnlySpan AsSubstring(int startCharIndex, int endCharIndex) + { + int startOffset = _charOffsets[startCharIndex]; + int endOffset = _charOffsets[endCharIndex]; + + return _text.AsSpan()[startOffset..endOffset]; + } + + public Utf8Text AppendNullTerminated(ReadOnlySpan toAppend) + { + int length = toAppend.IndexOf((byte)0); + if (length >= 0) + { + toAppend = toAppend[..length]; + } + + return Append(toAppend); + } + + public Utf8Text Append(ReadOnlySpan toAppend) + { + byte[] combined = new byte[_text.Length + toAppend.Length]; + + _text.AsSpan().CopyTo(combined.AsSpan()[.._text.Length]); + toAppend.CopyTo(combined.AsSpan()[_text.Length..]); + + return new(combined); + } + + public void CopyTo(Span destination) + { + _text.CopyTo(destination[.._text.Length]); + + if (destination.Length > _text.Length) + { + destination[_text.Length] = 0; + } + } + + public ReadOnlySpan AsSpan() + { + return _text; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Utf8Util.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Utf8Util.cs new file mode 100644 index 00000000..e2a02708 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/Utf8Util.cs @@ -0,0 +1,41 @@ +using System; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + static class Utf8Util + { + public static Utf8ParseResult NormalizeFormKC(Span output, ReadOnlySpan input) + { + int length = input.IndexOf((byte)0); + if (length >= 0) + { + input = input[..length]; + } + + UTF8Encoding encoding = new(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + + string text; + + try + { + text = encoding.GetString(input); + } + catch (ArgumentException) + { + return Utf8ParseResult.InvalidCharacter; + } + + string normalizedText = text.Normalize(NormalizationForm.FormKC); + + int outputIndex = Encoding.UTF8.GetBytes(normalizedText, output); + + if (outputIndex < output.Length) + { + output[outputIndex] = 0; + } + + return Utf8ParseResult.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/INgcService.cs b/src/Ryujinx.Horizon/Sdk/Ngc/INgcService.cs new file mode 100644 index 00000000..90f07822 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/INgcService.cs @@ -0,0 +1,14 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Ngc +{ + interface INgcService : IServiceObject + { + Result GetContentVersion(out uint version); + Result Check(out uint checkMask, ReadOnlySpan text, uint regionMask, ProfanityFilterOption option); + Result Mask(out int maskedWordsCount, Span filteredText, ReadOnlySpan text, uint regionMask, ProfanityFilterOption option); + Result Reload(); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/MaskMode.cs b/src/Ryujinx.Horizon/Sdk/Ngc/MaskMode.cs new file mode 100644 index 00000000..f4810ac8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/MaskMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.Ngc +{ + enum MaskMode + { + Overwrite = 0, + ReplaceByOneCharacter = 1, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/NgcResult.cs b/src/Ryujinx.Horizon/Sdk/Ngc/NgcResult.cs new file mode 100644 index 00000000..c53687fe --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/NgcResult.cs @@ -0,0 +1,16 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Ngc +{ + static class NgcResult + { + private const int ModuleId = 146; + + public static Result InvalidPointer => new(ModuleId, 3); + public static Result InvalidSize => new(ModuleId, 4); + public static Result InvalidUtf8Encoding => new(ModuleId, 5); + public static Result AllocationFailed => new(ModuleId, 101); + public static Result DataAccessError => new(ModuleId, 102); + public static Result GenericUtf8Error => new(ModuleId, 103); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/ProfanityFilterFlags.cs b/src/Ryujinx.Horizon/Sdk/Ngc/ProfanityFilterFlags.cs new file mode 100644 index 00000000..4b5f86b4 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/ProfanityFilterFlags.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Ngc +{ + [Flags] + enum ProfanityFilterFlags + { + None = 0, + MatchNormalizedFormKC = 1 << 0, + MatchSimilarForm = 1 << 1, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/ProfanityFilterOption.cs b/src/Ryujinx.Horizon/Sdk/Ngc/ProfanityFilterOption.cs new file mode 100644 index 00000000..4a2ab715 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/ProfanityFilterOption.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Ngc +{ + [StructLayout(LayoutKind.Sequential, Size = 0x14, Pack = 0x4)] + readonly struct ProfanityFilterOption + { + public readonly SkipMode SkipAtSignCheck; + public readonly MaskMode MaskMode; + public readonly ProfanityFilterFlags Flags; + public readonly uint SystemRegionMask; + public readonly uint Reserved; + + public ProfanityFilterOption(SkipMode skipAtSignCheck, MaskMode maskMode, ProfanityFilterFlags flags, uint systemRegionMask) + { + SkipAtSignCheck = skipAtSignCheck; + MaskMode = maskMode; + Flags = flags; + SystemRegionMask = systemRegionMask; + Reserved = 0; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/SkipMode.cs b/src/Ryujinx.Horizon/Sdk/Ngc/SkipMode.cs new file mode 100644 index 00000000..c177a5d2 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/SkipMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.Ngc +{ + enum SkipMode + { + DoNotSkip, + SkipAtSignCheck, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ns/ApplicationControlProperty.cs b/src/Ryujinx.Horizon/Sdk/Ns/ApplicationControlProperty.cs new file mode 100644 index 00000000..12c19168 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ns/ApplicationControlProperty.cs @@ -0,0 +1,309 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Ns +{ + public struct ApplicationControlProperty + { + public Array16 Title; + public Array37 Isbn; + public StartupUserAccountValue StartupUserAccount; + public UserAccountSwitchLockValue UserAccountSwitchLock; + public AddOnContentRegistrationTypeValue AddOnContentRegistrationType; + public AttributeFlagValue AttributeFlag; + public uint SupportedLanguageFlag; + public ParentalControlFlagValue ParentalControlFlag; + public ScreenshotValue Screenshot; + public VideoCaptureValue VideoCapture; + public DataLossConfirmationValue DataLossConfirmation; + public PlayLogPolicyValue PlayLogPolicy; + public ulong PresenceGroupId; + public Array32 RatingAge; + public Array16 DisplayVersion; + public ulong AddOnContentBaseId; + public ulong SaveDataOwnerId; + public long UserAccountSaveDataSize; + public long UserAccountSaveDataJournalSize; + public long DeviceSaveDataSize; + public long DeviceSaveDataJournalSize; + public long BcatDeliveryCacheStorageSize; + public Array8 ApplicationErrorCodeCategory; + public Array8 LocalCommunicationId; + public LogoTypeValue LogoType; + public LogoHandlingValue LogoHandling; + public RuntimeAddOnContentInstallValue RuntimeAddOnContentInstall; + public RuntimeParameterDeliveryValue RuntimeParameterDelivery; + public Array2 Reserved30F4; + public CrashReportValue CrashReport; + public HdcpValue Hdcp; + public ulong SeedForPseudoDeviceId; + public Array65 BcatPassphrase; + public StartupUserAccountOptionFlagValue StartupUserAccountOption; + public Array6 ReservedForUserAccountSaveDataOperation; + public long UserAccountSaveDataSizeMax; + public long UserAccountSaveDataJournalSizeMax; + public long DeviceSaveDataSizeMax; + public long DeviceSaveDataJournalSizeMax; + public long TemporaryStorageSize; + public long CacheStorageSize; + public long CacheStorageJournalSize; + public long CacheStorageDataAndJournalSizeMax; + public ushort CacheStorageIndexMax; + public byte Reserved318A; + public byte RuntimeUpgrade; + public uint SupportingLimitedLicenses; + public Array16 PlayLogQueryableApplicationId; + public PlayLogQueryCapabilityValue PlayLogQueryCapability; + public RepairFlagValue RepairFlag; + public byte ProgramIndex; + public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag; + public Array4 Reserved3214; + public ApplicationNeighborDetectionClientConfiguration NeighborDetectionClientConfiguration; + public ApplicationJitConfiguration JitConfiguration; + public RequiredAddOnContentsSetBinaryDescriptor RequiredAddOnContentsSetBinaryDescriptors; + public PlayReportPermissionValue PlayReportPermission; + public CrashScreenshotForProdValue CrashScreenshotForProd; + public CrashScreenshotForDevValue CrashScreenshotForDev; + public byte ContentsAvailabilityTransitionPolicy; + public Array4 Reserved3404; + public AccessibleLaunchRequiredVersionValue AccessibleLaunchRequiredVersion; + public ByteArray3000 Reserved3448; + + public readonly string IsbnString => Encoding.UTF8.GetString(Isbn.AsSpan()).TrimEnd('\0'); + public readonly string DisplayVersionString => Encoding.UTF8.GetString(DisplayVersion.AsSpan()).TrimEnd('\0'); + public readonly string ApplicationErrorCodeCategoryString => Encoding.UTF8.GetString(ApplicationErrorCodeCategory.AsSpan()).TrimEnd('\0'); + public readonly string BcatPassphraseString => Encoding.UTF8.GetString(BcatPassphrase.AsSpan()).TrimEnd('\0'); + + public struct ApplicationTitle + { + public ByteArray512 Name; + public Array256 Publisher; + + public readonly string NameString => Encoding.UTF8.GetString(Name.AsSpan()).TrimEnd('\0'); + public readonly string PublisherString => Encoding.UTF8.GetString(Publisher.AsSpan()).TrimEnd('\0'); + } + + public struct ApplicationNeighborDetectionClientConfiguration + { + public ApplicationNeighborDetectionGroupConfiguration SendGroupConfiguration; + public Array16 ReceivableGroupConfigurations; + } + + public struct ApplicationNeighborDetectionGroupConfiguration + { + public ulong GroupId; + public Array16 Key; + } + + public struct ApplicationJitConfiguration + { + public JitConfigurationFlag Flags; + public long MemorySize; + } + + public struct RequiredAddOnContentsSetBinaryDescriptor + { + public Array32 Descriptors; + } + + public struct AccessibleLaunchRequiredVersionValue + { + public Array8 ApplicationId; + } + + public enum Language + { + AmericanEnglish = 0, + BritishEnglish = 1, + Japanese = 2, + French = 3, + German = 4, + LatinAmericanSpanish = 5, + Spanish = 6, + Italian = 7, + Dutch = 8, + CanadianFrench = 9, + Portuguese = 10, + Russian = 11, + Korean = 12, + TraditionalChinese = 13, + SimplifiedChinese = 14, + BrazilianPortuguese = 15, + } + + public enum Organization + { + CERO = 0, + GRACGCRB = 1, + GSRMR = 2, + ESRB = 3, + ClassInd = 4, + USK = 5, + PEGI = 6, + PEGIPortugal = 7, + PEGIBBFC = 8, + Russian = 9, + ACB = 10, + OFLC = 11, + IARCGeneric = 12, + } + + public enum StartupUserAccountValue : byte + { + None = 0, + Required = 1, + RequiredWithNetworkServiceAccountAvailable = 2, + } + + public enum UserAccountSwitchLockValue : byte + { + Disable = 0, + Enable = 1, + } + + public enum AddOnContentRegistrationTypeValue : byte + { + AllOnLaunch = 0, + OnDemand = 1, + } + + [Flags] + public enum AttributeFlagValue + { + None = 0, + Demo = 1 << 0, + RetailInteractiveDisplay = 1 << 1, + } + + public enum ParentalControlFlagValue + { + None = 0, + FreeCommunication = 1, + } + + public enum ScreenshotValue : byte + { + Allow = 0, + Deny = 1, + } + + public enum VideoCaptureValue : byte + { + Disable = 0, + Manual = 1, + Enable = 2, + } + + public enum DataLossConfirmationValue : byte + { + None = 0, + Required = 1, + } + + public enum PlayLogPolicyValue : byte + { + Open = 0, + LogOnly = 1, + None = 2, + Closed = 3, + All = Open, + } + + public enum LogoTypeValue : byte + { + LicensedByNintendo = 0, + DistributedByNintendo = 1, + Nintendo = 2, + } + + public enum LogoHandlingValue : byte + { + Auto = 0, + Manual = 1, + } + + public enum RuntimeAddOnContentInstallValue : byte + { + Deny = 0, + AllowAppend = 1, + AllowAppendButDontDownloadWhenUsingNetwork = 2, + } + + public enum RuntimeParameterDeliveryValue : byte + { + Always = 0, + AlwaysIfUserStateMatched = 1, + OnRestart = 2, + } + + public enum CrashReportValue : byte + { + Deny = 0, + Allow = 1, + } + + public enum HdcpValue : byte + { + None = 0, + Required = 1, + } + + [Flags] + public enum StartupUserAccountOptionFlagValue : byte + { + None = 0, + IsOptional = 1 << 0, + } + + public enum PlayLogQueryCapabilityValue : byte + { + None = 0, + WhiteList = 1, + All = 2, + } + + [Flags] + public enum RepairFlagValue : byte + { + None = 0, + SuppressGameCardAccess = 1 << 0, + } + + [Flags] + public enum RequiredNetworkServiceLicenseOnLaunchValue : byte + { + None = 0, + Common = 1 << 0, + } + + [Flags] + public enum JitConfigurationFlag : ulong + { + None = 0, + Enabled = 1 << 0, + } + + [Flags] + public enum PlayReportPermissionValue : byte + { + None = 0, + TargetMarketing = 1 << 0, + } + + public enum CrashScreenshotForProdValue : byte + { + Deny = 0, + Allow = 1, + } + + public enum CrashScreenshotForDevValue : byte + { + Deny = 0, + Allow = 1, + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/Event.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/Event.cs new file mode 100644 index 00000000..cad25131 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/Event.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + class Event : IDisposable + { + private EventType _event; + + public object EventLock => _event.Lock; + public LinkedList MultiWaitHolders => _event.MultiWaitHolders; + + public Event(EventClearMode clearMode) + { + Os.InitializeEvent(out _event, signaled: false, clearMode); + } + + public TriBool IsSignaledThreadUnsafe() + { + return _event.Signaled ? TriBool.True : TriBool.False; + } + + public void Wait() + { + Os.WaitEvent(ref _event); + } + + public bool TryWait() + { + return Os.TryWaitEvent(ref _event); + } + + public bool TimedWait(TimeSpan timeout) + { + return Os.TimedWaitEvent(ref _event, timeout); + } + + public void Signal() + { + Os.SignalEvent(ref _event); + } + + public void Clear() + { + Os.ClearEvent(ref _event); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Os.FinalizeEvent(ref _event); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs new file mode 100644 index 00000000..75b1cb80 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + enum EventClearMode + { + ManualClear, + AutoClear, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs new file mode 100644 index 00000000..573e79b6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + struct EventType + { + public LinkedList MultiWaitHolders; + public bool Signaled; + public bool InitiallySignaled; + public EventClearMode ClearMode; + public InitializationState State; + public ulong BroadcastCounter; + public object Lock; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs new file mode 100644 index 00000000..4da4ee86 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs @@ -0,0 +1,89 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.OsTypes.Impl +{ + static class InterProcessEvent + { + public static Result Create(ref InterProcessEventType ipEvent, EventClearMode clearMode) + { + Result result = InterProcessEventImpl.Create(out int writableHandle, out int readableHandle); + + if (result != Result.Success) + { + return result; + } + + ipEvent = new InterProcessEventType( + clearMode == EventClearMode.AutoClear, + true, + true, + readableHandle, + writableHandle); + + return Result.Success; + } + + public static void Destroy(ref InterProcessEventType ipEvent) + { + ipEvent.State = InitializationState.NotInitialized; + + if (ipEvent.ReadableHandleManaged) + { + if (ipEvent.ReadableHandle != 0) + { + InterProcessEventImpl.Close(ipEvent.ReadableHandle); + } + ipEvent.ReadableHandleManaged = false; + } + + if (ipEvent.WritableHandleManaged) + { + if (ipEvent.WritableHandle != 0) + { + InterProcessEventImpl.Close(ipEvent.WritableHandle); + } + ipEvent.WritableHandleManaged = false; + } + } + + public static int DetachReadableHandle(ref InterProcessEventType ipEvent) + { + int handle = ipEvent.ReadableHandle; + + ipEvent.ReadableHandle = 0; + ipEvent.ReadableHandleManaged = false; + + return handle; + } + + public static int DetachWritableHandle(ref InterProcessEventType ipEvent) + { + int handle = ipEvent.WritableHandle; + + ipEvent.WritableHandle = 0; + ipEvent.WritableHandleManaged = false; + + return handle; + } + + public static int GetReadableHandle(ref InterProcessEventType ipEvent) + { + return ipEvent.ReadableHandle; + } + + public static int GetWritableHandle(ref InterProcessEventType ipEvent) + { + return ipEvent.WritableHandle; + } + + public static void Signal(ref InterProcessEventType ipEvent) + { + InterProcessEventImpl.Signal(ipEvent.WritableHandle); + } + + public static void Clear(ref InterProcessEventType ipEvent) + { + InterProcessEventImpl.Clear(ipEvent.ReadableHandle == 0 ? ipEvent.WritableHandle : ipEvent.ReadableHandle); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs new file mode 100644 index 00000000..e15913c0 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs @@ -0,0 +1,136 @@ +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.Horizon.Sdk.OsTypes.Impl +{ + static class InterProcessEventImpl + { + public static Result Create(out int writableHandle, out int readableHandle) + { + Result result = HorizonStatic.Syscall.CreateEvent(out writableHandle, out readableHandle); + + if (result == KernelResult.OutOfResource) + { + return OsResult.OutOfResource; + } + + result.AbortOnFailure(); + + return Result.Success; + } + + public static void Close(int handle) + { + if (handle != 0) + { + HorizonStatic.Syscall.CloseHandle(handle).AbortOnFailure(); + } + } + + public static void Signal(int handle) + { + HorizonStatic.Syscall.SignalEvent(handle).AbortOnFailure(); + } + + public static void Clear(int handle) + { + HorizonStatic.Syscall.ClearEvent(handle).AbortOnFailure(); + } + + public static void Wait(int handle, bool autoClear) + { + Span handles = stackalloc int[1]; + + handles[0] = handle; + + while (true) + { + Result result = HorizonStatic.Syscall.WaitSynchronization(out _, handles, -1L); + + if (result == Result.Success) + { + if (autoClear) + { + result = HorizonStatic.Syscall.ResetSignal(handle); + + if (result == KernelResult.InvalidState) + { + continue; + } + + result.AbortOnFailure(); + } + + return; + } + + result.AbortUnless(KernelResult.Cancelled); + } + } + + public static bool TryWait(int handle, bool autoClear) + { + if (autoClear) + { + return HorizonStatic.Syscall.ResetSignal(handle) == Result.Success; + } + + Span handles = stackalloc int[1]; + + handles[0] = handle; + + while (true) + { + Result result = HorizonStatic.Syscall.WaitSynchronization(out _, handles, 0); + + if (result == Result.Success) + { + return true; + } + else if (result == KernelResult.TimedOut) + { + return false; + } + + result.AbortUnless(KernelResult.Cancelled); + } + } + + public static bool TimedWait(int handle, bool autoClear, TimeSpan timeout) + { + Span handles = stackalloc int[1]; + + handles[0] = handle; + + long timeoutNs = timeout.Milliseconds * 1000000L; + + while (true) + { + Result result = HorizonStatic.Syscall.WaitSynchronization(out _, handles, timeoutNs); + + if (result == Result.Success) + { + if (autoClear) + { + result = HorizonStatic.Syscall.ResetSignal(handle); + + if (result == KernelResult.InvalidState) + { + continue; + } + + result.AbortOnFailure(); + } + + return true; + } + else if (result == KernelResult.TimedOut) + { + return false; + } + + result.AbortUnless(KernelResult.Cancelled); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs new file mode 100644 index 00000000..40635200 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs @@ -0,0 +1,245 @@ +using Ryujinx.Common; +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.OsTypes.Impl +{ + class MultiWaitImpl + { + private const int WaitTimedOut = -1; + private const int WaitCancelled = -2; + private const int WaitInvalid = -3; + + private readonly List _multiWaits; + + private readonly object _lock = new(); + + private int _waitingThreadHandle; + + private MultiWaitHolderBase _signaledHolder; + + public long CurrentTime { get; private set; } + + public IEnumerable MultiWaits => _multiWaits; + + public MultiWaitImpl() + { + _multiWaits = new List(); + } + + public void LinkMultiWaitHolder(MultiWaitHolderBase multiWaitHolder) + { + _multiWaits.Add(multiWaitHolder); + } + + public void UnlinkMultiWaitHolder(MultiWaitHolderBase multiWaitHolder) + { + _multiWaits.Remove(multiWaitHolder); + } + + public void MoveAllFrom(MultiWaitImpl other) + { + foreach (MultiWaitHolderBase multiWait in other._multiWaits) + { + multiWait.SetMultiWait(this); + } + + _multiWaits.AddRange(other._multiWaits); + + other._multiWaits.Clear(); + } + + public MultiWaitHolderBase WaitAnyImpl(bool infinite, long timeout) + { + _signaledHolder = null; + _waitingThreadHandle = Os.GetCurrentThreadHandle(); + + MultiWaitHolderBase result = LinkHoldersToObjectList(); + + lock (_lock) + { + if (_signaledHolder != null) + { + result = _signaledHolder; + } + } + + result ??= WaitAnyHandleImpl(infinite, timeout); + + UnlinkHoldersFromObjectsList(); + _waitingThreadHandle = 0; + + return result; + } + + private MultiWaitHolderBase WaitAnyHandleImpl(bool infinite, long timeout) + { + Span objectHandles = new int[64]; + + Span objects = new MultiWaitHolderBase[64]; + + int count = FillObjectsArray(objectHandles, objects); + + long endTime = infinite ? long.MaxValue : PerformanceCounter.ElapsedMilliseconds * 1000000; + + while (true) + { + CurrentTime = PerformanceCounter.ElapsedMilliseconds * 1000000; + + MultiWaitHolderBase minTimeoutObject = RecalcMultiWaitTimeout(endTime, out long minTimeout); + + int index; + + if (count == 0 && minTimeout == 0) + { + index = WaitTimedOut; + } + else + { + index = WaitSynchronization(objectHandles[..count], minTimeout); + + DebugUtil.Assert(index != WaitInvalid); + } + + switch (index) + { + case WaitTimedOut: + if (minTimeoutObject != null) + { + CurrentTime = PerformanceCounter.ElapsedMilliseconds * 1000000; + + if (minTimeoutObject.Signaled == TriBool.True) + { + lock (_lock) + { + _signaledHolder = minTimeoutObject; + + return _signaledHolder; + } + } + } + else + { + return null; + } + break; + case WaitCancelled: + lock (_lock) + { + if (_signaledHolder != null) + { + return _signaledHolder; + } + } + break; + default: + lock (_lock) + { + _signaledHolder = objects[index]; + + return _signaledHolder; + } + } + } + } + + private int FillObjectsArray(Span handles, Span objects) + { + int count = 0; + + foreach (MultiWaitHolderBase holder in _multiWaits) + { + int handle = holder.Handle; + + if (handle != 0) + { + handles[count] = handle; + objects[count] = holder; + + count++; + } + } + + return count; + } + + private MultiWaitHolderBase RecalcMultiWaitTimeout(long endTime, out long minTimeout) + { + MultiWaitHolderBase minTimeHolder = null; + + long minTime = endTime; + + foreach (MultiWaitHolderBase holder in _multiWaits) + { + long currentTime = holder.GetAbsoluteTimeToWakeup(); + + if ((ulong)currentTime < (ulong)minTime) + { + minTimeHolder = holder; + + minTime = currentTime; + } + } + + minTimeout = (ulong)minTime < (ulong)CurrentTime ? 0 : minTime - CurrentTime; + + return minTimeHolder; + } + + private static int WaitSynchronization(ReadOnlySpan handles, long timeout) + { + Result result = HorizonStatic.Syscall.WaitSynchronization(out int index, handles, timeout); + + if (result == KernelResult.TimedOut) + { + return WaitTimedOut; + } + else if (result == KernelResult.Cancelled) + { + return WaitCancelled; + } + + result.AbortOnFailure(); + + return index; + } + + public void NotifyAndWakeUpThread(MultiWaitHolderBase holder) + { + lock (_lock) + { + if (_signaledHolder == null) + { + _signaledHolder = holder; + HorizonStatic.Syscall.CancelSynchronization(_waitingThreadHandle).AbortOnFailure(); + } + } + } + + private MultiWaitHolderBase LinkHoldersToObjectList() + { + MultiWaitHolderBase signaledHolder = null; + + foreach (MultiWaitHolderBase holder in _multiWaits) + { + TriBool isSignaled = holder.LinkToObjectList(); + + if (signaledHolder == null && isSignaled == TriBool.True) + { + signaledHolder = holder; + } + } + + return signaledHolder; + } + + private void UnlinkHoldersFromObjectsList() + { + foreach (MultiWaitHolderBase holder in _multiWaits) + { + holder.UnlinkFromObjectList(); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs new file mode 100644 index 00000000..3d5bb810 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + enum InitializationState : byte + { + NotInitialized, + Initialized, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs new file mode 100644 index 00000000..39c93e57 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + struct InterProcessEventType + { + public readonly bool AutoClear; + public InitializationState State; + public bool ReadableHandleManaged; + public bool WritableHandleManaged; + public int ReadableHandle; + public int WritableHandle; + + public InterProcessEventType( + bool autoClear, + bool readableHandleManaged, + bool writableHandleManaged, + int readableHandle, + int writableHandle) + { + AutoClear = autoClear; + State = InitializationState.Initialized; + ReadableHandleManaged = readableHandleManaged; + WritableHandleManaged = writableHandleManaged; + ReadableHandle = readableHandle; + WritableHandle = writableHandle; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs new file mode 100644 index 00000000..41d17802 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs @@ -0,0 +1,46 @@ +using Ryujinx.Horizon.Sdk.OsTypes.Impl; +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + class MultiWait + { + private readonly MultiWaitImpl _impl; + + public IEnumerable MultiWaits => _impl.MultiWaits; + + public MultiWait() + { + _impl = new MultiWaitImpl(); + } + + public void LinkMultiWaitHolder(MultiWaitHolderBase multiWaitHolder) + { + DebugUtil.Assert(!multiWaitHolder.IsLinked); + + _impl.LinkMultiWaitHolder(multiWaitHolder); + + multiWaitHolder.SetMultiWait(_impl); + } + + public void MoveAllFrom(MultiWait other) + { + _impl.MoveAllFrom(other._impl); + } + + public MultiWaitHolder WaitAny() + { + return (MultiWaitHolder)_impl.WaitAnyImpl(true, -1L); + } + + public MultiWaitHolder TryWaitAny() + { + return (MultiWaitHolder)_impl.WaitAnyImpl(false, 0); + } + + public MultiWaitHolder TimedWaitAny(long timeout) + { + return (MultiWaitHolder)_impl.WaitAnyImpl(false, timeout); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs new file mode 100644 index 00000000..e0473eca --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + class MultiWaitHolder : MultiWaitHolderBase + { + public object UserData { get; set; } + + public void UnlinkFromMultiWaitHolder() + { + DebugUtil.Assert(IsLinked); + + MultiWait.UnlinkMultiWaitHolder(this); + + SetMultiWait(null); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs new file mode 100644 index 00000000..4bccba6c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs @@ -0,0 +1,39 @@ +using Ryujinx.Horizon.Sdk.OsTypes.Impl; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + class MultiWaitHolderBase + { + protected MultiWaitImpl MultiWait; + + public bool IsLinked => MultiWait != null; + + public virtual TriBool Signaled => TriBool.False; + + public virtual int Handle => 0; + + public void SetMultiWait(MultiWaitImpl multiWait) + { + MultiWait = multiWait; + } + + public MultiWaitImpl GetMultiWait() + { + return MultiWait; + } + + public virtual TriBool LinkToObjectList() + { + return TriBool.Undefined; + } + + public virtual void UnlinkFromObjectList() + { + } + + public virtual long GetAbsoluteTimeToWakeup() + { + return long.MaxValue; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs new file mode 100644 index 00000000..f5597847 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + class MultiWaitHolderOfEvent : MultiWaitHolder + { + private readonly Event _event; + private LinkedListNode _node; + + public override TriBool Signaled + { + get + { + lock (_event.EventLock) + { + return _event.IsSignaledThreadUnsafe(); + } + } + } + + public MultiWaitHolderOfEvent(Event evnt) + { + _event = evnt; + } + + public override TriBool LinkToObjectList() + { + lock (_event.EventLock) + { + _node = _event.MultiWaitHolders.AddLast(this); + + return _event.IsSignaledThreadUnsafe(); + } + } + + public override void UnlinkFromObjectList() + { + lock (_event.EventLock) + { + _event.MultiWaitHolders.Remove(_node); + _node = null; + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs new file mode 100644 index 00000000..1f5fefde --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + class MultiWaitHolderOfHandle : MultiWaitHolder + { + private readonly int _handle; + + public override int Handle => _handle; + + public MultiWaitHolderOfHandle(int handle) + { + _handle = handle; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs new file mode 100644 index 00000000..8efe614f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + static partial class Os + { + public static void InitializeEvent(out EventType evnt, bool signaled, EventClearMode clearMode) + { + evnt = new EventType + { + MultiWaitHolders = new LinkedList(), + Signaled = signaled, + InitiallySignaled = signaled, + ClearMode = clearMode, + State = InitializationState.Initialized, + Lock = new object(), + }; + } + + public static void FinalizeEvent(ref EventType evnt) + { + evnt.State = InitializationState.NotInitialized; + } + + public static void WaitEvent(ref EventType evnt) + { + lock (evnt.Lock) + { + ulong currentCounter = evnt.BroadcastCounter; + + while (!evnt.Signaled) + { + if (currentCounter != evnt.BroadcastCounter) + { + break; + } + + Monitor.Wait(evnt.Lock); + } + + if (evnt.ClearMode == EventClearMode.AutoClear) + { + evnt.Signaled = false; + } + } + } + + public static bool TryWaitEvent(ref EventType evnt) + { + lock (evnt.Lock) + { + bool signaled = evnt.Signaled; + + if (evnt.ClearMode == EventClearMode.AutoClear) + { + evnt.Signaled = false; + } + + return signaled; + } + } + + public static bool TimedWaitEvent(ref EventType evnt, TimeSpan timeout) + { + lock (evnt.Lock) + { + ulong currentCounter = evnt.BroadcastCounter; + + while (!evnt.Signaled) + { + if (currentCounter != evnt.BroadcastCounter) + { + break; + } + + bool wasSignaledInTime = Monitor.Wait(evnt.Lock, timeout); + if (!wasSignaledInTime) + { + return false; + } + } + + if (evnt.ClearMode == EventClearMode.AutoClear) + { + evnt.Signaled = false; + } + } + + return true; + } + + public static void SignalEvent(ref EventType evnt) + { + lock (evnt.Lock) + { + if (evnt.Signaled) + { + return; + } + + evnt.Signaled = true; + + if (evnt.ClearMode == EventClearMode.ManualClear) + { + evnt.BroadcastCounter++; + Monitor.PulseAll(evnt.Lock); + } + else + { + Monitor.Pulse(evnt.Lock); + } + + foreach (MultiWaitHolderBase holder in evnt.MultiWaitHolders) + { + holder.GetMultiWait().NotifyAndWakeUpThread(holder); + } + } + } + + public static void ClearEvent(ref EventType evnt) + { + lock (evnt.Lock) + { + evnt.Signaled = false; + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs new file mode 100644 index 00000000..8e9648de --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + static partial class Os + { + public static void FinalizeMultiWaitHolder(MultiWaitHolderBase holder) + { + DebugUtil.Assert(!holder.IsLinked); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs new file mode 100644 index 00000000..c835be2c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs @@ -0,0 +1,33 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + static partial class Os + { + private const int SelfProcessHandle = (0x1ffff << 15) | 1; + + public static int GetCurrentProcessHandle() + { + return SelfProcessHandle; + } + + public static ulong GetCurrentProcessId() + { + return GetProcessId(GetCurrentProcessHandle()); + } + + private static ulong GetProcessId(int handle) + { + Result result = TryGetProcessId(handle, out ulong pid); + + result.AbortOnFailure(); + + return pid; + } + + private static Result TryGetProcessId(int handle, out ulong pid) + { + return HorizonStatic.Syscall.GetProcessId(out pid, handle); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs new file mode 100644 index 00000000..77da87fa --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs @@ -0,0 +1,11 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + static class OsResult + { + private const int ModuleId = 3; + + public static Result OutOfResource => new(ModuleId, 9); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs new file mode 100644 index 00000000..8fac94ab --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs @@ -0,0 +1,85 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.OsTypes.Impl; +using System; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + static partial class Os + { + public static Result CreateSystemEvent(out SystemEventType sysEvent, EventClearMode clearMode, bool interProcess) + { + sysEvent = new SystemEventType(); + + if (interProcess) + { + Result result = InterProcessEvent.Create(ref sysEvent.InterProcessEvent, clearMode); + + if (result != Result.Success) + { + return result; + } + + sysEvent.State = SystemEventType.InitializationState.InitializedAsInterProcess; + } + else + { + throw new NotImplementedException(); + } + + return Result.Success; + } + + public static void DestroySystemEvent(ref SystemEventType sysEvent) + { + var oldState = sysEvent.State; + sysEvent.State = SystemEventType.InitializationState.NotInitialized; + + switch (oldState) + { + case SystemEventType.InitializationState.InitializedAsInterProcess: + InterProcessEvent.Destroy(ref sysEvent.InterProcessEvent); + break; + } + } + + public static int DetachReadableHandleOfSystemEvent(ref SystemEventType sysEvent) + { + return InterProcessEvent.DetachReadableHandle(ref sysEvent.InterProcessEvent); + } + + public static int DetachWritableHandleOfSystemEvent(ref SystemEventType sysEvent) + { + return InterProcessEvent.DetachWritableHandle(ref sysEvent.InterProcessEvent); + } + + public static int GetReadableHandleOfSystemEvent(ref SystemEventType sysEvent) + { + return InterProcessEvent.GetReadableHandle(ref sysEvent.InterProcessEvent); + } + + public static int GetWritableHandleOfSystemEvent(ref SystemEventType sysEvent) + { + return InterProcessEvent.GetWritableHandle(ref sysEvent.InterProcessEvent); + } + + public static void SignalSystemEvent(ref SystemEventType sysEvent) + { + switch (sysEvent.State) + { + case SystemEventType.InitializationState.InitializedAsInterProcess: + InterProcessEvent.Signal(ref sysEvent.InterProcessEvent); + break; + } + } + + public static void ClearSystemEvent(ref SystemEventType sysEvent) + { + switch (sysEvent.State) + { + case SystemEventType.InitializationState.InitializedAsInterProcess: + InterProcessEvent.Clear(ref sysEvent.InterProcessEvent); + break; + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs new file mode 100644 index 00000000..d47493eb --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + static partial class Os + { + public static int GetCurrentThreadHandle() + { + return HorizonStatic.CurrentThreadHandle; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs new file mode 100644 index 00000000..a838f46c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + struct SystemEventType + { + public enum InitializationState : byte + { + NotInitialized, + InitializedAsEvent, + InitializedAsInterProcess, + } + + public InterProcessEventType InterProcessEvent; + public InitializationState State; + + public readonly bool NotInitialized => State == InitializationState.NotInitialized; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs new file mode 100644 index 00000000..b162db9b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + enum TriBool + { + False, + True, + Undefined, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ovln/IReceiverService.cs b/src/Ryujinx.Horizon/Sdk/Ovln/IReceiverService.cs new file mode 100644 index 00000000..d663ed60 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ovln/IReceiverService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Ovln +{ + interface IReceiverService : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ovln/ISenderService.cs b/src/Ryujinx.Horizon/Sdk/Ovln/ISenderService.cs new file mode 100644 index 00000000..b0b9b2f0 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ovln/ISenderService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Ovln +{ + interface ISenderService : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Prepo/IPrepoService.cs b/src/Ryujinx.Horizon/Sdk/Prepo/IPrepoService.cs new file mode 100644 index 00000000..fd63755e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Prepo/IPrepoService.cs @@ -0,0 +1,21 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.Sf; +using System; +using ApplicationId = Ryujinx.Horizon.Sdk.Ncm.ApplicationId; + +namespace Ryujinx.Horizon.Sdk.Prepo +{ + interface IPrepoService : IServiceObject + { + Result SaveReport(ReadOnlySpan gameRoomBuffer, ReadOnlySpan reportBuffer, ulong pid); + Result SaveReportWithUser(Uid userId, ReadOnlySpan gameRoomBuffer, ReadOnlySpan reportBuffer, ulong pid); + Result RequestImmediateTransmission(); + Result GetTransmissionStatus(out int status); + Result GetSystemSessionId(out ulong systemSessionId); + Result SaveSystemReport(ReadOnlySpan gameRoomBuffer, ApplicationId applicationId, ReadOnlySpan reportBuffer); + Result SaveSystemReportWithUser(Uid userId, ReadOnlySpan gameRoomBuffer, ApplicationId applicationId, ReadOnlySpan reportBuffer); + Result IsUserAgreementCheckEnabled(out bool enabled); + Result SetUserAgreementCheckEnabled(bool enabled); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Psc/IPmControl.cs b/src/Ryujinx.Horizon/Sdk/Psc/IPmControl.cs new file mode 100644 index 00000000..679a4353 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Psc/IPmControl.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Psc +{ + interface IPmControl : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Psc/IPmService.cs b/src/Ryujinx.Horizon/Sdk/Psc/IPmService.cs new file mode 100644 index 00000000..53e703f8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Psc/IPmService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Psc +{ + interface IPmService : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Psc/IPmStateLock.cs b/src/Ryujinx.Horizon/Sdk/Psc/IPmStateLock.cs new file mode 100644 index 00000000..2335b8c8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Psc/IPmStateLock.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Psc +{ + interface IPmStateLock : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/ServiceUtil.cs b/src/Ryujinx.Horizon/Sdk/ServiceUtil.cs new file mode 100644 index 00000000..5527c1e3 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/ServiceUtil.cs @@ -0,0 +1,288 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk +{ + static class ServiceUtil + { + public static Result SendRequest(out CmifResponse response, int sessionHandle, uint requestId, bool sendPid, scoped ReadOnlySpan data) + { + ulong tlsAddress = HorizonStatic.ThreadContext.TlsAddress; + int tlsSize = Api.TlsMessageBufferSize; + + using (var tlsRegion = HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize)) + { + CmifRequest request = CmifMessage.CreateRequest(tlsRegion.Memory.Span, new CmifRequestFormat + { + DataSize = data.Length, + RequestId = requestId, + SendPid = sendPid, + }); + + data.CopyTo(request.Data); + } + + Result result = HorizonStatic.Syscall.SendSyncRequest(sessionHandle); + + if (result.IsFailure) + { + response = default; + + return result; + } + + return CmifMessage.ParseResponse(out response, HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize).Memory.Span, false, 0); + } + + public static Result SendRequest( + out CmifResponse response, + int sessionHandle, + uint requestId, + bool sendPid, + scoped ReadOnlySpan data, + ReadOnlySpan bufferFlags, + ReadOnlySpan buffers) + { + ulong tlsAddress = HorizonStatic.ThreadContext.TlsAddress; + int tlsSize = Api.TlsMessageBufferSize; + + using (var tlsRegion = HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize)) + { + CmifRequestFormat format = new() + { + DataSize = data.Length, + RequestId = requestId, + SendPid = sendPid, + }; + + for (int index = 0; index < bufferFlags.Length; index++) + { + FormatProcessBuffer(ref format, bufferFlags[index]); + } + + CmifRequest request = CmifMessage.CreateRequest(tlsRegion.Memory.Span, format); + + for (int index = 0; index < buffers.Length; index++) + { + RequestProcessBuffer(ref request, buffers[index], bufferFlags[index]); + } + + data.CopyTo(request.Data); + } + + Result result = HorizonStatic.Syscall.SendSyncRequest(sessionHandle); + + if (result.IsFailure) + { + response = default; + + return result; + } + + return CmifMessage.ParseResponse(out response, HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize).Memory.Span, false, 0); + } + + private static void FormatProcessBuffer(ref CmifRequestFormat format, HipcBufferFlags flags) + { + if (flags == 0) + { + return; + } + + bool isIn = flags.HasFlag(HipcBufferFlags.In); + bool isOut = flags.HasFlag(HipcBufferFlags.Out); + + if (flags.HasFlag(HipcBufferFlags.AutoSelect)) + { + if (isIn) + { + format.InAutoBuffersCount++; + } + + if (isOut) + { + format.OutAutoBuffersCount++; + } + } + else if (flags.HasFlag(HipcBufferFlags.Pointer)) + { + if (isIn) + { + format.InPointersCount++; + } + + if (isOut) + { + if (flags.HasFlag(HipcBufferFlags.FixedSize)) + { + format.OutFixedPointersCount++; + } + else + { + format.OutPointersCount++; + } + } + } + else if (flags.HasFlag(HipcBufferFlags.MapAlias)) + { + if (isIn && isOut) + { + format.InOutBuffersCount++; + } + else if (isIn) + { + format.InBuffersCount++; + } + else + { + format.OutBuffersCount++; + } + } + } + + private static void RequestProcessBuffer(ref CmifRequest request, PointerAndSize buffer, HipcBufferFlags flags) + { + if (flags == 0) + { + return; + } + + bool isIn = flags.HasFlag(HipcBufferFlags.In); + bool isOut = flags.HasFlag(HipcBufferFlags.Out); + + if (flags.HasFlag(HipcBufferFlags.AutoSelect)) + { + HipcBufferMode mode = HipcBufferMode.Normal; + + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonSecure)) + { + mode = HipcBufferMode.NonSecure; + } + + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonDevice)) + { + mode = HipcBufferMode.NonDevice; + } + + if (isIn) + { + RequestInAutoBuffer(ref request, buffer.Address, buffer.Size, mode); + } + + if (isOut) + { + RequestOutAutoBuffer(ref request, buffer.Address, buffer.Size, mode); + } + } + else if (flags.HasFlag(HipcBufferFlags.Pointer)) + { + if (isIn) + { + RequestInPointer(ref request, buffer.Address, buffer.Size); + } + + if (isOut) + { + if (flags.HasFlag(HipcBufferFlags.FixedSize)) + { + RequestOutFixedPointer(ref request, buffer.Address, buffer.Size); + } + else + { + RequestOutPointer(ref request, buffer.Address, buffer.Size); + } + } + } + else if (flags.HasFlag(HipcBufferFlags.MapAlias)) + { + HipcBufferMode mode = HipcBufferMode.Normal; + + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonSecure)) + { + mode = HipcBufferMode.NonSecure; + } + + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonDevice)) + { + mode = HipcBufferMode.NonDevice; + } + + if (isIn && isOut) + { + RequestInOutBuffer(ref request, buffer.Address, buffer.Size, mode); + } + else if (isIn) + { + RequestInBuffer(ref request, buffer.Address, buffer.Size, mode); + } + else + { + RequestOutBuffer(ref request, buffer.Address, buffer.Size, mode); + } + } + } + + private static void RequestInAutoBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + if (request.ServerPointerSize != 0 && bufferSize <= (ulong)request.ServerPointerSize) + { + RequestInPointer(ref request, bufferAddress, bufferSize); + RequestInBuffer(ref request, 0UL, 0UL, mode); + } + else + { + RequestInPointer(ref request, 0UL, 0UL); + RequestInBuffer(ref request, bufferAddress, bufferSize, mode); + } + } + + private static void RequestOutAutoBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + if (request.ServerPointerSize != 0 && bufferSize <= (ulong)request.ServerPointerSize) + { + RequestOutPointer(ref request, bufferAddress, bufferSize); + RequestOutBuffer(ref request, 0UL, 0UL, mode); + } + else + { + RequestOutPointer(ref request, 0UL, 0UL); + RequestOutBuffer(ref request, bufferAddress, bufferSize, mode); + } + } + + private static void RequestInBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + request.Hipc.SendBuffers[request.SendBufferIndex++] = new HipcBufferDescriptor(bufferAddress, bufferSize, mode); + } + + private static void RequestOutBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + request.Hipc.ReceiveBuffers[request.RecvBufferIndex++] = new HipcBufferDescriptor(bufferAddress, bufferSize, mode); + } + + private static void RequestInOutBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + request.Hipc.ExchangeBuffers[request.ExchBufferIndex++] = new HipcBufferDescriptor(bufferAddress, bufferSize, mode); + } + + private static void RequestInPointer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize) + { + request.Hipc.SendStatics[request.SendStaticIndex++] = new HipcStaticDescriptor(bufferAddress, (ushort)bufferSize, request.CurrentInPointerId++); + request.ServerPointerSize -= (int)bufferSize; + } + + private static void RequestOutFixedPointer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize) + { + request.Hipc.ReceiveList[request.RecvListIndex++] = new HipcReceiveListEntry(bufferAddress, (ushort)bufferSize); + request.ServerPointerSize -= (int)bufferSize; + } + + private static void RequestOutPointer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize) + { + RequestOutFixedPointer(ref request, bufferAddress, bufferSize); + request.OutPointerSizes[request.OutPointerSizeIndex++] = (ushort)bufferSize; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/BatteryLot.cs b/src/Ryujinx.Horizon/Sdk/Settings/BatteryLot.cs new file mode 100644 index 00000000..71185fcd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/BatteryLot.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x1)] + struct BatteryLot + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerOffset.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerOffset.cs new file mode 100644 index 00000000..292a368f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerOffset.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)] + struct AccelerometerOffset + { + public ushort X; + public ushort Y; + public ushort Z; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerScale.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerScale.cs new file mode 100644 index 00000000..ef9d17ef --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerScale.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)] + struct AccelerometerScale + { + public ushort X; + public ushort Y; + public ushort Z; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcdsaCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcdsaCertificate.cs new file mode 100644 index 00000000..7cbab2f0 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcdsaCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x74, Pack = 0x4)] + struct AmiiboEcdsaCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsCertificate.cs new file mode 100644 index 00000000..8d16b51b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x24, Pack = 0x4)] + struct AmiiboEcqvBlsCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs new file mode 100644 index 00000000..da6ca53b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x48, Pack = 0x4)] + struct AmiiboEcqvBlsKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsRootCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsRootCertificate.cs new file mode 100644 index 00000000..e69e38a1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsRootCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x94, Pack = 0x4)] + struct AmiiboEcqvBlsRootCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvCertificate.cs new file mode 100644 index 00000000..43742fbb --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x4)] + struct AmiiboEcqvCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboKey.cs new file mode 100644 index 00000000..43ffccb0 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 0x4)] + struct AmiiboKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickFactoryCalibration.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickFactoryCalibration.cs new file mode 100644 index 00000000..3fe6f322 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickFactoryCalibration.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x9, Pack = 0x1)] + struct AnalogStickFactoryCalibration + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickModelParameter.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickModelParameter.cs new file mode 100644 index 00000000..a442032c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickModelParameter.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x12, Pack = 0x1)] + struct AnalogStickModelParameter + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/BdAddress.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/BdAddress.cs new file mode 100644 index 00000000..519d72e8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/BdAddress.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x1)] + struct BdAddress + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConfigurationId1.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConfigurationId1.cs new file mode 100644 index 00000000..40565805 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConfigurationId1.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x1E, Pack = 0x1)] + struct ConfigurationId1 + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConsoleSixAxisSensorHorizontalOffset.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConsoleSixAxisSensorHorizontalOffset.cs new file mode 100644 index 00000000..c5503edc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConsoleSixAxisSensorHorizontalOffset.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)] + struct ConsoleSixAxisSensorHorizontalOffset + { + public ushort X; + public ushort Y; + public ushort Z; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/CountryCode.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/CountryCode.cs new file mode 100644 index 00000000..daf2ba3b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/CountryCode.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + struct CountryCode + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceCertificate.cs new file mode 100644 index 00000000..727408ed --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x180)] + struct EccB233DeviceCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceKey.cs new file mode 100644 index 00000000..a0481f4d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 0x4)] + struct EccB233DeviceKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardCertificate.cs new file mode 100644 index 00000000..ce3908af --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x400)] + struct GameCardCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardKey.cs new file mode 100644 index 00000000..81144ac4 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x138)] + struct GameCardKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeOffset.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeOffset.cs new file mode 100644 index 00000000..801d117c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeOffset.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)] + struct GyroscopeOffset + { + public ushort X; + public ushort Y; + public ushort Z; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeScale.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeScale.cs new file mode 100644 index 00000000..7812281f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeScale.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)] + struct GyroscopeScale + { + public ushort X; + public ushort Y; + public ushort Z; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/MacAddress.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/MacAddress.cs new file mode 100644 index 00000000..65e222ee --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/MacAddress.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x1)] + struct MacAddress + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceCertificate.cs new file mode 100644 index 00000000..57217059 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x240)] + struct Rsa2048DeviceCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceKey.cs new file mode 100644 index 00000000..d2fd51cf --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x248)] + struct Rsa2048DeviceKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/SerialNumber.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SerialNumber.cs new file mode 100644 index 00000000..af664cdc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SerialNumber.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x1)] + struct SerialNumber + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/SpeakerParameter.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SpeakerParameter.cs new file mode 100644 index 00000000..f147f66f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SpeakerParameter.cs @@ -0,0 +1,32 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x5A, Pack = 0x2)] + struct SpeakerParameter + { + public ushort Version; + public Array34 Reserved; + public ushort SpeakerHpf2A1; + public ushort SpeakerHpf2A2; + public ushort SpeakerHpf2H0; + public ushort SpeakerEqInputVolume; + public ushort SpeakerEqOutputVolume; + public ushort SpeakerEqCtrl1; + public ushort SpeakerEqCtrl2; + public ushort SpeakerDrcAgcCtrl2; + public ushort SpeakerDrcAgcCtrl3; + public ushort SpeakerDrcAgcCtrl1; + public ushort SpeakerAnalogVolume; + public ushort HeadphoneAnalogVolume; + public ushort SpeakerDigitalVolumeMin; + public ushort SpeakerDigitalVolumeMax; + public ushort HeadphoneDigitalVolumeMin; + public ushort HeadphoneDigitalVolumeMax; + public ushort MicFixedGain; + public ushort MicVariableVolumeMin; + public ushort MicVariableVolumeMax; + public Array16 Reserved2; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslCertificate.cs new file mode 100644 index 00000000..5d825216 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x804)] + struct SslCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslKey.cs new file mode 100644 index 00000000..7d4b4136 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.Factory +{ + [StructLayout(LayoutKind.Sequential, Size = 0x138)] + struct SslKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Language.cs b/src/Ryujinx.Horizon/Sdk/Settings/Language.cs new file mode 100644 index 00000000..4ffc66fe --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/Language.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Horizon.Sdk.Settings +{ + enum Language : uint + { + Japanese, + AmericanEnglish, + French, + German, + Italian, + Spanish, + Chinese, + Korean, + Dutch, + Portuguese, + Russian, + Taiwanese, + BritishEnglish, + CanadianFrench, + LatinAmericanSpanish, + SimplifiedChinese, + TraditionalChinese, + BrazilianPortuguese, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/LanguageCode.cs b/src/Ryujinx.Horizon/Sdk/Settings/LanguageCode.cs new file mode 100644 index 00000000..dc971269 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/LanguageCode.cs @@ -0,0 +1,63 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Settings +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)] + struct LanguageCode + { + private static readonly string[] _languageCodes = new string[] + { + "ja", + "en-US", + "fr", + "de", + "it", + "es", + "zh-CN", + "ko", + "nl", + "pt", + "ru", + "zh-TW", + "en-GB", + "fr-CA", + "es-419", + "zh-Hans", + "zh-Hant", + "pt-BR" + }; + + public Array8 Value; + + public bool IsValid() + { + int length = Value.AsSpan().IndexOf((byte)0); + if (length < 0) + { + return false; + } + + string str = Encoding.ASCII.GetString(Value.AsSpan()[..length]); + + return _languageCodes.AsSpan().Contains(str); + } + + public LanguageCode(Language language) + { + if ((uint)language >= _languageCodes.Length) + { + throw new ArgumentOutOfRangeException(nameof(language)); + } + + Value = new LanguageCode(_languageCodes[(int)language]).Value; + } + + public LanguageCode(string strCode) + { + Encoding.ASCII.GetBytes(strCode, Value.AsSpan()); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/SettingsItemKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/SettingsItemKey.cs new file mode 100644 index 00000000..66118410 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/SettingsItemKey.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings +{ + [StructLayout(LayoutKind.Sequential, Size = 0x48)] + struct SettingsItemKey + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/SettingsName.cs b/src/Ryujinx.Horizon/Sdk/Settings/SettingsName.cs new file mode 100644 index 00000000..6864b8cd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/SettingsName.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings +{ + [StructLayout(LayoutKind.Sequential, Size = 0x48)] + struct SettingsName + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AccountNotificationSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountNotificationSettings.cs new file mode 100644 index 00000000..a2cbad6a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountNotificationSettings.cs @@ -0,0 +1,15 @@ +using Ryujinx.Horizon.Sdk.Account; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + struct AccountNotificationSettings + { +#pragma warning disable CS0649 // Field is never assigned to + public Uid UserId; + public uint Flags; + public byte FriendPresenceOverlayPermission; + public byte FriendInvitationOverlayPermission; + public ushort Reserved; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AccountOnlineStorageSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountOnlineStorageSettings.cs new file mode 100644 index 00000000..3ed77e52 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountOnlineStorageSettings.cs @@ -0,0 +1,6 @@ +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + struct AccountOnlineStorageSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AccountSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountSettings.cs new file mode 100644 index 00000000..bd27ea0b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountSettings.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x4, Pack = 0x4)] + struct AccountSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AllowedSslHost.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AllowedSslHost.cs new file mode 100644 index 00000000..cb90daf1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AllowedSslHost.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x100)] + struct AllowedSslHost + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AnalogStickUserCalibration.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AnalogStickUserCalibration.cs new file mode 100644 index 00000000..36023da9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AnalogStickUserCalibration.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)] + struct AnalogStickUserCalibration + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AppletLaunchFlag.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AppletLaunchFlag.cs new file mode 100644 index 00000000..00d6f4d0 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AppletLaunchFlag.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [Flags] + enum AppletLaunchFlag : uint + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AudioVolume.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AudioVolume.cs new file mode 100644 index 00000000..d246bc2b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AudioVolume.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)] + struct AudioVolume + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettings.cs new file mode 100644 index 00000000..00de6869 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettings.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x28, Pack = 0x4)] + struct BacklightSettings + { + // TODO: Determine field names. + public uint Unknown0x00; + public float Unknown0x04; + // 1st group + public float Unknown0x08; + public float Unknown0x0C; + public float Unknown0x10; + // 2nd group + public float Unknown0x14; + public float Unknown0x18; + public float Unknown0x1C; + public float Unknown0x20; + public float Unknown0x24; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettingsEx.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettingsEx.cs new file mode 100644 index 00000000..347afdfe --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettingsEx.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x2C, Pack = 0x4)] + struct BacklightSettingsEx + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/BlePairingSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/BlePairingSettings.cs new file mode 100644 index 00000000..d9b01f9f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/BlePairingSettings.cs @@ -0,0 +1,6 @@ +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + struct BlePairingSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/BluetoothDevicesSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/BluetoothDevicesSettings.cs new file mode 100644 index 00000000..ec5c97c5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/BluetoothDevicesSettings.cs @@ -0,0 +1,29 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + struct BluetoothDevicesSettings + { +#pragma warning disable CS0649 // Field is never assigned to + public Array6 BdAddr; + public Array32 DeviceName; + public Array3 ClassOfDevice; + public Array16 LinkKey; + public bool LinkKeyPresent; + public ushort Version; + public uint TrustedServices; + public ushort Vid; + public ushort Pid; + public byte SubClass; + public byte AttributeMask; + public ushort DescriptorLength; + public Array128 Descriptor; + public byte KeyType; + public byte DeviceType; + public ushort BrrSize; + public Array9 Brr; + public Array256 Reserved; + public Array43 Reserved2; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigRegisteredSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigRegisteredSettings.cs new file mode 100644 index 00000000..8bd4924e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigRegisteredSettings.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x5C8)] + struct ButtonConfigRegisteredSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigSettings.cs new file mode 100644 index 00000000..2f06e32e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigSettings.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x5A8)] + struct ButtonConfigSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationBias.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationBias.cs new file mode 100644 index 00000000..c70d4ff2 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationBias.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 0x4)] + struct ConsoleSixAxisSensorAccelerationBias + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationGain.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationGain.cs new file mode 100644 index 00000000..0803beb8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationGain.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x24, Pack = 0x4)] + struct ConsoleSixAxisSensorAccelerationGain + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularAcceleration.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularAcceleration.cs new file mode 100644 index 00000000..831e44bd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularAcceleration.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x24, Pack = 0x4)] + struct ConsoleSixAxisSensorAngularAcceleration + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityBias.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityBias.cs new file mode 100644 index 00000000..83d1faa8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityBias.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 0x4)] + struct ConsoleSixAxisSensorAngularVelocityBias + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityGain.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityGain.cs new file mode 100644 index 00000000..68e0c614 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityGain.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x24, Pack = 0x4)] + struct ConsoleSixAxisSensorAngularVelocityGain + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityTimeBias.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityTimeBias.cs new file mode 100644 index 00000000..47f3d951 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityTimeBias.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 0x4)] + struct ConsoleSixAxisSensorAngularVelocityTimeBias + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/DataDeletionSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/DataDeletionSettings.cs new file mode 100644 index 00000000..a10a265d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/DataDeletionSettings.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [Flags] + enum DataDeletionFlag : uint + { + AutomaticDeletionFlag = 1 << 0, + } + + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)] + struct DataDeletionSettings + { + public DataDeletionFlag Flags; + public uint UseCount; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/DeviceNickName.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/DeviceNickName.cs new file mode 100644 index 00000000..99c9f981 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/DeviceNickName.cs @@ -0,0 +1,25 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x80)] + struct DeviceNickName + { + public Array128 Value; + + public DeviceNickName(string value) + { + int bytesWritten = Encoding.ASCII.GetBytes(value, Value.AsSpan()); + if (bytesWritten < 128) + { + Value[bytesWritten] = 0; + } + else + { + Value[127] = 0; + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/Edid.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/Edid.cs new file mode 100644 index 00000000..3ff56685 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/Edid.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x200)] + struct Edid + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/EulaVersion.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/EulaVersion.cs new file mode 100644 index 00000000..65905b1b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/EulaVersion.cs @@ -0,0 +1,6 @@ +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + struct EulaVersion + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/FatalDirtyFlag.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/FatalDirtyFlag.cs new file mode 100644 index 00000000..6be94115 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/FatalDirtyFlag.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + struct FatalDirtyFlag + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersion.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersion.cs new file mode 100644 index 00000000..39825e01 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersion.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x100)] + struct FirmwareVersion + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersionDigest.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersionDigest.cs new file mode 100644 index 00000000..0027d7ef --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersionDigest.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40, Pack = 0x1)] + struct FirmwareVersionDigest + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/HomeMenuScheme.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/HomeMenuScheme.cs new file mode 100644 index 00000000..cc7b317b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/HomeMenuScheme.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x14, Pack = 0x1)] + struct HomeMenuScheme + { + public uint Main; + public uint Back; + public uint Sub; + public uint Bezel; + public uint Extra; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/HostFsMountPoint.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/HostFsMountPoint.cs new file mode 100644 index 00000000..1a66abac --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/HostFsMountPoint.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x100)] + struct HostFsMountPoint + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/InitialLaunchSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/InitialLaunchSettings.cs new file mode 100644 index 00000000..b3989de7 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/InitialLaunchSettings.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 0x8)] + struct InitialLaunchSettings + { + public uint Flags; + public uint Reserved; + public ulong TimeStamp1; + public ulong TimeStamp2; + public ulong TimeStamp3; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/NetworkSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/NetworkSettings.cs new file mode 100644 index 00000000..a0101b62 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/NetworkSettings.cs @@ -0,0 +1,6 @@ +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + struct NetworkSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/NotificationSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/NotificationSettings.cs new file mode 100644 index 00000000..2ce56c4d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/NotificationSettings.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [Flags] + enum NotificationFlag : uint + { + RingtoneFlag = 1 << 0, + DownloadCompletionFlag = 1 << 1, + EnablesNews = 1 << 8, + IncomingLampFlag = 1 << 9, + } + + enum NotificationVolume : uint + { + Mute, + Low, + High, + } + + struct NotificationTime + { +#pragma warning disable CS0649 // Field is never assigned to + public uint Hour; + public uint Minute; +#pragma warning restore CS0649 + } + + [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x4)] + struct NotificationSettings + { + public NotificationFlag Flag; + public NotificationVolume Volume; + public NotificationTime HeadTime; + public NotificationTime TailTime; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerLegacySettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerLegacySettings.cs new file mode 100644 index 00000000..845715df --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerLegacySettings.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x29)] + struct NxControllerLegacySettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerSettings.cs new file mode 100644 index 00000000..c8f81cec --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerSettings.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x42C)] + struct NxControllerSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/PtmFuelGaugeParameter.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/PtmFuelGaugeParameter.cs new file mode 100644 index 00000000..b843bcd6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/PtmFuelGaugeParameter.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x4)] + struct PtmFuelGaugeParameter + { + public ushort Rcomp0; + public ushort TempCo; + public ushort FullCap; + public ushort FullCapNom; + public ushort IavgEmpty; + public ushort QrTable00; + public ushort QrTable10; + public ushort QrTable20; + public ushort QrTable30; + public ushort Reserved; + public uint Cycles; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/RebootlessSystemUpdateVersion.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/RebootlessSystemUpdateVersion.cs new file mode 100644 index 00000000..b4e9b8b2 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/RebootlessSystemUpdateVersion.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x40, Pack = 0x4)] + struct RebootlessSystemUpdateVersion + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/SerialNumber.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/SerialNumber.cs new file mode 100644 index 00000000..22ddb85c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/SerialNumber.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x1)] + struct SerialNumber + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ServiceDiscoveryControlSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ServiceDiscoveryControlSettings.cs new file mode 100644 index 00000000..7c7b625a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ServiceDiscoveryControlSettings.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [Flags] + enum ServiceDiscoveryControlSettings : uint + { + IsChangeEnvironmentIdentifierDisabled = 1 << 0, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/SleepSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/SleepSettings.cs new file mode 100644 index 00000000..7493c677 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/SleepSettings.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [Flags] + enum SleepFlag : uint + { + SleepsWhilePlayingMedia = 1 << 0, + WakesAtPowerStateChange = 1 << 1, + } + + enum HandheldSleepPlan : uint + { + At1Min, + At3Min, + At5Min, + At10Min, + At30Min, + Never, + } + + enum ConsoleSleepPlan : uint + { + At1Hour, + At2Hour, + At3Hour, + At6Hour, + At12Hour, + Never, + } + + [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 0x4)] + struct SleepSettings + { + public SleepFlag Flags; + public HandheldSleepPlan HandheldSleepPlan; + public ConsoleSleepPlan ConsoleSleepPlan; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/TelemetryDirtyFlag.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/TelemetryDirtyFlag.cs new file mode 100644 index 00000000..46ec2d76 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/TelemetryDirtyFlag.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] + struct TelemetryDirtyFlag + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeId.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeId.cs new file mode 100644 index 00000000..886ec872 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeId.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x80, Pack = 0x8)] + struct ThemeId + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeSettings.cs new file mode 100644 index 00000000..ac36bcd8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeSettings.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)] + struct ThemeSettings + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/TvSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/TvSettings.cs new file mode 100644 index 00000000..5ee0b85d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Settings/System/TvSettings.cs @@ -0,0 +1,59 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Settings.System +{ + [Flags] + enum TvFlag : uint + { + Allows4k = 1 << 0, + Allows3d = 1 << 1, + AllowsCec = 1 << 2, + PreventsScreenBurnIn = 1 << 3, + } + + enum TvResolution : uint + { + Auto, + At1080p, + At720p, + At480p, + } + + enum HdmiContentType : uint + { + None, + Graphics, + Cinema, + Photo, + Game, + } + + enum RgbRange : uint + { + Auto, + Full, + Limited, + } + + enum CmuMode : uint + { + None, + ColorInvert, + HighContrast, + GrayScale, + } + + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 0x4)] + struct TvSettings + { + public TvFlag Flags; + public TvResolution TvResolution; + public HdmiContentType HdmiContentType; + public RgbRange RgbRange; + public CmuMode CmuMode; + public float TvUnderscan; + public float TvGamma; + public float ContrastRatio; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs new file mode 100644 index 00000000..cc1eb6cb --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + struct CmifDomainInHeader + { + public CmifDomainRequestType Type; + public byte ObjectsCount; + public ushort DataSize; + public int ObjectId; + public uint Padding; + public uint Token; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs new file mode 100644 index 00000000..23c1ce24 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + struct CmifDomainOutHeader + { +#pragma warning disable CS0649 // Field is never assigned to + public uint ObjectsCount; + public uint Padding; + public uint Padding2; + public uint Padding3; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs new file mode 100644 index 00000000..0781d589 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + enum CmifDomainRequestType : byte + { + Invalid = 0, + SendMessage = 1, + Close = 2, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs new file mode 100644 index 00000000..12442dad --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + struct CmifInHeader + { + public uint Magic; + public uint Version; + public uint CommandId; + public uint Token; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs new file mode 100644 index 00000000..6b4c5cc5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs @@ -0,0 +1,135 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + static class CmifMessage + { + public const uint CmifInHeaderMagic = 0x49434653; // SFCI + public const uint CmifOutHeaderMagic = 0x4f434653; // SFCO + + public static CmifRequest CreateRequest(Span output, CmifRequestFormat format) + { + int totalSize = 16; + + if (format.ObjectId != 0) + { + totalSize += Unsafe.SizeOf() + format.ObjectsCount * sizeof(int); + } + + totalSize += Unsafe.SizeOf() + format.DataSize; + totalSize = (totalSize + 1) & ~1; + + int outPointerSizeTableOffset = totalSize; + int outPointerSizeTableSize = format.OutAutoBuffersCount + format.OutPointersCount; + + totalSize += sizeof(ushort) * outPointerSizeTableSize; + + int rawDataSizeInWords = (totalSize + sizeof(uint) - 1) / sizeof(uint); + + CmifRequest request = new() + { + Hipc = HipcMessage.WriteMessage(output, new HipcMetadata + { + Type = format.Context != 0 ? (int)CommandType.RequestWithContext : (int)CommandType.Request, + SendStaticsCount = format.InAutoBuffersCount + format.InPointersCount, + SendBuffersCount = format.InAutoBuffersCount + format.InBuffersCount, + ReceiveBuffersCount = format.OutAutoBuffersCount + format.OutBuffersCount, + ExchangeBuffersCount = format.InOutBuffersCount, + DataWordsCount = rawDataSizeInWords, + ReceiveStaticsCount = outPointerSizeTableSize + format.OutFixedPointersCount, + SendPid = format.SendPid, + CopyHandlesCount = format.HandlesCount, + MoveHandlesCount = 0, + }), + }; + + Span data = request.Hipc.DataWords; + + if (format.ObjectId != 0) + { + ref CmifDomainInHeader domainHeader = ref MemoryMarshal.Cast(data)[0]; + + int payloadSize = Unsafe.SizeOf() + format.DataSize; + + domainHeader = new CmifDomainInHeader + { + Type = CmifDomainRequestType.SendMessage, + ObjectsCount = (byte)format.ObjectsCount, + DataSize = (ushort)payloadSize, + ObjectId = format.ObjectId, + Padding = 0, + Token = format.Context, + }; + + data = data[(Unsafe.SizeOf() / sizeof(uint))..]; + + request.Objects = data[((payloadSize + sizeof(uint) - 1) / sizeof(uint))..]; + } + + ref CmifInHeader header = ref MemoryMarshal.Cast(data)[0]; + + header = new CmifInHeader + { + Magic = CmifInHeaderMagic, + Version = format.Context != 0 ? 1u : 0u, + CommandId = format.RequestId, + Token = format.ObjectId != 0 ? 0u : format.Context, + }; + + request.Data = MemoryMarshal.Cast(data)[Unsafe.SizeOf()..]; + + int paddingSizeBefore = (rawDataSizeInWords - request.Hipc.DataWords.Length) * sizeof(uint); + + Span outPointerTable = MemoryMarshal.Cast(request.Hipc.DataWords)[(outPointerSizeTableOffset - paddingSizeBefore)..]; + + request.OutPointerSizes = MemoryMarshal.Cast(outPointerTable); + request.ServerPointerSize = format.ServerPointerSize; + + return request; + } + + public static Result ParseResponse(out CmifResponse response, Span input, bool isDomain, int size) + { + HipcMessage responseMessage = new(input); + + Span data = MemoryMarshal.Cast(responseMessage.Data.DataWords); + Span objects = Span.Empty; + + if (isDomain) + { + data = data[Unsafe.SizeOf()..]; + objects = MemoryMarshal.Cast(data[(Unsafe.SizeOf() + size)..]); + } + + CmifOutHeader header = MemoryMarshal.Cast(data)[0]; + + if (header.Magic != CmifOutHeaderMagic) + { + response = default; + + return SfResult.InvalidOutHeader; + } + + if (header.Result.IsFailure) + { + response = default; + + return header.Result; + } + + response = new CmifResponse + { + Data = data[Unsafe.SizeOf()..], + Objects = objects, + CopyHandles = responseMessage.Data.CopyHandles, + MoveHandles = responseMessage.Data.MoveHandles, + }; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs new file mode 100644 index 00000000..ddceca03 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs @@ -0,0 +1,14 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + struct CmifOutHeader + { +#pragma warning disable CS0649 // Field is never assigned to + public uint Magic; + public uint Version; + public Result Result; + public uint Token; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs new file mode 100644 index 00000000..62c15baa --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs @@ -0,0 +1,21 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + ref struct CmifRequest + { + public HipcMessageData Hipc; + public Span Data; + public Span OutPointerSizes; + public Span Objects; + public int ServerPointerSize; + public int CurrentInPointerId; + public int SendBufferIndex; + public int RecvBufferIndex; + public int ExchBufferIndex; + public int SendStaticIndex; + public int RecvListIndex; + public int OutPointerSizeIndex; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs new file mode 100644 index 00000000..37034199 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + struct CmifRequestFormat + { +#pragma warning disable CS0649 // Field is never assigned to + public int ObjectId; + public uint RequestId; + public uint Context; + public int DataSize; + public int ServerPointerSize; + public int InAutoBuffersCount; + public int OutAutoBuffersCount; + public int InBuffersCount; + public int OutBuffersCount; + public int InOutBuffersCount; + public int InPointersCount; + public int OutPointersCount; + public int OutFixedPointersCount; + public int ObjectsCount; + public int HandlesCount; + public bool SendPid; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs new file mode 100644 index 00000000..fbe90085 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs @@ -0,0 +1,12 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + ref struct CmifResponse + { + public ReadOnlySpan Data; + public ReadOnlySpan Objects; + public ReadOnlySpan CopyHandles; + public ReadOnlySpan MoveHandles; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs new file mode 100644 index 00000000..4f6c50fc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + enum CommandType + { + Invalid = 0, + LegacyRequest = 1, + Close = 2, + LegacyControl = 3, + Request = 4, + Control = 5, + RequestWithContext = 6, + ControlWithContext = 7, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs new file mode 100644 index 00000000..e7d1ab6b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + abstract partial class DomainServiceObject : ServerDomainBase, IServiceObject + { + public abstract ServerDomainBase GetServerDomain(); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs new file mode 100644 index 00000000..58957a70 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs @@ -0,0 +1,75 @@ +using Ryujinx.Horizon.Common; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + class DomainServiceObjectDispatchTable : ServiceDispatchTableBase + { + public override Result ProcessMessage(ref ServiceDispatchContext context, ReadOnlySpan inRawData) + { + return ProcessMessageImpl(ref context, ((DomainServiceObject)context.ServiceObject).GetServerDomain(), inRawData); + } + + private static Result ProcessMessageImpl(ref ServiceDispatchContext context, ServerDomainBase domain, ReadOnlySpan inRawData) + { + if (inRawData.Length < Unsafe.SizeOf()) + { + return SfResult.InvalidHeaderSize; + } + + var inHeader = MemoryMarshal.Cast(inRawData)[0]; + + ReadOnlySpan inDomainRawData = inRawData[Unsafe.SizeOf()..]; + + int targetObjectId = inHeader.ObjectId; + + switch (inHeader.Type) + { + case CmifDomainRequestType.SendMessage: + var targetObject = domain.GetObject(targetObjectId); + if (targetObject == null) + { + return SfResult.TargetNotFound; + } + + if (inHeader.DataSize + inHeader.ObjectsCount * sizeof(int) > inDomainRawData.Length) + { + return SfResult.InvalidHeaderSize; + } + + ReadOnlySpan inMessageRawData = inDomainRawData[..inHeader.DataSize]; + + if (inHeader.ObjectsCount > DomainServiceObjectProcessor.MaximumObjects) + { + return SfResult.InvalidInObjectsCount; + } + + int[] inObjectIds = new int[inHeader.ObjectsCount]; + + var domainProcessor = new DomainServiceObjectProcessor(domain, inObjectIds); + + if (context.Processor == null) + { + context.Processor = domainProcessor; + } + else + { + context.Processor.SetImplementationProcessor(domainProcessor); + } + + context.ServiceObject = targetObject.ServiceObject; + + return targetObject.ProcessMessage(ref context, inMessageRawData); + + case CmifDomainRequestType.Close: + domain.UnregisterObject(targetObjectId); + return Result.Success; + + default: + return SfResult.InvalidInHeader; + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs new file mode 100644 index 00000000..f677e059 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs @@ -0,0 +1,139 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + class DomainServiceObjectProcessor : ServerMessageProcessor + { + public const int MaximumObjects = 8; + + private ServerMessageProcessor _implProcessor; + private readonly ServerDomainBase _domain; + private int _outObjectIdsOffset; + private readonly int[] _inObjectIds; + private readonly int[] _reservedObjectIds; + private ServerMessageRuntimeMetadata _implMetadata; + + private int InObjectsCount => _inObjectIds.Length; + private int OutObjectsCount => _implMetadata.OutObjectsCount; + private int ImplOutDataTotalSize => _implMetadata.OutDataSize + _implMetadata.OutHeadersSize; + + public DomainServiceObjectProcessor(ServerDomainBase domain, int[] inObjectIds) + { + _domain = domain; + _inObjectIds = inObjectIds; + _reservedObjectIds = new int[MaximumObjects]; + } + + public override void SetImplementationProcessor(ServerMessageProcessor impl) + { + if (_implProcessor == null) + { + _implProcessor = impl; + } + else + { + _implProcessor.SetImplementationProcessor(impl); + } + + _implMetadata = _implProcessor.GetRuntimeMetadata(); + } + + public override ServerMessageRuntimeMetadata GetRuntimeMetadata() + { + var runtimeMetadata = _implProcessor.GetRuntimeMetadata(); + + return new ServerMessageRuntimeMetadata( + (ushort)(runtimeMetadata.InDataSize + runtimeMetadata.InObjectsCount * sizeof(int)), + (ushort)(runtimeMetadata.OutDataSize + runtimeMetadata.OutObjectsCount * sizeof(int)), + (byte)(runtimeMetadata.InHeadersSize + Unsafe.SizeOf()), + (byte)(runtimeMetadata.OutHeadersSize + Unsafe.SizeOf()), + 0, + 0); + } + + public override Result PrepareForProcess(ref ServiceDispatchContext context, ServerMessageRuntimeMetadata runtimeMetadata) + { + if (_implMetadata.InObjectsCount != InObjectsCount) + { + return SfResult.InvalidInObjectsCount; + } + + Result result = _domain.ReserveIds(new Span(_reservedObjectIds)[..OutObjectsCount]); + + if (result.IsFailure) + { + return result; + } + + return _implProcessor.PrepareForProcess(ref context, runtimeMetadata); + } + + public override Result GetInObjects(Span inObjects) + { + for (int i = 0; i < InObjectsCount; i++) + { + inObjects[i] = _domain.GetObject(_inObjectIds[i]); + } + + return Result.Success; + } + + public override HipcMessageData PrepareForReply(scoped ref ServiceDispatchContext context, out Span outRawData, ServerMessageRuntimeMetadata runtimeMetadata) + { + var response = _implProcessor.PrepareForReply(ref context, out outRawData, runtimeMetadata); + + int outHeaderSize = Unsafe.SizeOf(); + int implOutDataTotalSize = ImplOutDataTotalSize; + + DebugUtil.Assert(outHeaderSize + implOutDataTotalSize + OutObjectsCount * sizeof(int) <= outRawData.Length); + + outRawData = outRawData[outHeaderSize..]; + _outObjectIdsOffset = (response.DataWords.Length * sizeof(uint) - outRawData.Length) + implOutDataTotalSize; + + return response; + } + + public override void PrepareForErrorReply(scoped ref ServiceDispatchContext context, out Span outRawData, ServerMessageRuntimeMetadata runtimeMetadata) + { + _implProcessor.PrepareForErrorReply(ref context, out outRawData, runtimeMetadata); + + int outHeaderSize = Unsafe.SizeOf(); + int implOutDataTotalSize = ImplOutDataTotalSize; + + DebugUtil.Assert(outHeaderSize + implOutDataTotalSize <= outRawData.Length); + + outRawData = outRawData[outHeaderSize..]; + + _domain.UnreserveIds(new Span(_reservedObjectIds)[..OutObjectsCount]); + } + + public override void SetOutObjects(scoped ref ServiceDispatchContext context, HipcMessageData response, Span outObjects) + { + int outObjectsCount = OutObjectsCount; + Span objectIds = _reservedObjectIds; + + for (int i = 0; i < outObjectsCount; i++) + { + if (outObjects[i] == null) + { + _domain.UnreserveIds(objectIds.Slice(i, 1)); + objectIds[i] = 0; + continue; + } + + _domain.RegisterObject(objectIds[i], outObjects[i]); + } + + Span outObjectIds = MemoryMarshal.Cast(MemoryMarshal.Cast(response.DataWords)[_outObjectIdsOffset..]); + + for (int i = 0; i < outObjectsCount; i++) + { + outObjectIds[i] = objectIds[i]; + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs new file mode 100644 index 00000000..b3c4aae0 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs @@ -0,0 +1,69 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + struct HandlesToClose + { + private int _handle0; + private int _handle1; + private int _handle2; + private int _handle3; + private int _handle4; + private int _handle5; + private int _handle6; + private int _handle7; + + public int Count; + + public int this[int index] + { + readonly get + { + return index switch + { + 0 => _handle0, + 1 => _handle1, + 2 => _handle2, + 3 => _handle3, + 4 => _handle4, + 5 => _handle5, + 6 => _handle6, + 7 => _handle7, + _ => throw new IndexOutOfRangeException(), + }; + } + set + { + switch (index) + { + case 0: + _handle0 = value; + break; + case 1: + _handle1 = value; + break; + case 2: + _handle2 = value; + break; + case 3: + _handle3 = value; + break; + case 4: + _handle4 = value; + break; + case 5: + _handle5 = value; + break; + case 6: + _handle6 = value; + break; + case 7: + _handle7 = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs new file mode 100644 index 00000000..ddb6943f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + class InlineContext + { + public static int Set(int newContext) + { + // TODO: Implement (will require FS changes???) + return newContext; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs new file mode 100644 index 00000000..23780c7c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + readonly struct PointerAndSize + { + public static PointerAndSize Empty => new(0UL, 0UL); + + public ulong Address { get; } + public ulong Size { get; } + public bool IsEmpty => Size == 0UL; + + public PointerAndSize(ulong address, ulong size) + { + Address = address; + Size = size; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs new file mode 100644 index 00000000..0126d1f6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + readonly struct ScopedInlineContextChange : IDisposable + { + private readonly int _previousContext; + + public ScopedInlineContextChange(int newContext) + { + _previousContext = InlineContext.Set(newContext); + } + + public void Dispose() + { + InlineContext.Set(_previousContext); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs new file mode 100644 index 00000000..fc6350f7 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs @@ -0,0 +1,15 @@ +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + abstract class ServerDomainBase + { + public abstract Result ReserveIds(Span outIds); + public abstract void UnreserveIds(ReadOnlySpan ids); + public abstract void RegisterObject(int id, ServiceObjectHolder obj); + + public abstract ServiceObjectHolder UnregisterObject(int id); + public abstract ServiceObjectHolder GetObject(int id); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs new file mode 100644 index 00000000..7762345a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs @@ -0,0 +1,252 @@ +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + class ServerDomainManager + { + private class EntryManager + { + public class Entry + { + public int Id { get; } + public Domain Owner { get; set; } + public ServiceObjectHolder Obj { get; set; } + public LinkedListNode Node { get; set; } + + public Entry(int id) + { + Id = id; + } + } + + private readonly LinkedList _freeList; + private readonly Entry[] _entries; + + public EntryManager(int count) + { + _freeList = new LinkedList(); + _entries = new Entry[count]; + + for (int i = 0; i < count; i++) + { + _freeList.AddLast(_entries[i] = new Entry(i + 1)); + } + } + + public Entry AllocateEntry() + { + lock (_freeList) + { + if (_freeList.Count == 0) + { + return null; + } + + var entry = _freeList.First.Value; + _freeList.RemoveFirst(); + return entry; + } + } + + public void FreeEntry(Entry entry) + { + lock (_freeList) + { + DebugUtil.Assert(entry.Owner == null); + DebugUtil.Assert(entry.Obj == null); + _freeList.AddFirst(entry); + } + } + + public Entry GetEntry(int id) + { + if (id == 0) + { + return null; + } + + int index = id - 1; + + if ((uint)index >= (uint)_entries.Length) + { + return null; + } + + return _entries[index]; + } + } + + private class Domain : DomainServiceObject, IDisposable + { + private readonly ServerDomainManager _manager; + private readonly LinkedList _entries; + + public Domain(ServerDomainManager manager) + { + _manager = manager; + _entries = new LinkedList(); + } + + public override ServiceObjectHolder GetObject(int id) + { + var entry = _manager._entryManager.GetEntry(id); + if (entry == null) + { + return null; + } + + lock (_manager._entryOwnerLock) + { + if (entry.Owner != this) + { + return null; + } + } + + return entry.Obj.Clone(); + } + + public override ServerDomainBase GetServerDomain() + { + return this; + } + + public override void RegisterObject(int id, ServiceObjectHolder obj) + { + var entry = _manager._entryManager.GetEntry(id); + DebugUtil.Assert(entry != null); + + lock (_manager._entryOwnerLock) + { + DebugUtil.Assert(entry.Owner == null); + entry.Owner = this; + entry.Node = _entries.AddLast(entry); + } + + entry.Obj = obj; + } + + public override Result ReserveIds(Span outIds) + { + for (int i = 0; i < outIds.Length; i++) + { + var entry = _manager._entryManager.AllocateEntry(); + if (entry == null) + { + return SfResult.OutOfDomainEntries; + } + + DebugUtil.Assert(entry.Owner == null); + + outIds[i] = entry.Id; + } + + return Result.Success; + } + + public override ServiceObjectHolder UnregisterObject(int id) + { + var entry = _manager._entryManager.GetEntry(id); + if (entry == null) + { + return null; + } + + ServiceObjectHolder obj; + + lock (_manager._entryOwnerLock) + { + if (entry.Owner != this) + { + return null; + } + + entry.Owner = null; + obj = entry.Obj; + + if (obj.ServiceObject is IDisposable disposableObj) + { + disposableObj.Dispose(); + } + + entry.Obj = null; + _entries.Remove(entry.Node); + entry.Node = null; + } + + _manager._entryManager.FreeEntry(entry); + + return obj; + } + + public override void UnreserveIds(ReadOnlySpan ids) + { + for (int i = 0; i < ids.Length; i++) + { + var entry = _manager._entryManager.GetEntry(ids[i]); + + DebugUtil.Assert(entry != null); + DebugUtil.Assert(entry.Owner == null); + + _manager._entryManager.FreeEntry(entry); + } + } + + public void Dispose() + { + foreach (var entry in _entries) + { + if (entry.Obj.ServiceObject is IDisposable disposableObj) + { + disposableObj.Dispose(); + } + } + + _manager.FreeDomain(this); + } + } + + private readonly EntryManager _entryManager; + private readonly object _entryOwnerLock; + private readonly HashSet _domains; + private readonly int _maxDomains; + + public ServerDomainManager(int entryCount, int maxDomains) + { + _entryManager = new EntryManager(entryCount); + _entryOwnerLock = new object(); + _domains = new HashSet(); + _maxDomains = maxDomains; + } + + public DomainServiceObject AllocateDomainServiceObject() + { + lock (_domains) + { + if (_domains.Count == _maxDomains) + { + return null; + } + + var domain = new Domain(this); + _domains.Add(domain); + return domain; + } + } + + public static void DestroyDomainServiceObject(DomainServiceObject obj) + { + ((Domain)obj).Dispose(); + } + + private void FreeDomain(Domain domain) + { + lock (_domains) + { + _domains.Remove(domain); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs new file mode 100644 index 00000000..a682f716 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs @@ -0,0 +1,18 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + abstract class ServerMessageProcessor + { + public abstract void SetImplementationProcessor(ServerMessageProcessor impl); + public abstract ServerMessageRuntimeMetadata GetRuntimeMetadata(); + + public abstract Result PrepareForProcess(scoped ref ServiceDispatchContext context, ServerMessageRuntimeMetadata runtimeMetadata); + public abstract Result GetInObjects(Span inObjects); + public abstract HipcMessageData PrepareForReply(scoped ref ServiceDispatchContext context, out Span outRawData, ServerMessageRuntimeMetadata runtimeMetadata); + public abstract void PrepareForErrorReply(scoped ref ServiceDispatchContext context, out Span outRawData, ServerMessageRuntimeMetadata runtimeMetadata); + public abstract void SetOutObjects(scoped ref ServiceDispatchContext context, HipcMessageData response, Span outObjects); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs new file mode 100644 index 00000000..d79f852a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + readonly struct ServerMessageRuntimeMetadata + { + public ushort InDataSize { get; } + public ushort OutDataSize { get; } + public byte InHeadersSize { get; } + public byte OutHeadersSize { get; } + public byte InObjectsCount { get; } + public byte OutObjectsCount { get; } + + public int UnfixedOutPointerSizeOffset => InDataSize + InHeadersSize + 0x10; + + public ServerMessageRuntimeMetadata( + ushort inDataSize, + ushort outDataSize, + byte inHeadersSize, + byte outHeadersSize, + byte inObjectsCount, + byte outObjectsCount) + { + InDataSize = inDataSize; + OutDataSize = outDataSize; + InHeadersSize = inHeadersSize; + OutHeadersSize = outHeadersSize; + InObjectsCount = inObjectsCount; + OutObjectsCount = outObjectsCount; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs new file mode 100644 index 00000000..4ebe294b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs @@ -0,0 +1,18 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + ref struct ServiceDispatchContext + { + public IServiceObject ServiceObject; + public ServerSessionManager Manager; + public ServerSession Session; + public ServerMessageProcessor Processor; + public HandlesToClose HandlesToClose; + public PointerAndSize PointerBuffer; + public ReadOnlySpan InMessageBuffer; + public Span OutMessageBuffer; + public HipcMessage Request; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs new file mode 100644 index 00000000..008af90a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + readonly struct ServiceDispatchMeta + { + public ServiceDispatchTableBase DispatchTable { get; } + + public ServiceDispatchMeta(ServiceDispatchTableBase dispatchTable) + { + DispatchTable = dispatchTable; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs new file mode 100644 index 00000000..ef0372aa --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs @@ -0,0 +1,33 @@ +using Ryujinx.Horizon.Common; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + class ServiceDispatchTable : ServiceDispatchTableBase + { + private readonly string _objectName; + private readonly IReadOnlyDictionary _entries; + + public ServiceDispatchTable(string objectName, IReadOnlyDictionary entries) + { + _objectName = objectName; + _entries = entries; + } + + public override Result ProcessMessage(ref ServiceDispatchContext context, ReadOnlySpan inRawData) + { + return ProcessMessageImpl(ref context, inRawData, _entries, _objectName); + } + + public static ServiceDispatchTableBase Create(IServiceObject instance) + { + if (instance is DomainServiceObject) + { + return new DomainServiceObjectDispatchTable(); + } + + return new ServiceDispatchTable(instance.GetType().Name, instance.GetCommandHandlers()); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs new file mode 100644 index 00000000..2625a4c3 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs @@ -0,0 +1,94 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + abstract class ServiceDispatchTableBase + { + private const uint MaxCmifVersion = 1; + + public abstract Result ProcessMessage(ref ServiceDispatchContext context, ReadOnlySpan inRawData); + + protected static Result ProcessMessageImpl(ref ServiceDispatchContext context, ReadOnlySpan inRawData, IReadOnlyDictionary entries, string objectName) + { + if (inRawData.Length < Unsafe.SizeOf()) + { + Logger.Warning?.Print(LogClass.KernelIpc, $"Request message size 0x{inRawData.Length:X} is invalid"); + + return SfResult.InvalidHeaderSize; + } + + CmifInHeader inHeader = MemoryMarshal.Cast(inRawData)[0]; + + if (inHeader.Magic != CmifMessage.CmifInHeaderMagic || inHeader.Version > MaxCmifVersion) + { + Logger.Warning?.Print(LogClass.KernelIpc, $"Request message header magic value 0x{inHeader.Magic:X} is invalid"); + + return SfResult.InvalidInHeader; + } + + ReadOnlySpan inMessageRawData = inRawData[Unsafe.SizeOf()..]; + uint commandId = inHeader.CommandId; + + var outHeader = Span.Empty; + + if (!entries.TryGetValue((int)commandId, out var commandHandler)) + { + if (HorizonStatic.Options.IgnoreMissingServices) + { + // If ignore missing services is enabled, just pretend that everything is fine. + PrepareForStubReply(ref context, out Span outRawData); + CommandHandler.GetCmifOutHeaderPointer(ref outHeader, ref outRawData); + outHeader[0] = new CmifOutHeader { Magic = CmifMessage.CmifOutHeaderMagic, Result = Result.Success }; + + Logger.Warning?.Print(LogClass.Service, $"Missing service {objectName} (command ID: {commandId}) ignored"); + + return Result.Success; + } + else if (HorizonStatic.Options.ThrowOnInvalidCommandIds) + { + throw new NotImplementedException($"{objectName} command ID: {commandId} is not implemented"); + } + + return SfResult.UnknownCommandId; + } + + Logger.Trace?.Print(LogClass.KernelIpc, $"{objectName}.{commandHandler.MethodName} called"); + + Result commandResult = commandHandler.Invoke(ref outHeader, ref context, inMessageRawData); + + if (commandResult.Module == SfResult.ModuleId || + commandResult.Module == HipcResult.ModuleId) + { + Logger.Warning?.Print(LogClass.KernelIpc, $"{commandHandler.MethodName} returned error {commandResult}"); + } + + if (SfResult.RequestContextChanged(commandResult)) + { + return commandResult; + } + + if (outHeader.IsEmpty) + { + commandResult.AbortOnSuccess(); + + return commandResult; + } + + outHeader[0] = new CmifOutHeader { Magic = CmifMessage.CmifOutHeaderMagic, Result = commandResult }; + + return Result.Success; + } + + private static void PrepareForStubReply(scoped ref ServiceDispatchContext context, out Span outRawData) + { + var response = HipcMessage.WriteResponse(context.OutMessageBuffer, 0, 0x20 / sizeof(uint), 0, 0); + outRawData = MemoryMarshal.Cast(response.DataWords); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs new file mode 100644 index 00000000..4dd43537 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs @@ -0,0 +1,34 @@ +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Cmif +{ + class ServiceObjectHolder + { + public IServiceObject ServiceObject { get; } + + private readonly ServiceDispatchMeta _dispatchMeta; + + public ServiceObjectHolder(ServiceObjectHolder objectHolder) + { + ServiceObject = objectHolder.ServiceObject; + _dispatchMeta = objectHolder._dispatchMeta; + } + + public ServiceObjectHolder(IServiceObject serviceImpl) + { + ServiceObject = serviceImpl; + _dispatchMeta = new ServiceDispatchMeta(ServiceDispatchTable.Create(serviceImpl)); + } + + public ServiceObjectHolder Clone() + { + return new ServiceObjectHolder(this); + } + + public Result ProcessMessage(ref ServiceDispatchContext context, ReadOnlySpan inRawData) + { + return _dispatchMeta.DispatchTable.ProcessMessage(ref context, inRawData); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs b/src/Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs new file mode 100644 index 00000000..5af7d156 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + class CmifCommandAttribute : Attribute + { + public uint CommandId { get; } + + public CmifCommandAttribute(uint commandId) + { + CommandId = commandId; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs b/src/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs new file mode 100644 index 00000000..48407a73 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs @@ -0,0 +1,56 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + enum CommandArgType : byte + { + Invalid, + + Buffer, + InArgument, + InCopyHandle, + InMoveHandle, + InObject, + OutArgument, + OutCopyHandle, + OutMoveHandle, + OutObject, + ProcessId, + } + + readonly struct CommandArg + { + public CommandArgType Type { get; } + public HipcBufferFlags BufferFlags { get; } + public ushort BufferFixedSize { get; } + public int ArgSize { get; } + public int ArgAlignment { get; } + + public CommandArg(CommandArgType type) + { + Type = type; + BufferFlags = default; + BufferFixedSize = 0; + ArgSize = 0; + ArgAlignment = 0; + } + + public CommandArg(CommandArgType type, int argSize, int argAlignment) + { + Type = type; + BufferFlags = default; + BufferFixedSize = 0; + ArgSize = argSize; + ArgAlignment = argAlignment; + } + + public CommandArg(HipcBufferFlags flags, ushort fixedSize = 0) + { + Type = CommandArgType.Buffer; + BufferFlags = flags; + BufferFixedSize = fixedSize; + ArgSize = 0; + ArgAlignment = 0; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs b/src/Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs new file mode 100644 index 00000000..f6f0e068 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs @@ -0,0 +1,38 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + [AttributeUsage(AttributeTargets.Parameter)] + class BufferAttribute : Attribute + { + public HipcBufferFlags Flags { get; } + public ushort FixedSize { get; } + + public BufferAttribute(HipcBufferFlags flags) + { + Flags = flags; + } + + public BufferAttribute(HipcBufferFlags flags, ushort fixedSize) + { + Flags = flags | HipcBufferFlags.FixedSize; + FixedSize = fixedSize; + } + } + + [AttributeUsage(AttributeTargets.Parameter)] + class ClientProcessIdAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter)] + class CopyHandleAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Parameter)] + class MoveHandleAttribute : Attribute + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs b/src/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs new file mode 100644 index 00000000..d0efe0d4 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs @@ -0,0 +1,52 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + class CommandHandler + { + public delegate Result MethodInvoke( + ref ServiceDispatchContext context, + HipcCommandProcessor processor, + ServerMessageRuntimeMetadata runtimeMetadata, + ReadOnlySpan inRawData, + ref Span outHeader); + + private readonly MethodInvoke _invoke; + private readonly HipcCommandProcessor _processor; + + public string MethodName => _invoke.Method.Name; + + public CommandHandler(MethodInvoke invoke, params CommandArg[] args) + { + _invoke = invoke; + _processor = new HipcCommandProcessor(args); + } + + public Result Invoke(ref Span outHeader, ref ServiceDispatchContext context, ReadOnlySpan inRawData) + { + if (context.Processor == null) + { + context.Processor = _processor; + } + else + { + context.Processor.SetImplementationProcessor(_processor); + } + + var runtimeMetadata = context.Processor.GetRuntimeMetadata(); + Result result = context.Processor.PrepareForProcess(ref context, runtimeMetadata); + + return result.IsFailure ? result : _invoke(ref context, _processor, runtimeMetadata, inRawData, ref outHeader); + } + + public static void GetCmifOutHeaderPointer(ref Span outHeader, ref Span outRawData) + { + outHeader = MemoryMarshal.Cast(outRawData)[..1]; + outRawData = outRawData[Unsafe.SizeOf()..]; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs b/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs new file mode 100644 index 00000000..7f528464 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs @@ -0,0 +1,75 @@ +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + static class CommandSerialization + { + public static ReadOnlySequence GetReadOnlySequence(PointerAndSize bufferRange) + { + return HorizonStatic.AddressSpace.GetReadOnlySequence(bufferRange.Address, checked((int)bufferRange.Size)); + } + + public static ReadOnlySpan GetReadOnlySpan(PointerAndSize bufferRange) + { + return HorizonStatic.AddressSpace.GetSpan(bufferRange.Address, checked((int)bufferRange.Size)); + } + + public static WritableRegion GetWritableRegion(PointerAndSize bufferRange) + { + return HorizonStatic.AddressSpace.GetWritableRegion(bufferRange.Address, checked((int)bufferRange.Size)); + } + + public static ref T GetRef(PointerAndSize bufferRange) where T : unmanaged + { + var writableRegion = GetWritableRegion(bufferRange); + + return ref MemoryMarshal.Cast(writableRegion.Memory.Span)[0]; + } + + public static object DeserializeArg(ref ServiceDispatchContext context, ReadOnlySpan inRawData, int offset) where T : unmanaged + { + return MemoryMarshal.Cast(inRawData.Slice(offset, Unsafe.SizeOf()))[0]; + } + + public static T DeserializeArg(ReadOnlySpan inRawData, int offset) where T : unmanaged + { + return MemoryMarshal.Cast(inRawData.Slice(offset, Unsafe.SizeOf()))[0]; + } + + public static ulong DeserializeClientProcessId(ref ServiceDispatchContext context) + { + return context.Request.Pid; + } + + public static int DeserializeCopyHandle(ref ServiceDispatchContext context, int index) + { + return context.Request.Data.CopyHandles[index]; + } + + public static int DeserializeMoveHandle(ref ServiceDispatchContext context, int index) + { + return context.Request.Data.MoveHandles[index]; + } + + public static void SerializeArg(Span outRawData, int offset, T value) where T : unmanaged + { + MemoryMarshal.Cast(outRawData.Slice(offset, Unsafe.SizeOf()))[0] = value; + } + + public static void SerializeCopyHandle(HipcMessageData response, int index, int value) + { + response.CopyHandles[index] = value; + } + + public static void SerializeMoveHandle(HipcMessageData response, int index, int value) + { + response.MoveHandles[index] = value; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs new file mode 100644 index 00000000..5f3f6706 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs @@ -0,0 +1,81 @@ +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + static class Api + { + public const int TlsMessageBufferSize = 0x100; + + public static Result Receive(out ReceiveResult recvResult, int sessionHandle, Span messageBuffer) + { + Result result = ReceiveImpl(sessionHandle, messageBuffer); + + if (result == KernelResult.PortRemoteClosed) + { + recvResult = ReceiveResult.Closed; + + return Result.Success; + } + else if (result == KernelResult.ReceiveListBroken) + { + recvResult = ReceiveResult.NeedsRetry; + + return Result.Success; + } + + recvResult = ReceiveResult.Success; + + return result; + } + + private static Result ReceiveImpl(int sessionHandle, Span messageBuffer) + { + Span handles = stackalloc int[1]; + + handles[0] = sessionHandle; + + var tlsSpan = HorizonStatic.AddressSpace.GetSpan(HorizonStatic.ThreadContext.TlsAddress, TlsMessageBufferSize); + + if (messageBuffer == tlsSpan) + { + return HorizonStatic.Syscall.ReplyAndReceive(out _, handles, 0, -1L); + } + + throw new NotImplementedException(); + } + + public static Result Reply(int sessionHandle, ReadOnlySpan messageBuffer) + { + Result result = ReplyImpl(sessionHandle, messageBuffer); + + result.AbortUnless(KernelResult.TimedOut, KernelResult.PortRemoteClosed); + + return Result.Success; + } + + private static Result ReplyImpl(int sessionHandle, ReadOnlySpan messageBuffer) + { + var tlsSpan = HorizonStatic.AddressSpace.GetSpan(HorizonStatic.ThreadContext.TlsAddress, TlsMessageBufferSize); + + if (messageBuffer == tlsSpan) + { + return HorizonStatic.Syscall.ReplyAndReceive(out _, ReadOnlySpan.Empty, sessionHandle, 0); + } + + throw new NotImplementedException(); + } + + public static Result CreateSession(out int serverHandle, out int clientHandle) + { + Result result = HorizonStatic.Syscall.CreateSession(out serverHandle, out clientHandle, false, null); + + if (result == KernelResult.OutOfResource) + { + return HipcResult.OutOfSessions; + } + + return result; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs new file mode 100644 index 00000000..04abf693 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs @@ -0,0 +1,65 @@ +using Ryujinx.Common.Utilities; +using Ryujinx.Horizon.Sdk.Sf.Cmif; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + struct Header + { + private uint _word0; + private uint _word1; + + public CommandType Type + { + readonly get => (CommandType)_word0.Extract(0, 16); + set => _word0 = _word0.Insert(0, 16, (uint)value); + } + + public int SendStaticsCount + { + readonly get => (int)_word0.Extract(16, 4); + set => _word0 = _word0.Insert(16, 4, (uint)value); + } + + public int SendBuffersCount + { + readonly get => (int)_word0.Extract(20, 4); + set => _word0 = _word0.Insert(20, 4, (uint)value); + } + + public int ReceiveBuffersCount + { + readonly get => (int)_word0.Extract(24, 4); + set => _word0 = _word0.Insert(24, 4, (uint)value); + } + + public int ExchangeBuffersCount + { + readonly get => (int)_word0.Extract(28, 4); + set => _word0 = _word0.Insert(28, 4, (uint)value); + } + + public int DataWordsCount + { + readonly get => (int)_word1.Extract(0, 10); + set => _word1 = _word1.Insert(0, 10, (uint)value); + } + + public int ReceiveStaticMode + { + readonly get => (int)_word1.Extract(10, 4); + set => _word1 = _word1.Insert(10, 4, (uint)value); + } + + public int ReceiveListOffset + { + readonly get => (int)_word1.Extract(20, 11); + set => _word1 = _word1.Insert(20, 11, (uint)value); + } + + public bool HasSpecialHeader + { + readonly get => _word1.Extract(31); + set => _word1 = _word1.Insert(31, value); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs new file mode 100644 index 00000000..4e962894 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + readonly struct HipcBufferDescriptor + { +#pragma warning disable CS0649 // Field is never assigned to + private readonly uint _sizeLow; + private readonly uint _addressLow; + private readonly uint _word2; +#pragma warning restore CS0649 + + public ulong Address => _addressLow | (((ulong)_word2 << 4) & 0xf00000000UL) | (((ulong)_word2 << 34) & 0x7000000000UL); + public ulong Size => _sizeLow | ((ulong)_word2 << 8) & 0xf00000000UL; + public HipcBufferMode Mode => (HipcBufferMode)(_word2 & 3); + + public HipcBufferDescriptor(ulong address, ulong size, HipcBufferMode mode) + { + _sizeLow = (uint)size; + _addressLow = (uint)address; + _word2 = (uint)mode | ((uint)(address >> 34) & 0x1c) | ((uint)(size >> 32) << 24) | ((uint)(address >> 4) & 0xf0000000); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs new file mode 100644 index 00000000..b3995613 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + [Flags] + enum HipcBufferFlags : byte + { + In = 1 << 0, + Out = 1 << 1, + MapAlias = 1 << 2, + Pointer = 1 << 3, + FixedSize = 1 << 4, + AutoSelect = 1 << 5, + MapTransferAllowsNonSecure = 1 << 6, + MapTransferAllowsNonDevice = 1 << 7, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs new file mode 100644 index 00000000..ffd46c5e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + enum HipcBufferMode + { + Normal = 0, + NonSecure = 1, + Invalid = 2, + NonDevice = 3, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs new file mode 100644 index 00000000..4f0bbb01 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs @@ -0,0 +1,115 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + partial class HipcManager : IServiceObject + { + private readonly ServerDomainSessionManager _manager; + private readonly ServerSession _session; + + public HipcManager(ServerDomainSessionManager manager, ServerSession session) + { + _manager = manager; + _session = session; + } + + [CmifCommand(0)] + public Result ConvertCurrentObjectToDomain(out int objectId) + { + objectId = 0; + + var domain = _manager.Domain.AllocateDomainServiceObject(); + if (domain == null) + { + return HipcResult.OutOfDomains; + } + + bool succeeded = false; + + try + { + Span objectIds = stackalloc int[1]; + + Result result = domain.ReserveIds(objectIds); + + if (result.IsFailure) + { + return result; + } + + objectId = objectIds[0]; + succeeded = true; + } + finally + { + if (!succeeded) + { + ServerDomainManager.DestroyDomainServiceObject(domain); + } + } + + domain.RegisterObject(objectId, _session.ServiceObjectHolder); + _session.ServiceObjectHolder = new ServiceObjectHolder(domain); + + return Result.Success; + } + + [CmifCommand(1)] + public Result CopyFromCurrentDomain([MoveHandle] out int clientHandle, int objectId) + { + clientHandle = 0; + + if (_session.ServiceObjectHolder.ServiceObject is not DomainServiceObject domain) + { + return HipcResult.TargetNotDomain; + } + + var obj = domain.GetObject(objectId); + if (obj == null) + { + return HipcResult.DomainObjectNotFound; + } + + Api.CreateSession(out int serverHandle, out clientHandle).AbortOnFailure(); + _manager.RegisterSession(serverHandle, obj).AbortOnFailure(); + + return Result.Success; + } + + [CmifCommand(2)] + public Result CloneCurrentObject([MoveHandle] out int clientHandle) + { + return CloneCurrentObjectImpl(out clientHandle, _manager); + } + + [CmifCommand(3)] + public void QueryPointerBufferSize(out ushort size) + { + size = (ushort)_session.PointerBuffer.Size; + } + + [CmifCommand(4)] + public Result CloneCurrentObjectEx([MoveHandle] out int clientHandle, uint tag) + { + return CloneCurrentObjectImpl(out clientHandle, _manager.GetSessionManagerByTag(tag)); + } + + private Result CloneCurrentObjectImpl(out int clientHandle, ServerSessionManager manager) + { + clientHandle = 0; + + var clone = _session.ServiceObjectHolder.Clone(); + if (clone == null) + { + return HipcResult.DomainObjectNotFound; + } + + Api.CreateSession(out int serverHandle, out clientHandle).AbortOnFailure(); + manager.RegisterSession(serverHandle, clone).AbortOnFailure(); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs new file mode 100644 index 00000000..73321a89 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs @@ -0,0 +1,221 @@ +using Ryujinx.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + ref struct HipcMessage + { + public const int AutoReceiveStatic = byte.MaxValue; + + public HipcMetadata Meta; + public HipcMessageData Data; + public ulong Pid; + + public HipcMessage(Span data) + { + int initialLength = data.Length; + + Header header = MemoryMarshal.Cast(data)[0]; + + data = data[Unsafe.SizeOf
()..]; + + int receiveStaticsCount = 0; + ulong pid = 0; + + if (header.ReceiveStaticMode != 0) + { + if (header.ReceiveStaticMode == 2) + { + receiveStaticsCount = AutoReceiveStatic; + } + else if (header.ReceiveStaticMode > 2) + { + receiveStaticsCount = header.ReceiveStaticMode - 2; + } + } + + SpecialHeader specialHeader = default; + + if (header.HasSpecialHeader) + { + specialHeader = MemoryMarshal.Cast(data)[0]; + data = data[Unsafe.SizeOf()..]; + + if (specialHeader.SendPid) + { + pid = MemoryMarshal.Cast(data)[0]; + data = data[sizeof(ulong)..]; + } + } + + Meta = new HipcMetadata + { + Type = (int)header.Type, + SendStaticsCount = header.SendStaticsCount, + SendBuffersCount = header.SendBuffersCount, + ReceiveBuffersCount = header.ReceiveBuffersCount, + ExchangeBuffersCount = header.ExchangeBuffersCount, + DataWordsCount = header.DataWordsCount, + ReceiveStaticsCount = receiveStaticsCount, + SendPid = specialHeader.SendPid, + CopyHandlesCount = specialHeader.CopyHandlesCount, + MoveHandlesCount = specialHeader.MoveHandlesCount, + }; + + Data = CreateMessageData(Meta, data, initialLength); + Pid = pid; + } + + public static HipcMessageData WriteResponse( + Span destination, + int sendStaticCount, + int dataWordsCount, + int copyHandlesCount, + int moveHandlesCount) + { + return WriteMessage(destination, new HipcMetadata + { + SendStaticsCount = sendStaticCount, + DataWordsCount = dataWordsCount, + CopyHandlesCount = copyHandlesCount, + MoveHandlesCount = moveHandlesCount, + }); + } + + public static HipcMessageData WriteMessage(Span destination, HipcMetadata meta) + { + int initialLength = destination.Length; + bool hasSpecialHeader = meta.SendPid || meta.CopyHandlesCount != 0 || meta.MoveHandlesCount != 0; + + MemoryMarshal.Cast(destination)[0] = new Header + { + Type = (CommandType)meta.Type, + SendStaticsCount = meta.SendStaticsCount, + SendBuffersCount = meta.SendBuffersCount, + ReceiveBuffersCount = meta.ReceiveBuffersCount, + ExchangeBuffersCount = meta.ExchangeBuffersCount, + DataWordsCount = meta.DataWordsCount, + ReceiveStaticMode = meta.ReceiveStaticsCount != 0 ? (meta.ReceiveStaticsCount != AutoReceiveStatic ? meta.ReceiveStaticsCount + 2 : 2) : 0, + HasSpecialHeader = hasSpecialHeader, + }; + + destination = destination[Unsafe.SizeOf
()..]; + + if (hasSpecialHeader) + { + MemoryMarshal.Cast(destination)[0] = new SpecialHeader + { + SendPid = meta.SendPid, + CopyHandlesCount = meta.CopyHandlesCount, + MoveHandlesCount = meta.MoveHandlesCount, + }; + + destination = destination[Unsafe.SizeOf()..]; + + if (meta.SendPid) + { + destination = destination[sizeof(ulong)..]; + } + } + + return CreateMessageData(meta, destination, initialLength); + } + + private static HipcMessageData CreateMessageData(HipcMetadata meta, Span data, int initialLength) + { + Span copyHandles = Span.Empty; + + if (meta.CopyHandlesCount != 0) + { + copyHandles = MemoryMarshal.Cast(data)[..meta.CopyHandlesCount]; + + data = data[(meta.CopyHandlesCount * sizeof(int))..]; + } + + Span moveHandles = Span.Empty; + + if (meta.MoveHandlesCount != 0) + { + moveHandles = MemoryMarshal.Cast(data)[..meta.MoveHandlesCount]; + + data = data[(meta.MoveHandlesCount * sizeof(int))..]; + } + + Span sendStatics = Span.Empty; + + if (meta.SendStaticsCount != 0) + { + sendStatics = MemoryMarshal.Cast(data)[..meta.SendStaticsCount]; + + data = data[(meta.SendStaticsCount * Unsafe.SizeOf())..]; + } + + Span sendBuffers = Span.Empty; + + if (meta.SendBuffersCount != 0) + { + sendBuffers = MemoryMarshal.Cast(data)[..meta.SendBuffersCount]; + + data = data[(meta.SendBuffersCount * Unsafe.SizeOf())..]; + } + + Span receiveBuffers = Span.Empty; + + if (meta.ReceiveBuffersCount != 0) + { + receiveBuffers = MemoryMarshal.Cast(data)[..meta.ReceiveBuffersCount]; + + data = data[(meta.ReceiveBuffersCount * Unsafe.SizeOf())..]; + } + + Span exchangeBuffers = Span.Empty; + + if (meta.ExchangeBuffersCount != 0) + { + exchangeBuffers = MemoryMarshal.Cast(data)[..meta.ExchangeBuffersCount]; + + data = data[(meta.ExchangeBuffersCount * Unsafe.SizeOf())..]; + } + + Span dataWords = Span.Empty; + Span dataWordsPadded = Span.Empty; + + if (meta.DataWordsCount != 0) + { + int dataOffset = initialLength - data.Length; + int dataOffsetAligned = BitUtils.AlignUp(dataOffset, 0x10); + int padding = (dataOffsetAligned - dataOffset) / sizeof(uint); + + dataWords = MemoryMarshal.Cast(data)[padding..meta.DataWordsCount]; + dataWordsPadded = MemoryMarshal.Cast(data)[..meta.DataWordsCount]; + + data = data[(meta.DataWordsCount * sizeof(uint))..]; + } + + Span receiveList = Span.Empty; + + if (meta.ReceiveStaticsCount != 0) + { + int receiveListSize = meta.ReceiveStaticsCount == AutoReceiveStatic ? 1 : meta.ReceiveStaticsCount; + + receiveList = MemoryMarshal.Cast(data)[..receiveListSize]; + } + + return new HipcMessageData + { + SendStatics = sendStatics, + SendBuffers = sendBuffers, + ReceiveBuffers = receiveBuffers, + ExchangeBuffers = exchangeBuffers, + DataWords = dataWords, + DataWordsPadded = dataWordsPadded, + ReceiveList = receiveList, + CopyHandles = copyHandles, + MoveHandles = moveHandles, + }; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs new file mode 100644 index 00000000..0d45d756 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs @@ -0,0 +1,17 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + ref struct HipcMessageData + { + public Span SendStatics; + public Span SendBuffers; + public Span ReceiveBuffers; + public Span ExchangeBuffers; + public Span DataWords; + public Span DataWordsPadded; + public Span ReceiveList; + public Span CopyHandles; + public Span MoveHandles; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs new file mode 100644 index 00000000..add17ce1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + struct HipcMetadata + { + public int Type; + public int SendStaticsCount; + public int SendBuffersCount; + public int ReceiveBuffersCount; + public int ExchangeBuffersCount; + public int DataWordsCount; + public int ReceiveStaticsCount; + public bool SendPid; + public int CopyHandlesCount; + public int MoveHandlesCount; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs new file mode 100644 index 00000000..9a7c23e9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + readonly struct HipcReceiveListEntry + { +#pragma warning disable IDE0052 // Remove unread private member + private readonly uint _addressLow; + private readonly uint _word1; +#pragma warning restore IDE0052 + + public HipcReceiveListEntry(ulong address, ulong size) + { + _addressLow = (uint)address; + _word1 = (ushort)(address >> 32) | (uint)(size << 16); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs new file mode 100644 index 00000000..faf5dc41 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs @@ -0,0 +1,21 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + static class HipcResult + { + public const int ModuleId = 11; + +#pragma warning disable IDE0055 // Disable formatting + public static Result OutOfSessionMemory => new(ModuleId, 102); + public static Result OutOfSessions => new(ModuleId, 131); + public static Result PointerBufferTooSmall => new(ModuleId, 141); + public static Result OutOfDomains => new(ModuleId, 200); + public static Result InvalidRequestSize => new(ModuleId, 402); + public static Result UnknownCommandType => new(ModuleId, 403); + public static Result InvalidCmifRequest => new(ModuleId, 420); + public static Result TargetNotDomain => new(ModuleId, 491); + public static Result DomainObjectNotFound => new(ModuleId, 492); + #pragma warning restore IDE0055 + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs new file mode 100644 index 00000000..3d11d021 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + readonly struct HipcStaticDescriptor + { + private readonly ulong _data; + + public ulong Address => ((((_data >> 2) & 0x70) | ((_data >> 12) & 0xf)) << 32) | (_data >> 32); + public ushort Size => (ushort)(_data >> 16); + public int ReceiveIndex => (int)(_data & 0xf); + + public HipcStaticDescriptor(ulong address, ushort size, int receiveIndex) + { + ulong data = (uint)(receiveIndex & 0xf) | ((uint)size << 16); + + data |= address << 32; + data |= (address >> 20) & 0xf000; + data |= (address >> 30) & 0xffc0; + + _data = data; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs new file mode 100644 index 00000000..aa0135aa --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + readonly struct ManagerOptions + { + public static ManagerOptions Default => new(0, 0, 0, false); + + public int PointerBufferSize { get; } + public int MaxDomains { get; } + public int MaxDomainObjects { get; } + public bool CanDeferInvokeRequest { get; } + + public ManagerOptions(int pointerBufferSize, int maxDomains, int maxDomainObjects, bool canDeferInvokeRequest) + { + PointerBufferSize = pointerBufferSize; + MaxDomains = maxDomains; + MaxDomainObjects = maxDomainObjects; + CanDeferInvokeRequest = canDeferInvokeRequest; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs new file mode 100644 index 00000000..9e3a67a7 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + enum ReceiveResult + { + Success, + Closed, + NeedsRetry, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs new file mode 100644 index 00000000..935c34a8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs @@ -0,0 +1,36 @@ +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + class Server : MultiWaitHolderOfHandle + { + public int PortIndex { get; } + public int PortHandle { get; } + public ServiceName Name { get; } + public bool Managed { get; } + public ServiceObjectHolder StaticObject { get; } + + public Server( + int portIndex, + int portHandle, + ServiceName name, + bool managed, + ServiceObjectHolder staticHoder) : base(portHandle) + { + PortHandle = portHandle; + Name = name; + Managed = managed; + + if (staticHoder != null) + { + StaticObject = staticHoder; + } + else + { + PortIndex = portIndex; + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs new file mode 100644 index 00000000..daac6fc9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs @@ -0,0 +1,23 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using System; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + class ServerDomainSessionManager : ServerSessionManager + { + public ServerDomainManager Domain { get; } + + public ServerDomainSessionManager(int entryCount, int maxDomains) + { + Domain = new ServerDomainManager(entryCount, maxDomains); + } + + protected override Result DispatchManagerRequest(ServerSession session, Span inMessage, Span outMessage) + { + HipcManager hipcManager = new(this, session); + + return DispatchRequest(new ServiceObjectHolder(hipcManager), session, inMessage, outMessage); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs new file mode 100644 index 00000000..e8957b75 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs @@ -0,0 +1,197 @@ +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sm; +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + class ServerManager : ServerManagerBase, IDisposable + { + private readonly SmApi _sm; + private readonly int _pointerBufferSize; + private readonly bool _canDeferInvokeRequest; + private readonly int _maxSessions; + + private readonly ulong _pointerBuffersBaseAddress; + private readonly ulong _savedMessagesBaseAddress; + + private readonly object _resourceLock; + private readonly ulong[] _sessionAllocationBitmap; + private readonly HashSet _sessions; + private readonly HashSet _servers; + + public ServerManager(HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(sm, options) + { + _sm = sm; + _pointerBufferSize = options.PointerBufferSize; + _canDeferInvokeRequest = options.CanDeferInvokeRequest; + _maxSessions = maxSessions; + + if (allocator != null) + { + if (options.PointerBufferSize != 0) + { + _pointerBuffersBaseAddress = allocator.Allocate((ulong)maxSessions * (ulong)options.PointerBufferSize); + } + + if (options.CanDeferInvokeRequest) + { + _savedMessagesBaseAddress = allocator.Allocate((ulong)maxSessions * Api.TlsMessageBufferSize); + } + } + + _resourceLock = new object(); + _sessionAllocationBitmap = new ulong[(maxSessions + 63) / 64]; + _sessions = new HashSet(); + _servers = new HashSet(); + } + + private static PointerAndSize GetObjectBySessionIndex(ServerSession session, ulong baseAddress, ulong size) + { + return new PointerAndSize(baseAddress + (ulong)session.SessionIndex * size, size); + } + + protected override ServerSession AllocateSession(int sessionHandle, ServiceObjectHolder obj) + { + int sessionIndex = -1; + + lock (_resourceLock) + { + if (_sessions.Count >= _maxSessions) + { + return null; + } + + for (int i = 0; i < _sessionAllocationBitmap.Length; i++) + { + ref ulong mask = ref _sessionAllocationBitmap[i]; + + if (mask != ulong.MaxValue) + { + int bit = BitOperations.TrailingZeroCount(~mask); + sessionIndex = i * 64 + bit; + mask |= 1UL << bit; + + break; + } + } + + if (sessionIndex == -1) + { + return null; + } + + ServerSession session = new(sessionIndex, sessionHandle, obj); + + _sessions.Add(session); + + return session; + } + } + + protected override void FreeSession(ServerSession session) + { + if (session.ServiceObjectHolder.ServiceObject is IDisposable disposableObj) + { + disposableObj.Dispose(); + } + + lock (_resourceLock) + { + _sessionAllocationBitmap[session.SessionIndex / 64] &= ~(1UL << (session.SessionIndex & 63)); + _sessions.Remove(session); + } + } + + protected override Server AllocateServer( + int portIndex, + int portHandle, + ServiceName name, + bool managed, + ServiceObjectHolder staticHoder) + { + lock (_resourceLock) + { + Server server = new(portIndex, portHandle, name, managed, staticHoder); + + _servers.Add(server); + + return server; + } + } + + protected override void DestroyServer(Server server) + { + lock (_resourceLock) + { + server.UnlinkFromMultiWaitHolder(); + Os.FinalizeMultiWaitHolder(server); + + if (server.Managed) + { + // We should AbortOnFailure, but sometimes SM is already gone when this is called, + // so let's just ignore potential errors. + _sm.UnregisterService(server.Name); + + HorizonStatic.Syscall.CloseHandle(server.PortHandle); + } + + _servers.Remove(server); + } + } + + protected override PointerAndSize GetSessionPointerBuffer(ServerSession session) + { + if (_pointerBufferSize > 0) + { + return GetObjectBySessionIndex(session, _pointerBuffersBaseAddress, (ulong)_pointerBufferSize); + } + + return PointerAndSize.Empty; + } + + protected override PointerAndSize GetSessionSavedMessageBuffer(ServerSession session) + { + if (_canDeferInvokeRequest) + { + return GetObjectBySessionIndex(session, _savedMessagesBaseAddress, Api.TlsMessageBufferSize); + } + + return PointerAndSize.Empty; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + lock (_resourceLock) + { + ServerSession[] sessionsToClose = new ServerSession[_sessions.Count]; + + _sessions.CopyTo(sessionsToClose); + + foreach (ServerSession session in sessionsToClose) + { + CloseSessionImpl(session); + } + + Server[] serversToClose = new Server[_servers.Count]; + + _servers.CopyTo(serversToClose); + + foreach (Server server in serversToClose) + { + DestroyServer(server); + } + } + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs new file mode 100644 index 00000000..570e3c80 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs @@ -0,0 +1,329 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sm; +using System; +using System.Linq; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + class ServerManagerBase : ServerDomainSessionManager + { + private readonly SmApi _sm; + + private readonly bool _canDeferInvokeRequest; + + private readonly MultiWait _multiWait; + private readonly MultiWait _waitList; + + private readonly object _multiWaitSelectionLock; + private readonly object _waitListLock; + + private readonly Event _requestStopEvent; + private readonly Event _notifyEvent; + + private readonly MultiWaitHolderBase _requestStopEventHolder; + private readonly MultiWaitHolderBase _notifyEventHolder; + + private enum UserDataTag + { + Server = 1, + Session = 2, + } + + public ServerManagerBase(SmApi sm, ManagerOptions options) : base(options.MaxDomainObjects, options.MaxDomains) + { + _sm = sm; + _canDeferInvokeRequest = options.CanDeferInvokeRequest; + + _multiWait = new MultiWait(); + _waitList = new MultiWait(); + + _multiWaitSelectionLock = new object(); + _waitListLock = new object(); + + _requestStopEvent = new Event(EventClearMode.ManualClear); + _notifyEvent = new Event(EventClearMode.ManualClear); + + _requestStopEventHolder = new MultiWaitHolderOfEvent(_requestStopEvent); + _multiWait.LinkMultiWaitHolder(_requestStopEventHolder); + + _notifyEventHolder = new MultiWaitHolderOfEvent(_notifyEvent); + _multiWait.LinkMultiWaitHolder(_notifyEventHolder); + } + + public void RegisterObjectForServer(IServiceObject staticObject, int portHandle) + { + RegisterServerImpl(0, new ServiceObjectHolder(staticObject), portHandle); + } + + public Result RegisterObjectForServer(IServiceObject staticObject, ServiceName name, int maxSessions) + { + return RegisterServerImpl(0, new ServiceObjectHolder(staticObject), name, maxSessions); + } + + public void RegisterServer(int portIndex, int portHandle) + { + RegisterServerImpl(portIndex, null, portHandle); + } + + public Result RegisterServer(int portIndex, ServiceName name, int maxSessions) + { + return RegisterServerImpl(portIndex, null, name, maxSessions); + } + + private void RegisterServerImpl(int portIndex, ServiceObjectHolder staticHolder, int portHandle) + { + Server server = AllocateServer(portIndex, portHandle, ServiceName.Invalid, managed: false, staticHolder); + + RegisterServerImpl(server); + } + + private Result RegisterServerImpl(int portIndex, ServiceObjectHolder staticHolder, ServiceName name, int maxSessions) + { + Result result = _sm.RegisterService(out int portHandle, name, maxSessions, isLight: false); + + if (result.IsFailure) + { + return result; + } + + Server server = AllocateServer(portIndex, portHandle, name, managed: true, staticHolder); + + RegisterServerImpl(server); + + return Result.Success; + } + + private void RegisterServerImpl(Server server) + { + server.UserData = UserDataTag.Server; + + _multiWait.LinkMultiWaitHolder(server); + } + + protected virtual Result OnNeedsToAccept(int portIndex, Server server) + { + throw new NotSupportedException(); + } + + protected Result AcceptImpl(Server server, IServiceObject obj) + { + return AcceptSession(server.PortHandle, new ServiceObjectHolder(obj)); + } + + public void ServiceRequests() + { + while (WaitAndProcessRequestsImpl()) + { + } + + // Unlink pending sessions, dispose expects them to be already unlinked. + + ServerSession[] serverSessions = Enumerable.OfType(_multiWait.MultiWaits).ToArray(); + + foreach (ServerSession serverSession in serverSessions) + { + if (serverSession.IsLinked) + { + serverSession.UnlinkFromMultiWaitHolder(); + } + } + } + + public void WaitAndProcessRequests() + { + WaitAndProcessRequestsImpl(); + } + + private bool WaitAndProcessRequestsImpl() + { + try + { + MultiWaitHolder multiWait = WaitSignaled(); + + if (multiWait == null) + { + return false; + } + + DebugUtil.Assert(Process(multiWait).IsSuccess); + + return HorizonStatic.ThreadContext.Running; + } + catch (ThreadTerminatedException) + { + return false; + } + } + + private MultiWaitHolder WaitSignaled() + { + lock (_multiWaitSelectionLock) + { + while (true) + { + ProcessWaitList(); + + MultiWaitHolder selected = _multiWait.WaitAny(); + + if (selected == _requestStopEventHolder) + { + return null; + } + else if (selected == _notifyEventHolder) + { + _notifyEvent.Clear(); + } + else + { + selected.UnlinkFromMultiWaitHolder(); + + return selected; + } + } + } + } + + public void ResumeProcessing() + { + _requestStopEvent.Clear(); + } + + public void RequestStopProcessing() + { + _requestStopEvent.Signal(); + } + + protected override void RegisterSessionToWaitList(ServerSession session) + { + session.HasReceived = false; + session.UserData = UserDataTag.Session; + + RegisterToWaitList(session); + } + + private void RegisterToWaitList(MultiWaitHolder holder) + { + lock (_waitListLock) + { + _waitList.LinkMultiWaitHolder(holder); + _notifyEvent.Signal(); + } + } + + private void ProcessWaitList() + { + lock (_waitListLock) + { + _multiWait.MoveAllFrom(_waitList); + } + } + + private Result Process(MultiWaitHolder holder) + { + return (UserDataTag)holder.UserData switch + { + UserDataTag.Server => ProcessForServer(holder), + UserDataTag.Session => ProcessForSession(holder), + _ => throw new NotImplementedException(((UserDataTag)holder.UserData).ToString()), + }; + } + + private Result ProcessForServer(MultiWaitHolder holder) + { + DebugUtil.Assert((UserDataTag)holder.UserData == UserDataTag.Server); + + Server server = (Server)holder; + + try + { + if (server.StaticObject != null) + { + return AcceptSession(server.PortHandle, server.StaticObject.Clone()); + } + else + { + return OnNeedsToAccept(server.PortIndex, server); + } + } + finally + { + RegisterToWaitList(server); + } + } + + private Result ProcessForSession(MultiWaitHolder holder) + { + DebugUtil.Assert((UserDataTag)holder.UserData == UserDataTag.Session); + + ServerSession session = (ServerSession)holder; + + using var tlsMessage = HorizonStatic.AddressSpace.GetWritableRegion(HorizonStatic.ThreadContext.TlsAddress, Api.TlsMessageBufferSize); + + Result result; + + if (_canDeferInvokeRequest) + { + // If the request is deferred, we save the message on a temporary buffer to process it later. + using var savedMessage = HorizonStatic.AddressSpace.GetWritableRegion(session.SavedMessage.Address, (int)session.SavedMessage.Size); + + DebugUtil.Assert(tlsMessage.Memory.Length == savedMessage.Memory.Length); + + if (!session.HasReceived) + { + result = ReceiveRequest(session, tlsMessage.Memory.Span); + + if (result.IsFailure) + { + return result; + } + + session.HasReceived = true; + + tlsMessage.Memory.Span.CopyTo(savedMessage.Memory.Span); + } + else + { + savedMessage.Memory.Span.CopyTo(tlsMessage.Memory.Span); + } + + result = ProcessRequest(session, tlsMessage.Memory.Span); + + if (result.IsFailure && !SfResult.Invalidated(result)) + { + return result; + } + } + else + { + if (!session.HasReceived) + { + result = ReceiveRequest(session, tlsMessage.Memory.Span); + + if (result.IsFailure) + { + return result; + } + + session.HasReceived = true; + } + + result = ProcessRequest(session, tlsMessage.Memory.Span); + + if (result.IsFailure) + { + // Those results are not valid because the service does not support deferral. + if (SfResult.RequestDeferred(result) || SfResult.Invalidated(result)) + { + result.AbortOnFailure(); + } + + return result; + } + } + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs new file mode 100644 index 00000000..66d5be19 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs @@ -0,0 +1,23 @@ +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf.Cmif; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + class ServerSession : MultiWaitHolderOfHandle + { + public ServiceObjectHolder ServiceObjectHolder { get; set; } + public PointerAndSize PointerBuffer { get; set; } + public PointerAndSize SavedMessage { get; set; } + public int SessionIndex { get; } + public int SessionHandle { get; } + public bool IsClosed { get; set; } + public bool HasReceived { get; set; } + + public ServerSession(int index, int handle, ServiceObjectHolder obj) : base(handle) + { + ServiceObjectHolder = obj; + SessionIndex = index; + SessionHandle = handle; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs new file mode 100644 index 00000000..bd5a4844 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs @@ -0,0 +1,331 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sm; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + class ServerSessionManager + { + public Result AcceptSession(int portHandle, ServiceObjectHolder obj) + { + return AcceptSession(out _, portHandle, obj); + } + + private Result AcceptSession(out ServerSession session, int portHandle, ServiceObjectHolder obj) + { + return AcceptSessionImpl(out session, portHandle, obj); + } + + private Result AcceptSessionImpl(out ServerSession session, int portHandle, ServiceObjectHolder obj) + { + session = null; + + Result result = HorizonStatic.Syscall.AcceptSession(out int sessionHandle, portHandle); + + if (result.IsFailure) + { + return result; + } + + bool succeeded = false; + + try + { + result = RegisterSessionImpl(out session, sessionHandle, obj); + + if (result.IsFailure) + { + return result; + } + + succeeded = true; + } + finally + { + if (!succeeded) + { + HorizonStatic.Syscall.CloseHandle(sessionHandle); + } + } + + return Result.Success; + } + + public Result RegisterSession(int sessionHandle, ServiceObjectHolder obj) + { + return RegisterSession(out _, sessionHandle, obj); + } + + public Result RegisterSession(out ServerSession session, int sessionHandle, ServiceObjectHolder obj) + { + return RegisterSessionImpl(out session, sessionHandle, obj); + } + + private Result RegisterSessionImpl(out ServerSession session, int sessionHandle, ServiceObjectHolder obj) + { + Result result = CreateSessionImpl(out session, sessionHandle, obj); + + if (result.IsFailure) + { + return result; + } + + session.PointerBuffer = GetSessionPointerBuffer(session); + session.SavedMessage = GetSessionSavedMessageBuffer(session); + + RegisterSessionToWaitList(session); + + return Result.Success; + } + + protected virtual void RegisterSessionToWaitList(ServerSession session) + { + throw new NotSupportedException(); + } + + private Result CreateSessionImpl(out ServerSession session, int sessionHandle, ServiceObjectHolder obj) + { + session = AllocateSession(sessionHandle, obj); + + if (session == null) + { + return HipcResult.OutOfSessionMemory; + } + + return Result.Success; + } + + protected virtual ServerSession AllocateSession(int sessionHandle, ServiceObjectHolder obj) + { + throw new NotSupportedException(); + } + + protected virtual void FreeSession(ServerSession session) + { + throw new NotSupportedException(); + } + + protected virtual Server AllocateServer( + int portIndex, + int portHandle, + ServiceName name, + bool managed, + ServiceObjectHolder staticHoder) + { + throw new NotSupportedException(); + } + + protected virtual void DestroyServer(Server server) + { + throw new NotSupportedException(); + } + + protected virtual PointerAndSize GetSessionPointerBuffer(ServerSession session) + { + throw new NotSupportedException(); + } + + protected virtual PointerAndSize GetSessionSavedMessageBuffer(ServerSession session) + { + throw new NotSupportedException(); + } + + private void DestroySession(ServerSession session) + { + FreeSession(session); + } + + protected void CloseSessionImpl(ServerSession session) + { + int sessionHandle = session.Handle; + + Os.FinalizeMultiWaitHolder(session); + DestroySession(session); + HorizonStatic.Syscall.CloseHandle(sessionHandle).AbortOnFailure(); + } + + private static CommandType GetCmifCommandType(ReadOnlySpan message) + { + return MemoryMarshal.Cast(message)[0].Type; + } + + public Result ProcessRequest(ServerSession session, Span message) + { + if (session.IsClosed || GetCmifCommandType(message) == CommandType.Close) + { + CloseSessionImpl(session); + + return Result.Success; + } + + Result result = ProcessRequestImpl(session, message, message); + + if (result.IsSuccess) + { + RegisterSessionToWaitList(session); + + return Result.Success; + } + else if (SfResult.RequestContextChanged(result)) + { + return result; + } + + Logger.Warning?.Print(LogClass.KernelIpc, $"Request processing returned error {result}"); + + CloseSessionImpl(session); + + return Result.Success; + } + + private Result ProcessRequestImpl(ServerSession session, Span inMessage, Span outMessage) + { + CommandType commandType = GetCmifCommandType(inMessage); + + using var _ = new ScopedInlineContextChange(GetInlineContext(commandType, inMessage)); + + return commandType switch + { + CommandType.Request or CommandType.RequestWithContext => DispatchRequest(session.ServiceObjectHolder, session, inMessage, outMessage), + CommandType.Control or CommandType.ControlWithContext => DispatchManagerRequest(session, inMessage, outMessage), + _ => HipcResult.UnknownCommandType, + }; + } + + private static int GetInlineContext(CommandType commandType, ReadOnlySpan inMessage) + { + switch (commandType) + { + case CommandType.RequestWithContext: + case CommandType.ControlWithContext: + if (inMessage.Length >= 0x10) + { + return MemoryMarshal.Cast(inMessage)[3]; + } + break; + } + + return 0; + } + + protected static Result ReceiveRequest(ServerSession session, Span message) + { + return ReceiveRequestImpl(session, message); + } + + private static Result ReceiveRequestImpl(ServerSession session, Span message) + { + PointerAndSize pointerBuffer = session.PointerBuffer; + + while (true) + { + if (pointerBuffer.Address != 0) + { + HipcMessageData messageData = HipcMessage.WriteMessage(message, new HipcMetadata + { + Type = (int)CommandType.Invalid, + ReceiveStaticsCount = HipcMessage.AutoReceiveStatic, + }); + + messageData.ReceiveList[0] = new HipcReceiveListEntry(pointerBuffer.Address, pointerBuffer.Size); + } + else + { + MemoryMarshal.Cast(message)[0] = new Header + { + Type = CommandType.Invalid, + }; + } + + Result result = Api.Receive(out ReceiveResult recvResult, session.Handle, message); + + if (result.IsFailure) + { + return result; + } + + switch (recvResult) + { + case ReceiveResult.Success: + session.IsClosed = false; + return Result.Success; + case ReceiveResult.Closed: + session.IsClosed = true; + return Result.Success; + } + } + } + + protected virtual Result DispatchManagerRequest(ServerSession session, Span inMessage, Span outMessage) + { + return SfResult.NotSupported; + } + + protected virtual Result DispatchRequest( + ServiceObjectHolder objectHolder, + ServerSession session, + Span inMessage, + Span outMessage) + { + HipcMessage request; + + try + { + request = new HipcMessage(inMessage); + } + catch (ArgumentOutOfRangeException) + { + return HipcResult.InvalidRequestSize; + } + + var dispatchCtx = new ServiceDispatchContext + { + ServiceObject = objectHolder.ServiceObject, + Manager = this, + Session = session, + HandlesToClose = new HandlesToClose(), + PointerBuffer = session.PointerBuffer, + InMessageBuffer = inMessage, + OutMessageBuffer = outMessage, + Request = request, + }; + + ReadOnlySpan inRawData = MemoryMarshal.Cast(dispatchCtx.Request.Data.DataWords); + + int inRawSize = dispatchCtx.Request.Meta.DataWordsCount * sizeof(uint); + + if (inRawSize < 0x10) + { + return HipcResult.InvalidRequestSize; + } + + Result result = objectHolder.ProcessMessage(ref dispatchCtx, inRawData); + + if (result.IsFailure) + { + return result; + } + + result = Api.Reply(session.SessionHandle, outMessage); + + ref var handlesToClose = ref dispatchCtx.HandlesToClose; + + for (int i = 0; i < handlesToClose.Count; i++) + { + HorizonStatic.Syscall.CloseHandle(handlesToClose[i]).AbortOnFailure(); + } + + return result; + } + + public ServerSessionManager GetSessionManagerByTag(uint tag) + { + // Official FW does not do anything with the tag currently. + return this; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs new file mode 100644 index 00000000..150159fb --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs @@ -0,0 +1,27 @@ +using Ryujinx.Common.Utilities; + +namespace Ryujinx.Horizon.Sdk.Sf.Hipc +{ + struct SpecialHeader + { + private uint _word; + + public bool SendPid + { + readonly get => _word.Extract(0); + set => _word = _word.Insert(0, value); + } + + public int CopyHandlesCount + { + readonly get => (int)_word.Extract(1, 4); + set => _word = _word.Insert(1, 4, (uint)value); + } + + public int MoveHandlesCount + { + readonly get => (int)_word.Extract(5, 4); + set => _word = _word.Insert(5, 4, (uint)value); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs b/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs new file mode 100644 index 00000000..dc34f791 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs @@ -0,0 +1,430 @@ +using Ryujinx.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + class HipcCommandProcessor : ServerMessageProcessor + { + private readonly CommandArg[] _args; + + private readonly int[] _inOffsets; + private readonly int[] _outOffsets; + private readonly PointerAndSize[] _bufferRanges; + + private readonly bool _hasInProcessIdHolder; + private readonly int _inObjectsCount; + private readonly int _outObjectsCount; + private readonly int _inMapAliasBuffersCount; + private readonly int _outMapAliasBuffersCount; + private readonly int _inPointerBuffersCount; + private readonly int _outPointerBuffersCount; + private readonly int _outFixedSizePointerBuffersCount; + private readonly int _inMoveHandlesCount; + private readonly int _inCopyHandlesCount; + private readonly int _outMoveHandlesCount; + private readonly int _outCopyHandlesCount; + + public int FunctionArgumentsCount => _args.Length; + + public int InRawDataSize => BitUtils.AlignUp(_inOffsets[^1], sizeof(ushort)); + public int OutRawDataSize => BitUtils.AlignUp(_outOffsets[^1], sizeof(uint)); + + private int OutUnfixedSizePointerBuffersCount => _outPointerBuffersCount - _outFixedSizePointerBuffersCount; + + public HipcCommandProcessor(CommandArg[] args) + { + _args = args; + + foreach (CommandArg argInfo in args) + { + switch (argInfo.Type) + { + case CommandArgType.Buffer: + var flags = argInfo.BufferFlags; + + if (flags.HasFlag(HipcBufferFlags.In)) + { + if (flags.HasFlag(HipcBufferFlags.AutoSelect)) + { + _inMapAliasBuffersCount++; + _inPointerBuffersCount++; + } + else if (flags.HasFlag(HipcBufferFlags.MapAlias)) + { + _inMapAliasBuffersCount++; + } + else if (flags.HasFlag(HipcBufferFlags.Pointer)) + { + _inPointerBuffersCount++; + } + } + else + { + bool autoSelect = flags.HasFlag(HipcBufferFlags.AutoSelect); + if (autoSelect || flags.HasFlag(HipcBufferFlags.Pointer)) + { + _outPointerBuffersCount++; + + if (flags.HasFlag(HipcBufferFlags.FixedSize)) + { + _outFixedSizePointerBuffersCount++; + } + } + + if (autoSelect || flags.HasFlag(HipcBufferFlags.MapAlias)) + { + _outMapAliasBuffersCount++; + } + } + break; + case CommandArgType.InCopyHandle: + _inCopyHandlesCount++; + break; + case CommandArgType.InMoveHandle: + _inMoveHandlesCount++; + break; + case CommandArgType.InObject: + _inObjectsCount++; + break; + case CommandArgType.ProcessId: + _hasInProcessIdHolder = true; + break; + case CommandArgType.OutCopyHandle: + _outCopyHandlesCount++; + break; + case CommandArgType.OutMoveHandle: + _outMoveHandlesCount++; + break; + case CommandArgType.OutObject: + _outObjectsCount++; + break; + } + } + + _inOffsets = RawDataOffsetCalculator.Calculate(args.Where(x => x.Type == CommandArgType.InArgument).ToArray()); + _outOffsets = RawDataOffsetCalculator.Calculate(args.Where(x => x.Type == CommandArgType.OutArgument).ToArray()); + _bufferRanges = new PointerAndSize[args.Length]; + } + + public int GetInArgOffset(int argIndex) + { + return _inOffsets[argIndex]; + } + + public int GetOutArgOffset(int argIndex) + { + return _outOffsets[argIndex]; + } + + public PointerAndSize GetBufferRange(int argIndex) + { + return _bufferRanges[argIndex]; + } + + public Result ProcessBuffers(ref ServiceDispatchContext context, scoped Span isBufferMapAlias, ServerMessageRuntimeMetadata runtimeMetadata) + { + bool mapAliasBuffersValid = true; + + ulong pointerBufferTail = context.PointerBuffer.Address; + ulong pointerBufferHead = pointerBufferTail + context.PointerBuffer.Size; + + int sendMapAliasIndex = 0; + int recvMapAliasIndex = 0; + int sendPointerIndex = 0; + int unfixedRecvPointerIndex = 0; + + for (int i = 0; i < _args.Length; i++) + { + if (_args[i].Type != CommandArgType.Buffer) + { + continue; + } + + var flags = _args[i].BufferFlags; + bool isMapAlias; + + if (flags.HasFlag(HipcBufferFlags.MapAlias)) + { + isMapAlias = true; + } + else if (flags.HasFlag(HipcBufferFlags.Pointer)) + { + isMapAlias = false; + } + else /* if (flags.HasFlag(HipcBufferFlags.HipcAutoSelect)) */ + { + var descriptor = flags.HasFlag(HipcBufferFlags.In) + ? context.Request.Data.SendBuffers[sendMapAliasIndex] + : context.Request.Data.ReceiveBuffers[recvMapAliasIndex]; + + isMapAlias = descriptor.Address != 0UL; + } + + isBufferMapAlias[i] = isMapAlias; + + if (isMapAlias) + { + var descriptor = flags.HasFlag(HipcBufferFlags.In) + ? context.Request.Data.SendBuffers[sendMapAliasIndex++] + : context.Request.Data.ReceiveBuffers[recvMapAliasIndex++]; + + _bufferRanges[i] = new PointerAndSize(descriptor.Address, descriptor.Size); + + if (!IsMapTransferModeValid(flags, descriptor.Mode)) + { + mapAliasBuffersValid = false; + } + } + else + { + if (flags.HasFlag(HipcBufferFlags.In)) + { + var descriptor = context.Request.Data.SendStatics[sendPointerIndex++]; + ulong address = descriptor.Address; + ulong size = descriptor.Size; + + _bufferRanges[i] = new PointerAndSize(address, size); + + if (size != 0) + { + pointerBufferTail = Math.Max(pointerBufferTail, address + size); + } + } + else /* if (flags.HasFlag(HipcBufferFlags.Out)) */ + { + ulong size; + + if (flags.HasFlag(HipcBufferFlags.FixedSize)) + { + size = _args[i].BufferFixedSize; + } + else + { + var data = MemoryMarshal.Cast(context.Request.Data.DataWordsPadded); + var recvPointerSizes = MemoryMarshal.Cast(data[runtimeMetadata.UnfixedOutPointerSizeOffset..]); + + size = recvPointerSizes[unfixedRecvPointerIndex++]; + } + + pointerBufferHead = BitUtils.AlignDown(pointerBufferHead - size, 0x10UL); + _bufferRanges[i] = new PointerAndSize(pointerBufferHead, size); + } + } + } + + if (!mapAliasBuffersValid) + { + return HipcResult.InvalidCmifRequest; + } + + if (_outPointerBuffersCount != 0 && pointerBufferTail > pointerBufferHead) + { + return HipcResult.PointerBufferTooSmall; + } + + return Result.Success; + } + + private static bool IsMapTransferModeValid(HipcBufferFlags flags, HipcBufferMode mode) + { + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonSecure)) + { + return mode == HipcBufferMode.NonSecure; + } + + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonDevice)) + { + return mode == HipcBufferMode.NonDevice; + } + + return mode == HipcBufferMode.Normal; + } + + public void SetOutBuffers(HipcMessageData response, ReadOnlySpan isBufferMapAlias) + { + int recvPointerIndex = 0; + + for (int i = 0; i < _args.Length; i++) + { + if (_args[i].Type != CommandArgType.Buffer) + { + continue; + } + + var flags = _args[i].BufferFlags; + if (!flags.HasFlag(HipcBufferFlags.Out)) + { + continue; + } + + var buffer = _bufferRanges[i]; + + if (flags.HasFlag(HipcBufferFlags.Pointer)) + { + response.SendStatics[recvPointerIndex] = new HipcStaticDescriptor(buffer.Address, (ushort)buffer.Size, recvPointerIndex); + } + else if (flags.HasFlag(HipcBufferFlags.AutoSelect)) + { + if (!isBufferMapAlias[i]) + { + response.SendStatics[recvPointerIndex] = new HipcStaticDescriptor(buffer.Address, (ushort)buffer.Size, recvPointerIndex); + } + else + { + response.SendStatics[recvPointerIndex] = new HipcStaticDescriptor(0UL, 0, recvPointerIndex); + } + } + + recvPointerIndex++; + } + } + + public override void SetImplementationProcessor(ServerMessageProcessor impl) + { + // We don't need to do anything here as this should be always the last processor to be called. + } + + public override ServerMessageRuntimeMetadata GetRuntimeMetadata() + { + return new ServerMessageRuntimeMetadata( + (ushort)InRawDataSize, + (ushort)OutRawDataSize, + (byte)Unsafe.SizeOf(), + (byte)Unsafe.SizeOf(), + (byte)_inObjectsCount, + (byte)_outObjectsCount); + } + + public override Result PrepareForProcess(ref ServiceDispatchContext context, ServerMessageRuntimeMetadata runtimeMetadata) + { + ref var meta = ref context.Request.Meta; + bool requestValid = true; + requestValid &= meta.SendPid == _hasInProcessIdHolder; + requestValid &= meta.SendStaticsCount == _inPointerBuffersCount; + requestValid &= meta.SendBuffersCount == _inMapAliasBuffersCount; + requestValid &= meta.ReceiveBuffersCount == _outMapAliasBuffersCount; + requestValid &= meta.ExchangeBuffersCount == 0; + requestValid &= meta.CopyHandlesCount == _inCopyHandlesCount; + requestValid &= meta.MoveHandlesCount == _inMoveHandlesCount; + + int rawSizeInBytes = meta.DataWordsCount * sizeof(uint); + int commandRawSize = BitUtils.AlignUp(runtimeMetadata.UnfixedOutPointerSizeOffset + (OutUnfixedSizePointerBuffersCount * sizeof(ushort)), sizeof(uint)); + + requestValid &= rawSizeInBytes >= commandRawSize; + + return requestValid ? Result.Success : HipcResult.InvalidCmifRequest; + } + + public Result GetInObjects(ServerMessageProcessor processor, Span objects) + { + if (objects.Length == 0) + { + return Result.Success; + } + + ServiceObjectHolder[] inObjects = new ServiceObjectHolder[objects.Length]; + Result result = processor.GetInObjects(inObjects); + + if (result.IsFailure) + { + return result; + } + + int inObjectIndex = 0; + + foreach (CommandArg t in _args) + { + if (t.Type != CommandArgType.InObject) + { + continue; + } + + int index = inObjectIndex++; + var inObject = inObjects[index]; + + objects[index] = inObject?.ServiceObject; + } + + return Result.Success; + } + + public override Result GetInObjects(Span inObjects) + { + return SfResult.NotSupported; + } + + public override HipcMessageData PrepareForReply(scoped ref ServiceDispatchContext context, out Span outRawData, ServerMessageRuntimeMetadata runtimeMetadata) + { + int rawDataSize = OutRawDataSize + runtimeMetadata.OutHeadersSize; + var response = HipcMessage.WriteResponse( + context.OutMessageBuffer, + _outPointerBuffersCount, + (BitUtils.AlignUp(rawDataSize, 4) + 0x10) / sizeof(uint), + _outCopyHandlesCount, + _outMoveHandlesCount + runtimeMetadata.OutObjectsCount); + outRawData = MemoryMarshal.Cast(response.DataWords); + + return response; + } + + public override void PrepareForErrorReply(scoped ref ServiceDispatchContext context, out Span outRawData, ServerMessageRuntimeMetadata runtimeMetadata) + { + int rawDataSize = runtimeMetadata.OutHeadersSize; + var response = HipcMessage.WriteResponse( + context.OutMessageBuffer, + 0, + (BitUtils.AlignUp(rawDataSize, 4) + 0x10) / sizeof(uint), + 0, + 0); + + outRawData = MemoryMarshal.Cast(response.DataWords); + } + +#pragma warning disable CA1822 // Mark member as static + public void SetOutObjects(ref ServiceDispatchContext context, HipcMessageData response, Span objects) +#pragma warning restore CA1822 + { + if (objects.Length == 0) + { + return; + } + + ServiceObjectHolder[] outObjects = new ServiceObjectHolder[objects.Length]; + + for (int i = 0; i < objects.Length; i++) + { + outObjects[i] = objects[i] != null ? new ServiceObjectHolder(objects[i]) : null; + } + + context.Processor.SetOutObjects(ref context, response, outObjects); + } + + public override void SetOutObjects(scoped ref ServiceDispatchContext context, HipcMessageData response, Span outObjects) + { + for (int index = 0; index < _outObjectsCount; index++) + { + SetOutObjectImpl(index, response, context.Manager, outObjects[index]); + } + } + + private static void SetOutObjectImpl(int index, HipcMessageData response, ServerSessionManager manager, ServiceObjectHolder obj) + { + if (obj == null) + { + response.MoveHandles[index] = 0; + + return; + } + + Api.CreateSession(out int serverHandle, out int clientHandle).AbortOnFailure(); + manager.RegisterSession(serverHandle, obj).AbortOnFailure(); + response.MoveHandles[index] = clientHandle; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs b/src/Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs new file mode 100644 index 00000000..f777f510 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + interface IServiceObject + { + IReadOnlyDictionary GetCommandHandlers(); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs b/src/Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs new file mode 100644 index 00000000..573a50f2 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs @@ -0,0 +1,49 @@ +using Ryujinx.Common; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + static class RawDataOffsetCalculator + { + public static int[] Calculate(CommandArg[] args) + { + int[] offsets = new int[args.Length + 1]; + + if (args.Length != 0) + { + int argsCount = args.Length; + + int[] sizes = new int[argsCount]; + int[] aligns = new int[argsCount]; + int[] map = new int[argsCount]; + + for (int i = 0; i < argsCount; i++) + { + sizes[i] = args[i].ArgSize; + aligns[i] = args[i].ArgAlignment; + map[i] = i; + } + + for (int i = 1; i < argsCount; i++) + { + for (int j = i; j > 0 && aligns[map[j - 1]] > aligns[map[j]]; j--) + { + (map[j], map[j - 1]) = (map[j - 1], map[j]); + } + } + + int offset = 0; + + foreach (int i in map) + { + offset = BitUtils.AlignUp(offset, aligns[i]); + offsets[i] = offset; + offset += sizes[i]; + } + + offsets[argsCount] = offset; + } + + return offsets; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sf/SfResult.cs b/src/Ryujinx.Horizon/Sdk/Sf/SfResult.cs new file mode 100644 index 00000000..32f160b2 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sf/SfResult.cs @@ -0,0 +1,29 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Sf +{ + static class SfResult + { + public const int ModuleId = 10; + +#pragma warning disable IDE0055 // Disable formatting + public static Result NotSupported => new(ModuleId, 1); + public static Result InvalidHeaderSize => new(ModuleId, 202); + public static Result InvalidInHeader => new(ModuleId, 211); + public static Result InvalidOutHeader => new(ModuleId, 212); + public static Result UnknownCommandId => new(ModuleId, 221); + public static Result InvalidOutRawSize => new(ModuleId, 232); + public static Result InvalidInObjectsCount => new(ModuleId, 235); + public static Result InvalidOutObjectsCount => new(ModuleId, 236); + public static Result InvalidInObject => new(ModuleId, 239); + public static Result TargetNotFound => new(ModuleId, 261); + public static Result OutOfDomainEntries => new(ModuleId, 301); + public static Result InvalidatedByUser => new(ModuleId, 802); + public static Result RequestDeferredByUser => new(ModuleId, 812); + + public static bool RequestContextChanged(Result result) => result.InRange(800, 899); + public static bool Invalidated(Result result) => result.InRange(801, 809); + public static bool RequestDeferred(Result result) => result.InRange(811, 819); +#pragma warning restore IDE0055 + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sm/IManagerService.cs b/src/Ryujinx.Horizon/Sdk/Sm/IManagerService.cs new file mode 100644 index 00000000..8d8578fe --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sm/IManagerService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Sm +{ + interface IManagerService : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sm/IUserService.cs b/src/Ryujinx.Horizon/Sdk/Sm/IUserService.cs new file mode 100644 index 00000000..bc05c007 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sm/IUserService.cs @@ -0,0 +1,13 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Sm +{ + interface IUserService : IServiceObject + { + Result Initialize(ulong clientProcessId); + Result GetService(out int handle, ServiceName name); + Result RegisterService(out int handle, ServiceName name, int maxSessions, bool isLight); + Result UnregisterService(ServiceName name); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs b/src/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs new file mode 100644 index 00000000..b44106d6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs @@ -0,0 +1,99 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Sm +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public readonly struct ServiceName + { + public static ServiceName Invalid { get; } = new(0); + + public bool IsValid => Packed != 0; + + public const int Length = sizeof(ulong); + + public ulong Packed { get; } + + public byte this[int index] + { + get + { + if ((uint)index >= sizeof(ulong)) + { + throw new IndexOutOfRangeException(); + } + + return (byte)(Packed >> (index * 8)); + } + } + + private ServiceName(ulong packed) + { + Packed = packed; + } + + public static ServiceName Encode(string name) + { + ulong packed = 0; + + for (int index = 0; index < sizeof(ulong); index++) + { + if (index < name.Length) + { + packed |= (ulong)(byte)name[index] << (index * 8); + } + else + { + break; + } + } + + return new ServiceName(packed); + } + + public override bool Equals(object obj) + { + return obj is ServiceName serviceName && serviceName.Equals(this); + } + + public bool Equals(ServiceName other) + { + return other.Packed == Packed; + } + + public override int GetHashCode() + { + return Packed.GetHashCode(); + } + + public static bool operator ==(ServiceName lhs, ServiceName rhs) + { + return lhs.Equals(rhs); + } + + public static bool operator !=(ServiceName lhs, ServiceName rhs) + { + return !lhs.Equals(rhs); + } + + public override string ToString() + { + StringBuilder nameBuilder = new(); + + for (int index = 0; index < sizeof(ulong); index++) + { + byte character = (byte)(Packed >> (index * 8)); + + if (character == 0) + { + break; + } + + nameBuilder.Append((char)character); + } + + return nameBuilder.ToString(); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Sm/SmApi.cs b/src/Ryujinx.Horizon/Sdk/Sm/SmApi.cs new file mode 100644 index 00000000..a63a03a5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Sm/SmApi.cs @@ -0,0 +1,125 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using System; + +namespace Ryujinx.Horizon.Sdk.Sm +{ + public class SmApi : IDisposable + { + private const string SmName = "sm:"; + + private int _portHandle; + + public Result Initialize() + { + Result result = HorizonStatic.Syscall.ConnectToNamedPort(out int portHandle, SmName); + + while (result == KernelResult.NotFound) + { + HorizonStatic.Syscall.SleepThread(50000000L); + result = HorizonStatic.Syscall.ConnectToNamedPort(out portHandle, SmName); + } + + if (result.IsFailure) + { + return result; + } + + _portHandle = portHandle; + + return RegisterClient(); + } + + private Result RegisterClient() + { + Span data = stackalloc byte[8]; + + SpanWriter writer = new(data); + + writer.Write(0UL); + + return ServiceUtil.SendRequest(out _, _portHandle, 0, sendPid: true, data); + } + + public Result GetServiceHandle(out int handle, ServiceName name) + { + Span data = stackalloc byte[8]; + + SpanWriter writer = new(data); + + writer.Write(name); + + Result result = ServiceUtil.SendRequest(out CmifResponse response, _portHandle, 1, sendPid: false, data); + + if (result.IsFailure) + { + handle = 0; + + return result; + } + + handle = response.MoveHandles[0]; + + return Result.Success; + } + + public Result RegisterService(out int handle, ServiceName name, int maxSessions, bool isLight) + { + Span data = stackalloc byte[16]; + + SpanWriter writer = new(data); + + writer.Write(name); + writer.Write(isLight ? 1 : 0); + writer.Write(maxSessions); + + Result result = ServiceUtil.SendRequest(out CmifResponse response, _portHandle, 2, sendPid: false, data); + + if (result.IsFailure) + { + handle = 0; + + return result; + } + + handle = response.MoveHandles[0]; + + return Result.Success; + } + + public Result UnregisterService(ServiceName name) + { + Span data = stackalloc byte[8]; + + SpanWriter writer = new(data); + + writer.Write(name); + + return ServiceUtil.SendRequest(out _, _portHandle, 3, sendPid: false, data); + } + + public Result DetachClient() + { + Span data = stackalloc byte[8]; + + SpanWriter writer = new(data); + + writer.Write(0UL); + + return ServiceUtil.SendRequest(out _, _portHandle, 4, sendPid: true, data); + } + + public void Dispose() + { + if (_portHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_portHandle); + + _portHandle = 0; + } + + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Srepo/ISrepoService.cs b/src/Ryujinx.Horizon/Sdk/Srepo/ISrepoService.cs new file mode 100644 index 00000000..35218b44 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Srepo/ISrepoService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Srepo +{ + interface ISrepoService : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ts/DeviceCode.cs b/src/Ryujinx.Horizon/Sdk/Ts/DeviceCode.cs new file mode 100644 index 00000000..4fce4238 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ts/DeviceCode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.Ts +{ + enum DeviceCode : uint + { + Internal = 0x41000001, + External = 0x41000002, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ts/IMeasurementServer.cs b/src/Ryujinx.Horizon/Sdk/Ts/IMeasurementServer.cs new file mode 100644 index 00000000..ba9c2a74 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ts/IMeasurementServer.cs @@ -0,0 +1,14 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Ts +{ + interface IMeasurementServer : IServiceObject + { + Result GetTemperatureRange(out int minimumTemperature, out int maximumTemperature, Location location); + Result GetTemperature(out int temperature, Location location); + Result SetMeasurementMode(Location location, byte measurementMode); + Result GetTemperatureMilliC(out int temperatureMilliC, Location location); + Result OpenSession(out ISession session, DeviceCode deviceCode); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ts/ISession.cs b/src/Ryujinx.Horizon/Sdk/Ts/ISession.cs new file mode 100644 index 00000000..23c0d94f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ts/ISession.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Ts +{ + interface ISession : IServiceObject + { + Result GetTemperatureRange(out int minimumTemperature, out int maximumTemperature); + Result GetTemperature(out int temperature); + Result SetMeasurementMode(byte measurementMode); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ts/Location.cs b/src/Ryujinx.Horizon/Sdk/Ts/Location.cs new file mode 100644 index 00000000..177b0ee8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ts/Location.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.Ts +{ + enum Location : byte + { + Internal, + External, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IClientRootSession.cs b/src/Ryujinx.Horizon/Sdk/Usb/IClientRootSession.cs new file mode 100644 index 00000000..b69b94d9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IClientRootSession.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IClientRootSession : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IDsRootSession.cs b/src/Ryujinx.Horizon/Sdk/Usb/IDsRootSession.cs new file mode 100644 index 00000000..fb802a26 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IDsRootSession.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IDsRootSession : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IPdCradleManager.cs b/src/Ryujinx.Horizon/Sdk/Usb/IPdCradleManager.cs new file mode 100644 index 00000000..125d2e8f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IPdCradleManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IPdCradleManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IPdManager.cs b/src/Ryujinx.Horizon/Sdk/Usb/IPdManager.cs new file mode 100644 index 00000000..52953882 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IPdManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IPdManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IPdManufactureManager.cs b/src/Ryujinx.Horizon/Sdk/Usb/IPdManufactureManager.cs new file mode 100644 index 00000000..eba159c0 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IPdManufactureManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IPdManufactureManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IPmObserverService.cs b/src/Ryujinx.Horizon/Sdk/Usb/IPmObserverService.cs new file mode 100644 index 00000000..03b1c89b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IPmObserverService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IPmObserverService : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IPmService.cs b/src/Ryujinx.Horizon/Sdk/Usb/IPmService.cs new file mode 100644 index 00000000..88510eae --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IPmService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IPmService : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Usb/IQdbManager.cs b/src/Ryujinx.Horizon/Sdk/Usb/IQdbManager.cs new file mode 100644 index 00000000..1615b020 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Usb/IQdbManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Usb +{ + interface IQdbManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Wlan/IDetectManager.cs b/src/Ryujinx.Horizon/Sdk/Wlan/IDetectManager.cs new file mode 100644 index 00000000..a48af332 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Wlan/IDetectManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Wlan +{ + interface IDetectManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Wlan/IGeneralServiceCreator.cs b/src/Ryujinx.Horizon/Sdk/Wlan/IGeneralServiceCreator.cs new file mode 100644 index 00000000..7a2d15ea --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Wlan/IGeneralServiceCreator.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Wlan +{ + interface IGeneralServiceCreator : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Wlan/IInfraManager.cs b/src/Ryujinx.Horizon/Sdk/Wlan/IInfraManager.cs new file mode 100644 index 00000000..473c13a2 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Wlan/IInfraManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Wlan +{ + interface IInfraManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Wlan/ILocalGetActionFrame.cs b/src/Ryujinx.Horizon/Sdk/Wlan/ILocalGetActionFrame.cs new file mode 100644 index 00000000..a6b6f475 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Wlan/ILocalGetActionFrame.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Wlan +{ + interface ILocalGetActionFrame : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Wlan/ILocalGetFrame.cs b/src/Ryujinx.Horizon/Sdk/Wlan/ILocalGetFrame.cs new file mode 100644 index 00000000..000a7f8a --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Wlan/ILocalGetFrame.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Wlan +{ + interface ILocalGetFrame : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Wlan/ILocalManager.cs b/src/Ryujinx.Horizon/Sdk/Wlan/ILocalManager.cs new file mode 100644 index 00000000..485a66a1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Wlan/ILocalManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Wlan +{ + interface ILocalManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Wlan/IPrivateServiceCreator.cs b/src/Ryujinx.Horizon/Sdk/Wlan/IPrivateServiceCreator.cs new file mode 100644 index 00000000..200d8862 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Wlan/IPrivateServiceCreator.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Wlan +{ + interface IPrivateServiceCreator : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Wlan/ISfDriverServiceCreator.cs b/src/Ryujinx.Horizon/Sdk/Wlan/ISfDriverServiceCreator.cs new file mode 100644 index 00000000..5a19e208 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Wlan/ISfDriverServiceCreator.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Wlan +{ + interface ISfDriverServiceCreator : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Wlan/ISocketGetFrame.cs b/src/Ryujinx.Horizon/Sdk/Wlan/ISocketGetFrame.cs new file mode 100644 index 00000000..0c9967c5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Wlan/ISocketGetFrame.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Wlan +{ + interface ISocketGetFrame : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Wlan/ISocketManager.cs b/src/Ryujinx.Horizon/Sdk/Wlan/ISocketManager.cs new file mode 100644 index 00000000..6df88b54 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Wlan/ISocketManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Wlan +{ + interface ISocketManager : IServiceObject + { + } +} diff --git a/src/Ryujinx.Horizon/ServiceEntry.cs b/src/Ryujinx.Horizon/ServiceEntry.cs new file mode 100644 index 00000000..edf76fcd --- /dev/null +++ b/src/Ryujinx.Horizon/ServiceEntry.cs @@ -0,0 +1,27 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.Horizon +{ + public readonly struct ServiceEntry + { + private readonly Action _entrypoint; + private readonly ServiceTable _serviceTable; + private readonly HorizonOptions _options; + + internal ServiceEntry(Action entrypoint, ServiceTable serviceTable, HorizonOptions options) + { + _entrypoint = entrypoint; + _serviceTable = serviceTable; + _options = options; + } + + public void Start(ISyscallApi syscallApi, IVirtualMemoryManager addressSpace, IThreadContext threadContext) + { + HorizonStatic.Register(_options, syscallApi, addressSpace, threadContext, (int)threadContext.GetX(1)); + + _entrypoint(_serviceTable); + } + } +} diff --git a/src/Ryujinx.Horizon/ServiceTable.cs b/src/Ryujinx.Horizon/ServiceTable.cs new file mode 100644 index 00000000..28c43a71 --- /dev/null +++ b/src/Ryujinx.Horizon/ServiceTable.cs @@ -0,0 +1,93 @@ +using Ryujinx.Horizon.Arp; +using Ryujinx.Horizon.Audio; +using Ryujinx.Horizon.Bcat; +using Ryujinx.Horizon.Friends; +using Ryujinx.Horizon.Hshl; +using Ryujinx.Horizon.Ins; +using Ryujinx.Horizon.Lbl; +using Ryujinx.Horizon.LogManager; +using Ryujinx.Horizon.MmNv; +using Ryujinx.Horizon.Ngc; +using Ryujinx.Horizon.Ovln; +using Ryujinx.Horizon.Prepo; +using Ryujinx.Horizon.Psc; +using Ryujinx.Horizon.Ptm; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Srepo; +using Ryujinx.Horizon.Usb; +using Ryujinx.Horizon.Wlan; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Horizon +{ + public class ServiceTable + { + private int _readyServices; + private int _totalServices; + + private readonly ManualResetEvent _servicesReadyEvent = new(false); + + public IReader ArpReader { get; internal set; } + public IWriter ArpWriter { get; internal set; } + + public IEnumerable GetServices(HorizonOptions options) + { + List entries = new(); + + void RegisterService() where T : IService + { + entries.Add(new ServiceEntry(T.Main, this, options)); + } + + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); // TODO: Merge with audio once we can start multiple threads. + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); + RegisterService(); + + _totalServices = entries.Count; + + return entries; + } + + internal void SignalServiceReady() + { + if (Interlocked.Increment(ref _readyServices) == _totalServices) + { + _servicesReadyEvent.Set(); + } + } + + public void WaitServicesReady() + { + _servicesReadyEvent.WaitOne(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _servicesReadyEvent.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Horizon/Sm/Impl/ServiceInfo.cs b/src/Ryujinx.Horizon/Sm/Impl/ServiceInfo.cs new file mode 100644 index 00000000..39ed57ce --- /dev/null +++ b/src/Ryujinx.Horizon/Sm/Impl/ServiceInfo.cs @@ -0,0 +1,20 @@ +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Sm.Impl +{ + struct ServiceInfo + { + public ServiceName Name; + public ulong OwnerProcessId; + public int PortHandle; + + public void Free() + { + HorizonStatic.Syscall.CloseHandle(PortHandle); + + Name = ServiceName.Invalid; + OwnerProcessId = 0L; + PortHandle = 0; + } + } +} diff --git a/src/Ryujinx.Horizon/Sm/Impl/ServiceManager.cs b/src/Ryujinx.Horizon/Sm/Impl/ServiceManager.cs new file mode 100644 index 00000000..177cc0d3 --- /dev/null +++ b/src/Ryujinx.Horizon/Sm/Impl/ServiceManager.cs @@ -0,0 +1,185 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Sm.Impl +{ + class ServiceManager + { + private const int MaxServicesCount = 256; + + private readonly ServiceInfo[] _services; + + public ServiceManager() + { + _services = new ServiceInfo[MaxServicesCount]; + } + + public Result GetService(out int handle, ulong processId, ServiceName name) + { + handle = 0; + Result result = ValidateServiceName(name); + + if (result.IsFailure) + { + return result; + } + + // TODO: Validation with GetProcessInfo etc. + + int serviceIndex = GetServiceInfo(name); + + if (serviceIndex < 0) + { + return SfResult.RequestDeferredByUser; + } + + result = GetServiceImpl(out handle, ref _services[serviceIndex]); + + return result == KernelResult.SessionCountExceeded ? SmResult.OutOfSessions : result; + } + + private static Result GetServiceImpl(out int handle, ref ServiceInfo serviceInfo) + { + return HorizonStatic.Syscall.ConnectToPort(out handle, serviceInfo.PortHandle); + } + + public Result RegisterService(out int handle, ulong processId, ServiceName name, int maxSessions, bool isLight) + { + handle = 0; + Result result = ValidateServiceName(name); + + if (result.IsFailure) + { + return result; + } + + // TODO: Validation with GetProcessInfo etc. + return HasServiceInfo(name) ? SmResult.AlreadyRegistered : RegisterServiceImpl(out handle, processId, name, maxSessions, isLight); + } + + public Result RegisterServiceForSelf(out int handle, ServiceName name, int maxSessions) + { + return RegisterServiceImpl(out handle, Os.GetCurrentProcessId(), name, maxSessions, false); + } + + private Result RegisterServiceImpl(out int handle, ulong processId, ServiceName name, int maxSessions, bool isLight) + { + handle = 0; + + Result result = ValidateServiceName(name); + + if (!result.IsSuccess) + { + return result; + } + + if (HasServiceInfo(name)) + { + return SmResult.AlreadyRegistered; + } + + int freeServiceIndex = GetFreeService(); + + if (freeServiceIndex < 0) + { + return SmResult.OutOfServices; + } + + ref ServiceInfo freeService = ref _services[freeServiceIndex]; + + result = HorizonStatic.Syscall.CreatePort(out handle, out int clientPort, maxSessions, isLight, null); + + if (!result.IsSuccess) + { + return result; + } + + freeService.PortHandle = clientPort; + freeService.Name = name; + freeService.OwnerProcessId = processId; + + return Result.Success; + } + + public Result UnregisterService(ulong processId, ServiceName name) + { + Result result = ValidateServiceName(name); + + if (result.IsFailure) + { + return result; + } + + // TODO: Validation with GetProcessInfo etc. + + int serviceIndex = GetServiceInfo(name); + if (serviceIndex < 0) + { + return SmResult.NotRegistered; + } + + ref var serviceInfo = ref _services[serviceIndex]; + if (serviceInfo.OwnerProcessId != processId) + { + return SmResult.NotAllowed; + } + + serviceInfo.Free(); + + return Result.Success; + } + + private static Result ValidateServiceName(ServiceName name) + { + if (name[0] == 0) + { + return SmResult.InvalidServiceName; + } + + int nameLength = 1; + + for (; nameLength < ServiceName.Length; nameLength++) + { + if (name[nameLength] == 0) + { + break; + } + } + + while (nameLength < ServiceName.Length) + { + if (name[nameLength++] != 0) + { + return SmResult.InvalidServiceName; + } + } + + return Result.Success; + } + + private bool HasServiceInfo(ServiceName name) + { + return GetServiceInfo(name) != -1; + } + + private int GetFreeService() + { + return GetServiceInfo(ServiceName.Invalid); + } + + private int GetServiceInfo(ServiceName name) + { + for (int index = 0; index < MaxServicesCount; index++) + { + if (_services[index].Name == name) + { + return index; + } + } + + return -1; + } + } +} diff --git a/src/Ryujinx.Horizon/Sm/Ipc/ManagerService.cs b/src/Ryujinx.Horizon/Sm/Ipc/ManagerService.cs new file mode 100644 index 00000000..b9fb9cda --- /dev/null +++ b/src/Ryujinx.Horizon/Sm/Ipc/ManagerService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Sm.Ipc +{ + partial class ManagerService : IManagerService + { + } +} diff --git a/src/Ryujinx.Horizon/Sm/Ipc/UserService.cs b/src/Ryujinx.Horizon/Sm/Ipc/UserService.cs new file mode 100644 index 00000000..6223f9b5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sm/Ipc/UserService.cs @@ -0,0 +1,66 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sm; +using Ryujinx.Horizon.Sm.Impl; + +namespace Ryujinx.Horizon.Sm.Ipc +{ + partial class UserService : IUserService + { + private readonly ServiceManager _serviceManager; + + private ulong _clientProcessId; + private bool _initialized; + + public UserService(ServiceManager serviceManager) + { + _serviceManager = serviceManager; + } + + [CmifCommand(0)] + public Result Initialize([ClientProcessId] ulong clientProcessId) + { + _clientProcessId = clientProcessId; + _initialized = true; + + return Result.Success; + } + + [CmifCommand(1)] + public Result GetService([MoveHandle] out int handle, ServiceName name) + { + if (!_initialized) + { + handle = 0; + + return SmResult.InvalidClient; + } + + return _serviceManager.GetService(out handle, _clientProcessId, name); + } + + [CmifCommand(2)] + public Result RegisterService([MoveHandle] out int handle, ServiceName name, int maxSessions, bool isLight) + { + if (!_initialized) + { + handle = 0; + + return SmResult.InvalidClient; + } + + return _serviceManager.RegisterService(out handle, _clientProcessId, name, maxSessions, isLight); + } + + [CmifCommand(3)] + public Result UnregisterService(ServiceName name) + { + if (!_initialized) + { + return SmResult.InvalidClient; + } + + return _serviceManager.UnregisterService(_clientProcessId, name); + } + } +} diff --git a/src/Ryujinx.Horizon/Sm/SmMain.cs b/src/Ryujinx.Horizon/Sm/SmMain.cs new file mode 100644 index 00000000..024d569f --- /dev/null +++ b/src/Ryujinx.Horizon/Sm/SmMain.cs @@ -0,0 +1,32 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using Ryujinx.Horizon.Sm.Impl; +using Ryujinx.Horizon.Sm.Types; + +namespace Ryujinx.Horizon.Sm +{ + public class SmMain + { + private const int SmMaxSessionsCount = 64; + private const int SmmMaxSessionsCount = 1; + private const int SmTotalMaxSessionsCount = SmMaxSessionsCount + SmmMaxSessionsCount; + + private const int MaxPortsCount = 2; + + private SmServerManager _serverManager; + + private readonly ServiceManager _serviceManager = new(); + + public void Main() + { + HorizonStatic.Syscall.ManageNamedPort(out int smHandle, "sm:", SmMaxSessionsCount).AbortOnFailure(); + + _serverManager = new SmServerManager(_serviceManager, null, null, MaxPortsCount, ManagerOptions.Default, SmTotalMaxSessionsCount); + + _serverManager.RegisterServer((int)SmPortIndex.User, smHandle); + _serviceManager.RegisterServiceForSelf(out int smmHandle, ServiceName.Encode("sm:m"), SmmMaxSessionsCount).AbortOnFailure(); + _serverManager.RegisterServer((int)SmPortIndex.Manager, smmHandle); + _serverManager.ServiceRequests(); + } + } +} diff --git a/src/Ryujinx.Horizon/Sm/SmResult.cs b/src/Ryujinx.Horizon/Sm/SmResult.cs new file mode 100644 index 00000000..4cc42378 --- /dev/null +++ b/src/Ryujinx.Horizon/Sm/SmResult.cs @@ -0,0 +1,21 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sm +{ + static class SmResult + { + private const int ModuleId = 21; + +#pragma warning disable IDE0055 // Disable formatting + public static Result OutOfProcess => new(ModuleId, 1); + public static Result InvalidClient => new(ModuleId, 2); + public static Result OutOfSessions => new(ModuleId, 3); + public static Result AlreadyRegistered => new(ModuleId, 4); + public static Result OutOfServices => new(ModuleId, 5); + public static Result InvalidServiceName => new(ModuleId, 6); + public static Result NotRegistered => new(ModuleId, 7); + public static Result NotAllowed => new(ModuleId, 8); + public static Result TooLargeAccessControl => new(ModuleId, 9); +#pragma warning restore IDE0055 + } +} diff --git a/src/Ryujinx.Horizon/Sm/SmServerManager.cs b/src/Ryujinx.Horizon/Sm/SmServerManager.cs new file mode 100644 index 00000000..4b1fa256 --- /dev/null +++ b/src/Ryujinx.Horizon/Sm/SmServerManager.cs @@ -0,0 +1,30 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using Ryujinx.Horizon.Sm.Impl; +using Ryujinx.Horizon.Sm.Ipc; +using Ryujinx.Horizon.Sm.Types; +using System; + +namespace Ryujinx.Horizon.Sm +{ + class SmServerManager : ServerManager + { + private readonly ServiceManager _serviceManager; + + public SmServerManager(ServiceManager serviceManager, HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions) + { + _serviceManager = serviceManager; + } + + protected override Result OnNeedsToAccept(int portIndex, Server server) + { + return (SmPortIndex)portIndex switch + { + SmPortIndex.User => AcceptImpl(server, new UserService(_serviceManager)), + SmPortIndex.Manager => AcceptImpl(server, new ManagerService()), + _ => throw new ArgumentOutOfRangeException(nameof(portIndex)), + }; + } + } +} diff --git a/src/Ryujinx.Horizon/Sm/Types/SmPortIndex.cs b/src/Ryujinx.Horizon/Sm/Types/SmPortIndex.cs new file mode 100644 index 00000000..fd024bbb --- /dev/null +++ b/src/Ryujinx.Horizon/Sm/Types/SmPortIndex.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sm.Types +{ + enum SmPortIndex + { + User, + Manager, + } +} diff --git a/src/Ryujinx.Horizon/Srepo/Ipc/SrepoService.cs b/src/Ryujinx.Horizon/Srepo/Ipc/SrepoService.cs new file mode 100644 index 00000000..df5ac0f0 --- /dev/null +++ b/src/Ryujinx.Horizon/Srepo/Ipc/SrepoService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Srepo; + +namespace Ryujinx.Horizon.Srepo.Ipc +{ + partial class SrepoService : ISrepoService + { + } +} diff --git a/src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs b/src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs new file mode 100644 index 00000000..44d00822 --- /dev/null +++ b/src/Ryujinx.Horizon/Srepo/SrepoIpcServer.cs @@ -0,0 +1,47 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using Ryujinx.Horizon.Srepo.Ipc; + +namespace Ryujinx.Horizon.Srepo +{ + class SrepoIpcServer + { + private const int SrepoAMaxSessionsCount = 2; + private const int SrepoUMaxSessionsCount = 30; + private const int TotalMaxSessionsCount = SrepoAMaxSessionsCount + SrepoUMaxSessionsCount; + + private const int PointerBufferSize = 0x80; + private const int MaxDomains = 32; + private const int MaxDomainObjects = 192; + private const int MaxPortsCount = 2; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, TotalMaxSessionsCount); + + _serverManager.RegisterObjectForServer(new SrepoService(), ServiceName.Encode("srepo:a"), SrepoAMaxSessionsCount); // 5.0.0+ + _serverManager.RegisterObjectForServer(new SrepoService(), ServiceName.Encode("srepo:u"), SrepoUMaxSessionsCount); // 5.0.0+ + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Srepo/SrepoMain.cs b/src/Ryujinx.Horizon/Srepo/SrepoMain.cs new file mode 100644 index 00000000..9c90b94a --- /dev/null +++ b/src/Ryujinx.Horizon/Srepo/SrepoMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Srepo +{ + class SrepoMain : IService + { + public static void Main(ServiceTable serviceTable) + { + SrepoIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/ClientRootSession.cs b/src/Ryujinx.Horizon/Usb/Ipc/ClientRootSession.cs new file mode 100644 index 00000000..dec3f3f9 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/ClientRootSession.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class ClientRootSession : IClientRootSession + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/DsRootSession.cs b/src/Ryujinx.Horizon/Usb/Ipc/DsRootSession.cs new file mode 100644 index 00000000..1dd0d32b --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/DsRootSession.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class DsRootSession : IDsRootSession + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/PdCradleManager.cs b/src/Ryujinx.Horizon/Usb/Ipc/PdCradleManager.cs new file mode 100644 index 00000000..5c0efb66 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/PdCradleManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class PdCradleManager : IPdCradleManager + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/PdManager.cs b/src/Ryujinx.Horizon/Usb/Ipc/PdManager.cs new file mode 100644 index 00000000..1c502919 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/PdManager.cs @@ -0,0 +1,9 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class PdManager : IPdManager + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/PdManufactureManager.cs b/src/Ryujinx.Horizon/Usb/Ipc/PdManufactureManager.cs new file mode 100644 index 00000000..a909c861 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/PdManufactureManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class PdManufactureManager : IPdManufactureManager + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/PmObserverService.cs b/src/Ryujinx.Horizon/Usb/Ipc/PmObserverService.cs new file mode 100644 index 00000000..e1debf42 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/PmObserverService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class PmObserverService : IPmObserverService + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/PmService.cs b/src/Ryujinx.Horizon/Usb/Ipc/PmService.cs new file mode 100644 index 00000000..8daa27c4 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/PmService.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class PmService : IPmService + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/Ipc/QdbManager.cs b/src/Ryujinx.Horizon/Usb/Ipc/QdbManager.cs new file mode 100644 index 00000000..a458bef0 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/Ipc/QdbManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Usb; + +namespace Ryujinx.Horizon.Usb.Ipc +{ + partial class QdbManager : IQdbManager + { + } +} diff --git a/src/Ryujinx.Horizon/Usb/UsbIpcServer.cs b/src/Ryujinx.Horizon/Usb/UsbIpcServer.cs new file mode 100644 index 00000000..a04b81f9 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/UsbIpcServer.cs @@ -0,0 +1,72 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using Ryujinx.Horizon.Usb.Ipc; + +namespace Ryujinx.Horizon.Usb +{ + class UsbIpcServer + { + private const int UsbDsMaxSessionsCount = 4; + private const int UsbHsMaxSessionsCount = 20; + private const int UsbHsAMaxSessionsCount = 3; + private const int UsbObsvMaxSessionsCount = 2; + private const int UsbPdMaxSessionsCount = 6; + private const int UsbPdCMaxSessionsCount = 4; + private const int UsbPdMMaxSessionsCount = 1; + private const int UsbPmMaxSessionsCount = 5; + private const int UsbQdbMaxSessionsCount = 4; + private const int TotalMaxSessionsCount = + UsbDsMaxSessionsCount + + UsbHsMaxSessionsCount + + UsbHsAMaxSessionsCount + + UsbObsvMaxSessionsCount + + UsbPdMaxSessionsCount + + UsbPdCMaxSessionsCount + + UsbPdMMaxSessionsCount + + UsbPmMaxSessionsCount + + UsbQdbMaxSessionsCount; + + private const int PointerBufferSize = 0; + private const int MaxDomains = 0; + private const int MaxDomainObjects = 0; + private const int MaxPortsCount = 9; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterObjectForServer(new DsRootSession(), ServiceName.Encode("usb:ds"), UsbDsMaxSessionsCount); + _serverManager.RegisterObjectForServer(new ClientRootSession(), ServiceName.Encode("usb:hs"), UsbHsMaxSessionsCount); + _serverManager.RegisterObjectForServer(new ClientRootSession(), ServiceName.Encode("usb:hs:a"), UsbHsAMaxSessionsCount); // 7.0.0+ + _serverManager.RegisterObjectForServer(new PmObserverService(), ServiceName.Encode("usb:obsv"), UsbObsvMaxSessionsCount); // 8.0.0+ + _serverManager.RegisterObjectForServer(new PdManager(), ServiceName.Encode("usb:pd"), UsbPdMaxSessionsCount); + _serverManager.RegisterObjectForServer(new PdCradleManager(), ServiceName.Encode("usb:pd:c"), UsbPdCMaxSessionsCount); + _serverManager.RegisterObjectForServer(new PdManufactureManager(), ServiceName.Encode("usb:pd:m"), UsbPdMMaxSessionsCount); // 1.0.0 + _serverManager.RegisterObjectForServer(new PmService(), ServiceName.Encode("usb:pm"), UsbPmMaxSessionsCount); + _serverManager.RegisterObjectForServer(new QdbManager(), ServiceName.Encode("usb:qdb"), UsbQdbMaxSessionsCount); // 7.0.0+ +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Usb/UsbMain.cs b/src/Ryujinx.Horizon/Usb/UsbMain.cs new file mode 100644 index 00000000..a9c2e680 --- /dev/null +++ b/src/Ryujinx.Horizon/Usb/UsbMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Usb +{ + class UsbMain : IService + { + public static void Main(ServiceTable serviceTable) + { + UsbIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Wlan/Ipc/DetectManager.cs b/src/Ryujinx.Horizon/Wlan/Ipc/DetectManager.cs new file mode 100644 index 00000000..9158807b --- /dev/null +++ b/src/Ryujinx.Horizon/Wlan/Ipc/DetectManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Wlan; + +namespace Ryujinx.Horizon.Wlan.Ipc +{ + partial class DetectManager : IDetectManager + { + } +} diff --git a/src/Ryujinx.Horizon/Wlan/Ipc/GeneralServiceCreator.cs b/src/Ryujinx.Horizon/Wlan/Ipc/GeneralServiceCreator.cs new file mode 100644 index 00000000..ef5407d8 --- /dev/null +++ b/src/Ryujinx.Horizon/Wlan/Ipc/GeneralServiceCreator.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Wlan; + +namespace Ryujinx.Horizon.Wlan.Ipc +{ + partial class GeneralServiceCreator : IGeneralServiceCreator + { + } +} diff --git a/src/Ryujinx.Horizon/Wlan/Ipc/InfraManager.cs b/src/Ryujinx.Horizon/Wlan/Ipc/InfraManager.cs new file mode 100644 index 00000000..7d3f0fac --- /dev/null +++ b/src/Ryujinx.Horizon/Wlan/Ipc/InfraManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Wlan; + +namespace Ryujinx.Horizon.Wlan.Ipc +{ + partial class InfraManager : IInfraManager + { + } +} diff --git a/src/Ryujinx.Horizon/Wlan/Ipc/LocalGetActionFrame.cs b/src/Ryujinx.Horizon/Wlan/Ipc/LocalGetActionFrame.cs new file mode 100644 index 00000000..4606f6e2 --- /dev/null +++ b/src/Ryujinx.Horizon/Wlan/Ipc/LocalGetActionFrame.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Wlan; + +namespace Ryujinx.Horizon.Wlan.Ipc +{ + partial class LocalGetActionFrame : ILocalGetActionFrame + { + } +} diff --git a/src/Ryujinx.Horizon/Wlan/Ipc/LocalGetFrame.cs b/src/Ryujinx.Horizon/Wlan/Ipc/LocalGetFrame.cs new file mode 100644 index 00000000..ff5bedb0 --- /dev/null +++ b/src/Ryujinx.Horizon/Wlan/Ipc/LocalGetFrame.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Wlan; + +namespace Ryujinx.Horizon.Wlan.Ipc +{ + partial class LocalGetFrame : ILocalGetFrame + { + } +} diff --git a/src/Ryujinx.Horizon/Wlan/Ipc/LocalManager.cs b/src/Ryujinx.Horizon/Wlan/Ipc/LocalManager.cs new file mode 100644 index 00000000..60242332 --- /dev/null +++ b/src/Ryujinx.Horizon/Wlan/Ipc/LocalManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Wlan; + +namespace Ryujinx.Horizon.Wlan.Ipc +{ + partial class LocalManager : ILocalManager + { + } +} diff --git a/src/Ryujinx.Horizon/Wlan/Ipc/PrivateServiceCreator.cs b/src/Ryujinx.Horizon/Wlan/Ipc/PrivateServiceCreator.cs new file mode 100644 index 00000000..7045419d --- /dev/null +++ b/src/Ryujinx.Horizon/Wlan/Ipc/PrivateServiceCreator.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Wlan; + +namespace Ryujinx.Horizon.Wlan.Ipc +{ + partial class PrivateServiceCreator : IPrivateServiceCreator + { + } +} diff --git a/src/Ryujinx.Horizon/Wlan/Ipc/SfDriverServiceCreator.cs b/src/Ryujinx.Horizon/Wlan/Ipc/SfDriverServiceCreator.cs new file mode 100644 index 00000000..212bfae1 --- /dev/null +++ b/src/Ryujinx.Horizon/Wlan/Ipc/SfDriverServiceCreator.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Wlan; + +namespace Ryujinx.Horizon.Wlan.Ipc +{ + partial class SfDriverServiceCreator : ISfDriverServiceCreator + { + } +} diff --git a/src/Ryujinx.Horizon/Wlan/Ipc/SocketGetFrame.cs b/src/Ryujinx.Horizon/Wlan/Ipc/SocketGetFrame.cs new file mode 100644 index 00000000..4104980e --- /dev/null +++ b/src/Ryujinx.Horizon/Wlan/Ipc/SocketGetFrame.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Wlan; + +namespace Ryujinx.Horizon.Wlan.Ipc +{ + partial class SocketGetFrame : ISocketGetFrame + { + } +} diff --git a/src/Ryujinx.Horizon/Wlan/Ipc/SocketManager.cs b/src/Ryujinx.Horizon/Wlan/Ipc/SocketManager.cs new file mode 100644 index 00000000..947d2a1e --- /dev/null +++ b/src/Ryujinx.Horizon/Wlan/Ipc/SocketManager.cs @@ -0,0 +1,8 @@ +using Ryujinx.Horizon.Sdk.Wlan; + +namespace Ryujinx.Horizon.Wlan.Ipc +{ + partial class SocketManager : ISocketManager + { + } +} diff --git a/src/Ryujinx.Horizon/Wlan/WlanIpcServer.cs b/src/Ryujinx.Horizon/Wlan/WlanIpcServer.cs new file mode 100644 index 00000000..776b9a7c --- /dev/null +++ b/src/Ryujinx.Horizon/Wlan/WlanIpcServer.cs @@ -0,0 +1,60 @@ +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using Ryujinx.Horizon.Wlan.Ipc; + +namespace Ryujinx.Horizon.Wlan +{ + class WlanIpcServer + { + private const int WlanOtherMaxSessionsCount = 10; + private const int WlanDtcMaxSessionsCount = 4; + private const int WlanMaxSessionsCount = 30; + private const int WlanNdMaxSessionsCount = 5; + private const int WlanPMaxSessionsCount = 30; + private const int TotalMaxSessionsCount = WlanDtcMaxSessionsCount + WlanMaxSessionsCount + WlanNdMaxSessionsCount + WlanPMaxSessionsCount + WlanOtherMaxSessionsCount * 6; + + private const int PointerBufferSize = 0x1000; + private const int MaxDomains = 16; + private const int MaxDomainObjects = 10; + private const int MaxPortsCount = 10; + + private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, TotalMaxSessionsCount); + +#pragma warning disable IDE0055 // Disable formatting + _serverManager.RegisterObjectForServer(new GeneralServiceCreator(), ServiceName.Encode("wlan"), WlanMaxSessionsCount); // 15.0.0+ + _serverManager.RegisterObjectForServer(new DetectManager(), ServiceName.Encode("wlan:dtc"), WlanDtcMaxSessionsCount); // 6.0.0-14.1.2 + _serverManager.RegisterObjectForServer(new InfraManager(), ServiceName.Encode("wlan:inf"), WlanOtherMaxSessionsCount); // 1.0.0-14.1.2 + _serverManager.RegisterObjectForServer(new LocalManager(), ServiceName.Encode("wlan:lcl"), WlanOtherMaxSessionsCount); // 1.0.0-14.1.2 + _serverManager.RegisterObjectForServer(new LocalGetFrame(), ServiceName.Encode("wlan:lg"), WlanOtherMaxSessionsCount); // 1.0.0-14.1.2 + _serverManager.RegisterObjectForServer(new LocalGetActionFrame(), ServiceName.Encode("wlan:lga"), WlanOtherMaxSessionsCount); // 1.0.0-14.1.2 + _serverManager.RegisterObjectForServer(new SfDriverServiceCreator(), ServiceName.Encode("wlan:nd"), WlanNdMaxSessionsCount); // 15.0.0+ + _serverManager.RegisterObjectForServer(new PrivateServiceCreator(), ServiceName.Encode("wlan:p"), WlanPMaxSessionsCount); // 15.0.0+ + _serverManager.RegisterObjectForServer(new SocketGetFrame(), ServiceName.Encode("wlan:sg"), WlanOtherMaxSessionsCount); // 1.0.0-14.1.2 + _serverManager.RegisterObjectForServer(new SocketManager(), ServiceName.Encode("wlan:soc"), WlanOtherMaxSessionsCount); // 1.0.0-14.1.2 +#pragma warning restore IDE0055 + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _serverManager.Dispose(); + _sm.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Wlan/WlanMain.cs b/src/Ryujinx.Horizon/Wlan/WlanMain.cs new file mode 100644 index 00000000..6eef64a2 --- /dev/null +++ b/src/Ryujinx.Horizon/Wlan/WlanMain.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.Horizon.Wlan +{ + class WlanMain : IService + { + public static void Main(ServiceTable serviceTable) + { + WlanIpcServer ipcServer = new(); + + ipcServer.Initialize(); + + serviceTable.SignalServiceReady(); + + ipcServer.ServiceRequests(); + ipcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj b/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj new file mode 100644 index 00000000..1ab79d08 --- /dev/null +++ b/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + true + + + + + + + + diff --git a/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs b/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs new file mode 100644 index 00000000..187ca48d --- /dev/null +++ b/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs @@ -0,0 +1,390 @@ +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Logging; +using System; +using System.Collections.Generic; +using System.Numerics; +using static SDL2.SDL; + +namespace Ryujinx.Input.SDL2 +{ + class SDL2Gamepad : IGamepad + { + private bool HasConfiguration => _configuration != null; + + private record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From); + + private StandardControllerInputConfig _configuration; + + private static readonly SDL_GameControllerButton[] _buttonsDriverMapping = new SDL_GameControllerButton[(int)GamepadButtonInputId.Count] + { + // Unbound, ignored. + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, + + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, + + // NOTE: The left and right trigger are axis, we handle those differently + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, + + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_MISC1, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE1, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE2, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE3, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE4, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_TOUCHPAD, + + // Virtual buttons are invalid, ignored. + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, + SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, + }; + + private readonly object _userMappingLock = new(); + + private readonly List _buttonsUserMapping; + + private readonly StickInputId[] _stickUserMapping = new StickInputId[(int)StickInputId.Count] + { + StickInputId.Unbound, + StickInputId.Left, + StickInputId.Right, + }; + + public GamepadFeaturesFlag Features { get; } + + private IntPtr _gamepadHandle; + + private float _triggerThreshold; + + public SDL2Gamepad(IntPtr gamepadHandle, string driverId) + { + _gamepadHandle = gamepadHandle; + _buttonsUserMapping = new List(20); + + Name = SDL_GameControllerName(_gamepadHandle); + Id = driverId; + Features = GetFeaturesFlag(); + _triggerThreshold = 0.0f; + + // Enable motion tracking + if (Features.HasFlag(GamepadFeaturesFlag.Motion)) + { + if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, SDL_bool.SDL_TRUE) != 0) + { + Logger.Error?.Print(LogClass.Hid, $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_ACCEL}."); + } + + if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO, SDL_bool.SDL_TRUE) != 0) + { + Logger.Error?.Print(LogClass.Hid, $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_GYRO}."); + } + } + } + + private GamepadFeaturesFlag GetFeaturesFlag() + { + GamepadFeaturesFlag result = GamepadFeaturesFlag.None; + + if (SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) == SDL_bool.SDL_TRUE && + SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO) == SDL_bool.SDL_TRUE) + { + result |= GamepadFeaturesFlag.Motion; + } + + int error = SDL_GameControllerRumble(_gamepadHandle, 0, 0, 100); + + if (error == 0) + { + result |= GamepadFeaturesFlag.Rumble; + } + + return result; + } + + public string Id { get; } + public string Name { get; } + + public bool IsConnected => SDL_GameControllerGetAttached(_gamepadHandle) == SDL_bool.SDL_TRUE; + + protected virtual void Dispose(bool disposing) + { + if (disposing && _gamepadHandle != IntPtr.Zero) + { + SDL_GameControllerClose(_gamepadHandle); + + _gamepadHandle = IntPtr.Zero; + } + } + + public void Dispose() + { + Dispose(true); + } + + public void SetTriggerThreshold(float triggerThreshold) + { + _triggerThreshold = triggerThreshold; + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + if (Features.HasFlag(GamepadFeaturesFlag.Rumble)) + { + ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue); + ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue); + + if (durationMs == uint.MaxValue) + { + if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY) != 0) + { + Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller."); + } + } + else if (durationMs > SDL_HAPTIC_INFINITY) + { + Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}"); + } + else + { + if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs) != 0) + { + Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller."); + } + } + } + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + SDL_SensorType sensorType = SDL_SensorType.SDL_SENSOR_INVALID; + + if (inputId == MotionInputId.Accelerometer) + { + sensorType = SDL_SensorType.SDL_SENSOR_ACCEL; + } + else if (inputId == MotionInputId.Gyroscope) + { + sensorType = SDL_SensorType.SDL_SENSOR_GYRO; + } + + if (Features.HasFlag(GamepadFeaturesFlag.Motion) && sensorType != SDL_SensorType.SDL_SENSOR_INVALID) + { + const int ElementCount = 3; + + unsafe + { + float* values = stackalloc float[ElementCount]; + + int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (IntPtr)values, ElementCount); + + if (result == 0) + { + Vector3 value = new(values[0], values[1], values[2]); + + if (inputId == MotionInputId.Gyroscope) + { + return RadToDegree(value); + } + + if (inputId == MotionInputId.Accelerometer) + { + return GsToMs2(value); + } + + return value; + } + } + } + + return Vector3.Zero; + } + + private static Vector3 RadToDegree(Vector3 rad) + { + return rad * (180 / MathF.PI); + } + + private static Vector3 GsToMs2(Vector3 gs) + { + return gs / SDL_STANDARD_GRAVITY; + } + + public void SetConfiguration(InputConfig configuration) + { + lock (_userMappingLock) + { + _configuration = (StandardControllerInputConfig)configuration; + + _buttonsUserMapping.Clear(); + + // First update sticks + _stickUserMapping[(int)StickInputId.Left] = (StickInputId)_configuration.LeftJoyconStick.Joystick; + _stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick; + + // Then left joycon + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (GamepadButtonInputId)_configuration.LeftJoycon.DpadUp)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (GamepadButtonInputId)_configuration.LeftJoycon.DpadDown)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (GamepadButtonInputId)_configuration.LeftJoycon.DpadRight)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonL)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl)); + + // Finally right joycon + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (GamepadButtonInputId)_configuration.RightJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (GamepadButtonInputId)_configuration.RightJoycon.ButtonA)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (GamepadButtonInputId)_configuration.RightJoycon.ButtonB)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (GamepadButtonInputId)_configuration.RightJoycon.ButtonX)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (GamepadButtonInputId)_configuration.RightJoycon.ButtonY)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (GamepadButtonInputId)_configuration.RightJoycon.ButtonR)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (GamepadButtonInputId)_configuration.RightJoycon.ButtonZr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSl)); + + SetTriggerThreshold(_configuration.TriggerThreshold); + } + } + + public GamepadStateSnapshot GetStateSnapshot() + { + return IGamepad.GetStateSnapshot(this); + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + GamepadStateSnapshot rawState = GetStateSnapshot(); + GamepadStateSnapshot result = default; + + lock (_userMappingLock) + { + if (_buttonsUserMapping.Count == 0) + { + return rawState; + } + + foreach (ButtonMappingEntry entry in _buttonsUserMapping) + { + if (entry.From == GamepadButtonInputId.Unbound || entry.To == GamepadButtonInputId.Unbound) + { + continue; + } + + // Do not touch state of button already pressed + if (!result.IsPressed(entry.To)) + { + result.SetPressed(entry.To, rawState.IsPressed(entry.From)); + } + } + + (float leftStickX, float leftStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Left]); + (float rightStickX, float rightStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Right]); + + result.SetStick(StickInputId.Left, leftStickX, leftStickY); + result.SetStick(StickInputId.Right, rightStickX, rightStickY); + } + + return result; + } + + private static float ConvertRawStickValue(short value) + { + const float ConvertRate = 1.0f / (short.MaxValue + 0.5f); + + return value * ConvertRate; + } + + public (float, float) GetStick(StickInputId inputId) + { + if (inputId == StickInputId.Unbound) + { + return (0.0f, 0.0f); + } + + short stickX; + short stickY; + + if (inputId == StickInputId.Left) + { + stickX = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX); + stickY = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY); + } + else if (inputId == StickInputId.Right) + { + stickX = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX); + stickY = SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY); + } + else + { + throw new NotSupportedException($"Unsupported stick {inputId}"); + } + + float resultX = ConvertRawStickValue(stickX); + float resultY = -ConvertRawStickValue(stickY); + + if (HasConfiguration) + { + if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.InvertStickX) || + (inputId == StickInputId.Right && _configuration.RightJoyconStick.InvertStickX)) + { + resultX = -resultX; + } + + if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.InvertStickY) || + (inputId == StickInputId.Right && _configuration.RightJoyconStick.InvertStickY)) + { + resultY = -resultY; + } + + if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.Rotate90CW) || + (inputId == StickInputId.Right && _configuration.RightJoyconStick.Rotate90CW)) + { + float temp = resultX; + resultX = resultY; + resultY = -temp; + } + } + + return (resultX, resultY); + } + + public bool IsPressed(GamepadButtonInputId inputId) + { + if (inputId == GamepadButtonInputId.LeftTrigger) + { + return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) > _triggerThreshold; + } + + if (inputId == GamepadButtonInputId.RightTrigger) + { + return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) > _triggerThreshold; + } + + if (_buttonsDriverMapping[(int)inputId] == SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID) + { + return false; + } + + return SDL_GameControllerGetButton(_gamepadHandle, _buttonsDriverMapping[(int)inputId]) == 1; + } + } +} diff --git a/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs new file mode 100644 index 00000000..c741493c --- /dev/null +++ b/src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs @@ -0,0 +1,175 @@ +using Ryujinx.SDL2.Common; +using System; +using System.Collections.Generic; +using static SDL2.SDL; + +namespace Ryujinx.Input.SDL2 +{ + public class SDL2GamepadDriver : IGamepadDriver + { + private readonly Dictionary _gamepadsInstanceIdsMapping; + private readonly List _gamepadsIds; + private readonly object _lock = new object(); + + public ReadOnlySpan GamepadsIds + { + get + { + lock (_lock) + { + return _gamepadsIds.ToArray(); + } + } + } + + public string DriverName => "SDL2"; + + public event Action OnGamepadConnected; + public event Action OnGamepadDisconnected; + + public SDL2GamepadDriver() + { + _gamepadsInstanceIdsMapping = new Dictionary(); + _gamepadsIds = new List(); + + SDL2Driver.Instance.Initialize(); + SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected; + SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected; + + // Add already connected gamepads + int numJoysticks = SDL_NumJoysticks(); + + for (int joystickIndex = 0; joystickIndex < numJoysticks; joystickIndex++) + { + HandleJoyStickConnected(joystickIndex, SDL_JoystickGetDeviceInstanceID(joystickIndex)); + } + } + + private string GenerateGamepadId(int joystickIndex) + { + Guid guid = SDL_JoystickGetDeviceGUID(joystickIndex); + + // Add a unique identifier to the start of the GUID in case of duplicates. + + if (guid == Guid.Empty) + { + return null; + } + + string id; + + lock (_lock) + { + int guidIndex = 0; + id = guidIndex + "-" + guid; + + while (_gamepadsIds.Contains(id)) + { + id = (++guidIndex) + "-" + guid; + } + } + + return id; + } + + private int GetJoystickIndexByGamepadId(string id) + { + lock (_lock) + { + return _gamepadsIds.IndexOf(id); + } + } + + private void HandleJoyStickDisconnected(int joystickInstanceId) + { + if (_gamepadsInstanceIdsMapping.TryGetValue(joystickInstanceId, out string id)) + { + _gamepadsInstanceIdsMapping.Remove(joystickInstanceId); + + lock (_lock) + { + _gamepadsIds.Remove(id); + } + + OnGamepadDisconnected?.Invoke(id); + } + } + + private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId) + { + if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE) + { + if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId)) + { + // Sometimes a JoyStick connected event fires after the app starts even though it was connected before + // so it is rejected to avoid doubling the entries. + return; + } + + string id = GenerateGamepadId(joystickDeviceId); + + if (id == null) + { + return; + } + + if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id)) + { + lock (_lock) + { + _gamepadsIds.Add(id); + } + + OnGamepadConnected?.Invoke(id); + } + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + SDL2Driver.Instance.OnJoyStickConnected -= HandleJoyStickConnected; + SDL2Driver.Instance.OnJoystickDisconnected -= HandleJoyStickDisconnected; + + // Simulate a full disconnect when disposing + foreach (string id in _gamepadsIds) + { + OnGamepadDisconnected?.Invoke(id); + } + + lock (_lock) + { + _gamepadsIds.Clear(); + } + + SDL2Driver.Instance.Dispose(); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + public IGamepad GetGamepad(string id) + { + int joystickIndex = GetJoystickIndexByGamepadId(id); + + if (joystickIndex == -1) + { + return null; + } + + IntPtr gamepadHandle = SDL_GameControllerOpen(joystickIndex); + + if (gamepadHandle == IntPtr.Zero) + { + return null; + } + + return new SDL2Gamepad(gamepadHandle, id); + } + } +} diff --git a/src/Ryujinx.Input.SDL2/SDL2Keyboard.cs b/src/Ryujinx.Input.SDL2/SDL2Keyboard.cs new file mode 100644 index 00000000..bc0a7e66 --- /dev/null +++ b/src/Ryujinx.Input.SDL2/SDL2Keyboard.cs @@ -0,0 +1,411 @@ +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using static SDL2.SDL; + +using ConfigKey = Ryujinx.Common.Configuration.Hid.Key; + +namespace Ryujinx.Input.SDL2 +{ + class SDL2Keyboard : IKeyboard + { + private class ButtonMappingEntry + { + public readonly GamepadButtonInputId To; + public readonly Key From; + + public ButtonMappingEntry(GamepadButtonInputId to, Key from) + { + To = to; + From = from; + } + } + + private readonly object _userMappingLock = new(); + +#pragma warning disable IDE0052 // Remove unread private member + private readonly SDL2KeyboardDriver _driver; +#pragma warning restore IDE0052 + private StandardKeyboardInputConfig _configuration; + private readonly List _buttonsUserMapping; + + private static readonly SDL_Keycode[] _keysDriverMapping = new SDL_Keycode[(int)Key.Count] + { + // INVALID + SDL_Keycode.SDLK_0, + // Presented as modifiers, so invalid here. + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + + SDL_Keycode.SDLK_F1, + SDL_Keycode.SDLK_F2, + SDL_Keycode.SDLK_F3, + SDL_Keycode.SDLK_F4, + SDL_Keycode.SDLK_F5, + SDL_Keycode.SDLK_F6, + SDL_Keycode.SDLK_F7, + SDL_Keycode.SDLK_F8, + SDL_Keycode.SDLK_F9, + SDL_Keycode.SDLK_F10, + SDL_Keycode.SDLK_F11, + SDL_Keycode.SDLK_F12, + SDL_Keycode.SDLK_F13, + SDL_Keycode.SDLK_F14, + SDL_Keycode.SDLK_F15, + SDL_Keycode.SDLK_F16, + SDL_Keycode.SDLK_F17, + SDL_Keycode.SDLK_F18, + SDL_Keycode.SDLK_F19, + SDL_Keycode.SDLK_F20, + SDL_Keycode.SDLK_F21, + SDL_Keycode.SDLK_F22, + SDL_Keycode.SDLK_F23, + SDL_Keycode.SDLK_F24, + + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_0, + + SDL_Keycode.SDLK_UP, + SDL_Keycode.SDLK_DOWN, + SDL_Keycode.SDLK_LEFT, + SDL_Keycode.SDLK_RIGHT, + SDL_Keycode.SDLK_RETURN, + SDL_Keycode.SDLK_ESCAPE, + SDL_Keycode.SDLK_SPACE, + SDL_Keycode.SDLK_TAB, + SDL_Keycode.SDLK_BACKSPACE, + SDL_Keycode.SDLK_INSERT, + SDL_Keycode.SDLK_DELETE, + SDL_Keycode.SDLK_PAGEUP, + SDL_Keycode.SDLK_PAGEDOWN, + SDL_Keycode.SDLK_HOME, + SDL_Keycode.SDLK_END, + SDL_Keycode.SDLK_CAPSLOCK, + SDL_Keycode.SDLK_SCROLLLOCK, + SDL_Keycode.SDLK_PRINTSCREEN, + SDL_Keycode.SDLK_PAUSE, + SDL_Keycode.SDLK_NUMLOCKCLEAR, + SDL_Keycode.SDLK_CLEAR, + SDL_Keycode.SDLK_KP_0, + SDL_Keycode.SDLK_KP_1, + SDL_Keycode.SDLK_KP_2, + SDL_Keycode.SDLK_KP_3, + SDL_Keycode.SDLK_KP_4, + SDL_Keycode.SDLK_KP_5, + SDL_Keycode.SDLK_KP_6, + SDL_Keycode.SDLK_KP_7, + SDL_Keycode.SDLK_KP_8, + SDL_Keycode.SDLK_KP_9, + SDL_Keycode.SDLK_KP_DIVIDE, + SDL_Keycode.SDLK_KP_MULTIPLY, + SDL_Keycode.SDLK_KP_MINUS, + SDL_Keycode.SDLK_KP_PLUS, + SDL_Keycode.SDLK_KP_DECIMAL, + SDL_Keycode.SDLK_KP_ENTER, + SDL_Keycode.SDLK_a, + SDL_Keycode.SDLK_b, + SDL_Keycode.SDLK_c, + SDL_Keycode.SDLK_d, + SDL_Keycode.SDLK_e, + SDL_Keycode.SDLK_f, + SDL_Keycode.SDLK_g, + SDL_Keycode.SDLK_h, + SDL_Keycode.SDLK_i, + SDL_Keycode.SDLK_j, + SDL_Keycode.SDLK_k, + SDL_Keycode.SDLK_l, + SDL_Keycode.SDLK_m, + SDL_Keycode.SDLK_n, + SDL_Keycode.SDLK_o, + SDL_Keycode.SDLK_p, + SDL_Keycode.SDLK_q, + SDL_Keycode.SDLK_r, + SDL_Keycode.SDLK_s, + SDL_Keycode.SDLK_t, + SDL_Keycode.SDLK_u, + SDL_Keycode.SDLK_v, + SDL_Keycode.SDLK_w, + SDL_Keycode.SDLK_x, + SDL_Keycode.SDLK_y, + SDL_Keycode.SDLK_z, + SDL_Keycode.SDLK_0, + SDL_Keycode.SDLK_1, + SDL_Keycode.SDLK_2, + SDL_Keycode.SDLK_3, + SDL_Keycode.SDLK_4, + SDL_Keycode.SDLK_5, + SDL_Keycode.SDLK_6, + SDL_Keycode.SDLK_7, + SDL_Keycode.SDLK_8, + SDL_Keycode.SDLK_9, + SDL_Keycode.SDLK_BACKQUOTE, + SDL_Keycode.SDLK_BACKQUOTE, + SDL_Keycode.SDLK_MINUS, + SDL_Keycode.SDLK_PLUS, + SDL_Keycode.SDLK_LEFTBRACKET, + SDL_Keycode.SDLK_RIGHTBRACKET, + SDL_Keycode.SDLK_SEMICOLON, + SDL_Keycode.SDLK_QUOTE, + SDL_Keycode.SDLK_COMMA, + SDL_Keycode.SDLK_PERIOD, + SDL_Keycode.SDLK_SLASH, + SDL_Keycode.SDLK_BACKSLASH, + + // Invalids + SDL_Keycode.SDLK_0, + }; + + public SDL2Keyboard(SDL2KeyboardDriver driver, string id, string name) + { + _driver = driver; + Id = id; + Name = name; + _buttonsUserMapping = new List(); + } + + private bool HasConfiguration => _configuration != null; + + public string Id { get; } + + public string Name { get; } + + public bool IsConnected => true; + + public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None; + + public void Dispose() + { + // No operations + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ToSDL2Scancode(Key key) + { + if (key >= Key.Unknown && key <= Key.Menu) + { + return -1; + } + + return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key]); + } + + private static SDL_Keymod GetKeyboardModifierMask(Key key) + { + return key switch + { + Key.ShiftLeft => SDL_Keymod.KMOD_LSHIFT, + Key.ShiftRight => SDL_Keymod.KMOD_RSHIFT, + Key.ControlLeft => SDL_Keymod.KMOD_LCTRL, + Key.ControlRight => SDL_Keymod.KMOD_RCTRL, + Key.AltLeft => SDL_Keymod.KMOD_LALT, + Key.AltRight => SDL_Keymod.KMOD_RALT, + Key.WinLeft => SDL_Keymod.KMOD_LGUI, + Key.WinRight => SDL_Keymod.KMOD_RGUI, + // NOTE: Menu key isn't supported by SDL2. + _ => SDL_Keymod.KMOD_NONE, + }; + } + + public KeyboardStateSnapshot GetKeyboardStateSnapshot() + { + ReadOnlySpan rawKeyboardState; + SDL_Keymod rawKeyboardModifierState = SDL_GetModState(); + + unsafe + { + IntPtr statePtr = SDL_GetKeyboardState(out int numKeys); + + rawKeyboardState = new ReadOnlySpan((byte*)statePtr, numKeys); + } + + bool[] keysState = new bool[(int)Key.Count]; + + for (Key key = 0; key < Key.Count; key++) + { + int index = ToSDL2Scancode(key); + if (index == -1) + { + SDL_Keymod modifierMask = GetKeyboardModifierMask(key); + + if (modifierMask == SDL_Keymod.KMOD_NONE) + { + continue; + } + + keysState[(int)key] = (rawKeyboardModifierState & modifierMask) == modifierMask; + } + else + { + keysState[(int)key] = rawKeyboardState[index] == 1; + } + } + + return new KeyboardStateSnapshot(keysState); + } + + private static float ConvertRawStickValue(short value) + { + const float ConvertRate = 1.0f / (short.MaxValue + 0.5f); + + return value * ConvertRate; + } + + private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick stickConfig) + { + short stickX = 0; + short stickY = 0; + + if (snapshot.IsPressed((Key)stickConfig.StickUp)) + { + stickY += 1; + } + + if (snapshot.IsPressed((Key)stickConfig.StickDown)) + { + stickY -= 1; + } + + if (snapshot.IsPressed((Key)stickConfig.StickRight)) + { + stickX += 1; + } + + if (snapshot.IsPressed((Key)stickConfig.StickLeft)) + { + stickX -= 1; + } + + Vector2 stick = Vector2.Normalize(new Vector2(stickX, stickY)); + + return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue)); + } + + public GamepadStateSnapshot GetMappedStateSnapshot() + { + KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot(); + GamepadStateSnapshot result = default; + + lock (_userMappingLock) + { + if (!HasConfiguration) + { + return result; + } + + foreach (ButtonMappingEntry entry in _buttonsUserMapping) + { + if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound) + { + continue; + } + + // Do not touch state of button already pressed + if (!result.IsPressed(entry.To)) + { + result.SetPressed(entry.To, rawState.IsPressed(entry.From)); + } + } + + (short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick); + (short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick); + + result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY)); + result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY)); + } + + return result; + } + + public GamepadStateSnapshot GetStateSnapshot() + { + throw new NotSupportedException(); + } + + public (float, float) GetStick(StickInputId inputId) + { + throw new NotSupportedException(); + } + + public bool IsPressed(GamepadButtonInputId inputId) + { + throw new NotSupportedException(); + } + + public bool IsPressed(Key key) + { + // We only implement GetKeyboardStateSnapshot. + throw new NotSupportedException(); + } + + public void SetConfiguration(InputConfig configuration) + { + lock (_userMappingLock) + { + _configuration = (StandardKeyboardInputConfig)configuration; + + // First clear the buttons mapping + _buttonsUserMapping.Clear(); + + // Then configure left joycon + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl)); + + // Finally configure right joycon + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr)); + _buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl)); + } + } + + public void SetTriggerThreshold(float triggerThreshold) + { + // No operations + } + + public void Rumble(float lowFrequency, float highFrequency, uint durationMs) + { + // No operations + } + + public Vector3 GetMotionData(MotionInputId inputId) + { + // No operations + + return Vector3.Zero; + } + } +} diff --git a/src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs b/src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs new file mode 100644 index 00000000..965f7935 --- /dev/null +++ b/src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs @@ -0,0 +1,55 @@ +using Ryujinx.SDL2.Common; +using System; + +namespace Ryujinx.Input.SDL2 +{ + public class SDL2KeyboardDriver : IGamepadDriver + { + public SDL2KeyboardDriver() + { + SDL2Driver.Instance.Initialize(); + } + + public string DriverName => "SDL2"; + + private static readonly string[] _keyboardIdentifers = new string[1] { "0" }; + + public ReadOnlySpan GamepadsIds => _keyboardIdentifers; + + public event Action OnGamepadConnected + { + add { } + remove { } + } + + public event Action OnGamepadDisconnected + { + add { } + remove { } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + SDL2Driver.Instance.Dispose(); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + public IGamepad GetGamepad(string id) + { + if (!_keyboardIdentifers[0].Equals(id)) + { + return null; + } + + return new SDL2Keyboard(this, _keyboardIdentifers[0], "All keyboards"); + } + } +} diff --git a/src/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs b/src/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs new file mode 100644 index 00000000..80fed2b8 --- /dev/null +++ b/src/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Ryujinx.Input.Assigner +{ + /// + /// implementation for regular . + /// + public class GamepadButtonAssigner : IButtonAssigner + { + private readonly IGamepad _gamepad; + + private GamepadStateSnapshot _currState; + + private GamepadStateSnapshot _prevState; + + private readonly JoystickButtonDetector _detector; + + private readonly bool _forStick; + + public GamepadButtonAssigner(IGamepad gamepad, float triggerThreshold, bool forStick) + { + _gamepad = gamepad; + _detector = new JoystickButtonDetector(); + _forStick = forStick; + + _gamepad?.SetTriggerThreshold(triggerThreshold); + } + + public void Initialize() + { + if (_gamepad != null) + { + _currState = _gamepad.GetStateSnapshot(); + _prevState = _currState; + } + } + + public void ReadInput() + { + if (_gamepad != null) + { + _prevState = _currState; + _currState = _gamepad.GetStateSnapshot(); + } + + CollectButtonStats(); + } + + public bool IsAnyButtonPressed() + { + return _detector.IsAnyButtonPressed(); + } + + public bool ShouldCancel() + { + return _gamepad == null || !_gamepad.IsConnected; + } + + public Button? GetPressedButton() + { + IEnumerable pressedButtons = _detector.GetPressedButtons(); + + return !_forStick ? new(pressedButtons.FirstOrDefault()) : new((StickInputId)pressedButtons.FirstOrDefault()); + } + + private void CollectButtonStats() + { + if (_forStick) + { + for (StickInputId inputId = StickInputId.Left; inputId < StickInputId.Count; inputId++) + { + (float x, float y) = _currState.GetStick(inputId); + + float value; + + if (x != 0.0f) + { + value = x; + } + else if (y != 0.0f) + { + value = y; + } + else + { + continue; + } + + _detector.AddInput((GamepadButtonInputId)inputId, value); + } + } + else + { + for (GamepadButtonInputId inputId = GamepadButtonInputId.A; inputId < GamepadButtonInputId.Count; inputId++) + { + if (_currState.IsPressed(inputId) && !_prevState.IsPressed(inputId)) + { + _detector.AddInput(inputId, 1); + } + + if (!_currState.IsPressed(inputId) && _prevState.IsPressed(inputId)) + { + _detector.AddInput(inputId, -1); + } + } + } + } + + private class JoystickButtonDetector + { + private readonly Dictionary _stats; + + public JoystickButtonDetector() + { + _stats = new Dictionary(); + } + + public bool IsAnyButtonPressed() + { + return _stats.Values.Any(CheckButtonPressed); + } + + public IEnumerable GetPressedButtons() + { + return _stats.Where(kvp => CheckButtonPressed(kvp.Value)).Select(kvp => kvp.Key); + } + + public void AddInput(GamepadButtonInputId button, float value) + { + + if (!_stats.TryGetValue(button, out InputSummary inputSummary)) + { + inputSummary = new InputSummary(); + _stats.Add(button, inputSummary); + } + + inputSummary.AddInput(value); + } + + public override string ToString() + { + StringWriter writer = new(); + + foreach (var kvp in _stats) + { + writer.WriteLine($"Button {kvp.Key} -> {kvp.Value}"); + } + + return writer.ToString(); + } + + private bool CheckButtonPressed(InputSummary sequence) + { + float distance = Math.Abs(sequence.Min - sequence.Avg) + Math.Abs(sequence.Max - sequence.Avg); + return distance > 1.5; // distance range [0, 2] + } + } + + private class InputSummary + { + public float Min, Max, Sum, Avg; + + public int NumSamples; + + public InputSummary() + { + Min = float.MaxValue; + Max = float.MinValue; + Sum = 0; + NumSamples = 0; + Avg = 0; + } + + public void AddInput(float value) + { + Min = Math.Min(Min, value); + Max = Math.Max(Max, value); + Sum += value; + NumSamples += 1; + Avg = Sum / NumSamples; + } + + public override string ToString() + { + return $"Avg: {Avg} Min: {Min} Max: {Max} Sum: {Sum} NumSamples: {NumSamples}"; + } + } + } +} diff --git a/src/Ryujinx.Input/Assigner/IButtonAssigner.cs b/src/Ryujinx.Input/Assigner/IButtonAssigner.cs new file mode 100644 index 00000000..688fbddb --- /dev/null +++ b/src/Ryujinx.Input/Assigner/IButtonAssigner.cs @@ -0,0 +1,36 @@ +namespace Ryujinx.Input.Assigner +{ + /// + /// An interface that allows to gather the driver input info to assign to a button on the UI. + /// + public interface IButtonAssigner + { + /// + /// Initialize the button assigner. + /// + void Initialize(); + + /// + /// Read input. + /// + void ReadInput(); + + /// + /// Check if a button was pressed. + /// + /// True if a button was pressed + bool IsAnyButtonPressed(); + + /// + /// Indicate if the user of this API should cancel operations. This is triggered for example when a gamepad get disconnected or when a user cancel assignation operations. + /// + /// True if the user of this API should cancel operations + bool ShouldCancel(); + + /// + /// Get the pressed button that was read in by the button assigner. + /// + /// The pressed button that was read + Button? GetPressedButton(); + } +} diff --git a/src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs b/src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs new file mode 100644 index 00000000..3c011a63 --- /dev/null +++ b/src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs @@ -0,0 +1,50 @@ +namespace Ryujinx.Input.Assigner +{ + /// + /// implementation for . + /// + public class KeyboardKeyAssigner : IButtonAssigner + { + private readonly IKeyboard _keyboard; + + private KeyboardStateSnapshot _keyboardState; + + public KeyboardKeyAssigner(IKeyboard keyboard) + { + _keyboard = keyboard; + } + + public void Initialize() { } + + public void ReadInput() + { + _keyboardState = _keyboard.GetKeyboardStateSnapshot(); + } + + public bool IsAnyButtonPressed() + { + return GetPressedButton() is not null; + } + + public bool ShouldCancel() + { + return _keyboardState.IsPressed(Key.Escape); + } + + public Button? GetPressedButton() + { + Button? keyPressed = null; + + for (Key key = Key.Unknown; key < Key.Count; key++) + { + if (_keyboardState.IsPressed(key)) + { + keyPressed = new(key); + break; + } + } + + return !ShouldCancel() ? keyPressed : null; + } + } +} diff --git a/src/Ryujinx.Input/Button.cs b/src/Ryujinx.Input/Button.cs new file mode 100644 index 00000000..4289901c --- /dev/null +++ b/src/Ryujinx.Input/Button.cs @@ -0,0 +1,33 @@ +using System; + +namespace Ryujinx.Input +{ + public readonly struct Button + { + public readonly ButtonType Type; + private readonly uint _rawValue; + + public Button(Key key) + { + Type = ButtonType.Key; + _rawValue = (uint)key; + } + + public Button(GamepadButtonInputId gamepad) + { + Type = ButtonType.GamepadButtonInputId; + _rawValue = (uint)gamepad; + } + + public Button(StickInputId stick) + { + Type = ButtonType.StickId; + _rawValue = (uint)stick; + } + + public T AsHidType() where T : Enum + { + return (T)Enum.ToObject(typeof(T), _rawValue); + } + } +} diff --git a/src/Ryujinx.Input/ButtonType.cs b/src/Ryujinx.Input/ButtonType.cs new file mode 100644 index 00000000..25ef5eea --- /dev/null +++ b/src/Ryujinx.Input/ButtonType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Input +{ + public enum ButtonType + { + Key, + GamepadButtonInputId, + StickId, + } +} diff --git a/src/Ryujinx.Input/GamepadButtonInputId.cs b/src/Ryujinx.Input/GamepadButtonInputId.cs new file mode 100644 index 00000000..59baa22b --- /dev/null +++ b/src/Ryujinx.Input/GamepadButtonInputId.cs @@ -0,0 +1,57 @@ +namespace Ryujinx.Input +{ + /// + /// Represent a button from a gamepad. + /// + public enum GamepadButtonInputId : byte + { + Unbound, + A, + B, + X, + Y, + LeftStick, + RightStick, + LeftShoulder, + RightShoulder, + + // Likely axis + LeftTrigger, + // Likely axis + RightTrigger, + + DpadUp, + DpadDown, + DpadLeft, + DpadRight, + + // Special buttons + + Minus, + Plus, + + Back = Minus, + Start = Plus, + + Guide, + Misc1, + + // Xbox Elite paddle + Paddle1, + Paddle2, + Paddle3, + Paddle4, + + // PS5 touchpad button + Touchpad, + + // Virtual buttons for single joycon + SingleLeftTrigger0, + SingleRightTrigger0, + + SingleLeftTrigger1, + SingleRightTrigger1, + + Count, + } +} diff --git a/src/Ryujinx.Input/GamepadFeaturesFlag.cs b/src/Ryujinx.Input/GamepadFeaturesFlag.cs new file mode 100644 index 00000000..69ec2368 --- /dev/null +++ b/src/Ryujinx.Input/GamepadFeaturesFlag.cs @@ -0,0 +1,28 @@ +using System; + +namespace Ryujinx.Input +{ + /// + /// Represent features supported by a . + /// + [Flags] + public enum GamepadFeaturesFlag + { + /// + /// No features are supported + /// + None, + + /// + /// Rumble + /// + /// Also named haptic + Rumble, + + /// + /// Motion + /// Also named sixaxis + /// + Motion, + } +} diff --git a/src/Ryujinx.Input/GamepadStateSnapshot.cs b/src/Ryujinx.Input/GamepadStateSnapshot.cs new file mode 100644 index 00000000..3de08f57 --- /dev/null +++ b/src/Ryujinx.Input/GamepadStateSnapshot.cs @@ -0,0 +1,70 @@ +using Ryujinx.Common.Memory; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Input +{ + /// + /// A snapshot of a . + /// + public struct GamepadStateSnapshot + { + // NOTE: Update Array size if JoystickInputId is changed. + private Array3> _joysticksState; + // NOTE: Update Array size if GamepadInputId is changed. + private Array28 _buttonsState; + + /// + /// Create a new instance of . + /// + /// The joysticks state + /// The buttons state + public GamepadStateSnapshot(Array3> joysticksState, Array28 buttonsState) + { + _joysticksState = joysticksState; + _buttonsState = buttonsState; + } + + /// + /// Check if a given input button is pressed. + /// + /// The button id + /// True if the given button is pressed + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsPressed(GamepadButtonInputId inputId) => _buttonsState[(int)inputId]; + + + /// + /// Set the state of a given button. + /// + /// The button id + /// The state to assign for the given button. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetPressed(GamepadButtonInputId inputId, bool value) => _buttonsState[(int)inputId] = value; + + /// + /// Get the values of a given input joystick. + /// + /// The stick id + /// The values of the given input joystick + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public (float, float) GetStick(StickInputId inputId) + { + var result = _joysticksState[(int)inputId]; + + return (result[0], result[1]); + } + + /// + /// Set the values of a given input joystick. + /// + /// The stick id + /// The x axis value + /// The y axis value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetStick(StickInputId inputId, float x, float y) + { + _joysticksState[(int)inputId][0] = x; + _joysticksState[(int)inputId][1] = y; + } + } +} diff --git a/src/Ryujinx.Input/HLE/InputManager.cs b/src/Ryujinx.Input/HLE/InputManager.cs new file mode 100644 index 00000000..7111f550 --- /dev/null +++ b/src/Ryujinx.Input/HLE/InputManager.cs @@ -0,0 +1,55 @@ +using System; + +namespace Ryujinx.Input.HLE +{ + public class InputManager : IDisposable + { + public IGamepadDriver KeyboardDriver { get; private set; } + public IGamepadDriver GamepadDriver { get; private set; } + public IGamepadDriver MouseDriver { get; private set; } + + public InputManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver) + { + KeyboardDriver = keyboardDriver; + GamepadDriver = gamepadDriver; + } + + public void SetMouseDriver(IGamepadDriver mouseDriver) + { + MouseDriver?.Dispose(); + + MouseDriver = mouseDriver; + } + + public NpadManager CreateNpadManager() + { + return new NpadManager(KeyboardDriver, GamepadDriver, MouseDriver); + } + + public TouchScreenManager CreateTouchScreenManager() + { + if (MouseDriver == null) + { + throw new InvalidOperationException("Mouse Driver has not been initialized."); + } + + return new TouchScreenManager(MouseDriver.GetGamepad("0") as IMouse); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + KeyboardDriver?.Dispose(); + GamepadDriver?.Dispose(); + MouseDriver?.Dispose(); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs new file mode 100644 index 00000000..38074528 --- /dev/null +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -0,0 +1,558 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Hid; +using System; +using System.Collections.Concurrent; +using System.Numerics; +using System.Runtime.CompilerServices; +using CemuHookClient = Ryujinx.Input.Motion.CemuHook.Client; +using ConfigControllerType = Ryujinx.Common.Configuration.Hid.ControllerType; + +namespace Ryujinx.Input.HLE +{ + public class NpadController : IDisposable + { + private class HLEButtonMappingEntry + { + public readonly GamepadButtonInputId DriverInputId; + public readonly ControllerKeys HLEInput; + + public HLEButtonMappingEntry(GamepadButtonInputId driverInputId, ControllerKeys hleInput) + { + DriverInputId = driverInputId; + HLEInput = hleInput; + } + } + + private static readonly HLEButtonMappingEntry[] _hleButtonMapping = { + new(GamepadButtonInputId.A, ControllerKeys.A), + new(GamepadButtonInputId.B, ControllerKeys.B), + new(GamepadButtonInputId.X, ControllerKeys.X), + new(GamepadButtonInputId.Y, ControllerKeys.Y), + new(GamepadButtonInputId.LeftStick, ControllerKeys.LStick), + new(GamepadButtonInputId.RightStick, ControllerKeys.RStick), + new(GamepadButtonInputId.LeftShoulder, ControllerKeys.L), + new(GamepadButtonInputId.RightShoulder, ControllerKeys.R), + new(GamepadButtonInputId.LeftTrigger, ControllerKeys.Zl), + new(GamepadButtonInputId.RightTrigger, ControllerKeys.Zr), + new(GamepadButtonInputId.DpadUp, ControllerKeys.DpadUp), + new(GamepadButtonInputId.DpadDown, ControllerKeys.DpadDown), + new(GamepadButtonInputId.DpadLeft, ControllerKeys.DpadLeft), + new(GamepadButtonInputId.DpadRight, ControllerKeys.DpadRight), + new(GamepadButtonInputId.Minus, ControllerKeys.Minus), + new(GamepadButtonInputId.Plus, ControllerKeys.Plus), + + new(GamepadButtonInputId.SingleLeftTrigger0, ControllerKeys.SlLeft), + new(GamepadButtonInputId.SingleRightTrigger0, ControllerKeys.SrLeft), + new(GamepadButtonInputId.SingleLeftTrigger1, ControllerKeys.SlRight), + new(GamepadButtonInputId.SingleRightTrigger1, ControllerKeys.SrRight), + }; + + private class HLEKeyboardMappingEntry + { + public readonly Key TargetKey; + public readonly byte Target; + + public HLEKeyboardMappingEntry(Key targetKey, byte target) + { + TargetKey = targetKey; + Target = target; + } + } + + private static readonly HLEKeyboardMappingEntry[] _keyMapping = { + new(Key.A, 0x4), + new(Key.B, 0x5), + new(Key.C, 0x6), + new(Key.D, 0x7), + new(Key.E, 0x8), + new(Key.F, 0x9), + new(Key.G, 0xA), + new(Key.H, 0xB), + new(Key.I, 0xC), + new(Key.J, 0xD), + new(Key.K, 0xE), + new(Key.L, 0xF), + new(Key.M, 0x10), + new(Key.N, 0x11), + new(Key.O, 0x12), + new(Key.P, 0x13), + new(Key.Q, 0x14), + new(Key.R, 0x15), + new(Key.S, 0x16), + new(Key.T, 0x17), + new(Key.U, 0x18), + new(Key.V, 0x19), + new(Key.W, 0x1A), + new(Key.X, 0x1B), + new(Key.Y, 0x1C), + new(Key.Z, 0x1D), + + new(Key.Number1, 0x1E), + new(Key.Number2, 0x1F), + new(Key.Number3, 0x20), + new(Key.Number4, 0x21), + new(Key.Number5, 0x22), + new(Key.Number6, 0x23), + new(Key.Number7, 0x24), + new(Key.Number8, 0x25), + new(Key.Number9, 0x26), + new(Key.Number0, 0x27), + + new(Key.Enter, 0x28), + new(Key.Escape, 0x29), + new(Key.BackSpace, 0x2A), + new(Key.Tab, 0x2B), + new(Key.Space, 0x2C), + new(Key.Minus, 0x2D), + new(Key.Plus, 0x2E), + new(Key.BracketLeft, 0x2F), + new(Key.BracketRight, 0x30), + new(Key.BackSlash, 0x31), + new(Key.Tilde, 0x32), + new(Key.Semicolon, 0x33), + new(Key.Quote, 0x34), + new(Key.Grave, 0x35), + new(Key.Comma, 0x36), + new(Key.Period, 0x37), + new(Key.Slash, 0x38), + new(Key.CapsLock, 0x39), + + new(Key.F1, 0x3a), + new(Key.F2, 0x3b), + new(Key.F3, 0x3c), + new(Key.F4, 0x3d), + new(Key.F5, 0x3e), + new(Key.F6, 0x3f), + new(Key.F7, 0x40), + new(Key.F8, 0x41), + new(Key.F9, 0x42), + new(Key.F10, 0x43), + new(Key.F11, 0x44), + new(Key.F12, 0x45), + + new(Key.PrintScreen, 0x46), + new(Key.ScrollLock, 0x47), + new(Key.Pause, 0x48), + new(Key.Insert, 0x49), + new(Key.Home, 0x4A), + new(Key.PageUp, 0x4B), + new(Key.Delete, 0x4C), + new(Key.End, 0x4D), + new(Key.PageDown, 0x4E), + new(Key.Right, 0x4F), + new(Key.Left, 0x50), + new(Key.Down, 0x51), + new(Key.Up, 0x52), + + new(Key.NumLock, 0x53), + new(Key.KeypadDivide, 0x54), + new(Key.KeypadMultiply, 0x55), + new(Key.KeypadSubtract, 0x56), + new(Key.KeypadAdd, 0x57), + new(Key.KeypadEnter, 0x58), + new(Key.Keypad1, 0x59), + new(Key.Keypad2, 0x5A), + new(Key.Keypad3, 0x5B), + new(Key.Keypad4, 0x5C), + new(Key.Keypad5, 0x5D), + new(Key.Keypad6, 0x5E), + new(Key.Keypad7, 0x5F), + new(Key.Keypad8, 0x60), + new(Key.Keypad9, 0x61), + new(Key.Keypad0, 0x62), + new(Key.KeypadDecimal, 0x63), + + new(Key.F13, 0x68), + new(Key.F14, 0x69), + new(Key.F15, 0x6A), + new(Key.F16, 0x6B), + new(Key.F17, 0x6C), + new(Key.F18, 0x6D), + new(Key.F19, 0x6E), + new(Key.F20, 0x6F), + new(Key.F21, 0x70), + new(Key.F22, 0x71), + new(Key.F23, 0x72), + new(Key.F24, 0x73), + + new(Key.ControlLeft, 0xE0), + new(Key.ShiftLeft, 0xE1), + new(Key.AltLeft, 0xE2), + new(Key.WinLeft, 0xE3), + new(Key.ControlRight, 0xE4), + new(Key.ShiftRight, 0xE5), + new(Key.AltRight, 0xE6), + new(Key.WinRight, 0xE7), + }; + + private static readonly HLEKeyboardMappingEntry[] _keyModifierMapping = { + new(Key.ControlLeft, 0), + new(Key.ShiftLeft, 1), + new(Key.AltLeft, 2), + new(Key.WinLeft, 3), + new(Key.ControlRight, 4), + new(Key.ShiftRight, 5), + new(Key.AltRight, 6), + new(Key.WinRight, 7), + new(Key.CapsLock, 8), + new(Key.ScrollLock, 9), + new(Key.NumLock, 10), + }; + + private MotionInput _leftMotionInput; + private MotionInput _rightMotionInput; + + private IGamepad _gamepad; + private InputConfig _config; + + public IGamepadDriver GamepadDriver { get; private set; } + public GamepadStateSnapshot State { get; private set; } + + public string Id { get; private set; } + + private readonly CemuHookClient _cemuHookClient; + + public NpadController(CemuHookClient cemuHookClient) + { + State = default; + Id = null; + _cemuHookClient = cemuHookClient; + } + + public bool UpdateDriverConfiguration(IGamepadDriver gamepadDriver, InputConfig config) + { + GamepadDriver = gamepadDriver; + + _gamepad?.Dispose(); + + Id = config.Id; + _gamepad = GamepadDriver.GetGamepad(Id); + + UpdateUserConfiguration(config); + + return _gamepad != null; + } + + public void UpdateUserConfiguration(InputConfig config) + { + if (config is StandardControllerInputConfig controllerConfig) + { + bool needsMotionInputUpdate = _config is not StandardControllerInputConfig oldControllerConfig || + ((oldControllerConfig.Motion.EnableMotion != controllerConfig.Motion.EnableMotion) && + (oldControllerConfig.Motion.MotionBackend != controllerConfig.Motion.MotionBackend)); + + if (needsMotionInputUpdate) + { + UpdateMotionInput(controllerConfig.Motion); + } + } + else + { + // Non-controller doesn't have motions. + _leftMotionInput = null; + } + + _config = config; + + _gamepad?.SetConfiguration(config); + } + + private void UpdateMotionInput(MotionConfigController motionConfig) + { + if (motionConfig.MotionBackend != MotionInputBackendType.CemuHook) + { + _leftMotionInput = new MotionInput(); + } + else + { + _leftMotionInput = null; + } + } + + public void Update() + { + // _gamepad may be altered by other threads + var gamepad = _gamepad; + + if (gamepad != null && GamepadDriver != null) + { + State = gamepad.GetMappedStateSnapshot(); + + if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion) + { + if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver) + { + if (gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion)) + { + Vector3 accelerometer = gamepad.GetMotionData(MotionInputId.Accelerometer); + Vector3 gyroscope = gamepad.GetMotionData(MotionInputId.Gyroscope); + + accelerometer = new Vector3(accelerometer.X, -accelerometer.Z, accelerometer.Y); + gyroscope = new Vector3(gyroscope.X, -gyroscope.Z, gyroscope.Y); + + _leftMotionInput.Update(accelerometer, gyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone); + + if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair) + { + _rightMotionInput = _leftMotionInput; + } + } + } + else if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook && controllerConfig.Motion is CemuHookMotionConfigController cemuControllerConfig) + { + int clientId = (int)controllerConfig.PlayerIndex; + + // First of all ensure we are registered + _cemuHookClient.RegisterClient(clientId, cemuControllerConfig.DsuServerHost, cemuControllerConfig.DsuServerPort); + + // Then request and retrieve the data + _cemuHookClient.RequestData(clientId, cemuControllerConfig.Slot); + _cemuHookClient.TryGetData(clientId, cemuControllerConfig.Slot, out _leftMotionInput); + + if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair) + { + if (!cemuControllerConfig.MirrorInput) + { + _cemuHookClient.RequestData(clientId, cemuControllerConfig.AltSlot); + _cemuHookClient.TryGetData(clientId, cemuControllerConfig.AltSlot, out _rightMotionInput); + } + else + { + _rightMotionInput = _leftMotionInput; + } + } + } + } + } + else + { + // Reset states + State = default; + _leftMotionInput = null; + } + } + + public GamepadInput GetHLEInputState() + { + GamepadInput state = new(); + + // First update all buttons + foreach (HLEButtonMappingEntry entry in _hleButtonMapping) + { + if (State.IsPressed(entry.DriverInputId)) + { + state.Buttons |= entry.HLEInput; + } + } + + if (_gamepad is IKeyboard) + { + (float leftAxisX, float leftAxisY) = State.GetStick(StickInputId.Left); + (float rightAxisX, float rightAxisY) = State.GetStick(StickInputId.Right); + + state.LStick = new JoystickPosition + { + Dx = ClampAxis(leftAxisX), + Dy = ClampAxis(leftAxisY), + }; + + state.RStick = new JoystickPosition + { + Dx = ClampAxis(rightAxisX), + Dy = ClampAxis(rightAxisY), + }; + } + else if (_config is StandardControllerInputConfig controllerConfig) + { + (float leftAxisX, float leftAxisY) = State.GetStick(StickInputId.Left); + (float rightAxisX, float rightAxisY) = State.GetStick(StickInputId.Right); + + state.LStick = ClampToCircle(ApplyDeadzone(leftAxisX, leftAxisY, controllerConfig.DeadzoneLeft), controllerConfig.RangeLeft); + state.RStick = ClampToCircle(ApplyDeadzone(rightAxisX, rightAxisY, controllerConfig.DeadzoneRight), controllerConfig.RangeRight); + } + + return state; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static JoystickPosition ApplyDeadzone(float x, float y, float deadzone) + { + float magnitudeClamped = Math.Min(MathF.Sqrt(x * x + y * y), 1f); + + if (magnitudeClamped <= deadzone) + { + return new JoystickPosition { Dx = 0, Dy = 0 }; + } + + return new JoystickPosition + { + Dx = ClampAxis((x / magnitudeClamped) * ((magnitudeClamped - deadzone) / (1 - deadzone))), + Dy = ClampAxis((y / magnitudeClamped) * ((magnitudeClamped - deadzone) / (1 - deadzone))), + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short ClampAxis(float value) + { + if (Math.Sign(value) < 0) + { + return (short)Math.Max(value * -short.MinValue, short.MinValue); + } + + return (short)Math.Min(value * short.MaxValue, short.MaxValue); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static JoystickPosition ClampToCircle(JoystickPosition position, float range) + { + Vector2 point = new Vector2(position.Dx, position.Dy) * range; + + if (point.Length() > short.MaxValue) + { + point = point / point.Length() * short.MaxValue; + } + + return new JoystickPosition + { + Dx = (int)point.X, + Dy = (int)point.Y, + }; + } + + public SixAxisInput GetHLEMotionState(bool isJoyconRightPair = false) + { + float[] orientationForHLE = new float[9]; + Vector3 gyroscope; + Vector3 accelerometer; + Vector3 rotation; + + MotionInput motionInput = _leftMotionInput; + + if (isJoyconRightPair) + { + if (_rightMotionInput == null) + { + return default; + } + + motionInput = _rightMotionInput; + } + + if (motionInput != null) + { + gyroscope = Truncate(motionInput.Gyroscrope * 0.0027f, 3); + accelerometer = Truncate(motionInput.Accelerometer, 3); + rotation = Truncate(motionInput.Rotation * 0.0027f, 3); + + Matrix4x4 orientation = motionInput.GetOrientation(); + + orientationForHLE[0] = Math.Clamp(orientation.M11, -1f, 1f); + orientationForHLE[1] = Math.Clamp(orientation.M12, -1f, 1f); + orientationForHLE[2] = Math.Clamp(orientation.M13, -1f, 1f); + orientationForHLE[3] = Math.Clamp(orientation.M21, -1f, 1f); + orientationForHLE[4] = Math.Clamp(orientation.M22, -1f, 1f); + orientationForHLE[5] = Math.Clamp(orientation.M23, -1f, 1f); + orientationForHLE[6] = Math.Clamp(orientation.M31, -1f, 1f); + orientationForHLE[7] = Math.Clamp(orientation.M32, -1f, 1f); + orientationForHLE[8] = Math.Clamp(orientation.M33, -1f, 1f); + } + else + { + gyroscope = new Vector3(); + accelerometer = new Vector3(); + rotation = new Vector3(); + } + + return new SixAxisInput + { + Accelerometer = accelerometer, + Gyroscope = gyroscope, + Rotation = rotation, + Orientation = orientationForHLE, + }; + } + + private static Vector3 Truncate(Vector3 value, int decimals) + { + float power = MathF.Pow(10, decimals); + + value.X = float.IsNegative(value.X) ? MathF.Ceiling(value.X * power) / power : MathF.Floor(value.X * power) / power; + value.Y = float.IsNegative(value.Y) ? MathF.Ceiling(value.Y * power) / power : MathF.Floor(value.Y * power) / power; + value.Z = float.IsNegative(value.Z) ? MathF.Ceiling(value.Z * power) / power : MathF.Floor(value.Z * power) / power; + + return value; + } + + public static KeyboardInput GetHLEKeyboardInput(IGamepadDriver KeyboardDriver) + { + var keyboard = KeyboardDriver.GetGamepad("0") as IKeyboard; + + KeyboardStateSnapshot keyboardState = keyboard.GetKeyboardStateSnapshot(); + + KeyboardInput hidKeyboard = new() + { + Modifier = 0, + Keys = new ulong[0x4], + }; + + foreach (HLEKeyboardMappingEntry entry in _keyMapping) + { + ulong value = keyboardState.IsPressed(entry.TargetKey) ? 1UL : 0UL; + + hidKeyboard.Keys[entry.Target / 0x40] |= (value << (entry.Target % 0x40)); + } + + foreach (HLEKeyboardMappingEntry entry in _keyModifierMapping) + { + int value = keyboardState.IsPressed(entry.TargetKey) ? 1 : 0; + + hidKeyboard.Modifier |= value << entry.Target; + } + + return hidKeyboard; + + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _gamepad?.Dispose(); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + public void UpdateRumble(ConcurrentQueue<(VibrationValue, VibrationValue)> queue) + { + if (queue.TryDequeue(out (VibrationValue, VibrationValue) dualVibrationValue)) + { + if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Rumble.EnableRumble) + { + VibrationValue leftVibrationValue = dualVibrationValue.Item1; + VibrationValue rightVibrationValue = dualVibrationValue.Item2; + + float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble)); + float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble)); + + _gamepad.Rumble(low, high, uint.MaxValue); + + Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " + + $"L.low.amp={leftVibrationValue.AmplitudeLow}, " + + $"L.high.amp={leftVibrationValue.AmplitudeHigh}, " + + $"R.low.amp={rightVibrationValue.AmplitudeLow}, " + + $"R.high.amp={rightVibrationValue.AmplitudeHigh} " + + $"--> ({low}, {high})"); + } + } + } + } +} diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs new file mode 100644 index 00000000..1dc87358 --- /dev/null +++ b/src/Ryujinx.Input/HLE/NpadManager.cs @@ -0,0 +1,357 @@ +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using Ryujinx.HLE.HOS.Services.Hid; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using CemuHookClient = Ryujinx.Input.Motion.CemuHook.Client; +using ControllerType = Ryujinx.Common.Configuration.Hid.ControllerType; +using PlayerIndex = Ryujinx.HLE.HOS.Services.Hid.PlayerIndex; +using Switch = Ryujinx.HLE.Switch; + +namespace Ryujinx.Input.HLE +{ + public class NpadManager : IDisposable + { + private readonly CemuHookClient _cemuHookClient; + + private readonly object _lock = new(); + + private bool _blockInputUpdates; + + private const int MaxControllers = 9; + + private readonly NpadController[] _controllers; + + private readonly IGamepadDriver _keyboardDriver; + private readonly IGamepadDriver _gamepadDriver; + private readonly IGamepadDriver _mouseDriver; + private bool _isDisposed; + + private List _inputConfig; + private bool _enableKeyboard; + private bool _enableMouse; + private Switch _device; + + public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver) + { + _controllers = new NpadController[MaxControllers]; + _cemuHookClient = new CemuHookClient(this); + + _keyboardDriver = keyboardDriver; + _gamepadDriver = gamepadDriver; + _mouseDriver = mouseDriver; + _inputConfig = new List(); + + _gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; + _gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; + } + + private void RefreshInputConfigForHLE() + { + lock (_lock) + { + List validInputs = new(); + foreach (var inputConfigEntry in _inputConfig) + { + if (_controllers[(int)inputConfigEntry.PlayerIndex] != null) + { + validInputs.Add(inputConfigEntry); + } + } + + _device.Hid.RefreshInputConfig(validInputs); + } + } + + private void HandleOnGamepadDisconnected(string obj) + { + // Force input reload + lock (_lock) + { + // Forcibly disconnect any controllers with this ID. + for (int i = 0; i < _controllers.Length; i++) + { + if (_controllers[i]?.Id == obj) + { + _controllers[i]?.Dispose(); + _controllers[i] = null; + } + } + + ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse); + } + } + + private void HandleOnGamepadConnected(string id) + { + // Force input reload + ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool DriverConfigurationUpdate(ref NpadController controller, InputConfig config) + { + IGamepadDriver targetDriver = _gamepadDriver; + + if (config is StandardControllerInputConfig) + { + targetDriver = _gamepadDriver; + } + else if (config is StandardKeyboardInputConfig) + { + targetDriver = _keyboardDriver; + } + + Debug.Assert(targetDriver != null, "Unknown input configuration!"); + + if (controller.GamepadDriver != targetDriver || controller.Id != config.Id) + { + return controller.UpdateDriverConfiguration(targetDriver, config); + } + + return controller.GamepadDriver != null; + } + + public void ReloadConfiguration(List inputConfig, bool enableKeyboard, bool enableMouse) + { + lock (_lock) + { + NpadController[] oldControllers = _controllers.ToArray(); + + List validInputs = new(); + + foreach (InputConfig inputConfigEntry in inputConfig) + { + NpadController controller; + int index = (int)inputConfigEntry.PlayerIndex; + + if (oldControllers[index] != null) + { + // Try reuse the existing controller. + controller = oldControllers[index]; + oldControllers[index] = null; + } + else + { + controller = new(_cemuHookClient); + } + + bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry); + + if (!isValid) + { + _controllers[index] = null; + controller.Dispose(); + } + else + { + _controllers[index] = controller; + validInputs.Add(inputConfigEntry); + } + } + + for (int i = 0; i < oldControllers.Length; i++) + { + // Disconnect any controllers that weren't reused by the new configuration. + + oldControllers[i]?.Dispose(); + oldControllers[i] = null; + } + + _inputConfig = inputConfig; + _enableKeyboard = enableKeyboard; + _enableMouse = enableMouse; + + _device.Hid.RefreshInputConfig(validInputs); + } + } + + public void UnblockInputUpdates() + { + lock (_lock) + { + foreach (InputConfig inputConfig in _inputConfig) + { + _controllers[(int)inputConfig.PlayerIndex]?.GamepadDriver?.Clear(); + } + + _blockInputUpdates = false; + } + } + + public void BlockInputUpdates() + { + lock (_lock) + { + _blockInputUpdates = true; + } + } + + public void Initialize(Switch device, List inputConfig, bool enableKeyboard, bool enableMouse) + { + _device = device; + _device.Configuration.RefreshInputConfig = RefreshInputConfigForHLE; + + ReloadConfiguration(inputConfig, enableKeyboard, enableMouse); + } + + public void Update(float aspectRatio = 1) + { + lock (_lock) + { + List hleInputStates = new(); + List hleMotionStates = new(NpadDevices.MaxControllers); + + KeyboardInput? hleKeyboardInput = null; + + foreach (InputConfig inputConfig in _inputConfig) + { + GamepadInput inputState = default; + (SixAxisInput, SixAxisInput) motionState = default; + + NpadController controller = _controllers[(int)inputConfig.PlayerIndex]; + PlayerIndex playerIndex = (PlayerIndex)inputConfig.PlayerIndex; + + bool isJoyconPair = false; + + // Do we allow input updates and is a controller connected? + if (!_blockInputUpdates && controller != null) + { + DriverConfigurationUpdate(ref controller, inputConfig); + + controller.UpdateUserConfiguration(inputConfig); + controller.Update(); + controller.UpdateRumble(_device.Hid.Npads.GetRumbleQueue(playerIndex)); + + inputState = controller.GetHLEInputState(); + + inputState.Buttons |= _device.Hid.UpdateStickButtons(inputState.LStick, inputState.RStick); + + isJoyconPair = inputConfig.ControllerType == ControllerType.JoyconPair; + + var altMotionState = isJoyconPair ? controller.GetHLEMotionState(true) : default; + + motionState = (controller.GetHLEMotionState(), altMotionState); + } + else + { + // Ensure that orientation isn't null + motionState.Item1.Orientation = new float[9]; + } + + inputState.PlayerId = playerIndex; + motionState.Item1.PlayerId = playerIndex; + + hleInputStates.Add(inputState); + hleMotionStates.Add(motionState.Item1); + + if (isJoyconPair && !motionState.Item2.Equals(default)) + { + motionState.Item2.PlayerId = playerIndex; + + hleMotionStates.Add(motionState.Item2); + } + } + + if (!_blockInputUpdates && _enableKeyboard) + { + hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver); + } + + _device.Hid.Npads.Update(hleInputStates); + _device.Hid.Npads.UpdateSixAxis(hleMotionStates); + + if (hleKeyboardInput.HasValue) + { + _device.Hid.Keyboard.Update(hleKeyboardInput.Value); + } + + if (_enableMouse) + { + var mouse = _mouseDriver.GetGamepad("0") as IMouse; + + var mouseInput = IMouse.GetMouseStateSnapshot(mouse); + + uint buttons = 0; + + if (mouseInput.IsPressed(MouseButton.Button1)) + { + buttons |= 1 << 0; + } + + if (mouseInput.IsPressed(MouseButton.Button2)) + { + buttons |= 1 << 1; + } + + if (mouseInput.IsPressed(MouseButton.Button3)) + { + buttons |= 1 << 2; + } + + if (mouseInput.IsPressed(MouseButton.Button4)) + { + buttons |= 1 << 3; + } + + if (mouseInput.IsPressed(MouseButton.Button5)) + { + buttons |= 1 << 4; + } + + var position = IMouse.GetScreenPosition(mouseInput.Position, mouse.ClientSize, aspectRatio); + + _device.Hid.Mouse.Update((int)position.X, (int)position.Y, buttons, (int)mouseInput.Scroll.X, (int)mouseInput.Scroll.Y, true); + } + else + { + _device.Hid.Mouse.Update(0, 0); + } + + _device.TamperMachine.UpdateInput(hleInputStates); + } + } + + internal InputConfig GetPlayerInputConfigByIndex(int index) + { + lock (_lock) + { + return _inputConfig.Find(x => x.PlayerIndex == (Common.Configuration.Hid.PlayerIndex)index); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + lock (_lock) + { + if (!_isDisposed) + { + _cemuHookClient.Dispose(); + + _gamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; + _gamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; + + for (int i = 0; i < _controllers.Length; i++) + { + _controllers[i]?.Dispose(); + } + + _isDisposed = true; + } + } + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Input/HLE/TouchScreenManager.cs b/src/Ryujinx.Input/HLE/TouchScreenManager.cs new file mode 100644 index 00000000..c613f928 --- /dev/null +++ b/src/Ryujinx.Input/HLE/TouchScreenManager.cs @@ -0,0 +1,102 @@ +using Ryujinx.HLE; +using Ryujinx.HLE.HOS.Services.Hid; +using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen; +using System; + +namespace Ryujinx.Input.HLE +{ + public class TouchScreenManager : IDisposable + { + private readonly IMouse _mouse; + private Switch _device; + private bool _wasClicking; + + public TouchScreenManager(IMouse mouse) + { + _mouse = mouse; + } + + public void Initialize(Switch device) + { + _device = device; + } + + public bool Update(bool isFocused, bool isClicking = false, float aspectRatio = 0) + { + if (!isFocused || (!_wasClicking && !isClicking)) + { + // In case we lost focus, send the end touch. + if (_wasClicking && !isClicking) + { + MouseStateSnapshot snapshot = IMouse.GetMouseStateSnapshot(_mouse); + var touchPosition = IMouse.GetScreenPosition(snapshot.Position, _mouse.ClientSize, aspectRatio); + + TouchPoint currentPoint = new() + { + Attribute = TouchAttribute.End, + + X = (uint)touchPosition.X, + Y = (uint)touchPosition.Y, + + // Placeholder values till more data is acquired + DiameterX = 10, + DiameterY = 10, + Angle = 90, + }; + + _device.Hid.Touchscreen.Update(currentPoint); + + } + + _wasClicking = false; + + _device.Hid.Touchscreen.Update(); + + return false; + } + + if (aspectRatio > 0) + { + MouseStateSnapshot snapshot = IMouse.GetMouseStateSnapshot(_mouse); + var touchPosition = IMouse.GetScreenPosition(snapshot.Position, _mouse.ClientSize, aspectRatio); + + TouchAttribute attribute = TouchAttribute.None; + + if (!_wasClicking && isClicking) + { + attribute = TouchAttribute.Start; + } + else if (_wasClicking && !isClicking) + { + attribute = TouchAttribute.End; + } + + TouchPoint currentPoint = new() + { + Attribute = attribute, + + X = (uint)touchPosition.X, + Y = (uint)touchPosition.Y, + + // Placeholder values till more data is acquired + DiameterX = 10, + DiameterY = 10, + Angle = 90, + }; + + _device.Hid.Touchscreen.Update(currentPoint); + + _wasClicking = isClicking; + + return true; + } + + return false; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Input/IGamepad.cs b/src/Ryujinx.Input/IGamepad.cs new file mode 100644 index 00000000..3853f281 --- /dev/null +++ b/src/Ryujinx.Input/IGamepad.cs @@ -0,0 +1,122 @@ +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Memory; +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Input +{ + /// + /// Represent an emulated gamepad. + /// + public interface IGamepad : IDisposable + { + /// + /// Features supported by the gamepad. + /// + GamepadFeaturesFlag Features { get; } + + /// + /// Unique Id of the gamepad. + /// + string Id { get; } + + /// + /// The name of the gamepad. + /// + string Name { get; } + + /// + /// True if the gamepad is connected. + /// + bool IsConnected { get; } + + /// + /// Check if a given input button is pressed on the gamepad. + /// + /// The button id + /// True if the given button is pressed on the gamepad + bool IsPressed(GamepadButtonInputId inputId); + + /// + /// Get the values of a given input joystick on the gamepad. + /// + /// The stick id + /// The values of the given input joystick on the gamepad + (float, float) GetStick(StickInputId inputId); + + /// + /// Get the values of a given motion sensors on the gamepad. + /// + /// The motion id + /// The values of the given motion sensors on the gamepad. + Vector3 GetMotionData(MotionInputId inputId); + + /// + /// Configure the threshold of the triggers on the gamepad. + /// + /// The threshold value for the triggers on the gamepad + void SetTriggerThreshold(float triggerThreshold); + + /// + /// Set the configuration of the gamepad. + /// + /// This expect config to be in the format expected by the driver + /// The configuration of the gamepad + void SetConfiguration(InputConfig configuration); + + /// + /// Starts a rumble effect on the gamepad. + /// + /// The intensity of the low frequency from 0.0f to 1.0f + /// The intensity of the high frequency from 0.0f to 1.0f + /// The duration of the rumble effect in milliseconds. + void Rumble(float lowFrequency, float highFrequency, uint durationMs); + + /// + /// Get a snaphost of the state of the gamepad that is remapped with the informations from the set via . + /// + /// A remapped snaphost of the state of the gamepad. + GamepadStateSnapshot GetMappedStateSnapshot(); + + /// + /// Get a snaphost of the state of the gamepad. + /// + /// A snaphost of the state of the gamepad. + GamepadStateSnapshot GetStateSnapshot(); + + /// + /// Get a snaphost of the state of a gamepad. + /// + /// The gamepad to do a snapshot of + /// A snaphost of the state of the gamepad. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static GamepadStateSnapshot GetStateSnapshot(IGamepad gamepad) + { + // NOTE: Update Array size if JoystickInputId is changed. + Array3> joysticksState = default; + + for (StickInputId inputId = StickInputId.Left; inputId < StickInputId.Count; inputId++) + { + (float state0, float state1) = gamepad.GetStick(inputId); + + Array2 state = default; + + state[0] = state0; + state[1] = state1; + + joysticksState[(int)inputId] = state; + } + + // NOTE: Update Array size if GamepadInputId is changed. + Array28 buttonsState = default; + + for (GamepadButtonInputId inputId = GamepadButtonInputId.A; inputId < GamepadButtonInputId.Count; inputId++) + { + buttonsState[(int)inputId] = gamepad.IsPressed(inputId); + } + + return new GamepadStateSnapshot(joysticksState, buttonsState); + } + } +} diff --git a/src/Ryujinx.Input/IGamepadDriver.cs b/src/Ryujinx.Input/IGamepadDriver.cs new file mode 100644 index 00000000..625c3e69 --- /dev/null +++ b/src/Ryujinx.Input/IGamepadDriver.cs @@ -0,0 +1,43 @@ +using System; + +namespace Ryujinx.Input +{ + /// + /// Represent an emulated gamepad driver used to provide input in the emulator. + /// + public interface IGamepadDriver : IDisposable + { + /// + /// The name of the driver + /// + string DriverName { get; } + + /// + /// The unique ids of the gamepads connected. + /// + ReadOnlySpan GamepadsIds { get; } + + /// + /// Event triggered when a gamepad is connected. + /// + event Action OnGamepadConnected; + + /// + /// Event triggered when a gamepad is disconnected. + /// + event Action OnGamepadDisconnected; + + /// + /// Open a gampad by its unique id. + /// + /// The unique id of the gamepad + /// An instance of associated to the gamepad id given or null if not found + IGamepad GetGamepad(string id); + + /// + /// Clear the internal state of the driver. + /// + /// Does nothing by default. + void Clear() { } + } +} diff --git a/src/Ryujinx.Input/IKeyboard.cs b/src/Ryujinx.Input/IKeyboard.cs new file mode 100644 index 00000000..2fc66011 --- /dev/null +++ b/src/Ryujinx.Input/IKeyboard.cs @@ -0,0 +1,41 @@ +using System.Runtime.CompilerServices; + +namespace Ryujinx.Input +{ + /// + /// Represent an emulated keyboard. + /// + public interface IKeyboard : IGamepad + { + /// + /// Check if a given key is pressed on the keyboard. + /// + /// The key + /// True if the given key is pressed on the keyboard + bool IsPressed(Key key); + + /// + /// Get a snaphost of the state of the keyboard. + /// + /// A snaphost of the state of the keyboard. + KeyboardStateSnapshot GetKeyboardStateSnapshot(); + + /// + /// Get a snaphost of the state of a keyboard. + /// + /// The keyboard to do a snapshot of + /// A snaphost of the state of the keyboard. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static KeyboardStateSnapshot GetStateSnapshot(IKeyboard keyboard) + { + bool[] keysState = new bool[(int)Key.Count]; + + for (Key key = 0; key < Key.Count; key++) + { + keysState[(int)key] = keyboard.IsPressed(key); + } + + return new KeyboardStateSnapshot(keysState); + } + } +} diff --git a/src/Ryujinx.Input/IMouse.cs b/src/Ryujinx.Input/IMouse.cs new file mode 100644 index 00000000..e20e7798 --- /dev/null +++ b/src/Ryujinx.Input/IMouse.cs @@ -0,0 +1,106 @@ +using System.Drawing; +using System.Numerics; + +namespace Ryujinx.Input +{ + /// + /// Represent an emulated mouse. + /// + public interface IMouse : IGamepad + { +#pragma warning disable IDE0051 // Remove unused private member + private const int SwitchPanelWidth = 1280; +#pragma warning restore IDE0051 + private const int SwitchPanelHeight = 720; + + /// + /// Check if a given button is pressed on the mouse. + /// + /// The button + /// True if the given button is pressed on the mouse + bool IsButtonPressed(MouseButton button); + + /// + /// Get the position of the mouse in the client. + /// + Vector2 GetPosition(); + + /// + /// Get the mouse scroll delta. + /// + Vector2 GetScroll(); + + /// + /// Get the client size. + /// + Size ClientSize { get; } + + /// + /// Get the button states of the mouse. + /// + bool[] Buttons { get; } + + /// + /// Get a snaphost of the state of a mouse. + /// + /// The mouse to do a snapshot of + /// A snaphost of the state of the mouse. + public static MouseStateSnapshot GetMouseStateSnapshot(IMouse mouse) + { + bool[] buttons = new bool[(int)MouseButton.Count]; + + mouse.Buttons.CopyTo(buttons, 0); + + return new MouseStateSnapshot(buttons, mouse.GetPosition(), mouse.GetScroll()); + } + + /// + /// Get the position of a mouse on screen relative to the app's view + /// + /// The position of the mouse in the client + /// The size of the client + /// The aspect ratio of the view + /// A snaphost of the state of the mouse. + public static Vector2 GetScreenPosition(Vector2 mousePosition, Size clientSize, float aspectRatio) + { + float mouseX = mousePosition.X; + float mouseY = mousePosition.Y; + + float aspectWidth = SwitchPanelHeight * aspectRatio; + + int screenWidth = clientSize.Width; + int screenHeight = clientSize.Height; + + if (clientSize.Width > clientSize.Height * aspectWidth / SwitchPanelHeight) + { + screenWidth = (int)(clientSize.Height * aspectWidth) / SwitchPanelHeight; + } + else + { + screenHeight = (clientSize.Width * SwitchPanelHeight) / (int)aspectWidth; + } + + int startX = (clientSize.Width - screenWidth) >> 1; + int startY = (clientSize.Height - screenHeight) >> 1; + + int endX = startX + screenWidth; + int endY = startY + screenHeight; + + if (mouseX >= startX && + mouseY >= startY && + mouseX < endX && + mouseY < endY) + { + int screenMouseX = (int)mouseX - startX; + int screenMouseY = (int)mouseY - startY; + + mouseX = (screenMouseX * (int)aspectWidth) / screenWidth; + mouseY = (screenMouseY * SwitchPanelHeight) / screenHeight; + + return new Vector2(mouseX, mouseY); + } + + return new Vector2(); + } + } +} diff --git a/src/Ryujinx.Input/Key.cs b/src/Ryujinx.Input/Key.cs new file mode 100644 index 00000000..60402215 --- /dev/null +++ b/src/Ryujinx.Input/Key.cs @@ -0,0 +1,142 @@ +namespace Ryujinx.Input +{ + /// + /// Represent a key from a keyboard. + /// + public enum Key + { + Unknown, + ShiftLeft, + ShiftRight, + ControlLeft, + ControlRight, + AltLeft, + AltRight, + WinLeft, + WinRight, + Menu, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + F25, + F26, + F27, + F28, + F29, + F30, + F31, + F32, + F33, + F34, + F35, + Up, + Down, + Left, + Right, + Enter, + Escape, + Space, + Tab, + BackSpace, + Insert, + Delete, + PageUp, + PageDown, + Home, + End, + CapsLock, + ScrollLock, + PrintScreen, + Pause, + NumLock, + Clear, + Keypad0, + Keypad1, + Keypad2, + Keypad3, + Keypad4, + Keypad5, + Keypad6, + Keypad7, + Keypad8, + Keypad9, + KeypadDivide, + KeypadMultiply, + KeypadSubtract, + KeypadAdd, + KeypadDecimal, + KeypadEnter, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + Number0, + Number1, + Number2, + Number3, + Number4, + Number5, + Number6, + Number7, + Number8, + Number9, + Tilde, + Grave, + Minus, + Plus, + BracketLeft, + BracketRight, + Semicolon, + Quote, + Comma, + Period, + Slash, + BackSlash, + Unbound, + + Count, + } +} diff --git a/src/Ryujinx.Input/KeyboardStateSnapshot.cs b/src/Ryujinx.Input/KeyboardStateSnapshot.cs new file mode 100644 index 00000000..e0374a86 --- /dev/null +++ b/src/Ryujinx.Input/KeyboardStateSnapshot.cs @@ -0,0 +1,29 @@ +using System.Runtime.CompilerServices; + +namespace Ryujinx.Input +{ + /// + /// A snapshot of a . + /// + public class KeyboardStateSnapshot + { + private readonly bool[] _keysState; + + /// + /// Create a new . + /// + /// The keys state + public KeyboardStateSnapshot(bool[] keysState) + { + _keysState = keysState; + } + + /// + /// Check if a given key is pressed. + /// + /// The key + /// True if the given key is pressed + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsPressed(Key key) => _keysState[(int)key]; + } +} diff --git a/src/Ryujinx.Input/Motion/CemuHook/Client.cs b/src/Ryujinx.Input/Motion/CemuHook/Client.cs new file mode 100644 index 00000000..e19f3d84 --- /dev/null +++ b/src/Ryujinx.Input/Motion/CemuHook/Client.cs @@ -0,0 +1,472 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Input.HLE; +using Ryujinx.Input.Motion.CemuHook.Protocol; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Hashing; +using System.Net; +using System.Net.Sockets; +using System.Numerics; +using System.Threading.Tasks; + +namespace Ryujinx.Input.Motion.CemuHook +{ + public class Client : IDisposable + { + public const uint Magic = 0x43555344; // DSUC + public const ushort Version = 1001; + + private bool _active; + + private readonly Dictionary _hosts; + private readonly Dictionary> _motionData; + private readonly Dictionary _clients; + + private readonly bool[] _clientErrorStatus = new bool[Enum.GetValues().Length]; + private readonly long[] _clientRetryTimer = new long[Enum.GetValues().Length]; + private readonly NpadManager _npadManager; + + public Client(NpadManager npadManager) + { + _npadManager = npadManager; + _hosts = new Dictionary(); + _motionData = new Dictionary>(); + _clients = new Dictionary(); + + CloseClients(); + } + + public void CloseClients() + { + _active = false; + + lock (_clients) + { + foreach (var client in _clients) + { + try + { + client.Value?.Dispose(); + } + catch (SocketException socketException) + { + Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to dispose motion client. Error: {socketException.ErrorCode}"); + } + } + + _hosts.Clear(); + _clients.Clear(); + _motionData.Clear(); + } + } + + public void RegisterClient(int player, string host, int port) + { + if (_clients.ContainsKey(player) || !CanConnect(player)) + { + return; + } + + lock (_clients) + { + if (_clients.ContainsKey(player) || !CanConnect(player)) + { + return; + } + + UdpClient client = null; + + try + { + IPEndPoint endPoint = new(IPAddress.Parse(host), port); + + client = new UdpClient(host, port); + + _clients.Add(player, client); + _hosts.Add(player, endPoint); + + _active = true; + + Task.Run(() => + { + ReceiveLoop(player); + }); + } + catch (FormatException formatException) + { + if (!_clientErrorStatus[player]) + { + Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to connect to motion source at {host}:{port}. Error: {formatException.Message}"); + + _clientErrorStatus[player] = true; + } + } + catch (SocketException socketException) + { + if (!_clientErrorStatus[player]) + { + Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to connect to motion source at {host}:{port}. Error: {socketException.ErrorCode}"); + + _clientErrorStatus[player] = true; + } + + RemoveClient(player); + + client?.Dispose(); + + SetRetryTimer(player); + } + catch (Exception exception) + { + Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to register motion client. Error: {exception.Message}"); + + _clientErrorStatus[player] = true; + + RemoveClient(player); + + client?.Dispose(); + + SetRetryTimer(player); + } + } + } + + public bool TryGetData(int player, int slot, out MotionInput input) + { + lock (_motionData) + { + if (_motionData.TryGetValue(player, out Dictionary value)) + { + if (value.TryGetValue(slot, out input)) + { + return true; + } + } + } + + input = null; + + return false; + } + + private void RemoveClient(int clientId) + { + _clients?.Remove(clientId); + + _hosts?.Remove(clientId); + } + + private void Send(byte[] data, int clientId) + { + if (_clients.TryGetValue(clientId, out UdpClient client)) + { + if (client != null && client.Client != null && client.Client.Connected) + { + try + { + client?.Send(data, data.Length); + } + catch (SocketException socketException) + { + if (!_clientErrorStatus[clientId]) + { + Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to send data request to motion source at {client.Client.RemoteEndPoint}. Error: {socketException.ErrorCode}"); + } + + _clientErrorStatus[clientId] = true; + + RemoveClient(clientId); + + client?.Dispose(); + + SetRetryTimer(clientId); + } + catch (ObjectDisposedException) + { + _clientErrorStatus[clientId] = true; + + RemoveClient(clientId); + + client?.Dispose(); + + SetRetryTimer(clientId); + } + } + } + } + + private byte[] Receive(int clientId, int timeout = 0) + { + if (_hosts.TryGetValue(clientId, out IPEndPoint endPoint) && _clients.TryGetValue(clientId, out UdpClient client)) + { + if (client != null && client.Client != null && client.Client.Connected) + { + client.Client.ReceiveTimeout = timeout; + + var result = client?.Receive(ref endPoint); + + if (result.Length > 0) + { + _clientErrorStatus[clientId] = false; + } + + return result; + } + } + + throw new Exception($"Client {clientId} is not registered."); + } + + private void SetRetryTimer(int clientId) + { + var elapsedMs = PerformanceCounter.ElapsedMilliseconds; + + _clientRetryTimer[clientId] = elapsedMs; + } + + private void ResetRetryTimer(int clientId) + { + _clientRetryTimer[clientId] = 0; + } + + private bool CanConnect(int clientId) + { + return _clientRetryTimer[clientId] == 0 || PerformanceCounter.ElapsedMilliseconds - 5000 > _clientRetryTimer[clientId]; + } + + public void ReceiveLoop(int clientId) + { + if (_hosts.TryGetValue(clientId, out IPEndPoint endPoint) && _clients.TryGetValue(clientId, out UdpClient client)) + { + if (client != null && client.Client != null && client.Client.Connected) + { + try + { + while (_active) + { + byte[] data = Receive(clientId); + + if (data.Length == 0) + { + continue; + } + + Task.Run(() => HandleResponse(data, clientId)); + } + } + catch (SocketException socketException) + { + if (!_clientErrorStatus[clientId]) + { + Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to receive data from motion source at {endPoint}. Error: {socketException.ErrorCode}"); + } + + _clientErrorStatus[clientId] = true; + + RemoveClient(clientId); + + client?.Dispose(); + + SetRetryTimer(clientId); + } + catch (ObjectDisposedException) + { + _clientErrorStatus[clientId] = true; + + RemoveClient(clientId); + + client?.Dispose(); + + SetRetryTimer(clientId); + } + } + } + } + + public void HandleResponse(byte[] data, int clientId) + { + ResetRetryTimer(clientId); + + MessageType type = (MessageType)BitConverter.ToUInt32(data.AsSpan().Slice(16, 4)); + + data = data.AsSpan()[16..].ToArray(); + + using MemoryStream stream = new(data); + using BinaryReader reader = new(stream); + + switch (type) + { + case MessageType.Protocol: + break; + case MessageType.Info: + ControllerInfoResponse contollerInfo = reader.ReadStruct(); + break; + case MessageType.Data: + ControllerDataResponse inputData = reader.ReadStruct(); + + Vector3 accelerometer = new() + { + X = -inputData.AccelerometerX, + Y = inputData.AccelerometerZ, + Z = -inputData.AccelerometerY, + }; + + Vector3 gyroscrope = new() + { + X = inputData.GyroscopePitch, + Y = inputData.GyroscopeRoll, + Z = -inputData.GyroscopeYaw, + }; + + ulong timestamp = inputData.MotionTimestamp; + + InputConfig config = _npadManager.GetPlayerInputConfigByIndex(clientId); + + lock (_motionData) + { + // Sanity check the configuration state and remove client if needed if needed. + if (config is StandardControllerInputConfig controllerConfig && + controllerConfig.Motion.EnableMotion && + controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook && + controllerConfig.Motion is CemuHookMotionConfigController cemuHookConfig) + { + int slot = inputData.Shared.Slot; + + if (_motionData.TryGetValue(clientId, out var motionDataItem)) + { + if (motionDataItem.TryGetValue(slot, out var previousData)) + { + previousData.Update(accelerometer, gyroscrope, timestamp, cemuHookConfig.Sensitivity, (float)cemuHookConfig.GyroDeadzone); + } + else + { + MotionInput input = new(); + + input.Update(accelerometer, gyroscrope, timestamp, cemuHookConfig.Sensitivity, (float)cemuHookConfig.GyroDeadzone); + + motionDataItem.Add(slot, input); + } + } + else + { + MotionInput input = new(); + + input.Update(accelerometer, gyroscrope, timestamp, cemuHookConfig.Sensitivity, (float)cemuHookConfig.GyroDeadzone); + + _motionData.Add(clientId, new Dictionary { { slot, input } }); + } + } + else + { + RemoveClient(clientId); + } + } + break; + } + } + + public void RequestInfo(int clientId, int slot) + { + if (!_active) + { + return; + } + + Header header = GenerateHeader(clientId); + + using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + using BinaryWriter writer = new(stream); + + writer.WriteStruct(header); + + ControllerInfoRequest request = new() + { + Type = MessageType.Info, + PortsCount = 4, + }; + + request.PortIndices[0] = (byte)slot; + + writer.WriteStruct(request); + + header.Length = (ushort)(stream.Length - 16); + + writer.Seek(6, SeekOrigin.Begin); + writer.Write(header.Length); + + Crc32.Hash(stream.ToArray(), header.Crc32.AsSpan()); + + writer.Seek(8, SeekOrigin.Begin); + writer.Write(header.Crc32.AsSpan()); + + byte[] data = stream.ToArray(); + + Send(data, clientId); + } + + public void RequestData(int clientId, int slot) + { + if (!_active) + { + return; + } + + Header header = GenerateHeader(clientId); + + using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + using BinaryWriter writer = new(stream); + + writer.WriteStruct(header); + + ControllerDataRequest request = new() + { + Type = MessageType.Data, + Slot = (byte)slot, + SubscriberType = SubscriberType.Slot, + }; + + writer.WriteStruct(request); + + header.Length = (ushort)(stream.Length - 16); + + writer.Seek(6, SeekOrigin.Begin); + writer.Write(header.Length); + + Crc32.Hash(stream.ToArray(), header.Crc32.AsSpan()); + + writer.Seek(8, SeekOrigin.Begin); + writer.Write(header.Crc32.AsSpan()); + + byte[] data = stream.ToArray(); + + Send(data, clientId); + } + + private static Header GenerateHeader(int clientId) + { + Header header = new() + { + Id = (uint)clientId, + MagicString = Magic, + Version = Version, + Length = 0, + }; + + return header; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _active = false; + + CloseClients(); + } + } +} diff --git a/src/Ryujinx.Input/Motion/CemuHook/Protocol/ControllerData.cs b/src/Ryujinx.Input/Motion/CemuHook/Protocol/ControllerData.cs new file mode 100644 index 00000000..3a2282ad --- /dev/null +++ b/src/Ryujinx.Input/Motion/CemuHook/Protocol/ControllerData.cs @@ -0,0 +1,47 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Input.Motion.CemuHook.Protocol +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ControllerDataRequest + { + public MessageType Type; + public SubscriberType SubscriberType; + public byte Slot; + public Array6 MacAddress; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ControllerDataResponse + { + public SharedResponse Shared; + public byte Connected; + public uint PacketId; + public byte ExtraButtons; + public byte MainButtons; + public ushort PSExtraInput; + public ushort LeftStickXY; + public ushort RightStickXY; + public uint DPadAnalog; + public ulong MainButtonsAnalog; + + public Array6 Touch1; + public Array6 Touch2; + + public ulong MotionTimestamp; + public float AccelerometerX; + public float AccelerometerY; + public float AccelerometerZ; + public float GyroscopePitch; + public float GyroscopeYaw; + public float GyroscopeRoll; + } + + enum SubscriberType : byte + { + All, + Slot, + Mac, + } +} diff --git a/src/Ryujinx.Input/Motion/CemuHook/Protocol/ControllerInfo.cs b/src/Ryujinx.Input/Motion/CemuHook/Protocol/ControllerInfo.cs new file mode 100644 index 00000000..d19c99c5 --- /dev/null +++ b/src/Ryujinx.Input/Motion/CemuHook/Protocol/ControllerInfo.cs @@ -0,0 +1,20 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Input.Motion.CemuHook.Protocol +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ControllerInfoResponse + { + public SharedResponse Shared; + private readonly byte _zero; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ControllerInfoRequest + { + public MessageType Type; + public int PortsCount; + public Array4 PortIndices; + } +} diff --git a/src/Ryujinx.Input/Motion/CemuHook/Protocol/Header.cs b/src/Ryujinx.Input/Motion/CemuHook/Protocol/Header.cs new file mode 100644 index 00000000..9faa189c --- /dev/null +++ b/src/Ryujinx.Input/Motion/CemuHook/Protocol/Header.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Input.Motion.CemuHook.Protocol +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct Header + { + public uint MagicString; + public ushort Version; + public ushort Length; + public Array4 Crc32; + public uint Id; + } +} diff --git a/src/Ryujinx.Input/Motion/CemuHook/Protocol/MessageType.cs b/src/Ryujinx.Input/Motion/CemuHook/Protocol/MessageType.cs new file mode 100644 index 00000000..5c4b04a2 --- /dev/null +++ b/src/Ryujinx.Input/Motion/CemuHook/Protocol/MessageType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Input.Motion.CemuHook.Protocol +{ + public enum MessageType : uint + { + Protocol = 0x100000, + Info, + Data, + } +} diff --git a/src/Ryujinx.Input/Motion/CemuHook/Protocol/SharedResponse.cs b/src/Ryujinx.Input/Motion/CemuHook/Protocol/SharedResponse.cs new file mode 100644 index 00000000..3cb52bb8 --- /dev/null +++ b/src/Ryujinx.Input/Motion/CemuHook/Protocol/SharedResponse.cs @@ -0,0 +1,51 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Input.Motion.CemuHook.Protocol +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SharedResponse + { + public MessageType Type; + public byte Slot; + public SlotState State; + public DeviceModelType ModelType; + public ConnectionType ConnectionType; + + public Array6 MacAddress; + public BatteryStatus BatteryStatus; + } + + public enum SlotState : byte + { + Disconnected, + Reserved, + Connected, + } + + public enum DeviceModelType : byte + { + None, + PartialGyro, + FullGyro, + } + + public enum ConnectionType : byte + { + None, + USB, + Bluetooth, + } + + public enum BatteryStatus : byte + { + NA, + Dying, + Low, + Medium, + High, + Full, + Charging, + Charged, + } +} diff --git a/src/Ryujinx.Input/Motion/MotionInput.cs b/src/Ryujinx.Input/Motion/MotionInput.cs new file mode 100644 index 00000000..9d781c58 --- /dev/null +++ b/src/Ryujinx.Input/Motion/MotionInput.cs @@ -0,0 +1,65 @@ +using Ryujinx.Input.Motion; +using System; +using System.Numerics; + +namespace Ryujinx.Input +{ + public class MotionInput + { + public ulong TimeStamp { get; set; } + public Vector3 Accelerometer { get; set; } + public Vector3 Gyroscrope { get; set; } + public Vector3 Rotation { get; set; } + + private readonly MotionSensorFilter _filter; + + public MotionInput() + { + TimeStamp = 0; + Accelerometer = new Vector3(); + Gyroscrope = new Vector3(); + Rotation = new Vector3(); + + // TODO: RE the correct filter. + _filter = new MotionSensorFilter(0f); + } + + public void Update(Vector3 accel, Vector3 gyro, ulong timestamp, int sensitivity, float deadzone) + { + if (TimeStamp != 0) + { + Accelerometer = -accel; + + if (gyro.Length() < deadzone) + { + gyro = Vector3.Zero; + } + + gyro *= (sensitivity / 100f); + + Gyroscrope = gyro; + + float deltaTime = MathF.Abs((long)(timestamp - TimeStamp) / 1000000f); + + Vector3 deltaGyro = gyro * deltaTime; + + Rotation += deltaGyro; + + _filter.SamplePeriod = deltaTime; + _filter.Update(accel, DegreeToRad(gyro)); + } + + TimeStamp = timestamp; + } + + public Matrix4x4 GetOrientation() + { + return Matrix4x4.CreateFromQuaternion(_filter.Quaternion); + } + + private static Vector3 DegreeToRad(Vector3 degree) + { + return degree * (MathF.PI / 180); + } + } +} diff --git a/src/Ryujinx.Input/Motion/MotionSensorFilter.cs b/src/Ryujinx.Input/Motion/MotionSensorFilter.cs new file mode 100644 index 00000000..e51633c1 --- /dev/null +++ b/src/Ryujinx.Input/Motion/MotionSensorFilter.cs @@ -0,0 +1,162 @@ +using System.Numerics; + +namespace Ryujinx.Input.Motion +{ + // MahonyAHRS class. Madgwick's implementation of Mayhony's AHRS algorithm. + // See: https://x-io.co.uk/open-source-imu-and-ahrs-algorithms/ + // Based on: https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs + class MotionSensorFilter + { + /// + /// Sample rate coefficient. + /// + public const float SampleRateCoefficient = 0.45f; + + /// + /// Gets or sets the sample period. + /// + public float SamplePeriod { get; set; } + + /// + /// Gets or sets the algorithm proportional gain. + /// + public float Kp { get; set; } + + /// + /// Gets or sets the algorithm integral gain. + /// + public float Ki { get; set; } + + /// + /// Gets the Quaternion output. + /// + public Quaternion Quaternion { get; private set; } + + /// + /// Integral error. + /// + private Vector3 _intergralError; + + /// + /// Initializes a new instance of the class. + /// + /// + /// Sample period. + /// + public MotionSensorFilter(float samplePeriod) : this(samplePeriod, 1f, 0f) { } + + /// + /// Initializes a new instance of the class. + /// + /// + /// Sample period. + /// + /// + /// Algorithm proportional gain. + /// + public MotionSensorFilter(float samplePeriod, float kp) : this(samplePeriod, kp, 0f) { } + + /// + /// Initializes a new instance of the class. + /// + /// + /// Sample period. + /// + /// + /// Algorithm proportional gain. + /// + /// + /// Algorithm integral gain. + /// + public MotionSensorFilter(float samplePeriod, float kp, float ki) + { + SamplePeriod = samplePeriod; + Kp = kp; + Ki = ki; + + Reset(); + + _intergralError = new Vector3(); + } + + /// + /// Algorithm IMU update method. Requires only gyroscope and accelerometer data. + /// + /// + /// Accelerometer measurement in any calibrated units. + /// + /// + /// Gyroscope measurement in radians. + /// + public void Update(Vector3 accel, Vector3 gyro) + { + // Normalise accelerometer measurement. + float norm = 1f / accel.Length(); + + if (!float.IsFinite(norm)) + { + return; + } + + accel *= norm; + + float q2 = Quaternion.X; + float q3 = Quaternion.Y; + float q4 = Quaternion.Z; + float q1 = Quaternion.W; + + // Estimated direction of gravity. + Vector3 gravity = new() + { + X = 2f * (q2 * q4 - q1 * q3), + Y = 2f * (q1 * q2 + q3 * q4), + Z = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4, + }; + + // Error is cross product between estimated direction and measured direction of gravity. + Vector3 error = new() + { + X = accel.Y * gravity.Z - accel.Z * gravity.Y, + Y = accel.Z * gravity.X - accel.X * gravity.Z, + Z = accel.X * gravity.Y - accel.Y * gravity.X, + }; + + if (Ki > 0f) + { + _intergralError += error; // Accumulate integral error. + } + else + { + _intergralError = Vector3.Zero; // Prevent integral wind up. + } + + // Apply feedback terms. + gyro += (Kp * error) + (Ki * _intergralError); + + // Integrate rate of change of quaternion. + Vector3 delta = new(q2, q3, q4); + + q1 += (-q2 * gyro.X - q3 * gyro.Y - q4 * gyro.Z) * (SampleRateCoefficient * SamplePeriod); + q2 += (q1 * gyro.X + delta.Y * gyro.Z - delta.Z * gyro.Y) * (SampleRateCoefficient * SamplePeriod); + q3 += (q1 * gyro.Y - delta.X * gyro.Z + delta.Z * gyro.X) * (SampleRateCoefficient * SamplePeriod); + q4 += (q1 * gyro.Z + delta.X * gyro.Y - delta.Y * gyro.X) * (SampleRateCoefficient * SamplePeriod); + + // Normalise quaternion. + Quaternion quaternion = new(q2, q3, q4, q1); + + norm = 1f / quaternion.Length(); + + if (!float.IsFinite(norm)) + { + return; + } + + Quaternion = quaternion * norm; + } + + public void Reset() + { + Quaternion = Quaternion.Identity; + } + } +} diff --git a/src/Ryujinx.Input/MotionInputId.cs b/src/Ryujinx.Input/MotionInputId.cs new file mode 100644 index 00000000..8aeb043a --- /dev/null +++ b/src/Ryujinx.Input/MotionInputId.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.Input +{ + /// + /// Represent a motion sensor on a gamepad. + /// + public enum MotionInputId : byte + { + /// + /// Invalid. + /// + Invalid, + + /// + /// Accelerometer. + /// + /// Values are in m/s^2 + Accelerometer, + + /// + /// Gyroscope. + /// + /// Values are in degrees + Gyroscope, + } +} diff --git a/src/Ryujinx.Input/MouseButton.cs b/src/Ryujinx.Input/MouseButton.cs new file mode 100644 index 00000000..a2991002 --- /dev/null +++ b/src/Ryujinx.Input/MouseButton.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Input +{ + public enum MouseButton : byte + { + Button1, + Button2, + Button3, + Button4, + Button5, + Button6, + Button7, + Button8, + Button9, + Count, + } +} diff --git a/src/Ryujinx.Input/MouseStateSnapshot.cs b/src/Ryujinx.Input/MouseStateSnapshot.cs new file mode 100644 index 00000000..9efc9f9c --- /dev/null +++ b/src/Ryujinx.Input/MouseStateSnapshot.cs @@ -0,0 +1,45 @@ +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Input +{ + /// + /// A snapshot of a . + /// + public class MouseStateSnapshot + { + private readonly bool[] _buttonState; + + /// + /// The position of the mouse cursor + /// + public Vector2 Position { get; } + + /// + /// The scroll delta of the mouse + /// + public Vector2 Scroll { get; } + + /// + /// Create a new . + /// + /// The button state + /// The position of the cursor + /// The scroll delta + public MouseStateSnapshot(bool[] buttonState, Vector2 position, Vector2 scroll) + { + _buttonState = buttonState; + + Position = position; + Scroll = scroll; + } + + /// + /// Check if a given button is pressed. + /// + /// The button + /// True if the given button is pressed + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsPressed(MouseButton button) => _buttonState[(int)button]; + } +} diff --git a/src/Ryujinx.Input/Ryujinx.Input.csproj b/src/Ryujinx.Input/Ryujinx.Input.csproj new file mode 100644 index 00000000..59a9eeb6 --- /dev/null +++ b/src/Ryujinx.Input/Ryujinx.Input.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + true + + + + + + + + + + + + diff --git a/src/Ryujinx.Input/StickInputId.cs b/src/Ryujinx.Input/StickInputId.cs new file mode 100644 index 00000000..fa2113ec --- /dev/null +++ b/src/Ryujinx.Input/StickInputId.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.Input +{ + /// + /// Represent a joystick from a gamepad. + /// + public enum StickInputId : byte + { + Unbound, + Left, + Right, + + Count, + } +} diff --git a/src/Ryujinx.Memory/AddressSpaceManager.cs b/src/Ryujinx.Memory/AddressSpaceManager.cs new file mode 100644 index 00000000..807c5c0f --- /dev/null +++ b/src/Ryujinx.Memory/AddressSpaceManager.cs @@ -0,0 +1,260 @@ +using Ryujinx.Memory.Range; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Memory +{ + /// + /// Represents a address space manager. + /// Supports virtual memory region mapping, address translation and read/write access to mapped regions. + /// + public sealed class AddressSpaceManager : VirtualMemoryManagerBase, IVirtualMemoryManager + { + /// + public bool UsesPrivateAllocations => false; + + /// + /// Address space width in bits. + /// + public int AddressSpaceBits { get; } + + private readonly MemoryBlock _backingMemory; + private readonly PageTable _pageTable; + + protected override ulong AddressSpaceSize { get; } + + /// + /// Creates a new instance of the memory manager. + /// + /// Physical backing memory where virtual memory will be mapped to + /// Size of the address space + public AddressSpaceManager(MemoryBlock backingMemory, ulong addressSpaceSize) + { + ulong asSize = PageSize; + int asBits = PageBits; + + while (asSize < addressSpaceSize) + { + asSize <<= 1; + asBits++; + } + + AddressSpaceBits = asBits; + AddressSpaceSize = asSize; + _backingMemory = backingMemory; + _pageTable = new PageTable(); + } + + /// + public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) + { + AssertValidAddressAndSize(va, size); + + while (size != 0) + { + _pageTable.Map(va, (nuint)(ulong)_backingMemory.GetPointer(pa, PageSize)); + + va += PageSize; + pa += PageSize; + size -= PageSize; + } + } + + public override void MapForeign(ulong va, nuint hostPointer, ulong size) + { + AssertValidAddressAndSize(va, size); + + while (size != 0) + { + _pageTable.Map(va, hostPointer); + + va += PageSize; + hostPointer += PageSize; + size -= PageSize; + } + } + + /// + public void Unmap(ulong va, ulong size) + { + AssertValidAddressAndSize(va, size); + + while (size != 0) + { + _pageTable.Unmap(va); + + va += PageSize; + size -= PageSize; + } + } + + /// + public unsafe ref T GetRef(ulong va) where T : unmanaged + { + if (!IsContiguous(va, Unsafe.SizeOf())) + { + ThrowMemoryNotContiguous(); + } + + return ref *(T*)GetHostAddress(va); + } + + /// + public IEnumerable GetHostRegions(ulong va, ulong size) + { + if (size == 0) + { + return Enumerable.Empty(); + } + + return GetHostRegionsImpl(va, size); + } + + /// + public IEnumerable GetPhysicalRegions(ulong va, ulong size) + { + if (size == 0) + { + return Enumerable.Empty(); + } + + var hostRegions = GetHostRegionsImpl(va, size); + if (hostRegions == null) + { + return null; + } + + var regions = new MemoryRange[hostRegions.Count]; + + ulong backingStart = (ulong)_backingMemory.Pointer; + ulong backingEnd = backingStart + _backingMemory.Size; + + int count = 0; + + for (int i = 0; i < regions.Length; i++) + { + var hostRegion = hostRegions[i]; + + if (hostRegion.Address >= backingStart && hostRegion.Address < backingEnd) + { + regions[count++] = new MemoryRange(hostRegion.Address - backingStart, hostRegion.Size); + } + } + + if (count != regions.Length) + { + return new ArraySegment(regions, 0, count); + } + + return regions; + } + + private List GetHostRegionsImpl(ulong va, ulong size) + { + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) + { + return null; + } + + int pages = GetPagesCount(va, size, out va); + + var regions = new List(); + + nuint regionStart = GetHostAddress(va); + ulong regionSize = PageSize; + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return null; + } + + nuint newHostAddress = GetHostAddress(va + PageSize); + + if (GetHostAddress(va) + PageSize != newHostAddress) + { + regions.Add(new HostMemoryRange(regionStart, regionSize)); + regionStart = newHostAddress; + regionSize = 0; + } + + va += PageSize; + regionSize += PageSize; + } + + regions.Add(new HostMemoryRange(regionStart, regionSize)); + + return regions; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool IsMapped(ulong va) + { + if (!ValidateAddress(va)) + { + return false; + } + + return _pageTable.Read(va) != 0; + } + + /// + public bool IsRangeMapped(ulong va, ulong size) + { + if (size == 0) + { + return true; + } + + if (!ValidateAddressAndSize(va, size)) + { + return false; + } + + int pages = GetPagesCount(va, (uint)size, out va); + + for (int page = 0; page < pages; page++) + { + if (!IsMapped(va)) + { + return false; + } + + va += PageSize; + } + + return true; + } + + private nuint GetHostAddress(ulong va) + { + return _pageTable.Read(va) + (nuint)(va & PageMask); + } + + /// + public void Reprotect(ulong va, ulong size, MemoryPermission protection) + { + } + + /// + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest = false) + { + throw new NotImplementedException(); + } + + protected unsafe override Memory GetPhysicalAddressMemory(nuint pa, int size) + => new NativeMemoryManager((byte*)pa, size).Memory; + + protected override unsafe Span GetPhysicalAddressSpan(nuint pa, int size) + => new Span((void*)pa, size); + + protected override nuint TranslateVirtualAddressChecked(ulong va) + => GetHostAddress(va); + + protected override nuint TranslateVirtualAddressUnchecked(ulong va) + => GetHostAddress(va); + } +} diff --git a/src/Ryujinx.Memory/BytesReadOnlySequenceSegment.cs b/src/Ryujinx.Memory/BytesReadOnlySequenceSegment.cs new file mode 100644 index 00000000..5fe8d936 --- /dev/null +++ b/src/Ryujinx.Memory/BytesReadOnlySequenceSegment.cs @@ -0,0 +1,60 @@ +using System; +using System.Buffers; +using System.Runtime.InteropServices; + +namespace Ryujinx.Memory +{ + /// + /// A concrete implementation of , + /// with methods to help build a full sequence. + /// + public sealed class BytesReadOnlySequenceSegment : ReadOnlySequenceSegment + { + public BytesReadOnlySequenceSegment(Memory memory) => Memory = memory; + + public BytesReadOnlySequenceSegment Append(Memory memory) + { + var nextSegment = new BytesReadOnlySequenceSegment(memory) + { + RunningIndex = RunningIndex + Memory.Length + }; + + Next = nextSegment; + + return nextSegment; + } + + /// + /// Attempts to determine if the current and are contiguous. + /// Only works if both were created by a . + /// + /// The segment to check if continuous with the current one + /// The starting address of the contiguous segment + /// The size of the contiguous segment + /// True if the segments are contiguous, otherwise false + public unsafe bool IsContiguousWith(Memory other, out nuint contiguousStart, out int contiguousSize) + { + if (MemoryMarshal.TryGetMemoryManager>(Memory, out var thisMemoryManager) && + MemoryMarshal.TryGetMemoryManager>(other, out var otherMemoryManager) && + thisMemoryManager.Pointer + thisMemoryManager.Length == otherMemoryManager.Pointer) + { + contiguousStart = (nuint)thisMemoryManager.Pointer; + contiguousSize = thisMemoryManager.Length + otherMemoryManager.Length; + return true; + } + else + { + contiguousStart = 0; + contiguousSize = 0; + return false; + } + } + + /// + /// Replaces the current value with the one provided. + /// + /// The new segment to hold in this + public void Replace(Memory memory) + => Memory = memory; + } +} diff --git a/src/Ryujinx.Memory/IRefCounted.cs b/src/Ryujinx.Memory/IRefCounted.cs new file mode 100644 index 00000000..697a1627 --- /dev/null +++ b/src/Ryujinx.Memory/IRefCounted.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Memory +{ + public interface IRefCounted + { + void IncrementReferenceCount(); + void DecrementReferenceCount(); + } +} diff --git a/src/Ryujinx.Memory/IVirtualMemoryManager.cs b/src/Ryujinx.Memory/IVirtualMemoryManager.cs new file mode 100644 index 00000000..102cedc9 --- /dev/null +++ b/src/Ryujinx.Memory/IVirtualMemoryManager.cs @@ -0,0 +1,230 @@ +using Ryujinx.Memory.Range; +using System; +using System.Buffers; +using System.Collections.Generic; + +namespace Ryujinx.Memory +{ + public interface IVirtualMemoryManager + { + /// + /// Indicates whether the memory manager creates private allocations when the flag is set on map. + /// + /// True if private mappings might be used, false otherwise + bool UsesPrivateAllocations { get; } + + /// + /// Maps a virtual memory range into a physical memory range. + /// + /// + /// Addresses and size must be page aligned. + /// + /// Virtual memory address + /// Physical memory address where the region should be mapped to + /// Size to be mapped + /// Flags controlling memory mapping + void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags); + + /// + /// Maps a virtual memory range into an arbitrary host memory range. + /// + /// + /// Addresses and size must be page aligned. + /// Not all memory managers supports this feature. + /// + /// Virtual memory address + /// Host pointer where the virtual region should be mapped + /// Size to be mapped + void MapForeign(ulong va, nuint hostPointer, ulong size); + + /// + /// Unmaps a previously mapped range of virtual memory. + /// + /// Virtual address of the range to be unmapped + /// Size of the range to be unmapped + void Unmap(ulong va, ulong size); + + /// + /// Reads data from CPU mapped memory. + /// + /// Type of the data being read + /// Virtual address of the data in memory + /// The data + /// Throw for unhandled invalid or unmapped memory accesses + T Read(ulong va) where T : unmanaged; + + /// + /// Reads data from CPU mapped memory. + /// + /// Virtual address of the data in memory + /// Span to store the data being read into + /// Throw for unhandled invalid or unmapped memory accesses + void Read(ulong va, Span data); + + /// + /// Writes data to CPU mapped memory. + /// + /// Type of the data being written + /// Virtual address to write the data into + /// Data to be written + /// Throw for unhandled invalid or unmapped memory accesses + void Write(ulong va, T value) where T : unmanaged; + + /// + /// Writes data to CPU mapped memory, with write tracking. + /// + /// Virtual address to write the data into + /// Data to be written + /// Throw for unhandled invalid or unmapped memory accesses + void Write(ulong va, ReadOnlySpan data); + + /// + /// Writes data to CPU mapped memory, with write tracking. + /// + /// Virtual address to write the data into + /// Data to be written + /// Throw for unhandled invalid or unmapped memory accesses + public void Write(ulong va, ReadOnlySequence data) + { + foreach (ReadOnlyMemory segment in data) + { + Write(va, segment.Span); + va += (ulong)segment.Length; + } + } + + /// + /// Writes data to the application process, returning false if the data was not changed. + /// This triggers read memory tracking, as a redundancy check would be useless if the data is not up to date. + /// + /// The memory manager can return that memory has changed when it hasn't to avoid expensive data copies. + /// Virtual address to write the data into + /// Data to be written + /// Throw for unhandled invalid or unmapped memory accesses + /// True if the data was changed, false otherwise + bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data); + + /// + /// Fills the specified memory region with the value specified in . + /// + /// Virtual address to fill the value into + /// Size of the memory region to fill + /// Value to fill with + void Fill(ulong va, ulong size, byte value) + { + const int MaxChunkSize = 1 << 24; + + for (ulong subOffset = 0; subOffset < size; subOffset += MaxChunkSize) + { + int copySize = (int)Math.Min(MaxChunkSize, size - subOffset); + + using var writableRegion = GetWritableRegion(va + subOffset, copySize); + + writableRegion.Memory.Span.Fill(value); + } + } + + /// + /// Gets a read-only sequence of read-only memory blocks from CPU mapped memory. + /// + /// Virtual address of the data + /// Size of the data + /// True if read tracking is triggered on the memory + /// A read-only sequence of read-only memory of the data + /// Throw for unhandled invalid or unmapped memory accesses + ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false); + + /// + /// Gets a read-only span of data from CPU mapped memory. + /// + /// Virtual address of the data + /// Size of the data + /// True if read tracking is triggered on the span + /// A read-only span of the data + /// Throw for unhandled invalid or unmapped memory accesses + ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false); + + /// + /// Gets a region of memory that can be written to. + /// + /// Virtual address of the data + /// Size of the data + /// True if write tracking is triggered on the span + /// A writable region of memory containing the data + /// Throw for unhandled invalid or unmapped memory accesses + WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false); + + /// + /// Gets a reference for the given type at the specified virtual memory address. + /// + /// + /// The data must be located at a contiguous memory region. + /// + /// Type of the data to get the reference + /// Virtual address of the data + /// A reference to the data in memory + /// Throw if the specified memory region is not contiguous in physical memory + ref T GetRef(ulong va) where T : unmanaged; + + /// + /// Gets the host regions that make up the given virtual address region. + /// If any part of the virtual region is unmapped, null is returned. + /// + /// Virtual address of the range + /// Size of the range + /// Array of host regions + IEnumerable GetHostRegions(ulong va, ulong size); + + /// + /// Gets the physical regions that make up the given virtual address region. + /// If any part of the virtual region is unmapped, null is returned. + /// + /// Virtual address of the range + /// Size of the range + /// Array of physical regions + IEnumerable GetPhysicalRegions(ulong va, ulong size); + + /// + /// Checks if the page at a given CPU virtual address is mapped. + /// + /// Virtual address to check + /// True if the address is mapped, false otherwise + bool IsMapped(ulong va); + + /// + /// Checks if a memory range is mapped. + /// + /// Virtual address of the range + /// Size of the range in bytes + /// True if the entire range is mapped, false otherwise + bool IsRangeMapped(ulong va, ulong size); + + /// + /// Alerts the memory tracking that a given region has been read from or written to. + /// This should be called before read/write is performed. + /// + /// Virtual address of the region + /// Size of the region + /// True if the region was written, false if read + /// True if the access is precise, false otherwise + /// Optional ID of the handles that should not be signalled + void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null); + + /// + /// Reprotect a region of virtual memory for guest access. + /// + /// Virtual address base + /// Size of the region to protect + /// Memory protection to set + void Reprotect(ulong va, ulong size, MemoryPermission protection); + + /// + /// Reprotect a region of virtual memory for tracking. + /// + /// Virtual address base + /// Size of the region to protect + /// Memory protection to set + /// True if the protection is for guest access, false otherwise + void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest); + } +} diff --git a/src/Ryujinx.Memory/IWritableBlock.cs b/src/Ryujinx.Memory/IWritableBlock.cs new file mode 100644 index 00000000..78ae2479 --- /dev/null +++ b/src/Ryujinx.Memory/IWritableBlock.cs @@ -0,0 +1,27 @@ +using System; +using System.Buffers; + +namespace Ryujinx.Memory +{ + public interface IWritableBlock + { + /// + /// Writes data to CPU mapped memory, with write tracking. + /// + /// Virtual address to write the data into + /// Data to be written + /// Throw for unhandled invalid or unmapped memory accesses + void Write(ulong va, ReadOnlySequence data) + { + foreach (ReadOnlyMemory segment in data) + { + Write(va, segment.Span); + va += (ulong)segment.Length; + } + } + + void Write(ulong va, ReadOnlySpan data); + + void WriteUntracked(ulong va, ReadOnlySpan data) => Write(va, data); + } +} diff --git a/src/Ryujinx.Memory/InvalidAccessHandler.cs b/src/Ryujinx.Memory/InvalidAccessHandler.cs new file mode 100644 index 00000000..963cee9d --- /dev/null +++ b/src/Ryujinx.Memory/InvalidAccessHandler.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Memory +{ + /// + /// Function that handles a invalid memory access from the emulated CPU. + /// + /// Virtual address of the invalid region that is being accessed + /// True if the invalid access should be ignored, false otherwise + public delegate bool InvalidAccessHandler(ulong va); +} diff --git a/src/Ryujinx.Memory/InvalidMemoryRegionException.cs b/src/Ryujinx.Memory/InvalidMemoryRegionException.cs new file mode 100644 index 00000000..55503dd7 --- /dev/null +++ b/src/Ryujinx.Memory/InvalidMemoryRegionException.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ryujinx.Memory +{ + public class InvalidMemoryRegionException : Exception + { + public InvalidMemoryRegionException() : base("Attempted to access an invalid memory region.") + { + } + + public InvalidMemoryRegionException(string message) : base(message) + { + } + + public InvalidMemoryRegionException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/Ryujinx.Memory/MemoryAllocationFlags.cs b/src/Ryujinx.Memory/MemoryAllocationFlags.cs new file mode 100644 index 00000000..d8d1a83c --- /dev/null +++ b/src/Ryujinx.Memory/MemoryAllocationFlags.cs @@ -0,0 +1,52 @@ +using System; + +namespace Ryujinx.Memory +{ + /// + /// Flags that controls allocation and other properties of the memory block memory. + /// + [Flags] + public enum MemoryAllocationFlags + { + /// + /// No special allocation settings. + /// + None = 0, + + /// + /// Reserve a region of memory on the process address space, + /// without actually allocation any backing memory. + /// + Reserve = 1 << 0, + + /// + /// Enables read and write tracking of the memory block. + /// This currently does nothing and is reserved for future use. + /// + Tracked = 1 << 1, + + /// + /// Enables mirroring of the memory block through aliasing of memory pages. + /// When enabled, this allows creating more memory blocks sharing the same backing storage. + /// + Mirrorable = 1 << 2, + + /// + /// Indicates that the memory block should support mapping views of a mirrorable memory block. + /// The block that is to have their views mapped should be created with the flag. + /// + ViewCompatible = 1 << 3, + + /// + /// If used with the flag, indicates that the memory block will only be used as + /// backing storage and will never be accessed directly, so the memory for the block will not be mapped. + /// + NoMap = 1 << 4, + + /// + /// Indicates that the memory will be used to store JIT generated code. + /// On some platforms, this requires special flags to be passed that will allow the memory to be executable. + /// + Jit = 1 << 5, + } +} diff --git a/src/Ryujinx.Memory/MemoryBlock.cs b/src/Ryujinx.Memory/MemoryBlock.cs new file mode 100644 index 00000000..59ee269b --- /dev/null +++ b/src/Ryujinx.Memory/MemoryBlock.cs @@ -0,0 +1,442 @@ +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.Memory +{ + /// + /// Represents a block of contiguous physical guest memory. + /// + public sealed class MemoryBlock : IWritableBlock, IDisposable + { + private readonly bool _usesSharedMemory; + private readonly bool _isMirror; + private readonly bool _viewCompatible; + private readonly bool _forJit; + private IntPtr _sharedMemory; + private IntPtr _pointer; + + /// + /// Pointer to the memory block data. + /// + public IntPtr Pointer => _pointer; + + /// + /// Size of the memory block. + /// + public ulong Size { get; } + + /// + /// Creates a new instance of the memory block class. + /// + /// Size of the memory block in bytes + /// Flags that controls memory block memory allocation + /// Throw when there's an error while allocating the requested size + /// Throw when the current platform is not supported + public MemoryBlock(ulong size, MemoryAllocationFlags flags = MemoryAllocationFlags.None) + { + if (flags.HasFlag(MemoryAllocationFlags.Mirrorable)) + { + _sharedMemory = MemoryManagement.CreateSharedMemory(size, flags.HasFlag(MemoryAllocationFlags.Reserve)); + + if (!flags.HasFlag(MemoryAllocationFlags.NoMap)) + { + _pointer = MemoryManagement.MapSharedMemory(_sharedMemory, size); + } + + _usesSharedMemory = true; + } + else if (flags.HasFlag(MemoryAllocationFlags.Reserve)) + { + _viewCompatible = flags.HasFlag(MemoryAllocationFlags.ViewCompatible); + _forJit = flags.HasFlag(MemoryAllocationFlags.Jit); + _pointer = MemoryManagement.Reserve(size, _forJit, _viewCompatible); + } + else + { + _forJit = flags.HasFlag(MemoryAllocationFlags.Jit); + _pointer = MemoryManagement.Allocate(size, _forJit); + } + + Size = size; + } + + /// + /// Creates a new instance of the memory block class, with a existing backing storage. + /// + /// Size of the memory block in bytes + /// Shared memory to use as backing storage for this block + /// Throw when there's an error while mapping the shared memory + /// Throw when the current platform is not supported + private MemoryBlock(ulong size, IntPtr sharedMemory) + { + _pointer = MemoryManagement.MapSharedMemory(sharedMemory, size); + Size = size; + _usesSharedMemory = true; + _isMirror = true; + } + + /// + /// Creates a memory block that shares the backing storage with this block. + /// The memory and page commitments will be shared, however memory protections are separate. + /// + /// A new memory block that shares storage with this one + /// Throw when the current memory block does not support mirroring + /// Throw when there's an error while mapping the shared memory + /// Throw when the current platform is not supported + public MemoryBlock CreateMirror() + { + if (_sharedMemory == IntPtr.Zero) + { + throw new NotSupportedException("Mirroring is not supported on the memory block because the Mirrorable flag was not set."); + } + + return new MemoryBlock(Size, _sharedMemory); + } + + /// + /// Commits a region of memory that has previously been reserved. + /// This can be used to allocate memory on demand. + /// + /// Starting offset of the range to be committed + /// Size of the range to be committed + /// Throw when the operation was not successful + /// Throw when the memory block has already been disposed + /// Throw when either or are out of range + public void Commit(ulong offset, ulong size) + { + MemoryManagement.Commit(GetPointerInternal(offset, size), size, _forJit); + } + + /// + /// Decommits a region of memory that has previously been reserved and optionally comitted. + /// This can be used to free previously allocated memory on demand. + /// + /// Starting offset of the range to be decommitted + /// Size of the range to be decommitted + /// Throw when the operation was not successful + /// Throw when the memory block has already been disposed + /// Throw when either or are out of range + public void Decommit(ulong offset, ulong size) + { + MemoryManagement.Decommit(GetPointerInternal(offset, size), size); + } + + /// + /// Maps a view of memory from another memory block. + /// + /// Memory block from where the backing memory will be taken + /// Offset on of the region that should be mapped + /// Offset to map the view into on this block + /// Size of the range to be mapped + /// Throw when the source memory block does not support mirroring + /// Throw when the memory block has already been disposed + /// Throw when either or are out of range + public void MapView(MemoryBlock srcBlock, ulong srcOffset, ulong dstOffset, ulong size) + { + if (srcBlock._sharedMemory == IntPtr.Zero) + { + throw new ArgumentException("The source memory block is not mirrorable, and thus cannot be mapped on the current block."); + } + + MemoryManagement.MapView(srcBlock._sharedMemory, srcOffset, GetPointerInternal(dstOffset, size), size, this); + } + + /// + /// Unmaps a view of memory from another memory block. + /// + /// Memory block from where the backing memory was taken during map + /// Offset of the view previously mapped with + /// Size of the range to be unmapped + public void UnmapView(MemoryBlock srcBlock, ulong offset, ulong size) + { + MemoryManagement.UnmapView(srcBlock._sharedMemory, GetPointerInternal(offset, size), size, this); + } + + /// + /// Reprotects a region of memory. + /// + /// Starting offset of the range to be reprotected + /// Size of the range to be reprotected + /// New memory permissions + /// True if a failed reprotect should throw + /// Throw when the memory block has already been disposed + /// Throw when either or are out of range + /// Throw when is invalid + public void Reprotect(ulong offset, ulong size, MemoryPermission permission, bool throwOnFail = true) + { + MemoryManagement.Reprotect(GetPointerInternal(offset, size), size, permission, _viewCompatible, throwOnFail); + } + + /// + /// Reads bytes from the memory block. + /// + /// Starting offset of the range being read + /// Span where the bytes being read will be copied to + /// Throw when the memory block has already been disposed + /// Throw when the memory region specified for the data is out of range + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Read(ulong offset, Span data) + { + GetSpan(offset, data.Length).CopyTo(data); + } + + /// + /// Reads data from the memory block. + /// + /// Type of the data + /// Offset where the data is located + /// Data at the specified address + /// Throw when the memory block has already been disposed + /// Throw when the memory region specified for the data is out of range + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Read(ulong offset) where T : unmanaged + { + return GetRef(offset); + } + + /// + /// Writes bytes to the memory block. + /// + /// Starting offset of the range being written + /// Span where the bytes being written will be copied from + /// Throw when the memory block has already been disposed + /// Throw when the memory region specified for the data is out of range + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ulong offset, ReadOnlySpan data) + { + data.CopyTo(GetSpan(offset, data.Length)); + } + + /// + /// Writes data to the memory block. + /// + /// Type of the data being written + /// Offset to write the data into + /// Data to be written + /// Throw when the memory block has already been disposed + /// Throw when the memory region specified for the data is out of range + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ulong offset, T data) where T : unmanaged + { + GetRef(offset) = data; + } + + /// + /// Copies data from one memory location to another. + /// + /// Destination offset to write the data into + /// Source offset to read the data from + /// Size of the copy in bytes + /// Throw when the memory block has already been disposed + /// Throw when , or is out of range + public void Copy(ulong dstOffset, ulong srcOffset, ulong size) + { + const int MaxChunkSize = 1 << 24; + + for (ulong offset = 0; offset < size; offset += MaxChunkSize) + { + int copySize = (int)Math.Min(MaxChunkSize, size - offset); + + Write(dstOffset + offset, GetSpan(srcOffset + offset, copySize)); + } + } + + /// + /// Fills a region of memory with . + /// + /// Offset of the region to fill with + /// Size in bytes of the region to fill + /// Value to use for the fill + /// Throw when the memory block has already been disposed + /// Throw when either or are out of range + public void Fill(ulong offset, ulong size, byte value) + { + const int MaxChunkSize = 1 << 24; + + for (ulong subOffset = 0; subOffset < size; subOffset += MaxChunkSize) + { + int copySize = (int)Math.Min(MaxChunkSize, size - subOffset); + + GetSpan(offset + subOffset, copySize).Fill(value); + } + } + + /// + /// Gets a reference of the data at a given memory block region. + /// + /// Data type + /// Offset of the memory region + /// A reference to the given memory region data + /// Throw when the memory block has already been disposed + /// Throw when either or are out of range + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe ref T GetRef(ulong offset) where T : unmanaged + { + IntPtr ptr = _pointer; + + ObjectDisposedException.ThrowIf(ptr == IntPtr.Zero, this); + + int size = Unsafe.SizeOf(); + + ulong endOffset = offset + (ulong)size; + + if (endOffset > Size || endOffset < offset) + { + ThrowInvalidMemoryRegionException(); + } + + return ref Unsafe.AsRef((void*)PtrAddr(ptr, offset)); + } + + /// + /// Gets the pointer of a given memory block region. + /// + /// Start offset of the memory region + /// Size in bytes of the region + /// The pointer to the memory region + /// Throw when the memory block has already been disposed + /// Throw when either or are out of range + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IntPtr GetPointer(ulong offset, ulong size) => GetPointerInternal(offset, size); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private IntPtr GetPointerInternal(ulong offset, ulong size) + { + IntPtr ptr = _pointer; + + ObjectDisposedException.ThrowIf(ptr == IntPtr.Zero, this); + + ulong endOffset = offset + size; + + if (endOffset > Size || endOffset < offset) + { + ThrowInvalidMemoryRegionException(); + } + + return PtrAddr(ptr, offset); + } + + /// + /// Gets the of a given memory block region. + /// + /// Start offset of the memory region + /// Size in bytes of the region + /// Span of the memory region + /// Throw when the memory block has already been disposed + /// Throw when either or are out of range + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe Span GetSpan(ulong offset, int size) + { + return new Span((void*)GetPointerInternal(offset, (ulong)size), size); + } + + /// + /// Gets the of a given memory block region. + /// + /// Start offset of the memory region + /// Size in bytes of the region + /// Memory of the memory region + /// Throw when the memory block has already been disposed + /// Throw when either or are out of range + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe Memory GetMemory(ulong offset, int size) + { + return new NativeMemoryManager((byte*)GetPointerInternal(offset, (ulong)size), size).Memory; + } + + /// + /// Gets a writable region of a given memory block region. + /// + /// Start offset of the memory region + /// Size in bytes of the region + /// Writable region of the memory region + /// Throw when the memory block has already been disposed + /// Throw when either or are out of range + public WritableRegion GetWritableRegion(ulong offset, int size) + { + return new WritableRegion(null, offset, GetMemory(offset, size)); + } + + /// + /// Adds a 64-bits offset to a native pointer. + /// + /// Native pointer + /// Offset to add + /// Native pointer with the added offset + private static IntPtr PtrAddr(IntPtr pointer, ulong offset) + { + return new IntPtr(pointer.ToInt64() + (long)offset); + } + + /// + /// Frees the memory allocated for this memory block. + /// + /// + /// It's an error to use the memory block after disposal. + /// + public void Dispose() + { + FreeMemory(); + + GC.SuppressFinalize(this); + } + + ~MemoryBlock() => FreeMemory(); + + private void FreeMemory() + { + IntPtr ptr = Interlocked.Exchange(ref _pointer, IntPtr.Zero); + + // If pointer is null, the memory was already freed or never allocated. + if (ptr != IntPtr.Zero) + { + if (_usesSharedMemory) + { + MemoryManagement.UnmapSharedMemory(ptr, Size); + } + else + { + MemoryManagement.Free(ptr, Size); + } + } + + if (!_isMirror) + { + IntPtr sharedMemory = Interlocked.Exchange(ref _sharedMemory, IntPtr.Zero); + + if (sharedMemory != IntPtr.Zero) + { + MemoryManagement.DestroySharedMemory(sharedMemory); + } + } + } + + /// + /// Checks if the specified memory allocation flags are supported on the current platform. + /// + /// Flags to be checked + /// True if the platform supports all the flags, false otherwise + public static bool SupportsFlags(MemoryAllocationFlags flags) + { + if (flags.HasFlag(MemoryAllocationFlags.ViewCompatible)) + { + if (OperatingSystem.IsWindows()) + { + return OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134); + } + + return OperatingSystem.IsLinux() || OperatingSystem.IsMacOS(); + } + + return true; + } + + public static ulong GetPageSize() + { + return (ulong)Environment.SystemPageSize; + } + + private static void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException(); + } +} diff --git a/src/Ryujinx.Memory/MemoryConstants.cs b/src/Ryujinx.Memory/MemoryConstants.cs new file mode 100644 index 00000000..6cf9403b --- /dev/null +++ b/src/Ryujinx.Memory/MemoryConstants.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Memory +{ + static class MemoryConstants + { + public const int PageBits = 12; + public const int PageSize = 1 << PageBits; + public const int PageMask = PageSize - 1; + } +} diff --git a/src/Ryujinx.Memory/MemoryManagement.cs b/src/Ryujinx.Memory/MemoryManagement.cs new file mode 100644 index 00000000..860d3f36 --- /dev/null +++ b/src/Ryujinx.Memory/MemoryManagement.cs @@ -0,0 +1,206 @@ +using System; + +namespace Ryujinx.Memory +{ + public static class MemoryManagement + { + public static IntPtr Allocate(ulong size, bool forJit) + { + if (OperatingSystem.IsWindows()) + { + return MemoryManagementWindows.Allocate((IntPtr)size); + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + return MemoryManagementUnix.Allocate(size, forJit); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static IntPtr Reserve(ulong size, bool forJit, bool viewCompatible) + { + if (OperatingSystem.IsWindows()) + { + return MemoryManagementWindows.Reserve((IntPtr)size, viewCompatible); + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + return MemoryManagementUnix.Reserve(size, forJit); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static void Commit(IntPtr address, ulong size, bool forJit) + { + if (OperatingSystem.IsWindows()) + { + MemoryManagementWindows.Commit(address, (IntPtr)size); + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + MemoryManagementUnix.Commit(address, size, forJit); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static void Decommit(IntPtr address, ulong size) + { + if (OperatingSystem.IsWindows()) + { + MemoryManagementWindows.Decommit(address, (IntPtr)size); + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + MemoryManagementUnix.Decommit(address, size); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr address, ulong size, MemoryBlock owner) + { + if (OperatingSystem.IsWindows()) + { + MemoryManagementWindows.MapView(sharedMemory, srcOffset, address, (IntPtr)size, owner); + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + MemoryManagementUnix.MapView(sharedMemory, srcOffset, address, size); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static void UnmapView(IntPtr sharedMemory, IntPtr address, ulong size, MemoryBlock owner) + { + if (OperatingSystem.IsWindows()) + { + MemoryManagementWindows.UnmapView(sharedMemory, address, (IntPtr)size, owner); + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + MemoryManagementUnix.UnmapView(address, size); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static void Reprotect(IntPtr address, ulong size, MemoryPermission permission, bool forView, bool throwOnFail) + { + bool result; + + if (OperatingSystem.IsWindows()) + { + result = MemoryManagementWindows.Reprotect(address, (IntPtr)size, permission, forView); + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + result = MemoryManagementUnix.Reprotect(address, size, permission); + } + else + { + throw new PlatformNotSupportedException(); + } + + if (!result && throwOnFail) + { + throw new MemoryProtectionException(permission); + } + } + + public static bool Free(IntPtr address, ulong size) + { + if (OperatingSystem.IsWindows()) + { + return MemoryManagementWindows.Free(address, (IntPtr)size); + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + return MemoryManagementUnix.Free(address); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static IntPtr CreateSharedMemory(ulong size, bool reserve) + { + if (OperatingSystem.IsWindows()) + { + return MemoryManagementWindows.CreateSharedMemory((IntPtr)size, reserve); + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + return MemoryManagementUnix.CreateSharedMemory(size, reserve); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static void DestroySharedMemory(IntPtr handle) + { + if (OperatingSystem.IsWindows()) + { + MemoryManagementWindows.DestroySharedMemory(handle); + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + MemoryManagementUnix.DestroySharedMemory(handle); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static IntPtr MapSharedMemory(IntPtr handle, ulong size) + { + if (OperatingSystem.IsWindows()) + { + return MemoryManagementWindows.MapSharedMemory(handle); + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + return MemoryManagementUnix.MapSharedMemory(handle, size); + } + else + { + throw new PlatformNotSupportedException(); + } + } + + public static void UnmapSharedMemory(IntPtr address, ulong size) + { + if (OperatingSystem.IsWindows()) + { + MemoryManagementWindows.UnmapSharedMemory(address); + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + MemoryManagementUnix.UnmapSharedMemory(address, size); + } + else + { + throw new PlatformNotSupportedException(); + } + } + } +} diff --git a/src/Ryujinx.Memory/MemoryManagementUnix.cs b/src/Ryujinx.Memory/MemoryManagementUnix.cs new file mode 100644 index 00000000..e132dbbb --- /dev/null +++ b/src/Ryujinx.Memory/MemoryManagementUnix.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Concurrent; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using static Ryujinx.Memory.MemoryManagerUnixHelper; + +namespace Ryujinx.Memory +{ + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + static class MemoryManagementUnix + { + private static readonly ConcurrentDictionary _allocations = new(); + + public static IntPtr Allocate(ulong size, bool forJit) + { + return AllocateInternal(size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, forJit); + } + + public static IntPtr Reserve(ulong size, bool forJit) + { + return AllocateInternal(size, MmapProts.PROT_NONE, forJit); + } + + private static IntPtr AllocateInternal(ulong size, MmapProts prot, bool forJit, bool shared = false) + { + MmapFlags flags = MmapFlags.MAP_ANONYMOUS; + + if (shared) + { + flags |= MmapFlags.MAP_SHARED | MmapFlags.MAP_UNLOCKED; + } + else + { + flags |= MmapFlags.MAP_PRIVATE; + } + + if (prot == MmapProts.PROT_NONE) + { + flags |= MmapFlags.MAP_NORESERVE; + } + + if (OperatingSystem.IsMacOSVersionAtLeast(10, 14) && forJit) + { + flags |= MmapFlags.MAP_JIT_DARWIN; + + if (prot == (MmapProts.PROT_READ | MmapProts.PROT_WRITE)) + { + prot |= MmapProts.PROT_EXEC; + } + } + + IntPtr ptr = Mmap(IntPtr.Zero, size, prot, flags, -1, 0); + + if (ptr == MAP_FAILED) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + + if (!_allocations.TryAdd(ptr, size)) + { + // This should be impossible, kernel shouldn't return an already mapped address. + throw new InvalidOperationException(); + } + + return ptr; + } + + public static void Commit(IntPtr address, ulong size, bool forJit) + { + MmapProts prot = MmapProts.PROT_READ | MmapProts.PROT_WRITE; + + if (OperatingSystem.IsMacOSVersionAtLeast(10, 14) && forJit) + { + prot |= MmapProts.PROT_EXEC; + } + + if (mprotect(address, size, prot) != 0) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + } + + public static void Decommit(IntPtr address, ulong size) + { + // Must be writable for madvise to work properly. + if (mprotect(address, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE) != 0) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + + if (madvise(address, size, MADV_REMOVE) != 0) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + + if (mprotect(address, size, MmapProts.PROT_NONE) != 0) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + } + + public static bool Reprotect(IntPtr address, ulong size, MemoryPermission permission) + { + return mprotect(address, size, GetProtection(permission)) == 0; + } + + private static MmapProts GetProtection(MemoryPermission permission) + { + return permission switch + { + MemoryPermission.None => MmapProts.PROT_NONE, + MemoryPermission.Read => MmapProts.PROT_READ, + MemoryPermission.ReadAndWrite => MmapProts.PROT_READ | MmapProts.PROT_WRITE, + MemoryPermission.ReadAndExecute => MmapProts.PROT_READ | MmapProts.PROT_EXEC, + MemoryPermission.ReadWriteExecute => MmapProts.PROT_READ | MmapProts.PROT_WRITE | MmapProts.PROT_EXEC, + MemoryPermission.Execute => MmapProts.PROT_EXEC, + _ => throw new MemoryProtectionException(permission), + }; + } + + public static bool Free(IntPtr address) + { + if (_allocations.TryRemove(address, out ulong size)) + { + return munmap(address, size) == 0; + } + + return false; + } + + public static bool Unmap(IntPtr address, ulong size) + { + return munmap(address, size) == 0; + } + + public unsafe static IntPtr CreateSharedMemory(ulong size, bool reserve) + { + int fd; + + if (OperatingSystem.IsMacOS()) + { + byte[] memName = "Ryujinx-XXXXXX"u8.ToArray(); + + fixed (byte* pMemName = memName) + { + fd = shm_open((IntPtr)pMemName, 0x2 | 0x200 | 0x800 | 0x400, 384); // O_RDWR | O_CREAT | O_EXCL | O_TRUNC, 0600 + if (fd == -1) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + + if (shm_unlink((IntPtr)pMemName) != 0) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + } + } + else + { + byte[] fileName = "/dev/shm/Ryujinx-XXXXXX"u8.ToArray(); + + fixed (byte* pFileName = fileName) + { + fd = mkstemp((IntPtr)pFileName); + if (fd == -1) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + + if (unlink((IntPtr)pFileName) != 0) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + } + } + + if (ftruncate(fd, (IntPtr)size) != 0) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + + return fd; + } + + public static void DestroySharedMemory(IntPtr handle) + { + close(handle.ToInt32()); + } + + public static IntPtr MapSharedMemory(IntPtr handle, ulong size) + { + return Mmap(IntPtr.Zero, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, MmapFlags.MAP_SHARED, handle.ToInt32(), 0); + } + + public static void UnmapSharedMemory(IntPtr address, ulong size) + { + munmap(address, size); + } + + public static void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, ulong size) + { + Mmap(location, size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, MmapFlags.MAP_FIXED | MmapFlags.MAP_SHARED, sharedMemory.ToInt32(), (long)srcOffset); + } + + public static void UnmapView(IntPtr location, ulong size) + { + Mmap(location, size, MmapProts.PROT_NONE, MmapFlags.MAP_FIXED | MmapFlags.MAP_PRIVATE | MmapFlags.MAP_ANONYMOUS | MmapFlags.MAP_NORESERVE, -1, 0); + } + } +} diff --git a/src/Ryujinx.Memory/MemoryManagementWindows.cs b/src/Ryujinx.Memory/MemoryManagementWindows.cs new file mode 100644 index 00000000..742ef6c9 --- /dev/null +++ b/src/Ryujinx.Memory/MemoryManagementWindows.cs @@ -0,0 +1,151 @@ +using Ryujinx.Memory.WindowsShared; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Memory +{ + [SupportedOSPlatform("windows")] + static class MemoryManagementWindows + { + public const int PageSize = 0x1000; + + private static readonly PlaceholderManager _placeholders = new(); + + public static IntPtr Allocate(IntPtr size) + { + return AllocateInternal(size, AllocationType.Reserve | AllocationType.Commit); + } + + public static IntPtr Reserve(IntPtr size, bool viewCompatible) + { + if (viewCompatible) + { + IntPtr baseAddress = AllocateInternal2(size, AllocationType.Reserve | AllocationType.ReservePlaceholder); + + _placeholders.ReserveRange((ulong)baseAddress, (ulong)size); + + return baseAddress; + } + + return AllocateInternal(size, AllocationType.Reserve); + } + + private static IntPtr AllocateInternal(IntPtr size, AllocationType flags = 0) + { + IntPtr ptr = WindowsApi.VirtualAlloc(IntPtr.Zero, size, flags, MemoryProtection.ReadWrite); + + if (ptr == IntPtr.Zero) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + + return ptr; + } + + private static IntPtr AllocateInternal2(IntPtr size, AllocationType flags = 0) + { + IntPtr ptr = WindowsApi.VirtualAlloc2(WindowsApi.CurrentProcessHandle, IntPtr.Zero, size, flags, MemoryProtection.NoAccess, IntPtr.Zero, 0); + + if (ptr == IntPtr.Zero) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + + return ptr; + } + + public static void Commit(IntPtr location, IntPtr size) + { + if (WindowsApi.VirtualAlloc(location, size, AllocationType.Commit, MemoryProtection.ReadWrite) == IntPtr.Zero) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + } + + public static void Decommit(IntPtr location, IntPtr size) + { + if (!WindowsApi.VirtualFree(location, size, AllocationType.Decommit)) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + } + + public static void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size, MemoryBlock owner) + { + _placeholders.MapView(sharedMemory, srcOffset, location, size, owner); + } + + public static void UnmapView(IntPtr sharedMemory, IntPtr location, IntPtr size, MemoryBlock owner) + { + _placeholders.UnmapView(sharedMemory, location, size, owner); + } + + public static bool Reprotect(IntPtr address, IntPtr size, MemoryPermission permission, bool forView) + { + if (forView) + { + return _placeholders.ReprotectView(address, size, permission); + } + else + { + return WindowsApi.VirtualProtect(address, size, WindowsApi.GetProtection(permission), out _); + } + } + + public static bool Free(IntPtr address, IntPtr size) + { + _placeholders.UnreserveRange((ulong)address, (ulong)size); + + return WindowsApi.VirtualFree(address, IntPtr.Zero, AllocationType.Release); + } + + public static IntPtr CreateSharedMemory(IntPtr size, bool reserve) + { + var prot = reserve ? FileMapProtection.SectionReserve : FileMapProtection.SectionCommit; + + IntPtr handle = WindowsApi.CreateFileMapping( + WindowsApi.InvalidHandleValue, + IntPtr.Zero, + FileMapProtection.PageReadWrite | prot, + (uint)(size.ToInt64() >> 32), + (uint)size.ToInt64(), + null); + + if (handle == IntPtr.Zero) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + + return handle; + } + + public static void DestroySharedMemory(IntPtr handle) + { + if (!WindowsApi.CloseHandle(handle)) + { + throw new ArgumentException("Invalid handle.", nameof(handle)); + } + } + + public static IntPtr MapSharedMemory(IntPtr handle) + { + IntPtr ptr = WindowsApi.MapViewOfFile(handle, 4 | 2, 0, 0, IntPtr.Zero); + + if (ptr == IntPtr.Zero) + { + throw new SystemException(Marshal.GetLastPInvokeErrorMessage()); + } + + return ptr; + } + + public static void UnmapSharedMemory(IntPtr address) + { + if (!WindowsApi.UnmapViewOfFile(address)) + { + throw new ArgumentException("Invalid address.", nameof(address)); + } + } + } +} diff --git a/src/Ryujinx.Memory/MemoryManagerUnixHelper.cs b/src/Ryujinx.Memory/MemoryManagerUnixHelper.cs new file mode 100644 index 00000000..43888c85 --- /dev/null +++ b/src/Ryujinx.Memory/MemoryManagerUnixHelper.cs @@ -0,0 +1,172 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Memory +{ + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + public static partial class MemoryManagerUnixHelper + { + [Flags] + public enum MmapProts : uint + { + PROT_NONE = 0, + PROT_READ = 1, + PROT_WRITE = 2, + PROT_EXEC = 4, + } + + [Flags] + public enum MmapFlags : uint + { + MAP_SHARED = 1, + MAP_PRIVATE = 2, + MAP_ANONYMOUS = 4, + MAP_NORESERVE = 8, + MAP_FIXED = 16, + MAP_UNLOCKED = 32, + MAP_JIT_DARWIN = 0x800, + } + + [Flags] + public enum OpenFlags : uint + { + O_RDONLY = 0, + O_WRONLY = 1, + O_RDWR = 2, + O_CREAT = 4, + O_EXCL = 8, + O_NOCTTY = 16, + O_TRUNC = 32, + O_APPEND = 64, + O_NONBLOCK = 128, + O_SYNC = 256, + } + + public const IntPtr MAP_FAILED = -1; + + private const int MAP_ANONYMOUS_LINUX_GENERIC = 0x20; + private const int MAP_NORESERVE_LINUX_GENERIC = 0x4000; + private const int MAP_UNLOCKED_LINUX_GENERIC = 0x80000; + + private const int MAP_NORESERVE_DARWIN = 0x40; + private const int MAP_ANONYMOUS_DARWIN = 0x1000; + + public const int MADV_DONTNEED = 4; + public const int MADV_REMOVE = 9; + + [LibraryImport("libc", EntryPoint = "mmap", SetLastError = true)] + private static partial IntPtr Internal_mmap(IntPtr address, ulong length, MmapProts prot, int flags, int fd, long offset); + + [LibraryImport("libc", SetLastError = true)] + public static partial int mprotect(IntPtr address, ulong length, MmapProts prot); + + [LibraryImport("libc", SetLastError = true)] + public static partial int munmap(IntPtr address, ulong length); + + [LibraryImport("libc", SetLastError = true)] + public static partial IntPtr mremap(IntPtr old_address, ulong old_size, ulong new_size, int flags, IntPtr new_address); + + [LibraryImport("libc", SetLastError = true)] + public static partial int madvise(IntPtr address, ulong size, int advice); + + [LibraryImport("libc", SetLastError = true)] + public static partial int mkstemp(IntPtr template); + + [LibraryImport("libc", SetLastError = true)] + public static partial int unlink(IntPtr pathname); + + [LibraryImport("libc", SetLastError = true)] + public static partial int ftruncate(int fildes, IntPtr length); + + [LibraryImport("libc", SetLastError = true)] + public static partial int close(int fd); + + [LibraryImport("libc", SetLastError = true)] + public static partial int shm_open(IntPtr name, int oflag, uint mode); + + [LibraryImport("libc", SetLastError = true)] + public static partial int shm_unlink(IntPtr name); + + private static int MmapFlagsToSystemFlags(MmapFlags flags) + { + int result = 0; + + if (flags.HasFlag(MmapFlags.MAP_SHARED)) + { + result |= (int)MmapFlags.MAP_SHARED; + } + + if (flags.HasFlag(MmapFlags.MAP_PRIVATE)) + { + result |= (int)MmapFlags.MAP_PRIVATE; + } + + if (flags.HasFlag(MmapFlags.MAP_FIXED)) + { + result |= (int)MmapFlags.MAP_FIXED; + } + + if (flags.HasFlag(MmapFlags.MAP_ANONYMOUS)) + { + if (OperatingSystem.IsLinux()) + { + result |= MAP_ANONYMOUS_LINUX_GENERIC; + } + else if (OperatingSystem.IsMacOS()) + { + result |= MAP_ANONYMOUS_DARWIN; + } + else + { + throw new NotImplementedException(); + } + } + + if (flags.HasFlag(MmapFlags.MAP_NORESERVE)) + { + if (OperatingSystem.IsLinux()) + { + result |= MAP_NORESERVE_LINUX_GENERIC; + } + else if (OperatingSystem.IsMacOS()) + { + result |= MAP_NORESERVE_DARWIN; + } + else + { + throw new NotImplementedException(); + } + } + + if (flags.HasFlag(MmapFlags.MAP_UNLOCKED)) + { + if (OperatingSystem.IsLinux()) + { + result |= MAP_UNLOCKED_LINUX_GENERIC; + } + else if (OperatingSystem.IsMacOS()) + { + // FIXME: Doesn't exist on Darwin + } + else + { + throw new NotImplementedException(); + } + } + + if (flags.HasFlag(MmapFlags.MAP_JIT_DARWIN) && OperatingSystem.IsMacOSVersionAtLeast(10, 14)) + { + result |= (int)MmapFlags.MAP_JIT_DARWIN; + } + + return result; + } + + public static IntPtr Mmap(IntPtr address, ulong length, MmapProts prot, MmapFlags flags, int fd, long offset) + { + return Internal_mmap(address, length, prot, MmapFlagsToSystemFlags(flags), fd, offset); + } + } +} diff --git a/src/Ryujinx.Memory/MemoryMapFlags.cs b/src/Ryujinx.Memory/MemoryMapFlags.cs new file mode 100644 index 00000000..5c023cc1 --- /dev/null +++ b/src/Ryujinx.Memory/MemoryMapFlags.cs @@ -0,0 +1,23 @@ +using System; + +namespace Ryujinx.Memory +{ + /// + /// Flags that indicate how the host memory should be mapped. + /// + [Flags] + public enum MemoryMapFlags + { + /// + /// No mapping flags. + /// + None = 0, + + /// + /// Indicates that the implementation is free to ignore the specified backing memory offset + /// and allocate its own private storage for the mapping. + /// This allows some mappings that would otherwise fail due to host platform restrictions to succeed. + /// + Private = 1 << 0, + } +} diff --git a/src/Ryujinx.Memory/MemoryNotContiguousException.cs b/src/Ryujinx.Memory/MemoryNotContiguousException.cs new file mode 100644 index 00000000..b392ead8 --- /dev/null +++ b/src/Ryujinx.Memory/MemoryNotContiguousException.cs @@ -0,0 +1,19 @@ +using System; + +namespace Ryujinx.Memory +{ + public class MemoryNotContiguousException : Exception + { + public MemoryNotContiguousException() : base("The specified memory region is not contiguous.") + { + } + + public MemoryNotContiguousException(string message) : base(message) + { + } + + public MemoryNotContiguousException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/Ryujinx.Memory/MemoryPermission.cs b/src/Ryujinx.Memory/MemoryPermission.cs new file mode 100644 index 00000000..fae9428a --- /dev/null +++ b/src/Ryujinx.Memory/MemoryPermission.cs @@ -0,0 +1,51 @@ +using System; + +namespace Ryujinx.Memory +{ + /// + /// Memory access permission control. + /// + [Flags] + public enum MemoryPermission + { + /// + /// No access is allowed on the memory region. + /// + None = 0, + + /// + /// Allow reads on the memory region. + /// + Read = 1 << 0, + + /// + /// Allow writes on the memory region. + /// + Write = 1 << 1, + + /// + /// Allow code execution on the memory region. + /// + Execute = 1 << 2, + + /// + /// Allow reads and writes on the memory region. + /// + ReadAndWrite = Read | Write, + + /// + /// Allow reads and code execution on the memory region. + /// + ReadAndExecute = Read | Execute, + + /// + /// Allow reads, writes, and code execution on the memory region. + /// + ReadWriteExecute = Read | Write | Execute, + + /// + /// Indicates an invalid protection. + /// + Invalid = 255, + } +} diff --git a/src/Ryujinx.Memory/MemoryProtectionException.cs b/src/Ryujinx.Memory/MemoryProtectionException.cs new file mode 100644 index 00000000..304ce8ed --- /dev/null +++ b/src/Ryujinx.Memory/MemoryProtectionException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Memory +{ + class MemoryProtectionException : Exception + { + public MemoryProtectionException() + { + } + + public MemoryProtectionException(MemoryPermission permission) : base($"Failed to set memory protection to \"{permission}\": {Marshal.GetLastPInvokeErrorMessage()}") + { + } + + public MemoryProtectionException(string message) : base(message) + { + } + + public MemoryProtectionException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/Ryujinx.Memory/NativeMemoryManager.cs b/src/Ryujinx.Memory/NativeMemoryManager.cs new file mode 100644 index 00000000..cb8d5c24 --- /dev/null +++ b/src/Ryujinx.Memory/NativeMemoryManager.cs @@ -0,0 +1,51 @@ +using System; +using System.Buffers; + +namespace Ryujinx.Memory +{ + public unsafe class NativeMemoryManager : MemoryManager where T : unmanaged + { + private readonly T* _pointer; + private readonly int _length; + + public NativeMemoryManager(nuint pointer, int length) + : this((T*)pointer, length) + { + } + + public NativeMemoryManager(T* pointer, int length) + { + _pointer = pointer; + _length = length; + } + + public unsafe T* Pointer => _pointer; + + public int Length => _length; + + public override Span GetSpan() + { + return new Span((void*)_pointer, _length); + } + + public override MemoryHandle Pin(int elementIndex = 0) + { + if ((uint)elementIndex >= _length) + { + throw new ArgumentOutOfRangeException(nameof(elementIndex)); + } + + return new MemoryHandle((void*)(_pointer + elementIndex)); + } + + public override void Unpin() + { + // No need to do anything as pointer already points no native memory, not GC tracked. + } + + protected override void Dispose(bool disposing) + { + // Nothing to dispose, MemoryBlock still owns the memory. + } + } +} diff --git a/src/Ryujinx.Memory/PageTable.cs b/src/Ryujinx.Memory/PageTable.cs new file mode 100644 index 00000000..ff22d028 --- /dev/null +++ b/src/Ryujinx.Memory/PageTable.cs @@ -0,0 +1,141 @@ +namespace Ryujinx.Memory +{ + public class PageTable where T : unmanaged + { + public const int PageBits = 12; + public const int PageSize = 1 << PageBits; + public const int PageMask = PageSize - 1; + + private const int PtLevelBits = 9; // 9 * 4 + 12 = 48 (max address space size) + private const int PtLevelSize = 1 << PtLevelBits; + private const int PtLevelMask = PtLevelSize - 1; + + private readonly T[][][][] _pageTable; + + public PageTable() + { + _pageTable = new T[PtLevelSize][][][]; + } + + public T Read(ulong va) + { + int l3 = (int)(va >> PageBits) & PtLevelMask; + int l2 = (int)(va >> (PageBits + PtLevelBits)) & PtLevelMask; + int l1 = (int)(va >> (PageBits + PtLevelBits * 2)) & PtLevelMask; + int l0 = (int)(va >> (PageBits + PtLevelBits * 3)) & PtLevelMask; + + if (_pageTable[l0] == null) + { + return default; + } + + if (_pageTable[l0][l1] == null) + { + return default; + } + + if (_pageTable[l0][l1][l2] == null) + { + return default; + } + + return _pageTable[l0][l1][l2][l3]; + } + + public void Map(ulong va, T value) + { + int l3 = (int)(va >> PageBits) & PtLevelMask; + int l2 = (int)(va >> (PageBits + PtLevelBits)) & PtLevelMask; + int l1 = (int)(va >> (PageBits + PtLevelBits * 2)) & PtLevelMask; + int l0 = (int)(va >> (PageBits + PtLevelBits * 3)) & PtLevelMask; + + if (_pageTable[l0] == null) + { + _pageTable[l0] = new T[PtLevelSize][][]; + } + + if (_pageTable[l0][l1] == null) + { + _pageTable[l0][l1] = new T[PtLevelSize][]; + } + + if (_pageTable[l0][l1][l2] == null) + { + _pageTable[l0][l1][l2] = new T[PtLevelSize]; + } + + _pageTable[l0][l1][l2][l3] = value; + } + + public void Unmap(ulong va) + { + int l3 = (int)(va >> PageBits) & PtLevelMask; + int l2 = (int)(va >> (PageBits + PtLevelBits)) & PtLevelMask; + int l1 = (int)(va >> (PageBits + PtLevelBits * 2)) & PtLevelMask; + int l0 = (int)(va >> (PageBits + PtLevelBits * 3)) & PtLevelMask; + + if (_pageTable[l0] == null) + { + return; + } + + if (_pageTable[l0][l1] == null) + { + return; + } + + if (_pageTable[l0][l1][l2] == null) + { + return; + } + + _pageTable[l0][l1][l2][l3] = default; + + bool empty = true; + + for (int i = 0; i < _pageTable[l0][l1][l2].Length; i++) + { + empty &= _pageTable[l0][l1][l2][i].Equals(default); + } + + if (empty) + { + _pageTable[l0][l1][l2] = null; + + RemoveIfAllNull(l0, l1); + } + } + + private void RemoveIfAllNull(int l0, int l1) + { + bool empty = true; + + for (int i = 0; i < _pageTable[l0][l1].Length; i++) + { + empty &= (_pageTable[l0][l1][i] == null); + } + + if (empty) + { + _pageTable[l0][l1] = null; + + RemoveIfAllNull(l0); + } + } + + private void RemoveIfAllNull(int l0) + { + bool empty = true; + + for (int i = 0; i < _pageTable[l0].Length; i++) + { + empty &= (_pageTable[l0][i] == null); + } + + if (empty) + { + _pageTable[l0] = null; + } + } + } +} diff --git a/src/Ryujinx.Memory/Range/HostMemoryRange.cs b/src/Ryujinx.Memory/Range/HostMemoryRange.cs new file mode 100644 index 00000000..a4abebb5 --- /dev/null +++ b/src/Ryujinx.Memory/Range/HostMemoryRange.cs @@ -0,0 +1,81 @@ +using System; + +namespace Ryujinx.Memory.Range +{ + /// + /// Range of memory composed of an address and size. + /// + public readonly struct HostMemoryRange : IEquatable + { + /// + /// An empty memory range, with a null address and zero size. + /// + public static HostMemoryRange Empty => new(0, 0); + + /// + /// Start address of the range. + /// + public nuint Address { get; } + + /// + /// Size of the range in bytes. + /// + public ulong Size { get; } + + /// + /// Address where the range ends (exclusive). + /// + public nuint EndAddress => Address + (nuint)Size; + + /// + /// Creates a new memory range with the specified address and size. + /// + /// Start address + /// Size in bytes + public HostMemoryRange(nuint address, ulong size) + { + Address = address; + Size = size; + } + + /// + /// Checks if the range overlaps with another. + /// + /// The other range to check for overlap + /// True if the ranges overlap, false otherwise + public bool OverlapsWith(HostMemoryRange other) + { + nuint thisAddress = Address; + nuint thisEndAddress = EndAddress; + nuint otherAddress = other.Address; + nuint otherEndAddress = other.EndAddress; + + return thisAddress < otherEndAddress && otherAddress < thisEndAddress; + } + + public override bool Equals(object obj) + { + return obj is HostMemoryRange other && Equals(other); + } + + public bool Equals(HostMemoryRange other) + { + return Address == other.Address && Size == other.Size; + } + + public override int GetHashCode() + { + return HashCode.Combine(Address, Size); + } + + public static bool operator ==(HostMemoryRange left, HostMemoryRange right) + { + return left.Equals(right); + } + + public static bool operator !=(HostMemoryRange left, HostMemoryRange right) + { + return !(left == right); + } + } +} diff --git a/src/Ryujinx.Memory/Range/IMultiRangeItem.cs b/src/Ryujinx.Memory/Range/IMultiRangeItem.cs new file mode 100644 index 00000000..5f9611c7 --- /dev/null +++ b/src/Ryujinx.Memory/Range/IMultiRangeItem.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.Memory.Range +{ + public interface IMultiRangeItem + { + MultiRange Range { get; } + + ulong BaseAddress + { + get + { + for (int index = 0; index < Range.Count; index++) + { + MemoryRange subRange = Range.GetSubRange(index); + + if (!MemoryRange.IsInvalid(ref subRange)) + { + return subRange.Address; + } + } + + return MemoryRange.InvalidAddress; + } + } + } +} diff --git a/src/Ryujinx.Memory/Range/INonOverlappingRange.cs b/src/Ryujinx.Memory/Range/INonOverlappingRange.cs new file mode 100644 index 00000000..23194e0f --- /dev/null +++ b/src/Ryujinx.Memory/Range/INonOverlappingRange.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Memory.Range +{ + /// + /// Range of memory that can be split in two. + /// + interface INonOverlappingRange : IRange + { + /// + /// Split this region into two, around the specified address. + /// This region is updated to end at the split address, and a new region is created to represent past that point. + /// + /// Address to split the region around + /// The second part of the split region, with start address at the given split. + public INonOverlappingRange Split(ulong splitAddress); + } +} diff --git a/src/Ryujinx.Memory/Range/IRange.cs b/src/Ryujinx.Memory/Range/IRange.cs new file mode 100644 index 00000000..c85e21d1 --- /dev/null +++ b/src/Ryujinx.Memory/Range/IRange.cs @@ -0,0 +1,31 @@ +namespace Ryujinx.Memory.Range +{ + /// + /// Range of memory. + /// + public interface IRange + { + /// + /// Base address. + /// + ulong Address { get; } + + /// + /// Size of the range. + /// + ulong Size { get; } + + /// + /// End address. + /// + ulong EndAddress { get; } + + /// + /// Check if this range overlaps with another. + /// + /// Base address + /// Size of the range + /// True if overlapping, false otherwise + bool OverlapsWith(ulong address, ulong size); + } +} diff --git a/src/Ryujinx.Memory/Range/MemoryRange.cs b/src/Ryujinx.Memory/Range/MemoryRange.cs new file mode 100644 index 00000000..20e9d00b --- /dev/null +++ b/src/Ryujinx.Memory/Range/MemoryRange.cs @@ -0,0 +1,91 @@ +namespace Ryujinx.Memory.Range +{ + /// + /// Range of memory composed of an address and size. + /// + public readonly record struct MemoryRange + { + /// + /// Special address value used to indicate than an address is invalid. + /// + internal const ulong InvalidAddress = ulong.MaxValue; + + /// + /// An empty memory range, with a null address and zero size. + /// + public static MemoryRange Empty => new(0UL, 0); + + /// + /// Start address of the range. + /// + public ulong Address { get; } + + /// + /// Size of the range in bytes. + /// + public ulong Size { get; } + + /// + /// Address where the range ends (exclusive). + /// + public ulong EndAddress => Address + Size; + + /// + /// Creates a new memory range with the specified address and size. + /// + /// Start address + /// Size in bytes + public MemoryRange(ulong address, ulong size) + { + Address = address; + Size = size; + } + + /// + /// Checks if the range overlaps with another. + /// + /// The other range to check for overlap + /// True if the ranges overlap, false otherwise + public bool OverlapsWith(MemoryRange other) + { + ulong thisAddress = Address; + ulong thisEndAddress = EndAddress; + ulong otherAddress = other.Address; + ulong otherEndAddress = other.EndAddress; + + // If any of the ranges if invalid (address + size overflows), + // then they are never considered to overlap. + if (thisEndAddress < thisAddress || otherEndAddress < otherAddress) + { + return false; + } + + return thisAddress < otherEndAddress && otherAddress < thisEndAddress; + } + + /// + /// Checks if a given sub-range of memory is invalid. + /// Those are used to represent unmapped memory regions (holes in the region mapping). + /// + /// Memory range to check + /// True if the memory range is considered invalid, false otherwise + internal static bool IsInvalid(ref MemoryRange subRange) + { + return subRange.Address == InvalidAddress; + } + + /// + /// Returns a string summary of the memory range. + /// + /// A string summary of the memory range + public override string ToString() + { + if (Address == InvalidAddress) + { + return $"[Unmapped 0x{Size:X}]"; + } + + return $"[0x{Address:X}, 0x{EndAddress:X})"; + } + } +} diff --git a/src/Ryujinx.Memory/Range/MultiRange.cs b/src/Ryujinx.Memory/Range/MultiRange.cs new file mode 100644 index 00000000..093e2190 --- /dev/null +++ b/src/Ryujinx.Memory/Range/MultiRange.cs @@ -0,0 +1,351 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Memory.Range +{ + /// + /// Sequence of physical memory regions that a single non-contiguous virtual memory region maps to. + /// + public readonly struct MultiRange : IEquatable + { + private const ulong InvalidAddress = ulong.MaxValue; + + private readonly MemoryRange _singleRange; + private readonly MemoryRange[] _ranges; + + private bool HasSingleRange => _ranges == null; + + /// + /// Indicates that the range is fully unmapped. + /// + public bool IsUnmapped => HasSingleRange && _singleRange.Address == InvalidAddress; + + /// + /// Total of physical sub-ranges on the virtual memory region. + /// + public int Count => HasSingleRange ? 1 : _ranges.Length; + + /// + /// Creates a new multi-range with a single physical region. + /// + /// Start address of the region + /// Size of the region in bytes + public MultiRange(ulong address, ulong size) + { + _singleRange = new MemoryRange(address, size); + _ranges = null; + } + + /// + /// Creates a new multi-range with multiple physical regions. + /// + /// Array of physical regions + /// is null + public MultiRange(MemoryRange[] ranges) + { + ArgumentNullException.ThrowIfNull(ranges); + + if (ranges.Length == 1) + { + _singleRange = ranges[0]; + _ranges = null; + } + else + { + _singleRange = MemoryRange.Empty; + _ranges = ranges; + } + } + + /// + /// Gets a slice of the multi-range. + /// + /// Offset of the slice into the multi-range in bytes + /// Size of the slice in bytes + /// A new multi-range representing the given slice of this one + public MultiRange Slice(ulong offset, ulong size) + { + if (HasSingleRange) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan(size, _singleRange.Size - offset); + + return new MultiRange(_singleRange.Address + offset, size); + } + else + { + var ranges = new List(); + + foreach (MemoryRange range in _ranges) + { + if ((long)offset <= 0) + { + ranges.Add(new MemoryRange(range.Address, Math.Min(size, range.Size))); + size -= range.Size; + } + else if (offset < range.Size) + { + ulong sliceSize = Math.Min(size, range.Size - offset); + + if (range.Address == InvalidAddress) + { + ranges.Add(new MemoryRange(range.Address, sliceSize)); + } + else + { + ranges.Add(new MemoryRange(range.Address + offset, sliceSize)); + } + + size -= sliceSize; + } + + if ((long)size <= 0) + { + break; + } + + offset -= range.Size; + } + + return ranges.Count == 1 ? new MultiRange(ranges[0].Address, ranges[0].Size) : new MultiRange(ranges.ToArray()); + } + } + + /// + /// Gets the physical region at the specified index. + /// + /// Index of the physical region + /// Region at the index specified + /// is invalid + public MemoryRange GetSubRange(int index) + { + if (HasSingleRange) + { + ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0); + + return _singleRange; + } + else + { + if ((uint)index >= _ranges.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return _ranges[index]; + } + } + + /// + /// Gets the physical region at the specified index, without explicit bounds checking. + /// + /// Index of the physical region + /// Region at the index specified + private MemoryRange GetSubRangeUnchecked(int index) + { + return HasSingleRange ? _singleRange : _ranges[index]; + } + + /// + /// Check if two multi-ranges overlap with each other. + /// + /// Other multi-range to check for overlap + /// True if any sub-range overlaps, false otherwise + public bool OverlapsWith(MultiRange other) + { + if (HasSingleRange && other.HasSingleRange) + { + return _singleRange.OverlapsWith(other._singleRange); + } + else + { + for (int i = 0; i < Count; i++) + { + MemoryRange currentRange = GetSubRangeUnchecked(i); + + for (int j = 0; j < other.Count; j++) + { + if (currentRange.OverlapsWith(other.GetSubRangeUnchecked(j))) + { + return true; + } + } + } + } + + return false; + } + + /// + /// Checks if a given multi-range is fully contained inside another. + /// + /// Multi-range to be checked + /// True if all the sub-ranges on are contained inside the multi-range, with the same order, false otherwise + public bool Contains(MultiRange other) + { + return FindOffset(other) >= 0; + } + + /// + /// Calculates the offset of a given multi-range inside another, when the multi-range is fully contained + /// inside the other multi-range, otherwise returns -1. + /// + /// Multi-range that should be fully contained inside this one + /// Offset in bytes if fully contained, otherwise -1 + public int FindOffset(MultiRange other) + { + int thisCount = Count; + int otherCount = other.Count; + + if (thisCount == 1 && otherCount == 1) + { + MemoryRange otherFirstRange = other.GetSubRangeUnchecked(0); + MemoryRange currentFirstRange = GetSubRangeUnchecked(0); + + if (otherFirstRange.Address >= currentFirstRange.Address && + otherFirstRange.EndAddress <= currentFirstRange.EndAddress) + { + return (int)(otherFirstRange.Address - currentFirstRange.Address); + } + } + else if (thisCount >= otherCount) + { + ulong baseOffset = 0; + + MemoryRange otherFirstRange = other.GetSubRangeUnchecked(0); + MemoryRange otherLastRange = other.GetSubRangeUnchecked(otherCount - 1); + + for (int i = 0; i < (thisCount - otherCount) + 1; baseOffset += GetSubRangeUnchecked(i).Size, i++) + { + MemoryRange currentFirstRange = GetSubRangeUnchecked(i); + MemoryRange currentLastRange = GetSubRangeUnchecked(i + otherCount - 1); + + if (otherCount > 1) + { + if (otherFirstRange.Address < currentFirstRange.Address || + otherFirstRange.EndAddress != currentFirstRange.EndAddress) + { + continue; + } + + if (otherLastRange.Address != currentLastRange.Address || + otherLastRange.EndAddress > currentLastRange.EndAddress) + { + continue; + } + + bool fullMatch = true; + + for (int j = 1; j < otherCount - 1; j++) + { + if (!GetSubRangeUnchecked(i + j).Equals(other.GetSubRangeUnchecked(j))) + { + fullMatch = false; + break; + } + } + + if (!fullMatch) + { + continue; + } + } + else if (currentFirstRange.Address > otherFirstRange.Address || + currentFirstRange.EndAddress < otherFirstRange.EndAddress) + { + continue; + } + + return (int)(baseOffset + (otherFirstRange.Address - currentFirstRange.Address)); + } + } + + return -1; + } + + /// + /// Gets the total size of all sub-ranges in bytes. + /// + /// Total size in bytes + public ulong GetSize() + { + if (HasSingleRange) + { + return _singleRange.Size; + } + + ulong sum = 0; + + foreach (MemoryRange range in _ranges) + { + sum += range.Size; + } + + return sum; + } + + public override bool Equals(object obj) + { + return obj is MultiRange other && Equals(other); + } + + public bool Equals(MultiRange other) + { + if (HasSingleRange && other.HasSingleRange) + { + return _singleRange.Equals(other._singleRange); + } + + int thisCount = Count; + if (thisCount != other.Count) + { + return false; + } + + for (int i = 0; i < thisCount; i++) + { + if (!GetSubRangeUnchecked(i).Equals(other.GetSubRangeUnchecked(i))) + { + return false; + } + } + + return true; + } + + public override int GetHashCode() + { + if (HasSingleRange) + { + return _singleRange.GetHashCode(); + } + + HashCode hash = new(); + + foreach (MemoryRange range in _ranges) + { + hash.Add(range); + } + + return hash.ToHashCode(); + } + + /// + /// Returns a string summary of the ranges contained in the MultiRange. + /// + /// A string summary of the ranges contained within + public override string ToString() + { + return HasSingleRange ? _singleRange.ToString() : string.Join(", ", _ranges); + } + + public static bool operator ==(MultiRange left, MultiRange right) + { + return left.Equals(right); + } + + public static bool operator !=(MultiRange left, MultiRange right) + { + return !(left == right); + } + } +} diff --git a/src/Ryujinx.Memory/Range/MultiRangeList.cs b/src/Ryujinx.Memory/Range/MultiRangeList.cs new file mode 100644 index 00000000..c3c6ae79 --- /dev/null +++ b/src/Ryujinx.Memory/Range/MultiRangeList.cs @@ -0,0 +1,199 @@ +using Ryujinx.Common.Collections; +using System.Collections; +using System.Collections.Generic; + +namespace Ryujinx.Memory.Range +{ + public class MultiRangeList : IEnumerable where T : IMultiRangeItem + { + private readonly IntervalTree _items; + + public int Count { get; private set; } + + /// + /// Creates a new range list. + /// + public MultiRangeList() + { + _items = new IntervalTree(); + } + + /// + /// Adds a new item to the list. + /// + /// The item to be added + public void Add(T item) + { + MultiRange range = item.Range; + + for (int i = 0; i < range.Count; i++) + { + var subrange = range.GetSubRange(i); + + if (MemoryRange.IsInvalid(ref subrange)) + { + continue; + } + + _items.Add(subrange.Address, subrange.EndAddress, item); + } + + Count++; + } + + /// + /// Removes an item from the list. + /// + /// The item to be removed + /// True if the item was removed, or false if it was not found + public bool Remove(T item) + { + MultiRange range = item.Range; + + int removed = 0; + + for (int i = 0; i < range.Count; i++) + { + var subrange = range.GetSubRange(i); + + if (MemoryRange.IsInvalid(ref subrange)) + { + continue; + } + + removed += _items.Remove(subrange.Address, item); + } + + if (removed > 0) + { + // All deleted intervals are for the same item - the one we removed. + Count--; + } + + return removed > 0; + } + + /// + /// Gets all items on the list overlapping the specified memory range. + /// + /// Start address of the range + /// Size in bytes of the range + /// Output array where matches will be written. It is automatically resized to fit the results + /// The number of overlapping items found + public int FindOverlaps(ulong address, ulong size, ref T[] output) + { + return FindOverlaps(new MultiRange(address, size), ref output); + } + + /// + /// Gets all items on the list overlapping the specified memory ranges. + /// + /// Ranges of memory being searched + /// Output array where matches will be written. It is automatically resized to fit the results + /// The number of overlapping items found + public int FindOverlaps(MultiRange range, ref T[] output) + { + int overlapCount = 0; + + for (int i = 0; i < range.Count; i++) + { + var subrange = range.GetSubRange(i); + + if (MemoryRange.IsInvalid(ref subrange)) + { + continue; + } + + overlapCount = _items.Get(subrange.Address, subrange.EndAddress, ref output, overlapCount); + } + + // Remove any duplicates, caused by items having multiple sub range nodes in the tree. + if (overlapCount > 1) + { + int insertPtr = 0; + for (int i = 0; i < overlapCount; i++) + { + T item = output[i]; + bool duplicate = false; + + for (int j = insertPtr - 1; j >= 0; j--) + { + if (item.Equals(output[j])) + { + duplicate = true; + break; + } + } + + if (!duplicate) + { + if (insertPtr != i) + { + output[insertPtr] = item; + } + + insertPtr++; + } + } + + overlapCount = insertPtr; + } + + return overlapCount; + } + + /// + /// Gets all items on the list starting at the specified memory address. + /// + /// Base address to find + /// Output array where matches will be written. It is automatically resized to fit the results + /// The number of matches found + public int FindOverlaps(ulong baseAddress, ref T[] output) + { + int count = _items.Get(baseAddress, ref output); + + // Only output items with matching base address + int insertPtr = 0; + for (int i = 0; i < count; i++) + { + if (output[i].BaseAddress == baseAddress) + { + if (i != insertPtr) + { + output[insertPtr] = output[i]; + } + + insertPtr++; + } + } + + return insertPtr; + } + + private List GetList() + { + var items = _items.AsList(); + var result = new List(); + + foreach (RangeNode item in items) + { + if (item.Start == item.Value.BaseAddress) + { + result.Add(item.Value); + } + } + + return result; + } + + public IEnumerator GetEnumerator() + { + return GetList().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetList().GetEnumerator(); + } + } +} diff --git a/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs b/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs new file mode 100644 index 00000000..51158917 --- /dev/null +++ b/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Memory.Range +{ + /// + /// A range list that assumes ranges are non-overlapping, with list items that can be split in two to avoid overlaps. + /// + /// Type of the range. + class NonOverlappingRangeList : RangeList where T : INonOverlappingRange + { + /// + /// Finds a list of regions that cover the desired (address, size) range. + /// If this range starts or ends in the middle of an existing region, it is split and only the relevant part is added. + /// If there is no matching region, or there is a gap, then new regions are created with the factory. + /// Regions are added to the list in address ascending order. + /// + /// List to add found regions to + /// Start address of the search region + /// Size of the search region + /// Factory for creating new ranges + public void GetOrAddRegions(List list, ulong address, ulong size, Func factory) + { + // (regarding the specific case this generalized function is used for) + // A new region may be split into multiple parts if multiple virtual regions have mapped to it. + // For instance, while a virtual mapping could cover 0-2 in physical space, the space 0-1 may have already been reserved... + // So we need to return both the split 0-1 and 1-2 ranges. + + var results = new T[1]; + int count = FindOverlapsNonOverlapping(address, size, ref results); + + if (count == 0) + { + // The region is fully unmapped. Create and add it to the range list. + T region = factory(address, size); + list.Add(region); + Add(region); + } + else + { + ulong lastAddress = address; + ulong endAddress = address + size; + + for (int i = 0; i < count; i++) + { + T region = results[i]; + if (count == 1 && region.Address == address && region.Size == size) + { + // Exact match, no splitting required. + list.Add(region); + return; + } + + if (lastAddress < region.Address) + { + // There is a gap between this region and the last. We need to fill it. + T fillRegion = factory(lastAddress, region.Address - lastAddress); + list.Add(fillRegion); + Add(fillRegion); + } + + if (region.Address < address) + { + // Split the region around our base address and take the high half. + + region = Split(region, address); + } + + if (region.EndAddress > address + size) + { + // Split the region around our end address and take the low half. + + Split(region, address + size); + } + + list.Add(region); + lastAddress = region.EndAddress; + } + + if (lastAddress < endAddress) + { + // There is a gap between this region and the end. We need to fill it. + T fillRegion = factory(lastAddress, endAddress - lastAddress); + list.Add(fillRegion); + Add(fillRegion); + } + } + } + + /// + /// Splits a region around a target point and updates the region list. + /// The original region's size is modified, but its address stays the same. + /// A new region starting from the split address is added to the region list and returned. + /// + /// The region to split + /// The address to split with + /// The new region (high part) + private T Split(T region, ulong splitAddress) + { + T newRegion = (T)region.Split(splitAddress); + Update(region); + Add(newRegion); + return newRegion; + } + } +} diff --git a/src/Ryujinx.Memory/Range/RangeList.cs b/src/Ryujinx.Memory/Range/RangeList.cs new file mode 100644 index 00000000..72cef1de --- /dev/null +++ b/src/Ryujinx.Memory/Range/RangeList.cs @@ -0,0 +1,483 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Memory.Range +{ + /// + /// Sorted list of ranges that supports binary search. + /// + /// Type of the range. + public class RangeList : IEnumerable where T : IRange + { + private readonly struct RangeItem where TValue : IRange + { + public readonly ulong Address; + public readonly ulong EndAddress; + + public readonly TValue Value; + + public RangeItem(TValue value) + { + Value = value; + + Address = value.Address; + EndAddress = value.Address + value.Size; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool OverlapsWith(ulong address, ulong endAddress) + { + return Address < endAddress && address < EndAddress; + } + } + + private const int BackingInitialSize = 1024; + private const int ArrayGrowthSize = 32; + + private RangeItem[] _items; + private readonly int _backingGrowthSize; + + public int Count { get; protected set; } + + /// + /// Creates a new range list. + /// + /// The initial size of the backing array + public RangeList(int backingInitialSize = BackingInitialSize) + { + _backingGrowthSize = backingInitialSize; + _items = new RangeItem[backingInitialSize]; + } + + /// + /// Adds a new item to the list. + /// + /// The item to be added + public void Add(T item) + { + int index = BinarySearch(item.Address); + + if (index < 0) + { + index = ~index; + } + + Insert(index, new RangeItem(item)); + } + + /// + /// Updates an item's end address on the list. Address must be the same. + /// + /// The item to be updated + /// True if the item was located and updated, false otherwise + public bool Update(T item) + { + int index = BinarySearch(item.Address); + + if (index >= 0) + { + while (index > 0 && _items[index - 1].Address == item.Address) + { + index--; + } + + while (index < Count) + { + if (_items[index].Value.Equals(item)) + { + _items[index] = new RangeItem(item); + + return true; + } + + if (_items[index].Address > item.Address) + { + break; + } + + index++; + } + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Insert(int index, RangeItem item) + { + if (Count + 1 > _items.Length) + { + Array.Resize(ref _items, _items.Length + _backingGrowthSize); + } + + if (index >= Count) + { + if (index == Count) + { + _items[Count++] = item; + } + } + else + { + Array.Copy(_items, index, _items, index + 1, Count - index); + + _items[index] = item; + Count++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RemoveAt(int index) + { + if (index < --Count) + { + Array.Copy(_items, index + 1, _items, index, Count - index); + } + } + + /// + /// Removes an item from the list. + /// + /// The item to be removed + /// True if the item was removed, or false if it was not found + public bool Remove(T item) + { + int index = BinarySearch(item.Address); + + if (index >= 0) + { + while (index > 0 && _items[index - 1].Address == item.Address) + { + index--; + } + + while (index < Count) + { + if (_items[index].Value.Equals(item)) + { + RemoveAt(index); + + return true; + } + + if (_items[index].Address > item.Address) + { + break; + } + + index++; + } + } + + return false; + } + + /// + /// Updates an item's end address. + /// + /// The item to be updated + public void UpdateEndAddress(T item) + { + int index = BinarySearch(item.Address); + + if (index >= 0) + { + while (index > 0 && _items[index - 1].Address == item.Address) + { + index--; + } + + while (index < Count) + { + if (_items[index].Value.Equals(item)) + { + _items[index] = new RangeItem(item); + + return; + } + + if (_items[index].Address > item.Address) + { + break; + } + + index++; + } + } + } + + /// + /// Gets the first item on the list overlapping in memory with the specified item. + /// + /// + /// Despite the name, this has no ordering guarantees of the returned item. + /// It only ensures that the item returned overlaps the specified item. + /// + /// Item to check for overlaps + /// The overlapping item, or the default value for the type if none found + public T FindFirstOverlap(T item) + { + return FindFirstOverlap(item.Address, item.Size); + } + + /// + /// Gets the first item on the list overlapping the specified memory range. + /// + /// + /// Despite the name, this has no ordering guarantees of the returned item. + /// It only ensures that the item returned overlaps the specified memory range. + /// + /// Start address of the range + /// Size in bytes of the range + /// The overlapping item, or the default value for the type if none found + public T FindFirstOverlap(ulong address, ulong size) + { + int index = BinarySearch(address, address + size); + + if (index < 0) + { + return default; + } + + return _items[index].Value; + } + + /// + /// Gets all items overlapping with the specified item in memory. + /// + /// Item to check for overlaps + /// Output array where matches will be written. It is automatically resized to fit the results + /// The number of overlapping items found + public int FindOverlaps(T item, ref T[] output) + { + return FindOverlaps(item.Address, item.Size, ref output); + } + + /// + /// Gets all items on the list overlapping the specified memory range. + /// + /// Start address of the range + /// Size in bytes of the range + /// Output array where matches will be written. It is automatically resized to fit the results + /// The number of overlapping items found + public int FindOverlaps(ulong address, ulong size, ref T[] output) + { + int outputIndex = 0; + + ulong endAddress = address + size; + + for (int i = 0; i < Count; i++) + { + ref RangeItem item = ref _items[i]; + + if (item.Address >= endAddress) + { + break; + } + + if (item.OverlapsWith(address, endAddress)) + { + if (outputIndex == output.Length) + { + Array.Resize(ref output, outputIndex + ArrayGrowthSize); + } + + output[outputIndex++] = item.Value; + } + } + + return outputIndex; + } + + /// + /// Gets all items overlapping with the specified item in memory. + /// + /// + /// This method only returns correct results if none of the items on the list overlaps with + /// each other. If that is not the case, this method should not be used. + /// This method is faster than the regular method to find all overlaps. + /// + /// Item to check for overlaps + /// Output array where matches will be written. It is automatically resized to fit the results + /// The number of overlapping items found + public int FindOverlapsNonOverlapping(T item, ref T[] output) + { + return FindOverlapsNonOverlapping(item.Address, item.Size, ref output); + } + + /// + /// Gets all items on the list overlapping the specified memory range. + /// + /// + /// This method only returns correct results if none of the items on the list overlaps with + /// each other. If that is not the case, this method should not be used. + /// This method is faster than the regular method to find all overlaps. + /// + /// Start address of the range + /// Size in bytes of the range + /// Output array where matches will be written. It is automatically resized to fit the results + /// The number of overlapping items found + public int FindOverlapsNonOverlapping(ulong address, ulong size, ref T[] output) + { + // This is a bit faster than FindOverlaps, but only works + // when none of the items on the list overlaps with each other. + int outputIndex = 0; + + ulong endAddress = address + size; + + int index = BinarySearch(address, endAddress); + + if (index >= 0) + { + while (index > 0 && _items[index - 1].OverlapsWith(address, endAddress)) + { + index--; + } + + do + { + if (outputIndex == output.Length) + { + Array.Resize(ref output, outputIndex + ArrayGrowthSize); + } + + output[outputIndex++] = _items[index++].Value; + } + while (index < Count && _items[index].OverlapsWith(address, endAddress)); + } + + return outputIndex; + } + + /// + /// Gets all items on the list with the specified memory address. + /// + /// Address to find + /// Output array where matches will be written. It is automatically resized to fit the results + /// The number of matches found + public int FindOverlaps(ulong address, ref T[] output) + { + int index = BinarySearch(address); + + int outputIndex = 0; + + if (index >= 0) + { + while (index > 0 && _items[index - 1].Address == address) + { + index--; + } + + while (index < Count) + { + ref RangeItem overlap = ref _items[index++]; + + if (overlap.Address != address) + { + break; + } + + if (outputIndex == output.Length) + { + Array.Resize(ref output, outputIndex + ArrayGrowthSize); + } + + output[outputIndex++] = overlap.Value; + } + } + + return outputIndex; + } + + /// + /// Performs binary search on the internal list of items. + /// + /// Address to find + /// List index of the item, or complement index of nearest item with lower value on the list + private int BinarySearch(ulong address) + { + int left = 0; + int right = Count - 1; + + while (left <= right) + { + int range = right - left; + + int middle = left + (range >> 1); + + ref RangeItem item = ref _items[middle]; + + if (item.Address == address) + { + return middle; + } + + if (address < item.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return ~left; + } + + /// + /// Performs binary search for items overlapping a given memory range. + /// + /// Start address of the range + /// End address of the range + /// List index of the item, or complement index of nearest item with lower value on the list + private int BinarySearch(ulong address, ulong endAddress) + { + int left = 0; + int right = Count - 1; + + while (left <= right) + { + int range = right - left; + + int middle = left + (range >> 1); + + ref RangeItem item = ref _items[middle]; + + if (item.OverlapsWith(address, endAddress)) + { + return middle; + } + + if (address < item.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return ~left; + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Count; i++) + { + yield return _items[i].Value; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (int i = 0; i < Count; i++) + { + yield return _items[i].Value; + } + } + } +} diff --git a/src/Ryujinx.Memory/Ryujinx.Memory.csproj b/src/Ryujinx.Memory/Ryujinx.Memory.csproj new file mode 100644 index 00000000..8310a3e5 --- /dev/null +++ b/src/Ryujinx.Memory/Ryujinx.Memory.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + true + + + + + + + diff --git a/src/Ryujinx.Memory/Tracking/AbstractRegion.cs b/src/Ryujinx.Memory/Tracking/AbstractRegion.cs new file mode 100644 index 00000000..7226fe95 --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/AbstractRegion.cs @@ -0,0 +1,73 @@ +using Ryujinx.Memory.Range; + +namespace Ryujinx.Memory.Tracking +{ + /// + /// A region of memory. + /// + abstract class AbstractRegion : INonOverlappingRange + { + /// + /// Base address. + /// + public ulong Address { get; } + + /// + /// Size of the range in bytes. + /// + public ulong Size { get; protected set; } + + /// + /// End address. + /// + public ulong EndAddress => Address + Size; + + /// + /// Create a new region. + /// + /// Base address + /// Size of the range + protected AbstractRegion(ulong address, ulong size) + { + Address = address; + Size = size; + } + + /// + /// Check if this range overlaps with another. + /// + /// Base address + /// Size of the range + /// True if overlapping, false otherwise + public bool OverlapsWith(ulong address, ulong size) + { + return Address < address + size && address < EndAddress; + } + + /// + /// Signals to the handles that a memory event has occurred, and unprotects the region. Assumes that the tracking lock has been obtained. + /// + /// Address accessed + /// Size of the region affected in bytes + /// Whether the region was written to or read + /// Optional ID of the handles that should not be signalled + public abstract void Signal(ulong address, ulong size, bool write, int? exemptId); + + /// + /// Signals to the handles that a precise memory event has occurred. Assumes that the tracking lock has been obtained. + /// + /// Address accessed + /// Size of the region affected in bytes + /// Whether the region was written to or read + /// Optional ID of the handles that should not be signalled + public abstract void SignalPrecise(ulong address, ulong size, bool write, int? exemptId); + + /// + /// Split this region into two, around the specified address. + /// This region is updated to end at the split address, and a new region is created to represent past that point. + /// + /// Address to split the region around + /// The second part of the split region, with start address at the given split. + public abstract INonOverlappingRange Split(ulong splitAddress); + } +} diff --git a/src/Ryujinx.Memory/Tracking/BitMap.cs b/src/Ryujinx.Memory/Tracking/BitMap.cs new file mode 100644 index 00000000..8f0086fc --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/BitMap.cs @@ -0,0 +1,199 @@ +using System.Runtime.CompilerServices; + +namespace Ryujinx.Memory.Tracking +{ + /// + /// A bitmap that can check or set large ranges of true/false values at once. + /// + readonly struct BitMap + { + public const int IntSize = 64; + + private const int IntShift = 6; + private const int IntMask = IntSize - 1; + + /// + /// Masks representing the bitmap. Least significant bit first, 64-bits per mask. + /// + public readonly long[] Masks; + + /// + /// Create a new bitmap. + /// + /// The number of bits to reserve + public BitMap(int count) + { + Masks = new long[(count + IntMask) / IntSize]; + } + + /// + /// Check if any bit in the bitmap is set. + /// + /// True if any bits are set, false otherwise + public bool AnySet() + { + for (int i = 0; i < Masks.Length; i++) + { + if (Masks[i] != 0) + { + return true; + } + } + + return false; + } + + /// + /// Check if a bit in the bitmap is set. + /// + /// The bit index to check + /// True if the bit is set, false otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsSet(int bit) + { + int wordIndex = bit >> IntShift; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + return (Masks[wordIndex] & wordMask) != 0; + } + + /// + /// Check if any bit in a range of bits in the bitmap are set. (inclusive) + /// + /// The first bit index to check + /// The last bit index to check + /// True if a bit is set, false otherwise + public bool IsSet(int start, int end) + { + if (start == end) + { + return IsSet(start); + } + + int startIndex = start >> IntShift; + int startBit = start & IntMask; + long startMask = -1L << startBit; + + int endIndex = end >> IntShift; + int endBit = end & IntMask; + long endMask = (long)(ulong.MaxValue >> (IntMask - endBit)); + + if (startIndex == endIndex) + { + return (Masks[startIndex] & startMask & endMask) != 0; + } + + if ((Masks[startIndex] & startMask) != 0) + { + return true; + } + + for (int i = startIndex + 1; i < endIndex; i++) + { + if (Masks[i] != 0) + { + return true; + } + } + + if ((Masks[endIndex] & endMask) != 0) + { + return true; + } + + return false; + } + + /// + /// Set a bit at a specific index to 1. + /// + /// The bit index to set + /// True if the bit is set, false if it was already set + public bool Set(int bit) + { + int wordIndex = bit >> IntShift; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + if ((Masks[wordIndex] & wordMask) != 0) + { + return false; + } + + Masks[wordIndex] |= wordMask; + + return true; + } + + /// + /// Set a range of bits in the bitmap to 1. + /// + /// The first bit index to set + /// The last bit index to set + public void SetRange(int start, int end) + { + if (start == end) + { + Set(start); + return; + } + + int startIndex = start >> IntShift; + int startBit = start & IntMask; + long startMask = -1L << startBit; + + int endIndex = end >> IntShift; + int endBit = end & IntMask; + long endMask = (long)(ulong.MaxValue >> (IntMask - endBit)); + + if (startIndex == endIndex) + { + Masks[startIndex] |= startMask & endMask; + } + else + { + Masks[startIndex] |= startMask; + + for (int i = startIndex + 1; i < endIndex; i++) + { + Masks[i] |= -1; + } + + Masks[endIndex] |= endMask; + } + } + + /// + /// Clear a bit at a specific index to 0. + /// + /// The bit index to clear + /// True if the bit was set, false if it was not + public bool Clear(int bit) + { + int wordIndex = bit >> IntShift; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + bool wasSet = (Masks[wordIndex] & wordMask) != 0; + + Masks[wordIndex] &= ~wordMask; + + return wasSet; + } + + /// + /// Clear the bitmap entirely, setting all bits to 0. + /// + public void Clear() + { + for (int i = 0; i < Masks.Length; i++) + { + Masks[i] = 0; + } + } + } +} diff --git a/src/Ryujinx.Memory/Tracking/ConcurrentBitmap.cs b/src/Ryujinx.Memory/Tracking/ConcurrentBitmap.cs new file mode 100644 index 00000000..b588b0a8 --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/ConcurrentBitmap.cs @@ -0,0 +1,152 @@ +using System; +using System.Threading; + +namespace Ryujinx.Memory.Tracking +{ + /// + /// A bitmap that can be safely modified from multiple threads. + /// + internal class ConcurrentBitmap + { + public const int IntSize = 64; + + public const int IntShift = 6; + public const int IntMask = IntSize - 1; + + /// + /// Masks representing the bitmap. Least significant bit first, 64-bits per mask. + /// + public readonly long[] Masks; + + /// + /// Create a new multithreaded bitmap. + /// + /// The number of bits to reserve + /// Whether the bits should be initially set or not + public ConcurrentBitmap(int count, bool set) + { + Masks = new long[(count + IntMask) / IntSize]; + + if (set) + { + Array.Fill(Masks, -1L); + } + } + + /// + /// Check if any bit in the bitmap is set. + /// + /// True if any bits are set, false otherwise + public bool AnySet() + { + for (int i = 0; i < Masks.Length; i++) + { + if (Interlocked.Read(ref Masks[i]) != 0) + { + return true; + } + } + + return false; + } + + /// + /// Check if a bit in the bitmap is set. + /// + /// The bit index to check + /// True if the bit is set, false otherwise + public bool IsSet(int bit) + { + int wordIndex = bit >> IntShift; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + return (Interlocked.Read(ref Masks[wordIndex]) & wordMask) != 0; + } + + /// + /// Check if any bit in a range of bits in the bitmap are set. (inclusive) + /// + /// The first bit index to check + /// The last bit index to check + /// True if a bit is set, false otherwise + public bool IsSet(int start, int end) + { + if (start == end) + { + return IsSet(start); + } + + int startIndex = start >> IntShift; + int startBit = start & IntMask; + long startMask = -1L << startBit; + + int endIndex = end >> IntShift; + int endBit = end & IntMask; + long endMask = (long)(ulong.MaxValue >> (IntMask - endBit)); + + long startValue = Interlocked.Read(ref Masks[startIndex]); + + if (startIndex == endIndex) + { + return (startValue & startMask & endMask) != 0; + } + + if ((startValue & startMask) != 0) + { + return true; + } + + for (int i = startIndex + 1; i < endIndex; i++) + { + if (Interlocked.Read(ref Masks[i]) != 0) + { + return true; + } + } + + long endValue = Interlocked.Read(ref Masks[endIndex]); + + if ((endValue & endMask) != 0) + { + return true; + } + + return false; + } + + /// + /// Set a bit at a specific index to either true or false. + /// + /// The bit index to set + /// Whether the bit should be set or not + public void Set(int bit, bool value) + { + int wordIndex = bit >> IntShift; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + if (value) + { + Interlocked.Or(ref Masks[wordIndex], wordMask); + } + else + { + Interlocked.And(ref Masks[wordIndex], ~wordMask); + } + } + + /// + /// Clear the bitmap entirely, setting all bits to 0. + /// + public void Clear() + { + for (int i = 0; i < Masks.Length; i++) + { + Interlocked.Exchange(ref Masks[i], 0); + } + } + } +} diff --git a/src/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs b/src/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs new file mode 100644 index 00000000..62b58344 --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs @@ -0,0 +1,55 @@ +using System; + +namespace Ryujinx.Memory.Tracking +{ + public interface IMultiRegionHandle : IDisposable + { + /// + /// True if any write has occurred to the whole region since the last use of QueryModified (with no subregion specified). + /// + bool Dirty { get; } + + /// + /// Force the range of handles to be dirty, without reprotecting. + /// + /// Start address of the range + /// Size of the range + public void ForceDirty(ulong address, ulong size); + + /// + /// Check if any part of the region has been modified, and perform an action for each. + /// Contiguous modified regions are combined. + /// + /// Action to perform for modified regions + void QueryModified(Action modifiedAction); + + + /// + /// Check if part of the region has been modified within a given range, and perform an action for each. + /// The range is aligned to the level of granularity of the contained handles. + /// Contiguous modified regions are combined. + /// + /// Start address of the range + /// Size of the range + /// Action to perform for modified regions + void QueryModified(ulong address, ulong size, Action modifiedAction); + + /// + /// Check if part of the region has been modified within a given range, and perform an action for each. + /// The sequence number provided is compared with each handle's saved sequence number. + /// If it is equal, then the handle's dirty flag is ignored. Otherwise, the sequence number is saved. + /// The range is aligned to the level of granularity of the contained handles. + /// Contiguous modified regions are combined. + /// + /// Start address of the range + /// Size of the range + /// Action to perform for modified regions + /// Current sequence number + void QueryModified(ulong address, ulong size, Action modifiedAction, int sequenceNumber); + + /// + /// Signal that one of the subregions of this multi-region has been modified. This sets the overall dirty flag. + /// + void SignalWrite(); + } +} diff --git a/src/Ryujinx.Memory/Tracking/IRegionHandle.cs b/src/Ryujinx.Memory/Tracking/IRegionHandle.cs new file mode 100644 index 00000000..28bae832 --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/IRegionHandle.cs @@ -0,0 +1,18 @@ +using System; + +namespace Ryujinx.Memory.Tracking +{ + public interface IRegionHandle : IDisposable + { + bool Dirty { get; } + + ulong Address { get; } + ulong Size { get; } + ulong EndAddress { get; } + + void ForceDirty(); + void Reprotect(bool asDirty = false); + void RegisterAction(RegionSignal action); + void RegisterPreciseAction(PreciseRegionSignal action); + } +} diff --git a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs new file mode 100644 index 00000000..96cb2c5f --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs @@ -0,0 +1,379 @@ +using Ryujinx.Common.Pools; +using Ryujinx.Memory.Range; +using System.Collections.Generic; + +namespace Ryujinx.Memory.Tracking +{ + /// + /// Manages memory tracking for a given virutal/physical memory block. + /// + public class MemoryTracking + { + private readonly IVirtualMemoryManager _memoryManager; + private readonly InvalidAccessHandler _invalidAccessHandler; + + // Only use these from within the lock. + private readonly NonOverlappingRangeList _virtualRegions; + // Guest virtual regions are a subset of the normal virtual regions, with potentially different protection + // and expanded area of effect on platforms that don't support misaligned page protection. + private readonly NonOverlappingRangeList _guestVirtualRegions; + + private readonly int _pageSize; + + private readonly bool _singleByteGuestTracking; + + /// + /// This lock must be obtained when traversing or updating the region-handle hierarchy. + /// It is not required when reading dirty flags. + /// + internal object TrackingLock = new(); + + /// + /// Create a new tracking structure for the given "physical" memory block, + /// with a given "virtual" memory manager that will provide mappings and virtual memory protection. + /// + /// + /// If is true, the memory manager must also support protection on partially + /// unmapped regions without throwing exceptions or dropping protection on the mapped portion. + /// + /// Virtual memory manager + /// Page size of the virtual memory space + /// Method to call for invalid memory accesses + /// True if the guest only signals writes for the first byte + public MemoryTracking( + IVirtualMemoryManager memoryManager, + int pageSize, + InvalidAccessHandler invalidAccessHandler = null, + bool singleByteGuestTracking = false) + { + _memoryManager = memoryManager; + _pageSize = pageSize; + _invalidAccessHandler = invalidAccessHandler; + _singleByteGuestTracking = singleByteGuestTracking; + + _virtualRegions = new NonOverlappingRangeList(); + _guestVirtualRegions = new NonOverlappingRangeList(); + } + + private (ulong address, ulong size) PageAlign(ulong address, ulong size) + { + ulong pageMask = (ulong)_pageSize - 1; + ulong rA = address & ~pageMask; + ulong rS = ((address + size + pageMask) & ~pageMask) - rA; + return (rA, rS); + } + + /// + /// Indicate that a virtual region has been mapped, and which physical region it has been mapped to. + /// Should be called after the mapping is complete. + /// + /// Virtual memory address + /// Size to be mapped + public void Map(ulong va, ulong size) + { + // A mapping may mean we need to re-evaluate each VirtualRegion's affected area. + // Find all handles that overlap with the range, we need to recalculate their physical regions + + lock (TrackingLock) + { + ref var overlaps = ref ThreadStaticArray.Get(); + + for (int type = 0; type < 2; type++) + { + NonOverlappingRangeList regions = type == 0 ? _virtualRegions : _guestVirtualRegions; + + int count = regions.FindOverlapsNonOverlapping(va, size, ref overlaps); + + for (int i = 0; i < count; i++) + { + VirtualRegion region = overlaps[i]; + + // If the region has been fully remapped, signal that it has been mapped again. + bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size); + if (remapped) + { + region.SignalMappingChanged(true); + } + + region.UpdateProtection(); + } + } + } + } + + /// + /// Indicate that a virtual region has been unmapped. + /// Should be called before the unmapping is complete. + /// + /// Virtual memory address + /// Size to be unmapped + public void Unmap(ulong va, ulong size) + { + // An unmapping may mean we need to re-evaluate each VirtualRegion's affected area. + // Find all handles that overlap with the range, we need to notify them that the region was unmapped. + + lock (TrackingLock) + { + ref var overlaps = ref ThreadStaticArray.Get(); + + for (int type = 0; type < 2; type++) + { + NonOverlappingRangeList regions = type == 0 ? _virtualRegions : _guestVirtualRegions; + + int count = regions.FindOverlapsNonOverlapping(va, size, ref overlaps); + + for (int i = 0; i < count; i++) + { + VirtualRegion region = overlaps[i]; + + region.SignalMappingChanged(false); + } + } + } + } + + /// + /// Alter a tracked memory region to properly capture unaligned accesses. + /// For most memory manager modes, this does nothing. + /// + /// Original region address + /// Original region size + /// A new address and size for tracking unaligned accesses + internal (ulong newAddress, ulong newSize) GetUnalignedSafeRegion(ulong address, ulong size) + { + if (_singleByteGuestTracking) + { + // The guest only signals the first byte of each memory access with the current memory manager. + // To catch unaligned access properly, we need to also protect the page before the address. + + // Assume that the address and size are already aligned. + + return (address - (ulong)_pageSize, size + (ulong)_pageSize); + } + else + { + return (address, size); + } + } + + /// + /// Get a list of virtual regions that a handle covers. + /// + /// Starting virtual memory address of the handle + /// Size of the handle's memory region + /// True if getting handles for guest protection, false otherwise + /// A list of virtual regions within the given range + internal List GetVirtualRegionsForHandle(ulong va, ulong size, bool guest) + { + List result = new(); + NonOverlappingRangeList regions = guest ? _guestVirtualRegions : _virtualRegions; + regions.GetOrAddRegions(result, va, size, (va, size) => new VirtualRegion(this, va, size, guest)); + + return result; + } + + /// + /// Remove a virtual region from the range list. This assumes that the lock has been acquired. + /// + /// Region to remove + internal void RemoveVirtual(VirtualRegion region) + { + if (region.Guest) + { + _guestVirtualRegions.Remove(region); + } + else + { + _virtualRegions.Remove(region); + } + } + + /// + /// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with. + /// + /// CPU virtual address of the region + /// Size of the region + /// Handles to inherit state from or reuse. When none are present, provide null + /// Desired granularity of write tracking + /// Handle ID + /// Region flags + /// The memory tracking handle + public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable handles, ulong granularity, int id, RegionFlags flags = RegionFlags.None) + { + return new MultiRegionHandle(this, address, size, handles, granularity, id, flags); + } + + /// + /// Obtains a smart memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with. + /// + /// CPU virtual address of the region + /// Size of the region + /// Desired granularity of write tracking + /// Handle ID + /// The memory tracking handle + public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id) + { + (address, size) = PageAlign(address, size); + + return new SmartMultiRegionHandle(this, address, size, granularity, id); + } + + /// + /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with. + /// + /// CPU virtual address of the region + /// Size of the region + /// Handle ID + /// Region flags + /// The memory tracking handle + public RegionHandle BeginTracking(ulong address, ulong size, int id, RegionFlags flags = RegionFlags.None) + { + var (paAddress, paSize) = PageAlign(address, size); + + lock (TrackingLock) + { + bool mapped = _memoryManager.IsRangeMapped(address, size); + RegionHandle handle = new(this, paAddress, paSize, address, size, id, flags, mapped); + + return handle; + } + } + + /// + /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with. + /// + /// CPU virtual address of the region + /// Size of the region + /// The bitmap owning the dirty flag for this handle + /// The bit of this handle within the dirty flag + /// Handle ID + /// Region flags + /// The memory tracking handle + internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit, int id, RegionFlags flags = RegionFlags.None) + { + var (paAddress, paSize) = PageAlign(address, size); + + lock (TrackingLock) + { + bool mapped = _memoryManager.IsRangeMapped(address, size); + RegionHandle handle = new(this, paAddress, paSize, address, size, bitmap, bit, id, flags, mapped); + + return handle; + } + } + + /// + /// Signal that a virtual memory event happened at the given location. + /// The memory event is assumed to be triggered by guest code. + /// + /// Virtual address accessed + /// Size of the region affected in bytes + /// Whether the region was written to or read + /// True if the event triggered any tracking regions, false otherwise + public bool VirtualMemoryEvent(ulong address, ulong size, bool write) + { + return VirtualMemoryEvent(address, size, write, precise: false, exemptId: null, guest: true); + } + + /// + /// Signal that a virtual memory event happened at the given location. + /// This can be flagged as a precise event, which will avoid reprotection and call special handlers if possible. + /// A precise event has an exact address and size, rather than triggering on page granularity. + /// + /// Virtual address accessed + /// Size of the region affected in bytes + /// Whether the region was written to or read + /// True if the access is precise, false otherwise + /// Optional ID that of the handles that should not be signalled + /// True if the access is from the guest, false otherwise + /// True if the event triggered any tracking regions, false otherwise + public bool VirtualMemoryEvent(ulong address, ulong size, bool write, bool precise, int? exemptId = null, bool guest = false) + { + // Look up the virtual region using the region list. + // Signal up the chain to relevant handles. + + bool shouldThrow = false; + + lock (TrackingLock) + { + ref var overlaps = ref ThreadStaticArray.Get(); + + NonOverlappingRangeList regions = guest ? _guestVirtualRegions : _virtualRegions; + + int count = regions.FindOverlapsNonOverlapping(address, size, ref overlaps); + + if (count == 0 && !precise) + { + if (_memoryManager.IsRangeMapped(address, size)) + { + // TODO: There is currently the possibility that a page can be protected after its virtual region is removed. + // This code handles that case when it happens, but it would be better to find out how this happens. + _memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite, guest); + return true; // This memory _should_ be mapped, so we need to try again. + } + else + { + shouldThrow = true; + } + } + else + { + if (guest && _singleByteGuestTracking) + { + // Increase the access size to trigger handles with misaligned accesses. + size += (ulong)_pageSize; + } + + for (int i = 0; i < count; i++) + { + VirtualRegion region = overlaps[i]; + + if (precise) + { + region.SignalPrecise(address, size, write, exemptId); + } + else + { + region.Signal(address, size, write, exemptId); + } + } + } + } + + if (shouldThrow) + { + _invalidAccessHandler?.Invoke(address); + + // We can't continue - it's impossible to remove protection from the page. + // Even if the access handler wants us to continue, we wouldn't be able to. + throw new InvalidMemoryRegionException(); + } + + return true; + } + + /// + /// Reprotect a given virtual region. The virtual memory manager will handle this. + /// + /// Region to reprotect + /// Memory permission to protect with + /// True if the protection is for guest access, false otherwise + internal void ProtectVirtualRegion(VirtualRegion region, MemoryPermission permission, bool guest) + { + _memoryManager.TrackingReprotect(region.Address, region.Size, permission, guest); + } + + /// + /// Returns the number of virtual regions currently being tracked. + /// Useful for tests and metrics. + /// + /// The number of virtual regions + public int GetRegionCount() + { + lock (TrackingLock) + { + return _virtualRegions.Count; + } + } + } +} diff --git a/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs b/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs new file mode 100644 index 00000000..6fdca69f --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs @@ -0,0 +1,419 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Ryujinx.Memory.Tracking +{ + /// + /// A region handle that tracks a large region using many smaller handles, to provide + /// granular tracking that can be used to track partial updates. Backed by a bitmap + /// to improve performance when scanning large regions. + /// + public class MultiRegionHandle : IMultiRegionHandle + { + /// + /// A list of region handles for each granularity sized chunk of the whole region. + /// + private readonly RegionHandle[] _handles; + private readonly ulong Address; + private readonly ulong Granularity; + private readonly ulong Size; + + private readonly ConcurrentBitmap _dirtyBitmap; + + private int _sequenceNumber; + private readonly BitMap _sequenceNumberBitmap; + private readonly BitMap _dirtyCheckedBitmap; + private int _uncheckedHandles; + + public bool Dirty { get; private set; } = true; + + internal MultiRegionHandle( + MemoryTracking tracking, + ulong address, + ulong size, + IEnumerable handles, + ulong granularity, + int id, + RegionFlags flags) + { + _handles = new RegionHandle[(size + granularity - 1) / granularity]; + Granularity = granularity; + + _dirtyBitmap = new ConcurrentBitmap(_handles.Length, true); + _sequenceNumberBitmap = new BitMap(_handles.Length); + _dirtyCheckedBitmap = new BitMap(_handles.Length); + + int i = 0; + + if (handles != null) + { + // Inherit from the handles we were given. Any gaps must be filled with new handles, + // and old handles larger than our granularity must copy their state onto new granular handles and dispose. + // It is assumed that the provided handles do not overlap, in order, are on page boundaries, + // and don't extend past the requested range. + + foreach (RegionHandle handle in handles.Cast()) + { + int startIndex = (int)((handle.RealAddress - address) / granularity); + + // Fill any gap left before this handle. + while (i < startIndex) + { + RegionHandle fillHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags); + fillHandle.Parent = this; + _handles[i++] = fillHandle; + } + + lock (tracking.TrackingLock) + { + if (handle is RegionHandle bitHandle && handle.Size == granularity) + { + handle.Parent = this; + + bitHandle.ReplaceBitmap(_dirtyBitmap, i); + + _handles[i++] = bitHandle; + } + else + { + int endIndex = (int)((handle.RealEndAddress - address) / granularity); + + while (i < endIndex) + { + RegionHandle splitHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags); + splitHandle.Parent = this; + + splitHandle.Reprotect(handle.Dirty); + + RegionSignal signal = handle.PreAction; + if (signal != null) + { + splitHandle.RegisterAction(signal); + } + + _handles[i++] = splitHandle; + } + + handle.Dispose(); + } + } + } + } + + // Fill any remaining space with new handles. + while (i < _handles.Length) + { + RegionHandle handle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id, flags); + handle.Parent = this; + _handles[i++] = handle; + } + + _uncheckedHandles = _handles.Length; + + Address = address; + Size = size; + } + + public void SignalWrite() + { + Dirty = true; + } + + public IEnumerable GetHandles() + { + return _handles; + } + + public void ForceDirty(ulong address, ulong size) + { + Dirty = true; + + int startHandle = (int)((address - Address) / Granularity); + int lastHandle = (int)((address + (size - 1) - Address) / Granularity); + + for (int i = startHandle; i <= lastHandle; i++) + { + if (_sequenceNumberBitmap.Clear(i)) + { + _uncheckedHandles++; + } + + _handles[i].ForceDirty(); + } + } + + public void QueryModified(Action modifiedAction) + { + if (!Dirty) + { + return; + } + + Dirty = false; + + QueryModified(Address, Size, modifiedAction); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ParseDirtyBits(long dirtyBits, ref int baseBit, ref int prevHandle, ref ulong rgStart, ref ulong rgSize, Action modifiedAction) + { + while (dirtyBits != 0) + { + int bit = BitOperations.TrailingZeroCount(dirtyBits); + + dirtyBits &= ~(1L << bit); + + int handleIndex = baseBit + bit; + + RegionHandle handle = _handles[handleIndex]; + + if (handleIndex != prevHandle + 1) + { + // Submit handles scanned until the gap as dirty + if (rgSize != 0) + { + modifiedAction(rgStart, rgSize); + rgSize = 0; + } + + rgStart = handle.RealAddress; + } + + if (handle.Dirty) + { + rgSize += handle.RealSize; + handle.Reprotect(); + } + + prevHandle = handleIndex; + } + + baseBit += ConcurrentBitmap.IntSize; + } + + public void QueryModified(ulong address, ulong size, Action modifiedAction) + { + int startHandle = (int)((address - Address) / Granularity); + int lastHandle = (int)((address + (size - 1) - Address) / Granularity); + + ulong rgStart = Address + (ulong)startHandle * Granularity; + + if (startHandle == lastHandle) + { + RegionHandle handle = _handles[startHandle]; + + if (handle.Dirty) + { + handle.Reprotect(); + modifiedAction(rgStart, handle.RealSize); + } + + return; + } + + ulong rgSize = 0; + + long[] masks = _dirtyBitmap.Masks; + + int startIndex = startHandle >> ConcurrentBitmap.IntShift; + int startBit = startHandle & ConcurrentBitmap.IntMask; + long startMask = -1L << startBit; + + int endIndex = lastHandle >> ConcurrentBitmap.IntShift; + int endBit = lastHandle & ConcurrentBitmap.IntMask; + long endMask = (long)(ulong.MaxValue >> (ConcurrentBitmap.IntMask - endBit)); + + long startValue = Volatile.Read(ref masks[startIndex]); + + int baseBit = startIndex << ConcurrentBitmap.IntShift; + int prevHandle = startHandle - 1; + + if (startIndex == endIndex) + { + ParseDirtyBits(startValue & startMask & endMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); + } + else + { + ParseDirtyBits(startValue & startMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); + + for (int i = startIndex + 1; i < endIndex; i++) + { + ParseDirtyBits(Volatile.Read(ref masks[i]), ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); + } + + long endValue = Volatile.Read(ref masks[endIndex]); + + ParseDirtyBits(endValue & endMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); + } + + if (rgSize != 0) + { + modifiedAction(rgStart, rgSize); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ParseDirtyBits(long dirtyBits, long mask, int index, long[] seqMasks, long[] checkMasks, ref int baseBit, ref int prevHandle, ref ulong rgStart, ref ulong rgSize, Action modifiedAction) + { + long seqMask = mask & ~seqMasks[index]; + long checkMask = (~dirtyBits) & seqMask; + dirtyBits &= seqMask; + + while (dirtyBits != 0) + { + int bit = BitOperations.TrailingZeroCount(dirtyBits); + long bitValue = 1L << bit; + + dirtyBits &= ~bitValue; + + int handleIndex = baseBit + bit; + + RegionHandle handle = _handles[handleIndex]; + + if (handleIndex != prevHandle + 1) + { + // Submit handles scanned until the gap as dirty + if (rgSize != 0) + { + modifiedAction(rgStart, rgSize); + rgSize = 0; + } + rgStart = handle.RealAddress; + } + + rgSize += handle.RealSize; + handle.Reprotect(false, (checkMasks[index] & bitValue) == 0); + + checkMasks[index] &= ~bitValue; + + prevHandle = handleIndex; + } + + checkMasks[index] |= checkMask; + seqMasks[index] |= mask; + _uncheckedHandles -= BitOperations.PopCount((ulong)seqMask); + + baseBit += ConcurrentBitmap.IntSize; + } + + public void QueryModified(ulong address, ulong size, Action modifiedAction, int sequenceNumber) + { + int startHandle = (int)((address - Address) / Granularity); + int lastHandle = (int)((address + (size - 1) - Address) / Granularity); + + ulong rgStart = Address + (ulong)startHandle * Granularity; + + if (sequenceNumber != _sequenceNumber) + { + if (_uncheckedHandles != _handles.Length) + { + _sequenceNumberBitmap.Clear(); + _uncheckedHandles = _handles.Length; + } + + _sequenceNumber = sequenceNumber; + } + + if (startHandle == lastHandle) + { + var handle = _handles[startHandle]; + if (_sequenceNumberBitmap.Set(startHandle)) + { + _uncheckedHandles--; + + if (handle.DirtyOrVolatile()) + { + handle.Reprotect(); + + modifiedAction(rgStart, handle.RealSize); + } + } + + return; + } + + if (_uncheckedHandles == 0) + { + return; + } + + ulong rgSize = 0; + + long[] seqMasks = _sequenceNumberBitmap.Masks; + long[] checkedMasks = _dirtyCheckedBitmap.Masks; + long[] masks = _dirtyBitmap.Masks; + + int startIndex = startHandle >> ConcurrentBitmap.IntShift; + int startBit = startHandle & ConcurrentBitmap.IntMask; + long startMask = -1L << startBit; + + int endIndex = lastHandle >> ConcurrentBitmap.IntShift; + int endBit = lastHandle & ConcurrentBitmap.IntMask; + long endMask = (long)(ulong.MaxValue >> (ConcurrentBitmap.IntMask - endBit)); + + long startValue = Volatile.Read(ref masks[startIndex]); + + int baseBit = startIndex << ConcurrentBitmap.IntShift; + int prevHandle = startHandle - 1; + + if (startIndex == endIndex) + { + ParseDirtyBits(startValue, startMask & endMask, startIndex, seqMasks, checkedMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); + } + else + { + ParseDirtyBits(startValue, startMask, startIndex, seqMasks, checkedMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); + + for (int i = startIndex + 1; i < endIndex; i++) + { + ParseDirtyBits(Volatile.Read(ref masks[i]), -1L, i, seqMasks, checkedMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); + } + + long endValue = Volatile.Read(ref masks[endIndex]); + + ParseDirtyBits(endValue, endMask, endIndex, seqMasks, checkedMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction); + } + + if (rgSize != 0) + { + modifiedAction(rgStart, rgSize); + } + } + + public void RegisterAction(ulong address, ulong size, RegionSignal action) + { + int startHandle = (int)((address - Address) / Granularity); + int lastHandle = (int)((address + (size - 1) - Address) / Granularity); + + for (int i = startHandle; i <= lastHandle; i++) + { + _handles[i].RegisterAction(action); + } + } + + public void RegisterPreciseAction(ulong address, ulong size, PreciseRegionSignal action) + { + int startHandle = (int)((address - Address) / Granularity); + int lastHandle = (int)((address + (size - 1) - Address) / Granularity); + + for (int i = startHandle; i <= lastHandle; i++) + { + _handles[i].RegisterPreciseAction(action); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + foreach (var handle in _handles) + { + handle.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Memory/Tracking/PreciseRegionSignal.cs b/src/Ryujinx.Memory/Tracking/PreciseRegionSignal.cs new file mode 100644 index 00000000..9828584a --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/PreciseRegionSignal.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Memory.Tracking +{ + public delegate bool PreciseRegionSignal(ulong address, ulong size, bool write); +} diff --git a/src/Ryujinx.Memory/Tracking/RegionFlags.cs b/src/Ryujinx.Memory/Tracking/RegionFlags.cs new file mode 100644 index 00000000..ceb8e56a --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/RegionFlags.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ryujinx.Memory.Tracking +{ + [Flags] + public enum RegionFlags + { + None = 0, + + /// + /// Access to the resource is expected to occasionally be unaligned. + /// With some memory managers, guest protection must extend into the previous page to cover unaligned access. + /// If this is not expected, protection is not altered, which can avoid unintended resource dirty/flush. + /// + UnalignedAccess = 1, + } +} diff --git a/src/Ryujinx.Memory/Tracking/RegionHandle.cs b/src/Ryujinx.Memory/Tracking/RegionHandle.cs new file mode 100644 index 00000000..a94ffa43 --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/RegionHandle.cs @@ -0,0 +1,523 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Ryujinx.Memory.Tracking +{ + /// + /// A tracking handle for a given region of virtual memory. The Dirty flag is updated whenever any changes are made, + /// and an action can be performed when the region is read to or written from. + /// + public class RegionHandle : IRegionHandle + { + /// + /// If more than this number of checks have been performed on a dirty flag since its last reprotect, + /// then it is dirtied infrequently. + /// + private const int CheckCountForInfrequent = 3; + + /// + /// Number of frequent dirty/consume in a row to make this handle volatile. + /// + private const int VolatileThreshold = 5; + + public bool Dirty + { + get + { + return Bitmap.IsSet(DirtyBit); + } + protected set + { + Bitmap.Set(DirtyBit, value); + } + } + + internal int SequenceNumber { get; set; } + internal int Id { get; } + + public bool Unmapped { get; private set; } + + public ulong Address { get; } + public ulong Size { get; } + public ulong EndAddress { get; } + + public ulong RealAddress { get; } + public ulong RealSize { get; } + public ulong RealEndAddress { get; } + + internal IMultiRegionHandle Parent { get; set; } + + private event Action OnDirty; + + private readonly object _preActionLock = new(); + private RegionSignal _preAction; // Action to perform before a read or write. This will block the memory access. + private PreciseRegionSignal _preciseAction; // Action to perform on a precise read or write. + private readonly List _regions; + private readonly List _guestRegions; + private readonly List _allRegions; + private readonly MemoryTracking _tracking; + private bool _disposed; + + private int _checkCount = 0; + private int _volatileCount = 0; + private bool _volatile; + + internal MemoryPermission RequiredPermission + { + get + { + // If this is unmapped, allow reprotecting as RW as it can't be dirtied. + // This is required for the partial unmap cases where part of the data are still being accessed. + if (Unmapped) + { + return MemoryPermission.ReadAndWrite; + } + + if (_preAction != null) + { + return MemoryPermission.None; + } + + return Dirty ? MemoryPermission.ReadAndWrite : MemoryPermission.Read; + } + } + + internal RegionSignal PreAction => _preAction; + + internal ConcurrentBitmap Bitmap; + internal int DirtyBit; + + /// + /// Create a new bitmap backed region handle. The handle is registered with the given tracking object, + /// and will be notified of any changes to the specified region. + /// + /// Tracking object for the target memory block + /// Virtual address of the region to track + /// Size of the region to track + /// The real, unaligned address of the handle + /// The real, unaligned size of the handle + /// The bitmap the dirty flag for this handle is stored in + /// The bit index representing the dirty flag for this handle + /// Handle ID + /// Region flags + /// True if the region handle starts mapped + internal RegionHandle( + MemoryTracking tracking, + ulong address, + ulong size, + ulong realAddress, + ulong realSize, + ConcurrentBitmap bitmap, + int bit, + int id, + RegionFlags flags, + bool mapped = true) + { + Bitmap = bitmap; + DirtyBit = bit; + + Dirty = mapped; + + Id = id; + + Unmapped = !mapped; + Address = address; + Size = size; + EndAddress = address + size; + + RealAddress = realAddress; + RealSize = realSize; + RealEndAddress = realAddress + realSize; + + _tracking = tracking; + + _regions = tracking.GetVirtualRegionsForHandle(address, size, false); + _guestRegions = GetGuestRegions(tracking, address, size, flags); + _allRegions = new List(_regions.Count + _guestRegions.Count); + + InitializeRegions(); + } + + /// + /// Create a new region handle. The handle is registered with the given tracking object, + /// and will be notified of any changes to the specified region. + /// + /// Tracking object for the target memory block + /// Virtual address of the region to track + /// Size of the region to track + /// The real, unaligned address of the handle + /// The real, unaligned size of the handle + /// Handle ID + /// Region flags + /// True if the region handle starts mapped + internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, int id, RegionFlags flags, bool mapped = true) + { + Bitmap = new ConcurrentBitmap(1, mapped); + + Id = id; + + Unmapped = !mapped; + + Address = address; + Size = size; + EndAddress = address + size; + + RealAddress = realAddress; + RealSize = realSize; + RealEndAddress = realAddress + realSize; + + _tracking = tracking; + + _regions = tracking.GetVirtualRegionsForHandle(address, size, false); + _guestRegions = GetGuestRegions(tracking, address, size, flags); + _allRegions = new List(_regions.Count + _guestRegions.Count); + + InitializeRegions(); + } + + private List GetGuestRegions(MemoryTracking tracking, ulong address, ulong size, RegionFlags flags) + { + ulong guestAddress; + ulong guestSize; + + if (flags.HasFlag(RegionFlags.UnalignedAccess)) + { + (guestAddress, guestSize) = tracking.GetUnalignedSafeRegion(address, size); + } + else + { + (guestAddress, guestSize) = (address, size); + } + + return tracking.GetVirtualRegionsForHandle(guestAddress, guestSize, true); + } + + private void InitializeRegions() + { + _allRegions.AddRange(_regions); + _allRegions.AddRange(_guestRegions); + + foreach (var region in _allRegions) + { + region.Handles.Add(this); + } + } + + /// + /// Replace the bitmap and bit index used to track dirty state. + /// + /// + /// The tracking lock should be held when this is called, to ensure neither bitmap is modified. + /// + /// The bitmap the dirty flag for this handle is stored in + /// The bit index representing the dirty flag for this handle + internal void ReplaceBitmap(ConcurrentBitmap bitmap, int bit) + { + // Assumes the tracking lock is held, so nothing else can signal right now. + + var oldBitmap = Bitmap; + var oldBit = DirtyBit; + + bitmap.Set(bit, Dirty); + + Bitmap = bitmap; + DirtyBit = bit; + + Dirty |= oldBitmap.IsSet(oldBit); + } + + /// + /// Clear the volatile state of this handle. + /// + private void ClearVolatile() + { + _volatileCount = 0; + _volatile = false; + } + + /// + /// Check if this handle is dirty, or if it is volatile. (changes very often) + /// + /// True if the handle is dirty or volatile, false otherwise + public bool DirtyOrVolatile() + { + _checkCount++; + return _volatile || Dirty; + } + + /// + /// Signal that a memory action occurred within this handle's virtual regions. + /// + /// Address accessed + /// Size of the region affected in bytes + /// Whether the region was written to or read + /// Reference to the handles being iterated, in case the list needs to be copied + internal void Signal(ulong address, ulong size, bool write, ref IList handleIterable) + { + // If this handle was already unmapped (even if just partially), + // then we have nothing to do until it is mapped again. + // The pre-action should be still consumed to avoid flushing on remap. + if (Unmapped) + { + Interlocked.Exchange(ref _preAction, null); + return; + } + + if (_preAction != null) + { + // Limit the range to within this handle. + ulong maxAddress = Math.Max(address, RealAddress); + ulong minEndAddress = Math.Min(address + size, RealAddress + RealSize); + + // Copy the handles list in case it changes when we're out of the lock. + if (handleIterable is List) + { + handleIterable = handleIterable.ToArray(); + } + + // Temporarily release the tracking lock while we're running the action. + Monitor.Exit(_tracking.TrackingLock); + + try + { + lock (_preActionLock) + { + _preAction?.Invoke(maxAddress, minEndAddress - maxAddress); + + // The action is removed after it returns, to ensure that the null check above succeeds when + // it's still in progress rather than continuing and possibly missing a required data flush. + Interlocked.Exchange(ref _preAction, null); + } + } + finally + { + Monitor.Enter(_tracking.TrackingLock); + } + } + + if (write) + { + bool oldDirty = Dirty; + Dirty = true; + if (!oldDirty) + { + OnDirty?.Invoke(); + } + Parent?.SignalWrite(); + } + } + + /// + /// Signal that a precise memory action occurred within this handle's virtual regions. + /// If there is no precise action, or the action returns false, the normal signal handler will be called. + /// + /// Address accessed + /// Size of the region affected in bytes + /// Whether the region was written to or read + /// Reference to the handles being iterated, in case the list needs to be copied + /// True if a precise action was performed and returned true, false otherwise + internal bool SignalPrecise(ulong address, ulong size, bool write, ref IList handleIterable) + { + if (!Unmapped && _preciseAction != null && _preciseAction(address, size, write)) + { + return true; + } + + Signal(address, size, write, ref handleIterable); + + return false; + } + + /// + /// Force this handle to be dirty, without reprotecting. + /// + public void ForceDirty() + { + Dirty = true; + } + + /// + /// Consume the dirty flag for this handle, and reprotect so it can be set on the next write. + /// + /// True if the handle should be reprotected as dirty, rather than have it cleared + /// True if this reprotect is the result of consecutive dirty checks + public void Reprotect(bool asDirty, bool consecutiveCheck = false) + { + if (_volatile) + { + return; + } + + Dirty = asDirty; + + bool protectionChanged = false; + + lock (_tracking.TrackingLock) + { + foreach (VirtualRegion region in _allRegions) + { + protectionChanged |= region.UpdateProtection(); + } + } + + if (!protectionChanged) + { + // Counteract the check count being incremented when this handle was forced dirty. + // It doesn't count for protected write tracking. + + _checkCount--; + } + else if (!asDirty) + { + if (consecutiveCheck || (_checkCount > 0 && _checkCount < CheckCountForInfrequent)) + { + if (++_volatileCount >= VolatileThreshold && _preAction == null) + { + _volatile = true; + return; + } + } + else + { + _volatileCount = 0; + } + + _checkCount = 0; + } + } + + /// + /// Consume the dirty flag for this handle, and reprotect so it can be set on the next write. + /// + /// True if the handle should be reprotected as dirty, rather than have it cleared + public void Reprotect(bool asDirty = false) + { + Reprotect(asDirty, false); + } + + /// + /// Register an action to perform when the tracked region is read or written. + /// The action is automatically removed after it runs. + /// + /// Action to call on read or write + public void RegisterAction(RegionSignal action) + { + ClearVolatile(); + + lock (_preActionLock) + { + RegionSignal lastAction = Interlocked.Exchange(ref _preAction, action); + + if (lastAction == null && action != lastAction) + { + lock (_tracking.TrackingLock) + { + foreach (VirtualRegion region in _allRegions) + { + region.UpdateProtection(); + } + } + } + } + } + + /// + /// Register an action to perform when a precise access occurs (one with exact address and size). + /// If the action returns true, read/write tracking are skipped. + /// + /// Action to call on read or write + public void RegisterPreciseAction(PreciseRegionSignal action) + { + _preciseAction = action; + } + + /// + /// Register an action to perform when the region is written to. + /// This action will not be removed when it is called - it is called each time the dirty flag is set. + /// + /// Action to call on dirty + public void RegisterDirtyEvent(Action action) + { + OnDirty += action; + } + + /// + /// Add a child virtual region to this handle. + /// + /// Virtual region to add as a child + internal void AddChild(VirtualRegion region) + { + if (region.Guest) + { + _guestRegions.Add(region); + } + else + { + _regions.Add(region); + } + + _allRegions.Add(region); + } + + /// + /// Signal that this handle has been mapped or unmapped. + /// + /// True if the handle has been mapped, false if unmapped + internal void SignalMappingChanged(bool mapped) + { + if (Unmapped == mapped) + { + Unmapped = !mapped; + + if (Unmapped) + { + ClearVolatile(); + Dirty = false; + } + } + } + + /// + /// Check if this region overlaps with another. + /// + /// Base address + /// Size of the region + /// True if overlapping, false otherwise + public bool OverlapsWith(ulong address, ulong size) + { + return Address < address + size && address < EndAddress; + } + + /// + /// Determines if this handle's memory range matches another exactly. + /// + /// The other handle + /// True on a match, false otherwise + public bool RangeEquals(RegionHandle other) + { + return RealAddress == other.RealAddress && RealSize == other.RealSize; + } + + /// + /// Dispose the handle. Within the tracking lock, this removes references from virtual regions. + /// + public void Dispose() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + GC.SuppressFinalize(this); + + _disposed = true; + + lock (_tracking.TrackingLock) + { + foreach (VirtualRegion region in _allRegions) + { + region.RemoveHandle(this); + } + } + } + } +} diff --git a/src/Ryujinx.Memory/Tracking/RegionSignal.cs b/src/Ryujinx.Memory/Tracking/RegionSignal.cs new file mode 100644 index 00000000..78c8687f --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/RegionSignal.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Memory.Tracking +{ + public delegate void RegionSignal(ulong address, ulong size); +} diff --git a/src/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs b/src/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs new file mode 100644 index 00000000..57129a18 --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs @@ -0,0 +1,282 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Memory.Tracking +{ + /// + /// A MultiRegionHandle that attempts to segment a region's handles into the regions requested + /// to avoid iterating over granular chunks for canonically large regions. + /// If minimum granularity is to be expected, use MultiRegionHandle. + /// + public class SmartMultiRegionHandle : IMultiRegionHandle + { + /// + /// A list of region handles starting at each granularity size increment. + /// + private readonly RegionHandle[] _handles; + private readonly ulong _address; + private readonly ulong _granularity; + private readonly ulong _size; + private readonly MemoryTracking _tracking; + private readonly int _id; + + public bool Dirty { get; private set; } = true; + + internal SmartMultiRegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong granularity, int id) + { + // For this multi-region handle, the handle list starts empty. + // As regions are queried, they are added to the _handles array at their start index. + // When a region being added overlaps another, the existing region is split. + // A query can therefore scan multiple regions, though with no overlaps they can cover a large area. + + _tracking = tracking; + _handles = new RegionHandle[size / granularity]; + _granularity = granularity; + + _address = address; + _size = size; + _id = id; + } + + public void SignalWrite() + { + Dirty = true; + } + + public void ForceDirty(ulong address, ulong size) + { + foreach (var handle in _handles) + { + if (handle != null && handle.OverlapsWith(address, size)) + { + handle.ForceDirty(); + } + } + } + + public void RegisterAction(RegionSignal action) + { + foreach (var handle in _handles) + { + if (handle != null) + { + handle?.RegisterAction((address, size) => action(handle.Address, handle.Size)); + } + } + } + + public void RegisterPreciseAction(PreciseRegionSignal action) + { + foreach (var handle in _handles) + { + if (handle != null) + { + handle?.RegisterPreciseAction((address, size, write) => action(handle.Address, handle.Size, write)); + } + } + } + + public void QueryModified(Action modifiedAction) + { + if (!Dirty) + { + return; + } + + Dirty = false; + + QueryModified(_address, _size, modifiedAction); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ulong HandlesToBytes(int handles) + { + return (ulong)handles * _granularity; + } + + private void SplitHandle(int handleIndex, int splitIndex) + { + RegionHandle handle = _handles[handleIndex]; + ulong address = _address + HandlesToBytes(handleIndex); + ulong size = HandlesToBytes(splitIndex - handleIndex); + + // First, the target handle must be removed. Its data can still be used to determine the new handles. + RegionSignal signal = handle.PreAction; + handle.Dispose(); + + RegionHandle splitLow = _tracking.BeginTracking(address, size, _id); + splitLow.Parent = this; + if (signal != null) + { + splitLow.RegisterAction(signal); + } + _handles[handleIndex] = splitLow; + + RegionHandle splitHigh = _tracking.BeginTracking(address + size, handle.Size - size, _id); + splitHigh.Parent = this; + if (signal != null) + { + splitHigh.RegisterAction(signal); + } + _handles[splitIndex] = splitHigh; + } + + private void CreateHandle(int startHandle, int lastHandle) + { + ulong startAddress = _address + HandlesToBytes(startHandle); + + // Scan for the first handle before us. If it's overlapping us, it must be split. + for (int i = startHandle - 1; i >= 0; i--) + { + RegionHandle handle = _handles[i]; + if (handle != null) + { + if (handle.EndAddress > startAddress) + { + SplitHandle(i, startHandle); + return; // The remainer of this handle should be filled in later on. + } + break; + } + } + + // Scan for handles after us. We should create a handle that goes up to this handle's start point, if present. + for (int i = startHandle + 1; i <= lastHandle; i++) + { + RegionHandle handle = _handles[i]; + if (handle != null) + { + // Fill up to the found handle. + handle = _tracking.BeginTracking(startAddress, HandlesToBytes(i - startHandle), _id); + handle.Parent = this; + _handles[startHandle] = handle; + return; + } + } + + // Can fill the whole range. + _handles[startHandle] = _tracking.BeginTracking(startAddress, HandlesToBytes(1 + lastHandle - startHandle), _id); + _handles[startHandle].Parent = this; + } + + public void QueryModified(ulong address, ulong size, Action modifiedAction) + { + int startHandle = (int)((address - _address) / _granularity); + int lastHandle = (int)((address + (size - 1) - _address) / _granularity); + + ulong rgStart = _address + (ulong)startHandle * _granularity; + ulong rgSize = 0; + + ulong endAddress = _address + ((ulong)lastHandle + 1) * _granularity; + + int i = startHandle; + + while (i <= lastHandle) + { + RegionHandle handle = _handles[i]; + if (handle == null) + { + // Missing handle. A new handle must be created. + CreateHandle(i, lastHandle); + handle = _handles[i]; + } + + if (handle.EndAddress > endAddress) + { + // End address of handle is beyond the end of the search. Force a split. + SplitHandle(i, lastHandle + 1); + handle = _handles[i]; + } + + if (handle.Dirty) + { + rgSize += handle.Size; + handle.Reprotect(); + } + else + { + // Submit the region scanned so far as dirty + if (rgSize != 0) + { + modifiedAction(rgStart, rgSize); + rgSize = 0; + } + rgStart = handle.EndAddress; + } + + i += (int)(handle.Size / _granularity); + } + + if (rgSize != 0) + { + modifiedAction(rgStart, rgSize); + } + } + + public void QueryModified(ulong address, ulong size, Action modifiedAction, int sequenceNumber) + { + int startHandle = (int)((address - _address) / _granularity); + int lastHandle = (int)((address + (size - 1) - _address) / _granularity); + + ulong rgStart = _address + (ulong)startHandle * _granularity; + ulong rgSize = 0; + + ulong endAddress = _address + ((ulong)lastHandle + 1) * _granularity; + + int i = startHandle; + + while (i <= lastHandle) + { + RegionHandle handle = _handles[i]; + if (handle == null) + { + // Missing handle. A new handle must be created. + CreateHandle(i, lastHandle); + handle = _handles[i]; + } + + if (handle.EndAddress > endAddress) + { + // End address of handle is beyond the end of the search. Force a split. + SplitHandle(i, lastHandle + 1); + handle = _handles[i]; + } + + if (handle.Dirty && sequenceNumber != handle.SequenceNumber) + { + rgSize += handle.Size; + handle.Reprotect(); + } + else + { + // Submit the region scanned so far as dirty + if (rgSize != 0) + { + modifiedAction(rgStart, rgSize); + rgSize = 0; + } + rgStart = handle.EndAddress; + } + + handle.SequenceNumber = sequenceNumber; + + i += (int)(handle.Size / _granularity); + } + + if (rgSize != 0) + { + modifiedAction(rgStart, rgSize); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + foreach (var handle in _handles) + { + handle?.Dispose(); + } + } + } +} diff --git a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs new file mode 100644 index 00000000..35e9c2d9 --- /dev/null +++ b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs @@ -0,0 +1,154 @@ +using Ryujinx.Memory.Range; +using System.Collections.Generic; + +namespace Ryujinx.Memory.Tracking +{ + /// + /// A region of virtual memory. + /// + class VirtualRegion : AbstractRegion + { + public List Handles = new(); + + private readonly MemoryTracking _tracking; + private MemoryPermission _lastPermission; + + public bool Guest { get; } + + public VirtualRegion(MemoryTracking tracking, ulong address, ulong size, bool guest, MemoryPermission lastPermission = MemoryPermission.Invalid) : base(address, size) + { + _lastPermission = lastPermission; + _tracking = tracking; + + Guest = guest; + } + + /// + public override void Signal(ulong address, ulong size, bool write, int? exemptId) + { + IList handles = Handles; + + for (int i = 0; i < handles.Count; i++) + { + if (exemptId == null || handles[i].Id != exemptId.Value) + { + handles[i].Signal(address, size, write, ref handles); + } + } + + UpdateProtection(); + } + + /// + public override void SignalPrecise(ulong address, ulong size, bool write, int? exemptId) + { + IList handles = Handles; + + bool allPrecise = true; + + for (int i = 0; i < handles.Count; i++) + { + if (exemptId == null || handles[i].Id != exemptId.Value) + { + allPrecise &= handles[i].SignalPrecise(address, size, write, ref handles); + } + } + + // Only update protection if a regular signal handler was called. + // This allows precise actions to skip reprotection costs if they want (they can still do it manually). + if (!allPrecise) + { + UpdateProtection(); + } + } + + /// + /// Signal that this region has been mapped or unmapped. + /// + /// True if the region has been mapped, false if unmapped + public void SignalMappingChanged(bool mapped) + { + _lastPermission = MemoryPermission.Invalid; + + if (!Guest) + { + foreach (RegionHandle handle in Handles) + { + handle.SignalMappingChanged(mapped); + } + } + } + + /// + /// Gets the strictest permission that the child handles demand. Assumes that the tracking lock has been obtained. + /// + /// Protection level that this region demands + public MemoryPermission GetRequiredPermission() + { + // Start with Read/Write, each handle can strip off permissions as necessary. + // Assumes the tracking lock has already been obtained. + + MemoryPermission result = MemoryPermission.ReadAndWrite; + + foreach (var handle in Handles) + { + result &= handle.RequiredPermission; + if (result == 0) + { + return result; + } + } + return result; + } + + /// + /// Updates the protection for this virtual region. + /// + public bool UpdateProtection() + { + MemoryPermission permission = GetRequiredPermission(); + + if (_lastPermission != permission) + { + _tracking.ProtectVirtualRegion(this, permission, Guest); + _lastPermission = permission; + + return true; + } + + return false; + } + + /// + /// Removes a handle from this virtual region. If there are no handles left, this virtual region is removed. + /// + /// Handle to remove + public void RemoveHandle(RegionHandle handle) + { + lock (_tracking.TrackingLock) + { + Handles.Remove(handle); + UpdateProtection(); + if (Handles.Count == 0) + { + _tracking.RemoveVirtual(this); + } + } + } + + public override INonOverlappingRange Split(ulong splitAddress) + { + VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, Guest, _lastPermission); + Size = splitAddress - Address; + + // The new region inherits all of our parents. + newRegion.Handles = new List(Handles); + foreach (var parent in Handles) + { + parent.AddChild(newRegion); + } + + return newRegion; + } + } +} diff --git a/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs b/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs new file mode 100644 index 00000000..f4107224 --- /dev/null +++ b/src/Ryujinx.Memory/VirtualMemoryManagerBase.cs @@ -0,0 +1,405 @@ +using Ryujinx.Common.Memory; +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Memory +{ + public abstract class VirtualMemoryManagerBase : IWritableBlock + { + public const int PageBits = 12; + public const int PageSize = 1 << PageBits; + public const int PageMask = PageSize - 1; + + protected abstract ulong AddressSpaceSize { get; } + + public virtual ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySequence.Empty; + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, false); + } + + if (IsContiguousAndMapped(va, size)) + { + nuint pa = TranslateVirtualAddressUnchecked(va); + + return new ReadOnlySequence(GetPhysicalAddressMemory(pa, size)); + } + else + { + AssertValidAddressAndSize(va, size); + + int offset = 0, segmentSize; + + BytesReadOnlySequenceSegment first = null, last = null; + + if ((va & PageMask) != 0) + { + nuint pa = TranslateVirtualAddressChecked(va); + + segmentSize = Math.Min(size, PageSize - (int)(va & PageMask)); + + Memory memory = GetPhysicalAddressMemory(pa, segmentSize); + + first = last = new BytesReadOnlySequenceSegment(memory); + + offset += segmentSize; + } + + for (; offset < size; offset += segmentSize) + { + nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset); + + segmentSize = Math.Min(size - offset, PageSize); + + Memory memory = GetPhysicalAddressMemory(pa, segmentSize); + + if (first is null) + { + first = last = new BytesReadOnlySequenceSegment(memory); + } + else + { + if (last.IsContiguousWith(memory, out nuint contiguousStart, out int contiguousSize)) + { + last.Replace(GetPhysicalAddressMemory(contiguousStart, contiguousSize)); + } + else + { + last = last.Append(memory); + } + } + } + + return new ReadOnlySequence(first, 0, last, (int)(size - last.RunningIndex)); + } + } + + public virtual ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySpan.Empty; + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, false); + } + + if (IsContiguousAndMapped(va, size)) + { + nuint pa = TranslateVirtualAddressUnchecked(va); + + return GetPhysicalAddressSpan(pa, size); + } + else + { + Span data = new byte[size]; + + Read(va, data); + + return data; + } + } + + public virtual WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return new WritableRegion(null, va, Memory.Empty); + } + + if (tracked) + { + SignalMemoryTracking(va, (ulong)size, true); + } + + if (IsContiguousAndMapped(va, size)) + { + nuint pa = TranslateVirtualAddressUnchecked(va); + + return new WritableRegion(null, va, GetPhysicalAddressMemory(pa, size)); + } + else + { + MemoryOwner memoryOwner = MemoryOwner.Rent(size); + + Read(va, memoryOwner.Span); + + return new WritableRegion(this, va, memoryOwner); + } + } + + public abstract bool IsMapped(ulong va); + + public virtual void MapForeign(ulong va, nuint hostPointer, ulong size) + { + throw new NotSupportedException(); + } + + public virtual T Read(ulong va) where T : unmanaged + { + return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; + } + + public virtual void Read(ulong va, Span data) + { + if (data.Length == 0) + { + return; + } + + AssertValidAddressAndSize(va, data.Length); + + int offset = 0, size; + + if ((va & PageMask) != 0) + { + nuint pa = TranslateVirtualAddressChecked(va); + + size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); + + GetPhysicalAddressSpan(pa, size).CopyTo(data[..size]); + + offset += size; + } + + for (; offset < data.Length; offset += size) + { + nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset); + + size = Math.Min(data.Length - offset, PageSize); + + GetPhysicalAddressSpan(pa, size).CopyTo(data.Slice(offset, size)); + } + } + + public virtual T ReadTracked(ulong va) where T : unmanaged + { + SignalMemoryTracking(va, (ulong)Unsafe.SizeOf(), false); + + return Read(va); + } + + public virtual void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + { + // No default implementation + } + + public virtual void Write(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return; + } + + SignalMemoryTracking(va, (ulong)data.Length, true); + + WriteImpl(va, data); + } + + public virtual void Write(ulong va, T value) where T : unmanaged + { + Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); + } + + public virtual void WriteUntracked(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return; + } + + WriteImpl(va, data); + } + + public virtual bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return false; + } + + if (IsContiguousAndMapped(va, data.Length)) + { + SignalMemoryTracking(va, (ulong)data.Length, false); + + nuint pa = TranslateVirtualAddressChecked(va); + + var target = GetPhysicalAddressSpan(pa, data.Length); + + bool changed = !data.SequenceEqual(target); + + if (changed) + { + data.CopyTo(target); + } + + return changed; + } + else + { + Write(va, data); + + return true; + } + } + + /// + /// Ensures the combination of virtual address and size is part of the addressable space. + /// + /// Virtual address of the range + /// Size of the range in bytes + /// Throw when the memory region specified outside the addressable space + protected void AssertValidAddressAndSize(ulong va, ulong size) + { + if (!ValidateAddressAndSize(va, size)) + { + throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}"); + } + } + + /// + /// Ensures the combination of virtual address and size is part of the addressable space. + /// + /// Virtual address of the range + /// Size of the range in bytes + /// Throw when the memory region specified outside the addressable space + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void AssertValidAddressAndSize(ulong va, int size) + => AssertValidAddressAndSize(va, (ulong)size); + + /// + /// Computes the number of pages in a virtual address range. + /// + /// Virtual address of the range + /// Size of the range + /// The virtual address of the beginning of the first page + /// This function does not differentiate between allocated and unallocated pages. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static int GetPagesCount(ulong va, ulong size, out ulong startVa) + { + // WARNING: Always check if ulong does not overflow during the operations. + startVa = va & ~(ulong)PageMask; + ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; + + return (int)(vaSpan / PageSize); + } + + protected abstract Memory GetPhysicalAddressMemory(nuint pa, int size); + + protected abstract Span GetPhysicalAddressSpan(nuint pa, int size); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool IsContiguous(ulong va, int size) => IsContiguous(va, (ulong)size); + + protected virtual bool IsContiguous(ulong va, ulong size) + { + if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) + { + return false; + } + + int pages = GetPagesCount(va, size, out va); + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return false; + } + + if (TranslateVirtualAddressUnchecked(va) + PageSize != TranslateVirtualAddressUnchecked(va + PageSize)) + { + return false; + } + + va += PageSize; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool IsContiguousAndMapped(ulong va, int size) + => IsContiguous(va, size) && IsMapped(va); + + protected abstract nuint TranslateVirtualAddressChecked(ulong va); + + protected abstract nuint TranslateVirtualAddressUnchecked(ulong va); + + /// + /// Checks if the virtual address is part of the addressable space. + /// + /// Virtual address + /// True if the virtual address is part of the addressable space + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected bool ValidateAddress(ulong va) + { + return va < AddressSpaceSize; + } + + /// + /// Checks if the combination of virtual address and size is part of the addressable space. + /// + /// Virtual address of the range + /// Size of the range in bytes + /// True if the combination of virtual address and size is part of the addressable space + protected bool ValidateAddressAndSize(ulong va, ulong size) + { + ulong endVa = va + size; + return endVa >= va && endVa >= size && endVa <= AddressSpaceSize; + } + + protected static void ThrowInvalidMemoryRegionException(string message) + => throw new InvalidMemoryRegionException(message); + + protected static void ThrowMemoryNotContiguous() + => throw new MemoryNotContiguousException(); + + protected virtual void WriteImpl(ulong va, ReadOnlySpan data) + { + AssertValidAddressAndSize(va, data.Length); + + if (IsContiguousAndMapped(va, data.Length)) + { + nuint pa = TranslateVirtualAddressUnchecked(va); + + data.CopyTo(GetPhysicalAddressSpan(pa, data.Length)); + } + else + { + int offset = 0, size; + + if ((va & PageMask) != 0) + { + nuint pa = TranslateVirtualAddressChecked(va); + + size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); + + data[..size].CopyTo(GetPhysicalAddressSpan(pa, size)); + + offset += size; + } + + for (; offset < data.Length; offset += size) + { + nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset); + + size = Math.Min(data.Length - offset, PageSize); + + data.Slice(offset, size).CopyTo(GetPhysicalAddressSpan(pa, size)); + } + } + } + + } +} diff --git a/src/Ryujinx.Memory/WindowsShared/MappingTree.cs b/src/Ryujinx.Memory/WindowsShared/MappingTree.cs new file mode 100644 index 00000000..9ca84b56 --- /dev/null +++ b/src/Ryujinx.Memory/WindowsShared/MappingTree.cs @@ -0,0 +1,87 @@ +using Ryujinx.Common.Collections; +using System; + +namespace Ryujinx.Memory.WindowsShared +{ + /// + /// A intrusive Red-Black Tree that also supports getting nodes overlapping a given range. + /// + /// Type of the value stored on the node + class MappingTree : IntrusiveRedBlackTree> + { + private const int ArrayGrowthSize = 16; + + public int GetNodes(ulong start, ulong end, ref RangeNode[] overlaps, int overlapCount = 0) + { + RangeNode node = this.GetNodeByKey(start); + + for (; node != null; node = node.Successor) + { + if (overlaps.Length <= overlapCount) + { + Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize); + } + + overlaps[overlapCount++] = node; + + if (node.End >= end) + { + break; + } + } + + return overlapCount; + } + } + + class RangeNode : IntrusiveRedBlackTreeNode>, IComparable>, IComparable + { + public ulong Start { get; } + public ulong End { get; private set; } + public T Value { get; } + + public RangeNode(ulong start, ulong end, T value) + { + Start = start; + End = end; + Value = value; + } + + public void Extend(ulong sizeDelta) + { + End += sizeDelta; + } + + public int CompareTo(RangeNode other) + { + if (Start < other.Start) + { + return -1; + } + else if (Start <= other.End - 1UL) + { + return 0; + } + else + { + return 1; + } + } + + public int CompareTo(ulong address) + { + if (address < Start) + { + return 1; + } + else if (address <= End - 1UL) + { + return 0; + } + else + { + return -1; + } + } + } +} diff --git a/src/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs b/src/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs new file mode 100644 index 00000000..b68a076c --- /dev/null +++ b/src/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs @@ -0,0 +1,736 @@ +using Ryujinx.Common.Collections; +using Ryujinx.Common.Memory.PartialUnmaps; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; +using System.Threading; + +namespace Ryujinx.Memory.WindowsShared +{ + /// + /// Windows memory placeholder manager. + /// + [SupportedOSPlatform("windows")] + class PlaceholderManager + { + private const int InitialOverlapsSize = 10; + + private readonly MappingTree _mappings; + private readonly MappingTree _protections; + private readonly IntPtr _partialUnmapStatePtr; + private readonly Thread _partialUnmapTrimThread; + + /// + /// Creates a new instance of the Windows memory placeholder manager. + /// + public PlaceholderManager() + { + _mappings = new MappingTree(); + _protections = new MappingTree(); + + _partialUnmapStatePtr = PartialUnmapState.GlobalState; + + _partialUnmapTrimThread = new Thread(TrimThreadLocalMapLoop) + { + Name = "CPU.PartialUnmapTrimThread", + IsBackground = true, + }; + _partialUnmapTrimThread.Start(); + } + + /// + /// Gets a reference to the partial unmap state struct. + /// + /// A reference to the partial unmap state struct + private unsafe ref PartialUnmapState GetPartialUnmapState() + { + return ref Unsafe.AsRef((void*)_partialUnmapStatePtr); + } + + /// + /// Trims inactive threads from the partial unmap state's thread mapping every few seconds. + /// Should be run in a Background thread so that it doesn't stop the program from closing. + /// + private void TrimThreadLocalMapLoop() + { + while (true) + { + Thread.Sleep(2000); + GetPartialUnmapState().TrimThreads(); + } + } + + /// + /// Reserves a range of the address space to be later mapped as shared memory views. + /// + /// Start address of the region to reserve + /// Size in bytes of the region to reserve + public void ReserveRange(ulong address, ulong size) + { + lock (_mappings) + { + _mappings.Add(new RangeNode(address, address + size, ulong.MaxValue)); + } + + lock (_protections) + { + _protections.Add(new RangeNode(address, address + size, MemoryPermission.None)); + } + } + + /// + /// Unreserves a range of memory that has been previously reserved with . + /// + /// Start address of the region to unreserve + /// Size in bytes of the region to unreserve + /// Thrown when the Windows API returns an error unreserving the memory + public void UnreserveRange(ulong address, ulong size) + { + ulong endAddress = address + size; + + lock (_mappings) + { + RangeNode node = _mappings.GetNodeByKey(address); + RangeNode successorNode; + + for (; node != null; node = successorNode) + { + successorNode = node.Successor; + + if (IsMapped(node.Value)) + { + if (!WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)node.Start, 2)) + { + throw new WindowsApiException("UnmapViewOfFile2"); + } + } + + _mappings.Remove(node); + + if (node.End >= endAddress) + { + break; + } + } + } + + RemoveProtection(address, size); + } + + /// + /// Maps a shared memory view on a previously reserved memory region. + /// + /// Shared memory that will be the backing storage for the view + /// Offset in the shared memory to map + /// Address to map the view into + /// Size of the view in bytes + /// Memory block that owns the mapping + public void MapView(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size, MemoryBlock owner) + { + ref var partialUnmapLock = ref GetPartialUnmapState().PartialUnmapLock; + partialUnmapLock.AcquireReaderLock(); + + try + { + UnmapViewInternal(sharedMemory, location, size, owner, updateProtection: false); + MapViewInternal(sharedMemory, srcOffset, location, size, updateProtection: true); + } + finally + { + partialUnmapLock.ReleaseReaderLock(); + } + } + + /// + /// Maps a shared memory view on a previously reserved memory region. + /// + /// Shared memory that will be the backing storage for the view + /// Offset in the shared memory to map + /// Address to map the view into + /// Size of the view in bytes + /// Indicates if the memory protections should be updated after the map + /// Thrown when the Windows API returns an error mapping the memory + private void MapViewInternal(IntPtr sharedMemory, ulong srcOffset, IntPtr location, IntPtr size, bool updateProtection) + { + SplitForMap((ulong)location, (ulong)size, srcOffset); + + var ptr = WindowsApi.MapViewOfFile3( + sharedMemory, + WindowsApi.CurrentProcessHandle, + location, + srcOffset, + size, + 0x4000, + MemoryProtection.ReadWrite, + IntPtr.Zero, + 0); + + if (ptr == IntPtr.Zero) + { + throw new WindowsApiException("MapViewOfFile3"); + } + + if (updateProtection) + { + UpdateProtection((ulong)location, (ulong)size, MemoryPermission.ReadAndWrite); + } + } + + /// + /// Splits a larger placeholder, slicing at the start and end address, for a new memory mapping. + /// + /// Address to split + /// Size of the new region + /// Offset in the shared memory that will be mapped + private void SplitForMap(ulong address, ulong size, ulong backingOffset) + { + ulong endAddress = address + size; + + var overlaps = new RangeNode[InitialOverlapsSize]; + + lock (_mappings) + { + int count = _mappings.GetNodes(address, endAddress, ref overlaps); + + Debug.Assert(count == 1); + Debug.Assert(!IsMapped(overlaps[0].Value)); + + var overlap = overlaps[0]; + + ulong overlapStart = overlap.Start; + ulong overlapEnd = overlap.End; + ulong overlapValue = overlap.Value; + + _mappings.Remove(overlap); + + bool overlapStartsBefore = overlapStart < address; + bool overlapEndsAfter = overlapEnd > endAddress; + + if (overlapStartsBefore && overlapEndsAfter) + { + CheckFreeResult(WindowsApi.VirtualFree( + (IntPtr)address, + (IntPtr)size, + AllocationType.Release | AllocationType.PreservePlaceholder)); + + _mappings.Add(new RangeNode(overlapStart, address, overlapValue)); + _mappings.Add(new RangeNode(endAddress, overlapEnd, AddBackingOffset(overlapValue, endAddress - overlapStart))); + } + else if (overlapStartsBefore) + { + ulong overlappedSize = overlapEnd - address; + + CheckFreeResult(WindowsApi.VirtualFree( + (IntPtr)address, + (IntPtr)overlappedSize, + AllocationType.Release | AllocationType.PreservePlaceholder)); + + _mappings.Add(new RangeNode(overlapStart, address, overlapValue)); + } + else if (overlapEndsAfter) + { + ulong overlappedSize = endAddress - overlapStart; + + CheckFreeResult(WindowsApi.VirtualFree( + (IntPtr)overlapStart, + (IntPtr)overlappedSize, + AllocationType.Release | AllocationType.PreservePlaceholder)); + + _mappings.Add(new RangeNode(endAddress, overlapEnd, AddBackingOffset(overlapValue, overlappedSize))); + } + + _mappings.Add(new RangeNode(address, endAddress, backingOffset)); + } + } + + /// + /// Unmaps a view that has been previously mapped with . + /// + /// + /// For "partial unmaps" (when not the entire mapped range is being unmapped), it might be + /// necessary to unmap the whole range and then remap the sub-ranges that should remain mapped. + /// + /// Shared memory that the view being unmapped belongs to + /// Address to unmap + /// Size of the region to unmap in bytes + /// Memory block that owns the mapping + public void UnmapView(IntPtr sharedMemory, IntPtr location, IntPtr size, MemoryBlock owner) + { + ref var partialUnmapLock = ref GetPartialUnmapState().PartialUnmapLock; + partialUnmapLock.AcquireReaderLock(); + + try + { + UnmapViewInternal(sharedMemory, location, size, owner, updateProtection: true); + } + finally + { + partialUnmapLock.ReleaseReaderLock(); + } + } + + /// + /// Unmaps a view that has been previously mapped with . + /// + /// + /// For "partial unmaps" (when not the entire mapped range is being unmapped), it might be + /// necessary to unmap the whole range and then remap the sub-ranges that should remain mapped. + /// + /// Shared memory that the view being unmapped belongs to + /// Address to unmap + /// Size of the region to unmap in bytes + /// Memory block that owns the mapping + /// Indicates if the memory protections should be updated after the unmap + /// Thrown when the Windows API returns an error unmapping or remapping the memory + private void UnmapViewInternal(IntPtr sharedMemory, IntPtr location, IntPtr size, MemoryBlock owner, bool updateProtection) + { + ulong startAddress = (ulong)location; + ulong unmapSize = (ulong)size; + ulong endAddress = startAddress + unmapSize; + + var overlaps = new RangeNode[InitialOverlapsSize]; + int count; + + lock (_mappings) + { + count = _mappings.GetNodes(startAddress, endAddress, ref overlaps); + } + + for (int index = 0; index < count; index++) + { + var overlap = overlaps[index]; + + if (IsMapped(overlap.Value)) + { + lock (_mappings) + { + _mappings.Remove(overlap); + _mappings.Add(new RangeNode(overlap.Start, overlap.End, ulong.MaxValue)); + } + + bool overlapStartsBefore = overlap.Start < startAddress; + bool overlapEndsAfter = overlap.End > endAddress; + + if (overlapStartsBefore || overlapEndsAfter) + { + // If the overlap extends beyond the region we are unmapping, + // then we need to re-map the regions that are supposed to remain mapped. + // This is necessary because Windows does not support partial view unmaps. + // That is, you can only fully unmap a view that was previously mapped, you can't just unmap a chunck of it. + + ref var partialUnmapState = ref GetPartialUnmapState(); + ref var partialUnmapLock = ref partialUnmapState.PartialUnmapLock; + partialUnmapLock.UpgradeToWriterLock(); + + try + { + partialUnmapState.PartialUnmapsCount++; + + if (!WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)overlap.Start, 2)) + { + throw new WindowsApiException("UnmapViewOfFile2"); + } + + if (overlapStartsBefore) + { + ulong remapSize = startAddress - overlap.Start; + + MapViewInternal(sharedMemory, overlap.Value, (IntPtr)overlap.Start, (IntPtr)remapSize, updateProtection: false); + RestoreRangeProtection(overlap.Start, remapSize); + } + + if (overlapEndsAfter) + { + ulong overlappedSize = endAddress - overlap.Start; + ulong remapBackingOffset = overlap.Value + overlappedSize; + ulong remapAddress = overlap.Start + overlappedSize; + ulong remapSize = overlap.End - endAddress; + + MapViewInternal(sharedMemory, remapBackingOffset, (IntPtr)remapAddress, (IntPtr)remapSize, updateProtection: false); + RestoreRangeProtection(remapAddress, remapSize); + } + } + finally + { + partialUnmapLock.DowngradeFromWriterLock(); + } + } + else if (!WindowsApi.UnmapViewOfFile2(WindowsApi.CurrentProcessHandle, (IntPtr)overlap.Start, 2)) + { + throw new WindowsApiException("UnmapViewOfFile2"); + } + } + } + + CoalesceForUnmap(startAddress, unmapSize, owner); + + if (updateProtection) + { + UpdateProtection(startAddress, unmapSize, MemoryPermission.None); + } + } + + /// + /// Coalesces adjacent placeholders after unmap. + /// + /// Address of the region that was unmapped + /// Size of the region that was unmapped in bytes + /// Memory block that owns the mapping + private void CoalesceForUnmap(ulong address, ulong size, MemoryBlock owner) + { + ulong endAddress = address + size; + ulong blockAddress = (ulong)owner.Pointer; + ulong blockEnd = blockAddress + owner.Size; + int unmappedCount = 0; + + lock (_mappings) + { + RangeNode node = _mappings.GetNodeByKey(address); + + if (node == null) + { + // Nothing to coalesce if we have no overlaps. + return; + } + + RangeNode predecessor = node.Predecessor; + RangeNode successor = null; + + for (; node != null; node = successor) + { + successor = node.Successor; + var overlap = node; + + if (!IsMapped(overlap.Value)) + { + address = Math.Min(address, overlap.Start); + endAddress = Math.Max(endAddress, overlap.End); + + _mappings.Remove(overlap); + unmappedCount++; + } + + if (node.End >= endAddress) + { + break; + } + } + + if (predecessor != null && !IsMapped(predecessor.Value) && predecessor.Start >= blockAddress) + { + address = Math.Min(address, predecessor.Start); + + _mappings.Remove(predecessor); + unmappedCount++; + } + + if (successor != null && !IsMapped(successor.Value) && successor.End <= blockEnd) + { + endAddress = Math.Max(endAddress, successor.End); + + _mappings.Remove(successor); + unmappedCount++; + } + + _mappings.Add(new RangeNode(address, endAddress, ulong.MaxValue)); + } + + if (unmappedCount > 1) + { + size = endAddress - address; + + CheckFreeResult(WindowsApi.VirtualFree( + (IntPtr)address, + (IntPtr)size, + AllocationType.Release | AllocationType.CoalescePlaceholders)); + } + } + + /// + /// Reprotects a region of memory that has been mapped. + /// + /// Address of the region to reprotect + /// Size of the region to reprotect in bytes + /// New permissions + /// True if the reprotection was successful, false otherwise + public bool ReprotectView(IntPtr address, IntPtr size, MemoryPermission permission) + { + ref var partialUnmapLock = ref GetPartialUnmapState().PartialUnmapLock; + partialUnmapLock.AcquireReaderLock(); + + try + { + return ReprotectViewInternal(address, size, permission, false); + } + finally + { + partialUnmapLock.ReleaseReaderLock(); + } + } + + /// + /// Reprotects a region of memory that has been mapped. + /// + /// Address of the region to reprotect + /// Size of the region to reprotect in bytes + /// New permissions + /// Throw an exception instead of returning an error if the operation fails + /// True if the reprotection was successful or if is true, false otherwise + /// If is true, it is thrown when the Windows API returns an error reprotecting the memory + private bool ReprotectViewInternal(IntPtr address, IntPtr size, MemoryPermission permission, bool throwOnError) + { + ulong reprotectAddress = (ulong)address; + ulong reprotectSize = (ulong)size; + ulong endAddress = reprotectAddress + reprotectSize; + + bool success = true; + + lock (_mappings) + { + RangeNode node = _mappings.GetNodeByKey(reprotectAddress); + RangeNode successorNode; + + for (; node != null; node = successorNode) + { + successorNode = node.Successor; + var overlap = node; + + ulong mappedAddress = overlap.Start; + ulong mappedSize = overlap.End - overlap.Start; + + if (mappedAddress < reprotectAddress) + { + ulong delta = reprotectAddress - mappedAddress; + mappedAddress = reprotectAddress; + mappedSize -= delta; + } + + ulong mappedEndAddress = mappedAddress + mappedSize; + + if (mappedEndAddress > endAddress) + { + ulong delta = mappedEndAddress - endAddress; + mappedSize -= delta; + } + + if (!WindowsApi.VirtualProtect((IntPtr)mappedAddress, (IntPtr)mappedSize, WindowsApi.GetProtection(permission), out _)) + { + if (throwOnError) + { + throw new WindowsApiException("VirtualProtect"); + } + + success = false; + } + + if (node.End >= endAddress) + { + break; + } + } + } + + UpdateProtection(reprotectAddress, reprotectSize, permission); + + return success; + } + + /// + /// Checks the result of a VirtualFree operation, throwing if needed. + /// + /// Operation result + /// Thrown if is false + private static void CheckFreeResult(bool success) + { + if (!success) + { + throw new WindowsApiException("VirtualFree"); + } + } + + /// + /// Adds an offset to a backing offset. This will do nothing if the backing offset is the special "unmapped" value. + /// + /// Backing offset + /// Offset to be added + /// Added offset or just if the region is unmapped + private static ulong AddBackingOffset(ulong backingOffset, ulong offset) + { + if (backingOffset == ulong.MaxValue) + { + return backingOffset; + } + + return backingOffset + offset; + } + + /// + /// Checks if a region is unmapped. + /// + /// Backing offset to check + /// True if the backing offset is the special "unmapped" value, false otherwise + private static bool IsMapped(ulong backingOffset) + { + return backingOffset != ulong.MaxValue; + } + + /// + /// Adds a protection to the list of protections. + /// + /// Address of the protected region + /// Size of the protected region in bytes + /// Memory permissions of the region + private void UpdateProtection(ulong address, ulong size, MemoryPermission permission) + { + ulong endAddress = address + size; + + lock (_protections) + { + RangeNode node = _protections.GetNodeByKey(address); + + if (node != null && + node.Start <= address && + node.End >= endAddress && + node.Value == permission) + { + return; + } + + RangeNode successorNode; + + ulong startAddress = address; + + for (; node != null; node = successorNode) + { + successorNode = node.Successor; + var protection = node; + + ulong protAddress = protection.Start; + ulong protEndAddress = protection.End; + MemoryPermission protPermission = protection.Value; + + _protections.Remove(protection); + + if (protPermission == permission) + { + if (startAddress > protAddress) + { + startAddress = protAddress; + } + + if (endAddress < protEndAddress) + { + endAddress = protEndAddress; + } + } + else + { + if (startAddress > protAddress) + { + _protections.Add(new RangeNode(protAddress, startAddress, protPermission)); + } + + if (endAddress < protEndAddress) + { + _protections.Add(new RangeNode(endAddress, protEndAddress, protPermission)); + } + } + + if (node.End >= endAddress) + { + break; + } + } + + _protections.Add(new RangeNode(startAddress, endAddress, permission)); + } + } + + /// + /// Removes protection from the list of protections. + /// + /// Address of the protected region + /// Size of the protected region in bytes + private void RemoveProtection(ulong address, ulong size) + { + ulong endAddress = address + size; + + lock (_protections) + { + RangeNode node = _protections.GetNodeByKey(address); + RangeNode successorNode; + + for (; node != null; node = successorNode) + { + successorNode = node.Successor; + var protection = node; + + ulong protAddress = protection.Start; + ulong protEndAddress = protection.End; + MemoryPermission protPermission = protection.Value; + + _protections.Remove(protection); + + if (address > protAddress) + { + _protections.Add(new RangeNode(protAddress, address, protPermission)); + } + + if (endAddress < protEndAddress) + { + _protections.Add(new RangeNode(endAddress, protEndAddress, protPermission)); + } + + if (node.End >= endAddress) + { + break; + } + } + } + } + + /// + /// Restores the protection of a given memory region that was remapped, using the protections list. + /// + /// Address of the remapped region + /// Size of the remapped region in bytes + private void RestoreRangeProtection(ulong address, ulong size) + { + ulong endAddress = address + size; + var overlaps = new RangeNode[InitialOverlapsSize]; + int count; + + lock (_protections) + { + count = _protections.GetNodes(address, endAddress, ref overlaps); + } + + for (int index = 0; index < count; index++) + { + var protection = overlaps[index]; + + // If protection is R/W we don't need to reprotect as views are initially mapped as R/W. + if (protection.Value == MemoryPermission.ReadAndWrite) + { + continue; + } + + ulong protAddress = protection.Start; + ulong protEndAddress = protection.End; + + if (protAddress < address) + { + protAddress = address; + } + + if (protEndAddress > endAddress) + { + protEndAddress = endAddress; + } + + ReprotectViewInternal((IntPtr)protAddress, (IntPtr)(protEndAddress - protAddress), protection.Value, true); + } + } + } +} diff --git a/src/Ryujinx.Memory/WindowsShared/WindowsApi.cs b/src/Ryujinx.Memory/WindowsShared/WindowsApi.cs new file mode 100644 index 00000000..82903c05 --- /dev/null +++ b/src/Ryujinx.Memory/WindowsShared/WindowsApi.cs @@ -0,0 +1,103 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Memory.WindowsShared +{ + [SupportedOSPlatform("windows")] + static partial class WindowsApi + { + public static readonly IntPtr InvalidHandleValue = new(-1); + public static readonly IntPtr CurrentProcessHandle = new(-1); + + [LibraryImport("kernel32.dll", SetLastError = true)] + public static partial IntPtr VirtualAlloc( + IntPtr lpAddress, + IntPtr dwSize, + AllocationType flAllocationType, + MemoryProtection flProtect); + + [LibraryImport("KernelBase.dll", SetLastError = true)] + public static partial IntPtr VirtualAlloc2( + IntPtr process, + IntPtr lpAddress, + IntPtr dwSize, + AllocationType flAllocationType, + MemoryProtection flProtect, + IntPtr extendedParameters, + ulong parameterCount); + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool VirtualProtect( + IntPtr lpAddress, + IntPtr dwSize, + MemoryProtection flNewProtect, + out MemoryProtection lpflOldProtect); + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool VirtualFree(IntPtr lpAddress, IntPtr dwSize, AllocationType dwFreeType); + + [LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "CreateFileMappingW")] + public static partial IntPtr CreateFileMapping( + IntPtr hFile, + IntPtr lpFileMappingAttributes, + FileMapProtection flProtect, + uint dwMaximumSizeHigh, + uint dwMaximumSizeLow, + [MarshalAs(UnmanagedType.LPWStr)] string lpName); + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool CloseHandle(IntPtr hObject); + + [LibraryImport("kernel32.dll", SetLastError = true)] + public static partial IntPtr MapViewOfFile( + IntPtr hFileMappingObject, + uint dwDesiredAccess, + uint dwFileOffsetHigh, + uint dwFileOffsetLow, + IntPtr dwNumberOfBytesToMap); + + [LibraryImport("KernelBase.dll", SetLastError = true)] + public static partial IntPtr MapViewOfFile3( + IntPtr hFileMappingObject, + IntPtr process, + IntPtr baseAddress, + ulong offset, + IntPtr dwNumberOfBytesToMap, + ulong allocationType, + MemoryProtection dwDesiredAccess, + IntPtr extendedParameters, + ulong parameterCount); + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool UnmapViewOfFile(IntPtr lpBaseAddress); + + [LibraryImport("KernelBase.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool UnmapViewOfFile2(IntPtr process, IntPtr lpBaseAddress, ulong unmapFlags); + + [LibraryImport("kernel32.dll")] + public static partial uint GetLastError(); + + [LibraryImport("kernel32.dll")] + public static partial int GetCurrentThreadId(); + + public static MemoryProtection GetProtection(MemoryPermission permission) + { + return permission switch + { + MemoryPermission.None => MemoryProtection.NoAccess, + MemoryPermission.Read => MemoryProtection.ReadOnly, + MemoryPermission.ReadAndWrite => MemoryProtection.ReadWrite, + MemoryPermission.ReadAndExecute => MemoryProtection.ExecuteRead, + MemoryPermission.ReadWriteExecute => MemoryProtection.ExecuteReadWrite, + MemoryPermission.Execute => MemoryProtection.Execute, + _ => throw new MemoryProtectionException(permission), + }; + } + } +} diff --git a/src/Ryujinx.Memory/WindowsShared/WindowsApiException.cs b/src/Ryujinx.Memory/WindowsShared/WindowsApiException.cs new file mode 100644 index 00000000..308006ab --- /dev/null +++ b/src/Ryujinx.Memory/WindowsShared/WindowsApiException.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.Versioning; + +namespace Ryujinx.Memory.WindowsShared +{ + [SupportedOSPlatform("windows")] + class WindowsApiException : Exception + { + public WindowsApiException() + { + } + + public WindowsApiException(string functionName) : base(CreateMessage(functionName)) + { + } + + public WindowsApiException(string functionName, Exception inner) : base(CreateMessage(functionName), inner) + { + } + + private static string CreateMessage(string functionName) + { + return $"{functionName} returned error code 0x{WindowsApi.GetLastError():X}."; + } + } +} diff --git a/src/Ryujinx.Memory/WindowsShared/WindowsFlags.cs b/src/Ryujinx.Memory/WindowsShared/WindowsFlags.cs new file mode 100644 index 00000000..6effff74 --- /dev/null +++ b/src/Ryujinx.Memory/WindowsShared/WindowsFlags.cs @@ -0,0 +1,52 @@ +using System; + +namespace Ryujinx.Memory.WindowsShared +{ + [Flags] + enum AllocationType : uint + { + CoalescePlaceholders = 0x1, + PreservePlaceholder = 0x2, + Commit = 0x1000, + Reserve = 0x2000, + Decommit = 0x4000, + ReplacePlaceholder = Decommit, + Release = 0x8000, + ReservePlaceholder = 0x40000, + Reset = 0x80000, + Physical = 0x400000, + TopDown = 0x100000, + WriteWatch = 0x200000, + LargePages = 0x20000000, + } + + [Flags] + enum MemoryProtection : uint + { + NoAccess = 0x01, + ReadOnly = 0x02, + ReadWrite = 0x04, + WriteCopy = 0x08, + Execute = 0x10, + ExecuteRead = 0x20, + ExecuteReadWrite = 0x40, + ExecuteWriteCopy = 0x80, + GuardModifierflag = 0x100, + NoCacheModifierflag = 0x200, + WriteCombineModifierflag = 0x400, + } + + [Flags] + enum FileMapProtection : uint + { + PageReadonly = 0x02, + PageReadWrite = 0x04, + PageWriteCopy = 0x08, + PageExecuteRead = 0x20, + PageExecuteReadWrite = 0x40, + SectionCommit = 0x8000000, + SectionImage = 0x1000000, + SectionNoCache = 0x10000000, + SectionReserve = 0x4000000, + } +} diff --git a/src/Ryujinx.Memory/WritableRegion.cs b/src/Ryujinx.Memory/WritableRegion.cs new file mode 100644 index 00000000..2c21ef4e --- /dev/null +++ b/src/Ryujinx.Memory/WritableRegion.cs @@ -0,0 +1,48 @@ +using System; +using System.Buffers; + +namespace Ryujinx.Memory +{ + public sealed class WritableRegion : IDisposable + { + private readonly IWritableBlock _block; + private readonly ulong _va; + private readonly IMemoryOwner _memoryOwner; + private readonly bool _tracked; + + private bool NeedsWriteback => _block != null; + + public Memory Memory { get; } + + public WritableRegion(IWritableBlock block, ulong va, Memory memory, bool tracked = false) + { + _block = block; + _va = va; + _tracked = tracked; + Memory = memory; + } + + public WritableRegion(IWritableBlock block, ulong va, IMemoryOwner memoryOwner, bool tracked = false) + : this(block, va, memoryOwner.Memory, tracked) + { + _memoryOwner = memoryOwner; + } + + public void Dispose() + { + if (NeedsWriteback) + { + if (_tracked) + { + _block.Write(_va, Memory.Span); + } + else + { + _block.WriteUntracked(_va, Memory.Span); + } + } + + _memoryOwner?.Dispose(); + } + } +} diff --git a/src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj b/src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj new file mode 100644 index 00000000..8e795304 --- /dev/null +++ b/src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + + + + + + + + + + + diff --git a/src/Ryujinx.SDL2.Common/SDL2Driver.cs b/src/Ryujinx.SDL2.Common/SDL2Driver.cs new file mode 100644 index 00000000..ed6d9419 --- /dev/null +++ b/src/Ryujinx.SDL2.Common/SDL2Driver.cs @@ -0,0 +1,208 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using static SDL2.SDL; + +namespace Ryujinx.SDL2.Common +{ + public class SDL2Driver : IDisposable + { + private static SDL2Driver _instance; + + public static SDL2Driver Instance + { + get + { + _instance ??= new SDL2Driver(); + + return _instance; + } + } + + public static Action MainThreadDispatcher { get; set; } + + private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO; + + private bool _isRunning; + private uint _refereceCount; + private Thread _worker; + + public event Action OnJoyStickConnected; + public event Action OnJoystickDisconnected; + + private ConcurrentDictionary> _registeredWindowHandlers; + + private readonly object _lock = new(); + + private SDL2Driver() { } + + private const string SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS = "SDL_JOYSTICK_HIDAPI_COMBINE_JOY_CONS"; + + public void Initialize() + { + lock (_lock) + { + _refereceCount++; + + if (_isRunning) + { + return; + } + + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); + SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"); + + + // NOTE: As of SDL2 2.24.0, joycons are combined by default but the motion source only come from one of them. + // We disable this behavior for now. + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS, "0"); + + if (SDL_Init(SdlInitFlags) != 0) + { + string errorMessage = $"SDL2 initialization failed with error \"{SDL_GetError()}\""; + + Logger.Error?.Print(LogClass.Application, errorMessage); + + throw new Exception(errorMessage); + } + + // First ensure that we only enable joystick events (for connected/disconnected). + if (SDL_GameControllerEventState(SDL_IGNORE) != SDL_IGNORE) + { + Logger.Error?.PrintMsg(LogClass.Application, "Couldn't change the state of game controller events."); + } + + if (SDL_JoystickEventState(SDL_ENABLE) < 0) + { + Logger.Error?.PrintMsg(LogClass.Application, $"Failed to enable joystick event polling: {SDL_GetError()}"); + } + + // Disable all joysticks information, we don't need them no need to flood the event queue for that. + SDL_EventState(SDL_EventType.SDL_JOYAXISMOTION, SDL_DISABLE); + SDL_EventState(SDL_EventType.SDL_JOYBALLMOTION, SDL_DISABLE); + SDL_EventState(SDL_EventType.SDL_JOYHATMOTION, SDL_DISABLE); + SDL_EventState(SDL_EventType.SDL_JOYBUTTONDOWN, SDL_DISABLE); + SDL_EventState(SDL_EventType.SDL_JOYBUTTONUP, SDL_DISABLE); + + SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE); + + string gamepadDbPath = Path.Combine(AppDataManager.BaseDirPath, "SDL_GameControllerDB.txt"); + + if (File.Exists(gamepadDbPath)) + { + SDL_GameControllerAddMappingsFromFile(gamepadDbPath); + } + + _registeredWindowHandlers = new ConcurrentDictionary>(); + _worker = new Thread(EventWorker); + _isRunning = true; + _worker.Start(); + } + } + + public bool RegisterWindow(uint windowId, Action windowEventHandler) + { + return _registeredWindowHandlers.TryAdd(windowId, windowEventHandler); + } + + public void UnregisterWindow(uint windowId) + { + _registeredWindowHandlers.Remove(windowId, out _); + } + + private void HandleSDLEvent(ref SDL_Event evnt) + { + if (evnt.type == SDL_EventType.SDL_JOYDEVICEADDED) + { + int deviceId = evnt.cbutton.which; + + // SDL2 loves to be inconsistent here by providing the device id instead of the instance id (like on removed event), as such we just grab it and send it inside our system. + int instanceId = SDL_JoystickGetDeviceInstanceID(deviceId); + + if (instanceId == -1) + { + return; + } + + Logger.Debug?.Print(LogClass.Application, $"Added joystick instance id {instanceId}"); + + OnJoyStickConnected?.Invoke(deviceId, instanceId); + } + else if (evnt.type == SDL_EventType.SDL_JOYDEVICEREMOVED) + { + Logger.Debug?.Print(LogClass.Application, $"Removed joystick instance id {evnt.cbutton.which}"); + + OnJoystickDisconnected?.Invoke(evnt.cbutton.which); + } + else if (evnt.type == SDL_EventType.SDL_WINDOWEVENT || evnt.type == SDL_EventType.SDL_MOUSEBUTTONDOWN || evnt.type == SDL_EventType.SDL_MOUSEBUTTONUP) + { + if (_registeredWindowHandlers.TryGetValue(evnt.window.windowID, out Action handler)) + { + handler(evnt); + } + } + } + + private void EventWorker() + { + const int WaitTimeMs = 10; + + using ManualResetEventSlim waitHandle = new(false); + + while (_isRunning) + { + MainThreadDispatcher?.Invoke(() => + { + while (SDL_PollEvent(out SDL_Event evnt) != 0) + { + HandleSDLEvent(ref evnt); + } + }); + + waitHandle.Wait(WaitTimeMs); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!disposing) + { + return; + } + + lock (_lock) + { + if (_isRunning) + { + _refereceCount--; + + if (_refereceCount == 0) + { + _isRunning = false; + + _worker?.Join(); + + SDL_Quit(); + + OnJoyStickConnected = null; + OnJoystickDisconnected = null; + } + } + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + } +} diff --git a/src/Ryujinx.ShaderTools/Program.cs b/src/Ryujinx.ShaderTools/Program.cs new file mode 100644 index 00000000..a84d7b46 --- /dev/null +++ b/src/Ryujinx.ShaderTools/Program.cs @@ -0,0 +1,159 @@ +using CommandLine; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.ShaderTools +{ + class Program + { + private class GpuAccessor : IGpuAccessor + { + private const int DefaultArrayLength = 32; + + private readonly byte[] _data; + + private int _texturesCount; + private int _imagesCount; + + public GpuAccessor(byte[] data) + { + _data = data; + _texturesCount = 0; + _imagesCount = 0; + } + + public SetBindingPair CreateConstantBufferBinding(int index) + { + return new SetBindingPair(0, index + 1); + } + + public SetBindingPair CreateImageBinding(int count, bool isBuffer) + { + int binding = _imagesCount; + + _imagesCount += count; + + return new SetBindingPair(3, binding); + } + + public SetBindingPair CreateStorageBufferBinding(int index) + { + return new SetBindingPair(1, index); + } + + public SetBindingPair CreateTextureBinding(int count, bool isBuffer) + { + int binding = _texturesCount; + + _texturesCount += count; + + return new SetBindingPair(2, binding); + } + + public ReadOnlySpan GetCode(ulong address, int minimumSize) + { + return MemoryMarshal.Cast(new ReadOnlySpan(_data)[(int)address..]); + } + + public int QuerySamplerArrayLengthFromPool() + { + return DefaultArrayLength; + } + + public int QueryTextureArrayLengthFromBuffer(int slot) + { + return DefaultArrayLength; + } + + public int QueryTextureArrayLengthFromPool() + { + return DefaultArrayLength; + } + } + + private class Options + { + [Option("compute", Required = false, Default = false, HelpText = "Indicate that the shader is a compute shader.")] + public bool Compute { get; set; } + + [Option("vertex-as-compute", Required = false, Default = false, HelpText = "Indicate that the shader is a vertex shader and should be converted to compute.")] + public bool VertexAsCompute { get; set; } + + [Option("vertex-passthrough", Required = false, Default = false, HelpText = "Indicate that the shader is a vertex passthrough shader for compute output.")] + public bool VertexPassthrough { get; set; } + + [Option("target-language", Required = false, Default = TargetLanguage.Glsl, HelpText = "Indicate the target shader language to use.")] + public TargetLanguage TargetLanguage { get; set; } + + [Option("target-api", Required = false, Default = TargetApi.OpenGL, HelpText = "Indicate the target graphics api to use.")] + public TargetApi TargetApi { get; set; } + + [Value(0, MetaName = "input", HelpText = "Binary Maxwell shader input path.", Required = true)] + public string InputPath { get; set; } + + [Value(1, MetaName = "output", HelpText = "Decompiled shader output path.", Required = false)] + public string OutputPath { get; set; } + } + + static void HandleArguments(Options options) + { + TranslationFlags flags = TranslationFlags.DebugMode; + + if (options.Compute) + { + flags |= TranslationFlags.Compute; + } + + byte[] data = File.ReadAllBytes(options.InputPath); + + TranslationOptions translationOptions = new(options.TargetLanguage, options.TargetApi, flags); + TranslatorContext translatorContext = Translator.CreateContext(0, new GpuAccessor(data), translationOptions); + + ShaderProgram program; + + if (options.VertexPassthrough) + { + program = translatorContext.GenerateVertexPassthroughForCompute(); + } + else + { + program = translatorContext.Translate(options.VertexAsCompute); + } + + if (options.OutputPath == null) + { + if (program.BinaryCode != null) + { + using Stream outputStream = Console.OpenStandardOutput(); + + outputStream.Write(program.BinaryCode); + } + else + { + Console.WriteLine(program.Code); + } + } + else + { + if (program.BinaryCode != null) + { + File.WriteAllBytes(options.OutputPath, program.BinaryCode); + } + else + { + File.WriteAllText(options.OutputPath, program.Code); + } + } + } + + static void Main(string[] args) + { + Parser.Default.ParseArguments(args) + .WithParsed(options => HandleArguments(options)) + .WithNotParsed(errors => errors.Output()); + } + } +} diff --git a/src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj b/src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj new file mode 100644 index 00000000..ab89fb5c --- /dev/null +++ b/src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + Exe + Debug;Release + + + + + + + + + + + diff --git a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs new file mode 100644 index 00000000..3fe44db2 --- /dev/null +++ b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs @@ -0,0 +1,121 @@ +using Ryujinx.Memory; +using Ryujinx.Memory.Range; +using System; +using System.Buffers; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Memory +{ + public class MockVirtualMemoryManager : IVirtualMemoryManager + { + public bool UsesPrivateAllocations => false; + + public bool NoMappings = false; + + public event Action OnProtect; + + public MockVirtualMemoryManager(ulong size, int pageSize) + { + } + + public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) + { + throw new NotImplementedException(); + } + + public void MapForeign(ulong va, nuint hostAddress, ulong size) + { + throw new NotImplementedException(); + } + + public void Unmap(ulong va, ulong size) + { + throw new NotImplementedException(); + } + + public T Read(ulong va) where T : unmanaged + { + throw new NotImplementedException(); + } + + public void Read(ulong va, Span data) + { + throw new NotImplementedException(); + } + + public void Write(ulong va, T value) where T : unmanaged + { + throw new NotImplementedException(); + } + + public void Write(ulong va, ReadOnlySpan data) + { + throw new NotImplementedException(); + } + + public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + { + throw new NotImplementedException(); + } + + public ReadOnlySequence GetReadOnlySequence(ulong va, int size, bool tracked = false) + { + throw new NotImplementedException(); + } + + public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + { + throw new NotImplementedException(); + } + + public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) + { + throw new NotImplementedException(); + } + + public ref T GetRef(ulong va) where T : unmanaged + { + throw new NotImplementedException(); + } + + IEnumerable IVirtualMemoryManager.GetHostRegions(ulong va, ulong size) + { + throw new NotImplementedException(); + } + + IEnumerable IVirtualMemoryManager.GetPhysicalRegions(ulong va, ulong size) + { + return NoMappings ? Array.Empty() : new MemoryRange[] { new MemoryRange(va, size) }; + } + + public bool IsMapped(ulong va) + { + return true; + } + + public bool IsRangeMapped(ulong va, ulong size) + { + return true; + } + + public ulong GetPhysicalAddress(ulong va) + { + throw new NotImplementedException(); + } + + public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + { + throw new NotImplementedException(); + } + + public void Reprotect(ulong va, ulong size, MemoryPermission protection) + { + throw new NotImplementedException(); + } + + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection, bool guest) + { + OnProtect?.Invoke(va, size, protection); + } + } +} diff --git a/src/Ryujinx.Tests.Memory/MultiRegionTrackingTests.cs b/src/Ryujinx.Tests.Memory/MultiRegionTrackingTests.cs new file mode 100644 index 00000000..c71a3e1f --- /dev/null +++ b/src/Ryujinx.Tests.Memory/MultiRegionTrackingTests.cs @@ -0,0 +1,439 @@ +using NUnit.Framework; +using Ryujinx.Memory; +using Ryujinx.Memory.Tracking; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Tests.Memory +{ + public class MultiRegionTrackingTests + { + private const ulong MemorySize = 0x8000; + private const int PageSize = 4096; + + private MemoryBlock _memoryBlock; + private MemoryTracking _tracking; + private MockVirtualMemoryManager _memoryManager; + + [SetUp] + public void Setup() + { + _memoryBlock = new MemoryBlock(MemorySize); + _memoryManager = new MockVirtualMemoryManager(MemorySize, PageSize); + _tracking = new MemoryTracking(_memoryManager, PageSize); + } + + [TearDown] + public void Teardown() + { + _memoryBlock.Dispose(); + } + + private IMultiRegionHandle GetGranular(bool smart, ulong address, ulong size, ulong granularity) + { + return smart ? + _tracking.BeginSmartGranularTracking(address, size, granularity, 0) : + (IMultiRegionHandle)_tracking.BeginGranularTracking(address, size, null, granularity, 0); + } + + private static void RandomOrder(Random random, List indices, Action action) + { + List choices = indices.ToList(); + + while (choices.Count > 0) + { + int choice = random.Next(choices.Count); + action(choices[choice]); + choices.RemoveAt(choice); + } + } + + private static int ExpectQueryInOrder(IMultiRegionHandle handle, ulong startAddress, ulong size, Func addressPredicate) + { + int regionCount = 0; + ulong lastAddress = startAddress; + + handle.QueryModified(startAddress, size, (address, range) => + { + Assert.IsTrue(addressPredicate(address)); // Written pages must be even. + Assert.GreaterOrEqual(address, lastAddress); // Must be signalled in ascending order, regardless of write order. + lastAddress = address; + regionCount++; + }); + + return regionCount; + } + + private static int ExpectQueryInOrder(IMultiRegionHandle handle, ulong startAddress, ulong size, Func addressPredicate, int sequenceNumber) + { + int regionCount = 0; + ulong lastAddress = startAddress; + + handle.QueryModified(startAddress, size, (address, range) => + { + Assert.IsTrue(addressPredicate(address)); // Written pages must be even. + Assert.GreaterOrEqual(address, lastAddress); // Must be signalled in ascending order, regardless of write order. + lastAddress = address; + regionCount++; + }, sequenceNumber); + + return regionCount; + } + + private static void PreparePages(IMultiRegionHandle handle, int pageCount, ulong address = 0) + { + Random random = new(); + + // Make sure the list has minimum granularity (smart region changes granularity based on requested ranges) + RandomOrder(random, Enumerable.Range(0, pageCount).ToList(), (i) => + { + ulong resultAddress = ulong.MaxValue; + handle.QueryModified((ulong)i * PageSize + address, PageSize, (address, range) => + { + resultAddress = address; + }); + Assert.AreEqual(resultAddress, (ulong)i * PageSize + address); + }); + } + + [Test] + public void DirtyRegionOrdering([Values] bool smart) + { + const int PageCount = 32; + IMultiRegionHandle handle = GetGranular(smart, 0, PageSize * PageCount, PageSize); + + Random random = new(); + + PreparePages(handle, PageCount); + + IEnumerable halfRange = Enumerable.Range(0, PageCount / 2); + List odd = halfRange.Select(x => x * 2 + 1).ToList(); + List even = halfRange.Select(x => x * 2).ToList(); + + // Write to all the odd pages. + RandomOrder(random, odd, (i) => + { + _tracking.VirtualMemoryEvent((ulong)i * PageSize, PageSize, true); + }); + + int oddRegionCount = ExpectQueryInOrder(handle, 0, PageSize * PageCount, (address) => (address / PageSize) % 2 == 1); + + Assert.AreEqual(oddRegionCount, PageCount / 2); // Must have written to all odd pages. + + // Write to all the even pages. + RandomOrder(random, even, (i) => + { + _tracking.VirtualMemoryEvent((ulong)i * PageSize, PageSize, true); + }); + + int evenRegionCount = ExpectQueryInOrder(handle, 0, PageSize * PageCount, (address) => (address / PageSize) % 2 == 0); + + Assert.AreEqual(evenRegionCount, PageCount / 2); + } + + [Test] + public void SequenceNumber([Values] bool smart) + { + // The sequence number can be used to ignore dirty flags, and defer their consumption until later. + // If a user consumes a dirty flag with sequence number 1, then there is a write to the protected region, + // the dirty flag will not be acknowledged until the sequence number is 2. + + // This is useful for situations where we know that the data was complete when the sequence number was set. + // ...essentially, when that data can only be updated on a future sequence number. + + const int PageCount = 32; + IMultiRegionHandle handle = GetGranular(smart, 0, PageSize * PageCount, PageSize); + + PreparePages(handle, PageCount); + + Random random = new(); + + IEnumerable halfRange = Enumerable.Range(0, PageCount / 2); + List odd = halfRange.Select(x => x * 2 + 1).ToList(); + List even = halfRange.Select(x => x * 2).ToList(); + + // Write to all the odd pages. + RandomOrder(random, odd, (i) => + { + _tracking.VirtualMemoryEvent((ulong)i * PageSize, PageSize, true); + }); + + int oddRegionCount = 0; + + // Track with sequence number 1. Future dirty flags should only be consumed with sequence number != 1. + // Only track the odd pages, so the even ones don't have their sequence number set. + + foreach (int index in odd) + { + handle.QueryModified((ulong)index * PageSize, PageSize, (address, range) => + { + oddRegionCount++; + }, 1); + } + + Assert.AreEqual(oddRegionCount, PageCount / 2); // Must have written to all odd pages. + + // Write to all pages. + + _tracking.VirtualMemoryEvent(0, PageSize * PageCount, true); + + // Only the even regions should be reported for sequence number 1. + + int evenRegionCount = ExpectQueryInOrder(handle, 0, PageSize * PageCount, (address) => (address / PageSize) % 2 == 0, 1); + + Assert.AreEqual(evenRegionCount, PageCount / 2); // Must have written to all even pages. + + oddRegionCount = 0; + + handle.QueryModified(0, PageSize * PageCount, (address, range) => { oddRegionCount++; }, 1); + + Assert.AreEqual(oddRegionCount, 0); // Sequence number has not changed, so found no dirty subregions. + + // With sequence number 2, all all pages should be reported as modified. + + oddRegionCount = ExpectQueryInOrder(handle, 0, PageSize * PageCount, (address) => (address / PageSize) % 2 == 1, 2); + + Assert.AreEqual(oddRegionCount, PageCount / 2); // Must have written to all odd pages. + } + + [Test] + public void SmartRegionTracking() + { + // Smart multi region handles dynamically change their tracking granularity based on QueryMemory calls. + // This can save on reprotects on larger resources. + + const int PageCount = 32; + IMultiRegionHandle handle = GetGranular(true, 0, PageSize * PageCount, PageSize); + + // Query some large regions to prep the subdivision of the tracking region. + + int[] regionSizes = new int[] { 6, 4, 3, 2, 6, 1 }; + ulong address = 0; + + for (int i = 0; i < regionSizes.Length; i++) + { + int region = regionSizes[i]; + handle.QueryModified(address, (ulong)(PageSize * region), (address, size) => { }); + + // There should be a gap between regions, + // So that they don't combine and we can see the full effects. + address += (ulong)(PageSize * (region + 1)); + } + + // Clear modified. + handle.QueryModified((address, size) => { }); + + // Trigger each region with a 1 byte write. + address = 0; + + for (int i = 0; i < regionSizes.Length; i++) + { + int region = regionSizes[i]; + _tracking.VirtualMemoryEvent(address, 1, true); + address += (ulong)(PageSize * (region + 1)); + } + + int regionInd = 0; + ulong expectedAddress = 0; + + // Expect each region to trigger in its entirety, in address ascending order. + handle.QueryModified((address, size) => + { + int region = regionSizes[regionInd++]; + + Assert.AreEqual(address, expectedAddress); + Assert.AreEqual(size, (ulong)(PageSize * region)); + + expectedAddress += (ulong)(PageSize * (region + 1)); + }); + } + + [Test] + public void DisposeMultiHandles([Values] bool smart) + { + // Create and initialize two overlapping Multi Region Handles, with PageSize granularity. + const int PageCount = 32; + const int OverlapStart = 16; + + Assert.AreEqual(0, _tracking.GetRegionCount()); + + IMultiRegionHandle handleLow = GetGranular(smart, 0, PageSize * PageCount, PageSize); + PreparePages(handleLow, PageCount); + + Assert.AreEqual(PageCount, _tracking.GetRegionCount()); + + IMultiRegionHandle handleHigh = GetGranular(smart, PageSize * OverlapStart, PageSize * PageCount, PageSize); + PreparePages(handleHigh, PageCount, PageSize * OverlapStart); + + // Combined pages (and assuming overlapStart <= pageCount) should be pageCount after overlapStart. + int totalPages = OverlapStart + PageCount; + + Assert.AreEqual(totalPages, _tracking.GetRegionCount()); + + handleLow.Dispose(); // After disposing one, the pages for the other remain. + + Assert.AreEqual(PageCount, _tracking.GetRegionCount()); + + handleHigh.Dispose(); // After disposing the other, there are no pages left. + + Assert.AreEqual(0, _tracking.GetRegionCount()); + } + + [Test] + public void InheritHandles() + { + // Test merging the following into a granular region handle: + // - 3x gap (creates new granular handles) + // - 3x from multiregion: not dirty, dirty and with action + // - 2x gap + // - 3x single page: not dirty, dirty and with action + // - 3x two page: not dirty, dirty and with action (handle is not reused, but its state is copied to the granular handles) + // - 1x gap + // For a total of 18 pages. + + bool[] actionsTriggered = new bool[3]; + + MultiRegionHandle granular = _tracking.BeginGranularTracking(PageSize * 3, PageSize * 3, null, PageSize, 0); + PreparePages(granular, 3, PageSize * 3); + + // Write to the second handle in the multiregion. + _tracking.VirtualMemoryEvent(PageSize * 4, PageSize, true); + + // Add an action to the third handle in the multiregion. + granular.RegisterAction(PageSize * 5, PageSize, (_, _) => { actionsTriggered[0] = true; }); + + RegionHandle[] singlePages = new RegionHandle[3]; + + for (int i = 0; i < 3; i++) + { + singlePages[i] = _tracking.BeginTracking(PageSize * (8 + (ulong)i), PageSize, 0); + singlePages[i].Reprotect(); + } + + // Write to the second handle. + _tracking.VirtualMemoryEvent(PageSize * 9, PageSize, true); + + // Add an action to the third handle. + singlePages[2].RegisterAction((_, _) => { actionsTriggered[1] = true; }); + + RegionHandle[] doublePages = new RegionHandle[3]; + + for (int i = 0; i < 3; i++) + { + doublePages[i] = _tracking.BeginTracking(PageSize * (11 + (ulong)i * 2), PageSize * 2, 0); + doublePages[i].Reprotect(); + } + + // Write to the second handle. + _tracking.VirtualMemoryEvent(PageSize * 13, PageSize * 2, true); + + // Add an action to the third handle. + doublePages[2].RegisterAction((_, _) => { actionsTriggered[2] = true; }); + + // Finally, create a granular handle that inherits all these handles. + + IEnumerable[] handleGroups = new IEnumerable[] + { + granular.GetHandles(), + singlePages, + doublePages, + }; + + MultiRegionHandle combined = _tracking.BeginGranularTracking(0, PageSize * 18, handleGroups.SelectMany((handles) => handles), PageSize, 0); + + bool[] expectedDirty = new bool[] + { + true, true, true, // Gap. + false, true, false, // Multi-region. + true, true, // Gap. + false, true, false, // Individual handles. + false, false, true, true, false, false, // Double size handles. + true, // Gap. + }; + + for (int i = 0; i < 18; i++) + { + bool modified = false; + combined.QueryModified(PageSize * (ulong)i, PageSize, (_, _) => { modified = true; }); + + Assert.AreEqual(expectedDirty[i], modified); + } + + Assert.AreEqual(new bool[3], actionsTriggered); + + _tracking.VirtualMemoryEvent(PageSize * 5, PageSize, false); + Assert.IsTrue(actionsTriggered[0]); + + _tracking.VirtualMemoryEvent(PageSize * 10, PageSize, false); + Assert.IsTrue(actionsTriggered[1]); + + _tracking.VirtualMemoryEvent(PageSize * 15, PageSize, false); + Assert.IsTrue(actionsTriggered[2]); + + // The double page handles should be disposed, as they were split into granular handles. + foreach (RegionHandle doublePage in doublePages) + { + // These should have been disposed. + bool throws = false; + + try + { + doublePage.Dispose(); + } + catch (ObjectDisposedException) + { + throws = true; + } + + Assert.IsTrue(throws); + } + + IEnumerable combinedHandles = combined.GetHandles(); + + Assert.AreEqual(handleGroups[0].ElementAt(0), combinedHandles.ElementAt(3)); + Assert.AreEqual(handleGroups[0].ElementAt(1), combinedHandles.ElementAt(4)); + Assert.AreEqual(handleGroups[0].ElementAt(2), combinedHandles.ElementAt(5)); + + Assert.AreEqual(singlePages[0], combinedHandles.ElementAt(8)); + Assert.AreEqual(singlePages[1], combinedHandles.ElementAt(9)); + Assert.AreEqual(singlePages[2], combinedHandles.ElementAt(10)); + } + + [Test] + public void PreciseAction() + { + bool actionTriggered = false; + + MultiRegionHandle granular = _tracking.BeginGranularTracking(PageSize * 3, PageSize * 3, null, PageSize, 0); + PreparePages(granular, 3, PageSize * 3); + + // Add a precise action to the second and third handle in the multiregion. + granular.RegisterPreciseAction(PageSize * 4, PageSize * 2, (_, _, _) => { actionTriggered = true; return true; }); + + // Precise write to first handle in the multiregion. + _tracking.VirtualMemoryEvent(PageSize * 3, PageSize, true, precise: true); + Assert.IsFalse(actionTriggered); // Action not triggered. + + bool firstPageModified = false; + granular.QueryModified(PageSize * 3, PageSize, (_, _) => { firstPageModified = true; }); + Assert.IsTrue(firstPageModified); // First page is modified. + + // Precise write to all handles in the multiregion. + _tracking.VirtualMemoryEvent(PageSize * 3, PageSize * 3, true, precise: true); + + bool[] pagesModified = new bool[3]; + + for (int i = 3; i < 6; i++) + { + int index = i - 3; + granular.QueryModified(PageSize * (ulong)i, PageSize, (_, _) => { pagesModified[index] = true; }); + } + + Assert.IsTrue(actionTriggered); // Action triggered. + + // Precise writes are ignored on two later handles due to the action returning true. + Assert.AreEqual(pagesModified, new bool[] { true, false, false }); + } + } +} diff --git a/src/Ryujinx.Tests.Memory/Ryujinx.Tests.Memory.csproj b/src/Ryujinx.Tests.Memory/Ryujinx.Tests.Memory.csproj new file mode 100644 index 00000000..f0506083 --- /dev/null +++ b/src/Ryujinx.Tests.Memory/Ryujinx.Tests.Memory.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + false + + + + + + + + + + + + + diff --git a/src/Ryujinx.Tests.Memory/Tests.cs b/src/Ryujinx.Tests.Memory/Tests.cs new file mode 100644 index 00000000..bfc6344b --- /dev/null +++ b/src/Ryujinx.Tests.Memory/Tests.cs @@ -0,0 +1,118 @@ +using NUnit.Framework; +using Ryujinx.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Tests.Memory +{ + public class Tests + { + private static readonly ulong _memorySize = MemoryBlock.GetPageSize() * 8; + + private MemoryBlock _memoryBlock; + + [SetUp] + public void Setup() + { + _memoryBlock = new MemoryBlock(_memorySize); + } + + [TearDown] + public void Teardown() + { + _memoryBlock.Dispose(); + } + + [Test] + public void Test_Read() + { + Marshal.WriteInt32(_memoryBlock.Pointer, 0x2020, 0x1234abcd); + + Assert.AreEqual(_memoryBlock.Read(0x2020), 0x1234abcd); + } + + [Test] + public void Test_Write() + { + _memoryBlock.Write(0x2040, 0xbadc0de); + + Assert.AreEqual(Marshal.ReadInt32(_memoryBlock.Pointer, 0x2040), 0xbadc0de); + } + + [Test] + // Memory aliasing tests fail on CI at the moment. + [Platform(Exclude = "MacOsX")] + public void Test_Alias() + { + ulong pageSize = MemoryBlock.GetPageSize(); + ulong blockSize = MemoryBlock.GetPageSize() * 16; + + using MemoryBlock backing = new(blockSize, MemoryAllocationFlags.Mirrorable); + using MemoryBlock toAlias = new(blockSize, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible); + + toAlias.MapView(backing, pageSize, 0, pageSize * 4); + toAlias.UnmapView(backing, pageSize * 3, pageSize); + + toAlias.Write(0, 0xbadc0de); + Assert.AreEqual(Marshal.ReadInt32(backing.Pointer, (int)pageSize), 0xbadc0de); + } + + [Test] + // Memory aliasing tests fail on CI at the moment. + [Platform(Exclude = "MacOsX")] + public void Test_AliasRandom() + { + ulong pageSize = MemoryBlock.GetPageSize(); + int pageBits = (int)ulong.Log2(pageSize); + ulong blockSize = MemoryBlock.GetPageSize() * 128; + + using MemoryBlock backing = new(blockSize, MemoryAllocationFlags.Mirrorable); + using MemoryBlock toAlias = new(blockSize, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible); + + Random rng = new(123); + + for (int i = 0; i < 20000; i++) + { + int srcPage = rng.Next(0, 64); + int dstPage = rng.Next(0, 64); + int pages = rng.Next(1, 65); + + if ((rng.Next() & 1) != 0) + { + toAlias.MapView(backing, (ulong)srcPage << pageBits, (ulong)dstPage << pageBits, (ulong)pages << pageBits); + + int offset = rng.Next(0, (int)pageSize - sizeof(int)); + + toAlias.Write((ulong)((dstPage << pageBits) + offset), 0xbadc0de); + Assert.AreEqual(Marshal.ReadInt32(backing.Pointer, (srcPage << pageBits) + offset), 0xbadc0de); + } + else + { + toAlias.UnmapView(backing, (ulong)dstPage << pageBits, (ulong)pages << pageBits); + } + } + } + + [Test] + // Memory aliasing tests fail on CI at the moment. + [Platform(Exclude = "MacOsX")] + public void Test_AliasMapLeak() + { + ulong pageSize = MemoryBlock.GetPageSize(); + ulong size = 100000 * pageSize; // The mappings limit on Linux is usually around 65K, so let's make sure we are above that. + + using MemoryBlock backing = new(pageSize, MemoryAllocationFlags.Mirrorable); + using MemoryBlock toAlias = new(size, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible); + + for (ulong offset = 0; offset < size; offset += pageSize) + { + toAlias.MapView(backing, 0, offset, pageSize); + + toAlias.Write(offset, 0xbadc0de); + Assert.AreEqual(0xbadc0de, backing.Read(0)); + + toAlias.UnmapView(backing, offset, pageSize); + } + } + } +} diff --git a/src/Ryujinx.Tests.Memory/TrackingTests.cs b/src/Ryujinx.Tests.Memory/TrackingTests.cs new file mode 100644 index 00000000..c74446cf --- /dev/null +++ b/src/Ryujinx.Tests.Memory/TrackingTests.cs @@ -0,0 +1,512 @@ +using NUnit.Framework; +using Ryujinx.Memory; +using Ryujinx.Memory.Tracking; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; + +namespace Ryujinx.Tests.Memory +{ + public class TrackingTests + { + private const int RndCnt = 3; + + private const ulong MemorySize = 0x8000; + private const int PageSize = 4096; + + private MemoryBlock _memoryBlock; + private MemoryTracking _tracking; + private MockVirtualMemoryManager _memoryManager; + + [SetUp] + public void Setup() + { + _memoryBlock = new MemoryBlock(MemorySize); + _memoryManager = new MockVirtualMemoryManager(MemorySize, PageSize); + _tracking = new MemoryTracking(_memoryManager, PageSize); + } + + [TearDown] + public void Teardown() + { + _memoryBlock.Dispose(); + } + + private bool TestSingleWrite(RegionHandle handle, ulong address, ulong size) + { + handle.Reprotect(); + + _tracking.VirtualMemoryEvent(address, size, true); + + return handle.Dirty; + } + + [Test] + public void SingleRegion() + { + RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0); + (ulong address, ulong size)? readTrackingTriggered = null; + handle.RegisterAction((address, size) => + { + readTrackingTriggered = (address, size); + }); + + bool dirtyInitial = handle.Dirty; + Assert.True(dirtyInitial); // Handle starts dirty. + + handle.Reprotect(); + + bool dirtyAfterReprotect = handle.Dirty; + Assert.False(dirtyAfterReprotect); // Handle is no longer dirty. + + _tracking.VirtualMemoryEvent(PageSize * 2, 4, true); + _tracking.VirtualMemoryEvent(PageSize * 2, 4, false); + + bool dirtyAfterUnrelatedReadWrite = handle.Dirty; + Assert.False(dirtyAfterUnrelatedReadWrite); // Not dirtied, as the write was to an unrelated address. + + Assert.IsNull(readTrackingTriggered); // Hasn't been triggered yet + + _tracking.VirtualMemoryEvent(0, 4, false); + + bool dirtyAfterRelatedRead = handle.Dirty; + Assert.False(dirtyAfterRelatedRead); // Only triggers on write. + Assert.AreEqual(readTrackingTriggered, (0UL, 4UL)); // Read action was triggered. + + readTrackingTriggered = null; + _tracking.VirtualMemoryEvent(0, 4, true); + + bool dirtyAfterRelatedWrite = handle.Dirty; + Assert.True(dirtyAfterRelatedWrite); // Dirty flag should now be set. + + _tracking.VirtualMemoryEvent(4, 4, true); + bool dirtyAfterRelatedWrite2 = handle.Dirty; + Assert.True(dirtyAfterRelatedWrite2); // Dirty flag should still be set. + + handle.Reprotect(); + + bool dirtyAfterReprotect2 = handle.Dirty; + Assert.False(dirtyAfterReprotect2); // Handle is no longer dirty. + + handle.Dispose(); + + bool dirtyAfterDispose = TestSingleWrite(handle, 0, 4); + Assert.False(dirtyAfterDispose); // Handle cannot be triggered when disposed + } + + [Test] + public void OverlappingRegions() + { + RegionHandle allHandle = _tracking.BeginTracking(0, PageSize * 16, 0); + allHandle.Reprotect(); + + (ulong address, ulong size)? readTrackingTriggeredAll = null; + + void RegisterReadAction() + { + readTrackingTriggeredAll = null; + allHandle.RegisterAction((address, size) => + { + readTrackingTriggeredAll = (address, size); + }); + } + + RegisterReadAction(); + + // Create 16 page sized handles contained within the allHandle. + RegionHandle[] containedHandles = new RegionHandle[16]; + + for (int i = 0; i < 16; i++) + { + containedHandles[i] = _tracking.BeginTracking((ulong)i * PageSize, PageSize, 0); + containedHandles[i].Reprotect(); + } + + for (int i = 0; i < 16; i++) + { + // No handles are dirty. + Assert.False(allHandle.Dirty); + Assert.IsNull(readTrackingTriggeredAll); + for (int j = 0; j < 16; j++) + { + Assert.False(containedHandles[j].Dirty); + } + + _tracking.VirtualMemoryEvent((ulong)i * PageSize, 1, true); + + // Only the handle covering the entire range and the relevant contained handle are dirty. + Assert.True(allHandle.Dirty); + Assert.AreEqual(readTrackingTriggeredAll, ((ulong)i * PageSize, 1UL)); // Triggered read tracking + for (int j = 0; j < 16; j++) + { + if (j == i) + { + Assert.True(containedHandles[j].Dirty); + } + else + { + Assert.False(containedHandles[j].Dirty); + } + } + + // Clear flags and reset read action. + RegisterReadAction(); + allHandle.Reprotect(); + containedHandles[i].Reprotect(); + } + } + + [Test] + public void PageAlignment( + [Values(1ul, 512ul, 2048ul, 4096ul, 65536ul)][Random(1ul, 65536ul, RndCnt)] ulong address, + [Values(1ul, 4ul, 1024ul, 4096ul, 65536ul)][Random(1ul, 65536ul, RndCnt)] ulong size) + { + ulong alignedStart = (address / PageSize) * PageSize; + ulong alignedEnd = ((address + size + PageSize - 1) / PageSize) * PageSize; + ulong alignedSize = alignedEnd - alignedStart; + + RegionHandle handle = _tracking.BeginTracking(address, size, 0); + + // Anywhere inside the pages the region is contained on should trigger. + + bool originalRangeTriggers = TestSingleWrite(handle, address, size); + Assert.True(originalRangeTriggers); + + bool alignedRangeTriggers = TestSingleWrite(handle, alignedStart, alignedSize); + Assert.True(alignedRangeTriggers); + + bool alignedStartTriggers = TestSingleWrite(handle, alignedStart, 1); + Assert.True(alignedStartTriggers); + + bool alignedEndTriggers = TestSingleWrite(handle, alignedEnd - 1, 1); + Assert.True(alignedEndTriggers); + + // Outside the tracked range should not trigger. + + bool alignedBeforeTriggers = TestSingleWrite(handle, alignedStart - 1, 1); + Assert.False(alignedBeforeTriggers); + + bool alignedAfterTriggers = TestSingleWrite(handle, alignedEnd, 1); + Assert.False(alignedAfterTriggers); + } + + [Test, Explicit, Timeout(1000)] + public void Multithreading() + { + // Multithreading sanity test + // Multiple threads can easily read/write memory regions from any existing handle. + // Handles can also be owned by different threads, though they should have one owner thread. + // Handles can be created and disposed at any time, by any thread. + + // This test should not throw or deadlock due to invalid state. + + const int ThreadCount = 1; + const int HandlesPerThread = 16; + long finishedTime = 0; + + RegionHandle[] handles = new RegionHandle[ThreadCount * HandlesPerThread]; + Random globalRand = new(); + + for (int i = 0; i < handles.Length; i++) + { + handles[i] = _tracking.BeginTracking((ulong)i * PageSize, PageSize, 0); + handles[i].Reprotect(); + } + + List testThreads = new(); + + // Dirty flag consumer threads + int dirtyFlagReprotects = 0; + for (int i = 0; i < ThreadCount; i++) + { + int randSeed = i; + testThreads.Add(new Thread(() => + { + int handleBase = randSeed * HandlesPerThread; + while (Stopwatch.GetTimestamp() < finishedTime) + { + Random random = new(randSeed); + RegionHandle handle = handles[handleBase + random.Next(HandlesPerThread)]; + + if (handle.Dirty) + { + handle.Reprotect(); + Interlocked.Increment(ref dirtyFlagReprotects); + } + } + })); + } + + // Write trigger threads + int writeTriggers = 0; + for (int i = 0; i < ThreadCount; i++) + { + int randSeed = i; + testThreads.Add(new Thread(() => + { + Random random = new(randSeed); + ulong handleBase = (ulong)(randSeed * HandlesPerThread * PageSize); + while (Stopwatch.GetTimestamp() < finishedTime) + { + _tracking.VirtualMemoryEvent(handleBase + (ulong)random.Next(PageSize * HandlesPerThread), PageSize / 2, true); + Interlocked.Increment(ref writeTriggers); + } + })); + } + + // Handle create/delete threads + int handleLifecycles = 0; + for (int i = 0; i < ThreadCount; i++) + { + int randSeed = i; + testThreads.Add(new Thread(() => + { + int maxAddress = ThreadCount * HandlesPerThread * PageSize; + Random random = new(randSeed + 512); + while (Stopwatch.GetTimestamp() < finishedTime) + { + RegionHandle handle = _tracking.BeginTracking((ulong)random.Next(maxAddress), (ulong)random.Next(65536), 0); + + handle.Dispose(); + + Interlocked.Increment(ref handleLifecycles); + } + })); + } + + finishedTime = Stopwatch.GetTimestamp() + Stopwatch.Frequency / 2; // Run for 500ms; + + foreach (Thread thread in testThreads) + { + thread.Start(); + } + + foreach (Thread thread in testThreads) + { + thread.Join(); + } + + Assert.Greater(dirtyFlagReprotects, 10); + Assert.Greater(writeTriggers, 10); + Assert.Greater(handleLifecycles, 10); + } + + [Test] + public void ReadActionThreadConsumption() + { + // Read actions should only be triggered once for each registration. + // The implementation should use an interlocked exchange to make sure other threads can't get the action. + + RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0); + + int triggeredCount = 0; + int registeredCount = 0; + int signalThreadsDone = 0; + bool isRegistered = false; + + void RegisterReadAction() + { + registeredCount++; + handle.RegisterAction((address, size) => + { + isRegistered = false; + Interlocked.Increment(ref triggeredCount); + }); + } + + const int ThreadCount = 16; + const int IterationCount = 10000; + Thread[] signalThreads = new Thread[ThreadCount]; + + for (int i = 0; i < ThreadCount; i++) + { + int randSeed = i; + signalThreads[i] = new Thread(() => + { + Random random = new(randSeed); + for (int j = 0; j < IterationCount; j++) + { + _tracking.VirtualMemoryEvent((ulong)random.Next(PageSize), 4, false); + } + Interlocked.Increment(ref signalThreadsDone); + }); + } + + for (int i = 0; i < ThreadCount; i++) + { + signalThreads[i].Start(); + } + + while (signalThreadsDone != -1) + { + if (signalThreadsDone == ThreadCount) + { + signalThreadsDone = -1; + } + + if (!isRegistered) + { + isRegistered = true; + RegisterReadAction(); + } + } + + // The action should trigger exactly once for every registration, + // then we register once after all the threads signalling it cease. + Assert.AreEqual(registeredCount, triggeredCount + 1); + } + + [Test] + public void DisposeHandles() + { + // Ensure that disposed handles correctly remove their virtual and physical regions. + + RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0); + handle.Reprotect(); + + Assert.AreEqual(1, _tracking.GetRegionCount()); + + handle.Dispose(); + + Assert.AreEqual(0, _tracking.GetRegionCount()); + + // Two handles, small entirely contains big. + // We expect there to be three regions after creating both, one for the small region and two covering the big one around it. + // Regions are always split to avoid overlapping, which is why there are three instead of two. + + RegionHandle handleSmall = _tracking.BeginTracking(PageSize, PageSize, 0); + RegionHandle handleBig = _tracking.BeginTracking(0, PageSize * 4, 0); + + Assert.AreEqual(3, _tracking.GetRegionCount()); + + // After disposing the big region, only the small one will remain. + handleBig.Dispose(); + + Assert.AreEqual(1, _tracking.GetRegionCount()); + + handleSmall.Dispose(); + + Assert.AreEqual(0, _tracking.GetRegionCount()); + } + + [Test] + public void ReadAndWriteProtection() + { + MemoryPermission protection = MemoryPermission.ReadAndWrite; + + _memoryManager.OnProtect += (va, size, newProtection) => + { + Assert.AreEqual((0, PageSize), (va, size)); // Should protect the exact region all the operations use. + protection = newProtection; + }; + + RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0); + + // After creating the handle, there is no protection yet. + Assert.AreEqual(MemoryPermission.ReadAndWrite, protection); + + bool dirtyInitial = handle.Dirty; + Assert.True(dirtyInitial); // Handle starts dirty. + + handle.Reprotect(); + + // After a reprotect, there is write protection, which will set a dirty flag when any write happens. + Assert.AreEqual(MemoryPermission.Read, protection); + + (ulong address, ulong size)? readTrackingTriggered = null; + handle.RegisterAction((address, size) => + { + readTrackingTriggered = (address, size); + }); + + // Registering an action adds read/write protection. + Assert.AreEqual(MemoryPermission.None, protection); + + bool dirtyAfterReprotect = handle.Dirty; + Assert.False(dirtyAfterReprotect); // Handle is no longer dirty. + + // First we should read, which will trigger the action. This _should not_ remove write protection on the memory. + + _tracking.VirtualMemoryEvent(0, 4, false); + + bool dirtyAfterRead = handle.Dirty; + Assert.False(dirtyAfterRead); // Not dirtied, as this was a read. + + Assert.AreEqual(readTrackingTriggered, (0UL, 4UL)); // Read action was triggered. + + Assert.AreEqual(MemoryPermission.Read, protection); // Write protection is still present. + + readTrackingTriggered = null; + + // Now, perform a write. + + _tracking.VirtualMemoryEvent(0, 4, true); + + bool dirtyAfterWriteAfterRead = handle.Dirty; + Assert.True(dirtyAfterWriteAfterRead); // Should be dirty. + + Assert.AreEqual(MemoryPermission.ReadAndWrite, protection); // All protection is now be removed from the memory. + + Assert.IsNull(readTrackingTriggered); // Read tracking was removed when the action fired, as it can only fire once. + + handle.Dispose(); + } + + [Test] + public void PreciseAction() + { + RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0); + + (ulong address, ulong size, bool write)? preciseTriggered = null; + handle.RegisterPreciseAction((address, size, write) => + { + preciseTriggered = (address, size, write); + + return true; + }); + + (ulong address, ulong size)? readTrackingTriggered = null; + handle.RegisterAction((address, size) => + { + readTrackingTriggered = (address, size); + }); + + handle.Reprotect(); + + _tracking.VirtualMemoryEvent(0, 4, false, precise: true); + + Assert.IsNull(readTrackingTriggered); // Hasn't been triggered - precise action returned true. + Assert.AreEqual(preciseTriggered, (0UL, 4UL, false)); // Precise action was triggered. + + _tracking.VirtualMemoryEvent(0, 4, true, precise: true); + + Assert.IsNull(readTrackingTriggered); // Still hasn't been triggered. + bool dirtyAfterPreciseActionTrue = handle.Dirty; + Assert.False(dirtyAfterPreciseActionTrue); // Not dirtied - precise action returned true. + Assert.AreEqual(preciseTriggered, (0UL, 4UL, true)); // Precise action was triggered. + + // Handle is now dirty. + handle.Reprotect(true); + preciseTriggered = null; + + _tracking.VirtualMemoryEvent(4, 4, true, precise: true); + Assert.AreEqual(preciseTriggered, (4UL, 4UL, true)); // Precise action was triggered even though handle was dirty. + + handle.Reprotect(); + handle.RegisterPreciseAction((address, size, write) => + { + preciseTriggered = (address, size, write); + + return false; // Now, we return false, which indicates that the regular read/write behaviours should trigger. + }); + + _tracking.VirtualMemoryEvent(8, 4, true, precise: true); + + Assert.AreEqual(readTrackingTriggered, (8UL, 4UL)); // Read action triggered, as precise action returned false. + bool dirtyAfterPreciseActionFalse = handle.Dirty; + Assert.True(dirtyAfterPreciseActionFalse); // Dirtied, as precise action returned false. + Assert.AreEqual(preciseTriggered, (8UL, 4UL, true)); // Precise action was triggered. + } + } +} diff --git a/src/Ryujinx.Tests.Unicorn/IndexedProperty.cs b/src/Ryujinx.Tests.Unicorn/IndexedProperty.cs new file mode 100644 index 00000000..347b91a0 --- /dev/null +++ b/src/Ryujinx.Tests.Unicorn/IndexedProperty.cs @@ -0,0 +1,28 @@ +using System; + +namespace Ryujinx.Tests.Unicorn +{ + public class IndexedProperty + { + private readonly Func _getFunc; + private readonly Action _setAction; + + public IndexedProperty(Func getFunc, Action setAction) + { + _getFunc = getFunc; + _setAction = setAction; + } + + public TValue this[TIndex index] + { + get + { + return _getFunc(index); + } + set + { + _setAction(index, value); + } + } + } +} diff --git a/src/Ryujinx.Tests.Unicorn/MemoryPermission.cs b/src/Ryujinx.Tests.Unicorn/MemoryPermission.cs new file mode 100644 index 00000000..6d3e7370 --- /dev/null +++ b/src/Ryujinx.Tests.Unicorn/MemoryPermission.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.Tests.Unicorn +{ + [Flags] + public enum MemoryPermission + { + None = 0, + Read = 1, + Write = 2, + Exec = 4, + All = 7, + } +} diff --git a/src/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj b/src/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj new file mode 100644 index 00000000..befacfb2 --- /dev/null +++ b/src/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + true + Debug;Release + + + + false + + + + + + + diff --git a/src/Ryujinx.Tests.Unicorn/SimdValue.cs b/src/Ryujinx.Tests.Unicorn/SimdValue.cs new file mode 100644 index 00000000..0401c693 --- /dev/null +++ b/src/Ryujinx.Tests.Unicorn/SimdValue.cs @@ -0,0 +1,110 @@ +using System; + +namespace Ryujinx.Tests.Unicorn +{ + public readonly struct SimdValue : IEquatable + { + private readonly ulong _e0; + private readonly ulong _e1; + + public SimdValue(ulong e0, ulong e1) + { + _e0 = e0; + _e1 = e1; + } + + public SimdValue(byte[] data) + { + _e0 = (ulong)BitConverter.ToInt64(data, 0); + _e1 = (ulong)BitConverter.ToInt64(data, 8); + } + + public float AsFloat() + { + return GetFloat(0); + } + + public double AsDouble() + { + return GetDouble(0); + } + + public float GetFloat(int index) + { + return BitConverter.Int32BitsToSingle(GetInt32(index)); + } + + public double GetDouble(int index) + { + return BitConverter.Int64BitsToDouble(GetInt64(index)); + } + + public int GetInt32(int index) => (int)GetUInt32(index); + public long GetInt64(int index) => (long)GetUInt64(index); + + public uint GetUInt32(int index) + { + return index switch + { + 0 => (uint)(_e0 >> 0), + 1 => (uint)(_e0 >> 32), + 2 => (uint)(_e1 >> 0), + 3 => (uint)(_e1 >> 32), + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + } + + public ulong GetUInt64(int index) + { + return index switch + { + 0 => _e0, + 1 => _e1, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + } + + public byte[] ToArray() + { + byte[] e0Data = BitConverter.GetBytes(_e0); + byte[] e1Data = BitConverter.GetBytes(_e1); + + byte[] data = new byte[16]; + + Buffer.BlockCopy(e0Data, 0, data, 0, 8); + Buffer.BlockCopy(e1Data, 0, data, 8, 8); + + return data; + } + + public override int GetHashCode() + { + return HashCode.Combine(_e0, _e1); + } + + public static bool operator ==(SimdValue x, SimdValue y) + { + return x.Equals(y); + } + + public static bool operator !=(SimdValue x, SimdValue y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is SimdValue vector && Equals(vector); + } + + public bool Equals(SimdValue other) + { + return other._e0 == _e0 && other._e1 == _e1; + } + + public override string ToString() + { + return $"0x{_e1:X16}{_e0:X16}"; + } + } +} diff --git a/src/Ryujinx.Tests.Unicorn/UnicornAArch32.cs b/src/Ryujinx.Tests.Unicorn/UnicornAArch32.cs new file mode 100644 index 00000000..6fe62b74 --- /dev/null +++ b/src/Ryujinx.Tests.Unicorn/UnicornAArch32.cs @@ -0,0 +1,285 @@ +using System; +using UnicornEngine.Const; + +namespace Ryujinx.Tests.Unicorn +{ + public class UnicornAArch32 : IDisposable + { + internal readonly UnicornEngine.Unicorn Uc; + private bool _isDisposed; + + public IndexedProperty R => new(GetX, SetX); + + public IndexedProperty Q => new(GetQ, SetQ); + + public uint LR + { + get => GetRegister(Arm.UC_ARM_REG_LR); + set => SetRegister(Arm.UC_ARM_REG_LR, value); + } + + public uint SP + { + get => GetRegister(Arm.UC_ARM_REG_SP); + set => SetRegister(Arm.UC_ARM_REG_SP, value); + } + + public uint PC + { + get => GetRegister(Arm.UC_ARM_REG_PC) & 0xfffffffeu; + set => SetRegister(Arm.UC_ARM_REG_PC, (value & 0xfffffffeu) | (ThumbFlag ? 1u : 0u)); + } + + public uint CPSR + { + get => GetRegister(Arm.UC_ARM_REG_CPSR); + set => SetRegister(Arm.UC_ARM_REG_CPSR, value); + } + + public int Fpscr + { + get => (int)GetRegister(Arm.UC_ARM_REG_FPSCR) | ((int)GetRegister(Arm.UC_ARM_REG_FPSCR_NZCV)); + set => SetRegister(Arm.UC_ARM_REG_FPSCR, (uint)value); + } + + public bool QFlag + { + get => (CPSR & 0x8000000u) != 0; + set => CPSR = (CPSR & ~0x8000000u) | (value ? 0x8000000u : 0u); + } + + public bool OverflowFlag + { + get => (CPSR & 0x10000000u) != 0; + set => CPSR = (CPSR & ~0x10000000u) | (value ? 0x10000000u : 0u); + } + + public bool CarryFlag + { + get => (CPSR & 0x20000000u) != 0; + set => CPSR = (CPSR & ~0x20000000u) | (value ? 0x20000000u : 0u); + } + + public bool ZeroFlag + { + get => (CPSR & 0x40000000u) != 0; + set => CPSR = (CPSR & ~0x40000000u) | (value ? 0x40000000u : 0u); + } + + public bool NegativeFlag + { + get => (CPSR & 0x80000000u) != 0; + set => CPSR = (CPSR & ~0x80000000u) | (value ? 0x80000000u : 0u); + } + + public bool ThumbFlag + { + get => (CPSR & 0x00000020u) != 0; + set + { + CPSR = (CPSR & ~0x00000020u) | (value ? 0x00000020u : 0u); + SetRegister(Arm.UC_ARM_REG_PC, (GetRegister(Arm.UC_ARM_REG_PC) & 0xfffffffeu) | (value ? 1u : 0u)); + } + } + + public UnicornAArch32() + { + Uc = new UnicornEngine.Unicorn(Common.UC_ARCH_ARM, Common.UC_MODE_LITTLE_ENDIAN); + + SetRegister(Arm.UC_ARM_REG_C1_C0_2, GetRegister(Arm.UC_ARM_REG_C1_C0_2) | 0xf00000); + SetRegister(Arm.UC_ARM_REG_FPEXC, 0x40000000); + } + + ~UnicornAArch32() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_isDisposed) + { + Uc.Close(); + _isDisposed = true; + } + } + + public void RunForCount(ulong count) + { + // FIXME: untilAddr should be 0xFFFFFFFFFFFFFFFFu + Uc.EmuStart(this.PC, -1, 0, (long)count); + } + + public void Step() + { + RunForCount(1); + } + + private static readonly int[] _xRegisters = + { + Arm.UC_ARM_REG_R0, + Arm.UC_ARM_REG_R1, + Arm.UC_ARM_REG_R2, + Arm.UC_ARM_REG_R3, + Arm.UC_ARM_REG_R4, + Arm.UC_ARM_REG_R5, + Arm.UC_ARM_REG_R6, + Arm.UC_ARM_REG_R7, + Arm.UC_ARM_REG_R8, + Arm.UC_ARM_REG_R9, + Arm.UC_ARM_REG_R10, + Arm.UC_ARM_REG_R11, + Arm.UC_ARM_REG_R12, + Arm.UC_ARM_REG_R13, + Arm.UC_ARM_REG_R14, + Arm.UC_ARM_REG_R15, + }; + +#pragma warning disable IDE0051, IDE0052 // Remove unused private member + private static readonly int[] _qRegisters = + { + Arm.UC_ARM_REG_Q0, + Arm.UC_ARM_REG_Q1, + Arm.UC_ARM_REG_Q2, + Arm.UC_ARM_REG_Q3, + Arm.UC_ARM_REG_Q4, + Arm.UC_ARM_REG_Q5, + Arm.UC_ARM_REG_Q6, + Arm.UC_ARM_REG_Q7, + Arm.UC_ARM_REG_Q8, + Arm.UC_ARM_REG_Q9, + Arm.UC_ARM_REG_Q10, + Arm.UC_ARM_REG_Q11, + Arm.UC_ARM_REG_Q12, + Arm.UC_ARM_REG_Q13, + Arm.UC_ARM_REG_Q14, + Arm.UC_ARM_REG_Q15, + }; +#pragma warning restore IDE0051, IDE0052 + + public uint GetX(int index) + { + if ((uint)index > 15) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return GetRegister(_xRegisters[index]); + } + + public void SetX(int index, uint value) + { + if ((uint)index > 15) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + SetRegister(_xRegisters[index], value); + } + + public SimdValue GetQ(int index) + { + if ((uint)index > 15) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + // Getting quadword registers from Unicorn A32 seems to be broken, so we combine its 2 doubleword registers instead. + return GetVector(Arm.UC_ARM_REG_D0 + index * 2); + } + + public void SetQ(int index, SimdValue value) + { + if ((uint)index > 15) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + SetVector(Arm.UC_ARM_REG_D0 + index * 2, value); + } + + public uint GetRegister(int register) + { + byte[] data = new byte[4]; + + Uc.RegRead(register, data); + + return BitConverter.ToUInt32(data, 0); + } + + public void SetRegister(int register, uint value) + { + byte[] data = BitConverter.GetBytes(value); + + Uc.RegWrite(register, data); + } + + public SimdValue GetVector(int register) + { + byte[] data = new byte[8]; + + Uc.RegRead(register, data); + ulong lo = BitConverter.ToUInt64(data, 0); + Uc.RegRead(register + 1, data); + ulong hi = BitConverter.ToUInt64(data, 0); + + return new SimdValue(lo, hi); + } + + private void SetVector(int register, SimdValue value) + { + byte[] data = BitConverter.GetBytes(value.GetUInt64(0)); + Uc.RegWrite(register, data); + data = BitConverter.GetBytes(value.GetUInt64(1)); + Uc.RegWrite(register + 1, data); + } + + public byte[] MemoryRead(ulong address, ulong size) + { + byte[] value = new byte[size]; + + Uc.MemRead((long)address, value); + + return value; + } + + public byte MemoryRead8(ulong address) => MemoryRead(address, 1)[0]; + public ushort MemoryRead16(ulong address) => BitConverter.ToUInt16(MemoryRead(address, 2), 0); + public uint MemoryRead32(ulong address) => BitConverter.ToUInt32(MemoryRead(address, 4), 0); + public ulong MemoryRead64(ulong address) => BitConverter.ToUInt64(MemoryRead(address, 8), 0); + + public void MemoryWrite(ulong address, byte[] value) + { + Uc.MemWrite((long)address, value); + } + + public void MemoryWrite8(ulong address, byte value) => MemoryWrite(address, new[] { value }); + public void MemoryWrite16(ulong address, short value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite16(ulong address, ushort value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite32(ulong address, int value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite32(ulong address, uint value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite64(ulong address, long value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite64(ulong address, ulong value) => MemoryWrite(address, BitConverter.GetBytes(value)); + + public void MemoryMap(ulong address, ulong size, MemoryPermission permissions) + { + Uc.MemMap((long)address, (long)size, (int)permissions); + } + + public void MemoryUnmap(ulong address, ulong size) + { + Uc.MemUnmap((long)address, (long)size); + } + + public void MemoryProtect(ulong address, ulong size, MemoryPermission permissions) + { + Uc.MemProtect((long)address, (long)size, (int)permissions); + } + } +} diff --git a/src/Ryujinx.Tests.Unicorn/UnicornAArch64.cs b/src/Ryujinx.Tests.Unicorn/UnicornAArch64.cs new file mode 100644 index 00000000..bdb53558 --- /dev/null +++ b/src/Ryujinx.Tests.Unicorn/UnicornAArch64.cs @@ -0,0 +1,298 @@ +using System; +using UnicornEngine.Const; + +namespace Ryujinx.Tests.Unicorn +{ + public class UnicornAArch64 : IDisposable + { + internal readonly UnicornEngine.Unicorn Uc; + private bool _isDisposed; + + public IndexedProperty X => new(GetX, SetX); + + public IndexedProperty Q => new(GetQ, SetQ); + + public ulong LR + { + get => GetRegister(Arm64.UC_ARM64_REG_LR); + set => SetRegister(Arm64.UC_ARM64_REG_LR, value); + } + + public ulong SP + { + get => GetRegister(Arm64.UC_ARM64_REG_SP); + set => SetRegister(Arm64.UC_ARM64_REG_SP, value); + } + + public ulong PC + { + get => GetRegister(Arm64.UC_ARM64_REG_PC); + set => SetRegister(Arm64.UC_ARM64_REG_PC, value); + } + + public uint Pstate + { + get => (uint)GetRegister(Arm64.UC_ARM64_REG_PSTATE); + set => SetRegister(Arm64.UC_ARM64_REG_PSTATE, value); + } + + public int Fpcr + { + get => (int)GetRegister(Arm64.UC_ARM64_REG_FPCR); + set => SetRegister(Arm64.UC_ARM64_REG_FPCR, (uint)value); + } + + public int Fpsr + { + get => (int)GetRegister(Arm64.UC_ARM64_REG_FPSR); + set => SetRegister(Arm64.UC_ARM64_REG_FPSR, (uint)value); + } + + public bool OverflowFlag + { + get => (Pstate & 0x10000000u) != 0; + set => Pstate = (Pstate & ~0x10000000u) | (value ? 0x10000000u : 0u); + } + + public bool CarryFlag + { + get => (Pstate & 0x20000000u) != 0; + set => Pstate = (Pstate & ~0x20000000u) | (value ? 0x20000000u : 0u); + } + + public bool ZeroFlag + { + get => (Pstate & 0x40000000u) != 0; + set => Pstate = (Pstate & ~0x40000000u) | (value ? 0x40000000u : 0u); + } + + public bool NegativeFlag + { + get => (Pstate & 0x80000000u) != 0; + set => Pstate = (Pstate & ~0x80000000u) | (value ? 0x80000000u : 0u); + } + + public UnicornAArch64() + { + Uc = new UnicornEngine.Unicorn(Common.UC_ARCH_ARM64, Common.UC_MODE_LITTLE_ENDIAN); + + SetRegister(Arm64.UC_ARM64_REG_CPACR_EL1, 0x00300000); + } + + ~UnicornAArch64() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_isDisposed) + { + Uc.Close(); + _isDisposed = true; + } + } + + public void RunForCount(ulong count) + { + // FIXME: untilAddr should be 0xFFFFFFFFFFFFFFFFul + Uc.EmuStart((long)this.PC, -1, 0, (long)count); + } + + public void Step() + { + RunForCount(1); + } + + private static readonly int[] _xRegisters = + { + Arm64.UC_ARM64_REG_X0, + Arm64.UC_ARM64_REG_X1, + Arm64.UC_ARM64_REG_X2, + Arm64.UC_ARM64_REG_X3, + Arm64.UC_ARM64_REG_X4, + Arm64.UC_ARM64_REG_X5, + Arm64.UC_ARM64_REG_X6, + Arm64.UC_ARM64_REG_X7, + Arm64.UC_ARM64_REG_X8, + Arm64.UC_ARM64_REG_X9, + Arm64.UC_ARM64_REG_X10, + Arm64.UC_ARM64_REG_X11, + Arm64.UC_ARM64_REG_X12, + Arm64.UC_ARM64_REG_X13, + Arm64.UC_ARM64_REG_X14, + Arm64.UC_ARM64_REG_X15, + Arm64.UC_ARM64_REG_X16, + Arm64.UC_ARM64_REG_X17, + Arm64.UC_ARM64_REG_X18, + Arm64.UC_ARM64_REG_X19, + Arm64.UC_ARM64_REG_X20, + Arm64.UC_ARM64_REG_X21, + Arm64.UC_ARM64_REG_X22, + Arm64.UC_ARM64_REG_X23, + Arm64.UC_ARM64_REG_X24, + Arm64.UC_ARM64_REG_X25, + Arm64.UC_ARM64_REG_X26, + Arm64.UC_ARM64_REG_X27, + Arm64.UC_ARM64_REG_X28, + Arm64.UC_ARM64_REG_X29, + Arm64.UC_ARM64_REG_X30, + }; + + private static readonly int[] _qRegisters = + { + Arm64.UC_ARM64_REG_Q0, + Arm64.UC_ARM64_REG_Q1, + Arm64.UC_ARM64_REG_Q2, + Arm64.UC_ARM64_REG_Q3, + Arm64.UC_ARM64_REG_Q4, + Arm64.UC_ARM64_REG_Q5, + Arm64.UC_ARM64_REG_Q6, + Arm64.UC_ARM64_REG_Q7, + Arm64.UC_ARM64_REG_Q8, + Arm64.UC_ARM64_REG_Q9, + Arm64.UC_ARM64_REG_Q10, + Arm64.UC_ARM64_REG_Q11, + Arm64.UC_ARM64_REG_Q12, + Arm64.UC_ARM64_REG_Q13, + Arm64.UC_ARM64_REG_Q14, + Arm64.UC_ARM64_REG_Q15, + Arm64.UC_ARM64_REG_Q16, + Arm64.UC_ARM64_REG_Q17, + Arm64.UC_ARM64_REG_Q18, + Arm64.UC_ARM64_REG_Q19, + Arm64.UC_ARM64_REG_Q20, + Arm64.UC_ARM64_REG_Q21, + Arm64.UC_ARM64_REG_Q22, + Arm64.UC_ARM64_REG_Q23, + Arm64.UC_ARM64_REG_Q24, + Arm64.UC_ARM64_REG_Q25, + Arm64.UC_ARM64_REG_Q26, + Arm64.UC_ARM64_REG_Q27, + Arm64.UC_ARM64_REG_Q28, + Arm64.UC_ARM64_REG_Q29, + Arm64.UC_ARM64_REG_Q30, + Arm64.UC_ARM64_REG_Q31, + }; + + public ulong GetX(int index) + { + if ((uint)index > 30) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return GetRegister(_xRegisters[index]); + } + + public void SetX(int index, ulong value) + { + if ((uint)index > 30) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + SetRegister(_xRegisters[index], value); + } + + public SimdValue GetQ(int index) + { + if ((uint)index > 31) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return GetVector(_qRegisters[index]); + } + + public void SetQ(int index, SimdValue value) + { + if ((uint)index > 31) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + SetVector(_qRegisters[index], value); + } + + private ulong GetRegister(int register) + { + byte[] data = new byte[8]; + + Uc.RegRead(register, data); + + return BitConverter.ToUInt64(data, 0); + } + + private void SetRegister(int register, ulong value) + { + byte[] data = BitConverter.GetBytes(value); + + Uc.RegWrite(register, data); + } + + private SimdValue GetVector(int register) + { + byte[] data = new byte[16]; + + Uc.RegRead(register, data); + + return new SimdValue(data); + } + + private void SetVector(int register, SimdValue value) + { + byte[] data = value.ToArray(); + + Uc.RegWrite(register, data); + } + + public byte[] MemoryRead(ulong address, ulong size) + { + byte[] value = new byte[size]; + + Uc.MemRead((long)address, value); + + return value; + } + + public byte MemoryRead8(ulong address) => MemoryRead(address, 1)[0]; + public ushort MemoryRead16(ulong address) => BitConverter.ToUInt16(MemoryRead(address, 2), 0); + public uint MemoryRead32(ulong address) => BitConverter.ToUInt32(MemoryRead(address, 4), 0); + public ulong MemoryRead64(ulong address) => BitConverter.ToUInt64(MemoryRead(address, 8), 0); + + public void MemoryWrite(ulong address, byte[] value) + { + Uc.MemWrite((long)address, value); + } + + public void MemoryWrite8(ulong address, byte value) => MemoryWrite(address, new[] { value }); + public void MemoryWrite16(ulong address, short value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite16(ulong address, ushort value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite32(ulong address, int value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite32(ulong address, uint value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite64(ulong address, long value) => MemoryWrite(address, BitConverter.GetBytes(value)); + public void MemoryWrite64(ulong address, ulong value) => MemoryWrite(address, BitConverter.GetBytes(value)); + + public void MemoryMap(ulong address, ulong size, MemoryPermission permissions) + { + Uc.MemMap((long)address, (long)size, (int)permissions); + } + + public void MemoryUnmap(ulong address, ulong size) + { + Uc.MemUnmap((long)address, (long)size); + } + + public void MemoryProtect(ulong address, ulong size, MemoryPermission permissions) + { + Uc.MemProtect((long)address, (long)size, (int)permissions); + } + } +} diff --git a/src/Ryujinx.Tests/.runsettings b/src/Ryujinx.Tests/.runsettings new file mode 100644 index 00000000..ca70d359 --- /dev/null +++ b/src/Ryujinx.Tests/.runsettings @@ -0,0 +1,8 @@ + + + + + 1 + + + diff --git a/src/Ryujinx.Tests/Audio/Renderer/AudioRendererConfigurationTests.cs b/src/Ryujinx.Tests/Audio/Renderer/AudioRendererConfigurationTests.cs new file mode 100644 index 00000000..845b6493 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/AudioRendererConfigurationTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class AudioRendererConfigurationTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x34, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/BehaviourParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/BehaviourParameterTests.cs new file mode 100644 index 00000000..1d8c8964 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/BehaviourParameterTests.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class BehaviourParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x10, Unsafe.SizeOf()); + Assert.AreEqual(0x10, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/BiquadFilterParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/BiquadFilterParameterTests.cs new file mode 100644 index 00000000..617b5245 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/BiquadFilterParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class BiquadFilterParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0xC, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Common/UpdateDataHeaderTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Common/UpdateDataHeaderTests.cs new file mode 100644 index 00000000..256679f7 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Common/UpdateDataHeaderTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Common +{ + class UpdateDataHeaderTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x40, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Common/VoiceUpdateStateTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Common/VoiceUpdateStateTests.cs new file mode 100644 index 00000000..7b09d18c --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Common/VoiceUpdateStateTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Common +{ + class VoiceUpdateStateTests + { + [Test] + public void EnsureTypeSize() + { + Assert.LessOrEqual(Unsafe.SizeOf(), 0x100); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Common/WaveBufferTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Common/WaveBufferTests.cs new file mode 100644 index 00000000..91f9c056 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Common/WaveBufferTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Common +{ + class WaveBufferTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x30, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Dsp/ResamplerTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Dsp/ResamplerTests.cs new file mode 100644 index 00000000..f393c971 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Dsp/ResamplerTests.cs @@ -0,0 +1,86 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Dsp; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Tests.Audio.Renderer.Dsp +{ + class ResamplerTests + { + [Test] + [TestCase(VoiceInParameter.SampleRateConversionQuality.Low)] + [TestCase(VoiceInParameter.SampleRateConversionQuality.Default)] + [TestCase(VoiceInParameter.SampleRateConversionQuality.High)] + public void TestResamplerConsistencyUpsampling(VoiceInParameter.SampleRateConversionQuality quality) + { + DoResamplingTest(44100, 48000, quality); + } + + [Test] + [TestCase(VoiceInParameter.SampleRateConversionQuality.Low)] + [TestCase(VoiceInParameter.SampleRateConversionQuality.Default)] + [TestCase(VoiceInParameter.SampleRateConversionQuality.High)] + public void TestResamplerConsistencyDownsampling(VoiceInParameter.SampleRateConversionQuality quality) + { + DoResamplingTest(48000, 44100, quality); + } + + /// + /// Generates a 1-second sine wave sample at input rate, resamples it to output rate, and + /// ensures that it resampled at the expected rate with no discontinuities + /// + /// The input sample rate to test + /// The output sample rate to test + /// The resampler quality to use + private static void DoResamplingTest(int inputRate, int outputRate, VoiceInParameter.SampleRateConversionQuality quality) + { + float inputSampleRate = inputRate; + float outputSampleRate = outputRate; + int inputSampleCount = inputRate; + int outputSampleCount = outputRate; + short[] inputBuffer = new short[inputSampleCount + 100]; // add some safety buffer at the end + float[] outputBuffer = new float[outputSampleCount + 100]; + for (int sample = 0; sample < inputBuffer.Length; sample++) + { + // 440 hz sine wave with amplitude = 0.5f at input sample rate + inputBuffer[sample] = (short)(32767 * MathF.Sin((440 / inputSampleRate) * sample * MathF.PI * 2f) * 0.5f); + } + + float fraction = 0; + + ResamplerHelper.Resample( + outputBuffer.AsSpan(), + inputBuffer.AsSpan(), + inputSampleRate / outputSampleRate, + ref fraction, + outputSampleCount, + quality, + false); + + float[] expectedOutput = new float[outputSampleCount]; + float sumDifference = 0; + int delay = quality switch + { + VoiceInParameter.SampleRateConversionQuality.High => 3, + VoiceInParameter.SampleRateConversionQuality.Default => 1, + _ => 0, + }; + + for (int sample = 0; sample < outputSampleCount; sample++) + { + outputBuffer[sample] /= 32767; + // 440 hz sine wave with amplitude = 0.5f at output sample rate + expectedOutput[sample] = MathF.Sin((440 / outputSampleRate) * (sample + delay) * MathF.PI * 2f) * 0.5f; + float thisDelta = Math.Abs(expectedOutput[sample] - outputBuffer[sample]); + + // Ensure no discontinuities + Assert.IsTrue(thisDelta < 0.1f); + sumDifference += thisDelta; + } + + sumDifference /= outputSampleCount; + // Expect the output to be 99% similar to the expected resampled sine wave + Assert.IsTrue(sumDifference < 0.01f); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Dsp/UpsamplerTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Dsp/UpsamplerTests.cs new file mode 100644 index 00000000..3b1b9444 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Dsp/UpsamplerTests.cs @@ -0,0 +1,57 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Dsp; +using Ryujinx.Audio.Renderer.Server.Upsampler; +using System; + +namespace Ryujinx.Tests.Audio.Renderer.Dsp +{ + class UpsamplerTests + { + [Test] + public void TestUpsamplerConsistency() + { + UpsamplerBufferState bufferState = new(); + int inputBlockSize = 160; + int numInputSamples = 32000; + int numOutputSamples = 48000; + float inputSampleRate = numInputSamples; + float outputSampleRate = numOutputSamples; + float[] inputBuffer = new float[numInputSamples + 100]; + float[] outputBuffer = new float[numOutputSamples + 100]; + for (int sample = 0; sample < inputBuffer.Length; sample++) + { + // 440 hz sine wave with amplitude = 0.5f at input sample rate + inputBuffer[sample] = MathF.Sin((440 / inputSampleRate) * sample * MathF.PI * 2f) * 0.5f; + } + + int inputIdx = 0; + int outputIdx = 0; + while (inputIdx + inputBlockSize < numInputSamples) + { + int outputBufLength = (int)Math.Round((inputIdx + inputBlockSize) * outputSampleRate / inputSampleRate) - outputIdx; + UpsamplerHelper.Upsample( + outputBuffer.AsSpan(outputIdx), + inputBuffer.AsSpan(inputIdx), + outputBufLength, + inputBlockSize, + ref bufferState); + + inputIdx += inputBlockSize; + outputIdx += outputBufLength; + } + + float[] expectedOutput = new float[numOutputSamples]; + float sumDifference = 0; + for (int sample = 0; sample < numOutputSamples; sample++) + { + // 440 hz sine wave with amplitude = 0.5f at output sample rate with an offset of 15 + expectedOutput[sample] = MathF.Sin((440 / outputSampleRate) * (sample - 15) * MathF.PI * 2f) * 0.5f; + sumDifference += Math.Abs(expectedOutput[sample] - outputBuffer[sample]); + } + + sumDifference /= expectedOutput.Length; + // Expect the output to be 98% similar to the expected resampled sine wave + Assert.IsTrue(sumDifference < 0.02f); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs new file mode 100644 index 00000000..ee28423b --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class EffectInfoParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0xC0, Unsafe.SizeOf()); + Assert.AreEqual(0xC0, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs b/src/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs new file mode 100644 index 00000000..2a444943 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class EffectOutStatusTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x10, Unsafe.SizeOf()); + Assert.AreEqual(0x90, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/MemoryPoolParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/MemoryPoolParameterTests.cs new file mode 100644 index 00000000..733629a9 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/MemoryPoolParameterTests.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class MemoryPoolParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x20, Unsafe.SizeOf()); + Assert.AreEqual(0x10, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/BehaviourErrorInfoOutStatusTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/BehaviourErrorInfoOutStatusTests.cs new file mode 100644 index 00000000..a2172956 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/BehaviourErrorInfoOutStatusTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class BehaviourErrorInfoOutStatusTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0xB0, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/AuxParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/AuxParameterTests.cs new file mode 100644 index 00000000..75b24c40 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/AuxParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class AuxParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x6C, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameterTests.cs new file mode 100644 index 00000000..73c1ea9d --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class BiquadFilterEffectParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x18, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BufferMixerParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BufferMixerParameterTests.cs new file mode 100644 index 00000000..7a86e373 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BufferMixerParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class BufferMixerParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x94, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/CompressorParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/CompressorParameterTests.cs new file mode 100644 index 00000000..de7733ae --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/CompressorParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class CompressorParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x38, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/DelayParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/DelayParameterTests.cs new file mode 100644 index 00000000..3daaf9a1 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/DelayParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class DelayParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x35, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterParameterTests.cs new file mode 100644 index 00000000..b7499956 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class LimiterParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x44, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterStatisticsTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterStatisticsTests.cs new file mode 100644 index 00000000..9b3bfb13 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterStatisticsTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class LimiterStatisticsTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x30, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/Reverb3dParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/Reverb3dParameterTests.cs new file mode 100644 index 00000000..a5d562fb --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/Reverb3dParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class Reverb3dParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x49, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/ReverbParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/ReverbParameterTests.cs new file mode 100644 index 00000000..6cc103f8 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/ReverbParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Effect +{ + class ReverbParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x41, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdateTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdateTests.cs new file mode 100644 index 00000000..148e8a5d --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdateTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class MixInParameterDirtyOnlyUpdateTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x20, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/MixParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/MixParameterTests.cs new file mode 100644 index 00000000..d9c0e1c7 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/MixParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class MixParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x930, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceInParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceInParameterTests.cs new file mode 100644 index 00000000..685b9ed5 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceInParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Performance; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class PerformanceInParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x10, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceOutStatusTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceOutStatusTests.cs new file mode 100644 index 00000000..eb39fe48 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceOutStatusTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Performance; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class PerformanceOutStatusTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x10, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/RendererInfoOutStatusTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/RendererInfoOutStatusTests.cs new file mode 100644 index 00000000..34f04965 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/RendererInfoOutStatusTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class RendererInfoOutStatusTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x10, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/CircularBufferParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/CircularBufferParameterTests.cs new file mode 100644 index 00000000..f5113fd0 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/CircularBufferParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Sink; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Sink +{ + class CircularBufferParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x24, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/DeviceParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/DeviceParameterTests.cs new file mode 100644 index 00000000..e7677c82 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/DeviceParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter.Sink; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter.Sink +{ + class DeviceParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x11C, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/SinkInParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/SinkInParameterTests.cs new file mode 100644 index 00000000..84f9cd9a --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/SinkInParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class SinkInParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x140, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/SinkOutStatusTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/SinkOutStatusTests.cs new file mode 100644 index 00000000..18c501d2 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/SinkOutStatusTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class SinkOutStatusTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x20, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Parameter/SplitterInParamHeaderTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Parameter/SplitterInParamHeaderTests.cs new file mode 100644 index 00000000..f7a2965f --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Parameter/SplitterInParamHeaderTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Parameter +{ + class SplitterInParamHeaderTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x20, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/AddressInfoTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/AddressInfoTests.cs new file mode 100644 index 00000000..53a66258 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/AddressInfoTests.cs @@ -0,0 +1,35 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class AddressInfoTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x20, Unsafe.SizeOf()); + } + + [Test] + public void TestGetReference() + { + MemoryPoolState[] memoryPoolState = new MemoryPoolState[1]; + memoryPoolState[0] = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + memoryPoolState[0].SetCpuAddress(0x1000000, 0x10000); + memoryPoolState[0].DspAddress = 0x4000000; + + AddressInfo addressInfo = AddressInfo.Create(0x1000000, 0x1000); + + addressInfo.ForceMappedDspAddress = 0x2000000; + + Assert.AreEqual(0x2000000, addressInfo.GetReference(true)); + + addressInfo.SetupMemoryPool(memoryPoolState.AsSpan()); + + Assert.AreEqual(0x4000000, addressInfo.GetReference(true)); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs new file mode 100644 index 00000000..3e48a5b4 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs @@ -0,0 +1,372 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + public class BehaviourContextTests + { + [Test] + public void TestCheckFeature() + { + int latestRevision = BehaviourContext.BaseRevisionMagic + BehaviourContext.LastRevision; + int previousRevision = BehaviourContext.BaseRevisionMagic + (BehaviourContext.LastRevision - 1); + int invalidRevision = BehaviourContext.BaseRevisionMagic + (BehaviourContext.LastRevision + 1); + + Assert.IsTrue(BehaviourContext.CheckFeatureSupported(latestRevision, latestRevision)); + Assert.IsFalse(BehaviourContext.CheckFeatureSupported(previousRevision, latestRevision)); + Assert.IsTrue(BehaviourContext.CheckFeatureSupported(latestRevision, previousRevision)); + // In case we get an invalid revision, this is supposed to auto default to REV1 internally.. idk what the hell Nintendo was thinking here.. + Assert.IsTrue(BehaviourContext.CheckFeatureSupported(invalidRevision, latestRevision)); + } + + [Test] + public void TestsMemoryPoolForceMappingEnabled() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision1); + + Assert.IsFalse(behaviourContext.IsMemoryPoolForceMappingEnabled()); + + behaviourContext.UpdateFlags(0x1); + + Assert.IsTrue(behaviourContext.IsMemoryPoolForceMappingEnabled()); + } + + [Test] + public void TestRevision1() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision1); + + Assert.IsFalse(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsFalse(behaviourContext.IsSplitterSupported()); + Assert.IsFalse(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsFalse(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsFalse(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsFalse(behaviourContext.IsSplitterBugFixed()); + Assert.IsFalse(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsFalse(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); + Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(1, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision2() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision2); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsFalse(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsFalse(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsFalse(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsFalse(behaviourContext.IsSplitterBugFixed()); + Assert.IsFalse(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsFalse(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); + Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(1, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision3() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision3); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsFalse(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsFalse(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsFalse(behaviourContext.IsSplitterBugFixed()); + Assert.IsFalse(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsFalse(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); + Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(1, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision4() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision4); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsFalse(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsFalse(behaviourContext.IsSplitterBugFixed()); + Assert.IsFalse(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsFalse(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); + Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(1, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision5() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision5); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsTrue(behaviourContext.IsSplitterBugFixed()); + Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); + Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision6() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision6); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsTrue(behaviourContext.IsSplitterBugFixed()); + Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); + Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision7() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision7); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsTrue(behaviourContext.IsSplitterBugFixed()); + Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); + Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision8() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision8); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsTrue(behaviourContext.IsSplitterBugFixed()); + Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); + Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision9() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision9); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsTrue(behaviourContext.IsSplitterBugFixed()); + Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); + Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision10() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision10); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsTrue(behaviourContext.IsSplitterBugFixed()); + Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); + Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported()); + Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision11() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision11); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsTrue(behaviourContext.IsSplitterBugFixed()); + Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); + Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported()); + Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision12() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision12); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsTrue(behaviourContext.IsSplitterBugFixed()); + Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); + Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported()); + Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/MemoryPoolStateTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/MemoryPoolStateTests.cs new file mode 100644 index 00000000..c6a2e473 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/MemoryPoolStateTests.cs @@ -0,0 +1,62 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class MemoryPoolStateTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(Unsafe.SizeOf(), 0x20); + } + + [Test] + public void TestContains() + { + MemoryPoolState memoryPool = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + + memoryPool.SetCpuAddress(0x1000000, 0x1000); + + memoryPool.DspAddress = 0x2000000; + + Assert.IsTrue(memoryPool.Contains(0x1000000, 0x10)); + Assert.IsTrue(memoryPool.Contains(0x1000FE0, 0x10)); + Assert.IsTrue(memoryPool.Contains(0x1000FFF, 0x1)); + Assert.IsFalse(memoryPool.Contains(0x1000FFF, 0x2)); + Assert.IsFalse(memoryPool.Contains(0x1001000, 0x10)); + Assert.IsFalse(memoryPool.Contains(0x2000000, 0x10)); + } + + [Test] + public void TestTranslate() + { + MemoryPoolState memoryPool = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + + memoryPool.SetCpuAddress(0x1000000, 0x1000); + + memoryPool.DspAddress = 0x2000000; + + Assert.AreEqual(0x2000FE0, memoryPool.Translate(0x1000FE0, 0x10)); + Assert.AreEqual(0x2000FFF, memoryPool.Translate(0x1000FFF, 0x1)); + Assert.AreEqual(0x0, memoryPool.Translate(0x1000FFF, 0x2)); + Assert.AreEqual(0x0, memoryPool.Translate(0x1001000, 0x10)); + Assert.AreEqual(0x0, memoryPool.Translate(0x2000000, 0x10)); + } + + [Test] + public void TestIsMapped() + { + MemoryPoolState memoryPool = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + + memoryPool.SetCpuAddress(0x1000000, 0x1000); + + Assert.IsFalse(memoryPool.IsMapped()); + + memoryPool.DspAddress = 0x2000000; + + Assert.IsTrue(memoryPool.IsMapped()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/MixStateTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/MixStateTests.cs new file mode 100644 index 00000000..6262913b --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/MixStateTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.Mix; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class MixStateTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x940, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/PoolMapperTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/PoolMapperTests.cs new file mode 100644 index 00000000..d7879d62 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/PoolMapperTests.cs @@ -0,0 +1,134 @@ +using NUnit.Framework; +using Ryujinx.Audio; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class PoolMapperTests + { + private const uint DummyProcessHandle = 0xCAFEBABE; + + [Test] + public void TestInitializeSystemPool() + { + PoolMapper poolMapper = new(DummyProcessHandle, true); + MemoryPoolState memoryPoolDsp = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp); + MemoryPoolState memoryPoolCpu = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + + const CpuAddress CpuAddress = 0x20000; + const DspAddress DspAddress = CpuAddress; // TODO: DSP LLE + const ulong CpuSize = 0x1000; + + Assert.IsFalse(poolMapper.InitializeSystemPool(ref memoryPoolCpu, CpuAddress, CpuSize)); + Assert.IsTrue(poolMapper.InitializeSystemPool(ref memoryPoolDsp, CpuAddress, CpuSize)); + + Assert.AreEqual(CpuAddress, memoryPoolDsp.CpuAddress); + Assert.AreEqual(CpuSize, memoryPoolDsp.Size); + Assert.AreEqual(DspAddress, memoryPoolDsp.DspAddress); + } + + [Test] + public void TestGetProcessHandle() + { + PoolMapper poolMapper = new(DummyProcessHandle, true); + MemoryPoolState memoryPoolDsp = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp); + MemoryPoolState memoryPoolCpu = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + + Assert.AreEqual(0xFFFF8001, poolMapper.GetProcessHandle(ref memoryPoolCpu)); + Assert.AreEqual(DummyProcessHandle, poolMapper.GetProcessHandle(ref memoryPoolDsp)); + } + + [Test] + public void TestMappings() + { + PoolMapper poolMapper = new(DummyProcessHandle, true); + MemoryPoolState memoryPoolDsp = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp); + MemoryPoolState memoryPoolCpu = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + + const CpuAddress CpuAddress = 0x20000; + const DspAddress DspAddress = CpuAddress; // TODO: DSP LLE + const ulong CpuSize = 0x1000; + + memoryPoolDsp.SetCpuAddress(CpuAddress, CpuSize); + memoryPoolCpu.SetCpuAddress(CpuAddress, CpuSize); + + Assert.AreEqual(DspAddress, poolMapper.Map(ref memoryPoolCpu)); + Assert.AreEqual(DspAddress, poolMapper.Map(ref memoryPoolDsp)); + Assert.AreEqual(DspAddress, memoryPoolDsp.DspAddress); + Assert.IsTrue(poolMapper.Unmap(ref memoryPoolCpu)); + + memoryPoolDsp.IsUsed = true; + Assert.IsFalse(poolMapper.Unmap(ref memoryPoolDsp)); + memoryPoolDsp.IsUsed = false; + Assert.IsTrue(poolMapper.Unmap(ref memoryPoolDsp)); + } + + [Test] + public void TestTryAttachBuffer() + { + const CpuAddress CpuAddress = 0x20000; + const DspAddress DspAddress = CpuAddress; // TODO: DSP LLE + const ulong CpuSize = 0x1000; + + const int MemoryPoolStateArraySize = 0x10; + const CpuAddress CpuAddressRegionEnding = CpuAddress * MemoryPoolStateArraySize; + + MemoryPoolState[] memoryPoolStateArray = new MemoryPoolState[MemoryPoolStateArraySize]; + + for (int i = 0; i < memoryPoolStateArray.Length; i++) + { + memoryPoolStateArray[i] = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + memoryPoolStateArray[i].SetCpuAddress(CpuAddress + (ulong)i * CpuSize, CpuSize); + } + + + AddressInfo addressInfo = AddressInfo.Create(); + + PoolMapper poolMapper = new(DummyProcessHandle, true); + + Assert.IsTrue(poolMapper.TryAttachBuffer(out ErrorInfo errorInfo, ref addressInfo, 0, 0)); + + Assert.AreEqual(ResultCode.InvalidAddressInfo, errorInfo.ErrorCode); + Assert.AreEqual(0, errorInfo.ExtraErrorInfo); + Assert.AreEqual(0, addressInfo.ForceMappedDspAddress); + + Assert.IsTrue(poolMapper.TryAttachBuffer(out errorInfo, ref addressInfo, CpuAddress, CpuSize)); + + Assert.AreEqual(ResultCode.InvalidAddressInfo, errorInfo.ErrorCode); + Assert.AreEqual(CpuAddress, errorInfo.ExtraErrorInfo); + Assert.AreEqual(DspAddress, addressInfo.ForceMappedDspAddress); + + poolMapper = new PoolMapper(DummyProcessHandle, false); + + Assert.IsFalse(poolMapper.TryAttachBuffer(out _, ref addressInfo, 0, 0)); + + addressInfo.ForceMappedDspAddress = 0; + + Assert.IsFalse(poolMapper.TryAttachBuffer(out errorInfo, ref addressInfo, CpuAddress, CpuSize)); + + Assert.AreEqual(ResultCode.InvalidAddressInfo, errorInfo.ErrorCode); + Assert.AreEqual(CpuAddress, errorInfo.ExtraErrorInfo); + Assert.AreEqual(0, addressInfo.ForceMappedDspAddress); + + poolMapper = new PoolMapper(DummyProcessHandle, memoryPoolStateArray.AsMemory(), false); + + Assert.IsFalse(poolMapper.TryAttachBuffer(out errorInfo, ref addressInfo, CpuAddressRegionEnding, CpuSize)); + + Assert.AreEqual(ResultCode.InvalidAddressInfo, errorInfo.ErrorCode); + Assert.AreEqual(CpuAddressRegionEnding, errorInfo.ExtraErrorInfo); + Assert.AreEqual(0, addressInfo.ForceMappedDspAddress); + Assert.IsFalse(addressInfo.HasMemoryPoolState); + + Assert.IsTrue(poolMapper.TryAttachBuffer(out errorInfo, ref addressInfo, CpuAddress, CpuSize)); + + Assert.AreEqual(ResultCode.Success, errorInfo.ErrorCode); + Assert.AreEqual(0, errorInfo.ExtraErrorInfo); + Assert.AreEqual(0, addressInfo.ForceMappedDspAddress); + Assert.IsTrue(addressInfo.HasMemoryPoolState); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs new file mode 100644 index 00000000..80b80133 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.Splitter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class SplitterDestinationTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0xE0, Unsafe.SizeOf()); + Assert.AreEqual(0x110, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterStateTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterStateTests.cs new file mode 100644 index 00000000..0421bd9d --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterStateTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.Splitter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class SplitterStateTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x20, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/VoiceChannelResourceTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/VoiceChannelResourceTests.cs new file mode 100644 index 00000000..565ac7a6 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/VoiceChannelResourceTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.Voice; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class VoiceChannelResourceTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0xD0, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/VoiceStateTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/VoiceStateTests.cs new file mode 100644 index 00000000..dbd6eff8 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/VoiceStateTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.Voice; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class VoiceStateTests + { + [Test] + public void EnsureTypeSize() + { + Assert.LessOrEqual(Unsafe.SizeOf(), 0x220); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/WaveBufferTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/WaveBufferTests.cs new file mode 100644 index 00000000..0e2ed0e8 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/WaveBufferTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Server.Voice; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer.Server +{ + class WaveBufferTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x58, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/VoiceChannelResourceInParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/VoiceChannelResourceInParameterTests.cs new file mode 100644 index 00000000..2bcfd32c --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/VoiceChannelResourceInParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class VoiceChannelResourceInParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x70, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/VoiceInParameterTests.cs b/src/Ryujinx.Tests/Audio/Renderer/VoiceInParameterTests.cs new file mode 100644 index 00000000..239da195 --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/VoiceInParameterTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class VoiceInParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x170, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Audio/Renderer/VoiceOutStatusTests.cs b/src/Ryujinx.Tests/Audio/Renderer/VoiceOutStatusTests.cs new file mode 100644 index 00000000..1579d89b --- /dev/null +++ b/src/Ryujinx.Tests/Audio/Renderer/VoiceOutStatusTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Ryujinx.Audio.Renderer.Parameter; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Audio.Renderer +{ + class VoiceOutStatusTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x10, Unsafe.SizeOf()); + } + } +} diff --git a/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs b/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs new file mode 100644 index 00000000..c0127530 --- /dev/null +++ b/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs @@ -0,0 +1,359 @@ +using NUnit.Framework; +using Ryujinx.Common.Extensions; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Tests.Common.Extensions +{ + public class SequenceReaderExtensionsTests + { + [TestCase(null)] + [TestCase(sizeof(int) + 1)] + public void GetRefOrRefToCopy_ReadsMultiSegmentedSequenceSuccessfully(int? maxSegmentSize) + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(3).ToArray(); + + ReadOnlySequence sequence = + CreateSegmentedByteSequence(originalStructs, maxSegmentSize ?? Unsafe.SizeOf()); + + var sequenceReader = new SequenceReader(sequence); + + foreach (var original in originalStructs) + { + // Act + ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy(out _); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + } + } + + [Test] + public void GetRefOrRefToCopy_FragmentedSequenceReturnsRefToCopy() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, 3); + + var sequenceReader = new SequenceReader(sequence); + + foreach (var original in originalStructs) + { + // Act + ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy(out var copy); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + MyUnmanagedStruct.Assert(Assert.AreEqual, read, copy); + } + } + + [Test] + public void GetRefOrRefToCopy_ContiguousSequenceReturnsRefToBuffer() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue); + + var sequenceReader = new SequenceReader(sequence); + + foreach (var original in originalStructs) + { + // Act + ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy(out var copy); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + MyUnmanagedStruct.Assert(Assert.AreNotEqual, read, copy); + } + } + + [Test] + public void GetRefOrRefToCopy_ThrowsWhenNotEnoughData() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue); + + // Act/Assert + Assert.Throws(() => + { + var sequenceReader = new SequenceReader(sequence); + + sequenceReader.Advance(1); + + ref readonly MyUnmanagedStruct result = ref sequenceReader.GetRefOrRefToCopy(out _); + }); + } + + [Test] + public void ReadLittleEndian_Int32_RoundTripsSuccessfully() + { + // Arrange + const int TestValue = 0x1234abcd; + + byte[] buffer = new byte[sizeof(int)]; + + BinaryPrimitives.WriteInt32LittleEndian(buffer.AsSpan(), TestValue); + + var sequenceReader = new SequenceReader(new ReadOnlySequence(buffer)); + + // Act + sequenceReader.ReadLittleEndian(out int roundTrippedValue); + + // Assert + Assert.AreEqual(TestValue, roundTrippedValue); + } + + [Test] + public void ReadLittleEndian_Int32_ResultIsNotBigEndian() + { + // Arrange + const int TestValue = 0x1234abcd; + + byte[] buffer = new byte[sizeof(int)]; + + BinaryPrimitives.WriteInt32BigEndian(buffer.AsSpan(), TestValue); + + var sequenceReader = new SequenceReader(new ReadOnlySequence(buffer)); + + // Act + sequenceReader.ReadLittleEndian(out int roundTrippedValue); + + // Assert + Assert.AreNotEqual(TestValue, roundTrippedValue); + } + + [Test] + public void ReadLittleEndian_Int32_ThrowsWhenNotEnoughData() + { + // Arrange + const int TestValue = 0x1234abcd; + + byte[] buffer = new byte[sizeof(int)]; + + BinaryPrimitives.WriteInt32BigEndian(buffer.AsSpan(), TestValue); + + // Act/Assert + Assert.Throws(() => + { + var sequenceReader = new SequenceReader(new ReadOnlySequence(buffer)); + sequenceReader.Advance(1); + + sequenceReader.ReadLittleEndian(out int roundTrippedValue); + }); + } + + [Test] + public void ReadUnmanaged_ContiguousSequence_Succeeds() + => ReadUnmanaged_Succeeds(int.MaxValue); + + [Test] + public void ReadUnmanaged_FragmentedSequence_Succeeds() + => ReadUnmanaged_Succeeds(sizeof(int) + 1); + + [Test] + public void ReadUnmanaged_ThrowsWhenNotEnoughData() + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue); + + // Act/Assert + Assert.Throws(() => + { + var sequenceReader = new SequenceReader(sequence); + + sequenceReader.Advance(1); + + sequenceReader.ReadUnmanaged(out MyUnmanagedStruct read); + }); + } + + [Test] + public void SetConsumed_ContiguousSequence_SucceedsWhenValid() + => SetConsumed_SucceedsWhenValid(int.MaxValue); + + [Test] + public void SetConsumed_FragmentedSequence_SucceedsWhenValid() + => SetConsumed_SucceedsWhenValid(sizeof(int) + 1); + + [Test] + public void SetConsumed_ThrowsWhenBeyondActualLength() + { + const int StructCount = 2; + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(StructCount).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, MyUnmanagedStruct.SizeOf); + + Assert.Throws(() => + { + var sequenceReader = new SequenceReader(sequence); + + sequenceReader.SetConsumed(MyUnmanagedStruct.SizeOf * StructCount + 1); + }); + } + + private static void ReadUnmanaged_Succeeds(int maxSegmentLength) + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(3).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, maxSegmentLength); + + var sequenceReader = new SequenceReader(sequence); + + foreach (var original in originalStructs) + { + // Act + sequenceReader.ReadUnmanaged(out MyUnmanagedStruct read); + + // Assert + MyUnmanagedStruct.Assert(Assert.AreEqual, original, read); + } + } + + private static void SetConsumed_SucceedsWhenValid(int maxSegmentLength) + { + // Arrange + MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(2).ToArray(); + + ReadOnlySequence sequence = CreateSegmentedByteSequence(originalStructs, maxSegmentLength); + + var sequenceReader = new SequenceReader(sequence); + + static void SetConsumedAndAssert(scoped ref SequenceReader sequenceReader, long consumed) + { + sequenceReader.SetConsumed(consumed); + Assert.AreEqual(consumed, sequenceReader.Consumed); + } + + // Act/Assert + ref readonly MyUnmanagedStruct struct0A = ref sequenceReader.GetRefOrRefToCopy(out _); + + Assert.AreEqual(sequenceReader.Consumed, MyUnmanagedStruct.SizeOf); + + SetConsumedAndAssert(ref sequenceReader, 0); + + ref readonly MyUnmanagedStruct struct0B = ref sequenceReader.GetRefOrRefToCopy(out _); + + MyUnmanagedStruct.Assert(Assert.AreEqual, struct0A, struct0B); + + SetConsumedAndAssert(ref sequenceReader, 1); + + SetConsumedAndAssert(ref sequenceReader, MyUnmanagedStruct.SizeOf); + + ref readonly MyUnmanagedStruct struct1A = ref sequenceReader.GetRefOrRefToCopy(out _); + + SetConsumedAndAssert(ref sequenceReader, MyUnmanagedStruct.SizeOf); + + ref readonly MyUnmanagedStruct struct1B = ref sequenceReader.GetRefOrRefToCopy(out _); + + MyUnmanagedStruct.Assert(Assert.AreEqual, struct1A, struct1B); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct MyUnmanagedStruct + { + public int BehaviourSize; + public int MemoryPoolsSize; + public short VoicesSize; + public int VoiceResourcesSize; + public short EffectsSize; + public int RenderInfoSize; + + public unsafe fixed byte Reserved[16]; + + public static readonly int SizeOf = Unsafe.SizeOf(); + + public static unsafe MyUnmanagedStruct Generate(Random rng) + { + const int BaseInt32Value = 0x1234abcd; + const short BaseInt16Value = 0x5678; + + var result = new MyUnmanagedStruct + { + BehaviourSize = BaseInt32Value ^ rng.Next(), + MemoryPoolsSize = BaseInt32Value ^ rng.Next(), + VoicesSize = (short)(BaseInt16Value ^ rng.Next()), + VoiceResourcesSize = BaseInt32Value ^ rng.Next(), + EffectsSize = (short)(BaseInt16Value ^ rng.Next()), + RenderInfoSize = BaseInt32Value ^ rng.Next(), + }; + + Unsafe.Write(result.Reserved, rng.NextInt64()); + + return result; + } + + public static unsafe void Assert(Action assert, in MyUnmanagedStruct expected, in MyUnmanagedStruct actual) + { + assert(expected.BehaviourSize, actual.BehaviourSize); + assert(expected.MemoryPoolsSize, actual.MemoryPoolsSize); + assert(expected.VoicesSize, actual.VoicesSize); + assert(expected.VoiceResourcesSize, actual.VoiceResourcesSize); + assert(expected.EffectsSize, actual.EffectsSize); + assert(expected.RenderInfoSize, actual.RenderInfoSize); + + fixed (void* expectedReservedPtr = expected.Reserved) + fixed (void* actualReservedPtr = actual.Reserved) + { + long expectedReservedLong = Unsafe.Read(expectedReservedPtr); + long actualReservedLong = Unsafe.Read(actualReservedPtr); + + assert(expectedReservedLong, actualReservedLong); + } + } + } + + private static IEnumerable EnumerateNewUnmanagedStructs() + { + var rng = new Random(0); + + while (true) + { + yield return MyUnmanagedStruct.Generate(rng); + } + } + + private static ReadOnlySequence CreateSegmentedByteSequence(T[] array, int maxSegmentLength) where T : unmanaged + { + byte[] arrayBytes = MemoryMarshal.AsBytes(array.AsSpan()).ToArray(); + var memory = new Memory(arrayBytes); + int index = 0; + + BytesReadOnlySequenceSegment first = null, last = null; + + while (index < memory.Length) + { + int nextSegmentLength = Math.Min(maxSegmentLength, memory.Length - index); + var nextSegment = memory.Slice(index, nextSegmentLength); + + if (first == null) + { + first = last = new BytesReadOnlySequenceSegment(nextSegment); + } + else + { + last = last.Append(nextSegment); + } + + index += nextSegmentLength; + } + + return new ReadOnlySequence(first, 0, last, (int)(memory.Length - last.RunningIndex)); + } + } +} diff --git a/src/Ryujinx.Tests/Cpu/Arm64CodeGenCommonTests.cs b/src/Ryujinx.Tests/Cpu/Arm64CodeGenCommonTests.cs new file mode 100644 index 00000000..0092d9a1 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/Arm64CodeGenCommonTests.cs @@ -0,0 +1,46 @@ +using ARMeilleure.CodeGen.Arm64; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + public class Arm64CodeGenCommonTests + { + public struct TestCase + { + public ulong Value; + public bool Valid; + public int ImmN; + public int ImmS; + public int ImmR; + } + + public static readonly TestCase[] TestCases = + { + new() { Value = 0, Valid = false, ImmN = 0, ImmS = 0, ImmR = 0 }, + new() { Value = 0x970977f35f848714, Valid = false, ImmN = 0, ImmS = 0, ImmR = 0 }, + new() { Value = 0xffffffffffffffff, Valid = false, ImmN = 0, ImmS = 0, ImmR = 0 }, + new() { Value = 0x5555555555555555, Valid = true, ImmN = 0, ImmS = 0x3c, ImmR = 0 }, + new() { Value = 0xaaaaaaaaaaaaaaaa, Valid = true, ImmN = 0, ImmS = 0x3c, ImmR = 1 }, + new() { Value = 0x6666666666666666, Valid = true, ImmN = 0, ImmS = 0x39, ImmR = 3 }, + new() { Value = 0x1c1c1c1c1c1c1c1c, Valid = true, ImmN = 0, ImmS = 0x32, ImmR = 6 }, + new() { Value = 0x0f0f0f0f0f0f0f0f, Valid = true, ImmN = 0, ImmS = 0x33, ImmR = 0 }, + new() { Value = 0xf1f1f1f1f1f1f1f1, Valid = true, ImmN = 0, ImmS = 0x34, ImmR = 4 }, + new() { Value = 0xe7e7e7e7e7e7e7e7, Valid = true, ImmN = 0, ImmS = 0x35, ImmR = 3 }, + new() { Value = 0xc001c001c001c001, Valid = true, ImmN = 0, ImmS = 0x22, ImmR = 2 }, + new() { Value = 0x0000038000000380, Valid = true, ImmN = 0, ImmS = 0x02, ImmR = 25 }, + new() { Value = 0xffff8fffffff8fff, Valid = true, ImmN = 0, ImmS = 0x1c, ImmR = 17 }, + new() { Value = 0x000000000ffff800, Valid = true, ImmN = 1, ImmS = 0x10, ImmR = 53 }, + }; + + [Test] + public void BitImmTests([ValueSource(nameof(TestCases))] TestCase test) + { + bool valid = CodeGenCommon.TryEncodeBitMask(test.Value, out int immN, out int immS, out int immR); + + Assert.That(valid, Is.EqualTo(test.Valid)); + Assert.That(immN, Is.EqualTo(test.ImmN)); + Assert.That(immS, Is.EqualTo(test.ImmS)); + Assert.That(immR, Is.EqualTo(test.ImmR)); + } + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuContext.cs b/src/Ryujinx.Tests/Cpu/CpuContext.cs new file mode 100644 index 00000000..96b4965a --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuContext.cs @@ -0,0 +1,39 @@ +using ARMeilleure.Memory; +using ARMeilleure.State; +using ARMeilleure.Translation; +using Ryujinx.Cpu; +using Ryujinx.Cpu.Jit; + +namespace Ryujinx.Tests.Cpu +{ + public class CpuContext + { + private readonly Translator _translator; + + public CpuContext(IMemoryManager memory, bool for64Bit) + { + _translator = new Translator(new JitMemoryAllocator(), memory, for64Bit); + memory.UnmapEvent += UnmapHandler; + } + + private void UnmapHandler(ulong address, ulong size) + { + _translator.InvalidateJitCacheRegion(address, size); + } + + public static ExecutionContext CreateExecutionContext() + { + return new ExecutionContext(new JitMemoryAllocator(), new TickSource(19200000)); + } + + public void Execute(ExecutionContext context, ulong address) + { + _translator.Execute(context, address); + } + + public void InvalidateCacheRegion(ulong address, ulong size) + { + _translator.InvalidateJitCacheRegion(address, size); + } + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTest.cs b/src/Ryujinx.Tests/Cpu/CpuTest.cs new file mode 100644 index 00000000..da0f03e6 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTest.cs @@ -0,0 +1,604 @@ +using ARMeilleure; +using ARMeilleure.State; +using ARMeilleure.Translation; +using NUnit.Framework; +using Ryujinx.Cpu.Jit; +using Ryujinx.Memory; +using Ryujinx.Tests.Unicorn; +using System; +using MemoryPermission = Ryujinx.Tests.Unicorn.MemoryPermission; + +namespace Ryujinx.Tests.Cpu +{ + [TestFixture] + public class CpuTest + { + protected static readonly ulong Size = MemoryBlock.GetPageSize(); +#pragma warning disable CA2211 // Non-constant fields should not be visible + protected static ulong CodeBaseAddress = Size; + protected static ulong DataBaseAddress = CodeBaseAddress + Size; +#pragma warning restore CA2211 + + private static readonly bool _ignoreFpcrFz = false; + private static readonly bool _ignoreFpcrDn = false; + + private static readonly bool _ignoreAllExceptFpsrQc = false; + + private ulong _currAddress; + + private MemoryBlock _ram; + + private MemoryManager _memory; + + private ExecutionContext _context; + + private CpuContext _cpuContext; + + private UnicornAArch64 _unicornEmu; + + private bool _usingMemory; + + [SetUp] + public void Setup() + { + int pageBits = (int)ulong.Log2(Size); + + _ram = new MemoryBlock(Size * 2); + _memory = new MemoryManager(_ram, 1ul << (pageBits + 4)); + _memory.IncrementReferenceCount(); + + // Some tests depends on hardcoded address that were computed for 4KiB. + // We change the layout on non 4KiB platforms to keep compat here. + if (Size > 0x1000) + { + DataBaseAddress = 0; + CodeBaseAddress = Size; + } + + _currAddress = CodeBaseAddress; + + _memory.Map(CodeBaseAddress, 0, Size, MemoryMapFlags.Private); + _memory.Map(DataBaseAddress, Size, Size, MemoryMapFlags.Private); + + _context = CpuContext.CreateExecutionContext(); + + _cpuContext = new CpuContext(_memory, for64Bit: true); + + // Prevent registering LCQ functions in the FunctionTable to avoid initializing and populating the table, + // which improves test durations. + Optimizations.AllowLcqInFunctionTable = false; + Optimizations.UseUnmanagedDispatchLoop = false; + + _unicornEmu = new UnicornAArch64(); + _unicornEmu.MemoryMap(CodeBaseAddress, Size, MemoryPermission.Read | MemoryPermission.Exec); + _unicornEmu.MemoryMap(DataBaseAddress, Size, MemoryPermission.Read | MemoryPermission.Write); + _unicornEmu.PC = CodeBaseAddress; + } + + [TearDown] + public void Teardown() + { + _unicornEmu.Dispose(); + _unicornEmu = null; + + _memory.DecrementReferenceCount(); + _context.Dispose(); + _ram.Dispose(); + + _memory = null; + _context = null; + _cpuContext = null; + _unicornEmu = null; + + _usingMemory = false; + } + + protected void Reset() + { + Teardown(); + Setup(); + } + + protected void Opcode(uint opcode) + { + _memory.Write(_currAddress, opcode); + + _unicornEmu.MemoryWrite32(_currAddress, opcode); + + _currAddress += 4; + } + + protected ExecutionContext GetContext() => _context; + + protected void SetContext(ulong x0 = 0, + ulong x1 = 0, + ulong x2 = 0, + ulong x3 = 0, + ulong x31 = 0, + V128 v0 = default, + V128 v1 = default, + V128 v2 = default, + V128 v3 = default, + V128 v4 = default, + V128 v5 = default, + V128 v30 = default, + V128 v31 = default, + bool overflow = false, + bool carry = false, + bool zero = false, + bool negative = false, + int fpcr = 0, + int fpsr = 0) + { + _context.SetX(0, x0); + _context.SetX(1, x1); + _context.SetX(2, x2); + _context.SetX(3, x3); + _context.SetX(31, x31); + + _context.SetV(0, v0); + _context.SetV(1, v1); + _context.SetV(2, v2); + _context.SetV(3, v3); + _context.SetV(4, v4); + _context.SetV(5, v5); + _context.SetV(30, v30); + _context.SetV(31, v31); + + _context.SetPstateFlag(PState.VFlag, overflow); + _context.SetPstateFlag(PState.CFlag, carry); + _context.SetPstateFlag(PState.ZFlag, zero); + _context.SetPstateFlag(PState.NFlag, negative); + + _context.Fpcr = (FPCR)fpcr; + _context.Fpsr = (FPSR)fpsr; + + _unicornEmu.X[0] = x0; + _unicornEmu.X[1] = x1; + _unicornEmu.X[2] = x2; + _unicornEmu.X[3] = x3; + _unicornEmu.SP = x31; + + _unicornEmu.Q[0] = V128ToSimdValue(v0); + _unicornEmu.Q[1] = V128ToSimdValue(v1); + _unicornEmu.Q[2] = V128ToSimdValue(v2); + _unicornEmu.Q[3] = V128ToSimdValue(v3); + _unicornEmu.Q[4] = V128ToSimdValue(v4); + _unicornEmu.Q[5] = V128ToSimdValue(v5); + _unicornEmu.Q[30] = V128ToSimdValue(v30); + _unicornEmu.Q[31] = V128ToSimdValue(v31); + + _unicornEmu.OverflowFlag = overflow; + _unicornEmu.CarryFlag = carry; + _unicornEmu.ZeroFlag = zero; + _unicornEmu.NegativeFlag = negative; + + _unicornEmu.Fpcr = fpcr; + _unicornEmu.Fpsr = fpsr; + } + + protected void ExecuteOpcodes(bool runUnicorn = true) + { + _cpuContext.Execute(_context, CodeBaseAddress); + + if (runUnicorn) + { + _unicornEmu.RunForCount((_currAddress - CodeBaseAddress - 4) / 4); + } + } + + protected ExecutionContext SingleOpcode(uint opcode, + ulong x0 = 0, + ulong x1 = 0, + ulong x2 = 0, + ulong x3 = 0, + ulong x31 = 0, + V128 v0 = default, + V128 v1 = default, + V128 v2 = default, + V128 v3 = default, + V128 v4 = default, + V128 v5 = default, + V128 v30 = default, + V128 v31 = default, + bool overflow = false, + bool carry = false, + bool zero = false, + bool negative = false, + int fpcr = 0, + int fpsr = 0, + bool runUnicorn = true) + { + if (_ignoreFpcrFz) + { + fpcr &= ~(1 << (int)Fpcr.Fz); + } + + if (_ignoreFpcrDn) + { + fpcr &= ~(1 << (int)Fpcr.Dn); + } + + Opcode(opcode); + Opcode(0xD65F03C0); // RET + SetContext(x0, x1, x2, x3, x31, v0, v1, v2, v3, v4, v5, v30, v31, overflow, carry, zero, negative, fpcr, fpsr); + ExecuteOpcodes(runUnicorn); + + return GetContext(); + } + + protected void SetWorkingMemory(ulong offset, byte[] data) + { + _memory.Write(DataBaseAddress + offset, data); + + _unicornEmu.MemoryWrite(DataBaseAddress + offset, data); + + _usingMemory = true; // When true, CompareAgainstUnicorn checks the working memory for equality too. + } + + protected void SetWorkingMemory(ulong offset, byte data) + { + _memory.Write(DataBaseAddress + offset, data); + + _unicornEmu.MemoryWrite8(DataBaseAddress + offset, data); + + _usingMemory = true; // When true, CompareAgainstUnicorn checks the working memory for equality too. + } + + /// Rounding Mode control field. + public enum RMode + { + /// Round to Nearest mode. + Rn, + /// Round towards Plus Infinity mode. + Rp, + /// Round towards Minus Infinity mode. + Rm, + /// Round towards Zero mode. + Rz, + } + + /// Floating-point Control Register. + protected enum Fpcr + { + /// Rounding Mode control field. + RMode = 22, + /// Flush-to-zero mode control bit. + Fz = 24, + /// Default NaN mode control bit. + Dn = 25, + /// Alternative half-precision control bit. + Ahp = 26, + } + + /// Floating-point Status Register. + [Flags] + protected enum Fpsr + { + None = 0, + + /// Invalid Operation cumulative floating-point exception bit. + Ioc = 1 << 0, + /// Divide by Zero cumulative floating-point exception bit. + Dzc = 1 << 1, + /// Overflow cumulative floating-point exception bit. + Ofc = 1 << 2, + /// Underflow cumulative floating-point exception bit. + Ufc = 1 << 3, + /// Inexact cumulative floating-point exception bit. + Ixc = 1 << 4, + /// Input Denormal cumulative floating-point exception bit. + Idc = 1 << 7, + + /// Cumulative saturation bit. + Qc = 1 << 27, + } + + [Flags] + protected enum FpSkips + { + None = 0, + + IfNaNS = 1, + IfNaND = 2, + + IfUnderflow = 4, + IfOverflow = 8, + } + + protected enum FpTolerances + { + None, + + UpToOneUlpsS, + UpToOneUlpsD, + } + + protected void CompareAgainstUnicorn( + Fpsr fpsrMask = Fpsr.None, + FpSkips fpSkips = FpSkips.None, + FpTolerances fpTolerances = FpTolerances.None) + { + if (_ignoreAllExceptFpsrQc) + { + fpsrMask &= Fpsr.Qc; + } + + if (fpSkips != FpSkips.None) + { + ManageFpSkips(fpSkips); + } + +#pragma warning disable IDE0055 // Disable formatting + Assert.That(_context.GetX(0), Is.EqualTo(_unicornEmu.X[0]), "X0"); + Assert.That(_context.GetX(1), Is.EqualTo(_unicornEmu.X[1]), "X1"); + Assert.That(_context.GetX(2), Is.EqualTo(_unicornEmu.X[2]), "X2"); + Assert.That(_context.GetX(3), Is.EqualTo(_unicornEmu.X[3]), "X3"); + Assert.That(_context.GetX(4), Is.EqualTo(_unicornEmu.X[4])); + Assert.That(_context.GetX(5), Is.EqualTo(_unicornEmu.X[5])); + Assert.That(_context.GetX(6), Is.EqualTo(_unicornEmu.X[6])); + Assert.That(_context.GetX(7), Is.EqualTo(_unicornEmu.X[7])); + Assert.That(_context.GetX(8), Is.EqualTo(_unicornEmu.X[8])); + Assert.That(_context.GetX(9), Is.EqualTo(_unicornEmu.X[9])); + Assert.That(_context.GetX(10), Is.EqualTo(_unicornEmu.X[10])); + Assert.That(_context.GetX(11), Is.EqualTo(_unicornEmu.X[11])); + Assert.That(_context.GetX(12), Is.EqualTo(_unicornEmu.X[12])); + Assert.That(_context.GetX(13), Is.EqualTo(_unicornEmu.X[13])); + Assert.That(_context.GetX(14), Is.EqualTo(_unicornEmu.X[14])); + Assert.That(_context.GetX(15), Is.EqualTo(_unicornEmu.X[15])); + Assert.That(_context.GetX(16), Is.EqualTo(_unicornEmu.X[16])); + Assert.That(_context.GetX(17), Is.EqualTo(_unicornEmu.X[17])); + Assert.That(_context.GetX(18), Is.EqualTo(_unicornEmu.X[18])); + Assert.That(_context.GetX(19), Is.EqualTo(_unicornEmu.X[19])); + Assert.That(_context.GetX(20), Is.EqualTo(_unicornEmu.X[20])); + Assert.That(_context.GetX(21), Is.EqualTo(_unicornEmu.X[21])); + Assert.That(_context.GetX(22), Is.EqualTo(_unicornEmu.X[22])); + Assert.That(_context.GetX(23), Is.EqualTo(_unicornEmu.X[23])); + Assert.That(_context.GetX(24), Is.EqualTo(_unicornEmu.X[24])); + Assert.That(_context.GetX(25), Is.EqualTo(_unicornEmu.X[25])); + Assert.That(_context.GetX(26), Is.EqualTo(_unicornEmu.X[26])); + Assert.That(_context.GetX(27), Is.EqualTo(_unicornEmu.X[27])); + Assert.That(_context.GetX(28), Is.EqualTo(_unicornEmu.X[28])); + Assert.That(_context.GetX(29), Is.EqualTo(_unicornEmu.X[29])); + Assert.That(_context.GetX(30), Is.EqualTo(_unicornEmu.X[30])); + Assert.That(_context.GetX(31), Is.EqualTo(_unicornEmu.SP), "X31"); +#pragma warning restore IDE0055 + + if (fpTolerances == FpTolerances.None) + { + Assert.That(V128ToSimdValue(_context.GetV(0)), Is.EqualTo(_unicornEmu.Q[0]), "V0"); + } + else + { + ManageFpTolerances(fpTolerances); + } + +#pragma warning disable IDE0055 // Disable formatting + Assert.That(V128ToSimdValue(_context.GetV(1)), Is.EqualTo(_unicornEmu.Q[1]), "V1"); + Assert.That(V128ToSimdValue(_context.GetV(2)), Is.EqualTo(_unicornEmu.Q[2]), "V2"); + Assert.That(V128ToSimdValue(_context.GetV(3)), Is.EqualTo(_unicornEmu.Q[3]), "V3"); + Assert.That(V128ToSimdValue(_context.GetV(4)), Is.EqualTo(_unicornEmu.Q[4]), "V4"); + Assert.That(V128ToSimdValue(_context.GetV(5)), Is.EqualTo(_unicornEmu.Q[5]), "V5"); + Assert.That(V128ToSimdValue(_context.GetV(6)), Is.EqualTo(_unicornEmu.Q[6])); + Assert.That(V128ToSimdValue(_context.GetV(7)), Is.EqualTo(_unicornEmu.Q[7])); + Assert.That(V128ToSimdValue(_context.GetV(8)), Is.EqualTo(_unicornEmu.Q[8])); + Assert.That(V128ToSimdValue(_context.GetV(9)), Is.EqualTo(_unicornEmu.Q[9])); + Assert.That(V128ToSimdValue(_context.GetV(10)), Is.EqualTo(_unicornEmu.Q[10])); + Assert.That(V128ToSimdValue(_context.GetV(11)), Is.EqualTo(_unicornEmu.Q[11])); + Assert.That(V128ToSimdValue(_context.GetV(12)), Is.EqualTo(_unicornEmu.Q[12])); + Assert.That(V128ToSimdValue(_context.GetV(13)), Is.EqualTo(_unicornEmu.Q[13])); + Assert.That(V128ToSimdValue(_context.GetV(14)), Is.EqualTo(_unicornEmu.Q[14])); + Assert.That(V128ToSimdValue(_context.GetV(15)), Is.EqualTo(_unicornEmu.Q[15])); + Assert.That(V128ToSimdValue(_context.GetV(16)), Is.EqualTo(_unicornEmu.Q[16])); + Assert.That(V128ToSimdValue(_context.GetV(17)), Is.EqualTo(_unicornEmu.Q[17])); + Assert.That(V128ToSimdValue(_context.GetV(18)), Is.EqualTo(_unicornEmu.Q[18])); + Assert.That(V128ToSimdValue(_context.GetV(19)), Is.EqualTo(_unicornEmu.Q[19])); + Assert.That(V128ToSimdValue(_context.GetV(20)), Is.EqualTo(_unicornEmu.Q[20])); + Assert.That(V128ToSimdValue(_context.GetV(21)), Is.EqualTo(_unicornEmu.Q[21])); + Assert.That(V128ToSimdValue(_context.GetV(22)), Is.EqualTo(_unicornEmu.Q[22])); + Assert.That(V128ToSimdValue(_context.GetV(23)), Is.EqualTo(_unicornEmu.Q[23])); + Assert.That(V128ToSimdValue(_context.GetV(24)), Is.EqualTo(_unicornEmu.Q[24])); + Assert.That(V128ToSimdValue(_context.GetV(25)), Is.EqualTo(_unicornEmu.Q[25])); + Assert.That(V128ToSimdValue(_context.GetV(26)), Is.EqualTo(_unicornEmu.Q[26])); + Assert.That(V128ToSimdValue(_context.GetV(27)), Is.EqualTo(_unicornEmu.Q[27])); + Assert.That(V128ToSimdValue(_context.GetV(28)), Is.EqualTo(_unicornEmu.Q[28])); + Assert.That(V128ToSimdValue(_context.GetV(29)), Is.EqualTo(_unicornEmu.Q[29])); + Assert.That(V128ToSimdValue(_context.GetV(30)), Is.EqualTo(_unicornEmu.Q[30]), "V30"); + Assert.That(V128ToSimdValue(_context.GetV(31)), Is.EqualTo(_unicornEmu.Q[31]), "V31"); + + Assert.Multiple(() => + { + Assert.That(_context.GetPstateFlag(PState.VFlag), Is.EqualTo(_unicornEmu.OverflowFlag), "VFlag"); + Assert.That(_context.GetPstateFlag(PState.CFlag), Is.EqualTo(_unicornEmu.CarryFlag), "CFlag"); + Assert.That(_context.GetPstateFlag(PState.ZFlag), Is.EqualTo(_unicornEmu.ZeroFlag), "ZFlag"); + Assert.That(_context.GetPstateFlag(PState.NFlag), Is.EqualTo(_unicornEmu.NegativeFlag), "NFlag"); + }); + + Assert.That((int)_context.Fpcr, Is.EqualTo(_unicornEmu.Fpcr), "Fpcr"); + Assert.That((int)_context.Fpsr & (int)fpsrMask, Is.EqualTo(_unicornEmu.Fpsr & (int)fpsrMask), "Fpsr"); +#pragma warning restore IDE0055 + + if (_usingMemory) + { + byte[] mem = _memory.GetSpan(DataBaseAddress, (int)Size).ToArray(); + byte[] unicornMem = _unicornEmu.MemoryRead(DataBaseAddress, Size); + + Assert.That(mem, Is.EqualTo(unicornMem), "Data"); + } + } + + private void ManageFpSkips(FpSkips fpSkips) + { + if (fpSkips.HasFlag(FpSkips.IfNaNS)) + { + if (float.IsNaN(_unicornEmu.Q[0].AsFloat())) + { + Assert.Ignore("NaN test."); + } + } + else if (fpSkips.HasFlag(FpSkips.IfNaND)) + { + if (double.IsNaN(_unicornEmu.Q[0].AsDouble())) + { + Assert.Ignore("NaN test."); + } + } + + if (fpSkips.HasFlag(FpSkips.IfUnderflow)) + { + if ((_unicornEmu.Fpsr & (int)Fpsr.Ufc) != 0) + { + Assert.Ignore("Underflow test."); + } + } + + if (fpSkips.HasFlag(FpSkips.IfOverflow)) + { + if ((_unicornEmu.Fpsr & (int)Fpsr.Ofc) != 0) + { + Assert.Ignore("Overflow test."); + } + } + } + + private void ManageFpTolerances(FpTolerances fpTolerances) + { + bool IsNormalOrSubnormalS(float f) => float.IsNormal(f) || float.IsSubnormal(f); + bool IsNormalOrSubnormalD(double d) => double.IsNormal(d) || double.IsSubnormal(d); + + if (!Is.EqualTo(_unicornEmu.Q[0]).ApplyTo(V128ToSimdValue(_context.GetV(0))).IsSuccess) + { + if (fpTolerances == FpTolerances.UpToOneUlpsS) + { + if (IsNormalOrSubnormalS(_unicornEmu.Q[0].AsFloat()) && + IsNormalOrSubnormalS(_context.GetV(0).As())) + { + Assert.Multiple(() => + { + Assert.That(_context.GetV(0).Extract(0), + Is.EqualTo(_unicornEmu.Q[0].GetFloat(0)).Within(1).Ulps, "V0[0]"); + Assert.That(_context.GetV(0).Extract(1), + Is.EqualTo(_unicornEmu.Q[0].GetFloat(1)).Within(1).Ulps, "V0[1]"); + Assert.That(_context.GetV(0).Extract(2), + Is.EqualTo(_unicornEmu.Q[0].GetFloat(2)).Within(1).Ulps, "V0[2]"); + Assert.That(_context.GetV(0).Extract(3), + Is.EqualTo(_unicornEmu.Q[0].GetFloat(3)).Within(1).Ulps, "V0[3]"); + }); + + Console.WriteLine(fpTolerances); + } + else + { + Assert.That(V128ToSimdValue(_context.GetV(0)), Is.EqualTo(_unicornEmu.Q[0])); + } + } + + if (fpTolerances == FpTolerances.UpToOneUlpsD) + { + if (IsNormalOrSubnormalD(_unicornEmu.Q[0].AsDouble()) && + IsNormalOrSubnormalD(_context.GetV(0).As())) + { + Assert.Multiple(() => + { + Assert.That(_context.GetV(0).Extract(0), + Is.EqualTo(_unicornEmu.Q[0].GetDouble(0)).Within(1).Ulps, "V0[0]"); + Assert.That(_context.GetV(0).Extract(1), + Is.EqualTo(_unicornEmu.Q[0].GetDouble(1)).Within(1).Ulps, "V0[1]"); + }); + + Console.WriteLine(fpTolerances); + } + else + { + Assert.That(V128ToSimdValue(_context.GetV(0)), Is.EqualTo(_unicornEmu.Q[0])); + } + } + } + } + + private static SimdValue V128ToSimdValue(V128 value) + { + return new SimdValue(value.Extract(0), value.Extract(1)); + } + + protected static V128 MakeVectorScalar(float value) => new(value); + protected static V128 MakeVectorScalar(double value) => new(value); + + protected static V128 MakeVectorE0(ulong e0) => new(e0, 0); + protected static V128 MakeVectorE1(ulong e1) => new(0, e1); + + protected static V128 MakeVectorE0E1(ulong e0, ulong e1) => new(e0, e1); + + protected static ulong GetVectorE0(V128 vector) => vector.Extract(0); + protected static ulong GetVectorE1(V128 vector) => vector.Extract(1); + + protected static ushort GenNormalH() + { + uint rnd; + + do + rnd = TestContext.CurrentContext.Random.NextUShort(); + while ((rnd & 0x7C00u) == 0u || + (~rnd & 0x7C00u) == 0u); + + return (ushort)rnd; + } + + protected static ushort GenSubnormalH() + { + uint rnd; + + do + rnd = TestContext.CurrentContext.Random.NextUShort(); + while ((rnd & 0x03FFu) == 0u); + + return (ushort)(rnd & 0x83FFu); + } + + protected static uint GenNormalS() + { + uint rnd; + + do + rnd = TestContext.CurrentContext.Random.NextUInt(); + while ((rnd & 0x7F800000u) == 0u || + (~rnd & 0x7F800000u) == 0u); + + return rnd; + } + + protected static uint GenSubnormalS() + { + uint rnd; + + do + rnd = TestContext.CurrentContext.Random.NextUInt(); + while ((rnd & 0x007FFFFFu) == 0u); + + return rnd & 0x807FFFFFu; + } + + protected static ulong GenNormalD() + { + ulong rnd; + + do + rnd = TestContext.CurrentContext.Random.NextULong(); + while ((rnd & 0x7FF0000000000000ul) == 0ul || + (~rnd & 0x7FF0000000000000ul) == 0ul); + + return rnd; + } + + protected static ulong GenSubnormalD() + { + ulong rnd; + + do + rnd = TestContext.CurrentContext.Random.NextULong(); + while ((rnd & 0x000FFFFFFFFFFFFFul) == 0ul); + + return rnd & 0x800FFFFFFFFFFFFFul; + } + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTest32.cs b/src/Ryujinx.Tests/Cpu/CpuTest32.cs new file mode 100644 index 00000000..6a690834 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTest32.cs @@ -0,0 +1,644 @@ +using ARMeilleure; +using ARMeilleure.State; +using ARMeilleure.Translation; +using NUnit.Framework; +using Ryujinx.Cpu.Jit; +using Ryujinx.Memory; +using Ryujinx.Tests.Unicorn; +using System; +using MemoryPermission = Ryujinx.Tests.Unicorn.MemoryPermission; + +namespace Ryujinx.Tests.Cpu +{ + [TestFixture] + public class CpuTest32 + { + protected static readonly uint Size = (uint)MemoryBlock.GetPageSize(); +#pragma warning disable CA2211 // Non-constant fields should not be visible + protected static uint CodeBaseAddress = Size; + protected static uint DataBaseAddress = CodeBaseAddress + Size; +#pragma warning restore CA2211 + + private uint _currAddress; + + private MemoryBlock _ram; + + private MemoryManager _memory; + + private ExecutionContext _context; + + private CpuContext _cpuContext; + private UnicornAArch32 _unicornEmu; + + private bool _usingMemory; + + [SetUp] + public void Setup() + { + int pageBits = (int)ulong.Log2(Size); + + _ram = new MemoryBlock(Size * 2); + _memory = new MemoryManager(_ram, 1ul << (pageBits + 4)); + _memory.IncrementReferenceCount(); + + // Some tests depends on hardcoded address that were computed for 4KiB. + // We change the layout on non 4KiB platforms to keep compat here. + if (Size > 0x1000) + { + DataBaseAddress = 0; + CodeBaseAddress = Size; + } + + _currAddress = CodeBaseAddress; + + _memory.Map(CodeBaseAddress, 0, Size, MemoryMapFlags.Private); + _memory.Map(DataBaseAddress, Size, Size, MemoryMapFlags.Private); + + _context = CpuContext.CreateExecutionContext(); + _context.IsAarch32 = true; + + _cpuContext = new CpuContext(_memory, for64Bit: false); + + // Prevent registering LCQ functions in the FunctionTable to avoid initializing and populating the table, + // which improves test durations. + Optimizations.AllowLcqInFunctionTable = false; + Optimizations.UseUnmanagedDispatchLoop = false; + + _unicornEmu = new UnicornAArch32(); + _unicornEmu.MemoryMap(CodeBaseAddress, Size, MemoryPermission.Read | MemoryPermission.Exec); + _unicornEmu.MemoryMap(DataBaseAddress, Size, MemoryPermission.Read | MemoryPermission.Write); + _unicornEmu.PC = CodeBaseAddress; + } + + [TearDown] + public void Teardown() + { + _unicornEmu.Dispose(); + _unicornEmu = null; + + _memory.DecrementReferenceCount(); + _context.Dispose(); + _ram.Dispose(); + + _memory = null; + _context = null; + _cpuContext = null; + _unicornEmu = null; + + _usingMemory = false; + } + + protected void Reset() + { + Teardown(); + Setup(); + } + + protected void Opcode(uint opcode) + { + _memory.Write(_currAddress, opcode); + + _unicornEmu.MemoryWrite32(_currAddress, opcode); + + _currAddress += 4; + } + + protected void ThumbOpcode(ushort opcode) + { + _memory.Write(_currAddress, opcode); + + _unicornEmu.MemoryWrite16(_currAddress, opcode); + + _currAddress += 2; + } + + protected ExecutionContext GetContext() => _context; + + protected void SetContext(uint r0 = 0, + uint r1 = 0, + uint r2 = 0, + uint r3 = 0, + uint sp = 0, + V128 v0 = default, + V128 v1 = default, + V128 v2 = default, + V128 v3 = default, + V128 v4 = default, + V128 v5 = default, + V128 v14 = default, + V128 v15 = default, + bool saturation = false, + bool overflow = false, + bool carry = false, + bool zero = false, + bool negative = false, + int fpscr = 0, + bool thumb = false) + { + _context.SetX(0, r0); + _context.SetX(1, r1); + _context.SetX(2, r2); + _context.SetX(3, r3); + _context.SetX(13, sp); + + _context.SetV(0, v0); + _context.SetV(1, v1); + _context.SetV(2, v2); + _context.SetV(3, v3); + _context.SetV(4, v4); + _context.SetV(5, v5); + _context.SetV(14, v14); + _context.SetV(15, v15); + + _context.SetPstateFlag(PState.QFlag, saturation); + _context.SetPstateFlag(PState.VFlag, overflow); + _context.SetPstateFlag(PState.CFlag, carry); + _context.SetPstateFlag(PState.ZFlag, zero); + _context.SetPstateFlag(PState.NFlag, negative); + + _context.Fpscr = (FPSCR)fpscr; + + _context.SetPstateFlag(PState.TFlag, thumb); + + _unicornEmu.R[0] = r0; + _unicornEmu.R[1] = r1; + _unicornEmu.R[2] = r2; + _unicornEmu.R[3] = r3; + _unicornEmu.SP = sp; + + _unicornEmu.Q[0] = V128ToSimdValue(v0); + _unicornEmu.Q[1] = V128ToSimdValue(v1); + _unicornEmu.Q[2] = V128ToSimdValue(v2); + _unicornEmu.Q[3] = V128ToSimdValue(v3); + _unicornEmu.Q[4] = V128ToSimdValue(v4); + _unicornEmu.Q[5] = V128ToSimdValue(v5); + _unicornEmu.Q[14] = V128ToSimdValue(v14); + _unicornEmu.Q[15] = V128ToSimdValue(v15); + + _unicornEmu.QFlag = saturation; + _unicornEmu.OverflowFlag = overflow; + _unicornEmu.CarryFlag = carry; + _unicornEmu.ZeroFlag = zero; + _unicornEmu.NegativeFlag = negative; + + _unicornEmu.Fpscr = fpscr; + + _unicornEmu.ThumbFlag = thumb; + } + + protected void ExecuteOpcodes(bool runUnicorn = true) + { + _cpuContext.Execute(_context, CodeBaseAddress); + + if (runUnicorn) + { + _unicornEmu.RunForCount((_currAddress - CodeBaseAddress - 4) / 4); + } + } + + protected ExecutionContext SingleOpcode(uint opcode, + uint r0 = 0, + uint r1 = 0, + uint r2 = 0, + uint r3 = 0, + uint sp = 0, + V128 v0 = default, + V128 v1 = default, + V128 v2 = default, + V128 v3 = default, + V128 v4 = default, + V128 v5 = default, + V128 v14 = default, + V128 v15 = default, + bool saturation = false, + bool overflow = false, + bool carry = false, + bool zero = false, + bool negative = false, + int fpscr = 0, + bool runUnicorn = true) + { + Opcode(opcode); + Opcode(0xE12FFF1E); // BX LR + SetContext(r0, r1, r2, r3, sp, v0, v1, v2, v3, v4, v5, v14, v15, saturation, overflow, carry, zero, negative, fpscr); + ExecuteOpcodes(runUnicorn); + + return GetContext(); + } + + protected ExecutionContext SingleThumbOpcode(ushort opcode, + uint r0 = 0, + uint r1 = 0, + uint r2 = 0, + uint r3 = 0, + uint sp = 0, + bool saturation = false, + bool overflow = false, + bool carry = false, + bool zero = false, + bool negative = false, + int fpscr = 0, + bool runUnicorn = true) + { + ThumbOpcode(opcode); + ThumbOpcode(0x4770); // BX LR + SetContext(r0, r1, r2, r3, sp, default, default, default, default, default, default, default, default, saturation, overflow, carry, zero, negative, fpscr, thumb: true); + ExecuteOpcodes(runUnicorn); + + return GetContext(); + } + + public void RunPrecomputedTestCase(PrecomputedThumbTestCase test) + { + foreach (ushort instruction in test.Instructions) + { + ThumbOpcode(instruction); + } + + for (int i = 0; i < 15; i++) + { + GetContext().SetX(i, test.StartRegs[i]); + } + + uint startCpsr = test.StartRegs[15]; + for (int i = 0; i < 32; i++) + { + GetContext().SetPstateFlag((PState)i, (startCpsr & (1u << i)) != 0); + } + + ExecuteOpcodes(runUnicorn: false); + + for (int i = 0; i < 15; i++) + { + Assert.That(GetContext().GetX(i), Is.EqualTo(test.FinalRegs[i])); + } + + uint finalCpsr = test.FinalRegs[15]; + Assert.That(GetContext().Pstate, Is.EqualTo(finalCpsr)); + } + + public void RunPrecomputedTestCase(PrecomputedMemoryThumbTestCase test) + { + byte[] testMem = new byte[Size]; + + for (ulong i = 0; i < Size; i += 2) + { + testMem[i + 0] = (byte)((i + DataBaseAddress) >> 0); + testMem[i + 1] = (byte)((i + DataBaseAddress) >> 8); + } + + SetWorkingMemory(0, testMem); + + RunPrecomputedTestCase(new PrecomputedThumbTestCase + { + Instructions = test.Instructions, + StartRegs = test.StartRegs, + FinalRegs = test.FinalRegs, + }); + + foreach (var (address, value) in test.MemoryDelta) + { + testMem[address - DataBaseAddress + 0] = (byte)(value >> 0); + testMem[address - DataBaseAddress + 1] = (byte)(value >> 8); + } + + byte[] mem = _memory.GetSpan(DataBaseAddress, (int)Size).ToArray(); + + Assert.That(mem, Is.EqualTo(testMem), "testmem"); + } + + protected void SetWorkingMemory(uint offset, byte[] data) + { + _memory.Write(DataBaseAddress + offset, data); + + _unicornEmu.MemoryWrite(DataBaseAddress + offset, data); + + _usingMemory = true; // When true, CompareAgainstUnicorn checks the working memory for equality too. + } + + /// Rounding Mode control field. + public enum RMode + { + /// Round to Nearest mode. + Rn, + /// Round towards Plus Infinity mode. + Rp, + /// Round towards Minus Infinity mode. + Rm, + /// Round towards Zero mode. + Rz, + } + + /// Floating-point Control Register. + protected enum Fpcr + { + /// Rounding Mode control field. + RMode = 22, + /// Flush-to-zero mode control bit. + Fz = 24, + /// Default NaN mode control bit. + Dn = 25, + /// Alternative half-precision control bit. + Ahp = 26, + } + + /// Floating-point Status Register. + [Flags] + protected enum Fpsr + { + None = 0, + + /// Invalid Operation cumulative floating-point exception bit. + Ioc = 1 << 0, + /// Divide by Zero cumulative floating-point exception bit. + Dzc = 1 << 1, + /// Overflow cumulative floating-point exception bit. + Ofc = 1 << 2, + /// Underflow cumulative floating-point exception bit. + Ufc = 1 << 3, + /// Inexact cumulative floating-point exception bit. + Ixc = 1 << 4, + /// Input Denormal cumulative floating-point exception bit. + Idc = 1 << 7, + + /// Cumulative saturation bit. + Qc = 1 << 27, + + /// NZCV flags. + Nzcv = (1 << 31) | (1 << 30) | (1 << 29) | (1 << 28), + } + + [Flags] + protected enum FpSkips + { + None = 0, + + IfNaNS = 1, + IfNaND = 2, + + IfUnderflow = 4, + IfOverflow = 8, + } + + protected enum FpTolerances + { + None, + + UpToOneUlpsS, + UpToOneUlpsD, + } + + protected void CompareAgainstUnicorn( + Fpsr fpsrMask = Fpsr.None, + FpSkips fpSkips = FpSkips.None, + FpTolerances fpTolerances = FpTolerances.None) + { + if (fpSkips != FpSkips.None) + { + ManageFpSkips(fpSkips); + } + + Assert.That(_context.GetX(0), Is.EqualTo(_unicornEmu.R[0]), "R0"); + Assert.That(_context.GetX(1), Is.EqualTo(_unicornEmu.R[1]), "R1"); + Assert.That(_context.GetX(2), Is.EqualTo(_unicornEmu.R[2]), "R2"); + Assert.That(_context.GetX(3), Is.EqualTo(_unicornEmu.R[3]), "R3"); + Assert.That(_context.GetX(4), Is.EqualTo(_unicornEmu.R[4])); + Assert.That(_context.GetX(5), Is.EqualTo(_unicornEmu.R[5])); + Assert.That(_context.GetX(6), Is.EqualTo(_unicornEmu.R[6])); + Assert.That(_context.GetX(7), Is.EqualTo(_unicornEmu.R[7])); + Assert.That(_context.GetX(8), Is.EqualTo(_unicornEmu.R[8])); + Assert.That(_context.GetX(9), Is.EqualTo(_unicornEmu.R[9])); + Assert.That(_context.GetX(10), Is.EqualTo(_unicornEmu.R[10])); + Assert.That(_context.GetX(11), Is.EqualTo(_unicornEmu.R[11])); + Assert.That(_context.GetX(12), Is.EqualTo(_unicornEmu.R[12])); + Assert.That(_context.GetX(13), Is.EqualTo(_unicornEmu.SP), "SP"); + Assert.That(_context.GetX(14), Is.EqualTo(_unicornEmu.R[14])); + + if (fpTolerances == FpTolerances.None) + { + Assert.That(V128ToSimdValue(_context.GetV(0)), Is.EqualTo(_unicornEmu.Q[0]), "V0"); + } + else + { + ManageFpTolerances(fpTolerances); + } + Assert.That(V128ToSimdValue(_context.GetV(1)), Is.EqualTo(_unicornEmu.Q[1]), "V1"); + Assert.That(V128ToSimdValue(_context.GetV(2)), Is.EqualTo(_unicornEmu.Q[2]), "V2"); + Assert.That(V128ToSimdValue(_context.GetV(3)), Is.EqualTo(_unicornEmu.Q[3]), "V3"); + Assert.That(V128ToSimdValue(_context.GetV(4)), Is.EqualTo(_unicornEmu.Q[4]), "V4"); + Assert.That(V128ToSimdValue(_context.GetV(5)), Is.EqualTo(_unicornEmu.Q[5]), "V5"); + Assert.That(V128ToSimdValue(_context.GetV(6)), Is.EqualTo(_unicornEmu.Q[6])); + Assert.That(V128ToSimdValue(_context.GetV(7)), Is.EqualTo(_unicornEmu.Q[7])); + Assert.That(V128ToSimdValue(_context.GetV(8)), Is.EqualTo(_unicornEmu.Q[8])); + Assert.That(V128ToSimdValue(_context.GetV(9)), Is.EqualTo(_unicornEmu.Q[9])); + Assert.That(V128ToSimdValue(_context.GetV(10)), Is.EqualTo(_unicornEmu.Q[10])); + Assert.That(V128ToSimdValue(_context.GetV(11)), Is.EqualTo(_unicornEmu.Q[11])); + Assert.That(V128ToSimdValue(_context.GetV(12)), Is.EqualTo(_unicornEmu.Q[12])); + Assert.That(V128ToSimdValue(_context.GetV(13)), Is.EqualTo(_unicornEmu.Q[13])); + Assert.That(V128ToSimdValue(_context.GetV(14)), Is.EqualTo(_unicornEmu.Q[14]), "V14"); + Assert.That(V128ToSimdValue(_context.GetV(15)), Is.EqualTo(_unicornEmu.Q[15]), "V15"); + + Assert.Multiple(() => + { + Assert.That(_context.GetPstateFlag(PState.GE0Flag), Is.EqualTo((_unicornEmu.CPSR & (1u << 16)) != 0), "GE0Flag"); + Assert.That(_context.GetPstateFlag(PState.GE1Flag), Is.EqualTo((_unicornEmu.CPSR & (1u << 17)) != 0), "GE1Flag"); + Assert.That(_context.GetPstateFlag(PState.GE2Flag), Is.EqualTo((_unicornEmu.CPSR & (1u << 18)) != 0), "GE2Flag"); + Assert.That(_context.GetPstateFlag(PState.GE3Flag), Is.EqualTo((_unicornEmu.CPSR & (1u << 19)) != 0), "GE3Flag"); + Assert.That(_context.GetPstateFlag(PState.QFlag), Is.EqualTo(_unicornEmu.QFlag), "QFlag"); + Assert.That(_context.GetPstateFlag(PState.VFlag), Is.EqualTo(_unicornEmu.OverflowFlag), "VFlag"); + Assert.That(_context.GetPstateFlag(PState.CFlag), Is.EqualTo(_unicornEmu.CarryFlag), "CFlag"); + Assert.That(_context.GetPstateFlag(PState.ZFlag), Is.EqualTo(_unicornEmu.ZeroFlag), "ZFlag"); + Assert.That(_context.GetPstateFlag(PState.NFlag), Is.EqualTo(_unicornEmu.NegativeFlag), "NFlag"); + }); + + Assert.That((int)_context.Fpscr & (int)fpsrMask, Is.EqualTo(_unicornEmu.Fpscr & (int)fpsrMask), "Fpscr"); + + if (_usingMemory) + { + byte[] mem = _memory.GetSpan(DataBaseAddress, (int)Size).ToArray(); + byte[] unicornMem = _unicornEmu.MemoryRead(DataBaseAddress, Size); + + Assert.That(mem, Is.EqualTo(unicornMem), "Data"); + } + } + + private void ManageFpSkips(FpSkips fpSkips) + { + if (fpSkips.HasFlag(FpSkips.IfNaNS)) + { + if (float.IsNaN(_unicornEmu.Q[0].AsFloat())) + { + Assert.Ignore("NaN test."); + } + } + else if (fpSkips.HasFlag(FpSkips.IfNaND)) + { + if (double.IsNaN(_unicornEmu.Q[0].AsDouble())) + { + Assert.Ignore("NaN test."); + } + } + + if (fpSkips.HasFlag(FpSkips.IfUnderflow)) + { + if ((_unicornEmu.Fpscr & (int)Fpsr.Ufc) != 0) + { + Assert.Ignore("Underflow test."); + } + } + + if (fpSkips.HasFlag(FpSkips.IfOverflow)) + { + if ((_unicornEmu.Fpscr & (int)Fpsr.Ofc) != 0) + { + Assert.Ignore("Overflow test."); + } + } + } + + private void ManageFpTolerances(FpTolerances fpTolerances) + { + bool IsNormalOrSubnormalS(float f) => float.IsNormal(f) || float.IsSubnormal(f); + bool IsNormalOrSubnormalD(double d) => double.IsNormal(d) || double.IsSubnormal(d); + + if (!Is.EqualTo(_unicornEmu.Q[0]).ApplyTo(V128ToSimdValue(_context.GetV(0))).IsSuccess) + { + if (fpTolerances == FpTolerances.UpToOneUlpsS) + { + if (IsNormalOrSubnormalS(_unicornEmu.Q[0].AsFloat()) && + IsNormalOrSubnormalS(_context.GetV(0).As())) + { + Assert.Multiple(() => + { + Assert.That(_context.GetV(0).Extract(0), + Is.EqualTo(_unicornEmu.Q[0].GetFloat(0)).Within(1).Ulps, "V0[0]"); + Assert.That(_context.GetV(0).Extract(1), + Is.EqualTo(_unicornEmu.Q[0].GetFloat(1)).Within(1).Ulps, "V0[1]"); + Assert.That(_context.GetV(0).Extract(2), + Is.EqualTo(_unicornEmu.Q[0].GetFloat(2)).Within(1).Ulps, "V0[2]"); + Assert.That(_context.GetV(0).Extract(3), + Is.EqualTo(_unicornEmu.Q[0].GetFloat(3)).Within(1).Ulps, "V0[3]"); + }); + + Console.WriteLine(fpTolerances); + } + else + { + Assert.That(V128ToSimdValue(_context.GetV(0)), Is.EqualTo(_unicornEmu.Q[0])); + } + } + + if (fpTolerances == FpTolerances.UpToOneUlpsD) + { + if (IsNormalOrSubnormalD(_unicornEmu.Q[0].AsDouble()) && + IsNormalOrSubnormalD(_context.GetV(0).As())) + { + Assert.Multiple(() => + { + Assert.That(_context.GetV(0).Extract(0), + Is.EqualTo(_unicornEmu.Q[0].GetDouble(0)).Within(1).Ulps, "V0[0]"); + Assert.That(_context.GetV(0).Extract(1), + Is.EqualTo(_unicornEmu.Q[0].GetDouble(1)).Within(1).Ulps, "V0[1]"); + }); + + Console.WriteLine(fpTolerances); + } + else + { + Assert.That(V128ToSimdValue(_context.GetV(0)), Is.EqualTo(_unicornEmu.Q[0])); + } + } + } + } + + private static SimdValue V128ToSimdValue(V128 value) + { + return new SimdValue(value.Extract(0), value.Extract(1)); + } + + protected static V128 MakeVectorScalar(float value) => new(value); + protected static V128 MakeVectorScalar(double value) => new(value); + + protected static V128 MakeVectorE0(ulong e0) => new(e0, 0); + protected static V128 MakeVectorE1(ulong e1) => new(0, e1); + + protected static V128 MakeVectorE0E1(ulong e0, ulong e1) => new(e0, e1); + + protected static V128 MakeVectorE0E1E2E3(uint e0, uint e1, uint e2, uint e3) + { + return new V128(e0, e1, e2, e3); + } + + protected static ulong GetVectorE0(V128 vector) => vector.Extract(0); + protected static ulong GetVectorE1(V128 vector) => vector.Extract(1); + + protected static ushort GenNormalH() + { + uint rnd; + + do + rnd = TestContext.CurrentContext.Random.NextUShort(); + while ((rnd & 0x7C00u) == 0u || + (~rnd & 0x7C00u) == 0u); + + return (ushort)rnd; + } + + protected static ushort GenSubnormalH() + { + uint rnd; + + do + rnd = TestContext.CurrentContext.Random.NextUShort(); + while ((rnd & 0x03FFu) == 0u); + + return (ushort)(rnd & 0x83FFu); + } + + protected static uint GenNormalS() + { + uint rnd; + + do + rnd = TestContext.CurrentContext.Random.NextUInt(); + while ((rnd & 0x7F800000u) == 0u || + (~rnd & 0x7F800000u) == 0u); + + return rnd; + } + + protected static uint GenSubnormalS() + { + uint rnd; + + do + rnd = TestContext.CurrentContext.Random.NextUInt(); + while ((rnd & 0x007FFFFFu) == 0u); + + return rnd & 0x807FFFFFu; + } + + protected static ulong GenNormalD() + { + ulong rnd; + + do + rnd = TestContext.CurrentContext.Random.NextULong(); + while ((rnd & 0x7FF0000000000000ul) == 0ul || + (~rnd & 0x7FF0000000000000ul) == 0ul); + + return rnd; + } + + protected static ulong GenSubnormalD() + { + ulong rnd; + + do + rnd = TestContext.CurrentContext.Random.NextULong(); + while ((rnd & 0x000FFFFFFFFFFFFFul) == 0ul); + + return rnd & 0x800FFFFFFFFFFFFFul; + } + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestAlu.cs b/src/Ryujinx.Tests/Cpu/CpuTestAlu.cs new file mode 100644 index 00000000..9c1876c8 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestAlu.cs @@ -0,0 +1,280 @@ +#define Alu + +using NUnit.Framework; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("Alu")] + public sealed class CpuTestAlu : CpuTest + { +#if Alu + + #region "Helper methods" + private static uint GenLeadingSignsMinus32(int cnt) // 0 <= cnt <= 31 + { + return ~GenLeadingZeros32(cnt + 1); + } + + private static ulong GenLeadingSignsMinus64(int cnt) // 0 <= cnt <= 63 + { + return ~GenLeadingZeros64(cnt + 1); + } + + private static uint GenLeadingSignsPlus32(int cnt) // 0 <= cnt <= 31 + { + return GenLeadingZeros32(cnt + 1); + } + + private static ulong GenLeadingSignsPlus64(int cnt) // 0 <= cnt <= 63 + { + return GenLeadingZeros64(cnt + 1); + } + + private static uint GenLeadingZeros32(int cnt) // 0 <= cnt <= 32 + { + if (cnt == 32) + { + return 0u; + } + + if (cnt == 31) + { + return 1u; + } + + uint rnd = TestContext.CurrentContext.Random.NextUInt(); + int mask = int.MinValue; + + return (rnd >> (cnt + 1)) | ((uint)mask >> cnt); + } + + private static ulong GenLeadingZeros64(int cnt) // 0 <= cnt <= 64 + { + if (cnt == 64) + { + return 0ul; + } + + if (cnt == 63) + { + return 1ul; + } + + ulong rnd = TestContext.CurrentContext.Random.NextULong(); + long mask = long.MinValue; + + return (rnd >> (cnt + 1)) | ((ulong)mask >> cnt); + } + #endregion + + #region "ValueSource (Types)" + private static IEnumerable GenLeadingSignsX() + { + for (int cnt = 0; cnt <= 63; cnt++) + { + yield return GenLeadingSignsMinus64(cnt); + yield return GenLeadingSignsPlus64(cnt); + } + } + + private static IEnumerable GenLeadingSignsW() + { + for (int cnt = 0; cnt <= 31; cnt++) + { + yield return GenLeadingSignsMinus32(cnt); + yield return GenLeadingSignsPlus32(cnt); + } + } + + private static IEnumerable GenLeadingZerosX() + { + for (int cnt = 0; cnt <= 64; cnt++) + { + yield return GenLeadingZeros64(cnt); + } + } + + private static IEnumerable GenLeadingZerosW() + { + for (int cnt = 0; cnt <= 32; cnt++) + { + yield return GenLeadingZeros32(cnt); + } + } + #endregion + + [Test, Pairwise, Description("CLS , ")] + public void Cls_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(GenLeadingSignsX))] ulong xn) + { + uint opcode = 0xDAC01400; // CLS X0, X0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CLS , ")] + public void Cls_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(GenLeadingSignsW))] uint wn) + { + uint opcode = 0x5AC01400; // CLS W0, W0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CLZ , ")] + public void Clz_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(GenLeadingZerosX))] ulong xn) + { + uint opcode = 0xDAC01000; // CLZ X0, X0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CLZ , ")] + public void Clz_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(GenLeadingZerosW))] uint wn) + { + uint opcode = 0x5AC01000; // CLZ W0, W0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("RBIT , ")] + public void Rbit_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn) + { + uint opcode = 0xDAC00000; // RBIT X0, X0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("RBIT , ")] + public void Rbit_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn) + { + uint opcode = 0x5AC00000; // RBIT W0, W0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("REV16 , ")] + public void Rev16_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn) + { + uint opcode = 0xDAC00400; // REV16 X0, X0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("REV16 , ")] + public void Rev16_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn) + { + uint opcode = 0x5AC00400; // REV16 W0, W0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("REV32 , ")] + public void Rev32_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn) + { + uint opcode = 0xDAC00800; // REV32 X0, X0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("REV , ")] + public void Rev32_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn) + { + uint opcode = 0x5AC00800; // REV W0, W0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("REV64 , ")] + public void Rev64_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn) + { + uint opcode = 0xDAC00C00; // REV64 X0, X0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestAlu32.cs b/src/Ryujinx.Tests/Cpu/CpuTestAlu32.cs new file mode 100644 index 00000000..1e66d811 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestAlu32.cs @@ -0,0 +1,264 @@ +#define Alu32 + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("Alu32")] + public sealed class CpuTestAlu32 : CpuTest32 + { +#if Alu32 + + #region "ValueSource (Opcodes)" + private static uint[] SuHAddSub8() + { + return new[] + { + 0xe6100f90u, // SADD8 R0, R0, R0 + 0xe6100ff0u, // SSUB8 R0, R0, R0 + 0xe6300f90u, // SHADD8 R0, R0, R0 + 0xe6300ff0u, // SHSUB8 R0, R0, R0 + 0xe6500f90u, // UADD8 R0, R0, R0 + 0xe6500ff0u, // USUB8 R0, R0, R0 + 0xe6700f90u, // UHADD8 R0, R0, R0 + 0xe6700ff0u, // UHSUB8 R0, R0, R0 + }; + } + + private static uint[] UQAddSub16() + { + return new[] + { + 0xe6200f10u, // QADD16 R0, R0, R0 + 0xe6600f10u, // UQADD16 R0, R0, R0 + 0xe6600f70u, // UQSUB16 R0, R0, R0 + }; + } + + private static uint[] UQAddSub8() + { + return new[] + { + 0xe6600f90u, // UQADD8 R0, R0, R0 + 0xe6600ff0u, // UQSUB8 R0, R0, R0 + }; + } + + private static uint[] SsatUsat() + { + return new[] + { + 0xe6a00010u, // SSAT R0, #1, R0, LSL #0 + 0xe6a00050u, // SSAT R0, #1, R0, ASR #32 + 0xe6e00010u, // USAT R0, #0, R0, LSL #0 + 0xe6e00050u, // USAT R0, #0, R0, ASR #32 + }; + } + + private static uint[] Ssat16Usat16() + { + return new[] + { + 0xe6a00f30u, // SSAT16 R0, #1, R0 + 0xe6e00f30u, // USAT16 R0, #0, R0 + }; + } + + private static uint[] LsrLslAsrRor() + { + return new[] + { + 0xe1b00030u, // LSRS R0, R0, R0 + 0xe1b00010u, // LSLS R0, R0, R0 + 0xe1b00050u, // ASRS R0, R0, R0 + 0xe1b00070u, // RORS R0, R0, R0 + }; + } + #endregion + + private const int RndCnt = 2; + + [Test, Pairwise, Description("RBIT , ")] + public void Rbit_32bit([Values(0u, 0xdu)] uint rd, + [Values(1u, 0xdu)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn) + { + uint opcode = 0xe6ff0f30u; // RBIT R0, R0 + opcode |= ((rm & 15) << 0) | ((rd & 15) << 12); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r1: wn, sp: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Lsr_Lsl_Asr_Ror([ValueSource(nameof(LsrLslAsrRor))] uint opcode, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint shiftValue, + [Range(0, 31)] int shiftAmount) + { + uint rd = 0; + uint rm = 1; + uint rs = 2; + opcode |= ((rm & 15) << 0) | ((rd & 15) << 12) | ((rs & 15) << 8); + + SingleOpcode(opcode, r1: shiftValue, r2: (uint)shiftAmount); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Shadd8([Values(0u, 0xdu)] uint rd, + [Values(1u)] uint rm, + [Values(2u)] uint rn, + [Random(RndCnt)] uint w0, + [Random(RndCnt)] uint w1, + [Random(RndCnt)] uint w2) + { + uint opcode = 0xE6300F90u; // SHADD8 R0, R0, R0 + + opcode |= ((rm & 15) << 0) | ((rd & 15) << 12) | ((rn & 15) << 16); + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: w0, r1: w1, r2: w2, sp: sp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Shsub8([Values(0u, 0xdu)] uint rd, + [Values(1u)] uint rm, + [Values(2u)] uint rn, + [Random(RndCnt)] uint w0, + [Random(RndCnt)] uint w1, + [Random(RndCnt)] uint w2) + { + uint opcode = 0xE6300FF0u; // SHSUB8 R0, R0, R0 + + opcode |= ((rm & 15) << 0) | ((rd & 15) << 12) | ((rn & 15) << 16); + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: w0, r1: w1, r2: w2, sp: sp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Ssat_Usat([ValueSource(nameof(SsatUsat))] uint opcode, + [Values(0u, 0xdu)] uint rd, + [Values(1u, 0xdu)] uint rn, + [Values(0u, 7u, 8u, 0xfu, 0x10u, 0x1fu)] uint sat, + [Values(0u, 7u, 8u, 0xfu, 0x10u, 0x1fu)] uint shift, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn) + { + opcode |= ((rn & 15) << 0) | ((shift & 31) << 7) | ((rd & 15) << 12) | ((sat & 31) << 16); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r1: wn, sp: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Ssat16_Usat16([ValueSource(nameof(Ssat16Usat16))] uint opcode, + [Values(0u, 0xdu)] uint rd, + [Values(1u, 0xdu)] uint rn, + [Values(0u, 7u, 8u, 0xfu)] uint sat, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn) + { + opcode |= ((rn & 15) << 0) | ((rd & 15) << 12) | ((sat & 15) << 16); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r1: wn, sp: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_H_AddSub_8([ValueSource(nameof(SuHAddSub8))] uint opcode, + [Values(0u, 0xdu)] uint rd, + [Values(1u)] uint rm, + [Values(2u)] uint rn, + [Random(RndCnt)] uint w0, + [Random(RndCnt)] uint w1, + [Random(RndCnt)] uint w2) + { + opcode |= ((rm & 15) << 0) | ((rd & 15) << 12) | ((rn & 15) << 16); + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: w0, r1: w1, r2: w2, sp: sp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void U_Q_AddSub_16([ValueSource(nameof(UQAddSub16))] uint opcode, + [Values(0u, 0xdu)] uint rd, + [Values(1u)] uint rm, + [Values(2u)] uint rn, + [Random(RndCnt)] uint w0, + [Random(RndCnt)] uint w1, + [Random(RndCnt)] uint w2) + { + opcode |= ((rm & 15) << 0) | ((rd & 15) << 12) | ((rn & 15) << 16); + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: w0, r1: w1, r2: w2, sp: sp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void U_Q_AddSub_8([ValueSource(nameof(UQAddSub8))] uint opcode, + [Values(0u, 0xdu)] uint rd, + [Values(1u)] uint rm, + [Values(2u)] uint rn, + [Random(RndCnt)] uint w0, + [Random(RndCnt)] uint w1, + [Random(RndCnt)] uint w2) + { + opcode |= ((rm & 15) << 0) | ((rd & 15) << 12) | ((rn & 15) << 16); + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: w0, r1: w1, r2: w2, sp: sp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Uadd8_Sel([Values(0u)] uint rd, + [Values(1u)] uint rm, + [Values(2u)] uint rn, + [Random(RndCnt)] uint w0, + [Random(RndCnt)] uint w1, + [Random(RndCnt)] uint w2) + { + uint opUadd8 = 0xE6500F90; // UADD8 R0, R0, R0 + uint opSel = 0xE6800FB0; // SEL R0, R0, R0 + + opUadd8 |= ((rm & 15) << 0) | ((rd & 15) << 12) | ((rn & 15) << 16); + opSel |= ((rm & 15) << 0) | ((rd & 15) << 12) | ((rn & 15) << 16); + + SetContext(r0: w0, r1: w1, r2: w2); + Opcode(opUadd8); + Opcode(opSel); + Opcode(0xE12FFF1E); // BX LR + ExecuteOpcodes(); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestAluBinary.cs b/src/Ryujinx.Tests/Cpu/CpuTestAluBinary.cs new file mode 100644 index 00000000..1e48086b --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestAluBinary.cs @@ -0,0 +1,307 @@ +#define AluBinary + +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("AluBinary")] + public sealed class CpuTestAluBinary : CpuTest + { +#if AluBinary + public struct CrcTest + { + public uint Crc; + public ulong Value; + public bool C; + + public uint[] Results; // One result for each CRC variant (8, 16, 32) + + public CrcTest(uint crc, ulong value, bool c, params uint[] results) + { + Crc = crc; + Value = value; + C = c; + Results = results; + } + } + + #region "ValueSource (CRC32)" + private static CrcTest[] _CRC32_Test_Values_() + { + // Created with http://www.sunshine2k.de/coding/javascript/crc/crc_js.html, with: + // - non-reflected polynomials + // - input reflected, result reflected + // - bytes in order of increasing significance + // - xor 0 + // Only includes non-C variant, as the other can be tested with unicorn. + + return new[] + { + new CrcTest(0x00000000u, 0x00_00_00_00_00_00_00_00u, false, 0x00000000, 0x00000000, 0x00000000, 0x00000000), + new CrcTest(0x00000000u, 0x7f_ff_ff_ff_ff_ff_ff_ffu, false, 0x2d02ef8d, 0xbe2612ff, 0xdebb20e3, 0xa9de8355), + new CrcTest(0x00000000u, 0x80_00_00_00_00_00_00_00u, false, 0x00000000, 0x00000000, 0x00000000, 0xedb88320), + new CrcTest(0x00000000u, 0xff_ff_ff_ff_ff_ff_ff_ffu, false, 0x2d02ef8d, 0xbe2612ff, 0xdebb20e3, 0x44660075), + new CrcTest(0x00000000u, 0xa0_02_f1_ca_52_78_8c_1cu, false, 0x14015c4f, 0x02799256, 0x9063c9e5, 0x8816610a), + + new CrcTest(0xffffffffu, 0x00_00_00_00_00_00_00_00u, false, 0x2dfd1072, 0xbe26ed00, 0xdebb20e3, 0x9add2096), + new CrcTest(0xffffffffu, 0x7f_ff_ff_ff_ff_ff_ff_ffu, false, 0x00ffffff, 0x0000ffff, 0x00000000, 0x3303a3c3), + new CrcTest(0xffffffffu, 0x80_00_00_00_00_00_00_00u, false, 0x2dfd1072, 0xbe26ed00, 0xdebb20e3, 0x7765a3b6), + new CrcTest(0xffffffffu, 0xff_ff_ff_ff_ff_ff_ff_ffu, false, 0x00ffffff, 0x0000ffff, 0x00000000, 0xdebb20e3), + new CrcTest(0xffffffffu, 0xa0_02_f1_ca_52_78_8c_1cu, false, 0x39fc4c3d, 0xbc5f7f56, 0x4ed8e906, 0x12cb419c), + }; + } + #endregion + + [Test, Combinatorial] + public void Crc32_b_h_w_x([Values(0u)] uint rd, + [Values(1u)] uint rn, + [Values(2u)] uint rm, + [Range(0u, 3u)] uint size, + [ValueSource(nameof(_CRC32_Test_Values_))] CrcTest test) + { + uint opcode = 0x1AC04000; // CRC32B W0, W0, W0 + + opcode |= size << 10; + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + if (size == 3) + { + opcode |= 0x80000000; + } + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: test.Crc, x2: test.Value, x31: w31, runUnicorn: false); + + ExecutionContext context = GetContext(); + ulong result = context.GetX((int)rd); + Assert.That(result == test.Results[size]); + } + + [Test, Pairwise, Description("CRC32X , , "), Ignore("Unicorn fails.")] + public void Crc32x([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] uint wn, + [Values((ulong)0x00_00_00_00_00_00_00_00, + (ulong)0x7F_FF_FF_FF_FF_FF_FF_FF, + 0x80_00_00_00_00_00_00_00, + 0xFF_FF_FF_FF_FF_FF_FF_FF)] ulong xm) + { + uint opcode = 0x9AC04C00; // CRC32X W0, W0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: xm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CRC32W , , "), Ignore("Unicorn fails.")] + public void Crc32w([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] uint wn, + [Values((uint)0x00_00_00_00, (uint)0x7F_FF_FF_FF, + 0x80_00_00_00, 0xFF_FF_FF_FF)] uint wm) + { + uint opcode = 0x1AC04800; // CRC32W W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CRC32H , , "), Ignore("Unicorn fails.")] + public void Crc32h([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] uint wn, + [Values((ushort)0x00_00, (ushort)0x7F_FF, + (ushort)0x80_00, (ushort)0xFF_FF)] ushort wm) + { + uint opcode = 0x1AC04400; // CRC32H W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CRC32B , , "), Ignore("Unicorn fails.")] + public void Crc32b([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] uint wn, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] byte wm) + { + uint opcode = 0x1AC04000; // CRC32B W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CRC32CX , , ")] + public void Crc32cx([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] uint wn, + [Values((ulong)0x00_00_00_00_00_00_00_00, + (ulong)0x7F_FF_FF_FF_FF_FF_FF_FF, + 0x80_00_00_00_00_00_00_00, + 0xFF_FF_FF_FF_FF_FF_FF_FF)] ulong xm) + { + uint opcode = 0x9AC05C00; // CRC32CX W0, W0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: xm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CRC32CW , , ")] + public void Crc32cw([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] uint wn, + [Values((uint)0x00_00_00_00, (uint)0x7F_FF_FF_FF, + 0x80_00_00_00, 0xFF_FF_FF_FF)] uint wm) + { + uint opcode = 0x1AC05800; // CRC32CW W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CRC32CH , , ")] + public void Crc32ch([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] uint wn, + [Values((ushort)0x00_00, (ushort)0x7F_FF, + (ushort)0x80_00, (ushort)0xFF_FF)] ushort wm) + { + uint opcode = 0x1AC05400; // CRC32CH W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CRC32CB , , ")] + public void Crc32cb([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0xFFFFFFFFu)] uint wn, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] byte wm) + { + uint opcode = 0x1AC05000; // CRC32CB W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SDIV , , ")] + public void Sdiv_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm) + { + uint opcode = 0x9AC00C00; // SDIV X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SDIV , , ")] + public void Sdiv_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm) + { + uint opcode = 0x1AC00C00; // SDIV W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UDIV , , ")] + public void Udiv_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm) + { + uint opcode = 0x9AC00800; // UDIV X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UDIV , , ")] + public void Udiv_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm) + { + uint opcode = 0x1AC00800; // UDIV W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestAluBinary32.cs b/src/Ryujinx.Tests/Cpu/CpuTestAluBinary32.cs new file mode 100644 index 00000000..43d05436 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestAluBinary32.cs @@ -0,0 +1,95 @@ +#define AluBinary32 + +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + + [Category("AluBinary32")] + public sealed class CpuTestAluBinary32 : CpuTest32 + { +#if AluBinary32 + public struct CrcTest32 + { + public uint Crc; + public uint Value; + public bool C; + + public uint[] Results; // One result for each CRC variant (8, 16, 32) + + public CrcTest32(uint crc, uint value, bool c, params uint[] results) + { + Crc = crc; + Value = value; + C = c; + Results = results; + } + } + + #region "ValueSource (CRC32/CRC32C)" + private static CrcTest32[] _CRC32_Test_Values_() + { + // Created with http://www.sunshine2k.de/coding/javascript/crc/crc_js.html, with: + // - non-reflected polynomials + // - input reflected, result reflected + // - bytes in order of increasing significance + // - xor 0 + + return new[] + { + new CrcTest32(0x00000000u, 0x00_00_00_00u, false, 0x00000000, 0x00000000, 0x00000000), + new CrcTest32(0x00000000u, 0x7f_ff_ff_ffu, false, 0x2d02ef8d, 0xbe2612ff, 0x3303a3c3), + new CrcTest32(0x00000000u, 0x80_00_00_00u, false, 0x00000000, 0x00000000, 0xedb88320), + new CrcTest32(0x00000000u, 0xff_ff_ff_ffu, false, 0x2d02ef8d, 0xbe2612ff, 0xdebb20e3), + new CrcTest32(0x00000000u, 0x9d_cb_12_f0u, false, 0xbdbdf21c, 0xe70590f5, 0x3f7480c5), + + new CrcTest32(0xffffffffu, 0x00_00_00_00u, false, 0x2dfd1072, 0xbe26ed00, 0xdebb20e3), + new CrcTest32(0xffffffffu, 0x7f_ff_ff_ffu, false, 0x00ffffff, 0x0000ffff, 0xedb88320), + new CrcTest32(0xffffffffu, 0x80_00_00_00u, false, 0x2dfd1072, 0xbe26ed00, 0x3303a3c3), + new CrcTest32(0xffffffffu, 0xff_ff_ff_ffu, false, 0x00ffffff, 0x0000ffff, 0x00000000), + new CrcTest32(0xffffffffu, 0x9d_cb_12_f0u, false, 0x9040e26e, 0x59237df5, 0xe1cfa026), + + new CrcTest32(0x00000000u, 0x00_00_00_00u, true, 0x00000000, 0x00000000, 0x00000000), + new CrcTest32(0x00000000u, 0x7f_ff_ff_ffu, true, 0xad7d5351, 0x0e9e77d2, 0x356e8f40), + new CrcTest32(0x00000000u, 0x80_00_00_00u, true, 0x00000000, 0x00000000, 0x82f63b78), + new CrcTest32(0x00000000u, 0xff_ff_ff_ffu, true, 0xad7d5351, 0x0e9e77d2, 0xb798b438), + new CrcTest32(0x00000000u, 0x9d_cb_12_f0u, true, 0xf36e6f75, 0xb5ff99e6, 0x782dfbf1), + + new CrcTest32(0xffffffffu, 0x00_00_00_00u, true, 0xad82acae, 0x0e9e882d, 0xb798b438), + new CrcTest32(0xffffffffu, 0x7f_ff_ff_ffu, true, 0x00ffffff, 0x0000ffff, 0x82f63b78), + new CrcTest32(0xffffffffu, 0x80_00_00_00u, true, 0xad82acae, 0x0e9e882d, 0x356e8f40), + new CrcTest32(0xffffffffu, 0xff_ff_ff_ffu, true, 0x00ffffff, 0x0000ffff, 0x00000000), + new CrcTest32(0xffffffffu, 0x9d_cb_12_f0u, true, 0x5eecc3db, 0xbb6111cb, 0xcfb54fc9), + }; + } + #endregion + + [Test, Combinatorial] + public void Crc32_Crc32c_b_h_w([Values(0u)] uint rd, + [Values(1u)] uint rn, + [Values(2u)] uint rm, + [Range(0u, 2u)] uint size, + [ValueSource(nameof(_CRC32_Test_Values_))] CrcTest32 test) + { + // Unicorn does not yet support 32bit crc instructions, so test against a known table of results/values. + + uint opcode = 0xe1000040; // CRC32B R0, R0, R0 + opcode |= ((rm & 15) << 0) | ((rd & 15) << 12) | ((rn & 15) << 16); + opcode |= size << 21; + if (test.C) + { + opcode |= 1 << 9; + } + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r1: test.Crc, r2: test.Value, sp: sp, runUnicorn: false); + + ExecutionContext context = GetContext(); + ulong result = context.GetX((int)rd); + Assert.That(result == test.Results[size]); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestAluImm.cs b/src/Ryujinx.Tests/Cpu/CpuTestAluImm.cs new file mode 100644 index 00000000..5c00cbc4 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestAluImm.cs @@ -0,0 +1,433 @@ +#define AluImm + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("AluImm")] + public sealed class CpuTestAluImm : CpuTest + { +#if AluImm + + [Test, Pairwise, Description("ADD , , #{, }")] + public void Add_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values(0u, 4095u)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint opcode = 0x91000000; // ADD X0, X0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + + if (rn != 31) + { + SingleOpcode(opcode, x1: xnSp); + } + else + { + SingleOpcode(opcode, x31: xnSp); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADD , , #{, }")] + public void Add_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values(0u, 4095u)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint opcode = 0x11000000; // ADD W0, W0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + + if (rn != 31) + { + SingleOpcode(opcode, x1: wnWsp); + } + else + { + SingleOpcode(opcode, x31: wnWsp); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDS , , #{, }")] + public void Adds_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values(0u, 4095u)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint opcode = 0xB1000000; // ADDS X0, X0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + + if (rn != 31) + { + SingleOpcode(opcode, x1: xnSp); + } + else + { + SingleOpcode(opcode, x31: xnSp); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDS , , #{, }")] + public void Adds_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values(0u, 4095u)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint opcode = 0x31000000; // ADDS W0, W0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + + if (rn != 31) + { + SingleOpcode(opcode, x1: wnWsp); + } + else + { + SingleOpcode(opcode, x31: wnWsp); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("AND , , #")] + public void And_N1_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 31u, 32u, 62u)] uint imms, // + [Values(0u, 31u, 32u, 63u)] uint immr) // + { + uint opcode = 0x92400000; // AND X0, X0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("AND , , #")] + public void And_N0_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 15u, 16u, 30u)] uint imms, // + [Values(0u, 15u, 16u, 31u)] uint immr) // + { + uint opcode = 0x92000000; // AND X0, X0, #0x100000001 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("AND , , #")] + public void And_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 15u, 16u, 30u)] uint imms, // + [Values(0u, 15u, 16u, 31u)] uint immr) // + { + uint opcode = 0x12000000; // AND W0, W0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ANDS , , #")] + public void Ands_N1_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 31u, 32u, 62u)] uint imms, // + [Values(0u, 31u, 32u, 63u)] uint immr) // + { + uint opcode = 0xF2400000; // ANDS X0, X0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ANDS , , #")] + public void Ands_N0_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 15u, 16u, 30u)] uint imms, // + [Values(0u, 15u, 16u, 31u)] uint immr) // + { + uint opcode = 0xF2000000; // ANDS X0, X0, #0x100000001 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ANDS , , #")] + public void Ands_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 15u, 16u, 30u)] uint imms, // + [Values(0u, 15u, 16u, 31u)] uint immr) // + { + uint opcode = 0x72000000; // ANDS W0, W0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("EOR , , #")] + public void Eor_N1_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 31u, 32u, 62u)] uint imms, // + [Values(0u, 31u, 32u, 63u)] uint immr) // + { + uint opcode = 0xD2400000; // EOR X0, X0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("EOR , , #")] + public void Eor_N0_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 15u, 16u, 30u)] uint imms, // + [Values(0u, 15u, 16u, 31u)] uint immr) // + { + uint opcode = 0xD2000000; // EOR X0, X0, #0x100000001 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("EOR , , #")] + public void Eor_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 15u, 16u, 30u)] uint imms, // + [Values(0u, 15u, 16u, 31u)] uint immr) // + { + uint opcode = 0x52000000; // EOR W0, W0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ORR , , #")] + public void Orr_N1_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 31u, 32u, 62u)] uint imms, // + [Values(0u, 31u, 32u, 63u)] uint immr) // + { + uint opcode = 0xB2400000; // ORR X0, X0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ORR , , #")] + public void Orr_N0_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 15u, 16u, 30u)] uint imms, // + [Values(0u, 15u, 16u, 31u)] uint immr) // + { + uint opcode = 0xB2000000; // ORR X0, X0, #0x100000001 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ORR , , #")] + public void Orr_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 15u, 16u, 30u)] uint imms, // + [Values(0u, 15u, 16u, 31u)] uint immr) // + { + uint opcode = 0x32000000; // ORR W0, W0, #0x1 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB , , #{, }")] + public void Sub_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values(0u, 4095u)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint opcode = 0xD1000000; // SUB X0, X0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + + if (rn != 31) + { + SingleOpcode(opcode, x1: xnSp); + } + else + { + SingleOpcode(opcode, x31: xnSp); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB , , #{, }")] + public void Sub_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values(0u, 4095u)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint opcode = 0x51000000; // SUB W0, W0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + + if (rn != 31) + { + SingleOpcode(opcode, x1: wnWsp); + } + else + { + SingleOpcode(opcode, x31: wnWsp); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUBS , , #{, }")] + public void Subs_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values(0u, 4095u)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint opcode = 0xF1000000; // SUBS X0, X0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + + if (rn != 31) + { + SingleOpcode(opcode, x1: xnSp); + } + else + { + SingleOpcode(opcode, x31: xnSp); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUBS , , #{, }")] + public void Subs_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values(0u, 4095u)] uint imm, + [Values(0b00u, 0b01u)] uint shift) // + { + uint opcode = 0x71000000; // SUBS W0, W0, #0, LSL #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((imm & 4095) << 10); + + if (rn != 31) + { + SingleOpcode(opcode, x1: wnWsp); + } + else + { + SingleOpcode(opcode, x31: wnWsp); + } + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestAluImm32.cs b/src/Ryujinx.Tests/Cpu/CpuTestAluImm32.cs new file mode 100644 index 00000000..eeeef085 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestAluImm32.cs @@ -0,0 +1,55 @@ +#define AluRs32 + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("AluImm32")] + public sealed class CpuTestAluImm32 : CpuTest32 + { +#if AluRs32 + + #region "ValueSource (Opcodes)" + private static uint[] Opcodes() + { + return new[] + { + 0xe2a00000u, // ADC R0, R0, #0 + 0xe2b00000u, // ADCS R0, R0, #0 + 0xe2800000u, // ADD R0, R0, #0 + 0xe2900000u, // ADDS R0, R0, #0 + 0xe3c00000u, // BIC R0, R0, #0 + 0xe3d00000u, // BICS R0, R0, #0 + 0xe2600000u, // RSB R0, R0, #0 + 0xe2700000u, // RSBS R0, R0, #0 + 0xe2e00000u, // RSC R0, R0, #0 + 0xe2f00000u, // RSCS R0, R0, #0 + 0xe2c00000u, // SBC R0, R0, #0 + 0xe2d00000u, // SBCS R0, R0, #0 + 0xe2400000u, // SUB R0, R0, #0 + 0xe2500000u, // SUBS R0, R0, #0 + }; + } + #endregion + + private const int RndCnt = 2; + + [Test, Pairwise] + public void TestCpuTestAluImm32([ValueSource(nameof(Opcodes))] uint opcode, + [Values(0u, 13u)] uint rd, + [Values(1u, 13u)] uint rn, + [Random(RndCnt)] uint imm, + [Random(RndCnt)] uint wn, + [Values(true, false)] bool carryIn) + { + opcode |= ((imm & 0xfff) << 0) | ((rn & 15) << 16) | ((rd & 15) << 12); + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r1: wn, sp: sp, carry: carryIn); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestAluRs.cs b/src/Ryujinx.Tests/Cpu/CpuTestAluRs.cs new file mode 100644 index 00000000..f63360c2 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestAluRs.cs @@ -0,0 +1,895 @@ +#define AluRs + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("AluRs")] + public sealed class CpuTestAluRs : CpuTest + { +#if AluRs + + [Test, Pairwise, Description("ADC , , ")] + public void Adc_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values] bool carryIn) + { + uint opcode = 0x9A000000; // ADC X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31, carry: carryIn); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADC , , ")] + public void Adc_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values] bool carryIn) + { + uint opcode = 0x1A000000; // ADC W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31, carry: carryIn); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADCS , , ")] + public void Adcs_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values] bool carryIn) + { + uint opcode = 0xBA000000; // ADCS X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31, carry: carryIn); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADCS , , ")] + public void Adcs_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values] bool carryIn) + { + uint opcode = 0x3A000000; // ADCS W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31, carry: carryIn); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADD , , {, #}")] + public void Add_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] uint amount) + { + uint opcode = 0x8B000000; // ADD X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADD , , {, #}")] + public void Add_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] uint amount) + { + uint opcode = 0x0B000000; // ADD W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDS , , {, #}")] + public void Adds_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] uint amount) + { + uint opcode = 0xAB000000; // ADDS X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDS , , {, #}")] + public void Adds_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] uint amount) + { + uint opcode = 0x2B000000; // ADDS W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("AND , , {, #}")] + public void And_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] uint amount) + { + uint opcode = 0x8A000000; // AND X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("AND , , {, #}")] + public void And_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] uint amount) + { + uint opcode = 0x0A000000; // AND W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ANDS , , {, #}")] + public void Ands_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] uint amount) + { + uint opcode = 0xEA000000; // ANDS X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ANDS , , {, #}")] + public void Ands_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] uint amount) + { + uint opcode = 0x6A000000; // ANDS W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ASRV , , ")] + public void Asrv_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0ul, 31ul, 32ul, 63ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm) + { + uint opcode = 0x9AC02800; // ASRV X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ASRV , , ")] + public void Asrv_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 15u, 16u, 31u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm) + { + uint opcode = 0x1AC02800; // ASRV W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("BIC , , {, #}")] + public void Bic_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] uint amount) + { + uint opcode = 0x8A200000; // BIC X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("BIC , , {, #}")] + public void Bic_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] uint amount) + { + uint opcode = 0x0A200000; // BIC W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("BICS , , {, #}")] + public void Bics_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] uint amount) + { + uint opcode = 0xEA200000; // BICS X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("BICS , , {, #}")] + public void Bics_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] uint amount) + { + uint opcode = 0x6A200000; // BICS W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("EON , , {, #}")] + public void Eon_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] uint amount) + { + uint opcode = 0xCA200000; // EON X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("EON , , {, #}")] + public void Eon_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] uint amount) + { + uint opcode = 0x4A200000; // EON W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("EOR , , {, #}")] + public void Eor_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] uint amount) + { + uint opcode = 0xCA000000; // EOR X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("EOR , , {, #}")] + public void Eor_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] uint amount) + { + uint opcode = 0x4A000000; // EOR W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("EXTR , , , #")] + public void Extr_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0u, 31u, 32u, 63u)] uint lsb) + { + uint opcode = 0x93C00000; // EXTR X0, X0, X0, #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((lsb & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("EXTR , , , #")] + public void Extr_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0u, 15u, 16u, 31u)] uint lsb) + { + uint opcode = 0x13800000; // EXTR W0, W0, W0, #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((lsb & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("LSLV , , ")] + public void Lslv_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0ul, 31ul, 32ul, 63ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm) + { + uint opcode = 0x9AC02000; // LSLV X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("LSLV , , ")] + public void Lslv_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 15u, 16u, 31u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm) + { + uint opcode = 0x1AC02000; // LSLV W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("LSRV , , ")] + public void Lsrv_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0ul, 31ul, 32ul, 63ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm) + { + uint opcode = 0x9AC02400; // LSRV X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("LSRV , , ")] + public void Lsrv_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 15u, 16u, 31u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm) + { + uint opcode = 0x1AC02400; // LSRV W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ORN , , {, #}")] + public void Orn_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] uint amount) + { + uint opcode = 0xAA200000; // ORN X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ORN , , {, #}")] + public void Orn_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] uint amount) + { + uint opcode = 0x2A200000; // ORN W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ORR , , {, #}")] + public void Orr_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] uint amount) + { + uint opcode = 0xAA000000; // ORR X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ORR , , {, #}")] + public void Orr_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] uint amount) + { + uint opcode = 0x2A000000; // ORR W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("RORV , , ")] + public void Rorv_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0ul, 31ul, 32ul, 63ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm) + { + uint opcode = 0x9AC02C00; // RORV X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("RORV , , ")] + public void Rorv_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 15u, 16u, 31u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm) + { + uint opcode = 0x1AC02C00; // RORV W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SBC , , ")] + public void Sbc_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values] bool carryIn) + { + uint opcode = 0xDA000000; // SBC X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31, carry: carryIn); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SBC , , ")] + public void Sbc_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values] bool carryIn) + { + uint opcode = 0x5A000000; // SBC W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31, carry: carryIn); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SBCS , , ")] + public void Sbcs_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values] bool carryIn) + { + uint opcode = 0xFA000000; // SBCS X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31, carry: carryIn); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SBCS , , ")] + public void Sbcs_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values] bool carryIn) + { + uint opcode = 0x7A000000; // SBCS W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31, carry: carryIn); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB , , {, #}")] + public void Sub_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] uint amount) + { + uint opcode = 0xCB000000; // SUB X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB , , {, #}")] + public void Sub_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] uint amount) + { + uint opcode = 0x4B000000; // SUB W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUBS , , {, #}")] + public void Subs_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 31u, 32u, 63u)] uint amount) + { + uint opcode = 0xEB000000; // SUBS X0, X0, X0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUBS , , {, #}")] + public void Subs_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b00u, 0b01u, 0b10u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] uint amount) + { + uint opcode = 0x6B000000; // SUBS W0, W0, W0, LSL #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((shift & 3) << 22) | ((amount & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestAluRs32.cs b/src/Ryujinx.Tests/Cpu/CpuTestAluRs32.cs new file mode 100644 index 00000000..0e71b183 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestAluRs32.cs @@ -0,0 +1,82 @@ +#define AluRs32 + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("AluRs32")] + public sealed class CpuTestAluRs32 : CpuTest32 + { +#if AluRs32 + + #region "ValueSource (Opcodes)" + private static uint[] _Add_Adds_Rsb_Rsbs_() + { + return new[] + { + 0xe0800000u, // ADD R0, R0, R0, LSL #0 + 0xe0900000u, // ADDS R0, R0, R0, LSL #0 + 0xe0600000u, // RSB R0, R0, R0, LSL #0 + 0xe0700000u, // RSBS R0, R0, R0, LSL #0 + }; + } + + private static uint[] _Adc_Adcs_Rsc_Rscs_Sbc_Sbcs_() + { + return new[] + { + 0xe0a00000u, // ADC R0, R0, R0 + 0xe0b00000u, // ADCS R0, R0, R0 + 0xe0e00000u, // RSC R0, R0, R0 + 0xe0f00000u, // RSCS R0, R0, R0 + 0xe0c00000u, // SBC R0, R0, R0 + 0xe0d00000u, // SBCS R0, R0, R0 + }; + } + #endregion + + + [Test, Pairwise] + public void Adc_Adcs_Rsc_Rscs_Sbc_Sbcs([ValueSource(nameof(_Adc_Adcs_Rsc_Rscs_Sbc_Sbcs_))] uint opcode, + [Values(0u, 13u)] uint rd, + [Values(1u, 13u)] uint rn, + [Values(2u, 13u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values] bool carryIn) + { + opcode |= ((rm & 15) << 0) | ((rn & 15) << 16) | ((rd & 15) << 12); + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r1: wn, r2: wm, sp: sp, carry: carryIn); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Add_Adds_Rsb_Rsbs([ValueSource(nameof(_Add_Adds_Rsb_Rsbs_))] uint opcode, + [Values(0u, 13u)] uint rd, + [Values(1u, 13u)] uint rn, + [Values(2u, 13u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint shift, // + [Values(0u, 15u, 16u, 31u)] uint amount) + { + opcode |= ((rm & 15) << 0) | ((rn & 15) << 16) | ((rd & 15) << 12); + opcode |= ((shift & 3) << 5) | ((amount & 31) << 7); + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r1: wn, r2: wm, sp: sp); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestAluRx.cs b/src/Ryujinx.Tests/Cpu/CpuTestAluRx.cs new file mode 100644 index 00000000..9897bdba --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestAluRx.cs @@ -0,0 +1,723 @@ +#define AluRx + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("AluRx")] + public sealed class CpuTestAluRx : CpuTest + { +#if AluRx + + [Test, Pairwise, Description("ADD , , {, {#}}")] + public void Add_X_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((ulong)0x0000000000000000, (ulong)0x7FFFFFFFFFFFFFFF, + 0x8000000000000000, 0xFFFFFFFFFFFFFFFF)] ulong xm, + [Values(0b011u, 0b111u)] uint extend, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x8B206000; // ADD X0, X0, X0, UXTX #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + if (rn != 31) + { + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xnSp, x2: xm, x31: x31); + } + else + { + SingleOpcode(opcode, x31: xnSp, x2: xm); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADD , , {, {#}}")] + public void Add_W_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + 0x80000000, 0xFFFFFFFF)] uint wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x8B200000; // ADD X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + if (rn != 31) + { + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: x31); + } + else + { + SingleOpcode(opcode, x31: xnSp, x2: wm); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADD , , {, {#}}")] + public void Add_H_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] ushort wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x8B200000; // ADD X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + if (rn != 31) + { + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: x31); + } + else + { + SingleOpcode(opcode, x31: xnSp, x2: wm); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADD , , {, {#}}")] + public void Add_B_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] byte wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x8B200000; // ADD X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + if (rn != 31) + { + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: x31); + } + else + { + SingleOpcode(opcode, x31: xnSp, x2: wm); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADD , , {, {#}}")] + public void Add_W_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + 0x80000000, 0xFFFFFFFF)] uint wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x0B200000; // ADD W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + if (rn != 31) + { + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: w31); + } + else + { + SingleOpcode(opcode, x31: wnWsp, x2: wm); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADD , , {, {#}}")] + public void Add_H_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] ushort wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x0B200000; // ADD W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + if (rn != 31) + { + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: w31); + } + else + { + SingleOpcode(opcode, x31: wnWsp, x2: wm); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADD , , {, {#}}")] + public void Add_B_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] byte wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x0B200000; // ADD W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + if (rn != 31) + { + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: w31); + } + else + { + SingleOpcode(opcode, x31: wnWsp, x2: wm); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDS , , {, {#}}")] + public void Adds_X_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((ulong)0x0000000000000000, (ulong)0x7FFFFFFFFFFFFFFF, + 0x8000000000000000, 0xFFFFFFFFFFFFFFFF)] ulong xm, + [Values(0b011u, 0b111u)] uint extend, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0xAB206000; // ADDS X0, X0, X0, UXTX #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + SingleOpcode(opcode, x1: xnSp, x2: xm, x31: xnSp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDS , , {, {#}}")] + public void Adds_W_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + 0x80000000, 0xFFFFFFFF)] uint wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0xAB200000; // ADDS X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: xnSp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDS , , {, {#}}")] + public void Adds_H_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] ushort wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0xAB200000; // ADDS X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: xnSp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDS , , {, {#}}")] + public void Adds_B_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] byte wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0xAB200000; // ADDS X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: xnSp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDS , , {, {#}}")] + public void Adds_W_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + 0x80000000, 0xFFFFFFFF)] uint wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x2B200000; // ADDS W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: wnWsp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDS , , {, {#}}")] + public void Adds_H_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] ushort wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x2B200000; // ADDS W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: wnWsp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDS , , {, {#}}")] + public void Adds_B_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] byte wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x2B200000; // ADDS W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: wnWsp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB , , {, {#}}")] + public void Sub_X_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((ulong)0x0000000000000000, (ulong)0x7FFFFFFFFFFFFFFF, + 0x8000000000000000, 0xFFFFFFFFFFFFFFFF)] ulong xm, + [Values(0b011u, 0b111u)] uint extend, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0xCB206000; // SUB X0, X0, X0, UXTX #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + if (rn != 31) + { + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xnSp, x2: xm, x31: x31); + } + else + { + SingleOpcode(opcode, x31: xnSp, x2: xm); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB , , {, {#}}")] + public void Sub_W_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + 0x80000000, 0xFFFFFFFF)] uint wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0xCB200000; // SUB X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + if (rn != 31) + { + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: x31); + } + else + { + SingleOpcode(opcode, x31: xnSp, x2: wm); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB , , {, {#}}")] + public void Sub_H_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] ushort wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0xCB200000; // SUB X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + if (rn != 31) + { + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: x31); + } + else + { + SingleOpcode(opcode, x31: xnSp, x2: wm); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB , , {, {#}}")] + public void Sub_B_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] byte wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0xCB200000; // SUB X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + if (rn != 31) + { + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: x31); + } + else + { + SingleOpcode(opcode, x31: xnSp, x2: wm); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB , , {, {#}}")] + public void Sub_W_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + 0x80000000, 0xFFFFFFFF)] uint wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x4B200000; // SUB W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + if (rn != 31) + { + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: w31); + } + else + { + SingleOpcode(opcode, x31: wnWsp, x2: wm); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB , , {, {#}}")] + public void Sub_H_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] ushort wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x4B200000; // SUB W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + if (rn != 31) + { + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: w31); + } + else + { + SingleOpcode(opcode, x31: wnWsp, x2: wm); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB , , {, {#}}")] + public void Sub_B_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] byte wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x4B200000; // SUB W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + if (rn != 31) + { + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: w31); + } + else + { + SingleOpcode(opcode, x31: wnWsp, x2: wm); + } + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUBS , , {, {#}}")] + public void Subs_X_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((ulong)0x0000000000000000, (ulong)0x7FFFFFFFFFFFFFFF, + 0x8000000000000000, 0xFFFFFFFFFFFFFFFF)] ulong xm, + [Values(0b011u, 0b111u)] uint extend, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0xEB206000; // SUBS X0, X0, X0, UXTX #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + SingleOpcode(opcode, x1: xnSp, x2: xm, x31: xnSp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUBS , , {, {#}}")] + public void Subs_W_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + 0x80000000, 0xFFFFFFFF)] uint wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0xEB200000; // SUBS X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: xnSp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUBS , , {, {#}}")] + public void Subs_H_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] ushort wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0xEB200000; // SUBS X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: xnSp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUBS , , {, {#}}")] + public void Subs_B_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xnSp, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] byte wm, + [Values(0b000u, 0b001u, 0b010u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0xEB200000; // SUBS X0, X0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + SingleOpcode(opcode, x1: xnSp, x2: wm, x31: xnSp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUBS , , {, {#}}")] + public void Subs_W_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values((uint)0x00000000, (uint)0x7FFFFFFF, + 0x80000000, 0xFFFFFFFF)] uint wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x6B200000; // SUBS W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: wnWsp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUBS , , {, {#}}")] + public void Subs_H_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values((ushort)0x0000, (ushort)0x7FFF, + (ushort)0x8000, (ushort)0xFFFF)] ushort wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x6B200000; // SUBS W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: wnWsp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUBS , , {, {#}}")] + public void Subs_B_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wnWsp, + [Values((byte)0x00, (byte)0x7F, + (byte)0x80, (byte)0xFF)] byte wm, + [Values(0b000u, 0b001u, 0b010u, 0b011u, // + [Values(0u, 1u, 2u, 3u, 4u)] uint amount) + { + uint opcode = 0x6B200000; // SUBS W0, W0, W0, UXTB #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((extend & 7) << 13) | ((amount & 7) << 10); + + SingleOpcode(opcode, x1: wnWsp, x2: wm, x31: wnWsp); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestBf32.cs b/src/Ryujinx.Tests/Cpu/CpuTestBf32.cs new file mode 100644 index 00000000..197171b8 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestBf32.cs @@ -0,0 +1,106 @@ +#define Bf32 + +using NUnit.Framework; +using System; + +namespace Ryujinx.Tests.Cpu +{ + [Category("Bf32")] + public sealed class CpuTestBf32 : CpuTest32 + { +#if Bf32 + private const int RndCnt = 2; + + [Test, Pairwise, Description("BFC , #, #")] + public void Bfc([Values(0u, 0xdu)] uint rd, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wd, + [Values(0u, 15u, 16u, 31u)] uint lsb, + [Values(0u, 15u, 16u, 31u)] uint msb) + { + msb = Math.Max(lsb, msb); // Don't test unpredictable for now. + uint opcode = 0xe7c0001fu; // BFC R0, #0, #1 + opcode |= ((rd & 0xf) << 12); + opcode |= ((msb & 31) << 16) | ((lsb & 31) << 7); + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: wd, sp: sp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("BFI , , #, #")] + public void Bfi([Values(0u, 0xdu)] uint rd, + [Values(1u, 0xdu)] uint rn, + [Random(RndCnt)] uint wd, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 15u, 16u, 31u)] uint lsb, + [Values(0u, 15u, 16u, 31u)] uint msb) + { + msb = Math.Max(lsb, msb); // Don't test unpredictable for now. + uint opcode = 0xe7c00010u; // BFI R0, R0, #0, #1 + opcode |= ((rd & 0xf) << 12); + opcode |= ((rn & 0xf) << 0); + opcode |= ((msb & 31) << 16) | ((lsb & 31) << 7); + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: wd, r1: wn, sp: sp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UBFX , , #, #")] + public void Ubfx([Values(0u, 0xdu)] uint rd, + [Values(1u, 0xdu)] uint rn, + [Random(RndCnt)] uint wd, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 15u, 16u, 31u)] uint lsb, + [Values(0u, 15u, 16u, 31u)] uint widthm1) + { + if (lsb + widthm1 > 31) + { + widthm1 -= (lsb + widthm1) - 31; + } + uint opcode = 0xe7e00050u; // UBFX R0, R0, #0, #1 + opcode |= ((rd & 0xf) << 12); + opcode |= ((rn & 0xf) << 0); + opcode |= ((widthm1 & 31) << 16) | ((lsb & 31) << 7); + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: wd, r1: wn, sp: sp); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SBFX , , #, #")] + public void Sbfx([Values(0u, 0xdu)] uint rd, + [Values(1u, 0xdu)] uint rn, + [Random(RndCnt)] uint wd, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 15u, 16u, 31u)] uint lsb, + [Values(0u, 15u, 16u, 31u)] uint widthm1) + { + if (lsb + widthm1 > 31) + { + widthm1 -= (lsb + widthm1) - 31; + } + uint opcode = 0xe7a00050u; // SBFX R0, R0, #0, #1 + opcode |= ((rd & 0xf) << 12); + opcode |= ((rn & 0xf) << 0); + opcode |= ((widthm1 & 31) << 16) | ((lsb & 31) << 7); + + uint sp = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: wd, r1: wn, sp: sp); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestBfm.cs b/src/Ryujinx.Tests/Cpu/CpuTestBfm.cs new file mode 100644 index 00000000..ed911d05 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestBfm.cs @@ -0,0 +1,130 @@ +#define Bfm + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("Bfm")] + public sealed class CpuTestBfm : CpuTest + { +#if Bfm + private const int RndCnt = 2; + + [Test, Pairwise, Description("BFM , , #, #")] + public void Bfm_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Random(RndCnt)] ulong xd, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 31u, 32u, 63u)] uint immr, + [Values(0u, 31u, 32u, 63u)] uint imms) + { + uint opcode = 0xB3400000; // BFM X0, X0, #0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x0: xd, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("BFM , , #, #")] + public void Bfm_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Random(RndCnt)] uint wd, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 15u, 16u, 31u)] uint immr, + [Values(0u, 15u, 16u, 31u)] uint imms) + { + uint opcode = 0x33000000; // BFM W0, W0, #0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x0: wd, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SBFM , , #, #")] + public void Sbfm_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 31u, 32u, 63u)] uint immr, + [Values(0u, 31u, 32u, 63u)] uint imms) + { + uint opcode = 0x93400000; // SBFM X0, X0, #0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SBFM , , #, #")] + public void Sbfm_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 15u, 16u, 31u)] uint immr, + [Values(0u, 15u, 16u, 31u)] uint imms) + { + uint opcode = 0x13000000; // SBFM W0, W0, #0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UBFM , , #, #")] + public void Ubfm_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 31u, 32u, 63u)] uint immr, + [Values(0u, 31u, 32u, 63u)] uint imms) + { + uint opcode = 0xD3400000; // UBFM X0, X0, #0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UBFM , , #, #")] + public void Ubfm_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 15u, 16u, 31u)] uint immr, + [Values(0u, 15u, 16u, 31u)] uint imms) + { + uint opcode = 0x53000000; // UBFM W0, W0, #0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((immr & 63) << 16) | ((imms & 63) << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs b/src/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs new file mode 100644 index 00000000..1bad4c87 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs @@ -0,0 +1,102 @@ +#define CcmpImm + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("CcmpImm")] + public sealed class CpuTestCcmpImm : CpuTest + { +#if CcmpImm + private const int RndCntNzcv = 2; + + [Test, Pairwise, Description("CCMN , #, #, ")] + public void Ccmn_64bit([Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 31u)] uint imm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0xBA400800; // CCMN X0, #0, #0, EQ + opcode |= ((rn & 31) << 5); + opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CCMN , #, #, ")] + public void Ccmn_32bit([Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 31u)] uint imm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0x3A400800; // CCMN W0, #0, #0, EQ + opcode |= ((rn & 31) << 5); + opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CCMP , #, #, ")] + public void Ccmp_64bit([Values(1u, 31u)] uint rn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 31u)] uint imm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0xFA400800; // CCMP X0, #0, #0, EQ + opcode |= ((rn & 31) << 5); + opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CCMP , #, #, ")] + public void Ccmp_32bit([Values(1u, 31u)] uint rn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 31u)] uint imm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0x7A400800; // CCMP W0, #0, #0, EQ + opcode |= ((rn & 31) << 5); + opcode |= ((imm & 31) << 16) | ((cond & 15) << 12) | ((nzcv & 15) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x31: w31); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs b/src/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs new file mode 100644 index 00000000..27fe2411 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs @@ -0,0 +1,110 @@ +#define CcmpReg + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("CcmpReg")] + public sealed class CpuTestCcmpReg : CpuTest + { +#if CcmpReg + private const int RndCntNzcv = 2; + + [Test, Pairwise, Description("CCMN , , #, ")] + public void Ccmn_64bit([Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0xBA400000; // CCMN X0, X0, #0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5); + opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CCMN , , #, ")] + public void Ccmn_32bit([Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0x3A400000; // CCMN W0, W0, #0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5); + opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CCMP , , #, ")] + public void Ccmp_64bit([Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0xFA400000; // CCMP X0, X0, #0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5); + opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CCMP , , #, ")] + public void Ccmp_32bit([Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0x7A400000; // CCMP W0, W0, #0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5); + opcode |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestCsel.cs b/src/Ryujinx.Tests/Cpu/CpuTestCsel.cs new file mode 100644 index 00000000..86b1f092 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestCsel.cs @@ -0,0 +1,205 @@ +#define Csel + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("Csel")] + public sealed class CpuTestCsel : CpuTest + { +#if Csel + + [Test, Pairwise, Description("CSEL , , , ")] + public void Csel_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0x9A800000; // CSEL X0, X0, X0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CSEL , , , ")] + public void Csel_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0x1A800000; // CSEL W0, W0, W0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CSINC , , , ")] + public void Csinc_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0x9A800400; // CSINC X0, X0, X0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CSINC , , , ")] + public void Csinc_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0x1A800400; // CSINC W0, W0, W0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CSINV , , , ")] + public void Csinv_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0xDA800000; // CSINV X0, X0, X0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CSINV , , , ")] + public void Csinv_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0x5A800000; // CSINV W0, W0, W0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CSNEG , , , ")] + public void Csneg_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0xDA800400; // CSNEG X0, X0, X0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CSNEG , , , ")] + public void Csneg_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opcode = 0x5A800400; // CSNEG W0, W0, W0, EQ + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((cond & 15) << 12); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x31: w31); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestMisc.cs b/src/Ryujinx.Tests/Cpu/CpuTestMisc.cs new file mode 100644 index 00000000..aab00976 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestMisc.cs @@ -0,0 +1,485 @@ +#define Misc + +using ARMeilleure.State; +using NUnit.Framework; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("Misc")] + public sealed class CpuTestMisc : CpuTest + { +#if Misc + + #region "ValueSource (Types)" + private static IEnumerable _1S_F_() + { + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + } + } + #endregion + + private const int RndCnt = 2; + + private static readonly bool _noZeros = false; + private static readonly bool _noInfs = false; + private static readonly bool _noNaNs = false; + + #region "AluImm & Csel" + [Test, Pairwise] + public void Adds_Csinc_64bit([Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 4095u)] uint imm, + [Values(0b00u, 0b01u)] uint shift, // + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opCmn = 0xB100001F; // ADDS X31, X0, #0, LSL #0 -> CMN X0, #0, LSL #0 + uint opCset = 0x9A9F07E0; // CSINC X0, X31, X31, EQ -> CSET X0, NE + + opCmn |= ((shift & 3) << 22) | ((imm & 4095) << 10); + opCset |= ((cond & 15) << 12); + + SetContext(x0: xn); + Opcode(opCmn); + Opcode(opCset); + Opcode(0xD65F03C0); // RET + ExecuteOpcodes(); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Adds_Csinc_32bit([Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 4095u)] uint imm, + [Values(0b00u, 0b01u)] uint shift, // + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opCmn = 0x3100001F; // ADDS W31, W0, #0, LSL #0 -> CMN W0, #0, LSL #0 + uint opCset = 0x1A9F07E0; // CSINC W0, W31, W31, EQ -> CSET W0, NE + + opCmn |= ((shift & 3) << 22) | ((imm & 4095) << 10); + opCset |= ((cond & 15) << 12); + + SetContext(x0: wn); + Opcode(opCmn); + Opcode(opCset); + Opcode(0xD65F03C0); // RET + ExecuteOpcodes(); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Subs_Csinc_64bit([Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0u, 4095u)] uint imm, + [Values(0b00u, 0b01u)] uint shift, // + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opCmp = 0xF100001F; // SUBS X31, X0, #0, LSL #0 -> CMP X0, #0, LSL #0 + uint opCset = 0x9A9F07E0; // CSINC X0, X31, X31, EQ -> CSET X0, NE + + opCmp |= ((shift & 3) << 22) | ((imm & 4095) << 10); + opCset |= ((cond & 15) << 12); + + SetContext(x0: xn); + Opcode(opCmp); + Opcode(opCset); + Opcode(0xD65F03C0); // RET + ExecuteOpcodes(); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Subs_Csinc_32bit([Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0u, 4095u)] uint imm, + [Values(0b00u, 0b01u)] uint shift, // + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + uint opCmp = 0x7100001F; // SUBS W31, W0, #0, LSL #0 -> CMP W0, #0, LSL #0 + uint opCset = 0x1A9F07E0; // CSINC W0, W31, W31, EQ -> CSET W0, NE + + opCmp |= ((shift & 3) << 22) | ((imm & 4095) << 10); + opCset |= ((cond & 15) << 12); + + SetContext(x0: wn); + Opcode(opCmp); + Opcode(opCset); + Opcode(0xD65F03C0); // RET + ExecuteOpcodes(); + + CompareAgainstUnicorn(); + } + #endregion + + // Roots. + [Explicit] + [TestCase(0xFFFFFFFDu)] + [TestCase(0x00000005u)] + public void Misc1(uint a) + { + // ((a + 3) * (a - 5)) / ((a + 5) * (a - 3)) = 0 + + /* + ADD W2, W0, 3 + SUB W1, W0, #5 + MUL W2, W2, W1 + ADD W1, W0, 5 + SUB W0, W0, #3 + MUL W0, W1, W0 + SDIV W0, W2, W0 + RET + */ + + SetContext(x0: a); + Opcode(0x11000C02); + Opcode(0x51001401); + Opcode(0x1B017C42); + Opcode(0x11001401); + Opcode(0x51000C00); + Opcode(0x1B007C20); + Opcode(0x1AC00C40); + Opcode(0xD65F03C0); + ExecuteOpcodes(); + + Assert.That(GetContext().GetX(0), Is.Zero); + } + + // 18 integer solutions. + [Explicit] + [TestCase(-20f, -5f)] + [TestCase(-12f, -6f)] + [TestCase(-12f, 3f)] + [TestCase(-8f, -8f)] + [TestCase(-6f, -12f)] + [TestCase(-5f, -20f)] + [TestCase(-4f, 2f)] + [TestCase(-3f, 12f)] + [TestCase(-2f, 4f)] + [TestCase(2f, -4f)] + [TestCase(3f, -12f)] + [TestCase(4f, -2f)] + [TestCase(5f, 20f)] + [TestCase(6f, 12f)] + [TestCase(8f, 8f)] + [TestCase(12f, -3f)] + [TestCase(12f, 6f)] + [TestCase(20f, 5f)] + public void Misc2(float a, float b) + { + // 1 / ((1 / a + 1 / b) ^ 2) = 16 + + /* + FMOV S2, 1.0e+0 + FDIV S0, S2, S0 + FDIV S1, S2, S1 + FADD S0, S0, S1 + FDIV S0, S2, S0 + FMUL S0, S0, S0 + RET + */ + + SetContext(v0: MakeVectorScalar(a), v1: MakeVectorScalar(b)); + Opcode(0x1E2E1002); + Opcode(0x1E201840); + Opcode(0x1E211841); + Opcode(0x1E212800); + Opcode(0x1E201840); + Opcode(0x1E200800); + Opcode(0xD65F03C0); + ExecuteOpcodes(); + + Assert.That(GetContext().GetV(0).As(), Is.EqualTo(16f)); + } + + // 18 integer solutions. + [Explicit] + [TestCase(-20d, -5d)] + [TestCase(-12d, -6d)] + [TestCase(-12d, 3d)] + [TestCase(-8d, -8d)] + [TestCase(-6d, -12d)] + [TestCase(-5d, -20d)] + [TestCase(-4d, 2d)] + [TestCase(-3d, 12d)] + [TestCase(-2d, 4d)] + [TestCase(2d, -4d)] + [TestCase(3d, -12d)] + [TestCase(4d, -2d)] + [TestCase(5d, 20d)] + [TestCase(6d, 12d)] + [TestCase(8d, 8d)] + [TestCase(12d, -3d)] + [TestCase(12d, 6d)] + [TestCase(20d, 5d)] + public void Misc3(double a, double b) + { + // 1 / ((1 / a + 1 / b) ^ 2) = 16 + + /* + FMOV D2, 1.0e+0 + FDIV D0, D2, D0 + FDIV D1, D2, D1 + FADD D0, D0, D1 + FDIV D0, D2, D0 + FMUL D0, D0, D0 + RET + */ + + SetContext(v0: MakeVectorScalar(a), v1: MakeVectorScalar(b)); + Opcode(0x1E6E1002); + Opcode(0x1E601840); + Opcode(0x1E611841); + Opcode(0x1E612800); + Opcode(0x1E601840); + Opcode(0x1E600800); + Opcode(0xD65F03C0); + ExecuteOpcodes(); + + Assert.That(GetContext().GetV(0).As(), Is.EqualTo(16d)); + } + + [Test, Ignore("The Tester supports only one return point.")] + public void MiscF([Range(0u, 92u, 1u)] uint a) + { + static ulong Fn(uint n) + { + ulong x = 0, y = 1, z; + + if (n == 0) + { + return x; + } + + for (uint i = 2; i <= n; i++) + { + z = x + y; + x = y; + y = z; + } + + return y; + } + + /* + 0x0000000000001000: MOV W4, W0 + 0x0000000000001004: CBZ W0, #0x34 + 0x0000000000001008: CMP W0, #1 + 0x000000000000100C: B.LS #0x34 + 0x0000000000001010: MOVZ W2, #0x2 + 0x0000000000001014: MOVZ X1, #0x1 + 0x0000000000001018: MOVZ X3, #0 + 0x000000000000101C: ADD X0, X3, X1 + 0x0000000000001020: ADD W2, W2, #1 + 0x0000000000001024: MOV X3, X1 + 0x0000000000001028: MOV X1, X0 + 0x000000000000102C: CMP W4, W2 + 0x0000000000001030: B.HS #-0x14 + 0x0000000000001034: RET + 0x0000000000001038: MOVZ X0, #0 + 0x000000000000103C: RET + 0x0000000000001040: MOVZ X0, #0x1 + 0x0000000000001044: RET + */ + + SetContext(x0: a); + Opcode(0x2A0003E4); + Opcode(0x340001A0); + Opcode(0x7100041F); + Opcode(0x540001A9); + Opcode(0x52800042); + Opcode(0xD2800021); + Opcode(0xD2800003); + Opcode(0x8B010060); + Opcode(0x11000442); + Opcode(0xAA0103E3); + Opcode(0xAA0003E1); + Opcode(0x6B02009F); + Opcode(0x54FFFF62); + Opcode(0xD65F03C0); + Opcode(0xD2800000); + Opcode(0xD65F03C0); + Opcode(0xD2800020); + Opcode(0xD65F03C0); + ExecuteOpcodes(); + + Assert.That(GetContext().GetX(0), Is.EqualTo(Fn(a))); + } + + [Explicit] + [Test] + public void MiscR() + { + const ulong Result = 5; + + /* + 0x0000000000001000: MOV X0, #2 + 0x0000000000001004: MOV X1, #3 + 0x0000000000001008: ADD X0, X0, X1 + 0x000000000000100C: RET + */ + + Opcode(0xD2800040); + Opcode(0xD2800061); + Opcode(0x8B010000); + Opcode(0xD65F03C0); + ExecuteOpcodes(); + + Assert.That(GetContext().GetX(0), Is.EqualTo(Result)); + + Reset(); + + /* + 0x0000000000001000: MOV X0, #3 + 0x0000000000001004: MOV X1, #2 + 0x0000000000001008: ADD X0, X0, X1 + 0x000000000000100C: RET + */ + + Opcode(0xD2800060); + Opcode(0xD2800041); + Opcode(0x8B010000); + Opcode(0xD65F03C0); + ExecuteOpcodes(); + + Assert.That(GetContext().GetX(0), Is.EqualTo(Result)); + } + + [Explicit] + [TestCase(0ul)] + [TestCase(1ul)] + [TestCase(2ul)] + [TestCase(42ul)] + public void SanityCheck(ulong a) + { + uint opcode = 0xD503201F; // NOP + ExecutionContext context = SingleOpcode(opcode, x0: a); + + Assert.That(context.GetX(0), Is.EqualTo(a)); + } + + [Explicit] + [Test, Pairwise] + public void Misc4([ValueSource(nameof(_1S_F_))] ulong a, + [ValueSource(nameof(_1S_F_))] ulong b, + [ValueSource(nameof(_1S_F_))] ulong c, + [Values(0ul, 1ul, 2ul, 3ul)] ulong displacement) + { + if (!BitConverter.IsLittleEndian) + { + Assert.Ignore(); + } + + for (ulong gapOffset = 0; gapOffset < displacement; gapOffset++) + { + SetWorkingMemory(gapOffset, TestContext.CurrentContext.Random.NextByte()); + } + + SetWorkingMemory(0x0 + displacement, BitConverter.GetBytes((uint)b)); + + SetWorkingMemory(0x4 + displacement, BitConverter.GetBytes((uint)c)); + + SetWorkingMemory(0x8 + displacement, TestContext.CurrentContext.Random.NextByte()); + SetWorkingMemory(0x9 + displacement, TestContext.CurrentContext.Random.NextByte()); + SetWorkingMemory(0xA + displacement, TestContext.CurrentContext.Random.NextByte()); + SetWorkingMemory(0xB + displacement, TestContext.CurrentContext.Random.NextByte()); + + SetContext( + x0: DataBaseAddress + displacement, + v0: MakeVectorE0E1(a, TestContext.CurrentContext.Random.NextULong()), + v1: MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()), + v2: MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()), + overflow: TestContext.CurrentContext.Random.NextBool(), + carry: TestContext.CurrentContext.Random.NextBool(), + zero: TestContext.CurrentContext.Random.NextBool(), + negative: TestContext.CurrentContext.Random.NextBool()); + + Opcode(0xBD400001); // LDR S1, [X0,#0] + Opcode(0xBD400402); // LDR S2, [X0,#4] + Opcode(0x1E215801); // FMIN S1, S0, S1 + Opcode(0x1E222000); // FCMP S0, S2 + Opcode(0x1E214C40); // FCSEL S0, S2, S1, MI + Opcode(0xBD000800); // STR S0, [X0,#8] + Opcode(0xD65F03C0); // RET + ExecuteOpcodes(); + + CompareAgainstUnicorn(); + } + + [Explicit] + [Test] + public void Misc5([ValueSource(nameof(_1S_F_))] ulong a) + { + SetContext( + v0: MakeVectorE0E1(a, TestContext.CurrentContext.Random.NextULong()), + v1: MakeVectorE0E1(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()), + overflow: TestContext.CurrentContext.Random.NextBool(), + carry: TestContext.CurrentContext.Random.NextBool(), + zero: TestContext.CurrentContext.Random.NextBool(), + negative: TestContext.CurrentContext.Random.NextBool()); + + Opcode(0x1E202008); // FCMP S0, #0.0 + Opcode(0x1E2E1001); // FMOV S1, #1.0 + Opcode(0x1E215800); // FMIN S0, S0, S1 + Opcode(0x1E2703E1); // FMOV S1, WZR + Opcode(0x1E204C20); // FCSEL S0, S1, S0, MI + Opcode(0xD65F03C0); // RET + ExecuteOpcodes(); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestMisc32.cs b/src/Ryujinx.Tests/Cpu/CpuTestMisc32.cs new file mode 100644 index 00000000..e984a158 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestMisc32.cs @@ -0,0 +1,111 @@ +#define Misc32 + +using ARMeilleure.State; +using NUnit.Framework; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("Misc32")] + public sealed class CpuTestMisc32 : CpuTest32 + { +#if Misc32 + + #region "ValueSource (Types)" + private static IEnumerable _1S_F_() + { + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + } + } + #endregion + + private const int RndCnt = 2; + + private static readonly bool _noZeros = false; + private static readonly bool _noInfs = false; + private static readonly bool _noNaNs = false; + + [Test, Pairwise] + public void Vmsr_Vcmp_Vmrs([ValueSource(nameof(_1S_F_))] ulong a, + [ValueSource(nameof(_1S_F_))] ulong b, + [Values] bool mode1, + [Values] bool mode2, + [Values] bool mode3) + { + V128 v4 = MakeVectorE0(a); + V128 v5 = MakeVectorE0(b); + + uint r0 = mode1 + ? TestContext.CurrentContext.Random.NextUInt(0xf) << 28 + : TestContext.CurrentContext.Random.NextUInt(); + + bool v = mode3 && TestContext.CurrentContext.Random.NextBool(); + bool c = mode3 && TestContext.CurrentContext.Random.NextBool(); + bool z = mode3 && TestContext.CurrentContext.Random.NextBool(); + bool n = mode3 && TestContext.CurrentContext.Random.NextBool(); + + int fpscr = mode1 + ? (int)TestContext.CurrentContext.Random.NextUInt() + : (int)TestContext.CurrentContext.Random.NextUInt(0xf) << 28; + + SetContext(r0: r0, v4: v4, v5: v5, overflow: v, carry: c, zero: z, negative: n, fpscr: fpscr); + + if (mode1) + { + Opcode(0xEEE10A10); // VMSR FPSCR, R0 + } + Opcode(0xEEB48A4A); // VCMP.F32 S16, S20 + if (mode2) + { + Opcode(0xEEF10A10); // VMRS R0, FPSCR + Opcode(0xE200020F); // AND R0, #0xF0000000 // R0 &= "Fpsr.Nzcv". + } + if (mode3) + { + Opcode(0xEEF1FA10); // VMRS APSR_NZCV, FPSCR + } + Opcode(0xE12FFF1E); // BX LR + + ExecuteOpcodes(); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Nzcv); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestMov.cs b/src/Ryujinx.Tests/Cpu/CpuTestMov.cs new file mode 100644 index 00000000..c8ee3857 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestMov.cs @@ -0,0 +1,112 @@ +#define Mov + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("Mov")] + public sealed class CpuTestMov : CpuTest + { +#if Mov + private const int RndCnt = 2; + + [Test, Pairwise, Description("MOVK , #{, LSL #}")] + public void Movk_64bit([Values(0u, 31u)] uint rd, + [Random(RndCnt)] ulong xd, + [Values(0u, 65535u)] uint imm, + [Values(0u, 16u, 32u, 48u)] uint shift) + { + uint opcode = 0xF2800000; // MOVK X0, #0, LSL #0 + opcode |= ((rd & 31) << 0); + opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x0: xd, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("MOVK , #{, LSL #}")] + public void Movk_32bit([Values(0u, 31u)] uint rd, + [Random(RndCnt)] uint wd, + [Values(0u, 65535u)] uint imm, + [Values(0u, 16u)] uint shift) + { + uint opcode = 0x72800000; // MOVK W0, #0, LSL #0 + opcode |= ((rd & 31) << 0); + opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x0: wd, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("MOVN , #{, LSL #}")] + public void Movn_64bit([Values(0u, 31u)] uint rd, + [Values(0u, 65535u)] uint imm, + [Values(0u, 16u, 32u, 48u)] uint shift) + { + uint opcode = 0x92800000; // MOVN X0, #0, LSL #0 + opcode |= ((rd & 31) << 0); + opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("MOVN , #{, LSL #}")] + public void Movn_32bit([Values(0u, 31u)] uint rd, + [Values(0u, 65535u)] uint imm, + [Values(0u, 16u)] uint shift) + { + uint opcode = 0x12800000; // MOVN W0, #0, LSL #0 + opcode |= ((rd & 31) << 0); + opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("MOVZ , #{, LSL #}")] + public void Movz_64bit([Values(0u, 31u)] uint rd, + [Values(0u, 65535u)] uint imm, + [Values(0u, 16u, 32u, 48u)] uint shift) + { + uint opcode = 0xD2800000; // MOVZ X0, #0, LSL #0 + opcode |= ((rd & 31) << 0); + opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("MOVZ , #{, LSL #}")] + public void Movz_32bit([Values(0u, 31u)] uint rd, + [Values(0u, 65535u)] uint imm, + [Values(0u, 16u)] uint shift) + { + uint opcode = 0x52800000; // MOVZ W0, #0, LSL #0 + opcode |= ((rd & 31) << 0); + opcode |= (((shift / 16) & 3) << 21) | ((imm & 65535) << 5); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x31: w31); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestMul.cs b/src/Ryujinx.Tests/Cpu/CpuTestMul.cs new file mode 100644 index 00000000..164ed977 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestMul.cs @@ -0,0 +1,226 @@ +#define Mul + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("Mul")] + public sealed class CpuTestMul : CpuTest + { +#if Mul + [Test, Pairwise, Description("MADD , , , ")] + public void Madd_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xa) + { + uint opcode = 0x9B000000; // MADD X0, X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x3: xa, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("MADD , , , ")] + public void Madd_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wa) + { + uint opcode = 0x1B000000; // MADD W0, W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x3: wa, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("MSUB , , , ")] + public void Msub_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xa) + { + uint opcode = 0x9B008000; // MSUB X0, X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x3: xa, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("MSUB , , , ")] + public void Msub_32bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wa) + { + uint opcode = 0x1B008000; // MSUB W0, W0, W0, W0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, x1: wn, x2: wm, x3: wa, x31: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMADDL , , , ")] + public void Smaddl_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xa) + { + uint opcode = 0x9B200000; // SMADDL X0, W0, W0, X0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: wn, x2: wm, x3: xa, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UMADDL , , , ")] + public void Umaddl_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xa) + { + uint opcode = 0x9BA00000; // UMADDL X0, W0, W0, X0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: wn, x2: wm, x3: xa, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMSUBL , , , ")] + public void Smsubl_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xa) + { + uint opcode = 0x9B208000; // SMSUBL X0, W0, W0, X0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: wn, x2: wm, x3: xa, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UMSUBL , , , ")] + public void Umsubl_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(3u, 31u)] uint ra, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xa) + { + uint opcode = 0x9BA08000; // UMSUBL X0, W0, W0, X0 + opcode |= ((rm & 31) << 16) | ((ra & 31) << 10) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: wn, x2: wm, x3: xa, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMULH , , ")] + public void Smulh_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm) + { + uint opcode = 0x9B407C00; // SMULH X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UMULH , , ")] + public void Umulh_64bit([Values(0u, 31u)] uint rd, + [Values(1u, 31u)] uint rn, + [Values(2u, 31u)] uint rm, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xn, + [Values(0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul)] ulong xm) + { + uint opcode = 0x9BC07C00; // UMULH X0, X0, X0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcode, x1: xn, x2: xm, x31: x31); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestMul32.cs b/src/Ryujinx.Tests/Cpu/CpuTestMul32.cs new file mode 100644 index 00000000..7e4b4c06 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestMul32.cs @@ -0,0 +1,137 @@ +#define Mul32 + +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("Mul32")] + public sealed class CpuTestMul32 : CpuTest32 + { +#if Mul32 + + #region "ValueSource (Opcodes)" + private static uint[] _Smlabb_Smlabt_Smlatb_Smlatt_() + { + return new[] + { + 0xe1000080u, // SMLABB R0, R0, R0, R0 + 0xe10000C0u, // SMLABT R0, R0, R0, R0 + 0xe10000A0u, // SMLATB R0, R0, R0, R0 + 0xe10000E0u, // SMLATT R0, R0, R0, R0 + }; + } + + private static uint[] _Smlawb_Smlawt_() + { + return new[] + { + 0xe1200080u, // SMLAWB R0, R0, R0, R0 + 0xe12000C0u, // SMLAWT R0, R0, R0, R0 + }; + } + + private static uint[] _Smulbb_Smulbt_Smultb_Smultt_() + { + return new[] + { + 0xe1600080u, // SMULBB R0, R0, R0 + 0xe16000C0u, // SMULBT R0, R0, R0 + 0xe16000A0u, // SMULTB R0, R0, R0 + 0xe16000E0u, // SMULTT R0, R0, R0 + }; + } + + private static uint[] _Smulwb_Smulwt_() + { + return new[] + { + 0xe12000a0u, // SMULWB R0, R0, R0 + 0xe12000e0u, // SMULWT R0, R0, R0 + }; + } + #endregion + + [Test, Pairwise, Description("SMLA , , , ")] + public void Smla___32bit([ValueSource(nameof(_Smlabb_Smlabt_Smlatb_Smlatt_))] uint opcode, + [Values(0u, 0xdu)] uint rn, + [Values(1u, 0xdu)] uint rm, + [Values(2u, 0xdu)] uint ra, + [Values(3u, 0xdu)] uint rd, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wa) + { + opcode |= ((rn & 15) << 0) | ((rm & 15) << 8) | ((ra & 15) << 12) | ((rd & 15) << 16); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: wn, r1: wm, r2: wa, sp: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMLAW , , , ")] + public void Smlaw__32bit([ValueSource(nameof(_Smlawb_Smlawt_))] uint opcode, + [Values(0u, 0xdu)] uint rn, + [Values(1u, 0xdu)] uint rm, + [Values(2u, 0xdu)] uint ra, + [Values(3u, 0xdu)] uint rd, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wa) + { + opcode |= ((rn & 15) << 0) | ((rm & 15) << 8) | ((ra & 15) << 12) | ((rd & 15) << 16); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: wn, r1: wm, r2: wa, sp: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMUL , , ")] + public void Smul___32bit([ValueSource(nameof(_Smulbb_Smulbt_Smultb_Smultt_))] uint opcode, + [Values(0u, 0xdu)] uint rn, + [Values(1u, 0xdu)] uint rm, + [Values(2u, 0xdu)] uint rd, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm) + { + opcode |= ((rn & 15) << 0) | ((rm & 15) << 8) | ((rd & 15) << 16); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: wn, r1: wm, sp: w31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMULW , , ")] + public void Smulw__32bit([ValueSource(nameof(_Smulwb_Smulwt_))] uint opcode, + [Values(0u, 0xdu)] uint rn, + [Values(1u, 0xdu)] uint rm, + [Values(2u, 0xdu)] uint rd, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wn, + [Values(0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu)] uint wm) + { + opcode |= ((rn & 15) << 0) | ((rm & 15) << 8) | ((rd & 15) << 16); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + + SingleOpcode(opcode, r0: wn, r1: wm, sp: w31); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimd.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimd.cs new file mode 100644 index 00000000..eb763618 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimd.cs @@ -0,0 +1,3665 @@ +#define Simd + +using ARMeilleure.State; +using NUnit.Framework; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("Simd")] + public sealed class CpuTestSimd : CpuTest + { +#if Simd + + #region "Helper methods" + private static byte GenLeadingSignsMinus8(int cnt) // 0 <= cnt <= 7 + { + return (byte)(~(uint)GenLeadingZeros8(cnt + 1)); + } + + private static ushort GenLeadingSignsMinus16(int cnt) // 0 <= cnt <= 15 + { + return (ushort)(~(uint)GenLeadingZeros16(cnt + 1)); + } + + private static uint GenLeadingSignsMinus32(int cnt) // 0 <= cnt <= 31 + { + return ~GenLeadingZeros32(cnt + 1); + } + + private static byte GenLeadingSignsPlus8(int cnt) // 0 <= cnt <= 7 + { + return GenLeadingZeros8(cnt + 1); + } + + private static ushort GenLeadingSignsPlus16(int cnt) // 0 <= cnt <= 15 + { + return GenLeadingZeros16(cnt + 1); + } + + private static uint GenLeadingSignsPlus32(int cnt) // 0 <= cnt <= 31 + { + return GenLeadingZeros32(cnt + 1); + } + + private static byte GenLeadingZeros8(int cnt) // 0 <= cnt <= 8 + { + if (cnt == 8) + { + return 0; + } + + if (cnt == 7) + { + return 1; + } + + byte rnd = TestContext.CurrentContext.Random.NextByte(); + sbyte mask = sbyte.MinValue; + + return (byte)(((uint)rnd >> (cnt + 1)) | ((uint)((byte)mask) >> cnt)); + } + + private static ushort GenLeadingZeros16(int cnt) // 0 <= cnt <= 16 + { + if (cnt == 16) + { + return 0; + } + + if (cnt == 15) + { + return 1; + } + + ushort rnd = TestContext.CurrentContext.Random.NextUShort(); + short mask = short.MinValue; + + return (ushort)(((uint)rnd >> (cnt + 1)) | ((uint)((ushort)mask) >> cnt)); + } + + private static uint GenLeadingZeros32(int cnt) // 0 <= cnt <= 32 + { + if (cnt == 32) + { + return 0u; + } + + if (cnt == 31) + { + return 1u; + } + + uint rnd = TestContext.CurrentContext.Random.NextUInt(); + int mask = int.MinValue; + + return (rnd >> (cnt + 1)) | ((uint)mask >> cnt); + } + #endregion + + #region "ValueSource (Types)" + private static ulong[] _1B1H1S1D_() + { + return new[] { + 0x0000000000000000ul, 0x000000000000007Ful, + 0x0000000000000080ul, 0x00000000000000FFul, + 0x0000000000007FFFul, 0x0000000000008000ul, + 0x000000000000FFFFul, 0x000000007FFFFFFFul, + 0x0000000080000000ul, 0x00000000FFFFFFFFul, + 0x7FFFFFFFFFFFFFFFul, 0x8000000000000000ul, + 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _1D_() + { + return new[] { + 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _1H1S1D_() + { + return new[] { + 0x0000000000000000ul, 0x0000000000007FFFul, + 0x0000000000008000ul, 0x000000000000FFFFul, + 0x000000007FFFFFFFul, 0x0000000080000000ul, + 0x00000000FFFFFFFFul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _1S_() + { + return new[] { + 0x0000000000000000ul, 0x000000007FFFFFFFul, + 0x0000000080000000ul, 0x00000000FFFFFFFFul, + }; + } + + private static ulong[] _2S_() + { + return new[] { + 0x0000000000000000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _4H_() + { + return new[] { + 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _4H2S1D_() + { + return new[] { + 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _8B_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _8B4H_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _8B4H2S_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _8B4H2S1D_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static uint[] _W_() + { + return new[] { + 0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu, + }; + } + + private static ulong[] _X_() + { + return new[] { + 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static IEnumerable _1H_F_() + { + yield return 0x000000000000FBFFul; // -Max Normal + yield return 0x0000000000008400ul; // -Min Normal + yield return 0x00000000000083FFul; // -Max Subnormal + yield return 0x0000000000008001ul; // -Min Subnormal + yield return 0x0000000000007BFFul; // +Max Normal + yield return 0x0000000000000400ul; // +Min Normal + yield return 0x00000000000003FFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal + + if (!_noZeros) + { + yield return 0x0000000000008000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0x000000000000FC00ul; // -Infinity + yield return 0x0000000000007C00ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0x000000000000FE00ul; // -QNaN (all zeros payload) + yield return 0x000000000000FDFFul; // -SNaN (all ones payload) + yield return 0x0000000000007E00ul; // +QNaN (all zeros payload) (DefaultNaN) + yield return 0x0000000000007DFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUShort(); + ulong rnd1 = GenNormalH(); + ulong rnd2 = GenSubnormalH(); + + yield return (grbg << 48) | (grbg << 32) | (grbg << 16) | rnd1; + yield return (grbg << 48) | (grbg << 32) | (grbg << 16) | rnd2; + } + } + + private static IEnumerable _4H_F_() + { + yield return 0xFBFFFBFFFBFFFBFFul; // -Max Normal + yield return 0x8400840084008400ul; // -Min Normal + yield return 0x83FF83FF83FF83FFul; // -Max Subnormal + yield return 0x8001800180018001ul; // -Min Subnormal + yield return 0x7BFF7BFF7BFF7BFFul; // +Max Normal + yield return 0x0400040004000400ul; // +Min Normal + yield return 0x03FF03FF03FF03FFul; // +Max Subnormal + yield return 0x0001000100010001ul; // +Min Subnormal + + if (!_noZeros) + { + yield return 0x8000800080008000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFC00FC00FC00FC00ul; // -Infinity + yield return 0x7C007C007C007C00ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFE00FE00FE00FE00ul; // -QNaN (all zeros payload) + yield return 0xFDFFFDFFFDFFFDFFul; // -SNaN (all ones payload) + yield return 0x7E007E007E007E00ul; // +QNaN (all zeros payload) (DefaultNaN) + yield return 0x7DFF7DFF7DFF7DFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalH(); + ulong rnd2 = GenSubnormalH(); + + yield return (rnd1 << 48) | (rnd1 << 32) | (rnd1 << 16) | rnd1; + yield return (rnd2 << 48) | (rnd2 << 32) | (rnd2 << 16) | rnd2; + } + } + + private static IEnumerable _1S_F_() + { + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + } + } + + private static IEnumerable _1S_F_W_() + { + // int + yield return 0x00000000CF000001ul; // -2.1474839E9f (-2147483904) + yield return 0x00000000CF000000ul; // -2.14748365E9f (-2147483648) + yield return 0x00000000CEFFFFFFul; // -2.14748352E9f (-2147483520) + yield return 0x000000004F000001ul; // 2.1474839E9f (2147483904) + yield return 0x000000004F000000ul; // 2.14748365E9f (2147483648) + yield return 0x000000004EFFFFFFul; // 2.14748352E9f (2147483520) + + // uint + yield return 0x000000004F800001ul; // 4.2949678E9f (4294967808) + yield return 0x000000004F800000ul; // 4.2949673E9f (4294967296) + yield return 0x000000004F7FFFFFul; // 4.29496704E9f (4294967040) + + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + + ulong rnd1 = (uint)BitConverter.SingleToInt32Bits((int)TestContext.CurrentContext.Random.NextUInt()); + ulong rnd2 = (uint)BitConverter.SingleToInt32Bits(TestContext.CurrentContext.Random.NextUInt()); + + ulong rnd3 = GenNormalS(); + ulong rnd4 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + + yield return (grbg << 32) | rnd3; + yield return (grbg << 32) | rnd4; + } + } + + private static IEnumerable _2S_F_() + { + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + } + } + + private static IEnumerable _2S_F_W_() + { + // int + yield return 0xCF000001CF000001ul; // -2.1474839E9f (-2147483904) + yield return 0xCF000000CF000000ul; // -2.14748365E9f (-2147483648) + yield return 0xCEFFFFFFCEFFFFFFul; // -2.14748352E9f (-2147483520) + yield return 0x4F0000014F000001ul; // 2.1474839E9f (2147483904) + yield return 0x4F0000004F000000ul; // 2.14748365E9f (2147483648) + yield return 0x4EFFFFFF4EFFFFFFul; // 2.14748352E9f (2147483520) + + // uint + yield return 0x4F8000014F800001ul; // 4.2949678E9f (4294967808) + yield return 0x4F8000004F800000ul; // 4.2949673E9f (4294967296) + yield return 0x4F7FFFFF4F7FFFFFul; // 4.29496704E9f (4294967040) + + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = (uint)BitConverter.SingleToInt32Bits( + (int)TestContext.CurrentContext.Random.NextUInt()); + ulong rnd2 = (uint)BitConverter.SingleToInt32Bits( + TestContext.CurrentContext.Random.NextUInt()); + + ulong rnd3 = GenNormalS(); + ulong rnd4 = GenSubnormalS(); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + + yield return (rnd3 << 32) | rnd3; + yield return (rnd4 << 32) | rnd4; + } + } + + private static IEnumerable _1D_F_() + { + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalD(); + ulong rnd2 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + } + } + + private static IEnumerable _1D_F_X_() + { + // long + yield return 0xC3E0000000000001ul; // -9.2233720368547780E18d (-9223372036854778000) + yield return 0xC3E0000000000000ul; // -9.2233720368547760E18d (-9223372036854776000) + yield return 0xC3DFFFFFFFFFFFFFul; // -9.2233720368547750E18d (-9223372036854775000) + yield return 0x43E0000000000001ul; // 9.2233720368547780E18d (9223372036854778000) + yield return 0x43E0000000000000ul; // 9.2233720368547760E18d (9223372036854776000) + yield return 0x43DFFFFFFFFFFFFFul; // 9.2233720368547750E18d (9223372036854775000) + + // ulong + yield return 0x43F0000000000001ul; // 1.8446744073709556e19d (18446744073709556000) + yield return 0x43F0000000000000ul; // 1.8446744073709552E19d (18446744073709552000) + yield return 0x43EFFFFFFFFFFFFFul; // 1.8446744073709550e19d (18446744073709550000) + + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = (ulong)BitConverter.DoubleToInt64Bits( + (long)TestContext.CurrentContext.Random.NextULong()); + ulong rnd2 = (ulong)BitConverter.DoubleToInt64Bits( + TestContext.CurrentContext.Random.NextULong()); + + ulong rnd3 = GenNormalD(); + ulong rnd4 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + + yield return rnd3; + yield return rnd4; + } + } + + private static IEnumerable _GenLeadingSigns8B_() + { + for (int cnt = 0; cnt <= 7; cnt++) + { + ulong rnd1 = GenLeadingSignsMinus8(cnt); + ulong rnd2 = GenLeadingSignsPlus8(cnt); + + yield return (rnd1 << 56) | (rnd1 << 48) | (rnd1 << 40) | (rnd1 << 32) | + (rnd1 << 24) | (rnd1 << 16) | (rnd1 << 08) | rnd1; + yield return (rnd2 << 56) | (rnd2 << 48) | (rnd2 << 40) | (rnd2 << 32) | + (rnd2 << 24) | (rnd2 << 16) | (rnd2 << 08) | rnd2; + } + } + + private static IEnumerable _GenLeadingSigns4H_() + { + for (int cnt = 0; cnt <= 15; cnt++) + { + ulong rnd1 = GenLeadingSignsMinus16(cnt); + ulong rnd2 = GenLeadingSignsPlus16(cnt); + + yield return (rnd1 << 48) | (rnd1 << 32) | (rnd1 << 16) | rnd1; + yield return (rnd2 << 48) | (rnd2 << 32) | (rnd2 << 16) | rnd2; + } + } + + private static IEnumerable _GenLeadingSigns2S_() + { + for (int cnt = 0; cnt <= 31; cnt++) + { + ulong rnd1 = GenLeadingSignsMinus32(cnt); + ulong rnd2 = GenLeadingSignsPlus32(cnt); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + } + } + + private static IEnumerable _GenLeadingZeros8B_() + { + for (int cnt = 0; cnt <= 8; cnt++) + { + ulong rnd = GenLeadingZeros8(cnt); + + yield return (rnd << 56) | (rnd << 48) | (rnd << 40) | (rnd << 32) | + (rnd << 24) | (rnd << 16) | (rnd << 08) | rnd; + } + } + + private static IEnumerable _GenLeadingZeros4H_() + { + for (int cnt = 0; cnt <= 16; cnt++) + { + ulong rnd = GenLeadingZeros16(cnt); + + yield return (rnd << 48) | (rnd << 32) | (rnd << 16) | rnd; + } + } + + private static IEnumerable _GenLeadingZeros2S_() + { + for (int cnt = 0; cnt <= 32; cnt++) + { + ulong rnd = GenLeadingZeros32(cnt); + + yield return (rnd << 32) | rnd; + } + } + + private static IEnumerable _GenPopCnt8B_() + { + for (ulong cnt = 0ul; cnt <= 255ul; cnt++) + { + yield return (cnt << 56) | (cnt << 48) | (cnt << 40) | (cnt << 32) | + (cnt << 24) | (cnt << 16) | (cnt << 08) | cnt; + } + } + #endregion + + #region "ValueSource (Opcodes)" + private static uint[] _SU_Add_Max_Min_V_V_8BB_4HH_() + { + return new[] + { + 0x0E31B800u, // ADDV B0, V0.8B + 0x0E30A800u, // SMAXV B0, V0.8B + 0x0E31A800u, // SMINV B0, V0.8B + 0x2E30A800u, // UMAXV B0, V0.8B + 0x2E31A800u, // UMINV B0, V0.8B + }; + } + + private static uint[] _SU_Add_Max_Min_V_V_16BB_8HH_4SS_() + { + return new[] + { + 0x4E31B800u, // ADDV B0, V0.16B + 0x4E30A800u, // SMAXV B0, V0.16B + 0x4E31A800u, // SMINV B0, V0.16B + 0x6E30A800u, // UMAXV B0, V0.16B + 0x6E31A800u, // UMINV B0, V0.16B + }; + } + + private static uint[] _F_Abs_Neg_Recpx_Sqrt_S_S_() + { + return new[] + { + 0x1E20C020u, // FABS S0, S1 + 0x1E214020u, // FNEG S0, S1 + 0x5EA1F820u, // FRECPX S0, S1 + 0x1E21C020u, // FSQRT S0, S1 + }; + } + + private static uint[] _F_Abs_Neg_Recpx_Sqrt_S_D_() + { + return new[] + { + 0x1E60C020u, // FABS D0, D1 + 0x1E614020u, // FNEG D0, D1 + 0x5EE1F820u, // FRECPX D0, D1 + 0x1E61C020u, // FSQRT D0, D1 + }; + } + + private static uint[] _F_Abs_Neg_Sqrt_V_2S_4S_() + { + return new[] + { + 0x0EA0F800u, // FABS V0.2S, V0.2S + 0x2EA0F800u, // FNEG V0.2S, V0.2S + 0x2EA1F800u, // FSQRT V0.2S, V0.2S + }; + } + + private static uint[] _F_Abs_Neg_Sqrt_V_2D_() + { + return new[] + { + 0x4EE0F800u, // FABS V0.2D, V0.2D + 0x6EE0F800u, // FNEG V0.2D, V0.2D + 0x6EE1F800u, // FSQRT V0.2D, V0.2D + }; + } + + private static uint[] _F_Add_Max_Min_Nm_P_S_2SS_() + { + return new[] + { + 0x7E30D820u, // FADDP S0, V1.2S + 0x7E30C820u, // FMAXNMP S0, V1.2S + 0x7E30F820u, // FMAXP S0, V1.2S + 0x7EB0C820u, // FMINNMP S0, V1.2S + 0x7EB0F820u, // FMINP S0, V1.2S + }; + } + + private static uint[] _F_Add_Max_Min_Nm_P_S_2DD_() + { + return new[] + { + 0x7E70D820u, // FADDP D0, V1.2D + 0x7E70C820u, // FMAXNMP D0, V1.2D + 0x7E70F820u, // FMAXP D0, V1.2D + 0x7EF0C820u, // FMINNMP D0, V1.2D + 0x7EF0F820u, // FMINP D0, V1.2D + }; + } + + private static uint[] _F_Cm_EqGeGtLeLt_S_S_() + { + return new[] + { + 0x5EA0D820u, // FCMEQ S0, S1, #0.0 + 0x7EA0C820u, // FCMGE S0, S1, #0.0 + 0x5EA0C820u, // FCMGT S0, S1, #0.0 + 0x7EA0D820u, // FCMLE S0, S1, #0.0 + 0x5EA0E820u, // FCMLT S0, S1, #0.0 + }; + } + + private static uint[] _F_Cm_EqGeGtLeLt_S_D_() + { + return new[] + { + 0x5EE0D820u, // FCMEQ D0, D1, #0.0 + 0x7EE0C820u, // FCMGE D0, D1, #0.0 + 0x5EE0C820u, // FCMGT D0, D1, #0.0 + 0x7EE0D820u, // FCMLE D0, D1, #0.0 + 0x5EE0E820u, // FCMLT D0, D1, #0.0 + }; + } + + private static uint[] _F_Cm_EqGeGtLeLt_V_2S_4S_() + { + return new[] + { + 0x0EA0D800u, // FCMEQ V0.2S, V0.2S, #0.0 + 0x2EA0C800u, // FCMGE V0.2S, V0.2S, #0.0 + 0x0EA0C800u, // FCMGT V0.2S, V0.2S, #0.0 + 0x2EA0D800u, // FCMLE V0.2S, V0.2S, #0.0 + 0x0EA0E800u, // FCMLT V0.2S, V0.2S, #0.0 + }; + } + + private static uint[] _F_Cm_EqGeGtLeLt_V_2D_() + { + return new[] + { + 0x4EE0D800u, // FCMEQ V0.2D, V0.2D, #0.0 + 0x6EE0C800u, // FCMGE V0.2D, V0.2D, #0.0 + 0x4EE0C800u, // FCMGT V0.2D, V0.2D, #0.0 + 0x6EE0D800u, // FCMLE V0.2D, V0.2D, #0.0 + 0x4EE0E800u, // FCMLT V0.2D, V0.2D, #0.0 + }; + } + + private static uint[] _F_Cmp_Cmpe_S_S_() + { + return new[] + { + 0x1E202028u, // FCMP S1, #0.0 + 0x1E202038u, // FCMPE S1, #0.0 + }; + } + + private static uint[] _F_Cmp_Cmpe_S_D_() + { + return new[] + { + 0x1E602028u, // FCMP D1, #0.0 + 0x1E602038u, // FCMPE D1, #0.0 + }; + } + + private static uint[] _F_Cvt_S_SD_() + { + return new[] + { + 0x1E22C020u, // FCVT D0, S1 + }; + } + + private static uint[] _F_Cvt_S_DS_() + { + return new[] + { + 0x1E624020u, // FCVT S0, D1 + }; + } + + private static uint[] _F_Cvt_S_SH_() + { + return new[] + { + 0x1E23C020u, // FCVT H0, S1 + }; + } + + private static uint[] _F_Cvt_S_DH_() + { + return new[] + { + 0x1E63C020u, // FCVT H0, D1 + }; + } + + private static uint[] _F_Cvt_S_HS_() + { + return new[] + { + 0x1EE24020u, // FCVT S0, H1 + }; + } + + private static uint[] _F_Cvt_S_HD_() + { + return new[] + { + 0x1EE2C020u, // FCVT D0, H1 + }; + } + + private static uint[] _F_Cvt_ANZ_SU_S_S_() + { + return new[] + { + 0x5E21C820u, // FCVTAS S0, S1 + 0x7E21C820u, // FCVTAU S0, S1 + 0x5E21A820u, // FCVTNS S0, S1 + 0x7E21A820u, // FCVTNU S0, S1 + 0x5EA1B820u, // FCVTZS S0, S1 + 0x7EA1B820u, // FCVTZU S0, S1 + }; + } + + private static uint[] _F_Cvt_ANZ_SU_S_D_() + { + return new[] + { + 0x5E61C820u, // FCVTAS D0, D1 + 0x7E61C820u, // FCVTAU D0, D1 + 0x5E61A820u, // FCVTNS D0, D1 + 0x7E61A820u, // FCVTNU D0, D1 + 0x5EE1B820u, // FCVTZS D0, D1 + 0x7EE1B820u, // FCVTZU D0, D1 + }; + } + + private static uint[] _F_Cvt_ANZ_SU_V_2S_4S_() + { + return new[] + { + 0x0E21C800u, // FCVTAS V0.2S, V0.2S + 0x2E21C800u, // FCVTAU V0.2S, V0.2S + 0x0E21B800u, // FCVTMS V0.2S, V0.2S + 0x0E21A800u, // FCVTNS V0.2S, V0.2S + 0x2E21A800u, // FCVTNU V0.2S, V0.2S + 0x0EA1B800u, // FCVTZS V0.2S, V0.2S + 0x2EA1B800u, // FCVTZU V0.2S, V0.2S + }; + } + + private static uint[] _F_Cvt_ANZ_SU_V_2D_() + { + return new[] + { + 0x4E61C800u, // FCVTAS V0.2D, V0.2D + 0x6E61C800u, // FCVTAU V0.2D, V0.2D + 0x4E61B800u, // FCVTMS V0.2D, V0.2D + 0x4E61A800u, // FCVTNS V0.2D, V0.2D + 0x6E61A800u, // FCVTNU V0.2D, V0.2D + 0x4EE1B800u, // FCVTZS V0.2D, V0.2D + 0x6EE1B800u, // FCVTZU V0.2D, V0.2D + }; + } + + private static uint[] _F_Cvtl_V_4H4S_8H4S_() + { + return new[] + { + 0x0E217800u, // FCVTL V0.4S, V0.4H + }; + } + + private static uint[] _F_Cvtl_V_2S2D_4S2D_() + { + return new[] + { + 0x0E617800u, // FCVTL V0.2D, V0.2S + }; + } + + private static uint[] _F_Cvtn_V_4S4H_4S8H_() + { + return new[] + { + 0x0E216800u, // FCVTN V0.4H, V0.4S + }; + } + + private static uint[] _F_Cvtn_V_2D2S_2D4S_() + { + return new[] + { + 0x0E616800u, // FCVTN V0.2S, V0.2D + }; + } + + private static uint[] _F_Max_Min_Nm_V_V_4SS_() + { + return new[] + { + 0x6E30C800u, // FMAXNMV S0, V0.4S + 0x6E30F800u, // FMAXV S0, V0.4S + 0x6EB0C800u, // FMINNMV S0, V0.4S + 0x6EB0F800u, // FMINV S0, V0.4S + }; + } + + private static uint[] _F_Mov_Ftoi_SW_() + { + return new[] + { + 0x1E260000u, // FMOV W0, S0 + }; + } + + private static uint[] _F_Mov_Ftoi_DX_() + { + return new[] + { + 0x9E660000u, // FMOV X0, D0 + }; + } + + private static uint[] _F_Mov_Ftoi1_DX_() + { + return new[] + { + 0x9EAE0000u, // FMOV X0, V0.D[1] + }; + } + + private static uint[] _F_Mov_Itof_WS_() + { + return new[] + { + 0x1E270000u, // FMOV S0, W0 + }; + } + + private static uint[] _F_Mov_Itof_XD_() + { + return new[] + { + 0x9E670000u, // FMOV D0, X0 + }; + } + + private static uint[] _F_Mov_Itof1_XD_() + { + return new[] + { + 0x9EAF0000u, // FMOV V0.D[1], X0 + }; + } + + private static uint[] _F_Mov_S_S_() + { + return new[] + { + 0x1E204020u, // FMOV S0, S1 + }; + } + + private static uint[] _F_Mov_S_D_() + { + return new[] + { + 0x1E604020u, // FMOV D0, D1 + }; + } + + private static uint[] _F_Recpe_Rsqrte_S_S_() + { + return new[] + { + 0x5EA1D820u, // FRECPE S0, S1 + 0x7EA1D820u, // FRSQRTE S0, S1 + }; + } + + private static uint[] _F_Recpe_Rsqrte_S_D_() + { + return new[] + { + 0x5EE1D820u, // FRECPE D0, D1 + 0x7EE1D820u, // FRSQRTE D0, D1 + }; + } + + private static uint[] _F_Recpe_Rsqrte_V_2S_4S_() + { + return new[] + { + 0x0EA1D800u, // FRECPE V0.2S, V0.2S + 0x2EA1D800u, // FRSQRTE V0.2S, V0.2S + }; + } + + private static uint[] _F_Recpe_Rsqrte_V_2D_() + { + return new[] + { + 0x4EE1D800u, // FRECPE V0.2D, V0.2D + 0x6EE1D800u, // FRSQRTE V0.2D, V0.2D + }; + } + + private static uint[] _F_Rint_AMNPZ_S_S_() + { + return new[] + { + 0x1E264020u, // FRINTA S0, S1 + 0x1E254020u, // FRINTM S0, S1 + 0x1E244020u, // FRINTN S0, S1 + 0x1E24C020u, // FRINTP S0, S1 + 0x1E25C020u, // FRINTZ S0, S1 + }; + } + + private static uint[] _F_Rint_AMNPZ_S_D_() + { + return new[] + { + 0x1E664020u, // FRINTA D0, D1 + 0x1E654020u, // FRINTM D0, D1 + 0x1E644020u, // FRINTN D0, D1 + 0x1E64C020u, // FRINTP D0, D1 + 0x1E65C020u, // FRINTZ D0, D1 + }; + } + + private static uint[] _F_Rint_AMNPZ_V_2S_4S_() + { + return new[] + { + 0x2E218800u, // FRINTA V0.2S, V0.2S + 0x0E219800u, // FRINTM V0.2S, V0.2S + 0x0E218800u, // FRINTN V0.2S, V0.2S + 0x0EA18800u, // FRINTP V0.2S, V0.2S + 0x0EA19800u, // FRINTZ V0.2S, V0.2S + }; + } + + private static uint[] _F_Rint_AMNPZ_V_2D_() + { + return new[] + { + 0x6E618800u, // FRINTA V0.2D, V0.2D + 0x4E619800u, // FRINTM V0.2D, V0.2D + 0x4E618800u, // FRINTN V0.2D, V0.2D + 0x4EE18800u, // FRINTP V0.2D, V0.2D + 0x4EE19800u, // FRINTZ V0.2D, V0.2D + }; + } + + private static uint[] _F_Rint_IX_S_S_() + { + return new[] + { + 0x1E27C020u, // FRINTI S0, S1 + 0x1E274020u, // FRINTX S0, S1 + }; + } + + private static uint[] _F_Rint_IX_S_D_() + { + return new[] + { + 0x1E67C020u, // FRINTI D0, D1 + 0x1E674020u, // FRINTX D0, D1 + }; + } + + private static uint[] _F_Rint_IX_V_2S_4S_() + { + return new[] + { + 0x2EA19800u, // FRINTI V0.2S, V0.2S + 0x2E219800u, // FRINTX V0.2S, V0.2S + }; + } + + private static uint[] _F_Rint_IX_V_2D_() + { + return new[] + { + 0x6EE19800u, // FRINTI V0.2D, V0.2D + 0x6E619800u, // FRINTX V0.2D, V0.2D + }; + } + + private static uint[] _SU_Addl_V_V_8BH_4HS_() + { + return new[] + { + 0x0E303800u, // SADDLV H0, V0.8B + 0x2E303800u, // UADDLV H0, V0.8B + }; + } + + private static uint[] _SU_Addl_V_V_16BH_8HS_4SD_() + { + return new[] + { + 0x4E303800u, // SADDLV H0, V0.16B + 0x6E303800u, // UADDLV H0, V0.16B + }; + } + + private static uint[] _SU_Cvt_F_S_S_() + { + return new[] + { + 0x5E21D820u, // SCVTF S0, S1 + 0x7E21D820u, // UCVTF S0, S1 + }; + } + + private static uint[] _SU_Cvt_F_S_D_() + { + return new[] + { + 0x5E61D820u, // SCVTF D0, D1 + 0x7E61D820u, // UCVTF D0, D1 + }; + } + + private static uint[] _SU_Cvt_F_V_2S_4S_() + { + return new[] + { + 0x0E21D800u, // SCVTF V0.2S, V0.2S + 0x2E21D800u, // UCVTF V0.2S, V0.2S + }; + } + + private static uint[] _SU_Cvt_F_V_2D_() + { + return new[] + { + 0x4E61D800u, // SCVTF V0.2D, V0.2D + 0x6E61D800u, // UCVTF V0.2D, V0.2D + }; + } + + private static uint[] _Sha1h_Sha1su1_V_() + { + return new[] + { + 0x5E280800u, // SHA1H S0, S0 + 0x5E281800u, // SHA1SU1 V0.4S, V0.4S + }; + } + + private static uint[] _Sha256su0_V_() + { + return new[] + { + 0x5E282800u, // SHA256SU0 V0.4S, V0.4S + }; + } + #endregion + + private const int RndCnt = 2; + + private static readonly bool _noZeros = false; + private static readonly bool _noInfs = false; + private static readonly bool _noNaNs = false; + + [Test, Pairwise, Description("ABS , ")] + public void Abs_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a) + { + uint opcode = 0x5EE0B800; // ABS D0, D0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ABS ., .")] + public void Abs_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E20B800; // ABS V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ABS ., .")] + public void Abs_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E20B800; // ABS V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDP , .")] + public void Addp_S_2DD([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a) + { + uint opcode = 0x5EF1B800; // ADDP D0, V0.2D + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Add_Max_Min_V_V_8BB_4HH([ValueSource(nameof(_SU_Add_Max_Min_V_V_8BB_4HH_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H_))] ulong z, + [ValueSource(nameof(_8B4H_))] ulong a, + [Values(0b00u, 0b01u)] uint size) // <8BB, 4HH> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Add_Max_Min_V_V_16BB_8HH_4SS([ValueSource(nameof(_SU_Add_Max_Min_V_V_16BB_8HH_4SS_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16BB, 8HH, 4SS> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CLS ., .")] + public void Cls_V_8B_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))][Random(RndCnt)] ulong z, + [ValueSource(nameof(_GenLeadingSigns8B_))] ulong a, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + uint opcode = 0x0E204800; // CLS V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CLS ., .")] + public void Cls_V_4H_8H([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H_))][Random(RndCnt)] ulong z, + [ValueSource(nameof(_GenLeadingSigns4H_))] ulong a, + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + uint opcode = 0x0E604800; // CLS V0.4H, V0.4H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CLS ., .")] + public void Cls_V_2S_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_))][Random(RndCnt)] ulong z, + [ValueSource(nameof(_GenLeadingSigns2S_))] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint opcode = 0x0EA04800; // CLS V0.2S, V0.2S + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CLZ ., .")] + public void Clz_V_8B_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))][Random(RndCnt)] ulong z, + [ValueSource(nameof(_GenLeadingZeros8B_))] ulong a, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + uint opcode = 0x2E204800; // CLZ V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CLZ ., .")] + public void Clz_V_4H_8H([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H_))][Random(RndCnt)] ulong z, + [ValueSource(nameof(_GenLeadingZeros4H_))] ulong a, + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + uint opcode = 0x2E604800; // CLZ V0.4H, V0.4H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CLZ ., .")] + public void Clz_V_2S_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_))][Random(RndCnt)] ulong z, + [ValueSource(nameof(_GenLeadingZeros2S_))] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint opcode = 0x2EA04800; // CLZ V0.2S, V0.2S + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMEQ , , #0")] + public void Cmeq_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a) + { + uint opcode = 0x5EE09800; // CMEQ D0, D0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMEQ ., ., #0")] + public void Cmeq_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E209800; // CMEQ V0.8B, V0.8B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMEQ ., ., #0")] + public void Cmeq_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E209800; // CMEQ V0.16B, V0.16B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGE , , #0")] + public void Cmge_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a) + { + uint opcode = 0x7EE08800; // CMGE D0, D0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGE ., ., #0")] + public void Cmge_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E208800; // CMGE V0.8B, V0.8B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGE ., ., #0")] + public void Cmge_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E208800; // CMGE V0.16B, V0.16B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGT , , #0")] + public void Cmgt_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a) + { + uint opcode = 0x5EE08800; // CMGT D0, D0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGT ., ., #0")] + public void Cmgt_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E208800; // CMGT V0.8B, V0.8B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGT ., ., #0")] + public void Cmgt_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E208800; // CMGT V0.16B, V0.16B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMLE , , #0")] + public void Cmle_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a) + { + uint opcode = 0x7EE09800; // CMLE D0, D0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMLE ., ., #0")] + public void Cmle_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E209800; // CMLE V0.8B, V0.8B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMLE ., ., #0")] + public void Cmle_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E209800; // CMLE V0.16B, V0.16B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMLT , , #0")] + public void Cmlt_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a) + { + uint opcode = 0x5EE0A800; // CMLT D0, D0, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMLT ., ., #0")] + public void Cmlt_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E20A800; // CMLT V0.8B, V0.8B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMLT ., ., #0")] + public void Cmlt_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E20A800; // CMLT V0.16B, V0.16B, #0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CNT ., .")] + public void Cnt_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))][Random(RndCnt)] ulong z, + [ValueSource(nameof(_GenPopCnt8B_))] ulong a) + { + uint opcode = 0x0E205800; // CNT V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CNT ., .")] + public void Cnt_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))][Random(RndCnt)] ulong z, + [ValueSource(nameof(_GenPopCnt8B_))] ulong a) + { + uint opcode = 0x4E205800; // CNT V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Abs_Neg_Recpx_Sqrt_S_S([ValueSource(nameof(_F_Abs_Neg_Recpx_Sqrt_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Abs_Neg_Recpx_Sqrt_S_D([ValueSource(nameof(_F_Abs_Neg_Recpx_Sqrt_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Abs_Neg_Sqrt_V_2S_4S([ValueSource(nameof(_F_Abs_Neg_Sqrt_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Abs_Neg_Sqrt_V_2D([ValueSource(nameof(_F_Abs_Neg_Sqrt_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_F_))] ulong z, + [ValueSource(nameof(_1D_F_))] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Add_Max_Min_Nm_P_S_2SS([ValueSource(nameof(_F_Add_Max_Min_Nm_P_S_2SS_))] uint opcodes, + [ValueSource(nameof(_2S_F_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Add_Max_Min_Nm_P_S_2DD([ValueSource(nameof(_F_Add_Max_Min_Nm_P_S_2DD_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a0, + [ValueSource(nameof(_1D_F_))] ulong a1) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a0, a1); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Cm_EqGeGtLeLt_S_S([ValueSource(nameof(_F_Cm_EqGeGtLeLt_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Cm_EqGeGtLeLt_S_D([ValueSource(nameof(_F_Cm_EqGeGtLeLt_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Cm_EqGeGtLeLt_V_2S_4S([ValueSource(nameof(_F_Cm_EqGeGtLeLt_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Cm_EqGeGtLeLt_V_2D([ValueSource(nameof(_F_Cm_EqGeGtLeLt_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_F_))] ulong z, + [ValueSource(nameof(_1D_F_))] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Cmp_Cmpe_S_S([ValueSource(nameof(_F_Cmp_Cmpe_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a) + { + V128 v1 = MakeVectorE0(a); + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + SingleOpcode(opcodes, v1: v1, overflow: v, carry: c, zero: z, negative: n); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc); + } + + [Test, Pairwise] + [Explicit] + public void F_Cmp_Cmpe_S_D([ValueSource(nameof(_F_Cmp_Cmpe_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a) + { + V128 v1 = MakeVectorE0(a); + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + SingleOpcode(opcodes, v1: v1, overflow: v, carry: c, zero: z, negative: n); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_S_SD([ValueSource(nameof(_F_Cvt_S_SD_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_S_DS([ValueSource(nameof(_F_Cvt_S_DS_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + // Unicorn seems to default all rounding modes to RMode.Rn. + [Test, Pairwise] + [Explicit] + public void F_Cvt_S_SH([ValueSource(nameof(_F_Cvt_S_SH_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a, + [Values(RMode.Rn)] RMode rMode) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_S_DH([ValueSource(nameof(_F_Cvt_S_DH_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a, + [Values(RMode.Rn)] RMode rMode) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_S_HS([ValueSource(nameof(_F_Cvt_S_HS_))] uint opcodes, + [ValueSource(nameof(_1H_F_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_S_HD([ValueSource(nameof(_F_Cvt_S_HD_))] uint opcodes, + [ValueSource(nameof(_1H_F_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_ANZ_SU_S_S([ValueSource(nameof(_F_Cvt_ANZ_SU_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_W_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_ANZ_SU_S_D([ValueSource(nameof(_F_Cvt_ANZ_SU_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_X_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_ANZ_SU_V_2S_4S([ValueSource(nameof(_F_Cvt_ANZ_SU_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_F_W_))] ulong z, + [ValueSource(nameof(_2S_F_W_))] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_ANZ_SU_V_2D([ValueSource(nameof(_F_Cvt_ANZ_SU_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_F_X_))] ulong z, + [ValueSource(nameof(_1D_F_X_))] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvtl_V_4H4S_8H4S([ValueSource(nameof(_F_Cvtl_V_4H4S_8H4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H_F_))] ulong z, + [ValueSource(nameof(_4H_F_))] ulong a, + [Values(0b0u, 0b1u)] uint q, // <4H4S, 8H4S> + [Values(RMode.Rn)] RMode rMode) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + fpcr |= rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + fpcr |= rnd & (1 << (int)Fpcr.Ahp); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Ofc | Fpsr.Ufc | Fpsr.Ixc); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvtl_V_2S2D_4S2D([ValueSource(nameof(_F_Cvtl_V_2S2D_4S2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S2D, 4S2D> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + // Unicorn seems to default all rounding modes to RMode.Rn. + [Test, Pairwise] + [Explicit] + public void F_Cvtn_V_4S4H_4S8H([ValueSource(nameof(_F_Cvtn_V_4S4H_4S8H_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a, + [Values(0b0u, 0b1u)] uint q, // <4S4H, 4S8H> + [Values(RMode.Rn)] RMode rMode) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, a); + V128 v1 = MakeVectorE0E1(a, z); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + fpcr |= rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + fpcr |= rnd & (1 << (int)Fpcr.Ahp); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Ofc | Fpsr.Ufc | Fpsr.Ixc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvtn_V_2D2S_2D4S([ValueSource(nameof(_F_Cvtn_V_2D2S_2D4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_F_))] ulong z, + [ValueSource(nameof(_1D_F_))] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2D2S, 2D4S> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, a); + V128 v1 = MakeVectorE0E1(a, z); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Max_Min_Nm_V_V_4SS([ValueSource(nameof(_F_Max_Min_Nm_V_V_4SS_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Mov_Ftoi_SW([ValueSource(nameof(_F_Mov_Ftoi_SW_))] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_1S_F_))] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Mov_Ftoi_DX([ValueSource(nameof(_F_Mov_Ftoi_DX_))] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_1D_F_))] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Mov_Ftoi1_DX([ValueSource(nameof(_F_Mov_Ftoi1_DX_))] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_1D_F_))] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE1(a); + + SingleOpcode(opcodes, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Mov_Itof_WS([ValueSource(nameof(_F_Mov_Itof_WS_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_W_))] uint wn) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Mov_Itof_XD([ValueSource(nameof(_F_Mov_Itof_XD_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_X_))] ulong xn) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Mov_Itof1_XD([ValueSource(nameof(_F_Mov_Itof1_XD_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_X_))] ulong xn) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0(z); + + SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Mov_S_S([ValueSource(nameof(_F_Mov_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Mov_S_D([ValueSource(nameof(_F_Mov_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Recpe_Rsqrte_S_S([ValueSource(nameof(_F_Recpe_Rsqrte_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a, + [Values(RMode.Rn)] RMode rMode) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + fpcr |= rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Ofc | Fpsr.Ufc | Fpsr.Ixc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Recpe_Rsqrte_S_D([ValueSource(nameof(_F_Recpe_Rsqrte_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a, + [Values(RMode.Rn)] RMode rMode) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + fpcr |= rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Ofc | Fpsr.Ufc | Fpsr.Ixc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Recpe_Rsqrte_V_2S_4S([ValueSource(nameof(_F_Recpe_Rsqrte_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a, + [Values(0b0u, 0b1u)] uint q, // <2S, 4S> + [Values(RMode.Rn)] RMode rMode) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + fpcr |= rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Ofc | Fpsr.Ufc | Fpsr.Ixc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Recpe_Rsqrte_V_2D([ValueSource(nameof(_F_Recpe_Rsqrte_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_F_))] ulong z, + [ValueSource(nameof(_1D_F_))] ulong a, + [Values(RMode.Rn)] RMode rMode) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + fpcr |= rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Ofc | Fpsr.Ufc | Fpsr.Ixc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Rint_AMNPZ_S_S([ValueSource(nameof(_F_Rint_AMNPZ_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Rint_AMNPZ_S_D([ValueSource(nameof(_F_Rint_AMNPZ_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Rint_AMNPZ_V_2S_4S([ValueSource(nameof(_F_Rint_AMNPZ_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Rint_AMNPZ_V_2D([ValueSource(nameof(_F_Rint_AMNPZ_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_F_))] ulong z, + [ValueSource(nameof(_1D_F_))] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Rint_IX_S_S([ValueSource(nameof(_F_Rint_IX_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a, + [Values] RMode rMode) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Rint_IX_S_D([ValueSource(nameof(_F_Rint_IX_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a, + [Values] RMode rMode) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Rint_IX_V_2S_4S([ValueSource(nameof(_F_Rint_IX_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a, + [Values(0b0u, 0b1u)] uint q, // <2S, 4S> + [Values] RMode rMode) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Rint_IX_V_2D([ValueSource(nameof(_F_Rint_IX_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_F_))] ulong z, + [ValueSource(nameof(_1D_F_))] ulong a, + [Values] RMode rMode) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + int fpcr = (int)rMode << (int)Fpcr.RMode; + + SingleOpcode(opcodes, v0: v0, v1: v1, fpcr: fpcr); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("NEG , ")] + public void Neg_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a) + { + uint opcode = 0x7EE0B800; // NEG D0, D0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("NEG ., .")] + public void Neg_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E20B800; // NEG V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("NEG ., .")] + public void Neg_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E20B800; // NEG V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("NOT ., .")] + public void Not_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a) + { + uint opcode = 0x2E205800; // NOT V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("NOT ., .")] + public void Not_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a) + { + uint opcode = 0x6E205800; // NOT V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("RBIT ., .")] + public void Rbit_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a) + { + uint opcode = 0x2E605800; // RBIT V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("RBIT ., .")] + public void Rbit_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a) + { + uint opcode = 0x6E605800; // RBIT V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("REV16 ., .")] + public void Rev16_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a) + { + uint opcode = 0x0E201800; // REV16 V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("REV16 ., .")] + public void Rev16_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a) + { + uint opcode = 0x4E201800; // REV16 V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("REV32 ., .")] + public void Rev32_V_8B_4H([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H_))] ulong z, + [ValueSource(nameof(_8B4H_))] ulong a, + [Values(0b00u, 0b01u)] uint size) // <8B, 4H> + { + uint opcode = 0x2E200800; // REV32 V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("REV32 ., .")] + public void Rev32_V_16B_8H([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H_))] ulong z, + [ValueSource(nameof(_8B4H_))] ulong a, + [Values(0b00u, 0b01u)] uint size) // <16B, 8H> + { + uint opcode = 0x6E200800; // REV32 V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("REV64 ., .")] + public void Rev64_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E200800; // REV64 V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("REV64 ., .")] + public void Rev64_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x4E200800; // REV64 V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SADALP ., .")] + public void Sadalp_V_8B4H_4H2S_2S1D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B4H, 4H2S, 2S1D> + { + uint opcode = 0x0E206800; // SADALP V0.4H, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SADALP ., .")] + public void Sadalp_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x4E206800; // SADALP V0.8H, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SADDLP ., .")] + public void Saddlp_V_8B4H_4H2S_2S1D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B4H, 4H2S, 2S1D> + { + uint opcode = 0x0E202800; // SADDLP V0.4H, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SADDLP ., .")] + public void Saddlp_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x4E202800; // SADDLP V0.8H, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Addl_V_V_8BH_4HS([ValueSource(nameof(_SU_Addl_V_V_8BH_4HS_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H_))] ulong z, + [ValueSource(nameof(_8B4H_))] ulong a, + [Values(0b00u, 0b01u)] uint size) // <8BH, 4HS> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Addl_V_V_16BH_8HS_4SD([ValueSource(nameof(_SU_Addl_V_V_16BH_8HS_4SD_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16BH, 8HS, 4SD> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_S_S([ValueSource(nameof(_SU_Cvt_F_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_S_D([ValueSource(nameof(_SU_Cvt_F_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_))] ulong a) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_V_2S_4S([ValueSource(nameof(_SU_Cvt_F_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_2S_))] ulong a, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_V_2D([ValueSource(nameof(_SU_Cvt_F_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Sha1h_Sha1su1_V([ValueSource(nameof(_Sha1h_Sha1su1_V_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Random(RndCnt / 2)] ulong z0, [Random(RndCnt / 2)] ulong z1, + [Random(RndCnt / 2)] ulong a0, [Random(RndCnt / 2)] ulong a1) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Sha256su0_V([ValueSource(nameof(_Sha256su0_V_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Random(RndCnt / 2)] ulong z0, [Random(RndCnt / 2)] ulong z1, + [Random(RndCnt / 2)] ulong a0, [Random(RndCnt / 2)] ulong a1) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SHLL{2} ., ., #")] + public void Shll_V([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size, // + [Values(0b0u, 0b1u)] uint q) + { + uint opcode = 0x2E213800; // SHLL V0.8H, V0.8B, #8 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SQABS , ")] + public void Sqabs_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1B1H1S1D_))] ulong z, + [ValueSource(nameof(_1B1H1S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x5E207800; // SQABS B0, B0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQABS ., .")] + public void Sqabs_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E207800; // SQABS V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQABS ., .")] + public void Sqabs_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E207800; // SQABS V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQNEG , ")] + public void Sqneg_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1B1H1S1D_))] ulong z, + [ValueSource(nameof(_1B1H1S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x7E207800; // SQNEG B0, B0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQNEG ., .")] + public void Sqneg_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E207800; // SQNEG V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQNEG ., .")] + public void Sqneg_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E207800; // SQNEG V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQXTN , ")] + public void Sqxtn_S_HB_SH_DS([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1H1S1D_))] ulong z, + [ValueSource(nameof(_1H1S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // + { + uint opcode = 0x5E214800; // SQXTN B0, H0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQXTN{2} ., .")] + public void Sqxtn_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> + { + uint opcode = 0x0E214800; // SQXTN V0.8B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQXTN{2} ., .")] + public void Sqxtn_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> + { + uint opcode = 0x4E214800; // SQXTN2 V0.16B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQXTUN , ")] + public void Sqxtun_S_HB_SH_DS([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1H1S1D_))] ulong z, + [ValueSource(nameof(_1H1S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // + { + uint opcode = 0x7E212800; // SQXTUN B0, H0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQXTUN{2} ., .")] + public void Sqxtun_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> + { + uint opcode = 0x2E212800; // SQXTUN V0.8B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQXTUN{2} ., .")] + public void Sqxtun_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> + { + uint opcode = 0x6E212800; // SQXTUN2 V0.16B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SUQADD , ")] + public void Suqadd_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1B1H1S1D_))] ulong z, + [ValueSource(nameof(_1B1H1S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x5E203800; // SUQADD B0, B0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SUQADD ., .")] + public void Suqadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E203800; // SUQADD V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SUQADD ., .")] + public void Suqadd_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E203800; // SUQADD V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("UADALP ., .")] + public void Uadalp_V_8B4H_4H2S_2S1D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B4H, 4H2S, 2S1D> + { + uint opcode = 0x2E206800; // UADALP V0.4H, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADALP ., .")] + public void Uadalp_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x6E206800; // UADALP V0.8H, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADDLP ., .")] + public void Uaddlp_V_8B4H_4H2S_2S1D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B4H, 4H2S, 2S1D> + { + uint opcode = 0x2E202800; // UADDLP V0.4H, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADDLP ., .")] + public void Uaddlp_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x6E202800; // UADDLP V0.8H, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UQXTN , ")] + public void Uqxtn_S_HB_SH_DS([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1H1S1D_))] ulong z, + [ValueSource(nameof(_1H1S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // + { + uint opcode = 0x7E214800; // UQXTN B0, H0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("UQXTN{2} ., .")] + public void Uqxtn_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> + { + uint opcode = 0x2E214800; // UQXTN V0.8B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("UQXTN{2} ., .")] + public void Uqxtn_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> + { + uint opcode = 0x6E214800; // UQXTN2 V0.16B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("USQADD , ")] + public void Usqadd_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1B1H1S1D_))] ulong z, + [ValueSource(nameof(_1B1H1S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x7E203800; // USQADD B0, B0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("USQADD ., .")] + public void Usqadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E203800; // USQADD V0.8B, V0.8B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("USQADD ., .")] + public void Usqadd_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E203800; // USQADD V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("XTN{2} ., .")] + public void Xtn_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> + { + uint opcode = 0x0E212800; // XTN V0.8B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("XTN{2} ., .")] + public void Xtn_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> + { + uint opcode = 0x4E212800; // XTN2 V0.16B, V0.8H + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimd32.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimd32.cs new file mode 100644 index 00000000..08202c9e --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimd32.cs @@ -0,0 +1,381 @@ +#define Simd32 + +using ARMeilleure.State; +using NUnit.Framework; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("Simd32")] + public sealed class CpuTestSimd32 : CpuTest32 + { +#if Simd32 + + #region "ValueSource (Opcodes)" + private static uint[] _Vabs_Vneg_Vpaddl_I_() + { + return new[] + { + 0xf3b10300u, // VABS.S8 D0, D0 + 0xf3b10380u, // VNEG.S8 D0, D0 + 0xf3b00200u, // VPADDL.S8 D0, D0 + }; + } + + private static uint[] _Vabs_Vneg_F_() + { + return new[] + { + 0xf3b90700u, // VABS.F32 D0, D0 + 0xf3b90780u, // VNEG.F32 D0, D0 + }; + } + #endregion + + #region "ValueSource (Types)" + private static ulong[] _8B4H2S_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static IEnumerable _1S_F_() + { + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + } + } + + private static IEnumerable _2S_F_() + { + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + } + } + + private static IEnumerable _1D_F_() + { + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalD(); + ulong rnd2 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + } + } + + private static IEnumerable _GenPopCnt8B_() + { + for (ulong cnt = 0ul; cnt <= 255ul; cnt++) + { + yield return (cnt << 56) | (cnt << 48) | (cnt << 40) | (cnt << 32) | + (cnt << 24) | (cnt << 16) | (cnt << 08) | cnt; + } + } + #endregion + + private const int RndCnt = 2; + + private static readonly bool _noZeros = false; + private static readonly bool _noInfs = false; + private static readonly bool _noNaNs = false; + + [Test, Pairwise, Description("SHA256SU0.32 , ")] + public void Sha256su0_V([Values(0xF3BA03C0u)] uint opcode, + [Values(0u)] uint rd, + [Values(2u)] uint rm, + [Values(0x9BCBBF7443FB4F91ul)] ulong z0, + [Values(0x482C58A58CBCBD59ul)] ulong z1, + [Values(0xA0099B803625F82Aul)] ulong a0, + [Values(0x1AA3B0B4E1AB4C8Cul)] ulong a1, + [Values(0x29A44D72598F15F3ul)] ulong resultL, + [Values(0x74CED221E2793F07ul)] ulong resultH) + { + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + + ExecutionContext context = SingleOpcode(opcode, v0: v0, v1: v1, runUnicorn: false); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + + // Unicorn does not yet support hash instructions in A32. + // CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Vabs_Vneg_Vpaddl_V_I([ValueSource(nameof(_Vabs_Vneg_Vpaddl_I_))] uint opcode, + [Range(0u, 3u)] uint rd, + [Range(0u, 3u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0u, 1u, 2u)] uint size, // + [Values] bool q) + { + if (q) + { + opcode |= 1 << 6; + + rd >>= 1; + rd <<= 1; + rm >>= 1; + rm <<= 1; + } + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + opcode |= (size & 0x3) << 18; + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Vabs_Vneg_V_F32([ValueSource(nameof(_Vabs_Vneg_F_))] uint opcode, + [Range(0u, 3u)] uint rd, + [Range(0u, 3u)] uint rm, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong b, + [Values] bool q) + { + if (q) + { + opcode |= 1 << 6; + + rd >>= 1; + rd <<= 1; + rm >>= 1; + rm <<= 1; + } + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VCNT.8 D0, D0 | VCNT.8 Q0, Q0")] + public void Vcnt([Values(0u, 1u)] uint rd, + [Values(0u, 1u)] uint rm, + [ValueSource(nameof(_GenPopCnt8B_))] ulong d0, + [Values] bool q) + { + ulong d1 = ~d0; // It's expensive to have a second generator. + + uint opcode = 0xf3b00500u; // VCNT.8 D0, D0 + + if (q) + { + opcode |= 1u << 6; + + rd &= ~1u; + rm &= ~1u; + } + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + V128 v0 = MakeVectorE0E1(d0, d1); + + SingleOpcode(opcode, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Vmovn_V([Range(0u, 3u)] uint rd, + [Range(0u, 3u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0u, 1u, 2u, 3u)] uint op, + [Values(0u, 1u, 2u)] uint size) // + { + rm >>= 1; + rm <<= 1; + + uint opcode = 0xf3b20200u; // VMOVN.S16 D0, Q0 + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + opcode |= (op & 0x3) << 6; + opcode |= (size & 0x3) << 18; + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VSHLL. {}, , #")] + public void Vshll([Values(0u, 2u)] uint rd, + [Values(1u, 0u)] uint rm, + [Values(0u, 1u, 2u)] uint size, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b) + { + uint opcode = 0xf3b20300u; // VSHLL.I8 Q0, D0, #8 + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= size << 18; + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VSWP D0, D0")] + public void Vswp([Values(0u, 1u)] uint rd, + [Values(0u, 1u)] uint rm, + [Values] bool q) + { + uint opcode = 0xf3b20000u; // VSWP D0, D0 + + if (q) + { + opcode |= 1u << 6; + + rd &= ~1u; + rm &= ~1u; + } + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + V128 v0 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v1 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdCrypto.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdCrypto.cs new file mode 100644 index 00000000..80612f1c --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdCrypto.cs @@ -0,0 +1,144 @@ +// https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf + +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + public class CpuTestSimdCrypto : CpuTest + { + [Test, Description("AESD .16B, .16B")] + public void Aesd_V([Values(0u)] uint rd, + [Values(1u)] uint rn, + [Values(0x7B5B546573745665ul)] ulong valueH, + [Values(0x63746F725D53475Dul)] ulong valueL, + [Random(2)] ulong roundKeyH, + [Random(2)] ulong roundKeyL, + [Values(0x8DCAB9BC035006BCul)] ulong resultH, + [Values(0x8F57161E00CAFD8Dul)] ulong resultL) + { + uint opcode = 0x4E285800; // AESD V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(roundKeyL ^ valueL, roundKeyH ^ valueH); + V128 v1 = MakeVectorE0E1(roundKeyL, roundKeyH); + + ExecutionContext context = SingleOpcode(opcode, v0: v0, v1: v1); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(1)), Is.EqualTo(roundKeyL)); + Assert.That(GetVectorE1(context.GetV(1)), Is.EqualTo(roundKeyH)); + }); + + CompareAgainstUnicorn(); + } + + [Test, Description("AESE .16B, .16B")] + public void Aese_V([Values(0u)] uint rd, + [Values(1u)] uint rn, + [Values(0x7B5B546573745665ul)] ulong valueH, + [Values(0x63746F725D53475Dul)] ulong valueL, + [Random(2)] ulong roundKeyH, + [Random(2)] ulong roundKeyL, + [Values(0x8F92A04DFBED204Dul)] ulong resultH, + [Values(0x4C39B1402192A84Cul)] ulong resultL) + { + uint opcode = 0x4E284800; // AESE V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(roundKeyL ^ valueL, roundKeyH ^ valueH); + V128 v1 = MakeVectorE0E1(roundKeyL, roundKeyH); + + ExecutionContext context = SingleOpcode(opcode, v0: v0, v1: v1); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(1)), Is.EqualTo(roundKeyL)); + Assert.That(GetVectorE1(context.GetV(1)), Is.EqualTo(roundKeyH)); + }); + + CompareAgainstUnicorn(); + } + + [Test, Description("AESIMC .16B, .16B")] + public void Aesimc_V([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(0x8DCAB9DC035006BCul)] ulong valueH, + [Values(0x8F57161E00CAFD8Dul)] ulong valueL, + [Values(0xD635A667928B5EAEul)] ulong resultH, + [Values(0xEEC9CC3BC55F5777ul)] ulong resultL) + { + uint opcode = 0x4E287800; // AESIMC V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v = MakeVectorE0E1(valueL, valueH); + + ExecutionContext context = SingleOpcode( + opcode, + v0: rn == 0u ? v : default, + v1: rn == 1u ? v : default); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + if (rn == 1u) + { + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(1)), Is.EqualTo(valueL)); + Assert.That(GetVectorE1(context.GetV(1)), Is.EqualTo(valueH)); + }); + } + + CompareAgainstUnicorn(); + } + + [Test, Description("AESMC .16B, .16B")] + public void Aesmc_V([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(0x627A6F6644B109C8ul)] ulong valueH, + [Values(0x2B18330A81C3B3E5ul)] ulong valueL, + [Values(0x7B5B546573745665ul)] ulong resultH, + [Values(0x63746F725D53475Dul)] ulong resultL) + { + uint opcode = 0x4E286800; // AESMC V0.16B, V0.16B + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v = MakeVectorE0E1(valueL, valueH); + + ExecutionContext context = SingleOpcode( + opcode, + v0: rn == 0u ? v : default, + v1: rn == 1u ? v : default); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + if (rn == 1u) + { + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(1)), Is.EqualTo(valueL)); + Assert.That(GetVectorE1(context.GetV(1)), Is.EqualTo(valueH)); + }); + } + + CompareAgainstUnicorn(); + } + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdCrypto32.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdCrypto32.cs new file mode 100644 index 00000000..60076da4 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdCrypto32.cs @@ -0,0 +1,154 @@ +// https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf + +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + public class CpuTestSimdCrypto32 : CpuTest32 + { + [Test, Description("AESD.8 , ")] + public void Aesd_V([Values(0u)] uint rd, + [Values(2u)] uint rm, + [Values(0x7B5B546573745665ul)] ulong valueH, + [Values(0x63746F725D53475Dul)] ulong valueL, + [Random(2)] ulong roundKeyH, + [Random(2)] ulong roundKeyL, + [Values(0x8DCAB9BC035006BCul)] ulong resultH, + [Values(0x8F57161E00CAFD8Dul)] ulong resultL) + { + uint opcode = 0xf3b00340; // AESD.8 Q0, Q0 + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + + V128 v0 = MakeVectorE0E1(roundKeyL ^ valueL, roundKeyH ^ valueH); + V128 v1 = MakeVectorE0E1(roundKeyL, roundKeyH); + + ExecutionContext context = SingleOpcode(opcode, v0: v0, v1: v1, runUnicorn: false); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(1)), Is.EqualTo(roundKeyL)); + Assert.That(GetVectorE1(context.GetV(1)), Is.EqualTo(roundKeyH)); + }); + + // Unicorn does not yet support crypto instructions in A32. + // CompareAgainstUnicorn(); + } + + [Test, Description("AESE.8 , ")] + public void Aese_V([Values(0u)] uint rd, + [Values(2u)] uint rm, + [Values(0x7B5B546573745665ul)] ulong valueH, + [Values(0x63746F725D53475Dul)] ulong valueL, + [Random(2)] ulong roundKeyH, + [Random(2)] ulong roundKeyL, + [Values(0x8F92A04DFBED204Dul)] ulong resultH, + [Values(0x4C39B1402192A84Cul)] ulong resultL) + { + uint opcode = 0xf3b00300; // AESE.8 Q0, Q0 + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + + V128 v0 = MakeVectorE0E1(roundKeyL ^ valueL, roundKeyH ^ valueH); + V128 v1 = MakeVectorE0E1(roundKeyL, roundKeyH); + + ExecutionContext context = SingleOpcode(opcode, v0: v0, v1: v1, runUnicorn: false); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(1)), Is.EqualTo(roundKeyL)); + Assert.That(GetVectorE1(context.GetV(1)), Is.EqualTo(roundKeyH)); + }); + + // Unicorn does not yet support crypto instructions in A32. + // CompareAgainstUnicorn(); + } + + [Test, Description("AESIMC.8 , ")] + public void Aesimc_V([Values(0u)] uint rd, + [Values(2u, 0u)] uint rm, + [Values(0x8DCAB9DC035006BCul)] ulong valueH, + [Values(0x8F57161E00CAFD8Dul)] ulong valueL, + [Values(0xD635A667928B5EAEul)] ulong resultH, + [Values(0xEEC9CC3BC55F5777ul)] ulong resultL) + { + uint opcode = 0xf3b003c0; // AESIMC.8 Q0, Q0 + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + + V128 v = MakeVectorE0E1(valueL, valueH); + + ExecutionContext context = SingleOpcode( + opcode, + v0: rm == 0u ? v : default, + v1: rm == 2u ? v : default, + runUnicorn: false); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + if (rm == 2u) + { + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(1)), Is.EqualTo(valueL)); + Assert.That(GetVectorE1(context.GetV(1)), Is.EqualTo(valueH)); + }); + } + + // Unicorn does not yet support crypto instructions in A32. + // CompareAgainstUnicorn(); + } + + [Test, Description("AESMC.8 , ")] + public void Aesmc_V([Values(0u)] uint rd, + [Values(2u, 0u)] uint rm, + [Values(0x627A6F6644B109C8ul)] ulong valueH, + [Values(0x2B18330A81C3B3E5ul)] ulong valueL, + [Values(0x7B5B546573745665ul)] ulong resultH, + [Values(0x63746F725D53475Dul)] ulong resultL) + { + uint opcode = 0xf3b00380; // AESMC.8 Q0, Q0 + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + + V128 v = MakeVectorE0E1(valueL, valueH); + + ExecutionContext context = SingleOpcode( + opcode, + v0: rm == 0u ? v : default, + v1: rm == 2u ? v : default, + runUnicorn: false); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + if (rm == 2u) + { + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(1)), Is.EqualTo(valueL)); + Assert.That(GetVectorE1(context.GetV(1)), Is.EqualTo(valueH)); + }); + } + + // Unicorn does not yet support crypto instructions in A32. + // CompareAgainstUnicorn(); + } + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs new file mode 100644 index 00000000..007c0f8c --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs @@ -0,0 +1,694 @@ +#define SimdCvt + +using ARMeilleure.State; +using NUnit.Framework; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdCvt")] + public sealed class CpuTestSimdCvt : CpuTest + { +#if SimdCvt + + #region "ValueSource (Types)" + private static uint[] _W_() + { + return new[] { + 0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu, + }; + } + + private static ulong[] _X_() + { + return new[] { + 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static IEnumerable _1S_F_WX_() + { + // int + yield return 0x00000000CF000001ul; // -2.1474839E9f (-2147483904) + yield return 0x00000000CF000000ul; // -2.14748365E9f (-2147483648) + yield return 0x00000000CEFFFFFFul; // -2.14748352E9f (-2147483520) + yield return 0x000000004F000001ul; // 2.1474839E9f (2147483904) + yield return 0x000000004F000000ul; // 2.14748365E9f (2147483648) + yield return 0x000000004EFFFFFFul; // 2.14748352E9f (2147483520) + + // long + yield return 0x00000000DF000001ul; // -9.223373E18f (-9223373136366403584) + yield return 0x00000000DF000000ul; // -9.223372E18f (-9223372036854775808) + yield return 0x00000000DEFFFFFFul; // -9.2233715E18f (-9223371487098961920) + yield return 0x000000005F000001ul; // 9.223373E18f (9223373136366403584) + yield return 0x000000005F000000ul; // 9.223372E18f (9223372036854775808) + yield return 0x000000005EFFFFFFul; // 9.2233715E18f (9223371487098961920) + + // uint + yield return 0x000000004F800001ul; // 4.2949678E9f (4294967808) + yield return 0x000000004F800000ul; // 4.2949673E9f (4294967296) + yield return 0x000000004F7FFFFFul; // 4.29496704E9f (4294967040) + + // ulong + yield return 0x000000005F800001ul; // 1.8446746E19f (18446746272732807168) + yield return 0x000000005F800000ul; // 1.8446744E19f (18446744073709551616) + yield return 0x000000005F7FFFFFul; // 1.8446743E19f (18446742974197923840) + + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + + ulong rnd1 = (uint)BitConverter.SingleToInt32Bits( + (int)TestContext.CurrentContext.Random.NextUInt()); + ulong rnd2 = (uint)BitConverter.SingleToInt32Bits( + (long)TestContext.CurrentContext.Random.NextULong()); + ulong rnd3 = (uint)BitConverter.SingleToInt32Bits( + TestContext.CurrentContext.Random.NextUInt()); + ulong rnd4 = (uint)BitConverter.SingleToInt32Bits( + TestContext.CurrentContext.Random.NextULong()); + + ulong rnd5 = GenNormalS(); + ulong rnd6 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + yield return (grbg << 32) | rnd3; + yield return (grbg << 32) | rnd4; + + yield return (grbg << 32) | rnd5; + yield return (grbg << 32) | rnd6; + } + } + + private static IEnumerable _1D_F_WX_() + { + // int + yield return 0xC1E0000000200000ul; // -2147483649.0000000d (-2147483649) + yield return 0xC1E0000000000000ul; // -2147483648.0000000d (-2147483648) + yield return 0xC1DFFFFFFFC00000ul; // -2147483647.0000000d (-2147483647) + yield return 0x41E0000000200000ul; // 2147483649.0000000d (2147483649) + yield return 0x41E0000000000000ul; // 2147483648.0000000d (2147483648) + yield return 0x41DFFFFFFFC00000ul; // 2147483647.0000000d (2147483647) + + // long + yield return 0xC3E0000000000001ul; // -9.2233720368547780E18d (-9223372036854778000) + yield return 0xC3E0000000000000ul; // -9.2233720368547760E18d (-9223372036854776000) + yield return 0xC3DFFFFFFFFFFFFFul; // -9.2233720368547750E18d (-9223372036854775000) + yield return 0x43E0000000000001ul; // 9.2233720368547780E18d (9223372036854778000) + yield return 0x43E0000000000000ul; // 9.2233720368547760E18d (9223372036854776000) + yield return 0x43DFFFFFFFFFFFFFul; // 9.2233720368547750E18d (9223372036854775000) + + // uint + yield return 0x41F0000000100000ul; // 4294967297.0000000d (4294967297) + yield return 0x41F0000000000000ul; // 4294967296.0000000d (4294967296) + yield return 0x41EFFFFFFFE00000ul; // 4294967295.0000000d (4294967295) + + // ulong + yield return 0x43F0000000000001ul; // 1.8446744073709556e19d (18446744073709556000) + yield return 0x43F0000000000000ul; // 1.8446744073709552E19d (18446744073709552000) + yield return 0x43EFFFFFFFFFFFFFul; // 1.8446744073709550e19d (18446744073709550000) + + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = (ulong)BitConverter.DoubleToInt64Bits( + (int)TestContext.CurrentContext.Random.NextUInt()); + ulong rnd2 = (ulong)BitConverter.DoubleToInt64Bits( + (long)TestContext.CurrentContext.Random.NextULong()); + ulong rnd3 = (ulong)BitConverter.DoubleToInt64Bits( + TestContext.CurrentContext.Random.NextUInt()); + ulong rnd4 = (ulong)BitConverter.DoubleToInt64Bits( + TestContext.CurrentContext.Random.NextULong()); + + ulong rnd5 = GenNormalD(); + ulong rnd6 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + yield return rnd3; + yield return rnd4; + + yield return rnd5; + yield return rnd6; + } + } + #endregion + + #region "ValueSource (Opcodes)" + private static uint[] _F_Cvt_AMPZ_SU_Gp_SW_() + { + return new[] + { + 0x1E240000u, // FCVTAS W0, S0 + 0x1E250000u, // FCVTAU W0, S0 + 0x1E300000u, // FCVTMS W0, S0 + 0x1E310000u, // FCVTMU W0, S0 + 0x1E200000u, // FCVTNS W0, S0 + 0x1E280000u, // FCVTPS W0, S0 + 0x1E290000u, // FCVTPU W0, S0 + 0x1E380000u, // FCVTZS W0, S0 + 0x1E390000u, // FCVTZU W0, S0 + }; + } + + private static uint[] _F_Cvt_AMPZ_SU_Gp_SX_() + { + return new[] + { + 0x9E240000u, // FCVTAS X0, S0 + 0x9E250000u, // FCVTAU X0, S0 + 0x9E300000u, // FCVTMS X0, S0 + 0x9E310000u, // FCVTMU X0, S0 + 0x9E200000u, // FCVTNS X0, S0 + 0x9E280000u, // FCVTPS X0, S0 + 0x9E290000u, // FCVTPU X0, S0 + 0x9E380000u, // FCVTZS X0, S0 + 0x9E390000u, // FCVTZU X0, S0 + }; + } + + private static uint[] _F_Cvt_AMPZ_SU_Gp_DW_() + { + return new[] + { + 0x1E640000u, // FCVTAS W0, D0 + 0x1E650000u, // FCVTAU W0, D0 + 0x1E700000u, // FCVTMS W0, D0 + 0x1E710000u, // FCVTMU W0, D0 + 0x1E600000u, // FCVTNS W0, D0 + 0x1E680000u, // FCVTPS W0, D0 + 0x1E690000u, // FCVTPU W0, D0 + 0x1E780000u, // FCVTZS W0, D0 + 0x1E790000u, // FCVTZU W0, D0 + }; + } + + private static uint[] _F_Cvt_AMPZ_SU_Gp_DX_() + { + return new[] + { + 0x9E640000u, // FCVTAS X0, D0 + 0x9E650000u, // FCVTAU X0, D0 + 0x9E700000u, // FCVTMS X0, D0 + 0x9E710000u, // FCVTMU X0, D0 + 0x9E600000u, // FCVTNS X0, D0 + 0x9E680000u, // FCVTPS X0, D0 + 0x9E690000u, // FCVTPU X0, D0 + 0x9E780000u, // FCVTZS X0, D0 + 0x9E790000u, // FCVTZU X0, D0 + }; + } + + private static uint[] _F_Cvt_Z_SU_Gp_Fixed_SW_() + { + return new[] + { + 0x1E188000u, // FCVTZS W0, S0, #32 + 0x1E198000u, // FCVTZU W0, S0, #32 + }; + } + + private static uint[] _F_Cvt_Z_SU_Gp_Fixed_SX_() + { + return new[] + { + 0x9E180000u, // FCVTZS X0, S0, #64 + 0x9E190000u, // FCVTZU X0, S0, #64 + }; + } + + private static uint[] _F_Cvt_Z_SU_Gp_Fixed_DW_() + { + return new[] + { + 0x1E588000u, // FCVTZS W0, D0, #32 + 0x1E598000u, // FCVTZU W0, D0, #32 + }; + } + + private static uint[] _F_Cvt_Z_SU_Gp_Fixed_DX_() + { + return new[] + { + 0x9E580000u, // FCVTZS X0, D0, #64 + 0x9E590000u, // FCVTZU X0, D0, #64 + }; + } + + private static uint[] _SU_Cvt_F_Gp_WS_() + { + return new[] + { + 0x1E220000u, // SCVTF S0, W0 + 0x1E230000u, // UCVTF S0, W0 + }; + } + + private static uint[] _SU_Cvt_F_Gp_WD_() + { + return new[] + { + 0x1E620000u, // SCVTF D0, W0 + 0x1E630000u, // UCVTF D0, W0 + }; + } + + private static uint[] _SU_Cvt_F_Gp_XS_() + { + return new[] + { + 0x9E220000u, // SCVTF S0, X0 + 0x9E230000u, // UCVTF S0, X0 + }; + } + + private static uint[] _SU_Cvt_F_Gp_XD_() + { + return new[] + { + 0x9E620000u, // SCVTF D0, X0 + 0x9E630000u, // UCVTF D0, X0 + }; + } + + private static uint[] _SU_Cvt_F_Gp_Fixed_WS_() + { + return new[] + { + 0x1E028000u, // SCVTF S0, W0, #32 + 0x1E038000u, // UCVTF S0, W0, #32 + }; + } + + private static uint[] _SU_Cvt_F_Gp_Fixed_WD_() + { + return new[] + { + 0x1E428000u, // SCVTF D0, W0, #32 + 0x1E438000u, // UCVTF D0, W0, #32 + }; + } + + private static uint[] _SU_Cvt_F_Gp_Fixed_XS_() + { + return new[] + { + 0x9E020000u, // SCVTF S0, X0, #64 + 0x9E030000u, // UCVTF S0, X0, #64 + }; + } + + private static uint[] _SU_Cvt_F_Gp_Fixed_XD_() + { + return new[] + { + 0x9E420000u, // SCVTF D0, X0, #64 + 0x9E430000u, // UCVTF D0, X0, #64 + }; + } + #endregion + + private const int RndCnt = 2; + + private static readonly bool _noZeros = false; + private static readonly bool _noInfs = false; + private static readonly bool _noNaNs = false; + + [Test, Pairwise] + [Explicit] + public void F_Cvt_AMPZ_SU_Gp_SW([ValueSource(nameof(_F_Cvt_AMPZ_SU_Gp_SW_))] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_1S_F_WX_))] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_AMPZ_SU_Gp_SX([ValueSource(nameof(_F_Cvt_AMPZ_SU_Gp_SX_))] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_1S_F_WX_))] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_AMPZ_SU_Gp_DW([ValueSource(nameof(_F_Cvt_AMPZ_SU_Gp_DW_))] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_1D_F_WX_))] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_AMPZ_SU_Gp_DX([ValueSource(nameof(_F_Cvt_AMPZ_SU_Gp_DX_))] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_1D_F_WX_))] ulong a) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_Z_SU_Gp_Fixed_SW([ValueSource(nameof(_F_Cvt_Z_SU_Gp_Fixed_SW_))] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_1S_F_WX_))] ulong a, + [Values(1u, 32u)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_Z_SU_Gp_Fixed_SX([ValueSource(nameof(_F_Cvt_Z_SU_Gp_Fixed_SX_))] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_1S_F_WX_))] ulong a, + [Values(1u, 64u)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_Z_SU_Gp_Fixed_DW([ValueSource(nameof(_F_Cvt_Z_SU_Gp_Fixed_DW_))] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_1D_F_WX_))] ulong a, + [Values(1u, 32u)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_Z_SU_Gp_Fixed_DX([ValueSource(nameof(_F_Cvt_Z_SU_Gp_Fixed_DX_))] uint opcodes, + [Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_1D_F_WX_))] ulong a, + [Values(1u, 64u)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_Gp_WS([ValueSource(nameof(_SU_Cvt_F_Gp_WS_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_W_))] uint wn) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_Gp_WD([ValueSource(nameof(_SU_Cvt_F_Gp_WD_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_W_))] uint wn) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_Gp_XS([ValueSource(nameof(_SU_Cvt_F_Gp_XS_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_X_))] ulong xn) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_Gp_XD([ValueSource(nameof(_SU_Cvt_F_Gp_XD_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_X_))] ulong xn) + { + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_Gp_Fixed_WS([ValueSource(nameof(_SU_Cvt_F_Gp_Fixed_WS_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_W_))] uint wn, + [Values(1u, 32u)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_Gp_Fixed_WD([ValueSource(nameof(_SU_Cvt_F_Gp_Fixed_WD_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_W_))] uint wn, + [Values(1u, 32u)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_Gp_Fixed_XS([ValueSource(nameof(_SU_Cvt_F_Gp_Fixed_XS_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_X_))] ulong xn, + [Values(1u, 64u)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_Gp_Fixed_XD([ValueSource(nameof(_SU_Cvt_F_Gp_Fixed_XD_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_X_))] ulong xn, + [Values(1u, 64u)] uint fBits) + { + uint scale = (64u - fBits) & 0x3Fu; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (scale << 10); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs new file mode 100644 index 00000000..ba201a48 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs @@ -0,0 +1,555 @@ +#define SimdCvt32 + +using ARMeilleure.State; +using NUnit.Framework; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdCvt32")] + public sealed class CpuTestSimdCvt32 : CpuTest32 + { +#if SimdCvt32 + + #region "ValueSource (Opcodes)" + private static uint[] _Vrint_AMNP_V_F32_() + { + return new[] + { + 0xf3ba0500u, // VRINTA.F32 Q0, Q0 + 0xf3ba0680u, // VRINTM.F32 Q0, Q0 + 0xf3ba0400u, // VRINTN.F32 Q0, Q0 + 0xf3ba0780u, // VRINTP.F32 Q0, Q0 + }; + } + #endregion + + #region "ValueSource (Types)" + private static uint[] _1S_() + { + return new[] { + 0x00000000u, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu, + }; + } + + private static IEnumerable _1S_F_() + { + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + } + } + + private static IEnumerable _2S_F_() + { + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + } + } + + private static IEnumerable _1D_F_() + { + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalD(); + ulong rnd2 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + } + } + #endregion + + private const int RndCnt = 2; + + private static readonly bool _noZeros = false; + private static readonly bool _noInfs = false; + private static readonly bool _noNaNs = false; + + [Explicit] + [Test, Pairwise, Description("VCVT.
.F32 , ")] + public void Vcvt_F32_I32([Values(0u, 1u, 2u, 3u)] uint rd, + [Values(0u, 1u, 2u, 3u)] uint rm, + [ValueSource(nameof(_1S_F_))] ulong s0, + [ValueSource(nameof(_1S_F_))] ulong s1, + [ValueSource(nameof(_1S_F_))] ulong s2, + [ValueSource(nameof(_1S_F_))] ulong s3, + [Values] bool unsigned) // + { + uint opcode = 0xeebc0ac0u; // VCVT.U32.F32 S0, S0 + + if (!unsigned) + { + opcode |= 1 << 16; // opc2<0> + } + + opcode |= ((rd & 0x1e) << 11) | ((rd & 0x1) << 22); + opcode |= ((rm & 0x1e) >> 1) | ((rm & 0x1) << 5); + + V128 v0 = MakeVectorE0E1E2E3((uint)s0, (uint)s1, (uint)s2, (uint)s3); + + SingleOpcode(opcode, v0: v0); + + CompareAgainstUnicorn(); + } + + [Explicit] + [Test, Pairwise, Description("VCVT.
.F64 , ")] + public void Vcvt_F64_I32([Values(0u, 1u, 2u, 3u)] uint rd, + [Values(0u, 1u)] uint rm, + [ValueSource(nameof(_1D_F_))] ulong d0, + [ValueSource(nameof(_1D_F_))] ulong d1, + [Values] bool unsigned) // + { + uint opcode = 0xeebc0bc0u; // VCVT.U32.F64 S0, D0 + + if (!unsigned) + { + opcode |= 1 << 16; // opc2<0> + } + + opcode |= ((rd & 0x1e) << 11) | ((rd & 0x1) << 22); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + V128 v0 = MakeVectorE0E1(d0, d1); + + SingleOpcode(opcode, v0: v0); + + CompareAgainstUnicorn(); + } + + [Explicit] + [Test, Pairwise, Description("VCVT.F32.
, ")] + public void Vcvt_I32_F32([Values(0u, 1u, 2u, 3u)] uint rd, + [Values(0u, 1u, 2u, 3u)] uint rm, + [ValueSource(nameof(_1S_))] uint s0, + [ValueSource(nameof(_1S_))] uint s1, + [ValueSource(nameof(_1S_))] uint s2, + [ValueSource(nameof(_1S_))] uint s3, + [Values] bool unsigned, // + [Values(RMode.Rn)] RMode rMode) + { + uint opcode = 0xeeb80a40u; // VCVT.F32.U32 S0, S0 + + if (!unsigned) + { + opcode |= 1 << 7; // op + } + + opcode |= ((rm & 0x1e) >> 1) | ((rm & 0x1) << 5); + opcode |= ((rd & 0x1e) << 11) | ((rd & 0x1) << 22); + + V128 v0 = MakeVectorE0E1E2E3(s0, s1, s2, s3); + + int fpscr = (int)rMode << (int)Fpcr.RMode; + + SingleOpcode(opcode, v0: v0, fpscr: fpscr); + + CompareAgainstUnicorn(); + } + + [Explicit] + [Test, Pairwise, Description("VCVT.F64.
, ")] + public void Vcvt_I32_F64([Values(0u, 1u)] uint rd, + [Values(0u, 1u, 2u, 3u)] uint rm, + [ValueSource(nameof(_1S_))] uint s0, + [ValueSource(nameof(_1S_))] uint s1, + [ValueSource(nameof(_1S_))] uint s2, + [ValueSource(nameof(_1S_))] uint s3, + [Values] bool unsigned, // + [Values(RMode.Rn)] RMode rMode) + { + uint opcode = 0xeeb80b40u; // VCVT.F64.U32 D0, S0 + + if (!unsigned) + { + opcode |= 1 << 7; // op + } + + opcode |= ((rm & 0x1e) >> 1) | ((rm & 0x1) << 5); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + + V128 v0 = MakeVectorE0E1E2E3(s0, s1, s2, s3); + + int fpscr = (int)rMode << (int)Fpcr.RMode; + + SingleOpcode(opcode, v0: v0, fpscr: fpscr); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void Vrint_AMNP_V_F32([ValueSource(nameof(_Vrint_AMNP_V_F32_))] uint opcode, + [Values(0u, 1u, 2u, 3u)] uint rd, + [Values(0u, 1u, 2u, 3u)] uint rm, + [ValueSource(nameof(_2S_F_))] ulong d0, + [ValueSource(nameof(_2S_F_))] ulong d1, + [ValueSource(nameof(_2S_F_))] ulong d2, + [ValueSource(nameof(_2S_F_))] ulong d3, + [Values] bool q) + { + if (q) + { + opcode |= 1 << 6; + + rd >>= 1; + rd <<= 1; + rm >>= 1; + rm <<= 1; + } + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + V128 v0 = MakeVectorE0E1(d0, d1); + V128 v1 = MakeVectorE0E1(d2, d3); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VRINTX.F , ")] + public void Vrintx_S([Values(0u, 1u)] uint rd, + [Values(0u, 1u)] uint rm, + [Values(2u, 3u)] uint size, + [ValueSource(nameof(_1D_F_))] ulong s0, + [ValueSource(nameof(_1D_F_))] ulong s1, + [ValueSource(nameof(_1D_F_))] ulong s2, + [Values(RMode.Rn, RMode.Rm, RMode.Rp)] RMode rMode) + { + uint opcode = 0xEB70A40; + V128 v0, v1, v2; + if (size == 2) + { + opcode |= ((rm & 0x1e) >> 1) | ((rm & 0x1) << 5); + opcode |= ((rd & 0x1e) >> 11) | ((rm & 0x1) << 22); + v0 = MakeVectorE0E1((uint)BitConverter.SingleToInt32Bits(s0), (uint)BitConverter.SingleToInt32Bits(s0)); + v1 = MakeVectorE0E1((uint)BitConverter.SingleToInt32Bits(s1), (uint)BitConverter.SingleToInt32Bits(s0)); + v2 = MakeVectorE0E1((uint)BitConverter.SingleToInt32Bits(s2), (uint)BitConverter.SingleToInt32Bits(s1)); + } + else + { + opcode |= ((rm & 0xf) << 0) | ((rd & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + v0 = MakeVectorE0E1((uint)BitConverter.DoubleToInt64Bits(s0), (uint)BitConverter.DoubleToInt64Bits(s0)); + v1 = MakeVectorE0E1((uint)BitConverter.DoubleToInt64Bits(s1), (uint)BitConverter.DoubleToInt64Bits(s0)); + v2 = MakeVectorE0E1((uint)BitConverter.DoubleToInt64Bits(s2), (uint)BitConverter.DoubleToInt64Bits(s1)); + } + + opcode |= ((size & 3) << 8); + + int fpscr = (int)rMode << (int)Fpcr.RMode; + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, fpscr: fpscr); + + CompareAgainstUnicorn(); + } + + [Explicit] + [Test, Pairwise, Description("VCVT.F16.F32 , ")] + public void Vcvt_F32_F16([Values(0u, 1u, 2u, 3u)] uint rd, + [Values(0u, 1u, 2u, 3u)] uint rm, + [ValueSource(nameof(_1S_))] uint s0, + [ValueSource(nameof(_1S_))] uint s1, + [ValueSource(nameof(_1S_))] uint s2, + [ValueSource(nameof(_1S_))] uint s3, + [Values] bool top) + { + uint opcode = 0xeeb30a40; // VCVTB.F16.F32 S0, D0 + + if (top) + { + opcode |= 1 << 7; + } + + opcode |= ((rd & 0x1e) << 11) | ((rd & 0x1) << 22); + opcode |= ((rm & 0x1e) >> 1) | ((rm & 0x1) << 5); + + V128 v0 = MakeVectorE0E1E2E3(s0, s1, s2, s3); + + SingleOpcode(opcode, v0: v0); + + CompareAgainstUnicorn(); + } + + [Explicit] + [Test, Pairwise, Description("VCVT.F16.F64 , ")] + public void Vcvt_F64_F16([Values(0u, 1u, 2u, 3u)] uint rd, + [Values(0u, 1u)] uint rm, + [ValueSource(nameof(_1D_F_))] ulong d0, + [ValueSource(nameof(_1D_F_))] ulong d1, + [Values] bool top) + { + uint opcode = 0xeeb30b40; // VCVTB.F16.F64 S0, D0 + + if (top) + { + opcode |= 1 << 7; + } + + opcode |= ((rd & 0x1e) << 11) | ((rd & 0x1) << 22); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + V128 v0 = MakeVectorE0E1(d0, d1); + + SingleOpcode(opcode, v0: v0); + + CompareAgainstUnicorn(); + } + + [Explicit] + [Test, Pairwise, Description("VCVT.F.F16 , ")] + public void Vcvt_F16_Fx([Values(0u, 1u, 2u, 3u)] uint rd, + [Values(0u, 1u, 2u, 3u)] uint rm, + [ValueSource(nameof(_1D_F_))] ulong d0, + [ValueSource(nameof(_1D_F_))] ulong d1, + [Values] bool top, + [Values] bool sz) + { + uint opcode = 0xeeb20a40; // VCVTB.F32.F16 S0, S0 + + if (top) + { + opcode |= 1 << 7; + } + + if (sz) + { + opcode |= 1 << 8; + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + } + else + { + opcode |= ((rd & 0x1e) << 11) | ((rd & 0x1) << 22); + } + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + V128 v0 = MakeVectorE0E1(d0, d1); + + SingleOpcode(opcode, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VCVT.I32.F32 , , #")] + public void Vcvt_V_Fixed_F32_I32([Values(0u, 1u, 2u, 3u)] uint vd, + [Values(0u, 1u, 2u, 3u)] uint vm, + [ValueSource(nameof(_1S_F_))][Random(RndCnt)] ulong s0, + [ValueSource(nameof(_1S_F_))][Random(RndCnt)] ulong s1, + [ValueSource(nameof(_1S_F_))][Random(RndCnt)] ulong s2, + [ValueSource(nameof(_1S_F_))][Random(RndCnt)] ulong s3, + [Random(32u, 63u, 1)] uint fixImm, + [Values] bool unsigned, + [Values] bool q) + { + uint opcode = 0xF2800F10u; // VCVT.U32.F32 D0, D0, #0 + + if (q) + { + opcode |= 1 << 6; + vm <<= 1; + vd <<= 1; + } + + if (unsigned) + { + opcode |= 1 << 24; + } + + opcode |= ((vm & 0x10) << 1); + opcode |= ((vm & 0xf) << 0); + + opcode |= ((vd & 0x10) << 18); + opcode |= ((vd & 0xf) << 12); + + opcode |= (fixImm & 0x3f) << 16; + + var v0 = new V128((uint)s0, (uint)s1, (uint)s2, (uint)s3); + + SingleOpcode(opcode, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VCVT.F32.I32 , , #")] + public void Vcvt_V_Fixed_I32_F32([Values(0u, 1u, 2u, 3u)] uint vd, + [Values(0u, 1u, 2u, 3u)] uint vm, + [ValueSource(nameof(_1S_))][Random(RndCnt)] uint s0, + [ValueSource(nameof(_1S_))][Random(RndCnt)] uint s1, + [ValueSource(nameof(_1S_))][Random(RndCnt)] uint s2, + [ValueSource(nameof(_1S_))][Random(RndCnt)] uint s3, + [Range(32u, 63u, 1)] uint fixImm, + [Values] bool unsigned, + [Values] bool q) + { + uint opcode = 0xF2800E10u; // VCVT.F32.U32 D0, D0, #0 + + if (q) + { + opcode |= 1 << 6; + vm <<= 1; + vd <<= 1; + } + + if (unsigned) + { + opcode |= 1 << 24; + } + + opcode |= ((vm & 0x10) << 1); + opcode |= ((vm & 0xf) << 0); + + opcode |= ((vd & 0x10) << 18); + opcode |= ((vd & 0xf) << 12); + + opcode |= (fixImm & 0x3f) << 16; + + var v0 = new V128(s0, s1, s2, s3); + + SingleOpcode(opcode, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VRINTR.F , ")] + [Platform(Exclude = "Linux,MacOsX")] // Instruction isn't testable due to Unicorn. + public void Vrintr([Values(0u, 1u)] uint rd, + [Values(0u, 1u)] uint rm, + [Values(2u, 3u)] uint size, + [ValueSource(nameof(_1D_F_))] ulong s0, + [ValueSource(nameof(_1D_F_))] ulong s1, + [ValueSource(nameof(_1D_F_))] ulong s2, + [Values(RMode.Rn, RMode.Rm, RMode.Rp)] RMode rMode) + { + uint opcode = 0xEEB60A40; + + V128 v0, v1, v2; + + if (size == 2) + { + opcode |= ((rm & 0x1e) >> 1) | ((rm & 0x1) << 5); + opcode |= ((rd & 0x1e) << 11) | ((rd & 0x1) << 22); + v0 = MakeVectorE0E1((uint)BitConverter.SingleToInt32Bits(s0), (uint)BitConverter.SingleToInt32Bits(s0)); + v1 = MakeVectorE0E1((uint)BitConverter.SingleToInt32Bits(s1), (uint)BitConverter.SingleToInt32Bits(s0)); + v2 = MakeVectorE0E1((uint)BitConverter.SingleToInt32Bits(s2), (uint)BitConverter.SingleToInt32Bits(s1)); + } + else + { + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + v0 = MakeVectorE0E1((uint)BitConverter.DoubleToInt64Bits(s0), (uint)BitConverter.DoubleToInt64Bits(s0)); + v1 = MakeVectorE0E1((uint)BitConverter.DoubleToInt64Bits(s1), (uint)BitConverter.DoubleToInt64Bits(s0)); + v2 = MakeVectorE0E1((uint)BitConverter.DoubleToInt64Bits(s2), (uint)BitConverter.DoubleToInt64Bits(s1)); + } + + opcode |= ((size & 3) << 8); + + int fpscr = (int)rMode << (int)Fpcr.RMode; + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, fpscr: fpscr); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdExt.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdExt.cs new file mode 100644 index 00000000..59bc4cb7 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdExt.cs @@ -0,0 +1,72 @@ +#define SimdExt + +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdExt")] + public sealed class CpuTestSimdExt : CpuTest + { +#if SimdExt + + #region "ValueSource" + private static ulong[] _8B_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + #endregion + + [Test, Pairwise, Description("EXT .8B, .8B, .8B, #")] + public void Ext_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b, + [Values(0u, 7u)] uint index) + { + uint imm4 = index & 0x7u; + + uint opcode = 0x2E000000; // EXT V0.8B, V0.8B, V0.8B, #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm4 << 11); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("EXT .16B, .16B, .16B, #")] + public void Ext_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b, + [Values(0u, 15u)] uint index) + { + uint imm4 = index & 0xFu; + + uint opcode = 0x6E000000; // EXT V0.16B, V0.16B, V0.16B, #0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm4 << 11); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdFcond.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdFcond.cs new file mode 100644 index 00000000..d6d12b27 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdFcond.cs @@ -0,0 +1,239 @@ +#define SimdFcond + +using ARMeilleure.State; +using NUnit.Framework; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdFcond")] + public sealed class CpuTestSimdFcond : CpuTest + { +#if SimdFcond + + #region "ValueSource (Types)" + private static IEnumerable _1S_F_() + { + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + } + } + + private static IEnumerable _1D_F_() + { + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalD(); + ulong rnd2 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + } + } + #endregion + + #region "ValueSource (Opcodes)" + private static uint[] _F_Ccmp_Ccmpe_S_S_() + { + return new[] + { + 0x1E220420u, // FCCMP S1, S2, #0, EQ + 0x1E220430u, // FCCMPE S1, S2, #0, EQ + }; + } + + private static uint[] _F_Ccmp_Ccmpe_S_D_() + { + return new[] + { + 0x1E620420u, // FCCMP D1, D2, #0, EQ + 0x1E620430u, // FCCMPE D1, D2, #0, EQ + }; + } + + private static uint[] _F_Csel_S_S_() + { + return new[] + { + 0x1E220C20u, // FCSEL S0, S1, S2, EQ + }; + } + + private static uint[] _F_Csel_S_D_() + { + return new[] + { + 0x1E620C20u, // FCSEL D0, D1, D2, EQ + }; + } + #endregion + + private const int RndCnt = 2; + private const int RndCntNzcv = 2; + + private static readonly bool _noZeros = false; + private static readonly bool _noInfs = false; + private static readonly bool _noNaNs = false; + + [Test, Pairwise] + [Explicit] + public void F_Ccmp_Ccmpe_S_S([ValueSource(nameof(_F_Ccmp_Ccmpe_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a, + [ValueSource(nameof(_1S_F_))] ulong b, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + opcodes |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + SingleOpcode(opcodes, v1: v1, v2: v2, overflow: v, carry: c, zero: z, negative: n); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc); + } + + [Test, Pairwise] + [Explicit] + public void F_Ccmp_Ccmpe_S_D([ValueSource(nameof(_F_Ccmp_Ccmpe_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b, + [Random(0u, 15u, RndCntNzcv)] uint nzcv, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + opcodes |= ((cond & 15) << 12) | ((nzcv & 15) << 0); + + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + SingleOpcode(opcodes, v1: v1, v2: v2, overflow: v, carry: c, zero: z, negative: n); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc); + } + + [Test, Pairwise] + [Explicit] + public void F_Csel_S_S([ValueSource(nameof(_F_Csel_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a, + [ValueSource(nameof(_1S_F_))] ulong b, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + opcodes |= ((cond & 15) << 12); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Csel_S_D([ValueSource(nameof(_F_Csel_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b, + [Values(0b0000u, 0b0001u, 0b0010u, 0b0011u, // + { + opcodes |= ((cond & 15) << 12); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdFmov.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdFmov.cs new file mode 100644 index 00000000..0c258269 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdFmov.cs @@ -0,0 +1,62 @@ +#define SimdFmov + +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdFmov")] + public sealed class CpuTestSimdFmov : CpuTest + { +#if SimdFmov + + #region "ValueSource" + private static uint[] _F_Mov_Si_S_() + { + return new[] + { + 0x1E201000u, // FMOV S0, #2.0 + }; + } + + private static uint[] _F_Mov_Si_D_() + { + return new[] + { + 0x1E601000u, // FMOV D0, #2.0 + }; + } + #endregion + + [Test, Pairwise] + [Explicit] + public void F_Mov_Si_S([ValueSource(nameof(_F_Mov_Si_S_))] uint opcodes, + [Range(0u, 255u, 1u)] uint imm8) + { + opcodes |= ((imm8 & 0xFFu) << 13); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Mov_Si_D([ValueSource(nameof(_F_Mov_Si_D_))] uint opcodes, + [Range(0u, 255u, 1u)] uint imm8) + { + opcodes |= ((imm8 & 0xFFu) << 13); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdImm.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdImm.cs new file mode 100644 index 00000000..27e3b41a --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdImm.cs @@ -0,0 +1,402 @@ +#define SimdImm + +using ARMeilleure.State; +using NUnit.Framework; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdImm")] + public sealed class CpuTestSimdImm : CpuTest + { +#if SimdImm + + #region "Helper methods" + // abcdefgh -> aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffgggggggghhhhhhhh + private static ulong ExpandImm8(byte imm8) + { + ulong imm64 = 0ul; + + for (int i = 0, j = 0; i < 8; i++, j += 8) + { + if (((imm8 >> i) & 0b1) != 0) + { + imm64 |= 0b11111111ul << j; + } + } + + return imm64; + } + + // aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffgggggggghhhhhhhh -> abcdefgh + private static byte ShrinkImm64(ulong imm64) + { + byte imm8 = 0; + + for (int i = 0, j = 0; i < 8; i++, j += 8) + { + if (((imm64 >> j) & 0b11111111ul) != 0ul) // Note: no format check. + { + imm8 |= (byte)(0b1 << i); + } + } + + return imm8; + } + #endregion + + #region "ValueSource (Types)" + private static ulong[] _2S_() + { + return new[] { + 0x0000000000000000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _4H_() + { + return new[] { + 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static IEnumerable _8BIT_IMM_() + { + yield return 0x00; + yield return 0x7F; + yield return 0x80; + yield return 0xFF; + + for (int cnt = 1; cnt <= RndCntImm8; cnt++) + { + byte imm8 = TestContext.CurrentContext.Random.NextByte(); + + yield return imm8; + } + } + + private static IEnumerable _64BIT_IMM_() + { + yield return ExpandImm8(0x00); + yield return ExpandImm8(0x7F); + yield return ExpandImm8(0x80); + yield return ExpandImm8(0xFF); + + for (int cnt = 1; cnt <= RndCntImm64; cnt++) + { + byte imm8 = TestContext.CurrentContext.Random.NextByte(); + + yield return ExpandImm8(imm8); + } + } + #endregion + + #region "ValueSource (Opcodes)" + private static uint[] _Bic_Orr_Vi_16bit_() + { + return new[] + { + 0x2F009400u, // BIC V0.4H, #0 + 0x0F009400u, // ORR V0.4H, #0 + }; + } + + private static uint[] _Bic_Orr_Vi_32bit_() + { + return new[] + { + 0x2F001400u, // BIC V0.2S, #0 + 0x0F001400u, // ORR V0.2S, #0 + }; + } + + private static uint[] _F_Mov_Vi_2S_() + { + return new[] + { + 0x0F00F400u, // FMOV V0.2S, #2.0 + }; + } + + private static uint[] _F_Mov_Vi_4S_() + { + return new[] + { + 0x4F00F400u, // FMOV V0.4S, #2.0 + }; + } + + private static uint[] _F_Mov_Vi_2D_() + { + return new[] + { + 0x6F00F400u, // FMOV V0.2D, #2.0 + }; + } + + private static uint[] _Movi_V_8bit_() + { + return new[] + { + 0x0F00E400u, // MOVI V0.8B, #0 + }; + } + + private static uint[] _Movi_Mvni_V_16bit_shifted_imm_() + { + return new[] + { + 0x0F008400u, // MOVI V0.4H, #0 + 0x2F008400u, // MVNI V0.4H, #0 + }; + } + + private static uint[] _Movi_Mvni_V_32bit_shifted_imm_() + { + return new[] + { + 0x0F000400u, // MOVI V0.2S, #0 + 0x2F000400u, // MVNI V0.2S, #0 + }; + } + + private static uint[] _Movi_Mvni_V_32bit_shifting_ones_() + { + return new[] + { + 0x0F00C400u, // MOVI V0.2S, #0, MSL #8 + 0x2F00C400u, // MVNI V0.2S, #0, MSL #8 + }; + } + + private static uint[] _Movi_V_64bit_scalar_() + { + return new[] + { + 0x2F00E400u, // MOVI D0, #0 + }; + } + + private static uint[] _Movi_V_64bit_vector_() + { + return new[] + { + 0x6F00E400u, // MOVI V0.2D, #0 + }; + } + #endregion + + private const int RndCntImm8 = 2; + private const int RndCntImm64 = 2; + + [Test, Pairwise] + public void Bic_Orr_Vi_16bit([ValueSource(nameof(_Bic_Orr_Vi_16bit_))] uint opcodes, + [ValueSource(nameof(_4H_))] ulong z, + [ValueSource(nameof(_8BIT_IMM_))] byte imm8, + [Values(0b0u, 0b1u)] uint amount, // <0, 8> + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + opcodes |= ((amount & 1) << 13); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Bic_Orr_Vi_32bit([ValueSource(nameof(_Bic_Orr_Vi_32bit_))] uint opcodes, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_8BIT_IMM_))] byte imm8, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint amount, // <0, 8, 16, 24> + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + opcodes |= ((amount & 3) << 13); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Mov_Vi_2S([ValueSource(nameof(_F_Mov_Vi_2S_))] uint opcodes, + [Range(0u, 255u, 1u)] uint abcdefgh) + { + uint abc = (abcdefgh & 0xE0u) >> 5; + uint defgh = (abcdefgh & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Mov_Vi_4S([ValueSource(nameof(_F_Mov_Vi_4S_))] uint opcodes, + [Range(0u, 255u, 1u)] uint abcdefgh) + { + uint abc = (abcdefgh & 0xE0u) >> 5; + uint defgh = (abcdefgh & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + + SingleOpcode(opcodes); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Mov_Vi_2D([ValueSource(nameof(_F_Mov_Vi_2D_))] uint opcodes, + [Range(0u, 255u, 1u)] uint abcdefgh) + { + uint abc = (abcdefgh & 0xE0u) >> 5; + uint defgh = (abcdefgh & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + + SingleOpcode(opcodes); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Movi_V_8bit([ValueSource(nameof(_Movi_V_8bit_))] uint opcodes, + [ValueSource(nameof(_8BIT_IMM_))] byte imm8, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + opcodes |= ((q & 1) << 30); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(q == 0u ? z : 0ul); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Movi_Mvni_V_16bit_shifted_imm([ValueSource(nameof(_Movi_Mvni_V_16bit_shifted_imm_))] uint opcodes, + [ValueSource(nameof(_8BIT_IMM_))] byte imm8, + [Values(0b0u, 0b1u)] uint amount, // <0, 8> + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + opcodes |= ((amount & 1) << 13); + opcodes |= ((q & 1) << 30); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(q == 0u ? z : 0ul); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Movi_Mvni_V_32bit_shifted_imm([ValueSource(nameof(_Movi_Mvni_V_32bit_shifted_imm_))] uint opcodes, + [ValueSource(nameof(_8BIT_IMM_))] byte imm8, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint amount, // <0, 8, 16, 24> + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + opcodes |= ((amount & 3) << 13); + opcodes |= ((q & 1) << 30); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(q == 0u ? z : 0ul); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Movi_Mvni_V_32bit_shifting_ones([ValueSource(nameof(_Movi_Mvni_V_32bit_shifting_ones_))] uint opcodes, + [ValueSource(nameof(_8BIT_IMM_))] byte imm8, + [Values(0b0u, 0b1u)] uint amount, // <8, 16> + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + opcodes |= ((amount & 1) << 12); + opcodes |= ((q & 1) << 30); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(q == 0u ? z : 0ul); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Movi_V_64bit_scalar([ValueSource(nameof(_Movi_V_64bit_scalar_))] uint opcodes, + [ValueSource(nameof(_64BIT_IMM_))] ulong imm) + { + byte imm8 = ShrinkImm64(imm); + + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + + SingleOpcode(opcodes, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Movi_V_64bit_vector([ValueSource(nameof(_Movi_V_64bit_vector_))] uint opcodes, + [ValueSource(nameof(_64BIT_IMM_))] ulong imm) + { + byte imm8 = ShrinkImm64(imm); + + uint abc = (imm8 & 0xE0u) >> 5; + uint defgh = (imm8 & 0x1Fu); + + opcodes |= (abc << 16) | (defgh << 5); + + SingleOpcode(opcodes); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs new file mode 100644 index 00000000..83dc0770 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs @@ -0,0 +1,705 @@ +#define SimdIns + +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdIns")] + public sealed class CpuTestSimdIns : CpuTest + { +#if SimdIns + + #region "ValueSource" + private static ulong[] _1D_() + { + return new[] { + 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _2S_() + { + return new[] { + 0x0000000000000000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _4H_() + { + return new[] { + 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _8B_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _8B4H_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _8B4H2S_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static uint[] _W_() + { + return new[] { + 0x00000000u, 0x0000007Fu, + 0x00000080u, 0x000000FFu, + 0x00007FFFu, 0x00008000u, + 0x0000FFFFu, 0x7FFFFFFFu, + 0x80000000u, 0xFFFFFFFFu, + }; + } + + private static ulong[] _X_() + { + return new[] { + 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + #endregion + + [Test, Pairwise, Description("DUP ., W")] + public void Dup_Gp_W([Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_W_))] uint wn, + [Values(0, 1, 2)] int size, // Q0: <8B, 4H, 2S> + [Values(0b0u, 0b1u)] uint q) // Q1: <16B, 8H, 4S> + { + uint imm5 = (1u << size) & 0x1Fu; + + uint opcode = 0x0E000C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= ((q & 1) << 30); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcode, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP ., X")] + public void Dup_Gp_X([Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_X_))] ulong xn) + { + uint opcode = 0x4E080C00; // DUP V0.2D, X0 + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcode, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP B0, V1.B[]")] + public void Dup_S_B([ValueSource(nameof(_8B_))] ulong a, + [Values(0u, 15u)] uint index) + { + const int TestSize = 0; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x5E000420; // RESERVED + opcode |= (imm5 << 16); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP H0, V1.H[]")] + public void Dup_S_H([ValueSource(nameof(_4H_))] ulong a, + [Values(0u, 7u)] uint index) + { + const int TestSize = 1; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x5E000420; // RESERVED + opcode |= (imm5 << 16); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP S0, V1.S[]")] + public void Dup_S_S([ValueSource(nameof(_2S_))] ulong a, + [Values(0u, 1u, 2u, 3u)] uint index) + { + const int TestSize = 2; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x5E000420; // RESERVED + opcode |= (imm5 << 16); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP D0, V1.D[]")] + public void Dup_S_D([ValueSource(nameof(_1D_))] ulong a, + [Values(0u, 1u)] uint index) + { + const int TestSize = 3; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x5E000420; // RESERVED + opcode |= (imm5 << 16); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP ., .B[]")] + public void Dup_V_8B_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [Values(0u, 15u)] uint index, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + const int TestSize = 0; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x0E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP ., .H[]")] + public void Dup_V_4H_8H([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H_))] ulong z, + [ValueSource(nameof(_4H_))] ulong a, + [Values(0u, 7u)] uint index, + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + const int TestSize = 1; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x0E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP ., .S[]")] + public void Dup_V_2S_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_2S_))] ulong a, + [Values(0u, 1u, 2u, 3u)] uint index, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + const int TestSize = 2; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x0E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("DUP ., .D[]")] + public void Dup_V_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [Values(0u, 1u)] uint index, + [Values(0b1u)] uint q) // <2D> + { + const int TestSize = 3; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x0E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .B[], W")] + public void Ins_Gp_WB([Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_W_))] uint wn, + [Values(0u, 15u)] uint index) + { + const int TestSize = 0; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x4E001C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcode, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .H[], W")] + public void Ins_Gp_WH([Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_4H_))] ulong z, + [ValueSource(nameof(_W_))] uint wn, + [Values(0u, 7u)] uint index) + { + const int TestSize = 1; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x4E001C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcode, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .S[], W")] + public void Ins_Gp_WS([Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_W_))] uint wn, + [Values(0u, 1u, 2u, 3u)] uint index) + { + const int TestSize = 2; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x4E001C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcode, x1: wn, x31: w31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .D[], X")] + public void Ins_Gp_XD([Values(0u)] uint rd, + [Values(1u, 31u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_X_))] ulong xn, + [Values(0u, 1u)] uint index) + { + const int TestSize = 3; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x4E001C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + + SingleOpcode(opcode, x1: xn, x31: x31, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .B[], .B[]")] + public void Ins_V_BB([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [Values(0u, 15u)] uint dstIndex, + [Values(0u, 15u)] uint srcIndex) + { + const int TestSize = 0; + + uint imm5 = (dstIndex << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + uint imm4 = (srcIndex << TestSize) & 0xFu; + + uint opcode = 0x6E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= (imm4 << 11); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .H[], .H[]")] + public void Ins_V_HH([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H_))] ulong z, + [ValueSource(nameof(_4H_))] ulong a, + [Values(0u, 7u)] uint dstIndex, + [Values(0u, 7u)] uint srcIndex) + { + const int TestSize = 1; + + uint imm5 = (dstIndex << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + uint imm4 = (srcIndex << TestSize) & 0xFu; + + uint opcode = 0x6E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= (imm4 << 11); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .S[], .S[]")] + public void Ins_V_SS([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_2S_))] ulong a, + [Values(0u, 1u, 2u, 3u)] uint dstIndex, + [Values(0u, 1u, 2u, 3u)] uint srcIndex) + { + const int TestSize = 2; + + uint imm5 = (dstIndex << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + uint imm4 = (srcIndex << TestSize) & 0xFu; + + uint opcode = 0x6E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= (imm4 << 11); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("INS .D[], .D[]")] + public void Ins_V_DD([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [Values(0u, 1u)] uint dstIndex, + [Values(0u, 1u)] uint srcIndex) + { + const int TestSize = 3; + + uint imm5 = (dstIndex << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + uint imm4 = (srcIndex << TestSize) & 0xFu; + + uint opcode = 0x6E000400; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + opcode |= (imm4 << 11); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMOV , .B[]")] + public void Smov_S_BW([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_8B_))] ulong a, + [Values(0u, 15u)] uint index) + { + const int TestSize = 0; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x0E002C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMOV , .H[]")] + public void Smov_S_HW([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_4H_))] ulong a, + [Values(0u, 7u)] uint index) + { + const int TestSize = 1; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x0E002C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMOV , .B[]")] + public void Smov_S_BX([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_8B_))] ulong a, + [Values(0u, 15u)] uint index) + { + const int TestSize = 0; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x4E002C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMOV , .H[]")] + public void Smov_S_HX([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_4H_))] ulong a, + [Values(0u, 7u)] uint index) + { + const int TestSize = 1; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x4E002C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SMOV , .S[]")] + public void Smov_S_SX([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_2S_))] ulong a, + [Values(0u, 1u, 2u, 3u)] uint index) + { + const int TestSize = 2; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x4E002C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UMOV , .B[]")] + public void Umov_S_BW([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_8B_))] ulong a, + [Values(0u, 15u)] uint index) + { + const int TestSize = 0; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x0E003C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UMOV , .H[]")] + public void Umov_S_HW([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_4H_))] ulong a, + [Values(0u, 7u)] uint index) + { + const int TestSize = 1; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x0E003C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UMOV , .S[]")] + public void Umov_S_SW([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_2S_))] ulong a, + [Values(0u, 1u, 2u, 3u)] uint index) + { + const int TestSize = 2; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x0E003C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x0 = (ulong)TestContext.CurrentContext.Random.NextUInt() << 32; + uint w31 = TestContext.CurrentContext.Random.NextUInt(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x0: x0, x31: w31, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UMOV , .D[]")] + public void Umov_S_DX([Values(0u, 31u)] uint rd, + [Values(1u)] uint rn, + [ValueSource(nameof(_1D_))] ulong a, + [Values(0u, 1u)] uint index) + { + const int TestSize = 3; + + uint imm5 = (index << (TestSize + 1) | 1u << TestSize) & 0x1Fu; + + uint opcode = 0x4E003C00; // RESERVED + opcode |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= (imm5 << 16); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcode, x31: x31, v1: v1); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdLogical32.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdLogical32.cs new file mode 100644 index 00000000..819d9300 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdLogical32.cs @@ -0,0 +1,162 @@ +#define SimdLogical32 + +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdLogical32")] + public sealed class CpuTestSimdLogical32 : CpuTest32 + { +#if SimdLogical32 + + #region "ValueSource (Types)" + private static ulong[] _8B4H2S_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + #endregion + + #region "ValueSource (Opcodes)" + private static uint[] _Vbic_Vbif_Vbit_Vbsl_Vand_Vorn_Vorr_Veor_I_() + { + return new[] + { + 0xf2100110u, // VBIC D0, D0, D0 + 0xf3300110u, // VBIF D0, D0, D0 + 0xf3200110u, // VBIT D0, D0, D0 + 0xf3100110u, // VBSL D0, D0, D0 + 0xf2000110u, // VAND D0, D0, D0 + 0xf2300110u, // VORN D0, D0, D0 + 0xf2200110u, // VORR D0, D0, D0 + 0xf3000110u, // VEOR D0, D0, D0 + }; + } + + private static uint[] _Vbic_Vorr_II_() + { + return new[] + { + 0xf2800130u, // VBIC.I32 D0, #0 (A1) + 0xf2800930u, // VBIC.I16 D0, #0 (A2) + 0xf2800110u, // VORR.I32 D0, #0 (A1) + 0xf2800910u, // VORR.I16 D0, #0 (A2) + }; + } + #endregion + + [Test, Pairwise] + public void Vbic_Vbif_Vbit_Vbsl_Vand_Vorn_Vorr_Veor_I([ValueSource(nameof(_Vbic_Vbif_Vbit_Vbsl_Vand_Vorn_Vorr_Veor_I_))] uint opcode, + [Range(0u, 5u)] uint rd, + [Range(0u, 5u)] uint rn, + [Range(0u, 5u)] uint rm, + [Values(ulong.MinValue, ulong.MaxValue)] ulong z, + [Values(ulong.MinValue, ulong.MaxValue)] ulong a, + [Values(ulong.MinValue, ulong.MaxValue)] ulong b, + [Values] bool q) + { + if (q) + { + opcode |= 1 << 6; + + rd >>= 1; + rd <<= 1; + rn >>= 1; + rn <<= 1; + rm >>= 1; + rm <<= 1; + } + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Vbic_Vorr_II([ValueSource(nameof(_Vbic_Vorr_II_))] uint opcode, + [Values(0u, 1u)] uint rd, + [Values(ulong.MinValue, ulong.MaxValue)] ulong z, + [Values(byte.MinValue, byte.MaxValue)] byte imm, + [Values(0u, 1u, 2u, 3u)] uint cMode, + [Values] bool q) + { + if ((opcode & 0x800) != 0) // cmode<3> == '1' (A2) + { + cMode &= 1; + } + + if (q) + { + opcode |= 1 << 6; + + rd >>= 1; + rd <<= 1; + } + + opcode |= ((uint)imm & 0xf) << 0; + opcode |= ((uint)imm & 0x70) << 12; + opcode |= ((uint)imm & 0x80) << 17; + opcode |= (cMode & 0x3) << 9; + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + + V128 v0 = MakeVectorE0E1(z, ~z); + + SingleOpcode(opcode, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VTST.
, , ")] + public void Vtst([Range(0u, 5u)] uint rd, + [Range(0u, 5u)] uint rn, + [Range(0u, 5u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0u, 1u, 2u)] uint size, + [Values] bool q) + { + uint opcode = 0xf2000810u; // VTST.8 D0, D0, D0 + + if (q) + { + opcode |= 1 << 6; + + rd >>= 1; + rd <<= 1; + rn >>= 1; + rn <<= 1; + rm >>= 1; + rm <<= 1; + } + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + opcode |= (size & 0x3) << 20; + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdMemory32.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdMemory32.cs new file mode 100644 index 00000000..d59e963b --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdMemory32.cs @@ -0,0 +1,340 @@ +#define SimdMemory32 + +using ARMeilleure.State; +using NUnit.Framework; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdMemory32")] + public sealed class CpuTestSimdMemory32 : CpuTest32 + { + private static readonly uint _testOffset = DataBaseAddress + 0x500; +#if SimdMemory32 + + private readonly uint[] _ldStModes = + { + // LD1 + 0b0111, + 0b1010, + 0b0110, + 0b0010, + + // LD2 + 0b1000, + 0b1001, + 0b0011, + + // LD3 + 0b0100, + 0b0101, + + // LD4 + 0b0000, + 0b0001, + }; + + [Test, Pairwise, Description("VLDn. , [ {:}]{ /!/, } (single n element structure)")] + public void Vldn_Single([Values(0u, 1u, 2u)] uint size, + [Values(0u, 13u)] uint rn, + [Values(1u, 13u, 15u)] uint rm, + [Values(0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u)] uint vd, + [Range(0u, 7u)] uint index, + [Range(0u, 3u)] uint n, + [Values(0x0u)] uint offset) + { + var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize()); + SetWorkingMemory(0, data); + + uint opcode = 0xf4a00000u; // VLD1.8 {D0[0]}, [R0], R0 + + opcode |= ((size & 3) << 10) | ((rn & 15) << 16) | (rm & 15); + + uint indexAlign = (index << (int)(1 + size)) & 15; + + opcode |= (indexAlign) << 4; + + opcode |= ((vd & 0x10) << 18); + opcode |= ((vd & 0xf) << 12); + + opcode |= (n & 3) << 8; // LD1 is 0, LD2 is 1 etc. + + SingleOpcode(opcode, r0: _testOffset, r1: offset, sp: _testOffset); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VLDn. , [ {:}]{ /!/, } (all lanes)")] + public void Vldn_All([Values(0u, 13u)] uint rn, + [Values(1u, 13u, 15u)] uint rm, + [Values(0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u)] uint vd, + [Range(0u, 3u)] uint n, + [Range(0u, 2u)] uint size, + [Values] bool t, + [Values(0x0u)] uint offset) + { + var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize()); + SetWorkingMemory(0, data); + + uint opcode = 0xf4a00c00u; // VLD1.8 {D0[0]}, [R0], R0 + + opcode |= ((size & 3) << 6) | ((rn & 15) << 16) | (rm & 15); + + opcode |= ((vd & 0x10) << 18); + opcode |= ((vd & 0xf) << 12); + + opcode |= (n & 3) << 8; // LD1 is 0, LD2 is 1 etc. + if (t) + { + opcode |= 1 << 5; + } + + SingleOpcode(opcode, r0: _testOffset, r1: offset, sp: _testOffset); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VLDn. , [ {:}]{ /!/, } (multiple n element structures)")] + public void Vldn_Pair([Values(0u, 1u, 2u, 3u)] uint size, + [Values(0u, 13u)] uint rn, + [Values(1u, 13u, 15u)] uint rm, + [Values(0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u)] uint vd, + [Range(0u, 10u)] uint mode, + [Values(0x0u)] uint offset) + { + var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize()); + SetWorkingMemory(0, data); + + uint opcode = 0xf4200000u; // VLD4.8 {D0, D1, D2, D3}, [R0], R0 + + if (mode > 3 && size == 3) + { + // A size of 3 is only valid for VLD1. + size = 2; + } + + opcode |= ((size & 3) << 6) | ((rn & 15) << 16) | (rm & 15) | (_ldStModes[mode] << 8); + + opcode |= ((vd & 0x10) << 18); + opcode |= ((vd & 0xf) << 12); + + SingleOpcode(opcode, r0: _testOffset, r1: offset, sp: _testOffset); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VSTn. , [ {:}]{ /!/, } (single n element structure)")] + public void Vstn_Single([Values(0u, 1u, 2u)] uint size, + [Values(0u, 13u)] uint rn, + [Values(1u, 13u, 15u)] uint rm, + [Values(0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u)] uint vd, + [Range(0u, 7u)] uint index, + [Range(0u, 3u)] uint n, + [Values(0x0u)] uint offset) + { + var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize()); + SetWorkingMemory(0, data); + + (V128 vec1, V128 vec2, V128 vec3, V128 vec4) = GenerateTestVectors(); + + uint opcode = 0xf4800000u; // VST1.8 {D0[0]}, [R0], R0 + + opcode |= ((size & 3) << 10) | ((rn & 15) << 16) | (rm & 15); + + uint indexAlign = (index << (int)(1 + size)) & 15; + + opcode |= (indexAlign) << 4; + + opcode |= ((vd & 0x10) << 18); + opcode |= ((vd & 0xf) << 12); + + opcode |= (n & 3) << 8; // ST1 is 0, ST2 is 1 etc. + + SingleOpcode(opcode, r0: _testOffset, r1: offset, v1: vec1, v2: vec2, v3: vec3, v4: vec4, sp: _testOffset); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VSTn. , [ {:}]{ /!/, } (multiple n element structures)")] + public void Vstn_Pair([Values(0u, 1u, 2u, 3u)] uint size, + [Values(0u, 13u)] uint rn, + [Values(1u, 13u, 15u)] uint rm, + [Values(0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u)] uint vd, + [Range(0u, 10u)] uint mode, + [Values(0x0u)] uint offset) + { + var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize()); + SetWorkingMemory(0, data); + + (V128 vec1, V128 vec2, V128 vec3, V128 vec4) = GenerateTestVectors(); + + uint opcode = 0xf4000000u; // VST4.8 {D0, D1, D2, D3}, [R0], R0 + + if (mode > 3 && size == 3) + { + // A size of 3 is only valid for VST1. + size = 2; + } + + opcode |= ((size & 3) << 6) | ((rn & 15) << 16) | (rm & 15) | (_ldStModes[mode] << 8); + + opcode |= ((vd & 0x10) << 18); + opcode |= ((vd & 0xf) << 12); + + SingleOpcode(opcode, r0: _testOffset, r1: offset, v1: vec1, v2: vec2, v3: vec3, v4: vec4, sp: _testOffset); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VLDM. {!}, ")] + public void Vldm([Values(0u, 13u)] uint rn, + [Values(0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u)] uint vd, + [Range(0u, 2u)] uint mode, + [Values(0x1u, 0x32u)] uint regs, + [Values] bool single) + { + var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize()); + SetWorkingMemory(0, data); + + uint opcode = 0xec100a00u; // VST4.8 {D0, D1, D2, D3}, [R0], R0 + + uint[] vldmModes = + { + // Note: 3rd 0 leaves a space for "D". + 0b0100, // Increment after. + 0b0101, // Increment after. (!) + 0b1001, // Decrement before. (!) + }; + + opcode |= ((vldmModes[mode] & 15) << 21); + opcode |= ((rn & 15) << 16); + + opcode |= ((vd & 0x10) << 18); + opcode |= ((vd & 0xf) << 12); + + opcode |= ((uint)(single ? 0 : 1) << 8); + + if (!single) + { + regs <<= 1; // Low bit must be 0 - must be even number of registers. + } + + uint regSize = single ? 1u : 2u; + + if (vd + (regs / regSize) > 32) // Can't address further than S31 or D31. + { + regs -= (vd + (regs / regSize)) - 32; + } + + if (regs / regSize > 16) // Can't do more than 16 registers at a time. + { + regs = 16 * regSize; + } + + opcode |= regs & 0xff; + + SingleOpcode(opcode, r0: _testOffset, sp: _testOffset); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VLDR. , [ {, #{+/-}}]")] + public void Vldr([Values(2u, 3u)] uint size, // FP16 is not supported for now + [Values(0u)] uint rn, + [Values(0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u)] uint sd, + [Values(0x0u)] uint imm, + [Values] bool sub) + { + var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize()); + SetWorkingMemory(0, data); + + uint opcode = 0xed900a00u; // VLDR.32 S0, [R0, #0] + opcode |= ((size & 3) << 8) | ((rn & 15) << 16); + + if (sub) + { + opcode &= ~(uint)(1 << 23); + } + + if (size == 2) + { + opcode |= ((sd & 0x1) << 22); + opcode |= ((sd & 0x1e) << 11); + } + else + { + opcode |= ((sd & 0x10) << 18); + opcode |= ((sd & 0xf) << 12); + } + opcode |= imm & 0xff; + + SingleOpcode(opcode, r0: _testOffset); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VSTR. , [ {, #{+/-}}]")] + public void Vstr([Values(2u, 3u)] uint size, // FP16 is not supported for now + [Values(0u)] uint rn, + [Values(0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u)] uint sd, + [Values(0x0u)] uint imm, + [Values] bool sub) + { + var data = GenerateVectorSequence((int)MemoryBlock.GetPageSize()); + SetWorkingMemory(0, data); + + uint opcode = 0xed800a00u; // VSTR.32 S0, [R0, #0] + opcode |= ((size & 3) << 8) | ((rn & 15) << 16); + + if (sub) + { + opcode &= ~(uint)(1 << 23); + } + + if (size == 2) + { + opcode |= ((sd & 0x1) << 22); + opcode |= ((sd & 0x1e) << 11); + } + else + { + opcode |= ((sd & 0x10) << 18); + opcode |= ((sd & 0xf) << 12); + } + opcode |= imm & 0xff; + + (V128 vec1, V128 vec2, _, _) = GenerateTestVectors(); + + SingleOpcode(opcode, r0: _testOffset, v0: vec1, v1: vec2); + + CompareAgainstUnicorn(); + } + + private static (V128, V128, V128, V128) GenerateTestVectors() + { + return ( + new V128(-12.43f, 1872.23f, 4456.23f, -5622.2f), + new V128(0.0f, float.NaN, float.PositiveInfinity, float.NegativeInfinity), + new V128(1.23e10f, -0.0f, -0.123f, 0.123f), + new V128(float.Epsilon, 3.5f, 925.23f, -104.9f) + ); + } + + private static byte[] GenerateVectorSequence(int length) + { + int floatLength = length >> 2; + float[] data = new float[floatLength]; + + for (int i = 0; i < floatLength; i++) + { + data[i] = i + (i / 9f); + } + + var result = new byte[length]; + Buffer.BlockCopy(data, 0, result, 0, result.Length); + return result; + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdMov32.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdMov32.cs new file mode 100644 index 00000000..85f77fff --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdMov32.cs @@ -0,0 +1,610 @@ +#define SimdMov32 + +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdMov32")] + public sealed class CpuTestSimdMov32 : CpuTest32 + { +#if SimdMov32 + private const int RndCntImm = 2; + + [Test, Pairwise, Description("VMOV.I
, #")] + public void Movi_V([Range(0u, 10u)] uint variant, + [Values(0u, 1u, 2u, 3u)] uint vd, + [Values(0x0u)] uint imm, + [Values] bool q) + { + uint[] variants = + { + // I32 + 0b0000_0, + 0b0010_0, + 0b0100_0, + 0b0110_0, + + // I16 + 0b1000_0, + 0b1010_0, + + // DT + 0b1100_0, + 0b1101_0, + 0b1110_0, + 0b1111_0, + + 0b1110_1, + }; + + uint opcode = 0xf2800010u; // VMOV.I32 D0, #0 + + uint cmodeOp = variants[variant]; + + if (q) + { + vd <<= 1; + } + + opcode |= ((cmodeOp & 1) << 5) | ((cmodeOp & 0x1e) << 7); + opcode |= (q ? 1u : 0u) << 6; + opcode |= (imm & 0xf) | ((imm & 0x70) << 12) | ((imm & 0x80) << 16); + + opcode |= (vd & 0x10) << 18; + opcode |= (vd & 0xf) << 12; + + SingleOpcode(opcode); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VMOV.F , #")] + public void Movi_S([Range(2u, 3u)] uint size, + [Values(0u, 1u, 2u, 3u)] uint vd, + [Values(0x0u)] uint imm) + { + uint opcode = 0xeeb00800u; + opcode |= (size & 3) << 8; + opcode |= (imm & 0xf) | ((imm & 0xf0) << 12); + + if (size == 2) + { + opcode |= ((vd & 0x1) << 22); + opcode |= ((vd & 0x1e) << 11); + } + else + { + opcode |= ((vd & 0x10) << 18); + opcode |= ((vd & 0xf) << 12); + } + + SingleOpcode(opcode); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VMOV , ")] + public void Mov_GP([Values(0u, 1u, 2u, 3u)] uint vn, + [Values(0u, 1u, 2u, 3u)] uint rt, + [Random(RndCntImm)] uint valueRn, + [Random(RndCntImm)] ulong valueVn1, + [Random(RndCntImm)] ulong valueVn2, + [Values] bool op) + { + uint opcode = 0xee000a10u; // VMOV S0, R0 + opcode |= (vn & 1) << 7; + opcode |= (vn & 0x1e) << 15; + opcode |= (rt & 0xf) << 12; + + if (op) + { + opcode |= 1 << 20; + } + + SingleOpcode(opcode, r0: valueRn, r1: valueRn, r2: valueRn, r3: valueRn, v0: new V128(valueVn1, valueVn2)); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VMOV. , ")] + public void Mov_GP_Elem([Range(0u, 7u)] uint vn, + [Values(0u, 1u, 2u, 3u)] uint rt, + [Range(0u, 2u)] uint size, + [Range(0u, 7u)] uint index, + [Random(1)] uint valueRn, + [Random(1)] ulong valueVn1, + [Random(1)] ulong valueVn2, + [Values] bool op, + [Values] bool u) + { + uint opcode = 0xee000b10u; // VMOV.32 D0[0], R0 + + uint opEncode = 0b01000; + switch (size) + { + case 0: + opEncode = (0b1000) | index & 7; + break; + case 1: + opEncode = (0b0001) | ((index & 3) << 1); + break; + case 2: + opEncode = (index & 1) << 2; + break; + } + + opcode |= ((opEncode >> 2) << 21) | ((opEncode & 3) << 5); + + opcode |= (vn & 0x10) << 3; + opcode |= (vn & 0xf) << 16; + opcode |= (rt & 0xf) << 12; + + if (op) + { + opcode |= 1 << 20; + if (u && size != 2) + { + opcode |= 1 << 23; + } + } + + SingleOpcode(opcode, r0: valueRn, r1: valueRn, r2: valueRn, r3: valueRn, v0: new V128(valueVn1, valueVn2), v1: new V128(valueVn2, valueVn1)); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("(VMOV , , ), (VMOV , , )")] + public void Mov_GP_D([Values(0u, 1u, 2u, 3u)] uint vm, + [Values(0u, 1u, 2u, 3u)] uint rt, + [Values(0u, 1u, 2u, 3u)] uint rt2, + [Random(RndCntImm)] uint valueRt1, + [Random(RndCntImm)] uint valueRt2, + [Random(RndCntImm)] ulong valueVn1, + [Random(RndCntImm)] ulong valueVn2, + [Values] bool op) + { + uint opcode = 0xec400b10u; // VMOV D0, R0, R0 + opcode |= (vm & 0x10) << 1; + opcode |= (vm & 0xf); + opcode |= (rt & 0xf) << 12; + opcode |= (rt2 & 0xf) << 16; + + if (op) + { + opcode |= 1 << 20; + } + + SingleOpcode(opcode, r0: valueRt1, r1: valueRt2, r2: valueRt1, r3: valueRt2, v0: new V128(valueVn1, valueVn2)); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("(VMOV , , , ), (VMOV , , , )")] + public void Mov_GP_2([Range(0u, 7u)] uint vm, + [Values(0u, 1u, 2u, 3u)] uint rt, + [Values(0u, 1u, 2u, 3u)] uint rt2, + [Random(RndCntImm)] uint valueRt1, + [Random(RndCntImm)] uint valueRt2, + [Random(RndCntImm)] ulong valueVn1, + [Random(RndCntImm)] ulong valueVn2, + [Values] bool op) + { + uint opcode = 0xec400a10u; // VMOV S0, S1, R0, R0 + opcode |= (vm & 1) << 5; + opcode |= (vm & 0x1e) >> 1; + opcode |= (rt & 0xf) << 12; + opcode |= (rt2 & 0xf) << 16; + + if (op) + { + opcode |= 1 << 20; + } + + SingleOpcode(opcode, r0: valueRt1, r1: valueRt2, r2: valueRt1, r3: valueRt2, v0: new V128(valueVn1, valueVn2), v1: new V128(valueVn2, valueVn1)); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VMOVN.
, ")] + public void Movn_V([Range(0u, 1u, 2u)] uint size, + [Values(0u, 1u, 2u, 3u)] uint vd, + [Values(0u, 2u, 4u, 8u)] uint vm) + { + uint opcode = 0xf3b20200u; // VMOVN.I16 D0, Q0 + + opcode |= (size & 0x3) << 18; + opcode |= ((vm & 0x10) << 1); + opcode |= ((vm & 0xf) << 0); + + opcode |= ((vd & 0x10) << 18); + opcode |= ((vd & 0xf) << 12); + + V128 v0 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v1 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v2 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v3 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, v3: v3); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VMOVL. , ")] + public void Vmovl([Values(0u, 1u, 2u, 3u)] uint vm, + [Values(0u, 2u, 4u, 6u)] uint vd, + [Values(1u, 2u, 4u)] uint imm3H, + [Values] bool u) + { + // This is not VMOVL because imm3H = 0, but once + // we shift in the imm3H value it turns into VMOVL. + uint opcode = 0xf2800a10u; // VMOV.I16 D0, #0 + + opcode |= (vm & 0x10) << 1; + opcode |= (vm & 0xf); + opcode |= (vd & 0x10) << 18; + opcode |= (vd & 0xf) << 12; + opcode |= (imm3H & 0x7) << 19; + if (u) + { + opcode |= 1 << 24; + } + + V128 v0 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v1 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v2 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v3 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, v3: v3); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VMVN. , ")] + public void Vmvn([Range(0u, 1u, 2u)] uint size, + [Values(0u, 1u, 2u, 3u)] uint vd, + [Values(0u, 2u, 4u, 8u)] uint vm, + [Values] bool q) + { + uint opcode = 0xf3b00580u; // VMVN D0, D0 + + if (q) + { + opcode |= 1 << 6; + vm <<= 1; + vd <<= 1; + } + + opcode |= (size & 0x3) << 18; + opcode |= (vm & 0x10) << 1; + opcode |= (vm & 0xf) << 0; + + opcode |= (vd & 0x10) << 18; + opcode |= (vd & 0xf) << 12; + + V128 v0 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v1 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v2 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v3 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, v3: v3); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VMVN.I
, #")] + public void Mvni_V([Range(0u, 7u)] uint variant, + [Values(0u, 1u, 2u, 3u)] uint vd, + [Values(0x0u)] uint imm, + [Values] bool q) + { + uint[] variants = + { + // I32 + 0b0000, + 0b0010, + 0b0100, + 0b0110, + + // I16 + 0b1000, + 0b1010, + + // I32 + 0b1100, + 0b1101, + }; + + uint opcode = 0xf2800030u; // VMVN.I32 D0, #0 + + uint cmodeOp = variants[variant]; + + if (q) + { + vd <<= 1; + } + + opcode |= (cmodeOp & 0xf) << 8; + opcode |= (q ? 1u : 0u) << 6; + opcode |= (imm & 0xf) | ((imm & 0x70) << 12) | ((imm & 0x80) << 16); + + opcode |= (vd & 0x10) << 18; + opcode |= (vd & 0xf) << 12; + + SingleOpcode(opcode); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VTRN. , ")] + public void Vtrn([Values(0u, 1u, 2u, 3u)] uint vm, + [Values(0u, 1u, 2u, 3u)] uint vd, + [Values(0u, 1u, 2u)] uint size, + [Values] bool q) + { + uint opcode = 0xf3b20080u; // VTRN.8 D0, D0 + if (vm == vd) + { + return; // Undefined. + } + + if (q) + { + opcode |= 1 << 6; + vd <<= 1; + vm <<= 1; + } + + opcode |= (vm & 0x10) << 1; + opcode |= (vm & 0xf); + opcode |= (vd & 0x10) << 18; + opcode |= (vd & 0xf) << 12; + opcode |= (size & 0x3) << 18; + + V128 v0 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v1 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v2 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v3 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, v3: v3); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VZIP. , ")] + public void Vzip([Values(0u, 1u, 2u, 3u)] uint vm, + [Values(0u, 1u, 2u, 3u)] uint vd, + [Values(0u, 1u, 2u)] uint size, + [Values] bool q) + { + uint opcode = 0xf3b20180u; // VZIP.8 D0, D0 + if (vm == vd || (size == 2 && !q)) + { + return; // Undefined. + } + + if (q) + { + opcode |= 1 << 6; + vd <<= 1; + vm <<= 1; + } + + opcode |= (vm & 0x10) << 1; + opcode |= (vm & 0xf); + opcode |= (vd & 0x10) << 18; + opcode |= (vd & 0xf) << 12; + opcode |= (size & 0x3) << 18; + + V128 v0 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v1 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v2 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v3 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, v3: v3); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VUZP. , ")] + public void Vuzp([Values(0u, 1u, 2u, 3u)] uint vm, + [Values(0u, 1u, 2u, 3u)] uint vd, + [Values(0u, 1u, 2u)] uint size, + [Values] bool q) + { + uint opcode = 0xf3b20100u; // VUZP.8 d0, d0 + if (vm == vd || (size == 2 && !q)) + { + return; // Undefined. + } + + if (q) + { + opcode |= 1 << 6; + vd <<= 1; + vm <<= 1; + } + + opcode |= (vm & 0x10) << 1; + opcode |= (vm & 0xf); + opcode |= (vd & 0x10) << 18; + opcode |= (vd & 0xf) << 12; + opcode |= (size & 0x3) << 18; + + V128 v0 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v1 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v2 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v3 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, v3: v3); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VTBL.8
, {list}, ")] + public void Vtbl([Range(0u, 6u)] uint vm, // Indices, include potentially invalid. + [Range(4u, 12u)] uint vn, // Selection. + [Values(0u, 1u)] uint vd, // Destinations. + [Range(0u, 3u)] uint length, + [Values] bool x) + { + uint opcode = 0xf3b00800u; // VTBL.8 D0, {D0}, D0 + if (vn + length > 31) + { + return; // Undefined. + } + + if (x) + { + opcode |= 1 << 6; + } + opcode |= (vm & 0x10) << 1; + opcode |= (vm & 0xf); + opcode |= (vd & 0x10) << 18; + opcode |= (vd & 0xf) << 12; + + opcode |= (vn & 0x10) << 3; + opcode |= (vn & 0xf) << 16; + opcode |= (length & 0x3) << 8; + + var rnd = TestContext.CurrentContext.Random; + V128 v2 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v3 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v4 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v5 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + + byte maxIndex = (byte)(length * 8 - 1); + byte[] b0 = new byte[16]; + byte[] b1 = new byte[16]; + for (int i = 0; i < 16; i++) + { + b0[i] = rnd.NextByte(maxIndex); + b1[i] = rnd.NextByte(maxIndex); + } + + V128 v0 = new(b0); + V128 v1 = new(b1); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, v3: v3, v4: v4, v5: v5); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VEXT.8 {,} , , #")] + public void Vext([Values(0u, 1u, 2u, 3u)] uint vm, + [Values(0u, 1u, 2u, 3u)] uint vn, + [Values(0u, 1u, 2u, 3u)] uint vd, + [Values(0u, 15u)] uint imm4, + [Values] bool q) + { + uint opcode = 0xf2b00000; // VEXT.32 D0, D0, D0, #0 + + if (q) + { + opcode |= 1 << 6; + vd <<= 1; + vm <<= 1; + vn <<= 1; + } + else if (imm4 > 7) + { + return; // Undefined. + } + opcode |= (vm & 0x10) << 1; + opcode |= (vm & 0xf); + opcode |= (vd & 0x10) << 18; + opcode |= (vd & 0xf) << 12; + opcode |= (vn & 0x10) << 3; + opcode |= (vn & 0xf) << 16; + opcode |= (imm4 & 0xf) << 8; + + V128 v0 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v1 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v2 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v3 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, v3: v3); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VDUP. , ")] + public void Vdup_GP([Values(0u, 1u, 2u, 3u)] uint vd, + [Values(0u, 1u, 2u, 3u)] uint rt, + [Values(0u, 1u, 2u)] uint size, + [Random(RndCntImm)] uint valueRn, + [Random(RndCntImm)] ulong valueVn1, + [Random(RndCntImm)] ulong valueVn2, + [Values] bool q) + { + uint opcode = 0xee800b10; // VDUP.32 d0, r0 + + if (q) + { + opcode |= 1 << 21; + vd <<= 1; + } + + opcode |= (vd & 0x10) << 3; + opcode |= (vd & 0xf) << 16; + opcode |= (rt & 0xf) << 12; + + opcode |= (size & 1) << 5; // E + opcode |= (size & 2) << 21; // B + + V128 v1 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + + SingleOpcode(opcode, r0: valueRn, r1: valueRn, r2: valueRn, r3: valueRn, v0: new V128(valueVn1, valueVn2), v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VDUP. , ")] + public void Vdup_S([Values(0u, 1u, 2u, 3u)] uint vd, + [Values(0u, 1u, 2u, 3u)] uint vm, + [Values(0u, 1u, 2u)] uint size, + [Range(0u, 7u)] uint index, + [Random(RndCntImm)] ulong valueVn1, + [Random(RndCntImm)] ulong valueVn2, + [Values] bool q) + { + uint opcode = 0xf3b00c00; + + if (q) + { + opcode |= 1 << 6; + vd <<= 1; + } + + opcode |= (vd & 0x10) << 18; + opcode |= (vd & 0xf) << 12; + opcode |= (vm & 0x10) << 1; + opcode |= (vm & 0xf); + + uint imm4 = 0; + switch (size) + { + case 0: + imm4 |= 0b0100 | ((index & 1) << 3); + break; + case 1: + imm4 |= 0b0010 | ((index & 3) << 2); + break; + case 2: + imm4 |= 0b0001 | ((index & 7) << 1); + break; + } + + opcode |= imm4 << 16; + + V128 v1 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v2 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + V128 v3 = new(TestContext.CurrentContext.Random.NextULong(), TestContext.CurrentContext.Random.NextULong()); + + SingleOpcode(opcode, v0: new V128(valueVn1, valueVn2), v1: v1, v2: v2, v3: v3); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs new file mode 100644 index 00000000..207f7608 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs @@ -0,0 +1,4057 @@ +#define SimdReg + +using ARMeilleure.State; +using NUnit.Framework; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdReg")] + public sealed class CpuTestSimdReg : CpuTest + { +#if SimdReg + + #region "ValueSource (Types)" + private static ulong[] _1B1H1S1D_() + { + return new[] { + 0x0000000000000000ul, 0x000000000000007Ful, + 0x0000000000000080ul, 0x00000000000000FFul, + 0x0000000000007FFFul, 0x0000000000008000ul, + 0x000000000000FFFFul, 0x000000007FFFFFFFul, + 0x0000000080000000ul, 0x00000000FFFFFFFFul, + 0x7FFFFFFFFFFFFFFFul, 0x8000000000000000ul, + 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _1D_() + { + return new[] { + 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _1H1S_() + { + return new[] { + 0x0000000000000000ul, 0x0000000000007FFFul, + 0x0000000000008000ul, 0x000000000000FFFFul, + 0x000000007FFFFFFFul, 0x0000000080000000ul, + 0x00000000FFFFFFFFul, + }; + } + + private static ulong[] _4H2S_() + { + return new[] { + 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _4H2S1D_() + { + return new[] { + 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _8B_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _8B1D_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _8B4H2S_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _8B4H2S1D_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static IEnumerable _1S_F_() + { + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + } + } + + private static IEnumerable _2S_F_() + { + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + } + } + + private static IEnumerable _1D_F_() + { + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalD(); + ulong rnd2 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + } + } + #endregion + + #region "ValueSource (Opcodes)" + private static uint[] _F_Abd_Add_Div_Mul_Mulx_Nmul_Sub_S_S_() + { + return new[] + { + 0x7EA2D420u, // FABD S0, S1, S2 + 0x1E222820u, // FADD S0, S1, S2 + 0x1E221820u, // FDIV S0, S1, S2 + 0x1E220820u, // FMUL S0, S1, S2 + 0x5E22DC20u, // FMULX S0, S1, S2 + 0x1E228820u, // FNMUL S0, S1, S2 + 0x1E223820u, // FSUB S0, S1, S2 + }; + } + + private static uint[] _F_Abd_Add_Div_Mul_Mulx_Nmul_Sub_S_D_() + { + return new[] + { + 0x7EE2D420u, // FABD D0, D1, D2 + 0x1E622820u, // FADD D0, D1, D2 + 0x1E621820u, // FDIV D0, D1, D2 + 0x1E620820u, // FMUL D0, D1, D2 + 0x5E62DC20u, // FMULX D0, D1, D2 + 0x1E628820u, // FNMUL D0, D1, D2 + 0x1E623820u, // FSUB D0, D1, D2 + }; + } + + private static uint[] _F_Abd_Add_Div_Mul_Mulx_Sub_P_V_2S_4S_() + { + return new[] + { + 0x2EA0D400u, // FABD V0.2S, V0.2S, V0.2S + 0x0E20D400u, // FADD V0.2S, V0.2S, V0.2S + 0x2E20D400u, // FADDP V0.2S, V0.2S, V0.2S + 0x2E20FC00u, // FDIV V0.2S, V0.2S, V0.2S + 0x2E20DC00u, // FMUL V0.2S, V0.2S, V0.2S + 0x0E20DC00u, // FMULX V0.2S, V0.2S, V0.2S + 0x0EA0D400u, // FSUB V0.2S, V0.2S, V0.2S + }; + } + + private static uint[] _F_Abd_Add_Div_Mul_Mulx_Sub_P_V_2D_() + { + return new[] + { + 0x6EE0D400u, // FABD V0.2D, V0.2D, V0.2D + 0x4E60D400u, // FADD V0.2D, V0.2D, V0.2D + 0x6E60D400u, // FADDP V0.2D, V0.2D, V0.2D + 0x6E60FC00u, // FDIV V0.2D, V0.2D, V0.2D + 0x6E60DC00u, // FMUL V0.2D, V0.2D, V0.2D + 0x4E60DC00u, // FMULX V0.2D, V0.2D, V0.2D + 0x4EE0D400u, // FSUB V0.2D, V0.2D, V0.2D + }; + } + + private static uint[] _F_AcCm_EqGeGt_S_S_() + { + return new[] + { + 0x7E22EC20u, // FACGE S0, S1, S2 + 0x7EA2EC20u, // FACGT S0, S1, S2 + 0x5E22E420u, // FCMEQ S0, S1, S2 + 0x7E22E420u, // FCMGE S0, S1, S2 + 0x7EA2E420u, // FCMGT S0, S1, S2 + }; + } + + private static uint[] _F_AcCm_EqGeGt_S_D_() + { + return new[] + { + 0x7E62EC20u, // FACGE D0, D1, D2 + 0x7EE2EC20u, // FACGT D0, D1, D2 + 0x5E62E420u, // FCMEQ D0, D1, D2 + 0x7E62E420u, // FCMGE D0, D1, D2 + 0x7EE2E420u, // FCMGT D0, D1, D2 + }; + } + + private static uint[] _F_AcCm_EqGeGt_V_2S_4S_() + { + return new[] + { + 0x2E20EC00u, // FACGE V0.2S, V0.2S, V0.2S + 0x2EA0EC00u, // FACGT V0.2S, V0.2S, V0.2S + 0x0E20E400u, // FCMEQ V0.2S, V0.2S, V0.2S + 0x2E20E400u, // FCMGE V0.2S, V0.2S, V0.2S + 0x2EA0E400u, // FCMGT V0.2S, V0.2S, V0.2S + }; + } + + private static uint[] _F_AcCm_EqGeGt_V_2D_() + { + return new[] + { + 0x6E60EC00u, // FACGE V0.2D, V0.2D, V0.2D + 0x6EE0EC00u, // FACGT V0.2D, V0.2D, V0.2D + 0x4E60E400u, // FCMEQ V0.2D, V0.2D, V0.2D + 0x6E60E400u, // FCMGE V0.2D, V0.2D, V0.2D + 0x6EE0E400u, // FCMGT V0.2D, V0.2D, V0.2D + }; + } + + private static uint[] _F_Cmp_Cmpe_S_S_() + { + return new[] + { + 0x1E222020u, // FCMP S1, S2 + 0x1E222030u, // FCMPE S1, S2 + }; + } + + private static uint[] _F_Cmp_Cmpe_S_D_() + { + return new[] + { + 0x1E622020u, // FCMP D1, D2 + 0x1E622030u, // FCMPE D1, D2 + }; + } + + private static uint[] _F_Madd_Msub_Nmadd_Nmsub_S_S_() + { + return new[] + { + 0x1F020C20u, // FMADD S0, S1, S2, S3 + 0x1F028C20u, // FMSUB S0, S1, S2, S3 + 0x1F220C20u, // FNMADD S0, S1, S2, S3 + 0x1F228C20u, // FNMSUB S0, S1, S2, S3 + }; + } + + private static uint[] _F_Madd_Msub_Nmadd_Nmsub_S_D_() + { + return new[] + { + 0x1F420C20u, // FMADD D0, D1, D2, D3 + 0x1F428C20u, // FMSUB D0, D1, D2, D3 + 0x1F620C20u, // FNMADD D0, D1, D2, D3 + 0x1F628C20u, // FNMSUB D0, D1, D2, D3 + }; + } + + private static uint[] _F_Max_Min_Nm_S_S_() + { + return new[] + { + 0x1E224820u, // FMAX S0, S1, S2 + 0x1E226820u, // FMAXNM S0, S1, S2 + 0x1E225820u, // FMIN S0, S1, S2 + 0x1E227820u, // FMINNM S0, S1, S2 + }; + } + + private static uint[] _F_Max_Min_Nm_S_D_() + { + return new[] + { + 0x1E624820u, // FMAX D0, D1, D2 + 0x1E626820u, // FMAXNM D0, D1, D2 + 0x1E625820u, // FMIN D0, D1, D2 + 0x1E627820u, // FMINNM D0, D1, D2 + }; + } + + private static uint[] _F_Max_Min_Nm_P_V_2S_4S_() + { + return new[] + { + 0x0E20F400u, // FMAX V0.2S, V0.2S, V0.2S + 0x0E20C400u, // FMAXNM V0.2S, V0.2S, V0.2S + 0x2E20C400u, // FMAXNMP V0.2S, V0.2S, V0.2S + 0x2E20F400u, // FMAXP V0.2S, V0.2S, V0.2S + 0x0EA0F400u, // FMIN V0.2S, V0.2S, V0.2S + 0x0EA0C400u, // FMINNM V0.2S, V0.2S, V0.2S + 0x2EA0C400u, // FMINNMP V0.2S, V0.2S, V0.2S + 0x2EA0F400u, // FMINP V0.2S, V0.2S, V0.2S + }; + } + + private static uint[] _F_Max_Min_Nm_P_V_2D_() + { + return new[] + { + 0x4E60F400u, // FMAX V0.2D, V0.2D, V0.2D + 0x4E60C400u, // FMAXNM V0.2D, V0.2D, V0.2D + 0x6E60C400u, // FMAXNMP V0.2D, V0.2D, V0.2D + 0x6E60F400u, // FMAXP V0.2D, V0.2D, V0.2D + 0x4EE0F400u, // FMIN V0.2D, V0.2D, V0.2D + 0x4EE0C400u, // FMINNM V0.2D, V0.2D, V0.2D + 0x6EE0C400u, // FMINNMP V0.2D, V0.2D, V0.2D + 0x6EE0F400u, // FMINP V0.2D, V0.2D, V0.2D + }; + } + + private static uint[] _F_Mla_Mls_V_2S_4S_() + { + return new[] + { + 0x0E20CC00u, // FMLA V0.2S, V0.2S, V0.2S + 0x0EA0CC00u, // FMLS V0.2S, V0.2S, V0.2S + }; + } + + private static uint[] _F_Mla_Mls_V_2D_() + { + return new[] + { + 0x4E60CC00u, // FMLA V0.2D, V0.2D, V0.2D + 0x4EE0CC00u, // FMLS V0.2D, V0.2D, V0.2D + }; + } + + private static uint[] _F_Recps_Rsqrts_S_S_() + { + return new[] + { + 0x5E22FC20u, // FRECPS S0, S1, S2 + 0x5EA2FC20u, // FRSQRTS S0, S1, S2 + }; + } + + private static uint[] _F_Recps_Rsqrts_S_D_() + { + return new[] + { + 0x5E62FC20u, // FRECPS D0, D1, D2 + 0x5EE2FC20u, // FRSQRTS D0, D1, D2 + }; + } + + private static uint[] _F_Recps_Rsqrts_V_2S_4S_() + { + return new[] + { + 0x0E20FC00u, // FRECPS V0.2S, V0.2S, V0.2S + 0x0EA0FC00u, // FRSQRTS V0.2S, V0.2S, V0.2S + }; + } + + private static uint[] _F_Recps_Rsqrts_V_2D_() + { + return new[] + { + 0x4E60FC00u, // FRECPS V0.2D, V0.2D, V0.2D + 0x4EE0FC00u, // FRSQRTS V0.2D, V0.2D, V0.2D + }; + } + + private static uint[] _Mla_Mls_Mul_V_8B_4H_2S_() + { + return new[] + { + 0x0E209400u, // MLA V0.8B, V0.8B, V0.8B + 0x2E209400u, // MLS V0.8B, V0.8B, V0.8B + 0x0E209C00u, // MUL V0.8B, V0.8B, V0.8B + }; + } + + private static uint[] _Mla_Mls_Mul_V_16B_8H_4S_() + { + return new[] + { + 0x4E209400u, // MLA V0.16B, V0.16B, V0.16B + 0x6E209400u, // MLS V0.16B, V0.16B, V0.16B + 0x4E209C00u, // MUL V0.16B, V0.16B, V0.16B + }; + } + + private static uint[] _Sha1c_Sha1m_Sha1p_Sha1su0_V_() + { + return new[] + { + 0x5E000000u, // SHA1C Q0, S0, V0.4S + 0x5E002000u, // SHA1M Q0, S0, V0.4S + 0x5E001000u, // SHA1P Q0, S0, V0.4S + 0x5E003000u, // SHA1SU0 V0.4S, V0.4S, V0.4S + }; + } + + private static uint[] _Sha256h_Sha256h2_Sha256su1_V_() + { + return new[] + { + 0x5E004000u, // SHA256H Q0, Q0, V0.4S + 0x5E005000u, // SHA256H2 Q0, Q0, V0.4S + 0x5E006000u, // SHA256SU1 V0.4S, V0.4S, V0.4S + }; + } + + private static uint[] _SU_Max_Min_P_V_() + { + return new[] + { + 0x0E206400u, // SMAX V0.8B, V0.8B, V0.8B + 0x0E20A400u, // SMAXP V0.8B, V0.8B, V0.8B + 0x0E206C00u, // SMIN V0.8B, V0.8B, V0.8B + 0x0E20AC00u, // SMINP V0.8B, V0.8B, V0.8B + 0x2E206400u, // UMAX V0.8B, V0.8B, V0.8B + 0x2E20A400u, // UMAXP V0.8B, V0.8B, V0.8B + 0x2E206C00u, // UMIN V0.8B, V0.8B, V0.8B + 0x2E20AC00u, // UMINP V0.8B, V0.8B, V0.8B + }; + } + + private static uint[] _SU_Mlal_Mlsl_Mull_V_8B8H_4H4S_2S2D_() + { + return new[] + { + 0x0E208000u, // SMLAL V0.8H, V0.8B, V0.8B + 0x0E20A000u, // SMLSL V0.8H, V0.8B, V0.8B + 0x0E20C000u, // SMULL V0.8H, V0.8B, V0.8B + 0x2E208000u, // UMLAL V0.8H, V0.8B, V0.8B + 0x2E20A000u, // UMLSL V0.8H, V0.8B, V0.8B + 0x2E20C000u, // UMULL V0.8H, V0.8B, V0.8B + }; + } + + private static uint[] _SU_Mlal_Mlsl_Mull_V_16B8H_8H4S_4S2D_() + { + return new[] + { + 0x4E208000u, // SMLAL2 V0.8H, V0.16B, V0.16B + 0x4E20A000u, // SMLSL2 V0.8H, V0.16B, V0.16B + 0x4E20C000u, // SMULL2 V0.8H, V0.16B, V0.16B + 0x6E208000u, // UMLAL2 V0.8H, V0.16B, V0.16B + 0x6E20A000u, // UMLSL2 V0.8H, V0.16B, V0.16B + 0x6E20C000u, // UMULL2 V0.8H, V0.16B, V0.16B + }; + } + + private static uint[] _ShlReg_S_D_() + { + return new[] + { + 0x5EE04400u, // SSHL D0, D0, D0 + 0x7EE04400u, // USHL D0, D0, D0 + }; + } + + private static uint[] _ShlReg_V_8B_4H_2S_() + { + return new[] + { + 0x0E205C00u, // SQRSHL V0.8B, V0.8B, V0.8B + 0x0E204C00u, // SQSHL V0.8B, V0.8B, V0.8B + 0x0E205400u, // SRSHL V0.8B, V0.8B, V0.8B + 0x0E204400u, // SSHL V0.8B, V0.8B, V0.8B + 0x2E205C00u, // UQRSHL V0.8B, V0.8B, V0.8B + 0x2E204C00u, // UQSHL V0.8B, V0.8B, V0.8B + 0x2E205400u, // URSHL V0.8B, V0.8B, V0.8B + 0x2E204400u, // USHL V0.8B, V0.8B, V0.8B + }; + } + + private static uint[] _ShlReg_V_16B_8H_4S_2D_() + { + return new[] + { + 0x4E205C00u, // SQRSHL V0.16B, V0.16B, V0.16B + 0x4E204C00u, // SQSHL V0.16B, V0.16B, V0.16B + 0x4E205400u, // SRSHL V0.16B, V0.16B, V0.16B + 0x4E204400u, // SSHL V0.16B, V0.16B, V0.16B + 0x6E205C00u, // UQRSHL V0.16B, V0.16B, V0.16B + 0x6E204C00u, // UQSHL V0.16B, V0.16B, V0.16B + 0x6E205400u, // URSHL V0.16B, V0.16B, V0.16B + 0x6E204400u, // USHL V0.16B, V0.16B, V0.16B + }; + } + #endregion + + private const int RndCnt = 2; + + private static readonly bool _noZeros = false; + private static readonly bool _noInfs = false; + private static readonly bool _noNaNs = false; + + [Test, Pairwise, Description("ADD , , ")] + public void Add_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [ValueSource(nameof(_1D_))] ulong b) + { + uint opcode = 0x5EE08400; // ADD D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADD ., ., .")] + public void Add_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E208400; // ADD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADD ., ., .")] + public void Add_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E208400; // ADD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDHN{2} ., ., .")] + public void Addhn_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [ValueSource(nameof(_4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> + { + uint opcode = 0x0E204000; // ADDHN V0.8B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDHN{2} ., ., .")] + public void Addhn_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [ValueSource(nameof(_4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> + { + uint opcode = 0x4E204000; // ADDHN2 V0.16B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDP ., ., .")] + public void Addp_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E20BC00; // ADDP V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ADDP ., ., .")] + public void Addp_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E20BC00; // ADDP V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("AND ., ., .")] + public void And_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x0E201C00; // AND V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("AND ., ., .")] + public void And_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x4E201C00; // AND V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("BIC ., ., .")] + public void Bic_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x0E601C00; // BIC V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("BIC ., ., .")] + public void Bic_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x4E601C00; // BIC V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("BIF ., ., .")] + public void Bif_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x2EE01C00; // BIF V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("BIF ., ., .")] + public void Bif_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x6EE01C00; // BIF V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("BIT ., ., .")] + public void Bit_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x2EA01C00; // BIT V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("BIT ., ., .")] + public void Bit_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x6EA01C00; // BIT V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("BSL ., ., .")] + public void Bsl_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x2E601C00; // BSL V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("BSL ., ., .")] + public void Bsl_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x6E601C00; // BSL V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMEQ , , ")] + public void Cmeq_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [ValueSource(nameof(_1D_))] ulong b) + { + uint opcode = 0x7EE08C00; // CMEQ D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMEQ ., ., .")] + public void Cmeq_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E208C00; // CMEQ V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMEQ ., ., .")] + public void Cmeq_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E208C00; // CMEQ V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGE , , ")] + public void Cmge_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [ValueSource(nameof(_1D_))] ulong b) + { + uint opcode = 0x5EE03C00; // CMGE D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGE ., ., .")] + public void Cmge_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E203C00; // CMGE V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGE ., ., .")] + public void Cmge_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E203C00; // CMGE V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGT , , ")] + public void Cmgt_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [ValueSource(nameof(_1D_))] ulong b) + { + uint opcode = 0x5EE03400; // CMGT D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGT ., ., .")] + public void Cmgt_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E203400; // CMGT V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMGT ., ., .")] + public void Cmgt_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E203400; // CMGT V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMHI , , ")] + public void Cmhi_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [ValueSource(nameof(_1D_))] ulong b) + { + uint opcode = 0x7EE03400; // CMHI D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMHI ., ., .")] + public void Cmhi_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E203400; // CMHI V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMHI ., ., .")] + public void Cmhi_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E203400; // CMHI V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMHS , , ")] + public void Cmhs_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [ValueSource(nameof(_1D_))] ulong b) + { + uint opcode = 0x7EE03C00; // CMHS D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMHS ., ., .")] + public void Cmhs_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E203C00; // CMHS V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMHS ., ., .")] + public void Cmhs_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E203C00; // CMHS V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMTST , , ")] + public void Cmtst_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [ValueSource(nameof(_1D_))] ulong b) + { + uint opcode = 0x5EE08C00; // CMTST D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMTST ., ., .")] + public void Cmtst_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E208C00; // CMTST V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("CMTST ., ., .")] + public void Cmtst_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E208C00; // CMTST V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("EOR ., ., .")] + public void Eor_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x2E201C00; // EOR V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("EOR ., ., .")] + public void Eor_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x6E201C00; // EOR V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Abd_Add_Div_Mul_Mulx_Nmul_Sub_S_S([ValueSource(nameof(_F_Abd_Add_Div_Mul_Mulx_Nmul_Sub_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a, + [ValueSource(nameof(_1S_F_))] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Abd_Add_Div_Mul_Mulx_Nmul_Sub_S_D([ValueSource(nameof(_F_Abd_Add_Div_Mul_Mulx_Nmul_Sub_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Abd_Add_Div_Mul_Mulx_Sub_P_V_2S_4S([ValueSource(nameof(_F_Abd_Add_Div_Mul_Mulx_Sub_P_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a, + [ValueSource(nameof(_2S_F_))] ulong b, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Abd_Add_Div_Mul_Mulx_Sub_P_V_2D([ValueSource(nameof(_F_Abd_Add_Div_Mul_Mulx_Sub_P_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_F_))] ulong z, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Dzc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_AcCm_EqGeGt_S_S([ValueSource(nameof(_F_AcCm_EqGeGt_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a, + [ValueSource(nameof(_1S_F_))] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_AcCm_EqGeGt_S_D([ValueSource(nameof(_F_AcCm_EqGeGt_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_AcCm_EqGeGt_V_2S_4S([ValueSource(nameof(_F_AcCm_EqGeGt_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a, + [ValueSource(nameof(_2S_F_))] ulong b, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_AcCm_EqGeGt_V_2D([ValueSource(nameof(_F_AcCm_EqGeGt_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_F_))] ulong z, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Cmp_Cmpe_S_S([ValueSource(nameof(_F_Cmp_Cmpe_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a, + [ValueSource(nameof(_1S_F_))] ulong b) + { + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + SingleOpcode(opcodes, v1: v1, v2: v2, overflow: v, carry: c, zero: z, negative: n); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc); + } + + [Test, Pairwise] + [Explicit] + public void F_Cmp_Cmpe_S_D([ValueSource(nameof(_F_Cmp_Cmpe_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b) + { + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + SingleOpcode(opcodes, v1: v1, v2: v2, overflow: v, carry: c, zero: z, negative: n); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc); + } + + // Fused. + [Test, Pairwise] + [Explicit] + public void F_Madd_Msub_Nmadd_Nmsub_S_S([ValueSource(nameof(_F_Madd_Msub_Nmadd_Nmsub_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a, + [ValueSource(nameof(_1S_F_))] ulong b, + [ValueSource(nameof(_1S_F_))] ulong c) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + V128 v3 = MakeVectorE0(c); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, v3: v3, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsS); + } + + // Fused. + [Test, Pairwise] + [Explicit] + public void F_Madd_Msub_Nmadd_Nmsub_S_D([ValueSource(nameof(_F_Madd_Msub_Nmadd_Nmsub_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b, + [ValueSource(nameof(_1D_F_))] ulong c) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + V128 v3 = MakeVectorE0(c); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, v3: v3, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsD); + } + + [Test, Pairwise] + [Explicit] + public void F_Max_Min_Nm_S_S([ValueSource(nameof(_F_Max_Min_Nm_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a, + [ValueSource(nameof(_1S_F_))] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Max_Min_Nm_S_D([ValueSource(nameof(_F_Max_Min_Nm_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Max_Min_Nm_P_V_2S_4S([ValueSource(nameof(_F_Max_Min_Nm_P_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a, + [ValueSource(nameof(_2S_F_))] ulong b, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Max_Min_Nm_P_V_2D([ValueSource(nameof(_F_Max_Min_Nm_P_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_F_))] ulong z, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + // Fused. + [Test, Pairwise] + [Explicit] + public void F_Mla_Mls_V_2S_4S([ValueSource(nameof(_F_Mla_Mls_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a, + [ValueSource(nameof(_2S_F_))] ulong b, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsS); + } + + // Fused. + [Test, Pairwise] + [Explicit] + public void F_Mla_Mls_V_2D([ValueSource(nameof(_F_Mla_Mls_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_F_))] ulong z, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsD); + } + + // Fused. + [Test, Pairwise] + [Explicit] + public void F_Recps_Rsqrts_S_S([ValueSource(nameof(_F_Recps_Rsqrts_S_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a, + [ValueSource(nameof(_1S_F_))] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsS); + } + + // Fused. + [Test, Pairwise] + [Explicit] + public void F_Recps_Rsqrts_S_D([ValueSource(nameof(_F_Recps_Rsqrts_S_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b) + { + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsD); + } + + // Fused. + [Test, Pairwise] + [Explicit] + public void F_Recps_Rsqrts_V_2S_4S([ValueSource(nameof(_F_Recps_Rsqrts_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a, + [ValueSource(nameof(_2S_F_))] ulong b, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * q); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsS); + } + + // Fused. + [Test, Pairwise] + [Explicit] + public void F_Recps_Rsqrts_V_2D([ValueSource(nameof(_F_Recps_Rsqrts_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_F_))] ulong z, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsD); + } + + [Test, Pairwise] + public void Mla_Mls_Mul_V_8B_4H_2S([ValueSource(nameof(_Mla_Mls_Mul_V_8B_4H_2S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Mla_Mls_Mul_V_16B_8H_4S([ValueSource(nameof(_Mla_Mls_Mul_V_16B_8H_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ORN ., ., .")] + public void Orn_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x0EE01C00; // ORN V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ORN ., ., .")] + public void Orn_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x4EE01C00; // ORN V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ORR ., ., .")] + public void Orr_V_8B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x0EA01C00; // ORR V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ORR ., ., .")] + public void Orr_V_16B([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [ValueSource(nameof(_8B_))] ulong b) + { + uint opcode = 0x4EA01C00; // ORR V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("PMULL{2} ., ., .")] + public void Pmull_V([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B1D_))] ulong z0, + [ValueSource(nameof(_8B1D_))] ulong z1, + [ValueSource(nameof(_8B1D_))] ulong a0, + [ValueSource(nameof(_8B1D_))] ulong a1, + [ValueSource(nameof(_8B1D_))] ulong b0, + [ValueSource(nameof(_8B1D_))] ulong b1, + [Values(0b00u, 0b11u)] uint size, // Q0: <8B, 1D> => <8H, 1Q> + [Values(0b0u, 0b1u)] uint q) // Q1: <16B, 2D> => <8H, 1Q> + { + uint opcode = 0x0E20E000; // PMULL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + opcode |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + V128 v2 = MakeVectorE0E1(b0, b1); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("RADDHN{2} ., ., .")] + public void Raddhn_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [ValueSource(nameof(_4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> + { + uint opcode = 0x2E204000; // RADDHN V0.8B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("RADDHN{2} ., ., .")] + public void Raddhn_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [ValueSource(nameof(_4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> + { + uint opcode = 0x6E204000; // RADDHN2 V0.16B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("RSUBHN{2} ., ., .")] + public void Rsubhn_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [ValueSource(nameof(_4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> + { + uint opcode = 0x2E206000; // RSUBHN V0.8B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("RSUBHN{2} ., ., .")] + public void Rsubhn_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [ValueSource(nameof(_4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> + { + uint opcode = 0x6E206000; // RSUBHN2 V0.16B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SABA ., ., .")] + public void Saba_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E207C00; // SABA V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SABA ., ., .")] + public void Saba_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x4E207C00; // SABA V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SABAL{2} ., ., .")] + public void Sabal_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + uint opcode = 0x0E205000; // SABAL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SABAL{2} ., ., .")] + public void Sabal_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x4E205000; // SABAL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SABD ., ., .")] + public void Sabd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E207400; // SABD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SABD ., ., .")] + public void Sabd_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x4E207400; // SABD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SABDL{2} ., ., .")] + public void Sabdl_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + uint opcode = 0x0E207000; // SABDL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SABDL{2} ., ., .")] + public void Sabdl_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x4E207000; // SABDL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SADDL{2} ., ., .")] + public void Saddl_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + uint opcode = 0x0E200000; // SADDL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SADDL{2} ., ., .")] + public void Saddl_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x4E200000; // SADDL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SADDW{2} ., ., .")] + public void Saddw_V_8B8H8H_4H4S4S_2S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))][Random(RndCnt)] ulong a, + [ValueSource(nameof(_8B4H2S_))][Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H8H, 4H4S4S, 2S2D2D> + { + uint opcode = 0x0E201000; // SADDW V0.8H, V0.8H, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SADDW{2} ., ., .")] + public void Saddw_V_16B8H8H_8H4S4S_4S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))][Random(RndCnt)] ulong a, + [ValueSource(nameof(_8B4H2S_))][Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H8H, 8H4S4S, 4S2D2D> + { + uint opcode = 0x4E201000; // SADDW2 V0.8H, V0.8H, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Sha1c_Sha1m_Sha1p_Sha1su0_V([ValueSource(nameof(_Sha1c_Sha1m_Sha1p_Sha1su0_V_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [Random(RndCnt / 2)] ulong z0, [Random(RndCnt / 2)] ulong z1, + [Random(RndCnt / 2)] ulong a0, [Random(RndCnt / 2)] ulong a1, + [Random(RndCnt / 2)] ulong b0, [Random(RndCnt / 2)] ulong b1) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + V128 v2 = MakeVectorE0E1(b0, b1); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Sha256h_Sha256h2_Sha256su1_V([ValueSource(nameof(_Sha256h_Sha256h2_Sha256su1_V_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [Random(RndCnt / 2)] ulong z0, [Random(RndCnt / 2)] ulong z1, + [Random(RndCnt / 2)] ulong a0, [Random(RndCnt / 2)] ulong a1, + [Random(RndCnt / 2)] ulong b0, [Random(RndCnt / 2)] ulong b1) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + V128 v2 = MakeVectorE0E1(b0, b1); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SHADD ., ., .")] + public void Shadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E200400; // SHADD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SHADD ., ., .")] + public void Shadd_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x4E200400; // SHADD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SHSUB ., ., .")] + public void Shsub_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E202400; // SHSUB V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SHSUB ., ., .")] + public void Shsub_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x4E202400; // SHSUB V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Max_Min_P_V([ValueSource(nameof(_SU_Max_Min_P_V_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size, // Q0: <8B, 4H, 2S> + [Values(0b0u, 0b1u)] uint q) // Q1: <16B, 8H, 4S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * q); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Mlal_Mlsl_Mull_V_8B8H_4H4S_2S2D([ValueSource(nameof(_SU_Mlal_Mlsl_Mull_V_8B8H_4H4S_2S2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Mlal_Mlsl_Mull_V_16B8H_8H4S_4S2D([ValueSource(nameof(_SU_Mlal_Mlsl_Mull_V_16B8H_8H4S_4S2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SQADD , , ")] + public void Sqadd_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1B1H1S1D_))] ulong z, + [ValueSource(nameof(_1B1H1S1D_))] ulong a, + [ValueSource(nameof(_1B1H1S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x5E200C00; // SQADD B0, B0, B0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQADD ., ., .")] + public void Sqadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E200C00; // SQADD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQADD ., ., .")] + public void Sqadd_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E200C00; // SQADD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQDMULH , , ")] + public void Sqdmulh_S_H_S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1H1S_))] ulong z, + [ValueSource(nameof(_1H1S_))] ulong a, + [ValueSource(nameof(_1H1S_))] ulong b, + [Values(0b01u, 0b10u)] uint size) // + { + uint opcode = 0x5E20B400; // SQDMULH B0, B0, B0 (RESERVED) + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQDMULH ., ., .")] + public void Sqdmulh_V_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_4H2S_))] ulong z, + [ValueSource(nameof(_4H2S_))] ulong a, + [ValueSource(nameof(_4H2S_))] ulong b, + [Values(0b01u, 0b10u)] uint size) // <4H, 2S> + { + uint opcode = 0x0E20B400; // SQDMULH V0.8B, V0.8B, V0.8B (RESERVED) + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQDMULH ., ., .")] + public void Sqdmulh_V_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_4H2S_))] ulong z, + [ValueSource(nameof(_4H2S_))] ulong a, + [ValueSource(nameof(_4H2S_))] ulong b, + [Values(0b01u, 0b10u)] uint size) // <8H, 4S> + { + uint opcode = 0x4E20B400; // SQDMULH V0.16B, V0.16B, V0.16B (RESERVED) + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQRDMULH , , ")] + public void Sqrdmulh_S_H_S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1H1S_))] ulong z, + [ValueSource(nameof(_1H1S_))] ulong a, + [ValueSource(nameof(_1H1S_))] ulong b, + [Values(0b01u, 0b10u)] uint size) // + { + uint opcode = 0x7E20B400; // SQRDMULH B0, B0, B0 (RESERVED) + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQRDMULH ., ., .")] + public void Sqrdmulh_V_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_4H2S_))] ulong z, + [ValueSource(nameof(_4H2S_))] ulong a, + [ValueSource(nameof(_4H2S_))] ulong b, + [Values(0b01u, 0b10u)] uint size) // <4H, 2S> + { + uint opcode = 0x2E20B400; // SQRDMULH V0.8B, V0.8B, V0.8B (RESERVED) + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQRDMULH ., ., .")] + public void Sqrdmulh_V_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_4H2S_))] ulong z, + [ValueSource(nameof(_4H2S_))] ulong a, + [ValueSource(nameof(_4H2S_))] ulong b, + [Values(0b01u, 0b10u)] uint size) // <8H, 4S> + { + uint opcode = 0x6E20B400; // SQRDMULH V0.16B, V0.16B, V0.16B (RESERVED) + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQSUB , , ")] + public void Sqsub_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1B1H1S1D_))] ulong z, + [ValueSource(nameof(_1B1H1S1D_))] ulong a, + [ValueSource(nameof(_1B1H1S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x5E202C00; // SQSUB B0, B0, B0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQSUB ., ., .")] + public void Sqsub_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E202C00; // SQSUB V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SQSUB ., ., .")] + public void Sqsub_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E202C00; // SQSUB V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SRHADD ., ., .")] + public void Srhadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E201400; // SRHADD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SRHADD ., ., .")] + public void Srhadd_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x4E201400; // SRHADD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShlReg_S_D([ValueSource(nameof(_ShlReg_S_D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [ValueSource(nameof(_1D_))] ulong b) + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void ShlReg_V_8B_4H_2S([ValueSource(nameof(_ShlReg_V_8B_4H_2S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void ShlReg_V_16B_8H_4S_2D([ValueSource(nameof(_ShlReg_V_16B_8H_4S_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("SSUBL{2} ., ., .")] + public void Ssubl_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + uint opcode = 0x0E202000; // SSUBL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SSUBL{2} ., ., .")] + public void Ssubl_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x4E202000; // SSUBL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SSUBW{2} ., ., .")] + public void Ssubw_V_8B8H8H_4H4S4S_2S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))][Random(RndCnt)] ulong a, + [ValueSource(nameof(_8B4H2S_))][Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H8H, 4H4S4S, 2S2D2D> + { + uint opcode = 0x0E203000; // SSUBW V0.8H, V0.8H, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SSUBW{2} ., ., .")] + public void Ssubw_V_16B8H8H_8H4S4S_4S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))][Random(RndCnt)] ulong a, + [ValueSource(nameof(_8B4H2S_))][Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H8H, 8H4S4S, 4S2D2D> + { + uint opcode = 0x4E203000; // SSUBW2 V0.8H, V0.8H, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB , , ")] + public void Sub_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [ValueSource(nameof(_1D_))] ulong b) + { + uint opcode = 0x7EE08400; // SUB D0, D0, D0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB ., ., .")] + public void Sub_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E208400; // SUB V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUB ., ., .")] + public void Sub_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E208400; // SUB V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUBHN{2} ., ., .")] + public void Subhn_V_8H8B_4S4H_2D2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [ValueSource(nameof(_4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H8B, 4S4H, 2D2S> + { + uint opcode = 0x0E206000; // SUBHN V0.8B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SUBHN{2} ., ., .")] + public void Subhn_V_8H16B_4S8H_2D4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))] ulong a, + [ValueSource(nameof(_4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8H16B, 4S8H, 2D4S> + { + uint opcode = 0x4E206000; // SUBHN2 V0.16B, V0.8H, V0.8H + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("TRN1 ., ., .")] + public void Trn1_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E002800; // TRN1 V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("TRN1 ., ., .")] + public void Trn1_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E002800; // TRN1 V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("TRN2 ., ., .")] + public void Trn2_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E006800; // TRN2 V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("TRN2 ., ., .")] + public void Trn2_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E006800; // TRN2 V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UABA ., ., .")] + public void Uaba_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E207C00; // UABA V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UABA ., ., .")] + public void Uaba_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x6E207C00; // UABA V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UABAL{2} ., ., .")] + public void Uabal_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + uint opcode = 0x2E205000; // UABAL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UABAL{2} ., ., .")] + public void Uabal_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x6E205000; // UABAL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UABD ., ., .")] + public void Uabd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E207400; // UABD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UABD ., ., .")] + public void Uabd_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x6E207400; // UABD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UABDL{2} ., ., .")] + public void Uabdl_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + uint opcode = 0x2E207000; // UABDL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UABDL{2} ., ., .")] + public void Uabdl_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x6E207000; // UABDL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADDL{2} ., ., .")] + public void Uaddl_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + uint opcode = 0x2E200000; // UADDL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADDL{2} ., ., .")] + public void Uaddl_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x6E200000; // UADDL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADDW{2} ., ., .")] + public void Uaddw_V_8B8H8H_4H4S4S_2S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))][Random(RndCnt)] ulong a, + [ValueSource(nameof(_8B4H2S_))][Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H8H, 4H4S4S, 2S2D2D> + { + uint opcode = 0x2E201000; // UADDW V0.8H, V0.8H, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UADDW{2} ., ., .")] + public void Uaddw_V_16B8H8H_8H4S4S_4S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))][Random(RndCnt)] ulong a, + [ValueSource(nameof(_8B4H2S_))][Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H8H, 8H4S4S, 4S2D2D> + { + uint opcode = 0x6E201000; // UADDW2 V0.8H, V0.8H, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UHADD ., ., .")] + public void Uhadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E200400; // UHADD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UHADD ., ., .")] + public void Uhadd_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x6E200400; // UHADD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UHSUB ., ., .")] + public void Uhsub_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E202400; // UHSUB V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UHSUB ., ., .")] + public void Uhsub_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x6E202400; // UHSUB V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UQADD , , ")] + public void Uqadd_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1B1H1S1D_))] ulong z, + [ValueSource(nameof(_1B1H1S1D_))] ulong a, + [ValueSource(nameof(_1B1H1S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x7E200C00; // UQADD B0, B0, B0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("UQADD ., ., .")] + public void Uqadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E200C00; // UQADD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("UQADD ., ., .")] + public void Uqadd_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E200C00; // UQADD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("UQSUB , , ")] + public void Uqsub_S_B_H_S_D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1B1H1S1D_))] ulong z, + [ValueSource(nameof(_1B1H1S1D_))] ulong a, + [ValueSource(nameof(_1B1H1S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // + { + uint opcode = 0x7E202C00; // UQSUB B0, B0, B0 + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("UQSUB ., ., .")] + public void Uqsub_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E202C00; // UQSUB V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("UQSUB ., ., .")] + public void Uqsub_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x6E202C00; // UQSUB V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise, Description("URHADD ., ., .")] + public void Urhadd_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x2E201400; // URHADD V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("URHADD ., ., .")] + public void Urhadd_V_16B_8H_4S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B, 8H, 4S> + { + uint opcode = 0x6E201400; // URHADD V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("USUBL{2} ., ., .")] + public void Usubl_V_8B8H_4H4S_2S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H, 4H4S, 2S2D> + { + uint opcode = 0x2E202000; // USUBL V0.8H, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("USUBL{2} ., ., .")] + public void Usubl_V_16B8H_8H4S_4S2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H, 8H4S, 4S2D> + { + uint opcode = 0x6E202000; // USUBL2 V0.8H, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE1(a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("USUBW{2} ., ., .")] + public void Usubw_V_8B8H8H_4H4S4S_2S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))][Random(RndCnt)] ulong a, + [ValueSource(nameof(_8B4H2S_))][Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B8H8H, 4H4S4S, 2S2D2D> + { + uint opcode = 0x2E203000; // USUBW V0.8H, V0.8H, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("USUBW{2} ., ., .")] + public void Usubw_V_16B8H8H_8H4S4S_4S2D2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_4H2S1D_))][Random(RndCnt)] ulong a, + [ValueSource(nameof(_8B4H2S_))][Random(RndCnt)] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <16B8H8H, 8H4S4S, 4S2D2D> + { + uint opcode = 0x6E203000; // USUBW2 V0.8H, V0.8H, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE1(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UZP1 ., ., .")] + public void Uzp1_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E001800; // UZP1 V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UZP1 ., ., .")] + public void Uzp1_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E001800; // UZP1 V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UZP2 ., ., .")] + public void Uzp2_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E005800; // UZP2 V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("UZP2 ., ., .")] + public void Uzp2_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E005800; // UZP2 V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ZIP1 ., ., .")] + public void Zip1_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E003800; // ZIP1 V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ZIP1 ., ., .")] + public void Zip1_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E003800; // ZIP1 V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ZIP2 ., ., .")] + public void Zip2_V_8B_4H_2S([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S_))] ulong z, + [ValueSource(nameof(_8B4H2S_))] ulong a, + [ValueSource(nameof(_8B4H2S_))] ulong b, + [Values(0b00u, 0b01u, 0b10u)] uint size) // <8B, 4H, 2S> + { + uint opcode = 0x0E007800; // ZIP2 V0.8B, V0.8B, V0.8B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("ZIP2 ., ., .")] + public void Zip2_V_16B_8H_4S_2D([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0b00u, 0b01u, 0b10u, 0b11u)] uint size) // <16B, 8H, 4S, 2D> + { + uint opcode = 0x4E007800; // ZIP2 V0.16B, V0.16B, V0.16B + opcode |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcode |= ((size & 3) << 22); + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs new file mode 100644 index 00000000..843273dc --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs @@ -0,0 +1,984 @@ +#define SimdReg32 + +using ARMeilleure.State; +using NUnit.Framework; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdReg32")] + public sealed class CpuTestSimdReg32 : CpuTest32 + { +#if SimdReg32 + + #region "ValueSource (Opcodes)" + private static uint[] _V_Add_Sub_Long_Wide_I_() + { + return new[] + { + 0xf2800000u, // VADDL.S8 Q0, D0, D0 + 0xf2800100u, // VADDW.S8 Q0, Q0, D0 + 0xf2800200u, // VSUBL.S8 Q0, D0, D0 + 0xf2800300u, // VSUBW.S8 Q0, Q0, D0 + }; + } + + private static uint[] _Vfma_Vfms_Vfnma_Vfnms_S_F32_() + { + return new[] + { + 0xEEA00A00u, // VFMA. F32 S0, S0, S0 + 0xEEA00A40u, // VFMS. F32 S0, S0, S0 + 0xEE900A40u, // VFNMA.F32 S0, S0, S0 + 0xEE900A00u, // VFNMS.F32 S0, S0, S0 + }; + } + + private static uint[] _Vfma_Vfms_Vfnma_Vfnms_S_F64_() + { + return new[] + { + 0xEEA00B00u, // VFMA. F64 D0, D0, D0 + 0xEEA00B40u, // VFMS. F64 D0, D0, D0 + 0xEE900B40u, // VFNMA.F64 D0, D0, D0 + 0xEE900B00u, // VFNMS.F64 D0, D0, D0 + }; + } + + private static uint[] _Vfma_Vfms_V_F32_() + { + return new[] + { + 0xF2000C10u, // VFMA.F32 D0, D0, D0 + 0xF2200C10u, // VFMS.F32 D0, D0, D0 + }; + } + + private static uint[] _Vmla_Vmls_Vnmla_Vnmls_S_F32_() + { + return new[] + { + 0xEE000A00u, // VMLA. F32 S0, S0, S0 + 0xEE000A40u, // VMLS. F32 S0, S0, S0 + 0xEE100A40u, // VNMLA.F32 S0, S0, S0 + 0xEE100A00u, // VNMLS.F32 S0, S0, S0 + }; + } + + private static uint[] _Vmla_Vmls_Vnmla_Vnmls_S_F64_() + { + return new[] + { + 0xEE000B00u, // VMLA. F64 D0, D0, D0 + 0xEE000B40u, // VMLS. F64 D0, D0, D0 + 0xEE100B40u, // VNMLA.F64 D0, D0, D0 + 0xEE100B00u, // VNMLS.F64 D0, D0, D0 + }; + } + + private static uint[] _Vmlal_Vmlsl_V_I_() + { + return new[] + { + 0xf2800800u, // VMLAL.S8 Q0, D0, D0 + 0xf2800a00u, // VMLSL.S8 Q0, D0, D0 + }; + } + + private static uint[] _Vp_Add_Max_Min_F_() + { + return new[] + { + 0xf3000d00u, // VPADD.F32 D0, D0, D0 + 0xf3000f00u, // VPMAX.F32 D0, D0, D0 + 0xf3200f00u, // VPMIN.F32 D0, D0, D0 + }; + } + + private static uint[] _Vp_Add_I_() + { + return new[] + { + 0xf2000b10u, // VPADD.I8 D0, D0, D0 + }; + } + + private static uint[] _V_Pmax_Pmin_Rhadd_I_() + { + return new[] + { + 0xf2000a00u, // VPMAX .S8 D0, D0, D0 + 0xf2000a10u, // VPMIN .S8 D0, D0, D0 + 0xf2000100u, // VRHADD.S8 D0, D0, D0 + }; + } + + private static uint[] _Vq_Add_Sub_I_() + { + return new[] + { + 0xf2000050u, // VQADD.S8 Q0, Q0, Q0 + 0xf2000250u, // VQSUB.S8 Q0, Q0, Q0 + }; + } + #endregion + + #region "ValueSource (Types)" + private static ulong[] _8B1D_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _8B4H2S1D_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static IEnumerable _1S_F_() + { + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + } + } + + private static IEnumerable _2S_F_() + { + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + } + } + + private static IEnumerable _1D_F_() + { + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalD(); + ulong rnd2 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + } + } + #endregion + + private const int RndCnt = 2; + + private static readonly bool _noZeros = false; + private static readonly bool _noInfs = false; + private static readonly bool _noNaNs = false; + + [Test, Pairwise, Description("SHA256H.32 , , ")] + public void Sha256h_V([Values(0xF3000C40u)] uint opcode, + [Values(0u)] uint rd, + [Values(2u)] uint rn, + [Values(4u)] uint rm, + [Values(0xAEE65C11943FB939ul)] ulong z0, + [Values(0xA89A87F110291DA3ul)] ulong z1, + [Values(0xE9F766DB7A49EA7Dul)] ulong a0, + [Values(0x3053F46B0C2F3507ul)] ulong a1, + [Values(0x6E86A473B9D4A778ul)] ulong b0, + [Values(0x7BE4F9E638156BB1ul)] ulong b1, + [Values(0x1F1DC4A98DA9C132ul)] ulong resultL, + [Values(0xDB9A2A7B47031A0Dul)] ulong resultH) + { + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + V128 v2 = MakeVectorE0E1(b0, b1); + + ExecutionContext context = SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, runUnicorn: false); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + + // Unicorn does not yet support hash instructions in A32. + // CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SHA256H2.32 , , ")] + public void Sha256h2_V([Values(0xF3100C40u)] uint opcode, + [Values(0u)] uint rd, + [Values(2u)] uint rn, + [Values(4u)] uint rm, + [Values(0xAEE65C11943FB939ul)] ulong z0, + [Values(0xA89A87F110291DA3ul)] ulong z1, + [Values(0xE9F766DB7A49EA7Dul)] ulong a0, + [Values(0x3053F46B0C2F3507ul)] ulong a1, + [Values(0x6E86A473B9D4A778ul)] ulong b0, + [Values(0x7BE4F9E638156BB1ul)] ulong b1, + [Values(0x0A1177E9D9C9B611ul)] ulong resultL, + [Values(0xF5A826404928A515ul)] ulong resultH) + { + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + V128 v2 = MakeVectorE0E1(b0, b1); + + ExecutionContext context = SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, runUnicorn: false); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + + // Unicorn does not yet support hash instructions in A32. + // CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("SHA256SU1.32 , , ")] + public void Sha256su1_V([Values(0xF3200C40u)] uint opcode, + [Values(0u)] uint rd, + [Values(2u)] uint rn, + [Values(4u)] uint rm, + [Values(0xAEE65C11943FB939ul)] ulong z0, + [Values(0xA89A87F110291DA3ul)] ulong z1, + [Values(0xE9F766DB7A49EA7Dul)] ulong a0, + [Values(0x3053F46B0C2F3507ul)] ulong a1, + [Values(0x6E86A473B9D4A778ul)] ulong b0, + [Values(0x7BE4F9E638156BB1ul)] ulong b1, + [Values(0x9EE69CC896D7DE66ul)] ulong resultL, + [Values(0x004A147155573E54ul)] ulong resultH) + { + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + V128 v2 = MakeVectorE0E1(b0, b1); + + ExecutionContext context = SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, runUnicorn: false); + + Assert.Multiple(() => + { + Assert.That(GetVectorE0(context.GetV(0)), Is.EqualTo(resultL)); + Assert.That(GetVectorE1(context.GetV(0)), Is.EqualTo(resultH)); + }); + + // Unicorn does not yet support hash instructions in A32. + // CompareAgainstUnicorn(); + } + + [Explicit] + [Test, Pairwise, Description("VADD.f32 V0, V0, V0")] + public void Vadd_F32([Values(0u)] uint rd, + [Values(0u, 1u)] uint rn, + [Values(0u, 2u)] uint rm, + [ValueSource(nameof(_2S_F_))] ulong z0, + [ValueSource(nameof(_2S_F_))] ulong z1, + [ValueSource(nameof(_2S_F_))] ulong a0, + [ValueSource(nameof(_2S_F_))] ulong a1, + [ValueSource(nameof(_2S_F_))] ulong b0, + [ValueSource(nameof(_2S_F_))] ulong b1, + [Values] bool q) + { + uint opcode = 0xf2000d00u; // VADD.F32 D0, D0, D0 + if (q) + { + opcode |= 1 << 6; + rm <<= 1; + rn <<= 1; + rd <<= 1; + } + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + V128 v2 = MakeVectorE0E1(b0, b1); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void V_Add_Sub_Long_Wide_I([ValueSource(nameof(_V_Add_Sub_Long_Wide_I_))] uint opcode, + [Range(0u, 5u)] uint rd, + [Range(0u, 5u)] uint rn, + [Range(0u, 5u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0u, 1u, 2u)] uint size, // + [Values] bool u) // + { + if (u) + { + opcode |= 1 << 24; + } + + rd >>= 1; + rd <<= 1; + rn >>= 1; + rn <<= 1; + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + opcode |= (size & 0x3) << 20; + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VCMP.f Vd, Vm")] + public void Vcmp([Values(2u, 3u)] uint size, + [ValueSource(nameof(_1S_F_))] ulong a, + [ValueSource(nameof(_1S_F_))] ulong b, + [Values] bool e) + { + uint opcode = 0xeeb40840u; + uint rm = 1; + uint rd = 2; + + if (size == 3) + { + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + } + else + { + opcode |= ((rm & 0x1e) >> 1) | ((rm & 0x1) << 5); + opcode |= ((rd & 0x1e) << 11) | ((rd & 0x1) << 22); + } + + opcode |= ((size & 3) << 8); + if (e) + { + opcode |= 1 << 7; + } + + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0(b); + + int fpscr = (int)(TestContext.CurrentContext.Random.NextUInt(0xf) << 28); + + SingleOpcode(opcode, v1: v1, v2: v2, fpscr: fpscr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Nzcv); + } + + // Fused. + [Test, Pairwise] + [Explicit] + public void Vfma_Vfms_Vfnma_Vfnms_S_F32([ValueSource(nameof(_Vfma_Vfms_Vfnma_Vfnms_S_F32_))] uint opcode, + [Values(0u, 1u, 2u, 3u)] uint rd, + [Values(0u, 1u, 2u, 3u)] uint rn, + [Values(0u, 1u, 2u, 3u)] uint rm, + [ValueSource(nameof(_1S_F_))] ulong s0, + [ValueSource(nameof(_1S_F_))] ulong s1, + [ValueSource(nameof(_1S_F_))] ulong s2, + [ValueSource(nameof(_1S_F_))] ulong s3) + { + opcode |= (((rd & 0x1) << 22) | (rd & 0x1e) << 11); + opcode |= (((rn & 0x1) << 7) | (rn & 0x1e) << 15); + opcode |= (((rm & 0x1) << 5) | (rm & 0x1e) >> 1); + + V128 v0 = MakeVectorE0E1E2E3((uint)s0, (uint)s1, (uint)s2, (uint)s3); + + SingleOpcode(opcode, v0: v0); + + CompareAgainstUnicorn(); + } + + // Fused. + [Test, Pairwise] + [Explicit] + public void Vfma_Vfms_Vfnma_Vfnms_S_F64([ValueSource(nameof(_Vfma_Vfms_Vfnma_Vfnms_S_F64_))] uint opcode, + [Values(0u, 1u)] uint rd, + [Values(0u, 1u)] uint rn, + [Values(0u, 1u)] uint rm, + [ValueSource(nameof(_1D_F_))] ulong d0, + [ValueSource(nameof(_1D_F_))] ulong d1) + { + opcode |= (((rd & 0x10) << 18) | (rd & 0xf) << 12); + opcode |= (((rn & 0x10) << 3) | (rn & 0xf) << 16); + opcode |= (((rm & 0x10) << 1) | (rm & 0xf) << 0); + + V128 v0 = MakeVectorE0E1(d0, d1); + + SingleOpcode(opcode, v0: v0); + + CompareAgainstUnicorn(); + } + + // Fused. + [Test, Pairwise] + [Explicit] + public void Vfma_Vfms_V_F32([ValueSource(nameof(_Vfma_Vfms_V_F32_))] uint opcode, + [Values(0u, 1u, 2u, 3u)] uint rd, + [Values(0u, 1u, 2u, 3u)] uint rn, + [Values(0u, 1u, 2u, 3u)] uint rm, + [ValueSource(nameof(_2S_F_))] ulong d0, + [ValueSource(nameof(_2S_F_))] ulong d1, + [ValueSource(nameof(_2S_F_))] ulong d2, + [ValueSource(nameof(_2S_F_))] ulong d3, + [Values] bool q) + { + if (q) + { + opcode |= 1 << 6; + + rd >>= 1; + rd <<= 1; + rn >>= 1; + rn <<= 1; + rm >>= 1; + rm <<= 1; + } + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + V128 v0 = MakeVectorE0E1(d0, d1); + V128 v1 = MakeVectorE0E1(d2, d3); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void Vmla_Vmls_Vnmla_Vnmls_S_F32([ValueSource(nameof(_Vmla_Vmls_Vnmla_Vnmls_S_F32_))] uint opcode, + [Values(0u, 1u, 2u, 3u)] uint rd, + [Values(0u, 1u, 2u, 3u)] uint rn, + [Values(0u, 1u, 2u, 3u)] uint rm, + [ValueSource(nameof(_1S_F_))] ulong s0, + [ValueSource(nameof(_1S_F_))] ulong s1, + [ValueSource(nameof(_1S_F_))] ulong s2, + [ValueSource(nameof(_1S_F_))] ulong s3) + { + opcode |= (((rd & 0x1) << 22) | (rd & 0x1e) << 11); + opcode |= (((rn & 0x1) << 7) | (rn & 0x1e) << 15); + opcode |= (((rm & 0x1) << 5) | (rm & 0x1e) >> 1); + + V128 v0 = MakeVectorE0E1E2E3((uint)s0, (uint)s1, (uint)s2, (uint)s3); + + SingleOpcode(opcode, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void Vmla_Vmls_Vnmla_Vnmls_S_F64([ValueSource(nameof(_Vmla_Vmls_Vnmla_Vnmls_S_F64_))] uint opcode, + [Values(0u, 1u)] uint rd, + [Values(0u, 1u)] uint rn, + [Values(0u, 1u)] uint rm, + [ValueSource(nameof(_1D_F_))] ulong d0, + [ValueSource(nameof(_1D_F_))] ulong d1) + { + opcode |= (((rd & 0x10) << 18) | (rd & 0xf) << 12); + opcode |= (((rn & 0x10) << 3) | (rn & 0xf) << 16); + opcode |= (((rm & 0x10) << 1) | (rm & 0xf) << 0); + + V128 v0 = MakeVectorE0E1(d0, d1); + + SingleOpcode(opcode, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Vmlal_Vmlsl_I([ValueSource(nameof(_Vmlal_Vmlsl_V_I_))] uint opcode, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [Values(0u, 1u, 2u)] uint size, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b, + [Values] bool u) + { + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + + opcode |= size << 20; + + if (u) + { + opcode |= 1 << 24; + } + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VMULL. , , ")] + public void Vmull_I([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [Values(0u, 1u, 2u)] uint size, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b, + [Values] bool op, + [Values] bool u) + { + uint opcode = 0xf2800c00u; // VMULL.S8 Q0, D0, D0 + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + + if (op) + { + opcode |= 1 << 9; + size = 0; + u = false; + } + + opcode |= size << 20; + + if (u) + { + opcode |= 1 << 24; + } + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VMULL. , , ")] + public void Vmull_I_P8_P64([Values(0u, 1u)] uint rd, + [Values(0u, 1u)] uint rn, + [Values(0u, 1u)] uint rm, + [ValueSource(nameof(_8B1D_))] ulong d0, + [ValueSource(nameof(_8B1D_))] ulong d1, + [Values(0u/*, 2u*/)] uint size) // + { + /*if (size == 2u) + { + Assert.Ignore("Ryujinx.Tests.Unicorn.UnicornException : Invalid instruction (UC_ERR_INSN_INVALID)"); + }*/ + + uint opcode = 0xf2800e00u; // VMULL.P8 Q0, D0, D0 + + rd >>= 1; + rd <<= 1; + + opcode |= (((rd & 0x10) << 18) | (rd & 0xf) << 12); + opcode |= (((rn & 0x10) << 3) | (rn & 0xf) << 16); + opcode |= (((rm & 0x10) << 1) | (rm & 0xf) << 0); + + opcode |= (size & 0x3) << 20; + + V128 v0 = MakeVectorE0E1(d0, d1); + + SingleOpcode(opcode, v0: v0); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VSHL. {}, , ")] + public void Vshl([Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [Values(0u, 1u, 2u, 3u)] uint size, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b, + [Values] bool q, + [Values] bool u) + { + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + Assert.Ignore("Unicorn on ARM64 crash while executing this test"); + } + + uint opcode = 0xf2000400u; // VSHL.S8 D0, D0, D0 + if (q) + { + opcode |= 1 << 6; + rm <<= 1; + rn <<= 1; + rd <<= 1; + } + + if (u) + { + opcode |= 1 << 24; + } + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + + opcode |= size << 20; + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Explicit] + [Test, Pairwise] + public void Vp_Add_Max_Min_F([ValueSource(nameof(_Vp_Add_Max_Min_F_))] uint opcode, + [Values(0u)] uint rd, + [Range(0u, 7u)] uint rn, + [Range(0u, 7u)] uint rm, + [ValueSource(nameof(_2S_F_))] ulong z0, + [ValueSource(nameof(_2S_F_))] ulong z1, + [ValueSource(nameof(_2S_F_))] ulong a0, + [ValueSource(nameof(_2S_F_))] ulong a1, + [ValueSource(nameof(_2S_F_))] ulong b0, + [ValueSource(nameof(_2S_F_))] ulong b1) + { + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + + V128 v0 = MakeVectorE0E1(z0, z1); + V128 v1 = MakeVectorE0E1(a0, a1); + V128 v2 = MakeVectorE0E1(b0, b1); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Vp_Add_I([ValueSource(nameof(_Vp_Add_I_))] uint opcode, + [Values(0u)] uint rd, + [Range(0u, 5u)] uint rn, + [Range(0u, 5u)] uint rm, + [Values(0u, 1u, 2u)] uint size, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b) + { + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + + opcode |= size << 20; + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void V_Pmax_Pmin_Rhadd_I([ValueSource(nameof(_V_Pmax_Pmin_Rhadd_I_))] uint opcode, + [Values(0u)] uint rd, + [Range(0u, 5u)] uint rn, + [Range(0u, 5u)] uint rm, + [Values(0u, 1u, 2u)] uint size, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b, + [Values] bool u) + { + if (u) + { + opcode |= 1 << 24; + } + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + + opcode |= size << 20; + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Vq_Add_Sub_I([ValueSource(nameof(_Vq_Add_Sub_I_))] uint opcode, + [Range(0u, 5u)] uint rd, + [Range(0u, 5u)] uint rn, + [Range(0u, 5u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(0u, 1u, 2u)] uint size, // + [Values] bool u) // + { + if (u) + { + opcode |= 1 << 24; + } + + rd >>= 1; + rd <<= 1; + rn >>= 1; + rn <<= 1; + rm >>= 1; + rm <<= 1; + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + opcode |= (size & 0x3) << 20; + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VQDMULH. , , ")] + public void Vqdmulh_I([Range(0u, 5u)] uint rd, + [Range(0u, 5u)] uint rn, + [Range(0u, 5u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(1u, 2u)] uint size) // + { + rd >>= 1; + rd <<= 1; + rn >>= 1; + rn <<= 1; + rm >>= 1; + rm <<= 1; + + uint opcode = 0xf2100b40u & ~(3u << 20); // VQDMULH.S16 Q0, Q0, Q0 + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + opcode |= (size & 0x3) << 20; + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VQRDMULH. , , ")] + public void Vqrdmulh_I([Range(0u, 5u)] uint rd, + [Range(0u, 5u)] uint rn, + [Range(0u, 5u)] uint rm, + [ValueSource(nameof(_8B4H2S1D_))] ulong z, + [ValueSource(nameof(_8B4H2S1D_))] ulong a, + [ValueSource(nameof(_8B4H2S1D_))] ulong b, + [Values(1u, 2u)] uint size) // + { + rd >>= 1; + rd <<= 1; + rn >>= 1; + rn <<= 1; + rm >>= 1; + rm <<= 1; + + uint opcode = 0xf3100b40u & ~(3u << 20); // VQRDMULH.S16 Q0, Q0, Q0 + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + opcode |= (size & 0x3) << 20; + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(a, ~a); + V128 v2 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Vp_Add_Long_Accumulate([Values(0u, 2u, 4u, 8u)] uint rd, + [Values(0u, 2u, 4u, 8u)] uint rm, + [Values(0u, 1u, 2u)] uint size, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b, + [Values] bool q, + [Values] bool unsigned) + { + uint opcode = 0xF3B00600; // VPADAL.S8 D0, Q0 + + if (q) + { + opcode |= 1 << 6; + rm <<= 1; + rd <<= 1; + } + + if (unsigned) + { + opcode |= 1 << 7; + } + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + + opcode |= size << 18; + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdRegElem.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdRegElem.cs new file mode 100644 index 00000000..23c6961f --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdRegElem.cs @@ -0,0 +1,195 @@ +#define SimdRegElem + +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdRegElem")] + public sealed class CpuTestSimdRegElem : CpuTest + { +#if SimdRegElem + + #region "ValueSource (Types)" + private static ulong[] _2S_() + { + return new[] { + 0x0000000000000000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _4H_() + { + return new[] { + 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + #endregion + + #region "ValueSource (Opcodes)" + private static uint[] _Mla_Mls_Mul_Sqdmulh_Sqrdmulh_Ve_4H_8H_() + { + return new[] + { + 0x2F400000u, // MLA V0.4H, V0.4H, V0.H[0] + 0x2F404000u, // MLS V0.4H, V0.4H, V0.H[0] + 0x0F408000u, // MUL V0.4H, V0.4H, V0.H[0] + 0x0F40C000u, // SQDMULH V0.4H, V0.4H, V0.H[0] + 0x0F40D000u, // SQRDMULH V0.4H, V0.4H, V0.H[0] + }; + } + + private static uint[] _Mla_Mls_Mul_Sqdmulh_Sqrdmulh_Ve_2S_4S_() + { + return new[] + { + 0x2F800000u, // MLA V0.2S, V0.2S, V0.S[0] + 0x2F804000u, // MLS V0.2S, V0.2S, V0.S[0] + 0x0F808000u, // MUL V0.2S, V0.2S, V0.S[0] + 0x0F80C000u, // SQDMULH V0.2S, V0.2S, V0.S[0] + 0x0F80D000u, // SQRDMULH V0.2S, V0.2S, V0.S[0] + }; + } + + private static uint[] _SU_Mlal_Mlsl_Mull_Ve_4H4S_8H4S_() + { + return new[] + { + 0x0F402000u, // SMLAL V0.4S, V0.4H, V0.H[0] + 0x0F406000u, // SMLSL V0.4S, V0.4H, V0.H[0] + 0x0F40A000u, // SMULL V0.4S, V0.4H, V0.H[0] + 0x2F402000u, // UMLAL V0.4S, V0.4H, V0.H[0] + 0x2F406000u, // UMLSL V0.4S, V0.4H, V0.H[0] + 0x2F40A000u, // UMULL V0.4S, V0.4H, V0.H[0] + }; + } + + private static uint[] _SU_Mlal_Mlsl_Mull_Ve_2S2D_4S2D_() + { + return new[] + { + 0x0F802000u, // SMLAL V0.2D, V0.2S, V0.S[0] + 0x0F806000u, // SMLSL V0.2D, V0.2S, V0.S[0] + 0x0F80A000u, // SMULL V0.2D, V0.2S, V0.S[0] + 0x2F802000u, // UMLAL V0.2D, V0.2S, V0.S[0] + 0x2F806000u, // UMLSL V0.2D, V0.2S, V0.S[0] + 0x2F80A000u, // UMULL V0.2D, V0.2S, V0.S[0] + }; + } + #endregion + + + [Test, Pairwise] + public void Mla_Mls_Mul_Sqdmulh_Sqrdmulh_Ve_4H_8H([ValueSource(nameof(_Mla_Mls_Mul_Sqdmulh_Sqrdmulh_Ve_4H_8H_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_4H_))] ulong z, + [ValueSource(nameof(_4H_))] ulong a, + [ValueSource(nameof(_4H_))] ulong b, + [Values(0u, 7u)] uint index, + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + uint h = (index >> 2) & 1; + uint l = (index >> 1) & 1; + uint m = index & 1; + + opcodes |= ((rm & 15) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (l << 21) | (m << 20) | (h << 11); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * h); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void Mla_Mls_Mul_Sqdmulh_Sqrdmulh_Ve_2S_4S([ValueSource(nameof(_Mla_Mls_Mul_Sqdmulh_Sqrdmulh_Ve_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_2S_))] ulong a, + [ValueSource(nameof(_2S_))] ulong b, + [Values(0u, 1u, 2u, 3u)] uint index, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint h = (index >> 1) & 1; + uint l = index & 1; + + opcodes |= ((rm & 15) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (l << 21) | (h << 11); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * h); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void SU_Mlal_Mlsl_Mull_Ve_4H4S_8H4S([ValueSource(nameof(_SU_Mlal_Mlsl_Mull_Ve_4H4S_8H4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_4H_))] ulong z, + [ValueSource(nameof(_4H_))] ulong a, + [ValueSource(nameof(_4H_))] ulong b, + [Values(0u, 7u)] uint index, + [Values(0b0u, 0b1u)] uint q) // <4H4S, 8H4S> + { + uint h = (index >> 2) & 1; + uint l = (index >> 1) & 1; + uint m = index & 1; + + opcodes |= ((rm & 15) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (l << 21) | (m << 20) | (h << 11); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + V128 v2 = MakeVectorE0E1(b, b * h); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Mlal_Mlsl_Mull_Ve_2S2D_4S2D([ValueSource(nameof(_SU_Mlal_Mlsl_Mull_Ve_2S2D_4S2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_2S_))] ulong a, + [ValueSource(nameof(_2S_))] ulong b, + [Values(0u, 1u, 2u, 3u)] uint index, + [Values(0b0u, 0b1u)] uint q) // <2S2D, 4S2D> + { + uint h = (index >> 1) & 1; + uint l = index & 1; + + opcodes |= ((rm & 15) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (l << 21) | (h << 11); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + V128 v2 = MakeVectorE0E1(b, b * h); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdRegElem32.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdRegElem32.cs new file mode 100644 index 00000000..49aab051 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdRegElem32.cs @@ -0,0 +1,80 @@ +#define SimdRegElem32 + +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdRegElem32")] + public sealed class CpuTestSimdRegElem32 : CpuTest32 + { +#if SimdRegElem32 + private const int RndCnt = 2; + + [Test, Pairwise, Description("VMUL. {}, , []")] + public void Vmul_1I([Values(1u, 0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(26u, 25u, 10u, 9u, 2u, 0u)] uint rm, + [Values(1u, 2u)] uint size, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b, + [Values] bool q) + { + uint opcode = 0xf2900840u & ~(3u << 20); // VMUL.I16 D0, D0, D0[0] + if (q) + { + opcode |= 1 << 24; + rn <<= 1; + rd <<= 1; + } + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + + opcode |= size << 20; + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VMULL. , , []")] + public void Vmull_1([Values(2u, 0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(26u, 25u, 10u, 9u, 2u, 0u)] uint rm, + [Values(1u, 2u)] uint size, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b, + [Values] bool u) + { + uint opcode = 0xf2900a40u & ~(3u << 20); // VMULL.S16 Q0, D0, D0[0] + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rn & 0xf) << 16) | ((rn & 0x10) << 3); + + opcode |= size << 20; + + if (u) + { + opcode |= 1 << 24; + } + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdRegElemF.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdRegElemF.cs new file mode 100644 index 00000000..1b670da7 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdRegElemF.cs @@ -0,0 +1,457 @@ +#define SimdRegElemF + +using ARMeilleure.State; +using NUnit.Framework; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdRegElemF")] + public sealed class CpuTestSimdRegElemF : CpuTest + { +#if SimdRegElemF + + #region "ValueSource (Types)" + private static IEnumerable _1S_F_() + { + yield return 0x00000000FF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x0000000080800000ul; // -Min Normal + yield return 0x00000000807FFFFFul; // -Max Subnormal + yield return 0x0000000080000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x000000007F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0000000000800000ul; // +Min Normal + yield return 0x00000000007FFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x0000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0x00000000FF800000ul; // -Infinity + yield return 0x000000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0x00000000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0x00000000FFBFFFFFul; // -SNaN (all ones payload) + yield return 0x000000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x000000007FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong grbg = TestContext.CurrentContext.Random.NextUInt(); + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (grbg << 32) | rnd1; + yield return (grbg << 32) | rnd2; + } + } + + private static IEnumerable _2S_F_() + { + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalS(); + ulong rnd2 = GenSubnormalS(); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + } + } + + private static IEnumerable _1D_F_() + { + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = GenNormalD(); + ulong rnd2 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + } + } + #endregion + + #region "ValueSource (Opcodes)" + private static uint[] _F_Mla_Mls_Se_S_() + { + return new[] + { + 0x5F821020u, // FMLA S0, S1, V2.S[0] + 0x5F825020u, // FMLS S0, S1, V2.S[0] + }; + } + + private static uint[] _F_Mla_Mls_Se_D_() + { + return new[] + { + 0x5FC21020u, // FMLA D0, D1, V2.D[0] + 0x5FC25020u, // FMLS D0, D1, V2.D[0] + }; + } + + private static uint[] _F_Mla_Mls_Ve_2S_4S_() + { + return new[] + { + 0x0F801000u, // FMLA V0.2S, V0.2S, V0.S[0] + 0x0F805000u, // FMLS V0.2S, V0.2S, V0.S[0] + }; + } + + private static uint[] _F_Mla_Mls_Ve_2D_() + { + return new[] + { + 0x4FC01000u, // FMLA V0.2D, V0.2D, V0.D[0] + 0x4FC05000u, // FMLS V0.2D, V0.2D, V0.D[0] + }; + } + + private static uint[] _F_Mul_Mulx_Se_S_() + { + return new[] + { + 0x5F829020u, // FMUL S0, S1, V2.S[0] + 0x7F829020u, // FMULX S0, S1, V2.S[0] + }; + } + + private static uint[] _F_Mul_Mulx_Se_D_() + { + return new[] + { + 0x5FC29020u, // FMUL D0, D1, V2.D[0] + 0x7FC29020u, // FMULX D0, D1, V2.D[0] + }; + } + + private static uint[] _F_Mul_Mulx_Ve_2S_4S_() + { + return new[] + { + 0x0F809000u, // FMUL V0.2S, V0.2S, V0.S[0] + 0x2F809000u, // FMULX V0.2S, V0.2S, V0.S[0] + }; + } + + private static uint[] _F_Mul_Mulx_Ve_2D_() + { + return new[] + { + 0x4FC09000u, // FMUL V0.2D, V0.2D, V0.D[0] + 0x6FC09000u, // FMULX V0.2D, V0.2D, V0.D[0] + }; + } + #endregion + + private const int RndCnt = 2; + + private static readonly bool _noZeros = false; + private static readonly bool _noInfs = false; + private static readonly bool _noNaNs = false; + + // Fused. + [Test, Pairwise] + [Explicit] + public void F_Mla_Mls_Se_S([ValueSource(nameof(_F_Mla_Mls_Se_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong z, + [ValueSource(nameof(_1S_F_))] ulong a, + [ValueSource(nameof(_2S_F_))] ulong b, + [Values(0u, 1u, 2u, 3u)] uint index) + { + uint h = (index >> 1) & 1; + uint l = index & 1; + + opcodes |= (l << 21) | (h << 11); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsS); + } + + // Fused. + [Test, Pairwise] + [Explicit] + public void F_Mla_Mls_Se_D([ValueSource(nameof(_F_Mla_Mls_Se_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong z, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b, + [Values(0u, 1u)] uint index) + { + uint h = index & 1; + + opcodes |= h << 11; + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsD); + } + + // Fused. + [Test, Pairwise] + [Explicit] + public void F_Mla_Mls_Ve_2S_4S([ValueSource(nameof(_F_Mla_Mls_Ve_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a, + [ValueSource(nameof(_2S_F_))] ulong b, + [Values(0u, 1u, 2u, 3u)] uint index, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint h = (index >> 1) & 1; + uint l = index & 1; + + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (l << 21) | (h << 11); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsS); + } + + // Fused. + [Test, Pairwise] + [Explicit] + public void F_Mla_Mls_Ve_2D([ValueSource(nameof(_F_Mla_Mls_Ve_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_F_))] ulong z, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b, + [Values(0u, 1u)] uint index) + { + uint h = index & 1; + + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= h << 11; + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(Fpsr.Ioc | Fpsr.Idc, FpSkips.IfUnderflow, FpTolerances.UpToOneUlpsD); + } + + [Test, Pairwise] + [Explicit] + public void F_Mul_Mulx_Se_S([ValueSource(nameof(_F_Mul_Mulx_Se_S_))] uint opcodes, + [ValueSource(nameof(_1S_F_))] ulong a, + [ValueSource(nameof(_2S_F_))] ulong b, + [Values(0u, 1u, 2u, 3u)] uint index) + { + uint h = (index >> 1) & 1; + uint l = index & 1; + + opcodes |= (l << 21) | (h << 11); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Mul_Mulx_Se_D([ValueSource(nameof(_F_Mul_Mulx_Se_D_))] uint opcodes, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b, + [Values(0u, 1u)] uint index) + { + uint h = index & 1; + + opcodes |= h << 11; + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Mul_Mulx_Ve_2S_4S([ValueSource(nameof(_F_Mul_Mulx_Ve_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_2S_F_))] ulong z, + [ValueSource(nameof(_2S_F_))] ulong a, + [ValueSource(nameof(_2S_F_))] ulong b, + [Values(0u, 1u, 2u, 3u)] uint index, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint h = (index >> 1) & 1; + uint l = index & 1; + + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (l << 21) | (h << 11); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } + + [Test, Pairwise] + [Explicit] + public void F_Mul_Mulx_Ve_2D([ValueSource(nameof(_F_Mul_Mulx_Ve_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [Values(2u, 0u)] uint rm, + [ValueSource(nameof(_1D_F_))] ulong z, + [ValueSource(nameof(_1D_F_))] ulong a, + [ValueSource(nameof(_1D_F_))] ulong b, + [Values(0u, 1u)] uint index) + { + uint h = index & 1; + + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= h << 11; + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + V128 v2 = MakeVectorE0E1(b, b * h); + + int rnd = (int)TestContext.CurrentContext.Random.NextUInt(); + + int fpcr = rnd & (1 << (int)Fpcr.Fz); + fpcr |= rnd & (1 << (int)Fpcr.Dn); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, fpcr: fpcr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Ioc | Fpsr.Idc); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs new file mode 100644 index 00000000..9816bc2c --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs @@ -0,0 +1,1280 @@ +#define SimdShImm + +using ARMeilleure.State; +using NUnit.Framework; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdShImm")] + public sealed class CpuTestSimdShImm : CpuTest + { +#if SimdShImm + + #region "ValueSource (Types)" + private static ulong[] _1D_() + { + return new[] { + 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _1H_() + { + return new[] { + 0x0000000000000000ul, 0x0000000000007FFFul, + 0x0000000000008000ul, 0x000000000000FFFFul, + }; + } + + private static ulong[] _1S_() + { + return new[] { + 0x0000000000000000ul, 0x000000007FFFFFFFul, + 0x0000000080000000ul, 0x00000000FFFFFFFFul, + }; + } + + private static ulong[] _2S_() + { + return new[] { + 0x0000000000000000ul, 0x7FFFFFFF7FFFFFFFul, + 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _4H_() + { + return new[] { + 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, + 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _8B_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static IEnumerable _2S_F_W_() + { + // int + yield return 0xCF000001CF000001ul; // -2.1474839E9f (-2147483904) + yield return 0xCF000000CF000000ul; // -2.14748365E9f (-2147483648) + yield return 0xCEFFFFFFCEFFFFFFul; // -2.14748352E9f (-2147483520) + yield return 0x4F0000014F000001ul; // 2.1474839E9f (2147483904) + yield return 0x4F0000004F000000ul; // 2.14748365E9f (2147483648) + yield return 0x4EFFFFFF4EFFFFFFul; // 2.14748352E9f (2147483520) + + // uint + yield return 0x4F8000014F800001ul; // 4.2949678E9f (4294967808) + yield return 0x4F8000004F800000ul; // 4.2949673E9f (4294967296) + yield return 0x4F7FFFFF4F7FFFFFul; // 4.29496704E9f (4294967040) + + yield return 0xFF7FFFFFFF7FFFFFul; // -Max Normal (float.MinValue) + yield return 0x8080000080800000ul; // -Min Normal + yield return 0x807FFFFF807FFFFFul; // -Max Subnormal + yield return 0x8000000180000001ul; // -Min Subnormal (-float.Epsilon) + yield return 0x7F7FFFFF7F7FFFFFul; // +Max Normal (float.MaxValue) + yield return 0x0080000000800000ul; // +Min Normal + yield return 0x007FFFFF007FFFFFul; // +Max Subnormal + yield return 0x0000000100000001ul; // +Min Subnormal (float.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000080000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFF800000FF800000ul; // -Infinity + yield return 0x7F8000007F800000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFC00000FFC00000ul; // -QNaN (all zeros payload) (float.NaN) + yield return 0xFFBFFFFFFFBFFFFFul; // -SNaN (all ones payload) + yield return 0x7FC000007FC00000ul; // +QNaN (all zeros payload) (-float.NaN) (DefaultNaN) + yield return 0x7FBFFFFF7FBFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = (uint)BitConverter.SingleToInt32Bits((int)TestContext.CurrentContext.Random.NextUInt()); + ulong rnd2 = (uint)BitConverter.SingleToInt32Bits(TestContext.CurrentContext.Random.NextUInt()); + + ulong rnd3 = GenNormalS(); + ulong rnd4 = GenSubnormalS(); + + yield return (rnd1 << 32) | rnd1; + yield return (rnd2 << 32) | rnd2; + + yield return (rnd3 << 32) | rnd3; + yield return (rnd4 << 32) | rnd4; + } + } + + private static IEnumerable _1D_F_X_() + { + // long + yield return 0xC3E0000000000001ul; // -9.2233720368547780E18d (-9223372036854778000) + yield return 0xC3E0000000000000ul; // -9.2233720368547760E18d (-9223372036854776000) + yield return 0xC3DFFFFFFFFFFFFFul; // -9.2233720368547750E18d (-9223372036854775000) + yield return 0x43E0000000000001ul; // 9.2233720368547780E18d (9223372036854778000) + yield return 0x43E0000000000000ul; // 9.2233720368547760E18d (9223372036854776000) + yield return 0x43DFFFFFFFFFFFFFul; // 9.2233720368547750E18d (9223372036854775000) + + // ulong + yield return 0x43F0000000000001ul; // 1.8446744073709556e19d (18446744073709556000) + yield return 0x43F0000000000000ul; // 1.8446744073709552E19d (18446744073709552000) + yield return 0x43EFFFFFFFFFFFFFul; // 1.8446744073709550e19d (18446744073709550000) + + yield return 0xFFEFFFFFFFFFFFFFul; // -Max Normal (double.MinValue) + yield return 0x8010000000000000ul; // -Min Normal + yield return 0x800FFFFFFFFFFFFFul; // -Max Subnormal + yield return 0x8000000000000001ul; // -Min Subnormal (-double.Epsilon) + yield return 0x7FEFFFFFFFFFFFFFul; // +Max Normal (double.MaxValue) + yield return 0x0010000000000000ul; // +Min Normal + yield return 0x000FFFFFFFFFFFFFul; // +Max Subnormal + yield return 0x0000000000000001ul; // +Min Subnormal (double.Epsilon) + + if (!_noZeros) + { + yield return 0x8000000000000000ul; // -Zero + yield return 0x0000000000000000ul; // +Zero + } + + if (!_noInfs) + { + yield return 0xFFF0000000000000ul; // -Infinity + yield return 0x7FF0000000000000ul; // +Infinity + } + + if (!_noNaNs) + { + yield return 0xFFF8000000000000ul; // -QNaN (all zeros payload) (double.NaN) + yield return 0xFFF7FFFFFFFFFFFFul; // -SNaN (all ones payload) + yield return 0x7FF8000000000000ul; // +QNaN (all zeros payload) (-double.NaN) (DefaultNaN) + yield return 0x7FF7FFFFFFFFFFFFul; // +SNaN (all ones payload) + } + + for (int cnt = 1; cnt <= RndCnt; cnt++) + { + ulong rnd1 = (ulong)BitConverter.DoubleToInt64Bits( + (long)TestContext.CurrentContext.Random.NextULong()); + ulong rnd2 = (ulong)BitConverter.DoubleToInt64Bits( + TestContext.CurrentContext.Random.NextULong()); + + ulong rnd3 = GenNormalD(); + ulong rnd4 = GenSubnormalD(); + + yield return rnd1; + yield return rnd2; + + yield return rnd3; + yield return rnd4; + } + } + #endregion + + #region "ValueSource (Opcodes)" + private static uint[] _F_Cvt_Z_SU_V_Fixed_2S_4S_() + { + return new[] + { + 0x0F20FC00u, // FCVTZS V0.2S, V0.2S, #32 + 0x2F20FC00u, // FCVTZU V0.2S, V0.2S, #32 + }; + } + + private static uint[] _F_Cvt_Z_SU_V_Fixed_2D_() + { + return new[] + { + 0x4F40FC00u, // FCVTZS V0.2D, V0.2D, #64 + 0x6F40FC00u, // FCVTZU V0.2D, V0.2D, #64 + }; + } + + private static uint[] _SU_Cvt_F_S_Fixed_S_() + { + return new[] + { + 0x5F20E420u, // SCVTF S0, S1, #32 + 0x7F20E420u, // UCVTF S0, S1, #32 + }; + } + + private static uint[] _SU_Cvt_F_S_Fixed_D_() + { + return new[] + { + 0x5F40E420u, // SCVTF D0, D1, #64 + 0x7F40E420u, // UCVTF D0, D1, #64 + }; + } + + private static uint[] _SU_Cvt_F_V_Fixed_2S_4S_() + { + return new[] + { + 0x0F20E400u, // SCVTF V0.2S, V0.2S, #32 + 0x2F20E400u, // UCVTF V0.2S, V0.2S, #32 + }; + } + + private static uint[] _SU_Cvt_F_V_Fixed_2D_() + { + return new[] + { + 0x4F40E400u, // SCVTF V0.2D, V0.2D, #64 + 0x6F40E400u, // UCVTF V0.2D, V0.2D, #64 + }; + } + + private static uint[] _Shl_Sli_S_D_() + { + return new[] + { + 0x5F405400u, // SHL D0, D0, #0 + 0x7F405400u, // SLI D0, D0, #0 + }; + } + + private static uint[] _Shl_Sli_V_8B_16B_() + { + return new[] + { + 0x0F085400u, // SHL V0.8B, V0.8B, #0 + 0x2F085400u, // SLI V0.8B, V0.8B, #0 + }; + } + + private static uint[] _Shl_Sli_V_4H_8H_() + { + return new[] + { + 0x0F105400u, // SHL V0.4H, V0.4H, #0 + 0x2F105400u, // SLI V0.4H, V0.4H, #0 + }; + } + + private static uint[] _Shl_Sli_V_2S_4S_() + { + return new[] + { + 0x0F205400u, // SHL V0.2S, V0.2S, #0 + 0x2F205400u, // SLI V0.2S, V0.2S, #0 + }; + } + + private static uint[] _Shl_Sli_V_2D_() + { + return new[] + { + 0x4F405400u, // SHL V0.2D, V0.2D, #0 + 0x6F405400u, // SLI V0.2D, V0.2D, #0 + }; + } + + private static uint[] _SU_Shll_V_8B8H_16B8H_() + { + return new[] + { + 0x0F08A400u, // SSHLL V0.8H, V0.8B, #0 + 0x2F08A400u, // USHLL V0.8H, V0.8B, #0 + }; + } + + private static uint[] _SU_Shll_V_4H4S_8H4S_() + { + return new[] + { + 0x0F10A400u, // SSHLL V0.4S, V0.4H, #0 + 0x2F10A400u, // USHLL V0.4S, V0.4H, #0 + }; + } + + private static uint[] _SU_Shll_V_2S2D_4S2D_() + { + return new[] + { + 0x0F20A400u, // SSHLL V0.2D, V0.2S, #0 + 0x2F20A400u, // USHLL V0.2D, V0.2S, #0 + }; + } + + private static uint[] _ShlImm_S_D_() + { + return new[] + { + 0x5F407400u, // SQSHL D0, D0, #0 + }; + } + + private static uint[] _ShlImm_V_8B_16B_() + { + return new[] + { + 0x0F087400u, // SQSHL V0.8B, V0.8B, #0 + }; + } + + private static uint[] _ShlImm_V_4H_8H_() + { + return new[] + { + 0x0F107400u, // SQSHL V0.4H, V0.4H, #0 + }; + } + + private static uint[] _ShlImm_V_2S_4S_() + { + return new[] + { + 0x0F207400u, // SQSHL V0.2S, V0.2S, #0 + }; + } + + private static uint[] _ShlImm_V_2D_() + { + return new[] + { + 0x4F407400u, // SQSHL V0.2D, V0.2D, #0 + }; + } + + private static uint[] _ShrImm_Sri_S_D_() + { + return new[] + { + 0x7F404400u, // SRI D0, D0, #64 + 0x5F402400u, // SRSHR D0, D0, #64 + 0x5F403400u, // SRSRA D0, D0, #64 + 0x5F400400u, // SSHR D0, D0, #64 + 0x5F401400u, // SSRA D0, D0, #64 + 0x7F402400u, // URSHR D0, D0, #64 + 0x7F403400u, // URSRA D0, D0, #64 + 0x7F400400u, // USHR D0, D0, #64 + 0x7F401400u, // USRA D0, D0, #64 + }; + } + + private static uint[] _ShrImm_Sri_V_8B_16B_() + { + return new[] + { + 0x2F084400u, // SRI V0.8B, V0.8B, #8 + 0x0F082400u, // SRSHR V0.8B, V0.8B, #8 + 0x0F083400u, // SRSRA V0.8B, V0.8B, #8 + 0x0F080400u, // SSHR V0.8B, V0.8B, #8 + 0x0F081400u, // SSRA V0.8B, V0.8B, #8 + 0x2F082400u, // URSHR V0.8B, V0.8B, #8 + 0x2F083400u, // URSRA V0.8B, V0.8B, #8 + 0x2F080400u, // USHR V0.8B, V0.8B, #8 + 0x2F081400u, // USRA V0.8B, V0.8B, #8 + }; + } + + private static uint[] _ShrImm_Sri_V_4H_8H_() + { + return new[] + { + 0x2F104400u, // SRI V0.4H, V0.4H, #16 + 0x0F102400u, // SRSHR V0.4H, V0.4H, #16 + 0x0F103400u, // SRSRA V0.4H, V0.4H, #16 + 0x0F100400u, // SSHR V0.4H, V0.4H, #16 + 0x0F101400u, // SSRA V0.4H, V0.4H, #16 + 0x2F102400u, // URSHR V0.4H, V0.4H, #16 + 0x2F103400u, // URSRA V0.4H, V0.4H, #16 + 0x2F100400u, // USHR V0.4H, V0.4H, #16 + 0x2F101400u, // USRA V0.4H, V0.4H, #16 + }; + } + + private static uint[] _ShrImm_Sri_V_2S_4S_() + { + return new[] + { + 0x2F204400u, // SRI V0.2S, V0.2S, #32 + 0x0F202400u, // SRSHR V0.2S, V0.2S, #32 + 0x0F203400u, // SRSRA V0.2S, V0.2S, #32 + 0x0F200400u, // SSHR V0.2S, V0.2S, #32 + 0x0F201400u, // SSRA V0.2S, V0.2S, #32 + 0x2F202400u, // URSHR V0.2S, V0.2S, #32 + 0x2F203400u, // URSRA V0.2S, V0.2S, #32 + 0x2F200400u, // USHR V0.2S, V0.2S, #32 + 0x2F201400u, // USRA V0.2S, V0.2S, #32 + }; + } + + private static uint[] _ShrImm_Sri_V_2D_() + { + return new[] + { + 0x6F404400u, // SRI V0.2D, V0.2D, #64 + 0x4F402400u, // SRSHR V0.2D, V0.2D, #64 + 0x4F403400u, // SRSRA V0.2D, V0.2D, #64 + 0x4F400400u, // SSHR V0.2D, V0.2D, #64 + 0x4F401400u, // SSRA V0.2D, V0.2D, #64 + 0x6F402400u, // URSHR V0.2D, V0.2D, #64 + 0x6F403400u, // URSRA V0.2D, V0.2D, #64 + 0x6F400400u, // USHR V0.2D, V0.2D, #64 + 0x6F401400u, // USRA V0.2D, V0.2D, #64 + }; + } + + private static uint[] _ShrImmNarrow_V_8H8B_8H16B_() + { + return new[] + { + 0x0F088C00u, // RSHRN V0.8B, V0.8H, #8 + 0x0F088400u, // SHRN V0.8B, V0.8H, #8 + }; + } + + private static uint[] _ShrImmNarrow_V_4S4H_4S8H_() + { + return new[] + { + 0x0F108C00u, // RSHRN V0.4H, V0.4S, #16 + 0x0F108400u, // SHRN V0.4H, V0.4S, #16 + }; + } + + private static uint[] _ShrImmNarrow_V_2D2S_2D4S_() + { + return new[] + { + 0x0F208C00u, // RSHRN V0.2S, V0.2D, #32 + 0x0F208400u, // SHRN V0.2S, V0.2D, #32 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_S_HB_() + { + return new[] + { + 0x5F089C00u, // SQRSHRN B0, H0, #8 + 0x7F089C00u, // UQRSHRN B0, H0, #8 + 0x7F088C00u, // SQRSHRUN B0, H0, #8 + 0x5F089400u, // SQSHRN B0, H0, #8 + 0x7F089400u, // UQSHRN B0, H0, #8 + 0x7F088400u, // SQSHRUN B0, H0, #8 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_S_SH_() + { + return new[] + { + 0x5F109C00u, // SQRSHRN H0, S0, #16 + 0x7F109C00u, // UQRSHRN H0, S0, #16 + 0x7F108C00u, // SQRSHRUN H0, S0, #16 + 0x5F109400u, // SQSHRN H0, S0, #16 + 0x7F109400u, // UQSHRN H0, S0, #16 + 0x7F108400u, // SQSHRUN H0, S0, #16 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_S_DS_() + { + return new[] + { + 0x5F209C00u, // SQRSHRN S0, D0, #32 + 0x7F209C00u, // UQRSHRN S0, D0, #32 + 0x7F208C00u, // SQRSHRUN S0, D0, #32 + 0x5F209400u, // SQSHRN S0, D0, #32 + 0x7F209400u, // UQSHRN S0, D0, #32 + 0x7F208400u, // SQSHRUN S0, D0, #32 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_V_8H8B_8H16B_() + { + return new[] + { + 0x0F089C00u, // SQRSHRN V0.8B, V0.8H, #8 + 0x2F089C00u, // UQRSHRN V0.8B, V0.8H, #8 + 0x2F088C00u, // SQRSHRUN V0.8B, V0.8H, #8 + 0x0F089400u, // SQSHRN V0.8B, V0.8H, #8 + 0x2F089400u, // UQSHRN V0.8B, V0.8H, #8 + 0x2F088400u, // SQSHRUN V0.8B, V0.8H, #8 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_V_4S4H_4S8H_() + { + return new[] + { + 0x0F109C00u, // SQRSHRN V0.4H, V0.4S, #16 + 0x2F109C00u, // UQRSHRN V0.4H, V0.4S, #16 + 0x2F108C00u, // SQRSHRUN V0.4H, V0.4S, #16 + 0x0F109400u, // SQSHRN V0.4H, V0.4S, #16 + 0x2F109400u, // UQSHRN V0.4H, V0.4S, #16 + 0x2F108400u, // SQSHRUN V0.4H, V0.4S, #16 + }; + } + + private static uint[] _ShrImmSaturatingNarrow_V_2D2S_2D4S_() + { + return new[] + { + 0x0F209C00u, // SQRSHRN V0.2S, V0.2D, #32 + 0x2F209C00u, // UQRSHRN V0.2S, V0.2D, #32 + 0x2F208C00u, // SQRSHRUN V0.2S, V0.2D, #32 + 0x0F209400u, // SQSHRN V0.2S, V0.2D, #32 + 0x2F209400u, // UQSHRN V0.2S, V0.2D, #32 + 0x2F208400u, // SQSHRUN V0.2S, V0.2D, #32 + }; + } + #endregion + + private const int RndCnt = 2; + + private static readonly bool _noZeros = false; + private static readonly bool _noInfs = false; + private static readonly bool _noNaNs = false; + + [Test, Pairwise] + [Explicit] + public void F_Cvt_Z_SU_V_Fixed_2S_4S([ValueSource(nameof(_F_Cvt_Z_SU_V_Fixed_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_F_W_))] ulong z, + [ValueSource(nameof(_2S_F_W_))] ulong a, + [Values(1u, 32u)] uint fBits, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint immHb = (64 - fBits) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void F_Cvt_Z_SU_V_Fixed_2D([ValueSource(nameof(_F_Cvt_Z_SU_V_Fixed_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_F_X_))] ulong z, + [ValueSource(nameof(_1D_F_X_))] ulong a, + [Values(1u, 64u)] uint fBits) + { + uint immHb = (128 - fBits) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_S_Fixed_S([ValueSource(nameof(_SU_Cvt_F_S_Fixed_S_))] uint opcodes, + [ValueSource(nameof(_1S_))] ulong a, + [Values(1u, 32u)] uint fBits) + { + uint immHb = (64 - fBits) & 0x7F; + + opcodes |= (immHb << 16); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_S_Fixed_D([ValueSource(nameof(_SU_Cvt_F_S_Fixed_D_))] uint opcodes, + [ValueSource(nameof(_1D_))] ulong a, + [Values(1u, 64u)] uint fBits) + { + uint immHb = (128 - fBits) & 0x7F; + + opcodes |= (immHb << 16); + + ulong z = TestContext.CurrentContext.Random.NextULong(); + V128 v0 = MakeVectorE1(z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_V_Fixed_2S_4S([ValueSource(nameof(_SU_Cvt_F_V_Fixed_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_2S_))] ulong a, + [Values(1u, 32u)] uint fBits, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint immHb = (64 - fBits) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + [Explicit] + public void SU_Cvt_F_V_Fixed_2D([ValueSource(nameof(_SU_Cvt_F_V_Fixed_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [Values(1u, 64u)] uint fBits) + { + uint immHb = (128 - fBits) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Shl_Sli_S_D([ValueSource(nameof(_Shl_Sli_S_D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [Values(0u, 63u)] uint shift) + { + uint immHb = (64 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Shl_Sli_V_8B_16B([ValueSource(nameof(_Shl_Sli_V_8B_16B_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [Values(0u, 7u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + uint immHb = (8 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Shl_Sli_V_4H_8H([ValueSource(nameof(_Shl_Sli_V_4H_8H_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H_))] ulong z, + [ValueSource(nameof(_4H_))] ulong a, + [Values(0u, 15u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + uint immHb = (16 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Shl_Sli_V_2S_4S([ValueSource(nameof(_Shl_Sli_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_2S_))] ulong a, + [Values(0u, 31u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint immHb = (32 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Shl_Sli_V_2D([ValueSource(nameof(_Shl_Sli_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [Values(0u, 63u)] uint shift) + { + uint immHb = (64 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Shll_V_8B8H_16B8H([ValueSource(nameof(_SU_Shll_V_8B8H_16B8H_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [Values(0u, 7u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <8B8H, 16B8H> + { + uint immHb = (8 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Shll_V_4H4S_8H4S([ValueSource(nameof(_SU_Shll_V_4H4S_8H4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H_))] ulong z, + [ValueSource(nameof(_4H_))] ulong a, + [Values(0u, 15u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <4H4S, 8H4S> + { + uint immHb = (16 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void SU_Shll_V_2S2D_4S2D([ValueSource(nameof(_SU_Shll_V_2S2D_4S2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_2S_))] ulong a, + [Values(0u, 31u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <2S2D, 4S2D> + { + uint immHb = (32 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(q == 0u ? a : 0ul, q == 1u ? a : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShlImm_S_D([ValueSource(nameof(_ShlImm_S_D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [Values(1u, 64u)] uint shift) + { + uint immHb = (64 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShlImm_V_8B_16B([ValueSource(nameof(_ShlImm_V_8B_16B_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [Values(1u, 8u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + uint immHb = (8 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShlImm_V_4H_8H([ValueSource(nameof(_ShlImm_V_4H_8H_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H_))] ulong z, + [ValueSource(nameof(_4H_))] ulong a, + [Values(1u, 16u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + uint immHb = (16 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShlImm_V_2S_4S([ValueSource(nameof(_ShlImm_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_2S_))] ulong a, + [Values(1u, 32u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint immHb = (32 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= (((q | (immHb >> 6)) & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShlImm_V_2D([ValueSource(nameof(_ShlImm_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [Values(1u, 64u)] uint shift) + { + uint immHb = (64 + shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImm_Sri_S_D([ValueSource(nameof(_ShrImm_Sri_S_D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [Values(1u, 64u)] uint shift) + { + uint immHb = (128 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImm_Sri_V_8B_16B([ValueSource(nameof(_ShrImm_Sri_V_8B_16B_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong a, + [Values(1u, 8u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + uint immHb = (16 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImm_Sri_V_4H_8H([ValueSource(nameof(_ShrImm_Sri_V_4H_8H_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H_))] ulong z, + [ValueSource(nameof(_4H_))] ulong a, + [Values(1u, 16u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <4H, 8H> + { + uint immHb = (32 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImm_Sri_V_2S_4S([ValueSource(nameof(_ShrImm_Sri_V_2S_4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_2S_))] ulong a, + [Values(1u, 32u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <2S, 4S> + { + uint immHb = (64 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a * q); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImm_Sri_V_2D([ValueSource(nameof(_ShrImm_Sri_V_2D_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [Values(1u, 64u)] uint shift) + { + uint immHb = (128 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImmNarrow_V_8H8B_8H16B([ValueSource(nameof(_ShrImmNarrow_V_8H8B_8H16B_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H_))] ulong z, + [ValueSource(nameof(_4H_))] ulong a, + [Values(1u, 8u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <8H8B, 8H16B> + { + uint immHb = (16 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImmNarrow_V_4S4H_4S8H([ValueSource(nameof(_ShrImmNarrow_V_4S4H_4S8H_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_2S_))] ulong a, + [Values(1u, 16u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <4S4H, 4S8H> + { + uint immHb = (32 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImmNarrow_V_2D2S_2D4S([ValueSource(nameof(_ShrImmNarrow_V_2D2S_2D4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [Values(1u, 32u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <2D2S, 2D4S> + { + uint immHb = (64 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_S_HB([ValueSource(nameof(_ShrImmSaturatingNarrow_S_HB_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1H_))] ulong z, + [ValueSource(nameof(_1H_))] ulong a, + [Values(1u, 8u)] uint shift) + { + uint immHb = (16 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_S_SH([ValueSource(nameof(_ShrImmSaturatingNarrow_S_SH_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1S_))] ulong z, + [ValueSource(nameof(_1S_))] ulong a, + [Values(1u, 16u)] uint shift) + { + uint immHb = (32 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_S_DS([ValueSource(nameof(_ShrImmSaturatingNarrow_S_DS_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [Values(1u, 32u)] uint shift) + { + uint immHb = (64 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_V_8H8B_8H16B([ValueSource(nameof(_ShrImmSaturatingNarrow_V_8H8B_8H16B_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_4H_))] ulong z, + [ValueSource(nameof(_4H_))] ulong a, + [Values(1u, 8u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <8H8B, 8H16B> + { + uint immHb = (16 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_V_4S4H_4S8H([ValueSource(nameof(_ShrImmSaturatingNarrow_V_4S4H_4S8H_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_2S_))] ulong a, + [Values(1u, 16u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <4S4H, 4S8H> + { + uint immHb = (32 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void ShrImmSaturatingNarrow_V_2D2S_2D4S([ValueSource(nameof(_ShrImmSaturatingNarrow_V_2D2S_2D4S_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u, 0u)] uint rn, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong a, + [Values(1u, 32u)] uint shift, + [Values(0b0u, 0b1u)] uint q) // <2D2S, 2D4S> + { + uint immHb = (64 - shift) & 0x7F; + + opcodes |= ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= (immHb << 16); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0(a); + + SingleOpcode(opcodes, v0: v0, v1: v1); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdShImm32.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdShImm32.cs new file mode 100644 index 00000000..7375f4d5 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdShImm32.cs @@ -0,0 +1,362 @@ +#define SimdShImm32 + +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdShImm32")] + public sealed class CpuTestSimdShImm32 : CpuTest32 + { +#if SimdShImm32 + + #region "ValueSource (Types)" + private static ulong[] _1D_() + { + return new[] { + 0x0000000000000000ul, 0x7FFFFFFFFFFFFFFFul, + 0x8000000000000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _2S_() + { + return new[] + { + 0x0000000000000000ul, 0x7FFFFFFF7FFFFFFFul, 0x8000000080000000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _4H_() + { + return new[] + { + 0x0000000000000000ul, 0x7FFF7FFF7FFF7FFFul, 0x8000800080008000ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static ulong[] _8B_() + { + return new[] + { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, 0x8080808080808080ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + #endregion + + #region "ValueSource (Opcodes)" + private static uint[] _Vshr_Imm_SU8_() + { + return new[] + { + 0xf2880010u, // VSHR.S8 D0, D0, #8 + 0xf2880110u, // VSRA.S8 D0, D0, #8 + 0xf2880210u, // VRSHR.S8 D0, D0, #8 + 0xf2880310u, // VRSRA.S8 D0, D0, #8 + }; + } + + private static uint[] _Vshr_Imm_SU16_() + { + return new[] + { + 0xf2900010u, // VSHR.S16 D0, D0, #16 + 0xf2900110u, // VSRA.S16 D0, D0, #16 + 0xf2900210u, // VRSHR.S16 D0, D0, #16 + 0xf2900310u, // VRSRA.S16 D0, D0, #16 + }; + } + + private static uint[] _Vshr_Imm_SU32_() + { + return new[] + { + 0xf2a00010u, // VSHR.S32 D0, D0, #32 + 0xf2a00110u, // VSRA.S32 D0, D0, #32 + 0xf2a00210u, // VRSHR.S32 D0, D0, #32 + 0xf2a00310u, // VRSRA.S32 D0, D0, #32 + }; + } + + private static uint[] _Vshr_Imm_SU64_() + { + return new[] + { + 0xf2800190u, // VSRA.S64 D0, D0, #64 + 0xf2800290u, // VRSHR.S64 D0, D0, #64 + 0xf2800090u, // VSHR.S64 D0, D0, #64 + }; + } + + private static uint[] _Vqshrn_Vqrshrn_Vrshrn_Imm_() + { + return new[] + { + 0xf2800910u, // VORR.I16 D0, #0 (immediate value changes it into QSHRN) + 0xf2800950u, // VORR.I16 Q0, #0 (immediate value changes it into QRSHRN) + 0xf2800850u, // VMOV.I16 Q0, #0 (immediate value changes it into RSHRN) + }; + } + + private static uint[] _Vqshrun_Vqrshrun_Imm_() + { + return new[] + { + 0xf3800810u, // VMOV.I16 D0, #0x80 (immediate value changes it into QSHRUN) + 0xf3800850u, // VMOV.I16 Q0, #0x80 (immediate value changes it into QRSHRUN) + }; + } + #endregion + + private const int RndCnt = 2; + private const int RndCntShiftImm = 2; + + [Test, Pairwise] + public void Vshr_Imm_SU8([ValueSource(nameof(_Vshr_Imm_SU8_))] uint opcode, + [Range(0u, 3u)] uint rd, + [Range(0u, 3u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong b, + [Values(1u, 8u)] uint shiftImm, + [Values] bool u, + [Values] bool q) + { + uint imm6 = 16 - shiftImm; + + Vshr_Imm_SU(opcode, rd, rm, z, b, imm6, u, q); + } + + [Test, Pairwise] + public void Vshr_Imm_SU16([ValueSource(nameof(_Vshr_Imm_SU16_))] uint opcode, + [Range(0u, 3u)] uint rd, + [Range(0u, 3u)] uint rm, + [ValueSource(nameof(_4H_))] ulong z, + [ValueSource(nameof(_4H_))] ulong b, + [Values(1u, 16u)] uint shiftImm, + [Values] bool u, + [Values] bool q) + { + uint imm6 = 32 - shiftImm; + + Vshr_Imm_SU(opcode, rd, rm, z, b, imm6, u, q); + } + + [Test, Pairwise] + public void Vshr_Imm_SU32([ValueSource(nameof(_Vshr_Imm_SU32_))] uint opcode, + [Range(0u, 3u)] uint rd, + [Range(0u, 3u)] uint rm, + [ValueSource(nameof(_2S_))] ulong z, + [ValueSource(nameof(_2S_))] ulong b, + [Values(1u, 32u)] uint shiftImm, + [Values] bool u, + [Values] bool q) + { + uint imm6 = 64 - shiftImm; + + Vshr_Imm_SU(opcode, rd, rm, z, b, imm6, u, q); + } + + [Test, Pairwise] + public void Vshr_Imm_SU64([ValueSource(nameof(_Vshr_Imm_SU64_))] uint opcode, + [Range(0u, 3u)] uint rd, + [Range(0u, 3u)] uint rm, + [ValueSource(nameof(_1D_))] ulong z, + [ValueSource(nameof(_1D_))] ulong b, + [Values(1u, 64u)] uint shiftImm, + [Values] bool u, + [Values] bool q) + { + uint imm6 = 64 - shiftImm; + + Vshr_Imm_SU(opcode, rd, rm, z, b, imm6, u, q); + } + + private void Vshr_Imm_SU(uint opcode, uint rd, uint rm, ulong z, ulong b, uint imm6, bool u, bool q) + { + if (u) + { + opcode |= 1 << 24; + } + + if (q) + { + opcode |= 1 << 6; + + rd >>= 1; + rd <<= 1; + rm >>= 1; + rm <<= 1; + } + + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + + opcode |= (imm6 & 0x3f) << 16; + + V128 v0 = MakeVectorE0E1(z, ~z); + V128 v1 = MakeVectorE0E1(b, ~b); + + SingleOpcode(opcode, v0: v0, v1: v1); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VSHL. {}, , #")] + public void Vshl_Imm([Values(0u, 1u)] uint rd, + [Values(2u, 0u)] uint rm, + [Values(0u, 1u, 2u, 3u)] uint size, + [Random(RndCntShiftImm)] uint shiftImm, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b, + [Values] bool q) + { + uint opcode = 0xf2800510u; // VORR.I32 D0, #0 (immediate value changes it into SHL) + if (q) + { + opcode |= 1 << 6; + rm <<= 1; + rd <<= 1; + } + + uint imm = 1u << ((int)size + 3); + imm |= shiftImm & (imm - 1); + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((imm & 0x3f) << 16) | ((imm & 0x40) << 1); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VSHRN. , , #")] + public void Vshrn_Imm([Values(0u, 1u)] uint rd, + [Values(2u, 0u)] uint rm, + [Values(0u, 1u, 2u)] uint size, + [Random(RndCntShiftImm)] uint shiftImm, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b) + { + uint opcode = 0xf2800810u; // VMOV.I16 D0, #0 (immediate value changes it into SHRN) + + uint imm = 1u << ((int)size + 3); + imm |= shiftImm & (imm - 1); + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((imm & 0x3f) << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise, Description("VSLI. {}, , #")] + public void Vsli([Values(0u, 1u)] uint rd, + [Values(2u, 0u)] uint rm, + [Values(0u, 1u, 2u, 3u)] uint size, + [Random(RndCntShiftImm)] uint shiftImm, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b, + [Values] bool q) + { + uint opcode = 0xf3800510u; // VORR.I32 D0, #0x800000 (immediate value changes it into SLI) + if (q) + { + opcode |= 1 << 6; + rm <<= 1; + rd <<= 1; + } + + uint imm = 1u << ((int)size + 3); + imm |= shiftImm & (imm - 1); + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((imm & 0x3f) << 16) | ((imm & 0x40) << 1); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Vqshrn_Vqrshrn_Vrshrn_Imm([ValueSource(nameof(_Vqshrn_Vqrshrn_Vrshrn_Imm_))] uint opcode, + [Values(0u, 1u)] uint rd, + [Values(2u, 0u)] uint rm, + [Values(0u, 1u, 2u)] uint size, + [Random(RndCntShiftImm)] uint shiftImm, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b, + [Values] bool u) + { + uint imm = 1u << ((int)size + 3); + imm |= shiftImm & (imm - 1); + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((imm & 0x3f) << 16); + + if (u) + { + opcode |= 1u << 24; + } + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + int fpscr = (int)TestContext.CurrentContext.Random.NextUInt() & (int)Fpsr.Qc; + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, fpscr: fpscr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } + + [Test, Pairwise] + public void Vqshrun_Vqrshrun_Imm([ValueSource(nameof(_Vqshrun_Vqrshrun_Imm_))] uint opcode, + [Values(0u, 1u)] uint rd, + [Values(2u, 0u)] uint rm, + [Values(0u, 1u, 2u)] uint size, + [Random(RndCntShiftImm)] uint shiftImm, + [Random(RndCnt)] ulong z, + [Random(RndCnt)] ulong a, + [Random(RndCnt)] ulong b) + { + uint imm = 1u << ((int)size + 3); + imm |= shiftImm & (imm - 1); + + opcode |= ((rm & 0xf) << 0) | ((rm & 0x10) << 1); + opcode |= ((rd & 0xf) << 12) | ((rd & 0x10) << 18); + opcode |= ((imm & 0x3f) << 16); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(a, z); + V128 v2 = MakeVectorE0E1(b, z); + + int fpscr = (int)TestContext.CurrentContext.Random.NextUInt() & (int)Fpsr.Qc; + + SingleOpcode(opcode, v0: v0, v1: v1, v2: v2, fpscr: fpscr); + + CompareAgainstUnicorn(fpsrMask: Fpsr.Qc); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSimdTbl.cs b/src/Ryujinx.Tests/Cpu/CpuTestSimdTbl.cs new file mode 100644 index 00000000..78af6fe4 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSimdTbl.cs @@ -0,0 +1,319 @@ +#define SimdTbl + +using ARMeilleure.State; +using NUnit.Framework; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("SimdTbl")] + public sealed class CpuTestSimdTbl : CpuTest + { +#if SimdTbl + + #region "Helper methods" + private static ulong GenIdxsForTbls(int regs) + { + const byte IdxInRngMin = 0; + byte idxInRngMax = (byte)((16 * regs) - 1); + byte idxOutRngMin = (byte)(16 * regs); + const byte IdxOutRngMax = 255; + + ulong idxs = 0ul; + + for (int cnt = 1; cnt <= 8; cnt++) + { + ulong idxInRng = TestContext.CurrentContext.Random.NextByte(IdxInRngMin, idxInRngMax); + ulong idxOutRng = TestContext.CurrentContext.Random.NextByte(idxOutRngMin, IdxOutRngMax); + + ulong idx = TestContext.CurrentContext.Random.NextBool() ? idxInRng : idxOutRng; + + idxs = (idxs << 8) | idx; + } + + return idxs; + } + #endregion + + #region "ValueSource (Types)" + private static ulong[] _8B_() + { + return new[] { + 0x0000000000000000ul, 0x7F7F7F7F7F7F7F7Ful, + 0x8080808080808080ul, 0xFFFFFFFFFFFFFFFFul, + }; + } + + private static IEnumerable _GenIdxsForTbl1_() + { + yield return 0x0000000000000000ul; + yield return 0x7F7F7F7F7F7F7F7Ful; + yield return 0x8080808080808080ul; + yield return 0xFFFFFFFFFFFFFFFFul; + + for (int cnt = 1; cnt <= RndCntIdxs; cnt++) + { + yield return GenIdxsForTbls(regs: 1); + } + } + + private static IEnumerable _GenIdxsForTbl2_() + { + yield return 0x0000000000000000ul; + yield return 0x7F7F7F7F7F7F7F7Ful; + yield return 0x8080808080808080ul; + yield return 0xFFFFFFFFFFFFFFFFul; + + for (int cnt = 1; cnt <= RndCntIdxs; cnt++) + { + yield return GenIdxsForTbls(regs: 2); + } + } + + private static IEnumerable _GenIdxsForTbl3_() + { + yield return 0x0000000000000000ul; + yield return 0x7F7F7F7F7F7F7F7Ful; + yield return 0x8080808080808080ul; + yield return 0xFFFFFFFFFFFFFFFFul; + + for (int cnt = 1; cnt <= RndCntIdxs; cnt++) + { + yield return GenIdxsForTbls(regs: 3); + } + } + + private static IEnumerable _GenIdxsForTbl4_() + { + yield return 0x0000000000000000ul; + yield return 0x7F7F7F7F7F7F7F7Ful; + yield return 0x8080808080808080ul; + yield return 0xFFFFFFFFFFFFFFFFul; + + for (int cnt = 1; cnt <= RndCntIdxs; cnt++) + { + yield return GenIdxsForTbls(regs: 4); + } + } + #endregion + + #region "ValueSource (Opcodes)" + private static uint[] _SingleRegisterTable_V_8B_16B_() + { + return new[] + { + 0x0E000000u, // TBL V0.8B, { V0.16B }, V0.8B + 0x0E001000u, // TBX V0.8B, { V0.16B }, V0.8B + }; + } + + private static uint[] _TwoRegisterTable_V_8B_16B_() + { + return new[] + { + 0x0E002000u, // TBL V0.8B, { V0.16B, V1.16B }, V0.8B + 0x0E003000u, // TBX V0.8B, { V0.16B, V1.16B }, V0.8B + }; + } + + private static uint[] _ThreeRegisterTable_V_8B_16B_() + { + return new[] + { + 0x0E004000u, // TBL V0.8B, { V0.16B, V1.16B, V2.16B }, V0.8B + 0x0E005000u, // TBX V0.8B, { V0.16B, V1.16B, V2.16B }, V0.8B + }; + } + + private static uint[] _FourRegisterTable_V_8B_16B_() + { + return new[] + { + 0x0E006000u, // TBL V0.8B, { V0.16B, V1.16B, V2.16B, V3.16B }, V0.8B + 0x0E006000u, // TBX V0.8B, { V0.16B, V1.16B, V2.16B, V3.16B }, V0.8B + }; + } + #endregion + + private const int RndCntIdxs = 2; + + [Test, Pairwise] + public void SingleRegisterTable_V_8B_16B([ValueSource(nameof(_SingleRegisterTable_V_8B_16B_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u)] uint rn, + [Values(2u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong table0, + [ValueSource(nameof(_GenIdxsForTbl1_))] ulong indexes, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(table0, table0); + V128 v2 = MakeVectorE0E1(indexes, q == 1u ? indexes : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void TwoRegisterTable_V_8B_16B([ValueSource(nameof(_TwoRegisterTable_V_8B_16B_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u)] uint rn, + [Values(3u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong table0, + [ValueSource(nameof(_8B_))] ulong table1, + [ValueSource(nameof(_GenIdxsForTbl2_))] ulong indexes, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(table0, table0); + V128 v2 = MakeVectorE0E1(table1, table1); + V128 v3 = MakeVectorE0E1(indexes, q == 1u ? indexes : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, v3: v3); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Mod_TwoRegisterTable_V_8B_16B([ValueSource(nameof(_TwoRegisterTable_V_8B_16B_))] uint opcodes, + [Values(30u, 1u)] uint rd, + [Values(31u)] uint rn, + [Values(1u, 30u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong table0, + [ValueSource(nameof(_8B_))] ulong table1, + [ValueSource(nameof(_GenIdxsForTbl2_))] ulong indexes, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v30 = MakeVectorE0E1(z, z); + V128 v31 = MakeVectorE0E1(table0, table0); + V128 v0 = MakeVectorE0E1(table1, table1); + V128 v1 = MakeVectorE0E1(indexes, indexes); + + SingleOpcode(opcodes, v0: v0, v1: v1, v30: v30, v31: v31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void ThreeRegisterTable_V_8B_16B([ValueSource(nameof(_ThreeRegisterTable_V_8B_16B_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u)] uint rn, + [Values(4u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong table0, + [ValueSource(nameof(_8B_))] ulong table1, + [ValueSource(nameof(_8B_))] ulong table2, + [ValueSource(nameof(_GenIdxsForTbl3_))] ulong indexes, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(table0, table0); + V128 v2 = MakeVectorE0E1(table1, table1); + V128 v3 = MakeVectorE0E1(table2, table2); + V128 v4 = MakeVectorE0E1(indexes, q == 1u ? indexes : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, v3: v3, v4: v4); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Mod_ThreeRegisterTable_V_8B_16B([ValueSource(nameof(_ThreeRegisterTable_V_8B_16B_))] uint opcodes, + [Values(30u, 2u)] uint rd, + [Values(31u)] uint rn, + [Values(2u, 30u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong table0, + [ValueSource(nameof(_8B_))] ulong table1, + [ValueSource(nameof(_8B_))] ulong table2, + [ValueSource(nameof(_GenIdxsForTbl3_))] ulong indexes, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v30 = MakeVectorE0E1(z, z); + V128 v31 = MakeVectorE0E1(table0, table0); + V128 v0 = MakeVectorE0E1(table1, table1); + V128 v1 = MakeVectorE0E1(table2, table2); + V128 v2 = MakeVectorE0E1(indexes, indexes); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, v30: v30, v31: v31); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void FourRegisterTable_V_8B_16B([ValueSource(nameof(_FourRegisterTable_V_8B_16B_))] uint opcodes, + [Values(0u)] uint rd, + [Values(1u)] uint rn, + [Values(5u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong table0, + [ValueSource(nameof(_8B_))] ulong table1, + [ValueSource(nameof(_8B_))] ulong table2, + [ValueSource(nameof(_8B_))] ulong table3, + [ValueSource(nameof(_GenIdxsForTbl4_))] ulong indexes, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v0 = MakeVectorE0E1(z, z); + V128 v1 = MakeVectorE0E1(table0, table0); + V128 v2 = MakeVectorE0E1(table1, table1); + V128 v3 = MakeVectorE0E1(table2, table2); + V128 v4 = MakeVectorE0E1(table3, table3); + V128 v5 = MakeVectorE0E1(indexes, q == 1u ? indexes : 0ul); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, v3: v3, v4: v4, v5: v5); + + CompareAgainstUnicorn(); + } + + [Test, Pairwise] + public void Mod_FourRegisterTable_V_8B_16B([ValueSource(nameof(_FourRegisterTable_V_8B_16B_))] uint opcodes, + [Values(30u, 3u)] uint rd, + [Values(31u)] uint rn, + [Values(3u, 30u)] uint rm, + [ValueSource(nameof(_8B_))] ulong z, + [ValueSource(nameof(_8B_))] ulong table0, + [ValueSource(nameof(_8B_))] ulong table1, + [ValueSource(nameof(_8B_))] ulong table2, + [ValueSource(nameof(_8B_))] ulong table3, + [ValueSource(nameof(_GenIdxsForTbl4_))] ulong indexes, + [Values(0b0u, 0b1u)] uint q) // <8B, 16B> + { + opcodes |= ((rm & 31) << 16) | ((rn & 31) << 5) | ((rd & 31) << 0); + opcodes |= ((q & 1) << 30); + + V128 v30 = MakeVectorE0E1(z, z); + V128 v31 = MakeVectorE0E1(table0, table0); + V128 v0 = MakeVectorE0E1(table1, table1); + V128 v1 = MakeVectorE0E1(table2, table2); + V128 v2 = MakeVectorE0E1(table3, table3); + V128 v3 = MakeVectorE0E1(indexes, indexes); + + SingleOpcode(opcodes, v0: v0, v1: v1, v2: v2, v3: v3, v30: v30, v31: v31); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestSystem.cs b/src/Ryujinx.Tests/Cpu/CpuTestSystem.cs new file mode 100644 index 00000000..6c498ef0 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestSystem.cs @@ -0,0 +1,69 @@ +#define System + +using ARMeilleure.State; +using NUnit.Framework; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Cpu +{ + [Category("System")] + public sealed class CpuTestSystem : CpuTest + { +#if System + + #region "ValueSource (Types)" + private static IEnumerable _GenNzcv_() + { + yield return 0x0000000000000000ul; + yield return 0x7FFFFFFFFFFFFFFFul; + yield return 0x8000000000000000ul; + yield return 0xFFFFFFFFFFFFFFFFul; + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + ulong rnd = 0UL; + + rnd |= (v ? 1UL : 0UL) << (int)PState.VFlag; + rnd |= (c ? 1UL : 0UL) << (int)PState.CFlag; + rnd |= (z ? 1UL : 0UL) << (int)PState.ZFlag; + rnd |= (n ? 1UL : 0UL) << (int)PState.NFlag; + + yield return rnd; + } + #endregion + + #region "ValueSource (Opcodes)" + private static uint[] _MrsMsr_Nzcv_() + { + return new[] + { + 0xD53B4200u, // MRS X0, NZCV + 0xD51B4200u, // MSR NZCV, X0 + }; + } + #endregion + + [Test, Pairwise] + public void MrsMsr_Nzcv([ValueSource(nameof(_MrsMsr_Nzcv_))] uint opcodes, + [Values(0u, 1u, 31u)] uint rt, + [ValueSource(nameof(_GenNzcv_))] ulong xt) + { + opcodes |= (rt & 31) << 0; + + bool v = TestContext.CurrentContext.Random.NextBool(); + bool c = TestContext.CurrentContext.Random.NextBool(); + bool z = TestContext.CurrentContext.Random.NextBool(); + bool n = TestContext.CurrentContext.Random.NextBool(); + + ulong x31 = TestContext.CurrentContext.Random.NextULong(); + + SingleOpcode(opcodes, x0: xt, x1: xt, x31: x31, overflow: v, carry: c, zero: z, negative: n); + + CompareAgainstUnicorn(); + } +#endif + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestT32Alu.cs b/src/Ryujinx.Tests/Cpu/CpuTestT32Alu.cs new file mode 100644 index 00000000..a0d46692 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestT32Alu.cs @@ -0,0 +1,1014 @@ +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("T32Alu")] + public sealed class CpuTestT32Alu : CpuTest32 + { + [Test] + public void TestT32AluRsImm([ValueSource(nameof(RsImmTestCases))] PrecomputedThumbTestCase test) + { + RunPrecomputedTestCase(test); + } + + [Test] + public void TestT32AluImm([ValueSource(nameof(ImmTestCases))] PrecomputedThumbTestCase test) + { + RunPrecomputedTestCase(test); + } + + public static readonly PrecomputedThumbTestCase[] RsImmTestCases = + { + // TST (reg) + new() + { + Instructions = new ushort[] { 0xea18, 0x4f03, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x15a99211, 0x08a56ba3, 0x3c588032, 0xdac302ae, 0x6b7d5b2d, 0x4fe1d8dd, 0x04a574ba, 0x7873779d, 0x17a565d1, 0x63a4bf95, 0xd62594fb, 0x2b9aa84b, 0x20448ccd, 0x70b2197e, 0x00000000, 0x700001f0 }, + FinalRegs = new uint[] { 0x15a99211, 0x08a56ba3, 0x3c588032, 0xdac302ae, 0x6b7d5b2d, 0x4fe1d8dd, 0x04a574ba, 0x7873779d, 0x17a565d1, 0x63a4bf95, 0xd62594fb, 0x2b9aa84b, 0x20448ccd, 0x70b2197e, 0x00000000, 0x300001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea11, 0x5f67, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xc9754393, 0xec511f2a, 0xc365b8f1, 0xa024565a, 0x089ae8e2, 0xf0c91f23, 0x290f83f4, 0x48f2f445, 0xd3288f2b, 0x7d7b2e44, 0xe80dd37e, 0xb000697f, 0x95be1027, 0x74702206, 0x00000000, 0x200001f0 }, + FinalRegs = new uint[] { 0xc9754393, 0xec511f2a, 0xc365b8f1, 0xa024565a, 0x089ae8e2, 0xf0c91f23, 0x290f83f4, 0x48f2f445, 0xd3288f2b, 0x7d7b2e44, 0xe80dd37e, 0xb000697f, 0x95be1027, 0x74702206, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1a, 0x2fc9, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xe9c49eb7, 0x2ca13a97, 0x3fded5a8, 0x30e203e9, 0x811a9ee5, 0x504f95f2, 0x746794b4, 0xfe92b6d6, 0x7608d3c4, 0xf3c5ea36, 0x6290c8f2, 0x45a4a521, 0x359a615c, 0x25674915, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0xe9c49eb7, 0x2ca13a97, 0x3fded5a8, 0x30e203e9, 0x811a9ee5, 0x504f95f2, 0x746794b4, 0xfe92b6d6, 0x7608d3c4, 0xf3c5ea36, 0x6290c8f2, 0x45a4a521, 0x359a615c, 0x25674915, 0x00000000, 0x100001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea15, 0x0f85, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x3130c8d7, 0x5917d350, 0xdf48eedb, 0x23025883, 0x076175bb, 0x5402cc6c, 0x54a95806, 0x7f59c691, 0x9c3eeebf, 0x4b52b4d1, 0xb4eb9626, 0x21fa7996, 0x0ff0a95a, 0x6beb27fd, 0x00000000, 0x600001f0 }, + FinalRegs = new uint[] { 0x3130c8d7, 0x5917d350, 0xdf48eedb, 0x23025883, 0x076175bb, 0x5402cc6c, 0x54a95806, 0x7f59c691, 0x9c3eeebf, 0x4b52b4d1, 0xb4eb9626, 0x21fa7996, 0x0ff0a95a, 0x6beb27fd, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1b, 0x6feb, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x39889074, 0xbea8978e, 0x0331cc7a, 0x448e3b19, 0x33285e9e, 0xdf295408, 0x8444676e, 0xe6998904, 0x819e4da4, 0xb099272c, 0x101385a7, 0x71728a87, 0x76f95b3a, 0x8d5012e4, 0x00000000, 0xc00001f0 }, + FinalRegs = new uint[] { 0x39889074, 0xbea8978e, 0x0331cc7a, 0x448e3b19, 0x33285e9e, 0xdf295408, 0x8444676e, 0xe6998904, 0x819e4da4, 0xb099272c, 0x101385a7, 0x71728a87, 0x76f95b3a, 0x8d5012e4, 0x00000000, 0x000001d0 }, + }, + // AND (reg) + new() + { + Instructions = new ushort[] { 0xea18, 0x1f52, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xcbe174f1, 0x44be318c, 0x4a8a1a70, 0x1f3c8883, 0x33b316ee, 0x0591a3c5, 0x0ceff4a5, 0xd74988e2, 0xa5ef1873, 0xbd35a940, 0x52a9f4d8, 0xf8662781, 0xda558ea8, 0x4c7d50bc, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0xcbe174f1, 0x44be318c, 0x4a8a1a70, 0x1f3c8883, 0x33b316ee, 0x0591a3c5, 0x0ceff4a5, 0xd74988e2, 0xa5ef1873, 0xbd35a940, 0x52a9f4d8, 0xf8662781, 0xda558ea8, 0x4c7d50bc, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea19, 0x4f6b, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x97b9423c, 0x1c25286b, 0x50f84fef, 0xd917c24e, 0x2a5116af, 0xcc65ba10, 0xf5e9dc41, 0xf9f61d10, 0x9876cfe5, 0xd0fdd4bc, 0x95913be0, 0x844c820f, 0xfdaf9519, 0xf3fb09b6, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0x97b9423c, 0x1c25286b, 0x50f84fef, 0xd917c24e, 0x2a5116af, 0xcc65ba10, 0xf5e9dc41, 0xf9f61d10, 0x9876cfe5, 0xd0fdd4bc, 0x95913be0, 0x844c820f, 0xfdaf9519, 0xf3fb09b6, 0x00000000, 0x900001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1b, 0x3f52, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x1900757b, 0x6914c62d, 0x5eaa28ed, 0xd927c1f7, 0xfd2052ac, 0x146bcb99, 0x604f9b1d, 0xb395bf46, 0x3723ba84, 0xb909d3ec, 0x3db4365e, 0x42df68cd, 0x5fdc10cb, 0x4955b8be, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x1900757b, 0x6914c62d, 0x5eaa28ed, 0xd927c1f7, 0xfd2052ac, 0x146bcb99, 0x604f9b1d, 0xb395bf46, 0x3723ba84, 0xb909d3ec, 0x3db4365e, 0x42df68cd, 0x5fdc10cb, 0x4955b8be, 0x00000000, 0x100001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1a, 0x0f17, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x6134093f, 0x115a1456, 0xa7877f6e, 0x2070e9eb, 0x9ddf4a73, 0x14266482, 0x7f98e557, 0xbaa854e0, 0xa37f89a6, 0x641325de, 0xae2dc79b, 0x5b3f2af2, 0x476476d2, 0xb99cc9fd, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0x6134093f, 0x115a1456, 0xa7877f6e, 0x2070e9eb, 0x9ddf4a73, 0x14266482, 0x7f98e557, 0xbaa854e0, 0xa37f89a6, 0x641325de, 0xae2dc79b, 0x5b3f2af2, 0x476476d2, 0xb99cc9fd, 0x00000000, 0x700001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1b, 0x5f17, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xbaa2bc1a, 0xee3e86d4, 0x3179d65a, 0x8a63cf55, 0x48ea14f4, 0xf85c5d5b, 0x6af50974, 0xf3ded3e9, 0xdab4d6e6, 0x930c07eb, 0x8084b2dd, 0xf6518695, 0x4a3e0f7a, 0x581bd56a, 0x00000000, 0x300001f0 }, + FinalRegs = new uint[] { 0xbaa2bc1a, 0xee3e86d4, 0x3179d65a, 0x8a63cf55, 0x48ea14f4, 0xf85c5d5b, 0x6af50974, 0xf3ded3e9, 0xdab4d6e6, 0x930c07eb, 0x8084b2dd, 0xf6518695, 0x4a3e0f7a, 0x581bd56a, 0x00000000, 0x300001d0 }, + }, + // BIC (reg) + new() + { + Instructions = new ushort[] { 0xea1c, 0x0fbc, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xfc7b7a8b, 0xc1186d54, 0x0a83eda1, 0x88fed37c, 0x5438e8ea, 0xe0af3690, 0x6dba7b9f, 0xa7395bd6, 0xd43af274, 0xbb46f4c2, 0xb65dbcd5, 0xa6bd08b0, 0xb55971c7, 0x2244572e, 0x00000000, 0x700001f0 }, + FinalRegs = new uint[] { 0xfc7b7a8b, 0xc1186d54, 0x0a83eda1, 0x88fed37c, 0x5438e8ea, 0xe0af3690, 0x6dba7b9f, 0xa7395bd6, 0xd43af274, 0xbb46f4c2, 0xb65dbcd5, 0xa6bd08b0, 0xb55971c7, 0x2244572e, 0x00000000, 0xb00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1b, 0x5fe7, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x75f617c8, 0x12ac7ccd, 0x85e6d881, 0x30967bdd, 0xdf66b387, 0xb3d59ccf, 0xe3c824b4, 0xada7a9e4, 0x225da86f, 0x18e008ac, 0x51854224, 0xf3b43823, 0xde37f151, 0x6764b34a, 0x00000000, 0xe00001f0 }, + FinalRegs = new uint[] { 0x75f617c8, 0x12ac7ccd, 0x85e6d881, 0x30967bdd, 0xdf66b387, 0xb3d59ccf, 0xe3c824b4, 0xada7a9e4, 0x225da86f, 0x18e008ac, 0x51854224, 0xf3b43823, 0xde37f151, 0x6764b34a, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea14, 0x1fc3, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xde174255, 0x3968e364, 0xf1efd73b, 0x9a159a4e, 0x2b906c3e, 0xf1dfb847, 0x34e3e8f0, 0x39c33745, 0xc368a812, 0x8f3fe175, 0xe3da055f, 0x7737a5d5, 0x7464344a, 0xdb3ac192, 0x00000000, 0x000001f0 }, + FinalRegs = new uint[] { 0xde174255, 0x3968e364, 0xf1efd73b, 0x9a159a4e, 0x2b906c3e, 0xf1dfb847, 0x34e3e8f0, 0x39c33745, 0xc368a812, 0x8f3fe175, 0xe3da055f, 0x7737a5d5, 0x7464344a, 0xdb3ac192, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea18, 0x6f66, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x368ef4f3, 0x18583461, 0x94f6e104, 0x21e1c1b0, 0x009c85df, 0xe6bddfb2, 0x118e9dad, 0xcdf92eb5, 0xae18b093, 0xe24a54ab, 0x55d1a1a0, 0x0eed1bad, 0x8b6bce47, 0x20b1fdc2, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x368ef4f3, 0x18583461, 0x94f6e104, 0x21e1c1b0, 0x009c85df, 0xe6bddfb2, 0x118e9dad, 0xcdf92eb5, 0xae18b093, 0xe24a54ab, 0x55d1a1a0, 0x0eed1bad, 0x8b6bce47, 0x20b1fdc2, 0x00000000, 0x700001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1b, 0x3fc6, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x6f913e40, 0xd1814933, 0x181eb63c, 0x287a5050, 0xe5925dd9, 0x712ee261, 0xcca2e51d, 0x0e88a1ba, 0xa4c8d4c3, 0x26887e3e, 0x83b8de36, 0xc5a5d439, 0x8d2ace7a, 0x9df36292, 0x00000000, 0xe00001f0 }, + FinalRegs = new uint[] { 0x6f913e40, 0xd1814933, 0x181eb63c, 0x287a5050, 0xe5925dd9, 0x712ee261, 0xcca2e51d, 0x0e88a1ba, 0xa4c8d4c3, 0x26887e3e, 0x83b8de36, 0xc5a5d439, 0x8d2ace7a, 0x9df36292, 0x00000000, 0x200001d0 }, + }, + // MOV (reg) + new() + { + Instructions = new ushort[] { 0xea16, 0x2fdd, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x89fcb953, 0xafbf8db2, 0xad96137f, 0x7901360c, 0x513b561b, 0x2345a005, 0x0ece889b, 0xc8bb918f, 0x270458ce, 0x73bea675, 0xab735592, 0xf68e00e5, 0x88bf2dc1, 0x98601074, 0x00000000, 0xe00001f0 }, + FinalRegs = new uint[] { 0x89fcb953, 0xafbf8db2, 0xad96137f, 0x7901360c, 0x513b561b, 0x2345a005, 0x0ece889b, 0xc8bb918f, 0x270458ce, 0x73bea675, 0xab735592, 0xf68e00e5, 0x88bf2dc1, 0x98601074, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea19, 0x6fd6, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x35f5eea1, 0x75fe4252, 0x71165923, 0x13ad82d2, 0x01f69a1c, 0x33ff5351, 0x869c335f, 0x70ce9266, 0xf58868ad, 0x4f58e982, 0x89f7df88, 0xd0ba8d45, 0xf45e6e03, 0x7f653972, 0x00000000, 0x800001f0 }, + FinalRegs = new uint[] { 0x35f5eea1, 0x75fe4252, 0x71165923, 0x13ad82d2, 0x01f69a1c, 0x33ff5351, 0x869c335f, 0x70ce9266, 0xf58868ad, 0x4f58e982, 0x89f7df88, 0xd0ba8d45, 0xf45e6e03, 0x7f653972, 0x00000000, 0x600001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea14, 0x6f5d, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x1e83719a, 0x1b6405c5, 0x25d9d1d6, 0x3e5fc7f3, 0xd157d610, 0x271b5c46, 0xb65c2838, 0xe4590643, 0x2f2623d7, 0xf1155f93, 0xfa676221, 0x6fac2a1d, 0xc1fa1d8d, 0x8cfa89e1, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x1e83719a, 0x1b6405c5, 0x25d9d1d6, 0x3e5fc7f3, 0xd157d610, 0x271b5c46, 0xb65c2838, 0xe4590643, 0x2f2623d7, 0xf1155f93, 0xfa676221, 0x6fac2a1d, 0xc1fa1d8d, 0x8cfa89e1, 0x00000000, 0x500001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1c, 0x2fa2, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x431278f1, 0xd3fffe52, 0xbfb4d877, 0x10af0eeb, 0xd375b791, 0xbd19aa81, 0x45eb7ba3, 0x30e47d42, 0xc274e032, 0x6da10d33, 0xfeda1ba4, 0x3dc6205e, 0xc275197e, 0x6c8b86d1, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0x431278f1, 0xd3fffe52, 0xbfb4d877, 0x10af0eeb, 0xd375b791, 0xbd19aa81, 0x45eb7ba3, 0x30e47d42, 0xc274e032, 0x6da10d33, 0xfeda1ba4, 0x3dc6205e, 0xc275197e, 0x6c8b86d1, 0x00000000, 0x900001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1a, 0x7f7b, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x31f0830d, 0x69dda3f6, 0x983fc927, 0x0407652a, 0x32ceab65, 0xe76a77fd, 0x8a7dd0a6, 0x4892a02f, 0xeab00585, 0xa78bf230, 0x896dd5a9, 0xe3c44398, 0xc2d743d0, 0x42b03803, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x31f0830d, 0x69dda3f6, 0x983fc927, 0x0407652a, 0x32ceab65, 0xe76a77fd, 0x8a7dd0a6, 0x4892a02f, 0xeab00585, 0xa78bf230, 0x896dd5a9, 0xe3c44398, 0xc2d743d0, 0x42b03803, 0x00000000, 0x100001d0 }, + }, + // ORR (reg) + new() + { + Instructions = new ushort[] { 0xea10, 0x5f72, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x5834d41e, 0x7092ed2e, 0x8994242e, 0x7fac6d96, 0x4d896829, 0x1a578dec, 0x98649fd8, 0x3b713450, 0xca430792, 0xd68d5176, 0xfe0b5c4f, 0xd9caf416, 0xb0e9d5fa, 0x62c57422, 0x00000000, 0x800001f0 }, + FinalRegs = new uint[] { 0x5834d41e, 0x7092ed2e, 0x8994242e, 0x7fac6d96, 0x4d896829, 0x1a578dec, 0x98649fd8, 0x3b713450, 0xca430792, 0xd68d5176, 0xfe0b5c4f, 0xd9caf416, 0xb0e9d5fa, 0x62c57422, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea14, 0x0fb4, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x6842aa84, 0x0711ecb6, 0xebae7374, 0x6ea58edd, 0xa6d2837c, 0xbcc1e0d1, 0xe52c9d6c, 0x7bb5fa1c, 0xa7cd6f8a, 0x4558ddb7, 0x7adb449c, 0x95986dd8, 0x7432562c, 0x80d2595c, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0x6842aa84, 0x0711ecb6, 0xebae7374, 0x6ea58edd, 0xa6d2837c, 0xbcc1e0d1, 0xe52c9d6c, 0x7bb5fa1c, 0xa7cd6f8a, 0x4558ddb7, 0x7adb449c, 0x95986dd8, 0x7432562c, 0x80d2595c, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea14, 0x2f78, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x6be94e4c, 0x0569589b, 0xc7e6e127, 0xe5537aea, 0x323e7a85, 0x895e9a94, 0x2341f9b6, 0x9632a18a, 0xa790766f, 0x53533cf3, 0x83cec3aa, 0xa1d042af, 0xabff7e58, 0x614f9bc0, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0x6be94e4c, 0x0569589b, 0xc7e6e127, 0xe5537aea, 0x323e7a85, 0x895e9a94, 0x2341f9b6, 0x9632a18a, 0xa790766f, 0x53533cf3, 0x83cec3aa, 0xa1d042af, 0xabff7e58, 0x614f9bc0, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea12, 0x4fbc, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x2e43ac9a, 0xa6a8d4b6, 0xf5853279, 0xf152f284, 0xce9656e5, 0x07642918, 0xd6e25d4a, 0xdebc7fa6, 0x8c3af5e0, 0x3d00cd4c, 0x7e744bb4, 0x2a4b8015, 0x602ea481, 0xdef7571b, 0x00000000, 0x300001f0 }, + FinalRegs = new uint[] { 0x2e43ac9a, 0xa6a8d4b6, 0xf5853279, 0xf152f284, 0xce9656e5, 0x07642918, 0xd6e25d4a, 0xdebc7fa6, 0x8c3af5e0, 0x3d00cd4c, 0x7e744bb4, 0x2a4b8015, 0x602ea481, 0xdef7571b, 0x00000000, 0xb00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea14, 0x7f4c, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x67be4dae, 0xff0f74a8, 0xd769f9e1, 0xb4a98e0a, 0x2988a7dc, 0xb5726464, 0xb7b3fb27, 0x077e539c, 0x9c817cd4, 0xa8cc3981, 0xbe5a7591, 0xc753850a, 0xb8c612a7, 0x6d913c9b, 0x00000000, 0x900001f0 }, + FinalRegs = new uint[] { 0x67be4dae, 0xff0f74a8, 0xd769f9e1, 0xb4a98e0a, 0x2988a7dc, 0xb5726464, 0xb7b3fb27, 0x077e539c, 0x9c817cd4, 0xa8cc3981, 0xbe5a7591, 0xc753850a, 0xb8c612a7, 0x6d913c9b, 0x00000000, 0x100001d0 }, + }, + // MVN (reg) + new() + { + Instructions = new ushort[] { 0xea15, 0x0ffb, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x4a0a7b4c, 0x4e58d907, 0x386b8207, 0xcd71b0c4, 0x86dcf525, 0x8ae9dba4, 0xf5d6a418, 0xfac79f2e, 0x44cf918b, 0x5d38193b, 0xc17adeaf, 0xa4ad8a86, 0x69527ece, 0x69b75c61, 0x00000000, 0x900001f0 }, + FinalRegs = new uint[] { 0x4a0a7b4c, 0x4e58d907, 0x386b8207, 0xcd71b0c4, 0x86dcf525, 0x8ae9dba4, 0xf5d6a418, 0xfac79f2e, 0x44cf918b, 0x5d38193b, 0xc17adeaf, 0xa4ad8a86, 0x69527ece, 0x69b75c61, 0x00000000, 0xb00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1a, 0x4f01, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xce60a6df, 0xb1127c3f, 0x410d7b4a, 0xd9cfc917, 0xd1b2fc52, 0x8be1e03c, 0xde9b256d, 0xff989abd, 0x07e3c46a, 0x780e7d7c, 0xd807ce82, 0x5e5c8f2b, 0x09232f6d, 0x00746338, 0x00000000, 0x500001f0 }, + FinalRegs = new uint[] { 0xce60a6df, 0xb1127c3f, 0x410d7b4a, 0xd9cfc917, 0xd1b2fc52, 0x8be1e03c, 0xde9b256d, 0xff989abd, 0x07e3c46a, 0x780e7d7c, 0xd807ce82, 0x5e5c8f2b, 0x09232f6d, 0x00746338, 0x00000000, 0x100001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea18, 0x2f5e, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x18b1240a, 0xa896f734, 0xcd4a40bc, 0x28346a77, 0xbdf09586, 0x3c74ed70, 0x3e255ea3, 0xe55679b4, 0xcc602510, 0x9cd73bfb, 0xf21a6ddb, 0x263a4338, 0x06beb332, 0x0790ac93, 0x00000000, 0xa00001f0 }, + FinalRegs = new uint[] { 0x18b1240a, 0xa896f734, 0xcd4a40bc, 0x28346a77, 0xbdf09586, 0x3c74ed70, 0x3e255ea3, 0xe55679b4, 0xcc602510, 0x9cd73bfb, 0xf21a6ddb, 0x263a4338, 0x06beb332, 0x0790ac93, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1b, 0x7f41, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x0c25f69d, 0xc32dc28a, 0xf5e2fe71, 0xe46af209, 0x2d1b6ac8, 0xccac564c, 0x567cc561, 0x63707d28, 0xeae934c8, 0xab78e6f6, 0x2d78d86d, 0x76471cdc, 0x9b909f76, 0xa2cc099d, 0x00000000, 0x200001f0 }, + FinalRegs = new uint[] { 0x0c25f69d, 0xc32dc28a, 0xf5e2fe71, 0xe46af209, 0x2d1b6ac8, 0xccac564c, 0x567cc561, 0x63707d28, 0xeae934c8, 0xab78e6f6, 0x2d78d86d, 0x76471cdc, 0x9b909f76, 0xa2cc099d, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea19, 0x6ff6, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x6e79c449, 0xe9449bf7, 0x51f8fcb8, 0x138e0b80, 0x715312f2, 0x601ea894, 0xb01f9369, 0x02738c29, 0xee35545f, 0xb61ae4a2, 0xba412f08, 0x1d349e02, 0x56a0dfc0, 0x68cd5bfe, 0x00000000, 0x500001f0 }, + FinalRegs = new uint[] { 0x6e79c449, 0xe9449bf7, 0x51f8fcb8, 0x138e0b80, 0x715312f2, 0x601ea894, 0xb01f9369, 0x02738c29, 0xee35545f, 0xb61ae4a2, 0xba412f08, 0x1d349e02, 0x56a0dfc0, 0x68cd5bfe, 0x00000000, 0x100001d0 }, + }, + // ORN (reg) + new() + { + Instructions = new ushort[] { 0xea1b, 0x3fd0, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x77034e34, 0xd0727e58, 0x4748dbf2, 0x2becd551, 0x0a650329, 0x005548fa, 0xcfb963c2, 0x9561c965, 0xf157c850, 0x180a1a6c, 0x0252e103, 0x29d0f25a, 0xbd9bbecd, 0xbfd1347c, 0x00000000, 0x100001f0 }, + FinalRegs = new uint[] { 0x77034e34, 0xd0727e58, 0x4748dbf2, 0x2becd551, 0x0a650329, 0x005548fa, 0xcfb963c2, 0x9561c965, 0xf157c850, 0x180a1a6c, 0x0252e103, 0x29d0f25a, 0xbd9bbecd, 0xbfd1347c, 0x00000000, 0x300001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea16, 0x4f72, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x71cc7ddf, 0x67d4ce81, 0x60b04501, 0xcc90b805, 0xc5f34081, 0x5e83e9f7, 0xb5a78fa9, 0xc2497a71, 0xb20cdf14, 0x4de9f773, 0xf79525ec, 0x26534abd, 0xcd7b59d1, 0x5cfc9554, 0x00000000, 0x200001f0 }, + FinalRegs = new uint[] { 0x71cc7ddf, 0x67d4ce81, 0x60b04501, 0xcc90b805, 0xc5f34081, 0x5e83e9f7, 0xb5a78fa9, 0xc2497a71, 0xb20cdf14, 0x4de9f773, 0xf79525ec, 0x26534abd, 0xcd7b59d1, 0x5cfc9554, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1d, 0x4fa7, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x73259d94, 0xc85643db, 0xbf238eb1, 0x51648d99, 0xce2971c9, 0xf9e0e440, 0x90de33c9, 0xcf8ac8e9, 0xda964c21, 0x539eb057, 0x3a681b87, 0x11993d47, 0x05a1358f, 0xa8282529, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x73259d94, 0xc85643db, 0xbf238eb1, 0x51648d99, 0xce2971c9, 0xf9e0e440, 0x90de33c9, 0xcf8ac8e9, 0xda964c21, 0x539eb057, 0x3a681b87, 0x11993d47, 0x05a1358f, 0xa8282529, 0x00000000, 0xb00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea12, 0x3fdb, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd274d46b, 0x8937836f, 0x33b78178, 0xc250b807, 0xd3323d2f, 0x82e03ba2, 0xf93bf1a6, 0xb31e0c74, 0xc9238070, 0x957331d1, 0xfaadd1ee, 0x073d40fb, 0x05b3e8b4, 0x93e5233b, 0x00000000, 0x600001f0 }, + FinalRegs = new uint[] { 0xd274d46b, 0x8937836f, 0x33b78178, 0xc250b807, 0xd3323d2f, 0x82e03ba2, 0xf93bf1a6, 0xb31e0c74, 0xc9238070, 0x957331d1, 0xfaadd1ee, 0x073d40fb, 0x05b3e8b4, 0x93e5233b, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea15, 0x5f92, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x24755d61, 0xb65b742d, 0xb46476cf, 0x771a9fcd, 0x465b367f, 0x3daa2a47, 0x6984eeb8, 0x238e3187, 0xa9717261, 0x4592be1d, 0x46d19147, 0x6a6e4dc8, 0x4ddd896f, 0x2f899425, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0x24755d61, 0xb65b742d, 0xb46476cf, 0x771a9fcd, 0x465b367f, 0x3daa2a47, 0x6984eeb8, 0x238e3187, 0xa9717261, 0x4592be1d, 0x46d19147, 0x6a6e4dc8, 0x4ddd896f, 0x2f899425, 0x00000000, 0x300001d0 }, + }, + // TEQ (reg) + new() + { + Instructions = new ushort[] { 0xea1a, 0x2f54, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x36675cac, 0xd9259257, 0x0b8ab9ad, 0xbfef324e, 0xf9623cd6, 0xfc1919ff, 0x616b25f5, 0x2d26a3d3, 0x61eb12c8, 0xbb8d48f0, 0xbfb9c232, 0x10383506, 0x31d10885, 0xf29cb615, 0x00000000, 0x500001f0 }, + FinalRegs = new uint[] { 0x36675cac, 0xd9259257, 0x0b8ab9ad, 0xbfef324e, 0xf9623cd6, 0xfc1919ff, 0x616b25f5, 0x2d26a3d3, 0x61eb12c8, 0xbb8d48f0, 0xbfb9c232, 0x10383506, 0x31d10885, 0xf29cb615, 0x00000000, 0x100001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea17, 0x1f43, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xf7dce25d, 0xbe296478, 0xe1aee674, 0x0414c126, 0xa258cf11, 0x5347cc5f, 0x6f8ed2c9, 0xed554dbe, 0xd3073560, 0x627dbd64, 0xca8bb3fc, 0x9590e3a9, 0xe4bea6bc, 0x557934a6, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0xf7dce25d, 0xbe296478, 0xe1aee674, 0x0414c126, 0xa258cf11, 0x5347cc5f, 0x6f8ed2c9, 0xed554dbe, 0xd3073560, 0x627dbd64, 0xca8bb3fc, 0x9590e3a9, 0xe4bea6bc, 0x557934a6, 0x00000000, 0x900001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea13, 0x1f5b, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x84ad5535, 0xc1f15e65, 0x5ea0078b, 0x79df457d, 0x1c735fe5, 0x06dcfd95, 0x6db96dae, 0x572f572d, 0xac88a919, 0x56d850a6, 0xd5ce3a30, 0x2be992e8, 0x497a47ce, 0x38a74019, 0x00000000, 0xe00001f0 }, + FinalRegs = new uint[] { 0x84ad5535, 0xc1f15e65, 0x5ea0078b, 0x79df457d, 0x1c735fe5, 0x06dcfd95, 0x6db96dae, 0x572f572d, 0xac88a919, 0x56d850a6, 0xd5ce3a30, 0x2be992e8, 0x497a47ce, 0x38a74019, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea16, 0x3ff3, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xa85ea277, 0x6028643d, 0xa5a85f15, 0x47f0f2a5, 0x9b6eebdb, 0x9f064bc7, 0xab59939f, 0x1a278260, 0xb9f91cfa, 0xf913c49c, 0x2b5c0052, 0x1bf2d6dc, 0x81da80a4, 0xced90006, 0x00000000, 0x000001f0 }, + FinalRegs = new uint[] { 0xa85ea277, 0x6028643d, 0xa5a85f15, 0x47f0f2a5, 0x9b6eebdb, 0x9f064bc7, 0xab59939f, 0x1a278260, 0xb9f91cfa, 0xf913c49c, 0x2b5c0052, 0x1bf2d6dc, 0x81da80a4, 0xced90006, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea14, 0x3f09, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xdf494128, 0xbc975c16, 0x62c66823, 0x95be3737, 0xa07e8778, 0x6ce80cc7, 0xfea03385, 0x4c5bf35a, 0x5cd0bcdf, 0xc47451ab, 0x3849af70, 0x1329c14a, 0xb1f96f79, 0x321eaf12, 0x00000000, 0x700001f0 }, + FinalRegs = new uint[] { 0xdf494128, 0xbc975c16, 0x62c66823, 0x95be3737, 0xa07e8778, 0x6ce80cc7, 0xfea03385, 0x4c5bf35a, 0x5cd0bcdf, 0xc47451ab, 0x3849af70, 0x1329c14a, 0xb1f96f79, 0x321eaf12, 0x00000000, 0x300001d0 }, + }, + // EOR (reg) + new() + { + Instructions = new ushort[] { 0xea17, 0x6fc0, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x9c803ce5, 0x38e325f9, 0x4d32aea8, 0x0120f77b, 0x8e34b507, 0xee41aabf, 0x7e6d8a0c, 0x761a3f21, 0x99b57f1d, 0x32a4bbf3, 0x9902c1f4, 0xd5e2dd41, 0xe2a08209, 0x2896ceba, 0x00000000, 0xc00001f0 }, + FinalRegs = new uint[] { 0x9c803ce5, 0x38e325f9, 0x4d32aea8, 0x0120f77b, 0x8e34b507, 0xee41aabf, 0x7e6d8a0c, 0x761a3f21, 0x99b57f1d, 0x32a4bbf3, 0x9902c1f4, 0xd5e2dd41, 0xe2a08209, 0x2896ceba, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1c, 0x4f58, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xe9c3e9b7, 0x26c9e052, 0x708b6153, 0x72dbdc3c, 0xdd3e922d, 0xd0260aca, 0x38dcf6be, 0x4164575f, 0x5d8e03dc, 0x30bfa694, 0xe72a6609, 0xba632c43, 0x1f768178, 0x6b4f56a6, 0x00000000, 0x600001f0 }, + FinalRegs = new uint[] { 0xe9c3e9b7, 0x26c9e052, 0x708b6153, 0x72dbdc3c, 0xdd3e922d, 0xd0260aca, 0x38dcf6be, 0x4164575f, 0x5d8e03dc, 0x30bfa694, 0xe72a6609, 0xba632c43, 0x1f768178, 0x6b4f56a6, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea16, 0x7f31, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x9205c1f5, 0x80dd7e44, 0x8ecd5272, 0x3d8d3691, 0x35d45cca, 0x4b2d9eb3, 0xa1652285, 0x6a1cb7f1, 0x8e08b99f, 0xdf8f0c57, 0x28dd0dfa, 0xf2c0abbd, 0x167a6539, 0x75163a9d, 0x00000000, 0xa00001f0 }, + FinalRegs = new uint[] { 0x9205c1f5, 0x80dd7e44, 0x8ecd5272, 0x3d8d3691, 0x35d45cca, 0x4b2d9eb3, 0xa1652285, 0x6a1cb7f1, 0x8e08b99f, 0xdf8f0c57, 0x28dd0dfa, 0xf2c0abbd, 0x167a6539, 0x75163a9d, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea14, 0x4f80, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x96547d6c, 0x7afda83c, 0xb3edea2d, 0x49d05904, 0xb661ef76, 0xf1cce5ff, 0x2986ab1b, 0xcb39b044, 0x88937ad3, 0x962cf736, 0x80d6f109, 0xb73dd0d6, 0xb93f9f60, 0xb93a02c9, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x96547d6c, 0x7afda83c, 0xb3edea2d, 0x49d05904, 0xb661ef76, 0xf1cce5ff, 0x2986ab1b, 0xcb39b044, 0x88937ad3, 0x962cf736, 0x80d6f109, 0xb73dd0d6, 0xb93f9f60, 0xb93a02c9, 0x00000000, 0xb00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea12, 0x1f35, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xb1a94a51, 0xa8679784, 0xd7558ceb, 0x58d63d95, 0x6e5cf7eb, 0x0d40398d, 0xf88fa339, 0xbe88a56f, 0x7180f980, 0x0795ba21, 0x0732b252, 0xa51be7c8, 0x47c02749, 0xb0fbbd9f, 0x00000000, 0x800001f0 }, + FinalRegs = new uint[] { 0xb1a94a51, 0xa8679784, 0xd7558ceb, 0x58d63d95, 0x6e5cf7eb, 0x0d40398d, 0xf88fa339, 0xbe88a56f, 0x7180f980, 0x0795ba21, 0x0732b252, 0xa51be7c8, 0x47c02749, 0xb0fbbd9f, 0x00000000, 0xa00001d0 }, + }, + // CMN (reg) + new() + { + Instructions = new ushort[] { 0xea1a, 0x4fc5, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xf4440521, 0x26b151d9, 0x90053d26, 0x8c3bbde1, 0x4a757fa1, 0x34b63513, 0xd1d1a182, 0xa9123bc1, 0xadfbf652, 0xec28d3e6, 0x6ca54af1, 0x385d5637, 0x46280bac, 0x18f38d39, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0xf4440521, 0x26b151d9, 0x90053d26, 0x8c3bbde1, 0x4a757fa1, 0x34b63513, 0xd1d1a182, 0xa9123bc1, 0xadfbf652, 0xec28d3e6, 0x6ca54af1, 0x385d5637, 0x46280bac, 0x18f38d39, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea14, 0x4fe4, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x20abeba4, 0x77f9bd90, 0x5c09b098, 0xdebadd51, 0x6e114dcf, 0xd8212cf8, 0x2a6a57d2, 0xb9667ed8, 0x93817fef, 0x639cd8b4, 0x52b67bce, 0x681ee61c, 0x50bb5414, 0x9f297765, 0x00000000, 0x200001f0 }, + FinalRegs = new uint[] { 0x20abeba4, 0x77f9bd90, 0x5c09b098, 0xdebadd51, 0x6e114dcf, 0xd8212cf8, 0x2a6a57d2, 0xb9667ed8, 0x93817fef, 0x639cd8b4, 0x52b67bce, 0x681ee61c, 0x50bb5414, 0x9f297765, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1d, 0x6f51, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x12b54404, 0x9ad5df58, 0x8a73af2a, 0xd89dd454, 0x57f5a14b, 0xcee0a06f, 0xb53e67ca, 0x92730368, 0xab12a843, 0x929ae15d, 0xea1e4f49, 0xd7fadfbc, 0x9defdd99, 0xff22c9c8, 0x00000000, 0x600001f0 }, + FinalRegs = new uint[] { 0x12b54404, 0x9ad5df58, 0x8a73af2a, 0xd89dd454, 0x57f5a14b, 0xcee0a06f, 0xb53e67ca, 0x92730368, 0xab12a843, 0x929ae15d, 0xea1e4f49, 0xd7fadfbc, 0x9defdd99, 0xff22c9c8, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea15, 0x5f77, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x64dfdc90, 0xd570bc25, 0xae804ff7, 0x491ad040, 0xfe5f6d58, 0x850c1223, 0x39afac7b, 0xcc5a165a, 0x956a9473, 0x89d3941f, 0x6e520e57, 0x804dca75, 0xbd40cde8, 0xff68c0e7, 0x00000000, 0x700001f0 }, + FinalRegs = new uint[] { 0x64dfdc90, 0xd570bc25, 0xae804ff7, 0x491ad040, 0xfe5f6d58, 0x850c1223, 0x39afac7b, 0xcc5a165a, 0x956a9473, 0x89d3941f, 0x6e520e57, 0x804dca75, 0xbd40cde8, 0xff68c0e7, 0x00000000, 0xb00001d0 }, + }, + // ADD (reg) + new() + { + Instructions = new ushort[] { 0xea1c, 0x4f0b, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x8a81d956, 0x65b2b25b, 0x34dd981e, 0x924542f4, 0xeed4b95a, 0x096d832c, 0x8ddcb715, 0x2df1897b, 0x696d0d5c, 0xfa6853c1, 0xcbb52912, 0xe37a3fda, 0x54dd595d, 0x652e5a2b, 0x00000000, 0x600001f0 }, + FinalRegs = new uint[] { 0x8a81d956, 0x65b2b25b, 0x34dd981e, 0x924542f4, 0xeed4b95a, 0x096d832c, 0x8ddcb715, 0x2df1897b, 0x696d0d5c, 0xfa6853c1, 0xcbb52912, 0xe37a3fda, 0x54dd595d, 0x652e5a2b, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea12, 0x6faa, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x4b774bbf, 0xe1f3168c, 0xfcdf56d4, 0x0a9feca9, 0x8d832cd1, 0x27af5bb2, 0xe7123c8f, 0x5ae971a8, 0x7c86287f, 0x5e69f0a7, 0x43e672d3, 0xb552a0f4, 0xb8b4fc17, 0xa9cc9a9d, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0x4b774bbf, 0xe1f3168c, 0xfcdf56d4, 0x0a9feca9, 0x8d832cd1, 0x27af5bb2, 0xe7123c8f, 0x5ae971a8, 0x7c86287f, 0x5e69f0a7, 0x43e672d3, 0xb552a0f4, 0xb8b4fc17, 0xa9cc9a9d, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea18, 0x2f2c, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x907613be, 0x807d314f, 0x10328bb5, 0xf3433f78, 0x0fa6c190, 0x473e8ac5, 0x0019b12e, 0xa24d7590, 0x0fdac8d5, 0x24e4feea, 0xf5eadcbf, 0xdfd73f71, 0xee2c8957, 0xaef12e15, 0x00000000, 0x100001f0 }, + FinalRegs = new uint[] { 0x907613be, 0x807d314f, 0x10328bb5, 0xf3433f78, 0x0fa6c190, 0x473e8ac5, 0x0019b12e, 0xa24d7590, 0x0fdac8d5, 0x24e4feea, 0xf5eadcbf, 0xdfd73f71, 0xee2c8957, 0xaef12e15, 0x00000000, 0x100001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea16, 0x3f00, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x2f9a7f7c, 0xc6ad7d01, 0x12b220a4, 0x57b4e83c, 0x2132b566, 0xb4afd045, 0x2b5d39bf, 0xceeecd89, 0x724bff21, 0xb527620e, 0xa9fba943, 0xd2d70658, 0x4e69f57b, 0x55df6b8f, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x2f9a7f7c, 0xc6ad7d01, 0x12b220a4, 0x57b4e83c, 0x2132b566, 0xb4afd045, 0x2b5d39bf, 0xceeecd89, 0x724bff21, 0xb527620e, 0xa9fba943, 0xd2d70658, 0x4e69f57b, 0x55df6b8f, 0x00000000, 0x300001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea16, 0x2fdb, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x854d0c91, 0x895ded29, 0x3e81cd89, 0x9ed269cf, 0x8a7354fa, 0x95cfe79f, 0x07248663, 0x3ec81b86, 0x6f1086e0, 0x51b4c91c, 0xb2d0946b, 0x1b81a616, 0x2b03fe57, 0xfbde03fd, 0x00000000, 0x700001f0 }, + FinalRegs = new uint[] { 0x854d0c91, 0x895ded29, 0x3e81cd89, 0x9ed269cf, 0x8a7354fa, 0x95cfe79f, 0x07248663, 0x3ec81b86, 0x6f1086e0, 0x51b4c91c, 0xb2d0946b, 0x1b81a616, 0x2b03fe57, 0xfbde03fd, 0x00000000, 0x300001d0 }, + }, + // ADC (reg) + new() + { + Instructions = new ushort[] { 0xea1a, 0x3fe4, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x4333cbde, 0xb61e8731, 0x2121c6ec, 0x796ecc75, 0xb570472d, 0x203aa9ea, 0xdad7bf7e, 0x93654919, 0x9e4c4e08, 0x1e352004, 0x104a06d7, 0x6b9a4b8a, 0xa0bcd372, 0x1713789a, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0x4333cbde, 0xb61e8731, 0x2121c6ec, 0x796ecc75, 0xb570472d, 0x203aa9ea, 0xdad7bf7e, 0x93654919, 0x9e4c4e08, 0x1e352004, 0x104a06d7, 0x6b9a4b8a, 0xa0bcd372, 0x1713789a, 0x00000000, 0x300001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1b, 0x3f40, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xad935c4c, 0x4c7dcbfc, 0x802965ca, 0x1dccd2e8, 0xe960e9e1, 0x3bff0055, 0x204f6a43, 0xe31b010d, 0x33c8f3c5, 0x8e27b912, 0x1351e4a6, 0x90195167, 0xd9112661, 0xee319b51, 0x00000000, 0xe00001f0 }, + FinalRegs = new uint[] { 0xad935c4c, 0x4c7dcbfc, 0x802965ca, 0x1dccd2e8, 0xe960e9e1, 0x3bff0055, 0x204f6a43, 0xe31b010d, 0x33c8f3c5, 0x8e27b912, 0x1351e4a6, 0x90195167, 0xd9112661, 0xee319b51, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea15, 0x3fe8, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x26deb79f, 0x01ac9258, 0x5982ae34, 0x2c3c6df6, 0xabf6a749, 0x319d4b48, 0xce8cca6d, 0xe4b70851, 0x135c049c, 0xd8839d5e, 0xd3171a30, 0xfb09e096, 0x06d67a32, 0x0bab9825, 0x00000000, 0x100001f0 }, + FinalRegs = new uint[] { 0x26deb79f, 0x01ac9258, 0x5982ae34, 0x2c3c6df6, 0xabf6a749, 0x319d4b48, 0xce8cca6d, 0xe4b70851, 0x135c049c, 0xd8839d5e, 0xd3171a30, 0xfb09e096, 0x06d67a32, 0x0bab9825, 0x00000000, 0x100001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea12, 0x2fec, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd2c9ebca, 0x3ace22dc, 0x41603f45, 0x8cc7e2c2, 0xa160b815, 0xac1e7eb8, 0x57c49232, 0x62547a9b, 0xa18407fd, 0x5c549424, 0xf3eec0e1, 0x4185299f, 0x329a9063, 0x649d9b44, 0x00000000, 0x900001f0 }, + FinalRegs = new uint[] { 0xd2c9ebca, 0x3ace22dc, 0x41603f45, 0x8cc7e2c2, 0xa160b815, 0xac1e7eb8, 0x57c49232, 0x62547a9b, 0xa18407fd, 0x5c549424, 0xf3eec0e1, 0x4185299f, 0x329a9063, 0x649d9b44, 0x00000000, 0x100001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea15, 0x2f73, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xf3f8be7b, 0x05e02ec4, 0x78bd9fce, 0x6fb329c8, 0x3094f103, 0x7c93c61d, 0xaade3d9f, 0x381bf77c, 0x738389cd, 0xc68dc0e2, 0x60a06e86, 0x9b717afd, 0x9e51e4eb, 0xf7966699, 0x00000000, 0x500001f0 }, + FinalRegs = new uint[] { 0xf3f8be7b, 0x05e02ec4, 0x78bd9fce, 0x6fb329c8, 0x3094f103, 0x7c93c61d, 0xaade3d9f, 0x381bf77c, 0x738389cd, 0xc68dc0e2, 0x60a06e86, 0x9b717afd, 0x9e51e4eb, 0xf7966699, 0x00000000, 0x300001d0 }, + }, + // SBC (reg) + new() + { + Instructions = new ushort[] { 0xea1d, 0x3faa, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd839dfb1, 0x7cc5425c, 0xc55b5255, 0x37b845aa, 0x59f892d4, 0x7ef8e6ec, 0x3491a7fb, 0x87b88546, 0x72b4c444, 0x24cf48d7, 0x7f530fb7, 0x5b243902, 0x6c39c38f, 0x10e3165c, 0x00000000, 0x200001f0 }, + FinalRegs = new uint[] { 0xd839dfb1, 0x7cc5425c, 0xc55b5255, 0x37b845aa, 0x59f892d4, 0x7ef8e6ec, 0x3491a7fb, 0x87b88546, 0x72b4c444, 0x24cf48d7, 0x7f530fb7, 0x5b243902, 0x6c39c38f, 0x10e3165c, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea12, 0x1f7d, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xcd24aba8, 0x1d7d0523, 0xc150da45, 0x2f7eb96b, 0x9c1ed9af, 0x75056b89, 0x91c818d1, 0x8a07d574, 0x67ff1d4a, 0x6aca4429, 0xc4b5fb7c, 0x21e9ca50, 0xb95cbd15, 0xce3752e7, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0xcd24aba8, 0x1d7d0523, 0xc150da45, 0x2f7eb96b, 0x9c1ed9af, 0x75056b89, 0x91c818d1, 0x8a07d574, 0x67ff1d4a, 0x6aca4429, 0xc4b5fb7c, 0x21e9ca50, 0xb95cbd15, 0xce3752e7, 0x00000000, 0x100001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea10, 0x2fc2, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x401285a0, 0x7ab1e348, 0xf48a2615, 0x46d49913, 0xff5e9911, 0x4b4d7920, 0x8e1f921e, 0x05075455, 0x24e4acea, 0x8652e355, 0x11d0fe46, 0x0cfe7c08, 0xf326adee, 0x7fcde7ac, 0x00000000, 0x600001f0 }, + FinalRegs = new uint[] { 0x401285a0, 0x7ab1e348, 0xf48a2615, 0x46d49913, 0xff5e9911, 0x4b4d7920, 0x8e1f921e, 0x05075455, 0x24e4acea, 0x8652e355, 0x11d0fe46, 0x0cfe7c08, 0xf326adee, 0x7fcde7ac, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea16, 0x3f22, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x5bb504b3, 0x3dd293c9, 0x3b2b2d7c, 0x30c2876a, 0x1c99a70e, 0x741294e7, 0xfd5f7315, 0x0149b9db, 0x3975aa1c, 0x9269e207, 0xdc42fd14, 0xea6a1c89, 0xa03e7d65, 0x171c30ad, 0x00000000, 0xe00001f0 }, + FinalRegs = new uint[] { 0x5bb504b3, 0x3dd293c9, 0x3b2b2d7c, 0x30c2876a, 0x1c99a70e, 0x741294e7, 0xfd5f7315, 0x0149b9db, 0x3975aa1c, 0x9269e207, 0xdc42fd14, 0xea6a1c89, 0xa03e7d65, 0x171c30ad, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1b, 0x1f86, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xe0f5bbe3, 0xd96f3c62, 0x11944b25, 0x372e4b0e, 0x7c956b35, 0x03df46ac, 0x8f11684b, 0x3044502e, 0x6ebf2992, 0x4f3a0366, 0x9f36f014, 0x4c55f6aa, 0x6473e494, 0x8b6310d6, 0x00000000, 0xe00001f0 }, + FinalRegs = new uint[] { 0xe0f5bbe3, 0xd96f3c62, 0x11944b25, 0x372e4b0e, 0x7c956b35, 0x03df46ac, 0x8f11684b, 0x3044502e, 0x6ebf2992, 0x4f3a0366, 0x9f36f014, 0x4c55f6aa, 0x6473e494, 0x8b6310d6, 0x00000000, 0x200001d0 }, + }, + // CMP (reg) + new() + { + Instructions = new ushort[] { 0xea14, 0x6f45, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xe72e848a, 0x97499b66, 0xcde944bc, 0xf6a7e4e1, 0xd8860029, 0xc55c7e43, 0x58dc13d7, 0x5e1cf6ac, 0x8094a819, 0xdba64363, 0xd8f5423f, 0x6ae843f0, 0x69766600, 0x2814e4e6, 0x00000000, 0x600001f0 }, + FinalRegs = new uint[] { 0xe72e848a, 0x97499b66, 0xcde944bc, 0xf6a7e4e1, 0xd8860029, 0xc55c7e43, 0x58dc13d7, 0x5e1cf6ac, 0x8094a819, 0xdba64363, 0xd8f5423f, 0x6ae843f0, 0x69766600, 0x2814e4e6, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea14, 0x7fd8, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x323e5ef9, 0x46e23bdf, 0x8d69d89a, 0x9ffddd37, 0x53f4a07b, 0xe923f9bb, 0x5ea62678, 0x1709127c, 0xc0c20492, 0x0ee47a0c, 0xe137cc2e, 0x7d72db37, 0xca9eb971, 0x4447b224, 0x00000000, 0xe00001f0 }, + FinalRegs = new uint[] { 0x323e5ef9, 0x46e23bdf, 0x8d69d89a, 0x9ffddd37, 0x53f4a07b, 0xe923f9bb, 0x5ea62678, 0x1709127c, 0xc0c20492, 0x0ee47a0c, 0xe137cc2e, 0x7d72db37, 0xca9eb971, 0x4447b224, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea10, 0x3f43, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x4cce7ac7, 0x03055e03, 0x479ec669, 0x8b1d9783, 0xa59509e1, 0xa46866ef, 0x654578c4, 0x700e322b, 0xa4191329, 0xb1b8479a, 0xe555a2ce, 0x1ef22472, 0xd41fb2ae, 0x2d794684, 0x00000000, 0x200001f0 }, + FinalRegs = new uint[] { 0x4cce7ac7, 0x03055e03, 0x479ec669, 0x8b1d9783, 0xa59509e1, 0xa46866ef, 0x654578c4, 0x700e322b, 0xa4191329, 0xb1b8479a, 0xe555a2ce, 0x1ef22472, 0xd41fb2ae, 0x2d794684, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea18, 0x7fd2, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xeecfbfb2, 0xbe6288fd, 0x34c0fc94, 0x3a01b105, 0xe7dc6252, 0xc05813fa, 0x6613d82d, 0x90dc7a0c, 0x34637299, 0x58f6d0e7, 0xb151d65e, 0xca975eca, 0xf83b6533, 0x10177f01, 0x00000000, 0x600001f0 }, + FinalRegs = new uint[] { 0xeecfbfb2, 0xbe6288fd, 0x34c0fc94, 0x3a01b105, 0xe7dc6252, 0xc05813fa, 0x6613d82d, 0x90dc7a0c, 0x34637299, 0x58f6d0e7, 0xb151d65e, 0xca975eca, 0xf83b6533, 0x10177f01, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea14, 0x2f6e, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x54dcd0c5, 0x629131da, 0xc4010f0b, 0xf28a7f0f, 0x866d92a3, 0xbb9a1b32, 0xc8c7f425, 0x8d13d61f, 0x1f9a5d13, 0x83e0b2b7, 0x7ef44e14, 0x24c291a3, 0x851cc882, 0x31a056cb, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0x54dcd0c5, 0x629131da, 0xc4010f0b, 0xf28a7f0f, 0x866d92a3, 0xbb9a1b32, 0xc8c7f425, 0x8d13d61f, 0x1f9a5d13, 0x83e0b2b7, 0x7ef44e14, 0x24c291a3, 0x851cc882, 0x31a056cb, 0x00000000, 0x500001d0 }, + }, + // SUB (reg) + new() + { + Instructions = new ushort[] { 0xea1a, 0x6f56, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xf997cd9f, 0xf94c5bd7, 0x5411a289, 0x21311b8f, 0xee8a1fe4, 0x73808b62, 0x4daadf68, 0x14a1c57c, 0x92d98c4c, 0x31f999c9, 0x953b94b9, 0x108acc75, 0xcc38ea73, 0x5dc27e61, 0x00000000, 0x600001f0 }, + FinalRegs = new uint[] { 0xf997cd9f, 0xf94c5bd7, 0x5411a289, 0x21311b8f, 0xee8a1fe4, 0x73808b62, 0x4daadf68, 0x14a1c57c, 0x92d98c4c, 0x31f999c9, 0x953b94b9, 0x108acc75, 0xcc38ea73, 0x5dc27e61, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea19, 0x0f94, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x5f8de6f4, 0x09e82020, 0x480dc701, 0xd3303ca3, 0x8739e87a, 0x3da0b6d2, 0x10093787, 0xd30606fc, 0xd81d45da, 0xa66f5e86, 0xd8ddf48e, 0xa8321bd1, 0x62a75c1c, 0x3cffac30, 0x00000000, 0x800001f0 }, + FinalRegs = new uint[] { 0x5f8de6f4, 0x09e82020, 0x480dc701, 0xd3303ca3, 0x8739e87a, 0x3da0b6d2, 0x10093787, 0xd30606fc, 0xd81d45da, 0xa66f5e86, 0xd8ddf48e, 0xa8321bd1, 0x62a75c1c, 0x3cffac30, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea14, 0x7fc6, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x001f39cf, 0x76b925c8, 0x292b283a, 0x9d142282, 0x2cda04fa, 0x87f29de5, 0x9e9a98e4, 0x9d48ddbb, 0x9ea329fd, 0x653f2346, 0xfc116785, 0x6e565e16, 0x9a7f8c11, 0x46f1ecbb, 0x00000000, 0xd00001f0 }, + FinalRegs = new uint[] { 0x001f39cf, 0x76b925c8, 0x292b283a, 0x9d142282, 0x2cda04fa, 0x87f29de5, 0x9e9a98e4, 0x9d48ddbb, 0x9ea329fd, 0x653f2346, 0xfc116785, 0x6e565e16, 0x9a7f8c11, 0x46f1ecbb, 0x00000000, 0x500001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea19, 0x5fa5, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xfb6a4a50, 0xd074ee0e, 0x599131ef, 0x5db48236, 0xf287fcd1, 0xadea3b9f, 0xf2529f30, 0x6717a5af, 0xe1a3bc40, 0xd92e291b, 0x9b0337eb, 0xcab803ed, 0x255dd8a9, 0xea0e7824, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0xfb6a4a50, 0xd074ee0e, 0x599131ef, 0x5db48236, 0xf287fcd1, 0xadea3b9f, 0xf2529f30, 0x6717a5af, 0xe1a3bc40, 0xd92e291b, 0x9b0337eb, 0xcab803ed, 0x255dd8a9, 0xea0e7824, 0x00000000, 0xb00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1c, 0x6f86, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x3a000492, 0xc16be6fa, 0x20053393, 0x597617c9, 0xc30c0ac0, 0x0ed34739, 0xf964a3d4, 0x4dcf9b40, 0x93109692, 0x7ed22040, 0x1f57a26e, 0x008d29d2, 0x99b2dae8, 0xe8a14948, 0x00000000, 0x200001f0 }, + FinalRegs = new uint[] { 0x3a000492, 0xc16be6fa, 0x20053393, 0x597617c9, 0xc30c0ac0, 0x0ed34739, 0xf964a3d4, 0x4dcf9b40, 0x93109692, 0x7ed22040, 0x1f57a26e, 0x008d29d2, 0x99b2dae8, 0xe8a14948, 0x00000000, 0x200001d0 }, + }, + // RSB (reg) + new() + { + Instructions = new ushort[] { 0xea1a, 0x6f72, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x9a603e20, 0x10004fe3, 0x8c33bcef, 0x8a23db09, 0x47244c0c, 0x53417661, 0x6486ac8b, 0x5276c43b, 0x577f49a7, 0x34542492, 0xb4ac7c99, 0x5de5cb55, 0x8f6e1d72, 0x077d4a02, 0x00000000, 0xe00001f0 }, + FinalRegs = new uint[] { 0x9a603e20, 0x10004fe3, 0x8c33bcef, 0x8a23db09, 0x47244c0c, 0x53417661, 0x6486ac8b, 0x5276c43b, 0x577f49a7, 0x34542492, 0xb4ac7c99, 0x5de5cb55, 0x8f6e1d72, 0x077d4a02, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1b, 0x0ff3, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x6fdd73d7, 0xc6c4c438, 0x772312e2, 0xa57de93f, 0xa1edd64b, 0x8ee41d33, 0x85849a41, 0xac34953a, 0xb3d7c6b5, 0x439ceff1, 0xa3096172, 0x5d8f0654, 0x2e2993a3, 0xca221149, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0x6fdd73d7, 0xc6c4c438, 0x772312e2, 0xa57de93f, 0xa1edd64b, 0x8ee41d33, 0x85849a41, 0xac34953a, 0xb3d7c6b5, 0x439ceff1, 0xa3096172, 0x5d8f0654, 0x2e2993a3, 0xca221149, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1b, 0x1f34, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xbefd78a5, 0x6d071150, 0xe9ce2c88, 0x2251ed54, 0x30610b17, 0x6428697e, 0xf6e940a4, 0x2395634f, 0xdabff1a3, 0x89988d57, 0x85dd20b0, 0x2ca1311d, 0xcd0748d9, 0xedf55a6f, 0x00000000, 0x800001f0 }, + FinalRegs = new uint[] { 0xbefd78a5, 0x6d071150, 0xe9ce2c88, 0x2251ed54, 0x30610b17, 0x6428697e, 0xf6e940a4, 0x2395634f, 0xdabff1a3, 0x89988d57, 0x85dd20b0, 0x2ca1311d, 0xcd0748d9, 0xedf55a6f, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea16, 0x5f83, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x57018e40, 0xc4027d19, 0x33a32bd7, 0x6a75787a, 0x18f8569a, 0xbbf3a50d, 0x7f35656f, 0x66fbdad7, 0x3aa48c57, 0x39709ea2, 0x5972e4ba, 0xb2c2c772, 0x52f35620, 0x7ef9f1d6, 0x00000000, 0xd00001f0 }, + FinalRegs = new uint[] { 0x57018e40, 0xc4027d19, 0x33a32bd7, 0x6a75787a, 0x18f8569a, 0xbbf3a50d, 0x7f35656f, 0x66fbdad7, 0x3aa48c57, 0x39709ea2, 0x5972e4ba, 0xb2c2c772, 0x52f35620, 0x7ef9f1d6, 0x00000000, 0x100001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xea1a, 0x0fd8, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x79108ff6, 0x0cb1e662, 0x9eb9ffed, 0x1ee4d3de, 0x7a8fa20a, 0x1db7e216, 0x6fc42752, 0x9cb6cdad, 0xa497a582, 0x654c446f, 0xcbb31efc, 0x601e6995, 0xe328af35, 0x824026e7, 0x00000000, 0xd00001f0 }, + FinalRegs = new uint[] { 0x79108ff6, 0x0cb1e662, 0x9eb9ffed, 0x1ee4d3de, 0x7a8fa20a, 0x1db7e216, 0x6fc42752, 0x9cb6cdad, 0xa497a582, 0x654c446f, 0xcbb31efc, 0x601e6995, 0xe328af35, 0x824026e7, 0x00000000, 0x100001d0 }, + }, + }; + + public static readonly PrecomputedThumbTestCase[] ImmTestCases = + { + // TST (imm) + new() + { + Instructions = new ushort[] { 0xf018, 0x0fd4, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xf5a1b919, 0x37ee0ad4, 0xec1bbb30, 0x8345ecb1, 0xf733e93e, 0x76668927, 0xa9b16176, 0x34b9678e, 0xa6167f8b, 0xea4f20a9, 0x45345e75, 0xc8a2ea55, 0xae108472, 0x67b5e3a4, 0x00000001, 0xb00001f0 }, + FinalRegs = new uint[] { 0xf5a1b919, 0x37ee0ad4, 0xec1bbb30, 0x8345ecb1, 0xf733e93e, 0x76668927, 0xa9b16176, 0x34b9678e, 0xa6167f8b, 0xea4f20a9, 0x45345e75, 0xc8a2ea55, 0xae108472, 0x67b5e3a4, 0x00000001, 0x300001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf41b, 0x1fff, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xfc928b9b, 0xeff1f0a6, 0xe1cd22ba, 0xef1e4a75, 0x10779ef3, 0x3b003004, 0x7a532842, 0x2e71a8c4, 0x62b71ce6, 0x5ffcf3ce, 0xdbe8efa1, 0x86822f2b, 0x560da6b6, 0x46550850, 0x00000001, 0x700001f0 }, + FinalRegs = new uint[] { 0xfc928b9b, 0xeff1f0a6, 0xe1cd22ba, 0xef1e4a75, 0x10779ef3, 0x3b003004, 0x7a532842, 0x2e71a8c4, 0x62b71ce6, 0x5ffcf3ce, 0xdbe8efa1, 0x86822f2b, 0x560da6b6, 0x46550850, 0x00000001, 0x100001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf416, 0x7f97, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd0ba5d0a, 0xc8fa5c53, 0xac3069cb, 0x4be76d89, 0xcc9b4f47, 0x36984914, 0xd49fe0a5, 0x7d80c756, 0x8210fb6d, 0xcb498541, 0xc366597f, 0xacef4405, 0xdf6341a9, 0x6a1124b8, 0x00000001, 0xc00001f0 }, + FinalRegs = new uint[] { 0xd0ba5d0a, 0xc8fa5c53, 0xac3069cb, 0x4be76d89, 0xcc9b4f47, 0x36984914, 0xd49fe0a5, 0x7d80c756, 0x8210fb6d, 0xcb498541, 0xc366597f, 0xacef4405, 0xdf6341a9, 0x6a1124b8, 0x00000001, 0x000001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf016, 0x0f12, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xb8568ac2, 0x67a7ee09, 0x266abe3b, 0x9d93101d, 0x504b4adb, 0x45838822, 0x62126cc4, 0xf4198159, 0xf24a524c, 0x163fa3e9, 0x3c6d489e, 0xacef0dff, 0x73fc8fdd, 0x9d34fc09, 0x00000001, 0x800001f0 }, + FinalRegs = new uint[] { 0xb8568ac2, 0x67a7ee09, 0x266abe3b, 0x9d93101d, 0x504b4adb, 0x45838822, 0x62126cc4, 0xf4198159, 0xf24a524c, 0x163fa3e9, 0x3c6d489e, 0xacef0dff, 0x73fc8fdd, 0x9d34fc09, 0x00000001, 0x400001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf017, 0x2fd1, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x5548cee0, 0x59a88eb4, 0x3624d775, 0x98fe9a19, 0x84d1b83f, 0xed2c476a, 0x046a6aea, 0x0c92fadb, 0xdff5abe1, 0x91a16e82, 0xbb0f8ba4, 0x87c1888c, 0xa2df958e, 0x6cebba03, 0x00000001, 0x400001f0 }, + FinalRegs = new uint[] { 0x5548cee0, 0x59a88eb4, 0x3624d775, 0x98fe9a19, 0x84d1b83f, 0xed2c476a, 0x046a6aea, 0x0c92fadb, 0xdff5abe1, 0x91a16e82, 0xbb0f8ba4, 0x87c1888c, 0xa2df958e, 0x6cebba03, 0x00000001, 0x000001f0 }, + }, + // AND (imm) + new() + { + Instructions = new ushort[] { 0xf403, 0x3ce5, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xddd90378, 0xc3a2891f, 0x3d5007a2, 0x0f0c0756, 0xbdc5c113, 0xeee78000, 0x90693126, 0x8763b349, 0xbf6814b4, 0x40160bf9, 0xfff4a26d, 0x16a11d59, 0x26b3b8cc, 0xeb09487f, 0x00000001, 0x200001f0 }, + FinalRegs = new uint[] { 0xddd90378, 0xc3a2891f, 0x3d5007a2, 0x0f0c0756, 0xbdc5c113, 0xeee78000, 0x90693126, 0x8763b349, 0xbf6814b4, 0x40160bf9, 0xfff4a26d, 0x16a11d59, 0x00000200, 0xeb09487f, 0x00000001, 0x200001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf00a, 0x177d, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xce4db266, 0x67f21be8, 0x0de0c290, 0x2615cfc5, 0x6e3c46cb, 0x44a52240, 0xb12e0470, 0x903e0182, 0x61c32a9a, 0x58bf0753, 0xa9b3c209, 0x68ec1f37, 0x9320a3c9, 0xab952fd9, 0x00000001, 0xa00001f0 }, + FinalRegs = new uint[] { 0xce4db266, 0x67f21be8, 0x0de0c290, 0x2615cfc5, 0x6e3c46cb, 0x44a52240, 0xb12e0470, 0x00310009, 0x61c32a9a, 0x58bf0753, 0xa9b3c209, 0x68ec1f37, 0x9320a3c9, 0xab952fd9, 0x00000001, 0xa00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf014, 0x2913, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x4257c8eb, 0xd9eaf199, 0xe9381b0a, 0x39fcb309, 0xb1f24181, 0xebb2b7e4, 0x73799a0f, 0xc70a2fc7, 0xe2af6496, 0xb9014f5b, 0xe22ff568, 0x12dd4afe, 0x6d8544ac, 0x9293d043, 0x00000001, 0x000001f0 }, + FinalRegs = new uint[] { 0x4257c8eb, 0xd9eaf199, 0xe9381b0a, 0x39fcb309, 0xb1f24181, 0xebb2b7e4, 0x73799a0f, 0xc70a2fc7, 0xe2af6496, 0x11000100, 0xe22ff568, 0x12dd4afe, 0x6d8544ac, 0x9293d043, 0x00000001, 0x000001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf004, 0x27c2, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x8d7ceb35, 0x000e9260, 0x6825b561, 0xbcf66952, 0x3fbc7775, 0xd5afaa83, 0xe4fde261, 0xa35fa71e, 0xbefc5c9f, 0x667d9163, 0x8c2543b0, 0xd8489b89, 0x661ffec5, 0x45ccdaa8, 0x00000001, 0xd00001f0 }, + FinalRegs = new uint[] { 0x8d7ceb35, 0x000e9260, 0x6825b561, 0xbcf66952, 0x3fbc7775, 0xd5afaa83, 0xe4fde261, 0x02004200, 0xbefc5c9f, 0x667d9163, 0x8c2543b0, 0xd8489b89, 0x661ffec5, 0x45ccdaa8, 0x00000001, 0xd00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf008, 0x6ce5, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x78a2a638, 0x54ac2bd7, 0x60ad5509, 0x9c38b11f, 0x8dd109de, 0xd07aea77, 0xf5a0bcfc, 0xbcd81a17, 0x2f159ebd, 0xcc7e8454, 0x04621cce, 0xd0e9eca5, 0xb33f4ba6, 0x1e2bb5b2, 0x00000001, 0xf00001f0 }, + FinalRegs = new uint[] { 0x78a2a638, 0x54ac2bd7, 0x60ad5509, 0x9c38b11f, 0x8dd109de, 0xd07aea77, 0xf5a0bcfc, 0xbcd81a17, 0x2f159ebd, 0xcc7e8454, 0x04621cce, 0xd0e9eca5, 0x07000000, 0x1e2bb5b2, 0x00000001, 0xf00001f0 }, + }, + // BIC (imm) + new() + { + Instructions = new ushort[] { 0xf420, 0x6425, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x28c2ef44, 0x206bdfed, 0x30c780a9, 0x440ab4ab, 0x666fc882, 0x92a4aa1d, 0x3ceb6b36, 0xca757a75, 0xdf2f77b7, 0xae012305, 0x06b5c956, 0x0ff05e78, 0xad918973, 0x73778e28, 0x00000001, 0xe00001f0 }, + FinalRegs = new uint[] { 0x28c2ef44, 0x206bdfed, 0x30c780a9, 0x440ab4ab, 0x28c2e504, 0x92a4aa1d, 0x3ceb6b36, 0xca757a75, 0xdf2f77b7, 0xae012305, 0x06b5c956, 0x0ff05e78, 0xad918973, 0x73778e28, 0x00000001, 0xe00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf430, 0x44ed, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x5cd96673, 0xa1c27ac5, 0xbb205490, 0xa844d844, 0x1e66662f, 0x0f259402, 0xbe81472f, 0x36d55b13, 0x02c6d2a2, 0xc39c31b1, 0x59b71936, 0xf1914252, 0xef8188b8, 0x0c18bea1, 0x00000001, 0x700001f0 }, + FinalRegs = new uint[] { 0x5cd96673, 0xa1c27ac5, 0xbb205490, 0xa844d844, 0x5cd90073, 0x0f259402, 0xbe81472f, 0x36d55b13, 0x02c6d2a2, 0xc39c31b1, 0x59b71936, 0xf1914252, 0xef8188b8, 0x0c18bea1, 0x00000001, 0x100001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf038, 0x1334, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xe0afc0e0, 0x7ffd49d1, 0x6fe858fa, 0x7564882e, 0xaa895bf1, 0x725f4071, 0x612b9956, 0xb28fd700, 0xf0acd15c, 0xb62cf0bb, 0x1d9d5c1f, 0xdc3942a2, 0x9b3248ea, 0x3b7593ca, 0x00000001, 0xb00001f0 }, + FinalRegs = new uint[] { 0xe0afc0e0, 0x7ffd49d1, 0x6fe858fa, 0xf088d148, 0xaa895bf1, 0x725f4071, 0x612b9956, 0xb28fd700, 0xf0acd15c, 0xb62cf0bb, 0x1d9d5c1f, 0xdc3942a2, 0x9b3248ea, 0x3b7593ca, 0x00000001, 0xb00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf438, 0x51d7, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x75f070b0, 0x24e410c6, 0x128174fa, 0x04a10755, 0x6728fd35, 0xf6007f21, 0x0cb9efa3, 0x260e061c, 0xc5e02c94, 0x4aaa3354, 0x00796ab8, 0x897274d2, 0xe87dcffc, 0xa47bd3ab, 0x00000001, 0x200001f0 }, + FinalRegs = new uint[] { 0x75f070b0, 0xc5e02414, 0x128174fa, 0x04a10755, 0x6728fd35, 0xf6007f21, 0x0cb9efa3, 0x260e061c, 0xc5e02c94, 0x4aaa3354, 0x00796ab8, 0x897274d2, 0xe87dcffc, 0xa47bd3ab, 0x00000001, 0x800001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf438, 0x1db2, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x62dcbe9e, 0x55f42016, 0x6689f461, 0x31e32805, 0x1fc90a1e, 0x02e3a47f, 0xf236bafd, 0x65006290, 0x0065bd7f, 0xc1752579, 0x59528615, 0x6ef68c79, 0x138b8bb3, 0x0761d66c, 0x00000001, 0xc00001f0 }, + FinalRegs = new uint[] { 0x62dcbe9e, 0x55f42016, 0x6689f461, 0x31e32805, 0x1fc90a1e, 0x02e3a47f, 0xf236bafd, 0x65006290, 0x0065bd7f, 0xc1752579, 0x59528615, 0x6ef68c79, 0x138b8bb3, 0x0061bd7f, 0x00000001, 0x000001f0 }, + }, + // MOV (imm) + new() + { + Instructions = new ushort[] { 0xf45f, 0x5032, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x6ed98807, 0x536a9cb7, 0x79c2d5bb, 0xc2e9a860, 0x185b4e57, 0x77c1c99f, 0x99a24897, 0xc6cc4ea1, 0xe3d294a6, 0xb8e525ae, 0xca245840, 0x27943892, 0xa76ed6fe, 0xecbcffe8, 0x00000001, 0xc00001f0 }, + FinalRegs = new uint[] { 0x00002c80, 0x536a9cb7, 0x79c2d5bb, 0xc2e9a860, 0x185b4e57, 0x77c1c99f, 0x99a24897, 0xc6cc4ea1, 0xe3d294a6, 0xb8e525ae, 0xca245840, 0x27943892, 0xa76ed6fe, 0xecbcffe8, 0x00000001, 0x000001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf04f, 0x23f9, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x7e6bf90e, 0x87533a8f, 0x29389323, 0x96a37f30, 0x63e6e58e, 0xb8bb21d0, 0x5bd9ae04, 0x26b7a586, 0xfa359510, 0x131a4e95, 0x5d0adb02, 0xa8148f64, 0xbfe74669, 0xea2cdf2d, 0x00000001, 0xb00001f0 }, + FinalRegs = new uint[] { 0x7e6bf90e, 0x87533a8f, 0x29389323, 0xf900f900, 0x63e6e58e, 0xb8bb21d0, 0x5bd9ae04, 0x26b7a586, 0xfa359510, 0x131a4e95, 0x5d0adb02, 0xa8148f64, 0xbfe74669, 0xea2cdf2d, 0x00000001, 0xb00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf45f, 0x5351, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd8b5d8b2, 0x7ae44f2b, 0xda909eb2, 0xdb2fe423, 0xb2486971, 0x23427a2c, 0x96c88749, 0xb88d6d78, 0x2f4aa092, 0xbf40760f, 0x88d72a3f, 0x88854e62, 0x8d459486, 0x82a8ba9f, 0x00000001, 0x300001f0 }, + FinalRegs = new uint[] { 0xd8b5d8b2, 0x7ae44f2b, 0xda909eb2, 0x00003440, 0xb2486971, 0x23427a2c, 0x96c88749, 0xb88d6d78, 0x2f4aa092, 0xbf40760f, 0x88d72a3f, 0x88854e62, 0x8d459486, 0x82a8ba9f, 0x00000001, 0x100001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf45f, 0x207c, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x73063041, 0x038b5f4a, 0xf7e85421, 0xe935f110, 0x9a5c34de, 0xbc3dbed9, 0x1c57c517, 0x3294067f, 0x01cd78b5, 0xe7bfe428, 0x6e297fce, 0xccb2c833, 0x2e8bb930, 0xeb6e2004, 0x00000001, 0x300001f0 }, + FinalRegs = new uint[] { 0x000fc000, 0x038b5f4a, 0xf7e85421, 0xe935f110, 0x9a5c34de, 0xbc3dbed9, 0x1c57c517, 0x3294067f, 0x01cd78b5, 0xe7bfe428, 0x6e297fce, 0xccb2c833, 0x2e8bb930, 0xeb6e2004, 0x00000001, 0x100001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf45f, 0x5073, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd0d531fe, 0xeb1676df, 0x0acf5912, 0x6061b3fe, 0xecac2ae2, 0x40075143, 0x88a47781, 0x3ecb7baa, 0x6aee3603, 0x53133f32, 0x1e891e57, 0x4d7f8f94, 0xd09c727a, 0x28a79c93, 0x00000001, 0x400001f0 }, + FinalRegs = new uint[] { 0x00003cc0, 0xeb1676df, 0x0acf5912, 0x6061b3fe, 0xecac2ae2, 0x40075143, 0x88a47781, 0x3ecb7baa, 0x6aee3603, 0x53133f32, 0x1e891e57, 0x4d7f8f94, 0xd09c727a, 0x28a79c93, 0x00000001, 0x000001f0 }, + }, + // ORR (imm) + new() + { + Instructions = new ushort[] { 0xf45c, 0x03c9, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xc7d9e145, 0xc2a6bd5b, 0x92c8ca0b, 0x1fde4d2f, 0xa4705c7f, 0x47559e93, 0x9f9d3e22, 0x7b3719c0, 0x3746ffc9, 0xa1476ae8, 0x88f45e36, 0x0fc7d2a7, 0xaa94b64c, 0xe9fee33b, 0x00000001, 0x700001f0 }, + FinalRegs = new uint[] { 0xc7d9e145, 0xc2a6bd5b, 0x92c8ca0b, 0xaaf4b64c, 0xa4705c7f, 0x47559e93, 0x9f9d3e22, 0x7b3719c0, 0x3746ffc9, 0xa1476ae8, 0x88f45e36, 0x0fc7d2a7, 0xaa94b64c, 0xe9fee33b, 0x00000001, 0x900001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf441, 0x60ec, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x2262c23f, 0xf8ba9254, 0x2a870feb, 0xa66d2c1a, 0xa3bb8f6d, 0x2f754de2, 0xb3b0b9be, 0xc3cf59e8, 0xebaa6300, 0x22ea8a3d, 0xf3bcf0f4, 0xffb0aae8, 0x4982d5ab, 0x4c945119, 0x00000001, 0x800001f0 }, + FinalRegs = new uint[] { 0xf8ba9774, 0xf8ba9254, 0x2a870feb, 0xa66d2c1a, 0xa3bb8f6d, 0x2f754de2, 0xb3b0b9be, 0xc3cf59e8, 0xebaa6300, 0x22ea8a3d, 0xf3bcf0f4, 0xffb0aae8, 0x4982d5ab, 0x4c945119, 0x00000001, 0x800001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf44c, 0x5343, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x844cd5c4, 0x5a244353, 0xd74ff677, 0x25eefc9f, 0xa040f56f, 0x06e237a6, 0x7ccb1c91, 0xc9aa6d32, 0xf9e18bd6, 0xc0780954, 0x955d8f60, 0xa9cb014e, 0x64d583e2, 0x3e50533a, 0x00000001, 0x000001f0 }, + FinalRegs = new uint[] { 0x844cd5c4, 0x5a244353, 0xd74ff677, 0x64d5b3e2, 0xa040f56f, 0x06e237a6, 0x7ccb1c91, 0xc9aa6d32, 0xf9e18bd6, 0xc0780954, 0x955d8f60, 0xa9cb014e, 0x64d583e2, 0x3e50533a, 0x00000001, 0x000001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf040, 0x48e2, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x24423eae, 0x40f7667e, 0x017f283e, 0x72887399, 0x063f4da0, 0x9b57a1c5, 0x5500c630, 0x6a304cac, 0xf9f10e9a, 0x02cdd193, 0x3f42bccd, 0x3c52ef2e, 0x15858a11, 0x25fd30bf, 0x00000001, 0xc00001f0 }, + FinalRegs = new uint[] { 0x24423eae, 0x40f7667e, 0x017f283e, 0x72887399, 0x063f4da0, 0x9b57a1c5, 0x5500c630, 0x6a304cac, 0x75423eae, 0x02cdd193, 0x3f42bccd, 0x3c52ef2e, 0x15858a11, 0x25fd30bf, 0x00000001, 0xc00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf455, 0x1de0, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xc8c22d0e, 0x98a19d05, 0x61b4ea5e, 0x52f6f9a0, 0x2f8ceae4, 0x15649771, 0x61953174, 0x45b9d93f, 0x4e0629af, 0x30f43259, 0x863e8e5c, 0x3310b69e, 0xae5e5b9d, 0xf00e065a, 0x00000001, 0xb00001f0 }, + FinalRegs = new uint[] { 0xc8c22d0e, 0x98a19d05, 0x61b4ea5e, 0x52f6f9a0, 0x2f8ceae4, 0x15649771, 0x61953174, 0x45b9d93f, 0x4e0629af, 0x30f43259, 0x863e8e5c, 0x3310b69e, 0xae5e5b9d, 0x157c9771, 0x00000001, 0x100001f0 }, + }, + // MVN (imm) + new() + { + Instructions = new ushort[] { 0xf46f, 0x1681, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xb1267a38, 0xe72b03aa, 0x50dc392a, 0xaff74b0d, 0xf83a17ba, 0xb8edf09d, 0x799df56d, 0x1ecbd371, 0xb4a74b9a, 0xe79f52fb, 0xbcec8b62, 0xbb0b01ea, 0x26d72e8c, 0x1d2ac349, 0x00000001, 0x900001f0 }, + FinalRegs = new uint[] { 0xb1267a38, 0xe72b03aa, 0x50dc392a, 0xaff74b0d, 0xf83a17ba, 0xb8edf09d, 0xffefdfff, 0x1ecbd371, 0xb4a74b9a, 0xe79f52fb, 0xbcec8b62, 0xbb0b01ea, 0x26d72e8c, 0x1d2ac349, 0x00000001, 0x900001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf07f, 0x572f, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xa95387ad, 0x256c4ece, 0x32084d7a, 0x84935d58, 0x12f6880b, 0x3b386e47, 0xbeb69796, 0xdcf3fac5, 0xee2f9386, 0x25372541, 0x56499ba6, 0x06fa7586, 0xd114f908, 0x3442736e, 0x00000001, 0x400001f0 }, + FinalRegs = new uint[] { 0xa95387ad, 0x256c4ece, 0x32084d7a, 0x84935d58, 0x12f6880b, 0x3b386e47, 0xbeb69796, 0xd43fffff, 0xee2f9386, 0x25372541, 0x56499ba6, 0x06fa7586, 0xd114f908, 0x3442736e, 0x00000001, 0x800001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf46f, 0x17e3, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd7f2d1e1, 0x1d12b22c, 0x2c26620c, 0xeadb8ead, 0x73560a2e, 0xf521b384, 0x4094f3d2, 0x17ed0f6f, 0x79d30498, 0x6d47211a, 0x8fdfef1d, 0xce6cbfa7, 0x75dc1c1b, 0x2ffd5d28, 0x00000001, 0x700001f0 }, + FinalRegs = new uint[] { 0xd7f2d1e1, 0x1d12b22c, 0x2c26620c, 0xeadb8ead, 0x73560a2e, 0xf521b384, 0x4094f3d2, 0xffe39fff, 0x79d30498, 0x6d47211a, 0x8fdfef1d, 0xce6cbfa7, 0x75dc1c1b, 0x2ffd5d28, 0x00000001, 0x700001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf07f, 0x1431, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x4154dce7, 0x66c452e9, 0xff9bea1b, 0x228a4a5e, 0xe9fee66b, 0xddd7117f, 0x303cdcb6, 0x4bdf78a2, 0xfbcca92c, 0x2f628d24, 0x51816529, 0xcdea5042, 0x77a1e4a2, 0x8a745cb4, 0x00000001, 0xa00001f0 }, + FinalRegs = new uint[] { 0x4154dce7, 0x66c452e9, 0xff9bea1b, 0x228a4a5e, 0xffceffce, 0xddd7117f, 0x303cdcb6, 0x4bdf78a2, 0xfbcca92c, 0x2f628d24, 0x51816529, 0xcdea5042, 0x77a1e4a2, 0x8a745cb4, 0x00000001, 0xa00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf07f, 0x73ac, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd7b60274, 0x1ff3baba, 0xfdc8fa51, 0xcfacae9d, 0xd27a8214, 0xbbfb1abf, 0x3766111f, 0x89af2196, 0x4bd14cd6, 0x5af84659, 0xd279ed2f, 0x7abdf656, 0x868a6980, 0xd343d52a, 0x00000001, 0xd00001f0 }, + FinalRegs = new uint[] { 0xd7b60274, 0x1ff3baba, 0xfdc8fa51, 0xfea7ffff, 0xd27a8214, 0xbbfb1abf, 0x3766111f, 0x89af2196, 0x4bd14cd6, 0x5af84659, 0xd279ed2f, 0x7abdf656, 0x868a6980, 0xd343d52a, 0x00000001, 0x900001f0 }, + }, + // ORN (imm) + new() + { + Instructions = new ushort[] { 0xf464, 0x0976, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x02e1c999, 0x40c2ff04, 0x16f00059, 0xd360cd62, 0xcb34f9d2, 0x303b434a, 0x53e0151f, 0x188b36bc, 0x84868958, 0xebad0ada, 0xdcd0cb74, 0x64bc056c, 0xd17a7256, 0xb71ddae3, 0x00000001, 0x500001f0 }, + FinalRegs = new uint[] { 0x02e1c999, 0x40c2ff04, 0x16f00059, 0xd360cd62, 0xcb34f9d2, 0x303b434a, 0x53e0151f, 0x188b36bc, 0x84868958, 0xff3dffff, 0xdcd0cb74, 0x64bc056c, 0xd17a7256, 0xb71ddae3, 0x00000001, 0x500001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf477, 0x3c66, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x71910713, 0xd17f8e75, 0x2652c7ac, 0xfad0527a, 0xc52b726d, 0x29e66793, 0xa1011225, 0x00c8ecc1, 0x48af4edd, 0x5c4e2e67, 0xc5393bd5, 0x702fcda1, 0x4549b1cf, 0x72d5a971, 0x00000001, 0xd00001f0 }, + FinalRegs = new uint[] { 0x71910713, 0xd17f8e75, 0x2652c7ac, 0xfad0527a, 0xc52b726d, 0x29e66793, 0xa1011225, 0x00c8ecc1, 0x48af4edd, 0x5c4e2e67, 0xc5393bd5, 0x702fcda1, 0xfffcefff, 0x72d5a971, 0x00000001, 0x900001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf479, 0x1270, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x91060c85, 0x9b9c9033, 0x771ac325, 0x001e17c8, 0xb1adee43, 0xbaa9ec02, 0xf57f9f83, 0x3fed4e5c, 0x198cc3ea, 0x1a40edde, 0x6844391b, 0xa03319a0, 0xf741e11b, 0xc1892487, 0x00000001, 0x600001f0 }, + FinalRegs = new uint[] { 0x91060c85, 0x9b9c9033, 0xffc3ffff, 0x001e17c8, 0xb1adee43, 0xbaa9ec02, 0xf57f9f83, 0x3fed4e5c, 0x198cc3ea, 0x1a40edde, 0x6844391b, 0xa03319a0, 0xf741e11b, 0xc1892487, 0x00000001, 0x800001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf46f, 0x19d4, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x4fd5b2bd, 0x1c8f29ae, 0x12803c79, 0x93683874, 0xccd779c1, 0x6978c335, 0x06eb789d, 0xc8b74ef8, 0x51ca145a, 0x242d8047, 0x5036f51f, 0x13a4a4a2, 0x08818ae4, 0xe1687e67, 0x00000001, 0x000001f0 }, + FinalRegs = new uint[] { 0x4fd5b2bd, 0x1c8f29ae, 0x12803c79, 0x93683874, 0xccd779c1, 0x6978c335, 0x06eb789d, 0xc8b74ef8, 0x51ca145a, 0xffe57fff, 0x5036f51f, 0x13a4a4a2, 0x08818ae4, 0xe1687e67, 0x00000001, 0x000001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf07f, 0x614f, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x83c9ef5a, 0xb5933c7e, 0x2dc23d71, 0x5723ae27, 0x1218bc2c, 0x456f3dbd, 0xf6ee7d22, 0xde4df878, 0x3e800973, 0x39c4c131, 0x0676384d, 0xef62a558, 0x2acc92f2, 0x9cd71aa1, 0x00000001, 0xb00001f0 }, + FinalRegs = new uint[] { 0x83c9ef5a, 0xf30fffff, 0x2dc23d71, 0x5723ae27, 0x1218bc2c, 0x456f3dbd, 0xf6ee7d22, 0xde4df878, 0x3e800973, 0x39c4c131, 0x0676384d, 0xef62a558, 0x2acc92f2, 0x9cd71aa1, 0x00000001, 0x900001f0 }, + }, + // TEQ (imm) + new() + { + Instructions = new ushort[] { 0xf49b, 0x2fe4, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xc87047c3, 0x99001273, 0xa963adc7, 0xaba3d1a1, 0x4b9c13a0, 0xc42566ba, 0xee0b7ab1, 0x3e4423ec, 0x5d874e97, 0xfffb5799, 0xdb88f462, 0xbdc4a9e2, 0x3933e52b, 0xe1839111, 0x00000001, 0xc00001f0 }, + FinalRegs = new uint[] { 0xc87047c3, 0x99001273, 0xa963adc7, 0xaba3d1a1, 0x4b9c13a0, 0xc42566ba, 0xee0b7ab1, 0x3e4423ec, 0x5d874e97, 0xfffb5799, 0xdb88f462, 0xbdc4a9e2, 0x3933e52b, 0xe1839111, 0x00000001, 0x800001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf09b, 0x0f59, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x6d2c2ac7, 0xdd2b59f4, 0x3fc013f4, 0x567e744e, 0xc4feb096, 0x188454f3, 0xae13338b, 0x66a0a40b, 0xac995945, 0x7e27f097, 0x547cbd54, 0xd2abf0ab, 0x02c08b3e, 0xe6d1283f, 0x00000001, 0x500001f0 }, + FinalRegs = new uint[] { 0x6d2c2ac7, 0xdd2b59f4, 0x3fc013f4, 0x567e744e, 0xc4feb096, 0x188454f3, 0xae13338b, 0x66a0a40b, 0xac995945, 0x7e27f097, 0x547cbd54, 0xd2abf0ab, 0x02c08b3e, 0xe6d1283f, 0x00000001, 0x900001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf494, 0x6f3d, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x16342d21, 0xff794fb0, 0x513ba230, 0x7b9e4b2b, 0x9a2d1ba9, 0xebce0dae, 0xe792f2b8, 0xf4932236, 0x0bcd9542, 0x12bcab94, 0x0110b845, 0xdde237b0, 0xa401d5b9, 0xc3162f6d, 0x00000001, 0xc00001f0 }, + FinalRegs = new uint[] { 0x16342d21, 0xff794fb0, 0x513ba230, 0x7b9e4b2b, 0x9a2d1ba9, 0xebce0dae, 0xe792f2b8, 0xf4932236, 0x0bcd9542, 0x12bcab94, 0x0110b845, 0xdde237b0, 0xa401d5b9, 0xc3162f6d, 0x00000001, 0x800001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf09c, 0x6f59, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x8d9e2002, 0xfa519294, 0x700740d6, 0x29220c73, 0x8f0ad8b2, 0x6ce9d5e8, 0x12f9da7a, 0x286a9813, 0x2be49d73, 0x16241aa1, 0xe096f43b, 0x1fd0d3e2, 0x31791bb5, 0xa4943f4e, 0x00000001, 0xe00001f0 }, + FinalRegs = new uint[] { 0x8d9e2002, 0xfa519294, 0x700740d6, 0x29220c73, 0x8f0ad8b2, 0x6ce9d5e8, 0x12f9da7a, 0x286a9813, 0x2be49d73, 0x16241aa1, 0xe096f43b, 0x1fd0d3e2, 0x31791bb5, 0xa4943f4e, 0x00000001, 0x000001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf094, 0x6f35, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x222e0e7c, 0xa89d1fdf, 0xa7d67bc3, 0x658e1ee9, 0x10b41780, 0x5cd566a4, 0xce03a58a, 0x63fb9a9e, 0x4f5cb2bd, 0x14e72619, 0x296a9bd5, 0xbf7b1fb1, 0x705a45cc, 0xba8540ae, 0x00000001, 0x000001f0 }, + FinalRegs = new uint[] { 0x222e0e7c, 0xa89d1fdf, 0xa7d67bc3, 0x658e1ee9, 0x10b41780, 0x5cd566a4, 0xce03a58a, 0x63fb9a9e, 0x4f5cb2bd, 0x14e72619, 0x296a9bd5, 0xbf7b1fb1, 0x705a45cc, 0xba8540ae, 0x00000001, 0x000001f0 }, + }, + // EOR (imm) + new() + { + Instructions = new ushort[] { 0xf496, 0x54fb, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x6267728b, 0xc834f7c7, 0xa136a1d6, 0xfd9533e9, 0x096db729, 0x8fff8a73, 0x6a45348e, 0xd52111ed, 0xa5640aff, 0xa4cf82a6, 0x5ab70b5c, 0x5b3c4563, 0xf1a91ab7, 0x5718fdd1, 0x00000001, 0x500001f0 }, + FinalRegs = new uint[] { 0x6267728b, 0xc834f7c7, 0xa136a1d6, 0xfd9533e9, 0x6a452bee, 0x8fff8a73, 0x6a45348e, 0xd52111ed, 0xa5640aff, 0xa4cf82a6, 0x5ab70b5c, 0x5b3c4563, 0xf1a91ab7, 0x5718fdd1, 0x00000001, 0x100001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf08a, 0x339d, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xbf1e6da6, 0x2c10408a, 0xe961ddde, 0x5add8306, 0xc266064d, 0xa79569e1, 0x945c28ed, 0xb996f578, 0x68082b6e, 0x14cdd2c7, 0x7d0cc6a2, 0x8d6edfbf, 0x9151e24c, 0x63eaee32, 0x00000001, 0x300001f0 }, + FinalRegs = new uint[] { 0xbf1e6da6, 0x2c10408a, 0xe961ddde, 0xe0915b3f, 0xc266064d, 0xa79569e1, 0x945c28ed, 0xb996f578, 0x68082b6e, 0x14cdd2c7, 0x7d0cc6a2, 0x8d6edfbf, 0x9151e24c, 0x63eaee32, 0x00000001, 0x300001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf490, 0x27d8, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd6826a86, 0x39aa35f5, 0x2a8a913e, 0xd9dbf560, 0xcb1a9957, 0xe6779d2f, 0x0eeab3f9, 0xa463d4c2, 0xb3187660, 0xa51778c3, 0x73817179, 0x6d6dae92, 0x864a3e80, 0x43d8f181, 0x00000001, 0xe00001f0 }, + FinalRegs = new uint[] { 0xd6826a86, 0x39aa35f5, 0x2a8a913e, 0xd9dbf560, 0xcb1a9957, 0xe6779d2f, 0x0eeab3f9, 0xd684aa86, 0xb3187660, 0xa51778c3, 0x73817179, 0x6d6dae92, 0x864a3e80, 0x43d8f181, 0x00000001, 0x800001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf485, 0x3d32, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x063885c0, 0xa183a44d, 0x5cb2f961, 0xe44b8670, 0x8ec25495, 0xb8f5a831, 0x1c2fecb4, 0xfc15fcff, 0x28dd902e, 0xf0c875f4, 0x0af03bb5, 0xefe4ba8b, 0x10e57000, 0x4cd51767, 0x00000001, 0xb00001f0 }, + FinalRegs = new uint[] { 0x063885c0, 0xa183a44d, 0x5cb2f961, 0xe44b8670, 0x8ec25495, 0xb8f5a831, 0x1c2fecb4, 0xfc15fcff, 0x28dd902e, 0xf0c875f4, 0x0af03bb5, 0xefe4ba8b, 0x10e57000, 0xb8f76031, 0x00000001, 0xb00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf095, 0x58e8, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x5a60610b, 0x4d178413, 0x3b12edd0, 0x23afc7fc, 0x47f0647d, 0x327bd294, 0x52351d80, 0x36733323, 0x490a0d2a, 0x75d5888c, 0x9b45f4e6, 0x89ebf7dc, 0xd278dd78, 0x1b9b0bbd, 0x00000001, 0x400001f0 }, + FinalRegs = new uint[] { 0x5a60610b, 0x4d178413, 0x3b12edd0, 0x23afc7fc, 0x47f0647d, 0x327bd294, 0x52351d80, 0x36733323, 0x2f7bd294, 0x75d5888c, 0x9b45f4e6, 0x89ebf7dc, 0xd278dd78, 0x1b9b0bbd, 0x00000001, 0x000001f0 }, + }, + // CMN (imm) + new() + { + Instructions = new ushort[] { 0xf514, 0x6f12, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xec864396, 0xe2f483b8, 0x18df08c9, 0xae7780ba, 0xd16bc913, 0x892037de, 0x84a3589e, 0x3a468960, 0x004f92e4, 0x6fd793c2, 0x81b048c6, 0xe044e7cf, 0x2199ccda, 0x4667415d, 0x00000001, 0x000001f0 }, + FinalRegs = new uint[] { 0xec864396, 0xe2f483b8, 0x18df08c9, 0xae7780ba, 0xd16bc913, 0x892037de, 0x84a3589e, 0x3a468960, 0x004f92e4, 0x6fd793c2, 0x81b048c6, 0xe044e7cf, 0x2199ccda, 0x4667415d, 0x00000001, 0x800001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf517, 0x2f38, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x154548b0, 0x28aed64c, 0x533306b3, 0x8eace432, 0x9a6523f1, 0x22b08ccb, 0xe7fceaf6, 0x45429c2c, 0xf58378c1, 0x0ef49416, 0x88dbd472, 0xf6a35b6c, 0x46b19364, 0x52e4982d, 0x00000001, 0x900001f0 }, + FinalRegs = new uint[] { 0x154548b0, 0x28aed64c, 0x533306b3, 0x8eace432, 0x9a6523f1, 0x22b08ccb, 0xe7fceaf6, 0x45429c2c, 0xf58378c1, 0x0ef49416, 0x88dbd472, 0xf6a35b6c, 0x46b19364, 0x52e4982d, 0x00000001, 0x000001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf116, 0x7fe2, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x30b90186, 0xec7b038f, 0xcb392feb, 0x10c09c2f, 0x8619521d, 0xcf8d7075, 0x108f8f49, 0x6e44275d, 0x1728faed, 0xf2a0b2a4, 0x783cf97f, 0x201d6d0b, 0x317f276d, 0x5a7186e2, 0x00000001, 0x200001f0 }, + FinalRegs = new uint[] { 0x30b90186, 0xec7b038f, 0xcb392feb, 0x10c09c2f, 0x8619521d, 0xcf8d7075, 0x108f8f49, 0x6e44275d, 0x1728faed, 0xf2a0b2a4, 0x783cf97f, 0x201d6d0b, 0x317f276d, 0x5a7186e2, 0x00000001, 0x000001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf51b, 0x7f4a, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xedd3c03d, 0xd7cce5a2, 0xfc40b40a, 0x6a9c96f3, 0x40ca8c2d, 0xaa2973e1, 0xd7953408, 0xfa11d2df, 0x7cec28c2, 0x4e523380, 0x007a4ac6, 0x03890c29, 0xd1495b3e, 0xdf1af969, 0x00000001, 0x500001f0 }, + FinalRegs = new uint[] { 0xedd3c03d, 0xd7cce5a2, 0xfc40b40a, 0x6a9c96f3, 0x40ca8c2d, 0xaa2973e1, 0xd7953408, 0xfa11d2df, 0x7cec28c2, 0x4e523380, 0x007a4ac6, 0x03890c29, 0xd1495b3e, 0xdf1af969, 0x00000001, 0x000001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf11c, 0x5f9c, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd8c0c360, 0xd50bcc87, 0xe265e8b2, 0xca49cc71, 0xa6bb11c8, 0x13649388, 0x034a4c8c, 0xa3b4c570, 0x014d32ac, 0x1847d102, 0x7fc3678d, 0xb0e0f469, 0x9508a619, 0x2a2372e0, 0x00000001, 0xa00001f0 }, + FinalRegs = new uint[] { 0xd8c0c360, 0xd50bcc87, 0xe265e8b2, 0xca49cc71, 0xa6bb11c8, 0x13649388, 0x034a4c8c, 0xa3b4c570, 0x014d32ac, 0x1847d102, 0x7fc3678d, 0xb0e0f469, 0x9508a619, 0x2a2372e0, 0x00000001, 0x800001f0 }, + }, + // ADD (imm) + new() + { + Instructions = new ushort[] { 0xf10b, 0x00e0, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xadc2fe68, 0xa8a14518, 0x5baf5e87, 0x17e2b502, 0x638227c2, 0xba11428f, 0x98c5b963, 0x5b9cbcd3, 0xb4c11f97, 0x0ca6832e, 0xea26efa6, 0x7bb19ec8, 0x8ea04a89, 0x62d597c2, 0x00000001, 0x300001f0 }, + FinalRegs = new uint[] { 0x7bb19fa8, 0xa8a14518, 0x5baf5e87, 0x17e2b502, 0x638227c2, 0xba11428f, 0x98c5b963, 0x5b9cbcd3, 0xb4c11f97, 0x0ca6832e, 0xea26efa6, 0x7bb19ec8, 0x8ea04a89, 0x62d597c2, 0x00000001, 0x300001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf114, 0x7b41, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x9366f694, 0x02670e58, 0x6f3e74b8, 0x567d3e30, 0xeebb29c4, 0xc25ce8e6, 0x942b94c8, 0xc7dccdd9, 0xccfe17a9, 0xeacc4db1, 0xbbbc0fde, 0x248b7093, 0x7f66c92d, 0xfc063cb6, 0x00000001, 0xe00001f0 }, + FinalRegs = new uint[] { 0x9366f694, 0x02670e58, 0x6f3e74b8, 0x567d3e30, 0xeebb29c4, 0xc25ce8e6, 0x942b94c8, 0xc7dccdd9, 0xccfe17a9, 0xeacc4db1, 0xbbbc0fde, 0xf1bf29c4, 0x7f66c92d, 0xfc063cb6, 0x00000001, 0x800001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf51c, 0x21d1, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x2b53ae1e, 0x733046c3, 0xbcc33a3a, 0x2f7bbd50, 0xed2a39f2, 0xfee631ec, 0xeb6d3bc3, 0x9f9b502d, 0x30d20f7b, 0xdc75211b, 0xdb234e2b, 0x85008c86, 0x43beb508, 0x6a8303d5, 0x00000001, 0xd00001f0 }, + FinalRegs = new uint[] { 0x2b53ae1e, 0x43c53d08, 0xbcc33a3a, 0x2f7bbd50, 0xed2a39f2, 0xfee631ec, 0xeb6d3bc3, 0x9f9b502d, 0x30d20f7b, 0xdc75211b, 0xdb234e2b, 0x85008c86, 0x43beb508, 0x6a8303d5, 0x00000001, 0x000001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf513, 0x22e8, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xbb3b52c1, 0x40ff59f3, 0x05ca09c5, 0x440114be, 0xec3a4022, 0x0ff93d8c, 0x38868879, 0x824d36d8, 0xf513a9d8, 0xf1d0ad5a, 0xc453fdd8, 0xe3dc8d52, 0x1fc5a9ef, 0x809dbe9b, 0x00000001, 0xc00001f0 }, + FinalRegs = new uint[] { 0xbb3b52c1, 0x40ff59f3, 0x440854be, 0x440114be, 0xec3a4022, 0x0ff93d8c, 0x38868879, 0x824d36d8, 0xf513a9d8, 0xf1d0ad5a, 0xc453fdd8, 0xe3dc8d52, 0x1fc5a9ef, 0x809dbe9b, 0x00000001, 0x000001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf518, 0x68c7, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xf9c00a78, 0xf47ee408, 0xdc31e40b, 0x167902da, 0x03b23f2f, 0x6d41efdc, 0x9cb99b17, 0x21bfbf63, 0x9fbe8105, 0x250087d0, 0xe0588965, 0x0f0f669c, 0x2ed04b37, 0xc65c6e2e, 0x00000001, 0x100001f0 }, + FinalRegs = new uint[] { 0xf9c00a78, 0xf47ee408, 0xdc31e40b, 0x167902da, 0x03b23f2f, 0x6d41efdc, 0x9cb99b17, 0x21bfbf63, 0x9fbe873d, 0x250087d0, 0xe0588965, 0x0f0f669c, 0x2ed04b37, 0xc65c6e2e, 0x00000001, 0x800001f0 }, + }, + // ADC (imm) + new() + { + Instructions = new ushort[] { 0xf54d, 0x379a, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x09eb57e5, 0xc9981095, 0x94b0bf26, 0x27080c39, 0x9fba115a, 0xde0e1533, 0xaa5916aa, 0x1bfc2313, 0x32a96f13, 0x5b8f2d6c, 0x9098dcf2, 0x86143a3f, 0x5c004908, 0xd233cd08, 0x00000001, 0x300001f0 }, + FinalRegs = new uint[] { 0x09eb57e5, 0xc9981095, 0x94b0bf26, 0x27080c39, 0x9fba115a, 0xde0e1533, 0xaa5916aa, 0xd2350109, 0x32a96f13, 0x5b8f2d6c, 0x9098dcf2, 0x86143a3f, 0x5c004908, 0xd233cd08, 0x00000001, 0x300001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf149, 0x3a77, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xe32aaf45, 0x05fe0eac, 0x9c782c15, 0x9301164b, 0xa2f59aea, 0xe6b2618b, 0xfceb237a, 0xcfeb98bd, 0xaaa75e8d, 0xbb57f750, 0xd282f40d, 0xa181d4d7, 0x93313b48, 0x9a64c67f, 0x00000001, 0xf00001f0 }, + FinalRegs = new uint[] { 0xe32aaf45, 0x05fe0eac, 0x9c782c15, 0x9301164b, 0xa2f59aea, 0xe6b2618b, 0xfceb237a, 0xcfeb98bd, 0xaaa75e8d, 0xbb57f750, 0x32cf6ec8, 0xa181d4d7, 0x93313b48, 0x9a64c67f, 0x00000001, 0xf00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf549, 0x57c8, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x67da941e, 0x5d744410, 0x1f93bf8f, 0xb52727e0, 0x77ce10fe, 0xe7a40291, 0x40ac5a1f, 0x127e801f, 0x68233546, 0xdbe8086f, 0x82b65e68, 0xcf35c09b, 0x8846e02d, 0x5fd54256, 0x00000001, 0x200001f0 }, + FinalRegs = new uint[] { 0x67da941e, 0x5d744410, 0x1f93bf8f, 0xb52727e0, 0x77ce10fe, 0xe7a40291, 0x40ac5a1f, 0xdbe82170, 0x68233546, 0xdbe8086f, 0x82b65e68, 0xcf35c09b, 0x8846e02d, 0x5fd54256, 0x00000001, 0x200001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf15c, 0x1649, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x86cf07b1, 0x1c86e00f, 0x8dc39789, 0xe8fafb40, 0xb837bf22, 0xe9c2c765, 0xb9e8b84b, 0xdbc9663e, 0x979b81da, 0xfb7a5636, 0x9012981d, 0xf52ec47c, 0xf98f6294, 0xaf70ff24, 0x00000001, 0xe00001f0 }, + FinalRegs = new uint[] { 0x86cf07b1, 0x1c86e00f, 0x8dc39789, 0xe8fafb40, 0xb837bf22, 0xe9c2c765, 0xf9d862de, 0xdbc9663e, 0x979b81da, 0xfb7a5636, 0x9012981d, 0xf52ec47c, 0xf98f6294, 0xaf70ff24, 0x00000001, 0x800001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf144, 0x6ab6, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x151549e7, 0xbdfa6ced, 0x47ba5025, 0xaba24048, 0x17c38ef8, 0xf92095ec, 0xdccd5b6f, 0xcb3878a5, 0x30d25594, 0x94886d84, 0xaec74633, 0xbe39725f, 0x439d8ef1, 0xcd66a204, 0x00000001, 0x000001f0 }, + FinalRegs = new uint[] { 0x151549e7, 0xbdfa6ced, 0x47ba5025, 0xaba24048, 0x17c38ef8, 0xf92095ec, 0xdccd5b6f, 0xcb3878a5, 0x30d25594, 0x94886d84, 0x1d738ef8, 0xbe39725f, 0x439d8ef1, 0xcd66a204, 0x00000001, 0x000001f0 }, + }, + // SBC (imm) + new() + { + Instructions = new ushort[] { 0xf565, 0x3beb, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x23efd21b, 0x78e2f658, 0x37a4e044, 0x8feab92a, 0x9795995f, 0x66c7ddab, 0x1c29040f, 0x10034172, 0x2eede540, 0x961c1400, 0x34cf45b9, 0xdb736f38, 0xd601c8ed, 0x99a714af, 0x00000001, 0xf00001f0 }, + FinalRegs = new uint[] { 0x23efd21b, 0x78e2f658, 0x37a4e044, 0x8feab92a, 0x9795995f, 0x66c7ddab, 0x1c29040f, 0x10034172, 0x2eede540, 0x961c1400, 0x34cf45b9, 0x66c607ab, 0xd601c8ed, 0x99a714af, 0x00000001, 0xf00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf172, 0x1b0d, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x596b63ec, 0xb659c798, 0x300ca58e, 0x52200fa5, 0x0db74ebe, 0x01e5b394, 0xed83d480, 0x1a524b19, 0x593d9bd1, 0x1152a751, 0xf3e1cb1c, 0xfb9392e3, 0x08fc2cd9, 0xc3910cf3, 0x00000001, 0x000001f0 }, + FinalRegs = new uint[] { 0x596b63ec, 0xb659c798, 0x300ca58e, 0x52200fa5, 0x0db74ebe, 0x01e5b394, 0xed83d480, 0x1a524b19, 0x593d9bd1, 0x1152a751, 0xf3e1cb1c, 0x2fffa580, 0x08fc2cd9, 0xc3910cf3, 0x00000001, 0x200001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf57c, 0x14da, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x5eab5df9, 0x3cfdd390, 0xcfd20097, 0xc8986688, 0xa714c17c, 0xc9eee620, 0x6626498e, 0x2de48d3c, 0xc27c794f, 0xf7d0c67f, 0x75b6b9d9, 0xbaf9f630, 0x7bd89fad, 0xe5a2e298, 0x00000001, 0xe00001f0 }, + FinalRegs = new uint[] { 0x5eab5df9, 0x3cfdd390, 0xcfd20097, 0xc8986688, 0x7bbd5fad, 0xc9eee620, 0x6626498e, 0x2de48d3c, 0xc27c794f, 0xf7d0c67f, 0x75b6b9d9, 0xbaf9f630, 0x7bd89fad, 0xe5a2e298, 0x00000001, 0x200001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf57a, 0x6bbf, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xaee56760, 0xa9f9b7d4, 0x9dd85a8c, 0x4c8cea6b, 0x7807b53d, 0xd1349b90, 0xcf320f62, 0x7af6d0c9, 0xc61fac5f, 0x23b43bbd, 0xef7466b3, 0x98e322a8, 0x1e10ae81, 0xb6987dcc, 0x00000001, 0xa00001f0 }, + FinalRegs = new uint[] { 0xaee56760, 0xa9f9b7d4, 0x9dd85a8c, 0x4c8cea6b, 0x7807b53d, 0xd1349b90, 0xcf320f62, 0x7af6d0c9, 0xc61fac5f, 0x23b43bbd, 0xef7466b3, 0xef7460bb, 0x1e10ae81, 0xb6987dcc, 0x00000001, 0xa00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf171, 0x47e8, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x4164d035, 0x72eecb21, 0xbb63329c, 0x8883a249, 0x230b524b, 0x40c059ae, 0x529e2950, 0xd0f7b958, 0xae900a4a, 0xa5a3f2b5, 0xe68da7f3, 0x68fececb, 0x91a2f476, 0x3986b8a0, 0x00000001, 0x400001f0 }, + FinalRegs = new uint[] { 0x4164d035, 0x72eecb21, 0xbb63329c, 0x8883a249, 0x230b524b, 0x40c059ae, 0x529e2950, 0xfeeecb20, 0xae900a4a, 0xa5a3f2b5, 0xe68da7f3, 0x68fececb, 0x91a2f476, 0x3986b8a0, 0x00000001, 0x800001f0 }, + }, + // CMP (imm) + new() + { + Instructions = new ushort[] { 0xf5ba, 0x7f0c, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x32876eff, 0x3127746f, 0x25274f4b, 0x50ba4fa5, 0xa3013fb5, 0x4985e2cb, 0x43dad09c, 0xfb6e47f2, 0x673ee708, 0x3beee172, 0x4866bb83, 0x9368060a, 0x565ecf8e, 0xecc22394, 0x00000001, 0xc00001f0 }, + FinalRegs = new uint[] { 0x32876eff, 0x3127746f, 0x25274f4b, 0x50ba4fa5, 0xa3013fb5, 0x4985e2cb, 0x43dad09c, 0xfb6e47f2, 0x673ee708, 0x3beee172, 0x4866bb83, 0x9368060a, 0x565ecf8e, 0xecc22394, 0x00000001, 0x200001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf1b4, 0x5f0c, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xabb3ffca, 0x7dbdda85, 0xe413a0d4, 0xf2ea8958, 0x81be2593, 0x8b0997e0, 0x5319660b, 0xd4edc3d0, 0x4b147c71, 0xa60a6a5f, 0x9984a94a, 0xbabe5540, 0x24df8017, 0x1e97e9f5, 0x00000001, 0xd00001f0 }, + FinalRegs = new uint[] { 0xabb3ffca, 0x7dbdda85, 0xe413a0d4, 0xf2ea8958, 0x81be2593, 0x8b0997e0, 0x5319660b, 0xd4edc3d0, 0x4b147c71, 0xa60a6a5f, 0x9984a94a, 0xbabe5540, 0x24df8017, 0x1e97e9f5, 0x00000001, 0x300001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf5b1, 0x0f4b, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xf6edbf76, 0xd3f53e21, 0x37679835, 0x6af58147, 0x143dd6be, 0x4f6339d1, 0x0261fa88, 0x38fe033f, 0x1b503fb3, 0x802af22b, 0x22901e74, 0xae61d40e, 0xe1e850ee, 0xe353701c, 0x00000001, 0x200001f0 }, + FinalRegs = new uint[] { 0xf6edbf76, 0xd3f53e21, 0x37679835, 0x6af58147, 0x143dd6be, 0x4f6339d1, 0x0261fa88, 0x38fe033f, 0x1b503fb3, 0x802af22b, 0x22901e74, 0xae61d40e, 0xe1e850ee, 0xe353701c, 0x00000001, 0xa00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf5b2, 0x7f57, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x350b2e14, 0xcc603c9e, 0xa7a56491, 0x1f4fe90b, 0x6bb14aba, 0x325154ef, 0xc7655249, 0xe1a6077b, 0x145fc2f0, 0x21e0bc5e, 0x18275d8b, 0x0d8f37f0, 0xfdb56518, 0x405f5649, 0x00000001, 0x200001f0 }, + FinalRegs = new uint[] { 0x350b2e14, 0xcc603c9e, 0xa7a56491, 0x1f4fe90b, 0x6bb14aba, 0x325154ef, 0xc7655249, 0xe1a6077b, 0x145fc2f0, 0x21e0bc5e, 0x18275d8b, 0x0d8f37f0, 0xfdb56518, 0x405f5649, 0x00000001, 0xa00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf1b7, 0x0fd0, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x5a7f551b, 0x624d7cb7, 0xdc3e4dab, 0xd242610e, 0x8b7213db, 0x3c4f81df, 0x353e713e, 0x0ffdfd5c, 0xe56efdf9, 0x59330bc2, 0x1b91689c, 0x5497152e, 0x7ce02ab7, 0x0127aeca, 0x00000001, 0xd00001f0 }, + FinalRegs = new uint[] { 0x5a7f551b, 0x624d7cb7, 0xdc3e4dab, 0xd242610e, 0x8b7213db, 0x3c4f81df, 0x353e713e, 0x0ffdfd5c, 0xe56efdf9, 0x59330bc2, 0x1b91689c, 0x5497152e, 0x7ce02ab7, 0x0127aeca, 0x00000001, 0x200001f0 }, + }, + // SUB (imm) + new() + { + Instructions = new ushort[] { 0xf5a6, 0x2902, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x688a6dd6, 0xcabb9832, 0xa187c464, 0xe4474634, 0x19316c88, 0x8b99d147, 0xd67bc441, 0x48cfa0cf, 0x4cd8b792, 0x9593d34d, 0x66b5a570, 0x9065cc35, 0x6ddf1e6f, 0xd49a2985, 0x00000001, 0xf00001f0 }, + FinalRegs = new uint[] { 0x688a6dd6, 0xcabb9832, 0xa187c464, 0xe4474634, 0x19316c88, 0x8b99d147, 0xd67bc441, 0x48cfa0cf, 0x4cd8b792, 0xd673a441, 0x66b5a570, 0x9065cc35, 0x6ddf1e6f, 0xd49a2985, 0x00000001, 0xf00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf1a5, 0x4730, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x69e8d900, 0x3ca9d66e, 0x91788f4e, 0x6e821399, 0xd710747f, 0xc8e72a37, 0xf9f9702f, 0x8e689c3f, 0x87ef1e3c, 0xc8270c3e, 0xd76f0d87, 0x5482900c, 0xec43f474, 0x72617560, 0x00000001, 0x000001f0 }, + FinalRegs = new uint[] { 0x69e8d900, 0x3ca9d66e, 0x91788f4e, 0x6e821399, 0xd710747f, 0xc8e72a37, 0xf9f9702f, 0x18e72a37, 0x87ef1e3c, 0xc8270c3e, 0xd76f0d87, 0x5482900c, 0xec43f474, 0x72617560, 0x00000001, 0x000001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf5bd, 0x7d6b, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x56f27741, 0xdf3a0328, 0x49864f87, 0xd8b84caa, 0xd7a4cc2b, 0x85467faf, 0x6e972a47, 0xc2440b53, 0xa56fc6fa, 0xe86c3322, 0x19e1532d, 0x2984be63, 0xd7302738, 0xbf00369c, 0x00000001, 0xb00001f0 }, + FinalRegs = new uint[] { 0x56f27741, 0xdf3a0328, 0x49864f87, 0xd8b84caa, 0xd7a4cc2b, 0x85467faf, 0x6e972a47, 0xc2440b53, 0xa56fc6fa, 0xe86c3322, 0x19e1532d, 0x2984be63, 0xd7302738, 0xbf0032f0, 0x00000001, 0xa00001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf5aa, 0x048c, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xc48ce68c, 0x33c654cc, 0xa31ea382, 0x398c4095, 0xfff680a5, 0x5886b5f4, 0xb1debf0b, 0x8bd529bb, 0x1354ba05, 0xcf80960a, 0x18582cbe, 0x37ca8996, 0x08f95e3c, 0xc87fdb04, 0x00000001, 0x200001f0 }, + FinalRegs = new uint[] { 0xc48ce68c, 0x33c654cc, 0xa31ea382, 0x398c4095, 0x18122cbe, 0x5886b5f4, 0xb1debf0b, 0x8bd529bb, 0x1354ba05, 0xcf80960a, 0x18582cbe, 0x37ca8996, 0x08f95e3c, 0xc87fdb04, 0x00000001, 0x200001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf5ba, 0x13aa, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd2de6567, 0x993624bf, 0xcfbd492f, 0x7b922424, 0x9fa01912, 0x04225225, 0x3a812a6d, 0xe62792b8, 0xb47cee9a, 0x5694288e, 0x6c669666, 0x213701a6, 0xe423ad2d, 0xc7d5362b, 0x00000001, 0xb00001f0 }, + FinalRegs = new uint[] { 0xd2de6567, 0x993624bf, 0xcfbd492f, 0x6c515666, 0x9fa01912, 0x04225225, 0x3a812a6d, 0xe62792b8, 0xb47cee9a, 0x5694288e, 0x6c669666, 0x213701a6, 0xe423ad2d, 0xc7d5362b, 0x00000001, 0x200001f0 }, + }, + // RSB (imm) + new() + { + Instructions = new ushort[] { 0xf5dc, 0x767d, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x8496100e, 0x93007a60, 0x0d33d3dc, 0xd932c4e1, 0x6e05ad8d, 0xde3cc68e, 0x74400ff8, 0xce309ee7, 0x188e0ebd, 0xe10837ab, 0x6b2534e2, 0x280add20, 0x3adc0489, 0x8ef32355, 0x00000001, 0x600001f0 }, + FinalRegs = new uint[] { 0x8496100e, 0x93007a60, 0x0d33d3dc, 0xd932c4e1, 0x6e05ad8d, 0xde3cc68e, 0xc523ff6b, 0xce309ee7, 0x188e0ebd, 0xe10837ab, 0x6b2534e2, 0x280add20, 0x3adc0489, 0x8ef32355, 0x00000001, 0x800001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf1dc, 0x377d, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xc5d7fe20, 0xade81daf, 0xba65ccf8, 0xa101ee00, 0x3a2b70d9, 0xc90238d9, 0xc3b54049, 0x436bf83f, 0x99c96b58, 0xd134cb19, 0x4de47e7f, 0x6a175e2d, 0xd9e49229, 0x174d24ac, 0x00000001, 0x400001f0 }, + FinalRegs = new uint[] { 0xc5d7fe20, 0xade81daf, 0xba65ccf8, 0xa101ee00, 0x3a2b70d9, 0xc90238d9, 0xc3b54049, 0xa398eb54, 0x99c96b58, 0xd134cb19, 0x4de47e7f, 0x6a175e2d, 0xd9e49229, 0x174d24ac, 0x00000001, 0x900001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf5c5, 0x34bd, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xcea4f214, 0xdc15a8f8, 0xd22be9ef, 0x42c400c5, 0x2fd1fc9b, 0xca724b52, 0x5582071d, 0xd01b7816, 0xa4f5a435, 0xcfd50db5, 0x24e0c80b, 0x7b52178d, 0x11cd0449, 0xd6daa84a, 0x00000001, 0x800001f0 }, + FinalRegs = new uint[] { 0xcea4f214, 0xdc15a8f8, 0xd22be9ef, 0x42c400c5, 0x358f2eae, 0xca724b52, 0x5582071d, 0xd01b7816, 0xa4f5a435, 0xcfd50db5, 0x24e0c80b, 0x7b52178d, 0x11cd0449, 0xd6daa84a, 0x00000001, 0x800001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf1ce, 0x7846, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x3c676ff3, 0x511ea0cb, 0x15e79c80, 0x51a3a8c1, 0x535cc233, 0x6ae729a3, 0x4e5726da, 0x81260fb9, 0x24dd423a, 0x9e81d6c0, 0x812b3bd1, 0x55bd0f44, 0x1871ec65, 0x87087126, 0x00000001, 0x200001f0 }, + FinalRegs = new uint[] { 0x3c676ff3, 0x511ea0cb, 0x15e79c80, 0x51a3a8c1, 0x535cc233, 0x6ae729a3, 0x4e5726da, 0x81260fb9, 0x0317ffff, 0x9e81d6c0, 0x812b3bd1, 0x55bd0f44, 0x1871ec65, 0x87087126, 0x00000001, 0x200001f0 }, + }, + new() + { + Instructions = new ushort[] { 0xf5c5, 0x2418, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x2bb00694, 0x1c56a4c0, 0xc5cc4a3e, 0xc627c1ab, 0x4e4a8dfc, 0x1f3d71a4, 0x897d57b8, 0x0d4a7208, 0x433b7b88, 0xaaf24fd6, 0x2438f5f8, 0x9875e64a, 0xda475f22, 0x66d5e2e7, 0x00000001, 0x700001f0 }, + FinalRegs = new uint[] { 0x2bb00694, 0x1c56a4c0, 0xc5cc4a3e, 0xc627c1ab, 0xe0cc0e5c, 0x1f3d71a4, 0x897d57b8, 0x0d4a7208, 0x433b7b88, 0xaaf24fd6, 0x2438f5f8, 0x9875e64a, 0xda475f22, 0x66d5e2e7, 0x00000001, 0x700001f0 }, + }, + }; + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestT32Flow.cs b/src/Ryujinx.Tests/Cpu/CpuTestT32Flow.cs new file mode 100644 index 00000000..01159afc --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestT32Flow.cs @@ -0,0 +1,167 @@ +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("T32Flow")] + public sealed class CpuTestT32Flow : CpuTest32 + { + [Test] + public void TestT32B1() + { + // BNE label + ThumbOpcode(0xf040); + ThumbOpcode(0x8240); + for (int i = 0; i < 576; i++) + { + ThumbOpcode(0xe7fe); + } + // label: BX LR + ThumbOpcode(0x4770); + + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + } + + [Test] + public void TestT32B2() + { + // BNE label1 + ThumbOpcode(0xf040); + ThumbOpcode(0x8242); + // label2: BNE label3 + ThumbOpcode(0xf040); + ThumbOpcode(0x8242); + for (int i = 0; i < 576; i++) + { + ThumbOpcode(0xe7fe); + } + // label1: BNE label2 + ThumbOpcode(0xf47f); + ThumbOpcode(0xadbc); + // label3: BX LR + ThumbOpcode(0x4770); + + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + } + + [Test] + public void TestT32B3() + { + // B.W label + ThumbOpcode(0xf000); + ThumbOpcode(0xba40); + for (int i = 0; i < 576; i++) + { + ThumbOpcode(0xe7fe); + } + // label: BX LR + ThumbOpcode(0x4770); + + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + } + + [Test] + public void TestT32B4() + { + // B.W label1 + ThumbOpcode(0xf000); + ThumbOpcode(0xba42); + // label2: B.W label3 + ThumbOpcode(0xf000); + ThumbOpcode(0xba42); + for (int i = 0; i < 576; i++) + { + ThumbOpcode(0xe7fe); + } + // label1: B.W label2 + ThumbOpcode(0xf7ff); + ThumbOpcode(0xbdbc); + // label3: BX LR + ThumbOpcode(0x4770); + + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + } + + [Test] + public void TestT32Bl() + { + // BL label + ThumbOpcode(0xf000); + ThumbOpcode(0xf840); + for (int i = 0; i < 64; i++) + { + ThumbOpcode(0xe7fe); + } + ThumbOpcode(0x4670); // label: MOV R0, LR + ThumbOpcode(0x2100); // MOVS R1, #0 + ThumbOpcode(0x468e); // MOV LR, R1 + ThumbOpcode(0x4770); // BX LR + + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + + Assert.That(GetContext().GetX(0), Is.EqualTo(CodeBaseAddress + 0x5)); + } + + [Test] + public void TestT32Blx1() + { + // BLX label + ThumbOpcode(0xf000); + ThumbOpcode(0xe840); + for (int i = 0; i < 64; i++) + { + ThumbOpcode(0x4770); + } + // .arm ; label: MOV R0, LR + Opcode(0xe1a0000e); + // MOV LR, #0 + Opcode(0xe3a0e000); + // BX LR + Opcode(0xe12fff1e); + + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + + Assert.That(GetContext().GetX(0), Is.EqualTo(CodeBaseAddress + 0x5)); + Assert.That(GetContext().GetPstateFlag(PState.TFlag), Is.EqualTo(false)); + } + + [Test] + public void TestT32Blx2() + { + // NOP + ThumbOpcode(0xbf00); + // BLX label + ThumbOpcode(0xf000); + ThumbOpcode(0xe840); + for (int i = 0; i < 63; i++) + { + ThumbOpcode(0x4770); + } + // .arm ; label: MOV R0, LR + Opcode(0xe1a0000e); + // MOV LR, #0 + Opcode(0xe3a0e000); + // BX LR + Opcode(0xe12fff1e); + + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + + Assert.That(GetContext().GetX(0), Is.EqualTo(CodeBaseAddress + 0x7)); + Assert.That(GetContext().GetPstateFlag(PState.TFlag), Is.EqualTo(false)); + } + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestT32Mem.cs b/src/Ryujinx.Tests/Cpu/CpuTestT32Mem.cs new file mode 100644 index 00000000..94ccb950 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestT32Mem.cs @@ -0,0 +1,521 @@ +using NUnit.Framework; +using System; + +namespace Ryujinx.Tests.Cpu +{ + [Category("T32Mem")] + public sealed class CpuTestT32Mem : CpuTest32 + { + [Test] + public void TestT32MemImm([ValueSource(nameof(ImmTestCases))] PrecomputedMemoryThumbTestCase test) + { + RunPrecomputedTestCase(test); + } + + public static readonly PrecomputedMemoryThumbTestCase[] ImmTestCases = + { + // STRB (imm8) + new() + { + Instructions = new ushort[] { 0xf80c, 0x1b2f, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x000023bd, 0x000027bb, 0x00002715, 0x000028f5, 0x0000233f, 0x0000213b, 0x00002eea, 0x0000282b, 0x000021e1, 0x0000264c, 0x000029e0, 0x00002ae7, 0x000021ff, 0x000026e3, 0x00000001, 0x800001f0 }, + FinalRegs = new uint[] { 0x000023bd, 0x000027bb, 0x00002715, 0x000028f5, 0x0000233f, 0x0000213b, 0x00002eea, 0x0000282b, 0x000021e1, 0x0000264c, 0x000029e0, 0x00002ae7, 0x0000222e, 0x000026e3, 0x00000001, 0x800001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x21fe, Value: 0xbbfe) }, + }, + new() + { + Instructions = new ushort[] { 0xf80a, 0x2f81, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x0000223c, 0x00002db9, 0x00002900, 0x0000247c, 0x00002b0a, 0x0000266b, 0x000026df, 0x00002447, 0x000024bb, 0x00002687, 0x0000266f, 0x00002a80, 0x000025ff, 0x00002881, 0x00000001, 0xa00001f0 }, + FinalRegs = new uint[] { 0x0000223c, 0x00002db9, 0x00002900, 0x0000247c, 0x00002b0a, 0x0000266b, 0x000026df, 0x00002447, 0x000024bb, 0x00002687, 0x000026f0, 0x00002a80, 0x000025ff, 0x00002881, 0x00000001, 0xa00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x26f0, Value: 0x2600) }, + }, + new() + { + Instructions = new ushort[] { 0xf803, 0x6968, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x000026ed, 0x00002685, 0x00002cd1, 0x00002dac, 0x00002a23, 0x00002626, 0x00002ec9, 0x0000245c, 0x000024ef, 0x00002319, 0x000026ce, 0x0000214d, 0x00002401, 0x000028b4, 0x00000001, 0x300001f0 }, + FinalRegs = new uint[] { 0x000026ed, 0x00002685, 0x00002cd1, 0x00002d44, 0x00002a23, 0x00002626, 0x00002ec9, 0x0000245c, 0x000024ef, 0x00002319, 0x000026ce, 0x0000214d, 0x00002401, 0x000028b4, 0x00000001, 0x300001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2dac, Value: 0x2dc9) }, + }, + new() + { + Instructions = new ushort[] { 0xf804, 0x89ad, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x0000265d, 0x00002b9c, 0x00002360, 0x000029ec, 0x00002413, 0x00002d8e, 0x00002aad, 0x00002d29, 0x00002bca, 0x00002a44, 0x00002980, 0x00002710, 0x000022fa, 0x0000222e, 0x00000001, 0xc00001f0 }, + FinalRegs = new uint[] { 0x0000265d, 0x00002b9c, 0x00002360, 0x000029ec, 0x00002366, 0x00002d8e, 0x00002aad, 0x00002d29, 0x00002bca, 0x00002a44, 0x00002980, 0x00002710, 0x000022fa, 0x0000222e, 0x00000001, 0xc00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2412, Value: 0xca12) }, + }, + new() + { + Instructions = new ushort[] { 0xf80d, 0xa9fe, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x0000298d, 0x00002e6c, 0x00002986, 0x00002ebb, 0x0000213e, 0x00002e39, 0x0000246f, 0x00002b6c, 0x00002ee2, 0x0000259e, 0x0000250a, 0x000029f6, 0x000021e7, 0x00002d9d, 0x00000001, 0x900001f0 }, + FinalRegs = new uint[] { 0x0000298d, 0x00002e6c, 0x00002986, 0x00002ebb, 0x0000213e, 0x00002e39, 0x0000246f, 0x00002b6c, 0x00002ee2, 0x0000259e, 0x0000250a, 0x000029f6, 0x000021e7, 0x00002c9f, 0x00000001, 0x900001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2d9c, Value: 0x0a9c) }, + }, + new() + { + Instructions = new ushort[] { 0xf80d, 0x3c46, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002c6f, 0x000028cc, 0x000025f0, 0x000022cc, 0x00002de3, 0x0000243c, 0x000025fb, 0x00002e88, 0x00002985, 0x000023ee, 0x00002120, 0x00002d50, 0x0000270a, 0x00002bbd, 0x00000001, 0xa00001f0 }, + FinalRegs = new uint[] { 0x00002c6f, 0x000028cc, 0x000025f0, 0x000022cc, 0x00002de3, 0x0000243c, 0x000025fb, 0x00002e88, 0x00002985, 0x000023ee, 0x00002120, 0x00002d50, 0x0000270a, 0x00002bbd, 0x00000001, 0xa00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2b76, Value: 0xcc76) }, + }, + new() + { + Instructions = new ushort[] { 0xf801, 0x6c56, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002d6e, 0x00002530, 0x00002e6d, 0x00002942, 0x00002985, 0x00002d64, 0x00002a73, 0x00002ac6, 0x00002955, 0x00002881, 0x0000221d, 0x00002cb0, 0x0000225f, 0x00002534, 0x00000001, 0x100001f0 }, + FinalRegs = new uint[] { 0x00002d6e, 0x00002530, 0x00002e6d, 0x00002942, 0x00002985, 0x00002d64, 0x00002a73, 0x00002ac6, 0x00002955, 0x00002881, 0x0000221d, 0x00002cb0, 0x0000225f, 0x00002534, 0x00000001, 0x100001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x24da, Value: 0x2473) }, + }, + new() + { + Instructions = new ushort[] { 0xf809, 0xcc76, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002d50, 0x000025f2, 0x0000250a, 0x0000214c, 0x000023d1, 0x00002115, 0x00002c27, 0x00002540, 0x0000222b, 0x00002d03, 0x00002679, 0x00002b52, 0x00002eee, 0x00002b2a, 0x00000001, 0xd00001f0 }, + FinalRegs = new uint[] { 0x00002d50, 0x000025f2, 0x0000250a, 0x0000214c, 0x000023d1, 0x00002115, 0x00002c27, 0x00002540, 0x0000222b, 0x00002d03, 0x00002679, 0x00002b52, 0x00002eee, 0x00002b2a, 0x00000001, 0xd00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2c8c, Value: 0xee8c) }, + }, + new() + { + Instructions = new ushort[] { 0xf808, 0x1c8d, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002844, 0x00002b78, 0x000028b0, 0x000026ff, 0x0000280b, 0x00002e0b, 0x00002de4, 0x00002b53, 0x00002ecd, 0x000021b5, 0x000026bc, 0x00002e9d, 0x00002d33, 0x000027f0, 0x00000001, 0x800001f0 }, + FinalRegs = new uint[] { 0x00002844, 0x00002b78, 0x000028b0, 0x000026ff, 0x0000280b, 0x00002e0b, 0x00002de4, 0x00002b53, 0x00002ecd, 0x000021b5, 0x000026bc, 0x00002e9d, 0x00002d33, 0x000027f0, 0x00000001, 0x800001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2e40, Value: 0x2e78) }, + }, + new() + { + Instructions = new ushort[] { 0xf80b, 0xbc26, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002244, 0x000025ad, 0x00002434, 0x00002b06, 0x00002ebd, 0x0000292b, 0x00002431, 0x00002e12, 0x0000289b, 0x0000265a, 0x00002747, 0x00002bac, 0x00002dae, 0x00002582, 0x00000001, 0xf00001f0 }, + FinalRegs = new uint[] { 0x00002244, 0x000025ad, 0x00002434, 0x00002b06, 0x00002ebd, 0x0000292b, 0x00002431, 0x00002e12, 0x0000289b, 0x0000265a, 0x00002747, 0x00002bac, 0x00002dae, 0x00002582, 0x00000001, 0xf00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2b86, Value: 0x2bac) }, + }, + // STRB (imm12) + new() + { + Instructions = new ushort[] { 0xf887, 0x67c2, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x700001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x700001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x27c2, Value: 0x2700) }, + }, + new() + { + Instructions = new ushort[] { 0xf883, 0x9fda, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xc00001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xc00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2fda, Value: 0x2f00) }, + }, + new() + { + Instructions = new ushort[] { 0xf889, 0xd200, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x400001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x400001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf88c, 0x1c5b, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x500001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x500001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2c5a, Value: 0x005a) }, + }, + new() + { + Instructions = new ushort[] { 0xf887, 0x9fe2, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xe00001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xe00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2fe2, Value: 0x2f00) }, + }, + // STRH (imm8) + new() + { + Instructions = new ushort[] { 0xf826, 0x0b0a, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x000025a2, 0x000024d5, 0x00002ca1, 0x0000238a, 0x0000279c, 0x0000244c, 0x00002620, 0x00002c0e, 0x0000233e, 0x0000285f, 0x000021ab, 0x00002bd0, 0x0000281f, 0x00002be7, 0x00000001, 0x600001f0 }, + FinalRegs = new uint[] { 0x000025a2, 0x000024d5, 0x00002ca1, 0x0000238a, 0x0000279c, 0x0000244c, 0x0000262a, 0x00002c0e, 0x0000233e, 0x0000285f, 0x000021ab, 0x00002bd0, 0x0000281f, 0x00002be7, 0x00000001, 0x600001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2620, Value: 0x25a2) }, + }, + new() + { + Instructions = new ushort[] { 0xf827, 0xcf61, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002555, 0x0000238f, 0x00002829, 0x000028c8, 0x00002399, 0x00002aab, 0x00002d6f, 0x000029eb, 0x000029e0, 0x00002d33, 0x0000292a, 0x00002b33, 0x00002e29, 0x00002ca4, 0x00000001, 0x100001f0 }, + FinalRegs = new uint[] { 0x00002555, 0x0000238f, 0x00002829, 0x000028c8, 0x00002399, 0x00002aab, 0x00002d6f, 0x00002a4c, 0x000029e0, 0x00002d33, 0x0000292a, 0x00002b33, 0x00002e29, 0x00002ca4, 0x00000001, 0x100001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2a4c, Value: 0x2e29) }, + }, + new() + { + Instructions = new ushort[] { 0xf821, 0x9b00, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x000027ba, 0x00002514, 0x00002b07, 0x00002daf, 0x00002790, 0x0000274b, 0x00002379, 0x00002a98, 0x000024c8, 0x00002398, 0x000021ba, 0x00002959, 0x00002821, 0x00002d09, 0x00000001, 0x500001f0 }, + FinalRegs = new uint[] { 0x000027ba, 0x00002514, 0x00002b07, 0x00002daf, 0x00002790, 0x0000274b, 0x00002379, 0x00002a98, 0x000024c8, 0x00002398, 0x000021ba, 0x00002959, 0x00002821, 0x00002d09, 0x00000001, 0x500001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2514, Value: 0x2398) }, + }, + new() + { + Instructions = new ushort[] { 0xf82c, 0xa927, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x0000226a, 0x00002792, 0x00002870, 0x00002918, 0x00002757, 0x00002679, 0x00002546, 0x000027f5, 0x00002edc, 0x00002cd3, 0x0000274a, 0x00002562, 0x000029a1, 0x00002976, 0x00000001, 0x100001f0 }, + FinalRegs = new uint[] { 0x0000226a, 0x00002792, 0x00002870, 0x00002918, 0x00002757, 0x00002679, 0x00002546, 0x000027f5, 0x00002edc, 0x00002cd3, 0x0000274a, 0x00002562, 0x0000297a, 0x00002976, 0x00000001, 0x100001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x29a0, Value: 0x4aa0), (Address: 0x29a2, Value: 0x2927) }, + }, + new() + { + Instructions = new ushort[] { 0xf824, 0xcfe4, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x0000238b, 0x00002d22, 0x00002476, 0x000028ae, 0x00002442, 0x0000212b, 0x000026de, 0x00002a1a, 0x00002a02, 0x00002e47, 0x00002b2d, 0x00002427, 0x00002d1c, 0x000026d4, 0x00000001, 0xd00001f0 }, + FinalRegs = new uint[] { 0x0000238b, 0x00002d22, 0x00002476, 0x000028ae, 0x00002526, 0x0000212b, 0x000026de, 0x00002a1a, 0x00002a02, 0x00002e47, 0x00002b2d, 0x00002427, 0x00002d1c, 0x000026d4, 0x00000001, 0xd00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2526, Value: 0x2d1c) }, + }, + new() + { + Instructions = new ushort[] { 0xf820, 0x1c3d, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002227, 0x00002b29, 0x0000232a, 0x0000214e, 0x000029ef, 0x00002522, 0x000029d3, 0x0000286c, 0x000029b2, 0x00002147, 0x00002c65, 0x00002891, 0x000029c2, 0x000028a5, 0x00000001, 0x800001f0 }, + FinalRegs = new uint[] { 0x00002227, 0x00002b29, 0x0000232a, 0x0000214e, 0x000029ef, 0x00002522, 0x000029d3, 0x0000286c, 0x000029b2, 0x00002147, 0x00002c65, 0x00002891, 0x000029c2, 0x000028a5, 0x00000001, 0x800001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x21ea, Value: 0x2b29) }, + }, + new() + { + Instructions = new ushort[] { 0xf826, 0x1cdf, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002232, 0x000029a1, 0x00002938, 0x00002ae7, 0x000029a4, 0x00002366, 0x0000273a, 0x000023f6, 0x00002601, 0x00002919, 0x000028e3, 0x00002907, 0x000023c1, 0x00002138, 0x00000001, 0x100001f0 }, + FinalRegs = new uint[] { 0x00002232, 0x000029a1, 0x00002938, 0x00002ae7, 0x000029a4, 0x00002366, 0x0000273a, 0x000023f6, 0x00002601, 0x00002919, 0x000028e3, 0x00002907, 0x000023c1, 0x00002138, 0x00000001, 0x100001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x265a, Value: 0xa15a), (Address: 0x265c, Value: 0x2629) }, + }, + new() + { + Instructions = new ushort[] { 0xf82b, 0x3c66, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002974, 0x00002372, 0x0000276c, 0x000021df, 0x00002272, 0x00002928, 0x00002c50, 0x0000290e, 0x00002319, 0x000021d1, 0x00002a82, 0x000027ff, 0x00002730, 0x000027b2, 0x00000001, 0x700001f0 }, + FinalRegs = new uint[] { 0x00002974, 0x00002372, 0x0000276c, 0x000021df, 0x00002272, 0x00002928, 0x00002c50, 0x0000290e, 0x00002319, 0x000021d1, 0x00002a82, 0x000027ff, 0x00002730, 0x000027b2, 0x00000001, 0x700001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2798, Value: 0xdf98), (Address: 0x279a, Value: 0x2721) }, + }, + new() + { + Instructions = new ushort[] { 0xf822, 0x3c06, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x000021b8, 0x00002357, 0x00002b00, 0x00002207, 0x00002648, 0x0000219c, 0x000021d2, 0x000023b0, 0x00002368, 0x00002a41, 0x000026ac, 0x00002a86, 0x00002879, 0x00002c1d, 0x00000001, 0x700001f0 }, + FinalRegs = new uint[] { 0x000021b8, 0x00002357, 0x00002b00, 0x00002207, 0x00002648, 0x0000219c, 0x000021d2, 0x000023b0, 0x00002368, 0x00002a41, 0x000026ac, 0x00002a86, 0x00002879, 0x00002c1d, 0x00000001, 0x700001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2afa, Value: 0x2207) }, + }, + new() + { + Instructions = new ushort[] { 0xf824, 0xac84, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002796, 0x000027c8, 0x0000241b, 0x0000214d, 0x0000220b, 0x00002587, 0x00002130, 0x00002910, 0x00002ac2, 0x00002e74, 0x000028f8, 0x000024bf, 0x0000263a, 0x00002625, 0x00000001, 0x600001f0 }, + FinalRegs = new uint[] { 0x00002796, 0x000027c8, 0x0000241b, 0x0000214d, 0x0000220b, 0x00002587, 0x00002130, 0x00002910, 0x00002ac2, 0x00002e74, 0x000028f8, 0x000024bf, 0x0000263a, 0x00002625, 0x00000001, 0x600001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2186, Value: 0xf886), (Address: 0x2188, Value: 0x2128) }, + }, + // STRH (imm12) + new() + { + Instructions = new ushort[] { 0xf8a5, 0x59d4, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x000001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x000001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x29d4, Value: 0x2000) }, + }, + new() + { + Instructions = new ushort[] { 0xf8ac, 0xc533, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xe00001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xe00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2532, Value: 0x0032), (Address: 0x2534, Value: 0x2520) }, + }, + new() + { + Instructions = new ushort[] { 0xf8a3, 0xb559, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x000001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x000001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2558, Value: 0x0058), (Address: 0x255a, Value: 0x2520) }, + }, + new() + { + Instructions = new ushort[] { 0xf8a5, 0xdb3a, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xb00001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xb00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2b3a, Value: 0x2000) }, + }, + new() + { + Instructions = new ushort[] { 0xf8a9, 0x02cc, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xc00001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xc00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x22cc, Value: 0x2000) }, + }, + // STR (imm8) + new() + { + Instructions = new ushort[] { 0xf846, 0x1fb4, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002b17, 0x0000272f, 0x00002483, 0x0000284c, 0x0000287f, 0x0000238f, 0x0000222d, 0x00002259, 0x0000249d, 0x00002e3f, 0x00002323, 0x00002729, 0x000025c1, 0x00002866, 0x00000001, 0x900001f0 }, + FinalRegs = new uint[] { 0x00002b17, 0x0000272f, 0x00002483, 0x0000284c, 0x0000287f, 0x0000238f, 0x000022e1, 0x00002259, 0x0000249d, 0x00002e3f, 0x00002323, 0x00002729, 0x000025c1, 0x00002866, 0x00000001, 0x900001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x22e0, Value: 0x2fe0), (Address: 0x22e2, Value: 0x0027), (Address: 0x22e4, Value: 0x2200) }, + }, + new() + { + Instructions = new ushort[] { 0xf844, 0x3f11, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x000028e1, 0x00002d48, 0x000027d6, 0x000023ac, 0x000027bb, 0x000026cf, 0x000023c1, 0x00002633, 0x0000214b, 0x00002434, 0x0000239a, 0x000025c6, 0x00002148, 0x00002d1f, 0x00000001, 0x300001f0 }, + FinalRegs = new uint[] { 0x000028e1, 0x00002d48, 0x000027d6, 0x000023ac, 0x000027cc, 0x000026cf, 0x000023c1, 0x00002633, 0x0000214b, 0x00002434, 0x0000239a, 0x000025c6, 0x00002148, 0x00002d1f, 0x00000001, 0x300001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x27cc, Value: 0x23ac), (Address: 0x27ce, Value: 0x0000) }, + }, + new() + { + Instructions = new ushort[] { 0xf847, 0x09c2, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x0000248b, 0x00002396, 0x000023c5, 0x00002be0, 0x0000237d, 0x00002191, 0x00002da0, 0x0000211c, 0x00002d24, 0x000021e6, 0x000024ff, 0x00002268, 0x00002968, 0x0000244d, 0x00000001, 0x800001f0 }, + FinalRegs = new uint[] { 0x0000248b, 0x00002396, 0x000023c5, 0x00002be0, 0x0000237d, 0x00002191, 0x00002da0, 0x0000205a, 0x00002d24, 0x000021e6, 0x000024ff, 0x00002268, 0x00002968, 0x0000244d, 0x00000001, 0x800001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x211c, Value: 0x248b), (Address: 0x211e, Value: 0x0000) }, + }, + new() + { + Instructions = new ushort[] { 0xf84d, 0x7f23, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x000025b0, 0x0000260e, 0x00002343, 0x00002e36, 0x000024c5, 0x000029bc, 0x0000278e, 0x00002b63, 0x00002ce7, 0x000029af, 0x000023bf, 0x00002475, 0x00002197, 0x00002c33, 0x00000001, 0x200001f0 }, + FinalRegs = new uint[] { 0x000025b0, 0x0000260e, 0x00002343, 0x00002e36, 0x000024c5, 0x000029bc, 0x0000278e, 0x00002b63, 0x00002ce7, 0x000029af, 0x000023bf, 0x00002475, 0x00002197, 0x00002c56, 0x00000001, 0x200001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2c56, Value: 0x2b63), (Address: 0x2c58, Value: 0x0000) }, + }, + new() + { + Instructions = new ushort[] { 0xf843, 0x9d24, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002ce4, 0x00002e0e, 0x000026d5, 0x000025fb, 0x00002b78, 0x0000215a, 0x00002af7, 0x0000259c, 0x00002645, 0x000027dc, 0x00002163, 0x000028f5, 0x000029df, 0x0000230b, 0x00000001, 0x500001f0 }, + FinalRegs = new uint[] { 0x00002ce4, 0x00002e0e, 0x000026d5, 0x000025d7, 0x00002b78, 0x0000215a, 0x00002af7, 0x0000259c, 0x00002645, 0x000027dc, 0x00002163, 0x000028f5, 0x000029df, 0x0000230b, 0x00000001, 0x500001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x25d6, Value: 0xdcd6), (Address: 0x25d8, Value: 0x0027), (Address: 0x25da, Value: 0x2500) }, + }, + new() + { + Instructions = new ushort[] { 0xf849, 0xdc1a, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002d98, 0x0000254a, 0x00002540, 0x00002324, 0x0000264e, 0x00002523, 0x0000271f, 0x00002875, 0x000023b3, 0x00002680, 0x00002223, 0x000022bf, 0x000025f4, 0x00002d81, 0x00000001, 0x700001f0 }, + FinalRegs = new uint[] { 0x00002d98, 0x0000254a, 0x00002540, 0x00002324, 0x0000264e, 0x00002523, 0x0000271f, 0x00002875, 0x000023b3, 0x00002680, 0x00002223, 0x000022bf, 0x000025f4, 0x00002d81, 0x00000001, 0x700001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2666, Value: 0x2d81), (Address: 0x2668, Value: 0x0000) }, + }, + new() + { + Instructions = new ushort[] { 0xf849, 0x0cd1, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x0000255a, 0x00002655, 0x00002276, 0x000022e4, 0x00002eef, 0x00002e99, 0x00002b55, 0x00002a40, 0x00002661, 0x00002dbd, 0x00002687, 0x000024e1, 0x000023ea, 0x00002b88, 0x00000001, 0xc00001f0 }, + FinalRegs = new uint[] { 0x0000255a, 0x00002655, 0x00002276, 0x000022e4, 0x00002eef, 0x00002e99, 0x00002b55, 0x00002a40, 0x00002661, 0x00002dbd, 0x00002687, 0x000024e1, 0x000023ea, 0x00002b88, 0x00000001, 0xc00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2cec, Value: 0x255a), (Address: 0x2cee, Value: 0x0000) }, + }, + new() + { + Instructions = new ushort[] { 0xf847, 0x7c96, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x000027f6, 0x0000222a, 0x000024e1, 0x00002a2d, 0x00002ee8, 0x000023f2, 0x000029de, 0x00002a53, 0x000029da, 0x00002d2c, 0x00002d6f, 0x000026b8, 0x00002777, 0x00002e3a, 0x00000001, 0xf00001f0 }, + FinalRegs = new uint[] { 0x000027f6, 0x0000222a, 0x000024e1, 0x00002a2d, 0x00002ee8, 0x000023f2, 0x000029de, 0x00002a53, 0x000029da, 0x00002d2c, 0x00002d6f, 0x000026b8, 0x00002777, 0x00002e3a, 0x00000001, 0xf00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x29bc, Value: 0x53bc), (Address: 0x29be, Value: 0x002a), (Address: 0x29c0, Value: 0x2900) }, + }, + new() + { + Instructions = new ushort[] { 0xf84d, 0x8cbd, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002a58, 0x00002a59, 0x00002dfd, 0x00002ba8, 0x00002929, 0x00002146, 0x00002706, 0x000025f3, 0x000023d7, 0x0000221f, 0x000027ae, 0x00002a6e, 0x00002824, 0x00002357, 0x00000001, 0x600001f0 }, + FinalRegs = new uint[] { 0x00002a58, 0x00002a59, 0x00002dfd, 0x00002ba8, 0x00002929, 0x00002146, 0x00002706, 0x000025f3, 0x000023d7, 0x0000221f, 0x000027ae, 0x00002a6e, 0x00002824, 0x00002357, 0x00000001, 0x600001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x229a, Value: 0x23d7), (Address: 0x229c, Value: 0x0000) }, + }, + new() + { + Instructions = new ushort[] { 0xf846, 0xacaf, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x0000284f, 0x00002def, 0x0000292f, 0x000021e8, 0x0000274e, 0x00002518, 0x00002538, 0x00002375, 0x00002d28, 0x0000229a, 0x0000255f, 0x00002eca, 0x00002e15, 0x000021aa, 0x00000001, 0x100001f0 }, + FinalRegs = new uint[] { 0x0000284f, 0x00002def, 0x0000292f, 0x000021e8, 0x0000274e, 0x00002518, 0x00002538, 0x00002375, 0x00002d28, 0x0000229a, 0x0000255f, 0x00002eca, 0x00002e15, 0x000021aa, 0x00000001, 0x100001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2488, Value: 0x5f88), (Address: 0x248a, Value: 0x0025), (Address: 0x248c, Value: 0x2400) }, + }, + // STR (imm12) + new() + { + Instructions = new ushort[] { 0xf8cc, 0x1a6e, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x500001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x500001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2a6e, Value: 0x2000), (Address: 0x2a70, Value: 0x0000) }, + }, + new() + { + Instructions = new ushort[] { 0xf8c9, 0xcfc1, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xe00001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xe00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x2fc0, Value: 0x00c0), (Address: 0x2fc2, Value: 0x0020), (Address: 0x2fc4, Value: 0x2f00) }, + }, + new() + { + Instructions = new ushort[] { 0xf8c3, 0xb5dd, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x600001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x600001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x25dc, Value: 0x00dc), (Address: 0x25de, Value: 0x0020), (Address: 0x25e0, Value: 0x2500) }, + }, + new() + { + Instructions = new ushort[] { 0xf8c0, 0x69e9, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xe00001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xe00001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x29e8, Value: 0x00e8), (Address: 0x29ea, Value: 0x0020), (Address: 0x29ec, Value: 0x2900) }, + }, + new() + { + Instructions = new ushort[] { 0xf8cd, 0x028f, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x600001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x600001f0 }, + MemoryDelta = new (ulong Address, ushort Value)[] { (Address: 0x228e, Value: 0x008e), (Address: 0x2290, Value: 0x0020), (Address: 0x2292, Value: 0x2200) }, + }, + // LDRB (imm8) + new() + { + Instructions = new ushort[] { 0xf816, 0x1c48, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002cb8, 0x00002345, 0x00002ebc, 0x00002db8, 0x000021d4, 0x000026e4, 0x00002458, 0x000029e3, 0x000028d2, 0x000027f4, 0x000023d6, 0x00002def, 0x0000285c, 0x00002d06, 0x00000001, 0x600001f0 }, + FinalRegs = new uint[] { 0x00002cb8, 0x00000010, 0x00002ebc, 0x00002db8, 0x000021d4, 0x000026e4, 0x00002458, 0x000029e3, 0x000028d2, 0x000027f4, 0x000023d6, 0x00002def, 0x0000285c, 0x00002d06, 0x00000001, 0x600001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf815, 0x2d6e, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x000021e4, 0x00002425, 0x00002e42, 0x00002a58, 0x00002708, 0x00002965, 0x00002a1d, 0x00002ed5, 0x00002cc4, 0x000026e1, 0x00002b4b, 0x00002ade, 0x00002824, 0x00002975, 0x00000001, 0x100001f0 }, + FinalRegs = new uint[] { 0x000021e4, 0x00002425, 0x00000028, 0x00002a58, 0x00002708, 0x000028f7, 0x00002a1d, 0x00002ed5, 0x00002cc4, 0x000026e1, 0x00002b4b, 0x00002ade, 0x00002824, 0x00002975, 0x00000001, 0x100001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf818, 0x0d33, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002492, 0x0000214d, 0x00002827, 0x000021af, 0x0000215e, 0x000028d6, 0x000024ec, 0x00002984, 0x0000297b, 0x000024b5, 0x000024ca, 0x0000298f, 0x00002339, 0x00002b7e, 0x00000001, 0xd00001f0 }, + FinalRegs = new uint[] { 0x00000048, 0x0000214d, 0x00002827, 0x000021af, 0x0000215e, 0x000028d6, 0x000024ec, 0x00002984, 0x00002948, 0x000024b5, 0x000024ca, 0x0000298f, 0x00002339, 0x00002b7e, 0x00000001, 0xd00001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf810, 0xbff3, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002ea6, 0x000024fa, 0x00002346, 0x00002748, 0x0000283f, 0x00002770, 0x000023e3, 0x000021aa, 0x0000214a, 0x00002d58, 0x00002159, 0x000022e7, 0x00002242, 0x00002728, 0x00000001, 0x600001f0 }, + FinalRegs = new uint[] { 0x00002f99, 0x000024fa, 0x00002346, 0x00002748, 0x0000283f, 0x00002770, 0x000023e3, 0x000021aa, 0x0000214a, 0x00002d58, 0x00002159, 0x0000002f, 0x00002242, 0x00002728, 0x00000001, 0x600001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + // LDRB (imm12) + new() + { + Instructions = new ushort[] { 0xf892, 0xcc8f, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x100001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x0000002c, 0x00002000, 0x00000001, 0x100001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf89a, 0x7fdc, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x200001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x000000dc, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x200001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf890, 0x5f9f, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x800001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x0000002f, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x800001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf894, 0xdda1, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x900001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x0000002d, 0x00000001, 0x900001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf890, 0xc281, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x100001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000022, 0x00002000, 0x00000001, 0x100001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + // LDRH (imm8) + new() + { + Instructions = new ushort[] { 0xf834, 0x89d8, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002a9e, 0x00002d84, 0x00002e9b, 0x00002e7f, 0x000024a2, 0x00002b7b, 0x00002e3b, 0x0000299a, 0x00002dff, 0x00002a9e, 0x000027b2, 0x00002a90, 0x00002883, 0x0000288d, 0x00000001, 0x500001f0 }, + FinalRegs = new uint[] { 0x00002a9e, 0x00002d84, 0x00002e9b, 0x00002e7f, 0x000023ca, 0x00002b7b, 0x00002e3b, 0x0000299a, 0x000024a2, 0x00002a9e, 0x000027b2, 0x00002a90, 0x00002883, 0x0000288d, 0x00000001, 0x500001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf833, 0x6be4, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x000028bd, 0x00002b0e, 0x00002bc1, 0x00002a83, 0x00002293, 0x00002c7c, 0x00002bfe, 0x00002eb7, 0x0000299b, 0x000026e6, 0x0000219c, 0x00002d5e, 0x00002cd4, 0x000026cf, 0x00000001, 0xd00001f0 }, + FinalRegs = new uint[] { 0x000028bd, 0x00002b0e, 0x00002bc1, 0x00002b67, 0x00002293, 0x00002c7c, 0x0000842a, 0x00002eb7, 0x0000299b, 0x000026e6, 0x0000219c, 0x00002d5e, 0x00002cd4, 0x000026cf, 0x00000001, 0xd00001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf83d, 0x1bca, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x0000250e, 0x00002776, 0x000029e5, 0x0000276e, 0x00002c6b, 0x00002712, 0x00002a85, 0x00002d56, 0x000024c0, 0x00002d86, 0x0000254a, 0x00002549, 0x00002795, 0x00002e97, 0x00000001, 0x200001f0 }, + FinalRegs = new uint[] { 0x0000250e, 0x0000982e, 0x000029e5, 0x0000276e, 0x00002c6b, 0x00002712, 0x00002a85, 0x00002d56, 0x000024c0, 0x00002d86, 0x0000254a, 0x00002549, 0x00002795, 0x00002f61, 0x00000001, 0x200001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + // LDRH (imm12) + new() + { + Instructions = new ushort[] { 0xf8b7, 0x92fc, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xa00001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x000022fc, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xa00001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf8ba, 0xadd9, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xa00001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x0000da2d, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xa00001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf8bb, 0x0bb0, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xd00001f0 }, + FinalRegs = new uint[] { 0x00002bb0, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0xd00001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf8b8, 0xc3f8, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x600001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x000023f8, 0x00002000, 0x00000001, 0x600001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + // LDR (imm8) + new() + { + Instructions = new ushort[] { 0xf85b, 0x3fd1, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002a19, 0x00002e5b, 0x0000231b, 0x000021fa, 0x00002e95, 0x00002bd5, 0x00002e9c, 0x00002dfa, 0x000021d8, 0x00002ce1, 0x00002318, 0x00002735, 0x0000247d, 0x00002436, 0x00000001, 0xf00001f0 }, + FinalRegs = new uint[] { 0x00002a19, 0x00002e5b, 0x0000231b, 0x28082806, 0x00002e95, 0x00002bd5, 0x00002e9c, 0x00002dfa, 0x000021d8, 0x00002ce1, 0x00002318, 0x00002806, 0x0000247d, 0x00002436, 0x00000001, 0xf00001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf854, 0xab9e, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x0000214f, 0x00002578, 0x00002a98, 0x000021b0, 0x00002ebb, 0x0000284a, 0x00002319, 0x00002581, 0x00002179, 0x00002594, 0x00002373, 0x000028f4, 0x00002ec5, 0x00002e0a, 0x00000001, 0xb00001f0 }, + FinalRegs = new uint[] { 0x0000214f, 0x00002578, 0x00002a98, 0x000021b0, 0x00002f59, 0x0000284a, 0x00002319, 0x00002581, 0x00002179, 0x00002594, 0xbe2ebc2e, 0x000028f4, 0x00002ec5, 0x00002e0a, 0x00000001, 0xb00001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf852, 0x6d2d, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002e27, 0x00002676, 0x00002bde, 0x000022d9, 0x00002362, 0x00002d4b, 0x00002dab, 0x000022b6, 0x0000229c, 0x00002507, 0x00002848, 0x0000225f, 0x00002ac2, 0x000023c3, 0x00000001, 0xf00001f0 }, + FinalRegs = new uint[] { 0x00002e27, 0x00002676, 0x00002bb1, 0x000022d9, 0x00002362, 0x00002d4b, 0xb42bb22b, 0x000022b6, 0x0000229c, 0x00002507, 0x00002848, 0x0000225f, 0x00002ac2, 0x000023c3, 0x00000001, 0xf00001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf850, 0x8da5, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002559, 0x0000285e, 0x000021de, 0x00002223, 0x000023ff, 0x00002e05, 0x00002bf3, 0x000024a5, 0x00002124, 0x00002768, 0x00002a14, 0x0000219e, 0x00002739, 0x00002e3c, 0x00000001, 0xd00001f0 }, + FinalRegs = new uint[] { 0x000024b4, 0x0000285e, 0x000021de, 0x00002223, 0x000023ff, 0x00002e05, 0x00002bf3, 0x000024a5, 0x24b624b4, 0x00002768, 0x00002a14, 0x0000219e, 0x00002739, 0x00002e3c, 0x00000001, 0xd00001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf857, 0x19f6, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x000027f5, 0x0000285e, 0x000025f6, 0x00002e22, 0x00002224, 0x00002870, 0x00002ecc, 0x000024cf, 0x00002711, 0x0000241b, 0x00002ddf, 0x00002545, 0x000028ca, 0x000023c5, 0x00000001, 0x400001f0 }, + FinalRegs = new uint[] { 0x000027f5, 0xd224d024, 0x000025f6, 0x00002e22, 0x00002224, 0x00002870, 0x00002ecc, 0x000023d9, 0x00002711, 0x0000241b, 0x00002ddf, 0x00002545, 0x000028ca, 0x000023c5, 0x00000001, 0x400001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + // LDR (imm12) + new() + { + Instructions = new ushort[] { 0xf8d1, 0xc65e, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x000001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x2660265e, 0x00002000, 0x00000001, 0x000001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf8db, 0xd09b, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x800001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x9e209c20, 0x00000001, 0x800001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf8d2, 0x6fde, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x900001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x2fe02fde, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x900001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + new() + { + Instructions = new ushort[] { 0xf8dc, 0x3de5, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x500001f0 }, + FinalRegs = new uint[] { 0x00002000, 0x00002000, 0x00002000, 0xe82de62d, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00002000, 0x00000001, 0x500001f0 }, + MemoryDelta = Array.Empty<(ulong Address, ushort Value)>(), + }, + }; + } +} diff --git a/src/Ryujinx.Tests/Cpu/CpuTestThumb.cs b/src/Ryujinx.Tests/Cpu/CpuTestThumb.cs new file mode 100644 index 00000000..6111e53f --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/CpuTestThumb.cs @@ -0,0 +1,884 @@ +using ARMeilleure.State; +using NUnit.Framework; + +namespace Ryujinx.Tests.Cpu +{ + [Category("Thumb")] + public sealed class CpuTestThumb : CpuTest32 + { + private const int RndCnt = 2; + + public static uint RotateRight(uint value, int count) + { + return (value >> count) | (value << (32 - count)); + } + + [Test, Pairwise] + public void ShiftImm([Range(0u, 2u)] uint shiftType, [Range(1u, 0x1fu)] uint shiftImm, [Random(RndCnt)] uint w1, [Random(RndCnt)] uint w2) + { + uint opcode = 0x0000; // MOVS , , # + + uint rd = 1; + uint rm = 2; + opcode |= ((rd & 7) << 0) | ((rm & 7) << 3) | ((shiftImm & 0x1f) << 6) | ((shiftType & 3) << 11); + + SingleThumbOpcode((ushort)opcode, r1: w1, r2: w2, runUnicorn: false); + + switch (shiftType) + { + case 0: + Assert.That(GetContext().GetX(1), Is.EqualTo((w2 << (int)shiftImm) & 0xffffffffu)); + break; + case 1: + Assert.That(GetContext().GetX(1), Is.EqualTo((w2 >> (int)shiftImm) & 0xffffffffu)); + break; + case 2: + Assert.That(GetContext().GetX(1), Is.EqualTo(((int)w2 >> (int)shiftImm) & 0xffffffffu)); + break; + } + } + + [Test, Pairwise] + public void AddSubReg([Range(0u, 1u)] uint op, [Random(RndCnt)] uint w1, [Random(RndCnt)] uint w2) + { + uint opcode = 0x1800; // ADDS , , + + uint rd = 0; + uint rn = 1; + uint rm = 2; + opcode |= ((rd & 7) << 0) | ((rn & 7) << 3) | ((rm & 7) << 6) | ((op & 1) << 9); + + SingleThumbOpcode((ushort)opcode, r1: w1, r2: w2, runUnicorn: false); + + switch (op) + { + case 0: + Assert.That(GetContext().GetX(0), Is.EqualTo((w1 + w2) & 0xffffffffu)); + break; + case 1: + Assert.That(GetContext().GetX(0), Is.EqualTo((w1 - w2) & 0xffffffffu)); + break; + } + } + + [Test, Pairwise] + public void AddSubImm3([Range(0u, 1u)] uint op, [Range(0u, 7u)] uint imm, [Random(RndCnt)] uint w1) + { + uint opcode = 0x1c00; // ADDS , , # + + uint rd = 0; + uint rn = 1; + opcode |= ((rd & 7) << 0) | ((rn & 7) << 3) | ((imm & 7) << 6) | ((op & 1) << 9); + + SingleThumbOpcode((ushort)opcode, r1: w1, runUnicorn: false); + + switch (op) + { + case 0: + Assert.That(GetContext().GetX(0), Is.EqualTo((w1 + imm) & 0xffffffffu)); + break; + case 1: + Assert.That(GetContext().GetX(0), Is.EqualTo((w1 - imm) & 0xffffffffu)); + break; + } + } + + [Test, Pairwise] + public void AluImm8([Range(0u, 3u)] uint op, [Random(RndCnt)] uint imm, [Random(RndCnt)] uint w1) + { + imm &= 0xff; + + uint opcode = 0x2000; // MOVS , # + + uint rdn = 1; + opcode |= ((imm & 0xff) << 0) | ((rdn & 7) << 8) | ((op & 3) << 11); + + SingleThumbOpcode((ushort)opcode, r1: w1, runUnicorn: false); + + switch (op) + { + case 0: + Assert.That(GetContext().GetX(1), Is.EqualTo(imm)); + break; + case 1: + Assert.That(GetContext().GetX(1), Is.EqualTo(w1)); + cmpFlags: + { + uint result = w1 - imm; + uint overflow = (result ^ w1) & (w1 ^ imm); + Assert.That(GetContext().GetPstateFlag(PState.NFlag), Is.EqualTo((result >> 31) != 0)); + Assert.That(GetContext().GetPstateFlag(PState.ZFlag), Is.EqualTo(result == 0)); + Assert.That(GetContext().GetPstateFlag(PState.CFlag), Is.EqualTo(w1 >= imm)); + Assert.That(GetContext().GetPstateFlag(PState.VFlag), Is.EqualTo((overflow >> 31) != 0)); + } + break; + case 2: + Assert.That(GetContext().GetX(1), Is.EqualTo((w1 + imm) & 0xffffffffu)); + break; + case 3: + Assert.That(GetContext().GetX(1), Is.EqualTo((w1 - imm) & 0xffffffffu)); + goto cmpFlags; + } + } + + [Test, Pairwise] + public void AluRegLow([Range(0u, 0xfu)] uint op, [Random(RndCnt)] uint w1, [Random(RndCnt)] uint w2) + { + uint opcode = 0x4000; // ANDS , + + uint rd = 1; + uint rm = 2; + opcode |= ((rd & 7) << 0) | ((rm & 7) << 3) | ((op & 0xf) << 6); + + SingleThumbOpcode((ushort)opcode, r1: w1, r2: w2, runUnicorn: false); + + uint shift = w2 & 0xff; + switch (op) + { + case 0: + Assert.That(GetContext().GetX(1), Is.EqualTo(w1 & w2)); + break; + case 1: + Assert.That(GetContext().GetX(1), Is.EqualTo(w1 ^ w2)); + break; + case 2: + Assert.That(GetContext().GetX(1), Is.EqualTo(shift >= 32 ? 0 : w1 << (int)shift)); + break; + case 3: + Assert.That(GetContext().GetX(1), Is.EqualTo(shift >= 32 ? 0 : w1 >> (int)shift)); + break; + case 4: + Assert.That(GetContext().GetX(1), Is.EqualTo(shift >= 32 ? (uint)((int)w1 >> 31) : (uint)((int)w1 >> (int)shift))); + break; + case 5: + Assert.That(GetContext().GetX(1), Is.EqualTo(w1 + w2)); + break; + case 6: + Assert.That(GetContext().GetX(1), Is.EqualTo(w1 + ~w2)); + break; + case 7: + Assert.That(GetContext().GetX(1), Is.EqualTo(RotateRight(w1, (int)shift & 31))); + break; + case 8: + Assert.That(GetContext().GetX(1), Is.EqualTo(w1)); + { + uint result = w1 & w2; + Assert.That(GetContext().GetPstateFlag(PState.NFlag), Is.EqualTo((result >> 31) != 0)); + Assert.That(GetContext().GetPstateFlag(PState.ZFlag), Is.EqualTo(result == 0)); + } + break; + case 9: + Assert.That(GetContext().GetX(1), Is.EqualTo((uint)-w2)); + break; + case 10: + Assert.That(GetContext().GetX(1), Is.EqualTo(w1)); + { + uint result = w1 - w2; + uint overflow = (result ^ w1) & (w1 ^ w2); + Assert.That(GetContext().GetPstateFlag(PState.NFlag), Is.EqualTo((result >> 31) != 0)); + Assert.That(GetContext().GetPstateFlag(PState.ZFlag), Is.EqualTo(result == 0)); + Assert.That(GetContext().GetPstateFlag(PState.CFlag), Is.EqualTo(w1 >= w2)); + Assert.That(GetContext().GetPstateFlag(PState.VFlag), Is.EqualTo((overflow >> 31) != 0)); + } + break; + case 11: + Assert.That(GetContext().GetX(1), Is.EqualTo(w1)); + { + uint result = w1 + w2; + uint overflow = (result ^ w1) & ~(w1 ^ w2); + Assert.That(GetContext().GetPstateFlag(PState.NFlag), Is.EqualTo((result >> 31) != 0)); + Assert.That(GetContext().GetPstateFlag(PState.ZFlag), Is.EqualTo(result == 0)); + Assert.That(GetContext().GetPstateFlag(PState.CFlag), Is.EqualTo(result < w1)); + Assert.That(GetContext().GetPstateFlag(PState.VFlag), Is.EqualTo((overflow >> 31) != 0)); + } + break; + case 12: + Assert.That(GetContext().GetX(1), Is.EqualTo(w1 | w2)); + break; + case 13: + Assert.That(GetContext().GetX(1), Is.EqualTo(w1 * w2)); + break; + case 14: + Assert.That(GetContext().GetX(1), Is.EqualTo(w1 & ~w2)); + break; + case 15: + Assert.That(GetContext().GetX(1), Is.EqualTo(~w2)); + break; + } + } + + [Test, Pairwise] + public void AluRegHigh([Range(0u, 2u)] uint op, [Range(0u, 13u)] uint rd, [Range(0u, 13u)] uint rm, [Random(RndCnt)] uint w1, [Random(RndCnt)] uint w2) + { + if (rd == rm) + { + return; + } + + uint opcode = 0x4400; // ADDS , + + opcode |= ((rd & 7) << 0) | ((rm & 0xf) << 3) | ((rd & 8) << 4) | ((op & 3) << 8); + + ThumbOpcode((ushort)opcode); + ThumbOpcode(0x4770); // BX LR + + GetContext().SetX((int)rd, w1); + GetContext().SetX((int)rm, w2); + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + + switch (op) + { + case 0: + Assert.That(GetContext().GetX((int)rd), Is.EqualTo(w1 + w2)); + break; + case 1: + Assert.That(GetContext().GetX((int)rd), Is.EqualTo(w1)); + Assert.That(GetContext().GetX((int)rm), Is.EqualTo(w2)); + { + uint result = w1 - w2; + uint overflow = (result ^ w1) & (w1 ^ w2); + Assert.That(GetContext().GetPstateFlag(PState.NFlag), Is.EqualTo((result >> 31) != 0)); + Assert.That(GetContext().GetPstateFlag(PState.ZFlag), Is.EqualTo(result == 0)); + Assert.That(GetContext().GetPstateFlag(PState.CFlag), Is.EqualTo(w1 >= w2)); + Assert.That(GetContext().GetPstateFlag(PState.VFlag), Is.EqualTo((overflow >> 31) != 0)); + } + break; + case 2: + Assert.That(GetContext().GetX((int)rd), Is.EqualTo(w2)); + break; + } + } + + [Test] + public void SubSpTest() + { + ThumbOpcode(0xb0fd); // SUB SP, #0x1f4 + ThumbOpcode(0x4770); // BX LR + + GetContext().SetX(13, 0x40079d98); + GetContext().SetPstateFlag(PState.TFlag, true); + + ExecuteOpcodes(runUnicorn: false); + + Assert.That(GetContext().GetX(13), Is.EqualTo(0x40079ba4)); + } + + [Test] + public void TestRandomTestCases([ValueSource(nameof(RandomTestCases))] PrecomputedThumbTestCase test) + { + if (Size != 0x1000) + { + // TODO: Change it to depend on DataBaseAddress instead. + Assert.Ignore("This test currently only support 4KiB page size"); + } + + RunPrecomputedTestCase(test); + } + + public static readonly PrecomputedThumbTestCase[] RandomTestCases = + { + new() + { + Instructions = new ushort[] { 0x42ab, 0xabed, 0x43fc, 0x4360, 0x40c1, 0x40b5, 0x4353, 0x43ac, 0xba74, 0x4613, 0xb03a, 0xbf2a, 0xa5bd, 0x187b, 0x410a, 0x4405, 0x15b3, 0x1472, 0xb248, 0x43a6, 0x41af, 0xbaf4, 0x1a6d, 0x4684, 0xbf04, 0x4068, 0xb26e, 0x014b, 0x4064, 0xba15, 0x3d2d, 0xb22d, 0x4378, 0x2501, 0x4279, 0xb299, 0x1586, 0x42e7, 0x2f55, 0xba55, 0x1f6f, 0x443d, 0x4194, 0xbfcb, 0xbacd, 0xb2a0, 0x406b, 0x1b65, 0xbfd0, 0x1bf7, 0x41e7, 0xba76, 0x436f, 0x46f8, 0x4042, 0x1ffe, 0x447c, 0xba50, 0x341e, 0x42fd, 0x409c, 0xbf09, 0xb2cf, 0x1c99, 0x41f7, 0x41a6, 0x4278, 0x4040, 0x3747, 0x301a, 0x01cc, 0x4304, 0x1ca3, 0xab1d, 0x43dd, 0x41e3, 0x35ad, 0x43f5, 0xb091, 0x3214, 0x44f1, 0x41d7, 0xb244, 0xb2a2, 0x41e1, 0x3d7a, 0xbf24, 0xbac1, 0x0059, 0xba3b, 0xabe5, 0x4550, 0x0a9e, 0xbfe2, 0x034e, 0xb0b7, 0x0aec, 0x1ee6, 0x3276, 0x0866, 0xa763, 0x1e45, 0x4275, 0xad25, 0x416d, 0x0d6d, 0x0b50, 0xb200, 0x1eba, 0x4378, 0x0547, 0x43bb, 0xb2f2, 0xb2af, 0x1ea0, 0x25b0, 0x4287, 0x1d55, 0x2225, 0xbf46, 0xb276, 0xba57, 0x4253, 0x19e0, 0xbf32, 0x1b06, 0x40ab, 0xb2a4, 0x1eda, 0x1dfd, 0xaabd, 0x1b08, 0xba0e, 0x4014, 0xa079, 0x119b, 0x1d17, 0x41b1, 0x005a, 0xb2ba, 0x419a, 0x4361, 0x2d2a, 0xba36, 0x1b49, 0xbf0c, 0x423c, 0xbae6, 0x2bc0, 0x409c, 0xbfa0, 0xba02, 0x2496, 0xb279, 0xbace, 0xb2e0, 0xbf07, 0x3b17, 0x40a9, 0x3154, 0x41ab, 0x284e, 0xb03c, 0xbf06, 0x41ff, 0xba45, 0x4144, 0xab32, 0x4000, 0xb2b2, 0x3729, 0x46fb, 0xb042, 0x32d0, 0x0557, 0x3583, 0xb249, 0x43f5, 0x0a54, 0x416f, 0x1fc6, 0x4560, 0xb2f6, 0xa114, 0x1dd7, 0x40b4, 0x4304, 0xbfc2, 0x41f2, 0xb2e2, 0xb0d4, 0xbaca, 0xbf65, 0x4065, 0xa1e5, 0xb043, 0x4004, 0xb237, 0x41e9, 0xba0f, 0xaa8e, 0x421d, 0x1d12, 0x435e, 0x434b, 0x4356, 0xacaf, 0x4551, 0x1935, 0x1c1a, 0xba67, 0x2441, 0xbf9a, 0x41a0, 0xb2d8, 0x1c00, 0xad68, 0x4185, 0xb272, 0x43ba, 0x436c, 0xb08b, 0xb2f4, 0x4379, 0x1d0f, 0x46f8, 0x4379, 0x1b8f, 0x4260, 0x40d8, 0xbfa7, 0x0730, 0xb27d, 0xb242, 0x43d3, 0x46fd, 0xbf92, 0xb067, 0xaa15, 0x428c, 0xb2e1, 0x43e5, 0xb2f6, 0x1da1, 0x023e, 0x1a86, 0xba01, 0xb203, 0x13ae, 0xa6a6, 0x45d8, 0xba4c, 0xb228, 0x438c, 0xbae8, 0x4378, 0x3b55, 0xbf5c, 0xb036, 0xb261, 0x1bea, 0x41b8, 0x43f5, 0xba70, 0xa959, 0xb2a9, 0x41fd, 0x19ad, 0x41cd, 0x4171, 0xb046, 0xbf37, 0x431e, 0x4302, 0xb04f, 0x396f, 0x44f0, 0x36d3, 0x01b0, 0x18f5, 0x424b, 0xb27a, 0x43a9, 0xba49, 0x4357, 0x41e5, 0x42d4, 0x0f27, 0x4325, 0x1f9b, 0x447a, 0x46b8, 0xbf60, 0x43c5, 0xa78b, 0x46b2, 0xb2c4, 0x4322, 0xbfad, 0xbf90, 0x4275, 0x40c4, 0x1e91, 0xba0c, 0xbfd0, 0xafd3, 0xbfa0, 0xb2ff, 0xb2dc, 0x43a1, 0xb0db, 0xbf1e, 0x269d, 0xb21a, 0x405d, 0x1822, 0x3dc1, 0xb247, 0x119e, 0xb2b9, 0x4056, 0xaedd, 0xba59, 0x40ed, 0x43c0, 0xbf5f, 0xba35, 0xad1b, 0xb0db, 0x274a, 0x4042, 0x404c, 0xb0a6, 0x2983, 0x41b9, 0x4226, 0x455b, 0xbad3, 0xba5c, 0x42e3, 0xbf8f, 0x3b28, 0xadf6, 0x0321, 0x41c0, 0x223b, 0x40ca, 0x1ea5, 0x4256, 0x409a, 0x4339, 0xba7c, 0x43cf, 0x40bb, 0xb217, 0x427a, 0x289f, 0xb28b, 0xb250, 0xbf2a, 0xb084, 0x410c, 0x416b, 0x4079, 0xb2c9, 0x4149, 0x4143, 0x0c11, 0x461f, 0x18ed, 0x41e1, 0x4651, 0xba1c, 0x422b, 0x44fb, 0xb2b4, 0xbfde, 0x45e1, 0x4601, 0x4483, 0xa182, 0x3311, 0x40bd, 0x446a, 0x431c, 0x2f1e, 0xbac8, 0x02c3, 0x43d5, 0x415d, 0x42f7, 0x1cad, 0x1597, 0xb2cd, 0x1cbd, 0x42df, 0x069e, 0x0986, 0x435d, 0xbad5, 0xbf39, 0x1044, 0x417c, 0x3afb, 0x3be4, 0xb2b8, 0xb0b6, 0x401b, 0x1e4f, 0x4270, 0x4278, 0xacb6, 0xbad1, 0xbaee, 0x1920, 0xba01, 0x4367, 0xb24d, 0xb2db, 0x05da, 0xa2c6, 0x4627, 0xba2b, 0xbfa3, 0x42aa, 0x3637, 0xbafa, 0x4364, 0x43f3, 0xaaea, 0x40a5, 0x424f, 0x45a3, 0x44e9, 0x4477, 0xb09e, 0x42ea, 0x44b9, 0x4694, 0x4629, 0xba4b, 0x42c8, 0x412e, 0xbf9e, 0xba0a, 0x420a, 0xb2d0, 0xbae8, 0xbfb3, 0x40f8, 0x36d0, 0x40bd, 0x4395, 0x0d0f, 0xbafa, 0xb20b, 0x41c6, 0x40ea, 0xbf62, 0x429b, 0x4142, 0x43a6, 0x1f1b, 0xa9b4, 0x4455, 0xb0b1, 0xbae8, 0xb24c, 0x0396, 0x4360, 0xb26d, 0x0693, 0x1842, 0xba1e, 0xb29b, 0xba44, 0x1c45, 0x456f, 0x1a6a, 0x439c, 0xb24d, 0xac7d, 0x410b, 0x419f, 0xbf7e, 0x4057, 0x4377, 0x1ea6, 0x2f69, 0xbfc0, 0x1a75, 0xb2cf, 0x3759, 0x4187, 0x429b, 0x40d7, 0x18e7, 0x2eaa, 0x2bc6, 0xbf0d, 0x028c, 0x40f2, 0x421c, 0xbad1, 0xbf70, 0x41c5, 0xb012, 0x42fe, 0xb2ef, 0xbf96, 0xba10, 0x4103, 0x4434, 0x449a, 0x0b03, 0x40d5, 0x4221, 0x345a, 0x1491, 0xb2fc, 0x46e1, 0x456c, 0xb0f0, 0xb2ba, 0x4254, 0x415f, 0x4269, 0x434f, 0xb04c, 0x4316, 0xb049, 0xba18, 0x1988, 0x4194, 0x4412, 0xbfb3, 0x43f8, 0xba4f, 0xb218, 0x4033, 0x38a4, 0x3d99, 0xa6b2, 0xb076, 0xbfc0, 0x42c6, 0x40ad, 0x432a, 0x4226, 0x1ee9, 0x1aa5, 0xbf13, 0x3370, 0x2820, 0x1191, 0x432c, 0x40b8, 0x464e, 0x1e58, 0x462f, 0xb022, 0x40ba, 0x464f, 0x4083, 0x4683, 0x16c7, 0xbf43, 0x42a0, 0xba56, 0x2f3d, 0x417b, 0x43ff, 0xac7d, 0xb285, 0x4041, 0xba51, 0xbf31, 0x4041, 0xb281, 0xb2d8, 0x1a99, 0x4342, 0x402f, 0x4185, 0x40f3, 0xbfd0, 0x4274, 0x40a3, 0x4064, 0x39a4, 0xba04, 0x408e, 0x3250, 0x3c3d, 0xa2bb, 0xba1c, 0x1a4b, 0x40e5, 0x3ca6, 0xbf67, 0x4285, 0xa9f3, 0x46bc, 0x420d, 0xba05, 0xb009, 0x417c, 0xb27e, 0x1c48, 0x41c8, 0x42e3, 0x1e8c, 0x42b4, 0xba08, 0x3598, 0xb263, 0xb2f2, 0x1053, 0x40da, 0x37c7, 0x370d, 0x1853, 0x41e9, 0xb061, 0x1af7, 0x4015, 0x44aa, 0x1db0, 0x4165, 0xbf27, 0x3d1f, 0x42f4, 0x432f, 0x407c, 0x1457, 0x43c4, 0xb226, 0x40d3, 0x4317, 0x409d, 0x4024, 0xb062, 0xaeba, 0x3c71, 0x43a1, 0xada2, 0x4115, 0xba3f, 0x4163, 0x4386, 0xb2c9, 0x1b5a, 0xba22, 0xbf1e, 0xbaea, 0xbadc, 0x0ff5, 0x1102, 0x41a5, 0x0122, 0x43b3, 0x30c2, 0x43cb, 0x41c8, 0x0365, 0xaa50, 0xb200, 0x4347, 0x4049, 0x4101, 0x0ba9, 0x325b, 0x296c, 0x4364, 0x43d7, 0xbaf1, 0xbfb4, 0x436e, 0xb28f, 0x0163, 0x42d8, 0x4016, 0x33c6, 0x23f4, 0x40e6, 0x25d3, 0x40cf, 0x3a7c, 0x445c, 0x3781, 0x4083, 0xbf16, 0x46a5, 0xae3c, 0x43d1, 0xbf2f, 0x41d9, 0x41b5, 0xbac7, 0x4381, 0x434b, 0x099c, 0x2c64, 0x4202, 0x40c8, 0x4194, 0x4625, 0xbf19, 0x423b, 0x4098, 0x40f0, 0x437f, 0x2643, 0xbaee, 0x40a0, 0x00b2, 0xb06f, 0x42d2, 0x4047, 0xa2b2, 0xb284, 0x4008, 0xaeed, 0x46a9, 0x40d4, 0x42fe, 0x4084, 0x41d0, 0x4304, 0x0437, 0xbad6, 0x4247, 0x4068, 0x43db, 0xbf8e, 0x1fcf, 0x278a, 0xb202, 0x4304, 0x0bf0, 0x4389, 0x3f6d, 0xb266, 0xb243, 0x152d, 0x4630, 0xaee7, 0x0104, 0x40c3, 0x40b9, 0xa96c, 0xb2e0, 0xa0f9, 0xb273, 0x0d05, 0x402b, 0x408f, 0x0b31, 0xa305, 0xbf65, 0x4200, 0xb2e0, 0x43e8, 0x43a2, 0xb206, 0x436f, 0xba61, 0x4037, 0xba4c, 0xbad3, 0x46ed, 0x3501, 0x4321, 0x4137, 0x41c3, 0x17ef, 0x42b7, 0xba13, 0x4246, 0xbf58, 0xb210, 0xb04f, 0x40e1, 0x42e3, 0xa428, 0x404e, 0xb2d1, 0x4043, 0x3f7b, 0x1d55, 0x35c0, 0x1b98, 0x1f9b, 0x44db, 0xbf17, 0xb240, 0x1e91, 0xb0a7, 0x402a, 0x4045, 0x414b, 0x415c, 0x08b1, 0xba10, 0x188b, 0x4219, 0x32e9, 0x427a, 0x2242, 0x1f51, 0x43f8, 0x409c, 0xbaff, 0x1f4a, 0x418f, 0x4566, 0x4151, 0xbaca, 0x4083, 0x409f, 0x44e4, 0xbf70, 0x341c, 0xbf0e, 0x400f, 0x40b5, 0x466d, 0x01a2, 0x4493, 0x43e4, 0x40e4, 0xba5b, 0xb289, 0xbad8, 0x0d68, 0x424f, 0xb20e, 0x418b, 0xb2c3, 0xade0, 0x4066, 0xbf95, 0x2228, 0x4110, 0x1db8, 0xa1e2, 0x0589, 0x4203, 0x4371, 0xbf77, 0x3288, 0x42f8, 0x4341, 0x413e, 0xb027, 0xbaf9, 0xbf70, 0x157d, 0x1d0d, 0xa3c2, 0x1e40, 0x448c, 0x405b, 0x14c2, 0x433f, 0x43b6, 0xba18, 0x1689, 0xbf82, 0x40aa, 0x4048, 0x320d, 0x4620, 0x42c9, 0x1670, 0x409f, 0xba26, 0xbad0, 0x43a5, 0x31b7, 0xbac8, 0x4405, 0xa60d, 0x1e58, 0x2989, 0xb22b, 0x3c62, 0x409f, 0x4295, 0xb287, 0xad38, 0xb025, 0x43eb, 0xbfbf, 0x0126, 0x4253, 0x42df, 0xa723, 0xa96e, 0x42f7, 0x3748, 0xac86, 0x408a, 0xae5c, 0x3778, 0xb2e5, 0xbfc1, 0x418b, 0xbac3, 0xb0fb, 0x45a6, 0x4293, 0x426b, 0xb07e, 0x43c3, 0x34f3, 0x08bc, 0x02c7, 0xbfbc, 0x42af, 0x421d, 0x2675, 0x428b, 0xba07, 0x4107, 0xb07a, 0x433d, 0x438e, 0x1195, 0x403d, 0x4545, 0xbad3, 0x1b5e, 0xbf8f, 0xb289, 0x23df, 0x402d, 0x413b, 0x2af8, 0x003b, 0x46ad, 0xbf9a, 0x4271, 0x433d, 0x43c0, 0x2f27, 0xa5e3, 0x179d, 0x1ced, 0x445d, 0x43c0, 0x3eb2, 0x40e8, 0x39ba, 0x4088, 0x4117, 0x3137, 0x189b, 0xb024, 0xa52f, 0x41b4, 0x1f27, 0x439d, 0x42e2, 0x4265, 0xbfe0, 0x252f, 0xbf7f, 0x3bd7, 0x40dd, 0x2cc1, 0x1c04, 0x41a6, 0x1403, 0x408c, 0x46bd, 0x2a99, 0x4230, 0x42f9, 0x04e8, 0xba5d, 0x284d, 0x43c7, 0x4332, 0x3921, 0x443c, 0xbf08, 0x4390, 0x1edf, 0xa980, 0x40a7, 0xb250, 0xb074, 0x41fd, 0xa12a, 0x1edd, 0xbad8, 0xb231, 0xbfd7, 0x1951, 0x4337, 0x2393, 0x4491, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xfb8e51a4, 0xf9f21a25, 0x2a13c422, 0x57b1ba81, 0xf344e297, 0x1706c70a, 0xb7e92cf4, 0x512917e4, 0x5a86f7df, 0x755562e5, 0xae714612, 0x2c5f02dc, 0x0dcdaeee, 0x75c86709, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0x00000000, 0xffffff4b, 0xffffff4e, 0x00000093, 0xffffffff, 0xfffffffd, 0xffffff4e, 0x00000000, 0x00000000, 0x03f9e62a, 0x00000082, 0x000007de, 0xffff8cdd, 0x000042ac, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x18f0, 0x405d, 0x4152, 0x40a4, 0x426b, 0xb201, 0x1a63, 0x4623, 0x41a9, 0x1a48, 0x4165, 0xbf7d, 0xba5c, 0xbf00, 0xb06d, 0x43b7, 0x44ca, 0x41b2, 0x2b08, 0xb2c5, 0xb224, 0xb224, 0xb217, 0x2985, 0xba24, 0xb080, 0x43e8, 0x1d56, 0x45f2, 0x4226, 0x3d7c, 0x41bd, 0xbf9f, 0x4315, 0xbafe, 0x400f, 0x463e, 0x22db, 0x4143, 0x42c2, 0x43e0, 0x43e7, 0x40c5, 0xb221, 0xbfc2, 0xb0e4, 0xba4a, 0xbf90, 0x437a, 0x41e2, 0x403c, 0x2581, 0xa403, 0xa17c, 0x4322, 0x1bed, 0x01f8, 0xbf92, 0x3c28, 0x1ed9, 0x0c6a, 0xb219, 0xbf44, 0x416f, 0x18b5, 0xb2ce, 0x405e, 0x31f9, 0x1afe, 0xb068, 0xa488, 0xbaf0, 0x429e, 0xb265, 0x4154, 0x435c, 0x3f65, 0x4132, 0x400f, 0xbfcb, 0xb2b4, 0x4302, 0xa661, 0x429b, 0xba09, 0xb0bd, 0x1c5d, 0x4396, 0xb205, 0x4210, 0x4184, 0x4114, 0x30e9, 0x4694, 0x4281, 0xba3b, 0xb02c, 0x3c7c, 0xb068, 0x1f5f, 0xbad2, 0x2fc9, 0xb2fa, 0x422c, 0x40d1, 0x427a, 0xb00b, 0x1bd1, 0x1959, 0xbf94, 0x35f7, 0x415f, 0x4230, 0x4491, 0xbada, 0x4390, 0xa1d1, 0x4201, 0x4109, 0x3b9a, 0x4279, 0x4372, 0xbff0, 0xb2b4, 0x1c29, 0x0bba, 0x42ff, 0x0b07, 0x431a, 0x35b1, 0x2674, 0x1d4b, 0x43e4, 0xbfdc, 0x4385, 0x00d9, 0x33f4, 0x4010, 0xab8a, 0x42e7, 0xb093, 0xba39, 0x4140, 0x2120, 0xb2e8, 0x2254, 0x4354, 0x430e, 0xba16, 0x441b, 0x1b54, 0xaf2b, 0x4144, 0x424f, 0x3e6a, 0x0c2f, 0x4330, 0x44cc, 0x42a9, 0x433f, 0xbf93, 0x4367, 0x3de9, 0x4203, 0x4120, 0xa589, 0x1a1d, 0xaa95, 0x4068, 0x02ff, 0x4071, 0x44da, 0x181f, 0xbf8a, 0x2c8c, 0x183e, 0x40fd, 0x3711, 0xb2c1, 0x40d1, 0x0e30, 0x42e0, 0xba5f, 0xb00f, 0x0e5e, 0xb2b0, 0x419e, 0x42df, 0x4240, 0xa9a1, 0xaeba, 0x4319, 0x45f1, 0xbae7, 0x4390, 0x43ad, 0x40a0, 0x1e6e, 0x0c84, 0x2ddb, 0xbf45, 0xbfb0, 0x4405, 0x1ca9, 0x39cb, 0xb2e8, 0x0a6f, 0xba79, 0x4044, 0x4491, 0x3a70, 0xa45d, 0x425d, 0x3a30, 0x4107, 0xa248, 0x4055, 0x40f8, 0x43fb, 0x4078, 0x351f, 0x41a3, 0x439f, 0xba6a, 0x426a, 0x3f2f, 0xbf5d, 0x40ee, 0x43b9, 0x1b4a, 0xa56a, 0x2fec, 0xba54, 0x4613, 0x46c5, 0x4237, 0x3dd2, 0xb037, 0x3e1a, 0x04e2, 0x415d, 0x426c, 0xb2ed, 0x42c3, 0x416f, 0x1d8a, 0x42cd, 0x42c8, 0x422a, 0xbf7f, 0xa9b0, 0x4303, 0x43ea, 0x359a, 0x4058, 0x418f, 0x436e, 0xb289, 0x2731, 0x432b, 0x40c4, 0xb298, 0x401e, 0x011e, 0x4132, 0x43b6, 0x4148, 0x41a9, 0x413b, 0x4191, 0x1cb7, 0x0612, 0x4333, 0x3dfe, 0x4264, 0x4261, 0xaabe, 0xba60, 0x40d6, 0xbfe8, 0xa1fc, 0x025e, 0x4249, 0x33b2, 0x2d28, 0x428c, 0xb22d, 0xbae2, 0x449c, 0x40ac, 0x41b2, 0x193a, 0x435b, 0x4155, 0x4060, 0x42b8, 0x4363, 0x18d6, 0x4162, 0x145b, 0x439a, 0xbf2b, 0xba23, 0x41fa, 0x311d, 0xb2bf, 0x3045, 0xba6f, 0xbf06, 0x213a, 0x4565, 0xb0af, 0xb2cf, 0xa9db, 0x41b9, 0x19e3, 0x427d, 0xa296, 0x440c, 0x4363, 0x411f, 0x425b, 0x421a, 0x4207, 0x1840, 0x435e, 0x4285, 0xba48, 0xb2de, 0x420c, 0xba22, 0xbf5d, 0x41d3, 0x1db2, 0x400a, 0xb2b3, 0x42f1, 0x4014, 0x42a5, 0xae6b, 0x37bf, 0xa4b4, 0x437b, 0x4089, 0xbf23, 0x41ca, 0x43c7, 0x45b4, 0xb2d0, 0x418f, 0x4086, 0x03a8, 0x401c, 0x42b0, 0xb270, 0x41df, 0x0b28, 0x428e, 0x1cd2, 0x4666, 0x426a, 0x275d, 0x4189, 0x42a3, 0x0223, 0x40a6, 0xb041, 0x16a8, 0x2e46, 0xba19, 0xb2e6, 0x1b94, 0xbfc1, 0x40cd, 0x1074, 0xb29a, 0x411c, 0x41e6, 0xb052, 0x18f6, 0x4356, 0x43fc, 0x19b9, 0x4045, 0x1a37, 0x4582, 0x43ba, 0x462b, 0x2e1b, 0x4052, 0x0a1a, 0x43ae, 0x31a1, 0x41f2, 0x42dd, 0x17ea, 0x43ee, 0x42f3, 0x2cbf, 0xbf66, 0xb023, 0x40f6, 0x407e, 0x4499, 0x4234, 0x44f4, 0x404a, 0xb2ed, 0x40d4, 0xbaea, 0x0ea6, 0x439a, 0xae0e, 0x1334, 0x4558, 0x336a, 0xb03b, 0x0b94, 0x41cf, 0x4256, 0x4188, 0x31f8, 0x404b, 0x410e, 0xbf69, 0x2a6c, 0xbacb, 0x1e69, 0x011a, 0xb23e, 0x44d0, 0x3916, 0xb295, 0xa0d9, 0xbf84, 0x3b79, 0xa6c3, 0xb089, 0xbfb0, 0x4303, 0x331b, 0xb229, 0x4341, 0x0bbb, 0x1ad9, 0x4047, 0x1ae3, 0x431c, 0xa448, 0x4179, 0x432c, 0xb281, 0x348d, 0x4170, 0xbf47, 0x43e2, 0x41a1, 0x419f, 0xa0fb, 0xb2d7, 0xb2d1, 0x412f, 0x467f, 0xba40, 0xb2e0, 0xbad8, 0x423b, 0x4628, 0x43ea, 0x41b9, 0x41eb, 0xb2b3, 0xbf28, 0x4350, 0xa460, 0x433f, 0x4032, 0x4390, 0xb287, 0x1865, 0x4668, 0x448c, 0x41b5, 0xbf75, 0x41ce, 0xbacd, 0x18ba, 0x1c8d, 0x41fc, 0x42eb, 0xbf18, 0x401d, 0x3d5d, 0x4122, 0xb01a, 0x40c4, 0x4321, 0xa7b0, 0xba1f, 0x4035, 0x0f4e, 0x2829, 0x031f, 0x464a, 0x404f, 0xbf70, 0x4093, 0x4321, 0x42ad, 0x4230, 0x4124, 0x4218, 0x00a8, 0xa451, 0xb2b3, 0xbf7c, 0xb26f, 0x44d3, 0x423b, 0xbfd0, 0x04af, 0xb28f, 0x4335, 0x39ab, 0x42c8, 0xbf24, 0xbf70, 0x3665, 0xb24e, 0xb2d8, 0xa20d, 0x10a4, 0x407e, 0xb0c1, 0xbfc5, 0xb281, 0x400e, 0x4352, 0x46c8, 0xbfb8, 0x4074, 0x4410, 0x4249, 0x4335, 0x248a, 0x43aa, 0x4074, 0x4171, 0x43fd, 0x4060, 0x1fe9, 0xbfae, 0x4477, 0x2eb5, 0x410a, 0x187d, 0x41aa, 0x4137, 0x434c, 0x4353, 0x1ff7, 0xb215, 0x3c25, 0x183f, 0xb2ad, 0xbf4b, 0xb2c7, 0xb00b, 0x4339, 0x417b, 0x44f3, 0xaa2f, 0x4304, 0x43e5, 0x00a0, 0xba0a, 0x41b8, 0x4250, 0xbf22, 0x45bd, 0x41f0, 0xbaf1, 0x419a, 0x1d5c, 0x2eae, 0x42e7, 0x43e3, 0x4680, 0x433b, 0xb290, 0x26f8, 0x4398, 0xbff0, 0x41e6, 0x230e, 0x4173, 0xba5c, 0x077b, 0x4202, 0x40ff, 0xb2f9, 0x4137, 0xba2e, 0xbf90, 0xb08f, 0xb052, 0x42fe, 0xbf9a, 0xb046, 0xb023, 0x4348, 0x286e, 0x1c5b, 0x40af, 0x1c83, 0x41ae, 0xb2d7, 0x4198, 0xbafd, 0x42fd, 0x43ae, 0x3637, 0x30a2, 0x42e9, 0x34b2, 0x409b, 0x1fe7, 0x07b7, 0x19ea, 0xbf46, 0x2813, 0x1bb9, 0x42d9, 0x433b, 0xa211, 0xb24a, 0x24a3, 0xa199, 0x1fde, 0xb2c6, 0x4337, 0xbfce, 0x2ee1, 0x3710, 0xb20a, 0x41d4, 0x4026, 0x4361, 0x0eaf, 0x2d39, 0x1542, 0x04c4, 0x4361, 0x419b, 0x40e2, 0x417d, 0x4060, 0x1f09, 0x3473, 0x46ab, 0x430c, 0x400c, 0xb2d7, 0x4334, 0x4054, 0x0f0c, 0x188e, 0xbf92, 0xbaed, 0xb299, 0x4381, 0x0ac1, 0x0c56, 0x4267, 0x401f, 0x33e1, 0x4304, 0x3db4, 0xb0a2, 0x4635, 0x41f5, 0xbf92, 0x3fad, 0x4115, 0x1cf4, 0xb2bb, 0x4229, 0x437c, 0x4097, 0x412e, 0xa0b9, 0x4356, 0xb2ca, 0x43b0, 0x42f0, 0xba39, 0x4348, 0x4263, 0x4335, 0x42dc, 0x44f0, 0x42c6, 0xaa4f, 0x42bc, 0x43ed, 0xbf87, 0xb03d, 0x41cc, 0x1a30, 0x415e, 0xb257, 0x0f62, 0x4469, 0x4051, 0xa912, 0x4303, 0x4214, 0xb07d, 0x4067, 0xb264, 0x1dbe, 0xbf97, 0x41b8, 0x415c, 0x42d6, 0x414d, 0x13ac, 0x401c, 0x40ed, 0x25d9, 0x1883, 0x41e9, 0x410b, 0x1a38, 0xbfaa, 0x19e7, 0x19f6, 0x19db, 0x4173, 0x41a5, 0x17bc, 0x412d, 0x44d9, 0x4010, 0x1a2b, 0xbaf4, 0x393a, 0xba48, 0xb22d, 0xb0f5, 0xbaf2, 0xa232, 0xba3e, 0x41ce, 0x22dd, 0xbf8c, 0xb21b, 0x1ecd, 0x43a2, 0xba6b, 0x4044, 0x401e, 0x404b, 0x1854, 0xaafa, 0x4120, 0xa640, 0x43f7, 0x40c6, 0xb037, 0xa95e, 0xb2c5, 0xa3a2, 0x41f0, 0xb0e9, 0x4250, 0x4233, 0x438a, 0x401c, 0x46c5, 0x42ca, 0x4366, 0xbfd3, 0xba2b, 0x4029, 0x4060, 0x400f, 0x36eb, 0x404d, 0x400b, 0x171b, 0xb0c3, 0x4027, 0xb2eb, 0x0c68, 0xb04c, 0xa245, 0xb252, 0xb252, 0xb2e1, 0x414a, 0xbf0d, 0x426e, 0x1efb, 0xb2ba, 0x1865, 0xbafc, 0xb005, 0x41da, 0x40ce, 0x3cbd, 0x043e, 0x404c, 0x3d77, 0x4685, 0x1b3d, 0x2daa, 0xa0d8, 0xb2e3, 0x43b3, 0x4181, 0x2bba, 0x4316, 0x41dd, 0x4197, 0xbfc1, 0x0e0d, 0x430f, 0x11cd, 0x3a11, 0x413a, 0xb2b7, 0x4013, 0x409d, 0xb209, 0x281f, 0xba08, 0x41f6, 0xb024, 0x428b, 0xbafb, 0x42eb, 0x40c5, 0x11a0, 0x1876, 0xb009, 0x4004, 0xb224, 0x4395, 0xb04c, 0x3b7a, 0xbf01, 0x439b, 0xb247, 0x4162, 0x2a56, 0x1a27, 0x1d9a, 0x4065, 0xbf11, 0x05ff, 0xb25a, 0x1c63, 0xba4c, 0x3bdb, 0x40da, 0x33b3, 0x36fc, 0xbf7e, 0x41c5, 0xb0e1, 0x408f, 0x43a6, 0x187d, 0x4080, 0x009a, 0x41a1, 0x29d0, 0x41ad, 0x1c45, 0x37ad, 0x3de5, 0x1d62, 0x09f7, 0x402d, 0x43e5, 0xba78, 0xb221, 0xbad6, 0x1e4b, 0x289e, 0x43f0, 0x1bee, 0xb2dd, 0x43cc, 0xbfc2, 0x1d46, 0x40c2, 0xaaf5, 0x171e, 0xbfdf, 0x419c, 0xb2e8, 0xb20d, 0xbad1, 0xbadf, 0x4581, 0x27ba, 0x406b, 0x1a29, 0x436a, 0x466d, 0x121a, 0xb2e4, 0x4220, 0xba0f, 0x19d6, 0x1950, 0xb2ff, 0xbfb0, 0x1a2c, 0xb215, 0x4162, 0xbfcd, 0x05c0, 0x42cc, 0x40f1, 0xb2da, 0x05a2, 0x2929, 0xb0c1, 0xbf14, 0x4381, 0xbad8, 0x4480, 0xb0b6, 0x192e, 0x1f16, 0xba09, 0xbfc0, 0x4313, 0xb279, 0x40e9, 0x41af, 0x4233, 0x45e4, 0x4238, 0xb248, 0xb2a8, 0xb0f9, 0x3043, 0x4107, 0x070c, 0xba6a, 0x25de, 0x4212, 0x19d1, 0x41a4, 0xa17f, 0xbf36, 0x41fe, 0x08d3, 0x405f, 0xbff0, 0x41b2, 0x43e3, 0x4182, 0x08f4, 0x4293, 0x4267, 0x4030, 0x1822, 0xb2d6, 0x0575, 0x2c57, 0xb250, 0x166f, 0xaa81, 0x1c30, 0x4205, 0xab93, 0xbf60, 0xac46, 0x4328, 0xbf79, 0xb201, 0x33c5, 0x2c7f, 0x410c, 0x424e, 0x2676, 0xb0ce, 0x1f23, 0xbaf2, 0xb043, 0x392d, 0xb019, 0x04fb, 0x4227, 0xac6a, 0x27c0, 0x42a5, 0xba24, 0xbaf8, 0x4566, 0xb04f, 0x4299, 0x1cc3, 0x06e8, 0xb249, 0xbf00, 0xb0ba, 0x1be0, 0xbf96, 0x1251, 0xb2da, 0x3640, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x32016fcd, 0xf25a55e0, 0xc71b8a9f, 0x506131e5, 0xb5f08c7b, 0x8bec972b, 0x7ac97655, 0x7a33d75d, 0x0edf0fa3, 0x7cb7ebe2, 0x7905d5a9, 0x340efd29, 0x6029b34a, 0xfe1edb24, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0xef04ff40, 0x00000013, 0x00000003, 0xffffc003, 0xef050000, 0x08000000, 0x00000076, 0x000000c0, 0xe86c0001, 0x61d69c09, 0x29ccbeb4, 0xffffcd3f, 0x63b7d8f7, 0x0000049b, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x1e27, 0x1948, 0x1be1, 0x43de, 0x0cb5, 0x25a1, 0x4599, 0x0a02, 0xb22d, 0xb224, 0xba71, 0x420e, 0x1e8e, 0xb09a, 0xb2db, 0x40d0, 0xbf5f, 0x3342, 0x1e2b, 0x42b0, 0x43a6, 0xb2ab, 0x43dc, 0xaad3, 0x186c, 0x1b0b, 0xa519, 0xa22e, 0x2d3c, 0x1bde, 0x138c, 0x42ff, 0x4225, 0xba3f, 0xa44f, 0x4009, 0x2ad5, 0x417b, 0xb262, 0x43db, 0x41c3, 0x436b, 0xba2c, 0x40c9, 0x42ee, 0xba09, 0xbf75, 0x408d, 0x2dc2, 0xb27b, 0xb09e, 0x4468, 0x40da, 0x334e, 0x42f9, 0x40c7, 0x414b, 0x42b9, 0x4476, 0x40b3, 0x41b8, 0x4267, 0x41ac, 0xb25c, 0x4095, 0xbfcc, 0xb2e8, 0xb249, 0x1942, 0x4156, 0x1fae, 0xb27f, 0xb2ca, 0xbf60, 0x14ca, 0x408b, 0x12db, 0xb202, 0x40cf, 0x4037, 0xb2a7, 0x43f7, 0x41c0, 0xbf8a, 0x4340, 0xba72, 0x0994, 0x1910, 0x4442, 0xb073, 0x4351, 0x41fe, 0xbac0, 0x4415, 0x1f23, 0xb0ba, 0x3850, 0x4334, 0xbf75, 0x40bb, 0x1e9f, 0x40f5, 0x415c, 0xb046, 0x0819, 0x4222, 0xaae3, 0x3564, 0xb2ca, 0xba1d, 0x0578, 0xb26f, 0x4392, 0x127c, 0xa13e, 0x4307, 0xbfc5, 0x38f9, 0x40ae, 0x4013, 0xb24a, 0x422f, 0x436e, 0xb23a, 0x403c, 0x1d4e, 0xad67, 0xb28e, 0xbaf9, 0xba44, 0xb25d, 0x43be, 0x1ba1, 0x402e, 0x43c7, 0xba0e, 0xba5e, 0xbfad, 0x43ec, 0x42c4, 0x41f0, 0x1e15, 0x1ada, 0x42e2, 0xa13f, 0x086d, 0x4427, 0x4249, 0x416e, 0xb29c, 0x4388, 0x42c6, 0x42a9, 0x43e5, 0x41f1, 0x4024, 0x44fb, 0x40ba, 0xba65, 0x4143, 0x2a73, 0x21c3, 0x401d, 0x0307, 0xbf52, 0x2b55, 0x4412, 0xaf31, 0xbf99, 0x4374, 0x41c6, 0x4194, 0x187d, 0x03ca, 0x42a7, 0xbaca, 0x1c5c, 0x44a9, 0xba03, 0x1951, 0xaa90, 0xba3d, 0x0779, 0x1c92, 0xbf64, 0x1f8a, 0x3e68, 0xb2e4, 0xb078, 0x0aac, 0x1c35, 0x4105, 0x1fd2, 0x4260, 0x4119, 0x3a45, 0x43e5, 0x42e8, 0xa547, 0xb277, 0x3210, 0x2663, 0x4271, 0xb228, 0x4242, 0x4671, 0x40b7, 0x4328, 0xba4a, 0xbf22, 0xba26, 0x3a73, 0xad7d, 0x1ef1, 0x4114, 0xbf00, 0x420a, 0x4287, 0x40c6, 0xa529, 0x1c0e, 0x43c5, 0x0179, 0x305b, 0x4323, 0x1e26, 0x4261, 0x0a63, 0x45ae, 0x404d, 0x2fd0, 0x4341, 0xb0fb, 0xbfd5, 0x2fc4, 0x1f1e, 0x42d8, 0xbac4, 0xbae0, 0xb2ea, 0x281e, 0x17d1, 0x18fe, 0x4243, 0x4265, 0xb278, 0x4193, 0xaccc, 0x41e2, 0x4129, 0x4111, 0x41e7, 0xb02b, 0x42b1, 0xb252, 0x405e, 0x2ea6, 0x4355, 0xb29d, 0x408d, 0x4272, 0xbfbe, 0x4301, 0x425c, 0x43ef, 0x1be2, 0x1c16, 0x2a79, 0x3f7d, 0x446c, 0x1c40, 0x1890, 0xb20f, 0x19bc, 0x3f0f, 0x4109, 0xba4a, 0x1f0f, 0xb2b4, 0xba7e, 0x418a, 0x43e7, 0x094e, 0x4367, 0xaaf4, 0xbade, 0xbac4, 0x4082, 0xbfa4, 0x3bab, 0x4172, 0xb27e, 0x1bb3, 0x16c0, 0xb217, 0x408d, 0xbad1, 0xb202, 0x0c8a, 0x41d6, 0x432f, 0x1f7a, 0x1ab8, 0x43aa, 0xbae5, 0xbf8a, 0x4342, 0x0d85, 0x2184, 0xb2d6, 0x24ec, 0x4281, 0x4393, 0xbfba, 0x4542, 0x4368, 0xb05f, 0xb22b, 0xa6d1, 0x2917, 0x4071, 0x427a, 0xb2d9, 0x0c5b, 0xb0e2, 0xba75, 0x410d, 0x3696, 0x4078, 0x4203, 0xb2da, 0xb225, 0x4073, 0x408e, 0x43ec, 0x4335, 0x10a4, 0x33f6, 0x402e, 0x4264, 0xb240, 0xbfcb, 0x4314, 0x1b15, 0x41d7, 0xb0f6, 0x0643, 0xba6d, 0x4073, 0xad70, 0xbad7, 0x4646, 0x4275, 0xae3f, 0x46d5, 0xbf35, 0x4390, 0x0321, 0x06c6, 0x4219, 0x02fe, 0x4243, 0x41dd, 0x1e18, 0xb226, 0xbf67, 0x432d, 0x15c5, 0xb273, 0x4043, 0x1fb3, 0xb28e, 0x1df2, 0x42f4, 0x187f, 0xbfc2, 0xba73, 0x4356, 0x3703, 0xbfb2, 0x423d, 0x2afd, 0x412e, 0x42ff, 0xba54, 0xba15, 0xba18, 0x186a, 0xbf70, 0xba3e, 0x43bb, 0x0d94, 0x0940, 0xb015, 0x2f56, 0x4196, 0x4227, 0x437d, 0x40da, 0xb261, 0x4312, 0x4061, 0xbf32, 0x0d8e, 0x4203, 0x4308, 0xbac4, 0x437a, 0x4074, 0xbf5d, 0x427b, 0xb2c7, 0x43e4, 0x39b8, 0xb272, 0x42d4, 0x4183, 0x423e, 0x44ba, 0x428a, 0x2b49, 0x463f, 0x410a, 0x0679, 0x4194, 0xb0dd, 0x41d8, 0x43c4, 0x0a84, 0xbfd7, 0x3b2c, 0xb285, 0x41db, 0x40e2, 0x0e78, 0x43df, 0x4212, 0xbf00, 0x4072, 0x433f, 0xb26b, 0x4389, 0x431a, 0x1890, 0x4264, 0x1f58, 0xbf8a, 0x46d8, 0xa682, 0x1b9e, 0x41d6, 0xbfa2, 0x438e, 0x42f7, 0x46b9, 0x416d, 0xba21, 0xb015, 0x428c, 0x1135, 0x4221, 0x42eb, 0x2cbd, 0x40a3, 0xb033, 0xb2de, 0x4562, 0x43c1, 0x433d, 0x406a, 0xb273, 0x0ddf, 0x104a, 0x440a, 0x2b0d, 0x40ff, 0x40af, 0x43d5, 0xbf42, 0xbacd, 0x143b, 0x43f2, 0x4267, 0xbf7f, 0xb009, 0x1925, 0xb054, 0x1c76, 0x0fb2, 0xb2bd, 0x42cd, 0x43f9, 0x40b1, 0x42af, 0x40df, 0x42fb, 0x421a, 0x4259, 0x311c, 0x4057, 0xba52, 0xbf9f, 0x1f33, 0xba70, 0x44c3, 0xbae6, 0x3643, 0xbaf1, 0x4242, 0x00ff, 0x43ba, 0xb01b, 0x40c8, 0x4551, 0xb26b, 0x14dd, 0x43cd, 0xbfa8, 0xb29f, 0x31ff, 0xb2cd, 0xb2b9, 0x43ce, 0xa0da, 0x46e8, 0x43cd, 0x42a5, 0x4346, 0x431b, 0xba29, 0xb229, 0xbf44, 0x1ed8, 0x42a0, 0x42ed, 0xb067, 0x4217, 0x418a, 0xb020, 0x0ba2, 0xb25e, 0x1498, 0x238f, 0x1f2c, 0x1c97, 0xbf53, 0x41b2, 0x19de, 0x41ae, 0xb050, 0xbf25, 0x4139, 0x41dd, 0x4378, 0xa7a1, 0x1a9e, 0x0941, 0xb22f, 0x2449, 0x4048, 0xba34, 0xb2dd, 0xb2b3, 0xbfa7, 0x438d, 0xba40, 0x03a0, 0x0a6d, 0x11d7, 0x2d4f, 0xb2b8, 0x43c7, 0xb010, 0xb04e, 0x4335, 0xb233, 0x0886, 0x184c, 0x45a9, 0x42a7, 0x42cf, 0x1a82, 0x0bf2, 0x043d, 0x40e7, 0x428b, 0xbf3f, 0x3742, 0x1ac4, 0x4112, 0xbfd0, 0x41db, 0x4128, 0xab3c, 0xbaee, 0x4108, 0xba1b, 0x4298, 0x296b, 0x405a, 0xacd9, 0x4439, 0x392f, 0xbad4, 0x46d5, 0x4349, 0xbfa5, 0x18bd, 0x15c3, 0x4124, 0x1e91, 0xba64, 0xb26b, 0x45a9, 0x1e72, 0xb2e1, 0xa04c, 0x0fa8, 0x418c, 0xabfa, 0xaafd, 0x420f, 0x243b, 0xb0d6, 0xbfa0, 0x4485, 0x4360, 0x41f9, 0x418e, 0x41b2, 0xbf7c, 0xb22a, 0x421e, 0xaa51, 0x40cd, 0x41b0, 0xbfbb, 0x1e7f, 0x4224, 0x43be, 0x420a, 0xbfe1, 0x46d3, 0x4364, 0x427e, 0x404a, 0x422b, 0x3a5f, 0x4280, 0xba77, 0xb0ee, 0x41e9, 0xbfca, 0x4230, 0x4264, 0x2b53, 0xba07, 0x4136, 0xba67, 0xbfd7, 0xb0d5, 0x059a, 0x406a, 0x4216, 0x43d9, 0x3325, 0xa91c, 0xb048, 0x415c, 0xb20e, 0xb2c7, 0xba6f, 0x18d9, 0xba46, 0x1abb, 0x433a, 0x41d0, 0xbf55, 0x4300, 0x4030, 0x412b, 0x37b3, 0xbfd0, 0xa03a, 0x40c0, 0x05ba, 0xb26d, 0x437e, 0x1049, 0x43c6, 0xb00f, 0x44d0, 0x441b, 0x31f0, 0x40a5, 0x199e, 0x40ec, 0xbfa5, 0x1983, 0x4377, 0x40de, 0x2838, 0x1b98, 0x1385, 0x409b, 0x417a, 0x460c, 0x436d, 0x3510, 0xba26, 0x3883, 0x4110, 0x4495, 0x409e, 0xbfae, 0xb297, 0x4678, 0x143a, 0x40a3, 0xb221, 0x333b, 0x1f02, 0xb031, 0x1d7a, 0x4061, 0x4138, 0x41dd, 0xb2dc, 0xb2ed, 0xba39, 0xbf28, 0xb2e8, 0x3fbf, 0x411c, 0x3271, 0x14a9, 0x414f, 0xa624, 0x1d2c, 0xbfa0, 0x4195, 0x4361, 0xb234, 0xb052, 0x4097, 0x43be, 0x4083, 0xba41, 0xbf34, 0xad4e, 0x4109, 0xb256, 0xb2e5, 0x39b3, 0x43ae, 0xb2e3, 0x2479, 0x40ba, 0xb229, 0x40d2, 0xbf7f, 0x3365, 0x41ba, 0xba78, 0x1b0c, 0x40db, 0x4358, 0xa8af, 0x42d7, 0x4573, 0xb29d, 0x15fb, 0xb00a, 0xb2ee, 0xb25c, 0xbfc1, 0x428b, 0x0d95, 0x4121, 0x43d1, 0x424c, 0x2cb8, 0xbf4c, 0xa326, 0x42b0, 0x14f3, 0x43eb, 0x379b, 0x41b8, 0xba3d, 0x1d51, 0x42c0, 0x1012, 0x414d, 0x221b, 0x434d, 0x38f4, 0x19d3, 0xb22e, 0xbfcb, 0x41d2, 0x4124, 0x3cb4, 0x1be3, 0x40ae, 0xba40, 0xb0a4, 0x1bec, 0xbac0, 0x41da, 0x43a7, 0xbf21, 0x41f5, 0x406f, 0xb090, 0x2a79, 0x4314, 0x1e36, 0x1a8e, 0x4163, 0x336b, 0xb0c0, 0x43e4, 0x4266, 0x4312, 0x1e45, 0x42ba, 0x4215, 0x0214, 0xa1c8, 0x4032, 0x43ea, 0xba10, 0x43c3, 0x41cd, 0x46e5, 0x3272, 0x21ec, 0x26d0, 0xbf2f, 0x4266, 0xba19, 0x419e, 0x3453, 0xb2b1, 0xb21b, 0xb2ad, 0x400a, 0x40f5, 0x1c18, 0x433e, 0x40a5, 0x1e4e, 0xb0ec, 0x4354, 0x3d65, 0xaac5, 0xba57, 0x4649, 0x42ab, 0x4123, 0x4434, 0x43eb, 0x430c, 0x2549, 0x0da8, 0x4309, 0x4119, 0xbfa3, 0x4380, 0x24cb, 0x410d, 0x43da, 0x431a, 0x404c, 0xb008, 0x41f2, 0x4588, 0x4112, 0x4572, 0xb231, 0x4196, 0x14a7, 0xa3f8, 0x407d, 0x42a1, 0x4074, 0xbf9a, 0x40ef, 0x4450, 0xb2a4, 0x463d, 0xb0cb, 0xbfa0, 0x4141, 0x316d, 0x19f4, 0x1273, 0x4433, 0x3c10, 0x4228, 0x441e, 0xbfd6, 0x42bc, 0x4088, 0x430e, 0xb01d, 0x1fb4, 0x4054, 0xb287, 0x40ce, 0x4012, 0xba62, 0xb26d, 0x3cfc, 0x43d4, 0x2128, 0xba0d, 0x1837, 0x460b, 0x1add, 0xba23, 0x1abf, 0x437f, 0xbf2d, 0x43bb, 0x407e, 0xb09e, 0x428d, 0x4263, 0x18d9, 0x2f7a, 0x405d, 0x406b, 0x4293, 0xb251, 0x4151, 0x4300, 0x4383, 0xb240, 0xb2f2, 0x2fc6, 0x2bc6, 0xb234, 0xba56, 0xbf17, 0xba48, 0x41e6, 0xba28, 0x4035, 0x4324, 0xb050, 0x43e3, 0x1fb1, 0x42ab, 0x42d4, 0x0549, 0x456b, 0x41d0, 0xb0c4, 0xb07a, 0x42b5, 0x2d57, 0xba52, 0x40c0, 0x43f1, 0xb288, 0xb2db, 0xb231, 0xba37, 0xb2fd, 0x11f8, 0x416d, 0xbfa4, 0x41de, 0x3da7, 0x4157, 0x3ddb, 0x4083, 0x40a4, 0x43ea, 0xb004, 0x295b, 0x416b, 0xbfb6, 0x4695, 0xb275, 0x41db, 0x19b8, 0xb2ad, 0x402e, 0xbf7b, 0x44a4, 0xbfa0, 0x4035, 0x404c, 0xb034, 0x41e9, 0x46f0, 0xbfb2, 0x0925, 0xb022, 0xb0df, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x3ca5ddec, 0x567ac17b, 0x5090d856, 0x31dc27ca, 0xce87e6ed, 0x44ad92e1, 0xe67fdf6b, 0xfc9f1f92, 0x0b5c7af3, 0x2abf17e7, 0xcbcf4b4e, 0xcccdc713, 0x7e5f0f46, 0x63825539, 0x00000000, 0x900001f0 }, + FinalRegs = new uint[] { 0x00048400, 0x00001000, 0x00000181, 0xffffdebf, 0x00060440, 0x0000fe7e, 0x00008000, 0x00040400, 0x00000000, 0xfffffbe7, 0xcbcf4b30, 0xcbcf4b30, 0x7e650f86, 0x0000015d, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x3a7c, 0x0655, 0x41a9, 0x3921, 0x4147, 0x429a, 0x13d5, 0x415e, 0x43ad, 0x373d, 0xb2e7, 0xa82f, 0x45b2, 0xb299, 0x4174, 0xbf15, 0x1155, 0xba32, 0x3a9c, 0x4037, 0x437e, 0xb01c, 0x4245, 0xbf92, 0x4029, 0x4111, 0x3fd8, 0x4299, 0x1b95, 0x404a, 0x4057, 0x4315, 0x4047, 0x423f, 0xaf03, 0xb27d, 0x22a0, 0xb012, 0xa3cf, 0x40d9, 0x40c6, 0x0e90, 0x4694, 0x40fd, 0x418b, 0x03c9, 0x44e0, 0xa932, 0x4397, 0xba5b, 0xbf3d, 0x4223, 0x1ff6, 0xb296, 0x4322, 0x28dc, 0x32c9, 0x4108, 0xbfdb, 0x433f, 0xb249, 0xb00c, 0x1d27, 0x46cd, 0x1aa1, 0xba2b, 0x1c1d, 0xa28d, 0xbfd5, 0x0057, 0xbac6, 0x4308, 0x1a2a, 0x42d1, 0x18ff, 0x432f, 0xbae0, 0x415c, 0xb032, 0x3c6b, 0x43e3, 0xb265, 0x42a1, 0x4013, 0x430f, 0x1c8c, 0x1fa1, 0x33f7, 0x4168, 0xbf21, 0x2136, 0x431e, 0x422d, 0xbae0, 0x432d, 0xb0ed, 0xb2d8, 0x0a2c, 0x41e8, 0xbf80, 0xbf88, 0xba28, 0xb2aa, 0xb267, 0x430e, 0xbf47, 0x4091, 0x40a5, 0xb2b3, 0x1b9e, 0x41e0, 0x420f, 0x1bfd, 0x1a1f, 0x1e8e, 0xbf7e, 0xbaeb, 0xa08a, 0x437e, 0x4339, 0xb006, 0x221f, 0x42b5, 0xb2c8, 0xb202, 0xbaef, 0x1902, 0x4161, 0x413c, 0x42b3, 0x18ba, 0x2f5c, 0xb252, 0xba3d, 0x4166, 0x43ee, 0x438d, 0xbf8d, 0x46e2, 0x0540, 0x303a, 0x43f5, 0x42a2, 0x4081, 0x34fa, 0x024c, 0x11d5, 0x2ac3, 0xbf00, 0xbfcc, 0x45c3, 0x439d, 0x4019, 0x401d, 0x40b2, 0x0891, 0xa357, 0x0a3d, 0xbf00, 0x0e7b, 0xb29d, 0xae6d, 0x438f, 0x4364, 0x4311, 0x42bd, 0x19ad, 0x44a4, 0x4624, 0xb2e5, 0x4134, 0x43e5, 0xbf16, 0x402d, 0x4391, 0xba51, 0x438f, 0x18f7, 0x455a, 0xafda, 0xbf6f, 0x4125, 0xbf60, 0x2490, 0xba2a, 0x038d, 0x410b, 0x42e6, 0x4224, 0x40b0, 0x1a97, 0xa5ee, 0x4073, 0xbf90, 0x4362, 0x1fbf, 0x4314, 0x4051, 0xb234, 0x4336, 0xbf60, 0xb046, 0xba05, 0xa6cc, 0xb2e1, 0x3022, 0xbfe0, 0x4608, 0xb2b0, 0xbaf1, 0xbfc3, 0x2620, 0x45b5, 0xbad8, 0x408d, 0x24c8, 0xb242, 0xb0f6, 0x4284, 0x4251, 0xaed4, 0x4302, 0x4197, 0xba77, 0x1678, 0x45a8, 0xba65, 0x4333, 0x1e84, 0x1af4, 0x4212, 0xbaff, 0xa4d9, 0xac32, 0xbfd0, 0x2306, 0x42ac, 0xbfaf, 0xb04c, 0xb285, 0x21dc, 0x42a6, 0x4121, 0xbfc0, 0x432f, 0xbf99, 0x3be7, 0xbf90, 0x40e6, 0xb288, 0xb27f, 0x4328, 0x245d, 0x41ef, 0x43bd, 0xb291, 0x40bd, 0xb0b5, 0xbf90, 0xbfe4, 0xb209, 0x1860, 0x4270, 0x42f8, 0xbfb0, 0x40c2, 0xbaeb, 0x09fb, 0x44f1, 0x41fc, 0x406a, 0x4134, 0xbfb0, 0x332a, 0xb26e, 0x0619, 0xba45, 0xbf89, 0x432c, 0xb2c3, 0xac47, 0x1d59, 0x2714, 0xb098, 0x433a, 0xa38a, 0x4573, 0x40b7, 0x41d6, 0x4180, 0xb259, 0xad42, 0x46c1, 0xb076, 0x406b, 0x4131, 0x40fb, 0x243f, 0x4285, 0x1b92, 0xb256, 0xa70f, 0x4168, 0x40bd, 0xb290, 0x12c5, 0xbfb8, 0x3068, 0x42d4, 0x410b, 0x1ef1, 0x4258, 0x0368, 0x46b5, 0x406b, 0x40aa, 0x4321, 0x1f9d, 0x4173, 0x430c, 0x43e5, 0x4026, 0xb2d8, 0xbf01, 0x3733, 0xbac9, 0x4275, 0x4276, 0x43d1, 0x1b8c, 0x4169, 0x4146, 0xa9aa, 0x4302, 0x1316, 0x4416, 0x43ca, 0x02f5, 0x42bb, 0xa7f7, 0xb204, 0x4632, 0x416c, 0xb030, 0x409a, 0x404b, 0x4385, 0x41cd, 0xb2b5, 0xb287, 0x4390, 0xbfce, 0x1b41, 0xb279, 0x1df6, 0x4220, 0xb020, 0x40b1, 0x45d3, 0x1d7b, 0x4162, 0x4234, 0x4201, 0x41cb, 0x30ba, 0x4039, 0xba5e, 0x4402, 0x429d, 0xba4e, 0x4652, 0xb2e8, 0x19cc, 0x38fd, 0x41b3, 0xbfd0, 0x43d6, 0xbf01, 0x43ee, 0x42c8, 0x07a7, 0x1893, 0x43c5, 0x4013, 0x1288, 0xba4a, 0x1534, 0x4091, 0x429b, 0x426b, 0xb234, 0xbf3a, 0x2bf6, 0x1e2f, 0xb28a, 0xa71a, 0xba32, 0x41e8, 0xbf65, 0x43c9, 0xacce, 0x403b, 0xb250, 0xaa0b, 0xabc5, 0xa194, 0xb214, 0x421d, 0x40cc, 0xa0e9, 0xb2d7, 0x1eff, 0x43d1, 0x417e, 0x42f1, 0x4275, 0xba1b, 0x3012, 0x13d4, 0x1fdd, 0x0af4, 0x4255, 0x4264, 0xbf9b, 0x1f56, 0x43e9, 0x418b, 0xbad9, 0x410a, 0x083d, 0x30af, 0xba48, 0x2c36, 0xbfdb, 0xb0fa, 0xb216, 0x3970, 0xba5f, 0x0556, 0x435a, 0x41d2, 0xb2db, 0x4395, 0xb020, 0xa2df, 0x43f7, 0x1dca, 0xb017, 0xba46, 0xb2aa, 0xbfa0, 0x07b6, 0x4040, 0xbf60, 0xa3c8, 0xbf69, 0xb232, 0x3ac1, 0x42bc, 0x44a3, 0xbadf, 0x0651, 0xb27d, 0x1f97, 0xbf93, 0x1625, 0xb2f8, 0xb2b8, 0x1244, 0xbf00, 0x41ac, 0xba03, 0x4030, 0x3d8e, 0x08b1, 0x41c7, 0xb04e, 0x3332, 0x1f10, 0x2903, 0x2272, 0x462e, 0xb204, 0xbf3f, 0xb2d0, 0x4375, 0x4204, 0x4062, 0x180c, 0x4084, 0xb298, 0x0fc2, 0xb270, 0x4085, 0xbf9b, 0x243f, 0x4390, 0x4221, 0x4445, 0x43e3, 0x45a9, 0x43d4, 0xbfdc, 0x402e, 0xbaec, 0xa8b2, 0x40bd, 0x2eb6, 0x4281, 0xb206, 0xb268, 0x30ef, 0x4043, 0x434e, 0x1a1d, 0x14c5, 0x43a9, 0x1fa9, 0x4376, 0x41bd, 0x423c, 0x43ab, 0x1aa7, 0x42d6, 0x0513, 0x4033, 0x31c5, 0x410f, 0x1db1, 0x40d3, 0xbf0c, 0x422c, 0x427b, 0x4019, 0x4066, 0x419e, 0x1cc6, 0x3571, 0xa6f0, 0x1813, 0x40d3, 0xb099, 0x035f, 0x19c3, 0x42ad, 0x42fb, 0x1d9e, 0xb25f, 0x1a90, 0xb28f, 0xbf3b, 0x3c2e, 0xba0a, 0xb067, 0xb2d9, 0x401c, 0x406e, 0x1b33, 0x4320, 0x43f7, 0x0df9, 0xbf41, 0x4166, 0x42ca, 0x43e7, 0xba7c, 0xb077, 0x1792, 0x43d3, 0x4085, 0xb27a, 0x4293, 0x403d, 0x410c, 0xbf08, 0x11e3, 0x41c2, 0x4083, 0x4364, 0xa113, 0x4010, 0x207c, 0x42aa, 0x1b60, 0x25fe, 0x4097, 0xb202, 0x40fc, 0xb07c, 0xbfdb, 0x041e, 0x40e1, 0x4046, 0x43e8, 0x42a3, 0x424a, 0xba73, 0x4002, 0x4350, 0xb2ae, 0x05a7, 0xb244, 0xb2a4, 0x19b4, 0xbf81, 0x4146, 0x431b, 0x4137, 0x46f8, 0xbfb4, 0x423f, 0xba3c, 0x1c8a, 0x40b6, 0x416f, 0xbade, 0x0565, 0x4027, 0xb2e7, 0x42a7, 0x0c0a, 0x4213, 0x40e5, 0x4058, 0xba68, 0x41eb, 0x4234, 0xbf84, 0x4333, 0xba18, 0x4073, 0x39be, 0x4339, 0x1e78, 0x42e2, 0x1396, 0xba2e, 0x4051, 0x3e8c, 0x40a6, 0xbf5e, 0xbaef, 0x36c9, 0x4060, 0xbfe0, 0x19fb, 0xbf35, 0x40b3, 0xba2d, 0x417a, 0x425b, 0x0bc2, 0xa2c7, 0x43ef, 0x4550, 0x427f, 0x402d, 0x18b3, 0x4104, 0xba2e, 0x1a9a, 0x4243, 0xbacd, 0x4116, 0xb2dc, 0xba6c, 0x410d, 0xbfad, 0x4189, 0x40fb, 0x437f, 0xba6b, 0x1023, 0x4063, 0xb2af, 0xaa18, 0x2514, 0x41c0, 0x32e8, 0x01c7, 0x35a9, 0xbfad, 0x4029, 0xb083, 0xba10, 0xb2be, 0x3276, 0xaf71, 0x4086, 0xb277, 0xbf6b, 0xb2d6, 0xb2f3, 0xb2ae, 0x19c3, 0x4366, 0x443b, 0x4124, 0x43bf, 0xa919, 0x434e, 0x12bf, 0x009f, 0x4006, 0xbff0, 0x43c5, 0xbf1a, 0xb0f6, 0x416d, 0x4114, 0x4624, 0x4161, 0x4089, 0x43b1, 0x4155, 0xbad4, 0xb000, 0xb042, 0x191e, 0x41c1, 0x4013, 0xb24f, 0xbf6c, 0x1dee, 0xb294, 0x1ce2, 0x435d, 0x41b1, 0xad83, 0x11c0, 0x40ce, 0x317f, 0x43c6, 0xb21a, 0xb002, 0xb298, 0x41ae, 0xb20f, 0x05ef, 0xb2a6, 0xb2fe, 0x1ad7, 0x45a0, 0x030c, 0x4089, 0xb26b, 0x4088, 0x4079, 0x3dc9, 0xbf87, 0x42bb, 0x1efc, 0x4018, 0xb254, 0x40ce, 0x3abc, 0xb0be, 0x4017, 0x1890, 0x414e, 0x4670, 0x1c83, 0xbf70, 0xbf41, 0xb277, 0x4283, 0x42c1, 0x41b9, 0x4006, 0xb260, 0x40c6, 0x455f, 0x4169, 0xb09d, 0x19ba, 0x263d, 0x40f9, 0xb07c, 0x41f8, 0x41c3, 0xbf97, 0x4031, 0x40a4, 0x1d03, 0xb21e, 0x4051, 0x003a, 0x4191, 0x409f, 0xb240, 0xbfd0, 0x0eaa, 0x43c4, 0x4262, 0x112b, 0x4264, 0xba2c, 0x4239, 0xbfc1, 0x43d7, 0x422e, 0xb291, 0x323a, 0xbae2, 0xb22f, 0xb0da, 0xb26e, 0x1a25, 0x43e1, 0x4208, 0x407c, 0x1d1e, 0xb27e, 0x1c0c, 0x0edc, 0x43ae, 0x4064, 0x459c, 0xa866, 0xb06f, 0x437a, 0x411e, 0x0a15, 0x1aeb, 0xb228, 0xb21b, 0x426f, 0x42f6, 0xbf34, 0x44bc, 0x016d, 0xac00, 0xb2e4, 0xbafc, 0x41d6, 0x44eb, 0x4063, 0x41bd, 0x409d, 0x39be, 0x438f, 0x45d0, 0x172e, 0x45bb, 0xbf6e, 0xb28f, 0x4262, 0xb2b8, 0x1c01, 0x45c4, 0x188e, 0x432d, 0x3671, 0x4039, 0x4450, 0x0adc, 0x44eb, 0xbf2f, 0xbaec, 0xbfa0, 0x0c02, 0x4293, 0x43c3, 0x19b8, 0xb251, 0x22f4, 0x1f80, 0xba39, 0x42c6, 0x1caa, 0xa3ae, 0x3100, 0x41dc, 0x4252, 0xb00b, 0xba1e, 0x421b, 0x437e, 0x1770, 0x4094, 0xa2d9, 0x1b0d, 0x148d, 0x4328, 0x4569, 0xbf46, 0x4283, 0x43a3, 0x3399, 0x3025, 0x40ce, 0xb284, 0x432a, 0x03d0, 0xbf90, 0xbafd, 0xbfcb, 0x43ad, 0x4013, 0xba27, 0x3d2a, 0x466f, 0x1653, 0xb204, 0x225c, 0xb213, 0x21e4, 0x43d3, 0x0eb4, 0xbae8, 0x415e, 0x4244, 0x4239, 0x4692, 0x2014, 0x411f, 0x1dfb, 0x42a8, 0xbfb6, 0x42f3, 0x1138, 0x22d0, 0xbfbf, 0xbacc, 0x0049, 0xbfa0, 0x4315, 0xba7a, 0x4182, 0x142f, 0x42b7, 0xbaf4, 0x465e, 0x4030, 0xb21d, 0x1a68, 0xb240, 0x2a11, 0xa788, 0x4088, 0x373f, 0xbfc9, 0x4347, 0x4088, 0x40ad, 0xb24d, 0xba22, 0xba5b, 0xb2f6, 0x4294, 0x406d, 0x428c, 0x1e96, 0xba49, 0xb0bc, 0x41a1, 0x4298, 0xb099, 0x410b, 0xbf38, 0xb21e, 0x43a8, 0x41d9, 0x4316, 0x4308, 0xbf07, 0xba54, 0x402a, 0x045d, 0x4356, 0x4077, 0x1573, 0xb208, 0x4069, 0x4100, 0x42f4, 0xaffb, 0x284f, 0xae93, 0x4348, 0x33ec, 0xb2b1, 0x41ce, 0x40a1, 0x1683, 0x427f, 0x42c6, 0x18f3, 0xb29e, 0xb2c0, 0x3d0f, 0xba15, 0x4116, 0xbf12, 0x43bf, 0x1c9d, 0xbae6, 0x4167, 0x42e5, 0x40ff, 0x404f, 0x1a21, 0x4028, 0x4289, 0x438d, 0xbfa3, 0x182c, 0x1cd9, 0x4304, 0x45a0, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x916cb869, 0x845ddb9e, 0xc71efbae, 0xb5b0ddd7, 0xc96a166f, 0x0896734c, 0xe975a539, 0xda33958c, 0xf0631e50, 0x552ee4b2, 0x94f77f64, 0x1bf67a8f, 0x4cd88fc1, 0xd2a84a20, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x0990000d, 0xffa3ffff, 0x0990000a, 0x00000000, 0x00000000, 0xffffffa3, 0x00000000, 0xf0631ef0, 0xf0631ef0, 0x0000005c, 0x1bf68b67, 0x000000a0, 0x00000744, 0x00000000, 0x600001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x1e86, 0x39ce, 0xb015, 0x4241, 0xba7a, 0x0ca5, 0xba1c, 0xb2c7, 0x1ade, 0x40e0, 0xbfc0, 0x18d9, 0x4195, 0x43e7, 0x144f, 0x1aa8, 0x402c, 0x19e3, 0x0f08, 0xbf33, 0x0146, 0x0896, 0x4298, 0x4011, 0x40b9, 0x4225, 0xae23, 0xa371, 0xb2ea, 0xb293, 0xaa59, 0xbacb, 0xb203, 0x40e4, 0x42a3, 0x158d, 0x42d3, 0x4435, 0xa63f, 0x1cb7, 0xae3d, 0x1b9c, 0x1ae6, 0x43a5, 0x4682, 0x38c3, 0x4199, 0x40e3, 0x4462, 0xbfd6, 0x1737, 0x4034, 0x413d, 0x1491, 0x40bd, 0x40d4, 0x1b2a, 0xbf9f, 0xbafa, 0xb2e1, 0x435b, 0x4057, 0x414d, 0x43c2, 0x46b2, 0x4247, 0x41a7, 0x422e, 0x4082, 0xb207, 0x1a3a, 0x1515, 0x301f, 0x42a4, 0x391a, 0x41af, 0x4102, 0xb246, 0xbf1c, 0xb2a7, 0x1fcb, 0xbad6, 0x41e6, 0x290a, 0x42c7, 0x1a97, 0x4616, 0x42f6, 0x1668, 0x4144, 0xb229, 0x43de, 0x1abb, 0xaeb6, 0x4658, 0xb2fd, 0xbf80, 0xb027, 0x4068, 0xac56, 0x0b56, 0xba3e, 0xb0ae, 0x2699, 0x4175, 0xbf39, 0x4367, 0x39a9, 0x4377, 0x3b13, 0x0504, 0x41cb, 0x43fe, 0x43a9, 0xb037, 0x4226, 0x4090, 0xb219, 0x4236, 0x4043, 0x435b, 0x163b, 0x41b5, 0x42ff, 0x03d5, 0xbf4e, 0xbad9, 0x42d2, 0x4063, 0x18c2, 0x4441, 0x1dac, 0xb287, 0x40b8, 0x4007, 0xb28d, 0x41a5, 0x4230, 0xbfa0, 0x419f, 0x0084, 0x4042, 0x1fe7, 0x42e3, 0x03a8, 0x2f05, 0x4641, 0xba39, 0xb266, 0x42cd, 0xb0f9, 0xb012, 0x4066, 0xb276, 0xbf3c, 0xb2b6, 0x197f, 0x4091, 0x1871, 0xbf11, 0xba70, 0xa8b5, 0x404b, 0x1bce, 0x43bb, 0x3ac6, 0xb021, 0xb2f4, 0x41f0, 0x4402, 0x2c5a, 0x45cc, 0x437c, 0xb023, 0xb23c, 0x37b9, 0x412d, 0x42b2, 0x43a1, 0xb089, 0x417f, 0x43ad, 0x4220, 0x42b4, 0x4176, 0x2e55, 0xb228, 0xbf0d, 0x3038, 0x4315, 0x4302, 0xba33, 0x43e0, 0x43cd, 0x0765, 0xbfc3, 0x41b6, 0x1bbb, 0x4007, 0xafba, 0x1c38, 0xb25e, 0xb2f2, 0x1ad5, 0x4196, 0xba2c, 0x40b4, 0x1a04, 0xb01a, 0xba71, 0xb2a8, 0x4685, 0xb0b2, 0xb20c, 0xba54, 0x46c5, 0xbf86, 0xa737, 0x4309, 0xb230, 0x03e0, 0xb282, 0x45ae, 0xb293, 0x40d7, 0x40c5, 0x40da, 0xb2fc, 0x40fb, 0x42c4, 0xb217, 0x367a, 0x41bd, 0xba6f, 0xba59, 0xba7a, 0x08b7, 0xbf67, 0x13e5, 0x408c, 0x12cf, 0xa5fd, 0x44ad, 0x34f1, 0xb2eb, 0x407c, 0x2b7a, 0xbfe0, 0x4481, 0xb286, 0x18f7, 0x42c5, 0x1fa6, 0x41b2, 0x42de, 0x40a0, 0x2f21, 0x4090, 0x4309, 0x4071, 0x3c32, 0x4203, 0x42f0, 0xba32, 0x41d8, 0x1daa, 0xbfbf, 0x402f, 0xb2d2, 0x435b, 0x4158, 0x407e, 0x20ff, 0xba0a, 0x1a6a, 0xba33, 0x4093, 0x43f0, 0xbf57, 0x43f9, 0xb2a1, 0x432f, 0xb2f5, 0x41cc, 0x4244, 0x1dc6, 0xb2b1, 0xba27, 0xbfa7, 0x347c, 0xb042, 0xba06, 0x41b2, 0xbaeb, 0x199f, 0xbfa7, 0x432f, 0xb2dd, 0x1a37, 0xb2dc, 0x434d, 0xbac5, 0xbaf8, 0x420e, 0x3f3f, 0xb0b2, 0x4005, 0x2b8a, 0x1c8d, 0x46e8, 0xade4, 0x425b, 0x4152, 0x0240, 0x4175, 0x0d64, 0x449a, 0x4018, 0x4366, 0xb2d5, 0x226b, 0x40ae, 0xb20f, 0xbf57, 0xa2c7, 0x42ac, 0xaf3b, 0x461f, 0xba67, 0x2af4, 0x4230, 0x4087, 0x1543, 0x43a9, 0x4232, 0x432e, 0xba6b, 0x46da, 0xba31, 0xbacb, 0x431a, 0x4343, 0xbfa9, 0x40c7, 0x18fb, 0xb2c6, 0x4066, 0xbf13, 0x4399, 0x4480, 0x3d8f, 0x111f, 0xbf1f, 0xbad3, 0x42be, 0x22b8, 0x438a, 0x0dd7, 0xa490, 0x400b, 0x424b, 0x4285, 0x407a, 0xae75, 0x17ab, 0x41a9, 0x1d62, 0xb214, 0xbf29, 0x4084, 0xa46a, 0x23a2, 0x44d2, 0xb0e4, 0x4203, 0x45ac, 0x4266, 0x1e81, 0x1a45, 0x10c8, 0xb0e2, 0x0833, 0x40aa, 0xb2cf, 0x1866, 0x1948, 0x435b, 0x18e0, 0xb230, 0x3774, 0x41b7, 0x1d3c, 0xb2b5, 0xbf90, 0x4265, 0xbfba, 0x4008, 0xba33, 0x1e27, 0x42d4, 0x1eef, 0xaf74, 0x41b5, 0x4069, 0x0d28, 0x0e57, 0x4254, 0x406f, 0xaac3, 0x1f94, 0x14a1, 0x4290, 0x4141, 0x4216, 0xb01f, 0x081b, 0xbfa1, 0xbfb0, 0x412b, 0xb24b, 0xb035, 0xb21f, 0x4248, 0x3144, 0xba32, 0x41d9, 0x461c, 0x1bb8, 0x40c8, 0xb266, 0xb258, 0xb2d6, 0xbadf, 0x42d4, 0x1a11, 0xbae0, 0x4629, 0xbf80, 0x4268, 0x0cef, 0x410f, 0xba0f, 0xba5e, 0xbf16, 0x42b3, 0xa03d, 0xb2b4, 0xb030, 0x3899, 0x4541, 0xb2e3, 0x1803, 0x16d3, 0xbfc2, 0xb216, 0x41dd, 0x42fa, 0xbf80, 0xafdf, 0x42da, 0x40e8, 0x1c5b, 0xb291, 0x1afb, 0xbf31, 0x41e2, 0x3e9d, 0x3a13, 0x11f2, 0x40ae, 0x400e, 0xba28, 0x40b8, 0x409d, 0x417c, 0x1ee6, 0x4252, 0xaa67, 0x0485, 0x43c7, 0xb0f3, 0x410c, 0x28df, 0xba12, 0x2ed5, 0xbf9f, 0xba74, 0x1d89, 0x44ac, 0x437a, 0x1e57, 0xbff0, 0x40cf, 0x415b, 0xb0a7, 0x414b, 0x402c, 0x4495, 0x29be, 0xa885, 0x39bb, 0x43c7, 0xbfb8, 0xb233, 0x1b91, 0x438e, 0xb21c, 0x1b36, 0xbf61, 0x4378, 0x4393, 0xba2b, 0x34c6, 0x1fef, 0xaa51, 0xa22d, 0xb270, 0x0598, 0xbfb0, 0xbad6, 0x42d9, 0xba35, 0x1bb7, 0x45f3, 0x4115, 0x1cfd, 0x42e9, 0x402f, 0xb20c, 0xbfd0, 0xae0a, 0x40c1, 0xb09d, 0xba08, 0x1fad, 0x41a9, 0x4387, 0x461c, 0xbf53, 0x186f, 0x2e59, 0x42b8, 0x425d, 0xbf11, 0x436c, 0xba65, 0x43b9, 0x4626, 0x44c3, 0x41b6, 0x1884, 0xb057, 0x1e30, 0xbfdd, 0x43a6, 0x405e, 0xa060, 0x423e, 0xb014, 0xba47, 0xad06, 0x1f36, 0xbf75, 0x464a, 0x3179, 0xa268, 0x463e, 0xb086, 0x2610, 0x401f, 0x2f67, 0x4409, 0x42c7, 0x1f54, 0x409b, 0x4111, 0x424a, 0xbf5d, 0x43b2, 0x467c, 0x40dd, 0x4085, 0xa14a, 0xa7d5, 0xba37, 0x4378, 0x4152, 0x43f6, 0x022e, 0x22b8, 0xb219, 0xba5b, 0xbf27, 0x424a, 0x0809, 0x4413, 0xb046, 0xbf60, 0x10bf, 0x1d41, 0x41bd, 0x1ed3, 0xac2b, 0xb2e2, 0xbfa0, 0x0d1b, 0x1fa2, 0xbf0e, 0xba7c, 0x40e4, 0x404e, 0x4593, 0xb276, 0x4170, 0x3ab1, 0xaad4, 0x2013, 0x3e57, 0xb0b8, 0xba09, 0x463b, 0xa62e, 0x43ac, 0x4390, 0x1c3e, 0x41f4, 0x01b9, 0x43b4, 0xb20d, 0xbf69, 0x4246, 0x4183, 0x42f6, 0x1bae, 0x4081, 0xabe8, 0x4013, 0x44c1, 0xa15e, 0x4271, 0x41a5, 0x42e1, 0x1cc6, 0x4302, 0x1176, 0x0ade, 0x0697, 0x41ad, 0x42ab, 0x41c5, 0x4260, 0x454a, 0xba34, 0xbf02, 0xb035, 0xb04b, 0x430d, 0x4418, 0x1bdd, 0xbfba, 0xb26f, 0x1b87, 0x42f2, 0x403c, 0x420a, 0x40b0, 0x1c57, 0xb061, 0x438a, 0x4173, 0x2e49, 0xb2f9, 0xbfc6, 0x41db, 0x1ad3, 0xba5d, 0x456c, 0xba02, 0x4245, 0xb098, 0x4267, 0x4141, 0x4665, 0x44a4, 0x05b1, 0xb2c1, 0xb001, 0x4205, 0x43fb, 0x40df, 0x19be, 0x43a4, 0x405a, 0x463f, 0x4130, 0x41ee, 0xb2da, 0xbf97, 0xac39, 0x4601, 0x41e1, 0x4175, 0xa137, 0x43d0, 0x4182, 0x1bf3, 0xbfd9, 0x409d, 0xb2a1, 0xba51, 0x013e, 0xbac1, 0x12a3, 0x4069, 0xabd0, 0x4220, 0xb0f8, 0xa4f4, 0x198b, 0x2d94, 0x43ee, 0x1fce, 0xba06, 0x022e, 0x42ea, 0x4229, 0x1d1e, 0xb0e2, 0xbfa3, 0x0bed, 0x4095, 0xba7c, 0x0052, 0x403c, 0xbadb, 0x4180, 0xba66, 0x4028, 0x427c, 0x2473, 0xba2c, 0xbf9b, 0xb208, 0xba24, 0x41f2, 0x4377, 0x1dd5, 0x40c1, 0xbf90, 0xb2c3, 0x1b1d, 0x42ac, 0x45ca, 0xbf8a, 0x1edd, 0x276c, 0x46d5, 0x32fc, 0xa25c, 0xb2d8, 0x4063, 0xbfb0, 0x02b4, 0x1c84, 0x2bcf, 0x429e, 0xb0f0, 0x43c5, 0x0fe7, 0x4323, 0xb2af, 0x1c79, 0x42c9, 0xa5ef, 0x41e9, 0x039f, 0x46ec, 0x1cd3, 0x1747, 0xbf6e, 0x42ba, 0xbad7, 0x4048, 0xb25a, 0x1930, 0x4296, 0xb08c, 0x406a, 0xb066, 0x191b, 0x3bd6, 0xba7c, 0x416e, 0x0db1, 0x4273, 0xbff0, 0xbf19, 0xba02, 0xbaf5, 0x4297, 0x41cd, 0x42d0, 0x411a, 0x1adb, 0x3814, 0xbf68, 0xb0b9, 0x415a, 0x4658, 0xb215, 0x4198, 0x4082, 0x43f7, 0xb097, 0xba0e, 0x1ac2, 0x431a, 0xb075, 0x4253, 0xb2a6, 0xbf9a, 0x1816, 0x41d5, 0xb2ef, 0xb0fa, 0x428f, 0x1b75, 0xba57, 0x4241, 0x43b8, 0x41de, 0xb2c4, 0xbaf0, 0xa9d9, 0xbfd4, 0x3b4a, 0xbacd, 0x426c, 0x1fa9, 0x3d6f, 0xbf07, 0xa213, 0x42c7, 0x0aa3, 0x43a1, 0xb090, 0xba23, 0x42ff, 0xa8ce, 0x4313, 0x41f5, 0x4691, 0xa85a, 0xac75, 0xbfe0, 0x0459, 0xaabc, 0x42c0, 0xbf1a, 0x41e2, 0xb03b, 0x1b5e, 0x2210, 0x4263, 0xbfc0, 0x2f43, 0xa745, 0x1bcc, 0x403c, 0x443a, 0x44bd, 0xbfc0, 0xb25c, 0x400f, 0x42f4, 0xbfe4, 0x4446, 0xb23d, 0x4166, 0xba55, 0xb2b2, 0xb2aa, 0x19ad, 0xa192, 0xb0d9, 0xa1b6, 0x42d5, 0x429e, 0x402b, 0xbf37, 0xb2c3, 0x2823, 0x425c, 0x0c29, 0x42a0, 0x0885, 0x4077, 0x43db, 0xb2c9, 0x2cd3, 0x4249, 0xbf81, 0xb212, 0x400a, 0x133a, 0x03ef, 0x30dc, 0x2ebf, 0x1b95, 0x1abb, 0xbfb0, 0xb2e7, 0x41e0, 0x401b, 0x400f, 0x3cd1, 0x3f45, 0xbf5a, 0x43be, 0x44c1, 0x4662, 0xbfc0, 0x4019, 0x4357, 0xb298, 0x4278, 0x4338, 0x2492, 0x4279, 0xb0a0, 0x436e, 0x4378, 0x4272, 0x19be, 0x4243, 0x43dc, 0x41aa, 0x1938, 0xbf22, 0x4202, 0xb0b5, 0xbac3, 0x1989, 0xa4aa, 0xb21e, 0x43ad, 0x4495, 0xbfde, 0x2891, 0xb287, 0xb2bf, 0x182f, 0x436c, 0x0e88, 0xb2fd, 0x4207, 0xba23, 0xb0ac, 0xbf39, 0x3a14, 0xb2a6, 0x42f3, 0x13e5, 0xb097, 0x4008, 0x4286, 0x421c, 0xbf4a, 0x1dc9, 0x1a6a, 0x41bf, 0xba55, 0x2f4b, 0xaa67, 0x4175, 0x4003, 0x4189, 0xb279, 0xbf3e, 0x4102, 0x45dd, 0xbfd0, 0xbad2, 0x0cb3, 0xb06f, 0x43f0, 0x44ed, 0x1af4, 0x40e7, 0xbfa1, 0x1ea1, 0x461d, 0x42b7, 0xb0a6, 0x44c0, 0xa90f, 0x280c, 0x42ff, 0x39ac, 0x4334, 0x4395, 0xba55, 0x4119, 0x1c16, 0xbfa0, 0x40d1, 0xb24e, 0x41f0, 0x3292, 0xba52, 0x4016, 0x43ce, 0x0873, 0x3836, 0xb074, 0xb08c, 0xbacc, 0xbf8b, 0x4301, 0xb274, 0xbfa0, 0x41f3, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x5a5bb2e2, 0x70b44505, 0xf439f718, 0x7601686e, 0x506efe14, 0xe57877bf, 0xae23b7eb, 0x04b61cf8, 0x3e1feb19, 0x3ee9a3be, 0x31b2d471, 0x4f5928f4, 0x9e0f7156, 0x0e22cfed, 0x00000000, 0xc00001f0 }, + FinalRegs = new uint[] { 0xffffffc9, 0xffffffc9, 0x00009100, 0x7fffffff, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x7c404452, 0x8d794b1c, 0x4f5928f4, 0x8d794b1d, 0xfb201937, 0xf1ddb58c, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xbac1, 0x4158, 0x40ea, 0x242c, 0x4288, 0xb29e, 0x418a, 0xbf23, 0xb2fa, 0x4148, 0x3f4d, 0xba28, 0x43a5, 0xb0fb, 0x1ba0, 0x4000, 0x2c42, 0xbac7, 0x40bf, 0xa072, 0x43fc, 0x1202, 0x40a6, 0xba20, 0xbf96, 0xb0ff, 0x0d2b, 0xb221, 0xb274, 0xb237, 0x4048, 0x43ef, 0x45a1, 0x1d08, 0x1c73, 0xbf35, 0x44c0, 0xbacb, 0xb0bf, 0xa4a5, 0x06ac, 0xa673, 0x4076, 0x42de, 0x411c, 0x4316, 0xba5a, 0xb222, 0xbf60, 0x1f22, 0x4047, 0x4599, 0x43eb, 0x3d4e, 0x4684, 0x4159, 0x42d6, 0x4397, 0x4019, 0x37f0, 0x0c64, 0xba56, 0x404f, 0x419b, 0xbfab, 0x0a60, 0x1678, 0xb2e1, 0x4087, 0x4113, 0x1c75, 0x43e2, 0x4580, 0xb2f4, 0x1a65, 0x41bf, 0xa5c4, 0xbf00, 0x023e, 0xba5b, 0x40ec, 0x423f, 0x4265, 0x4159, 0xba17, 0x43d5, 0xbfa0, 0x419d, 0x0690, 0x4613, 0x4026, 0x4237, 0x419a, 0xbfbd, 0xaf55, 0xb248, 0x1543, 0x43c3, 0x1eac, 0x4208, 0xb224, 0xbacd, 0x4290, 0x40dd, 0xb2bb, 0x4551, 0x40a6, 0x4595, 0x4118, 0x1940, 0xbf41, 0x3427, 0x18e1, 0x405d, 0x4086, 0x42c3, 0xaaaf, 0x39b0, 0x40a6, 0x2091, 0xbf1f, 0xb29c, 0x0054, 0x3139, 0x40a2, 0x3fcc, 0x2890, 0x4073, 0x4378, 0x1fe5, 0x00f7, 0x1a7b, 0xbad5, 0x401d, 0x4229, 0x424e, 0x1e92, 0xa256, 0x40ed, 0xa108, 0xbfa8, 0x41b1, 0x40ad, 0x1f7c, 0xb2ce, 0xb27e, 0xbf7b, 0x4066, 0x305d, 0xba36, 0xbafb, 0x2265, 0xba43, 0xb213, 0x43fe, 0x40ba, 0xa9a2, 0xbfc5, 0x1c2e, 0x4254, 0xa6ce, 0x40c7, 0xb0d1, 0x416d, 0x425f, 0xa0b5, 0x4365, 0x1da1, 0xb204, 0x3eca, 0x430c, 0x4646, 0x43f9, 0x42c3, 0xbf68, 0x4058, 0x416d, 0x1e07, 0x1e89, 0x40ec, 0x4041, 0x40b4, 0xb01d, 0xb234, 0x4023, 0xb019, 0x4679, 0xb0ff, 0x4624, 0xad8f, 0x188f, 0x412e, 0xaf18, 0x1adc, 0xbf11, 0x1f44, 0x436d, 0x436b, 0xa7ad, 0x0ba2, 0xb28b, 0x4273, 0xb22d, 0x4097, 0x4105, 0x426c, 0xb25a, 0xbf90, 0x428c, 0x138c, 0x44e1, 0x418a, 0x1aff, 0x219f, 0xb269, 0xbf15, 0x0b01, 0x4201, 0x4008, 0xbafe, 0x3265, 0xb0b0, 0x4359, 0x45ba, 0x425f, 0xb214, 0x42e9, 0x4456, 0x404d, 0xbfd7, 0x40a2, 0xb0f8, 0x4032, 0x18b3, 0x3009, 0xb2ab, 0x41b7, 0x44c9, 0x4419, 0xb096, 0x429f, 0x433d, 0x38f2, 0xbfe0, 0x4083, 0x414b, 0x4055, 0xba74, 0xbf75, 0x4072, 0x41fb, 0xb04a, 0x43e3, 0x43f7, 0xad8d, 0xb251, 0xba67, 0x4300, 0x4259, 0x4206, 0xb0a2, 0xb242, 0x4699, 0x1c9e, 0xbf73, 0x434f, 0x0744, 0xa414, 0x4099, 0x4661, 0xba62, 0x2e9f, 0x431a, 0x05c6, 0x465d, 0xb2ee, 0xbae1, 0x093a, 0xbf90, 0x4399, 0xb21d, 0x40dd, 0xb2b5, 0x0057, 0xbf60, 0x1f1c, 0xbf18, 0xb28b, 0x41f3, 0xbadb, 0xbf9c, 0xb261, 0x129d, 0x42fb, 0x42b7, 0x4306, 0xb06f, 0xb2d6, 0x41b6, 0xa264, 0x4001, 0x2ef9, 0x2dc8, 0x40c6, 0x40f3, 0x456f, 0x414e, 0x26ae, 0x2625, 0x41bf, 0x40b0, 0x1a12, 0x31a8, 0xb000, 0x1a30, 0xbf31, 0x1198, 0x44fd, 0x043c, 0x4301, 0x1a9d, 0x10cc, 0xbfac, 0x401e, 0x135e, 0xb2df, 0x4356, 0xae2b, 0x40e0, 0x43f7, 0x322f, 0xba6b, 0xbfde, 0x1e91, 0x21a5, 0x413f, 0x4081, 0x134a, 0x43d9, 0x417b, 0xb089, 0xadcd, 0xb220, 0x43ab, 0x1aed, 0xa819, 0x1ee7, 0xb0de, 0xb2dd, 0x2249, 0x4122, 0x29d1, 0xa87d, 0xb0b2, 0x128c, 0x33a1, 0x0d9c, 0xbf16, 0x15ea, 0x4607, 0xbf70, 0x2f6c, 0x1900, 0x4060, 0x1abd, 0xb2f2, 0xba65, 0x4166, 0x3593, 0xbfcf, 0x1a90, 0x4308, 0x4447, 0xba58, 0x1bf2, 0x419c, 0x181c, 0x2f88, 0x4141, 0x418a, 0x4067, 0x12a7, 0xb0c5, 0x4197, 0x0a82, 0x0774, 0x34e5, 0x4456, 0x4109, 0x4209, 0x445d, 0x4029, 0x0d4e, 0x424b, 0x42ee, 0x41a0, 0x4673, 0xbf70, 0xbf3c, 0x18f1, 0x3e63, 0xa7f4, 0x097a, 0x4586, 0x4163, 0x40f1, 0xba57, 0x284b, 0x420e, 0x41cd, 0x4092, 0xba0d, 0x4230, 0x43ce, 0x445c, 0x44cd, 0xb23f, 0x40a6, 0x41ce, 0x1d86, 0xb235, 0xb251, 0xb289, 0x43ba, 0xbfc5, 0x408f, 0xba1b, 0xb0fe, 0x41c0, 0x4107, 0x36ad, 0x3817, 0x2df3, 0xab39, 0x223f, 0x4253, 0xbf67, 0x4148, 0x40d9, 0x42f0, 0xb0e5, 0x4314, 0x404d, 0xb2c4, 0x2d43, 0x41eb, 0x2b28, 0x4630, 0x21d0, 0x1f98, 0xb28a, 0x43c0, 0xbf80, 0x1d10, 0xacb5, 0x45db, 0xbf59, 0x1132, 0x1ef9, 0x429c, 0x1cce, 0x41af, 0xa433, 0x15e1, 0x42b5, 0x419c, 0x1b1f, 0xb2e5, 0x4012, 0xb28c, 0x1d9b, 0xa11e, 0x1a24, 0x423a, 0x40e8, 0xb257, 0x41f4, 0x4452, 0x24ad, 0xbf9a, 0x1809, 0xb2dd, 0xb2be, 0x4104, 0x19b0, 0x43c4, 0xb037, 0x43ea, 0xae35, 0x04ab, 0xb2f8, 0x2c34, 0x2827, 0x40bd, 0x466b, 0xbf59, 0xb029, 0xb219, 0x42fd, 0x434f, 0x1cfa, 0xa456, 0x44c4, 0x415b, 0x1e44, 0xbfbe, 0x4648, 0x4263, 0x41bc, 0x186f, 0xba0f, 0x1cda, 0x1a0c, 0xb0b6, 0x1c21, 0x143a, 0x4284, 0xbae2, 0x4083, 0x1fbb, 0x436d, 0x4286, 0x0262, 0x1fcd, 0x4368, 0x40b5, 0x4339, 0x360f, 0x420a, 0x40e0, 0xb29e, 0xbf61, 0x0a3d, 0x40d3, 0x433b, 0xb20b, 0x4588, 0x401e, 0xb2cf, 0x0ab7, 0x46ab, 0xbfcd, 0x42b0, 0x4199, 0x1deb, 0x16b0, 0x43bb, 0xa042, 0x0943, 0x42d8, 0x4298, 0x412d, 0xbf3c, 0xb01b, 0x43b0, 0xbafe, 0xb02c, 0x4086, 0xbfd1, 0x3145, 0x40aa, 0x1e27, 0xbaff, 0x2464, 0xbf60, 0x435d, 0x43cf, 0x41d7, 0x4408, 0xba3f, 0x40bb, 0x1900, 0x2295, 0xb2d8, 0x2325, 0x46d1, 0xb0f8, 0x438a, 0x43ea, 0x460f, 0x4205, 0x41a7, 0x0b21, 0x1c41, 0xbaeb, 0x3f2f, 0x4340, 0xbf08, 0x1d1a, 0x2ee5, 0xb0b8, 0x432e, 0xbf0f, 0x1edc, 0x4365, 0x1bda, 0xba31, 0x186c, 0xbf95, 0x4027, 0x4268, 0x4136, 0x41a1, 0x407d, 0x1d56, 0x420d, 0xbf11, 0x4246, 0x1bd6, 0x41eb, 0x41b7, 0xb073, 0x40bb, 0x1804, 0x4146, 0xb234, 0x1ca5, 0x40e5, 0xba68, 0xaa9b, 0x2f01, 0x4102, 0x40d0, 0x1c95, 0xb2be, 0x402f, 0xb264, 0xb29b, 0x46fb, 0xb288, 0x418b, 0xbf38, 0xa198, 0x0caa, 0x14ba, 0xadde, 0x033f, 0x40ae, 0x1b2b, 0xb0a9, 0x067e, 0x1fc9, 0x35ab, 0xbad2, 0xac87, 0x1001, 0xb2d3, 0x01a8, 0x407f, 0xbac0, 0xbf23, 0xb2d0, 0x45a6, 0x430b, 0x44e0, 0x4223, 0x42dd, 0xb2bf, 0xb209, 0x462b, 0xb2d5, 0x18a0, 0x4280, 0x1d75, 0x4261, 0xb2fc, 0xb2f7, 0x37a6, 0xba4c, 0x42ee, 0xbaf7, 0x2e93, 0xbf17, 0x4295, 0x43fc, 0xb06f, 0xb20b, 0x4065, 0x4068, 0xbfde, 0x40b8, 0x17c8, 0x43bd, 0x40c4, 0x4159, 0x118d, 0x412a, 0xaebf, 0xbf04, 0x2ba2, 0xb2d5, 0x0b54, 0x0295, 0x438a, 0xbaf1, 0x32b1, 0x2c88, 0x414a, 0x19c9, 0x4026, 0x434d, 0x2396, 0x1d91, 0xb24d, 0xad96, 0x418b, 0x37e2, 0x436c, 0x42ee, 0xbf8b, 0x4166, 0x433a, 0x40b5, 0xbae9, 0x41dd, 0x4303, 0x431a, 0x41f6, 0xbaef, 0x411a, 0xb2ab, 0x113c, 0x1a84, 0xb03e, 0x063d, 0xbf1e, 0xb22a, 0x3926, 0x40e6, 0xba22, 0x2aca, 0x2561, 0xba47, 0xba3b, 0x0a0a, 0xb05d, 0xb2fc, 0xbf95, 0x467e, 0x17a2, 0xba2d, 0xb031, 0x4227, 0x1c1d, 0x427c, 0x3211, 0xb086, 0x43af, 0x4041, 0xad6c, 0x1877, 0x202b, 0x405a, 0x446b, 0x34ef, 0x4084, 0xb252, 0x1ef7, 0x41c8, 0xbf6f, 0xb06f, 0x02fc, 0x409f, 0xba72, 0xb030, 0x4221, 0x4139, 0xbf87, 0x42d1, 0x19be, 0x42de, 0x46eb, 0xba3c, 0x4078, 0xbfb7, 0xba51, 0x43e8, 0x4194, 0x42c7, 0xbf04, 0x4085, 0x42de, 0x40aa, 0x303e, 0x4234, 0x1a9f, 0x1abc, 0x42bc, 0x3b66, 0x361d, 0x01fd, 0x41d2, 0x427e, 0x4332, 0xb21f, 0xb01b, 0xb2de, 0xba46, 0x40bb, 0x1f62, 0x419c, 0x43b1, 0xb2bf, 0xbf7a, 0x1f92, 0xa922, 0x018a, 0xbadf, 0xb229, 0xb02f, 0x195a, 0xbae1, 0x400f, 0x182c, 0xb252, 0x412a, 0x40d9, 0xbf7c, 0xbad9, 0x06da, 0x43e5, 0xb0ab, 0x431f, 0xba39, 0xb016, 0x1ac4, 0x406a, 0x414e, 0x42b8, 0x4237, 0xb28a, 0x44eb, 0x1aa1, 0x34f8, 0xba2a, 0x4568, 0xb210, 0x42b8, 0x1836, 0x434f, 0x4253, 0x21fd, 0x43ec, 0x4181, 0x4239, 0xbf79, 0x43eb, 0x4061, 0xb2c0, 0x29fd, 0x1ac6, 0x3e8c, 0xacfe, 0x43a7, 0xb257, 0xba3d, 0xb06b, 0x40ce, 0xbf61, 0x4205, 0x430d, 0x43ad, 0x02a9, 0x07c4, 0x41c3, 0x1c78, 0x4018, 0x4286, 0xbfd4, 0xbac1, 0x41e3, 0xba27, 0x1e65, 0xb20f, 0x4001, 0xbf68, 0x4642, 0x43af, 0x0ff7, 0x43c4, 0x0a6f, 0xbf19, 0x405d, 0x401c, 0xa368, 0x1c07, 0x06eb, 0x42d9, 0x1863, 0xb0ae, 0x42a9, 0x41ac, 0xb0b6, 0xbfa0, 0xbf90, 0xb2db, 0x4075, 0x4284, 0x431f, 0x1f38, 0xbf2d, 0x4385, 0xb00f, 0x40af, 0x1b7a, 0xaaa3, 0xa6de, 0xba15, 0x4159, 0x41a1, 0x3aee, 0x4072, 0xb0bb, 0x40fb, 0x442b, 0x3328, 0x1e7d, 0x1d86, 0xb232, 0x1b14, 0xb00d, 0x41b9, 0x40fe, 0x1a56, 0x1c3d, 0x2f3c, 0x09ad, 0x4111, 0xbf71, 0x4630, 0x030d, 0x4117, 0x4034, 0x3845, 0x0b31, 0xbae2, 0x462f, 0x0e01, 0xb225, 0x2df4, 0x4651, 0xbfa0, 0x4381, 0x1bcc, 0x432d, 0x4265, 0x42ad, 0x4379, 0x410a, 0xbf04, 0xb28c, 0x42a8, 0x414c, 0x4065, 0x1586, 0x43aa, 0x336d, 0x4211, 0x1bee, 0x42a7, 0x4026, 0x263a, 0x1fa1, 0x4354, 0xbfa9, 0x1d2b, 0x4337, 0x432e, 0xb2d6, 0x18a9, 0xb0a0, 0xba29, 0x4131, 0x4348, 0x403f, 0x0ab3, 0x4214, 0x4175, 0x4259, 0x43d7, 0x33cf, 0xba42, 0x11bb, 0xbf3a, 0xba77, 0x4098, 0x4232, 0xb2a7, 0x4375, 0x0786, 0x1f14, 0xa4fd, 0x01a8, 0xa0ad, 0x411e, 0x2763, 0xba0a, 0xb05f, 0xbac6, 0x4123, 0xbf46, 0x41b5, 0xb20d, 0xac2e, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x4ccb59f7, 0x5e2a5ea7, 0x0598ceb9, 0xfde9560f, 0xaeee5f97, 0x81c7018e, 0x8dfd005d, 0xf0809e08, 0xf2345361, 0x7be970a4, 0xd526e1ae, 0xd985c732, 0x2702a40c, 0xf395aaaa, 0x00000000, 0x300001f0 }, + FinalRegs = new uint[] { 0x00001a78, 0x00000000, 0x00000000, 0xffffffff, 0x00001bb4, 0x00000000, 0x0000781a, 0x00000063, 0xc8d14d87, 0xd526e1ae, 0xd526e1ae, 0xf296cb30, 0xe468a6c5, 0xf296b6a4, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x30ba, 0x3e17, 0xb2b6, 0x0e7a, 0x1ad3, 0x16d7, 0x3d09, 0xb2d4, 0x4149, 0x4388, 0xbf70, 0x403e, 0x4081, 0x2f93, 0xb263, 0x421d, 0x4136, 0x1fbc, 0xbadd, 0x42e0, 0x41df, 0x42f2, 0xb2ac, 0x4188, 0x1f29, 0xbf0c, 0x46e4, 0x0f06, 0x4220, 0xb090, 0xb2be, 0xa4e6, 0x4047, 0xbfe0, 0x465b, 0x3199, 0x4216, 0x1b5d, 0x0645, 0xb04b, 0x1ae2, 0xaec8, 0x1942, 0x4225, 0x4214, 0x3aa9, 0xba12, 0x41b2, 0x30a1, 0xb27b, 0xba72, 0xba69, 0x15c8, 0xbfdd, 0xb204, 0x427c, 0x42e0, 0xb260, 0x4169, 0xa447, 0xb204, 0x424e, 0x465d, 0x3fce, 0xabb3, 0x43f3, 0xba44, 0x418d, 0x42b9, 0xb045, 0x20a4, 0xb285, 0xb2c1, 0x424d, 0x0575, 0xb081, 0x0475, 0xb273, 0x43db, 0x4297, 0xbf16, 0x41d7, 0x1b1c, 0x1a44, 0x4220, 0x29a3, 0x4069, 0x4143, 0x464e, 0xb2e9, 0x4120, 0xb28b, 0xacb2, 0xac86, 0x2c6e, 0x43c5, 0x42d4, 0x42e7, 0xbf15, 0x1c4d, 0x41a9, 0x4300, 0x401c, 0x32f0, 0x431e, 0x4117, 0x43f3, 0xb041, 0x02d0, 0x443d, 0xbfc9, 0xb292, 0x436c, 0xb236, 0xb022, 0xb023, 0x430b, 0xbfc0, 0x4318, 0x456a, 0x2809, 0x4006, 0x432a, 0xba3e, 0x4396, 0x406b, 0xbf16, 0xba55, 0x4163, 0x4279, 0x035d, 0x4380, 0x4078, 0x1ccc, 0xba46, 0x4064, 0xb21a, 0x0727, 0x1a5e, 0xba7f, 0xa4ed, 0xbac7, 0x0244, 0xb26b, 0x23f1, 0x3321, 0xa490, 0x4284, 0xba64, 0xa24a, 0x4057, 0xbaff, 0x1877, 0x417c, 0x0d9c, 0xbf9c, 0x1c58, 0x42c3, 0x1c7f, 0xba5f, 0x0daa, 0x42d8, 0x41be, 0x40d2, 0x4298, 0x4118, 0x4085, 0x4353, 0xbf06, 0x402f, 0xb0fc, 0x45ab, 0x41da, 0x11d6, 0xbf04, 0xb025, 0x40a2, 0xb26c, 0x40ae, 0x40c5, 0x08ba, 0xb227, 0x4356, 0x423b, 0xb20c, 0x230b, 0x40e1, 0xb0fa, 0x4013, 0x14bd, 0x43de, 0x3daf, 0x0368, 0x407f, 0xb298, 0x41f8, 0x43de, 0x4048, 0x18a4, 0x08f7, 0x42b9, 0x41a8, 0xbf33, 0x404f, 0x4174, 0x4261, 0x43f1, 0x1609, 0x4281, 0x00ee, 0x4291, 0x43b1, 0x359a, 0xb0d0, 0xb09f, 0x40fe, 0xb2b5, 0x4466, 0xb050, 0xb25c, 0xba0b, 0xb03a, 0x04a6, 0xb247, 0x4390, 0xb291, 0x4198, 0xb02a, 0x421f, 0x40b3, 0xbf7d, 0x41f6, 0x23ee, 0x329a, 0x43d2, 0x421c, 0x4199, 0xb253, 0xb0c6, 0x3180, 0xb0b7, 0x43a0, 0xbfbf, 0x43e6, 0x1e76, 0x157f, 0x3290, 0x1952, 0x428a, 0x1b3f, 0x05ce, 0x0e2e, 0xb0bf, 0xb264, 0x41a3, 0x0a74, 0x4181, 0x4112, 0x3e9f, 0x42c4, 0x4214, 0x46b9, 0x1a96, 0xbf0f, 0xa787, 0x42fb, 0xb008, 0x40a0, 0x2262, 0xb261, 0x26a5, 0xa01b, 0x01fa, 0x40a4, 0xba0f, 0xa676, 0xb26b, 0xb262, 0xba1f, 0x4075, 0xbfc2, 0x415d, 0xb095, 0x1ead, 0x410a, 0x4328, 0x0c46, 0xb2e9, 0x430f, 0xb274, 0x415d, 0x420f, 0x197b, 0x46fb, 0x125d, 0x43ca, 0xa547, 0x4064, 0x425e, 0xbf2f, 0xb036, 0xb292, 0x40e9, 0x3d9e, 0xb227, 0xa088, 0xb0e0, 0xb21c, 0xafc4, 0xbace, 0x40a2, 0x100d, 0x405d, 0x135d, 0x4045, 0x054f, 0x430c, 0x415c, 0x3b73, 0x1c6b, 0x0905, 0x4213, 0x4471, 0xa947, 0xb269, 0xbf77, 0xba2b, 0x42c8, 0x119f, 0x1d18, 0x4031, 0x4657, 0x4240, 0x4147, 0x4191, 0x44ad, 0x410b, 0xbad7, 0x40d8, 0xa692, 0x1bd6, 0x4137, 0xb05c, 0x4204, 0xbf72, 0x22bb, 0xa447, 0x1ec3, 0xb07f, 0xbfd0, 0x438d, 0x4220, 0xbf4a, 0x40eb, 0x42f2, 0x2d2f, 0x40e1, 0xa238, 0x41fa, 0xb24e, 0xb2e5, 0xbfa0, 0x441c, 0xb056, 0x4365, 0x1cca, 0x1a9a, 0x4346, 0x2fbc, 0x4222, 0xb2c2, 0xbfda, 0x416c, 0xbfe0, 0xba4f, 0x40e8, 0x4386, 0xbfc0, 0x06e0, 0x4370, 0x419e, 0x3b0e, 0xae3e, 0x413b, 0xb0b1, 0xb02f, 0x1f7c, 0x377d, 0xbac4, 0x45d2, 0xb25d, 0x0d8c, 0x46c4, 0x4304, 0xa6dd, 0x1a5d, 0x4050, 0x410c, 0x4269, 0xb284, 0xbf2d, 0x414f, 0x044d, 0xba0a, 0x19a1, 0x4107, 0x4273, 0xbafa, 0xb222, 0x42ba, 0x45c0, 0x408b, 0x4081, 0x4102, 0x070a, 0xb2eb, 0xb056, 0x385b, 0x1b53, 0x046b, 0xba7b, 0x41a3, 0xbaf9, 0x4553, 0x313a, 0xb028, 0xbf42, 0x418c, 0x05c5, 0x4161, 0xa924, 0x4026, 0xba41, 0x40a4, 0x4285, 0x3ff7, 0x1c6a, 0xb207, 0x42ae, 0x4371, 0x116e, 0xbf35, 0x1d23, 0x429f, 0x1a06, 0x44fb, 0xb2ac, 0x40d4, 0x46e5, 0x403e, 0x4387, 0xb099, 0x4109, 0x40d0, 0x2f0a, 0x38cd, 0x02a6, 0xbf38, 0x46ac, 0x30d9, 0x087d, 0xb27b, 0x41ff, 0x1537, 0x4101, 0xba15, 0xb294, 0x41d6, 0x2af8, 0xba0e, 0x41d9, 0x3f8b, 0xba63, 0xb238, 0x4003, 0x1c25, 0x41c7, 0x43a7, 0x0ad6, 0xbf2b, 0x1ee5, 0xb2c4, 0xa57f, 0xb06e, 0x1e2e, 0x4039, 0x409b, 0x4102, 0x416d, 0xa855, 0xb235, 0x3847, 0xa008, 0x4411, 0xb26a, 0xa9fe, 0xbfd7, 0x41d7, 0xbf70, 0xbae6, 0xba06, 0x35df, 0xb219, 0x2f49, 0xb0cd, 0x35d3, 0xb0ca, 0x4383, 0xb233, 0x4381, 0x3a8f, 0x460c, 0x1df7, 0x0edb, 0x43d4, 0x1a05, 0x1318, 0xbac2, 0xb2b9, 0xb2d1, 0x4230, 0xbf3e, 0x422d, 0xb213, 0xb061, 0xa09a, 0x4057, 0x4077, 0x426c, 0x0376, 0x42a5, 0x19c9, 0xbf1d, 0x422d, 0xb2f2, 0xb04b, 0x41a5, 0x410e, 0xb2ac, 0x0819, 0x415b, 0x3316, 0x4314, 0x1fd3, 0xb075, 0xbfb3, 0xb08b, 0xa994, 0xbf80, 0xbae1, 0x40b4, 0xbfb8, 0x409d, 0x01ee, 0x43ec, 0x09d0, 0x2384, 0x4183, 0x416b, 0x411d, 0xbf78, 0xba54, 0xba72, 0x4397, 0x1eb9, 0x1efa, 0xb2ec, 0x183e, 0x18e1, 0x0a6c, 0x42c6, 0x232a, 0xba57, 0xbff0, 0x41f5, 0x42c9, 0x3b22, 0x421f, 0xb294, 0xb277, 0xa937, 0xbf8e, 0x462f, 0x20b5, 0x2e40, 0x4306, 0x43f1, 0x036f, 0xa324, 0xbf23, 0x3670, 0xba5c, 0x4050, 0x0906, 0x4389, 0x4684, 0x40b0, 0x3eb0, 0x1f2e, 0x1aba, 0x1963, 0x4145, 0x122d, 0xb2c1, 0x4555, 0x403d, 0xb28b, 0x4252, 0x1a41, 0x4077, 0x434f, 0x4459, 0x4360, 0xbf22, 0x1b7f, 0xb24e, 0x1433, 0x40b1, 0x41a3, 0xb2cd, 0xb021, 0x4251, 0x42e3, 0xb0b5, 0xb2d0, 0xbf55, 0xa034, 0x41e3, 0x05fb, 0x4067, 0x41c2, 0x4059, 0x40ea, 0x3288, 0x4328, 0x4268, 0xbf2d, 0xb05b, 0xb234, 0x4220, 0xba68, 0xbfc0, 0x42e8, 0x406d, 0x4371, 0x12db, 0x4157, 0x448c, 0x162f, 0x3d79, 0x4190, 0x4050, 0x0c23, 0x2cef, 0x415b, 0xbaf1, 0x4005, 0xbf57, 0x404d, 0x214b, 0xa974, 0x404c, 0x40cb, 0x03ca, 0x1675, 0x36c7, 0xbfda, 0xaecc, 0x4576, 0x2c9c, 0xa7ec, 0x423e, 0x1d8c, 0x4172, 0x437a, 0x0b36, 0xa6b8, 0x4160, 0x4648, 0x45c4, 0x1d48, 0x1cfb, 0xa650, 0x4265, 0xb2b3, 0x414c, 0x443a, 0x4378, 0xba29, 0x464e, 0x1d73, 0xbfa7, 0x121c, 0x2eb2, 0xba30, 0xb0e6, 0x40d8, 0x4108, 0xbf70, 0xbad4, 0xb27e, 0xbaca, 0x4694, 0x320f, 0x1605, 0xba48, 0xafdb, 0x41e1, 0x4095, 0x3649, 0x00ef, 0xbf25, 0x4233, 0x4226, 0x4390, 0x46b8, 0x4231, 0x43d0, 0x45d2, 0x42d7, 0x3871, 0x1a1d, 0x1d3e, 0xbad1, 0x418b, 0xb072, 0x1843, 0xb215, 0x1ae0, 0x0898, 0x4106, 0x41a2, 0xba0a, 0xbf43, 0x41c9, 0x4022, 0xa59a, 0xb035, 0x4549, 0xbfa7, 0x441f, 0x4167, 0x3b3a, 0x1bd7, 0xbf75, 0x3d63, 0x1bba, 0xb2ae, 0x401b, 0x0e2e, 0x42cd, 0x1ccd, 0x4028, 0xba22, 0x4343, 0x3458, 0x2dd4, 0xa814, 0x1985, 0x469a, 0x43c1, 0x4562, 0x444c, 0x19b5, 0x42ab, 0xbf7a, 0xb2a1, 0x4394, 0x4008, 0x0f19, 0xbae8, 0xb266, 0x4141, 0xae9b, 0x4349, 0x45ad, 0x1af6, 0x40c0, 0x40a2, 0xba1a, 0x439b, 0x1fb8, 0x11da, 0xa672, 0xb2d0, 0x4223, 0x449d, 0x431d, 0x42e8, 0x45ec, 0x18d7, 0xbf5e, 0x41df, 0xba7f, 0x423b, 0xa7ae, 0x28f4, 0x414b, 0x43fa, 0xb216, 0x190b, 0xa562, 0x4391, 0xb252, 0x03f3, 0xbac0, 0x40fe, 0x2da8, 0x4449, 0x0adc, 0x4103, 0xb0dd, 0x403f, 0x34fa, 0xbf00, 0x45a6, 0x4357, 0xbf8a, 0xb2a2, 0x4109, 0xadc3, 0x407b, 0x40ac, 0xb227, 0x41fa, 0xaccd, 0x46a8, 0x40fa, 0x1f95, 0xb069, 0x444a, 0x4037, 0xb230, 0x419b, 0x1426, 0x424d, 0xb05f, 0x107b, 0x4055, 0x3be8, 0x0e2b, 0xb0f2, 0x1793, 0x46cb, 0x1fe7, 0xbfc6, 0x4300, 0x1e82, 0x43cc, 0x34b4, 0x173b, 0xb290, 0x36a7, 0x4348, 0x0f9f, 0xba7e, 0x0bef, 0xb22d, 0x42bf, 0x4541, 0xbf70, 0x4193, 0xbff0, 0xb0f2, 0x4420, 0xb01f, 0x41b7, 0x4251, 0x4301, 0xbf0e, 0xbad0, 0xbafe, 0x1f39, 0x2717, 0x4651, 0x1858, 0xb0e6, 0xbf88, 0x0dc3, 0x162d, 0x1947, 0xb24e, 0x24fa, 0xba59, 0x18a4, 0x42f6, 0x4018, 0x42a2, 0x407c, 0x42c6, 0x30d6, 0x4299, 0xb081, 0x4288, 0x41f8, 0x01b4, 0xb00a, 0xb26d, 0x436c, 0xbf0c, 0xb27d, 0xb2a6, 0x45ae, 0x04d9, 0xa8af, 0xb050, 0xbf61, 0x3332, 0x27cd, 0x0c39, 0xb28b, 0xb0e7, 0x2bfa, 0x4231, 0x43cc, 0x4387, 0x1d3e, 0x2fcc, 0x0863, 0x43eb, 0xba00, 0x41bd, 0x4139, 0xbf80, 0x1a3d, 0xba4b, 0x1a7f, 0x41d5, 0x4695, 0x41eb, 0x4003, 0x4457, 0x4312, 0x3a69, 0xb253, 0xbf68, 0x4143, 0xba2f, 0xbaf5, 0xb2e5, 0xbf00, 0x0f33, 0x418a, 0xb254, 0xbf3a, 0xba72, 0x404a, 0xb297, 0xbf46, 0x4645, 0x41e7, 0x4320, 0x1a06, 0x44e0, 0x3e22, 0x41d4, 0xbaf8, 0x4226, 0x41a5, 0x4291, 0xbae8, 0xbaf5, 0x4199, 0x45d1, 0xbf0e, 0x1f1d, 0x3bb4, 0xba40, 0x0793, 0x4645, 0x0312, 0xb262, 0x41b9, 0xb20f, 0x42e3, 0x4018, 0x4570, 0x10d0, 0x038a, 0x434f, 0xb2d2, 0x4430, 0xa2b1, 0x042c, 0x194b, 0x45db, 0x43b7, 0xbf5e, 0x1b6d, 0xb282, 0x40a5, 0x4076, 0x2065, 0x1c00, 0x462d, 0xaded, 0x3907, 0x1fe4, 0x429a, 0x40bd, 0xba32, 0x43b0, 0x1b1d, 0x43ef, 0x41c0, 0x427a, 0xba34, 0xba7b, 0x2307, 0x008d, 0x46c3, 0x4373, 0x40f3, 0xbfb9, 0x4169, 0x4135, 0x413f, 0x2474, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x7602de1d, 0x625d8c21, 0x6c16bc87, 0xecb33bf3, 0xf2e0d596, 0x9ed96ef4, 0xc82738a1, 0xf563157e, 0x31d024d5, 0x481b545e, 0xbb853a8f, 0x828b1a9a, 0x77119f7e, 0x41a530b7, 0x00000000, 0x300001f0 }, + FinalRegs = new uint[] { 0x28000003, 0xf00ebff8, 0xd843d7d2, 0x00000000, 0x00000074, 0xc03affe0, 0x00000000, 0x00000000, 0x000017cb, 0xffffffae, 0x001b0000, 0x000017cb, 0xffffffff, 0xfffffffe, 0x00000000, 0x600001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x183a, 0xb2ba, 0xbff0, 0x4236, 0x4329, 0x416b, 0xb0ab, 0xbfc0, 0x461c, 0x1cc6, 0x438a, 0x1dfb, 0x43ef, 0x1b4e, 0xbaee, 0x36a4, 0xb2e2, 0x4169, 0xb048, 0x1d98, 0xa51c, 0x40cc, 0x4332, 0x42e2, 0xbf01, 0xa8ff, 0xb2c5, 0x4322, 0xba13, 0xb017, 0x278e, 0xba57, 0x43ca, 0x4097, 0xb239, 0x41e7, 0x43df, 0x1fe8, 0x4152, 0x34e7, 0xba3e, 0xb211, 0x41ce, 0xb229, 0x4194, 0xbfc8, 0x1c04, 0x403a, 0x43d2, 0xbf41, 0x434d, 0xba36, 0xba4f, 0x40f1, 0x12a2, 0xb2a6, 0x0ba3, 0x1430, 0xb2cf, 0xaf47, 0x4380, 0x215d, 0x18bc, 0xb0ff, 0x4088, 0x41b3, 0x4131, 0xb0ae, 0x1cf1, 0xbfdd, 0x42f0, 0xb2a4, 0x4245, 0x43dc, 0x19b7, 0x4322, 0xba58, 0x1c79, 0x4387, 0x4596, 0x1ece, 0x41fc, 0xbf01, 0x092d, 0xb016, 0x40d1, 0xbad3, 0xb2bf, 0x40a6, 0x4247, 0x402f, 0xa9b7, 0xb244, 0x19fa, 0x4244, 0x1b06, 0x18ef, 0x4181, 0x144c, 0x18b1, 0x4197, 0x4123, 0x3a3e, 0xb204, 0xbfbe, 0xbaea, 0x1c38, 0x436a, 0x2039, 0x1ccf, 0xaff3, 0x1847, 0xaecc, 0xb292, 0xb20f, 0x1f5a, 0xb2f2, 0x4183, 0x4004, 0x43b8, 0xab48, 0x43f9, 0x4234, 0x0ca8, 0x3607, 0x4118, 0x4206, 0xbfbe, 0x4073, 0x4688, 0x391a, 0x40b5, 0xba2f, 0xbf00, 0x3d16, 0x1b41, 0x421c, 0x434a, 0x413c, 0x405c, 0x41d8, 0x4375, 0x415c, 0x101b, 0x4540, 0x42ce, 0xb0aa, 0xbf9e, 0x42d0, 0x4215, 0x2cc7, 0x43db, 0xb23e, 0x2a9f, 0x461e, 0xbacf, 0x4344, 0xb2dd, 0x41d1, 0x4233, 0x2398, 0xa583, 0xb09d, 0xa90c, 0x3974, 0x407f, 0x41fe, 0xb03d, 0xb08a, 0x4283, 0x429a, 0x1730, 0x2b83, 0x414d, 0x3df6, 0x41ed, 0xbf0e, 0x19fd, 0x178e, 0x4158, 0xba46, 0x418b, 0x1030, 0xb033, 0x405b, 0x1e2f, 0x4063, 0xbfca, 0xbaf3, 0xb21d, 0x43dc, 0x412f, 0x42f0, 0xba72, 0xb040, 0x426e, 0x3339, 0xba6f, 0x10b4, 0x466c, 0x426b, 0xb276, 0x26bb, 0x42ea, 0x1338, 0x41f2, 0x4252, 0x41a3, 0x3467, 0x445c, 0x1034, 0x4577, 0x4277, 0x446c, 0x1933, 0x4032, 0xbfc6, 0x469c, 0xba1b, 0x2658, 0x4049, 0xb24d, 0x43ae, 0x42c8, 0x4316, 0x40cc, 0xb22b, 0xbf60, 0x3670, 0xba72, 0xb0e2, 0x40b8, 0xba6b, 0xb214, 0xbfd1, 0x428b, 0x1876, 0x1f3a, 0xb06f, 0x435e, 0x447a, 0x427d, 0xba75, 0xa04f, 0xaaee, 0x4222, 0x194f, 0x2702, 0xb05d, 0x015b, 0x4129, 0x425b, 0x4275, 0xaced, 0xb228, 0x04a5, 0x0d2c, 0x4140, 0xa285, 0xbfb3, 0x4208, 0x42c9, 0x380a, 0xbad3, 0x41c1, 0x4290, 0x4294, 0xba2b, 0xba5a, 0xbaed, 0x1fef, 0x4548, 0xb209, 0x43fd, 0x4233, 0xb0dd, 0xbff0, 0x418f, 0x4132, 0x4081, 0xae38, 0xb0e6, 0x4343, 0x427b, 0x1b87, 0xbf3c, 0xb051, 0x296c, 0xba3f, 0xa2de, 0xbf89, 0x4031, 0x1e9c, 0x429e, 0xb085, 0xbf90, 0x11c4, 0x40dc, 0x1dc6, 0x1d74, 0x4331, 0x4014, 0xb29f, 0x3445, 0x14a4, 0x4216, 0xbf51, 0x40f2, 0x4303, 0x4592, 0x42e2, 0x2058, 0xb296, 0xbfbc, 0xbfa0, 0x42d4, 0xbac6, 0x0f05, 0x4584, 0xba26, 0x4059, 0x26b3, 0x419d, 0xb2ac, 0x4261, 0xb200, 0x4201, 0x41cf, 0x4148, 0x4101, 0x43b7, 0xbf6f, 0x1c26, 0xb2f8, 0x426c, 0x1cce, 0x4438, 0xbaf2, 0x193a, 0x44d3, 0x1bf6, 0xba76, 0x39d2, 0x1223, 0x4243, 0xae1b, 0x405d, 0x40a8, 0x187c, 0x420f, 0xb226, 0xb050, 0x41ea, 0x418d, 0x42c6, 0xbf54, 0xa0f9, 0x38d5, 0x185c, 0x432a, 0xbfb4, 0xb2c7, 0x11a8, 0xb277, 0x4201, 0x41ab, 0xbf27, 0xb2ac, 0x1ab3, 0x3b89, 0x40f5, 0x2c46, 0x42ea, 0x43f4, 0x16c3, 0x4264, 0x3e2e, 0x4258, 0x4111, 0x40af, 0x01fe, 0x40b5, 0x0924, 0x1cb3, 0xba0f, 0xa039, 0x32d7, 0xbf80, 0x0949, 0x1bbe, 0xba79, 0xba3e, 0x41da, 0x4395, 0x4408, 0x18e5, 0xbf9e, 0x1ef5, 0x2b17, 0xa1b6, 0x1cb7, 0x42bb, 0x3992, 0x4109, 0x419c, 0x4445, 0x41d9, 0xbafc, 0x412d, 0x01bc, 0x4433, 0x434c, 0xba5a, 0xb207, 0xa3d7, 0xb090, 0x43bb, 0x42b1, 0xbfd7, 0x42e9, 0x4025, 0x331b, 0xad80, 0x41be, 0x430b, 0xb0c8, 0xbacd, 0x3b83, 0x1eca, 0xb073, 0x417d, 0x421a, 0x2cbc, 0x460d, 0xb224, 0x0041, 0xbfd0, 0xba2c, 0xb0d7, 0x42aa, 0x4360, 0xb239, 0xbf13, 0x4232, 0x43dc, 0x1d4d, 0x4172, 0x432f, 0xb217, 0x4014, 0xaf57, 0xb269, 0x4214, 0x401f, 0x4615, 0xba6d, 0x45f5, 0x3366, 0xba10, 0x41a7, 0x4320, 0x433c, 0x41aa, 0x4447, 0x43c4, 0x06b7, 0xbf13, 0x421a, 0x431e, 0x4337, 0xba43, 0x403e, 0xb281, 0x0bbe, 0x41ee, 0xa920, 0x4234, 0xb2f2, 0x43f3, 0x35f2, 0x41aa, 0xb247, 0x378e, 0xb214, 0x3c80, 0xba55, 0xb2fc, 0xb2ea, 0x402b, 0xb2bd, 0x4004, 0x4434, 0x443b, 0x0d49, 0xbfc5, 0x2a68, 0x4333, 0x4616, 0x421e, 0xb2b9, 0x4359, 0x43f8, 0x421a, 0x41bd, 0x16fe, 0xa8e2, 0xb25a, 0x435a, 0xb0d4, 0x416b, 0x1ea0, 0x4035, 0xb0ff, 0x4280, 0x31ab, 0xbf0c, 0x4043, 0x1d5c, 0xb01e, 0x414d, 0x43d1, 0x4283, 0x4008, 0x35e1, 0xb2c5, 0x4080, 0x26fa, 0x35a4, 0x2fee, 0xae2d, 0xbf94, 0x332b, 0x1c4b, 0xb234, 0xba72, 0x1f99, 0x4375, 0xb09b, 0xbfb1, 0x23e8, 0x1a82, 0xbad3, 0x4229, 0x40fa, 0x4271, 0x4454, 0x3f9c, 0xbf7f, 0x43dd, 0x43cf, 0x4051, 0x3ddd, 0x201d, 0xb293, 0xb260, 0xba02, 0x0725, 0x434e, 0x46b5, 0x46e9, 0x1748, 0x428b, 0xbac6, 0xb0b7, 0x41c3, 0xbaf9, 0xbf96, 0xb26d, 0x40bf, 0x1c2a, 0x41ee, 0x3251, 0x402b, 0x436d, 0xb012, 0x28c6, 0x3e10, 0x4472, 0xb064, 0x4254, 0x4008, 0x4326, 0x4187, 0x04fd, 0xb0f8, 0x4656, 0x400f, 0x1277, 0x3d1b, 0xba3a, 0x42bf, 0x43f3, 0xbf69, 0x4153, 0x43ad, 0x4695, 0x0135, 0x436b, 0x4167, 0xba22, 0xa2a9, 0x42f8, 0xbf0e, 0x437a, 0x1d74, 0xb01f, 0x017c, 0x1991, 0x42bc, 0x1faa, 0x43d2, 0x401f, 0xbf35, 0x43fc, 0xa8a4, 0x1b70, 0xb016, 0x0929, 0x2e6b, 0x4325, 0x4558, 0xb05d, 0xbf2e, 0x414c, 0x42cd, 0x4391, 0x433b, 0x4301, 0xbaeb, 0x1b75, 0xbfbb, 0xae43, 0x44aa, 0x41b4, 0x42a7, 0x1b64, 0x18d4, 0x2cac, 0xb222, 0xb2c7, 0x42c9, 0xbf90, 0xa53a, 0xbad0, 0x41b7, 0xb099, 0x46e3, 0xba47, 0x438d, 0x4244, 0xb0b5, 0x20f6, 0x456b, 0x433d, 0x4648, 0xba3f, 0x3fc9, 0xbf31, 0x194d, 0xac62, 0x42bb, 0x4315, 0x40ce, 0xaedd, 0xad17, 0x4026, 0x1270, 0xb0b2, 0x17c4, 0x1fbc, 0x417c, 0x1a36, 0x3eb7, 0xb004, 0xb03e, 0x4163, 0xba06, 0x424b, 0x43b4, 0xa91d, 0x4127, 0xb016, 0x4041, 0xb2db, 0x1abd, 0x2772, 0xbf2b, 0x410c, 0x1bc0, 0xbae4, 0x407f, 0x412b, 0x1900, 0x410f, 0x4255, 0xba25, 0x464a, 0xbf02, 0xbad9, 0x418a, 0x431a, 0xbad0, 0xb0b3, 0x4379, 0x442d, 0x4104, 0x13b2, 0x1b63, 0xbfc0, 0x1a55, 0x4271, 0x1d04, 0xb009, 0x42bd, 0x405c, 0x4175, 0xb268, 0x437d, 0xbfa3, 0x3b47, 0xbaec, 0xbae7, 0x0eca, 0xb275, 0x42d0, 0xb24a, 0x4369, 0x404d, 0x086e, 0x409c, 0x418f, 0x45b8, 0x3e1e, 0xbf81, 0x1f68, 0x435e, 0xba1b, 0x43b7, 0x40b5, 0x4064, 0x14b1, 0x13ed, 0x1849, 0xbfc9, 0xb05d, 0x40c3, 0x3623, 0xb2a0, 0x184d, 0x411c, 0x417e, 0xbf7a, 0x46e8, 0x41dc, 0x4329, 0x1cd8, 0xa148, 0xb0f5, 0x2e2a, 0x414d, 0x19ce, 0x41ee, 0x44cd, 0xa6a0, 0x412f, 0x4374, 0x43b9, 0xb06e, 0x1b62, 0x3bb0, 0x4336, 0x2103, 0xbf1f, 0x4291, 0xbadc, 0x4282, 0x4122, 0xab33, 0x4368, 0xbfb0, 0xbaf9, 0xb25f, 0xb0b5, 0xba09, 0xb271, 0x23c2, 0x4565, 0xa12c, 0xb24f, 0xbf3b, 0x2a8a, 0xa3fe, 0x09ff, 0xb288, 0x42b8, 0x40c4, 0xbac9, 0xba34, 0x425a, 0xba04, 0x42be, 0x0208, 0xb003, 0x29cc, 0xb23d, 0xa865, 0x4086, 0x2faa, 0xb0ab, 0xb0a2, 0xbfbb, 0x1955, 0x43af, 0xb2ba, 0x4211, 0x2327, 0x1d02, 0x4347, 0xb28d, 0x4126, 0xba03, 0x42f0, 0x4386, 0x4322, 0xa906, 0x0326, 0xba39, 0xba27, 0x1cfb, 0x4270, 0x419b, 0x0e80, 0x4662, 0x4303, 0x2737, 0x427d, 0x1a84, 0xbf7d, 0x4603, 0x1c74, 0xb27a, 0x4170, 0x346b, 0xba00, 0x38f6, 0x2096, 0x41aa, 0x1c51, 0x2ce4, 0xb097, 0x401d, 0xbf97, 0x4132, 0x439f, 0xb2dc, 0x43fc, 0x41a6, 0x1dc0, 0x4377, 0xb2c3, 0x4326, 0x4240, 0x42bd, 0x185d, 0x1a08, 0xba26, 0x4360, 0x4307, 0x093f, 0x23f5, 0x402e, 0x4423, 0x36b8, 0xacdd, 0x16a5, 0x4210, 0x421d, 0x4317, 0x42cb, 0x281f, 0xbf00, 0xbf38, 0x429c, 0x43f5, 0x4342, 0x44e4, 0xb217, 0x1af3, 0x40b5, 0x423f, 0x4095, 0xb0f7, 0x4593, 0xbf82, 0xbae0, 0x4132, 0xae4e, 0x2512, 0x4088, 0x4355, 0x43b2, 0xb24d, 0x3d61, 0x41ed, 0x1134, 0xb252, 0xb26b, 0x2517, 0x431f, 0xaf24, 0x42da, 0xbf31, 0xa58f, 0x3660, 0x227e, 0x43ef, 0x42de, 0x4203, 0x40fc, 0x42b6, 0x4158, 0x37ff, 0x366d, 0x4002, 0x1805, 0x466d, 0xb230, 0x423a, 0xb21e, 0xba0f, 0x41b2, 0xbad7, 0xb25e, 0x1948, 0xb029, 0xbf4b, 0x423f, 0xb2c8, 0xb25f, 0xba2d, 0x46b8, 0xb220, 0xbf60, 0x421e, 0xb20f, 0xba65, 0x4475, 0xbae1, 0x3874, 0x4031, 0xbf84, 0xb2d8, 0xbfe0, 0x2aa6, 0xbfa0, 0x4287, 0x4372, 0xba1e, 0x4250, 0xba77, 0x4619, 0x4098, 0x408f, 0x432d, 0xba12, 0xbfa2, 0x2b04, 0xba75, 0xb04d, 0x4251, 0x40c4, 0x4034, 0xba30, 0xba2a, 0x4010, 0x4139, 0x43ca, 0xba41, 0x18d1, 0x43ed, 0xb2c1, 0x4167, 0x2288, 0xba05, 0xad26, 0x0d4d, 0x4376, 0xbf53, 0x4139, 0xb2e0, 0xba4f, 0x4254, 0xbf98, 0x4331, 0xbf16, 0xa567, 0x420c, 0x081b, 0x1c4e, 0x03c9, 0x4220, 0xb097, 0x0871, 0x43da, 0xb06c, 0x427e, 0xb006, 0x42e0, 0xbf70, 0x42db, 0x06dc, 0x420f, 0x40eb, 0x41a4, 0xbfbc, 0x42d6, 0xb24a, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd3cfc4df, 0x5f6feaeb, 0x1bacfb37, 0xe934848c, 0x07b4b459, 0x8beb3310, 0x3e769455, 0x1dc2cbe1, 0xaa106f96, 0xf071655c, 0xa0cd784c, 0xed40276a, 0xb836b593, 0x485f4321, 0x00000000, 0x900001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x54ca3e07, 0x419af099, 0x485f4374, 0x90be86e8, 0xa9947992, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4499, 0x4021, 0x1d2e, 0x1c30, 0xb2ac, 0x44b1, 0x4148, 0xba7c, 0x26b5, 0x42af, 0x4151, 0xa7ea, 0x1bc7, 0x10ed, 0x439b, 0xa19b, 0x4201, 0x3aff, 0x2dc8, 0xba14, 0xb244, 0xbf0c, 0xbff0, 0x45c9, 0x418e, 0xbfc0, 0x4300, 0x282d, 0x4432, 0x41fc, 0xa442, 0x400d, 0xbaff, 0x1ed1, 0x1faa, 0x1e48, 0x433d, 0xbf5b, 0x4661, 0xb2ef, 0x45b9, 0x420e, 0xb0f9, 0x24c6, 0xb2aa, 0x429a, 0x403c, 0xb24c, 0xb0a2, 0xb205, 0xabd2, 0x42a8, 0x4660, 0x4340, 0x44ac, 0x46d9, 0x407c, 0x1b8f, 0xbf18, 0x440e, 0x46e1, 0x4184, 0x4210, 0x421f, 0x1bb3, 0x437a, 0xb272, 0x4320, 0x4230, 0x15b4, 0x110f, 0x2278, 0x418f, 0x1069, 0x0cde, 0x3a07, 0x420d, 0xb2d3, 0x4073, 0x40ee, 0xb0db, 0x3342, 0x4073, 0xbf31, 0xbaee, 0x4359, 0x43c6, 0x42e6, 0x0503, 0xba7b, 0xa23c, 0x42c8, 0x1b6c, 0xbf98, 0x434f, 0x1caa, 0x43f3, 0x4143, 0x41ca, 0x19c6, 0xbff0, 0x105d, 0x42a1, 0x3045, 0x419e, 0xb2c4, 0x4395, 0x40df, 0xa984, 0x41a6, 0x18c1, 0x41cc, 0x43bc, 0x211d, 0x429f, 0x324a, 0x102b, 0x40f3, 0x404a, 0xbf17, 0x40d2, 0x0930, 0xafec, 0x25ca, 0x43f6, 0x1ddd, 0x418d, 0x41d7, 0x43de, 0x18e2, 0x43b2, 0x43a8, 0xa27b, 0xba5e, 0xba38, 0x42fa, 0x4176, 0x1e51, 0xb28a, 0xac01, 0x093b, 0x42a1, 0xbff0, 0xb0fe, 0x29c7, 0x0dae, 0x403b, 0x44c9, 0xbf4c, 0x3212, 0x0a25, 0x43a6, 0x14ea, 0xb095, 0x4293, 0x42bf, 0xba44, 0x2636, 0xbaf8, 0x446a, 0x1967, 0x428c, 0xb0e5, 0xba08, 0xb0d3, 0x4081, 0x4293, 0x4208, 0xbfb0, 0x038e, 0x3581, 0x4484, 0x40b4, 0xba43, 0x424e, 0x4000, 0xbf38, 0x4054, 0xb2ff, 0x44e5, 0x41d8, 0x4291, 0x45eb, 0x43c2, 0xba56, 0x1c4e, 0x44bc, 0x41d0, 0x3ee3, 0x00fb, 0x4038, 0xa75b, 0xbae2, 0xbf23, 0x44a4, 0x404f, 0xb277, 0xba23, 0x4178, 0x4620, 0x410e, 0x4028, 0x4040, 0xa3e9, 0x2281, 0x2238, 0xb211, 0xb285, 0x1adf, 0x4138, 0x240f, 0x4353, 0x428c, 0x4347, 0xbf24, 0x4360, 0x1ffc, 0xbf57, 0x412c, 0x40c9, 0x3611, 0x4062, 0xa2f2, 0x4332, 0xb079, 0x42e9, 0x15c2, 0x40af, 0x4409, 0xba47, 0x423b, 0x1b23, 0x1fc7, 0xbf3b, 0x23f9, 0x40f3, 0x4044, 0x41fa, 0x438d, 0x4081, 0x436f, 0x429a, 0x266b, 0x4277, 0x4544, 0x3a28, 0xba6e, 0x4131, 0x1d81, 0xbaf7, 0x011a, 0x415c, 0x09a7, 0x41de, 0x40cb, 0xbfb0, 0x40b7, 0xbfad, 0x42c9, 0x1993, 0x426b, 0x1201, 0x41d7, 0x42db, 0xb258, 0x41db, 0x435c, 0x447d, 0x461d, 0x43dc, 0x2410, 0x4287, 0xb08b, 0xb2c9, 0x438e, 0x433a, 0xb233, 0xad57, 0x44b8, 0x40fc, 0x4459, 0x1c30, 0x4118, 0x3dfe, 0xba53, 0x1ee0, 0x4130, 0xbfa6, 0xbac3, 0x0f62, 0xa018, 0x424b, 0x4109, 0x4351, 0x407e, 0xbf70, 0x1a92, 0x4289, 0x2f97, 0x41bb, 0x414d, 0x419a, 0xb0ab, 0x40a6, 0xb20d, 0x405e, 0x43fe, 0x422e, 0x4033, 0x41b9, 0x027e, 0x45e6, 0xbfe2, 0x4365, 0x4301, 0xa5e0, 0x430e, 0x4260, 0x40a4, 0x44cb, 0x0824, 0x2e66, 0x4490, 0x1efc, 0xbade, 0x228a, 0xbf60, 0x4048, 0x43b7, 0xbfce, 0x3516, 0x1e5d, 0xaabb, 0x2dbd, 0x36a6, 0xb2fe, 0x459a, 0xb0af, 0x2212, 0xb2f3, 0x42a2, 0x4346, 0x18fb, 0xbfda, 0xb2bb, 0xba39, 0xb261, 0xb231, 0x40f7, 0x4221, 0xb0d0, 0x427e, 0x4270, 0x463e, 0x4332, 0x40ff, 0xb086, 0x43a2, 0x41fc, 0x42d8, 0xbf33, 0xba11, 0x41f6, 0xb23d, 0xb004, 0x41ef, 0x2b68, 0x41a9, 0xbfc8, 0x41af, 0xb0bf, 0x2d51, 0xb08a, 0x41e4, 0xba33, 0x0fbb, 0x445b, 0x3c22, 0x4404, 0x42b7, 0x42da, 0x396d, 0xbfd9, 0x4069, 0x46d9, 0x40c9, 0x424b, 0xa8a1, 0x4399, 0x0771, 0x1ff4, 0xba29, 0x4232, 0x2361, 0xbf1c, 0xb248, 0x40b0, 0x45a2, 0x4171, 0x40b1, 0xb281, 0x4187, 0xb2ff, 0x1a83, 0x160c, 0x1f34, 0xb049, 0xbf2d, 0x430b, 0x4024, 0x2813, 0xbfb0, 0x4146, 0x2134, 0xb2a3, 0x42ca, 0xb0e6, 0x42c3, 0xb2aa, 0x462d, 0x41ee, 0x3025, 0xbf41, 0x152d, 0xb2ab, 0x22cc, 0x22c3, 0xb2ae, 0xba61, 0x430d, 0xbaf2, 0x42ef, 0x40f0, 0x2d16, 0x19da, 0xbf98, 0xba66, 0x1b36, 0xb202, 0x41ba, 0x43ca, 0x28ca, 0x406c, 0x458d, 0xbf5f, 0xba62, 0x42a5, 0xbf70, 0x412a, 0x42e1, 0xa8e9, 0xb000, 0xb0b3, 0x16fc, 0x45e4, 0x4345, 0xbf67, 0x4115, 0x4226, 0xaf4f, 0x41cf, 0x29e1, 0xa321, 0x42e8, 0x426e, 0x3646, 0x42ea, 0xb2f7, 0x42de, 0xac68, 0x341c, 0xb21f, 0x4352, 0xba35, 0xbfe0, 0xb0be, 0xbf24, 0x40f8, 0x1ace, 0xa6be, 0x4627, 0x445b, 0x4130, 0x30b1, 0x26c4, 0xbf00, 0xb08c, 0xb2ed, 0xba6f, 0xb219, 0xb2d1, 0x1ce1, 0x0ae6, 0xb25f, 0xbfd4, 0x05e1, 0x4648, 0xba06, 0xabfd, 0x4068, 0xb27f, 0x1a01, 0x0851, 0x42ff, 0x4691, 0x3367, 0x436c, 0xb20c, 0x4096, 0x43bf, 0x1f4b, 0x10e5, 0xb24f, 0x4325, 0xbfc0, 0x4113, 0xbf44, 0x41ee, 0x4045, 0x43d5, 0xbfbb, 0xb2cb, 0x42e1, 0x3336, 0x43e5, 0x0914, 0xb245, 0xba06, 0x198e, 0xadcb, 0x4079, 0xb23c, 0xbf3f, 0xa03a, 0x4155, 0x41d7, 0xba45, 0x37fe, 0x2b2e, 0x404e, 0xbaf3, 0x405a, 0x18c6, 0x42c6, 0x40f1, 0x402d, 0x184d, 0xa630, 0x42f2, 0xbfac, 0xb2ff, 0xb26a, 0xb2fe, 0xabc6, 0xb288, 0x4180, 0xbaf8, 0x4239, 0x1a7f, 0xb2c1, 0x40cc, 0x3a53, 0x34be, 0x4273, 0x435d, 0xb009, 0x1f2d, 0x4392, 0x424b, 0x459e, 0x43d3, 0x409d, 0x41d6, 0x3faf, 0xa208, 0x4107, 0xbf82, 0x4442, 0xa1e1, 0x1bc0, 0x42ed, 0x4068, 0x0b47, 0xb04b, 0x43fa, 0xba1a, 0xac7d, 0x1899, 0xa69d, 0x4327, 0xad06, 0x1f24, 0x1a75, 0xa2d1, 0x4257, 0x4106, 0x411f, 0x43b0, 0x4406, 0x45f1, 0xb2d5, 0x41a7, 0xb2f6, 0xa9a5, 0xbf25, 0x402e, 0x46a4, 0x41de, 0x4323, 0x1e92, 0x2b9b, 0xbfe0, 0xbfa0, 0xa5cb, 0x4276, 0x19cd, 0x0928, 0x40a9, 0xb2da, 0x4224, 0x42ca, 0xb095, 0x4399, 0x1917, 0xb260, 0xba50, 0xbf2d, 0xa243, 0xb29f, 0x42a1, 0x45aa, 0xa358, 0xb299, 0x4411, 0x4056, 0x4322, 0x1261, 0x40cc, 0x4153, 0x43d7, 0x4492, 0xba2d, 0x42fa, 0x437e, 0x40d1, 0xb0b6, 0xb28a, 0xb2c8, 0x07f6, 0x45ad, 0x4063, 0xb27b, 0x41b8, 0x1df7, 0xbf6e, 0xb225, 0xbf90, 0x1823, 0xb0a6, 0xbaf9, 0xba4b, 0x441d, 0x1bb0, 0x46bd, 0xa111, 0x43cc, 0x40bb, 0x41f1, 0xbf1d, 0x1c87, 0x409b, 0xba0a, 0x41ac, 0xaec5, 0xb24c, 0xbfab, 0x41f9, 0x45d3, 0x2e14, 0xb0fa, 0x156b, 0x4346, 0xb2b6, 0x19af, 0xbaf0, 0x418d, 0xb29e, 0xba47, 0x4342, 0x19aa, 0x3a55, 0x42b5, 0x4623, 0x42ac, 0xb0bb, 0xb246, 0xbfd0, 0x40af, 0x420e, 0x1f14, 0x250e, 0x1e9b, 0xbfca, 0x1d20, 0xbf70, 0xba73, 0x3e76, 0x1f68, 0x1b7b, 0x41d5, 0xbadb, 0x18ab, 0x42a0, 0x4024, 0x03ef, 0xbfd2, 0x1d77, 0xb298, 0x40b2, 0xb20f, 0x4102, 0x4046, 0x42a3, 0xbf8f, 0xb2b4, 0x3410, 0x1aca, 0x4032, 0x1bfc, 0x3a0b, 0xbf1f, 0x40b4, 0xb2d8, 0xb02c, 0x4288, 0x425d, 0x2c05, 0xab89, 0xbaec, 0x43d7, 0xb22b, 0xb20d, 0x4013, 0xb22d, 0x1819, 0xbf0c, 0xaba0, 0x43c0, 0xbac0, 0xba66, 0x4013, 0xb065, 0x18aa, 0x4139, 0x42bc, 0x45c5, 0x4128, 0x405e, 0x41eb, 0xba01, 0x4302, 0xaf34, 0x402e, 0x1181, 0xba70, 0x40c3, 0x191d, 0x4037, 0x40e4, 0xba4b, 0x09b6, 0x4561, 0xbfb9, 0x43ea, 0x02ee, 0x4311, 0x40ea, 0x4394, 0x4367, 0x4059, 0x4399, 0x43b5, 0xb077, 0x40af, 0x4394, 0x42d0, 0xb2b5, 0xb0f4, 0x3bf1, 0x44dc, 0x1887, 0x401f, 0x3da4, 0xbf7a, 0xb26f, 0x4421, 0x2540, 0x4107, 0x4229, 0x4389, 0xba20, 0x3378, 0xbae3, 0xba3e, 0x1c59, 0x40c5, 0x40a4, 0x1a2f, 0x1f4a, 0x432b, 0xa962, 0x41a1, 0x4631, 0xbf3b, 0x4245, 0xb295, 0xb28d, 0x42be, 0xbae8, 0x1b8a, 0x1a0c, 0x44fc, 0x469a, 0x42b7, 0x1ba4, 0x41b3, 0x4361, 0x4241, 0x4034, 0x4099, 0x433d, 0x438f, 0x41c4, 0xb2e4, 0xbf92, 0x426d, 0x4023, 0xba0b, 0xba3c, 0x40d4, 0x4152, 0xb23a, 0x425e, 0x43ac, 0x4146, 0xbfcd, 0x1617, 0x41a8, 0x08af, 0x4364, 0xbadc, 0x4348, 0xb295, 0xb2ee, 0xb21f, 0x400c, 0x4214, 0x4253, 0x4193, 0x4544, 0x1ff0, 0x43bc, 0x2489, 0x4145, 0x414c, 0x409c, 0xa728, 0xb09b, 0xbf19, 0x431f, 0xba0d, 0xba6c, 0x4201, 0x4063, 0x418e, 0x2b73, 0x0d77, 0x4574, 0x408f, 0x31b6, 0x077e, 0xbf48, 0xb2ac, 0xb2be, 0x1088, 0x411e, 0x42e5, 0x410d, 0x428d, 0x4319, 0xbf36, 0x354d, 0x0e3b, 0xb25f, 0x4316, 0xbfa3, 0x419a, 0x039b, 0x41b6, 0x04fd, 0x4583, 0x4284, 0x43c1, 0xbf24, 0x14e3, 0x4366, 0x438f, 0x4388, 0x4335, 0x423b, 0x4181, 0xb227, 0x0e84, 0x0fa9, 0x43a6, 0x1264, 0x0b9d, 0xb0e9, 0xb283, 0x46f2, 0x0ca3, 0x4364, 0x4311, 0xbac1, 0x4115, 0xb0d0, 0x2aa3, 0x1923, 0xbf5b, 0xb04e, 0x35af, 0xa9d4, 0x42c9, 0x4315, 0xbf60, 0x3bfd, 0x0a60, 0xbad1, 0x405a, 0x18b4, 0xb20a, 0xb075, 0x432b, 0x43ea, 0x4272, 0xb2ab, 0x4218, 0x430a, 0x13ee, 0xba22, 0xbf8e, 0x17e6, 0xb260, 0x43ed, 0x439a, 0xb29d, 0x1e98, 0x4355, 0xba54, 0xaffd, 0xb2d2, 0x4394, 0x404e, 0xb08f, 0x3bdc, 0x0f8b, 0x4243, 0x4382, 0xbf48, 0x4138, 0x44fd, 0x422b, 0xb08f, 0x1150, 0xbf73, 0x41d6, 0x1b03, 0xb0bf, 0x432e, 0x283d, 0x45a8, 0x0fa2, 0x0992, 0x40c4, 0x46d2, 0x43d8, 0x42a4, 0x4119, 0x4016, 0x427d, 0xb0c3, 0xb29e, 0xa0df, 0x4258, 0xba3a, 0x1df4, 0x0f25, 0x2f1c, 0x435a, 0x421c, 0x41d4, 0xbf07, 0xb2f7, 0x1a9e, 0x43a2, 0x43d8, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x5cc0f53c, 0x72fcd234, 0xde457376, 0x49d4e05a, 0x758c22b2, 0x79c1668a, 0x3635ed63, 0xeac80e34, 0x347d2ad7, 0xd42d60b3, 0xe8a20e81, 0x381745c5, 0x0ec66078, 0xe13b4962, 0x00000000, 0x000001f0 }, + FinalRegs = new uint[] { 0x0000b3bc, 0x00000000, 0x8e4f0000, 0xffff4c43, 0x00004c4a, 0x00000000, 0x00004c43, 0x0000051b, 0x6c94709c, 0x00090000, 0x00000000, 0x55a4c7ed, 0x3cb8de89, 0x0000173b, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xb2ea, 0x42ba, 0x46c5, 0x422b, 0x1be5, 0x02d1, 0x1caa, 0x41bc, 0x43bd, 0x1dfd, 0xa8ec, 0xb044, 0x0ec5, 0x0aa4, 0x4022, 0x45c5, 0xb00b, 0x42da, 0x04c3, 0x00d9, 0x1e50, 0xbf03, 0x4184, 0x4254, 0xb282, 0x00fc, 0xb041, 0xb27c, 0xbafd, 0x18fc, 0x417c, 0x41d6, 0x4313, 0xba21, 0xb237, 0xbfb4, 0xb29f, 0xb260, 0x1acf, 0x21b0, 0xb062, 0xbac1, 0xbacb, 0x41aa, 0x1399, 0xb255, 0xb28d, 0x1f91, 0x408a, 0x1391, 0xb200, 0x40ba, 0x4234, 0xbad2, 0x4031, 0x417a, 0x43fe, 0x40ff, 0x4131, 0x44c2, 0xbf6d, 0xba23, 0xb22a, 0x1a46, 0x432b, 0x1a26, 0x1525, 0x4148, 0x421b, 0x3ac6, 0xba73, 0xb007, 0x448c, 0xb211, 0x40e9, 0xba0c, 0x2a7e, 0x409b, 0x4478, 0x4495, 0xba61, 0x40e5, 0x465d, 0x4110, 0xb07e, 0xbf04, 0x4174, 0x1ce1, 0xb2b7, 0xb26a, 0x40e2, 0x11ea, 0x091b, 0x46d5, 0x1def, 0x4351, 0x4244, 0x405a, 0x4109, 0xa87b, 0xb249, 0x44b1, 0xba4f, 0xb038, 0xb083, 0xbff0, 0x4016, 0x43f0, 0xb092, 0x03f6, 0x19ad, 0x4287, 0xbf76, 0x43b0, 0x4356, 0x3e6a, 0x1aa0, 0x41c5, 0xb2d4, 0x1cb6, 0xba0c, 0x43ce, 0x45f2, 0xb066, 0x361b, 0x294f, 0x4133, 0xaa78, 0x4659, 0x4019, 0x4310, 0x4158, 0x412d, 0x4077, 0x41d9, 0xba7e, 0xbf96, 0x112e, 0x3932, 0xb0cf, 0x4395, 0x4237, 0x2cd3, 0x35bb, 0x03ff, 0x3c3f, 0xa63b, 0x4352, 0x4110, 0x04e4, 0xaad1, 0xb05a, 0xb2af, 0x4101, 0x4309, 0x43c7, 0xa7cc, 0x1b1d, 0xb2bc, 0x42b2, 0x446d, 0x42e2, 0x4044, 0xbfae, 0xbfd0, 0xa92b, 0x2ca8, 0xba7a, 0x0c15, 0x18af, 0x10c6, 0x4125, 0x411c, 0xb290, 0x40c7, 0x38e2, 0x425a, 0x26d4, 0x45ca, 0x46b5, 0xa9af, 0xbfab, 0x401a, 0x3480, 0x43e2, 0xb205, 0xb06b, 0xb0ce, 0x4213, 0xba35, 0x42d3, 0x1d9c, 0x40c8, 0x43dd, 0x42cc, 0x264c, 0x0f86, 0xbf8a, 0x18b3, 0xb0b6, 0xbff0, 0x08c3, 0x146a, 0x43a1, 0xbfad, 0xb024, 0x419b, 0xada4, 0xb207, 0xa205, 0xb2b0, 0x4013, 0x1ffc, 0xb2dc, 0xb2a6, 0x4189, 0xa033, 0x373b, 0xa54c, 0x4229, 0x1f08, 0xbf17, 0x426b, 0xbf90, 0x18ca, 0xa154, 0x41c6, 0x3c30, 0x426a, 0x431a, 0x1add, 0xb0d0, 0xad69, 0x1d25, 0xbf1f, 0xb2c7, 0x26fe, 0x1dc6, 0xacb6, 0xba38, 0x4554, 0x19cd, 0x4167, 0xa82c, 0xb2d0, 0x15fe, 0x2a17, 0x43cf, 0x45bc, 0x4002, 0x438b, 0xb24a, 0x1bca, 0xb00a, 0x40e5, 0x4367, 0xbfa5, 0x425c, 0x1a88, 0x0641, 0x41e3, 0xb22c, 0xb245, 0x42d5, 0xb23f, 0x4615, 0x43fc, 0xbaed, 0x4272, 0x414c, 0xbf31, 0x41c1, 0xb2df, 0xb2cb, 0x402b, 0x42a6, 0x4356, 0xbf3e, 0xb2e9, 0x2814, 0xbfa0, 0x4143, 0xbfe0, 0x0b79, 0x1b81, 0x38a3, 0x412f, 0x20d2, 0x4068, 0x42cb, 0xbae8, 0x2a4f, 0x1d57, 0xb09d, 0x4168, 0xb208, 0x41e9, 0x408c, 0x357e, 0x469d, 0x43eb, 0x4189, 0xbac2, 0x40d8, 0x4580, 0xbf7a, 0x0b11, 0x4070, 0x4471, 0x43f4, 0x43b0, 0x4586, 0xba4a, 0x346a, 0xa7e9, 0x43b2, 0xbac3, 0x0423, 0x40d6, 0x248d, 0x40a5, 0x420a, 0x438f, 0x427b, 0x4202, 0x31ec, 0x2e50, 0xba26, 0xbf5e, 0x41f2, 0xb08f, 0x42ab, 0x2f2e, 0x4111, 0xbfd0, 0x4324, 0x40de, 0x4236, 0xba1b, 0x02e1, 0x3f0c, 0x0a8d, 0xa1e9, 0x092b, 0x42d4, 0x45dd, 0x43f4, 0x3b18, 0xaff9, 0xba2f, 0xba68, 0xb0de, 0x19b1, 0xbf37, 0x43c5, 0x1642, 0x41d7, 0xbf00, 0x311f, 0xbfd0, 0x4254, 0x423a, 0x4124, 0x1eb4, 0x4364, 0x4365, 0x40e0, 0xbf3e, 0x3c12, 0x43c1, 0x2fbc, 0xb0bd, 0xbac3, 0x4014, 0x41f6, 0x40dc, 0x40ac, 0x4330, 0x1e86, 0x438b, 0xb0d0, 0xac1f, 0x055f, 0x378c, 0x1f34, 0x1f9c, 0x0795, 0xb0c8, 0x410b, 0x4050, 0xbfdb, 0x41eb, 0x3e25, 0x4171, 0x432d, 0xb2d0, 0xb25f, 0xb281, 0x41e1, 0x432f, 0x4073, 0xbf60, 0x40fc, 0xbaf7, 0xb0ba, 0xaac8, 0x4318, 0x410c, 0xa369, 0xb08a, 0x42f8, 0x1c9b, 0xbf78, 0x34a5, 0x3209, 0xadae, 0x4272, 0x0d71, 0xa43d, 0x1180, 0x4229, 0x0689, 0x4313, 0xb208, 0x444c, 0x3a48, 0xba19, 0x4007, 0x4107, 0x2a6a, 0x41be, 0xb2ee, 0xbf42, 0x4332, 0x1870, 0x10cb, 0xb078, 0x411f, 0x29f2, 0xbfe0, 0x20df, 0x41f2, 0x39f3, 0x3ecf, 0x42fc, 0x1623, 0x43a9, 0xbf73, 0x40e3, 0x43ff, 0x40d3, 0x0ef5, 0x43a4, 0xb2aa, 0x4187, 0x403a, 0x1b1c, 0xb0be, 0x354e, 0xb09c, 0x402c, 0x2b3a, 0x0e11, 0x1335, 0xbf60, 0x44f1, 0x2ecf, 0x1b37, 0xb235, 0xb244, 0x428b, 0xb0bb, 0xb2d6, 0x19a6, 0x4589, 0xbf04, 0x1a8c, 0xb277, 0x13d1, 0x4394, 0x1f6f, 0xab11, 0x4335, 0x40f2, 0xb0b6, 0x1fb6, 0x403c, 0x40b0, 0x4274, 0x4345, 0x4282, 0x2a39, 0xb2e2, 0x4327, 0x4278, 0x44ab, 0xb014, 0x2313, 0x425e, 0x41e4, 0xbf6f, 0x1cd2, 0x40c1, 0x3319, 0x40dc, 0x43a4, 0x1839, 0x0638, 0x42b7, 0xbf17, 0x42b9, 0x0c5b, 0xb0e4, 0xb29a, 0xba78, 0xb290, 0x1ed9, 0xbf05, 0xb2ff, 0x42dc, 0x4253, 0x417a, 0x4304, 0x16d8, 0x3184, 0xb257, 0x3bd3, 0x419f, 0x3bd5, 0x4068, 0x10b7, 0x0f9e, 0x40a9, 0x4263, 0x414f, 0x43cd, 0xbfd0, 0xb0d9, 0x411e, 0xb26b, 0x3f5f, 0xbad3, 0x148b, 0x187a, 0x42ce, 0xb296, 0x1d75, 0xbf15, 0x4637, 0x42d9, 0x44d8, 0x4089, 0x05a3, 0x43d8, 0x43ad, 0x4091, 0xb21a, 0xb2bd, 0x4027, 0x1a6a, 0x40a2, 0x41ac, 0xa8f3, 0x41f9, 0xbf31, 0x4345, 0x3a56, 0xa529, 0x4215, 0x2faa, 0xb2b4, 0x195b, 0xb275, 0x4181, 0xba12, 0x43a6, 0x43f5, 0x315d, 0x4629, 0x1d9f, 0xbf94, 0x4212, 0x417b, 0x417c, 0xbfd1, 0x4614, 0x427d, 0x4140, 0xb2ae, 0x41ad, 0x1f90, 0x4286, 0x42ff, 0xac6c, 0xa113, 0xb29a, 0xb2e0, 0x0304, 0x46ed, 0xb2b4, 0xbf2c, 0x1079, 0x4056, 0x424b, 0x40b0, 0x43c2, 0x400d, 0xb2ba, 0xa574, 0x4354, 0x1145, 0x4567, 0x46a9, 0xba63, 0x4684, 0x4259, 0x4396, 0xb231, 0x4388, 0x4047, 0x40c5, 0x42b9, 0xba19, 0x31fc, 0xb012, 0x4154, 0x1a7b, 0x435c, 0xbf3b, 0x4211, 0x4136, 0xafba, 0x1a25, 0x4348, 0xb049, 0xba0e, 0xba6d, 0x43bd, 0xb231, 0xb2b6, 0x464e, 0x17cd, 0x4050, 0x438a, 0x410a, 0x41f7, 0x0d50, 0x41f6, 0x433c, 0xb0a6, 0xbaca, 0x40c2, 0xbf48, 0x4210, 0xbfc6, 0xba0c, 0xba18, 0x4641, 0xb2b3, 0x404f, 0x44f0, 0x4317, 0x2b5f, 0x4077, 0x458e, 0x1005, 0xb2a4, 0xbf42, 0x1ce5, 0x10f8, 0x4340, 0x43ca, 0xbfe0, 0x182e, 0x38a8, 0xa2ad, 0x42ed, 0x4592, 0x2346, 0xbf70, 0x43a2, 0x4037, 0x1360, 0xbafe, 0xb0bb, 0x43b2, 0x19c0, 0xbfa1, 0x4336, 0x4397, 0x1f3f, 0xba20, 0xb0e4, 0x4134, 0x1e98, 0x1926, 0xba44, 0x434d, 0xb087, 0x4673, 0xb20e, 0xba0f, 0x262a, 0xa308, 0x2693, 0x1e06, 0x424b, 0xb096, 0x439f, 0x36fa, 0x42d6, 0xb06a, 0x40fb, 0xbf13, 0xaa9f, 0x1d6c, 0xb2e0, 0x4363, 0x10e8, 0x1c58, 0xb290, 0xb072, 0x2deb, 0x421e, 0x1897, 0x4154, 0xab61, 0xba24, 0xbf70, 0x4326, 0x406a, 0x4595, 0x4169, 0xb228, 0x1e7f, 0xb20d, 0x188c, 0xb2a2, 0x1263, 0x0cbc, 0x46b0, 0xbf37, 0x42b0, 0x4374, 0x4312, 0x2515, 0xaa4a, 0x4150, 0x41fb, 0x434e, 0x42e0, 0x42f6, 0x408c, 0x26be, 0x42ea, 0x1edf, 0x1c47, 0x402c, 0x435e, 0xbf59, 0x3faf, 0x4462, 0xba3f, 0xb27d, 0xbf9d, 0x4207, 0xaede, 0xb223, 0x437f, 0x1786, 0x42a8, 0x4262, 0xb294, 0x1a29, 0xb2d6, 0x40cf, 0x1804, 0x42cb, 0xb025, 0xb265, 0x4298, 0xba0b, 0x4627, 0xb2d2, 0x40b4, 0xbf9b, 0x407c, 0x42b8, 0x18d0, 0xb2d0, 0xa7c8, 0x3b60, 0xb259, 0x2e49, 0xb228, 0x42dd, 0xb2b1, 0x237d, 0x43cd, 0x4001, 0x1a2b, 0xba71, 0xbf3f, 0x15f9, 0xb095, 0x01dc, 0x365f, 0x142e, 0xab9e, 0x4242, 0x4629, 0x424d, 0xb229, 0x404c, 0x0a33, 0x41a0, 0x455f, 0x077d, 0x1eb8, 0x1ba4, 0x13d8, 0x3aba, 0x1dbd, 0x4289, 0xbfca, 0x4253, 0x31bd, 0xb025, 0x27c1, 0x42dc, 0x1909, 0x37cd, 0xb28f, 0x43f0, 0x42af, 0x4185, 0xba6f, 0x466a, 0x15c5, 0x42e2, 0x3d8e, 0xba2d, 0x27ef, 0x45e6, 0xbfa3, 0x4204, 0x41b6, 0xb2bc, 0x4025, 0xbf67, 0x0886, 0x3457, 0x1cc4, 0x276c, 0x42a4, 0x3f63, 0xbad3, 0xbf16, 0xba62, 0xb087, 0x1e84, 0x4015, 0xb068, 0x31ca, 0x411e, 0x1fd1, 0xb01f, 0x4607, 0x1d77, 0x461c, 0x43cc, 0x401f, 0xb2b5, 0xa43d, 0xba66, 0xb28b, 0xbf16, 0xb209, 0x4097, 0x417e, 0x40bb, 0xa1b2, 0x4443, 0x4042, 0x405c, 0x1bfc, 0xba06, 0xb098, 0xb014, 0x40ec, 0x4159, 0x417a, 0xb2a6, 0x2ff6, 0xb21b, 0x44f8, 0x420a, 0xb239, 0x4156, 0xbf12, 0x1d1f, 0x42b6, 0xba5f, 0xb0a9, 0xbfe2, 0x43bd, 0x4384, 0x44bd, 0x4085, 0x43e4, 0x199f, 0x41bc, 0x186b, 0x429b, 0xb285, 0x1bf3, 0xb0ec, 0x4155, 0x40ff, 0x4019, 0x439f, 0xb0ad, 0x35bd, 0x21d8, 0x417f, 0x434d, 0x180b, 0x1a8d, 0xab25, 0xba3e, 0x1832, 0xbf37, 0x40f9, 0x427a, 0x4184, 0xba66, 0xba18, 0x4022, 0xbf60, 0xb2cc, 0xb2f2, 0x4127, 0x433b, 0xb0bc, 0xba3c, 0x4189, 0x439d, 0xb0e3, 0x4685, 0x3f24, 0x1d66, 0xbf08, 0x43c5, 0xb2de, 0x40f3, 0x183e, 0x430c, 0x40ea, 0x19e2, 0x1cd2, 0x1cf6, 0x3a92, 0xb0d3, 0xb2c3, 0x4236, 0x4313, 0x3162, 0x406d, 0xbf90, 0x42d3, 0x46d4, 0x40e9, 0x41a1, 0xa0cc, 0x4071, 0xba15, 0xbfa3, 0x3d16, 0x4361, 0x4247, 0x04b4, 0xb0ee, 0x1f85, 0x3042, 0x41a7, 0xb242, 0x428b, 0x4322, 0x420e, 0x38df, 0x4083, 0x41f9, 0x06c0, 0x420b, 0xb2a2, 0xb20f, 0xbf63, 0x4148, 0x0544, 0x4066, 0xa5e5, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x1ad1dd6f, 0x8a780aab, 0x92d86fe7, 0x3dcc1243, 0x715b8d62, 0xdd61381a, 0x5655e1f0, 0x38a9fd45, 0x5503834e, 0x5c5e12c5, 0xc6ca1316, 0xf112ee7a, 0x4c667450, 0xbace516f, 0x00000000, 0x200001f0 }, + FinalRegs = new uint[] { 0x78000000, 0xe27ff7b4, 0x00000000, 0x00000000, 0xff7c0000, 0x00001b68, 0xa713ffdf, 0xfffff7b4, 0x05001840, 0x00000000, 0x1bcd9664, 0xf112ee7a, 0x1bcd9664, 0xa713fcfc, 0x00000000, 0x600001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x43ea, 0xb039, 0xb0f0, 0x4163, 0x08a9, 0xba5c, 0x410d, 0x06ef, 0x42b8, 0xb095, 0x4045, 0x456a, 0xbf74, 0xabfb, 0x4084, 0x159e, 0xb230, 0x4372, 0xb005, 0x404f, 0x4017, 0x43f0, 0x43da, 0x40c7, 0xbf9f, 0x4207, 0x44c0, 0x0047, 0x0b05, 0xbf7b, 0xbfe0, 0x4057, 0x1d47, 0x1f0f, 0x40b2, 0x403c, 0xb09f, 0x03e8, 0x4246, 0xb26d, 0xb26c, 0xbf04, 0x42d6, 0xb040, 0x4631, 0x4092, 0x430e, 0xbf00, 0x40cc, 0xb243, 0x214e, 0x06a1, 0xbfc6, 0x40b0, 0x4631, 0x43cd, 0x4340, 0x13ca, 0x14d1, 0x4603, 0x4079, 0x2c83, 0x421d, 0xbacb, 0xafa3, 0x409c, 0xb2f9, 0xa17d, 0x30ee, 0xba61, 0x4207, 0x0a66, 0x420d, 0x4189, 0x41ea, 0x20f3, 0x42b9, 0xb20d, 0xa046, 0x4388, 0xb2bc, 0xbf7c, 0xb010, 0x43a3, 0xb21d, 0xb22f, 0xb2c8, 0x40bb, 0x19aa, 0xa4a9, 0xac73, 0x4296, 0x2be3, 0x0f78, 0x4107, 0x421a, 0x16f8, 0x427c, 0x444b, 0xbacb, 0x40b4, 0x0480, 0xb0b8, 0xba71, 0x402b, 0x40b5, 0x46a8, 0xbf34, 0xb25d, 0x2418, 0xaac1, 0xb251, 0x2ca1, 0xbf60, 0x4012, 0xbae5, 0xba4d, 0xba63, 0x4172, 0x41bd, 0xa74e, 0xbad9, 0x1f15, 0xb2d0, 0xb26e, 0x2e39, 0x413e, 0x4026, 0x4136, 0xb0eb, 0xb2fe, 0x4211, 0xbf76, 0x44fb, 0x43da, 0x3f14, 0x01c4, 0x41d7, 0x41ee, 0x0326, 0x237a, 0xb026, 0xbfbf, 0xb286, 0x40d8, 0x183c, 0xb2fc, 0x43cd, 0x41d4, 0x1b4c, 0x42c7, 0x03da, 0x4156, 0x40ce, 0x1888, 0x02d2, 0xba79, 0x40ef, 0x4081, 0xb23b, 0x4099, 0x417b, 0xbfa0, 0x4152, 0x4158, 0xbff0, 0xad95, 0x4477, 0x4176, 0x4132, 0xbadb, 0xbf0c, 0xba3a, 0x43e2, 0x1e39, 0x438f, 0x2393, 0x413b, 0x423d, 0xba05, 0x30e8, 0x4383, 0x4081, 0x1dc3, 0xba17, 0xbf9b, 0x294d, 0xac43, 0x18c2, 0x4362, 0xb2d4, 0x4349, 0x420d, 0x42a7, 0x42df, 0x2363, 0x40c8, 0x226c, 0x417a, 0xbfd0, 0x435c, 0x411d, 0xb07f, 0xba54, 0x1da1, 0xa883, 0x1b72, 0x4022, 0x43cf, 0x129b, 0x0952, 0x1a40, 0x1aaf, 0xbf3a, 0x406e, 0xb274, 0xb0a9, 0xbfe0, 0xba4e, 0x4004, 0x38de, 0x41cc, 0x4167, 0xb2a4, 0x413b, 0x1b27, 0xb072, 0x1ed4, 0x3b32, 0xb237, 0xb0fe, 0x1821, 0x414d, 0x41f1, 0x42b0, 0xbf1b, 0x426e, 0x460d, 0x4246, 0x41b1, 0x1c38, 0x4473, 0x413c, 0x4143, 0x24b8, 0x43bf, 0x43dc, 0x3a1b, 0x41fd, 0x4386, 0xba70, 0x41e7, 0x2256, 0x4086, 0x4341, 0x1af4, 0xb21b, 0xbfa0, 0x1f37, 0x4598, 0x431d, 0xbf63, 0x2db5, 0x416c, 0x428d, 0xb2a5, 0x4558, 0xb2f6, 0x41a8, 0x4014, 0x43f5, 0x33d6, 0x41cb, 0xb268, 0x4275, 0xb023, 0x438e, 0xbfe4, 0x415e, 0x19b9, 0x468a, 0xb252, 0x3626, 0x43ea, 0x2d17, 0x40aa, 0x42aa, 0xbfe0, 0x43a3, 0x1b9b, 0xba61, 0x4330, 0x0dbd, 0xa126, 0x40c7, 0x41de, 0xbfc6, 0x404d, 0x46a4, 0x4647, 0x4340, 0x40c8, 0xbf80, 0x42c4, 0x1df3, 0xbf90, 0xa7ac, 0xba4e, 0x4308, 0x0dac, 0xba47, 0x1d7a, 0xb06e, 0x3e91, 0x2e8b, 0x4008, 0x1c78, 0xb219, 0x4161, 0x410d, 0x4076, 0xbf9d, 0xa660, 0x422a, 0x42f6, 0x19e9, 0xbf9a, 0x43fb, 0x421e, 0xb28a, 0x40a0, 0xa3a0, 0x4081, 0x1990, 0xba24, 0xbf02, 0x41d5, 0x4078, 0x4176, 0xba52, 0x1f86, 0xb042, 0x430f, 0x2816, 0x428f, 0x4084, 0xa29f, 0xb019, 0xbfc8, 0x432c, 0x40a9, 0x40a3, 0x1b29, 0x43f5, 0x3867, 0x40cd, 0x4330, 0xb277, 0x3830, 0x41d4, 0xb083, 0x4164, 0x36a6, 0x4082, 0x4141, 0x1167, 0xb2fa, 0x1a54, 0x4284, 0x43e4, 0xa27e, 0xa4e3, 0x413d, 0xbf9a, 0x08da, 0x1b35, 0x4385, 0xb232, 0x4166, 0x422c, 0xbf90, 0x412f, 0xbf2f, 0x41b9, 0x1b51, 0xba0c, 0xbfb0, 0xba34, 0xbf8f, 0x1d3d, 0x30c1, 0x439c, 0x4543, 0xb2d0, 0x1fef, 0x4359, 0x4167, 0xb29f, 0x4331, 0xb2f5, 0x4308, 0xa8a7, 0xba06, 0x400f, 0x43e8, 0x246f, 0x4206, 0x416f, 0xb22c, 0x4043, 0x42b3, 0xbacd, 0x41ac, 0xb280, 0x27e3, 0xbf44, 0x402b, 0x4009, 0xb22b, 0x423d, 0xb28a, 0xb234, 0x4114, 0x42c3, 0x2c01, 0x4344, 0x40a0, 0x41e8, 0x180b, 0x259d, 0x4296, 0x0859, 0x4004, 0xbf6e, 0x43a3, 0x42ca, 0x409f, 0xbaca, 0x1c37, 0x4085, 0x40c7, 0x4113, 0xbac5, 0xb02d, 0x1c91, 0x3cb2, 0x0819, 0x4352, 0x432f, 0x3f57, 0x3c39, 0x43a0, 0xb063, 0x430f, 0x4374, 0x4131, 0xbf52, 0x43d4, 0x432d, 0x07d6, 0xa457, 0x431b, 0xad5a, 0xb24f, 0x16dd, 0xb026, 0x22aa, 0x432c, 0xa6c9, 0x41f6, 0xac8c, 0xb07d, 0xa40a, 0x4137, 0xbf95, 0x3196, 0x41e4, 0xb2c0, 0x4362, 0xb24b, 0x407a, 0x4093, 0xbfdf, 0x0cd1, 0x3b7c, 0x427f, 0x4312, 0xa0a2, 0x20fd, 0x41cc, 0xba31, 0x424e, 0x1aa4, 0xbacb, 0xb26a, 0x4048, 0x4416, 0x43e5, 0x42d4, 0xba20, 0xbf8f, 0x4079, 0xb27e, 0x4271, 0x40ee, 0x466d, 0x2d6c, 0x1843, 0x4083, 0x4327, 0x0cc7, 0x4582, 0xbf6f, 0x424c, 0xafb3, 0x41a0, 0x4239, 0x4404, 0xbf80, 0x418d, 0x43e0, 0x1ab2, 0xbfaf, 0x43ab, 0x4357, 0x42cb, 0xb0bf, 0xba12, 0x1396, 0x41de, 0x4116, 0x2112, 0x1b76, 0x4007, 0x44e9, 0xb224, 0x14d0, 0x402d, 0x412e, 0xb2d1, 0x2afc, 0x42f2, 0x1eca, 0x3412, 0x4472, 0x4252, 0x40cd, 0xb0fa, 0x0898, 0xbf79, 0xbaca, 0x3752, 0x0b3b, 0x1f37, 0x3758, 0x42b8, 0x43bd, 0x01e7, 0x42d1, 0x400a, 0xb044, 0xb2c6, 0x40bb, 0x4384, 0x3f19, 0x43ae, 0xba2c, 0x05ef, 0xb211, 0x1e3a, 0x437d, 0xbfa0, 0xbf7b, 0xb0a2, 0x4326, 0x1c73, 0x425b, 0x412d, 0x44d0, 0x11c1, 0x1b69, 0xb2c3, 0x1f89, 0xa6b4, 0x2ed9, 0x418d, 0x1ccb, 0xaa53, 0x402d, 0x1a6b, 0x3c2d, 0x4622, 0xb2c7, 0xa4b8, 0x4066, 0x45cb, 0x10a2, 0x4010, 0x41bb, 0x4300, 0xbf15, 0xb0b0, 0xba4e, 0xb29f, 0x1d46, 0xba36, 0x43b8, 0x0c37, 0x4499, 0x1bcf, 0xae12, 0xb26a, 0x42d5, 0x4362, 0x25db, 0xba02, 0xb29e, 0x3650, 0x4564, 0x4158, 0x1d0e, 0x4132, 0x4237, 0xb06f, 0x4377, 0xbf92, 0xbfb0, 0x4151, 0x443e, 0x439f, 0x43af, 0xaf28, 0x4151, 0x40d7, 0x40c7, 0x070e, 0x467f, 0xb226, 0x4418, 0xb016, 0x4288, 0xb009, 0x3e9f, 0x2434, 0x45f5, 0xb0a5, 0x1b5f, 0xb270, 0xbf39, 0xb2b4, 0x4093, 0x169a, 0x43c0, 0x29c4, 0xbf61, 0x4206, 0x2bcc, 0x401c, 0x43b0, 0x432e, 0x07bf, 0x40a7, 0x1b14, 0xb09b, 0x1a2b, 0xa498, 0xbaf3, 0x4204, 0x415b, 0xb02f, 0x42c9, 0x4264, 0x40eb, 0x40b9, 0x2f8a, 0x162c, 0x1b93, 0x18c7, 0x40d6, 0x4484, 0xbf63, 0x407b, 0x40cc, 0x19bd, 0x4106, 0xbaec, 0x4076, 0x4271, 0xadcc, 0x42b6, 0xbf12, 0xb2fc, 0xb267, 0x40ea, 0x40c4, 0x34ed, 0xba7e, 0x0e23, 0xae59, 0xbf4a, 0x0743, 0x43d7, 0x42dd, 0x4097, 0x40ed, 0x40a8, 0x45ed, 0x1e93, 0xb213, 0x1c3b, 0x0ea9, 0x420d, 0xb038, 0xba5a, 0x42ed, 0x41e0, 0x41b2, 0x4327, 0x19c5, 0x42d8, 0x26fb, 0x43fd, 0x0f88, 0x43a7, 0x1c9b, 0xb207, 0xbfc9, 0xb2c5, 0x430a, 0x2512, 0x4356, 0x41ed, 0x4078, 0x40c0, 0xbf99, 0x4141, 0xba72, 0x4226, 0x42d0, 0xb2b2, 0x4150, 0x42d1, 0xb04c, 0xba33, 0x2f36, 0x45b0, 0x18b0, 0x1d78, 0xbf43, 0x426a, 0x4350, 0x4287, 0x406a, 0x1911, 0x4199, 0x4074, 0xbf9a, 0x43f2, 0x4344, 0x41f6, 0x0efc, 0x30a0, 0x2e3e, 0x4359, 0xb261, 0x46b5, 0x4164, 0x25dd, 0x0839, 0xa6d1, 0x4181, 0x4627, 0x419a, 0xba3a, 0xbf16, 0x3ee3, 0x158a, 0x407d, 0x424f, 0xba32, 0xb212, 0x41f9, 0xb019, 0xbf80, 0xbfa0, 0xb2af, 0xb2bb, 0xb21e, 0x427c, 0x441b, 0x40e7, 0x3e04, 0xba75, 0xbfde, 0x40dc, 0x4278, 0x2b7d, 0x3966, 0xb06e, 0x1733, 0x465b, 0x4185, 0xb2f9, 0x42dd, 0xb246, 0xbff0, 0xb019, 0x42e4, 0xb259, 0x42a0, 0xba79, 0x18d5, 0x2f05, 0x4093, 0xbf7d, 0x2891, 0x423a, 0xb201, 0xb001, 0x1ce7, 0x1de1, 0xbafd, 0xb2b0, 0x42d6, 0x41f4, 0x206e, 0xb0b6, 0x3d9b, 0x3156, 0xbff0, 0x4483, 0x46d2, 0x41bb, 0x22a6, 0x413e, 0x4180, 0x3d93, 0x46f3, 0xb2e1, 0x4064, 0x42b4, 0xb279, 0xbf6c, 0x41fd, 0x42a1, 0x396b, 0x1383, 0x42ec, 0x1e32, 0x4564, 0x312c, 0x416f, 0x43be, 0x2633, 0x1e58, 0xb29b, 0x415a, 0x384f, 0x43ec, 0x428a, 0xbad6, 0x40bb, 0xba2d, 0xbf3b, 0x41fb, 0xb26f, 0x4208, 0x407e, 0xbacd, 0x1bb6, 0xbf6c, 0x435d, 0x41db, 0x2381, 0x402e, 0x4278, 0x40cb, 0xba40, 0xbac3, 0x3c20, 0x3b4a, 0x1a38, 0x437d, 0x4100, 0x40d5, 0xb0fd, 0x46c4, 0xbfdf, 0x4251, 0xba51, 0x42c7, 0x0106, 0x0ab7, 0x1cf3, 0xa039, 0x1e55, 0x42b5, 0x43c6, 0x4614, 0x409c, 0x4383, 0xbf76, 0x45f3, 0x01b8, 0xb045, 0xba1d, 0x4000, 0xbfe0, 0x41ac, 0x42e8, 0xb055, 0xa8af, 0x4413, 0x3917, 0xba4b, 0x42a6, 0xb097, 0x3a05, 0x410d, 0x43a3, 0xa1f5, 0xba69, 0x4164, 0x295b, 0xb0d1, 0x43d5, 0xbf6d, 0x0b74, 0x41c4, 0xba32, 0x1c4d, 0x40cf, 0x41c7, 0x4591, 0xa4d4, 0x4376, 0x131b, 0x42f1, 0x4308, 0x42a9, 0x1d4d, 0x41bc, 0x425f, 0x42b1, 0xacd1, 0xaa4f, 0x45de, 0x0d5f, 0x270c, 0x1e62, 0x4353, 0x4113, 0x2cc0, 0xa57c, 0x1eba, 0x4153, 0xbfa8, 0x413a, 0x41be, 0xbf2f, 0x22f9, 0xada0, 0x4171, 0xb2fc, 0x458e, 0x405a, 0x4119, 0x42a9, 0xae09, 0x435e, 0x1c93, 0xbf32, 0x1d79, 0x401d, 0x4098, 0xbfa0, 0x3409, 0x41d9, 0x46a1, 0xbfa3, 0x2470, 0x4583, 0x41be, 0xb295, 0x1aab, 0x319a, 0xbf97, 0x1b2d, 0x0205, 0x424c, 0x41a8, 0x433f, 0xbae9, 0xba25, 0xbfd0, 0x43ab, 0x401c, 0x2ddb, 0x3ade, 0x404a, 0x4203, 0xb2b9, 0x4559, 0xba40, 0xaa0b, 0x3a83, 0xa48e, 0x4288, 0x10f8, 0x40fd, 0xbfd2, 0xb09f, 0x4280, 0x40bf, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x3dfcb14d, 0x03113440, 0x43d4feab, 0xf4773a5f, 0x012ea932, 0x9dad9d54, 0xf24b822f, 0x4f0bd97c, 0x95955220, 0xe3991301, 0xb50130a1, 0xe8c03445, 0x19b5cca3, 0x0afb31b9, 0x00000000, 0x000001f0 }, + FinalRegs = new uint[] { 0x00000001, 0x0000000c, 0x3bae2320, 0x00000000, 0x00001a18, 0x0006677f, 0x54cd640e, 0x0000000c, 0x001bcc0d, 0x3bae26c4, 0x001c3ffd, 0x00000000, 0x001bcc0d, 0x3bae2377, 0x00000000, 0x600001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x1ba6, 0x4015, 0xb0f7, 0xba63, 0x1f03, 0xb220, 0x42c4, 0x1831, 0x2745, 0x42db, 0xb2af, 0x455f, 0x0c95, 0x2125, 0x454b, 0x3a14, 0x17d0, 0xb250, 0x4237, 0x1fb6, 0xbf79, 0x43dd, 0x4336, 0x407f, 0x1b4f, 0x305a, 0x4094, 0x3a18, 0xa357, 0xadef, 0xbaee, 0x3be9, 0x438a, 0xbf90, 0x4316, 0xba54, 0x40a1, 0x46e8, 0x1b13, 0xba2d, 0x4083, 0xbad4, 0xbf13, 0x1852, 0xb064, 0x1935, 0x4494, 0x4159, 0x4433, 0x2b74, 0xba35, 0x212c, 0x41a2, 0x41f0, 0x4078, 0xb24f, 0xb213, 0x4049, 0x439b, 0x1e45, 0x417c, 0xbff0, 0x17c1, 0x4304, 0x4363, 0xbf71, 0xb267, 0x41ef, 0x4213, 0x46f9, 0x1c2a, 0xba31, 0x40b6, 0xbfce, 0x43c3, 0x4341, 0x42f4, 0x4159, 0x42ea, 0x4233, 0x427f, 0xb2fa, 0x439b, 0x43b9, 0xba25, 0x1f55, 0x41bd, 0xbf8d, 0x3fdf, 0x40fd, 0x3e6e, 0xb219, 0x04ff, 0x2c3e, 0x44e2, 0x409c, 0x403c, 0x0d36, 0x441e, 0x01ff, 0x0059, 0x41db, 0x1af3, 0x0cd0, 0x1db2, 0x4384, 0x42bf, 0x1c2b, 0x2068, 0x4648, 0xb201, 0x3ab3, 0x380e, 0xba27, 0x4160, 0x0100, 0x1adc, 0xbf1c, 0x10ff, 0x0ce6, 0x4100, 0x4211, 0x4242, 0x417a, 0x419c, 0x4026, 0x4247, 0xb26e, 0x0b32, 0xb022, 0xbf82, 0xa679, 0x4307, 0xba4f, 0xbfce, 0x3af9, 0x1cf8, 0x0647, 0xbf59, 0xb225, 0x423b, 0xbff0, 0xba38, 0x3523, 0x3a16, 0x15c0, 0xb290, 0xa1f9, 0x4369, 0x422a, 0x42c8, 0x1897, 0xabe5, 0xbfc1, 0xb205, 0x22e3, 0x42cf, 0xb091, 0x30d5, 0xb244, 0x266b, 0x402b, 0x4076, 0x1cfb, 0x466d, 0xa4e3, 0xa5a0, 0x40f9, 0xb229, 0x433f, 0xb075, 0xb2b0, 0x4216, 0x4295, 0xbf4d, 0xb04e, 0x1ca6, 0x44e8, 0x4136, 0x42a2, 0x3f2b, 0x42a7, 0x425f, 0x4240, 0x46d2, 0x416b, 0x0590, 0x4118, 0xbae6, 0xb058, 0xae7a, 0xbfab, 0x19a7, 0xba3a, 0x15dc, 0x0445, 0x1dbf, 0x14eb, 0xbfb9, 0x4159, 0x0825, 0x40e2, 0x43a5, 0xb2dc, 0xb2c5, 0x4382, 0x4389, 0xb0c7, 0x4196, 0x2221, 0x4133, 0xba0e, 0xbfb0, 0xbf49, 0x1baa, 0x1239, 0xb2b2, 0x4078, 0x1b33, 0xba6b, 0x3c77, 0x412e, 0x397b, 0x1e15, 0xbfd2, 0xa6f7, 0xb23f, 0x3196, 0x43bf, 0x40a7, 0xab47, 0x43a0, 0xb028, 0xba0f, 0x4557, 0xa960, 0xb05a, 0x4004, 0x41b9, 0x41be, 0xbad8, 0xbf17, 0x42fb, 0x467f, 0x42fd, 0xba23, 0x1a1f, 0xb291, 0x1c6a, 0x4190, 0x23a3, 0x448b, 0x4047, 0x4284, 0xb2ab, 0xb052, 0x429a, 0x4108, 0x1114, 0x4498, 0x401c, 0xb24b, 0x415b, 0xb2e8, 0xbfb0, 0x45d9, 0xbadf, 0xbfb2, 0xbff0, 0x41d4, 0xb09a, 0x431a, 0x405b, 0x1d7d, 0x1d8c, 0x4388, 0x060c, 0x1f0b, 0xba03, 0x4167, 0xb2af, 0xb2e8, 0x4095, 0x1b2a, 0x4142, 0xb038, 0x1e28, 0x2d5a, 0xb007, 0x37ae, 0x432f, 0x43f9, 0x41f5, 0xbf72, 0x4377, 0x0585, 0xba7a, 0x2893, 0x3546, 0x1b6b, 0x4315, 0x3494, 0x4000, 0x4305, 0x3758, 0xbf05, 0x4298, 0x43ca, 0x4299, 0x0b40, 0x433c, 0xba6f, 0x41e3, 0x29c3, 0xa073, 0x176f, 0x41fa, 0x465e, 0x28ab, 0xbf04, 0x40f8, 0x4347, 0x4357, 0x4269, 0x43f7, 0x42f1, 0x41b9, 0x4373, 0x41ac, 0x419f, 0xb03a, 0x0ff1, 0xba12, 0x469d, 0x233d, 0x440a, 0xbac6, 0x43ca, 0x2379, 0x1332, 0x43d0, 0xb2cc, 0x4281, 0xbf0e, 0xb24d, 0xbf90, 0xb291, 0x4043, 0x2e1b, 0x02b8, 0xbf74, 0xb2ea, 0x4399, 0x408e, 0x42ed, 0x456a, 0x414d, 0xba4d, 0x1929, 0x039e, 0xb22d, 0x38ee, 0x408e, 0x40b8, 0xb20f, 0x410a, 0x4553, 0x189f, 0x110e, 0x17e1, 0x414a, 0x1ec4, 0x4499, 0xba68, 0xb060, 0x42c3, 0x4369, 0xbf0f, 0x3aec, 0x4157, 0x3771, 0x46cc, 0xb230, 0x4169, 0xb054, 0x10b7, 0x14f3, 0x33ae, 0x40cc, 0x1f4e, 0xba61, 0x4356, 0x422e, 0x4047, 0x409d, 0x41b5, 0x4193, 0x2017, 0xb013, 0x4559, 0xb20d, 0xba6c, 0x41db, 0xba06, 0x4167, 0x4252, 0xbf09, 0x4360, 0x188d, 0xb239, 0x4385, 0x413a, 0xb2e8, 0x4303, 0x0c1d, 0xbff0, 0xb2ea, 0xb06b, 0xba28, 0xb217, 0x4330, 0xa300, 0x16ab, 0x1053, 0x0c0c, 0x414b, 0x43dd, 0x0a6d, 0x428d, 0x4378, 0x1b7a, 0xbf80, 0xb265, 0xa0df, 0x4079, 0xbfbb, 0x3808, 0x1e91, 0xa711, 0xbaf8, 0x4665, 0x2c9b, 0x03fb, 0x2a72, 0x4186, 0x4052, 0x43ff, 0x407b, 0xa0ed, 0x4207, 0xbf70, 0x43d7, 0x1367, 0xb2b4, 0xb207, 0xa2b7, 0x0b98, 0xba72, 0xbf44, 0xb05e, 0x41db, 0x4231, 0xba15, 0xba3d, 0xbfe0, 0x3b3c, 0x434f, 0x1a8a, 0x4373, 0x437e, 0x19db, 0x45f0, 0xb291, 0xbfa8, 0x40c4, 0xbada, 0xb2a6, 0x410c, 0x2f9d, 0x4499, 0xb254, 0xb251, 0x4688, 0x36c7, 0xb28b, 0x3ca1, 0x2de1, 0x401d, 0xb233, 0xba5a, 0xb03a, 0xb2c0, 0x429c, 0x43b8, 0x40e6, 0xbfdc, 0x415f, 0x40c8, 0xb295, 0x46d0, 0x449a, 0xa9e5, 0x17e0, 0x4127, 0x4165, 0x4213, 0x4324, 0x3d81, 0x413b, 0x41c3, 0x1fb5, 0x4041, 0x1dce, 0xb2aa, 0xbaee, 0x2b6b, 0xa9ff, 0xbfaa, 0x1e69, 0xb056, 0xba20, 0xbaf9, 0x3584, 0x1ce5, 0x4170, 0xbfd6, 0x4029, 0x3811, 0x43c3, 0xba36, 0x4136, 0x0fcd, 0xa452, 0xa18a, 0x42f8, 0xb236, 0x46d1, 0x180f, 0x42b5, 0x43ac, 0xb2c3, 0xba0f, 0xb004, 0x4638, 0xb2bb, 0xba5f, 0xba19, 0xbf11, 0x4640, 0x1f23, 0xba63, 0xb2d6, 0x4159, 0xbf6d, 0x4242, 0x3570, 0xb2b6, 0x410c, 0x4243, 0x4340, 0x4347, 0x23c7, 0xb257, 0x1e21, 0xb2b1, 0x402c, 0x429e, 0xb2fc, 0x3a4c, 0xbfa0, 0x41bc, 0x4659, 0x02de, 0x40c0, 0xb21e, 0xbfd8, 0x40fb, 0x37f9, 0x17e4, 0xb0ab, 0x4144, 0x4158, 0x4182, 0xaaae, 0x400a, 0x41ac, 0x40ff, 0x4207, 0x414f, 0x41e8, 0xbf09, 0xb2c8, 0xb0a2, 0xb229, 0x41a3, 0x4210, 0xb20a, 0x11b2, 0x2fdf, 0xb242, 0x34cc, 0x430a, 0x4462, 0xb24f, 0x42ed, 0x3e95, 0x4214, 0xa49d, 0x1a98, 0x42ae, 0x407f, 0x1c46, 0xb260, 0x46d2, 0xbf38, 0x45a2, 0x4105, 0xb25a, 0x406b, 0x0e70, 0x1c2e, 0xbf6c, 0x42c7, 0x1e6b, 0x40f0, 0x1edc, 0x418f, 0x1c63, 0x4403, 0xaeec, 0x1d5e, 0xb289, 0xb20d, 0x0fbd, 0x40f0, 0xb0a7, 0xba3d, 0x43e8, 0xbfa4, 0x12ff, 0x437b, 0x43dc, 0xba54, 0x416d, 0x42b7, 0xb2e8, 0x1d0a, 0xbf60, 0x1ac0, 0x1972, 0xb07d, 0xba21, 0xbf5f, 0x45b1, 0xa40b, 0x42b3, 0x4008, 0x20cf, 0x184a, 0x41fe, 0x2f74, 0x4129, 0xba6e, 0x43f7, 0x438e, 0x459a, 0xba5f, 0x1d12, 0x1c06, 0xba3b, 0x4338, 0x4319, 0x412e, 0x14df, 0xbac7, 0xbf06, 0x0b43, 0xa015, 0x1998, 0x1ae9, 0xb251, 0x1b33, 0x29d5, 0x1def, 0x4306, 0x4170, 0x4674, 0x4036, 0xb29a, 0x415e, 0x4367, 0xbf36, 0xba3a, 0x400f, 0x1aab, 0x41d9, 0x0d1c, 0x4598, 0x44b5, 0xa285, 0x406f, 0xbf07, 0xba2b, 0xba21, 0x41ae, 0x4488, 0xbf81, 0x24c3, 0xbf00, 0x080d, 0x3f1d, 0x45c8, 0x01cb, 0xa168, 0xbf88, 0x1d8a, 0x4469, 0x0868, 0x1fb2, 0xb28d, 0x40c5, 0xb23b, 0xba1b, 0x4075, 0x43fd, 0x30ab, 0xb2d8, 0x3f10, 0x1c49, 0xab69, 0xbf27, 0x1415, 0x0643, 0x46f1, 0x1c17, 0xbad5, 0x4405, 0xa383, 0xa1ed, 0x420c, 0x1866, 0x41e3, 0x43c0, 0xba28, 0x428f, 0xb08f, 0xba7b, 0x4576, 0x0987, 0x4266, 0x42a8, 0xb278, 0xba0b, 0xbfdb, 0x1ae6, 0x19bc, 0x191d, 0x4019, 0x4358, 0x2895, 0x10c4, 0x33fe, 0x1eb2, 0x41bd, 0x42fd, 0x4230, 0x1c37, 0x445d, 0x1970, 0x420a, 0x4325, 0x4179, 0x4053, 0x0bb2, 0x4373, 0x3cd8, 0x40c0, 0x4194, 0xad38, 0xbf9e, 0x144c, 0x215a, 0x4100, 0xb29f, 0xbfe8, 0x146d, 0x1a76, 0x37cd, 0x2bc4, 0xba24, 0x4359, 0x0417, 0xbfcd, 0x0ffa, 0x4100, 0xbac8, 0x41c8, 0x350d, 0xb2d9, 0x41b3, 0xbf5f, 0xba5a, 0x1f92, 0x442f, 0x4342, 0xb2cc, 0x42f7, 0x42ec, 0xbfa0, 0xa018, 0x3c0a, 0x4650, 0x464a, 0x1d7b, 0xbf11, 0xba23, 0x421c, 0x42ea, 0xb23c, 0xb2cf, 0x131a, 0xbf8f, 0x00af, 0xb29d, 0xba50, 0xbaee, 0x4607, 0x4645, 0xb2a8, 0xa65e, 0xb0ae, 0x42c2, 0x4153, 0xbfb5, 0x1f5c, 0x2a64, 0xb04d, 0xb221, 0x2bcf, 0x4102, 0x4115, 0x4301, 0x1a80, 0x38b0, 0x41da, 0xbaf9, 0x42a2, 0xba22, 0x42dc, 0xba39, 0x41c0, 0x46bb, 0x3fbd, 0x1f57, 0x412f, 0x4150, 0x4319, 0xbf01, 0x4162, 0x4211, 0xba0b, 0x2ec4, 0x1345, 0x4336, 0x4064, 0x4622, 0x4372, 0x422b, 0x43b3, 0x427e, 0xb0f6, 0xbf46, 0x333c, 0x43d0, 0xb2fd, 0x4219, 0xb06a, 0x114c, 0xb287, 0x44e3, 0x41a7, 0xb089, 0x422e, 0x4059, 0xb066, 0xabef, 0x190a, 0x4263, 0x1dd3, 0x4209, 0xbf67, 0x42ca, 0x409b, 0x4286, 0xba45, 0x42a3, 0xbae1, 0x2947, 0x414f, 0x43b7, 0x0fc6, 0x4350, 0xbfab, 0x40e6, 0x45b6, 0x1dab, 0x4132, 0x4289, 0xb060, 0xbf97, 0x4212, 0xba0b, 0x068c, 0x4390, 0x4181, 0x03ac, 0x406a, 0x4044, 0xaa37, 0xb247, 0x46ab, 0x41dc, 0xa6bd, 0xbf6d, 0x4007, 0xb2fe, 0x40ee, 0x1047, 0x41d3, 0xb29b, 0x428d, 0x4275, 0x4392, 0xb2da, 0x40fa, 0xb0e0, 0xbf4c, 0xb0b4, 0x05a7, 0x414a, 0x1c39, 0xb2e9, 0x4080, 0x430f, 0x3d9b, 0x1ef4, 0x4167, 0xbf19, 0xb01b, 0x42dc, 0xb04b, 0x44cd, 0xb284, 0xb046, 0x3574, 0xb2c5, 0x41b5, 0xae2e, 0xb216, 0x1fe5, 0x10e6, 0xb2b8, 0x18a6, 0x4600, 0x4050, 0x406f, 0x420a, 0x41da, 0x43c4, 0xbf14, 0x202f, 0x43ea, 0x41e7, 0x4044, 0x3472, 0xb2f3, 0x42f8, 0x1937, 0x1ffd, 0xbf07, 0x420a, 0x0351, 0xbad2, 0xba49, 0x43be, 0x41eb, 0xbf70, 0x40b7, 0x4479, 0x0ed9, 0x4261, 0xb066, 0xb22d, 0x1b22, 0x4225, 0x1edd, 0x4180, 0x4090, 0x409c, 0x46ab, 0x413c, 0x3858, 0x4127, 0x42c6, 0xbfb0, 0xa6c1, 0xbfd4, 0x46e0, 0x423f, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xcb6e3399, 0x779d0a27, 0xb05aed8d, 0x68a1a047, 0x971f3c1b, 0xd982dff6, 0x911b2b51, 0x3af672e5, 0xc8b54557, 0xb853ba8e, 0x82d6c3bf, 0x67250d2b, 0x982aa4b0, 0x611a482f, 0x00000000, 0x900001f0 }, + FinalRegs = new uint[] { 0xffffffa8, 0x845c5046, 0x00000000, 0x00006500, 0x7ba3afba, 0x000064fd, 0x00001ae0, 0x00000000, 0x982aa4b0, 0x00000000, 0x1b016935, 0x000064fd, 0x982aa4b0, 0xffd0f718, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x1604, 0x0a21, 0x4319, 0x4564, 0xb217, 0x459a, 0x4428, 0xb20d, 0x1b2c, 0x2a6d, 0x403e, 0x410b, 0xba30, 0x1e1d, 0xbad8, 0x309a, 0xbf63, 0xbfa0, 0xb283, 0x1430, 0x468c, 0xb071, 0xb082, 0xb0c2, 0xbfa0, 0x1caf, 0xb278, 0x4270, 0x307d, 0xbacc, 0xb0d7, 0x40ec, 0xbacf, 0x33cb, 0xa1ac, 0x1b5a, 0xbf80, 0xb2e6, 0xba4b, 0x1e68, 0xbfe0, 0xb2dc, 0x4085, 0xbf7f, 0x2943, 0x1edf, 0x4346, 0x32fb, 0x410d, 0x44f4, 0x1e4f, 0xa16f, 0xb25d, 0x4348, 0xbf18, 0xbac6, 0x0b17, 0xbac6, 0xba0c, 0xb232, 0x3f9e, 0xb22f, 0xa529, 0xbfe0, 0x43b4, 0x2768, 0x427a, 0xb2fd, 0x408f, 0x4269, 0x4388, 0x42c0, 0x3a6c, 0xbf25, 0x4219, 0x4311, 0x1de4, 0x2564, 0x41c2, 0x1963, 0xb239, 0x42c3, 0x421f, 0xb0a4, 0x403f, 0x1c19, 0x1f37, 0x40f1, 0xbfc8, 0x40ac, 0x43aa, 0x0762, 0x4672, 0xb226, 0x3a12, 0x1aa7, 0xbad5, 0xb21a, 0x42ff, 0xbfdb, 0x4302, 0x1a52, 0x16ad, 0x42cd, 0x43fa, 0xb2f5, 0x413b, 0x411c, 0xb044, 0xb056, 0xa3cf, 0x40a9, 0x11c8, 0x455b, 0x41f6, 0xb04e, 0x3cea, 0x4257, 0x4251, 0x42e9, 0x2352, 0x3e76, 0x1aa5, 0x4086, 0x4355, 0x40e8, 0x3a84, 0x103c, 0xbfb5, 0x4227, 0xb07d, 0x1056, 0x4078, 0x4166, 0xba60, 0x1dd5, 0xbfdc, 0xb294, 0x4020, 0xa37a, 0x433d, 0xb2ab, 0xb284, 0x4223, 0xbf22, 0x43c2, 0x18f8, 0x4181, 0x40f5, 0xb2fb, 0x38ea, 0x4151, 0xac89, 0xbff0, 0x40a4, 0xa65b, 0x098e, 0x4020, 0x415c, 0x244e, 0x4034, 0x27e0, 0xbf94, 0xb2ec, 0xb0d0, 0x4255, 0xbf41, 0x22ee, 0x42c3, 0x139b, 0xa16a, 0xbfc0, 0x18b8, 0x4020, 0xb05e, 0xa1b8, 0xbad3, 0xbf2e, 0x460e, 0xbaf4, 0x23aa, 0x1c22, 0x34b3, 0xba2c, 0xa046, 0x223a, 0x4236, 0x1f81, 0x40e4, 0x43ea, 0x065e, 0x4251, 0x4266, 0x409b, 0x2271, 0x43be, 0x4026, 0x4394, 0x2c3b, 0x1dfa, 0xb287, 0xa1f2, 0xaed8, 0x4044, 0x42d9, 0xbf33, 0xb2db, 0xaaec, 0x1a26, 0xa731, 0x41be, 0x4203, 0xa536, 0x4663, 0x09c1, 0x4236, 0x1bcd, 0x234a, 0x3426, 0x420c, 0x43fa, 0x42e2, 0x1fcf, 0x43f1, 0x43ef, 0x4122, 0xab1c, 0x40cb, 0x41ac, 0x40ba, 0xb2f7, 0x46b0, 0xa3fb, 0xb037, 0x4583, 0xbf1e, 0x40a3, 0x4005, 0xafe0, 0xba7e, 0x42a9, 0x38ab, 0xb2b3, 0x43bd, 0x43c4, 0x1ca1, 0x4208, 0x410d, 0x4319, 0xba00, 0x43a2, 0xbae2, 0x194f, 0x316c, 0xba00, 0xbfc1, 0x41af, 0x40eb, 0x2d1e, 0x0f59, 0xba37, 0x40ec, 0x454f, 0x23a9, 0x0eb7, 0x4130, 0x410c, 0x4273, 0xba56, 0xa4e0, 0x42d8, 0x439b, 0xa5a8, 0x43e4, 0x41ab, 0x463e, 0x4097, 0x407f, 0xbf83, 0x4203, 0x0ecb, 0x41d5, 0x423b, 0x413e, 0x41aa, 0xbf5f, 0x14be, 0x40f8, 0xbada, 0xb294, 0x1a4d, 0x41c1, 0x120e, 0x424b, 0x1ac7, 0x42e8, 0x2300, 0x4386, 0x46f2, 0x41f1, 0x413f, 0x4078, 0x4193, 0x42b0, 0x07d0, 0x194c, 0x4647, 0x4216, 0x3906, 0x1e78, 0x4038, 0x045d, 0xb2be, 0x2603, 0x416d, 0xbf4b, 0xb26e, 0x403f, 0x45c3, 0x0349, 0xba7a, 0x4186, 0x4438, 0xb078, 0xbfe0, 0x4314, 0xb236, 0xb0fc, 0x4467, 0x420b, 0x407c, 0x43d2, 0x4252, 0xba6b, 0xbadc, 0xbf4c, 0x4021, 0xba6d, 0xb2c7, 0x1f82, 0xb07b, 0x1706, 0xbf1c, 0xbfd0, 0x1d27, 0x4176, 0xb284, 0x380c, 0x4642, 0x4607, 0xb0cc, 0x44d9, 0xb2a7, 0x40b5, 0x42c2, 0xb2d6, 0xba22, 0x1ae8, 0x28fa, 0x4030, 0x0012, 0x19af, 0x4545, 0x407f, 0x43fd, 0x4603, 0xbfd6, 0x41de, 0x1ede, 0xba4c, 0x41a1, 0x41c3, 0x4371, 0x408e, 0x41bc, 0x42cd, 0x42be, 0x4172, 0xab14, 0x4068, 0xa34b, 0x1bfc, 0x1929, 0xb2a6, 0xbf76, 0xb2eb, 0x410c, 0x1fa5, 0x4241, 0x469a, 0x1eec, 0x408b, 0xbf15, 0x4304, 0x464f, 0x1424, 0x431e, 0xbf66, 0xa1af, 0xa8a3, 0xba6d, 0xb2c3, 0xba52, 0x0e61, 0x40a3, 0x4348, 0xbaf0, 0x412c, 0x1aae, 0x42a3, 0x1da6, 0x4404, 0x424c, 0x336d, 0x4044, 0x40bc, 0xb2cb, 0x3f79, 0x416e, 0xbf5f, 0x2954, 0xba01, 0xba05, 0xaef8, 0x1e85, 0x4035, 0x41c5, 0xb062, 0x1500, 0x4008, 0xbf85, 0x0e11, 0x279e, 0x4125, 0x42c2, 0xbac4, 0x438a, 0x46f2, 0x0210, 0xba56, 0xba5b, 0x11f8, 0xba09, 0xbf5f, 0xb2bd, 0x1e57, 0x3541, 0xba26, 0x1e26, 0x10de, 0x20bb, 0xbf90, 0xbf3c, 0x4182, 0x43cc, 0x40cb, 0x4275, 0x4150, 0x4164, 0x41c0, 0x42d8, 0x40c8, 0x3c64, 0xa413, 0xa6e0, 0x18fa, 0xa514, 0x464f, 0x40f1, 0x43a0, 0x1131, 0x4067, 0x4017, 0x0294, 0xb2f3, 0x4159, 0xba10, 0x3e05, 0xbf8b, 0x042f, 0x431c, 0x40c7, 0xba5a, 0x1cfe, 0x40d2, 0x4178, 0xb2ad, 0x2175, 0x41df, 0x226c, 0x426e, 0xb0a0, 0x4081, 0x42ec, 0x4035, 0x40b2, 0xbfd7, 0x4165, 0x4419, 0x43cd, 0x46ba, 0xbfc0, 0x4384, 0x0cc7, 0x1e7a, 0x466a, 0x04ed, 0x0fdc, 0x42e2, 0x42a3, 0x2874, 0x42df, 0xb2e6, 0x223a, 0x3d2a, 0x349c, 0x41b0, 0x1fb5, 0x2ef3, 0xbfa3, 0x14e3, 0xbac0, 0x4357, 0xb2a5, 0xba2a, 0x4161, 0x4218, 0xb0b3, 0x4110, 0x15d7, 0x412e, 0xbfaf, 0xb2bb, 0x1930, 0xba71, 0x1dc0, 0xbf53, 0x4363, 0x4237, 0x206b, 0x1a83, 0x1f94, 0x41fb, 0x4071, 0x4632, 0x41ca, 0xb000, 0x2298, 0x4191, 0xa626, 0xb215, 0x24be, 0x40b3, 0x02a0, 0xb209, 0x2ff4, 0x0d5d, 0x43ef, 0x21e6, 0x1915, 0x401f, 0xbf84, 0x461f, 0x4244, 0x421c, 0xba26, 0x43f2, 0xba11, 0x42b4, 0x17d4, 0x43e7, 0x072e, 0x41a1, 0xaee1, 0x4090, 0xb20e, 0x40e9, 0xb207, 0x1dd0, 0x1cc6, 0xbfb1, 0xb045, 0x2731, 0x4271, 0xbade, 0xb217, 0x403f, 0x35ea, 0x1eea, 0xaa77, 0x2632, 0x19c7, 0xba1a, 0x433f, 0x439e, 0xbf15, 0x1a25, 0x40b9, 0x14f0, 0x3dd4, 0x3c2a, 0x3be5, 0x43c9, 0x40e1, 0x435d, 0x41eb, 0xb236, 0x46ec, 0x1701, 0x403b, 0xbaee, 0xb030, 0x43d4, 0xa1a0, 0x0433, 0xb0ed, 0xbaee, 0xbf47, 0xb25d, 0x410c, 0x41e2, 0x1d4a, 0x1fa0, 0x3d98, 0x1f8f, 0x3a41, 0x41c6, 0x456d, 0x04a1, 0xb2ca, 0x1d5e, 0x40ca, 0xa917, 0x40d1, 0xa2c5, 0x428f, 0x225d, 0x11fe, 0x42d3, 0xbfd9, 0xb2d2, 0x1ca9, 0xafda, 0xbf80, 0xb252, 0x2a1a, 0xbaf3, 0x430a, 0xb244, 0x1c06, 0xa3c7, 0x1c7b, 0x407f, 0xba7a, 0xb2c9, 0x43af, 0x4266, 0xba74, 0xbfbd, 0x1822, 0xba00, 0xb218, 0xb23a, 0x42da, 0x1ec2, 0x412b, 0xb257, 0x2422, 0xbf68, 0xbac7, 0x43fa, 0x1072, 0x4102, 0xb2f7, 0xba43, 0x4303, 0xbac6, 0xb296, 0x1cd6, 0x4379, 0x180e, 0x3654, 0x432f, 0xbf80, 0x228d, 0x03ca, 0x43d0, 0x43b1, 0x2ddd, 0x41fa, 0x35b8, 0x35fe, 0x4212, 0x3dc5, 0xbf59, 0x446f, 0xba34, 0xb234, 0x45c0, 0x461c, 0x1c69, 0xa91c, 0xbfab, 0x43e0, 0x098b, 0x4052, 0x4389, 0xb2b5, 0x1ed5, 0x4129, 0x0b1b, 0x2abf, 0x1b81, 0x32cd, 0x431f, 0xbf60, 0x1adb, 0x42fb, 0x40f0, 0x43d0, 0xb272, 0x4041, 0x421b, 0x43f7, 0xbf2c, 0x46a2, 0x43d8, 0x43bd, 0x439c, 0xba01, 0x3e66, 0x19dc, 0xbf28, 0xb04a, 0x4286, 0x429d, 0x25ec, 0x4381, 0xba19, 0xb2f8, 0x4042, 0x1f44, 0x1ea0, 0x42dc, 0x438d, 0x40f1, 0x1f2f, 0xba50, 0x4311, 0xa700, 0xb0ed, 0x1c9b, 0xb0c5, 0x2742, 0x431c, 0xbf1e, 0x409e, 0xaf81, 0xba68, 0x4364, 0x4078, 0x0ac1, 0xab14, 0xbaec, 0x4499, 0x0a77, 0x0594, 0x2e04, 0xba67, 0x2c60, 0x41c8, 0x443e, 0x4273, 0xb00f, 0x41b1, 0x43b1, 0x1f25, 0xbf34, 0x4282, 0xb2fc, 0xbf44, 0x400d, 0xb275, 0xaf07, 0xb06d, 0x44db, 0x12f2, 0x4082, 0x0b28, 0xb0ac, 0xbfc0, 0xb2ca, 0xb2cb, 0x4462, 0x45b6, 0x4328, 0xb24a, 0x2a77, 0x425e, 0xbf78, 0xb24c, 0x43f5, 0x17b8, 0x2ab7, 0xb285, 0x40b9, 0xa7d7, 0x45e4, 0xb0e1, 0x4395, 0x4020, 0x2b72, 0x4200, 0xb069, 0x40db, 0x41af, 0x42dc, 0x41c6, 0x16a0, 0xb268, 0xbf68, 0xb0e3, 0x421b, 0x4208, 0xa3e3, 0x3cd0, 0xbf54, 0x3d55, 0xba7c, 0x4399, 0xb213, 0x43d0, 0x160a, 0xbfe4, 0x4659, 0x4327, 0x107b, 0xb2f7, 0x42ea, 0xbf00, 0x43d4, 0x2fa0, 0x41ad, 0x195b, 0xb2c5, 0x05dc, 0x43c7, 0x4074, 0x426e, 0xb084, 0x4354, 0x1993, 0xbf44, 0x4226, 0x118b, 0x0a67, 0xbaca, 0x43ea, 0x2159, 0x416e, 0x190e, 0x0629, 0xbf68, 0x4155, 0x41f9, 0x2408, 0xbf2c, 0x1a7a, 0x4137, 0x17b6, 0x4092, 0xb039, 0x404c, 0x4324, 0x40de, 0x40df, 0xbf35, 0x400e, 0x1853, 0x4246, 0x3887, 0xb2b8, 0x44b3, 0x0cc0, 0x294e, 0x0158, 0xb24b, 0xba1c, 0x1f56, 0x0aff, 0xb298, 0xbf69, 0x32c4, 0x44ad, 0x1804, 0x4189, 0x40ff, 0x1993, 0xada4, 0x1f03, 0xbaec, 0x43ed, 0x40e9, 0x1957, 0x40af, 0x46dc, 0xad97, 0x4368, 0x4271, 0x4156, 0xbf52, 0x41ba, 0x4348, 0x4550, 0x0bf9, 0x4039, 0x4692, 0x2bed, 0x40d5, 0xba49, 0x0c78, 0x1ba4, 0xba44, 0x0087, 0x4045, 0x42fe, 0xb299, 0xba7b, 0xbf86, 0x46e9, 0x1a43, 0x0677, 0x3bf7, 0x445e, 0x1c2d, 0x4208, 0x43e0, 0x18ff, 0x1077, 0x2711, 0x425a, 0xba76, 0xb04f, 0xb2a9, 0x1a42, 0x449a, 0xb2e8, 0x4019, 0x1195, 0x42c2, 0xb208, 0x449c, 0xbacc, 0xba2f, 0xbf90, 0xbf44, 0xbacb, 0xbaeb, 0x1d0c, 0x4119, 0xb2e4, 0xb209, 0x45c3, 0x4580, 0xba39, 0x41f0, 0xbf7b, 0xb239, 0x42ed, 0x412f, 0x3276, 0xb0e3, 0xb285, 0x146e, 0xb064, 0xa6c5, 0xbf12, 0x4037, 0xbaf9, 0xb2ba, 0x4345, 0x156c, 0x404b, 0x407a, 0x1c20, 0x44f2, 0xba73, 0x404e, 0x4338, 0x2e25, 0xba5e, 0x1b5b, 0xb2f5, 0x4247, 0xbf5a, 0xa67b, 0xb257, 0x1c8e, 0x2226, 0x43d4, 0xb045, 0x4197, 0x41f6, 0x08df, 0xbf5c, 0x42b0, 0x4170, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x51ade2cc, 0xec524c8c, 0x38eff947, 0x36d7c854, 0xe5629cca, 0xa8d08a62, 0x7a884da5, 0xfbdda384, 0xdb38fbe9, 0xf6976dfd, 0x005fd422, 0x93c6813c, 0xb8f2dcc9, 0x6cfd2716, 0x00000000, 0x500001f0 }, + FinalRegs = new uint[] { 0x40ffffff, 0xffffffff, 0x00000026, 0xff767516, 0xffffffd9, 0x000000b4, 0x80000000, 0x1feecea2, 0x6cfd1de1, 0xf75b1a7b, 0xffffff09, 0x278d022c, 0x278d0135, 0x6cfd2fc6, 0x00000000, 0x900001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xa6c2, 0x4488, 0xb2d1, 0xb06a, 0x4083, 0x44ca, 0xba42, 0x1d14, 0x420d, 0xbade, 0x4103, 0x40f9, 0x4003, 0xb0d9, 0x4552, 0xbfcc, 0xa2db, 0xa663, 0xba1e, 0x2e6c, 0x43d1, 0x407f, 0x401a, 0xb2c1, 0x4631, 0x0681, 0xa7bb, 0x4649, 0xb05b, 0xbfe1, 0x28a0, 0x1af0, 0x16fa, 0x45c8, 0xab5a, 0x4399, 0x4423, 0x1bdd, 0xb044, 0x2499, 0x4440, 0x3ee0, 0xb02f, 0x0ddd, 0x4127, 0xbf08, 0xba20, 0xba1c, 0x03ac, 0xb0f7, 0x40e5, 0x4116, 0x4116, 0x468a, 0x422d, 0xb2d9, 0x43c9, 0x41ea, 0x4054, 0xbace, 0x437c, 0xbf91, 0x40a4, 0xb247, 0x18af, 0x16e1, 0xb255, 0xbade, 0xb0a3, 0x1f17, 0xbf47, 0xb0be, 0x1037, 0xb2b8, 0x175f, 0x18fc, 0xb286, 0x41bd, 0x46f3, 0x42b4, 0x0b47, 0x4359, 0x18c2, 0xadd5, 0xbf2c, 0x1c76, 0xba76, 0x4132, 0xba48, 0x43f4, 0x168c, 0x416e, 0xbfab, 0x4194, 0x1bc0, 0x28da, 0x42c5, 0x4423, 0x41ff, 0xb2ff, 0xb008, 0x20d3, 0x437e, 0x4034, 0xba40, 0x425e, 0xa06c, 0xba68, 0xba72, 0x00eb, 0x41b2, 0x411e, 0x1b3d, 0x2f08, 0xb089, 0x411a, 0x14ff, 0xb2be, 0x4633, 0xbf7d, 0x4150, 0x40c2, 0xb28a, 0x1e72, 0xba3d, 0x0aae, 0x435e, 0x205c, 0xb067, 0x4353, 0x21c0, 0x43e1, 0x4141, 0x0572, 0x43ae, 0x427c, 0x42ae, 0x4026, 0x2df2, 0x405c, 0x37ec, 0x4074, 0x1559, 0xb2e7, 0xbfe0, 0xb2b4, 0x41cb, 0x0d6c, 0x433a, 0xbf64, 0x1d3e, 0xb2f3, 0xba2e, 0x4020, 0xba4f, 0xb2b6, 0x43ff, 0xa82c, 0x1929, 0x421e, 0x18af, 0x4415, 0xbf33, 0x4698, 0x1b4a, 0x27e0, 0x40f2, 0xb25e, 0xba7b, 0xb26a, 0x4309, 0x4064, 0x402b, 0x4098, 0xb245, 0x43bd, 0x0c9f, 0x42de, 0x015f, 0x4321, 0x1a23, 0xb28e, 0xba29, 0x406b, 0x1998, 0x4265, 0xbf38, 0x4314, 0xb2b6, 0x165c, 0xbafc, 0xbf70, 0xb048, 0xba43, 0x46bb, 0x4001, 0x0cb8, 0x43fb, 0x270f, 0xb2fc, 0x0ae9, 0x4223, 0x423f, 0x26fd, 0x419e, 0xa390, 0xb0ff, 0xb2cf, 0x0bc0, 0xbf4b, 0x4135, 0x4559, 0xb2bf, 0xba2e, 0xa3e9, 0x4310, 0xba44, 0x43ff, 0x4394, 0x401c, 0x41f6, 0x42d0, 0xbf84, 0xa7d9, 0x42c0, 0x0a4b, 0x4285, 0x42a0, 0xb040, 0x1ba8, 0xa9fa, 0xba04, 0x358a, 0x4003, 0x46d4, 0x41fe, 0xba48, 0x2c12, 0x41a3, 0x4161, 0x4372, 0x448b, 0xbf98, 0x0627, 0xb23c, 0x4170, 0x0a61, 0xb279, 0x4260, 0x06b6, 0x3f4f, 0x4319, 0x40a8, 0x24ea, 0xbfe8, 0x412c, 0x3af2, 0x415d, 0x42f8, 0x27fc, 0xb04c, 0x2615, 0x432f, 0x447e, 0xb0b6, 0xb0e8, 0xbf15, 0x4034, 0xbadf, 0xba1e, 0x43d6, 0x00f1, 0x2101, 0xb229, 0x270d, 0x0d8f, 0x1c5a, 0x3665, 0x4364, 0xbad0, 0x435f, 0x1ae3, 0x20f4, 0x42f6, 0x4606, 0x38db, 0x1391, 0x42ee, 0x4550, 0xb233, 0xa680, 0x4310, 0x4005, 0x41a2, 0x42dc, 0x43e8, 0xbf0e, 0xb2ad, 0xa2f4, 0x1508, 0xae86, 0x4320, 0xbf55, 0x2d19, 0x43ef, 0x42db, 0xb0a9, 0x3556, 0xb01e, 0x4176, 0x43b8, 0x4462, 0x077f, 0xba0b, 0x42d6, 0xb2c2, 0x4336, 0x4384, 0xb2bf, 0x4002, 0x4262, 0x1f32, 0x46db, 0x4239, 0xb200, 0x1f3c, 0x4167, 0xb23b, 0xb0d4, 0x43cd, 0x0623, 0xbfb5, 0x2020, 0x1c56, 0x1cf6, 0x433f, 0x43b3, 0x429e, 0x41e3, 0xb2aa, 0x43fd, 0x1cac, 0x3d18, 0x4077, 0x411f, 0x4058, 0x43dd, 0xb244, 0x41ee, 0x4268, 0x2fa0, 0x243a, 0xb00a, 0xb2e4, 0x3b24, 0xa3df, 0xbfa6, 0x42c4, 0xb072, 0x1cbb, 0x4090, 0xbf9f, 0xba2a, 0x3835, 0xb27c, 0x4217, 0x4283, 0xb232, 0xbf1c, 0x43c9, 0xb2a2, 0xb2a3, 0x4194, 0xb25c, 0xbf92, 0x4085, 0x4278, 0x4073, 0x40b4, 0x423c, 0x1d1a, 0xbac1, 0x43b0, 0x4330, 0x1c88, 0xb210, 0x2e6a, 0x4018, 0x41ac, 0x0ac5, 0xb2ea, 0x4569, 0x141c, 0xbf8b, 0xbf70, 0x42ab, 0x4031, 0xbacb, 0x41ec, 0x4019, 0x3670, 0x2cf6, 0x401f, 0x26a1, 0x41eb, 0xb23f, 0xb221, 0x4671, 0xb2bb, 0xbf60, 0x363e, 0x420b, 0xab50, 0xbf77, 0x1e0f, 0xae4e, 0xb025, 0xba19, 0x1f73, 0x37a5, 0x1c37, 0xbf90, 0xbf93, 0xb058, 0x1c46, 0x1c5d, 0x19a0, 0x0b41, 0xbfa0, 0xb2ae, 0x1f46, 0xb237, 0x14c9, 0x405c, 0x2f26, 0x46e8, 0x41a5, 0x46b0, 0xb22d, 0xb03f, 0x1fc2, 0x3b4f, 0xbfa6, 0x1faa, 0x432c, 0x19ea, 0x40f5, 0x4246, 0x1f94, 0x4385, 0xa1df, 0x38f5, 0x049a, 0x455f, 0xa132, 0x08ee, 0x1c37, 0xbfa0, 0x1e5e, 0x401a, 0x43ac, 0x402c, 0x4222, 0x3931, 0x2124, 0x44aa, 0x42a8, 0xb0f0, 0xa8c8, 0x1e5e, 0xa92c, 0xbf65, 0x4045, 0xbaeb, 0xba57, 0x44ad, 0xac8a, 0x420b, 0x353f, 0xb23e, 0xa0ff, 0x1872, 0x41cb, 0xb22e, 0xb0cc, 0x4213, 0x40e3, 0x0aef, 0x411c, 0xb0bc, 0x1d52, 0xa778, 0xb0d0, 0xbf56, 0x43d1, 0x42f4, 0x1d5b, 0x1dc2, 0x0f45, 0x4381, 0xb214, 0xb09f, 0x4175, 0x4680, 0x24ea, 0xb231, 0xb264, 0xa2ed, 0xbace, 0x181b, 0xbf08, 0x42a5, 0xa7c2, 0x4018, 0xbfbf, 0xb2b2, 0x4309, 0x4264, 0x02c3, 0xa5f0, 0x1d34, 0xb209, 0x4090, 0x3005, 0x431d, 0xb223, 0x434e, 0x0cec, 0x0970, 0x43d2, 0x402e, 0x403d, 0x1385, 0x1eac, 0x1fe2, 0x427b, 0x0159, 0x0702, 0x0390, 0xbf6b, 0x40e8, 0x403d, 0xa17c, 0x2fb8, 0x41ec, 0x4112, 0xba4c, 0x2c3a, 0xaecc, 0x458a, 0x435a, 0x1950, 0x429d, 0x1d8e, 0xaf4e, 0x36e9, 0xb205, 0xb0d3, 0x4394, 0x4489, 0x425e, 0x01cf, 0xbae3, 0x4131, 0xbae9, 0x4001, 0x0f9b, 0x431c, 0xbf91, 0xabc7, 0x40b6, 0xba5c, 0xa210, 0xbaf8, 0x0f46, 0x43d3, 0x3404, 0xaaea, 0x430e, 0x4248, 0x1d7e, 0x42e1, 0x41ff, 0x011a, 0xb230, 0x4037, 0x0e79, 0x3a4a, 0xb0d9, 0xbf97, 0xb21f, 0xafa8, 0xa731, 0x4185, 0x1b7d, 0xb095, 0x4156, 0xb2cb, 0x14d5, 0x41e0, 0x40b7, 0x31e8, 0x42ee, 0x2060, 0x3a39, 0x419b, 0x40a7, 0x1e06, 0x39bc, 0x1930, 0x4238, 0x412b, 0x43e6, 0xbfcb, 0xbaf1, 0x3518, 0x27a0, 0x42d2, 0xb2a4, 0xbfdc, 0x4293, 0x4337, 0xae16, 0x4429, 0x4050, 0xb079, 0x4031, 0x1638, 0x420b, 0x1f17, 0x43a4, 0x1bf4, 0x433b, 0x178f, 0x4060, 0xbf7b, 0x4339, 0x43d1, 0x40c6, 0xbaea, 0x154c, 0x4247, 0x412a, 0x41a0, 0x41d1, 0x4260, 0xbacf, 0x20af, 0x4285, 0x126a, 0xbf44, 0xbfb0, 0x4425, 0xb22f, 0x1a6f, 0x427c, 0x4196, 0x4367, 0x4213, 0x1620, 0xbf7b, 0x4565, 0x32f9, 0x40b2, 0x395c, 0x4105, 0xbf70, 0xbfb5, 0x412d, 0x409e, 0x180e, 0x1a80, 0x4136, 0x27e3, 0xbae3, 0x1c3a, 0xb213, 0x1301, 0xb2fb, 0xb098, 0xbfd0, 0x324c, 0x2876, 0xab33, 0x1c23, 0x248e, 0xb256, 0x1dd9, 0x0d3e, 0x01e8, 0x403b, 0x40fa, 0x41a4, 0x41f6, 0xbf69, 0x4371, 0x40dd, 0x46a1, 0x402c, 0x41e8, 0xba49, 0x40d7, 0x4605, 0x1aa1, 0x37d2, 0xba62, 0xbf61, 0xb28b, 0x4136, 0x36ee, 0x40fc, 0x3ead, 0x412c, 0x43aa, 0x233d, 0x3ac3, 0x421a, 0x4117, 0x091d, 0xb2c9, 0x163a, 0x408b, 0x4273, 0x141e, 0xa5b2, 0x40da, 0x4082, 0xbfd8, 0x4080, 0x4347, 0x1830, 0x414c, 0xbafe, 0xbf07, 0x21d3, 0x435b, 0x415c, 0xbf00, 0xb285, 0x0453, 0x1b3f, 0x383a, 0x40db, 0x36f2, 0xba4d, 0x4128, 0xbf72, 0x428f, 0xa6c7, 0x19c1, 0x2878, 0x1f51, 0xba30, 0xbae9, 0x4346, 0x43c2, 0x0c82, 0x1d23, 0x466b, 0x40e0, 0xba10, 0xac8c, 0x1ff2, 0x1adf, 0x4356, 0x401c, 0xbaf8, 0xac9d, 0xb000, 0x41e9, 0xb297, 0x4155, 0xbfdb, 0xb04e, 0xb281, 0x43a2, 0x428c, 0xb2d8, 0x42bb, 0x2cfb, 0xb2de, 0x1fc8, 0x4301, 0x45f2, 0x1ee3, 0x04c6, 0xba61, 0x4002, 0x0798, 0xb29a, 0x433d, 0x1e61, 0x42fb, 0xbf56, 0x411f, 0xb0ed, 0xbf90, 0x456d, 0x1437, 0x413c, 0x4126, 0x42ca, 0xbad4, 0x1917, 0x4350, 0xbae2, 0x4135, 0x0247, 0x418b, 0x151d, 0x173d, 0x4054, 0xb284, 0xb22f, 0x4296, 0xb28f, 0x2e6c, 0xb2c0, 0x1f1c, 0xbfa9, 0x0906, 0xba7e, 0x1b04, 0xba58, 0xbaee, 0x4000, 0xb200, 0xb0d5, 0x414f, 0x14ff, 0xb0a0, 0x4220, 0x4249, 0xba6a, 0x4187, 0xbf53, 0x43bd, 0x1de3, 0x467d, 0x3a73, 0x18c3, 0x4263, 0x42cd, 0x42ec, 0x44b5, 0x089b, 0x1ce1, 0x18d2, 0x41d3, 0x3636, 0x1de6, 0xba4c, 0xb08d, 0x1b88, 0x1d9e, 0x3a85, 0xbfd3, 0xb051, 0xb2cd, 0x1c2b, 0xb07a, 0xb261, 0x07f4, 0x3ffa, 0xb0a1, 0x42a9, 0x40f3, 0xba2c, 0x40d5, 0xb026, 0x08bd, 0x415d, 0x1e67, 0x4377, 0x43c9, 0x1d0c, 0xb278, 0x437e, 0x4159, 0x456c, 0xbf90, 0x4191, 0x428e, 0xbfa9, 0x2e5f, 0x43af, 0x41d7, 0xb259, 0x42e0, 0x4221, 0xb2d4, 0xb07e, 0x4186, 0xbf80, 0x40a3, 0x45bd, 0x416a, 0x4324, 0x4068, 0x3029, 0x43dc, 0x41d1, 0xa79d, 0xbf1d, 0x2443, 0xba2b, 0x4111, 0x1a70, 0xa102, 0x43a3, 0xb0c8, 0xba62, 0x41b6, 0x41b6, 0x09a5, 0x41a6, 0x427a, 0x3e4d, 0x419e, 0x062f, 0xb24e, 0x1110, 0x41c9, 0x41ad, 0x2a40, 0xbf6b, 0x4279, 0x4347, 0xb242, 0xb2b0, 0x097c, 0x460e, 0x1e67, 0x4144, 0xa11e, 0x1ee4, 0x351d, 0xb247, 0x43f5, 0x1a26, 0x4357, 0x41fa, 0x40cb, 0x41ac, 0xba63, 0x1d28, 0xba76, 0x28e1, 0x38e7, 0x4098, 0x42b3, 0xbfbd, 0xb2bc, 0xbadc, 0xbad0, 0xba1a, 0x40fd, 0x42ca, 0x40ad, 0x42d4, 0xb251, 0xb2f2, 0xb2fd, 0x4138, 0x1b82, 0xba23, 0x1efb, 0x11d5, 0x413c, 0xb273, 0x1ce6, 0xa2a1, 0xbf79, 0x4197, 0x43e3, 0xbae9, 0x4015, 0x43e9, 0xa21c, 0xb0d0, 0x4059, 0x408f, 0x4067, 0x4387, 0xb2f2, 0x461b, 0x4019, 0x40a4, 0x4619, 0x41f1, 0xb04c, 0x46ac, 0x43d7, 0xa438, 0x4108, 0x0e03, 0xba59, 0x41e4, 0x43db, 0xb2e0, 0xb2b8, 0x1f88, 0xbf55, 0x432d, 0xb223, 0x09e7, 0xaea6, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x76fb2e83, 0x2c9a97ce, 0x4afe2dc9, 0x2a8a6d29, 0xf34a1d2a, 0xe92c22a4, 0xcd859d8b, 0xab9291ff, 0xdca83fbb, 0x24a63f86, 0xf4d326a0, 0xc55ab8ca, 0x50c313fb, 0x47bdd172, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0xfffffffa, 0x00000000, 0x00000003, 0x0000018a, 0x4000018a, 0xff91f802, 0x47bdd252, 0xfffffffc, 0x000017c0, 0x24a35e06, 0x20022b04, 0x47bdd732, 0xff91f802, 0x47bdcfba, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xb247, 0xbac6, 0xb2bc, 0xba00, 0x4236, 0x4542, 0xbf84, 0x4353, 0x454b, 0x2d7d, 0xb214, 0xbf73, 0x2346, 0x1da1, 0xa600, 0x41de, 0xb0cf, 0x45c5, 0xb057, 0xbf11, 0x43f5, 0x4171, 0x026e, 0x2a0c, 0xafe4, 0x4215, 0xba19, 0x3837, 0x4677, 0x428a, 0xb281, 0xb2e1, 0x43af, 0x427f, 0xbf45, 0x41ef, 0xb289, 0x2c54, 0x4128, 0x4076, 0xb2d4, 0x41c2, 0x191b, 0x438c, 0xa81f, 0xb075, 0xb2e1, 0xbfb9, 0x4396, 0x2ac3, 0x4052, 0x4453, 0xb2c7, 0x40b6, 0x4273, 0x40b6, 0x424e, 0x449d, 0x0849, 0x42c1, 0xa3b4, 0x2413, 0x1e42, 0x40c1, 0x41f8, 0x1e05, 0xbf6e, 0xbae1, 0x1e62, 0x445c, 0x3418, 0x4363, 0x4094, 0x445c, 0x4283, 0x4611, 0xa556, 0x42fb, 0x1f16, 0x43f5, 0x1a7a, 0x020f, 0xba79, 0xbf24, 0x44cb, 0x0902, 0x4376, 0x19ed, 0x4181, 0x41b4, 0xb222, 0x4391, 0xbfcd, 0x05b5, 0x1fa3, 0x422c, 0xb210, 0x18d5, 0xbf70, 0x40c1, 0x42b9, 0x107f, 0xb281, 0xa90c, 0xbfba, 0x4301, 0x4282, 0xb254, 0x2d6a, 0x42d5, 0x42c1, 0x3ff4, 0xb09d, 0x0856, 0x41a4, 0x4357, 0x42d8, 0xbfd9, 0x3dcf, 0xba42, 0x206a, 0x4168, 0x1f5e, 0x460a, 0x0f10, 0xa577, 0x456c, 0x3967, 0x4643, 0xbf4e, 0x4183, 0x36fb, 0x41ea, 0xb0dc, 0x32d3, 0x43ee, 0x31b0, 0x4030, 0x407c, 0xb22d, 0x4455, 0x3342, 0xb0d7, 0xbf83, 0x1c18, 0x35e6, 0xb063, 0x4315, 0x45ec, 0x1f25, 0x18e9, 0xb264, 0xa7c2, 0x3e41, 0x43b8, 0x3363, 0x0c12, 0x0d27, 0x3e41, 0xbfb0, 0xba45, 0xa29a, 0xabaa, 0x3d44, 0xb2f5, 0xa599, 0x3ed6, 0x423f, 0x45da, 0x28ca, 0x40aa, 0x4288, 0x2acc, 0xbfc6, 0x21e5, 0x41a9, 0x4140, 0x4236, 0x0189, 0xb21a, 0x197d, 0xbf93, 0x13b7, 0x44a5, 0x3404, 0x443b, 0x421b, 0xba46, 0xba41, 0xb2d1, 0x022f, 0xb276, 0xba18, 0xb26a, 0xb20e, 0xb294, 0x42c5, 0x42f5, 0x1b96, 0x11dc, 0x1c7e, 0xbfb5, 0x46db, 0xab1b, 0x416e, 0xb0a2, 0xb2ab, 0x43dc, 0x3076, 0xba40, 0x2e15, 0xba09, 0xb2df, 0x4100, 0x4139, 0x004d, 0x40fe, 0xbf38, 0xa914, 0x42b4, 0x43cb, 0x43e8, 0x407c, 0x21cf, 0x2d7f, 0xb2e4, 0xb26f, 0xb2e2, 0x18d5, 0xb03e, 0x45a8, 0x433a, 0xb235, 0x44b4, 0xb000, 0x43ea, 0x313a, 0x4641, 0xb268, 0x3771, 0x40e5, 0xbff0, 0xbf9c, 0x4051, 0xba56, 0x07db, 0xb2b3, 0xb0e7, 0x40ed, 0xb264, 0x4193, 0xb08b, 0x422e, 0x4079, 0x26bc, 0x42e8, 0xba16, 0xa565, 0xba1c, 0xb06c, 0x409b, 0x43f5, 0x41fb, 0x42fa, 0xb2ea, 0x4184, 0x23d2, 0x431b, 0x4125, 0x1ef6, 0xbfa3, 0xaecf, 0x44b8, 0xb2a2, 0x1b75, 0x4145, 0x46f4, 0x42d4, 0x11f0, 0xb255, 0x411d, 0x121a, 0x42d1, 0x1a78, 0xbfae, 0x1d7b, 0xb0e1, 0x40e4, 0x422b, 0x428a, 0xba68, 0x18d6, 0x2f33, 0x4093, 0x433d, 0xbf32, 0x45c9, 0x4227, 0x423b, 0x41a1, 0x0f2d, 0x2021, 0x4679, 0x1f9b, 0x418c, 0x436b, 0xbf87, 0x41e4, 0x41bd, 0x407b, 0xa9d5, 0x1e4b, 0x00d3, 0x4389, 0x42b6, 0x46bd, 0x401e, 0x4067, 0x41d4, 0x4341, 0x4146, 0x2898, 0x463d, 0x1ece, 0x324f, 0xbaed, 0x4186, 0xbf31, 0x1dc1, 0xb275, 0x3cd7, 0xbf00, 0x4162, 0x4202, 0x431b, 0xba19, 0xb281, 0x40f0, 0xb2d9, 0x1ebc, 0xb2ae, 0x40ec, 0x42c1, 0xb2b9, 0x4241, 0x1eec, 0x081f, 0x4225, 0x3497, 0x4371, 0xac55, 0xad9c, 0xbf7e, 0xa740, 0x1a53, 0xb2ee, 0x0406, 0x403a, 0x40de, 0x1ea4, 0x4252, 0xa544, 0x41b5, 0xba14, 0x4225, 0x42c2, 0x430d, 0xbf43, 0x40db, 0xa456, 0x1b59, 0x4347, 0xb2b6, 0xbff0, 0xbae4, 0x4363, 0xb200, 0x40dd, 0xba74, 0x420a, 0xbf2e, 0xa9e7, 0x3c0c, 0x4285, 0x13d7, 0xb28b, 0xb05e, 0x1826, 0x42bd, 0x2d51, 0x4242, 0x4227, 0xbfd3, 0x43fb, 0x41ce, 0x4171, 0x416e, 0x4313, 0xa531, 0x4302, 0x010c, 0x201b, 0x2549, 0x45b4, 0x1366, 0x41a2, 0xba6f, 0xb2f8, 0x4049, 0x1e75, 0x4471, 0x45be, 0x1814, 0x4430, 0x415e, 0xbf4f, 0x187a, 0x418d, 0x2a18, 0xb278, 0x4002, 0x2b61, 0xbf6f, 0x06cc, 0x4265, 0x41fb, 0x408d, 0xbf56, 0x3963, 0x1e83, 0x45c0, 0xa06b, 0xa6e9, 0xb252, 0x1e7d, 0xb000, 0x4024, 0xb0ab, 0x4394, 0x418b, 0x1873, 0xaeb9, 0xbfaf, 0x4331, 0x1e39, 0x4405, 0x1ccb, 0x274d, 0x4344, 0xb0be, 0x41ca, 0x4268, 0x431f, 0x44c9, 0x41bf, 0x40d0, 0x42f3, 0xb09a, 0x1c08, 0x2223, 0xbf24, 0x0599, 0x46a3, 0x4207, 0x4114, 0xbf48, 0x1e64, 0xb0fe, 0x2c19, 0x4074, 0xab7a, 0x41d7, 0x4262, 0x46b0, 0x15eb, 0x42ae, 0xb23b, 0x35c8, 0x4567, 0x4177, 0x42a7, 0x4219, 0xb018, 0xb231, 0x42f7, 0xa4cc, 0x4414, 0x2023, 0x1c10, 0xbf3d, 0x1242, 0x4258, 0xb046, 0x4159, 0xba45, 0x4585, 0x4276, 0x4123, 0xb243, 0x443d, 0x409b, 0xa4f1, 0x426d, 0x42e9, 0x3228, 0x4645, 0x4543, 0x2747, 0x42ed, 0x4316, 0xbf7e, 0x4120, 0x41ec, 0x41eb, 0x4174, 0x424f, 0xbfb1, 0x03c4, 0x2fe1, 0xb25d, 0x4068, 0x1b8f, 0xb270, 0xb058, 0x1c88, 0xbf6f, 0x1fce, 0x1bb9, 0xb23a, 0x4300, 0x4480, 0xaf8f, 0xb21d, 0xba5e, 0xba13, 0x1e5e, 0xbad9, 0x41a4, 0xb2c8, 0x40dd, 0xb27c, 0x382e, 0x089a, 0x429a, 0x4076, 0xae8a, 0xbf72, 0x1e10, 0x433d, 0xa7c9, 0xb0b4, 0xb034, 0xb0d4, 0x41b6, 0x1858, 0xbf7e, 0x1ec5, 0x40e7, 0x02cc, 0x41ce, 0x06fb, 0x42e7, 0xa6f3, 0x1a85, 0x40e9, 0x427f, 0x0051, 0xb0ca, 0xb28e, 0x1888, 0x4682, 0x4344, 0x1f2e, 0x429e, 0x426a, 0x324f, 0x0eae, 0xbf9d, 0xb00a, 0x4081, 0xab91, 0x465e, 0xbf2b, 0xaf28, 0x4091, 0x428f, 0x45c4, 0x191d, 0x191b, 0x42bb, 0x0d5a, 0x428a, 0x463e, 0xbf44, 0x400a, 0x40b3, 0x0aa6, 0x4085, 0xba42, 0xbf1f, 0x425e, 0xbfa0, 0xb008, 0x4315, 0xb232, 0xba77, 0x1412, 0x41dc, 0x43c2, 0x0566, 0xbf6e, 0x34aa, 0xb272, 0x1b03, 0x4171, 0x1b82, 0x0772, 0x2458, 0xab4d, 0x1c39, 0x43f9, 0x429b, 0x40ad, 0x42a2, 0xba19, 0xad15, 0xb078, 0x42f2, 0x1e62, 0x43a8, 0x3a59, 0x425a, 0xb00c, 0x0ce3, 0x29fc, 0xb291, 0x3847, 0x1bc4, 0xbf86, 0x431b, 0x1b36, 0xb26f, 0x40ee, 0x43aa, 0x124f, 0xbac5, 0x0f90, 0x402d, 0xb2d0, 0xb2b6, 0x46b1, 0x4327, 0xa182, 0xbf9a, 0x406e, 0x43a9, 0x430a, 0x05fc, 0x41d8, 0x4072, 0xbf12, 0x4287, 0x43d0, 0x2863, 0x4392, 0x1bf0, 0xa512, 0xbf92, 0x442c, 0x187c, 0x4429, 0xbad3, 0x43a2, 0x42cb, 0xa016, 0x422c, 0xbfb1, 0xb245, 0x1b85, 0x42a3, 0xba1d, 0xb023, 0x4185, 0x40ce, 0x43e7, 0x1c32, 0x4222, 0xa312, 0x34a2, 0xbfc0, 0x42a0, 0xb26c, 0x1e0d, 0x19fd, 0x434a, 0xba0c, 0x41be, 0x40a1, 0x1009, 0x410c, 0xbf9c, 0x441c, 0x41ca, 0xbf0b, 0x088d, 0xbf60, 0x2873, 0x1968, 0x41b9, 0x411f, 0x06ff, 0x463f, 0x40f2, 0x4220, 0x4278, 0x41bb, 0x06d0, 0x42d7, 0x42e2, 0x43c5, 0x1ece, 0x3ef3, 0x4218, 0xb2ae, 0x41bc, 0xa77e, 0x1068, 0xbf28, 0xbf00, 0xb296, 0x11b9, 0x4309, 0xbafd, 0xb286, 0x40e8, 0xbfd1, 0x437a, 0x35d5, 0x42d1, 0x424e, 0x4548, 0x40a7, 0x4056, 0x40ff, 0x44c9, 0xb26a, 0xba07, 0x43ca, 0x4310, 0xb2b3, 0x40ce, 0x1810, 0x462f, 0xb28f, 0xb276, 0x4266, 0x4669, 0x1e7a, 0xb21a, 0x21ce, 0x4611, 0xbf53, 0xbac4, 0x4477, 0xb2ff, 0x1eda, 0xb2de, 0x0819, 0xb28b, 0x4029, 0xbaeb, 0x050d, 0x43bb, 0x4327, 0xb23b, 0x27fd, 0xbf98, 0x1d5c, 0x4275, 0xaa35, 0x186d, 0x2879, 0xbaf5, 0x4011, 0x401c, 0x43a0, 0x3fd9, 0xba7a, 0x1b93, 0xb288, 0xaad0, 0xbf22, 0x3c38, 0xb26b, 0x2763, 0x3f20, 0x4003, 0x1a28, 0xb26d, 0xabec, 0x0d07, 0x0bf5, 0x4117, 0xbac5, 0x434a, 0xb0ee, 0xb028, 0x433d, 0x4695, 0xba77, 0x3ea1, 0x4177, 0x446f, 0xbad3, 0xa161, 0x11de, 0x1a2b, 0xb23f, 0xb2cd, 0x3450, 0xbf61, 0x3f26, 0xb291, 0x34e5, 0x46c1, 0x1fce, 0x4215, 0x423b, 0xab8c, 0x1627, 0x1011, 0x40e9, 0xb0d3, 0x1f42, 0x4164, 0xbfb3, 0x1886, 0x4018, 0x2218, 0xbadc, 0x4245, 0x42ab, 0x1f0c, 0xb2ec, 0xaa74, 0xad5c, 0x413a, 0xbf79, 0xb07a, 0x4213, 0x43ed, 0x41ac, 0x423d, 0x06e5, 0x0ad8, 0x1aaf, 0x4388, 0xb280, 0xbae9, 0x403a, 0xa331, 0xb2d9, 0xbfdf, 0x421e, 0xb0ad, 0x424e, 0x4065, 0x422c, 0x1001, 0x1a77, 0x4017, 0xbf65, 0x14cb, 0x4573, 0xb26b, 0x4371, 0x4026, 0x46fc, 0x42e2, 0xa06e, 0xae37, 0x0f59, 0xb045, 0x42d9, 0x4136, 0xba1b, 0xb2cf, 0xb2da, 0x3ce5, 0x3b2e, 0x44f9, 0x46b9, 0x3256, 0x4265, 0xbff0, 0x410d, 0xbff0, 0x2599, 0x4287, 0xbf74, 0x42ae, 0x4009, 0x2b2f, 0x4167, 0x4321, 0x459b, 0x42da, 0x416f, 0x406c, 0x42c4, 0xb2c2, 0x4630, 0x40db, 0x39ab, 0x0d75, 0xbf55, 0xb2df, 0x3c88, 0xb084, 0xb220, 0x2296, 0xbae0, 0x3e6b, 0xb002, 0x1d1f, 0x2309, 0xb26d, 0xba77, 0x2e13, 0x4084, 0xb29b, 0x10cb, 0x4397, 0xbf3b, 0xb0da, 0x42ee, 0xb2e6, 0x42c4, 0x4603, 0x1c1b, 0xbfd0, 0xbfc0, 0x4297, 0x1f20, 0x3f0e, 0x4246, 0x27a5, 0x42ca, 0xb2d0, 0x02d7, 0xaf51, 0x1d35, 0x41c5, 0x43cc, 0xb251, 0xb24f, 0x428f, 0x4101, 0x3bfb, 0x1f9d, 0xbf46, 0x4314, 0x469c, 0x1932, 0x40bf, 0xafff, 0x1cb1, 0x4141, 0xa9be, 0x43ed, 0x41a7, 0x4035, 0xba21, 0x1f1b, 0x1f56, 0x4054, 0x1554, 0x456b, 0x4249, 0xbf41, 0x4327, 0xba1f, 0x41ab, 0x407a, 0xb050, 0xbaf9, 0xb272, 0xabcc, 0x40c8, 0x4078, 0x1bcf, 0x42ae, 0xba1c, 0x42fc, 0x40b0, 0xb2ef, 0xb0c6, 0x401d, 0x46fd, 0x29e9, 0xb2a4, 0x1805, 0x42b5, 0xb01d, 0xbf47, 0x40e8, 0xb0d8, 0x056d, 0x4271, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x1a5fd34d, 0x3103aa3c, 0x3e4f9ef4, 0xf82df4a2, 0x3ba27697, 0x32d28d47, 0x5158d643, 0x5cf29a73, 0x8ce84a47, 0x4e93790b, 0xc7f42d1c, 0x6b5dd957, 0x35ca5f7d, 0xde453ad4, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0x00000000, 0xffff9c02, 0xffffff91, 0x000004b0, 0x00000000, 0x00000000, 0x00000091, 0x00000000, 0x00000844, 0x00000000, 0x19860000, 0x6b5dd957, 0xffff8204, 0x000016e4, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xa14c, 0x4132, 0x41bc, 0x4020, 0x0b92, 0x157b, 0x41d2, 0x4033, 0x0ac1, 0x1a9f, 0x4029, 0x27ea, 0xb2ec, 0xbff0, 0x1c03, 0xb249, 0xbfba, 0x2af5, 0x063a, 0xb2f7, 0x1fde, 0x4650, 0x419d, 0x4254, 0x423c, 0x45c5, 0x43f5, 0xbfb7, 0xb22c, 0x439d, 0x41ae, 0xa021, 0x405d, 0xb2f5, 0xa199, 0x4328, 0x43c1, 0xa29f, 0x3ace, 0x40cd, 0xb29a, 0x4192, 0x4165, 0xbaf5, 0x281e, 0x2f02, 0xa053, 0xb2a9, 0x414c, 0x2319, 0x40bb, 0xbfdb, 0xbf80, 0xa7d7, 0xbff0, 0x424b, 0xb031, 0x4063, 0xbf29, 0x443d, 0xba16, 0x2f1f, 0x2cae, 0x4108, 0xb0e1, 0x4362, 0x3931, 0xbac3, 0xbf8d, 0xb286, 0xb2bd, 0x43b0, 0xb01c, 0x42f2, 0xbfb5, 0xba58, 0x4066, 0x42a6, 0x40f4, 0x25d9, 0xba15, 0xba76, 0x4173, 0x32e9, 0x4184, 0x45f3, 0xb0e2, 0xb2e3, 0x32b6, 0xbf81, 0x4136, 0x448a, 0xba7b, 0xba55, 0xb23c, 0x4153, 0x4323, 0xb2cb, 0x18e5, 0x4161, 0xba7b, 0x363d, 0x408c, 0xbf39, 0x400f, 0x406c, 0x41c0, 0x420d, 0x32e5, 0xb24a, 0x2bb8, 0xaa8c, 0xb053, 0xb276, 0xbf4d, 0x4109, 0xae28, 0xba33, 0x429e, 0xb055, 0x2c5a, 0x43c2, 0xb283, 0x05b1, 0xbfd3, 0x19f9, 0x4081, 0xbace, 0x43c3, 0x455e, 0x42aa, 0xbfcf, 0x42d3, 0x4000, 0xb23a, 0x42ee, 0x41b4, 0xba39, 0x2cd1, 0x303c, 0x43a4, 0xbfbc, 0xb2db, 0x2de9, 0xbaf6, 0xafed, 0xba77, 0x42f4, 0x4163, 0x41b5, 0x41b5, 0x3a75, 0x4298, 0x4215, 0xba5c, 0x42bc, 0xb2c8, 0x41ec, 0x2f90, 0x42b1, 0xb065, 0xbfbc, 0xba6f, 0x456e, 0xb234, 0xbf42, 0x44b4, 0x46f9, 0x1b3f, 0x1d00, 0x4201, 0xb223, 0xa1f4, 0x27e7, 0x41c0, 0x1e5c, 0x46b1, 0x412b, 0xa523, 0xbf00, 0x4033, 0x151c, 0x3328, 0xba10, 0x420f, 0x45b4, 0x1217, 0xbf6f, 0xa77d, 0x2c63, 0x1985, 0x411a, 0xb262, 0xbf1f, 0xb260, 0x1a16, 0x43ac, 0x41d4, 0x440c, 0x41f6, 0x3409, 0xb2a6, 0xbfe1, 0x2013, 0x43e1, 0x43fd, 0x46d1, 0x4160, 0xbf73, 0x4132, 0x429b, 0x23b3, 0xbae9, 0x10bd, 0xba7f, 0xba1b, 0x1886, 0x109c, 0x41a6, 0xb201, 0x1be2, 0xbf95, 0x410e, 0x46a2, 0x430a, 0x02f1, 0xb2d5, 0xb206, 0xbae9, 0x1b5c, 0x40f3, 0x44da, 0xbf43, 0xabc8, 0x0f66, 0x1883, 0x458b, 0x426d, 0xa283, 0x4033, 0x416e, 0x4173, 0x4461, 0xb2f6, 0x1139, 0xb0b0, 0xbfe0, 0x437a, 0x41f9, 0xb226, 0x4359, 0x4160, 0x4618, 0xb27e, 0x41ab, 0xbfb4, 0xb0f4, 0x4603, 0x401e, 0x2dd7, 0x1b5c, 0xb2ec, 0x16ab, 0xb00d, 0x41b9, 0xa2eb, 0xb00d, 0x40bf, 0x2e03, 0x46b2, 0xbf13, 0x38c1, 0x1ed1, 0xbac9, 0x4036, 0xb0bf, 0x41bc, 0xba14, 0x02dc, 0x41c1, 0x4383, 0xba42, 0xb223, 0xba53, 0x4037, 0xba00, 0x43d0, 0x4362, 0x42ea, 0xb03f, 0x41a3, 0x4111, 0x001b, 0x0080, 0xbf7f, 0xb299, 0x40e7, 0x4158, 0x4196, 0x1a5d, 0x07f6, 0x41ee, 0x0c49, 0xb2c8, 0xb243, 0x409a, 0x392d, 0xb2bf, 0xa325, 0x4629, 0xbfd4, 0x4334, 0x4305, 0x2dd2, 0xbfe0, 0xb01d, 0x36a2, 0x4211, 0xbfe4, 0x4151, 0xbfb0, 0xb2da, 0xbf70, 0xb2ee, 0x43a8, 0xbf85, 0x448d, 0x43e6, 0x1b34, 0x18e6, 0x43f7, 0x1d9f, 0xb251, 0x40b6, 0x0822, 0x4048, 0xb086, 0x0161, 0xbaca, 0xb211, 0x0500, 0xb076, 0x400d, 0xbf9c, 0x40bd, 0x41bd, 0x22d5, 0x421d, 0x3876, 0x1a86, 0x408e, 0x357b, 0x4567, 0x41f6, 0xb29f, 0x43f4, 0x439b, 0x431b, 0x4115, 0x3962, 0xb0cc, 0x4198, 0x0afa, 0x445d, 0xbac5, 0xaa18, 0x41e1, 0x43e4, 0xbf59, 0x43b8, 0x431e, 0x1849, 0x4233, 0x4393, 0x42dd, 0x4275, 0x430f, 0x420b, 0xbf90, 0x4116, 0x43b5, 0xb226, 0xad7b, 0x45e8, 0x4202, 0x43c7, 0x2c0c, 0x4070, 0x4185, 0x43ce, 0xbad1, 0x1a16, 0x1c88, 0x413b, 0x4163, 0x2d64, 0xb03b, 0xbf63, 0x1d22, 0x40c5, 0x285b, 0x1f82, 0x41ba, 0x417d, 0xba1b, 0x4219, 0x40e2, 0x4297, 0x4307, 0x1873, 0x4050, 0xbfa0, 0x229d, 0x4055, 0x436a, 0x3545, 0x1a18, 0x42ef, 0xb280, 0x40a1, 0x1c13, 0xb05e, 0x408e, 0x42d4, 0x2688, 0x40d3, 0xbfc3, 0xa2eb, 0x421d, 0x440e, 0x090b, 0xb264, 0x4160, 0xb2b3, 0xbad4, 0xba36, 0x40f8, 0xb2f5, 0x3be0, 0xb096, 0x1da7, 0x1f4e, 0xba52, 0x4155, 0x4273, 0xbf8e, 0x42f4, 0x4247, 0x4148, 0x428e, 0x4481, 0x1cd6, 0x436c, 0xb22a, 0x4189, 0x27ea, 0x42c1, 0x435f, 0x41ca, 0x24f5, 0x40a8, 0x4304, 0x4575, 0x416a, 0x42d4, 0x41d8, 0x1cf2, 0x430a, 0x4249, 0xa6d5, 0x4208, 0x4361, 0xbf77, 0xb2bf, 0x4061, 0x42f5, 0x1df1, 0xb2fb, 0x2eb2, 0x1f36, 0x0636, 0x4233, 0xb051, 0x0d22, 0x437a, 0x187c, 0x431d, 0x2642, 0x4329, 0xba5e, 0x402e, 0x1a7d, 0xb27c, 0xbfc0, 0x4382, 0x430d, 0xaad4, 0x4033, 0x423d, 0x4266, 0xbfc1, 0x1a59, 0x4349, 0x416c, 0xb236, 0x4337, 0x2973, 0x43cb, 0x41d8, 0xb031, 0x431b, 0x1bce, 0x1553, 0x40ea, 0x0c9e, 0xba3b, 0x1192, 0x41a9, 0x4261, 0x4086, 0x1582, 0x4637, 0x4056, 0xbf8c, 0x4600, 0x429b, 0xbfac, 0x2ba6, 0x2e02, 0xbf2d, 0x463a, 0x41d0, 0x1d05, 0x43f9, 0x2c9c, 0x40af, 0xb00f, 0x2d8b, 0x4201, 0x4354, 0xba21, 0x1e4b, 0x41ae, 0xb2ba, 0xb28c, 0xa6c2, 0xbafd, 0x27e8, 0xa499, 0xbf8f, 0x4272, 0x076d, 0x4342, 0x4337, 0x1f5e, 0x41a3, 0x37d5, 0x417e, 0x4215, 0x4678, 0x143e, 0xbafc, 0xab29, 0x4095, 0xb28d, 0x0ed2, 0xbf80, 0xabb9, 0x433d, 0xba51, 0x0bba, 0xaa69, 0xa93d, 0x4117, 0xbfc7, 0x312f, 0xb099, 0x119d, 0x4393, 0x40e9, 0xbf94, 0x1cb6, 0x4294, 0x43f5, 0x42b8, 0xb26f, 0x2772, 0xb0a6, 0x413b, 0xadeb, 0x408e, 0xbfbc, 0x4542, 0xba61, 0xb066, 0x40f9, 0x45d5, 0xabc9, 0xba51, 0x19c0, 0x4202, 0x4245, 0x4162, 0xb256, 0x0fff, 0x1ca8, 0xbaf9, 0x3ce8, 0x45ee, 0x1bd9, 0xbfa0, 0xa474, 0xbfb3, 0x42c1, 0x39dc, 0xb08f, 0x303f, 0xba67, 0x1ec2, 0x43bd, 0xb2b5, 0x42df, 0x0cbd, 0x240e, 0x402b, 0x411d, 0xba53, 0xb20e, 0x41b8, 0xbf78, 0x413f, 0xbff0, 0x0612, 0xbf36, 0x4685, 0x3a69, 0x4318, 0x4137, 0xba21, 0x42ba, 0x4259, 0x4207, 0x41d7, 0xb2bc, 0x3b7e, 0x40d0, 0x423b, 0xab19, 0xb2ae, 0xb2b8, 0x43a4, 0xba50, 0xba2f, 0x21c8, 0x24c1, 0x4110, 0x422d, 0xbf4d, 0xa1c2, 0x1745, 0x18d8, 0x095c, 0xb260, 0x4070, 0xb23f, 0xa815, 0x346e, 0xb21f, 0x418c, 0x407f, 0xb259, 0xba4e, 0x3d84, 0xb2ff, 0x40d6, 0x44b3, 0x1bfb, 0xa6b7, 0x41f2, 0x412c, 0x4262, 0xbafd, 0x4460, 0xbfb0, 0x414c, 0xbfa9, 0x129a, 0x43d9, 0x09f1, 0x4262, 0x2b67, 0x4369, 0x40b7, 0xbf4c, 0x4473, 0xba7f, 0xba3f, 0xba30, 0xb039, 0x4378, 0x431a, 0xbf71, 0xb20b, 0xba76, 0x410a, 0x4284, 0xa2fb, 0xb283, 0xb2c3, 0xa762, 0x3e09, 0xba6c, 0xbf65, 0x4387, 0x218b, 0x2a1c, 0x4184, 0xb2b6, 0x41c0, 0x4298, 0x4249, 0x4397, 0x1ed0, 0xbafe, 0x4659, 0x1fc5, 0xa570, 0x4684, 0x40d0, 0xb012, 0x2c6b, 0xb22b, 0x105a, 0x4229, 0x409a, 0x43ce, 0xb06d, 0x4085, 0x4280, 0x12a1, 0xbf4d, 0xb09d, 0x436c, 0x281c, 0x40ff, 0x0cc9, 0xbfad, 0x1195, 0x42b4, 0x43f7, 0x415b, 0x40ad, 0x43e6, 0xb016, 0x4086, 0x4037, 0x0149, 0xbfd0, 0x41f5, 0x3cc4, 0xad52, 0x2422, 0xa9f5, 0x41c6, 0x26b2, 0x4226, 0xb27e, 0xbf3e, 0x02a3, 0xbac8, 0xb225, 0xbf31, 0x0663, 0xaea1, 0x4010, 0x00b1, 0x4201, 0xb0ce, 0xba4c, 0x400b, 0x4442, 0x436d, 0x0722, 0x42f6, 0x4321, 0x4001, 0x1f25, 0x411e, 0x401d, 0xba3a, 0x0ba3, 0xb2e1, 0x0141, 0xb01f, 0xb26c, 0x41a7, 0x435a, 0xaa86, 0xbfbb, 0x459c, 0x1caf, 0x441e, 0x43e5, 0x3643, 0x4240, 0xb292, 0x3097, 0xb2c2, 0x1b2f, 0x0db8, 0x40ab, 0xba0c, 0xb21c, 0x415e, 0x42ba, 0x4182, 0x25a2, 0x42c0, 0xb08f, 0x2808, 0xbf97, 0x400b, 0x43af, 0xb22e, 0x416e, 0xba40, 0xbf80, 0x43fe, 0x4349, 0xbada, 0x4003, 0xbff0, 0x0a40, 0x3d0b, 0x412c, 0xbf2f, 0x430f, 0x101c, 0x1a47, 0x41ce, 0xb2de, 0x4359, 0x4297, 0x41ad, 0x1d4a, 0x44eb, 0xbf80, 0x436f, 0xa5d7, 0x1cb2, 0xbf1f, 0x2907, 0x1b41, 0x43c7, 0x42a7, 0x41d0, 0xb2d9, 0x429f, 0x43d2, 0xa0eb, 0x4106, 0xb2d9, 0xa5a9, 0xa43e, 0x43d7, 0x12d3, 0x1e49, 0x41e3, 0x4589, 0x433d, 0x430f, 0xab65, 0xb22d, 0xbaee, 0xb22a, 0xaec2, 0xb265, 0xba6c, 0x1a12, 0xbfb2, 0x4137, 0xa12b, 0x4115, 0x4306, 0x43e9, 0x1c8b, 0x1542, 0x2360, 0x45ea, 0x4387, 0xba7a, 0x1fab, 0xb245, 0xbfd8, 0x2725, 0xbf78, 0x4146, 0x428a, 0xb21b, 0x19f9, 0xb282, 0xb2da, 0x4622, 0x40ae, 0x46ac, 0x41ac, 0xbf33, 0xb213, 0xb22d, 0xba56, 0x40b7, 0xab93, 0x18e7, 0x4372, 0x40ce, 0xb23d, 0x1a8c, 0xb281, 0x42a2, 0x41ce, 0xb22e, 0x43ee, 0xb221, 0xbf2f, 0xb2c3, 0x4081, 0x23a1, 0x4162, 0x42b6, 0xb004, 0x1e59, 0xb2d0, 0x4121, 0x435d, 0xb2af, 0xb2f0, 0x42ab, 0xbfe0, 0x1cb8, 0x44f3, 0x4605, 0x1e0e, 0x4625, 0x31e0, 0x4570, 0x40ec, 0xbf7a, 0x4173, 0xa0f7, 0xb2f3, 0xba19, 0x4149, 0x1f51, 0xba44, 0x29ac, 0x402d, 0x1808, 0x01f2, 0x41bf, 0x4186, 0x42e4, 0x10e4, 0x1f7a, 0x416a, 0xb0b8, 0x4130, 0x4467, 0xbf4a, 0xba17, 0x4237, 0xb2bd, 0x1cb4, 0x1955, 0x3010, 0xbf6c, 0x419f, 0xbf90, 0x4326, 0x423d, 0x40b3, 0xb24d, 0x413a, 0x414f, 0x4317, 0x4018, 0xb254, 0x0dd2, 0x4281, 0x4175, 0x4590, 0x19ad, 0x405f, 0x1d33, 0xbf2b, 0x3132, 0x1412, 0x1bbc, 0x1bb8, 0x4259, 0x426e, 0x414f, 0x4358, 0x43df, 0xb056, 0x4677, 0xa879, 0xade8, 0xba78, 0x436a, 0xb211, 0x4013, 0x40e9, 0x4280, 0xbff0, 0x41e8, 0x068f, 0x185e, 0x43c5, 0x43c6, 0xb021, 0xbfa7, 0x42da, 0x1aa9, 0x4214, 0xa017, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x371597f2, 0x76cf74c6, 0xf6f07bf9, 0x14bc3ff2, 0x44d46dd4, 0x85b9d3ca, 0x44827883, 0x4ba02ac3, 0x45d4fe43, 0x55355bd7, 0xa1571ee2, 0x4065eef1, 0x6a318c25, 0x4295c0bf, 0x00000000, 0x600001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x45d4fe43, 0xa1571ee2, 0x00000000, 0x82fbba0c, 0x00000064, 0x4295cc27, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x200c, 0xbf4b, 0x3eb3, 0x4389, 0x4279, 0xb2c4, 0x432f, 0xb2ad, 0x1a23, 0xa8ec, 0x4396, 0x424d, 0x4177, 0xb0e4, 0x30b3, 0x4175, 0x42f2, 0x43e7, 0x40d1, 0x1e00, 0xb20a, 0x1f46, 0x42a6, 0x401c, 0xbf26, 0x4342, 0x4082, 0x38b3, 0x01dc, 0xbfe0, 0x429c, 0x4192, 0xb294, 0x1c89, 0xbf8a, 0x4290, 0xba28, 0xba38, 0x4255, 0xa885, 0x41bc, 0x1e2c, 0x4188, 0xb25d, 0xb245, 0x0f0d, 0x1af7, 0x1a13, 0x00b2, 0x23e6, 0x05cc, 0x350b, 0x43da, 0xb2ee, 0xa17e, 0x4290, 0x11a2, 0x4151, 0x1629, 0xbfaa, 0x44a8, 0x3802, 0x2ddb, 0x3c35, 0x4328, 0x1c04, 0x41cc, 0x4010, 0x4031, 0x41b9, 0xbfcd, 0x420c, 0x43fe, 0xb209, 0x4070, 0x4009, 0xb04f, 0x1b1a, 0x1ad0, 0xba64, 0xab75, 0xbaf7, 0xb01e, 0x41f5, 0x423f, 0x46d9, 0x0581, 0x40aa, 0x2fd3, 0x19d7, 0x1d89, 0x357c, 0xb0e6, 0x1ef7, 0xbf29, 0x44e1, 0x066d, 0x3b50, 0xac4d, 0x19a8, 0x449d, 0x0215, 0x0662, 0x4348, 0x4168, 0x4392, 0xac1f, 0xb02d, 0xbf88, 0x1835, 0xa8cb, 0xb06d, 0x426f, 0x3a1f, 0x1aeb, 0xb01d, 0xb014, 0x4221, 0x1e94, 0xbf53, 0xb287, 0xadbc, 0xb275, 0x42b6, 0x46c4, 0xab25, 0x394f, 0x1a27, 0x1dc2, 0xbff0, 0x43bc, 0xb062, 0xb2bd, 0x4109, 0x4305, 0xbf37, 0xb09e, 0x40a7, 0xb2ca, 0x42dd, 0xb248, 0xb0c0, 0x311c, 0x40cd, 0x40e7, 0x428f, 0x422c, 0x4213, 0x4101, 0x1838, 0x1f0a, 0x1b05, 0xbad3, 0xb28d, 0xbf70, 0x40bc, 0x4383, 0x43f6, 0x0bb7, 0x4036, 0x4582, 0x3552, 0xbf0f, 0x4329, 0xb244, 0xb2cf, 0x4095, 0xb261, 0x41f2, 0x083b, 0xb278, 0x0ce4, 0x415c, 0xbf3f, 0x4001, 0xb0ec, 0x0cbe, 0x1347, 0xbf1d, 0x4254, 0x199b, 0x1f28, 0xb2c1, 0x071c, 0x4234, 0xba53, 0xb074, 0xba5d, 0x4391, 0x3240, 0xb2e7, 0x4320, 0x19b5, 0x020e, 0x439b, 0x2782, 0xba6a, 0x094a, 0xb044, 0x40f5, 0xb21a, 0x40cd, 0xb274, 0xbf9a, 0x424f, 0x4624, 0x4009, 0xb23e, 0x42ef, 0x4284, 0x4548, 0x1da1, 0x19f5, 0x4100, 0xb2b2, 0xad48, 0x40a6, 0x1f3d, 0x45e1, 0xb22d, 0xbf12, 0x4008, 0x08ae, 0x420c, 0xbae7, 0x4273, 0x4225, 0x1c10, 0xbf68, 0x4070, 0x4113, 0xb08a, 0x0a0a, 0xa398, 0x08f0, 0xbfe0, 0x0f55, 0x41c3, 0x1c12, 0x385a, 0xa0f9, 0x1e68, 0x13ce, 0x1ffe, 0x4032, 0x435f, 0xba10, 0xb231, 0xb27f, 0xbf13, 0x42e5, 0x16d2, 0xb2e2, 0x4379, 0xba75, 0x428e, 0x4239, 0x4311, 0x42df, 0x43ed, 0x29dd, 0xbfa4, 0xb05e, 0xaf16, 0x1b1d, 0x4101, 0x415a, 0xb2a8, 0x40ed, 0x3eb0, 0x4346, 0x412b, 0xb28f, 0x415a, 0xbfd9, 0x425f, 0x4182, 0xb249, 0x43e6, 0xb2b9, 0x1400, 0x4073, 0x1272, 0x42d9, 0x4391, 0x407b, 0x43ba, 0xb07c, 0x41b6, 0xba09, 0x4419, 0xbfba, 0x40ae, 0x44f2, 0x1e03, 0x3d31, 0x4313, 0x417c, 0x1f4a, 0x40e1, 0x1b00, 0x425e, 0x46d2, 0x406c, 0xbfc3, 0x1ef0, 0x4266, 0x407f, 0x2f1e, 0x2462, 0x43ae, 0xb077, 0x4187, 0x4624, 0x4366, 0xba60, 0x43ed, 0x4429, 0xb20c, 0x2bc8, 0x181e, 0xbfb0, 0xb02b, 0xb26f, 0xa896, 0x42e8, 0x1cc9, 0x404d, 0x2159, 0xb27e, 0xbf2e, 0xbff0, 0x0378, 0x4598, 0x45ec, 0xbfda, 0xba4e, 0xb250, 0xb2b4, 0xb0f9, 0x43d1, 0xb027, 0x41e3, 0x4076, 0x43df, 0xbafd, 0x4397, 0x4046, 0x4567, 0xb251, 0x454f, 0x44e5, 0xbf8d, 0xba25, 0x461d, 0x415d, 0xae84, 0x4577, 0xbaeb, 0x0cd3, 0x4201, 0x025f, 0x4126, 0x2412, 0x1896, 0x424a, 0x1daf, 0xb22e, 0x4311, 0xba23, 0xbaf4, 0x4243, 0x3d58, 0xb0c3, 0xb0dc, 0xb230, 0x424f, 0x401f, 0xbf32, 0xa999, 0x42d4, 0x4306, 0xbfd2, 0x45f4, 0xaab3, 0xb0d5, 0x34d6, 0xb280, 0xb229, 0x4068, 0x1e55, 0x4368, 0x40bd, 0xba7f, 0xba63, 0x4108, 0x1fd9, 0xb000, 0xb295, 0x40b9, 0x2f6d, 0x447c, 0x43b9, 0xbf7c, 0xb059, 0xba47, 0x40d6, 0x1e41, 0x378b, 0xbfa0, 0x25c8, 0x2b2f, 0x014f, 0xbf03, 0x4139, 0xb238, 0x01a8, 0x3707, 0x1878, 0x4301, 0x19e6, 0xab18, 0xba2e, 0x4126, 0x43eb, 0xb29f, 0x42a6, 0x41bb, 0x0eca, 0x08f5, 0x414a, 0x41c5, 0x4305, 0x43bd, 0x40ed, 0x0b88, 0x4387, 0x4380, 0xb0fd, 0x31e5, 0xba59, 0xbf90, 0xbf57, 0x40f1, 0x435f, 0x385f, 0x403b, 0xbadc, 0x301d, 0xb265, 0x440c, 0xbac3, 0xb2a3, 0x402c, 0x45bb, 0x43e4, 0x2833, 0x4109, 0xae7c, 0xb0d2, 0x458e, 0x40ec, 0x14c5, 0x405a, 0x2480, 0x0070, 0xbf24, 0x41b2, 0xba46, 0x401c, 0xbf27, 0x2583, 0xb24d, 0x41ff, 0x32fc, 0xb0d4, 0x4045, 0x4145, 0x00ca, 0x4067, 0x42ac, 0x4094, 0x3145, 0x4120, 0x1e2a, 0xba20, 0x4156, 0x3e8c, 0x40e0, 0x1c02, 0xba0d, 0xb061, 0x46fd, 0x42e6, 0xbf6b, 0x411b, 0x1fbf, 0xb070, 0x1846, 0xb22b, 0x4695, 0x407e, 0xba6e, 0x10bc, 0xbf94, 0x42fd, 0x43b9, 0xac56, 0x414a, 0x0183, 0x41a6, 0x42f3, 0x433d, 0x19c1, 0x4053, 0x4204, 0xb282, 0xb241, 0x1e8c, 0xb287, 0x421b, 0xb270, 0xb036, 0xb285, 0x4220, 0xbaca, 0xb289, 0xb275, 0x2849, 0x410d, 0xa4a3, 0xb231, 0xbfb4, 0x1e9b, 0x413e, 0xa953, 0xb289, 0x408c, 0x4237, 0x3547, 0xb0fe, 0xbf4c, 0x45d8, 0x424e, 0x40a3, 0x1e5c, 0x41c3, 0xbafe, 0x32ef, 0x4191, 0x41f2, 0x1d7d, 0xaacc, 0x43f2, 0x418f, 0xb203, 0xb0cb, 0x3ac6, 0xa494, 0x41fe, 0x4015, 0xbace, 0x34f5, 0x4385, 0xbf1c, 0x323a, 0x414f, 0x4312, 0x402a, 0x426f, 0x41b7, 0x4257, 0x2fda, 0xb2d1, 0x4251, 0xbf34, 0x405a, 0x44c0, 0xbfbc, 0x45e2, 0x4285, 0x40b7, 0x4293, 0xb0fa, 0xba14, 0x15bf, 0x4350, 0x420a, 0x418c, 0x33de, 0xba58, 0xa868, 0x1eb6, 0xb202, 0x43a4, 0x439c, 0x42a7, 0x4323, 0x45e6, 0x1e1b, 0xa2d3, 0x1833, 0x1896, 0x4174, 0x411b, 0xbf6d, 0x13be, 0x2d69, 0x41eb, 0x429c, 0xb2d2, 0xb2a0, 0x2ec9, 0x41ee, 0x1b0a, 0x1fbf, 0x423a, 0x1db5, 0xba10, 0x40df, 0x41dc, 0xb25f, 0xb2b9, 0xb2dd, 0x45dd, 0x403d, 0x31a2, 0x4190, 0x402b, 0xbf5c, 0x43ce, 0xa551, 0x386b, 0xbae4, 0x09fd, 0x4318, 0xbf46, 0x413d, 0x1c10, 0x41e9, 0x42b9, 0x1792, 0x419e, 0x1f8d, 0xbfb2, 0xb2be, 0x426b, 0x12da, 0xa7d4, 0x19a3, 0x40c7, 0xbad2, 0x43ee, 0x45cb, 0x0e6b, 0xabd7, 0x094f, 0xbac9, 0x40a0, 0x43aa, 0xbfe2, 0x4375, 0x2acd, 0x41de, 0x43e1, 0x1035, 0x067c, 0x40e4, 0x43cd, 0x1499, 0x19fd, 0xb09a, 0x43eb, 0x3b91, 0x0573, 0x4043, 0x1edc, 0xba7a, 0x4341, 0x37ca, 0x4030, 0x1037, 0xbae7, 0xb2b5, 0x4339, 0xbf03, 0x1731, 0x461f, 0xb040, 0x417d, 0x41dc, 0xb013, 0x1e18, 0x05e4, 0xbfb8, 0x41d7, 0x1c58, 0x0e05, 0x41ed, 0x431f, 0x406f, 0x43f3, 0x19df, 0x4058, 0x412a, 0x1e46, 0x4248, 0x4561, 0xbf6d, 0x4073, 0x429a, 0x4280, 0x1bd4, 0xb0fe, 0x4024, 0xbfde, 0x41ca, 0x4143, 0x424d, 0x4137, 0x434d, 0x4206, 0x2331, 0x434b, 0x41d2, 0x412d, 0x3037, 0x423c, 0x4582, 0xbaed, 0x41f8, 0x4113, 0x4584, 0xbf2a, 0xb234, 0x1de1, 0xb2cd, 0x4099, 0xa0ca, 0x4167, 0xbf00, 0x42e3, 0x2ee1, 0x1d91, 0x0fa0, 0x400d, 0x3248, 0xbfde, 0x25d6, 0x4332, 0x0ca8, 0x18fd, 0xb25f, 0xba37, 0x4121, 0x4402, 0x42c8, 0xbfc0, 0x3ff9, 0x41c3, 0xb2a8, 0x184f, 0x37a4, 0x439d, 0x17f7, 0x4273, 0x0b72, 0x428d, 0x4293, 0xad65, 0xbf60, 0x1be0, 0xba20, 0x43e8, 0x4033, 0x219f, 0xbf27, 0x425b, 0x1cab, 0x42c0, 0xa753, 0x411b, 0x42ad, 0x14cd, 0xba71, 0x4221, 0xba79, 0xbfbf, 0x423f, 0x437b, 0x418d, 0x43c5, 0x4062, 0x0c46, 0x3c2f, 0x442a, 0xbf26, 0x1a90, 0xb2b7, 0x07b4, 0xbf52, 0x04df, 0x42e1, 0x1d76, 0x418b, 0x333f, 0x1f08, 0x4007, 0x1246, 0xb259, 0x4084, 0x4223, 0xb257, 0xb26b, 0x2ba1, 0xb232, 0x11ce, 0xb25e, 0xb213, 0xbf5e, 0xb26f, 0x1bdd, 0x426c, 0x4375, 0x18e4, 0x403c, 0x42b8, 0xb02b, 0x4197, 0x1861, 0x2f7a, 0xbfb8, 0xa15b, 0x43d1, 0x3df1, 0xb2e7, 0x43dd, 0x425d, 0xba27, 0x4281, 0xbaf7, 0x4644, 0xbf4d, 0xa7c4, 0x4208, 0x411c, 0x43f3, 0xbacf, 0xb04f, 0x4091, 0x41d0, 0x2fd4, 0x4332, 0xb205, 0x45ee, 0xb2b7, 0xb0a1, 0xb235, 0x214e, 0x4232, 0x41e1, 0xbada, 0x2e04, 0xb2ba, 0x40f8, 0x3e46, 0xbf05, 0x3862, 0x0057, 0x426a, 0x401e, 0xb014, 0xba19, 0x0d65, 0xba7d, 0x41cc, 0x34c8, 0x4201, 0x4202, 0x43a6, 0xb004, 0xbf2c, 0xa057, 0x0d9f, 0x431a, 0xba38, 0x40b7, 0xb277, 0x4275, 0x27a1, 0x4222, 0xb098, 0xb02e, 0xb2de, 0x40e5, 0xbf27, 0x42a3, 0xad53, 0x4627, 0xbfe0, 0xba06, 0x4098, 0xb24c, 0x40e0, 0x0ec3, 0x411a, 0x403f, 0xb256, 0x42eb, 0xba5f, 0xbfb6, 0x42ac, 0x41cd, 0xb2ec, 0xbf85, 0x0e6f, 0x164c, 0xb207, 0x434c, 0xbfe0, 0x40f4, 0xbaed, 0x4056, 0xb20a, 0x1834, 0x4659, 0x1d2e, 0x0587, 0xbfb6, 0xaaf8, 0x45c6, 0x401b, 0xba6a, 0xba64, 0x40b0, 0xba31, 0x0731, 0x41ae, 0x427d, 0x4021, 0x272c, 0xba2e, 0x409f, 0xbae6, 0x4048, 0x4065, 0xbf06, 0x40e6, 0x430c, 0xb20a, 0xba68, 0xba40, 0xbf95, 0x4171, 0x43f2, 0xb25a, 0x43cb, 0x42d0, 0x46db, 0xa3e7, 0x43e8, 0x402c, 0x053e, 0x422a, 0x4261, 0x429e, 0x417d, 0xb234, 0x41f5, 0x2f6f, 0x44d2, 0xbf6a, 0x1fff, 0x1a5b, 0xba78, 0x29e0, 0x43ad, 0x4200, 0xb2dd, 0x0096, 0x3a21, 0xb247, 0x382e, 0xba2e, 0x4336, 0x4021, 0x4221, 0xbf59, 0x43cd, 0x1810, 0xa198, 0x2e75, 0xba5d, 0x42dd, 0x42a6, 0x422b, 0xbf07, 0xa0f8, 0x4206, 0x41d6, 0x3238, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x3aa91aa4, 0x4c5784a8, 0x59e0657d, 0x51ef61ac, 0x760b00f1, 0x0da5a30b, 0x69a772e3, 0x96f320f6, 0x01c12c2c, 0xe5449e38, 0x74e0fd57, 0xcf8b32f0, 0x484f3e23, 0x911d3cda, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0xffffffb0, 0x00000000, 0x00000017, 0x00001b1c, 0x00000000, 0x00001c1b, 0x38000000, 0xffffffff, 0x0382586e, 0x17da7113, 0xe9c1faae, 0xcf8b32f0, 0x01c12c37, 0xfffffbd4, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xb251, 0x317d, 0xb2ae, 0x2093, 0xb04a, 0xb0b8, 0x1e16, 0x405a, 0xbfb8, 0xb0cf, 0x4099, 0x431e, 0x41e5, 0xbf70, 0xb0e1, 0xa18f, 0x1f25, 0xba65, 0x1d38, 0x1119, 0xba41, 0xb23a, 0x03ae, 0x01a6, 0x1dfc, 0x1e5c, 0x4181, 0xba71, 0x401c, 0x3c40, 0x4154, 0x1895, 0xbfd0, 0xbf7c, 0x1af1, 0x429c, 0x1148, 0x41b5, 0x432d, 0x43f4, 0x461b, 0x41d6, 0x1ef7, 0x1d2d, 0x41c8, 0x3021, 0xb25c, 0x431b, 0xa59d, 0x4355, 0xbae7, 0xbff0, 0xb2fa, 0x39aa, 0x0c26, 0x4144, 0x0954, 0x419b, 0x03a0, 0xbfd1, 0x45e9, 0x439a, 0x4127, 0xb23d, 0x4132, 0x1ae1, 0x4671, 0x4613, 0x45e2, 0xbac3, 0x421c, 0x1379, 0x1ea9, 0x16a8, 0x406d, 0x3d2b, 0x4267, 0xb205, 0x4042, 0x4232, 0x45e4, 0xb2ba, 0x1a6c, 0xb2a8, 0xb0fe, 0x2dc3, 0xb0d7, 0xbf4a, 0x407c, 0x40f2, 0xb21b, 0x0c04, 0x2884, 0x1b10, 0x42fb, 0x41b3, 0x0235, 0x2120, 0xb062, 0x438b, 0x444c, 0x43d0, 0x41d9, 0x1dc3, 0xb02b, 0x1b8e, 0x4231, 0xba4a, 0x4372, 0x42d1, 0xb246, 0x1cfa, 0x2420, 0x41ec, 0x41a2, 0x43cf, 0xbf6c, 0x0d46, 0xba57, 0x4266, 0xaca4, 0xb220, 0x33fb, 0xb2da, 0x40ac, 0x4207, 0xb2ca, 0x40fa, 0xb2ce, 0x2f83, 0x426d, 0x1f13, 0x42f0, 0x1902, 0x4374, 0x1fff, 0xa5f3, 0x40e8, 0x42ed, 0x43c6, 0x40de, 0x4244, 0x0f5d, 0x40f3, 0xbfb6, 0x36a3, 0x418e, 0xb0f3, 0x46c5, 0xbac8, 0x1cc6, 0x1495, 0x4121, 0xb286, 0x43d1, 0x431b, 0xad3e, 0xb240, 0xbaed, 0xba30, 0x41af, 0x400c, 0xb216, 0x08b2, 0x4426, 0x4321, 0xb28e, 0x439c, 0xbfa4, 0x2883, 0xba2e, 0x094c, 0x4382, 0x4118, 0x19a5, 0x05b0, 0x4000, 0x4064, 0x19b5, 0x435f, 0xb2e0, 0x4399, 0x1ec1, 0xb09a, 0x28c9, 0x0895, 0xba6b, 0x41c7, 0x4481, 0x42f6, 0x1dc4, 0xbfb8, 0xb249, 0xba32, 0xa293, 0xba7e, 0x40c7, 0x4369, 0x4211, 0x4198, 0x4294, 0x4256, 0x1be2, 0x1f8a, 0xb20f, 0x40c6, 0x409c, 0xbfd4, 0x4197, 0x42ab, 0x4149, 0x4284, 0x4610, 0xb2fd, 0x41f9, 0xb260, 0x427d, 0x18bf, 0x45f4, 0xa24d, 0xba4e, 0xba66, 0xb2dd, 0xbaed, 0xbf25, 0x1a95, 0x1d6a, 0xb2c6, 0x4005, 0x0738, 0x1930, 0xba7b, 0xb289, 0x14bb, 0x13be, 0x4368, 0x4005, 0xba63, 0x18f7, 0x078c, 0x464a, 0xbf84, 0x0f5a, 0x1e55, 0x4262, 0x26de, 0x4132, 0x421e, 0xb04b, 0x0632, 0xb2a5, 0x43a4, 0xb03d, 0x18a6, 0x41f7, 0xb2dc, 0xb226, 0xbf34, 0x4427, 0x4019, 0x4448, 0xbaeb, 0x43de, 0x42d8, 0x4345, 0x407f, 0x4691, 0x4245, 0x4200, 0x1c1d, 0x4130, 0xa717, 0x417e, 0x42eb, 0x3c1a, 0xa401, 0xbf24, 0x0fd2, 0x424e, 0xb26c, 0x4315, 0x4064, 0xb025, 0x4431, 0xb2d9, 0x0876, 0xb2df, 0x0bb1, 0xbf48, 0x259e, 0x3f2b, 0x38c9, 0x44fb, 0x4216, 0xb0ab, 0x4138, 0x4203, 0xbfab, 0xa3bb, 0xba73, 0x3abe, 0x4136, 0x4212, 0x129e, 0xbfb0, 0xbf67, 0xb2f7, 0xbafe, 0x4066, 0x4329, 0xb0dc, 0xb050, 0x42d8, 0xb05c, 0x421d, 0x43c2, 0x42c6, 0x2ed9, 0x4088, 0xbf7e, 0x43c3, 0xba22, 0x40b5, 0x18ed, 0x4064, 0xbfd1, 0xaf28, 0xb0d5, 0x03d5, 0xb2d3, 0x2eb3, 0x10a5, 0x4298, 0xba28, 0x1329, 0x3160, 0xb01f, 0xaea8, 0x41fa, 0xb24a, 0x44c4, 0x05f4, 0x1eaf, 0xba36, 0xbf60, 0x1f18, 0x2c2c, 0x408b, 0x3269, 0x436e, 0xb2c0, 0x1392, 0xbfa1, 0x4220, 0x41f7, 0xbacc, 0xbad9, 0x0d2a, 0xb0bb, 0x4175, 0xb20e, 0x4007, 0xb225, 0x4152, 0xbf62, 0xb0f3, 0x4127, 0x1e7d, 0x4615, 0x375f, 0x4545, 0x4064, 0x40ed, 0x4015, 0x4082, 0x404a, 0xb211, 0x4073, 0x41f6, 0x42ae, 0x409d, 0x4348, 0x068b, 0x1364, 0x42af, 0x45c6, 0x1fdc, 0x25a7, 0xb24c, 0xa940, 0x1cc5, 0x40d4, 0x4006, 0xbfca, 0x4352, 0xb288, 0x2129, 0x438b, 0xb2f9, 0xa900, 0x40a9, 0xbf03, 0x456d, 0x2efc, 0xbad4, 0x1f74, 0x4204, 0x421f, 0xba1f, 0x42a2, 0x404d, 0x2d74, 0x406b, 0x2fb9, 0x4334, 0xbfd6, 0x40c5, 0x4105, 0x42a6, 0xbf00, 0x4024, 0x406a, 0x43c7, 0x4456, 0xb26d, 0x4034, 0x4230, 0x0f59, 0x4065, 0x4037, 0x0ee0, 0xadf8, 0xbf7c, 0xb2e3, 0x43b7, 0x429c, 0x40e0, 0x4338, 0xba25, 0x424e, 0x005a, 0x400e, 0x407e, 0x43e8, 0xbf0d, 0x4270, 0x1dd0, 0x4021, 0x1eb2, 0x4381, 0xae1b, 0x4122, 0x0dc4, 0xba5e, 0xbf79, 0x46da, 0x1bb1, 0x1117, 0xb03a, 0xa9c5, 0xb0de, 0x1e18, 0x42c2, 0x4043, 0x42f9, 0x4151, 0x013b, 0xbf00, 0xbf76, 0xb2c1, 0x4263, 0x40fd, 0xb02f, 0x41e1, 0x4484, 0xbfd8, 0x27ae, 0xba27, 0x430f, 0x283f, 0x4331, 0xb254, 0x4183, 0x1f9f, 0x1a56, 0xb264, 0xbf70, 0x2af2, 0x4390, 0x032a, 0x4383, 0x31cd, 0x18b8, 0x32ac, 0x41de, 0x43b5, 0xb2a0, 0x434f, 0xbf68, 0x466f, 0x3cd0, 0x4398, 0xb2a7, 0x4225, 0xb204, 0x4445, 0x4446, 0xba17, 0x4102, 0x11e8, 0x1f7d, 0xbf29, 0x438c, 0xb242, 0xb23c, 0x4016, 0x41ee, 0xbf15, 0x0f9b, 0x2845, 0x431f, 0x4012, 0xa905, 0x43bd, 0x40a7, 0xb21c, 0xbfa0, 0xb218, 0x41fb, 0x21c7, 0x1e5c, 0x2eba, 0x26a0, 0xba1e, 0x42a2, 0xbf23, 0x1c9b, 0x3ddb, 0x1ade, 0x421e, 0xb2ae, 0xa855, 0xbacc, 0x4067, 0x4670, 0xb299, 0x4633, 0x4009, 0xbfe2, 0x4005, 0x37ee, 0xbfc0, 0x0bf7, 0x439b, 0x4099, 0x425a, 0x1ebc, 0x445b, 0x4373, 0xba1c, 0x41b7, 0xb2d8, 0xb2b6, 0xbf34, 0x4273, 0x4121, 0x435f, 0x4158, 0x4021, 0x4231, 0x0c75, 0x4369, 0x3a94, 0xb247, 0x43c7, 0x4556, 0x4186, 0xb22d, 0xb25c, 0x42f0, 0x31d2, 0xbf46, 0x4062, 0x2ee4, 0x0296, 0x41f3, 0x4173, 0x4295, 0xb2db, 0x1d70, 0xbf84, 0x29a9, 0x2a9d, 0x41a0, 0x400d, 0xbf80, 0xb2a8, 0x1d70, 0x430d, 0xb22b, 0x4299, 0x0a23, 0x402a, 0xb275, 0xbf8c, 0x4405, 0x41bc, 0x43dd, 0xbf80, 0xb258, 0x4231, 0x1842, 0xa26e, 0x1e27, 0xb2ad, 0xb064, 0x4057, 0x46a1, 0x43e5, 0x439b, 0x4011, 0x41ce, 0x194e, 0xba1f, 0xb004, 0xb26b, 0x43ff, 0xb253, 0x4287, 0x402a, 0x40b4, 0x3a6d, 0xbfd4, 0xba49, 0x4172, 0x4043, 0x1e30, 0xb24c, 0xbad8, 0x4061, 0x418f, 0x1940, 0x4180, 0xbf7d, 0x23b5, 0x42a2, 0x40f9, 0x1e06, 0xbf99, 0xba15, 0x4164, 0xbac4, 0xb246, 0xa06a, 0x1923, 0x4553, 0xba1e, 0xbfa0, 0xab04, 0x4290, 0x4298, 0x3dc9, 0x4031, 0x40f5, 0x43aa, 0x329f, 0x45ae, 0xb209, 0xb2cf, 0xbf9d, 0x4123, 0x400e, 0xbae6, 0xb0c4, 0x05a3, 0x4353, 0x4057, 0xb060, 0x424c, 0x4189, 0xb2ec, 0x401f, 0xb2be, 0x1e8c, 0x4656, 0xb05d, 0x0fb9, 0x4069, 0x42b0, 0xb21f, 0xb277, 0x43f0, 0x4071, 0x419c, 0x22b1, 0xb02f, 0xa8c1, 0xb2ae, 0xbf84, 0x2d2c, 0x1df6, 0x4617, 0xae87, 0xb20f, 0x06bb, 0x420e, 0x0ba4, 0x4440, 0x422d, 0x4207, 0xb0ac, 0x431e, 0x1829, 0xb23b, 0xb23a, 0x41ed, 0x4016, 0xb023, 0x19db, 0x4351, 0xb2b8, 0x4151, 0xbf73, 0x4607, 0xaa70, 0x465a, 0x4242, 0xba3e, 0xb27f, 0x43ce, 0x0c36, 0xa72f, 0xb29d, 0x415d, 0xbfb3, 0xb0a3, 0xb2da, 0x429d, 0xb2df, 0x408c, 0x445a, 0x43fc, 0x4022, 0x4262, 0x1782, 0x4424, 0x40ef, 0x39b4, 0x1cf4, 0xb22d, 0x41a7, 0xba1e, 0x40be, 0x40ac, 0x36a1, 0xbae3, 0xbfb1, 0xac8c, 0xb23c, 0x40ca, 0xb203, 0x1d66, 0x117b, 0xb2a0, 0xba12, 0x401b, 0xaafd, 0xbaed, 0x1f75, 0x1c02, 0x42b4, 0x1f7f, 0x1d3c, 0xbad4, 0xbf24, 0x020b, 0x4226, 0x425f, 0xb27e, 0x3ed4, 0xba40, 0x2724, 0x4006, 0x107b, 0x2d4b, 0x464d, 0xb215, 0x41b4, 0x4172, 0x42dd, 0xa957, 0xba2e, 0x3f84, 0x1fdb, 0x290d, 0x43e3, 0xbf99, 0xb231, 0x422c, 0x02c8, 0xbacb, 0x432c, 0x1b38, 0xa296, 0x424f, 0x4640, 0x4175, 0xbf5b, 0x436b, 0x428b, 0x40a1, 0x4629, 0x40cd, 0xbfa4, 0x097e, 0x42e0, 0x4566, 0x4063, 0x3ef3, 0x464e, 0xb25c, 0x04b0, 0x4272, 0x41b1, 0x422d, 0xb070, 0x4254, 0xb034, 0x40b5, 0xb2a4, 0x41f0, 0x3546, 0xbf36, 0x40be, 0xba51, 0x437e, 0xba32, 0xbf60, 0x04b2, 0x16eb, 0x4092, 0x444f, 0x4634, 0x405a, 0x420b, 0x4342, 0x46b0, 0x1e8d, 0x4206, 0xba4c, 0xba29, 0x45d9, 0x1cd4, 0xbf8f, 0x42e7, 0xb0c7, 0x047c, 0x1f41, 0x4132, 0x4125, 0x23aa, 0x26ae, 0x294f, 0xae1f, 0x408d, 0x1aaa, 0xb052, 0xb2d6, 0xb2bf, 0x437e, 0xaf3c, 0x42ab, 0x2fe4, 0xb0d7, 0xbfc0, 0x38cb, 0x428e, 0xb2c3, 0xb081, 0x429f, 0xb220, 0xbf03, 0x4018, 0x1e76, 0x4167, 0x14fc, 0x42a0, 0x4642, 0x4543, 0xba6d, 0x1e81, 0x42fa, 0xbaeb, 0xbae0, 0xbac6, 0x0364, 0x0be5, 0xb2f1, 0x456b, 0xb225, 0xb2ba, 0x426a, 0x4359, 0x40db, 0xbf05, 0x4255, 0x147a, 0x4072, 0x0e8c, 0x0c55, 0x4178, 0xb28d, 0x4272, 0xba3b, 0x38c8, 0x41af, 0x46b8, 0x4075, 0x435c, 0x1b06, 0xbaeb, 0x3f2b, 0x0271, 0x310e, 0x424e, 0x013d, 0x4038, 0x2416, 0x35e0, 0x428d, 0xb0b8, 0x424a, 0x4380, 0x43e2, 0xbf3e, 0x4115, 0xb079, 0x4134, 0x3588, 0x20b7, 0xaa55, 0x43d3, 0x1ff2, 0x1ce9, 0x420e, 0x1ba2, 0x4007, 0x4327, 0xb0f2, 0xb082, 0xbfd4, 0x4390, 0xb233, 0xb2b2, 0xaa54, 0xb2d9, 0x4142, 0x4065, 0x41a9, 0x43fc, 0x42c6, 0x0daa, 0xb29e, 0xa5ae, 0xb288, 0x4241, 0xb237, 0x4303, 0x189b, 0xbf1e, 0x0a78, 0xbace, 0x0a87, 0x4154, 0x31e7, 0xbf3b, 0xba28, 0x419c, 0xba17, 0x3f48, 0x3ef8, 0x42f1, 0x4104, 0x4605, 0x413c, 0x4558, 0xb22a, 0xb287, 0xba47, 0x33a1, 0x430d, 0x42aa, 0xbf32, 0xb270, 0x42b3, 0x4392, 0x0bbe, 0x43bf, 0x41fc, 0x19ee, 0x2639, 0x42ca, 0x4219, 0xb2ec, 0x4600, 0x436c, 0xb011, 0xbaea, 0xb218, 0x412a, 0x41d8, 0x2cb9, 0x41b7, 0x4001, 0x0d3f, 0x1bc5, 0xbf03, 0x42d0, 0x4091, 0x40bd, 0x1ecd, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xaf3cc1f1, 0x05fac305, 0x3db327e1, 0x36ea8c1c, 0x589f9f57, 0xa4ce47b1, 0x8ee4ab13, 0xd31f4de3, 0xa8944d30, 0xd6eab546, 0xbc8af831, 0x69b223cd, 0x24711ddc, 0x33c795e8, 0x00000000, 0x500001f0 }, + FinalRegs = new uint[] { 0x00059360, 0x00000060, 0x00000000, 0x00002c9b, 0x00003d09, 0x0000005d, 0x00000039, 0x00000fff, 0xa89457bc, 0xffffffdb, 0x69b23619, 0x69b23619, 0xcd056b3d, 0xa89454e4, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x2427, 0x3f90, 0x2091, 0x2633, 0x40e5, 0x429e, 0xb272, 0x4572, 0xbacb, 0x358e, 0xb054, 0x426b, 0xb2ad, 0x17b5, 0xbae7, 0x403b, 0xb2af, 0x199f, 0xa0e1, 0x148a, 0xb068, 0x40ac, 0x1c34, 0x41cd, 0x4387, 0xbfa8, 0x42c7, 0xa9ac, 0x2d10, 0xbae3, 0x419c, 0xba60, 0x004e, 0xb0c7, 0x18c0, 0x42fd, 0x4021, 0x4592, 0x4365, 0x4591, 0xba50, 0x4440, 0xa24c, 0x438b, 0x1aea, 0x404f, 0x4171, 0xbf8f, 0x27e3, 0x4170, 0xb024, 0x4156, 0xb2c5, 0x4361, 0x2b01, 0x42af, 0x43a7, 0x1c67, 0xb088, 0xaf29, 0xba76, 0x45a6, 0x1daf, 0x06d5, 0x40b0, 0x40d8, 0xba5c, 0x1cb8, 0x185a, 0xa518, 0x43ce, 0xbf76, 0x4378, 0x4216, 0x43cc, 0x3321, 0x40af, 0xbae8, 0x41a2, 0x40ac, 0x4602, 0xbf00, 0x1919, 0x40d8, 0x1df2, 0x1eac, 0xbf07, 0x18df, 0x41ea, 0xba54, 0x4211, 0x2835, 0xb230, 0x2c47, 0xb2aa, 0x4118, 0x4013, 0x4327, 0x3e32, 0xbfd0, 0x4351, 0x41ba, 0x40c9, 0x4174, 0x181a, 0x423a, 0xb2ad, 0x424d, 0x4296, 0x4232, 0xbaf5, 0xba6d, 0xb0ef, 0xbf48, 0xbafe, 0x428b, 0xba5f, 0x3c65, 0xba32, 0x0379, 0x3701, 0x40bb, 0xbae9, 0x4164, 0x42f8, 0x435a, 0x042a, 0xb2d2, 0x4159, 0x425d, 0x46cb, 0x1db3, 0x4463, 0x4299, 0xbf0c, 0x37c3, 0x0331, 0x19be, 0x406b, 0x41ad, 0xb22c, 0x43bc, 0x4195, 0x4351, 0x4227, 0xb097, 0x42b8, 0xb2a9, 0x408e, 0xb0c5, 0x411b, 0xbfcd, 0xb200, 0x4113, 0x40d2, 0x35f4, 0x1bb3, 0xbf97, 0x1d53, 0x41ad, 0x1c7d, 0xb283, 0x43c9, 0x419b, 0x4263, 0xb0a4, 0x1f68, 0xa4bb, 0x4005, 0xbac2, 0x3f7c, 0x2f53, 0x4192, 0xb05d, 0x03fb, 0x4619, 0xba2e, 0xb2ad, 0xb038, 0x0315, 0xbf1c, 0x43ba, 0xb0da, 0xbfb0, 0x1836, 0xb08d, 0xb066, 0xb0a2, 0x4132, 0x1883, 0x4688, 0xac7b, 0x35bc, 0x4016, 0x40b4, 0x40fe, 0x09dd, 0xb2b4, 0x41b9, 0x4331, 0x46d8, 0xbfd4, 0x1b67, 0x427f, 0x0d12, 0x4141, 0x3ca3, 0x1bf4, 0x406f, 0xbf04, 0x425f, 0xa73c, 0x1fd5, 0x441b, 0xb04a, 0x0fa5, 0xbf23, 0x4407, 0xbfb0, 0x24d0, 0xb073, 0x4283, 0xbf16, 0x061a, 0x41d7, 0x2266, 0x41ef, 0xb2ef, 0x4324, 0x40ba, 0x0c29, 0xb08c, 0x4160, 0x43e0, 0x439a, 0x3f33, 0xb03e, 0xb264, 0x4275, 0x45f2, 0x070e, 0x41b4, 0x43d4, 0x4127, 0x2d76, 0x0c90, 0x4207, 0xbf97, 0x4250, 0x4221, 0x4017, 0xbac8, 0xaf4c, 0xbac6, 0x1d9f, 0x4148, 0xa3e9, 0xa244, 0x4013, 0x1ffe, 0xbf6a, 0x4031, 0x41d6, 0xaa58, 0x40cc, 0x328a, 0x437e, 0xb06f, 0xbaeb, 0xbf15, 0x4106, 0x06a9, 0x41fd, 0x0967, 0x3300, 0x22da, 0x41c8, 0x45d4, 0x4030, 0xba6e, 0xb042, 0x1fc5, 0xba63, 0xa4e4, 0x2c03, 0xbfa0, 0x3858, 0xb2c5, 0xb0d9, 0x4067, 0x42c8, 0x4225, 0x0778, 0xbf07, 0x42a6, 0xb273, 0x4048, 0x42a1, 0x1125, 0x41ea, 0x051b, 0x19e4, 0x42c7, 0x09cd, 0x1a48, 0x0be5, 0xb271, 0xbf9e, 0x4167, 0x4471, 0xb2ed, 0x4058, 0x423d, 0x219d, 0x43bc, 0x1bbb, 0x22c4, 0x1e06, 0x4153, 0xb267, 0x2bde, 0x1d6b, 0x40db, 0x41cf, 0x437e, 0xba1a, 0x166c, 0xb068, 0xb23a, 0xbf77, 0xb238, 0x1fde, 0x4335, 0x4640, 0x409e, 0x42b4, 0x434f, 0xbad5, 0x2036, 0x4314, 0xb2e2, 0xb2fa, 0xb020, 0x41f7, 0xba26, 0xb002, 0x1200, 0x4326, 0x32db, 0x156e, 0xb2e7, 0x424f, 0x4079, 0xbfab, 0x3d18, 0x447a, 0x10c9, 0x454b, 0xa020, 0xbf70, 0xbfa9, 0x4336, 0x3cc4, 0x2dc9, 0x423d, 0x46ab, 0x4150, 0x1847, 0x40ec, 0x0a6d, 0xbf19, 0x36e4, 0x436a, 0x436c, 0x1efc, 0x4200, 0xbfc5, 0xa281, 0xbac7, 0xb0e5, 0x41bf, 0x4119, 0x13e8, 0x4142, 0x43e0, 0x40bd, 0x0769, 0x41d6, 0xa91c, 0xb2db, 0x4078, 0x41e3, 0x40e5, 0x1b36, 0xb247, 0x42cb, 0xbf75, 0x4175, 0x4258, 0x4276, 0x4572, 0x4237, 0x1eac, 0xb2ca, 0x4171, 0x40b8, 0x42d1, 0x4293, 0x41bd, 0xbfe0, 0x34d8, 0x4024, 0x4128, 0xa7bb, 0x43c5, 0x4342, 0x42a0, 0x0306, 0x42cb, 0x4082, 0xbf70, 0x0fa2, 0x43ef, 0x45d6, 0x4300, 0xbf94, 0x077c, 0xbaf3, 0xa980, 0x33d4, 0x3b98, 0x18ab, 0x435f, 0x4310, 0xb0a1, 0x40e1, 0x149a, 0xaab9, 0x422a, 0xb2df, 0xbf80, 0x4592, 0x086f, 0x337d, 0x1dd5, 0x09b8, 0x197f, 0x45a0, 0x422d, 0xb084, 0xbf19, 0xbaf5, 0x04e9, 0xba29, 0x194a, 0x1a57, 0x32bd, 0xa80e, 0xb2c8, 0x3542, 0x2086, 0x422c, 0xba36, 0x46ab, 0x14bc, 0x20b4, 0x402f, 0x40ff, 0x46e1, 0x40a0, 0x43fc, 0x41ce, 0xbf4f, 0xb26f, 0x415b, 0x1523, 0x061f, 0xb090, 0x06ec, 0xb2eb, 0xb292, 0x426f, 0x42db, 0x167a, 0x43a3, 0x424f, 0xb247, 0x1b40, 0x1e25, 0x4087, 0xbad4, 0x431f, 0x435d, 0x006f, 0xbfb1, 0x40b3, 0x39d4, 0x4291, 0x165f, 0x42b8, 0x4200, 0x42aa, 0x43f5, 0x154e, 0xa5de, 0xba50, 0x3da7, 0x0d4e, 0xa9bf, 0xba1c, 0x402b, 0x418c, 0x4360, 0xb056, 0x3ef6, 0xbfcd, 0x4038, 0x1a87, 0x2473, 0x4317, 0x4285, 0x40c7, 0x40f6, 0xbf8b, 0x4077, 0x431a, 0x05ad, 0xba42, 0x4040, 0x16d8, 0x1913, 0xb265, 0x4436, 0x4102, 0x40fd, 0xa3f4, 0xba27, 0x4109, 0x40b2, 0xb035, 0x437e, 0xbf84, 0xba37, 0x189e, 0x0bee, 0xba76, 0xb2d0, 0xb204, 0xa150, 0x41a7, 0x41bb, 0xb295, 0x33f3, 0xbf80, 0x2f8a, 0x459b, 0xabe4, 0x11a0, 0x426b, 0xba78, 0xb272, 0x3718, 0x43c8, 0xbfb7, 0xb224, 0x1ec8, 0x06d7, 0x4327, 0x273c, 0xb2fe, 0x4294, 0x179f, 0x42a5, 0x4131, 0x43f0, 0xb25d, 0x4298, 0x42b2, 0x40b1, 0xbff0, 0x0bbe, 0xbf22, 0x2944, 0x4147, 0x0969, 0x433c, 0x1b91, 0x419b, 0xa0b4, 0x429a, 0xaa98, 0xa7ae, 0xbfad, 0x430e, 0xb2b8, 0x40dc, 0x403f, 0x19b8, 0x41f4, 0x1da0, 0x406c, 0x40aa, 0x1e72, 0xbfc0, 0x41e6, 0x43a2, 0x40d6, 0x1a1a, 0x191d, 0x1b1b, 0x2f8f, 0xb0d0, 0x1ce2, 0x4375, 0xb04b, 0xba0c, 0x4146, 0x4305, 0x40b4, 0xbf1b, 0x42d1, 0xb2ae, 0x4347, 0xb22e, 0x42ad, 0x41df, 0x1d6d, 0xb2b8, 0x409b, 0x38ce, 0xba78, 0x2add, 0x4377, 0x3fea, 0x42d5, 0xb2a0, 0xac6f, 0x3f00, 0x42e0, 0x42e4, 0x121d, 0x411a, 0xba59, 0x43b2, 0xb069, 0x4310, 0xb297, 0xbf88, 0x41ac, 0xb033, 0x1884, 0xb030, 0x42fa, 0x2a9b, 0x419a, 0xb270, 0x014f, 0x4100, 0x414a, 0x42b2, 0xb264, 0x43a7, 0x2e7d, 0x417d, 0x30eb, 0x4367, 0x429d, 0x3747, 0x3eda, 0x3129, 0xbfb4, 0x1f57, 0x0b56, 0x4039, 0xb26f, 0x462f, 0xb26f, 0x4181, 0x445f, 0x46c3, 0xb033, 0xbfb0, 0xb201, 0x422b, 0x4017, 0x431d, 0x4274, 0x40b4, 0x4481, 0x44a2, 0xaeb3, 0x42ad, 0x41ec, 0x40f8, 0xb2a7, 0x294e, 0xbf64, 0x41b6, 0xbac6, 0x423e, 0xb0b6, 0xba73, 0x45cc, 0x1d55, 0xb2f8, 0x3cee, 0x187b, 0x0e9e, 0x41b7, 0x42ce, 0xa773, 0xb2d4, 0x18dc, 0x207b, 0x4050, 0x4198, 0x3ee7, 0x40ea, 0xb243, 0x18a8, 0xbfe2, 0xba17, 0xbf00, 0x2883, 0xb06f, 0x40ce, 0x42fc, 0xba28, 0x4201, 0x1c4e, 0xbf80, 0xbf19, 0xae7d, 0x406a, 0x0bf7, 0xb0a2, 0xbf70, 0x4683, 0x4319, 0xbad2, 0xa731, 0x43ae, 0xba75, 0x426d, 0x2679, 0x2eeb, 0x149d, 0x45c5, 0x2848, 0xb229, 0xada6, 0x3a2c, 0x4002, 0x4098, 0xbf58, 0x4004, 0x43dc, 0x4585, 0x28bf, 0x4291, 0x4025, 0xba3d, 0xb242, 0x310b, 0xba76, 0xb2da, 0x4275, 0x41a4, 0x44b2, 0xb283, 0xb251, 0x3af7, 0x4188, 0x424d, 0xbf47, 0x38f5, 0x4602, 0x402d, 0x15f2, 0xb0e8, 0x4364, 0x2c36, 0x14be, 0x40a0, 0xbf4b, 0x40c8, 0x4150, 0x3428, 0x3375, 0xa475, 0x43c8, 0x4129, 0xbf00, 0x40de, 0x415d, 0x4235, 0x43fb, 0xb0f8, 0x4120, 0xbad0, 0x43b6, 0x4220, 0xb0d3, 0xba72, 0x0b42, 0xbf00, 0x421d, 0xb2d2, 0x0ccc, 0xb2ee, 0x2fb3, 0xaa42, 0xb0b1, 0xbf54, 0x4182, 0x40ce, 0x1fc6, 0xb291, 0x133c, 0x40ac, 0x405f, 0xbfca, 0xac9d, 0x4024, 0x0355, 0x43d6, 0x46b4, 0xb254, 0x1e21, 0x1824, 0x41f8, 0xbf56, 0xb0f4, 0x4396, 0x3777, 0xb01f, 0x42a3, 0xb2c6, 0x41ef, 0x0db1, 0xb0fb, 0xba3f, 0xa7bf, 0x4217, 0x0bf8, 0x4327, 0x4368, 0xbfc0, 0x42e1, 0xb0fe, 0xba62, 0x427f, 0xbf80, 0x3777, 0xb2f7, 0xbf16, 0x42a0, 0xaa78, 0x42d0, 0x41d5, 0xa44e, 0xbadd, 0xb0be, 0xb2e0, 0xba54, 0x4121, 0xbfd0, 0x2647, 0x4574, 0xba1c, 0x20a2, 0x1ae6, 0x2e30, 0x43e5, 0xb27d, 0xb0ee, 0x4053, 0xb2b6, 0x1c72, 0xbf70, 0x1562, 0xbfa9, 0x1f12, 0x4198, 0x4297, 0xb09b, 0x440e, 0x1eb9, 0x2ec0, 0xbfa4, 0x1c9d, 0xba34, 0x428e, 0x22cb, 0x4313, 0xb29f, 0x418d, 0xb27b, 0x4664, 0x1daa, 0xba05, 0xb230, 0xbf07, 0x314a, 0x42c9, 0x03a2, 0x4198, 0x1c5f, 0xb0aa, 0xb078, 0x404d, 0xb2b7, 0x4204, 0x4378, 0xbf09, 0xbfe0, 0x4307, 0x41fa, 0xba3f, 0xba74, 0xb042, 0x0f95, 0xb251, 0x44d8, 0x0fa5, 0x42a6, 0x0927, 0x1e0f, 0xbfd9, 0xb0ac, 0xa99d, 0x3a4b, 0x3435, 0x4154, 0x4046, 0xb231, 0xb201, 0x19c1, 0x418b, 0x0d1a, 0x4000, 0xb032, 0xbfc1, 0xb257, 0x2c04, 0xb029, 0xba42, 0x42ab, 0x2783, 0x1c91, 0x2f6d, 0x40cb, 0x1ac3, 0x4093, 0x45da, 0x434d, 0x4429, 0xbf55, 0x419c, 0x41c1, 0x43f2, 0x3a64, 0x4041, 0x183f, 0x4103, 0x4057, 0x41ec, 0x4268, 0x20b0, 0xb294, 0xba16, 0xb238, 0xbf0a, 0x4622, 0x432e, 0x3ed4, 0x46a2, 0xb239, 0xb2ca, 0xb2bb, 0xbfa3, 0x41fb, 0x400b, 0x42f9, 0x4296, 0xb215, 0x11b5, 0xb292, 0xbfcb, 0xab70, 0x4088, 0x18fc, 0x40fa, 0x31cf, 0x44c0, 0x1a3e, 0x20a8, 0x36f0, 0x421a, 0x4547, 0x4101, 0xb259, 0x4094, 0xa222, 0x1d0f, 0xbfc0, 0x0582, 0xb080, 0xb214, 0x4605, 0x407d, 0x1a4a, 0x400d, 0x4168, 0xb091, 0x1ef9, 0x424b, 0xba65, 0xbfc3, 0x4033, 0x4258, 0xb043, 0x4095, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x9f18068a, 0x03643f9c, 0x4d4c0930, 0xb5ba15e8, 0xff9da78d, 0x8731d796, 0x094b9e5e, 0x291d98f7, 0x1392fdc3, 0x4fe11ddf, 0x2f0cc7b5, 0x2d5bda6f, 0x2d85b32d, 0x081e2031, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0xffffffa0, 0xffffff96, 0x00000000, 0x00000060, 0x00000000, 0x00000000, 0x000000f0, 0xffffff99, 0x2fc23bbc, 0x2d85b418, 0x0000d42f, 0xc7ffffff, 0xf7e1d472, 0x081e259d, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xb235, 0x3f52, 0xb2ed, 0xa9b5, 0xbf53, 0x2bef, 0x1829, 0x2f6e, 0xba3a, 0xba63, 0x42c9, 0x41fa, 0xbf22, 0x415e, 0xa64f, 0xb072, 0xb2fe, 0x2efd, 0x4080, 0x0976, 0xb299, 0x4673, 0xb22c, 0x436d, 0xb2f3, 0x41f3, 0x02e9, 0x1955, 0x43fa, 0x411d, 0x4197, 0x41b0, 0x40cd, 0xbae5, 0xbadf, 0xbf42, 0x41ed, 0x41ea, 0xb240, 0xba2a, 0xb0eb, 0x4049, 0x40e4, 0xb223, 0x4361, 0x430c, 0x4144, 0x17db, 0x4053, 0xb284, 0xb0f5, 0x4074, 0x401b, 0x213c, 0x40eb, 0x4331, 0xb27f, 0xb2ba, 0xb2c6, 0x428e, 0x40c1, 0xbf8b, 0xb239, 0x41c2, 0x43a2, 0xb2f8, 0x4193, 0x4005, 0x432e, 0x4596, 0x44b9, 0x43a5, 0xb288, 0x3e1e, 0x449d, 0xb2b7, 0x1b4f, 0x2bc5, 0x43f4, 0xba10, 0x409c, 0x4090, 0x414c, 0x0672, 0xbfd4, 0x4356, 0x44b3, 0x424e, 0x1fb8, 0x4434, 0xb00d, 0x4045, 0x2fcd, 0x40fb, 0xba20, 0x41ae, 0x4266, 0xba12, 0x407b, 0x420a, 0x1a0f, 0x2281, 0x31c6, 0x1abf, 0x1d76, 0x41b8, 0x4055, 0x45c4, 0x43cc, 0x40c8, 0x4165, 0x163e, 0xbf1f, 0xba6c, 0xacaf, 0xba04, 0x412c, 0xb2a6, 0x42fb, 0xbaea, 0xb0ac, 0x446b, 0x4381, 0xa37d, 0xbf27, 0x4163, 0x42f8, 0xb24c, 0x41d5, 0x182b, 0x426e, 0x40e5, 0x3fa2, 0xbf9f, 0xba3c, 0x2e88, 0x4362, 0x3a89, 0xb2f3, 0xbacc, 0xb218, 0x3a16, 0x2c8f, 0x42f4, 0x43a4, 0xbf2c, 0x2016, 0x0105, 0x2c60, 0x425d, 0xbf17, 0x443d, 0x19cd, 0x21e8, 0x2803, 0x402d, 0x40e0, 0x1330, 0x1a90, 0x42e5, 0x151c, 0xb022, 0xbf47, 0x1f76, 0xba71, 0xb207, 0x40f2, 0x4172, 0x0965, 0x42af, 0x407f, 0x4162, 0x4355, 0x13d1, 0xbfa0, 0x0910, 0x4574, 0x42f4, 0x206f, 0x431d, 0x437c, 0x42a0, 0x4108, 0x4469, 0x21dc, 0x42d4, 0x3b95, 0xbad0, 0x420f, 0x1295, 0x428b, 0xbfd6, 0x05f7, 0xbf00, 0x4685, 0x4193, 0x463c, 0x4190, 0xbf5e, 0xba3e, 0xb2b8, 0xbafb, 0x07a5, 0x4320, 0x4161, 0x43d8, 0x004d, 0x40f4, 0x4040, 0xb253, 0x412b, 0xb230, 0x408f, 0xbf00, 0x021f, 0xbfa0, 0x42aa, 0x3978, 0x3bc6, 0x1e67, 0x406f, 0x4161, 0x4628, 0xad0c, 0x4232, 0xba05, 0xb2ea, 0xbf45, 0x2bbd, 0x224d, 0x43ce, 0x4262, 0xbfe1, 0x0ac9, 0x42d2, 0x4592, 0x2030, 0x3981, 0xbf7d, 0x3d39, 0xae51, 0xbfd0, 0x4161, 0x41e6, 0x414d, 0xba32, 0x4680, 0x311a, 0x18e4, 0x1f85, 0xbf64, 0x1a28, 0xbadf, 0x46e4, 0xa5c9, 0x4199, 0x3d07, 0x0825, 0xbf2d, 0x40e5, 0x21ec, 0x0b46, 0x430a, 0x1cd7, 0xa4e2, 0x4396, 0x4370, 0x2c8a, 0xbf04, 0xb273, 0xba2e, 0x423f, 0x428d, 0x1f54, 0xbfc6, 0x41cc, 0x1aa1, 0x402c, 0x4397, 0x4175, 0xb287, 0x4222, 0x46c3, 0x305a, 0x421f, 0x41c1, 0xb283, 0x43a0, 0x431f, 0x1ec0, 0x43fa, 0x0821, 0xba45, 0x405d, 0xb0e4, 0x407e, 0xbfbf, 0x00ab, 0xb22b, 0x309d, 0x4269, 0x4021, 0xb09e, 0xbf0d, 0xb251, 0xbae7, 0x42de, 0x41d1, 0x4132, 0xb08a, 0xa1ec, 0x1fc5, 0xb09a, 0x4332, 0xbfd0, 0x26f4, 0x4069, 0x42e6, 0xb275, 0x1ae3, 0x4350, 0xbf31, 0x3b04, 0x1f7a, 0x4152, 0xbaca, 0x1f2f, 0xb032, 0x1d51, 0x44cc, 0x4016, 0x18ce, 0x41fc, 0x1d3e, 0x409d, 0xbf4b, 0x4241, 0x2393, 0x4139, 0x4374, 0x43c6, 0x40fb, 0x41ba, 0x370d, 0xb205, 0x42d5, 0x4231, 0xb2c5, 0x4253, 0x42c6, 0x40ea, 0x38fd, 0xb2f0, 0x1002, 0x1676, 0x42bc, 0xbfdb, 0x1c6c, 0x0b4f, 0xb230, 0x41ee, 0x4233, 0x29f8, 0x191e, 0xba4a, 0x408e, 0x45bd, 0xb20f, 0x3a1d, 0x2891, 0x3659, 0x127e, 0x43a2, 0x167c, 0xb2b5, 0xb298, 0x43b5, 0xb2ba, 0x4344, 0x426e, 0xb259, 0x1293, 0xbf25, 0x4134, 0x2a59, 0x40d7, 0xafc9, 0x2b8d, 0x42da, 0xb03d, 0xb000, 0x4656, 0x420a, 0x2c8d, 0x065e, 0x4252, 0x43c6, 0x458b, 0xa5fd, 0x4111, 0x42fd, 0xbf85, 0xbaff, 0x4032, 0xb2b7, 0xb236, 0xb044, 0xb205, 0xbae5, 0x416e, 0x42e0, 0x412d, 0x1d40, 0x0fc4, 0x42e7, 0x428e, 0x4376, 0x1b9e, 0x263a, 0x42c3, 0x43a4, 0x08bf, 0x409a, 0x2124, 0x40f9, 0xb29c, 0xbf9d, 0x415e, 0x4003, 0x275f, 0xaf43, 0xba41, 0xa013, 0xb02a, 0x4398, 0x424f, 0x411f, 0x42ee, 0xa2da, 0x45f3, 0x440d, 0xb2ed, 0x429c, 0x4263, 0x1f66, 0x0f8d, 0x3500, 0xbf68, 0x40a6, 0xb2de, 0x43df, 0x40a1, 0xba52, 0xb0fb, 0x145d, 0x19ce, 0x43a1, 0x427d, 0x4493, 0x40cf, 0x4103, 0x463b, 0xb0cf, 0x4113, 0x458c, 0x1ac8, 0x030e, 0x42ef, 0x4381, 0x19ae, 0xa923, 0x4209, 0xbf91, 0xb01d, 0x199e, 0x1a59, 0x41d7, 0x4390, 0x414f, 0x4144, 0x19df, 0xa99a, 0x43d8, 0x4059, 0x400b, 0xba42, 0x4327, 0xb20f, 0x2ad1, 0xbfa6, 0x4335, 0xb2ef, 0x10be, 0x1845, 0xbfd8, 0xb2a8, 0x40f2, 0x405d, 0x42bd, 0x43c2, 0xabd0, 0x40f0, 0x4361, 0x413b, 0x1dcf, 0xb20d, 0x3168, 0xba5b, 0x43e1, 0x43a3, 0xb053, 0xba1f, 0xa340, 0xba35, 0x27b0, 0x45c6, 0x4093, 0x40d7, 0x43fd, 0x1f45, 0xbfb9, 0x424e, 0xaf65, 0x1215, 0xb247, 0xb220, 0x44d0, 0xb27c, 0xbf82, 0x232d, 0xa8e7, 0x42cb, 0x3cd3, 0x1aea, 0x4110, 0xbaee, 0x2c77, 0x4028, 0xb2f8, 0x2376, 0xb262, 0x0ccd, 0x4059, 0x40bc, 0xba54, 0x4367, 0xb2dc, 0x4012, 0x4012, 0x4221, 0x182a, 0x195a, 0xbf02, 0x4361, 0x04db, 0x427f, 0x44a2, 0x43c3, 0x41d8, 0x427f, 0x017f, 0xaffb, 0xbfc9, 0x426e, 0xb09d, 0x11ce, 0xb21b, 0xba66, 0x0ab4, 0x420b, 0x075f, 0xb25e, 0xbf95, 0x42bc, 0xb23a, 0x4070, 0x41b8, 0x43c6, 0xa5d7, 0x4271, 0x438a, 0xbf05, 0xac31, 0xbadf, 0x43e3, 0x1eb1, 0xb225, 0x4237, 0x4015, 0x425d, 0x07fc, 0xa809, 0x43b6, 0x43f8, 0x4330, 0x4028, 0xbf5a, 0x42c3, 0x1ce7, 0xb2eb, 0x42ef, 0x34ec, 0x4334, 0xba3d, 0x1d32, 0xb232, 0xbaca, 0xb23c, 0x4039, 0xadc4, 0xb0a5, 0xb2f2, 0x199a, 0x41f9, 0xb26c, 0x404a, 0xba1e, 0x40ac, 0xb044, 0xba2a, 0xbf35, 0xb015, 0xba51, 0xa8ac, 0x1963, 0x20b1, 0xbf39, 0x40ad, 0x40ed, 0x4151, 0x2d49, 0xb20b, 0xba2d, 0x1edd, 0xb211, 0x1f04, 0x4339, 0x4349, 0xb218, 0x4286, 0x43d1, 0xbfd3, 0xba2d, 0x41ce, 0x18d1, 0x4543, 0x1d0b, 0xa053, 0xbfe0, 0x15da, 0x2d45, 0x3d82, 0x4115, 0x04a6, 0x43b4, 0xb065, 0x282f, 0x0267, 0xa36f, 0x422f, 0x011c, 0xbf0e, 0x029e, 0xba71, 0xad75, 0x43c6, 0x4288, 0x42f7, 0x4616, 0xbf2b, 0x404e, 0x1a44, 0x30f1, 0xb09e, 0x4354, 0xb2c7, 0x4000, 0x3fd3, 0x18f4, 0x2e42, 0xb279, 0x4257, 0xb0f8, 0x42e9, 0x4065, 0x4021, 0xba0f, 0x4121, 0x419d, 0x40e4, 0x41c7, 0x259c, 0x4023, 0x435a, 0x1afd, 0x1f12, 0xba21, 0xbf4e, 0x4313, 0x254b, 0xb27e, 0x41f6, 0x4557, 0x4287, 0xb270, 0x42cf, 0xb07e, 0xbf94, 0xb2c9, 0x432f, 0x4397, 0xafbd, 0x3d40, 0xa8ae, 0x3690, 0x40b8, 0xafdc, 0xb064, 0x26fe, 0x43a3, 0x466f, 0x39e6, 0xb2ee, 0x0008, 0x4273, 0xba2e, 0x1d7b, 0x39d9, 0xbf48, 0xba57, 0x1c17, 0x0edb, 0x415e, 0xbfc6, 0x14c0, 0xbacf, 0xbacc, 0xacb0, 0x4359, 0xb21a, 0xbf5d, 0x415e, 0x41cf, 0x4423, 0x42f2, 0xb27b, 0x41ac, 0x4571, 0xbf00, 0xba7e, 0xb01c, 0x42db, 0x2818, 0x3d11, 0xbf90, 0x4186, 0xb0f6, 0xb0c3, 0xba49, 0x1c79, 0xbac8, 0x19b8, 0x407a, 0xa439, 0x1af9, 0x414e, 0xb227, 0xa3ab, 0xbf83, 0x4107, 0xba3e, 0x1e2f, 0xbfe0, 0x0b3b, 0x437d, 0x40b5, 0xba44, 0x45f2, 0x447f, 0xb2f9, 0xb2f6, 0x44a0, 0x4470, 0x4634, 0x42df, 0x1b5d, 0x426f, 0xbfb0, 0x4056, 0x1e3d, 0x1ad9, 0xa59d, 0xbfc0, 0x1404, 0xbf48, 0xb2f1, 0xbf61, 0x099c, 0x43c9, 0xba5a, 0xb278, 0x4002, 0x4083, 0x08c7, 0x42d0, 0x44eb, 0xa734, 0x462e, 0x4121, 0x4218, 0x4060, 0x402f, 0x415b, 0x464b, 0x441c, 0xabc9, 0xb255, 0xbaf4, 0xb2c5, 0x43c2, 0xbf7b, 0x1f61, 0x1dda, 0x42f6, 0xb22d, 0xbf72, 0x4604, 0x4384, 0x1ef3, 0x1d46, 0xbf05, 0xbafd, 0xb207, 0xaf8f, 0x4561, 0xba57, 0x406d, 0x421e, 0x420f, 0x41c5, 0x45e5, 0x1dbd, 0x1fc6, 0x41f5, 0x4203, 0x422f, 0x42c3, 0xb247, 0xb069, 0xbf5c, 0x414b, 0x41c3, 0x41fa, 0xbf00, 0xafae, 0x4215, 0xbf4a, 0x4000, 0x41b2, 0x1e3b, 0x2842, 0x133b, 0x42d9, 0x4165, 0x4262, 0x43b9, 0x2626, 0x41b8, 0x4295, 0xacbc, 0x2fcf, 0xbfe2, 0x2b38, 0x4020, 0x09e3, 0xbfae, 0x40bb, 0x4289, 0xb226, 0xbf17, 0xa215, 0x4326, 0x2a3b, 0x0daa, 0xbf98, 0xb045, 0x42b7, 0x462d, 0x43d2, 0xa357, 0xb20e, 0x401b, 0x0d93, 0xba3d, 0xba27, 0x3a82, 0xbf60, 0x1844, 0x4211, 0x4004, 0x42ac, 0xba4b, 0x282a, 0xb272, 0x42f8, 0xbf90, 0xa34e, 0xbf0f, 0x43f0, 0x2f72, 0xb06f, 0x461e, 0x1d80, 0x19fb, 0xada0, 0xa8f2, 0x3b75, 0xbf02, 0xb294, 0x0e2f, 0x0a11, 0xb21e, 0x1af3, 0x43b1, 0xa521, 0xb264, 0xbad0, 0x43fc, 0x42c9, 0x1877, 0x403d, 0x43fc, 0x0217, 0xbf0c, 0xb0cd, 0xb2a4, 0x4196, 0xb2ee, 0x2b9c, 0xb007, 0x41a1, 0xbf2c, 0xba44, 0xb06d, 0xba5d, 0x4063, 0x1c68, 0x45c8, 0xbf71, 0x4100, 0xb274, 0xb0c0, 0x4421, 0x3a36, 0x0655, 0x204b, 0x1fc2, 0xb2b6, 0x1c68, 0xb050, 0x1e86, 0x3b90, 0x3b9e, 0x2bb7, 0xa64d, 0xaf8e, 0xbf14, 0x4219, 0x074a, 0x43c7, 0x4179, 0x42ae, 0xb026, 0x4551, 0xbfc9, 0x427e, 0xa12c, 0x465b, 0x42c1, 0x419c, 0x2a70, 0x4200, 0x4217, 0x4437, 0x1842, 0xbf70, 0xb03c, 0xa47b, 0x40fd, 0x4623, 0x1ee8, 0xbf23, 0xb23b, 0x41a1, 0x40ff, 0x42be, 0x1b0b, 0x43a1, 0x4193, 0x4008, 0xba0d, 0x4392, 0x0f39, 0x46bc, 0x2b84, 0x462e, 0xbf0d, 0x426a, 0xb093, 0x3ef0, 0x2b53, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x284dbe99, 0x058f67cb, 0xb9dae013, 0xa1c1f55b, 0x52670afd, 0x96fcee8f, 0x8a2497af, 0xbd5ee36d, 0x01bd25ee, 0x57094880, 0x25509dd6, 0xeb081cb3, 0x37cda5c3, 0x8213e3ef, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x01ffe449, 0x00000000, 0x00000000, 0x65ffccb7, 0x000019a4, 0x49e4ff65, 0x49e4fe75, 0x00000000, 0x25508003, 0x57094880, 0x25509e4c, 0x000070de, 0x00000000, 0xffffa158, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x1d00, 0x407e, 0x3525, 0x2982, 0x43ea, 0x4352, 0x4151, 0xbf5e, 0x09a3, 0x4196, 0xa5c3, 0x3298, 0xb061, 0x1d31, 0x4086, 0xbf05, 0xb040, 0x1336, 0x1c1e, 0xa99b, 0x1174, 0x3dc2, 0x4117, 0x40bb, 0x43a0, 0xbaec, 0xb0de, 0xb229, 0x4450, 0xbf05, 0x4142, 0x1f56, 0x4160, 0xb258, 0x438b, 0x4160, 0x2c59, 0x433f, 0x13c8, 0x35ae, 0x4398, 0x1eb1, 0x236d, 0x406a, 0x41cf, 0xbfd4, 0x417a, 0x41a3, 0x42d5, 0xb2f5, 0x410e, 0x41b9, 0x45bb, 0x4083, 0x4189, 0x41b7, 0x01e2, 0x1f77, 0xba53, 0x2388, 0x4561, 0x40c8, 0xbf1f, 0x401d, 0x007d, 0x26a6, 0x24e5, 0xb209, 0x4623, 0x4346, 0x403a, 0xb0b1, 0xba2d, 0x41d9, 0x1af2, 0x444e, 0xb0ea, 0x40f1, 0x01d5, 0xa4bd, 0xbf91, 0x41d9, 0x425c, 0x414e, 0x40d0, 0xba56, 0xb2ce, 0x0d2b, 0x1984, 0xb0bb, 0x3257, 0x2ba1, 0x43d1, 0x1f95, 0x43d6, 0x40d2, 0x1fa9, 0xba3f, 0xbaca, 0x4294, 0x174e, 0x40ef, 0x434e, 0x4089, 0x445b, 0x19e7, 0x42a7, 0xbfb9, 0x4056, 0xac80, 0x2696, 0x43f6, 0x465f, 0x425d, 0xba41, 0x4092, 0x251d, 0x1e2e, 0x40af, 0x4397, 0xbfb0, 0xbf9f, 0x4166, 0x444d, 0x41c4, 0xbaf0, 0x316d, 0x43db, 0x414b, 0x32c1, 0x325c, 0x41ab, 0xbaf1, 0xbf60, 0x08d1, 0x313c, 0x4387, 0x4613, 0xbf0b, 0x19f0, 0x411d, 0x4141, 0x11c6, 0xb2c3, 0x1f54, 0x41d1, 0xb030, 0xb227, 0x1d06, 0x355b, 0xbf21, 0x0f72, 0x41b6, 0x1f75, 0xb048, 0x4382, 0x43ff, 0x411f, 0x1bbf, 0xb263, 0x434d, 0x40c1, 0x1f58, 0x2d9e, 0x403d, 0x4341, 0x4379, 0xb2a8, 0x30d6, 0xbf08, 0x4027, 0x4312, 0xbacf, 0x4021, 0x414c, 0x4304, 0x1878, 0x2a19, 0x4688, 0x40a8, 0x146a, 0x410c, 0xb02d, 0xb2bc, 0x2063, 0x3346, 0x438f, 0x43fd, 0x4160, 0x1a4f, 0x4373, 0x4128, 0x4132, 0xb2c2, 0xb0ce, 0xa0e0, 0xbf3f, 0x4282, 0x423e, 0x41fc, 0xac39, 0xbfb8, 0xbfc0, 0x1996, 0x3e39, 0x0f2b, 0x1ece, 0xb069, 0x40fc, 0xbaf6, 0xbfc2, 0x0f93, 0xb0f4, 0x4346, 0x08d7, 0xba06, 0xb200, 0xba43, 0xba72, 0x0fc7, 0x1b92, 0xba08, 0x41f0, 0x19fe, 0xb256, 0x1bac, 0x4222, 0xb062, 0xbade, 0xb0aa, 0x436d, 0x1faa, 0x0663, 0xbf80, 0xbf53, 0x4195, 0x408b, 0x4034, 0xb2c6, 0x1a0f, 0xb2e8, 0xb291, 0x40b8, 0xbaec, 0x117c, 0x27ef, 0x4084, 0x405a, 0x40e7, 0xafea, 0x40d1, 0x1157, 0x4121, 0xb06f, 0x4135, 0x2090, 0x16e4, 0x4318, 0x1c80, 0x310f, 0xb0e2, 0x40cf, 0xbf8e, 0x407a, 0xb09c, 0x388f, 0x4299, 0x1fdd, 0xb23f, 0xbf15, 0x1f0a, 0x40b2, 0x42de, 0x2ca0, 0x424f, 0xa934, 0x40be, 0x1dda, 0xb04d, 0x0c79, 0xbac7, 0x18ec, 0x4300, 0x1a08, 0xb219, 0x2eab, 0xbf8e, 0x1a3c, 0xb228, 0xad72, 0x403c, 0xb000, 0x44b2, 0x23a8, 0x41a6, 0x43d7, 0x404d, 0x0805, 0x43e8, 0x459b, 0xa1b1, 0x45b3, 0x4159, 0x44b4, 0x09a2, 0x42a1, 0xbf7e, 0xb2d5, 0xb007, 0x391a, 0x1bb2, 0x45d1, 0x4179, 0x420f, 0xba1f, 0x1ad5, 0xb22f, 0xb05f, 0xb0a6, 0xb269, 0x279d, 0x41c9, 0x4012, 0x0e08, 0xbf11, 0xba49, 0x417d, 0x3e9d, 0x436e, 0x4189, 0x42fb, 0x43e7, 0x4229, 0x40f3, 0x1a1e, 0x1ef2, 0x1061, 0x133f, 0xbac4, 0xb211, 0x42f1, 0x46ab, 0xb25e, 0x4387, 0x404f, 0x400e, 0xb038, 0x4167, 0x4564, 0xb201, 0x4073, 0xb03c, 0x234a, 0xbfae, 0x46e0, 0xb20d, 0x1862, 0xbf88, 0x42b6, 0xb029, 0xa541, 0x406d, 0x4236, 0x413e, 0x45e5, 0x1b98, 0x4224, 0x40ad, 0x41d4, 0x4197, 0x0ec7, 0xbac8, 0x425b, 0x0254, 0xb28b, 0x328c, 0xba1a, 0x42db, 0xba4f, 0x418b, 0x4104, 0xb265, 0x40d9, 0xbf39, 0x41df, 0xb23c, 0x43bd, 0x43b3, 0x4312, 0x41de, 0xbf2b, 0x365d, 0x195c, 0x40ac, 0x0fea, 0x1dc7, 0x2d8d, 0x420f, 0x43bc, 0x41ac, 0xb0d0, 0xb279, 0x25fb, 0x4386, 0xb095, 0xa6c5, 0x4106, 0x3591, 0x41c0, 0xb06c, 0x43f2, 0x4037, 0x4388, 0x0cab, 0x4065, 0xa28e, 0xbf02, 0x4209, 0x1874, 0xb0c1, 0x4085, 0xb2c9, 0xb099, 0xb045, 0x4183, 0x13ed, 0x1dd4, 0xa5f9, 0x42d7, 0x1691, 0x436c, 0xa84e, 0x40e4, 0x439e, 0xb2cc, 0x0e65, 0xb238, 0x42b6, 0x3c33, 0xbf73, 0x1e26, 0xb2f2, 0x4376, 0x43dc, 0xbacf, 0x40dd, 0x431d, 0xba58, 0x2b96, 0x463a, 0x3767, 0x42cb, 0xb0ec, 0x40d7, 0xb007, 0x454f, 0xb28f, 0x1ee1, 0xbf1c, 0xb20e, 0x454f, 0x46dc, 0x4380, 0xa1b6, 0xbfd2, 0x07c6, 0x0f93, 0x4294, 0x4159, 0xbf39, 0xb27e, 0x4396, 0x40b8, 0x27c1, 0x410a, 0xb05f, 0x4364, 0x0507, 0x41a0, 0xb2ea, 0x1c8d, 0x007f, 0x214d, 0x4191, 0x11b4, 0x4096, 0x01bb, 0xbf2a, 0xaff8, 0x2725, 0xb29d, 0x1c3a, 0x41a2, 0x0bea, 0x4337, 0x413b, 0x3f7a, 0x4493, 0x4058, 0x1f5f, 0x29c8, 0x1b7d, 0xba6b, 0x41a1, 0x1142, 0x1b05, 0xba5a, 0x422a, 0xb23e, 0xbfd3, 0x4171, 0x43c0, 0xb04b, 0x4417, 0x2814, 0x4068, 0x40b9, 0x4123, 0xa999, 0x44ba, 0x4375, 0x3e79, 0x42d2, 0x4104, 0x1b76, 0x3c21, 0x4150, 0x431e, 0xb2fe, 0xba47, 0xb279, 0xbf85, 0x415b, 0x1c20, 0x4477, 0xac9a, 0x28ba, 0x433f, 0xbfd0, 0xb0fb, 0x409b, 0x4299, 0xba02, 0x418e, 0xa2a0, 0x3aae, 0xb0ad, 0xa584, 0x1a84, 0xa29f, 0x3a28, 0xa99b, 0xba7b, 0xb0d9, 0xba18, 0x32f2, 0x460b, 0x441a, 0x3bb7, 0xbfd5, 0x4324, 0xb2bb, 0x4117, 0x4563, 0x1fd6, 0xba34, 0x429e, 0x4078, 0xaba3, 0xa4bb, 0x0e12, 0xb259, 0xb2f0, 0x443a, 0x4215, 0x42f5, 0xa264, 0xbaed, 0xba2b, 0xbf1b, 0xba59, 0x0098, 0xb205, 0x414e, 0x1e4e, 0x40a9, 0x42f0, 0x4052, 0x4485, 0xbf93, 0x15d8, 0x3ddc, 0x407d, 0x4381, 0x418a, 0xa2dd, 0xb29f, 0xb27d, 0xb0f2, 0x304b, 0x1d56, 0x46e5, 0xa925, 0x2fb8, 0x419e, 0x45ec, 0x41b4, 0x406d, 0x13e1, 0x426b, 0x4095, 0x4262, 0x15d2, 0x443f, 0x41d1, 0xbfcb, 0x2b00, 0x4319, 0xb229, 0xb21e, 0x4277, 0xbf35, 0xb28c, 0xb22d, 0x4558, 0xbad2, 0x43b9, 0xbac8, 0x404d, 0xba27, 0x4236, 0xb217, 0xacb6, 0x3991, 0xb271, 0xac00, 0xb2ba, 0x3bf7, 0x438b, 0xb2b9, 0x26a3, 0x431d, 0x2cf2, 0x421f, 0x23ac, 0x41db, 0x40c1, 0xb2c7, 0x1678, 0x427c, 0xbf8b, 0x3bd6, 0x43b7, 0x041a, 0x42ab, 0x21b7, 0x3236, 0x1892, 0x149e, 0xb27f, 0xb28e, 0x0325, 0xba7c, 0x43a8, 0x4242, 0x441c, 0xbaf4, 0x4210, 0x417f, 0x401e, 0x4252, 0xb2d2, 0x4321, 0x4062, 0xb2d4, 0x3dcc, 0xbfac, 0x40ab, 0xb00f, 0xb08f, 0x4148, 0x41e1, 0xb0a5, 0x41b9, 0x0630, 0x0865, 0x43e9, 0xb0c2, 0x4551, 0x4131, 0x41c5, 0x4014, 0x2bfc, 0x42a0, 0xbf4e, 0x407f, 0x41f6, 0x1b7a, 0x0220, 0x41ac, 0x099e, 0x46b4, 0x42d8, 0xb223, 0x423d, 0x42d1, 0x1d10, 0x41bd, 0x4188, 0x4165, 0x12c7, 0x4174, 0x4018, 0x41e2, 0x414e, 0xbae3, 0x4250, 0x43f9, 0x1ed9, 0x28a1, 0x0e3f, 0x408e, 0x43db, 0xbf9b, 0x4009, 0xb295, 0x4131, 0xb275, 0x4099, 0xb2b9, 0x0ea5, 0x456c, 0x1eaa, 0xa4ed, 0xb085, 0x4194, 0x4197, 0x0d72, 0xaffc, 0x4031, 0x0194, 0xadd6, 0xb270, 0x1eac, 0x1981, 0x2bdd, 0xba1f, 0xbf70, 0xbfbf, 0xbad7, 0x4073, 0x4454, 0xba1d, 0x446b, 0x419e, 0x4021, 0xbfd0, 0x40a7, 0x1e5e, 0xbf63, 0xb2cf, 0x462a, 0xba68, 0x0c00, 0xb02a, 0xb070, 0x1318, 0x2880, 0xb061, 0x0995, 0x4273, 0xb283, 0xbad8, 0x1b22, 0x40a1, 0x15d5, 0xbf84, 0x419e, 0xb274, 0xba71, 0x46d4, 0x45b1, 0x432e, 0x1fb3, 0x381a, 0x42d1, 0x4183, 0x4588, 0x408b, 0x4599, 0x43f9, 0x3324, 0x4338, 0xbf60, 0xbff0, 0xbff0, 0x1fb8, 0xbafc, 0x421c, 0x40d7, 0x40fb, 0xbf79, 0xbade, 0xba55, 0x2335, 0xb047, 0xbfb8, 0x403b, 0xb0a0, 0xbad2, 0x1c8b, 0xba65, 0x3c31, 0x407d, 0x1992, 0x2c0f, 0x0e6d, 0x2f2a, 0x4119, 0xb218, 0x0128, 0x4216, 0xa76e, 0x4390, 0x4045, 0x466f, 0xba55, 0x43e0, 0x3c13, 0x41f9, 0xbf91, 0x3368, 0x1acc, 0x4040, 0x4349, 0x406d, 0x408e, 0xbaf5, 0x42f7, 0x400c, 0xb21a, 0x4306, 0xb253, 0x41e1, 0x32fa, 0x3fdf, 0xa3d0, 0xb207, 0x402c, 0xb25a, 0xba45, 0xbfcf, 0xa252, 0x443a, 0x40c1, 0x0341, 0xbf05, 0xabce, 0x4354, 0x4559, 0xb000, 0x40c2, 0x02ff, 0xb288, 0x425d, 0x40ec, 0x4599, 0xba76, 0x4198, 0x1188, 0x385c, 0x193e, 0x411e, 0xba18, 0xbf47, 0x41ff, 0x1b0c, 0x4298, 0x40c5, 0xb235, 0xba44, 0x3560, 0x4346, 0x415a, 0x199e, 0x1b6e, 0xbad1, 0x45b6, 0x42a7, 0x4253, 0xa5a4, 0x4384, 0x420a, 0xb0aa, 0xbf11, 0x3731, 0x12c0, 0xadb1, 0x426f, 0x4310, 0x2243, 0x25ec, 0xb2e4, 0x0845, 0xbf97, 0x426f, 0x425f, 0x40d3, 0xb2c2, 0x3293, 0x4610, 0xa6f4, 0x4376, 0x1838, 0x4143, 0x179e, 0x4381, 0x1922, 0x15ff, 0x41b3, 0xb2fb, 0x188a, 0xbf3e, 0xb028, 0x3da2, 0x4227, 0x43ef, 0x4079, 0xb055, 0x429d, 0xba59, 0x2586, 0xb0ad, 0x19bf, 0x41fd, 0x4023, 0xb2a1, 0x43e8, 0xa823, 0x4266, 0x41f9, 0x42a1, 0xb228, 0x2c34, 0x41da, 0xb211, 0x16da, 0x2895, 0x4392, 0x2eea, 0xbf24, 0x4255, 0x1922, 0x40c6, 0xbaf3, 0xb2ff, 0x40dc, 0x4141, 0x437f, 0x43c4, 0xb283, 0x1b59, 0x4198, 0x3aba, 0x1f81, 0x1f5b, 0x429a, 0x4204, 0x4037, 0xaefc, 0x183d, 0x1309, 0x4563, 0x09a5, 0xb207, 0xba1f, 0xbfa0, 0x1cd7, 0xbfa3, 0x4258, 0xb205, 0x427a, 0x43b2, 0x41cc, 0x1797, 0x43de, 0xb227, 0x1a3c, 0x4038, 0x4355, 0x4033, 0xbf6b, 0x42f5, 0xb083, 0x4396, 0x3d30, 0x0b59, 0x38cb, 0x4319, 0x37ab, 0x425a, 0x251a, 0x41fd, 0xb2c8, 0x454b, 0xbaea, 0x40ec, 0x43fd, 0x4286, 0x445e, 0x4689, 0xb270, 0x4011, 0x2d71, 0xb0df, 0x4026, 0xa569, 0xbf2e, 0x4121, 0x4393, 0x40e1, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x0542461d, 0xf5f04e12, 0x8bc7b1fe, 0xbd178d21, 0x26456cae, 0xca50d85d, 0xf3453942, 0x72f63230, 0x00bbda65, 0xae6250a2, 0xb5896c69, 0xd8e9e883, 0x00e97798, 0xcb5e9574, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0xffffffc9, 0x00000000, 0x00000000, 0x00000000, 0xfffff79f, 0x00001998, 0xfffff389, 0xfffff84a, 0x00e9e597, 0x00000000, 0xb58955e6, 0xfffffff5, 0xb58955e6, 0x000000c1, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x0039, 0x3a23, 0x427e, 0x40e9, 0xb280, 0x0a50, 0xbfc9, 0x4114, 0x0bf0, 0x1254, 0xbf90, 0x2ad8, 0x440a, 0x414d, 0xbae8, 0xb243, 0xb01e, 0x2da6, 0xae3f, 0x4280, 0x445e, 0x4080, 0x4080, 0x3da8, 0x29dc, 0x2d10, 0x4373, 0x426e, 0xbf22, 0xb24b, 0x424a, 0x4332, 0x40a5, 0x46e3, 0xb28a, 0x42af, 0x4574, 0x1219, 0x1a9c, 0x43df, 0x438a, 0x43dc, 0xbfa9, 0x4318, 0xb28d, 0xb278, 0x0f00, 0xa9a1, 0x4359, 0xb0d5, 0xbf9a, 0x4283, 0xb2bf, 0x1f9a, 0xbfd0, 0x4054, 0x439c, 0xbfd0, 0x4243, 0xb026, 0x0245, 0xbf3b, 0x1b0d, 0x21ca, 0x1fff, 0x4199, 0x1a16, 0x425a, 0xb05a, 0x43cb, 0xbfd2, 0xba68, 0x434e, 0xb237, 0x438c, 0xa6ac, 0xbfcb, 0xbad2, 0x3246, 0x4255, 0xb263, 0x0abb, 0xaec5, 0x2008, 0xb049, 0xa1ea, 0xaa8d, 0xb2d9, 0x2152, 0xb27a, 0x4121, 0xb05a, 0xac70, 0x4138, 0xae5b, 0x2ee8, 0x18cd, 0xb253, 0x4143, 0xba11, 0x4483, 0x41b1, 0xbfcd, 0x1bac, 0x4222, 0x41e5, 0xa4ae, 0x1904, 0x4070, 0x1ff3, 0xb290, 0x40b3, 0xb031, 0x4098, 0x4624, 0x1a74, 0x4284, 0x05c1, 0x401d, 0x416b, 0xb238, 0xbf25, 0x434d, 0x1fa2, 0x40b2, 0x11e3, 0xb2cc, 0x413a, 0x41b7, 0xbf94, 0x43d4, 0x189b, 0xbacf, 0x4571, 0x430f, 0x1056, 0xba57, 0xb202, 0x1be7, 0x42eb, 0xb20e, 0x4544, 0x3f1d, 0xbfbf, 0x18ff, 0xbafa, 0x1a37, 0x4352, 0x0bfe, 0x1eec, 0x42e9, 0xbfd0, 0x419b, 0xbf6c, 0x3b65, 0x4115, 0x4051, 0xbfe0, 0x0eb4, 0x40b3, 0xa454, 0x4399, 0xb05f, 0x39c5, 0x4061, 0x1cb2, 0xbf0a, 0x369f, 0x210e, 0xb0b8, 0xbaf7, 0xa0a5, 0x4197, 0x45ce, 0x4073, 0x1918, 0xb2b8, 0x02a5, 0xb2e8, 0xbf79, 0xb24a, 0x43c6, 0x4034, 0xb08e, 0x46cc, 0x42be, 0x050b, 0x414a, 0x432e, 0x422b, 0xba14, 0x18f5, 0x372a, 0x239e, 0xb0e7, 0x1843, 0x4148, 0x465c, 0xb0b9, 0x3b44, 0x420e, 0xbf9a, 0x4299, 0x39a6, 0x2008, 0x2573, 0x4154, 0xb022, 0xae69, 0x417c, 0x3cbf, 0xb29d, 0x2b2d, 0xba06, 0x4175, 0xb222, 0xbfe4, 0xb205, 0x430a, 0xb211, 0x43ff, 0x0a2c, 0x41e5, 0x1575, 0x0b8d, 0xaf72, 0x1b99, 0x1e09, 0x4149, 0x40b8, 0x1d9c, 0x0c47, 0xba36, 0xb279, 0x4001, 0xb0d3, 0x1b1d, 0xbf13, 0xb219, 0x25db, 0x2745, 0x40c1, 0x1830, 0x4117, 0x428b, 0xb297, 0xb0b8, 0x406e, 0x40ec, 0x41f2, 0x42a1, 0x40da, 0x1df0, 0xaad8, 0x0ad3, 0x40f2, 0x42ae, 0xbf04, 0xba01, 0xae3e, 0xb26a, 0x1dbf, 0x442a, 0xb01f, 0x4051, 0x4147, 0xadb7, 0x420a, 0x1b8a, 0x18b9, 0xb0ab, 0xab57, 0xb072, 0x425d, 0x434a, 0xb2e0, 0xb280, 0xba33, 0x2c1d, 0x430f, 0x1080, 0xbf34, 0x41af, 0xba02, 0xb09a, 0x43d2, 0x1971, 0x3a78, 0x42e3, 0x4300, 0x4482, 0x1bbf, 0x02dd, 0x422a, 0x41a2, 0xbfdd, 0x4592, 0x41af, 0xba74, 0x15b3, 0x42cf, 0xba5b, 0xb2e1, 0xba39, 0x1e81, 0x461f, 0x415f, 0x4173, 0x4253, 0x4041, 0x405a, 0x1f00, 0xbf92, 0xa1f5, 0xb260, 0x456a, 0xb2ff, 0x3029, 0x45a5, 0x418f, 0xb01b, 0xba4d, 0x2658, 0x4384, 0xba5c, 0x3e1b, 0x35d4, 0x174a, 0x09ac, 0xb009, 0xb2ee, 0xb255, 0xbfda, 0x2f7e, 0xa637, 0x41d5, 0x1d37, 0xb0cc, 0x100a, 0x288d, 0x1d1f, 0x4129, 0xba7f, 0x19c8, 0xbf5f, 0x43ee, 0xb0d3, 0x4111, 0x4142, 0xb283, 0x1fd6, 0x4498, 0x43b5, 0x194a, 0x4124, 0x3b97, 0x4670, 0x427c, 0x45dd, 0x410c, 0x4377, 0xbadd, 0x4184, 0x424c, 0x40f7, 0xb25c, 0x4052, 0x2cdd, 0xbfa6, 0xbfe0, 0x416f, 0xb048, 0x4126, 0x40f3, 0xb0f5, 0x4153, 0x13cf, 0xbfd0, 0x41fd, 0x4072, 0x0639, 0x22bf, 0x3c4b, 0x0edb, 0x401d, 0x1f77, 0x4126, 0x4580, 0xbfe8, 0x1bd0, 0xb0fd, 0x2d03, 0x43e4, 0x4418, 0x405a, 0x41cd, 0x28aa, 0x425c, 0x4107, 0xa420, 0x4678, 0xba4f, 0xa155, 0x41e3, 0x4430, 0x400f, 0xba18, 0xba08, 0xb219, 0xbad8, 0xb2e6, 0xbfa2, 0x0925, 0x40f6, 0xba5f, 0xb0e6, 0xbf70, 0xb253, 0x40fd, 0x430e, 0x3042, 0x40ad, 0x4016, 0xba0f, 0xbf5e, 0x4544, 0x3faf, 0x464f, 0x1cb3, 0xbf90, 0x432c, 0x277c, 0x41e9, 0x42d3, 0x4181, 0x4149, 0xb27c, 0x19f6, 0x1e2f, 0x4118, 0x426f, 0xb214, 0xba78, 0x418b, 0xa945, 0x15e6, 0xba23, 0xb242, 0xbfd0, 0x4322, 0xbfab, 0x41cb, 0x4194, 0xba49, 0x4199, 0x42c0, 0xb2b0, 0x43d2, 0xb21d, 0x3ad9, 0x3b7a, 0xba1c, 0xbff0, 0x1b38, 0xbfc6, 0x42a1, 0xba7b, 0x4051, 0xba14, 0x413e, 0xbf85, 0xbfc0, 0xba1a, 0x43cc, 0x44f5, 0xb013, 0xba6c, 0x3c27, 0x4057, 0xb2f1, 0x1bb7, 0x1d41, 0xb28f, 0x42a1, 0xbfda, 0x1d46, 0xb0da, 0xb244, 0xa4aa, 0xb2b6, 0x1b67, 0x2f35, 0x1f03, 0x41cb, 0xb271, 0xbac4, 0x059f, 0x423a, 0xba22, 0x40e9, 0xb0e1, 0xb01b, 0xa845, 0xb063, 0xbf8c, 0x43a3, 0x40b4, 0x39d6, 0x4175, 0x4282, 0xb0de, 0x4037, 0x41e0, 0x42aa, 0xba1c, 0x41ae, 0x1d11, 0xbfa4, 0x186a, 0x4310, 0x432e, 0x1856, 0xb229, 0x4079, 0xa1d2, 0xbf80, 0x4665, 0x4225, 0xbfe2, 0x4372, 0x42a3, 0x448c, 0xbf7c, 0x4293, 0xba76, 0x2941, 0x42ff, 0x15ef, 0xbaee, 0x1735, 0x407c, 0x40c1, 0x1fab, 0xbf44, 0x43cd, 0xb2a0, 0xb2ad, 0x405e, 0x43d4, 0x40a5, 0x4128, 0x2340, 0x43c0, 0x027b, 0x434d, 0x1e76, 0xba2d, 0x4119, 0xbf0d, 0x1c53, 0x0600, 0x40dd, 0x4183, 0x1a96, 0x4167, 0x1c55, 0x4333, 0x46ad, 0xb22e, 0xb244, 0x41ee, 0x40da, 0x4063, 0x022b, 0xb26b, 0x43f3, 0x435a, 0x186e, 0xba0f, 0x1894, 0x4212, 0x4073, 0x41ca, 0x4368, 0xbfb9, 0x2971, 0xbf60, 0xb2f6, 0x43bc, 0xbf9e, 0x4651, 0xb224, 0xa186, 0x40ba, 0x415f, 0x42b0, 0xb044, 0x410c, 0x4035, 0x46c3, 0x3fa3, 0x400a, 0xbf60, 0xbf07, 0xb00a, 0xb263, 0x1cd7, 0x4119, 0x463a, 0x42c9, 0x4307, 0x1f80, 0x030c, 0xb2f0, 0x427c, 0x4619, 0x438a, 0xbfb1, 0x4178, 0x4197, 0x425b, 0x41fd, 0xb26f, 0x4101, 0xb08d, 0x2e09, 0xb072, 0x413a, 0xb281, 0x1eee, 0xaac3, 0x4175, 0x405d, 0x41d8, 0x4665, 0x418b, 0x458a, 0x4059, 0xac9b, 0x3661, 0x4332, 0xbf3a, 0xba5d, 0x4332, 0x4458, 0xb290, 0x1aa8, 0x42db, 0xa756, 0xb2df, 0x284d, 0x1cf5, 0x4148, 0x416b, 0x4445, 0x4114, 0x44aa, 0x4455, 0x40b5, 0xb01a, 0xb249, 0xbad0, 0x1fec, 0x4025, 0x19c2, 0x2014, 0x40c4, 0x42b8, 0xbf93, 0x415b, 0xb08a, 0x4363, 0x43ee, 0xba7e, 0xb2ec, 0xb28d, 0xb0e9, 0xb069, 0x43f1, 0xb2d9, 0xb254, 0x436c, 0x4479, 0x4345, 0xb0d7, 0xba17, 0x4213, 0x46d1, 0x30a5, 0x2e55, 0xbf4f, 0xba63, 0x4426, 0x363e, 0xa9e7, 0x4294, 0xb257, 0x20d4, 0xb22a, 0x1b17, 0x43e6, 0x419e, 0x40cc, 0xaf14, 0xba0b, 0xa0e6, 0xb086, 0x42a3, 0x28ed, 0x43a1, 0x427c, 0xb2f0, 0x1ccc, 0xba6c, 0x41b9, 0xb022, 0xba4f, 0x19a4, 0xbf12, 0x40a4, 0x412b, 0x412d, 0x1e6a, 0x4122, 0x412c, 0x4365, 0x0f73, 0xb2b8, 0x40ca, 0x07ee, 0x3940, 0x200e, 0x377a, 0x4167, 0x40d2, 0x4311, 0x4351, 0x433c, 0x1749, 0x1c33, 0x4137, 0xbf90, 0x1fc8, 0xbfc7, 0x43d6, 0x4294, 0x4338, 0xb27b, 0x4268, 0x2cd4, 0xbf1f, 0x41af, 0xbac7, 0x413b, 0x4099, 0x411f, 0x4270, 0x43e7, 0x40bc, 0xbfdc, 0x425a, 0x4489, 0x4004, 0x4162, 0x2867, 0x454d, 0x1607, 0xba54, 0xb2e4, 0x40c2, 0xbff0, 0x1641, 0xb2b1, 0xbf1d, 0x4560, 0x4371, 0xb2ca, 0x2dc0, 0x41ae, 0x4290, 0x43a2, 0x02d6, 0x1e8f, 0xa801, 0xb054, 0xb03f, 0x4204, 0xba5e, 0x1d4b, 0x42ba, 0x11a0, 0x1f7f, 0xba40, 0x421d, 0xba0d, 0xbacc, 0x434c, 0x4369, 0xbfb5, 0x0d27, 0x2c32, 0x4057, 0x4215, 0x438a, 0xb26c, 0xb028, 0x438a, 0x432d, 0x40a6, 0x0e5c, 0xb224, 0x410c, 0x40f8, 0xa486, 0x421f, 0x0fd8, 0xbf51, 0xab22, 0x135b, 0x1bad, 0x1afa, 0x43de, 0x43cc, 0x2f57, 0x41ff, 0xb27f, 0x15b7, 0x4362, 0xbf03, 0x4084, 0x444e, 0xb21c, 0x4285, 0x41d1, 0x199f, 0x4199, 0x1b47, 0xb2ee, 0x4150, 0xbfd5, 0x432e, 0xa445, 0x40b2, 0x41f0, 0xbac4, 0x45a9, 0x1ee8, 0x0504, 0x410d, 0x34f6, 0x4096, 0x34d1, 0xb26e, 0x439d, 0x24b8, 0x4299, 0x1226, 0x4009, 0xbf46, 0xbff0, 0x42a0, 0x40f7, 0x42b7, 0x43f7, 0x4164, 0xaa14, 0x424a, 0x45d8, 0x414c, 0xbf9d, 0xb218, 0x4223, 0x4430, 0x4036, 0x4163, 0xb0ac, 0xbfaa, 0x409d, 0xb2db, 0x0ae2, 0xb23b, 0xb218, 0x1a2c, 0x441b, 0xa0d2, 0x0e6a, 0xbfb9, 0x3fec, 0x22c2, 0x3012, 0x43ec, 0x1c2c, 0x4264, 0x42f9, 0xb2a2, 0xba13, 0xb270, 0x41dd, 0xb28f, 0x3315, 0x4267, 0x404a, 0x40b8, 0x4077, 0xbf8a, 0x4287, 0x0ce2, 0x406e, 0xbf2b, 0x1ab6, 0xb0aa, 0xbf90, 0xb2c4, 0x4666, 0x1af4, 0xba17, 0x4049, 0x4179, 0xb01b, 0x4154, 0x42e8, 0xb2dc, 0x431f, 0x4029, 0x4442, 0x19ff, 0x3b2a, 0x1a44, 0x1ac3, 0x160c, 0xb06d, 0x31fd, 0xb073, 0x4313, 0x3f1b, 0x43a2, 0xb22f, 0xa562, 0xbfb1, 0xba65, 0xb0d2, 0x1c51, 0x4158, 0xb27e, 0x40b8, 0x43fd, 0x4294, 0xb042, 0xba17, 0xb2ff, 0xbfdf, 0x400f, 0x3598, 0x404c, 0x4101, 0xb202, 0x2e65, 0x417a, 0xb05b, 0x1cd6, 0xab8e, 0x0d74, 0xbff0, 0x3f0d, 0xbadb, 0x0dce, 0x435f, 0xb269, 0xb291, 0x42e0, 0x46bd, 0x41c9, 0xbf63, 0xac82, 0x194d, 0x1cb3, 0x42db, 0xba49, 0x41dd, 0x40b3, 0x1c57, 0x43c3, 0xbf9c, 0x1d84, 0xb2bd, 0xb2c2, 0xbfe2, 0x1df6, 0x405c, 0x4022, 0x4173, 0x423f, 0x40ce, 0x4198, 0x42d3, 0x1cac, 0xbf65, 0x42bf, 0xba1c, 0x1889, 0x42c2, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xaa459540, 0xb7417607, 0x90b1ad8e, 0x16822de0, 0x187003f9, 0x872131ae, 0x3224d4e1, 0x42eca858, 0x011e7d28, 0x6dc708e1, 0xb65556a7, 0xb63bce08, 0xc50698a6, 0x40eb158e, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x023ec6b9, 0x0080b091, 0x00000060, 0xfee09ca6, 0x00006364, 0x00006362, 0x00000000, 0x00006362, 0x011f635a, 0xc060e558, 0xc060e558, 0x011f635a, 0x6dc72045, 0x00028578, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xbac5, 0x1cc4, 0x3c3f, 0x43b2, 0x408c, 0x4133, 0x4354, 0x4190, 0xbf13, 0x4393, 0xba2e, 0x41f1, 0xb2db, 0xaaa2, 0x4338, 0xbf80, 0xba55, 0x211b, 0x37fa, 0x407b, 0x4103, 0x3bfe, 0xb08f, 0xbf09, 0x413b, 0xa520, 0x4223, 0x1e36, 0xb047, 0x4388, 0x44ed, 0x43b3, 0x180a, 0x41fd, 0x1940, 0x4037, 0x4136, 0x4075, 0xa15f, 0x3b50, 0xb2e2, 0x0a6b, 0x343e, 0xb0b8, 0xae5e, 0x1666, 0xa2f2, 0x42fa, 0x4045, 0xbf3b, 0x4490, 0xb250, 0x4197, 0x406f, 0x1dd6, 0xa912, 0x41b6, 0xa73c, 0x41a3, 0xbf70, 0x40be, 0x1c04, 0x42b0, 0x2e98, 0x27d6, 0xba6a, 0x1eed, 0xb21d, 0x05ea, 0xbfa8, 0xa64d, 0x41e5, 0x42f8, 0x4241, 0xbf90, 0x40b6, 0x4301, 0x3ef6, 0x2451, 0xb076, 0xbf6e, 0x45a5, 0x1fc7, 0x2e04, 0x2934, 0x4098, 0x42e1, 0x102b, 0xbfcb, 0x2cb4, 0xb2ca, 0x1679, 0x2d5a, 0xba39, 0xb280, 0xa5d9, 0x1f1e, 0x3bd4, 0xb20d, 0x4089, 0xba3f, 0x0ef3, 0x392f, 0x40b4, 0xba51, 0x44c8, 0x4340, 0x402b, 0x4400, 0xbf3c, 0x4655, 0x4011, 0xa8bd, 0x057f, 0x432a, 0x1827, 0x434b, 0xbfab, 0xb25c, 0x1467, 0x4101, 0x40b2, 0x43e0, 0x43e1, 0x404c, 0x46bc, 0xb23b, 0x3c33, 0x081d, 0xb006, 0xbaca, 0xb2dc, 0xb202, 0xa346, 0x43a2, 0xb072, 0x4092, 0x1e9d, 0xa2b5, 0x4111, 0xbfac, 0xaf78, 0x2c58, 0x1f71, 0xa7c8, 0xb2ac, 0x3fd4, 0x45cb, 0x44b2, 0x196d, 0x4007, 0x42b5, 0x1953, 0x4049, 0xb0d6, 0xb2e3, 0x4384, 0x1f22, 0x163d, 0xb288, 0xba2f, 0x4226, 0xb292, 0xbf66, 0xba2b, 0xbac8, 0xb0fa, 0xb079, 0x1205, 0xb2f9, 0x43de, 0x3cff, 0x424e, 0x40e0, 0x0cf1, 0x4073, 0x4390, 0x4082, 0x4000, 0x4084, 0xabf0, 0xabd0, 0x0886, 0x466a, 0x41ca, 0xb0ed, 0xb2ec, 0x41fc, 0xbf4b, 0x4478, 0xba51, 0xb2df, 0x419b, 0x1e34, 0xb2f8, 0xbf49, 0xaa3d, 0x41c6, 0x41a3, 0x1aba, 0x40b2, 0x44bd, 0x3d3a, 0xb2bf, 0x28a6, 0x4012, 0x438e, 0x43bc, 0x0ad5, 0xbaca, 0xbf5e, 0x4445, 0x42dc, 0xba1a, 0xa19c, 0x42c9, 0xbfd5, 0x40a1, 0x4048, 0x1893, 0xabd3, 0x404f, 0x4244, 0x4187, 0x4621, 0x4330, 0xb01f, 0x46d3, 0x0861, 0xb2e6, 0x415f, 0x3a61, 0xb24d, 0x1e93, 0x1bac, 0x42d8, 0xb280, 0x40af, 0x40b7, 0x4045, 0xbfbf, 0x40ec, 0xba4c, 0x4494, 0x1454, 0x3538, 0xb2c8, 0xba51, 0x0e63, 0xb276, 0x1d0e, 0x20ee, 0x4337, 0x4204, 0x401e, 0x43b4, 0xb23f, 0x12a9, 0x229b, 0x44ed, 0x444d, 0x407a, 0x1ec5, 0x45c4, 0xba1a, 0xbfc3, 0x3dad, 0x40f6, 0x4085, 0x2377, 0x4418, 0x43a7, 0x2f7d, 0x4071, 0xbfaa, 0x129b, 0xbae5, 0x4691, 0x1be4, 0x1d41, 0x43ce, 0x35f0, 0x1bda, 0x411e, 0xba1a, 0xb2dc, 0x40f8, 0xa3d4, 0x431c, 0x4368, 0xba34, 0x1c65, 0xba13, 0x439f, 0x1b26, 0x4477, 0x40e3, 0x43a2, 0xbf00, 0xaab3, 0xba61, 0x43ab, 0xbf19, 0x4145, 0x40af, 0x182b, 0xb2b4, 0xba74, 0xb0a9, 0x4111, 0x448c, 0x4107, 0x4069, 0x0494, 0x41ef, 0x1af8, 0x42c0, 0x424d, 0x40a4, 0x43da, 0x4246, 0xb260, 0x41cb, 0x403a, 0x424c, 0xba33, 0x43a4, 0x37d0, 0x4390, 0x189b, 0xb2a0, 0xb052, 0xbf83, 0xa25c, 0x24d7, 0xb01e, 0x34d1, 0x3654, 0xb239, 0x1874, 0xba5e, 0xb27a, 0xb24e, 0x406b, 0x43a9, 0x45e2, 0xbafa, 0x4258, 0x393e, 0xbf7b, 0xb2d2, 0xa24e, 0xb293, 0x4027, 0x4268, 0xbf60, 0x4100, 0xb08b, 0x4062, 0x16d0, 0xbf71, 0x4132, 0x4398, 0x41d6, 0x4074, 0x059c, 0x437d, 0xbaf1, 0x400f, 0x4239, 0x0f8a, 0x4387, 0x1e07, 0x40b2, 0xb0a9, 0x4168, 0x43f8, 0xbf84, 0x0189, 0x1606, 0xb0fe, 0x41fa, 0xbfc5, 0x418e, 0x1a73, 0x42bf, 0x4269, 0x41b7, 0xbf95, 0x1abf, 0xac8e, 0x41b7, 0x4249, 0x41e2, 0xb06d, 0x186f, 0x4097, 0x4333, 0x4223, 0x424e, 0x46c4, 0x435b, 0x1c82, 0x416c, 0x40f2, 0xb000, 0xb09b, 0x434a, 0xbac7, 0x4255, 0x365a, 0xb27e, 0x4223, 0xbf2a, 0x4113, 0x411f, 0x4234, 0x3feb, 0xb2bf, 0x436a, 0xb0cd, 0x18e6, 0xa251, 0xbf72, 0x196c, 0xbac9, 0x190f, 0xb065, 0x4222, 0xbf9f, 0x0e34, 0xb234, 0x04ae, 0x401a, 0x19e3, 0xa8be, 0xba60, 0xb0b5, 0xbfd6, 0x4288, 0x375c, 0xb2d6, 0x4269, 0x419e, 0x46c5, 0x228a, 0x467e, 0xba01, 0x2f41, 0x415e, 0xb0d3, 0xb0de, 0x306a, 0x41b2, 0xb251, 0x432f, 0x430c, 0x189f, 0xbf69, 0x4061, 0x4423, 0x0dcf, 0xb272, 0x43e9, 0xa0e9, 0x03b4, 0x4606, 0xba5c, 0x42e5, 0x442d, 0x428f, 0x4044, 0x2c64, 0x4188, 0x4250, 0xa133, 0xbfe1, 0x4467, 0x4379, 0xb209, 0x43ef, 0x10af, 0x42b5, 0x43cd, 0xbad7, 0x4385, 0x1434, 0x4371, 0x44a9, 0xbaff, 0xb2c5, 0xb028, 0x46b1, 0x249e, 0x1a7c, 0x3608, 0x1561, 0x19da, 0x420f, 0x38a2, 0xa417, 0xbf8f, 0x1805, 0x18f3, 0x2abc, 0x44d3, 0xb210, 0x422d, 0xb29e, 0xb2d8, 0x4463, 0x4148, 0xa528, 0x407c, 0x40a0, 0x433e, 0xbfc1, 0x43dc, 0x38b7, 0xb232, 0x43f0, 0x3de3, 0x3077, 0xbf9d, 0x3ccd, 0xb0be, 0xb2c7, 0xba66, 0x420c, 0xb21e, 0x22d8, 0x2e67, 0xba0c, 0x4293, 0x2f4a, 0xba64, 0x413f, 0x1a2d, 0x4177, 0x424a, 0x4258, 0x4280, 0xa986, 0x3839, 0x4171, 0xb055, 0x4330, 0x4233, 0x41a5, 0x1f63, 0xbf78, 0x43c6, 0xb24c, 0x4199, 0xb2bd, 0x42a6, 0x0c98, 0x1f23, 0xb0ce, 0x4159, 0x1e07, 0xbad3, 0xbae3, 0x42db, 0x41bc, 0xb227, 0xba6d, 0x42f3, 0xbf52, 0xb0f4, 0x45c9, 0x4358, 0x4012, 0x411e, 0x41e5, 0x0b04, 0x41af, 0x43d1, 0xbf1e, 0x2603, 0x466d, 0xba17, 0x1fb1, 0x4363, 0xb21e, 0x4221, 0xb093, 0xbadd, 0xba2c, 0xb2a4, 0x43a0, 0xb2f0, 0xb221, 0x4601, 0xbf2b, 0x4629, 0xb07b, 0x0cb6, 0xb262, 0x439e, 0x40d0, 0xb02b, 0x337d, 0xbf03, 0x4683, 0xba7e, 0x18b9, 0x425b, 0xb0e9, 0x467f, 0x22c2, 0xbfba, 0x400a, 0x426b, 0x43b5, 0x3e16, 0x417e, 0xa4af, 0x40c7, 0x4379, 0x4079, 0x2d9d, 0x4374, 0x4333, 0x412e, 0x2fb4, 0xbf70, 0x4295, 0xae48, 0x1be3, 0x41f9, 0xbfd8, 0xb074, 0x4275, 0x45b5, 0x4133, 0x40a1, 0x402a, 0x405f, 0xbfbd, 0x423b, 0x463c, 0xba19, 0x439b, 0x3b6f, 0xb23f, 0x0f1d, 0xba56, 0xb261, 0xba54, 0xa2ae, 0x46bd, 0x43f6, 0x4017, 0xb2b0, 0x4435, 0x40bd, 0x1e76, 0x413e, 0x410b, 0x411b, 0x1a0e, 0x12dc, 0x1d50, 0x42d7, 0x420f, 0x40e3, 0xb2d6, 0xbfa6, 0xb242, 0xb2eb, 0x40e3, 0x4368, 0x24cc, 0x455d, 0x06b0, 0xbf49, 0xb0a0, 0x3dec, 0xba24, 0x432a, 0x4296, 0x40f9, 0xbff0, 0xa816, 0xb27b, 0x4053, 0xb095, 0x42a4, 0x46e2, 0x19c6, 0x421d, 0x406d, 0xbfc1, 0x4226, 0x4101, 0xbafc, 0x427e, 0x29ba, 0x2f83, 0x41b6, 0x4315, 0x4252, 0xbfa0, 0x45f0, 0x0cbd, 0x40dd, 0x1a3c, 0x43c3, 0x4020, 0xbacd, 0x414d, 0x41e7, 0x4323, 0xb25a, 0x1983, 0x17c3, 0xbf8a, 0x40ab, 0x4601, 0x44f9, 0x4080, 0x3d43, 0x09f7, 0x43de, 0x4637, 0x4052, 0x40dc, 0xbaeb, 0x446a, 0x1e28, 0x4007, 0x359a, 0x45ae, 0xab99, 0x23d8, 0x1cb7, 0xb29f, 0x41c3, 0x4463, 0xbf4f, 0x1f1a, 0x42b4, 0xbacd, 0xb27c, 0xb25b, 0x1aa8, 0xadce, 0xbfd0, 0x352d, 0x4383, 0xb26b, 0x43c0, 0xb201, 0x425c, 0x4356, 0x40a4, 0x43f7, 0x40f8, 0xbf6a, 0x4149, 0x3093, 0xba79, 0xba61, 0x431d, 0x1bb2, 0x405d, 0x09e3, 0x43c3, 0xb0da, 0x4116, 0x4561, 0x4094, 0xbfe0, 0xba18, 0xbaf1, 0xbf8c, 0xb225, 0x12db, 0x1aa6, 0x40f3, 0x327f, 0xb2c3, 0x15eb, 0xb22c, 0x27c4, 0xb229, 0x4015, 0xa837, 0xa0aa, 0x4045, 0x44c9, 0xb2cd, 0x416d, 0xbf58, 0x441a, 0xba16, 0x44aa, 0x1be5, 0xb2dd, 0x456e, 0x2da2, 0x4485, 0x3a61, 0x43d4, 0x127d, 0x0f03, 0x4046, 0xa7c2, 0x43ba, 0x422d, 0xb2f8, 0xa357, 0x40bc, 0x1f75, 0xa5db, 0xbf90, 0x42a1, 0x41bc, 0xbf87, 0x1bc0, 0xbf60, 0x3130, 0x40a2, 0x416d, 0x4171, 0x43d6, 0x180c, 0x46c4, 0x1efb, 0xbfad, 0x118b, 0xb211, 0x41cd, 0xba1f, 0xa45a, 0x400a, 0xbfc1, 0xba49, 0x4103, 0x4212, 0xb20b, 0x42d7, 0x42e0, 0x413a, 0xba68, 0x42d4, 0x4241, 0x41b2, 0x427f, 0x42e9, 0xbfa7, 0xb09c, 0x20ae, 0x3704, 0xb2ce, 0x1f73, 0xa18a, 0xbf80, 0x4266, 0xb0d8, 0x419a, 0xbf41, 0x4270, 0x1810, 0x4264, 0x39da, 0x1d65, 0x41a7, 0xa97c, 0x1288, 0xba61, 0x0e71, 0x425c, 0x41e5, 0x4542, 0x42fe, 0x4183, 0xb283, 0x401d, 0x00d1, 0x4232, 0x4246, 0x226e, 0x1918, 0x4299, 0x15ac, 0xbfad, 0x1ce1, 0x2a0a, 0x40a6, 0xb2fd, 0x3635, 0x1933, 0x412c, 0x1439, 0xa558, 0x4220, 0xbf33, 0x43c6, 0x416d, 0x26aa, 0xb213, 0x44e4, 0xbfe4, 0x29dc, 0x1d40, 0x42a2, 0x1ea9, 0x3f4b, 0x2e37, 0xb0e2, 0x0098, 0x427a, 0x4347, 0xbfe0, 0xb0a6, 0x053b, 0xb236, 0xba79, 0x444d, 0x429b, 0xbf94, 0xba27, 0xb2a0, 0x42aa, 0xb2e1, 0x43d0, 0xbf90, 0xba6b, 0xba4d, 0xa3ce, 0xb266, 0x43e6, 0x23cf, 0x422e, 0x4102, 0xaaf3, 0xa539, 0x419a, 0x137f, 0x45d8, 0x4067, 0xbfc2, 0x40f6, 0x416f, 0x08f8, 0x1f01, 0xb22b, 0x40a3, 0x40f9, 0x3a0e, 0x4370, 0xb2ad, 0x437a, 0x16d6, 0x0960, 0x1f6d, 0xa78d, 0x0385, 0xb2c2, 0x41cf, 0xb2f5, 0x1f83, 0xbaef, 0x4077, 0x1b8c, 0xba12, 0x4386, 0x40d0, 0xbf12, 0x40a8, 0xb237, 0x4237, 0xba4f, 0x4296, 0x45c8, 0x1f18, 0x1d76, 0x43ab, 0x3e32, 0x43f0, 0xbf72, 0x1a78, 0x1b64, 0x4028, 0x40cf, 0x43bb, 0x1f2c, 0x438f, 0x1fb8, 0x4282, 0x4669, 0x41d5, 0x1db5, 0xbafd, 0x41d4, 0x11c4, 0x405f, 0x1fb3, 0xb24b, 0x034f, 0x4345, 0x0970, 0x44fd, 0xb06b, 0xbf87, 0xa48d, 0x4492, 0xb095, 0x40b4, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xec6fcb87, 0x37fe5171, 0x476101c9, 0xb4b6dc30, 0x961e2dee, 0xdd91c1a4, 0xedf2ecd8, 0xdc6fc622, 0x228a66a9, 0x3398789a, 0xa6476515, 0xda1e950b, 0x49ed8832, 0xb877df50, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x07fffffe, 0x0000285e, 0x00000000, 0x0000005e, 0x00001a14, 0x00000000, 0xffffffd3, 0x050bc000, 0x5622f36b, 0x00002e90, 0x5622f36b, 0xa6476511, 0xac45e6d6, 0x000041e6, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x42f8, 0x07a3, 0xba44, 0x42a4, 0xbff0, 0x43f8, 0x2211, 0xb240, 0x40ef, 0x3564, 0xbad8, 0x45a3, 0x41ca, 0xba31, 0x4163, 0xbf47, 0x0b13, 0x42a0, 0xb288, 0x4498, 0x40a7, 0x4388, 0x3a67, 0xba1f, 0x4109, 0x4267, 0xb250, 0x437a, 0x46d4, 0x1fc3, 0x44cb, 0x421f, 0x36f5, 0xba5b, 0x1805, 0x46f8, 0x39bb, 0x170b, 0xb245, 0x4671, 0x40a1, 0x4175, 0xbf8e, 0xaafd, 0x4046, 0x415e, 0x41ad, 0x4342, 0x43b2, 0x182a, 0x1fea, 0xbfce, 0xa26c, 0x4232, 0x4184, 0xbfcb, 0x421c, 0x4350, 0xbf00, 0x420a, 0x4073, 0x434b, 0x43c6, 0xba76, 0xba57, 0x43fa, 0x422a, 0xb0d1, 0x42ff, 0xb2fe, 0x4327, 0xbf52, 0x41ef, 0x0772, 0xb293, 0x4371, 0xb0d4, 0x3aea, 0x3a19, 0xbac1, 0x408c, 0x4177, 0xbf00, 0xa92b, 0xba79, 0x4317, 0x4231, 0x4139, 0xbae8, 0x4107, 0xba54, 0x43c4, 0x4066, 0x1a61, 0xba23, 0xb0b9, 0x41a5, 0x410a, 0x43f4, 0xb027, 0xbfbe, 0xb02e, 0xaa85, 0xa3ba, 0x1c41, 0x3ab9, 0x1700, 0x44db, 0x1e0f, 0x40fc, 0xbaeb, 0xb015, 0xbf5c, 0x433d, 0x3457, 0x408e, 0x443e, 0xa97f, 0x19f0, 0xbf6a, 0x2099, 0x41a3, 0x400d, 0x41b8, 0x42d7, 0x4049, 0x416f, 0x428f, 0xbad9, 0xbf65, 0xb066, 0xa894, 0x18db, 0x11e7, 0x4351, 0x4113, 0x4182, 0xbae8, 0x1ea4, 0x3b34, 0xbf8d, 0xba5c, 0x4198, 0x436b, 0x421d, 0xbaf3, 0x435b, 0x12bd, 0x40cd, 0x2ba7, 0x180e, 0x43a7, 0x409b, 0xba76, 0x20b6, 0xba6e, 0x22f8, 0x184a, 0x41c9, 0x2f18, 0x43bb, 0xb28a, 0x3e03, 0x42bd, 0x1873, 0x4076, 0xbf42, 0xbaf0, 0x40b4, 0x185e, 0xbfaa, 0xbf00, 0x14e1, 0x27ae, 0x16e9, 0x43d5, 0x412c, 0xbf8f, 0x1bc8, 0x4048, 0x403e, 0x176a, 0x4126, 0x469d, 0xba7f, 0x0dc0, 0x4363, 0x436d, 0x1848, 0x1944, 0x422e, 0xb273, 0xbf00, 0x1c50, 0xbfc2, 0xb2b1, 0x421b, 0xbfd0, 0x320b, 0x300e, 0x1cd6, 0xb2ae, 0x433e, 0xb2f0, 0x1f18, 0x2045, 0x3f7d, 0xb2ac, 0x31e0, 0x1882, 0x1c64, 0xb2e2, 0x1d30, 0xbf90, 0x4316, 0x4429, 0x3b53, 0xba3c, 0x1c65, 0x21a2, 0x4246, 0xb070, 0xbfc6, 0xa834, 0xb2c4, 0xa89e, 0xb24a, 0xb292, 0x1341, 0xb20c, 0xbad2, 0x40a1, 0x425e, 0x0227, 0x4294, 0x2458, 0x4278, 0x4130, 0xad67, 0xb2dc, 0x4112, 0x4329, 0xb037, 0xa54d, 0x031f, 0x4105, 0xbfca, 0x400f, 0x233b, 0x4108, 0x1a02, 0xbf44, 0xb0a7, 0x416d, 0xbfdb, 0xb2d8, 0x2ed4, 0x4046, 0x1bd0, 0x1e0e, 0x426a, 0xa1db, 0x1fb1, 0x40b9, 0x4304, 0x424a, 0x02d1, 0xb245, 0x4153, 0x41cb, 0xbf22, 0x43f8, 0x41c8, 0x18fc, 0xa2a2, 0x42cf, 0x411e, 0xbfc6, 0x41db, 0xb2ed, 0xae81, 0x43c9, 0xbaef, 0x40fa, 0x0fd7, 0x430f, 0x072e, 0x3c69, 0xa74f, 0xbfa0, 0xba11, 0x410c, 0x000b, 0x4028, 0x1c4d, 0xba4c, 0x0ca2, 0x0b17, 0x43bd, 0x0149, 0xbfa7, 0x1c43, 0xbae0, 0x4245, 0x14f5, 0xba01, 0x4231, 0x421e, 0x4390, 0xa298, 0x4200, 0x2a55, 0x416f, 0x1a4f, 0x42b7, 0x097a, 0x1d13, 0x2dc8, 0xb2f7, 0x3cbf, 0x4270, 0x1f05, 0xa2c0, 0x057e, 0x40bb, 0xbac7, 0x1fef, 0xbfc6, 0x2651, 0x1ef8, 0x41fb, 0x382d, 0x40b0, 0xa7ea, 0xb034, 0x439a, 0xba7d, 0x4387, 0xb2f0, 0x4319, 0xbad5, 0x4427, 0x19c9, 0xb27d, 0xbf70, 0x2c31, 0x4260, 0x098c, 0x40de, 0x4220, 0x418c, 0x435b, 0x4589, 0xb27d, 0xb290, 0x1d46, 0xbf13, 0xb21d, 0xb2b3, 0x42cc, 0xba42, 0x40c0, 0x02a9, 0x4345, 0x40f4, 0xbfe4, 0x436f, 0xb25e, 0x1f19, 0xba1a, 0x2f2c, 0xb23a, 0xb238, 0xb239, 0x436b, 0x4287, 0xbac9, 0xbaf9, 0x1b47, 0xb245, 0x4338, 0x0077, 0x4179, 0xbae7, 0x431d, 0x4108, 0xb06f, 0x418a, 0x4253, 0xb02a, 0xa84c, 0xbfc2, 0xba77, 0x1a6c, 0x4018, 0x2ed1, 0xabb2, 0x409b, 0xa523, 0x4335, 0x4319, 0x4250, 0xb256, 0xb228, 0xa371, 0x1b22, 0x4350, 0x4273, 0xb09d, 0x18a4, 0x4297, 0x0814, 0xbf58, 0x27b9, 0x22c1, 0xb053, 0xa1b1, 0x414c, 0x1e1a, 0xbfd2, 0x4246, 0x42af, 0x4340, 0x4069, 0x4236, 0x4334, 0x425c, 0x41e5, 0x43ff, 0x4349, 0x3950, 0x237f, 0xba38, 0x42f8, 0x40f8, 0x034e, 0xb2c9, 0xb255, 0xba46, 0x426d, 0xa40b, 0x1ed1, 0xbf19, 0xb26f, 0x158c, 0x43a4, 0x402a, 0xb205, 0xbaf6, 0x45db, 0x4167, 0xb28a, 0x218e, 0x189d, 0x1d70, 0x458c, 0x072f, 0x424e, 0x1eff, 0xbacd, 0xb2ca, 0x45c1, 0x0ef0, 0xac77, 0xbfb2, 0x4099, 0x3d38, 0xad0b, 0xbfa4, 0x07ea, 0x41cb, 0x4262, 0x428f, 0x117b, 0xbfc0, 0x33fb, 0xbfc0, 0x413d, 0x4414, 0x45c1, 0x15b1, 0x01b3, 0x419c, 0x4257, 0xbfc6, 0x1c88, 0x406f, 0xa0e7, 0x4068, 0x4292, 0xb295, 0x1e8d, 0x41e8, 0x3710, 0x42da, 0x4234, 0xb0b0, 0xb06b, 0xbf77, 0x0b97, 0x4060, 0xba38, 0x1cb0, 0xba36, 0x427b, 0x4281, 0x411f, 0xb04b, 0x4026, 0xa71a, 0xbf9f, 0x41a0, 0xba1c, 0xb289, 0xadd3, 0xba4b, 0x43d7, 0xb0c9, 0xbafb, 0xba0a, 0x414e, 0x401b, 0x2a82, 0x4300, 0x43a9, 0xbfdd, 0x40e4, 0x4327, 0x1c8d, 0x1c33, 0x4260, 0x43de, 0x4669, 0x433f, 0xb253, 0x1d24, 0x107c, 0xba13, 0x40f4, 0x43f5, 0xbf90, 0x438e, 0x1c5e, 0x41e1, 0x3f39, 0x1bd4, 0xbfd9, 0xb2c4, 0x418e, 0xb2e7, 0x15b8, 0xac3d, 0x2430, 0xb291, 0x42c9, 0x4003, 0xb2fd, 0x34aa, 0x4680, 0x2a30, 0x42f9, 0x43eb, 0xbf41, 0x41eb, 0x2f26, 0xaa5f, 0x42b7, 0x42e0, 0x3e1d, 0xba1c, 0x4261, 0x0676, 0xbfe8, 0x423c, 0x3bd6, 0xba24, 0xbf70, 0x4152, 0x228a, 0x1848, 0x120b, 0xb262, 0x4309, 0x402e, 0x4106, 0xa354, 0x200b, 0x415e, 0xbfd6, 0x407f, 0x4563, 0x41b5, 0x212e, 0xb0e2, 0xbfb6, 0xae17, 0xb021, 0x2a99, 0xb0cd, 0xa019, 0xb090, 0x42ad, 0x42ad, 0x15ca, 0x43d3, 0x43cf, 0xb2e3, 0x43dd, 0xb2be, 0xac16, 0x41bb, 0x249a, 0x42b0, 0x083b, 0x4435, 0x4349, 0x40b3, 0x463a, 0x1a4e, 0xba55, 0xbf19, 0x40fe, 0x4300, 0xba69, 0x4299, 0x1f04, 0x013a, 0xb276, 0x09e9, 0x0926, 0x402a, 0x46c9, 0x1243, 0x4347, 0x412e, 0xb26f, 0xb2d5, 0x4388, 0x41cb, 0xba1e, 0xbfa2, 0xbac4, 0x4254, 0xb2e2, 0x0ffb, 0x20b7, 0x40b7, 0xb2b1, 0x432e, 0x466a, 0x40b0, 0xb2a7, 0x43c7, 0x18b4, 0x4279, 0xb272, 0x41c0, 0x4561, 0x40bb, 0xb047, 0x1fcc, 0xa0e0, 0x437a, 0xbf36, 0x2ab2, 0xb202, 0xad4d, 0xbad7, 0x0427, 0x1995, 0x31b3, 0x1235, 0x1f89, 0x340f, 0x45b5, 0x4556, 0x1941, 0x4203, 0x4593, 0x4071, 0x40a0, 0x387d, 0x3c40, 0x15de, 0x4215, 0xb219, 0xbf5a, 0x16e9, 0x44ed, 0xa529, 0x1cd4, 0x428b, 0x414a, 0xb2d6, 0x4572, 0xbf41, 0x414d, 0x40e1, 0x1862, 0x44d9, 0x414a, 0x4292, 0x425e, 0xb061, 0x436c, 0x3ba1, 0xb247, 0x40cb, 0xbaed, 0x412e, 0xba4b, 0xb298, 0x431c, 0xbf36, 0x0bfe, 0x3111, 0xba26, 0x1fb8, 0x4215, 0x1841, 0x40b7, 0xb0f5, 0xbf80, 0x42f8, 0x08a3, 0x45ee, 0x40d2, 0xb2f2, 0x33d3, 0xbf0d, 0x4200, 0x4083, 0x42af, 0x1a3b, 0xbfd1, 0xb060, 0xb0a4, 0x3ab1, 0xb23d, 0xba06, 0x4296, 0xb29f, 0x421c, 0xb2bd, 0xbae4, 0x46fa, 0x467e, 0xb019, 0x40f0, 0xb216, 0xbf35, 0x107c, 0xb296, 0x42c3, 0x4418, 0xba0c, 0xb22f, 0x0196, 0x42bc, 0x27fa, 0xa5b8, 0xbf02, 0x407c, 0x2e4e, 0xb28c, 0x091b, 0x1c4e, 0x1a4e, 0x4320, 0x40f6, 0x419b, 0xbac2, 0x43ab, 0xba4e, 0x4331, 0xb2ba, 0x43aa, 0x437f, 0x3ace, 0x27c8, 0xb246, 0x265d, 0x1ceb, 0x2cc8, 0xbff0, 0x2612, 0xbf1b, 0x1fd7, 0x43b0, 0xa909, 0x418b, 0xae30, 0xbfa2, 0x4563, 0x37cc, 0x4396, 0x4303, 0x4129, 0x1d9b, 0xbfcd, 0xb228, 0x43d6, 0x417e, 0xb00c, 0x4142, 0xba08, 0x41b2, 0x4260, 0x4066, 0x4362, 0x4210, 0xbf81, 0x40a6, 0x3a50, 0x40ac, 0x305e, 0xba75, 0x41e3, 0xb09c, 0xbf5a, 0x42e9, 0xbae5, 0x1a1b, 0x418d, 0xa430, 0x41ae, 0x4265, 0x2f35, 0x0f5f, 0x41b7, 0xbf71, 0xb056, 0x3e3a, 0xb2b8, 0x4227, 0x16be, 0xb213, 0x3ea4, 0xbf0c, 0xa864, 0xb25c, 0x42e2, 0x4083, 0x23de, 0x07bc, 0x42ca, 0x42cb, 0x1a5f, 0x3d6f, 0xb266, 0x3a16, 0x43ba, 0x434f, 0x41db, 0xb0b8, 0x427d, 0xb0b7, 0xbfe1, 0x1e76, 0x400e, 0xba71, 0xa62e, 0xb23b, 0x4358, 0x18ae, 0xb292, 0x4254, 0xbfbb, 0xba59, 0xb28e, 0x33cb, 0x46aa, 0x4219, 0xa5a6, 0x1ed8, 0x42d0, 0x150b, 0x4214, 0x42ed, 0x2da7, 0xb22b, 0x0e19, 0x3532, 0x43f0, 0x42a1, 0x439e, 0x40b3, 0x4361, 0x2193, 0x4451, 0x3894, 0x2d7e, 0x43c2, 0x1f33, 0xb0d9, 0x4440, 0xbf39, 0x1af5, 0x3c98, 0x42b7, 0x2c63, 0x1bb5, 0x06ac, 0x415c, 0x4012, 0x0d2f, 0x43cf, 0x43d1, 0x4272, 0x411d, 0x1dbc, 0xa93d, 0x1f13, 0x4222, 0x1904, 0xb24e, 0x44b1, 0xa1e6, 0x4074, 0x1a21, 0x4046, 0x4095, 0x4047, 0xbf8a, 0xba15, 0xb0ae, 0xa64e, 0xb26f, 0x4387, 0x05f8, 0x4548, 0xba5d, 0xb227, 0x407e, 0x43e8, 0xb238, 0x1f65, 0x45a2, 0x40f5, 0xba4a, 0x35d1, 0x403f, 0x4637, 0xbf49, 0x1dc2, 0x128b, 0xa29c, 0x41c0, 0xba48, 0x1adb, 0xb286, 0xb250, 0xb0af, 0x4132, 0x43e3, 0x1895, 0xba7b, 0x169e, 0x113d, 0x4589, 0x1fa1, 0xbfb7, 0x412d, 0x43fe, 0xb232, 0x3c71, 0x2e4a, 0xa936, 0x417e, 0x410c, 0xa245, 0x0a3e, 0x16c3, 0x0a0b, 0x0823, 0x3518, 0xba3e, 0x4135, 0x45f0, 0x412c, 0x4217, 0x4068, 0x4376, 0xb232, 0x416d, 0x42a2, 0x46db, 0x40a5, 0xb269, 0xb070, 0xbfbf, 0x2ff5, 0x2a6b, 0xb2a0, 0x45be, 0x1ab5, 0x1e69, 0x0d07, 0x4018, 0x1fcb, 0x414a, 0xb065, 0x08e8, 0xbaff, 0xbad4, 0x4332, 0xa6fd, 0x441e, 0xba61, 0xbfc2, 0xba53, 0xa9e9, 0x4260, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x618c22bd, 0x5bef2a39, 0xee6c28df, 0x55cc73d5, 0xfa7df287, 0x5074f3f0, 0x2ce650c2, 0xead87231, 0x32e2b3c0, 0xe82f84ef, 0x182094bd, 0x98581b15, 0x94fdcdeb, 0xe3d0b1ff, 0x00000000, 0x800001f0 }, + FinalRegs = new uint[] { 0x00006f62, 0x6525d096, 0x68d99e90, 0xd968909e, 0xffff909e, 0x68da0000, 0x68da1bcc, 0xffffff0f, 0xb3350301, 0xe82f84f5, 0x000000df, 0x010f4008, 0x182094bd, 0x6525ccf2, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x40a3, 0x4088, 0x431e, 0x40a5, 0xb02c, 0xba79, 0xb24d, 0x40f5, 0x43df, 0x43c1, 0xaef2, 0x4008, 0x414b, 0x41fc, 0xb235, 0xbfc0, 0xafbb, 0x1c05, 0x196a, 0x40b4, 0xb072, 0x38d1, 0xbf43, 0x46b1, 0xa0e6, 0x45a4, 0x4043, 0xbf90, 0x4626, 0xb2fc, 0xbf7e, 0xba61, 0xa1ca, 0x2175, 0xaf95, 0x4312, 0x1d8a, 0x190e, 0xb2df, 0x1698, 0x2d52, 0x1a9a, 0xb0ed, 0x407f, 0xa460, 0x4254, 0xb066, 0xbf89, 0xb0a9, 0xb2b8, 0x1a5d, 0x4194, 0x4211, 0x41a0, 0x40e0, 0x4139, 0x4362, 0x4336, 0x42a4, 0x447b, 0xabbd, 0x443a, 0x4459, 0xaf35, 0xbfb2, 0x45cc, 0x1e31, 0x1b94, 0xb07a, 0xb29e, 0x4212, 0xbae6, 0x427e, 0xb034, 0x2b28, 0x400a, 0x3ca6, 0x43a4, 0xb04a, 0xb248, 0x409a, 0xab1f, 0x4278, 0xb04b, 0x407f, 0x4291, 0xbf75, 0x415f, 0x4087, 0xb298, 0x41d4, 0x423e, 0x1b3f, 0x4206, 0xba6d, 0xb2d7, 0x458c, 0x319d, 0x4222, 0x40c1, 0x43ac, 0x4340, 0x1308, 0x4011, 0x4118, 0x40b3, 0x4379, 0xba4b, 0xb23e, 0xbf74, 0x40b5, 0x3319, 0xb07b, 0x41cb, 0xb2f9, 0x431d, 0x02c4, 0x1996, 0x419a, 0x435b, 0x255c, 0xb2f5, 0x33ad, 0xb04a, 0x4408, 0x0974, 0x42f7, 0x4339, 0xb280, 0x1b82, 0xb247, 0x464c, 0x4211, 0xbf0b, 0x4030, 0x43e6, 0x4087, 0x3611, 0x0981, 0x416e, 0x4315, 0x43c1, 0x2147, 0x4090, 0x4136, 0xbf66, 0x2933, 0x2ecf, 0x43eb, 0xb283, 0x42af, 0x1c96, 0xb09f, 0x41f2, 0xb21c, 0xbf09, 0xb087, 0x4463, 0xbaf1, 0x419d, 0xa7b0, 0xb211, 0x41a6, 0x3f86, 0xba54, 0x3385, 0x03b0, 0xb211, 0x4331, 0xaf96, 0x4119, 0x0c9e, 0x419b, 0xb2dc, 0x43d0, 0xb24c, 0x4042, 0xbf00, 0x4162, 0x1fc3, 0x4189, 0xb25a, 0x06f9, 0xb274, 0xbf6c, 0xbacf, 0x407e, 0x4204, 0x140f, 0x4309, 0x4176, 0x407e, 0x4130, 0x1c8a, 0x4354, 0x2411, 0x2605, 0xb057, 0xbf70, 0x406b, 0x3cd9, 0x437f, 0xa2e1, 0xba57, 0x069e, 0xbf76, 0x46ec, 0xbaf9, 0xb2ae, 0x436a, 0xadd9, 0xb09d, 0x40db, 0x4244, 0x1a39, 0xac08, 0x408d, 0x4021, 0x4263, 0xb281, 0x4418, 0x4116, 0x42f6, 0xa919, 0x1470, 0x1f33, 0x4245, 0x434f, 0x43ff, 0x43a1, 0x0d92, 0x4051, 0x43af, 0x30c5, 0xbf46, 0x425e, 0x4008, 0x4678, 0x42db, 0x1c2c, 0x4410, 0x1c4e, 0x4282, 0xba0d, 0x4075, 0x194f, 0x41ab, 0xb065, 0x1871, 0x412f, 0x0399, 0xa0db, 0xb239, 0x4559, 0x43d6, 0x1faf, 0x411f, 0xb0b7, 0x4606, 0x43c2, 0xbf81, 0x0823, 0x2a43, 0x18c5, 0x1a77, 0xad27, 0x43ff, 0x4212, 0x0e3c, 0x411a, 0x43a6, 0x1916, 0x4394, 0xb225, 0x1eb5, 0x449b, 0x438e, 0x40ca, 0x4623, 0xb2e3, 0x41ef, 0x4493, 0x3e02, 0x4069, 0x1c2b, 0xb234, 0x1e69, 0x41df, 0xb2b8, 0x41d8, 0xbf56, 0x05ad, 0xba1c, 0x42c2, 0x4051, 0x42b2, 0x40f4, 0x43ce, 0x44f0, 0x43f3, 0x4229, 0x213e, 0xb2c2, 0x3200, 0x4214, 0x42b6, 0x005f, 0x4075, 0xb272, 0xbaf8, 0xb224, 0xb29f, 0xbfd5, 0x40c1, 0x438e, 0x43c4, 0x0aa3, 0xa689, 0xb22f, 0x2428, 0x45bd, 0x4004, 0x43f9, 0xb29e, 0x4679, 0xb21b, 0x40bb, 0x4027, 0x40f4, 0x4074, 0xbf7b, 0x45c2, 0x43f6, 0xb2a0, 0x416c, 0xba07, 0x1da5, 0x416e, 0xba1c, 0x406a, 0x0523, 0xa9b7, 0x352b, 0x35c0, 0xbf97, 0xb26c, 0xbf60, 0x40f8, 0xba7a, 0x423f, 0x02f2, 0x4068, 0x3e52, 0x42c7, 0x4050, 0x1d10, 0x4211, 0x42d9, 0x4565, 0xb2a5, 0x416d, 0xba27, 0x420a, 0xb228, 0xbf80, 0xa3ea, 0x437b, 0xb2cf, 0xb2cb, 0x1473, 0xbf7c, 0x4285, 0x41b6, 0x4280, 0x3a9f, 0xba06, 0xba22, 0x3f7e, 0x1822, 0x1663, 0xbf80, 0x182b, 0x1d47, 0x37c4, 0xaa51, 0x1efc, 0xbfb7, 0x43b7, 0xba12, 0xbaf0, 0x40e9, 0xbf80, 0xb09e, 0x4448, 0x1f27, 0x4083, 0xac6a, 0x400e, 0x1c32, 0xba54, 0xba14, 0x249e, 0x3f80, 0x4610, 0x4293, 0x4396, 0x1e57, 0xb23f, 0xb093, 0x4161, 0x42c0, 0x3494, 0x40d5, 0xbf56, 0x1ee5, 0x2143, 0x18b0, 0x4360, 0x46a4, 0x1930, 0xba5a, 0x44d0, 0x1ad6, 0x371e, 0xb2ce, 0x1534, 0xb09c, 0x1b0b, 0x095d, 0x2f9f, 0x1608, 0x40f7, 0xbada, 0x05c1, 0xbf3e, 0xa521, 0xb29a, 0x42af, 0x404a, 0x0f8c, 0x41c9, 0xb089, 0x1a99, 0xba0a, 0x1fc9, 0xb25b, 0x41a2, 0x4109, 0x40b7, 0x364f, 0x2648, 0x011f, 0x4157, 0xbf47, 0xb037, 0x1b4a, 0x4096, 0x41a8, 0x4246, 0xba0f, 0x40f0, 0xb250, 0xbf59, 0x42b7, 0xb251, 0xba5b, 0xb0d1, 0x4208, 0x4103, 0x40a7, 0x4355, 0xb216, 0x4476, 0x1962, 0xb03d, 0xba1a, 0x1928, 0x1f0b, 0xbf71, 0x2d76, 0x40f6, 0x4369, 0x29d5, 0x42f6, 0x1dd5, 0xb25e, 0xaf1a, 0x42fd, 0x40b9, 0x41bb, 0xa259, 0x40b7, 0x45f6, 0x4283, 0xbf3e, 0xba6c, 0xbae8, 0x41cb, 0xbf08, 0x42f3, 0x15e3, 0x1eb6, 0x386c, 0xb090, 0x1d6c, 0x3160, 0x43ca, 0x1a41, 0xb260, 0x4252, 0x3a39, 0xae9f, 0x41cf, 0xb2ae, 0x40ed, 0x4347, 0xb2ae, 0xba31, 0xbfda, 0x43a6, 0x4125, 0xb036, 0x408f, 0xba00, 0x1ddf, 0x4062, 0xb0ba, 0x4176, 0xba23, 0x2c17, 0x42e6, 0x3bc2, 0x43fb, 0x11b5, 0x400e, 0x439d, 0x3079, 0xb0a7, 0xbf7b, 0xb0e1, 0x4271, 0x44d1, 0xbfb0, 0xb028, 0xb015, 0x43d1, 0x4322, 0x1dcf, 0x42cc, 0xbac0, 0x1847, 0xbfd1, 0xb036, 0xb060, 0x2bea, 0x4020, 0x4193, 0x43d5, 0xaa64, 0x41c7, 0x427c, 0x41ec, 0xbf0b, 0x4398, 0x4200, 0x4283, 0x4000, 0x1f81, 0x44d1, 0x42d6, 0x402c, 0x0e29, 0x4606, 0xb0b9, 0x4093, 0x40fa, 0x42b6, 0xba48, 0x43a9, 0xbf72, 0x1aba, 0xbf00, 0x369e, 0xbf00, 0x4109, 0x251e, 0xba6f, 0x40bc, 0x4464, 0x40d4, 0x3776, 0xb2de, 0xb01a, 0xb05d, 0x27ed, 0xb252, 0x368f, 0xb044, 0x45e0, 0xba62, 0x4135, 0xbf23, 0x4188, 0x41f4, 0xb223, 0x418e, 0x40e4, 0x4256, 0xbf01, 0x411b, 0x1f16, 0x4253, 0xbadd, 0x43e0, 0xaeab, 0x4431, 0x24cb, 0x30b8, 0x406e, 0xb0e9, 0x41aa, 0x43a6, 0x438e, 0x4342, 0x4307, 0x419c, 0x4053, 0x4311, 0x41c7, 0x4152, 0x1ba5, 0x41db, 0x42a0, 0x40b8, 0xbf2e, 0xb01c, 0x423b, 0x1d13, 0xb262, 0xba11, 0x1803, 0x433e, 0x287b, 0x1977, 0xb0ba, 0x297d, 0xbf01, 0xb294, 0x40bf, 0x4391, 0x43e8, 0x232d, 0x431a, 0x4228, 0x375a, 0x36d7, 0x424d, 0xbf60, 0x4071, 0x40e8, 0x3242, 0xb0fe, 0x4214, 0x41a3, 0xbf62, 0x2756, 0x40f0, 0xba17, 0x3696, 0x427f, 0xaaa1, 0xbfa9, 0x4285, 0x427c, 0xa783, 0x4369, 0x274e, 0xb299, 0x411c, 0x40f0, 0x073b, 0x40d4, 0x43f7, 0xba1b, 0xb22d, 0x1c83, 0x1f11, 0x4277, 0x32b1, 0xbf52, 0x424b, 0x438f, 0xbfd0, 0x4118, 0x39e7, 0xba37, 0x3e6d, 0x41f3, 0x4239, 0x2daa, 0xbfa6, 0x432e, 0xb015, 0xb2f6, 0xbaf2, 0x4184, 0x4324, 0x1c78, 0xbaf1, 0xba29, 0xbfca, 0x1b4d, 0xb28b, 0x4577, 0x3ade, 0x43b3, 0xbf64, 0x401f, 0x41d7, 0x4060, 0x1be9, 0x4453, 0xba44, 0x37d4, 0x424a, 0xb006, 0x1882, 0xbac4, 0xba5b, 0x3c59, 0x0c92, 0x42a0, 0xba4e, 0x2d9b, 0xbfe4, 0x441b, 0x2199, 0x05fd, 0x43b2, 0xba42, 0x4301, 0x1ec4, 0x4656, 0xb0e4, 0xb06e, 0xbf82, 0x40a6, 0xaebe, 0x30ca, 0x4181, 0x42a4, 0xb24d, 0x4080, 0x43ca, 0x3592, 0x1943, 0x4217, 0x43ee, 0x08c3, 0xb003, 0x2cbd, 0x4412, 0x2e2f, 0x26db, 0x4179, 0x42a4, 0x36d3, 0xbfb2, 0x4386, 0x431c, 0x44ba, 0x4464, 0xb281, 0x40b3, 0x4152, 0xb25b, 0x41f2, 0xb059, 0xb278, 0x0dfe, 0x425f, 0x4215, 0x3ca0, 0x4234, 0xbad2, 0x41f0, 0x3f2d, 0x43ce, 0x4218, 0xbf12, 0x45e9, 0x1a54, 0x1fff, 0xacd6, 0x410e, 0xbf8f, 0xb0e1, 0x352d, 0x1fed, 0x1af4, 0x428e, 0x3c91, 0xb039, 0x4079, 0x4347, 0x3c04, 0x4424, 0x4260, 0xa798, 0x41b9, 0x4033, 0x4238, 0x2419, 0x4227, 0x0d53, 0xb2c3, 0x42d7, 0xbf14, 0x46a5, 0x0852, 0xa387, 0x13d9, 0x41c7, 0x1f38, 0x43e8, 0x1dcc, 0x43e6, 0x465a, 0x419a, 0x4387, 0x4338, 0x43a0, 0x033e, 0xba5d, 0x4176, 0x3d63, 0x3285, 0x1d0b, 0xacee, 0x4105, 0x44e8, 0x3c90, 0xa4a5, 0xbf17, 0x432e, 0x4020, 0x4367, 0x43ee, 0x43f8, 0x3f58, 0x1f04, 0x08fa, 0x43cf, 0xb097, 0xba07, 0xb228, 0x45c4, 0x09ae, 0xbf31, 0x31a7, 0x3a09, 0x42cf, 0x4019, 0x2390, 0x4145, 0x4179, 0x40bd, 0xb243, 0xbf5d, 0x3f0c, 0x3406, 0x1f8a, 0xba3b, 0xbfb5, 0x43b0, 0x3120, 0xb26c, 0xa3b0, 0x4272, 0x465d, 0x3214, 0xba0e, 0x3a60, 0x4230, 0x2d1c, 0x43ea, 0xbf63, 0x43ae, 0x14fa, 0x38d6, 0x423d, 0xac84, 0xb210, 0x402e, 0xb273, 0x441e, 0x43b0, 0xab4e, 0xb275, 0x42c8, 0x41f1, 0xb2ba, 0x42fd, 0x40b9, 0x4244, 0x42ed, 0x420d, 0x4190, 0xba13, 0xba1f, 0x2758, 0x40d7, 0x1012, 0x41f7, 0xbff0, 0xbfaf, 0x1aee, 0x425b, 0x0e1a, 0x3ddb, 0xb2e9, 0xba50, 0x3fd8, 0x1ba9, 0xb002, 0x437d, 0x4279, 0x34fb, 0xba3b, 0x43e3, 0x4185, 0xba1f, 0xa0db, 0x4218, 0x3d58, 0x0f00, 0xb2b0, 0x429f, 0xb063, 0xbf0d, 0xba52, 0xba07, 0xa5c8, 0x409c, 0x431f, 0xb2f9, 0x1ff7, 0xb0e6, 0x463f, 0x3ac2, 0xba14, 0xba7c, 0xbf43, 0xbff0, 0x40b3, 0x2e64, 0xa3a0, 0x1bac, 0xb2c7, 0x1f9a, 0x31d1, 0x387c, 0x1993, 0x4224, 0xba20, 0x4044, 0xb2f2, 0x1983, 0x428f, 0x11fc, 0x00a5, 0xb071, 0x12ae, 0x4007, 0x15cc, 0xbfcf, 0x416e, 0x0667, 0x1b46, 0xb250, 0x4285, 0x41ea, 0xa38d, 0x43dd, 0x43a7, 0xa77b, 0x0711, 0xba66, 0x445c, 0x1b40, 0x4095, 0x41b8, 0x400b, 0x4079, 0x3567, 0x43a6, 0x461d, 0x435d, 0x4036, 0x42fa, 0xa9a6, 0x43b7, 0xb295, 0x427e, 0x4300, 0xbf54, 0xb0f4, 0x2b0b, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x36179bbd, 0x4c84b624, 0xf2f6b902, 0xcc71b138, 0x0afbc809, 0xac7ce4df, 0xb01ce320, 0xe9d87f98, 0xc61c700a, 0x6f44ba87, 0x76d17376, 0x6c662875, 0x88e99f9f, 0x5ec4aa7f, 0x00000000, 0x500001f0 }, + FinalRegs = new uint[] { 0xffffffeb, 0x00000415, 0x6000000a, 0x00000000, 0xf16324f3, 0x0000000a, 0xffffe660, 0x000019a0, 0x3cede399, 0xd596226d, 0xe4d0754a, 0xf16324f3, 0x00000132, 0x0000017d, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4194, 0x41e1, 0x426d, 0xabea, 0xa7e7, 0x181f, 0x4209, 0x38b4, 0x4330, 0x42fd, 0x40db, 0xbfb4, 0x1903, 0x4570, 0xba65, 0xa18b, 0x1868, 0x41bc, 0xb269, 0x3a0f, 0xb2ef, 0x43d0, 0x301c, 0x1b59, 0x4185, 0xa33d, 0x43c3, 0x448a, 0xa735, 0x1c22, 0x4273, 0x4584, 0xb215, 0xbfb5, 0xb059, 0x43ed, 0x412b, 0x1c26, 0xb0cb, 0xbae5, 0x40ca, 0xb28a, 0x19c5, 0x4120, 0x4274, 0x4594, 0xba53, 0x42c7, 0xbf71, 0x4182, 0x43e9, 0x412a, 0x28e9, 0x4057, 0x4310, 0x41a1, 0x40cd, 0xbfac, 0xba50, 0xaa8a, 0x26a9, 0x432c, 0x46b1, 0x454c, 0x1542, 0x4222, 0x02f8, 0xb2f9, 0x4014, 0xb23d, 0xbf55, 0xbfd0, 0x404a, 0x3680, 0x40a4, 0x4275, 0x42b9, 0x4249, 0xbfa7, 0x2da6, 0xa070, 0x4377, 0x10a2, 0x411c, 0x1f50, 0x466a, 0xa9fd, 0x237a, 0x43e4, 0xb2b7, 0x4457, 0x4157, 0x2ec4, 0x4606, 0x41f1, 0x4432, 0x1376, 0x4143, 0x4663, 0x43ae, 0xbf8a, 0x426f, 0xb299, 0x422f, 0x1c31, 0x415c, 0x39de, 0xbf60, 0x25cc, 0x4097, 0xb24e, 0xbac7, 0xb2c8, 0x4030, 0x43f2, 0x41af, 0x4012, 0xb2d2, 0x4415, 0xbf9e, 0x4208, 0xba30, 0x1d45, 0x0615, 0xb2d8, 0xba11, 0x40f9, 0x4011, 0x42c7, 0xb262, 0x41df, 0x429f, 0x0b94, 0x1fbf, 0xbf94, 0xb2c3, 0x4073, 0x40e2, 0x4166, 0xa2e0, 0x4475, 0x4026, 0xb0a2, 0xb20a, 0x1c3b, 0xa334, 0xa04c, 0xaae3, 0xbf85, 0xba51, 0x42c5, 0xa2df, 0x43c6, 0x42f2, 0x4252, 0x43bb, 0xbf96, 0xa5a2, 0x41ae, 0xa663, 0x11de, 0xbaeb, 0x1b6f, 0x42f3, 0xba22, 0xbfe1, 0x438c, 0xba76, 0xb2e7, 0xa84c, 0x4389, 0x14e8, 0x316f, 0x1e56, 0xa1ea, 0x4270, 0x0962, 0x1f5d, 0x40f4, 0xbf69, 0xbae7, 0x4019, 0x1c1e, 0x43fe, 0x4245, 0x1a32, 0x418f, 0x10f5, 0x1a47, 0x1d6f, 0xa720, 0xb0e0, 0x43ae, 0x0ac4, 0x4232, 0x4044, 0x42fe, 0xbaf8, 0xba6b, 0x19ef, 0x408b, 0x4287, 0x41a5, 0xbfc6, 0x1d24, 0xb2f7, 0xb291, 0xaf34, 0x40f3, 0x1a78, 0xba54, 0x08f7, 0x19b6, 0x3b08, 0xba23, 0x318f, 0x1fe9, 0x4646, 0xbf41, 0x1b4a, 0x4224, 0xb201, 0x221a, 0x4027, 0xb09c, 0xbada, 0x438d, 0xa29c, 0xba71, 0x43ed, 0x40ee, 0x408a, 0x4366, 0xbf02, 0xb2f8, 0xb09a, 0x35f4, 0xb2db, 0x0d06, 0xba58, 0x42c8, 0x4374, 0x40f9, 0x42c4, 0xbaee, 0x4014, 0xba77, 0x4330, 0xb0f9, 0x43b5, 0x3ee4, 0xba5c, 0xa37c, 0x18f9, 0xba01, 0x43e4, 0x45ad, 0x3b98, 0x431c, 0xae45, 0xb0e9, 0xba6a, 0xbf3f, 0xb093, 0x40cf, 0x31f1, 0xbae3, 0x18e3, 0xafbb, 0x46fb, 0x4467, 0x4377, 0x4439, 0x46fd, 0x01e6, 0x43ca, 0x423d, 0xbf80, 0xb2f8, 0x439c, 0x4158, 0xbf9e, 0x4343, 0x433d, 0x41a2, 0x2ced, 0xbae8, 0x1b42, 0x3e7d, 0x41ba, 0x4022, 0x43a7, 0x424b, 0x4253, 0xb0e9, 0x42b3, 0x1d29, 0x2454, 0x412b, 0x4253, 0x4156, 0xbfd3, 0x423a, 0x400b, 0xba35, 0x1cf9, 0x00db, 0x04bb, 0x1b4c, 0xbafc, 0x1fb0, 0xb034, 0xb2a7, 0x4328, 0x4248, 0xbf67, 0x1bc5, 0x42c7, 0x157d, 0xb206, 0x4040, 0xa039, 0xaf24, 0x41b8, 0xa314, 0x2db8, 0x1a50, 0xb2df, 0x35ca, 0x423d, 0xb23f, 0x41ee, 0x456c, 0x4365, 0xb2d6, 0x421a, 0xba7d, 0x43be, 0x43a1, 0xbf39, 0x2460, 0xb271, 0x2223, 0x3e58, 0x1ac1, 0x4225, 0x422b, 0xb0af, 0xb2df, 0xba76, 0x4085, 0x4226, 0x1f02, 0xb2a0, 0x41a6, 0xa49b, 0xbfb0, 0x4097, 0x4145, 0xbf82, 0x414e, 0xb240, 0x246d, 0xba4b, 0xaca3, 0x35f0, 0x42c9, 0x3d4b, 0x46db, 0x43fe, 0xba60, 0x1736, 0x100c, 0xbaf4, 0xa17b, 0x42ae, 0xbfdd, 0x0867, 0xb0b5, 0x4236, 0x420b, 0x4256, 0x4166, 0x4653, 0x4372, 0x1a16, 0xa649, 0x43e3, 0x42a6, 0xbf5f, 0x3fd3, 0x31db, 0x41fa, 0x2e10, 0x3186, 0xa1ae, 0xb012, 0xb2f5, 0x3260, 0x44a2, 0x43dc, 0x05d4, 0x41a8, 0xb030, 0x443b, 0x2d52, 0xba2f, 0x44f2, 0x4200, 0xba05, 0x42f4, 0x431b, 0x1ae3, 0xb2db, 0x4405, 0xbaeb, 0x3772, 0x423d, 0xbf1f, 0x20f7, 0x40be, 0x1b66, 0x1b9f, 0x439e, 0x0d71, 0xa52b, 0x1fd0, 0x2322, 0xbff0, 0xac82, 0xbfa0, 0x274e, 0x4079, 0xafe0, 0x1f38, 0x4235, 0x1de4, 0x403d, 0x434b, 0x343f, 0xba6d, 0xba07, 0xbf02, 0x439f, 0x46c1, 0xba20, 0xb206, 0x4127, 0xba1f, 0x4226, 0xb295, 0x436b, 0xb2aa, 0xa2b9, 0xba5e, 0x423f, 0xb008, 0xb00f, 0xb081, 0x0a56, 0xbfa3, 0x3f44, 0xb0a9, 0x4022, 0x244d, 0xb07d, 0xba2b, 0xbf90, 0xbfa0, 0x41a1, 0xba26, 0x2997, 0xa317, 0xb2ce, 0x410e, 0xba51, 0x40a8, 0x4285, 0x20bd, 0x411d, 0x1eaa, 0xbfcd, 0x4027, 0x4032, 0x42f8, 0x286a, 0xb212, 0x4191, 0x184a, 0x43a3, 0x4097, 0xba16, 0x369b, 0x4336, 0x46e2, 0xa6ed, 0x41f2, 0x0322, 0xb2ab, 0xbf48, 0x4139, 0x4077, 0x4096, 0x1da1, 0x4266, 0x4365, 0xb051, 0x413b, 0x45cb, 0x1533, 0x425c, 0x423e, 0xba34, 0x4679, 0x3ad5, 0x439b, 0x420d, 0x4228, 0x4406, 0xbf42, 0x4211, 0x4272, 0xb2a1, 0xb272, 0xb2d0, 0xa24b, 0xb0b4, 0x36f9, 0x1c6f, 0x405c, 0x36ad, 0x411f, 0x41d2, 0x426f, 0xb2f9, 0x29f9, 0x4572, 0x43cf, 0x4016, 0x4068, 0x4262, 0x151a, 0xbfa5, 0xafd2, 0x4382, 0x4308, 0x46e4, 0x43cb, 0x18af, 0x428e, 0xac0f, 0x1f7c, 0xbfc1, 0x425a, 0x424e, 0x120e, 0x43fa, 0x0332, 0x19c4, 0x4044, 0xbf0a, 0xbac9, 0x435b, 0x4255, 0xbf92, 0x41b7, 0x42a9, 0x08e3, 0x4114, 0x42ad, 0xb27b, 0x1ab9, 0x0adf, 0xbaed, 0x40b7, 0x307f, 0x4457, 0x40f1, 0x4296, 0xb218, 0xb21d, 0xb2d0, 0x4054, 0x40b4, 0x41ee, 0x45d1, 0x1f57, 0x011c, 0x4174, 0xbf02, 0x417a, 0x4124, 0x2931, 0x4021, 0xbac1, 0xb2ac, 0x4034, 0x4138, 0x44a1, 0x41a5, 0x4034, 0x30f3, 0x1952, 0xb003, 0x3fb2, 0x4624, 0xb04f, 0x3f0f, 0xbf54, 0xa9c0, 0x43af, 0xb2b0, 0xb0f0, 0xb2d8, 0x4288, 0x17b6, 0x2e75, 0x416f, 0x1f51, 0x1a93, 0xbfe4, 0x410c, 0x4465, 0x407b, 0x426d, 0x46a2, 0x37d7, 0xbfc7, 0xb2ee, 0x09c2, 0x41b0, 0x41a5, 0xba50, 0x4081, 0x459d, 0x4437, 0x433e, 0x1473, 0x435d, 0xbf00, 0x43fa, 0x1863, 0xb021, 0x4585, 0x4096, 0x1b2a, 0xbfb2, 0x067e, 0x18e2, 0xb20f, 0x43af, 0x4080, 0x1b32, 0xb29e, 0x0a11, 0x434a, 0x4392, 0x424e, 0xb2e3, 0x40a6, 0xab7a, 0x09d9, 0x1fed, 0x43c1, 0x19d4, 0x4089, 0xb236, 0x1f21, 0xb2e0, 0x4289, 0x461d, 0xaaf9, 0xbf3c, 0x1183, 0x0293, 0xa59b, 0x43bc, 0xb047, 0x403e, 0xb26a, 0xbfd2, 0x11e9, 0x4672, 0xbfa0, 0x414f, 0x39c9, 0xb2e5, 0x43a6, 0x43f4, 0xa04b, 0xa4bb, 0x4359, 0x4297, 0x41cb, 0x44e3, 0x2f11, 0xb237, 0x1971, 0x1c47, 0xbf3a, 0x424d, 0x448d, 0xb211, 0x412c, 0x41c1, 0x41e1, 0x41cc, 0x4265, 0xb08b, 0x4224, 0xba4b, 0xb240, 0xba27, 0xba7b, 0xbaca, 0xbfac, 0xb27c, 0x1d89, 0x43be, 0x1f77, 0xbf00, 0x43ea, 0x4326, 0xba22, 0x0315, 0xbf02, 0x42a2, 0x405a, 0xba05, 0x4152, 0xbfb0, 0xba4c, 0x42c6, 0x4334, 0x18b9, 0x4250, 0x40c8, 0xbf77, 0x33a6, 0xa4d7, 0xba55, 0x4361, 0x274f, 0xba03, 0x1ae7, 0x4038, 0xbf8d, 0x40fe, 0x1b4b, 0x4063, 0x40b7, 0xb2c8, 0x40ce, 0x38c1, 0xba40, 0x410f, 0x4445, 0x33b7, 0xba40, 0x4378, 0x3c3e, 0x4286, 0x41b1, 0xb219, 0xb23e, 0x437b, 0x2d6d, 0xbf9f, 0x1a09, 0xb00d, 0x436b, 0x41a5, 0x2188, 0xb29e, 0xb02f, 0x4255, 0x40ff, 0x4095, 0x4226, 0x1baf, 0x46ad, 0x262a, 0xa6a5, 0x1bf9, 0x41f0, 0x42fb, 0x43b9, 0xbf4a, 0x1d63, 0x1a48, 0x3668, 0x2ad8, 0xa836, 0xb0ad, 0xb205, 0xbf96, 0x1e1d, 0x27ba, 0x4677, 0xbac0, 0xa376, 0x40be, 0xbf05, 0xb2e9, 0x4164, 0x3e9d, 0x43b9, 0x4330, 0x1136, 0xba46, 0x41ae, 0x41c7, 0xb20f, 0xbafd, 0x42a2, 0x4007, 0xa269, 0x434e, 0xac40, 0x42da, 0x4553, 0xb2f3, 0xbfce, 0x433f, 0xa7f6, 0x4294, 0xb2e6, 0x41f3, 0x4355, 0x10e0, 0xb226, 0xb239, 0x1a8c, 0xb014, 0x431c, 0x1391, 0xba2d, 0x41ba, 0x402f, 0xb025, 0xb2bb, 0x35c5, 0xa0c6, 0x4042, 0xb27b, 0x4120, 0x250f, 0x4272, 0xbf32, 0x1a7c, 0x1e48, 0xbaf6, 0x1f12, 0x432a, 0x4608, 0x40f1, 0x1b8e, 0x1e05, 0x4021, 0x4053, 0xbf48, 0x41aa, 0xb2e4, 0xb230, 0x43ea, 0x1f4c, 0xa125, 0x1ef8, 0x4139, 0x4376, 0xbf6d, 0x42db, 0x32da, 0x4222, 0x2930, 0x2189, 0x4116, 0xb077, 0x429a, 0x40db, 0x45ac, 0x1a86, 0x1546, 0xb21d, 0x463a, 0x4062, 0xbf3e, 0x1a52, 0x407c, 0xba38, 0x454e, 0xa28b, 0x42a2, 0x3760, 0x1e45, 0x19ab, 0x292e, 0x0f80, 0x1d7e, 0x4572, 0x19be, 0x4318, 0xb0e2, 0x2ead, 0xbf15, 0x43d3, 0xb297, 0x4156, 0x4274, 0x3215, 0x43d5, 0x4101, 0x4009, 0x14bb, 0x4217, 0xb228, 0xba72, 0x1a16, 0x41af, 0x40c8, 0x4314, 0xb22a, 0x43a4, 0xb212, 0x4139, 0xb23d, 0x1b80, 0x1b4f, 0xb064, 0xbfb1, 0xb0ea, 0x419e, 0xa853, 0x406b, 0x41ba, 0xbfbd, 0x4353, 0x4018, 0x45e1, 0xba2b, 0xb264, 0x42e9, 0xb2e9, 0x40c2, 0x395e, 0x437f, 0x43f4, 0x406e, 0x0807, 0xba3e, 0xbf88, 0x430a, 0x43af, 0x4417, 0x426d, 0x410c, 0xbff0, 0x4683, 0xb25f, 0xbf00, 0xbf5c, 0xba47, 0x4033, 0x1942, 0x43e0, 0x12e6, 0x3c46, 0x436c, 0xba3c, 0x0145, 0x43cc, 0xb2d6, 0x4477, 0xb2d5, 0xb0d1, 0x4189, 0x43eb, 0x1ac6, 0xb258, 0xb0b6, 0xbf57, 0xbf60, 0x4285, 0x4364, 0x416f, 0x4111, 0x0d56, 0xb286, 0x4066, 0xb2dd, 0x4156, 0x4217, 0xa9c0, 0x43c6, 0xaf85, 0xb258, 0x41cb, 0xbfc4, 0x4220, 0x43bc, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x5163634f, 0x69953bc6, 0xc471194b, 0xd797bd76, 0xca82e30c, 0xd0e3d0b1, 0x315d5946, 0x74209518, 0xbfc706cf, 0x907cff0b, 0x9e3f1e57, 0x892343bb, 0x37dc0fed, 0xbc49e0f0, 0x00000000, 0xa00001f0 }, + FinalRegs = new uint[] { 0x00000025, 0x9eb0014e, 0xfffec9da, 0xfc97ffff, 0x00000e10, 0x00000025, 0xffffffda, 0x9eb00062, 0xbfc706cf, 0x000000ad, 0x00000000, 0xfffee373, 0x37dc0fed, 0x9eaffe4e, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xbacf, 0xbfa6, 0xb202, 0x40b6, 0x4165, 0x42b2, 0xb299, 0x40d5, 0x4183, 0xb09b, 0x4018, 0xb2a2, 0x4123, 0x0034, 0x45b6, 0x05e6, 0xaadf, 0x400e, 0x44da, 0xb220, 0xbfc8, 0x4147, 0x089b, 0x40e1, 0xa64f, 0xac9d, 0xb2cc, 0x39d6, 0xbf46, 0x42c9, 0x4314, 0x2b9a, 0x42cc, 0x19d2, 0x187f, 0x3b09, 0xba5f, 0xb2ef, 0x4281, 0x4298, 0x4253, 0xb284, 0x40ef, 0x4148, 0xaa53, 0x2d5e, 0x4301, 0xbf49, 0x4365, 0x3271, 0xb278, 0x10ca, 0x3100, 0x4298, 0xbfaa, 0xb27e, 0x46b9, 0x1972, 0xb08a, 0x428c, 0x4398, 0x1610, 0x4160, 0x40e9, 0x403b, 0x0964, 0xbf0d, 0x40e6, 0x40b1, 0x4183, 0x43af, 0x4195, 0x3dda, 0xa5d0, 0x2ba7, 0xb0d0, 0x1486, 0x4312, 0x42c5, 0xa382, 0x016e, 0xbf5c, 0x428b, 0xb2f2, 0x4367, 0xbf27, 0xba7b, 0x4676, 0x4229, 0x41f9, 0xb0b1, 0x4371, 0x40e3, 0x41f3, 0x1d96, 0x4019, 0xbad5, 0x0714, 0xbaf8, 0x3df6, 0x1b08, 0xb2ad, 0x32ce, 0xb26b, 0x120d, 0x438c, 0x41a6, 0x1e12, 0x421c, 0x416b, 0xb2ca, 0x4298, 0x31dc, 0xbf0c, 0x27c0, 0x41ea, 0x18b6, 0x4097, 0x42ed, 0x059a, 0x42fd, 0x428a, 0x4216, 0x1846, 0x43ec, 0x4194, 0x247c, 0x4347, 0xbf8f, 0x45b4, 0xb2cd, 0x41e7, 0xb2b8, 0xaf7f, 0xbf6c, 0xbae3, 0x4117, 0x43ae, 0x41b9, 0xbfe0, 0x144c, 0xbf13, 0x4292, 0x0fcd, 0x4329, 0x43f7, 0x1aba, 0x2109, 0x41e3, 0x4426, 0x401c, 0x40ea, 0xae86, 0x42c2, 0xbf02, 0x43c1, 0x2e2f, 0x42c3, 0x40a1, 0x4161, 0x43ff, 0x1a3e, 0x4334, 0xb234, 0xb256, 0x4037, 0xbaf8, 0x40e5, 0xba12, 0x2f78, 0x4019, 0x4144, 0x4305, 0x21a3, 0xbfcf, 0x0a76, 0x412b, 0xad51, 0x01b7, 0x4276, 0x1aa1, 0x42eb, 0x405b, 0xa6b4, 0x40d1, 0x410a, 0x41f4, 0x1a37, 0x1bd6, 0x38ea, 0xb27b, 0x4120, 0x4238, 0xb250, 0x4099, 0x437a, 0xbfce, 0xba3b, 0xbfb0, 0xb211, 0xa07d, 0x1deb, 0xbad9, 0x448d, 0x1528, 0xbf00, 0xb2d4, 0x44b3, 0x1efb, 0x408d, 0x41c8, 0x43f3, 0x4289, 0x43fa, 0x2672, 0xb2cc, 0x4253, 0x4234, 0xbadc, 0x4049, 0x430a, 0xb280, 0x42ef, 0xbfb7, 0xb276, 0xbac8, 0x416f, 0xb2aa, 0x1bc1, 0x0b1a, 0xb0ee, 0x4022, 0xb273, 0x2575, 0x1867, 0x1e00, 0xb245, 0x41d9, 0x446e, 0xba5f, 0xb07a, 0xb263, 0x2274, 0x135c, 0x4045, 0xb29a, 0x19c5, 0xbf56, 0x4361, 0x422a, 0x4193, 0x4138, 0x0c7c, 0x40b9, 0x2b3b, 0xb2e2, 0xb075, 0x40b4, 0x434e, 0x4363, 0x43d6, 0x10cf, 0x400d, 0x1fc8, 0xbf3d, 0x41ee, 0x4244, 0x2a6c, 0x1e41, 0x3e24, 0x4675, 0x43b5, 0x4032, 0xb080, 0xbf95, 0x431d, 0x4159, 0x43d6, 0xba46, 0xb025, 0xbfae, 0x4024, 0x14c7, 0x2339, 0x41f7, 0x445f, 0x43e8, 0x12db, 0xba11, 0x42bb, 0x42d0, 0x1b2e, 0x1d0b, 0xba72, 0x1c00, 0x1672, 0xbf5a, 0x40bc, 0x1094, 0xb068, 0xb053, 0xb027, 0xb22a, 0x213b, 0x4133, 0xb0e5, 0x1c94, 0xb244, 0x4228, 0x41ec, 0xb2a8, 0x440a, 0x3ea0, 0x3cd2, 0xb28b, 0x431a, 0x4372, 0x4084, 0xb2b5, 0x4135, 0xbf3b, 0x0936, 0x4385, 0x3455, 0x1f02, 0xb2b5, 0x00b2, 0x0a84, 0x43b7, 0x41a9, 0x42ca, 0x2e91, 0xbf18, 0xb242, 0xbfb0, 0x4220, 0xb2bf, 0x44ba, 0x41e7, 0x4181, 0x1eaa, 0x18c0, 0x1a7a, 0x4123, 0x3ffe, 0xa60e, 0x434f, 0x082b, 0x428f, 0xbf4b, 0x3482, 0x18c0, 0x4158, 0x4454, 0xbf5e, 0xbae3, 0x18cf, 0x4221, 0x42e8, 0x2748, 0xbfbd, 0x424f, 0x41f9, 0x41d8, 0x40e2, 0x285a, 0x426a, 0x43e0, 0x4170, 0x4387, 0xaf96, 0x3f64, 0x4066, 0xbf28, 0x4089, 0x2a20, 0x43c9, 0x43fd, 0x4096, 0x460b, 0xbf7b, 0xbadd, 0x1a8d, 0x1c50, 0x401c, 0x41ea, 0xa29b, 0xba76, 0xbae3, 0xb2f9, 0x4240, 0x4193, 0x0dd9, 0xbf9a, 0xba6e, 0x4362, 0x2f82, 0x41b2, 0x4111, 0x442b, 0xa3ab, 0x4069, 0xba77, 0x3e1a, 0xbfa0, 0xaac3, 0x1fa1, 0x4122, 0x4601, 0x43f2, 0x4031, 0x43b3, 0x1305, 0xb2ff, 0x1da3, 0x40a3, 0x4019, 0xbfba, 0x40a3, 0xba43, 0x41f9, 0xb2a0, 0xae39, 0x41d2, 0x0268, 0xb2b4, 0x4082, 0x4197, 0x1ed8, 0x4323, 0x43a4, 0xbaef, 0x082d, 0x4065, 0x4188, 0x42c9, 0xa7c3, 0xbf45, 0x4269, 0x401e, 0x45b1, 0x427b, 0x43cf, 0x4242, 0xba2b, 0xbf0f, 0x4392, 0xb09c, 0xb284, 0x4117, 0x17e3, 0x414a, 0xb0ac, 0xbfd7, 0xba34, 0xbf70, 0xb0ba, 0x4243, 0xb20e, 0x431e, 0x441b, 0x43e7, 0x4005, 0xacd8, 0xba47, 0xbf85, 0xa91c, 0x42c8, 0x0b69, 0xa1d4, 0x1f9d, 0x4100, 0x41c3, 0x40a5, 0xb2a1, 0x1f71, 0xb2d3, 0x4424, 0x4176, 0x439e, 0xb2e0, 0x42f8, 0x40d1, 0xb204, 0x4617, 0x462b, 0x40e6, 0x416f, 0x4197, 0xb2ea, 0x42cf, 0x0d0d, 0xbfa7, 0xb2b5, 0x13da, 0xbaec, 0x4351, 0x42af, 0xbaf1, 0xb2e7, 0x0ff2, 0x45d2, 0xbf9a, 0x414d, 0x1ddb, 0x4047, 0x4106, 0xa497, 0x40b7, 0x2043, 0xbfe1, 0x40e4, 0x41e4, 0xb207, 0xb255, 0x4134, 0x285e, 0xb24d, 0xb28c, 0x40ab, 0x4359, 0xbfc9, 0xb25f, 0x3c0a, 0x40d3, 0x1b61, 0x41fe, 0x4443, 0x45cc, 0x4242, 0xb275, 0xb24b, 0x228c, 0x42c1, 0x437a, 0x40d9, 0x27fc, 0x38fc, 0x43e4, 0x418f, 0xbf3c, 0xb043, 0x18c6, 0x4246, 0xbfab, 0xb248, 0x08df, 0xba5e, 0x419f, 0x41d2, 0x41c1, 0x4084, 0x42a7, 0x46e4, 0x40d0, 0x43e2, 0xaef6, 0x40f3, 0xb2f6, 0x422b, 0xa7b8, 0x4596, 0x405a, 0xb203, 0xad67, 0xb023, 0xbf0b, 0x3f5c, 0x46d9, 0x29a6, 0x42c9, 0x41c7, 0xbaf7, 0x1173, 0x41cf, 0x4388, 0xbf5b, 0x45c3, 0xba74, 0xbad9, 0xb279, 0x40ad, 0x4591, 0x4332, 0x40ae, 0x1963, 0x1ced, 0x0f02, 0xba62, 0x42b6, 0x4260, 0xbaed, 0x42e7, 0xb2f9, 0x312b, 0x43d8, 0x1dfa, 0x1440, 0xa31d, 0x44c2, 0xba08, 0xb01a, 0xbac7, 0xbf9d, 0x45dc, 0x43ca, 0xaad2, 0x0c3a, 0xaa18, 0x1024, 0x3f34, 0x46fb, 0xb0e3, 0xb2e8, 0x331a, 0xbf80, 0x2092, 0x4257, 0x4328, 0xb0e0, 0xb07d, 0x4268, 0xbf76, 0x0232, 0x42df, 0xb2a0, 0xb2ab, 0x4022, 0x402f, 0x42d3, 0x1ec6, 0xb2f6, 0x18a4, 0xbaed, 0x41a9, 0xbad1, 0x4175, 0xb06b, 0x405f, 0x4189, 0xaa10, 0x42c4, 0xba1a, 0xa362, 0x2a89, 0x4022, 0xb0df, 0x0583, 0xba04, 0xbf98, 0x46b4, 0x0d9a, 0xbaec, 0xba1c, 0x2507, 0x43be, 0x43d0, 0x407c, 0xbf80, 0x0db8, 0x4114, 0x4329, 0xbf4a, 0x442b, 0xb0d9, 0xb02a, 0x4063, 0x068e, 0x40a1, 0xbfcb, 0x4398, 0x1aab, 0x42ec, 0x41b6, 0xba5a, 0x411e, 0x456b, 0x42a1, 0xba39, 0x4183, 0x4222, 0xbfb1, 0x43ca, 0xb05a, 0x4474, 0x4546, 0x1140, 0x18cc, 0x43e2, 0xba0c, 0x2c60, 0x428a, 0x426c, 0x1909, 0x42b3, 0xb049, 0x425a, 0x4234, 0x406a, 0x02de, 0x0a53, 0x4038, 0x4079, 0xbfe0, 0x40d4, 0xba20, 0x4010, 0xbaeb, 0xb2b8, 0x3c73, 0x384d, 0xbf5f, 0xb22c, 0x3e1f, 0xb2f5, 0xa11e, 0x1d16, 0x1cd4, 0xbfaa, 0x0d49, 0x4112, 0x09c1, 0x4249, 0xb211, 0x4393, 0xb210, 0xb295, 0xbf43, 0x08a4, 0x40ad, 0x4635, 0xbf70, 0x462d, 0x07ae, 0x4357, 0xb07c, 0x433c, 0x1d49, 0x394d, 0x4340, 0x4310, 0x3c5b, 0xae19, 0xb044, 0xb0c1, 0x4675, 0x3437, 0xb225, 0x4013, 0xba48, 0x42e5, 0x382d, 0x42f8, 0x01ac, 0xbf28, 0x426a, 0x424b, 0x45ce, 0xb0f4, 0x40fc, 0xb28a, 0xb2aa, 0xb098, 0xb2ec, 0x4193, 0x1b94, 0x40c9, 0x4145, 0x22d4, 0x14ab, 0xbf32, 0x3629, 0x46b5, 0x454b, 0x1c0c, 0xbaf7, 0x1078, 0xb20f, 0x434d, 0xbaff, 0x4327, 0x4282, 0x4251, 0x3560, 0x419b, 0x4694, 0x1f84, 0x42e6, 0xa35c, 0x4194, 0xa25f, 0x2f49, 0xbace, 0x1cdf, 0xb062, 0x2c47, 0x41b0, 0xbf77, 0x429e, 0xb280, 0x42e6, 0x40e5, 0xb282, 0x4002, 0x08db, 0xba3f, 0x46fc, 0xb00b, 0xba3b, 0x41dd, 0xb286, 0x1677, 0x1eff, 0xb254, 0xb2c2, 0x0100, 0x1699, 0x1c80, 0x00fd, 0x0604, 0x3bfd, 0x15b4, 0xba0c, 0x25fc, 0x42db, 0x4354, 0xbf0b, 0x1c85, 0x40cc, 0xb2f8, 0xb01c, 0x43f9, 0xb2e3, 0x438f, 0x45cb, 0x1d66, 0x430c, 0x46c0, 0x42bc, 0x41d6, 0xbfaf, 0xba63, 0x078d, 0x42c2, 0x4266, 0x4591, 0x2b98, 0x4478, 0x1fa1, 0x3b41, 0xb0ea, 0xb032, 0x4033, 0xb093, 0x0e51, 0x4357, 0xba31, 0xba29, 0xaa85, 0x40e3, 0x1e97, 0x1f3a, 0x067b, 0x428d, 0x4266, 0x430c, 0xb2ee, 0xbfcb, 0xac4b, 0xba04, 0xb009, 0x4290, 0x12a4, 0x40a2, 0x1d68, 0x4140, 0x07d3, 0x1fcb, 0xb226, 0x03c1, 0x459d, 0x42a3, 0x40a1, 0x409f, 0x1a49, 0x4040, 0x39e8, 0x4214, 0x40c5, 0xb066, 0xbf4e, 0x43ec, 0x01da, 0x420f, 0x43d5, 0xba21, 0x0941, 0xba04, 0x401a, 0xba44, 0x421c, 0xbf98, 0xbac2, 0xa729, 0xbf80, 0x0966, 0x1970, 0x402f, 0x22fc, 0x432b, 0x4026, 0x434b, 0x43e7, 0xb06f, 0xbf08, 0x403d, 0x1454, 0x44e8, 0xbf4c, 0xbafd, 0x42cd, 0x104f, 0x4361, 0x4253, 0x34fe, 0x1d4d, 0xa421, 0x21da, 0x4488, 0xbafb, 0x418f, 0x41ec, 0xb203, 0x4190, 0x45d6, 0x3a4f, 0xbac0, 0xbf9c, 0x45a4, 0x43c1, 0xba5c, 0x0eaa, 0x4241, 0xb230, 0xb21a, 0xb070, 0x43f9, 0x40a0, 0x42ec, 0x423c, 0xba04, 0x41b0, 0xb25d, 0xb09e, 0x462e, 0x41ae, 0x40f5, 0x4338, 0x28dc, 0xb004, 0xaf65, 0xb29b, 0x40d0, 0x2689, 0x4302, 0xbf37, 0x1467, 0x40ff, 0xab16, 0x40cf, 0x45a8, 0x403c, 0x4340, 0x05f5, 0x2cca, 0x42d3, 0x1583, 0x1a0e, 0x40ae, 0x3d53, 0x412e, 0x4354, 0x4328, 0x461a, 0x4208, 0x413c, 0x366a, 0xbad5, 0x41be, 0x1b57, 0xbf3f, 0x43d2, 0x425f, 0x324a, 0xb0b4, 0x3dbb, 0x4395, 0x4168, 0x4381, 0xb0af, 0x1b4b, 0x43b9, 0xb272, 0xb290, 0x3ca5, 0xba31, 0xba02, 0x43a2, 0xba37, 0xbf00, 0x1d08, 0x4442, 0x4261, 0x3c8b, 0x09bd, 0x4051, 0x294a, 0x435e, 0xbf9a, 0x426b, 0xb0de, 0xba13, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xb365a6c0, 0x01723518, 0x0009419f, 0x745fc87d, 0x5ed42207, 0x1f49801a, 0x138d5de7, 0xd2498a44, 0x0115d59c, 0x1cc12bbe, 0x5e000be4, 0xe36b4a94, 0x3974216e, 0x5afb38c7, 0x00000000, 0x900001f0 }, + FinalRegs = new uint[] { 0x69000004, 0x5c11201c, 0x5c1120b9, 0xb920115c, 0xfffffed0, 0x01a40000, 0x00004ffb, 0x69000000, 0x5c1120b9, 0xf9076780, 0x42812c14, 0x000014ca, 0x00001650, 0x5afb4adf, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x3e9f, 0x423a, 0xb038, 0x1a66, 0x2ab4, 0xbf88, 0x41f2, 0x43ed, 0x46e2, 0xbafa, 0xb277, 0xacc3, 0x3d1f, 0xb2f5, 0x008e, 0x4288, 0x298f, 0x4058, 0x1fc4, 0x4234, 0x4372, 0x422f, 0xb260, 0x41ca, 0x4030, 0x4004, 0x40a9, 0x40c8, 0xb2e9, 0xb24e, 0x1d5a, 0xbf9b, 0xb062, 0x4160, 0x3cb1, 0xba5f, 0x43a1, 0x4362, 0x4553, 0x4260, 0x4329, 0x1061, 0xba58, 0xb2f4, 0x40ad, 0x41be, 0x436a, 0x43a3, 0xbfc0, 0xb0f6, 0x401a, 0x0c62, 0x1513, 0xb205, 0xb215, 0x090b, 0x1c21, 0x28af, 0x4092, 0xb294, 0xbfbd, 0x2cb1, 0x1b1e, 0x0984, 0x4175, 0xa7e8, 0xb273, 0x2636, 0x410b, 0x45c0, 0x41ec, 0xb2eb, 0xb0d0, 0x42ca, 0xba52, 0x4185, 0x1455, 0x4600, 0xb215, 0x1947, 0x24af, 0xba32, 0xbfab, 0x40ac, 0xba0b, 0xba5b, 0x07fa, 0x0723, 0xb2c4, 0x2163, 0x42d5, 0x4004, 0x40a7, 0x45d8, 0x43af, 0x42b4, 0x465d, 0xba27, 0x4395, 0x2d9c, 0x192f, 0x1a6a, 0xb28d, 0xbfc7, 0x458b, 0x4281, 0xadc4, 0x4092, 0x125e, 0xbfac, 0x43c8, 0xadde, 0x2713, 0xb265, 0xbf62, 0x3095, 0xbff0, 0x1bd1, 0x40a8, 0x43e3, 0x18c1, 0x41b0, 0x3fb9, 0x18ae, 0x4314, 0x347e, 0x4258, 0x45b5, 0x4660, 0x401d, 0x1dcc, 0x4667, 0x420b, 0x4172, 0xac70, 0x39f3, 0x4097, 0x19b6, 0x41fe, 0xb0c1, 0x4623, 0x1394, 0xbf25, 0xba65, 0x4101, 0x439d, 0x42e1, 0x4333, 0xb0e5, 0x41dd, 0xb268, 0x22c7, 0x2674, 0xba71, 0x421f, 0x4450, 0x0cd7, 0x40e4, 0x1b43, 0x3ea7, 0x1311, 0xbf4e, 0x411b, 0x4112, 0xa8fa, 0xbf19, 0x4015, 0x3844, 0x40d4, 0x4087, 0xbfa7, 0x1f9b, 0x0c01, 0x213b, 0x3a56, 0x4242, 0x40bf, 0x4233, 0xb046, 0x42b5, 0x4556, 0x3058, 0x42b8, 0x36be, 0x437b, 0x1eb6, 0xbacd, 0x4173, 0x433f, 0x42a2, 0x41f3, 0xba23, 0x412f, 0xacfb, 0x4221, 0xbf25, 0x40b0, 0x40b4, 0xba72, 0xb0f2, 0x461f, 0x3123, 0x037e, 0x4580, 0xa9da, 0x2938, 0x42a6, 0x3aaa, 0x4125, 0x2721, 0xbfa4, 0x2a82, 0xb215, 0x41c0, 0xa1c3, 0xb27f, 0x4223, 0xba05, 0x1e73, 0x4054, 0x4629, 0x4415, 0x1cae, 0xb222, 0x1ac2, 0x424f, 0x206a, 0x4080, 0xb2c3, 0x412a, 0xa37e, 0x4037, 0xb26d, 0xb09a, 0xb211, 0x442d, 0xaca9, 0xbf0d, 0x34d4, 0xbfa0, 0x196a, 0x408f, 0x4313, 0x41ea, 0x1560, 0x46ea, 0x449c, 0x3a47, 0x40a6, 0x440a, 0xb267, 0x33b8, 0xb22c, 0x46eb, 0x1824, 0x3843, 0xbfca, 0xbfd0, 0x0716, 0x1031, 0xba00, 0x0daf, 0xba7e, 0xba72, 0xba11, 0x4375, 0xad64, 0xa1db, 0xb263, 0x42e7, 0x46f3, 0xa375, 0x4097, 0xa116, 0x4302, 0x19f5, 0x40ce, 0xbaf9, 0x269e, 0xbf0b, 0x407e, 0xb0f0, 0x45f4, 0x40fc, 0x1e2e, 0xba56, 0x4136, 0xba41, 0x4222, 0xbf1c, 0xb00c, 0x433f, 0xba7a, 0x41a5, 0xbfd0, 0x4260, 0xaf36, 0x42f3, 0x4376, 0xb0e8, 0x42e7, 0x0b6f, 0x4315, 0x1e04, 0xa4ef, 0x135b, 0xa6cd, 0x29d1, 0x40b6, 0x42d0, 0x4596, 0x4383, 0x466d, 0xbf7d, 0x46e4, 0x3014, 0xba02, 0x2ad7, 0xb2fe, 0x3de9, 0x1aa7, 0x2d8c, 0x150c, 0x4233, 0x40a0, 0xba40, 0x3a7a, 0x4361, 0x4447, 0xae8e, 0x4268, 0x4391, 0x41c8, 0x29dc, 0x4174, 0xb23c, 0x1bfc, 0x2c34, 0xbfc0, 0x194b, 0xbf9c, 0x43a6, 0xba78, 0x1b4b, 0x43ce, 0xbad7, 0xbfad, 0x067f, 0x4117, 0x4298, 0x4380, 0xb206, 0xba22, 0x4553, 0x3d55, 0x42f4, 0x1c7d, 0x419e, 0x206f, 0x1af1, 0xbff0, 0x3531, 0x1d92, 0x4150, 0x3fb8, 0x1e5f, 0x4250, 0x1fa1, 0x4202, 0xb0a1, 0xbf11, 0x45d9, 0x4676, 0x40fa, 0x421e, 0x1f6a, 0x0bf8, 0x29cc, 0xbf9d, 0x412e, 0xbf60, 0x3489, 0x4007, 0xba3b, 0x1836, 0x297c, 0x4284, 0x42e1, 0x4409, 0x429f, 0xba1a, 0xbac9, 0xb2b9, 0x3794, 0xa128, 0xa5c4, 0x42fb, 0x4593, 0x4184, 0xb0de, 0x0384, 0xbf3a, 0x3416, 0x3ea6, 0x411c, 0x42cd, 0xbfc0, 0xbaf3, 0xaa2e, 0x4317, 0xb2b2, 0x42d2, 0xb025, 0xb2d1, 0x091b, 0x423a, 0xb094, 0x468d, 0x4426, 0x31f9, 0xbf2e, 0x1e6b, 0x4388, 0x1c68, 0xba48, 0x3089, 0x4607, 0xba01, 0x416d, 0x1d85, 0x4424, 0x40d4, 0x41fa, 0xb28e, 0x1d5f, 0x4177, 0xb0d1, 0x1c5e, 0x4203, 0x0cf5, 0xb24b, 0x1cde, 0x1d65, 0x4281, 0xbfa6, 0xba36, 0x42a4, 0x4243, 0x44fa, 0x43b6, 0xa162, 0x41be, 0xb05d, 0x001b, 0x4283, 0xb2fd, 0x0395, 0xb28d, 0x40fb, 0xba1f, 0x43b1, 0x41af, 0x4157, 0xb245, 0x42f4, 0xb06b, 0xb27c, 0x1d2f, 0x45ee, 0x4435, 0xbfe2, 0x190a, 0x43a7, 0x4565, 0x41c3, 0xba46, 0x42d3, 0xb014, 0x23e7, 0x43a1, 0x436d, 0x4386, 0x1b31, 0x44e9, 0x427e, 0x1b44, 0xba66, 0xb02b, 0x404f, 0x42df, 0x438c, 0xba6f, 0x4083, 0xb02c, 0xba59, 0xaf47, 0x456d, 0xbfbd, 0xb2bd, 0x1e66, 0xbaee, 0x41af, 0xb05e, 0xbf70, 0x41eb, 0x4099, 0x42f1, 0x41d3, 0x40ed, 0xaefe, 0x4223, 0xa7ab, 0x02cb, 0x4393, 0xb258, 0xbfa3, 0x4311, 0x43f3, 0xb2f5, 0x43f4, 0xbf5e, 0x1854, 0x2a65, 0x1c8d, 0x4363, 0x4087, 0x40b2, 0x410a, 0xa11a, 0xb2ca, 0x1efa, 0x20d8, 0x419c, 0x41fd, 0x4283, 0x43f4, 0x4264, 0x420a, 0x469c, 0x1961, 0xb2fc, 0x429a, 0xb2b7, 0x4293, 0xa373, 0xbf98, 0xab6d, 0xb266, 0x01e3, 0x4176, 0xa2e0, 0x0265, 0x42ba, 0x40ce, 0x0695, 0x42a8, 0x3b85, 0x1ea1, 0x428b, 0x442a, 0x4452, 0xbf3c, 0x1bcb, 0xba76, 0x466b, 0x41bb, 0x44b8, 0x401d, 0xba78, 0x4020, 0x46a2, 0xb2ce, 0xb24f, 0x4202, 0xba02, 0x46d1, 0xbf3b, 0x4329, 0x0e1d, 0x40f7, 0x0daa, 0x40b6, 0xb2f0, 0xb208, 0x45a8, 0xbf0a, 0x43d7, 0xb238, 0x43e5, 0x43f3, 0x1dee, 0x3982, 0xbaf5, 0xbf70, 0x022f, 0x42d8, 0x4270, 0xae68, 0x1b50, 0xb2fa, 0x43ce, 0xa22b, 0x4103, 0xb0fe, 0xbf0a, 0xba03, 0xba58, 0xb21c, 0x4242, 0x428b, 0xb21b, 0x46fc, 0xbad7, 0x44f9, 0x409d, 0x1fa0, 0x422f, 0x4045, 0x0cb9, 0xbf37, 0x4168, 0x42e2, 0x1d17, 0x4146, 0x404b, 0x4397, 0x4023, 0x4218, 0x1be7, 0xb20c, 0xbfd0, 0x4166, 0x4677, 0x442e, 0x0bc1, 0x462e, 0x4103, 0x1b0b, 0xb0b5, 0xbf39, 0x3c04, 0xb2a6, 0x4132, 0x031d, 0xbac8, 0xb0d9, 0x436b, 0xb208, 0x4127, 0xbad3, 0x0705, 0x116f, 0xb23b, 0xba75, 0xaccc, 0xba6c, 0x19b5, 0x42c1, 0xb296, 0xbf98, 0x28ab, 0x1165, 0x4195, 0xb06b, 0x4356, 0xbf21, 0x2e2c, 0xba6e, 0xbf70, 0x1b50, 0x1d56, 0x4299, 0x41d0, 0xb2b1, 0x4097, 0x436b, 0x442b, 0xba7f, 0x409b, 0x2918, 0x40a4, 0x2155, 0x444c, 0x3bce, 0x4417, 0x1d3d, 0xb0b1, 0x4414, 0xb28b, 0xbf9e, 0x26d9, 0xba2e, 0x1b11, 0xbf60, 0x40a3, 0xb244, 0xb2b3, 0x4357, 0x40fd, 0x4253, 0x1b39, 0x408f, 0x43e5, 0x2a5f, 0x467b, 0x0fc0, 0x43fa, 0x1ad5, 0x0dca, 0x414e, 0x4401, 0x1cb1, 0xb229, 0x0cdd, 0x19a8, 0x29c7, 0xb2d4, 0x45b6, 0xbf6a, 0x085b, 0x0aa6, 0x195f, 0x4178, 0x0eae, 0x42af, 0xbfa2, 0x287d, 0x40e7, 0x45cb, 0x4233, 0xa5e9, 0x4005, 0xba4a, 0x42c7, 0xbf99, 0xba16, 0x402b, 0xb0a5, 0xb286, 0x40cb, 0x436b, 0x4326, 0x42da, 0x1762, 0x41f8, 0xb2c4, 0x4172, 0x3324, 0x1be2, 0xb210, 0x0a29, 0x2800, 0x225c, 0x02b0, 0xa200, 0x4125, 0x43c4, 0x4055, 0xb22c, 0x2b85, 0x42bc, 0x3465, 0xa4e5, 0xbfba, 0x4268, 0x33b2, 0x1ef1, 0x1d70, 0x4314, 0x4335, 0x4112, 0x2e63, 0x1fc5, 0xb091, 0x0ce5, 0x42a8, 0x17b0, 0x407a, 0xbf5d, 0x407a, 0x3b90, 0x0908, 0xb282, 0x1327, 0xbf00, 0x4376, 0x4278, 0x4075, 0x1de1, 0x4209, 0xb2c6, 0x42fd, 0x0018, 0x1d05, 0x46a3, 0x4290, 0xba7b, 0x0fc7, 0x1652, 0xbac1, 0x4309, 0x41b7, 0x36d6, 0xbf34, 0x43d6, 0x449c, 0xb2a3, 0xb269, 0x41d7, 0x24c0, 0xba4e, 0x40a0, 0xb005, 0x4454, 0x1542, 0x4311, 0x40ef, 0xb28f, 0x42c9, 0x2a56, 0x44f9, 0x407f, 0x454b, 0x2eff, 0x43e9, 0xba66, 0x4332, 0x4344, 0xb0a0, 0xbf31, 0x3bdd, 0x1e29, 0x1289, 0x3d48, 0x4077, 0xace3, 0x43ee, 0xb085, 0x1d32, 0x43b9, 0x411b, 0xb2ee, 0xb06f, 0xb26a, 0xb0ed, 0x0de4, 0x2e64, 0x36cd, 0x44d9, 0xbf26, 0x2866, 0xb271, 0x4132, 0x435c, 0xab9f, 0xa5f0, 0x40e8, 0x1dc4, 0x44e5, 0x409e, 0x15a3, 0x2a03, 0x4032, 0x43c2, 0xb29d, 0x3611, 0xbf02, 0x1a71, 0xb2bd, 0x44a5, 0x44dd, 0x2f63, 0x1176, 0x4017, 0x1c57, 0x42c5, 0xb257, 0xb01a, 0xb210, 0xba3e, 0x1033, 0xbaea, 0x2913, 0xb216, 0xbfa0, 0x414a, 0xbf01, 0x45dc, 0xba34, 0x43a4, 0x09ca, 0x44cc, 0x1fe2, 0xba21, 0x1c5d, 0x1a65, 0xba3f, 0xbfa4, 0x4344, 0xb24f, 0x1d4a, 0x4191, 0x4444, 0xba06, 0x413b, 0xbf58, 0x2509, 0xba2b, 0xb2a8, 0x1f68, 0x3a63, 0x467a, 0x05de, 0x4186, 0xba34, 0x42db, 0x1e2d, 0xbaef, 0xbfd0, 0x1c2d, 0x4176, 0x26bd, 0x4214, 0xa8fb, 0x4187, 0xba36, 0x42aa, 0x4073, 0x46d8, 0xb2f4, 0x40b5, 0x43fa, 0xbf87, 0x4225, 0x4336, 0x4376, 0x1c37, 0x4142, 0xb2e3, 0xbfe0, 0x3578, 0x41e3, 0x41cb, 0xb246, 0x402f, 0x2890, 0x422c, 0x40e9, 0x1892, 0x422d, 0xb209, 0xb2d6, 0x4045, 0xa2a6, 0x43bc, 0xbf37, 0x43ee, 0x4239, 0x326a, 0x3538, 0xb240, 0x43e8, 0xaae3, 0x3b5c, 0xa277, 0xb26f, 0xbf6d, 0xaf28, 0x4372, 0x1e45, 0x40f4, 0xb09f, 0x46e9, 0xb272, 0x4275, 0x2cb5, 0x42df, 0x03a3, 0x413f, 0xbac0, 0x40fa, 0xbad1, 0x0fba, 0xb2ca, 0x42dc, 0x4119, 0xb299, 0x41b9, 0x415f, 0xb240, 0xbf9c, 0xb224, 0x41de, 0x42f1, 0xbace, 0x34a5, 0xb073, 0xb2e6, 0x4073, 0x4185, 0xba32, 0x429b, 0x037d, 0xbfb0, 0x1855, 0xba71, 0x4199, 0x425a, 0x2faf, 0x410d, 0x41f6, 0x4349, 0x1eb6, 0x4333, 0x40dd, 0x40e9, 0xbf8b, 0x431c, 0x1eb9, 0x40c9, 0xb2ca, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x034bf2c4, 0xca960aa8, 0x9456a116, 0xb988fc86, 0x50cab48a, 0xa5759d59, 0x0dfb191d, 0x2b41a1c1, 0x1b2df3ef, 0x1463f435, 0x89ed0b15, 0x9d5b9a9c, 0x39b4b3b7, 0x23d2d921, 0x00000000, 0xc00001f0 }, + FinalRegs = new uint[] { 0xffffffc7, 0xfffffffd, 0x000000fd, 0x280000a7, 0x000000a5, 0x00000000, 0x28000003, 0xffffffff, 0x00001dec, 0x00003494, 0x000000ac, 0x00001dec, 0x00005e9c, 0x00003660, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x404a, 0x1e38, 0x41ce, 0x41f6, 0xa61e, 0x43e1, 0xba33, 0x0e8f, 0x4366, 0x1fcf, 0x1f02, 0x417c, 0xb24c, 0xbf09, 0xadc6, 0x1cfb, 0x2652, 0x3b78, 0x43dd, 0x4058, 0x435c, 0x418d, 0x41e9, 0xba1e, 0x461a, 0x417c, 0x4070, 0x2e53, 0xbf46, 0xb213, 0x0a50, 0x43ce, 0x403c, 0xb0fb, 0xba32, 0x43c9, 0x408b, 0x4240, 0xba6c, 0x4345, 0x43c8, 0x422f, 0x1b87, 0xb288, 0x1b5c, 0xba0a, 0xbf8c, 0x4371, 0xba11, 0xbf45, 0xa04e, 0xb290, 0x430b, 0x43e5, 0x1ae7, 0x41e3, 0x4260, 0x4631, 0x3e11, 0x46e1, 0xbae9, 0x4207, 0x40ac, 0x404c, 0x2f8f, 0xbf2e, 0x43db, 0x0ef2, 0x41bd, 0x0426, 0xb2e5, 0x3c47, 0xb092, 0x4331, 0xae0c, 0xa2f5, 0x420d, 0x4245, 0x436a, 0x1e7b, 0xac86, 0xb221, 0xb0d1, 0x38f0, 0xbfe0, 0x420b, 0x428d, 0x46c3, 0xa967, 0xb236, 0xbac2, 0x41fc, 0x4026, 0xbf4a, 0x1a93, 0xa7fd, 0x43e8, 0x0d91, 0x42f0, 0xb013, 0x1cb9, 0xb2bd, 0x039a, 0x4307, 0xbfb2, 0x049f, 0x1b17, 0x0a41, 0x18b0, 0xb23e, 0x4126, 0x14d5, 0x3efd, 0x42cd, 0xa767, 0x431e, 0x41f3, 0xb29c, 0x06c1, 0x29be, 0x363e, 0xb26a, 0xa0f2, 0x1e42, 0xbad5, 0xbaca, 0x1ab0, 0xba62, 0xb29d, 0xba6a, 0xbf27, 0x4385, 0x4135, 0x4637, 0x429e, 0xb04e, 0xbf90, 0x432d, 0x4199, 0x40ff, 0x4009, 0x4229, 0xb250, 0x055e, 0x2803, 0xb27c, 0x40f2, 0x4060, 0x41de, 0xb087, 0x2667, 0x439e, 0x466d, 0x4060, 0x1c10, 0x4003, 0xa2e9, 0xb0fc, 0xbfca, 0x4230, 0x42db, 0x1a8c, 0xb2dc, 0x42d5, 0x40d2, 0xba72, 0xbad8, 0x2197, 0x4029, 0x434b, 0x1b03, 0x20bf, 0xb2d0, 0x45e6, 0x4248, 0xac8e, 0x45d8, 0xb23f, 0x431c, 0x2e97, 0x4041, 0x413f, 0xbf92, 0x42b5, 0x4351, 0x46c5, 0x4098, 0x40d4, 0xb2cd, 0x43af, 0x41a1, 0xb035, 0x3e50, 0xbf45, 0x419c, 0x3771, 0xb270, 0x0cda, 0x1f3d, 0xb2a1, 0x18d4, 0x3595, 0xa300, 0x2257, 0x4230, 0xb267, 0xbf63, 0x22b5, 0x334c, 0x4008, 0xb2fb, 0xba14, 0x435b, 0x4253, 0x2bbd, 0x4375, 0x114d, 0xa458, 0x2764, 0x4073, 0x437d, 0x32d1, 0xb034, 0xba67, 0x40b2, 0xa45b, 0xa541, 0x417e, 0xbf93, 0x449d, 0x19af, 0xbada, 0x1c8f, 0xb288, 0x44b9, 0x35bc, 0x407f, 0x4389, 0x099d, 0x1daf, 0x433e, 0x07cc, 0x31b7, 0xb0e3, 0xa4fb, 0x4095, 0xb207, 0x40ec, 0xb2e8, 0x0ec7, 0x0c90, 0xb218, 0xbf6d, 0xa111, 0x1cb3, 0x2234, 0x1fa6, 0x4242, 0x2c82, 0x41d4, 0x2053, 0xbac3, 0x0cd9, 0x2ead, 0x425d, 0xb0c0, 0x44b0, 0xb2b5, 0xb01f, 0x1ce8, 0x46b1, 0x4156, 0xb2e2, 0x41b7, 0xae46, 0x1ee3, 0x444a, 0x415f, 0x4375, 0x1251, 0xbaee, 0xbf13, 0xb22f, 0x401b, 0x19ba, 0x4290, 0xa67b, 0xbac7, 0x1b71, 0x4156, 0x241a, 0x4577, 0x3cbe, 0x1b08, 0x40b2, 0x2f7d, 0x1a27, 0x4335, 0xbaf1, 0x2f52, 0x1850, 0x2414, 0x4129, 0xb24d, 0xbfbb, 0xb283, 0x07e8, 0x399e, 0xbac4, 0xb0af, 0x374d, 0x1bc1, 0x0f91, 0xb09d, 0x22a6, 0x419f, 0x3131, 0x1bdc, 0x42f7, 0x43d7, 0x29ac, 0xbf4c, 0xa2c6, 0xac1c, 0x1d92, 0x4127, 0xaa2a, 0xba5b, 0x4159, 0x41b8, 0x4320, 0x1a6f, 0x249a, 0x3fec, 0xbf5c, 0x41c0, 0x42ed, 0x1a95, 0x1ce2, 0xa1a3, 0x41e9, 0x4014, 0x05f4, 0xb03d, 0x4177, 0x3a2b, 0x4230, 0xabc9, 0x115d, 0x4308, 0x41c6, 0xb273, 0xb2cf, 0xa77c, 0xac87, 0x417e, 0x300c, 0x28a6, 0x42a9, 0xbf39, 0xaeb8, 0x1d9b, 0x1aed, 0x4346, 0xbaf9, 0x462a, 0x4199, 0x0418, 0x45cc, 0x4371, 0x04e7, 0x43f4, 0xb204, 0x4398, 0x438c, 0x43eb, 0xb201, 0x4155, 0x0a6e, 0x2d49, 0x42a3, 0xbac0, 0x43f4, 0x4245, 0xbfa3, 0x1edc, 0x446c, 0x3da5, 0x42e3, 0x42ae, 0xbf2c, 0xb25a, 0x40e7, 0xbfae, 0xb2ec, 0xba4a, 0x4301, 0x43c0, 0x0f46, 0x4182, 0x4233, 0x4084, 0xa36e, 0xbf9f, 0x410e, 0x42a0, 0x1879, 0x43d8, 0x44ea, 0xba40, 0xbfc5, 0x43fe, 0x42b9, 0x42b1, 0x4172, 0x4151, 0x42d4, 0xb25a, 0xbff0, 0xb026, 0x4116, 0x1ac9, 0x435b, 0xbfa2, 0x4482, 0x188e, 0x46f0, 0x26c3, 0x4103, 0x4491, 0xbf80, 0x2b1b, 0x41a2, 0x4022, 0x4132, 0x4235, 0x4214, 0x43d6, 0x4025, 0xa55b, 0x4040, 0x2d18, 0xac95, 0x4316, 0xbadd, 0x41c9, 0xbf9c, 0x2e9b, 0x34ac, 0x40d6, 0x1f75, 0xbfc0, 0x43a8, 0x3e7b, 0x20b6, 0xb0d7, 0xb22f, 0x41d0, 0x1c3a, 0x23a0, 0x1a3a, 0x426e, 0x4178, 0x45c8, 0x426a, 0xbf18, 0x42d7, 0x42e9, 0x40c1, 0x40da, 0xb273, 0xba11, 0xb209, 0xb07b, 0x18af, 0x40bf, 0x4253, 0x0e6f, 0xbf9d, 0x42a7, 0xa767, 0xba35, 0xad49, 0x1d61, 0x1dca, 0x436a, 0x2f36, 0x40e9, 0x1cf2, 0x4061, 0xba3e, 0x099e, 0xac24, 0x4559, 0x467b, 0x1a05, 0x08ce, 0xb295, 0xbfb3, 0x4057, 0x4384, 0x1f5a, 0x13e2, 0x4561, 0x442b, 0xbf02, 0x1563, 0xba37, 0xb230, 0xb0be, 0x41a4, 0x4235, 0xbf3c, 0x4008, 0xb04a, 0x4297, 0xb20d, 0x2cca, 0xb0d8, 0x4386, 0x4054, 0x402c, 0xbf60, 0xbf82, 0x41d9, 0x18bf, 0x1a14, 0x1236, 0x2e49, 0x429d, 0xa669, 0x4120, 0xbfdb, 0x3c29, 0x04af, 0x3169, 0x425f, 0xa558, 0xbf0d, 0xaf86, 0xb0ef, 0x0707, 0x41ba, 0xb263, 0xbfb9, 0x42eb, 0x3db5, 0x3ac5, 0xb214, 0x40ab, 0x42c6, 0x1d21, 0x439c, 0x2200, 0xb0fb, 0x4695, 0x41ef, 0x4178, 0x42ea, 0x43ec, 0xb297, 0x419d, 0x4091, 0x405b, 0x43a6, 0xb2de, 0x21ab, 0xba6d, 0x4002, 0xabb4, 0x400f, 0x40a2, 0xbf2f, 0xb243, 0xa1a2, 0x460c, 0xb2e0, 0xbf9f, 0x1ad6, 0x41d4, 0xb0a0, 0x1b7f, 0x43d7, 0xb228, 0xb2ca, 0x2655, 0x3efc, 0xb249, 0x27dc, 0x428c, 0x40f9, 0x4354, 0x429d, 0xb266, 0x4595, 0xb0cf, 0xb2c9, 0x401e, 0x4264, 0x42b4, 0x0c35, 0x0e87, 0x407f, 0x4336, 0x403d, 0x1df2, 0x41c1, 0xbf39, 0x43bd, 0x4174, 0x4329, 0x44a2, 0x1932, 0xbf4b, 0xb282, 0x1ded, 0xbacf, 0x404a, 0xb246, 0x4630, 0x1ac0, 0x43d7, 0x40a2, 0x4270, 0xb2e1, 0x0cf0, 0x1ecc, 0xbac3, 0x408b, 0x4358, 0x1ca6, 0xbf6c, 0x416b, 0x2d6e, 0xb017, 0x4385, 0xa144, 0xb2b5, 0x41fd, 0xbf28, 0x1aef, 0xb239, 0xb2ba, 0x430e, 0xb0c0, 0x42cb, 0xbfdd, 0xb020, 0x44d9, 0x414d, 0x402b, 0xb033, 0xba4c, 0x1ace, 0x401b, 0xb200, 0x40ae, 0xa013, 0x1fb4, 0x42a2, 0xba53, 0x4006, 0xbfc5, 0xba13, 0xba0a, 0xb21c, 0x406c, 0x0f32, 0x0d61, 0xb28a, 0xadd9, 0x2943, 0x42d8, 0xb212, 0x1e8f, 0xb273, 0x426f, 0xb08b, 0xa7ca, 0xbac8, 0x43b2, 0xa2d0, 0x0940, 0xbaee, 0x42b3, 0x42fe, 0x0239, 0xbf6c, 0x4082, 0xb272, 0x1219, 0x4101, 0x43a8, 0x403e, 0xb251, 0xb251, 0xb035, 0x1abe, 0x1853, 0x4373, 0x4086, 0x4330, 0xb22d, 0x4491, 0x42cb, 0x442d, 0x073a, 0x1abd, 0xb23c, 0xbf6e, 0x43b5, 0x1cd5, 0x1cac, 0x1a26, 0x42c9, 0x4312, 0x41f6, 0x034a, 0xbf1b, 0xba22, 0x43c1, 0x408b, 0x3efd, 0xbf8e, 0x45b1, 0x1ff9, 0xb0b4, 0x42a4, 0x40ff, 0xbae4, 0xa80c, 0xac90, 0x42fd, 0x41e9, 0x434a, 0xba62, 0x4263, 0xafea, 0x42a8, 0x1ebd, 0x0ace, 0x41e2, 0x448b, 0xb231, 0x4186, 0x40cf, 0xbf93, 0xba41, 0x4355, 0xbaca, 0xba6e, 0xb2c3, 0xa584, 0x421d, 0x42c3, 0x0b33, 0xb24a, 0x459b, 0x406a, 0x44c3, 0x1c47, 0x1dbe, 0x1b02, 0xa03c, 0x43af, 0xba58, 0x41b4, 0x43f2, 0x1e2f, 0xbfb8, 0x2fcb, 0x43d3, 0x425e, 0x2f90, 0xb2eb, 0xbfaf, 0x4446, 0x42d0, 0xb0e1, 0x3d22, 0x40fe, 0x425a, 0x439f, 0x4190, 0x417d, 0xb23f, 0x0e6c, 0xbf5d, 0xbfc0, 0xbfb0, 0x4136, 0x3dbb, 0x4548, 0x1ed0, 0x2e7c, 0x427e, 0x420b, 0x1a4a, 0x1b24, 0x405e, 0x42c8, 0x1e86, 0xb2b6, 0x4369, 0x02c5, 0x41c0, 0x4586, 0x417c, 0xb23c, 0x13d1, 0x2eb6, 0xbf9a, 0x4406, 0xbae8, 0x0fc1, 0x4302, 0x37a4, 0x3676, 0x16fc, 0x4292, 0xbf8b, 0x4022, 0x4122, 0xb2d8, 0x3706, 0x4258, 0xbf91, 0x4310, 0x4631, 0x408d, 0x19e5, 0xb01d, 0x4087, 0xa7ab, 0x0ffe, 0xba00, 0x37e6, 0x41ec, 0x407c, 0x406d, 0x1896, 0x43c4, 0x1666, 0x4023, 0x26e4, 0xbf7d, 0xad97, 0x43cd, 0x41cd, 0x215e, 0x41f7, 0x40e6, 0xb270, 0x414c, 0x18d9, 0x3998, 0xb078, 0xb2f5, 0x42bd, 0x0eef, 0x42c4, 0x0f53, 0xaf6b, 0xb29b, 0x11f7, 0x1f08, 0x4030, 0x4384, 0x3b0a, 0x3012, 0x4004, 0x43f4, 0x4636, 0x403f, 0x4484, 0xbfd4, 0xba40, 0xb049, 0x4608, 0x40c4, 0xbf92, 0x1898, 0x36dd, 0x403d, 0x1b5e, 0x4136, 0xbac7, 0x0f85, 0x1f13, 0x4337, 0xbae7, 0x416d, 0xa007, 0xbfcf, 0xaa8b, 0x2329, 0xb28f, 0x4380, 0x431c, 0xa918, 0xadc2, 0xad91, 0x250b, 0x4127, 0x11a2, 0x4395, 0xba07, 0x2356, 0xb21a, 0x4367, 0xb25b, 0x32db, 0x435e, 0x0285, 0xa8d6, 0x4164, 0x045d, 0x3a1c, 0x4191, 0xb0f5, 0x192a, 0x43c2, 0xb278, 0xbf1f, 0xb2e9, 0x456c, 0x4308, 0xb2f4, 0x4261, 0xbf2c, 0xbfe0, 0x4592, 0x431b, 0x402c, 0x1e54, 0x427f, 0x1f79, 0x4052, 0xa524, 0x4132, 0x2bc5, 0x0dfc, 0x1b3d, 0x4053, 0x42e1, 0xbaea, 0x3163, 0x42bc, 0xbfb1, 0x3240, 0x34fb, 0x42b0, 0xb034, 0xadf0, 0x4069, 0x413f, 0xbad6, 0x4335, 0xba39, 0x428e, 0x3b25, 0x41f9, 0x1816, 0x2ec7, 0x1e92, 0x4307, 0xb2d1, 0x405c, 0x41a0, 0x0efc, 0xa329, 0xbfc3, 0x435e, 0x438f, 0x4352, 0xb0c0, 0xb259, 0xae76, 0x40bf, 0x18f7, 0xbae0, 0xb2e7, 0xa6fe, 0x46c3, 0xbf69, 0x4613, 0x406b, 0xb2eb, 0x43ce, 0xa9e3, 0x10eb, 0x46ec, 0xba10, 0x427a, 0x412b, 0xbf9e, 0x41c1, 0x4353, 0xb2c7, 0x42f8, 0xafae, 0x1ab7, 0xb062, 0x407d, 0x4330, 0xb2af, 0x3801, 0xbfab, 0x43c8, 0x423d, 0x4159, 0x41e0, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x7059abe5, 0xb415f906, 0x8490eae9, 0x0ff183c6, 0xf39f6a2c, 0xb4413795, 0xee05d88d, 0x50185d92, 0xf621a094, 0x8902e42e, 0x9ecca830, 0x152bbe7f, 0x038e4ec7, 0xeb5d3b32, 0x00000000, 0xe00001f0 }, + FinalRegs = new uint[] { 0x89283ba8, 0x000001bb, 0xfffffff1, 0x00000000, 0x0000000f, 0x0000240e, 0x00001ba8, 0x0000240e, 0x00000000, 0x000013e8, 0x94ee481b, 0x00000000, 0xffffffe8, 0x00000170, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xba51, 0x414a, 0x4030, 0x4380, 0x4328, 0xb07b, 0x425a, 0xb214, 0x42fd, 0x2b6a, 0xaf72, 0x0da2, 0xb260, 0xb0a2, 0x4017, 0x4242, 0x38f4, 0x40d8, 0xb2b5, 0xbf43, 0x40f1, 0xb29b, 0x4240, 0x412b, 0x1c5d, 0xb27f, 0x2483, 0x4322, 0xba3d, 0x380c, 0xba4e, 0x1a5d, 0x4387, 0xb21d, 0xb2b6, 0xbf29, 0x2514, 0x43b9, 0x424b, 0x3cb5, 0xbf90, 0x40b2, 0x2a94, 0x42ce, 0x42df, 0xb23a, 0x12c2, 0xb232, 0x3230, 0x194f, 0xbf9e, 0xac89, 0x40d9, 0x4023, 0x424a, 0x2736, 0x0a55, 0x401a, 0x1835, 0x402e, 0xb20b, 0xbf6f, 0x2155, 0xbaf4, 0x39c6, 0x0161, 0x4217, 0x33de, 0x379d, 0x1dbc, 0x0b38, 0xb2ff, 0x23eb, 0x4072, 0xbf8a, 0x4201, 0x4240, 0x41b1, 0x1916, 0x40c4, 0xb26a, 0x4345, 0x0e81, 0xbf17, 0x4359, 0x4396, 0x4185, 0xa8f1, 0x43ce, 0x0472, 0x424d, 0x438d, 0x4018, 0x3684, 0xb244, 0x4349, 0x40a9, 0x400c, 0x41ac, 0x42ff, 0xaea0, 0xa7e6, 0xb2ec, 0x31eb, 0xb211, 0x42c8, 0x2649, 0x4064, 0xbad9, 0xbf05, 0x4169, 0x4664, 0x4679, 0x4576, 0x415b, 0xb200, 0x1f31, 0x2abf, 0xb0f4, 0x46d5, 0x4387, 0x40fa, 0x42dd, 0x18c0, 0xb021, 0x3d89, 0x403f, 0x4257, 0x1f9a, 0x43ed, 0x3c5c, 0x1c96, 0x41d4, 0xb201, 0x2ff5, 0x43ab, 0x0f0f, 0xbf69, 0x4637, 0x1895, 0x1d62, 0xa75b, 0xb0c3, 0x45d2, 0x432b, 0xb217, 0x18c7, 0x41dd, 0xa705, 0x4219, 0x433d, 0xa9c2, 0xb29d, 0x40f1, 0xbaf1, 0x420c, 0xb256, 0x4112, 0xbfbd, 0x1ab8, 0xba34, 0xbfe0, 0xbadf, 0xa4ed, 0x4323, 0x41a6, 0x40d6, 0x40c3, 0x4361, 0x0974, 0x1990, 0x42dc, 0x4377, 0xb2d8, 0x44d4, 0xba4e, 0x46e9, 0x188c, 0xbfd5, 0x429b, 0x4374, 0x4551, 0xb259, 0x418c, 0x4588, 0x43fe, 0xbfd0, 0x4175, 0x4227, 0x44b8, 0xbfe0, 0x2e9a, 0x406a, 0x2535, 0x4168, 0x0ad7, 0x1d42, 0x4264, 0xb086, 0xbada, 0xb00f, 0xb26f, 0x1b8d, 0x1c5c, 0x44c1, 0x3f77, 0x0f15, 0xb24c, 0xbf0e, 0x42d1, 0x44eb, 0x4253, 0x4233, 0x4203, 0xbfcd, 0x427a, 0x18a6, 0x4011, 0x29a1, 0x40cc, 0x448c, 0x1d4f, 0x0d2b, 0xb2dc, 0x4026, 0x12ff, 0x434c, 0x06a7, 0x415f, 0x41e4, 0x1842, 0xbf3a, 0xba5a, 0x4082, 0x43d2, 0x42a7, 0xa3c3, 0x41f5, 0x4341, 0x35dd, 0x425e, 0xb2c6, 0x4396, 0x420d, 0xb298, 0xbfd0, 0xa02a, 0x060e, 0x009f, 0x0988, 0x22b2, 0x4168, 0xbfbd, 0x437e, 0x38d0, 0x08f1, 0x41eb, 0x41c6, 0x21e9, 0x40d5, 0x1495, 0x436d, 0x41a6, 0x43f7, 0x21f7, 0xb04a, 0x4349, 0xb2d4, 0x0baa, 0xb034, 0x4087, 0x41f3, 0x40ff, 0xbf48, 0x41e7, 0xa4cb, 0x3b1b, 0xa68b, 0x18b1, 0xbaeb, 0xb045, 0xb231, 0x4194, 0x4425, 0x40f8, 0x1fda, 0xa62a, 0x4302, 0xb2ee, 0x40f9, 0x1bb8, 0x4365, 0x42ff, 0x434d, 0xba42, 0xb2ad, 0xbfb3, 0xba16, 0xb2bd, 0x41df, 0x42c9, 0x4367, 0x0235, 0x34d6, 0x4176, 0x40cb, 0x4295, 0x42b9, 0x0de1, 0x41c8, 0x41be, 0xb2ea, 0x1312, 0x4230, 0x1907, 0xb2f6, 0x4215, 0xba0a, 0xba32, 0x42a1, 0xbf07, 0x397f, 0xbfc0, 0x4206, 0x124e, 0x40cb, 0x440d, 0xb0a0, 0xb27c, 0x3960, 0x411d, 0x4217, 0x463a, 0x3a91, 0x3950, 0x438c, 0x1c16, 0xbfcb, 0xb207, 0x435b, 0x40ee, 0xbfb0, 0x2ebf, 0x3df7, 0xb21e, 0xba74, 0x1b05, 0x46fb, 0x428d, 0x43c1, 0x4196, 0x4379, 0x4239, 0x19e7, 0x411d, 0xbfd2, 0x3b6c, 0x42a7, 0x1ab9, 0xbafe, 0x40f8, 0x0e6a, 0x4422, 0xba1f, 0xbfa5, 0xba17, 0x43cb, 0x3d92, 0x43f7, 0x18a0, 0xbf21, 0x2196, 0x1fb2, 0x2491, 0x402b, 0xbf6a, 0x244e, 0x43b9, 0x03b7, 0x4228, 0x40e1, 0x42e3, 0x4353, 0x00d9, 0x41a9, 0xbac9, 0xbf74, 0x1b6a, 0x4381, 0x412b, 0x43d6, 0xbf26, 0x4194, 0x19f9, 0xb28a, 0x4000, 0xba61, 0x41a6, 0x1f6d, 0x19da, 0xb211, 0xbfa5, 0x1bf6, 0x4070, 0x1f03, 0xb275, 0x413d, 0x40f0, 0x45c8, 0x4036, 0x1844, 0x400a, 0x408b, 0x1cf3, 0xb2f6, 0x4302, 0xbf23, 0x16bb, 0x40c6, 0xba47, 0xba62, 0x42b8, 0xb24d, 0x4088, 0xba35, 0x43f5, 0xbade, 0x434f, 0x1ae1, 0x1df1, 0xb2da, 0xbfcc, 0x42ab, 0x1ece, 0x4252, 0x1e4c, 0x3aed, 0x4175, 0xb2c9, 0x422d, 0x4306, 0x3dd4, 0x4247, 0x413f, 0x3a6f, 0xb0c0, 0xb052, 0x41eb, 0xb222, 0xb05d, 0xbf16, 0x4332, 0x4142, 0xb29f, 0xb26d, 0x43b0, 0x2c2f, 0xa515, 0x43e4, 0x3d7a, 0xbf80, 0x2e76, 0x45e0, 0x1b4e, 0xbfd2, 0x45e2, 0x4033, 0x4305, 0xbf47, 0x1ad6, 0xbff0, 0x150c, 0x3e9a, 0x1ea8, 0x4120, 0x19ce, 0xbf79, 0x43ba, 0xbfe0, 0x3779, 0x42db, 0x39fe, 0xac2d, 0x1c17, 0x43b7, 0x4338, 0x29d6, 0x4069, 0xb0ba, 0xbf90, 0xa22d, 0x423c, 0x1df9, 0x40ba, 0xb24a, 0xbf87, 0x1999, 0xb277, 0x465f, 0x41da, 0xba31, 0xb2b8, 0xba07, 0x1950, 0x2ad8, 0x03a6, 0x4598, 0x1e2e, 0x4291, 0x439e, 0x43b4, 0xb0dd, 0x407d, 0x1859, 0xbf36, 0x196c, 0xb2f4, 0x19c2, 0x426f, 0xb2ed, 0xba27, 0xb209, 0x44e1, 0x2f28, 0x412a, 0x1f41, 0x26d7, 0xba26, 0x2bcb, 0x43ce, 0xbfa0, 0x1ee7, 0x307b, 0x1f27, 0x1438, 0x3bae, 0xb20a, 0x2af0, 0x1747, 0xbf03, 0xb272, 0x42c1, 0x445d, 0x1032, 0x43ac, 0x4048, 0x40c7, 0x43eb, 0x20f9, 0x4167, 0x4257, 0x419d, 0xb254, 0x020a, 0x0824, 0xbf60, 0x1bc9, 0xaa64, 0x4306, 0x41a8, 0x4368, 0x2461, 0x4412, 0x0a06, 0x426b, 0x380f, 0x429d, 0xb2b6, 0xb06a, 0xbf2c, 0x44a2, 0x4216, 0x42c3, 0xb24d, 0x455a, 0x417b, 0x4237, 0x1a76, 0xba20, 0xb08b, 0xba73, 0x178c, 0x144c, 0x1477, 0x3fff, 0xbac6, 0x40c6, 0x416b, 0xb221, 0x2221, 0x14a6, 0x3de1, 0xbf8f, 0x426c, 0xb28f, 0x4094, 0xb21a, 0x371b, 0x4654, 0x46ec, 0xba7f, 0x086c, 0x159f, 0xbaf8, 0x4094, 0xb2d1, 0x412e, 0x4087, 0xb246, 0x4190, 0x4318, 0xb2f2, 0x0736, 0xb269, 0xbf60, 0x4569, 0x466c, 0x4271, 0x4591, 0xbf91, 0x1b68, 0x4287, 0xba47, 0x4338, 0x42c7, 0xb27a, 0x4046, 0x4280, 0xbf77, 0x42e7, 0x456a, 0x117c, 0x4216, 0xbf51, 0x443e, 0x4362, 0x4036, 0x4336, 0x1e6b, 0xb25e, 0x0281, 0x4258, 0x456f, 0x4247, 0xb253, 0x358c, 0x4225, 0xb209, 0x4292, 0x2ac6, 0xb2c5, 0x2643, 0xad8c, 0x432d, 0xba18, 0x4155, 0x2239, 0x2027, 0xb27b, 0x4161, 0xb224, 0x0e38, 0xbf6f, 0x316c, 0xba5f, 0xad65, 0x4059, 0x1cd7, 0x4174, 0x325d, 0x1c2f, 0x41f3, 0xbaeb, 0x4065, 0xbf18, 0x4107, 0xb04b, 0xb2e6, 0x4060, 0x4058, 0x43e0, 0x13fe, 0x3d85, 0x4240, 0xbf58, 0x41ac, 0xbacd, 0xb218, 0x422f, 0xb228, 0x04cc, 0x1ed0, 0x43c1, 0x414a, 0x21dd, 0x1de5, 0x40d8, 0x4174, 0x4607, 0x430e, 0x4303, 0x40fd, 0xbf80, 0xbf96, 0x0173, 0xb21f, 0x44bb, 0xba35, 0x1e73, 0x40cf, 0xb0a8, 0x412d, 0x43ee, 0x42de, 0xb258, 0x411f, 0x4139, 0xb2aa, 0xae46, 0x0246, 0x421c, 0x18d7, 0x4258, 0xba6e, 0xb09e, 0x4024, 0xbaec, 0xba26, 0x41aa, 0x1e8d, 0x2a8c, 0xbf2e, 0x43e9, 0x46a9, 0x42ef, 0x43a1, 0x409c, 0x4176, 0x4133, 0xba70, 0x4649, 0xb0c6, 0x41a3, 0x4285, 0xb2e9, 0x41d2, 0x3f71, 0x403e, 0x4322, 0x4627, 0xbfae, 0xaa92, 0x43c6, 0x42d2, 0xac63, 0x0a31, 0xb258, 0xbfc6, 0x417a, 0xbaf1, 0xba1f, 0x43b8, 0x41fb, 0x4253, 0x1b4b, 0x4419, 0xbf4e, 0x4012, 0xb245, 0x3e4e, 0x44f8, 0x368f, 0x4324, 0x416d, 0x409b, 0x4254, 0xbf80, 0x27fa, 0xa534, 0xbf90, 0x1ab2, 0xb2ec, 0x4335, 0x1eb9, 0xba45, 0x4333, 0xbfb9, 0x1c4d, 0x2c8a, 0x2e8a, 0x423a, 0xb0e3, 0xb21c, 0x422d, 0xbf8e, 0x2e30, 0x42a7, 0xb0bf, 0x43b5, 0x1af2, 0x43c7, 0x1aef, 0x466c, 0xbf13, 0x3802, 0xba0b, 0x4043, 0xb089, 0xba08, 0x4153, 0x1a77, 0xb2fd, 0xa20a, 0x43b8, 0x402e, 0x4031, 0x18ce, 0x400a, 0xbf60, 0x32d5, 0x2169, 0xba6d, 0x41f3, 0x1722, 0x4396, 0xb266, 0x4165, 0xb2ad, 0x402a, 0x40ca, 0xb254, 0xbf15, 0x1b9e, 0x32eb, 0x4148, 0xba7a, 0x3889, 0x1cf3, 0x4117, 0xbad5, 0x40aa, 0x41e2, 0xbf67, 0x30d2, 0x1428, 0x42ab, 0x3d03, 0xa881, 0xbfb0, 0xb2bd, 0x1759, 0x4550, 0x0672, 0x1807, 0x300d, 0x40ad, 0x2e4a, 0xb237, 0x290f, 0xb228, 0x433e, 0x4273, 0x0bf7, 0xb0ac, 0xbaf3, 0xb00c, 0x0408, 0x4009, 0xbf3d, 0x411e, 0x433e, 0x4079, 0x41fb, 0x4375, 0x43b5, 0x4365, 0xb02c, 0x4654, 0x44e5, 0x4271, 0xb216, 0x41c5, 0x42c0, 0x4630, 0x3d54, 0xbfde, 0xba79, 0x1dba, 0xb0b7, 0x402a, 0x4279, 0x4120, 0x1e83, 0x4383, 0x18e9, 0x462c, 0x4178, 0x40a4, 0x4285, 0xb058, 0x1e53, 0xbf27, 0xb025, 0x29c0, 0x40d3, 0xb2e7, 0xba77, 0xbae1, 0x2061, 0x425a, 0x42e6, 0xbf71, 0x4018, 0x4337, 0x4287, 0xbafd, 0x42eb, 0x418e, 0x4177, 0xb01f, 0x4274, 0x1e4b, 0xad9b, 0xb2d2, 0xbad4, 0x40aa, 0x4302, 0x18be, 0x4214, 0x427c, 0x41ed, 0x18e8, 0xbfb5, 0x41ee, 0x41c8, 0x43f5, 0x401a, 0xaacd, 0x402a, 0x0dae, 0xb05e, 0x40af, 0x4019, 0x1c2f, 0x4273, 0x28ca, 0x3e2e, 0x4636, 0xba00, 0x41cf, 0xbfcd, 0x4393, 0x4225, 0x42c6, 0x08dd, 0x4085, 0x41df, 0x43e0, 0x1ef3, 0x2fc5, 0x1893, 0x407c, 0x4263, 0xa557, 0xb0ad, 0xbfbd, 0x2c87, 0xb254, 0xba35, 0x404b, 0x1f02, 0x4128, 0xba23, 0x21ae, 0x04df, 0x4097, 0xb06f, 0x4630, 0xb2d9, 0x4076, 0x4118, 0x34d4, 0x402c, 0x4014, 0x4660, 0x1b57, 0x1ed9, 0x1d79, 0x0656, 0x1a34, 0x3ed4, 0xbfde, 0x1cbc, 0x4318, 0xba0c, 0x4485, 0x3cb7, 0x1be9, 0x411e, 0x0c28, 0x4320, 0x4048, 0xba28, 0xba50, 0xb0ce, 0x43d5, 0x409f, 0x454c, 0xa404, 0x41ce, 0xbf25, 0x402a, 0x4621, 0x4193, 0x41d2, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xc53d8d03, 0xff04be91, 0xd7eb41e1, 0xf292368c, 0xc17640e7, 0x57b5fdc3, 0x3ca7f7e5, 0x438f9293, 0xbf87ea5c, 0x7ed4e28f, 0xf51a6864, 0x539c9c7c, 0x62d8cca2, 0xc0701359, 0x00000000, 0x700001f0 }, + FinalRegs = new uint[] { 0xfffffcff, 0x000017f4, 0x00000000, 0x00000800, 0x000017f4, 0x00000003, 0xf2cf7fff, 0xffffe718, 0xc0464ce0, 0x0d53d3ba, 0xf51a6864, 0x000012ba, 0xf51a6b68, 0xdf4f4290, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x2af4, 0x4229, 0x413f, 0xb264, 0x4327, 0x4106, 0x402a, 0xb0a1, 0x45b0, 0xb2fa, 0x1ebf, 0x441e, 0x43f6, 0xbf8f, 0x4628, 0x3760, 0x4360, 0x42af, 0x4179, 0xbf1f, 0x41d3, 0x1991, 0x4181, 0xb293, 0x4235, 0x1f56, 0x4650, 0xaf8a, 0xadfe, 0xb20e, 0x1abd, 0xba65, 0x4210, 0xb2ea, 0xa27b, 0xbafe, 0xbf00, 0x41ca, 0x1a78, 0x1668, 0xb2a2, 0xb061, 0x400f, 0x43a0, 0xbf58, 0x41af, 0x413f, 0x183a, 0x1e39, 0xbf15, 0x41e3, 0x4322, 0x41ed, 0xb0d3, 0x37ae, 0x120e, 0xb229, 0x417e, 0xb2dd, 0x4181, 0x08cd, 0xb2e2, 0x42b3, 0x43f4, 0x0bb2, 0x40ca, 0x412b, 0xb066, 0xbad4, 0x1c95, 0x4128, 0xa2c0, 0xbfa1, 0x4165, 0x40a1, 0xb2fa, 0x4054, 0x439d, 0xb29b, 0xb219, 0xb0b8, 0xbae4, 0x10ce, 0x43c6, 0xba78, 0x4163, 0xb2b7, 0x4151, 0x416f, 0x4323, 0xb205, 0x4443, 0x421b, 0x4378, 0x439d, 0x4306, 0x11f7, 0x41ce, 0xb270, 0x4040, 0x3828, 0xbf4a, 0xa7f5, 0x4036, 0xbfb0, 0x46fb, 0x4638, 0x41ce, 0x4018, 0xb2f7, 0x3777, 0x4297, 0x4372, 0x1b90, 0x215f, 0x4096, 0x436b, 0x0217, 0xb26a, 0xb200, 0xa480, 0x4406, 0x2fbe, 0x0701, 0x426c, 0xb2f6, 0x41b3, 0x431f, 0xb2aa, 0x406a, 0xbf3a, 0x427b, 0x064a, 0x1ad3, 0xb265, 0xac12, 0xb0a2, 0x4100, 0xbf7e, 0x1d85, 0x43a1, 0x4009, 0x1338, 0x4315, 0xba41, 0x3f3c, 0x4211, 0x40a4, 0x055d, 0x455a, 0xba61, 0x439e, 0xbf80, 0x43ab, 0x45ed, 0x3ab9, 0xb2e2, 0x3618, 0xbf76, 0x2654, 0x4555, 0xa5a8, 0x4349, 0xaa9c, 0x41c5, 0x4356, 0x3c41, 0x40c2, 0x085c, 0x40a7, 0xbfba, 0xbae7, 0x42b5, 0x42a0, 0x21d7, 0x420f, 0x1a68, 0x41f7, 0xbfa0, 0x43ab, 0xbf9a, 0x41b4, 0x397b, 0xb23b, 0x1b68, 0x4260, 0xb256, 0x432a, 0xb2b4, 0x44c8, 0xb2cc, 0x409c, 0xbff0, 0x416e, 0xbf13, 0x4312, 0xb0bb, 0x342f, 0x43c9, 0x4107, 0x1a19, 0x223f, 0xabb8, 0x43f4, 0xbf83, 0x2d30, 0x4155, 0x424b, 0xb2b6, 0x4296, 0xb21c, 0x3cca, 0x41fd, 0xba12, 0x424c, 0xbf2e, 0xba38, 0xbacd, 0xa7e7, 0x00be, 0x43f9, 0x1a9f, 0x2f15, 0x1eb0, 0x42c1, 0x4576, 0x418c, 0x4203, 0x1428, 0x1b1a, 0xbaff, 0x430a, 0x41d1, 0x42ee, 0x42d1, 0xb0dc, 0x437a, 0xbf6e, 0x4009, 0xb2f8, 0x43e9, 0x4188, 0x3d63, 0x1f9c, 0xa47d, 0xba6a, 0xbadd, 0x45d4, 0xbfb1, 0xb2bf, 0x1fdf, 0x41c9, 0xbf90, 0xba05, 0xb2e1, 0x4012, 0x4353, 0x438d, 0x462c, 0xb019, 0x41c6, 0x4092, 0xb2dd, 0xb21e, 0xba72, 0x2b0f, 0x401e, 0xba33, 0x0636, 0xb213, 0x3216, 0x41a6, 0xbf99, 0x3934, 0x4290, 0xbff0, 0xba7f, 0x0aa5, 0x1b9d, 0x1b5e, 0xba5d, 0xbfd8, 0x39cd, 0x41ea, 0x427e, 0x42ea, 0xb292, 0xb2c5, 0x422d, 0x413d, 0xba0d, 0xafa1, 0x324a, 0xbf26, 0xb243, 0x1cb1, 0x4017, 0x4430, 0x42ce, 0xbf51, 0xb23e, 0xba00, 0xba27, 0x18b7, 0x4084, 0xb0d7, 0xb229, 0x423c, 0x429e, 0xbad8, 0xba28, 0x430c, 0xb070, 0x4091, 0x4191, 0x4038, 0x436d, 0x4285, 0x41de, 0x4037, 0xbfbb, 0x45a5, 0x1c02, 0x2189, 0x0fae, 0xb25d, 0x1d7c, 0x43ef, 0xa694, 0x10c2, 0xbfc8, 0xba12, 0xad26, 0x1501, 0x41c6, 0x4212, 0x197b, 0x4350, 0x403b, 0xbfe0, 0xb29d, 0x4558, 0x4205, 0x439c, 0x444f, 0xb01c, 0x1e76, 0xb2d5, 0x44f9, 0x46d3, 0x4036, 0x41de, 0xba6d, 0x2a32, 0xbf38, 0x4160, 0xbf37, 0xb0ec, 0xba41, 0xb28f, 0x41f1, 0xbf86, 0x2b4a, 0xb209, 0x4338, 0x1eb8, 0xbf04, 0x4336, 0xb210, 0xb0d2, 0x41de, 0xaa40, 0xb00f, 0xb025, 0x424e, 0x4454, 0x43c9, 0x42c6, 0x4158, 0x42e4, 0xbf22, 0x40d0, 0x40b8, 0xb2cf, 0x1b2e, 0xba36, 0xb2df, 0xbf4e, 0xbad7, 0x4172, 0x41ab, 0x46e0, 0x048d, 0x40d0, 0x42d6, 0x4302, 0xbacf, 0x42b1, 0x30ee, 0xb2ce, 0x43e0, 0x3e75, 0x402b, 0x0cb9, 0xba34, 0x0d88, 0xbfce, 0x0bf1, 0x1510, 0x4316, 0x463d, 0x458e, 0x1925, 0xaec3, 0xbf4c, 0x408b, 0xb08c, 0xb268, 0x4283, 0x410e, 0xbfc8, 0x261d, 0x1efb, 0x4309, 0x43ca, 0x4080, 0x423b, 0x4099, 0x4566, 0x4000, 0x10e8, 0xb20e, 0xba40, 0xbad2, 0x21b3, 0x4310, 0x4023, 0x1c67, 0x418e, 0xa05b, 0x4275, 0xb00a, 0xbf7c, 0x1ae8, 0xba49, 0x1039, 0x438d, 0x435f, 0x40ad, 0xb287, 0x282a, 0xaa2d, 0x427f, 0x42c7, 0x4354, 0x435d, 0x41b9, 0xb06f, 0x46e9, 0xba23, 0x40e8, 0x408d, 0x4013, 0xbf0a, 0x435c, 0x2384, 0x4542, 0xa98c, 0xb20b, 0xb27c, 0xbafa, 0x4303, 0x4097, 0x41f9, 0x42ea, 0x407e, 0x3c9a, 0x4097, 0x4359, 0x1e96, 0x0020, 0x3c97, 0xba3c, 0xb2ff, 0x42ca, 0x4471, 0x4076, 0xb250, 0xbf7e, 0xbfd0, 0xb23f, 0x4365, 0x4638, 0x1d42, 0x21a7, 0x1325, 0x4367, 0xb038, 0x4379, 0x4304, 0x4574, 0x410c, 0xbf8e, 0x23da, 0x40ad, 0xb202, 0x42a5, 0x4321, 0x44f4, 0x1a22, 0xba30, 0x40f6, 0x40a1, 0xa786, 0xb0b2, 0x414a, 0x43cd, 0x42ef, 0x034a, 0xbf3d, 0x4029, 0x463e, 0x0ac8, 0x43f0, 0x1ac0, 0x401e, 0xbaea, 0x0d5e, 0xbfd3, 0xb2a2, 0x42b9, 0xb2ff, 0x4151, 0x42e3, 0x4083, 0x1864, 0x42f9, 0x19ba, 0xb058, 0x1a60, 0xa486, 0x2355, 0x2060, 0xbf0b, 0x42bd, 0x418e, 0x1ceb, 0x4290, 0x1f52, 0x1c67, 0xa739, 0x437c, 0x189e, 0x43e6, 0xbf0f, 0x415d, 0xb208, 0xb267, 0xa399, 0x411f, 0x1954, 0x346b, 0x19f7, 0x43e8, 0x433b, 0x3630, 0xba34, 0xbad2, 0xbf2c, 0xb291, 0x42b5, 0x4102, 0x1bbe, 0x4033, 0x4632, 0x1ffe, 0x436a, 0x4364, 0xbaf1, 0xbf94, 0xb289, 0xb0fc, 0x432a, 0x1ced, 0xba74, 0x2483, 0xbf5b, 0x426c, 0xb207, 0x41d8, 0x4157, 0x42e7, 0x42df, 0xb26e, 0x13cd, 0x4120, 0x1d31, 0xa70b, 0xbff0, 0xb03f, 0xb272, 0x4388, 0x370d, 0x1d74, 0x1ce9, 0x40ee, 0x403b, 0x1c1b, 0xbac0, 0x1007, 0x4008, 0x1996, 0xbf32, 0x1b8b, 0x23d1, 0x4556, 0x1cac, 0xa867, 0x3b4b, 0xb22c, 0x4419, 0xba1d, 0x4384, 0x4038, 0xba49, 0x4173, 0x3e55, 0xba7a, 0x4041, 0x421a, 0x4475, 0x409a, 0xbfd0, 0xb023, 0xb2be, 0xb2b7, 0x46ad, 0x4027, 0xa0cf, 0xbfe1, 0x3b58, 0x4156, 0xb213, 0x4388, 0x4443, 0xb21a, 0x42a6, 0xb2bd, 0xbace, 0x415c, 0x19b0, 0xbf97, 0x0d21, 0x1f02, 0x4133, 0x0a3b, 0xb027, 0x39b7, 0x429d, 0xb2b5, 0x4243, 0x00bf, 0x42a1, 0x45eb, 0xbf99, 0x4124, 0x2790, 0x4250, 0xbae9, 0x1d8c, 0x041e, 0x4222, 0x4243, 0x421a, 0x41e6, 0x1efc, 0x4611, 0xbfd6, 0x1e2d, 0x41a8, 0x4220, 0x42ee, 0x40ae, 0xba77, 0x4177, 0xae2a, 0x4190, 0x43f0, 0x1dc7, 0xb036, 0x464f, 0x4654, 0x4100, 0xbad3, 0x420e, 0x2f13, 0x42c7, 0xb00b, 0x4387, 0x12cc, 0x406e, 0x419b, 0x420e, 0x1e6c, 0x2d42, 0xb24d, 0xbf11, 0x421e, 0x43a3, 0x18d2, 0x3178, 0x4093, 0x42cd, 0x1c0a, 0x4173, 0x1821, 0x1c38, 0x43d8, 0x44e5, 0x4239, 0x13f2, 0xbf11, 0x42df, 0xba20, 0x4020, 0x4190, 0x40d4, 0x438a, 0x403e, 0xb22b, 0x400a, 0x2101, 0x223e, 0x0d56, 0xbf9b, 0x4328, 0xb276, 0xb09a, 0xb229, 0x1c8c, 0x09ad, 0x1cc6, 0x0e1f, 0x43f6, 0x411c, 0x41b2, 0x265b, 0xba5c, 0x0f83, 0x41be, 0x2897, 0xba67, 0x40eb, 0x413c, 0x1c6c, 0x4010, 0x1829, 0x43ce, 0x4292, 0xbf06, 0x4156, 0x43d6, 0x431b, 0xb2e6, 0x4167, 0x0318, 0xbfe0, 0x4224, 0x13e7, 0x4165, 0x4402, 0x41be, 0xa3ad, 0xb22e, 0xba0d, 0x1fae, 0xaf2b, 0x13bb, 0xb299, 0x422d, 0x4113, 0x3fef, 0x41ea, 0x1ebd, 0x12b6, 0x1b80, 0xbf4e, 0xb2f6, 0x33be, 0x4075, 0xbf35, 0xb24b, 0x42fc, 0x4039, 0x18d0, 0x2d6f, 0x1e21, 0x1cd0, 0xba62, 0xab70, 0xa93f, 0xba33, 0xbf92, 0xb23f, 0x41fc, 0x176e, 0x436f, 0x4246, 0x4119, 0x445f, 0xba22, 0xbf60, 0x4075, 0x45da, 0xbf70, 0x4011, 0xb29b, 0x1d67, 0x413f, 0x0a8d, 0x464c, 0xac75, 0x432d, 0xa2cf, 0x0872, 0x18ae, 0x4315, 0xbf66, 0xb249, 0xba19, 0x4074, 0xa674, 0x4009, 0xbf90, 0x4373, 0x4072, 0x1f85, 0x1968, 0x3a9a, 0x1e06, 0x44cd, 0x4152, 0x433c, 0xba71, 0x1835, 0x40bf, 0xbf80, 0x4150, 0xbf05, 0xb241, 0xba5b, 0x41a7, 0x4445, 0x4232, 0xb2c9, 0x418b, 0xb20c, 0x436c, 0x46b9, 0xba16, 0x411b, 0x40d0, 0x1af2, 0xb06c, 0xbf3f, 0x429d, 0x4296, 0xac36, 0x40b1, 0x19f1, 0x1a06, 0x131c, 0x403f, 0x19e2, 0xa234, 0xbf62, 0xba62, 0x2ad5, 0x1b63, 0x4048, 0xb00f, 0xb265, 0xb03b, 0xb2da, 0x17c2, 0xb0c6, 0x1c3e, 0x41c4, 0x42af, 0xbfae, 0x2515, 0xb2e3, 0x42f6, 0x4065, 0x21bc, 0x2a1c, 0x2d3e, 0x43ba, 0x40e0, 0x435e, 0xb24d, 0x0e36, 0x406f, 0x404b, 0x1c48, 0xbf97, 0xbf70, 0x4153, 0xb20f, 0xb25e, 0x43c9, 0x4226, 0x40dd, 0x405c, 0x43cb, 0x0d09, 0x19fa, 0x1d04, 0x430c, 0x4029, 0xb2fa, 0xbf1f, 0x0aa8, 0xb223, 0xb2ca, 0x413f, 0x1b91, 0x36e0, 0x19dc, 0x005f, 0x1420, 0x40fc, 0x439f, 0xbfb3, 0x0279, 0x1824, 0x1879, 0xa189, 0xba14, 0x33bb, 0x4266, 0x0602, 0x41c4, 0xba73, 0x40eb, 0x419b, 0xbfc3, 0x4179, 0x183d, 0x4339, 0x41bb, 0xa7fd, 0xb257, 0x42bf, 0x4551, 0x425d, 0xba0a, 0x1e0f, 0x200a, 0x4204, 0x42ca, 0xa1de, 0x0124, 0x4282, 0x1d1d, 0x411c, 0x18bd, 0x406d, 0xba1f, 0xbad9, 0x0714, 0x421f, 0xbf5f, 0x409f, 0x4317, 0x419c, 0xba34, 0x40e1, 0x1b14, 0xbf21, 0x38e6, 0x1016, 0x4387, 0x42c9, 0x10ca, 0x101a, 0x43e1, 0xb2a6, 0x1c7d, 0x4318, 0xbf49, 0x4573, 0x1e86, 0xb256, 0x40df, 0x43fb, 0xbfd3, 0xa2c5, 0x4175, 0xb22e, 0x18cb, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xff38a7fb, 0x4577c13c, 0xbafd4bd9, 0x608edce2, 0x0b12ce77, 0xdd0d292c, 0xd713d1ca, 0x0199fdd1, 0xee4843e5, 0x33f7a879, 0xf40bded8, 0x113e3e39, 0xfa00e8ce, 0xeb295429, 0x00000000, 0xe00001f0 }, + FinalRegs = new uint[] { 0xffffffbe, 0x3ffeffff, 0x00001ae0, 0x3ffefffe, 0xc0010000, 0x000000dc, 0xffffffff, 0x00000000, 0xfa00e8ce, 0x5b129380, 0xf40bded8, 0xf40bded8, 0xfa00e8ce, 0x9b2a428a, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xbae5, 0xb227, 0x44d8, 0x43d3, 0x41d7, 0x40a2, 0x41d7, 0x42f3, 0x42eb, 0xb065, 0x42f8, 0xbfd8, 0x1e0b, 0x4085, 0x4121, 0x4130, 0xbf3d, 0x4042, 0x43f3, 0xa770, 0x4317, 0xb214, 0x4005, 0x2688, 0xb26b, 0x4044, 0x41b2, 0x2c6e, 0x1b18, 0x1d98, 0x1f7c, 0xb0fc, 0x427e, 0xbfa4, 0x0b8c, 0xb2a3, 0x1991, 0x3cc3, 0xa055, 0x1bcb, 0x42a4, 0xaa13, 0xbf8c, 0x400e, 0x1fcd, 0x420e, 0x437f, 0xb009, 0x44e0, 0x40d3, 0x45ad, 0x4242, 0x435b, 0x24d1, 0xa139, 0x3049, 0xba6c, 0x1689, 0x2538, 0x009e, 0x12e4, 0x4226, 0x1db2, 0xa704, 0x41ff, 0xb252, 0xbf25, 0xb201, 0x45a3, 0xb2f1, 0xb06a, 0x10c5, 0x24cf, 0x42ed, 0x4420, 0x40d0, 0xb29b, 0xbf0a, 0x2ff2, 0x46d3, 0xba69, 0x44da, 0x0515, 0xba24, 0xb263, 0x1e73, 0x43ce, 0x4269, 0xbfb0, 0x4322, 0x41e8, 0xb229, 0x1d20, 0x46aa, 0xb2c3, 0x4613, 0x41d9, 0x0ac4, 0x4352, 0x4031, 0x40dd, 0xa769, 0xb008, 0xb09e, 0x29dd, 0xbf87, 0x437b, 0x0284, 0x4337, 0x4224, 0x417f, 0x3e53, 0x44a4, 0x400d, 0x1cf0, 0x4388, 0xbaf0, 0x4181, 0x4399, 0x2de9, 0x1200, 0xbf97, 0x4200, 0x41ea, 0xb0e3, 0x40f6, 0x4436, 0x4077, 0xa2c4, 0xbf6c, 0x1095, 0x1f06, 0x05cd, 0xb2e0, 0x40a7, 0xb0e6, 0x4090, 0xa9ac, 0x1fff, 0x4303, 0xb08d, 0xbad3, 0x413b, 0xbf2a, 0xbae2, 0xbfd0, 0x41d7, 0x437e, 0x42f2, 0xa025, 0x42d1, 0x3ebf, 0x1acc, 0xb28a, 0xa56f, 0x407b, 0x1ffc, 0x4124, 0x400b, 0x41fb, 0x1d4e, 0x40a0, 0xba67, 0x4383, 0x4239, 0x418c, 0x43e7, 0xba5c, 0xb03e, 0x4260, 0x404d, 0xbf8d, 0x1f0c, 0x1e74, 0x4132, 0x41ce, 0x4263, 0xb204, 0x1be7, 0x426d, 0x40ca, 0x408d, 0x4298, 0x183a, 0x1b8f, 0x4063, 0xb207, 0xbf7d, 0x4259, 0x2680, 0x44e4, 0x414f, 0x4390, 0x4218, 0x4111, 0x4477, 0xa2f6, 0xaac4, 0x40f7, 0xb24e, 0x026d, 0x1dae, 0x4296, 0x4151, 0xb0b1, 0x439b, 0x4264, 0x0f33, 0x1cee, 0xba12, 0x4026, 0x32d6, 0xbf26, 0xba1d, 0x4310, 0x1da4, 0xbfa5, 0xa234, 0x4551, 0xba63, 0xad72, 0x38dd, 0x421f, 0x418b, 0x4198, 0xba0c, 0xb063, 0xba67, 0xbf00, 0xa1f0, 0x419d, 0x459e, 0x2726, 0x40c7, 0xbad0, 0x4105, 0x1835, 0x4245, 0x4088, 0x411d, 0x0545, 0x42fa, 0x4549, 0xbf68, 0x0932, 0x3b74, 0x27a2, 0xbad1, 0x403e, 0x1edf, 0x125f, 0x4020, 0xba01, 0xb243, 0x40cc, 0x40c3, 0x431a, 0xba6a, 0x1e8f, 0xb297, 0x3805, 0x4385, 0x3068, 0x1ffa, 0x4568, 0x4240, 0x41c8, 0x449d, 0xbfda, 0x423a, 0x1853, 0xb217, 0xa4c3, 0x4260, 0x41e0, 0x405f, 0x4258, 0x4348, 0xb0ad, 0x407e, 0x42e6, 0x1c88, 0xbafc, 0x4112, 0x42f5, 0xba7c, 0xb200, 0xb243, 0x4050, 0xbf8c, 0x409c, 0x16eb, 0x4324, 0xb26a, 0xb0e0, 0xbf11, 0xba41, 0x4276, 0x3fbd, 0xb227, 0x46f9, 0x4096, 0x4115, 0x428f, 0x4341, 0xba68, 0x430d, 0x439c, 0x0258, 0xb0ac, 0x3247, 0xb2fd, 0x18dc, 0x4196, 0x1bc4, 0xbf69, 0x1fc3, 0x17db, 0x4028, 0x426b, 0xb045, 0x3fc7, 0x403b, 0x42b2, 0x4155, 0x4027, 0x4055, 0x4397, 0x40ca, 0xb2e4, 0xbfe0, 0x4067, 0x4213, 0xb019, 0x42f1, 0x18c4, 0xb26e, 0x2a83, 0x40d4, 0x43a9, 0x3df6, 0xb2d0, 0xbfd0, 0x1aa2, 0xbf38, 0x4283, 0x41a3, 0x4261, 0x07ee, 0x406e, 0x4323, 0x24cd, 0x413d, 0xb2d9, 0x4217, 0x41a7, 0x0450, 0x40e0, 0xb0f8, 0xb2cd, 0xbf6f, 0xa598, 0x36ad, 0xbafa, 0x4247, 0x428c, 0x419b, 0x2ddd, 0x2ff4, 0x42a3, 0x420b, 0x0665, 0xb09f, 0x25aa, 0xb222, 0x424b, 0x41d5, 0x084b, 0xbfdc, 0x448c, 0x1ee0, 0x427a, 0x42ec, 0xb24b, 0xb203, 0x4277, 0x40d6, 0x43b7, 0xbfce, 0x40fd, 0xb05a, 0x443e, 0x4319, 0xaf57, 0x4372, 0xbfb3, 0x0ea7, 0x1c4d, 0xa3d4, 0x4445, 0x424c, 0x0597, 0xb2e2, 0x44bb, 0xba22, 0x448a, 0x40b2, 0x0495, 0x1d64, 0x4227, 0xbaf8, 0xb2eb, 0x40e2, 0x350e, 0x38b4, 0x19de, 0x4351, 0x43d9, 0x4078, 0x0edb, 0x42b0, 0xbf08, 0x07cf, 0x41f1, 0xbff0, 0xbad4, 0x45a0, 0xbae6, 0xb001, 0xbad4, 0x430d, 0x425b, 0xbf6d, 0x0b2e, 0xa8b3, 0xb282, 0xbaff, 0xaf30, 0x0047, 0x18af, 0x2845, 0xbf0a, 0xba43, 0xb2e9, 0x445d, 0x42b1, 0x415c, 0x428b, 0x4305, 0x4298, 0x432a, 0x43ed, 0x142c, 0xab21, 0x4167, 0x211c, 0x4346, 0xbf7f, 0xb0a2, 0xb2a2, 0x23cf, 0xb22b, 0x41d2, 0x4187, 0x3931, 0x1ad7, 0x402b, 0x41d2, 0x4128, 0x0ad0, 0x4170, 0x1dac, 0xba34, 0x402d, 0x40e6, 0x41d2, 0x0a0b, 0x45f2, 0xa738, 0xa730, 0x4076, 0xbf74, 0x44f0, 0x2744, 0x1bf9, 0xbafa, 0xb258, 0xb215, 0xbf9f, 0x46b2, 0xb083, 0xb2d0, 0x18ba, 0xba65, 0x43ee, 0xbfdc, 0x0348, 0xb2a8, 0xbf72, 0x4345, 0x1fa2, 0xb08b, 0x433a, 0xbf14, 0xba2d, 0xbacf, 0x400b, 0xbac3, 0x4203, 0x443c, 0x40e6, 0x1f06, 0x43ab, 0xb2c5, 0xba4f, 0x417d, 0x43b4, 0x4044, 0x1f8a, 0x4299, 0xaa10, 0x183b, 0xbfd4, 0x454a, 0x3c00, 0xb2c6, 0x0d40, 0x411f, 0x4292, 0x11dc, 0xb2e2, 0x422f, 0xb2cc, 0x41f2, 0x438b, 0xbac1, 0x423e, 0x4329, 0x4390, 0xb019, 0x40e1, 0x458c, 0x4305, 0x09ce, 0x4666, 0x4459, 0x46dd, 0x0581, 0x4368, 0x34e4, 0xbf5c, 0xb22a, 0x4199, 0x4330, 0x42e2, 0x46ec, 0x3b8e, 0x2edc, 0x4173, 0x4356, 0x41e0, 0x43ab, 0x0b77, 0x4222, 0x1f4c, 0xba39, 0x407d, 0xb298, 0xb26d, 0x4088, 0xb0b2, 0x4055, 0xbf59, 0x4689, 0xb004, 0x4380, 0x406b, 0x19ab, 0x2018, 0x447c, 0xbf1f, 0x41e0, 0xb24f, 0x406b, 0x2d16, 0x05be, 0x4252, 0x008b, 0x1d45, 0x424b, 0x41fe, 0xb282, 0x4089, 0xbf25, 0xb252, 0x430c, 0xba72, 0x42e9, 0x3df2, 0x4351, 0x40af, 0xbf79, 0x4375, 0x1be2, 0x43b9, 0x28e9, 0xbf08, 0xb203, 0x4201, 0x40b3, 0x0265, 0xb060, 0x4236, 0x41dc, 0xbfe0, 0x1d87, 0x41bf, 0x4040, 0xb27e, 0x0ad0, 0x3812, 0x4141, 0x1d57, 0x43fc, 0x1e44, 0xba47, 0xa9bb, 0xbfbc, 0x414d, 0x4386, 0x1cd0, 0x4445, 0x4014, 0xba62, 0x19ed, 0x41d1, 0xbfbf, 0x41bc, 0xb0d9, 0x42b1, 0xb281, 0x3f21, 0xbf65, 0x409f, 0x4051, 0x004b, 0xba2b, 0x463d, 0xaa52, 0xa84c, 0xb0ec, 0x06ee, 0xa560, 0x463c, 0xb007, 0x2991, 0xb22c, 0xbf80, 0x3632, 0xb296, 0x3c62, 0x43cc, 0x120c, 0x42e2, 0x4030, 0xbfd0, 0x43a0, 0x0c17, 0x3416, 0x4624, 0xaae3, 0xbfd2, 0x454e, 0x430d, 0xba74, 0x41b8, 0x37e3, 0x3b51, 0x411f, 0x4200, 0x437a, 0x1e3e, 0x4098, 0xbf4b, 0x19a0, 0x4544, 0xa8a3, 0x41ec, 0xb0de, 0xbf80, 0x3899, 0x0994, 0x1c6c, 0x42d6, 0xba74, 0x43c6, 0xba20, 0x429d, 0x4010, 0x118b, 0x402c, 0xbf62, 0x42b4, 0x4638, 0x4269, 0x2880, 0xb015, 0x36fd, 0x4179, 0x1d14, 0x46da, 0x1a37, 0x21a6, 0xb0b8, 0xb2a7, 0x1f43, 0xba10, 0x1ea2, 0xba07, 0xb09e, 0x28a5, 0x426e, 0x3bc9, 0x41b8, 0x465e, 0x1cfe, 0xbf72, 0x41e6, 0x1bc3, 0xb04a, 0xbae4, 0x43df, 0x4281, 0x2e57, 0x408f, 0xb08e, 0x4069, 0xbfb0, 0x440a, 0xbfb0, 0xb230, 0xb0f2, 0x45b4, 0x402b, 0xb2e8, 0x4207, 0xbac1, 0x4186, 0xba45, 0x43d8, 0x43b2, 0x42cf, 0x41ad, 0x432d, 0xbf66, 0x401f, 0x43f5, 0x44f8, 0x42af, 0xa978, 0x439a, 0xb2fd, 0xb259, 0xb24a, 0x42b1, 0xba6d, 0x398d, 0x4190, 0x43cf, 0xb2a3, 0xba25, 0x26d9, 0x43db, 0xba1f, 0x1d49, 0x197a, 0xb0bc, 0x40ae, 0xa9b4, 0x3109, 0xbf03, 0x4125, 0x31de, 0x459b, 0xb2e0, 0xba14, 0x41bb, 0x437a, 0x425a, 0x1a58, 0x288b, 0x1d8f, 0xb084, 0x4235, 0x4349, 0x1631, 0x4043, 0x2199, 0x075f, 0xb071, 0x0a46, 0xba55, 0x40a5, 0x26e7, 0xbf00, 0x3d20, 0xa8f9, 0x4025, 0x44c4, 0xbf2a, 0x40eb, 0xba2a, 0xba0b, 0x4115, 0x42c2, 0x1d34, 0xba10, 0xba7c, 0x4079, 0x1b5d, 0xba50, 0x4369, 0x3eba, 0x400b, 0xb0b8, 0xbae1, 0x41f2, 0x43ef, 0x42a4, 0x4559, 0x242c, 0xacb2, 0xad93, 0xbf72, 0x4297, 0x42fc, 0x4269, 0x43b5, 0x1e97, 0x40dc, 0x35e6, 0xbacf, 0x1a73, 0x411a, 0xbae3, 0xbf33, 0xb2b2, 0x34a3, 0x425a, 0x405e, 0x3399, 0x21cc, 0x195d, 0x435e, 0x4099, 0xbfa0, 0xae2f, 0x43d9, 0x0109, 0x4025, 0x4155, 0x4462, 0x1602, 0x43bd, 0x4248, 0x4194, 0x434a, 0xabb9, 0xb2c8, 0xbf4d, 0x413d, 0xba58, 0x03e3, 0x40f5, 0x403c, 0x1598, 0x4265, 0x1f54, 0x4188, 0xb0aa, 0x18e2, 0x4223, 0xbfb0, 0x410b, 0x40f5, 0x1ab4, 0x23b7, 0x19ba, 0xbf7f, 0x18fe, 0x199f, 0x4011, 0x410d, 0x27b0, 0x4339, 0x1d01, 0x2979, 0x31af, 0xa4a3, 0x1dfb, 0x401b, 0x40b7, 0x3d6e, 0xb22d, 0xbf64, 0xb040, 0x3846, 0xbfbf, 0xb24b, 0x41fd, 0x2226, 0x43cf, 0x3af2, 0x4365, 0xb003, 0x4333, 0xaa10, 0x1ca0, 0xb0f2, 0xbf9f, 0x1fed, 0xb09e, 0x41b3, 0x468b, 0x40f3, 0x4398, 0x413c, 0x40b8, 0xb017, 0xb226, 0x43f4, 0x4340, 0x41d7, 0x01f2, 0x4374, 0x410a, 0x28e5, 0xb07f, 0x0cf8, 0x40c0, 0x422f, 0xbfdd, 0x4023, 0x085c, 0xb22c, 0x41c8, 0x4433, 0x45c9, 0x43d7, 0x4378, 0xbf06, 0x4570, 0x40e0, 0xb004, 0xb292, 0x449d, 0x1d73, 0xb24e, 0x4347, 0x4071, 0xb2d4, 0x1aed, 0x43ec, 0x1f32, 0x43e5, 0xa386, 0x3bc4, 0x4418, 0x40d4, 0x4031, 0xbfd5, 0x41f7, 0x401c, 0x41cd, 0x2c18, 0xb04b, 0x43da, 0xbafd, 0x41de, 0x4169, 0x406e, 0x43ee, 0x204a, 0xbf41, 0xbaff, 0x42ab, 0xbac3, 0x4691, 0xb269, 0x188e, 0x4277, 0xbf81, 0x43c8, 0xb223, 0x1dfd, 0x2342, 0x12d6, 0xb254, 0x19ce, 0x359d, 0x4343, 0x1cc8, 0x00c8, 0x26e1, 0xa58f, 0xb2c4, 0x4047, 0x3780, 0x4157, 0x3829, 0xbf48, 0x447c, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xf96275eb, 0x02fe41e7, 0xeefa2e22, 0x4b46bc43, 0x003dddf6, 0xa6272337, 0xb656ca8c, 0x7ccb9d65, 0xf8d3063c, 0x09bff66f, 0x1d231647, 0xe7c3d8f9, 0x0fbd2fe9, 0x8a2c763d, 0x00000000, 0x900001f0 }, + FinalRegs = new uint[] { 0xffffffd7, 0x00000000, 0xffffe71f, 0x000730c0, 0x000017ea, 0x00001a18, 0x000000e1, 0x00000080, 0xf054250c, 0x00000000, 0x1d231647, 0x00000a54, 0x0d773b53, 0x1d23118b, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x409f, 0x4371, 0x4362, 0xb217, 0xbf00, 0x43fa, 0xbf8c, 0x18bb, 0x42a0, 0xba1a, 0xada9, 0x403c, 0x00d4, 0xbf02, 0xb01d, 0x43d0, 0xb264, 0xb2aa, 0xb0dd, 0x425d, 0x1dc1, 0x0378, 0xb072, 0x1d92, 0x2fa8, 0x4140, 0x40ac, 0x4620, 0x0a08, 0x42a6, 0xba07, 0xbafe, 0x4171, 0x40c2, 0x4376, 0x41db, 0x40e1, 0x4305, 0xbf97, 0x14b7, 0xba36, 0x4393, 0x1f2f, 0x4483, 0xbf46, 0x2407, 0xba63, 0x4096, 0x4103, 0x4650, 0x2684, 0x41aa, 0x4377, 0x43fc, 0x11ca, 0x4595, 0x3398, 0x43ef, 0x4634, 0x436d, 0xb060, 0x0ea8, 0xb2b5, 0x2045, 0x43ef, 0x46a4, 0x40b6, 0x40bd, 0x127f, 0x290e, 0x4087, 0x422c, 0x1856, 0xbf8c, 0x4065, 0x3e07, 0xb004, 0x44ec, 0xbf4c, 0x193a, 0xbaf4, 0xafa0, 0xb2c6, 0x406a, 0x1865, 0xbfc0, 0x19b2, 0x43d6, 0x1b78, 0xb039, 0x407a, 0x4163, 0x411a, 0x1d8c, 0x04e2, 0x1988, 0xb215, 0x40a0, 0x13bd, 0x38a5, 0xb2ce, 0x403b, 0x4291, 0x0562, 0x4680, 0x402c, 0xbf2e, 0x409c, 0xbaf0, 0x433f, 0xa329, 0x4031, 0x3824, 0x403f, 0xb2c0, 0x428b, 0x0991, 0x4153, 0x3e46, 0x4097, 0x2e7d, 0x05f3, 0xb2ee, 0xbade, 0x2267, 0xba3a, 0x198c, 0xbf6d, 0x461a, 0x337e, 0x43f0, 0xb225, 0x42a7, 0xb2ab, 0x45f3, 0x1f4b, 0x421a, 0xbac9, 0xbf47, 0x42c3, 0x44b4, 0x1c39, 0x0574, 0x4051, 0x4010, 0xb20e, 0xb2b3, 0x4575, 0x2bae, 0x1821, 0x366d, 0xb273, 0xb237, 0xbf4e, 0x425c, 0x4340, 0x191e, 0xbaf4, 0xbadf, 0x1a8b, 0x118f, 0xbfb0, 0x1ee0, 0x0041, 0xbfd9, 0xa940, 0xba2a, 0x4389, 0xba3a, 0x410e, 0xb03c, 0xba6a, 0x0b96, 0xb2d5, 0x1cb5, 0x026c, 0x4176, 0x1c59, 0x43d8, 0xb291, 0x3bf6, 0x1cff, 0x0650, 0xb2f6, 0x4348, 0x460d, 0x411d, 0x413c, 0x42fe, 0x0242, 0x415c, 0x088e, 0xbfb4, 0x136d, 0x2a54, 0x4183, 0xb2ae, 0x4457, 0xb0ff, 0x2d0f, 0x1f2b, 0x437a, 0x2ba1, 0xbfb4, 0x439e, 0x4196, 0x43e3, 0xb23e, 0x1be7, 0x4181, 0x4250, 0xbfd0, 0x413d, 0xbf80, 0x4289, 0xb058, 0xbac1, 0x432b, 0x40b4, 0xbf8a, 0x400d, 0x236d, 0x42a4, 0xa46c, 0xb053, 0x2598, 0x015b, 0x0966, 0x4335, 0xb29d, 0xbf51, 0x4185, 0xb2f6, 0xac2b, 0xb2fa, 0x3432, 0x4628, 0x2d72, 0xb053, 0xa1e1, 0x059e, 0xb225, 0x42e2, 0x4383, 0x400f, 0x42bc, 0x190e, 0x4094, 0x43f7, 0xb245, 0x403e, 0x41b3, 0x419d, 0xbf65, 0x4045, 0xb013, 0x417b, 0x0ea0, 0xaf84, 0x42d0, 0xb228, 0x40f7, 0xba01, 0xbfc0, 0xb2d9, 0x1dc9, 0xbfc6, 0x1aca, 0x4375, 0x1b51, 0x1f9c, 0x1b0a, 0x0661, 0x447e, 0x436a, 0x4051, 0xad93, 0x41b5, 0x4213, 0x006d, 0x40da, 0x4619, 0x42d5, 0xbaf4, 0xaabe, 0xbf70, 0x38e2, 0xbf8b, 0x40ce, 0xb21f, 0xbfd0, 0xb24e, 0xab0e, 0xbae3, 0xb25d, 0xb2c7, 0x443c, 0x42f8, 0x4173, 0x420a, 0xb2d7, 0x42dc, 0xb283, 0xa7ba, 0xbfb0, 0x4074, 0xb2d4, 0xbfb2, 0x40b8, 0x40bb, 0x1964, 0x40ff, 0x43e7, 0x4290, 0xb26c, 0xb226, 0x240c, 0xb090, 0x4321, 0xb26b, 0x4234, 0xba39, 0xba20, 0x40e5, 0x4153, 0x0199, 0xb262, 0x0485, 0x40f7, 0x2228, 0xbfe0, 0x4241, 0x4287, 0x4352, 0xb233, 0x1f2a, 0xbf07, 0xb0fb, 0x19b1, 0x3a72, 0x41c3, 0x1d5c, 0x0cfd, 0xbf90, 0xb293, 0xbaca, 0xbf6d, 0xba19, 0xb06f, 0x2275, 0xb246, 0xae73, 0x405f, 0xb247, 0x40c7, 0x1cb3, 0xb2ef, 0x4125, 0x413d, 0x1e18, 0x28fe, 0xb0a7, 0xb0af, 0xbfb5, 0xba0e, 0x40ee, 0x41f1, 0x424b, 0x4308, 0x17c4, 0xb00a, 0x42ff, 0xbf49, 0x21f1, 0x409f, 0x43b7, 0x1990, 0xbfa6, 0x4602, 0x1a37, 0xb28e, 0x4223, 0x401b, 0x0ee5, 0x4382, 0x4017, 0xba24, 0x402e, 0x4220, 0x41a7, 0x411b, 0xb2e5, 0x42f2, 0x41f2, 0xb247, 0xbf90, 0x430e, 0x2206, 0x3b7b, 0x4253, 0x4376, 0x4017, 0xba4d, 0x4257, 0xbf28, 0xb2a0, 0x4194, 0x42b6, 0x1919, 0x40c2, 0x467a, 0x4374, 0xb2cd, 0x1d2c, 0x4462, 0x40d7, 0x402a, 0xba75, 0xb0f9, 0x44c0, 0x428a, 0x3166, 0x0ecd, 0xbf07, 0x0e7f, 0xb03c, 0xb023, 0x416d, 0x4106, 0xba15, 0x072f, 0x1f48, 0x1d5c, 0x45b6, 0x3b35, 0x4003, 0xbff0, 0x3519, 0x4268, 0x40a9, 0x4364, 0x40a1, 0x412e, 0x4393, 0x462b, 0xa299, 0x2497, 0xb286, 0xacdc, 0xb027, 0xbf70, 0xbaee, 0xb213, 0xbf5c, 0xb00c, 0x4376, 0x4541, 0x33d7, 0x41d9, 0x43a6, 0x2c34, 0x44fc, 0xb001, 0x05ac, 0x40d4, 0x4201, 0x2dc1, 0xbaf3, 0xb201, 0xba73, 0x1f51, 0xb20a, 0x43ab, 0x4100, 0x4389, 0xba77, 0xbf3f, 0xa8c2, 0xba34, 0x41ce, 0x43fa, 0xb059, 0x46db, 0xbf7f, 0xb2e4, 0x43d5, 0xba10, 0x41c4, 0xbad6, 0x41df, 0x1918, 0x4117, 0x117b, 0xbf00, 0x42ed, 0x442e, 0x4229, 0x4393, 0x422e, 0xba58, 0x4058, 0xb270, 0xba2f, 0x40a7, 0x36a0, 0x405b, 0x2f9e, 0xbfb9, 0x41df, 0x4201, 0x3389, 0x405f, 0xa198, 0x43f0, 0x4151, 0x410c, 0x2c84, 0x2f92, 0x4206, 0xba04, 0x462b, 0xbf1a, 0x41eb, 0x2a6c, 0x4245, 0xa394, 0xb28e, 0xb24e, 0x04f0, 0x46e1, 0x42b3, 0xb03f, 0x30ef, 0x1d17, 0xbaec, 0xb2f8, 0x4168, 0x1b83, 0xbfac, 0x2cce, 0x4159, 0xb227, 0x45a2, 0x43d6, 0x43ca, 0x4124, 0xbf07, 0x0220, 0x40f0, 0x4201, 0xb224, 0x32db, 0x4340, 0x4145, 0x42c3, 0xbfa0, 0xb2e1, 0x42ee, 0xb0ed, 0x40e9, 0x414b, 0x42bd, 0x4555, 0x09ca, 0xb076, 0x1fcf, 0x09e5, 0xaf80, 0xb209, 0x40f0, 0xb04a, 0x4015, 0x41b1, 0x0408, 0xbfa8, 0x177c, 0xbf60, 0x41f9, 0x4251, 0xbf1f, 0x464a, 0xbf70, 0xba33, 0x425c, 0x4233, 0x454e, 0x4374, 0xbadb, 0x4105, 0x431b, 0x32b3, 0xb042, 0xb242, 0x4357, 0x28d3, 0x20b8, 0x2d3a, 0xba3a, 0xba75, 0x409b, 0xb2e3, 0x2cf2, 0x4078, 0xb257, 0xbf55, 0x427d, 0xb29a, 0xbad7, 0xbff0, 0x4207, 0x3766, 0x1fff, 0x1e14, 0x3528, 0x41c9, 0x27a9, 0x4114, 0x44f8, 0x18b7, 0x1c48, 0x4278, 0x2785, 0x1db7, 0x4473, 0x41f3, 0x18f7, 0x2d3c, 0xbfd8, 0x1c68, 0x402d, 0x43db, 0x4017, 0xb049, 0x401a, 0xba2f, 0xb090, 0x4558, 0x42e2, 0xba3b, 0x444c, 0xb01f, 0xad32, 0x4391, 0xbafc, 0xbf18, 0x418c, 0xb224, 0xb2bd, 0x1d32, 0x4076, 0xac73, 0x3419, 0x4128, 0x435b, 0x44bb, 0x42ed, 0xba33, 0xaaf8, 0xba11, 0xbf80, 0xb04f, 0x4253, 0x3fd6, 0x43df, 0xba71, 0xb255, 0xbf23, 0xbad5, 0xb01d, 0x4054, 0x4178, 0x4213, 0xbad9, 0xbf5f, 0x43e8, 0xb204, 0x45bd, 0x311b, 0x4286, 0xb261, 0x2ba0, 0x1a16, 0xb042, 0x4417, 0x1915, 0x4220, 0x429f, 0x4223, 0x41b3, 0xbac5, 0xb06a, 0x1b2c, 0x1452, 0xbf74, 0xb251, 0x423d, 0x1d7d, 0x43f3, 0xba14, 0x230c, 0x41e8, 0x436a, 0xb2c1, 0x40fd, 0x413f, 0x420a, 0xb2dd, 0xb28c, 0x46a5, 0xb291, 0xb2ee, 0x1f83, 0xb2e8, 0xb2cf, 0xa1bd, 0xb0c7, 0x1ba4, 0xbae4, 0x425a, 0x41f3, 0xb04b, 0xbfdc, 0x0f78, 0xba29, 0xb0e8, 0x42c9, 0xb291, 0xba18, 0x432b, 0xbfac, 0x4109, 0xab55, 0x436f, 0x4349, 0x4132, 0x41d9, 0xb240, 0x4227, 0x43c5, 0x403c, 0x1d71, 0x4182, 0x403c, 0x40e0, 0x4077, 0xb27b, 0x45bc, 0xa153, 0xb017, 0x429e, 0x42ee, 0x4453, 0x4064, 0xb0c1, 0xbf43, 0x0c27, 0x0f14, 0x3796, 0x433a, 0xad1c, 0xb27f, 0x431e, 0x1f68, 0xbf00, 0x3852, 0x43b8, 0x32f1, 0x4380, 0x1ec3, 0x1500, 0xa820, 0xa8b8, 0xbf73, 0xb28d, 0xac37, 0x1ab0, 0x0759, 0xaf00, 0xb28d, 0x40e7, 0x4314, 0xbaf9, 0xb05d, 0x33b3, 0x372a, 0x430c, 0x421d, 0x432d, 0xbfc7, 0x14a2, 0xba0b, 0x2f72, 0x45d5, 0x40da, 0xbf94, 0x4398, 0xb2c2, 0x10af, 0xba46, 0xaedb, 0x41ed, 0xba66, 0xba70, 0xb28d, 0x4130, 0x4106, 0x225a, 0x42d1, 0xba7f, 0xb07b, 0x41f3, 0x18bc, 0xa31f, 0x1c7e, 0x2979, 0xb267, 0x430c, 0xbfb3, 0x4005, 0x45b6, 0x1d0a, 0xb29c, 0xbfe8, 0x413f, 0x4212, 0x1a6c, 0xb2cd, 0x41a3, 0x1b19, 0xad5e, 0xba4a, 0x155b, 0x0e84, 0x4041, 0x43fc, 0x41ac, 0xba0c, 0x41ab, 0x38f2, 0x43c9, 0x1f49, 0xb006, 0xb218, 0x0657, 0x0076, 0xb071, 0xb292, 0xbf33, 0x42d9, 0x419a, 0x430a, 0x40d0, 0x43ed, 0xae7a, 0xba09, 0x1dee, 0x4172, 0xbac3, 0x40b6, 0x418f, 0x4102, 0xba3a, 0xb255, 0xa8b8, 0x28b4, 0xbf2a, 0xbac3, 0x46b3, 0xb029, 0x402b, 0x18ac, 0x14fb, 0xbf26, 0x42ff, 0x4545, 0x413c, 0x43f9, 0xb099, 0xb2ea, 0xa112, 0x4037, 0xac7a, 0x42d3, 0xb2d3, 0x4136, 0xba7d, 0x4081, 0x40fb, 0x4421, 0x41ea, 0x46eb, 0x43d5, 0x1956, 0x3dd2, 0x1e1c, 0x4323, 0x1833, 0xb0c3, 0x4267, 0x428e, 0xbad9, 0xbfb5, 0x1cb6, 0x4248, 0x42b6, 0x19fb, 0xb0a6, 0x1a7f, 0xba5a, 0x1a28, 0x4446, 0x4207, 0x3fd4, 0x4119, 0x4069, 0xb215, 0xa19b, 0x4392, 0xbaef, 0xb2de, 0x1c2a, 0x46f5, 0xbf73, 0x4286, 0xbff0, 0x4006, 0xb279, 0xb27f, 0x428e, 0x416f, 0x411a, 0xbfd8, 0x0c5e, 0x444e, 0x43c3, 0x156d, 0x4175, 0xb2fb, 0x3877, 0xb2d7, 0x400a, 0x4429, 0x38fe, 0xb27a, 0x1868, 0x429f, 0xb0d5, 0x1515, 0x42ce, 0x45a1, 0x43e7, 0xbf55, 0xba1e, 0x4365, 0x1d96, 0x42fc, 0x43f5, 0x41ed, 0x4020, 0x4390, 0x1fa2, 0xbfd6, 0xb25f, 0x4128, 0x43ad, 0x1f47, 0x4373, 0x41e2, 0x4040, 0x4102, 0xad91, 0xb2ea, 0x4064, 0x44f9, 0x1cc9, 0x42d0, 0x2e1d, 0xb094, 0xbad1, 0xb0bd, 0x4144, 0x4074, 0xbfa0, 0x00b7, 0x4556, 0xb21d, 0xba47, 0x42e0, 0xb0fb, 0xbfcd, 0x193b, 0x4266, 0xb230, 0x41ec, 0x4271, 0x416e, 0xa323, 0x4225, 0xa208, 0x4057, 0x356c, 0x2a9d, 0x0db2, 0xbfc1, 0x19d8, 0x4242, 0x4240, 0x41eb, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x1ecc668c, 0x2e9c60ff, 0x9990f0ee, 0xf1a38886, 0x3f294482, 0xc54ff2ce, 0x36359d5d, 0x6e0e101b, 0xc58ac456, 0x564992fe, 0xcc932f92, 0x05cbf818, 0x7c022c30, 0xc34238ff, 0x00000000, 0x700001f0 }, + FinalRegs = new uint[] { 0xffffcfb8, 0xc342cf05, 0xffffcfb8, 0x80000185, 0xc342cf05, 0x000013a4, 0x3cbd4433, 0x000017f0, 0x0000138c, 0xc34266a3, 0xcc932f92, 0x00000371, 0xc3424f05, 0xfffffb7c, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x430e, 0x1b60, 0x2a48, 0xbadf, 0xb061, 0x420b, 0x41bf, 0xba6c, 0xb2dd, 0x3070, 0xb2ae, 0xb21f, 0x4226, 0x4260, 0xbaef, 0xb233, 0x4645, 0x439f, 0x2b19, 0x394a, 0xba1e, 0xba60, 0x40fe, 0xba31, 0xbf48, 0x4296, 0x433d, 0x4353, 0x26f9, 0x42c3, 0x4286, 0x2142, 0xba78, 0xa6be, 0x2ec6, 0xba58, 0xb2e3, 0x43c8, 0x43ce, 0x3eb2, 0x1c58, 0xbf61, 0xb24d, 0x1a44, 0xb2f5, 0xa49d, 0xba3d, 0xba1c, 0x420f, 0x0ec6, 0xbf1f, 0x4636, 0xad48, 0x4181, 0x4155, 0x3050, 0x4001, 0xba03, 0x422b, 0x0266, 0xb0c4, 0xba50, 0xa8b4, 0x1e73, 0x41f5, 0xa645, 0x436a, 0x277c, 0x432a, 0x1b94, 0x1d44, 0xb294, 0x4080, 0x42a6, 0xb256, 0xbf2d, 0x1926, 0x4000, 0x4396, 0xb221, 0xb071, 0x0912, 0xba74, 0xb2d9, 0x3bd3, 0x45f5, 0x35e1, 0xb2a0, 0x42f2, 0x1e6b, 0x1595, 0x0e57, 0x42a0, 0x45d6, 0xba44, 0x41a2, 0xb26d, 0x43d3, 0xa79e, 0x434c, 0xbfd9, 0x40a3, 0x418c, 0xb230, 0x2774, 0x414a, 0x4385, 0xba77, 0x4019, 0x4202, 0x4251, 0xbafe, 0x4243, 0xb0e9, 0x1fc2, 0x4196, 0x2115, 0x4570, 0x420b, 0x4344, 0x40fc, 0x1820, 0xbf13, 0x40f3, 0xbaf1, 0x4280, 0x4110, 0x43ba, 0x25f9, 0x4175, 0xbfa0, 0x09ba, 0x35e7, 0x41ec, 0x12ba, 0xb249, 0xbade, 0x3bf7, 0x3c20, 0xba5f, 0x4094, 0x3b99, 0x2bb1, 0xb2e3, 0x4166, 0x402f, 0x414c, 0x425d, 0x415d, 0x43ba, 0x40da, 0xbfdf, 0x45e1, 0x412c, 0xb2da, 0x44d5, 0x4180, 0x3b39, 0x4221, 0xbf62, 0xba33, 0x42c8, 0xadf9, 0xb2af, 0xb28f, 0x4254, 0x4121, 0x393f, 0xa074, 0x4320, 0xb0ef, 0x400e, 0xbf37, 0x41a4, 0xbfb0, 0x39e8, 0x4226, 0xafd2, 0x400d, 0x435e, 0x42f0, 0xb223, 0x403e, 0xba0c, 0xb09d, 0x42a0, 0x2b3a, 0x4016, 0x4308, 0xae2c, 0x3e99, 0x06a6, 0x45cb, 0x0741, 0xba55, 0xbf93, 0x1c12, 0x40e2, 0x24c4, 0x1fb7, 0x41ca, 0x2258, 0xbf5d, 0x3ccc, 0x420f, 0x41d4, 0xba60, 0x402d, 0x40e3, 0xb261, 0xbfcd, 0x415d, 0xb01f, 0x1901, 0x420b, 0x1a54, 0x3fa6, 0x4021, 0x1dcc, 0xb260, 0x425f, 0xb038, 0xba79, 0xba11, 0x103d, 0x1f3c, 0xba7c, 0xb283, 0xbfba, 0xb24d, 0xb273, 0x3b72, 0x3c0a, 0x40ca, 0xb2c3, 0x3882, 0xb282, 0xba42, 0x402a, 0x4655, 0x43bf, 0x2362, 0x43c6, 0x4148, 0xb08d, 0x4072, 0xa1f3, 0x3784, 0xb2a2, 0x438a, 0xbf3c, 0x22a4, 0x401c, 0x4201, 0xa508, 0xbae9, 0x4490, 0xb2ea, 0x1de4, 0xbae9, 0x43ab, 0x3fcb, 0x4028, 0xa9ca, 0x1a68, 0x2cfc, 0xb01d, 0xbfb8, 0x406b, 0x3db1, 0x41ff, 0x2386, 0x3663, 0xb206, 0x421c, 0xab7f, 0x41c8, 0xba13, 0x2a54, 0x41db, 0x407d, 0xb066, 0xbf1f, 0x1a65, 0x1d6f, 0x066a, 0x428d, 0x4065, 0x404f, 0x1ac2, 0x44db, 0x0b90, 0x4488, 0x4255, 0x4550, 0xb0c3, 0x4291, 0x414c, 0xb2e4, 0x4231, 0x430e, 0xb240, 0x4003, 0x1210, 0x43b1, 0xb246, 0x4344, 0xbf4f, 0x44f9, 0xb218, 0xba07, 0x4053, 0xbf60, 0x4248, 0x1432, 0x45c3, 0x4083, 0x4403, 0x2f70, 0xbfb9, 0xaecd, 0xb050, 0x40e7, 0x409f, 0xaeeb, 0x40d6, 0x4315, 0x125b, 0x400b, 0xb0a3, 0x44db, 0xbf37, 0x4202, 0x4179, 0x1d0e, 0x2d2d, 0xa705, 0x43ee, 0x3206, 0x1586, 0xb25f, 0x37da, 0x423c, 0x4151, 0x401f, 0x04c3, 0x219b, 0x22dd, 0x402d, 0x1c87, 0x4201, 0x41bb, 0xbfa6, 0xb040, 0xaf1a, 0x4372, 0xbadd, 0xbf02, 0x0c72, 0xb20c, 0x41cd, 0xb2e9, 0x4115, 0x1f3c, 0x4000, 0x44e1, 0xb2c9, 0x31aa, 0xbac6, 0xb083, 0x2dc0, 0x432d, 0x2a5c, 0xb2d3, 0x4019, 0x46ed, 0xbf6f, 0x4322, 0x4466, 0xb249, 0x2cf1, 0x4387, 0x4170, 0x43e0, 0x40a7, 0x16ff, 0x1793, 0x29d4, 0xba1d, 0x2029, 0x4173, 0xb217, 0x434f, 0xb231, 0xa7fa, 0xbf5e, 0x43ce, 0xb0ae, 0x3cf2, 0x3a2e, 0x43c6, 0x2f25, 0x4304, 0x0394, 0x43de, 0x4312, 0x429c, 0x1fd2, 0xb2c4, 0x3066, 0x4159, 0x4192, 0x46f8, 0xbae6, 0x412d, 0x400d, 0x4272, 0x3c14, 0x427f, 0x447c, 0xb294, 0x4609, 0xbf88, 0x43c4, 0x35ea, 0x4308, 0xbacc, 0x42b6, 0x433f, 0xa5b2, 0x43f6, 0xb212, 0x0a9d, 0xbae2, 0x118e, 0x4214, 0x4344, 0x1ff4, 0x18ec, 0xb025, 0x1865, 0x04b5, 0xbf63, 0x4102, 0xbae5, 0x41a5, 0x40fc, 0x1fee, 0x42f4, 0x438e, 0x1d08, 0xb27c, 0xba61, 0x40b7, 0x03e2, 0x1bcf, 0x34ad, 0xb2b0, 0x007d, 0x3c4f, 0x41d8, 0x45d1, 0x41ac, 0x442b, 0x424d, 0x4279, 0xa121, 0x44a9, 0x41f3, 0x01c8, 0x40bd, 0x414f, 0xbfcf, 0xba19, 0x42ea, 0x2383, 0x00d8, 0xba6a, 0xba41, 0xb01c, 0x317c, 0x40a8, 0x401d, 0xb054, 0x400b, 0x1d1a, 0x1fd3, 0x1b51, 0xbfa8, 0x403c, 0xb003, 0x4336, 0x3dd6, 0x40d2, 0x410b, 0xafc3, 0xabd6, 0xa7d5, 0x02c0, 0x4201, 0x4242, 0xa5f6, 0xb061, 0xba2d, 0x1be8, 0x0262, 0x434c, 0xa56e, 0xbf95, 0xba08, 0x22fd, 0xb22d, 0xa383, 0x1876, 0x024b, 0x4134, 0x2c98, 0x4232, 0xa829, 0x41cc, 0x4237, 0x4287, 0x44db, 0x46fa, 0x415c, 0x432b, 0x416d, 0x4231, 0xaba6, 0xb208, 0x402f, 0xba7d, 0xa377, 0x4005, 0xacdb, 0xbfc2, 0x1ce5, 0xbf70, 0xb26c, 0x3bb5, 0x05cf, 0xb0f3, 0xb222, 0x4398, 0x4632, 0x4132, 0x4371, 0x1bd3, 0xb212, 0xbfac, 0xbf80, 0x41e2, 0xbf3e, 0x431e, 0x0ade, 0x4225, 0x26de, 0x4062, 0x4660, 0x4054, 0xbf5a, 0x43b4, 0x4282, 0x4255, 0x1cb3, 0x286c, 0xba6f, 0xabbd, 0xb0f6, 0x4206, 0x4154, 0x40f2, 0x42e7, 0x30f7, 0x4005, 0x45b6, 0xbf9f, 0x40a2, 0xb2b8, 0xba26, 0x2711, 0xad55, 0x4382, 0xb0b4, 0x1990, 0xba12, 0xb2e0, 0xba00, 0xa4f2, 0x4356, 0xb211, 0x435a, 0x4052, 0xaaa8, 0x404c, 0x41d0, 0x1cb9, 0xb28e, 0x41dc, 0x2275, 0x4492, 0x0359, 0xb2e5, 0xbf4e, 0x1301, 0x44d5, 0x4122, 0x39cf, 0x347b, 0x43cd, 0x28be, 0x1851, 0x1a41, 0x40e7, 0xba0e, 0x18bd, 0xb095, 0xb269, 0x4029, 0x1879, 0x1b1b, 0x4156, 0x4210, 0xbf3b, 0x44ac, 0x405e, 0x42b1, 0x4306, 0xb297, 0x4198, 0xb002, 0x4232, 0x3704, 0x3643, 0x4220, 0xbf9a, 0x3986, 0xb2a7, 0x412d, 0x416f, 0x3c6c, 0xba06, 0x1bda, 0x435d, 0x421d, 0x41cf, 0xa2e4, 0x1cf3, 0x442b, 0x40ec, 0xb281, 0x1ebc, 0x4373, 0x40c9, 0xac98, 0xbff0, 0xa100, 0x409f, 0xb290, 0x41f9, 0x1733, 0xb255, 0x1efe, 0x4004, 0xbf3e, 0xb0c3, 0x4023, 0x09b0, 0x0995, 0x1ec1, 0x2998, 0xba06, 0x1e93, 0x43cc, 0xb2de, 0x42d3, 0x41ac, 0x4354, 0x4013, 0xa9a8, 0xbfd4, 0xb241, 0x4022, 0x1dcf, 0xb0ee, 0x43cc, 0x1a03, 0xbac9, 0x1b7e, 0x4311, 0x439e, 0xbf8a, 0x025e, 0x43b7, 0xa7ed, 0x4276, 0x4200, 0x42fa, 0xb04a, 0xa648, 0x418e, 0x40bd, 0x09bd, 0xbaec, 0x42d1, 0x2ede, 0x1b6e, 0x06b5, 0x08c6, 0xb2ba, 0xb2f6, 0x1d2f, 0x1aac, 0xba61, 0xbf2d, 0xb283, 0x4311, 0xb203, 0x4193, 0xa79a, 0x1eb0, 0x436e, 0xba53, 0x4612, 0x42d8, 0xb202, 0x3e0c, 0xaf8a, 0xba34, 0x4084, 0xabe0, 0x19fe, 0x4331, 0x4337, 0xb247, 0x4647, 0x407b, 0x31ff, 0xbf1c, 0x4148, 0x385b, 0x3fa5, 0xb2e5, 0x1e35, 0x087e, 0xaef2, 0xb093, 0x424c, 0xb227, 0x43dc, 0x102d, 0x4612, 0xb2c3, 0xb221, 0xb2ac, 0xbf24, 0x4359, 0x42b6, 0xb25d, 0x3d87, 0x42e8, 0xbaea, 0x436d, 0x1a91, 0xbafc, 0x42db, 0xa79c, 0x1946, 0x4140, 0x40af, 0x4095, 0x1ce6, 0x12fc, 0x2a6a, 0x40fd, 0x1e44, 0xbf75, 0x1ecd, 0x4114, 0x1fb8, 0xb07d, 0x41e1, 0x4306, 0x4040, 0x4007, 0xba64, 0xbf70, 0x4011, 0xbf39, 0x40be, 0xb257, 0xb05a, 0xb000, 0x2cf7, 0x0fae, 0xaa47, 0xbf1f, 0x4095, 0x43a6, 0xbfc0, 0x34a2, 0x3dc5, 0x42fe, 0x4329, 0x4135, 0xbae2, 0x46b9, 0xbfdb, 0x1c14, 0x4482, 0x432a, 0xb29a, 0x1fb4, 0x1869, 0x199f, 0x43b6, 0x3cd5, 0x437e, 0x2331, 0xb201, 0x4348, 0x2663, 0x41c9, 0x421e, 0xb2a6, 0x41a1, 0xbfc4, 0x3897, 0x4458, 0x41a9, 0xb2c6, 0xbfbf, 0xbf70, 0x1c52, 0x4132, 0x1e34, 0x0d87, 0xbae6, 0xb2b4, 0x2385, 0x41c1, 0x42ab, 0x1a6a, 0x07b7, 0x40aa, 0xbf21, 0x403b, 0xbaee, 0x43d9, 0x43ef, 0x41ab, 0x426c, 0x4280, 0x4019, 0xbf60, 0x4245, 0x1f77, 0x4061, 0xba66, 0x4086, 0x337a, 0x1942, 0x2580, 0x0667, 0x40dd, 0x4135, 0xbf56, 0x408c, 0xb2b1, 0xba18, 0xb2ac, 0xbf1b, 0x42d0, 0xb0c8, 0x418c, 0xb283, 0xaf4c, 0x1bab, 0xb047, 0xb092, 0x16b1, 0xbae1, 0xa058, 0xa49b, 0x4007, 0xaca0, 0x1e8e, 0x41dc, 0x4065, 0x3fbb, 0x4192, 0x191a, 0xb263, 0xbfa9, 0x4384, 0x438c, 0x46da, 0x1e3b, 0x1cc2, 0x43a3, 0xba39, 0xbfa5, 0xb0bf, 0x1e42, 0x1a0a, 0x1cad, 0x401c, 0x439f, 0xa4ab, 0x4302, 0x4484, 0x40d3, 0xbfd0, 0x41cd, 0x3bc6, 0x1fc1, 0x41b9, 0xb255, 0x41c4, 0xba13, 0xb298, 0x2459, 0x054c, 0xbfd4, 0x41d4, 0x1b7d, 0x1d2c, 0x2806, 0x1bec, 0xb2f3, 0x268c, 0x4131, 0x42cb, 0x4146, 0xbf8a, 0x42c8, 0x4057, 0xa7d7, 0x36f5, 0xb082, 0x439c, 0x42cf, 0x454e, 0x403b, 0xb225, 0x1e75, 0x4357, 0x2f15, 0x4096, 0x314f, 0xa555, 0xba10, 0x4003, 0x3197, 0xba21, 0x40a5, 0xbf9f, 0x41cd, 0x24ad, 0xaf2c, 0x42d0, 0x38f1, 0x407c, 0x429e, 0xb03e, 0xbf1f, 0x40c7, 0xb294, 0xa6a8, 0x387d, 0x1236, 0x4148, 0x3c48, 0xbf76, 0x00f5, 0xb2bf, 0x12a8, 0x423b, 0x41c2, 0x4206, 0xb0a4, 0x0c83, 0x40ff, 0x4138, 0xbfb0, 0x1b14, 0x4228, 0x3697, 0x3a11, 0x4107, 0xbafd, 0x447c, 0x43a3, 0xb28f, 0x4246, 0x454f, 0x432e, 0xbfdc, 0xb2d1, 0x437e, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xcc2ef87d, 0xae95a3fd, 0x5e6e0ca5, 0x1ef09cca, 0x00ea6c00, 0x963e3b79, 0xc2107f27, 0x7b8c8e2a, 0xa52501a4, 0xdd47e07c, 0x58514dc3, 0xb401ec82, 0x88632001, 0xabb2221b, 0x00000000, 0x900001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x0000003e, 0x0000183e, 0x00000000, 0x544dfc0a, 0x00000000, 0x00000000, 0x0000ffff, 0x0000133c, 0xffffffff, 0x0000148f, 0xa00f6410, 0x88633849, 0xabb2349a, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x1a9a, 0xaba5, 0x42a3, 0x4339, 0x43b9, 0xb263, 0xb077, 0xbf80, 0x409b, 0x4178, 0xba22, 0x439a, 0x428e, 0xb0b6, 0x404e, 0x1ae0, 0xba1e, 0x0e81, 0x41c2, 0xbfb8, 0x40e2, 0x18d2, 0x462c, 0x43c9, 0x1e7d, 0xb21b, 0x43ce, 0x45b5, 0x42d0, 0xb038, 0x42c9, 0x4238, 0x1e37, 0x42b5, 0x41cc, 0x42f9, 0xb072, 0x4154, 0x030b, 0x4384, 0x42d6, 0x46a8, 0xbf6c, 0x0ce2, 0xb24b, 0x4305, 0x4418, 0xb006, 0x404c, 0x438f, 0x438f, 0x0532, 0x41e4, 0x4290, 0x4019, 0xba4e, 0xbf54, 0x412d, 0x4202, 0x4208, 0x4614, 0x193b, 0xbf57, 0x3ef2, 0x4692, 0xbaf4, 0xad07, 0xba72, 0x4549, 0xb22f, 0x40a5, 0x4184, 0x1cf9, 0x416c, 0x421a, 0x4371, 0xba66, 0xba38, 0x1c55, 0xaa65, 0xaa8c, 0x4070, 0x0cf2, 0x1ff4, 0x1a0b, 0xbfad, 0xb219, 0x400b, 0x2178, 0x1d28, 0x43f7, 0x419f, 0x421b, 0x1f66, 0x40a0, 0x4224, 0x4370, 0x4325, 0x37c4, 0xa62d, 0xb080, 0x43e0, 0xb2a1, 0x00db, 0xab3b, 0xbf15, 0x40ae, 0x109e, 0x3d73, 0x1fc1, 0x19fb, 0x3e2e, 0x42d5, 0x1946, 0x419c, 0x4135, 0x02c7, 0x46fb, 0xb22c, 0x40b6, 0x43a1, 0x4229, 0x4382, 0x42c3, 0x422d, 0x3ecb, 0x4289, 0x407d, 0xb090, 0xbf12, 0x0c06, 0xba06, 0x4324, 0x439b, 0x440d, 0x4177, 0x41a8, 0x41d5, 0x4282, 0xbaff, 0xbfc0, 0x3148, 0x4294, 0xbfb0, 0x1b88, 0x1f7c, 0xb2ba, 0xbf9f, 0x4085, 0x421a, 0xb2d2, 0x1e81, 0x40e0, 0x41c4, 0xbade, 0x19d2, 0x4289, 0xb237, 0xba1c, 0xba3c, 0x41e1, 0xba5e, 0xb2b8, 0x41f3, 0x423a, 0xb272, 0x40d7, 0x41d2, 0x4046, 0xbf88, 0x40d6, 0x4045, 0x43ff, 0x18df, 0xa909, 0x40cf, 0x42bc, 0x4198, 0x0130, 0x071f, 0x427e, 0x45a3, 0x1b5c, 0xbf0a, 0xb222, 0x3b72, 0x4039, 0x43af, 0x1dfe, 0x4338, 0xbfc2, 0x43f5, 0x41f6, 0x4012, 0x416e, 0x1d8b, 0x1ff4, 0xb2e2, 0x29ae, 0xa123, 0xb2ef, 0xbf26, 0x1f7f, 0x347b, 0x3277, 0x41e4, 0x43b0, 0x41b1, 0x4193, 0xbf05, 0xba7f, 0xb26d, 0x2ee4, 0x1eb0, 0x43dd, 0x413e, 0x4072, 0x1b4b, 0x4047, 0xb2ae, 0xbaf6, 0x41d7, 0xa6ec, 0xbfdb, 0xb250, 0x4057, 0x2618, 0xb282, 0x4321, 0x4288, 0x400d, 0x4138, 0xa678, 0xa9a6, 0xba47, 0xb2e9, 0x1995, 0x43d3, 0x4237, 0xb03d, 0x4144, 0x46a2, 0xb206, 0x1d19, 0x1b25, 0x4389, 0x4104, 0xb066, 0x1d21, 0x1f7e, 0x2adb, 0xbfd2, 0xa0ea, 0xa87a, 0x4382, 0xb018, 0x44ba, 0x425f, 0x3c44, 0x40eb, 0x43b6, 0xb0a6, 0xba7c, 0xba0c, 0xaeec, 0x4592, 0xb036, 0xbf15, 0xba48, 0xb076, 0x4042, 0xac7c, 0x4560, 0x4003, 0xb03b, 0x1bc1, 0x0ad5, 0x4293, 0xb262, 0x1c32, 0x199d, 0x435d, 0xbf80, 0x2273, 0xb2b2, 0x427b, 0x23cc, 0xb251, 0xb09b, 0x42c1, 0x37da, 0x437a, 0xb022, 0x4384, 0x4280, 0x2b09, 0x4396, 0xbfb8, 0xb09d, 0xaebd, 0xbf34, 0x4346, 0x169e, 0x425d, 0xb036, 0x1e3d, 0x1384, 0xb00e, 0x1cce, 0x135c, 0x4266, 0x025b, 0x46cd, 0x3cf3, 0xbf9d, 0x43d8, 0x41be, 0x2ba8, 0x1ba7, 0x43b3, 0x1ae9, 0xbad7, 0x4019, 0x1deb, 0x292d, 0x413c, 0x4096, 0xb203, 0x4033, 0x1e9e, 0x40c4, 0x2339, 0xb228, 0xb090, 0xbf2a, 0x4172, 0xb22f, 0x4418, 0x162b, 0x4061, 0xa58e, 0xba40, 0x42e7, 0x41e1, 0xb27b, 0x1c28, 0x45ae, 0x41a1, 0x4383, 0x4149, 0x4382, 0x1e4c, 0xbf14, 0x40bd, 0x4209, 0x401c, 0x0482, 0x42f0, 0x4681, 0x43d3, 0x4226, 0xadee, 0x1e44, 0xb0ad, 0x417e, 0x1efc, 0xbf49, 0xb22f, 0x2bad, 0x42a4, 0x42b2, 0xba1f, 0x4562, 0x40d3, 0x4619, 0xbae8, 0xba17, 0x4154, 0xbf6a, 0x407f, 0x40e1, 0x4359, 0x42f3, 0x137f, 0xb2b2, 0x419e, 0xbf0a, 0x4277, 0x41ca, 0x4025, 0x41f8, 0x11b6, 0xb015, 0x42bc, 0xb098, 0xbf26, 0x4382, 0xba65, 0x2399, 0x44e3, 0x1b58, 0x4395, 0x30cc, 0x41c1, 0x41fa, 0x4632, 0xa687, 0x192f, 0x3f96, 0x45d8, 0x41b2, 0x4075, 0x4080, 0xbf9f, 0x43b9, 0x1902, 0x1a58, 0xb003, 0xa201, 0xb28f, 0x405c, 0x41d8, 0xb0cb, 0x42ba, 0x43c2, 0x40fa, 0x4331, 0xb294, 0xb205, 0xb0a7, 0xbfc2, 0x4268, 0x403f, 0x40bf, 0x41e1, 0x407b, 0x4013, 0x2de8, 0xbf75, 0xb039, 0x1f48, 0x428a, 0x4226, 0xba11, 0x0c28, 0x338a, 0x1d9a, 0xb075, 0x3f72, 0x3f5e, 0x4165, 0x4585, 0x3a3d, 0x427e, 0xb0b8, 0xa4b0, 0x436a, 0x4385, 0xbf02, 0x45ba, 0xb07c, 0x42b1, 0xb26b, 0xb22a, 0x4297, 0x1bda, 0x4634, 0x0a08, 0x43e7, 0x169b, 0xa98d, 0x43e2, 0x42f6, 0x42c1, 0x41c2, 0x1cfd, 0xbf48, 0x40b7, 0x40de, 0x1f98, 0xb246, 0xb03d, 0x1e78, 0x3ac4, 0x407d, 0x1862, 0xb238, 0x2451, 0xba4a, 0x445e, 0x1a72, 0x4252, 0x1d66, 0xbf41, 0x42df, 0x31d5, 0xb095, 0x41d9, 0x434b, 0x4303, 0x2470, 0x1dff, 0x469a, 0x45b8, 0x418e, 0x456e, 0x4417, 0xba49, 0xb2b7, 0x4228, 0xa06c, 0x41e7, 0xa99b, 0x36cf, 0x46c4, 0x1852, 0x142a, 0xbfce, 0x06af, 0x4135, 0xb260, 0x3bfc, 0x410a, 0xbf90, 0xb0ca, 0x4165, 0xbf2d, 0x4001, 0x42d4, 0x2d2d, 0x46e0, 0x190d, 0xbfba, 0x18a6, 0xb240, 0x4309, 0x1378, 0x466f, 0x41e6, 0xb079, 0x188f, 0x4012, 0xba77, 0x082f, 0x4018, 0xba48, 0x1786, 0xbac1, 0x1734, 0xbf73, 0x1a6e, 0x4295, 0x1bc2, 0x1956, 0x4196, 0x0623, 0x3a43, 0x4197, 0xbf91, 0xb283, 0x4234, 0x0324, 0x4663, 0x4050, 0x4364, 0xb237, 0xb22e, 0x362c, 0xb21f, 0x2ab6, 0x3262, 0x1b32, 0xbf49, 0xaf0d, 0xb2d7, 0xba6d, 0x43e4, 0xb2a6, 0xb20d, 0x390c, 0xbfcf, 0x1a4a, 0xb2df, 0x447c, 0x4555, 0x437d, 0x412f, 0xbf90, 0xb0fe, 0x43bc, 0xb210, 0xb22b, 0xba63, 0xba2e, 0x4293, 0xb015, 0x0ba7, 0xbf92, 0x1956, 0x413e, 0x40db, 0xa728, 0x4360, 0x04c0, 0xb060, 0x41fa, 0x460d, 0x4168, 0xb24f, 0xaff6, 0x4284, 0x4018, 0x002e, 0x43ea, 0xbfb4, 0x3528, 0x40d5, 0xba2d, 0x4296, 0x1e62, 0x44d9, 0x189c, 0xb0c5, 0x4485, 0xb2b2, 0x42e6, 0x432f, 0x26d4, 0xa09d, 0x0530, 0xb0df, 0x42af, 0x4367, 0x1aff, 0x40c6, 0x2ceb, 0xba26, 0x4337, 0x4013, 0x42a1, 0xbf47, 0x1a0a, 0x435d, 0x4602, 0x26ee, 0x4313, 0x4128, 0x4135, 0x1ada, 0x1f63, 0x40be, 0x1a3d, 0xac4c, 0x40dd, 0x2cc1, 0x206b, 0x4224, 0xbf4e, 0xb2b9, 0x2ace, 0x1fba, 0xbad6, 0x42b6, 0xbf3e, 0xba6f, 0x41a1, 0xb0c5, 0x20d2, 0xb243, 0x1113, 0xba49, 0x04a8, 0xb253, 0xb0e5, 0xb27b, 0xba5e, 0x1f73, 0x4165, 0xb2e0, 0xb239, 0x4044, 0x431f, 0xb2ed, 0x047d, 0x2407, 0x464b, 0x1298, 0xbfc3, 0x4216, 0x1e84, 0x40c9, 0x4383, 0xb0de, 0x410b, 0xb03b, 0x41a5, 0x409b, 0x1186, 0xbfd2, 0x442c, 0xb0ed, 0x1b84, 0x06a9, 0x43d7, 0xb2ee, 0x4370, 0x420f, 0x41a9, 0x4281, 0xbf00, 0xbac5, 0xad27, 0x1f67, 0x4304, 0xbfe0, 0x4079, 0xba16, 0x425e, 0xbf2a, 0xb00d, 0xa749, 0x3c0b, 0xb00e, 0x19d6, 0xb264, 0x433f, 0x2049, 0x406c, 0xbf61, 0x420e, 0x4223, 0x41b5, 0x418e, 0x4032, 0x4012, 0x4379, 0x4323, 0x3185, 0x43ba, 0x4190, 0xb093, 0xafab, 0x4592, 0xb0b0, 0xbfae, 0x173a, 0x412d, 0x4273, 0x2c58, 0x183c, 0x4389, 0x420c, 0xb211, 0x4304, 0x43e2, 0x41eb, 0x335a, 0x431e, 0x1a7d, 0x4119, 0x12ff, 0xbf3a, 0x42c1, 0x1ea5, 0xb060, 0x4222, 0xb0d4, 0xa12d, 0x4217, 0x0a9a, 0xaa17, 0x4206, 0x445f, 0x4348, 0x20b4, 0xb209, 0xbf11, 0x4072, 0x4023, 0xbadf, 0x41f3, 0x43bf, 0xa351, 0xb266, 0x43a0, 0x1e38, 0x43a9, 0xb24b, 0x2ade, 0x4167, 0x3e49, 0x41f3, 0x4103, 0x0576, 0x433e, 0x42d4, 0xbf91, 0xb2f7, 0x45c9, 0x41b5, 0x4040, 0x3aff, 0xbac7, 0xb213, 0x4192, 0xbf00, 0xb205, 0x43e9, 0x4264, 0x1c65, 0x41e6, 0x45f4, 0xbf7b, 0x4290, 0x42dd, 0x1e56, 0xbadf, 0xb03a, 0xadad, 0x42f2, 0x404b, 0x4635, 0x1eaf, 0x46a5, 0xb25c, 0x2d7c, 0x4053, 0x4005, 0xb068, 0x42b6, 0x1a03, 0x4346, 0xa2f9, 0x402f, 0x40ed, 0xbf51, 0x4694, 0x23a6, 0x1a50, 0x29c4, 0x404b, 0x1283, 0x425c, 0x1de7, 0x40ad, 0xb205, 0x402f, 0x4399, 0x1cb5, 0x430e, 0xb2ba, 0x19d9, 0xbf1f, 0x43f4, 0x41df, 0x1cc0, 0x407d, 0x430f, 0xbf06, 0x43e0, 0xad43, 0x065c, 0x42ed, 0x260a, 0xba07, 0x4415, 0xb0c3, 0x419b, 0x180a, 0xb049, 0x38a3, 0xbf93, 0xb0f3, 0x1c0b, 0xb241, 0x417f, 0xbf9f, 0x428f, 0x39fb, 0x45e5, 0x407d, 0x4664, 0x4202, 0xbaf6, 0x43c1, 0x42a5, 0x409e, 0x4049, 0x0925, 0xadf3, 0x0581, 0x4043, 0x1a2c, 0x42ca, 0x1b73, 0xbfaa, 0xb248, 0x42bb, 0xb253, 0xb254, 0x4432, 0x43ac, 0xbaf8, 0xba1e, 0x27ea, 0xbf55, 0x17bc, 0x4193, 0x0a07, 0x198a, 0x41f9, 0xbf95, 0x4370, 0x2713, 0x3500, 0x4093, 0x41df, 0xba7c, 0x19ec, 0xb21f, 0x357a, 0x0226, 0x3727, 0x43db, 0xb0a4, 0x4017, 0xa52a, 0x4381, 0x40ae, 0xb258, 0x2d00, 0x2206, 0x4286, 0xa01c, 0x4452, 0x43b3, 0xaf31, 0x4208, 0x4342, 0x1cc8, 0x41e0, 0xbf88, 0x02a7, 0xb002, 0x394d, 0xb0f4, 0x43c2, 0x42f8, 0x413a, 0x411c, 0xbace, 0x3d27, 0x42d9, 0x1410, 0x3a6d, 0x4106, 0x027a, 0x4346, 0x408a, 0x43bb, 0x43c6, 0x41b2, 0xbf08, 0x1c77, 0x421b, 0x1322, 0x4425, 0xbace, 0x2ecb, 0x43ad, 0x4415, 0x414a, 0x420e, 0xba66, 0x41a3, 0x40eb, 0x3809, 0x2732, 0xb0b1, 0xb2dc, 0x3668, 0xbf89, 0x413a, 0xb03e, 0x3a5e, 0x41cd, 0xbf37, 0x4372, 0xb2db, 0xacdb, 0x086c, 0x42be, 0x4275, 0xbafd, 0x422b, 0x1bcc, 0x4272, 0x41c1, 0x0e46, 0x433d, 0xbfc1, 0x0ce0, 0xaaad, 0x1982, 0x42cb, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x46ce3787, 0x91b9dd82, 0x5c30a10f, 0x98b32b04, 0x61635373, 0xdfb71ffc, 0x105be440, 0x6bd3228d, 0x9979b985, 0xe4135b49, 0x78e8170d, 0x00193fd1, 0xf7f4c7a5, 0x0e660f37, 0x00000000, 0xd00001f0 }, + FinalRegs = new uint[] { 0x00001fff, 0xfffecfff, 0x0000207e, 0xe413586f, 0xffffff81, 0x00003232, 0x0000007f, 0x00000032, 0x6bd3228c, 0xf7f4ed77, 0x00000000, 0xf7f4d893, 0x00001a68, 0x1beca537, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x40de, 0xbf27, 0x1dbc, 0x42ee, 0x414f, 0xab56, 0x4170, 0x4225, 0x1b8e, 0xa89e, 0x00b6, 0x3e79, 0x46d8, 0xb2dc, 0x410d, 0x4058, 0x4395, 0x08c6, 0x43ce, 0xb000, 0xb07f, 0xa5ad, 0xbf14, 0x1da0, 0xb283, 0xbf2e, 0xb0a5, 0x425c, 0xb0ff, 0x4097, 0x3cc7, 0xb2ea, 0x406a, 0x401a, 0x4189, 0x1d12, 0x4265, 0x4167, 0x409d, 0x19c6, 0x40ef, 0xba26, 0x43e0, 0xbf94, 0xbf80, 0x1afd, 0xb067, 0xbfa0, 0xb227, 0x18c1, 0x4584, 0x249c, 0x4037, 0x1d6e, 0x462f, 0x42c7, 0xbf0d, 0xaf08, 0x1c64, 0xba1d, 0x40b0, 0x41a4, 0xa66a, 0x41b1, 0x4164, 0x1108, 0xbfb0, 0x096a, 0xb25f, 0x41aa, 0x1af7, 0x28de, 0x44a1, 0xb261, 0x4084, 0x43bd, 0xbfd3, 0x423d, 0x2de7, 0x1d14, 0x4079, 0xa83f, 0x41fb, 0xb216, 0x0c50, 0x1b4f, 0x42ec, 0xb212, 0x4085, 0x4211, 0x42a1, 0x1fdd, 0x4288, 0xba4e, 0x1029, 0x1d39, 0x3e7c, 0x4175, 0x4065, 0xb045, 0x40a2, 0xa278, 0xba5b, 0xbfd0, 0xad9f, 0xafe7, 0xbfdd, 0x1c5c, 0xb055, 0x3b51, 0x420c, 0xa911, 0x45b2, 0x43f3, 0xbf14, 0x403f, 0x3b14, 0x406f, 0x43c3, 0x41f5, 0x0da8, 0x4083, 0x09b3, 0xb20d, 0xaa12, 0xb022, 0x4693, 0xbf87, 0xba59, 0x165c, 0xb299, 0x19e1, 0x410a, 0x072b, 0xb236, 0x427d, 0x4310, 0xb2b1, 0x43c9, 0xaf02, 0x1ff3, 0xbf27, 0xadf3, 0xaec5, 0x42ee, 0x426e, 0xbfb6, 0x4153, 0x143e, 0x4354, 0x3bde, 0x2571, 0xaaa9, 0xbfb2, 0x401a, 0x4014, 0x186d, 0x1b1d, 0x1d49, 0xb0ed, 0xbfe2, 0x0f4b, 0xb28e, 0x4353, 0x2d51, 0xb219, 0x2806, 0x4614, 0x4046, 0x0727, 0x439d, 0xb2d1, 0xa936, 0xb21c, 0x2643, 0x4338, 0x4115, 0x2663, 0x1fab, 0xb234, 0x4484, 0x46ad, 0x1de8, 0xb291, 0x4379, 0x3751, 0x4672, 0xb23e, 0xbfa3, 0x411f, 0xb217, 0xb0af, 0xba5f, 0xb287, 0xba1a, 0x40b7, 0x428f, 0x4179, 0xbaf8, 0x42ba, 0xb01d, 0xb216, 0x432e, 0x411c, 0x405c, 0x10dd, 0x4358, 0x4277, 0x1b1d, 0x1afd, 0x442f, 0x408e, 0xbf1a, 0xb2d1, 0x20f4, 0x42e0, 0x1865, 0x4360, 0x419b, 0xad85, 0xbf47, 0x42dc, 0x1c28, 0x099a, 0xb021, 0x42c6, 0x3001, 0x4204, 0xb04c, 0x43cc, 0x4011, 0x4383, 0x42c0, 0x42b8, 0x426c, 0x431e, 0x4073, 0xb0de, 0xbf7d, 0xbadf, 0x1b97, 0x44dd, 0xba7a, 0x43a2, 0x2527, 0x1e82, 0x17ea, 0x1ae3, 0x1f1f, 0x40ad, 0xaf9c, 0x44f8, 0xa34e, 0x19ef, 0x3993, 0xbf9a, 0xb246, 0xba3a, 0x2ae0, 0x40b1, 0x230a, 0x41c9, 0x41d8, 0x4612, 0x4217, 0xb2af, 0x0e46, 0x469b, 0x06c2, 0xbfbd, 0xb2d8, 0xb20c, 0xb278, 0xa4ee, 0x4220, 0x2bee, 0xbf03, 0x0713, 0x46a4, 0xb296, 0x45c9, 0x381e, 0x26f9, 0x2596, 0xb20d, 0x1855, 0x31ca, 0xabe7, 0x1e1f, 0xbf95, 0x42ec, 0x4335, 0xb261, 0xba75, 0xba1e, 0xb2e6, 0xb277, 0x41ff, 0x408f, 0x1968, 0x401a, 0xb03f, 0x02b7, 0xb000, 0x3dd6, 0x4165, 0x42c7, 0x4159, 0xbfa8, 0x433e, 0x0a1e, 0x42bf, 0x145b, 0xbaf9, 0x4327, 0x4355, 0xafcf, 0x2661, 0x3e86, 0x415c, 0xa1d9, 0xbae1, 0x1dba, 0xa6de, 0x3bcb, 0xbfd0, 0x1fe8, 0x0997, 0x4268, 0x42d9, 0x432c, 0x3238, 0x1bbb, 0x4076, 0xbfc7, 0x0e40, 0x078c, 0x42ad, 0x2739, 0x3255, 0xbf88, 0x4096, 0x1d9a, 0xb228, 0xbfc1, 0x4025, 0x4021, 0xb21a, 0x41ac, 0x410a, 0x40bb, 0x1b99, 0x22c4, 0xaf4c, 0x21f4, 0x40fc, 0xb0d0, 0xbf18, 0x1ce1, 0xba1c, 0x1d77, 0xb08c, 0xb2ca, 0x160e, 0x1660, 0x0400, 0x41c4, 0x4038, 0x40d4, 0x19d5, 0x40de, 0x4216, 0xb0f3, 0x05c6, 0xba08, 0x2094, 0x18df, 0x43a2, 0x0d66, 0x42b6, 0xbf5d, 0x4088, 0x4150, 0x4207, 0x1fc0, 0x434d, 0xba10, 0x404c, 0x43da, 0xbfd4, 0xa24f, 0xba3a, 0xba01, 0x0721, 0xba0d, 0x41eb, 0xb2d4, 0xb2f9, 0x40d5, 0x423a, 0x430f, 0x41d1, 0x19f0, 0x179f, 0x42a6, 0x4325, 0x37cd, 0x1943, 0x43a7, 0xbf38, 0x3469, 0xbf55, 0xb024, 0x415e, 0xb24e, 0xb042, 0xb29f, 0xb257, 0x43d5, 0x421c, 0x017e, 0x33bd, 0x3636, 0x4102, 0x40b7, 0x4562, 0xb092, 0x42dd, 0xbac5, 0x4352, 0xbf64, 0x4608, 0xa1f5, 0x1810, 0xb080, 0xa0f4, 0x4373, 0x40bb, 0x43f3, 0x42bb, 0x4372, 0x0653, 0x41c3, 0x4205, 0xbafa, 0x0977, 0xbaff, 0xb2c9, 0x43e7, 0xba3e, 0x443d, 0x4390, 0xbf80, 0x409d, 0x42ae, 0x1d3a, 0xb0dd, 0xbfa7, 0xba14, 0x43c7, 0x4064, 0xab20, 0x41a3, 0xb22f, 0x41f3, 0x4338, 0x4088, 0xa77f, 0x3c19, 0x4212, 0x41a2, 0x407b, 0x0a0e, 0x41df, 0x1fba, 0xba60, 0x1432, 0x1fdf, 0x4082, 0x1b87, 0x2588, 0x430b, 0xaf0a, 0xbf44, 0xba6c, 0xbace, 0x16c6, 0x1fb1, 0xa6d9, 0x1b20, 0x4246, 0x2523, 0x00c0, 0x43b2, 0x4612, 0xb0a6, 0x4180, 0x46bb, 0x42da, 0x430f, 0xb2ac, 0xbafe, 0x4655, 0xb2d3, 0xba32, 0xbfb8, 0x4259, 0x1f8c, 0x40d1, 0x415f, 0x404d, 0x4418, 0x40db, 0x43d8, 0x08aa, 0x1e2b, 0x1ef5, 0x41fe, 0x4346, 0x4269, 0xadc3, 0xb2f5, 0x42ab, 0xb0c9, 0xb034, 0x28d6, 0x4276, 0xba16, 0x4294, 0xad99, 0x418f, 0x06b5, 0xbfba, 0x427c, 0x4542, 0xb2fa, 0xa6ac, 0x418d, 0x4564, 0xb04e, 0xbf80, 0x3973, 0x3f72, 0x1830, 0xbfd2, 0xacbf, 0xbaf1, 0x4546, 0x203c, 0x1a47, 0x28e1, 0xbf2e, 0xb235, 0x1eae, 0x4195, 0x1476, 0x43da, 0x4450, 0x428b, 0xb22d, 0xbfd0, 0x1805, 0xba04, 0x1a83, 0x297b, 0x403a, 0x1fca, 0xbaf8, 0x40b6, 0x43c5, 0x3560, 0x43f8, 0x416d, 0xba13, 0x4329, 0xa727, 0x159f, 0x4212, 0xbad7, 0x4085, 0xbfb5, 0x33d8, 0x4388, 0xb280, 0xba44, 0x41cd, 0x1f18, 0x40ae, 0x437a, 0xb081, 0x4340, 0x4163, 0x1a20, 0xbad1, 0xbf03, 0x4390, 0x41d0, 0x40c1, 0x1ff4, 0xb299, 0xbfd0, 0x1bc9, 0xbad6, 0x16cb, 0x4062, 0x1c0c, 0x41df, 0x401b, 0x40e3, 0x4103, 0xa549, 0xbfb0, 0x3b0b, 0x4405, 0x40ca, 0xb290, 0x4100, 0x433c, 0x424b, 0x2d35, 0x46eb, 0xbf70, 0x40ae, 0xbf9d, 0x425c, 0x41c5, 0x4168, 0x434a, 0xbfd6, 0x25f5, 0x43b9, 0x3278, 0x432d, 0x3711, 0x181a, 0x1b7e, 0xba70, 0x437d, 0xb2ed, 0x259f, 0x19f0, 0x1c30, 0x1b3b, 0x43df, 0x443a, 0xb29a, 0x4091, 0xad24, 0xbfaa, 0x419b, 0x25e8, 0x20d2, 0xb2f9, 0xba50, 0x42ef, 0x42b5, 0x368e, 0x3a76, 0xb240, 0x1e37, 0xbafb, 0x42ac, 0x1a73, 0x2848, 0xb0a9, 0x4656, 0x4138, 0xbad8, 0x4115, 0x42ba, 0x425b, 0x414f, 0x430f, 0xb2f1, 0x1ec6, 0xbf37, 0x4171, 0x41a4, 0x41bd, 0x1911, 0xb268, 0x44e9, 0xac0d, 0x3861, 0xb2cf, 0x425a, 0x0c98, 0x1799, 0x3eca, 0x1076, 0xba36, 0x35c9, 0x1a07, 0x4131, 0x4312, 0xbf14, 0x41df, 0x40fc, 0x1d3f, 0x424b, 0x4332, 0x4186, 0xbfc2, 0x42cb, 0x4202, 0xb202, 0xb2d1, 0x1331, 0x2071, 0xb2a9, 0x41ea, 0x4129, 0xab06, 0x4396, 0x41aa, 0x1d8b, 0xba52, 0xb23f, 0x07dd, 0xa183, 0x19f1, 0x05cf, 0x1809, 0x434c, 0xbf84, 0x4008, 0x4286, 0x4289, 0x0abd, 0x20ef, 0xb2ed, 0x1a86, 0x43f6, 0xbadc, 0x4257, 0x3cd6, 0xb207, 0x43ab, 0xbf90, 0xb2d2, 0x4208, 0x405f, 0x1bad, 0x4315, 0x0b32, 0xbf74, 0xbfd0, 0x3ed1, 0x4074, 0x423c, 0x40fd, 0x441c, 0xa2af, 0x2616, 0x2ca8, 0x11e7, 0x438b, 0x43d2, 0x046c, 0xbfbd, 0x18f0, 0x1c25, 0x42fc, 0x43c8, 0x03e7, 0x409d, 0x4277, 0x46f3, 0x4162, 0xbf14, 0xa46b, 0xaf5f, 0x16cb, 0x422b, 0xabe0, 0x0653, 0x0cb8, 0xb2ca, 0xb286, 0x1b6d, 0x4134, 0x215e, 0x437f, 0x4049, 0x41c9, 0x1b35, 0x4214, 0x4048, 0xbf42, 0x43ca, 0xb2ac, 0x43de, 0xa5f1, 0x42b0, 0xb075, 0x413f, 0xba3c, 0x41f2, 0x1d5b, 0x1b0f, 0x3cad, 0x4175, 0x43df, 0xbfd0, 0xb081, 0x0b73, 0x4330, 0xb079, 0x41ec, 0xb232, 0x4621, 0xb06e, 0x400f, 0xa68f, 0x2c56, 0x42f3, 0x1a96, 0xbfab, 0x418c, 0x43b6, 0x4360, 0x4310, 0x1012, 0x4315, 0x42c4, 0x1df4, 0x41ed, 0x2de9, 0x4277, 0x46c8, 0x411e, 0xbf9a, 0xba26, 0xbfd0, 0x4229, 0x1deb, 0x42b0, 0xb245, 0x40bc, 0x426e, 0x42af, 0xb2a0, 0x427c, 0x4262, 0x43ae, 0x4648, 0x4346, 0x40a4, 0x4135, 0xba30, 0x1a4b, 0x437f, 0x402b, 0x4064, 0x2f49, 0x4699, 0xa05f, 0xbaeb, 0x08a5, 0xbf32, 0xba5a, 0x19b5, 0x1807, 0x43d6, 0x4243, 0x4548, 0x015d, 0x09fa, 0x4161, 0x40c3, 0x1528, 0x22e7, 0x43e1, 0x40c1, 0x42f5, 0x4219, 0x1719, 0x4391, 0xba42, 0x1aa8, 0x42de, 0xba05, 0x40e1, 0x42b8, 0x46e2, 0xb07e, 0xbf16, 0x435c, 0x4255, 0x42a5, 0x414d, 0x0353, 0x423e, 0xb0ac, 0xaf6a, 0xbf6d, 0x423a, 0x4303, 0x42b4, 0x42ff, 0xbf09, 0x4043, 0x4052, 0x42cb, 0x439d, 0x3cae, 0x1752, 0xbff0, 0x4120, 0x1b16, 0x42cd, 0xb0bd, 0x4109, 0xbf2b, 0xba63, 0x415b, 0x1f93, 0xbaff, 0xb25c, 0x4327, 0xb0ea, 0x456f, 0xb280, 0x41da, 0x2a24, 0x184d, 0x420f, 0x400b, 0x42d8, 0xb200, 0xb270, 0xb050, 0xba5b, 0xb20b, 0x1eb7, 0x432e, 0xbfd1, 0x4367, 0x2e30, 0xb225, 0x43d0, 0x4211, 0xb2e7, 0x40b4, 0x41b3, 0xba16, 0x409e, 0x1452, 0x28cd, 0x4342, 0x335d, 0x25d5, 0x1fcb, 0x0ab1, 0x4179, 0x40a2, 0xb213, 0x4113, 0x298a, 0x415c, 0x4209, 0x42dd, 0x41c1, 0xb0f2, 0x423a, 0x4021, 0xbf8c, 0x0529, 0xa789, 0xbae0, 0xb217, 0x4195, 0x42dd, 0x424a, 0x40dc, 0x429b, 0x18b2, 0xb228, 0x4288, 0x171a, 0x438a, 0xb2a5, 0x4453, 0xb04f, 0x0047, 0xb27d, 0xbfbf, 0x3cd5, 0xb294, 0x1550, 0x402f, 0xb027, 0x1e0f, 0x43b7, 0x42bb, 0xbf9c, 0xa295, 0x43c2, 0xb2b3, 0xaec7, 0x4168, 0x43bd, 0xb09b, 0xbae4, 0x40dc, 0x4351, 0x4266, 0x0b06, 0xb24c, 0x18ee, 0x41b7, 0xbf7c, 0x43fc, 0x420c, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x1c45d6ba, 0x53e984f2, 0x3a0df554, 0xdcee8efa, 0x8d61a15b, 0x42a11c1a, 0xe46650af, 0x77ca9117, 0x8878f892, 0x1c01e917, 0x9521508e, 0x1cb29f47, 0xca2a8d7c, 0xe5798906, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0x0000007d, 0x00000000, 0x00000000, 0x00000000, 0xffffffa8, 0xffffffa8, 0xffffffa8, 0x00000057, 0x017b7293, 0x00000000, 0xca2a8d7b, 0x00000000, 0xca2a8d7b, 0xe5798e79, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x1ed2, 0xb283, 0x434a, 0x3937, 0x4179, 0x4253, 0x4017, 0x023f, 0x42bc, 0x408d, 0xb22a, 0x4268, 0xba5b, 0x1a31, 0x40a2, 0x1dd9, 0x2161, 0x4449, 0x4000, 0x402e, 0x41ce, 0xbf83, 0xba1e, 0x44b0, 0xba18, 0x425e, 0xa764, 0xbfc0, 0x406f, 0x40bd, 0xb0b7, 0xa99c, 0x4005, 0xa0c2, 0x41a4, 0x3ff8, 0x402c, 0x036d, 0xbf57, 0x3e68, 0x4153, 0xb0e0, 0x4689, 0x42e0, 0x406f, 0x40c8, 0xb0b1, 0x4593, 0x40f8, 0xb2d1, 0xa877, 0x4379, 0x434e, 0xba1e, 0xbf71, 0xba71, 0xb2b1, 0xb0c1, 0xb232, 0x41fc, 0x463f, 0x4069, 0xb25b, 0x4241, 0xb2c0, 0x405a, 0x4048, 0xba32, 0x4258, 0x41ef, 0xbf78, 0x0754, 0xb277, 0xbf60, 0x4089, 0xba4a, 0x4074, 0xb252, 0xb2cb, 0x4134, 0x434c, 0x43e1, 0x1e9c, 0x1c02, 0x40a7, 0xb24e, 0x1e02, 0xbf17, 0x3511, 0xb2a3, 0xba19, 0xaccd, 0x4091, 0x1fc1, 0xb057, 0x43b8, 0xb2c8, 0x4180, 0x377e, 0x43d3, 0x09e2, 0x4266, 0xb26c, 0xbf1f, 0x4260, 0xb24e, 0xb24a, 0xb2ac, 0xbfd0, 0xba7a, 0x45b2, 0x4038, 0x418f, 0x15f9, 0xba63, 0xba2d, 0x42f3, 0x403e, 0x4038, 0x41c9, 0x4152, 0x2ee7, 0x4259, 0x0226, 0xbf41, 0x21d2, 0x4430, 0x4007, 0x17ed, 0xbfbd, 0x4058, 0x3e16, 0x407a, 0x40e3, 0x41a0, 0x43d8, 0x404a, 0x40f2, 0x1605, 0x42a3, 0x4631, 0x44f0, 0xbfe0, 0xa21c, 0x290b, 0xaade, 0x40aa, 0x43ae, 0x4126, 0x29c9, 0x420d, 0xbfa4, 0xba6b, 0x41cc, 0x2a49, 0xa316, 0x4308, 0xb04c, 0x4261, 0x0a3b, 0x27f8, 0x422b, 0x02f7, 0x401f, 0x4084, 0xa4c4, 0x1d89, 0xaf46, 0x4159, 0xbad0, 0xb287, 0x41f4, 0x0ae8, 0x423b, 0x3f2b, 0xb210, 0x41bf, 0xbfa5, 0x4333, 0x447e, 0x4403, 0xb24d, 0xbf80, 0x4201, 0x40ff, 0xb2da, 0x41df, 0xb2fa, 0x4257, 0x4366, 0x43e5, 0xbfba, 0x431d, 0x4558, 0xb296, 0xba51, 0xbacf, 0x4313, 0xbadd, 0x40e6, 0x25fe, 0x4223, 0xb2e4, 0x400a, 0x362d, 0x40bd, 0x1e5c, 0x4010, 0xa3e8, 0x4695, 0x22de, 0x3070, 0x1c9a, 0x2d98, 0xb0b1, 0x1f41, 0xb2e6, 0x415c, 0xbac2, 0xbf69, 0x3816, 0x1f3a, 0x1f46, 0xb06c, 0x10f7, 0x1ee5, 0x400e, 0xbf08, 0x2a02, 0x179e, 0xadb5, 0x42f2, 0x410d, 0x4258, 0xb24d, 0x4424, 0xbaf2, 0x4143, 0x40e8, 0x4339, 0xb231, 0x437a, 0xb2f1, 0x42fd, 0x1dff, 0xbf91, 0x43ed, 0x468c, 0x427d, 0xb211, 0x4388, 0x4101, 0xba6d, 0x3708, 0x19fc, 0xb2fe, 0x410f, 0x1fdc, 0x2d26, 0x0b81, 0xb23e, 0x46b2, 0x4194, 0xacb8, 0x40ec, 0x3012, 0xbfb0, 0x45b1, 0xb2b7, 0x1ea0, 0xb246, 0x4007, 0x23e5, 0xbf92, 0x426f, 0x1eab, 0x4371, 0x0c6a, 0xb21d, 0x4442, 0x1954, 0x1957, 0xba05, 0xb27f, 0xb00c, 0x437f, 0xb022, 0x4223, 0x1d0e, 0xbf97, 0xbaf4, 0x1c45, 0xba0e, 0x469d, 0x4049, 0x2a26, 0xb2f0, 0x4272, 0x441a, 0x465b, 0xb2b7, 0x1885, 0x45ca, 0x43b9, 0x1b66, 0x42fe, 0x467e, 0x3529, 0x36da, 0x43fb, 0x1fea, 0x4249, 0x281b, 0xbf81, 0x3ce3, 0xb25a, 0x4119, 0x41d7, 0x1ba6, 0x426c, 0x41ad, 0x4041, 0x41af, 0x44b2, 0x41c6, 0xbf86, 0x41b6, 0xac9f, 0x2b28, 0x4001, 0xba03, 0xbfb0, 0x40b8, 0x3572, 0xba6e, 0x19d2, 0xbf22, 0xacf7, 0xab27, 0x2775, 0x40d1, 0x438f, 0x4214, 0x17c4, 0x40f8, 0x42ce, 0xa942, 0x3854, 0x44cd, 0x1cf3, 0xbfb8, 0x1a6f, 0x1817, 0x2f34, 0x42f3, 0xaa11, 0xba6c, 0x4560, 0x2208, 0x43ac, 0x45bb, 0x45f5, 0x09eb, 0xbf06, 0xb251, 0x1def, 0xbff0, 0x0c9f, 0x40e4, 0x4453, 0x1aab, 0x4365, 0xb2e1, 0x46a4, 0x4000, 0x12f4, 0x3204, 0x4360, 0x4350, 0x4185, 0x41ba, 0x3fca, 0x31d6, 0xa211, 0x42eb, 0x42a0, 0xadb7, 0xbaec, 0x41d4, 0x409c, 0xbaf5, 0xbfa1, 0x15b0, 0x4234, 0x4345, 0x4215, 0x43fc, 0xba60, 0xb034, 0x25ec, 0x4108, 0xb250, 0x2343, 0x44aa, 0xb2ed, 0x4234, 0x46ba, 0x420a, 0x0fcf, 0xbf2e, 0xb0aa, 0xa5a5, 0xb0e2, 0x41d3, 0xb292, 0x1d9c, 0xbf75, 0x190f, 0x2337, 0xb245, 0x080c, 0xb2cf, 0xa08b, 0x40ce, 0x40c3, 0x4121, 0x192b, 0x327a, 0x4341, 0x0fb8, 0xbfe0, 0xb2a8, 0x26ea, 0x3b79, 0x41eb, 0x18ea, 0xb28a, 0x3412, 0xbff0, 0xbf2d, 0x43a0, 0xaba3, 0x1b78, 0x43f6, 0x406c, 0x43ad, 0x3e65, 0xbac1, 0x45ae, 0x3dce, 0x42da, 0x43c0, 0x42d3, 0x412a, 0xbfc7, 0x0784, 0x41db, 0xb288, 0x209d, 0x117f, 0x404f, 0x2dca, 0x2fa2, 0x0b73, 0x414d, 0x406e, 0xbad2, 0x431b, 0xbafd, 0x466b, 0x429e, 0x4198, 0xb2cb, 0xb2e6, 0xbf9e, 0x404a, 0xb2ba, 0xb096, 0xbac1, 0xba3b, 0x037e, 0x40fa, 0xb26c, 0x416c, 0xb2cb, 0xb014, 0x14dc, 0x412c, 0x2d78, 0x1d2e, 0x199d, 0xb2e8, 0x2adc, 0x426c, 0x1e46, 0x4305, 0x1c4a, 0x43a9, 0x1c02, 0x43e3, 0x466d, 0xbf6a, 0x3376, 0x11a5, 0x37df, 0x416c, 0x1bb3, 0xb27b, 0x4385, 0x0a41, 0x1dd5, 0xb2c3, 0x409e, 0x4112, 0x1cb0, 0x428d, 0x0dfe, 0x4391, 0xbf90, 0xba2b, 0xb2a9, 0x4159, 0x426e, 0x2534, 0x4260, 0xbafc, 0xb294, 0x4332, 0x3fed, 0xbfa9, 0x42a7, 0x422b, 0x1f0f, 0xbaf5, 0x21f3, 0xba2f, 0x15fb, 0xb2fa, 0xb025, 0x4024, 0x4014, 0x3c14, 0xbfac, 0x3734, 0xba6e, 0x435b, 0xba12, 0x2012, 0x4058, 0x423e, 0xabd2, 0x3645, 0x40c3, 0x20d0, 0x1d9d, 0x43c2, 0x41b3, 0x4693, 0x40b4, 0x2296, 0x409b, 0xb291, 0x437a, 0x424b, 0xbaf7, 0x4104, 0x437f, 0xbf96, 0x4102, 0x1886, 0x0fb9, 0x426e, 0x40b8, 0xb20e, 0x431d, 0x4291, 0x4291, 0x0c05, 0xb2d4, 0x199c, 0x428f, 0x44db, 0x413d, 0xbf31, 0xb289, 0x43f0, 0x332d, 0x4569, 0x4299, 0xb014, 0xba7d, 0xb0f3, 0x402e, 0x4045, 0x06a4, 0x43ca, 0x4094, 0x31bd, 0x4081, 0x427b, 0xb20d, 0xb2e6, 0x4632, 0x162d, 0x41c4, 0x2895, 0x40e7, 0x4653, 0xa661, 0xbf66, 0xbf90, 0x42b2, 0xaab5, 0xb090, 0x1583, 0x4639, 0xb28d, 0xb270, 0x40de, 0xbf82, 0xb222, 0x4270, 0x435a, 0x0de4, 0xb092, 0x40a8, 0x429e, 0x3997, 0x414e, 0x4399, 0x21a7, 0xb0e8, 0x4137, 0x46c0, 0x2806, 0x4172, 0x421d, 0x4322, 0x412e, 0x423d, 0x4127, 0x135d, 0x40da, 0x422f, 0x1a75, 0x40f4, 0xbaf0, 0xbf18, 0xba57, 0x0d36, 0xb2bb, 0x0ca6, 0x42fd, 0x425b, 0xbfe2, 0x4373, 0xbfc0, 0xa74d, 0xbfb6, 0xb262, 0xba79, 0x1e98, 0xa253, 0x0db2, 0x4160, 0x29eb, 0x0771, 0x4360, 0x1f61, 0xb2ac, 0x4196, 0x433c, 0x412b, 0x4054, 0x4197, 0xb04b, 0x240f, 0xb2fb, 0x4277, 0x4292, 0xb0a4, 0x1b9f, 0x4007, 0xb01d, 0xbf3d, 0xbfe0, 0x45e0, 0x4297, 0x420b, 0x4659, 0x4051, 0x4012, 0xbf78, 0x468a, 0x4083, 0x2755, 0x42be, 0x4699, 0x40fc, 0xb0e4, 0xaead, 0x458e, 0xa47e, 0x43cd, 0xac1b, 0x4401, 0x4182, 0x45f1, 0xbad0, 0x277d, 0x4580, 0x43f0, 0x1e04, 0x4001, 0x41f1, 0x42a9, 0xbf31, 0x2018, 0x435d, 0xb097, 0x0bdd, 0x449c, 0x4553, 0x1052, 0x41e1, 0x42ef, 0x43a3, 0x04ac, 0xae49, 0x4346, 0x1de8, 0x1ce6, 0x44a3, 0x432c, 0x4646, 0xb240, 0xb29f, 0x1d48, 0x436d, 0xbf36, 0x06c9, 0xbfa0, 0xb231, 0xbf52, 0x1d57, 0x40df, 0x40de, 0xb23c, 0x39bb, 0x2829, 0x4313, 0xa2f6, 0xb003, 0x446e, 0x1c7c, 0x3a4f, 0x007b, 0x427e, 0x083a, 0x1d84, 0xb088, 0x4176, 0x433b, 0x439c, 0xbf05, 0x44c2, 0x42ae, 0x4211, 0xa9ac, 0x1a23, 0xb20a, 0x46e4, 0xa4cc, 0xba2b, 0x4655, 0x2b20, 0x4070, 0xbfc0, 0x40ff, 0x4071, 0x1e59, 0x43a5, 0xbfdf, 0x1c22, 0x4315, 0x462d, 0x4183, 0x4560, 0x43b1, 0x1dc0, 0x4089, 0x17b2, 0x416c, 0x4345, 0x4302, 0xbf1d, 0x42a9, 0xbaf4, 0x40ea, 0x418d, 0xbad4, 0x1f28, 0x4035, 0x4255, 0x2404, 0x43a6, 0x4167, 0xba28, 0x2c13, 0x1a6a, 0x1bb3, 0x4186, 0x430e, 0x4556, 0x1a01, 0xbacc, 0xba0c, 0x410c, 0xb2e3, 0xbf1a, 0x1fc7, 0x4229, 0x462a, 0x4295, 0x1a63, 0xbf00, 0xbf70, 0xb26c, 0x1360, 0xbf95, 0x4151, 0x1132, 0x432b, 0x42d3, 0x0fb4, 0xba25, 0x1505, 0xbfde, 0x4338, 0x41dc, 0x4223, 0x3524, 0x422f, 0x4104, 0x414f, 0x3667, 0x1993, 0x4174, 0x40c2, 0xba13, 0xba2b, 0x289e, 0xba20, 0x406e, 0x20ae, 0xbacb, 0xa364, 0x40d6, 0xbfb8, 0x404f, 0x4398, 0xae33, 0x4023, 0x4333, 0x40c9, 0x43e8, 0x1b35, 0xb0c3, 0xbafb, 0x08c2, 0x4139, 0x42b3, 0xb089, 0xb242, 0x4129, 0xb008, 0xbf47, 0x2a1c, 0xb092, 0x2c58, 0x43c8, 0x41ca, 0x40c9, 0x32e6, 0x4182, 0x1769, 0x363c, 0x1639, 0x4231, 0xb286, 0x4075, 0xbf7f, 0x424c, 0x0094, 0x1c3d, 0x43a3, 0x40b6, 0x41ca, 0x435a, 0x3614, 0x378c, 0xb213, 0xbf85, 0x1839, 0xbae4, 0x1dd2, 0x414c, 0x4040, 0x41d3, 0x4040, 0xb254, 0x4110, 0x1e0a, 0x42e6, 0x095a, 0x0a7b, 0xbf46, 0x1375, 0xba1e, 0x4224, 0x41ee, 0xa734, 0x40dd, 0x42ee, 0x2906, 0x397f, 0xba5b, 0x41eb, 0xa422, 0x3dd1, 0x1dfc, 0x4444, 0x1c7b, 0x4068, 0x4258, 0x1350, 0xb2a6, 0x4155, 0x4213, 0x43ab, 0xbf37, 0xa368, 0x0c7a, 0x4175, 0x1cf7, 0x4260, 0x04fe, 0x18dc, 0xba1c, 0xa450, 0x2a7c, 0x351b, 0x416f, 0xbf55, 0x3b8b, 0x401f, 0x4282, 0xa41b, 0x41e1, 0xa299, 0xa6f3, 0xaca9, 0x1c37, 0x0145, 0x4285, 0xb269, 0x438b, 0x138c, 0x1eb7, 0x1d6e, 0xa51c, 0xb2d2, 0xae4b, 0xb2fc, 0xac6e, 0x4176, 0x1937, 0x42f2, 0x4293, 0xbf48, 0x4280, 0x4685, 0x421f, 0x4394, 0x050c, 0xbfc4, 0x372a, 0x401d, 0xbae7, 0x42f6, 0x40b6, 0xa1e8, 0x44aa, 0x3def, 0x429c, 0x3117, 0x1dba, 0x1a58, 0x415a, 0xa67a, 0x43b3, 0xbf21, 0xbf90, 0xa973, 0xba48, 0xba58, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x2222d05c, 0xcfc689d4, 0xc2140172, 0x47506221, 0xe5f77550, 0x38359c85, 0x4e95a963, 0xe20bac00, 0x9f1922b1, 0x5e07b7dc, 0x32b31e2b, 0x9b89a1e5, 0x70e8b6e8, 0xae4936fc, 0x00000000, 0x100001f0 }, + FinalRegs = new uint[] { 0xffffe4fa, 0x00001b77, 0x00000077, 0x00000041, 0xf8000000, 0x00001719, 0x000019b8, 0x00000000, 0x9f1922b1, 0x00000057, 0x00001666, 0xfffffe5e, 0x00007157, 0x60e6c544, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x439f, 0x404c, 0x454a, 0x41ed, 0xbf7a, 0x1535, 0x428d, 0x1c71, 0x4125, 0xb281, 0x40a4, 0xb25a, 0x40f7, 0xaab2, 0xb256, 0xb236, 0xb08c, 0x4180, 0x1249, 0x3e60, 0x235c, 0x40f0, 0xba68, 0xbaf7, 0x45e3, 0x4314, 0x411a, 0xb2f1, 0xbf35, 0x4689, 0xb26e, 0xba71, 0x4072, 0x1cd9, 0x4451, 0x4343, 0x4275, 0x1acd, 0x3026, 0x43af, 0x4279, 0xb2fb, 0xb24f, 0x41c1, 0x4101, 0x40d8, 0x4171, 0x436e, 0xb092, 0xbf9b, 0x4190, 0x40ab, 0x1bbc, 0x1ad7, 0x45a3, 0x412f, 0x1374, 0x45c8, 0x1e94, 0x40c4, 0xb0d8, 0x41c8, 0x4044, 0x1d6f, 0x4134, 0x45d8, 0x426b, 0x404c, 0x1986, 0x401c, 0xb2aa, 0x4375, 0x423e, 0x1ac8, 0xbfb3, 0xb296, 0xba27, 0x3e43, 0x4315, 0xbfb0, 0x1914, 0x43f8, 0x42a1, 0x426a, 0x0cfa, 0x4027, 0x4270, 0xadfb, 0x45dc, 0x309c, 0x407d, 0xa010, 0x1d78, 0x34b3, 0x1b29, 0xb2c0, 0x463a, 0x196a, 0x41b1, 0x42c5, 0x40a5, 0x454d, 0xb28f, 0xbf37, 0xba22, 0x1c18, 0x2832, 0x41fe, 0x1a1b, 0x1dd0, 0x4634, 0x4058, 0x2da1, 0x427b, 0xb2c7, 0x4280, 0x1e11, 0x40a9, 0xba2d, 0xba19, 0xbf44, 0xba11, 0x4068, 0x180e, 0x4178, 0x41ee, 0x4651, 0x407e, 0x4353, 0x4245, 0xbae4, 0xa4d0, 0x0dd5, 0xb27b, 0x1be0, 0x4062, 0xb220, 0x4352, 0x40e3, 0x41c9, 0xb21f, 0xbf00, 0xbf03, 0x29da, 0x40c0, 0x4179, 0x1f95, 0x187d, 0x43d8, 0x1ad8, 0xb223, 0x4648, 0x3fb2, 0x2ec5, 0xba50, 0x42c2, 0x3b0b, 0x1fbf, 0xb240, 0x41e0, 0x3a20, 0x032c, 0xb21a, 0xa161, 0x42bb, 0xbf26, 0xbf80, 0x446c, 0x4305, 0xb068, 0xba5f, 0x130a, 0x2736, 0xabf8, 0x1967, 0x2038, 0xb260, 0xba40, 0x4241, 0xaf27, 0x4381, 0x40af, 0x438c, 0x437c, 0x4672, 0xbae3, 0x3e4c, 0xb228, 0x1cc4, 0x432e, 0xbf3a, 0x1c2b, 0x1678, 0xa23f, 0x1dd4, 0x42a9, 0xbff0, 0x397e, 0x42a1, 0xbf54, 0x41a6, 0x4124, 0xb2f5, 0x1d50, 0x42a9, 0xba79, 0xba33, 0xa80d, 0xbf60, 0x4329, 0x4161, 0x3a12, 0x40ed, 0x4562, 0x4303, 0x1853, 0xbf6b, 0x4379, 0x4307, 0x436e, 0x41b0, 0xba44, 0x4178, 0xbf90, 0xb2db, 0xa99c, 0x4671, 0x46a9, 0x44b3, 0x40fc, 0xab9e, 0xb21d, 0x42a7, 0x40f3, 0xb2cd, 0x24b3, 0x41b7, 0x1db8, 0xbf4c, 0x4207, 0xba4b, 0x4227, 0xb064, 0x405a, 0x416a, 0x1222, 0xb0e1, 0x0ff4, 0xba13, 0xbf8a, 0x423f, 0x440b, 0x4007, 0xbadf, 0xb25b, 0xae38, 0x229d, 0xbfcb, 0x3f66, 0x259e, 0xb092, 0x4248, 0x422f, 0x18b3, 0xbfd2, 0x147a, 0x43ce, 0xac74, 0xa9c0, 0x454c, 0xba1f, 0x0865, 0x40bf, 0xb291, 0xb241, 0x4311, 0x1e25, 0xb2e5, 0x3038, 0x4647, 0x4254, 0x4007, 0x4276, 0x42cb, 0x4024, 0x1ee1, 0x206a, 0x0c99, 0x3aea, 0xbfc8, 0xbf60, 0x1eea, 0x410e, 0x4365, 0x03ee, 0x1d17, 0xb2e5, 0x4244, 0x0fc2, 0x1bf2, 0x1b74, 0xbac0, 0x1770, 0xbfcd, 0x02d1, 0x4251, 0x4200, 0xb2c5, 0xbafb, 0x43b8, 0xbf87, 0x425a, 0xbfd0, 0x18bb, 0x4186, 0xb0de, 0xba16, 0x199f, 0x444c, 0x4265, 0xa487, 0x41cf, 0x40e4, 0xb093, 0xbfa0, 0x4164, 0x0082, 0x433b, 0x40a3, 0x1957, 0x0653, 0xba10, 0x1011, 0x43cc, 0xb08c, 0xb24a, 0x2f88, 0xbfe4, 0xb291, 0x4251, 0xb0ec, 0xba67, 0x3fec, 0x401c, 0x2df3, 0xb225, 0x1ed0, 0x22cf, 0x41c4, 0xbfdc, 0xaa7d, 0x1e25, 0x40a5, 0x43a0, 0x41bf, 0xba39, 0x40cc, 0x4117, 0x46b9, 0x407b, 0xb0d5, 0x1cfb, 0x43ed, 0xb2ce, 0x4229, 0x2a4a, 0x1317, 0x41f3, 0x1a08, 0x1aae, 0x1737, 0xbf8a, 0x1ee8, 0x430e, 0x401b, 0x419c, 0x4121, 0xba1d, 0x109a, 0x18d9, 0x1dc3, 0x4331, 0xbac3, 0x00f7, 0x4178, 0x41c8, 0xa2e7, 0xb2be, 0x18bc, 0xb288, 0x1527, 0x43e5, 0x1c45, 0x2f37, 0x423d, 0xba43, 0x4238, 0xbf75, 0x22e5, 0xbfb0, 0x4073, 0x34ad, 0xa802, 0xb084, 0x4154, 0x4333, 0xa7e3, 0x4170, 0x43d9, 0xbfa0, 0x43c6, 0x43d0, 0xb292, 0xa2aa, 0x42e5, 0xbace, 0x42a1, 0x4361, 0x42f9, 0xbf4c, 0x423e, 0x0e80, 0xb233, 0xb2c7, 0xa44f, 0x1a00, 0xbf00, 0x1d47, 0xb265, 0xb203, 0x4330, 0x4268, 0x40c9, 0xb299, 0xba4d, 0xb23a, 0x2d3b, 0xba5e, 0xbf00, 0x43cb, 0xbf28, 0x1595, 0x43e5, 0x0dd2, 0xb287, 0x429c, 0x4683, 0xbfe0, 0x410c, 0x4363, 0x117d, 0x0706, 0x377b, 0xbfa4, 0xb2e7, 0x42e5, 0x45e1, 0x441b, 0x42bf, 0xb08c, 0x427e, 0xb05a, 0x09e5, 0xbf69, 0x438c, 0xbafe, 0x45f6, 0xaad2, 0x0016, 0xbf0b, 0x419d, 0x4024, 0x4146, 0xb0b5, 0x461d, 0x41f8, 0xb044, 0x1cc0, 0xbae5, 0xb086, 0x44cc, 0x420f, 0xb0b0, 0x4679, 0x4009, 0x1a55, 0x404c, 0x41cb, 0x4035, 0x4313, 0x4085, 0x404f, 0x000e, 0x1544, 0xada3, 0xbf09, 0x2288, 0xb215, 0x40ff, 0x426a, 0x40a6, 0xb26e, 0x305e, 0xb206, 0x3b04, 0xa30a, 0xba3a, 0x459e, 0xba24, 0x418b, 0x40ad, 0x440a, 0x435d, 0xb262, 0x42ad, 0xb0d9, 0xb08c, 0xbf5c, 0x4351, 0xb2d0, 0xbf12, 0x1b96, 0xb27a, 0x4154, 0xa009, 0x1c94, 0xa32e, 0x1c08, 0x38a7, 0x43af, 0x418b, 0x43c7, 0x296c, 0x273c, 0x4276, 0x25d3, 0x42c3, 0xba0c, 0xb284, 0xbf19, 0xb2ea, 0xba0e, 0x4069, 0xb003, 0x469c, 0xb0ee, 0xb219, 0x43cb, 0x424b, 0x41f6, 0x3973, 0xb271, 0x401c, 0x23a6, 0x40ea, 0xba16, 0x194a, 0xa210, 0x1c5b, 0xb07a, 0xbfa1, 0x4271, 0x190b, 0xba0c, 0x0783, 0xa0dc, 0x46d0, 0xbf54, 0x42fc, 0x0ea4, 0x1cde, 0x0a0a, 0x4115, 0xb2b7, 0xb2fa, 0xbf2f, 0x436e, 0x4374, 0xba73, 0x43cd, 0x4460, 0xad56, 0xb2cd, 0x4157, 0x40c1, 0x1a23, 0x4011, 0x42de, 0x42ab, 0x41f5, 0x4299, 0x43d1, 0x36ef, 0x4135, 0xade3, 0xbfcc, 0x1d9e, 0x425d, 0x43d0, 0xb26e, 0xb26d, 0xb0fe, 0xbaf7, 0x4106, 0x43d0, 0x4311, 0xbfdc, 0x2ac1, 0xb048, 0x434f, 0xb2b4, 0x12bc, 0x1ff4, 0x1aa2, 0xbad4, 0x428d, 0xb28c, 0x44e3, 0x4323, 0x407e, 0x350d, 0x4600, 0x4304, 0x40b5, 0x4253, 0xb2d9, 0xbfd7, 0xb062, 0xb0bd, 0x4567, 0x465d, 0xba1f, 0x0580, 0x46da, 0xb073, 0x3249, 0xb2fc, 0x41a4, 0x4235, 0x2daa, 0xb097, 0xbf5a, 0x41aa, 0x44c3, 0x42c0, 0x0536, 0xb239, 0x42c5, 0xbf6e, 0x19da, 0x296c, 0x324a, 0x395f, 0x40c6, 0x40fe, 0x4023, 0x00f5, 0x281c, 0x2646, 0x3352, 0xb2b7, 0xb2fb, 0xb27b, 0x44a0, 0x41ee, 0xbf81, 0xa1c3, 0x43e2, 0x4047, 0x43c2, 0xba7c, 0x2eae, 0xb281, 0x1807, 0x23c0, 0x1b92, 0x4116, 0xa4a6, 0x43f3, 0x4333, 0x1ae6, 0x25ec, 0x373d, 0xbfc3, 0xafa6, 0x409b, 0x1ffa, 0x183a, 0x46d5, 0x42e4, 0x418e, 0x422e, 0x43ad, 0xad75, 0x18e8, 0x4087, 0x0fb4, 0x0dc6, 0x1913, 0xabed, 0x4331, 0x40fa, 0xbf95, 0x18ab, 0x1aa0, 0x33e6, 0x4354, 0x4338, 0xb2cf, 0x4683, 0x4289, 0x42a1, 0x1ed3, 0x43c5, 0x434e, 0xb2bc, 0xa1bd, 0x4098, 0x418a, 0x25fe, 0x4348, 0x27b1, 0xa5ce, 0x121a, 0x4229, 0xbfa4, 0xb2d8, 0xb290, 0xbfdd, 0xa03c, 0x4217, 0x41b7, 0x41c0, 0xb0e8, 0x19ee, 0x41ea, 0x4294, 0x418d, 0x4075, 0xb04c, 0xbfce, 0xb083, 0x3289, 0x40a7, 0x429d, 0x4277, 0x454a, 0x43a4, 0x1f9a, 0xb2cf, 0x40a6, 0x42b7, 0xb277, 0x27de, 0x43cd, 0xba20, 0xb255, 0x4301, 0x43de, 0x41f0, 0xba42, 0x40b3, 0x05ae, 0xbafb, 0xbaf1, 0x443b, 0x4022, 0xbf68, 0xb286, 0xb2f3, 0xac85, 0x4129, 0x435d, 0x4344, 0x42c3, 0x41d8, 0xb247, 0xbf65, 0x42b9, 0x0ff6, 0x412c, 0xb2e8, 0x4406, 0x42e1, 0x3032, 0x416c, 0xb298, 0xab81, 0xb090, 0x434d, 0xbf87, 0x1cb0, 0x404c, 0xaf0e, 0x33b1, 0x3a35, 0x1c47, 0x43e5, 0xbacc, 0x323d, 0x336a, 0x4125, 0xb062, 0xba38, 0x429b, 0x4173, 0xbf95, 0x40bd, 0x164b, 0xb232, 0xb292, 0x4283, 0xa0d8, 0xb0a5, 0x197f, 0x43d2, 0x1b4f, 0x092b, 0x40d5, 0x1abc, 0x468d, 0xb0ac, 0xb289, 0xb231, 0x1f75, 0xba2c, 0x3a4e, 0x429d, 0x3a08, 0x414a, 0x400f, 0xbfc5, 0x42db, 0x4041, 0x4204, 0xb225, 0x022c, 0x41a9, 0x1def, 0x1cf5, 0x4217, 0x4047, 0x0cb3, 0x3896, 0x4383, 0x28ec, 0x4036, 0x434c, 0x424d, 0x392c, 0x0f8f, 0x2d25, 0x1ca0, 0x40d1, 0x4273, 0x4365, 0x41ce, 0xbf27, 0xb22d, 0x1762, 0x430b, 0xb23a, 0x419f, 0x40f7, 0x421c, 0x44f9, 0x41fa, 0x43d0, 0x428a, 0xb010, 0xb0b8, 0x402a, 0x40ba, 0xbf65, 0x3a3b, 0x4205, 0x41d3, 0xb2df, 0xb019, 0x422b, 0x4207, 0xba0a, 0x430c, 0x18fd, 0xbaf8, 0x4162, 0x400e, 0x4099, 0x40bd, 0x268f, 0xbaee, 0x42ed, 0x1a55, 0x42a5, 0x19bf, 0xb229, 0xb2de, 0x4169, 0xba79, 0x0f16, 0xb2d5, 0x4193, 0xbf2f, 0x0861, 0x4315, 0x05f0, 0xba76, 0xb2e6, 0xbf75, 0x0d63, 0x4580, 0x41dc, 0x4196, 0xbad8, 0x042b, 0x1a1c, 0x1e88, 0xba71, 0x19b1, 0x08a3, 0x314e, 0xba39, 0x40f9, 0x23e3, 0xb0e9, 0x11cb, 0x41b8, 0xbf1c, 0x1a4f, 0xa122, 0x416d, 0xb045, 0xb05f, 0x456c, 0x2fbd, 0x1eae, 0x43ef, 0x1264, 0x41be, 0x2531, 0x4240, 0xbf8c, 0x40fa, 0xb2ef, 0xaa96, 0x4398, 0x40fe, 0xb2e5, 0x4331, 0x419d, 0xba00, 0x4618, 0x4219, 0x408f, 0x438c, 0xb2a7, 0x41f6, 0xb272, 0xab3a, 0x1bd0, 0x2ece, 0x40ff, 0x1f10, 0xbf34, 0x2253, 0x40a3, 0x426e, 0x4206, 0x4262, 0x4354, 0x2793, 0xae78, 0x427a, 0x4370, 0x1911, 0x387b, 0x46e4, 0x18dc, 0x4259, 0x4013, 0xad55, 0x443c, 0x4231, 0xb015, 0x42d5, 0xa0d6, 0x41b8, 0xbf92, 0x4689, 0x4305, 0x3f97, 0x4356, 0xba74, 0x40a2, 0x43b8, 0x46bc, 0x4002, 0x0f62, 0xbacf, 0xbadb, 0x4562, 0xbf3c, 0xbfd0, 0x1eb6, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xb6e12d7d, 0x2bc8f2a0, 0xe074d4cb, 0x76b33514, 0x92975e84, 0xc4f82ea7, 0xeb560f6d, 0x9507ee5e, 0xd48a048b, 0x1aa04244, 0xe9639443, 0xe7dafbbf, 0x050a37ec, 0x91c1b356, 0x00000000, 0xd00001f0 }, + FinalRegs = new uint[] { 0x00000001, 0xffffff18, 0x00000007, 0x00006800, 0xfeff60ec, 0x00001b7d, 0xfffeec5e, 0x000018ff, 0xe9639443, 0x000016c5, 0x00001547, 0xf400171a, 0xfffffffc, 0x00000054, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xb29c, 0xba51, 0x43ec, 0x4103, 0x372e, 0x2b03, 0x42b6, 0x41ad, 0xbac4, 0x463c, 0x2969, 0x24f2, 0x2801, 0x4274, 0xb048, 0x438e, 0x3129, 0x41e5, 0xb223, 0x40f8, 0xb004, 0xbac2, 0xba3e, 0x1aa2, 0xbf3b, 0x427c, 0x458c, 0x4062, 0x1d1d, 0x4184, 0x16db, 0x4429, 0x1dae, 0x4326, 0xbacf, 0x404a, 0xb2cc, 0xbf0a, 0xac02, 0x2e4d, 0x29e9, 0x298b, 0x30b9, 0xb0c0, 0xbf43, 0x1d40, 0x0fd8, 0xb21a, 0x2ed1, 0xb23d, 0x1fc5, 0x4639, 0x4308, 0x41f1, 0xba0c, 0x0fb2, 0xbff0, 0xbfd4, 0x40c3, 0xba36, 0x42d3, 0xba79, 0x42f6, 0x44c1, 0x414d, 0x430a, 0x4163, 0xbf0d, 0x3df5, 0x4390, 0xb280, 0xa4a3, 0x1918, 0x4075, 0x40f7, 0x4254, 0x431b, 0x40f2, 0x4272, 0xb252, 0x43c5, 0xb21f, 0xba24, 0xbfaa, 0x4096, 0xbac4, 0x4469, 0xbf28, 0xb098, 0xb24b, 0x42b8, 0x3c87, 0xb0bf, 0xba48, 0xa742, 0x41ed, 0xba57, 0xbff0, 0x42dd, 0x43f2, 0xb2e8, 0x1f6e, 0x2f4d, 0xbfc8, 0xafdb, 0xbaf9, 0x4313, 0x2895, 0xb06b, 0x180c, 0xb247, 0x1a0e, 0x4002, 0xb2be, 0x3206, 0x43e3, 0x40ec, 0x4609, 0xbf00, 0x410e, 0x4328, 0x45c5, 0xbf21, 0x169f, 0x1a33, 0xa416, 0xb2af, 0x42d6, 0xbfa4, 0x439f, 0xacea, 0x40c3, 0x43d7, 0x4184, 0x1ea5, 0xbade, 0x41b2, 0xbf96, 0xbac9, 0x1703, 0x18d6, 0xbf60, 0x1cb9, 0x2c88, 0x3e09, 0xba5f, 0x3f42, 0xb2d1, 0xb2a6, 0x42cf, 0xb2be, 0x2c25, 0x4315, 0xba76, 0xb029, 0x41fe, 0x437c, 0x4186, 0x41ce, 0x1adf, 0xba71, 0xb0d3, 0xbf76, 0x4399, 0x42b3, 0x418d, 0x4258, 0x1fe3, 0x4150, 0x4595, 0x1b75, 0x4255, 0x40d4, 0x42ee, 0x4207, 0xba40, 0x41c3, 0x030d, 0x2a47, 0x1f8f, 0x40a9, 0xb0c1, 0xbfae, 0x4080, 0x4358, 0x4005, 0xbfe2, 0x1724, 0xa1be, 0x183c, 0x408e, 0x3b25, 0xb255, 0x4222, 0xb2e2, 0x1244, 0xb0bd, 0xa6fa, 0x1fe0, 0xb05f, 0xb237, 0x07aa, 0xbaef, 0xa553, 0x3698, 0x41c0, 0x41a0, 0x4354, 0xbfcc, 0x46d0, 0xb0fd, 0x1374, 0x42ec, 0x4077, 0xa062, 0x1e82, 0x40fe, 0xb227, 0x172b, 0xbfa3, 0x0541, 0xb093, 0xba2e, 0x4359, 0x3053, 0x4163, 0x4190, 0x01d8, 0x434b, 0x420a, 0x0cdf, 0x1b4d, 0x4045, 0x09df, 0xb268, 0x1d2c, 0xb008, 0xbfe2, 0x4342, 0x4138, 0x2a50, 0x4197, 0x43d7, 0xbafc, 0x443a, 0xa855, 0xb00a, 0x43b9, 0xbaf2, 0xbf4d, 0x1f75, 0x42d8, 0x431a, 0x407c, 0xbac1, 0x42b1, 0x08e3, 0x4217, 0x2206, 0x436d, 0x429d, 0x43d8, 0xb061, 0x4280, 0x4164, 0x422c, 0xbfd8, 0x405c, 0x1ae9, 0x1cce, 0x4346, 0xb079, 0x2ad0, 0xafb0, 0x10f5, 0x430d, 0x41ad, 0xbfe2, 0x0a34, 0x0894, 0x1b3e, 0x429b, 0x46a8, 0xb2e6, 0x40cb, 0x3e07, 0x40ea, 0x2248, 0x4096, 0xba45, 0x43b5, 0x40c4, 0x40c0, 0x1a05, 0x4044, 0x4303, 0x42ab, 0x15f7, 0x4679, 0x4321, 0x447e, 0x429b, 0xbfc2, 0x1d66, 0x42f6, 0xb079, 0x24c9, 0x189d, 0xbad0, 0x20cb, 0x43d4, 0x1572, 0x4352, 0x2b09, 0xb2b0, 0x0800, 0xba78, 0x1e22, 0x1973, 0xb27e, 0xb2d0, 0xb21c, 0x3d33, 0x2ea5, 0x0470, 0x419e, 0x066e, 0xbadb, 0x43a1, 0x412f, 0xbf52, 0xba60, 0xb2a5, 0xba7f, 0xb019, 0x4117, 0x43cf, 0x1f1f, 0x42ca, 0x40ab, 0xa704, 0x18b4, 0x3160, 0x435a, 0xb2da, 0xbaee, 0x252b, 0xbf94, 0x374f, 0xb0e2, 0x3612, 0xbf90, 0x1c26, 0xa209, 0x4173, 0x4119, 0x4375, 0xb09a, 0x2b4a, 0x41d0, 0x404e, 0x4337, 0xa05b, 0xbad3, 0x3b56, 0x1b76, 0xba1d, 0xb217, 0x41de, 0xbf1a, 0x1e6f, 0xb234, 0xbae1, 0x45c5, 0x2c47, 0x41e8, 0x1fe5, 0x42bb, 0xbacb, 0xb007, 0x43e8, 0x423a, 0x3f88, 0x0af3, 0x1cbb, 0x408f, 0x427d, 0x403e, 0xb253, 0x43b4, 0x436a, 0xb252, 0x2ae0, 0x3ae8, 0x435e, 0xb06f, 0xbf84, 0x34ff, 0x1acd, 0xba06, 0x22dc, 0x4022, 0x4343, 0x45f4, 0x4361, 0x18d9, 0x40fc, 0xb024, 0x4208, 0x1820, 0x4391, 0x41ea, 0x37a3, 0x46c8, 0xbf60, 0xb04f, 0x41dd, 0x0cfa, 0x0e15, 0xa479, 0xbac6, 0xbf54, 0x46c2, 0x288a, 0x182e, 0xba13, 0x1f09, 0x40dd, 0xa3e4, 0x426a, 0xb27d, 0x2af6, 0xbafe, 0x41e7, 0xba35, 0xb07c, 0x050a, 0xb2e1, 0xabb5, 0x1423, 0x3493, 0x40be, 0x2644, 0x1da5, 0x1bae, 0x1e6c, 0x41c0, 0x44c9, 0xbfc3, 0x40c4, 0x21e1, 0xaeb3, 0x1fdc, 0xb254, 0x418a, 0xbf0b, 0xb224, 0xae41, 0x42e5, 0x42f6, 0x2d3f, 0x4137, 0xb093, 0x4105, 0x420d, 0x40ac, 0xb210, 0x42dd, 0x2bf7, 0x43bc, 0x417d, 0xb2f3, 0xac37, 0x4031, 0xb2cc, 0x405b, 0x415f, 0x23b1, 0xb215, 0xba5e, 0xa2d0, 0x4415, 0x2582, 0xbf1c, 0x408a, 0x42a5, 0x4118, 0xb292, 0x413e, 0x4472, 0x406b, 0xb2c3, 0x3639, 0x40e3, 0xb25e, 0xbfd0, 0x40f2, 0x1fd9, 0xbfb0, 0x1e92, 0x1e5f, 0x40df, 0x4665, 0xbf37, 0x4479, 0x1602, 0x1a33, 0x437a, 0x2900, 0x4411, 0xbf00, 0xbf60, 0x4108, 0x41ef, 0xb2d3, 0x42f9, 0x38a6, 0x43f4, 0xb2d5, 0x292a, 0xba42, 0x2eb4, 0xbae0, 0xa21b, 0x1a60, 0xbf8a, 0xb239, 0x1dea, 0x1be6, 0x4042, 0x18b4, 0x4217, 0x0c3d, 0x1826, 0x41d1, 0xba1c, 0x36b3, 0xa241, 0x43f6, 0x4435, 0x425e, 0x3c53, 0x2281, 0x25fe, 0x409d, 0x42c6, 0xba3b, 0x43ba, 0xbf86, 0xba54, 0x2522, 0x43df, 0xbfd7, 0x18f1, 0x4308, 0x1fb2, 0x429d, 0xb010, 0x4445, 0x4169, 0x4054, 0x2abe, 0xa0b6, 0x3317, 0x1f53, 0x42d4, 0x4272, 0x46ab, 0x2008, 0x1d36, 0xbf73, 0x42d6, 0xb238, 0x425f, 0x411c, 0x4098, 0xb044, 0xb23a, 0x420d, 0x1f38, 0xb20e, 0x0203, 0xacf3, 0xb2e9, 0x4055, 0x1667, 0x42a1, 0x4073, 0x3006, 0x4229, 0x41e2, 0x4630, 0xb2e0, 0xa326, 0x4146, 0x1d00, 0xbf35, 0xb2d1, 0x14a1, 0xb2e9, 0x31e8, 0x0f16, 0x41ec, 0x4245, 0x1a7a, 0x421a, 0x2429, 0x4017, 0x0882, 0x3395, 0x00f1, 0xbad4, 0x42b0, 0x43d9, 0x293c, 0xbf98, 0x1938, 0x40a9, 0xb26c, 0x1bf5, 0x1a28, 0x4016, 0x432b, 0xb21e, 0x43e9, 0x4457, 0x417a, 0x41b0, 0x4052, 0xbae8, 0xa883, 0xb266, 0xba3a, 0x431e, 0x1b96, 0x4238, 0x43ac, 0xbfc0, 0x4157, 0xbf04, 0xaa02, 0x42a0, 0xba3b, 0x4114, 0x4145, 0x3e46, 0x0cc6, 0x1885, 0x4102, 0x43ba, 0x430e, 0xb2d1, 0xb286, 0x3084, 0x01e7, 0xa8bb, 0x42d1, 0xba6c, 0x402b, 0x427c, 0x4279, 0xbf61, 0x4177, 0x40f1, 0x424e, 0x01c2, 0x11e7, 0x369e, 0x413e, 0x1a54, 0xb090, 0x1844, 0x43c1, 0x42dc, 0x4038, 0xbadb, 0xbf1c, 0x4345, 0xbaf7, 0x4103, 0xb297, 0xb030, 0xba42, 0xac35, 0x4078, 0xba2a, 0xbfb9, 0x416f, 0xba16, 0xa940, 0x41f8, 0xbf65, 0x43c9, 0x45a4, 0xb2fe, 0x4020, 0xba2b, 0x400e, 0xb282, 0x40ec, 0x1afb, 0x4584, 0xbf70, 0x3323, 0x41c6, 0x1bfb, 0xb288, 0xbfac, 0xba01, 0xa344, 0x3658, 0xba58, 0x408e, 0x1efe, 0xb0bf, 0x119d, 0x0af0, 0x4194, 0x4162, 0x419b, 0x4311, 0xb2f7, 0x4312, 0x411a, 0xbf70, 0x405d, 0x405d, 0x1b4b, 0xbad8, 0x416e, 0x4327, 0xbf63, 0x4311, 0x1fcc, 0x41f8, 0x2cae, 0x41d4, 0x422b, 0xbf6b, 0x4260, 0x43cb, 0x1cd2, 0xa5a3, 0xbfb0, 0xb030, 0xbfac, 0xbfc0, 0x4148, 0x4264, 0x3326, 0x41cb, 0x4132, 0x2736, 0xb012, 0x11c0, 0x3453, 0xbfa9, 0x1e76, 0xb0f2, 0xb2b5, 0xba0e, 0x09f7, 0x1b71, 0x46a5, 0x415a, 0xb29f, 0x4493, 0xb0f9, 0x3943, 0x317b, 0x44e4, 0x438c, 0xbf0b, 0x4144, 0x07a1, 0x4392, 0xadf3, 0xaf15, 0x1422, 0x212f, 0xa165, 0x44db, 0x070c, 0x4381, 0xb0e2, 0x1f2b, 0x40a3, 0x4647, 0x3bf5, 0x1b02, 0x42c9, 0x2996, 0x43a7, 0x0d6b, 0x46eb, 0xb0e6, 0x4160, 0x41be, 0xb053, 0xb083, 0x1e27, 0xbf2a, 0x22b5, 0xbaf4, 0x08fb, 0x4556, 0x464b, 0x4333, 0x1337, 0x1a48, 0xb29e, 0x4167, 0x41ee, 0xb2d8, 0xbfa9, 0x1cc6, 0x4351, 0xb28b, 0x4308, 0x43f7, 0x432e, 0x1f58, 0x43b0, 0x4057, 0x41e8, 0x411c, 0x22d8, 0x3135, 0xba7c, 0x43a2, 0x2df5, 0x42cb, 0xb2d6, 0x441d, 0xb2ef, 0x2d14, 0x436b, 0xaed5, 0x322f, 0x1da6, 0xb08e, 0xbf2e, 0xb271, 0x33f0, 0x33be, 0x424c, 0x1be6, 0x4319, 0x2dc9, 0xb06b, 0x1f95, 0x3107, 0xb2ca, 0x439b, 0x1965, 0x2ed1, 0x45ba, 0x2066, 0xbf64, 0x1aca, 0x411b, 0xae5e, 0xadca, 0xb0cf, 0xba28, 0xbf85, 0xb278, 0x41c5, 0xb287, 0xb26e, 0xa4a7, 0x1aeb, 0xba16, 0x41b8, 0x3a44, 0xbf80, 0x42e4, 0x0580, 0x3e82, 0xb2f4, 0x1afe, 0xb244, 0xb25b, 0xbafb, 0x1961, 0x41f9, 0xbfb0, 0xba5f, 0x0ba4, 0x40d9, 0xb21b, 0xbf29, 0x01dd, 0xa878, 0xbfd0, 0xb2ad, 0x1ba2, 0x1c18, 0xaf6a, 0x39a8, 0xbfc0, 0x3400, 0x40b3, 0x192a, 0x4128, 0x3f37, 0x42c0, 0x14c8, 0x42d4, 0x402d, 0x21a6, 0xb25b, 0x4045, 0x184f, 0x406f, 0xb0de, 0x4046, 0xbf8a, 0xba50, 0xaae3, 0x2719, 0xb2d4, 0xb22b, 0x35ad, 0x40fb, 0x0e62, 0x43ac, 0x439a, 0x0f5d, 0x4063, 0x1ca1, 0x40a4, 0x406a, 0x18fb, 0x27b5, 0x409a, 0xbf6b, 0x43c7, 0x293d, 0x074b, 0x336f, 0x456f, 0x41e1, 0x40a7, 0x2b00, 0x3c88, 0x4616, 0x1f8d, 0xb2b5, 0x129a, 0x4224, 0x407e, 0xbf53, 0x3fae, 0xbacf, 0x4313, 0x46ec, 0x1a1c, 0x4268, 0xb07b, 0x433e, 0x4407, 0x4189, 0x45ce, 0x428c, 0xb068, 0x413f, 0xbfac, 0xb200, 0xbae2, 0x1f79, 0x4236, 0x417c, 0xa561, 0xbaf0, 0xb2f6, 0x1290, 0xb243, 0xba63, 0x40c8, 0xbfa0, 0xafcf, 0x43f2, 0x1bdd, 0x42bf, 0xbfa3, 0x42c0, 0xba51, 0x1c8c, 0xba37, 0xb239, 0x4008, 0xb2e7, 0xb290, 0xbaee, 0x431d, 0x421f, 0x4364, 0x437c, 0x01b9, 0x43ad, 0x4544, 0x2c61, 0x4640, 0xa354, 0x2ad2, 0x2d4b, 0x4052, 0xbf2f, 0xaff9, 0xaa46, 0xae6b, 0x0c27, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x60ab798e, 0xeb64755b, 0x32fad41e, 0x906517a9, 0x6b2d648d, 0x9c5a9d43, 0xefba7776, 0x297a6da6, 0x3750e535, 0xf070ec03, 0x5dff78cd, 0x69746ba1, 0xc40e9ad7, 0xa94af44d, 0x00000000, 0x200001f0 }, + FinalRegs = new uint[] { 0x27c1d138, 0x00000040, 0x00000374, 0x0000192c, 0x82cf9201, 0x00000000, 0x00000408, 0x000082cf, 0x27c1d138, 0x4f83a270, 0x27c1d138, 0x00000068, 0xfffffed0, 0x0000025c, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xb234, 0x4215, 0x2ab0, 0x43d9, 0x42a0, 0x432b, 0x0bb3, 0x191b, 0x086d, 0x4251, 0x42d3, 0xba6f, 0xa2e4, 0x4069, 0x42ec, 0xae3e, 0xb293, 0xbf16, 0xa0fb, 0x433a, 0x4366, 0x45a5, 0x41d4, 0xb2ca, 0x1ed4, 0xb06d, 0x2a1c, 0x1fbf, 0xbaf6, 0xb266, 0xb293, 0x1c35, 0x3693, 0x2df7, 0xb031, 0x1c23, 0xad6c, 0x36e1, 0x4116, 0xbfc0, 0x32a2, 0xba37, 0x455d, 0x40e1, 0x4047, 0xbf01, 0xbaf8, 0xbad3, 0x19a9, 0x4255, 0xb074, 0x4360, 0xba2b, 0x1e38, 0x18c5, 0x44ed, 0x44e5, 0x187b, 0xb22a, 0x40a3, 0x1852, 0xb276, 0x40dc, 0x42dc, 0x2cf9, 0xb041, 0x18c1, 0xba29, 0xbff0, 0xa748, 0x42ab, 0xbf7d, 0x253d, 0x4352, 0x0c85, 0x438f, 0x4274, 0x3cf4, 0xafce, 0x1974, 0xb074, 0xbfa6, 0x4257, 0xba5b, 0x4037, 0xa515, 0xb0bd, 0x04fe, 0x1cf8, 0x420c, 0x4119, 0x4184, 0x4312, 0x45d3, 0x41ef, 0x4011, 0xb00a, 0xb027, 0xb0f9, 0x41bc, 0x4087, 0x41bd, 0xa595, 0x4126, 0x45d8, 0xbfbd, 0x2aaa, 0xb22b, 0x4361, 0xb2ad, 0x02e9, 0x43f5, 0xb21b, 0x41c2, 0x0d25, 0x03aa, 0x3663, 0x4404, 0x45ce, 0xbf8a, 0x21db, 0x4362, 0x1af0, 0xbf68, 0x415b, 0x4223, 0x2828, 0x4075, 0xbf42, 0xb262, 0xacd1, 0xb011, 0x1da5, 0x43fc, 0x408c, 0x43b3, 0x43c5, 0xb0c7, 0xa5b2, 0xbf17, 0x409d, 0xbac7, 0x4440, 0x0ba9, 0x4025, 0x2022, 0xa67c, 0x4214, 0x40e4, 0xba71, 0xb28c, 0x40ea, 0x41cf, 0x41e2, 0xbfbf, 0xb2bc, 0x3e8c, 0x40eb, 0xb2d4, 0xa13f, 0x4311, 0xbf52, 0x19f8, 0xbf70, 0xb2f4, 0xb06c, 0x0ab2, 0x0a8e, 0x40ae, 0xba23, 0x1c9c, 0x4211, 0x17ac, 0x1c16, 0x1235, 0xab09, 0x1a59, 0x41f7, 0x46a3, 0x1b42, 0x416e, 0x0f01, 0x0b26, 0x407f, 0x4228, 0x0d9f, 0x43c2, 0x435d, 0xbf22, 0x4270, 0x1893, 0x4246, 0xbf06, 0x30d0, 0xba13, 0xae81, 0x0378, 0x4088, 0x463e, 0x4101, 0x4243, 0x1ddd, 0x41bf, 0xba17, 0xae45, 0x1c8e, 0xb014, 0xa8ef, 0x4167, 0x4207, 0x40c6, 0xb018, 0xa950, 0x4073, 0x049b, 0x41a2, 0x45c5, 0x424b, 0x439a, 0x1a0a, 0x3231, 0xbf32, 0xb20b, 0x433b, 0xba2b, 0xbf5d, 0x430c, 0xb2f5, 0x4131, 0x43e4, 0x1e5f, 0x42e8, 0x1f98, 0x115d, 0x4268, 0x41eb, 0x05f8, 0x28e7, 0x06ea, 0xb002, 0xb2d0, 0x1cac, 0x43ca, 0x0c9b, 0x425b, 0xbfe0, 0x40d1, 0x42a1, 0x3faf, 0x403d, 0xb011, 0xb204, 0x4204, 0x4274, 0xbf7f, 0x0588, 0xa465, 0xbadf, 0x1860, 0x0720, 0x4115, 0xbaf0, 0x417e, 0x4569, 0xb211, 0x40a2, 0x43d8, 0x38ea, 0x4061, 0x42df, 0xb2d6, 0x415e, 0x432c, 0xba38, 0x4353, 0x4352, 0x45a5, 0xbf05, 0x4078, 0x3f2a, 0xba41, 0x44e8, 0xb209, 0x1da4, 0xb2c8, 0x41e8, 0xbf29, 0xbafe, 0x1a67, 0x1829, 0x1bf3, 0x0647, 0xb2c6, 0x0c9f, 0x41f5, 0x11dc, 0xbf87, 0x4124, 0xbacc, 0x424a, 0x2a70, 0x403e, 0xb25a, 0x1d42, 0xa62a, 0x4097, 0x43d5, 0xbfa0, 0x3791, 0xba00, 0x1644, 0xba57, 0xb215, 0x4251, 0x07ef, 0xbf4a, 0x2bd0, 0x2e96, 0x4355, 0x4393, 0x4376, 0xbf59, 0x411a, 0x400e, 0xba18, 0x43f5, 0xbf65, 0x4054, 0xae5d, 0xb2e8, 0x4230, 0xa533, 0x4134, 0xb2d9, 0x4149, 0x3cee, 0xbf80, 0x40a1, 0x1745, 0x1b9c, 0x1b90, 0x41be, 0x4468, 0xab8c, 0x4618, 0x1d05, 0x1c91, 0x259f, 0x4228, 0x414b, 0x4028, 0xbfd9, 0x4298, 0x4139, 0x1f8a, 0x4355, 0x4302, 0x42f9, 0x1951, 0xbf9c, 0xba05, 0x400c, 0xacba, 0x44b1, 0xb216, 0x4151, 0x2013, 0x40df, 0x3de1, 0x1f4a, 0x4036, 0x401e, 0x42a0, 0x42cd, 0x40dd, 0x428c, 0xbfd2, 0x4012, 0x1b19, 0x4099, 0x437f, 0x0289, 0x40ef, 0x1f98, 0x418f, 0x19e0, 0x40c5, 0xb2ea, 0x4366, 0x46ea, 0x427a, 0x4464, 0x4262, 0x4280, 0xbafa, 0xbf6f, 0x21d0, 0x1e60, 0x0a92, 0x0f02, 0x2122, 0x434c, 0x40c6, 0xba0c, 0xbf64, 0xa3fa, 0x10b4, 0xbac4, 0x41f5, 0x4079, 0x4197, 0x226a, 0xb2bf, 0x209e, 0x4085, 0xb07d, 0x437c, 0x3671, 0x2d5b, 0x4090, 0x44d0, 0xbf3b, 0x4259, 0xbaf9, 0x4329, 0x418d, 0x4292, 0x4485, 0x4694, 0x4059, 0xb21e, 0x41e6, 0xad2c, 0x3caf, 0xbf0b, 0xb29f, 0x4133, 0x4366, 0x04e2, 0xba2f, 0x1f7a, 0x41a6, 0x0e62, 0xb2f1, 0x43a3, 0xb09c, 0x443d, 0x025f, 0x4128, 0x1fb1, 0x1dea, 0x3733, 0xbfc5, 0x4263, 0xbad3, 0x42f0, 0xb238, 0xba0d, 0x3c1f, 0x4206, 0x41bb, 0x430a, 0x43ad, 0x1984, 0x4303, 0x0cb0, 0xba11, 0x1c1f, 0xb2df, 0xba43, 0x415b, 0xbfa4, 0xbf90, 0x1c9d, 0x1d4a, 0xab5e, 0x46f0, 0xb22e, 0xba16, 0x09f6, 0xba6a, 0x408e, 0x4380, 0xac19, 0x439f, 0x43d2, 0xbfa0, 0x4258, 0xa366, 0x4051, 0xbf36, 0x438a, 0x429f, 0x43f3, 0xb2c5, 0x3bd3, 0x1fa1, 0xb23c, 0x443d, 0xba27, 0xb089, 0x4354, 0x0f0f, 0x1da0, 0x4384, 0x4228, 0xb201, 0xbfe2, 0x4227, 0x395f, 0x447c, 0x43b0, 0x3a46, 0x1ba2, 0xbf80, 0x1c7c, 0xb200, 0x42e2, 0xba2b, 0xb279, 0x43e7, 0xb275, 0x3d99, 0x23a0, 0x41c6, 0x020a, 0x456b, 0x41ea, 0xb0dc, 0x466d, 0x3dec, 0xa2f3, 0x0a1d, 0xbfcc, 0x2a39, 0x215f, 0xbade, 0xb2a5, 0xb22a, 0x406c, 0x4093, 0x33ea, 0x117d, 0xabbf, 0x4291, 0x4055, 0x425e, 0x43e6, 0x4202, 0x4383, 0xbf3e, 0xae2d, 0x4165, 0x404b, 0x45e2, 0xbf60, 0xb0c6, 0x405b, 0xbff0, 0x0ed3, 0xb256, 0x4305, 0x02d6, 0x10a9, 0xac5f, 0xba55, 0xad0d, 0x1b53, 0x42c5, 0x40b6, 0xb211, 0x40cc, 0x1d30, 0x4098, 0x1df1, 0xbf66, 0x4553, 0x41af, 0x0cff, 0x4307, 0x40fa, 0x318f, 0x1b3e, 0xb2f6, 0x442e, 0xb229, 0x0b45, 0xb2de, 0x1948, 0xba36, 0x1a5b, 0x40ae, 0x0dc0, 0x4316, 0x1e5a, 0x258e, 0x4299, 0x1e58, 0x42b1, 0x4322, 0xb058, 0xbf0b, 0x080d, 0xba66, 0x4326, 0x096f, 0x4366, 0x42eb, 0x1b82, 0x43af, 0x4032, 0xb20e, 0x42a5, 0x1ed5, 0xa3f5, 0x410f, 0xba5d, 0x4120, 0xbf5f, 0x2aa9, 0xba1b, 0x0051, 0x40ad, 0x04a1, 0x420e, 0x3c42, 0x42e0, 0x42bb, 0x43d1, 0x447f, 0xbf14, 0x0ca2, 0xba56, 0x42ed, 0x4372, 0x2ffe, 0x42bd, 0x411e, 0x400a, 0x4285, 0x4652, 0xb079, 0x40ee, 0x0a3a, 0x11c7, 0x403f, 0x14df, 0x40eb, 0xb236, 0x323f, 0x43d7, 0x05c3, 0xbfca, 0xb0cc, 0xba51, 0x1d2c, 0xbf72, 0xba3a, 0x2c3a, 0x4154, 0xbf31, 0xba27, 0x19d1, 0x43b4, 0x4167, 0xba36, 0x42bb, 0x0e3a, 0x4011, 0x4363, 0xbae0, 0x186e, 0x40d1, 0x3dff, 0x38c3, 0x12b7, 0x0292, 0x4561, 0xb037, 0x1dda, 0x401e, 0x43d8, 0xacf4, 0x3e49, 0xbf81, 0x1a75, 0x1d50, 0xb2e7, 0x4080, 0x108c, 0x0fbc, 0x41c5, 0x4189, 0xbad6, 0x1d8f, 0x3b0a, 0xa7a7, 0xbfe0, 0x4072, 0x43a6, 0x19e2, 0x4376, 0x09a4, 0x42fe, 0x0780, 0x4158, 0xbfc9, 0x435d, 0xa29f, 0x4111, 0x01c5, 0x435f, 0x41b2, 0xba73, 0x46cd, 0xbf1b, 0xba2a, 0x161c, 0xb0f2, 0xbff0, 0x00c3, 0xa332, 0x2528, 0x19e9, 0x26a9, 0x1d5c, 0xb2ff, 0x42d8, 0xbf70, 0x175e, 0xbf3c, 0x40db, 0x43af, 0x2e2b, 0xb207, 0x421b, 0x4172, 0x0976, 0x410d, 0x439f, 0x1933, 0x1b15, 0x0093, 0x43df, 0x41f1, 0x40c6, 0x1c52, 0x1e4d, 0x4600, 0x0be1, 0xbf7d, 0x2de0, 0x43e4, 0x4319, 0x428c, 0x41bd, 0x4155, 0x46ab, 0x2730, 0x3a48, 0x4365, 0x403d, 0x1ad8, 0x0537, 0x41a0, 0x1994, 0x401b, 0x4088, 0xb2a0, 0x4050, 0x1dc2, 0x435d, 0x437d, 0xb0b7, 0x4060, 0x4109, 0x44aa, 0x41c1, 0xbf8b, 0x43f8, 0xb2bc, 0xba6e, 0xb023, 0x215b, 0x43c9, 0x43c4, 0x1feb, 0x00f7, 0x40e7, 0xb267, 0xbfc5, 0xb229, 0x42e8, 0xbad3, 0x4171, 0x400c, 0xbadf, 0x42ac, 0x431d, 0x4393, 0x4380, 0xa188, 0xbf81, 0xa3d8, 0x442b, 0x42d3, 0x41ce, 0x1e19, 0x4143, 0x4005, 0xb0c1, 0x4211, 0x455c, 0x25ba, 0x0780, 0x422e, 0x400f, 0x45d4, 0x2e74, 0x41cb, 0x1c7e, 0xbfcb, 0xb26a, 0x46f5, 0x3ee4, 0xa482, 0x18c4, 0x44b2, 0x4204, 0x43db, 0x3545, 0x421f, 0xb2f0, 0x463e, 0x2001, 0x43eb, 0x42d4, 0xb28a, 0x3886, 0x4191, 0xadd9, 0x415b, 0x4138, 0x4103, 0x45c9, 0x111b, 0xb02b, 0xb0e6, 0xbf3d, 0x23cd, 0x40ad, 0xb2d2, 0xb0cb, 0xb2e1, 0x21e4, 0xba28, 0xba05, 0xbf48, 0xbfb0, 0x060e, 0x1fa5, 0xba0d, 0x45dd, 0x4017, 0xbf1b, 0x30fd, 0x210c, 0x42b3, 0x17b5, 0x401f, 0x0b91, 0x264c, 0x40af, 0x40c2, 0xbf7d, 0x0302, 0xa7b3, 0x43e0, 0xba6c, 0xbf18, 0x0add, 0x1fe5, 0x0f9f, 0xb073, 0x381f, 0x1b07, 0xb0fa, 0x40d8, 0x1852, 0x4211, 0x42a6, 0x4036, 0xbac4, 0xa27b, 0x435d, 0x18eb, 0x4171, 0x4318, 0xb08e, 0x4296, 0xb2f4, 0xb2ea, 0x3e89, 0xbf32, 0x43b9, 0x1a0b, 0xb2d2, 0x429c, 0x2aad, 0xbf90, 0x18a6, 0x4360, 0x1a4a, 0x4435, 0xba4e, 0x4071, 0x1b83, 0x465d, 0x2ed8, 0x1a91, 0xbada, 0xbfb1, 0x41a8, 0x278e, 0x4182, 0x42c1, 0x1821, 0xbfa0, 0xbad2, 0xb0fe, 0x42e1, 0x1964, 0x46b9, 0xb2a2, 0x40f9, 0xbacc, 0x4221, 0x425a, 0x4464, 0x418e, 0xb0e6, 0x4392, 0x3a7b, 0x4206, 0x1970, 0xbf67, 0x4241, 0xb2f2, 0xbad7, 0x429e, 0x4332, 0x42e6, 0xa4a7, 0x42f6, 0x4011, 0xa32e, 0x40bb, 0x4651, 0x4458, 0x4309, 0x43f5, 0x438c, 0x2ca1, 0xba00, 0x438e, 0xba55, 0x1c48, 0x43f6, 0x41d3, 0xb2a4, 0x4034, 0xba2f, 0xbf0f, 0xbf60, 0xbaf7, 0x42fc, 0xbacb, 0x4217, 0x421e, 0x4113, 0x1863, 0x4103, 0xb052, 0x43b2, 0xb2ab, 0x440f, 0xb22a, 0x4207, 0x40df, 0x449b, 0x185d, 0xbf33, 0x0307, 0x1f5c, 0xb22f, 0x365e, 0x415a, 0xb218, 0x44fc, 0x3db0, 0x41bf, 0x41e6, 0x21d8, 0x412b, 0x4065, 0xbf4d, 0x1013, 0xa00f, 0x444e, 0x4341, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x81c55787, 0xada11b20, 0x3e10df97, 0x7b862743, 0x1f0517ba, 0x4d0933c2, 0x10df7fe1, 0x2dec0c53, 0xcbcf889f, 0x3e17e3e5, 0x8eacf129, 0xd8f1b147, 0xb8b631ab, 0x63405cb4, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x0000181c, 0x000000d8, 0x0000fffe, 0x00000000, 0x00000a00, 0x7f37fe5f, 0xfffffffc, 0x00000000, 0x00000000, 0x0000008e, 0x7f36f510, 0xfb5e1489, 0x0000183a, 0xbf82a5e0, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x43e9, 0xb2ae, 0x2b63, 0x4354, 0xbf91, 0xb23c, 0x4231, 0xb075, 0xb2fc, 0xba3a, 0x4162, 0x1cd2, 0x42af, 0x426e, 0x43fa, 0x0099, 0x1acc, 0x4434, 0x411d, 0x4624, 0xb25e, 0xbfc4, 0x1ef8, 0x45ac, 0x0b2d, 0xb2b0, 0x424d, 0x365f, 0x4336, 0xb0ac, 0x2a8b, 0xaa5c, 0x1bc1, 0x40ca, 0x18db, 0x3c16, 0x4181, 0x4348, 0x1cdf, 0x0413, 0xb2ad, 0xbfce, 0x1e96, 0x4304, 0x405b, 0x42ed, 0x1cea, 0x4159, 0x4057, 0x1c6b, 0x4256, 0x372b, 0x41d8, 0x4268, 0xa6ea, 0x430f, 0x1e36, 0x42bc, 0x40f5, 0xa28f, 0x1ae4, 0xb2a0, 0x4282, 0xa134, 0x4355, 0x42ba, 0x4395, 0x1faa, 0x154b, 0x434a, 0xbf6e, 0x43d1, 0xb2cd, 0x40b8, 0xbfe0, 0x0c97, 0x2541, 0x350e, 0x41c6, 0x40b5, 0x1e4d, 0x4048, 0x4657, 0x428a, 0x07dc, 0x42bb, 0x3923, 0x430d, 0x41a0, 0xbaed, 0xbf4e, 0x40ee, 0x0acb, 0x1f73, 0xbae9, 0xa18e, 0x41a8, 0x1947, 0x181b, 0x4248, 0x4349, 0xbf11, 0x1e48, 0x41d8, 0x18dc, 0x2073, 0xb20f, 0x41df, 0x41bf, 0x1ba3, 0xbf90, 0x4144, 0x428d, 0x41f9, 0x43b8, 0xb239, 0x4180, 0x432a, 0x4119, 0x46f8, 0x4329, 0x4091, 0x4302, 0xb228, 0x4094, 0x40b1, 0xbfcd, 0x2d26, 0x454f, 0x18a4, 0x42fb, 0x1b0e, 0x0d5a, 0x1ddb, 0x14c4, 0x43c2, 0xa374, 0x426b, 0xb0c9, 0x4262, 0xbf80, 0xbf8f, 0x46d3, 0x40cb, 0x4377, 0xb2b3, 0x3a47, 0xba60, 0xbfd9, 0xb267, 0x429a, 0x1806, 0x43d3, 0x3142, 0xb2be, 0x1487, 0x18ab, 0x1cd7, 0xba36, 0x1027, 0xb028, 0x1742, 0x435b, 0x4147, 0x2501, 0x41f0, 0xbadb, 0x40af, 0x444b, 0xb24c, 0xbae8, 0x40aa, 0xb2fe, 0xa75f, 0xbfcb, 0x4162, 0x4146, 0x4328, 0x2a69, 0x4247, 0x286e, 0x0b98, 0x24fb, 0xb061, 0x43a8, 0x409b, 0x1a0b, 0x41b6, 0x435a, 0xb244, 0x242e, 0xbad4, 0x0f4e, 0xbf0b, 0x4069, 0x273a, 0x4422, 0x4208, 0x4248, 0x43fa, 0x4406, 0xb2c1, 0x4046, 0x40bb, 0x4267, 0xb2b6, 0x03b8, 0xb2fd, 0x3dd7, 0xba6c, 0xb29b, 0xba17, 0x4109, 0x1b66, 0xb2df, 0xb005, 0x3496, 0x4598, 0xbf48, 0x40b2, 0x2979, 0x40d4, 0x4269, 0xb2ba, 0x3b3b, 0x408f, 0x336d, 0x1b00, 0x42c9, 0x2a70, 0xbf00, 0xaf87, 0x4386, 0x40c3, 0x4601, 0x461a, 0xbf90, 0xb217, 0x1d8e, 0x1c52, 0x42eb, 0x403f, 0xa25c, 0xa437, 0xbfcd, 0x19b1, 0x413f, 0x404a, 0x19f3, 0xbae3, 0x20a3, 0xbf4e, 0x3dd9, 0x4157, 0xb2ee, 0x29e2, 0xbad3, 0x42ab, 0x40af, 0xbf0d, 0xbaea, 0x43c8, 0xbae8, 0x0451, 0x424a, 0x1c6b, 0x400e, 0x4144, 0x3a3a, 0x412f, 0xbf04, 0x42df, 0x3821, 0x40ec, 0x433e, 0x4196, 0x43fd, 0x42e0, 0xb00f, 0x44dc, 0x2af8, 0x157c, 0x4316, 0xb095, 0xbf4b, 0x1a70, 0x1aaf, 0xb263, 0x41a3, 0x42ae, 0x44ba, 0x289a, 0xb293, 0x40d8, 0x3bc3, 0x42a1, 0x41e8, 0xbaf7, 0x403d, 0x418d, 0x42cf, 0xb023, 0xb05c, 0x4029, 0x40e4, 0x4209, 0x40fc, 0x108c, 0x40d5, 0xbf14, 0xb0e2, 0x425c, 0x4220, 0x244c, 0x0736, 0x1d18, 0x401d, 0x40ac, 0x4045, 0x4051, 0xaf3a, 0xbaef, 0x4451, 0x439a, 0x43ba, 0x406b, 0x1daf, 0x409e, 0x44ad, 0xb09c, 0x4087, 0x205e, 0x4540, 0x40fc, 0xb27d, 0xbf7a, 0x28c4, 0x42c5, 0x4178, 0x42eb, 0x40d3, 0xb273, 0x45f3, 0x401b, 0x43ca, 0x43f4, 0x43d1, 0xaed9, 0xba70, 0x41c3, 0x11e8, 0x41bb, 0x40e2, 0x41c0, 0xa5a2, 0x09c4, 0xbfc0, 0x43aa, 0x388c, 0x411e, 0x189f, 0xbf81, 0x4339, 0x1f88, 0x1b87, 0xa26a, 0xbf0a, 0x4366, 0x415a, 0x42ac, 0x41bc, 0xbf60, 0xba4b, 0xba12, 0x41b7, 0x413d, 0xb02f, 0x4235, 0x0548, 0x0509, 0x41de, 0x42c6, 0x16c5, 0x3d8d, 0xbf0d, 0x2de3, 0x40b7, 0x417f, 0x40f6, 0xa413, 0x4232, 0xba60, 0x4126, 0x4044, 0x1bd0, 0x2d4b, 0x20bd, 0xb215, 0x40d9, 0x415c, 0xbf08, 0x1d75, 0x1e01, 0x411c, 0x0b49, 0x410e, 0xbfcf, 0x1455, 0x0ee7, 0x05cd, 0x4394, 0xb2b1, 0x432c, 0x25fb, 0x4671, 0x4053, 0x3e48, 0x0be7, 0x405f, 0x4276, 0x41f6, 0x440e, 0x40f6, 0x435a, 0x41a5, 0xb2ef, 0x3d3f, 0x443a, 0x332a, 0xb00d, 0x4432, 0x1d1c, 0x428e, 0x4296, 0x42ef, 0xbfaf, 0x4062, 0x1918, 0x08bd, 0x4241, 0xba2f, 0x40b9, 0x1e32, 0x42be, 0x2983, 0xbf99, 0x402b, 0x423e, 0x41e5, 0x403a, 0xbf70, 0x41d1, 0x42cf, 0x434f, 0x41ea, 0x1dd7, 0x418c, 0x418f, 0xb248, 0x1e0f, 0x2ec4, 0x42f5, 0x40e2, 0x4330, 0x4118, 0xad76, 0xb089, 0x41b9, 0x40de, 0x42fa, 0xba54, 0xb2c6, 0xbf09, 0x4102, 0x41e7, 0xb08b, 0x41bd, 0x1dbf, 0x4140, 0x4009, 0x42fc, 0x46db, 0xbad8, 0x45ae, 0x40e0, 0x4212, 0x43fd, 0x43cb, 0x435f, 0x4276, 0x15a5, 0xb222, 0x409b, 0x40cb, 0xbf48, 0xad26, 0x40e7, 0x3491, 0x36b5, 0xbfa6, 0x4192, 0x43e1, 0x430d, 0xb28b, 0xaa14, 0x42eb, 0x4492, 0x41a8, 0xa8d2, 0x436e, 0x41c9, 0x4211, 0x42cf, 0x0d26, 0x41ea, 0x40bd, 0x0f79, 0x40d2, 0x394c, 0x4072, 0x40c9, 0xbf84, 0xb278, 0x2591, 0x43a9, 0x13e4, 0xb287, 0x02ad, 0x3290, 0x43ec, 0x4141, 0x431a, 0x46a9, 0x4629, 0x2e09, 0x1a14, 0x41f5, 0xba19, 0x35d8, 0x2c4a, 0x43dc, 0xb28e, 0x40b7, 0x41db, 0x41fe, 0x2f4d, 0xbfe8, 0x4459, 0x46d8, 0x42c5, 0x1d4f, 0x1a9e, 0x4203, 0x42b2, 0x4299, 0x428d, 0xbf29, 0x4129, 0xb223, 0x427a, 0x41c5, 0x089e, 0xad9a, 0xb24c, 0x1f2a, 0xb023, 0xb291, 0x42b3, 0xb07a, 0x1e3c, 0xa3d6, 0x4194, 0xbaf7, 0x370e, 0x1faa, 0x197f, 0x409e, 0x19c8, 0x4616, 0xbf7a, 0x42a2, 0x437e, 0xba3e, 0x4312, 0x428a, 0x418a, 0x433b, 0x03d8, 0x0250, 0xb29a, 0x1dd0, 0x33c0, 0x4334, 0xb0fd, 0xbfa0, 0xb279, 0x4067, 0x405b, 0xb23e, 0x2224, 0x4375, 0x2e15, 0x406f, 0x43b4, 0xbf94, 0xba6d, 0x04eb, 0x42f4, 0x42d7, 0x4184, 0x40cb, 0x40c5, 0x422d, 0x19c7, 0x415e, 0x426f, 0x4124, 0xb2e7, 0xa9a4, 0x40ed, 0x412f, 0x43c9, 0x3ca8, 0x1b05, 0x32fc, 0x4276, 0x4037, 0x447f, 0x40c3, 0xb267, 0xbf81, 0x43bb, 0xb243, 0x4570, 0x2b22, 0x0e9c, 0x4063, 0x1bea, 0x4055, 0x2367, 0xba35, 0x1e69, 0x0926, 0x44f5, 0xb00b, 0x18f8, 0x4007, 0xb255, 0x4350, 0xb0a5, 0x4095, 0xba26, 0xa26b, 0x14b9, 0x4346, 0xbfae, 0x4312, 0x4446, 0xbf80, 0xa9c8, 0xba7d, 0x410a, 0xbfa2, 0x2319, 0xb282, 0x4185, 0x425f, 0x41c1, 0x409c, 0x1a10, 0x1003, 0x284b, 0xbae6, 0xb221, 0xb242, 0x4262, 0xbad8, 0xbafe, 0xaa78, 0x4223, 0x41d8, 0xb0f9, 0x4091, 0x445c, 0x321e, 0x42c1, 0x4023, 0x411a, 0x438f, 0x43fd, 0xbf29, 0x434f, 0xa7a0, 0x1d7a, 0x40f0, 0xba4f, 0x2879, 0x4093, 0xa50b, 0x42b5, 0xb289, 0x43dd, 0xb014, 0xba26, 0x0450, 0x413b, 0x1828, 0xba63, 0x4156, 0x292f, 0x428f, 0xb053, 0xbf68, 0x42c4, 0x059b, 0x14ec, 0x4207, 0x43c8, 0x43b7, 0xba72, 0xb297, 0x1ab9, 0x1525, 0x416d, 0x420c, 0x446b, 0xb205, 0x466b, 0x416f, 0x19c5, 0x4351, 0x22d5, 0x4670, 0xb06b, 0x0374, 0x066b, 0xbfb9, 0x0c25, 0xabee, 0x4143, 0x41f2, 0x181b, 0x42f7, 0x40ab, 0x4130, 0xba4a, 0x1a6d, 0xba02, 0x4165, 0xbf60, 0x417e, 0x340c, 0xba72, 0xbaff, 0x3ddc, 0xbf35, 0x415a, 0x4011, 0x42cb, 0x437c, 0x3d03, 0x41d4, 0x1855, 0x0b24, 0xa86b, 0x41ef, 0xb262, 0x41a4, 0xb272, 0x43a4, 0x030c, 0x4365, 0xbf52, 0x46b5, 0x464d, 0x42b3, 0x1b6e, 0xba3d, 0x20f7, 0x35dc, 0x2ee9, 0xbf4e, 0x4378, 0xaf6a, 0x142e, 0xb290, 0x42d9, 0x40d8, 0xb069, 0x1fb9, 0x430a, 0x0594, 0x4231, 0xbafd, 0x3a53, 0xb2b1, 0xb0e0, 0x001d, 0xa85d, 0x17cd, 0x42b5, 0x4330, 0xb012, 0xbacd, 0x0ed9, 0xbf86, 0xba22, 0x4068, 0x263e, 0x0048, 0xb2a4, 0xba4a, 0xbf3a, 0x4224, 0x426e, 0x0a8d, 0xa5f2, 0x14f4, 0x4164, 0x1e8e, 0x324b, 0xba1c, 0x40ed, 0x4108, 0x170d, 0x30b8, 0x04a7, 0xb073, 0x2d88, 0x4171, 0x1b68, 0xbfa4, 0x40fe, 0xbac1, 0xba57, 0xb2ce, 0x330f, 0x2614, 0x070e, 0xb0c2, 0x4244, 0xad7d, 0x06ac, 0xa5ef, 0x0d63, 0x42aa, 0xb2df, 0xb2ce, 0x42d1, 0x2ea4, 0xbf4d, 0x4185, 0x41bb, 0x45e1, 0x326c, 0x42ea, 0x443c, 0xb219, 0x41a7, 0xbf31, 0x42af, 0x43f5, 0x4137, 0x1efb, 0x4175, 0x40fa, 0x4340, 0x40f0, 0x4342, 0x43f6, 0xbafe, 0x17c7, 0xbfcc, 0x4485, 0x43e3, 0x4085, 0x4003, 0x4187, 0x07dd, 0x3c2d, 0x1acf, 0x40a2, 0xbae2, 0x417c, 0xbfe0, 0x198c, 0xbf8c, 0x1a73, 0xbafc, 0x2c1d, 0x0cf6, 0x43a9, 0x403d, 0xbf34, 0x4297, 0xb000, 0xb27b, 0xab7d, 0x05c9, 0xb245, 0x4326, 0x426d, 0x4226, 0x42df, 0xb0d8, 0x46f2, 0x43ca, 0x4096, 0x283c, 0x4406, 0x41bc, 0xbf6d, 0x433b, 0x458b, 0x41be, 0xb019, 0x4029, 0x4167, 0x42a1, 0x4024, 0xbaf1, 0xb26c, 0x1e3d, 0xb292, 0x41ea, 0x241b, 0x4024, 0x1aec, 0x431e, 0x4606, 0x4209, 0x356f, 0x4308, 0xa65e, 0xbf7d, 0xbf70, 0x1a6b, 0xbaf8, 0x42f3, 0x1dac, 0xb2bf, 0x26b6, 0xba4a, 0x460b, 0x4390, 0x422d, 0x0a84, 0xb0d1, 0x43d7, 0x4022, 0xb0ee, 0x409e, 0xba72, 0x2a10, 0x429f, 0xbf70, 0x304b, 0xa8bc, 0xbf2c, 0x4288, 0x41cc, 0xbf28, 0xba26, 0x419b, 0x42af, 0x4357, 0xab5e, 0x4307, 0xbae7, 0x4449, 0x2dca, 0x46d9, 0xa4d7, 0x4384, 0xb0ae, 0x39ca, 0x4153, 0xba66, 0x43ae, 0xba2b, 0xbfe1, 0x189b, 0x4062, 0xbaf2, 0xba5c, 0xb041, 0x431f, 0x0f58, 0x4474, 0xbf19, 0x421c, 0x09c9, 0x0947, 0xb013, 0xba56, 0x3cdb, 0x43d0, 0xbf7a, 0x40d6, 0x079a, 0x030a, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x69b72ece, 0x9ea70faf, 0x0bef8eae, 0x8b8f6dd5, 0x4fde440e, 0x8c63dba4, 0xee0baa0f, 0xb2812116, 0x764ead9f, 0xda04bbb5, 0xc49fd907, 0xf371f841, 0xb64410f1, 0x71a52826, 0x00000000, 0x000001f0 }, + FinalRegs = new uint[] { 0xffffef27, 0x00002035, 0x00000000, 0x4e010000, 0x014dff25, 0x0000014e, 0x00000000, 0x00000000, 0xf371f841, 0xf371f841, 0x00000000, 0xf371f841, 0xa9b60932, 0x41f962cf, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x40c2, 0x4026, 0x4391, 0x4114, 0x43fc, 0x4369, 0x4099, 0x0707, 0xbfe0, 0xbf8f, 0x3fa9, 0x4220, 0x1d09, 0x4261, 0xba25, 0xb018, 0x2131, 0x41ab, 0x404f, 0x4031, 0xbaf3, 0xb2b6, 0x1e5f, 0x4202, 0x1b23, 0x42e2, 0x2214, 0x4247, 0xba0a, 0x4146, 0xbf5f, 0x0974, 0xa6cd, 0x1ecc, 0x41a9, 0x403b, 0x4312, 0xb08f, 0x4074, 0x4191, 0x4381, 0x404f, 0x45a0, 0xb2e0, 0x0258, 0x1eb3, 0x057e, 0x444f, 0x259e, 0xbfc7, 0x1e86, 0xb080, 0x43cd, 0x1c81, 0x41cb, 0xba3f, 0x423a, 0x4627, 0x4040, 0x32a8, 0x3811, 0x463d, 0x284f, 0x0180, 0xb29e, 0x2fcb, 0x18ee, 0xba4b, 0x4239, 0xba51, 0x42b7, 0xbad0, 0x429c, 0xbf2c, 0x438a, 0x41aa, 0x0bf1, 0x23b2, 0x4393, 0xb27f, 0xafd2, 0xb233, 0x4434, 0x2e78, 0x41f2, 0xbfac, 0xb27e, 0x1ace, 0x40b0, 0x45d2, 0x400e, 0x1fed, 0xb071, 0x239f, 0x4171, 0x18f1, 0xa24f, 0xb2b1, 0x421b, 0x0b5c, 0x464b, 0xba55, 0x424a, 0x4541, 0xbf90, 0x440d, 0xb2de, 0x42a9, 0xb05b, 0xbfce, 0x431a, 0x465b, 0x1108, 0x411b, 0xbafd, 0x4141, 0x40fd, 0x174d, 0x46a8, 0xba6f, 0x2829, 0x0e03, 0x19ed, 0x26d3, 0x4379, 0x40a2, 0xb21b, 0x40bc, 0x185d, 0x4352, 0xb09a, 0xb280, 0x40ae, 0x42a9, 0x4218, 0xba0a, 0x1aa9, 0xbf86, 0xaf41, 0x40df, 0xb2f9, 0x429a, 0xa93c, 0x26b5, 0x22a0, 0xa0ec, 0x1def, 0xb2ea, 0xb231, 0x437b, 0xab30, 0x41ee, 0x1f12, 0x423f, 0x43b6, 0x4218, 0xb0e1, 0x40b9, 0x4175, 0x41aa, 0xbf9f, 0xb04a, 0xb28d, 0xba0e, 0x40f5, 0x1bda, 0x4460, 0x45ce, 0xb253, 0x1fbc, 0x2b3b, 0xb054, 0x216b, 0x1ef5, 0xb239, 0x4117, 0x3e6a, 0x1c59, 0xb212, 0x1e71, 0x435d, 0xb0f6, 0x40af, 0x426f, 0xb267, 0xbfac, 0xb29a, 0x4231, 0x1064, 0xb2bf, 0x42ee, 0xbf90, 0x4194, 0xb2cf, 0x4029, 0x1651, 0x43f7, 0x4091, 0xa737, 0x1c29, 0x36ec, 0x2620, 0x4664, 0xb210, 0x4630, 0xaa7e, 0x4254, 0x4571, 0xbf04, 0x41ad, 0x40b9, 0x407d, 0x06a0, 0xb29d, 0x43af, 0xb2b2, 0xbafc, 0xb2bc, 0xb278, 0xb235, 0x40d5, 0x1e08, 0x3d80, 0x42f6, 0xb01d, 0x4383, 0x08db, 0x428b, 0x43d1, 0x1835, 0x1efa, 0x221b, 0xb057, 0x4051, 0x0216, 0xbfb5, 0xad04, 0x424a, 0x42b2, 0x427a, 0xa99f, 0x2987, 0xb2a1, 0x0a0e, 0xb2d1, 0xbf97, 0x41b9, 0x41d7, 0x18b3, 0x1e5f, 0x3969, 0x323f, 0x1a8e, 0x4643, 0x46e1, 0x4005, 0x435e, 0x4353, 0x35c5, 0x41af, 0x43c6, 0xbaf6, 0x05c4, 0xba7f, 0x41dc, 0x464f, 0x454e, 0xbfe1, 0x3f23, 0x46e4, 0x42e1, 0xb26b, 0x1fe7, 0x44a3, 0x22da, 0x02a8, 0x4366, 0x0239, 0xb2ba, 0x431e, 0x4267, 0xba59, 0x2155, 0x41ff, 0x43b6, 0x4112, 0xb289, 0xb28d, 0xa0b5, 0x415d, 0x4234, 0xbf81, 0x442b, 0x3b05, 0x4359, 0xb2f1, 0xad7d, 0xb2c7, 0x411f, 0x414b, 0x1ce1, 0x41dc, 0xb2ed, 0x4580, 0xba56, 0xb259, 0x407a, 0x1c86, 0xba38, 0x422a, 0x40c4, 0xbf81, 0x411b, 0x1dd0, 0x1eba, 0x3194, 0x3435, 0xb225, 0x43a8, 0x1b6d, 0x231b, 0xbf7d, 0x46ab, 0xb2ed, 0xb0f3, 0xb254, 0xb0e0, 0x41f3, 0x2773, 0x1869, 0x40c3, 0xb25f, 0x1820, 0xbf97, 0x3435, 0x41d2, 0xb2f2, 0x1c37, 0x176a, 0x425f, 0x43f8, 0x400b, 0x23a7, 0xb09a, 0xbff0, 0x414d, 0x4221, 0x283e, 0x43bd, 0x41b9, 0xbaeb, 0x3c41, 0x2790, 0x0b0a, 0xb2e4, 0x1fca, 0xbf80, 0xb0f0, 0xbac8, 0xbfc0, 0xbf7c, 0xba70, 0x41d4, 0x4655, 0x22f4, 0x40bc, 0x4223, 0x42e7, 0x39e7, 0x437c, 0xa249, 0x0634, 0x1fac, 0x413f, 0x2aa4, 0xa70a, 0x4651, 0x424e, 0x4074, 0xbfca, 0x405f, 0x1201, 0xba3f, 0x40ee, 0x40d4, 0x46ac, 0xa9d0, 0x1dc7, 0x41b3, 0x1d14, 0x41c8, 0x332d, 0xb2be, 0x4290, 0x4625, 0xa891, 0xbf70, 0x4335, 0xbf38, 0x42bc, 0xac43, 0x4180, 0x4357, 0x41c2, 0x3ce3, 0x368c, 0x414b, 0x40d4, 0x40a4, 0x4072, 0xbadd, 0x436c, 0x4102, 0x34c9, 0x4430, 0x43e2, 0x407d, 0x018e, 0xa86d, 0x431e, 0x4072, 0xbf74, 0x4271, 0xba6f, 0x4333, 0x180b, 0xb2c8, 0x18ca, 0x40a4, 0x2445, 0xaf56, 0x43a6, 0x438d, 0x41b5, 0xb20a, 0xb0ec, 0x410c, 0x012a, 0x40d4, 0x43e2, 0x40c9, 0x41ce, 0xb2c3, 0xba5e, 0xbf95, 0x4135, 0x1f74, 0x4107, 0x46e4, 0xbfd8, 0x42ed, 0x0c9a, 0x0919, 0x43f9, 0xbfc4, 0x4035, 0x0f3f, 0xbac2, 0xab07, 0x2db8, 0x0c01, 0x4012, 0x1ef4, 0xaae2, 0xb02a, 0xbaf4, 0xbfbc, 0x41d3, 0x4416, 0x40c7, 0x44e4, 0xb2c9, 0x40fa, 0x38e7, 0x42da, 0xba4a, 0x4145, 0x3e87, 0xb018, 0x42d3, 0x07f6, 0x42ec, 0xb2b7, 0x4379, 0xb060, 0xba0d, 0x40ff, 0x2f57, 0xba5b, 0x4263, 0xbf8b, 0x426a, 0x42e1, 0x41b0, 0x461c, 0x438c, 0x33c7, 0x43ef, 0x46b8, 0x1dd3, 0xb233, 0xbf1f, 0x439e, 0x4459, 0x4411, 0xbfd0, 0xab1b, 0xb067, 0xb277, 0x01d0, 0x415b, 0x416e, 0xb20e, 0x4309, 0x423b, 0x0763, 0xbf7e, 0xb04d, 0xb2b9, 0x1c91, 0x4399, 0x4341, 0xba55, 0x40ae, 0xb288, 0xb06e, 0xb2a7, 0x4095, 0x44e8, 0x19a4, 0x42d0, 0x4318, 0xb063, 0x41c6, 0xb284, 0x415f, 0xba44, 0x3c51, 0x4388, 0xb0d2, 0x433a, 0x418e, 0x430b, 0x40bb, 0x4075, 0xbf96, 0x4191, 0xba78, 0x4380, 0x402f, 0xbaec, 0x45da, 0xa7ba, 0xb280, 0x42cd, 0x0e15, 0xbfc0, 0x4292, 0x46c9, 0x44ad, 0xb280, 0x4246, 0xb262, 0xb247, 0x4082, 0x402b, 0xb230, 0xba1b, 0xb077, 0x41f3, 0xbf0c, 0x1901, 0xb016, 0x1edf, 0x3519, 0x4058, 0x418b, 0x42bb, 0xbfd0, 0x014d, 0x43dd, 0xbf52, 0x1b22, 0x434a, 0x41f1, 0x4181, 0xa9a5, 0xb20d, 0xb0cb, 0xbf42, 0x3727, 0xb271, 0x4088, 0xabfb, 0xbf61, 0x1ab9, 0x0344, 0x40cd, 0x41d4, 0x1f44, 0x43b7, 0x298e, 0xbf72, 0x1c94, 0xb0ed, 0x2b19, 0x4427, 0x4337, 0x40c3, 0x3601, 0x19a2, 0x414d, 0x18c7, 0x4006, 0x4313, 0x4253, 0x1d49, 0xbf94, 0xba13, 0xa1dc, 0xb2d7, 0x0b0b, 0x41c8, 0x41b5, 0x1b21, 0x3242, 0x1d53, 0x3ac5, 0xb0ec, 0xba22, 0xa9c9, 0x40ef, 0x4027, 0xb0bf, 0xb00f, 0x211f, 0xb018, 0x1fb8, 0x14c1, 0x410c, 0x4081, 0x4604, 0x3ab7, 0xb20f, 0x4301, 0xbf54, 0x42dc, 0x1861, 0x41bf, 0x405e, 0x1dbd, 0x17ce, 0x46e1, 0x4287, 0xa83c, 0x3e4d, 0xb03a, 0xb2bb, 0x41cd, 0x40a4, 0x430e, 0xb288, 0xb25d, 0x4312, 0xb0de, 0x18b3, 0xb2c9, 0x08aa, 0xba4f, 0xbf61, 0x42aa, 0xb249, 0x2856, 0xa13d, 0x4092, 0x424b, 0x2284, 0xb206, 0x40c5, 0x2801, 0x443b, 0x43df, 0x0c18, 0xbf35, 0x42f2, 0x4031, 0x4048, 0xb225, 0x4215, 0x449c, 0x1ecf, 0x43fd, 0x421a, 0x4584, 0xb03e, 0x2473, 0x16d8, 0x42c8, 0x44db, 0x43bb, 0xba55, 0xba18, 0xb06e, 0x1a1d, 0xb281, 0x23c5, 0xbf72, 0x4288, 0x176a, 0x431a, 0x4392, 0xb0d3, 0x43d6, 0x0263, 0x43e7, 0xbfde, 0x0aab, 0x22c9, 0x4203, 0xba25, 0xb2ed, 0x0d84, 0x3b27, 0x17d9, 0x1c5e, 0xb027, 0x19d8, 0xb22d, 0x4284, 0xada7, 0x44c3, 0x431d, 0x1549, 0xb293, 0xb20b, 0xbaf3, 0x419b, 0x2a68, 0x3d99, 0xbf3f, 0x411d, 0xb2c7, 0x4346, 0x1ea1, 0xbf22, 0x1c0c, 0x16ab, 0x1a2e, 0xb0c8, 0x4253, 0x432b, 0xa1c3, 0xbadb, 0x408b, 0x4339, 0x064c, 0x43be, 0x421d, 0x439c, 0x4042, 0x4197, 0x11d2, 0xbf18, 0xbad3, 0x1e4a, 0x4239, 0x2672, 0xb267, 0xa0f4, 0x4240, 0xba29, 0x42a2, 0x4225, 0xbf73, 0x4263, 0x0e6d, 0x43a7, 0x1be0, 0x4460, 0xba4b, 0x42ad, 0x406a, 0x4002, 0x40c9, 0x41e8, 0xb237, 0x4281, 0x4378, 0x4473, 0xb297, 0x3829, 0xbf60, 0x4110, 0x426d, 0x1b20, 0x1ade, 0xbac1, 0x4227, 0xb216, 0x41ed, 0x430d, 0xbf46, 0x4200, 0xb22b, 0x46ed, 0x1496, 0x180b, 0x3e53, 0x4601, 0x4404, 0xbf37, 0x42a3, 0x24ec, 0x3704, 0x2ead, 0xb041, 0xb239, 0xb0f5, 0x418b, 0xbaef, 0xba37, 0x413c, 0x4076, 0xbada, 0x35ef, 0x40cf, 0xbf8b, 0x4379, 0x3548, 0x1edb, 0x41ea, 0x1d56, 0xba6a, 0xba46, 0xb0ac, 0x33d6, 0x40e1, 0x21ce, 0xbf8f, 0xba17, 0xb257, 0xba54, 0xba5f, 0x26c7, 0x40fc, 0x4283, 0xbf04, 0xb280, 0x43de, 0x1eed, 0x3a39, 0x401d, 0x4310, 0xba5b, 0x4656, 0x404c, 0xbfd9, 0x465b, 0xba4b, 0x4656, 0x438a, 0x4347, 0xa967, 0x4049, 0x41c0, 0x1930, 0xb040, 0x433f, 0x1c50, 0xbf54, 0x41bf, 0x4027, 0xbf03, 0xb0dd, 0x42b3, 0x435a, 0xb292, 0x404c, 0xb290, 0xba28, 0x400b, 0x4092, 0x406e, 0x4318, 0x442c, 0x0c48, 0xa5ad, 0x4148, 0x424d, 0x4279, 0x40b1, 0x4331, 0xb27b, 0xb049, 0x1cae, 0x45ab, 0x4319, 0xb06e, 0x40b7, 0xbf1c, 0x4484, 0x1ce2, 0x439b, 0x41d4, 0xb28f, 0x4038, 0x45c1, 0xaf03, 0xa786, 0x409e, 0x0b05, 0x1295, 0x3ba6, 0xa9df, 0x42df, 0xba11, 0xb2d0, 0xba4e, 0xbf38, 0x43a1, 0x3a98, 0x4163, 0x41e4, 0x40e0, 0x4619, 0x4259, 0x1b6a, 0xbf81, 0x396c, 0x3ebc, 0xb2e6, 0xad08, 0x4350, 0x13f0, 0x1898, 0x1f15, 0xbfaa, 0x4335, 0x321b, 0x4189, 0x40b1, 0x0a31, 0xbfa3, 0x3fc0, 0x2d58, 0x43ad, 0xa85a, 0x40af, 0x423d, 0x02b8, 0x1dfe, 0xbac5, 0x4342, 0x40e1, 0x41fb, 0xab7c, 0x28a1, 0x4219, 0x098d, 0x4085, 0xb20e, 0x2e27, 0x0b99, 0x4403, 0xb2c0, 0x405d, 0xb0bc, 0xb0cb, 0xbf98, 0x42c0, 0xb207, 0xb250, 0x40d5, 0x19ff, 0xb2bb, 0x42cc, 0x42d6, 0xae29, 0xba31, 0xbfdd, 0x3003, 0x0f3d, 0xb091, 0xba54, 0x41ed, 0x40e3, 0x20e4, 0xb012, 0xb2c2, 0x409d, 0xb28e, 0x080e, 0xaedd, 0x06d2, 0xb226, 0x4160, 0x4059, 0xb229, 0x040c, 0x4223, 0x422f, 0x3772, 0xb2b6, 0x417e, 0xb21b, 0xbacb, 0xb2d4, 0x2fb1, 0x45e2, 0xbfc9, 0x41f2, 0x42c9, 0x0d4f, 0x4300, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x82916b79, 0x515ecb9e, 0x9eeb80a7, 0x4448bdb6, 0x9fbc4379, 0x7f419a33, 0x42be67d1, 0x712a0dae, 0x4382a2e5, 0x7d2ed79f, 0xe8c9993b, 0x9fd1abb1, 0xed4e4991, 0xf7cf8b5c, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0xa000b5cd, 0x00000000, 0x00000008, 0x00000000, 0x00000000, 0x00000000, 0x0000b55a, 0x00000000, 0xf7cf8df7, 0xd1933276, 0xe8c9993b, 0xf7cf8df7, 0xd1942582, 0xf7cf8df0, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x45f6, 0xa7e3, 0x4240, 0x4552, 0x19ea, 0x1ee9, 0x4223, 0xb029, 0x03fc, 0x41ff, 0x4699, 0x4543, 0x40eb, 0x1189, 0xb2eb, 0x2ed0, 0x46c5, 0x152e, 0xbf8b, 0x42ee, 0x454e, 0x2a1e, 0x3168, 0xa0a7, 0x1fa6, 0x415b, 0xb286, 0x3cff, 0x35a2, 0x405c, 0x4228, 0x40e1, 0x43e2, 0x40c6, 0x37cc, 0xbfda, 0x4320, 0xba70, 0xb067, 0x43f0, 0xb2b7, 0x4213, 0x4247, 0xb229, 0xb2f5, 0x42e7, 0x40cd, 0x1bdf, 0x43f8, 0x3887, 0xa60a, 0xba2e, 0x1b0d, 0x1e22, 0xba51, 0x437e, 0x45e4, 0x466e, 0xb017, 0x29df, 0x4057, 0x19e9, 0xb0cd, 0xbf34, 0x4466, 0x1283, 0xb0de, 0x42ff, 0x1c85, 0x4430, 0x42b0, 0xba35, 0x4274, 0x41e6, 0x4331, 0x42dd, 0x40ec, 0xbf43, 0xb23f, 0x405d, 0xba5f, 0x4563, 0x432c, 0xba0d, 0x43a2, 0x2a71, 0x439d, 0xb26e, 0x4685, 0xb086, 0xb217, 0xb264, 0x4412, 0xbff0, 0x4388, 0x18ff, 0x43c7, 0x407f, 0x4174, 0x410b, 0x4324, 0x469c, 0x410a, 0x4302, 0xbaff, 0xbfa5, 0x4371, 0xb26b, 0xb079, 0x42f6, 0xbf8f, 0xb251, 0x411a, 0x42e3, 0x21bb, 0xba73, 0xb2c3, 0x3374, 0x0cd7, 0x4128, 0x1ca9, 0x40fd, 0xae9f, 0x40fc, 0x1df9, 0x44a8, 0x1d1d, 0x4061, 0xb08f, 0x40f0, 0x4375, 0xb244, 0x45f3, 0x404a, 0x406e, 0xbf5f, 0x4146, 0x41ff, 0x4065, 0x4071, 0x4391, 0x0932, 0x42c2, 0xba62, 0x4146, 0x402b, 0x424f, 0x4263, 0xba47, 0xb0df, 0xbf79, 0x419b, 0x40d5, 0xbfa0, 0x4157, 0xb2e6, 0x42f3, 0x4216, 0x4174, 0x40b0, 0x3c8e, 0xa763, 0x0bb7, 0xba04, 0x45de, 0x4598, 0x1c37, 0xb250, 0x414b, 0xb299, 0xba17, 0xb0c3, 0xa7f5, 0x1e74, 0xbfc7, 0xb23b, 0x42ad, 0x4235, 0xb270, 0x11c2, 0x436f, 0x42ac, 0x45f4, 0x1221, 0xb26e, 0x46a4, 0xb283, 0x2610, 0xb05b, 0x098e, 0x1946, 0x439c, 0xa1ce, 0x4057, 0x16ac, 0x4357, 0xb2d1, 0x4231, 0x4300, 0xae54, 0xbf70, 0x40f3, 0xb2e8, 0xbf2d, 0x1b23, 0xba0a, 0xb0fc, 0x280d, 0x423d, 0xba50, 0xb29c, 0x416e, 0x41f3, 0x42f3, 0xb06a, 0xabca, 0x24ba, 0x1ba6, 0xb2ef, 0x41ec, 0xb22a, 0x1b17, 0xaa10, 0x41b2, 0xba10, 0x1fa7, 0x3414, 0xbacb, 0x4439, 0xbf90, 0xae5e, 0x41a1, 0xbfd4, 0xba4d, 0xb271, 0x0305, 0x422a, 0x411a, 0x4194, 0xb20e, 0xb29b, 0x41b3, 0x46a1, 0xb2c4, 0x3014, 0x40dc, 0x36e7, 0x432d, 0x09b3, 0x430c, 0xb01f, 0x12d7, 0x40aa, 0xb2d9, 0x055b, 0xbaee, 0x42d2, 0x43b4, 0x1875, 0xbf51, 0xa093, 0x226a, 0x284d, 0xb29f, 0x44a4, 0x3de8, 0x35aa, 0x465e, 0xb266, 0xb0a1, 0xb2f5, 0x437f, 0x4250, 0xa47f, 0xbfb4, 0x273b, 0x1d3e, 0x15c1, 0x1c60, 0x0830, 0x0bcd, 0x4255, 0x44db, 0xbf1b, 0x434d, 0x2582, 0xb094, 0x1fa4, 0xa542, 0x05e2, 0xb29f, 0xba52, 0xb00f, 0xb2f3, 0x4285, 0x1a85, 0x4636, 0xb09e, 0x2a91, 0xbf90, 0xb28f, 0xbf80, 0xb234, 0x18fc, 0xbf2d, 0xb07c, 0x41bf, 0x425d, 0x419e, 0x4327, 0x4026, 0x4241, 0xa452, 0x0cbc, 0x41f0, 0x46f4, 0x42d6, 0x4464, 0x1f9f, 0xba60, 0xbfe4, 0x42a5, 0x40a2, 0xb2bf, 0x405f, 0xb2f5, 0x4333, 0x43af, 0x435d, 0x191d, 0x4002, 0xbf68, 0x42a3, 0xb0ee, 0x26e9, 0xbfcc, 0x3226, 0x4342, 0x4280, 0x1715, 0x19cc, 0xb2ed, 0x4407, 0x2653, 0xbaf6, 0xb2c4, 0x43c4, 0xb2b1, 0x4131, 0x1090, 0x41e3, 0x4217, 0xbf90, 0xba7f, 0x401d, 0xb268, 0x408f, 0x4287, 0x4257, 0xbfce, 0x4185, 0x1e28, 0x4265, 0x45ec, 0x43da, 0x422e, 0x2405, 0x00ae, 0xb269, 0xb2c5, 0xbfb9, 0x3700, 0xb067, 0x41ab, 0x408c, 0x4053, 0x4023, 0xbfc6, 0x1c83, 0x4078, 0xace2, 0xbf00, 0x390d, 0x4247, 0x4343, 0x4291, 0x44e4, 0x18eb, 0x4214, 0x4044, 0xb0ae, 0xbacd, 0x4171, 0xb0b7, 0x42f1, 0x4045, 0xb277, 0x40dc, 0x19a8, 0xbf91, 0x09fd, 0xb21c, 0xba3e, 0x41a5, 0xbad5, 0x1abb, 0xbfbd, 0x3179, 0x22a8, 0x1555, 0x31a9, 0xbfe0, 0x420d, 0xbf7d, 0x428d, 0xbaea, 0x1e6b, 0x2b66, 0xb05d, 0x1ac3, 0x4176, 0x41c6, 0x41c5, 0x421b, 0xb2ec, 0x41e9, 0x2cf0, 0x4386, 0x4108, 0xbf1f, 0x4066, 0x181d, 0xb2a1, 0x43f5, 0x43a1, 0x00a7, 0x42ff, 0x1f65, 0x43aa, 0xb24d, 0x0ab5, 0x42ee, 0x2d96, 0x456b, 0xbaec, 0x3863, 0xbaf2, 0xbafd, 0x1f60, 0xb2f5, 0x4144, 0x2be1, 0xaca3, 0xb2a9, 0xbf96, 0xaed3, 0xac10, 0x404d, 0x4040, 0xbac2, 0x228d, 0x4144, 0x43d7, 0x45e5, 0x419f, 0x1d89, 0x4463, 0x311c, 0xbf4a, 0x1cd6, 0x4618, 0xb2db, 0x43fa, 0x40ae, 0xb0af, 0x32a0, 0xb2fe, 0xaef6, 0x1de3, 0x4302, 0x42a0, 0xabb6, 0x411e, 0x439b, 0x464b, 0x406f, 0x2a8b, 0x38cd, 0xbfc2, 0x422d, 0xb068, 0x4327, 0x4328, 0x4109, 0xbf4b, 0x0a21, 0x44b3, 0x4089, 0x40bd, 0x18ca, 0x4112, 0xba1f, 0x0bf6, 0x31e8, 0x41bf, 0x45c1, 0x4354, 0x2f9f, 0x0993, 0xba25, 0x4010, 0x1a4b, 0x43ab, 0xbf8e, 0xbacb, 0xa49a, 0x42f5, 0xb029, 0xbfc8, 0x4376, 0x40af, 0x464f, 0x01c2, 0x43da, 0x4251, 0x40a8, 0x422c, 0x438e, 0xbacf, 0x0b64, 0x437d, 0xa181, 0x40ca, 0x411f, 0x4677, 0x4353, 0x2f02, 0x43f2, 0x05fe, 0xb2e2, 0xb083, 0xbf3a, 0x303c, 0xb007, 0x24d0, 0xb27d, 0xb216, 0x3049, 0xba34, 0x4399, 0xb27a, 0x2f9c, 0xb006, 0x223e, 0x0844, 0x4044, 0x436a, 0xb276, 0xb2b6, 0xba1e, 0xbf52, 0x425c, 0xbaee, 0x42d5, 0x417d, 0x4187, 0xbaf2, 0x4238, 0x402b, 0x4206, 0x41cd, 0x05b2, 0x402d, 0x408b, 0x4681, 0x419d, 0xb0b5, 0xb28d, 0xbfe8, 0x27bb, 0x25c4, 0x02ee, 0x40e2, 0x435b, 0x40ad, 0x43ea, 0x1fd2, 0x4065, 0x1d5a, 0x4347, 0xb2d5, 0x436f, 0xae55, 0xba71, 0x0f2c, 0x4187, 0xbf73, 0xb20a, 0x44eb, 0xa5dd, 0x40ed, 0xba53, 0xba13, 0x4191, 0x42e5, 0x4193, 0x4168, 0x40ab, 0x4476, 0x4135, 0x41c3, 0x4145, 0x353e, 0xbfd0, 0xb220, 0xa3e1, 0x42a1, 0xb2d3, 0xb2af, 0x433d, 0x412a, 0x28d7, 0x411d, 0x42bc, 0x4155, 0xbfd5, 0xac98, 0x4353, 0x4142, 0x4435, 0x03bd, 0x402c, 0xb056, 0xb08b, 0x460f, 0xb284, 0x45e0, 0x4240, 0x265c, 0xba4a, 0xa052, 0xa53d, 0xbae5, 0xb2a0, 0xbf31, 0x413b, 0x1ecb, 0x4234, 0xb27a, 0xb204, 0xac49, 0x4387, 0xb064, 0xbf80, 0xbfd1, 0xb229, 0x4342, 0x41b5, 0x43f9, 0x43a7, 0x405e, 0x449d, 0x43c1, 0xba4e, 0x4181, 0x40a6, 0x238d, 0x4280, 0x422f, 0x401a, 0xbf70, 0x09df, 0xa4e7, 0x4040, 0x37cc, 0xbfa2, 0x300b, 0x013f, 0x423e, 0x42ab, 0x412a, 0x45b3, 0xb2f0, 0x405b, 0x12ca, 0x40a4, 0xabdf, 0x43ab, 0x4383, 0x43c4, 0xba14, 0x4105, 0xbf05, 0x14da, 0x4294, 0x4065, 0x40f4, 0xb246, 0x4179, 0x1d11, 0xb240, 0x283b, 0xb2df, 0xbf75, 0x1b5a, 0x435a, 0x05db, 0xbaf6, 0x4399, 0x4394, 0x42b3, 0x1fdc, 0x40e4, 0x41ba, 0x432e, 0x438f, 0xb2e0, 0x3f84, 0xb003, 0x137f, 0xb051, 0x419f, 0xba3e, 0xbad0, 0xb208, 0x417b, 0x431f, 0x2355, 0xb25c, 0x266c, 0x43f6, 0x4478, 0xbfa8, 0x42af, 0x4167, 0x2ef4, 0x42b2, 0x411a, 0x4151, 0xba77, 0x4391, 0x27b8, 0xba78, 0x0a0c, 0xb2d7, 0x4595, 0x41e3, 0x40dc, 0x41e5, 0x41ec, 0x1c70, 0xbfe4, 0x41bc, 0x4109, 0xb2fd, 0xb292, 0x0159, 0x240f, 0x4363, 0x4398, 0x4371, 0x1ba9, 0x4088, 0x3e3e, 0x4016, 0x4363, 0xb2c2, 0x05b5, 0xbfb0, 0xba63, 0x24f5, 0xb23c, 0xbf03, 0x4146, 0xb2a1, 0x380d, 0xb267, 0x3bde, 0x4092, 0xb274, 0x251e, 0x421c, 0x0687, 0xba79, 0x059e, 0x429b, 0x45b9, 0x0d1c, 0x45a6, 0x1185, 0x46a3, 0x0230, 0x4128, 0x1b96, 0x3e30, 0xbaca, 0x418f, 0x43d4, 0xbfda, 0xb0e8, 0xbf80, 0x3cf6, 0x4264, 0x4005, 0xa0bc, 0x3a93, 0x1777, 0x454d, 0xbad4, 0x4066, 0x414f, 0x181b, 0x1b26, 0x40d9, 0x42bd, 0xba42, 0x1930, 0x0023, 0x405b, 0xbf3b, 0xad73, 0xb278, 0x4111, 0xbadc, 0x4276, 0xaa86, 0x4310, 0x441a, 0x0ee1, 0x2eb8, 0x4379, 0x4268, 0x43ed, 0xb268, 0x187e, 0x4221, 0x2188, 0xb05a, 0x43e6, 0x43ed, 0x4148, 0xb2cf, 0x4491, 0xbf3f, 0x0b27, 0x1cc0, 0xb236, 0x1a4a, 0x43fe, 0x43c3, 0xba58, 0x4326, 0xb0d9, 0x1a6c, 0xa71b, 0x1461, 0x40cc, 0x41ba, 0x1d96, 0x42aa, 0x19d1, 0x1413, 0xba22, 0xb2a4, 0xba5b, 0x441f, 0x0936, 0x3137, 0x4491, 0xbf76, 0xb0dd, 0xa93b, 0xb0a4, 0xb09d, 0x1677, 0x1dd3, 0x40fa, 0xa6a6, 0x4054, 0x424f, 0xb008, 0x4650, 0x401f, 0xab43, 0x43a7, 0x1a59, 0x4034, 0xbacd, 0xaff8, 0xb062, 0xbfc3, 0x4081, 0xba07, 0x459e, 0x4037, 0x1f32, 0xba28, 0x43b7, 0x0c12, 0x4133, 0x1b17, 0xbf94, 0x4286, 0x4212, 0x41cd, 0x1953, 0x4049, 0xba1c, 0x43cc, 0x1c4e, 0x43ac, 0x432d, 0x4616, 0x45c3, 0x3f22, 0x0c24, 0x2bbe, 0x414e, 0xbf51, 0x41ba, 0x3fed, 0x42b0, 0xb2eb, 0x1a7a, 0x1d81, 0x43d0, 0x4358, 0x1f1a, 0x4362, 0xba60, 0x42a1, 0x40e8, 0x43f5, 0x1c51, 0x4249, 0xba7b, 0xbf27, 0x4169, 0x4296, 0xbf70, 0x4344, 0x4356, 0xbaff, 0x436f, 0x42d8, 0x0bc9, 0xba0d, 0x428e, 0x4038, 0x40f8, 0x1ba6, 0x428e, 0x417a, 0x44b1, 0xb0cc, 0x4328, 0x4187, 0x4090, 0x41d3, 0x458c, 0xba28, 0xbfba, 0x42e5, 0x459b, 0xb27c, 0x42ec, 0x2a3e, 0x408c, 0x3be5, 0x2aec, 0xbf0d, 0xb282, 0xba27, 0x43fa, 0x419b, 0x43c8, 0x1934, 0x445c, 0xbf86, 0xb0e9, 0x4240, 0x4282, 0x044a, 0xb240, 0xaa3d, 0x424a, 0x39f5, 0x0ee1, 0x41ab, 0x1a6e, 0x4324, 0x0e64, 0x41e5, 0xbf8c, 0x4170, 0x19eb, 0x3ff4, 0x4404, 0xb021, 0x435a, 0x4380, 0x46b0, 0xb2ea, 0x24d2, 0x40ed, 0x42d8, 0x40e1, 0x4245, 0x4108, 0x19c7, 0x428d, 0x422e, 0x4244, 0x260e, 0xb045, 0xb060, 0x1ab1, 0x40b1, 0xb0b9, 0x42eb, 0xbf9e, 0x1a35, 0xb24a, 0x4411, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xdb27f4a8, 0x745a69a1, 0xc76c58a7, 0xcb93d578, 0x59551d1e, 0xdd275e29, 0xa41a07ab, 0xcd4584a9, 0x8cd3e811, 0x92d54b8c, 0x7a714058, 0xd6ca1b2d, 0xbb97bd53, 0x4fb916ee, 0x00000000, 0xc00001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x00034000, 0x00000000, 0x0382fe19, 0x00000000, 0x0000000e, 0x0000000e, 0xffffff0c, 0xf47d00e9, 0x1f5a553b, 0x7a714058, 0x00000000, 0x00000000, 0x605fe8c0, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4283, 0xa90f, 0x4210, 0x1dab, 0x4172, 0x40c4, 0x40b1, 0x44d5, 0x2a9b, 0xba65, 0x4002, 0x1842, 0x308f, 0xb226, 0x42c5, 0x425c, 0x40fd, 0xb279, 0xba34, 0x4284, 0xbf60, 0x3d5d, 0x420c, 0x401a, 0x0e39, 0xbf79, 0x4395, 0x1ccb, 0x41ae, 0x1ddc, 0x4062, 0x44db, 0x2e06, 0x4389, 0x1d42, 0x1c39, 0x4347, 0xb298, 0xb007, 0x43a7, 0xb22a, 0xba22, 0xb22c, 0xba54, 0x4304, 0xa8d3, 0x42a8, 0x2c0a, 0x40a3, 0x2b03, 0x1b34, 0xb2ef, 0x06d3, 0xbf3a, 0x1d56, 0x1ef0, 0x0fa3, 0xb2a8, 0x1939, 0xbff0, 0x422c, 0x019c, 0x1f8e, 0x42cb, 0xbae4, 0x17be, 0x404e, 0xb2d3, 0x1fa5, 0xb209, 0x42ae, 0x2bb5, 0x424e, 0xb273, 0x4202, 0xba33, 0xb2af, 0x1d35, 0x1a2f, 0x1cf0, 0xb266, 0x2255, 0xbf3f, 0xac6b, 0x42f7, 0xb092, 0x1263, 0x46a9, 0x4099, 0x105a, 0x18b5, 0x34aa, 0x1b91, 0x4129, 0x1449, 0x4096, 0xb27a, 0x437a, 0x40f5, 0xb27d, 0x1628, 0xb242, 0x42da, 0x404b, 0xbfbb, 0xb077, 0x4297, 0x0570, 0x4152, 0x2525, 0x1be3, 0xbfe0, 0x4633, 0x12e2, 0x4547, 0xba67, 0x06a6, 0x40c9, 0x371b, 0xbfb0, 0x4201, 0x0101, 0xba0d, 0xbfd9, 0x4192, 0x2123, 0x4117, 0x428e, 0x4084, 0xba04, 0x417c, 0x4390, 0x41e5, 0x41ad, 0x462e, 0x46da, 0x4268, 0x40b4, 0xb279, 0xa5df, 0xb26a, 0xbf4d, 0x356c, 0x09bf, 0xb06e, 0xb292, 0xb2f2, 0x1897, 0x0d15, 0xb261, 0xb29b, 0x40e6, 0x3e3a, 0x46ba, 0x3342, 0x1d7b, 0x401c, 0xb0ce, 0x177f, 0x430d, 0x0189, 0x43ba, 0x4309, 0xb20f, 0xb228, 0xac9c, 0x086d, 0x13e5, 0xbf61, 0x1fc8, 0x41b5, 0x2273, 0x40ab, 0x4334, 0x43fd, 0xbf6d, 0x4304, 0x4286, 0x4372, 0xb213, 0x4426, 0x02e4, 0x40da, 0xb2bd, 0x441a, 0xbf79, 0x40c6, 0x1e05, 0x1b15, 0x420d, 0x4248, 0xbf60, 0xb093, 0x39aa, 0x40c8, 0x085d, 0x37e8, 0x400a, 0x15dc, 0x218d, 0xb223, 0x1910, 0xb248, 0xb00d, 0x4168, 0x44c2, 0x418d, 0xb0e5, 0xb0b2, 0x33e7, 0xa4e7, 0x43e3, 0x0066, 0x165b, 0x4231, 0xbfa7, 0x2e38, 0xb249, 0x431a, 0x4089, 0xba16, 0xa64d, 0x4181, 0xbfd0, 0x3a3f, 0xb258, 0x44cd, 0xaa78, 0x41d7, 0xa0cb, 0x4279, 0x4061, 0x41de, 0xb2bc, 0x4125, 0xae63, 0x41bc, 0x418f, 0xba71, 0xa347, 0xbf4f, 0x4666, 0xb204, 0x4202, 0x42db, 0xb006, 0xbf7b, 0x0978, 0x1c50, 0x1bc7, 0x1818, 0x053b, 0xbfb5, 0xa485, 0x4134, 0x27f9, 0x2199, 0xae03, 0x4126, 0x46e5, 0x1a12, 0x1ee6, 0xb22d, 0x2692, 0x4361, 0x411d, 0x4064, 0x4091, 0x3d71, 0x3936, 0xb2a0, 0x34de, 0xbfdf, 0x4552, 0x1bb7, 0x1a4a, 0x1264, 0x40d4, 0x1d83, 0x4227, 0xbfe0, 0x0a4d, 0x429b, 0x3aa5, 0x4055, 0x43f3, 0x1c83, 0xbf5a, 0x4567, 0x43f6, 0xa01f, 0x1cdc, 0x40bd, 0x41c3, 0x4223, 0x3fea, 0x422b, 0x3d0a, 0x18f7, 0x432d, 0x1e00, 0xbf04, 0xaf5d, 0x1b42, 0xb0e2, 0x46e4, 0xbf09, 0x463a, 0x4355, 0xb0dd, 0x44d2, 0xb24c, 0xba2b, 0xa14c, 0xbfc0, 0xb2b7, 0xba12, 0x43f1, 0xb294, 0x41b6, 0xb247, 0x0688, 0x43e8, 0x42c7, 0x4155, 0x43c4, 0x44fd, 0xa8d2, 0x19a7, 0xb2d5, 0xb035, 0xb2a8, 0x43fd, 0x455d, 0x4314, 0x1f85, 0xbf2c, 0x4287, 0x4154, 0x4204, 0xbf06, 0x4201, 0x408b, 0x40f5, 0x43f3, 0x43b4, 0x4329, 0x42ab, 0x45d0, 0xb093, 0x434c, 0x0f4f, 0x1d8c, 0x402f, 0xba58, 0x444c, 0xb294, 0x41b1, 0x42a7, 0x4177, 0xbfc7, 0x4560, 0xba10, 0xb28a, 0x41f7, 0x40c1, 0xb21b, 0x4068, 0xacc3, 0x41b5, 0x0829, 0x42eb, 0x408f, 0x46f9, 0x1c2f, 0xa7ff, 0xb27e, 0xb2a2, 0x194b, 0x4182, 0x3acc, 0x1527, 0x4143, 0x40f4, 0x3a3c, 0xb2ea, 0x44b5, 0x4076, 0xbf69, 0x34c1, 0x43ce, 0xb299, 0x18d5, 0xbfa0, 0xb24d, 0xbf15, 0x427e, 0xb003, 0x4289, 0xba4b, 0x356d, 0xb2a7, 0xa497, 0xba5a, 0x405e, 0xb249, 0xb2a7, 0x4176, 0x426b, 0xb204, 0xaea9, 0x4110, 0x2479, 0x3502, 0x46ca, 0xbfbc, 0x4321, 0x41f4, 0x4170, 0xba41, 0x1d2c, 0xba4a, 0x11a4, 0x009a, 0x28c5, 0x43d3, 0xbfcf, 0x4034, 0xabeb, 0xaac6, 0xa608, 0x4056, 0x338f, 0x0394, 0xb02f, 0x4274, 0xa5c6, 0x1be8, 0x40fc, 0x4029, 0xb2b5, 0x0227, 0x090b, 0xbacc, 0x1116, 0x2c45, 0x08ee, 0x14fa, 0xbf82, 0x405a, 0x423f, 0xba2a, 0x4052, 0x4200, 0xa671, 0x0efd, 0x42de, 0x1e89, 0xb049, 0xb264, 0x3e26, 0x41d5, 0x4445, 0x32af, 0xb0e8, 0xbafb, 0xb0f7, 0x4093, 0xb034, 0xb2e8, 0x2e69, 0xb259, 0x2479, 0x439d, 0xb2de, 0x1b5b, 0xbf8c, 0x437b, 0xbad1, 0x217d, 0x4093, 0xb2e6, 0x1972, 0xb2e5, 0x1d51, 0x4210, 0x4310, 0xb20c, 0xad06, 0x1879, 0x40b7, 0x4077, 0xbfbd, 0x1fa9, 0xb00b, 0x40bf, 0xbaf2, 0xa690, 0x4298, 0xa00d, 0x2c47, 0x1e51, 0x41c2, 0x42dc, 0xbf6e, 0xb0c0, 0x2ddd, 0x43b9, 0xba69, 0xa0f1, 0x1c40, 0xbfe0, 0xba04, 0x4263, 0xba26, 0xa397, 0x1731, 0x0de3, 0x40bc, 0xa1e7, 0x411c, 0x4005, 0x417a, 0xb043, 0x43d3, 0x42a1, 0x4053, 0x42e8, 0xb0eb, 0xbf16, 0x1c21, 0x4442, 0xbf80, 0x4164, 0x401e, 0x41e5, 0x18d4, 0xb241, 0x45ed, 0x4037, 0xba05, 0x415a, 0xbf25, 0xa2a9, 0x0e62, 0x26af, 0x182b, 0x4134, 0xa7ee, 0x4212, 0x42bd, 0x4243, 0x402a, 0x4182, 0xbf2e, 0x16a0, 0xb213, 0x42b4, 0xb2fe, 0xb20f, 0xb23c, 0x0bb8, 0x43da, 0x4265, 0x42cb, 0xb02a, 0xb226, 0x43db, 0x421e, 0x41e7, 0x1f20, 0x22ca, 0x41d9, 0x1e79, 0xb00b, 0x41cf, 0x401b, 0x3174, 0xb0f4, 0x404a, 0xbf03, 0x4674, 0xb243, 0x43cb, 0x431a, 0x43cd, 0xb0af, 0x0481, 0xaca0, 0x419a, 0x3433, 0x43dc, 0x4197, 0x3d98, 0x19f0, 0x21f1, 0x42d2, 0xba2f, 0xa60c, 0x4337, 0x4102, 0x2851, 0xba4b, 0x44db, 0x4432, 0x422c, 0x4339, 0x4002, 0x1e4d, 0xbf7d, 0x434f, 0xb20d, 0xb00e, 0x3545, 0x41a7, 0x40bb, 0x03ab, 0xba58, 0x2faa, 0x45eb, 0x0643, 0x4098, 0xb2ff, 0xb038, 0xa185, 0x3d65, 0x062b, 0xb010, 0xb20e, 0x1fab, 0x417d, 0x46bc, 0x080c, 0x1913, 0x4146, 0xbfd3, 0x42af, 0x064d, 0xbaeb, 0xba5c, 0x2be1, 0xbf90, 0x404a, 0x0e2c, 0xba56, 0x434e, 0x4320, 0x4180, 0x4658, 0x42d3, 0x4281, 0x1986, 0x424a, 0x4281, 0xb28f, 0x42db, 0xbf6b, 0x41da, 0x42d3, 0xbaed, 0x43de, 0x1d8d, 0x04df, 0x1e09, 0x421f, 0xb21a, 0xb2eb, 0x41cf, 0xbff0, 0x44bb, 0x1ce0, 0x185f, 0x2ceb, 0x42a9, 0x1d0f, 0x4000, 0x1c0c, 0x4127, 0xba21, 0xbac3, 0x3822, 0x15bb, 0x3d53, 0xbf47, 0xb064, 0xb075, 0xb258, 0xba4e, 0x4314, 0x466c, 0x41d2, 0x1a9c, 0xb056, 0x413e, 0x4089, 0xb0d0, 0x40c2, 0x4202, 0x421f, 0x1ca7, 0xb2bc, 0x3348, 0xb280, 0xbf6a, 0xb0ae, 0x016e, 0xb0a7, 0x4672, 0x4147, 0x3098, 0xa236, 0xb00f, 0xbf1a, 0x4330, 0x42ec, 0xbf00, 0xba37, 0xb2b5, 0x4182, 0x4075, 0xb038, 0x413e, 0xba59, 0x45eb, 0xba2b, 0xbaf7, 0x44b1, 0x1dd1, 0x417b, 0x1a2c, 0x40d5, 0x438a, 0xb03a, 0x1d73, 0xb21e, 0xb25d, 0xb27f, 0xbf1e, 0x430a, 0xb0ec, 0x4159, 0x2733, 0x4247, 0x424a, 0xbf4a, 0xb263, 0x1b5f, 0x40ae, 0xb2fe, 0xbf76, 0x407d, 0xb28a, 0x1f72, 0x1e7c, 0x463c, 0x42c2, 0x40e7, 0x4166, 0x4380, 0x1603, 0x1b69, 0xb212, 0x1739, 0x4377, 0x3103, 0xbf49, 0x40ac, 0x278e, 0xb07d, 0x45c1, 0x1ef5, 0xbff0, 0xb2a0, 0x4379, 0x1956, 0xb233, 0xb26d, 0xbfdf, 0x463b, 0x4096, 0xaeb4, 0x17d4, 0x4663, 0xba6b, 0x4640, 0x3004, 0x404c, 0x46aa, 0xbaed, 0x3318, 0x44e5, 0x1d45, 0x1c9c, 0x1883, 0x425e, 0x0c46, 0xba40, 0xa97f, 0xbff0, 0x0b6c, 0x425e, 0x4207, 0x420c, 0x4257, 0xbf86, 0xabef, 0x40d0, 0x4050, 0x43bf, 0x38f6, 0xb240, 0x3692, 0xbf9c, 0x4288, 0x0c04, 0x41a9, 0xac55, 0xb0b2, 0x414c, 0x1b78, 0xb003, 0x3531, 0xb280, 0x0064, 0x4437, 0x1c72, 0x1f4c, 0xbf1c, 0x435e, 0x40fd, 0x3a0e, 0xbfa0, 0xba1b, 0x4060, 0xba2e, 0x3a97, 0xbaf7, 0x39bd, 0x4264, 0x4472, 0x4328, 0xbf49, 0x1acf, 0x42f6, 0x42b4, 0xb294, 0xb272, 0xb090, 0x4654, 0x4017, 0x467f, 0x468a, 0xace4, 0x44ed, 0x4557, 0x466a, 0x1257, 0x4153, 0xb05e, 0x4121, 0x41b0, 0x4393, 0x410b, 0xbaf3, 0x4556, 0x4242, 0x1f43, 0xbf1d, 0x42c3, 0xb240, 0x4038, 0x404b, 0x43e7, 0xb0fa, 0x420a, 0xbf9d, 0x4323, 0xb0a3, 0x4340, 0x42e9, 0x0052, 0x1cc4, 0x4305, 0x020b, 0xb09d, 0x411d, 0x2c14, 0xb0ee, 0x416c, 0x38f9, 0x4357, 0xb2b9, 0xb21f, 0x4152, 0xbfa4, 0xa537, 0x1f40, 0x0f0b, 0xa47e, 0x4040, 0xbade, 0x401b, 0xb006, 0x0394, 0x2d32, 0xbfba, 0x438b, 0x42b3, 0xb2b0, 0xa573, 0x02fc, 0x1ac5, 0x28b6, 0x43c8, 0x2d0f, 0x3b61, 0x423d, 0x262f, 0x411e, 0x43c5, 0x435d, 0x03e2, 0xae57, 0x1b6e, 0x4238, 0x4110, 0xbf31, 0x2052, 0x38bb, 0x454c, 0x2b69, 0x4009, 0x0ac4, 0x0dad, 0x41f9, 0x1f6e, 0x414d, 0x30e6, 0xb2ea, 0xbfa0, 0x0fbf, 0x42e4, 0xbfe0, 0x400e, 0x061f, 0x00c8, 0xba25, 0x427c, 0x38db, 0xbf5a, 0x44bd, 0x42c9, 0x417e, 0x032e, 0x4205, 0x2143, 0xaf50, 0xbfb2, 0x36cb, 0xb2f9, 0x402c, 0x427a, 0x4350, 0x4315, 0xb0a9, 0x402c, 0x0802, 0x3632, 0x4495, 0x4179, 0x1c57, 0x1bd3, 0xbfd8, 0xa483, 0x3213, 0x4113, 0x4341, 0xb2de, 0xba5c, 0x4246, 0xba0f, 0x0363, 0x3661, 0x41b0, 0xb0ae, 0x4138, 0x4143, 0xba45, 0xb2aa, 0xba01, 0x1824, 0xb049, 0x0a3a, 0x4096, 0xb0ea, 0x274a, 0x43eb, 0xb274, 0xbfde, 0x1aea, 0x417b, 0x0b2f, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd2fc5a9a, 0xcbe6f391, 0xc5271121, 0x66c4d03a, 0xce34ff10, 0xe796f79c, 0x37623fb3, 0x4d059abb, 0xf938c94f, 0x8d0659ac, 0xc525e62e, 0xad6bb1ea, 0xe561afeb, 0x8226b8c9, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0xfffec5fe, 0xfec5feff, 0x00807dfa, 0x0100013a, 0x00000000, 0xfefffec5, 0x00000000, 0x0000004a, 0xf938c94f, 0x0002e84e, 0xec28f749, 0xb5aec7a8, 0x000000c3, 0x69c37868, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x1d4f, 0xbfc5, 0xb068, 0x40ba, 0x3da9, 0xaeb3, 0x40b6, 0xa066, 0x13be, 0x4028, 0x4049, 0xba69, 0x4602, 0xbfaf, 0x21d5, 0x1879, 0xa68c, 0xa2f5, 0x41f6, 0x0953, 0xbfd0, 0x2aeb, 0x1d85, 0x3904, 0x38c6, 0xb07e, 0x4079, 0x04e6, 0x1634, 0xbf3c, 0x40cd, 0x4237, 0x41ca, 0xbf72, 0x41ee, 0x1cc9, 0xac16, 0x435a, 0xa938, 0x1f53, 0x3c3e, 0xbf9b, 0xb0b9, 0xb0fa, 0x2372, 0x031a, 0xb011, 0xa5c9, 0x2022, 0xb062, 0x40d8, 0xb223, 0x2f62, 0x46a8, 0xb0f8, 0x4201, 0x4398, 0x19ce, 0x404a, 0xa2b4, 0x2542, 0x41c1, 0xbfc1, 0x42af, 0xb065, 0x20ad, 0x1db5, 0x405a, 0x4008, 0x42bd, 0x0eb3, 0x19c3, 0x414f, 0x4288, 0xb2fe, 0x423d, 0x41ff, 0x4285, 0x4249, 0x42a4, 0xbfe2, 0x4226, 0x1c2a, 0x1323, 0xaf72, 0xb008, 0x42a1, 0x14d3, 0x12cd, 0x4655, 0xb2b7, 0xadf9, 0xba1c, 0x40b0, 0xb21d, 0x4045, 0x1ba6, 0x41d3, 0x41ee, 0xbf6a, 0x416b, 0x3ed9, 0x424d, 0xb238, 0x420e, 0xb22f, 0x4013, 0x3404, 0x41bc, 0x08d8, 0xba7f, 0x407c, 0x1924, 0x424b, 0xb2cc, 0xb232, 0x0896, 0x2a78, 0x43b1, 0x437c, 0xa967, 0x4546, 0xbf8f, 0x4039, 0xa773, 0x40f6, 0xb236, 0xb2e8, 0x43fa, 0x1b34, 0x420e, 0xa787, 0x43a8, 0x2f7a, 0x40b1, 0x46d1, 0x1c90, 0x41ad, 0x4200, 0xba78, 0xbf9f, 0x406d, 0x23c7, 0x0b7f, 0xb2ed, 0x3622, 0x1dd2, 0x425a, 0x43bc, 0xb0ea, 0x4544, 0xa6be, 0x460e, 0x1722, 0x42e9, 0x273e, 0x4366, 0x4691, 0xa936, 0x41a2, 0xbf2e, 0x3231, 0x42a6, 0x43da, 0x2f37, 0x1d1a, 0xb26c, 0xbaff, 0x404c, 0x4132, 0x4076, 0x43fc, 0xb218, 0xba40, 0x4337, 0x385b, 0xb265, 0xb018, 0x462a, 0xba63, 0x41e6, 0xbf59, 0x4289, 0x1fa7, 0x40d7, 0xba59, 0xba5a, 0x0422, 0x07af, 0x428d, 0xb211, 0x4355, 0x431d, 0xbf51, 0x39c4, 0x1ee0, 0x0119, 0x414d, 0xa186, 0x1809, 0x42b4, 0x4065, 0xb2db, 0x43e6, 0xb26c, 0x45b6, 0x1be2, 0xb2ac, 0x42a9, 0x4067, 0xaf6c, 0xb2df, 0xbf1b, 0x4299, 0xb015, 0xb064, 0x44c9, 0x43f8, 0x2914, 0x1c11, 0x2d02, 0x1ebc, 0x43bb, 0x400e, 0x1cb3, 0x4246, 0xb00a, 0xb003, 0xbfb0, 0x1909, 0x3166, 0x30fd, 0xb25f, 0x1c45, 0x1e5e, 0xba68, 0xbf5a, 0x43a4, 0x1a5e, 0x4080, 0x050c, 0xbfc0, 0x14c5, 0x41c0, 0x438e, 0x416e, 0xba10, 0x1a22, 0xb0c6, 0x3aaf, 0x43a3, 0x0345, 0xbf72, 0xb248, 0x44c3, 0x42a4, 0xba19, 0x1e6b, 0x4475, 0x4347, 0xb29a, 0xb285, 0xb2dc, 0x40e7, 0x18bd, 0x4022, 0xb26f, 0xb01e, 0x4329, 0x4592, 0xa298, 0x433b, 0x2c82, 0x413d, 0xbf6e, 0x4554, 0x418b, 0xb0ab, 0x1a76, 0x4015, 0x4050, 0x4613, 0x42a6, 0x435b, 0x467c, 0x0354, 0x3662, 0x093c, 0xb2a8, 0xb2f2, 0x42db, 0x41ff, 0x4030, 0x420c, 0xb063, 0x4580, 0xbf1f, 0x07e2, 0x408b, 0xb0af, 0xb096, 0xba7c, 0xb273, 0x4336, 0x4628, 0x2ede, 0x4249, 0x41fa, 0x3d4c, 0x43b4, 0x40b6, 0x4132, 0xb2c7, 0x138e, 0x2c2a, 0xafa8, 0xb284, 0x1fcc, 0xbad1, 0xba35, 0x40d2, 0xae8d, 0x40b7, 0xb0ef, 0xb245, 0xbf88, 0x1aa3, 0x36d9, 0x4217, 0x0d64, 0x4247, 0xb07b, 0x4093, 0x4398, 0xbf95, 0xbaeb, 0x41db, 0x009d, 0x2ac3, 0xb298, 0x3a13, 0x4361, 0x072c, 0x01ba, 0x1b20, 0x1bc5, 0x404b, 0x0f91, 0x4129, 0xb01a, 0x0794, 0x4001, 0x406a, 0x273d, 0x40a3, 0x4021, 0x441a, 0x418d, 0x4161, 0xbf2f, 0x40d9, 0xbfe0, 0x0fdc, 0x1ae0, 0x37ff, 0x42ed, 0xb0a6, 0x2e0f, 0xa6c5, 0xb0ba, 0x19cc, 0xb21f, 0x220f, 0xb2d3, 0x1dbf, 0x433e, 0x44f4, 0x36f5, 0x422f, 0x1a45, 0x052c, 0x40fd, 0x4285, 0x4294, 0xbae3, 0xba73, 0x42e7, 0xbfab, 0x40f3, 0xba5f, 0x168e, 0x405c, 0x4200, 0x4267, 0xba7d, 0x42ca, 0xb294, 0x42a6, 0x43b9, 0xb2d1, 0x40b4, 0xa04c, 0x18b5, 0x4627, 0x3b0d, 0x4292, 0x460c, 0x4133, 0xb2c5, 0x40f7, 0x42cd, 0xbf21, 0x1c7e, 0x2cb5, 0x2027, 0x4689, 0xb20f, 0xbfd4, 0x1f88, 0xa0aa, 0x1a08, 0xa41d, 0xb2dc, 0x1cae, 0x423e, 0x40e5, 0x43ea, 0x182c, 0xbadd, 0x4349, 0x41b3, 0x1a46, 0x4545, 0xbff0, 0x19b3, 0x3943, 0x0759, 0x36e9, 0x42ff, 0x27f6, 0x0bda, 0x22f4, 0xbf9e, 0x405a, 0x2a35, 0xb2bf, 0x432c, 0x0307, 0xab18, 0x4128, 0x4350, 0x1eae, 0xac20, 0x411d, 0xb2bc, 0x39f4, 0x43b9, 0x4065, 0x1f99, 0x1fc1, 0x4245, 0xa9e3, 0x456e, 0x41ae, 0x4251, 0x4292, 0x4277, 0xbf3a, 0xba5e, 0xb205, 0xb27b, 0x40ca, 0xb233, 0xb037, 0x411f, 0x2fdc, 0xba7a, 0xb0d7, 0x42fa, 0x2a0d, 0x29bf, 0x46ad, 0xb062, 0xa1f5, 0x1f2f, 0x434b, 0x41f3, 0x2f98, 0xbf72, 0x41c9, 0x40da, 0xa73f, 0x4388, 0x42bf, 0xb201, 0x342d, 0x41d8, 0xb2e2, 0xb2a8, 0x2e8d, 0x4096, 0xb280, 0xba42, 0x40fd, 0x418e, 0x4616, 0x1902, 0x4162, 0x1a4b, 0xbfbc, 0xb22c, 0x4108, 0x432f, 0x46bb, 0x46c9, 0xba32, 0x1a44, 0xae82, 0x45c4, 0xba4d, 0x12e4, 0x4273, 0xbaf9, 0xbfd0, 0x43ab, 0x193e, 0x46e9, 0xb09f, 0x116a, 0x4062, 0xbf4b, 0x3063, 0x4186, 0x4022, 0x4413, 0x1e60, 0x4221, 0xb067, 0x42cc, 0x43b5, 0xbaec, 0x2f0a, 0xba21, 0x40f1, 0xb021, 0x42c5, 0x41b5, 0x4408, 0x019a, 0x1bce, 0x4445, 0xbade, 0x3ba5, 0x1b4c, 0x3db9, 0x407c, 0xba29, 0x438a, 0x28d3, 0xbf36, 0x464d, 0x426d, 0x3ef8, 0xb2d1, 0x41b4, 0x1b02, 0xb2b2, 0x18f4, 0x436a, 0x1c88, 0x3013, 0xb2e5, 0xb0fb, 0xb20a, 0x1f07, 0xaaf3, 0xb09b, 0x4150, 0xbf44, 0x425f, 0xb26c, 0xba7d, 0xbfda, 0x1a16, 0x2eb2, 0x4127, 0x4358, 0xbf25, 0x1e43, 0xb26e, 0x46b5, 0xb2f1, 0xb20f, 0x4190, 0xba77, 0xb2d0, 0xb256, 0x192d, 0x1c18, 0x0155, 0x4019, 0xbaf7, 0x424e, 0xba74, 0xb28c, 0xba1e, 0x1a15, 0xb084, 0x3be4, 0xbf44, 0x4244, 0xba70, 0x4109, 0x0237, 0xb2fb, 0x43fb, 0x4113, 0xbae0, 0xa7a3, 0x4552, 0x422f, 0xb291, 0xb263, 0xbfd4, 0xb22b, 0x2765, 0xbaf4, 0xb049, 0x1a38, 0x405e, 0x1b6f, 0xb25e, 0xba65, 0x0639, 0x4581, 0xbaca, 0x41a7, 0x1c50, 0x4030, 0x4272, 0x435e, 0xbf43, 0x4225, 0x180c, 0x42f2, 0x416f, 0x429f, 0xbad2, 0x40bd, 0x448d, 0x417e, 0xb220, 0x10d2, 0xbf06, 0x4068, 0x3614, 0x1307, 0x432a, 0x46d0, 0x4233, 0x41c4, 0x46f3, 0x1c04, 0x40a0, 0xaa36, 0xbaf7, 0xb2e8, 0x425c, 0x42ba, 0x3767, 0xb20e, 0x400b, 0xb087, 0x1886, 0x2c3e, 0x2840, 0xb06a, 0xbf05, 0x43a1, 0xb250, 0x4543, 0x412c, 0x22a1, 0x41c1, 0x4133, 0x1976, 0x404f, 0x425a, 0xaad6, 0x41ed, 0x40a4, 0x4333, 0x4278, 0xb04c, 0x41c5, 0x42c3, 0x336a, 0x4004, 0x411b, 0xb24b, 0x4399, 0x43b7, 0x4098, 0x4180, 0xbf7b, 0x41d0, 0x423c, 0x4301, 0x416c, 0x432b, 0xb2a3, 0xb2a2, 0xb0d9, 0x42df, 0x1237, 0x43d1, 0x437d, 0x41fd, 0x4352, 0xba00, 0xbf6b, 0x1c45, 0x0c59, 0x21e6, 0x1d8a, 0x1a4b, 0x43cf, 0x098f, 0x2f7b, 0x4307, 0xba68, 0x4093, 0xb2cf, 0x4106, 0x19a1, 0x0fd2, 0xbac5, 0x1a26, 0x43da, 0x4279, 0x40dd, 0xbf01, 0xb0b5, 0x41e6, 0x3dc6, 0x406f, 0xb258, 0x19fe, 0x40e1, 0xba16, 0x4267, 0x4093, 0xb2e8, 0x42c0, 0xb282, 0x4083, 0xb269, 0x40f8, 0x3805, 0xbfc3, 0x437a, 0x425d, 0xb0f4, 0x2503, 0x4074, 0xb021, 0xbfaa, 0x42f4, 0x413d, 0x42c7, 0x0bf2, 0xb06c, 0x1949, 0x40e4, 0x40b4, 0x1a5d, 0xbf13, 0xb2a1, 0x230b, 0xb0e9, 0xb294, 0x46b8, 0x2508, 0xb09d, 0xb24f, 0xb0d1, 0xad5e, 0xbfb2, 0x4037, 0xba4b, 0x1f76, 0x1ed9, 0x412a, 0xbf90, 0x4694, 0xb0aa, 0x39bf, 0x4070, 0x4492, 0x0b7e, 0xa95a, 0xb08c, 0x0ebd, 0x4381, 0xa285, 0x3e4b, 0xbf07, 0x4159, 0xae4d, 0x4269, 0xb277, 0x46d3, 0x29f9, 0x45be, 0x4362, 0x1925, 0xba2a, 0x4573, 0x0196, 0xb2ea, 0x434c, 0xb26d, 0xbae2, 0x1e42, 0x411c, 0x416e, 0x4259, 0xb28b, 0xbfe2, 0xbac6, 0x437e, 0x076e, 0x41ba, 0x407a, 0x45f1, 0x4205, 0xb289, 0x1840, 0x4027, 0x4190, 0x408f, 0xb23c, 0x419a, 0x001e, 0xb0b6, 0x4081, 0xba25, 0xa216, 0xb2ff, 0x405c, 0x18ad, 0x426a, 0x4179, 0xb21a, 0xbf8a, 0x4312, 0x44d3, 0xba1d, 0x42a8, 0x283f, 0x4169, 0x13bc, 0xb00d, 0xaac5, 0x403a, 0x439d, 0xaaca, 0x4369, 0x1c07, 0x4256, 0x434e, 0xb054, 0x1634, 0xbfdf, 0x1ab7, 0x3f94, 0x1b06, 0x42f4, 0x407e, 0x058b, 0x409a, 0xba4b, 0x180e, 0xaf82, 0x43f8, 0x161d, 0x44e0, 0xbf14, 0x4215, 0x1acc, 0x401a, 0x402e, 0x41e4, 0x4678, 0x1d6b, 0x1d43, 0x427b, 0x015f, 0xaf31, 0x41de, 0x0fa6, 0xbf45, 0xb2c8, 0x417c, 0x438b, 0x45be, 0x4334, 0xba6f, 0x429a, 0xbf00, 0xb2ad, 0x4342, 0x4154, 0xbf26, 0xb2ad, 0x4347, 0x16db, 0x4423, 0x40ac, 0x4384, 0xb270, 0xb005, 0x42b8, 0x4305, 0xba31, 0x1d89, 0x2ca0, 0xb21f, 0xba55, 0xba38, 0xb26a, 0x30c9, 0xb20e, 0xb224, 0x4069, 0x431a, 0x43fa, 0xa8e2, 0x41a8, 0x40f1, 0x4253, 0x1eae, 0xbf0b, 0x405d, 0x3e93, 0x43ec, 0x45a3, 0xb267, 0x1c8a, 0x19fc, 0x417c, 0xb214, 0x4350, 0x4083, 0x2458, 0x42cc, 0x40f4, 0x40f0, 0x31dd, 0x43b0, 0xb2bf, 0x419f, 0xb2bb, 0x41f0, 0xbfbd, 0x42bd, 0x1c0f, 0xb06e, 0x4085, 0x43ea, 0xb2c7, 0xb0c4, 0x428a, 0x4294, 0xb2ea, 0x44d1, 0x4374, 0x4361, 0x41bf, 0xa12e, 0x02fa, 0xbac8, 0xbf15, 0x4004, 0x3b3a, 0x05be, 0x41c9, 0x40ab, 0xbadf, 0x3b7c, 0xb075, 0x2c0e, 0x02fb, 0x18b4, 0x19f6, 0x43eb, 0xb254, 0x431d, 0x40e1, 0x40f1, 0x0f2a, 0x4248, 0xbf95, 0xa360, 0x4095, 0x1a15, 0x4387, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x14152dbc, 0xac42cb3d, 0x80d39deb, 0xf842bf41, 0xc80af0cb, 0xb2924a15, 0xe20c7a41, 0xd0c9f409, 0xfd5e5b81, 0x4d1dced7, 0x6694566b, 0x954b09bd, 0x823d8b85, 0xd32e765b, 0x00000000, 0xe00001f0 }, + FinalRegs = new uint[] { 0xffffe7a0, 0x00001860, 0x0000000f, 0x00001958, 0x00000000, 0x0000186f, 0xffc00000, 0x00000000, 0x00000000, 0x669456ff, 0x6694566b, 0xcd28acd6, 0x00000000, 0x000060e9, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4379, 0x2480, 0x4389, 0x4462, 0xb2b1, 0xb229, 0x4064, 0xb2ce, 0xbf8a, 0xb233, 0x4052, 0xbafd, 0x40a1, 0x047d, 0xb2a8, 0xbac2, 0x43fc, 0x41d7, 0xb0c2, 0xba50, 0x41ac, 0x24ec, 0x1cde, 0x4678, 0xb0e8, 0x46f0, 0x438c, 0x4034, 0x3f2b, 0xa9c5, 0xa5d8, 0x1472, 0xbfc6, 0xb2f0, 0x223e, 0x0dc1, 0x408e, 0x4220, 0x417f, 0xa4bb, 0x4347, 0xa2fa, 0xbf8a, 0x1a45, 0x0d2e, 0xb2ed, 0xbaf2, 0x41cf, 0x437f, 0xb077, 0xb271, 0xb256, 0x4138, 0x0a13, 0x439d, 0x275a, 0xb0ad, 0x4094, 0x4133, 0xbfbc, 0xb0d7, 0x290e, 0x43ec, 0x41df, 0x259f, 0x402a, 0x42c6, 0x4148, 0xa7d3, 0x2e2c, 0xb2c3, 0xb223, 0x439b, 0x4209, 0x1e5b, 0xb213, 0x421f, 0x0902, 0xb2b4, 0xba54, 0x4340, 0x1988, 0xbfcb, 0x43b0, 0xba07, 0x436e, 0x41b2, 0xb0ff, 0x43f6, 0x42a5, 0x29d5, 0x4325, 0x22f7, 0x403b, 0x4363, 0x1a03, 0x08bc, 0x1a8e, 0x458a, 0x1328, 0xba5c, 0x315f, 0x40af, 0xbadc, 0x1b66, 0x1bfa, 0x1a5d, 0xb06a, 0x3d85, 0xb077, 0x434c, 0x40c6, 0xbf59, 0x40ec, 0x40a9, 0x4491, 0x4274, 0xba68, 0x1b59, 0x2148, 0xba32, 0x288d, 0x41ba, 0x2e3b, 0x4561, 0xbf5a, 0x42e1, 0x445d, 0x4181, 0x1f97, 0x436c, 0xb005, 0xba1d, 0xab7e, 0x38a1, 0x3f8a, 0x4139, 0x463c, 0xb047, 0xbafd, 0x43c1, 0x40a5, 0x429c, 0xba1c, 0xad94, 0x420a, 0xb275, 0xba3e, 0x447c, 0x1b00, 0xba5e, 0xb282, 0xbf78, 0xb226, 0xb293, 0x1e34, 0x1cf0, 0xae4a, 0xbfa7, 0x40d6, 0xba23, 0x46a0, 0x2172, 0x44eb, 0xbf2f, 0x4095, 0x42cb, 0x2e23, 0x312c, 0xb2e8, 0x1a06, 0x431b, 0xb2b4, 0x42ae, 0x424d, 0xb2ec, 0x1bbd, 0x45f2, 0x137e, 0x204b, 0x18e0, 0x40be, 0xb2c0, 0x25a8, 0x4319, 0xb277, 0x2318, 0x4181, 0xb29e, 0x43a4, 0x1b2b, 0xa73b, 0xbfc7, 0x074b, 0x2a98, 0xb07d, 0xba4b, 0xb256, 0x4088, 0xa8c4, 0xa053, 0x4448, 0x41ca, 0x42f1, 0xb239, 0xbf2d, 0x40a8, 0x3a69, 0x2ffe, 0x3bec, 0x23ee, 0x43f1, 0x2309, 0x43c7, 0x431c, 0x42a9, 0xb29d, 0x41dc, 0x39ff, 0x412f, 0xb2c0, 0xba55, 0xa7bf, 0x41cc, 0xbad7, 0x4104, 0x018a, 0xb2bf, 0x43c6, 0x4170, 0xb28f, 0x41f2, 0x409b, 0x4372, 0xb007, 0xbf2e, 0x01f0, 0xba38, 0x409d, 0xb0ad, 0x4363, 0xb042, 0x2a86, 0x4140, 0x415f, 0xbfb8, 0x41e5, 0x28f2, 0x4035, 0x42ce, 0xb0ae, 0x43d6, 0x4357, 0x44a9, 0x40a8, 0x4117, 0x433b, 0x4213, 0x3385, 0x09e7, 0xbf80, 0xb063, 0xaa00, 0xb013, 0xbf90, 0x409a, 0x4007, 0xbf04, 0x4060, 0x4377, 0xb0f3, 0xb0ab, 0x4279, 0x1a56, 0xba2b, 0x1931, 0x434c, 0xb090, 0x0500, 0x1b0f, 0x1a85, 0x4681, 0x3e3a, 0x43ce, 0xb2fa, 0x430b, 0x4043, 0xbfb0, 0x3a7d, 0xbf76, 0xb009, 0xbace, 0xbf70, 0x403a, 0x438c, 0x41fd, 0x433c, 0x4202, 0x2d3d, 0x4277, 0x428e, 0x4156, 0xb2d2, 0xbf68, 0x3ab5, 0x409c, 0x1f03, 0x1d6d, 0x42f8, 0x0aaf, 0x2d64, 0x2022, 0xbf12, 0x137d, 0xba6f, 0x45a2, 0xa7a7, 0x40f5, 0x45d3, 0xb2e4, 0x3f2e, 0xba5e, 0x1df2, 0xafc9, 0x42be, 0x4683, 0x4001, 0xba00, 0xb2ae, 0xba1c, 0x089d, 0xb052, 0x1bb8, 0x198b, 0x2162, 0x1f49, 0x3d1b, 0x46fa, 0xbf68, 0x4018, 0xa1e0, 0x40fa, 0x1bdd, 0xbf7c, 0x1a6b, 0x435a, 0xb240, 0x434e, 0x2af4, 0x438b, 0x40f3, 0x4049, 0x43df, 0xb231, 0x0e09, 0x434a, 0x1a40, 0x4079, 0x34eb, 0x41b5, 0x4033, 0x431b, 0x28b1, 0x4575, 0xb022, 0x4221, 0x41b5, 0xbf05, 0x1e57, 0x41d7, 0xb26b, 0x40e3, 0x18d7, 0x40c4, 0x4204, 0xb249, 0x1c5a, 0xbfa4, 0x4063, 0x36b9, 0x4414, 0x42ca, 0x1c8b, 0x42d0, 0x458c, 0xb04f, 0xbf79, 0x4369, 0x1d45, 0x383b, 0x41af, 0x132e, 0x1a3c, 0x0552, 0x467b, 0xba03, 0xb09d, 0xb0ed, 0x43a8, 0x2abd, 0x181a, 0x43c3, 0xb2f9, 0xaa2e, 0x38d6, 0xba77, 0x427b, 0x41f9, 0xbf21, 0x42e7, 0xb282, 0x4224, 0xb2e2, 0x1fab, 0x40f1, 0xbfd0, 0xbf84, 0x01fc, 0x42d2, 0x4379, 0xb000, 0x4246, 0xbfbd, 0xba44, 0x42ec, 0x43e8, 0x0d71, 0x238f, 0xbfd8, 0x46d4, 0x4068, 0x430e, 0xa79c, 0xa11e, 0x4375, 0x0698, 0xb241, 0x4343, 0xbaf7, 0x1af4, 0x4399, 0x4227, 0x43c3, 0xb2e3, 0xb08b, 0x39ed, 0xb07b, 0xb2d1, 0x4162, 0x425e, 0x0066, 0x4046, 0x4025, 0x0499, 0xbf48, 0x430d, 0xba16, 0x215d, 0x2992, 0x4078, 0xb226, 0x126c, 0x406e, 0x43cc, 0xb23f, 0x43db, 0xbf37, 0x2e15, 0xbaec, 0x4356, 0x3c9d, 0x4393, 0x245d, 0x411b, 0xbf00, 0x2629, 0xb21e, 0x423e, 0x27ca, 0x4001, 0x46e2, 0x4191, 0x439b, 0xba57, 0x465a, 0x1d3a, 0x1cb4, 0x4377, 0xbfc5, 0x40f1, 0x43b1, 0x42ea, 0x4249, 0xba57, 0x439e, 0x4263, 0x42cf, 0x4204, 0xb296, 0x4022, 0xa2ce, 0x4155, 0x07a1, 0x4005, 0x41bc, 0x430f, 0xbf0c, 0x4388, 0x1383, 0xb0da, 0x2956, 0x44e3, 0x432a, 0x4061, 0x421b, 0x4215, 0x455a, 0x41fa, 0x1d49, 0x4165, 0x40b8, 0x1258, 0x208a, 0xbf12, 0xa283, 0x1d11, 0x41bf, 0xbf55, 0xba7b, 0x4204, 0xba24, 0xbff0, 0xbf61, 0x40e1, 0x1ed0, 0x43c5, 0x42c8, 0x4215, 0xbad5, 0x4220, 0xb029, 0x439a, 0xb07b, 0xa83b, 0xb2ef, 0x1fb5, 0x4298, 0xb071, 0x40ef, 0x42e1, 0x2792, 0x1a2b, 0x1fb9, 0x32df, 0x209b, 0x3ecf, 0xba10, 0x409e, 0xb253, 0x4041, 0x196f, 0x443c, 0xbfd9, 0x189c, 0x441c, 0xbac0, 0xaa98, 0x0b73, 0xb229, 0x42d2, 0x4225, 0xbff0, 0x4226, 0x404c, 0x43b7, 0x4039, 0x4348, 0x411d, 0x40d0, 0x401b, 0x4187, 0xa008, 0xb2fa, 0xa858, 0xbf35, 0x420b, 0xb2c3, 0xb2fb, 0xb03c, 0x4063, 0xb0e0, 0xbfb0, 0x1b44, 0xbf5d, 0x4034, 0x4427, 0x43a2, 0xba5a, 0x46d8, 0x46d1, 0x3045, 0x33da, 0x43bb, 0x4018, 0x0618, 0x4192, 0x4357, 0x430e, 0x414b, 0x3656, 0xb2b3, 0xb03f, 0xbf90, 0x460b, 0x43c5, 0xbf1c, 0x4368, 0xba32, 0xbfc0, 0x415b, 0x4113, 0x1e66, 0x1eef, 0x4217, 0xba3a, 0xb280, 0x4355, 0x44a0, 0x4340, 0xb097, 0x423c, 0x43f6, 0x2cf6, 0xb250, 0x4627, 0x42cc, 0x2090, 0xba59, 0x4277, 0x24e1, 0x1f43, 0x14a1, 0xa852, 0xbfb6, 0x45f4, 0x46ec, 0x2c9b, 0x3866, 0xb2a8, 0x1d24, 0x4311, 0xb051, 0x4082, 0x2e93, 0x40f7, 0x40e3, 0xb010, 0xac6c, 0x1928, 0xbfdd, 0x0c95, 0xba78, 0x4409, 0x41af, 0x3367, 0x4204, 0x436a, 0x1d3c, 0x4043, 0xb0f9, 0x417f, 0x42ea, 0x42c7, 0x1cd8, 0x1db7, 0xa9fc, 0x1d0c, 0xbf60, 0x4589, 0x41e8, 0x25ec, 0x422c, 0xb239, 0x3b17, 0x45f5, 0xbf7b, 0x0725, 0x40b4, 0x4005, 0xb2f4, 0xbff0, 0x40a6, 0x412e, 0x4167, 0x30c3, 0x403b, 0xbfb8, 0x43c5, 0x402b, 0x1fa2, 0x3aca, 0xbfc5, 0xbafa, 0x4232, 0x4632, 0xb2b6, 0x4639, 0x4061, 0x4256, 0xb2e8, 0x17be, 0x4346, 0x4313, 0xbfb6, 0xba6f, 0x4103, 0xb030, 0x4196, 0xb03a, 0x0a90, 0xbf90, 0x4610, 0x40ae, 0xba66, 0x1d15, 0xa4b5, 0x4210, 0x42a7, 0xbac1, 0xbfb4, 0xaa8b, 0xb2e7, 0x405f, 0x4013, 0xa7f6, 0x1a48, 0x38c2, 0x420c, 0x210c, 0x40ad, 0x4309, 0x2e0a, 0xba29, 0xbad8, 0x0cb0, 0x4333, 0xba55, 0x1e96, 0xb21b, 0x2b22, 0x3f84, 0xbfa1, 0xa0a2, 0x419a, 0xb2c8, 0x40c3, 0x42ef, 0x4132, 0x41f4, 0xbfc6, 0x4650, 0x4322, 0x341e, 0xbf8b, 0x426b, 0x46b2, 0xb271, 0x422c, 0x4205, 0x4329, 0x419d, 0xa66e, 0xb28b, 0x409d, 0x3f5c, 0x37f3, 0x4135, 0xb25e, 0x4032, 0x4368, 0x44ad, 0x4089, 0x468a, 0x4309, 0x19f0, 0xb2ec, 0x419a, 0x4642, 0xaade, 0x221c, 0xbf12, 0xa8f7, 0xb251, 0x43cd, 0x4189, 0xbadc, 0x0a21, 0xb02b, 0x43a2, 0x4136, 0xbf07, 0x005c, 0x1ab6, 0x4192, 0x4084, 0x36b5, 0xb239, 0x19f8, 0xb236, 0x1a91, 0x40f4, 0x1c8b, 0x4142, 0x015e, 0x0894, 0x4229, 0xb090, 0xb051, 0x4073, 0x42f0, 0x1f44, 0x0dc9, 0x447f, 0xba48, 0x4150, 0x1af6, 0xba5e, 0x2fa7, 0xb088, 0x421c, 0xbf92, 0x43b1, 0x4007, 0x1992, 0x411c, 0x4021, 0x4004, 0x26a4, 0x0e50, 0x43ad, 0x3588, 0x1d58, 0x426c, 0xb0c6, 0x2222, 0xb2ef, 0x4440, 0xbf59, 0xb05b, 0x42fb, 0xb277, 0x4314, 0xbacf, 0x17b8, 0x4192, 0x40c9, 0x1d08, 0x43a8, 0xba2a, 0xb069, 0x43b9, 0x44d5, 0x10b0, 0xb2cd, 0x46e9, 0x4054, 0x4417, 0x2d2c, 0x42fb, 0x4094, 0xbf9c, 0x4598, 0x41d7, 0x4257, 0x4284, 0x4277, 0xaf28, 0xba78, 0xb0d1, 0x40f2, 0x43d2, 0x41e7, 0xb25d, 0x420c, 0x40e6, 0x1fa1, 0xb00c, 0x4066, 0x40ee, 0xbf08, 0x4387, 0xbf70, 0xb048, 0x42af, 0xbac4, 0x44cb, 0x40dd, 0x2044, 0x4247, 0x1dda, 0x250b, 0xbadd, 0x45a8, 0x4195, 0xbf38, 0x42d6, 0x4613, 0xb26d, 0x4581, 0x2f5e, 0xb2c5, 0x464e, 0xba66, 0x41c2, 0x2431, 0x4301, 0x4063, 0x2637, 0x46a0, 0xb059, 0x1d7b, 0x1f71, 0xb089, 0xbf15, 0x4111, 0x4372, 0xba3f, 0x1869, 0x13c5, 0x42cd, 0xb216, 0xb2b9, 0x4324, 0xbf70, 0x418d, 0x405a, 0x130b, 0xbfd7, 0x43e3, 0x131f, 0x42d2, 0x42b0, 0x0eb5, 0xba07, 0xb261, 0x4173, 0x4124, 0x3d11, 0x410c, 0xb268, 0xb23b, 0xb219, 0x4044, 0x011f, 0x33bd, 0x1f80, 0xb2c8, 0x4133, 0x4352, 0x1aad, 0xbfc2, 0x3647, 0xb2ef, 0x1964, 0x41f0, 0x2fd2, 0x4291, 0x438f, 0x1518, 0x4184, 0x4138, 0x1c5b, 0xa1de, 0xae02, 0x40f0, 0xbfdc, 0xba47, 0x40eb, 0x42f7, 0x2577, 0x1ba5, 0x431d, 0x4227, 0x1e88, 0xbf28, 0x1cfb, 0x409d, 0xbf5d, 0x4039, 0x4328, 0xb23c, 0x0f79, 0xb03e, 0x4177, 0x44c3, 0x4334, 0xbf4d, 0x40bc, 0x4343, 0x402a, 0x40c6, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x2facc260, 0xdcf5a5c6, 0x996babf4, 0xcf26a6f8, 0x2df7e7e5, 0xee4ea13b, 0xf856da3e, 0x6c32216c, 0x0f115238, 0x504a66b5, 0xb0cc8c19, 0x7ac767c7, 0x4550fd6f, 0x45a84035, 0x00000000, 0x100001f0 }, + FinalRegs = new uint[] { 0x00001b12, 0x00000000, 0x60000d99, 0x00000003, 0xfffc0000, 0xd2bdaee8, 0x0000116a, 0x45a84a12, 0x00000031, 0x45a848bd, 0x00000000, 0x8af9467f, 0x45a844ad, 0x45a84b01, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xb2fe, 0x3eee, 0x4080, 0x4136, 0xb2e1, 0xb2b8, 0xba6e, 0x4298, 0x4164, 0x427d, 0x42cc, 0x416f, 0xbf4b, 0xba4a, 0x1e12, 0x43ca, 0x1c32, 0x41dd, 0x40be, 0x4374, 0x401a, 0xb0ad, 0x1506, 0x19bc, 0x11fe, 0xb298, 0x44c2, 0x1c97, 0xbf29, 0x4123, 0x4052, 0x4092, 0x4160, 0x209c, 0x42d7, 0x413b, 0x4333, 0xad04, 0x1147, 0x4054, 0x437a, 0xbaf8, 0x427a, 0x4197, 0x2636, 0xbfa2, 0x1958, 0x41b0, 0x40a3, 0x1ee7, 0x4132, 0x4306, 0x404f, 0xba70, 0x432f, 0x4551, 0xb0d9, 0xa5fd, 0x2460, 0x06fc, 0x3f2e, 0x29f1, 0x4233, 0x4318, 0x1044, 0xb277, 0xbf09, 0xb28f, 0x439f, 0x43ba, 0x06a4, 0x43b8, 0xba39, 0x1ebb, 0xb21f, 0x41e4, 0x4280, 0xbfb9, 0x3ebe, 0x11aa, 0x1b41, 0x1f7b, 0x4407, 0x2a7a, 0x4211, 0x41bb, 0xb0ae, 0x418b, 0x1665, 0x4177, 0xbfc0, 0xb280, 0x1905, 0x4267, 0xbfc6, 0x43a0, 0x42e5, 0x45b4, 0xba2e, 0x43a3, 0x2486, 0x40eb, 0x4297, 0x23fa, 0xbf71, 0x41e5, 0x4449, 0xb051, 0x0d5b, 0x4027, 0xba23, 0x3126, 0xbf57, 0x40b8, 0x41cf, 0x464f, 0x4082, 0x4075, 0x416a, 0x18df, 0xb0e9, 0xba0a, 0x4172, 0x14b5, 0x4394, 0x4232, 0x0ff1, 0xbf96, 0x406f, 0x43d7, 0x42e3, 0x0dc2, 0x4025, 0x46a0, 0x1d27, 0xb2f3, 0x42e4, 0x4099, 0x1a00, 0xb0b2, 0x1b48, 0x40a8, 0x420f, 0x43cd, 0xb270, 0xbf92, 0x412d, 0x4178, 0x4400, 0xb2f4, 0x40f1, 0x409f, 0x124a, 0x4584, 0x4312, 0x40f4, 0x1b70, 0x441e, 0x1866, 0x40ba, 0x4337, 0xbf1f, 0xb235, 0x41a4, 0xbf80, 0xb250, 0x40f9, 0xb2f5, 0x4237, 0xba2e, 0xbf1a, 0x433c, 0xb2b5, 0xbad6, 0xb0c9, 0xbf41, 0x43b3, 0x43c4, 0x3c17, 0x414f, 0x4194, 0xaf3b, 0x42cb, 0x1b27, 0x41bf, 0x418c, 0x050e, 0x44e5, 0x41ac, 0x45db, 0x431e, 0x43e8, 0x421e, 0x41f6, 0x18a1, 0x417f, 0xbf7f, 0x412b, 0x1dc5, 0xbacb, 0x46e1, 0x1321, 0x1e34, 0x42c6, 0x413f, 0x3725, 0x3245, 0x43cd, 0x1de1, 0x40d4, 0x35bb, 0x42c1, 0x4100, 0x4438, 0x028c, 0xb208, 0x4449, 0xbae6, 0xb08b, 0x0936, 0x433c, 0x412e, 0xb095, 0x41b8, 0x1d8c, 0x41b3, 0xbfa8, 0x4616, 0x4043, 0x45b3, 0x4214, 0x1ceb, 0x4133, 0x410a, 0x1c15, 0xbf16, 0xb2fc, 0xba2d, 0xab9f, 0x4037, 0x1f6d, 0x2f24, 0x4309, 0x4431, 0xbace, 0xba3d, 0x43cc, 0xb2e1, 0x137d, 0x43d5, 0x3f6c, 0x41e8, 0x3089, 0x42bf, 0x45d3, 0x4049, 0xbf89, 0x0e07, 0x2da2, 0x427e, 0x43c9, 0xb2cd, 0x1f71, 0xadba, 0xb2e3, 0xba77, 0x4250, 0x46fc, 0x24c9, 0x43ce, 0xb02f, 0x468d, 0xba17, 0x2ee2, 0x448b, 0xbfbd, 0x195a, 0x41a3, 0x33fb, 0x437a, 0xbf65, 0x4004, 0x43ab, 0x42c3, 0xaa5c, 0x2944, 0x1baf, 0x2a27, 0x4266, 0xbf91, 0x41c6, 0x42b8, 0xba74, 0x41b7, 0x355c, 0xbae4, 0x405f, 0x40be, 0xbaf5, 0x437e, 0xbac0, 0x4204, 0x436f, 0x46f3, 0x1f70, 0x4277, 0x4053, 0xbf80, 0xb2ee, 0xbfd5, 0xb2b0, 0x41e9, 0x424b, 0x4135, 0x4561, 0xbae0, 0xb200, 0xba21, 0x4360, 0xb0c1, 0xb207, 0x40a1, 0x4373, 0xb09e, 0x0929, 0x3cad, 0x1997, 0x411a, 0xb020, 0xbfa2, 0x3fca, 0xb23d, 0x087e, 0x055d, 0x4149, 0xb289, 0x2152, 0x1be1, 0x43b1, 0x40a1, 0xbf0d, 0x431d, 0xa7b2, 0x3d9a, 0x40a1, 0x42cf, 0x43e4, 0xbfc2, 0xaf46, 0xb23e, 0xb2e6, 0xb279, 0xa8af, 0xb076, 0xb0e7, 0x184a, 0x41bf, 0x42c8, 0x3d7b, 0xb02c, 0x1bd0, 0x1866, 0x14fe, 0x19c0, 0x409b, 0x435c, 0x4169, 0x405d, 0x4556, 0x4052, 0x435b, 0x412b, 0xbf73, 0x1ed8, 0x419f, 0x198b, 0x43da, 0x42f3, 0xba35, 0x1db6, 0xb012, 0x43d6, 0x0534, 0xabbe, 0x2ce2, 0x427f, 0x435f, 0x4169, 0x4265, 0xbf58, 0xb280, 0xbf90, 0x404e, 0xba75, 0x4162, 0x4105, 0xba5d, 0x40c4, 0x4044, 0x3603, 0x43c0, 0xb2ba, 0xb0b5, 0x247f, 0xba45, 0x4330, 0xb21a, 0x40ba, 0xba1d, 0xb24f, 0x43e3, 0xb09c, 0xbf5e, 0xb07d, 0xba5a, 0xb093, 0xb244, 0xba35, 0x419d, 0x4303, 0x09ce, 0x1af4, 0x4584, 0xb215, 0x4385, 0xb2ef, 0x46a4, 0x413d, 0x4389, 0x1d06, 0x07b8, 0x4558, 0x4494, 0x460a, 0xbf3c, 0x4158, 0xb225, 0xb2f1, 0x41ce, 0xbf2e, 0xb054, 0x4249, 0x45c6, 0x433e, 0x4685, 0x0ade, 0x4172, 0x42dc, 0x0fbd, 0x24d2, 0x4101, 0xbfd0, 0xb2a9, 0x4155, 0x416a, 0x4186, 0xba42, 0xb269, 0x1b7d, 0x4132, 0xa644, 0x1fc0, 0x43a1, 0xbf23, 0x02c3, 0x280b, 0x40b4, 0x4149, 0x4029, 0x217e, 0x10d4, 0x422a, 0x43ba, 0xba4b, 0x4291, 0x04ca, 0x1b38, 0x439a, 0x2d58, 0x42ca, 0x1e61, 0x2260, 0x4639, 0x46ca, 0x45be, 0xbf26, 0xb26d, 0x422e, 0x28d7, 0x4055, 0x3584, 0x41ff, 0x4322, 0xb27a, 0x40ee, 0x42f5, 0xba76, 0x40cc, 0xba68, 0x41b2, 0x1053, 0xbf7a, 0x4351, 0xb2a9, 0x4209, 0x2895, 0xb013, 0xbfd0, 0xa94c, 0x32bd, 0x41d8, 0xb283, 0x405c, 0x195d, 0xb25d, 0x1f07, 0xb249, 0x1a63, 0xb079, 0xaa92, 0x140c, 0xa9f0, 0x2d54, 0xa645, 0x37b2, 0x0ae8, 0xbf57, 0xbaf4, 0xba7c, 0x0abd, 0x42b9, 0x405c, 0x3f47, 0x43df, 0xb2db, 0x4329, 0x4134, 0x298d, 0xbaf3, 0xbfd5, 0xb2e3, 0x193e, 0xb232, 0xb276, 0xa3a0, 0x1d92, 0x41f6, 0x420f, 0x424d, 0xba32, 0x426a, 0xa208, 0x37fd, 0xba7e, 0x4363, 0x405f, 0x2b88, 0x1094, 0xbf1b, 0x1f17, 0xa26c, 0xbf70, 0xb27a, 0xb03a, 0x415b, 0x436d, 0xacf7, 0x42b2, 0xa2a5, 0x42f9, 0x414b, 0x4236, 0x1c8d, 0xbfa9, 0x41d8, 0x45a8, 0x43f5, 0x3226, 0xb0ec, 0x4248, 0x1e80, 0x4192, 0x1c5b, 0xb287, 0x4107, 0x467b, 0x4391, 0x406b, 0x4489, 0x18c6, 0x44dc, 0x4126, 0x1c8d, 0xb029, 0x42ac, 0x4055, 0x413e, 0x408e, 0xbf76, 0x052a, 0xbf90, 0xb261, 0xb2ae, 0x42b4, 0x26af, 0x43d5, 0x4084, 0xba6b, 0xb002, 0x09f3, 0x1f9b, 0x4300, 0xbf92, 0xa86c, 0x4187, 0x46f3, 0xb0cb, 0x41c1, 0x400e, 0x04b6, 0xbaf0, 0x10bc, 0xb0ff, 0xbfc1, 0xb244, 0x3bf7, 0x45a2, 0x4173, 0x415b, 0xba34, 0x3976, 0x1837, 0x40ad, 0x197e, 0x284b, 0x459a, 0x17e0, 0x41bf, 0x408f, 0x1ae0, 0x13a0, 0xbacd, 0x40c1, 0x420b, 0xba22, 0x44e8, 0x41a9, 0xa4b1, 0xb2ba, 0x41b2, 0x2d95, 0x4320, 0x4663, 0xbf37, 0xa9ae, 0xb2c5, 0x2310, 0x4329, 0x4239, 0xb0ef, 0x2d06, 0x4418, 0x42f3, 0xba76, 0xa639, 0x45d8, 0xb263, 0x2dc3, 0x1ce6, 0xbf4b, 0x43e6, 0xa2b7, 0x1c0c, 0x0252, 0xbf9b, 0x4151, 0x4062, 0x4148, 0x4360, 0x43bf, 0x07ca, 0x15e8, 0x40e2, 0x34b3, 0x433a, 0x42e4, 0x38ce, 0x34cc, 0x1796, 0xb04d, 0x3fc2, 0x46d3, 0x422e, 0xb09d, 0xbaf0, 0x4319, 0x4057, 0x435e, 0xbfe0, 0x4393, 0xbf64, 0x1a89, 0x422e, 0xb01e, 0x400c, 0xb07d, 0x1d17, 0x426c, 0x46ab, 0x454f, 0x44e5, 0x4085, 0xb209, 0x194d, 0x1f97, 0x411d, 0x4051, 0x1fd4, 0x3a6e, 0xbf4a, 0xb27a, 0x0cda, 0xae68, 0x4026, 0xb2cc, 0xba30, 0xb24b, 0x1e97, 0xbf77, 0x4231, 0xa0c7, 0xb268, 0x268b, 0xb0ad, 0xb209, 0x1b9f, 0x4035, 0x4237, 0x0231, 0x1aaf, 0xb0cb, 0x1d8e, 0x42e4, 0x42e4, 0x21cf, 0xb2d2, 0x420d, 0x4041, 0xac5b, 0xa18a, 0x43f6, 0xbaf7, 0x388e, 0x1c7f, 0xa574, 0xbf89, 0x43bd, 0x1ab6, 0xba61, 0x4218, 0x1ea0, 0x42c9, 0x432d, 0x4376, 0x1933, 0x4134, 0x1d9b, 0x27df, 0x43c4, 0xbfd5, 0x4203, 0xa8dc, 0x0ae8, 0x440a, 0xba0c, 0x3294, 0x4174, 0x42c9, 0xa2d7, 0x42ed, 0xbad3, 0x1bfe, 0x4326, 0x4035, 0x4348, 0xb292, 0xbf9e, 0x18f3, 0x43f1, 0x1367, 0x411b, 0x4477, 0x1e0b, 0x1d22, 0x1c39, 0x40bd, 0x435a, 0x1be5, 0x13c2, 0x1b7c, 0x433c, 0xa9d8, 0xa165, 0xbf69, 0xb057, 0x413d, 0x40f9, 0xb202, 0x3e95, 0x4185, 0xbf29, 0x1f39, 0x19fd, 0x42f1, 0xa2f2, 0xb2d2, 0xb070, 0x415c, 0xbf24, 0x43ba, 0x3aca, 0x1a2f, 0x4150, 0x43de, 0xb2df, 0x436d, 0xbfba, 0xad69, 0x1080, 0xb0f5, 0x4307, 0x0ab6, 0x4002, 0x41f4, 0xba63, 0x4220, 0xb28f, 0xba7e, 0xa3b6, 0xa952, 0xba6a, 0x447f, 0x1cd0, 0xb055, 0x4392, 0xb231, 0x1af3, 0x4354, 0x41cf, 0xa231, 0xbfde, 0x35ca, 0xab3b, 0xb0e7, 0x43ea, 0xbae7, 0x4375, 0x43ce, 0x31e5, 0x393c, 0x42f9, 0x4135, 0xb260, 0x409b, 0xba67, 0xb26d, 0x3fac, 0xb26c, 0x425d, 0x43de, 0xbf6d, 0x435b, 0x2efd, 0x1ae3, 0x429f, 0x187d, 0xb0a5, 0x41d7, 0x40b9, 0xbad6, 0x15d3, 0x4666, 0x46ad, 0xbf6f, 0x2348, 0x4006, 0x1221, 0xba18, 0x4271, 0x4057, 0x1829, 0x1e42, 0xa497, 0x1dc7, 0x41eb, 0x4225, 0x43b5, 0x1603, 0xb04f, 0xbf84, 0x3451, 0xb055, 0x4499, 0xb2e1, 0x41cd, 0xb25a, 0x41d8, 0x4305, 0x40ee, 0x411e, 0xba6f, 0x0452, 0x4160, 0x413d, 0x408c, 0x4232, 0x4047, 0x4452, 0xba4c, 0xa758, 0xbf21, 0x4616, 0x1990, 0x422a, 0x1dae, 0x1f74, 0x0f3f, 0x1f61, 0xbac5, 0x1d9e, 0xbaf1, 0xbf12, 0x1bd8, 0x2b26, 0xb05d, 0x09a5, 0x408e, 0x1ca7, 0x0a35, 0xa904, 0xaed0, 0x16fb, 0xbf80, 0xa941, 0xae2a, 0x1e3e, 0xb256, 0xa51e, 0xb2c4, 0x435e, 0xb288, 0x403f, 0xb2a9, 0xa590, 0x40a3, 0x4112, 0xbf99, 0x1c5f, 0x426e, 0x40c1, 0xb26c, 0xbfae, 0xb2a3, 0x43a7, 0xb213, 0x420f, 0x429f, 0x4080, 0x38a0, 0xba39, 0x1afa, 0xb027, 0x36e3, 0x37ef, 0x42c4, 0x40c9, 0x42de, 0xb0ec, 0xbf0b, 0xbac5, 0x241d, 0x25fb, 0x4182, 0xb251, 0xaa91, 0x4383, 0xbfb3, 0xba70, 0xb0a0, 0xba77, 0x4068, 0x1c8f, 0x12d3, 0xb27f, 0xb2c8, 0xbada, 0x3b8f, 0xbf32, 0xbaec, 0xb092, 0xbae3, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x989fe979, 0xeb6d1dc2, 0x0199a793, 0xfd6ddabf, 0x2337d8f4, 0x10d088ef, 0x6abc19fa, 0x03f7a6b3, 0x8efc124d, 0x26bfcdfd, 0x162448d8, 0x1f47e67e, 0xbfa7bf3d, 0xf5019635, 0x00000000, 0x100001f0 }, + FinalRegs = new uint[] { 0x000000ea, 0xffffffea, 0xfffffbff, 0x00001d00, 0x0000001d, 0x000019b4, 0x000000a6, 0xffffffec, 0x3fffff72, 0xffd83f36, 0xbfa7bf3d, 0x000000d6, 0x020003a0, 0xffffdcc8, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x432c, 0x0c6d, 0xb075, 0x4339, 0x43c2, 0xb043, 0x4305, 0x42c6, 0x0cb5, 0x3d42, 0x3249, 0x40c7, 0xbf9f, 0x0213, 0x4268, 0x4192, 0x342f, 0x20a6, 0xb2bf, 0x0700, 0x41ae, 0x17af, 0x1c55, 0xbfd1, 0x437d, 0x3f9d, 0x40a9, 0x41e4, 0xba3d, 0x415d, 0xb2bf, 0x4211, 0x2b93, 0x4161, 0x412a, 0x41e7, 0x2540, 0x41a9, 0xbacf, 0x34a4, 0x1847, 0x448b, 0x448a, 0x4187, 0x40cc, 0xa4d4, 0xb2fd, 0xb2d5, 0x1e75, 0x0762, 0x43ac, 0x4020, 0xbf8f, 0xb207, 0x4351, 0xb296, 0xb230, 0xb0e4, 0xbf4c, 0x44e2, 0x4043, 0xbac6, 0xb26b, 0xb2c3, 0x3a43, 0x4182, 0x46d9, 0x312b, 0x32a1, 0x3341, 0x4136, 0x4385, 0xbf77, 0x30bb, 0x4083, 0x4621, 0xba7a, 0x46c0, 0xb2eb, 0x42c5, 0xb27f, 0x1e4f, 0x416f, 0x199e, 0x4463, 0xbf8b, 0x4117, 0xbaf4, 0x45f2, 0x3e2f, 0xb248, 0x41d0, 0xb068, 0x467f, 0xa251, 0xb26e, 0x29aa, 0x2bbf, 0xb2cf, 0x439e, 0x1e50, 0x41f9, 0xbf80, 0x456d, 0xbf48, 0x1ff7, 0x46f9, 0x28ba, 0xbae5, 0x455a, 0xb23a, 0x4234, 0x4224, 0x3582, 0x41d8, 0x4038, 0x420d, 0x42a4, 0x2823, 0x2019, 0xbf98, 0x289f, 0xb027, 0x3f10, 0x2356, 0x43d4, 0x4327, 0x2b77, 0xab61, 0x43dc, 0x41fe, 0x1560, 0x01d5, 0x43cc, 0x4310, 0xbf04, 0x4053, 0x093b, 0x43b5, 0x4311, 0xb033, 0x30ba, 0xae34, 0xbf78, 0x4051, 0xba31, 0x4113, 0x4364, 0xaa6d, 0x2548, 0xb254, 0x1979, 0x4399, 0xb2d1, 0x2a2e, 0xb22e, 0x40a2, 0x1026, 0x428d, 0xb29f, 0x18b9, 0xb0b5, 0x422a, 0x4138, 0x2e00, 0x402b, 0xbf2c, 0xb06a, 0x187c, 0x402f, 0xba21, 0x407d, 0x008f, 0x4119, 0xb2ef, 0x4260, 0x4185, 0x43f8, 0x4028, 0xbae3, 0x4005, 0x1f3a, 0xad44, 0xb28e, 0xa699, 0xa201, 0x4243, 0xb2b5, 0x1978, 0x40d2, 0xbf48, 0x430a, 0x1965, 0xb222, 0x123c, 0x40ab, 0x0239, 0xb2b0, 0xbf9d, 0x36b8, 0x2273, 0xafdb, 0xb2dd, 0x1b98, 0x3bd9, 0x2680, 0xba1b, 0x1485, 0x4387, 0x42e2, 0x3bc3, 0x43c9, 0xb005, 0x363c, 0xbf07, 0xba48, 0x3851, 0xba45, 0x4288, 0x41ea, 0x19f6, 0xb2e6, 0x1a33, 0x413c, 0x4002, 0x4304, 0x42de, 0xbadf, 0x1b8a, 0x4032, 0xb219, 0x4423, 0x4634, 0x4089, 0x4640, 0x1b8d, 0x4207, 0x3e42, 0x41f1, 0x42fa, 0xbfa7, 0x4601, 0x1f59, 0xb2cf, 0x40bf, 0x4559, 0x1eb2, 0x1bdb, 0x42af, 0xb226, 0x44f4, 0x43a2, 0x1aea, 0x3d70, 0x190d, 0x436a, 0xb2b9, 0x17f6, 0xba64, 0x44d3, 0x400c, 0xb033, 0x4015, 0x0c75, 0xba2c, 0xbae8, 0xbf64, 0x1a67, 0x411e, 0xb2dc, 0x41ca, 0x45ec, 0x08d1, 0x4039, 0x42b9, 0x40f1, 0xb076, 0xae19, 0xb256, 0x3711, 0x4067, 0x410b, 0xa4c0, 0x1cd5, 0x1c2e, 0x1f01, 0x426c, 0x4274, 0x075d, 0xbf66, 0x2825, 0x1fca, 0xb266, 0xbaf2, 0x424a, 0x42f6, 0x236d, 0xb27f, 0xb2fa, 0x45eb, 0xbf0e, 0x1cdd, 0xaf37, 0x43a7, 0xb08e, 0x4258, 0x41f3, 0x211e, 0x40b6, 0xba7a, 0xbf76, 0x467c, 0x417d, 0x4173, 0x4623, 0x4058, 0xbad5, 0x423c, 0xbf80, 0x42e7, 0x3860, 0x2a24, 0x0276, 0xb033, 0xbfdc, 0xb2c3, 0xb224, 0xb27e, 0x2369, 0xba38, 0x4277, 0xb07a, 0x42c2, 0x41c2, 0x4668, 0xa9f2, 0xbae0, 0xbfc2, 0x41c2, 0x414e, 0x4322, 0x42d9, 0xbaf8, 0x169e, 0x4229, 0x2d30, 0x402b, 0x42ef, 0xb2e0, 0x42c4, 0x3c0f, 0x33ac, 0x117b, 0xbacd, 0x1961, 0x41af, 0x4041, 0xba1d, 0xbfa0, 0xbf7c, 0x403d, 0x3710, 0x429d, 0x41e8, 0xba30, 0xbf80, 0xacb2, 0x196a, 0x40b1, 0x416c, 0x1a81, 0xbf90, 0x41ef, 0xb079, 0x412c, 0xb00a, 0xbf80, 0x03d7, 0xb28b, 0x44ed, 0x4153, 0xbf7f, 0x413a, 0x4553, 0x416b, 0x4030, 0x408a, 0x1fc8, 0xb0d4, 0x3ad1, 0x4153, 0x4188, 0x409e, 0x45a0, 0x390b, 0x0957, 0x105d, 0x2a7c, 0x437c, 0xbf4f, 0xba1b, 0x1847, 0xb22a, 0x4574, 0xba75, 0x4162, 0x17f6, 0x4357, 0x42e1, 0x4186, 0x4378, 0x4035, 0xa34c, 0x43e1, 0x1d89, 0x46a1, 0x3132, 0x443e, 0x4173, 0x45c2, 0x1fd2, 0x41f9, 0xbfb4, 0x460c, 0x1f6e, 0x3143, 0x435a, 0xb2bc, 0x4272, 0x1719, 0xbacc, 0xbf46, 0x427d, 0x40d9, 0x437e, 0x03aa, 0x41fd, 0x4187, 0x3d10, 0x40b6, 0xb2d2, 0xbfd0, 0x427d, 0x421c, 0x430a, 0xba14, 0x18cb, 0xba2c, 0xb087, 0xbfb3, 0x3e14, 0x4305, 0x4134, 0xb291, 0x165c, 0x406b, 0xbf1e, 0x40cd, 0xb2dc, 0x403c, 0x029a, 0x0386, 0x406e, 0x429d, 0x1e23, 0x441c, 0x0b65, 0x460e, 0xb224, 0x4158, 0x3c93, 0xa9dc, 0xbfbd, 0x0dfc, 0x46ba, 0xb07f, 0xba01, 0x414d, 0x1744, 0x1cce, 0x40fa, 0x4120, 0x2b23, 0x4465, 0x1c51, 0x0aef, 0x4234, 0xb04a, 0x4392, 0x2c8e, 0xb20e, 0xbf61, 0xbae5, 0xba31, 0x4055, 0x42b4, 0xb28b, 0x02b5, 0x02a8, 0xa873, 0x417e, 0x41fe, 0x42c0, 0x4259, 0x41bc, 0xbff0, 0xbfc1, 0x1987, 0x40d2, 0x1b97, 0x41fc, 0x0019, 0x4139, 0xb212, 0x151d, 0x4231, 0xbfb0, 0xbafb, 0x06e7, 0x1817, 0x4270, 0x006c, 0x409c, 0x3797, 0x1801, 0x4116, 0xb242, 0x4028, 0xb2e8, 0x0e08, 0x426b, 0xbf8c, 0x1eb2, 0xb27e, 0xb2e9, 0x3814, 0x0b2a, 0xb2e7, 0xbf87, 0x2e03, 0x1bd0, 0x43f5, 0x4356, 0x42b1, 0xa47c, 0xb0f4, 0x427d, 0xb26a, 0xb287, 0x1b09, 0x0484, 0xbf85, 0x4112, 0xb057, 0xbfc0, 0x2044, 0x435f, 0x41a3, 0x3ba9, 0x4141, 0xb2a4, 0x4060, 0x40af, 0xb0fd, 0x4243, 0x4170, 0x422a, 0xba39, 0x416d, 0xbf81, 0xb2ef, 0xb061, 0xb015, 0x437e, 0xbfe2, 0x4168, 0x13c9, 0xb02d, 0x296a, 0x42d2, 0x3994, 0x426b, 0x46c3, 0x1a5d, 0x4336, 0x4133, 0x43a4, 0xb202, 0x42dd, 0x3226, 0xb2a9, 0xb0e7, 0x23db, 0xb072, 0x1bf5, 0x43f7, 0x44c5, 0xb223, 0x4312, 0xbf47, 0xba56, 0x1089, 0x27e8, 0xb208, 0xabdd, 0x3972, 0x41ce, 0x4371, 0x41c1, 0x4082, 0x2c57, 0x1ef7, 0x428c, 0x4053, 0x21b6, 0x447d, 0xb015, 0x40d2, 0x4110, 0x0df3, 0xbf8d, 0xb28d, 0x4350, 0xbada, 0x4058, 0xbaf3, 0x1e42, 0xbad4, 0xabca, 0x2d58, 0x41b7, 0x421f, 0x40a4, 0x0c09, 0xb022, 0x4170, 0x1811, 0x3af8, 0x30c1, 0xba50, 0x41e6, 0x4477, 0x425d, 0x4076, 0x42ca, 0x43a5, 0x1834, 0xbf7a, 0x31ad, 0xbafe, 0x1bc6, 0xb247, 0x1db0, 0x418f, 0x4163, 0x4008, 0x4674, 0xb2f5, 0xb269, 0xb215, 0x066e, 0x19dc, 0x24a4, 0xba0e, 0x4100, 0xbac7, 0xa0f0, 0x46ec, 0xa3c8, 0x311e, 0x0490, 0xb04b, 0x4016, 0x4085, 0x4024, 0xbf5b, 0x4028, 0xb2e5, 0xb054, 0x4189, 0xb267, 0xa751, 0x42b0, 0x40ca, 0xaf76, 0xbace, 0xbad2, 0xbf99, 0x1ef3, 0x435f, 0x4384, 0x3383, 0x4247, 0x4338, 0x1a84, 0x416a, 0xbaea, 0x41a6, 0x4002, 0xbf90, 0xb05a, 0xa518, 0x3010, 0x43eb, 0x4350, 0x352f, 0x43c9, 0xba2b, 0x3361, 0x421e, 0xb268, 0x407d, 0xbfb4, 0xba4e, 0x44b5, 0x41a2, 0x419f, 0x410f, 0x46c9, 0x19d8, 0x0eec, 0xbad3, 0xa0a0, 0xbf60, 0xbfda, 0x188a, 0x129a, 0x400d, 0xba64, 0x4310, 0x41af, 0xba79, 0xba55, 0xb063, 0xbf80, 0xbf0e, 0x442d, 0x1826, 0x42d1, 0x1bf8, 0x435f, 0xba15, 0x2f8f, 0x1945, 0xa28c, 0x41f4, 0xbf93, 0x1eab, 0xaaf6, 0xba0b, 0x2fda, 0xbfb0, 0x4228, 0xa802, 0xb2f6, 0x425d, 0xb2b8, 0xbf33, 0x418e, 0x435b, 0x1feb, 0x4252, 0x4359, 0x462b, 0xba17, 0x40bf, 0x43f9, 0x43d5, 0x03c1, 0x40bb, 0x40f7, 0x4391, 0xaa2c, 0x45b8, 0x4678, 0x42ad, 0x4088, 0x03c6, 0x4573, 0x40e9, 0xb282, 0x41cd, 0xb2f6, 0x1913, 0x40cd, 0x41e9, 0xbfa5, 0x262c, 0x42c9, 0x28d4, 0x3fec, 0x3b30, 0x42a4, 0x41ab, 0xbff0, 0x43f8, 0xb225, 0x41dc, 0xb2ff, 0x1866, 0x2870, 0x4286, 0xab63, 0x3889, 0x407d, 0x40fa, 0x25a1, 0x4488, 0x416c, 0x433a, 0xbfbf, 0x0155, 0x43f6, 0xb225, 0xb22e, 0x403f, 0x1355, 0x428f, 0xba2e, 0x438b, 0xb074, 0x445a, 0xb2de, 0xb0f1, 0x4241, 0x387f, 0xa5ab, 0x401c, 0xb02a, 0xb23a, 0xb22c, 0x2cba, 0x43c6, 0x1755, 0x4313, 0xbfb1, 0xa2de, 0x085c, 0xb0d9, 0x2705, 0xbf81, 0xb2af, 0xba09, 0x4314, 0xb2fa, 0xbad6, 0xbfb0, 0xbaf6, 0x1ba9, 0x4699, 0xb058, 0x43de, 0x32d1, 0x1c10, 0x41cb, 0xb281, 0x41be, 0x4360, 0xb296, 0x01d1, 0xb227, 0xafe4, 0x3965, 0x19d5, 0x40b6, 0xb09d, 0xb2e4, 0x42c3, 0x426a, 0xab02, 0xbf48, 0x4143, 0xbf1e, 0xb091, 0x0169, 0x09c9, 0xba62, 0x1f2b, 0xb060, 0xb0c4, 0x4353, 0x4377, 0x1c67, 0x4083, 0x1ba9, 0x43cb, 0x435f, 0x1b26, 0xb2ed, 0x3409, 0x2f3c, 0x41d0, 0x4306, 0x19f8, 0xb035, 0x4401, 0x076b, 0x4474, 0x1add, 0x41a4, 0xbf6a, 0x2302, 0x401a, 0x44f1, 0xbf6c, 0x426f, 0xb214, 0xb25d, 0xb226, 0x010f, 0x4261, 0xbacc, 0xbf25, 0x434e, 0x2220, 0xa694, 0x0089, 0x421d, 0x4083, 0x4092, 0x4333, 0xb22f, 0x4217, 0x432b, 0xb0d5, 0x4637, 0xab70, 0x42b4, 0x3cef, 0xb21a, 0x09ba, 0xb2fb, 0x4193, 0x41eb, 0xba2a, 0xba50, 0xa277, 0x4198, 0x4075, 0xbfbc, 0xaba3, 0x1814, 0x1dbd, 0xb260, 0x4305, 0xbfc0, 0x4334, 0x43cb, 0x2690, 0x1dd1, 0x42f4, 0xbf3f, 0x4294, 0x034c, 0x31f5, 0x4616, 0x4340, 0x224f, 0xba03, 0x4063, 0xb2ef, 0x4026, 0x4182, 0x4321, 0xaa72, 0x4242, 0x4265, 0xb085, 0xbaf2, 0xbfc9, 0xb2dc, 0x432c, 0x0a3b, 0xba19, 0x40fa, 0x4580, 0x1ec1, 0xba1b, 0x1a32, 0xa367, 0xbfe0, 0xacd3, 0x4179, 0x13e8, 0x2574, 0xb25c, 0xb228, 0x1852, 0xbfcb, 0xb2b6, 0x415c, 0x42bc, 0xba53, 0x439d, 0xbf2a, 0x15c3, 0xb2ac, 0x0d9c, 0xbf1c, 0xba32, 0xbaed, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x73229dce, 0x0f640776, 0x9c25ec11, 0x66a54f6d, 0xca4c2372, 0x617f5393, 0xfc67eb08, 0x99c5589c, 0x6805463b, 0x1f6ada41, 0x7ed9c21c, 0x50147fab, 0x6e783256, 0x5296ab63, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x00000074, 0x00000135, 0x10000000, 0x00000000, 0x00000000, 0x00003000, 0x00000010, 0x00000017, 0x6805463b, 0x0d36c137, 0x0a3e95fc, 0x6805463b, 0x0d32b38d, 0x0d36bf7f, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x42c2, 0x2d74, 0x4116, 0x4185, 0x40fd, 0x428a, 0x1707, 0x406c, 0xb298, 0xb252, 0x41ac, 0x1a53, 0xba5c, 0x411c, 0x4253, 0x3541, 0xbf27, 0x015a, 0xba76, 0x1226, 0x4251, 0xba16, 0xbfd0, 0x352c, 0x417b, 0xb009, 0x34c2, 0x41a9, 0x1d34, 0x438e, 0xb083, 0xb24a, 0x41d0, 0x1f5b, 0x1c0b, 0x4322, 0x1ae0, 0x234a, 0xbfd2, 0x43c9, 0x4212, 0x1556, 0x456d, 0x07bd, 0x4336, 0x43ab, 0xba1f, 0x3fd4, 0x4210, 0x41e2, 0x18d9, 0x1d4d, 0x4572, 0x4199, 0xb25e, 0xb207, 0x43fd, 0x3dee, 0x4382, 0x42fb, 0xb0cc, 0xba09, 0x2beb, 0x00e1, 0xba2a, 0xb068, 0xbf31, 0x40f0, 0x3c42, 0xb236, 0xb274, 0x44da, 0xa2d8, 0xb211, 0xba22, 0x43f8, 0x4102, 0x42e7, 0x46ca, 0x24ab, 0x4423, 0xb0f0, 0xb221, 0x421f, 0xb20d, 0x1dbe, 0x460c, 0x4040, 0xb026, 0xba47, 0x404d, 0xb22b, 0xbfb3, 0x43a8, 0x107b, 0xbf60, 0xb2d0, 0x430a, 0x3e97, 0xb272, 0x1c2b, 0x42b5, 0x4053, 0xb286, 0x42e0, 0xba44, 0x4114, 0x421c, 0xb2bf, 0x1eb5, 0x41de, 0x4097, 0xb273, 0x4121, 0x198c, 0xb281, 0xb03c, 0x409c, 0xbf46, 0xb2db, 0x443a, 0x41bf, 0xb244, 0x43a6, 0xaa45, 0xb03b, 0x43c8, 0x1d75, 0x400b, 0x1c22, 0xbae4, 0x0c72, 0x4130, 0xbae0, 0x46cc, 0xbfb1, 0x43ad, 0xba49, 0x41c2, 0x0327, 0x4027, 0x41ff, 0x4085, 0xbfcb, 0x412a, 0x1157, 0x43ab, 0xb23c, 0xb278, 0x421c, 0x416c, 0x4173, 0x43b1, 0x41c4, 0x40e9, 0x420e, 0x1bdf, 0xbfd0, 0x4672, 0xba6d, 0x07d1, 0x43c0, 0xb2aa, 0x40d1, 0x41d4, 0xa179, 0x44b2, 0xb052, 0x464a, 0xb24e, 0x4476, 0xa5a9, 0xbf0d, 0xbf90, 0x40c4, 0x44d9, 0x43cc, 0xbf00, 0x42bd, 0x4123, 0xb0ef, 0xba5f, 0x3212, 0x26f0, 0x4357, 0xb227, 0x43ef, 0x28d3, 0x1a18, 0x1e20, 0xade4, 0x42b0, 0xae3b, 0xa4cb, 0x1e56, 0x41c0, 0x434c, 0xbf51, 0x411e, 0xbfe0, 0xba57, 0xb06f, 0x4278, 0xbadd, 0xa0b7, 0x43b7, 0x41fe, 0x4284, 0x41a9, 0x40d7, 0xb0e4, 0x4418, 0x2466, 0x401f, 0xbfa4, 0xb258, 0x1aa7, 0x427b, 0xb2ec, 0x40e4, 0x40f3, 0x1f6b, 0x4462, 0xb29d, 0xb09d, 0x3a5d, 0x43d9, 0xb253, 0x40a7, 0xbac5, 0x4193, 0x0e8b, 0x43c0, 0x4214, 0x2268, 0xb2fc, 0x24b2, 0xb20c, 0x431e, 0x31a0, 0x43de, 0xbfc7, 0xad2f, 0xb0f3, 0x1fc6, 0xb272, 0x40fe, 0xb24f, 0x4115, 0x456e, 0x3ea3, 0x4122, 0x4323, 0x41b7, 0x414b, 0x4624, 0x33c0, 0x1eb4, 0x4333, 0x419e, 0x1a49, 0x1cc2, 0x35b5, 0x423e, 0xbf7c, 0x1f87, 0xb2cd, 0x43a1, 0xbf1c, 0x4215, 0xba79, 0xbfd7, 0xbad0, 0xba62, 0x4213, 0x429a, 0xbfbb, 0x43d6, 0x4197, 0x426d, 0x43f5, 0x4267, 0x40cd, 0xb2ad, 0x4071, 0xa27f, 0x3837, 0x408f, 0x41f2, 0x4167, 0x1d86, 0xaa44, 0xbae5, 0x1f57, 0x456a, 0x45c6, 0x42fb, 0xb2bc, 0xbf21, 0x199d, 0x1f8b, 0xba71, 0x443f, 0x1192, 0x4240, 0x43c2, 0xba2b, 0x4196, 0xb242, 0x3996, 0x0844, 0x4340, 0x4691, 0xbfe4, 0x1911, 0xb0fd, 0x402d, 0x419f, 0xad1e, 0x4038, 0x3877, 0x2e52, 0xb2a3, 0x2181, 0x35e8, 0xa034, 0x4115, 0x1b67, 0x43c1, 0x1c10, 0x2cac, 0x2a71, 0xb2f6, 0xa5dc, 0x4184, 0x4013, 0x19f5, 0x40c7, 0x4085, 0xbfc8, 0x456d, 0x4235, 0xba27, 0xbac8, 0xb298, 0xb29c, 0xbf00, 0xb051, 0x42d1, 0xb220, 0xbf03, 0xa71c, 0x44c0, 0x4327, 0x195d, 0x4092, 0x3e66, 0x40d0, 0x4345, 0x42fe, 0x41f3, 0x11cf, 0x4043, 0x1c92, 0xbacc, 0xbf04, 0x406f, 0x1da6, 0xba6a, 0x4059, 0xb01b, 0x43b6, 0x3fce, 0x43ff, 0x403e, 0x43da, 0xafac, 0x409e, 0x2481, 0x4148, 0x439e, 0x4220, 0x44a2, 0x437c, 0xbf3f, 0xb24d, 0x0870, 0x1cbd, 0x41bc, 0x4035, 0xb27f, 0xbf0a, 0x411c, 0x40f0, 0x1740, 0x4449, 0x4254, 0x430e, 0xb209, 0x41b6, 0x43f3, 0xb244, 0xbf70, 0x19bf, 0x412d, 0xa727, 0x1c54, 0x41e4, 0x43ee, 0xb093, 0xbf2b, 0xba16, 0x4063, 0x1e36, 0x35fc, 0xbae6, 0x4198, 0xbfb5, 0x01d4, 0x3182, 0x1fe1, 0x40f2, 0xbfc9, 0xb248, 0x2caa, 0x36f3, 0x40ac, 0x18b3, 0xb2a0, 0x43bc, 0xbfc0, 0xbf81, 0xb044, 0x43e6, 0x1fc6, 0xa96e, 0xb2fd, 0xa63b, 0xb002, 0x1aa8, 0xba59, 0x4307, 0x401a, 0x3bc9, 0xb2b3, 0x0cde, 0x4183, 0x4163, 0x4493, 0x430f, 0x4302, 0x42a0, 0x1d82, 0xbfd0, 0xb21e, 0x432c, 0xbfd9, 0x30c9, 0x406e, 0xa56b, 0xb05a, 0x3184, 0xba02, 0x43a5, 0xb204, 0xb229, 0xa861, 0x0096, 0x2731, 0xbf48, 0x13cf, 0x424a, 0x4564, 0xb2e8, 0x1c0d, 0x03f4, 0x434c, 0xb051, 0x0c36, 0x4286, 0xb2a2, 0xb082, 0x11ca, 0x195d, 0x35c8, 0xa2bb, 0x4649, 0x41a1, 0x423c, 0x458e, 0xb28a, 0xb0e0, 0x1dea, 0x365e, 0xbfdc, 0x33a6, 0xba77, 0x4318, 0x09c2, 0xbf9e, 0x1d19, 0x1660, 0x4213, 0x3a84, 0x1c5a, 0x1e48, 0x4174, 0x4585, 0x1cb4, 0xbf3f, 0x1280, 0x4215, 0x4036, 0x43f3, 0x4361, 0x4119, 0xb26a, 0x40a1, 0xb0e1, 0x4609, 0x3d49, 0x3257, 0xb2c3, 0xbfc0, 0x41c0, 0x41ff, 0x40ff, 0x3cec, 0x1c4c, 0x4217, 0xb20e, 0x411c, 0xb07c, 0x41ce, 0xbf79, 0x4349, 0x4392, 0x1b61, 0xba4f, 0x415e, 0x3dc1, 0x1623, 0xb0d5, 0x4099, 0x41c4, 0xbf0d, 0x1e73, 0x43e9, 0x4461, 0xb2ab, 0xba3a, 0x43a1, 0x4083, 0x428c, 0x4277, 0x0587, 0xb29d, 0xae40, 0xbad2, 0x42ad, 0x45e1, 0x407a, 0x4290, 0x409c, 0x42b5, 0x4001, 0xb2af, 0x25ef, 0x32ea, 0x1ad7, 0xbfc7, 0x42a0, 0x1cf5, 0x3c74, 0xbf00, 0x41f7, 0xb270, 0x4009, 0xaf02, 0x4331, 0x4627, 0x42ac, 0x4020, 0xbfb3, 0x42c2, 0xa284, 0xab3b, 0x40b2, 0x4319, 0xb051, 0x42cc, 0xb0ae, 0x206b, 0x413b, 0xb241, 0x2001, 0xb2b6, 0xbfa2, 0x408d, 0x34d3, 0x43db, 0xba01, 0x1a1f, 0x4396, 0x42b9, 0x4335, 0xa054, 0xa317, 0x1c89, 0x4186, 0xb01f, 0xba56, 0x4616, 0xb204, 0xb24e, 0x028d, 0x41d0, 0xb217, 0x2943, 0x402d, 0x1de0, 0x1988, 0xba51, 0xbfdd, 0xa8d1, 0x41d9, 0x40e2, 0x441a, 0x41fc, 0x4018, 0x4354, 0x430c, 0x404a, 0xb2a3, 0x42dc, 0xb230, 0xa56c, 0xbf05, 0x4121, 0x434e, 0xbae3, 0x401c, 0x2b71, 0xb2bb, 0x43b2, 0x084a, 0x19e1, 0x1f23, 0x444a, 0x40cf, 0xbf21, 0x41ef, 0xb243, 0x43ab, 0x404d, 0xb229, 0x00d5, 0xb2ce, 0x0a30, 0x41f0, 0x1c86, 0x419b, 0x1f9f, 0xa91e, 0x4164, 0xb0a2, 0x318d, 0x414a, 0xa788, 0xbf60, 0x42aa, 0x432f, 0x1754, 0xba1d, 0x4148, 0xbf0e, 0x44bc, 0xba0e, 0xabb9, 0x18ad, 0x0df7, 0x46e3, 0xbad6, 0x432d, 0x42c3, 0xb21e, 0x1ee1, 0x43be, 0x437d, 0xb23d, 0x43a1, 0x42cb, 0x0e87, 0xb226, 0xba14, 0x1cd1, 0x0971, 0x1a7d, 0x2824, 0x40bb, 0xb2bb, 0x0c39, 0xbf9f, 0xb2a2, 0x43b1, 0x0463, 0x426a, 0x18bb, 0xba13, 0x4311, 0xabcc, 0xba1e, 0x41d1, 0x3d05, 0x43f5, 0xafc8, 0x3d69, 0x405e, 0x38ae, 0xa74e, 0x401b, 0x417b, 0x378f, 0x4204, 0xbf60, 0x1c69, 0x378e, 0x0c89, 0xbfb4, 0xbfc0, 0xb235, 0x4032, 0xb218, 0x4011, 0x4188, 0x3b23, 0x4277, 0x186e, 0x3f07, 0x3860, 0x44f5, 0x46ac, 0xb019, 0x42fc, 0x41ef, 0x2835, 0x45e4, 0xbf77, 0x4038, 0x2bca, 0x42fc, 0x4303, 0xbad5, 0x00fc, 0x197c, 0x2b44, 0x39e8, 0xb2aa, 0x4674, 0xa0f7, 0xb2b3, 0xa01d, 0x1b99, 0x429e, 0x101a, 0x41c5, 0xb085, 0x4320, 0x3520, 0x1c04, 0x4283, 0x420c, 0x3cf3, 0x42f2, 0x414f, 0xb2c6, 0xbfdf, 0x41db, 0x4139, 0x18e3, 0xba4c, 0x43b7, 0xb0c2, 0x051f, 0x42cc, 0x4300, 0x1caf, 0x1ef3, 0x417a, 0x1aef, 0xbfa1, 0x43d4, 0x18cd, 0xb24c, 0x2610, 0xb22b, 0x4355, 0xb213, 0x1c9b, 0xb266, 0xbf82, 0x422b, 0xb048, 0x03e0, 0x4312, 0x4325, 0xa9c3, 0x43a1, 0xb083, 0x295f, 0x4257, 0xb221, 0x1ab7, 0xba30, 0x43b0, 0x27d6, 0x03ce, 0x403e, 0x4247, 0x1a77, 0x179e, 0x1ecd, 0x42b2, 0x1fcb, 0x3acb, 0xbf93, 0x1fdb, 0xb06b, 0xb2ec, 0x4116, 0xbad3, 0x0437, 0xb209, 0x2cc6, 0x1355, 0x402d, 0x4424, 0xbff0, 0xb0df, 0xb2ae, 0x4373, 0xbf75, 0x448c, 0x1c21, 0x1ebf, 0x40f4, 0x427d, 0x417f, 0xb205, 0x40fb, 0x406a, 0xba49, 0x40b8, 0xba45, 0x45d0, 0x419a, 0x41d2, 0x43c6, 0x1ada, 0x42bb, 0x44dd, 0xbf87, 0x1c48, 0x1116, 0x02ee, 0x41db, 0xb225, 0x3137, 0x0f02, 0xaf6e, 0x1ba4, 0x4085, 0xb2fd, 0x4699, 0xbf70, 0x4401, 0x03f6, 0xb0d6, 0xa069, 0x42f2, 0x1a31, 0x1c36, 0x421c, 0x403f, 0x3545, 0x1f3f, 0x4255, 0x1cae, 0xb0ef, 0x3f75, 0x41bc, 0xbfde, 0x40f2, 0x42cc, 0x1dbe, 0xb240, 0xb00b, 0x0f1b, 0xba61, 0x41da, 0x424a, 0xba4b, 0xbf0d, 0x2f8d, 0x418f, 0x189f, 0xb045, 0xa835, 0x2001, 0x28d8, 0xb2e9, 0x4018, 0x441d, 0xb0ff, 0x156f, 0xb240, 0x1d6c, 0xb209, 0xb067, 0xbf60, 0x4280, 0xbaee, 0xbafb, 0x41dd, 0xb040, 0xa92d, 0xbfac, 0x00f2, 0x1d85, 0x320b, 0xb2f5, 0x415e, 0x42d2, 0xb23b, 0x0f07, 0x436d, 0x4142, 0xb2fa, 0xb218, 0xba0a, 0x4088, 0xbf80, 0x19da, 0x1356, 0x43c8, 0x2171, 0x43e1, 0xbfda, 0x4215, 0x42bd, 0x4364, 0x424e, 0xb228, 0x4009, 0xbaee, 0x1bce, 0x0502, 0xb2ae, 0xb217, 0xa6c0, 0x2900, 0x4033, 0x40e8, 0xba45, 0x2caf, 0xb21f, 0xbae6, 0x37ae, 0x2684, 0x43c8, 0x31de, 0xb26f, 0x40eb, 0x4293, 0xb209, 0xbf0e, 0x41ea, 0x4290, 0x40c1, 0xb282, 0x1aa9, 0x28c0, 0x1b96, 0x1d5d, 0x13c2, 0x437f, 0xafd5, 0x41fa, 0xba21, 0x0efd, 0x412a, 0xb08c, 0x4011, 0x46ed, 0xbaee, 0x0121, 0xb250, 0x4075, 0x4386, 0x41a3, 0xbfbe, 0x21d1, 0x34ae, 0x2fce, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xebd6df8c, 0x38bf55a7, 0xab137ca7, 0x96f9989a, 0x94c70e1c, 0x27865596, 0x517c3a55, 0x121297cb, 0xb921216d, 0x142c6285, 0xbbeafbb7, 0x8e13fca7, 0x38c24dd1, 0x08c88e6c, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0x00000061, 0x000000d1, 0x0ffffc61, 0xbd9eb746, 0x42616367, 0x00000303, 0x00000300, 0x1cf4f061, 0xb921216d, 0x00000000, 0x142c6306, 0x142c6285, 0x0000589c, 0x1cf4ecdd, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4338, 0x4299, 0x454c, 0x40a5, 0x4298, 0xbfa8, 0x3d94, 0x19e4, 0x345c, 0x45c0, 0x41ff, 0x1ab0, 0x19a5, 0x420a, 0x41df, 0xba39, 0x3566, 0x410a, 0x4337, 0x444a, 0x4290, 0x0663, 0xabc7, 0xa261, 0x324d, 0xbf41, 0xb222, 0x31c6, 0x10f2, 0xaee3, 0x43e0, 0x297b, 0xb284, 0x42f3, 0xbf60, 0x419e, 0x1577, 0xb2a4, 0x43a9, 0x1fcf, 0x44f2, 0x4404, 0xbaf0, 0x1f57, 0x4047, 0x1e49, 0x4187, 0xbf56, 0x40f3, 0x1380, 0x1fe3, 0x41a4, 0x4320, 0x407d, 0x428a, 0x1547, 0x439d, 0x40b0, 0x3a5b, 0x430b, 0x43f6, 0x4256, 0x195f, 0x423f, 0xb225, 0x4616, 0x4041, 0xb0c4, 0x43e0, 0xbfa6, 0xa0f5, 0xb077, 0x419f, 0xb2da, 0x1d2d, 0x40c0, 0xbfd0, 0xb243, 0x41f6, 0x41ff, 0x4390, 0x435b, 0x416e, 0x3f68, 0x0b87, 0x435d, 0xba48, 0x1af2, 0x410f, 0xa9b6, 0xbfbc, 0x4608, 0x1e08, 0x0178, 0xbfa8, 0x4019, 0xbae4, 0xb2f8, 0xa945, 0x1037, 0x421e, 0xb083, 0x422d, 0xa323, 0x34de, 0x439b, 0x407a, 0x4076, 0x4343, 0x4150, 0xb242, 0xba63, 0x302d, 0xbfd6, 0x06a7, 0x2697, 0x41d0, 0x415d, 0x4205, 0xbf70, 0x40c4, 0x1aa6, 0x42c8, 0x43b3, 0x4044, 0x4250, 0x3f43, 0x25e4, 0xbfc5, 0x19c7, 0xbf60, 0x43f1, 0xa2d9, 0xba5c, 0x4112, 0x461e, 0x1fc7, 0x1ad9, 0x425a, 0x4100, 0x2df6, 0xb284, 0x4295, 0x01fe, 0x0e8d, 0x42c2, 0x401f, 0xb277, 0xba3c, 0x4231, 0xbf43, 0x1e0b, 0x2c65, 0x12e5, 0x4119, 0x463a, 0x408e, 0x4358, 0x4356, 0x40d5, 0x4329, 0xbf71, 0x11c0, 0x0e12, 0x4022, 0x41e7, 0x467d, 0x460e, 0xb221, 0x41a2, 0x40bf, 0xbf27, 0xaeb8, 0xb2fc, 0xb0f1, 0x42ad, 0x1a47, 0x0111, 0x1a9e, 0xb04f, 0x40de, 0x4146, 0x3ad4, 0xa618, 0x408d, 0x1a2f, 0x1b24, 0x1dcd, 0xbac0, 0x3909, 0x3e34, 0x43e0, 0x4328, 0x434d, 0x4268, 0x404b, 0x1493, 0x435b, 0x45b9, 0xbfc9, 0x4439, 0x412a, 0x1de7, 0x0b4e, 0x436b, 0x4173, 0x304b, 0x447e, 0xa9c9, 0x403b, 0x42d6, 0x42a2, 0x43da, 0x1d54, 0x42eb, 0x4378, 0x464b, 0x1a7b, 0x292c, 0x3a9a, 0xbf75, 0x1ab2, 0x2b8f, 0x1db2, 0xb2d8, 0xb288, 0xbaed, 0x4202, 0x1df2, 0xb282, 0x0d5f, 0xbf05, 0x2cb7, 0xb293, 0x09e9, 0x4016, 0x403d, 0x43aa, 0x42bb, 0x4218, 0x44a0, 0x40e0, 0x4079, 0x1b6b, 0x41b3, 0x429e, 0x469c, 0x42fe, 0x1a84, 0x4481, 0xb2c7, 0xa3da, 0x42e6, 0xbfb9, 0x1f14, 0xb2ef, 0xba2b, 0x38bf, 0xadd9, 0x42fd, 0x1918, 0x1bc9, 0x0495, 0x08f9, 0x40a9, 0x3a59, 0x1821, 0x45f5, 0x4050, 0x427c, 0xb234, 0x45d0, 0xbaef, 0x43cc, 0xbfaf, 0xbad3, 0xb20a, 0xba2a, 0x43fb, 0xb26d, 0x1913, 0xb074, 0x1e82, 0x427c, 0x1bb6, 0x40d6, 0xbf04, 0xb202, 0x1ad3, 0x4041, 0xb26d, 0xb284, 0x405f, 0x1e47, 0x421f, 0x4036, 0x40e8, 0x4117, 0x414b, 0x421c, 0x40bb, 0xb2c4, 0x189b, 0xb20e, 0x4262, 0xabcd, 0xbf8c, 0xba15, 0x40ec, 0x406d, 0xb032, 0xb288, 0x2202, 0x1574, 0xb280, 0xbfa2, 0x4229, 0x0a10, 0x25cd, 0xb0bb, 0x442d, 0x41d1, 0x400e, 0x40d7, 0x41da, 0xb0f5, 0x439e, 0x1c27, 0xbfa5, 0x1037, 0xa2f5, 0x1d46, 0xbfe0, 0x4118, 0x2e5b, 0x1d76, 0xb2eb, 0x413e, 0x422e, 0x2f5d, 0xb277, 0x4255, 0x293d, 0x42ba, 0xa3c3, 0x3e34, 0xbfa9, 0x4372, 0x459b, 0xb2a6, 0xbfb0, 0x418f, 0x4039, 0x420b, 0xa411, 0x3f81, 0xbf71, 0x463b, 0x0320, 0x4136, 0xba7e, 0xa4fa, 0x41cd, 0xba36, 0xbf36, 0xb0f5, 0x415e, 0x1e0c, 0x44cb, 0xba29, 0x1e27, 0x04f0, 0x1d30, 0xbfe0, 0x41c2, 0x412d, 0xb270, 0x1d49, 0x1c0f, 0xb04c, 0x434d, 0x3c92, 0x1f4e, 0x30be, 0xade7, 0x4344, 0xba1f, 0xa02e, 0x4113, 0x1e0d, 0xb25b, 0xbfa4, 0xb001, 0x3134, 0x409f, 0x4150, 0x1b5c, 0x4007, 0xa6fa, 0x2ffd, 0x42d5, 0xba53, 0xb23f, 0x40e3, 0xb0ca, 0xa7f4, 0x18ef, 0x0936, 0x43c7, 0xbfc2, 0xab87, 0x4378, 0x3710, 0x40f6, 0x4622, 0x456a, 0x46ca, 0x46a4, 0x41b8, 0x412b, 0xba2f, 0x1e94, 0xba4e, 0xb2e4, 0x43e0, 0x1a71, 0x411d, 0x4164, 0x0b2b, 0x40bb, 0x43c1, 0x4267, 0xba1d, 0xb2ab, 0xbfc0, 0x0624, 0xbfbd, 0xb02d, 0x43e9, 0x18ad, 0x3146, 0xa882, 0x227f, 0x4382, 0x4314, 0x19cc, 0xb25d, 0x465b, 0x43c9, 0x1800, 0x4169, 0x426f, 0x427a, 0x182c, 0x41ed, 0x2dfb, 0x1239, 0x41a2, 0x1aae, 0x3887, 0x42c2, 0x4352, 0x46d2, 0x4362, 0xb27a, 0xbf5b, 0x3437, 0xb09b, 0x41fa, 0x1fa2, 0xb2ee, 0x3c06, 0x437b, 0x2315, 0x410e, 0x404e, 0xbf78, 0x4231, 0x4387, 0x4258, 0x32ba, 0xb264, 0x43c0, 0xbfc0, 0x4244, 0x46b2, 0x408a, 0xba45, 0xbf07, 0x1a65, 0x19c0, 0x448b, 0x3b5f, 0x419d, 0x4653, 0xbaf2, 0x423f, 0x4118, 0x4138, 0xbaf1, 0xb2ed, 0xb26c, 0x2947, 0x41d4, 0xb265, 0xa04c, 0x2b65, 0xba2d, 0xbad6, 0x418c, 0x3bc0, 0xb26b, 0x1ebd, 0x43e5, 0xbfd4, 0x43ae, 0xb271, 0x4226, 0xb03c, 0x409a, 0xb21f, 0x42e8, 0xbf1b, 0x449b, 0x4310, 0xb2a4, 0x245b, 0x4322, 0xa98a, 0x41eb, 0xb284, 0xb2ac, 0x410f, 0xb2cc, 0x439f, 0x437f, 0x4120, 0xba47, 0x190f, 0x4313, 0x424d, 0x36a7, 0x432a, 0x45e3, 0xb263, 0xbf13, 0xb0c7, 0x423a, 0x437b, 0x41a1, 0x42b1, 0xba59, 0x43e4, 0x40a7, 0x4679, 0x438a, 0xad17, 0x43b4, 0x1420, 0xb229, 0x42b8, 0xb2cf, 0x1a2e, 0x3665, 0x08c0, 0xbf9d, 0x32d9, 0x0937, 0xb0be, 0x43c4, 0x4220, 0x1d47, 0x40f6, 0x4373, 0x4257, 0xafdc, 0x23cf, 0x41f4, 0x3b0e, 0x40ce, 0xb230, 0xb255, 0x4664, 0x1dcb, 0xb291, 0x42fa, 0xbf91, 0x1d7c, 0xb020, 0xba72, 0x4007, 0x3978, 0x4054, 0xbaed, 0x1bf6, 0x31ff, 0x46cc, 0xa2f6, 0x3bd0, 0xba7e, 0x193e, 0xb287, 0xb031, 0xbae0, 0x40d3, 0xb0d4, 0xb0ae, 0x02d5, 0x434e, 0x403d, 0xafe6, 0x3faa, 0x411c, 0xbfbd, 0x0cd3, 0x119f, 0x4394, 0xbacc, 0xb2e1, 0x4180, 0x4351, 0xbaeb, 0x43a4, 0x43f9, 0x4138, 0x2b8b, 0x2caf, 0x413e, 0x41a8, 0xbf48, 0x42c4, 0x012d, 0x4172, 0x422d, 0xb08b, 0x4295, 0x19b4, 0x1fd9, 0x40d1, 0x45e2, 0x4228, 0x424b, 0xbad8, 0x40ee, 0x40e9, 0x439d, 0xbaef, 0xbadf, 0xbf2a, 0x40c8, 0x192c, 0x4408, 0x401c, 0x2766, 0x30bd, 0x40f5, 0x4387, 0x3c4a, 0xbf0f, 0xba2e, 0xb242, 0x4624, 0x1a7b, 0xb27e, 0xba6c, 0x4169, 0x419f, 0x406b, 0x4427, 0x0415, 0x3065, 0x4015, 0xb2eb, 0x1cdd, 0x4222, 0xb225, 0xa0ed, 0x1826, 0xba16, 0x437d, 0x0dae, 0xb0fe, 0xbfb6, 0xb256, 0x1666, 0x42df, 0x45b4, 0x0c1b, 0xba18, 0xb29b, 0x4586, 0xbf32, 0x1bfe, 0xbad5, 0x422c, 0xbf2d, 0x1cc6, 0x1e0d, 0xb0c9, 0x437c, 0x342f, 0x429d, 0x4103, 0xbf7c, 0x41a5, 0xb02f, 0x4035, 0x0a5d, 0xbac1, 0xac84, 0x4071, 0x0d2f, 0x4235, 0x4203, 0x44b8, 0x0677, 0xba51, 0xb0d1, 0xbae3, 0xae14, 0xbf49, 0xb29a, 0x41ff, 0x1298, 0x1e6a, 0xb286, 0x4156, 0x3c1e, 0x4088, 0x075d, 0x414c, 0x4343, 0x4197, 0xa990, 0xb085, 0xacb8, 0x1984, 0xbfe0, 0xa3f4, 0x423f, 0xb2e3, 0x41d6, 0x1d5d, 0xbf65, 0x407c, 0x4269, 0x348b, 0xb2af, 0x43e5, 0x4169, 0x42e6, 0x4258, 0x434b, 0xbfc0, 0x4385, 0x3551, 0x4452, 0x41fd, 0x461c, 0xb2c5, 0x1e89, 0xba0b, 0xbf00, 0xacdf, 0xb2c0, 0x4241, 0x4247, 0x421b, 0x436f, 0x43d2, 0x14e5, 0x45a5, 0xbf1b, 0x1e6b, 0x4195, 0x45f2, 0xb27b, 0x4125, 0xb29f, 0xb26e, 0x4010, 0x422a, 0xa1a0, 0xbafc, 0xaa80, 0x1c8f, 0xb06f, 0x43a8, 0x42ac, 0xbae1, 0x415f, 0x117d, 0x4339, 0xb250, 0xbfe2, 0x4263, 0xb2bd, 0x409c, 0xb269, 0x19a4, 0xbfc0, 0x0fdf, 0x41f6, 0x4621, 0x4044, 0x40f5, 0x2e6e, 0x4221, 0x46d3, 0xbf9a, 0xba60, 0x41bf, 0x424a, 0x43bf, 0xbf9c, 0x26c7, 0x4242, 0x1d65, 0xb050, 0x4612, 0xa5c5, 0x1da4, 0x43a3, 0xb0fb, 0x444d, 0xba09, 0x3649, 0x4062, 0x1293, 0xba5b, 0x2db9, 0x43dc, 0x4333, 0x1a29, 0xbf0d, 0x0c2d, 0x4306, 0x4001, 0xbf70, 0xb04a, 0x43b5, 0x32dc, 0xba2b, 0x4367, 0x412e, 0x191f, 0x1a9a, 0x4027, 0x4139, 0x1951, 0xbf00, 0x4239, 0x40e2, 0x42c2, 0x40f2, 0x0880, 0x43f8, 0x1da1, 0xbf99, 0xb245, 0x464e, 0xbaf4, 0x4220, 0x4653, 0x43b9, 0x3e09, 0xbfc5, 0xba26, 0xb287, 0x1747, 0xb231, 0xb2ff, 0x21a4, 0xba1e, 0x44b4, 0x44a3, 0x4281, 0x4113, 0x40ab, 0x02cd, 0xb0f6, 0x40c1, 0xba59, 0x42a6, 0xb2e1, 0x1ed0, 0x3728, 0xba7f, 0x425d, 0xbf82, 0x2b31, 0xb03e, 0x4133, 0x1ce6, 0x413d, 0x4027, 0xb2c5, 0x1e77, 0x424d, 0x4051, 0xa275, 0xb00b, 0x4090, 0x408c, 0xbf79, 0x0e9e, 0x0767, 0x196e, 0x424a, 0x46fa, 0x2756, 0x24c8, 0x2108, 0x09cd, 0x1b81, 0x421a, 0xb256, 0xb007, 0x0ad0, 0x40cd, 0x4114, 0x404a, 0x400e, 0x40d8, 0xbf70, 0x4476, 0x01e4, 0x40e0, 0x2e5d, 0xbf9a, 0x2d03, 0x45c2, 0xbf00, 0xb0c5, 0x4585, 0x40e6, 0x42dd, 0xbac3, 0x41c0, 0x1a33, 0x1f44, 0x4347, 0x41ec, 0x20fe, 0xbf69, 0x3e36, 0x41e0, 0xadd6, 0x1aee, 0x2879, 0xb258, 0x432a, 0x2c20, 0x1ba8, 0x0ba8, 0x4361, 0x42bc, 0x408c, 0xbf9a, 0x4127, 0x42e9, 0xba00, 0x4127, 0x0bd8, 0x421a, 0x1d0b, 0xb2c6, 0xb0b8, 0xbf65, 0x2eb1, 0x43ee, 0x43e4, 0x4103, 0xb041, 0x40e0, 0xa3f4, 0x421a, 0xb284, 0xba0c, 0xae98, 0x42b7, 0x2272, 0x1c87, 0x3cf3, 0x41c5, 0x423b, 0xb202, 0x1f4a, 0x43a6, 0x124a, 0xb20e, 0xb0b4, 0x40e4, 0x40e9, 0x4135, 0xa232, 0x18a6, 0xbf48, 0x461c, 0xba4a, 0xba16, 0xa76b, 0x4106, 0x4289, 0x43fe, 0xba65, 0x4127, 0x0a75, 0xaddb, 0x2441, 0x1904, 0x1705, 0xb2c6, 0x1503, 0xbff0, 0x43c1, 0xbf52, 0xbac9, 0xb241, 0xbf80, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x2fdaaeb9, 0xb79696f8, 0x5b75e8b3, 0xb8bc6fba, 0xcb255eac, 0xe7fd35fd, 0xddb0f0b1, 0x6a6b7524, 0x61dac898, 0x5adfbd3a, 0xe9b18555, 0xf3de49cb, 0x123c8a0c, 0x745a0959, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0x00001400, 0x00000000, 0x00000000, 0x00000000, 0x00001441, 0x00000000, 0x00000000, 0x00000000, 0x61dac89a, 0x5adfbff0, 0x0000171e, 0x00000900, 0x5adfbff0, 0x745a0295, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4177, 0x1819, 0x2414, 0x1b72, 0x43dc, 0xbf80, 0xba41, 0xa052, 0xb20b, 0xb083, 0xa090, 0x4329, 0xa61b, 0x43d1, 0x46cc, 0x1a14, 0xbf68, 0x46fa, 0xb233, 0x1a2a, 0xb2b8, 0xa819, 0xb037, 0xba10, 0x4042, 0xb298, 0x41fb, 0x409a, 0xa96a, 0xbfbd, 0x42cf, 0x4253, 0x408a, 0x405b, 0x42f3, 0x4059, 0xba0f, 0x4567, 0x1cdc, 0x46fc, 0x408c, 0x4422, 0x35df, 0x1b9c, 0x2518, 0x40fd, 0xbfc0, 0x427e, 0x088c, 0x12b3, 0x420e, 0x1f21, 0xb2db, 0x4082, 0xba04, 0x43d5, 0x417d, 0x40cc, 0xbfe1, 0xb2e4, 0x41cb, 0xb26a, 0x0fc6, 0x41fd, 0xa87b, 0x19ba, 0xb082, 0x4007, 0x403f, 0xb051, 0xb2ae, 0xbfb4, 0x41ae, 0x30da, 0x3846, 0x428b, 0x42d0, 0x4346, 0x42c7, 0x432c, 0x4130, 0x1e5c, 0x456b, 0x40d8, 0x4220, 0x40aa, 0x4158, 0x431d, 0x44bb, 0x1bcc, 0x04bf, 0x1cfa, 0x4025, 0x119d, 0xbfbb, 0x44ed, 0xa46d, 0x405d, 0x444c, 0x1c30, 0x4203, 0x45c5, 0x41d8, 0xb032, 0xbaf2, 0x0ea6, 0x45da, 0x4201, 0xb08f, 0xba40, 0x46fd, 0xb036, 0x4299, 0xb204, 0x41b6, 0x2913, 0x3bba, 0xba0b, 0x0540, 0x417a, 0xbafd, 0xbf73, 0x4048, 0xb25b, 0x1f4c, 0x429e, 0x3765, 0x1070, 0x1cd1, 0xa19b, 0x462d, 0xae77, 0x4138, 0x4417, 0xb26e, 0xade1, 0x1fc2, 0xb066, 0x43e2, 0x45e4, 0xba67, 0xb2dc, 0x1ce1, 0x4618, 0x41f8, 0x41cc, 0xbfd9, 0x21de, 0xba18, 0xbad6, 0x1ced, 0x4268, 0x408e, 0xbf42, 0xb203, 0xb22f, 0x41a0, 0xb01e, 0x148e, 0xb213, 0x1f18, 0xb066, 0x4220, 0x402f, 0x2024, 0xb0d0, 0xb029, 0x4013, 0xb2e6, 0xaad2, 0x4412, 0x2186, 0x08b7, 0xb017, 0x2c68, 0xac81, 0x03a5, 0x410d, 0x1ad9, 0x4385, 0xbf8f, 0x423e, 0x43a9, 0x4540, 0xb206, 0x1913, 0x45f4, 0x410a, 0xba27, 0xb03a, 0xa1a3, 0x1bab, 0x40a9, 0x1e67, 0x1d41, 0x45a1, 0x43a2, 0x433c, 0xbf3e, 0x447b, 0x40fa, 0x42f1, 0xbfba, 0x3ad7, 0x1a64, 0x436a, 0x4236, 0xb244, 0x423e, 0x46ca, 0x20e3, 0xb2e8, 0x433e, 0xb2e5, 0x2bee, 0x42bb, 0xa98d, 0xad9c, 0x4386, 0x2257, 0xbff0, 0x42c6, 0xbf33, 0x4493, 0xb283, 0x42fe, 0x2141, 0x4036, 0x45ee, 0x4021, 0x4094, 0x1804, 0x415d, 0xbf83, 0x1901, 0xba69, 0x40b4, 0x2756, 0xa4e3, 0x46f5, 0x44e4, 0xbf08, 0x409c, 0xb28d, 0x432e, 0x40b3, 0x2d20, 0x3e1f, 0xbfb6, 0xada3, 0xba6b, 0x1a51, 0xb230, 0x417d, 0xb08f, 0x4147, 0xb204, 0x1c74, 0xbfc0, 0x40d6, 0x297f, 0x42da, 0x4647, 0x0785, 0x41ed, 0x43e8, 0xbfc3, 0x1814, 0x40f2, 0x44d1, 0x4336, 0x40cd, 0x4310, 0x0796, 0x0448, 0x0afd, 0x2b10, 0x454c, 0x409c, 0x4071, 0x4190, 0x022b, 0x42aa, 0x4077, 0x3044, 0x43c2, 0xb201, 0xbf8d, 0xba41, 0x4057, 0xb057, 0x1b40, 0xb24a, 0xa1a7, 0x4307, 0x0539, 0x416a, 0xba50, 0xbf87, 0xba7b, 0x43a4, 0x2500, 0xba5b, 0x43bb, 0x42f3, 0x4611, 0x4280, 0xbac7, 0x41ae, 0x1ca3, 0x43b2, 0xa44d, 0xba30, 0x0ab4, 0x1dcd, 0x461a, 0x435a, 0x025a, 0x432a, 0x20e9, 0xa954, 0x4392, 0xba42, 0x424c, 0x41b8, 0xb235, 0xbf51, 0x4185, 0xb006, 0x4049, 0xb26a, 0x417b, 0xb0e2, 0x19d5, 0x3021, 0xbf00, 0x19dd, 0x4005, 0x4153, 0x1c3e, 0x411b, 0xb272, 0x4222, 0xb249, 0x45e0, 0xb258, 0xb0fc, 0x193e, 0x42eb, 0xb2b2, 0x3edb, 0x4186, 0xb0cb, 0x302a, 0xbf36, 0xb289, 0x4139, 0x4193, 0xb08c, 0x42b8, 0xb254, 0x4421, 0x411c, 0x1ef1, 0xb041, 0x46e3, 0xbfb8, 0x43c0, 0x4015, 0xa898, 0xb20e, 0x42ce, 0x42ac, 0x4067, 0x43d6, 0x42a2, 0xba33, 0xbafe, 0x4208, 0x4142, 0x4159, 0x328e, 0x31d0, 0x40f7, 0x318d, 0x191f, 0x4393, 0x21e0, 0xbf15, 0x4350, 0x19a5, 0x44b5, 0x1a52, 0x18d9, 0x4298, 0xb2f1, 0xb283, 0x1d3c, 0x184a, 0x1919, 0x4185, 0x389d, 0x1fcf, 0x4075, 0x4354, 0x426d, 0x412b, 0x08c8, 0xaa20, 0xb0fc, 0x430a, 0x4371, 0xb036, 0xba55, 0x401d, 0x0a8a, 0x4216, 0xbf76, 0x418a, 0x438a, 0x4239, 0x41f2, 0x43bf, 0x1183, 0x163f, 0x1d04, 0x44d4, 0x4142, 0x40ec, 0x2292, 0x4216, 0x114a, 0x402c, 0x4182, 0x4274, 0x4175, 0xbf34, 0x0866, 0xba17, 0x425d, 0x408c, 0x455f, 0x4226, 0x352f, 0xbfd0, 0x41aa, 0xba23, 0xb22e, 0x4332, 0xb2dc, 0xb0f0, 0x41c1, 0x4244, 0x1935, 0xba0a, 0x434a, 0x4102, 0xbf3b, 0x4006, 0x40df, 0xb0f8, 0x4056, 0x400f, 0x43f9, 0x43ce, 0x42fe, 0x40d4, 0x28b0, 0x429b, 0xbf74, 0x182a, 0x41cb, 0xb286, 0x43e0, 0xbaf2, 0x4048, 0x4290, 0xb01d, 0x42a0, 0x4607, 0xb05c, 0x4350, 0x1c44, 0x332b, 0x4472, 0x41e1, 0x4367, 0x4326, 0x246e, 0x424d, 0xacbc, 0xb25d, 0x42fc, 0x1cfd, 0xbfba, 0x1862, 0x4434, 0x4642, 0x1a03, 0xac03, 0xb011, 0xb268, 0x0d38, 0x4269, 0x447e, 0x40d4, 0xb02c, 0x08bf, 0xb2b2, 0xbfa0, 0x410b, 0xb284, 0x1c4a, 0x414b, 0xbaf7, 0x4177, 0x3a36, 0xbf4a, 0x1dd8, 0x41c6, 0x4094, 0x4586, 0xbfb9, 0xb0e7, 0x43c0, 0x45dc, 0x0f5d, 0x164f, 0x0ba3, 0x1ddc, 0x0097, 0xb29b, 0xba38, 0xba74, 0x4381, 0xb0da, 0x4250, 0xb2a1, 0x41d4, 0x4081, 0x43bb, 0x4406, 0x345b, 0x3396, 0x1de6, 0x1962, 0x1b0e, 0xbf09, 0xb297, 0x44a0, 0x2a36, 0x3650, 0x427f, 0xb26b, 0xb271, 0x438f, 0x4339, 0x4129, 0x376e, 0x4296, 0x1ea5, 0xbfd8, 0x4225, 0x41e9, 0xbadc, 0x4033, 0x06d5, 0xb204, 0x4004, 0x43db, 0xb056, 0x1a4d, 0x168e, 0xbf13, 0xb2c3, 0x40b7, 0x4099, 0x0e40, 0x04b2, 0x2491, 0xbfb0, 0xba29, 0x40ab, 0x45c5, 0xb235, 0x4682, 0x44cd, 0x4336, 0xa9f5, 0x3173, 0xb2ba, 0xb2ae, 0x410b, 0x2490, 0xba68, 0xb05a, 0x4243, 0x4046, 0xb29e, 0xb26d, 0x412d, 0xbfc5, 0x3c36, 0x43ad, 0x4233, 0xb276, 0x1bbe, 0x4130, 0xad7d, 0x43e8, 0xbf86, 0x42b4, 0xb201, 0x39a2, 0xa298, 0x422d, 0x420e, 0xbae0, 0x42ba, 0x419b, 0x4373, 0x3c8a, 0x1b84, 0x2f19, 0x4215, 0xbae3, 0x4301, 0xbfd4, 0xb271, 0x4071, 0x184c, 0xbaf9, 0x1a13, 0x46d8, 0xbfc7, 0xb230, 0x46e9, 0x3397, 0x44d2, 0x2603, 0xba1b, 0x217b, 0xb0fd, 0x42c2, 0x0680, 0x1e31, 0xb203, 0xbac1, 0x2ed6, 0x41cc, 0xb232, 0x423d, 0x24a3, 0xbf43, 0x3980, 0x4273, 0x4234, 0xa0f4, 0xb270, 0x401e, 0xad39, 0xb045, 0x1bbb, 0x1f36, 0xb0ac, 0xba3f, 0xb227, 0x4433, 0xb001, 0xb060, 0x1679, 0x287b, 0x4134, 0xb2b5, 0xabaa, 0x1a26, 0x44a2, 0x42fe, 0xb293, 0xbf8d, 0x4198, 0x1f37, 0xb2c9, 0x425c, 0xb2e3, 0xaaa2, 0xbf26, 0x4306, 0xb013, 0x1e28, 0x1668, 0x411a, 0x4370, 0x0257, 0x1efc, 0x4060, 0x4243, 0x4419, 0xb211, 0x1ade, 0x413e, 0xbfd6, 0x4329, 0x4305, 0x423c, 0x1a58, 0x4075, 0x4377, 0xb24c, 0x4055, 0x1b29, 0xb212, 0xba6d, 0x4011, 0x265d, 0xaa17, 0xa9c1, 0xb2ed, 0x4011, 0x31b0, 0x421d, 0x4399, 0xb0da, 0x4040, 0x4160, 0xbfd0, 0xbad9, 0xbfb4, 0x45f3, 0x4329, 0x434a, 0x40fe, 0x432e, 0xb290, 0x4043, 0x4077, 0xba4d, 0x238f, 0x40ea, 0x46da, 0x3def, 0xbf0e, 0x4565, 0x180b, 0x25be, 0xba29, 0xb0cf, 0x421a, 0xba15, 0x0c57, 0x1ca8, 0x4202, 0x4369, 0x3400, 0xb230, 0xb010, 0x1e9e, 0x4185, 0xaa1b, 0x4069, 0x41f7, 0xba0a, 0x1bbf, 0x401d, 0x1bff, 0xbf2a, 0xb24b, 0xb06f, 0x24ba, 0xa781, 0x42f6, 0xb26b, 0x2a8a, 0xba6c, 0x2c03, 0xb2e8, 0xb24a, 0x42aa, 0x45e3, 0x40a0, 0x40e2, 0x4044, 0x406e, 0x4011, 0xb2b0, 0xbfa7, 0x4155, 0xb20c, 0x421a, 0xb011, 0x06e8, 0x1c54, 0xb20d, 0xafeb, 0xbf90, 0xbfa1, 0x1d96, 0xb2c9, 0xbae8, 0x4123, 0xb29f, 0xac33, 0xbfc0, 0xb2d6, 0x119e, 0x41fa, 0xba79, 0x436a, 0x41f6, 0xbadc, 0x406d, 0x08c6, 0xbafc, 0x4247, 0x419d, 0x40e4, 0x4032, 0xbf9d, 0xb2b5, 0x4301, 0x1fd1, 0x1878, 0x190b, 0x41b6, 0x1a06, 0x1884, 0x413e, 0x1e69, 0xb26e, 0x400f, 0xba25, 0xacaa, 0xaa90, 0xb2f7, 0x41be, 0x414d, 0x2fe7, 0xb01b, 0xbf6b, 0xbf80, 0x4424, 0x1ac3, 0x2b49, 0x42d4, 0xba6d, 0x00b4, 0xaccd, 0x2bba, 0x42b0, 0x4317, 0x408d, 0x4088, 0x1c63, 0x41f2, 0x340f, 0x41be, 0xbfbc, 0xba04, 0xb2a4, 0xb03e, 0x41f3, 0x43d5, 0x3fb9, 0x4130, 0x40b7, 0x1a94, 0xba2c, 0xb24d, 0x40ac, 0x4397, 0x1e2d, 0x0f84, 0x1ac9, 0x4094, 0xba79, 0x4014, 0x345f, 0x4369, 0xa53c, 0xb236, 0x417e, 0xba2d, 0x0ddf, 0x41eb, 0xbfd8, 0xb236, 0x4430, 0x1c1f, 0x408b, 0xb260, 0xab37, 0xb252, 0x40e5, 0x41fe, 0xb289, 0x4157, 0x416e, 0x1cc9, 0xbfb0, 0x40ba, 0x4358, 0x1f8c, 0x40fb, 0xb009, 0xb296, 0x3130, 0x4042, 0x43ca, 0x433c, 0xbf12, 0x4349, 0xbfa0, 0x4092, 0xb0f5, 0xb2a1, 0x4112, 0x2f71, 0xba43, 0x4480, 0x4075, 0x4066, 0x09fb, 0x404b, 0xb263, 0xbfa9, 0xb224, 0x41dd, 0xb03f, 0x19ba, 0x45e5, 0x1a02, 0x40ef, 0x3fa2, 0x1ee5, 0x0f21, 0x4175, 0x4009, 0x4364, 0x0066, 0x4243, 0x1b6e, 0x3b34, 0x40fd, 0x058f, 0x4021, 0xa4f2, 0xb2c8, 0xbf3a, 0xbfe0, 0x402b, 0x4633, 0x2f79, 0xb027, 0xb066, 0x4046, 0x186b, 0x4288, 0x41ef, 0x28df, 0x3cb6, 0x0efe, 0xba01, 0x42a7, 0x3d58, 0xbae9, 0x41eb, 0x1c69, 0xbae0, 0x434d, 0x44f4, 0x41fc, 0x32c2, 0xb028, 0xbf71, 0x40a0, 0x2fd0, 0x1eea, 0xb02c, 0x4393, 0x3037, 0x2793, 0x405e, 0x25a9, 0x2f3c, 0x3e1b, 0xb2fe, 0x41ba, 0xbfd7, 0xba39, 0xb249, 0x4150, 0x43c2, 0x43e1, 0xba14, 0x412c, 0xb003, 0x0b24, 0x4243, 0x306c, 0x436a, 0x1a6a, 0x1cff, 0x1599, 0x43b2, 0x4306, 0x4264, 0x424f, 0x22b4, 0xb2bb, 0x418e, 0x4021, 0xbf0f, 0xb243, 0x1971, 0x43d7, 0x435d, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xa631da32, 0x8b25e0fa, 0xc036f7d7, 0x14e1473e, 0x0d42c45b, 0x1272500d, 0xe6b2d752, 0xafd08c78, 0xdf628cd7, 0xcc7f4ab5, 0xc596f8ca, 0x035ba176, 0x9004931a, 0xcc587fb1, 0x00000000, 0x300001f0 }, + FinalRegs = new uint[] { 0x000000a3, 0x00000000, 0x000000b4, 0xffffffa3, 0x00000000, 0x000000a9, 0x000000b3, 0x00000001, 0xe32471b4, 0xcc7f08d4, 0x000020a4, 0x000020a4, 0xcc7f6b59, 0xcc7f0a40, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x1eba, 0xbf6c, 0x40f9, 0x42e2, 0xbf00, 0x4352, 0xba71, 0x415f, 0x4121, 0x4370, 0x18bf, 0xb228, 0xb21f, 0x437a, 0x410e, 0xa9be, 0x4094, 0xbf01, 0x1bfc, 0x417c, 0x1d9a, 0x342e, 0xb058, 0xa3e9, 0x15d8, 0x42da, 0xbfd6, 0x414f, 0x43b2, 0x46dd, 0x41c9, 0x46d0, 0x46f2, 0xba5f, 0x1558, 0x4221, 0x4156, 0x42f2, 0x38cc, 0x1fa0, 0x436a, 0xb062, 0x4070, 0x19c3, 0xb205, 0x4206, 0xba6a, 0x42be, 0x4388, 0x42ae, 0xb29c, 0xbf1b, 0xbae8, 0x00da, 0xa33e, 0x1ca7, 0x40fd, 0xbf81, 0xb2d7, 0xba69, 0x1ea1, 0xb0bb, 0x0645, 0x1cb6, 0xb27a, 0xbfb0, 0x28b8, 0xbad7, 0x40e0, 0x4253, 0xbaf6, 0x1466, 0x318a, 0xa4ad, 0x1f98, 0xbf4a, 0xbaf1, 0x4350, 0x08cd, 0xbfb6, 0xb047, 0x0b95, 0xb250, 0x405b, 0x4125, 0x42f5, 0x1da1, 0xa969, 0xbfc4, 0x400d, 0xb2b6, 0x4321, 0x43ec, 0xb279, 0x41e6, 0x066a, 0x1e01, 0xbf6c, 0xb260, 0xb099, 0x4237, 0x4192, 0x4453, 0xb258, 0x40c4, 0x1f86, 0x1f54, 0x1da2, 0xb0cf, 0x460b, 0x40e3, 0xbfc0, 0xb27b, 0xba0a, 0x424d, 0xb099, 0x1e52, 0xb213, 0x4073, 0xb091, 0x4456, 0x4549, 0xbfc0, 0xbfb5, 0x435a, 0x1eb5, 0xb2fc, 0xb2b8, 0x1ccd, 0x0313, 0xbacd, 0x1835, 0x4048, 0xba3e, 0x423e, 0xb0ef, 0x33e7, 0x406b, 0xb294, 0x4176, 0xaaa3, 0x43e9, 0xba3e, 0x4592, 0xb2b8, 0x41ee, 0x4166, 0x40d4, 0x407a, 0x41b4, 0xb08c, 0x466c, 0x466b, 0xbf84, 0xb256, 0x05dd, 0x402d, 0x400d, 0xae81, 0x448a, 0x409e, 0xb2bb, 0x4330, 0xb2a2, 0x3a1d, 0x155f, 0x4375, 0x42a3, 0x4042, 0x1e6c, 0x4043, 0x437f, 0xbf5d, 0x0a83, 0x0278, 0xbaef, 0xb225, 0xb2c6, 0xa084, 0x42c4, 0xb044, 0xb276, 0x4254, 0x1ff5, 0x3ecd, 0x42d5, 0x40ab, 0x1d0b, 0xbf5b, 0xa84c, 0x42fa, 0x4153, 0xb20f, 0x43f5, 0xb0ae, 0x40a6, 0xba43, 0x40d1, 0x4031, 0xb201, 0x40a2, 0xa821, 0x41ba, 0x057c, 0x4158, 0xbfdc, 0x4058, 0x0e46, 0x41b1, 0x1ede, 0x430b, 0x427a, 0x2579, 0xb210, 0x10b9, 0x40ac, 0x144d, 0x277e, 0x4361, 0x407b, 0x4292, 0x438e, 0x1c52, 0xbf29, 0x43da, 0x4175, 0x40cd, 0x422e, 0x1bce, 0xbafe, 0x4048, 0x4392, 0x1f58, 0xb06f, 0x4172, 0x140a, 0x42c1, 0x4129, 0x3dc7, 0x43c9, 0xbf62, 0x1cfc, 0x1c76, 0xb258, 0xb2a7, 0x0c27, 0x428d, 0x419d, 0x4247, 0xbfa0, 0x4003, 0x462a, 0xb20e, 0xba7f, 0x410b, 0xbf49, 0xb063, 0x406f, 0x1d81, 0x0426, 0x1ecc, 0x37da, 0xba5c, 0x265f, 0xba45, 0x4623, 0xb2ce, 0x437a, 0x3263, 0x4216, 0x46d2, 0xbf60, 0xb2b3, 0x438b, 0x43a6, 0x127c, 0x2a4a, 0xb087, 0x418d, 0xbfdc, 0xbaf9, 0x2d49, 0xbad8, 0x4555, 0x4034, 0xbf3c, 0x4163, 0x4195, 0x1af2, 0x436b, 0xba0c, 0x4645, 0xb006, 0xba20, 0x2774, 0x0b44, 0x4191, 0x40c4, 0x3255, 0x4316, 0xad9e, 0xb281, 0x2148, 0x19bf, 0x2cb2, 0x41da, 0x4245, 0x4304, 0xbf1c, 0x4266, 0x40d7, 0x1d42, 0x420e, 0x0277, 0x4072, 0x1f21, 0x4117, 0xbfcb, 0x4020, 0xbaf0, 0x3750, 0x438c, 0xb249, 0xb232, 0x42bf, 0x087b, 0xbf60, 0x20a3, 0x2159, 0x4376, 0x401f, 0xac09, 0x41b6, 0xb2eb, 0x44e0, 0x44f5, 0x4180, 0xbf70, 0x4027, 0x4376, 0x4249, 0xbf76, 0x4013, 0xa685, 0x4178, 0x1b24, 0x3bf1, 0x43fc, 0x4564, 0x4359, 0xbf60, 0x3001, 0xbf7a, 0x42b3, 0x431f, 0x0fb3, 0xba60, 0xab2c, 0x4084, 0x4382, 0x1b93, 0x445d, 0x0fa1, 0x42c8, 0xaf44, 0x326c, 0x2fce, 0x41fa, 0x46ed, 0x435a, 0x43e7, 0x3b92, 0x41ad, 0x4387, 0x4148, 0xb243, 0xba0f, 0x468d, 0x41d3, 0x4283, 0x3a35, 0xbf74, 0xb280, 0xb26e, 0x4288, 0x1b27, 0x3713, 0x460d, 0x4608, 0xa4ad, 0x40af, 0x2de0, 0x111b, 0xbafa, 0x421f, 0x432d, 0xba3b, 0x4353, 0x418e, 0xbfa8, 0x41f0, 0xba53, 0xb0e1, 0xb0b1, 0x13bc, 0xba4a, 0x42d6, 0xb26d, 0x468c, 0x4064, 0xb048, 0xa308, 0x4171, 0xbf7a, 0xa025, 0x4079, 0x287b, 0xb2d9, 0xafc6, 0x1287, 0x3e24, 0xbfc0, 0x4383, 0x0175, 0xb229, 0xb098, 0xba56, 0x1cdf, 0xb216, 0x3b28, 0x4083, 0x41cd, 0x4233, 0xb0fb, 0xba6a, 0x40f7, 0xb092, 0x4151, 0x4332, 0xbf01, 0x431e, 0x4064, 0x3e5b, 0xbae3, 0x41a0, 0xbad8, 0x4396, 0x16ef, 0xb2ac, 0x43db, 0xba05, 0x42a4, 0x287a, 0xbfe4, 0x423c, 0x4093, 0x4363, 0x428c, 0x4245, 0xbf1d, 0xb288, 0x4170, 0x44fd, 0xb20d, 0x1ba5, 0x41df, 0x404e, 0x1bf4, 0x363f, 0x4235, 0x2879, 0x3929, 0x1c19, 0x462f, 0x40ea, 0xb291, 0x1ba1, 0x40fa, 0x1144, 0xb291, 0xbf16, 0x4355, 0x410a, 0x4359, 0x2f7f, 0xbf4a, 0x4315, 0x18ae, 0xb28b, 0x013c, 0x2be1, 0x423a, 0x426b, 0x2686, 0x0bda, 0xb070, 0x1a04, 0x433b, 0x4000, 0x4374, 0x122a, 0xab72, 0x240a, 0x43cd, 0x1fcc, 0xb24a, 0x3817, 0xb28a, 0x43a1, 0xbfbf, 0x41ad, 0x4353, 0x1ca2, 0x0f5f, 0xb2cc, 0x335c, 0xbfdc, 0x4564, 0x42f9, 0xb27b, 0x4334, 0x4164, 0x3681, 0x42f9, 0xbf4e, 0x4638, 0x4449, 0x43ed, 0x19bb, 0xb215, 0xbfe0, 0x423f, 0x2108, 0x4383, 0xb228, 0x4652, 0x1dc3, 0xba3f, 0xbfc4, 0x438b, 0x40e8, 0x4567, 0x418c, 0x40fc, 0xba28, 0x40b1, 0x1e4c, 0xbfba, 0x3458, 0xb0ab, 0x2ad2, 0x45e9, 0x1a0c, 0x43b5, 0x4198, 0xab8d, 0x41f9, 0x1d52, 0x4436, 0x2e46, 0x436d, 0xba14, 0x449b, 0x1c84, 0xb2bc, 0xba6a, 0xbf28, 0x382e, 0x41c1, 0x4630, 0x40fb, 0xb25a, 0xa5ff, 0xbf66, 0x407f, 0x1ba9, 0x40f7, 0xba4d, 0xad6a, 0x42d4, 0x416a, 0x41ec, 0x058b, 0xb03f, 0x400b, 0x41de, 0x42f8, 0xb239, 0x43a1, 0xb0bc, 0xb278, 0x3723, 0x2aba, 0x08ce, 0xbac4, 0x4117, 0xba71, 0x43e7, 0x4160, 0x40f9, 0xbfb5, 0xb204, 0x46aa, 0x2031, 0x427d, 0x434d, 0xbac1, 0x41c2, 0xba36, 0x3418, 0x4154, 0x0077, 0xbf8f, 0x1ce1, 0xb299, 0x403b, 0x4327, 0x417e, 0x2a98, 0x41f9, 0x44fb, 0x4284, 0x3e49, 0x4338, 0xb20a, 0x0cc0, 0xb24c, 0x4379, 0x0318, 0x4361, 0xaf1c, 0x4374, 0x4050, 0x18ef, 0xb060, 0x2d77, 0x1924, 0x41bd, 0xbfa8, 0xb0e8, 0x4190, 0x42dc, 0x415d, 0x40ea, 0xb23b, 0x400a, 0x22d5, 0xb25d, 0x1dd6, 0x1d96, 0x2ce9, 0xbf91, 0x4038, 0xb066, 0x43e7, 0x1822, 0xb269, 0x4593, 0xb2f5, 0x19bd, 0xa3f6, 0x0e7e, 0xb24b, 0xba7d, 0x42ca, 0x4663, 0xbfad, 0x4299, 0x1e65, 0x4225, 0x2788, 0x43e8, 0xb0d3, 0xba16, 0x1e5d, 0x404a, 0x1f58, 0x4365, 0xaa39, 0xb2bb, 0xb220, 0x4582, 0x43a7, 0xb256, 0xb209, 0xb2ab, 0xb0d2, 0x43e4, 0x42c8, 0x413f, 0xb243, 0xbf53, 0x2e4e, 0x466d, 0x4037, 0x41b3, 0x4440, 0xbf49, 0x42be, 0xb2cd, 0x4600, 0x32a9, 0x4148, 0x446c, 0x4220, 0xb0de, 0x0ce2, 0xb0b4, 0xabd7, 0x42ff, 0xb026, 0x419a, 0x40b3, 0x0b25, 0xb2cd, 0x3c35, 0x19ab, 0x1f78, 0xbfb0, 0x0e94, 0x1e9d, 0x258f, 0xa68b, 0xbf65, 0xb08c, 0x4419, 0x079e, 0xb268, 0x0e32, 0xba48, 0xb263, 0x404f, 0x0184, 0x4363, 0x40ac, 0x2bea, 0x4333, 0x4212, 0x1b18, 0x2632, 0x419e, 0xaddd, 0x26d7, 0xab35, 0x43ae, 0x44a1, 0x4553, 0xa3ae, 0xb035, 0x43eb, 0x431a, 0x3b78, 0xbf5f, 0x44f2, 0xa8a4, 0x2b62, 0x41b5, 0xb2a0, 0xb26b, 0xbf60, 0x408a, 0x42f5, 0xb248, 0x4186, 0x0be3, 0xba08, 0xba09, 0x43b1, 0x32eb, 0x435e, 0x1a5f, 0x46a1, 0xa636, 0x40a4, 0x1cc4, 0xb205, 0x4008, 0x1ab0, 0x43dd, 0x44ba, 0x1a0a, 0x41e8, 0xbf0b, 0x4107, 0x43fb, 0xb278, 0x0d1b, 0xb24f, 0x4072, 0xb0fe, 0x1f22, 0x41d6, 0xa4b7, 0xa2e7, 0xbace, 0x1986, 0x41ea, 0xbf8f, 0xb2bd, 0xa889, 0x2c1e, 0x4377, 0x43fe, 0x4208, 0x0d22, 0x1fb7, 0x13a5, 0x1dc7, 0xb2a7, 0x4322, 0xb0e3, 0x20ce, 0x2c78, 0xb02e, 0x1dd1, 0x4260, 0x409c, 0x4072, 0x41c0, 0x43b5, 0xb229, 0x42ae, 0xb2fb, 0x41c4, 0x0949, 0xbf46, 0x4240, 0x0cde, 0x1cd9, 0x3d63, 0xba31, 0x4182, 0x46c9, 0x207c, 0xbfd0, 0xba2e, 0xb03a, 0xa2cf, 0x430a, 0xbf00, 0xbfa3, 0x2012, 0x1aef, 0xbae5, 0x429b, 0x42a3, 0x40d8, 0xb27e, 0x43e3, 0x00d0, 0xbfd0, 0xb0f6, 0x235a, 0x4206, 0x28e5, 0x12df, 0x44c8, 0x29d4, 0x07dc, 0xb0ee, 0x2a46, 0x4684, 0x41c8, 0x4256, 0x419f, 0x1840, 0xbfb6, 0x2d6c, 0xbf00, 0x4111, 0xb2a4, 0x2ac4, 0x4357, 0x2969, 0xbfdb, 0x06e7, 0x1834, 0x4303, 0x1301, 0x4349, 0x4359, 0x4199, 0x42d7, 0x43df, 0x1c72, 0xbf29, 0x42e9, 0x0dc8, 0x444f, 0xb2b3, 0xbade, 0x4096, 0xbf39, 0x4222, 0x4132, 0xbaf8, 0x423a, 0x411a, 0x3d0e, 0x27cd, 0xa5b1, 0xba60, 0x402c, 0x1c06, 0x1e7f, 0x4125, 0x437c, 0x2d39, 0x41e8, 0x43b0, 0x1aec, 0x410f, 0xbf70, 0x4151, 0xbfd7, 0xb24b, 0x434a, 0x420a, 0xb0bb, 0x40b5, 0x2a48, 0x42f7, 0x4357, 0xa542, 0x4377, 0x456a, 0xba32, 0x256c, 0x4293, 0x420d, 0x41b2, 0x4116, 0x0db1, 0xb2fd, 0x2b59, 0xbf5e, 0x4386, 0x46a9, 0x44f0, 0x403d, 0x42a2, 0xbfad, 0x4332, 0xb278, 0x33f7, 0x4154, 0x42d1, 0x45a4, 0xbf2c, 0x43d3, 0x4328, 0x1b3b, 0x2fb0, 0xb2fc, 0xba1f, 0x4081, 0xbac6, 0x43a8, 0xaf84, 0x41f1, 0x4242, 0x417b, 0xb210, 0xbf2e, 0x40d4, 0x43d6, 0x1ccf, 0x187b, 0x308a, 0x4377, 0x42ba, 0x31d7, 0xaffe, 0x1fcd, 0x44e8, 0xb28b, 0x1bb6, 0x441a, 0xa875, 0x40b0, 0x40b8, 0xa3ff, 0xbaea, 0xbf0f, 0xb025, 0xb00c, 0x400b, 0xb242, 0xba32, 0x46f3, 0x4637, 0xba20, 0xa645, 0x42df, 0x40a8, 0x42ed, 0xbf5c, 0x1a0c, 0x1899, 0xbaf9, 0xb259, 0x430a, 0xb2f9, 0x026c, 0x4245, 0xb2d4, 0x2e6f, 0x4339, 0xa74d, 0xafb6, 0x410a, 0xb20b, 0x431a, 0x41cc, 0xaa3b, 0x4138, 0xb0c4, 0x4481, 0xb22d, 0x4048, 0xbf81, 0x29ef, 0x44eb, 0x4173, 0xbff0, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x229975ae, 0x62ce2e1e, 0xd2564bd4, 0x39d45247, 0xd49d4418, 0x1318e716, 0x729bdcf6, 0x3efcaaa5, 0xa00a7b25, 0x4ae3af8e, 0x4525a887, 0x9586bb86, 0x0c982ef9, 0x7b92ae66, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x00000000, 0xfffff740, 0x00000000, 0x000000a8, 0x00000000, 0x000018d8, 0xfffff92c, 0x51bdcd40, 0x00000000, 0x00000005, 0x00000000, 0xfffffff8, 0xfffff544, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4140, 0xb2b3, 0x4323, 0xba15, 0x4344, 0xbff0, 0xbfbf, 0x43a6, 0x4559, 0x4223, 0x44dc, 0x4127, 0xb0a9, 0xbf8a, 0xaaf8, 0x46c3, 0x4581, 0x40c3, 0x34c1, 0x0454, 0x4391, 0x1926, 0x0506, 0x409b, 0xb240, 0xb2d2, 0xb2bc, 0x41a3, 0x401e, 0x467e, 0x461e, 0x418a, 0xbfa2, 0x4106, 0xb2d4, 0xba41, 0x41e2, 0xbf60, 0xa5bd, 0xb0f2, 0x0ae4, 0xaf92, 0x4178, 0x4288, 0xaafb, 0x2c49, 0x4297, 0x42aa, 0xb279, 0x417e, 0x4030, 0xb092, 0x43af, 0xb2dd, 0x40f2, 0x4248, 0x4185, 0x424d, 0x1bde, 0x42be, 0xbfa3, 0xb2fb, 0x4202, 0xb284, 0x1e62, 0x4435, 0x1a75, 0xad47, 0x1f63, 0xbfc9, 0x4248, 0x42a4, 0x4043, 0x406f, 0x4235, 0x2e18, 0xba30, 0x0eb5, 0xb010, 0x4284, 0x1c3d, 0xba47, 0x1ab5, 0x40c5, 0x42bb, 0x42b3, 0xb2bd, 0x40cb, 0xbf66, 0x42b1, 0xba69, 0xa9e3, 0xba74, 0xba6b, 0x362a, 0xbaf3, 0x41fa, 0x3157, 0x43e1, 0x197e, 0x2357, 0x2121, 0x4399, 0x40e8, 0xba4e, 0x46a4, 0xb24a, 0x1e4c, 0xbf14, 0x434f, 0xba7e, 0x0794, 0x41a9, 0x4271, 0x0540, 0xb245, 0x4034, 0xa808, 0xa761, 0x1e1b, 0x432a, 0xbfd0, 0x4056, 0x0f35, 0x42dc, 0x41e8, 0x28b7, 0x41e4, 0x42d3, 0x4003, 0xb266, 0x23b0, 0x40f3, 0xbfc2, 0xba05, 0x37dc, 0xa115, 0x2d82, 0x427d, 0x4267, 0xb231, 0x425b, 0x4333, 0x15c9, 0x4365, 0x33cc, 0x4245, 0xb258, 0x4456, 0x418d, 0xbfda, 0xa73d, 0x4151, 0xb06a, 0x113e, 0x40a9, 0x0bd6, 0x4011, 0x0e0d, 0x428b, 0xb085, 0x1887, 0x4306, 0x1e58, 0xba34, 0x0264, 0x1425, 0xbf75, 0xbf80, 0x063a, 0x4075, 0xb0ad, 0x4301, 0x41a9, 0xb2de, 0xb06d, 0xbf4e, 0x2c08, 0x195b, 0x1f78, 0x4665, 0x18a8, 0x427d, 0x1e10, 0x1902, 0x4184, 0xb26c, 0x4202, 0xabcc, 0xa09e, 0xb240, 0xbf97, 0xb2f9, 0x0e5d, 0x4016, 0x41e6, 0x0fa0, 0xb03e, 0xba09, 0xba2d, 0x1d8d, 0x4236, 0x4169, 0x42e1, 0xbfa2, 0x1c7a, 0x4209, 0x46d8, 0x40ba, 0x131e, 0x43e9, 0x4322, 0x2c4c, 0xb0f2, 0x420d, 0x43cf, 0xbfe0, 0xbaf7, 0x11fd, 0x3c3c, 0xb074, 0xa603, 0x40c0, 0xba24, 0xbfdf, 0xba1f, 0x4355, 0x1c3e, 0x3c1f, 0x1771, 0x435e, 0x43f0, 0xbfe0, 0xbf1e, 0x2909, 0x4388, 0xb283, 0xbfb0, 0x4345, 0xb037, 0x4422, 0xba30, 0xb299, 0xb27c, 0xba4e, 0x439c, 0x402a, 0xbfb7, 0x42b5, 0x4306, 0x12ed, 0x4036, 0xa7ea, 0x3301, 0x00dd, 0x43ff, 0xbf80, 0x43a5, 0xa662, 0xb2cc, 0x1fd1, 0xb2f3, 0xb2e5, 0xba3b, 0x41d9, 0x19bb, 0xbf53, 0xb2d3, 0x4060, 0x40a0, 0x41e4, 0x430e, 0x1ace, 0xb292, 0x4126, 0x14a7, 0x4213, 0xba11, 0xba68, 0xbaea, 0x40f4, 0x1efb, 0xad4e, 0xb219, 0x403a, 0xa6a1, 0xbac6, 0x400e, 0x4179, 0x27cf, 0xbf2c, 0xb00d, 0x41da, 0xba16, 0x43f4, 0x419b, 0xae62, 0x40e8, 0xba76, 0xa541, 0xba4f, 0x42c2, 0x41aa, 0x446e, 0x423c, 0x400e, 0xbf45, 0x411e, 0xba27, 0x4150, 0xbff0, 0xbfb0, 0x1d0a, 0x4110, 0x0264, 0xb08d, 0x4094, 0xb237, 0x433d, 0xb2df, 0x445a, 0x18bd, 0x232b, 0x42aa, 0x4049, 0x41e8, 0x4241, 0x43c0, 0xabef, 0xbf27, 0x466a, 0xb235, 0xb210, 0x40c1, 0x1b03, 0x34b9, 0x330b, 0x0d21, 0x1c91, 0x411e, 0x4309, 0x24b1, 0x4694, 0x4352, 0xb06b, 0x43fe, 0x4335, 0x4062, 0xb262, 0xb2cf, 0x41ba, 0x1f6a, 0xbf19, 0xaa47, 0x41d2, 0x3d1a, 0x4214, 0xb041, 0xba7d, 0x4256, 0x44cc, 0x443b, 0x0975, 0x40f5, 0xb218, 0x3e53, 0xb20d, 0x3524, 0xb266, 0x3cde, 0xb268, 0x4488, 0x4242, 0x0d85, 0x4359, 0xbfc0, 0xb2c5, 0xbf42, 0xb2ac, 0x30e2, 0x40f1, 0x0e8c, 0x4253, 0x41f5, 0x4338, 0x23ee, 0x4452, 0x435b, 0xbfe0, 0x4322, 0xba03, 0x4434, 0x425e, 0x4138, 0xb0f5, 0xb027, 0x1244, 0x4199, 0x069e, 0x1cd5, 0x43e7, 0xb251, 0x2463, 0x1b4f, 0x369f, 0xbf07, 0x3825, 0xb2a9, 0x425d, 0x01f8, 0x459d, 0x41f8, 0x1c1a, 0xbfb0, 0x4036, 0x400b, 0x4020, 0x42a5, 0x1c96, 0x4282, 0x1e80, 0x1a86, 0xa400, 0xbfd0, 0x437f, 0x42de, 0x4071, 0x43a6, 0x403d, 0xb2c8, 0x36f0, 0xbf7d, 0xa9ca, 0x4321, 0x4397, 0x3f2d, 0xb0ff, 0x1db9, 0x4327, 0x42ee, 0x4102, 0x1929, 0x1a60, 0x42ae, 0x43fa, 0x4238, 0x335e, 0x4195, 0x41a0, 0x434f, 0x07df, 0x0b88, 0x415c, 0x1bed, 0x1239, 0x2484, 0xbfe0, 0x429a, 0x43f1, 0xbf00, 0xbf81, 0x1d1a, 0x03ca, 0xb001, 0x1144, 0xba4c, 0x4588, 0x4298, 0x1adc, 0x0bf3, 0x425c, 0x40bc, 0x46d0, 0x4196, 0x3c97, 0x1a0e, 0x40d8, 0x4689, 0x4211, 0xb2c7, 0x3858, 0xbf94, 0x1f8b, 0x1dd1, 0x43fc, 0x4221, 0x4134, 0x4091, 0xbf45, 0xb216, 0xa369, 0x425f, 0x41f1, 0x1d77, 0x4351, 0x4052, 0x43b8, 0xaab0, 0x41bd, 0x436a, 0x10b8, 0x1be7, 0xb0a0, 0x4408, 0xba5c, 0x46ed, 0x4280, 0x43e5, 0xb24f, 0x413e, 0x40de, 0x4072, 0x0af6, 0xb0fc, 0x182b, 0xbf18, 0x40a2, 0x4274, 0xbf7b, 0x40e1, 0x4374, 0x1980, 0x446c, 0x3816, 0xb011, 0x1a4f, 0x432b, 0x3edd, 0x443b, 0x4100, 0x4089, 0x1aab, 0xadf1, 0xb27b, 0x41b2, 0x4654, 0x14fc, 0x42be, 0x0988, 0xbade, 0x01d9, 0xbfb2, 0x4495, 0xa451, 0xb236, 0x40a0, 0xa32b, 0x160f, 0x158c, 0x1b5a, 0x1bf9, 0x415f, 0xa9d3, 0x2d3b, 0x0186, 0x2ee4, 0xb2ee, 0x4203, 0x4282, 0xbf25, 0x42fb, 0x41cd, 0x4005, 0x412e, 0x4166, 0xb060, 0x419a, 0xb096, 0x421a, 0xbf9e, 0xbaea, 0xb24b, 0xb266, 0x04cc, 0x4346, 0x1cf3, 0x3752, 0x4191, 0x40df, 0x2bc9, 0xba3f, 0x418a, 0xa35c, 0x40bf, 0xb202, 0xbf75, 0xb022, 0xb2af, 0x4494, 0xb2b3, 0x42ff, 0x4234, 0xba3d, 0x4294, 0xafd7, 0xbf5e, 0x42dd, 0xb06e, 0xb289, 0x16e1, 0x4296, 0xba26, 0xb23d, 0x3187, 0x4257, 0x43b4, 0xb055, 0x43f0, 0xba3b, 0xba4f, 0x4264, 0x1d45, 0x4139, 0x2e94, 0x43c5, 0xbf3a, 0xa887, 0x1db3, 0xb2ed, 0x4155, 0x403a, 0x459c, 0xbf16, 0x4185, 0x435d, 0x4233, 0x4040, 0x195b, 0x3b21, 0x3ac0, 0x41d6, 0xb0d6, 0xbaf9, 0x32b7, 0xb25a, 0xa09f, 0x403a, 0x36e7, 0x01a5, 0x0cbb, 0x19d8, 0x432c, 0x4144, 0xb210, 0x40a9, 0x369e, 0xb208, 0xbfe0, 0xbf74, 0xba77, 0x43fd, 0x430e, 0x431d, 0x0f9e, 0x4093, 0x41ca, 0xac30, 0x1d21, 0x435a, 0xba6a, 0x4070, 0xa76e, 0x40a2, 0x3100, 0x433f, 0x1c9a, 0x447d, 0x0792, 0x28d8, 0xb2ae, 0x309b, 0xa509, 0xb2c1, 0x43d3, 0x1ab7, 0xbf39, 0xb261, 0xa83d, 0x0040, 0x43f9, 0x43aa, 0x0d1a, 0xb2f5, 0x1f4f, 0xa00a, 0x1b3a, 0xbae6, 0xbfba, 0x1dc5, 0x4244, 0x41c2, 0xb2ab, 0x4202, 0xb29c, 0xb257, 0xa703, 0x427e, 0xbf89, 0xb275, 0x4019, 0x46ca, 0x1d3d, 0x1a9b, 0x1f9c, 0x06a0, 0x2760, 0xb258, 0x42d9, 0xbfe0, 0xb284, 0x4693, 0xb028, 0xba10, 0x4070, 0xb0d6, 0x4117, 0x4144, 0x1cb9, 0xbafd, 0x414d, 0x428b, 0x404c, 0x40a9, 0xbfc1, 0xbad9, 0x1cd4, 0x4193, 0x2175, 0x4424, 0x4353, 0x41fb, 0x1085, 0xbfd9, 0x40d9, 0xb226, 0xba45, 0x4397, 0xba74, 0xb04a, 0x0585, 0x0166, 0x43fc, 0x0cb9, 0x4163, 0xa2c8, 0x4167, 0x4298, 0xb2f4, 0x4178, 0x25dd, 0xbf78, 0x4219, 0xba57, 0x2cec, 0x41eb, 0xb2b4, 0x42e3, 0x41d4, 0x4028, 0x19fc, 0x25f6, 0x42c6, 0x411a, 0xb2fc, 0x405f, 0x2db0, 0xba12, 0xba0d, 0x4086, 0x190a, 0xb0d4, 0xb0c7, 0x1a0e, 0x1865, 0x43d6, 0x40f3, 0xbf53, 0x4346, 0x2cce, 0x1b64, 0x4251, 0x4132, 0x3b90, 0x4106, 0x4068, 0x42c8, 0xba69, 0x435b, 0x409c, 0xb23c, 0x40d8, 0xba3a, 0x41f8, 0x0255, 0xba6c, 0x4057, 0x154c, 0x423d, 0xb295, 0xbf5e, 0x39c1, 0xb2c6, 0xb2aa, 0x42ba, 0x41e1, 0x2188, 0xba24, 0xba62, 0xb07c, 0x21ef, 0xb0f3, 0x43ec, 0x4167, 0x16d4, 0x03a1, 0x042a, 0x41bd, 0x41ef, 0x0ce7, 0x40b1, 0x1945, 0x4412, 0x416d, 0xbf73, 0x0cc6, 0x43be, 0x406c, 0x4650, 0x434e, 0xb09f, 0x0557, 0x13bd, 0x1647, 0x1a01, 0x40eb, 0x4257, 0x165b, 0x39b5, 0xba40, 0xbf8d, 0xbf00, 0x45cc, 0xacf1, 0xb21a, 0xb045, 0xba06, 0x42c3, 0x434c, 0x22f5, 0x4361, 0x4168, 0x2d5a, 0x4197, 0x42c2, 0x429f, 0x420f, 0x41ce, 0x4072, 0xb2e6, 0x42b7, 0x0373, 0x0dd4, 0xb2b9, 0x426e, 0x4398, 0x41d1, 0x4391, 0x4388, 0xbfbb, 0x1116, 0x41d9, 0x43a3, 0xb01c, 0x423c, 0xbf23, 0x31dc, 0x4363, 0x1dda, 0x41cb, 0xbf57, 0xba39, 0xa456, 0xb210, 0xb2c3, 0xbacd, 0xb2ae, 0x19df, 0x0c65, 0xbf3c, 0x42c9, 0x4073, 0x46a9, 0x41b0, 0xa506, 0xafdf, 0x4279, 0x416e, 0xa894, 0x15c3, 0x4182, 0x17b8, 0x41c0, 0x2e9c, 0x40bf, 0x4297, 0x42c9, 0x221d, 0xbfe0, 0x27c7, 0xb072, 0xbf13, 0x42e0, 0x1610, 0x43b9, 0x388e, 0x0521, 0xb29e, 0x38ac, 0x1a33, 0x4163, 0x40ae, 0x0262, 0x19ad, 0x1987, 0xbf04, 0x4029, 0x1f7d, 0x4358, 0x046c, 0x1f40, 0xbf00, 0x4052, 0x4228, 0xb0a8, 0xb094, 0xbfdc, 0x4183, 0x1e1d, 0xb006, 0xb244, 0x46b2, 0x40e0, 0x438a, 0x1cf2, 0x09e7, 0xb077, 0x3b0c, 0x42c6, 0x411d, 0x40f4, 0x1d92, 0xb230, 0x3ef5, 0x25ff, 0x40d3, 0xb2fa, 0x0906, 0x33f4, 0x4616, 0xbf93, 0x3405, 0x401a, 0xafb7, 0x4653, 0xb2f2, 0xba5f, 0x43cd, 0x433e, 0xaa58, 0xba2f, 0xb21f, 0x44f2, 0xb2c9, 0xb27f, 0xb219, 0x1f6e, 0x4234, 0xb241, 0x41fc, 0xb299, 0x4262, 0x4332, 0x43c9, 0x1ef1, 0xb209, 0xbf31, 0x4169, 0x4561, 0x439f, 0x43bb, 0x416e, 0x3af5, 0x435e, 0x2c99, 0x407a, 0x41eb, 0xa890, 0x4362, 0xb278, 0xba0b, 0x3322, 0x40ee, 0x4381, 0x3041, 0x434d, 0xbf33, 0xb0f7, 0x40a3, 0x46bd, 0xac4b, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x3b6339cb, 0xac4b9998, 0x998fa8a0, 0x40e73eaf, 0xf51f8ebf, 0x6f72ff20, 0xf89a82e1, 0x902e336b, 0xebc6f449, 0x4b10b8cd, 0x4c95ab80, 0xffe2804d, 0xe91ff5c2, 0x37fd5281, 0x00000000, 0x000001f0 }, + FinalRegs = new uint[] { 0x00000041, 0xfffffff7, 0x0c803106, 0xf8000021, 0x37fd52a9, 0x02400009, 0x00000000, 0x00000000, 0x4c95ab80, 0x00000000, 0x06f00000, 0xc802acfb, 0x4af3391b, 0x37fd517d, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xa5fa, 0x42f7, 0xb2c5, 0x40ad, 0x440d, 0x430e, 0x1a77, 0xbadb, 0x4199, 0x40e9, 0xa24d, 0xb221, 0x1a13, 0x403c, 0x4128, 0xba46, 0xbf7c, 0x43e8, 0x1a3e, 0xba00, 0x1aaf, 0x42f7, 0x433a, 0xbacb, 0x46c1, 0x4312, 0x43a5, 0x2370, 0x40f9, 0x195f, 0x4133, 0xb06b, 0xba1a, 0xba11, 0x09a0, 0x42e4, 0x4269, 0x42f2, 0xb28d, 0x0ba3, 0x05ab, 0xb298, 0xbfcf, 0xbaff, 0x4302, 0x438e, 0x4286, 0xb06a, 0x42ae, 0xbae0, 0x049b, 0x39b9, 0x4570, 0x4222, 0x0878, 0xbf66, 0x3af7, 0xbf90, 0x45e6, 0x4321, 0x400b, 0x0fdf, 0x42ff, 0xba07, 0x4424, 0x427d, 0x2f2b, 0xb23e, 0x4233, 0xbad7, 0xba58, 0xb293, 0x4430, 0xa78b, 0x424f, 0xb2f0, 0x1cab, 0xbf4f, 0x431c, 0x4199, 0xb0fa, 0x29cf, 0x405a, 0x150b, 0xb2fb, 0x43a7, 0x41c5, 0x31b3, 0xb258, 0x40dc, 0xbfb0, 0xb0b5, 0xa486, 0x4338, 0xba40, 0xbfe0, 0xba52, 0x4157, 0xa730, 0x431c, 0x188f, 0xbf92, 0x4306, 0x415b, 0x251b, 0x407f, 0xaef4, 0x4127, 0x0478, 0x0bcd, 0xb2b8, 0xa743, 0x4220, 0x420f, 0x1bfb, 0x4340, 0xa62a, 0x4329, 0xbf70, 0x2233, 0x43d6, 0x46da, 0x06d3, 0x1a7b, 0xb2fb, 0x08eb, 0x4372, 0xb2cc, 0x20d8, 0x0227, 0xbfc2, 0x1f6f, 0x2906, 0xb2c8, 0x4676, 0xb086, 0xb21f, 0x43e1, 0x4094, 0xbf0b, 0x4269, 0x4153, 0x322b, 0x05ec, 0xb00f, 0x41e5, 0xab98, 0xb03e, 0xb2f7, 0x44dd, 0xb29b, 0x1b5f, 0xb223, 0x1136, 0xb222, 0xb0d3, 0xb2c3, 0x413d, 0xba0c, 0x40c4, 0xbaf2, 0x355b, 0x4352, 0xbaec, 0x428a, 0x3477, 0xbf07, 0x427f, 0x193c, 0x4101, 0x4047, 0x42bd, 0x316c, 0x1da0, 0x460e, 0x18ac, 0x442d, 0x4489, 0xb261, 0xbf6a, 0xb265, 0x4134, 0x430b, 0xb280, 0xb007, 0x146b, 0x0779, 0xb07d, 0x4478, 0x42b5, 0xb25c, 0xbfaa, 0xa1d2, 0x4139, 0xb206, 0xb212, 0xba5e, 0xb261, 0xb237, 0xba11, 0xba18, 0x4357, 0x4178, 0x43cd, 0x4234, 0x4134, 0xb2cc, 0x4388, 0x4374, 0xa39f, 0x43e8, 0x411e, 0xb274, 0xbf5f, 0x1e46, 0x1ddd, 0x404b, 0xb279, 0x37e1, 0x44c4, 0xb093, 0xbf6c, 0xbf70, 0xb068, 0x1584, 0x4018, 0xb256, 0x00cb, 0x4205, 0xa715, 0xbf70, 0x4253, 0x44c5, 0x1f85, 0x189e, 0xbfa0, 0x400c, 0x3a61, 0x424c, 0x46dc, 0xab38, 0xbfb7, 0x0388, 0xba10, 0x1f7f, 0xb052, 0x43f1, 0xbf80, 0xb26b, 0xb2c1, 0x4249, 0x40d6, 0x26e0, 0xbae6, 0x4132, 0x404f, 0x461b, 0x4062, 0x1a87, 0x23fe, 0xb216, 0xba38, 0x4141, 0xa221, 0x40ab, 0x19c9, 0x1f3c, 0xbf65, 0x40fe, 0xb268, 0x413a, 0xb0b3, 0x44e4, 0xa369, 0x4211, 0x2341, 0xbfb0, 0xbfb2, 0xb2aa, 0x2549, 0x4034, 0xb269, 0xa110, 0x4140, 0x4089, 0x4028, 0x2609, 0x4218, 0x43e4, 0xbf00, 0xb2e3, 0xba33, 0x42db, 0x40d0, 0x18fb, 0x3271, 0xbf19, 0x3067, 0x3c16, 0x4130, 0x3563, 0x072b, 0xb043, 0xb0df, 0xb077, 0x4326, 0x42cc, 0x24bd, 0x37cb, 0x41c6, 0x4350, 0x18da, 0xb267, 0x44da, 0x0379, 0xaf9b, 0x42c5, 0xb04f, 0x4012, 0x401e, 0x433c, 0x23d8, 0x4130, 0x1b4a, 0xb264, 0x416a, 0xbf18, 0xb2e9, 0xb056, 0x1944, 0xba2c, 0x4110, 0x2e24, 0x3a58, 0xb291, 0xba4a, 0x322e, 0x45d4, 0x43a9, 0xba4d, 0xbfe1, 0xba67, 0xa1ba, 0x0aab, 0x41d7, 0x1393, 0xaff4, 0xbfc0, 0xaa25, 0x4004, 0x4646, 0x044a, 0x42f6, 0x407f, 0x432c, 0x4394, 0xbf60, 0x409a, 0x4174, 0x0bde, 0x41b7, 0x42e1, 0x42e0, 0x19fd, 0x072c, 0xb005, 0x23da, 0xba36, 0x013b, 0x02b8, 0xbf83, 0x1896, 0x38ab, 0x1802, 0xa075, 0xba64, 0xb23b, 0x40e7, 0x4245, 0x42f4, 0xa000, 0xb27a, 0xbaef, 0x4052, 0x06e6, 0x3e0f, 0xb02a, 0x29cd, 0x0a60, 0x42ad, 0x25d3, 0x4220, 0xb24c, 0x1ad2, 0x4077, 0xb218, 0x4030, 0x4273, 0xbfc1, 0x4288, 0xb0e5, 0x42d9, 0xb219, 0x4303, 0xb2a7, 0x1845, 0x43d0, 0x4378, 0x3c9c, 0x33b3, 0x4156, 0xb0ec, 0xbaf1, 0x3ee5, 0xbada, 0xb2ff, 0x4571, 0x41dc, 0xbfc9, 0xb043, 0x0309, 0xa14d, 0x411d, 0x0eb5, 0x41da, 0x406f, 0x43a7, 0xba70, 0xba57, 0x1bbf, 0x42bb, 0x3d25, 0x1b41, 0xb0f2, 0xba0c, 0x4127, 0xbfcf, 0x4157, 0x1e08, 0xb2a9, 0x4615, 0x41bd, 0x4279, 0xab6d, 0xbaeb, 0x05c6, 0x41de, 0xb24b, 0x0bf1, 0x42ba, 0x42be, 0x392a, 0x4251, 0xba79, 0x0623, 0xbac2, 0x43c4, 0x4279, 0x4222, 0x1d6e, 0x43a2, 0x10dc, 0xb0d9, 0x18c8, 0xb25c, 0xbfa8, 0x402c, 0x1b45, 0x42bd, 0x4367, 0xb0c8, 0x4375, 0x4341, 0xb2d6, 0x42ee, 0xa002, 0x4279, 0xb21b, 0x4397, 0x4057, 0x40c4, 0x2547, 0xa55a, 0x41ed, 0xb06b, 0x43c9, 0x45d2, 0x41b8, 0x4338, 0xbfc9, 0xb249, 0x066a, 0xa8d4, 0xbfa0, 0x41e7, 0x013f, 0x40e1, 0xb27d, 0xba48, 0x19f8, 0xb0d6, 0xb02a, 0xb022, 0x41e3, 0x4363, 0xb200, 0xbf46, 0x431d, 0x0a44, 0x419c, 0x42ce, 0x2bd3, 0x42c1, 0xb0d5, 0x0d26, 0x44d8, 0x1cfa, 0x4054, 0xba30, 0xb29a, 0xb241, 0xbfd0, 0xb297, 0xb2b9, 0x43f8, 0xb23e, 0xb2ad, 0xba6f, 0xb23d, 0xb013, 0xba77, 0x3e95, 0xba62, 0xb0cc, 0xba22, 0xbf79, 0xba6e, 0x1ac2, 0x339f, 0xb041, 0x415b, 0xb289, 0x42b5, 0xb246, 0x0107, 0x406c, 0x4219, 0x41ae, 0x1dfa, 0xba38, 0xb217, 0xb217, 0x432f, 0x43fe, 0x1e95, 0xbfcc, 0x43df, 0x06a8, 0x4158, 0xad6e, 0x43d8, 0x1902, 0x43ba, 0x4289, 0xbf55, 0xba62, 0xbafa, 0x41dc, 0x3893, 0x41a6, 0x3bd9, 0x0066, 0x4433, 0xbae0, 0x4373, 0x4367, 0x3924, 0x4193, 0xb295, 0xb23e, 0x19cf, 0xb24b, 0x42a2, 0x3c87, 0xb015, 0xbf55, 0x1b93, 0x46bd, 0x423e, 0x43f0, 0xb0b4, 0x4036, 0xb2ef, 0x0fc6, 0xb225, 0x45c6, 0xa8dd, 0xb00a, 0x4091, 0xb265, 0x4211, 0xbacb, 0xbf68, 0xb21d, 0x1058, 0x4385, 0xaa47, 0x32ae, 0x46f0, 0xbf29, 0x1858, 0xab7c, 0xba78, 0x44d3, 0x464d, 0x42dd, 0x43d2, 0x17b7, 0xb0f1, 0x4138, 0x408d, 0xbf6f, 0x41b8, 0xb25c, 0x1b45, 0x40b6, 0x4084, 0x1a56, 0x1c02, 0xb2c7, 0x41df, 0xb0f9, 0x3625, 0x0a77, 0xbf80, 0x1997, 0x441f, 0x410a, 0xb209, 0x4323, 0x2e9c, 0xade2, 0xb211, 0x40aa, 0x4618, 0x4160, 0x18d1, 0x4398, 0xb25a, 0x409d, 0x4311, 0xbf05, 0x29a5, 0x438b, 0xb003, 0xa5db, 0x42b5, 0xbae1, 0x4011, 0x4415, 0x416c, 0xbfce, 0x08cd, 0xb29b, 0xb26c, 0x40c6, 0xbf8f, 0x41de, 0xb09d, 0x3a51, 0x2fba, 0x4341, 0xb27d, 0xb250, 0x438d, 0x43a3, 0x417a, 0xb2b8, 0xba3b, 0x40c8, 0x4349, 0x4323, 0xb2b1, 0x45c1, 0x41a5, 0xba36, 0x4138, 0x401f, 0xbfd0, 0xb20b, 0xb27d, 0x4267, 0x42e2, 0x45a8, 0xbf9e, 0xbadd, 0x41e8, 0x1c5e, 0x4133, 0xbaef, 0xbf6e, 0xa20d, 0xba19, 0x4194, 0x283b, 0x1d04, 0xb2bf, 0x42aa, 0x2109, 0x433c, 0x45ca, 0xa3e1, 0xbff0, 0xb0cc, 0x3304, 0x40af, 0xa1b6, 0x38e2, 0xba5f, 0x3c6d, 0xb2c9, 0x44f3, 0xb23c, 0x3f80, 0xbf60, 0x419c, 0x427c, 0x4207, 0xbfc5, 0x1e21, 0xa5e6, 0x40b7, 0x4020, 0xbf70, 0xa166, 0xbf2b, 0xb065, 0x10e9, 0x4355, 0xb262, 0x43ad, 0x40aa, 0x0291, 0x1958, 0x1c0b, 0x02ce, 0x4046, 0x3833, 0x1b88, 0xbfd0, 0xbf60, 0xbf66, 0xb09b, 0x1abf, 0x4381, 0x40fe, 0x4175, 0x3d96, 0x1bcb, 0x0a61, 0x43ee, 0x2678, 0xa7a5, 0x40d8, 0xb283, 0x4419, 0x40df, 0x1f45, 0x436f, 0x4085, 0x40c5, 0x413d, 0xb251, 0xbf15, 0x0cf4, 0x4205, 0xba59, 0xb239, 0x4394, 0x41ba, 0x4162, 0x17bb, 0xb2c0, 0x404c, 0x4155, 0x41e9, 0xbf2c, 0x42f5, 0xb254, 0x43a2, 0xbad4, 0xb266, 0xbaf9, 0x1a34, 0x2cef, 0xba72, 0x411d, 0xb0d2, 0x423e, 0x4437, 0x460a, 0xbfb2, 0x38b1, 0x28e2, 0xb285, 0xbfd0, 0x0d57, 0xb0a5, 0x439c, 0xb21f, 0x0547, 0x2a77, 0x43a2, 0x3c57, 0xb223, 0x1a5c, 0x12e5, 0xaa5a, 0xbf97, 0xba6b, 0x1f64, 0xba18, 0x431a, 0xb2fc, 0x4245, 0x438c, 0x4248, 0xbf60, 0x403c, 0x436e, 0x42c7, 0xb218, 0x4169, 0xbfc0, 0xba72, 0x4285, 0x4493, 0x4296, 0x4255, 0xb0e8, 0x45c9, 0x4083, 0xbfaf, 0xbf60, 0x4323, 0xbfd0, 0xb260, 0x45e4, 0x40e6, 0x43b3, 0xbf73, 0x440f, 0x1b4e, 0x430c, 0x43aa, 0xba3a, 0xb27b, 0xb251, 0x2bbe, 0xb2c2, 0xae3d, 0xb2d6, 0xba1b, 0x3599, 0x40ce, 0xb060, 0x40bc, 0xb237, 0x4087, 0x0927, 0x41a4, 0xb03b, 0x2497, 0x4485, 0x41c7, 0x426c, 0x4097, 0xbfaf, 0x41ed, 0xba17, 0x43fb, 0x40a4, 0xbf4e, 0xa776, 0x206d, 0x288a, 0x4300, 0x4111, 0x40cf, 0x4287, 0x404e, 0x426f, 0x2cfd, 0xb26a, 0xabd6, 0x43c3, 0xbfa9, 0x259b, 0x2856, 0x41ef, 0x196c, 0xb242, 0x45e4, 0x41ad, 0xa510, 0xb2ba, 0x16fe, 0x44e8, 0x42f3, 0x40a6, 0x1d3a, 0x2313, 0x4167, 0x44c5, 0x43e2, 0xa74d, 0x42a4, 0x257a, 0x1d63, 0xb202, 0x1ccf, 0x46b2, 0x2a5f, 0x4354, 0x39a8, 0xbf65, 0xb09e, 0x43e6, 0xa4e6, 0x1cb6, 0x1f46, 0x1ff3, 0x348a, 0xb220, 0xb2d8, 0xbfa0, 0xbf5d, 0x415d, 0xb265, 0x4299, 0x413f, 0xba62, 0x031b, 0x4473, 0x0ba1, 0x1acf, 0x41ce, 0x4041, 0x403b, 0xb276, 0xba7e, 0x0190, 0x0272, 0x408e, 0xbf13, 0xb2ea, 0x4216, 0x43a0, 0x4216, 0x4034, 0x401f, 0x43c0, 0x4147, 0xba0b, 0x42c1, 0xba30, 0xbaf4, 0x416a, 0x4178, 0xbad5, 0xbf90, 0xa8a6, 0x4192, 0xbfc3, 0x40c7, 0x4377, 0x2630, 0xb0eb, 0x45a1, 0x43dd, 0x1a55, 0xb229, 0xb2dc, 0x406a, 0x40f7, 0xb075, 0x43d3, 0xb2aa, 0x0a60, 0x07d8, 0x42ab, 0x4266, 0xbf69, 0x436f, 0xbfe0, 0x45d9, 0x1de6, 0x4003, 0x435b, 0xa29c, 0xb255, 0x2ad4, 0x41c4, 0x458d, 0xbf04, 0xba6b, 0x1aad, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xa3e9d705, 0xe9aa48da, 0xc172d1f9, 0x9b3d01b0, 0x01afea06, 0xeb3a9b8a, 0x12cc8d93, 0x01c88d27, 0x58e8c5c1, 0xdc241641, 0x7eb66cf7, 0x4ab2dd86, 0x10ad69fd, 0x3b68308b, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x00000000, 0xffffff9e, 0x00001a40, 0x00000000, 0x00000000, 0x00000040, 0x00000000, 0xffe68b7f, 0x008fee88, 0x58e8c62c, 0x00000000, 0x22b65886, 0x9565bb0c, 0x011fdd38, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x43cc, 0x407a, 0x415b, 0x42d2, 0xb20d, 0x41de, 0x1b41, 0x19b6, 0xb051, 0x1dcf, 0x36bc, 0x29fd, 0xbf00, 0x15eb, 0x415d, 0x2be9, 0x1bd3, 0xba76, 0x41a0, 0x45b0, 0xb26e, 0x0c41, 0x3dc0, 0xa863, 0x08ac, 0xbf87, 0x01a2, 0xba51, 0x430e, 0x1090, 0x425e, 0x428c, 0xba79, 0xb0b7, 0x1c66, 0x1c0e, 0x1bf3, 0x40df, 0x4663, 0x4346, 0x1b2f, 0x19fe, 0xbfdd, 0x0720, 0xa14b, 0x1a70, 0xba79, 0xba54, 0xba53, 0x42ef, 0x42c0, 0x409f, 0xbfd6, 0x4276, 0x41df, 0x2a4b, 0x43d6, 0x4094, 0x403d, 0x456a, 0x4027, 0x40b1, 0xac6c, 0x4201, 0x426d, 0x40f2, 0x3d25, 0xb26c, 0x2c76, 0x4193, 0xb0ae, 0x4048, 0x46f4, 0x0f9b, 0xbf00, 0x435b, 0xbfd0, 0x4166, 0x42da, 0xbf78, 0x42a6, 0x455c, 0x428d, 0xbafc, 0x140b, 0xbf8d, 0xb201, 0x4348, 0x4198, 0x1ad2, 0x41fb, 0xba39, 0xb295, 0xbf07, 0x43cb, 0x1d7c, 0xb285, 0x4223, 0x41a0, 0x2f9b, 0xbfa0, 0xb0ab, 0xb00d, 0x411f, 0x463a, 0x43d7, 0xb240, 0xbac6, 0x4067, 0xaa78, 0xb25f, 0x41f2, 0x438d, 0x42ed, 0x4217, 0xbf36, 0x4110, 0x4127, 0xb2dd, 0x4033, 0xae52, 0x4633, 0x401e, 0x1060, 0xb026, 0x3426, 0x4328, 0x4223, 0x3866, 0x4665, 0x1c0d, 0x05a2, 0xbf5c, 0xb2e6, 0xa188, 0xac49, 0x424b, 0x261f, 0x41c5, 0x41e9, 0xad0a, 0x2738, 0xba47, 0xa121, 0xb095, 0x45ee, 0x18ca, 0xbf80, 0x030f, 0x4279, 0x46e2, 0x418d, 0x44c5, 0xb250, 0x4248, 0xb026, 0xbfa8, 0x41d3, 0xa696, 0x426f, 0x45da, 0xb294, 0x40e2, 0x1ccd, 0xa595, 0x420e, 0xaf3b, 0x10ee, 0xb0ae, 0x3c78, 0x413a, 0xb0b4, 0x23f4, 0xbf5d, 0x18be, 0xb077, 0x4070, 0x022e, 0x41be, 0xb2a9, 0x42c3, 0xbf1f, 0x4194, 0x037b, 0xbfd0, 0x42ef, 0x025e, 0xb21c, 0xbf91, 0x4016, 0x43e7, 0x4280, 0x409e, 0xb25b, 0xb023, 0xb224, 0xaa0f, 0xba4b, 0x1b56, 0xba31, 0xa177, 0x4118, 0x08be, 0x433b, 0xb023, 0x40cb, 0x2d55, 0xb0a3, 0x40ef, 0x44d9, 0x420f, 0xb0a7, 0x39b0, 0x412b, 0xbfa8, 0x1e44, 0x4602, 0xbfa3, 0xba1e, 0xb2c6, 0x429a, 0xb205, 0x047a, 0xb0fe, 0x4373, 0xbad8, 0x434c, 0x30e0, 0x1ee5, 0xbfb0, 0x410e, 0xb21e, 0xa97d, 0x42b1, 0x4671, 0x433f, 0xbf0d, 0x0ee6, 0x42ac, 0x19e3, 0x41a3, 0xbfa0, 0xba44, 0xa51c, 0xb241, 0xb2af, 0x415a, 0x0a3c, 0xbf1c, 0x28a0, 0x43d5, 0x4200, 0x43f3, 0x4240, 0x419c, 0x3d0b, 0xb09a, 0xa807, 0x42fa, 0x41e0, 0xbad5, 0x3438, 0xba09, 0xb0f9, 0x4628, 0x4167, 0x1b7b, 0xba0a, 0xa17a, 0xb276, 0x40b8, 0x121a, 0x133c, 0xbf34, 0x3613, 0xb259, 0x4105, 0x41de, 0x4155, 0xa2c3, 0x189d, 0xbf65, 0xb017, 0xa019, 0xb082, 0x40c1, 0x4319, 0x1806, 0x1131, 0xbf87, 0x402b, 0x2960, 0x1969, 0x4232, 0x465a, 0xa10c, 0x464a, 0x37eb, 0x45a3, 0x401d, 0x1f11, 0xb0f1, 0x4210, 0x439a, 0x4048, 0x44a1, 0x28fb, 0x44f0, 0xb0b6, 0x41bf, 0x199b, 0xb268, 0x421a, 0x0b58, 0x40ad, 0x18b6, 0xbf09, 0x402a, 0x137c, 0x4046, 0x175f, 0xb0cf, 0x419b, 0xbf52, 0x419d, 0x4213, 0x04a6, 0xba01, 0xbff0, 0x01d4, 0x1932, 0x42d7, 0x4123, 0x4160, 0x0e55, 0x40c5, 0x195a, 0x43bc, 0x413d, 0x1d89, 0x40e8, 0xb21b, 0xb2a3, 0x43e1, 0x417e, 0x423c, 0x24d1, 0xbf6d, 0xa57c, 0x2ff1, 0x05fa, 0x4200, 0xb26f, 0x05f6, 0x064f, 0xbfcd, 0xb2dd, 0x40ed, 0x360f, 0xbafa, 0xb0e6, 0x2f81, 0x09cc, 0x4072, 0x408c, 0x0899, 0xb26c, 0x1ee4, 0x465f, 0x4175, 0xa5b6, 0xba63, 0x1ffc, 0x4094, 0x1a4f, 0xba23, 0xbf2b, 0x25dc, 0xba1f, 0x413b, 0x4105, 0x4180, 0x4186, 0xb2f9, 0x4194, 0xbfae, 0x1da7, 0x1afc, 0x4330, 0x3083, 0x43e5, 0x1f44, 0xba0a, 0x4469, 0x4062, 0xbaeb, 0x16ab, 0x363c, 0x1bdd, 0x40b0, 0xbfa8, 0x4346, 0x4129, 0xb080, 0x4450, 0x40d1, 0x12e2, 0x40a6, 0xae28, 0xbf74, 0xb08f, 0xb280, 0x0fb5, 0xb287, 0xb28e, 0x4238, 0x296f, 0x4398, 0x43b3, 0xb0b7, 0xba78, 0xbf73, 0x41be, 0x40ec, 0x1602, 0xb227, 0xb2c4, 0xbf28, 0x4298, 0x2f81, 0xbadc, 0x1dd0, 0xb255, 0x2521, 0xbac6, 0x03e6, 0xa5ac, 0xba11, 0x401e, 0x4172, 0x1912, 0x43a1, 0xbfa0, 0x41da, 0xb283, 0x2be3, 0xbaff, 0x24bf, 0x415a, 0x1da5, 0x4212, 0x436f, 0x41b8, 0xb29e, 0xbf28, 0xb2e8, 0x1ff8, 0xabd8, 0xb064, 0xba4e, 0x4136, 0x425c, 0x41b5, 0xba40, 0x19bb, 0x26a6, 0xbf26, 0xbaf6, 0xba55, 0x4169, 0x40c2, 0xb2c0, 0x0da2, 0x41fc, 0x3d98, 0x43d1, 0x19af, 0x4023, 0x0a9d, 0xb003, 0xb046, 0x1866, 0x40b2, 0x41af, 0xbafa, 0x1bd8, 0xbfa8, 0x4450, 0x427a, 0x4190, 0x1aa2, 0xa784, 0x1069, 0xb095, 0x181a, 0x4450, 0x41b6, 0x1bf7, 0x1d12, 0x347a, 0x41a3, 0xb263, 0x4215, 0x431c, 0x433c, 0xbf61, 0xb200, 0xb2db, 0x1c1d, 0xb2e5, 0xbf60, 0x12e2, 0xa6a8, 0x45aa, 0xb2bc, 0x41ca, 0xb2d7, 0xab7e, 0x0d22, 0xbf9e, 0x40b8, 0x2c08, 0x1db6, 0x3d47, 0x42ee, 0x4081, 0x412b, 0xa807, 0x350a, 0x43c6, 0xaea7, 0xbf5d, 0xa806, 0xb2cc, 0x4143, 0x30fb, 0x0043, 0x46b9, 0x1c7f, 0x1e13, 0x0da5, 0xb214, 0x3da5, 0x437f, 0x3532, 0x458a, 0x4131, 0x424b, 0x144f, 0x433a, 0x3f9b, 0x0dfc, 0x1faf, 0xb253, 0x4137, 0xbfd0, 0xbf49, 0x4107, 0x24b5, 0x4324, 0x40fb, 0x30c9, 0x1ea2, 0x324f, 0x1a34, 0x0dc7, 0x40e7, 0x1d48, 0x41dd, 0xae7c, 0x43a3, 0x299f, 0xbf01, 0xb077, 0x3770, 0x4140, 0xbfe0, 0x4265, 0x4019, 0x40ed, 0x0afe, 0x1b29, 0x1afc, 0xb23c, 0x04db, 0xb214, 0x439d, 0x2938, 0x0092, 0x40b0, 0x43c0, 0x43d5, 0xb0c1, 0xa37e, 0x1e61, 0xb021, 0x4022, 0xbf6f, 0x4114, 0x407d, 0x428c, 0x415e, 0x4256, 0xb0ea, 0x41e6, 0xbaf7, 0x4060, 0x1ee1, 0x1100, 0x42d5, 0xb25f, 0xb063, 0xb253, 0x1945, 0x30ee, 0x4210, 0x3373, 0x42a7, 0xbf72, 0x42d1, 0xb038, 0xbae9, 0x43ea, 0x40c7, 0xbfa0, 0x299d, 0xb294, 0x404e, 0x085d, 0x4341, 0x4172, 0x1c2a, 0x4183, 0xbf2b, 0xb2b7, 0x40ab, 0xb279, 0x43a9, 0x1cc5, 0x4208, 0x402f, 0x43b8, 0x43ee, 0x41e4, 0x3928, 0xba55, 0x4235, 0x2c5d, 0xbf90, 0x4278, 0xab33, 0x4338, 0x1631, 0x43b3, 0xbfc0, 0x058f, 0xba1c, 0x424d, 0x40e6, 0x3bc1, 0x43a9, 0xba00, 0xbf3e, 0x41fc, 0xa8a3, 0xba2b, 0x1e86, 0x23a6, 0x41f5, 0x40e4, 0x40c8, 0x430d, 0x46b2, 0xba5d, 0x415b, 0x32b1, 0x43ec, 0x2faf, 0xbfda, 0x43ac, 0xa3e2, 0xb283, 0x1c45, 0x4341, 0x4133, 0x40b4, 0x36da, 0x1c1e, 0x4395, 0xba7e, 0x4070, 0x1aeb, 0x467a, 0xb29e, 0x345f, 0x4364, 0xbf00, 0x412e, 0xb2e8, 0x1b7e, 0x2b8a, 0x42c7, 0x412b, 0x4097, 0xb2bf, 0xbf0c, 0x1914, 0x443e, 0x3c97, 0x310b, 0x4091, 0x4367, 0x409f, 0xba38, 0xbf33, 0x403d, 0x4024, 0x1d51, 0x43ed, 0xba4c, 0x4057, 0x46fc, 0x4356, 0x4095, 0x4119, 0x4019, 0xb232, 0xba07, 0x22e0, 0x4082, 0xb038, 0x19e5, 0x1171, 0xbfc3, 0x1cf0, 0x1e78, 0xb2bd, 0x250a, 0x4128, 0x42bf, 0x43c4, 0xb0cf, 0xb269, 0xba09, 0x42dd, 0x1ad6, 0x41c3, 0x2cab, 0x411d, 0xbf95, 0xaf53, 0x45b5, 0x4255, 0x41d7, 0x43c1, 0x4447, 0xb2c7, 0xb284, 0x1c1e, 0xba76, 0xaada, 0x1344, 0x40ac, 0x426f, 0xaa92, 0x41d1, 0x261f, 0x189e, 0x1904, 0x41fe, 0xb283, 0xa695, 0xbfa3, 0xb0f1, 0x41dd, 0xb2c2, 0xb2fd, 0x431b, 0x438d, 0xb2a1, 0x4240, 0x42c9, 0xa2b2, 0x345e, 0x435e, 0xbf1f, 0xba7f, 0x41d4, 0x1e3f, 0x0eee, 0x1d12, 0x42d2, 0xbfb0, 0x41b0, 0xa79a, 0xbf80, 0x32a0, 0xb20d, 0x3e30, 0xb2ad, 0xb273, 0x41a4, 0xb2c3, 0x404c, 0xba3e, 0x4377, 0x4197, 0xbfd9, 0xba3a, 0xb23e, 0x41c3, 0xb042, 0xb2ee, 0xb24c, 0x43ce, 0xbade, 0xa3fb, 0x04f6, 0xb293, 0xa149, 0xb25e, 0x4319, 0x19e5, 0xbf1c, 0x1878, 0x1bb1, 0x4061, 0x4657, 0x414d, 0x1f05, 0xba55, 0x1a6a, 0x4065, 0x0674, 0xab5f, 0xa70a, 0xbf87, 0x4095, 0xb089, 0x4121, 0x4300, 0x4012, 0xbfbf, 0x40a1, 0x436c, 0x4032, 0xbad1, 0xa20b, 0x1d95, 0xa4be, 0x1deb, 0x419f, 0x4324, 0x4418, 0x4363, 0x42ce, 0x403c, 0xbfb3, 0xb07a, 0x4179, 0xbaf5, 0x1daa, 0xb22a, 0x4671, 0xba28, 0x188a, 0x41cd, 0xb298, 0x19e9, 0x428b, 0x1e5c, 0x413a, 0x3a1f, 0x41d5, 0xa080, 0x43d2, 0x28e1, 0x436e, 0x40ce, 0xb27c, 0x26cf, 0x407e, 0x456a, 0x43c7, 0xb253, 0x4667, 0xbf34, 0x411e, 0x43f8, 0x4152, 0xbaff, 0x42ce, 0x4147, 0x128c, 0x4095, 0x1a0a, 0x4392, 0x1c9b, 0x41ed, 0x2a7a, 0x01f2, 0xbf00, 0x004d, 0xb2b3, 0x1d3b, 0xb0e9, 0xb2f3, 0xbf93, 0x10bb, 0x41bf, 0x1f9e, 0x1d2d, 0xb2d4, 0x08c2, 0x4190, 0xbadc, 0xba46, 0x42cc, 0x446d, 0x424e, 0x4146, 0x40ef, 0xa359, 0x1a54, 0xb066, 0xba55, 0xbacf, 0x41f3, 0x4689, 0xbf0f, 0x1492, 0xbae7, 0xb2b4, 0xb2a0, 0x413f, 0x00d2, 0x406e, 0x40dd, 0xb2c9, 0x0345, 0xb20e, 0x432d, 0x4227, 0x34a0, 0x429c, 0x416b, 0xb08d, 0xbf4f, 0x19d9, 0x42bd, 0xa315, 0x442a, 0x1a15, 0x445e, 0xaea8, 0x0a9d, 0x35e8, 0xb29f, 0x1a1e, 0x3022, 0xb050, 0xa914, 0x0e5a, 0x4267, 0x4445, 0x4367, 0x1ff7, 0x40f8, 0x23dd, 0x4283, 0x454d, 0x415e, 0x4199, 0xbfe4, 0xbfc0, 0x4185, 0x4564, 0xb059, 0x409f, 0xbf55, 0x446b, 0x2c78, 0x43b4, 0x435a, 0xa547, 0x411d, 0x2c11, 0xb0a8, 0x0e79, 0xb23c, 0x4111, 0x42e8, 0x1a3e, 0x1e15, 0xbf06, 0x0cce, 0x1e92, 0x0485, 0xba74, 0x0a52, 0xa544, 0x4120, 0xb087, 0x41e8, 0xba64, 0xbf25, 0x3059, 0xb2f3, 0x4301, 0xb2e3, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x5b8d957d, 0xda740f74, 0xf45748f5, 0x13975c8e, 0xd433eb4e, 0xa749743f, 0x6c798277, 0xd9c1885d, 0xc3623606, 0xea7f747e, 0x8bf0542c, 0xfe1fe0b1, 0xd0d3fbf2, 0xed311e85, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x00000000, 0x007fffff, 0xb0934dd8, 0x00000000, 0x000018e4, 0x00000000, 0x00000000, 0xc3623606, 0xffff8cd5, 0xb0934e5d, 0xfe1fe0b1, 0x000015a6, 0xb0934c3f, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x2722, 0x4544, 0x4378, 0x437b, 0x33fe, 0x41f0, 0x1814, 0x1583, 0x0945, 0xa03e, 0xb24c, 0x1ba2, 0x1e8e, 0x4291, 0xb2d6, 0x1b2d, 0x1637, 0xbfca, 0x3736, 0xb2a1, 0x41d7, 0x42e1, 0xb2b3, 0x4007, 0xb073, 0x1d95, 0xb219, 0x3d08, 0x415a, 0x4282, 0x4245, 0x43c9, 0x1a86, 0xb2bf, 0x463e, 0x40af, 0x1cd7, 0x43b1, 0x4108, 0x0da6, 0x430f, 0x418b, 0xbf2b, 0x4608, 0x441c, 0x41b7, 0xb254, 0xba40, 0x42b7, 0x1de0, 0xbf70, 0x1d62, 0xba76, 0xb045, 0x4138, 0x4067, 0x0654, 0x1e1a, 0xb015, 0x447a, 0x430b, 0x4154, 0x2823, 0x4685, 0xbf75, 0x4080, 0x426a, 0x43a2, 0x40eb, 0x437c, 0x4048, 0x4285, 0xba0d, 0x1eea, 0x4364, 0xbf29, 0x41b9, 0x4351, 0x4204, 0x1c9d, 0xa86b, 0x1174, 0x4064, 0x00d4, 0x4021, 0xb047, 0xba13, 0x4113, 0xb2a0, 0x12fd, 0x4171, 0x4178, 0x410e, 0xba24, 0x432b, 0xb2b0, 0x3ef8, 0xb2ea, 0x410e, 0x1db0, 0x18de, 0x1ffa, 0x1bc9, 0xbf27, 0x43af, 0x4021, 0xb2f6, 0x4307, 0x400e, 0xba4a, 0x44f4, 0x43e4, 0x22a9, 0xba03, 0x1c54, 0x4356, 0x1c4c, 0xbf8a, 0x426e, 0x4136, 0xac9b, 0xb011, 0xba49, 0x4458, 0x1230, 0x4551, 0x4003, 0xbf41, 0x1ff7, 0xb2a2, 0x19dc, 0xabbe, 0xbf28, 0xa7a7, 0x407c, 0x4019, 0xa9c5, 0x4479, 0xb07b, 0x00e2, 0xbf16, 0xb2a7, 0x323b, 0xbfe0, 0x42fd, 0xb2b7, 0x40fd, 0x4140, 0x421e, 0x41dc, 0x179a, 0x2e91, 0xb25a, 0xb0af, 0x4384, 0x4157, 0x45ad, 0x280d, 0xbfa2, 0x467b, 0xba07, 0x4262, 0x36bd, 0xb0d2, 0x289b, 0x4211, 0x4152, 0x41ef, 0x426e, 0xb2f2, 0x4356, 0x4614, 0xbaf0, 0x4229, 0x314a, 0x4182, 0xbf1e, 0x1fd7, 0xba5d, 0xad77, 0x43aa, 0xbae9, 0xbfd0, 0x0a05, 0xa8b2, 0x419a, 0xb23c, 0x4367, 0x4090, 0xbf70, 0xbf1c, 0xbf00, 0xb0f9, 0x4274, 0x4299, 0xb210, 0xb215, 0x1e80, 0x08e8, 0x43ee, 0x412f, 0x379d, 0x402e, 0x1919, 0xaff5, 0x4106, 0xbae6, 0x42e4, 0x45aa, 0x40e4, 0x1b18, 0x43b9, 0xbf1d, 0xbaca, 0x3c0c, 0xb090, 0x463c, 0x1fe5, 0xb21c, 0x4129, 0x3dd7, 0xb24f, 0xaeb3, 0x4651, 0xb0f0, 0x0629, 0xba16, 0x42be, 0x4147, 0x1d46, 0x2698, 0x436f, 0xb080, 0xbff0, 0x4337, 0x3d59, 0x465e, 0x2c6d, 0x4262, 0xb088, 0x417d, 0x41b8, 0xbf12, 0xbac4, 0x40c5, 0x1406, 0x3e5f, 0x3f1c, 0x04b0, 0xa681, 0x469a, 0x433a, 0x443c, 0x26c9, 0x41eb, 0x4198, 0xbfd3, 0xb2d5, 0x4380, 0xb24d, 0xba72, 0x4366, 0x42af, 0x1ae5, 0x43ff, 0x43ed, 0x412e, 0x1d52, 0x405a, 0x41ed, 0x45cd, 0x41ae, 0xb2b8, 0xbfda, 0x2331, 0x4430, 0x0f27, 0x4036, 0x1f45, 0x40b2, 0xb0a7, 0x4230, 0x0a6e, 0x44f8, 0x460d, 0xb29f, 0xa318, 0x430b, 0x3556, 0xae3e, 0xb034, 0xbf57, 0xb2d5, 0x3b8c, 0x44ea, 0xb05a, 0x42f4, 0xa5f5, 0x2209, 0x416c, 0x4466, 0x422b, 0x4263, 0x43d2, 0x1468, 0xb265, 0xbfcb, 0xbad4, 0xb28f, 0xbadb, 0xa163, 0x443b, 0x3b15, 0x197c, 0x261a, 0x43a1, 0x4028, 0xb24b, 0x4182, 0x1e7a, 0xb2d8, 0x4108, 0xba42, 0xbfe0, 0x42ba, 0x4212, 0x1849, 0x1a51, 0x1a9f, 0x15e7, 0xba28, 0xb27f, 0xbf23, 0x41c9, 0x3442, 0xad53, 0x406f, 0x0712, 0x1cd0, 0x467c, 0x1da8, 0x42fe, 0x43e1, 0xba5f, 0xb2fa, 0x41be, 0x3f24, 0xbfb0, 0x426b, 0xbf05, 0x2ee5, 0xbade, 0x43f9, 0x43ed, 0x4195, 0x4176, 0x4294, 0x4286, 0x00e1, 0xacdb, 0x1e04, 0x1f46, 0xbfc4, 0xa533, 0x4165, 0x41a6, 0x4344, 0xbf97, 0xba71, 0xa713, 0xbafc, 0x40a1, 0x00da, 0xb0c8, 0xba61, 0x4161, 0x1da3, 0x4053, 0x19d0, 0x40dc, 0x4036, 0x4075, 0x40a6, 0xab72, 0x08d1, 0xb21b, 0xaddc, 0x416e, 0x1c32, 0x41b8, 0x0b12, 0x1434, 0xb042, 0x408a, 0xb24d, 0x1743, 0xb287, 0xbf37, 0x40cd, 0x417f, 0x34ae, 0x427e, 0x42d5, 0x4039, 0x360f, 0x413f, 0x3864, 0x27c9, 0x40f8, 0x4318, 0x422e, 0x3c63, 0x1bd9, 0x424c, 0x1384, 0x1c1e, 0xa82e, 0xbfc8, 0xa049, 0x079e, 0xa832, 0x46e8, 0xba13, 0xbfc0, 0x1af3, 0xb2a3, 0xb074, 0x19b9, 0x2c75, 0xbacf, 0x3dca, 0x0f0b, 0x2979, 0x4007, 0x43be, 0xba62, 0xbadc, 0xbf87, 0x125a, 0x435f, 0x1c1a, 0x13ab, 0x42b7, 0xbf70, 0x42cc, 0x1978, 0x41b2, 0xba50, 0x42be, 0x4346, 0x4336, 0xb2a5, 0x45bd, 0xa15a, 0x2038, 0xba4c, 0x1a84, 0x430b, 0xb2b4, 0xbfae, 0x41c1, 0x42d3, 0x1ef4, 0xb01a, 0x1c63, 0x210b, 0xb272, 0xa49d, 0x1be2, 0x18b4, 0x1c08, 0xa4d3, 0x1f6b, 0x113c, 0x4663, 0x41c6, 0x4247, 0x402f, 0x4379, 0x0949, 0x4008, 0x42c6, 0x41fc, 0x0b89, 0xbf7c, 0x4370, 0x422d, 0x4362, 0x0b78, 0x040c, 0x43da, 0x462d, 0xb251, 0x1e56, 0x4123, 0xaaef, 0x43a6, 0x0408, 0x2a06, 0xb276, 0xb091, 0x42a4, 0x4319, 0x3ce5, 0xb297, 0x40df, 0xba2d, 0x3233, 0x29dc, 0x407f, 0xbf4a, 0x4685, 0xba15, 0x1404, 0xba06, 0x42a9, 0x262a, 0x3e76, 0x0ba0, 0xb2e8, 0x4482, 0x4047, 0xb037, 0xb06b, 0x05a4, 0x40ad, 0x435d, 0x4057, 0x4280, 0x4049, 0x42b3, 0xbf6d, 0xba7a, 0x42f6, 0x0984, 0x2d9f, 0x44d5, 0x4374, 0x4047, 0x1a23, 0x435b, 0x1f11, 0xb213, 0xba1a, 0x4030, 0x43ca, 0x0f9c, 0x4682, 0xaed1, 0x45be, 0x42eb, 0x41ef, 0xa19a, 0xa571, 0x43e4, 0xbf4c, 0x4255, 0x43bc, 0x40d9, 0x0a28, 0x2786, 0xbf33, 0x42c9, 0x1b40, 0xb2c6, 0xb0a8, 0xafac, 0x40a8, 0x1c68, 0x4184, 0x1420, 0xb2d5, 0xbf34, 0x433a, 0xb24b, 0x45e4, 0x0183, 0xb22d, 0xb091, 0xb2cf, 0x4038, 0x41a3, 0xbf60, 0x40ef, 0x4360, 0x45c4, 0xb080, 0x339d, 0xba16, 0x4115, 0xbf96, 0x406e, 0xaf42, 0xb28f, 0x413d, 0xbfbb, 0x4146, 0xb038, 0x4285, 0x216e, 0xb264, 0x431e, 0x42b9, 0x2f56, 0x0e0c, 0x4217, 0xa12d, 0xac5c, 0x4328, 0x40a2, 0x1a29, 0xb0e9, 0x442f, 0x41bc, 0x37c6, 0xb0cc, 0x18ce, 0xb042, 0x1b4e, 0xbf90, 0xbf9d, 0x419f, 0x414f, 0x1eb1, 0x1f9e, 0x4294, 0xa197, 0x1896, 0xbaf5, 0x4272, 0x45c8, 0x0da9, 0x424c, 0x2c62, 0x4022, 0x1bfa, 0x4091, 0x0417, 0xb099, 0xba6b, 0x40c9, 0x1d35, 0x43c1, 0xb0e2, 0x443b, 0x4137, 0x0c33, 0x418c, 0xbf7e, 0x4196, 0xba14, 0x4585, 0xbfca, 0x41f0, 0x3bb0, 0xb044, 0xb2e7, 0x2b49, 0x4428, 0xbfca, 0xb092, 0x4051, 0x4201, 0x1a8d, 0xbf90, 0xba5c, 0xb0bf, 0x4151, 0x19c8, 0xb293, 0x437a, 0xb2a9, 0xbace, 0xbf71, 0x40da, 0xb24e, 0x0bb8, 0x1879, 0xba3b, 0x402b, 0x1e0d, 0xbad2, 0xb204, 0xba07, 0x3165, 0xb08b, 0x3c16, 0x1e6d, 0x40f9, 0x1379, 0x17f9, 0xaf70, 0x4287, 0xbf2b, 0xbaf2, 0xba11, 0x41e7, 0x0360, 0xb237, 0x43d9, 0x1db8, 0xba48, 0xba5d, 0xbac7, 0xbfdd, 0x0be8, 0x1c5b, 0x20b0, 0x4099, 0x3a34, 0x4254, 0xb236, 0x419a, 0x1b31, 0xbf2d, 0x42a2, 0x4271, 0xbad5, 0xb254, 0x1bb2, 0x43b2, 0xb2b2, 0xbae9, 0xb22b, 0xa066, 0xba09, 0x4028, 0x400a, 0xb212, 0x42a3, 0x4047, 0xbf4a, 0x2bc0, 0xa346, 0x40a3, 0x42ee, 0x138f, 0x1b40, 0xba12, 0x1b0e, 0xa916, 0xb241, 0x400b, 0xbfdb, 0xba28, 0x41a1, 0x4390, 0x3d2b, 0x443e, 0x4673, 0x1d4d, 0x4120, 0x4269, 0x419d, 0x46f2, 0x4682, 0x41c5, 0xb2d2, 0xb234, 0x4036, 0xbf7e, 0xba39, 0xb2e8, 0x466f, 0x441e, 0x1b36, 0x3f5d, 0xba69, 0x20de, 0x1fdf, 0x18d0, 0x095f, 0x416b, 0x437d, 0x4331, 0x40de, 0x0e6a, 0x353b, 0x13c7, 0x4221, 0x437c, 0x1f97, 0x3a6d, 0x435d, 0x42cc, 0x43c4, 0x310b, 0x428c, 0x2383, 0xbfc7, 0xb0c1, 0x1fd1, 0xa1ce, 0x41d6, 0x45a6, 0xb275, 0x4257, 0xb2b3, 0x43ab, 0x4278, 0x1ecd, 0x359e, 0xb2e9, 0xba0c, 0xbad3, 0x1dc2, 0x419e, 0x424a, 0x41aa, 0xb2c1, 0x44e5, 0x40f0, 0x1778, 0x4088, 0xbfd6, 0x4246, 0x4197, 0x1c5c, 0x1b2e, 0xb28b, 0x0f3d, 0x1e81, 0x4122, 0x2e73, 0x1db5, 0x42fa, 0x1d43, 0x43f2, 0xba18, 0x0068, 0x42ff, 0x0dd0, 0x4247, 0x45ce, 0xb2ba, 0xa73d, 0x428f, 0x4329, 0xb225, 0xbf62, 0xba24, 0x43a0, 0x204c, 0x1466, 0xb2dc, 0x3f46, 0x42a7, 0x43a4, 0xba61, 0xb0f1, 0x40bb, 0x40c9, 0x4192, 0x4113, 0x1d47, 0x448d, 0xaa2a, 0xbaf6, 0xb2db, 0x4294, 0x4206, 0x0429, 0xb2b2, 0x41ee, 0x4081, 0x44b3, 0xbfd1, 0xbadd, 0x424e, 0x2a0e, 0x334e, 0x41f0, 0x1b77, 0xbae8, 0x4067, 0x42eb, 0x00b1, 0x423e, 0x4073, 0x4106, 0x40a9, 0x416b, 0x128f, 0x0281, 0xba3c, 0xbac5, 0x0c2f, 0xb012, 0x1c56, 0x40a6, 0x4096, 0x40e4, 0x42b5, 0xb090, 0x42ea, 0xbf49, 0x4287, 0x4051, 0x43d7, 0xba1d, 0x41dc, 0x4161, 0x2296, 0x4251, 0x4166, 0x43ad, 0xba47, 0x1b9f, 0xb026, 0xb2d0, 0xb2ec, 0x420d, 0x4355, 0x434c, 0xb08f, 0xb252, 0x1b04, 0xa364, 0xbafc, 0x19a4, 0x3c98, 0xbf9f, 0x41c0, 0x34d7, 0x456e, 0x42d5, 0xb264, 0x43df, 0x4542, 0xba15, 0x40de, 0x42d1, 0x1a20, 0xbf2f, 0x4103, 0xacdc, 0x40cc, 0xb099, 0x43eb, 0x43df, 0x317c, 0xba70, 0x1e2c, 0xbf84, 0x4158, 0x1c12, 0x40cb, 0x46d1, 0x408a, 0x037c, 0xb071, 0x4347, 0x4320, 0x25fa, 0x4078, 0xb25d, 0x1e50, 0xa645, 0x2831, 0xbf51, 0x4092, 0x19b4, 0xa3f1, 0xbada, 0x4036, 0x42a7, 0x09ac, 0x1f55, 0xb247, 0x2753, 0x426b, 0x40a5, 0x4223, 0x40f3, 0xbfae, 0x292e, 0x1f9b, 0x46cc, 0x181d, 0x40ca, 0x413f, 0x0b7e, 0x423a, 0xb264, 0x43b4, 0x214a, 0xad2e, 0x215a, 0xba7f, 0x409b, 0x4568, 0xaf04, 0x2c49, 0xa536, 0x425e, 0x42fe, 0xa0ba, 0x4107, 0xbf77, 0xb019, 0x1640, 0x4336, 0x2631, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x7f732ac7, 0xd652158c, 0xc810dc10, 0x451cfb90, 0x698e290f, 0x67d2fe9c, 0xfa98fc70, 0xc792f08a, 0x938d63b5, 0xf0a64fe6, 0xd6444518, 0x197a89a8, 0x8b76fcf7, 0x660046dd, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x00001ab4, 0x0000005a, 0x00000000, 0x00000000, 0x00000000, 0x0000189c, 0x00000031, 0xffffffff, 0x000000ac, 0x00000000, 0x00000000, 0x197a89a7, 0x00000000, 0x8b76fcc7, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4143, 0x4476, 0x3a03, 0x419e, 0x43b2, 0x0275, 0x1d6f, 0x4247, 0x28b0, 0x412b, 0x405c, 0x4317, 0xb2f8, 0xb0d0, 0x4669, 0xbf17, 0xb2cf, 0x2c9b, 0x0e98, 0xba5c, 0x4230, 0x2d3b, 0x28af, 0x46bd, 0x423d, 0x429e, 0xbae4, 0x4683, 0xba7e, 0x4359, 0xb0cb, 0xbfcd, 0x0cd3, 0xa0ac, 0x41a5, 0x1bcb, 0xba6f, 0x44f1, 0x4058, 0x0d71, 0x46cb, 0xbfb0, 0x4310, 0x0701, 0x4026, 0x4320, 0xb2fe, 0x4317, 0xb28d, 0xb269, 0xb2d7, 0x43f5, 0xbaed, 0x40d1, 0x42cd, 0xbfdc, 0x075b, 0x4183, 0x12da, 0x4646, 0xb258, 0x2fa1, 0xba16, 0xba7f, 0xb2f9, 0xa1b7, 0x4198, 0x3436, 0x428a, 0x41da, 0x0f61, 0x43c0, 0xb2a7, 0xba6e, 0xb210, 0x1820, 0x1923, 0xb278, 0x4136, 0x2035, 0x429e, 0xb2c2, 0xbf77, 0xb260, 0x4616, 0x1d26, 0x4176, 0x42fc, 0x43c9, 0xa2cf, 0x454d, 0x4342, 0x2e4c, 0xbf16, 0xb09b, 0xbaea, 0x438f, 0xbf65, 0xb0a5, 0xa6ba, 0x1f3d, 0x415f, 0xb296, 0x4113, 0x03e8, 0x2fa2, 0x19e9, 0x2348, 0x44e3, 0xb0d0, 0x43b4, 0x28e6, 0x1b96, 0x41d4, 0x0f21, 0x439d, 0xb07b, 0xbf74, 0x3b03, 0xba3f, 0x1942, 0x459e, 0x194f, 0xb0a7, 0x174e, 0xbf92, 0xb00d, 0x3171, 0x1f1d, 0x436c, 0xbf86, 0x15ba, 0x3e2f, 0x1711, 0x4351, 0x2b18, 0x418b, 0x0a50, 0x40a0, 0xba20, 0x4154, 0x4174, 0x1d34, 0x3da5, 0x41f4, 0x403a, 0x4201, 0xba51, 0x33d0, 0x1823, 0xb0b7, 0x4396, 0x40c1, 0xbf96, 0xb212, 0x23d4, 0xa987, 0x4344, 0x3df9, 0x32bf, 0x4082, 0xb297, 0x4174, 0xac14, 0x4297, 0xb2e2, 0xb2e8, 0xbfaa, 0x429e, 0xb0b8, 0x465f, 0xba46, 0x433d, 0xa49c, 0x432d, 0x3e9f, 0x1e23, 0xb2a2, 0xa5aa, 0xb0cf, 0xba2f, 0x41f9, 0xbfce, 0x41a6, 0x41af, 0x29a7, 0xbf59, 0xb004, 0x4003, 0xbf00, 0x3449, 0x40b1, 0x116e, 0xb027, 0xb25c, 0xabd8, 0x1bad, 0x0806, 0x0827, 0x46e1, 0x41b0, 0x419d, 0x1e88, 0xb213, 0xbad2, 0x341a, 0xafb2, 0x4416, 0x429e, 0x2857, 0x40a7, 0xba0c, 0x4205, 0x0eeb, 0x43b9, 0xbf2d, 0x41e5, 0x1a16, 0x45a0, 0x423c, 0xb00d, 0x1f94, 0xbae0, 0xbf1b, 0x42d0, 0xb071, 0x4345, 0x4340, 0x41ab, 0xb067, 0x3432, 0xbf1d, 0xb2ef, 0xb061, 0xba7f, 0x3047, 0xa46e, 0x429e, 0xbaf6, 0x41b3, 0x45ec, 0xb20a, 0xb2d5, 0x4360, 0x2af9, 0xb061, 0x2ed2, 0x1329, 0x1c60, 0x4151, 0x1351, 0x465d, 0x40f1, 0x4388, 0x408b, 0xba1e, 0xbfd0, 0xbfb6, 0x4132, 0x074b, 0x18de, 0x420a, 0x43ec, 0xb2e0, 0x4319, 0x41a9, 0xb0e2, 0x4190, 0x400d, 0x4159, 0xba5f, 0xa852, 0x4346, 0xb0b5, 0xbf44, 0xa4e4, 0x1b56, 0x1a5d, 0xba77, 0xbae3, 0xb2be, 0x405a, 0x41e3, 0x3cc6, 0x1ebf, 0x19c2, 0x41ed, 0x4498, 0x1c91, 0x3ae6, 0x42b5, 0xb2e3, 0x18f6, 0xaa8b, 0x45d8, 0x414a, 0xbf6a, 0x4172, 0x4017, 0x1a6f, 0x45d8, 0xa35e, 0x4259, 0xba00, 0x1ede, 0x4125, 0xba04, 0xb2bc, 0x419b, 0x18ef, 0xb20b, 0x18bc, 0x449a, 0xbad5, 0x40b3, 0x42d1, 0x2138, 0x1f04, 0xb266, 0x410f, 0xb04f, 0xba03, 0xbf44, 0x1a63, 0xb276, 0xb2ff, 0x408e, 0x2b75, 0x468d, 0x402e, 0x4265, 0x1e0a, 0x1e17, 0xba0d, 0x0383, 0x44d5, 0x1333, 0x1bf7, 0x408e, 0x0079, 0x4227, 0x43ba, 0x41f2, 0xa9a9, 0x4072, 0x42ca, 0x124f, 0xba40, 0xba14, 0x4146, 0xbf03, 0x426f, 0xba4f, 0xba49, 0xb28b, 0x4368, 0xbf47, 0x4315, 0x3f70, 0x43ed, 0x310d, 0xb013, 0xbacc, 0xbfc0, 0xbf90, 0x4272, 0xbf85, 0xb06d, 0x4162, 0x4155, 0x1d1a, 0xa425, 0x08d3, 0x1f77, 0xb0a4, 0x1e2b, 0xbf00, 0x40f9, 0x2f9d, 0x3eea, 0xb035, 0x42a1, 0xbaf2, 0x2410, 0xa6e0, 0xbf6f, 0x4335, 0x4319, 0x43bc, 0x1f9b, 0x41e0, 0x1b46, 0xbfe0, 0x4297, 0x4177, 0x19ef, 0x42bb, 0x4075, 0x40d4, 0x42cb, 0x1db4, 0xb251, 0x400a, 0xbfa3, 0x441c, 0xb2ce, 0x4187, 0x4365, 0xba30, 0x43a9, 0x42b6, 0x4330, 0x41eb, 0x1341, 0xb016, 0xba5b, 0x4208, 0xbff0, 0x42ba, 0x4001, 0x42b4, 0xb243, 0xb2bd, 0x1cca, 0x420a, 0xa277, 0x4129, 0x40c6, 0x187b, 0x3b6a, 0xbfc9, 0x4083, 0xb2a0, 0x07bb, 0x413d, 0x4116, 0xae4f, 0xb097, 0x39d7, 0xba11, 0x43d3, 0x42b9, 0x2daf, 0x43cd, 0xb21e, 0x4185, 0x4203, 0xba2e, 0xb21a, 0x4095, 0xbf2d, 0x4230, 0x42c8, 0x4277, 0x1e62, 0x19ae, 0x4309, 0xba43, 0xba09, 0x415b, 0x40b8, 0x4271, 0xaf78, 0xb298, 0x434f, 0x2fe1, 0x414a, 0x43b1, 0xbf72, 0x4043, 0xbff0, 0x0c11, 0x4161, 0x40e6, 0x191d, 0xb222, 0xa0d6, 0x4247, 0x2b58, 0x1941, 0x4157, 0x4223, 0xac08, 0xb0a0, 0x1aa4, 0x42fa, 0x18bb, 0x4080, 0x1a11, 0xb26c, 0x3e43, 0x4089, 0x41ac, 0xba1c, 0x4326, 0x42b3, 0xbf9c, 0x21eb, 0xba4a, 0xbf3e, 0x0c75, 0x2d1e, 0x43a7, 0xb200, 0x4005, 0x42a7, 0x1848, 0xb2db, 0x05fb, 0xbae2, 0xbf65, 0x407e, 0xb2f1, 0x418a, 0x43db, 0xb2a6, 0x4244, 0xbf2a, 0xa9d3, 0x0194, 0x42dd, 0x25d1, 0xb2d1, 0x41f3, 0x42da, 0x3808, 0x412a, 0xb287, 0x44d3, 0x1977, 0x41c2, 0x1091, 0xbf28, 0xbae4, 0x4144, 0xba62, 0x4643, 0x40f8, 0x4194, 0x1bec, 0x407a, 0xb0f3, 0xb234, 0xba45, 0xb0d3, 0xba4e, 0x404e, 0xbf76, 0xb2e2, 0xbaec, 0x4665, 0xaa4c, 0xb20e, 0xba08, 0xa8b3, 0xb072, 0x1b10, 0xbacb, 0xb064, 0x41ef, 0x44f2, 0xaaa2, 0x4362, 0x1eb0, 0xa948, 0x4236, 0x46c4, 0x1b32, 0x40f1, 0x2641, 0xab44, 0x4222, 0x42be, 0x1bc0, 0x4639, 0x4135, 0xbfe4, 0x434a, 0xba33, 0x1ee0, 0xb229, 0xb06d, 0x44e9, 0x04af, 0x40d1, 0x4456, 0x46e9, 0x4112, 0x18ce, 0x2066, 0xbfe8, 0x4240, 0xbaf0, 0xbfcf, 0x4691, 0x079d, 0x438f, 0x406e, 0x4075, 0x2821, 0xa06e, 0x4167, 0x4385, 0x3a13, 0xbac8, 0x45e4, 0x429d, 0x42b0, 0xb20b, 0xbfb1, 0xa2ee, 0x35d9, 0x1d65, 0x152c, 0xb04e, 0x0f48, 0x254e, 0x1ce5, 0x2e0f, 0x3304, 0xb0bf, 0xb2de, 0x1c11, 0x1e33, 0x1957, 0xbf60, 0x19b6, 0x400b, 0x401a, 0xbfd9, 0x4621, 0x19a6, 0x2a35, 0x26e2, 0xb294, 0x412b, 0x4165, 0x1b4f, 0xad1b, 0x2f76, 0x1a42, 0xbf65, 0x3406, 0xb20a, 0x43f5, 0xba32, 0x45db, 0x23fe, 0xbad4, 0x1ab0, 0x222a, 0xb22a, 0xb206, 0x43cd, 0x13e5, 0x1c19, 0x2ac8, 0x268a, 0x4319, 0x1c7b, 0x1f02, 0x45f3, 0xb25c, 0x400e, 0xa29c, 0xb041, 0xbfd2, 0x4261, 0xb0dc, 0xa298, 0x447b, 0xbfc3, 0x2c44, 0xba23, 0x41f3, 0x43a1, 0x41ec, 0x43ad, 0x41e2, 0x401d, 0xb096, 0x40dd, 0x3336, 0x415c, 0x4037, 0x42e5, 0x4323, 0xb257, 0xb2a7, 0x1cc7, 0xba28, 0x40de, 0x40dd, 0x4069, 0x1bda, 0x46e4, 0xbf4d, 0x010c, 0x1533, 0x41f8, 0x42c3, 0x41ff, 0x14cd, 0x4291, 0x1da6, 0x16e2, 0x3752, 0xafe8, 0x4192, 0x46c4, 0x4204, 0x40b8, 0x222c, 0x23e3, 0xaca3, 0xbf5c, 0x02c8, 0x4219, 0xb078, 0x205d, 0x4267, 0x41c0, 0xba36, 0x1c8d, 0x35be, 0xb2d4, 0xb2fe, 0x40e7, 0x404f, 0x43a1, 0x3017, 0x0f83, 0xb08b, 0x40ff, 0xbf68, 0x40f7, 0x2d84, 0x4072, 0xb2a9, 0x089e, 0x1c2f, 0x1ccf, 0x412c, 0xba2b, 0x1cda, 0x19d2, 0xb04a, 0xb053, 0x2419, 0x422f, 0x4269, 0x1997, 0x0701, 0x1326, 0x0e29, 0x4105, 0x425b, 0x4111, 0x4677, 0x4373, 0xbf6c, 0x1ad8, 0x4305, 0xba7c, 0x064b, 0x34a4, 0x4241, 0xb2b5, 0xbf0e, 0x4282, 0x187e, 0xbaf4, 0x460d, 0x417c, 0xba19, 0xbf90, 0x1e52, 0x42f2, 0x41c9, 0x23d2, 0xb221, 0xb2cc, 0x1f32, 0xba0d, 0x43b3, 0x408e, 0x4040, 0x43d5, 0xb05f, 0x40bb, 0x44f8, 0x179c, 0xa95e, 0x18de, 0xb229, 0x42ce, 0x4310, 0xbf45, 0x426a, 0xab45, 0x4339, 0x40a8, 0xabbe, 0x43d3, 0x3ba2, 0x1a98, 0x43f4, 0x4364, 0xaa92, 0x1878, 0xaf21, 0x4422, 0xa6c1, 0xa127, 0xba16, 0x4032, 0x1ed2, 0x4354, 0x46c2, 0x4275, 0x3beb, 0xb240, 0x4029, 0x417c, 0x41a4, 0x42c1, 0xbf5b, 0x411a, 0x4134, 0x3a11, 0x39e1, 0x2cc5, 0xb0f1, 0x35af, 0xb2b3, 0xb214, 0x4312, 0xbfc3, 0x4151, 0xb227, 0x4190, 0xa889, 0x402e, 0xaa1c, 0x4236, 0xbf72, 0x1ab4, 0x40f2, 0xab93, 0x33d2, 0xb26d, 0xa602, 0xb090, 0xb0af, 0x4333, 0xba5e, 0x4036, 0x411a, 0x3860, 0xb2dd, 0x4322, 0x41f9, 0xbff0, 0x031c, 0x4093, 0xb05a, 0x4284, 0x40fe, 0xb031, 0x31d3, 0xb060, 0xbf7f, 0xb02b, 0xb254, 0x42e4, 0xb247, 0xbf00, 0xb221, 0xb040, 0xb2e4, 0x40b1, 0x1dff, 0x400c, 0xba6e, 0x4457, 0x1e3f, 0xb2bf, 0x40de, 0xbfc3, 0x1e09, 0x42ed, 0xb2a3, 0xba5d, 0xbfe0, 0xba5f, 0xba77, 0x1976, 0x42c1, 0xb278, 0x46e5, 0x40b7, 0x4495, 0x43b4, 0xb00e, 0x40f8, 0x27fa, 0x4170, 0x422a, 0x411e, 0x1b39, 0x41e0, 0x447f, 0xbf94, 0xa87e, 0x41a6, 0x45f5, 0xb204, 0x43bb, 0xb270, 0xb0f6, 0xb0f3, 0x0fcf, 0x2554, 0xb202, 0x4009, 0x4056, 0x37e1, 0x1d32, 0x41e1, 0x1988, 0xba4e, 0x468b, 0x4186, 0x42d5, 0x42a4, 0xbf21, 0x3a5e, 0xbff0, 0x13c6, 0x2352, 0x1def, 0xb26e, 0x41ed, 0x429b, 0x4253, 0xb2d1, 0x4180, 0xbf6f, 0x427b, 0x0ff2, 0xa388, 0x42e5, 0xb2aa, 0x1c71, 0x429b, 0xb072, 0xb22d, 0xb211, 0xb066, 0x173e, 0x1d80, 0x4253, 0xa2d6, 0x4033, 0x404a, 0xb2a5, 0x413b, 0xbade, 0x417c, 0x40c2, 0xb257, 0x1fd5, 0x0bb8, 0x42aa, 0x1d98, 0x43e6, 0xbfba, 0x1e34, 0x020a, 0xb238, 0x3c80, 0x4023, 0x4000, 0x437c, 0xbf80, 0x15e7, 0x1366, 0x42d7, 0x44bd, 0xbf2b, 0x4274, 0x28cf, 0x4376, 0x46f5, 0x0319, 0x465b, 0x4087, 0x4106, 0x4135, 0x0ef5, 0xb2e9, 0xb07d, 0x415a, 0xba1f, 0x4368, 0x40af, 0x469c, 0x41bf, 0x2d0c, 0x4305, 0x4222, 0x41e3, 0xbf98, 0x4649, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xbb7d36f6, 0x99698622, 0x49d98852, 0x36cb9669, 0x824878bf, 0xc93e95e5, 0x63323f2d, 0xc8f7f318, 0x96c967ec, 0x5a2d76e7, 0x8dfbec30, 0x6fa00b5f, 0x2d920f82, 0x5d28d234, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x00000000, 0x004003e8, 0xfa000000, 0x000022ea, 0x00000000, 0x00000000, 0xffffffff, 0x9681941b, 0x00000000, 0x9681941b, 0x000003e8, 0x000003e8, 0x000001f4, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x43de, 0x4327, 0xb263, 0xa931, 0x4228, 0xb061, 0xbac6, 0x434d, 0xa868, 0xbf90, 0xba1c, 0x43bf, 0xbf70, 0x4229, 0x42b0, 0x40f0, 0x32dd, 0xba5d, 0xbf85, 0x43a4, 0xb2af, 0xb2b1, 0x4021, 0x2d77, 0xa666, 0x4176, 0x4089, 0x426e, 0x41de, 0x4345, 0x400b, 0x4056, 0x1bee, 0x19d7, 0xa0e7, 0x40b1, 0x2328, 0xbf06, 0xa385, 0x465d, 0xb277, 0x4664, 0x1a4b, 0xbad8, 0x0428, 0x0685, 0x4028, 0x300a, 0x4349, 0xba0f, 0xb2e3, 0x4662, 0x40d7, 0x435e, 0x0b39, 0x421f, 0x4670, 0xb2d6, 0x17aa, 0x4652, 0x4688, 0x1d08, 0x0544, 0x1fd9, 0xbfb3, 0x4166, 0x40ef, 0xbff0, 0xb0c9, 0x047f, 0x4063, 0x1011, 0x1f38, 0x4475, 0x427b, 0x4149, 0xba6c, 0x428f, 0x43da, 0xb2c2, 0x1f1a, 0x40d0, 0x4283, 0x1b46, 0x2aad, 0xb26d, 0x42b6, 0xa275, 0xba02, 0x40bf, 0x047c, 0xb2de, 0xbf60, 0x41a9, 0xbf28, 0x43ae, 0xba2b, 0x1dbc, 0x0735, 0x4234, 0x408b, 0xba25, 0x1086, 0x420d, 0xb074, 0xb290, 0x41e9, 0x410d, 0x4256, 0x42a2, 0x40bd, 0xabce, 0x4380, 0x4015, 0x4169, 0x405d, 0x3fbd, 0xb253, 0x433a, 0xbfd7, 0x41b2, 0x15be, 0x188a, 0x0fc1, 0x2952, 0x35a4, 0x40f1, 0x2a4c, 0xbfbd, 0xb2e4, 0xb23a, 0x1f28, 0x4630, 0x388c, 0x4147, 0x40f0, 0x4567, 0xbac7, 0xbfa0, 0x436c, 0x3e60, 0xb2d1, 0xb0e4, 0xb26a, 0x1830, 0xba33, 0xb26f, 0x2b61, 0x400b, 0x4346, 0xb069, 0x4039, 0x0aaa, 0xba75, 0x43d5, 0x417b, 0x42ec, 0xae57, 0xbf6e, 0x41cf, 0x3075, 0xb29b, 0x15ef, 0x1b99, 0x435d, 0xb215, 0x42fd, 0x4234, 0x2e88, 0xba7d, 0xbade, 0xba31, 0xbad9, 0xa2a3, 0x42f1, 0x1bb3, 0xbafa, 0xa940, 0x40a6, 0x3689, 0xb2ba, 0xbfaf, 0x4052, 0x1a92, 0xb2be, 0x462e, 0x41d1, 0x4145, 0x1619, 0x46c2, 0x3167, 0x409e, 0xba38, 0xb20f, 0xbacc, 0xb238, 0x4297, 0xbfa1, 0xbaf4, 0x38a8, 0xb293, 0xa847, 0x4138, 0x4175, 0x40fa, 0x259a, 0x4244, 0x4029, 0x3497, 0x15f3, 0xb07c, 0xbacb, 0x433f, 0x42e5, 0x3baf, 0x4029, 0x1aab, 0x20de, 0xa582, 0xbf87, 0xb2ff, 0x4098, 0xbace, 0x415c, 0x40cf, 0xb272, 0xbfdf, 0x40e0, 0x42dd, 0x4122, 0x41ba, 0xb2e2, 0x2632, 0x4153, 0xbf7d, 0x3131, 0x3b4d, 0x43ba, 0x41ab, 0x3b6c, 0xb2b3, 0x31e5, 0xba05, 0xb268, 0x1431, 0xba04, 0x4556, 0x43ab, 0x421c, 0xb2a4, 0xb23c, 0x410e, 0x42c9, 0x4063, 0x1da5, 0x402f, 0xb288, 0x0b26, 0x0570, 0xb03b, 0x4260, 0x4096, 0xbf8e, 0x10ad, 0x3f3a, 0x418e, 0x0b8b, 0x2f4f, 0x405a, 0xb00a, 0xb07e, 0x04ce, 0x41fa, 0x4271, 0x30c8, 0x40e5, 0x43f5, 0x3887, 0xa453, 0x4608, 0xbf71, 0x0220, 0x4540, 0x4188, 0xbff0, 0x434c, 0x3c0b, 0x41eb, 0x2d26, 0x4675, 0x4149, 0x4614, 0xb238, 0x308c, 0x4005, 0xbf5f, 0x4041, 0x0a0d, 0xb258, 0x43f4, 0x4316, 0x4170, 0xb2dc, 0xbfb6, 0x2875, 0xb052, 0x40dd, 0xbfb4, 0x4208, 0xba45, 0x409b, 0x4385, 0xa8f3, 0xb213, 0x42cb, 0x3f92, 0x3ae4, 0xb2f3, 0x3366, 0xb236, 0x4399, 0x400d, 0x4128, 0x4419, 0x34de, 0xa079, 0x43d7, 0xbaf8, 0x4277, 0x0594, 0x42fd, 0x4413, 0x43fe, 0xbf78, 0xb29b, 0x41ee, 0x185b, 0x19f6, 0xba51, 0x4012, 0x44d8, 0x05fc, 0xa2ea, 0xb2f6, 0x415a, 0xb2a7, 0x310d, 0x19e3, 0x408f, 0x19c7, 0xa5d4, 0xb05a, 0x1485, 0x4162, 0x41f0, 0x4200, 0x402a, 0xbf49, 0x0fdf, 0x185a, 0x1e02, 0xb2ab, 0xba13, 0xbaca, 0x46ed, 0x1e91, 0x42f5, 0xbf77, 0x459b, 0x42ae, 0x40eb, 0x19c3, 0x4213, 0x2acb, 0x1080, 0x41c1, 0x42fb, 0xb211, 0xab6c, 0x309e, 0xba6e, 0xb03e, 0x43fa, 0x42c5, 0xa06c, 0x1777, 0x3986, 0xbf51, 0x40d2, 0x4691, 0x22f9, 0x43fe, 0x40c2, 0x4215, 0xb028, 0x43a4, 0x42ae, 0xb2bd, 0x429e, 0x433d, 0x42e7, 0x42f5, 0xa94c, 0xbfaa, 0x34b2, 0xba3d, 0x424d, 0x4291, 0x4120, 0x1bb9, 0x43c2, 0x1ff7, 0x40fa, 0x342f, 0x43fe, 0x42bf, 0x433e, 0xbfb5, 0x4076, 0x3aaa, 0x4158, 0xb2bf, 0x40d7, 0x4068, 0xbf6f, 0xb036, 0x40ed, 0xbaec, 0x18fe, 0x00f2, 0xbadd, 0x439b, 0xa23d, 0x42a5, 0x411a, 0x41d8, 0x16cf, 0xba28, 0xbfe2, 0xba20, 0x43cf, 0xb2ae, 0xb2bc, 0xb2f7, 0x448c, 0x43dc, 0x43ae, 0xb2a8, 0xa5a6, 0x4216, 0x4295, 0x344b, 0x1da4, 0x4273, 0x408f, 0xb21d, 0xa9a9, 0x43db, 0x42b0, 0xbf5a, 0x4640, 0xb20a, 0xa7bb, 0x417c, 0xb26b, 0xbfd7, 0x1e2c, 0x4250, 0xb053, 0x4071, 0x424f, 0xb0d4, 0x4373, 0x429a, 0xba12, 0x4078, 0xba4e, 0x2762, 0xa5a4, 0xb200, 0x44b3, 0x2257, 0x0339, 0x42b5, 0x1a8f, 0x45b2, 0x4033, 0xbaed, 0x1f10, 0x1b45, 0xbae3, 0x3bae, 0x40d9, 0x03ff, 0x4175, 0xbf45, 0x32f9, 0x4053, 0x3ad1, 0x27bf, 0x40c4, 0xbf60, 0x40d2, 0x4202, 0x41f2, 0xb2be, 0x4341, 0xb25c, 0x1b18, 0x40bd, 0xb089, 0xbae2, 0xbf5f, 0x4403, 0x4607, 0xa684, 0xa2f3, 0x40ae, 0xba3b, 0x4358, 0x422c, 0x4167, 0x40a7, 0x4215, 0x4440, 0xb219, 0xbfd0, 0x40d5, 0x4038, 0xb2ca, 0x42e9, 0x43af, 0x42cb, 0x413c, 0x4158, 0x44a3, 0x422f, 0xbadf, 0xb279, 0xb286, 0x1f1e, 0xbf09, 0x4319, 0xba13, 0xba31, 0xa86a, 0x4367, 0xbf00, 0xb0d8, 0x423f, 0x413a, 0x44d5, 0xbac4, 0xad13, 0x4093, 0x4149, 0x41fb, 0x422b, 0x1e34, 0x3f5e, 0xb246, 0x41b8, 0xbf70, 0x435d, 0xbfdd, 0x01b2, 0x420b, 0xbfc0, 0x464f, 0xb034, 0x1c2d, 0x1a10, 0xbfd5, 0xa298, 0x412d, 0x41a3, 0x42de, 0x42b2, 0xba1e, 0xb298, 0x4170, 0x1827, 0x4093, 0xb015, 0xba4a, 0x2cf0, 0xb2d9, 0xb067, 0x0c34, 0x35ed, 0x3b0a, 0x085a, 0x46ab, 0x158d, 0x3889, 0x42ab, 0x1c3a, 0xbfc6, 0x372b, 0x17bc, 0x3899, 0x4611, 0x1a8a, 0xbf5d, 0x44d8, 0xb042, 0x1ab4, 0xb0ac, 0x438a, 0x41af, 0xba06, 0xb25e, 0x164a, 0x4067, 0xb01c, 0x43eb, 0xbf2e, 0x1b94, 0xba3c, 0x1494, 0xbf77, 0x41da, 0x09e3, 0xbadc, 0xa22f, 0x3fcc, 0x1f3d, 0xba4a, 0x400a, 0x432e, 0xb289, 0x1b35, 0xb07e, 0x3cc2, 0x428c, 0x2025, 0xafee, 0x251a, 0xbfb3, 0x44cb, 0x45a8, 0xb294, 0x4328, 0xb08c, 0xb0b4, 0xba26, 0x4097, 0xaa3d, 0x40a6, 0x434b, 0x4560, 0x4660, 0x1f5f, 0x0839, 0x42f0, 0xb2b9, 0x465b, 0x41b9, 0x194f, 0x405a, 0x400d, 0xa719, 0x2c50, 0x1e4e, 0xb242, 0xba30, 0x42ae, 0xb24f, 0xbf43, 0x4681, 0x1a48, 0x1d26, 0xb0fd, 0x4004, 0x423e, 0xa9f0, 0xba40, 0xbaff, 0x43e1, 0xa99b, 0x4432, 0x41f4, 0xbf26, 0x0a13, 0x1821, 0x2ba4, 0x44b9, 0xba22, 0x322b, 0x1c9a, 0x432c, 0xba70, 0xb234, 0x3ca7, 0x4547, 0x4078, 0x4144, 0x40ec, 0xad9a, 0x04d0, 0x4327, 0x37e6, 0x09ea, 0x3f50, 0x1526, 0x407d, 0x1835, 0x4402, 0x1e38, 0x0896, 0xbf04, 0x1909, 0xb2db, 0xb297, 0xb2a2, 0x012d, 0x389d, 0x0b0f, 0x0c37, 0xb0af, 0x4460, 0x4057, 0x44cb, 0x41c7, 0xbaef, 0x429c, 0xaf1a, 0x46dd, 0x4305, 0xb00e, 0x4071, 0x1817, 0xbf34, 0x4349, 0x01c4, 0xb0f7, 0xb209, 0xbfc0, 0x4362, 0xa7c2, 0x43bc, 0x45cd, 0x2edb, 0xb28f, 0xbf53, 0x4102, 0x44cc, 0x431a, 0x1c7c, 0xb207, 0x445e, 0x432c, 0xb099, 0x1de3, 0x46b2, 0x2163, 0x429a, 0x4108, 0x1361, 0xb09d, 0xbac4, 0xb285, 0x0eaa, 0xbf65, 0x437f, 0x41b1, 0x4197, 0x1ff3, 0x2b09, 0x403b, 0x41b8, 0xb231, 0x42c8, 0x42cf, 0x43e6, 0x0f35, 0x1989, 0x1ff2, 0x41c6, 0xbfd0, 0x1e9b, 0x434a, 0x42e3, 0xa90c, 0x40a4, 0x4208, 0x4200, 0xbf8a, 0x1b32, 0x410f, 0x2ee4, 0x33ed, 0xb240, 0xb0f4, 0x40c3, 0x4250, 0x2be7, 0xb019, 0xb2dd, 0xbfd7, 0xb0d4, 0xba08, 0xb2fb, 0x41b7, 0x1308, 0x4267, 0x41c0, 0xbfd0, 0x434c, 0xb06c, 0x4540, 0xb204, 0x1a34, 0x4140, 0xbad7, 0x1cc9, 0x3e9e, 0x390a, 0x40fa, 0x404e, 0xbfaa, 0x08cb, 0x4261, 0x4583, 0x339d, 0x1e37, 0x42fc, 0x437e, 0x435b, 0x3be3, 0x1891, 0xb222, 0x4245, 0x4130, 0x08b8, 0x447a, 0xb226, 0x034a, 0x4392, 0x4379, 0x40ec, 0xa38a, 0xba18, 0x42d9, 0x02f5, 0x4337, 0x41da, 0xbfc6, 0x4491, 0x4071, 0xb215, 0x1b51, 0x41dd, 0x41cd, 0x4223, 0x00af, 0xb2a7, 0x42e5, 0xb020, 0x43e6, 0xb27b, 0x439a, 0x4213, 0xbac3, 0x426a, 0x3d19, 0xa5b8, 0xb0f3, 0xbfc0, 0xba34, 0xba5e, 0xbfc6, 0x42f6, 0x103e, 0x45c1, 0x4482, 0x42ab, 0x2d50, 0x1f4e, 0x411b, 0x4316, 0xb01d, 0x45ee, 0x1db2, 0xb0a5, 0xbf6e, 0x0cf1, 0x1ac8, 0xba74, 0x43ca, 0xba28, 0xa979, 0x18a9, 0xbf01, 0xa761, 0x41fb, 0xb23a, 0x4242, 0x4358, 0x4163, 0xaabf, 0x13cb, 0xbf08, 0x189a, 0xb257, 0x43ee, 0x4085, 0x46f2, 0xb281, 0x45e3, 0x1a1f, 0xa8aa, 0x45e8, 0xba58, 0xb24c, 0x1e6b, 0xb252, 0x41b6, 0x43f7, 0xbf0c, 0xbad8, 0xb276, 0x43b7, 0x4205, 0x283f, 0x4208, 0x2afa, 0x46f1, 0x387c, 0x4269, 0x433a, 0x201c, 0x4072, 0x45bb, 0xbf55, 0x27d4, 0xb0b8, 0x4049, 0x426f, 0x400d, 0x4031, 0x41da, 0x41f1, 0x42c2, 0x4336, 0x432c, 0x4187, 0x445d, 0x423d, 0xb0cc, 0x408d, 0x43d5, 0x401b, 0x4397, 0xb26d, 0xba34, 0x41e3, 0x4326, 0x4355, 0xb01e, 0xbad7, 0xbfdd, 0xba3c, 0xba46, 0xba70, 0xa0a6, 0x1e83, 0x41cd, 0x41f0, 0x4256, 0xbf00, 0xb2df, 0x41c4, 0x45d3, 0xb2c6, 0xa98f, 0xbfde, 0x3ce1, 0x4263, 0x40b3, 0x45b6, 0x445f, 0xbf0b, 0x1d50, 0xb238, 0x2de2, 0x2b7f, 0xb0b8, 0x1629, 0x411b, 0xaeab, 0x4306, 0x2c95, 0x02e2, 0x4208, 0xba37, 0x25ef, 0xb08d, 0x4352, 0x2571, 0x425e, 0xba43, 0xb21d, 0xbfd2, 0x412b, 0x4644, 0x260f, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd8508733, 0x37686f1d, 0x97ff025a, 0x7e27c2d9, 0xe771ba18, 0xaba51d16, 0x78d8fc78, 0x633a3766, 0xf31e6a39, 0xaa1cca97, 0x3f7fc03c, 0x49e22f13, 0xd6f3fcdf, 0x6bb27080, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0xffffcb9d, 0x00000000, 0xe1000000, 0xffffffff, 0xffffff1e, 0xffff9dcb, 0xe0000000, 0xdfcfffff, 0x49e23000, 0x00000000, 0x00000000, 0xaa1ccb83, 0xd6f3fcdf, 0xaa1cc3e3, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x46f3, 0xbfa0, 0xba72, 0x422f, 0x4194, 0x403f, 0x418a, 0x1b2f, 0x1a10, 0xb282, 0xb004, 0xad90, 0xa257, 0x43cc, 0x1e2d, 0x44a9, 0x4093, 0xba76, 0x4158, 0xbfe0, 0x2347, 0x434f, 0x0059, 0x45eb, 0xbf72, 0x40e7, 0xb086, 0x1feb, 0x4339, 0x40e6, 0x42bd, 0x409b, 0x4000, 0x4420, 0xba49, 0x4276, 0xb210, 0x2b38, 0xba5c, 0x1a1e, 0xab1f, 0x2962, 0x20bd, 0x42ae, 0xb0ab, 0x2234, 0x3607, 0x0b88, 0x29e7, 0x4378, 0xb06b, 0x40ec, 0xbf28, 0x43f2, 0x4135, 0xb00e, 0x1c37, 0x4025, 0x3570, 0xbf80, 0x42db, 0x249a, 0x4328, 0x4211, 0x41c7, 0x4239, 0x2924, 0xa5ef, 0x0387, 0x1a9f, 0x4352, 0x1f8f, 0xb031, 0x41bd, 0x1cdd, 0xb2ed, 0x4002, 0x4182, 0x07c7, 0xbf77, 0x406f, 0x431f, 0x42d9, 0x3e9c, 0xbf60, 0x4377, 0x4277, 0x4314, 0xb001, 0xbf6b, 0x4070, 0x41f7, 0xbadf, 0x0139, 0xb246, 0xba0a, 0x439e, 0x433b, 0x18e3, 0x320f, 0x0f77, 0x45a2, 0x1d6b, 0x1a0b, 0x42eb, 0xbf01, 0x1a8f, 0x4286, 0xa09a, 0xbae4, 0x431c, 0x4307, 0x3e46, 0x430d, 0x42ce, 0x433f, 0x109e, 0xa573, 0xba79, 0x1ef6, 0x41cf, 0x29c8, 0x0dca, 0x0e56, 0xbae7, 0x3d0d, 0x4374, 0x1e44, 0x0b81, 0xba51, 0x4087, 0x4425, 0x4283, 0x420e, 0xbf94, 0x409f, 0x42be, 0x4353, 0x436d, 0x4291, 0xbfc4, 0x4365, 0x40b3, 0x418f, 0xbf3e, 0x1e3d, 0x46ba, 0x1f8c, 0x42ea, 0x1c25, 0x4135, 0xb21a, 0xb29b, 0x16e9, 0x401d, 0x4653, 0xa607, 0x43e2, 0x0ec5, 0x4201, 0xbae0, 0xba26, 0xa713, 0x43c5, 0x23cf, 0x10a8, 0xb08e, 0x4232, 0x43cb, 0x43d9, 0x0774, 0xb03b, 0xbf62, 0x4205, 0xbfa0, 0x4661, 0x403b, 0xbf17, 0x40bb, 0xbaee, 0x42a1, 0x42b3, 0x463e, 0xafdf, 0x3543, 0x3528, 0x1eea, 0x4038, 0x1dd7, 0x38ba, 0x4092, 0x41b0, 0xba32, 0x4416, 0x44fd, 0xbfc8, 0x439f, 0x4319, 0xa47d, 0xb275, 0xb244, 0xbf95, 0x1453, 0xb04f, 0x1c40, 0x3477, 0xbf76, 0xaeb7, 0x0406, 0xbad8, 0xbf5e, 0x1a0d, 0xb2d6, 0xba5e, 0x437b, 0x0b3c, 0x1d54, 0x3c58, 0x404e, 0x427b, 0x25f8, 0xbfa0, 0x4053, 0x127f, 0x42e6, 0x438b, 0xbf23, 0xb2d9, 0x4389, 0x4350, 0xade8, 0x439e, 0x4485, 0x3ec6, 0x19a8, 0x0fe2, 0x42fc, 0x4262, 0xada2, 0x40e6, 0xb0e5, 0x4251, 0xb221, 0x1f4c, 0x127c, 0xbf56, 0x25d6, 0x20f9, 0x4329, 0x4123, 0x413d, 0xb2df, 0xb23b, 0x4653, 0xb2e4, 0xb0a9, 0x42ea, 0xb215, 0x4130, 0x4249, 0x4391, 0xb28f, 0x4127, 0x420e, 0x16ab, 0xa580, 0xb26c, 0x29b2, 0xa29e, 0xb09c, 0xb20f, 0xbfd2, 0x42e1, 0x42cf, 0x0a23, 0x4049, 0x433c, 0x45e0, 0xbf46, 0x407f, 0x408f, 0x4165, 0x4148, 0x19e5, 0x4594, 0xb0f1, 0x438d, 0x4000, 0x42b7, 0x4136, 0x31af, 0x40ea, 0xb0b9, 0x4323, 0x0710, 0xbfa2, 0x38a1, 0x40d2, 0x41a9, 0x4149, 0x0c49, 0x4168, 0x3f89, 0x433c, 0xb2fd, 0x42e3, 0x4353, 0x40a7, 0x43e8, 0xba3a, 0xb266, 0x4397, 0x0a3a, 0x40ab, 0x1fc5, 0x1f5f, 0x4117, 0x4573, 0xb01a, 0x418c, 0x44a9, 0xaa7b, 0x13a2, 0xbfe4, 0xb213, 0x41c3, 0xb28f, 0x2ff1, 0x2600, 0x2452, 0x438c, 0xa2cc, 0x1b55, 0x42ad, 0xb2fc, 0x1067, 0xb2b8, 0x41f0, 0x4625, 0x0ac8, 0xb25e, 0x441f, 0x40a8, 0x43d6, 0x18c7, 0xbadb, 0xb292, 0x4453, 0xbf8b, 0x19d8, 0x1f32, 0x18f9, 0x46b5, 0xb22a, 0x088b, 0xba5a, 0xba7f, 0xb068, 0x03a6, 0x444b, 0xb21c, 0x3014, 0x41cb, 0xbf26, 0x4015, 0xb2c5, 0x1a29, 0x1a7f, 0x0b53, 0x4219, 0x3afd, 0xba1c, 0x0def, 0x3b5b, 0x423c, 0x4003, 0x42a3, 0xb0f8, 0x42ce, 0x4357, 0x41ed, 0x4070, 0x42c4, 0x1c75, 0xa517, 0xb0ab, 0xba3f, 0x4341, 0x2c35, 0x1bd9, 0xbf76, 0x07be, 0x4173, 0x4145, 0xb0a5, 0x11a1, 0x4068, 0x43d8, 0x42e2, 0x40f6, 0xbafd, 0x1fc1, 0x407e, 0xba38, 0x4109, 0xb016, 0x40a6, 0x43d5, 0x4274, 0xbf1e, 0x2433, 0x289d, 0x43ca, 0xb25d, 0xba5d, 0x0a85, 0x43d7, 0x3e24, 0x0e1d, 0x1723, 0x444a, 0x18e3, 0xb2ba, 0xba2c, 0xba55, 0x075d, 0x43e8, 0x0247, 0x40fc, 0x1e94, 0xbfa6, 0x1c10, 0x3a69, 0x4023, 0x4581, 0x1925, 0xb0d7, 0x1f3f, 0xb0a9, 0x45e6, 0x2ec7, 0x41c3, 0x046b, 0x1f29, 0xbf4e, 0xb289, 0xb250, 0x425b, 0x40df, 0xb2b8, 0xab56, 0x1cb4, 0xbac1, 0x4271, 0xbf64, 0x4373, 0x3c6a, 0xbf5a, 0x43bb, 0x429f, 0x1f9e, 0xbac2, 0x404a, 0x0103, 0x4213, 0x46f1, 0x287a, 0x4088, 0xbacf, 0x4331, 0xba4b, 0x32fc, 0x43bb, 0xb02b, 0x4176, 0xa3dc, 0x44e1, 0x1f3a, 0xbfce, 0x3dc6, 0x4227, 0xba11, 0x4148, 0x09b4, 0xbfd3, 0xb245, 0x4394, 0x40ce, 0x07e5, 0x1956, 0x0db9, 0xb238, 0x40f2, 0x412b, 0x1af5, 0xa1f7, 0x466b, 0xba34, 0x4038, 0xa130, 0x404e, 0x19ca, 0x3a04, 0x1d1c, 0x4077, 0x439c, 0xbadb, 0x1f1d, 0x36ff, 0xbf4e, 0x42d1, 0xba5d, 0x4020, 0x0dd2, 0x42a9, 0x1943, 0x405d, 0xba7f, 0x41a1, 0x447d, 0x4181, 0x0963, 0xb038, 0x436e, 0x412f, 0x404f, 0xae5c, 0x41e7, 0x40e5, 0x434b, 0x20c5, 0x1eb2, 0x4010, 0x3a97, 0xbfb1, 0xb047, 0x0ba6, 0x3abe, 0xb252, 0x4104, 0x4346, 0xb206, 0xad63, 0xbf46, 0x4144, 0x41e0, 0x422b, 0xb0ed, 0x4389, 0x423b, 0xba65, 0xbaf3, 0xaaed, 0xb219, 0xba43, 0x243c, 0x197c, 0x421c, 0x18fd, 0x4313, 0x1cae, 0x140a, 0x42a0, 0xbf4e, 0x4015, 0x18f5, 0x42c6, 0x4115, 0x42c0, 0xba0a, 0x437a, 0xbf5f, 0xb0ae, 0x1d9d, 0x4085, 0x4350, 0x1a00, 0x369b, 0xb22b, 0x24f9, 0x4253, 0x360c, 0x41cd, 0x0fb1, 0xb0a6, 0x025d, 0x18c4, 0xb251, 0x40c6, 0x4349, 0xb2cc, 0xa0be, 0x4377, 0x43da, 0x16b2, 0x413d, 0xbf80, 0x44ac, 0xbfdb, 0xb266, 0x0d95, 0x420e, 0xb083, 0x1b6c, 0x4305, 0xbae4, 0xb28f, 0xb236, 0xba09, 0x2da7, 0xb252, 0xba09, 0xa115, 0x447d, 0xb272, 0xbf75, 0x40f6, 0x0b5a, 0xb086, 0x466b, 0x4632, 0xbf0a, 0x43e0, 0x1f0f, 0xb2e7, 0xba3d, 0x36d4, 0x07bc, 0x4172, 0x3697, 0xb221, 0x0e04, 0x28f3, 0x42e6, 0x198a, 0x4089, 0xbf90, 0x3694, 0x2837, 0x4082, 0x4263, 0x4142, 0x4074, 0x2fc6, 0x1e01, 0xab23, 0x0cb5, 0x4033, 0x0067, 0xbfca, 0x111a, 0x4261, 0x29e3, 0x462d, 0xb299, 0x4072, 0xbfe0, 0x4342, 0xbf3d, 0x0830, 0x1afe, 0x419c, 0x1ce9, 0x42e6, 0x40d5, 0x41cc, 0xb072, 0x3bd5, 0xbfdf, 0x1807, 0x44c2, 0xba38, 0x4693, 0x41a2, 0x4072, 0x42e7, 0xbaef, 0x01b3, 0x25c5, 0x4006, 0x096e, 0x4115, 0x0622, 0x43ac, 0xb00c, 0x1f03, 0xbf48, 0x41c4, 0x43dc, 0x415a, 0x20b3, 0x4064, 0xba24, 0x40f8, 0xb23f, 0x0b4b, 0x42ec, 0xb014, 0x4119, 0xbf78, 0x4393, 0x3aa5, 0x410a, 0x0b23, 0x442f, 0xba39, 0x4298, 0xba42, 0x42c5, 0xa365, 0xbfba, 0x40a8, 0x17e3, 0x4057, 0x417c, 0xaa59, 0xafc2, 0x33ad, 0xa425, 0xbfb9, 0x2e0c, 0x0192, 0xaa5d, 0x0448, 0x421e, 0x4357, 0xb2f0, 0x2a8b, 0x378b, 0xb219, 0x416c, 0xbf8e, 0x43ff, 0x4348, 0x4262, 0xba30, 0x202d, 0xa784, 0xadcc, 0xba34, 0x121b, 0x41da, 0xbfd1, 0xb293, 0x435f, 0x31bc, 0xb263, 0x40a0, 0xbf1c, 0x46cd, 0x22d6, 0x3198, 0x250f, 0x2fc5, 0x4100, 0x4090, 0xbfd2, 0xb207, 0xba6f, 0x436b, 0xba5f, 0x4618, 0xb2d4, 0x1123, 0xba54, 0x33e5, 0x43eb, 0x1e8d, 0xb21b, 0xaf42, 0xab0f, 0xb2cb, 0x4101, 0x4026, 0xb2b9, 0xaaf3, 0x1f93, 0x4208, 0x1d01, 0x400d, 0xbf99, 0x3677, 0x182f, 0x3f84, 0x363f, 0x185c, 0xb2d0, 0x0c54, 0x43dd, 0xa014, 0xb23f, 0x41f8, 0xba0b, 0xba39, 0x255a, 0x4313, 0x4449, 0x1934, 0x4317, 0x4102, 0x156d, 0x01fd, 0x41ec, 0x41e1, 0xb023, 0xbf62, 0x41b9, 0x3f1c, 0x3b9c, 0x1930, 0x1e90, 0x429a, 0x1a6c, 0x1dda, 0x1834, 0x440c, 0xb284, 0xb225, 0xae4e, 0x4080, 0xbfca, 0x1cfd, 0x264f, 0x4298, 0x185e, 0x1dc1, 0xbf17, 0x244c, 0x4546, 0x1b6d, 0xbaf7, 0xb286, 0x2054, 0x3c0d, 0x3570, 0x25a7, 0xb261, 0xb21e, 0xa108, 0x13c0, 0x4274, 0x4310, 0xbf42, 0x4212, 0x40c5, 0x438f, 0x0e16, 0x424f, 0xbf90, 0x438e, 0x37c7, 0xbf70, 0x1c3a, 0x437c, 0x06c9, 0x3d63, 0xba3c, 0x1fc0, 0xb223, 0xb245, 0x4003, 0x43aa, 0x0028, 0xba31, 0x038a, 0xba46, 0x1c2d, 0x4435, 0x0726, 0x41d5, 0xbfa6, 0xb2a3, 0xad24, 0x4662, 0x1fdc, 0xb2f7, 0x4012, 0x37dc, 0x418c, 0x1bf3, 0x42b6, 0x44ca, 0x03ed, 0xba5a, 0x431e, 0xbfdf, 0x4315, 0x437b, 0x42ea, 0xbad9, 0xad16, 0xb24d, 0x0b0b, 0xb24e, 0xbfc0, 0xb05b, 0x435f, 0xb255, 0x424e, 0xbae9, 0x4148, 0xb05f, 0xb243, 0x4182, 0x0e5a, 0xb205, 0xbad7, 0x2fd5, 0xbf21, 0x40ac, 0xb2a3, 0xba5c, 0xb2e0, 0x4061, 0x402e, 0xb0a7, 0x3380, 0x42b8, 0xa8cf, 0x2a06, 0x42ab, 0xb296, 0x42c7, 0x41fd, 0xb25b, 0x4333, 0x419f, 0x437b, 0x2ab1, 0xbaf1, 0x17dd, 0x43a2, 0x36ea, 0x4153, 0xbf91, 0x4023, 0x410e, 0xb2b9, 0xb0e9, 0xace2, 0x00d9, 0x2367, 0xb2f9, 0x41df, 0x2087, 0x3147, 0x41f5, 0xb086, 0x40e6, 0x43ef, 0xb21a, 0x41fa, 0xbf6b, 0x311c, 0xb297, 0xba54, 0xa3cd, 0x4189, 0xb252, 0x4186, 0x43d4, 0x4312, 0x4096, 0xb20e, 0x45f6, 0xba7c, 0x3889, 0x4085, 0x4067, 0x4254, 0x1829, 0x414f, 0x4447, 0x2373, 0x4184, 0xb24e, 0x408b, 0x404b, 0xa39a, 0xa843, 0x4007, 0x401e, 0xbf68, 0xba09, 0xb0f7, 0x46a1, 0xb068, 0x427a, 0x42c9, 0x43f4, 0x41e2, 0x40fa, 0xbac6, 0x4377, 0xbf70, 0x3cb1, 0x41b3, 0x43a8, 0xbf74, 0x1f66, 0xb25c, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x320c594d, 0xb070758d, 0x7506c560, 0x186ad0f5, 0x6d69bf75, 0xb7cad7d5, 0x7a31742f, 0xa62baad6, 0x3f33b7a4, 0x3237b374, 0x612a830d, 0xd1ce3d3c, 0xdf52c51d, 0x8c1be78a, 0x00000000, 0xa00001f0 }, + FinalRegs = new uint[] { 0xdf52c8e9, 0xfffffffe, 0x0077f7e0, 0x00003048, 0xffffe53e, 0x00000000, 0xffffe539, 0xd3cd7808, 0x3f33b7a4, 0xffffff9b, 0x407d482a, 0x00000000, 0xdf52c51d, 0xdf52c7a1, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x43ce, 0xba1b, 0xba2e, 0x02ec, 0x4241, 0xb244, 0x0d54, 0x40ed, 0x2352, 0xb025, 0x4172, 0x3f8e, 0x1fc3, 0x423a, 0x43ee, 0x41e7, 0x4155, 0xa91e, 0xbaee, 0x41fb, 0x35a0, 0x1a31, 0x414f, 0x4075, 0xbf8f, 0x39ec, 0x4032, 0x29b2, 0x2093, 0x4196, 0x1b62, 0x4411, 0x3f4e, 0xb242, 0x226a, 0xb2b0, 0x3fc2, 0xba70, 0x43b8, 0xbf7b, 0xa6be, 0x41ab, 0xbae6, 0x401e, 0x46a4, 0xbf2e, 0xb09d, 0x435c, 0x423a, 0xb2a8, 0xac17, 0x4382, 0xb2e8, 0x33df, 0x45a1, 0x11b9, 0x4435, 0xbacf, 0xbf23, 0x2fe4, 0x4137, 0x1be5, 0x1528, 0x41c1, 0xb241, 0xb2e2, 0x2601, 0xb2c8, 0xbace, 0x3e99, 0x417c, 0x4362, 0x426b, 0xb2cb, 0xac16, 0x42dd, 0xab93, 0xbad6, 0x44fb, 0x1c86, 0x445b, 0xa008, 0x41ca, 0xbfe0, 0x45d3, 0xb20a, 0xb06e, 0x443d, 0xbfce, 0x43cf, 0x4278, 0x406a, 0xb23d, 0xac1d, 0x4317, 0x14b6, 0x1aba, 0x4437, 0x409d, 0x40d8, 0x1c08, 0xad81, 0x2e13, 0x43da, 0x354d, 0x418a, 0x1911, 0x3024, 0x40cb, 0xbf4c, 0x190c, 0x400b, 0x4090, 0xbfcf, 0xb09d, 0x3f1b, 0xb235, 0x1f6d, 0x1a67, 0x4078, 0x4045, 0x066d, 0x404d, 0x4148, 0xba75, 0x1c4a, 0xb219, 0x2277, 0x41f9, 0xb0c6, 0xb2a5, 0x0907, 0xbf4b, 0xb244, 0x0cdc, 0xb2ea, 0x4093, 0x42a8, 0x40e0, 0x3381, 0x0521, 0x41dd, 0xb290, 0x4064, 0xb08d, 0x400b, 0x43fa, 0x4349, 0xbf2b, 0xab6c, 0xb0ed, 0x43df, 0x0ae3, 0x24ae, 0xbf67, 0x407d, 0x1041, 0xbae3, 0x43fe, 0xbf63, 0x4185, 0xb033, 0x40df, 0x4362, 0x30bd, 0x455d, 0xb090, 0x411a, 0x44b3, 0x40da, 0xbf80, 0x41d1, 0xb2eb, 0x3e13, 0x4112, 0x40b0, 0x1f92, 0x407e, 0x36db, 0x41cf, 0x4397, 0x1982, 0xbad6, 0xb0be, 0x240e, 0x1c50, 0xba3f, 0x4224, 0xbfdf, 0xa4fd, 0x42c3, 0x4022, 0x4021, 0x1e68, 0xb23d, 0x43c1, 0x46fc, 0x4667, 0xba1c, 0x381b, 0x4189, 0x4186, 0x42da, 0xba72, 0x4230, 0x2b23, 0x40b4, 0x431c, 0x414c, 0xa472, 0x419e, 0xba03, 0x429b, 0x0c07, 0x1985, 0x412a, 0x3faa, 0x4689, 0xbf8f, 0x41e3, 0x427b, 0xb28d, 0xa220, 0xa8e1, 0x42ad, 0x4302, 0x41d7, 0xb224, 0x4240, 0x4063, 0x418d, 0xb2f7, 0x422d, 0x2633, 0x46f8, 0x13db, 0xbf84, 0x3ef1, 0x434c, 0x115c, 0xbf98, 0xba27, 0x1d98, 0xbf95, 0x4273, 0x1cdc, 0x428c, 0xb215, 0x3d32, 0xb219, 0xba4a, 0x4640, 0x4349, 0x4123, 0xba50, 0x447f, 0x3398, 0x4017, 0x325e, 0x1afd, 0xb20b, 0x1f41, 0xb200, 0x422e, 0x16a1, 0x43b4, 0x1f01, 0xbf58, 0x42f1, 0xb2c7, 0xb269, 0x41c9, 0x4445, 0x42fd, 0x4228, 0x1796, 0x1d3a, 0x40d8, 0xbf23, 0x44ac, 0x433c, 0x41d0, 0x280a, 0x4555, 0x428d, 0xbf48, 0xb219, 0x40e5, 0x4178, 0x4072, 0x438f, 0x4351, 0x45a5, 0x42ed, 0xa654, 0xb27b, 0xbfab, 0x1c9b, 0x42b1, 0xb03b, 0xba2d, 0x19b2, 0x4279, 0x429a, 0x4025, 0x41d4, 0xb228, 0x426f, 0xb2dd, 0xb26b, 0x4377, 0x18e1, 0x1a2d, 0x1e4f, 0x40b3, 0x42e2, 0x4249, 0x1b7c, 0xbaec, 0x40c5, 0x193f, 0x414c, 0xba00, 0x437b, 0x40d7, 0xba26, 0xbf4c, 0x43b5, 0x3b21, 0xab9e, 0x44ac, 0x18b3, 0x40c8, 0xbaed, 0x17c8, 0x3a04, 0x1751, 0x41c0, 0x41b2, 0x41c4, 0xb039, 0xbf01, 0x43de, 0x41f5, 0x1b0f, 0x4336, 0xba08, 0xb03b, 0x41d8, 0x366c, 0x0cf1, 0x4615, 0x412e, 0x4541, 0x030a, 0x28f4, 0x419c, 0x45e3, 0x3ee7, 0x3be2, 0xb031, 0x4301, 0x4327, 0x403a, 0xbad8, 0x425e, 0x42f5, 0xbfaa, 0x413e, 0x448c, 0xafe4, 0x40de, 0x417c, 0xbfb0, 0x22f0, 0xb2de, 0x4197, 0x421f, 0x410f, 0xb2d4, 0x40e4, 0x4117, 0x40ef, 0xbf54, 0x415e, 0xa3a1, 0x42bb, 0x40b0, 0x43b0, 0x4176, 0xb0b5, 0xb2e8, 0x3f9f, 0xba16, 0x4130, 0x412a, 0xbf5b, 0x1a0c, 0x42ba, 0x43e9, 0xa069, 0xb287, 0x41fd, 0xb0f8, 0x4275, 0x4443, 0x2643, 0x41ee, 0xba6b, 0xb25d, 0x32bc, 0xacb5, 0x45e2, 0x1049, 0xbf01, 0x4093, 0x4349, 0xaf09, 0xba72, 0xbadf, 0xbf00, 0xbf3c, 0xb0ff, 0x4190, 0xbf70, 0x4246, 0x1fb4, 0x439b, 0x0a5c, 0x265f, 0x409e, 0x1bae, 0xbae4, 0x20cd, 0x1f9b, 0x1ec0, 0x3cf8, 0xbae2, 0xb0d1, 0xaeaf, 0xbac7, 0x44f2, 0x416f, 0xbf1a, 0x415a, 0x2deb, 0x42f6, 0xa78e, 0x26f7, 0x429c, 0x30db, 0xb0d6, 0x4348, 0x4161, 0x409a, 0x432f, 0xbae7, 0x4105, 0x41b1, 0x4103, 0x1f6d, 0x12bd, 0x4362, 0x42d4, 0xbfa2, 0xbaf3, 0x4287, 0x1c0e, 0xb27a, 0x4628, 0x1861, 0x0bdf, 0x4139, 0x3f0a, 0xbfe0, 0x431b, 0x40bf, 0x403c, 0x24d3, 0x11e4, 0x1802, 0x2b5b, 0xa8ce, 0x15cd, 0x0a73, 0x1f3d, 0x1aa4, 0x41e8, 0xbf7c, 0xba36, 0x1a63, 0x1d63, 0x255d, 0x437d, 0x0969, 0xbac2, 0xba01, 0x4053, 0x40a7, 0x46ec, 0xb2f4, 0x1eab, 0xb243, 0x1a9f, 0x40a3, 0x4488, 0xb217, 0x434b, 0xb22e, 0x36e8, 0xb0a1, 0xb2b3, 0xbf25, 0x33da, 0x37e0, 0x43ad, 0x4418, 0x38ba, 0xa987, 0x4139, 0xbf2a, 0xa07a, 0x39a8, 0xb282, 0xba45, 0x43af, 0xba0b, 0x4275, 0x4273, 0xbad6, 0x435a, 0xb088, 0x402a, 0x40a6, 0xb029, 0x4304, 0xaa79, 0xb263, 0x4094, 0x08e2, 0x42cb, 0x42e7, 0x40dc, 0x1604, 0x43ca, 0x2b37, 0xbfc2, 0x4398, 0x300a, 0x41f9, 0xbf9d, 0x4485, 0xa8ec, 0x3bfb, 0xb23f, 0x0380, 0x3972, 0x40d9, 0x4019, 0x1c09, 0x1091, 0xbadf, 0x40c8, 0x43d0, 0x45aa, 0xbf90, 0xb224, 0x43fb, 0xb25c, 0xbf99, 0xbaf7, 0xb08e, 0x1b57, 0xb202, 0xb26b, 0x4160, 0xb03b, 0x1b50, 0x1f3c, 0x4107, 0x4027, 0x446f, 0x4183, 0x323e, 0xa035, 0x41ff, 0x415d, 0xbaff, 0x4634, 0x430a, 0x434f, 0x414d, 0xba3f, 0x1c05, 0xb0e7, 0xbf6b, 0x43e5, 0x3391, 0x1fed, 0x4341, 0x46f3, 0x4280, 0x4103, 0x401e, 0x1e8c, 0xba73, 0x4110, 0x404e, 0x2e56, 0x1b29, 0x1fc5, 0x4562, 0x418f, 0xbfa1, 0x414a, 0xb2fd, 0x42e9, 0xb2c3, 0x4170, 0xb242, 0x4371, 0x160c, 0xb0da, 0x3438, 0xba7e, 0xba02, 0x40ac, 0xbf92, 0x438b, 0x4009, 0x41b3, 0xbae1, 0xb26c, 0x060c, 0x4023, 0x4377, 0x35b8, 0x413d, 0x411a, 0x43b2, 0x0ad3, 0x42a6, 0xbf00, 0x4279, 0x1fe4, 0xb228, 0x42f0, 0x0ab4, 0x41bf, 0x41f0, 0x24fe, 0xbf67, 0x2bbb, 0x0f1e, 0x4101, 0x4402, 0xb2f6, 0x435e, 0xba00, 0xbfb0, 0x41bd, 0x4540, 0xbfb8, 0x1ba4, 0xb25c, 0x1478, 0x415b, 0xb01a, 0x4067, 0xa8f1, 0x429b, 0x1211, 0x42d8, 0xbf6b, 0x42b0, 0xba0f, 0x4175, 0x42a3, 0x3eba, 0x418b, 0x3558, 0x4044, 0x1c92, 0x40ee, 0x4345, 0x429a, 0x42f0, 0xbac9, 0x46b4, 0x429d, 0xbf0a, 0x4338, 0x418b, 0x43c0, 0x4224, 0xbf0f, 0xaa79, 0xba2e, 0x2cfb, 0xa092, 0x4003, 0x2293, 0xb2fb, 0x1a02, 0x4298, 0x4204, 0x41f3, 0x255f, 0x1884, 0xbfc0, 0x403a, 0x407b, 0xb044, 0xbf5d, 0x12b1, 0x3384, 0x4336, 0xb05d, 0x42be, 0x419e, 0xb2ad, 0xba4a, 0x4117, 0xba5b, 0xb292, 0xbafd, 0x428e, 0x43f5, 0x40f8, 0x4601, 0x406b, 0xae07, 0x42e5, 0x4053, 0x4068, 0xb0b4, 0x3961, 0x4340, 0x37c8, 0xbfcb, 0x423c, 0xa03f, 0x4342, 0x423c, 0xbf88, 0x0fdd, 0x4193, 0xb2d9, 0x43da, 0xbfa0, 0x1396, 0x40af, 0x4317, 0xa51a, 0x211a, 0xadb9, 0x433e, 0xba22, 0x41f9, 0x180e, 0x411c, 0xba61, 0x4001, 0x4268, 0x03fc, 0xbf00, 0xb281, 0xb23f, 0xbae2, 0xbf80, 0xbf65, 0x43e1, 0x394f, 0x35a0, 0x329b, 0x422f, 0x4164, 0x0d8f, 0x4237, 0x465a, 0x43b6, 0xbaf0, 0xbfe0, 0xbf11, 0x4267, 0xba2f, 0x41a6, 0xb210, 0x4221, 0x4319, 0x1482, 0x3205, 0xb256, 0x2dfd, 0xba01, 0x402a, 0xb2ca, 0x163f, 0x432d, 0x41fd, 0x4161, 0xb0b4, 0x42f8, 0x40c2, 0x1aa5, 0x4167, 0xb051, 0x0978, 0x4149, 0xbf1f, 0x4175, 0x43fa, 0xb252, 0x4140, 0x4215, 0xba55, 0xb2d2, 0x4210, 0x406a, 0xaf57, 0xacee, 0xa935, 0xba5d, 0x4259, 0x05bf, 0xb015, 0x43e6, 0x4192, 0xa8db, 0x44e2, 0x1588, 0x0d4d, 0x437f, 0x0618, 0xbfdb, 0x41e3, 0x4108, 0xa17e, 0x416f, 0x1b33, 0x42df, 0x438e, 0x41d0, 0xbfb1, 0x0d0f, 0xb28f, 0xba4b, 0x3c20, 0x42e3, 0x0f9b, 0x41af, 0x40fd, 0x415c, 0x40f5, 0x436d, 0xa5a7, 0xa82e, 0xba46, 0x42ac, 0xb204, 0x4067, 0x438d, 0x40e0, 0x09d3, 0xaaf7, 0xbf79, 0x4386, 0x1b23, 0x41ea, 0x4010, 0xbff0, 0x4491, 0xa083, 0xb227, 0xb2d3, 0x4196, 0x1c87, 0xb22a, 0xb09b, 0x01cd, 0x1c6f, 0xafc3, 0x43be, 0xa9d2, 0x42c6, 0x0ad5, 0x4341, 0x432b, 0xba5c, 0xb0bc, 0x4120, 0x412e, 0x4475, 0x4284, 0xb01b, 0xbfda, 0xa893, 0x1afa, 0xa310, 0x40f9, 0xb25d, 0x42b4, 0xb00e, 0xb06b, 0xb26e, 0x43eb, 0x4237, 0x40a9, 0x4369, 0x40cc, 0x4233, 0x40fc, 0x3d00, 0x43aa, 0xb2d7, 0x40bc, 0xbf00, 0xb296, 0xb223, 0x0c49, 0xaaff, 0x4180, 0x18bc, 0xbf70, 0xbf6c, 0x430a, 0xb208, 0x0bd5, 0x1bcd, 0x421c, 0xb289, 0xb250, 0xbf1e, 0x41b7, 0x44fa, 0xb0f3, 0x2760, 0x1c87, 0x189c, 0xb217, 0x41a3, 0xaf06, 0xbfb4, 0x422b, 0x2caf, 0x1b6f, 0x429c, 0x406b, 0x417d, 0x41c5, 0xa01c, 0xb258, 0x43ee, 0x1c6b, 0x4171, 0xb0ab, 0xa87d, 0x40e3, 0x1b3f, 0xba4a, 0xbf46, 0x3418, 0x19da, 0xa67e, 0x4223, 0x3401, 0x3b45, 0x2a8f, 0x45ab, 0xb249, 0xb2d7, 0x416d, 0xbf80, 0x1b06, 0xba61, 0x070c, 0xb2e8, 0x4394, 0x3fb6, 0x41b7, 0xbf56, 0x4087, 0x42fa, 0xb01d, 0x438c, 0x1fb3, 0xbae0, 0xb01a, 0xa6da, 0x4396, 0xb009, 0x3260, 0x4347, 0x4097, 0xb06f, 0x42b9, 0x2e15, 0xb033, 0x4253, 0x43a3, 0x0261, 0x1e9b, 0x4657, 0xb2fb, 0xb288, 0xbf9e, 0x42ed, 0x43d9, 0xba33, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x3e63c940, 0xd2f40362, 0xbb77737e, 0x677685e4, 0x97ccf408, 0x3dfa0433, 0x43332c01, 0xa5fc83ef, 0x5b5a10e7, 0x93fd7fe6, 0xe271cf59, 0xac70a2fd, 0xc4be1954, 0x93de22cc, 0x00000000, 0x900001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x00000000, 0xfffff05f, 0x00000059, 0x00000000, 0x00000020, 0x00001000, 0xe271cf59, 0x49f6f315, 0x93de1f28, 0xe271cf59, 0x00000000, 0x00000000, 0x93de1f1c, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4010, 0x421c, 0x3d78, 0x4344, 0x43d7, 0x401a, 0x1ce0, 0x4051, 0xb2b6, 0x0a48, 0xbad9, 0x4206, 0x4163, 0x1efa, 0x402d, 0x1e69, 0x4221, 0xbfba, 0x4154, 0xb2b8, 0x19e2, 0xb243, 0x15ea, 0x406b, 0x46d3, 0x4546, 0x417a, 0x41b7, 0xba61, 0x41fd, 0x41d5, 0x45bd, 0xb0fa, 0x0951, 0x454f, 0x0377, 0x4262, 0xb097, 0xb04b, 0xb277, 0x37da, 0xbfcc, 0x15dc, 0x4385, 0x18c8, 0xb22d, 0x412b, 0xb029, 0xba75, 0x411d, 0x14c5, 0x456f, 0xbaf2, 0xba3d, 0x45f3, 0xb0da, 0xb087, 0x4253, 0x411a, 0xb2fa, 0x469d, 0x44cd, 0x0459, 0x4410, 0x1378, 0x1239, 0xbf2d, 0x423d, 0x070b, 0x4353, 0x43a9, 0xb015, 0x4301, 0xbae3, 0x0c4b, 0x46bd, 0x4013, 0x3765, 0x4147, 0xbf59, 0x1d79, 0x2918, 0xb014, 0xb223, 0x4202, 0x1ceb, 0x18bb, 0x4274, 0xaa53, 0x4201, 0xbacc, 0xa37f, 0xb0af, 0x411f, 0x41cf, 0x186b, 0x40cd, 0x14ec, 0x4018, 0x435d, 0x327d, 0x43eb, 0x4305, 0xbf64, 0x13ef, 0x40d6, 0x4095, 0x369f, 0x4065, 0x41b3, 0x1cc7, 0xbf8b, 0xb297, 0x432e, 0x4268, 0x4002, 0x430a, 0x4479, 0xb048, 0x421e, 0x1ca3, 0xa979, 0xb01d, 0x4459, 0x07f7, 0xb262, 0x1862, 0xb289, 0xb211, 0xbac9, 0x43cd, 0xbfdf, 0xb072, 0x421a, 0x412d, 0x2962, 0x46ec, 0xb09e, 0x1e20, 0x44c1, 0xbfd1, 0xb00c, 0x437b, 0x18ee, 0x41e2, 0xba0c, 0xba2c, 0x04b5, 0xb05b, 0x4596, 0xa87e, 0x422a, 0x40cc, 0x4002, 0x40a3, 0xb041, 0xbf8b, 0x40d4, 0xb0c0, 0x41b8, 0x45e3, 0x3460, 0xb24b, 0xb285, 0xbf04, 0x4225, 0x4064, 0x420a, 0xbf34, 0x43ed, 0x0cab, 0x4194, 0x0ffb, 0x41b8, 0x43e0, 0x4173, 0xbf9a, 0x3fab, 0x210e, 0xa820, 0x426a, 0x4395, 0x43ee, 0x3657, 0xbfc0, 0x430b, 0x438b, 0x1b6d, 0x1c7b, 0xb231, 0x42f2, 0x439e, 0x4389, 0x41b5, 0x4082, 0x1de1, 0x1ca6, 0xbfd0, 0x34cc, 0x1dda, 0xae7c, 0x4181, 0xbf2f, 0x410f, 0xbaf2, 0x40aa, 0x409e, 0x13c8, 0x4076, 0x1bd4, 0x4020, 0x4128, 0xba1f, 0xb24b, 0xa578, 0xaabf, 0x4049, 0xa063, 0x418b, 0xbafd, 0x407c, 0xb234, 0x4380, 0x4288, 0x43e2, 0xbf38, 0x422b, 0xbf80, 0x43b2, 0x43fc, 0x3c13, 0x4282, 0xab63, 0x41db, 0x298d, 0xba1e, 0x1f37, 0x41e6, 0x464d, 0xb031, 0x4380, 0x467c, 0x1b44, 0xbf07, 0x1891, 0x0bcc, 0x420c, 0x4330, 0x4038, 0x4155, 0x4162, 0x4421, 0x4071, 0xbafd, 0x1b7a, 0x42b8, 0x37f8, 0x46aa, 0x0159, 0xb28a, 0x43cf, 0x43ba, 0xb056, 0x43b4, 0xb067, 0xbf35, 0x46fa, 0x40e7, 0x45c4, 0x42dd, 0xbfa3, 0x1995, 0x407b, 0x3862, 0x43bd, 0x0793, 0xbaf8, 0x1f40, 0x40fd, 0x1a53, 0xbf7f, 0x384d, 0x261a, 0x416d, 0x4200, 0x204f, 0x40e1, 0xb0eb, 0x0a31, 0x43ac, 0xb266, 0x1b0e, 0x4127, 0x27e7, 0x4570, 0x2786, 0x40e4, 0xb23c, 0x44dd, 0x4125, 0xbf6a, 0x402a, 0x40f1, 0x4161, 0x45f1, 0x18cf, 0x1e84, 0xba1e, 0x42dd, 0x406e, 0x09d5, 0xbac2, 0xaa6d, 0x4128, 0x226e, 0xb26e, 0xaf3c, 0x0e65, 0xbf75, 0x45d1, 0x18a2, 0x433e, 0x2402, 0x41e3, 0x43cb, 0x427f, 0xbac3, 0x4180, 0x1df8, 0x41c7, 0x1bf7, 0x439f, 0xab0c, 0x4463, 0xbf4a, 0x43d8, 0x44ba, 0x415a, 0x40d1, 0x4167, 0x4026, 0x0966, 0x423e, 0x1de0, 0x41f5, 0xb07c, 0x4628, 0x4288, 0x460a, 0x1376, 0xbf94, 0x4364, 0x41f7, 0x43de, 0xba74, 0x4027, 0x4453, 0x1dd1, 0x1a65, 0xa7ce, 0xb2c5, 0x4658, 0xb29b, 0xb2b8, 0x18be, 0xb2bc, 0xb022, 0x402c, 0xba7a, 0x0b2a, 0x40d6, 0xba03, 0xbf27, 0x4613, 0x1739, 0x1c8d, 0xb258, 0x1f23, 0xba74, 0x45e6, 0x1b3c, 0x4090, 0x0567, 0xbf4c, 0x1e05, 0xba36, 0xb2a9, 0xbfa5, 0xbae7, 0xafde, 0xba56, 0xb2c6, 0x08f6, 0xb0e6, 0x42dd, 0xbf74, 0xb0a8, 0x2a22, 0x1c73, 0x0bf3, 0xa2ab, 0x4359, 0x07b8, 0x40ea, 0x4207, 0xba17, 0x3f61, 0x1f21, 0x4040, 0x41ed, 0xbae2, 0xba23, 0x42d9, 0xb2e5, 0xa1b9, 0xb0a8, 0xa31a, 0xbf02, 0x1e10, 0xba2a, 0xb20d, 0x4261, 0x4013, 0x1b84, 0x40ce, 0xb218, 0xabc9, 0x1aa3, 0xba7f, 0x42e1, 0x4322, 0x41a1, 0x2a78, 0x4245, 0x46f0, 0x4189, 0x4164, 0xb080, 0x4003, 0x145d, 0xbf67, 0xba42, 0xa206, 0x1c5f, 0x4353, 0x441d, 0xb220, 0xbf3f, 0xba17, 0x416a, 0xb04e, 0x40e8, 0x40f1, 0x41ca, 0xbfb5, 0xba7b, 0xb2e2, 0x1300, 0xb0d2, 0xb03d, 0x435e, 0xb297, 0x1980, 0x4074, 0xbfcc, 0xa9ef, 0x0790, 0x464b, 0x3062, 0x4238, 0x3c74, 0x1fad, 0x34e8, 0x467f, 0xaa62, 0xa96d, 0xbf13, 0xbad9, 0x1c8e, 0xb21f, 0x4236, 0x4249, 0x40d1, 0x4371, 0x4130, 0x4066, 0x274a, 0x4073, 0xb2ee, 0x2f2d, 0x1a18, 0x26db, 0xb206, 0xb26b, 0x44d0, 0xb21e, 0x0cc4, 0xba08, 0x401b, 0xb0a5, 0xbf3b, 0x4362, 0xbf70, 0x4205, 0x432f, 0xba36, 0x4015, 0xb264, 0x4021, 0xb2e6, 0x40ff, 0x4323, 0xbadb, 0x22db, 0x0a58, 0xbf90, 0xb2a7, 0x378b, 0xbfc0, 0x4334, 0x41ff, 0x41a6, 0xab67, 0x4149, 0x42bf, 0x4295, 0xbf00, 0xbf82, 0xa510, 0xb2ac, 0x429a, 0x1e48, 0x2779, 0x4316, 0x1d29, 0x41b7, 0x408a, 0x4213, 0xba3c, 0x1423, 0xbfd2, 0x4647, 0x41ed, 0x219f, 0xbf8f, 0xba7d, 0x1ada, 0x1e7f, 0x3e40, 0x4325, 0x4166, 0x4206, 0x4335, 0x33d2, 0xb22b, 0x1f30, 0x42da, 0x402e, 0x43c6, 0xbfd6, 0x4312, 0xb2d3, 0x41c1, 0xb22a, 0x438d, 0x02af, 0xbf4a, 0x41d6, 0x0ac5, 0x40eb, 0xb00d, 0x41c8, 0x09ce, 0xb276, 0xbfd3, 0x24d8, 0x44b9, 0x4581, 0x4156, 0x071a, 0xba20, 0xb2ee, 0x41cd, 0xa9ca, 0xbf80, 0x2ad3, 0x4242, 0x4040, 0x1ba8, 0xb2c0, 0x41df, 0xbfab, 0xbad7, 0x1efd, 0x418e, 0xba0d, 0x2484, 0x41d0, 0x1b17, 0x402f, 0xb2c3, 0xb062, 0x02be, 0xbfbe, 0x0c82, 0x41c5, 0xba53, 0xa095, 0xa516, 0x1939, 0x43bf, 0x1e52, 0x408a, 0x2dff, 0x4001, 0x4146, 0x439e, 0x4397, 0xbf00, 0x40db, 0xba6a, 0x4445, 0x429d, 0xb2f1, 0x1d3d, 0x43a0, 0xbf74, 0x4246, 0xb096, 0xa063, 0xb269, 0x3cbc, 0x4552, 0xb221, 0xba52, 0xbadc, 0x4211, 0x3825, 0xb2db, 0xbfa0, 0xba0c, 0x3ad0, 0xb261, 0x4170, 0x430c, 0x41ec, 0x1dd5, 0x0d4d, 0x4140, 0x1a8b, 0x1904, 0xbfa5, 0x41f9, 0xba0c, 0x436d, 0x1d2a, 0x3b51, 0xba39, 0x4014, 0x40f5, 0x40a8, 0x415d, 0x2e7c, 0x3625, 0xb283, 0x421b, 0x2b20, 0x205e, 0x2a8e, 0x43be, 0xba39, 0xbf33, 0x4173, 0x2675, 0x10d5, 0x284b, 0x4227, 0xaaa0, 0x1bcb, 0x1c0e, 0x2a8d, 0x43fa, 0x417e, 0x4379, 0x4096, 0x1d43, 0x409a, 0x42b3, 0xba76, 0x0be7, 0x4316, 0xaed0, 0xbf1d, 0x407c, 0x42da, 0x18c3, 0x1f4d, 0x420e, 0x2a5f, 0xbfa9, 0x40a8, 0x1eb0, 0x4273, 0x40ea, 0x43d0, 0x07e3, 0xb210, 0xba4c, 0x41a8, 0xb294, 0xbf5d, 0x1a32, 0xbafd, 0x4031, 0xa0dc, 0xb2d0, 0x40bb, 0x431e, 0xbf73, 0x4186, 0x4054, 0x36b8, 0x40f2, 0x18fa, 0x1dc6, 0x431a, 0x4298, 0x43a4, 0x25c7, 0x1976, 0x1aa5, 0x41e8, 0x43cb, 0xbfb0, 0xbf58, 0x4328, 0xbf02, 0x43ad, 0x4118, 0xb0b3, 0x40cc, 0x24f8, 0x40e8, 0x4127, 0xa3a1, 0x30df, 0xb238, 0x3b0a, 0xb274, 0x4210, 0xb296, 0x4131, 0xb2dc, 0x37ca, 0x4243, 0x405c, 0x45ab, 0xbf2b, 0x270b, 0xbae4, 0x1bcd, 0x4396, 0x438b, 0x1ed0, 0xbf80, 0x2b6e, 0x436a, 0xb2ea, 0x437f, 0x4606, 0x1f3f, 0x4269, 0x0f94, 0xb223, 0x0628, 0xbf98, 0xbf60, 0xb234, 0xb23d, 0x43be, 0x4137, 0x40fe, 0x41f4, 0x40f5, 0xa3f3, 0xb2ba, 0xba6a, 0x4126, 0x4364, 0xb2f1, 0x42e2, 0xba6b, 0xb0b8, 0x199e, 0xb22a, 0x41e9, 0x461f, 0x431f, 0x44cd, 0x1bdb, 0x1b8e, 0x1deb, 0xbf3c, 0x253b, 0x4355, 0x41ad, 0x4340, 0x40ab, 0x1952, 0x40d0, 0x1cee, 0x43cd, 0xbad6, 0x2461, 0xbf28, 0x28e7, 0xbad3, 0x4420, 0xb29b, 0x436d, 0xb01c, 0xa3bc, 0x4173, 0x4194, 0xb2f1, 0x4298, 0xb0d3, 0xbfb0, 0xb200, 0x415d, 0x406e, 0xba4e, 0x4182, 0x41a4, 0x4207, 0x40dc, 0xbaec, 0x193c, 0x415a, 0x4317, 0xbf83, 0x1e0b, 0x3d29, 0x40a1, 0x4333, 0xb046, 0xaf27, 0x1988, 0xb28a, 0xb204, 0x16d6, 0xac6b, 0x425b, 0x1f0d, 0x4251, 0x408e, 0x42de, 0x464c, 0xbfae, 0x40e7, 0x44b8, 0x43fc, 0x4065, 0x4333, 0x4479, 0x0559, 0xbaeb, 0xb252, 0x41f3, 0x4380, 0xb230, 0x46c0, 0x44a0, 0xbf8e, 0xb023, 0x4288, 0x4017, 0x406b, 0x4288, 0x4398, 0x18cc, 0xabdd, 0x429a, 0x42ab, 0xbf95, 0x43f2, 0x4057, 0x4442, 0x40ee, 0x4111, 0x4358, 0xa518, 0x34fa, 0x4180, 0x3ecd, 0x4223, 0xb265, 0x42a4, 0x1f7c, 0xb296, 0x1d3c, 0x430f, 0x442d, 0x4307, 0x437a, 0xbf27, 0x1d82, 0x391c, 0xb2a8, 0xbae1, 0x40a0, 0x41b3, 0x40e2, 0xb27e, 0x43a4, 0xafdf, 0x43ad, 0xb024, 0x40e0, 0x4048, 0x3c42, 0x1d9d, 0x1954, 0x0cd9, 0xbf4b, 0x4236, 0xa2ef, 0x424e, 0xbac2, 0xba67, 0x4103, 0x43eb, 0x1d60, 0x459e, 0x4059, 0x41b5, 0xbf83, 0x4310, 0xba42, 0x3684, 0xb299, 0x43ca, 0x1f66, 0x43e1, 0x1358, 0xb08d, 0x41ee, 0x1b3a, 0x4386, 0x40ad, 0x43fd, 0x4653, 0xb0bc, 0xb2ac, 0x405b, 0x037e, 0x418c, 0x1887, 0x42bd, 0x05ee, 0x46ed, 0x4238, 0x4062, 0x43f9, 0xbfad, 0x461f, 0xb0fa, 0x3e21, 0x411c, 0x403b, 0x2342, 0x3259, 0x076d, 0xbf81, 0x4009, 0x4152, 0xba08, 0x1811, 0x4312, 0x32f2, 0x1b6f, 0x408c, 0x43d8, 0x437e, 0x40c1, 0x40a1, 0x40cd, 0x43af, 0x2042, 0x4342, 0x4082, 0x4029, 0xa300, 0xbf49, 0xb076, 0x421e, 0x45a2, 0x185f, 0xba07, 0x4283, 0x4306, 0x3c84, 0x454f, 0x42d5, 0x4071, 0xae63, 0xb2b0, 0xab50, 0x21b6, 0x4024, 0x4485, 0xb292, 0xb266, 0x424d, 0x1559, 0x0623, 0x42e7, 0xb2e3, 0x4094, 0xbf38, 0x4378, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xfbee3e06, 0xf7bc3994, 0x7ccee582, 0x65c6dfd8, 0x7d765d73, 0x2669bec2, 0x4d20d1a8, 0x414bdcb0, 0x53f02dc6, 0xad07dc25, 0xbd4dd249, 0x070bd4e7, 0x1404f4cb, 0x80985f65, 0x00000000, 0x700001f0 }, + FinalRegs = new uint[] { 0x000061d2, 0xfffffdef, 0x00000000, 0x0000007c, 0xffffff7c, 0xffffff4a, 0x0000007c, 0x42000000, 0x0b273264, 0x00a885eb, 0x0b273265, 0xbd4dd249, 0x00000322, 0xbdf6c218, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xba5b, 0x07d6, 0x17ae, 0x42a8, 0x465e, 0x4335, 0x121d, 0xa28d, 0x4605, 0x4319, 0x4115, 0x1eed, 0x40b7, 0x43ba, 0x4680, 0x4079, 0x4604, 0xb239, 0x41f3, 0x40fa, 0xba5e, 0x4247, 0xba6d, 0x42ce, 0xbfb7, 0xba11, 0x4025, 0xbfa0, 0x4034, 0xb096, 0x1948, 0x4194, 0x42a7, 0x43b5, 0x416e, 0x40a9, 0x42db, 0x4601, 0xbf38, 0xb2e6, 0x426f, 0x444a, 0x40ee, 0x44ed, 0x44d1, 0x4000, 0xb215, 0x30a0, 0xba5a, 0xb261, 0x41bb, 0xb00b, 0xbf6c, 0xbae1, 0x0ef2, 0x431c, 0x00d4, 0x446c, 0x4357, 0x42d7, 0x2fd0, 0xb224, 0xb099, 0x424c, 0x1b9f, 0x01d3, 0x40e7, 0x3e10, 0x4648, 0xbfcb, 0x0411, 0xba0b, 0x40bb, 0xbac8, 0x24c9, 0x42cf, 0xba3d, 0x01fa, 0x1a3f, 0x03a8, 0x28e0, 0x42e1, 0x40b4, 0xb24b, 0x1cd5, 0xb2c8, 0x43ef, 0x2f59, 0x18d0, 0xa4b2, 0xba71, 0x2bb4, 0xb088, 0x21d1, 0xb2db, 0xb2b4, 0xbfb7, 0x1d84, 0x1c34, 0x438f, 0x40d3, 0x4542, 0xaf8e, 0xbad0, 0x4106, 0x28c9, 0x4102, 0x40e9, 0x42d2, 0xbf2c, 0xb203, 0xb295, 0x4262, 0x42ab, 0xbfca, 0x4477, 0x43df, 0x425f, 0xb2b8, 0x425d, 0xb2f7, 0x1d27, 0x40e0, 0x41d8, 0x40e2, 0x3bf3, 0x40f4, 0x416c, 0x17a2, 0xa33f, 0xb254, 0xba60, 0xb056, 0xbfd7, 0x4208, 0xb025, 0x4590, 0x18f9, 0xb20c, 0xba78, 0xb059, 0xba72, 0x2cea, 0x0a1e, 0xbf00, 0xaaee, 0x4651, 0x4315, 0xbaf9, 0x42c3, 0x4176, 0xa362, 0x0638, 0x2acd, 0x1a26, 0xb251, 0x40c7, 0x4469, 0x410c, 0xbfd7, 0x43a1, 0x419c, 0x4093, 0x43d5, 0x3b5f, 0x33cf, 0xa83a, 0x4160, 0xb222, 0x41bf, 0xb2c9, 0x4324, 0x1fd8, 0x1cd4, 0xb0ab, 0x427b, 0xbaf5, 0x407f, 0xbf63, 0x19af, 0xa6ef, 0x438b, 0x43cb, 0xbfc0, 0x4367, 0x428f, 0x4088, 0x4219, 0xb273, 0x0619, 0x4385, 0x420f, 0x46e3, 0x4466, 0xb019, 0x4366, 0x402f, 0xbf36, 0xb2b2, 0x2705, 0x1202, 0x0143, 0xbfa7, 0xbfc0, 0xad30, 0x42ee, 0x4279, 0x4273, 0x1d83, 0x432e, 0xb2fd, 0x42af, 0xbfd7, 0x4304, 0xba66, 0x1f02, 0x04b7, 0x4547, 0x0537, 0x4478, 0x4396, 0x421a, 0xbf64, 0x421e, 0x2be3, 0x41cd, 0x43fd, 0x435c, 0x4205, 0xa4ee, 0x439a, 0x4270, 0x42cd, 0x435b, 0x4057, 0x2f2c, 0xb22c, 0x27ee, 0x43df, 0xb2bd, 0x40ce, 0x17f1, 0xbf58, 0x4179, 0xba3d, 0x4036, 0x4150, 0xa831, 0xba54, 0x4278, 0x4270, 0x1454, 0x42e3, 0x4099, 0xb253, 0x41b7, 0x1c6d, 0x4148, 0x4158, 0xb096, 0x4495, 0xb07e, 0xbaca, 0xb2b8, 0x1b78, 0xbf53, 0xb2a8, 0xb2a0, 0x3743, 0xba20, 0x41cc, 0x1435, 0x438c, 0x4074, 0xb09f, 0xb291, 0x1fb0, 0x4040, 0xbafc, 0x1ebc, 0x14da, 0xbfa0, 0x4235, 0xb22c, 0x41b4, 0x3a86, 0x433a, 0xbfa8, 0xb047, 0x4639, 0xbadf, 0x4225, 0x43f8, 0x0dab, 0xa751, 0xbff0, 0x4347, 0xbfb6, 0xba06, 0xbadf, 0xb084, 0x06c0, 0x2b46, 0x42b3, 0x014f, 0x0b86, 0xba4f, 0x44ab, 0xb2c4, 0x154e, 0xb24e, 0xbaf5, 0xa94c, 0x30d0, 0x435b, 0xbaeb, 0xbfa3, 0x41fc, 0xba0e, 0x4180, 0xba0b, 0x1810, 0x0d4e, 0xba04, 0x0f96, 0xb0db, 0x4117, 0x4335, 0x4314, 0x3157, 0xb26d, 0x4554, 0xb23a, 0xb2ad, 0x3ee7, 0x3bcf, 0xba46, 0x42cc, 0x415a, 0x421b, 0x43bf, 0xbf80, 0x1b5d, 0xba10, 0x099c, 0x421d, 0xbf32, 0x20d0, 0xb21d, 0xacf5, 0xba23, 0x4120, 0x4102, 0xbaf4, 0xbfa8, 0x2f4b, 0x1913, 0x2795, 0x43fe, 0x4472, 0x4419, 0x4224, 0x423b, 0xba6a, 0x33f4, 0x466a, 0x0602, 0x42e6, 0x45c5, 0x0a3f, 0xb279, 0xb294, 0x42cb, 0x4013, 0xb2f7, 0x41ce, 0x1fbe, 0x4229, 0x41eb, 0xbf5d, 0x0f87, 0x4120, 0x4233, 0x410a, 0x3a7d, 0x1365, 0x4633, 0x42f0, 0xba0c, 0x415a, 0x438f, 0xb062, 0x42f1, 0x4205, 0xba13, 0xbfa5, 0xb221, 0xb2eb, 0x436a, 0xb2cf, 0xb241, 0x4120, 0x4233, 0x446e, 0x40c9, 0x3943, 0xb2c5, 0x0b33, 0x13f7, 0xb236, 0xa84f, 0x415a, 0xb09b, 0x43e2, 0x4105, 0xbfc0, 0x438e, 0x1901, 0x4146, 0xbf52, 0xa619, 0xba67, 0x4354, 0x3f22, 0x430e, 0x44d5, 0x1fbf, 0x413b, 0xa872, 0x4304, 0xba65, 0xb27e, 0xb20f, 0x41a5, 0xbade, 0x4157, 0xaeb4, 0x423d, 0x42fa, 0xbfc1, 0x4416, 0x434d, 0x44ea, 0xb239, 0x4594, 0x0037, 0xbf1b, 0x1864, 0x42d5, 0xba2f, 0x017e, 0x4065, 0xb0bc, 0x13a1, 0xbfa0, 0x43b2, 0x41a4, 0xb269, 0x46d1, 0xb27b, 0x410e, 0xb24b, 0xbf06, 0xb061, 0x0c0c, 0xb294, 0x4010, 0x1e48, 0x435f, 0xa4ab, 0xb2db, 0x248f, 0xa37f, 0x403a, 0xb0df, 0x4023, 0x42ba, 0x42dd, 0x1e2d, 0x4299, 0x41a2, 0x2d1b, 0x2b1b, 0x41f5, 0x194d, 0x422c, 0xb23b, 0x427d, 0xb2a0, 0xbf1a, 0xab55, 0x43ca, 0xa750, 0xa102, 0xbaed, 0xbff0, 0xba5f, 0x4274, 0xb28e, 0xa5be, 0x40e5, 0xb20e, 0x4173, 0x2778, 0xb26b, 0xa8ff, 0x18f0, 0x4036, 0xbf65, 0x1b9b, 0x46ec, 0xb0bf, 0x4017, 0x4126, 0x2f2d, 0x3504, 0xb05a, 0x45e5, 0x4053, 0x41f7, 0x43d1, 0x417c, 0x421c, 0xbfe2, 0x4377, 0x1723, 0xbff0, 0x46c2, 0xa232, 0xaec0, 0xba6c, 0xb251, 0xb2f6, 0x4060, 0x404f, 0x1ce5, 0xbf88, 0x4655, 0x1a5a, 0x403d, 0xba12, 0x19c7, 0xba50, 0x4169, 0x4106, 0x4250, 0xb291, 0x42c7, 0x4347, 0x27bd, 0x2c35, 0x0626, 0x1ec8, 0x0280, 0x400e, 0x2bf8, 0xb263, 0xb2af, 0xb01d, 0xba2b, 0x1cf0, 0xbfdf, 0x431f, 0x4604, 0xb295, 0xba0c, 0xb218, 0x4325, 0x213d, 0x415a, 0xb0aa, 0x410d, 0x41d0, 0x18ec, 0x445d, 0x4031, 0x1a49, 0xbaf3, 0x4089, 0x4001, 0x42c8, 0x40d9, 0x1fd4, 0xbf3f, 0x41ab, 0xa638, 0x42fa, 0x1c8f, 0xa077, 0x1b97, 0xb204, 0x4210, 0x42f2, 0x1b51, 0xb0ed, 0x1bc5, 0xb25f, 0xba2d, 0x335d, 0x3031, 0x15d4, 0x403d, 0xa32c, 0xbadb, 0x41be, 0xbf33, 0xba5e, 0xba5f, 0x45ba, 0xb01a, 0x4214, 0xbad1, 0xbf82, 0x429c, 0x171a, 0x1728, 0xbfb2, 0x413e, 0xac11, 0x42bc, 0x4149, 0x41b5, 0x433a, 0xb26f, 0x1d40, 0x461b, 0x4046, 0xb243, 0xb2fc, 0x4259, 0x1b06, 0x4253, 0x4179, 0x4369, 0xba48, 0x403d, 0xb2b1, 0xb06f, 0xb21c, 0x4122, 0x2dc7, 0xbf9d, 0x43c5, 0xb2cd, 0xa4b1, 0x417e, 0xb232, 0x42e0, 0xbfdc, 0xb073, 0xb2a0, 0x1caa, 0xb2d4, 0x4037, 0x16f9, 0x05b2, 0xbf00, 0x43f5, 0x4135, 0x43d8, 0x4115, 0x42e9, 0xb248, 0x4037, 0xb274, 0xb249, 0x4284, 0x3542, 0x3c24, 0xa2e5, 0x1a2f, 0x40d1, 0xb2e3, 0x41da, 0x4255, 0xbf0f, 0x2d1a, 0x46d8, 0x422c, 0x28a3, 0x1c9e, 0x1f60, 0xb0a9, 0xb08f, 0x4056, 0x19b4, 0xb20b, 0xba7b, 0x4308, 0x40de, 0xb204, 0x4684, 0x4179, 0x0f4b, 0xb2e6, 0x45f0, 0x43f6, 0x43f6, 0x4181, 0xb2ab, 0xbf82, 0xb242, 0x2355, 0xb227, 0x1e77, 0x403d, 0x4381, 0x404c, 0x427e, 0x4220, 0xbfd0, 0x3630, 0xb282, 0xbf75, 0x1fe1, 0x1f1f, 0x288b, 0x40f3, 0x46f9, 0x39bb, 0x4328, 0x407f, 0x4245, 0x434a, 0xae95, 0x41c9, 0xba43, 0x09d6, 0x4179, 0xa589, 0x1d18, 0x1cdd, 0x42dc, 0xbf02, 0x35d5, 0x43c8, 0x4270, 0x403c, 0xba6d, 0x42ea, 0xbf65, 0x1ade, 0x2288, 0x4660, 0x437f, 0x4281, 0xba01, 0xb2e8, 0x4248, 0x46d1, 0xba30, 0x41b0, 0xb21e, 0x4017, 0x024f, 0x4305, 0x40cb, 0xb29c, 0x40d0, 0x0b3f, 0x0661, 0xb2fc, 0x4083, 0x196d, 0xb2aa, 0x3149, 0xbfc9, 0x41ae, 0x40ea, 0x4126, 0x462a, 0x42e4, 0x434f, 0x4242, 0xb2e1, 0x43de, 0x43e2, 0x41cb, 0x4112, 0xba58, 0x42c6, 0x426d, 0xb082, 0x43e7, 0x419e, 0x43ad, 0x4576, 0x42a1, 0x40a6, 0xbf54, 0x40a9, 0xbac0, 0xb254, 0x1bc9, 0x3ef4, 0xba5a, 0xa96a, 0x40a2, 0x4688, 0xb2a4, 0xba68, 0xb21e, 0xb2a2, 0xbf38, 0x0405, 0xb219, 0x2f28, 0x40fb, 0xb23c, 0xb29e, 0x4035, 0xb0cc, 0x4653, 0x4006, 0x460b, 0xbf3c, 0x427b, 0x4182, 0x4274, 0xbff0, 0x4252, 0x1272, 0xbfa4, 0xae65, 0x0ca1, 0x14f9, 0x00b0, 0x40b8, 0xba23, 0x4349, 0xbfa3, 0x4461, 0x063b, 0xb229, 0x41eb, 0xb224, 0x4124, 0xb0cc, 0x4342, 0x3167, 0x42c9, 0x1d8e, 0x4626, 0x3374, 0x4167, 0x4060, 0x45dd, 0x1ad6, 0x3446, 0x4237, 0x442f, 0x4214, 0x1d7c, 0x46c5, 0x42c7, 0x4491, 0x3e38, 0x1d56, 0x42cc, 0xaec2, 0xbf8e, 0x4090, 0xba77, 0x45d4, 0x40e8, 0x190e, 0x1b87, 0x0e51, 0xba73, 0x0806, 0xb206, 0xbf46, 0x4326, 0x111b, 0xba4a, 0x1b79, 0xa71d, 0x42a8, 0x4294, 0x423c, 0x4667, 0xba77, 0x4110, 0x43a9, 0x1d4d, 0x42c5, 0xb2f7, 0xb216, 0x40d9, 0xb06f, 0x43ba, 0x41b9, 0x467f, 0xa135, 0xbaee, 0x40e7, 0xbfc6, 0xb241, 0xa4a6, 0x423b, 0x408e, 0x364b, 0xa098, 0x4005, 0xb275, 0xb0f2, 0xba79, 0xbfa0, 0x4387, 0x41d9, 0x0761, 0xb251, 0x418c, 0xba33, 0xbf51, 0x4007, 0xb256, 0x44c3, 0xb212, 0x434f, 0x40b4, 0x0edf, 0x4546, 0x4343, 0x45b3, 0xb29c, 0x40fe, 0x341e, 0x1af1, 0x42dc, 0x1c07, 0xba79, 0x08bb, 0xbfc7, 0x16a1, 0x4655, 0x1fe7, 0x4235, 0xbf3d, 0x338e, 0x43a2, 0x36ad, 0x2e82, 0xa991, 0x423e, 0xba76, 0xba17, 0x1a36, 0xbad3, 0x42e4, 0x41d8, 0xb206, 0x41fd, 0x3bb8, 0x42a0, 0xb2ec, 0xbacd, 0x43c3, 0x1bde, 0xba65, 0x4188, 0x41a4, 0x40fb, 0x4225, 0x25ce, 0xbf97, 0xb22b, 0x19e4, 0x43cc, 0x4101, 0x27f0, 0x1f7e, 0x1d8d, 0xb23d, 0x4280, 0x1d99, 0x41ca, 0x4170, 0x440d, 0xbae8, 0x4114, 0x0519, 0x41db, 0x214d, 0x4255, 0xb25f, 0x1b50, 0x14bd, 0xba46, 0xbf0f, 0x43b1, 0x20c0, 0xa4da, 0xb273, 0xbf65, 0x0636, 0x43e3, 0xb006, 0x410e, 0x161c, 0xba02, 0x4471, 0x2f5a, 0x40e6, 0xbf55, 0x4030, 0xba21, 0x412c, 0xb2a7, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd10f75bd, 0x744765d0, 0x4700bcdf, 0x41a4f9ee, 0x0164f011, 0x5d046666, 0xcc6ba3ee, 0xcaed7f40, 0xc754ff08, 0xa741fc6a, 0x1e4ce65a, 0x41d5f9d4, 0xf0c8c8a5, 0x9b837a0d, 0x00000000, 0x200001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x0000004d, 0x00000000, 0x03380000, 0x00000003, 0x00000000, 0x00000000, 0x00000000, 0x5553dda0, 0xd10f75bd, 0xd10f75bd, 0xf0c8c8a5, 0xffffff86, 0x5553ddac, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xa199, 0x43c5, 0x4111, 0x1dd7, 0x4284, 0x4103, 0x41a4, 0xbf24, 0x0c40, 0x44e5, 0x448a, 0x1c19, 0x4275, 0x1e0c, 0xa7fd, 0x1c09, 0x009a, 0x1ee8, 0x459c, 0x43d0, 0x40b2, 0x426f, 0x402e, 0x4421, 0xbae9, 0x2640, 0x402a, 0x1fcd, 0x2ff9, 0x3926, 0xbf1b, 0xb281, 0xaacb, 0x45b0, 0x1c61, 0xba01, 0xb22d, 0x277d, 0x4042, 0x4374, 0x13b1, 0x146d, 0x410f, 0xb09b, 0x179d, 0x421b, 0x1ae6, 0x4085, 0x2920, 0x425a, 0x43df, 0xb244, 0x4080, 0x42dd, 0x4178, 0x43a1, 0x4057, 0xa843, 0xb2ea, 0x4195, 0xbf3f, 0x4058, 0x4042, 0xa1e7, 0x4263, 0x4060, 0x45ea, 0xba42, 0x40c5, 0xbf63, 0x0953, 0x4287, 0x11ec, 0xba1b, 0x2900, 0x1d63, 0x425b, 0xb209, 0x427d, 0x2ffd, 0x40a6, 0xba7b, 0xbfa1, 0xb227, 0x1390, 0xb2ae, 0x413f, 0xb230, 0x1ea6, 0xb063, 0x44d9, 0x43ed, 0x38e6, 0xb266, 0x11c6, 0x4353, 0x4395, 0x423d, 0x4038, 0xb0dd, 0x418d, 0x405b, 0x4165, 0xbf39, 0x1373, 0x4200, 0x458b, 0x42d4, 0x4307, 0x1f3f, 0xbf95, 0x405d, 0x43cd, 0xbaf2, 0xa1f9, 0x40f1, 0x40cf, 0x40ec, 0xb203, 0xbfcc, 0x420f, 0x1ebf, 0x2b1b, 0x4186, 0x1e1f, 0x464a, 0xba6a, 0x423d, 0x4453, 0x1f67, 0x434e, 0x27b3, 0x15d6, 0xb2d8, 0xbf53, 0xba22, 0x413b, 0x187f, 0x42fd, 0xa8c3, 0xba09, 0x41fd, 0x4442, 0x420c, 0xb296, 0xbac5, 0x4215, 0x40f2, 0x4082, 0x1a04, 0x11e2, 0x1cbf, 0x43d9, 0xb216, 0x4026, 0x3608, 0x43ea, 0x4022, 0x4092, 0x0e1f, 0x429b, 0xb2e8, 0x42df, 0xbfad, 0x349f, 0xb2ff, 0x4212, 0x4168, 0xbad1, 0x390c, 0x41e0, 0xa34a, 0x40cf, 0x419f, 0x40db, 0x2fbd, 0xb0a9, 0x4329, 0x4351, 0x4129, 0x4104, 0x4268, 0xb2c4, 0xbf13, 0x1afa, 0x2de8, 0xb0e4, 0x4191, 0xb234, 0x4041, 0xb0b4, 0xb05c, 0xba7f, 0x2b96, 0xbfc6, 0xba54, 0x434b, 0x447f, 0xbfc2, 0x4113, 0xb2ea, 0xae3d, 0x417a, 0x42f8, 0x1884, 0x1f4a, 0xba51, 0xa2aa, 0xbf15, 0x4386, 0xba75, 0x40e8, 0x1f84, 0x4202, 0x42d7, 0x19e7, 0xbf80, 0x4168, 0x45e6, 0xbff0, 0x4152, 0xbf0c, 0xabb2, 0xa8e2, 0x42f0, 0x43d5, 0x405c, 0x4229, 0x42b1, 0xb0d1, 0xbf98, 0xba2d, 0x43c0, 0x024c, 0x3d62, 0xb22e, 0x400d, 0xba54, 0x4002, 0x2696, 0x1c64, 0x1d10, 0xb2f5, 0x40b8, 0xb04f, 0x24dd, 0x43fb, 0x4042, 0x182b, 0xbafd, 0x46d2, 0xbf78, 0x4317, 0x2901, 0xadeb, 0xa020, 0x43de, 0x352d, 0x4025, 0x4067, 0x4184, 0x42af, 0x4197, 0x410b, 0x4240, 0x42ef, 0xbfcc, 0x3013, 0x454d, 0xbf58, 0x4317, 0x0df1, 0x1877, 0x422f, 0xb01b, 0xb083, 0x402d, 0xb24c, 0x3837, 0xb0ad, 0x1a40, 0x0cad, 0xa457, 0x1c71, 0xb0c5, 0x40c7, 0xbf24, 0x4115, 0x4613, 0x41da, 0x1e59, 0x1099, 0xa19d, 0x25c9, 0xba1f, 0x0da3, 0xa9c4, 0x4263, 0x4128, 0xaae7, 0xb051, 0x427c, 0xba0b, 0x41fc, 0xb234, 0x1cec, 0x40be, 0xb221, 0xba4c, 0x1fcd, 0xb21e, 0xbf4c, 0xb0fd, 0x4172, 0x1d00, 0x1b78, 0x424e, 0xbf1e, 0x1f40, 0x0324, 0x4271, 0x41f0, 0xa81e, 0x4692, 0xb21e, 0x4114, 0xbf39, 0x46aa, 0x19a7, 0x2315, 0xbadf, 0xab7d, 0x1edf, 0xba3a, 0x1a7a, 0x1ede, 0xba56, 0xb074, 0xb207, 0x460f, 0xb20f, 0xba15, 0x40de, 0x40d3, 0x4029, 0x3388, 0xb265, 0x4274, 0xba28, 0xbfd4, 0xbac1, 0x403a, 0xb011, 0xb282, 0xb212, 0xbf37, 0x4134, 0x401e, 0x42b3, 0xbaf5, 0xbfbe, 0x417e, 0x4009, 0x14f9, 0xba76, 0xb2d3, 0x40de, 0x43ee, 0x446f, 0x4238, 0xbf90, 0x4302, 0x1f23, 0x0475, 0xb256, 0x40a3, 0x4008, 0xb0d8, 0x4147, 0x3b2a, 0x00cf, 0x4381, 0xb233, 0xbff0, 0xba1c, 0x43ff, 0x41a2, 0x4376, 0xb20f, 0xbf49, 0xb2b4, 0x0dbc, 0x3189, 0x418e, 0xba32, 0x1bd8, 0xbf6f, 0xba48, 0xb207, 0xa2ad, 0x411d, 0x1a87, 0x2df4, 0x03c2, 0x2dfd, 0x4046, 0xbf90, 0x3240, 0xb015, 0x4003, 0xb29c, 0x44a2, 0x43b7, 0x4378, 0x0fd8, 0x42fe, 0x466c, 0x41a6, 0x1940, 0x4087, 0xbfba, 0x4106, 0x3ee3, 0x4219, 0x409b, 0x1976, 0x18e7, 0x24f6, 0x3f8d, 0xbad5, 0x40d9, 0x1c9a, 0x2916, 0x1cd9, 0x41e2, 0x4069, 0x1c0c, 0x45c3, 0xb0e6, 0xbfb9, 0x40dc, 0xb055, 0x0946, 0x349c, 0x4004, 0x42d3, 0x1e78, 0x43f0, 0x427c, 0xb20b, 0xb2da, 0x4206, 0x42aa, 0x1d43, 0x428a, 0x19a6, 0xb04f, 0x1f39, 0x2fdc, 0x0fa5, 0x067d, 0x2bbc, 0x405a, 0x0080, 0xbfdd, 0x1ab5, 0xba61, 0x46ec, 0x2e41, 0x43aa, 0x4080, 0x437a, 0x4112, 0x428a, 0x1efc, 0xbaf3, 0xbfc3, 0x4203, 0x45ed, 0x42d6, 0x4552, 0x0588, 0x2ab8, 0x19d9, 0x4391, 0x1c52, 0xbf4f, 0x422e, 0x43c2, 0x43be, 0x13f3, 0x40da, 0xb2ba, 0x3a04, 0xb2cf, 0x0bb2, 0x40ab, 0x42fd, 0x3dd3, 0x417d, 0xb08a, 0x4494, 0xa6be, 0xb229, 0x426d, 0x341a, 0x4312, 0x4212, 0x4258, 0x4195, 0xb2ff, 0x4011, 0x410a, 0x42c5, 0xbfc3, 0xadc5, 0x4367, 0xb29b, 0x4272, 0x416b, 0x42d9, 0xb0bb, 0x458d, 0x4290, 0xa66c, 0x1b4c, 0xac1d, 0x4092, 0xbfd5, 0xb081, 0x438b, 0x4029, 0x00df, 0xb009, 0x449c, 0x432c, 0x425f, 0x08fe, 0xb2dd, 0xba3c, 0x4053, 0x4111, 0x43e5, 0x42fe, 0xba44, 0xb26b, 0x3581, 0x439c, 0x354a, 0x0240, 0x3da2, 0x1bad, 0x4143, 0x407e, 0xbf76, 0x40ac, 0x403e, 0x402b, 0x1fdf, 0xb2c3, 0x40cf, 0x33fb, 0x42cb, 0xb2de, 0x4075, 0x1f1c, 0x43f7, 0x4081, 0x4386, 0xb248, 0x1b37, 0xb237, 0xb251, 0xbae9, 0xb2fb, 0x43da, 0x41d1, 0xba24, 0xbfc3, 0xad4b, 0x40ad, 0x0935, 0xb2b5, 0x43f4, 0xa290, 0xb24c, 0xaea0, 0xbaf1, 0xba23, 0xab45, 0x1e3d, 0x4219, 0x4350, 0x12e3, 0x0d50, 0x4545, 0xbf70, 0x1c23, 0xb28c, 0xb2df, 0x4375, 0x44d4, 0x42b2, 0x43dd, 0xbaf0, 0x41ef, 0xbf91, 0xb24b, 0xab23, 0x1a7b, 0x285e, 0xb09a, 0x40d1, 0x4149, 0x4084, 0x1cc5, 0xba75, 0x4348, 0xba27, 0xa8ac, 0xa7d2, 0x42b9, 0x334a, 0x4594, 0x40c1, 0x42e5, 0x41a1, 0x43f8, 0x4378, 0xa603, 0xb264, 0xba4d, 0xbf53, 0xb073, 0x1f6a, 0x1d2f, 0x1c93, 0x46b3, 0x4289, 0xa678, 0x1899, 0x434b, 0x438d, 0x43e2, 0xa5b7, 0x1f65, 0x3ea4, 0x44dd, 0x43ba, 0xb2b5, 0x2490, 0x4316, 0x18d2, 0xb257, 0x4472, 0xbfb4, 0x403c, 0x4036, 0x1989, 0xa6ae, 0xb211, 0x1d91, 0x43ea, 0x404a, 0x30c3, 0x43ac, 0x124b, 0x164d, 0x40e4, 0x4271, 0xb2c4, 0x2d41, 0x417b, 0xbf14, 0x40cc, 0xba78, 0x426e, 0x31c8, 0x44c3, 0xafc8, 0x43d0, 0x1d72, 0x0d4e, 0x1c65, 0xa4d5, 0xbf7d, 0x2ea8, 0xb0c3, 0x4424, 0x466c, 0x16b6, 0x43c5, 0x181f, 0x1f13, 0x398f, 0x42e2, 0x4354, 0x18b0, 0x2c22, 0x43c8, 0xae35, 0x44d4, 0x4113, 0x1834, 0x4590, 0xbf27, 0x400e, 0x42e3, 0x4289, 0xb28d, 0x0cf4, 0x32b2, 0x0f02, 0x414b, 0x40d5, 0x43c8, 0x4565, 0x376a, 0x330d, 0x1a0e, 0xbf53, 0xb0bd, 0x42b4, 0x422e, 0xb05a, 0x41bb, 0xa3cb, 0x0947, 0xb064, 0x42c4, 0x4156, 0x1bb2, 0x0104, 0x42c6, 0x4181, 0x4096, 0xba72, 0xb0da, 0x42e1, 0xa882, 0x427e, 0x4078, 0x4174, 0x4165, 0x406c, 0xbf01, 0xb0e3, 0x40ec, 0x29ba, 0xba6a, 0x183d, 0xb22c, 0x1b41, 0xb20d, 0xbf18, 0x027e, 0x42de, 0x429a, 0x4326, 0xbace, 0x412b, 0x4394, 0x43cf, 0xbfa1, 0xaeee, 0x4398, 0x18da, 0x42d5, 0x1c5e, 0x3817, 0x312b, 0x145c, 0xb294, 0x42c0, 0x40b9, 0x42fb, 0x3eb7, 0x44e1, 0x43ef, 0x4137, 0x0b6e, 0x4248, 0x4260, 0x4130, 0x43ea, 0x413f, 0x1ae0, 0x41b9, 0x43dd, 0x40dc, 0xbf8f, 0x42bb, 0x425a, 0x40cf, 0xbaca, 0x1f8a, 0xb243, 0x1802, 0x1e4f, 0x035e, 0x411a, 0x4160, 0xae4e, 0x1d60, 0xbfdb, 0x32ef, 0x42b2, 0x46d4, 0xbf70, 0x1ffa, 0x40bc, 0xb05a, 0xbaef, 0xb2ef, 0xb2aa, 0xbf8c, 0x194e, 0x42e9, 0x46d3, 0x4388, 0x40d0, 0x200f, 0xba17, 0xba0d, 0x443e, 0x2066, 0x410d, 0x40ca, 0xa869, 0xb07f, 0x425e, 0x432d, 0xb2b5, 0x43db, 0x1d95, 0x435a, 0xaf6a, 0xb2b0, 0x1b4d, 0x41de, 0xba5f, 0x1217, 0xbf81, 0xb217, 0xb224, 0x1db7, 0x1a1b, 0x2695, 0x406a, 0x3710, 0xb291, 0x3615, 0x3e59, 0x4181, 0x188d, 0x41fd, 0xbace, 0x46b2, 0x431a, 0x4121, 0x43bf, 0x186f, 0x4178, 0x00f5, 0xbf39, 0xa8c3, 0x0415, 0xba21, 0xbad8, 0x3fc6, 0x4116, 0x41f6, 0x443e, 0x0825, 0x43cf, 0x4388, 0x3e82, 0x171a, 0xb0e0, 0xbfa7, 0x42eb, 0x4006, 0x4029, 0x4128, 0x422a, 0x4279, 0x44a1, 0x45cc, 0x1913, 0x4174, 0xbf54, 0xb2a7, 0x4123, 0x4236, 0xba06, 0x425d, 0x1d1c, 0xbf12, 0x12b2, 0xba57, 0x19cf, 0xba6a, 0xb29e, 0xbfb6, 0x2718, 0xaf18, 0xba0b, 0xb2d7, 0x405e, 0x3f04, 0x268f, 0x426e, 0x1a97, 0xa68b, 0x1dab, 0x4104, 0x4661, 0x280a, 0x4550, 0x29ff, 0x41c9, 0xbf70, 0x432b, 0x1fb6, 0x2eb9, 0x419c, 0xb2cc, 0x2b88, 0x0da0, 0xb2b1, 0x42a6, 0x1ad6, 0xbfc1, 0x4599, 0x40b2, 0xb291, 0x43d2, 0x421a, 0x4209, 0x432a, 0xb04f, 0xbaf6, 0x16c6, 0x46d3, 0xbac0, 0x1dbe, 0xbff0, 0x40e7, 0x2de7, 0xbf21, 0x1d42, 0x0f07, 0x4274, 0x4151, 0x46e4, 0xb00a, 0xbad8, 0xbf34, 0x40a1, 0x2ef9, 0x4396, 0xbf53, 0x40e9, 0x4239, 0x42da, 0x405f, 0xba0c, 0x4368, 0xb2de, 0x4449, 0x4278, 0xb2e9, 0x4317, 0x405f, 0xb063, 0x4100, 0x4698, 0x40b0, 0x45c5, 0xb2df, 0x454f, 0x2aca, 0x42ea, 0x43b0, 0x3976, 0x4327, 0x2cbd, 0xbf7d, 0x1c79, 0x2d95, 0x41cd, 0x1af1, 0x408a, 0x2eae, 0x419d, 0x1859, 0x42bf, 0x4272, 0x412a, 0x42a9, 0x0694, 0x1f68, 0x4653, 0x3a31, 0x4277, 0xa6d1, 0x413c, 0xb23c, 0xbfc0, 0x42f6, 0x4377, 0x418f, 0x464c, 0x4133, 0x4129, 0xb294, 0xbfa9, 0x247d, 0x1b16, 0x21cc, 0xbf70, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x181236f9, 0x459e9e0f, 0xd961342f, 0x122ddd5e, 0x38b78514, 0x9508b2ee, 0x97896aed, 0xad92e120, 0xfd37fa46, 0x2e0a81f8, 0x8cf97fb4, 0x8d4b1243, 0x5e1eda21, 0x54da9be4, 0x00000000, 0x500001f0 }, + FinalRegs = new uint[] { 0x00fffff3, 0x000000cc, 0xffffffce, 0xffffffff, 0x0000007d, 0x00fffff8, 0x00001b20, 0xffff4210, 0x00000007, 0x6e4f1c33, 0xfffffaff, 0xfffffaff, 0x000000c5, 0x54dab658, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4239, 0xba45, 0xa273, 0xbacd, 0xb2e4, 0x0b51, 0x406c, 0x43a9, 0x1ae2, 0xbad9, 0x1eff, 0xbfc2, 0x42b3, 0xb0ef, 0xb0ab, 0x2572, 0x4344, 0x4165, 0xb279, 0x127a, 0xb2e2, 0xac76, 0x35b9, 0xb2d5, 0x41d2, 0x3d37, 0x43f2, 0xbf9a, 0xb207, 0x40b9, 0xba37, 0x21f3, 0x03e4, 0x430f, 0x444d, 0x1d73, 0x4354, 0x07a6, 0x1b8f, 0xb284, 0xb239, 0xbaed, 0xb2b2, 0xb2a6, 0x45da, 0xb20a, 0x1f8f, 0xb21e, 0x40fa, 0xa7cd, 0xb237, 0xbfd7, 0x444e, 0x4113, 0xbad8, 0x34c6, 0xbf43, 0xa315, 0x14bc, 0x416b, 0x4246, 0x3ae5, 0x46a3, 0xac12, 0xba29, 0xad08, 0x11c5, 0xb28e, 0x1443, 0xbf60, 0x41cb, 0xba32, 0x40a7, 0x40a0, 0x4206, 0xb250, 0xb268, 0x435b, 0xbf8f, 0x429d, 0xbfc0, 0x422b, 0x43e4, 0xb070, 0x1d90, 0x405b, 0x40f8, 0x435b, 0xb232, 0x4251, 0xbfac, 0x424d, 0x39e5, 0xb2b8, 0x46e3, 0x0171, 0x46bd, 0xbf90, 0x40c3, 0x36af, 0x429a, 0x41e9, 0xb25d, 0xb000, 0x3c9d, 0x096e, 0x4030, 0xbfd1, 0x1f7e, 0x27fe, 0xb073, 0x41e0, 0xa276, 0xb0ae, 0xb062, 0x1d63, 0xb203, 0xbfaf, 0x4323, 0x4376, 0xbac1, 0xb0e5, 0x43b8, 0x26f1, 0x4120, 0x37ee, 0xbf7a, 0xb232, 0x43ca, 0x43b9, 0xb2c2, 0x46c0, 0x3ea3, 0x152b, 0x4202, 0x4232, 0xb2b0, 0xbf15, 0xb299, 0xba6a, 0x4199, 0x102a, 0x4023, 0x401a, 0xaa93, 0x4190, 0xba4c, 0xa1ab, 0xb2bd, 0xb017, 0x4375, 0x40b6, 0x43b8, 0x18cf, 0x18c8, 0x406c, 0xac65, 0x2956, 0x0fe6, 0x4001, 0x1c31, 0x423a, 0x42c6, 0x41fa, 0x1bf9, 0x40b7, 0x21bc, 0xbf43, 0x405e, 0x1ce7, 0x41a4, 0x415e, 0x41eb, 0x40a9, 0x41fc, 0xb004, 0x42e1, 0x4370, 0x42db, 0xbaee, 0x42df, 0x0d78, 0xb0f8, 0x4394, 0xb2a4, 0x3923, 0x3c97, 0x4206, 0xb0fb, 0xb21c, 0x10cb, 0x4050, 0x3634, 0xbf03, 0xb0ad, 0x46ed, 0x38c3, 0x1945, 0x4236, 0x17c2, 0x282d, 0xbae2, 0x40b6, 0x22e8, 0x42ac, 0x1b6d, 0xba53, 0x414e, 0xb013, 0xb0c2, 0x41ff, 0xb281, 0x1cd3, 0x41da, 0x4141, 0x420f, 0xa91b, 0x35d5, 0xa6d8, 0xba09, 0x4156, 0x278e, 0xbf11, 0x45be, 0xa4ee, 0xb28b, 0x3d9d, 0xb2b3, 0x0dea, 0xa044, 0x43d1, 0x2843, 0x425b, 0xa5dc, 0x26b9, 0x1ef7, 0xba61, 0x028d, 0xa302, 0xb061, 0x4306, 0x2b7d, 0x1b33, 0x4215, 0x1dbe, 0x40cc, 0x41af, 0x1fef, 0xbf77, 0x4308, 0xba38, 0x4306, 0x4099, 0xb275, 0xbf6d, 0xbae5, 0x1f9f, 0x1cc6, 0x1101, 0x42ca, 0x2e58, 0x41a7, 0xb235, 0x29fa, 0x43dd, 0x41d8, 0x402c, 0x4138, 0x41e8, 0x3d38, 0xba10, 0x41f6, 0xb26f, 0x1e35, 0xba03, 0xb2b3, 0x1c33, 0x40f7, 0x3fbf, 0x4359, 0x45ae, 0xbf1f, 0x405e, 0x43f4, 0x419a, 0x207e, 0xa253, 0xb084, 0xac7c, 0xb232, 0xbf00, 0xb01e, 0x1b16, 0x318e, 0xae09, 0x447c, 0x1a2c, 0xaf1f, 0x0953, 0xaf93, 0xb23f, 0xb286, 0x43b0, 0x1cdb, 0xba74, 0x2e38, 0xbf3d, 0x3640, 0x1eba, 0x3f29, 0x4339, 0x43b4, 0x257a, 0xba48, 0xb20d, 0x188c, 0x412b, 0x1232, 0xba24, 0x464e, 0xb2f5, 0x42fe, 0x40d8, 0xa5b3, 0xb210, 0x43ad, 0xaf54, 0xb273, 0x0770, 0x4261, 0x415a, 0x42cf, 0x3104, 0x1c29, 0x368d, 0xba20, 0xbfd2, 0x416b, 0xb2ed, 0x40a7, 0x1ff4, 0xbaf4, 0x1d6a, 0x1d89, 0x4398, 0x1839, 0xb0d7, 0x458e, 0xba38, 0x4019, 0x432c, 0x028b, 0x4323, 0xb09c, 0x1444, 0x423d, 0xb286, 0xbfe0, 0xba19, 0x1439, 0x346c, 0xbfa5, 0xb0ab, 0x2da0, 0x403d, 0x40b0, 0x404a, 0x105c, 0x1c9c, 0x408b, 0x4239, 0x2701, 0x226e, 0x41ba, 0xb246, 0x4123, 0xb0ba, 0x40b6, 0xbfcf, 0xb065, 0x4220, 0x4200, 0x410e, 0x0e39, 0x415f, 0xba6e, 0x43ef, 0x424c, 0x3ba0, 0x3e8d, 0x45b6, 0x4037, 0x4179, 0x4241, 0xb0dd, 0xbf2f, 0x40ff, 0x434a, 0x4336, 0xbada, 0xb279, 0xb0f4, 0x11c7, 0x41c2, 0x1e64, 0x4301, 0x1e52, 0x1892, 0xbfd2, 0x409a, 0xba57, 0x4197, 0x432d, 0xbf25, 0x40a2, 0xac2d, 0xaaed, 0xb257, 0x34e1, 0xba04, 0x4258, 0x18b0, 0x4384, 0x1397, 0xa3ae, 0xb0d7, 0xb28f, 0x425e, 0x422b, 0x4314, 0x222d, 0x3b0e, 0xbf97, 0x41c3, 0x4209, 0x43b1, 0x2442, 0xa92c, 0xb02c, 0xbf35, 0xb293, 0x2e27, 0x41f5, 0x4322, 0x0a11, 0x40ee, 0x0966, 0xbad5, 0xb28e, 0x19b3, 0x4015, 0x45be, 0xb25d, 0x415d, 0x41e0, 0x1bc8, 0xbfd9, 0xb08a, 0x1c28, 0x4281, 0x4309, 0x430a, 0x402c, 0xb2c2, 0x40e4, 0xba22, 0xb00d, 0x418c, 0x32ee, 0x40a5, 0xb2bb, 0x43fb, 0xb2f9, 0x43bc, 0x41b8, 0x4098, 0x1a85, 0x4162, 0x41d2, 0x40be, 0x4613, 0x2b71, 0x0ee4, 0xb0ef, 0xbae0, 0xbf17, 0x43cb, 0x4082, 0xb20f, 0x41a0, 0x198a, 0x2530, 0x2c43, 0x3981, 0x1f52, 0x423e, 0xbf04, 0x43a2, 0x4043, 0x4079, 0x1bb9, 0x4268, 0x2138, 0x1aee, 0xbf88, 0x419c, 0xb2cd, 0x1cf4, 0x03df, 0x1acf, 0x4589, 0xba06, 0x4207, 0x43d8, 0xba09, 0xbfa0, 0x4606, 0x4243, 0x417c, 0x3043, 0xbf6e, 0x4365, 0x4488, 0x405d, 0x2d15, 0x41c4, 0x4111, 0xbf62, 0x4321, 0x4135, 0x3c6e, 0x0893, 0x260f, 0x400c, 0x4091, 0x4135, 0x40a7, 0x116e, 0x467d, 0xbf2e, 0x0fae, 0x4091, 0xa2a7, 0x0bed, 0x4550, 0x4268, 0x4305, 0xbf46, 0xb2e8, 0x3fe3, 0x1942, 0x439f, 0xa9d4, 0xbf9b, 0x4236, 0x1d7a, 0x28a1, 0xb0d2, 0x3a43, 0x40f6, 0x42c5, 0x4275, 0xb245, 0x43c2, 0x3019, 0x1eff, 0xa434, 0x4360, 0x3af7, 0x42bb, 0xbf80, 0x44ab, 0xbf4d, 0xab4f, 0xb068, 0xaf79, 0x4291, 0xb281, 0x4621, 0x42fa, 0xb2fe, 0x10f6, 0xbf80, 0x4018, 0x41ed, 0xb24f, 0xb02e, 0x456f, 0x41da, 0xbf1d, 0xbf90, 0x4118, 0x4307, 0x422d, 0x430e, 0x41c3, 0x0fe8, 0x315d, 0xb2b8, 0x1d2f, 0x1e41, 0x0a83, 0x41ac, 0x1d8c, 0x43b9, 0x4691, 0x45c2, 0xba25, 0x1cf0, 0xba01, 0x0098, 0xbfb2, 0x05fd, 0x404f, 0x4268, 0x0194, 0xabd2, 0xbfd0, 0xbf93, 0x1489, 0xb274, 0x2bec, 0x4225, 0x2a29, 0xba47, 0x23e9, 0xa627, 0xba14, 0x40ea, 0xa9ef, 0x27b9, 0xba57, 0xbf5d, 0x1a24, 0x465d, 0x4319, 0xba31, 0x4170, 0x04ed, 0xb295, 0xbaca, 0x417b, 0x4130, 0xb28a, 0x406b, 0xb252, 0x4090, 0x371e, 0x43fd, 0x1b99, 0x222a, 0x40f2, 0x4607, 0xbf91, 0xa406, 0x0d68, 0x410b, 0x41cb, 0xbfca, 0x43b5, 0x1cc7, 0x3cf1, 0xb275, 0x438c, 0x409d, 0xa26c, 0x433d, 0x1e60, 0xb24f, 0xbf63, 0x1b7e, 0x423d, 0x129a, 0xb0d1, 0x40b2, 0x1f17, 0x1fa4, 0x4095, 0xb068, 0xbf70, 0x40aa, 0x2399, 0x012b, 0x00cb, 0x410b, 0x2251, 0x1eed, 0x1f84, 0xaab2, 0xb064, 0x4090, 0xb043, 0x4180, 0x46a0, 0x2617, 0x4203, 0x4122, 0x136f, 0xbf46, 0x195a, 0x43d9, 0xb025, 0xb02b, 0x42db, 0x41e2, 0xa62b, 0x404e, 0xba7f, 0xbfa9, 0x31f0, 0x43db, 0x46b1, 0x404a, 0x1862, 0x425a, 0xb21e, 0x41a0, 0x4573, 0x40f0, 0xb228, 0x423b, 0x1f8f, 0x410f, 0x0667, 0x44a3, 0x02bf, 0xb280, 0x4008, 0x44d0, 0x4216, 0xbf61, 0x3b93, 0x420f, 0x0b94, 0xa28c, 0x432b, 0x1fa5, 0xbf5e, 0x4305, 0x42dc, 0x42ce, 0x4335, 0x1c24, 0x4615, 0x4295, 0x11a7, 0xba38, 0x42e6, 0x46aa, 0x1faa, 0xb2b0, 0xb222, 0x1112, 0x094a, 0x43d4, 0x4034, 0x1b1b, 0xbf66, 0x1fa4, 0x4082, 0x4228, 0x1b74, 0xb252, 0x3875, 0xb2f6, 0x23ec, 0x420b, 0x40b2, 0x46a8, 0x06f2, 0x1954, 0xb206, 0xbfac, 0x421d, 0xbac3, 0xbf80, 0xb0e5, 0x4274, 0xbfaf, 0xbf70, 0x36bc, 0xb2ae, 0xba44, 0x405c, 0xbf70, 0xb0be, 0xbadf, 0x304b, 0x4231, 0xb250, 0x18ca, 0xba6b, 0x4144, 0xb248, 0x2d0a, 0xbf90, 0x4474, 0x43e4, 0x4307, 0xa8c6, 0x4153, 0x41c6, 0xbf5c, 0x4161, 0x0875, 0x432f, 0xb234, 0xb2f0, 0x417e, 0xb2b2, 0x3ea9, 0x42d7, 0xba5e, 0xb04b, 0x1d7c, 0x4227, 0xbfbd, 0x422f, 0x43db, 0x3b1d, 0x41a3, 0x431b, 0xb29f, 0x433a, 0xb275, 0xa6ed, 0x4035, 0x07e4, 0x1978, 0x4096, 0x1f30, 0x386d, 0x4132, 0x41e5, 0xb254, 0x04e9, 0x1f81, 0xb2c5, 0x1507, 0xbf49, 0x4464, 0x431d, 0xa8cc, 0x41ab, 0x42df, 0x4158, 0x43e5, 0x4243, 0xb230, 0xb24b, 0xb262, 0x0f27, 0x42c5, 0x4321, 0xbad1, 0x0b26, 0x24b1, 0x18e4, 0xb285, 0xbf8d, 0x434a, 0x42f9, 0x4007, 0x4061, 0xbfc3, 0x3a04, 0x45ed, 0xa055, 0x44c8, 0xa20f, 0x423d, 0x1ee2, 0x44b5, 0x1e41, 0xae96, 0x0d96, 0x411b, 0x4384, 0x45ab, 0xbf15, 0x1f8a, 0x0b2d, 0x42e4, 0x437d, 0xb23c, 0x3ca6, 0x1d27, 0x429f, 0xbad6, 0x4056, 0x429a, 0xbf02, 0xb0c7, 0xbfb0, 0x4300, 0x413c, 0x40ca, 0x42e3, 0x426b, 0x08df, 0xa9a2, 0x44ec, 0x42e1, 0x4295, 0x0436, 0xb2e8, 0xbf53, 0x4499, 0xa2c7, 0x4000, 0x4274, 0x4299, 0xb211, 0x40c7, 0x31f0, 0xb25e, 0xb2f4, 0x413b, 0x1d10, 0x3f32, 0xbf22, 0x4381, 0x2c17, 0xb2a3, 0x4075, 0x3d15, 0x4455, 0x1a65, 0x40c1, 0x2389, 0x41eb, 0x1238, 0xb2ab, 0x1fea, 0x4022, 0x1f25, 0x4072, 0x403e, 0xb021, 0x45c5, 0x1c07, 0x421a, 0x4557, 0xbf60, 0x416b, 0xb2f3, 0xbf7d, 0x4005, 0x3e21, 0xbf60, 0xb225, 0x409b, 0x4340, 0x00db, 0x413d, 0x4275, 0x4574, 0x44b1, 0x412f, 0x4049, 0x2295, 0x20e6, 0xbf0c, 0x4021, 0x185e, 0x4317, 0x41a6, 0xb24e, 0x41c4, 0x2d7b, 0xb07b, 0x0b1e, 0x2317, 0x4254, 0x295f, 0x42f9, 0x4644, 0x1a5e, 0xb208, 0xba1e, 0x400d, 0x1bc5, 0xa2fc, 0x194f, 0xbfb5, 0xb26e, 0x40fd, 0x37c1, 0x4314, 0x432e, 0xaa7f, 0x17da, 0x43e1, 0x1ad4, 0xb06b, 0x4001, 0x071f, 0xba54, 0x1eeb, 0x1ddb, 0x2da8, 0x432c, 0x307f, 0x41c2, 0x2eea, 0xbad1, 0x43d7, 0xbf5c, 0x1edb, 0xbaed, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x7050d002, 0x2ad824dc, 0xcda44a5a, 0xbe440822, 0x6fa909fc, 0x5324049c, 0xb3f8f834, 0x42ed399f, 0x207b9830, 0xa4b5347a, 0x1f876040, 0xfb4d4a81, 0x16ce792a, 0x219b1d90, 0x00000000, 0x300001f0 }, + FinalRegs = new uint[] { 0x0000007f, 0x00000000, 0x00000000, 0x00000004, 0x00000000, 0x00000000, 0x17000000, 0xffffffff, 0x00001631, 0x0000160f, 0x00000001, 0x16ce8d4b, 0x16cfdbf9, 0x000166eb, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x1d91, 0xbae9, 0x3565, 0xb2f7, 0x4627, 0x41f1, 0xbfe8, 0x4294, 0x0fb8, 0x2445, 0x0e33, 0x29fa, 0x116e, 0x345c, 0x44b5, 0xb2d2, 0x4013, 0x4246, 0x2612, 0x4133, 0x42bc, 0xb28a, 0xbf14, 0x4370, 0x194f, 0x4319, 0x4264, 0xb0d1, 0x1c87, 0x4329, 0xbfc4, 0x441e, 0x4212, 0x1b80, 0x4261, 0x0436, 0x46e1, 0x444e, 0x1a0b, 0x40d3, 0xa4e6, 0x407b, 0x4201, 0x426d, 0x43a3, 0x436d, 0x1a3c, 0x1b83, 0xad83, 0xbadf, 0x4016, 0xb058, 0xacc4, 0xbf90, 0x43ea, 0x4445, 0xb2fe, 0x08da, 0xbf29, 0x41a9, 0x0835, 0x0f8d, 0x1e32, 0x0a48, 0x41f4, 0xbaca, 0x41e4, 0x41ac, 0xba02, 0x1db6, 0xb099, 0x1f86, 0xbfd0, 0xa3b6, 0x463a, 0x4108, 0x2cb1, 0xb0ce, 0xb261, 0xba1b, 0x1edc, 0xba7a, 0x4285, 0xb206, 0x4078, 0x4252, 0xa63c, 0x107d, 0xbf9b, 0x4331, 0x40c1, 0x415c, 0x4062, 0x186b, 0x1e9d, 0x4014, 0x32c3, 0x43e7, 0x40e6, 0x4382, 0xba6e, 0x4332, 0x1cea, 0x0bb1, 0x1de5, 0x429c, 0x41f1, 0x4243, 0x0059, 0x41db, 0xa558, 0xbf7c, 0x4203, 0x43a7, 0x43d8, 0x4460, 0x413e, 0x439e, 0x424d, 0x1ae1, 0xbadd, 0x1cb2, 0x415a, 0xb240, 0x311d, 0x41de, 0x155b, 0xa4c4, 0xb25d, 0xb2b4, 0x4329, 0x445c, 0x40be, 0xa150, 0x4284, 0x44eb, 0x456d, 0x40a2, 0x439d, 0xbf6d, 0x4073, 0xa3d2, 0xb036, 0x41c9, 0xbad3, 0x41a2, 0x432a, 0xba1c, 0xbafe, 0x42a1, 0x43e8, 0x4120, 0x414b, 0xb202, 0x251f, 0xba64, 0xbac8, 0x1977, 0x4291, 0x19a1, 0x1459, 0x1af3, 0xb0ca, 0x3165, 0x1a2f, 0xb224, 0xa3d5, 0xbfd5, 0xb20e, 0xb2df, 0x4689, 0x4143, 0xbae5, 0x4169, 0x40e7, 0xa294, 0xaa9c, 0xba66, 0x2969, 0x35a9, 0x4363, 0xbf69, 0x40e8, 0xa271, 0x41f2, 0x4391, 0x426b, 0x40c5, 0x411c, 0x46ed, 0x1839, 0x4345, 0x4202, 0x4056, 0xb26c, 0x406d, 0x16ec, 0x4276, 0x1249, 0xba23, 0x4301, 0x409a, 0xbfa4, 0x408e, 0xb2d2, 0x0054, 0x4092, 0x421f, 0x4313, 0xb2f2, 0x4115, 0x1c95, 0x1ba7, 0xba42, 0x0497, 0x0d14, 0x1c8d, 0x41ca, 0x400e, 0x436d, 0x17de, 0x1cc0, 0x417c, 0x1e15, 0x1250, 0x4288, 0x0add, 0x2783, 0x40d7, 0xbfce, 0xbfa0, 0x41e4, 0x3706, 0x33b9, 0xba5f, 0xb063, 0xb215, 0x421c, 0x04a2, 0xbf0b, 0xa42c, 0xba4a, 0x425f, 0xb2f4, 0x0891, 0xbf60, 0x1796, 0x43d7, 0x1520, 0xb26d, 0xbf9c, 0xb28d, 0x1b2e, 0x4137, 0x41be, 0xa19f, 0x40eb, 0x4247, 0x2c5d, 0x40fd, 0x41e9, 0xb260, 0x4069, 0x2417, 0x407b, 0xbf62, 0x2e6e, 0x42e9, 0x1f8b, 0xbae8, 0x1d37, 0x3234, 0x4287, 0xba75, 0x4232, 0x245c, 0x1176, 0xb025, 0x3f5c, 0xbfd9, 0xba5c, 0x40ad, 0x43d1, 0x189d, 0xba3c, 0x41c0, 0x4240, 0x41f1, 0x4276, 0x42ed, 0x4242, 0x4398, 0x40de, 0x444b, 0xb295, 0x411c, 0x0bf0, 0x3c69, 0x40d3, 0xbf1d, 0x41e0, 0x320b, 0x43bd, 0x02b9, 0x41da, 0xbaf3, 0x1052, 0xb27b, 0x4116, 0x1de7, 0xb2d3, 0x41b7, 0x1b37, 0x15a2, 0xb074, 0x4246, 0x428b, 0x1dbe, 0x1dd3, 0x1a49, 0xba20, 0x04d8, 0xbfe1, 0xb0fc, 0x400a, 0xbaf7, 0xbae1, 0xb043, 0x2790, 0xb273, 0xabe4, 0x405b, 0xa232, 0xbf33, 0x2d1d, 0x40f3, 0x4129, 0x133f, 0x41a7, 0xa789, 0x4264, 0x292e, 0x4056, 0x2f67, 0x40c9, 0x0a78, 0x41b6, 0x43cc, 0x0601, 0x4177, 0x3fd1, 0x42bf, 0x1e48, 0x4256, 0x426d, 0x1342, 0x4279, 0x4341, 0xab7e, 0x43f5, 0xb293, 0x2783, 0x2ef7, 0xbfdb, 0x43e2, 0x4098, 0x41eb, 0x4115, 0x1b25, 0x1f61, 0x43b5, 0x0cf3, 0x3f68, 0x0ce3, 0x004b, 0xb2de, 0x41e2, 0xb07e, 0x1ed0, 0x2e59, 0xba46, 0xba44, 0x3742, 0x17bf, 0x3e63, 0xbf07, 0xa4a5, 0xba1f, 0x4249, 0x3421, 0x435b, 0x434f, 0xba74, 0x0c5d, 0x4121, 0x3700, 0xb226, 0x3869, 0xb279, 0x46dd, 0x441c, 0x43da, 0x40ab, 0xbf2d, 0x1e81, 0x422c, 0x1c93, 0x4090, 0x424c, 0x4108, 0xb213, 0xa996, 0x3006, 0x438e, 0x41f8, 0x1c12, 0x20d7, 0x41d7, 0x0620, 0x4216, 0x22be, 0xba02, 0xbf60, 0x18b1, 0x431f, 0xbad4, 0x0f22, 0x4607, 0xb257, 0x38ed, 0xbfca, 0xb25c, 0xb245, 0x401b, 0x4009, 0x4303, 0x1615, 0xbf23, 0x42e0, 0x42db, 0x428f, 0x42f0, 0xb205, 0x1d0b, 0x404b, 0x2453, 0xba3d, 0x0eb6, 0x4319, 0xb073, 0x0b49, 0x43e2, 0xba38, 0xbf41, 0x1dc4, 0x1ea7, 0x1ffc, 0x408f, 0x3064, 0x189c, 0xa006, 0xbaef, 0x405e, 0x0d23, 0x2a88, 0x43a7, 0x401c, 0x092a, 0x4023, 0x440f, 0x41aa, 0x407f, 0x353c, 0x1de4, 0x4250, 0x4431, 0x4606, 0x4160, 0x42c3, 0xbf5e, 0x1f23, 0x3650, 0x4652, 0xa61a, 0x44db, 0x446f, 0x418b, 0xba3a, 0xb217, 0x03a3, 0xb03c, 0xb00e, 0x42bf, 0x4582, 0x41af, 0xb22c, 0x1c40, 0x42d8, 0xa1af, 0x42a8, 0x40d6, 0x40c3, 0xb27b, 0xbf88, 0xa181, 0x18bd, 0x1d56, 0xb2c3, 0x3309, 0xbfd9, 0x0e90, 0x1b15, 0xb2cf, 0xbafd, 0x446c, 0x4166, 0x427f, 0x2cd4, 0xbff0, 0xb20b, 0xba7f, 0x4374, 0xbf44, 0xb0fa, 0xb2a7, 0x4595, 0x4240, 0x4405, 0xb253, 0xba4d, 0x1879, 0xb231, 0x2937, 0x2c54, 0x1b7c, 0x0fca, 0xb0be, 0xbf00, 0x428c, 0x2f47, 0x4248, 0x4075, 0x121d, 0x40bc, 0xbad3, 0xba4e, 0x44f8, 0xbf7e, 0x42f5, 0x4319, 0xb2d6, 0xa214, 0x41ca, 0x41b6, 0xb001, 0xbf88, 0xa731, 0x4452, 0x1ee4, 0xb2ab, 0x39e6, 0xb092, 0x4097, 0x407b, 0xba6b, 0x464d, 0x44c1, 0x40d8, 0x100e, 0x4010, 0x46eb, 0xbafb, 0x07e0, 0xb2d3, 0x4332, 0x41ab, 0x4022, 0x46b9, 0x3214, 0xbf8d, 0xba7d, 0x407a, 0x42c2, 0x195d, 0x2988, 0xb23d, 0x4382, 0xbf46, 0xb236, 0x44c2, 0x1f13, 0x0599, 0x1edf, 0x1984, 0xba34, 0x25fe, 0xb202, 0x2128, 0xb227, 0xbf5e, 0x1bd5, 0x4265, 0x454a, 0x1485, 0xa79d, 0x1484, 0x409e, 0x431e, 0x4028, 0x4272, 0xba7d, 0xb2d3, 0xb228, 0x4093, 0x3c65, 0xba5d, 0x3bb9, 0xb2cf, 0xbf76, 0xb053, 0x4272, 0x41fd, 0xb052, 0x3908, 0xb042, 0x407f, 0xb29e, 0x43cd, 0x2c83, 0x4310, 0x4680, 0xb210, 0x0762, 0x4350, 0xbf77, 0xb243, 0x1b98, 0xb0f5, 0x4041, 0xb287, 0x4038, 0xbad5, 0x408d, 0xabd0, 0xba34, 0xbf71, 0xb2cd, 0x4168, 0x1b2e, 0x42e3, 0x1824, 0xb21f, 0x3ab2, 0x025b, 0xb23f, 0x45f3, 0xbaf0, 0x1d87, 0x40e1, 0x4177, 0xb21c, 0x4362, 0x43c5, 0x3708, 0x0554, 0x1d4d, 0xb073, 0xbfe4, 0xac1d, 0x4172, 0x0cf2, 0x4048, 0xbaf8, 0x442b, 0x1a0b, 0xbf67, 0x426e, 0x1c1c, 0x40a9, 0xbf60, 0x434d, 0xbae5, 0x4426, 0xb280, 0x150b, 0xb2ac, 0x4421, 0xbf2d, 0x1ede, 0x42c3, 0x426f, 0xbf90, 0x40aa, 0x4175, 0xb205, 0xb2f0, 0xaf19, 0xbfa7, 0x43b3, 0xba7f, 0xada2, 0x40ff, 0x4263, 0x0093, 0x42a8, 0xb2d3, 0x4114, 0x381b, 0x417b, 0xb2e3, 0xb206, 0xa446, 0xb204, 0x4192, 0x2850, 0xbfc0, 0x4078, 0x0d01, 0xbf53, 0x1bf6, 0x4218, 0x4065, 0x435e, 0x016b, 0x3bcd, 0x4326, 0x4335, 0xadc9, 0x1c41, 0x2724, 0xbad3, 0x4125, 0x42f7, 0xae86, 0xb2ac, 0xaced, 0x42fb, 0x4572, 0xba46, 0x4178, 0xbfc0, 0x0a8a, 0x02c0, 0xa6fb, 0x4117, 0x430b, 0xbf73, 0x1ee6, 0x1b3a, 0x42e0, 0x4279, 0x40d8, 0x45a4, 0xb204, 0x40e3, 0x3114, 0x4274, 0xa303, 0x421b, 0xbac2, 0x42c5, 0x4284, 0xbfca, 0x4142, 0x1e76, 0x41f2, 0xb272, 0x1ec0, 0xb2fa, 0x4494, 0x1ec1, 0x1ec0, 0x414a, 0xba7d, 0x4647, 0x439b, 0xbfa0, 0x40fa, 0xbaef, 0x0dd9, 0xb26c, 0xa054, 0x4032, 0x40aa, 0xba37, 0x456f, 0xbfb3, 0x40a4, 0x4027, 0x12fe, 0x25e9, 0x259e, 0x2bbc, 0x4223, 0xba2c, 0x2553, 0xbf24, 0xa784, 0xb2db, 0xb23c, 0x4362, 0x40c8, 0x24fc, 0xa161, 0x08af, 0xb26a, 0x43cc, 0xb2f7, 0x41ad, 0xbfb6, 0x44a3, 0xbfb0, 0x1c2a, 0xb2cc, 0x456f, 0x040a, 0x400e, 0xb270, 0x288c, 0x4056, 0x29c3, 0x4330, 0x1244, 0x128a, 0xbfa6, 0x1f86, 0x3fb5, 0x40ec, 0x40ab, 0x4078, 0xba5d, 0x43a9, 0xbad0, 0x4052, 0x40e3, 0x428e, 0x4603, 0xbf03, 0x416b, 0x0d03, 0xa3b5, 0x1f7e, 0x3e32, 0xb04e, 0x4085, 0xb2d4, 0xb211, 0x4394, 0x3b55, 0xb2d7, 0xbf3b, 0x1379, 0x41d9, 0x4121, 0xb250, 0x43e4, 0xb231, 0x14c6, 0x4245, 0x40ce, 0xa90a, 0x45f4, 0x43bd, 0xb28c, 0xb058, 0x41ac, 0xb276, 0x322f, 0x42b0, 0xa84f, 0x42f0, 0x43da, 0x299e, 0xbfc1, 0x13ef, 0x4298, 0x4304, 0x1aea, 0xb006, 0x4352, 0x41cc, 0x1c38, 0x2f37, 0x439e, 0x4329, 0x45f6, 0x41d2, 0x4253, 0xb209, 0x1129, 0xb27f, 0xbaec, 0x40fd, 0xbf74, 0xa497, 0x372a, 0xbaea, 0xb06a, 0xb24c, 0xba25, 0x45d2, 0x1638, 0xb0b8, 0xba4e, 0x406b, 0xbaf5, 0xbf35, 0x4340, 0x1ffe, 0x4075, 0x1ebb, 0x4029, 0xba36, 0x3d21, 0x0bf9, 0x1dd1, 0x43f4, 0x4316, 0xb241, 0xb212, 0xb2d7, 0x42cc, 0x4379, 0x1e94, 0xb03b, 0x3bc7, 0xbfd0, 0xba35, 0xa8cd, 0x009f, 0x1f3f, 0x436e, 0xbaf2, 0x4369, 0x1f29, 0x4164, 0xbf39, 0x433f, 0x403d, 0x316c, 0x4252, 0x4204, 0xb2b4, 0xba22, 0x19f7, 0x4120, 0x412c, 0x05e0, 0xb2c5, 0x3aa5, 0xbf67, 0x46a0, 0x4076, 0xb2aa, 0x436f, 0x43a9, 0x4183, 0x1c21, 0x12f9, 0x408a, 0x41bb, 0x42a6, 0x220c, 0xb2cf, 0x3de8, 0xbfb5, 0xbf00, 0x2586, 0xb2ce, 0x40ba, 0xa486, 0xa26e, 0x1b02, 0xb063, 0x40d3, 0x07ab, 0x26fd, 0xba10, 0x43ac, 0xbf24, 0x3285, 0x3eb9, 0x1f75, 0xb202, 0x2771, 0xacd9, 0xba3d, 0x4326, 0xbf60, 0xb263, 0x4013, 0x3cb8, 0xbaef, 0x4074, 0x42f5, 0x43f8, 0x40d9, 0x20ec, 0x42f6, 0xbf2b, 0x4336, 0x4321, 0xb06c, 0x2f28, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x45444af8, 0x3713ac06, 0xc0dfa961, 0xa4318add, 0x64c2ab43, 0xd5a78f68, 0x13e65e07, 0x035cff70, 0xf5623146, 0xb8ad178f, 0xade3820c, 0x0224fa96, 0x9850b729, 0xe11a6537, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0x000000ec, 0x00000000, 0xffffffff, 0x0000006b, 0x0000014c, 0x71000000, 0xe1eca9ff, 0x00000000, 0x00003ff7, 0x00000000, 0xade3820c, 0xe1ec9c83, 0x9850b74d, 0xe1eca7b7, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xa520, 0xb223, 0x4254, 0x04d2, 0xb2bb, 0x0b78, 0x4241, 0x3e94, 0x4367, 0xbf06, 0x40db, 0x409c, 0xbfb0, 0x40bd, 0xb297, 0x44fa, 0xb2b3, 0x406c, 0x4348, 0xb2ac, 0x4158, 0x40b0, 0x2122, 0x3cd3, 0x433b, 0xbf01, 0x42bb, 0xb2b2, 0x3a8d, 0x43f1, 0x4200, 0x1c8e, 0xada7, 0x40f6, 0x403d, 0x40db, 0x422b, 0xbad1, 0xa3da, 0x4319, 0xa9a2, 0xb21f, 0x4231, 0x41b5, 0x4104, 0x043a, 0xba2f, 0xbfb6, 0x41b2, 0x05c3, 0xb0be, 0x1de0, 0xa730, 0xba13, 0x05fe, 0x41ea, 0xbf4a, 0xa3f1, 0x182e, 0xb217, 0xad55, 0x44d9, 0xb0c0, 0x4389, 0x401f, 0xba25, 0x18ab, 0x02e1, 0x46dd, 0xba26, 0xba21, 0xb2bc, 0x42a2, 0x124d, 0xbfd0, 0xb271, 0x01b0, 0x42e0, 0x4011, 0xa094, 0xb02f, 0x43dd, 0xbf74, 0x41b0, 0x41bc, 0x4133, 0x105d, 0xb28f, 0xb0d0, 0xaf65, 0xaf83, 0xbf71, 0x4196, 0xb06b, 0x4122, 0x400d, 0x41ac, 0x1863, 0xb2e0, 0xb218, 0x41b8, 0x4588, 0xb2a0, 0xbfdb, 0x436c, 0x0f45, 0x0994, 0x43ab, 0x4106, 0x2c4b, 0x01db, 0xb048, 0x4495, 0x42a5, 0x40ae, 0x419c, 0x41ca, 0x40be, 0xa8ed, 0x3fe9, 0x42ea, 0x4127, 0x425f, 0x4499, 0x1c46, 0xb258, 0x3f68, 0xa832, 0xbf0c, 0x42cf, 0xa86c, 0x42ce, 0x4113, 0xb24f, 0xb0ec, 0xb0e0, 0x3d7f, 0xa273, 0x40cd, 0xb03f, 0x41ef, 0x4266, 0x413e, 0xb20c, 0x43b9, 0xb019, 0x4097, 0x05f4, 0x3445, 0x408b, 0x409a, 0x2673, 0xbf4f, 0x417b, 0xbaee, 0x443c, 0xbae3, 0x426c, 0xbf2d, 0x4185, 0x4366, 0x431c, 0x3f62, 0x42bd, 0x46c5, 0x416f, 0xb205, 0x4011, 0x07d0, 0x06d4, 0x413a, 0xbf5b, 0xb2b5, 0x1ae2, 0xa9a9, 0xba0e, 0x401c, 0xb22e, 0x44f0, 0x17c2, 0xb036, 0x1cf0, 0x4096, 0x4299, 0xba11, 0xa27d, 0x1e0a, 0xbf7f, 0xb285, 0x4342, 0x24a7, 0x1a9e, 0x2cbc, 0x4218, 0x0e6d, 0xb21d, 0x437c, 0x4392, 0x41e9, 0xb05d, 0x4101, 0xa9d4, 0x4371, 0x41ab, 0x1bc2, 0x4241, 0x4002, 0xb2d4, 0xb016, 0xbac0, 0xb201, 0x4398, 0x40a2, 0xbf49, 0x430c, 0x1c44, 0x3eed, 0xab02, 0xb20f, 0xbf3f, 0x17a7, 0x418f, 0x4151, 0xba01, 0xbf9b, 0x41a4, 0x0136, 0xa343, 0xb29b, 0x2019, 0xab1a, 0x43d2, 0xbf5e, 0x42b3, 0x297f, 0xb2ce, 0x2057, 0x1d80, 0x1633, 0x4310, 0x337b, 0x40bc, 0x4077, 0xb224, 0xb298, 0x2a78, 0x4233, 0x0d0e, 0x32a6, 0x42cb, 0x4396, 0x40a2, 0xb00f, 0x4050, 0xb271, 0x40ee, 0x410a, 0xbfc8, 0xb230, 0x1a49, 0x4360, 0xb03d, 0x0d57, 0x4276, 0x406d, 0xb223, 0x40b9, 0x40b7, 0x1e40, 0xb257, 0x07e4, 0xa579, 0xb217, 0xbf7c, 0xbae9, 0x4333, 0xbae6, 0xb23c, 0xbae4, 0x423d, 0x4121, 0xbacc, 0x41eb, 0xaaae, 0x1c89, 0x1960, 0x42af, 0x2b33, 0x4287, 0x4000, 0xb27b, 0x436d, 0x458d, 0x42aa, 0x43fd, 0x423a, 0x43fd, 0xbfc3, 0x1e10, 0x4317, 0xb272, 0xbaec, 0x4152, 0x4173, 0x30c9, 0x404e, 0x2173, 0x46c8, 0xb260, 0x4084, 0xb234, 0x4217, 0x1fbc, 0xbf13, 0x46b1, 0x41fd, 0xa309, 0x1f35, 0x418e, 0xb21a, 0x43a6, 0x41f1, 0x4219, 0x45d4, 0x45c6, 0x1ac2, 0x1c3e, 0xbad6, 0xbfc0, 0xba74, 0x4342, 0x41e9, 0x43c2, 0x4051, 0xbf88, 0xb2ce, 0xbfb0, 0xb03b, 0x248a, 0x42c8, 0xa7f9, 0xb2dc, 0x4284, 0xb27d, 0x4207, 0x43d4, 0x3a09, 0x43b6, 0xbfd2, 0x4636, 0x431f, 0x359a, 0x4035, 0xa58b, 0xb293, 0xba72, 0x40b4, 0x42fa, 0x08f7, 0x415c, 0x16ac, 0x438c, 0x42d5, 0x4069, 0x18ce, 0xa2a3, 0x4259, 0x245a, 0x2c8c, 0x4104, 0xbf52, 0xa8d4, 0x1a52, 0xae71, 0xb0dc, 0xa6c6, 0xb240, 0x33b4, 0x3314, 0x4183, 0x183d, 0x1fe5, 0xba4f, 0x1b3b, 0x41b3, 0x3897, 0xb209, 0x4380, 0x417f, 0xbaef, 0xb2e6, 0x42a0, 0x4177, 0xbfb2, 0x1e3c, 0x4250, 0xba34, 0x402c, 0x2e43, 0x435b, 0xbf8f, 0x3463, 0x40a0, 0x4174, 0x44dd, 0x40cf, 0x400b, 0x2fca, 0x3ccd, 0x2e2e, 0x3764, 0xa626, 0x40bc, 0xa3ba, 0x44ca, 0xbfa7, 0x43ed, 0xb278, 0x412a, 0xba1a, 0x01d4, 0x185c, 0x4365, 0x389a, 0x37b6, 0x4111, 0x40f7, 0x4114, 0x06a0, 0x4167, 0x3736, 0x42dc, 0x40c3, 0x32cb, 0x41b0, 0x412b, 0x2c08, 0xbacd, 0x1806, 0xb247, 0x3a1f, 0xbf70, 0x4137, 0xbf9e, 0x42a3, 0x3af2, 0x424f, 0x2f4b, 0xba6c, 0x3b5b, 0xbff0, 0xb2bc, 0x414b, 0x4570, 0xb216, 0xb2a1, 0xbad1, 0x40ae, 0x40d0, 0x41ca, 0x4059, 0xacfd, 0x43ae, 0x43de, 0x428c, 0x19ac, 0x2ed4, 0xba26, 0xbfc5, 0x1e66, 0x403a, 0x41a7, 0x3900, 0x432b, 0x4386, 0x3670, 0x1674, 0x0a49, 0x41b9, 0x438a, 0x42b9, 0x41aa, 0x40a8, 0x43c9, 0xba1c, 0x4167, 0xb0ed, 0x4359, 0xa917, 0xbf59, 0xb040, 0x4205, 0x43eb, 0x1811, 0x1e8e, 0x41cd, 0x41f2, 0x4190, 0x41b0, 0x368c, 0x387e, 0x1545, 0x18d3, 0x40d8, 0x4279, 0x438b, 0x0c33, 0xbf28, 0x4218, 0x41c7, 0xa71d, 0x4373, 0x229d, 0x4605, 0x2a5d, 0xb265, 0xbf15, 0x1c05, 0xb006, 0x193f, 0xba38, 0x4338, 0xbf4e, 0x4177, 0x4138, 0x22ae, 0xb268, 0x40e2, 0xbfa0, 0x42a9, 0x4612, 0x267c, 0x41c9, 0xb291, 0xbad0, 0xba3a, 0x45c2, 0xbac5, 0x43df, 0xb00c, 0x43e4, 0x1faf, 0x1c18, 0x1f38, 0x42f0, 0x1ac4, 0xbf2d, 0x4158, 0x40dc, 0x4329, 0xba24, 0x4009, 0xbace, 0x4206, 0x0e36, 0x3b42, 0x023c, 0x0ab2, 0x42f3, 0xb2d2, 0x4165, 0x236b, 0x4125, 0xb239, 0x45db, 0xbf5e, 0x4198, 0x444c, 0x1601, 0x19a8, 0xbaf6, 0xb251, 0x4035, 0x4169, 0x4265, 0x406f, 0x403c, 0x4319, 0xb29f, 0x4052, 0xb273, 0x1bfe, 0x4588, 0x40fe, 0x41cc, 0x40bb, 0x46f3, 0x42ff, 0xa67e, 0x1fff, 0x10c0, 0xbf33, 0x4286, 0x412d, 0xb26d, 0x41bd, 0x436e, 0x439d, 0x417e, 0x0746, 0x42c6, 0x04ce, 0x406d, 0x4249, 0xb22a, 0x43ff, 0x27d4, 0xb2a7, 0xbf95, 0xb287, 0x366b, 0x1cc0, 0x1802, 0xba5c, 0x43e7, 0x4061, 0x4153, 0x150c, 0x4681, 0x44a1, 0x45cd, 0xbad6, 0xbfc9, 0x42f2, 0x1bfc, 0x4180, 0x25a3, 0x42d3, 0x409b, 0xb0e1, 0x4676, 0xbfa1, 0x436a, 0x42b6, 0xba61, 0x424c, 0x4354, 0x340e, 0x43b7, 0x0ce6, 0x45bd, 0xa0d6, 0x42e8, 0xba77, 0x4037, 0x42b2, 0x446d, 0xbae6, 0x439f, 0xb0a1, 0xba6e, 0x4231, 0xa05f, 0x46ca, 0xb081, 0xba3e, 0x1d40, 0x435a, 0x196f, 0x4168, 0x405e, 0xbf9f, 0x4056, 0x30ab, 0x43be, 0x1956, 0xb01d, 0x41d5, 0xba3b, 0xbf48, 0x08f2, 0x42d2, 0x42a7, 0x4291, 0x423d, 0x405e, 0xb2a9, 0x2cb9, 0x43bd, 0x4270, 0xb2d2, 0x420d, 0x1494, 0x4098, 0xa21a, 0x43d1, 0x442e, 0x4559, 0x3192, 0x43d2, 0xbad2, 0x4379, 0x4298, 0xb2d6, 0xbafd, 0xbfc5, 0xbaca, 0xbaf2, 0x1ba9, 0x422c, 0xb2b0, 0x096c, 0x0443, 0xbafe, 0x44e4, 0xba0a, 0x1d8f, 0x15f2, 0xba7b, 0xbad0, 0xb295, 0x41f3, 0xb212, 0xb0dc, 0x24d5, 0x40c8, 0xba3a, 0x4023, 0xb2cd, 0xa317, 0xa11d, 0x4374, 0x1e3e, 0x414d, 0x401b, 0xbf85, 0x418b, 0xae75, 0xb226, 0x37f7, 0x4392, 0x432b, 0x426c, 0x4312, 0x0f46, 0xbfd0, 0x406c, 0xa729, 0x4136, 0x0f4f, 0x40dd, 0x1d25, 0x1a81, 0x41e9, 0x36ab, 0x4694, 0x4558, 0x1868, 0xb220, 0xbfa3, 0x1ecd, 0x4274, 0x1b66, 0x2988, 0x41e0, 0x08f1, 0x4214, 0xb28a, 0xb243, 0x4071, 0x2b56, 0x4038, 0x438f, 0x2441, 0x41bd, 0x41ba, 0x31cf, 0x408b, 0xb036, 0x1db4, 0x19c7, 0xb2c0, 0xbf55, 0x0c41, 0xbaf0, 0xb2ae, 0x4007, 0x1b06, 0xb08f, 0x1d3d, 0x430e, 0x2937, 0x41a0, 0x4209, 0x4448, 0x402a, 0xba03, 0x3597, 0xb20b, 0x45b3, 0x4376, 0x4663, 0x437c, 0x417e, 0xb088, 0xbf9c, 0x14f2, 0xb29a, 0x2554, 0x18c1, 0x180a, 0x417f, 0x0c42, 0xba14, 0xb05c, 0x41ba, 0xbf5e, 0x2562, 0x4243, 0x420f, 0x0a72, 0x438c, 0xb0ab, 0x4210, 0x1dca, 0xb2d9, 0x434a, 0xbf60, 0xad83, 0xbf35, 0xbadb, 0x407a, 0xb2a5, 0xa130, 0x429e, 0xa4e3, 0x142b, 0x35b8, 0xbfc0, 0x40a6, 0xbaf6, 0x3089, 0x4280, 0xba19, 0x43a6, 0x0fef, 0x442e, 0x1cf6, 0xb2ac, 0x18b2, 0xb0c3, 0xbad1, 0x42e0, 0x391a, 0x435c, 0xbf87, 0xb272, 0x05bc, 0x4226, 0xb2f2, 0x4313, 0x459e, 0x4326, 0x4495, 0x4236, 0x1239, 0xbf55, 0x43f8, 0xb289, 0xbf70, 0xb249, 0xb2cd, 0xb224, 0x4017, 0x1d5d, 0x4026, 0x423e, 0x1214, 0x431a, 0x351a, 0x40d3, 0xbf88, 0xba4e, 0x434f, 0x3815, 0x15ac, 0x1693, 0x40ad, 0xbf90, 0x43ac, 0x42c3, 0x060d, 0x4013, 0x3896, 0x428c, 0x2d3e, 0x435b, 0xaeac, 0x42b7, 0xbfbc, 0x42c5, 0x31b5, 0x1b10, 0x0f85, 0xbaf0, 0xbfa5, 0x09a7, 0x0762, 0xb2cb, 0xab8a, 0xba7c, 0x3c28, 0x40a2, 0x173c, 0xa6f8, 0x0788, 0x4623, 0x4324, 0x4310, 0xb208, 0x3297, 0x4667, 0x41c1, 0xbfc7, 0xbf00, 0xb222, 0x0499, 0x422f, 0x46f8, 0xa72c, 0x2e9e, 0xa41b, 0x4370, 0xbf08, 0xb073, 0xbfc0, 0x04bb, 0x2e56, 0xba6a, 0xba56, 0x4206, 0x4306, 0x427b, 0xb060, 0x4260, 0x4298, 0x4360, 0x434f, 0xbf70, 0xa2d2, 0xbae6, 0xbf80, 0xac39, 0x441b, 0xb2e7, 0x40d7, 0x42a2, 0x42b5, 0x08fe, 0xbf05, 0x195f, 0x414a, 0x40a5, 0x4344, 0xbafe, 0x4212, 0xb225, 0x429d, 0x428b, 0x22bc, 0x43e8, 0xb27d, 0xb2ac, 0x4108, 0xb2d2, 0x1d5f, 0x23c3, 0x4257, 0xb060, 0x4105, 0xb208, 0x441f, 0x2f98, 0x1f2b, 0xbf17, 0x43d2, 0x413e, 0xba24, 0x40ca, 0x4101, 0xbf90, 0x43dc, 0x42b6, 0x431d, 0x42db, 0x43e6, 0xac1f, 0x42c9, 0xba0b, 0x46a4, 0xba41, 0x42f3, 0x45a2, 0xbf2e, 0xb088, 0x40fd, 0x0d9d, 0x1a17, 0x2ee0, 0x1b8c, 0x41cc, 0x42b4, 0xb294, 0x436d, 0xabb1, 0xa7ed, 0xba2c, 0x4002, 0x4214, 0x417c, 0x03a3, 0x1b3a, 0x4645, 0xa99a, 0x4661, 0x41b7, 0xbacd, 0x2cb1, 0x1dca, 0x1d24, 0x42c5, 0xba4c, 0xbf9c, 0x1e2b, 0xb24b, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x6858fdc0, 0x2c117adb, 0x4e91c10b, 0x490c37fa, 0xdc3cc5ba, 0x19d843cc, 0x38054938, 0xa1f0a710, 0x540c538f, 0x8e7e0cd6, 0x8458328c, 0x917b025c, 0x75c492d1, 0x64488841, 0x00000000, 0x000001f0 }, + FinalRegs = new uint[] { 0xffffa800, 0x540c5724, 0x540c572b, 0x00000024, 0x0c542457, 0x00002457, 0xfffffffc, 0x00001b93, 0x00001730, 0xffffff42, 0xffffff42, 0x00000000, 0x540c5724, 0x540c5688, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xab86, 0x436f, 0xb06c, 0x401b, 0x4278, 0x4283, 0x4243, 0x1d91, 0x427d, 0x312e, 0xad98, 0x4070, 0x4267, 0x4481, 0x438f, 0xba4e, 0xb280, 0xb2d0, 0x4139, 0x40e3, 0x42f9, 0xbf7f, 0x242c, 0x43f9, 0x40cf, 0xb0cc, 0x410e, 0xb250, 0xb083, 0x2759, 0x434c, 0xbad5, 0x0875, 0xb036, 0x426c, 0x3721, 0x412f, 0xbf4c, 0x1bd7, 0x4137, 0xb04a, 0xb2d2, 0xb017, 0xbae5, 0xb258, 0x41ec, 0x1aa9, 0x1e1b, 0x42d2, 0x42a6, 0xb2a0, 0xbf36, 0xbae1, 0x426e, 0x4011, 0x41fc, 0x3afd, 0x4289, 0x355c, 0xb24f, 0x0e2c, 0x4391, 0xb2b5, 0x4059, 0x4030, 0xb299, 0xb281, 0xb04f, 0xb0d6, 0xbf33, 0x0a54, 0xbfe0, 0x41a6, 0x1b05, 0xbae0, 0xac71, 0xbf00, 0xb042, 0x18f7, 0x4576, 0x4056, 0x0b0d, 0xb224, 0xbf62, 0xba1e, 0x4629, 0xad90, 0x44e5, 0x1a37, 0x41c5, 0xbae3, 0xba68, 0x4436, 0x45b4, 0x4203, 0xba1c, 0x026e, 0x4649, 0x42c6, 0x4166, 0xba1d, 0x1d0b, 0x4006, 0x12f0, 0x0f55, 0x425a, 0xbfe1, 0xb264, 0x4672, 0xb031, 0xbac4, 0x2a1f, 0x42ec, 0x4147, 0x437f, 0x4165, 0x407e, 0x1e5e, 0x429e, 0xb03d, 0x262b, 0x4181, 0xa9bc, 0x1b17, 0xbf60, 0x1a3b, 0xa1c0, 0x18d3, 0xb03d, 0xbf25, 0xbad5, 0x421b, 0xbaeb, 0x18d4, 0x3660, 0x42ad, 0xbad0, 0xbfc0, 0x42b4, 0x0065, 0xba05, 0xb242, 0x41d4, 0x43f1, 0x427a, 0xbaf9, 0x4594, 0x41ba, 0xb004, 0xbf11, 0x3700, 0xb272, 0x4242, 0x1bab, 0xa2be, 0x43af, 0x418a, 0xb2c7, 0x0201, 0xb2c8, 0x425c, 0x405e, 0x4119, 0xb20b, 0x22a8, 0xb0bf, 0x1fd3, 0x0867, 0x4324, 0xbf89, 0xb01e, 0xb0a3, 0x3674, 0x29de, 0x4349, 0xb070, 0x00b9, 0xb27a, 0x4396, 0x45f2, 0x280b, 0x4288, 0xbf9e, 0x42fb, 0x0703, 0x40e5, 0x44e5, 0x1bd1, 0x46a2, 0xb2ca, 0x18e5, 0x010b, 0x16dc, 0x4389, 0x440c, 0x4134, 0x0684, 0x3025, 0x400b, 0x4272, 0xba24, 0x2c2f, 0x43c7, 0x2148, 0x151a, 0x433b, 0x41a2, 0xa6d0, 0xbf57, 0x085a, 0xb2c6, 0x44ba, 0x40a1, 0x43cd, 0xb2bc, 0xba3e, 0xb2d1, 0xba61, 0x0c3e, 0x1cfa, 0xbf84, 0x1b40, 0x372a, 0x3b42, 0xb06c, 0x43b7, 0x1d1f, 0x41fc, 0x41f6, 0x4552, 0xb29d, 0x43dc, 0x1a4e, 0x2de6, 0x4204, 0x407e, 0x16a1, 0xb276, 0x40ba, 0xb223, 0x4377, 0x4073, 0xb007, 0xbfdc, 0x41e3, 0x3889, 0x4117, 0x43bb, 0xba05, 0xb2ac, 0x274e, 0xb0fe, 0x01ee, 0x0c90, 0x46ab, 0x1775, 0x426c, 0xbaf1, 0x1030, 0x2822, 0xbad0, 0x089f, 0x4036, 0xbaf4, 0x1e1b, 0xba3b, 0x428e, 0x420c, 0xbf4e, 0x4228, 0x1bf3, 0x40dd, 0x40c8, 0x4010, 0xa54e, 0x4646, 0xba62, 0xbf84, 0x1d90, 0xb25f, 0xbf0f, 0xbacd, 0x410b, 0xb25c, 0x43ec, 0x25fc, 0xa882, 0x29ac, 0x4339, 0x2a4f, 0x431a, 0x032a, 0x4384, 0x3a9f, 0xb2e9, 0x401d, 0x1d5e, 0xbf64, 0x1da6, 0x062e, 0x4150, 0x0935, 0xa143, 0x2840, 0xbac7, 0xbac8, 0x44dc, 0x1af0, 0x434a, 0xba37, 0x432e, 0x4223, 0xbf00, 0x40be, 0x41ac, 0xb2d9, 0x4373, 0x19dc, 0x402c, 0x3f03, 0x43f9, 0xbf44, 0x4057, 0x07ef, 0xb2e1, 0x4308, 0xba47, 0x4033, 0x4069, 0x1590, 0xb2d7, 0x2ff9, 0xb281, 0x07a2, 0x0caa, 0x4022, 0x42fa, 0x425b, 0xba05, 0x2f80, 0xbadf, 0xa0c2, 0xbf14, 0x41ca, 0xbafb, 0x3b0a, 0x084d, 0x41c2, 0x43d6, 0x016f, 0xba73, 0x40ff, 0x1e2f, 0x4634, 0xa32a, 0x4212, 0x4234, 0x193e, 0xb036, 0xb2d3, 0xb2b5, 0x40f7, 0x40a5, 0x43f4, 0xbf84, 0x4322, 0x431a, 0x40e3, 0x3a14, 0xb2ef, 0xba6a, 0x439a, 0x43e2, 0x40db, 0xba35, 0x279e, 0x41f9, 0xb053, 0x400d, 0x4442, 0x45d4, 0xa582, 0xbf6a, 0x4249, 0x05f6, 0x439d, 0xb29f, 0x41d6, 0xa1a1, 0x1849, 0xbf8e, 0x4215, 0x130c, 0x43c9, 0xbfe0, 0xb2ff, 0x1d87, 0x4378, 0xb062, 0x42de, 0x4335, 0xb0b6, 0x416d, 0x4241, 0x08b9, 0x04fb, 0xba2d, 0x44f2, 0x0af9, 0xba57, 0x17c9, 0x4188, 0x1f59, 0x02cc, 0x42fb, 0xbf8f, 0xb289, 0x4148, 0x0ee2, 0x43ef, 0xba1c, 0x40df, 0x456e, 0xb235, 0x1d34, 0x43b0, 0x44ed, 0x4010, 0x43a1, 0x4096, 0x1a2d, 0xbf9d, 0xb01c, 0x417a, 0x429d, 0xb226, 0x19eb, 0xb20e, 0x434a, 0x46ba, 0x4374, 0xbf48, 0x41ea, 0x23aa, 0x41f7, 0xaef1, 0x4331, 0x41c3, 0x1dff, 0x349b, 0x220e, 0x43ec, 0xaf93, 0xbf7d, 0xba45, 0x33db, 0x41e2, 0x465f, 0x42d7, 0xb268, 0xba51, 0x42bf, 0xba33, 0xb03c, 0x42b2, 0x101c, 0x422b, 0xbf05, 0xb2a6, 0x348a, 0xba46, 0x1691, 0xb055, 0xbf60, 0xb058, 0x40f3, 0x404b, 0x3a1b, 0x44b1, 0x18d2, 0x165e, 0x439a, 0x401c, 0x1860, 0x44ca, 0xba4f, 0xbf7a, 0x1f7f, 0x410a, 0x44e0, 0xa367, 0xa9b9, 0x43e5, 0x41a3, 0x112b, 0xba5f, 0xbf01, 0xbf80, 0x4288, 0x4613, 0x1f10, 0x2223, 0xb2ab, 0xb231, 0xba03, 0x415d, 0x38a5, 0xba64, 0x4287, 0x42c7, 0x3c02, 0x1b36, 0x444e, 0xbf0c, 0x42ec, 0x419b, 0xb08b, 0x0e22, 0xbf58, 0x45a0, 0x3d48, 0xa1d0, 0xba09, 0x2ae7, 0x1240, 0xa3da, 0x430b, 0x41d5, 0xb267, 0x4173, 0xab95, 0x4323, 0xb24f, 0x436b, 0x12fc, 0xbf06, 0xbac0, 0x184d, 0xb2c8, 0x1e2e, 0x1b5c, 0x1f35, 0x414f, 0xb2f4, 0x41c8, 0x1bf7, 0x1c02, 0x4179, 0x054d, 0xa9e5, 0x19de, 0x40d4, 0xba00, 0xbae8, 0x406d, 0x4090, 0x16e9, 0xba19, 0xbf17, 0x4135, 0x2fdd, 0xa4f3, 0x4007, 0x43be, 0x4103, 0x46a4, 0xb201, 0x4061, 0xba27, 0xb056, 0x1c97, 0xb294, 0xba6c, 0x1ed4, 0xbacf, 0x35d1, 0x3094, 0x1c4d, 0xba09, 0x28a4, 0xb0a4, 0x2f5f, 0x4263, 0xb03c, 0x0667, 0x31f0, 0xbfc7, 0x259c, 0x41a9, 0x3f6d, 0x4345, 0x4240, 0x1b3e, 0x40d3, 0xba05, 0x4066, 0xbad7, 0xb0e7, 0x1f93, 0xbff0, 0x1c32, 0x41c5, 0x36e4, 0xb086, 0x3d60, 0x1927, 0x4339, 0x41fc, 0xb27d, 0x4253, 0xa158, 0xba4f, 0x0102, 0xbf2a, 0x353f, 0x1eb5, 0x400c, 0xab8b, 0x402c, 0x44fc, 0x384f, 0xb253, 0xacde, 0xaec7, 0x446b, 0xb2dc, 0x413e, 0x1d6c, 0x4206, 0xb298, 0x4297, 0xb2cf, 0x4008, 0x41b4, 0x2866, 0x4115, 0x4369, 0x46e5, 0xbf70, 0x4006, 0xa904, 0x40c4, 0xbf56, 0xa695, 0x42a2, 0x40c0, 0x4152, 0x409a, 0x411b, 0x408e, 0xbf80, 0xb074, 0x43be, 0x072c, 0x40a7, 0x41e3, 0xb0d3, 0x455e, 0xb2a5, 0x1be1, 0x4178, 0x405a, 0x4066, 0xb242, 0x42da, 0xb205, 0xba4a, 0xbfde, 0xba32, 0x45ce, 0x1dc4, 0x42be, 0x4257, 0xb22d, 0xaad1, 0x41f9, 0xb298, 0x4033, 0xb26c, 0xb08a, 0x4210, 0x4094, 0x40d3, 0x4105, 0x41be, 0x02d9, 0x405f, 0xba3a, 0xbac2, 0xbfde, 0x436d, 0x41f2, 0x42cb, 0x4225, 0x40d2, 0x44e5, 0x42a0, 0x41f1, 0x41b7, 0x1e7e, 0x4385, 0x114c, 0x1e14, 0xbf26, 0x37d0, 0xb00b, 0x2124, 0xb276, 0xbac4, 0x41ad, 0x4077, 0x44d0, 0xba27, 0xbf13, 0x2327, 0x21a0, 0x22e5, 0xb069, 0xba4a, 0xb22a, 0xad1e, 0x4207, 0x2a4e, 0x1a9e, 0xb0a8, 0x4095, 0xb0b5, 0x419e, 0xb2fd, 0x422c, 0x33c7, 0xbfb1, 0x4050, 0xb2ed, 0x29d5, 0x191a, 0x1c19, 0xbfbe, 0x1823, 0x447c, 0x41ee, 0x0ba8, 0x46d3, 0xbfb0, 0xaf6e, 0xbfa3, 0x42cb, 0xb043, 0xaf56, 0x4122, 0x405e, 0x1e9e, 0xb2f6, 0x4171, 0x18c9, 0xba2d, 0x0c08, 0x4200, 0x402a, 0x401b, 0xadfe, 0x4365, 0x1fd0, 0x411b, 0xb03b, 0x43d2, 0x42d3, 0xba5c, 0xbfcf, 0x420b, 0x253c, 0xaf62, 0xbafc, 0x4223, 0x4484, 0x41bd, 0x424a, 0xba4b, 0x41be, 0x2525, 0x4262, 0x432b, 0x3897, 0x29b2, 0xbf42, 0xa188, 0x4004, 0x449b, 0x46e5, 0x42ec, 0x45be, 0x3f6a, 0xbf60, 0x436d, 0x412f, 0xbf60, 0x4284, 0xb227, 0xba46, 0x4329, 0xa61f, 0xb21d, 0xbfb7, 0x43aa, 0x1d94, 0xafd1, 0x4102, 0xa3bc, 0xb240, 0x42d0, 0x1b46, 0x4140, 0x1d73, 0x19e9, 0xb0b2, 0x1ac2, 0xb2be, 0xbf57, 0x42e1, 0x455c, 0xb27d, 0x42fa, 0x3cb0, 0xb218, 0x41fc, 0x3d3a, 0x2b79, 0xb2c3, 0x1853, 0xbfa0, 0x4556, 0x4035, 0x1d83, 0x33c4, 0x131e, 0xbad3, 0x428c, 0x41ff, 0x456a, 0xbf56, 0x41f6, 0x4115, 0x4654, 0xa539, 0x43ae, 0x41d6, 0xba1c, 0x3b02, 0x2859, 0xba41, 0xa4f5, 0x43de, 0x41d6, 0xb251, 0x401d, 0xbfe4, 0x42dc, 0xa953, 0x4129, 0x406e, 0x09c1, 0x3105, 0x2ce2, 0xa65c, 0x42e6, 0x42a8, 0x34ac, 0x4270, 0x42ef, 0x3dc8, 0xb2dd, 0x189c, 0xb2bd, 0x1d7d, 0x44d8, 0xb224, 0xbf6a, 0xba40, 0x4194, 0x0198, 0x436f, 0x423c, 0x429f, 0x4077, 0xbad1, 0x1ac3, 0xb0be, 0x2bdf, 0xb051, 0x435d, 0x1573, 0x1d5f, 0x0b67, 0xb0e6, 0x430f, 0x4025, 0xbad2, 0xbfa7, 0x1896, 0x4386, 0x1e61, 0xb250, 0x2733, 0x13f6, 0xba6c, 0x4246, 0x1d98, 0x41f0, 0x4362, 0xb213, 0x41f1, 0xb074, 0xb050, 0x4139, 0xbac9, 0x420e, 0x416e, 0x0a81, 0xbf61, 0xba0f, 0x41c5, 0x40f4, 0xb2e3, 0x2f84, 0xb278, 0xbf67, 0x46c5, 0xba2e, 0x40f1, 0x1036, 0xbff0, 0x3a3b, 0xbaee, 0x3276, 0xb093, 0x1b72, 0x400d, 0x3cda, 0xbf24, 0x197a, 0x42e5, 0x4623, 0xbfbb, 0x1cc0, 0x42cd, 0x41dd, 0x2127, 0x1817, 0x404e, 0x4207, 0x4331, 0x1bf1, 0xbfd7, 0xa8e1, 0x46eb, 0x4372, 0x406b, 0x4670, 0xbf70, 0x401c, 0x403d, 0x4087, 0x4039, 0x43ce, 0x266c, 0x42c2, 0x1b06, 0x2d2d, 0xb0ad, 0x4613, 0x4029, 0x1cbb, 0x1878, 0x4294, 0x4154, 0xb253, 0xbf9b, 0xba0f, 0x4400, 0x422a, 0x34f1, 0xbfd3, 0xb20e, 0x41bd, 0x1c19, 0x418d, 0x3be1, 0xb056, 0x43cc, 0x40e0, 0x133b, 0x43fa, 0x1a07, 0x4086, 0xb26c, 0xbf46, 0x1caf, 0xbae1, 0xb0bf, 0xb2cc, 0xb024, 0x4345, 0x4474, 0xb031, 0x4553, 0x1c60, 0x2731, 0xb2be, 0x297d, 0x4367, 0x40ff, 0x401c, 0xba33, 0x2258, 0xbfce, 0xb280, 0x40c7, 0x0b15, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x38d6f90d, 0xcd984b26, 0xc8b29dc9, 0x67c34614, 0x14951b86, 0xae2631e0, 0x418035e3, 0x147814c2, 0xb4096d8b, 0xc30ae4ae, 0xb5c6061f, 0x1b3e3fa0, 0x0b9ebe6d, 0x746bc3e8, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0x00000100, 0xffffffff, 0x00000058, 0x31000000, 0x00000000, 0x00000000, 0x00000031, 0x00000000, 0x00fc342b, 0x2eb36651, 0x26796350, 0x00002db3, 0x00002d03, 0x00002fab, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x1971, 0x439c, 0x401d, 0x1924, 0x1fc0, 0x3352, 0xba14, 0x4306, 0x432b, 0xb268, 0x1c6f, 0x4158, 0x4633, 0x37a0, 0xbfbe, 0x4286, 0x410c, 0x41c5, 0xb209, 0xb2f5, 0xa3dd, 0x40f7, 0xb087, 0xae09, 0xb2de, 0xba4c, 0x4349, 0xa6c5, 0x436c, 0xb05c, 0x4575, 0x2190, 0xbf3c, 0x1ed6, 0xa383, 0x4192, 0xbf60, 0xba2a, 0x36db, 0x4116, 0x2df4, 0x41a7, 0x299f, 0x406c, 0xa653, 0xbaca, 0xb282, 0xbac8, 0x40f7, 0x4669, 0xb274, 0xb022, 0xb2eb, 0x0ccf, 0x41b7, 0x20ed, 0x047a, 0xb083, 0xbf33, 0xb2bf, 0xa78d, 0x3ae6, 0x3882, 0xba1b, 0x455f, 0xbaeb, 0xaa73, 0x435d, 0xb201, 0x42d4, 0x4183, 0x415e, 0xba41, 0x41a3, 0xbfa5, 0x43ac, 0x4255, 0x1b2d, 0x422c, 0x4217, 0xbafe, 0x420c, 0x43e3, 0x40e3, 0x4199, 0x42fc, 0xbf4c, 0x41c6, 0x1001, 0x4674, 0x41d0, 0x404c, 0x4193, 0x4301, 0x16cb, 0x2399, 0x3509, 0x419b, 0x43d0, 0x18c3, 0x43d3, 0x40b9, 0x43ed, 0x137b, 0x40be, 0x2372, 0x3e64, 0x0ccb, 0xbf9b, 0x41a1, 0x26ed, 0x4359, 0x1326, 0xa32a, 0x4364, 0x3046, 0x410d, 0x416d, 0x3711, 0x41db, 0x40b5, 0x429f, 0x43a8, 0x42d0, 0xb2f9, 0x0758, 0xadfe, 0x421e, 0xb247, 0x2f6a, 0xbf0e, 0xb2e4, 0x2aad, 0x43a4, 0xb2d4, 0x439a, 0x4206, 0xbad9, 0x4153, 0x0dc3, 0xb217, 0xa3ea, 0x1a60, 0x1768, 0x4555, 0x40f9, 0x4205, 0x463d, 0x3e44, 0x4016, 0x3638, 0xba58, 0xb2c2, 0xba3a, 0xba04, 0x4224, 0x4359, 0x0001, 0x422a, 0xbf3c, 0x4342, 0x1b5e, 0xb22b, 0xb2ef, 0x36ae, 0xb204, 0xbfb0, 0x1858, 0x41da, 0x241b, 0xb04f, 0xb28d, 0xba31, 0x42f0, 0x42e7, 0x4028, 0x1dde, 0x45cd, 0xba2a, 0x40c0, 0x18b0, 0x0b4c, 0xbfbd, 0x1b99, 0x0418, 0x4239, 0x3b1d, 0x23c6, 0xba4f, 0x1f5d, 0xb2b0, 0xbac5, 0x1c2a, 0x45f1, 0x422b, 0x43bd, 0xa17c, 0xbf33, 0xba74, 0xb235, 0x0c06, 0x43b2, 0x40b3, 0xb0d2, 0xbf39, 0x4182, 0x46d9, 0xb2f1, 0x4271, 0x43e5, 0xb2b0, 0x41de, 0xbf00, 0x317f, 0x41e1, 0xa9e4, 0x18b0, 0x2a87, 0x4151, 0x43a1, 0x4179, 0x4204, 0x42c1, 0x44f8, 0x4389, 0x40f0, 0x2442, 0x43af, 0xbfa8, 0x3cac, 0x4365, 0x2149, 0x41ff, 0x443f, 0x4190, 0xb0ba, 0x216f, 0xb225, 0xb248, 0xb0f5, 0xba3e, 0x0b19, 0x1bc6, 0x41a7, 0xb0d7, 0xb2dc, 0xb20b, 0x1da9, 0x432d, 0x4293, 0x4035, 0x01ab, 0x2725, 0xbf93, 0xb264, 0x42b6, 0xb074, 0xb2db, 0xb2b6, 0x1dd3, 0x40fc, 0xba52, 0x4301, 0x1e30, 0xba78, 0x413c, 0xbfab, 0xa981, 0x414a, 0x439f, 0x4015, 0x0769, 0xb23d, 0x43de, 0x2bb2, 0xba4a, 0x4261, 0x412c, 0xbf9c, 0x4135, 0xb2ce, 0x4224, 0x1fcf, 0x42bc, 0x18cd, 0x1b98, 0x2bdb, 0x3605, 0x43df, 0xa64c, 0xbf9a, 0x32a3, 0x43ec, 0xb2d2, 0x43a1, 0xba7d, 0x43ee, 0x0e94, 0x44c9, 0x01d6, 0x4140, 0x2921, 0x424d, 0xb0a4, 0x0caa, 0xbf85, 0x41d4, 0x19a8, 0x0dc9, 0x4161, 0x1b35, 0x43ee, 0xa171, 0x4477, 0x4385, 0xba16, 0x420f, 0x3013, 0x0573, 0x4281, 0x3bf9, 0x181a, 0xb0bd, 0xb046, 0x3254, 0x4654, 0x43fd, 0xba34, 0x42b0, 0x4215, 0xb022, 0x42cf, 0x4225, 0x4281, 0xbf0e, 0x4021, 0x4217, 0x4351, 0x4283, 0xb2d9, 0xb223, 0xbfe0, 0x4209, 0xbfb6, 0xbaeb, 0x078c, 0xb21e, 0x1bc3, 0x2e6e, 0x43d9, 0x41c8, 0x40e0, 0x4361, 0x4225, 0x4078, 0x37fa, 0xb065, 0x4390, 0x40bd, 0x418c, 0xbfcf, 0xba18, 0x40e6, 0x4039, 0xbf00, 0x1bf0, 0x441a, 0x4345, 0x42d6, 0x2c06, 0x238c, 0x4284, 0x413b, 0x4295, 0xb291, 0x21de, 0xaabe, 0xba14, 0x4253, 0x3c60, 0xb29b, 0xbf00, 0xbfbc, 0x4081, 0xb2d5, 0xb2f1, 0x4253, 0x04c1, 0xb055, 0x1f26, 0x1d48, 0xbf88, 0x401d, 0x2150, 0x40ff, 0x08dd, 0xbf45, 0x18ce, 0x4341, 0x4217, 0x40b9, 0x09c6, 0x43a1, 0xbac8, 0xb28f, 0x420c, 0x41ea, 0xb2f7, 0x0e48, 0xa96a, 0xb29e, 0x0631, 0x3cc9, 0x43cb, 0x3771, 0xb23c, 0x46f9, 0xa882, 0xb2d3, 0xba10, 0x4375, 0x17f2, 0x03ab, 0xb2da, 0x43b0, 0x43ef, 0xbfbb, 0x2f20, 0xb051, 0x1ffa, 0x42c9, 0x2d15, 0x2157, 0xa778, 0x432c, 0x421a, 0x4430, 0x43f6, 0x4271, 0x436f, 0xbf62, 0xbac5, 0x4288, 0x24a8, 0x4361, 0xad27, 0x433f, 0x3a4e, 0xba0e, 0x41ed, 0xb2b6, 0xb27a, 0xbf53, 0xa105, 0x1f58, 0xa2f2, 0x43ff, 0xb074, 0x1ac5, 0x421e, 0x4241, 0xb2b7, 0x2ad2, 0x40d9, 0xa5fd, 0x25d8, 0xb2b6, 0xa3c8, 0xb2bf, 0xb226, 0xba6d, 0x07ed, 0x43db, 0x4261, 0x40e7, 0x41a5, 0x4619, 0x3e15, 0xb25a, 0x427f, 0xbff0, 0x1a2f, 0xbfd6, 0xbac4, 0x42ba, 0x0922, 0x11a7, 0x439f, 0x44d0, 0x43ff, 0x3c72, 0xba33, 0x4386, 0x1f3a, 0x40c0, 0x418f, 0x411a, 0x4212, 0x4110, 0x428f, 0x447c, 0xb097, 0xbf77, 0x42b7, 0x0621, 0xbacb, 0x46ca, 0x43af, 0x01f4, 0xba26, 0x1815, 0x41e1, 0x4150, 0x4171, 0x43b4, 0xa937, 0x412f, 0x42df, 0x429a, 0x415e, 0x431c, 0xad19, 0x4282, 0x4073, 0xbf87, 0x43d0, 0xba54, 0x2db6, 0x4350, 0x4237, 0x4164, 0xbae5, 0xb22b, 0x0a33, 0x43f8, 0xbf33, 0x40f2, 0xb2bc, 0xb2a4, 0x40e6, 0x41c9, 0x326c, 0x41c8, 0x42e9, 0x42af, 0xba3b, 0x359d, 0x4109, 0x4028, 0xb269, 0xba16, 0xb2fc, 0x3acb, 0x4063, 0xbf16, 0x09a3, 0xb2d5, 0xba22, 0x4142, 0xbf8b, 0x1c3c, 0x3e0e, 0xb29c, 0x1164, 0x331c, 0x1ce8, 0xbff0, 0x43d7, 0x4137, 0x429b, 0x18a9, 0x4193, 0xb0cc, 0xba49, 0xba38, 0x411f, 0xb205, 0xbfa6, 0x4173, 0x4142, 0x43e0, 0x4089, 0x182e, 0xba01, 0x41ea, 0x3a41, 0x2930, 0x4025, 0x0594, 0x1556, 0x0b43, 0x46f2, 0x248c, 0x4340, 0xbf7b, 0x4257, 0x4328, 0xba28, 0x04e8, 0x42c7, 0x4207, 0x1d28, 0x40d1, 0x2167, 0x43a3, 0x284d, 0xbf05, 0xb2a2, 0x1605, 0xb276, 0x412d, 0x40f3, 0x378b, 0xba41, 0xbaf7, 0x42a3, 0x425d, 0x45b4, 0xbf9b, 0xa2c2, 0xba13, 0x410c, 0x45da, 0x4372, 0x4228, 0x0a0b, 0x4550, 0x4295, 0x06c1, 0xb2cd, 0x1487, 0x31fb, 0xbac1, 0xb200, 0xba13, 0x2c7f, 0x4449, 0x42f8, 0xbaf4, 0xb2b4, 0x24b1, 0x4166, 0xba16, 0xbf7c, 0x4171, 0x4311, 0x32bc, 0x4572, 0x3a43, 0x4690, 0xb2b3, 0xb037, 0x41c0, 0x4198, 0x4459, 0x41e3, 0x42e3, 0x2d4e, 0x05da, 0xb20a, 0x1cfd, 0xb28f, 0xb080, 0xa6d7, 0x4268, 0x243a, 0x43b9, 0xbf27, 0x14b0, 0x2410, 0xba40, 0x1f3a, 0x4008, 0xbfd9, 0x1b4b, 0x40a4, 0x2a6f, 0x14aa, 0x407b, 0xba5c, 0x41ec, 0x4245, 0x1b03, 0x4315, 0x4139, 0x2c74, 0xbfc0, 0x1806, 0x0ae6, 0xbae8, 0x3d2f, 0xb244, 0x0bac, 0xb245, 0x4304, 0xbf90, 0xbf9a, 0xb218, 0x408a, 0x42e1, 0x0fa0, 0xb06d, 0x43d5, 0x41b2, 0xb20e, 0x222f, 0xbfd3, 0x3289, 0xb2d2, 0x45e1, 0xb0ab, 0xbf8b, 0xba5c, 0x41a9, 0x41e2, 0x42ee, 0x446d, 0x41d9, 0x4362, 0x4265, 0xba7d, 0x43c5, 0x2cce, 0xb284, 0xb2fb, 0x40fa, 0x3ddf, 0x4188, 0xb251, 0xba1d, 0xbf26, 0x424b, 0x4566, 0x14d4, 0x3784, 0xbf70, 0x4225, 0xb22b, 0x1f12, 0x4022, 0xb061, 0x4145, 0x42d3, 0xb2c9, 0x2199, 0x42e5, 0x1a4d, 0x25c8, 0xbf56, 0x401e, 0x1c37, 0xba38, 0x1498, 0x4052, 0x4181, 0xba67, 0x41ab, 0x1bc7, 0x43d8, 0x4379, 0x42d0, 0x4211, 0x3a14, 0x00c0, 0x4068, 0x41dd, 0x4138, 0x40cb, 0xb21f, 0xb0c9, 0xba5c, 0x4004, 0xbfd5, 0x43bf, 0x4344, 0x06d0, 0x40ea, 0x2d6a, 0xbfe4, 0x42bc, 0x40a0, 0x40c6, 0xaa16, 0x42bd, 0x421a, 0xb2d7, 0x41ca, 0xb016, 0x43ef, 0xae2d, 0x4341, 0xb0c0, 0xba1b, 0x40c7, 0xb0cd, 0x3c18, 0x2ab4, 0x41d2, 0x0506, 0x2144, 0x183c, 0xba30, 0xb266, 0xb2e5, 0xbf90, 0xbf4a, 0xa979, 0x4300, 0xb26e, 0xbfd0, 0xba66, 0x13dc, 0xb28e, 0x1a28, 0x4158, 0x43ab, 0x4075, 0x40f2, 0x4593, 0x198f, 0x38c5, 0x4061, 0xbf23, 0x3e56, 0xa0ef, 0xba50, 0x4433, 0xba0b, 0xad1b, 0x1df6, 0xbad9, 0xb253, 0x40cc, 0x424c, 0x1a18, 0x1b36, 0x186b, 0xbfdf, 0x4167, 0x0075, 0x431c, 0x400c, 0x3e22, 0x434d, 0xba3b, 0x4388, 0x2f78, 0x407f, 0x4254, 0x1610, 0xba4b, 0x43f8, 0x4089, 0xb231, 0x4050, 0xb29d, 0x0c5a, 0x3afd, 0x42e8, 0x1c7a, 0xb241, 0x0cad, 0xb268, 0xbfc0, 0xbf6a, 0xb2cc, 0x37b0, 0x46eb, 0x4129, 0x1e5b, 0xb216, 0x0748, 0x41d1, 0x4308, 0x45ac, 0xb2a5, 0x43d5, 0x4049, 0x4076, 0xb28d, 0x2611, 0xb26d, 0xba2e, 0x34d4, 0xbafb, 0x3761, 0x417a, 0x4156, 0x4575, 0xb210, 0xbf4d, 0x42ba, 0xba26, 0xa039, 0x422c, 0x4357, 0xaee4, 0x41ab, 0x43eb, 0x1d12, 0x434c, 0x1818, 0x0a20, 0xb208, 0xb2a2, 0x4187, 0xba69, 0x43de, 0xa5ce, 0xbfac, 0xb0db, 0xb274, 0x4347, 0x4151, 0x1f64, 0x1c6b, 0x1db7, 0xbad5, 0x1e0a, 0x198e, 0x416f, 0xbacf, 0x42fe, 0x4173, 0x183b, 0x44f4, 0x4075, 0x43a0, 0xbaf0, 0xbf41, 0x1e71, 0x39a2, 0x1dc5, 0xb212, 0x0159, 0xbaea, 0x1fdd, 0x42d7, 0x4453, 0xb2dc, 0x0817, 0x4698, 0xba22, 0xbf58, 0x04db, 0xb216, 0x1b55, 0x42e8, 0xba7c, 0xbf88, 0xa1f0, 0xba12, 0xb21f, 0x2628, 0x4091, 0x4554, 0x0c86, 0x4098, 0xb0fb, 0x43e1, 0xbaed, 0xb2c1, 0x1f65, 0x4019, 0x4365, 0x0c81, 0x40dd, 0xaf49, 0x40f2, 0x40a3, 0xba56, 0x4378, 0xa9c0, 0xbf81, 0xba7f, 0x404b, 0xaec3, 0x3eec, 0xbadf, 0xacb7, 0x434a, 0xba10, 0xbaeb, 0x1ea3, 0xbf19, 0x413d, 0xb07a, 0x1d45, 0x42ff, 0xb08d, 0x1a6b, 0xa7c4, 0xa3d1, 0x40bb, 0xba7f, 0x460a, 0x45cb, 0x4490, 0xb097, 0x40f8, 0x00c0, 0x3d1a, 0x42bb, 0x30ae, 0x1c4f, 0xad13, 0xbafd, 0x40c3, 0x40e1, 0x4046, 0x434e, 0xbf3a, 0x185c, 0x409a, 0x26fc, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x6e009d47, 0x4404ee01, 0xc475426e, 0xc7651e4f, 0x99397e2c, 0xe2311d1e, 0x13b3b0d5, 0x94ee1dcc, 0x8a96e390, 0xfb8d253a, 0x3cd58f7e, 0xf938ad79, 0x61646a13, 0x14d0ae7f, 0x00000000, 0x100001f0 }, + FinalRegs = new uint[] { 0x000000ae, 0x00000000, 0x14d0af83, 0x00000000, 0x00000000, 0xffff84af, 0x00000000, 0x14d0af84, 0x14d0b083, 0x00001344, 0x00000000, 0xf938ad79, 0x61646a13, 0x14d0addb, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xb08d, 0xba58, 0x1eea, 0x4241, 0x439f, 0x27f1, 0x40eb, 0xb27f, 0x1bed, 0x407c, 0x4327, 0x41ea, 0x27fd, 0x410a, 0xbf6d, 0x4038, 0xa806, 0xba0f, 0xb265, 0xb22d, 0x1b44, 0x1c14, 0x431d, 0xaad5, 0x3ee7, 0x4034, 0x2d0b, 0x2dcc, 0x4334, 0x3e1c, 0xbf00, 0x11eb, 0xb2db, 0xa20c, 0xb058, 0xbfab, 0x4390, 0xb042, 0xb051, 0x2fcb, 0x415c, 0x0a57, 0x408c, 0x1d6e, 0xa072, 0xba55, 0x2581, 0x447b, 0x43a5, 0x440b, 0x1e63, 0xb2cc, 0xa334, 0xba69, 0xabd9, 0x1d2d, 0xbf62, 0x4140, 0x024e, 0xb2ac, 0x40ac, 0x4567, 0xba10, 0x1eb6, 0x3178, 0x0a5f, 0xb2bc, 0x40b0, 0xbfd4, 0x4285, 0x0c35, 0xb21e, 0xbacd, 0x1a41, 0x3ab5, 0x117a, 0x4469, 0xb2a3, 0x2dc3, 0x416d, 0x4541, 0xba7f, 0x1a93, 0x4389, 0xbf57, 0x301f, 0x4320, 0x28ae, 0x4020, 0xbac7, 0x1192, 0xbf45, 0x42ba, 0x43c3, 0x42b2, 0x0233, 0x1fa2, 0xb0b2, 0x41f2, 0xbaf6, 0xbf92, 0x0ec4, 0xb28d, 0x406b, 0xbff0, 0x403f, 0xba61, 0xb204, 0xbaeb, 0xba0b, 0x402a, 0x1ee1, 0x431b, 0x4331, 0x2513, 0x2108, 0xbf62, 0x4257, 0xa533, 0x3fd8, 0x42c7, 0x2195, 0xb2df, 0x42ab, 0x2830, 0x4373, 0xb286, 0x3afb, 0x42a2, 0x2e11, 0x4188, 0xb0c4, 0xba0e, 0x1d2e, 0x4158, 0xa422, 0x1dec, 0x417a, 0xb239, 0x43c5, 0x4074, 0xbf1c, 0x459e, 0x417c, 0x1b2b, 0xb2d6, 0xb296, 0x43ca, 0xbfd6, 0x449d, 0x23aa, 0x4198, 0xb267, 0xa248, 0xbf00, 0x4368, 0xb04a, 0x0734, 0x1ae6, 0x42d9, 0x414f, 0x1ed1, 0x4291, 0x1cde, 0xba3d, 0x4103, 0xa7df, 0x4586, 0xb257, 0x2302, 0x46a2, 0x43e6, 0xba41, 0x4128, 0xb2dc, 0x268d, 0xb235, 0xbfb7, 0x183a, 0x432d, 0xb0be, 0x40c4, 0x46c2, 0x42ae, 0x1cef, 0x16a2, 0x432a, 0x034c, 0xa8cc, 0x1aa9, 0x4635, 0x33d5, 0x42be, 0xb225, 0x4054, 0x1856, 0xb02b, 0x4683, 0xbfd6, 0x3dcb, 0x45ad, 0x4149, 0x42ec, 0xa5a3, 0xbfb0, 0xb04d, 0xb266, 0x405c, 0x40ef, 0xb23d, 0xb2fe, 0x432d, 0x41fe, 0x43ad, 0xb268, 0xa0d6, 0x41d9, 0x023a, 0xb291, 0x4397, 0xb2fb, 0x035e, 0xbf3f, 0x43aa, 0x4371, 0x3540, 0x4013, 0x42bc, 0xba4d, 0xb04e, 0x4156, 0x43be, 0x40b7, 0x40a9, 0x409c, 0x4288, 0x415e, 0x0f3e, 0xb009, 0xbfd7, 0xb0d2, 0x4263, 0x3228, 0x26dd, 0x1b78, 0x4170, 0x408e, 0xbfc7, 0x1ed8, 0x459d, 0x4199, 0x41af, 0x4340, 0x434a, 0x43a6, 0xbfc1, 0x2986, 0xbadc, 0x1d21, 0x00cc, 0x42e0, 0xae51, 0xa7dc, 0xb24e, 0x42fb, 0x1ba5, 0x1a40, 0x1ce0, 0x1e2b, 0x355f, 0x4357, 0x40c2, 0xba61, 0x4182, 0x4383, 0x430e, 0x1593, 0x41d6, 0xb0fa, 0x410f, 0xb21c, 0x4075, 0xbf8f, 0xb2c4, 0x4285, 0x4003, 0x1281, 0x414a, 0x408b, 0x403e, 0x2363, 0x412a, 0x430e, 0xbf45, 0x2282, 0xb24a, 0x330a, 0x4145, 0x4285, 0x43ed, 0x4031, 0x18ea, 0x4227, 0xbf80, 0x2468, 0xba52, 0x41ce, 0x4659, 0x4095, 0xb28c, 0xa9d4, 0x4439, 0x1b5b, 0x1349, 0x41d5, 0xbac3, 0xb09a, 0xba48, 0x4216, 0xbfa1, 0x0eec, 0x205a, 0x1cee, 0x44f9, 0x43c9, 0x0ee8, 0x0e50, 0x4258, 0x4029, 0x403f, 0xb226, 0x42bc, 0x1ba6, 0xbf48, 0x42a7, 0x40f2, 0x4260, 0x4499, 0xbfd3, 0x38e7, 0x40f2, 0x2087, 0x4189, 0x2243, 0x4111, 0x3fa8, 0x40bb, 0xb0be, 0x4026, 0x1b74, 0xbf0e, 0xb047, 0xb20e, 0x1961, 0x4241, 0x4063, 0xba4f, 0x40dd, 0x411b, 0xafbd, 0x39a3, 0x4254, 0x4262, 0x41e0, 0xbacd, 0x2264, 0x40bc, 0xa651, 0xb20c, 0xba0d, 0xb261, 0x4484, 0x432a, 0xbfdf, 0xb01a, 0xb20e, 0x2256, 0xbac1, 0x1ac5, 0xb2eb, 0xb2ee, 0x2927, 0x009a, 0x4291, 0x3575, 0xb21a, 0xb2d1, 0x1a67, 0xbaff, 0x4115, 0x42ef, 0xbf26, 0x0fa4, 0x415d, 0xb253, 0x010c, 0xb0aa, 0x2d7b, 0xb020, 0xb0da, 0xae00, 0x2f87, 0xb095, 0x0ca0, 0x42f8, 0x4037, 0x1c6f, 0x436b, 0x42b4, 0x1d3d, 0x3251, 0x43ff, 0x411a, 0x4394, 0xbf71, 0x1ac3, 0x42b2, 0x37b3, 0x3db9, 0x4388, 0xb080, 0x4314, 0x406c, 0x40ad, 0xb0ac, 0xb047, 0xbaff, 0x4091, 0x4372, 0xb29b, 0x4119, 0x45ab, 0x405d, 0xbf95, 0x3f6d, 0x1d73, 0x1896, 0x4074, 0xbad7, 0x211b, 0xa48f, 0x436d, 0x4181, 0x1e91, 0x1d3a, 0x42d0, 0xb25b, 0x43c6, 0xba43, 0xb284, 0xbfe0, 0x1eed, 0x0218, 0x40a2, 0x42a8, 0x40a7, 0x1c26, 0xbf00, 0xbf38, 0x447c, 0xbae0, 0x40d9, 0x1c9a, 0x42ec, 0x43f7, 0x386f, 0xbf59, 0x189b, 0x4037, 0x4323, 0xb281, 0xba6c, 0x1a55, 0xb2a2, 0x4218, 0x41d1, 0x4215, 0x383f, 0x429f, 0xbf89, 0x2d17, 0x46b9, 0xb0d1, 0xbf70, 0xba01, 0xbf2e, 0x1f89, 0x45a3, 0xb07c, 0xadd1, 0x42dd, 0x400c, 0x1adc, 0x43b9, 0x4072, 0x418c, 0x0a53, 0xad90, 0x1f17, 0x418f, 0xbacf, 0x1fc4, 0xa559, 0xba37, 0x1b9c, 0x461c, 0x42c8, 0x401e, 0x1971, 0x43b5, 0xba29, 0xa499, 0x4366, 0xbf7d, 0x1b05, 0xbadb, 0xbad4, 0x1b87, 0x43f7, 0x45b4, 0xa2ab, 0x43a8, 0xb08b, 0x18c0, 0xb2fe, 0x435d, 0x0129, 0x4200, 0x43fa, 0x33ac, 0xb2f5, 0x411c, 0x423b, 0x418e, 0x3876, 0xb22f, 0xbad0, 0x4464, 0xbf0e, 0x24f1, 0xb21c, 0x42dc, 0x42c1, 0x40f5, 0xa9ad, 0x4283, 0xb2a1, 0x432e, 0xbfb0, 0x428b, 0x42d3, 0x08fd, 0x0f6c, 0x437c, 0x0f32, 0x43ea, 0x0768, 0x4192, 0x4001, 0x01af, 0x22ab, 0x43df, 0x41d1, 0xa87c, 0x1cc1, 0x0e0e, 0xbf55, 0x1b17, 0x4265, 0xb0e6, 0x4359, 0x43ca, 0x43bd, 0x40ff, 0x4110, 0x1cf1, 0x4037, 0xb254, 0x1957, 0xbf41, 0x0fec, 0x45b3, 0xb21a, 0x41e8, 0xb21e, 0x417f, 0xaf50, 0x0c9d, 0x4395, 0x4122, 0x43ef, 0xb01c, 0x42fa, 0x41da, 0x43a3, 0x41c8, 0x3d6b, 0xb06d, 0x41c4, 0xba07, 0x391d, 0x4214, 0xb2f7, 0x44dc, 0xa011, 0x42fa, 0x4483, 0x1ddc, 0xbf7a, 0xb24d, 0x4270, 0x269c, 0xba2a, 0x435a, 0xb29d, 0x1ce6, 0x1852, 0x093e, 0xb0ed, 0xbaf1, 0x1d00, 0x02b5, 0x17ec, 0x42d8, 0xba16, 0x4624, 0x26af, 0x41b1, 0x1631, 0xb2fe, 0x4361, 0x1058, 0x41db, 0x1992, 0xbf1e, 0x41d8, 0x40c2, 0x43a3, 0x42ba, 0x41b7, 0x1d22, 0x43d5, 0xb213, 0x0005, 0x4004, 0x4171, 0x4038, 0x4364, 0x3420, 0x4135, 0x445c, 0x42f6, 0x4282, 0x4110, 0xbf5a, 0x3781, 0xb2f5, 0x40f8, 0xb2bc, 0xb264, 0x4591, 0x1812, 0xb06d, 0xba2f, 0xbf43, 0xa219, 0x46c0, 0xb2f3, 0x40f0, 0xbf5d, 0xbae7, 0xa3f0, 0x423e, 0xb246, 0x36e6, 0x402e, 0x400e, 0xb2af, 0xbfde, 0xba53, 0x085b, 0xab40, 0x43ac, 0x431a, 0x2778, 0x1da4, 0xb029, 0x14e4, 0x439f, 0x15ca, 0xa275, 0x1f63, 0x41ad, 0x3dee, 0xbf97, 0xbff0, 0x40e4, 0x448d, 0xb2d7, 0xbf8a, 0x41e5, 0xba2e, 0x2351, 0x1b2d, 0xbfe0, 0x4261, 0x414d, 0xbf55, 0xb28e, 0x1bb7, 0x439d, 0x41f4, 0x04a2, 0x040e, 0xb2a2, 0x4313, 0x1df1, 0xbac7, 0x41cc, 0x41d6, 0xbfb5, 0x432e, 0x42c0, 0x1cdd, 0x1ccb, 0xbf07, 0xb052, 0x1ead, 0x4281, 0x431d, 0x4365, 0x4206, 0x1b51, 0x43b9, 0x16d4, 0x0878, 0xbf89, 0x40d5, 0xa72b, 0xbad8, 0x3d59, 0x43b9, 0x42ff, 0x454d, 0xbae8, 0x11ea, 0xb2d7, 0x1455, 0xb2bc, 0xb29d, 0xba69, 0x461c, 0x19b1, 0xb282, 0xb054, 0x08a5, 0x45cc, 0x40ec, 0xbf9b, 0x0e9d, 0x1b02, 0x4099, 0x4080, 0x4322, 0x1313, 0x236c, 0x19cb, 0xa05e, 0x4350, 0x4299, 0xba45, 0x1afa, 0x1a45, 0x41f1, 0x058f, 0x18aa, 0x4190, 0xb0b6, 0xbfc8, 0xba6c, 0xb019, 0x4260, 0x40cb, 0x40e2, 0x1c47, 0xae93, 0x340d, 0x0e55, 0xba34, 0xaacd, 0xbfae, 0x416d, 0xb0ef, 0x4056, 0xba07, 0x41eb, 0x4490, 0xba61, 0x402c, 0x469a, 0x3dbe, 0x4015, 0x4560, 0x439c, 0x2497, 0x3ceb, 0x4196, 0xbf2d, 0x3df9, 0xb029, 0x1aa2, 0x1f04, 0xbfb3, 0xb006, 0xb001, 0x4067, 0x405f, 0xbaf8, 0xba17, 0xbf6f, 0xb2a3, 0x32e4, 0x27be, 0x4297, 0x3a02, 0x438b, 0x422b, 0xb0bc, 0x437e, 0x1f3a, 0xba0e, 0x40fa, 0xbf00, 0xa05d, 0x4052, 0x4302, 0x3ae8, 0xb207, 0xb01a, 0x4317, 0x440d, 0x44ca, 0xbad5, 0xa59a, 0x462c, 0xb22a, 0xa1e3, 0x46c4, 0x417a, 0xbf14, 0xbaec, 0xb296, 0x43c8, 0xb213, 0x2bbb, 0xb20d, 0x0e03, 0x43a6, 0x139c, 0xba1a, 0xacf0, 0x406e, 0x0198, 0xbf60, 0x400f, 0x428d, 0x40dc, 0x1881, 0xba24, 0x4222, 0xb0f8, 0x4380, 0x4093, 0x40a8, 0x3f1c, 0xbf49, 0xb2ae, 0xb238, 0x42d6, 0xb20c, 0x24d6, 0x1e47, 0xbf1b, 0x396a, 0x1a86, 0x4184, 0x402e, 0x3053, 0xb26c, 0x0b64, 0x4109, 0x46c2, 0x424b, 0xba29, 0x1c0e, 0x41bd, 0x4459, 0xb21f, 0xb20a, 0x43af, 0x424a, 0x432c, 0xb0db, 0x41c0, 0x4454, 0xbf88, 0x37f0, 0x4129, 0x4137, 0x436b, 0x44b8, 0x4043, 0x310e, 0xba09, 0x258e, 0x40a9, 0x4205, 0x4269, 0xba39, 0xb011, 0xbf00, 0x1e11, 0xba64, 0x1c52, 0x4113, 0x2228, 0xb2bf, 0xb058, 0x2bfe, 0x430f, 0x1d24, 0xbf0e, 0x43f1, 0x4113, 0xb268, 0x189e, 0x43a1, 0xb21a, 0x4193, 0xb287, 0x346a, 0x419b, 0x42a4, 0xad64, 0x41ac, 0x459d, 0x30a2, 0x2363, 0x40ad, 0x462f, 0x459c, 0x3579, 0x298b, 0x4105, 0xbfab, 0x3d00, 0x42dd, 0x4390, 0x1c75, 0x4156, 0x1cb5, 0x4349, 0x43f2, 0x45d6, 0x40f5, 0x4328, 0x41d2, 0x4165, 0xb00f, 0x4023, 0xba7b, 0xb0d4, 0x41f4, 0xbff0, 0x4085, 0x40a0, 0xbf78, 0x1892, 0xb067, 0x4055, 0x4094, 0x42d6, 0xba35, 0x42b0, 0x41e8, 0xac04, 0x42d6, 0x41aa, 0xba6c, 0xb273, 0x1c21, 0x40cf, 0x4492, 0x46d5, 0x1965, 0x4221, 0xb2c4, 0x43e7, 0x415a, 0x432f, 0x407f, 0xbf8c, 0xb283, 0x46a5, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd25485a3, 0x65f9ab76, 0x3fac21bc, 0xb38c4c4b, 0x9ae6ab78, 0xff30a4a6, 0x01b8bb18, 0x9f4c9ead, 0xa09d6abf, 0x3ea9cd0d, 0x60d7d350, 0x04406247, 0x923282a2, 0xf465cdc9, 0x00000000, 0x300001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x00280000, 0xd7ff6025, 0x00000028, 0x00000000, 0x28280000, 0x00000028, 0x00000000, 0x55f83e89, 0x3eaa3cd7, 0x2df79e85, 0xb55ae7e5, 0x55f83e88, 0x00000000, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xbad5, 0x1d59, 0xa4c4, 0x1a8d, 0x1b22, 0xa8c9, 0x45cc, 0x422b, 0x3e2e, 0x4264, 0x4080, 0x3475, 0x4121, 0x41b2, 0x4344, 0xa434, 0x45f0, 0x1e00, 0xbf34, 0x4409, 0x4310, 0x43ce, 0x43ed, 0x4044, 0x4019, 0x41dc, 0x1ea8, 0x4076, 0xbaf0, 0x42b2, 0x41ce, 0x406e, 0xb2e7, 0xbfc0, 0xb202, 0x1d55, 0xbf74, 0x42b3, 0xa20f, 0x41e6, 0x1551, 0x29e3, 0x417b, 0x4037, 0x2314, 0x4311, 0x4163, 0xb2d6, 0x416c, 0xadaa, 0xb258, 0x3667, 0xbfcd, 0x42b1, 0x42ab, 0xbfb0, 0x4591, 0xa1d1, 0xb05f, 0x442c, 0x1b90, 0x42c0, 0xb29a, 0x43e5, 0xb283, 0xba53, 0xb228, 0xbad6, 0x4031, 0x185e, 0xba00, 0xb2d1, 0x4298, 0xb2c9, 0xbfbd, 0x4229, 0xb297, 0xb016, 0x42a1, 0x4022, 0x120d, 0xba0f, 0x1dce, 0x435a, 0x4039, 0x183a, 0xbf70, 0x3a82, 0xa4d5, 0x250f, 0x4341, 0x413b, 0x4209, 0x467a, 0xbfa0, 0x1dc9, 0xbad6, 0x4106, 0x43dd, 0xbf8b, 0x45a0, 0x4253, 0x44d5, 0x1d62, 0x4128, 0xaaf0, 0x40be, 0x4373, 0xba76, 0x4314, 0xbf35, 0x400c, 0x019c, 0x43aa, 0x42dd, 0xba6c, 0xb23f, 0x4437, 0xbfd8, 0x4419, 0x2e1a, 0x4270, 0x429f, 0xba18, 0x2478, 0x3fa1, 0x40b6, 0x4130, 0x43c6, 0xbf9f, 0x401f, 0x01ae, 0x4264, 0xb264, 0x41db, 0xbac5, 0xbfd6, 0x4678, 0xb20e, 0x4244, 0x44c5, 0x1729, 0xba6d, 0x418b, 0x46a2, 0xb0bf, 0x38e1, 0x4386, 0xba49, 0x4387, 0x1dd4, 0x1e09, 0x40dd, 0x45e6, 0x40c6, 0xbf81, 0x05a7, 0xb2a8, 0xba43, 0x0104, 0x0c94, 0x42d5, 0x4153, 0x407a, 0xbfb6, 0x406f, 0x4146, 0x1c98, 0x43cf, 0x40c8, 0x40bf, 0x432b, 0x1ab7, 0xbf0b, 0x0f94, 0x42bb, 0xba05, 0xba0f, 0x43dd, 0x40d1, 0xb28d, 0x1d03, 0xb02f, 0x43f1, 0x430f, 0xbf19, 0xb05c, 0xba78, 0x423d, 0x41d4, 0x44d0, 0xa90f, 0x120b, 0x4649, 0x40b5, 0x3754, 0x42d1, 0x40a8, 0x2a90, 0xb20b, 0x41e6, 0xb066, 0xb290, 0x4188, 0x40c2, 0xb273, 0xbf41, 0x00f2, 0x427b, 0x1c07, 0x4654, 0xab5e, 0x03e3, 0x441a, 0x40c2, 0x4273, 0xb0a5, 0xba56, 0x45bd, 0x42a5, 0x4369, 0x049c, 0x23de, 0xbf76, 0x1a8a, 0x424c, 0xbff0, 0xbfc0, 0x1e0a, 0xb2c1, 0x4238, 0x4388, 0x4485, 0x34b5, 0xa687, 0x4235, 0xb295, 0xb0a0, 0x4175, 0x4280, 0xadc3, 0xa75e, 0x467c, 0x4169, 0xb20c, 0x42cb, 0xbf12, 0x1ad9, 0x1acf, 0x46e8, 0xba62, 0x4180, 0x1e87, 0x4028, 0x093f, 0x1810, 0xb015, 0x42d2, 0x1ba6, 0xbf04, 0xb0d5, 0x4156, 0x43e8, 0x4246, 0xbac6, 0x4204, 0xb272, 0xba2f, 0xb24e, 0x422c, 0x40e9, 0x43ec, 0x4019, 0x41a1, 0x1cad, 0x007a, 0x41b7, 0x4231, 0xbfc1, 0x3779, 0x1010, 0xbafe, 0x4311, 0x45b9, 0x469a, 0x41fe, 0xb205, 0x17e7, 0x0096, 0x166f, 0x4090, 0xb260, 0x4241, 0xb0ad, 0x1c8e, 0xbf4f, 0xb2a7, 0x432d, 0xba6a, 0x4358, 0xb26d, 0x429c, 0x40c8, 0x1de3, 0x244f, 0xa9e5, 0x412a, 0x1bff, 0x40b1, 0xb2c6, 0x469b, 0x0d3c, 0x1ef1, 0xbfb6, 0xa11f, 0x4011, 0xb298, 0x0847, 0x400c, 0xb255, 0xb0a0, 0xbae9, 0xba33, 0x413e, 0xb2a9, 0x40c4, 0x199a, 0xb2b8, 0xbf8b, 0x437d, 0x18dc, 0x19c1, 0x43ee, 0x0ce7, 0x0e99, 0x287c, 0x2f2a, 0x1e51, 0xbf90, 0xb217, 0x41a9, 0xb230, 0xb02b, 0xbf7b, 0x4319, 0x12d0, 0x4279, 0xba78, 0x1950, 0xbf90, 0x0410, 0xb08d, 0x260b, 0xbfa8, 0x3d18, 0x4338, 0x4185, 0x4061, 0x4042, 0x41ad, 0xbaef, 0x419e, 0x194f, 0x249b, 0xb2c1, 0xbf19, 0x1f07, 0x4370, 0x1cd7, 0x439a, 0xab32, 0x4256, 0xbf2f, 0x43cb, 0x412b, 0x41a7, 0x1b51, 0xb20f, 0xb03a, 0x4000, 0x3858, 0x3170, 0x2475, 0xae13, 0xb231, 0x4498, 0xbf9a, 0x45ad, 0x404e, 0x2df8, 0xb25b, 0x3db3, 0xa424, 0xb211, 0xb208, 0x43fb, 0x4241, 0x1937, 0x1c73, 0x40f4, 0x0729, 0x41cd, 0x2eff, 0x4288, 0xb0cc, 0x1be4, 0xbafc, 0xbf21, 0x1150, 0xba5b, 0x41ec, 0x1e5d, 0x4324, 0x430d, 0x189e, 0x416b, 0xb0ed, 0x1958, 0x43c4, 0x439c, 0x40aa, 0x42e3, 0xba03, 0x4255, 0x4014, 0x4583, 0xb2b2, 0xb278, 0xb233, 0x2f6f, 0x31e0, 0x41bb, 0xbfce, 0x439f, 0xa48e, 0x42cf, 0x434c, 0xb25d, 0x4317, 0xb281, 0x439b, 0x4098, 0x4201, 0xae51, 0x4281, 0x43c0, 0x417b, 0xb014, 0x4561, 0xba03, 0xb26b, 0x1e97, 0xb2f8, 0x4131, 0x4191, 0x40fe, 0xbf78, 0x227e, 0x4692, 0xba49, 0x4175, 0x4495, 0xb252, 0xba21, 0x43d7, 0x4317, 0x214c, 0xba01, 0x46d9, 0x41a8, 0x4023, 0xbf4b, 0x28eb, 0xb055, 0x431e, 0x40cb, 0x40d2, 0x1bce, 0x1a62, 0x4004, 0x438d, 0x1f3c, 0xb20b, 0x4025, 0x410f, 0xa075, 0xb262, 0xbf37, 0xbade, 0xb264, 0x1a2d, 0x19a6, 0x1811, 0xbfdd, 0xbad4, 0x4331, 0x2bc6, 0x42d3, 0x42ff, 0x23d1, 0x442b, 0x28f2, 0x1597, 0xbfa5, 0xb0ad, 0x41fc, 0x446c, 0xba71, 0x45d1, 0x24a8, 0x1f6f, 0xba30, 0x4228, 0x40dc, 0x402e, 0x43d7, 0xae6c, 0x403b, 0x4027, 0x4391, 0xbf69, 0xb2c4, 0x45ec, 0xba6a, 0x406c, 0x46a5, 0xba6a, 0xb2dd, 0xbad4, 0x41e5, 0x40a9, 0x41e5, 0x1abf, 0xbf74, 0x1c83, 0x42d6, 0x43d1, 0xba0f, 0x43f6, 0x042d, 0x4341, 0x42b2, 0xbf14, 0x1fe1, 0x4275, 0x4128, 0x401c, 0xb20d, 0x02b4, 0x4493, 0x41ac, 0xb21a, 0x417f, 0x4283, 0x2bd4, 0x40a7, 0xb293, 0x4028, 0xb2d5, 0xbac8, 0xb250, 0xbf2f, 0x426d, 0x424e, 0x433a, 0x1e87, 0x4352, 0xb295, 0xbadd, 0x4369, 0x05ca, 0x415d, 0xb215, 0x4247, 0x4220, 0x3a43, 0x0025, 0x4166, 0xbac9, 0xba2a, 0x4084, 0x0d4f, 0xb07c, 0x43b7, 0x45e5, 0x42de, 0x401b, 0x4382, 0xbf6b, 0x45ae, 0x279c, 0x4117, 0x4138, 0x0998, 0x425f, 0xbf24, 0x08b8, 0xb016, 0x1f7d, 0x2512, 0x19cd, 0xba6d, 0x42d8, 0xba0c, 0x30ef, 0x4407, 0x2342, 0x0c6e, 0x432c, 0x41d3, 0x13e8, 0x1350, 0x363a, 0xb072, 0x3631, 0xbf6f, 0x1a7d, 0x2ed0, 0x43d7, 0x28be, 0x3dc7, 0x15f5, 0x4118, 0xba6c, 0x4604, 0xa826, 0x423a, 0xb2bc, 0xb201, 0xbf0d, 0x41bc, 0x1afd, 0xb061, 0x3a1d, 0x4126, 0x42fe, 0x2aa7, 0x4165, 0xba5b, 0xb078, 0xbfc0, 0x4351, 0x42e4, 0x4183, 0x4211, 0x4651, 0x40b3, 0x4328, 0x43d0, 0x207b, 0xb24b, 0x1018, 0xae51, 0x18f3, 0x4037, 0xba1d, 0x1529, 0x3c02, 0xbf14, 0x0859, 0x40a1, 0x1daf, 0xb296, 0xb0b8, 0x2b0a, 0x4221, 0xbacc, 0x185d, 0x402d, 0x18ff, 0x42a7, 0xb28b, 0xbf6e, 0x43af, 0x419c, 0x3a08, 0x4230, 0x2623, 0x1903, 0x0462, 0x0149, 0xba1a, 0x1eba, 0x436d, 0xb0b8, 0x40df, 0xb279, 0xba60, 0x44f1, 0xba16, 0x4085, 0xb2c5, 0x1e6e, 0x40e0, 0xbf8c, 0x422a, 0x42ea, 0x42fe, 0x4544, 0xbf53, 0x430b, 0x3121, 0x4012, 0x4291, 0xb007, 0xb223, 0xb2a1, 0xb2ce, 0x09bd, 0x1d76, 0x4171, 0x40d3, 0x08b5, 0xacc9, 0x2e88, 0xb2dd, 0xb2e7, 0x42f5, 0x408d, 0x41a0, 0x41ef, 0x1c3f, 0x464e, 0x2766, 0xbf4a, 0x4087, 0xafb7, 0x461c, 0x43d1, 0xa153, 0x34a3, 0xbfa0, 0xba05, 0x0f87, 0x4095, 0xbfbf, 0x4252, 0x4160, 0x41d3, 0xb071, 0x09c2, 0xb05b, 0x4368, 0x40ab, 0x07d6, 0x45dd, 0xb047, 0x43b8, 0x42f8, 0x23c1, 0x19d1, 0x3c53, 0x42a3, 0x2e39, 0x4208, 0x04ed, 0x411c, 0x29ec, 0x43ac, 0xa6b8, 0x403b, 0xba30, 0xbf11, 0xb2fb, 0x1810, 0x05a6, 0x40b2, 0xa664, 0xbfaa, 0x434c, 0xb280, 0x2bd7, 0x22b9, 0x42ca, 0x0fe6, 0x2a39, 0xa493, 0x40ce, 0x424a, 0xba10, 0x42e2, 0xa50a, 0x43b9, 0x456b, 0xb2a1, 0x432b, 0x43ee, 0x05a5, 0x4142, 0x4417, 0x1ccf, 0x40a5, 0x43d8, 0xbfa1, 0xb268, 0x4092, 0xb20f, 0x401f, 0x40ea, 0x1b3d, 0xbaed, 0x0264, 0x0b78, 0x42fb, 0x0836, 0xb2ed, 0x424a, 0x40d1, 0x2cfd, 0x101f, 0x4293, 0x46a9, 0x46cb, 0xbfa3, 0x4058, 0x41d6, 0x40c1, 0xba3a, 0x45dc, 0x11ed, 0x3aa0, 0x468d, 0xbf80, 0xa4dc, 0x1936, 0x1766, 0x280e, 0x1ae8, 0xbfde, 0x33a9, 0x17d0, 0xa82c, 0xba1a, 0x43d1, 0x4310, 0x41b3, 0xb21a, 0x4270, 0xb285, 0x4055, 0xba60, 0x1c37, 0x224c, 0x43ce, 0x43c3, 0x234e, 0x43ca, 0xb213, 0xb29c, 0x4294, 0xba5c, 0xb2b3, 0x2d4b, 0xa6db, 0xbf1e, 0x4240, 0xa2f2, 0x138e, 0x1d24, 0x4235, 0x4569, 0x432e, 0x41ef, 0xa704, 0x25b2, 0x13bc, 0xb0eb, 0x41e4, 0x401e, 0xa704, 0x41ad, 0x41dd, 0x4298, 0x1b03, 0x423c, 0xb248, 0x405c, 0x0b80, 0x40c7, 0xba75, 0x43b0, 0xbfb6, 0x10b0, 0x41ec, 0xa1b4, 0xb26b, 0xb295, 0x412c, 0x44e5, 0x2796, 0x2eb7, 0x4165, 0xbfc9, 0x42b4, 0xac49, 0x40da, 0xba7f, 0x40c1, 0xbf00, 0x4376, 0x0fd1, 0x1a0f, 0x4356, 0x41ac, 0xb00e, 0x45f3, 0x2458, 0x333c, 0x0171, 0xb273, 0x4304, 0xbfe2, 0x45cb, 0xa2d9, 0x40d8, 0x4348, 0x1760, 0x412f, 0x42d4, 0x40a9, 0x424f, 0xbf18, 0xb07a, 0x40b1, 0x00cf, 0x43fa, 0x4299, 0x4367, 0x222f, 0x1212, 0x4401, 0x4388, 0x2451, 0xb29b, 0x4319, 0xb210, 0x420f, 0x40fd, 0x45b0, 0x32f6, 0x43b9, 0x1ca3, 0x1863, 0xbfac, 0xb083, 0x4385, 0x42d0, 0x422c, 0x1fe8, 0x2eaa, 0x1075, 0xb2a0, 0xbf0b, 0x42d9, 0xa9de, 0xb093, 0x42ce, 0x41eb, 0x40a4, 0x4242, 0x43df, 0xb254, 0xa2a0, 0x421b, 0x4305, 0xbf55, 0x10f1, 0xabe9, 0x41fb, 0x4333, 0x2b35, 0x405e, 0x1e82, 0x2a61, 0xbf46, 0x4234, 0xb26c, 0x1935, 0xbad9, 0x4164, 0xb241, 0x43fe, 0xb25b, 0x1df3, 0x4304, 0x1a0c, 0xba35, 0xbf0e, 0x336a, 0x3265, 0x068a, 0xba61, 0x06fd, 0x1c50, 0xb2c0, 0x4124, 0x4418, 0x41c6, 0xabd2, 0x429d, 0xba40, 0x4225, 0xb0e6, 0xb2c8, 0x4148, 0xb2b2, 0x400d, 0x1bc1, 0x24ff, 0xbf6c, 0x43f2, 0xba30, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xb95fc095, 0x46c9af8c, 0x44f999c1, 0xf75c725c, 0xeef9897b, 0x7583419d, 0xd7d5f784, 0xf2f60e6f, 0xa9e0afa9, 0x25abe41c, 0x5498383c, 0x7f430b81, 0xac6b7408, 0xf3f13b36, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0x00401400, 0x00000052, 0x00004000, 0xac6b75d0, 0x000000ff, 0x00000000, 0x00144000, 0xffffffae, 0x2206c8a0, 0x00000000, 0x0000007e, 0x00000000, 0xac6b7408, 0xac6b70f0, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x185d, 0x4287, 0x405b, 0xb0b0, 0x192e, 0xb012, 0x414b, 0x436f, 0xa9b7, 0x438f, 0x108e, 0xb067, 0x418e, 0x4013, 0x3736, 0x4549, 0x4259, 0xb2c8, 0x42a8, 0x415c, 0x43a9, 0xb28b, 0xbfb3, 0xb2f8, 0x43c9, 0x0a66, 0x4216, 0xbf64, 0x1ee1, 0x42e3, 0xb2bd, 0x00ef, 0xbaf4, 0x40be, 0x4353, 0x44a9, 0x4104, 0x1b6b, 0x403d, 0xb266, 0x078d, 0xba6b, 0x3c0d, 0x05b8, 0xbfbd, 0xbaee, 0x4277, 0x42c0, 0x0958, 0x4254, 0x40a3, 0x4250, 0xa6f7, 0x4050, 0x4541, 0xb211, 0x14d3, 0x41ae, 0x46c8, 0xba25, 0x3f96, 0x0fc8, 0xb246, 0xbf45, 0x45ce, 0x130d, 0xbf00, 0x2176, 0x404f, 0x435a, 0xafd3, 0x4093, 0xb2b3, 0x4202, 0x41df, 0x4089, 0x43d4, 0x0739, 0xaa0d, 0x3f8f, 0xba47, 0x1a4c, 0xb224, 0xbf5d, 0xb20e, 0x1e5c, 0x46a1, 0xba65, 0x40af, 0x41e6, 0x4076, 0xb23a, 0x3fa5, 0x438d, 0xba57, 0xae3f, 0x4141, 0xb2f9, 0xbfe0, 0x43d0, 0x1ec1, 0xb2b6, 0xb28b, 0x1ded, 0x055e, 0x41f0, 0x40f8, 0x0692, 0x4051, 0xbf97, 0x4388, 0xacb2, 0xb04b, 0x19ce, 0x32d5, 0x0b66, 0x4368, 0x1627, 0xb288, 0xb241, 0x4042, 0x2698, 0xb0b7, 0x429f, 0x262d, 0x40b3, 0xb065, 0x42f4, 0xbf14, 0x4009, 0x1661, 0x4304, 0x430d, 0xbfb5, 0x417a, 0x428d, 0x1d15, 0x0175, 0x4282, 0x1183, 0xb22c, 0x1fbc, 0x4352, 0x0e63, 0xa412, 0xba64, 0xa285, 0x41f2, 0x41de, 0x4372, 0xbafe, 0x00ef, 0x40d8, 0xb29d, 0x41a1, 0x44b5, 0xbfaa, 0x4668, 0xbaf8, 0x4180, 0x4462, 0x1dbb, 0x4492, 0x4022, 0xba5c, 0x4070, 0xb2b6, 0x4321, 0x4314, 0x4330, 0xa81d, 0x38c2, 0x427a, 0x417c, 0x4691, 0x4111, 0x4065, 0x1c3d, 0xb02f, 0x424a, 0x4047, 0x4349, 0x411a, 0x40fa, 0xbac1, 0xbf7e, 0x36e7, 0x4189, 0x233b, 0xb032, 0x4071, 0x2ae4, 0x2b36, 0x4290, 0x310b, 0xb22c, 0x430a, 0x4204, 0xbf7f, 0x172e, 0xba71, 0x08ee, 0x43bf, 0x1b31, 0xb068, 0x42b5, 0xbf00, 0x4545, 0xb08e, 0xa37d, 0xb234, 0xb011, 0xbfe4, 0xb208, 0xa6ee, 0x0e43, 0x107e, 0x405d, 0x1c5f, 0x0f55, 0x0342, 0x44cd, 0x2b29, 0xb086, 0xa40a, 0xb21d, 0x4028, 0x426b, 0x2a20, 0x4267, 0x406f, 0x419f, 0x4274, 0x4006, 0xa492, 0x18a2, 0x4177, 0x4140, 0x42a6, 0xbf83, 0x2b7b, 0x4242, 0x40a5, 0x079e, 0x46e0, 0x1907, 0x44f5, 0x094d, 0xb295, 0x438d, 0x409c, 0x465c, 0x43db, 0x36e9, 0xba2d, 0xbf95, 0x430f, 0x4129, 0xb2d0, 0x46dc, 0xa2f1, 0xbf16, 0x433f, 0xb27b, 0x4348, 0xaf01, 0xbf35, 0xba7d, 0x1b43, 0xbaf0, 0x4006, 0x2e62, 0x1e1d, 0xae4d, 0xa032, 0x40fd, 0x42bb, 0x4395, 0x423b, 0x420c, 0x41c1, 0x42fc, 0x466f, 0x4604, 0x41be, 0x4283, 0x426c, 0xbfd3, 0xb2d1, 0x4025, 0x42e2, 0x1d04, 0x228c, 0xb052, 0x37a0, 0x1c67, 0x403a, 0xbfa6, 0x0a6e, 0xb06e, 0xb0b7, 0xaeb4, 0x30fd, 0x423a, 0x41ed, 0x4040, 0x4116, 0x3637, 0x4302, 0xb2f8, 0x1e49, 0xb2e1, 0x437a, 0x05e7, 0x408c, 0xb02a, 0xba09, 0x402c, 0x43db, 0x4062, 0x4664, 0x38d1, 0xbfcb, 0xb012, 0x4595, 0xb2d4, 0xb224, 0xba4c, 0xbf06, 0x088c, 0x1077, 0x414a, 0xafd7, 0x1496, 0xbfa0, 0x45b0, 0x1c0c, 0x3354, 0x43e0, 0x1eab, 0x4281, 0x45c1, 0x406b, 0x075b, 0x3898, 0x4242, 0x430f, 0x4612, 0x40fb, 0xb03f, 0xb0f6, 0x400e, 0x4316, 0x418c, 0x270c, 0xbf93, 0xbac5, 0x18ed, 0xa70b, 0x42f6, 0x4238, 0xbf76, 0x3431, 0x4200, 0x46b8, 0x411c, 0x43a6, 0x439b, 0xbaff, 0x1a05, 0x404b, 0xb238, 0xb2f6, 0xba00, 0x0df6, 0x4021, 0x409b, 0xba4f, 0x1fb3, 0x252f, 0x4018, 0xa721, 0x0f4b, 0x40b9, 0x4211, 0x40d7, 0xba5c, 0xb0f1, 0xbfdf, 0x2384, 0xba0a, 0x435a, 0x42d7, 0x4653, 0x401e, 0xac83, 0x4073, 0xba4b, 0xb0df, 0x0b60, 0x4316, 0x4624, 0xa092, 0x0bf5, 0xbfb0, 0xb057, 0x42d6, 0x42fe, 0x156c, 0x41d6, 0xb0bb, 0x13d3, 0x4248, 0xbfbb, 0x437d, 0x0a8b, 0x4317, 0x40db, 0x42b3, 0x40cd, 0x4095, 0x4362, 0x42d2, 0x4475, 0x4137, 0xae4f, 0x400e, 0x0014, 0xbf7c, 0x420f, 0x1b5a, 0xbf70, 0x18ef, 0xb07e, 0x44fb, 0x43cf, 0x404b, 0x4171, 0xb0e2, 0xba20, 0xa7cc, 0x4580, 0xbf0e, 0x385a, 0x43d1, 0xac36, 0x434c, 0xb0d6, 0x4284, 0x12a5, 0x43bd, 0xb26e, 0x438a, 0xb059, 0x4370, 0x18bd, 0x362f, 0x4309, 0xb0fc, 0xb2d3, 0xbf13, 0xb23e, 0x4285, 0xb0fb, 0xba0b, 0x41b6, 0x04d1, 0xb249, 0x1ee9, 0xb2aa, 0x4340, 0x4285, 0x43e9, 0xbac6, 0xba62, 0x40d2, 0xb23d, 0xb0f0, 0x1cca, 0x083a, 0xb0b2, 0x43c6, 0x18d1, 0x2883, 0xbf66, 0x4121, 0xbaeb, 0x402e, 0x40c5, 0xbaf2, 0x41a9, 0x1839, 0xba61, 0xb0cf, 0xb2ae, 0x40ef, 0x405a, 0xb207, 0x1bb5, 0xab4a, 0x4572, 0x41f5, 0x33d6, 0x40a5, 0x1a06, 0xb2c3, 0x403c, 0x4341, 0xa0bd, 0x42a7, 0xb276, 0x1daf, 0x37ad, 0xbf5e, 0x3150, 0x43eb, 0x4213, 0xbf66, 0xb21d, 0x429b, 0x4004, 0x4012, 0x41ab, 0x467e, 0xb2d5, 0x4213, 0x4229, 0x0fbc, 0xa2a8, 0xbfd8, 0x4042, 0x32a7, 0x3304, 0xbf8a, 0x125d, 0x3c49, 0x4596, 0x4060, 0xa368, 0x448b, 0x409c, 0x2f24, 0xb0ca, 0x42ff, 0xb22a, 0x0971, 0x469a, 0xb260, 0x4243, 0x2673, 0x0f84, 0xbf36, 0xa7f5, 0xa907, 0xb081, 0xb2b0, 0x4157, 0x1ad4, 0x4360, 0x1c8f, 0xba6a, 0x40ac, 0x429a, 0x425f, 0xb062, 0xb2cd, 0x4228, 0x1806, 0x1d41, 0xbf0d, 0xba56, 0xbf60, 0xb255, 0x1952, 0x19ca, 0x2d41, 0xb0a0, 0x45e8, 0x3a49, 0xba58, 0x41cc, 0xbf00, 0xb283, 0x462e, 0x18ad, 0xbaf6, 0x26f5, 0x1680, 0x41bb, 0xb2d7, 0x40a9, 0xbf59, 0x40a2, 0xba67, 0x0b0a, 0xa067, 0x1d7b, 0x42ad, 0x2159, 0xbf2a, 0xa8ac, 0x421b, 0x1c35, 0x4381, 0x424c, 0x4216, 0xba6b, 0x4262, 0x432d, 0xb2f3, 0x402b, 0xb29f, 0xb032, 0x0bae, 0x40ba, 0x41a6, 0x4063, 0x405b, 0xb051, 0xbf9e, 0x4242, 0xb2cd, 0x2966, 0x4388, 0xb281, 0xb24f, 0x442a, 0xbae4, 0xb0d7, 0x25c2, 0x4013, 0x308b, 0x4231, 0x3ca8, 0x1fc9, 0x4563, 0x4420, 0xbaf2, 0x0a8b, 0xa349, 0xbada, 0x4188, 0x04c9, 0xb0c5, 0x1db0, 0xb079, 0x4204, 0x4305, 0xbf53, 0xa0e7, 0x434e, 0x1fb1, 0xb079, 0x1c64, 0x43e0, 0x1c51, 0x3ea3, 0x428e, 0x409d, 0x4323, 0x43b9, 0x42fd, 0x22fb, 0xb228, 0x41bb, 0x4320, 0x40f4, 0x2bec, 0x45cc, 0x419d, 0xb2d8, 0x325d, 0x44c3, 0x201f, 0x41d5, 0xbf59, 0xbf70, 0x2ef4, 0xb27b, 0xb03b, 0xa979, 0x24f4, 0xba21, 0x44a8, 0x028d, 0xb207, 0xa84c, 0x409d, 0xbf80, 0xbf26, 0xb2ef, 0xb2c9, 0x2659, 0x430f, 0xb2eb, 0x41d1, 0x10fa, 0x2baa, 0x09a6, 0xb02f, 0xb2c9, 0xba2f, 0xba67, 0x4211, 0xbf57, 0x10f3, 0x185c, 0x419a, 0x4271, 0xad46, 0xb077, 0x4627, 0xb2e2, 0x1a5e, 0x42a8, 0x422f, 0xb27a, 0x40f5, 0x4085, 0x0255, 0xa249, 0x4270, 0x4392, 0xb0ff, 0xb2e3, 0x46b2, 0xb2d1, 0x4001, 0x4639, 0x1fa7, 0x42f2, 0xa470, 0xbaf5, 0xbfdd, 0x1d21, 0x3f09, 0x2d80, 0x1aa9, 0xbf1a, 0x4061, 0x400e, 0xae29, 0xa3a0, 0x4010, 0x1f99, 0x121b, 0x41a0, 0xb2af, 0xbad1, 0xbf1c, 0xb298, 0x3db6, 0xb05b, 0xba33, 0xa3d9, 0xa2e3, 0x4162, 0x4188, 0x413b, 0x2805, 0x40a0, 0x4151, 0x2a6d, 0xbae7, 0x444f, 0x430e, 0x413a, 0x1767, 0xb05b, 0x303f, 0x001c, 0x43b9, 0x28e3, 0x3dad, 0x420e, 0x4205, 0xbf03, 0xb21b, 0x022e, 0x44d1, 0xb283, 0xb2c7, 0x0c1a, 0x4107, 0x198e, 0x43e8, 0x1c24, 0x3d5c, 0xa5ee, 0xbfd0, 0xbf43, 0x2a0c, 0x40ac, 0x4414, 0xa9ce, 0xbf2c, 0xbac9, 0x396d, 0x42fa, 0xbf0b, 0x4292, 0x43ba, 0x424a, 0xb25f, 0x42d6, 0x4188, 0x2f30, 0xb081, 0x38a0, 0x4213, 0x4130, 0x415f, 0x2c4c, 0xadab, 0xba23, 0xadc1, 0xb25f, 0xabf8, 0x45cd, 0x416d, 0x40d1, 0x403b, 0x1dc9, 0x4150, 0xba03, 0xb076, 0xbaf1, 0xbf2d, 0x40dc, 0x0d13, 0x18a9, 0x43a4, 0xb076, 0xba07, 0xb27d, 0x0b40, 0x3d88, 0x40a7, 0x0533, 0x2c71, 0x41cc, 0x42f9, 0x1ef9, 0x4583, 0x41e2, 0xba3b, 0xba44, 0x41b1, 0x42f2, 0xbfdf, 0xb23d, 0xbfc0, 0x071e, 0x19b6, 0x1a1f, 0x417f, 0xb268, 0x4132, 0xbaf6, 0x3af3, 0x1f37, 0x4352, 0x437d, 0xbfa2, 0x05b2, 0x43cb, 0x42a5, 0x43e5, 0x42ac, 0xa525, 0x18ad, 0xbace, 0x2d15, 0x1f1e, 0x01c6, 0xa4ad, 0x433a, 0x436a, 0x1e2a, 0x4389, 0x1898, 0xb2a5, 0x1a20, 0x42ec, 0xa6ed, 0xb038, 0xbacd, 0xbf8e, 0x41b9, 0x43ea, 0x42ba, 0x30cd, 0xaff4, 0x42fb, 0x460d, 0xbfa0, 0x1f87, 0x122b, 0x1b3f, 0x1cd8, 0x4109, 0x1cf9, 0x4146, 0x27b9, 0x4637, 0x43f7, 0xba68, 0x459b, 0xbf8d, 0xbf60, 0x1be6, 0x4691, 0xb267, 0x4062, 0xb2b4, 0xb2ca, 0x4198, 0x4331, 0x2b7d, 0xbf60, 0xbadc, 0xb221, 0x1ca0, 0xb200, 0x4007, 0x1e11, 0x43f6, 0x43af, 0x2b4b, 0x41d0, 0x21a9, 0x43d0, 0x43d1, 0xbfd2, 0x410a, 0xb0a3, 0xb266, 0x4153, 0x42fa, 0x371e, 0x4037, 0x1c33, 0x43de, 0xb0ef, 0xb059, 0x43fe, 0x410f, 0x0ed6, 0xb2ef, 0xbf33, 0xb080, 0xb023, 0x416c, 0x4390, 0x4484, 0x059f, 0x03c7, 0x4176, 0x425b, 0x42b5, 0xbf0b, 0x4680, 0x4202, 0x1ee9, 0x456a, 0xaae9, 0x4333, 0x1c96, 0x20f2, 0x3d1d, 0x434e, 0x39dd, 0x40c9, 0x433b, 0xbf21, 0x444b, 0xb295, 0x3462, 0x3275, 0x4110, 0xb214, 0x1edc, 0x4136, 0xbad1, 0x43a5, 0xb2ae, 0xadf4, 0x40ac, 0xa037, 0x4199, 0x41d7, 0x426e, 0xb2de, 0x43eb, 0xba38, 0xb253, 0x4092, 0xbfba, 0x413c, 0x4238, 0x41a0, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd919f6a3, 0x9e176573, 0x3795cb44, 0x666e776e, 0x370d6712, 0xb9f52030, 0x59d84426, 0xcda6b649, 0x3c751555, 0x13a5d9a9, 0x68b254f2, 0x4c783b43, 0xb4ebd830, 0x01180a73, 0x00000000, 0x900001f0 }, + FinalRegs = new uint[] { 0x0080fb0e, 0x005002e4, 0x00000000, 0x00000024, 0x00000000, 0x011021db, 0x0000003d, 0x0ffb8000, 0x0001470c, 0xfff80698, 0x00000003, 0x4c784f07, 0xb4ebd7a0, 0x01101e0b, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4139, 0x0239, 0x4470, 0x43b0, 0xa91a, 0xb068, 0x1c4a, 0x2666, 0x43c6, 0xa8cd, 0x4184, 0xbafc, 0xb29e, 0x42dd, 0x1781, 0x4033, 0x35f4, 0x31c2, 0xa3f5, 0xbf3a, 0x1884, 0xbafc, 0x424b, 0x42ab, 0x0955, 0x432a, 0x43f5, 0x410f, 0x4694, 0x401b, 0x41c7, 0xb058, 0x419f, 0x41f2, 0xb2fc, 0x1fd1, 0x464b, 0xbfb0, 0x41c5, 0x2b5f, 0x4330, 0x4152, 0xbfbf, 0xbf70, 0x43fb, 0x0114, 0x439b, 0x4376, 0x35bb, 0xa42b, 0x4130, 0x25df, 0x2ee3, 0x42a8, 0x4128, 0x42df, 0x4220, 0x43ec, 0x2ecd, 0xb291, 0xbf4f, 0xab48, 0x41da, 0x4198, 0x4214, 0x40c7, 0x3fb9, 0x424e, 0x414e, 0x01e3, 0x379f, 0x44b2, 0x4166, 0x42e8, 0x4208, 0xb2aa, 0xbf6f, 0xb05e, 0x40da, 0x463a, 0x193e, 0x42ef, 0x40ed, 0x1cc4, 0x4272, 0x1550, 0x42b1, 0x3bbb, 0x227a, 0x449c, 0x42b0, 0x41bf, 0xa310, 0xb0a6, 0x439e, 0x42a2, 0x4392, 0xbf1d, 0x2af5, 0xb2b1, 0x426b, 0x43dd, 0x4204, 0x466b, 0xb060, 0xbac4, 0xb0d2, 0x4320, 0x4006, 0x4303, 0xbf83, 0xa242, 0x42e1, 0xba65, 0xa4c1, 0xbfe0, 0xb26a, 0x3dc0, 0x1e3f, 0x423d, 0x439e, 0xba4e, 0xab3b, 0xba29, 0x4606, 0x41f2, 0xbac9, 0xb23d, 0xaac3, 0x19e2, 0xbf78, 0x1ad9, 0x4089, 0xba78, 0x46a3, 0xa084, 0xb27c, 0xbf72, 0x1439, 0x4000, 0xb275, 0x40cc, 0x4252, 0xbf7b, 0xbad4, 0x43d2, 0x1ce1, 0x4562, 0xb20b, 0x2fbd, 0xbadd, 0xba61, 0x2a48, 0x405a, 0x4343, 0xbf42, 0xb233, 0x4261, 0x423a, 0x42d4, 0x4330, 0x46f2, 0xbaf3, 0xb240, 0xba69, 0xba78, 0x091c, 0xb296, 0xbae4, 0x2223, 0x40f9, 0x42db, 0x1838, 0xb21f, 0x03b3, 0x43a0, 0xb2eb, 0xbf0f, 0x42ac, 0xb22e, 0xb013, 0xa332, 0xb2d0, 0xb271, 0x110f, 0x2fc7, 0x4668, 0x43dc, 0x41ef, 0x3a07, 0xbacf, 0x4158, 0x4129, 0x00ac, 0xb254, 0x416a, 0x4312, 0xad39, 0x4070, 0x1d12, 0x43e3, 0xaf2e, 0xb2fb, 0x250c, 0xa0c5, 0xbf96, 0x3811, 0x43a2, 0xa7d3, 0x1a37, 0x41b5, 0x414d, 0x43b6, 0xbacc, 0xa587, 0xbf35, 0x1ea8, 0x3706, 0xb0d5, 0x42f9, 0x405f, 0x426e, 0x1c33, 0x4369, 0xb01e, 0x4611, 0xa7cc, 0xbf9d, 0x1e4f, 0x1939, 0xbfe0, 0x43e7, 0x445d, 0xbfbc, 0x4146, 0x4093, 0xb255, 0x0325, 0x42bf, 0xbfe1, 0x45cb, 0x1a8f, 0xb2ca, 0x1912, 0x007c, 0xba33, 0x3771, 0x4355, 0xaa43, 0xbf00, 0xbad0, 0xbf93, 0x1e05, 0x1bb5, 0xb28c, 0x296d, 0xbf9c, 0xafa4, 0xb2e5, 0x4672, 0xbf27, 0xba7a, 0x419e, 0x4047, 0x4025, 0xb238, 0x4046, 0xb211, 0x3e6a, 0x4058, 0xbf32, 0x45c6, 0x3629, 0x42f1, 0x41da, 0xb2c1, 0x4441, 0x412d, 0xb09f, 0x43d8, 0x41c0, 0x40cb, 0x25d4, 0xa604, 0xa1e9, 0xb2b3, 0x42a5, 0x42c7, 0xb223, 0xb2f4, 0x403d, 0x41f9, 0x417e, 0xbf90, 0xbf3c, 0x1adc, 0x41c3, 0x0b57, 0xa98b, 0xace1, 0x19d8, 0x42bc, 0x141e, 0xbfb6, 0xb0bf, 0xba07, 0x425b, 0x3db1, 0x4186, 0x4052, 0x42c9, 0x114a, 0xb2c7, 0x4461, 0xbac5, 0x40f0, 0xa656, 0xa678, 0xb2a0, 0x1fdb, 0x186e, 0x4549, 0xb0c9, 0x2198, 0xb048, 0x4316, 0xa29b, 0xb2df, 0xb269, 0x1cf6, 0xbf8a, 0x418e, 0x012f, 0x1ff4, 0xb060, 0xad7c, 0x42a8, 0x42dd, 0x420e, 0x4153, 0xae8b, 0x03f1, 0x439f, 0x41f3, 0xbf83, 0x1395, 0xb281, 0x41f3, 0x19e3, 0x4002, 0x2ac4, 0x3a17, 0x4225, 0x4007, 0x4201, 0x45b4, 0xb29c, 0x410f, 0xa3ad, 0x43d4, 0xb2b9, 0x1d09, 0xb082, 0x4335, 0xbf58, 0xb297, 0xb0c3, 0x4240, 0xba79, 0xb230, 0xba2a, 0xbf57, 0xb0a6, 0xa975, 0x1f5b, 0x42e8, 0x190f, 0xbfc3, 0x420a, 0xb21a, 0x2c87, 0x417e, 0xbfe0, 0x40b1, 0x339a, 0x2f4c, 0x4353, 0x1b7c, 0x4349, 0x4335, 0x41c6, 0x4283, 0xa0bc, 0xa2da, 0x413b, 0x42c5, 0xbf8b, 0x4572, 0x061e, 0xbf90, 0x4159, 0xb280, 0xb0ef, 0x204a, 0x1dfe, 0x068c, 0x4122, 0x43be, 0x4286, 0xba1e, 0x4333, 0xb272, 0xa671, 0x4002, 0x42ed, 0x14b2, 0x32d9, 0xbf2c, 0x358d, 0x4615, 0x410b, 0x3ecd, 0xb223, 0x2f09, 0x1b6c, 0xbf2c, 0xbae5, 0x1cee, 0xb2fe, 0x42ea, 0xb045, 0x41ca, 0x4016, 0x10c6, 0x0828, 0xabe4, 0x123d, 0xa82a, 0xb04d, 0x45b0, 0xbfb0, 0x4118, 0xba15, 0x442d, 0x4098, 0x2145, 0x1a40, 0x1935, 0x394e, 0x45c2, 0xbf19, 0x402e, 0xb2ba, 0x42cb, 0x40d9, 0x4193, 0x41e8, 0x4261, 0x167e, 0xbad3, 0xbf1a, 0xb24d, 0x09c9, 0xb27f, 0x0295, 0x417d, 0x4362, 0x40a0, 0x4636, 0xba62, 0x2a28, 0x405d, 0x1ac1, 0x2e7e, 0x4027, 0x4222, 0xba7d, 0x429c, 0x40d6, 0x424b, 0x4430, 0xb042, 0xa73d, 0x406e, 0x3722, 0xb2a9, 0x4014, 0x44b1, 0xbfac, 0x4035, 0x4326, 0x0c54, 0x4007, 0xba2c, 0x43a7, 0x4333, 0x4092, 0xa7c2, 0xbf01, 0x27cb, 0xb047, 0x1a86, 0xbade, 0xb2b7, 0x2147, 0x0e71, 0xb295, 0xba38, 0x4388, 0x159c, 0x416b, 0xba1a, 0xb0d2, 0x0162, 0xba33, 0x40cd, 0x465c, 0x420f, 0x368f, 0x42bf, 0xbf60, 0x40f3, 0x4002, 0x0fed, 0x4165, 0x415c, 0xba29, 0xbfcd, 0x42b2, 0x40d4, 0xb0ac, 0x43f7, 0x12b0, 0x416d, 0x2be8, 0x4103, 0x408e, 0x43e6, 0x40d5, 0x427a, 0xba11, 0x1c74, 0x40b9, 0x4066, 0x4254, 0x4081, 0x2821, 0x4003, 0x44ab, 0x431a, 0x4282, 0x4569, 0x2f84, 0xb2ef, 0x4340, 0xb218, 0xbf8f, 0xb024, 0x1c87, 0x289f, 0x43d9, 0xba0a, 0x421d, 0x44f0, 0x42a2, 0x41b4, 0xa51b, 0x1c28, 0xa535, 0x40cb, 0x44b9, 0x40ed, 0x0bbe, 0x413e, 0x1eb8, 0x41ad, 0x1dac, 0x3f25, 0xb0ec, 0x12e7, 0xab9a, 0xbf77, 0xbae9, 0x1b49, 0x40a5, 0x40d7, 0x4392, 0x43e6, 0x190e, 0x40b0, 0xb2dc, 0x425f, 0x41ad, 0x10b1, 0x40f4, 0x43b7, 0x4037, 0xb03c, 0x1a72, 0x407b, 0xba50, 0x466e, 0xbf11, 0xbad4, 0xa393, 0xaa42, 0x41c5, 0x413c, 0x410a, 0xba1f, 0xba17, 0x41f4, 0x2138, 0xbfe0, 0x1ace, 0xba65, 0x40cc, 0x44e1, 0x1646, 0x4228, 0x43b7, 0xa7cf, 0x4160, 0xbfa8, 0xba18, 0x43f1, 0x1ed8, 0x4355, 0x2fce, 0xbacb, 0x41c5, 0x42c2, 0x44f8, 0x1f13, 0xbf5f, 0xaa2a, 0xb2fd, 0x1405, 0x25d1, 0xa644, 0x4134, 0x4280, 0x19f2, 0x0a34, 0x1b5e, 0x4137, 0xba1e, 0xba4c, 0x424c, 0x43d3, 0xb062, 0xb2b4, 0x43c3, 0x42a4, 0xb200, 0x4219, 0x4646, 0xa445, 0x4248, 0xbfe0, 0xbf05, 0x415f, 0x1832, 0x439d, 0xaaa6, 0xb234, 0x43b7, 0xb01a, 0xba02, 0x1f57, 0x43c9, 0x46c0, 0x4558, 0x4116, 0x1707, 0xb0b8, 0xbfc0, 0x41c0, 0x4032, 0x112c, 0x44e4, 0x4290, 0x04f6, 0xa289, 0x4226, 0x43bb, 0x1ee1, 0x434e, 0xbf66, 0x4210, 0x425f, 0xa705, 0x1aff, 0x31fd, 0xbf0e, 0x4295, 0x4107, 0xb283, 0x4067, 0x1dec, 0xb0b8, 0xb089, 0x2a04, 0x4172, 0xbfc0, 0x4217, 0xb2ea, 0x41a8, 0x41a2, 0x1610, 0x4294, 0xb016, 0x406e, 0x4281, 0x42a7, 0x1f7a, 0x1866, 0x4047, 0x4672, 0x20bc, 0x420b, 0x198a, 0xbf8a, 0x43f3, 0x3aed, 0xbfb0, 0x4612, 0x408c, 0x43e6, 0x0f02, 0x4214, 0xbfb5, 0xae8e, 0x408a, 0x41d9, 0x02f0, 0x341b, 0xb07d, 0x413b, 0xb0d9, 0xb2c5, 0x42f0, 0xb2f4, 0xba45, 0xa723, 0x42f1, 0x19aa, 0x4005, 0xb00b, 0x42ec, 0x4239, 0x43c4, 0x40ed, 0xaaa1, 0x1ff0, 0x4215, 0xb005, 0x414d, 0xb037, 0x43d1, 0xbf5b, 0xb24a, 0xb2cc, 0xb0e1, 0xb25e, 0x4298, 0x427a, 0xb0e5, 0x42be, 0x1cf1, 0x43b3, 0xb0b9, 0x4242, 0xbf0b, 0x435b, 0x14c6, 0x43d8, 0xb2e2, 0x414c, 0x4165, 0xad8d, 0xb2f3, 0xb053, 0x42a1, 0x4301, 0xa3c4, 0x43fe, 0x2e37, 0xb019, 0x4112, 0xba58, 0x40c9, 0x4204, 0x1870, 0x4592, 0x4360, 0x24a0, 0x42ab, 0x4066, 0x404b, 0x4144, 0x4252, 0x422c, 0xbfa5, 0x1caa, 0x02c7, 0x09fa, 0x4364, 0x43fe, 0x3b92, 0x4616, 0x4376, 0x1be2, 0x40ee, 0x34a4, 0x2c90, 0xba42, 0x42de, 0x435b, 0xb272, 0x413d, 0x1f64, 0x4099, 0x42b2, 0x43bb, 0x43fb, 0xbfd1, 0xba53, 0xb067, 0x42a2, 0x0f18, 0x43cc, 0x1f8e, 0x1dfe, 0xb28b, 0xbad5, 0x188f, 0x4363, 0x4299, 0x12b3, 0x0831, 0x41fb, 0x40fd, 0xbfa9, 0xba30, 0x4183, 0x42ad, 0x40b2, 0x418b, 0x42ea, 0xb0fb, 0x42aa, 0x42df, 0x1f34, 0x0bd2, 0x3df7, 0xb215, 0xba60, 0xb28d, 0x30e7, 0xb2a0, 0x4567, 0x42a8, 0xbfc3, 0xb0a0, 0x41c7, 0x0f8a, 0x16a1, 0x4168, 0x445e, 0xb236, 0x4271, 0xbf39, 0x4312, 0xbad2, 0x42e4, 0x1cb2, 0xb2c7, 0xbaee, 0x431c, 0x42dd, 0x42f2, 0xb262, 0x403a, 0x1dfb, 0x4330, 0x36a0, 0xba46, 0xb224, 0xbf56, 0x1b15, 0x3160, 0x40e6, 0xb025, 0x1390, 0x42a7, 0x078b, 0x4263, 0x141e, 0x0633, 0x4281, 0x40f5, 0x422b, 0x45f1, 0xba2b, 0x1a8a, 0x41a8, 0xb275, 0xb2a3, 0xb22a, 0xbf7a, 0x4540, 0x43da, 0x42da, 0xaa06, 0xb284, 0x3f4f, 0x42ae, 0x40a2, 0xbfb1, 0x4382, 0xbaf0, 0x1db0, 0x43af, 0x0b87, 0x433d, 0x461a, 0x2502, 0xb234, 0x3130, 0xb2b7, 0x43d2, 0xba2f, 0x37df, 0x3ab5, 0xafcb, 0x0f62, 0x46f8, 0x404f, 0x4560, 0x43fd, 0xbf59, 0x4237, 0x414e, 0x3b8d, 0x43e4, 0x4370, 0x2d12, 0xbf2b, 0x42cd, 0xba1a, 0xb2f9, 0xb2b0, 0xb0ea, 0xbac2, 0xb284, 0xaa89, 0x4041, 0x22ca, 0xb229, 0xafec, 0x438d, 0xbae3, 0x4314, 0x0739, 0xb229, 0xb2f4, 0xbf45, 0x189c, 0x43db, 0x4116, 0x409d, 0xb2b0, 0x4227, 0xb297, 0x469b, 0x31d0, 0x2360, 0x462f, 0xbacf, 0x4277, 0x305e, 0x2d0f, 0x381c, 0xbfcf, 0xb2f4, 0x42db, 0x435f, 0x3aaa, 0xb0ee, 0x44f4, 0x1b64, 0x4372, 0xb284, 0x40cd, 0x46d4, 0x13bf, 0xba5d, 0x4092, 0xba52, 0x41e2, 0x1dc6, 0xbf7d, 0x1eb2, 0x2606, 0x401f, 0x1d2f, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xb856fb9b, 0x192a6db9, 0x8500700e, 0x51377f3b, 0xcf8d1a8e, 0xeb924976, 0xf20bcc21, 0xd216b99a, 0xee51a075, 0x8bdc3aa5, 0x4c0f0144, 0xdedd3f79, 0xfe5590cd, 0x20762c18, 0x00000000, 0x600001f0 }, + FinalRegs = new uint[] { 0x00010041, 0x000000d0, 0x00010046, 0x00000060, 0x00000041, 0x00006000, 0x00000006, 0x00000000, 0x00001756, 0xad5388b8, 0x00000000, 0x000058ca, 0x00000000, 0x20762a7c, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x404c, 0x424a, 0x2986, 0xb245, 0x4373, 0x4073, 0x4006, 0x41df, 0x02dc, 0x3691, 0x42c5, 0xb2bf, 0x4020, 0x422c, 0x396a, 0xa360, 0xbf6f, 0x1d2b, 0x2d8e, 0x1815, 0x42f8, 0x41e0, 0x0084, 0x42c0, 0x43a4, 0x3d65, 0x11d0, 0xb2ee, 0x21d7, 0xbf8b, 0x3e83, 0x406a, 0x42ba, 0x4370, 0x446d, 0x3c88, 0x0c4e, 0x3ac9, 0x4284, 0xb2a9, 0x3c89, 0xb21a, 0x42df, 0x43e9, 0x4079, 0x1879, 0x40ea, 0xbafd, 0x435f, 0x40c2, 0x2c52, 0xba2c, 0xba4d, 0x45ba, 0xb288, 0x4228, 0x40cd, 0xbf67, 0x4092, 0x41bf, 0xba76, 0xb25f, 0x1c56, 0xb23c, 0xba28, 0x4063, 0x4083, 0xba20, 0xbad0, 0x40c5, 0xb245, 0xba73, 0x37df, 0x405d, 0x3e0c, 0xba79, 0xb241, 0x425a, 0x40c9, 0x4031, 0xb2b4, 0x4288, 0x4333, 0xbf95, 0xb20d, 0x42d8, 0xb0e6, 0x41c3, 0xbad8, 0x417b, 0x1c10, 0x030e, 0x403c, 0xb0e2, 0x12a0, 0x4451, 0x420b, 0xb20c, 0x3174, 0xbf60, 0xbada, 0xb2fd, 0xbf4a, 0xb22e, 0xb0ea, 0x19bf, 0x41cd, 0xbfbc, 0x412a, 0xba0d, 0xba38, 0x4092, 0x42af, 0x1db0, 0x41b5, 0xb281, 0x4059, 0xba6f, 0x410d, 0x4421, 0x410a, 0xa37d, 0xb289, 0x2b11, 0xad56, 0x383f, 0xb20c, 0xbfb3, 0x428f, 0x1f82, 0xba34, 0xb0ef, 0x1c73, 0x401e, 0xba6a, 0x40c8, 0x4554, 0xb0b6, 0x1d62, 0xb092, 0xb000, 0xbf7c, 0x40c0, 0x3aca, 0x1db0, 0x4124, 0x461c, 0xba2f, 0x1a4d, 0x1f44, 0xbfd9, 0x33dc, 0xb2c9, 0x41fe, 0x1921, 0xba75, 0x40ea, 0x424e, 0x43ca, 0xa25a, 0x423e, 0x4175, 0x410a, 0x41e1, 0x41c2, 0xbfca, 0x1f2c, 0xb2ce, 0xae78, 0xb262, 0x4045, 0x4287, 0xb24e, 0x1d5e, 0x4092, 0x2e56, 0xbacd, 0x400e, 0xba42, 0x40f4, 0xbafd, 0x4482, 0x40fc, 0x2f63, 0xb01c, 0x408d, 0x4008, 0x4250, 0xa846, 0xb2a7, 0x376b, 0xb012, 0xbad0, 0xbf7c, 0x4382, 0xb23e, 0x414c, 0x4091, 0xb0ab, 0x40fe, 0x4076, 0x458c, 0xa397, 0x445a, 0xb267, 0x43d7, 0x42c5, 0xa924, 0x41c9, 0x431d, 0x4207, 0x4182, 0x4012, 0x0c79, 0xb26a, 0x1b23, 0xabb2, 0x40df, 0xbf4a, 0x27b0, 0x4467, 0xa236, 0xba10, 0x4386, 0x42c2, 0x41d5, 0x4665, 0x41bb, 0x41d0, 0xba31, 0x4388, 0x1d9c, 0x2d56, 0x420d, 0x432e, 0xba31, 0x1d22, 0x4428, 0x419b, 0x1cd7, 0xb081, 0x4327, 0x4162, 0x42d8, 0x1947, 0xbf2c, 0x43cb, 0xb250, 0x4012, 0x3734, 0x4214, 0xa5bb, 0x4611, 0xba60, 0x2aed, 0xba05, 0x43a1, 0x431d, 0x0608, 0xbade, 0xa596, 0x4312, 0xb0b8, 0xb249, 0xbf6d, 0x42ac, 0xbfe0, 0x42e8, 0xb284, 0x42cd, 0x380e, 0x405b, 0x42e8, 0xb07d, 0xbad9, 0x43ca, 0x4282, 0xb283, 0x4353, 0x1ca4, 0xba3e, 0x41f3, 0x18a3, 0xb2da, 0x41d5, 0x416a, 0x2f3e, 0x46ec, 0xbf9c, 0x430f, 0x434e, 0xb2dd, 0xb27d, 0x1064, 0x43f0, 0xb2c9, 0x15a6, 0x28ab, 0xa5f9, 0x08db, 0xbfbe, 0x41ed, 0x41f9, 0x4385, 0x4197, 0x0b37, 0x418b, 0x133a, 0xb0f9, 0xba40, 0x434b, 0xb2b2, 0xa303, 0x464d, 0x1c7a, 0xbfe0, 0x46ac, 0x028d, 0x445c, 0xb034, 0xbf4f, 0x1af3, 0x1e0d, 0x4148, 0xaf26, 0xb01e, 0x1575, 0xba61, 0x29a4, 0xb238, 0x40ca, 0xbf5e, 0xab8b, 0x351a, 0x4121, 0xba67, 0xbfd2, 0xacfe, 0xba11, 0x4381, 0x186f, 0x36ef, 0xbae7, 0x1389, 0x1a97, 0x42be, 0x4123, 0x21ca, 0xb2ee, 0xb2b4, 0x463f, 0x2825, 0x449c, 0x4225, 0x4271, 0x1f0c, 0xba51, 0x43c4, 0x4148, 0x017d, 0xbf7b, 0x43e6, 0x41ee, 0xb255, 0x43e6, 0xb048, 0x4033, 0x4282, 0x412e, 0xbae8, 0xaede, 0x4125, 0x41c2, 0x4229, 0xb014, 0x403a, 0xb001, 0xba7c, 0xbada, 0x1dc4, 0x25c1, 0xa5f1, 0x43c5, 0xb293, 0x4011, 0x2dea, 0x1912, 0x43cc, 0xa25f, 0xba41, 0xbfaa, 0x41db, 0x4060, 0x1be4, 0x09de, 0x3dd2, 0x1ca8, 0xb200, 0x460d, 0x4066, 0x02af, 0x432e, 0x1dce, 0xb20f, 0x4468, 0x4058, 0xb037, 0x4137, 0x4158, 0xbf96, 0x39fe, 0x40a2, 0x417e, 0xa421, 0x3a98, 0x12e7, 0x4106, 0xb0fc, 0x2564, 0x40e0, 0xb0a6, 0x0a30, 0x42eb, 0x0ad5, 0x42ec, 0xbacc, 0xbff0, 0x13cc, 0x1bdc, 0x33ae, 0xbf90, 0xba18, 0x42f5, 0xb084, 0x410c, 0x4571, 0xbf94, 0x422b, 0x424b, 0x43a2, 0x433b, 0xba17, 0x1b36, 0x4221, 0x40e2, 0x4247, 0x1e2f, 0x3746, 0x43ef, 0xbf26, 0x1a41, 0x40ae, 0xb2f1, 0x4348, 0x43ce, 0x4128, 0xb27e, 0x400f, 0x41c4, 0xb023, 0x4054, 0x40cb, 0xb260, 0x41c9, 0x3d51, 0x42bf, 0x36f6, 0x1d00, 0x2cc0, 0x412b, 0xbf08, 0x03d1, 0xb280, 0x432e, 0xb0a1, 0xb2d1, 0xa60e, 0xb229, 0x418b, 0xbfe2, 0x438a, 0xba58, 0xa4a8, 0x4393, 0xb2ce, 0xbfd0, 0x447d, 0xb0fc, 0x42c8, 0xba09, 0x43eb, 0xbf42, 0xb24c, 0x19f5, 0x426b, 0xb2a7, 0x1baf, 0x1b6b, 0x4276, 0x0857, 0x22f0, 0xb238, 0x41fc, 0x440b, 0xbf88, 0x2db9, 0x1b79, 0x4117, 0xba00, 0x04c4, 0xbafa, 0x42fa, 0x0f86, 0x439c, 0xa156, 0xb274, 0x4223, 0xbfcf, 0x10c5, 0xb28b, 0x43df, 0x41f3, 0xbf8d, 0x43f8, 0x42b4, 0xb031, 0x0817, 0xb261, 0xbfe0, 0x4137, 0x1da3, 0x404b, 0xbae6, 0xb254, 0x412f, 0xbad5, 0xb295, 0x444e, 0xb28e, 0xb279, 0x406f, 0x45ea, 0xb2db, 0x1d07, 0xbfb3, 0x3b0e, 0x4138, 0x2a53, 0x421f, 0x40bd, 0x43cb, 0x430f, 0x21b6, 0x18d6, 0x43a4, 0xb04e, 0x43a3, 0x420e, 0x467e, 0x4134, 0xb060, 0x44fc, 0x41ed, 0xbff0, 0x43a6, 0x217f, 0xbad4, 0x417d, 0x42e7, 0x3065, 0x1ce3, 0x4016, 0xb218, 0x4219, 0xbf1d, 0x3fb0, 0x2c2e, 0x089c, 0xbaf3, 0x42ef, 0xb26b, 0xbf3d, 0x39ec, 0x4375, 0xb2e2, 0x191e, 0x433f, 0x38b8, 0x4217, 0xb074, 0xb272, 0xbfa4, 0x1bb0, 0x223f, 0xbaf3, 0x4192, 0xba64, 0x4278, 0xbace, 0x01b2, 0x431a, 0xa920, 0x40bf, 0x4498, 0x4331, 0x43fa, 0x4212, 0x41d4, 0x0257, 0x4136, 0x3ca6, 0x4229, 0xbad2, 0x199e, 0x08c5, 0x406e, 0x4480, 0xb2c0, 0xbf70, 0xbf23, 0x4295, 0x46d2, 0x159c, 0x40ca, 0x281b, 0x4333, 0xbfab, 0x1fdd, 0xba6f, 0x3ac7, 0xba26, 0x1da2, 0x4216, 0x1ca9, 0xb263, 0x408e, 0x4491, 0xb230, 0xac9f, 0x4098, 0x4302, 0x41ea, 0x181d, 0xb289, 0xb2fa, 0x1e16, 0x424c, 0x414f, 0x443e, 0x1c8d, 0x428f, 0x2079, 0x44d4, 0x437d, 0xbae2, 0x0ba5, 0xbf43, 0x435f, 0x4446, 0x4383, 0x1b2e, 0xb2c1, 0x1ac6, 0x45d5, 0xaa8d, 0xa9ff, 0x4280, 0xab67, 0xb222, 0xba68, 0x435d, 0x43a2, 0x1957, 0x425a, 0xb03f, 0x42dc, 0xba2c, 0x423f, 0x4071, 0xa66f, 0x06e5, 0xbf51, 0x40e0, 0x30d9, 0x4263, 0xba77, 0x18c4, 0x424f, 0x2054, 0x4136, 0xb09d, 0x3276, 0xb0c2, 0x0886, 0xb20e, 0x3afb, 0x41db, 0x31d9, 0x4062, 0x41cb, 0x1813, 0x40fc, 0x4389, 0xb0b2, 0xbaf4, 0xba68, 0xbf8f, 0x40c3, 0x4068, 0x4017, 0x131f, 0x4310, 0x113a, 0x1dad, 0x43ae, 0xb230, 0x2a6e, 0xb221, 0xb274, 0xbadf, 0xbfc9, 0x23d7, 0xba0a, 0xba0c, 0x40fa, 0xb2b1, 0xbfd0, 0xbf0c, 0x06f7, 0x18c9, 0x46ba, 0xba47, 0x422f, 0x04b2, 0xb2f1, 0x0069, 0x4037, 0xb243, 0x41a8, 0xb253, 0x1a27, 0xb069, 0x42e3, 0x007f, 0xa0c1, 0xaafc, 0xbf87, 0xaa98, 0xb2b1, 0x421a, 0xbaf5, 0x3e9b, 0xbfa0, 0xba22, 0x16cb, 0x408a, 0x1721, 0xb2a9, 0xb2c6, 0x158e, 0x40dd, 0x3ac2, 0xbf5a, 0xafd9, 0x4334, 0xba57, 0x3f09, 0xb0e9, 0xb01e, 0x1bb9, 0xbfc7, 0xb2d0, 0xbac9, 0xb042, 0x4138, 0xbfaa, 0x4429, 0x0e51, 0x424d, 0x423e, 0x3928, 0x415a, 0xa9ee, 0x4049, 0x1bfd, 0xb030, 0x417f, 0x1335, 0x4151, 0x41ad, 0xb2e4, 0x1b22, 0x42cb, 0x34dd, 0x4104, 0x186f, 0x3a3b, 0x437a, 0x42c9, 0x43c3, 0xb275, 0xb281, 0xbfb7, 0x1a57, 0x4257, 0xb06e, 0x215e, 0xa0cf, 0xa019, 0x4438, 0xb2ad, 0x446b, 0xba48, 0x4595, 0x44e3, 0xbf33, 0x4078, 0x4366, 0x1a43, 0x4078, 0x43e9, 0xb0c3, 0x44e9, 0xb008, 0x4000, 0x4015, 0x40f3, 0xbf7c, 0x4063, 0x41e7, 0xbf7d, 0xb0ee, 0xb27e, 0xa702, 0xb2b0, 0x414e, 0xbf00, 0xb2d7, 0x4033, 0x1fb8, 0xb06b, 0x2c22, 0xbfb0, 0x42ca, 0xb220, 0xb019, 0x4274, 0x125e, 0x426d, 0xab1b, 0x4046, 0x465a, 0xb2ca, 0xba26, 0xba09, 0xb07a, 0x4565, 0x4253, 0xbaf0, 0x1b4f, 0xbf67, 0x1ace, 0x403a, 0x1e60, 0x3a08, 0xb030, 0xba66, 0x4176, 0x4003, 0x4227, 0xb000, 0xba2d, 0xbf4b, 0x46a2, 0x4343, 0xba26, 0x438b, 0x4217, 0xb23c, 0x41c3, 0x44a0, 0x40dc, 0x40f1, 0xb29a, 0x1be5, 0xb085, 0x421e, 0x10ec, 0x41da, 0xbfd0, 0x423f, 0x40c7, 0x1689, 0xb2bd, 0x4005, 0xbf7e, 0xba14, 0xb234, 0x41a4, 0x2b94, 0x43f5, 0xb048, 0xb059, 0x0639, 0x4255, 0x14a6, 0x400e, 0x1f55, 0x41ad, 0x3822, 0x4218, 0x1cb8, 0xb22a, 0x4410, 0x40c0, 0xa6c5, 0xb03b, 0xbf46, 0x2d61, 0x43f2, 0x15b7, 0x4347, 0xacc9, 0x422d, 0xb2f4, 0xbfd0, 0xbfe4, 0x42fd, 0xb28c, 0x1d63, 0xba3f, 0x432e, 0x40bc, 0x4337, 0xbfa0, 0x05a9, 0x4160, 0xb030, 0x0da5, 0x0cad, 0x42e0, 0xb2fa, 0x3352, 0x4184, 0x4309, 0x4262, 0x1cfc, 0x43b2, 0xbf25, 0xbaf4, 0x1eb8, 0xba24, 0x3854, 0xb2af, 0x4074, 0x16ab, 0xb28c, 0xa7af, 0x348f, 0x415d, 0x2104, 0xa0dc, 0xba00, 0x129c, 0x413e, 0x40a6, 0x439a, 0x407c, 0x1c34, 0x1b66, 0xb256, 0x425d, 0x423c, 0xbf71, 0xa64f, 0x412c, 0xb257, 0x4035, 0x420d, 0x45da, 0x11e7, 0x403b, 0xb0a8, 0xb2c9, 0x420c, 0xba22, 0xb0f6, 0x211b, 0x41f9, 0xb2b1, 0xbf00, 0xba40, 0xbf45, 0xbada, 0xbade, 0xb24b, 0x424c, 0x4050, 0x40a3, 0x4669, 0xaaff, 0xb089, 0x4335, 0x4104, 0x401d, 0x418d, 0x4000, 0x42f2, 0xa8da, 0x4151, 0xbf38, 0x45a4, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x10ecc80c, 0x68d834e9, 0x195770d2, 0xf92fff26, 0x0d6457be, 0xa5d13c2e, 0x614b9061, 0xbd8cee14, 0x4cf5750a, 0x29eac697, 0xd29e8d3c, 0x1f3a1fb0, 0xc8c54efa, 0x28709a39, 0x00000000, 0x100001f0 }, + FinalRegs = new uint[] { 0x2870a341, 0x50e143f6, 0x2870a3f9, 0x00000000, 0xffffffff, 0xd78f6003, 0x000018dc, 0xffffffff, 0x4cf575b9, 0x525b6130, 0xffffcfff, 0x1bc387ef, 0xfc89683f, 0x28709fd9, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x155d, 0x212d, 0x4196, 0x4176, 0x42cb, 0x4436, 0xba13, 0x41cd, 0x4049, 0x42e3, 0x435c, 0xa81a, 0xba0c, 0xba6d, 0xbfa0, 0xb2d5, 0x42bc, 0xbfc0, 0xbf00, 0x45a9, 0xbf19, 0xba0c, 0xb256, 0xba51, 0x393b, 0xb048, 0xbf0f, 0xb2fc, 0x406e, 0x2068, 0x4007, 0xbf15, 0x4022, 0x4100, 0x4268, 0x408b, 0x42f9, 0xba0d, 0x41f9, 0x1b65, 0x4226, 0xb24a, 0x4677, 0x43ab, 0x3cba, 0x43ad, 0x3eae, 0x42df, 0xbf2a, 0x07ab, 0xb27e, 0x1e70, 0x440d, 0xb280, 0xba74, 0xb0a6, 0x1992, 0x275c, 0xb25e, 0xb027, 0x43c7, 0x4274, 0x4399, 0x40cb, 0x420e, 0x40be, 0x411c, 0x3e42, 0x1e52, 0x44ca, 0xb0b2, 0x4499, 0x418b, 0x3ecd, 0xac43, 0xbf76, 0xa566, 0xa33d, 0x4011, 0xbf22, 0x46ba, 0x45a1, 0x431d, 0x43a3, 0x4288, 0xbf02, 0x4157, 0x25bb, 0x24da, 0xbaf0, 0x4260, 0x12c9, 0x215f, 0xbf32, 0x432a, 0x419b, 0x413e, 0x1b77, 0xb0d4, 0x41a6, 0x0aae, 0xbff0, 0xb00f, 0x4091, 0x400e, 0x4036, 0xba1d, 0x1f20, 0x4194, 0xba73, 0x1a36, 0xbfc0, 0x405c, 0xbf93, 0x381b, 0x42d2, 0xba26, 0x41c8, 0xba43, 0xb03a, 0x410a, 0x4142, 0xb2b6, 0x46e1, 0x4227, 0xab57, 0xb29d, 0xbf3d, 0x195a, 0xba11, 0x42b7, 0x418a, 0x40b3, 0x420f, 0xb285, 0xbf41, 0x1aec, 0x10d6, 0x41eb, 0x2252, 0xbf80, 0xb0db, 0x1e20, 0x43c7, 0x41f8, 0xb24a, 0x4605, 0xbadb, 0x41b6, 0x4267, 0x3473, 0x42c0, 0x42e1, 0x42d2, 0x45aa, 0x4240, 0xbf90, 0x43d7, 0xb03a, 0x411f, 0x42ee, 0x4370, 0x4238, 0x4646, 0x1ce2, 0xbf65, 0x2fa6, 0x41fc, 0xba40, 0x40e0, 0x10fd, 0xb0b7, 0x1851, 0x4300, 0xbaf7, 0xb030, 0xb031, 0xa1cf, 0x135c, 0xb09e, 0x4131, 0xb039, 0x4548, 0x4022, 0xbfb2, 0x35c4, 0xb2a0, 0xb05a, 0xbfc3, 0x40bb, 0x4694, 0x419f, 0xbf80, 0x436f, 0x19c2, 0xb210, 0x1efd, 0x4104, 0x0fff, 0xbfc3, 0x4271, 0x4282, 0x2373, 0xb22e, 0x1d4c, 0x41a8, 0x41e0, 0xb264, 0xb0f2, 0xb202, 0x4087, 0xba73, 0x4351, 0x43cb, 0xae08, 0x40d4, 0x2529, 0xb22b, 0x412e, 0xa5ec, 0x42ce, 0x0a48, 0x3422, 0x186a, 0x2588, 0xba79, 0x40dc, 0x041b, 0xbf65, 0x434f, 0x1ca9, 0xb262, 0x299b, 0x4371, 0x43a0, 0x433d, 0x0586, 0x2f06, 0xb267, 0x4695, 0x4287, 0xbf7d, 0x1d88, 0x4665, 0xb282, 0x1912, 0x401e, 0x40cf, 0x1883, 0x4195, 0xba42, 0x45b4, 0xbf53, 0xb22f, 0xa9fe, 0xba1a, 0x3a48, 0xbaea, 0x4000, 0x07fe, 0x405f, 0x4351, 0xbf1d, 0x40f7, 0xb098, 0xb0a6, 0x3be8, 0x1980, 0xb014, 0x405d, 0x42bb, 0xac85, 0x06b7, 0x42a8, 0x42c8, 0x421e, 0xbf19, 0xb2ba, 0xb0b2, 0x4154, 0x42bc, 0x25fb, 0x4612, 0x4112, 0xb290, 0x40a0, 0x4195, 0x1e8b, 0x3ac1, 0x438d, 0x4340, 0x1a9e, 0x426a, 0x402a, 0x2d5c, 0xba5d, 0xb281, 0xb2ca, 0x4365, 0x191e, 0x2171, 0x3a69, 0x409a, 0x4304, 0xbfe1, 0x4316, 0xb2d2, 0xa9b9, 0x42e7, 0x4684, 0x2f78, 0xbaed, 0xbf81, 0x166e, 0x465e, 0xbadd, 0x4662, 0xbacb, 0x4003, 0x428b, 0x3668, 0x42ce, 0x42ab, 0xb22a, 0x40a0, 0x1cd8, 0x4029, 0xb2a7, 0xb061, 0x160d, 0x424f, 0xba7f, 0x4446, 0x3ea4, 0x0dac, 0xbfca, 0x0a49, 0xbae0, 0xb2d8, 0xabe7, 0xba1f, 0x40b2, 0xbafc, 0x4679, 0x37aa, 0x40e8, 0x12a2, 0xb0fa, 0x3fe1, 0x42a3, 0x1866, 0xba6b, 0x00a0, 0x3304, 0xbf5b, 0x4431, 0x40d8, 0x0a5a, 0x42e0, 0x41c7, 0x1980, 0xb096, 0xbfc3, 0xba10, 0x1efd, 0x2704, 0x419c, 0xb235, 0xbf97, 0x4391, 0x0740, 0x4057, 0x0d91, 0x4122, 0x1f06, 0x42d4, 0x42ce, 0x4211, 0x439a, 0x1d59, 0xbf98, 0xba68, 0x4275, 0x413f, 0x1d15, 0x4180, 0x414a, 0x421e, 0x438a, 0xa29c, 0x3fb4, 0xbf0b, 0x4025, 0x41ab, 0x428d, 0xb2b5, 0xb067, 0xbf90, 0xb258, 0xbf18, 0x2023, 0x43e4, 0x44d5, 0x37d4, 0xb09e, 0x427d, 0x29dc, 0x4171, 0x041a, 0xbfba, 0xb29f, 0xb26a, 0x427f, 0x4238, 0x4381, 0x15bc, 0x430b, 0x2760, 0x1aab, 0xb2ff, 0x4282, 0x103e, 0xa10f, 0x0891, 0x4056, 0x408e, 0x0276, 0xbfb6, 0x094c, 0x2c6a, 0xba4e, 0x403b, 0xb24c, 0xabc2, 0xb0f6, 0x4366, 0x1ded, 0x408a, 0x40bf, 0x4352, 0x10a3, 0xb0bc, 0x42e6, 0x40a5, 0x4551, 0x37ad, 0xbf02, 0xb0f1, 0x43e0, 0xb213, 0x4105, 0xb2bc, 0x40c4, 0xb20e, 0x1117, 0x40b2, 0x412b, 0x1dd0, 0x43a6, 0x43f9, 0x4391, 0x26fd, 0x07df, 0xbf13, 0x4152, 0x22a3, 0x1936, 0x1915, 0x40bb, 0x21c5, 0x2a65, 0x2572, 0x4117, 0x19ba, 0x43c3, 0x41ad, 0xb20a, 0x1fd6, 0x37bd, 0xb00b, 0x41b3, 0xa0b2, 0xb2f5, 0xb209, 0x40f3, 0xb244, 0x4380, 0xb20a, 0x41c7, 0xbf9c, 0xba2b, 0x4064, 0x4048, 0xb2f9, 0xbff0, 0x4399, 0x42bb, 0x1c85, 0x31be, 0x322d, 0x102a, 0x4614, 0x1860, 0x42fb, 0x1fd4, 0x1ba5, 0x4395, 0x3c83, 0xa80f, 0x439b, 0xba4a, 0xbf98, 0x40a3, 0x4065, 0x04dc, 0xbf38, 0x4260, 0xaa25, 0x220a, 0x05e2, 0x40ec, 0xbf60, 0x41b3, 0x127f, 0x40de, 0xb0a8, 0x43ba, 0x41be, 0xbf93, 0x2f9a, 0xb059, 0xb0a5, 0x4196, 0x4343, 0x400e, 0xbfb4, 0x4324, 0xb0fd, 0x09ed, 0xbad8, 0xb02d, 0x150b, 0x4016, 0xbfa6, 0xba3e, 0x439a, 0x3d0b, 0x1b7a, 0xba18, 0x241e, 0xa47d, 0xb22f, 0xb281, 0x40be, 0x4096, 0xb046, 0x43c2, 0x0c15, 0xb079, 0xb21e, 0xbf54, 0x42ab, 0xb234, 0x46b0, 0x1c7b, 0x4297, 0x43f4, 0x029f, 0x3405, 0x42d0, 0x42d2, 0x41b3, 0x40fe, 0x45c4, 0xb0db, 0x43ba, 0x4219, 0x4269, 0x1612, 0x41ac, 0x40ce, 0x3511, 0x4339, 0xb023, 0x130e, 0xb281, 0x1d1d, 0xbfcd, 0x419a, 0xba2b, 0x4011, 0xb295, 0x43f1, 0x40f7, 0xb264, 0xba37, 0xb2d0, 0xbf46, 0x18b3, 0x34c0, 0x040f, 0x401a, 0x138a, 0xa805, 0xb259, 0x440a, 0x1bee, 0xa5a7, 0x43f8, 0x407e, 0x424a, 0xb2f0, 0x433a, 0x39aa, 0x19d8, 0x42d2, 0xa041, 0x41c5, 0x214f, 0xb08f, 0x4231, 0x40b5, 0x05df, 0xbfa6, 0xa527, 0x455f, 0x2dc3, 0x1f02, 0xbaf8, 0x45e2, 0x466d, 0xa17d, 0x39d3, 0x1cdb, 0x4353, 0x43d0, 0x1b82, 0x0fc7, 0x4367, 0xba0b, 0xa21c, 0xbf4d, 0xba44, 0xa48c, 0x4185, 0x4168, 0x27d1, 0x21e1, 0x4316, 0x309b, 0x1859, 0x3d8d, 0x41fa, 0xba63, 0x2168, 0x0966, 0x43f5, 0x35ce, 0x3894, 0x412f, 0xbae5, 0x4557, 0x45b2, 0x4601, 0xb240, 0x4301, 0x4237, 0x4608, 0x1707, 0x08e5, 0xbf57, 0x3831, 0x41fe, 0x1722, 0x1f29, 0x45dd, 0x4233, 0x4550, 0xba2f, 0x3c89, 0xbfc0, 0xbf0c, 0xb2ad, 0x4042, 0x3c09, 0x1f76, 0xb29c, 0x43ce, 0x28fc, 0x4661, 0xb28c, 0x432b, 0x4277, 0xa32e, 0x42ae, 0x41c3, 0x1cad, 0x4203, 0x40bc, 0x43c6, 0xbf78, 0xbae9, 0x4222, 0x0094, 0x4350, 0x41d9, 0x415e, 0xb2b7, 0x4088, 0xb060, 0x40bd, 0xbf6b, 0x1a14, 0xa78c, 0xb2cf, 0x4021, 0x1adf, 0xba10, 0x1bfb, 0x41fe, 0x1b0e, 0x4369, 0x40e6, 0xbfab, 0x4077, 0x4673, 0x2c2d, 0x1df1, 0xb2bd, 0x42b0, 0xbf90, 0x03a8, 0xae03, 0x40ea, 0xbf15, 0x388f, 0x43ef, 0x2e69, 0x4130, 0x40b8, 0x3597, 0x4688, 0x1a86, 0x08aa, 0x4479, 0x45ea, 0x433c, 0xbf60, 0x1e2d, 0x1a2d, 0xb001, 0x1932, 0xaf95, 0x41aa, 0x4477, 0xb050, 0x42f6, 0xb219, 0xb03e, 0xb27e, 0xb0e7, 0xbae9, 0x4189, 0x4077, 0xbfb1, 0x407f, 0x454d, 0x044d, 0x410f, 0x46fa, 0x4265, 0x43aa, 0x41e0, 0x19da, 0x3196, 0x403d, 0x435c, 0x40f5, 0x4067, 0x3122, 0x1ea4, 0x40d7, 0x40dd, 0x1cb3, 0xb241, 0xb20d, 0xae94, 0xb20c, 0x42bb, 0x41a6, 0x4084, 0xa88d, 0xbfb8, 0x42c2, 0xb260, 0xba2d, 0x4119, 0xba63, 0xb2cf, 0x2aaa, 0xa2c2, 0x32e4, 0x42eb, 0xb24c, 0x04eb, 0x43fc, 0xb2c5, 0x4158, 0xb26f, 0x4664, 0x2f2a, 0xb0f7, 0x0290, 0x40d7, 0x1955, 0x1cc8, 0x405c, 0x1d87, 0x40b5, 0xbf89, 0xa488, 0xb2db, 0x401e, 0xb25d, 0xb2c8, 0xba28, 0xb09b, 0x0813, 0x06b2, 0xba07, 0x416e, 0x2476, 0x40c1, 0x1902, 0xb0c8, 0x3d59, 0x26cd, 0xbad0, 0x3338, 0xb2d5, 0x41c1, 0x19e0, 0xbfdf, 0x1e0b, 0x4301, 0x00f0, 0xb252, 0xb2be, 0x4614, 0x1c4b, 0xb00a, 0x44b3, 0xba75, 0x416c, 0x4283, 0x0e46, 0xae89, 0xba0e, 0x4287, 0x3290, 0x4296, 0x43fe, 0xa47a, 0x4243, 0xba58, 0x4076, 0xb2d3, 0xb2ee, 0x42c8, 0xbf8a, 0x4087, 0x465d, 0x0812, 0xb0d0, 0x1b04, 0x41c3, 0xba2c, 0x461b, 0x43c2, 0x1d5b, 0x439d, 0x41d2, 0xbf62, 0x4283, 0x41d3, 0x2cd1, 0x19da, 0xba70, 0xba7d, 0xbf4c, 0x4024, 0xa8a8, 0x3533, 0x4356, 0x4360, 0x40e8, 0x1a0c, 0x4217, 0x46ad, 0x2bcf, 0x4136, 0x41ba, 0xa279, 0x25e8, 0xbfe0, 0xb2a5, 0xa2f0, 0xbf15, 0x41e7, 0xbac9, 0x1f73, 0x410f, 0x0d8a, 0x40d3, 0xab54, 0x4219, 0x1984, 0xbada, 0x4384, 0x1b68, 0x44a8, 0x11d9, 0x4048, 0xbfa6, 0xb2f9, 0x42a0, 0x4237, 0x1298, 0xb0ce, 0x42e5, 0xb2c3, 0x40ce, 0x43eb, 0x29fe, 0x437a, 0xb238, 0xb2e5, 0xa899, 0xb05c, 0xbf91, 0x1947, 0x446b, 0x2586, 0x407f, 0xb275, 0x4193, 0x40eb, 0x439a, 0x2b6e, 0x4378, 0xb2b0, 0xba74, 0x438a, 0x1949, 0xab0c, 0xb208, 0xbae5, 0x4631, 0xbfcd, 0x4165, 0xba67, 0x4038, 0xb007, 0x177e, 0x4275, 0x1b62, 0x2fff, 0x45c6, 0x31d9, 0x429d, 0x18c1, 0x46dc, 0xb2c2, 0x41a9, 0x4390, 0x4362, 0xb0a3, 0xbae3, 0x102a, 0xb08c, 0x27c5, 0xbf0d, 0x1b86, 0xadce, 0xb0bb, 0x1d65, 0x412e, 0xb0d9, 0x2bf0, 0x320b, 0x433e, 0xadb6, 0x406a, 0x20e1, 0x1c81, 0x40ba, 0x13e1, 0x19cd, 0xb2a4, 0xbf5a, 0x46aa, 0x43b8, 0xb211, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x35faf52d, 0x178d83c4, 0xd9eed1e5, 0x2d403c23, 0x142e3ba1, 0xfdb96242, 0xb8568fae, 0xd9e9fcd8, 0xdb303d2f, 0xb29babc4, 0x02ba22df, 0x57781ed2, 0x79ab341b, 0x84723a31, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x00000020, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000000c5, 0x000000c5, 0x000000c5, 0x0fa51006, 0x79ab341b, 0x000000c5, 0x57781ed2, 0x57781ed2, 0xfffffd5f, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x0968, 0x41a2, 0x1b65, 0xb07e, 0xba6a, 0x403d, 0x25a7, 0xba28, 0x0ab4, 0x3baf, 0xbf63, 0x300c, 0x16a4, 0x3997, 0xb26e, 0xb094, 0xb27b, 0x4184, 0x26c7, 0x4019, 0xbf84, 0x437f, 0x1beb, 0x40ff, 0x4103, 0x4642, 0x41df, 0x4310, 0xa2cb, 0xbfbf, 0x33f8, 0x4387, 0x3837, 0x41fb, 0x26da, 0xb076, 0x459d, 0x044e, 0xbf19, 0x4041, 0x021c, 0x1051, 0x17ba, 0xba72, 0xb26d, 0x4418, 0x0d16, 0x4235, 0xb299, 0x2c02, 0xb27c, 0xbaf1, 0xac4f, 0x4434, 0xb20c, 0x0a23, 0x41dc, 0xb00f, 0xbf14, 0x3ed6, 0x404c, 0x45c2, 0x3528, 0x2bdb, 0xa5c7, 0x40b6, 0xbf60, 0x4042, 0x43ae, 0xa0f5, 0xbf62, 0x1fdf, 0xba2b, 0xb2eb, 0xa7ef, 0x1c7a, 0x02de, 0x43c3, 0xbfde, 0xb247, 0xba79, 0x4010, 0x43d3, 0x41eb, 0x4211, 0x43a9, 0x238e, 0x44ec, 0x3f4b, 0xb0e7, 0x4338, 0xb2b1, 0x40d2, 0x438c, 0x409e, 0xb285, 0x191c, 0xb2f6, 0x4047, 0x1d63, 0x213a, 0x400d, 0xb2c8, 0xa8b9, 0x04ea, 0x40ca, 0x407b, 0xbfdc, 0xb295, 0x265d, 0x404a, 0x427d, 0x42d7, 0xb03e, 0x1647, 0x4325, 0x183d, 0x0b07, 0x1c1a, 0xa8e1, 0x4147, 0xae6f, 0x2492, 0x10cf, 0x1af3, 0x411e, 0xa991, 0x12a1, 0xbfa1, 0x40df, 0xa2c7, 0x4275, 0x17de, 0x0093, 0x151f, 0x427d, 0x40d9, 0x4171, 0x429b, 0x3fac, 0x4170, 0x0994, 0x4140, 0xb04b, 0xad99, 0xb257, 0x3c21, 0x4301, 0xba22, 0xbae2, 0x3a3d, 0x4290, 0xbf5a, 0x41e3, 0x424d, 0xba3a, 0xb264, 0xb2e8, 0xbfe1, 0x42b7, 0xb217, 0x42a0, 0x404d, 0xb200, 0x42fd, 0x40c3, 0x410d, 0xb231, 0x434c, 0x41a2, 0x2c01, 0x02ff, 0x432f, 0x4335, 0x42a8, 0x426c, 0x4180, 0x446b, 0xbf80, 0xa9b8, 0x4223, 0xbf45, 0x02ae, 0xb2c5, 0xb291, 0xb0a2, 0x1e0f, 0xbaca, 0x44f9, 0x2594, 0xb02c, 0x1697, 0x1a0f, 0xba47, 0xa098, 0x411b, 0xbae1, 0x45eb, 0x42c2, 0xba6b, 0x409f, 0xb02c, 0x4382, 0xba24, 0xba79, 0x2dda, 0x4141, 0x41fc, 0x462d, 0xb0bd, 0xbfa8, 0x43d8, 0x2cda, 0x43dc, 0x401c, 0xba12, 0xb228, 0x436f, 0x423b, 0xb20a, 0x2089, 0xa5eb, 0x421a, 0xb2dc, 0x406c, 0xbf71, 0x439c, 0x43d2, 0xb27d, 0x43a6, 0xaa82, 0x4195, 0x403d, 0x1811, 0x41c9, 0xbafe, 0x41a8, 0x3d5e, 0x414b, 0xbf61, 0x1955, 0x374f, 0x2d82, 0xb23b, 0xb080, 0x0c12, 0x14c7, 0xba50, 0xb25c, 0x40ab, 0x4132, 0x4270, 0x4375, 0x184b, 0xbf3e, 0xbaec, 0x3645, 0x4045, 0x40e8, 0x1939, 0x40de, 0xbf03, 0x406e, 0x4201, 0x4544, 0x40fc, 0x427d, 0x2550, 0x0046, 0xa85e, 0x39dc, 0x419f, 0x401f, 0xb0a7, 0x4299, 0x1635, 0x430f, 0x40e4, 0x4163, 0x40b5, 0x4304, 0xbf80, 0x07b0, 0x1b13, 0xb207, 0x4218, 0x437c, 0xbf86, 0x400f, 0x4334, 0xb26a, 0x288d, 0x4235, 0x4462, 0xb000, 0xa2a9, 0xba54, 0x42e0, 0xb29e, 0x4273, 0xb09b, 0x341f, 0xb281, 0x33e0, 0x38e9, 0x40da, 0x464c, 0xbf9e, 0xa1f8, 0x40a4, 0x3a31, 0x4190, 0x459d, 0x40ec, 0x2a92, 0x463e, 0xba46, 0x014a, 0x4001, 0x1903, 0x428b, 0x38c6, 0x40d0, 0xb27c, 0x4361, 0x4467, 0x411d, 0x4044, 0x4205, 0x196f, 0xbf2b, 0xb2d9, 0x4351, 0xb29b, 0xbaef, 0x15fc, 0x410a, 0x1ac1, 0xb2c9, 0x150f, 0x419a, 0xb266, 0x0f11, 0x46a2, 0x412d, 0xb2da, 0xb259, 0x42ab, 0x0bad, 0x40a5, 0x42a6, 0x42a8, 0x1d2c, 0x42d0, 0xb2a3, 0x1ac2, 0xbf0a, 0x1c92, 0xb0a7, 0xaaa2, 0xbf5a, 0x4210, 0x428a, 0x1d36, 0xbfd4, 0x42d9, 0xb25f, 0x426e, 0x4198, 0x4200, 0x01dd, 0x1a2b, 0x4216, 0x4482, 0xbadf, 0x406f, 0x1839, 0x41f5, 0x43df, 0xb28f, 0xba7f, 0x402b, 0x412b, 0xb031, 0xbf1f, 0x419c, 0x43dc, 0xa31c, 0x43de, 0xb21c, 0x07e8, 0xa414, 0x41e8, 0x190a, 0x1974, 0x43a7, 0x43ec, 0xba51, 0x42a3, 0x4174, 0x1c77, 0x1cce, 0xb217, 0x0fd5, 0xbf4a, 0x07cb, 0x403c, 0x4011, 0xb20f, 0x2d7e, 0x4049, 0x4131, 0x1a6a, 0x187a, 0x420b, 0x4395, 0x24f5, 0x4009, 0xbf44, 0x0822, 0x0457, 0x137e, 0x1dd1, 0xb093, 0x1543, 0xb2df, 0x4022, 0xb0ca, 0xbf0a, 0xba0d, 0x44f5, 0x433e, 0xb274, 0x41ce, 0xb26d, 0x413e, 0x442b, 0xb0b6, 0x405b, 0x45b9, 0xb271, 0x454f, 0x3b98, 0xb250, 0x42ea, 0xbf41, 0x3ab9, 0x18ac, 0x42c8, 0x1f9c, 0x4342, 0x456d, 0x40d6, 0x32e8, 0x1e55, 0x40af, 0x40f6, 0x406d, 0xb2b0, 0x4081, 0xba3f, 0x45c3, 0xbfc9, 0x1197, 0x46e8, 0xb25f, 0x336f, 0x0655, 0xbf47, 0x460b, 0x467f, 0xaad1, 0xb0d9, 0x3dc1, 0xba3c, 0x1986, 0xbf18, 0x410f, 0x42de, 0x18db, 0xb284, 0xb2cb, 0x433c, 0x2e9e, 0x1c28, 0x4316, 0xbf0c, 0xa396, 0x44e4, 0x043d, 0x421d, 0x24f3, 0x46d8, 0xbfdb, 0x43ba, 0xbfe0, 0x3e5d, 0x42ba, 0x4141, 0x42d3, 0xba66, 0x4113, 0xbae7, 0xbfd2, 0x209b, 0xba1f, 0x430b, 0x40da, 0x25ba, 0x4183, 0x1e9b, 0x4160, 0x4321, 0x41ab, 0x1ddb, 0x1c06, 0x41c9, 0x44e5, 0xba67, 0x2f30, 0x42ad, 0x43be, 0xa2cc, 0x41d7, 0x434e, 0x41a1, 0x3017, 0x34dd, 0xbf42, 0xb253, 0x3bb3, 0x42a0, 0x40f5, 0x1c73, 0x42d9, 0xba2a, 0x42aa, 0xb2f7, 0x424f, 0xbf61, 0x427f, 0x319f, 0x4281, 0x0e25, 0x0bf2, 0x40df, 0x4367, 0xbf16, 0x128a, 0x42b4, 0x43af, 0x1d3c, 0x20d8, 0x42d0, 0x4374, 0xb291, 0x3c17, 0x413e, 0xbf67, 0xba0e, 0xb09e, 0x432c, 0x4355, 0x422b, 0x468a, 0xba1f, 0x414c, 0x4092, 0xb24a, 0x4085, 0x0c34, 0xb0e7, 0x055e, 0x4052, 0x350c, 0x00c1, 0x1b27, 0x42ff, 0xb258, 0x46db, 0xb204, 0xbf9a, 0x204b, 0xb228, 0x4438, 0x42aa, 0x1acc, 0x43c5, 0x40ea, 0x4028, 0x1ff9, 0x0634, 0xba3c, 0x41fe, 0x4205, 0x4310, 0x18b9, 0x1c8a, 0x4390, 0x23ab, 0x4131, 0x4632, 0xbf4e, 0x4110, 0x4337, 0x3ef8, 0xa221, 0xa445, 0x3505, 0x4016, 0xb0be, 0x41ca, 0xbff0, 0x40e0, 0xaf82, 0x43d3, 0x4004, 0xbaf2, 0x44fa, 0x43d3, 0xbadb, 0x1a50, 0x42ba, 0x07db, 0xba13, 0xb230, 0xbadf, 0x43a0, 0x18c3, 0xb2a0, 0xb283, 0xbf41, 0x1d95, 0x1070, 0x2958, 0xb047, 0x42fb, 0xb21f, 0x04f0, 0x43b4, 0xba7e, 0x4200, 0x41f2, 0x42ea, 0x42d1, 0x4270, 0x1f13, 0x434b, 0xb24d, 0x2b14, 0x1577, 0x1eae, 0x4207, 0x46d4, 0x41a0, 0x41d7, 0x0088, 0x4069, 0x1ee5, 0x40a1, 0xbf44, 0x4053, 0x440d, 0x1fe8, 0x427b, 0x4048, 0x40fb, 0x4340, 0xb270, 0x25d9, 0xba7a, 0x1859, 0x393f, 0xb2b3, 0x150b, 0x4332, 0x0152, 0x0589, 0xb2e5, 0x4088, 0x20a7, 0xba4d, 0xa406, 0x37d6, 0x40b6, 0xbadd, 0xbfd7, 0x409b, 0x4116, 0x031c, 0x417b, 0x438a, 0xbff0, 0x40a8, 0x1d5e, 0x426d, 0xb2ee, 0x4380, 0xbf0d, 0xb212, 0x1b52, 0x2108, 0x1b29, 0x1b85, 0x43cb, 0x379e, 0x43b6, 0xbfb0, 0xb0ce, 0x0e2c, 0x02eb, 0x223d, 0x4185, 0x400d, 0xb200, 0x435e, 0x4205, 0xba20, 0xbf5f, 0xb06d, 0x18b9, 0x4235, 0x42e5, 0xbaf4, 0xb0fe, 0x0de1, 0x458c, 0x463b, 0x1a59, 0x1efc, 0xb2de, 0x41df, 0x1f03, 0x3cb2, 0x1c76, 0xa923, 0x462f, 0x1abc, 0xbf0d, 0x41e6, 0x42ad, 0x4625, 0x0d75, 0x45bc, 0xba5a, 0x42b3, 0xba0b, 0x4250, 0x4123, 0x1956, 0x275c, 0xbaf5, 0x3f77, 0xba2f, 0x1b93, 0x4209, 0x4370, 0xb2d5, 0x4164, 0xbfe8, 0x40c2, 0x4399, 0x40ee, 0xbacf, 0x4230, 0x4055, 0x4342, 0x428e, 0x4372, 0x1af5, 0xb0f6, 0x4096, 0x1a4a, 0x1c74, 0x1b7b, 0x27d4, 0x1dfb, 0x3d43, 0x0c87, 0x2649, 0xbaf2, 0x40dc, 0xbf70, 0xbff0, 0xb24a, 0x40f6, 0xbf62, 0x42da, 0x46d3, 0x407b, 0x432d, 0xb207, 0x427a, 0x417b, 0x4156, 0x291d, 0x41c1, 0x41c8, 0x42d8, 0xb27c, 0xb0a5, 0x42cf, 0x2f18, 0x4661, 0x426a, 0x4316, 0x3f27, 0xbade, 0x42fd, 0x43e9, 0xbf5b, 0x4172, 0xac9d, 0x429e, 0x444b, 0xa3d2, 0x19ac, 0x411e, 0x211e, 0x40dc, 0x4332, 0x405f, 0x2ae6, 0x1bc6, 0xba2b, 0x1f21, 0x4124, 0x404b, 0x42b0, 0x4217, 0xb2b4, 0xb0cc, 0x2e76, 0x1f91, 0x23f0, 0xb206, 0x0d78, 0x4323, 0x312a, 0xbf46, 0x4198, 0x42de, 0x430c, 0x433a, 0x40f4, 0x41ea, 0x1c1d, 0x447e, 0xbae1, 0xa070, 0x415a, 0x4111, 0xb026, 0x4323, 0xbf72, 0x4419, 0x410e, 0x04c5, 0x422f, 0x16fa, 0x3627, 0x24fc, 0x42b9, 0xbf6e, 0x42ef, 0x1589, 0x05b3, 0xbf84, 0x3920, 0x01a7, 0x0d74, 0x4240, 0x241c, 0x1b1f, 0x408d, 0x1f58, 0x40d2, 0xb072, 0xbf29, 0x1994, 0x16a6, 0x403f, 0x4145, 0x2d03, 0xa4f8, 0x1b74, 0x4195, 0xba79, 0x4418, 0x4037, 0x40f8, 0xba19, 0xba7c, 0x44c8, 0x435a, 0x40e3, 0x43c3, 0x431b, 0x4124, 0x409b, 0x4355, 0x42db, 0x4300, 0x17a9, 0x3dfd, 0xbf0b, 0x41eb, 0xbac5, 0x0b64, 0x1f66, 0x098c, 0xbf60, 0x456a, 0xb0c2, 0x223c, 0x4301, 0xb20e, 0x4628, 0x4062, 0x1d7d, 0x43b7, 0x4369, 0x424c, 0xbaf7, 0xbfa9, 0xbace, 0x41c5, 0x4390, 0x420e, 0x40ff, 0x41f4, 0x4186, 0x4022, 0xbfa0, 0x4151, 0xa81d, 0x4019, 0x4220, 0x41f5, 0xbfcf, 0x43b6, 0xb290, 0xbfc0, 0x4311, 0x437d, 0xbfa0, 0xb2a4, 0xba2a, 0x41e6, 0xbf81, 0x0d08, 0x1e47, 0x1cb0, 0x4370, 0x12b8, 0xb261, 0xb26a, 0x437c, 0xbafe, 0x42b8, 0x43e5, 0xb0e5, 0xba58, 0x4158, 0x42a0, 0x4311, 0x4144, 0x1fc0, 0x1dc0, 0x08a7, 0x171f, 0x4637, 0x445d, 0x0a52, 0x1f8e, 0x1a78, 0xb2e5, 0xbf42, 0xba74, 0xba51, 0x1dd6, 0x45b6, 0x4278, 0x4389, 0x4230, 0xa3e1, 0x0f67, 0x42d0, 0xba44, 0xbfb0, 0x4383, 0x4583, 0xad13, 0x404e, 0x43b2, 0x4295, 0x0b4d, 0x42e6, 0x42bf, 0x107b, 0xbfb8, 0x1fa3, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x5384f6cd, 0x93b1eb86, 0x519fcf8e, 0x564cda67, 0xcde58997, 0xf3ec3c43, 0x6c08d4ea, 0x6e3c1d46, 0xe8c48cc6, 0xcea89f8e, 0x2f68758c, 0x0e162d4d, 0xc7597952, 0x77abbef9, 0x00000000, 0xa00001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000007, 0x00000000, 0xdcbede57, 0xcea8b10a, 0x00011309, 0x0e162d4d, 0x00011309, 0xf5b62f3f, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x1cdd, 0x1c98, 0xa82c, 0x22bd, 0xabb4, 0x4105, 0x3601, 0x432b, 0x406b, 0xb08c, 0xbacf, 0xb2ee, 0x382b, 0xb055, 0x4418, 0x16af, 0x4171, 0xb284, 0xb2c7, 0x4273, 0x43c5, 0xbf28, 0x4374, 0xb22c, 0xa063, 0x4242, 0x43f9, 0xba6a, 0x42a1, 0x4311, 0x4298, 0xb2b4, 0xbfcf, 0xba03, 0x41ed, 0xba7d, 0x444d, 0x1db4, 0x1de9, 0x192c, 0xbac1, 0x2cd3, 0xbfc9, 0xb224, 0xba09, 0x4098, 0x3ae8, 0x4310, 0xba6a, 0x4366, 0xb05a, 0xbf43, 0x406f, 0xb0a2, 0x0a86, 0x41c1, 0x413c, 0x1e56, 0x224c, 0xbf0e, 0xb016, 0x40d3, 0x40eb, 0x3b28, 0x4131, 0x30c8, 0xb23a, 0x40db, 0x2ff3, 0xb293, 0x1ea8, 0x4283, 0x4071, 0x40a5, 0x40db, 0x1539, 0xb2ff, 0xb0d4, 0xb2a4, 0xb2f4, 0xbf60, 0x433c, 0xbf0b, 0x3bac, 0x134e, 0xb241, 0xba6a, 0x1bf6, 0xb28b, 0xba3f, 0x43b1, 0x1f92, 0xba68, 0xb21e, 0x4272, 0x40a4, 0x2fde, 0xaec9, 0xad72, 0x42f0, 0x433f, 0xaa74, 0x1032, 0xa843, 0x19d7, 0xaf1b, 0xbf23, 0x14cb, 0x41a3, 0x1ff5, 0x40ca, 0xba1e, 0x4205, 0x4123, 0x401d, 0x1f2f, 0xbfc0, 0x3d68, 0x4368, 0x3441, 0x43e1, 0x04bf, 0x1934, 0xb299, 0x41bc, 0x4331, 0x00c2, 0xbf74, 0x4424, 0xb0de, 0x3563, 0xb2ea, 0x4325, 0x425f, 0x4331, 0x1516, 0x2bfa, 0xaa20, 0x4001, 0xb2dd, 0x1b4a, 0x435d, 0x1e15, 0x46fb, 0x1ca3, 0xb240, 0xba1e, 0x43ac, 0x04d1, 0x4288, 0xb053, 0x0649, 0x40ab, 0xbf6a, 0xa873, 0x41bd, 0x40bf, 0x0734, 0x410d, 0xbaf5, 0x431d, 0xb21d, 0x43bc, 0x1a8d, 0x43d1, 0x182f, 0xb2a7, 0xba53, 0xa1e5, 0x1f14, 0x40fa, 0x4595, 0x4373, 0x3a9e, 0x4316, 0x4382, 0x2233, 0x3f1e, 0xbfac, 0x285f, 0x435e, 0xb2e4, 0x1a0c, 0xbae3, 0x432c, 0xbf96, 0x3dd0, 0x4388, 0xb233, 0x1dd2, 0x0628, 0x40f6, 0xad19, 0xbfb0, 0xb01e, 0x3f19, 0x42fb, 0x4397, 0x42fb, 0x4380, 0x088d, 0x1c55, 0x4125, 0x41db, 0x4343, 0x285a, 0xb297, 0x0708, 0x1742, 0x438f, 0x40c9, 0x41ba, 0xb2d7, 0x42f4, 0xbf84, 0xb2be, 0x0ed1, 0xaa8e, 0x1766, 0xb2a2, 0xb23f, 0xb246, 0x42e7, 0xa98e, 0x2ed6, 0xba33, 0x4275, 0x3d19, 0xba01, 0xaf24, 0xba0e, 0x10fe, 0xbf70, 0x40ae, 0x0b85, 0x1b11, 0xba72, 0x1dbd, 0x4218, 0x420f, 0x43df, 0xba42, 0xbf27, 0xba3d, 0x4318, 0x4152, 0x431f, 0x417d, 0xbfaa, 0x1b13, 0x401f, 0x426e, 0xbfb1, 0xb098, 0x1cb5, 0xba05, 0x2f51, 0xa7e9, 0x4238, 0xb2de, 0x4374, 0x3239, 0xb0c3, 0xb2c4, 0xb2db, 0x43d1, 0x17fa, 0x1918, 0xba45, 0x4073, 0x19dc, 0x433b, 0x095c, 0x1f3f, 0x2dfd, 0x2105, 0x4120, 0x4174, 0x42cf, 0x32ac, 0xbf4a, 0xb28e, 0x401d, 0x445e, 0xbf00, 0xbf0d, 0x4353, 0xbfb0, 0x406e, 0x3f79, 0x2e94, 0xb225, 0x40fe, 0xa79f, 0x43fa, 0xb21f, 0x3c1d, 0xbaf9, 0x4178, 0xb224, 0x455b, 0x2da3, 0x36c0, 0x410b, 0xb2bd, 0x1fd4, 0x43ed, 0x4658, 0xbf45, 0xa2bb, 0xba4b, 0x1848, 0x407f, 0x0d0c, 0xb0d2, 0xbfb8, 0xb013, 0x41ff, 0xba13, 0x4246, 0x4413, 0x3ac4, 0xbf25, 0x0c28, 0xba3d, 0xbf00, 0xba77, 0x4115, 0x425e, 0xad5a, 0x44b9, 0x16b5, 0x445c, 0x4030, 0x41ba, 0x4337, 0x435f, 0x25e8, 0xbfe0, 0x43bf, 0x2e8c, 0x2603, 0x1d02, 0x339f, 0xb2da, 0xbf12, 0x4073, 0x46ed, 0x408a, 0x42a4, 0xbfa0, 0x428c, 0x3f60, 0xb264, 0x4656, 0xbf54, 0x4212, 0x1b30, 0x2cc3, 0xb2d3, 0x1c32, 0x4279, 0x2647, 0x4222, 0xba14, 0x1f8d, 0x414a, 0x4244, 0x4316, 0xa3df, 0x42d7, 0x42fc, 0xbf90, 0x4183, 0xa6b7, 0x4002, 0x2fb3, 0x4304, 0xba10, 0xbf80, 0xb21c, 0x414e, 0x1bc7, 0xbfad, 0xb2cd, 0x2f6f, 0xa397, 0x180a, 0xab69, 0xb0db, 0x0d11, 0xba35, 0xbfdb, 0x00c5, 0xa4b1, 0x400a, 0xba74, 0xa723, 0x050c, 0x4318, 0xaa07, 0x4135, 0x4181, 0xba71, 0x4090, 0x41a2, 0x412a, 0x4230, 0x4398, 0xbf18, 0x42c6, 0x441a, 0xb297, 0xb2fa, 0x41b7, 0x4134, 0xb290, 0x41c4, 0xb290, 0x195c, 0x4586, 0x433e, 0x402c, 0x34a4, 0x1add, 0x4316, 0x403b, 0xba51, 0x4257, 0x3d75, 0xba2d, 0x170f, 0x45bb, 0xbf26, 0x43ca, 0xb247, 0x427e, 0x4109, 0x1284, 0xbaf7, 0xbae0, 0xb200, 0x428b, 0x4376, 0x1cfc, 0x4091, 0x2b38, 0x1f17, 0x41c4, 0x40f0, 0xba52, 0x0c05, 0xbfaa, 0x4278, 0x41b2, 0x4334, 0x43b9, 0x42e4, 0x4338, 0x46cb, 0xaecc, 0x2b5b, 0x2407, 0xbace, 0x1ff5, 0x41ba, 0xb013, 0xb283, 0xbf94, 0x1c7b, 0x4444, 0xaf5b, 0x1e40, 0xbfe0, 0x4401, 0x40be, 0xa46a, 0x4229, 0x40a3, 0x43b4, 0x43e9, 0xb268, 0x419f, 0x41b5, 0x43b9, 0x4203, 0xbfa7, 0xb0a5, 0x46b9, 0x42ec, 0x1b9e, 0x400b, 0x06d9, 0xb0b2, 0x4230, 0xb0c6, 0x420e, 0x19d4, 0xbfe1, 0x40f8, 0x1f60, 0x19b8, 0x43c1, 0xb2a7, 0x4357, 0x4364, 0x4320, 0xba6a, 0x15cc, 0x40a4, 0x4378, 0xb0fe, 0x2e96, 0x4183, 0xba02, 0x4387, 0xb27f, 0x42cf, 0xaa6a, 0x404c, 0x4269, 0x036c, 0x42cf, 0x43df, 0x42a2, 0xbfda, 0x078b, 0x402b, 0x1d71, 0x43ee, 0x1a55, 0x1d58, 0x4077, 0x4021, 0x4165, 0xbf4a, 0x42c7, 0x4322, 0xb00e, 0x435a, 0x1fd9, 0xb227, 0x40da, 0x123a, 0x4323, 0x4353, 0xaa7b, 0xbf16, 0x40b0, 0x42df, 0x4334, 0x054d, 0x422d, 0xba0b, 0x46da, 0x44f2, 0xb214, 0x1a61, 0x20d7, 0x43b2, 0xb062, 0x369a, 0xa256, 0xb2f1, 0xb20f, 0x43ba, 0x3b86, 0x4278, 0x1b0f, 0x2c13, 0x4211, 0x46a0, 0xbf88, 0x43b1, 0x4094, 0xba06, 0x45d8, 0x410e, 0x1fc8, 0x1e0c, 0x429a, 0x3c95, 0x45ec, 0x42a6, 0xb2d2, 0x4039, 0x42c0, 0x40a9, 0x41f3, 0x1eb9, 0x04a4, 0xba5d, 0x4256, 0x4469, 0x40f4, 0x1687, 0x4244, 0x400d, 0x2eb0, 0xbf6f, 0xb018, 0xba24, 0x1911, 0x115e, 0x43c1, 0x406a, 0x4008, 0xb076, 0xba5f, 0xb0f5, 0x40a6, 0xb2f4, 0x0d52, 0xba72, 0x4172, 0xbf91, 0x1310, 0x4572, 0xad5e, 0x4399, 0x4081, 0x2c80, 0x258f, 0x1d8c, 0xb067, 0x426e, 0x40c0, 0xb2d0, 0x4544, 0xbf70, 0x4236, 0x43d2, 0x422f, 0x43e9, 0xb0a2, 0xbf3a, 0xa951, 0xb051, 0x1995, 0x42af, 0x4099, 0x40bb, 0x4221, 0x40b8, 0x4234, 0x422e, 0x444a, 0x2d03, 0x145f, 0x0cf6, 0x438c, 0x4358, 0x41e5, 0x4336, 0x0b76, 0x43e5, 0x4305, 0xb0fe, 0x412c, 0xa59e, 0x3a6d, 0xb23e, 0x4010, 0xbf23, 0x3890, 0x00ee, 0x40ec, 0x1a97, 0x1810, 0x436c, 0x43df, 0x4019, 0xb20f, 0xbfc0, 0x17e0, 0x0e47, 0xbfc8, 0x4068, 0x43bf, 0xbafa, 0x41be, 0x355a, 0xbfc3, 0x18e7, 0xbff0, 0x40e2, 0xbff0, 0x4132, 0x4207, 0xb042, 0x26ad, 0x4329, 0xb285, 0x425e, 0x46da, 0x100a, 0x070c, 0x40d9, 0xb2d3, 0xa9b2, 0x44fa, 0x3edc, 0x1d54, 0x435f, 0x3e4f, 0x2d4a, 0xbfcc, 0x4070, 0x4111, 0x42b6, 0x40f8, 0x210e, 0x3003, 0x41f4, 0x41e1, 0x1e19, 0x4152, 0xba39, 0xbaf6, 0x42aa, 0x4086, 0xa1ff, 0x42d5, 0x4294, 0x226a, 0x407c, 0x1f25, 0x3bea, 0x4158, 0x45bc, 0x445a, 0x29b5, 0xb2ed, 0xba7d, 0xbfa9, 0x41af, 0x31bf, 0x1f06, 0xb277, 0x4600, 0x40a6, 0xba24, 0x4023, 0x413b, 0xbac2, 0x1cdb, 0xba24, 0x00b5, 0x400b, 0x14e9, 0xba47, 0x42a3, 0x1eb7, 0x41b1, 0x42d2, 0x3742, 0xbfdc, 0x17d4, 0x1b82, 0x409c, 0x3640, 0x1a72, 0x4055, 0xba27, 0x4357, 0x2367, 0x43b5, 0x4607, 0xbf59, 0xb0c5, 0x1926, 0x4121, 0xba51, 0xb203, 0xbaf7, 0x066a, 0xb2b3, 0x43a6, 0x432b, 0x40b0, 0x085e, 0x0b22, 0xa46e, 0xbae7, 0xb2f0, 0x1d0e, 0xa37d, 0xba21, 0x40eb, 0x1083, 0x1bb3, 0x4223, 0x43a5, 0xb253, 0x402e, 0x4108, 0xbf37, 0xba0c, 0x43c0, 0xb079, 0x422d, 0x4081, 0x40e0, 0x45b9, 0x43b2, 0x4685, 0x189b, 0x400c, 0x0dbd, 0x425a, 0x0e61, 0xba61, 0x1ad3, 0x4335, 0x41a4, 0xbf64, 0x3714, 0x409a, 0x428d, 0x4421, 0xa9d1, 0xb298, 0x1dd4, 0xb028, 0x4600, 0x128e, 0x42da, 0x1a55, 0xb0da, 0xbf16, 0x423b, 0xba54, 0x42fa, 0x17f3, 0x0753, 0x4299, 0x414a, 0x1e9a, 0xb06c, 0x432b, 0xa23d, 0xba29, 0x4367, 0x19f0, 0x40c1, 0x1e5f, 0xb249, 0x1e21, 0x3078, 0x36fa, 0x2c53, 0xb033, 0x4362, 0x43cd, 0xa40c, 0xbf53, 0xba37, 0x4057, 0xb004, 0x411f, 0xbae2, 0x4671, 0x4276, 0x2505, 0x1044, 0x2743, 0x44cb, 0xaaed, 0x44e5, 0x04b3, 0x41bf, 0x1864, 0x4195, 0x4598, 0xb01d, 0xbf49, 0x4264, 0x4005, 0x0615, 0x43d3, 0xbf21, 0x4175, 0xb218, 0xba29, 0x4168, 0x1b8c, 0x407e, 0xbfa0, 0x4181, 0xb22d, 0x4350, 0x0803, 0x04f2, 0x1a2b, 0xba50, 0xbac6, 0xbf8d, 0x14a4, 0x459a, 0x41d1, 0xba19, 0xba51, 0x1cdb, 0xb283, 0xadd2, 0xb0a3, 0x4140, 0x1e4e, 0xb05a, 0x40ee, 0x428d, 0x37d3, 0x401b, 0x029e, 0x4351, 0x4230, 0x40ae, 0x40ab, 0x430f, 0x4121, 0x197e, 0x4307, 0x43ae, 0xb282, 0xab6a, 0xbf96, 0xb083, 0x41dc, 0x1d8b, 0x4131, 0x43f3, 0xb2af, 0x43e5, 0xb0bb, 0xb06d, 0x10ae, 0x42ef, 0x0d29, 0x4252, 0x1ef1, 0x400f, 0x4287, 0x1f0a, 0x0879, 0x38bd, 0x4306, 0x1094, 0x1b2a, 0x42ec, 0x26b2, 0x42fb, 0xbfbf, 0x1e68, 0x400f, 0x4261, 0x4248, 0x4301, 0x03a6, 0x401d, 0x43a6, 0x42ff, 0x412f, 0xb078, 0x2f4c, 0x059e, 0x404d, 0x40d0, 0x4033, 0x4067, 0x41d5, 0x40f7, 0x4044, 0xbff0, 0x4183, 0xbf23, 0xb208, 0x1922, 0x43cb, 0x2b84, 0xbf46, 0x1c9d, 0x2a1d, 0x41f7, 0x43d2, 0x4297, 0x11e1, 0x3929, 0xba74, 0x1b10, 0xb267, 0x1dc6, 0x403a, 0x420d, 0xb246, 0xb2e7, 0x0450, 0x04fc, 0xbf0e, 0x4680, 0xb015, 0x0f61, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x9761e594, 0x04a76bb3, 0xdf9b4478, 0xd3a7ec97, 0xb0bb8660, 0x52cb52ca, 0x60d713aa, 0x30959c7c, 0xca01d692, 0xd5efbf25, 0x11a77f9a, 0x39816f99, 0x60fa61fd, 0xf4de7402, 0x00000000, 0x200001f0 }, + FinalRegs = new uint[] { 0x00000000, 0xffffffd5, 0x00000000, 0x9e100020, 0x00000000, 0x9e100022, 0xffffffa5, 0x00000000, 0x00000000, 0xf4de7411, 0xd5efb38f, 0xcace1224, 0x60fa61fd, 0x60fa67ad, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x203c, 0x4138, 0xb29a, 0x402d, 0xbf0a, 0x4151, 0x42a8, 0x4489, 0x4091, 0xb2e9, 0xbf8d, 0xb205, 0x41e3, 0x08fb, 0x048b, 0x401f, 0xb2c6, 0x4106, 0xbf00, 0xba13, 0x391f, 0xba43, 0x4000, 0x407c, 0xba09, 0xb2bd, 0xbf25, 0x13a2, 0xb2ca, 0xaf42, 0x4327, 0xb2d4, 0xb2be, 0xaf98, 0x416c, 0x2cbd, 0x41c7, 0x401e, 0xb281, 0x4107, 0x42d2, 0xbfd2, 0x1939, 0xb248, 0xa7cd, 0x1ac5, 0xbf00, 0x2d7b, 0xb260, 0x43c6, 0xb28f, 0xb29a, 0x3844, 0x3701, 0xb08b, 0x1d56, 0xbff0, 0x42aa, 0xb2d2, 0xbfd8, 0x21a3, 0x34fc, 0x4617, 0x41a3, 0x284d, 0x447c, 0x42dc, 0xba10, 0x42ad, 0x1858, 0x1b99, 0x009e, 0xba48, 0xaf30, 0x413b, 0xbf6b, 0xba13, 0x1e8b, 0x29ae, 0x40bb, 0x1f0d, 0x1bf6, 0xb0fa, 0xb085, 0x1c93, 0xae0c, 0x44ad, 0xb294, 0x4327, 0x1ea8, 0x0fbb, 0x4172, 0x43e1, 0xb274, 0xab3a, 0x421e, 0x4003, 0x4370, 0x189a, 0x4289, 0xbf1d, 0xba66, 0x1d6f, 0xb2da, 0xbfb0, 0x4017, 0xa419, 0xba6f, 0x127d, 0x43f9, 0x0b3c, 0x1a9d, 0x1c19, 0x2d38, 0xbfa0, 0x38c1, 0x406f, 0x181c, 0x431a, 0x4069, 0x4359, 0xaa68, 0x1e23, 0x0012, 0x047a, 0xbf6e, 0x15c1, 0x4572, 0x4344, 0x0ff8, 0x46e4, 0x41db, 0x3216, 0x421c, 0x4195, 0x23cb, 0x4261, 0xa31a, 0xbad8, 0x06e8, 0x4197, 0xbf69, 0x420d, 0x228b, 0xbae0, 0x302d, 0x4046, 0x4016, 0x4137, 0xbf79, 0x2c74, 0x407b, 0xa65d, 0x401b, 0x43ea, 0x09f7, 0x41ad, 0x401d, 0x3324, 0xb2cb, 0xa421, 0x40b4, 0x43f1, 0xbfbe, 0x3ae0, 0x45c1, 0x4014, 0x41c2, 0x4596, 0xbaf1, 0xbf1f, 0x4035, 0xb29e, 0x4070, 0xb0cc, 0xbfb4, 0xbad1, 0xba09, 0x1b93, 0x414a, 0xbf43, 0x40b8, 0xb0ef, 0x41c2, 0x4157, 0x36c8, 0x42b7, 0x40dd, 0x43ab, 0x0d39, 0x32b9, 0x1cfc, 0x1c79, 0xa753, 0x4613, 0x418a, 0x40e8, 0xb25e, 0x0bf3, 0xbf9f, 0x4100, 0x3e0f, 0xb205, 0xb2d7, 0x12b8, 0x430e, 0x4032, 0x42a7, 0x18fe, 0x1ac1, 0x221c, 0xb086, 0x0cf7, 0x4367, 0x426e, 0xabea, 0x4282, 0x1da5, 0xaca3, 0x4277, 0xbf24, 0x4308, 0xb035, 0x41c0, 0x1434, 0xaf0d, 0xbfa0, 0xb20a, 0xbacd, 0xb2e8, 0x0248, 0x40cb, 0x41be, 0x43c5, 0x43a2, 0x40cb, 0x0c9a, 0x2979, 0x0138, 0x1f54, 0x4166, 0xb2de, 0x1e7e, 0x4132, 0x4194, 0xb2a8, 0xbfc3, 0x419d, 0xbadc, 0x43db, 0x4363, 0x42a6, 0xb02d, 0x40c1, 0x32dc, 0x467d, 0x43b0, 0x4581, 0x431c, 0x4232, 0x3daa, 0x436d, 0x4288, 0x40f7, 0x1554, 0x43f1, 0xbf0d, 0x0509, 0x2371, 0x4387, 0x42bc, 0x1f69, 0xb07f, 0x43e2, 0x29d5, 0x41d7, 0x297a, 0x4285, 0x09c3, 0xb2e8, 0x417d, 0xba00, 0x42d1, 0x4286, 0xb203, 0x40c7, 0x46e4, 0x4558, 0x40d4, 0x12b2, 0x3bf4, 0xb09a, 0x1a12, 0xbf48, 0xbaf4, 0x1858, 0x412f, 0x051a, 0x21cf, 0x4118, 0xbaf9, 0x1a50, 0xb245, 0xbf78, 0x43bd, 0x416a, 0x4178, 0xae37, 0x425a, 0x4038, 0xa7bb, 0x414d, 0x1bcd, 0x395a, 0x411d, 0xba0a, 0x41d4, 0xbf80, 0x059e, 0xb0bb, 0x0522, 0xb276, 0xb2f9, 0x2920, 0xa495, 0xbac2, 0xbfe4, 0xbfe0, 0x4081, 0xb0f1, 0xb258, 0x45b1, 0xa68e, 0x428f, 0xb015, 0x11ae, 0x0d68, 0xb255, 0x44e4, 0x43d2, 0x42ae, 0x310f, 0x1e7e, 0xb030, 0x2a4f, 0xbfa1, 0x1f11, 0xb213, 0x4237, 0x1e41, 0x43d5, 0xb2e0, 0x425f, 0xbf00, 0x432a, 0x0299, 0x18de, 0x434f, 0x4304, 0xb06a, 0x33aa, 0x1e4d, 0xba46, 0x42ca, 0x4115, 0x4116, 0xa597, 0x30fc, 0x10be, 0x41a3, 0xbf48, 0xa37b, 0x40eb, 0xb0e4, 0xb28b, 0x4002, 0x416c, 0x40aa, 0x461b, 0x43b9, 0x18d6, 0x4276, 0x4447, 0x4692, 0x464b, 0x427f, 0xba74, 0xba05, 0x2045, 0x1bad, 0x4097, 0xa1f9, 0x41a4, 0xb2e0, 0xbf34, 0xba77, 0x1ef1, 0xba6f, 0x435f, 0x44fb, 0xbf74, 0x4247, 0x4275, 0x1e8d, 0x40bc, 0x2747, 0x4001, 0x419c, 0x20f8, 0x428d, 0x02eb, 0xbac8, 0x0ba6, 0x44ac, 0x41ce, 0x4314, 0x0bdb, 0x1251, 0xb256, 0x423b, 0x4089, 0x4066, 0xbf63, 0x1cd3, 0x4095, 0xba21, 0x430c, 0x3453, 0x42b6, 0x0ace, 0x4181, 0x40ff, 0x4215, 0x43a8, 0x4244, 0x1aa6, 0x1a97, 0xa74d, 0x428c, 0x400a, 0xbf99, 0x1c2b, 0x2b70, 0x437c, 0x408d, 0x4367, 0xbf68, 0x3c30, 0x4382, 0x4227, 0x4266, 0x0e7d, 0xbae3, 0x1ab4, 0x43aa, 0x45f4, 0x4248, 0x15a1, 0x4152, 0xb2e6, 0x1a40, 0xa73a, 0x424e, 0x0b31, 0x1b75, 0xae32, 0xba41, 0x1b3a, 0x45d9, 0x1b7a, 0xbfd7, 0x4326, 0x46aa, 0x40b1, 0xb233, 0x42c9, 0x1a6b, 0xa894, 0x43ca, 0x433d, 0xbf45, 0x2f43, 0x421c, 0x1234, 0xb0be, 0xba6d, 0x1cc2, 0x43d3, 0xb248, 0x1f29, 0xb2e3, 0x4365, 0xbace, 0x31e2, 0xbf63, 0xa256, 0x1be2, 0x4420, 0x4339, 0xba0b, 0x1d57, 0x4694, 0xb28d, 0xbfad, 0x407f, 0xa64a, 0x4308, 0xbfd0, 0x443f, 0x05d3, 0xbfa3, 0xb27f, 0x3293, 0x40df, 0x41ff, 0x42e4, 0x4213, 0xb04d, 0x3ee4, 0x41d8, 0x1e59, 0xa2f5, 0x049c, 0xb2e5, 0x406f, 0xab82, 0x428f, 0xba3f, 0x41db, 0x415f, 0xbfe0, 0x406c, 0x46c8, 0xb29e, 0x1834, 0x067a, 0x4142, 0x433c, 0xb2d4, 0x045c, 0xbfdd, 0x43cb, 0x4105, 0x307f, 0x42a5, 0xaa0d, 0x1152, 0x4082, 0x338b, 0x1c40, 0x44a3, 0xbf67, 0xb28c, 0x1cfd, 0x1a7c, 0xb289, 0xbaca, 0x4056, 0x0316, 0x459d, 0x188c, 0x4341, 0x424e, 0xaaac, 0x0908, 0x41b4, 0xb233, 0x4150, 0xb2d1, 0x2241, 0x179d, 0xbfc7, 0x4249, 0xa363, 0x4179, 0x4125, 0x40d2, 0x429e, 0x41a1, 0xb23f, 0x386c, 0x0051, 0xba75, 0xba3d, 0xbfce, 0xb000, 0xbae5, 0x4402, 0x4366, 0x4374, 0x4546, 0x4185, 0x3555, 0xbf7b, 0x40ed, 0x39b8, 0x43f7, 0xb05d, 0x40cd, 0x4176, 0xbfd0, 0x2425, 0x1f85, 0x1b56, 0x4265, 0x403c, 0x4043, 0x4308, 0x4164, 0xb20f, 0xb2f7, 0x4336, 0x26a7, 0x026a, 0x45da, 0x236e, 0x4342, 0xa109, 0xa913, 0xbfd7, 0x2392, 0xba4b, 0xb270, 0xba5a, 0xb02f, 0xa1c0, 0xba4a, 0x41c4, 0x411e, 0x20bf, 0x4331, 0x1519, 0x1987, 0x1931, 0x44c5, 0x1546, 0xbac4, 0xbf48, 0xb2eb, 0x403f, 0x237f, 0x435e, 0xb229, 0x1cac, 0xbae2, 0xbacf, 0x42c9, 0x42d2, 0x0630, 0x22af, 0x407b, 0x43d6, 0x1a64, 0xba0e, 0x3269, 0x364a, 0xba0d, 0xae94, 0x4310, 0x357b, 0x41f1, 0xbfc9, 0xb2d1, 0x434d, 0x4223, 0x41b8, 0xb2b9, 0xbf80, 0x41af, 0x401a, 0xbf9e, 0x062f, 0x39e3, 0x4007, 0x41ff, 0x419a, 0x4379, 0x39f6, 0x43be, 0x2e46, 0xbadb, 0x1dc4, 0xa8b1, 0x1e25, 0x431d, 0x42af, 0x41f8, 0x232f, 0xba70, 0x4018, 0x4111, 0x4160, 0x40a1, 0xbfe8, 0x4001, 0x42a3, 0xab81, 0x420a, 0x41c6, 0x4366, 0x40f4, 0x3117, 0x4065, 0xba39, 0xb2f4, 0x1a3c, 0xb2e3, 0x435e, 0xb246, 0x423d, 0x43de, 0xbfcf, 0x42f2, 0x424c, 0x328b, 0xba0e, 0x1f34, 0x462b, 0x456b, 0xbadb, 0xb277, 0xbfb4, 0x40d1, 0xbafa, 0x4248, 0x127c, 0x324a, 0x1b77, 0x40c6, 0xba06, 0x42f0, 0x4043, 0x0313, 0x4542, 0x4084, 0x368e, 0xbf24, 0x4294, 0x1c3c, 0x408d, 0xb2b2, 0x314f, 0x1c72, 0x4267, 0x430a, 0xa020, 0xb0fa, 0x1aa7, 0xb229, 0x41af, 0x404a, 0x12c8, 0x4302, 0xb2b8, 0x406d, 0xbac6, 0x1a94, 0x4306, 0xb284, 0x41a1, 0xbf77, 0xb206, 0x41af, 0x3aea, 0x438f, 0xadb4, 0x1cc3, 0x11cd, 0xbac1, 0x1c4a, 0xa4b3, 0x415b, 0x3cba, 0x43e8, 0x41c0, 0xb29a, 0xb2a1, 0xa433, 0xbf73, 0x4553, 0x4198, 0x4243, 0x21e5, 0xb2a4, 0xbf90, 0x4319, 0x40e5, 0x433b, 0x423d, 0x42a7, 0x4149, 0x4346, 0x1abf, 0xba72, 0xb086, 0x40bf, 0x2f64, 0x43e8, 0x064e, 0xb2bd, 0x4617, 0xbfb5, 0xb2cf, 0x3673, 0x4608, 0xba4b, 0x1f3b, 0x1bcf, 0xbfb0, 0x42f8, 0x42d4, 0x4389, 0x40f2, 0x0335, 0x4337, 0x425b, 0xb26c, 0x422e, 0xb241, 0x42a4, 0x42c0, 0xbf01, 0x362f, 0x38e0, 0x42ee, 0x1944, 0x4312, 0xb2db, 0x12e0, 0x46f0, 0xba2e, 0xb0c2, 0x42c5, 0x1838, 0xbff0, 0x1d87, 0x462f, 0x428f, 0x411a, 0x1dc6, 0xa2d1, 0x45ea, 0x4261, 0x17a2, 0x4012, 0x400d, 0x1ae6, 0x4284, 0x1abf, 0xbf36, 0x40f0, 0x4389, 0x0c78, 0x1d1d, 0xb203, 0x407f, 0x14b5, 0xb2b3, 0x0fce, 0xba7c, 0xb2c9, 0xb25e, 0x2199, 0x419a, 0x4188, 0x430b, 0xb0ef, 0xba69, 0x4171, 0xb212, 0x41f1, 0xba4c, 0xbf8c, 0x45c4, 0xb211, 0x4266, 0x436a, 0x43de, 0xb0b4, 0x41e7, 0xb0d5, 0x424a, 0xba7f, 0xb212, 0x40c1, 0x4323, 0x4241, 0x42f0, 0x42d5, 0x4269, 0x433e, 0x1b80, 0x3715, 0xaafc, 0xba72, 0x4611, 0x1d89, 0xbfaa, 0xb0f9, 0x3a65, 0xb220, 0xb256, 0x4498, 0x40b6, 0x43af, 0x4334, 0x3daf, 0x43ee, 0xb2b8, 0x40fb, 0x3140, 0x4092, 0x412a, 0x4242, 0xaa96, 0x43b0, 0xb0b3, 0x1ccc, 0xbfe8, 0x4139, 0x4377, 0x41ad, 0x187d, 0xbfa4, 0xba15, 0x4166, 0x41a4, 0x3fb1, 0x4005, 0xb2c7, 0x413d, 0x3917, 0xbfe0, 0x41a5, 0xb211, 0x433d, 0x05fd, 0x4409, 0x4475, 0x41cc, 0x1e64, 0xbf89, 0xb28f, 0x1a04, 0xbf90, 0x42b3, 0xb2cc, 0x41a2, 0xa53c, 0xbfc0, 0x19b0, 0x4337, 0xbf48, 0xb282, 0x1f0e, 0x4370, 0x4358, 0xb21a, 0xbad7, 0x4043, 0x431f, 0x4551, 0x4685, 0x19cf, 0x4053, 0x408a, 0xb020, 0x43fd, 0x2dc1, 0x4603, 0xbf0f, 0x0b17, 0x415b, 0xac11, 0x407c, 0x4362, 0xba64, 0x4216, 0x1384, 0x09e0, 0xbad7, 0xba17, 0x426d, 0x438e, 0x33d3, 0x406d, 0x3b44, 0xb210, 0x2ec5, 0x40ab, 0x42e8, 0x195e, 0x1577, 0x425c, 0x4159, 0x42d0, 0x14ae, 0x1cb1, 0x4081, 0x1a6a, 0xbf57, 0xb2f2, 0x4301, 0x2184, 0x41b0, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x67423155, 0xe3599f1a, 0xf8825cc1, 0xb4b9fc16, 0xcf447746, 0x4ebf74b8, 0x62e4bf2f, 0x5a51ed64, 0xce8f8c9f, 0x5b8b5b0d, 0xed67892d, 0xb797f36a, 0xb91b1a79, 0x89af40a1, 0x00000000, 0x100001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x00000002, 0xfffffffe, 0x43e44438, 0xbc1bbbc8, 0x00000000, 0x00000000, 0x0000021f, 0xff1bffff, 0x5b8b5b0d, 0xfffffff5, 0x5738068c, 0x89af3e59, 0x21f22254, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x351e, 0xb20f, 0x43d6, 0xba50, 0x40d6, 0x40c0, 0xb0f4, 0x41e8, 0xb0e2, 0x4618, 0xb07e, 0x4103, 0xba3c, 0xbafb, 0x0552, 0x41d7, 0xbfab, 0x41e3, 0x40ae, 0xa6e2, 0x2bec, 0xb23a, 0x007d, 0x4264, 0xb07d, 0x432c, 0xbfe8, 0x4279, 0x44d1, 0x225c, 0x21f0, 0x0f23, 0x426d, 0x0b9f, 0x429c, 0x4275, 0xba66, 0x0a83, 0x4227, 0x31d8, 0x1ca1, 0xbfc5, 0x1fc2, 0xbadf, 0x4359, 0x4171, 0x4122, 0x0782, 0xbf51, 0xb06b, 0x4121, 0x408c, 0x3364, 0xb00d, 0xba44, 0x1aaa, 0x43b5, 0x4640, 0xbaf9, 0xaf1b, 0xb2eb, 0xba59, 0x423a, 0x0a7a, 0xb0fa, 0xb2cf, 0x45c8, 0x42e2, 0x2a0c, 0x1c8f, 0xbac4, 0xbf08, 0xb22e, 0x425b, 0xb08a, 0x43f2, 0xbfb7, 0x42fa, 0x4212, 0x1a85, 0x4292, 0xbacc, 0x34d2, 0xb260, 0x1fb1, 0xa08c, 0xbae8, 0x31bf, 0x40fb, 0xa24b, 0xb270, 0x4042, 0x43a4, 0x44b4, 0xb0a3, 0x26c1, 0xbf34, 0x43dc, 0x151d, 0x1be3, 0x40ec, 0xb224, 0xb280, 0x29f4, 0x1afe, 0x4248, 0xbf1a, 0x4282, 0x3389, 0xb25a, 0x1c1f, 0xbf48, 0x3fe4, 0xb25f, 0xa162, 0x2921, 0xbafa, 0x401a, 0x410b, 0xbf0b, 0x42e9, 0xb28a, 0xb270, 0x1a56, 0x2b14, 0x1d39, 0x431f, 0x1f4e, 0xb28e, 0x4177, 0x1a5f, 0xa81b, 0xa73a, 0x4392, 0x416c, 0x420b, 0x4119, 0x40ff, 0xbf65, 0x4039, 0x41cb, 0xb098, 0x41f1, 0x4126, 0xba70, 0xa037, 0x0686, 0x31f0, 0xba02, 0x2c1b, 0xa41f, 0xbf9f, 0x4037, 0x417c, 0x4371, 0x1c48, 0x39f5, 0xb0a2, 0x4271, 0xb201, 0x3107, 0xbf1b, 0x42a8, 0x42c0, 0x465b, 0xb20c, 0x2b6d, 0xbf6c, 0xb2d6, 0xb274, 0x1344, 0x4605, 0xb2a4, 0x359b, 0xba1d, 0xb2fb, 0xbf1b, 0x1dd4, 0x1d7e, 0x4679, 0x4591, 0xba75, 0xbf60, 0x42ce, 0xba57, 0x160d, 0x415e, 0x085d, 0x18d1, 0x1f0b, 0xbfd6, 0x429a, 0x411f, 0x4079, 0x4682, 0xbfbc, 0x44a4, 0x416f, 0x4029, 0x1975, 0x294b, 0xbad1, 0x43f0, 0x2017, 0x4083, 0x416e, 0x41f7, 0xbfd0, 0x45e0, 0x3c14, 0x4647, 0x44f1, 0x4681, 0xb034, 0x4433, 0x4192, 0x4281, 0xb08e, 0xba31, 0xbfb7, 0x427f, 0x4630, 0xbad4, 0x4379, 0x40c7, 0x4318, 0x3267, 0xb2e5, 0x4280, 0x42ee, 0x059f, 0x4054, 0x4585, 0x456d, 0xa855, 0x42f3, 0x0953, 0xba44, 0xb040, 0xb2af, 0x426d, 0x4635, 0x41a1, 0x405a, 0x1d67, 0x1941, 0x43fe, 0x425a, 0xbfba, 0x32e7, 0x1621, 0x41ef, 0x3fae, 0x1be4, 0x036f, 0x40d0, 0x430c, 0xb02e, 0xbace, 0x4060, 0x4299, 0xba14, 0x4557, 0xb22d, 0xbf4f, 0x4188, 0xba3a, 0xb2a1, 0x419f, 0x408e, 0x0dbf, 0xb2de, 0xaa6f, 0x401e, 0x4315, 0xb2f2, 0xbad9, 0xb0c6, 0x0710, 0xb025, 0x431e, 0x42b7, 0x1f99, 0xbf5d, 0x4273, 0x45f4, 0xb2ba, 0x410a, 0x19da, 0xb21e, 0xbafe, 0x32a4, 0xb24e, 0xbafc, 0xa511, 0x4175, 0x4209, 0x15ec, 0x4203, 0x4016, 0x4451, 0xbafd, 0x25f3, 0x409d, 0x1c68, 0xbfd7, 0x43b2, 0x41c1, 0x432d, 0x428c, 0xba10, 0x403a, 0xa089, 0x245f, 0xbf59, 0xb2a4, 0x151d, 0x38b5, 0xa654, 0x44f9, 0x44a3, 0xbfca, 0xb216, 0x1314, 0xba41, 0x289e, 0x0797, 0x40df, 0x409a, 0x430f, 0x40a5, 0x1e10, 0x40d4, 0x41a7, 0x11f6, 0x4384, 0xbf90, 0x42f6, 0x42c5, 0x403c, 0x43a9, 0xa104, 0x1d69, 0x417b, 0xa553, 0xbfe1, 0xb2e2, 0x24d6, 0x4095, 0x1fc7, 0xb243, 0x19ca, 0x419d, 0x40ca, 0x4039, 0x4145, 0xbfa0, 0xbfbb, 0xb233, 0xbf00, 0x40cf, 0x0ac0, 0xa120, 0x1f27, 0x094c, 0x4121, 0xbad4, 0x18a6, 0xbf05, 0x4333, 0xaca2, 0x19b2, 0x0f87, 0xbadf, 0x1653, 0xb251, 0x0c0e, 0xb027, 0x423d, 0x44f3, 0x43cf, 0xb06d, 0xaa68, 0x43d7, 0xaf3d, 0xaac6, 0x30bc, 0xae02, 0x0af9, 0x0a91, 0x40ba, 0x42eb, 0xb271, 0x42a8, 0xbfb8, 0x46d5, 0xa671, 0xb0ea, 0x43d2, 0x1a79, 0x35a2, 0xba41, 0x1a5a, 0x02ce, 0x4339, 0x4053, 0xbfd9, 0xa3d6, 0x2e6e, 0x416f, 0x43f7, 0xbafd, 0x427c, 0xbf76, 0xa945, 0x20fd, 0xba28, 0xb0b4, 0x4062, 0x21e7, 0xb09c, 0x4078, 0xa702, 0x41ce, 0x417a, 0xb219, 0x4196, 0xbf3d, 0xbaf7, 0x192c, 0xa208, 0x425e, 0xb2c4, 0x400a, 0x40f1, 0x4304, 0xb0e2, 0x4031, 0xb211, 0x42c3, 0x0eca, 0x46f1, 0x4072, 0x1c40, 0x40f6, 0x1b35, 0xb2f8, 0x43e3, 0x28f6, 0x43a3, 0x419a, 0x4337, 0x4311, 0x40ff, 0xbfb9, 0x312e, 0x4189, 0xa2f4, 0x45e9, 0x42f6, 0x1db4, 0x0c19, 0x44f1, 0xba6c, 0x425f, 0x4108, 0x41ba, 0x0903, 0xba53, 0x16c7, 0x4112, 0x044b, 0xb02f, 0xb21d, 0x389c, 0x420b, 0x424e, 0x309a, 0xbf87, 0x4161, 0x1fa5, 0x40cf, 0x4060, 0xbfdd, 0x4183, 0x1107, 0x3376, 0x433b, 0xa8eb, 0x352d, 0x1918, 0x0664, 0x4225, 0x401b, 0x42e0, 0xb208, 0x46a8, 0xbf38, 0xb277, 0x0fd4, 0x434e, 0x16bf, 0x43ea, 0xb2f3, 0x4124, 0x198f, 0xba5b, 0x43be, 0x1e44, 0xb2ae, 0xa745, 0xbfde, 0x405d, 0x4151, 0x41ce, 0x1c07, 0x41a7, 0x4166, 0x40fc, 0x38fe, 0x41d9, 0x4084, 0x4128, 0x1817, 0x433a, 0xbf18, 0xb269, 0x420f, 0x442e, 0xb020, 0x42e4, 0x182e, 0x2237, 0xb067, 0x4225, 0x41b4, 0x40cc, 0xbf88, 0x45d2, 0xba2b, 0x2749, 0xadb9, 0x1b34, 0x40e8, 0x0ff1, 0xb20a, 0xba70, 0x3396, 0xb261, 0x4296, 0x4387, 0x4002, 0x436b, 0x435f, 0x434c, 0xbaf7, 0x4077, 0xbfe1, 0x1f0d, 0xba30, 0xba16, 0x4085, 0xb2ad, 0x4029, 0xba54, 0x1c2f, 0xb27b, 0x0dea, 0x4233, 0xb202, 0x0952, 0xba7e, 0x31f6, 0x43fc, 0x4117, 0x45c9, 0xae39, 0xbf3f, 0x4226, 0x43b7, 0x4221, 0x44f2, 0x4157, 0x1efc, 0xbf32, 0x40ab, 0xbfc0, 0x2669, 0x4388, 0xb0a9, 0x406c, 0x28ca, 0x15e3, 0x4337, 0xba26, 0xad3c, 0x106b, 0x340e, 0xbff0, 0xba04, 0x412d, 0x415d, 0x410e, 0xbfbd, 0x427f, 0xb232, 0xac33, 0x4042, 0x40cc, 0xa690, 0x42ce, 0x2cf1, 0xbaf3, 0x40ae, 0xba6b, 0xba26, 0x0b5f, 0xb2e8, 0x4105, 0xbaf2, 0x4348, 0x4240, 0x1f5b, 0x408f, 0xbf38, 0xb282, 0x1c4d, 0x45ab, 0x43c5, 0xb253, 0xb26d, 0x1bfe, 0xbfb5, 0xb018, 0x366a, 0x4065, 0x122e, 0x408b, 0x313d, 0xba7e, 0x4002, 0xbacc, 0x4040, 0x40c3, 0x42dd, 0xb276, 0x0d1e, 0x43ab, 0xba2d, 0x190e, 0x4082, 0xb29e, 0xba38, 0x2855, 0xb074, 0x1ca5, 0x4337, 0x1d60, 0x1cfd, 0xbad9, 0x046c, 0xbfc1, 0xbfd0, 0x42d1, 0xb211, 0x1c31, 0x4694, 0xafe8, 0x43c6, 0x4303, 0x468d, 0xb214, 0x00fa, 0xb03c, 0x4280, 0x43c6, 0x4637, 0xbfde, 0x07a2, 0x4044, 0xba6b, 0xb096, 0xa983, 0x4592, 0x413a, 0x0518, 0x46e2, 0xa242, 0x25c1, 0x4103, 0xba62, 0xb001, 0x455f, 0xbf47, 0x11f2, 0xacab, 0x3992, 0xbad6, 0x439e, 0x4388, 0xb2b8, 0xba17, 0x432a, 0xb221, 0xba03, 0x1730, 0x43cf, 0x4265, 0xba50, 0x1b03, 0x428f, 0xbf2e, 0xb20c, 0x458c, 0x4390, 0x1afe, 0x42b9, 0x1d65, 0x4463, 0x42b5, 0x0dff, 0x4095, 0x42f6, 0x3496, 0x4441, 0x0c8a, 0xba61, 0x4325, 0x20f4, 0xb09c, 0x1c27, 0x420f, 0x40e0, 0xbf70, 0x21c7, 0x4240, 0x459b, 0x40a0, 0xb2ae, 0xbfd2, 0xac93, 0xb0b0, 0x31e9, 0x4131, 0x185d, 0x417c, 0xba60, 0xb24b, 0x2874, 0x40ef, 0xb206, 0xb2e9, 0x44fc, 0xb074, 0xb03b, 0x43a8, 0x426a, 0xba38, 0x43ce, 0x443f, 0xbf4b, 0x4141, 0x1f3c, 0xb2b6, 0x430b, 0xbfcf, 0x4292, 0xba52, 0x4172, 0xa9f9, 0x3050, 0x44e0, 0x43b0, 0x43e8, 0x40b3, 0x4257, 0xba6f, 0x4006, 0x4036, 0x4320, 0x46f5, 0x41f6, 0xa8e5, 0xba6a, 0x429d, 0x0a20, 0x19d7, 0x467f, 0xb20c, 0x400d, 0x4007, 0xbfaa, 0x4367, 0x21b7, 0x4142, 0x428b, 0x4369, 0x45a0, 0x2abb, 0x1af5, 0x4216, 0x1aee, 0xb02b, 0x121e, 0xb241, 0xbaee, 0x2d8b, 0x40d2, 0xbf69, 0x4117, 0x4366, 0x2bbd, 0xba10, 0xb09f, 0x4172, 0x426c, 0x23fc, 0x338e, 0xbf43, 0x4015, 0x42ee, 0xa3f1, 0x0369, 0x42e7, 0x062e, 0x1667, 0xb23b, 0x4433, 0x438e, 0xb0c9, 0x418e, 0xb0d8, 0xba4c, 0xbf13, 0x42e0, 0x1c20, 0x19f8, 0xb232, 0x4083, 0x410f, 0xad20, 0x412c, 0x3662, 0x2a58, 0x4089, 0xb275, 0x117c, 0x3b0c, 0xbf5d, 0x41c1, 0x4011, 0x4063, 0x2178, 0xab2f, 0x42a1, 0x439e, 0x2b3e, 0x45e2, 0xb084, 0xadfb, 0xa056, 0xbaf7, 0x3e15, 0x4464, 0xb046, 0xb24d, 0xb09d, 0x4202, 0x19f1, 0x4211, 0xbf5e, 0x4035, 0x400f, 0x1e45, 0x13ab, 0x40aa, 0x1ad0, 0x429f, 0xb0a4, 0xb05b, 0x0f39, 0xaa55, 0x2864, 0x437f, 0x43ca, 0xb2d1, 0x3846, 0x46e0, 0xb0d4, 0x4411, 0x1ce1, 0xb06d, 0x1246, 0x187d, 0x08ca, 0x46da, 0xbf95, 0xb273, 0xba11, 0x4211, 0x40d3, 0xb040, 0x05fd, 0x1e61, 0xa78d, 0x46f3, 0xb0e7, 0x4168, 0xba4c, 0xba0b, 0x000f, 0x41a0, 0x340c, 0x29e0, 0x2c2e, 0x42ab, 0xb01b, 0x421e, 0x2607, 0x1b58, 0xba0f, 0x442a, 0x4219, 0xb2ac, 0xbfd4, 0x414a, 0x0524, 0x3d35, 0xb0a0, 0x4343, 0x40e8, 0x40a9, 0x18f6, 0x14df, 0x41d4, 0x4542, 0x434a, 0x4306, 0x36f2, 0x1d38, 0x4595, 0x41a2, 0x42fd, 0xbf24, 0x4596, 0xa565, 0x4650, 0xb264, 0x4139, 0xb039, 0x4261, 0x41ae, 0xbaf5, 0x4112, 0x43c3, 0x0409, 0x4168, 0x40e4, 0xbfa8, 0xba31, 0xa175, 0x41ac, 0x2089, 0x423e, 0x40fe, 0x411e, 0x2535, 0x42e2, 0xa13b, 0x44c0, 0xb20f, 0xbf6e, 0x43be, 0xb2a5, 0x40bb, 0x4068, 0x19bb, 0x459d, 0x2ab5, 0x192c, 0x427e, 0x429e, 0x4286, 0x43ee, 0x415e, 0xb0cd, 0x46c3, 0x45a3, 0x4345, 0x4170, 0x064c, 0xb2bd, 0xbf19, 0xb091, 0x02aa, 0x3a96, 0xb2a8, 0x41a0, 0xb0c3, 0x4327, 0x18c6, 0x014d, 0x402a, 0xb299, 0xb207, 0x0b66, 0x3ced, 0xb2bb, 0x4188, 0x3513, 0xbaff, 0x4356, 0x437c, 0x42f2, 0xba74, 0xb239, 0xba67, 0x42b4, 0xbf3a, 0xb2d8, 0x1c3f, 0x2787, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x3be9d470, 0x621bd961, 0x561548a7, 0x32c98a3d, 0x487719ff, 0x1c15d826, 0x34d9255e, 0xaae8da54, 0xf65e2145, 0x5153589f, 0x032f5367, 0xf13554cd, 0xda5c80c1, 0x8ef09f26, 0x00000000, 0x900001f0 }, + FinalRegs = new uint[] { 0xffffffff, 0xffff8018, 0x00031000, 0x00001880, 0x00000000, 0x00031013, 0x00000000, 0x00000087, 0x00012ad4, 0x00000000, 0xf135552c, 0x00012ad4, 0x0000956a, 0xfffffd74, 0x00000000, 0x600001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xb2f7, 0x4028, 0x42dd, 0x4043, 0x422e, 0xbf5c, 0xa6cf, 0xb289, 0x06ce, 0xb229, 0x4065, 0x430d, 0x1986, 0x466b, 0x2c11, 0x0fcb, 0xba22, 0x412a, 0x40f3, 0xbf9d, 0x0cd5, 0x415a, 0x310f, 0xb07e, 0xb2da, 0xbf12, 0x3f1e, 0x3e0e, 0xb22c, 0x4414, 0xb0a3, 0x1a93, 0xbf15, 0x33ee, 0x1c22, 0x4184, 0x416b, 0x43cd, 0x4625, 0xba70, 0x438f, 0x4175, 0x4031, 0xbf53, 0x0d6a, 0x4234, 0x2c94, 0x4288, 0x4196, 0xb0ef, 0x3a5b, 0xb02b, 0xa285, 0x438c, 0x1914, 0xbfb0, 0xbf82, 0x4636, 0x4550, 0x23ad, 0x466e, 0xb04e, 0x4303, 0x4301, 0x1efc, 0xb267, 0xba31, 0x4247, 0xb01d, 0xbfa3, 0x4026, 0x11c3, 0x41fd, 0x40cc, 0x4150, 0x435d, 0xbf3f, 0xa514, 0xbfc0, 0x4293, 0x1e27, 0x42af, 0x405f, 0x3ad4, 0x40a2, 0x0423, 0x406a, 0x4236, 0xb2a5, 0x1ec3, 0x1f33, 0x29aa, 0x2865, 0x0b77, 0x2536, 0x1e74, 0x40a6, 0x43cb, 0x42e1, 0x402c, 0x4414, 0x25f3, 0x0c28, 0xbf7c, 0x0042, 0x4154, 0xbad1, 0x2d5b, 0x45c1, 0x4399, 0x3a0d, 0x41ea, 0x4398, 0xbfa0, 0xba3a, 0x41b8, 0x1f6c, 0xbf75, 0x1579, 0xb29c, 0x1d42, 0x18ce, 0x16f3, 0x44a3, 0x46b1, 0xbadc, 0x1943, 0xb2d6, 0x24b9, 0x4061, 0x2103, 0x1e64, 0x41c5, 0x41f9, 0xbf59, 0xba55, 0x40c0, 0x414f, 0xa5c1, 0x4189, 0x1f1d, 0x1afd, 0x41b6, 0xba58, 0xba3b, 0xb0b7, 0x2acf, 0x4370, 0x41e3, 0xb2d2, 0xb265, 0xb28b, 0x4133, 0xa98a, 0x403b, 0x3a58, 0x4271, 0x447e, 0x41fa, 0x40c7, 0xba58, 0x4319, 0x41e5, 0xb030, 0xbf49, 0x4074, 0xa3ca, 0xbfb0, 0x1928, 0x41a9, 0xba74, 0x4070, 0xb00f, 0x4225, 0xa687, 0x00f0, 0x406c, 0x4148, 0xbf83, 0x19c0, 0x4197, 0xb2da, 0x424c, 0xb2bc, 0xba23, 0x2e66, 0x4089, 0x28dd, 0xb2e4, 0x05cd, 0x4184, 0x05c6, 0x404d, 0xba2e, 0x0f8a, 0x4175, 0x412a, 0x44cc, 0xb0ef, 0xb042, 0xafe8, 0x420a, 0x4253, 0xbf83, 0x1b27, 0x4062, 0x424b, 0xae9c, 0xba7e, 0xb265, 0xb28a, 0x148f, 0xb2d3, 0x2e6d, 0x2efc, 0xba46, 0x40f7, 0x41fd, 0x411b, 0x4566, 0xb2b1, 0x4209, 0xb218, 0x0aeb, 0xb20d, 0x40c9, 0x226c, 0x429a, 0x22d3, 0xbfb0, 0xb286, 0x4275, 0x42a6, 0xbf96, 0x4235, 0x4222, 0x42e3, 0xb237, 0xb220, 0x4624, 0x1d89, 0xb031, 0x389e, 0x4196, 0xb0b4, 0x426e, 0x1ae5, 0x140f, 0x4398, 0x4667, 0x40e5, 0x0fe5, 0x14fe, 0x18e6, 0x44b2, 0x4285, 0x4044, 0x4233, 0x2a71, 0xb0c8, 0xb06f, 0xa688, 0xbfcb, 0x435e, 0x1af2, 0x1de1, 0x0607, 0xabf8, 0xbfc0, 0x42ef, 0x4220, 0xbf70, 0xb271, 0x3965, 0x4132, 0xab6b, 0x1665, 0xb2c5, 0x1fa6, 0x32f2, 0xb0aa, 0x41ca, 0x2064, 0x0cbe, 0x41a5, 0xbf60, 0x1fa4, 0xb02d, 0x4046, 0xa9a9, 0x41b7, 0xbf43, 0x4167, 0x0b2e, 0xa33a, 0x4202, 0xa8c2, 0x42c0, 0x425b, 0x3f6c, 0xbada, 0x1b7a, 0x40ae, 0x18c0, 0x4132, 0x407a, 0x40cb, 0xba02, 0xad3f, 0x4275, 0x4271, 0x1f2e, 0x1dec, 0xbfde, 0x4544, 0x0f82, 0x214e, 0xb284, 0x4089, 0x4334, 0x4251, 0x2dd9, 0x08e2, 0x431d, 0x4036, 0x09d7, 0x432b, 0x434c, 0xb203, 0xbf70, 0x3ce7, 0x3620, 0xbfa0, 0x155a, 0xbfb4, 0x198a, 0xb28e, 0xbf71, 0x047b, 0x1e2d, 0x42c4, 0xaa34, 0xba55, 0x4073, 0x2661, 0x4106, 0x3527, 0x1a73, 0x333f, 0x430d, 0xb26b, 0x3a6a, 0xb2f2, 0xbade, 0x42c5, 0x1d3a, 0xbf2f, 0xa17c, 0x4156, 0x4267, 0xb207, 0x4581, 0xb0f1, 0x4385, 0x1869, 0x4045, 0xb258, 0x0b70, 0x1f7d, 0xad43, 0x1f8d, 0x4235, 0xb24e, 0xb04c, 0xb0e0, 0x0d27, 0x3468, 0xbf9d, 0x189e, 0x1ccd, 0x43bc, 0x4060, 0xb2d5, 0x45bd, 0x1b74, 0x44dc, 0xbaf8, 0xab61, 0xb2d2, 0xb21c, 0x1e49, 0xafa9, 0x432a, 0x4374, 0x1b4d, 0xb24a, 0x4092, 0xb29b, 0xbf62, 0xb2d2, 0x36fc, 0x4131, 0x1d9b, 0x45f1, 0x0d6c, 0x398b, 0xb2f7, 0x424a, 0x4044, 0x03c7, 0xba2e, 0x1adb, 0x43eb, 0x40e2, 0x06a9, 0x07d8, 0x419c, 0xb291, 0xba06, 0xbf3e, 0xb28b, 0x268a, 0x41b9, 0x1119, 0x432f, 0xb29f, 0xab98, 0x4691, 0xb28e, 0x1c91, 0x128e, 0x4432, 0x435b, 0x1ae9, 0x418b, 0x05fb, 0x426f, 0xbfb2, 0x4306, 0x3ff6, 0x42f0, 0x40a1, 0x0abe, 0xba16, 0x0068, 0x4095, 0x09b3, 0x36a2, 0x4362, 0x4226, 0x407a, 0x2f81, 0xbfe2, 0x3c7d, 0x425b, 0x43f0, 0x218b, 0x3826, 0x00fc, 0x19cf, 0xb2a4, 0xba31, 0x4390, 0x43f4, 0x1d18, 0xb28e, 0xbaeb, 0x41a1, 0xbf80, 0x41e0, 0x1c8a, 0xba1e, 0x4212, 0x435d, 0x2bbc, 0xbf90, 0x432c, 0xbfae, 0x4211, 0xba70, 0x43f9, 0x42d5, 0x1ffa, 0x413d, 0xba40, 0xbfb2, 0x232a, 0x429c, 0x4490, 0x02b5, 0x4059, 0x404c, 0xba41, 0xa995, 0x415b, 0x40f7, 0x4041, 0x43e6, 0xba3b, 0xb2d7, 0x425d, 0x197f, 0x411d, 0xbf68, 0xba27, 0x1922, 0x1f2b, 0xbafe, 0x4454, 0x4310, 0x434c, 0xad88, 0x414d, 0xa4a5, 0xba15, 0xbfbc, 0x435d, 0x41ff, 0x41bc, 0x43fd, 0x1b7d, 0x3bc0, 0xb237, 0x1cf3, 0xba44, 0x4019, 0x439a, 0x4151, 0x4265, 0x406d, 0x4247, 0x2b08, 0x410e, 0x2a27, 0x42c5, 0xb2d0, 0xbf8c, 0x40d2, 0x42b5, 0xb075, 0x4324, 0x40d6, 0x431f, 0x40c8, 0x11fe, 0xa8ea, 0xbf5e, 0x4055, 0x1462, 0x091d, 0x194f, 0x0447, 0x045d, 0x4655, 0x37cb, 0xbf78, 0xba5e, 0x419f, 0x3835, 0x1c9d, 0x404a, 0x427e, 0x43c9, 0xbf60, 0x4276, 0x3259, 0x439b, 0x08a9, 0xb071, 0x4117, 0x44d3, 0xbad4, 0x42f7, 0xba45, 0x4469, 0x18e6, 0x42cd, 0x434f, 0x3f51, 0x433e, 0x45cb, 0xbf6d, 0xb268, 0x43ff, 0xbf90, 0x42c4, 0x40cc, 0x3fd6, 0x3d3e, 0x0b24, 0x40e1, 0xab12, 0xb2bf, 0xb0fc, 0xb05f, 0x43b7, 0x1dc3, 0xbf60, 0x44d8, 0xbf60, 0x43a2, 0xb2af, 0x4137, 0x41d8, 0x403e, 0x42ee, 0x32c0, 0x42a1, 0xba3b, 0x39e8, 0xbf01, 0x1a35, 0x43f6, 0x1e36, 0x4382, 0x40e2, 0xba04, 0x06a1, 0x1cc8, 0xa943, 0x4171, 0x415e, 0x4006, 0xba30, 0x411b, 0xbfc5, 0xac02, 0xb29c, 0xba4c, 0xb232, 0xba60, 0x4198, 0x42fc, 0x1a5e, 0x4269, 0xad0e, 0x0366, 0xa38a, 0x4045, 0x0a7b, 0x3899, 0x1bcc, 0xba2c, 0xb2b3, 0x45c5, 0x2575, 0x44ca, 0x4185, 0x06b8, 0x4185, 0x4018, 0xbfcd, 0x403f, 0x391d, 0xa99c, 0x17d1, 0xb280, 0xba7d, 0xba66, 0x1e75, 0x3ad1, 0x1304, 0xbfcb, 0x1e01, 0xbad0, 0x43cf, 0x4425, 0xb230, 0x43d8, 0xbae1, 0xbad7, 0x13f0, 0x42a0, 0x438c, 0x28f8, 0xb0a4, 0xb0a6, 0x196a, 0x1cc0, 0xbfc5, 0x0c99, 0x40e4, 0x1935, 0x45b4, 0xb011, 0x40ec, 0x40e9, 0x3f82, 0x42c8, 0x1aa3, 0x43be, 0x4034, 0xbf1d, 0xa56d, 0x44da, 0x19e3, 0xb2b1, 0xb2bb, 0x40e0, 0x40ef, 0x3c5d, 0x43f5, 0xb26e, 0xbfd5, 0x402f, 0x4023, 0xba3e, 0x38d4, 0xb255, 0x406d, 0x4244, 0xb06b, 0x42e1, 0xb045, 0x408a, 0x4382, 0x4102, 0x17cb, 0x4670, 0xbf21, 0xb28e, 0x4282, 0xb27c, 0x1f26, 0xb091, 0x42c9, 0x1e3a, 0xbad0, 0xbfe2, 0x1b74, 0x42a6, 0xba44, 0x42b2, 0x4291, 0xb21e, 0x404d, 0x4328, 0x4343, 0xad78, 0x42b3, 0x4169, 0x3557, 0x1b59, 0x4372, 0x403d, 0x41f5, 0xba79, 0x0e81, 0xa713, 0x2378, 0xba21, 0x4050, 0x40d7, 0xb050, 0xa3af, 0x417d, 0x4001, 0xbf95, 0xa8cc, 0x437c, 0x436e, 0x408e, 0x43aa, 0x3957, 0xb021, 0x4209, 0xb272, 0xba1e, 0x42a2, 0x4351, 0x467a, 0x13cc, 0x2db1, 0x466e, 0x4234, 0x43c0, 0xb0dc, 0x1fcc, 0xbf5e, 0x41e2, 0x17ab, 0x4173, 0x4110, 0x427b, 0x41b2, 0xa5af, 0x411e, 0xbaf2, 0xba49, 0x4197, 0x4448, 0xb068, 0x4283, 0x4354, 0xbfa0, 0x40f2, 0x1f52, 0x41d3, 0x4571, 0x1aab, 0xb2b5, 0xaa74, 0x4372, 0xbf26, 0xb2cd, 0x42e7, 0x18a8, 0x373f, 0xba7c, 0x40a7, 0x40ec, 0x1deb, 0x4380, 0xb072, 0x43b6, 0xab0a, 0xbf54, 0x42d3, 0xa8e9, 0x1d8b, 0x1f92, 0x4046, 0x1ae7, 0xafbf, 0x20de, 0x45cc, 0x189d, 0x42f4, 0x43e8, 0xb093, 0x292d, 0x412b, 0x430a, 0xbae8, 0x0ffa, 0x4080, 0x3294, 0x4665, 0x4284, 0x22c5, 0x25bf, 0xb27b, 0xbf3c, 0x3912, 0x2d61, 0xbadb, 0x44a3, 0xbf42, 0xb2a4, 0x411f, 0x1d60, 0x40f5, 0x21a9, 0x0250, 0x0a3c, 0x31e6, 0xbf77, 0x43ea, 0x1ff8, 0xbac4, 0xbac2, 0xb209, 0xbf1b, 0x43b9, 0x0791, 0x3579, 0x4447, 0xba5f, 0xbf92, 0x3c03, 0x4383, 0x4044, 0xa95a, 0xba71, 0xb229, 0x4303, 0x316a, 0x4389, 0x1ff4, 0x422e, 0xb2b3, 0x40e3, 0x4085, 0x3342, 0x42a2, 0x1d76, 0xb200, 0xae67, 0x45b2, 0xbf1d, 0xbadc, 0x27d9, 0x18e9, 0x41dd, 0x294b, 0xbf41, 0x0d48, 0x4137, 0x4015, 0x43f4, 0x317c, 0x42bf, 0x4391, 0x1774, 0x0d7f, 0x40b4, 0xba0f, 0x4111, 0xba30, 0x30fe, 0xb2b2, 0x438e, 0x4012, 0x460d, 0xbfc0, 0x2de0, 0xb2aa, 0x00da, 0x42bd, 0x4385, 0xbfa8, 0x1f7e, 0xb203, 0xb293, 0x2c9a, 0x1ea5, 0x2d34, 0x46aa, 0xb288, 0xbf7d, 0xba02, 0x2b0a, 0xb0a2, 0x43ae, 0x435c, 0x406e, 0xb09f, 0x1484, 0x416b, 0x46b3, 0x4314, 0x422f, 0xa751, 0x44e8, 0x43ae, 0xa901, 0xba66, 0x41ca, 0xbf0b, 0x43af, 0x1faf, 0x1fbf, 0xac1d, 0xac02, 0x235c, 0xbfc1, 0x4145, 0x1b22, 0x1c5c, 0x404a, 0xa600, 0x43b0, 0xbad1, 0xbf4b, 0x1b24, 0xa8e3, 0xba03, 0xaf8e, 0x295f, 0x1b40, 0x4178, 0xb205, 0x146f, 0x09ca, 0x2e3c, 0x43f7, 0x439b, 0x3295, 0x408a, 0xbf1a, 0x454f, 0x3bc1, 0xba5b, 0x4100, 0x40f2, 0x1af2, 0x1c9b, 0xa85c, 0xaf0f, 0xbfb0, 0xa8dc, 0x4218, 0x414a, 0xb205, 0xb2a2, 0x4160, 0xa22c, 0x00b4, 0x42e3, 0xba5b, 0xbf52, 0xba23, 0x40bd, 0xb01d, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xe4527cc5, 0xffa5922e, 0xca434c0d, 0x2c3146e6, 0x89b4f1a8, 0xd1d30b0d, 0x90e04460, 0x4d312bcd, 0xd0bf479f, 0x23e96a47, 0x987a7688, 0x7251199f, 0x0415194f, 0xea9d51e2, 0x00000000, 0x100001f0 }, + FinalRegs = new uint[] { 0xea9d5adb, 0x00001257, 0x0000187c, 0x105e0000, 0x00005e10, 0x00005a7e, 0x00001784, 0xea9d574a, 0xc6274cdf, 0x00000000, 0xfffffffe, 0x8aff0005, 0x766633dc, 0xea9d570e, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4166, 0xba66, 0x4108, 0x43d1, 0x2c5e, 0xb2fb, 0x4067, 0xafc6, 0x1915, 0x25ef, 0xaadd, 0xb016, 0xbac4, 0xba6e, 0x4415, 0x455e, 0xba35, 0xbf09, 0x4108, 0x0586, 0x42f9, 0x41e2, 0x15df, 0x279d, 0x18cb, 0x3d65, 0x40cd, 0xb0fe, 0x430b, 0xb006, 0x40a5, 0xb21d, 0x409f, 0xa0e2, 0xbf8f, 0x4328, 0x4444, 0xbf70, 0xb2be, 0x40b1, 0xb294, 0x4068, 0x414b, 0x13ae, 0x41e2, 0xb21d, 0x43ce, 0xba3d, 0xb26b, 0x1830, 0xb2b0, 0x440c, 0x29fa, 0x4290, 0xba40, 0x37eb, 0x43db, 0x422c, 0x4337, 0x30cc, 0x41cc, 0x4309, 0xb2f7, 0x401d, 0xbf1a, 0x4322, 0x43b8, 0x41b3, 0x33c5, 0x29e3, 0x0c91, 0x4240, 0x419f, 0xbf31, 0x4059, 0x407f, 0xa4cc, 0x179f, 0x42a2, 0xb28b, 0x413f, 0x40b7, 0xb276, 0x42f8, 0x416c, 0xba03, 0x287a, 0xbfa0, 0x3811, 0x4374, 0x44fd, 0x4147, 0x1d8b, 0x2828, 0x13cb, 0x4166, 0x45e0, 0xb206, 0x184e, 0x414d, 0xbfb9, 0xba1c, 0x40c7, 0x41fb, 0x41f5, 0x433c, 0x400e, 0x42da, 0x43a5, 0x1c04, 0x4202, 0x1c75, 0xb20a, 0x41d0, 0x405a, 0xba78, 0x3bb3, 0xbf19, 0xba6a, 0xb223, 0x35a6, 0x40a4, 0x0707, 0xbf61, 0xb2ac, 0x443e, 0x408c, 0x421c, 0x4128, 0x1975, 0xa7e9, 0x09d5, 0x1ffb, 0xb25d, 0x448a, 0x41e9, 0xaf03, 0x1ea5, 0x40bd, 0xb25f, 0x13f0, 0x4639, 0xba67, 0x404e, 0x1701, 0x423f, 0x3cf4, 0x1f10, 0x431e, 0xbfb6, 0xba7c, 0xb2b0, 0x1f0e, 0xba3e, 0x37c8, 0xba46, 0xba74, 0xba30, 0x41b1, 0x4291, 0x40ad, 0xb0ae, 0x4268, 0xb0a1, 0xba4e, 0x0d33, 0x3511, 0x03b1, 0x4383, 0xbf2e, 0x1a28, 0x43ce, 0x0d7a, 0x40e1, 0x4078, 0x13ce, 0xba1b, 0xb0fe, 0x2b59, 0x402c, 0xbfb6, 0xb288, 0xb25a, 0x41ee, 0x4343, 0x1c53, 0xb21d, 0x41af, 0xb2b4, 0x423d, 0xad88, 0x43cd, 0x4382, 0x40fd, 0x1376, 0x3a63, 0x4162, 0x4091, 0xb2fc, 0x43f8, 0x4247, 0x43b5, 0x4622, 0x41da, 0x1e63, 0x40e3, 0x435f, 0x4630, 0xbfd1, 0x19a2, 0x4237, 0x433c, 0x42eb, 0x3f17, 0xbf6a, 0xbacb, 0x4340, 0xb236, 0xbace, 0x43a4, 0xb20b, 0x1f3a, 0x393d, 0x42a8, 0x22da, 0x1b3d, 0x1884, 0x33e2, 0xa02b, 0xb087, 0x2942, 0xb038, 0xbad6, 0x4055, 0x418a, 0x4373, 0x411b, 0x4382, 0x4041, 0x41cf, 0x42fe, 0xbf79, 0x43bf, 0x1c07, 0xbac0, 0xb263, 0xba01, 0x429b, 0x0774, 0xb011, 0x4135, 0x42b9, 0xbf72, 0x4466, 0xbf60, 0x422b, 0x3aea, 0xbf1f, 0x1986, 0xb2f1, 0xa844, 0x41f7, 0xb029, 0x4319, 0x3b38, 0x4017, 0xbf34, 0x1bd1, 0xbfc0, 0x4014, 0x4304, 0xb2e2, 0x1824, 0xb28b, 0x430b, 0xa850, 0x2fdd, 0x1eb3, 0x46f2, 0x1974, 0x428c, 0x43c9, 0x42f1, 0xbf6a, 0x1d2a, 0x1f8c, 0x41ef, 0x440e, 0x4440, 0x40e9, 0x43a1, 0xaa22, 0x15b9, 0xbf25, 0xb2b8, 0x43f6, 0x40a6, 0x42f1, 0xb28a, 0xb23f, 0x4350, 0x418d, 0x42ea, 0xae84, 0xb062, 0xb297, 0x405c, 0x4132, 0x1943, 0x43da, 0xb012, 0x1d91, 0x3514, 0xbf6a, 0x4202, 0x00f2, 0x4243, 0xafb2, 0x414f, 0xbf11, 0x42aa, 0x40bd, 0x416f, 0x4196, 0xb061, 0x4119, 0x1225, 0x439e, 0x0de1, 0x2cc9, 0x1cdd, 0x3081, 0x431b, 0xb21e, 0x1e40, 0x3105, 0xbf98, 0xb0b5, 0xba65, 0xb2e7, 0x42d3, 0x0bab, 0x1fc2, 0xb0ca, 0x436f, 0xb296, 0x330d, 0x4078, 0xb25d, 0x4285, 0x3335, 0x2608, 0x34e2, 0x411d, 0x38b9, 0x1ab8, 0x023f, 0x10e9, 0x095b, 0x41d9, 0xbf60, 0xbf61, 0x4363, 0x1d56, 0x4658, 0x43f8, 0x41ac, 0x3b35, 0x4203, 0x204d, 0xbfb0, 0x4609, 0x413a, 0x4029, 0xba79, 0xba33, 0x456d, 0xb278, 0xa065, 0x40d8, 0x1c95, 0xb0ee, 0xb20f, 0xbf82, 0x404e, 0x4102, 0x4005, 0xafb3, 0x43ae, 0x1adb, 0x1ef9, 0x04e7, 0xb2b1, 0xbf90, 0x3eae, 0x44d4, 0x10e7, 0xbfaf, 0x42b4, 0x41b9, 0x4661, 0xb0ba, 0xbaf0, 0x4310, 0xba07, 0xadfc, 0x370b, 0x4078, 0xa86c, 0x43bf, 0x46a9, 0xb2c3, 0x438a, 0x0e58, 0x2d29, 0xb2c2, 0x0d85, 0x2025, 0x43be, 0xbf95, 0x43e2, 0x0190, 0xb03b, 0x43af, 0xba48, 0xbf60, 0x280a, 0x45c0, 0xb225, 0xbac3, 0x4171, 0x4286, 0x27a7, 0x4084, 0x1b18, 0x3e85, 0x1d9e, 0x222f, 0x2477, 0x1cd9, 0xba7e, 0xbfd1, 0x0c6e, 0x419b, 0xb284, 0x466c, 0x4076, 0x427f, 0x4657, 0x415f, 0x1d87, 0x0221, 0x346e, 0x4217, 0xbadb, 0x41f4, 0x41e9, 0xbac1, 0x42b2, 0x4177, 0x4118, 0x4128, 0x3828, 0x04a2, 0xbac4, 0xba30, 0xb0f2, 0xbf8a, 0xa678, 0x18bf, 0xb0ad, 0x40dd, 0x416e, 0x43d7, 0x44b9, 0xa0fd, 0x434b, 0xb097, 0x1f28, 0x4320, 0xb00e, 0x4377, 0x1187, 0xba24, 0x4160, 0xb027, 0xb20e, 0x43aa, 0x43cf, 0xbfd4, 0x39e1, 0x1d32, 0x40f2, 0x404a, 0xba4f, 0x00ca, 0xbaee, 0x426d, 0xb2f1, 0x16fd, 0x3012, 0x415e, 0x412c, 0x4221, 0xbf62, 0x1eaf, 0xab08, 0x40b8, 0x0851, 0x3a0d, 0x0010, 0xa39c, 0x0bbb, 0x438e, 0x115e, 0x2965, 0x43c4, 0x448a, 0x447b, 0x4389, 0x42a7, 0xba70, 0x46a5, 0x437f, 0x1a53, 0xb2d0, 0xb2b4, 0x417a, 0xb20f, 0x3ddf, 0x4213, 0xbf03, 0x419b, 0xb229, 0x1cc5, 0x42d9, 0x4307, 0xb0b0, 0x424e, 0xb0c5, 0x2b34, 0x430e, 0x42a2, 0x409c, 0x41eb, 0x4143, 0x4092, 0xbfab, 0x419d, 0x4370, 0x173a, 0xbac9, 0x44ba, 0xba24, 0x4292, 0xbf83, 0x10f4, 0x1352, 0x435f, 0x430a, 0x4113, 0x4556, 0xba04, 0x43b0, 0x43cc, 0x3369, 0xb0c6, 0x43ca, 0x42e2, 0xbf43, 0xb04a, 0xb0cf, 0xb2c0, 0xbff0, 0x436f, 0x40e7, 0xbf72, 0x1cda, 0xba12, 0xb2b6, 0x1294, 0xb083, 0x44f3, 0xbf4a, 0x4379, 0x4206, 0x4168, 0x18aa, 0x43b3, 0xb2d4, 0x442d, 0x4363, 0x4219, 0x3fba, 0xb2f9, 0x4226, 0xbff0, 0xb050, 0x1882, 0x4143, 0x4602, 0x3889, 0x19a3, 0xbf35, 0x4110, 0x429e, 0xb2dd, 0x4087, 0xba4b, 0x4303, 0x415b, 0xb031, 0xb003, 0x4062, 0x1aa9, 0x4066, 0x1ca6, 0xbf80, 0x1970, 0x0840, 0xa6a7, 0xaf8d, 0x40ea, 0x2ad2, 0xb2b7, 0x41aa, 0xbae6, 0x393e, 0x4264, 0x40ae, 0xbaf7, 0xbf6d, 0xb244, 0xba21, 0xba7b, 0x23d2, 0x400a, 0x4059, 0xba4d, 0x1505, 0x1d64, 0x42b9, 0x4197, 0x41f8, 0x4437, 0x41ac, 0x0a56, 0x1d3d, 0x4555, 0xbf8f, 0x4319, 0xbade, 0xba6e, 0x03a1, 0xb2b4, 0x40ac, 0xa7c5, 0xbf14, 0xac04, 0x221e, 0x4246, 0xb2d9, 0x4020, 0xba06, 0xbff0, 0x40d0, 0x2539, 0xba4f, 0x4283, 0x43b5, 0x42f1, 0x43a9, 0xb215, 0xba78, 0x4353, 0xb2ad, 0x0eb2, 0x40b9, 0x05f6, 0x1b03, 0x037a, 0x1922, 0x1d2d, 0x4218, 0xbf42, 0x2674, 0x44fd, 0x41e2, 0xba4d, 0x43f9, 0x10d1, 0x4121, 0x4309, 0xb25a, 0xba68, 0xb2d2, 0x402c, 0x27fb, 0x4143, 0xba48, 0xbfc6, 0x42cd, 0x438f, 0xb23a, 0x253a, 0x19ff, 0x40ed, 0x429a, 0x03f5, 0x412e, 0xba52, 0x43e5, 0xb2bf, 0x1bae, 0x43a5, 0x4389, 0x4253, 0x1bc9, 0xbf8c, 0xb20d, 0xb2be, 0x43ea, 0xa6fd, 0x3b1d, 0x40ad, 0x43d6, 0x43fd, 0x448a, 0x17ff, 0x430b, 0x41e0, 0xba42, 0xbf57, 0xaf35, 0x423c, 0x40b0, 0x43f4, 0xb245, 0x1a09, 0x04e0, 0x398e, 0x4049, 0x4389, 0x43b2, 0xbf00, 0xb0b9, 0x1f41, 0x41a5, 0x4143, 0x403e, 0xa712, 0xab59, 0xbac2, 0x422b, 0x21e4, 0xbfdd, 0xb273, 0x2b57, 0xa6e7, 0xa357, 0xa329, 0x427e, 0xb29d, 0x0b10, 0xbae1, 0xa29b, 0x2e57, 0x41dd, 0x43b3, 0xb2ff, 0xb201, 0xa480, 0x111a, 0x434f, 0x3828, 0x0c1e, 0x40bd, 0x1ddc, 0x4120, 0xbf90, 0x1f65, 0x4387, 0x4307, 0x4235, 0xbf87, 0xbff0, 0x41c6, 0x36e9, 0x4120, 0x40ca, 0x4383, 0xb211, 0x41f5, 0x4309, 0xbfd5, 0x4332, 0xb23a, 0x4227, 0x4358, 0x2229, 0x41a0, 0xb09b, 0x4162, 0x00af, 0x4017, 0xb270, 0xbf60, 0x4289, 0xba44, 0x415f, 0x3745, 0x1d25, 0xb24f, 0x415b, 0x1dde, 0x41d5, 0x421d, 0x4061, 0xb23d, 0xb0e1, 0xb231, 0xbfce, 0x4479, 0x18ae, 0x429b, 0x3aa6, 0xb031, 0xbf08, 0x4173, 0x4025, 0xbf01, 0xbf80, 0x429a, 0xa140, 0x4241, 0x4004, 0xbaf0, 0x1950, 0xbf00, 0x4069, 0xbf90, 0x42f8, 0x4149, 0x4352, 0x40ee, 0x1a2e, 0x1888, 0x2be6, 0xa75e, 0xb040, 0x42ed, 0x4158, 0xbf7a, 0x45aa, 0x0e95, 0x1014, 0x0d0c, 0x22e9, 0x1dc7, 0xbfa0, 0xb092, 0xbfb0, 0x41c7, 0x0377, 0xb2c9, 0x4260, 0x4371, 0x1ba6, 0x4273, 0x426c, 0x1a58, 0x1e6b, 0x0f52, 0x1c50, 0x3528, 0x4157, 0x4296, 0xbf43, 0x345e, 0x40f5, 0x429c, 0x42bc, 0x4312, 0xa05d, 0x1ea4, 0x40e2, 0xbf92, 0x38a7, 0x43b2, 0xbfb0, 0x1d58, 0xba54, 0xb256, 0xb255, 0x442e, 0x4293, 0xba67, 0x1804, 0xb27c, 0x425a, 0x4173, 0xba62, 0x4221, 0x41c7, 0x3475, 0xbfb8, 0x4002, 0xb0b4, 0x412c, 0x4589, 0xa77d, 0xb020, 0xbaff, 0xa2d3, 0x4344, 0xb205, 0x46b8, 0x1801, 0x1ee2, 0x4131, 0x43ef, 0x408a, 0x1107, 0x4168, 0xbf41, 0xb20c, 0x432c, 0x2c41, 0x3ee4, 0xa926, 0x40aa, 0xb0b8, 0x3a6f, 0x469a, 0x4379, 0xbf7c, 0x1f14, 0xb2c5, 0xb2bb, 0x1c36, 0xb265, 0xba1d, 0x41af, 0x374d, 0xb28e, 0x42b4, 0xbae7, 0x4258, 0x40f9, 0xa16f, 0xae72, 0x1f30, 0xbad1, 0x1e7c, 0x4319, 0xbfd9, 0xbae6, 0xb0d2, 0xb28f, 0xb29e, 0xb03f, 0x41eb, 0x416e, 0x4095, 0x3136, 0xa0cf, 0x1dd8, 0x40e6, 0x06b2, 0xb2a3, 0x0458, 0xb036, 0x435d, 0x43f2, 0x43b0, 0x315c, 0xba07, 0xb0ba, 0x439a, 0xb21d, 0x1079, 0xba14, 0xbf22, 0x433f, 0x40cd, 0xbfe0, 0xa72f, 0xbac2, 0x4134, 0xb0ed, 0xbfda, 0xb2d7, 0x41e2, 0xba61, 0x1b57, 0x406a, 0x4217, 0x4064, 0xba66, 0x3ffd, 0x41b3, 0x343a, 0xb233, 0xbfd3, 0x4111, 0x4213, 0x4093, 0x40c3, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x6a1fb324, 0x37d1732c, 0x1194c5d3, 0x5a48240b, 0xf3985d54, 0xf7ed5c2f, 0xf3b4ec29, 0x13619425, 0x3b477912, 0x36e07690, 0x5d185c63, 0x32e603e4, 0xdff550bc, 0xbcb83fd1, 0x00000000, 0x300001f0 }, + FinalRegs = new uint[] { 0x1a1c0000, 0x00000e0d, 0xffff8d0e, 0x00000000, 0x0000003a, 0xffff8d0e, 0x00000000, 0x000071f5, 0x00001419, 0x868c5020, 0xffffffff, 0x32e603e4, 0xdff550bc, 0x000217f8, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xb2dc, 0x425f, 0xb2d1, 0xb20b, 0x1b94, 0xbf39, 0x1983, 0x4350, 0x426e, 0x43a5, 0x4377, 0x1ee2, 0x1e44, 0x1b2d, 0x1ad2, 0x1cc0, 0x402d, 0xb2f6, 0x02f0, 0x3eb0, 0x4037, 0x0a3e, 0x418c, 0xbf8a, 0x405e, 0xa376, 0xb075, 0x420d, 0xb2b6, 0xb022, 0x440a, 0xb0cc, 0x46a9, 0x40e7, 0x21fc, 0xb27b, 0x437c, 0xa9bc, 0x1c9d, 0x42bd, 0x4197, 0xbf9f, 0x441e, 0xba11, 0x424e, 0xbff0, 0x41cb, 0xb21c, 0x428b, 0x1a18, 0x4007, 0x1795, 0x2000, 0x4006, 0x43b6, 0xbf3b, 0x42ec, 0x422d, 0x2e16, 0x29b0, 0x1bee, 0x407d, 0xb02d, 0x4075, 0xbf64, 0x407e, 0x4547, 0x4159, 0xb0eb, 0xb21d, 0x41f2, 0x09e7, 0x410d, 0x462b, 0x4583, 0xb2f8, 0x409e, 0x40a4, 0xbfd0, 0xb051, 0x4029, 0x3874, 0x4642, 0xbfba, 0x40d1, 0xb217, 0xb083, 0x2a1f, 0x4263, 0x468a, 0x322b, 0xba31, 0x0de5, 0x4091, 0xb243, 0xb296, 0x4259, 0xac7d, 0x2a5a, 0x4010, 0xbf98, 0x4116, 0x4166, 0x19d7, 0xba43, 0x0e2e, 0xbff0, 0x430f, 0x439b, 0xa2f3, 0x1914, 0x3b48, 0x18d2, 0x4065, 0xb07d, 0xb02c, 0xb2a7, 0xb2c8, 0x03b0, 0xa9b3, 0x45de, 0xbf00, 0xb27c, 0x4347, 0x1fc8, 0x40cc, 0x4095, 0xbf74, 0x2dfe, 0x4223, 0x42f8, 0x3a83, 0x1b47, 0x1be1, 0x1281, 0x42da, 0x03dc, 0x40f1, 0xb2c1, 0x4648, 0x44b0, 0xba72, 0xba36, 0x0d2d, 0x4001, 0xbf9b, 0x1890, 0x45d4, 0x42b6, 0x40f9, 0x420a, 0x410e, 0xaa69, 0xb263, 0xbf77, 0x00be, 0x4092, 0x40e7, 0xb2b1, 0x435a, 0x42b4, 0xb09f, 0x420d, 0xb06a, 0xba63, 0x4272, 0x42d0, 0x32b7, 0x43ce, 0x1af8, 0xb06d, 0x430c, 0x404c, 0x097a, 0xb2c9, 0xbf3c, 0x2533, 0xbadd, 0x46b0, 0x441f, 0xb0f8, 0x4384, 0x45da, 0x42fb, 0x4380, 0xbac6, 0x35c4, 0x1564, 0x0e66, 0xbfe0, 0x3645, 0xba64, 0x4229, 0xa6c0, 0xbf14, 0xb07b, 0x40fe, 0x4353, 0x1856, 0xa5a8, 0x1bb5, 0xaa8b, 0x40bf, 0xabf0, 0x4389, 0xbac0, 0xba7b, 0x43e4, 0x1ee2, 0x42ce, 0xbf0e, 0x46a0, 0x4279, 0x45ae, 0x4096, 0x0e54, 0x4106, 0xb25d, 0x40ec, 0x3483, 0x4090, 0x42cf, 0x1a7a, 0x406b, 0xb02c, 0x43b1, 0x4029, 0x2399, 0x19ce, 0xb233, 0x4303, 0xbfd0, 0x2c0c, 0x43d7, 0xb254, 0x27fa, 0x2ea7, 0x0a4b, 0x40b1, 0xbfc4, 0x1b4d, 0x420e, 0xb2b7, 0xb234, 0x4210, 0x43f8, 0x466b, 0x4134, 0xb2f1, 0x1f53, 0xb06b, 0x40a2, 0xb0f1, 0x40ea, 0xb0f5, 0x421d, 0x42df, 0x28d3, 0xba66, 0x42fc, 0xbfd5, 0xbfe0, 0xbfa0, 0xba2f, 0x45c1, 0xbae4, 0xb290, 0x4030, 0x436c, 0x466e, 0x44d9, 0xba3a, 0xba5f, 0xa987, 0x247e, 0x1273, 0x1925, 0xb240, 0xa2a8, 0x1d6a, 0xbf94, 0x42e4, 0x43e1, 0x42ff, 0xbf49, 0x454d, 0x442d, 0xb26b, 0xb054, 0x41d3, 0x402d, 0x40b2, 0x43e0, 0xbf2e, 0x0098, 0x4047, 0xb2af, 0x42e4, 0x4315, 0x41c2, 0xab7f, 0xb279, 0xb253, 0x4101, 0xb2fe, 0xa41d, 0xb2f0, 0x4357, 0x1b5e, 0x3f79, 0xb26e, 0x41bd, 0xbf69, 0x1813, 0x0a71, 0x0efc, 0x4215, 0x16f2, 0x4108, 0x4191, 0xbf2a, 0xb2a0, 0x1c23, 0xaeff, 0x21f2, 0x2a43, 0xbad6, 0xac3a, 0x43ee, 0x4226, 0xbf42, 0x412a, 0x04ce, 0x3044, 0x42fd, 0x430f, 0x3e1c, 0x4063, 0xbfac, 0x3557, 0x4258, 0x06d8, 0x4250, 0x43fa, 0xbaf8, 0x40e1, 0x11e8, 0x4000, 0x40ff, 0x1adf, 0x4693, 0x4257, 0x46d0, 0xbfba, 0xb2b9, 0xb23d, 0xba49, 0x4266, 0x4210, 0xa557, 0x1df4, 0xba45, 0x454c, 0xa556, 0x434e, 0x1a7a, 0x4380, 0xb09a, 0x4087, 0x1937, 0xb20f, 0x4067, 0xbfd3, 0x42b7, 0xb227, 0x41f9, 0xbaee, 0x4191, 0x4011, 0x2bf8, 0x41c0, 0x4599, 0x428a, 0x40ba, 0x1e9f, 0x12b1, 0x2cdc, 0xb285, 0xaa95, 0xbfbe, 0x4022, 0x466c, 0xba77, 0xb041, 0x42bc, 0x4016, 0x434b, 0x41c6, 0xba0a, 0x416d, 0x3ddc, 0xba28, 0x40f3, 0x3d50, 0x41fb, 0x437d, 0x42e3, 0x4343, 0x143b, 0xbf4b, 0x1a96, 0x43cd, 0x05a6, 0x41bf, 0x413e, 0x1de5, 0xb0c7, 0x436d, 0xbac4, 0x43cf, 0x42e9, 0x4294, 0x07b5, 0xa1ea, 0x4577, 0xbae6, 0x4387, 0x42eb, 0x2ba4, 0x4117, 0x4229, 0x4171, 0xb0b0, 0xbade, 0x4226, 0x43a4, 0xa8c8, 0x4213, 0xbf00, 0xbf88, 0xb07b, 0x4229, 0x322d, 0x2ed2, 0x42d6, 0x2426, 0x427b, 0xbf28, 0x039a, 0x4377, 0x1baf, 0x2d7d, 0x42fd, 0x4699, 0x4004, 0x4166, 0x4164, 0x0761, 0xb0da, 0xb2c5, 0x4178, 0x19b8, 0x42a1, 0xbfb9, 0x4299, 0x4192, 0x40f5, 0x42d0, 0xbf7f, 0x4353, 0x40d7, 0x4261, 0x09af, 0x1c71, 0x4492, 0x421b, 0xba64, 0x41a2, 0x4391, 0xbfe0, 0x18ad, 0x4114, 0x42ba, 0xbafa, 0xb0e0, 0xbf12, 0x3c22, 0x13d3, 0xb270, 0x1297, 0x4256, 0x4227, 0x4148, 0xbfa1, 0xbf80, 0x1863, 0x075c, 0xa63a, 0x192a, 0xb285, 0x415f, 0x371f, 0x36d4, 0x41f9, 0xb09f, 0x4246, 0xb2f3, 0xb000, 0x420d, 0x4315, 0xb2b3, 0x0bc4, 0x2021, 0x0dda, 0xb0c5, 0xbf7a, 0x432f, 0xa889, 0x4112, 0x42ea, 0x4310, 0xbf3e, 0xa3e2, 0x4291, 0x1be6, 0x43bd, 0x40b7, 0x205f, 0x4014, 0x31dc, 0xa38c, 0x4117, 0x4230, 0x4381, 0xbf66, 0x4002, 0x41df, 0x42f0, 0xbaf0, 0x4171, 0xba79, 0x1339, 0x45cb, 0x409d, 0xbf18, 0x4163, 0xb07c, 0x4172, 0x40c2, 0x2cb7, 0x4309, 0x4068, 0x1788, 0xba35, 0x42b2, 0x43a5, 0x43c5, 0x442f, 0xba0c, 0x014d, 0x432c, 0xb2d3, 0x3076, 0x4274, 0xb283, 0xa8fe, 0x1c15, 0x46d1, 0xb075, 0xbf1e, 0x42b9, 0x31df, 0xba3b, 0x414e, 0xbae5, 0xa161, 0xb2c0, 0x436c, 0xbf9c, 0x43c1, 0xbadb, 0x18b0, 0xbfe2, 0xaf82, 0x39f6, 0x41b3, 0x2696, 0x38ad, 0x42b5, 0xb043, 0x43dd, 0x40ca, 0x42f0, 0xba54, 0x4369, 0x0412, 0x4349, 0x438f, 0x4555, 0x4022, 0x412f, 0x41f4, 0x4139, 0xa343, 0x41c4, 0xba2c, 0xad94, 0xbfb4, 0x0d58, 0x3a42, 0x43a9, 0x41fb, 0xbaf7, 0x463c, 0x0151, 0x44e8, 0x43bf, 0x42b2, 0x42e1, 0x0479, 0x1801, 0xba2e, 0x1614, 0x41fe, 0x4204, 0xbad0, 0x18ad, 0x4056, 0xb0e2, 0xb2a8, 0x06e3, 0xb260, 0xbf3f, 0xba59, 0x4620, 0xba29, 0x3348, 0x45c4, 0xbaef, 0xb293, 0xbadb, 0x40d2, 0x4374, 0xba7f, 0x35fa, 0x1b6c, 0xb23b, 0x2a15, 0x1ed7, 0xa687, 0xa2c0, 0x01fc, 0xa940, 0xba6e, 0x459a, 0x4044, 0x437d, 0xb2ab, 0x3190, 0x2415, 0x2092, 0xbf87, 0x427d, 0xb271, 0x2f34, 0x21c7, 0x404d, 0xb08a, 0x1ce7, 0x30cd, 0x433c, 0x45ab, 0xb234, 0x438d, 0x3eae, 0x4062, 0xb224, 0x3c04, 0xb2ce, 0xb23a, 0x17a3, 0xbfbc, 0x1af8, 0x430c, 0x4264, 0x43d9, 0xb2b5, 0x40dc, 0xbad1, 0x1ad9, 0x41d7, 0x40b1, 0xb0e2, 0x43b9, 0x41ca, 0x352f, 0xbf70, 0x33cb, 0xac4b, 0xba11, 0xb0d1, 0x42c3, 0xbf0e, 0xb277, 0x1c5f, 0xb2f3, 0x4278, 0x4005, 0x40d3, 0xa013, 0x441a, 0x44b4, 0x089a, 0xad41, 0x1c00, 0xb238, 0x46a9, 0x40a7, 0xba4e, 0x43ab, 0x2692, 0x1f89, 0xb021, 0x41a7, 0xbfa2, 0x106c, 0x4213, 0xb251, 0x423c, 0x43fd, 0x423d, 0x406b, 0x40b7, 0x1cc0, 0xacca, 0x433f, 0x3dda, 0xba4a, 0x3137, 0x2707, 0x4167, 0x4016, 0xbf45, 0x40d7, 0xba6a, 0x403b, 0xae6b, 0x42ae, 0x4351, 0xb0bf, 0x420c, 0x2052, 0xb037, 0x1b19, 0x43cf, 0x42aa, 0x40cd, 0x4329, 0x0139, 0xbf32, 0x2fda, 0x1320, 0x256b, 0x421c, 0x41e4, 0x4080, 0x40b8, 0x07be, 0x3f93, 0x4270, 0xb2d8, 0x4295, 0x41ba, 0xb013, 0x440f, 0x4157, 0x42b1, 0x40f4, 0xb0f6, 0x1b7d, 0x408b, 0xbfe0, 0xba53, 0x433a, 0x464f, 0xbf25, 0x406e, 0xae0f, 0x1c12, 0xb230, 0xbacf, 0xbacb, 0x1fe3, 0xbf90, 0x4602, 0xbf82, 0xba73, 0x4481, 0x23b5, 0x4394, 0x4320, 0xbad1, 0x1aec, 0x27c6, 0x41bd, 0x4157, 0x1de8, 0xbfe0, 0x435e, 0x41de, 0x25df, 0x01fe, 0x40d6, 0x467f, 0xb081, 0xb0b2, 0x3885, 0x192d, 0x2974, 0x31f9, 0x458d, 0x2fc1, 0xb048, 0xbf18, 0x10c7, 0x4272, 0xb25a, 0x4198, 0xb221, 0x400f, 0xa6c2, 0x42d2, 0x1b23, 0x417b, 0x4456, 0x4465, 0x1fdf, 0x1c70, 0x4068, 0xb2d8, 0x4582, 0xb279, 0x40b9, 0xba17, 0x4243, 0x4060, 0x1936, 0xbf0f, 0xa1bc, 0xb0fa, 0x439d, 0x41e8, 0x39be, 0xb2e1, 0x4430, 0x40ff, 0x1a28, 0x42de, 0x1e90, 0x417e, 0x03bc, 0x4240, 0x435c, 0xbad7, 0x1de9, 0xa31a, 0x1135, 0x4198, 0x441f, 0x4276, 0xbf70, 0x1d98, 0xbfa4, 0x418f, 0xba2f, 0x41f1, 0x4182, 0x41e0, 0x425b, 0xb269, 0x1ff1, 0x4330, 0xb29e, 0x2039, 0x41b9, 0xa5d6, 0x42bb, 0xb2dd, 0x4025, 0x2734, 0x469b, 0x4368, 0x1c15, 0x41c3, 0xbfb1, 0x0bef, 0x4469, 0xb214, 0x3073, 0x423d, 0x1ecf, 0x437f, 0x41e6, 0xac31, 0xbf6d, 0x18aa, 0x4641, 0x2805, 0x43e1, 0xa531, 0x42a7, 0xb246, 0x1f48, 0x42a0, 0x1e10, 0x4263, 0x43eb, 0xb090, 0x4020, 0xa807, 0x4149, 0x42b1, 0xb291, 0xbf90, 0x2fbf, 0x1edd, 0x3d40, 0x433f, 0x3eb8, 0xbf5f, 0xb212, 0x4151, 0x43b7, 0x4415, 0x03b7, 0x40cb, 0xba3f, 0x438d, 0x41fd, 0x0518, 0x42cf, 0xa79a, 0xa354, 0x2ac3, 0xb25a, 0x4145, 0xa5e8, 0x400e, 0x1851, 0xbf87, 0x1c05, 0x4377, 0x405d, 0xba00, 0xb2d5, 0x4232, 0x319c, 0x4409, 0x1f3d, 0x41e6, 0x4069, 0xbfb4, 0x41d9, 0x18d0, 0x4307, 0x4567, 0x433e, 0x421c, 0x429c, 0x055c, 0x41ab, 0x40b1, 0x40f1, 0x353d, 0xaae0, 0x4237, 0xaadc, 0x23f3, 0xb0b0, 0xbf47, 0x401a, 0xaeea, 0x4077, 0xa7bd, 0x4209, 0x41a4, 0x46eb, 0x437f, 0x4226, 0xb2e2, 0xb04a, 0x4233, 0x4387, 0xbfc8, 0x46dc, 0x4381, 0xbada, 0x0d3d, 0x44cb, 0x405c, 0x4544, 0x438e, 0x419a, 0x40f9, 0x442a, 0xb000, 0xb2da, 0x1aa8, 0x1e51, 0x1dd4, 0x1319, 0xba33, 0x4176, 0x40eb, 0xbff0, 0xbf62, 0x42d7, 0x40e7, 0x413e, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xec5af9f4, 0x9436eb06, 0x9ba4ad42, 0x6f255f58, 0x3f12d10f, 0x0928b6c9, 0x415b7bf8, 0x054f2f3e, 0x41a3cd86, 0x6af229e8, 0xc9f61355, 0xeb58ff68, 0x0c0e2ab2, 0xe150fa69, 0x00000000, 0xe00001f0 }, + FinalRegs = new uint[] { 0xffffff39, 0x00000000, 0x000000f3, 0x00000000, 0x000000fa, 0x0000002c, 0x2eb370e8, 0x02c5a110, 0xe151009d, 0xe150fdb5, 0xffffffff, 0xc2a1f61a, 0xe150f865, 0xe150f98d, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x368f, 0x4181, 0xba43, 0x438d, 0xb2ff, 0x43b4, 0x1f09, 0xb23a, 0x4230, 0x293c, 0x4083, 0x243f, 0x2f0b, 0xba43, 0x4022, 0xbf99, 0x2d22, 0xb03a, 0xb2bd, 0x44d3, 0x08ec, 0x425e, 0xb00c, 0x407d, 0xba24, 0xafc8, 0x1de9, 0xbf70, 0x41a6, 0xbf58, 0x42e9, 0x264c, 0x45c3, 0x40bd, 0x4029, 0xba3a, 0x1fda, 0x405c, 0x4005, 0xb2cd, 0x4079, 0x3285, 0xa715, 0x411f, 0x4560, 0xbfb1, 0x406c, 0x4091, 0xa6a9, 0xb22f, 0x41d6, 0xb20e, 0x40ad, 0xbff0, 0xad3c, 0x443a, 0xbf80, 0xb256, 0x4025, 0x43b3, 0x1fd2, 0x00f7, 0x4160, 0xbf1f, 0x36b9, 0x19d7, 0xb26e, 0x4101, 0x41d5, 0x1d7a, 0xba56, 0x43a6, 0xbf83, 0xb2dc, 0x424b, 0x4124, 0xba59, 0x4307, 0xb0cf, 0x4328, 0x4065, 0xbafa, 0xb0c7, 0xb0af, 0xbf83, 0x43b6, 0xb2b8, 0x408c, 0x0730, 0xbfb4, 0xbff0, 0x02a9, 0xb286, 0x4258, 0x45b2, 0x42dd, 0x427e, 0x1faf, 0x1af5, 0x40fb, 0xb092, 0x4615, 0xbf29, 0x1b44, 0xbf60, 0xa826, 0xb250, 0x40cc, 0xb0ad, 0x23d8, 0x10b2, 0x4183, 0x426a, 0x4152, 0xbaf9, 0xacfe, 0x1cd8, 0xbfa2, 0x2419, 0x3078, 0x4083, 0xb2c7, 0x45ae, 0x38db, 0x1879, 0x40cf, 0x4220, 0xb23d, 0x20b8, 0xba32, 0x4031, 0x455b, 0x105c, 0xb295, 0x1af1, 0x12d9, 0xbfc9, 0x403c, 0x4133, 0x4261, 0x41aa, 0x4369, 0x032b, 0xb049, 0xad38, 0x27b3, 0xb20c, 0x424c, 0x4238, 0x1923, 0xb283, 0xb050, 0x424a, 0x1c55, 0x0a49, 0xb0f6, 0x43e2, 0x0e4f, 0x4219, 0x400a, 0x42ac, 0x414e, 0xa73b, 0xbf0f, 0x437b, 0x41ef, 0xa1bd, 0x4051, 0x4308, 0x16a2, 0xbfa0, 0xb26d, 0xba3c, 0x181e, 0xba7e, 0x42c2, 0xba45, 0xac7d, 0x087f, 0x4225, 0xba6c, 0x188b, 0xbf0a, 0xa98e, 0xb2da, 0x1f20, 0x19c1, 0xbf08, 0x40fe, 0x4137, 0xb23a, 0xbfb0, 0x4098, 0x1814, 0x4026, 0x2027, 0x4269, 0xb060, 0x1807, 0xb28e, 0x1ae8, 0xb2a0, 0x4308, 0x414a, 0x416e, 0x1358, 0x43c5, 0xb257, 0x4412, 0xbfc0, 0x2c84, 0x4212, 0xbf7f, 0x40b3, 0xb0cc, 0x404b, 0x0a2d, 0x1a49, 0x1eb1, 0x4342, 0x43b1, 0x1bc8, 0xa36f, 0x42b3, 0x1995, 0x43d8, 0x41c3, 0x407f, 0x410b, 0x40b9, 0x416c, 0xac31, 0x4010, 0xb08d, 0x4007, 0xb209, 0xb091, 0x4383, 0xb21e, 0x411d, 0xb02b, 0x44ad, 0xbf82, 0x18f7, 0x422a, 0xb01d, 0x3f8a, 0xb05f, 0x4589, 0x45a6, 0x4161, 0xba59, 0x41e8, 0xb280, 0x4052, 0xae0d, 0x43b8, 0x4297, 0xb2e8, 0xb260, 0x44a9, 0xbad8, 0x429b, 0x1271, 0x412a, 0x43ef, 0x403f, 0x1f4c, 0xbf7e, 0xa769, 0x40fa, 0xb2a7, 0x3cc5, 0xbf70, 0x17d1, 0x41de, 0x414f, 0xbf7f, 0xba4d, 0xa1df, 0x022b, 0xa268, 0x4237, 0x1a45, 0x40ce, 0x4324, 0x129a, 0x4087, 0x1820, 0x4287, 0x401a, 0x43a3, 0x268b, 0x1b43, 0xa18b, 0x1855, 0x4046, 0x406f, 0x43ee, 0x140b, 0xba3c, 0xb27d, 0x4227, 0xb2fc, 0xab9c, 0x418c, 0xbf2d, 0x42bc, 0x41a9, 0x02ae, 0xb203, 0x401d, 0x4250, 0x431a, 0x40b4, 0xba7c, 0x40db, 0x20f2, 0xb200, 0xa5a0, 0x31f7, 0x028e, 0xbfa0, 0xb2c0, 0x1584, 0xbadf, 0x4278, 0x43ab, 0x0582, 0x414a, 0x1a67, 0x3b4c, 0x1c8a, 0x42c3, 0x40e1, 0x4272, 0xbf41, 0xba3c, 0x42b7, 0x40fa, 0x0974, 0x42b7, 0x1c59, 0xbf17, 0x1c3b, 0x42b2, 0x42c7, 0x1feb, 0x419f, 0x24bb, 0x0932, 0x46c4, 0x42fd, 0x16d0, 0x42f3, 0xbac8, 0x125a, 0xb275, 0x18b1, 0x4432, 0x2522, 0xbf6c, 0x43e3, 0xba24, 0xb053, 0x439c, 0xa9a6, 0x1aa2, 0x3eaf, 0x402d, 0x0b2b, 0xa543, 0xbfa0, 0x4055, 0xbf12, 0x4168, 0x437e, 0x1ea7, 0x4022, 0xa4e7, 0xba4b, 0x394d, 0x195c, 0x1ab5, 0x41fd, 0x436d, 0xaa65, 0x2437, 0xbac6, 0x424a, 0xbfca, 0xba0e, 0xbaec, 0x4038, 0x1252, 0x258d, 0x4186, 0x45d2, 0x42a3, 0x45b8, 0x4160, 0xbf3e, 0x3d8d, 0xb2a1, 0xb277, 0xb2b9, 0xba2a, 0x42e5, 0x4149, 0x4345, 0xba37, 0x43a0, 0x4104, 0x20f0, 0xbac7, 0x43d0, 0x42a9, 0x4215, 0x4576, 0x0a5a, 0xb21c, 0x0657, 0xba38, 0x0f44, 0x1b98, 0x1deb, 0x43af, 0x4198, 0x40bf, 0x3af8, 0xbfd4, 0x03ec, 0x2f69, 0xb05c, 0x2d36, 0x1505, 0xb2be, 0x40ba, 0xb016, 0x40e9, 0x0007, 0xbf66, 0xb284, 0xb2fa, 0x19c8, 0x11bc, 0xba48, 0x4172, 0xa0c2, 0x41de, 0x42d9, 0xb224, 0x1e05, 0x42df, 0x1bf1, 0x272b, 0x4143, 0xbf97, 0xba04, 0x43a9, 0x16d4, 0xb2b7, 0xbafe, 0x033a, 0xba53, 0xb2a6, 0x432c, 0x425e, 0x35c4, 0x43d7, 0x43a1, 0xb209, 0x40d7, 0xbad6, 0x434c, 0x4380, 0xbf61, 0x43db, 0xa3c6, 0x4202, 0x41a0, 0x4370, 0x42d2, 0xb241, 0x0725, 0xb24a, 0x41b8, 0x1e71, 0x42dc, 0x419c, 0x1ccf, 0xb267, 0x4427, 0xb2f7, 0xb235, 0x2c94, 0x19eb, 0xb2b2, 0xbf76, 0x3401, 0x41d7, 0x4212, 0x1e3e, 0xae23, 0x3934, 0x4128, 0xb245, 0x1fcb, 0xbfcb, 0x1a32, 0x44dc, 0x41a4, 0x3ec2, 0x4381, 0x318a, 0x408b, 0x4076, 0x42be, 0x4214, 0x0530, 0x4351, 0x2735, 0xad1b, 0x436c, 0x43dc, 0xba34, 0x4276, 0x41eb, 0xb01f, 0x3246, 0x0717, 0x437e, 0x4021, 0x4043, 0xb237, 0x1876, 0x417d, 0x41d4, 0xbf57, 0xba07, 0xb2e7, 0x1f8b, 0xb28c, 0xbf21, 0x4243, 0x4157, 0x3d59, 0x40be, 0xb22b, 0xb21f, 0x2afc, 0x0525, 0x4291, 0x465e, 0x402d, 0x45ce, 0x0f5f, 0x3952, 0xaaf9, 0x414a, 0xbacc, 0x0081, 0x2e90, 0x4280, 0xbfa5, 0x404c, 0x413e, 0xb2d4, 0x38c8, 0xbf0a, 0x3124, 0x402c, 0x404e, 0x3c59, 0x41f1, 0xba4a, 0xbadc, 0x4117, 0xa1c4, 0x414e, 0x46b4, 0x40f7, 0xb251, 0x46a9, 0xa050, 0xb28c, 0x4369, 0x029c, 0x424c, 0x469b, 0x4266, 0x43b8, 0x2c0e, 0xbfbc, 0x1825, 0xadd6, 0x1b78, 0xba75, 0x10ef, 0x26bd, 0xb09b, 0x42b8, 0xba65, 0x3f0f, 0x0476, 0xb0dc, 0xb21f, 0xb02a, 0x1cb4, 0x468d, 0x3cbe, 0xbf29, 0xbad6, 0x439d, 0xb2d8, 0x1fc1, 0x4260, 0x4541, 0xb271, 0xbac0, 0x1d46, 0xb273, 0x197c, 0x4280, 0x426a, 0x1f19, 0x426c, 0xb284, 0x4230, 0xbf60, 0xb2fd, 0xb203, 0x18b1, 0xbf58, 0x39ee, 0x1ff4, 0x27de, 0xbfa0, 0xb27d, 0xbfe0, 0xb21c, 0x4165, 0x404d, 0xb2d9, 0x45c5, 0x4333, 0x1ba5, 0x4568, 0x43d8, 0xb277, 0x1ef0, 0x124f, 0xb251, 0x1d1d, 0xbf4a, 0x409b, 0xb23c, 0x4400, 0x4391, 0x293e, 0x1a99, 0x40be, 0x0314, 0x43bb, 0x41f5, 0xb28c, 0x1db7, 0x40c4, 0xb00c, 0x4175, 0x40af, 0x4291, 0x3db2, 0xa7cb, 0x4031, 0xba26, 0x43fa, 0x188c, 0x403e, 0x0de1, 0x432b, 0xb027, 0x2431, 0xbf74, 0x41e4, 0x432b, 0xb20f, 0x2b39, 0x4352, 0xaff4, 0xb2eb, 0x45ca, 0x4273, 0x4142, 0x4366, 0x40c1, 0x44fb, 0x1930, 0xb2a5, 0x1bf9, 0xb294, 0x42ec, 0x431c, 0x4345, 0xbf55, 0x0d4b, 0xb07f, 0x0095, 0x2cff, 0xb284, 0x14a1, 0x42bb, 0x4643, 0x436c, 0xb2f1, 0x44e8, 0xb23f, 0x4207, 0x2f1e, 0xb2d7, 0xa512, 0xac75, 0x3a91, 0x231d, 0x3c3d, 0x4151, 0x3341, 0x01ad, 0x44d5, 0xbf3c, 0x1763, 0xa0da, 0x41af, 0x1a77, 0x406d, 0x40fa, 0x18ed, 0x429f, 0x1a22, 0x295a, 0x2dbf, 0x4007, 0x0072, 0xbae7, 0x4343, 0x425d, 0xb2db, 0xb2d4, 0xac1e, 0x40cc, 0x1068, 0x46ca, 0x40c0, 0x417f, 0x4025, 0x4121, 0xbf1d, 0xb297, 0x1c35, 0x43f0, 0xba3f, 0x1e51, 0x43c9, 0x40de, 0x435b, 0xba05, 0x42b9, 0xb270, 0xbf05, 0x3d22, 0x4206, 0x1c39, 0xa97f, 0xba7e, 0xba1b, 0x31a6, 0x414f, 0x39ef, 0xb26a, 0xbad8, 0x2fd1, 0x1abe, 0x1f4e, 0x43a4, 0xb289, 0xb069, 0xb256, 0x4150, 0x181b, 0xbf6a, 0xa9e8, 0xb299, 0x260b, 0x1b81, 0x1356, 0x09f9, 0x16e5, 0x4353, 0x0218, 0x41e3, 0x42c2, 0x2b68, 0xba77, 0x2cc6, 0x4577, 0x42d2, 0x212f, 0x42b8, 0x4063, 0xb235, 0x195a, 0x3085, 0xbf69, 0x4208, 0x2c24, 0x0117, 0x27bc, 0x421d, 0x4165, 0x4033, 0xbf65, 0x38d0, 0x3e41, 0x46c4, 0x40d8, 0xb2d5, 0xbf60, 0x1ab8, 0x402b, 0x23ec, 0xbfc0, 0xbf21, 0xba39, 0x4176, 0x22b6, 0xa1ca, 0x4041, 0x1e24, 0x2334, 0xb079, 0x1ac9, 0x428c, 0x43a1, 0x4556, 0x1ebd, 0xa512, 0xba5c, 0x2319, 0x1ffe, 0xb25d, 0x421b, 0xbf9b, 0x4012, 0x41de, 0x41ad, 0xbaee, 0x42c6, 0x41d7, 0xb20a, 0x33f2, 0x42de, 0x41a2, 0x0d16, 0xba7a, 0x4289, 0xba26, 0xbf49, 0xb2ae, 0x129d, 0xb2eb, 0x4223, 0x1864, 0x0aad, 0xbf57, 0x412c, 0x404f, 0xaa07, 0x41c6, 0x40ef, 0x114a, 0x46f8, 0xbac8, 0x45f1, 0xa8a5, 0x4338, 0x43ab, 0x0833, 0x2140, 0x41b6, 0x2a3e, 0xb018, 0x4215, 0xbf05, 0xba24, 0xa61c, 0xbae2, 0x3218, 0x1078, 0xa5bb, 0x3cf2, 0x34b0, 0xbacd, 0xb085, 0xbf6d, 0xbac5, 0x46d2, 0x4335, 0x421e, 0xb2e6, 0x1df5, 0x412e, 0xbf08, 0x4060, 0x1c63, 0x1c85, 0xba0b, 0xb25a, 0x430b, 0xbfbe, 0xb072, 0x4307, 0x43f1, 0x436c, 0x463f, 0x0028, 0x4311, 0xa4ad, 0x4648, 0x4361, 0x4175, 0x228c, 0x42e9, 0x40e8, 0x0f81, 0x081b, 0x4146, 0xbf81, 0x42a1, 0xac30, 0x40f1, 0x4492, 0x4336, 0xb2fb, 0x1b1d, 0xb2ac, 0x23c8, 0x44ad, 0x1f91, 0x43fe, 0x42b7, 0x1bed, 0x1cb1, 0x3518, 0x0864, 0x4183, 0x42de, 0x4307, 0xb283, 0x43af, 0xb2cc, 0xba5f, 0x1411, 0xb298, 0x4071, 0xbf16, 0x40c9, 0x4637, 0x4307, 0xbf53, 0x1a70, 0x40ca, 0xb26b, 0x42e6, 0xb079, 0xa782, 0x1c70, 0x4623, 0x40b1, 0x41f2, 0x46a4, 0x2ac3, 0x4229, 0x3f2d, 0x4302, 0x030f, 0xa7bd, 0xbf81, 0x4040, 0xbafc, 0xa376, 0xb05e, 0xbf6b, 0x42ec, 0xa754, 0x41e9, 0x433d, 0x4325, 0x2462, 0x4205, 0xa968, 0xbff0, 0x00c8, 0x4378, 0x430f, 0x4136, 0x41f5, 0x2f8e, 0x44fa, 0x413c, 0xbfb9, 0xb097, 0xbaf6, 0xb03e, 0x43d0, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x8b2100eb, 0x3af85a8f, 0x0bb662e6, 0x7ac5449f, 0x6c8b9706, 0x2d86ea00, 0xd9186e8c, 0xb3755866, 0x7227be38, 0x7f97e5fb, 0xc176d2f9, 0x1ff0913a, 0xcb8e1a87, 0x6b0408ce, 0x00000000, 0x800001f0 }, + FinalRegs = new uint[] { 0xffffff72, 0xc176c228, 0x0000008d, 0x00000002, 0x00000000, 0xffffff36, 0x00000000, 0xc176db3c, 0x000016e4, 0x00000000, 0x000017e2, 0x0000206e, 0x00000002, 0xc176c180, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xbac5, 0x426b, 0x16bc, 0x2d44, 0x43aa, 0xbf7b, 0x43f8, 0x42b9, 0x458b, 0x1f7e, 0xb0ac, 0xb292, 0x42e3, 0xb251, 0xb2dd, 0x1b86, 0x35fd, 0x410c, 0xac53, 0x3357, 0x35fb, 0x4355, 0x413e, 0xb2b9, 0x42f8, 0xbfa3, 0x408f, 0x1d27, 0xb0dd, 0xbf70, 0x2c41, 0x1bb6, 0xbf0d, 0xb208, 0x2cad, 0xb296, 0x4132, 0x4119, 0x411d, 0x403a, 0x4332, 0xb2cb, 0xbac4, 0x4074, 0x403d, 0x18a4, 0x45ae, 0x42c9, 0xb043, 0xb0b4, 0x40c4, 0xba53, 0x409b, 0xbf63, 0x1a75, 0xb040, 0x4166, 0x3f7e, 0xa55e, 0x428d, 0x4235, 0x459c, 0x42d5, 0x4111, 0x42dc, 0x43ca, 0x4049, 0x4091, 0x29d5, 0xbf61, 0x40cd, 0x422d, 0x4170, 0x4541, 0x1062, 0x1cf3, 0x4248, 0x422e, 0x1cdc, 0xabfc, 0xa3f2, 0x0677, 0x28d6, 0x143c, 0xb2d6, 0xba55, 0xb261, 0x41f7, 0x439c, 0x2b7b, 0x1c82, 0xb008, 0x2432, 0xbf62, 0xa8ee, 0x37a9, 0x1e86, 0xa85b, 0xb25c, 0x434c, 0xbf96, 0x423e, 0x4093, 0x44e1, 0x42ff, 0x41bf, 0xa8c3, 0x43cb, 0x3a77, 0x2b99, 0x0976, 0x3160, 0x4030, 0xb2db, 0xba36, 0x3ef4, 0x4617, 0x1ab5, 0x02d3, 0xb018, 0x43ad, 0x40cd, 0xbf89, 0x4390, 0x15d6, 0x4255, 0x46dc, 0xac0e, 0x11b4, 0xbf05, 0xac46, 0x4392, 0xbac5, 0x1842, 0x40df, 0x4113, 0x4126, 0x1cac, 0x3e49, 0x4051, 0xb2cf, 0x43b7, 0xbf78, 0x07e7, 0xb210, 0xb26b, 0x1f7b, 0x03b0, 0x42db, 0x2d35, 0x43eb, 0xb23a, 0x418a, 0xaddb, 0x1c63, 0xbf33, 0x35a5, 0x4349, 0x0da2, 0x428e, 0x4132, 0x40f8, 0xba40, 0x41e8, 0x40e8, 0x2f5d, 0x417c, 0x1bc0, 0x41b3, 0xaa64, 0x1b12, 0x0f9c, 0x435b, 0x2204, 0x42d7, 0x19ce, 0x4059, 0x40b3, 0xad3a, 0x43a1, 0x44b9, 0x42f0, 0xb2d8, 0x2938, 0xbf8c, 0x0c53, 0x41c7, 0x4177, 0xbaca, 0x40a7, 0x4607, 0x4238, 0x4100, 0x14e5, 0x4271, 0x4148, 0x24b6, 0x108d, 0xb234, 0xafb1, 0x20a6, 0xbf2e, 0x1ff8, 0xba48, 0x42a8, 0x43a6, 0x40f7, 0x3723, 0x1e21, 0x4004, 0x14b3, 0xbae8, 0x43f2, 0x4223, 0xa5a7, 0x4021, 0xbf60, 0xb2dd, 0xb2fa, 0x4144, 0xb284, 0x135c, 0xbf8b, 0x40a8, 0x4238, 0xb26e, 0x42b4, 0x43c5, 0x3a5f, 0x43e5, 0x2fe1, 0xbf5e, 0x039d, 0x3102, 0x4076, 0x40ec, 0x1e43, 0xbfc1, 0x1f10, 0x212d, 0xa889, 0xb2af, 0x1ccd, 0xb22b, 0x4373, 0x09c1, 0xbf49, 0x40ab, 0xb2f7, 0x4037, 0xb28e, 0x41fd, 0x0334, 0x1a33, 0x41ce, 0x462f, 0x41a4, 0x4181, 0x1ac3, 0xbf46, 0xb054, 0xaf5f, 0x41f6, 0x3343, 0x42e6, 0x4281, 0x0710, 0x0255, 0x41fa, 0x43ca, 0x1bf1, 0x4138, 0x2a43, 0x4388, 0xb0b1, 0xb2d3, 0x41ef, 0x43f1, 0x4090, 0x4195, 0x4151, 0x15b9, 0x1eb1, 0x1923, 0x1b16, 0x4352, 0xbfde, 0x42e7, 0x4130, 0xb248, 0x43a6, 0x460a, 0xbf39, 0x1a10, 0x4375, 0x4089, 0xbacc, 0x26f9, 0x4582, 0xbf6d, 0xb20a, 0xba36, 0x41c7, 0xb22a, 0xb05e, 0xba3f, 0x1ffe, 0x34c2, 0xb244, 0x2181, 0xb2f0, 0x3c08, 0x08a6, 0xba2d, 0x4221, 0x33fb, 0x4162, 0x439d, 0x43e9, 0x1eb8, 0x430e, 0x1f07, 0xbadb, 0xba6e, 0x02d2, 0xbf4e, 0xb294, 0xb2a7, 0xba6c, 0x41a4, 0x4647, 0xb231, 0x41c2, 0xb29e, 0x0648, 0x0ca8, 0x4273, 0x425d, 0xbf33, 0xa38e, 0xba5e, 0x407a, 0x1415, 0x3523, 0x4023, 0x2600, 0x4029, 0x426a, 0xb25a, 0xb2c1, 0x1b6e, 0x42aa, 0x4040, 0xb2bd, 0xab1b, 0x40de, 0xb05c, 0x427f, 0xbf2e, 0xb004, 0xba52, 0x1b44, 0x413d, 0x4147, 0x41a7, 0xbac9, 0xb27d, 0x1c52, 0x42aa, 0x1c6e, 0x43d7, 0xb04a, 0x4676, 0x1f18, 0x41fe, 0x42db, 0x4252, 0xbf4d, 0xb098, 0x3625, 0xb29f, 0x43c0, 0xa08e, 0xbf8c, 0x4317, 0xba78, 0x40ae, 0x3641, 0x42c7, 0xaf31, 0x40ae, 0x4360, 0x4066, 0x1a32, 0x4138, 0xba1f, 0xba21, 0x23aa, 0xba5f, 0x45e9, 0xba0c, 0xbfc6, 0xb2ad, 0x4389, 0x0aea, 0x0da6, 0x17ad, 0x1de4, 0x4247, 0x4040, 0xb2e0, 0x43d1, 0x1a71, 0xb088, 0xba62, 0x4034, 0x4077, 0x40dc, 0x1a7b, 0x05a4, 0xba3d, 0x4424, 0xbf55, 0xbfb0, 0x1408, 0x408c, 0x43c3, 0x42dd, 0x4464, 0xa818, 0x1161, 0x19ea, 0xb0ce, 0x24ce, 0xb2af, 0x15fa, 0x027b, 0xba01, 0xa369, 0x014f, 0x4036, 0x43ed, 0x40e5, 0x320e, 0x4214, 0xbaed, 0x275f, 0x400c, 0xba13, 0xa866, 0xbfb0, 0x41b7, 0xbfcd, 0x2c19, 0xbaea, 0x421e, 0xb296, 0xbf42, 0x42cc, 0x1726, 0xb08d, 0x4314, 0xbf87, 0x4107, 0x0425, 0x4586, 0x3b4d, 0x40c1, 0xbafc, 0xb0d5, 0xbf1e, 0x0b9a, 0xba4c, 0xb0e4, 0x3056, 0xbf80, 0xafd4, 0xb2fb, 0x4395, 0x4669, 0xb296, 0x1914, 0x4009, 0x2ac8, 0x4102, 0x4116, 0x40c1, 0x4130, 0x4493, 0x1bc0, 0xb250, 0x463b, 0x0469, 0x424b, 0x1b6b, 0x3e46, 0xb2fa, 0x4151, 0xbfac, 0x44d9, 0x1c7a, 0x1a00, 0x420b, 0xbaf5, 0x407e, 0xb21e, 0xad7b, 0x425c, 0xbf7a, 0x1db5, 0xae92, 0x1b35, 0x1ea1, 0x2cf5, 0x1af1, 0x4210, 0xbaf4, 0x42a0, 0x4124, 0x41dd, 0x4215, 0x13b4, 0x43e6, 0xbfa4, 0x416b, 0x1e6a, 0xbfc0, 0x0d58, 0x40c0, 0x454e, 0x0202, 0x40b5, 0xb276, 0x4333, 0x4574, 0xa2ac, 0x2a76, 0x425d, 0x40fc, 0x40da, 0xba47, 0x2428, 0xbf19, 0xb2a3, 0xb269, 0x2843, 0x3a89, 0x1f48, 0xbf7f, 0x416c, 0x4208, 0x4356, 0x43f2, 0xbae5, 0xb274, 0x4037, 0xb237, 0xaa79, 0x42d2, 0x1d34, 0x426f, 0xb236, 0x43a3, 0x4200, 0xbf60, 0x18b8, 0x1d7f, 0xbf8d, 0xb273, 0x4190, 0x3d1e, 0x415c, 0xba38, 0xbfc0, 0x13f8, 0x4117, 0xba3e, 0xb274, 0xbfb5, 0x4077, 0xb279, 0x4009, 0x3ac8, 0xaa7c, 0x0983, 0xbf8b, 0xba24, 0xb27b, 0x1e7e, 0xb0d1, 0x42c2, 0xaba6, 0xba50, 0x1eea, 0x056b, 0xa171, 0x43b3, 0x35ca, 0xb0e3, 0xb0c5, 0x08e0, 0xb22e, 0x414b, 0x41e6, 0x40bc, 0x2800, 0x402f, 0x409f, 0x164c, 0x44b3, 0x4154, 0x4158, 0x418a, 0x4028, 0xbf94, 0xb28b, 0xaff1, 0x41ec, 0x0d8d, 0x41a2, 0x1b3b, 0xaf95, 0xba4a, 0x1ee5, 0x40cd, 0x1840, 0xae03, 0xb0bc, 0x4643, 0x434f, 0x0c96, 0xba07, 0x3748, 0x46d8, 0xbf26, 0x4195, 0x0b00, 0x41ab, 0x4315, 0x405a, 0x4145, 0x14f4, 0xa261, 0x42dc, 0x3318, 0x424c, 0x1fdc, 0x0500, 0x1acf, 0xba4d, 0x1f49, 0x40c1, 0x1825, 0x42cf, 0x4175, 0x4097, 0x293b, 0xba0d, 0x4014, 0x207a, 0x407d, 0x4361, 0x437a, 0xbf4b, 0x4086, 0x41a0, 0x3c12, 0xb0ca, 0x460c, 0x4162, 0xb24d, 0x425e, 0xb2cd, 0x0dcb, 0x13ba, 0x40c2, 0x41af, 0x29cb, 0x0956, 0x41aa, 0xb216, 0x4112, 0x4349, 0x4431, 0x2734, 0x422e, 0xbf2a, 0x40b8, 0x4649, 0xb068, 0x438c, 0x426d, 0xabc5, 0xb0a7, 0xbf9c, 0x4671, 0x42e1, 0x43ee, 0xb02a, 0x4217, 0xb0ce, 0x4026, 0x4269, 0x4374, 0x3931, 0x02f5, 0x42c6, 0x43aa, 0x2532, 0x2c53, 0x4231, 0xb2dd, 0x43a9, 0x1c6d, 0x4274, 0x2948, 0x4162, 0xbfa3, 0x427f, 0xb01c, 0x0396, 0x4174, 0x1327, 0x36ab, 0x41f4, 0x41b6, 0x3fda, 0x4648, 0x4095, 0x1c98, 0x41a0, 0xbf56, 0x4144, 0xb0dd, 0xb28c, 0x42d5, 0x4280, 0xbf81, 0xaccd, 0x45ee, 0x4145, 0x4342, 0xbfb7, 0x4261, 0xba45, 0xb01f, 0x418a, 0x439f, 0x42bf, 0x43fc, 0xba22, 0xbfe0, 0x2446, 0xbfde, 0x1c19, 0xb0f5, 0xba68, 0x1b4a, 0x00d7, 0x4377, 0x425d, 0x46db, 0x411e, 0xb2b8, 0x406a, 0x1945, 0x43b9, 0xa423, 0x42af, 0xbf76, 0x412e, 0x1fa8, 0xb25f, 0x4591, 0xb27f, 0x461b, 0x41e3, 0x0cc4, 0x0236, 0x4157, 0x288b, 0x05d7, 0xbf87, 0xba66, 0x43bf, 0xba33, 0x18c4, 0x416c, 0xbacb, 0x0077, 0x398d, 0x414d, 0x1fc4, 0x447d, 0xbaef, 0xb2a8, 0x1833, 0xba42, 0xbf51, 0x41c6, 0x43ae, 0x420b, 0x2a87, 0x437e, 0x4206, 0x42ff, 0x4221, 0xa594, 0x2023, 0x4311, 0xb241, 0x416f, 0x40e2, 0x0418, 0x422b, 0x43c2, 0x4385, 0xbf81, 0xb284, 0x408d, 0x40ee, 0x4377, 0x43b3, 0x416d, 0x4353, 0x432b, 0x44a1, 0x43d3, 0xbf81, 0xbaf1, 0x40a6, 0x42c2, 0x0cf8, 0x4068, 0x1ff8, 0xb231, 0x41f9, 0x430f, 0x43a0, 0x1705, 0x0110, 0x09ac, 0xbace, 0x4230, 0x46d4, 0xaec3, 0x4353, 0x416e, 0xb20e, 0x43fc, 0xba7d, 0xbf17, 0x41b0, 0x33b5, 0xba1c, 0x4046, 0x400a, 0xbf8f, 0xb0bd, 0x42d2, 0xacb0, 0x1ddc, 0xb2f1, 0x40a3, 0x4082, 0x4318, 0x2d71, 0x19ec, 0xb0fd, 0x4336, 0xb2cd, 0x40cc, 0xaba4, 0x1a86, 0x42f0, 0x4668, 0x38ff, 0x1e05, 0x4040, 0x184a, 0x4015, 0xbf38, 0x4242, 0x4626, 0xad8d, 0x43c2, 0xb277, 0x4030, 0xbae2, 0x41a5, 0x4215, 0x1513, 0xbf6a, 0x4142, 0x43ba, 0x44f0, 0x4178, 0x2703, 0x428d, 0x42c0, 0x2ffb, 0x438b, 0x42cd, 0x40b5, 0x4435, 0x445d, 0xa385, 0x4082, 0x4148, 0xb2eb, 0x022f, 0x423b, 0x42f5, 0xbf5f, 0x04f0, 0xb098, 0x40a5, 0xba1f, 0x44c1, 0x3b57, 0x4094, 0x41ed, 0xb208, 0x3d9b, 0x0060, 0xb0d1, 0x4352, 0x2507, 0x4282, 0x4273, 0xa9fa, 0xa561, 0xb2b5, 0x3a86, 0x4076, 0xa2e9, 0xbfc3, 0x2a84, 0xbad6, 0xba74, 0x4361, 0x074c, 0x4169, 0xba42, 0xb086, 0xb070, 0xb23e, 0x3b35, 0x387f, 0x4303, 0x1fb5, 0x4267, 0xba3a, 0xbade, 0xbfa8, 0x41aa, 0x41e6, 0xbf80, 0x43fb, 0xbf90, 0x1deb, 0x43bc, 0x4019, 0xb21d, 0x409f, 0xb24c, 0x197a, 0x4264, 0xb2c1, 0xba06, 0xba7d, 0xb00e, 0x43e2, 0x4624, 0xabe7, 0x4348, 0x42b3, 0xbfc5, 0xb209, 0x40ef, 0x43e5, 0x3ada, 0x3431, 0x414a, 0x41de, 0x136c, 0xb01d, 0x413b, 0xba68, 0xbfa0, 0x1ecb, 0x402f, 0x43df, 0x43ca, 0x1de1, 0xbf34, 0x1d54, 0x2ffe, 0x43bf, 0x1dcb, 0x41fd, 0x438d, 0xb2c1, 0xbf8c, 0xaf57, 0x46d3, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x83fd023f, 0x982a2ada, 0x932883b1, 0x8b07a16b, 0x6509d713, 0x387a315c, 0x306e7acf, 0x817d06d5, 0xc0872b3a, 0x0acac3f9, 0x3a05fe03, 0x78554f05, 0x868e89dd, 0x6a74d709, 0x00000000, 0x400001f0 }, + FinalRegs = new uint[] { 0x80000000, 0x00000000, 0xffffff4a, 0x0000040e, 0xffffff4f, 0x00800000, 0x5a9b0180, 0x00000000, 0x78554db0, 0x8204e12c, 0x3a05fe03, 0x3a05fe03, 0x3a05fe03, 0x6a74d1d9, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x3ec5, 0x42d3, 0x06b4, 0xbf80, 0x430d, 0x40b3, 0x3a02, 0x43fd, 0x1ef3, 0x4051, 0x426b, 0xa108, 0xbf48, 0xba7c, 0xba08, 0x2831, 0xb055, 0xbad1, 0xb278, 0x0b30, 0xbff0, 0xa224, 0x1beb, 0x24d4, 0x1873, 0xb2c9, 0x44d9, 0x1eb9, 0x4086, 0xb2b6, 0x4172, 0x40e2, 0x1f7c, 0x403a, 0xbf9c, 0xa542, 0x19da, 0x429c, 0x42c9, 0x431d, 0x1d07, 0xb059, 0x3791, 0x306d, 0xa7cc, 0xb28a, 0x43c8, 0xbf62, 0xb02d, 0xba06, 0x1b78, 0x21ea, 0x41b6, 0xbfae, 0x0ca8, 0x1ae7, 0x4307, 0x39e3, 0x1e5e, 0x03f8, 0x04c8, 0x427d, 0x0676, 0x3256, 0x1b57, 0xb080, 0xb2cb, 0x418d, 0xb2ae, 0xbf51, 0x412f, 0xba3c, 0x1916, 0x4198, 0x40da, 0x3425, 0x43bb, 0x4339, 0xbf59, 0x42ff, 0x3058, 0x4108, 0xba09, 0x1c3b, 0x1f5b, 0x3703, 0x404a, 0x4259, 0x40b3, 0x40a8, 0x3e59, 0xa7ab, 0xbf66, 0x404f, 0x42bc, 0xbaf0, 0x22c3, 0xbfc0, 0xb03d, 0x4018, 0x1a56, 0x4383, 0x4446, 0x42c1, 0xb292, 0x246e, 0x4426, 0x1aec, 0x1fa7, 0xbf07, 0x3231, 0x4116, 0x1177, 0x45c6, 0x3f2b, 0xa87c, 0x4170, 0xba41, 0x436e, 0x4012, 0x4553, 0xadc9, 0xae57, 0x4034, 0xbaf0, 0xa094, 0x293a, 0x412d, 0x4145, 0xbfc7, 0xb0ed, 0x164e, 0x2376, 0x19ae, 0x404e, 0x338a, 0x42a5, 0xbad0, 0xb275, 0x4217, 0x40c3, 0x40a8, 0x4015, 0x1ce6, 0xb2f5, 0x435d, 0x403b, 0xaaed, 0x41e0, 0x1b07, 0x4390, 0x08eb, 0x2e05, 0x41c0, 0x193f, 0x421f, 0x2648, 0x4074, 0xa3c0, 0xbf0d, 0x0eb2, 0x29cd, 0x403b, 0xafe2, 0xba0a, 0xbff0, 0x40db, 0x1f18, 0xba01, 0xbf90, 0xb0e6, 0xb096, 0xbaf6, 0xbf90, 0x18a5, 0xbfbb, 0x40eb, 0x1d3b, 0x4150, 0x1993, 0xbf9d, 0x413c, 0xb2e0, 0x1b5d, 0x415a, 0xb244, 0x0152, 0xb2ed, 0x19ce, 0xba30, 0xba2e, 0xb277, 0xb271, 0x0387, 0xa74d, 0x4088, 0x4141, 0x4226, 0xbf5c, 0x46e2, 0x0897, 0x41c1, 0x428a, 0xb272, 0x1e00, 0x42d3, 0xb297, 0xba46, 0x40e1, 0x18fb, 0xbf0b, 0xba08, 0xb222, 0xbfb0, 0xbacf, 0x1a87, 0x428a, 0x42ee, 0x414c, 0x4606, 0x4375, 0x45db, 0x4171, 0x40f1, 0xb297, 0x4133, 0x3d66, 0x4206, 0x43ec, 0x43f8, 0xbf2e, 0xbfc0, 0xba27, 0x43ef, 0x407f, 0xbaef, 0x4306, 0x4350, 0x1e3c, 0x4085, 0xb25f, 0x4039, 0xb2dd, 0x40e1, 0x462b, 0x4633, 0x4048, 0x17ca, 0xba44, 0xbfa3, 0x4208, 0xb282, 0xba26, 0xb2da, 0x00d2, 0x4151, 0x40f7, 0x42e9, 0x4203, 0xba5a, 0x203f, 0x465b, 0xb20a, 0xbfdc, 0xa24b, 0x2188, 0x144b, 0xbfc0, 0x4001, 0xbf74, 0x41c4, 0x15cf, 0x42fc, 0x4269, 0xaad7, 0x4353, 0x308d, 0xb27f, 0x4242, 0x4276, 0x43b6, 0x4243, 0xa999, 0xba34, 0xa8cb, 0xbfe8, 0xb0e3, 0x40c0, 0x402e, 0xb22d, 0xb06b, 0x1b53, 0xb0bb, 0x447f, 0x459a, 0x4083, 0x31f0, 0xb018, 0x3814, 0x43e1, 0x1c61, 0x417d, 0xa3ab, 0xbac9, 0x0d83, 0xa3e9, 0xbfcb, 0x1c34, 0x0847, 0x012c, 0x34c5, 0x412f, 0xb21c, 0x4286, 0xb24c, 0x40eb, 0x1dc0, 0x4006, 0x437d, 0xb01b, 0xbf45, 0x38bb, 0x4092, 0x41ab, 0xb283, 0x424c, 0xb036, 0xa4ec, 0x1f66, 0x1f00, 0xbac8, 0xbf8a, 0x42d7, 0x42aa, 0x4140, 0x09ec, 0x183a, 0x1e06, 0x405c, 0xb05b, 0xb0ef, 0x1d87, 0x1807, 0x41bf, 0x411c, 0x4168, 0xbf19, 0xb050, 0xad8f, 0x1cd4, 0x0d12, 0x411f, 0x4071, 0x187d, 0xa451, 0x4354, 0x4170, 0x17d0, 0x419b, 0x41ca, 0x4293, 0x1d2e, 0x41e0, 0x4131, 0xba25, 0xbf4c, 0x4313, 0x22f1, 0xbf91, 0x4601, 0xb2d8, 0x39d6, 0xb2d3, 0x1e51, 0x46c0, 0xba66, 0x2408, 0xb0aa, 0xb0b7, 0xbf5d, 0xbfc0, 0x438d, 0x24bb, 0x23a9, 0x4285, 0x1d19, 0x431b, 0xa1e4, 0x43b2, 0x1015, 0xbad8, 0x408d, 0x40c5, 0x401d, 0xbf77, 0xb0a3, 0x40d9, 0x3dd4, 0x4291, 0xb2ad, 0x4611, 0x4243, 0x42d3, 0x4274, 0x430f, 0x1f8f, 0x4458, 0xbf43, 0xbaf1, 0xb05f, 0xba34, 0xb2bd, 0xb2f2, 0xa7ef, 0x4368, 0xb2b9, 0xbfae, 0xbf00, 0xbad8, 0xbaf8, 0x40f2, 0x403e, 0xbf3d, 0xbaff, 0x4352, 0xb0b8, 0x414a, 0xa4e6, 0x1278, 0x419d, 0x41be, 0x1ef6, 0x4611, 0xb240, 0x4300, 0x4151, 0xba3a, 0x2800, 0x40c0, 0x40f1, 0x433b, 0x1e6b, 0xbf83, 0x40e0, 0x43cc, 0xb089, 0x42ff, 0xafd5, 0x4288, 0x1bd6, 0xbfc0, 0xb2e9, 0x403b, 0x40e1, 0x1ff6, 0x408d, 0xb284, 0x40f6, 0x41eb, 0x1c1c, 0x411c, 0x439d, 0x4546, 0x418d, 0xb288, 0x4026, 0x31bb, 0xbfb9, 0xb032, 0x421e, 0x0492, 0x4349, 0xac8e, 0xb272, 0x40d2, 0x4017, 0xbf90, 0xbf00, 0x2e71, 0xba43, 0x421b, 0x4094, 0x4223, 0xbf49, 0xb2a3, 0xb286, 0x02b5, 0x424a, 0x426f, 0xb21c, 0xb277, 0x4137, 0x43fe, 0xbf4d, 0x418a, 0xba41, 0x05a2, 0x4005, 0xbf34, 0x4591, 0xb209, 0xba3e, 0x418c, 0x4190, 0xb2ab, 0x4243, 0x04ac, 0x40b1, 0x3fd9, 0xbfcf, 0x1bd4, 0xb2b3, 0x42ba, 0xb0e0, 0x1cf2, 0x3fc6, 0x042d, 0xbf26, 0xba72, 0xa972, 0x018f, 0x42e3, 0xab4b, 0xb253, 0x40a4, 0x43bc, 0xbad4, 0x444a, 0xb298, 0x4095, 0x05a4, 0x3e20, 0x41dc, 0xba4d, 0xb29a, 0x4210, 0xba2d, 0x40e4, 0x3b94, 0xbfe0, 0x405d, 0x43cc, 0x4282, 0xbfca, 0x4296, 0x41d1, 0x4319, 0xba33, 0x417f, 0xbfd9, 0xb24d, 0xa08c, 0x405c, 0x4397, 0x4201, 0xb2ff, 0x2561, 0x00f1, 0x3ee1, 0x43d3, 0x4177, 0xb289, 0x407f, 0xbacb, 0x30e3, 0x37a9, 0xba34, 0x432c, 0x43d7, 0x4017, 0x428b, 0xa7e4, 0x42be, 0xbf3c, 0xba26, 0xb25c, 0x414c, 0x3494, 0xaeb7, 0x42f3, 0x1325, 0xbf96, 0x089b, 0xbf60, 0x4147, 0x0189, 0x42fc, 0xbfa4, 0x4137, 0xbac5, 0x1f1c, 0x42ea, 0xb2a0, 0x39d2, 0x1068, 0xb2a7, 0x4371, 0x4086, 0xbad1, 0x418e, 0x211d, 0x1284, 0x0ab4, 0x359e, 0x4161, 0x1eab, 0x0245, 0x430b, 0x42ea, 0xba51, 0x4082, 0x43a9, 0x03f5, 0xa5cb, 0x1316, 0xbf36, 0x4611, 0xbaee, 0xba0a, 0xbfd2, 0x409c, 0xb091, 0x43b3, 0x4248, 0x426e, 0xba02, 0x0db8, 0x1a5b, 0xbfb5, 0x43b0, 0xb2ad, 0x09aa, 0x1205, 0xb266, 0x415b, 0x434d, 0x1fe3, 0xb0a3, 0xbf93, 0x4559, 0x4439, 0x212b, 0x3af6, 0x189c, 0x1be0, 0x4248, 0xb285, 0xbf96, 0x41c3, 0xb254, 0x0c55, 0x4274, 0x3ed3, 0x431c, 0x407a, 0x43fe, 0x1e2d, 0x18a3, 0x4126, 0x42ad, 0x06fe, 0x29b3, 0x144b, 0x4051, 0x4114, 0x1f76, 0x1e83, 0x1855, 0xbadb, 0xbf34, 0xb291, 0xb2d4, 0xb253, 0x414e, 0xb282, 0xba7a, 0x41f3, 0xba13, 0xb20a, 0xbf36, 0x428d, 0x1d7b, 0xbad0, 0xb2bf, 0xb254, 0xb0a6, 0xb078, 0x4053, 0x43d9, 0x4162, 0xb274, 0x41fd, 0x184a, 0x4399, 0x2dd4, 0x4307, 0x40f4, 0xba36, 0x43c2, 0x4197, 0x436c, 0x4203, 0x1fbe, 0xbf31, 0x408d, 0xb2ee, 0x4226, 0x414e, 0xbf3a, 0x23bf, 0x46c9, 0x3892, 0xba42, 0x41b5, 0x1b55, 0x4282, 0x401d, 0x1d0c, 0x1a9c, 0x257a, 0xb2b4, 0x42c1, 0x4201, 0xb23b, 0x43a1, 0x43bd, 0x407e, 0x43d6, 0x1b28, 0x13fb, 0x4064, 0x1cb2, 0x314e, 0x2ae2, 0xba18, 0xbf0b, 0xa9ec, 0x4165, 0x43d1, 0xb2ab, 0x1879, 0xb0fe, 0xb290, 0x380d, 0xb22c, 0x1d2e, 0x17c1, 0x4011, 0x43f2, 0xbf87, 0xba26, 0xb293, 0x1854, 0x34a5, 0xb2f5, 0x43c1, 0x2ea9, 0xa962, 0xb266, 0x0e46, 0xb055, 0x412d, 0x43e8, 0x419a, 0xb23e, 0x1b36, 0x180b, 0x434f, 0x1a50, 0xbac0, 0xbaf0, 0x09f6, 0x18d9, 0xba51, 0xb2ee, 0x32fe, 0x403b, 0x1db0, 0x0fa6, 0xbf97, 0xb26b, 0x4315, 0x42a2, 0xba2a, 0x431b, 0x1a42, 0x1f3a, 0xbf4e, 0x40cc, 0x41da, 0x4109, 0x422d, 0x1bd6, 0x439f, 0xb203, 0xbfd1, 0x462d, 0x404b, 0x420b, 0x4298, 0x153f, 0xb0c7, 0xba66, 0xb258, 0xb27a, 0x417e, 0x1ca0, 0x46f9, 0x0c87, 0x401d, 0x42e3, 0xbacc, 0x2a1c, 0x21ff, 0x4187, 0xb001, 0x4054, 0xbae9, 0xbf98, 0x411c, 0xb27f, 0x2f39, 0x42cc, 0x4602, 0xb07f, 0xb21b, 0x418b, 0x03f0, 0xb021, 0xb03f, 0xbf5a, 0x410c, 0x40ac, 0xa162, 0x4021, 0xba5b, 0xa977, 0x204c, 0x43f6, 0x40a9, 0x1e9c, 0x4283, 0xbf2d, 0x2abd, 0x4249, 0xba02, 0xa9c6, 0x4008, 0x4424, 0x2c94, 0xbf61, 0x18ae, 0xb07d, 0x46c3, 0x185f, 0xb286, 0xb2b7, 0x1ada, 0x4147, 0x403a, 0x2702, 0x4065, 0x404c, 0x4274, 0x42aa, 0x434f, 0xb0a5, 0x4315, 0x0ac8, 0x19eb, 0xa9ea, 0x2541, 0x460f, 0x4634, 0xbf25, 0x436c, 0x42a5, 0xb256, 0x425f, 0xb20d, 0x4225, 0x4669, 0x23f8, 0xab09, 0x43c7, 0x34cf, 0x0f49, 0x415d, 0xba25, 0x4230, 0xac19, 0xb243, 0x42d7, 0x42e5, 0x182d, 0x424d, 0x4213, 0xba2c, 0x1ad0, 0x4301, 0x2446, 0x2b9e, 0xabb5, 0x426d, 0xbfad, 0xa4d2, 0x41c1, 0x1de8, 0x28af, 0xb252, 0x18f0, 0x342f, 0x2d6e, 0xb240, 0x427e, 0xbf5a, 0x41c0, 0x446a, 0x1a36, 0xb223, 0x1b1a, 0x4319, 0xba13, 0x4332, 0x4293, 0x1cc3, 0xb092, 0x4339, 0x41b6, 0x21e9, 0xb237, 0xbaeb, 0xbf06, 0x1d64, 0xa016, 0x41ec, 0x413a, 0x1abc, 0x42fc, 0xb274, 0xb0bc, 0x4016, 0x42d6, 0xb220, 0x43b0, 0x44fa, 0x44cb, 0xbf1c, 0x1ed5, 0x4229, 0x45f4, 0xbf67, 0x4142, 0x1e8e, 0x434c, 0x418e, 0xb0fb, 0x4369, 0x46b2, 0xae1c, 0xbfa6, 0xb294, 0xba4c, 0x0ff8, 0x149e, 0x4201, 0xba72, 0x44a9, 0x465f, 0x42c9, 0xbad2, 0x3fc6, 0x4379, 0x1969, 0x417e, 0x409b, 0x46e8, 0x40a2, 0xba58, 0x1c75, 0x42f0, 0x40ba, 0x12ae, 0x32c5, 0x348d, 0x402a, 0x2812, 0x40e9, 0x41b1, 0xbf89, 0x1914, 0x41ac, 0x07c5, 0x406e, 0x43a2, 0x42f0, 0x1150, 0x3395, 0x3055, 0xbfb6, 0x00f6, 0x40d4, 0x1c99, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x12c1cca9, 0x2d72bbb5, 0x1bcf8ad0, 0x4c840915, 0x42730230, 0xb7a8e9c4, 0xcb0a2f1b, 0xf9eb0e6a, 0x3ce2afc4, 0x2182885a, 0x1ed96c99, 0x0cf55f95, 0xb8e6e008, 0xecaa7d5b, 0x00000000, 0x100001f0 }, + FinalRegs = new uint[] { 0x00000055, 0xfffcc2a2, 0x00000001, 0x00000795, 0x79854540, 0x0cf57523, 0x00033d5d, 0x0cf57521, 0xecaa795b, 0x0000164f, 0xffffff17, 0x0cf575e7, 0xb8e6e008, 0xecaa795b, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4393, 0x413f, 0x4166, 0xa433, 0x409c, 0xb2e8, 0x1cc5, 0xba5a, 0xaf1d, 0x4249, 0x158a, 0x400c, 0xbf02, 0xba4d, 0x1b2a, 0x0683, 0x409b, 0x4033, 0x2766, 0xbf00, 0x43bc, 0x4593, 0x4623, 0x40f0, 0xba2d, 0x433c, 0xbfcc, 0x0712, 0xa0ee, 0x301a, 0x400e, 0x4218, 0x428a, 0xa5a0, 0x4362, 0xb28f, 0x4270, 0x3fd6, 0x40b4, 0x2c32, 0x4494, 0xb0e0, 0xba73, 0x2cfd, 0x4118, 0xba2a, 0xb24c, 0x2d2c, 0x1a18, 0x00d8, 0xbacb, 0x4396, 0xbf0b, 0xb244, 0x1eb5, 0x4154, 0xbacc, 0xb258, 0x1b71, 0xbfb7, 0xb2f8, 0xb0c5, 0x01d6, 0xbad7, 0x240f, 0x1dd7, 0x42f5, 0x4691, 0xbfb0, 0x1ca2, 0x15b5, 0x3be3, 0x4127, 0x4122, 0x403e, 0x4065, 0x4314, 0x1a38, 0x1e93, 0xba0d, 0x1ea0, 0xbaf7, 0xa3e8, 0x42c5, 0x1f42, 0xbf1b, 0x40c5, 0x461d, 0xb013, 0x40a2, 0x1903, 0x4388, 0xb090, 0xbfc1, 0x42fe, 0x42a9, 0x4353, 0x41c2, 0xba5c, 0x406b, 0x4351, 0x41ba, 0xbfe0, 0xb254, 0x1057, 0x41ba, 0xba06, 0xb259, 0x067d, 0x40f4, 0xba18, 0x45b0, 0x3200, 0x405a, 0xba03, 0xbfde, 0x4127, 0x4379, 0x400c, 0xb20e, 0x0f5d, 0x41b4, 0x1f75, 0x416d, 0x413c, 0x1d18, 0x3c86, 0x41e6, 0x1d1e, 0xb281, 0x1f3c, 0x4218, 0x40f8, 0xbf29, 0x286f, 0xa620, 0xb2bb, 0x0150, 0xbac4, 0xac02, 0x439c, 0xb0fa, 0x40df, 0x1f98, 0x4316, 0x3d6d, 0x414e, 0x4159, 0xba17, 0xbfb1, 0x1a85, 0x4214, 0x423d, 0x0134, 0x4669, 0xb254, 0xbf95, 0xb2ed, 0x428b, 0x4328, 0x4256, 0x12d9, 0x40cc, 0xb23e, 0xb025, 0x42fe, 0x4022, 0xb0da, 0x42a9, 0xad61, 0x1f53, 0x46f9, 0x4462, 0xba7b, 0x3985, 0x4142, 0xa656, 0x21c4, 0x4255, 0xba64, 0xb20b, 0xa593, 0x43be, 0xbf8f, 0x463b, 0xb038, 0xb030, 0x4055, 0x17ca, 0xbacb, 0x1f67, 0x4055, 0x1923, 0x1a46, 0xb2a0, 0x41af, 0x4285, 0xbfa4, 0x3160, 0x42c8, 0x29ec, 0x1c50, 0x412c, 0xbaec, 0xb225, 0x44d4, 0xba56, 0xb23d, 0xbfdb, 0x275b, 0x415f, 0xb0ea, 0x21fb, 0x40d9, 0x4325, 0xb20c, 0x43d8, 0xa916, 0x4239, 0x438b, 0x418f, 0x4275, 0xb25f, 0x2252, 0xb262, 0x4688, 0x1c27, 0xb234, 0x19e4, 0x41b6, 0xbfb4, 0x3226, 0xa8bd, 0x40b1, 0xb24f, 0x1848, 0x436f, 0x4372, 0x436f, 0x426f, 0xb2b6, 0xba5a, 0xb2c6, 0x2bac, 0x4160, 0x4002, 0x40b0, 0xba46, 0x319b, 0xb251, 0x2a9a, 0x428b, 0x4239, 0x28d5, 0x46e9, 0x4030, 0x4167, 0xbf2e, 0xbf00, 0x41cc, 0xb2e1, 0xba20, 0x4080, 0x1179, 0x4484, 0xbaf4, 0xbf48, 0xb2f8, 0x4320, 0x426b, 0xb2ee, 0x350c, 0xb241, 0x4386, 0x3af4, 0x42b4, 0xa8e5, 0x4369, 0x4315, 0x4110, 0x407b, 0xbaff, 0x419e, 0xbf52, 0x40f2, 0x45b5, 0x41ec, 0x402a, 0xb21b, 0xbf6c, 0xb0ad, 0x40cd, 0x4094, 0xb07c, 0x1fbb, 0x286b, 0x422d, 0x1f2b, 0x401a, 0x42aa, 0x406b, 0x4473, 0x41d8, 0xb08d, 0xba59, 0xb03e, 0x1d7d, 0xaf13, 0x45e8, 0x4169, 0x418a, 0x4366, 0xb001, 0x3913, 0x40ef, 0x40fa, 0xbf74, 0x1bcd, 0x42e7, 0x40cf, 0x1959, 0xb20a, 0xb2ec, 0x1261, 0x40ab, 0x4062, 0x34eb, 0x40fb, 0x4255, 0xb052, 0x40e4, 0x414f, 0x4241, 0xb21f, 0xb2f9, 0x2894, 0x4546, 0x2e67, 0xbf0a, 0x2dcd, 0x3df4, 0x1431, 0x426d, 0x162d, 0xbfd8, 0x4255, 0xbaf2, 0x4263, 0xa5ca, 0x43e9, 0xb06b, 0x2b34, 0xba1b, 0xbf60, 0x42bc, 0x0a25, 0x4584, 0x40f2, 0xb07f, 0x3185, 0xb0b6, 0xbaf2, 0xbf86, 0xafbf, 0xb2a6, 0x10ea, 0x4379, 0x43ec, 0x4291, 0x2f80, 0xb2a8, 0x400b, 0xb2a7, 0x4007, 0xb23c, 0x454b, 0x40ab, 0xbad0, 0xbf84, 0x43ab, 0x4252, 0x40c8, 0x400f, 0x43ae, 0xa451, 0x458e, 0xbae0, 0x4683, 0x412d, 0x3ea7, 0x2233, 0x4564, 0x3aa8, 0x421a, 0x1fc8, 0xa2ab, 0xba49, 0x18b2, 0x2a32, 0x40b7, 0xb25e, 0xba1f, 0xb025, 0xb2d7, 0x41f2, 0xbfe1, 0x1fa1, 0xb0a2, 0x43bd, 0x1812, 0x0fdf, 0xba2f, 0x42e5, 0xb238, 0x197b, 0x1d3f, 0xbfa1, 0x1def, 0x1a08, 0x41f5, 0x352c, 0xb021, 0x1891, 0x004f, 0x427c, 0x402a, 0x41fb, 0x4303, 0x4396, 0x3d52, 0xbac4, 0xba78, 0x40b0, 0x1d86, 0xb249, 0x40c1, 0x4030, 0xbac9, 0xbf3a, 0x412d, 0x4335, 0x3fe3, 0xb2f3, 0x0403, 0xb24c, 0x4194, 0x402f, 0x4245, 0x43a7, 0x4298, 0x2c2c, 0x1d25, 0x0a9b, 0xba72, 0x41f4, 0x4083, 0x1161, 0xba17, 0xac8c, 0x4595, 0x42b2, 0x4457, 0x2b24, 0xba1b, 0x4135, 0xb0ba, 0xbf14, 0x41e8, 0x2741, 0x1bcb, 0x3afa, 0x438c, 0xbf36, 0x4028, 0x40d6, 0x42dd, 0xb2cf, 0x4334, 0x4390, 0x41f0, 0xbf8d, 0x4576, 0x2bc5, 0x46b2, 0xa44d, 0x438e, 0x2439, 0xb2c1, 0x181c, 0xa1d4, 0x4448, 0xbf67, 0x4360, 0xb00a, 0xbfa0, 0x423f, 0x32f8, 0x3dbe, 0x4301, 0x43aa, 0x1ba3, 0x4258, 0xbf80, 0x41df, 0xb242, 0x1e9a, 0x0f0a, 0x389a, 0xb2fa, 0x4220, 0x401f, 0x4561, 0x1a77, 0xbf5b, 0xb2e7, 0x1879, 0x4132, 0x46db, 0xb2e1, 0x1aa2, 0x4100, 0x25d0, 0x403b, 0x13ba, 0xbf69, 0x4238, 0xba3f, 0xa5ad, 0x1b87, 0x4406, 0xa157, 0x006d, 0xbfc0, 0x4349, 0x16fb, 0x18ca, 0x406f, 0xb049, 0x43cb, 0x435e, 0xabbe, 0xba66, 0xbf8f, 0x29de, 0xbfe0, 0x35d1, 0x21e8, 0xb2b1, 0x4019, 0x1f16, 0xb07f, 0x4124, 0x437b, 0x324a, 0x4641, 0x4192, 0x40cb, 0xbf5b, 0xb257, 0x1a8b, 0x0f49, 0x0eeb, 0x4244, 0x4038, 0x4499, 0x43ce, 0xbf18, 0x1b06, 0x1fb0, 0xbfa0, 0x2f5c, 0x44dd, 0x4381, 0x4245, 0xb223, 0x4260, 0x1c09, 0x12f5, 0x0433, 0x4285, 0xbf48, 0x43fa, 0x05a8, 0x3557, 0x42ed, 0xb03e, 0xba76, 0x4124, 0x1ab2, 0xbf95, 0x4010, 0x4581, 0x459a, 0xb229, 0x0b3c, 0x4452, 0xa755, 0x4476, 0xb066, 0x13df, 0xb0b4, 0x41f9, 0xb2ac, 0x432a, 0x409f, 0x2410, 0xba48, 0xbade, 0x4291, 0x085f, 0xb2dd, 0x4346, 0xbfc6, 0x4069, 0x408a, 0x45ed, 0x4370, 0x2b4f, 0x463d, 0x1f7a, 0xba33, 0x4119, 0x4119, 0x4277, 0x08b3, 0x24d8, 0x018b, 0x1dd1, 0x4190, 0xb259, 0x408e, 0xb094, 0x434b, 0xa16e, 0x417b, 0xb28c, 0xbf92, 0x1ae1, 0x14f3, 0x1b4a, 0x4549, 0xbf48, 0x3edd, 0x0f59, 0x3f27, 0x18da, 0xb2ee, 0x412b, 0xa606, 0xba3a, 0xb236, 0x41c3, 0xb024, 0xb0d3, 0x40f2, 0x4301, 0xb0eb, 0x40a1, 0x284b, 0x40bd, 0x1e28, 0x41d3, 0x4243, 0xbfca, 0xa24e, 0x4105, 0x0a58, 0x1fd6, 0x41f4, 0xbfc8, 0xb268, 0x42ac, 0x413b, 0xb285, 0xb0d4, 0x400b, 0x43e2, 0x3133, 0x4316, 0x4319, 0x4398, 0xb27b, 0x2780, 0x4343, 0x17d6, 0xb242, 0x1282, 0x1f3e, 0x4285, 0x42a2, 0xb0ed, 0x40a5, 0x439a, 0x1c5f, 0x1d3b, 0xb032, 0xbfbd, 0xb26f, 0x42e2, 0x4350, 0x4353, 0x1d6d, 0xb2af, 0x40ad, 0x41e6, 0x4080, 0x43b6, 0x4064, 0x4007, 0x40a8, 0x4302, 0x11e1, 0x41dd, 0xb2cd, 0x4350, 0xba24, 0x4208, 0xb24d, 0x1c89, 0x4262, 0xba71, 0xbf89, 0x4357, 0x42ef, 0xb2d6, 0xb26e, 0xb208, 0xb275, 0xbf1d, 0xb2ee, 0x1944, 0xb2ce, 0xba7f, 0x36b1, 0x388f, 0x40ce, 0x4368, 0x19b9, 0x01c7, 0x24f5, 0xba7b, 0x4267, 0x4143, 0xbf32, 0x4205, 0xba59, 0x40c0, 0x3dbe, 0x46d5, 0x1ac7, 0x4195, 0x41db, 0xb2bb, 0x43c4, 0x1fcd, 0x42f1, 0x42d8, 0x1df1, 0xb2fa, 0x41d5, 0xbf0c, 0x41a8, 0x4201, 0xa7e8, 0xbf02, 0x42d1, 0x4116, 0x1c10, 0x43f6, 0x40df, 0x0a6b, 0x41ac, 0x42f3, 0xb2b6, 0x43bb, 0x1010, 0xba5d, 0x43c6, 0xb022, 0x3144, 0x41f9, 0xac9e, 0x43b2, 0x3daf, 0xa1b3, 0x19fa, 0xbfae, 0x42b3, 0x467d, 0x4000, 0xaa59, 0xba2c, 0x090f, 0x1860, 0xb022, 0x2209, 0x1d3a, 0xba15, 0x4138, 0x43fb, 0x424b, 0xbaf1, 0x41fd, 0xba49, 0x461b, 0x41b4, 0xae22, 0xa412, 0x1d38, 0xbf4a, 0x4239, 0xb25f, 0x258a, 0xab14, 0x4021, 0x33b7, 0x400d, 0x44eb, 0x439c, 0x1ff6, 0x4056, 0x414b, 0x18e1, 0x34b5, 0xb23e, 0x465d, 0xa16f, 0x41a9, 0x1047, 0x2a0d, 0x290c, 0xa552, 0x403b, 0xbf43, 0x46d2, 0xb07c, 0x42ca, 0xb007, 0xbae7, 0x45e5, 0x41aa, 0xa900, 0x4148, 0xb203, 0x411a, 0x44d0, 0xb236, 0x40bf, 0x2b7a, 0xbfd1, 0x423c, 0x4323, 0xba6d, 0x4325, 0x45b0, 0x4173, 0xbf46, 0xbada, 0x430e, 0x42d7, 0x415f, 0x4335, 0x445e, 0x4038, 0x4170, 0x4041, 0x422f, 0x4206, 0x4306, 0xba01, 0xb080, 0x0116, 0x1e6f, 0x31c7, 0xb210, 0xba4a, 0xb26a, 0xb2a4, 0x3127, 0x4140, 0xafb4, 0xb28c, 0xb279, 0xbf31, 0x4456, 0x40c0, 0x344c, 0x43eb, 0x0763, 0x042b, 0x4160, 0x0f4f, 0x4484, 0xa6f6, 0x4115, 0x436f, 0x3b87, 0xb283, 0x39a6, 0x4013, 0x2aca, 0x414d, 0x4008, 0xb29b, 0xbfe4, 0x466c, 0x43db, 0x42c5, 0xbac7, 0x22cb, 0x4678, 0x4017, 0xb218, 0x1bfe, 0x43a6, 0xb2e2, 0x1b61, 0x4423, 0x2a87, 0x430c, 0x1d9a, 0xb206, 0x425d, 0xb21c, 0xbaeb, 0x1d87, 0x4123, 0xb030, 0xbfc8, 0xb25a, 0xa621, 0x16b0, 0xba19, 0x411f, 0x43b7, 0x26fe, 0x41c8, 0x419f, 0x40cc, 0xa286, 0xba7d, 0xbf4e, 0xb2a6, 0xb21e, 0x19e8, 0x22f9, 0x43e2, 0x1b9a, 0xb0d5, 0xbae9, 0x403a, 0xb2d7, 0x420a, 0x4345, 0xbfd8, 0x434e, 0x2382, 0x40dc, 0xb2dc, 0x1efb, 0xbfaf, 0xbfb0, 0xbaed, 0xbaea, 0x4249, 0x1806, 0x4459, 0xba0d, 0x1be8, 0xb2c6, 0x4346, 0x4302, 0x1483, 0x36d3, 0xb25d, 0x1e58, 0x0383, 0x42e7, 0xb0c2, 0x2be8, 0x1172, 0x1f26, 0x40d3, 0x1c1f, 0x42f0, 0x0b5c, 0xbf41, 0xa6e6, 0x4318, 0x3b45, 0x3690, 0x43c9, 0x40c7, 0x43c9, 0xbf38, 0x44f5, 0xbf00, 0x1d4e, 0xb055, 0x40d0, 0x298a, 0x4352, 0xba74, 0xa308, 0x1c72, 0x44f4, 0xb28c, 0xba4a, 0x438c, 0x4208, 0x1ce7, 0x4581, 0xbf93, 0x445f, 0x4041, 0xb06f, 0x40e4, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xd71e5741, 0xc26ca9a0, 0xc815b4b8, 0x2e87799c, 0xd6d7ab03, 0x28643399, 0xee47b812, 0x4d2e6ed2, 0xb0352da7, 0xaedffe24, 0x03886f34, 0xf8a78ddb, 0x3400fdbb, 0x65c78639, 0x00000000, 0x700001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x0388d025, 0x880325d0, 0x000017f8, 0x00000000, 0x00000074, 0x0388d02a, 0x00000003, 0x694ff1d5, 0x65c78249, 0x03886f34, 0x0388b858, 0x7789f72c, 0x038871d4, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x45a4, 0x08a7, 0xa344, 0xba0d, 0xba6a, 0x18aa, 0xb2ed, 0x027f, 0x414e, 0x3b58, 0xb06c, 0x4019, 0x443a, 0x43d5, 0x43d9, 0x3988, 0xb2fb, 0x3731, 0x42e1, 0x4198, 0xbade, 0x1852, 0x414d, 0xb2bf, 0xb272, 0xbfb3, 0x4083, 0x42cb, 0x4084, 0x023d, 0x4218, 0xba3e, 0x434c, 0xb2bb, 0x41ee, 0x1d8f, 0xbf08, 0xb2d7, 0x1ade, 0x20d7, 0x0a8f, 0x4679, 0xba77, 0x4156, 0x44c8, 0xbf0f, 0xba07, 0xb234, 0x4326, 0xb018, 0xb28c, 0x159a, 0xb050, 0x442f, 0xa0b3, 0xb0b2, 0x1cae, 0xbfc3, 0x42bf, 0xb23d, 0x4296, 0x409b, 0x46fa, 0x40c7, 0x41a1, 0xbf0f, 0x44f9, 0x28e2, 0x3304, 0x41f9, 0xbf00, 0x462c, 0x4233, 0x1f2f, 0xbad2, 0x2961, 0x2bad, 0xb0ab, 0xbf60, 0x40d6, 0x1633, 0x4280, 0xbf8f, 0x445a, 0x4398, 0x42e2, 0x40f3, 0xb05e, 0x1e98, 0x467e, 0x11ff, 0x1a98, 0x42c5, 0x4038, 0xb223, 0x1e66, 0xbae3, 0x1dcf, 0x23d7, 0xbfd1, 0x4431, 0x43ee, 0x2d7b, 0x4275, 0x07dc, 0x4320, 0x41ea, 0xb25b, 0x0fe5, 0x4131, 0x4108, 0x4338, 0xba61, 0x42d0, 0x0f3d, 0x1a3d, 0x1c5e, 0x4244, 0x40e1, 0xb094, 0xb207, 0x4499, 0xbf6e, 0xb209, 0x41a2, 0x37d8, 0xb018, 0xa0e3, 0xb217, 0x4626, 0x4466, 0x15ef, 0x433b, 0x1cf5, 0x0846, 0x191b, 0xb203, 0x4396, 0x4217, 0xbf69, 0xb090, 0x436a, 0x1b63, 0x426f, 0x0022, 0x4110, 0x2019, 0xb26d, 0x2962, 0xaf0d, 0x43d9, 0xb246, 0x085b, 0xb049, 0x43df, 0x412e, 0x42a5, 0x027f, 0xbf89, 0xb237, 0x42ae, 0x42a7, 0x36db, 0xb25a, 0x43cf, 0x1d28, 0xb0ca, 0x404e, 0xb2a9, 0x402d, 0x4268, 0xad94, 0xbf32, 0x2cef, 0x40c2, 0xba2e, 0x43fd, 0x4168, 0x43d1, 0xb2a8, 0xba30, 0x1b80, 0x40dc, 0x4035, 0x4676, 0xb08c, 0x4267, 0x409b, 0x462f, 0x408d, 0x3b28, 0x18fc, 0x4344, 0x4016, 0x42b4, 0x41a1, 0xbfdb, 0x4156, 0xa1fb, 0xb2b5, 0x1860, 0xb2aa, 0x033d, 0x42cf, 0x4399, 0x430f, 0x410f, 0xba6d, 0xbf51, 0x41fe, 0x2855, 0x45c1, 0x1740, 0x4321, 0xa3e5, 0x422e, 0x4289, 0x01e5, 0x14b8, 0xaef7, 0x4012, 0x1c72, 0x4261, 0xa856, 0x069e, 0xba61, 0x40c1, 0x0edc, 0x4040, 0xba69, 0x4012, 0x4157, 0xba5b, 0x45d6, 0xba32, 0xb000, 0x1efb, 0x4351, 0xbf36, 0x4548, 0xb0b2, 0x344a, 0xba1b, 0x1ff8, 0x4104, 0x43c9, 0xac71, 0x3da1, 0xb095, 0x423a, 0x410a, 0x43c1, 0x10b8, 0x431c, 0x42c6, 0x4100, 0xbfda, 0x3ae2, 0x46d0, 0x4241, 0xbae3, 0x0dba, 0x4297, 0xaa48, 0x286a, 0x42b0, 0x43d8, 0x00e7, 0x10ae, 0x0281, 0xb013, 0x4556, 0x1b24, 0x4093, 0x2ab6, 0x40cd, 0x41aa, 0x193f, 0x2446, 0x4344, 0x4161, 0xb2e5, 0x42e9, 0xbfb4, 0x25e1, 0x40c6, 0x4320, 0x4214, 0x4185, 0x076d, 0xb273, 0x4149, 0x0f14, 0x43bd, 0x109a, 0x4636, 0x411c, 0x43f5, 0xbfe1, 0x1ed8, 0x437e, 0x40b7, 0x0795, 0x1f94, 0x4678, 0x00c4, 0x0040, 0x1f3a, 0xbaef, 0x096f, 0x3ce4, 0x4061, 0x4241, 0x42ca, 0xb250, 0x458e, 0x308f, 0x45ba, 0x41f6, 0xb22d, 0xb0a2, 0xbaf1, 0xbfb6, 0x0318, 0x0f57, 0xac8a, 0xb2bf, 0xbfd0, 0x438d, 0x1a76, 0x4447, 0x42c0, 0xbfae, 0x182a, 0x1469, 0xa377, 0x1b30, 0xb2f1, 0x1354, 0x42e2, 0xaf4d, 0xa156, 0x40f8, 0xb26a, 0x416e, 0x2cdc, 0x2741, 0xb06b, 0x4322, 0x4689, 0x41d1, 0x462a, 0x1f1d, 0xb297, 0x415d, 0x26de, 0x4089, 0xbf78, 0x4359, 0x42f6, 0xa6c6, 0x19f5, 0x29df, 0x4285, 0x459e, 0xb297, 0x2047, 0xbf4c, 0x0551, 0x1b21, 0xb27c, 0x413a, 0x4369, 0x4052, 0xbaeb, 0x3da1, 0x06a6, 0x40de, 0x4154, 0x18f7, 0x45ea, 0x44e4, 0x43e9, 0xba2e, 0x30a4, 0xaf60, 0x430e, 0xbac3, 0x1af2, 0x4158, 0xb21a, 0xbf2b, 0xbadf, 0x4055, 0x41d9, 0x4018, 0x44cd, 0xbfd0, 0x3e73, 0xa07c, 0xbad5, 0xb2a1, 0x1a51, 0x41e0, 0xbfe0, 0x462f, 0x409f, 0x4095, 0xba69, 0x04c2, 0x426e, 0xbf00, 0x40ae, 0x409d, 0x4312, 0x417f, 0x4312, 0x098f, 0xb0e9, 0xbfa4, 0x416a, 0xb074, 0x44f1, 0x421b, 0xb02c, 0x3394, 0xaced, 0x4636, 0x43b6, 0x2b1c, 0x1f46, 0x1ab5, 0x4235, 0x4030, 0x40e0, 0xba47, 0xb207, 0xb25c, 0x1786, 0xbaf3, 0x25bc, 0x430b, 0x4179, 0x436c, 0xbf7b, 0x4616, 0xb033, 0xa35a, 0x0a2f, 0x433c, 0x434f, 0xb254, 0xb2b7, 0x40a2, 0x140c, 0x1d7a, 0xb0df, 0xbf9b, 0x1396, 0x4323, 0x04ee, 0x1ad3, 0x1df6, 0x4001, 0x0825, 0xa444, 0x4221, 0x2085, 0xbfb0, 0x42fc, 0xa67e, 0x419c, 0xba0a, 0xa828, 0x40ec, 0x28b6, 0x4364, 0x4362, 0x2892, 0x419c, 0xb206, 0xb2f1, 0x1795, 0xbf6d, 0x0f94, 0x43e5, 0x1e8c, 0xa665, 0xb0e9, 0x06c5, 0xb22e, 0xbac4, 0x1f02, 0xba21, 0x0048, 0x43f7, 0x402f, 0xbf69, 0x43e2, 0x39d0, 0x1c2b, 0x0c3d, 0x41f5, 0x40a7, 0x4254, 0x4303, 0xa98f, 0x19bb, 0x27cd, 0x43bc, 0xb219, 0x4208, 0x43db, 0xb0e1, 0x42ac, 0x43c3, 0x400d, 0x4151, 0x45e1, 0x4097, 0xba76, 0x0cb1, 0x420d, 0x42eb, 0x466a, 0xbf9b, 0x409e, 0x46c0, 0x4168, 0xaba2, 0x01b0, 0x358b, 0x42e1, 0xbad1, 0x43e7, 0x415f, 0x42bf, 0xb09d, 0x1b7c, 0x38eb, 0xb212, 0xb298, 0x1b3e, 0x3148, 0xbfbd, 0xb038, 0xb003, 0x40b0, 0x1604, 0xbf5b, 0x45a4, 0x1e35, 0x2f5a, 0x0a27, 0x40ff, 0x42d9, 0x4196, 0x0bf3, 0xb0cf, 0x43c3, 0x14c9, 0x41c2, 0x419c, 0xb05f, 0xba3d, 0x407e, 0x4118, 0xbaed, 0x2f0a, 0x066f, 0x409c, 0x4132, 0x40c5, 0xbff0, 0x4592, 0xbf26, 0x1bfb, 0x1004, 0x439a, 0x41bb, 0xbf5e, 0x4543, 0x4243, 0x1faa, 0x4099, 0xb2c4, 0x1c03, 0xb2a7, 0xb200, 0x42e0, 0x01fa, 0x4083, 0x40dd, 0x19d7, 0xb2a3, 0xb2e3, 0xbf8a, 0x19a1, 0xa995, 0x40ad, 0x4564, 0x1a6c, 0x44b1, 0xbf82, 0x29f0, 0x407f, 0x4032, 0x0d94, 0x43c7, 0x1cd1, 0xbfdc, 0xb02a, 0xba7c, 0xa53e, 0x191e, 0x3274, 0x06ff, 0xbf0d, 0xa643, 0x19ed, 0x1ab6, 0x4292, 0xade2, 0x4138, 0x35a7, 0x42a6, 0x2df3, 0xbfc8, 0x410c, 0x194b, 0x40d9, 0x4350, 0xbac2, 0x42b8, 0xba17, 0x408f, 0x430c, 0xb2b4, 0x425b, 0x4319, 0x423d, 0x42c0, 0xb2cf, 0xb233, 0x4601, 0xba6b, 0x448d, 0x435f, 0x4133, 0x421b, 0xbfd7, 0xb263, 0xba08, 0x4386, 0x40c0, 0xba74, 0xb283, 0x4275, 0x405a, 0xa597, 0x3eab, 0xa03e, 0x4317, 0x4194, 0x46d5, 0x40d6, 0x4626, 0x407a, 0x4618, 0xba4b, 0x4185, 0x1fd4, 0xbac4, 0x4676, 0xbad6, 0xbade, 0x42a0, 0x425b, 0x43ee, 0xbf5a, 0x4137, 0xba4e, 0x40aa, 0x43e6, 0xb283, 0x4295, 0xa6de, 0x418f, 0x4314, 0x0bb1, 0x4278, 0xbaea, 0xb256, 0xb0c2, 0x0164, 0x218f, 0xbfb1, 0x40d5, 0x40d7, 0x19d6, 0x4197, 0x4255, 0x42cf, 0x4220, 0x41f8, 0x455e, 0x42fc, 0x42b1, 0x40e2, 0xb01b, 0xb207, 0x4362, 0x0dc2, 0x2f3b, 0x2d06, 0xb259, 0x46ec, 0x46c9, 0xba27, 0x400c, 0xba15, 0xba27, 0x28db, 0x416d, 0x1f98, 0xbf66, 0x4101, 0x4379, 0x4008, 0x26ab, 0x2a72, 0x14da, 0x4675, 0x40b5, 0xbaf2, 0xb2a0, 0x26d3, 0x44c5, 0x094c, 0x41cc, 0xbad0, 0xb255, 0x40f8, 0xbaf0, 0x41d1, 0x1a2c, 0x19e9, 0xb213, 0x263a, 0x429b, 0x4443, 0x1d60, 0x401d, 0xb2b7, 0xbf25, 0x1edf, 0x437a, 0xb0e0, 0x1fdb, 0x1eff, 0xbf08, 0x40c6, 0x418d, 0xac60, 0x41c8, 0x4311, 0xb056, 0x43fd, 0xba3a, 0x1e0a, 0x1c07, 0x4110, 0xba6f, 0xba05, 0x465e, 0x198e, 0xba67, 0x40eb, 0xbfae, 0xb2af, 0x4272, 0xa551, 0xb29a, 0x43dc, 0x42df, 0xba5c, 0x400b, 0x1e9c, 0xae12, 0x447c, 0x429a, 0x456d, 0x41f1, 0x4650, 0xba05, 0x40b7, 0x4385, 0xb2de, 0x4067, 0xbf2a, 0x4065, 0xb20c, 0xbf80, 0x26f3, 0x4060, 0xb27a, 0xba1e, 0xb2e5, 0x1006, 0xbfd0, 0xb226, 0xb225, 0x430a, 0x412a, 0xbf83, 0x1af2, 0x1ce9, 0x413e, 0xa670, 0x43ef, 0x43c2, 0x24d3, 0x1cdf, 0xba14, 0xbf07, 0x4635, 0x432c, 0x2b26, 0x0b9d, 0x443a, 0x1dff, 0x1813, 0xb232, 0xbf77, 0x4308, 0x0d17, 0x1c3f, 0xba68, 0x1bac, 0x445e, 0xb228, 0x43a3, 0x1d1d, 0x1fa3, 0x0765, 0x421b, 0x4133, 0x422d, 0x4113, 0x4169, 0xba43, 0x404c, 0x416f, 0x423d, 0x442f, 0xba0a, 0xbacb, 0x201f, 0xb2d1, 0x42ec, 0x32d0, 0x419e, 0xbf83, 0xbac6, 0x41a1, 0x09d4, 0x1866, 0x422b, 0x4190, 0x422c, 0x4120, 0x1b77, 0x1aa2, 0x43b8, 0x400c, 0x43c2, 0x20d1, 0xb283, 0x421d, 0x1c4a, 0x4105, 0xba4e, 0xb2d5, 0xb257, 0x420e, 0x1800, 0x434b, 0xb256, 0x4365, 0xbf33, 0x412e, 0x1fb4, 0x18f4, 0x4175, 0x4122, 0x1309, 0x41dd, 0xb2a7, 0x4093, 0x2718, 0x223b, 0xb2cd, 0xba41, 0xbf57, 0x435c, 0xa9d4, 0x40c6, 0x4628, 0xa687, 0x1abd, 0xba66, 0x42ef, 0x1edb, 0xb2a1, 0x4174, 0xac29, 0x1832, 0x3f4b, 0x40c1, 0xba29, 0x428f, 0xa663, 0xbf29, 0x437a, 0x44d5, 0x3e25, 0x1e7c, 0x46d4, 0x42d5, 0x0605, 0x3dfc, 0xb25f, 0x415b, 0xbfc3, 0x26e3, 0x3d32, 0x43a2, 0xb063, 0x4216, 0xba27, 0x4147, 0xbfc1, 0xb203, 0x40bf, 0x404c, 0x4498, 0x28ea, 0xba56, 0x456c, 0xb0f5, 0xbf80, 0x230a, 0xb21b, 0x1e7c, 0x3ee1, 0x435f, 0x1e9a, 0x417d, 0x404c, 0xbf0a, 0xba00, 0x41c0, 0xb21d, 0x4261, 0x1f4a, 0x4043, 0x41ec, 0x438f, 0xb0f5, 0xb275, 0xbfc6, 0xb0e6, 0x2fb0, 0x430d, 0x06b2, 0x435a, 0xa453, 0x46a8, 0x4074, 0x43a7, 0x0363, 0x404d, 0xbaf1, 0xba12, 0x1a9d, 0x420c, 0x447c, 0x41f8, 0x434c, 0xb292, 0xbf18, 0xb2c0, 0x4294, 0x4258, 0x4060, 0xbf9f, 0xb2b3, 0x3a02, 0xae29, 0xba20, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x3cccdcbb, 0xb8b42a16, 0x5515dd66, 0x79ed5e7e, 0x48648228, 0x836897c4, 0x72bc80f9, 0xe9ea3611, 0xd38985d1, 0x943bed5a, 0x921c041b, 0x24eee228, 0x42062aa5, 0xb425effb, 0x00000000, 0x700001f0 }, + FinalRegs = new uint[] { 0x5058e007, 0x00001f30, 0x000000d6, 0x0000301f, 0x07e05850, 0x051ddf28, 0x00001ba0, 0x01000000, 0x0000001f, 0x000010d8, 0x00001080, 0x24eee228, 0x00001080, 0x00001afc, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4037, 0x4485, 0x422f, 0x1977, 0x42be, 0x400d, 0x43d8, 0x4058, 0x423d, 0x2582, 0x1864, 0xbfc1, 0x4207, 0x4056, 0x1ff5, 0xb2e1, 0x4389, 0xac7e, 0x45ac, 0x41ad, 0x408a, 0x429a, 0x1539, 0xba4c, 0xa1e6, 0xba01, 0xba09, 0xba5e, 0x421d, 0x4005, 0x247d, 0x40f4, 0xbf52, 0x42ac, 0xb214, 0xba5d, 0x100c, 0x448b, 0x080c, 0x4371, 0xbac9, 0xb2db, 0x4319, 0x43a1, 0x4219, 0x43fe, 0x422b, 0x3cd5, 0xb080, 0x4107, 0x403e, 0x4196, 0x0c01, 0xb2a8, 0x2e67, 0xb242, 0x439e, 0x42c0, 0x047e, 0xbfaf, 0x1b5d, 0x43ec, 0x406f, 0x11e0, 0xb2c9, 0x2097, 0xba48, 0x43a2, 0xbf23, 0xb213, 0x43d8, 0xba5c, 0x0e20, 0x43e3, 0x4052, 0x43ad, 0x2a92, 0x1b79, 0x421d, 0xba10, 0x4612, 0x4408, 0xba35, 0x411e, 0xb273, 0xb241, 0x43ec, 0xb017, 0x2b0d, 0x41fb, 0x3ce9, 0xb2b0, 0x40b7, 0xbff0, 0x4191, 0xb226, 0x2331, 0xbf3f, 0xb0e7, 0xba0c, 0xb016, 0xba4c, 0x4323, 0x4241, 0x40ad, 0xbf23, 0x426d, 0x4083, 0x1bd2, 0xa309, 0x1e36, 0xa492, 0xb0c1, 0x1ba7, 0xba32, 0xb2e5, 0x4340, 0x43a3, 0x41af, 0x1493, 0x11be, 0xba11, 0x4274, 0xba14, 0xb056, 0x1c8c, 0x2879, 0x4028, 0x077b, 0x406e, 0x3ff9, 0xbf85, 0x4284, 0x46b9, 0xb247, 0xa8d2, 0x409f, 0xb2c5, 0xba69, 0x050c, 0x1b37, 0x1086, 0x4247, 0x40d0, 0xac04, 0x41e5, 0x41b2, 0x40ff, 0xac10, 0x431d, 0xa41c, 0x4147, 0x44c9, 0xb2b5, 0x4333, 0xbf51, 0x46a9, 0x4598, 0x4110, 0x403c, 0xb26d, 0xb0aa, 0x2212, 0x449a, 0x2adb, 0x1f40, 0xb0c6, 0xba43, 0x3c79, 0x4152, 0xba29, 0x414a, 0x4285, 0x01b1, 0x3d68, 0x1ff0, 0x43ae, 0x410e, 0xbf6f, 0xba00, 0xa19f, 0xb071, 0x4007, 0x41f6, 0xb28f, 0x435e, 0x0d65, 0xa66a, 0x41db, 0x42d0, 0xb006, 0xb246, 0x4142, 0xbf00, 0xb28b, 0x0d98, 0xb297, 0xbf24, 0x409d, 0x4090, 0x434a, 0x4085, 0x42df, 0x40f4, 0x4009, 0x41af, 0x4157, 0x416f, 0x423d, 0x4281, 0xb05e, 0x4370, 0xba4f, 0x19da, 0xb206, 0xba78, 0xba32, 0xbfa5, 0x43f3, 0x2473, 0x28a5, 0x42ca, 0xbf78, 0xb229, 0xbaee, 0xba52, 0x4157, 0x40e9, 0xaa96, 0x409d, 0x4090, 0xb269, 0x4361, 0x466d, 0x434c, 0x43ab, 0x45b9, 0xba6b, 0x1c45, 0x18f8, 0xbfc7, 0x0236, 0x42ff, 0x35f7, 0x1e55, 0x4148, 0xb2f9, 0x16a8, 0x417e, 0x402d, 0x40cd, 0xb0a4, 0x313e, 0x225a, 0x1136, 0xa497, 0x45f4, 0xbfb6, 0x418e, 0x4639, 0x1c68, 0x419e, 0x3bee, 0x1b1a, 0xb030, 0x0ae1, 0x40bd, 0x41c8, 0xa186, 0xbf13, 0x35f5, 0x4172, 0xb2a5, 0x433d, 0x41de, 0xba70, 0x4194, 0x4157, 0x1c65, 0x2daa, 0xbfd0, 0xbf07, 0x4303, 0x4226, 0xba1d, 0xb0ad, 0xbf4c, 0x40b3, 0xbad6, 0xafc9, 0x13c7, 0x0dfe, 0xbad5, 0x4008, 0x1b04, 0x1317, 0xba12, 0x40a5, 0x43c5, 0xb045, 0x18e6, 0x414c, 0x0165, 0xb23a, 0x1c08, 0x305f, 0x3885, 0x4310, 0x4179, 0x4093, 0xb236, 0x407c, 0xb096, 0x405f, 0xbf4e, 0x1d98, 0x17f0, 0x41d5, 0x404c, 0x466b, 0x41e0, 0x41c7, 0xbfd0, 0x4302, 0xba29, 0x428b, 0x429c, 0xb01c, 0x2dec, 0x415c, 0x43d0, 0xb29e, 0x0b28, 0x2572, 0x41c2, 0xb23e, 0xbfb3, 0x1fcc, 0xb23b, 0xb29b, 0xb253, 0x4133, 0x42f0, 0x4555, 0xb00e, 0x3f9e, 0x4352, 0xba62, 0x458a, 0xbf0a, 0xb21b, 0xb2f8, 0x2bd1, 0x4347, 0xbfa8, 0x4140, 0x09a8, 0x16d2, 0x42ad, 0x40a0, 0xb29b, 0x056b, 0xa54c, 0x4224, 0xb2d8, 0x4360, 0xba52, 0x4606, 0x1ea5, 0xb20d, 0xb26c, 0x273f, 0x4062, 0x409c, 0x454c, 0xbf7f, 0x2edf, 0x4290, 0xb06e, 0x422c, 0x4244, 0x1f4d, 0xbf64, 0x432a, 0x1595, 0x413c, 0xbf2d, 0xb0c2, 0x4133, 0x41f7, 0x412b, 0x4482, 0x4347, 0xae85, 0x434d, 0x41a1, 0x42e2, 0xbae1, 0x42ed, 0xb29b, 0x4330, 0x42ba, 0x40df, 0x41eb, 0x3c9f, 0x43a2, 0x3e6f, 0x41e3, 0x229a, 0x41e4, 0x1d5e, 0xbfb6, 0x402f, 0xb201, 0x4024, 0xb030, 0x195a, 0xba4d, 0x464b, 0x4025, 0x0420, 0x1a5a, 0xb25e, 0x1a8d, 0x4133, 0x3e2c, 0x15ef, 0x45be, 0x1cb5, 0x0ec0, 0x1d99, 0x1419, 0x430b, 0x4009, 0x0d89, 0x0030, 0xb22e, 0xbfb3, 0xa6c3, 0x1c87, 0x2728, 0x2023, 0xbfb8, 0x1ee3, 0x413e, 0x41c4, 0x4384, 0x4433, 0xb04b, 0x28c5, 0xb003, 0xbf49, 0x0673, 0x1a62, 0xb2e5, 0x316f, 0xb2bb, 0xb2fe, 0x2ed3, 0x1c7c, 0x1b45, 0x41a2, 0xb29d, 0x16cb, 0x4267, 0xb284, 0x4235, 0x42d2, 0x1dff, 0x1d0a, 0x1ca3, 0x414b, 0xadb4, 0x43cd, 0x3db7, 0x410a, 0xb082, 0x1d92, 0x0461, 0xa273, 0x29f0, 0xbf77, 0x0edf, 0x1957, 0x1d7e, 0x4676, 0x4085, 0x43aa, 0xbff0, 0xb0f1, 0x4249, 0x4045, 0xb2ab, 0xad44, 0x41b4, 0x4129, 0xba75, 0x1dda, 0xaf01, 0x1aa6, 0x1e4d, 0xb27c, 0xb2eb, 0xaf98, 0x35e7, 0xbf07, 0x2f6f, 0x44f9, 0xb2b3, 0x1ed8, 0x3699, 0xbae5, 0x2d3b, 0x4017, 0x26dc, 0x2d2b, 0xba0e, 0xbf60, 0x1718, 0xbf00, 0x2c1a, 0x411e, 0x1a13, 0xb2b4, 0x219d, 0xbf07, 0x424b, 0x43bf, 0x4252, 0x098e, 0x4302, 0x250d, 0x4064, 0xb017, 0x3af3, 0x4222, 0xadfc, 0x41e5, 0xbad6, 0xa529, 0x4085, 0x41e1, 0xba1c, 0x4279, 0x43ba, 0x03fb, 0x2279, 0xb224, 0xb28e, 0x2e71, 0xb230, 0x3733, 0xb085, 0x41cb, 0xb2a8, 0xbf77, 0x4113, 0xa77d, 0x403b, 0x1d4f, 0x3e00, 0xa3df, 0xb243, 0xb2b5, 0x4396, 0x19ea, 0xba1c, 0xbfbc, 0xb23e, 0x410e, 0xba2e, 0x405f, 0x1df6, 0x1b93, 0x4148, 0x401a, 0x41ec, 0xb06d, 0x4277, 0x0f45, 0xb2c0, 0x42b2, 0x4326, 0x1426, 0x2c0d, 0xbf47, 0xba26, 0xb296, 0x1907, 0x43a4, 0x407c, 0x406f, 0x4332, 0x2146, 0x403c, 0x4173, 0x43c5, 0x1463, 0x3d72, 0xa539, 0x02d3, 0xbf85, 0xb2e9, 0x080e, 0x3996, 0xb0c8, 0x3133, 0x422c, 0xbaf6, 0x1e96, 0x414d, 0xb236, 0x4071, 0x1b45, 0x41a3, 0x1b5a, 0x04f2, 0xaaed, 0x43d5, 0x40c1, 0x43ee, 0x4631, 0xb0c0, 0x4616, 0x432f, 0x06c9, 0x1dca, 0xbfe1, 0x2a04, 0x106d, 0x40fd, 0x4417, 0xb245, 0x40c4, 0x4279, 0x1ab5, 0x424d, 0x1c8c, 0x2adf, 0xb215, 0x41fb, 0x1014, 0x41bc, 0x43a2, 0x428a, 0xba77, 0x2e89, 0xba5f, 0xb285, 0xb26f, 0x4071, 0x3346, 0xb22b, 0x4350, 0xbf01, 0x403f, 0x42ef, 0x2d94, 0xba29, 0x421b, 0xbf5c, 0x4164, 0x4616, 0x2ad2, 0xb22d, 0x1fea, 0xbf70, 0x42be, 0x409e, 0x42ef, 0x42fa, 0xb28e, 0xba3b, 0xba70, 0xb25a, 0x07ec, 0x405c, 0x40bf, 0x404e, 0x1bc1, 0xba1a, 0xba63, 0xb2e8, 0x3845, 0x4072, 0x1cc5, 0xbf6f, 0x1fd3, 0x1fd9, 0xb2f7, 0x4077, 0x423b, 0x402d, 0x1abd, 0xbf99, 0x4192, 0xbaf0, 0x3122, 0x2e08, 0x4029, 0xbafe, 0xb263, 0x42e5, 0x38e5, 0x43b0, 0xa37a, 0x3088, 0x421d, 0x2339, 0x1d50, 0xb232, 0x2ba9, 0x0059, 0x442f, 0x1e1a, 0x403d, 0x4223, 0x406b, 0xb005, 0xb2e7, 0xbf21, 0x4391, 0xbad2, 0x40bb, 0x04c5, 0x1e4a, 0x424a, 0x415e, 0x0467, 0xbad2, 0x40c5, 0xa1c5, 0x45d3, 0x43d3, 0xb24f, 0x3261, 0xb05b, 0x4364, 0x420b, 0x19f9, 0x2464, 0xbfd7, 0x240f, 0x411a, 0x0a1e, 0xbaf0, 0xb0bb, 0x40fa, 0xb232, 0x4480, 0x401a, 0x40ea, 0xaa93, 0xbff0, 0x0dd7, 0x43ce, 0x4279, 0xba2c, 0xb20b, 0x3d38, 0xbae1, 0xa965, 0x4201, 0x425a, 0x4217, 0x4469, 0x03f7, 0xbf3e, 0xb0d5, 0xb297, 0x42c4, 0x42b9, 0x42d7, 0x417f, 0x194b, 0x3e69, 0x409f, 0xbf60, 0x4219, 0xa318, 0x3a6a, 0xbf29, 0xb2a3, 0x4649, 0x467b, 0x1528, 0xb0b6, 0x40d0, 0xa445, 0xa6a6, 0x4153, 0x15db, 0x0d2b, 0xa059, 0x3ca7, 0x0813, 0x43fe, 0x199d, 0xbfd4, 0x4138, 0xb00a, 0x1936, 0x4111, 0xa06a, 0x43f8, 0x4041, 0xb2f4, 0x4293, 0xba58, 0x437f, 0x155d, 0x0878, 0xbad2, 0xb0cb, 0x1c3e, 0xbf19, 0x427a, 0x1a09, 0x17be, 0x1742, 0x1f52, 0xb282, 0x404e, 0xb263, 0xba0d, 0x425c, 0xb095, 0x41cd, 0x1d19, 0x0352, 0x1841, 0x26ff, 0xbf7c, 0xbadf, 0x1407, 0x42a3, 0x4025, 0x4330, 0xbaf6, 0xbf91, 0xa711, 0x4002, 0xa488, 0x1a65, 0x4052, 0x19a3, 0x1819, 0x43e7, 0xb239, 0x40a4, 0x31d2, 0xa11f, 0x42e6, 0x43d7, 0x4110, 0x4012, 0x2c2f, 0x43b5, 0x45a1, 0xb2dc, 0x12f9, 0x268b, 0x1993, 0x4228, 0xb20b, 0x419b, 0xb27b, 0x42bb, 0x46a2, 0xbfa9, 0x24ed, 0x41a1, 0x0d0a, 0x1559, 0x4114, 0x42f0, 0xa900, 0x4413, 0x4008, 0xa9f6, 0x05d6, 0xb2a4, 0x42e2, 0x3bdb, 0x435c, 0x1d9f, 0x183f, 0x4063, 0x0c6b, 0x30e3, 0xbf7c, 0x430f, 0x1169, 0xb20a, 0x1ce1, 0x3691, 0xa65e, 0xb20f, 0x40d9, 0x28c6, 0xba56, 0x403d, 0x43e8, 0x1e4e, 0xa2a3, 0xb21f, 0xa649, 0x4428, 0xa8cf, 0x4068, 0xb0f4, 0xb2b0, 0xbfcb, 0xb275, 0x1a56, 0x409b, 0x1ed9, 0x3908, 0x41ca, 0xb0d0, 0x1a06, 0x4230, 0x1f36, 0xbf90, 0xba26, 0x4020, 0xbf78, 0x42fe, 0x46b9, 0x3e8c, 0xa5e7, 0x4272, 0x0d53, 0x4159, 0x41b4, 0x4484, 0xbfc0, 0x4281, 0x414a, 0xad25, 0x195b, 0x43b8, 0x1f77, 0xbf67, 0x404e, 0xba33, 0xa665, 0x45ba, 0xb0ec, 0x41dd, 0x3f03, 0x343b, 0x43a5, 0xa8f0, 0x0ac1, 0x46ab, 0x4223, 0xb060, 0x4240, 0x41fc, 0x0d95, 0x1df4, 0x4299, 0xbff0, 0x0e19, 0xbaf4, 0x4137, 0x40e0, 0x4317, 0x3f08, 0xbf49, 0x1ad3, 0x1977, 0x1e1b, 0x434f, 0xb2ab, 0x22ae, 0xbfcc, 0x4435, 0x0e38, 0x414b, 0x2b42, 0x400f, 0x36e1, 0x41dd, 0xbade, 0x4190, 0xbf6f, 0xa805, 0x4073, 0x41bd, 0x05d3, 0x41b6, 0x1a96, 0x4075, 0x35a1, 0x3336, 0xa35c, 0x4332, 0x431f, 0x26bc, 0xb228, 0x0f68, 0x41ff, 0x438a, 0xb227, 0xbac0, 0xba78, 0xb073, 0x4083, 0x1d03, 0xbf41, 0x4159, 0x2f5c, 0x4081, 0x4337, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x3a4c2871, 0xacd9f7e9, 0x7bcd3cce, 0xadebfcc6, 0xde673e7c, 0x7dd2cf41, 0x7006af38, 0x2bb0bab5, 0xe2b978ed, 0x142f0bcb, 0x8bc78472, 0x492faf1e, 0x7421150a, 0x1ef52200, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0xffff18fc, 0x00000000, 0x000000a6, 0xffff1900, 0xfffffc18, 0x00063f47, 0x000000bc, 0xfffffcbc, 0xe2b978f1, 0x00000000, 0x000000b8, 0xaca0a530, 0x7421150a, 0x59414be9, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xb25e, 0x4201, 0xb0f6, 0x437b, 0xbf28, 0x1a61, 0x41f3, 0xb2af, 0xba52, 0xb0be, 0x0ee9, 0x18cb, 0x2ddc, 0x3e47, 0x4216, 0xb2e8, 0x40c7, 0x33d7, 0x42a9, 0x412a, 0x4322, 0x4313, 0x42b0, 0xbfbc, 0x42d1, 0x4309, 0xb2bc, 0xa626, 0xbfaf, 0x41e6, 0x4026, 0x4028, 0xa235, 0x40c3, 0xb030, 0xbfa0, 0x4331, 0xacd4, 0x2a6c, 0x4058, 0x353b, 0x42eb, 0x199e, 0x1a78, 0x1e98, 0x1222, 0x4677, 0x180d, 0x1b21, 0x36a8, 0x1d5e, 0xba01, 0x42b0, 0x4110, 0x416f, 0x4007, 0x1bb0, 0xab34, 0xbf31, 0x40f0, 0x46d4, 0x422e, 0xb2db, 0xa831, 0x2443, 0x40a8, 0xb002, 0xba2f, 0x41df, 0x4363, 0xa95d, 0xba57, 0xb2bf, 0x43b9, 0x427a, 0x4550, 0x1af8, 0xbad9, 0xb2cf, 0xbfb0, 0x4300, 0x4301, 0xbf4f, 0x42f3, 0xb2c5, 0xba77, 0x4157, 0xb2cc, 0x12ef, 0x187c, 0x46d9, 0xa1ec, 0x1c3f, 0xa4c0, 0x34a8, 0xa8ec, 0xb25d, 0x4253, 0x40d1, 0x4122, 0xbafd, 0x42fe, 0x19fb, 0x4566, 0xba75, 0x3820, 0x4115, 0x412e, 0x1f3f, 0xbf8d, 0x10fe, 0x4231, 0x40d3, 0xb054, 0x1750, 0xb07a, 0x1be5, 0x41a9, 0xb227, 0x431e, 0x41a3, 0xbae7, 0x43c0, 0x2338, 0x41c9, 0x4026, 0x4368, 0x4371, 0x42ad, 0xb2be, 0x1d31, 0x1cfb, 0x41f2, 0x3242, 0xbf33, 0x3a13, 0xbae1, 0xb03a, 0x4698, 0x4130, 0x44e0, 0xb238, 0x417b, 0x40f2, 0xbfbe, 0x4233, 0x4115, 0x45a8, 0x41cb, 0x46c3, 0x30b0, 0x42df, 0xa26e, 0xb2b7, 0x4205, 0xbf5b, 0xba12, 0xa0ae, 0x4247, 0x46e0, 0xb2c5, 0x41ed, 0x4068, 0x41e9, 0x19fd, 0x435a, 0x41e7, 0xb2f1, 0xb2b0, 0x41a1, 0x43c4, 0x45dc, 0x4387, 0x40b8, 0x2a85, 0x19fc, 0x4360, 0x4545, 0x1930, 0x41b3, 0x24ec, 0xbfa0, 0x42bc, 0x403f, 0xbf7d, 0xaf68, 0xb2d1, 0x4238, 0xb2a9, 0x4346, 0xb09b, 0xa482, 0x41d9, 0xba36, 0x4108, 0xb281, 0xba17, 0x28a4, 0x16f0, 0x10b0, 0x22f2, 0x467d, 0xac2a, 0x133e, 0x4195, 0x0338, 0x41dc, 0xbf03, 0x41c7, 0x4375, 0x41db, 0x4608, 0x45a0, 0x401a, 0x1b82, 0x42ca, 0xb01b, 0x206b, 0x4610, 0x1db7, 0x44dd, 0xa4e6, 0x0c75, 0x4064, 0x41ec, 0x27c6, 0x4371, 0x42f1, 0x418f, 0xb2d6, 0x237a, 0x209a, 0x364f, 0x4183, 0x4252, 0xbac8, 0xbf48, 0x32a8, 0x1805, 0x42d8, 0x1fab, 0x18b3, 0x41fa, 0x04ab, 0xbf80, 0x19b6, 0x1a4d, 0x41c2, 0x1b8f, 0xb23a, 0x40a9, 0xbaff, 0x40ec, 0x4313, 0x40d2, 0xb09a, 0xb218, 0x42da, 0x1f49, 0x41ab, 0xb22b, 0xbf58, 0xba62, 0x4020, 0x1f31, 0xb293, 0x4136, 0x24c0, 0x088e, 0x4252, 0x2136, 0x3252, 0x2efa, 0xa1bf, 0x04ad, 0x1c79, 0xb01c, 0x43f7, 0x42e6, 0x426d, 0x4364, 0x4236, 0xb298, 0xbf24, 0x26e7, 0xb27d, 0xae82, 0x414b, 0xa71c, 0x44f0, 0x4395, 0x421c, 0xb29b, 0x409f, 0xbfd9, 0xb206, 0x43c8, 0x19f8, 0xb24d, 0x4265, 0x419f, 0x414d, 0x429b, 0x45c5, 0x435a, 0x11d9, 0xb259, 0x4188, 0xba06, 0x204d, 0x1b38, 0x4296, 0xa47f, 0xbf03, 0x45ad, 0x167f, 0x23eb, 0xb237, 0xb289, 0x1c1c, 0x43ef, 0x42ad, 0xa496, 0x438e, 0xb0a6, 0x3afb, 0xb229, 0x2661, 0x4159, 0x4384, 0x3b07, 0x45b8, 0x09ae, 0x3811, 0xbfa1, 0x3db5, 0xb217, 0x2dff, 0x43a2, 0xb28a, 0xbfa3, 0xba3f, 0xb2ec, 0xb2e0, 0xb067, 0x0a10, 0xb20a, 0xa2ad, 0x446b, 0xb27d, 0x05f3, 0x4248, 0xb02b, 0x43be, 0xba20, 0xbf90, 0x2133, 0x0c37, 0x254d, 0x4144, 0xb213, 0x403f, 0x1356, 0xa35e, 0x4219, 0x438d, 0xb211, 0x42ae, 0xbfbd, 0x1e52, 0x3cf3, 0x1d3c, 0xb287, 0x0781, 0x405e, 0x41a9, 0x436e, 0x1ebf, 0xbf01, 0x0041, 0x29a5, 0x422e, 0xba5a, 0xb0f3, 0x4355, 0x41b5, 0x421c, 0x4191, 0x4276, 0x43cb, 0xbae3, 0x42d1, 0xba32, 0xbfc0, 0xb232, 0xbf6a, 0x4013, 0x4256, 0xba73, 0x40a9, 0xba05, 0x1de9, 0xbf04, 0x4170, 0x42da, 0x40a6, 0x13a4, 0x4084, 0x42da, 0xbfa0, 0x4081, 0xbfd9, 0x220b, 0xb26f, 0x41ee, 0xba5b, 0xba52, 0x43e1, 0xb2b4, 0x1ffd, 0xbf6b, 0xb278, 0x4690, 0x4370, 0x3669, 0xb21e, 0xb017, 0x375d, 0x4131, 0x407e, 0x40e7, 0x4638, 0x0d44, 0x4254, 0x3476, 0x41ec, 0x42fd, 0x1ccd, 0xb295, 0xbfd0, 0x401c, 0x3235, 0x4645, 0x155b, 0x0311, 0x41db, 0x40c0, 0x396d, 0x0ba6, 0xbfaf, 0x0e6d, 0x43fd, 0xa6bc, 0x401a, 0xbafc, 0x421e, 0xbf18, 0x1886, 0x4106, 0xbf27, 0x12cb, 0x43c8, 0xba28, 0x4011, 0xba7f, 0x1103, 0xbac5, 0x193b, 0x466d, 0x42c5, 0x184a, 0x0d38, 0xbf62, 0x3f92, 0x43e9, 0xaa66, 0xb093, 0x334d, 0xba06, 0x1ebb, 0xb2ff, 0x465e, 0xb24e, 0xbade, 0xbfa3, 0x2abf, 0x3186, 0x430a, 0x448a, 0x434f, 0xb291, 0x4215, 0x1952, 0xb003, 0x41ec, 0x44ed, 0x438f, 0x42f3, 0x424c, 0x4025, 0xb0d7, 0x18f0, 0xb25d, 0x416e, 0x4120, 0x45a9, 0xb0f9, 0xb2fd, 0xbaed, 0xba7d, 0xbfd3, 0x41ec, 0xb22b, 0x428a, 0x42e5, 0xb02c, 0x1b7c, 0x37e0, 0x430a, 0x190a, 0x25db, 0x2eff, 0xb276, 0x0b11, 0xbfe2, 0xab47, 0x251c, 0xb223, 0x31c7, 0xbaf2, 0xb001, 0xb212, 0x30f1, 0xa44c, 0x41ae, 0x418d, 0x40a4, 0xb0b6, 0x4331, 0x425a, 0x4040, 0x1849, 0xb03a, 0x1959, 0xb092, 0x429d, 0x4188, 0xbf12, 0x4619, 0xba68, 0xbf60, 0xbf80, 0x42b8, 0x0a84, 0xb2fe, 0x456c, 0xb0f3, 0x4194, 0xb272, 0xb0d7, 0xb2de, 0x4353, 0x0e33, 0x1c0a, 0xbac8, 0x42c6, 0x19ff, 0x43af, 0x4089, 0xbfb8, 0x3b9b, 0x096d, 0x31ac, 0x0234, 0x40cf, 0x24b8, 0x4278, 0x417a, 0x4309, 0x427e, 0xb070, 0x42cf, 0x40a9, 0x433b, 0x418d, 0xb2be, 0x1969, 0x429e, 0x43af, 0xbf1f, 0x0b1a, 0x4286, 0x41ce, 0x426d, 0x4136, 0x1ec7, 0x3425, 0xa663, 0x4457, 0xb2a4, 0x43b1, 0x41e0, 0xb2cc, 0x26cc, 0xbad6, 0xbacc, 0xb2e0, 0x4018, 0x3a50, 0xb2bf, 0xba3b, 0xbadb, 0xb0c4, 0xbf09, 0x1167, 0x4099, 0x044b, 0xba75, 0x43d6, 0xba3a, 0x43f5, 0x2c1c, 0xba5a, 0x43bc, 0x42a9, 0x1c95, 0xad4c, 0x43e4, 0x46cc, 0x42cc, 0x2605, 0x4009, 0x1871, 0x4069, 0xba37, 0x411d, 0x1807, 0x426d, 0x423b, 0x0fab, 0x4159, 0xbf83, 0x4290, 0x1d71, 0x3601, 0xa5d0, 0xb23a, 0x1cfb, 0x42fc, 0x435f, 0x4003, 0xa2b5, 0x4163, 0x4185, 0x403c, 0xb0d0, 0x1ae1, 0xb271, 0xafbc, 0x40cb, 0x4174, 0xbf69, 0xb258, 0xb2ec, 0x42c0, 0x41dd, 0x197d, 0xbf14, 0xa735, 0x38c4, 0x4188, 0xbfb0, 0xbf0d, 0x4117, 0x41ae, 0xb0e8, 0x411b, 0x4155, 0x43bb, 0xba44, 0x4198, 0xb2cd, 0xbfc7, 0xbae2, 0xb2c4, 0x43d7, 0x1e4d, 0x34ca, 0xb223, 0x439c, 0xb294, 0xbae1, 0x1d72, 0xbfe0, 0xb2ef, 0x4363, 0xbfb9, 0x413e, 0x408b, 0xa5be, 0x2675, 0x42d3, 0xb204, 0x1ff1, 0x096f, 0x41dd, 0x3c1e, 0xb2bd, 0x40c6, 0x2575, 0xb2b1, 0x1910, 0x4607, 0x42b8, 0x43dd, 0x4329, 0x4112, 0x1ea0, 0x1b9b, 0xa27c, 0xbadf, 0xbf81, 0x4088, 0x41a7, 0xada0, 0x0db4, 0xb2fe, 0x408a, 0x18da, 0xb026, 0xb0b3, 0x43f1, 0x4467, 0xbfaf, 0x4367, 0xbad4, 0x405f, 0x4286, 0x1a0d, 0xb2b7, 0x4571, 0x183e, 0x43cf, 0x1a0d, 0x4036, 0x424f, 0x41a4, 0x410f, 0x46a2, 0xaf85, 0xa17c, 0x433b, 0xbf0e, 0x4022, 0x43f0, 0x11a4, 0x40c4, 0xba2c, 0x18a6, 0x1a93, 0x41d1, 0xa8a3, 0xbaf8, 0x4584, 0xbff0, 0xb23a, 0x4385, 0x43d8, 0xb29b, 0x4362, 0xb20f, 0xb0a7, 0xad67, 0xb252, 0xbf8a, 0x1cb1, 0xb215, 0x41a4, 0x40b3, 0x2a7b, 0x0d23, 0x4199, 0x18ce, 0x4618, 0x144a, 0x3082, 0x1c3e, 0xbf57, 0x32de, 0x412d, 0xb298, 0x4648, 0x1c4d, 0x1df9, 0x3e54, 0xbf8e, 0x4488, 0x4092, 0xb200, 0xba13, 0x41a1, 0xba23, 0x1d7d, 0xba09, 0x2b1c, 0x4691, 0x397f, 0x4032, 0xbf0e, 0xba67, 0x40e5, 0xb2f3, 0xbff0, 0xba04, 0x3ec6, 0x1a0f, 0x408b, 0xb2e2, 0xbfe2, 0x4224, 0x195a, 0x3204, 0x430d, 0x39cb, 0x1c36, 0xb23f, 0x1977, 0xb030, 0x46f4, 0xb2fe, 0xba77, 0x4141, 0xb276, 0x4217, 0xbf60, 0xb223, 0xb2ca, 0x43bd, 0x4204, 0x41d0, 0xa049, 0x4352, 0xb035, 0xbf05, 0x4396, 0x429b, 0xa066, 0x0975, 0x409b, 0x46bd, 0x2aa5, 0xb2b9, 0xba09, 0xb05e, 0x422d, 0x1d65, 0x42c5, 0x42d4, 0x0533, 0x221b, 0x42c2, 0xa9dd, 0x2ac2, 0xad89, 0x4263, 0x4378, 0x40ff, 0x429b, 0x4282, 0x421f, 0xb0c4, 0xbf56, 0xb0c4, 0xb070, 0xbac9, 0x43ff, 0x1bd4, 0x418f, 0x09b8, 0xb0a8, 0xbfc7, 0x455a, 0x04bf, 0xba69, 0x4283, 0x404a, 0x43a2, 0x449b, 0x1e46, 0x4130, 0x4305, 0xb272, 0x1203, 0x43f4, 0x437e, 0xb066, 0x4273, 0x0f4c, 0x4612, 0xaa6e, 0x08d1, 0x437c, 0x461d, 0x44ac, 0xa824, 0xb2cd, 0xb01f, 0xbf53, 0xbfa0, 0xb283, 0x4151, 0xb284, 0x41f4, 0x406e, 0xb038, 0x4330, 0x420d, 0x44b5, 0xb2b2, 0xa730, 0xb2e0, 0x2b11, 0x4698, 0x4142, 0xb2f9, 0x42ec, 0xb08e, 0x3c8b, 0x42bc, 0xb22e, 0x4226, 0x400b, 0xb219, 0xbf25, 0x43c3, 0xbf00, 0x4345, 0x40aa, 0xb0a0, 0x1a70, 0x429e, 0x4074, 0x41af, 0xb272, 0xb0af, 0xb0f4, 0x42ff, 0xba34, 0x4342, 0x399a, 0x46cd, 0x19e4, 0x3c64, 0xbfcf, 0x40e9, 0x40c2, 0x3ac5, 0x323c, 0x307e, 0xba37, 0xba17, 0xb2f7, 0x207f, 0x431e, 0xac1e, 0xb0c8, 0x41b1, 0x436b, 0x40d1, 0xb02b, 0xbfe8, 0x4335, 0x41ba, 0x1cad, 0xa482, 0xbacd, 0xb285, 0x401c, 0xa85b, 0x4199, 0x4392, 0xbfbb, 0xb045, 0x0935, 0x30e0, 0x4226, 0xb22e, 0x282a, 0xb2e5, 0x40c8, 0x4326, 0x2bf9, 0x4197, 0xbf16, 0x4371, 0xb2ea, 0x40c4, 0x3f3c, 0xb285, 0x4170, 0x40ce, 0x2706, 0x308f, 0xbfbe, 0x4113, 0x0553, 0xbad5, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x9b4ed9e3, 0xfb4d9b4f, 0xf2becf95, 0xee40ea2c, 0x9b566ffa, 0x1e8d47a2, 0xeeda6eed, 0x8e4fe94e, 0x87b89292, 0x5bc99f48, 0xcdbc076e, 0x49f2a792, 0x65012e66, 0x2d75e18d, 0x00000000, 0xa00001f0 }, + FinalRegs = new uint[] { 0x40001b7a, 0x000019ff, 0x00000000, 0xffffffff, 0x00000000, 0x000000ec, 0x00000000, 0x00000006, 0xef680aac, 0x80000000, 0xffffffff, 0xc2cc9a01, 0xef680aac, 0x7fffff8c, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xba64, 0xba1a, 0x40a5, 0xba40, 0x1307, 0xb2a5, 0xb24d, 0x4102, 0xbfc0, 0xa0b9, 0xb20f, 0x181b, 0x439c, 0xa7dd, 0x1c89, 0xb011, 0x43c1, 0x1c4a, 0x418e, 0x1924, 0xa84d, 0xb235, 0x1f2a, 0x422d, 0xbf81, 0xb23e, 0x4057, 0x4036, 0xb2a5, 0xbf48, 0xb013, 0x42cf, 0x432a, 0xbfb6, 0x437b, 0x436d, 0x418a, 0x41cb, 0x2910, 0x1797, 0x424a, 0xa593, 0xb0aa, 0x429d, 0x40e6, 0x45c0, 0xb201, 0x4317, 0x4349, 0xb0a8, 0x4148, 0x19d4, 0x4255, 0x412a, 0xa20f, 0x1e2b, 0xbf1d, 0x403e, 0x42b4, 0x1a38, 0x4476, 0x436a, 0xab06, 0xb0e1, 0xbf82, 0x410d, 0xb2d0, 0x401a, 0x4356, 0xba0e, 0x40cc, 0x43e6, 0xba47, 0x408c, 0x43ae, 0xbf4a, 0x42cd, 0x1b95, 0x4046, 0x4299, 0x4380, 0x27bd, 0x431c, 0x4129, 0xbacc, 0x18be, 0x4380, 0xbf7c, 0x41ec, 0x19d7, 0xbf22, 0x40e2, 0x24c3, 0xb294, 0x4430, 0xb27b, 0x4648, 0x4360, 0xb24b, 0xb003, 0x4051, 0x42e7, 0x43d3, 0x41d4, 0x1bf1, 0xb035, 0xb2d4, 0xb2e9, 0x40e4, 0x435d, 0x3083, 0x464b, 0x41dd, 0x1b19, 0xb27e, 0x4224, 0x2dc0, 0x4181, 0x1624, 0xbf7d, 0xbae9, 0x4317, 0xabcc, 0x43d7, 0x438e, 0x4254, 0xb287, 0x40d0, 0xbf4e, 0xbaf3, 0x43f9, 0x4350, 0xa956, 0x1fc1, 0x4374, 0x44e5, 0xa792, 0xb2f3, 0x412b, 0xb201, 0xb241, 0xb2d3, 0xbfc0, 0xba76, 0x4246, 0xb019, 0x40e3, 0x198a, 0x2b9a, 0x3b90, 0x46d9, 0x072a, 0xba25, 0x3d43, 0x4097, 0x41e1, 0xbfc6, 0x4205, 0x04d8, 0x40f9, 0x1cd1, 0x1f55, 0x1f1e, 0xaa31, 0xb23e, 0x4598, 0x13f6, 0x4179, 0xaf2e, 0xbfae, 0x4357, 0x1374, 0x1ad4, 0xad33, 0x4156, 0x427e, 0xbfe4, 0x44e0, 0x4331, 0x43f4, 0x42c9, 0xbf85, 0x1a24, 0xa2f6, 0x41f2, 0xa107, 0x4232, 0xbf90, 0xa2a0, 0xb23e, 0x1f9e, 0x4187, 0x1bb0, 0xb26d, 0x426f, 0xb223, 0x4088, 0xacf5, 0x405e, 0x309e, 0x41ef, 0x22b8, 0xbfaa, 0x41bd, 0xbac2, 0x1c97, 0x4673, 0xb257, 0x41bf, 0x4055, 0xb287, 0x1bef, 0x44d5, 0x1b5a, 0xb06d, 0x42f0, 0x3c79, 0x420f, 0x1cc2, 0xbf2f, 0x44ca, 0x40ec, 0x41e5, 0xba3c, 0x4118, 0xba6d, 0xa231, 0xb266, 0xae71, 0xba56, 0xa854, 0x21c1, 0x39da, 0x18f7, 0x162a, 0x0af5, 0x4246, 0x43c3, 0x1839, 0xb0f9, 0x40d0, 0x1f0f, 0x46ac, 0x186e, 0x44a1, 0x40aa, 0xbfd1, 0x404c, 0x2780, 0x43f3, 0x4391, 0xbfc0, 0x439d, 0x0446, 0xba07, 0x411e, 0x4358, 0x40a1, 0x42eb, 0x408f, 0xa392, 0x25c7, 0x4289, 0xb203, 0x4220, 0xbfe0, 0xba43, 0xa384, 0x10d1, 0xbf4c, 0xba61, 0xbf90, 0x2fe4, 0x4339, 0x419e, 0xa2e4, 0xbf90, 0xa1b8, 0xb2ca, 0x431e, 0xa96f, 0xbaeb, 0x0eab, 0xba3f, 0x42da, 0x4092, 0xba50, 0x419f, 0x42ae, 0x428f, 0xbf7e, 0x43b6, 0x3644, 0xb0e6, 0xa3fd, 0x46c8, 0x4049, 0xb22e, 0x434c, 0x1dec, 0xb233, 0xb2de, 0x42f1, 0xb2d6, 0xb2ed, 0x3db5, 0x4326, 0x4279, 0x22e0, 0x2801, 0x42ac, 0x4331, 0x4192, 0x2136, 0xbf80, 0x4358, 0xbf48, 0xb09e, 0x0395, 0xb232, 0x41d8, 0xbfb0, 0x18fd, 0xba3e, 0x1da0, 0x45d5, 0x43a3, 0x41c0, 0x4028, 0x446a, 0x00dc, 0xbfa8, 0x2ac3, 0x292e, 0x439b, 0x421c, 0x1924, 0xb25b, 0x464b, 0x427e, 0x40a9, 0xbae4, 0x42ed, 0xb0be, 0x4650, 0x4205, 0x4062, 0x400c, 0xb283, 0x40a1, 0xb26e, 0x4438, 0x4210, 0xaf94, 0xb270, 0x18ad, 0xbf3c, 0x3d88, 0x41c3, 0x42eb, 0xbf51, 0x40f4, 0xba01, 0x4119, 0x42b6, 0x13e7, 0x2c7e, 0xb248, 0x3088, 0x2d04, 0xb22c, 0x45ce, 0x006d, 0x446f, 0x1a24, 0x4059, 0x40f6, 0x4360, 0xa82e, 0xb240, 0x42e0, 0xba21, 0x2130, 0x42b4, 0xb033, 0xbf04, 0x4020, 0x4093, 0x409d, 0x0fb2, 0x455f, 0x40d7, 0x168b, 0xba34, 0x2d4c, 0x426e, 0xba7b, 0x1b47, 0xb0a4, 0x4252, 0x38dd, 0x3e12, 0xb034, 0x429f, 0x4195, 0xbfd9, 0xb2f8, 0x427a, 0x3ecb, 0xaba8, 0xb27d, 0x44a4, 0xb2d5, 0x42cc, 0x43f0, 0x4369, 0x4281, 0x2b77, 0xbaf3, 0x1827, 0x3984, 0xb008, 0xba71, 0xbaeb, 0xbf68, 0xba04, 0xbf48, 0x40da, 0xb025, 0x324b, 0x41b9, 0xb0a9, 0xb02e, 0x45dc, 0x2fa9, 0x435c, 0x1b90, 0xbf7a, 0x41da, 0x1990, 0xba5a, 0xb206, 0x40c1, 0x4188, 0x4063, 0x4182, 0xb2d3, 0xba04, 0xbad2, 0x1f08, 0x031c, 0x179d, 0x1dcc, 0x1ed3, 0xa42e, 0x416d, 0x2dae, 0x05e5, 0x3b23, 0x09b7, 0x40c8, 0x1aa2, 0xb247, 0x1462, 0x40d1, 0x4049, 0xbf77, 0xb277, 0x1eee, 0xb298, 0xbf00, 0x42bb, 0x1f09, 0x423a, 0xba7f, 0x4217, 0xbaf5, 0xba2f, 0x3e9d, 0xbf3e, 0x40c8, 0x2bf6, 0xba24, 0xba43, 0x31ec, 0x2cae, 0x4211, 0xbf21, 0x1433, 0x467f, 0x1eab, 0xb2f5, 0x437e, 0x4143, 0xba44, 0xafe7, 0x4173, 0x418d, 0xad21, 0x0b5f, 0xa2f0, 0xba57, 0xbfaf, 0xb2ef, 0x3b63, 0x4155, 0xba08, 0xb20c, 0x4565, 0x407e, 0x4006, 0x27ce, 0xba15, 0xb2f7, 0x403e, 0xbafe, 0x4385, 0xb263, 0x4092, 0x43ba, 0xb2a9, 0x43bd, 0x424f, 0x4101, 0x45e3, 0x3773, 0xbfba, 0x1dbc, 0xb2ea, 0xb242, 0x40c7, 0xb057, 0xb2ee, 0x41d9, 0xb20d, 0x4052, 0x4391, 0xa641, 0xa730, 0xb20d, 0x3c10, 0x4477, 0x3eab, 0xa994, 0x2ce4, 0xbaf4, 0xbf62, 0xb252, 0xb2d1, 0x14c9, 0xbfc2, 0x4064, 0x4185, 0x1baf, 0x4298, 0xb2ab, 0xa44e, 0x40ea, 0xa273, 0x439d, 0x44d5, 0xba28, 0x41b2, 0x0318, 0x1e74, 0x4310, 0xbf0d, 0x1b3e, 0xba2c, 0x3286, 0x412d, 0x199a, 0xa2d6, 0x41de, 0xbfb0, 0x43e6, 0xa815, 0x162b, 0x16a3, 0x429e, 0xb210, 0x130d, 0xba4b, 0xba20, 0xbfbd, 0x2828, 0x00c1, 0x425e, 0x0047, 0x4324, 0x41af, 0x2141, 0x42c6, 0x42a2, 0xad20, 0x163f, 0x4385, 0x288d, 0xb0ad, 0x40a2, 0xba7a, 0xba4b, 0xb0ba, 0x4044, 0x0501, 0x33f5, 0x1845, 0xa3c5, 0xa7d3, 0x4274, 0xb270, 0xbafc, 0x4485, 0xbf35, 0x421e, 0x41a2, 0x4250, 0x421b, 0x04da, 0xb222, 0x1cd3, 0x0958, 0x4085, 0xa75d, 0x1d39, 0x059a, 0x43cb, 0xba2c, 0x40e8, 0x41e4, 0x4664, 0x40d9, 0xbf18, 0x43f1, 0x4350, 0x4267, 0x4027, 0xbf21, 0x1afb, 0x1264, 0xb258, 0x4575, 0x4124, 0xb215, 0x466d, 0x1870, 0x4319, 0xaa64, 0xbfc1, 0x4376, 0xb2fb, 0x437b, 0x438f, 0xb011, 0x2417, 0xb206, 0xbf00, 0xbaf8, 0xbfc5, 0xba1e, 0xa11d, 0xb27d, 0xadf2, 0x1b0f, 0x40c8, 0xbf05, 0x46d4, 0x43b0, 0x43e4, 0x4008, 0x426d, 0x1f5a, 0x423b, 0x1b37, 0x1df6, 0x2bde, 0xbadc, 0x1ff8, 0xbf3b, 0x43a8, 0x41de, 0x4239, 0x1def, 0x4184, 0x43ee, 0x431b, 0x41f0, 0x43f5, 0x4290, 0x422c, 0x3066, 0x19fd, 0xaeba, 0x409f, 0x1f41, 0x2c3e, 0x1f68, 0x40c7, 0x4146, 0x2ff8, 0xb2f6, 0xb026, 0xba41, 0xb220, 0x443f, 0x205a, 0xb2e6, 0x44cb, 0xbf3b, 0xbf90, 0xb2de, 0x0b90, 0x0e8f, 0xba26, 0x06c0, 0xbaed, 0x0acd, 0x408e, 0x31fb, 0x048f, 0x4577, 0x40db, 0xbfd5, 0x4181, 0xaa2b, 0x16a1, 0x401d, 0xbf19, 0x2bed, 0x4053, 0x3875, 0xb265, 0xad06, 0x4044, 0x11f9, 0xb289, 0x4602, 0xb0da, 0x461b, 0x40a1, 0xba3d, 0x13fd, 0x41a4, 0x41b3, 0x41eb, 0xb000, 0x4385, 0x46ec, 0x2780, 0x45be, 0x33bd, 0xbf34, 0x1963, 0x4266, 0x422e, 0x1c54, 0x1b2a, 0xa37d, 0xba7c, 0xba54, 0x4221, 0xba78, 0x1ef2, 0x425a, 0x40c8, 0x24fa, 0xbfcb, 0xb237, 0x420a, 0xb0bb, 0x1eeb, 0x40c3, 0x0071, 0x0ce2, 0xbf2e, 0x4297, 0x4227, 0x432c, 0xb01b, 0xae0a, 0xa328, 0x09dc, 0xb2cb, 0x4014, 0x1897, 0xbf54, 0xb07e, 0x234a, 0x284d, 0x44b1, 0x46fb, 0xb26b, 0x0044, 0xaf0c, 0xa318, 0x43b4, 0xa43d, 0xb2b1, 0x4082, 0x42ab, 0x108f, 0x41d8, 0xba0c, 0x4318, 0xbf12, 0x31db, 0x4296, 0x4376, 0x466e, 0x1702, 0xb08c, 0x1446, 0xb2c3, 0xb262, 0xb06b, 0x33ac, 0xb285, 0x1c39, 0x0554, 0xb254, 0x4010, 0x0807, 0x406d, 0x3fd4, 0xb2f0, 0x41d8, 0xb03d, 0xbf31, 0x0072, 0x4300, 0xabf2, 0x3778, 0x41ce, 0x425d, 0xa931, 0xb088, 0x104d, 0x1902, 0xb237, 0x1adb, 0x40fb, 0x4310, 0x03b3, 0x409c, 0xb0af, 0xb2e6, 0x06ba, 0x439d, 0x41c4, 0xbfc5, 0x4290, 0xb086, 0xb274, 0x1ff7, 0xbfca, 0x179a, 0x24c7, 0x407e, 0xba61, 0xbf1f, 0xb281, 0x41b6, 0x2aec, 0x41f5, 0x40fe, 0x27f0, 0x03f3, 0x1a45, 0x2020, 0x0c37, 0x18c6, 0x4420, 0xb225, 0x42fd, 0xbf87, 0x429e, 0x3085, 0x4240, 0x18df, 0x3b5f, 0x3e59, 0x42ba, 0x4369, 0x093c, 0x43df, 0xb2a3, 0x1f17, 0x427c, 0x1c1f, 0x17ae, 0x188a, 0x418f, 0xa2c1, 0x1b58, 0x4160, 0xbff0, 0x404f, 0x1985, 0xbf90, 0x1915, 0x434c, 0xbf11, 0x4177, 0x408b, 0x0d62, 0x186b, 0x1168, 0x18e3, 0xb235, 0x3037, 0x4695, 0xba1b, 0xa33d, 0xba31, 0xb2c3, 0x40f3, 0xb0c4, 0x08b1, 0x4207, 0x4398, 0x41d5, 0x2398, 0x440a, 0x2c54, 0xba66, 0x1c94, 0xbaf5, 0xbf9c, 0x437b, 0xb2f5, 0x4674, 0x40c3, 0x435f, 0x41d5, 0x4233, 0xac76, 0xbae1, 0xba0c, 0xba07, 0x46ba, 0xb220, 0x22a5, 0xb25d, 0xb204, 0x41f3, 0x04e6, 0x46b9, 0x43a2, 0x41bd, 0x40b6, 0x1d71, 0xbfb1, 0x41cf, 0x32a2, 0x1e0c, 0x43e4, 0xb05c, 0xbf8c, 0xb273, 0x4072, 0x462b, 0x2863, 0x415a, 0x4000, 0xb22a, 0x42f5, 0x41c1, 0xa19b, 0x436f, 0xa5ad, 0x0a9d, 0x40e3, 0x4066, 0xa244, 0x41c8, 0x40a8, 0x43e4, 0x4301, 0xbf64, 0x36fb, 0x3220, 0x45e5, 0xa505, 0xb222, 0xb2a2, 0xb09c, 0x4418, 0xb02c, 0x1dcc, 0x40ad, 0x0709, 0xa73b, 0x4374, 0x33ea, 0xbad1, 0xbfa2, 0x4021, 0x4082, 0x4449, 0x41b6, 0x1df3, 0x42a8, 0x0838, 0x41e1, 0x4104, 0x218c, 0x4384, 0x2d58, 0x006d, 0x4561, 0x4374, 0xb299, 0x4177, 0x4064, 0xa457, 0x3187, 0x1b34, 0xbf63, 0x4261, 0x4158, 0x2bf6, 0x435e, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xddc07271, 0x377926ce, 0x9a93827d, 0xcbef0283, 0x5bc182ba, 0xa9bfdede, 0x4f15e679, 0xd23d8725, 0x59c353e8, 0x9a2b180a, 0x2b318b54, 0xd61dea1b, 0x8cc70559, 0xca220005, 0x00000000, 0x300001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x0000008d, 0x00000000, 0x00000006, 0xffffe6b3, 0x00000000, 0xfffffffa, 0x000018ab, 0xc35cf61b, 0x00010000, 0x00010000, 0x00001630, 0xad4c19c4, 0x00001aa0, 0x00000000, 0xa00001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x418a, 0x18b4, 0xb262, 0x1dbf, 0xb028, 0x2f39, 0xba6e, 0x1e05, 0x41b8, 0x1a37, 0xbfc2, 0x3a0d, 0xaacb, 0x435a, 0x411a, 0x43c3, 0xbfc1, 0x4241, 0x1c8d, 0x46db, 0x2c85, 0x420a, 0xb29c, 0x35a9, 0xad4a, 0xb007, 0x433d, 0x42a9, 0x2e9d, 0x42dc, 0xba41, 0xba2b, 0xb236, 0x40ad, 0x2a1f, 0xa550, 0xb2ae, 0x0dd7, 0xbaea, 0x456a, 0x4299, 0x40d9, 0xa992, 0xbf0a, 0xbaff, 0xbad1, 0x4667, 0xaf90, 0x4281, 0x4310, 0xba51, 0xbf7c, 0x30f1, 0x43ff, 0x41c9, 0xb2f2, 0x412e, 0x43f1, 0xba1d, 0x45da, 0x275c, 0xb2d2, 0x337c, 0x4200, 0xbfb5, 0xb292, 0xba70, 0x4174, 0x40b0, 0x41e1, 0x42ed, 0x41e6, 0x439f, 0x224e, 0xba2c, 0x12e8, 0x411c, 0x1dea, 0x40f4, 0x4199, 0x43ac, 0x40fb, 0xb23b, 0x0f39, 0x4093, 0x45cb, 0x431a, 0x13c1, 0xbf73, 0xb271, 0x398f, 0xb018, 0x402a, 0xb002, 0xaac0, 0x1bc9, 0x2243, 0x0ec2, 0x3f51, 0xbf1b, 0x408c, 0xb0d0, 0x1c15, 0x03fc, 0x39ff, 0xb2a6, 0x02ca, 0x46ba, 0x3b17, 0x19ea, 0x40ef, 0x064f, 0x43f8, 0xbfda, 0x4268, 0x4074, 0x35bd, 0x44fa, 0x1ceb, 0xbf2d, 0x4325, 0x4157, 0x415c, 0x431a, 0xbaf1, 0xa1b0, 0x1b83, 0x01c9, 0x4306, 0x42e5, 0x172c, 0x0b7d, 0x405a, 0x4140, 0x4393, 0x4056, 0x1e6e, 0x414e, 0x40f4, 0x43a2, 0xb292, 0xbf3f, 0x4445, 0x190a, 0x431b, 0x1b32, 0xbf59, 0x4625, 0xad4e, 0x45bb, 0x446d, 0x44cc, 0x195c, 0x4619, 0xafd9, 0x0a89, 0x23e7, 0xa8ec, 0x425c, 0x41d0, 0x362d, 0x4257, 0xb036, 0xbf39, 0x44ec, 0xb00c, 0xb214, 0x1f74, 0xba33, 0x4574, 0x44f9, 0x41aa, 0x3e33, 0xbf7e, 0xb089, 0x4260, 0x44fd, 0xbfa8, 0x4262, 0x4348, 0xa90d, 0xaee0, 0xbf97, 0xba70, 0xba28, 0x4370, 0xb2c6, 0x41d4, 0xb21b, 0xb281, 0x43fc, 0xbfd3, 0x1a9f, 0xbac9, 0xbf60, 0x3f4b, 0x25a2, 0x4149, 0xbaeb, 0x4009, 0x238f, 0xba6d, 0xba55, 0xbfa0, 0x3191, 0x22ce, 0x3c36, 0xa50f, 0xaba5, 0xb0dc, 0x4216, 0xb2ff, 0x4322, 0x40fa, 0x418f, 0xba57, 0x4553, 0x1f57, 0xbf89, 0x0fe2, 0x3db5, 0xb201, 0x4111, 0xa77b, 0x42f0, 0x18c1, 0xbfa1, 0x1797, 0xbf80, 0x415c, 0xa873, 0x1a6f, 0x43c4, 0x41dc, 0x42d8, 0x4107, 0x40fa, 0x44d3, 0x401b, 0x0e14, 0xbfe0, 0xbacb, 0x0018, 0xb029, 0x403e, 0x4259, 0x408c, 0xb021, 0x0d72, 0x409f, 0x3e97, 0x0710, 0x422a, 0xbf23, 0xb08d, 0x1777, 0x2940, 0x40b2, 0xbae8, 0x1d10, 0x2a49, 0x24f4, 0x4297, 0x44d2, 0x405b, 0xbfb4, 0x4036, 0x3cc2, 0xbac2, 0x4287, 0x408f, 0x04cf, 0x4402, 0xbf36, 0x18a3, 0x1153, 0x4222, 0xb058, 0x403b, 0x424e, 0x1f0a, 0x419f, 0x43cd, 0xba14, 0x403f, 0xbae3, 0x1a87, 0xb275, 0x4053, 0x433d, 0xb2b7, 0x20a2, 0x40ed, 0x425c, 0x405e, 0xb2f0, 0x169c, 0x43de, 0xb2b4, 0xb030, 0x401e, 0xbf75, 0x4329, 0x1f63, 0x40ed, 0xb2e5, 0xb21e, 0x2dae, 0x13ff, 0xb246, 0x19fb, 0x43a2, 0x4179, 0x4304, 0x25a9, 0x1d4e, 0x1804, 0xb2d8, 0x41ba, 0xb28c, 0x4264, 0x4237, 0x27ff, 0x400a, 0xb00e, 0xb0c7, 0x42af, 0x33d3, 0x0b19, 0x42f9, 0xbf29, 0x41be, 0xb20e, 0xba0a, 0x25c7, 0x4215, 0x1b76, 0x16d0, 0xb08c, 0xb299, 0x4305, 0xbf94, 0xa65b, 0x02f3, 0xb226, 0x40c5, 0x185a, 0x42a4, 0xbf92, 0x1a1f, 0xb29b, 0x40d5, 0xba22, 0xb27b, 0xb0ac, 0x2ee1, 0x424e, 0xb2f8, 0x4299, 0xb26d, 0x0c8c, 0x4350, 0x4191, 0x1a7a, 0x439f, 0x1f7b, 0xb0f6, 0x442d, 0x0997, 0xbf36, 0x418d, 0xba3f, 0xb096, 0x0faa, 0xb201, 0x4302, 0x1919, 0x1d75, 0xb2f2, 0x4288, 0xbf00, 0xb203, 0x413f, 0x40ea, 0xbfd0, 0x198b, 0x4117, 0x439c, 0x09e7, 0xb277, 0x0cce, 0x43a1, 0xba4e, 0x466d, 0x18cf, 0x1e3e, 0xba56, 0xad9d, 0xbf71, 0x428e, 0x0a44, 0x434f, 0x4335, 0x401c, 0x403a, 0xbac5, 0xb0eb, 0xb075, 0x1ee0, 0x300b, 0x419a, 0x4152, 0x1833, 0x412f, 0x42cd, 0x42b6, 0x0e30, 0xa3fc, 0x40d4, 0xb226, 0x409d, 0x0a68, 0x3bb8, 0x1b5d, 0x1a26, 0xb2bb, 0xb290, 0x4294, 0xbfb8, 0x1d4a, 0x431a, 0xb071, 0x41f8, 0x42d3, 0x23e3, 0xb2c7, 0xb217, 0x436d, 0xba56, 0xb2c6, 0x417d, 0x1b54, 0xba72, 0x40eb, 0x0b91, 0xbf2f, 0xb242, 0xa679, 0x41bd, 0xba15, 0x4394, 0x419e, 0x42ad, 0x2bbf, 0x405e, 0x4128, 0x3858, 0x46cb, 0xbf79, 0x40e6, 0x42e0, 0x411c, 0x23b9, 0x42c2, 0x42f9, 0x00d5, 0x435b, 0x08e8, 0x2279, 0xbf94, 0xad4a, 0x4014, 0x40b4, 0x21dc, 0x435a, 0x46b9, 0xb2c5, 0xa749, 0x43b4, 0x40d2, 0x4408, 0x43d5, 0xbf60, 0x43d3, 0x408f, 0x2e00, 0x1ec6, 0x4186, 0xa036, 0x4147, 0xb227, 0xbf9d, 0x1a4b, 0x418f, 0x44b2, 0x40d9, 0xbf37, 0xb2ac, 0x1173, 0xb0e2, 0x409d, 0xb23e, 0xb230, 0x42da, 0x4081, 0x413d, 0xafe1, 0x1d08, 0x408b, 0x412a, 0x334a, 0x4023, 0x4028, 0xb014, 0xba0d, 0x05a5, 0x118a, 0x4090, 0x40cc, 0xb2da, 0x2c35, 0x4057, 0x40ee, 0x408c, 0xb259, 0x3bb3, 0xbf3d, 0x4323, 0x4384, 0xb286, 0x43a6, 0x41e6, 0x2ac2, 0x434b, 0x2a9c, 0x40bc, 0x43c4, 0x1f48, 0x2920, 0xab0a, 0x439e, 0xb0c9, 0xbf7a, 0x4081, 0xba67, 0xbf70, 0x42bc, 0x2f3c, 0x41b1, 0xa46f, 0xbace, 0x3c3d, 0xb2f1, 0x43c7, 0x4049, 0x11d4, 0xbf45, 0x4042, 0x1115, 0x4113, 0xba1b, 0xa069, 0x4571, 0xbacd, 0x1aee, 0x4499, 0xb06a, 0x42ac, 0x4069, 0xa4cd, 0x402a, 0x416b, 0x429c, 0x4362, 0x1314, 0x38c5, 0x400a, 0x4122, 0x3473, 0x413a, 0xba66, 0x27a8, 0x42ca, 0x4369, 0xbfce, 0x4268, 0xa867, 0x4072, 0x04f5, 0xba27, 0x43b5, 0x3c3f, 0xbf25, 0xba45, 0x439c, 0x1cd7, 0xb203, 0x45a3, 0xba2b, 0x41bd, 0x4013, 0x41a0, 0x43cd, 0xb2c4, 0xba18, 0x4097, 0x1507, 0xba2e, 0xaa23, 0x417a, 0x42fc, 0x1df3, 0x40c1, 0xba68, 0x41b6, 0xa65b, 0x438f, 0x1cbe, 0x01aa, 0xbf5d, 0x42a5, 0x070b, 0x423f, 0x465f, 0x4350, 0xa044, 0xb287, 0xb289, 0x42ad, 0x40fc, 0xafbe, 0x0b8c, 0xb2aa, 0x40d3, 0x463c, 0x4314, 0x1d39, 0x40e1, 0x1d12, 0x0eb4, 0xbf23, 0x43b0, 0xb2ca, 0x1b06, 0x414d, 0x1813, 0xb2f0, 0x1cec, 0xa02b, 0x454e, 0x4111, 0x19e3, 0x41f2, 0x4228, 0xbada, 0xb20f, 0x4342, 0x418c, 0x435d, 0x2d58, 0xb2af, 0x429b, 0x4020, 0xb2ae, 0x198b, 0x4256, 0xbf7c, 0xbaf9, 0xa179, 0xb012, 0xb2ed, 0x466e, 0x42b8, 0xb003, 0xb0c2, 0x429c, 0xb2fe, 0x00cb, 0x4440, 0x428e, 0xb033, 0x467e, 0x419e, 0x4396, 0xb261, 0x29a3, 0x434c, 0x1f85, 0x2734, 0x4068, 0xbf52, 0x44cb, 0xbac6, 0x4374, 0xb013, 0x42b7, 0x3f89, 0x163d, 0x1db9, 0xbf19, 0x430c, 0x4326, 0x440e, 0xba28, 0xba31, 0x43e2, 0x41b9, 0xad58, 0x4108, 0x1ad5, 0x428b, 0x1393, 0x4290, 0xb2b9, 0x403d, 0xb23d, 0xb00e, 0xaf72, 0x420e, 0x438e, 0x1298, 0x40db, 0x41ce, 0x40b8, 0x1da4, 0xb085, 0x420b, 0xbf18, 0x025d, 0x1b8b, 0x1038, 0x4191, 0x1e3f, 0x3912, 0x4015, 0x1b40, 0xba0a, 0xb0a5, 0x2dfa, 0x405a, 0xb2dc, 0x419f, 0x020d, 0xbf03, 0x420b, 0x4185, 0xbaca, 0xb0a1, 0x32e6, 0xbae1, 0x40ad, 0x0c4c, 0x426a, 0x442d, 0x0cbc, 0x3f03, 0x4220, 0xb025, 0xb05d, 0x0a54, 0x4253, 0x40e4, 0x0628, 0xba74, 0x4130, 0xb201, 0x3d5a, 0x1f92, 0x42fb, 0x2807, 0x3fd0, 0x434e, 0xbfd5, 0x433e, 0x4171, 0xb0c9, 0x4389, 0x0633, 0xbadd, 0x4468, 0xae94, 0x1efb, 0xb0ba, 0x4066, 0x18b4, 0xbf9b, 0x0986, 0x4373, 0x3dfe, 0xb2cf, 0x4276, 0x37fc, 0x4154, 0xb03b, 0x34e0, 0x1e93, 0x4394, 0x4261, 0x43ce, 0xb0b7, 0xb234, 0xb242, 0x406a, 0x42e3, 0x2ae2, 0x1b93, 0xbf96, 0xb07a, 0x4317, 0x1e51, 0x42e5, 0x430d, 0x324b, 0x4354, 0x410d, 0x41b8, 0x1eb2, 0x4035, 0x06cb, 0x4353, 0x43ca, 0xa102, 0x203d, 0x414e, 0xba16, 0x1b5a, 0xb2d4, 0xb292, 0xbf2d, 0x411e, 0xba6a, 0xb29e, 0x430b, 0x418f, 0xb017, 0x3d40, 0x4271, 0x43ca, 0xb20d, 0x42d1, 0x1ae9, 0x419d, 0x4021, 0x466a, 0x2bb4, 0x1ef8, 0x24be, 0x4146, 0xbf77, 0x423e, 0x4075, 0x4145, 0xb23b, 0xbae8, 0x0db2, 0xb20b, 0xb2bf, 0x0cbf, 0x183f, 0x0f19, 0x21e3, 0x4231, 0x46f9, 0x400b, 0xb0bf, 0x405f, 0xb047, 0x3f77, 0x1c33, 0x3d13, 0x3476, 0xb218, 0xbf7c, 0x4060, 0x43b8, 0xbf80, 0xb0c4, 0x40e7, 0x421c, 0xbf64, 0xb0de, 0x436e, 0x41b5, 0xb0bc, 0x4065, 0x40f4, 0x2bb9, 0xaf92, 0x4624, 0x1e4c, 0x4318, 0x41e2, 0x0adf, 0xb2d4, 0x1e99, 0x1e3b, 0x46f0, 0xa8b3, 0xbf7e, 0x32ac, 0x2b7a, 0x41fb, 0x40fd, 0xba62, 0xbf21, 0x0d64, 0x42b0, 0x3a8c, 0x4270, 0x1e40, 0xbfd4, 0xb298, 0x4084, 0x43e2, 0xbaf3, 0xb256, 0x422d, 0x2965, 0xb29d, 0xb260, 0x41ec, 0x41fb, 0x071c, 0xbf4e, 0x403a, 0x41c6, 0xbad3, 0x41b6, 0x4651, 0xb2c7, 0x44e2, 0x43fd, 0x421f, 0x09c3, 0x4018, 0xbaf3, 0xbff0, 0x106b, 0x40bc, 0x46bc, 0x41ba, 0x41be, 0xb297, 0x4270, 0x4369, 0xbf6c, 0x46b4, 0xb21a, 0x0857, 0x37bc, 0x41c4, 0x1cc5, 0xaddc, 0xb014, 0xb2e3, 0xb0fb, 0x1f42, 0x06c9, 0x412d, 0xa6cd, 0xb2bc, 0x40a6, 0xb0e1, 0x4174, 0xb29f, 0x4200, 0xbf55, 0xba34, 0x4080, 0x434f, 0xb249, 0x43f7, 0x44e1, 0xa252, 0x409d, 0x42bc, 0x4195, 0x4274, 0xba0f, 0x41ce, 0x43e9, 0x45e3, 0xb21b, 0x2ffd, 0xa5c2, 0xba7b, 0x4068, 0x4062, 0x0ea1, 0xbf68, 0x0330, 0x4041, 0x2f6d, 0x0761, 0x4354, 0x3531, 0x40c5, 0xac1f, 0x0a0d, 0x4104, 0x4338, 0x428a, 0x4293, 0xba73, 0x0b8b, 0x0958, 0xbfa3, 0x00ff, 0x43b3, 0x1bca, 0x4146, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xca173107, 0x59131982, 0xd9386e54, 0x24fdf592, 0x3f36eb46, 0x7926dfe1, 0x64131655, 0x72501df8, 0xd923625a, 0xfe222377, 0xf0f7cf65, 0x2d44a91d, 0x61d44f3a, 0x2f3c5836, 0x00000000, 0x300001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x00000000, 0xffffffc0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000040, 0x00000000, 0x000016c0, 0x8f32eca2, 0xfe2234ce, 0x00000000, 0x2f3c6602, 0x00000000, 0x400001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x23f8, 0xbfae, 0x415e, 0xbf00, 0x00dd, 0x1949, 0xb2d0, 0x45e8, 0x4179, 0x1814, 0xb001, 0xb239, 0x2993, 0xbfd8, 0x42ec, 0x3367, 0xbf7c, 0xb2a5, 0x3158, 0x4273, 0xbf16, 0x4099, 0x2952, 0x1ff7, 0xbfa8, 0x4059, 0xac74, 0x413a, 0xb03e, 0x1e71, 0x2513, 0x4293, 0x1e93, 0x4321, 0x16a0, 0x410e, 0x1d63, 0xb048, 0x43eb, 0x417c, 0xbf7f, 0xab52, 0xaa4f, 0x4267, 0xb2bd, 0xb227, 0x40ce, 0x0c3d, 0x09b8, 0x4218, 0xa803, 0x1b80, 0x4037, 0x185b, 0x3a2a, 0xbfdc, 0x4345, 0x42e2, 0x2885, 0xb0f7, 0xb2ca, 0x46aa, 0x2e1d, 0x436c, 0xba12, 0x4227, 0x0c58, 0xb075, 0x08a1, 0xba6b, 0xada7, 0xb034, 0xa86d, 0x426e, 0x428f, 0x4218, 0x4389, 0x1a12, 0xaab7, 0xbfe0, 0xb299, 0xbf3e, 0x41c7, 0x4200, 0x40cd, 0x4073, 0xb00b, 0x1122, 0x4270, 0x1516, 0xb0fe, 0x306c, 0x412d, 0x40ce, 0x3c5c, 0x1fab, 0x43bb, 0x4446, 0x405e, 0x40c6, 0xb03b, 0xba37, 0x403e, 0x46fc, 0xbf15, 0xb262, 0x1b66, 0xb26f, 0x4461, 0x22a8, 0x4276, 0x4289, 0x42e4, 0xbaf7, 0x404b, 0x166d, 0x44d9, 0x43fe, 0xb0eb, 0x43d9, 0xaa3b, 0x0b0e, 0xbf0a, 0x332e, 0xba4f, 0x1f48, 0x446d, 0x2fcc, 0x4009, 0x431a, 0x4006, 0x1f9b, 0x42c6, 0xb01d, 0x17a0, 0x4369, 0xbfc0, 0x07be, 0x4026, 0xb00f, 0x4412, 0x30d4, 0x4089, 0x461c, 0x432e, 0xbf36, 0xba17, 0x40ab, 0x432a, 0xa280, 0x1f32, 0xb0e4, 0x23c0, 0x1a9e, 0x4080, 0xba1f, 0xba5e, 0x2887, 0xad8b, 0x282d, 0x1882, 0x41ab, 0xb2e0, 0xb2e7, 0x4373, 0x41c2, 0x41ed, 0x45eb, 0xb00f, 0xb07f, 0xba27, 0xbf63, 0x1bcc, 0x4098, 0xa2a7, 0x189c, 0x4338, 0x4308, 0xbace, 0x1ff4, 0x40fd, 0x4279, 0x0ae8, 0x3b6d, 0x4315, 0x4654, 0x00d0, 0x425e, 0x40ef, 0x4357, 0x192b, 0xbf94, 0x43ce, 0x37fa, 0x1c40, 0x2cbf, 0x26f3, 0x1fe8, 0xb227, 0xabe3, 0x401e, 0x310b, 0x40ce, 0x4223, 0xbf65, 0xba30, 0x4352, 0x4241, 0x0f84, 0x1941, 0x4098, 0x40f7, 0x409a, 0x4081, 0x400c, 0x41da, 0xb256, 0x45cd, 0x1f42, 0x42f2, 0xbaef, 0x0335, 0xa8e0, 0x429a, 0xba0e, 0x40a3, 0x3d57, 0x439d, 0x406f, 0xbae3, 0xbfc6, 0x2f1b, 0xb2f5, 0xa396, 0x41ff, 0x40e4, 0x4401, 0x43f9, 0x4169, 0x40cb, 0x1f8b, 0x4335, 0xb25a, 0x4407, 0xba60, 0x4286, 0xa2ee, 0x4027, 0xbf3f, 0x1c86, 0x435c, 0x40bc, 0x41cc, 0x1bda, 0x2ad1, 0x1c02, 0xb04d, 0x42cb, 0x4084, 0x1dd2, 0xb2fd, 0xb0ac, 0xb280, 0x1dad, 0xb06c, 0x43a1, 0x4310, 0x40e4, 0x435f, 0xb2be, 0x4201, 0xbf7b, 0x1e70, 0x0de8, 0x02bf, 0x407d, 0x45d2, 0x1d92, 0x43c8, 0xbfa4, 0x401d, 0x431e, 0x4030, 0x40a1, 0x1fb9, 0x2bdc, 0xbf44, 0x423c, 0x41f8, 0x0525, 0xbf03, 0xbae5, 0x4316, 0x2fa1, 0xb042, 0x438d, 0x3f53, 0x424a, 0xa7ee, 0x4470, 0x0dc7, 0x4408, 0x18e7, 0xb2f9, 0xb01f, 0xa1af, 0xbf28, 0xbfc0, 0xb26b, 0xa1cc, 0xbf6c, 0x0855, 0x3e49, 0x45e1, 0x41a7, 0xba1b, 0x2d90, 0x4216, 0xb215, 0xb224, 0x4136, 0xb0fb, 0x4676, 0x1127, 0x4151, 0xba11, 0xb287, 0xb2e4, 0x4074, 0x4313, 0x060e, 0x1dc1, 0xb213, 0x4000, 0x42ca, 0x4085, 0x264b, 0xbf84, 0xa3c4, 0xb045, 0x0f04, 0x425a, 0xba22, 0x414a, 0x4155, 0x40ae, 0x12ca, 0x3ed5, 0xb0e1, 0x4080, 0xba16, 0xb04b, 0xba70, 0xbfa0, 0x40d5, 0x402b, 0xbaf1, 0x46b8, 0x1f45, 0xbf60, 0x423d, 0xb2ee, 0x2b14, 0x1e82, 0xbfc9, 0x1e6e, 0x415b, 0xba48, 0x2cf4, 0x43ee, 0x4322, 0xba57, 0xba68, 0x434d, 0xba0d, 0x31f2, 0xbf86, 0xa72b, 0x43d1, 0xb22c, 0xb29a, 0x3a3a, 0x433b, 0x41ad, 0x401a, 0x1df5, 0x0084, 0xa1db, 0xbfe0, 0x4091, 0xb294, 0x43f7, 0xbfde, 0x1d6a, 0x40c1, 0x4052, 0x14ba, 0x4315, 0x1b64, 0x4370, 0x1ef0, 0x0bf2, 0xb09b, 0xbf2e, 0x4260, 0xba0d, 0x0a68, 0xbfd0, 0x4197, 0x1e0a, 0xb204, 0x4282, 0x1afb, 0x4369, 0x438d, 0x41b4, 0x40e6, 0xba10, 0xb2e6, 0x436f, 0x40f8, 0x4207, 0x1986, 0x4342, 0xa33e, 0xb2e6, 0x1fe0, 0x402c, 0xbf48, 0xb03e, 0x40df, 0x0251, 0x1e47, 0x42dd, 0xb2d3, 0x1c88, 0xbf42, 0x36f1, 0xbaf1, 0x15ed, 0x433c, 0xbac4, 0xb0aa, 0x4315, 0xb242, 0x1fcd, 0x40b2, 0x420b, 0xb0e1, 0xba7c, 0xa872, 0x2966, 0xba76, 0x4635, 0x438b, 0x40b4, 0xbf80, 0xba5a, 0xbf80, 0x4350, 0xbfcf, 0x3a60, 0x45f4, 0x21f6, 0x2fc8, 0x425f, 0x2511, 0x42cf, 0x1945, 0x2264, 0x40ab, 0xbf8e, 0xb27e, 0xba02, 0x415f, 0xbfd7, 0x1026, 0x40a9, 0x1a50, 0xaca5, 0x4569, 0x1a72, 0x1ca3, 0x407a, 0xbac3, 0x402a, 0x1d13, 0x1ba5, 0x43bb, 0xb2fa, 0x3dc5, 0xb25f, 0xb2cd, 0x1eda, 0xba67, 0x39d3, 0x16ad, 0x1c10, 0x0b53, 0xb225, 0xbf55, 0xb20a, 0x29be, 0xb2cc, 0x43f5, 0x0c83, 0xb21f, 0x439e, 0x45d1, 0xb2b1, 0x0286, 0x441f, 0x0eb2, 0xbae9, 0xba76, 0xb2c4, 0x18f1, 0x40f6, 0xb2c9, 0x052d, 0xbaf3, 0x427f, 0x0215, 0x43ba, 0x4561, 0x14e4, 0xba1e, 0x41b5, 0xaf51, 0x42a3, 0xbf52, 0x4037, 0x410b, 0xba44, 0xbf70, 0x2dee, 0x005e, 0x4181, 0x1d78, 0x0559, 0xbf48, 0xb0ef, 0x40d1, 0x1bc3, 0x4012, 0x4613, 0x0053, 0x4351, 0xb2bd, 0x4381, 0x1a6a, 0x45c6, 0xb200, 0x4443, 0x08ef, 0xbfaf, 0x425e, 0x42e9, 0x437d, 0x10d0, 0xb25b, 0x441c, 0xbad8, 0x4263, 0x2561, 0xb02c, 0xbaf6, 0x428d, 0xb2d5, 0x406d, 0x4015, 0xb0dd, 0x45ac, 0x43d0, 0x365f, 0xb0df, 0xb2c3, 0x1829, 0xb0b3, 0x42e6, 0x02a5, 0x46d2, 0xbf0c, 0x4132, 0x409e, 0x30b6, 0xb25a, 0x42ef, 0x42f1, 0x0405, 0x41b2, 0x41e6, 0xb254, 0x4107, 0x40c6, 0x41a8, 0x0bec, 0x4134, 0xa686, 0xa857, 0xbf04, 0xab3f, 0xbfb0, 0xbf16, 0x4240, 0x42f4, 0xb2fb, 0xb2db, 0xb0f2, 0x197c, 0x0476, 0x406d, 0xb265, 0x40a1, 0x4389, 0x433e, 0xba5f, 0x41e8, 0x0e2f, 0x42f7, 0x4088, 0x3609, 0x24cb, 0x06bb, 0x428c, 0x1544, 0xbf19, 0xba5b, 0xba74, 0x4317, 0x42cc, 0xb261, 0xab8d, 0xa8bf, 0x003f, 0xa63e, 0xba06, 0x4061, 0x1a1d, 0x1baa, 0xbfb5, 0x42e0, 0x4273, 0xba55, 0xa83f, 0x46e0, 0x4324, 0x4556, 0x40e0, 0x421d, 0x2fcd, 0x42b4, 0x3700, 0xbae7, 0x19a2, 0xba1c, 0xba30, 0xb269, 0x1717, 0x413f, 0x1d62, 0x412e, 0x1196, 0x1c42, 0xbf66, 0x4017, 0x4238, 0x4351, 0x405a, 0x28e5, 0x29e7, 0x4555, 0x4052, 0x4356, 0x42bb, 0x079e, 0x42cc, 0xb245, 0xba69, 0x41c6, 0x4158, 0x3c55, 0x1ea8, 0xb25d, 0xbfdb, 0xbae1, 0x2de6, 0x19fe, 0x1b24, 0x4076, 0x46f1, 0xb020, 0xbadf, 0x4373, 0x4332, 0xacca, 0xb072, 0x440d, 0x445c, 0x192d, 0x317a, 0xbf48, 0x2038, 0x08bd, 0xaf9f, 0x0031, 0x4336, 0x15e1, 0xbfb5, 0x4387, 0xbacb, 0x4379, 0xb215, 0xb254, 0xbf7c, 0x19bc, 0x04e4, 0x4574, 0x432f, 0xb279, 0xbf6b, 0xbfd0, 0x2ec6, 0x41a4, 0x2665, 0xb0d5, 0x40e9, 0x435a, 0x4064, 0x1f9b, 0xb2ca, 0x0bee, 0x3d64, 0x3530, 0xbada, 0xa670, 0x42d5, 0x4200, 0x42f5, 0x42b8, 0xbad3, 0xba31, 0xbf71, 0x1cb2, 0xb205, 0x41eb, 0xba50, 0x4126, 0xbfad, 0xb0d7, 0x0788, 0xba09, 0x4246, 0x464a, 0x07d1, 0x438e, 0x4336, 0x2594, 0x3309, 0x4373, 0x4324, 0xb097, 0x428b, 0xbf90, 0x40e8, 0xa8ec, 0xbf3a, 0x0a6a, 0x2d9b, 0x04d6, 0x407d, 0xb233, 0x10b7, 0x40e0, 0x42a7, 0x43fd, 0xa709, 0x19a8, 0x40c3, 0x1b39, 0x19ea, 0x4492, 0x4046, 0xbae9, 0xba67, 0xaee3, 0x435c, 0x43ef, 0x409e, 0x4397, 0x3a1d, 0xbfa0, 0x4158, 0x4135, 0x1f07, 0xbf25, 0xb24f, 0x428f, 0xb2c9, 0x411e, 0xbf00, 0xba6c, 0x26ae, 0xb00f, 0x35f8, 0x419c, 0x1ea4, 0x42d4, 0xb207, 0x1611, 0x4399, 0xbf5c, 0x4283, 0x07d5, 0x10a8, 0x4387, 0x42db, 0xb259, 0xbf04, 0x1f26, 0x413c, 0x40b0, 0x4389, 0x1526, 0x212f, 0xbf63, 0xb0d6, 0xbad7, 0x33b4, 0x4322, 0x4317, 0x4188, 0x43eb, 0x40ea, 0xbfe4, 0x0285, 0x22a7, 0x4291, 0xbaf8, 0xb0ef, 0x438a, 0x1dd5, 0x4344, 0x266e, 0x41ca, 0x4155, 0x430a, 0xb2b7, 0x3f6f, 0xba27, 0x4372, 0xba32, 0x291c, 0x129c, 0xbfb2, 0xac6e, 0x4046, 0x4248, 0x4338, 0x12c1, 0xb227, 0x4168, 0x0f8e, 0x4038, 0x3caa, 0x43b0, 0x1a4b, 0xbf33, 0x21dd, 0xbae6, 0x1bab, 0x43a6, 0xbada, 0x1c96, 0x40a7, 0x4065, 0xb264, 0x1b34, 0x103f, 0x10e0, 0x007e, 0x0742, 0x405b, 0xbada, 0x466f, 0x438a, 0x40f5, 0xb287, 0x43a5, 0xb237, 0x4350, 0x44e5, 0x4222, 0x4067, 0x405e, 0xbfc6, 0x411e, 0x4222, 0x289f, 0xba0c, 0x2535, 0xb29e, 0xba3c, 0xb2a9, 0x1d5c, 0x1afb, 0x4033, 0x1ea6, 0xa772, 0x2d80, 0xa777, 0x43e4, 0x40c9, 0x401f, 0x315d, 0xba55, 0x4595, 0xb2a3, 0x409a, 0xbfaf, 0x46db, 0x4328, 0x4373, 0xb23b, 0xb0a6, 0x4093, 0xa89e, 0xb248, 0x43c9, 0x1643, 0xb04f, 0xb21b, 0x4365, 0xb2d3, 0x4660, 0x42b9, 0xb273, 0x0942, 0x4335, 0x1def, 0xab0c, 0xbf0e, 0x43af, 0x317b, 0x407e, 0x435d, 0x361a, 0x454c, 0x4304, 0x3b0b, 0x4046, 0xbf76, 0x4368, 0xb0fc, 0x4277, 0xa54e, 0x15dd, 0x43ef, 0x42ba, 0x436a, 0x3be5, 0x410e, 0x2870, 0xbf7e, 0x1890, 0x18a3, 0x4203, 0x4384, 0x4371, 0x0e8e, 0x42a8, 0x288f, 0x1920, 0x447b, 0xba58, 0x41e2, 0xb296, 0x42a4, 0x4151, 0xae23, 0x4268, 0x42ea, 0xb2dc, 0x42b5, 0x1ac1, 0x43af, 0xba4d, 0xbf3e, 0xb238, 0x4208, 0x2d85, 0xb284, 0x183c, 0x40c6, 0xa6b9, 0x429f, 0x0428, 0xb080, 0x23ff, 0x4191, 0xb286, 0xab3d, 0xbf23, 0xb2ab, 0x4101, 0xba54, 0x42ae, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xe99b8527, 0x3e866f78, 0xbb8f4079, 0xc9ef19b8, 0xaaf5b29d, 0x98cec417, 0x79e91cc3, 0x8e2e7557, 0xc691e551, 0x2095f998, 0xe3609aa0, 0xb521524b, 0xcc39afbb, 0xcf08047d, 0x00000000, 0x100001f0 }, + FinalRegs = new uint[] { 0x161c0000, 0x0cd2dc16, 0xf32d3fff, 0xcf08105b, 0x000000c2, 0x0000161c, 0x00000000, 0x00000061, 0x000010d2, 0x00000000, 0x00001643, 0xb521524b, 0x000010d2, 0xcf080f67, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xbf78, 0xb251, 0x1eb2, 0x15c1, 0x1b9e, 0xb26c, 0x46ba, 0x4392, 0x42d2, 0x42bd, 0x40e0, 0x1474, 0xba55, 0x4199, 0xb255, 0xbf64, 0x4214, 0x44fb, 0x437e, 0x426f, 0x4258, 0x4079, 0x409b, 0x4240, 0x42da, 0x02ff, 0x41f7, 0x1e1c, 0xbafd, 0x4108, 0x1976, 0x4304, 0x4363, 0x4376, 0x422f, 0x4448, 0x4164, 0x41f6, 0xaada, 0xbf0d, 0x43b8, 0xb0ab, 0x43c9, 0xb084, 0x0010, 0x419f, 0x4158, 0xb241, 0x4008, 0x44a8, 0x41dc, 0x42f1, 0x4083, 0x2913, 0x43e7, 0x4226, 0x1f85, 0xbf57, 0x40f3, 0xbac0, 0x43e2, 0x17b4, 0x401b, 0xa764, 0x19f8, 0xa5d3, 0xba04, 0x42c5, 0x410c, 0x0045, 0x40df, 0x45c4, 0x4370, 0xb0e6, 0x40f9, 0x1994, 0x42ea, 0xa907, 0xbf67, 0x4016, 0x1d1c, 0x0fd3, 0xb022, 0x1d1e, 0x4688, 0x44a4, 0x4357, 0x18dd, 0x3d71, 0x1ab6, 0x19b6, 0x1f8f, 0xaaf6, 0xb23a, 0x1b6a, 0x4137, 0x40a8, 0xbfe8, 0x4388, 0x422d, 0x434b, 0x0732, 0x4068, 0x42d5, 0x42f3, 0x41ad, 0xb20a, 0x4091, 0x411a, 0xba44, 0xa7d9, 0x1688, 0xb09c, 0x1d2f, 0x1cf1, 0xb2ca, 0x409c, 0x0873, 0x19b1, 0x409d, 0xb2e0, 0x4071, 0x425a, 0x04b0, 0xbf3a, 0x412f, 0x44e1, 0x3786, 0x412e, 0x419b, 0x4012, 0x4547, 0x19fb, 0xb0be, 0x4209, 0x4001, 0x43af, 0x429b, 0x20fd, 0x43d4, 0xbfa0, 0x40ee, 0x1a3d, 0xbfc0, 0x4418, 0xb2f9, 0x0a05, 0x2b88, 0xb23b, 0xbf5c, 0x183d, 0x4122, 0x463b, 0x3ed8, 0xbf70, 0x4381, 0xba5f, 0xba7d, 0x41ec, 0xba39, 0xbf70, 0x4596, 0x1660, 0x1e85, 0x4278, 0x43a4, 0xad91, 0x4046, 0x41fd, 0xba79, 0x408b, 0xbf4e, 0x0e75, 0xb22b, 0x1ee0, 0x4195, 0x1e6c, 0xb040, 0x43e2, 0x3745, 0xba65, 0x40e0, 0xa8ab, 0x4126, 0xbf41, 0xb20d, 0x4235, 0x0483, 0x43c5, 0x36d5, 0xaf62, 0xb0c7, 0x1e22, 0xbfa1, 0xb2e7, 0x17de, 0x3fa1, 0x2a2d, 0xb035, 0x4308, 0xa38f, 0x4115, 0xb001, 0xba31, 0x1d3f, 0x4093, 0xad99, 0x425f, 0x187a, 0x4206, 0xaaec, 0x41c2, 0x1b58, 0x4299, 0x419f, 0xbf59, 0x4252, 0xb275, 0x4157, 0x4314, 0x40c5, 0x4002, 0x4156, 0x4040, 0x1c34, 0xba34, 0x0b6f, 0x19d4, 0xba24, 0x40fd, 0x41cb, 0x4141, 0x3190, 0x19c1, 0xbad9, 0x1fdb, 0xbfab, 0x41ed, 0x43de, 0x4080, 0x3d58, 0x2532, 0x466a, 0xbfc0, 0xb049, 0x412a, 0xa8ca, 0xb2af, 0x420f, 0xbacd, 0x432a, 0xbfde, 0xb0e2, 0x43ea, 0x4100, 0x404d, 0x426e, 0xbf71, 0x124a, 0xb259, 0x407a, 0x43f7, 0x4215, 0x416f, 0x34fa, 0x4367, 0x40b1, 0xb239, 0x419f, 0xba28, 0xae76, 0x1a0e, 0xbf82, 0x4174, 0xb27d, 0xa2f6, 0x422b, 0x401c, 0x439a, 0xba51, 0x2d8f, 0xb063, 0x1d7c, 0x40f6, 0xb0f9, 0xb2aa, 0xbfaf, 0xaaaf, 0x40e9, 0x41a9, 0x16c4, 0xbad8, 0x1dda, 0x17ae, 0x26f9, 0xba47, 0xbad9, 0x41eb, 0x4086, 0x424f, 0x4234, 0xb290, 0x4236, 0xa8ce, 0x40fd, 0x18c3, 0x4291, 0xb2e9, 0x4240, 0xbf88, 0xbad6, 0x02a2, 0x439e, 0x1c1b, 0x1e78, 0xabba, 0x40c4, 0x42e9, 0xa4b2, 0x21f3, 0x1f64, 0x1c05, 0xbf6a, 0xb20b, 0x4034, 0x20f5, 0xbaf9, 0xac9b, 0xbfb0, 0xb02d, 0x43c9, 0x4020, 0x41f1, 0xb26e, 0x1c9d, 0xba7e, 0x4047, 0x41f4, 0x1bce, 0x412c, 0x4378, 0x3857, 0x43da, 0x419f, 0x4296, 0xba50, 0xbafd, 0x1f01, 0xbaf8, 0xa978, 0xbfb2, 0xb291, 0x42df, 0x410c, 0x40d3, 0x469b, 0x4388, 0x41eb, 0xbad7, 0x4367, 0x1aed, 0x411f, 0xba27, 0xb220, 0x417a, 0x1839, 0xb256, 0xb22e, 0xaeb8, 0xad5b, 0x0842, 0x2af8, 0x423d, 0xba02, 0xb256, 0xb093, 0xbfb5, 0x431d, 0xb01b, 0xb2cd, 0xba7c, 0x19f9, 0xbfa9, 0x1c2b, 0x40b2, 0xb024, 0xaf9c, 0xad91, 0x0191, 0x4152, 0x40b1, 0xbacb, 0xa5d4, 0x42e8, 0xb0b6, 0x080a, 0x41da, 0x4195, 0x40f4, 0x4329, 0xaa3c, 0x4602, 0x40f8, 0x1d9f, 0xbf86, 0x1d1d, 0xb041, 0x4294, 0xb215, 0x41a0, 0x4336, 0xb07c, 0xba73, 0x1537, 0xa583, 0x4332, 0xbfa3, 0xb03a, 0x2082, 0x405f, 0xb0e5, 0x4083, 0x3b48, 0x1fdd, 0x4399, 0x38f1, 0x41d9, 0xb09f, 0x1377, 0xb076, 0x195c, 0x4341, 0x1faa, 0x1a14, 0x1c98, 0x4177, 0xbf64, 0xbff0, 0x4341, 0x199c, 0xba2d, 0x4001, 0x4294, 0x271a, 0xbf49, 0xb2eb, 0x4625, 0xb2c7, 0x4282, 0x42c2, 0xbf90, 0x42cf, 0x4057, 0xbaef, 0x414a, 0xbf2b, 0x4196, 0x1071, 0x02c2, 0xa540, 0x424a, 0x032e, 0xb21b, 0x4331, 0x4049, 0x4673, 0xbfd2, 0x421f, 0x42ee, 0x417e, 0xa3ed, 0xb21e, 0xb0ba, 0xbac9, 0x4071, 0x3b78, 0x436b, 0xa792, 0xb0ea, 0xb271, 0xa5df, 0x1a45, 0x2020, 0x43ff, 0xb282, 0xb24f, 0xbfb6, 0x022c, 0xbae8, 0x3ac4, 0xb016, 0xba5a, 0x41e5, 0x407b, 0x0f5d, 0xb245, 0x1f55, 0x41ae, 0x43ba, 0x0399, 0x42df, 0x0d93, 0x4355, 0xba29, 0x4670, 0x4558, 0x047d, 0xbf86, 0x29a9, 0x19d7, 0x1f16, 0xb0fe, 0xbaf9, 0x21a5, 0x46fc, 0x2750, 0x1398, 0x2284, 0x0309, 0x461b, 0xb2cd, 0x29de, 0xbf0a, 0xba3f, 0x119d, 0xb24a, 0xa8e5, 0x4069, 0x148f, 0xb04d, 0xb264, 0xa8c6, 0x1dd3, 0x46b4, 0x18c7, 0x43bc, 0x40e3, 0xba4e, 0x405b, 0x4101, 0x1d87, 0x3d58, 0x43e6, 0x2199, 0xb21f, 0x1a00, 0x4301, 0x2a14, 0x02f4, 0xbfb7, 0x4441, 0xa4bf, 0x40bf, 0xb2d4, 0x4117, 0x11d9, 0x1f4a, 0x1923, 0x02f0, 0x4350, 0x1f6a, 0x4176, 0xb2f9, 0xb233, 0x31f5, 0x4012, 0xbfdd, 0x4297, 0x4575, 0x40ae, 0xba01, 0x42b1, 0xbacd, 0x40fa, 0x3d82, 0x4338, 0xba51, 0x411c, 0x4153, 0x2556, 0x4398, 0x1d63, 0x4305, 0x28c7, 0xbf0a, 0x4309, 0xb051, 0x42e7, 0x37b1, 0xb28c, 0x41f8, 0xb263, 0xbf60, 0x1f06, 0x421f, 0x4294, 0x401c, 0x43d3, 0xb2b3, 0x1a06, 0x428e, 0x42fe, 0xb246, 0xb27b, 0x43e7, 0x4135, 0x2f59, 0xbf6e, 0xac94, 0xbaf0, 0x12fd, 0x41da, 0x4337, 0xb294, 0xb06f, 0x4156, 0x42d8, 0x2309, 0x1de4, 0xb021, 0x0d01, 0x42fd, 0x1957, 0x44e1, 0x0a23, 0xb21d, 0xba2e, 0x2c2f, 0xa0c8, 0x40f8, 0x41f6, 0xbaed, 0x4224, 0xada8, 0x42ff, 0x4227, 0xbfc1, 0x2336, 0xbad7, 0xba01, 0xba5b, 0x1eec, 0xba44, 0x43d7, 0xbae3, 0xba1f, 0x417f, 0x3fbb, 0xb2aa, 0xb202, 0x07f6, 0xb249, 0x414f, 0x4144, 0xb25e, 0xba30, 0x413d, 0xb2c9, 0xb0a8, 0x029f, 0x19a0, 0xbf58, 0x4140, 0xb260, 0x0f52, 0xb23b, 0x42c4, 0x43db, 0xad0e, 0x43f0, 0x028d, 0xbf24, 0xbac3, 0x1c7e, 0x1094, 0xbff0, 0x41b1, 0x30a0, 0x01a0, 0x094c, 0x149b, 0xbfdf, 0x32f6, 0x41b3, 0x43d5, 0x35b7, 0x468c, 0x2936, 0x1877, 0x2db5, 0x436e, 0x18bc, 0x1c4b, 0xbf00, 0xb276, 0xbf00, 0xb262, 0xba52, 0x240e, 0x4281, 0x410b, 0xbfa2, 0x4173, 0x2aba, 0x43de, 0x2801, 0x432c, 0x40b4, 0x4119, 0xb2da, 0xaa5d, 0x43dd, 0x1c35, 0x4357, 0x4237, 0x191a, 0xba06, 0x3b92, 0x4018, 0xb299, 0x46c2, 0x1f3c, 0xba7a, 0x438e, 0x44b9, 0xba71, 0x401f, 0xbfd7, 0xbaf6, 0x41b4, 0x0e92, 0x4151, 0xb225, 0xb08b, 0x4035, 0x428c, 0x42b2, 0x41cb, 0xb2a3, 0xbae6, 0x41cd, 0xb2b9, 0xba59, 0x4022, 0xba11, 0x4246, 0x4625, 0x1b37, 0xad1c, 0x108d, 0x2de7, 0x1a8b, 0xbf9d, 0x1bc9, 0x43c4, 0x432a, 0x1a06, 0x4196, 0x3937, 0xb08f, 0xbad4, 0x18f6, 0x3f38, 0xbfc6, 0xb2d1, 0xb03a, 0x4246, 0x3ea1, 0x42bb, 0xb259, 0x43b0, 0xb2bb, 0xba47, 0xb28d, 0x1907, 0xbf6a, 0xb089, 0x4372, 0x4469, 0x182d, 0xb03d, 0xbf90, 0x4399, 0x4237, 0xb223, 0x36ba, 0x4173, 0x4233, 0x4616, 0x21de, 0x25e2, 0x4287, 0xb20a, 0x4612, 0x409c, 0xb24c, 0x40d6, 0x400f, 0xbad9, 0xbf01, 0x4324, 0x4374, 0xb0d1, 0xb21a, 0x1e51, 0xb2e8, 0x41ed, 0xbf60, 0x4664, 0x4043, 0x463a, 0xb21d, 0x430d, 0xbf19, 0x4203, 0x4339, 0x4549, 0xa026, 0x1ca7, 0xbaf4, 0xaaac, 0x4360, 0x2a0a, 0x125d, 0xb2de, 0x313b, 0x4025, 0xb221, 0xba16, 0x42e4, 0x4087, 0xba4e, 0x1f06, 0x31b6, 0x1af5, 0xbf3b, 0xb217, 0xb227, 0x3022, 0xba71, 0xbf11, 0x40db, 0x40a6, 0x4219, 0x4274, 0x4349, 0x4168, 0xbfb1, 0x4219, 0xb27f, 0x415d, 0x0685, 0x42f3, 0x4321, 0x400e, 0x4041, 0x3c1d, 0x41c8, 0xbacf, 0xa885, 0x425b, 0xa833, 0xb0ed, 0x4111, 0x408a, 0xbf60, 0x4048, 0xb2e6, 0x314a, 0xb21c, 0x2119, 0x4349, 0x1d8f, 0x43e4, 0x1ce5, 0xb2db, 0x4281, 0xbf3c, 0x1907, 0x431e, 0x41f2, 0x4309, 0x413f, 0x3c15, 0x1e57, 0x1875, 0x2a86, 0x1cde, 0x444d, 0xb26f, 0x40ad, 0x3408, 0x05f4, 0x42b4, 0x418f, 0x1bdb, 0x437c, 0xb29e, 0xa7c4, 0x44b8, 0x4254, 0x4198, 0x4568, 0xbf29, 0xba58, 0x42e5, 0x413d, 0xbae2, 0x42d2, 0x44a0, 0xb2b6, 0x431a, 0x3165, 0x1d28, 0x0b29, 0xb234, 0x409b, 0xb296, 0xbf5d, 0x13ee, 0x4310, 0x4015, 0x42b6, 0x1919, 0x21c3, 0xb249, 0x43dc, 0x459d, 0xbfc0, 0x4073, 0x4251, 0x428e, 0x42a6, 0xb212, 0xb20d, 0xbfc0, 0x40b8, 0xba7e, 0x42d8, 0xb234, 0x05c8, 0x4120, 0x1a7d, 0xb05f, 0x4608, 0x411a, 0xbf47, 0xba36, 0x19e1, 0x4019, 0x0eb2, 0x4090, 0x43f4, 0x42a7, 0xba3a, 0x0080, 0x42b9, 0xba72, 0xbf90, 0xb0ff, 0xb295, 0xa5d3, 0x4215, 0x36ac, 0x40be, 0x1a4a, 0xbad4, 0x09b5, 0xba41, 0x465c, 0xba6f, 0xb25f, 0xbfa2, 0x14b7, 0x09c9, 0x1ee3, 0x4397, 0x280b, 0xbf53, 0xb230, 0x42c9, 0x1896, 0x4454, 0xbafa, 0x0535, 0x42f0, 0x395d, 0x1d22, 0x4191, 0x4481, 0x1b2e, 0xba36, 0x4103, 0x1a29, 0x2b7c, 0x326a, 0x407a, 0x42a4, 0x0308, 0x4673, 0xbfd0, 0xb24d, 0x184f, 0x439d, 0xba62, 0x42f7, 0xbf93, 0x24a2, 0xb2d0, 0xb0f4, 0x13d7, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x7f786d81, 0xed0589af, 0xd3e5419b, 0x0129696d, 0xe25706db, 0x2c5ca927, 0x6d81e353, 0x64845d90, 0x7114f20e, 0x7890cd21, 0xe05b5922, 0xe345d064, 0xda1b31e1, 0xa9b62e07, 0x00000000, 0x000001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x00000000, 0xb6a9df2b, 0x00000000, 0x000000a2, 0x00000000, 0x21d44956, 0xffff6d53, 0x347f7fc7, 0x188dd287, 0xa9b62bdf, 0x00000000, 0xffffffff, 0xa9b62c9f, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x1e54, 0x40d8, 0x2f66, 0x276c, 0x416f, 0x1987, 0xb06a, 0x45ec, 0xba67, 0x408a, 0xba2f, 0xacd5, 0x29cf, 0x1872, 0x111e, 0x1b04, 0xbafd, 0x41a5, 0xbf97, 0x43df, 0xb06f, 0x1c82, 0x268b, 0xb2ba, 0xb08a, 0x407f, 0xbaea, 0x1d52, 0x06cb, 0x41e7, 0x4475, 0x43b8, 0xba6b, 0x1fb1, 0xbf3e, 0x4241, 0x3d38, 0x4120, 0x2a6b, 0xb217, 0xbfb8, 0x1f4e, 0xb218, 0xb2cc, 0x1ad6, 0x4128, 0x169b, 0xaa87, 0xaf67, 0xb2aa, 0xbfc0, 0xb0e8, 0x33a9, 0x43fb, 0x195b, 0x4091, 0x3613, 0x00c8, 0xbf09, 0xbff0, 0x0ab2, 0x4211, 0xbadc, 0x4100, 0xba4d, 0x0f34, 0xba3f, 0xb250, 0x1425, 0x1ad6, 0x1f20, 0xb0e6, 0xae48, 0x41f4, 0x4117, 0xb0d0, 0xba25, 0xb2f9, 0xba7d, 0x4063, 0x1e53, 0x40ef, 0x401c, 0x1b23, 0x059d, 0x4021, 0x4126, 0xbf0c, 0x3318, 0x4087, 0x4135, 0x430e, 0xbf69, 0x18c0, 0xb285, 0x4164, 0xbf90, 0x40e9, 0x43bf, 0xba12, 0x2a34, 0x4009, 0x4593, 0xbf06, 0x07c3, 0xb295, 0x369a, 0x41f4, 0x434a, 0xb01e, 0x42a5, 0x0343, 0x2ad7, 0x1be0, 0xbfce, 0x1bdd, 0x4484, 0x439c, 0x461b, 0x10f4, 0x2207, 0x0503, 0x40ab, 0xbf60, 0x4108, 0x4271, 0x1c07, 0x40d7, 0x1820, 0x01a5, 0x3519, 0xb283, 0xb0c1, 0xb28f, 0xb241, 0x238f, 0x43da, 0x2f61, 0xbfb2, 0xa2bd, 0xbae8, 0x410c, 0x46e2, 0xb012, 0x1e36, 0xb236, 0x45ba, 0xb23b, 0x2e93, 0x4369, 0x18a6, 0xb257, 0x4340, 0xba50, 0x4240, 0x4181, 0x1a86, 0xba64, 0x0cb1, 0x4065, 0xb21a, 0x1b54, 0xbf70, 0x462e, 0xb236, 0x01af, 0x41c3, 0xbf89, 0x4324, 0x4357, 0x41ae, 0x4654, 0x408c, 0x1c4d, 0x43ca, 0x405e, 0x19cc, 0x429b, 0xb026, 0x4337, 0x404b, 0x1c2f, 0xba75, 0x42f1, 0x1e97, 0xb27f, 0x425d, 0x049c, 0xb2e2, 0x1385, 0xb270, 0x1eb3, 0xa556, 0xbade, 0x4064, 0x22e3, 0xbfd1, 0x1cf0, 0x0f0c, 0x4266, 0xb0e2, 0x02c3, 0xb092, 0x1212, 0x3b16, 0x4349, 0x458d, 0x317f, 0x4263, 0x19ee, 0x3fde, 0x085c, 0x0f9e, 0x21c4, 0x41e8, 0xb27f, 0x1ae4, 0x425c, 0x2207, 0x43b9, 0xba3e, 0xb24a, 0xbfcc, 0x40b9, 0x40df, 0x4026, 0x415a, 0xb2db, 0x41e2, 0xbae2, 0x410a, 0x420e, 0xbfa5, 0x43d6, 0xb237, 0x40a9, 0x407f, 0x4075, 0xb23b, 0x1dbb, 0x4021, 0x16c5, 0x430d, 0xb258, 0x1fee, 0x44a5, 0xb03f, 0x4367, 0x43e4, 0x435a, 0x37aa, 0x1bd9, 0xbf08, 0x4143, 0xaea3, 0x2a14, 0x41ef, 0xb076, 0x1e69, 0x4644, 0x1971, 0x2fe4, 0x0aef, 0x3651, 0x43e0, 0x4171, 0x1c89, 0x417b, 0x4354, 0x43fa, 0x418d, 0x411b, 0x425a, 0xbf3a, 0x42b3, 0xa73a, 0x4396, 0xbfc0, 0xb258, 0x429c, 0x44c8, 0x4195, 0xba65, 0x46ed, 0xb0ec, 0x18f8, 0x43b9, 0xb246, 0x4306, 0x465f, 0xb2d4, 0xbf7e, 0x37f6, 0x4173, 0x4685, 0x0536, 0x3e40, 0x0691, 0x2372, 0xbf2e, 0xb213, 0xba2c, 0x1c23, 0x41e6, 0x30e0, 0x4062, 0x4030, 0xb229, 0x43d9, 0x2e16, 0x25b3, 0xb2ae, 0x439f, 0x18bb, 0x16b6, 0x4048, 0x4418, 0x42f7, 0xa615, 0xb008, 0xb2d6, 0x1958, 0x1b00, 0xb0d0, 0x4281, 0xb225, 0xab07, 0xbf82, 0x4276, 0x2565, 0x4156, 0x43eb, 0xb2fe, 0xba25, 0x4143, 0x435c, 0x41cf, 0x428c, 0xb2b4, 0x1772, 0x4398, 0x1b8d, 0xb016, 0x435d, 0xba5c, 0xbf63, 0xba66, 0x4100, 0xba5f, 0x43ac, 0xbfb1, 0x41c5, 0x3555, 0x3b44, 0x4305, 0x3e89, 0xba59, 0x1f34, 0x1828, 0x3d4b, 0xa344, 0xb2c2, 0x3ca8, 0x42ea, 0xbf88, 0x4250, 0xb2ac, 0x43fd, 0xb02a, 0x30bb, 0x0fba, 0x43b6, 0x4336, 0x432b, 0xb256, 0x432f, 0xb0bf, 0x4353, 0x4069, 0x095a, 0xa6cb, 0xbf47, 0x3074, 0x165b, 0x1fe4, 0x2ed8, 0xb272, 0x43d0, 0x43a8, 0x2770, 0xb05c, 0x4279, 0x4055, 0xbfd0, 0xbf84, 0x41aa, 0x43ec, 0x1daf, 0x4663, 0x40c5, 0x34e8, 0x447f, 0xa971, 0x145d, 0x00c3, 0x4281, 0x4392, 0xba2d, 0x3677, 0x42eb, 0x42d3, 0x28df, 0xb2c4, 0x4004, 0xa6ed, 0x40fb, 0x1947, 0xbf33, 0xbaee, 0x446b, 0x40b7, 0x402b, 0x460f, 0x1ef9, 0x4130, 0x28f6, 0xad23, 0x4042, 0x2583, 0x45b5, 0x41c8, 0x462e, 0x19f1, 0x4008, 0x4144, 0x421d, 0x4305, 0xba2b, 0x4103, 0xb2fd, 0xb239, 0x4365, 0xbf9a, 0xb219, 0x4461, 0x41a7, 0x43ac, 0xb2a4, 0x416a, 0xbfe0, 0xb09b, 0x4214, 0xae01, 0x2c45, 0x0c53, 0x4107, 0x43b4, 0x4005, 0xb22f, 0x1966, 0xbf60, 0x182c, 0x4011, 0x430e, 0x4291, 0x34de, 0xbfcc, 0xabcf, 0x4255, 0xba1e, 0xba3b, 0x4556, 0x0971, 0x1926, 0xb2ff, 0xaffe, 0x0c75, 0x401a, 0xbf69, 0x4026, 0x402a, 0x4406, 0x4213, 0xba16, 0x41da, 0x43f0, 0x41d3, 0x4312, 0x4228, 0x43fb, 0x44a8, 0x413c, 0x1e14, 0x1924, 0x435d, 0x409c, 0xb27c, 0x4113, 0x405c, 0x4180, 0xba02, 0x42c0, 0xba36, 0x4197, 0x4359, 0x319f, 0xbf82, 0x4092, 0x1fec, 0x1ee0, 0x43fa, 0x4170, 0x45b4, 0x1e51, 0x4109, 0x40e2, 0x2867, 0x30ff, 0x41f8, 0xbf69, 0x17f9, 0x0d33, 0x06dd, 0x1990, 0xa9a2, 0x3666, 0x1d0f, 0xa2cf, 0x4150, 0x4073, 0x217c, 0x1c58, 0x3267, 0x42d9, 0xb217, 0x439f, 0xbafd, 0x4054, 0x1a4c, 0x42e3, 0xbfc9, 0x2489, 0x4620, 0x00d9, 0x41c0, 0xbf90, 0xb23e, 0xb2a1, 0x27f9, 0x423a, 0x417f, 0x2922, 0xbac3, 0x1a8e, 0x1ff5, 0xb220, 0x421f, 0x4378, 0x433e, 0xa84c, 0xb081, 0x1a72, 0x4684, 0xb280, 0x1acb, 0x1d52, 0x43cb, 0x4125, 0x1b4d, 0xbf1b, 0x4190, 0x192c, 0x42fc, 0xaf1e, 0xbf3f, 0x42a6, 0x1ef6, 0x1a9b, 0xb076, 0x30ec, 0xb25b, 0x19f6, 0x29fb, 0x43f2, 0xb2b7, 0x40af, 0x438e, 0x1bbe, 0xb20b, 0x4166, 0x1a51, 0x4140, 0xb250, 0x23de, 0x4062, 0x39c9, 0xbf0d, 0x1ff7, 0xb04e, 0x4670, 0xb233, 0x40ca, 0x4280, 0x17e1, 0x4572, 0x435a, 0xb248, 0x198a, 0x427a, 0x21aa, 0x412d, 0x0d38, 0x1b5c, 0x1cda, 0x40ad, 0x081c, 0xb2ab, 0xbf98, 0x05b0, 0x157a, 0x42ad, 0x43dc, 0x09b2, 0xa299, 0x1def, 0x41dd, 0x2faa, 0x4089, 0x4161, 0x40eb, 0x400d, 0x42e7, 0xbf70, 0x408f, 0x439c, 0x11bc, 0x40c6, 0x1277, 0x2045, 0x426e, 0x420f, 0xbf70, 0xba1a, 0xbfc4, 0x088e, 0x4437, 0x19aa, 0xb095, 0xba24, 0x1d2b, 0x19d9, 0xb200, 0xb26c, 0xbac0, 0x42c3, 0xbf19, 0x3ab5, 0x185a, 0x1e03, 0x42bd, 0xb040, 0x456d, 0x0e83, 0x4244, 0x4001, 0xb257, 0xb255, 0x4016, 0x21f4, 0x134a, 0x1c09, 0xb2eb, 0xb2d5, 0x103c, 0x412f, 0xb2f8, 0x4645, 0x41ca, 0xbad7, 0x3c4c, 0x43f2, 0x4469, 0xbf7f, 0xb096, 0x404c, 0xb20e, 0x408f, 0xb293, 0x44f4, 0xb2ac, 0x4300, 0xb0d5, 0xb044, 0xb298, 0x401d, 0x40a5, 0x0257, 0xb2bd, 0x427d, 0x42d6, 0x432a, 0x41e0, 0xbf48, 0x3377, 0xb282, 0xabfc, 0xb0a9, 0xb2cf, 0x1ba6, 0x45c8, 0x4080, 0x1e90, 0xb0f0, 0x462e, 0xb229, 0x02cf, 0x41ba, 0x41b8, 0x4210, 0x3f35, 0x398c, 0xba39, 0x413b, 0xbf64, 0x21a7, 0x40b8, 0x0509, 0x4388, 0x20cc, 0xba62, 0x41b9, 0x1efd, 0x41c7, 0x413e, 0x40a3, 0x4136, 0x1b72, 0x4366, 0xba01, 0xb2e0, 0x428b, 0x438e, 0xa279, 0x43d2, 0x1b4e, 0x43ac, 0xbf99, 0x0ff8, 0x36ad, 0x46c3, 0x43e4, 0x42ae, 0xba6e, 0x368c, 0x12b7, 0x43cf, 0x405e, 0x1e52, 0xb28e, 0x0282, 0x280c, 0x4294, 0x2cf6, 0xbfd8, 0x418c, 0xa006, 0x4179, 0xb2b4, 0x415d, 0x0cba, 0x1502, 0x46da, 0x43cf, 0x42bd, 0x411a, 0x4037, 0x4265, 0x4268, 0xbfa9, 0xa149, 0x1de5, 0xb2d1, 0x416a, 0xb0af, 0x4390, 0x4302, 0xbf37, 0x36bf, 0x40ef, 0xaa8e, 0x1731, 0x2537, 0xaa7a, 0x439d, 0xbfdd, 0x416c, 0xb271, 0xb0a0, 0xb0cb, 0x462a, 0xa0ff, 0xaf7c, 0x4338, 0x41c6, 0x40e4, 0x430f, 0xbfae, 0x24ea, 0x41d3, 0x1baf, 0xbf0a, 0xb2b5, 0x415b, 0x4574, 0x42dd, 0x1886, 0xb298, 0x40e3, 0x23f6, 0x43b1, 0x4179, 0x421d, 0x00f3, 0x44fb, 0x43f2, 0xb2da, 0xbf43, 0x329d, 0x40df, 0x41f5, 0x43a4, 0x410f, 0xb070, 0x40c3, 0x0290, 0xba76, 0x40c0, 0x03b8, 0x449a, 0x1b97, 0x0f78, 0x419b, 0x422e, 0x179f, 0x4123, 0xa591, 0x1e2b, 0x1fda, 0x24bb, 0x412f, 0x4204, 0x4331, 0x40cc, 0x3aa9, 0xbfd7, 0x443b, 0x43ac, 0x130f, 0x4328, 0x2575, 0xba56, 0xb246, 0xa7fe, 0x46ac, 0x3801, 0x43dd, 0xba2b, 0x39e7, 0x0bd0, 0x42b3, 0x4038, 0x41f9, 0x4393, 0x4102, 0xb203, 0xb2f8, 0xa22e, 0xa526, 0xb08e, 0x2e59, 0x2b55, 0xb25f, 0x4663, 0xbf3e, 0x4005, 0x43c8, 0xb034, 0x3d71, 0x3d1d, 0xb258, 0x4322, 0x43a8, 0x4052, 0x2a11, 0x434d, 0xba44, 0xb283, 0x0014, 0x18b3, 0xbf85, 0x4236, 0x370f, 0xa4a4, 0x4081, 0xbfae, 0x3107, 0x4246, 0x4159, 0x40f4, 0xb262, 0x1bd6, 0xb275, 0x41ea, 0xbf8e, 0x40f6, 0x4004, 0xba73, 0xbf01, 0x42a3, 0x419b, 0x418a, 0x4309, 0x4300, 0x4053, 0xa24b, 0x43b2, 0x4105, 0x417a, 0x42ee, 0x3a99, 0x41b6, 0x1c97, 0x43e8, 0xbf95, 0x421b, 0xb22f, 0x468c, 0xba30, 0x171d, 0x42e5, 0x4283, 0xbf78, 0x1e0f, 0xb02e, 0x3bce, 0x46f2, 0xbaeb, 0x1a73, 0x1d12, 0xba2a, 0xa2cb, 0xb23a, 0x42a8, 0xa26b, 0x3c45, 0x0a4d, 0x1ce2, 0x1a89, 0xbfa3, 0x462f, 0x45f1, 0x0ccd, 0x19fc, 0x4432, 0xba68, 0x402c, 0x43bd, 0x4106, 0x2d24, 0x08ae, 0x41ee, 0x4395, 0x438a, 0x410d, 0x414e, 0x40c1, 0x41f5, 0xbae6, 0x2a67, 0x0307, 0x1b7c, 0x42fa, 0xba7a, 0x40e7, 0xbad0, 0xb218, 0x4551, 0xbfbc, 0xbfc0, 0x44e1, 0x19e1, 0xb2fc, 0x423b, 0xbf07, 0xb295, 0xb295, 0x4409, 0x42c5, 0xb285, 0x3d52, 0xbf66, 0xbf70, 0x4244, 0xba58, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x35dcd53f, 0x329b21b1, 0x5b679e3c, 0x95697b24, 0xded610ce, 0x5fb2bf66, 0xe3f3845c, 0xe4e7d67e, 0xb27d6955, 0x52a7f817, 0xf1c67637, 0xa8f2cd39, 0x50a9900e, 0xbf067896, 0x00000000, 0xb00001f0 }, + FinalRegs = new uint[] { 0xe8fef8ea, 0x08800000, 0x20020000, 0xfee8eaf8, 0x00000000, 0x0000eaa6, 0x00000000, 0x02200000, 0x0525616d, 0x52a7f817, 0x00000000, 0x052577df, 0x01171508, 0x00011300, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x2155, 0x1e10, 0x3dda, 0xa4a1, 0x40e5, 0xba3b, 0x40e6, 0x1cbe, 0xbf21, 0x3982, 0x403a, 0x3b83, 0x40d0, 0x1dbd, 0x4633, 0xbf27, 0x40fa, 0x4217, 0xba03, 0xa0e8, 0x4051, 0x43d7, 0xb2ad, 0xb23a, 0x4456, 0x40ca, 0xb28a, 0x4598, 0x414f, 0x183b, 0xbf47, 0xb04c, 0x2d55, 0xb200, 0x410d, 0x423c, 0x427c, 0x43e2, 0xbf75, 0x438e, 0x420c, 0xb040, 0x431f, 0xaaec, 0x1ea1, 0x4219, 0x0e67, 0x4107, 0x258a, 0x4363, 0x4209, 0xbfa0, 0x4244, 0x1fed, 0x082e, 0xa5ef, 0xba10, 0xb215, 0xbf60, 0xbadd, 0x441f, 0x4360, 0x4416, 0x0ced, 0x4396, 0xb2a5, 0xbfbd, 0x020b, 0x10f7, 0x40bc, 0x43fa, 0xba04, 0xb203, 0x424b, 0x3ef2, 0xb21f, 0xb2db, 0x18b7, 0x41eb, 0x32b6, 0x4415, 0x405d, 0x19be, 0x124d, 0xb067, 0x4048, 0xbf7e, 0x4163, 0x4494, 0x2c27, 0x41a4, 0x4094, 0x417b, 0xbadb, 0xb221, 0xac56, 0x4045, 0xbf00, 0xbf9e, 0xb02c, 0x1467, 0xa9eb, 0xb28c, 0x42f0, 0x42f1, 0x2b99, 0x10b3, 0x4181, 0x4565, 0x16bf, 0xbaf0, 0x3ce0, 0x0fdb, 0x182c, 0x4008, 0xb253, 0x405c, 0x4291, 0x427d, 0x1b26, 0x4275, 0x424d, 0x400e, 0xbf97, 0x24cc, 0xb0ac, 0x00e5, 0x4646, 0x2dad, 0xbfd5, 0x416b, 0x43dc, 0x15af, 0x405f, 0x42ae, 0xaffc, 0x435c, 0x407e, 0x25a2, 0xb0a1, 0x26af, 0x4440, 0x2f9f, 0x0c00, 0x44e2, 0x4079, 0x0965, 0xb21f, 0x43b0, 0x081f, 0x441c, 0xbf5c, 0x4350, 0x1b32, 0x41d9, 0x46d8, 0xba76, 0x42cf, 0x426e, 0x438e, 0x0cf5, 0xb00b, 0x414f, 0xba7f, 0x2ea0, 0x4601, 0x43de, 0x2e67, 0x0090, 0xb095, 0x43b4, 0x1edf, 0x4695, 0xb264, 0x0acf, 0xbf11, 0x42f8, 0xa71c, 0x42c7, 0xb29e, 0xb223, 0x08a7, 0x4425, 0x40c5, 0x4342, 0x466f, 0x1e7a, 0x425e, 0xba7c, 0xbaef, 0x4195, 0x40f0, 0x4105, 0x182a, 0xb249, 0x01b6, 0x43db, 0x33cd, 0xb098, 0x22ef, 0x4670, 0x1c06, 0x4143, 0xb2ad, 0xbfdd, 0x44ea, 0xba3e, 0x43d7, 0xb251, 0xba39, 0xba0d, 0x40bf, 0x46a2, 0x20a1, 0x41ab, 0x3578, 0xafc6, 0x19e4, 0x4127, 0xb06d, 0xacfe, 0x1d9e, 0xbf31, 0x43da, 0x0408, 0x42e6, 0xb2ca, 0x400e, 0xa0f7, 0x42c4, 0xb24e, 0x401f, 0xb220, 0x4494, 0x4410, 0xbf15, 0x417b, 0x42eb, 0x151d, 0x4295, 0x4188, 0xbae2, 0x40d5, 0x40c3, 0x1d6c, 0xa78d, 0x4059, 0x46ad, 0x1903, 0x1b14, 0x407f, 0xb249, 0x43cf, 0x415f, 0x4423, 0x410e, 0xba10, 0x0aac, 0x40ae, 0xa148, 0x0bda, 0xbfb6, 0x4161, 0xb0c0, 0xb2b3, 0xacde, 0x43b8, 0x40c4, 0xb2b5, 0x4476, 0x41cf, 0x4245, 0xbf2a, 0x1c2a, 0x34fe, 0x089d, 0x1943, 0xa5a5, 0x4611, 0x4013, 0x305a, 0x1a16, 0x43f1, 0x4118, 0xb2a5, 0x44a3, 0x1aa6, 0x4055, 0x412d, 0xb2c1, 0x43cf, 0x0ae2, 0xbf28, 0x4299, 0x42ac, 0xb203, 0xa899, 0x4160, 0xbf56, 0xbafe, 0x40d0, 0xb2ea, 0xb2b1, 0x1f3a, 0x1cf9, 0x43fd, 0xbf76, 0xba48, 0xbaca, 0xb2d1, 0x4268, 0x43a9, 0x465a, 0x1f70, 0xb0a5, 0xb021, 0x4016, 0xae2f, 0x4012, 0x3ad1, 0x402f, 0x3c2d, 0x425e, 0x40a7, 0xba78, 0xbf2f, 0x226a, 0x44a0, 0x417a, 0x040e, 0x0659, 0xb288, 0x41b4, 0xba78, 0x0a09, 0x4165, 0x2bfb, 0xbf05, 0x4222, 0x1bfa, 0xa40a, 0x43ab, 0x41db, 0x43e3, 0x419f, 0x4694, 0x43d0, 0x35e8, 0x1f83, 0x4116, 0x4206, 0xac92, 0x4034, 0x220e, 0x413e, 0xad23, 0x4116, 0x4430, 0xbf60, 0x0e3f, 0x1c57, 0xac22, 0x3c5b, 0xb208, 0x098b, 0xbf0c, 0x42db, 0x3b41, 0x435e, 0x4302, 0xbf19, 0x3fc7, 0x4222, 0xbac3, 0x1528, 0x427e, 0x428a, 0xb2ce, 0xb09d, 0x41f4, 0x435c, 0xba54, 0xbf3f, 0x2dc4, 0x1526, 0x4335, 0xba05, 0xbf5f, 0xb252, 0x4431, 0x2474, 0x4260, 0x4278, 0x41eb, 0x43f7, 0x41bc, 0x4274, 0x2a23, 0x1ea8, 0xb024, 0x163b, 0x4365, 0x3c89, 0x424f, 0xba1c, 0x43d1, 0x42ac, 0xbac7, 0x405a, 0xb2d1, 0x1d69, 0xb2a2, 0x41bf, 0x412b, 0x42f5, 0x141d, 0xbf5b, 0x43f7, 0x0b65, 0x4124, 0xbacc, 0x414a, 0x39dc, 0x4290, 0x160f, 0xb2c6, 0x46b2, 0x43b8, 0xbf90, 0x417c, 0x41ea, 0x4280, 0x438c, 0x40b9, 0x1fc0, 0x0291, 0x46e2, 0x432a, 0x4206, 0x2149, 0x06e2, 0xb218, 0xba0d, 0x3fc3, 0xbf68, 0x46c8, 0x4166, 0x437d, 0x41a0, 0xb00f, 0x26f5, 0x42b6, 0x466c, 0xaf14, 0xba28, 0x0364, 0x0f78, 0xa3a4, 0xbf6e, 0x4572, 0xb2fe, 0xb046, 0x413c, 0xbaf3, 0x4370, 0x4005, 0x2268, 0x42af, 0x4278, 0x4089, 0x439f, 0xbfd6, 0xaed2, 0x4305, 0xb2e2, 0xb2f6, 0xbf18, 0x42a3, 0x1ee9, 0x41bc, 0x1d48, 0x4313, 0xa6e2, 0x407a, 0xb09e, 0x459c, 0xb24b, 0xb015, 0xaa03, 0x18b9, 0xacea, 0x1e86, 0x4169, 0xbfc8, 0x1f6a, 0x4396, 0x347f, 0x182d, 0x3184, 0xba54, 0x221f, 0x40d0, 0x337e, 0x4272, 0x40c0, 0xb0de, 0x430d, 0x4105, 0x315e, 0x11cc, 0xbf90, 0x228a, 0x4273, 0xbada, 0x006d, 0xbf2a, 0x4334, 0x18c8, 0xb2d9, 0x1cf8, 0xba7c, 0xbaef, 0x4461, 0x2973, 0x2d26, 0x4042, 0xaba4, 0x4186, 0xb05c, 0xbfd4, 0x4254, 0x1e78, 0x4643, 0xb29a, 0x427a, 0x4370, 0x1f71, 0x419a, 0xa4c1, 0x07c9, 0xbf80, 0x3f05, 0x405f, 0x3150, 0xbf37, 0x40ce, 0xba27, 0xb2a0, 0x2398, 0xbfca, 0x43e4, 0xaa17, 0xba74, 0xb0d6, 0xb08f, 0x42c9, 0x42cd, 0xb224, 0x1ccf, 0xafbc, 0x4060, 0xb0f5, 0x423d, 0x43c4, 0xb2fc, 0xbac2, 0x4017, 0xb265, 0x431a, 0xbfca, 0x43a8, 0x415f, 0x40ff, 0xb0d7, 0xbae4, 0x1b93, 0xb2e9, 0x1a43, 0x28ac, 0x427c, 0xb215, 0x4017, 0xbf3d, 0xb2d0, 0x42be, 0x1d8f, 0x447c, 0x40ec, 0xba4e, 0x1910, 0xb041, 0x1fdd, 0x41b7, 0x40c9, 0x42c8, 0xbf60, 0x404f, 0xb27d, 0x4661, 0x4399, 0x43eb, 0xb20a, 0x460b, 0x1c22, 0xb26f, 0xbae0, 0xbff0, 0x421f, 0x1f27, 0x4264, 0x4290, 0xbf93, 0xb2a4, 0xb2a7, 0xbaff, 0xad42, 0x4391, 0xba27, 0x40f4, 0xbfd6, 0x42f8, 0x429f, 0x03c8, 0x40c6, 0x32d1, 0x40c8, 0xaf82, 0x02cf, 0x046b, 0x4285, 0x4291, 0xba45, 0xbadd, 0x40f2, 0x33e6, 0xba09, 0xba1e, 0x4607, 0x45d9, 0x4453, 0xb00d, 0x4222, 0x40b0, 0xbf19, 0xb20c, 0x1ab7, 0x41dc, 0x439e, 0x4221, 0x1f89, 0x400c, 0xb288, 0x42f2, 0x4543, 0x3d97, 0x43e3, 0xba58, 0xaa3d, 0x42d0, 0x1870, 0x26b1, 0xbfab, 0x4112, 0x42d9, 0x467c, 0x4052, 0x4631, 0xb286, 0xbf5e, 0x43c5, 0x44d0, 0x2fb1, 0x43b3, 0xb2ea, 0x1d46, 0x4076, 0x415a, 0x3bbe, 0xbf03, 0x0812, 0x2d4f, 0x1f62, 0x4322, 0x0a58, 0x40e4, 0x1fcd, 0xba52, 0x15b6, 0x43b0, 0x43a0, 0x400a, 0x4303, 0x4297, 0x40e7, 0xb0cf, 0x43e8, 0x15c1, 0x4134, 0x4248, 0x4576, 0x0870, 0x01c4, 0xbf63, 0xbac5, 0x42cd, 0x437d, 0x43e7, 0xb261, 0x42e2, 0xbf18, 0xbac0, 0x4037, 0x40d0, 0x1c84, 0x4149, 0xbf80, 0xb2c6, 0xbfe0, 0x30f1, 0xa467, 0x2e0e, 0xbf6b, 0x46e2, 0x4216, 0x43df, 0x1ab8, 0xbf23, 0x46d3, 0xba71, 0xb208, 0x099f, 0xbf60, 0xba1f, 0x40a5, 0x42d4, 0x21cb, 0x1ffd, 0x40c9, 0x42dc, 0x15f1, 0x279d, 0x2437, 0x0cea, 0xbfdd, 0xbae4, 0x438b, 0x4378, 0x463c, 0x0a74, 0x4005, 0xba1e, 0x41d7, 0x4481, 0x4274, 0x45da, 0xbf46, 0xb0e9, 0x3f19, 0x1bb1, 0x4581, 0x42ce, 0xb2df, 0xba6f, 0xb22f, 0x343f, 0x40d8, 0xae4d, 0x4225, 0x404d, 0x3748, 0x4641, 0x1924, 0xba34, 0xbf27, 0x42ba, 0x1e38, 0x40c4, 0x1e29, 0xbfb8, 0xa804, 0xbaf2, 0xbaec, 0x411a, 0x42e3, 0x402b, 0x404a, 0x4654, 0x403c, 0x229e, 0x4343, 0x4278, 0x420e, 0x414e, 0x1b28, 0x1ec8, 0x4137, 0xbf74, 0x4132, 0x40db, 0xba2b, 0x424e, 0x400f, 0x4342, 0xa268, 0x45cc, 0x41a1, 0xb210, 0x1e13, 0xba32, 0x405f, 0x4070, 0x40a8, 0x4495, 0xaf59, 0x1a8a, 0x4335, 0x1aaf, 0xa0de, 0x4557, 0x429b, 0xbf2b, 0x1a1f, 0xb24a, 0x20ba, 0x4026, 0x4426, 0x415d, 0xba49, 0x1a0f, 0xa6ba, 0x408e, 0xba37, 0xb067, 0xa1be, 0x43f0, 0xb26f, 0x06e2, 0x4101, 0x424b, 0xb004, 0x17fe, 0x417d, 0x4053, 0x133e, 0xbfa1, 0xa51b, 0xaf80, 0x43e6, 0x4031, 0x0893, 0x42a2, 0x113a, 0xbf86, 0xb211, 0xb2e9, 0x1955, 0xb2e0, 0x4249, 0x1fee, 0x43bf, 0x1f72, 0xb29c, 0x41ce, 0x1c6c, 0x1ac1, 0x4216, 0x4309, 0xbf90, 0x4176, 0x4114, 0x0c13, 0xb22a, 0x10be, 0x4293, 0x1255, 0x40dd, 0x1f80, 0xba69, 0xb298, 0x430c, 0x3114, 0xbfc2, 0x4209, 0x4682, 0x4160, 0x461f, 0x4003, 0x4104, 0x27e8, 0xb27f, 0x13b2, 0x4034, 0x061d, 0xb093, 0x30b2, 0x4252, 0xa0b3, 0xa03e, 0x4489, 0x4458, 0xbad3, 0xbf2e, 0x4301, 0x401f, 0x40ed, 0xba6c, 0x41bf, 0xb017, 0xbf5b, 0x4549, 0x4316, 0xb2ac, 0x2a29, 0x19a6, 0x437d, 0xaba0, 0x3226, 0xa26e, 0x1bde, 0x4143, 0x40a4, 0x46ac, 0x420e, 0x43d6, 0x40d2, 0x40e5, 0x059e, 0x41eb, 0xbfb2, 0xaf76, 0x4194, 0x425c, 0x3ca9, 0xbafa, 0x1cd4, 0x40b9, 0xb29b, 0x439a, 0x3586, 0xb28f, 0x43a5, 0x438b, 0x45ad, 0xb200, 0xbf90, 0x387c, 0x1693, 0x40a5, 0x43c8, 0x065e, 0x37af, 0xbf55, 0x4319, 0x41ee, 0x1ee5, 0x1d3e, 0x43f1, 0x1d52, 0x462d, 0x0129, 0xbae8, 0x4613, 0x1d81, 0x2a54, 0xba46, 0x432c, 0x101c, 0xb29a, 0x42c2, 0xbfd0, 0x2520, 0x32e6, 0xba32, 0xba5a, 0x40ef, 0xbf70, 0xa438, 0xbf48, 0x42cf, 0x4220, 0x40f8, 0x0b4e, 0x434e, 0x1909, 0x417e, 0x42ee, 0xbf02, 0xbfd0, 0x157f, 0x408d, 0x42dc, 0xba68, 0xb219, 0xb075, 0xbfa5, 0x2f93, 0x1fb8, 0x40f1, 0xb240, 0xb2b3, 0x2097, 0x42f4, 0x188b, 0xb0e8, 0x4326, 0x4157, 0x2694, 0x404c, 0xbaf8, 0x431a, 0x4308, 0x2760, 0x359d, 0xb290, 0x1cc4, 0x4133, 0x1771, 0x408f, 0x4159, 0xba63, 0x4222, 0xbf7d, 0x44f8, 0x4088, 0xbad6, 0x0287, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x8f725d16, 0x8f2d1fe5, 0xd2b28a66, 0x47a47346, 0xc0b4401b, 0xf8e7fb12, 0x4ba63b76, 0xa9e6ad4c, 0xa1d2cdf2, 0x35a0c989, 0xc174909a, 0x846af94c, 0xe89071ad, 0x3db39c20, 0x00000000, 0x000001f0 }, + FinalRegs = new uint[] { 0x0000414c, 0x00000000, 0x0000414c, 0x00004f41, 0x0000414f, 0x000000bd, 0x00004c41, 0x00000060, 0x08d61086, 0x35a0d48c, 0x00000000, 0x846afcc2, 0x00000000, 0x17fffbb8, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xbafe, 0xb286, 0x0988, 0xb25a, 0x4061, 0x1ed9, 0x4663, 0x430f, 0xb280, 0x2590, 0xb0a2, 0x43b0, 0x0a42, 0xa6b5, 0x4171, 0x3320, 0x4145, 0xb286, 0xba32, 0x1307, 0xbf7c, 0x435e, 0x1bc9, 0x41ff, 0x1e42, 0xb09a, 0x444e, 0x40b1, 0x4016, 0xbaca, 0x411c, 0x2fb8, 0x431a, 0xbf01, 0x4360, 0x41d8, 0x1e44, 0x1331, 0x3c8c, 0xb04e, 0x4296, 0x4548, 0xbf2e, 0x2139, 0x403d, 0x197a, 0xbf90, 0x1c14, 0x4472, 0x3fa6, 0x0f32, 0x21ac, 0x407e, 0xbaf8, 0xb255, 0xba53, 0x1b89, 0xbf65, 0x18ef, 0xba18, 0x405b, 0xb0bd, 0x0ebd, 0xb204, 0xba30, 0x413b, 0x4143, 0x4316, 0x41e9, 0x4367, 0x41c5, 0x1673, 0x103e, 0x09a5, 0x2ef0, 0xb263, 0x43e6, 0x42ec, 0x1c44, 0x082b, 0x45c5, 0x439b, 0xb2e9, 0xa969, 0xbf7b, 0xbfd0, 0x438b, 0x4132, 0xb0ec, 0x431b, 0x34ab, 0x0f49, 0xa401, 0x4124, 0xacc3, 0x4314, 0x1cf2, 0x3c2e, 0x309c, 0xb0be, 0xb0a3, 0x40e6, 0x4314, 0x41ab, 0x122b, 0xb2c2, 0x13c9, 0x423d, 0x423e, 0xbfce, 0x4175, 0xaa5d, 0x0876, 0x1c1c, 0xbad3, 0x4248, 0xb298, 0x0069, 0x16ba, 0x40c0, 0xbfc1, 0x43c4, 0x17a8, 0xa13f, 0x41a8, 0x40d6, 0x4680, 0x4319, 0xba2a, 0x44b8, 0x4470, 0xb0c8, 0xbf6d, 0x4303, 0x1c1a, 0x439b, 0xb271, 0xba0b, 0xb236, 0x41a2, 0x4071, 0x431e, 0x395c, 0xadd7, 0xba48, 0x4163, 0x40ee, 0x4195, 0x2da0, 0x19e4, 0x32b4, 0xb2ca, 0xb0b7, 0x45f5, 0xbfbd, 0x3c50, 0x23c9, 0x2092, 0x4424, 0x410a, 0x40d8, 0x43ee, 0xb2a8, 0x1d9f, 0x416e, 0x418c, 0x3fb8, 0x1557, 0xbac8, 0x4659, 0x4131, 0x0f9d, 0x434f, 0xb2a4, 0x41b5, 0xb2a8, 0xbf6c, 0x415e, 0xbacf, 0xb2f5, 0xb20a, 0xb059, 0xbaf2, 0x4003, 0x0bf5, 0x40b9, 0x4480, 0x0151, 0x0a9f, 0x4269, 0x439f, 0xb029, 0x4224, 0x04a3, 0x4365, 0x4071, 0x4630, 0x2d00, 0x4436, 0x1904, 0x3861, 0xbf71, 0x198a, 0xb2ad, 0x0641, 0x3aad, 0x425e, 0xb2e0, 0x00d3, 0xb030, 0xbf78, 0x4089, 0xba44, 0x1789, 0x0fb3, 0x45ba, 0xb2dc, 0xbf3a, 0x186d, 0x42c9, 0x432b, 0xb2ea, 0xb0cf, 0x42d5, 0xb22c, 0xb21e, 0x2355, 0xba01, 0xa56e, 0x41fd, 0xbad1, 0x2bcf, 0x1b37, 0xb2a4, 0x411f, 0x4018, 0x4342, 0x20f4, 0x43c4, 0x43be, 0xbfa1, 0x4139, 0x468b, 0x42c4, 0x1b6e, 0x21aa, 0x42a3, 0xb0cd, 0xb0d2, 0x431e, 0x431d, 0xbfa0, 0x428d, 0x35d6, 0xbf32, 0xae52, 0x431c, 0x1e62, 0xbaf6, 0x4099, 0xb015, 0x4122, 0x19f9, 0x01c1, 0xb2ef, 0xa849, 0x2614, 0xb24a, 0x02c0, 0x43ec, 0x0216, 0x42a0, 0x42c1, 0x43e2, 0x43d9, 0x1ae7, 0x1285, 0xb23e, 0x1d33, 0x187a, 0x3872, 0x4661, 0x3d9c, 0xbfaa, 0x4033, 0x4089, 0x41c3, 0x328b, 0x43d5, 0xb2ab, 0x06f1, 0xbaf9, 0x432e, 0x3160, 0x416a, 0x2c76, 0x4232, 0xbfe8, 0xbf70, 0xbf4f, 0xb20a, 0xb29a, 0x16b0, 0xb2f3, 0xb02f, 0x1ed3, 0x42e1, 0x44f8, 0xb279, 0xba78, 0xbff0, 0xb2ae, 0x433e, 0x388b, 0x3cac, 0xbf3f, 0xb2ab, 0x31d8, 0x34a0, 0xaa1a, 0xb22d, 0x3df9, 0xb28a, 0x43d6, 0x37ab, 0xb228, 0xbf1b, 0x41d9, 0x062a, 0xb256, 0x1c7f, 0xa48c, 0x441d, 0x403b, 0x20fc, 0x1841, 0x4260, 0xb280, 0xbfb0, 0x41ee, 0x4340, 0xb0dc, 0x415d, 0x41c7, 0x41c2, 0x4565, 0xb239, 0xba36, 0x435e, 0xb2c3, 0x0432, 0x434a, 0xbfd7, 0x4127, 0x26c1, 0x38e4, 0x4210, 0x400c, 0xb0ac, 0x46f9, 0xba4d, 0x4331, 0x2faf, 0x4088, 0x4014, 0x2e91, 0x42e5, 0xb0dc, 0xbfb3, 0x0e14, 0x42b4, 0xb251, 0x45a4, 0xbf13, 0x4460, 0xba07, 0x2702, 0x406e, 0xbfc0, 0x40d8, 0x3d8d, 0xb231, 0xb067, 0xb0cf, 0xba32, 0x4334, 0x29fc, 0x4051, 0x4068, 0x4617, 0x361f, 0x4013, 0x42c3, 0x1ff9, 0x437f, 0xba51, 0xb24f, 0x4150, 0xbf4f, 0xb2b8, 0x1a70, 0xaf84, 0x410e, 0xb207, 0x4041, 0x42f2, 0x4637, 0x00ae, 0x41b1, 0x411f, 0x4333, 0x41ae, 0x1d1b, 0xbf5b, 0x4313, 0x393e, 0xb277, 0xbfc0, 0x4103, 0x18c6, 0xae36, 0x429a, 0x42c7, 0x464b, 0x1569, 0x035a, 0x41c4, 0x3350, 0x209f, 0x43c8, 0x416e, 0x4305, 0x1daa, 0x43a6, 0x4325, 0x4248, 0x3079, 0xbf01, 0x4072, 0xafc0, 0xb299, 0x42a7, 0x422d, 0x40f4, 0xb2d7, 0xb2d8, 0xb006, 0x4318, 0xbf61, 0xb2c3, 0x2017, 0x4172, 0xa800, 0x43b6, 0xba42, 0x4367, 0x43a3, 0xb08f, 0x4267, 0x43c8, 0xb0f0, 0x0970, 0xbacc, 0x1b86, 0x4177, 0x439a, 0x41ce, 0x4053, 0x4307, 0xb29b, 0x3708, 0xb29f, 0x25a5, 0xb279, 0x4329, 0xb2df, 0xbf3c, 0x40c9, 0x435c, 0xbfd9, 0x4180, 0x01f8, 0x37cd, 0x4087, 0xbf80, 0x41fb, 0x43c3, 0xba0f, 0xb214, 0x1f01, 0x43e2, 0xbf28, 0xb2ce, 0x4181, 0x19db, 0x4124, 0xbff0, 0x170e, 0xb078, 0x41d6, 0x4325, 0x4033, 0xba21, 0x42b9, 0x15d9, 0x42c2, 0x0491, 0x42b1, 0x40c8, 0xba19, 0x4411, 0xbae6, 0xbac3, 0xbf61, 0x427b, 0x4341, 0xb2ba, 0xb233, 0xb2fa, 0x40c7, 0xb0b5, 0xb2b9, 0xb23a, 0x433a, 0x418e, 0xb240, 0xbf3d, 0x12df, 0x405d, 0x0bfe, 0x1c0d, 0xbff0, 0xb2a2, 0x42dc, 0x08fb, 0x42c4, 0x4129, 0x1eee, 0xba63, 0x41f4, 0xbfdd, 0x4239, 0xa32e, 0xbac6, 0x41dc, 0x1a05, 0x41dc, 0x3616, 0x4401, 0x40da, 0x4278, 0x15fd, 0x19be, 0xafdf, 0xada3, 0xbf22, 0x0b84, 0xbf90, 0x4558, 0x425f, 0x12b9, 0x42de, 0x43a1, 0x31dc, 0x1fb3, 0x1f66, 0x4313, 0x0dee, 0xa10b, 0x4272, 0x0ff7, 0x40cf, 0xbfb0, 0x4614, 0x4263, 0xbfd7, 0xa80c, 0x43d9, 0x4690, 0xba05, 0x40b9, 0x1a89, 0x3326, 0x43eb, 0x0377, 0x43f4, 0x4182, 0xb066, 0x25ac, 0x4076, 0x1848, 0x02ff, 0xbf2b, 0xb291, 0x4646, 0xa320, 0xb077, 0x41b0, 0x412b, 0x422b, 0x0c68, 0xbf62, 0x4139, 0x413d, 0x2f7a, 0x41a3, 0x417e, 0xb0c1, 0x3b6a, 0xb0d5, 0xb2a6, 0x43ef, 0x1a5e, 0x1de5, 0x4215, 0x211f, 0xbfd5, 0x1f96, 0x4694, 0x05d2, 0x2a85, 0x4460, 0x4180, 0x41bc, 0x30a7, 0x419a, 0x401d, 0x26d1, 0xb009, 0x4267, 0x40f1, 0x413e, 0xae3b, 0x4351, 0xa082, 0x3aec, 0xbf49, 0x42b4, 0x3cdf, 0xb031, 0xb225, 0xbf90, 0xb21e, 0xa23e, 0x3d65, 0x2014, 0xa8dc, 0xba0c, 0x4217, 0x17df, 0x4234, 0x18ea, 0x41f5, 0x0639, 0x45e4, 0x34d4, 0x4207, 0x0eb1, 0x421c, 0x408c, 0x405e, 0xa02e, 0xb273, 0x4109, 0x0240, 0x42a9, 0xbfcf, 0x0d28, 0x43ce, 0x2281, 0x430e, 0x2cde, 0xb08c, 0x4013, 0x165a, 0xb0db, 0xad8f, 0x3ed1, 0x111b, 0x461d, 0x08fb, 0x4074, 0x4076, 0x4030, 0x41a4, 0xb210, 0x144d, 0x24dc, 0x4063, 0x1890, 0x4313, 0x38c2, 0x416f, 0xbfc9, 0x270d, 0xb276, 0x4308, 0x1f80, 0x2858, 0x3b1a, 0x397f, 0x3b6e, 0x19f9, 0x43b3, 0x4249, 0xb2c7, 0x1f5a, 0xba0f, 0x42f8, 0xbfc3, 0xb09c, 0x1cc4, 0xa140, 0x4315, 0x4080, 0xb22d, 0x437d, 0x461f, 0xbada, 0x41af, 0xbf3c, 0x306b, 0x43ce, 0xb244, 0xba73, 0x4193, 0x400a, 0x1af3, 0x403f, 0xb26e, 0x1bee, 0x43a8, 0xba08, 0x400d, 0x4215, 0x3529, 0xba5b, 0x2623, 0xba16, 0x4025, 0x40d8, 0x43e9, 0x4225, 0x4112, 0xb2f4, 0x41db, 0x1c09, 0x1ece, 0xbf27, 0x1ac8, 0x4338, 0x4015, 0x4299, 0x4094, 0x4662, 0xba14, 0x43bd, 0x4082, 0x0e05, 0x40c2, 0x413e, 0x43bb, 0x42a6, 0x4146, 0x0cc0, 0x1a0b, 0x1c27, 0x4104, 0xbf93, 0x2799, 0x46ed, 0x1c30, 0xbfc0, 0x422b, 0xa622, 0x1866, 0x15a6, 0xbf80, 0x4334, 0x4060, 0x07a4, 0x14d3, 0xa26f, 0xbae9, 0xbf44, 0x4033, 0xba7e, 0xad85, 0x4045, 0x1bc4, 0x43b8, 0x43ed, 0x10c5, 0x4200, 0x4330, 0x44cd, 0x36e3, 0x4004, 0x4114, 0x438e, 0xba11, 0x4297, 0x19b2, 0x4222, 0xa7bb, 0xbfb4, 0xb0db, 0xba70, 0x430d, 0x4017, 0x1848, 0x1390, 0x4078, 0x4204, 0x42df, 0xba34, 0x414f, 0x4353, 0x4205, 0x01ea, 0xb249, 0xbf17, 0x4208, 0x42a3, 0x456e, 0xba29, 0xbf32, 0x430b, 0x19a9, 0xb09a, 0x41e6, 0xb0a6, 0x401f, 0xa293, 0x40bc, 0x1df0, 0x40df, 0x3ec9, 0x063a, 0x4255, 0xba51, 0xbf34, 0x405f, 0xb0d1, 0x3863, 0x0052, 0x1541, 0xa37a, 0x429d, 0x4285, 0x2a8b, 0xbf8b, 0x429e, 0x1dc2, 0x4193, 0xb249, 0xb0fe, 0x4135, 0x1864, 0xa8fd, 0xbfc0, 0x23fa, 0x4029, 0xb2b7, 0x4319, 0x408a, 0x4315, 0x4221, 0x1257, 0xb041, 0x0dcb, 0xbfb3, 0x4262, 0xb293, 0xbac4, 0xb20c, 0xa4ca, 0xbad8, 0x4215, 0x4188, 0x401a, 0x1a27, 0x41ce, 0xb282, 0x0e98, 0x4075, 0x40a9, 0xbafe, 0x3362, 0xb25e, 0x4165, 0x1cb7, 0x4025, 0x374e, 0xb2ac, 0x1b9c, 0xb28a, 0xbacb, 0xbf57, 0x2d7c, 0xb010, 0xa274, 0x3dab, 0x41a6, 0x44b9, 0x3456, 0xa547, 0x4304, 0x4056, 0x4359, 0x1e93, 0x416e, 0x4009, 0xba31, 0xbf6f, 0x410e, 0x40ac, 0x1854, 0xb0c6, 0xba4c, 0xa9db, 0xb2c3, 0x1cc0, 0x053b, 0xbf51, 0x04d7, 0x44f3, 0x4598, 0x4157, 0x4483, 0xb2f7, 0xbf5a, 0x40f9, 0x4075, 0xa00f, 0xbae4, 0xb0d9, 0x3a47, 0xbaec, 0x43b8, 0xb28f, 0x4308, 0xa8af, 0x4159, 0xb2c7, 0x4025, 0x2d96, 0x406f, 0xb28e, 0xb0e9, 0xbf44, 0xb2ba, 0x4236, 0x0e9d, 0x43cf, 0x420d, 0xb2f9, 0xb228, 0xb038, 0x42de, 0xb283, 0x43a7, 0x013a, 0xba3d, 0xb208, 0xa6da, 0x44fa, 0xb2cf, 0x3eb1, 0x1b4e, 0x404b, 0xbf60, 0xb2ae, 0x43a6, 0x44cb, 0x4297, 0xa637, 0x403f, 0xbfd8, 0xbfe0, 0x40ee, 0x41ed, 0x407c, 0x1927, 0x1c96, 0x4239, 0x19b4, 0xb25c, 0xbf3d, 0x20cf, 0xb025, 0xb2a8, 0x1849, 0x297d, 0x427e, 0x15b8, 0x42df, 0x4050, 0xb2fa, 0x4151, 0xbf3d, 0x267a, 0xb23f, 0x4098, 0x42cf, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xc83b3c92, 0xbd911bb4, 0xd5a5a9bb, 0xeab26e45, 0xe6aa6cea, 0xa200e8c6, 0x4e453677, 0x79cc53e1, 0x299e08fa, 0xe4a48c55, 0x96623e63, 0x103ae3c6, 0x15c81382, 0xac554abf, 0x00000000, 0x900001f0 }, + FinalRegs = new uint[] { 0x00000000, 0x000001fd, 0x000000fe, 0x000000fd, 0xfffffffd, 0xff5e0000, 0x0000007a, 0x000043fe, 0xbfc51110, 0x00001384, 0x966255f3, 0x00004087, 0xfffffea7, 0xac554d4d, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xba07, 0x1e95, 0x30c3, 0x4194, 0x2be0, 0x1a8a, 0x404b, 0x4110, 0x4339, 0x444c, 0x46ec, 0xb2bb, 0xba43, 0x2218, 0x4228, 0x40af, 0xa05c, 0xb02b, 0xb297, 0xb2a6, 0x41fe, 0xbf38, 0x43f7, 0x43a4, 0x06aa, 0x41ce, 0x4586, 0x42ed, 0xbafd, 0xb020, 0x4274, 0xa264, 0x356c, 0x43f1, 0x3264, 0xac1d, 0xb252, 0x430b, 0xab27, 0xb2e9, 0xbf90, 0x1aea, 0xbfdb, 0x193b, 0x4338, 0x1bee, 0xb2c8, 0xbac2, 0x420f, 0xbf0d, 0x4206, 0xb266, 0x2a23, 0xbae6, 0x40ff, 0xbfb0, 0xb06e, 0x1a93, 0x1bfa, 0x3b77, 0x4167, 0x420b, 0xba05, 0x2a9d, 0xba3d, 0x431f, 0xba6f, 0x41e5, 0x406a, 0x307a, 0x4298, 0x41cc, 0xb202, 0xb0a0, 0xafbe, 0xbfde, 0x4625, 0x43f4, 0x1b22, 0x41c9, 0x0924, 0xbac1, 0x424e, 0x4024, 0xbf28, 0x4128, 0xb2f2, 0xa079, 0xb078, 0xb252, 0x2be8, 0x41f0, 0x4207, 0x035e, 0x42cc, 0x3a3d, 0x43d3, 0xb2ac, 0xb2e1, 0x40ec, 0xa1c3, 0xb0ae, 0xb226, 0x4084, 0xb0b4, 0xae1e, 0xb206, 0xbf2c, 0x3a9c, 0x42e1, 0x40a8, 0x0650, 0xbfe0, 0x1d2d, 0x1ddf, 0x40fe, 0xbaf2, 0x400a, 0x01fa, 0x1bc9, 0x45bc, 0x4111, 0x467b, 0x1255, 0xa90c, 0x422e, 0x1d2f, 0x00a2, 0x434f, 0x383d, 0x3bbd, 0x41b1, 0x42c7, 0xb2d2, 0x0a93, 0xbf73, 0x403d, 0xba68, 0x428b, 0xb087, 0x1943, 0x1ea1, 0x1cfe, 0x43b4, 0x1c92, 0xb215, 0x1ffa, 0xb20e, 0x40a8, 0x0631, 0xb2fe, 0x42fb, 0x4690, 0xb2e8, 0x411b, 0xa461, 0x415d, 0xb0dd, 0xbfdc, 0xbac6, 0x40b3, 0xb269, 0xb2c0, 0x10c9, 0xbac9, 0xba5d, 0x40c1, 0xbf6e, 0xa5fa, 0x1681, 0xb0da, 0x1f93, 0xb20c, 0x4624, 0xb067, 0x414c, 0x179d, 0xba2a, 0x41f6, 0x4320, 0xbf90, 0x430e, 0x45b1, 0x4008, 0x4273, 0x1619, 0xbf18, 0x093d, 0x42f5, 0x101c, 0x408e, 0xb2a8, 0x4068, 0xba64, 0x1b43, 0x445e, 0x4130, 0x424f, 0x1af2, 0xb2a9, 0xb2c7, 0x1aeb, 0x0e84, 0xb2fc, 0x42a0, 0x06a4, 0x408a, 0xbf8f, 0x40e0, 0xbf70, 0x4673, 0x3809, 0x432d, 0x4011, 0x41de, 0xb03e, 0x42e6, 0xbaf3, 0xb0ef, 0x40e5, 0x4616, 0x40e3, 0x445b, 0xbf60, 0x46d3, 0x41a9, 0x4012, 0x189d, 0xba3b, 0x42ea, 0x418b, 0xbfba, 0x4306, 0x4106, 0x4177, 0x1a98, 0x1fa5, 0x4220, 0x466e, 0xb272, 0x42ab, 0xae2c, 0x41d9, 0x4335, 0x400a, 0xbf80, 0x34cf, 0x060e, 0xb233, 0xba1c, 0x2797, 0xb0a7, 0x446d, 0xb265, 0xadc0, 0xaf88, 0xbf5b, 0x418b, 0x40b3, 0xa037, 0x40fc, 0x4186, 0x4294, 0x43d1, 0x415d, 0x43a0, 0x438f, 0xbf9e, 0x461d, 0x198d, 0x388a, 0xada6, 0x41f7, 0x4335, 0xb24f, 0x2507, 0x1d8e, 0xb29a, 0x1f43, 0x42d2, 0xbae6, 0x42de, 0xa28d, 0x1aeb, 0x43dd, 0x40f9, 0x4253, 0x22a2, 0xa9b0, 0xb232, 0xbacb, 0xba56, 0xa533, 0xba38, 0x4154, 0xba5c, 0xbf15, 0x35e5, 0xb2c0, 0x43ce, 0x0e9c, 0x46eb, 0xbfc0, 0x40eb, 0x44fc, 0x42f3, 0x4337, 0x439c, 0x1aa7, 0x435e, 0x4215, 0x0762, 0x4186, 0x403d, 0x41b2, 0xbf5a, 0x42dc, 0x4097, 0x40f6, 0xb284, 0xba73, 0x4648, 0x408f, 0x407a, 0x1cd9, 0x4446, 0x43aa, 0xbf12, 0x11a8, 0x4164, 0x11f9, 0xae18, 0xb0c6, 0xb0de, 0x43af, 0x405a, 0x4328, 0x4236, 0xace1, 0xa3a0, 0xa0ac, 0x1c62, 0x1dc2, 0x431e, 0xba78, 0x433c, 0x42f8, 0x467f, 0x102a, 0xba5f, 0x422f, 0x4168, 0x431e, 0xa297, 0xbf41, 0x1ac0, 0xb0c8, 0xb2b8, 0x4290, 0x1a1a, 0x2d4d, 0x409e, 0x1b2d, 0x430c, 0xbfe1, 0x41e9, 0xba38, 0x1dc3, 0x400f, 0xba5e, 0x4158, 0xb011, 0x43bb, 0x40f6, 0x1d74, 0xba3c, 0x1af3, 0x4607, 0x2a9a, 0x44c8, 0x40cd, 0x43cd, 0x4253, 0x0b40, 0x419c, 0x1afb, 0x405f, 0x1664, 0xb076, 0x3250, 0x3abc, 0x4282, 0x425d, 0xbf8a, 0xb0dc, 0x2556, 0x41d1, 0x4251, 0x4046, 0x14af, 0x1b2e, 0xb22f, 0x1cbc, 0xb21d, 0x401d, 0x18e1, 0x436a, 0xbfd3, 0x07d1, 0xb2df, 0x24c2, 0x416b, 0x1bd4, 0x1d69, 0x4172, 0x4049, 0x3a4d, 0xacb4, 0x19c2, 0x45a5, 0x1236, 0x2647, 0x3ed1, 0xb03f, 0x43f8, 0xac19, 0x41b9, 0xbf34, 0xba5f, 0xb23c, 0x238f, 0xba3e, 0x4630, 0x4376, 0xb263, 0xb261, 0x431e, 0x4069, 0x1a93, 0x407f, 0x4000, 0x40a3, 0x42c8, 0xbfd4, 0x4322, 0xb2e4, 0xb0ad, 0xb2e1, 0xb273, 0xb203, 0x4064, 0x43b4, 0x144b, 0x41a3, 0x4143, 0xacf2, 0xa4dc, 0xbfe2, 0x4101, 0x406d, 0x4075, 0xba34, 0xb0e0, 0x4010, 0xb247, 0x437a, 0x2989, 0x4375, 0x290c, 0x464a, 0x43b0, 0xb201, 0x4320, 0x4097, 0x2155, 0xaa34, 0x1967, 0xb211, 0x3a11, 0x1522, 0xba36, 0x1b4a, 0xbf6d, 0xb216, 0xb2e3, 0x1c04, 0xa4c9, 0x1b2f, 0xbfe0, 0x43bb, 0xb2af, 0xa800, 0xbf90, 0x4016, 0xba5e, 0xbf7c, 0x4193, 0x19a4, 0x1235, 0x0b1f, 0x429d, 0x2f62, 0x43d7, 0x4341, 0x2659, 0xba31, 0x094f, 0x44db, 0x4034, 0x418e, 0x1f48, 0x46a2, 0xba6a, 0xbf4d, 0xb23d, 0x404d, 0x4111, 0x0d4b, 0x40fd, 0x41d2, 0x403e, 0x30ee, 0xb26f, 0x1ebb, 0x4201, 0xa2a4, 0x402e, 0x40df, 0x2596, 0x4395, 0x2ab3, 0xb276, 0x4287, 0x14a6, 0x4313, 0xbf85, 0x1cc0, 0x46cc, 0x440b, 0x243b, 0x4123, 0x15af, 0x40ae, 0x4336, 0x41b3, 0xb207, 0x40b7, 0x40ea, 0x4288, 0xa9af, 0x40ae, 0x4173, 0x4293, 0x40a2, 0x2d32, 0x12f0, 0x4265, 0x339c, 0x2d1d, 0x4273, 0x4033, 0xb273, 0xa8dd, 0x4088, 0x4393, 0xbf85, 0x200b, 0xbfc0, 0x4370, 0x416a, 0xba73, 0xbafc, 0x40d6, 0xb20a, 0x09bc, 0x366a, 0x3509, 0xbf84, 0x2e0f, 0x419a, 0xb06a, 0x41cd, 0x4115, 0x433e, 0x1cdb, 0x42d0, 0x46f3, 0xb06c, 0x388e, 0x43b6, 0x41ed, 0x190c, 0x43ab, 0x1654, 0x4043, 0x427d, 0x0edd, 0x4022, 0xbaca, 0x0775, 0x18b0, 0xa255, 0xbfb9, 0x1cb8, 0x41c3, 0xb245, 0xa4a7, 0x07dd, 0x37c1, 0x41fb, 0x42d0, 0x349d, 0x42db, 0xbf9b, 0xba23, 0x40a2, 0x43f6, 0x43d2, 0xba6f, 0x368e, 0x4004, 0x1d3f, 0x43ee, 0x4127, 0xbf80, 0xbaec, 0x410a, 0x2bd6, 0x2a9f, 0x40cc, 0x40dc, 0xba0b, 0x43c4, 0x4554, 0x1afc, 0xa651, 0x1ebe, 0x41aa, 0xbf60, 0xb23f, 0xa420, 0xbf4b, 0x4412, 0x4168, 0xb200, 0x42bd, 0x4439, 0x188c, 0x0cce, 0x43a1, 0x1936, 0x36b8, 0x41a5, 0x1fd1, 0x193d, 0x44c9, 0x38d3, 0x40d8, 0xb22e, 0xbfac, 0x1893, 0x2638, 0x17ae, 0x404a, 0x44c8, 0xb257, 0x4097, 0x4328, 0x414e, 0x1e25, 0x424e, 0x41f0, 0x08f4, 0xbfd0, 0xb081, 0x4348, 0x4062, 0x43af, 0xb2fa, 0xbfdf, 0xad9a, 0x14a9, 0x1b10, 0x40c4, 0x430b, 0xba6c, 0xbaee, 0x4670, 0x40cf, 0x415c, 0xb29c, 0x4095, 0xa03c, 0x45f3, 0xbfa0, 0x1b48, 0xbf53, 0xad0c, 0x1f88, 0x3697, 0x42a2, 0x2c12, 0x2442, 0xbad7, 0xa0c2, 0xb277, 0xb0d9, 0x43e8, 0xa0c3, 0x4006, 0x4399, 0xbfca, 0x0cbc, 0xb261, 0x2cc7, 0x417e, 0x40d9, 0x43f3, 0xb27c, 0x09c8, 0xb0b9, 0x125f, 0x429f, 0xbfc3, 0x3d9c, 0x0f46, 0x0047, 0x4258, 0x4081, 0x1968, 0x45a1, 0x292c, 0x4002, 0x4463, 0x1a41, 0x414a, 0xba3f, 0x3ebb, 0x152e, 0x41f9, 0xb25f, 0x4005, 0x36c5, 0x22ec, 0xba69, 0x1ab9, 0x32de, 0xba32, 0x1867, 0xb296, 0xb2c2, 0x06f3, 0xb2e9, 0xbf8d, 0x1ea4, 0x406a, 0x46ad, 0xbacc, 0x4677, 0x4097, 0xbfa0, 0x2090, 0x0a7d, 0x400a, 0x42b6, 0xba75, 0xbaf3, 0x43e5, 0xa474, 0x419e, 0x4425, 0x3cd2, 0x46fc, 0x4043, 0xbfc5, 0x32fe, 0xaf8a, 0x1802, 0x1dfe, 0x432f, 0xb002, 0x42ef, 0xba54, 0x363d, 0x1fc4, 0x4619, 0x4206, 0x4247, 0xb0ef, 0xac69, 0xb276, 0x45b6, 0x4322, 0x3c86, 0x42fd, 0xbf5c, 0x1e9d, 0x0ef5, 0x4118, 0xb037, 0x315b, 0x42a2, 0xb083, 0x4245, 0xba51, 0x46f9, 0xbf70, 0x34d3, 0xb281, 0x43b7, 0xba6b, 0xb0d4, 0xba17, 0x431b, 0x4084, 0x425a, 0x0967, 0x40e1, 0x4571, 0x4429, 0xbf87, 0xb265, 0x4011, 0x4066, 0xba0f, 0xb0f6, 0x4334, 0x1a54, 0x0d0c, 0xb271, 0x4394, 0xbf48, 0x1fae, 0xac17, 0x1fea, 0xb2d6, 0x2631, 0x3ebb, 0x1d7f, 0x46c4, 0xba4f, 0xbf7b, 0x45aa, 0x4240, 0x3de4, 0xbafa, 0x41c2, 0x45b5, 0xba4c, 0x4129, 0x4202, 0x4489, 0x4220, 0x43f3, 0xbfd0, 0x421e, 0xbfa0, 0x0784, 0x4181, 0x42f5, 0x42ae, 0xbfb6, 0x40f3, 0xbaf7, 0x030d, 0x430c, 0xb024, 0x4162, 0x4055, 0x444b, 0x4278, 0xb2f9, 0xbfc5, 0x2d34, 0x41c2, 0x4260, 0xa8c5, 0x0235, 0xb0a5, 0x44b0, 0x2efc, 0x1bbd, 0x41d0, 0x062f, 0xbfba, 0xb25a, 0xb023, 0x46fa, 0xbac7, 0xbfd0, 0x0a43, 0xbf14, 0x3ff5, 0x4073, 0x4130, 0x41a7, 0x2bd7, 0xbf90, 0xb2e5, 0x4030, 0x41bb, 0x4262, 0x2062, 0x4021, 0xb266, 0x1a01, 0x4264, 0xb234, 0x40cb, 0x425d, 0x25e2, 0x414a, 0x3c20, 0xb0ea, 0xa1c7, 0x14e0, 0xbfb7, 0x4170, 0x42fc, 0x43d7, 0x4580, 0xb26d, 0x1a13, 0x44dc, 0xb244, 0x00e2, 0xb088, 0x422a, 0xb203, 0x1ac1, 0xba09, 0xa3ad, 0xbfd0, 0x1c62, 0xbf26, 0x02ce, 0x167e, 0x4108, 0x1b88, 0x4351, 0x4259, 0xb05d, 0x40e7, 0xbf00, 0x42aa, 0x1aac, 0xb201, 0xb036, 0xba1c, 0x429f, 0x033c, 0x462b, 0xa687, 0xbf60, 0x4216, 0xb2b0, 0xb24c, 0x4381, 0xbf75, 0x3c94, 0x4360, 0x143b, 0x40a5, 0xbaca, 0x32ca, 0x109f, 0x353e, 0x1e88, 0x4123, 0x1418, 0x4040, 0x40ae, 0x4332, 0x134c, 0x4211, 0x4271, 0x1ddd, 0x2885, 0x431f, 0xbfc5, 0x1dc1, 0x4296, 0xb2af, 0xb2b3, 0xb2d0, 0x2abd, 0x296b, 0x3323, 0x418f, 0x438a, 0xa4d6, 0x1ae8, 0x0ab5, 0x4173, 0xb095, 0x42db, 0xb226, 0xb03d, 0xbf2f, 0xb210, 0xb247, 0x437f, 0xb2f5, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x804206f6, 0x99583a3f, 0xad754391, 0x26941fad, 0x139e5e23, 0x688cb146, 0x1ba6feec, 0x7e394ee6, 0x68aeb641, 0x77239003, 0x176ac985, 0x4d825eb1, 0xa99614a6, 0x1716467f, 0x00000000, 0x900001f0 }, + FinalRegs = new uint[] { 0xffffffe4, 0x00000000, 0x000021b0, 0x00000023, 0x00001b18, 0x00000018, 0x00001b18, 0x00000310, 0x4a3eb53f, 0x0000169f, 0x00000010, 0x00000000, 0x4a3eb5c9, 0x0603ff82, 0x00000000, 0x000001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x42b1, 0x434a, 0x1868, 0x1910, 0x4205, 0x3246, 0x2096, 0x439d, 0xba7d, 0xba78, 0x180e, 0xa168, 0xafc3, 0x40e6, 0xb2c6, 0xbf60, 0xbf80, 0x46ca, 0x2a71, 0xa766, 0xb269, 0x1f80, 0x14d9, 0x4050, 0xb209, 0xbf72, 0x0b06, 0x4024, 0x41fe, 0xa917, 0xba63, 0xb282, 0x41f2, 0x4171, 0x4288, 0x23f7, 0x032e, 0x4043, 0xb23d, 0xbf12, 0x430e, 0x1c8f, 0x1851, 0x4341, 0x0360, 0x4090, 0x2675, 0x4281, 0x411e, 0x4255, 0xba5b, 0xb283, 0x1af5, 0xb28d, 0xbf33, 0x41ed, 0xb07c, 0xae17, 0x1e11, 0x19ce, 0xb296, 0x33f9, 0x4392, 0xbaf3, 0x40bd, 0x433e, 0x40f9, 0xa383, 0x1b86, 0x1c9f, 0xb096, 0xbf00, 0x4369, 0x42ce, 0x1708, 0x37f1, 0xbf11, 0x431e, 0x28cb, 0x245a, 0x434f, 0x4298, 0x0366, 0x1b85, 0x22a8, 0x4214, 0x42fc, 0x43ca, 0x33ae, 0x42da, 0x1828, 0xabe1, 0x40e4, 0x39be, 0x1a59, 0xb01f, 0xbfb8, 0xb20e, 0x41cc, 0xb2ce, 0x4122, 0xac37, 0x435d, 0xbfc3, 0x4261, 0xb071, 0x24ed, 0xbadd, 0xba07, 0x19a6, 0x1e22, 0x4241, 0x4265, 0xb2f8, 0x401a, 0xb043, 0x4034, 0xb236, 0xb28b, 0x3189, 0xb085, 0xb239, 0x4409, 0x429d, 0x41c2, 0x30cc, 0xbfdf, 0xbaea, 0x40da, 0xb247, 0x1e98, 0xb20e, 0xbad2, 0x4136, 0x1f95, 0xaf3a, 0x2cae, 0x427a, 0x4015, 0x409f, 0x025c, 0xafcb, 0xb20f, 0x43a1, 0x1b77, 0x4222, 0x400b, 0x42c5, 0x1f2f, 0xbfc7, 0xb2ce, 0x4608, 0x4694, 0x42cb, 0x2963, 0x3619, 0x41dd, 0x43ac, 0xb204, 0x3c86, 0xbf05, 0xbac9, 0x1f7c, 0xba2e, 0x0be5, 0x38aa, 0x41aa, 0xa6f7, 0x432b, 0x4124, 0x43fa, 0x1d2d, 0x42d4, 0x1acf, 0x465e, 0x1aa0, 0x32b0, 0x410a, 0x4205, 0xb0b0, 0xbf9c, 0x409d, 0x3b35, 0x1b4f, 0x43eb, 0x189c, 0xbfb3, 0x43f9, 0x42a9, 0x19b1, 0x45cc, 0x1f2f, 0x2d27, 0x1f79, 0x41aa, 0xb20a, 0x420d, 0x45e0, 0xbf1e, 0xb008, 0x4254, 0x2b5d, 0x2382, 0xbf6a, 0x416f, 0xba16, 0x11bf, 0x40de, 0x1a5a, 0x182a, 0x4051, 0x40a8, 0xbf92, 0x41bc, 0xa479, 0x44ed, 0xa6bd, 0x449d, 0x35e7, 0xa2eb, 0x3f57, 0xb0a6, 0x191c, 0x401b, 0xb265, 0x4155, 0x4227, 0x42a1, 0x3f24, 0x1de3, 0x4312, 0x434f, 0x467f, 0xbfce, 0x4054, 0x42c8, 0x18a8, 0x4355, 0x42ad, 0x4139, 0x0b84, 0x2b9b, 0x3f55, 0x42ba, 0xbfc5, 0xa96c, 0x33cc, 0x41c6, 0xba77, 0x405f, 0x4009, 0x4106, 0x1ace, 0x0146, 0xbf1a, 0x1e1b, 0x41fb, 0x42b8, 0xbf82, 0x3dac, 0xaba4, 0xa622, 0x42d3, 0xb060, 0xba3f, 0xbaf0, 0x41b1, 0x4030, 0x410b, 0x1f0b, 0x03e5, 0x42a7, 0x2640, 0x416b, 0xb261, 0x1eaa, 0x4118, 0xba72, 0x4083, 0xbf31, 0x3606, 0xbac0, 0x25ff, 0xa90b, 0xa970, 0x4680, 0xb219, 0x4312, 0xb2b2, 0x4400, 0x1f32, 0xba03, 0x4371, 0x420f, 0x424b, 0xba02, 0x41f4, 0x46c1, 0x3158, 0xab7d, 0xba0b, 0xba2b, 0x42a4, 0x3e0b, 0x02c5, 0x4039, 0x4111, 0xb2f1, 0x0250, 0xbf5d, 0xbaf8, 0x464c, 0x43e7, 0x1baf, 0x41a3, 0x45c1, 0x4369, 0x4113, 0x415e, 0xbff0, 0x2a72, 0x1e9d, 0xba79, 0x083f, 0x41fe, 0x418c, 0x467e, 0x4472, 0x4484, 0xbf62, 0x4671, 0x4698, 0x4141, 0x41d0, 0x29ea, 0x1d92, 0xba36, 0xba01, 0x188a, 0x3f58, 0x43ec, 0x4396, 0xa1b9, 0x4266, 0x40ba, 0x4122, 0x43a4, 0xb269, 0x401f, 0x18f0, 0xb241, 0x4119, 0xb07a, 0xbf95, 0x08c9, 0x37d4, 0x21c0, 0x43e4, 0x4165, 0x468a, 0x40b6, 0xba3b, 0x0e8d, 0xba2f, 0xb287, 0x4223, 0x1bab, 0xb2e8, 0x41dd, 0x4065, 0xbf6c, 0xb23f, 0x2e6d, 0xb2fa, 0x4252, 0x0c56, 0xa14e, 0x4313, 0x42c3, 0x427c, 0x41ca, 0x430f, 0x4187, 0x11c1, 0x17df, 0x40ac, 0xb2c7, 0xb23e, 0xb0b3, 0xa0f6, 0x430f, 0xb25b, 0xbf81, 0xa50d, 0xb258, 0x217d, 0x40a7, 0x1bd7, 0xa080, 0x4248, 0x2643, 0xba66, 0xba2b, 0x4349, 0x1edb, 0x428e, 0x40a9, 0x2b5e, 0x439b, 0x417e, 0x402e, 0x12a6, 0x0e8d, 0xbfbb, 0x424f, 0xb06e, 0x4032, 0xa1c4, 0x4271, 0xb2e8, 0x4674, 0xb0cc, 0x427d, 0x42ac, 0x469b, 0x416b, 0xb201, 0xb2d2, 0x401a, 0x42b5, 0x4397, 0x4299, 0xb238, 0x4664, 0x40a4, 0xbafc, 0xbf7b, 0x1f88, 0x4334, 0x414b, 0x4365, 0x3112, 0x1aa7, 0x1ea7, 0x40f3, 0x1197, 0x45da, 0x411f, 0xbfdb, 0xb020, 0xba4b, 0x1e98, 0x42ea, 0x4388, 0x4344, 0x1956, 0x4225, 0x45c5, 0xb2b9, 0x435c, 0x1e1b, 0x40b7, 0x1bb0, 0x0400, 0x40d0, 0x43e1, 0x4071, 0x40ea, 0xb0b7, 0x122c, 0xb27d, 0x40d4, 0x4470, 0xbfc8, 0x4002, 0x408c, 0x0287, 0xb201, 0x4221, 0x408d, 0x42f2, 0x4009, 0xb2dd, 0x43ae, 0xba01, 0xbf17, 0xb2fb, 0xb271, 0x209a, 0x4201, 0xb203, 0x4202, 0x43e9, 0x2338, 0x40af, 0x2f11, 0x40c6, 0xbf70, 0x0018, 0xa318, 0xbae2, 0xb063, 0x1bbf, 0xba6f, 0xbf60, 0xb2fa, 0xb2c2, 0x40f1, 0xba51, 0xa87a, 0xbf1b, 0x43fe, 0x408f, 0x42b4, 0x09d9, 0xb2a1, 0xbae5, 0xbf32, 0x41fe, 0xa3c9, 0x42ec, 0x1bf6, 0x4151, 0xb2ac, 0xb26c, 0xa3e7, 0x1c0c, 0x4298, 0xb291, 0x41c3, 0x428b, 0xb278, 0xb062, 0x4054, 0xb232, 0x40c4, 0x1067, 0x43ea, 0x4261, 0x43c9, 0x1dca, 0xb044, 0xbfd4, 0xb2e7, 0xb221, 0xabdc, 0x42e2, 0x417e, 0x43fa, 0x42f2, 0xbaf1, 0xb237, 0x46b1, 0xbf70, 0x1b90, 0x1baf, 0x4029, 0xba6d, 0x4112, 0x4305, 0x2569, 0xb0c6, 0xbf2a, 0x430a, 0x434e, 0x411a, 0x4233, 0x41a0, 0x175a, 0x41c1, 0x4235, 0x404a, 0xb090, 0x40f4, 0xb25c, 0xb2b1, 0x41cb, 0xba1a, 0x4064, 0x1f35, 0x423d, 0x4124, 0x4264, 0x4344, 0xbf6b, 0xb0d3, 0x0de7, 0x40c9, 0xb204, 0xb257, 0x1d42, 0x4340, 0xba64, 0x2cc3, 0x23ec, 0xb223, 0x1ea1, 0x3050, 0x402c, 0x43e3, 0x2354, 0x4044, 0xbac8, 0xb262, 0x42c0, 0xb2c2, 0x40d2, 0x42e3, 0x45a2, 0x4176, 0x40c3, 0xbf45, 0xb069, 0x467a, 0xb00e, 0x4571, 0x412b, 0xbfb0, 0x42ab, 0x2c01, 0x461d, 0x33c4, 0x4124, 0x2d04, 0xbac0, 0xba2d, 0xb04a, 0x431c, 0xbf05, 0xb220, 0xb057, 0x2eb8, 0xba23, 0xbace, 0x38d4, 0xbfe0, 0x4076, 0xb2b3, 0x429a, 0xb041, 0x43c3, 0x3b35, 0x43f0, 0xb203, 0xa113, 0x2d45, 0xbff0, 0x1f05, 0xbf67, 0x422b, 0xba7c, 0x2794, 0x40c5, 0xbfba, 0x41a3, 0x43be, 0xbff0, 0xb27b, 0xb23c, 0x2e8b, 0x3d1e, 0x0282, 0x425e, 0xbad1, 0x4005, 0xbad3, 0xb0e5, 0x4227, 0xb010, 0x4381, 0xb2b3, 0xbf46, 0xb2ce, 0x1f53, 0x44ea, 0xa888, 0xae43, 0x424c, 0x1e4e, 0x18c7, 0x4424, 0xb0ff, 0xb274, 0x4294, 0x341d, 0x40bb, 0xa1b2, 0x4564, 0xba76, 0xbad6, 0x4177, 0xb000, 0xbae7, 0xbf8c, 0xb24e, 0x0516, 0x178a, 0xa2d3, 0x42bc, 0xbf32, 0x2fc5, 0x3c82, 0x188b, 0xb265, 0x4087, 0xb2c3, 0x293d, 0xba08, 0x438a, 0x0d71, 0x22c9, 0xb09a, 0xb2c6, 0xb259, 0x3f8c, 0x43bf, 0xbfd0, 0xbfc4, 0xb28a, 0x1f75, 0xb0dc, 0x418f, 0xae2d, 0x193f, 0xbadd, 0x31f3, 0x403c, 0x1537, 0xbaf9, 0xb299, 0x27eb, 0x4330, 0xb2df, 0xbf1d, 0xb253, 0xa5e3, 0x1b97, 0x40e3, 0x229a, 0xb0e9, 0x204d, 0x1e6b, 0x4217, 0xb056, 0x3e7d, 0x4347, 0x4151, 0x24fa, 0x231c, 0x4151, 0xb0bf, 0x4261, 0x4294, 0x1499, 0xa071, 0x4389, 0x40f0, 0xbf24, 0x43f0, 0xa34e, 0x3d28, 0x40fc, 0xba75, 0x42b5, 0xb235, 0x276f, 0xa13d, 0x4045, 0xae0d, 0x4339, 0xba7f, 0x46e0, 0x3011, 0xb222, 0x106e, 0x421e, 0x43fc, 0x306e, 0xbf6e, 0xb293, 0x410b, 0x1cf9, 0x4073, 0x38a8, 0xba4f, 0x0e8e, 0xba5a, 0x46e5, 0x0c13, 0x438c, 0x0dc5, 0xbf00, 0x42e2, 0x4098, 0x3453, 0x1904, 0xba61, 0x4475, 0xbf3c, 0x4280, 0x431e, 0x4411, 0x12e6, 0x3dce, 0x1d7d, 0x41f3, 0xb204, 0x1e51, 0x1e1c, 0x4082, 0x4220, 0x0544, 0x40eb, 0x40f7, 0x4161, 0x1ae9, 0x43e3, 0x4071, 0x2ecb, 0x401e, 0x44b5, 0xb2b8, 0xbf9a, 0x41ec, 0x324e, 0x19af, 0x44f2, 0xb2ce, 0xb248, 0x42dc, 0xba09, 0x3d53, 0x1a5c, 0xa24d, 0xa580, 0x08df, 0xba14, 0x4695, 0x4409, 0x4212, 0x0169, 0x41b5, 0xb2ae, 0x2638, 0x42b9, 0xbfcf, 0xb292, 0x441a, 0x19f3, 0x1831, 0xb200, 0xb2b0, 0xb2f8, 0xba7e, 0x43bf, 0x108a, 0x4113, 0xab7b, 0x42e2, 0x43bb, 0x406e, 0x44fa, 0x4225, 0xb2bb, 0x4416, 0x32c6, 0x41d4, 0x033a, 0x40f5, 0xba4d, 0xbfdb, 0xbf90, 0x3054, 0x1bc7, 0x2374, 0x40b9, 0xb2a7, 0x2123, 0x42c4, 0x2200, 0xb24b, 0xb096, 0xa563, 0x415a, 0x099a, 0x1bdd, 0x4279, 0x3b38, 0x421b, 0x3c50, 0x1efa, 0xb2ed, 0xb0ab, 0x1914, 0x34d6, 0x2af8, 0xba20, 0x056e, 0xbf52, 0x427b, 0x41dc, 0xbfc0, 0x0e3c, 0x4107, 0x439d, 0xb260, 0xb25d, 0x429c, 0x46b3, 0x179c, 0xbfc7, 0x43c5, 0xb27f, 0x197b, 0x402d, 0xb264, 0x4117, 0x2330, 0xbf70, 0x4073, 0xbf00, 0xbad5, 0xb069, 0x42ef, 0xaf7f, 0x4002, 0x436d, 0x4121, 0x1410, 0x1f1a, 0x042f, 0x423d, 0x3abd, 0xbf33, 0xb0cc, 0x425d, 0xb0ec, 0x41da, 0x4335, 0x4263, 0xb2b0, 0x047c, 0x0a77, 0x4326, 0xbac0, 0xabe5, 0x417c, 0x4141, 0xadbf, 0xbf62, 0xb051, 0x2cbe, 0x1cfa, 0x428d, 0x10c9, 0xa7b6, 0xba01, 0xbfd0, 0x4247, 0x43fe, 0x441a, 0x2a92, 0xab72, 0x406b, 0x2f61, 0xbfcc, 0x189e, 0x0ed2, 0x4337, 0x41fa, 0x4049, 0xb207, 0xa86f, 0x439d, 0xa2fa, 0xbf93, 0x1b7d, 0xb0b2, 0x2ea1, 0xaf72, 0xae7d, 0x436a, 0x415b, 0x412c, 0x1964, 0x1660, 0x3250, 0x40bb, 0x41b4, 0xa262, 0x430c, 0xbf22, 0xabab, 0x3c05, 0xb04a, 0x4322, 0x410c, 0xb23a, 0x26f1, 0x43db, 0xba7c, 0x42f8, 0x1fa5, 0x43f5, 0xad41, 0x4276, 0xbf85, 0x42e1, 0xb06d, 0x0bee, 0x43e4, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xe4628e3c, 0xb130dc40, 0x301f3068, 0x711e026e, 0x29a5a394, 0xa6eb85b1, 0xcd2f7a79, 0x2ec8b90f, 0xd90284cf, 0xe8b9b1aa, 0xa23589aa, 0x4701154b, 0x09e98069, 0x05583b86, 0x00000000, 0x000001f0 }, + FinalRegs = new uint[] { 0xffffffff, 0x00000000, 0x00001870, 0xffffe6ab, 0x00007018, 0x000018d4, 0x00000000, 0x00001870, 0x09e98068, 0xffffffff, 0x05585795, 0x0c600000, 0x09e98068, 0x000017d0, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x190c, 0x41dd, 0xb2fe, 0x408e, 0x4222, 0x32ca, 0x40b9, 0x3388, 0xba3b, 0x455c, 0xbfd0, 0x0a17, 0x40b6, 0x1dc6, 0x316f, 0x1fe4, 0x1ddd, 0x4312, 0x1f2e, 0x41c8, 0x4142, 0x43e6, 0xbf64, 0x4407, 0x4230, 0x46a5, 0x43bc, 0x422b, 0x43b7, 0x4111, 0x4125, 0x42fc, 0xb0eb, 0xba23, 0x4375, 0xb002, 0xba10, 0x4272, 0xb282, 0xb0f4, 0x2703, 0x4192, 0xb2c8, 0xbf3a, 0x415a, 0x41dc, 0x2e6f, 0x4326, 0xa155, 0x1c7e, 0x43a5, 0x3588, 0xbf33, 0x434c, 0x3d8b, 0xa455, 0x4028, 0x42b9, 0x42e8, 0x4117, 0x12c6, 0xb2ea, 0x0555, 0x39c4, 0x4674, 0x4339, 0xb2fd, 0xb241, 0xba30, 0x42f0, 0x2e82, 0x418f, 0xbad0, 0x4253, 0x075f, 0x4412, 0x1dbd, 0xb229, 0x43c2, 0xbfae, 0xb085, 0xb2f3, 0xb0e9, 0x1b1f, 0x1f0e, 0x281f, 0x18e7, 0x30b9, 0xbadf, 0xba64, 0x42ec, 0x4208, 0x2e04, 0x2730, 0x430e, 0xb2aa, 0xb244, 0x4103, 0x1eb8, 0x4229, 0x1e5c, 0x40b5, 0x3def, 0xa5b2, 0x33b8, 0x4389, 0x42a1, 0xba14, 0xbf98, 0xa852, 0x4343, 0x40e5, 0xbaf5, 0x188e, 0x1ac6, 0x4073, 0x0c0c, 0xbfc2, 0x4028, 0x404d, 0x1860, 0x442f, 0xb0f6, 0xbf61, 0x3c74, 0xbf60, 0x41a0, 0x1c6a, 0xba14, 0x12cd, 0x40bb, 0xbf70, 0x4034, 0xbfc2, 0xbf00, 0x40d4, 0xb2f2, 0x433a, 0x001a, 0xac53, 0x0ab7, 0x428d, 0x443b, 0x4335, 0x27cc, 0x1e0a, 0x40e1, 0x17e2, 0xbad0, 0xb297, 0x405b, 0xb270, 0x429a, 0xbad7, 0x41de, 0x4210, 0x1acf, 0x42a3, 0x443b, 0x1d64, 0x460d, 0xbf06, 0xbad0, 0xb2b7, 0x4092, 0xba0f, 0x438c, 0x439f, 0x291d, 0x2fba, 0xb2b8, 0x435c, 0xbf9b, 0xac65, 0x4277, 0x4171, 0x2bd5, 0x0832, 0x46c5, 0xb275, 0xb292, 0x4012, 0x1396, 0x4014, 0xb046, 0xba1d, 0xb2a1, 0x067f, 0xb26b, 0x432a, 0x1630, 0x2119, 0xb202, 0x42cc, 0xbf54, 0xb004, 0x43df, 0xaad2, 0xa411, 0x1ea9, 0x40d3, 0x1fe2, 0xaf6c, 0xb068, 0x40ab, 0x2124, 0x39a3, 0x1a23, 0xba65, 0xb2eb, 0xbfa4, 0xb035, 0xba7c, 0x40b7, 0x4013, 0xb292, 0x444c, 0xb0d2, 0x401e, 0xba6a, 0xba5b, 0x4368, 0x46ec, 0x4493, 0x4016, 0x4601, 0x4049, 0x15b2, 0xb298, 0x18e4, 0xb22a, 0x1f07, 0x101f, 0x1908, 0xbfaf, 0x434e, 0x1125, 0x41d5, 0x1322, 0x41d0, 0xbfa2, 0x4349, 0x1fa5, 0x4289, 0x1ebd, 0x10be, 0x4328, 0x4084, 0x4076, 0xa326, 0x4262, 0xba59, 0xb017, 0x1eb2, 0x43e8, 0x43a9, 0xb014, 0x089a, 0xb22b, 0x1cf0, 0x4490, 0x1529, 0x4371, 0xbfa0, 0x43cc, 0x4127, 0xbfb7, 0xba00, 0xba0d, 0x41f4, 0xb0ea, 0x1e04, 0xbacd, 0x40bf, 0x46bc, 0x403b, 0x2642, 0x400b, 0x1d25, 0x4339, 0x4124, 0x43fb, 0x4203, 0x40f2, 0xb0f1, 0xac2d, 0xbfc0, 0x1f1c, 0x3447, 0x40b2, 0xbfa8, 0xbfb0, 0xbfb2, 0x4373, 0xabab, 0x428b, 0x400a, 0xaa61, 0xbff0, 0x46c0, 0x45e8, 0x1f40, 0xbae0, 0x2815, 0x4130, 0x39ef, 0xbace, 0x3978, 0x1c85, 0x4023, 0x1fbc, 0xbf01, 0x413b, 0x1f38, 0x4248, 0xa2f5, 0x1354, 0xa20b, 0x4287, 0x2088, 0x377f, 0x4029, 0x41a5, 0x0b8a, 0xb2ff, 0x419e, 0x003c, 0xb28e, 0xbf2a, 0xbad0, 0x3fea, 0x4138, 0x434b, 0x41c3, 0x4371, 0xb08c, 0xb240, 0x41d3, 0x42c1, 0x43ee, 0x42ae, 0x41e0, 0xb287, 0x1c7c, 0x41fc, 0x4130, 0x46e1, 0x4391, 0x1d6d, 0x401f, 0x189e, 0xbf24, 0x206b, 0xb27e, 0xb2e3, 0x432a, 0xba3f, 0x43b6, 0x1e9c, 0x41e6, 0x1943, 0x411a, 0x40d7, 0x4367, 0xa70f, 0x1088, 0x422f, 0x1d6e, 0x197a, 0x42ae, 0x3fb9, 0x01b5, 0xb226, 0x40c7, 0x3246, 0xbf58, 0xb07e, 0x4250, 0xba4e, 0xa4d2, 0xa9fa, 0x2365, 0xb28d, 0xa57c, 0xbaf7, 0xba5b, 0xb010, 0xba53, 0xbadf, 0x4122, 0xbf57, 0xb010, 0x4374, 0xbae0, 0xb2fb, 0x1fda, 0x41c7, 0x432a, 0xaa9f, 0x19da, 0x1f13, 0xb2e1, 0x1d38, 0x44a4, 0xbfb7, 0x438e, 0x054a, 0x425a, 0x3229, 0xbac4, 0x40a6, 0x404f, 0x4348, 0x41da, 0x40db, 0xafa8, 0xb0ba, 0x4004, 0x432c, 0xb058, 0x4324, 0x1daf, 0x4309, 0x42bd, 0x18cf, 0xbf6e, 0x42ee, 0x418a, 0xb270, 0xb287, 0xb209, 0x4271, 0xb0e0, 0x3c45, 0x1a47, 0xb284, 0xb224, 0x410f, 0x4230, 0xb239, 0x41d8, 0xb09f, 0x4125, 0xba3f, 0x18ea, 0x1a7b, 0x3bb4, 0xb018, 0xbf4a, 0x2a5f, 0x31f9, 0x411f, 0x4142, 0x423b, 0x4573, 0xbfb0, 0xba72, 0x1940, 0xb032, 0xbae0, 0x43e3, 0xba0c, 0x2537, 0x4081, 0xb211, 0x15fa, 0x41fa, 0xbfa0, 0xab62, 0xb0d6, 0xba2d, 0xbf51, 0x400b, 0x30b2, 0x4036, 0xaea0, 0xbf00, 0x42e8, 0x2b15, 0x1f0d, 0x381f, 0x4103, 0x4097, 0x41be, 0x235b, 0x4091, 0xbaf5, 0xb296, 0x455e, 0xb252, 0x4223, 0xba21, 0xbf90, 0x2bb5, 0x4153, 0xbf31, 0xa17a, 0x1938, 0x4210, 0x4274, 0x2b0c, 0x43b2, 0x411d, 0x1a71, 0x40e4, 0x2d1a, 0x4041, 0x4091, 0x40c5, 0x4082, 0xb256, 0x43a8, 0x4365, 0x426f, 0x42fc, 0xb058, 0xb098, 0x4077, 0xb074, 0x426f, 0x2362, 0x40fd, 0xbf22, 0xb2ba, 0xbf60, 0x438c, 0x4005, 0x4240, 0xbfd6, 0x4120, 0x42ab, 0xb0fa, 0x4674, 0x40ef, 0xbfce, 0x42fb, 0xb03e, 0x426b, 0x4116, 0x426b, 0xb29c, 0x419f, 0x4106, 0x437d, 0x43f6, 0x4625, 0x0a71, 0x424b, 0xb287, 0xa587, 0x40c2, 0xa389, 0x4043, 0xb23a, 0x0f83, 0x0e74, 0xbfa3, 0x42c8, 0x1837, 0xb04a, 0xb215, 0x1f63, 0x409b, 0x466f, 0x4020, 0x40cb, 0x1fc4, 0x424a, 0x4041, 0xab3f, 0x2513, 0xba39, 0x2761, 0x08ef, 0xb01c, 0x40ec, 0x4344, 0x432a, 0x423a, 0xbac6, 0x008b, 0x43cc, 0x1f37, 0x0f45, 0xbf11, 0x4088, 0x4359, 0x42cc, 0x1c92, 0xbacb, 0xb06c, 0xaf25, 0xb299, 0xbfde, 0x4184, 0x3ea1, 0xaab7, 0xbfc0, 0x2538, 0xbfd4, 0x4100, 0x2625, 0x419a, 0xb210, 0x231f, 0x40b5, 0xbfd9, 0x1dd1, 0x1c34, 0x41a3, 0x1c6a, 0xa756, 0xbf70, 0x430f, 0x42c1, 0x4077, 0x404f, 0x41fc, 0x4245, 0xbff0, 0x42cb, 0x3c1f, 0x1c6b, 0xab3a, 0x4083, 0x4131, 0x4234, 0x41a8, 0x41fe, 0x435e, 0x4086, 0xbf98, 0xb2c8, 0xbae1, 0x3ccd, 0x441d, 0xb29c, 0x4047, 0xbf92, 0x42ea, 0x4077, 0x1f00, 0x2d3c, 0x434f, 0x1d83, 0x2a97, 0x4238, 0x43d0, 0xb274, 0x4000, 0x438d, 0x407a, 0x199e, 0x1ec9, 0x4041, 0x4133, 0x0b38, 0x437e, 0xbadb, 0x0c6c, 0x43c9, 0x436d, 0xb2b7, 0x1b76, 0x1207, 0x403a, 0xbfa2, 0x0e8e, 0x1b82, 0x2840, 0x4018, 0xbfa5, 0x2ffa, 0x4493, 0x1cf6, 0xbfe0, 0x243e, 0x4113, 0xb28a, 0x4057, 0xb2d6, 0x43a2, 0xb2ac, 0x40d0, 0x4198, 0xae4e, 0x42f7, 0x3681, 0x4231, 0x4455, 0x35d7, 0x1e5f, 0x411e, 0xb2dc, 0x422d, 0x1edd, 0x4164, 0x2df7, 0xa616, 0x1734, 0xbf53, 0x404c, 0x4061, 0x44c8, 0x43d0, 0xbfdb, 0x415f, 0x1c73, 0xb2de, 0x40bf, 0x462e, 0x4218, 0x1dcc, 0xba45, 0xaef1, 0x425a, 0xa89f, 0xb25a, 0xa25f, 0x1fa5, 0x417a, 0xba13, 0x418e, 0x42a4, 0x35d6, 0x1b4b, 0xba13, 0xba40, 0x1b23, 0x0b14, 0xb20c, 0x2bba, 0xba45, 0x3b65, 0x256f, 0xbfd7, 0xba0d, 0xb2d5, 0x4267, 0x24e6, 0x2d6d, 0x11bc, 0x42b6, 0xbf15, 0xb217, 0xb2d8, 0x405f, 0x4143, 0xa6e6, 0xa92f, 0x42fb, 0x42bd, 0x438b, 0x458a, 0x43c3, 0x420c, 0x43bb, 0x4068, 0x43d4, 0x3fa0, 0x1968, 0x03e1, 0xb291, 0xbf90, 0x2968, 0x4029, 0x38bc, 0x431d, 0x43af, 0x1b23, 0x41a3, 0xbfd9, 0x43f3, 0x229b, 0x0925, 0xb295, 0x15ec, 0xb223, 0x19d3, 0xba1c, 0xb080, 0xb27a, 0xbf7e, 0xba7d, 0xb236, 0x4365, 0x4273, 0x1c90, 0x21b5, 0x42cf, 0x40d6, 0x18c8, 0x0777, 0x281d, 0xbfc0, 0x40ca, 0x1b92, 0x412d, 0xa3aa, 0x44c3, 0x4220, 0x4087, 0x4110, 0x43b0, 0x1aad, 0x3f60, 0x1515, 0xb2fd, 0x40ea, 0xbf0c, 0x19d3, 0x41ba, 0x4003, 0x4040, 0xb239, 0x4057, 0xbfd0, 0x439d, 0x41c6, 0x4382, 0x4122, 0x30b4, 0xbff0, 0x4285, 0x4112, 0x0eec, 0x41f2, 0x4165, 0xba79, 0xbfc0, 0xbac0, 0x43bc, 0x1fa0, 0xbf3b, 0x17fb, 0xba32, 0xba37, 0xb218, 0x4397, 0x40c6, 0xb2e3, 0xbfe4, 0xb23e, 0xb07d, 0x42cb, 0x424e, 0x4269, 0x41e7, 0x4323, 0x40f6, 0x3575, 0x4110, 0x41bc, 0xb2e8, 0x4228, 0xb242, 0x403a, 0x4262, 0xba27, 0x153a, 0xba00, 0xba70, 0x2dde, 0xb290, 0xbf91, 0x40bd, 0x427b, 0x0a83, 0x2605, 0x40ca, 0xa13e, 0x408a, 0x2078, 0xa34c, 0xbfc0, 0x4671, 0x1ae9, 0xbaed, 0x2113, 0xb27b, 0x4138, 0xb268, 0x0f57, 0xbfa5, 0xaeb6, 0xbac7, 0x4304, 0xb03a, 0x1524, 0x4439, 0xb2cf, 0xbfb0, 0xb252, 0xb083, 0x1b1f, 0x4297, 0xa384, 0x4077, 0x1b4d, 0x4386, 0x41da, 0x29a1, 0xbfc0, 0x1f1a, 0x3f17, 0x4007, 0x42f5, 0x4192, 0xbfe8, 0x2373, 0x40b8, 0x4160, 0x4621, 0xb02b, 0xae8c, 0x13f9, 0xba3d, 0x25d3, 0x3d46, 0x41df, 0x4481, 0x1289, 0xa8ac, 0x4257, 0x1157, 0x423d, 0xbff0, 0xb2f2, 0xab13, 0xbfde, 0x412c, 0xb09e, 0x40ba, 0xb0a4, 0x4356, 0x43fe, 0x4209, 0xbf3a, 0x40f3, 0x43df, 0x39a1, 0xb0c3, 0x0992, 0x46ca, 0x4269, 0x417e, 0xb200, 0x46bb, 0x41d6, 0xba1c, 0xbfe0, 0x42a5, 0x3e74, 0x4651, 0x4371, 0x435c, 0x437a, 0x23a1, 0xb217, 0x1bd7, 0x1920, 0x449b, 0xb24c, 0xbf26, 0x4551, 0x41d7, 0x43d9, 0xae4b, 0x4008, 0x403d, 0xba74, 0x43e3, 0x185b, 0xba5c, 0xbf16, 0xba06, 0xbff0, 0x4118, 0x2b3b, 0x435a, 0x409b, 0x4085, 0x434b, 0xa0a7, 0xbf02, 0x1b80, 0xb2a7, 0x1fb5, 0x2ab4, 0x4102, 0x40ec, 0x2d72, 0xba16, 0x4076, 0x23fd, 0xab93, 0xba7e, 0xbf5d, 0x41a0, 0x1d5c, 0x43cc, 0x3cb3, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x96070e06, 0x60f8a631, 0x6d122e21, 0xad567bfc, 0x291715df, 0x4658e055, 0x3efa71b8, 0x42812292, 0x84889393, 0xde7eb11f, 0x47c9ca7e, 0x0ba82b44, 0x8ffeeee8, 0x71d424e5, 0x00000000, 0x100001f0 }, + FinalRegs = new uint[] { 0xb75339fa, 0xfffcaf4b, 0x00000000, 0x8488a09f, 0x000350b4, 0x48ace04c, 0x00002fab, 0x0000ab2f, 0x84889836, 0x00000741, 0x00000741, 0x000000a0, 0x00001640, 0x84889e53, 0x00000000, 0x200001d0 }, + }, + new() + { + Instructions = new ushort[] { 0x4361, 0xb2d9, 0x43f4, 0x2def, 0x1e99, 0x1afe, 0xbfc7, 0x43d0, 0x404f, 0xafd3, 0x4601, 0x4586, 0x4143, 0x42bd, 0x4118, 0x4128, 0x431b, 0xa978, 0x2d56, 0x4607, 0x4124, 0x401b, 0x40a2, 0x430f, 0xba2a, 0xae2f, 0xb035, 0x3079, 0xa7cc, 0x4374, 0x2f83, 0xb20d, 0xbf6c, 0xacbf, 0x40e3, 0xb23c, 0x02c5, 0x433b, 0x40fe, 0x284b, 0x4208, 0x4616, 0x444a, 0xbfc8, 0x3a02, 0x4427, 0x3cd7, 0xa66c, 0x3bf0, 0x4176, 0xb277, 0x0c72, 0x033a, 0x4374, 0x3b46, 0x4039, 0x4646, 0xbaef, 0x4694, 0x43d8, 0xbff0, 0x18d4, 0x191f, 0x1ba0, 0x34cd, 0x09c2, 0x41f8, 0x08e6, 0x22f3, 0xbf76, 0xb0c1, 0x4267, 0xba61, 0xb2cc, 0x412e, 0x41fc, 0x2b38, 0xba61, 0x4130, 0x3f3b, 0x19f1, 0xb2e9, 0x43d3, 0x429d, 0x3b51, 0x42a9, 0x3dcd, 0x06c6, 0x42e0, 0x404e, 0xbf2b, 0xa69c, 0xb26a, 0x4013, 0x2ddf, 0xba2c, 0xafc1, 0xb0c8, 0xb0e1, 0x0dbf, 0x42e7, 0x4102, 0xbf90, 0x4034, 0xb013, 0x1c3c, 0x41fb, 0x1961, 0xb26d, 0x45d2, 0xb07e, 0xbf87, 0x4085, 0x1cf2, 0xbfc0, 0x42ff, 0xa589, 0x43c3, 0xb24f, 0x418e, 0x4261, 0xab43, 0x1b92, 0xbf05, 0x4388, 0x43bb, 0x4038, 0x4393, 0x4187, 0xa081, 0xb0d2, 0x42e2, 0xbf00, 0x2535, 0x43fe, 0x42c1, 0xb24a, 0x408a, 0x1f7d, 0xbf92, 0x1bcb, 0x1b0c, 0x3141, 0xb262, 0xbf86, 0x08aa, 0xb2f5, 0x4262, 0xbfdf, 0x42ea, 0x460c, 0x424f, 0x1ada, 0x42f2, 0xa012, 0x081b, 0x4046, 0x42b5, 0xb008, 0xb231, 0xb0e8, 0x42c3, 0x4187, 0x464b, 0x4016, 0x1abe, 0x098f, 0x1e7f, 0xba68, 0x2a45, 0x1b1a, 0xbf96, 0x402f, 0x1350, 0x3bc2, 0x411b, 0x04a8, 0xb076, 0xb03b, 0x4550, 0xba39, 0xb22a, 0x42f4, 0x4334, 0x4249, 0xb268, 0x12a7, 0x414b, 0x45b1, 0x2e3a, 0xb021, 0x10fa, 0x4249, 0x40b0, 0xbf71, 0x422f, 0x1c42, 0xba7b, 0x41f7, 0xbacd, 0x3d7d, 0xba1d, 0x44d8, 0x45e3, 0x4021, 0x1933, 0xbac1, 0x386d, 0x2777, 0x2151, 0x425a, 0xb251, 0x4376, 0xb008, 0xb2fd, 0xbf42, 0x4191, 0x1724, 0x41da, 0x418a, 0x1a0b, 0x3c2d, 0x345d, 0x0f74, 0xa44e, 0x461c, 0x18d4, 0xa030, 0xb2e0, 0xba75, 0x3710, 0xbf81, 0xba2d, 0x2b8b, 0x1c31, 0x44e1, 0xbfbc, 0x40f3, 0x398e, 0xb260, 0x4567, 0x46e8, 0xb240, 0x1c35, 0xba1b, 0x46c1, 0x4109, 0x43b7, 0xb094, 0x1676, 0x4143, 0x43f1, 0xbf87, 0x136c, 0x4058, 0x1e68, 0x4234, 0x059a, 0x430d, 0x1631, 0x41fe, 0x43b6, 0xb2ed, 0xbff0, 0x4024, 0x406d, 0x4367, 0x418a, 0x4307, 0xbfaf, 0x4374, 0xbae2, 0x4097, 0x3185, 0xb0ec, 0xb051, 0x4052, 0xba60, 0x433d, 0xb210, 0x4347, 0x402e, 0x43d1, 0x412f, 0xb0f2, 0x2310, 0xbfac, 0x4191, 0xb2fd, 0x1db4, 0x4559, 0xb08f, 0xb2aa, 0x4182, 0x42ae, 0x4036, 0xaf83, 0xb234, 0x418d, 0xbf8c, 0x407b, 0xba34, 0x41cf, 0x4351, 0x41c5, 0xba77, 0x1cfd, 0xa0cd, 0xbac7, 0xbf43, 0xba21, 0x425d, 0xba4c, 0xba13, 0x413c, 0x4095, 0xa127, 0x4198, 0xb2f3, 0x1f4e, 0x40e9, 0x19cc, 0xba4c, 0xb2e0, 0x4241, 0xbf90, 0xb214, 0x4224, 0x40f4, 0x3c7c, 0x07b9, 0x43e1, 0x41c7, 0x4339, 0x4181, 0xba11, 0x0bcc, 0xbf29, 0x4051, 0x1dbf, 0xb25a, 0x07c0, 0xba76, 0x413e, 0x417f, 0x1d87, 0xa388, 0x1d2e, 0x40e6, 0x4165, 0x432e, 0x050e, 0xb0fd, 0x43f4, 0x1895, 0xbf32, 0xb090, 0x40ba, 0x1e9a, 0x1f13, 0x45d2, 0x4044, 0x1f04, 0xb2ea, 0x43fa, 0x4197, 0x43d6, 0x1de6, 0x4671, 0x402d, 0x4058, 0x1aca, 0x43c5, 0xadd4, 0x41e8, 0xba3b, 0xb273, 0xbf9c, 0x197a, 0xbaf6, 0xbfb0, 0xba0e, 0x45d1, 0x1f11, 0x4379, 0xaf6e, 0xbff0, 0xb019, 0xa676, 0xbf4e, 0x4264, 0x4351, 0xb051, 0xbae2, 0x415e, 0x4355, 0x4016, 0x437f, 0xbfca, 0x408e, 0x42ef, 0xb240, 0x40cc, 0x311b, 0x435a, 0xab55, 0x43de, 0xaeae, 0x20fa, 0xbacb, 0x4301, 0x43bd, 0x42b6, 0xbfb0, 0x403c, 0xaf7d, 0x1941, 0xbf05, 0xba02, 0x0c49, 0x42cc, 0x1e9d, 0x1efc, 0x0a69, 0x42a6, 0x42e6, 0x1ae2, 0xba55, 0xbfd0, 0x407b, 0xbaf9, 0xa02b, 0xba5d, 0xbf16, 0xb239, 0xbfa0, 0xba47, 0x418f, 0x41e9, 0xafc4, 0x23c8, 0xb04e, 0x42fb, 0xaf00, 0x402a, 0x4270, 0x4380, 0x4214, 0x4466, 0x2297, 0x441a, 0xbf6d, 0x4298, 0x1836, 0x1918, 0x3cff, 0x42ec, 0x4363, 0xbf60, 0x4311, 0xbfa3, 0x4004, 0x0c27, 0x41fe, 0x4346, 0xb0f2, 0x1544, 0x43b7, 0x412d, 0xab14, 0x1f41, 0xb2ab, 0x190f, 0x218f, 0xb2d9, 0x0e76, 0x40b8, 0xb2d0, 0x41da, 0xbf09, 0x1a5e, 0x4340, 0x4004, 0x0884, 0x437a, 0xbf90, 0x428a, 0x19e6, 0x4283, 0x439b, 0xbfb8, 0x43d1, 0xba5f, 0x4366, 0x06b4, 0x4390, 0xb20c, 0x2116, 0x10b5, 0x4266, 0x43d3, 0x17f9, 0x42d4, 0xbac7, 0x437c, 0x434e, 0x410d, 0xaef0, 0xad99, 0x231d, 0x41a0, 0xb2ac, 0x0716, 0x3728, 0x1cbc, 0xbac7, 0x42d9, 0xbf05, 0xb2fb, 0x438a, 0x08e7, 0x42f7, 0xa411, 0x35d7, 0x4631, 0x46f2, 0xa304, 0x4650, 0x4055, 0x416c, 0x4336, 0x0e29, 0x41cb, 0x40e5, 0xb2e9, 0x0e4d, 0x4172, 0x1d2c, 0x3f5c, 0x301e, 0x4245, 0xba5e, 0x0d11, 0xbf99, 0x4155, 0x02c2, 0xbfb0, 0x40ec, 0xb0a7, 0xb20a, 0xba21, 0xba5e, 0x41b8, 0x42cf, 0x41ab, 0xbfe2, 0xbae1, 0x3b4b, 0xb01c, 0x46cd, 0x43db, 0xb2c6, 0x46f0, 0x4141, 0x1c08, 0x41e4, 0xba11, 0x4127, 0xb220, 0xba75, 0x1846, 0xbf43, 0x1be2, 0x4073, 0x418f, 0x37e6, 0x2c57, 0xaf36, 0xb23b, 0x4095, 0x412e, 0x0e71, 0x42ba, 0x19ec, 0xb0ab, 0x19d9, 0xa24a, 0x362e, 0x408b, 0x1094, 0xb21f, 0x40c9, 0x42be, 0xb2c7, 0x409e, 0xbf15, 0xb0cf, 0xb00b, 0x415c, 0x4009, 0x4186, 0xb0c7, 0x4257, 0x1992, 0x40c9, 0xbf00, 0xb2d0, 0x4168, 0xbf9d, 0x43c4, 0xbaf8, 0xba68, 0xba26, 0x45bc, 0x4214, 0x1231, 0xbfd4, 0x4009, 0xba00, 0x3e72, 0xbf1e, 0xb29b, 0xbacb, 0xbaf9, 0x4352, 0x4105, 0x42e7, 0xaa0f, 0xb2b3, 0x43a6, 0x4699, 0x2170, 0x0c8e, 0xae4a, 0xbfe0, 0xb004, 0xb237, 0x1a88, 0xbf41, 0xb2ad, 0xbf00, 0x13a8, 0x42a5, 0x00cb, 0xa3f6, 0x1bd6, 0x44d1, 0xba06, 0x4278, 0xba65, 0xbfc0, 0x098f, 0x029d, 0x2a7b, 0xb2df, 0x07a7, 0x0105, 0xad3c, 0x43ac, 0x40bf, 0x428f, 0x339a, 0x40fb, 0x43b9, 0x446d, 0x42ac, 0x1083, 0xad05, 0xbf8c, 0x4264, 0x42e4, 0xbaf2, 0xbad8, 0xb21f, 0x4244, 0x25d1, 0xb282, 0x405c, 0x1a04, 0x4399, 0x414d, 0xb2fa, 0xba6b, 0xb216, 0xb250, 0x438b, 0x2b3c, 0x40b0, 0x425b, 0x412e, 0xb220, 0xb25f, 0xb2c2, 0x2ec8, 0xbf3a, 0xba70, 0x43bc, 0xb299, 0x4284, 0x369c, 0xbaff, 0x058f, 0xba2c, 0x1699, 0x42e3, 0x41ce, 0xabfc, 0xb2cc, 0x2a1e, 0xb201, 0x19d3, 0xb099, 0xbf34, 0x332f, 0x0535, 0x4422, 0x42b9, 0xbfdf, 0xb270, 0x400a, 0x40d7, 0x4024, 0x1f9e, 0x196d, 0x434c, 0x1dd6, 0x41df, 0xb219, 0x405d, 0x4022, 0x1e04, 0x41dc, 0x4296, 0x43c4, 0xba25, 0x145b, 0x42a6, 0x4154, 0xbf0a, 0x4061, 0x28b4, 0x1c9f, 0x4310, 0x274b, 0x4191, 0x4034, 0xbf7f, 0x439a, 0x42d8, 0xb0eb, 0xba0f, 0xba5a, 0x41a7, 0x43c1, 0x0538, 0x41ad, 0x42a1, 0x43ce, 0xbf04, 0xa79a, 0x1b98, 0xb018, 0xb0e0, 0x42f5, 0x4476, 0xaf71, 0x433b, 0xb212, 0xbf73, 0x0a7e, 0x2b1f, 0xbafb, 0xba4e, 0x0425, 0x420d, 0x43e2, 0x43e0, 0x4065, 0x4117, 0xba07, 0x2562, 0xa532, 0x43b3, 0xba29, 0x2779, 0x4353, 0x440b, 0x4140, 0xbf58, 0x27dc, 0x4286, 0xa57a, 0x42b2, 0x425d, 0xb2a9, 0xba52, 0x43de, 0x41ee, 0x0796, 0x1fa7, 0x2c5f, 0x2a61, 0x1c25, 0x425a, 0x40b7, 0x42f8, 0x4180, 0x40b4, 0xbfc5, 0x428e, 0x4205, 0xba23, 0x4003, 0x1f55, 0x3e3c, 0x416c, 0x42e0, 0xb234, 0x1d80, 0x432c, 0x417f, 0x4079, 0x3538, 0x1eff, 0x4475, 0xbf34, 0xb277, 0x43b6, 0x4355, 0x40d3, 0xb023, 0x17b5, 0x1d0a, 0x2b3a, 0x4455, 0x0615, 0xbf0b, 0x4045, 0x40ad, 0x2759, 0xb068, 0x40aa, 0x1f85, 0x436b, 0x4268, 0xb282, 0x40d6, 0xb0ed, 0xb01d, 0x422d, 0x4104, 0x4114, 0x1d54, 0xbf70, 0x1d50, 0xbf98, 0x432f, 0x403a, 0x417f, 0x4622, 0xb08c, 0x1bf4, 0x3219, 0x4079, 0x23f2, 0xba10, 0x29d4, 0x4224, 0x431b, 0x41ea, 0x4440, 0x41f5, 0x40c6, 0x1e5e, 0x01c4, 0x34a9, 0x1bde, 0x43b6, 0xb2e9, 0xbf24, 0x3fa4, 0xb2c1, 0x41d5, 0x4002, 0x4215, 0x185d, 0x1a17, 0xb27f, 0xb23a, 0x1397, 0x1e78, 0xb03b, 0x1cad, 0xba6a, 0x2a81, 0x0ee6, 0x4351, 0xbfbe, 0xb2fa, 0x225e, 0x429d, 0x410b, 0x4080, 0xba53, 0x4196, 0x40a2, 0x437b, 0x0a59, 0xb2aa, 0xbae9, 0x0534, 0x40a9, 0x465a, 0x448c, 0xb26a, 0xb213, 0x41c5, 0x424c, 0x18e1, 0x30fe, 0x4046, 0x40d8, 0xbf39, 0x1f57, 0x1865, 0x4059, 0x4376, 0x4564, 0x4438, 0x41e0, 0x12d8, 0xb06b, 0x4329, 0x42b6, 0x40f5, 0x41e4, 0xbac3, 0xb265, 0x4570, 0x42f8, 0x2fb0, 0x432d, 0x438e, 0x4222, 0x438f, 0xbf02, 0x43d6, 0x4179, 0x278d, 0x25df, 0xbafc, 0x251e, 0x1cfc, 0xba69, 0xbf31, 0x0ac3, 0xb0ad, 0x4205, 0x441e, 0x410c, 0xb273, 0x42be, 0x1e0d, 0xbf07, 0x468c, 0x4016, 0xa105, 0x437e, 0x416e, 0xbaf0, 0x418d, 0x3851, 0x2464, 0x43b8, 0x43e5, 0xbac0, 0x4146, 0x3c61, 0x2797, 0x4094, 0x41fb, 0x181a, 0x4437, 0xb273, 0x4365, 0x4321, 0x1fe4, 0x4199, 0x269e, 0x1c0a, 0xbf77, 0x40b0, 0x3eaf, 0x1f30, 0xa473, 0x26a3, 0x42f4, 0xb0a2, 0x4280, 0xb2d0, 0x2fb3, 0xb017, 0xb07d, 0x43f7, 0x439e, 0x43f1, 0xbfe0, 0x459e, 0x3cb3, 0xbfd6, 0x1244, 0x2ba2, 0x4391, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0xfaefb3b8, 0x78bd4750, 0x110d5992, 0x6de9a720, 0x42910210, 0xa47797d3, 0xb17bbffb, 0x69ee994d, 0xbfeace8e, 0x33a9e570, 0x6e9aadf6, 0xc053407f, 0x87f3e955, 0x34a56210, 0x00000000, 0x700001f0 }, + FinalRegs = new uint[] { 0x000000a1, 0xffffe85e, 0x000017a1, 0x00000002, 0x000018e1, 0x00000000, 0x000000a1, 0xffffff5c, 0x00000000, 0x0000ffbc, 0x00000000, 0xc053407f, 0x00021000, 0x34a56118, 0x00000000, 0x800001d0 }, + }, + new() + { + Instructions = new ushort[] { 0xba27, 0x42db, 0x40d3, 0x4362, 0x409f, 0x1812, 0xbac7, 0xa7df, 0x34cd, 0xa70f, 0x4280, 0x40b0, 0xb0ee, 0x01be, 0xb211, 0x4052, 0xb2a7, 0x4063, 0x406f, 0x4654, 0x434e, 0x42fb, 0xbf25, 0x4269, 0x1119, 0xb264, 0x4301, 0xba63, 0x1a18, 0xb2fb, 0x1887, 0x2f9b, 0xb233, 0x440e, 0x4206, 0xbad1, 0xb09d, 0x262b, 0xb2d3, 0xba06, 0xab5c, 0x2ee6, 0x4419, 0x339c, 0xb2d1, 0x4192, 0xbf72, 0xbae1, 0x410e, 0x41fe, 0x4193, 0xbf00, 0xba7d, 0xb2b6, 0x4318, 0x1eee, 0xb2f0, 0x413c, 0xba4b, 0x41e0, 0xb02d, 0x4287, 0x4467, 0xba35, 0x46d4, 0xa117, 0xbf5c, 0x40b4, 0xa074, 0x2102, 0x4230, 0x0167, 0x44e9, 0x0471, 0x4106, 0x42b1, 0x00af, 0x1a8c, 0x1ae2, 0xabbf, 0x42c5, 0x1b0b, 0x123f, 0x406d, 0xbf91, 0xb09b, 0xb276, 0xb07c, 0x4383, 0x18ab, 0x4156, 0xb2aa, 0xbf7b, 0x191b, 0xb228, 0xb263, 0x0dda, 0x4062, 0x46fa, 0x3720, 0x1f98, 0xb045, 0xb235, 0x1fa8, 0x4276, 0xb27a, 0xa3a8, 0x434b, 0x2044, 0x1f00, 0x169e, 0xbae6, 0xadf8, 0x4272, 0x4004, 0x42de, 0x2bee, 0xba2c, 0xbfb6, 0xb043, 0x415d, 0xb27f, 0x4213, 0x1ebb, 0x4077, 0xb284, 0xa9ab, 0x40a5, 0xb2a9, 0xb254, 0x4593, 0xa036, 0xb058, 0xb2ab, 0x3458, 0x1fa6, 0x3798, 0x4180, 0x41c0, 0x46cd, 0x1155, 0x42cd, 0xbf58, 0xba33, 0xa87e, 0xb012, 0x43eb, 0x2dc8, 0x42f8, 0x1e62, 0xb24f, 0x413d, 0x412d, 0x4335, 0x1915, 0x1ecf, 0x1358, 0x4419, 0x40ec, 0x438f, 0x2cb3, 0x360d, 0x2b61, 0x1cea, 0x0ed4, 0xb2e1, 0xb206, 0xb255, 0xbfa1, 0x1bc7, 0xb011, 0x4306, 0x3b69, 0xa9a9, 0xb20b, 0xad51, 0x420f, 0x1493, 0x4140, 0x405f, 0xb03f, 0x4107, 0x422c, 0x186c, 0xb285, 0x4350, 0x4115, 0x196d, 0xba29, 0xbaeb, 0x4340, 0x404c, 0x4195, 0xba1d, 0x4156, 0x4257, 0xbf45, 0x19bf, 0x4356, 0x3366, 0x40fa, 0x4022, 0x40e4, 0xa663, 0x284c, 0xbf4a, 0x415c, 0x0de3, 0x411d, 0x4268, 0x36e8, 0xbafe, 0xb0b5, 0x40b6, 0x406c, 0x4131, 0xb01b, 0x4281, 0x174c, 0xb06b, 0x421d, 0x412e, 0x4159, 0x4346, 0x01c4, 0x2f52, 0xbfd7, 0xb0f4, 0xb0b7, 0x4053, 0x1ce2, 0xbfdd, 0x40ae, 0xb286, 0x41d1, 0x4156, 0x1b8d, 0x41e1, 0x4281, 0x1844, 0xb297, 0x1c04, 0x405e, 0x1841, 0x412a, 0x414d, 0x45c8, 0xba0a, 0xb25d, 0xadc5, 0xa7eb, 0xbfad, 0x45e0, 0x41ce, 0x4230, 0x4232, 0x41e4, 0x42f1, 0x4566, 0x46d3, 0x1f16, 0x419c, 0x2db3, 0x4312, 0x30ff, 0x28e3, 0xbfd2, 0x4277, 0xa610, 0x41b8, 0xbad9, 0x42db, 0x1ff4, 0x088c, 0x41e5, 0x4104, 0xbf60, 0x0a11, 0x380e, 0xb24f, 0xb287, 0x42a9, 0xb006, 0xba45, 0x408d, 0x414f, 0x1d4e, 0xbf03, 0x3191, 0x41f3, 0xb260, 0x1f59, 0x4347, 0x42f8, 0x412d, 0x43f0, 0x1d4e, 0x42ba, 0xab0a, 0x1f8d, 0x432c, 0xaf72, 0x1bbd, 0x4130, 0x43ab, 0x41ba, 0x0066, 0xbf01, 0x1477, 0x410f, 0x40f3, 0x415c, 0x197b, 0x2fe1, 0x4542, 0xbf31, 0x4020, 0x420c, 0xa16c, 0xb231, 0xba34, 0x420e, 0x46f5, 0x0851, 0x4374, 0xbf22, 0xba20, 0x1df1, 0xb2fb, 0xb090, 0x431f, 0x44cc, 0x411a, 0xb2dd, 0x46b3, 0xba7b, 0x42b1, 0xb267, 0x41e5, 0x41c9, 0x414b, 0x40df, 0xa37b, 0xb2ef, 0x411d, 0xb29b, 0x1380, 0xbf2a, 0x41f5, 0xb280, 0xb2f2, 0x0ed2, 0x4329, 0xb248, 0x0969, 0x4631, 0x40f3, 0x43d0, 0xb093, 0xbf5f, 0xb204, 0x4463, 0x1d6d, 0x4382, 0x41a4, 0x40dc, 0x43f2, 0x460d, 0x42a1, 0x0a18, 0xb097, 0x23aa, 0x1acb, 0x1ff4, 0xb270, 0x0311, 0x0baa, 0x338c, 0xbf01, 0xb22a, 0x422c, 0xb2ae, 0x4369, 0x43d4, 0xba01, 0x41c8, 0xbafb, 0xb034, 0x0c66, 0xb221, 0x432c, 0x1b48, 0x413f, 0x4014, 0xa721, 0xbae1, 0xbae8, 0x192f, 0xba20, 0x416b, 0xb2cd, 0x408f, 0xbf61, 0x42a8, 0x3999, 0xbaff, 0x43a6, 0x416a, 0x4148, 0xb00e, 0x10c7, 0x1a90, 0xba63, 0xbf81, 0xb03e, 0xbaf7, 0x4046, 0x40a1, 0x439d, 0x4141, 0x2c5c, 0x29a6, 0x2b42, 0x0a7a, 0x3b4a, 0x42d6, 0x249b, 0x3240, 0x2ca8, 0xb2e1, 0xbfb8, 0x4059, 0x19ad, 0xb27c, 0xba73, 0x4270, 0x171d, 0x4183, 0xbac4, 0x43bf, 0x4418, 0x4169, 0x422e, 0x41e2, 0x43c2, 0xbf6d, 0x40de, 0xb283, 0x432a, 0x437e, 0xb2b8, 0x4277, 0x4338, 0x40d1, 0x42f7, 0xb2b2, 0xbaef, 0x16a7, 0x4124, 0x4341, 0xbf80, 0xb262, 0x021b, 0xb042, 0x18df, 0x1038, 0xb292, 0x1d14, 0xb0ee, 0x429e, 0x4019, 0xb293, 0x4216, 0xbf62, 0x07a6, 0xb281, 0x1f1b, 0x4660, 0x4568, 0x279d, 0x43e9, 0x45e1, 0xb27b, 0x40fe, 0xb242, 0x42d7, 0x2b9f, 0xb28d, 0xbfb1, 0xb2d4, 0x40ed, 0x43f7, 0xb05a, 0x34cd, 0xb2cc, 0x426d, 0x1c09, 0x414c, 0x4146, 0xb0ac, 0x434e, 0xb2f2, 0xba3c, 0xa981, 0x224b, 0xba0d, 0xb239, 0xbf8a, 0x3ae8, 0x40aa, 0x3459, 0xbf90, 0x4491, 0xb2e0, 0xba62, 0x425b, 0x41b8, 0x427e, 0xb24e, 0x4117, 0xbfc8, 0xa2f2, 0x1c4d, 0xaae2, 0x2776, 0xba4b, 0xbf03, 0x1f0e, 0x41ff, 0xa3a7, 0x21d9, 0x1520, 0xb2d1, 0x4344, 0x4669, 0x42b5, 0xba0a, 0x3274, 0xb23b, 0x1a2d, 0xbf60, 0x4424, 0x43e3, 0x4236, 0xbfa1, 0x4335, 0x41d8, 0xb2fa, 0xba3b, 0x3433, 0xb099, 0xbf26, 0x415a, 0x149b, 0x1e17, 0x2be1, 0x41c8, 0x26ac, 0x18c2, 0xbfe4, 0x27e9, 0x2413, 0x41c9, 0x29b1, 0xa042, 0xba56, 0xba44, 0x1c5f, 0xba32, 0xba60, 0xbac1, 0xa246, 0x1a6e, 0x41dc, 0xb2f1, 0x18f1, 0x1b38, 0x331a, 0x2a6f, 0x40e4, 0x0350, 0x1ff6, 0x43fb, 0xbf77, 0x2b4e, 0x1f7b, 0xa24e, 0xb05b, 0xbf51, 0x1b2f, 0xbaf4, 0x0756, 0x4238, 0x1c0d, 0x2351, 0xb03c, 0x2234, 0xb2c8, 0xb073, 0x1a1e, 0x4171, 0x3256, 0x0169, 0xa84a, 0x43f6, 0x2cb9, 0x429b, 0xba22, 0xbf9a, 0x43ba, 0x46cd, 0x30ba, 0x02fa, 0x42c5, 0x0ca2, 0x326a, 0x428b, 0x43be, 0x114e, 0xbf2c, 0x0816, 0x0903, 0x1a3d, 0xb291, 0x4025, 0xab9a, 0xa63e, 0x426f, 0x42f1, 0x1d96, 0x1e6c, 0x4117, 0x403d, 0x1ecb, 0x438c, 0xbfc4, 0x4110, 0xba01, 0x458c, 0x442e, 0xb07a, 0xb2fb, 0x43dc, 0xb2d4, 0x1bec, 0x407a, 0x19ff, 0x0fd7, 0x4321, 0x1e0c, 0x2ff7, 0xbf91, 0x4266, 0x1dd1, 0x42de, 0xbf90, 0xb208, 0x40a4, 0x1ff9, 0x41c7, 0xb276, 0x408b, 0xbfaf, 0xb2f4, 0x4056, 0xa933, 0x42c6, 0x4198, 0x42ee, 0xbad7, 0xb288, 0xb28f, 0x42b7, 0x43f8, 0xb27b, 0xaa4e, 0x4066, 0x30f5, 0x4242, 0x41e1, 0xba7e, 0xa066, 0x4038, 0x1c69, 0xba0c, 0x4372, 0xb08a, 0xbaef, 0x4314, 0x41c4, 0xbf9f, 0x4355, 0xb2be, 0xb096, 0x402d, 0xb206, 0xb2d6, 0x3674, 0x159d, 0x4093, 0x41dd, 0x427c, 0x40a7, 0xb0ca, 0xb214, 0xb25c, 0xbf1e, 0x4043, 0xa1bb, 0x41c7, 0x4655, 0x1805, 0x4313, 0xba4b, 0xb03c, 0xb2d9, 0xa67b, 0x291b, 0x0eb7, 0x425b, 0xb007, 0x4669, 0xb2aa, 0x41e8, 0x41b0, 0x4015, 0xa496, 0x3460, 0x43cd, 0x44f4, 0xbf01, 0x41cd, 0x4393, 0xad38, 0xba1a, 0xbf4b, 0x2960, 0x43a0, 0xba2d, 0x4559, 0xb0c7, 0x173a, 0x263a, 0xb23e, 0x43c1, 0x43aa, 0xa3b5, 0xba78, 0x0bf1, 0x4126, 0x067a, 0x42fc, 0x446f, 0x0b51, 0x2b2a, 0x439e, 0xb23c, 0xbf23, 0xb078, 0xbae6, 0x0398, 0xa8d3, 0x42ec, 0x431c, 0x41ed, 0x40d1, 0xba3d, 0x2317, 0xbac5, 0x42e1, 0x4268, 0x361b, 0x4235, 0x4151, 0x4596, 0x1ac6, 0x4215, 0xbfbc, 0xb0c0, 0x4296, 0x2525, 0x4287, 0xbf69, 0x24a2, 0x425c, 0xb258, 0xb01f, 0xbf3d, 0xb246, 0xb2cb, 0xbae2, 0xb284, 0xbac2, 0x4339, 0x4580, 0x3c52, 0x40f7, 0x04a0, 0xa9ab, 0x416b, 0xadba, 0x429d, 0x4270, 0x3def, 0x1a98, 0xbf65, 0x4286, 0x4056, 0x3ef0, 0xb287, 0x0bfb, 0x45e1, 0xb270, 0x0e86, 0x4006, 0x4593, 0xaf1d, 0x403a, 0x4113, 0x401e, 0x4209, 0x4018, 0x4130, 0x41ac, 0x425d, 0x42ba, 0xa052, 0x43de, 0x073f, 0x4018, 0xba5b, 0xb2e8, 0x1b83, 0x424a, 0x4000, 0xbf61, 0x1f92, 0x4086, 0x404a, 0x2004, 0x45e1, 0x4221, 0x4257, 0xbf9a, 0x1c3a, 0x20b9, 0x41b2, 0x429b, 0x4097, 0x40cb, 0xbfdb, 0x41de, 0x4107, 0xa45c, 0x426d, 0xafd3, 0x412f, 0x199b, 0x42ac, 0x4389, 0xbf41, 0xb20a, 0xa51d, 0x439f, 0x41ff, 0x4215, 0x3e5e, 0xba14, 0x18f3, 0x428a, 0x43fd, 0x438e, 0x4272, 0x1b74, 0x42ad, 0x417d, 0x1ff0, 0x1b9c, 0x41b7, 0x41b6, 0x405f, 0x17e5, 0xb2a3, 0x07dc, 0xbf22, 0xb08c, 0x409e, 0xb23e, 0xb2b4, 0x29f8, 0x44e8, 0x41ad, 0xba65, 0x4200, 0x41b5, 0xbf8c, 0x1ee4, 0x41ff, 0x40f0, 0xb2b3, 0x45dd, 0xba63, 0x1e16, 0xbfe2, 0x1ee9, 0xa4e7, 0xb07c, 0xae9f, 0xba20, 0x403a, 0x4260, 0x439a, 0x444f, 0x40bb, 0x430f, 0xbf57, 0x18d5, 0x43ba, 0x0350, 0x2a39, 0xaade, 0x0a7a, 0xb239, 0xb2c2, 0xb0a4, 0x0999, 0x4270, 0x4390, 0xbfb6, 0x4048, 0xbfa0, 0xb20d, 0x4026, 0xba4e, 0x430e, 0x4375, 0x4171, 0xb278, 0x4256, 0x40bd, 0x42e4, 0x1c1e, 0xb2de, 0xbf58, 0x059b, 0xb243, 0x197d, 0x4084, 0xa945, 0xb03f, 0x4430, 0x1c82, 0xa4a0, 0x43c7, 0x41e8, 0x41a5, 0xba57, 0x0ecc, 0x2e03, 0xba50, 0x42f7, 0xb017, 0x42b2, 0xbac1, 0xbfbc, 0x408a, 0x0c66, 0xa768, 0xb223, 0x427c, 0x448d, 0x445f, 0x437d, 0x405e, 0x42a3, 0x257d, 0x4485, 0x44ec, 0xba2b, 0x4177, 0x42ea, 0x42f5, 0x43cc, 0x4681, 0x4211, 0x416c, 0xb038, 0xba19, 0x4669, 0xbfbb, 0x08f8, 0x4338, 0xb2cf, 0x4653, 0xa218, 0xb2f6, 0x1894, 0xbaed, 0xb2c1, 0x19ae, 0x1138, 0x1b80, 0x4158, 0x41ea, 0xb083, 0xbfe2, 0xacb0, 0x1d95, 0xa07c, 0x4770, 0xe7fe }, + StartRegs = new uint[] { 0x53e412bb, 0xf8075b4a, 0xc06d1392, 0xad332bbd, 0x27e72f7d, 0xac8f8bd5, 0x3a5b2232, 0xc5e2c8ec, 0xe032a7a7, 0x2b035634, 0x4a720b19, 0x0f87e928, 0x73db65d8, 0x6c7bc94f, 0x00000000, 0xf00001f0 }, + FinalRegs = new uint[] { 0x000019d4, 0x00000000, 0x00001828, 0x7d000000, 0x977f681b, 0x0000182e, 0x00007d12, 0x00000067, 0x77b1c835, 0x00004100, 0x000010c8, 0x0000000e, 0x79708dab, 0x977f655b, 0x00000000, 0x200001d0 }, + }, + }; + } +} diff --git a/src/Ryujinx.Tests/Cpu/EnvironmentTests.cs b/src/Ryujinx.Tests/Cpu/EnvironmentTests.cs new file mode 100644 index 00000000..2a4775a3 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/EnvironmentTests.cs @@ -0,0 +1,94 @@ +using ARMeilleure.Translation; +using NUnit.Framework; +using Ryujinx.Cpu.Jit; +using Ryujinx.Tests.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Tests.Cpu +{ + internal class EnvironmentTests + { +#pragma warning disable IDE0052 // Remove unread private member + private static Translator _translator; +#pragma warning restore IDE0052 + + private static void EnsureTranslator() + { + // Create a translator, as one is needed to register the signal handler or emit methods. + _translator ??= new Translator(new JitMemoryAllocator(), new MockMemoryManager(), true); + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + private static float GetDenormal() + { + return BitConverter.Int32BitsToSingle(1); + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + private static float GetZero() + { + return BitConverter.Int32BitsToSingle(0); + } + + /// + /// This test ensures that managed methods do not reset floating point control flags. + /// This is used to avoid changing control flags when running methods that don't require it, such as SVC calls, software memory... + /// + [Test] + public void FpFlagsPInvoke() + { + EnsureTranslator(); + + // Subnormal results are not flushed to zero by default. + // This operation should not be allowed to do constant propagation, hence the methods that explicitly disallow inlining. + Assert.AreNotEqual(GetDenormal() + GetZero(), 0f); + + bool methodCalled = false; + bool isFz = false; + + var method = TranslatorTestMethods.GenerateFpFlagsPInvokeTest(); + + // This method sets flush-to-zero and then calls the managed method. + // Before and after setting the flags, it ensures subnormal addition works as expected. + // It returns a positive result if any tests fail, and 0 on success (or if the platform cannot change FP flags) + int result = method(Marshal.GetFunctionPointerForDelegate(ManagedMethod)); + + // Subnormal results are not flushed to zero by default, which we should have returned to exiting the method. + Assert.AreNotEqual(GetDenormal() + GetZero(), 0f); + + Assert.True(result == 0); + Assert.True(methodCalled); + Assert.True(isFz); + return; + + void ManagedMethod() + { + // Floating point math should not modify fp flags. + float test = 2f * 3.5f; + + if (test < 4f) + { + throw new Exception("Sanity check."); + } + + isFz = GetDenormal() + GetZero() == 0f; + + try + { + if (test >= 4f) + { + throw new Exception("Always throws."); + } + } + catch + { + // Exception handling should not modify fp flags. + + methodCalled = true; + } + } + } + } +} diff --git a/src/Ryujinx.Tests/Cpu/PrecomputedMemoryThumbTestCase.cs b/src/Ryujinx.Tests/Cpu/PrecomputedMemoryThumbTestCase.cs new file mode 100644 index 00000000..40ed2675 --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/PrecomputedMemoryThumbTestCase.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Tests.Cpu +{ + public struct PrecomputedMemoryThumbTestCase + { + public ushort[] Instructions; + public uint[] StartRegs; + public uint[] FinalRegs; + public (ulong Address, ushort Value)[] MemoryDelta; + } +} diff --git a/src/Ryujinx.Tests/Cpu/PrecomputedThumbTestCase.cs b/src/Ryujinx.Tests/Cpu/PrecomputedThumbTestCase.cs new file mode 100644 index 00000000..28d739fc --- /dev/null +++ b/src/Ryujinx.Tests/Cpu/PrecomputedThumbTestCase.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Tests.Cpu +{ + public class PrecomputedThumbTestCase + { + public ushort[] Instructions; + public uint[] StartRegs; + public uint[] FinalRegs; + } +} diff --git a/src/Ryujinx.Tests/HLE/SoftwareKeyboardTests.cs b/src/Ryujinx.Tests/HLE/SoftwareKeyboardTests.cs new file mode 100644 index 00000000..79ca2d48 --- /dev/null +++ b/src/Ryujinx.Tests/HLE/SoftwareKeyboardTests.cs @@ -0,0 +1,70 @@ +using NUnit.Framework; +using Ryujinx.HLE.HOS.Applets; +using System.Text; + +namespace Ryujinx.Tests.HLE +{ + public class SoftwareKeyboardTests + { + [Test] + public void StripUnicodeControlCodes_NullInput() + { + Assert.IsNull(SoftwareKeyboardApplet.StripUnicodeControlCodes(null)); + } + + [Test] + public void StripUnicodeControlCodes_EmptyInput() + { + Assert.AreEqual(string.Empty, SoftwareKeyboardApplet.StripUnicodeControlCodes(string.Empty)); + } + + [Test] + public void StripUnicodeControlCodes_Passthrough() + { + string[] prompts = { + "Please name him.", + "Name her, too.", + "Name your friend.", + "Name another friend.", + "Name your pet.", + "Favorite homemade food?", + "What’s your favorite thing?", + "Are you sure?", + }; + + foreach (string prompt in prompts) + { + Assert.AreEqual(prompt, SoftwareKeyboardApplet.StripUnicodeControlCodes(prompt)); + } + } + + [Test] + public void StripUnicodeControlCodes_StripsNewlines() + { + Assert.AreEqual("I am very tall", SoftwareKeyboardApplet.StripUnicodeControlCodes("I \r\nam \r\nvery \r\ntall")); + } + + [Test] + public void StripUnicodeControlCodes_StripsDeviceControls() + { + // 0x13 is control code DC3 used by some games + string specialInput = Encoding.UTF8.GetString(new byte[] { 0x13, 0x53, 0x68, 0x69, 0x6E, 0x65, 0x13 }); + Assert.AreEqual("Shine", SoftwareKeyboardApplet.StripUnicodeControlCodes(specialInput)); + } + + [Test] + public void StripUnicodeControlCodes_StripsToEmptyString() + { + string specialInput = Encoding.UTF8.GetString(new byte[] { 17, 18, 19, 20 }); // DC1 - DC4 special codes + Assert.AreEqual(string.Empty, SoftwareKeyboardApplet.StripUnicodeControlCodes(specialInput)); + } + + [Test] + public void StripUnicodeControlCodes_PreservesMultiCodePoints() + { + // Turtles are a good example of multi-codepoint Unicode chars + string specialInput = "♀ 🐢 🐢 ♂ "; + Assert.AreEqual(specialInput, SoftwareKeyboardApplet.StripUnicodeControlCodes(specialInput)); + } + } +} diff --git a/src/Ryujinx.Tests/Memory/MockMemoryManager.cs b/src/Ryujinx.Tests/Memory/MockMemoryManager.cs new file mode 100644 index 00000000..20c318de --- /dev/null +++ b/src/Ryujinx.Tests/Memory/MockMemoryManager.cs @@ -0,0 +1,53 @@ +using ARMeilleure.Memory; +using System; + +namespace Ryujinx.Tests.Memory +{ + internal class MockMemoryManager : IMemoryManager + { + public int AddressSpaceBits => throw new NotImplementedException(); + + public IntPtr PageTablePointer => throw new NotImplementedException(); + + public MemoryManagerType Type => MemoryManagerType.HostMappedUnsafe; + +#pragma warning disable CS0067 // The event is never used + public event Action UnmapEvent; +#pragma warning restore CS0067 + + public ref T GetRef(ulong va) where T : unmanaged + { + throw new NotImplementedException(); + } + + public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + { + throw new NotImplementedException(); + } + + public bool IsMapped(ulong va) + { + throw new NotImplementedException(); + } + + public T Read(ulong va) where T : unmanaged + { + throw new NotImplementedException(); + } + + public T ReadTracked(ulong va) where T : unmanaged + { + throw new NotImplementedException(); + } + + public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) + { + throw new NotImplementedException(); + } + + public void Write(ulong va, T value) where T : unmanaged + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Ryujinx.Tests/Memory/PartialUnmaps.cs b/src/Ryujinx.Tests/Memory/PartialUnmaps.cs new file mode 100644 index 00000000..ace68e5c --- /dev/null +++ b/src/Ryujinx.Tests/Memory/PartialUnmaps.cs @@ -0,0 +1,468 @@ +using ARMeilleure.Signal; +using ARMeilleure.Translation; +using NUnit.Framework; +using Ryujinx.Common.Memory.PartialUnmaps; +using Ryujinx.Cpu; +using Ryujinx.Cpu.Jit; +using Ryujinx.Memory; +using Ryujinx.Memory.Tracking; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Tests.Memory +{ + [TestFixture] + internal class PartialUnmaps + { + private static Translator _translator; + + private static (MemoryBlock virt, MemoryBlock mirror, MemoryEhMeilleure exceptionHandler) GetVirtual(ulong asSize) + { + MemoryAllocationFlags asFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible; + + var addressSpace = new MemoryBlock(asSize, asFlags); + var addressSpaceMirror = new MemoryBlock(asSize, asFlags); + + var tracking = new MemoryTracking(new MockVirtualMemoryManager(asSize, 0x1000), 0x1000); + var exceptionHandler = new MemoryEhMeilleure(addressSpace, addressSpaceMirror, tracking); + + return (addressSpace, addressSpaceMirror, exceptionHandler); + } + + private static int CountThreads(ref PartialUnmapState state) + { + int count = 0; + + ref var ids = ref state.LocalCounts.ThreadIds; + + for (int i = 0; i < ids.Length; i++) + { + if (ids[i] != 0) + { + count++; + } + } + + return count; + } + + private static void EnsureTranslator() + { + // Create a translator, as one is needed to register the signal handler or emit methods. + _translator ??= new Translator(new JitMemoryAllocator(), new MockMemoryManager(), true); + } + + [Test] + // Memory aliasing tests fail on CI at the moment. + [Platform(Exclude = "MacOsX")] + public void PartialUnmap([Values] bool readOnly) + { + // Set up an address space to test partial unmapping. + // Should register the signal handler to deal with this on Windows. + ulong vaSize = 0x100000; + + // The first 0x100000 is mapped to start. It is replaced from the center with the 0x200000 mapping. + var backing = new MemoryBlock(vaSize * 2, MemoryAllocationFlags.Mirrorable); + + (MemoryBlock unusedMainMemory, MemoryBlock memory, MemoryEhMeilleure exceptionHandler) = GetVirtual(vaSize * 2); + + EnsureTranslator(); + + ref var state = ref PartialUnmapState.GetRef(); + + Thread testThread = null; + bool shouldAccess = true; + + try + { + // Globally reset the struct for handling partial unmap races. + PartialUnmapState.Reset(); + bool error = false; + + // Create a large mapping. + memory.MapView(backing, 0, 0, vaSize); + + if (readOnly) + { + memory.Reprotect(0, vaSize, MemoryPermission.Read); + } + + if (readOnly) + { + // Write a value to the physical memory, then try to read it repeately from virtual. + // It should not change. + testThread = new Thread(() => + { + int i = 12345; + backing.Write(vaSize - 0x1000, i); + + while (shouldAccess) + { + if (memory.Read(vaSize - 0x1000) != i) + { + error = true; + shouldAccess = false; + } + } + }); + } + else + { + // Repeatedly write and check the value on the last page of the mapping on another thread. + testThread = new Thread(() => + { + int i = 0; + while (shouldAccess) + { + memory.Write(vaSize - 0x1000, i); + if (memory.Read(vaSize - 0x1000) != i) + { + error = true; + shouldAccess = false; + } + + i++; + } + }); + } + + testThread.Start(); + + // Create a smaller mapping, covering the larger mapping. + // Immediately try to write to the part of the larger mapping that did not change. + // Do this a lot, with the smaller mapping gradually increasing in size. Should not crash, data should not be lost. + + ulong pageSize = 0x1000; + int mappingExpandCount = (int)(vaSize / (pageSize * 2)) - 1; + ulong vaCenter = vaSize / 2; + + for (int i = 1; i <= mappingExpandCount; i++) + { + ulong start = vaCenter - (pageSize * (ulong)i); + ulong size = pageSize * (ulong)i * 2; + + ulong startPa = start + vaSize; + + memory.MapView(backing, startPa, start, size); + } + + // On Windows, this should put unmap counts on the thread local map. + if (OperatingSystem.IsWindows()) + { + // One thread should be present on the thread local map. Trimming should remove it. + Assert.AreEqual(1, CountThreads(ref state)); + } + + shouldAccess = false; + testThread.Join(); + + Assert.False(error); + + string test = null; + + try + { + test.IndexOf('1'); + } + catch (NullReferenceException) + { + // This shouldn't freeze. + } + + if (OperatingSystem.IsWindows()) + { + state.TrimThreads(); + + Assert.AreEqual(0, CountThreads(ref state)); + } + + /* + * Use this to test invalid access. Can't put this in the test suite unfortunately as invalid access crashes the test process. + * memory.Reprotect(vaSize - 0x1000, 0x1000, MemoryPermission.None); + * //memory.UnmapView(backing, vaSize - 0x1000, 0x1000); + * memory.Read(vaSize - 0x1000); + */ + } + finally + { + // In case something failed, we want to ensure the test thread is dead before disposing of the memory. + shouldAccess = false; + testThread?.Join(); + + exceptionHandler.Dispose(); + unusedMainMemory.Dispose(); + memory.Dispose(); + backing.Dispose(); + } + } + + [Test] + // Memory aliasing tests fail on CI at the moment. + [Platform(Exclude = "MacOsX")] + public unsafe void PartialUnmapNative() + { + + // Set up an address space to test partial unmapping. + // Should register the signal handler to deal with this on Windows. + ulong vaSize = 0x100000; + + // The first 0x100000 is mapped to start. It is replaced from the center with the 0x200000 mapping. + var backing = new MemoryBlock(vaSize * 2, MemoryAllocationFlags.Mirrorable); + + (MemoryBlock mainMemory, MemoryBlock unusedMirror, MemoryEhMeilleure exceptionHandler) = GetVirtual(vaSize * 2); + + EnsureTranslator(); + + ref var state = ref PartialUnmapState.GetRef(); + + // Create some state to be used for managing the native writing loop. + int stateSize = Unsafe.SizeOf(); + var statePtr = Marshal.AllocHGlobal(stateSize); + Unsafe.InitBlockUnaligned((void*)statePtr, 0, (uint)stateSize); + + ref NativeWriteLoopState writeLoopState = ref Unsafe.AsRef((void*)statePtr); + writeLoopState.Running = 1; + writeLoopState.Error = 0; + + try + { + // Globally reset the struct for handling partial unmap races. + PartialUnmapState.Reset(); + + // Create a large mapping. + mainMemory.MapView(backing, 0, 0, vaSize); + + var writeFunc = TestMethods.GenerateDebugNativeWriteLoop(); + IntPtr writePtr = mainMemory.GetPointer(vaSize - 0x1000, 4); + + Thread testThread = new(() => + { + writeFunc(statePtr, writePtr); + }); + + testThread.Start(); + + // Create a smaller mapping, covering the larger mapping. + // Immediately try to write to the part of the larger mapping that did not change. + // Do this a lot, with the smaller mapping gradually increasing in size. Should not crash, data should not be lost. + + ulong pageSize = 0x1000; + int mappingExpandCount = (int)(vaSize / (pageSize * 2)) - 1; + ulong vaCenter = vaSize / 2; + + for (int i = 1; i <= mappingExpandCount; i++) + { + ulong start = vaCenter - (pageSize * (ulong)i); + ulong size = pageSize * (ulong)i * 2; + + ulong startPa = start + vaSize; + + mainMemory.MapView(backing, startPa, start, size); + } + + writeLoopState.Running = 0; + testThread.Join(); + + Assert.False(writeLoopState.Error != 0); + } + finally + { + Marshal.FreeHGlobal(statePtr); + + exceptionHandler.Dispose(); + mainMemory.Dispose(); + unusedMirror.Dispose(); + backing.Dispose(); + } + } + + [Test] + // Only test in Windows, as this is only used on Windows and uses Windows APIs for trimming. + [Platform("Win")] + [SuppressMessage("Interoperability", "CA1416: Validate platform compatibility")] + public void ThreadLocalMap() + { + PartialUnmapState.Reset(); + ref var state = ref PartialUnmapState.GetRef(); + + bool running = true; + var testThread = new Thread(() => + { + PartialUnmapState.GetRef().RetryFromAccessViolation(); + while (running) + { + Thread.Sleep(1); + } + }); + + testThread.Start(); + Thread.Sleep(200); + + Assert.AreEqual(1, CountThreads(ref state)); + + // Trimming should not remove the thread as it's still active. + state.TrimThreads(); + Assert.AreEqual(1, CountThreads(ref state)); + + running = false; + + testThread.Join(); + + // Should trim now that it's inactive. + state.TrimThreads(); + Assert.AreEqual(0, CountThreads(ref state)); + } + + [Test] + // Only test in Windows, as this is only used on Windows and uses Windows APIs for trimming. + [Platform("Win")] + public unsafe void ThreadLocalMapNative() + { + EnsureTranslator(); + + PartialUnmapState.Reset(); + + ref var state = ref PartialUnmapState.GetRef(); + + fixed (void* localMap = &state.LocalCounts) + { + var getOrReserve = TestMethods.GenerateDebugThreadLocalMapGetOrReserve((IntPtr)localMap); + + for (int i = 0; i < ThreadLocalMap.MapSize; i++) + { + // Should obtain the index matching the call #. + Assert.AreEqual(i, getOrReserve(i + 1, i)); + + // Check that this and all previously reserved thread IDs and struct contents are intact. + for (int j = 0; j <= i; j++) + { + Assert.AreEqual(j + 1, state.LocalCounts.ThreadIds[j]); + Assert.AreEqual(j, state.LocalCounts.Structs[j]); + } + } + + // Trying to reserve again when the map is full should return -1. + Assert.AreEqual(-1, getOrReserve(200, 0)); + + for (int i = 0; i < ThreadLocalMap.MapSize; i++) + { + // Should obtain the index matching the call #, as it already exists. + Assert.AreEqual(i, getOrReserve(i + 1, -1)); + + // The struct should not be reset to -1. + Assert.AreEqual(i, state.LocalCounts.Structs[i]); + } + + // Clear one of the ids as if it were freed. + state.LocalCounts.ThreadIds[13] = 0; + + // GetOrReserve should now obtain and return 13. + Assert.AreEqual(13, getOrReserve(300, 301)); + Assert.AreEqual(300, state.LocalCounts.ThreadIds[13]); + Assert.AreEqual(301, state.LocalCounts.Structs[13]); + } + } + + [Test] + public void NativeReaderWriterLock() + { + var rwLock = new NativeReaderWriterLock(); + var threads = new List(); + + int value = 0; + + bool running = true; + bool error = false; + int readersAllowed = 1; + + for (int i = 0; i < 5; i++) + { + var readThread = new Thread(() => + { + int count = 0; + while (running) + { + rwLock.AcquireReaderLock(); + + int originalValue = Volatile.Read(ref value); + + count++; + + // Spin a bit. + for (int i = 0; i < 100; i++) + { + if (Volatile.Read(ref readersAllowed) == 0) + { + error = true; + running = false; + } + } + + // Should not change while the lock is held. + if (Volatile.Read(ref value) != originalValue) + { + error = true; + running = false; + } + + rwLock.ReleaseReaderLock(); + } + }); + + threads.Add(readThread); + } + + for (int i = 0; i < 2; i++) + { + var writeThread = new Thread(() => + { + int count = 0; + while (running) + { + rwLock.AcquireReaderLock(); + rwLock.UpgradeToWriterLock(); + + Thread.Sleep(2); + count++; + + Interlocked.Exchange(ref readersAllowed, 0); + + for (int i = 0; i < 10; i++) + { + Interlocked.Increment(ref value); + } + + Interlocked.Exchange(ref readersAllowed, 1); + + rwLock.DowngradeFromWriterLock(); + rwLock.ReleaseReaderLock(); + + Thread.Sleep(1); + } + }); + + threads.Add(writeThread); + } + + foreach (var thread in threads) + { + thread.Start(); + } + + Thread.Sleep(1000); + + running = false; + + foreach (var thread in threads) + { + thread.Join(); + } + + Assert.False(error); + } + } +} diff --git a/src/Ryujinx.Tests/Ryujinx.Tests.csproj b/src/Ryujinx.Tests/Ryujinx.Tests.csproj new file mode 100644 index 00000000..3be9787a --- /dev/null +++ b/src/Ryujinx.Tests/Ryujinx.Tests.csproj @@ -0,0 +1,50 @@ + + + + net8.0 + Exe + false + + windows + osx + linux + Debug;Release + $(MSBuildProjectDirectory)\.runsettings + + + + false + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.Tests/Time/TimeZoneRuleTests.cs b/src/Ryujinx.Tests/Time/TimeZoneRuleTests.cs new file mode 100644 index 00000000..8448309e --- /dev/null +++ b/src/Ryujinx.Tests/Time/TimeZoneRuleTests.cs @@ -0,0 +1,18 @@ +using NUnit.Framework; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Tests.Time +{ + internal class TimeZoneRuleTests + { + class EffectInfoParameterTests + { + [Test] + public void EnsureTypeSize() + { + Assert.AreEqual(0x4000, Unsafe.SizeOf()); + } + } + } +} diff --git a/src/Ryujinx.Tests/TreeDictionaryTests.cs b/src/Ryujinx.Tests/TreeDictionaryTests.cs new file mode 100644 index 00000000..ea9fb073 --- /dev/null +++ b/src/Ryujinx.Tests/TreeDictionaryTests.cs @@ -0,0 +1,244 @@ +using NUnit.Framework; +using Ryujinx.Common.Collections; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Tests.Collections +{ + class TreeDictionaryTests + { + [Test] + public void EnsureAddIntegrity() + { + TreeDictionary dictionary = new(); + + Assert.AreEqual(dictionary.Count, 0); + + dictionary.Add(2, 7); + dictionary.Add(1, 4); + dictionary.Add(10, 2); + dictionary.Add(4, 1); + dictionary.Add(3, 2); + dictionary.Add(11, 2); + dictionary.Add(5, 2); + + Assert.AreEqual(dictionary.Count, 7); + + List> list = dictionary.AsLevelOrderList(); + + /* + * Tree Should Look as Follows After Rotations + * + * 2 + * 1 4 + * 3 10 + * 5 11 + * + */ + + Assert.AreEqual(list.Count, dictionary.Count); + Assert.AreEqual(list[0].Key, 2); + Assert.AreEqual(list[1].Key, 1); + Assert.AreEqual(list[2].Key, 4); + Assert.AreEqual(list[3].Key, 3); + Assert.AreEqual(list[4].Key, 10); + Assert.AreEqual(list[5].Key, 5); + Assert.AreEqual(list[6].Key, 11); + } + + [Test] + public void EnsureRemoveIntegrity() + { + TreeDictionary dictionary = new(); + + Assert.AreEqual(dictionary.Count, 0); + + dictionary.Add(2, 7); + dictionary.Add(1, 4); + dictionary.Add(10, 2); + dictionary.Add(4, 1); + dictionary.Add(3, 2); + dictionary.Add(11, 2); + dictionary.Add(5, 2); + dictionary.Add(7, 2); + dictionary.Add(9, 2); + dictionary.Add(8, 2); + dictionary.Add(13, 2); + dictionary.Add(24, 2); + dictionary.Add(6, 2); + Assert.AreEqual(dictionary.Count, 13); + + List> list = dictionary.AsLevelOrderList(); + + /* + * Tree Should Look as Follows After Rotations + * + * 4 + * 2 10 + * 1 3 7 13 + * 5 9 11 24 + * 6 8 + */ + + foreach (KeyValuePair node in list) + { + Console.WriteLine($"{node.Key} -> {node.Value}"); + } + Assert.AreEqual(list.Count, dictionary.Count); + Assert.AreEqual(list[0].Key, 4); + Assert.AreEqual(list[1].Key, 2); + Assert.AreEqual(list[2].Key, 10); + Assert.AreEqual(list[3].Key, 1); + Assert.AreEqual(list[4].Key, 3); + Assert.AreEqual(list[5].Key, 7); + Assert.AreEqual(list[6].Key, 13); + Assert.AreEqual(list[7].Key, 5); + Assert.AreEqual(list[8].Key, 9); + Assert.AreEqual(list[9].Key, 11); + Assert.AreEqual(list[10].Key, 24); + Assert.AreEqual(list[11].Key, 6); + Assert.AreEqual(list[12].Key, 8); + + list.Clear(); + + dictionary.Remove(7); + + /* + * Tree Should Look as Follows After Removal + * + * 4 + * 2 10 + * 1 3 6 13 + * 5 9 11 24 + * 8 + */ + + list = dictionary.AsLevelOrderList(); + foreach (KeyValuePair node in list) + { + Console.WriteLine($"{node.Key} -> {node.Value}"); + } + Assert.AreEqual(list[0].Key, 4); + Assert.AreEqual(list[1].Key, 2); + Assert.AreEqual(list[2].Key, 10); + Assert.AreEqual(list[3].Key, 1); + Assert.AreEqual(list[4].Key, 3); + Assert.AreEqual(list[5].Key, 6); + Assert.AreEqual(list[6].Key, 13); + Assert.AreEqual(list[7].Key, 5); + Assert.AreEqual(list[8].Key, 9); + Assert.AreEqual(list[9].Key, 11); + Assert.AreEqual(list[10].Key, 24); + Assert.AreEqual(list[11].Key, 8); + + list.Clear(); + + dictionary.Remove(10); + + list = dictionary.AsLevelOrderList(); + /* + * Tree Should Look as Follows After Removal + * + * 4 + * 2 9 + * 1 3 6 13 + * 5 8 11 24 + * + */ + foreach (KeyValuePair node in list) + { + Console.WriteLine($"{node.Key} -> {node.Value}"); + } + Assert.AreEqual(list[0].Key, 4); + Assert.AreEqual(list[1].Key, 2); + Assert.AreEqual(list[2].Key, 9); + Assert.AreEqual(list[3].Key, 1); + Assert.AreEqual(list[4].Key, 3); + Assert.AreEqual(list[5].Key, 6); + Assert.AreEqual(list[6].Key, 13); + Assert.AreEqual(list[7].Key, 5); + Assert.AreEqual(list[8].Key, 8); + Assert.AreEqual(list[9].Key, 11); + Assert.AreEqual(list[10].Key, 24); + } + + [Test] + public void EnsureOverwriteIntegrity() + { + TreeDictionary dictionary = new(); + + Assert.AreEqual(dictionary.Count, 0); + + dictionary.Add(2, 7); + dictionary.Add(1, 4); + dictionary.Add(10, 2); + dictionary.Add(4, 1); + dictionary.Add(3, 2); + dictionary.Add(11, 2); + dictionary.Add(5, 2); + dictionary.Add(7, 2); + dictionary.Add(9, 2); + dictionary.Add(8, 2); + dictionary.Add(13, 2); + dictionary.Add(24, 2); + dictionary.Add(6, 2); + Assert.AreEqual(dictionary.Count, 13); + + List> list = dictionary.AsLevelOrderList(); + + foreach (KeyValuePair node in list) + { + Console.WriteLine($"{node.Key} -> {node.Value}"); + } + + /* + * Tree Should Look as Follows After Rotations + * + * 4 + * 2 10 + * 1 3 7 13 + * 5 9 11 24 + * 6 8 + */ + + Assert.AreEqual(list.Count, dictionary.Count); + Assert.AreEqual(list[0].Key, 4); + Assert.AreEqual(list[1].Key, 2); + Assert.AreEqual(list[2].Key, 10); + Assert.AreEqual(list[3].Key, 1); + Assert.AreEqual(list[4].Key, 3); + Assert.AreEqual(list[5].Key, 7); + Assert.AreEqual(list[6].Key, 13); + Assert.AreEqual(list[7].Key, 5); + Assert.AreEqual(list[8].Key, 9); + Assert.AreEqual(list[9].Key, 11); + Assert.AreEqual(list[10].Key, 24); + Assert.AreEqual(list[11].Key, 6); + Assert.AreEqual(list[12].Key, 8); + + Assert.AreEqual(list[4].Value, 2); + + dictionary.Add(3, 4); + + list = dictionary.AsLevelOrderList(); + + Assert.AreEqual(list[4].Value, 4); + + + // Assure that none of the nodes locations have been modified. + Assert.AreEqual(list[0].Key, 4); + Assert.AreEqual(list[1].Key, 2); + Assert.AreEqual(list[2].Key, 10); + Assert.AreEqual(list[3].Key, 1); + Assert.AreEqual(list[4].Key, 3); + Assert.AreEqual(list[5].Key, 7); + Assert.AreEqual(list[6].Key, 13); + Assert.AreEqual(list[7].Key, 5); + Assert.AreEqual(list[8].Key, 9); + Assert.AreEqual(list[9].Key, 11); + Assert.AreEqual(list[10].Key, 24); + Assert.AreEqual(list[11].Key, 6); + Assert.AreEqual(list[12].Key, 8); + } + } +} diff --git a/src/Ryujinx.UI.Common/App/ApplicationAddedEventArgs.cs b/src/Ryujinx.UI.Common/App/ApplicationAddedEventArgs.cs new file mode 100644 index 00000000..58e066b9 --- /dev/null +++ b/src/Ryujinx.UI.Common/App/ApplicationAddedEventArgs.cs @@ -0,0 +1,9 @@ +using System; + +namespace Ryujinx.UI.App.Common +{ + public class ApplicationAddedEventArgs : EventArgs + { + public ApplicationData AppData { get; set; } + } +} diff --git a/src/Ryujinx.UI.Common/App/ApplicationCountUpdatedEventArgs.cs b/src/Ryujinx.UI.Common/App/ApplicationCountUpdatedEventArgs.cs new file mode 100644 index 00000000..5ed7baf1 --- /dev/null +++ b/src/Ryujinx.UI.Common/App/ApplicationCountUpdatedEventArgs.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ryujinx.UI.App.Common +{ + public class ApplicationCountUpdatedEventArgs : EventArgs + { + public int NumAppsFound { get; set; } + public int NumAppsLoaded { get; set; } + } +} diff --git a/src/Ryujinx.UI.Common/App/ApplicationData.cs b/src/Ryujinx.UI.Common/App/ApplicationData.cs new file mode 100644 index 00000000..08bd2677 --- /dev/null +++ b/src/Ryujinx.UI.Common/App/ApplicationData.cs @@ -0,0 +1,166 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Loader; +using LibHac.Ns; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.Loaders.Processes.Extensions; +using Ryujinx.UI.Common.Helper; +using System; +using System.IO; +using System.Text.Json.Serialization; + +namespace Ryujinx.UI.App.Common +{ + public class ApplicationData + { + public bool Favorite { get; set; } + public byte[] Icon { get; set; } + public string Name { get; set; } = "Unknown"; + public ulong Id { get; set; } + public string Developer { get; set; } = "Unknown"; + public string Version { get; set; } = "0"; + public TimeSpan TimePlayed { get; set; } + public DateTime? LastPlayed { get; set; } + public string FileExtension { get; set; } + public long FileSize { get; set; } + public string Path { get; set; } + public BlitStruct ControlHolder { get; set; } + + public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed); + + public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed); + + public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize); + + [JsonIgnore] public string IdString => Id.ToString("x16"); + + [JsonIgnore] public ulong IdBase => Id & ~0x1FFFUL; + + [JsonIgnore] public string IdBaseString => IdBase.ToString("x16"); + + public static string GetBuildId(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel, string titleFilePath) + { + using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read); + + Nca mainNca = null; + Nca patchNca = null; + + if (!System.IO.Path.Exists(titleFilePath)) + { + Logger.Error?.Print(LogClass.Application, $"File \"{titleFilePath}\" does not exist."); + return string.Empty; + } + + string extension = System.IO.Path.GetExtension(titleFilePath).ToLower(); + + if (extension is ".nsp" or ".xci") + { + IFileSystem pfs; + + if (extension == ".xci") + { + Xci xci = new(virtualFileSystem.KeySet, file.AsStorage()); + + pfs = xci.OpenPartition(XciPartitionType.Secure); + } + else + { + var pfsTemp = new PartitionFileSystem(); + pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure(); + pfs = pfsTemp; + } + + foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) + { + using var ncaFile = new UniqueRef(); + + pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = new(virtualFileSystem.KeySet, ncaFile.Get.AsStorage()); + + if (nca.Header.ContentType != NcaContentType.Program) + { + continue; + } + + int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); + + if (nca.Header.GetFsHeader(dataIndex).IsPatchSection()) + { + patchNca = nca; + } + else + { + mainNca = nca; + } + } + } + else if (extension == ".nca") + { + mainNca = new Nca(virtualFileSystem.KeySet, file.AsStorage()); + } + + if (mainNca == null) + { + Logger.Error?.Print(LogClass.Application, "Extraction failure. The main NCA was not present in the selected file"); + + return string.Empty; + } + + (Nca updatePatchNca, _) = mainNca.GetUpdateData(virtualFileSystem, checkLevel, 0, out string _); + + if (updatePatchNca != null) + { + patchNca = updatePatchNca; + } + + IFileSystem codeFs = null; + + if (patchNca == null) + { + if (mainNca.CanOpenSection(NcaSectionType.Code)) + { + codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, IntegrityCheckLevel.ErrorOnInvalid); + } + } + else + { + if (patchNca.CanOpenSection(NcaSectionType.Code)) + { + codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, IntegrityCheckLevel.ErrorOnInvalid); + } + } + + if (codeFs == null) + { + Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA"); + + return string.Empty; + } + + const string MainExeFs = "main"; + + if (!codeFs.FileExists($"/{MainExeFs}")) + { + Logger.Error?.Print(LogClass.Loader, "No main binary ExeFS found in ExeFS"); + + return string.Empty; + } + + using var nsoFile = new UniqueRef(); + + codeFs.OpenFile(ref nsoFile.Ref, $"/{MainExeFs}".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + NsoReader reader = new(); + reader.Initialize(nsoFile.Release().AsStorage().AsFile(OpenMode.Read)).ThrowIfFailure(); + + return BitConverter.ToString(reader.Header.ModuleId.ItemsRo.ToArray()).Replace("-", "").ToUpper()[..16]; + } + } +} diff --git a/src/Ryujinx.UI.Common/App/ApplicationJsonSerializerContext.cs b/src/Ryujinx.UI.Common/App/ApplicationJsonSerializerContext.cs new file mode 100644 index 00000000..ada7cc34 --- /dev/null +++ b/src/Ryujinx.UI.Common/App/ApplicationJsonSerializerContext.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.UI.App.Common +{ + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(ApplicationMetadata))] + internal partial class ApplicationJsonSerializerContext : JsonSerializerContext + { + } +} diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs new file mode 100644 index 00000000..2defc1f6 --- /dev/null +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -0,0 +1,940 @@ +using LibHac; +using LibHac.Common; +using LibHac.Common.Keys; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Ns; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.HLE.Loaders.Npdm; +using Ryujinx.HLE.Loaders.Processes.Extensions; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Configuration.System; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.Json; +using System.Threading; +using ContentType = LibHac.Ncm.ContentType; +using Path = System.IO.Path; +using TimeSpan = System.TimeSpan; + +namespace Ryujinx.UI.App.Common +{ + public class ApplicationLibrary + { + public Language DesiredLanguage { get; set; } + public event EventHandler ApplicationAdded; + public event EventHandler ApplicationCountUpdated; + + private readonly byte[] _nspIcon; + private readonly byte[] _xciIcon; + private readonly byte[] _ncaIcon; + private readonly byte[] _nroIcon; + private readonly byte[] _nsoIcon; + + private readonly VirtualFileSystem _virtualFileSystem; + private readonly IntegrityCheckLevel _checkLevel; + private CancellationTokenSource _cancellationToken; + + private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public ApplicationLibrary(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel) + { + _virtualFileSystem = virtualFileSystem; + _checkLevel = checkLevel; + + _nspIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NSP.png"); + _xciIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_XCI.png"); + _ncaIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NCA.png"); + _nroIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NRO.png"); + _nsoIcon = GetResourceBytes("Ryujinx.UI.Common.Resources.Icon_NSO.png"); + } + + private static byte[] GetResourceBytes(string resourceName) + { + Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName); + byte[] resourceByteArray = new byte[resourceStream.Length]; + + resourceStream.ReadExactly(resourceByteArray); + + return resourceByteArray; + } + + /// The npdm file doesn't contain valid data. + /// The FsAccessHeader.ContentOwnerId section is not implemented. + /// An error occured while reading bytes from the stream. + /// The end of the stream is reached. + /// An I/O error occurred. + private ApplicationData GetApplicationFromExeFs(PartitionFileSystem pfs, string filePath) + { + ApplicationData data = new() + { + Icon = _nspIcon, + Path = filePath, + }; + + using UniqueRef npdmFile = new(); + + Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read); + + if (ResultFs.PathNotFound.Includes(result)) + { + Npdm npdm = new(npdmFile.Get.AsStream()); + + data.Name = npdm.TitleName; + data.Id = npdm.Aci0.TitleId; + } + + return data; + } + + /// The configured key set is missing a key. + /// The NCA header could not be decrypted. + /// The NCA version is not supported. + /// An error occured while reading PFS data. + /// The npdm file doesn't contain valid data. + /// The FsAccessHeader.ContentOwnerId section is not implemented. + /// An error occured while reading bytes from the stream. + /// The end of the stream is reached. + /// An I/O error occurred. + private ApplicationData GetApplicationFromNsp(PartitionFileSystem pfs, string filePath) + { + bool isExeFs = false; + + // If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application. + bool hasMainNca = false; + + foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*")) + { + if (Path.GetExtension(fileEntry.FullPath)?.ToLower() == ".nca") + { + using UniqueRef ncaFile = new(); + + try + { + pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage()); + int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); + + // Some main NCAs don't have a data partition, so check if the partition exists before opening it + if (nca.Header.ContentType == NcaContentType.Program && + !(nca.SectionExists(NcaSectionType.Data) && + nca.Header.GetFsHeader(dataIndex).IsPatchSection())) + { + hasMainNca = true; + + break; + } + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"Encountered an error while trying to load applications from file '{filePath}': {exception}"); + + return null; + } + } + else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main") + { + isExeFs = true; + } + } + + if (hasMainNca) + { + List applications = GetApplicationsFromPfs(pfs, filePath); + + switch (applications.Count) + { + case 1: + return applications[0]; + case >= 1: + Logger.Warning?.Print(LogClass.Application, $"File '{filePath}' contains more applications than expected: {applications.Count}"); + return applications[0]; + default: + return null; + } + } + + if (isExeFs) + { + return GetApplicationFromExeFs(pfs, filePath); + } + + return null; + } + + /// The configured key set is missing a key. + /// The NCA header could not be decrypted. + /// The NCA version is not supported. + /// An error occured while reading PFS data. + private List GetApplicationsFromPfs(IFileSystem pfs, string filePath) + { + var applications = new List(); + string extension = Path.GetExtension(filePath).ToLower(); + + foreach ((ulong titleId, ContentMetaData content) in pfs.GetContentData(ContentMetaType.Application, _virtualFileSystem, _checkLevel)) + { + ApplicationData applicationData = new() + { + Id = titleId, + Path = filePath, + }; + + Nca mainNca = content.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Program); + Nca controlNca = content.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Control); + + BlitStruct controlHolder = new(1); + + IFileSystem controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, _checkLevel); + + // Check if there is an update available. + if (IsUpdateApplied(mainNca, out IFileSystem updatedControlFs)) + { + // Replace the original ControlFs by the updated one. + controlFs = updatedControlFs; + } + + if (controlFs == null) + { + continue; + } + + ReadControlData(controlFs, controlHolder.ByteSpan); + + GetApplicationInformation(ref controlHolder.Value, ref applicationData); + + // Read the icon from the ControlFS and store it as a byte array + try + { + using UniqueRef icon = new(); + + controlFs.OpenFile(ref icon.Ref, $"/icon_{DesiredLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + using MemoryStream stream = new(); + + icon.Get.AsStream().CopyTo(stream); + applicationData.Icon = stream.ToArray(); + } + catch (HorizonResultException) + { + foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*")) + { + if (entry.Name == "control.nacp") + { + continue; + } + + using var icon = new UniqueRef(); + + controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + using MemoryStream stream = new(); + + icon.Get.AsStream().CopyTo(stream); + applicationData.Icon = stream.ToArray(); + + if (applicationData.Icon != null) + { + break; + } + } + + applicationData.Icon ??= extension == ".xci" ? _xciIcon : _nspIcon; + } + + applicationData.ControlHolder = controlHolder; + + applications.Add(applicationData); + } + + return applications; + } + + public bool TryGetApplicationsFromFile(string applicationPath, out List applications) + { + applications = []; + long fileSize; + + try + { + fileSize = new FileInfo(applicationPath).Length; + } + catch (FileNotFoundException) + { + Logger.Warning?.Print(LogClass.Application, $"The file was not found: '{applicationPath}'"); + + return false; + } + + BlitStruct controlHolder = new(1); + + try + { + string extension = Path.GetExtension(applicationPath).ToLower(); + + using FileStream file = new(applicationPath, FileMode.Open, FileAccess.Read); + + switch (extension) + { + case ".xci": + { + Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage()); + + applications = GetApplicationsFromPfs(xci.OpenPartition(XciPartitionType.Secure), applicationPath); + + if (applications.Count == 0) + { + return false; + } + + break; + } + case ".nsp": + case ".pfs0": + { + var pfs = new PartitionFileSystem(); + pfs.Initialize(file.AsStorage()).ThrowIfFailure(); + + ApplicationData result = GetApplicationFromNsp(pfs, applicationPath); + + if (result == null) + { + return false; + } + + applications.Add(result); + + break; + } + case ".nro": + { + BinaryReader reader = new(file); + ApplicationData application = new(); + + file.Seek(24, SeekOrigin.Begin); + + int assetOffset = reader.ReadInt32(); + + if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET") + { + byte[] iconSectionInfo = Read(assetOffset + 8, 0x10); + + long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0); + long iconSize = BitConverter.ToInt64(iconSectionInfo, 8); + + ulong nacpOffset = reader.ReadUInt64(); + ulong nacpSize = reader.ReadUInt64(); + + // Reads and stores game icon as byte array + if (iconSize > 0) + { + application.Icon = Read(assetOffset + iconOffset, (int)iconSize); + } + else + { + application.Icon = _nroIcon; + } + + // Read the NACP data + Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan); + + GetApplicationInformation(ref controlHolder.Value, ref application); + } + else + { + application.Icon = _nroIcon; + application.Name = Path.GetFileNameWithoutExtension(applicationPath); + } + + application.ControlHolder = controlHolder; + applications.Add(application); + + break; + + byte[] Read(long position, int size) + { + file.Seek(position, SeekOrigin.Begin); + + return reader.ReadBytes(size); + } + } + case ".nca": + { + ApplicationData application = new(); + + Nca nca = new(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage()); + + if (!nca.IsProgram() || nca.IsPatch()) + { + return false; + } + + application.Icon = _ncaIcon; + application.Name = Path.GetFileNameWithoutExtension(applicationPath); + application.ControlHolder = controlHolder; + + applications.Add(application); + + break; + } + // If its an NSO we just set defaults + case ".nso": + { + ApplicationData application = new() + { + Icon = _nsoIcon, + Name = Path.GetFileNameWithoutExtension(applicationPath), + }; + + applications.Add(application); + + break; + } + } + } + catch (MissingKeyException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}"); + + return false; + } + catch (InvalidDataException) + { + Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}"); + + return false; + } + catch (IOException exception) + { + Logger.Warning?.Print(LogClass.Application, exception.Message); + + return false; + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}"); + + return false; + } + + foreach (var data in applications) + { + // Only load metadata for applications with an ID + if (data.Id != 0) + { + ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata => + { + appMetadata.Title = data.Name; + + // Only do the migration if time_played has a value and timespan_played hasn't been updated yet. + if (appMetadata.TimePlayedOld != default && appMetadata.TimePlayed == TimeSpan.Zero) + { + appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld); + appMetadata.TimePlayedOld = default; + } + + // Only do the migration if last_played has a value and last_played_utc doesn't exist yet. + if (appMetadata.LastPlayedOld != default && !appMetadata.LastPlayed.HasValue) + { + // Migrate from string-based last_played to DateTime-based last_played_utc. + if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed)) + { + appMetadata.LastPlayed = lastPlayedOldParsed; + + // Migration successful: deleting last_played from the metadata file. + appMetadata.LastPlayedOld = default; + } + + } + }); + + data.Favorite = appMetadata.Favorite; + data.TimePlayed = appMetadata.TimePlayed; + data.LastPlayed = appMetadata.LastPlayed; + } + + data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper(); + data.FileSize = fileSize; + data.Path = applicationPath; + } + + return true; + } + + public void CancelLoading() + { + _cancellationToken?.Cancel(); + } + + public static void ReadControlData(IFileSystem controlFs, Span outProperty) + { + using UniqueRef controlFile = new(); + + controlFs.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure(); + } + + public void LoadApplications(List appDirs) + { + int numApplicationsFound = 0; + int numApplicationsLoaded = 0; + + _cancellationToken = new CancellationTokenSource(); + + // Builds the applications list with paths to found applications + List applicationPaths = new(); + + try + { + foreach (string appDir in appDirs) + { + if (_cancellationToken.Token.IsCancellationRequested) + { + return; + } + + if (!Directory.Exists(appDir)) + { + Logger.Warning?.Print(LogClass.Application, $"The specified game directory \"{appDir}\" does not exist."); + + continue; + } + + try + { + EnumerationOptions options = new() + { + RecurseSubdirectories = true, + IgnoreInaccessible = false, + }; + + IEnumerable files = Directory.EnumerateFiles(appDir, "*", options).Where(file => + { + return + (Path.GetExtension(file).ToLower() is ".nsp" && ConfigurationState.Instance.UI.ShownFileTypes.NSP.Value) || + (Path.GetExtension(file).ToLower() is ".pfs0" && ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Value) || + (Path.GetExtension(file).ToLower() is ".xci" && ConfigurationState.Instance.UI.ShownFileTypes.XCI.Value) || + (Path.GetExtension(file).ToLower() is ".nca" && ConfigurationState.Instance.UI.ShownFileTypes.NCA.Value) || + (Path.GetExtension(file).ToLower() is ".nro" && ConfigurationState.Instance.UI.ShownFileTypes.NRO.Value) || + (Path.GetExtension(file).ToLower() is ".nso" && ConfigurationState.Instance.UI.ShownFileTypes.NSO.Value); + }); + + foreach (string app in files) + { + if (_cancellationToken.Token.IsCancellationRequested) + { + return; + } + + var fileInfo = new FileInfo(app); + + try + { + var fullPath = fileInfo.ResolveLinkTarget(true)?.FullName ?? fileInfo.FullName; + + applicationPaths.Add(fullPath); + numApplicationsFound++; + } + catch (IOException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Failed to resolve the full path to file: \"{app}\" Error: {exception}"); + } + } + } + catch (UnauthorizedAccessException) + { + Logger.Warning?.Print(LogClass.Application, $"Failed to get access to directory: \"{appDir}\""); + } + } + + // Loops through applications list, creating a struct and then firing an event containing the struct for each application + foreach (string applicationPath in applicationPaths) + { + if (_cancellationToken.Token.IsCancellationRequested) + { + return; + } + + if (TryGetApplicationsFromFile(applicationPath, out List applications)) + { + foreach (var application in applications) + { + OnApplicationAdded(new ApplicationAddedEventArgs + { + AppData = application, + }); + } + + if (applications.Count > 1) + { + numApplicationsFound += applications.Count - 1; + } + + numApplicationsLoaded += applications.Count; + } + else + { + numApplicationsFound--; + } + + OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs + { + NumAppsFound = numApplicationsFound, + NumAppsLoaded = numApplicationsLoaded, + }); + } + + OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs + { + NumAppsFound = numApplicationsFound, + NumAppsLoaded = numApplicationsLoaded, + }); + } + finally + { + _cancellationToken.Dispose(); + _cancellationToken = null; + } + } + + protected void OnApplicationAdded(ApplicationAddedEventArgs e) + { + ApplicationAdded?.Invoke(null, e); + } + + protected void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e) + { + ApplicationCountUpdated?.Invoke(null, e); + } + + public static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action modifyFunction = null) + { + string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui"); + string metadataFile = Path.Combine(metadataFolder, "metadata.json"); + + ApplicationMetadata appMetadata; + + if (!File.Exists(metadataFile)) + { + Directory.CreateDirectory(metadataFolder); + + appMetadata = new ApplicationMetadata(); + + JsonHelper.SerializeToFile(metadataFile, appMetadata, _serializerContext.ApplicationMetadata); + } + + try + { + appMetadata = JsonHelper.DeserializeFromFile(metadataFile, _serializerContext.ApplicationMetadata); + } + catch (JsonException) + { + Logger.Warning?.Print(LogClass.Application, $"Failed to parse metadata json for {titleId}. Loading defaults."); + + appMetadata = new ApplicationMetadata(); + } + + if (modifyFunction != null) + { + modifyFunction(appMetadata); + + JsonHelper.SerializeToFile(metadataFile, appMetadata, _serializerContext.ApplicationMetadata); + } + + return appMetadata; + } + + public byte[] GetApplicationIcon(string applicationPath, Language desiredTitleLanguage, ulong applicationId) + { + byte[] applicationIcon = null; + + if (applicationId == 0) + { + if (Directory.Exists(applicationPath)) + { + return _ncaIcon; + } + + return Path.GetExtension(applicationPath).ToLower() switch + { + ".nsp" => _nspIcon, + ".pfs0" => _nspIcon, + ".xci" => _xciIcon, + ".nso" => _nsoIcon, + ".nro" => _nroIcon, + ".nca" => _ncaIcon, + _ => _ncaIcon, + }; + } + + try + { + // Look for icon only if applicationPath is not a directory + if (!Directory.Exists(applicationPath)) + { + string extension = Path.GetExtension(applicationPath).ToLower(); + + using FileStream file = new(applicationPath, FileMode.Open, FileAccess.Read); + + if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci") + { + try + { + IFileSystem pfs; + + bool isExeFs = false; + + if (extension == ".xci") + { + Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage()); + + pfs = xci.OpenPartition(XciPartitionType.Secure); + } + else + { + var pfsTemp = new PartitionFileSystem(); + pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure(); + pfs = pfsTemp; + + foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*")) + { + if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main") + { + isExeFs = true; + } + } + } + + if (isExeFs) + { + applicationIcon = _nspIcon; + } + else + { + // Store the ControlFS in variable called controlFs + Dictionary programs = pfs.GetContentData(ContentMetaType.Application, _virtualFileSystem, _checkLevel); + IFileSystem controlFs = null; + + if (programs.TryGetValue(applicationId, out ContentMetaData value)) + { + if (value.GetNcaByType(_virtualFileSystem.KeySet, ContentType.Control) is { } controlNca) + { + controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None); + } + } + + // Read the icon from the ControlFS and store it as a byte array + try + { + using var icon = new UniqueRef(); + + controlFs.OpenFile(ref icon.Ref, $"/icon_{desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + using MemoryStream stream = new(); + + icon.Get.AsStream().CopyTo(stream); + applicationIcon = stream.ToArray(); + } + catch (HorizonResultException) + { + foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*")) + { + if (entry.Name == "control.nacp") + { + continue; + } + + using var icon = new UniqueRef(); + + controlFs.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + using MemoryStream stream = new(); + icon.Get.AsStream().CopyTo(stream); + applicationIcon = stream.ToArray(); + + break; + } + + applicationIcon ??= extension == ".xci" ? _xciIcon : _nspIcon; + } + } + } + catch (MissingKeyException) + { + applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon; + } + catch (InvalidDataException) + { + applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon; + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}"); + } + } + else if (extension == ".nro") + { + BinaryReader reader = new(file); + + byte[] Read(long position, int size) + { + file.Seek(position, SeekOrigin.Begin); + + return reader.ReadBytes(size); + } + + try + { + file.Seek(24, SeekOrigin.Begin); + + int assetOffset = reader.ReadInt32(); + + if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET") + { + byte[] iconSectionInfo = Read(assetOffset + 8, 0x10); + + long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0); + long iconSize = BitConverter.ToInt64(iconSectionInfo, 8); + + // Reads and stores game icon as byte array + if (iconSize > 0) + { + applicationIcon = Read(assetOffset + iconOffset, (int)iconSize); + } + else + { + applicationIcon = _nroIcon; + } + } + else + { + applicationIcon = _nroIcon; + } + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}"); + } + } + else if (extension == ".nca") + { + applicationIcon = _ncaIcon; + } + // If its an NSO we just set defaults + else if (extension == ".nso") + { + applicationIcon = _nsoIcon; + } + } + } + catch (Exception) + { + Logger.Warning?.Print(LogClass.Application, $"Could not retrieve a valid icon for the app. Default icon will be used. Errored File: {applicationPath}"); + } + + return applicationIcon ?? _ncaIcon; + } + + private void GetApplicationInformation(ref ApplicationControlProperty controlData, ref ApplicationData data) + { + _ = Enum.TryParse(DesiredLanguage.ToString(), out TitleLanguage desiredTitleLanguage); + + if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage) + { + data.Name = controlData.Title[(int)desiredTitleLanguage].NameString.ToString(); + data.Developer = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString(); + } + else + { + data.Name = null; + data.Developer = null; + } + + if (string.IsNullOrWhiteSpace(data.Name)) + { + foreach (ref readonly var controlTitle in controlData.Title.ItemsRo) + { + if (!controlTitle.NameString.IsEmpty()) + { + data.Name = controlTitle.NameString.ToString(); + + break; + } + } + } + + if (string.IsNullOrWhiteSpace(data.Developer)) + { + foreach (ref readonly var controlTitle in controlData.Title.ItemsRo) + { + if (!controlTitle.PublisherString.IsEmpty()) + { + data.Developer = controlTitle.PublisherString.ToString(); + + break; + } + } + } + + if (data.Id == 0) + { + if (controlData.SaveDataOwnerId != 0) + { + data.Id = controlData.SaveDataOwnerId; + } + else if (controlData.PresenceGroupId != 0) + { + data.Id = controlData.PresenceGroupId; + } + else if (controlData.AddOnContentBaseId != 0) + { + data.Id = (controlData.AddOnContentBaseId - 0x1000); + } + } + + data.Version = controlData.DisplayVersionString.ToString(); + } + + private bool IsUpdateApplied(Nca mainNca, out IFileSystem updatedControlFs) + { + updatedControlFs = null; + + string updatePath = null; + + try + { + (Nca patchNca, Nca controlNca) = mainNca.GetUpdateData(_virtualFileSystem, _checkLevel, 0, out updatePath); + + if (patchNca != null && controlNca != null) + { + updatedControlFs = controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None); + + return true; + } + } + catch (InvalidDataException) + { + Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}"); + } + catch (MissingKeyException exception) + { + Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {updatePath}"); + } + + return false; + } + } +} diff --git a/src/Ryujinx.UI.Common/App/ApplicationMetadata.cs b/src/Ryujinx.UI.Common/App/ApplicationMetadata.cs new file mode 100644 index 00000000..81193c5b --- /dev/null +++ b/src/Ryujinx.UI.Common/App/ApplicationMetadata.cs @@ -0,0 +1,51 @@ +using System; +using System.Text.Json.Serialization; + +namespace Ryujinx.UI.App.Common +{ + public class ApplicationMetadata + { + public string Title { get; set; } + public bool Favorite { get; set; } + + [JsonPropertyName("timespan_played")] + public TimeSpan TimePlayed { get; set; } = TimeSpan.Zero; + + [JsonPropertyName("last_played_utc")] + public DateTime? LastPlayed { get; set; } = null; + + [JsonPropertyName("time_played")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public double TimePlayedOld { get; set; } + + [JsonPropertyName("last_played")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string LastPlayedOld { get; set; } + + /// + /// Updates . Call this before launching a game. + /// + public void UpdatePreGame() + { + LastPlayed = DateTime.UtcNow; + } + + /// + /// Updates and . Call this after a game ends. + /// + public void UpdatePostGame() + { + DateTime? prevLastPlayed = LastPlayed; + UpdatePreGame(); + + if (!prevLastPlayed.HasValue) + { + return; + } + + TimeSpan diff = DateTime.UtcNow - prevLastPlayed.Value; + double newTotalSeconds = TimePlayed.Add(diff).TotalSeconds; + TimePlayed = TimeSpan.FromSeconds(Math.Round(newTotalSeconds, MidpointRounding.AwayFromZero)); + } + } +} diff --git a/src/Ryujinx.UI.Common/Configuration/AudioBackend.cs b/src/Ryujinx.UI.Common/Configuration/AudioBackend.cs new file mode 100644 index 00000000..a952e7ac --- /dev/null +++ b/src/Ryujinx.UI.Common/Configuration/AudioBackend.cs @@ -0,0 +1,14 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.UI.Common.Configuration +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum AudioBackend + { + Dummy, + OpenAl, + SoundIo, + SDL2, + } +} diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs new file mode 100644 index 00000000..af3ad0a1 --- /dev/null +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs @@ -0,0 +1,419 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Multiplayer; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.UI.Common.Configuration.System; +using Ryujinx.UI.Common.Configuration.UI; +using System.Collections.Generic; +using System.Text.Json.Nodes; + +namespace Ryujinx.UI.Common.Configuration +{ + public class ConfigurationFileFormat + { + /// + /// The current version of the file format + /// + public const int CurrentVersion = 51; + + /// + /// Version of the configuration file format + /// + public int Version { get; set; } + + /// + /// Enables or disables logging to a file on disk + /// + public bool EnableFileLog { get; set; } + + /// + /// Whether or not backend threading is enabled. The "Auto" setting will determine whether threading should be enabled at runtime. + /// + public BackendThreading BackendThreading { get; set; } + + /// + /// Resolution Scale. An integer scale applied to applicable render targets. Values 1-4, or -1 to use a custom floating point scale instead. + /// + public int ResScale { get; set; } + + /// + /// Custom Resolution Scale. A custom floating point scale applied to applicable render targets. Only active when Resolution Scale is -1. + /// + public float ResScaleCustom { get; set; } + + /// + /// Max Anisotropy. Values range from 0 - 16. Set to -1 to let the game decide. + /// + public float MaxAnisotropy { get; set; } + + /// + /// Aspect Ratio applied to the renderer window. + /// + public AspectRatio AspectRatio { get; set; } + + /// + /// Applies anti-aliasing to the renderer. + /// + public AntiAliasing AntiAliasing { get; set; } + + /// + /// Sets the framebuffer upscaling type. + /// + public ScalingFilter ScalingFilter { get; set; } + + /// + /// Sets the framebuffer upscaling level. + /// + public int ScalingFilterLevel { get; set; } + + /// + /// Dumps shaders in this local directory + /// + public string GraphicsShadersDumpPath { get; set; } + + /// + /// Enables printing debug log messages + /// + public bool LoggingEnableDebug { get; set; } + + /// + /// Enables printing stub log messages + /// + public bool LoggingEnableStub { get; set; } + + /// + /// Enables printing info log messages + /// + public bool LoggingEnableInfo { get; set; } + + /// + /// Enables printing warning log messages + /// + public bool LoggingEnableWarn { get; set; } + + /// + /// Enables printing error log messages + /// + public bool LoggingEnableError { get; set; } + + /// + /// Enables printing trace log messages + /// + public bool LoggingEnableTrace { get; set; } + + /// + /// Enables printing guest log messages + /// + public bool LoggingEnableGuest { get; set; } + + /// + /// Enables printing FS access log messages + /// + public bool LoggingEnableFsAccessLog { get; set; } + + /// + /// Controls which log messages are written to the log targets + /// + public LogClass[] LoggingFilteredClasses { get; set; } + + /// + /// Change Graphics API debug log level + /// + public GraphicsDebugLevel LoggingGraphicsDebugLevel { get; set; } + + /// + /// Change System Language + /// + public Language SystemLanguage { get; set; } + + /// + /// Change System Region + /// + public Region SystemRegion { get; set; } + + /// + /// Change System TimeZone + /// + public string SystemTimeZone { get; set; } + + /// + /// Change System Time Offset in seconds + /// + public long SystemTimeOffset { get; set; } + + /// + /// Enables or disables Docked Mode + /// + public bool DockedMode { get; set; } + + /// + /// Enables or disables Discord Rich Presence + /// + public bool EnableDiscordIntegration { get; set; } + + /// + /// Checks for updates when Ryujinx starts when enabled + /// + public bool CheckUpdatesOnStart { get; set; } + + /// + /// Show "Confirm Exit" Dialog + /// + public bool ShowConfirmExit { get; set; } + + /// + /// Enables or disables save window size, position and state on close. + /// + public bool RememberWindowState { get; set; } + + /// + /// Enables hardware-accelerated rendering for Avalonia + /// + public bool EnableHardwareAcceleration { get; set; } + + /// + /// Whether to hide cursor on idle, always or never + /// + public HideCursorMode HideCursor { get; set; } + + /// + /// Enables or disables Vertical Sync + /// + public bool EnableVsync { get; set; } + + /// + /// Enables or disables Shader cache + /// + public bool EnableShaderCache { get; set; } + + /// + /// Enables or disables texture recompression + /// + public bool EnableTextureRecompression { get; set; } + + /// + /// Enables or disables Macro high-level emulation + /// + public bool EnableMacroHLE { get; set; } + + /// + /// Enables or disables color space passthrough, if available. + /// + public bool EnableColorSpacePassthrough { get; set; } + + /// + /// Enables or disables profiled translation cache persistency + /// + public bool EnablePtc { get; set; } + + /// + /// Enables or disables guest Internet access + /// + public bool EnableInternetAccess { get; set; } + + /// + /// Enables integrity checks on Game content files + /// + public bool EnableFsIntegrityChecks { get; set; } + + /// + /// Enables FS access log output to the console. Possible modes are 0-3 + /// + public int FsGlobalAccessLogMode { get; set; } + + /// + /// The selected audio backend + /// + public AudioBackend AudioBackend { get; set; } + + /// + /// The audio volume + /// + public float AudioVolume { get; set; } + + /// + /// The selected memory manager mode + /// + public MemoryManagerMode MemoryManagerMode { get; set; } + + /// + /// Expands the RAM amount on the emulated system from 4GiB to 6GiB + /// + public bool ExpandRam { get; set; } + + /// + /// Enable or disable ignoring missing services + /// + public bool IgnoreMissingServices { get; set; } + + /// + /// Used to toggle columns in the GUI + /// + public GuiColumns GuiColumns { get; set; } + + /// + /// Used to configure column sort settings in the GUI + /// + public ColumnSort ColumnSort { get; set; } + + /// + /// A list of directories containing games to be used to load games into the games list + /// + public List GameDirs { get; set; } + + /// + /// A list of file types to be hidden in the games List + /// + public ShownFileTypes ShownFileTypes { get; set; } + + /// + /// Main window start-up position, size and state + /// + public WindowStartup WindowStartup { get; set; } + + /// + /// Language Code for the UI + /// + public string LanguageCode { get; set; } + + /// + /// Enable or disable custom themes in the GUI + /// + public bool EnableCustomTheme { get; set; } + + /// + /// Path to custom GUI theme + /// + public string CustomThemePath { get; set; } + + /// + /// Chooses the base style // Not Used + /// + public string BaseStyle { get; set; } + + /// + /// Chooses the view mode of the game list // Not Used + /// + public int GameListViewMode { get; set; } + + /// + /// Show application name in Grid Mode // Not Used + /// + public bool ShowNames { get; set; } + + /// + /// Sets App Icon Size // Not Used + /// + public int GridSize { get; set; } + + /// + /// Sorts Apps in the game list // Not Used + /// + public int ApplicationSort { get; set; } + + /// + /// Sets if Grid is ordered in Ascending Order // Not Used + /// + public bool IsAscendingOrder { get; set; } + + /// + /// Start games in fullscreen mode + /// + public bool StartFullscreen { get; set; } + + /// + /// Show console window + /// + public bool ShowConsole { get; set; } + + /// + /// Enable or disable keyboard support (Independent from controllers binding) + /// + public bool EnableKeyboard { get; set; } + + /// + /// Enable or disable mouse support (Independent from controllers binding) + /// + public bool EnableMouse { get; set; } + + /// + /// Hotkey Keyboard Bindings + /// + public KeyboardHotkeys Hotkeys { get; set; } + + /// + /// Legacy keyboard control bindings + /// + /// Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions) + /// TODO: Remove this when those older versions aren't in use anymore. + public List KeyboardConfig { get; set; } + + /// + /// Legacy controller control bindings + /// + /// Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions) + /// TODO: Remove this when those older versions aren't in use anymore. + public List ControllerConfig { get; set; } + + /// + /// Input configurations + /// + public List InputConfig { get; set; } + + /// + /// Graphics backend + /// + public GraphicsBackend GraphicsBackend { get; set; } + + /// + /// Preferred GPU + /// + public string PreferredGpu { get; set; } + + /// + /// Multiplayer Mode + /// + public MultiplayerMode MultiplayerMode { get; set; } + + /// + /// GUID for the network interface used by LAN (or 0 for default) + /// + public string MultiplayerLanInterfaceId { get; set; } + + /// + /// Uses Hypervisor over JIT if available + /// + public bool UseHypervisor { get; set; } + + /// + /// Loads a configuration file from disk + /// + /// The path to the JSON configuration file + /// Parsed configuration file + public static bool TryLoad(string path, out ConfigurationFileFormat configurationFileFormat) + { + try + { + configurationFileFormat = JsonHelper.DeserializeFromFile(path, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat); + + return configurationFileFormat.Version != 0; + } + catch + { + configurationFileFormat = null; + + return false; + } + } + + /// + /// Save a configuration file to disk + /// + /// The path to the JSON configuration file + public void SaveConfig(string path) + { + JsonHelper.SerializeToFile(path, this, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat); + } + } +} diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormatSettings.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormatSettings.cs new file mode 100644 index 00000000..9861ebf1 --- /dev/null +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormatSettings.cs @@ -0,0 +1,9 @@ +using Ryujinx.Common.Utilities; + +namespace Ryujinx.UI.Common.Configuration +{ + internal static class ConfigurationFileFormatSettings + { + public static readonly ConfigurationJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + } +} diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationJsonSerializerContext.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationJsonSerializerContext.cs new file mode 100644 index 00000000..3c3e3f20 --- /dev/null +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationJsonSerializerContext.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.UI.Common.Configuration +{ + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(ConfigurationFileFormat))] + internal partial class ConfigurationJsonSerializerContext : JsonSerializerContext + { + } +} diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs new file mode 100644 index 00000000..8420dc5d --- /dev/null +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs @@ -0,0 +1,1613 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using Ryujinx.Common.Configuration.Multiplayer; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Vulkan; +using Ryujinx.UI.Common.Configuration.System; +using Ryujinx.UI.Common.Configuration.UI; +using Ryujinx.UI.Common.Helper; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.Json.Nodes; + +namespace Ryujinx.UI.Common.Configuration +{ + public class ConfigurationState + { + /// + /// UI configuration section + /// + public class UISection + { + public class Columns + { + public ReactiveObject FavColumn { get; private set; } + public ReactiveObject IconColumn { get; private set; } + public ReactiveObject AppColumn { get; private set; } + public ReactiveObject DevColumn { get; private set; } + public ReactiveObject VersionColumn { get; private set; } + public ReactiveObject TimePlayedColumn { get; private set; } + public ReactiveObject LastPlayedColumn { get; private set; } + public ReactiveObject FileExtColumn { get; private set; } + public ReactiveObject FileSizeColumn { get; private set; } + public ReactiveObject PathColumn { get; private set; } + + public Columns() + { + FavColumn = new ReactiveObject(); + IconColumn = new ReactiveObject(); + AppColumn = new ReactiveObject(); + DevColumn = new ReactiveObject(); + VersionColumn = new ReactiveObject(); + TimePlayedColumn = new ReactiveObject(); + LastPlayedColumn = new ReactiveObject(); + FileExtColumn = new ReactiveObject(); + FileSizeColumn = new ReactiveObject(); + PathColumn = new ReactiveObject(); + } + } + + public class ColumnSortSettings + { + public ReactiveObject SortColumnId { get; private set; } + public ReactiveObject SortAscending { get; private set; } + + public ColumnSortSettings() + { + SortColumnId = new ReactiveObject(); + SortAscending = new ReactiveObject(); + } + } + + /// + /// Used to toggle which file types are shown in the UI + /// + public class ShownFileTypeSettings + { + public ReactiveObject NSP { get; private set; } + public ReactiveObject PFS0 { get; private set; } + public ReactiveObject XCI { get; private set; } + public ReactiveObject NCA { get; private set; } + public ReactiveObject NRO { get; private set; } + public ReactiveObject NSO { get; private set; } + + public ShownFileTypeSettings() + { + NSP = new ReactiveObject(); + PFS0 = new ReactiveObject(); + XCI = new ReactiveObject(); + NCA = new ReactiveObject(); + NRO = new ReactiveObject(); + NSO = new ReactiveObject(); + } + } + + // + /// Determines main window start-up position, size and state + /// + public class WindowStartupSettings + { + public ReactiveObject WindowSizeWidth { get; private set; } + public ReactiveObject WindowSizeHeight { get; private set; } + public ReactiveObject WindowPositionX { get; private set; } + public ReactiveObject WindowPositionY { get; private set; } + public ReactiveObject WindowMaximized { get; private set; } + + public WindowStartupSettings() + { + WindowSizeWidth = new ReactiveObject(); + WindowSizeHeight = new ReactiveObject(); + WindowPositionX = new ReactiveObject(); + WindowPositionY = new ReactiveObject(); + WindowMaximized = new ReactiveObject(); + } + } + + /// + /// Used to toggle columns in the GUI + /// + public Columns GuiColumns { get; private set; } + + /// + /// Used to configure column sort settings in the GUI + /// + public ColumnSortSettings ColumnSort { get; private set; } + + /// + /// A list of directories containing games to be used to load games into the games list + /// + public ReactiveObject> GameDirs { get; private set; } + + /// + /// A list of file types to be hidden in the games List + /// + public ShownFileTypeSettings ShownFileTypes { get; private set; } + + /// + /// Determines main window start-up position, size and state + /// + public WindowStartupSettings WindowStartup { get; private set; } + + /// + /// Language Code for the UI + /// + public ReactiveObject LanguageCode { get; private set; } + + /// + /// Enable or disable custom themes in the GUI + /// + public ReactiveObject EnableCustomTheme { get; private set; } + + /// + /// Path to custom GUI theme + /// + public ReactiveObject CustomThemePath { get; private set; } + + /// + /// Selects the base style + /// + public ReactiveObject BaseStyle { get; private set; } + + /// + /// Start games in fullscreen mode + /// + public ReactiveObject StartFullscreen { get; private set; } + + /// + /// Hide / Show Console Window + /// + public ReactiveObject ShowConsole { get; private set; } + + /// + /// View Mode of the Game list + /// + public ReactiveObject GameListViewMode { get; private set; } + + /// + /// Show application name in Grid Mode + /// + public ReactiveObject ShowNames { get; private set; } + + /// + /// Sets App Icon Size in Grid Mode + /// + public ReactiveObject GridSize { get; private set; } + + /// + /// Sorts Apps in Grid Mode + /// + public ReactiveObject ApplicationSort { get; private set; } + + /// + /// Sets if Grid is ordered in Ascending Order + /// + public ReactiveObject IsAscendingOrder { get; private set; } + + public UISection() + { + GuiColumns = new Columns(); + ColumnSort = new ColumnSortSettings(); + GameDirs = new ReactiveObject>(); + ShownFileTypes = new ShownFileTypeSettings(); + WindowStartup = new WindowStartupSettings(); + EnableCustomTheme = new ReactiveObject(); + CustomThemePath = new ReactiveObject(); + BaseStyle = new ReactiveObject(); + StartFullscreen = new ReactiveObject(); + GameListViewMode = new ReactiveObject(); + ShowNames = new ReactiveObject(); + GridSize = new ReactiveObject(); + ApplicationSort = new ReactiveObject(); + IsAscendingOrder = new ReactiveObject(); + LanguageCode = new ReactiveObject(); + ShowConsole = new ReactiveObject(); + ShowConsole.Event += static (s, e) => { ConsoleHelper.SetConsoleWindowState(e.NewValue); }; + } + } + + /// + /// Logger configuration section + /// + public class LoggerSection + { + /// + /// Enables printing debug log messages + /// + public ReactiveObject EnableDebug { get; private set; } + + /// + /// Enables printing stub log messages + /// + public ReactiveObject EnableStub { get; private set; } + + /// + /// Enables printing info log messages + /// + public ReactiveObject EnableInfo { get; private set; } + + /// + /// Enables printing warning log messages + /// + public ReactiveObject EnableWarn { get; private set; } + + /// + /// Enables printing error log messages + /// + public ReactiveObject EnableError { get; private set; } + + /// + /// Enables printing trace log messages + /// + public ReactiveObject EnableTrace { get; private set; } + + /// + /// Enables printing guest log messages + /// + public ReactiveObject EnableGuest { get; private set; } + + /// + /// Enables printing FS access log messages + /// + public ReactiveObject EnableFsAccessLog { get; private set; } + + /// + /// Controls which log messages are written to the log targets + /// + public ReactiveObject FilteredClasses { get; private set; } + + /// + /// Enables or disables logging to a file on disk + /// + public ReactiveObject EnableFileLog { get; private set; } + + /// + /// Controls which OpenGL log messages are recorded in the log + /// + public ReactiveObject GraphicsDebugLevel { get; private set; } + + public LoggerSection() + { + EnableDebug = new ReactiveObject(); + EnableStub = new ReactiveObject(); + EnableInfo = new ReactiveObject(); + EnableWarn = new ReactiveObject(); + EnableError = new ReactiveObject(); + EnableTrace = new ReactiveObject(); + EnableGuest = new ReactiveObject(); + EnableFsAccessLog = new ReactiveObject(); + FilteredClasses = new ReactiveObject(); + EnableFileLog = new ReactiveObject(); + EnableFileLog.Event += static (sender, e) => LogValueChange(e, nameof(EnableFileLog)); + GraphicsDebugLevel = new ReactiveObject(); + } + } + + /// + /// System configuration section + /// + public class SystemSection + { + /// + /// Change System Language + /// + public ReactiveObject Language { get; private set; } + + /// + /// Change System Region + /// + public ReactiveObject Region { get; private set; } + + /// + /// Change System TimeZone + /// + public ReactiveObject TimeZone { get; private set; } + + /// + /// System Time Offset in Seconds + /// + public ReactiveObject SystemTimeOffset { get; private set; } + + /// + /// Enables or disables Docked Mode + /// + public ReactiveObject EnableDockedMode { get; private set; } + + /// + /// Enables or disables profiled translation cache persistency + /// + public ReactiveObject EnablePtc { get; private set; } + + /// + /// Enables or disables guest Internet access + /// + public ReactiveObject EnableInternetAccess { get; private set; } + + /// + /// Enables integrity checks on Game content files + /// + public ReactiveObject EnableFsIntegrityChecks { get; private set; } + + /// + /// Enables FS access log output to the console. Possible modes are 0-3 + /// + public ReactiveObject FsGlobalAccessLogMode { get; private set; } + + /// + /// The selected audio backend + /// + public ReactiveObject AudioBackend { get; private set; } + + /// + /// The audio backend volume + /// + public ReactiveObject AudioVolume { get; private set; } + + /// + /// The selected memory manager mode + /// + public ReactiveObject MemoryManagerMode { get; private set; } + + /// + /// Defines the amount of RAM available on the emulated system, and how it is distributed + /// + public ReactiveObject ExpandRam { get; private set; } + + /// + /// Enable or disable ignoring missing services + /// + public ReactiveObject IgnoreMissingServices { get; private set; } + + /// + /// Uses Hypervisor over JIT if available + /// + public ReactiveObject UseHypervisor { get; private set; } + + public SystemSection() + { + Language = new ReactiveObject(); + Region = new ReactiveObject(); + TimeZone = new ReactiveObject(); + SystemTimeOffset = new ReactiveObject(); + EnableDockedMode = new ReactiveObject(); + EnableDockedMode.Event += static (sender, e) => LogValueChange(e, nameof(EnableDockedMode)); + EnablePtc = new ReactiveObject(); + EnablePtc.Event += static (sender, e) => LogValueChange(e, nameof(EnablePtc)); + EnableInternetAccess = new ReactiveObject(); + EnableInternetAccess.Event += static (sender, e) => LogValueChange(e, nameof(EnableInternetAccess)); + EnableFsIntegrityChecks = new ReactiveObject(); + EnableFsIntegrityChecks.Event += static (sender, e) => LogValueChange(e, nameof(EnableFsIntegrityChecks)); + FsGlobalAccessLogMode = new ReactiveObject(); + FsGlobalAccessLogMode.Event += static (sender, e) => LogValueChange(e, nameof(FsGlobalAccessLogMode)); + AudioBackend = new ReactiveObject(); + AudioBackend.Event += static (sender, e) => LogValueChange(e, nameof(AudioBackend)); + MemoryManagerMode = new ReactiveObject(); + MemoryManagerMode.Event += static (sender, e) => LogValueChange(e, nameof(MemoryManagerMode)); + ExpandRam = new ReactiveObject(); + ExpandRam.Event += static (sender, e) => LogValueChange(e, nameof(ExpandRam)); + IgnoreMissingServices = new ReactiveObject(); + IgnoreMissingServices.Event += static (sender, e) => LogValueChange(e, nameof(IgnoreMissingServices)); + AudioVolume = new ReactiveObject(); + AudioVolume.Event += static (sender, e) => LogValueChange(e, nameof(AudioVolume)); + UseHypervisor = new ReactiveObject(); + UseHypervisor.Event += static (sender, e) => LogValueChange(e, nameof(UseHypervisor)); + } + } + + /// + /// Hid configuration section + /// + public class HidSection + { + /// + /// Enable or disable keyboard support (Independent from controllers binding) + /// + public ReactiveObject EnableKeyboard { get; private set; } + + /// + /// Enable or disable mouse support (Independent from controllers binding) + /// + public ReactiveObject EnableMouse { get; private set; } + + /// + /// Hotkey Keyboard Bindings + /// + public ReactiveObject Hotkeys { get; private set; } + + /// + /// Input device configuration. + /// NOTE: This ReactiveObject won't issue an event when the List has elements added or removed. + /// TODO: Implement a ReactiveList class. + /// + public ReactiveObject> InputConfig { get; private set; } + + public HidSection() + { + EnableKeyboard = new ReactiveObject(); + EnableMouse = new ReactiveObject(); + Hotkeys = new ReactiveObject(); + InputConfig = new ReactiveObject>(); + } + } + + /// + /// Graphics configuration section + /// + public class GraphicsSection + { + /// + /// Whether or not backend threading is enabled. The "Auto" setting will determine whether threading should be enabled at runtime. + /// + public ReactiveObject BackendThreading { get; private set; } + + /// + /// Max Anisotropy. Values range from 0 - 16. Set to -1 to let the game decide. + /// + public ReactiveObject MaxAnisotropy { get; private set; } + + /// + /// Aspect Ratio applied to the renderer window. + /// + public ReactiveObject AspectRatio { get; private set; } + + /// + /// Resolution Scale. An integer scale applied to applicable render targets. Values 1-4, or -1 to use a custom floating point scale instead. + /// + public ReactiveObject ResScale { get; private set; } + + /// + /// Custom Resolution Scale. A custom floating point scale applied to applicable render targets. Only active when Resolution Scale is -1. + /// + public ReactiveObject ResScaleCustom { get; private set; } + + /// + /// Dumps shaders in this local directory + /// + public ReactiveObject ShadersDumpPath { get; private set; } + + /// + /// Enables or disables Vertical Sync + /// + public ReactiveObject EnableVsync { get; private set; } + + /// + /// Enables or disables Shader cache + /// + public ReactiveObject EnableShaderCache { get; private set; } + + /// + /// Enables or disables texture recompression + /// + public ReactiveObject EnableTextureRecompression { get; private set; } + + /// + /// Enables or disables Macro high-level emulation + /// + public ReactiveObject EnableMacroHLE { get; private set; } + + /// + /// Enables or disables color space passthrough, if available. + /// + public ReactiveObject EnableColorSpacePassthrough { get; private set; } + + /// + /// Graphics backend + /// + public ReactiveObject GraphicsBackend { get; private set; } + + /// + /// Applies anti-aliasing to the renderer. + /// + public ReactiveObject AntiAliasing { get; private set; } + + /// + /// Sets the framebuffer upscaling type. + /// + public ReactiveObject ScalingFilter { get; private set; } + + /// + /// Sets the framebuffer upscaling level. + /// + public ReactiveObject ScalingFilterLevel { get; private set; } + + /// + /// Preferred GPU + /// + public ReactiveObject PreferredGpu { get; private set; } + + public GraphicsSection() + { + BackendThreading = new ReactiveObject(); + BackendThreading.Event += static (sender, e) => LogValueChange(e, nameof(BackendThreading)); + ResScale = new ReactiveObject(); + ResScale.Event += static (sender, e) => LogValueChange(e, nameof(ResScale)); + ResScaleCustom = new ReactiveObject(); + ResScaleCustom.Event += static (sender, e) => LogValueChange(e, nameof(ResScaleCustom)); + MaxAnisotropy = new ReactiveObject(); + MaxAnisotropy.Event += static (sender, e) => LogValueChange(e, nameof(MaxAnisotropy)); + AspectRatio = new ReactiveObject(); + AspectRatio.Event += static (sender, e) => LogValueChange(e, nameof(AspectRatio)); + ShadersDumpPath = new ReactiveObject(); + EnableVsync = new ReactiveObject(); + EnableVsync.Event += static (sender, e) => LogValueChange(e, nameof(EnableVsync)); + EnableShaderCache = new ReactiveObject(); + EnableShaderCache.Event += static (sender, e) => LogValueChange(e, nameof(EnableShaderCache)); + EnableTextureRecompression = new ReactiveObject(); + EnableTextureRecompression.Event += static (sender, e) => LogValueChange(e, nameof(EnableTextureRecompression)); + GraphicsBackend = new ReactiveObject(); + GraphicsBackend.Event += static (sender, e) => LogValueChange(e, nameof(GraphicsBackend)); + PreferredGpu = new ReactiveObject(); + PreferredGpu.Event += static (sender, e) => LogValueChange(e, nameof(PreferredGpu)); + EnableMacroHLE = new ReactiveObject(); + EnableMacroHLE.Event += static (sender, e) => LogValueChange(e, nameof(EnableMacroHLE)); + EnableColorSpacePassthrough = new ReactiveObject(); + EnableColorSpacePassthrough.Event += static (sender, e) => LogValueChange(e, nameof(EnableColorSpacePassthrough)); + AntiAliasing = new ReactiveObject(); + AntiAliasing.Event += static (sender, e) => LogValueChange(e, nameof(AntiAliasing)); + ScalingFilter = new ReactiveObject(); + ScalingFilter.Event += static (sender, e) => LogValueChange(e, nameof(ScalingFilter)); + ScalingFilterLevel = new ReactiveObject(); + ScalingFilterLevel.Event += static (sender, e) => LogValueChange(e, nameof(ScalingFilterLevel)); + } + } + + /// + /// Multiplayer configuration section + /// + public class MultiplayerSection + { + /// + /// GUID for the network interface used by LAN (or 0 for default) + /// + public ReactiveObject LanInterfaceId { get; private set; } + + /// + /// Multiplayer Mode + /// + public ReactiveObject Mode { get; private set; } + + public MultiplayerSection() + { + LanInterfaceId = new ReactiveObject(); + Mode = new ReactiveObject(); + Mode.Event += static (_, e) => LogValueChange(e, nameof(MultiplayerMode)); + } + } + + /// + /// The default configuration instance + /// + public static ConfigurationState Instance { get; private set; } + + /// + /// The UI section + /// + public UISection UI { get; private set; } + + /// + /// The Logger section + /// + public LoggerSection Logger { get; private set; } + + /// + /// The System section + /// + public SystemSection System { get; private set; } + + /// + /// The Graphics section + /// + public GraphicsSection Graphics { get; private set; } + + /// + /// The Hid section + /// + public HidSection Hid { get; private set; } + + /// + /// The Multiplayer section + /// + public MultiplayerSection Multiplayer { get; private set; } + + /// + /// Enables or disables Discord Rich Presence + /// + public ReactiveObject EnableDiscordIntegration { get; private set; } + + /// + /// Checks for updates when Ryujinx starts when enabled + /// + public ReactiveObject CheckUpdatesOnStart { get; private set; } + + /// + /// Show "Confirm Exit" Dialog + /// + public ReactiveObject ShowConfirmExit { get; private set; } + + /// + /// Enables or disables save window size, position and state on close. + /// + public ReactiveObject RememberWindowState { get; private set; } + + /// + /// Enables hardware-accelerated rendering for Avalonia + /// + public ReactiveObject EnableHardwareAcceleration { get; private set; } + + /// + /// Hide Cursor on Idle + /// + public ReactiveObject HideCursor { get; private set; } + + private ConfigurationState() + { + UI = new UISection(); + Logger = new LoggerSection(); + System = new SystemSection(); + Graphics = new GraphicsSection(); + Hid = new HidSection(); + Multiplayer = new MultiplayerSection(); + EnableDiscordIntegration = new ReactiveObject(); + CheckUpdatesOnStart = new ReactiveObject(); + ShowConfirmExit = new ReactiveObject(); + RememberWindowState = new ReactiveObject(); + EnableHardwareAcceleration = new ReactiveObject(); + HideCursor = new ReactiveObject(); + } + + public ConfigurationFileFormat ToFileFormat() + { + ConfigurationFileFormat configurationFile = new() + { + Version = ConfigurationFileFormat.CurrentVersion, + BackendThreading = Graphics.BackendThreading, + EnableFileLog = Logger.EnableFileLog, + ResScale = Graphics.ResScale, + ResScaleCustom = Graphics.ResScaleCustom, + MaxAnisotropy = Graphics.MaxAnisotropy, + AspectRatio = Graphics.AspectRatio, + AntiAliasing = Graphics.AntiAliasing, + ScalingFilter = Graphics.ScalingFilter, + ScalingFilterLevel = Graphics.ScalingFilterLevel, + GraphicsShadersDumpPath = Graphics.ShadersDumpPath, + LoggingEnableDebug = Logger.EnableDebug, + LoggingEnableStub = Logger.EnableStub, + LoggingEnableInfo = Logger.EnableInfo, + LoggingEnableWarn = Logger.EnableWarn, + LoggingEnableError = Logger.EnableError, + LoggingEnableTrace = Logger.EnableTrace, + LoggingEnableGuest = Logger.EnableGuest, + LoggingEnableFsAccessLog = Logger.EnableFsAccessLog, + LoggingFilteredClasses = Logger.FilteredClasses, + LoggingGraphicsDebugLevel = Logger.GraphicsDebugLevel, + SystemLanguage = System.Language, + SystemRegion = System.Region, + SystemTimeZone = System.TimeZone, + SystemTimeOffset = System.SystemTimeOffset, + DockedMode = System.EnableDockedMode, + EnableDiscordIntegration = EnableDiscordIntegration, + CheckUpdatesOnStart = CheckUpdatesOnStart, + ShowConfirmExit = ShowConfirmExit, + RememberWindowState = RememberWindowState, + EnableHardwareAcceleration = EnableHardwareAcceleration, + HideCursor = HideCursor, + EnableVsync = Graphics.EnableVsync, + EnableShaderCache = Graphics.EnableShaderCache, + EnableTextureRecompression = Graphics.EnableTextureRecompression, + EnableMacroHLE = Graphics.EnableMacroHLE, + EnableColorSpacePassthrough = Graphics.EnableColorSpacePassthrough, + EnablePtc = System.EnablePtc, + EnableInternetAccess = System.EnableInternetAccess, + EnableFsIntegrityChecks = System.EnableFsIntegrityChecks, + FsGlobalAccessLogMode = System.FsGlobalAccessLogMode, + AudioBackend = System.AudioBackend, + AudioVolume = System.AudioVolume, + MemoryManagerMode = System.MemoryManagerMode, + ExpandRam = System.ExpandRam, + IgnoreMissingServices = System.IgnoreMissingServices, + UseHypervisor = System.UseHypervisor, + GuiColumns = new GuiColumns + { + FavColumn = UI.GuiColumns.FavColumn, + IconColumn = UI.GuiColumns.IconColumn, + AppColumn = UI.GuiColumns.AppColumn, + DevColumn = UI.GuiColumns.DevColumn, + VersionColumn = UI.GuiColumns.VersionColumn, + TimePlayedColumn = UI.GuiColumns.TimePlayedColumn, + LastPlayedColumn = UI.GuiColumns.LastPlayedColumn, + FileExtColumn = UI.GuiColumns.FileExtColumn, + FileSizeColumn = UI.GuiColumns.FileSizeColumn, + PathColumn = UI.GuiColumns.PathColumn, + }, + ColumnSort = new ColumnSort + { + SortColumnId = UI.ColumnSort.SortColumnId, + SortAscending = UI.ColumnSort.SortAscending, + }, + GameDirs = UI.GameDirs, + ShownFileTypes = new ShownFileTypes + { + NSP = UI.ShownFileTypes.NSP, + PFS0 = UI.ShownFileTypes.PFS0, + XCI = UI.ShownFileTypes.XCI, + NCA = UI.ShownFileTypes.NCA, + NRO = UI.ShownFileTypes.NRO, + NSO = UI.ShownFileTypes.NSO, + }, + WindowStartup = new WindowStartup + { + WindowSizeWidth = UI.WindowStartup.WindowSizeWidth, + WindowSizeHeight = UI.WindowStartup.WindowSizeHeight, + WindowPositionX = UI.WindowStartup.WindowPositionX, + WindowPositionY = UI.WindowStartup.WindowPositionY, + WindowMaximized = UI.WindowStartup.WindowMaximized, + }, + LanguageCode = UI.LanguageCode, + EnableCustomTheme = UI.EnableCustomTheme, + CustomThemePath = UI.CustomThemePath, + BaseStyle = UI.BaseStyle, + GameListViewMode = UI.GameListViewMode, + ShowNames = UI.ShowNames, + GridSize = UI.GridSize, + ApplicationSort = UI.ApplicationSort, + IsAscendingOrder = UI.IsAscendingOrder, + StartFullscreen = UI.StartFullscreen, + ShowConsole = UI.ShowConsole, + EnableKeyboard = Hid.EnableKeyboard, + EnableMouse = Hid.EnableMouse, + Hotkeys = Hid.Hotkeys, + KeyboardConfig = new List(), + ControllerConfig = new List(), + InputConfig = Hid.InputConfig, + GraphicsBackend = Graphics.GraphicsBackend, + PreferredGpu = Graphics.PreferredGpu, + MultiplayerLanInterfaceId = Multiplayer.LanInterfaceId, + MultiplayerMode = Multiplayer.Mode, + }; + + return configurationFile; + } + + public void LoadDefault() + { + Logger.EnableFileLog.Value = true; + Graphics.BackendThreading.Value = BackendThreading.Auto; + Graphics.ResScale.Value = 1; + Graphics.ResScaleCustom.Value = 1.0f; + Graphics.MaxAnisotropy.Value = -1.0f; + Graphics.AspectRatio.Value = AspectRatio.Fixed16x9; + Graphics.GraphicsBackend.Value = DefaultGraphicsBackend(); + Graphics.PreferredGpu.Value = ""; + Graphics.ShadersDumpPath.Value = ""; + Logger.EnableDebug.Value = false; + Logger.EnableStub.Value = true; + Logger.EnableInfo.Value = true; + Logger.EnableWarn.Value = true; + Logger.EnableError.Value = true; + Logger.EnableTrace.Value = false; + Logger.EnableGuest.Value = true; + Logger.EnableFsAccessLog.Value = false; + Logger.FilteredClasses.Value = Array.Empty(); + Logger.GraphicsDebugLevel.Value = GraphicsDebugLevel.None; + System.Language.Value = Language.AmericanEnglish; + System.Region.Value = Region.USA; + System.TimeZone.Value = "UTC"; + System.SystemTimeOffset.Value = 0; + System.EnableDockedMode.Value = true; + EnableDiscordIntegration.Value = true; + CheckUpdatesOnStart.Value = true; + ShowConfirmExit.Value = true; + RememberWindowState.Value = true; + EnableHardwareAcceleration.Value = true; + HideCursor.Value = HideCursorMode.OnIdle; + Graphics.EnableVsync.Value = true; + Graphics.EnableShaderCache.Value = true; + Graphics.EnableTextureRecompression.Value = false; + Graphics.EnableMacroHLE.Value = true; + Graphics.EnableColorSpacePassthrough.Value = false; + Graphics.AntiAliasing.Value = AntiAliasing.None; + Graphics.ScalingFilter.Value = ScalingFilter.Bilinear; + Graphics.ScalingFilterLevel.Value = 80; + System.EnablePtc.Value = true; + System.EnableInternetAccess.Value = false; + System.EnableFsIntegrityChecks.Value = true; + System.FsGlobalAccessLogMode.Value = 0; + System.AudioBackend.Value = AudioBackend.SDL2; + System.AudioVolume.Value = 1; + System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe; + System.ExpandRam.Value = false; + System.IgnoreMissingServices.Value = false; + System.UseHypervisor.Value = true; + Multiplayer.LanInterfaceId.Value = "0"; + Multiplayer.Mode.Value = MultiplayerMode.Disabled; + UI.GuiColumns.FavColumn.Value = true; + UI.GuiColumns.IconColumn.Value = true; + UI.GuiColumns.AppColumn.Value = true; + UI.GuiColumns.DevColumn.Value = true; + UI.GuiColumns.VersionColumn.Value = true; + UI.GuiColumns.TimePlayedColumn.Value = true; + UI.GuiColumns.LastPlayedColumn.Value = true; + UI.GuiColumns.FileExtColumn.Value = true; + UI.GuiColumns.FileSizeColumn.Value = true; + UI.GuiColumns.PathColumn.Value = true; + UI.ColumnSort.SortColumnId.Value = 0; + UI.ColumnSort.SortAscending.Value = false; + UI.GameDirs.Value = new List(); + UI.ShownFileTypes.NSP.Value = true; + UI.ShownFileTypes.PFS0.Value = true; + UI.ShownFileTypes.XCI.Value = true; + UI.ShownFileTypes.NCA.Value = true; + UI.ShownFileTypes.NRO.Value = true; + UI.ShownFileTypes.NSO.Value = true; + UI.EnableCustomTheme.Value = true; + UI.LanguageCode.Value = "en_US"; + UI.CustomThemePath.Value = ""; + UI.BaseStyle.Value = "Dark"; + UI.GameListViewMode.Value = 0; + UI.ShowNames.Value = true; + UI.GridSize.Value = 2; + UI.ApplicationSort.Value = 0; + UI.IsAscendingOrder.Value = true; + UI.StartFullscreen.Value = false; + UI.ShowConsole.Value = true; + UI.WindowStartup.WindowSizeWidth.Value = 1280; + UI.WindowStartup.WindowSizeHeight.Value = 760; + UI.WindowStartup.WindowPositionX.Value = 0; + UI.WindowStartup.WindowPositionY.Value = 0; + UI.WindowStartup.WindowMaximized.Value = false; + Hid.EnableKeyboard.Value = false; + Hid.EnableMouse.Value = false; + Hid.Hotkeys.Value = new KeyboardHotkeys + { + ToggleVsync = Key.F1, + ToggleMute = Key.F2, + Screenshot = Key.F8, + ShowUI = Key.F4, + Pause = Key.F5, + ResScaleUp = Key.Unbound, + ResScaleDown = Key.Unbound, + VolumeUp = Key.Unbound, + VolumeDown = Key.Unbound, + }; + Hid.InputConfig.Value = new List + { + new StandardKeyboardInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.WindowKeyboard, + Id = "0", + PlayerIndex = PlayerIndex.Player1, + ControllerType = ControllerType.JoyconPair, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = Key.Up, + DpadDown = Key.Down, + DpadLeft = Key.Left, + DpadRight = Key.Right, + ButtonMinus = Key.Minus, + ButtonL = Key.E, + ButtonZl = Key.Q, + ButtonSl = Key.Unbound, + ButtonSr = Key.Unbound, + }, + LeftJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = Key.W, + StickDown = Key.S, + StickLeft = Key.A, + StickRight = Key.D, + StickButton = Key.F, + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = Key.Z, + ButtonB = Key.X, + ButtonX = Key.C, + ButtonY = Key.V, + ButtonPlus = Key.Plus, + ButtonR = Key.U, + ButtonZr = Key.O, + ButtonSl = Key.Unbound, + ButtonSr = Key.Unbound, + }, + RightJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = Key.I, + StickDown = Key.K, + StickLeft = Key.J, + StickRight = Key.L, + StickButton = Key.H, + }, + }, + }; + } + + public void Load(ConfigurationFileFormat configurationFileFormat, string configurationFilePath) + { + bool configurationFileUpdated = false; + + if (configurationFileFormat.Version < 0 || configurationFileFormat.Version > ConfigurationFileFormat.CurrentVersion) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Unsupported configuration version {configurationFileFormat.Version}, loading default."); + + LoadDefault(); + } + + if (configurationFileFormat.Version < 2) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 2."); + + configurationFileFormat.SystemRegion = Region.USA; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 3) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 3."); + + configurationFileFormat.SystemTimeZone = "UTC"; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 4) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 4."); + + configurationFileFormat.MaxAnisotropy = -1; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 5) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 5."); + + configurationFileFormat.SystemTimeOffset = 0; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 8) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 8."); + + configurationFileFormat.EnablePtc = true; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 9) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 9."); + + configurationFileFormat.ColumnSort = new ColumnSort + { + SortColumnId = 0, + SortAscending = false, + }; + + configurationFileFormat.Hotkeys = new KeyboardHotkeys + { + ToggleVsync = Key.F1, + }; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 10) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 10."); + + configurationFileFormat.AudioBackend = AudioBackend.OpenAl; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 11) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 11."); + + configurationFileFormat.ResScale = 1; + configurationFileFormat.ResScaleCustom = 1.0f; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 12) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 12."); + + configurationFileFormat.LoggingGraphicsDebugLevel = GraphicsDebugLevel.None; + + configurationFileUpdated = true; + } + + // configurationFileFormat.Version == 13 -> LDN1 + + if (configurationFileFormat.Version < 14) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 14."); + + configurationFileFormat.CheckUpdatesOnStart = true; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 16) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 16."); + + configurationFileFormat.EnableShaderCache = true; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 17) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 17."); + + configurationFileFormat.StartFullscreen = false; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 18) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 18."); + + configurationFileFormat.AspectRatio = AspectRatio.Fixed16x9; + + configurationFileUpdated = true; + } + + // configurationFileFormat.Version == 19 -> LDN2 + + if (configurationFileFormat.Version < 20) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 20."); + + configurationFileFormat.ShowConfirmExit = true; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 21) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 21."); + + // Initialize network config. + + configurationFileFormat.MultiplayerMode = MultiplayerMode.Disabled; + configurationFileFormat.MultiplayerLanInterfaceId = "0"; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 22) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 22."); + + configurationFileFormat.HideCursor = HideCursorMode.Never; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 24) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 24."); + + configurationFileFormat.InputConfig = new List + { + new StandardKeyboardInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.WindowKeyboard, + Id = "0", + PlayerIndex = PlayerIndex.Player1, + ControllerType = ControllerType.JoyconPair, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = Key.Up, + DpadDown = Key.Down, + DpadLeft = Key.Left, + DpadRight = Key.Right, + ButtonMinus = Key.Minus, + ButtonL = Key.E, + ButtonZl = Key.Q, + ButtonSl = Key.Unbound, + ButtonSr = Key.Unbound, + }, + LeftJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = Key.W, + StickDown = Key.S, + StickLeft = Key.A, + StickRight = Key.D, + StickButton = Key.F, + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = Key.Z, + ButtonB = Key.X, + ButtonX = Key.C, + ButtonY = Key.V, + ButtonPlus = Key.Plus, + ButtonR = Key.U, + ButtonZr = Key.O, + ButtonSl = Key.Unbound, + ButtonSr = Key.Unbound, + }, + RightJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = Key.I, + StickDown = Key.K, + StickLeft = Key.J, + StickRight = Key.L, + StickButton = Key.H, + }, + }, + }; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 25) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 25."); + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 26) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 26."); + + configurationFileFormat.MemoryManagerMode = MemoryManagerMode.HostMappedUnsafe; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 27) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 27."); + + configurationFileFormat.EnableMouse = false; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 28) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 28."); + + configurationFileFormat.Hotkeys = new KeyboardHotkeys + { + ToggleVsync = Key.F1, + Screenshot = Key.F8, + }; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 29) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 29."); + + configurationFileFormat.Hotkeys = new KeyboardHotkeys + { + ToggleVsync = Key.F1, + Screenshot = Key.F8, + ShowUI = Key.F4, + }; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 30) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 30."); + + foreach (InputConfig config in configurationFileFormat.InputConfig) + { + if (config is StandardControllerInputConfig controllerConfig) + { + controllerConfig.Rumble = new RumbleConfigController + { + EnableRumble = false, + StrongRumble = 1f, + WeakRumble = 1f, + }; + } + } + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 31) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 31."); + + configurationFileFormat.BackendThreading = BackendThreading.Auto; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 32) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 32."); + + configurationFileFormat.Hotkeys = new KeyboardHotkeys + { + ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync, + Screenshot = configurationFileFormat.Hotkeys.Screenshot, + ShowUI = configurationFileFormat.Hotkeys.ShowUI, + Pause = Key.F5, + }; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 33) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 33."); + + configurationFileFormat.Hotkeys = new KeyboardHotkeys + { + ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync, + Screenshot = configurationFileFormat.Hotkeys.Screenshot, + ShowUI = configurationFileFormat.Hotkeys.ShowUI, + Pause = configurationFileFormat.Hotkeys.Pause, + ToggleMute = Key.F2, + }; + + configurationFileFormat.AudioVolume = 1; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 34) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 34."); + + configurationFileFormat.EnableInternetAccess = false; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 35) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 35."); + + foreach (InputConfig config in configurationFileFormat.InputConfig) + { + if (config is StandardControllerInputConfig controllerConfig) + { + controllerConfig.RangeLeft = 1.0f; + controllerConfig.RangeRight = 1.0f; + } + } + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 36) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 36."); + + configurationFileFormat.LoggingEnableTrace = false; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 37) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 37."); + + configurationFileFormat.ShowConsole = true; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 38) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 38."); + + configurationFileFormat.BaseStyle = "Dark"; + configurationFileFormat.GameListViewMode = 0; + configurationFileFormat.ShowNames = true; + configurationFileFormat.GridSize = 2; + configurationFileFormat.LanguageCode = "en_US"; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 39) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 39."); + + configurationFileFormat.Hotkeys = new KeyboardHotkeys + { + ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync, + Screenshot = configurationFileFormat.Hotkeys.Screenshot, + ShowUI = configurationFileFormat.Hotkeys.ShowUI, + Pause = configurationFileFormat.Hotkeys.Pause, + ToggleMute = configurationFileFormat.Hotkeys.ToggleMute, + ResScaleUp = Key.Unbound, + ResScaleDown = Key.Unbound, + }; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 40) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 40."); + + configurationFileFormat.GraphicsBackend = GraphicsBackend.OpenGl; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 41) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 41."); + + configurationFileFormat.Hotkeys = new KeyboardHotkeys + { + ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync, + Screenshot = configurationFileFormat.Hotkeys.Screenshot, + ShowUI = configurationFileFormat.Hotkeys.ShowUI, + Pause = configurationFileFormat.Hotkeys.Pause, + ToggleMute = configurationFileFormat.Hotkeys.ToggleMute, + ResScaleUp = configurationFileFormat.Hotkeys.ResScaleUp, + ResScaleDown = configurationFileFormat.Hotkeys.ResScaleDown, + VolumeUp = Key.Unbound, + VolumeDown = Key.Unbound, + }; + } + + if (configurationFileFormat.Version < 42) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 42."); + + configurationFileFormat.EnableMacroHLE = true; + } + + if (configurationFileFormat.Version < 43) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 43."); + + configurationFileFormat.UseHypervisor = true; + } + + if (configurationFileFormat.Version < 44) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 44."); + + configurationFileFormat.AntiAliasing = AntiAliasing.None; + configurationFileFormat.ScalingFilter = ScalingFilter.Bilinear; + configurationFileFormat.ScalingFilterLevel = 80; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 45) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 45."); + + configurationFileFormat.ShownFileTypes = new ShownFileTypes + { + NSP = true, + PFS0 = true, + XCI = true, + NCA = true, + NRO = true, + NSO = true, + }; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 46) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 46."); + + configurationFileFormat.MultiplayerLanInterfaceId = "0"; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 47) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 47."); + + configurationFileFormat.WindowStartup = new WindowStartup + { + WindowPositionX = 0, + WindowPositionY = 0, + WindowSizeHeight = 760, + WindowSizeWidth = 1280, + WindowMaximized = false, + }; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 48) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 48."); + + configurationFileFormat.EnableColorSpacePassthrough = false; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 49) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 49."); + + if (OperatingSystem.IsMacOS()) + { + AppDataManager.FixMacOSConfigurationFolders(); + } + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 50) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 50."); + + configurationFileFormat.EnableHardwareAcceleration = true; + + configurationFileUpdated = true; + } + + if (configurationFileFormat.Version < 51) + { + Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 51."); + + configurationFileFormat.RememberWindowState = true; + + configurationFileUpdated = true; + } + + Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; + Graphics.ResScale.Value = configurationFileFormat.ResScale; + Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom; + Graphics.MaxAnisotropy.Value = configurationFileFormat.MaxAnisotropy; + Graphics.AspectRatio.Value = configurationFileFormat.AspectRatio; + Graphics.ShadersDumpPath.Value = configurationFileFormat.GraphicsShadersDumpPath; + Graphics.BackendThreading.Value = configurationFileFormat.BackendThreading; + Graphics.GraphicsBackend.Value = configurationFileFormat.GraphicsBackend; + Graphics.PreferredGpu.Value = configurationFileFormat.PreferredGpu; + Graphics.AntiAliasing.Value = configurationFileFormat.AntiAliasing; + Graphics.ScalingFilter.Value = configurationFileFormat.ScalingFilter; + Graphics.ScalingFilterLevel.Value = configurationFileFormat.ScalingFilterLevel; + Logger.EnableDebug.Value = configurationFileFormat.LoggingEnableDebug; + Logger.EnableStub.Value = configurationFileFormat.LoggingEnableStub; + Logger.EnableInfo.Value = configurationFileFormat.LoggingEnableInfo; + Logger.EnableWarn.Value = configurationFileFormat.LoggingEnableWarn; + Logger.EnableError.Value = configurationFileFormat.LoggingEnableError; + Logger.EnableTrace.Value = configurationFileFormat.LoggingEnableTrace; + Logger.EnableGuest.Value = configurationFileFormat.LoggingEnableGuest; + Logger.EnableFsAccessLog.Value = configurationFileFormat.LoggingEnableFsAccessLog; + Logger.FilteredClasses.Value = configurationFileFormat.LoggingFilteredClasses; + Logger.GraphicsDebugLevel.Value = configurationFileFormat.LoggingGraphicsDebugLevel; + System.Language.Value = configurationFileFormat.SystemLanguage; + System.Region.Value = configurationFileFormat.SystemRegion; + System.TimeZone.Value = configurationFileFormat.SystemTimeZone; + System.SystemTimeOffset.Value = configurationFileFormat.SystemTimeOffset; + System.EnableDockedMode.Value = configurationFileFormat.DockedMode; + EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration; + CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart; + ShowConfirmExit.Value = configurationFileFormat.ShowConfirmExit; + RememberWindowState.Value = configurationFileFormat.RememberWindowState; + EnableHardwareAcceleration.Value = configurationFileFormat.EnableHardwareAcceleration; + HideCursor.Value = configurationFileFormat.HideCursor; + Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync; + Graphics.EnableShaderCache.Value = configurationFileFormat.EnableShaderCache; + Graphics.EnableTextureRecompression.Value = configurationFileFormat.EnableTextureRecompression; + Graphics.EnableMacroHLE.Value = configurationFileFormat.EnableMacroHLE; + Graphics.EnableColorSpacePassthrough.Value = configurationFileFormat.EnableColorSpacePassthrough; + System.EnablePtc.Value = configurationFileFormat.EnablePtc; + System.EnableInternetAccess.Value = configurationFileFormat.EnableInternetAccess; + System.EnableFsIntegrityChecks.Value = configurationFileFormat.EnableFsIntegrityChecks; + System.FsGlobalAccessLogMode.Value = configurationFileFormat.FsGlobalAccessLogMode; + System.AudioBackend.Value = configurationFileFormat.AudioBackend; + System.AudioVolume.Value = configurationFileFormat.AudioVolume; + System.MemoryManagerMode.Value = configurationFileFormat.MemoryManagerMode; + System.ExpandRam.Value = configurationFileFormat.ExpandRam; + System.IgnoreMissingServices.Value = configurationFileFormat.IgnoreMissingServices; + System.UseHypervisor.Value = configurationFileFormat.UseHypervisor; + UI.GuiColumns.FavColumn.Value = configurationFileFormat.GuiColumns.FavColumn; + UI.GuiColumns.IconColumn.Value = configurationFileFormat.GuiColumns.IconColumn; + UI.GuiColumns.AppColumn.Value = configurationFileFormat.GuiColumns.AppColumn; + UI.GuiColumns.DevColumn.Value = configurationFileFormat.GuiColumns.DevColumn; + UI.GuiColumns.VersionColumn.Value = configurationFileFormat.GuiColumns.VersionColumn; + UI.GuiColumns.TimePlayedColumn.Value = configurationFileFormat.GuiColumns.TimePlayedColumn; + UI.GuiColumns.LastPlayedColumn.Value = configurationFileFormat.GuiColumns.LastPlayedColumn; + UI.GuiColumns.FileExtColumn.Value = configurationFileFormat.GuiColumns.FileExtColumn; + UI.GuiColumns.FileSizeColumn.Value = configurationFileFormat.GuiColumns.FileSizeColumn; + UI.GuiColumns.PathColumn.Value = configurationFileFormat.GuiColumns.PathColumn; + UI.ColumnSort.SortColumnId.Value = configurationFileFormat.ColumnSort.SortColumnId; + UI.ColumnSort.SortAscending.Value = configurationFileFormat.ColumnSort.SortAscending; + UI.GameDirs.Value = configurationFileFormat.GameDirs; + UI.ShownFileTypes.NSP.Value = configurationFileFormat.ShownFileTypes.NSP; + UI.ShownFileTypes.PFS0.Value = configurationFileFormat.ShownFileTypes.PFS0; + UI.ShownFileTypes.XCI.Value = configurationFileFormat.ShownFileTypes.XCI; + UI.ShownFileTypes.NCA.Value = configurationFileFormat.ShownFileTypes.NCA; + UI.ShownFileTypes.NRO.Value = configurationFileFormat.ShownFileTypes.NRO; + UI.ShownFileTypes.NSO.Value = configurationFileFormat.ShownFileTypes.NSO; + UI.EnableCustomTheme.Value = configurationFileFormat.EnableCustomTheme; + UI.LanguageCode.Value = configurationFileFormat.LanguageCode; + UI.CustomThemePath.Value = configurationFileFormat.CustomThemePath; + UI.BaseStyle.Value = configurationFileFormat.BaseStyle; + UI.GameListViewMode.Value = configurationFileFormat.GameListViewMode; + UI.ShowNames.Value = configurationFileFormat.ShowNames; + UI.IsAscendingOrder.Value = configurationFileFormat.IsAscendingOrder; + UI.GridSize.Value = configurationFileFormat.GridSize; + UI.ApplicationSort.Value = configurationFileFormat.ApplicationSort; + UI.StartFullscreen.Value = configurationFileFormat.StartFullscreen; + UI.ShowConsole.Value = configurationFileFormat.ShowConsole; + UI.WindowStartup.WindowSizeWidth.Value = configurationFileFormat.WindowStartup.WindowSizeWidth; + UI.WindowStartup.WindowSizeHeight.Value = configurationFileFormat.WindowStartup.WindowSizeHeight; + UI.WindowStartup.WindowPositionX.Value = configurationFileFormat.WindowStartup.WindowPositionX; + UI.WindowStartup.WindowPositionY.Value = configurationFileFormat.WindowStartup.WindowPositionY; + UI.WindowStartup.WindowMaximized.Value = configurationFileFormat.WindowStartup.WindowMaximized; + Hid.EnableKeyboard.Value = configurationFileFormat.EnableKeyboard; + Hid.EnableMouse.Value = configurationFileFormat.EnableMouse; + Hid.Hotkeys.Value = configurationFileFormat.Hotkeys; + Hid.InputConfig.Value = configurationFileFormat.InputConfig; + + if (Hid.InputConfig.Value == null) + { + Hid.InputConfig.Value = new List(); + } + + Multiplayer.LanInterfaceId.Value = configurationFileFormat.MultiplayerLanInterfaceId; + Multiplayer.Mode.Value = configurationFileFormat.MultiplayerMode; + + if (configurationFileUpdated) + { + ToFileFormat().SaveConfig(configurationFilePath); + + Ryujinx.Common.Logging.Logger.Notice.Print(LogClass.Application, $"Configuration file updated to version {ConfigurationFileFormat.CurrentVersion}"); + } + } + + private static GraphicsBackend DefaultGraphicsBackend() + { + // Any system running macOS or returning any amount of valid Vulkan devices should default to Vulkan. + // Checks for if the Vulkan version and featureset is compatible should be performed within VulkanRenderer. + if (OperatingSystem.IsMacOS() || VulkanRenderer.GetPhysicalDevices().Length > 0) + { + return GraphicsBackend.Vulkan; + } + + return GraphicsBackend.OpenGl; + } + + private static void LogValueChange(ReactiveEventArgs eventArgs, string valueName) + { + string message = string.Create(CultureInfo.InvariantCulture, $"{valueName} set to: {eventArgs.NewValue}"); + + Ryujinx.Common.Logging.Logger.Info?.Print(LogClass.Configuration, message); + } + + public static void Initialize() + { + if (Instance != null) + { + throw new InvalidOperationException("Configuration is already initialized"); + } + + Instance = new ConfigurationState(); + } + } +} diff --git a/src/Ryujinx.UI.Common/Configuration/FileTypes.cs b/src/Ryujinx.UI.Common/Configuration/FileTypes.cs new file mode 100644 index 00000000..1974207b --- /dev/null +++ b/src/Ryujinx.UI.Common/Configuration/FileTypes.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.UI.Common +{ + public enum FileTypes + { + NSP, + PFS0, + XCI, + NCA, + NRO, + NSO + } +} diff --git a/src/Ryujinx.UI.Common/Configuration/LoggerModule.cs b/src/Ryujinx.UI.Common/Configuration/LoggerModule.cs new file mode 100644 index 00000000..9cb28359 --- /dev/null +++ b/src/Ryujinx.UI.Common/Configuration/LoggerModule.cs @@ -0,0 +1,113 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Logging.Targets; +using System; +using System.IO; + +namespace Ryujinx.UI.Common.Configuration +{ + public static class LoggerModule + { + public static void Initialize() + { + ConfigurationState.Instance.Logger.EnableDebug.Event += ReloadEnableDebug; + ConfigurationState.Instance.Logger.EnableStub.Event += ReloadEnableStub; + ConfigurationState.Instance.Logger.EnableInfo.Event += ReloadEnableInfo; + ConfigurationState.Instance.Logger.EnableWarn.Event += ReloadEnableWarning; + ConfigurationState.Instance.Logger.EnableError.Event += ReloadEnableError; + ConfigurationState.Instance.Logger.EnableTrace.Event += ReloadEnableTrace; + ConfigurationState.Instance.Logger.EnableGuest.Event += ReloadEnableGuest; + ConfigurationState.Instance.Logger.EnableFsAccessLog.Event += ReloadEnableFsAccessLog; + ConfigurationState.Instance.Logger.FilteredClasses.Event += ReloadFilteredClasses; + ConfigurationState.Instance.Logger.EnableFileLog.Event += ReloadFileLogger; + } + + private static void ReloadEnableDebug(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Debug, e.NewValue); + } + + private static void ReloadEnableStub(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Stub, e.NewValue); + } + + private static void ReloadEnableInfo(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Info, e.NewValue); + } + + private static void ReloadEnableWarning(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Warning, e.NewValue); + } + + private static void ReloadEnableError(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Error, e.NewValue); + } + + private static void ReloadEnableTrace(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Trace, e.NewValue); + } + + private static void ReloadEnableGuest(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.Guest, e.NewValue); + } + + private static void ReloadEnableFsAccessLog(object sender, ReactiveEventArgs e) + { + Logger.SetEnable(LogLevel.AccessLog, e.NewValue); + } + + private static void ReloadFilteredClasses(object sender, ReactiveEventArgs e) + { + bool noFilter = e.NewValue.Length == 0; + + foreach (var logClass in Enum.GetValues()) + { + Logger.SetEnable(logClass, noFilter); + } + + foreach (var logClass in e.NewValue) + { + Logger.SetEnable(logClass, true); + } + } + + private static void ReloadFileLogger(object sender, ReactiveEventArgs e) + { + if (e.NewValue) + { + string logDir = AppDataManager.LogsDirPath; + FileStream logFile = null; + + if (!string.IsNullOrEmpty(logDir)) + { + logFile = FileLogTarget.PrepareLogFile(logDir); + } + + if (logFile == null) + { + Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the Logs directory, Application Data, or the Ryujinx directory is writable."); + Logger.RemoveTarget("file"); + + return; + } + + Logger.AddTarget(new AsyncLogTargetWrapper( + new FileLogTarget("file", logFile), + 1000, + AsyncLogTargetOverflowAction.Block + )); + } + else + { + Logger.RemoveTarget("file"); + } + } + } +} diff --git a/src/Ryujinx.UI.Common/Configuration/System/Language.cs b/src/Ryujinx.UI.Common/Configuration/System/Language.cs new file mode 100644 index 00000000..d1d395b0 --- /dev/null +++ b/src/Ryujinx.UI.Common/Configuration/System/Language.cs @@ -0,0 +1,28 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.UI.Common.Configuration.System +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum Language + { + Japanese, + AmericanEnglish, + French, + German, + Italian, + Spanish, + Chinese, + Korean, + Dutch, + Portuguese, + Russian, + Taiwanese, + BritishEnglish, + CanadianFrench, + LatinAmericanSpanish, + SimplifiedChinese, + TraditionalChinese, + BrazilianPortuguese, + } +} diff --git a/src/Ryujinx.UI.Common/Configuration/System/Region.cs b/src/Ryujinx.UI.Common/Configuration/System/Region.cs new file mode 100644 index 00000000..6087c70e --- /dev/null +++ b/src/Ryujinx.UI.Common/Configuration/System/Region.cs @@ -0,0 +1,17 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.UI.Common.Configuration.System +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum Region + { + Japan, + USA, + Europe, + Australia, + China, + Korea, + Taiwan, + } +} diff --git a/src/Ryujinx.UI.Common/Configuration/UI/ColumnSort.cs b/src/Ryujinx.UI.Common/Configuration/UI/ColumnSort.cs new file mode 100644 index 00000000..44e98c40 --- /dev/null +++ b/src/Ryujinx.UI.Common/Configuration/UI/ColumnSort.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.UI.Common.Configuration.UI +{ + public struct ColumnSort + { + public int SortColumnId { get; set; } + public bool SortAscending { get; set; } + } +} diff --git a/src/Ryujinx.UI.Common/Configuration/UI/GuiColumns.cs b/src/Ryujinx.UI.Common/Configuration/UI/GuiColumns.cs new file mode 100644 index 00000000..c778ef1f --- /dev/null +++ b/src/Ryujinx.UI.Common/Configuration/UI/GuiColumns.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.UI.Common.Configuration.UI +{ + public struct GuiColumns + { + public bool FavColumn { get; set; } + public bool IconColumn { get; set; } + public bool AppColumn { get; set; } + public bool DevColumn { get; set; } + public bool VersionColumn { get; set; } + public bool TimePlayedColumn { get; set; } + public bool LastPlayedColumn { get; set; } + public bool FileExtColumn { get; set; } + public bool FileSizeColumn { get; set; } + public bool PathColumn { get; set; } + } +} diff --git a/src/Ryujinx.UI.Common/Configuration/UI/ShownFileTypes.cs b/src/Ryujinx.UI.Common/Configuration/UI/ShownFileTypes.cs new file mode 100644 index 00000000..6c72a693 --- /dev/null +++ b/src/Ryujinx.UI.Common/Configuration/UI/ShownFileTypes.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.UI.Common.Configuration.UI +{ + public struct ShownFileTypes + { + public bool NSP { get; set; } + public bool PFS0 { get; set; } + public bool XCI { get; set; } + public bool NCA { get; set; } + public bool NRO { get; set; } + public bool NSO { get; set; } + } +} diff --git a/src/Ryujinx.UI.Common/Configuration/UI/WindowStartup.cs b/src/Ryujinx.UI.Common/Configuration/UI/WindowStartup.cs new file mode 100644 index 00000000..0df45913 --- /dev/null +++ b/src/Ryujinx.UI.Common/Configuration/UI/WindowStartup.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.UI.Common.Configuration.UI +{ + public struct WindowStartup + { + public int WindowSizeWidth { get; set; } + public int WindowSizeHeight { get; set; } + public int WindowPositionX { get; set; } + public int WindowPositionY { get; set; } + public bool WindowMaximized { get; set; } + } +} diff --git a/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs new file mode 100644 index 00000000..6966038b --- /dev/null +++ b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs @@ -0,0 +1,129 @@ +using DiscordRPC; +using Ryujinx.Common; +using Ryujinx.UI.Common.Configuration; +using System.Text; + +namespace Ryujinx.UI.Common +{ + public static class DiscordIntegrationModule + { + private const string Description = "A simple, experimental Nintendo Switch emulator."; + private const string ApplicationId = "1216775165866807456"; + + private const int ApplicationByteLimit = 128; + private const string Ellipsis = "…"; + + private static DiscordRpcClient _discordClient; + private static RichPresence _discordPresenceMain; + + public static void Initialize() + { + _discordPresenceMain = new RichPresence + { + Assets = new Assets + { + LargeImageKey = "ryujinx", + LargeImageText = Description, + }, + Details = "Main Menu", + State = "Idling", + Timestamps = Timestamps.Now, + Buttons = + [ + new Button + { + Label = "Website", + Url = "https://ryujinx.org/", + }, + ], + }; + + ConfigurationState.Instance.EnableDiscordIntegration.Event += Update; + } + + private static void Update(object sender, ReactiveEventArgs evnt) + { + if (evnt.OldValue != evnt.NewValue) + { + // If the integration was active, disable it and unload everything + if (evnt.OldValue) + { + _discordClient?.Dispose(); + + _discordClient = null; + } + + // If we need to activate it and the client isn't active, initialize it + if (evnt.NewValue && _discordClient == null) + { + _discordClient = new DiscordRpcClient(ApplicationId); + + _discordClient.Initialize(); + _discordClient.SetPresence(_discordPresenceMain); + } + } + } + + public static void SwitchToPlayingState(string titleId, string applicationName) + { + _discordClient?.SetPresence(new RichPresence + { + Assets = new Assets + { + LargeImageKey = "game", + LargeImageText = TruncateToByteLength(applicationName, ApplicationByteLimit), + SmallImageKey = "ryujinx", + SmallImageText = Description, + }, + Details = TruncateToByteLength($"Playing {applicationName}", ApplicationByteLimit), + State = (titleId == "0000000000000000") ? "Homebrew" : titleId.ToUpper(), + Timestamps = Timestamps.Now, + Buttons = + [ + new Button + { + Label = "Website", + Url = "https://ryujinx.org/", + }, + ], + }); + } + + public static void SwitchToMainMenu() + { + _discordClient?.SetPresence(_discordPresenceMain); + } + + private static string TruncateToByteLength(string input, int byteLimit) + { + if (Encoding.UTF8.GetByteCount(input) <= byteLimit) + { + return input; + } + + // Find the length to trim the string to guarantee we have space for the trailing ellipsis. + int trimLimit = byteLimit - Encoding.UTF8.GetByteCount(Ellipsis); + + // Make sure the string is long enough to perform the basic trim. + // Amount of bytes != Length of the string + if (input.Length > trimLimit) + { + // Basic trim to best case scenario of 1 byte characters. + input = input[..trimLimit]; + } + + while (Encoding.UTF8.GetByteCount(input) > trimLimit) + { + // Remove one character from the end of the string at a time. + input = input[..^1]; + } + + return input.TrimEnd() + Ellipsis; + } + + public static void Exit() + { + _discordClient?.Dispose(); + } + } +} diff --git a/src/Ryujinx.UI.Common/Extensions/FileTypeExtensions.cs b/src/Ryujinx.UI.Common/Extensions/FileTypeExtensions.cs new file mode 100644 index 00000000..7e71ba7a --- /dev/null +++ b/src/Ryujinx.UI.Common/Extensions/FileTypeExtensions.cs @@ -0,0 +1,25 @@ +using System; +using static Ryujinx.UI.Common.Configuration.ConfigurationState.UISection; + +namespace Ryujinx.UI.Common +{ + public static class FileTypesExtensions + { + /// + /// Gets the current value for the correlating FileType name. + /// + /// The name of the parameter to get the value of. + /// The config instance to get the value from. + /// The current value of the setting. Value is if the file type is the be shown on the games list, otherwise. + public static bool GetConfigValue(this FileTypes type, ShownFileTypeSettings config) => type switch + { + FileTypes.NSP => config.NSP.Value, + FileTypes.PFS0 => config.PFS0.Value, + FileTypes.XCI => config.XCI.Value, + FileTypes.NCA => config.NCA.Value, + FileTypes.NRO => config.NRO.Value, + FileTypes.NSO => config.NSO.Value, + _ => throw new ArgumentOutOfRangeException(nameof(type), type, null), + }; + } +} diff --git a/src/Ryujinx.UI.Common/Helper/CommandLineState.cs b/src/Ryujinx.UI.Common/Helper/CommandLineState.cs new file mode 100644 index 00000000..ae0e4d90 --- /dev/null +++ b/src/Ryujinx.UI.Common/Helper/CommandLineState.cs @@ -0,0 +1,108 @@ +using Ryujinx.Common.Logging; +using System.Collections.Generic; + +namespace Ryujinx.UI.Common.Helper +{ + public static class CommandLineState + { + public static string[] Arguments { get; private set; } + + public static bool? OverrideDockedMode { get; private set; } + public static bool? OverrideHardwareAcceleration { get; private set; } + public static string OverrideGraphicsBackend { get; private set; } + public static string OverrideHideCursor { get; private set; } + public static string BaseDirPathArg { get; private set; } + public static string Profile { get; private set; } + public static string LaunchPathArg { get; private set; } + public static string LaunchApplicationId { get; private set; } + public static bool StartFullscreenArg { get; private set; } + + public static void ParseArguments(string[] args) + { + List arguments = new(); + + // Parse Arguments. + for (int i = 0; i < args.Length; ++i) + { + string arg = args[i]; + + switch (arg) + { + case "-r": + case "--root-data-dir": + if (i + 1 >= args.Length) + { + Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'"); + + continue; + } + + BaseDirPathArg = args[++i]; + + arguments.Add(arg); + arguments.Add(args[i]); + break; + case "-p": + case "--profile": + if (i + 1 >= args.Length) + { + Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'"); + + continue; + } + + Profile = args[++i]; + + arguments.Add(arg); + arguments.Add(args[i]); + break; + case "-f": + case "--fullscreen": + StartFullscreenArg = true; + + arguments.Add(arg); + break; + case "-g": + case "--graphics-backend": + if (i + 1 >= args.Length) + { + Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'"); + + continue; + } + + OverrideGraphicsBackend = args[++i]; + break; + case "-i": + case "--application-id": + LaunchApplicationId = args[++i]; + break; + case "--docked-mode": + OverrideDockedMode = true; + break; + case "--handheld-mode": + OverrideDockedMode = false; + break; + case "--hide-cursor": + if (i + 1 >= args.Length) + { + Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'"); + + continue; + } + + OverrideHideCursor = args[++i]; + break; + case "--software-gui": + OverrideHardwareAcceleration = false; + break; + default: + LaunchPathArg = arg; + break; + } + } + + Arguments = arguments.ToArray(); + } + } +} diff --git a/src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs b/src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs new file mode 100644 index 00000000..208ff5c9 --- /dev/null +++ b/src/Ryujinx.UI.Common/Helper/ConsoleHelper.cs @@ -0,0 +1,50 @@ +using Ryujinx.Common.Logging; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.UI.Common.Helper +{ + public static partial class ConsoleHelper + { + public static bool SetConsoleWindowStateSupported => OperatingSystem.IsWindows(); + + public static void SetConsoleWindowState(bool show) + { + if (OperatingSystem.IsWindows()) + { + SetConsoleWindowStateWindows(show); + } + else if (show == false) + { + Logger.Warning?.Print(LogClass.Application, "OS doesn't support hiding console window"); + } + } + + [SupportedOSPlatform("windows")] + private static void SetConsoleWindowStateWindows(bool show) + { + const int SW_HIDE = 0; + const int SW_SHOW = 5; + + IntPtr hWnd = GetConsoleWindow(); + + if (hWnd == IntPtr.Zero) + { + Logger.Warning?.Print(LogClass.Application, "Attempted to show/hide console window but console window does not exist"); + return; + } + + ShowWindow(hWnd, show ? SW_SHOW : SW_HIDE); + } + + [SupportedOSPlatform("windows")] + [LibraryImport("kernel32")] + private static partial IntPtr GetConsoleWindow(); + + [SupportedOSPlatform("windows")] + [LibraryImport("user32")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool ShowWindow(IntPtr hWnd, int nCmdShow); + } +} diff --git a/src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs b/src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs new file mode 100644 index 00000000..7ed02031 --- /dev/null +++ b/src/Ryujinx.UI.Common/Helper/FileAssociationHelper.cs @@ -0,0 +1,202 @@ +using Microsoft.Win32; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.UI.Common.Helper +{ + public static partial class FileAssociationHelper + { + private static readonly string[] _fileExtensions = { ".nca", ".nro", ".nso", ".nsp", ".xci" }; + + [SupportedOSPlatform("linux")] + private static readonly string _mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime"); + + private const int SHCNE_ASSOCCHANGED = 0x8000000; + private const int SHCNF_FLUSH = 0x1000; + + [LibraryImport("shell32.dll", SetLastError = true)] + public static partial void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2); + + public static bool IsTypeAssociationSupported => (OperatingSystem.IsLinux() || OperatingSystem.IsWindows()) && !ReleaseInformation.IsFlatHubBuild; + + [SupportedOSPlatform("linux")] + private static bool AreMimeTypesRegisteredLinux() => File.Exists(Path.Combine(_mimeDbPath, "packages", "Ryujinx.xml")); + + [SupportedOSPlatform("linux")] + private static bool InstallLinuxMimeTypes(bool uninstall = false) + { + string installKeyword = uninstall ? "uninstall" : "install"; + + if ((uninstall && AreMimeTypesRegisteredLinux()) || (!uninstall && !AreMimeTypesRegisteredLinux())) + { + string mimeTypesFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "mime", "Ryujinx.xml"); + string additionalArgs = !uninstall ? "--novendor" : ""; + + using Process mimeProcess = new(); + + mimeProcess.StartInfo.FileName = "xdg-mime"; + mimeProcess.StartInfo.Arguments = $"{installKeyword} {additionalArgs} --mode user {mimeTypesFile}"; + + mimeProcess.Start(); + mimeProcess.WaitForExit(); + + if (mimeProcess.ExitCode != 0) + { + Logger.Error?.PrintMsg(LogClass.Application, $"Unable to {installKeyword} mime types. Make sure xdg-utils is installed. Process exited with code: {mimeProcess.ExitCode}"); + + return false; + } + + using Process updateMimeProcess = new(); + + updateMimeProcess.StartInfo.FileName = "update-mime-database"; + updateMimeProcess.StartInfo.Arguments = _mimeDbPath; + + updateMimeProcess.Start(); + updateMimeProcess.WaitForExit(); + + if (updateMimeProcess.ExitCode != 0) + { + Logger.Error?.PrintMsg(LogClass.Application, $"Could not update local mime database. Process exited with code: {updateMimeProcess.ExitCode}"); + } + } + + return true; + } + + [SupportedOSPlatform("windows")] + private static bool AreMimeTypesRegisteredWindows() + { + static bool CheckRegistering(string ext) + { + RegistryKey key = Registry.CurrentUser.OpenSubKey(@$"Software\Classes\{ext}"); + + if (key is null) + { + return false; + } + + var openCmd = key.OpenSubKey(@"shell\open\command"); + + string keyValue = (string)openCmd.GetValue(""); + + return keyValue is not null && (keyValue.Contains("Ryujinx") || keyValue.Contains(AppDomain.CurrentDomain.FriendlyName)); + } + + bool registered = false; + + foreach (string ext in _fileExtensions) + { + registered |= CheckRegistering(ext); + } + + return registered; + } + + [SupportedOSPlatform("windows")] + private static bool InstallWindowsMimeTypes(bool uninstall = false) + { + static bool RegisterExtension(string ext, bool uninstall = false) + { + string keyString = @$"Software\Classes\{ext}"; + + if (uninstall) + { + // If the types don't already exist, there's nothing to do and we can call this operation successful. + if (!AreMimeTypesRegisteredWindows()) + { + return true; + } + Logger.Debug?.Print(LogClass.Application, $"Removing type association {ext}"); + Registry.CurrentUser.DeleteSubKeyTree(keyString); + Logger.Debug?.Print(LogClass.Application, $"Removed type association {ext}"); + } + else + { + using var key = Registry.CurrentUser.CreateSubKey(keyString); + + if (key is null) + { + return false; + } + + Logger.Debug?.Print(LogClass.Application, $"Adding type association {ext}"); + using var openCmd = key.CreateSubKey(@"shell\open\command"); + openCmd.SetValue("", $"\"{Environment.ProcessPath}\" \"%1\""); + Logger.Debug?.Print(LogClass.Application, $"Added type association {ext}"); + + } + + return true; + } + + bool registered = false; + + foreach (string ext in _fileExtensions) + { + registered |= RegisterExtension(ext, uninstall); + } + + // Notify Explorer the file association has been changed. + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero); + + return registered; + } + + public static bool AreMimeTypesRegistered() + { + if (OperatingSystem.IsLinux()) + { + return AreMimeTypesRegisteredLinux(); + } + + if (OperatingSystem.IsWindows()) + { + return AreMimeTypesRegisteredWindows(); + } + + // TODO: Add macOS support. + + return false; + } + + public static bool Install() + { + if (OperatingSystem.IsLinux()) + { + return InstallLinuxMimeTypes(); + } + + if (OperatingSystem.IsWindows()) + { + return InstallWindowsMimeTypes(); + } + + // TODO: Add macOS support. + + return false; + } + + public static bool Uninstall() + { + if (OperatingSystem.IsLinux()) + { + return InstallLinuxMimeTypes(true); + } + + if (OperatingSystem.IsWindows()) + { + return InstallWindowsMimeTypes(true); + } + + // TODO: Add macOS support. + + return false; + } + } +} diff --git a/src/Ryujinx.UI.Common/Helper/LinuxHelper.cs b/src/Ryujinx.UI.Common/Helper/LinuxHelper.cs new file mode 100644 index 00000000..b5779379 --- /dev/null +++ b/src/Ryujinx.UI.Common/Helper/LinuxHelper.cs @@ -0,0 +1,62 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.Versioning; + +namespace Ryujinx.UI.Common.Helper +{ + [SupportedOSPlatform("linux")] + public static class LinuxHelper + { + // NOTE: This value was determined by manual tests and might need to be increased again. + public const int RecommendedVmMaxMapCount = 524288; + public const string VmMaxMapCountPath = "/proc/sys/vm/max_map_count"; + public const string SysCtlConfigPath = "/etc/sysctl.d/99-Ryujinx.conf"; + public static int VmMaxMapCount => int.Parse(File.ReadAllText(VmMaxMapCountPath)); + public static string PkExecPath { get; } = GetBinaryPath("pkexec"); + + private static string GetBinaryPath(string binary) + { + string pathVar = Environment.GetEnvironmentVariable("PATH"); + + if (pathVar is null || string.IsNullOrEmpty(binary)) + { + return null; + } + + foreach (var searchPath in pathVar.Split(":", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)) + { + string binaryPath = Path.Combine(searchPath, binary); + + if (File.Exists(binaryPath)) + { + return binaryPath; + } + } + + return null; + } + + public static int RunPkExec(string command) + { + if (PkExecPath == null) + { + return 1; + } + + using Process process = new() + { + StartInfo = + { + FileName = PkExecPath, + ArgumentList = { "sh", "-c", command }, + }, + }; + + process.Start(); + process.WaitForExit(); + + return process.ExitCode; + } + } +} diff --git a/src/Ryujinx.UI.Common/Helper/ObjectiveC.cs b/src/Ryujinx.UI.Common/Helper/ObjectiveC.cs new file mode 100644 index 00000000..6aba377a --- /dev/null +++ b/src/Ryujinx.UI.Common/Helper/ObjectiveC.cs @@ -0,0 +1,160 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.UI.Common.Helper +{ + [SupportedOSPlatform("macos")] + public static partial class ObjectiveC + { + private const string ObjCRuntime = "/usr/lib/libobjc.A.dylib"; + + [LibraryImport(ObjCRuntime, StringMarshalling = StringMarshalling.Utf8)] + private static partial IntPtr sel_getUid(string name); + + [LibraryImport(ObjCRuntime, StringMarshalling = StringMarshalling.Utf8)] + private static partial IntPtr objc_getClass(string name); + + [LibraryImport(ObjCRuntime)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector); + + [LibraryImport(ObjCRuntime)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value); + + [LibraryImport(ObjCRuntime)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value); + + [LibraryImport(ObjCRuntime)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector, NSRect point); + + [LibraryImport(ObjCRuntime)] + private static partial void objc_msgSend(IntPtr receiver, Selector selector, double value); + + [LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")] + private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector); + + [LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")] + private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector, IntPtr param); + + [LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend", StringMarshalling = StringMarshalling.Utf8)] + private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector, string param); + + [LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool bool_objc_msgSend(IntPtr receiver, Selector selector, IntPtr param); + + public readonly struct Object + { + public readonly IntPtr ObjPtr; + + private Object(IntPtr pointer) + { + ObjPtr = pointer; + } + + public Object(string name) + { + ObjPtr = objc_getClass(name); + } + + public void SendMessage(Selector selector) + { + objc_msgSend(ObjPtr, selector); + } + + public void SendMessage(Selector selector, byte value) + { + objc_msgSend(ObjPtr, selector, value); + } + + public void SendMessage(Selector selector, Object obj) + { + objc_msgSend(ObjPtr, selector, obj.ObjPtr); + } + + public void SendMessage(Selector selector, NSRect point) + { + objc_msgSend(ObjPtr, selector, point); + } + + public void SendMessage(Selector selector, double value) + { + objc_msgSend(ObjPtr, selector, value); + } + + public Object GetFromMessage(Selector selector) + { + return new Object(IntPtr_objc_msgSend(ObjPtr, selector)); + } + + public Object GetFromMessage(Selector selector, Object obj) + { + return new Object(IntPtr_objc_msgSend(ObjPtr, selector, obj.ObjPtr)); + } + + public Object GetFromMessage(Selector selector, NSString nsString) + { + return new Object(IntPtr_objc_msgSend(ObjPtr, selector, nsString.StrPtr)); + } + + public Object GetFromMessage(Selector selector, string param) + { + return new Object(IntPtr_objc_msgSend(ObjPtr, selector, param)); + } + + public bool GetBoolFromMessage(Selector selector, Object obj) + { + return bool_objc_msgSend(ObjPtr, selector, obj.ObjPtr); + } + } + + public readonly struct Selector + { + public readonly IntPtr SelPtr; + + private Selector(string name) + { + SelPtr = sel_getUid(name); + } + + public static implicit operator Selector(string value) => new(value); + } + + public readonly struct NSString + { + public readonly IntPtr StrPtr; + + public NSString(string aString) + { + IntPtr nsString = objc_getClass("NSString"); + StrPtr = IntPtr_objc_msgSend(nsString, "stringWithUTF8String:", aString); + } + + public static implicit operator IntPtr(NSString nsString) => nsString.StrPtr; + } + + public readonly struct NSPoint + { + public readonly double X; + public readonly double Y; + + public NSPoint(double x, double y) + { + X = x; + Y = y; + } + } + + public readonly struct NSRect + { + public readonly NSPoint Pos; + public readonly NSPoint Size; + + public NSRect(double x, double y, double width, double height) + { + Pos = new NSPoint(x, y); + Size = new NSPoint(width, height); + } + } + } +} diff --git a/src/Ryujinx.UI.Common/Helper/OpenHelper.cs b/src/Ryujinx.UI.Common/Helper/OpenHelper.cs new file mode 100644 index 00000000..af6170af --- /dev/null +++ b/src/Ryujinx.UI.Common/Helper/OpenHelper.cs @@ -0,0 +1,112 @@ +using Ryujinx.Common.Logging; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.UI.Common.Helper +{ + public static partial class OpenHelper + { + [LibraryImport("shell32.dll", SetLastError = true)] + private static partial int SHOpenFolderAndSelectItems(IntPtr pidlFolder, uint cidl, IntPtr apidl, uint dwFlags); + + [LibraryImport("shell32.dll", SetLastError = true)] + private static partial void ILFree(IntPtr pidlList); + + [LibraryImport("shell32.dll", SetLastError = true)] + private static partial IntPtr ILCreateFromPathW([MarshalAs(UnmanagedType.LPWStr)] string pszPath); + + public static void OpenFolder(string path) + { + if (Directory.Exists(path)) + { + Process.Start(new ProcessStartInfo + { + FileName = path, + UseShellExecute = true, + Verb = "open", + }); + } + else + { + Logger.Notice.Print(LogClass.Application, $"Directory \"{path}\" doesn't exist!"); + } + } + + public static void LocateFile(string path) + { + if (File.Exists(path)) + { + if (OperatingSystem.IsWindows()) + { + IntPtr pidlList = ILCreateFromPathW(path); + if (pidlList != IntPtr.Zero) + { + try + { + Marshal.ThrowExceptionForHR(SHOpenFolderAndSelectItems(pidlList, 0, IntPtr.Zero, 0)); + } + finally + { + ILFree(pidlList); + } + } + } + else if (OperatingSystem.IsMacOS()) + { + ObjectiveC.NSString nsStringPath = new(path); + ObjectiveC.Object nsUrl = new("NSURL"); + var urlPtr = nsUrl.GetFromMessage("fileURLWithPath:", nsStringPath); + + ObjectiveC.Object nsArray = new("NSArray"); + ObjectiveC.Object urlArray = nsArray.GetFromMessage("arrayWithObject:", urlPtr); + + ObjectiveC.Object nsWorkspace = new("NSWorkspace"); + ObjectiveC.Object sharedWorkspace = nsWorkspace.GetFromMessage("sharedWorkspace"); + + sharedWorkspace.SendMessage("activateFileViewerSelectingURLs:", urlArray); + } + else if (OperatingSystem.IsLinux()) + { + Process.Start("dbus-send", $"--session --print-reply --dest=org.freedesktop.FileManager1 --type=method_call /org/freedesktop/FileManager1 org.freedesktop.FileManager1.ShowItems array:string:\"file://{path}\" string:\"\""); + } + else + { + OpenFolder(Path.GetDirectoryName(path)); + } + } + else + { + Logger.Notice.Print(LogClass.Application, $"File \"{path}\" doesn't exist!"); + } + } + + public static void OpenUrl(string url) + { + if (OperatingSystem.IsWindows()) + { + Process.Start(new ProcessStartInfo("cmd", $"/c start {url.Replace("&", "^&")}")); + } + else if (OperatingSystem.IsLinux()) + { + Process.Start("xdg-open", url); + } + else if (OperatingSystem.IsMacOS()) + { + ObjectiveC.NSString nsStringPath = new(url); + ObjectiveC.Object nsUrl = new("NSURL"); + var urlPtr = nsUrl.GetFromMessage("URLWithString:", nsStringPath); + + ObjectiveC.Object nsWorkspace = new("NSWorkspace"); + ObjectiveC.Object sharedWorkspace = nsWorkspace.GetFromMessage("sharedWorkspace"); + + sharedWorkspace.GetBoolFromMessage("openURL:", urlPtr); + } + else + { + Logger.Notice.Print(LogClass.Application, $"Cannot open url \"{url}\" on this platform!"); + } + } + } +} diff --git a/src/Ryujinx.UI.Common/Helper/SetupValidator.cs b/src/Ryujinx.UI.Common/Helper/SetupValidator.cs new file mode 100644 index 00000000..a954be26 --- /dev/null +++ b/src/Ryujinx.UI.Common/Helper/SetupValidator.cs @@ -0,0 +1,114 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.FileSystem; +using System; +using System.IO; + +namespace Ryujinx.UI.Common.Helper +{ + /// + /// Ensure installation validity + /// + public static class SetupValidator + { + public static bool IsFirmwareValid(ContentManager contentManager, out UserError error) + { + bool hasFirmware = contentManager.GetCurrentFirmwareVersion() != null; + + if (hasFirmware) + { + error = UserError.Success; + + return true; + } + + error = UserError.NoFirmware; + + return false; + } + + public static bool CanFixStartApplication(ContentManager contentManager, string baseApplicationPath, UserError error, out SystemVersion firmwareVersion) + { + try + { + firmwareVersion = contentManager.VerifyFirmwarePackage(baseApplicationPath); + } + catch (Exception) + { + firmwareVersion = null; + } + + return error == UserError.NoFirmware && Path.GetExtension(baseApplicationPath).ToLowerInvariant() == ".xci" && firmwareVersion != null; + } + + public static bool TryFixStartApplication(ContentManager contentManager, string baseApplicationPath, UserError error, out UserError outError) + { + if (error == UserError.NoFirmware) + { + string baseApplicationExtension = Path.GetExtension(baseApplicationPath).ToLowerInvariant(); + + // If the target app to start is a XCI, try to install firmware from it + if (baseApplicationExtension == ".xci") + { + SystemVersion firmwareVersion; + + try + { + firmwareVersion = contentManager.VerifyFirmwarePackage(baseApplicationPath); + } + catch (Exception) + { + firmwareVersion = null; + } + + // The XCI is a valid firmware package, try to install the firmware from it! + if (firmwareVersion != null) + { + try + { + Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}"); + + contentManager.InstallFirmware(baseApplicationPath); + + Logger.Info?.Print(LogClass.Application, $"System version {firmwareVersion.VersionString} successfully installed."); + + outError = UserError.Success; + + return true; + } + catch (Exception) { } + } + + outError = error; + + return false; + } + } + + outError = error; + + return false; + } + + public static bool CanStartApplication(ContentManager contentManager, string baseApplicationPath, out UserError error) + { + if (Directory.Exists(baseApplicationPath) || File.Exists(baseApplicationPath)) + { + string baseApplicationExtension = Path.GetExtension(baseApplicationPath).ToLowerInvariant(); + + // NOTE: We don't force homebrew developers to install a system firmware. + if (baseApplicationExtension == ".nro" || baseApplicationExtension == ".nso") + { + error = UserError.Success; + + return true; + } + + return IsFirmwareValid(contentManager, out error); + } + + error = UserError.ApplicationNotFound; + + return false; + } + } +} diff --git a/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs b/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs new file mode 100644 index 00000000..1849f40c --- /dev/null +++ b/src/Ryujinx.UI.Common/Helper/ShortcutHelper.cs @@ -0,0 +1,172 @@ +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using ShellLink; +using SkiaSharp; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Versioning; + +namespace Ryujinx.UI.Common.Helper +{ + public static class ShortcutHelper + { + [SupportedOSPlatform("windows")] + private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath) + { + string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe"); + iconPath += ".ico"; + + MemoryStream iconDataStream = new(iconData); + using var image = SKBitmap.Decode(iconDataStream); + image.Resize(new SKImageInfo(128, 128), SKFilterQuality.High); + SaveBitmapAsIcon(image, iconPath); + + var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId), iconPath, 0); + shortcut.StringData.NameString = cleanedAppName; + shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk")); + } + + [SupportedOSPlatform("linux")] + private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName) + { + string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh"); + var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.desktop"); + iconPath += ".png"; + + var image = SKBitmap.Decode(iconData); + using var data = image.Encode(SKEncodedImageFormat.Png, 100); + using var file = File.OpenWrite(iconPath); + data.SaveTo(file); + + using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop")); + outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId)}"); + } + + [SupportedOSPlatform("macos")] + private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName) + { + string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"); + var plistFile = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-template.plist"); + var shortcutScript = EmbeddedResources.ReadAllText("Ryujinx.UI.Common/shortcut-launch-script.sh"); + // Macos .App folder + string contentFolderPath = Path.Combine("/Applications", cleanedAppName + ".app", "Contents"); + string scriptFolderPath = Path.Combine(contentFolderPath, "MacOS"); + + if (!Directory.Exists(scriptFolderPath)) + { + Directory.CreateDirectory(scriptFolderPath); + } + + // Runner script + const string ScriptName = "runner.sh"; + string scriptPath = Path.Combine(scriptFolderPath, ScriptName); + using StreamWriter scriptFile = new(scriptPath); + + scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId)); + + // Set execute permission + FileInfo fileInfo = new(scriptPath); + fileInfo.UnixFileMode |= UnixFileMode.UserExecute; + + // img + string resourceFolderPath = Path.Combine(contentFolderPath, "Resources"); + if (!Directory.Exists(resourceFolderPath)) + { + Directory.CreateDirectory(resourceFolderPath); + } + + const string IconName = "icon.png"; + var image = SKBitmap.Decode(iconData); + using var data = image.Encode(SKEncodedImageFormat.Png, 100); + using var file = File.OpenWrite(Path.Combine(resourceFolderPath, IconName)); + data.SaveTo(file); + + // plist file + using StreamWriter outputFile = new(Path.Combine(contentFolderPath, "Info.plist")); + outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName); + } + + public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData) + { + string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); + string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars())); + + if (OperatingSystem.IsWindows()) + { + string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app"); + + CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath); + + return; + } + + if (OperatingSystem.IsLinux()) + { + string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx"); + + Directory.CreateDirectory(iconPath); + CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName); + + return; + } + + if (OperatingSystem.IsMacOS()) + { + CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName); + + return; + } + + throw new NotImplementedException("Shortcut support has not been implemented yet for this OS."); + } + + private static string GetArgsString(string appFilePath, string applicationId) + { + // args are first defined as a list, for easier adjustments in the future + var argsList = new List(); + + if (!string.IsNullOrEmpty(CommandLineState.BaseDirPathArg)) + { + argsList.Add("--root-data-dir"); + argsList.Add($"\"{CommandLineState.BaseDirPathArg}\""); + } + + if (appFilePath.ToLower().EndsWith(".xci")) + { + argsList.Add("--application-id"); + argsList.Add($"\"{applicationId}\""); + } + + argsList.Add($"\"{appFilePath}\""); + + return String.Join(" ", argsList); + } + + /// + /// Creates a Icon (.ico) file using the source bitmap image at the specified file path. + /// + /// The source bitmap image that will be saved as an .ico file + /// The location that the new .ico file will be saved too (Make sure to include '.ico' in the path). + [SupportedOSPlatform("windows")] + private static void SaveBitmapAsIcon(SKBitmap source, string filePath) + { + // Code Modified From https://stackoverflow.com/a/11448060/368354 by Benlitz + byte[] header = { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 32, 0, 0, 0, 0, 0, 22, 0, 0, 0 }; + using FileStream fs = new(filePath, FileMode.Create); + + fs.Write(header); + // Writing actual data + using var data = source.Encode(SKEncodedImageFormat.Png, 100); + data.SaveTo(fs); + // Getting data length (file length minus header) + long dataLength = fs.Length - header.Length; + // Write it in the correct place + fs.Seek(14, SeekOrigin.Begin); + fs.WriteByte((byte)dataLength); + fs.WriteByte((byte)(dataLength >> 8)); + fs.WriteByte((byte)(dataLength >> 16)); + fs.WriteByte((byte)(dataLength >> 24)); + } + } +} diff --git a/src/Ryujinx.UI.Common/Helper/TitleHelper.cs b/src/Ryujinx.UI.Common/Helper/TitleHelper.cs new file mode 100644 index 00000000..8b47ac38 --- /dev/null +++ b/src/Ryujinx.UI.Common/Helper/TitleHelper.cs @@ -0,0 +1,30 @@ +using Ryujinx.HLE.Loaders.Processes; +using System; + +namespace Ryujinx.UI.Common.Helper +{ + public static class TitleHelper + { + public static string ActiveApplicationTitle(ProcessResult activeProcess, string applicationVersion, string pauseString = "") + { + if (activeProcess == null) + { + return String.Empty; + } + + 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)"; + + string appTitle = $"Ryujinx {applicationVersion} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}"; + + if (!string.IsNullOrEmpty(pauseString)) + { + appTitle += $" ({pauseString})"; + } + + return appTitle; + } + } +} diff --git a/src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs b/src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs new file mode 100644 index 00000000..8ea3e721 --- /dev/null +++ b/src/Ryujinx.UI.Common/Helper/ValueFormatUtils.cs @@ -0,0 +1,219 @@ +using System; +using System.Globalization; +using System.Linq; + +namespace Ryujinx.UI.Common.Helper +{ + public static class ValueFormatUtils + { + private static readonly string[] _fileSizeUnitStrings = + { + "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", // Base 10 units, used for formatting and parsing + "KB", "MB", "GB", "TB", "PB", "EB", // Base 2 units, used for parsing legacy values + }; + + /// + /// Used by . + /// + public enum FileSizeUnits + { + Auto = -1, + Bytes = 0, + Kibibytes = 1, + Mebibytes = 2, + Gibibytes = 3, + Tebibytes = 4, + Pebibytes = 5, + Exbibytes = 6, + Kilobytes = 7, + Megabytes = 8, + Gigabytes = 9, + Terabytes = 10, + Petabytes = 11, + Exabytes = 12, + } + + private const double SizeBase10 = 1000; + private const double SizeBase2 = 1024; + private const int UnitEBIndex = 6; + + #region Value formatters + + /// + /// Creates a human-readable string from a . + /// + /// The to be formatted. + /// A formatted string that can be displayed in the UI. + public static string FormatTimeSpan(TimeSpan? timeSpan) + { + if (!timeSpan.HasValue || timeSpan.Value.TotalSeconds < 1) + { + // Game was never played + return TimeSpan.Zero.ToString("c", CultureInfo.InvariantCulture); + } + + if (timeSpan.Value.TotalDays < 1) + { + // Game was played for less than a day + return timeSpan.Value.ToString("c", CultureInfo.InvariantCulture); + } + + // Game was played for more than a day + TimeSpan onlyTime = timeSpan.Value.Subtract(TimeSpan.FromDays(timeSpan.Value.Days)); + string onlyTimeString = onlyTime.ToString("c", CultureInfo.InvariantCulture); + + return $"{timeSpan.Value.Days}d, {onlyTimeString}"; + } + + /// + /// Creates a human-readable string from a . + /// + /// The to be formatted. This is expected to be UTC-based. + /// The that's used in formatting. Defaults to . + /// A formatted string that can be displayed in the UI. + public static string FormatDateTime(DateTime? utcDateTime, CultureInfo culture = null) + { + culture ??= CultureInfo.CurrentCulture; + + if (!utcDateTime.HasValue) + { + // In the Avalonia UI, this is turned into a localized version of "Never" by LocalizedNeverConverter. + return "Never"; + } + + return utcDateTime.Value.ToLocalTime().ToString(culture); + } + + /// + /// Creates a human-readable file size string. + /// + /// The file size in bytes. + /// Formats the passed size value as this unit, bypassing the automatic unit choice. + /// A human-readable file size string. + public static string FormatFileSize(long size, FileSizeUnits forceUnit = FileSizeUnits.Auto) + { + if (size <= 0) + { + return $"0 {_fileSizeUnitStrings[0]}"; + } + + int unitIndex = (int)forceUnit; + if (forceUnit == FileSizeUnits.Auto) + { + unitIndex = Convert.ToInt32(Math.Floor(Math.Log(size, SizeBase10))); + + // Apply an upper bound so that exabytes are the biggest unit used when formatting. + if (unitIndex > UnitEBIndex) + { + unitIndex = UnitEBIndex; + } + } + + double sizeRounded; + + if (unitIndex > UnitEBIndex) + { + sizeRounded = Math.Round(size / Math.Pow(SizeBase10, unitIndex - UnitEBIndex), 1); + } + else + { + sizeRounded = Math.Round(size / Math.Pow(SizeBase2, unitIndex), 1); + } + + string sizeFormatted = sizeRounded.ToString(CultureInfo.InvariantCulture); + + return $"{sizeFormatted} {_fileSizeUnitStrings[unitIndex]}"; + } + + #endregion + + #region Value parsers + + /// + /// Parses a string generated by and returns the original . + /// + /// A string representing a . + /// A object. If the input string couldn't been parsed, is returned. + public static TimeSpan ParseTimeSpan(string timeSpanString) + { + TimeSpan returnTimeSpan = TimeSpan.Zero; + + // An input string can either look like "01:23:45" or "1d, 01:23:45" if the timespan represents a duration of more than a day. + // Here, we split the input string to check if it's the former or the latter. + var valueSplit = timeSpanString.Split(", "); + if (valueSplit.Length > 1) + { + var dayPart = valueSplit[0].Split("d")[0]; + if (int.TryParse(dayPart, out int days)) + { + returnTimeSpan = returnTimeSpan.Add(TimeSpan.FromDays(days)); + } + } + + if (TimeSpan.TryParse(valueSplit.Last(), out TimeSpan parsedTimeSpan)) + { + returnTimeSpan = returnTimeSpan.Add(parsedTimeSpan); + } + + return returnTimeSpan; + } + + /// + /// Parses a string generated by and returns the original . + /// + /// The string representing a . + /// A object. If the input string couldn't be parsed, is returned. + public static DateTime ParseDateTime(string dateTimeString) + { + if (!DateTime.TryParse(dateTimeString, CultureInfo.CurrentCulture, out DateTime parsedDateTime)) + { + // Games that were never played are supposed to appear before the oldest played games in the list, + // so returning DateTime.UnixEpoch here makes sense. + return DateTime.UnixEpoch; + } + + return parsedDateTime; + } + + /// + /// Parses a string generated by and returns a representing a number of bytes. + /// + /// A string representing a file size formatted with . + /// A representing a number of bytes. + public static long ParseFileSize(string sizeString) + { + // Enumerating over the units backwards because otherwise, sizeString.EndsWith("B") would exit the loop in the first iteration. + for (int i = _fileSizeUnitStrings.Length - 1; i >= 0; i--) + { + string unit = _fileSizeUnitStrings[i]; + if (!sizeString.EndsWith(unit)) + { + continue; + } + + string numberString = sizeString.Split(" ")[0]; + if (!double.TryParse(numberString, CultureInfo.InvariantCulture, out double number)) + { + break; + } + + double sizeBase = SizeBase2; + + // If the unit index is one that points to a base 10 unit in the FileSizeUnitStrings array, subtract 6 to arrive at a usable power value. + if (i > UnitEBIndex) + { + i -= UnitEBIndex; + sizeBase = SizeBase10; + } + + number *= Math.Pow(sizeBase, i); + + return Convert.ToInt64(number); + } + + return 0; + } + + #endregion + } +} diff --git a/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApi.cs b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApi.cs new file mode 100644 index 00000000..7989f0f1 --- /dev/null +++ b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApi.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Ryujinx.UI.Common.Models.Amiibo +{ + public struct AmiiboApi : IEquatable + { + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("head")] + public string Head { get; set; } + [JsonPropertyName("tail")] + public string Tail { get; set; } + [JsonPropertyName("image")] + public string Image { get; set; } + [JsonPropertyName("amiiboSeries")] + public string AmiiboSeries { get; set; } + [JsonPropertyName("character")] + public string Character { get; set; } + [JsonPropertyName("gameSeries")] + public string GameSeries { get; set; } + [JsonPropertyName("type")] + public string Type { get; set; } + + [JsonPropertyName("release")] + public Dictionary Release { get; set; } + + [JsonPropertyName("gamesSwitch")] + public List GamesSwitch { get; set; } + + public readonly override string ToString() + { + return Name; + } + + public readonly string GetId() + { + return Head + Tail; + } + + public readonly bool Equals(AmiiboApi other) + { + return Head + Tail == other.Head + other.Tail; + } + + public readonly override bool Equals(object obj) + { + return obj is AmiiboApi other && Equals(other); + } + + public readonly override int GetHashCode() + { + return HashCode.Combine(Head, Tail); + } + + public static bool operator ==(AmiiboApi left, AmiiboApi right) + { + return left.Equals(right); + } + + public static bool operator !=(AmiiboApi left, AmiiboApi right) + { + return !(left == right); + } + } +} diff --git a/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs new file mode 100644 index 00000000..40e635bf --- /dev/null +++ b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Ryujinx.UI.Common.Models.Amiibo +{ + public class AmiiboApiGamesSwitch + { + [JsonPropertyName("amiiboUsage")] + public List AmiiboUsage { get; set; } + [JsonPropertyName("gameID")] + public List GameId { get; set; } + [JsonPropertyName("gameName")] + public string GameName { get; set; } + } +} diff --git a/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiUsage.cs b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiUsage.cs new file mode 100644 index 00000000..4f8d292b --- /dev/null +++ b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboApiUsage.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.UI.Common.Models.Amiibo +{ + public class AmiiboApiUsage + { + [JsonPropertyName("Usage")] + public string Usage { get; set; } + [JsonPropertyName("write")] + public bool Write { get; set; } + } +} diff --git a/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJson.cs b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJson.cs new file mode 100644 index 00000000..15083f50 --- /dev/null +++ b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJson.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Ryujinx.UI.Common.Models.Amiibo +{ + public struct AmiiboJson + { + [JsonPropertyName("amiibo")] + public List Amiibo { get; set; } + [JsonPropertyName("lastUpdated")] + public DateTime LastUpdated { get; set; } + } +} diff --git a/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs new file mode 100644 index 00000000..bc3f1303 --- /dev/null +++ b/src/Ryujinx.UI.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.UI.Common.Models.Amiibo +{ + [JsonSerializable(typeof(AmiiboJson))] + public partial class AmiiboJsonSerializerContext : JsonSerializerContext + { + } +} diff --git a/src/Ryujinx.UI.Common/Models/Github/GithubReleaseAssetJsonResponse.cs b/src/Ryujinx.UI.Common/Models/Github/GithubReleaseAssetJsonResponse.cs new file mode 100644 index 00000000..8f528dc0 --- /dev/null +++ b/src/Ryujinx.UI.Common/Models/Github/GithubReleaseAssetJsonResponse.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.UI.Common.Models.Github +{ + public class GithubReleaseAssetJsonResponse + { + public string Name { get; set; } + public string State { get; set; } + public string BrowserDownloadUrl { get; set; } + } +} diff --git a/src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonResponse.cs b/src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonResponse.cs new file mode 100644 index 00000000..0250e109 --- /dev/null +++ b/src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonResponse.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Ryujinx.UI.Common.Models.Github +{ + public class GithubReleasesJsonResponse + { + public string Name { get; set; } + public List Assets { get; set; } + } +} diff --git a/src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonSerializerContext.cs b/src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonSerializerContext.cs new file mode 100644 index 00000000..71864257 --- /dev/null +++ b/src/Ryujinx.UI.Common/Models/Github/GithubReleasesJsonSerializerContext.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.UI.Common.Models.Github +{ + [JsonSerializable(typeof(GithubReleasesJsonResponse), GenerationMode = JsonSourceGenerationMode.Metadata)] + public partial class GithubReleasesJsonSerializerContext : JsonSerializerContext + { + } +} diff --git a/src/Ryujinx.UI.Common/Resources/Controller_JoyConLeft.svg b/src/Ryujinx.UI.Common/Resources/Controller_JoyConLeft.svg new file mode 100644 index 00000000..03585e65 --- /dev/null +++ b/src/Ryujinx.UI.Common/Resources/Controller_JoyConLeft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Ryujinx.UI.Common/Resources/Controller_JoyConPair.svg b/src/Ryujinx.UI.Common/Resources/Controller_JoyConPair.svg new file mode 100644 index 00000000..c073c9c0 --- /dev/null +++ b/src/Ryujinx.UI.Common/Resources/Controller_JoyConPair.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Ryujinx.UI.Common/Resources/Controller_JoyConRight.svg b/src/Ryujinx.UI.Common/Resources/Controller_JoyConRight.svg new file mode 100644 index 00000000..f4f12514 --- /dev/null +++ b/src/Ryujinx.UI.Common/Resources/Controller_JoyConRight.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Ryujinx.UI.Common/Resources/Controller_ProCon.svg b/src/Ryujinx.UI.Common/Resources/Controller_ProCon.svg new file mode 100644 index 00000000..f5380f3a --- /dev/null +++ b/src/Ryujinx.UI.Common/Resources/Controller_ProCon.svg @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + + X + + + + Y + + + + B + + + + + + + + + + ZL + + + + + ZR + + + + R + + + + + L + + + diff --git a/src/Ryujinx.UI.Common/Resources/Icon_Blank.png b/src/Ryujinx.UI.Common/Resources/Icon_Blank.png new file mode 100644 index 0000000000000000000000000000000000000000..d2bba8a92f5dd5254d10460b9f6f0af468f8a1af GIT binary patch literal 16179 zcmV-3Kg__1P) z0w0%qm58=MTcIFo(`!+GL2ZN9K~NMyw02$8rsQ^R21?`^&YW+4Gv_dK2CU( zr-f(N^Qy_a!W~K~5)(cb_GofJ;d@h$R=8wxvcg{`YvIr&Se?SIWDQ4aSV?IaVRw9vVq1L zNmAtTh+2^9afu1h<1*50nEn6TS3NN@T&eExL*t8Ky!C>82g*gm_&hd@@(I|U;VN6r z&mF<&yXeKL=01eBEx5R@YSBHo+=i|vHKIj+B%8Q3k$~4TXkCK#TWDUX^z3(^(?HD+hehn%9D00009a7bBm000&x000&x0ZCFM@BjckSxH1eRCwC# zoCBt7S*?V~xLlkzz--QSU|6a+Jsyq)_22dCPxzcu? z2Ny#q48RG#I%CF+`sJ5j{^^%qdg+U6*RGwmZr!@kYu2n8v1QAa-8XOE+_iP<)>N%l zQ>9W#+qZAmb0e?SYH7!g9f{{{+qS7rWop|<0A&KT6R53#XP-yy>EpoMUPtcpGq&2Q zUUl_)oqj5(o^tladgGkF56XB}IeYMR(Vo7v<5}-beSD5a8Dslec+Ud%qnvpdSHO7) zY(8(x9JJrm-`~HgtE=nH5hF&t*4^Fx^!V}P&-vKLK6cTQPd>R-(>p^b44}OUzowKA z_8X5p^2oQIe){S6Y}l}2cOi`s10*AN0_*7LfB-=xM=B5!gl&p&u^shJM3Iqz*pxXD zN22vQC&mbIOz#_sM&N6SeU(Fy29cRXX2tJ+x<`IHlXK$s^YAg8S@8ZNBk{3S=XquX ztFnN&ao)UnCr_U~{Zw^Pf)Nx3(0&rXo{9d`x88c|Utf6Ph514RB`0JNppuXvYXMHY zAXbpr|D50;s>moDA(hk42`Cap)D$A(N2Ws=5SdOK$AISv5fhA z{ftXHAZitn5h}&K`2L-F=~?HXXBdRuwf3SkY0{(zSFBj^#{~-(T&$}0&B@ao+uDpTx zot>Q^A;jba2m!}A9HA+9iN;tE3k2eXPuoS zZ`;VTO`Y&BK)B9ad`;}zeH8%hok=i{Gs9?l0gS6TAZ7t)MjwIN@qPye2K4@viy^Rf zHCy}bZ-4v8^XJb$OItUypzWbH$cVr5#v5<^{UL`OvR6Lft_*4Y`t|AUx8F{0yzxd_ zwQ7};2v!H%$RS`5CEBDQG|45@rcIkv=7<78(T;L#P3G`^diHgAzt}f2A#dYrW{s}e zBGqDlCz9A7$?f$p1b5h>0XXryxf4})f!p9bI}>1D7=hk*Tq|S8wX;|D;F=U;vsNXI zO%l$yb|h`F^V(~#{n8$L?6KqMqmRCMFqs0?!9@J0o_gwmhaP(9pR?4j>fqme^Ud_) zi!Y{^Uw#<|AgK=n0LhUij-Z$Z5n-d0acLcG(`X8SQ1n4#P`x|KqNI+5&ODK+@a%1U zZso2z)4!=*51}w_WE3zsLDUe?jFkLcJ5jsFZ;(cJ+AQbvaoYO|6f8EP@o%n1#R0wMqr-Rlg&MGd2&b~+Bu;}rx>xDXS4qBlKy%A;33diZIhG5y(XerIy7o%kHoxi;laLDycQRHxkc7Oz@; z9ln1*3*I>Z(}~&{w?r~2^*fuHAIO^K{YM^oT%$qCdYg5c;6k@9DNr~ zOc5C0rLq$Yx$n!zq29HFcw^!OPogd0@7GNx*5+E2YewX|3MI$7+_9U`mA_{(4ZazVzwH`@7Tpw`CHUm~=7@#4jYJoeaQ3ncktHgVI5*WCoG3(Ln{HkhZx%srC{`9D7izE{l|NJ z_pCbsQRkY0*LhSWgypJ!Y!}xYwYa!H0eOfmpezPD#ox`Cgli0PEO%v`o0d(_GuxQ~ zRN7c`>AdKoi$1pZ-g}=b$+`RPy9?Qn{EcUoi;JV4tDI{sfgx&CuU#5PDd+9{n(QB4 zwc$U|oiny`bP_u9%6TayW36oo?0@+1;R>vAEeGiz?R;m_uIf93Lluu&5rm70<_tbw zJK_^^4a2pAra?7%*HdMQnsrZ9lkOaHD5F1SFBzY}#JB7Qu#$^(M^Lt+L&9HN>kK!QW0sJ0-k*Q2gR z2Hegxo_gvjwNqr8q+MGw0PRobA#+T*2zK`D*>SJz-?f3ido>!RePrwc2%2)7 zuF)yT*aSP#J~A)|`sz%S!~it*uYdjP?|k~xpFSry+0<@YK)p>p{~h<=fB#oqG7u!9 zZ`AE^WpY1F#`lc!9>-@uWCsUEZ4M6qyIc;i1CzA-(d!%c2{7NpqP*tt>2NVWS%c^8gzWVzxcBBWGc-Sk?9IX5U(e8BVLKv-^4vd3DgP{m~ zY;yb#tWD377L3;{ASGVQjU78yJ`IhfIk|>vI}L#FU=g2wM>8r$HhM4>nooo9L@xUDKvb6SI1u0Wv?IsE)R z-}~P8E|V5eOn}y^|MeW?^C=Dw>BvC^@4geHBm>@C9XbTM%R#=@OoI{fmzil-1WY<< zD$)uF=NI=Ga!ER;f_IlZ!%T5sV?OfpmoIGnbYnoSFXGyOOvJI*c?dw zrOg-$bsj#K!JY(I0c0>(i(Le(=3uOhLuYL7_IK@rU6vj@vpw}%N}9h zlH+xE&a6Y>^Ui-q>K7=06Owy=@P}I|CNoxA?+bjF6&X-oRs z2t+Y~2@@uGoI(7`6GwEnaI^KqUl>3I>Cb_XlT?QWK!6bNvDF*XFJD=kYJHp2_SNgf z?lo$Gp$HNn+18&jZ1?n&k>eVqf69hc)FSoce-tR^S>+xiR4ELg^-#ef6Jx?XOxKx$ z!zbR}khbdV*R4-GvSu)ZslR}n!u49JY}l9tU+5p^3;U)1*~#J^Hx2VJB#FmBRR=6F<_$tcTNCZ% zVMdE_jnDs#Jycy`(}8UO-Mv&hfxO?xznOii`h|}eGact8r&MnOvB$6ElL{>?wp3kU zI*#EPu8d3dLJvfTpvf)u0QC#)RYt3dWhlmFxDqXPw0tKJmp`!6=T9`NF6b+-;f~UmbeDWflF(IP1t9;dG^ZO~lRo22y!v;5|Hg%m zkF|5*J+4388&!c6za>+C-nV`p6iMSCD!c}EvU{&vtuo-@$``UbuSgybj!>jeVs{@` zwg7&ITg4)FKL?9v_ZH$cpBW?Vuk~ytUh^8%8BxI%&B=aG;?J*0n;8D<=Zoik=Z{BJ z_{<#n^Bo%&K$$yzbhkHl5MY=5v@MX+=H#*gP^MQh#L(Z;Qdk6=Ki*k*mMQ_C>ecW& z!iPVAX4puW_nroiRjY%lV|$30Ezq^@4JllqY*3FL35B;8c+79!K;L5kc-%6igI|+I zAPkdd!1g{6Qj`he+1(d*<}55Hui;m4nKIBbf;3jwvL^_SqD91O5zn8)*l#Qd&*H`4 z+n^qJZ1w9R@YQQXY&{Gq%2X-rzxQ#IVfz>i+sn^T`1`@PW(^RQ_a6{CdC~t3LaIK! zK$uRQhV}Myr(6D6s4bCunN2Rr-%nMiWW;3ypk)`k9gt>q`7hv+JsaY!Ss~6+#^DnY z{@?)!-zJT~mCOq%{u9TDogaK$`mj;31sV_=;A8CL%$XsqK>h|@w|+2x`~)JPe_!?; z!fHV^YzPQTU=Xz3`b1kBlgiH7*`89&vq3@f<5B{$#U988AX;tZ!t(VS4BK{yhwdc`9A z4g4xbGh{drIpcEi;=+H%BoKa6$AL?n2!v+LI9MOOg_ChqC5Xy|vlWL}yMW6GC?}Nu zi=@A_1z|Qt)lm0EF?tL4?g) zE2{*U75&hFz?LtQ2iE{VIsF|K@%&GX#$lJQfS{R+(DxfI?CS~DkU>CzCwxW^gnPQQ zAT;AA!uI+rVk-f?y2Gt@Is6U!o&#Zi^On})P>mZ6)#UL|M9#XF&tIVJ-cL@KGl|dG zSO7ZDA1*I|oJgzdvZb8Z6iqRTrxKn;3xlu*2EvXIld!J`6(3EN5`J?g!@qL}5T=u- zXqJmrm_K}g&14d{IC199f+BT7{3*7tI)n@u3`Bm0?rBoPBU{#=_K=|?WE&J|kIx@$ z)F~bEw=>rOK&hIjv$IeSz4DY3y7W4<7;HU9K#=#}B~Y5O(Akq=X(n8DqjT%0x|uW=0g`8UW;kO$H)C zQhPq;34inm-y1ZA<%$e{fC=#ono-z?q)MH1|c0%?9H0)MWS}90oTt+~$yt1Mn zY4S!nAfuFi%{u5eY=q5hwiBrYW-SiO@hkWh{662R(K55#3H%D{%hzC0rc4e;C{x&< zx4wFV-{GD;8+b^x^e^ykz2I)WgnWBEde!cN0_|KaTx0ETs!#{5|7)1ACz{1&( z5l0z-Xh4Xm<-f}fNHaXS0ckl#gM*xyKYtd~e7fd6VA!+;gm>vu@Mv2f&c<^83C+%; zD$x-h#f!477?yia|46t*Sc8J_ubB6rfp+#>5K3R)-+vDs8o{?-9T2*etD&2>0EADK zO7QI4PA(T9tpM2nvq@)NIsmz0M__jEFD()h6X^R7gZcdj(S@a+I9AZPI~8ph!tXO` z!iEjtUZ*@*_&5aLy0t-s-Mt4GBOs#oM!=ROP-aUFf-9X5?pd>dh z;7?FwOa}FYu>b{kJ%0hi;4v@`nS$`UcR{E}4kLACw8O@iuR0r8()LY#AZfBbzJ@i(l2G9Uni zfxn+2V9g@X?kvV+zPC=J}}5Bb50wLp5{|V_5wReZOIlq8~U45wBh|O2eMu z%H)sk_QocfAvYlFk#ZFVkrOr&kUj)yXP0(*y6!CWa~8m~bC0ObU zA|4)yilA;ip@_V0zIz{kilN7F7^lpFTZzJO&y@pQvSfnm&3yX~%x5n0TN205bsrAX ziZyUAUL?};uRz4zyNEb_9@Yo1;Rxg6WzEc{?2Q&16G82ZH8!)#K)N0qQ6Tof0q_A3 zzi{EgFZmCHGXJo9G1eWZ)oO+E&TMXgtj3b7RM6ULlhd&6i52> z>Du8smvM8BWchJZ<+tab_spMY0{nk44=@)cz5hHDxs>!1aE}R)egU#jjRGt~321)g&|05cjO^1?y1IG@u8kePux0T;(K%`AZ2?K|}M znrc9i<4uKk42?hmu-FfPx$LxNN=f~)I&G0|1D-wr*dG&a9snOZ(mB(CU#k=^knT9hPZl;LC=CickSK=WYp>!$X&`BM{el&%*S0aGr}inGTGm*|h>$ z7HkyYuFR}CvKeU4YiptL%U^%K{rvOKZ?qK`N&flmw{G*5o`1~EtP8j+6<8-=j%VUI z;Tb5+;EG8gY5@7(F&&uS=6HwIAUq=%g$`A_I2#aEjE|jb zJhOiIa(P3^=Qx{t4rR^n;b^dyyeXm2 z`~Xio14Ti=Zl4Wk-{0R4ii5VWCt%xS>7co?(4r5Z!^>w8r+n`$2HIXMY%{2cz??7d z{^G9RnShvT<1nZ_%D3}Rs$}>BIImJAbZIB{Kpb z-|jI1IS7;p6K23v1fp@ytjcAq285^sI2;i!^4;=t&iVX4i~^*0miwIIqWUkdqBBlu z3NqJd1Zn`wxd8mr(;4`sJ=QjyNZI^NCYR0mKrn1#6d=QVg#Y;P$q0Pu?k`RU&KUrt z2IsRy91Mb*pC3HE0x=Clt|kJt*PnN*8_*0k1i4Q#w+A;RvpxWMS?>vI{SQGnBd9+eWlWg^hVH`qiuHbJo^FO z)dIY~Z&^%9!>|O(%MY^se8}dJYBIri7b8C*b~KabWlJ+YJ3U9}UL6$hle2 zZNbz2pYxr=K}`ilrJvoN!3=Z%@$oUwxK{|s8Qn6zpJlVzYy-yi9cjSk0c1-i7K26m z@y8!ou&-=8k*@teYdV`9fab_Bv;tr|3osW8YB0#Ri-E+!AbjY#(J&$`%h31G37FSe zlKCP>pXdZUHW9GKbPj^yz{iu^io@c_( z+;=-Voq&s^lMi!%MIuRKj$ELts=wGAl=mt-PG#*0Q2_Y z3+sq{y~$t#WSMz7pSRy7H2@Z5)G$8uw#m9}c+<~Ccz?JCy8p~U1PaF>W~fns{lOq9 znL9W8B{rF82HmBU6I=d}(f=NP7LdxGax7tOmr>p9LA2 z1vt;XTLvuJ{`r29{F8YAV{*<}AHXK`0hom$8DmTWasxg;KXC%KbUwTm&<8NrO*)#p zFvv{6ebNqNPCL8*2!}ae_5p}He+z@25}Yl<22FrPQ1{zDLjYt1fCUq|IwLUOFE$&p z0NI50Pyk?ybqX+Dg63!`e%byP=`nMdotOnTpc8P&TI^gTuIGS@O+iJIfEs{o4l>X1 z1?U7^hL4;PP1zVEOM^R>dpIjlbON53jC}Nc&vDmeAZr1z`RNIeX_?q+z;ltN-;I9G z#)0`sSKzJ;Ttpwh5cU|?Nad>zKv5g$d&~g(P%DS{)B?Uz>8>;c6~lmRGK81eWcvNHA;>0#UNRMvh2c4L9Y8-9IorLv z>n~}*<9Hr3V9sa>d*}n055Ft45Co`ft^T)3Akw>j3Inb)tlTN-Rs(L6Et-b%{IiH; z3vyiA@kOD~ML`3@Xzp`1s{wR1;9Sqqi9vJzZoanI5!5@XR@;>8J5XK zO&2=wiFR)0_W@A2UtuuX42nG8A zY=N|8=g)lXbi{NxN900HK$?%;j*bmy&YA#sK|VmI0wbMU6X=B&Y1#zHUB3MRB%olD&;PQPsQ_R>kifaqrx~FAFHcRI&g3@8U?@%WpwMj#^3Tm`-_7?+C0GXpYP$^FQEY711AR{)iSQ< zIb0M1Viw?$1DR*+{^kMP)uuVsezbuu=n#2kBk1?0OU-%j+DW*K#XuZ<{E+4Va4k|C zCI38{JHb79CI|?NfwrB0T@l!x+pGuZ5@C@Ob-ybC$@)v5Qun0XEC*HqFbotm47zla zGl$5PuE126W}QHK|GD19YCw`O+BQQnpL%mSM82pD%ve904w0xD#sbi&kq*I!ZJT)}awZH=^N9iw5}fnL$46?K+r>cU0RU4gBmOhlFRQHj%EB!bB*6Alg{?h1jr_!d0&Yg zr`}$X!z7@*ryiga%s3X&37ADdLoHS&1o8Z7As{*dv#^X}GT=QU8EDRX*b|U{K3##b zZktfxL+6gje{c?`R>}L6PC$x;$U@!p1*id_j`7jAW{`c`y0L@E6K3Y4K7iep&P1SX z`r~igpWT1%`w%(R1WF62Tmasy9zU-Mum}l1*k#?VB@2SCjszD|f!pTiOzuhN1ZV;* z&ITNHEQf-yDBO4Bh9lKZCB&So$QaO1)e5y-mj?%0wNJK$atqX3VbeklwnLc8-FvLg^5{B#KJk>GjFA#%kD z*qwoCN#C2x4cHNgMh!f}S;L}w4MA>RxV%p1Ukd{fIQ9B-&UcHZAN_1_GSEJmkIi)saY*5CAzxdp z1vtK@KOm8)eQXNaJ3VE4Y&)Fe^Luzfmn!BTc3N8 z;?X=w<^tw;@NPlxWS~W|^*5!#*!w(do9-|rSfoGt))+I*ynO>XA$SoJfmo2t&qfoV zG?PWl^Y6d^{zk5V2=CKRKmGLf>#x7Q4Q)EMvzerzJeDyJfP-9sb_&h`TtBl=VqHLM z108C{JXl5nkPFZ=+dKOY2`}F1 z=$*+xHU&+UA9z+F9PUNVU`IcU0JJ%oQKn_|0U(`wk%I4<0AhEbIkNPZ!83FP;{8(q zEDHgWfw~94|56C_$G_~I1FR%L6h&{iwr$(CZQHi(aBbVxH<#aB<9lr>-rSc|QmNH^ z^=eL1V|4e_yLI2|O3mukm_2(oe#WR#qYxAngm&%PAwJ~KpI_dWHf>svY15`*!-fs8 zl}?>H;nJl`$d)Y|$l=3>v3T)fCZ|&z>EZFJDHNE?uBlwQ3c%Y}o=*v}jTI`T3zyqeifm zqeqWo?%cT`&6+iXudgo(6)FV6skROd4j4CX97vBIJ#g{jMP$j61%&7CxxM}R^#d6( zVgy`WUAZ5LT^Q8tYvy!d-nORzzuTs%AHpmKax##*udWjVKacHa)TmJdadB~|Q>PB% z1A{U}iWEw$IKoKBx0O?;PT|p`M;JSHEIM}Vh{A;nW8=n+Q0&>WN6wUt961sL1`I%k z3>mO>>sBZ>Z{Cb_>C&Np|NeXpJG#6_K`~-Lwr}5FzT?@mXE<@<1QhGmtwWV6RZzEX zT?y3Nw{OGC%L}%$Y11az?!0;PQba|pqN1WexZdNgUAxe$S1$%V#Z1KN<;#~K@7}!= z!5cVmATnppjJ|#Q;>eLB`1I*hvdlbH7=I9IcK}vl7G~77{zr{!p-^H~{?C9dUAnY% z)`}G?!fKW;UyjzTTT1{PJ9Z3Do;-mrkZabgLHF+6CCK;g-HZ3{-$TJ*p&(Ek_!{1( zU?>>y`}Xa_m@#9}rcE2HUAq=Kk&%%&dGaJ?&YX$*_3Mixhz}Htz`#Het=zeDi`&vU ze*8G1qoa9j0dN z1Z2nOsO0GANIT0BQ${hp6Q@j>5{j^}FwB`V2ReK&w~M*v-yb^6h0B&L%VQ&AWRfRO9;ODg`eN5j=w6uLQy_{JD+ZJ5)vL??6)92# zIdbHHNrMIroE8K^5qMvR;`Nf(YSGAKF>7khXTy!Yt^ci zREvX{HeVMOrb+q&H1U)vm;BV-@ku9RP*%o zgvqU2x5OE|eft(VwCjA2-Z3=kdWf^&wzH0)3jnv*Y{9|7P%w9Aj=yBd5=j9%ckT?Gf&~k* zuqw`~T)A>0Hag|Ym*-=2R1^Zap9u86q8F9*QQtTvXf^gCpsvI}TFq5mCs{3^LWK%q zGgYfrtr`fc@3j21r_57XgkmSOYE)qHjAsHEfZMihW6Gchk&Mbsn>MA5XXAb_V08Dw zeUaz3Z{KbP5rdEWM|Z>gnsop}bo(_cje&E0XbnV(zg4$LnUJoBW#F;Z^BsCZ^ zWC)L)E~=IKDy^t8Y_>@@(jOtT7X{lDAP z$iv%7wg4zQSjmzlzdKT7&6<^kHL1iCA0Hp)=u)+Q{`@(91uZ`FU-bu*%;jTZVqhz@ z>;>YjfHc36e7FfXJ=MxGTe>Z*xw}qem;>8P|l~A<+3J$lQ$Ckn)$LcG4 z=YcB54nxsjY3WWJeozdSyTrIXedh}@84`5;U1BgF` zcLa(wle_T^Cm!xb~1x95dQLIPdzhurRBa#$k1Xxw97*IPmj>+ky4Q zANZa0*_7A8`T<~sJFNulQ(WRyL+5lHGl7LA{umpN)_-hE8TETn5dXlbYhh;rAZ!@I z(c+JfJslKFJ5C)w>1E;1h0I{!fyl^@^*@9UuJuxM{_VEl*Y63K>7hQc6>-1SgAjW} zwKf)_yCr_YFYzw~e}XIVB(&YA^?dM+IPD_5hl5@T5VSV|1^Rff%m*iIxH|@%?rh@P zu>-I!2nbOqljDwi%y+!LJFMfl9PJU=&kpA`6#cF5ZUgTAFW|eM9oO}l(r{XY@9#dQ z+wJ83;Q5pOBP9lZ2#c{f|9Xiq>0Apt3&7`tfKk~;MJss0nQ;sRerMo(QC z<*^^Rj2QoT{*~e1UyND(A7`M44}M0sC#zJQ?wR?{YL&nH*{UN;$wTbHk^GO;nD>^H`Utv~7Osb70F3hN`hzMO`{JI$b(9{`+AFCMxbn!W8&hr`5>jbWhL9*m0ud)H`w%{PMU4cPxB!hqqX+G(d(SQym9P`7( zNmvjw;d7ZWFls>nCw&%?vx$fAEiI5n>eI%iMovpCo(<@9n)uLZEiw~_I&>byHNm-E zkH@n$@#ndTwA+e$<#Krlhhq+Lfo41~O}~#qaR=}7r{yZckkZ4 z0a@tGMl!1j?&yF-Q%1T;$Hd48qB%a#ef2zX6J?jp#z)H0VF>5)lZWeQ8v~;k&!bAi z)e-<;Cex#E?k+2N0Vj2>B^2TUBL4pFI@w>K-z`xo+;P@rQcLJ3Geqiir3A1lKp`hc z*a+3)XA>9i5T`rYBJAX-(?~#?*7Lh8PN%)f7}YRp=DqKp@@j81GZF}`b zsf*Q!RzD{HcZj(2GNbHtJZsSJFqUr5(ebL&BX7VXcJ3X=W$CG8Gu6hK&ZF%}Z`nHs zSnIAJ3a7Sh+d&bvL9G}?oGPw^+ELx8MvTU_ZQHi=8?%$Uk}qHOt)G%}lbiG2J<^^v zGi$IXDGWFOKsEI^zFyYS@&O&ob}x4eg9kRjq}S>xT7lb8)6%4l0Nb+2`PpQWLg{D* z2RgY3f(g&f8VQ=;1}<_Y?rhs5;j(0EtO14BN?-~xnBNI56l%W@6QX?1b{p6x-g8Gf zI>2{8a*t%H!HNKv^?4To(b+%z@WZte4*&xItg!z2>mO4Zb7g4duE5eFu$j$$cnrtN zYe|!kK5_fs<{*93?9WnCo4N+9ZX*g3UO8`mvnfkLEyr}YDqr5GttS$x1Wx<=e&*&r zC}6gj`0Qv|FzVoNBFRcyD$XRdhIc0@ld|DOqSfi!^LgcWf@X!6zpeQbZfJEy0|_{R z5y1AXgpKbRPOC4qu6&;E{1il-pW3+&MuQJFZSxU zJLY@Q)1MM>9 zZygD3Y4XVIzlFdAmd#;meKXaLOEamb>ti}BX(pKfVi(4(sCd6w$NYx^f>jyIj1d|E0GGhruE%r$q6!UoIx(8=YJC@UHRv*;y7?J(dX;1Tg7MXG2r*pB_o;d zP^sltR9E14M#8^fAOR=tdYAd^v(J7k^Yhqx@XJLLK!EH9khHV*wy(;U@358ywikQv z4iIW9f(%n}X+cB-I&GMOk}v)A__zfGO;DYNY_g7;*HwaDfvRIRQ-94-$>^|P ze0aJ|1PeZUMp5|e{#KV>TT&*1tu*)ys2n&B%)Qhz`K2ZPg8&%-qWNy(($6}f z$*~rlEj1^I5s+(UrblaXk~>@biN-ZfocTXk087LQC;(Wtnm8Uv?M`zRpZxD4778*= zL&wCqy<@{P&s~^eL2-1#WSHa6t|Krg|D?HW%te?ViGuGhyao8#%523aI8sHuPy+16*&L{08F$M zP*ZPzSu+G3Sk--N;)Pmp^W}On&kj}P_g>w4FJ#usfj4k?5O>K!n#0VT<25-xP3@g^ zIcLo3gtQ6>+-smKa7>7*-U;p#Ft$4n1&1cPi=Ny*&1|~a{JbLyqrt)Hz7$%%TXO?U zN@9)&BR2#PlA8IylG5I-$zU&P73cMx1^<5lfC(l8m)T*59lkFs;1CXinD#b3sXZu| zX7(d!o)(%K#hMZy0}(Zk-GXi(JA)DP40sJDDygx^Baro`Koh9!*0g>J@%1Tyj+s*% zlG%5{jVt@ck+<2_>Bu=A8@>{>2#i_glWTHCq3beOB^lu1vFt`Bv$19~KD*{fe6u$B zZIaCY0RZ9(eDu*r-_|6OdskgsN^=^#H@Mj|ln_#y{0q?0TBLGFNnscyrHDPGLp@4RK_&LUIw~PcX zHM4=sQ3!&DL&J1)n2Jq+p=OVTmHXl|&a*7j|M}AXE&u5hi~yo^2w{JOfQ$Y9f+Yu0t4GIK$bFOrV)dAjuf;V2N|H z1~SD}Tei?%0{~ucK1{WB(Tr;+*p5B>;nOQuHhJm{Kj-xm44l|y(w32zUIwGxJ-$z+?|b>!tf`w;8AN0O%v*(~Hr&@ktM? zc)niPG%f{aerA3`x1>5>hYY#iA=vHW_(5SbH7?=aB(`U3_Nbn-y1so{k&X8nS*r3nce&-#y zT$tzL`btE* zOk_6T*n}T*3VaWIk2dF>s1@)q_^|+tf3r_QdNjlZN>pOA%{IHQH2+1Tl;2-RnSb-A zi1{A?fDitwufBS`EP)k%{PD+K>m@FsuwDj&>%=W^)O4&j&+p7JcP_chcP*&QRE$t$ zX~M84{1gZd*0>dp!PA|wKvRg#!V`R`;Y|R#K#KS7XYM|RJvWU>K>W>wCjpFKgE1`$ zf|t+h5F@WKjQ_$SoNs&f*=H|?@K0sw?*IU-5DXZUCGhf2JMHvUwP)J9H4V|gapoTh zCYPW(6y;iYh8<1l*>rY|09kwvPBzf!)hG1AVC*WC|LL#uHwr#Y<($)gyRV(*dCGDf zN*&imD;0kPO6En_x^hvLC+Q#McU5apHv{tC^$}R7uE%(}MuYD_7 z=%nh`wkb^O2!DWp_23TEjV<)?dgsPl3$bIZ_ccJ|P(9)}KKQ@Enr#2P`v14m^1qbO|5PUaSGDe;()Lf)v1gA2 zebz|mXDx2GZcrEoVK|Bt>)h^t+RdriA9z53ogmGFA4U0=#+LPD@Q-+&fytMlNtc0_ zJTcJ2g<(OJH;0FTUNZ5)JM676_?~N6@@BZu<$)m`mo!-FbE!N$54J2!`C`e2BV8uh zGc;U53k+5M94FYpfg^1ucgg--2v%SH`WpZPANEsMFTK@S)CEAStE#GQ)DMs}bv)rH RX(j*w002ovPDHLkV1lP50o?!q literal 0 HcmV?d00001 diff --git a/src/Ryujinx.UI.Common/Resources/Icon_NCA.png b/src/Ryujinx.UI.Common/Resources/Icon_NCA.png new file mode 100644 index 0000000000000000000000000000000000000000..99def6cfd74712bffad207f9186a78f4ae141cd1 GIT binary patch literal 18432 zcmV*rKt#WZP) z0w0%qm58=MTcIFo(`!+GL2ZN9K~NMyw02$8rsQ^R21?`^&YW+4Gv_dK2CU( zr-f(N^Qy_a!W~K~5)(cb_GofJ;d@h$R=8wxvcg{`YvIr&Se?SIWDQ4aSV?IaVRw9vVq1L zNmAtTh+2^9afu1h<1*50nEn6TS3NN@T&eExL*t8Ky!C>82g*gm_&hd@@(I|U;VN6r z&mF<&yXeKL=01eBEx5R@YSBHo+=i|vHKIj+B%8Q3k$~4TXkCK#TWDUX^z3(^(?HD+hehn%9D00009a7bBm000&x000&x0ZCFM@BjctCP_p=RCwC# zoCBt7S*?V~xLlkzz--QSU|6a+Jsyq)_22dCPxzcu? z2Ny#q48RG#I%CF+`sJ5j{^^%qdg+U6*RGwmZr!@kYu2n8v1QAa-8XOE+_iP<)>N%l zQ>9W#+qZAmb0e?SYH7!g9f{{{+qS7rWop|<0A&KT6R53#XP-yy>EpoMUPtcpGq&2Q zUUl_)oqj5(o^tladgGkF56XB}IeYMR(Vo7v<5}-beSD5a8Dslec+Ud%qnvpdSHO7) zY(8(x9JJrm-`~HgtE=nH5hF&t*4^Fx^!V}P&-vKLK6cTQPd>R-(>p^b44}OUzowKA z_8X5p^2oQIe){S6Y}l}2cOi`s10*AN0_*7LfB-=xM=B5!gl&p&u^shJM3Iqz*pxXD zN22vQC&mbIOz#_sM&N6SeU(Fy29cRXX2tJ+x<`IHlXK$s^YAg8S@8ZNBk{3S=XquX ztFnN&ao)UnCr_U~{Zw^Pf)Nx3(0&rXo{9d`x88c|Utf6Ph514RB`0JNppuXvYXMHY zAXbpr|D50;s>moDA(hk42`Cap)D$A(N2Ws=5SdOK$AISv5fhA z{ftXHAZitn5h}&K`2L-F=~?HXXBdRuwf3SkY0{(zSFBj^#{~-(T&$}0&B@ao+uDpTx zot>Q^A;jba2m!}A9HA+9iN;tE3k2eXPuoS zZ`;VTO`Y&BK)B9ad`;}zeH8%hok=i{Gs9?l0gS6TAZ7t)MjwIN@qPye2K4@viy^Rf zHCy}bZ-4v8^XJb$OItUypzWbH$cVr5#v5<^{UL`OvR6Lft_*4Y`t|AUx8F{0yzxd_ zwQ7};2v!H%$RS`5CEBDQG|45@rcIkv=7<78(T;L#P3G`^diHgAzt}f2A#dYrW{s}e zBGqDlCz9A7$?f$p1b5h>0XXryxf4})f!p9bI}>1D7=hk*Tq|S8wX;|D;F=U;vsNXI zO%l$yb|h`F^V(~#{n8$L?6KqMqmRCMFqs0?!9@J0o_gwmhaP(9pR?4j>fqme^Ud_) zi!Y{^Uw#<|AgK=n0LhUij-Z$Z5n-d0acLcG(`X8SQ1n4#P`x|KqNI+5&ODK+@a%1U zZso2z)4!=*51}w_WE3zsLDUe?jFkLcJ5jsFZ;(cJ+AQbvaoYO|6f8EP@o%n1#R0wMqr-Rlg&MGd2&b~+Bu;}rx>xDXS4qBlKy%A;33diZIhG5y(XerIy7o%kHoxi;laLDycQRHxkc7Oz@; z9ln1*3*I>Z(}~&{w?r~2^*fuHAIO^K{YM^oT%$qCdYg5c;6k@9DNr~ zOc5C0rLq$Yx$n!zq29HFcw^!OPogd0@7GNx*5+E2YewX|3MI$7+_9U`mA_{(4ZazVzwH`@7Tpw`CHUm~=7@#4jYJoeaQ3ncktHgVI5*WCoG3(Ln{HkhZx%srC{`9D7izE{l|NJ z_pCbsQRkY0*LhSWgypJ!Y!}xYwYa!H0eOfmpezPD#ox`Cgli0PEO%v`o0d(_GuxQ~ zRN7c`>AdKoi$1pZ-g}=b$+`RPy9?Qn{EcUoi;JV4tDI{sfgx&CuU#5PDd+9{n(QB4 zwc$U|oiny`bP_u9%6TayW36oo?0@+1;R>vAEeGiz?R;m_uIf93Lluu&5rm70<_tbw zJK_^^4a2pAra?7%*HdMQnsrZ9lkOaHD5F1SFBzY}#JB7Qu#$^(M^Lt+L&9HN>kK!QW0sJ0-k*Q2gR z2Hegxo_gvjwNqr8q+MGw0PRobA#+T*2zK`D*>SJz-?f3ido>!RePrwc2%2)7 zuF)yT*aSP#J~A)|`sz%S!~it*uYdjP?|k~xpFSry+0<@YK)p>p{~h<=fB#oqG7u!9 zZ`AE^WpY1F#`lc!9>-@uWCsUEZ4M6qyIc;i1CzA-(d!%c2{7NpqP*tt>2NVWS%c^8gzWVzxcBBWGc-Sk?9IX5U(e8BVLKv-^4vd3DgP{m~ zY;yb#tWD377L3;{ASGVQjU78yJ`IhfIk|>vI}L#FU=g2wM>8r$HhM4>nooo9L@xUDKvb6SI1u0Wv?IsE)R z-}~P8E|V5eOn}y^|MeW?^C=Dw>BvC^@4geHBm>@C9XbTM%R#=@OoI{fmzil-1WY<< zD$)uF=NI=Ga!ER;f_IlZ!%T5sV?OfpmoIGnbYnoSFXGyOOvJI*c?dw zrOg-$bsj#K!JY(I0c0>(i(Le(=3uOhLuYL7_IK@rU6vj@vpw}%N}9h zlH+xE&a6Y>^Ui-q>K7=06Owy=@P}I|CNoxA?+bjF6&X-oRs z2t+Y~2@@uGoI(7`6GwEnaI^KqUl>3I>Cb_XlT?QWK!6bNvDF*XFJD=kYJHp2_SNgf z?lo$Gp$HNn+18&jZ1?n&k>eVqf69hc)FSoce-tR^S>+xiR4ELg^-#ef6Jx?XOxKx$ z!zbR}khbdV*R4-GvSu)ZslR}n!u49JY}l9tU+5p^3;~gA3 z{^*`rPchP)KspRx+r=S|s_*@c@gAfn(k$biXD5tz+%(L?kfeT^RbBAJm^TO&ZB4Y3 zhZ!x#H9p@Nd#Jj?rUTmmx_ha10(rlWpP7BC`h|}eGact8r&MnOvB$6ElL{>?wp3kU zI*#EPu8d3dLJvfTpvf)u0QC>;RYt3dWhlmFxDqXPw0tKJmp`!6=dWm1UC>ir#c4f& zyhxmACy#G|4)>Bo7Z#ls~Z&A%+B1obBD48-3@@kLjanemDhBm%jCClCZBWb;3^gY`+Zf)1aL%^ zF&WtFapeo)cRVT=1N(QdynD8iF7ul;8Z7VLZKTV5hIB??NBLG*gsmU%EZ${G z!LMcw{7%%!N6?KJ4SxSw2yA)-G@Uy_!dqcz+ZS?NksQ#D9SxPQ4+Q+XcQE!E1c67U z3wS-74uU z6+Ut@q8>d0@o&}yT^ z(5_m7zajKubi+mh(PvyPL3{+xngS9qeFC_oNkDYtCcyUOJz{~p+#GEa&h9wG#s%C# zKqXQ6FG~HTApo1Rtt9^QXXqPumfmzVTHY_L3k0pgV_CkTJ!B8(e0f%udx0k1Ai zA@cJ?(5T^nN+mTehW~Fs#t_teY6CQOd;BOdv4+j74J@v(*nJ^ZbsS}}!KI>Y)euKW}041lK zDfx_D3qao&-rTbQN|Nuc%WmbwmS}P*Z#BG&7X`5e2Lr4yvpB8?Z9bYNH3H^MMPS!X zAm-C&=qwkf@Si@xZZ=CpoOlc7K$SKz{*?W@0bzrN0@075XS%fT%8?yXJY@JN#TrE# z;|p&TV3wDkQxJ<>32fUM>cBt?WqvBVe~DQ;<6dD3k%C?iWqRQ0q+JR|=Ofp*Vd-Z@v{PfQug=#1<9?%(Vs zc9yad9q}qzoIQ$ReenE`5|@ZABn1D8KX?xMx${BP{{HwKuTD+i-=H3dVf9)V7Ayks zt5yZx{W~Z%1xP&r_G`B2tUC`tX>1G3j{W7Egt&rn;0W*^KT+Qi+R5X^Ja?zkMj3eFHdbSlhe z&*4v~GN*uc(s+OtcfEWC)6j7UA2tn9_wR#fM~|TF%Gk!PFQH?L>QK0HHK4l(z-Ir^ zgT%JjN~0+(-KeoZ^Z~&oNrIrobKq>oRck=h@w7;=eG?KsyMlNZ_W$?eRV3WB4(gyF z5R-6yM$oz?@Ne7@iLRYT{M)C*w?v2cK^+hPVp_2V&Z6tk9sHLs_#MrnIp7i{lzJ^< zW}rS20^Pu2ut!Bfpw?)hSv*g%ARwCyK#@XnK`qVRp^;H6TopG=5yx}xo9K1phldnpf0(0zP#d_%GVb} zIAimBPr+G)#Xezu2T}^OL&7RTVFra5ckPB{=W!7J#8olYLVSu9h2NARh;8yjADVU+ zxF!zZo(81W*ikT$)&{U^0u*lyggJa+u?o?Q9Om>cYiP+@&@Wsk?L$QcKZf#uCxCeyGt zxN-$!`*>pu&CoO;J0s=Z7(_|zNyFez zv3>dmfr{f0+IJv`_2nyRiL*R|GPGH1aM`o6C3|Bf#zg4&VuQ_WW*}W27pV|;Z~*)O zBrICA=xgD@pvph&Sd5(x)a&))_Ref=fbB1TLJJkVp@IrXay^{|Ncn zKYjZ2@{60M5faz}%Gd{_{*^E9ocT9upw_0%V~Y1sH}B;LQ3X z0cdwJkVt;a-Q;BMBoV0FvjI#6uC+S=Ga4Xra1brd=kx(&W}r^M!7)uW3m{MX9{M{` z4JfiYRCveG2owN={Q#KDPHU!=)W_nXc!ZLQthWaI30rZ6FKPw zq$D`a%+yHdPy^^;neGfcHXKxh3cy->ORyXsI*c8GxXybvChN8HxyTc$erlS%Rv^oQ zjRM@t%&K2D1MTzLy3qLf=l73~kB=X;6&FeVeSY>fU+MYB+|0UwTdBZ00dqVP&k4^! z(+qY@0#O6V_m1hn{H^Xw8&CrnPR}FH`=g z^RZJPWKmG>1hmx}Oe_V)%MabBM08*n!+`w*&vM{w+L#;5waDg(@E!@c83nK(KwhK% zW2XhX>c2=HFw6$*AwU|@>H`?$QP-A!uMm)@^Zh&7aE3u3JR=u{4pp-_8xU2DkDY5g zvwrxpeW2v?bT;Q4%A5~a_k$P)WX%jfK7Ou6UKIkI?!@~!>2nGdO={+FG+39sDWO+> zfTx{-q9EY3&jz$_Z*LEZgC?;jV7JH8L9;9IMIS(V_g4|8eD5p-p+a1pxo?Y42xoMtN)N@5hFPh*1H`&hyj3&HvPu}^Iu9Dcf9V^r8gLjkA!h?*#n!G5Ks@oe@0#Pc6{ZM+8598B&adlK$&3KV z%RMF_2Z0h{!VIX2Ks3&pRXL2+fDm;6ixJ@>ua%#(=JR_P1xW8K_c_Jol=?5yGfrs= zQrBn%Y5>Ez0Q{$?GZ1@hwUPZ=WSKvb$z`=d3WiyX0%Vwv@E;#O8G%Pne{ni+&Hx}a zIG@eoU=Y;&{NU*ocw}C?SO<_^f8MR$fTpk^$lao#JpEHL>jRKuy(g&o&+fongFb-y zEWk%nnf&Z6sW0XLq=T7j%+72(mtBJ9u_quZS``K{!IceKs&pcY{186;Zg%oK`vKl+ z1mbjHHX<7~@V83JsP;{rp6DUcPyPO^&!HgOlyEcX1f1WjtkF&>UPC|DqrviCWNj8? zTky31YrQ!f)Kp+p`a10y%ux4VUtjf%dxe0k=q=;>vurk-ZNRv`kp^rYK(=IJGuX8E z_jfkzp=~F!sQq6xoy`tF^~*4{0$@4|FgFWgFvyW&AaO7VA9@}%j0npz^f`0_=5>~2 z9&q%DPQbN^fZb0r+8hc1Hl%F+TCx8r%-|Z8Y`Kvlyb}wJ;9>C@;pPkx4q`PRz1WJV z2qavyT^1~={lEvGr1P)VOh2E+Fkr1n;%w2B_SlJJAh8U%);=pBD^GO-UTg|NN^t#1 z4BAomMNME8HqQKi#?jw8P!s^9{ua>$B+Y`<7IrlOu`dASz&U*iKh&kdAQOQ~1*15l z)>PMWWka7X2oei|AWX|%c*a4Z2{24=0oFUWq%`;Bm#sAO4(26AXQ>ekqZ5#jA21P^ z!l1fev~(6(wTk0DHA@bd3O(0KiSR8U;A9|9`?SgGIU5GAlm?5*KpZHwW;?LdI_udC z`T$b(%8!`@Aks8@j`z2MKxA=(<7dhyAbpO28o(f>f*}A>DC9kTCsNNd;Rp3U zKYpEngQSxWbAU}ENuz$bLA$EI*c_DiDmzYP?Fmr<1;9mf07;#L8?*Y)Xn^eYhs*{b z*^Bb>J7gX}srl6pFjX+30QFnic)9T+oa7HQ#$3}8c(eVy<-q^hy9!XLf+%`v#O^K( z?CtlX}YGjW(^>5p@t+#MgV6hxKcFbEm zet?I+Ql(0y!^e*wJ)c9rc=P5BZQs6~_V3?M*RNlvq@q}{V$`{FXR2SnJ{DkLyWOZ! zqiENzU1Y+!bLXgBxpL$ZlI52rrUVBA1aWr=Ey1Kys}yF)sI3x)YIL_&Cn6|}961u1 zhvWJ*@tt+{?AhMJ89#n}vKBv(kdQ!Q$Bw0n6)RGQ4jsG<$Tprof6j~Fh`oFF#_D7s zSzgMcz*Nb3m;tyB0$6ZzEK}Yr1P6U>XTSc(16s9eRqE5HPh13n6DLkk@7}$=^xg)J z963T~&YYo1lO|F3?%lmo%LWb|Iz&mqx^?Sl*sx(Jx11ji!X%(-MkAjsyzdxc8&H~m z@J*1X2B{f9&_EV&?b@}N`z0=3yqH?GYQ>A+8h-!&O(RB(pw_Khd)Y;_K-!FjofLfi z`V~RVX%>Lr)#+2t@}wUim!<9yW&pD1FBMv9y3Z+_Gv>d4|Nh=?nJH7I08@PZH9@Ms zbm>wG5~#SQ@eKUJa&G@j2X0b>sC?$oxt?z)2U6HHn{G-Nktvnw{K57cI==? zSi5#D4IDTS!N(aNz+f#4c2U;usl_AO`*0I77M zwKNtrQs|Z~TPCSq&6h79tyr;w)~{br5*<5sM7Dv?^Sk)5nDDtK9tCYjZEN%N>C>(| zEoxUOVu|1m;ql;n(N#@=+6Ij2=|Hz`-J;2pC*wN>_F#e?$F6Qd&z?PL<;s=GRx9Ef zgLK=pX%kd}UTL#z*)skd-+&(B&Ye3H2@ph;Dpd;Eg{j1Hv-|7M22`cNc!ec^OJIkE zI*w((vQERz!23X~{Qy_3T7`NWD#Ad5z}d5B>GkW^qyy4*DDC(`^@HZkn^T=SbtqM; zR93Fro;`a=hfbY3d5f%O&6@b`*zqH32Od3o1pdNa?XPF_<))%~1H^~~3=an3abL*+ zs4bhgISmMWz{7_RDF`4PH(|<@Dc(Yg7Fhfi09muK_ursF1L6Z-yLRnLIvhNBFzD1@ z5`uCBSWchvdtnLS5IpW1$S(=27?O$dQ8>O`0?zKt~4t`SXWdK(hYoaG89$LpTc{nSQAN#Es&KEvI9^V9YTv zqr=6E7g4MG_%8bQLHwb0r$gPkbx{(<6L(`I|G+m$U>Xr!iAZ?)@+GtgLD&BB`C{>p z)B)i7;%CSKxCHwE;Kr~R10?}#1OEQ~iyNeKcnu^7WXY0+7A;!DDQrY6T)2=bSFTKr z8#lI*rY0)&El{99Ts@e#Z{Mb?SFdWXAL@R!jKehI&6_tV61HvIM)m5|v&jMIuC*kZ zS%AjRU#J9(r4*PKJ;1v^f&+fK^0>+VbK)sK; zUx6v0Vu}!%Kp;E68VuA^f~7?m5S9S$!0EnPpK$T0*o8MhM;N` zch>&Hzddv2Ov;rj7wK^Fz!v=;!Q&2G3i7jxH3Q)8~LjoEn@qYaH0lTlb>Dpj&P=_vE zx|nuRa08gG)Dodru3QO{4CEOAUi@r9$6P>20y+jSayS90#vtlwu<8gDXf}Y)k}bH= zqepw$fDVHO4Wgq*kK%jzAOSf5nEn3{jA9H-@R{VhAo7g&0lI$#iTU&A>!i>K645+Q zrjn5tz4|-9!jpjs43mG3 z;8(N*Z-J-?Pe_%=j2VMrCoAVns9B23U4;@Alk4C;22zg zNf{>p&Y6Jh?>ZO+(tjpKZ0F6JM<%RZy_$RgEI;N9j073RdSeMlmC^YK80auyV+c%j z%4boiR&zjjINkHtU8QBeAT(| z6L0`b7h=lLb{mj48E!B0CZj%op&^KC9RMJtm~u!XQb54_!ODTnp9ew^4=7$XKby%f zqgVzK+eVEVMP=QMK7EZEH89?sV1f1CJo@Kw05$*~g}&=-a2Ngh^+QV$!NlV;jvqg6 z!SdTaKn6k_l}xByk{TFfTUnK)htq(rAuXB!mHFqJ$ZA1V5EM(mgKy59IYD$A66*$F z;ew0Mh==q8OxXD^U%pI?SFc`qQ+pF8Oz=t!4WM)c2Vt61z@U$x7j|B)Z8@e5Z9pvp zRQ(CQgK9kM5MdVpgpAAtu_9SJOfrO z!6nzkNOh9IgI01;Vc1gej_D)?|(|Ens-i4!M!*?>jhJb)zCzu3*;=U1jo z8GHu>4jdnbBfvbdO@#^-;2X$`-v~GYr%98BA^>L*8cjd2`27N=rW!zZ0YcE~X+i1+ z!b(8djl#1SW&rNhfKs2$I~cM9S=Ysm1k?+7AQi@;o5@T^^}s@0zI-_bk&vb*wZ8Xz z<{ngt2oh*Ts^1MeKWx8DuD{V-dz@8>00LVuopm?@oHY`V+sx{D6KqGvT>*CpIRNdU z4HyNM@9`_%fB_hoh}Z;#LFk7=dk`RyZDH~2{P~T{KSaD{BesDFSPa}p8B z_;CMlg9zDEt+a|E{VF!eXX8^$Niv^CB zpw6t-IQ9#J4`e{mqG7{^qyZK`EYSVc{(90_Y5?T%V2+1@!T^A1D+L-%^hqK{ZRfR6$6iO}>5mCHs1V^)go8>hT)2SgJP>up3<4ztGyv7ARl_)L zQmz*Ue{kKkcHY&iS0~nLSO7+@jn?5$o;)$RHS4lPb%Ri!BRq2;044Ju>Hr*qZwN*QJ=`nu%Y1V~ddN6>ZpIr~om zFfRyj3Z51OWgYAtSZ=C+-0Tg3xnp5pZ8|Z=#kvj)L@0%_)XWzAx za2AV!xcK<5LT5aSG-t^_+qo0G;~dPfVZ#^&=!(Gf{%1Wv&khi|Q1`nMkgUJ-DOFEO z+k%fmcUIIe=+aH@Tp~}p0#jX@bpq-A=Xn;Z0ZGDW+YHHk>CNR5`9o!3#`-yQ$Ok|J z7~V@}0q5#0|r!N}|K%>UBVC(9(&9{2&iNXLipC|w!!F6Bl)!AQY1cR6d0H|d+ zX=o?d?rpf&h4$AoJF43Y~(D28gR^-Of&uE$?g|2qP(odb}`XZ(E- z1wd!|A(4^R>6vYtPNZnE1z6hs>)Hy3d8`FUrwr4A`Mn1&MtV{^v_gg|hbOLr^8O3D4J|h{( z>A-U^43tEmb!FW)p}?(kN2KY4pu0=4pL!w?18QjvHiQ}g>KGq=YX;f3ts6Ut+%PkD zeE_>Horyrx^vB<}Kd1kC?@Q$3qi-#sa{<^_J$_yjU=R|1u*k<$#(K0G*Z_??sAo`Ca7KuZYl&qosT3{Ovg zG5+8Ctw)1(wW&UUNxDPpB5bm53fgy(KJhQdgspp?@*F&91QZMnVj>XtR=u(!uqC@l z(d?4R2s>Zaq5S;(9D_I*ggYROLmSvjc9A_@1-NZAyABsR0C$goIZOn~5Ma0d&UVeG zJ>R%Q+VR(r_(KFLoy}kr;1?H;h(J$oWs!PDK)|~sMgewCzZ3=(q5b9_vLg^5{OSNPxbXh-ES~zU{`X!ZRd~BY{ z4D43T5AwCeT7dIs`U4V)n#ZP~ebQ3~j5&Z*HWN@D9W3oT!Vw6Jr(Ypxs{v_Ek3d(C3^8mQW1!$+>x`5|9 z3nkVCv^LP8X3T>@6ac*dJ+r;D|B&z^|AsahvlaLv4j%jhW&#dFJT?n7dT|g6k6MC@ z{(%0C4<7TeHA{T}^Fn~q-!9rnPwn3(YKYx|3G=Oco0H`jQ~->Z1f%vtgaM!C0f;_; zZadc}K3eE+CIi_NG*y1+w%|E4K;%Xu&^7|l=43{hhS3LrbnZne{>B6ly92E&OMe+W zLsua7p8{Z52#^faJplfbLZGj&ujBLc^SI*Ui2wQbvd zs%<>iwrxAC^lVGTvui7=Z5wg(pJbBB)Y=C7C9}r8cjx@)KXWIu_!X*GuP*N^TC^z0 zqD70~;o*Uw#l^)1hYlS=xpL(|qNAg+e*JnV#*ZJ5tgI{)DpW|0RdRAN7A#l*#r*m6 zv2Wi#Ii`gR7e-D_4kk{V2*tW}>kt_k2~xg%c^p1`7;bKEP;A?_4L&|TAa(23#fcLq z(6wt<{4BAtu~@lsB}mVnJ#qZ_an!0+3xrc`Q&LheYt}4~Aw!1X;K73^Q>F|E&);)< zM~xZm}u%pX+6ci%{q@$ywe8-a~Pmqw10L89dyI^Bu zgAN@!NT6Q2bP0KRdH7kpyu4()t5&T_5f!nzdGjU+*Lw^K3c|2q!x;1wGZCxj&!2<5 zdGkgDZ|vByC|$ZVMvfebn3x!R`0(MM%sf>Xe-LVQ0G31_m{HgIA2q6lLW!m7e+F#h z#*L-3wrtrFmS*$j&FI&!p9D}`TpS)fdIVh{ckI}K!Gi}&kcWnb;_chFP%v012owjt zhPNpg3I=>wSQuu^n1TNN`(x+MozS^);|3BF6R~vZQgrInNgTniK(RP|`m~5v<;s=C zZRy0v$K%$mTRgUMoK1HW5D*~y5F8v#LE!aYUnfu<4|5RO@_}zX3t&zFQfsxzH8D=t zSY#$3J3g~gYHBL&EHkELF})KPELaeV3l}b6#flZs;d{A_wCd;2pNFDawQ7UbiXx+NCJZ_Sak-;n}(_I4t17Q*s6@?KaM&SMX_mbUBXz?iySFT(^etteo zC<;zaPPG5}w$qA>#W!IV*Ift&io(TGLuGfK7RaY2BV0;#*G`XY11YO-rt%D zsLuf~@AFzEBdY_jAo`D?eh^g#sK+fn8<4iYNRcA!)UrL@yLUIU7_F*mQn#A0E~kYy zpL_M{mApr9Cr_S)4r}42O`G!Ah!~kvsZxcd0j<8+brZT5*7p>MdiCnT#NOUs_ODKz zI;c>g0!%u0?kwNkq)8JsmO+>~xxHqSjIYDm|I?>Wu(h?t*XY)*8=q&H^-Tfh^KIL< z{acHJm^NP*7M4l+0yNe?2C*P|;H%n9w`As-0gJ5J)~Z!24oL}I8@8c z&4tOuixeM4z0L+ z?(EsKzxq6F{`KqEISybBlCkulv*5OK96=WV*r@Uxu@hP}vrrElu zSS%I=u?7L;rsL7qImeP;4l=iAff>G!24dUJ($)Km$C?e8q>Zi{=Jgx z52u8@EtktL#sAp=(!$ypejmc+X4`Z+-PI2uNIwAbDb_mzVa@muQ13$J#b_w3|5Y`j z)7U@61-S2!P$gzKLAbN)OAs7zFMXYDn-lP$YSSnHXa2eFT!1SC7yPq@Kgf(g&p7ow z6Q4qT|8p5ewp9Z96c>_e zeB=0QTnPjbe#pkH?7z4r_WE8Fq{pOPv>1B4w@&B;RfZoHo7NB9HQ4{L=?!gb!r z{d}!Ay~b07GaU30?EiWbP{GH85fA3MX!k$%&H~kzBM6{zSM+V@ts=oC_+Llx7@mE- zHGIS3ehYHoaH#pw@6#~cnV#0_^6I{W1ILR^-EQnqBF}vkQ5aL}9{tC>>ow11Tc_jj zj!56PT+dKA&%cF1$FjBC{FZTk=d-oZIxg@0ex|!{>Uwbhx89>A4*pOUXY;)C5nj)}{R<=dFn6ev+EkcO{s9y=DuQ7G@pG z(YVZ@xDE+CsW9l++m2z$9Q?e^e;rTPly)fZ#9Wc`_8++)oPXRu&FST*j9JZr{I@PU z({ACBh2T-o>Y3%sL30Kkv!K$ZlCcuBNR?E?+v>R8)+yttYq#2wnL_(10o$MA z=Y~^Dyd_ZQ6^5Ooo>vKd2CRct{D`s~&&CIQR#6Kf3|vB-?Dwozrkr?wfDEnG4&k-rcIyCC(y!5uG+E z`yjKRc}QwYL$_CTyLBlKk^`T@ZX>9|z}k(qi!w_3^nD5EZ<|zZcW1uGW>xchU(=DJ z};+`C$SeDkth@VaKMM#hv~8!w^DL zJ7SWb)I7IkWNW+YJ!SqzdQe&STWw+^_9Mn+)4{}b*lrW~ZP$4_eD-$Q#@<)wxtFU0 zaBzt*-~3YFjz3`Gzn`Ca)~+61IvD_fXGfrq0wduzb6^gL3?o2|oA;IYJ!)L3$vd1t zgA6bv+un{+R9&uAFXO0$tvc^rklp-bgfu*i^we_%We8vxCq&@0@Fogi3S5B$5}$)u zWklY!*?uVR`7*C+QT;D|#>W~=yX4XL#^9!0wMCE98-oxsjDvgXKI(k{rw_nL0Rf`s zLU<;YNkIx04+5x--aI_M?zfIN%2x|Ot+)Nywm|AKB$y0{Ok4a%Vbxj_De!O~gY<`z z6XF40FbVfvmAaDJOvsK})`{`MKxQ61vyL#%N^LV*uaB8C?Rvd{0nbS_KY%a&nUTV* zdK`UB%}3+|IR6$LWbRgA5FE{*(xaiIsfRRRK`BRkm^xDy#GLYTl`$Z-Ab{1LMddW{ zkZ)~)HfBC{HW@jqu{0Xsc1(QOv{jj@!yMcPbxUxCYd_CsCJoF@q=psqnycfX92;|} zOETkuX_CiWA>J}g>QzIS_uxR89_s*uc|_^d04^cDTObRIDI}|zu7#S3j{)h${h>+2WetE-CSxgVTi$ajGpo5~4TZX- z5Wn*~r|uWAr3P@C076cX&1guVklyp znHYIUNClUn9vVr*an&s7pL`NTnP>)xUMHHFiTh@bw%2Pe+19QEUrmjAtbv_-cBYnDnZfa%wSbBKaR2`OuO2=FI2pj3 zS65dbUnJ%`lFBQ=S|g~M?K*D5v2w~$ zLQ<1z%k$p57d_rGBYy$OZF){b3`o?IIl_S`!(ekB0zbd6Z(<|(EmY@dMG2KTJ8#ou zkjxOj+njsDt=q&sZ`!qA$7e@y{F><(CN{UIGrI@xgP-R+USc)5ZW^e9BCK`Lxc5*Vosd-rU@L1X5X5?gpGn(Z+SH@`4oR_9iyxL*O<8T;5W< zYAnoz6qOgmtiz2>E8Cv;*~U7EKIhtD1oW|P9qx1Ojs4u7$yD=Nr zL5(P0c{Nb&ahT!UY3hf=kYD|4+R8?PWat?k6ZE+`5(nOZA-00D&(5icb6p#WZXD|Tug8LO`vq; zpVNxtz{y0PkHf<%UTZ%CzCT?ul6r@dmfu}nfxjLZ|N4OhoVerN`qy87U0C7g<#pk= zjVFKsc^DwlPTpH%)n1jHLt-=fCaC;?&|48?D8*$25f5nDFbyRu$LSg477#Q;wG7#0 z9X+qB1iJ=Rzu7`vo_CJd*T^KJ(}L|XUE6RrJ3;~FO}{2wBK z%@YL_0BloD9M7Y7XE@6xuU*7KLuP2`oH#%4+%Us)7pB-yoSiTk=KQnk2n-6YWaRsi zDwo%0Wu!?{ez#jQUxptkGC$9)NlAk+y)~nU<~c3u={8TXzJOzPzR&yR;hjOmBygS~ z0)(5-T0I%Uu_jf$uK?f^XaEKPnCK{=rr!ReW(Yd3s{8i94YlEB<@*6e2fFxVo}H?) zK!%}hFJ;!#z#BOH5O>K!hQn0O@tB;SruWW!*=NG)q_heM+-sm~a1=yU?*#V=7~7qP zhQpBEMNe*@W;Weye(n*C(coaYFO8P>*4zL^Nz`}*Fin(_)XeJ=rF~PA!QRm-&TCz% z@&5n-6G#Sbef;sq&#x%p!n%nxWP2(LO=y^A_G4(C7TO+VT+1#45j~HG1>HV&1|#YW zcnl^g>9NQokoBfP6R13_Y5fv1)~5kFXHIWOX5WGvSN4q~Z}VBpk@I_O_)5?sFlL)2 z$K;Aa*JZFuGQh*%@-Q;W#+uFe#+oDXi~7j2Necf50LW0_#~**3Ta!qhRh@nIkKvmb zVxGOWxyc@PGi68*Mi8*-(CWVKY(!QSI@D+QZ)F_?HY5?vVzYO>8>2G)*+{}>*ez4Q zb86cdK#{BF9PC|97#eVBpxqvR&arz~Mgo_f*}&y21VO{8VGeUB#U{YevnRqLc;y=B zT2|=)UU~nf|6ell{_6$+m}C&BX{x`k1|JvKP1zb8KyRGt|C?c^*RXJe;t%GtO{AG; zV8}~PL=ww&2xALpINOm4G;;|g83P_Hacn9jk*k#g|nU|h|(ZfC7Q-TSJ%lz)jDoVSmW`zE!UW)x60ASL9pl-mj zn(uyI6=KY6uhl28oAUB_j_)z_bLz<+kYi%Lw$8wvYih|j(-Tmz_J8bTCy8kMKxYvt z=^lVl560`I`)#)wr*!}vBV+01=-v3F2UboyI&GRE1!sPyKA~Guoz*ErzVDPQf!`IT z$q3Y%{cWh}(Qke8%{O1Jk1not#T0q}0RY-sW3XQe1a_?6|EDoL&I81S-Zfsvj4%~n z@%;Q=y=QQ-9M*Ypi(X7%d5GX+vQg6O(!*Qf=tdwWLo+0@BGKi>-XFP$Zf3)!&`eL( z=UI5u?G;GyBH#raXyjH%GA(S-r?%g^1D6YXUwOZTXb->k+G`It<45Xm3cUXS0I{&m zfBEH?kB>h3=p(E7=Mj1EP&!JKcds|gZhiXC<6 z-AyzaG)OYixOsjgLn)N9vW{e#CDGwmknGBgya5k@9cd6fyq;0Pw=knlcQSdOFsd(`fS`SxfHnT^lNuiZP08O&Hqvt*`euxICeA@Ep$AplQTr;R!zU@FoCV zAjNa{HFqDw-Y|_KAfLar0FI!n+$4$XNW=Seijl_{#(zT*&X2zH&O3L)_*MEl0Kk-0 z2nGx)68PxE6HlB~?U~MMO+)nHSoueS$t9>xMY$H9X-5-!4xL>iU@dx%i48P*^$FcH z7`qBtm=jRQ0>fvhoPB!S?qg?op0Zr0QpdH?O2uCQ!nz1YS1!u(Bpn0;3wbQO;yrTa z#_39yRtKC{mFK_n?YG~4bY%QXB>EWuU>ZaL8?ycFx8Hs&lIHD7c^zGH;eD9yA_(N+ z%|qypIj$^pZ@_xrReAvid*5-BhPW3%*Xvmx>vPFNYol}5UK^T+K=zuhv0!S^O+sj7 zCiYCW=6vaiP2@2ZXQ7gZb7txN+5EPF-xZr_)LPs8PMbU8)6zv;|KW!phVWaeaev47 zhjQSlk_c~8>-M$o)O0Uxd*+#Eo?T+0E2>}HAqcHw`~d>igF8((w$R7#TN`gL#E!N8 zt^p#8_v7EEk(*5OYaT}yM*;*gS1@{v?9Xsmb>EZ(vl% zYex_)3G~mU@_X=u4?cKll;&GH+OD+m4*)Q|Kwzs;-+&!z-Ejm4JIt6dsK-N=^%Z%Y>WfB(%)ejVTO51`Oxa9bI@>;(gG|o80Ukw?A)> z?e7~1O`KY;OAv9vk83z+)Q)DHZuIr4NNz=y^?p)m(Wl>f>#e7nr^mVigrAIm0Dx&F z6Gk+!-6$g1VYJ<@9;EF~KKbOcYCE%lV82>-t8wWrwcV+SiR9(oXP(Av1LNmybOso! z1YOW$C0noGp~1!m`d5*OjkVXCz=cB|Mj43sT@$O`UoT>@aT`D!#W?!lYlAh}{%)uTLu!&&Ddk}3 zJEPxue=+=GYMjButH)p_vtTnT7EJEK=p7Ze`sN0m%!Lz&`z*QP*H~kTTaOWwnX#A& zqZ!bLJ{MKpb3rxs{?CQb>sMd@2EgFm n=b_V=zSmjk0wCTirPPCJ7=4KsaoCHE00000NkvXXu0mjfV*0L% literal 0 HcmV?d00001 diff --git a/src/Ryujinx.UI.Common/Resources/Icon_NRO.png b/src/Ryujinx.UI.Common/Resources/Icon_NRO.png new file mode 100644 index 0000000000000000000000000000000000000000..6cec176ad3d9ebccb5eac6e6e5562e380457b514 GIT binary patch literal 18404 zcmV*MKx4m&P) z0w0%qm58=MTcIFo(`!+GL2ZN9K~NMyw02$8rsQ^R21?`^&YW+4Gv_dK2CU( zr-f(N^Qy_a!W~K~5)(cb_GofJ;d@h$R=8wxvcg{`YvIr&Se?SIWDQ4aSV?IaVRw9vVq1L zNmAtTh+2^9afu1h<1*50nEn6TS3NN@T&eExL*t8Ky!C>82g*gm_&hd@@(I|U;VN6r z&mF<&yXeKL=01eBEx5R@YSBHo+=i|vHKIj+B%8Q3k$~4TXkCK#TWDUX^z3(^(?HD+hehn%9D00009a7bBm000&x000&x0ZCFM@Bjct3Q0skRCwC# zoCAz)OBROf;I@!{*Uz>TjBVQt2Eo`2Cw#VT+qP}nXd6@iNxtNtm5b5E>8_PjYM&Bz zE!Wy76(~@kK!E}U3KS?%pg@5F1?B`)%HHSbyC^Ude-|Q{{d*->s`5N%89-qG# z&n|{g7=ROeb@RH>+0(2Yi(_PYr%pAueY_eJ+t0=>z(`2kAC#xr=EIhQqwy_C=8&v3BRV45BBSi zKKkf4pLyn)caMyW^ayE$7$6zB6WF|Y^B_PF$&m_#1YwtnaIqcrPDGKBfY_8d5l5o+ zIw!^maZK+UiALaSiG7trkOq;NMP|kCmF|(>&g7i9{XBdOXBNEw$Vhx_)p?#7L0=XS zH*LG^wx?{i*=DDyixP~WFo5Qh`1MTmpSta~+y3$5i!W{`L{M@<76B>=39=U8#0z2t ziM?`ygQy~-aD-G&J13w>6j4)%h##2_Wk6&)aU28Iw_HN@G9YRkw2RDzKCz7XeEp0| zJ0NNmkr67zz4-o}dFff_pl29_-nI6kwBd#uKD7JpyZ>eT?YF-~Rn4Ih6b1m{*KWS~ z=3SnB_SwB2dE}AzN|Ni4b?74UtoEm@1*EL<>(G@)B#SB~QbpwuFpQy5Cx}vqwv;83v&dg!5h=L2rdkcNkcQ*Uo?dh^XUQ(s@7 zk_c7@+sGkc5GC5AAT-G()Y#aV${bN3DB4kut;rnTPtU#%?-%<2GzTxEK2G~=*$zD3eVow=T`2j zGyRuo*Fz|b8yN)*P7pN&G$SQ{*G|-~arxM8)4MZIeKl@8Cudeb^z+b`-)I++>1eIe z5Z`|L?eyxaucmxcl=}PoHJ0YC9ewoCf6tobfxU9=aFN2%-*J6^pxVIh(E9kmU{MiuuP7YLBPo+H)ShrcJrW{s5l^(W^M z9g9(uVSlve3>imzlh6<&5FY7_H1DaWp8CV9ufF=XvxN!NW`V=6C42e5u-9ID?Uz;l ziX1s2coDu-x!~aQ>!s**cSLfcL*gUZaNhYfpXxv;#*f!h5QO@ATh%eX6DEC>7{k}I z%yF<(g5vGK|i|=zkDICI*<(mHw<1;o%yW z7>3HLx8HvI$N&EKzh9W~@6Beu8K^a}^p^;2+i9nr4t@Ob$G4Z{*P+RVQv#tt1ozB4 z0;n!}-lLb@C6I$lHXsJOIO0cQbA)w>{hY8Iq7AJC_#R?xLzIHikD=raP4*w}@!hlT z1Vo){243e;l@OMz`mtSHbJXJE{siP9wt%u2=oEi1i%Gb~AjfiR#<@+i>3LQ&Gk{7H zYc4GpUwrXL_t|Hk^CUTYdU}LxNdCsN%EiS|&sEN~mcS4-s@E=!qm=V@eoeNbt2X=x zx^u>Mj!r^HUO6v?WURF*f&I^)KVN}0uH_*8qn+xCCyD97JPIuH>*o?GPs!B$AjAc#X$Qw2zHh!oWp#PxdA)yM$6 zZ48QWr`gj_Kdp9(Op~;0N(P|)={#hPDHp+RwbfQ}uk7Eof#qI}Mrj`zy8wcw9H(n^ z3NkjqPPC5<%z?f-6D2VKjs3gd{qEbJ`qZb+%}vIdO$(?uspo&*0}nj#6_*SIi70v4 ztlN%W79DI()PC^vQF5Caq=BI@CpQO59-+r`bgog(+F9qKMT=4nL2WPr>YD{P136=* z$-MU3YutReJx$C2!BDFA7y;fl34-OCcA3$C9yG*wN~rHJ1J^4>0ktSDrao`3Iui>C%NTS~ndS2Z;tl5%}2T z_#IfAo+T|9Z&?8;@mg-_(xvigXe`aiHB{4S0E7pN`1CuPQ8BX7gQ?JzBDmHSRezOn zPR?LCS3O4!&mEA_H^dm6T?~N+cW%7##t(OPcD7}02MwSwfK}=J&#M2sk2vCp1v*%l zz%Kors64XFJ@7c|ToZ5xKskr4`P>F|Gr86{CSxLog{0<2o~zn+79KFQ%B9XY7r-FJeNWWal?Lx(`uI>^_WX*NRsGBd4>fJrB9 zWx;|4&fG}|5Rb(4D@jESek!GOwT4?oWs+>`^sX4C$)uB}Ga!ER;f_IlZ?cJSQ(ej}z^WtumYjNi`qQ6Ynx)Au;@BKW z{N&T4)LwYu1xfvtUa1u*fGHx}k}0NVzlw|wWz~yu?6%u(_w2XdexFP!^(iJ`)r1Ha z4FD5hu{&mBXs2%(3Z#r|NKTV{6 zjX)F=SbzQXJ|0qz-v&ub4s8Sffs)q^=nHUr9VYILzH1$40N|2`ll!yBfhb=cPW8e7;-5oE zz4u5n)rl!!3g`nNhmFRh)u)7zFd>*`EE4D*g*!4SUxe?&7v zQ@X}+{3$0~))|6SGyhm80Wm_OKmloqli6gHsW|aZ_U=H#ffx+p_=up;$Lt&Q?aQVX z!x*6Y!7xA2a}FeA_nQ3kf%1c7S_`@t7Ht~bpA&6_((R)9ZsuD~-9eQ(2e>ZaIw0kV zG|n?V2_)MH#KG`uyY!Gp)$b0B_aHryW*PT93t_zD#=|@slH{XV)dWwBd4rJA)Bisr-%NVEUwD4my9eM_ zqBRcS|76>yPOfd+wr$(CZQHhO+qP|^h?-)Pyth03cFx;LFXpD3*O~lgwv4km_|A9E z*}T~Cv3@>$CJcmUlWLIjUp5WW-m+z}Gq3S-;8kmHKvfNtz)sC>L3;NQK2sn;L~JThf~ zf3qea=Bcw_{}=>0b)p1dFBfs{Jgld0;8(aTndmxVYOJw!ZxFBI#iaj5y?lwV0pme5 zB}>4sQ3D9<4eKN5&09omKMFZ@npFS$*e6Vd{c{NHufIYS;19prwLq*NKOubT637c7 zZNEMsmNREzyZh4Vk$)DQOEkR9E-oruo_3v5kUIuI%`Ub(K$tlIF6Wgq2NJAXCDl@4 zBPS#B(IXJQW=+7A&JVeO$>XFCKZJSdBgViUWJX*d%(0I*ZG3}7=|AGV(lE0r=7F4JuNZMP6~1XcP1da*#pG@C~B(|>-Qfp@7N_B zVTX^v`u#hITBk$6dB~1$5 zswMavLMui;Y$Om}<8le&BVg7P5dY~Dz$Hxrq8~Q_wkPiq3*^hq(Kg}ijzer*z#Rls z5-tBlslU_(U~{&W#DD$_L*vfUp|8;r-;-oR)bkf0Ub%9D@JBAfv|$s7cgYg)>e3V< zUrz*%8V;ybQsH9kKNNA1cj;2_ojwA8BXa#-n0D+0@vBu6o-Jy@qh=ZSHE9GAcIYr7 z7OYn^1(;3xp#p*3zDybJ0)W=^ceIJ;|5a%marGJq)m+3hV5HcuC)!~{fItm=#}0;P zhV&r%iIZV}`yFvrV4t4ws9OPl!_<2)_;>HAEe`F3vCvMP2vu~gYyJ8ShMohI9CD`Q zGj=Wj{arZRqX0^h@2<;k<-{)0#KhP4r=By96{0sAy)EN6Hik+_zRbbSJhW@#kmo%$N}*;`&X<3H^GZ ze@_D1wuU+&00NuA07Jhah>NU0enLNWI@s4oyK)i!M%0dDFj6;(A;efB*X(jh;u*V% zDYXdL?a3764gw0xfKl^{hGVF7roStNOPEj`zM{jYVg+#Vp5SMr=CEFQ2uJMie?mWT3Rn?0Z^1Hl4q{?6n!pmq&l$|$1HiKD4Sy10 zWzT&43qz+~;NO1$(GKhfkE~hnCsawb&`p~RIonbVeTLxMPAvY zANk^qN}!;WY25~xHf@HT=fNha1Z6J?>&a{Q6``MR(;Jvt?i7B-_WCVY>eMOWh&okD zuxxMM;deZ9=74~bmi~n>tQXRwkGS5RKv;r1a6n)DiGT$&;gLEO1j6-gX0tTB7`S*Y z6y#_HASw`C)%Kti7xUvP{nLQgw)T>u;hOnViVL5va ze?pZx1$2|f12nnoBNj8p6_#8Vf|95L}WZ2wXe|&SqM*21FfChXmUp$th<9QCf4^Tv!cFU-4h#e_ z3zugEu3G}X#to6^+IhskeM&qM9o`4Ezdwk1#Tqz^zC(BLU%udXw2S6|OO#M*wTPL4 zh6o7s1BbyL83}<}tA%#)JjH^5Y%TzyipQM@ zJ?Kmk^VcE#(q#}KNyamCW-+VKGjk?@<3KFu&Ld*cMs`AtI9fqXa!tOx;-T{K0TF6! ze(xzbi!j+IwC_Mlfp$okMJSA*5Yw*Rh}wA^gg!DA5h<}H(H*c)8A zg0X$Ru?x-6Fd#c3<=z-XLF`OGdK2iw9%CSf{_iZNd5d7#y-&*Wle544f}lptz@<$C ziI*2*65OLVRMG$E??1$!GWQyZuxWGPQK~3B^W*}TB852g#^1dM{@i7;OX7&R=SWyq zt%GODV$qR*6{7CnN7UJiuswPUN5Yn_M8t_x4u=%YiHl$&3ULPqz!yNm zqD70o7Tye6`G@U`vBQA|gF)QhnOz%T`^%ruK?My|PyudkfN&ng(}GK#62%N3A+P<@ zr%x}hWZ2!QrS0hc!`|8BMiB&IloW>!I05%RhY!FJ$V~1KR7g+yR9aecZ&RJTCyjb$ zOhoTg*MGgey;Tv%uacGZPc#AkBg_NLO{x1-%|v#Segd8`0n#r(7OGKzVJHF4tUnTf zoD3wpI57d(^Rtn^0e=vzZ2Df zBCA7%-xwN!0${Ko0CU-C&6JY*Se>>=Zv&n_0GJ;WZXN(1JJLDRfsbndq6W}&PVhUW zNjiuPL2_fVQD_PQkwXF?YXRszncadk0d7ueDI!n%bZVv;8tiK}1SLyQ4PepDaQdvEiVo`1~EtP8l63ak?_$20Ms@C-D~ zV8FOndD$Itf4j{oiyu@T_GUr{{P+Q#J?dCC^b-h0E1*c zb_#?n3hJGJwpxRUrNDUkq5G7G4h&-$uz%oL4xCLJb7Q#|*&GqxBLO#~0QLjOd(?mI zv|v~L7s&&L*?>I+NF!Q(0E0a0+R~pD0`lp6|4ufXVGs!K$VH(;)hx~iL>1#>=N`|j zAHHlKDEU5}%{hlM=L6RBAcg^1GXs#1pL>yKg#f2J@qSMFoI*vDnmHT|)+KLB=#?Mf z)6PIq5OCUO1KO`&zdk4qn#7)f-5yH^&91~3eE{j*UqzhqcV{us^k!k31sM^jb^G)e zPyL<=h-nGj&sU!+0Qiqjdq0aa%3E81KQ=5xj0#Y8p1(Z=0qN(nSPslaWKKZ6%f9Ws z4xq0htAsJ`^PnO8m%ah30f%7|ayCF#Z0-60#1o(At~vg*!W2O;g94!2`E{QvnGpba zy2k|MAW$Mqm;qH0h{jp7Du=Ne5TXuXF(O>#x$<+?e18w40O_6OIj6XsQvXGI#wkrf z>K=_i4PZDIfdBM#24at`HnQJ~Eb~V)xvW-5!7z(afDH2y{^P?ZBk<_yFHQ%}833dP z=es!^41$`UA3VJRkIZWq>j2X0&u^F2W;2CNlHoGqHt9y^f?B$ff!+Gho1<*818 zk6$O?AnD}89AJ}3(x@vpXjk+q$~t0B_I~0EUcw~g+gFK zi3Np#LM#OK{X4f#-EME@;mx~~fAgzZDZM*$@0s(R@6Nfdh>ni-XgApX{XM?EzTk@B z;9vv>2EvH<_jf!zJm5F*JqryDg^!O9bilv)=;#Qir>EkczP!BP@$peS%aD)|G3KD4 zAjHMRAt@;d{{H^ji-8;r_yxmnf1Z1C2C5my1>gt3-alQ0bb^Z-**#AES65fj*w_eH zOiWCms;UaVWo&E=Q&UrL#q8`XN=iy#1TCbZq9S-oN=gb65)u#<6@}#FWTd5~!AhuA z+w$@<78e(>va&*b_P<^9rSTOP7o)JSPy{R;sJ$gvVoGo@;IU}J$pBaea8E$FWs}+% z#I*OjRkXFWiA571A8(6(wSt3#1936c)z#?i>_k{t7(9WF(Oz0w!rfZ8XB;(vjZzQIXOW^MFq06vnlK1;N;E2 zLAVI0HlvZ}7XMu*jtwX`|KNuppBkiQ0N#XC@xH#k|EjR~_V$MM_I8YqkK?CoZ*LFH_G z&WdLL`}FtsBQrBo)Cs-=Y5mgDQh1ZQyF29O<_c+$lam8$FzzBPX9R3B%kxY>&cPsR zd+;CHI@W7#t_Pf-pIi99?T5$p@$mt793LN}zP=v1sH>~v+JaYJGAsdDV#=Qb^LGLI za54aXAe4sjLRU4J+BRIGkag(Eyiz5j-2I0xw*9y;D1UtB1OUwkgUFFt-J5N@CfnQF!@$4*rOvjx00993 zsI9HVcc4W(Gc$vpo*o!+cz6gihKGl7d3gzUbai#1xw#p>zP?Zi4-ZFHRu%}#I7UWB zFf=p-GbjU?oSZ~&Z?EO|lomNV0II~41A;yr3BY!Yx=}+-*=$X>M@L7=@3v&TI`H%J zLu+d*%FD}P#P05{QE;U+9urS~npAGnry)%HQD+=TI0l?Tywz1jR7z1F#2F5mEV4E2Tgdl{J zN>G&&fJ926DkTDn0H^>Gg$xRz1O#CFT|Pd?J?Gx{-g)QVIDf9Uy|-<*d)_(c`~K(u z|2|6^%#~7rnBd%`ZXP~-*!0ix`0?ZH-}wFew{d|KW^iy2>2xEDo{!Pjn~Gf8>Tg_NzV>2)&aM-WKhCu#7K}rA!W^%be_&vM=O1M89~v4e z6d;9!X8mn*nZDv7wFlQszg7TBwN~DXY6C{`hGfXyL0Br<076&> z$W`IBZHoY2fWyN>SZ=4~x4*yNEG#T=zqR53c#g36Gcz;h;NZZx@cHv+F=1(IukYL+ zgH-Tm&1nKht$Db-yhPi;DMK?HK;FG-qB+kPl*~YpAp;_lUOTf`92lVJ9030>6X@Hw zZ<)V653u0#^YdCv(^j(b;^G2fPN8rK^&WznB6N3m=l*>k-oAZnf};4}WKm)PXvzs_ z7n`+f|LLM2f)oKngoOJ#KNFRfP66!p&N1-n)ho{beMA*3bzr}}U);03y=_7O5)VIs zjoaH>;{yu6(0Kj&bXh*%IrPj2l7l!Bo0zQ^U&J|YgU zI#OT{_?${O_o)&yl48E}4WAkyFba%9%Qg2D3U-@0NjoJ9tpJGx1lo(O2TAvA6T~hG z+Sk{YkzWpA7J@WnF{`~&kO{o8vEll8INL8uL)zg4ELtE!Jx_@)b#V~h6D}Z;fRQ1S zoL$K-45F>U_F1m0S75M-raveDBo+Aj^{bO0TVG!%4WQUYZQX($9UW$6Wd$3s^Bx{O zdSrY!KR*wB|3#8t?y2nmY3KZTLLlC=e=k>soq>`UfJrYk0d(Ha3SD_s9f&GOK_3XK zaoN=kZ{ED&-Z|CKH2|8508UR&O<-_h5g-Eae>ZFnNvEGoc-t8m5s3o;Dj(C~nWqd` zbWj)stEyMhj6UG%>dMpv@Do(=!9`d~Qv2Y+1M~jHI|> zV}X1WK7d`L4!~41Ha2Elz|TK1G11U7f*|yPMeye4#7h+XBKo`B}#~h!~rnz zZHJcb-eMokXfmfN!1n+8^{WnMAf5$+B%%QyK}m_PvMNZ9K8w8i*rFMmrlP*(<>jpC zss}8vOweX_eWxN&A1m9E^yCJN4=K@P7i6$iTD;nVpsMei&>M!m7EMDi)lkOV0d`tS zY?Yp{KYsi$@7}#D@=H*}vmtE&90GFY-rip0!-o$aBw}e5b{b&oPuTq{B%4zNV7meN zHJ2?6;#F$`Di_>5dGe%IwE-xu0c-BU=H_O0l>xm4DfLwZoP%G!d_iA;APUu9Za1N` zvom&mB*oj7>;sRC36^zNe!S`n_VuT4)+59A>cQRbbPo}M;SQ&VsgR1D@hvFm5Xo+3aD z!dZlbP670SAWA@e8pU@pk$~~lfObnTR%WrQ%2go==y%m|mc6&PSG64&(u|$knGP@k zDZ;U|w8XP5vlFvml=kIw$bh@Ly0SLh4lp_C2~hTDgRQ5dZxo@`Dz{c<7lh~pn*xz5gk!LX<0A*2w?0vNoW($7SJFx zgxkZ1=pLlt-6W`6$snehTACnp$$SQO|qR8)nC5^FdT=^@DY22bVeXwlvv?C%lZIS4?_ z{PX#J#C^JJo;IB}JO0Z(|FZJR<;wR7&oI9*V4BQAo6i@r`aw&eR)Uz6DC5PLum~|m zyy7&B08#Uis@MuZ7CNz=OEFNH{44~bL1AITVRg;H2J42o2l&U{x!Y6{1W|NMEPR~J zhy=)h6o`VvhzH@V+BnkH)x3cjwDq*oInro8cyzz&`qzIu9JGfsb_F2sJv&hDPj?$y z*Qz79H?0|_DVZbaI{lperwK4G2yhDCEC|Xx0NzE4ZySQ}j^BxV6ZYMDA3y=nv)Z~Y zfCZyst568Y>A@iFN5b=;`*6L_pa9?pj-5|p5Gq=S1Qh~uH4CsE$iO#FfAat)!8w!m zeO3u}L5IkRgP`|Kmzwv!b1uL%ED#qzen@iwcor#cCI9Tso!}kkU{0wT#wb8n1g7^t z>jAn%SmZ$6?@B-&6)>te-P2mrd@$a%Op*c1=>sE;4V_!4)byEo)duWuL&@}V4ahlHQMC^xLg4!3WnAyTOgDP zpa2+`*xdn`Jb=s!Y(XH>^!`W{KMxhgfbl**7(@Yp-A`XO7J%wNs1-TLfNTPq@159j?CllVPXfw!>H#{zjB5~`fLR2z zsKv^JAikdx0-_VJ3(F`b1NIroKu!mqjRNq`rz=p_Z4(OIb?%7#z&V^+CHpCzfD{Rl zg*xdAPy;|6<450`LH2Fy#ttGU%*?$$fZdkPM4)N<$KSR;r~i8IOXLTd09ruj0ASC=?mvy(6EC{+f5?o9LZkwMwxhI_ypb0QJ8*r&(ITVCN;k_F-K&f^jH#0>0 z@ZiAVJ14(A0q2u|mJr~dk0j<9E}#Bl{6FZ>@_O%TQ+)uFbcfbO+GO1nwC^I&`=ZT8 ze&l(|bMT-MP%t!zi9p<2^~#FCmh2+mu$N3m*!i#yX$6Y~B$>vVpv*%dgYZmVclvH(SPo5;{_qKkqM1QewWq5wcTWBVhGJCwtF z(?UU{)&AZibehV6pcxVzLK6UwKk-@evAYlm4!r+7i>H38|Gn1;`Sl@VLy$W!Tu%Y! zy@Y|tKK+B|o_mX?AN_6NWT1I6ADd?~1G`o8g?w$X7U1?X{Q-$Y&0|y0KItg~#vDK@ zn+YhF4wm*E;RpoA)2|S;)qt}w`6smi7Knv7wDoxxDfZ?`G8Zt%gLeyhCj$+Vt-mP^ z#@^>!+jKWmf<<=B#a%~W*O;J*A_N985r_rJ{A@GY~%`vu%8|uAGfEc zr)|-uV>_Ek3d(C3^8mQW1!$+>x`5|13nkVCv^LP8X3T>@6ac*dJ+r;D|B&z^KcP*= zYz4lEg9pEWnSjF(kIe#&UL1tNrIz5LKcK(ygU5Vq%~Bu0ybz%Dw~IE?Q~TRQ4Y4~g zVZL>5bFv(R3V`vFVAOtyFyPfZ0MQ4~ZRh&LM+?1YGLTI{Q{@M~RS1W7k)I_3?MDFG zoUGb`QS<>IoqLgr-8skL~&Sc{{_OKY!Uf2Uxj+C=CB$ZQFLAY8%hBZQBkjJ=;?8?AnTI+eX~{Cz)h2 zwYI^2$*ghj-8ujH&)msub$MUWqD4U#Em{N*4-fn-E-o%Obm$Pul`97l9UYDJ>(@gu ze*Ac3Wo4mIp+a)3l9Q9MV8H??=Fgvxef##wF)duUFmiHoFmd8UDAui8hselCkn-ir z@uv%entjcj4$Rxu65xN^ zH1$K6gMpk3r0%QhKY^de_B|XN9B}vUU9@lC9=`&EvOs|XO01Y+q~rfANl8g~@ZbSv z&YTHnXJ^!|T^pXBo=}8@gvgnasZ*z7%$PAKQKAHVeSM+u_Vz}xV#P3e^k}|@9bMj| zpcpYA9UUFzJDxmwf`o(wD0c1I1sfY1bm-7Q0`=0ROUTR1!_VU7RtY-eXWu5QYsK#-OK|iC8^<{v71Zn>QkOW5C&Y!a^y(F#KhpkhY$Z`=BdK? zgHWpjuq67xjJnqUs8KBxN-S0XGhiDxZY-U(Wy_YZG@CbXM!$ajB!J@L;_&FvBj^IT zW5*5*9z0lrJTx>EZ{NO!g26&Tpg8a~yiLJSFyO<&!Z2gT4D|2cA3JyMgwBl{H;|Z^ zh^0%HqEn|%;s|~PipA;Er$w|XSFS8>OD8@)9=C4Y;<1(EY`UX>fB@Nt;NV~i07BS>!Gcg+xNre0 zR;+*y-^*>JRX>0JJQUTcRg)~Kla`hSJrGWvIwfU9>(;I1ag&UU3}#80?iv^v2$QI& zD2x~}0`K3ym+Wpri%)U5a^(v0^YdXsQE+l{qW#ymomN~dz6rCq?m{R~6fTw;D!ZF5 zfsG>Z@#9A`7)1m&Zrq4Xn>JDK{?<%DeGY(mpVulGSsj1{(SHo}gQzkz*Q zpFVwpt*tG-Mz?O=_&m$3ZwfGBGarp;~TkE=(?7yeQ7#_3PKrpV$(%WJXvO7oXV0Gf)#qvRuV25;aR76WjHL&i1-G5!2)Y2s z_L}YN*|Sivc4v*hVZ#O~0bE^Op;NPFO%AMzvuf6?nTU-}^XAR@7#$UbK<@tudSB58 zmG!KzpAxh@)GRW)Ly>xPNpvtgkr^AZIQV9D%0; z;l4*iM8MtMU4r^&p?KZDe;<4I?v>JD!h{JtcDne8Fs7%cBRe}=#Ksh-&+MJWwd`OF zgZ(-fYGB?f)uyfNQ^19FQp7#qLg#3hSsG@@Z^xDi#fzIm1OWwg&2(U{uZIJ5a<|}q z2nV1x%@S;%-KISc*RR$9lm*r_&DK4|VzDTQHJEtNbsauBtaS~;P=|ntkN>Lyllbz# zzrS@XFxe42gafo~TQbA72Ss?X0OW6@v;@q4e0az_PACGDNZwnvEC5~YsQCwdKW4$Mnhr!uc{H9#{MBLzFaFUoPY;a zn??aR^UrnX0$d@u;GZr0L1qMc#;NC-_!R2WDDT8)Q{E5B0}u(ftrF0uxR6xi8^>ScN+5{vLpE+@|HUn_*Y~0zJ*G8vUnB}3 z!InbU%J?hCUKLT|JR#<3O*i;credJyZ^Cw z7O1uyK>&@rqHjZQ6$viE|2l%l@a*fY;TsnBTaW{XL(PwVpN8Si^t4u&SN9zpI9_b( zc4LPUdG4c#!kAk3=s)INuX!%pIvt01MEbtvdWOPz{w)kTmaX09w~X^UpRJA7ae3$W zGu?$#*Ms}N^&Ty8@Q1QEo9CU62-D7^O=lEZHCw2(FzZ;3#$^V@bx7bzg+a&Ob_`4A;OA}r>v+1Rv_p9( z=8BZJ|H%E|{Nw&$qOP8mlf|3>gzyVZ`&6xvS-*!~nhH=J7HErB|(Fzg)lyh`XpShp@^{jTk_Z;#LC z{l_#MztN;Qy$S5VE$~VQ(qJxg4&t*vHv#=T-JB-CLrU0Q_4pIU!9T$J*$p@+*=B3* zoQ9in-+c4UT-a{)?pCEPaTe)|=(I`M2bl%ULsDBBy1lB~txI{39QYJ=8$lHY)^4m_ zlu^>B?@Ks;+oW>4JM%p@tD4{YnvNVLA7URInfIeL&a*Woe}c~1!lO?o13=32uRmT;!e8Al~-)p_rN?B*vUq~T$t zr=BAyLjc1#Ap)O;H&Fmn;0hd&_#DJ4Bl51z_CtBkmw8o->VNSwKGtB`C6B&01~=uZ zEqa{Z7=(~v9Nbg)QSSpdeE>!Z2oNb%b$N zYMaq|eaxI`*XsogcuuPM0etDtj1*?o)uNuO<2M5aZSO*x)BTAK&MtCQSTm7IfvWkn^F5Zf<6UJ)HJqCB?B9mGyc?~I1cEU$X-JOx z{R?&GQZvHl$GZRBMC`uGDBVt@27ZRIkQSl_#%{0S)JkldBbzXAD7j44IO9I-M|xCY zz>@)d_~D12To&t<+JKE^L(HTSLlN7|#K=QJD!2^w&`27Nt7bv}Eb z+&6Qyyr zGqudh4377#1x)mZ`}gmE_3#5)M zt~zMjl=>i%`z4iOIawWL9P~?GRaraKXtUueF3+>tlO3sMoVE80^ZGtaVAYrmJL*}r zZ+MhE+N`G1orIO}v`dOp7QYBm-L}2qMZTHlgqQE!zJ2@aIahQ#0Uu%naIVXSje8EK z8M(Yryss^8XPCo)UGUn+B|4DA{aTU{lA2Uop7-9p=<${r`3p#H({mzXK%$<^5e`Hd z2AlH``1yT(6C1&Ap*lw^N~p})d7CDKWQO?N=G+@@-6rmN)2{tGK0AWr*G#`KvAIQ^ z**$n4{5-J$5NXM$EtHX_mM2X)=lN~oV zlc7BQ)og%3*~BsjoV?e+$pn`4cgrwO{zq>lpUsF(qafe#ZkTi4;|GDL=h^c&)2-tX zV(s@KII;^vq(7n!&ZGR~mFaB#pb>&!cV|hSHU{wGQ)U9pr*%HOzP|qS=H})jkjkoZ zH{eu?Hm+-x7o;$^H?cV%0=F69@|N0FV__zwsJtL%9d2w|+4j88Hr7G(IoA#&ppSLy zaGz@@SJIbJ&fzNNZdlvI0`AO*xmY{uc4x0$O{QvIl&QyluI>i_Phgr|Bc9C^SQn4~ z5BAOh=C&({!nJMNZqW8ceL=0*joG*kYQ(7GjQ2J1)Qj|0vFB zTWTdzA=kXVyDV8CI!vK+X^4=P&_NwF@5}VOCLFEU8-ijbY zDJ~<3ctFdBX((AaPR|&(fS?(wWymJ$=y_cw*fps7%@*qNymP$1MkX1Z7Hro#`?Jc< z*PdAvUc0~5rPq;^iC{YoUIRJ@J_qJr>Y6O6iA!#-|1E1HyNPQFK12X*-KuWnHpd=& z?CCWV>=AhY{lM^naD`vSnD$myb6FRVMM9r$HK5aF{tbr7e5eT=4!e0S*?J!=jN;@V zP9-|@cEJw~q5!4+R-y)#QAyW zh8do_FvW)A?1afM=bv3iU{G)+Bj1lyxx6+jBTbs}yWN`kGW71st>Uecmq*?+hX)f%6OzAl!V`>d6p}HL2=-1puEw126!{OKnG7N2dDYKpi-oW99xJwo?9Hw%P$K?Dpy?5Tr zJ`+|arBy)SUISf&qadn!C%8|**zPag>z9zRJ`K=0b9zHE z`xe}|vTq!Do6lN~oZn-^SArISG21LTCRY@?E`wE)0UrLAhmlb>)@;T%)*Ojn)JK+0 zQusdrK!yT8{`lkEnnd!f>g=8(DMNZNf`Cid;44VDDl$XbIe2<}@Q&0AQ924`k zbq4NSQ%lB~o`8b2|6?aRNkroZI*Uk2_W+D~FkUa+Z@bMntpnf~8A~rm@5U!RuyWeb zY10fTIP)|03Eh(FtWFv7eWzpz{H`!fMxfU0Z$nLwe(RfWzWH)}baAaKrpWsb0MOPN zgZ)wVjiGaD|2W_q$d&%&E-uRwwq0WaV{Bez15X<>stwf)u|xLnx# z%KIfmd-%22UVFG1KT>~F;Qa>xh=pzb%P+rteDu*rA6d;mkH~|E(ov$kd%anH%QO%} zJ`;+r6?fLaML4cSca~yYvkH?{?5IQUZlckkL6VWi&GRD}N}-gMbtKCyi8d#~o{3Zg zjurfvQ{a8zeRMc)p;o}d;Ku?m{>?rK>CuoOP_hz-9(w4r<@xUzNq(1(lz;1{i1{A? zfERw&tXZ#BB(VL$g$qxqo49#yo~53y6Su+9)3M&1Mw*j32FoPa_W7(PSg?9=0RA3MYIl;t{=I3BjaBp(a!(?(;y1i zknL~3{q|##G;deR>*$gT@56K#K_Cxr9zu7_ab=-<1J?Vl(hD%y`;MD5#JvEzUeEGa zpGzKE8=brM+R!`%ve$Ht1yhS|5<(+0v1hV1=Sxp)B9Ey!3za;aGfVH!=C=*}uGmbY z*4pNG+T0PJmM-G@4?p}cgx^w)`#Z)zlmkzdM0lH8x36`lrh94IGtWHp>=FxIQT^Hu zL1-Q04-l{(+-bV8g+6}Y+IV{*cC7Vx4G>wpAOAj$++>T@$tRyx+nEIf`_;Nz zjZ1f_?M_WhBroqi^E74~7(Z{LGr(9S=z<+N}!|27(}nqHA{l zQ{_f$V-5U3M^4aNA6^U}Qj@$&DF;K}8U4=ti{Tej;|wNVJq9zG1)Ev1U~(5m@2I%d zH#g{HE}S^rXUPq}#u`i9dW@LNjKxeC&451ixv1)%3$jVL%#CC+Dgurqemw?^&fxS= z@vA6Kb_yy2kK;M@e=dYxzxw(&00!?q51qdBz0N`x0P$War5;oRDFcN-@H2yQ00000 LNkvXXu0mjfJl$nS literal 0 HcmV?d00001 diff --git a/src/Ryujinx.UI.Common/Resources/Icon_NSO.png b/src/Ryujinx.UI.Common/Resources/Icon_NSO.png new file mode 100644 index 0000000000000000000000000000000000000000..ad88459cfc9515dbf5f8ee0a909eb35d078e37e8 GIT binary patch literal 18705 zcmY(J1yJ06u*Y+UyK8YN(4xiNt+=$fySv-rZY?cPT#CE9ySuwn+#MeOnKy6dWuiBk z4u&s`EC4_o?-D;OBO;6)Dp<=rBt=^wj)Y)^f%~1T7U4@*06a^e?uXVkFlnOK zudq5{tt>LjX)7f^VVl4?VMT1=8dsf}yd76t*snVTW2tt&tozJPuqB%{KPEr4#0~D% zDt4S{YHPYK$}Al8NDGQ#>!iWMniX1+qN$I#bL!6(`JL)nh4;F^(93mgM-=S_=Leh3 zUlk2DcM*U6go!19cWdq_yy>M9`@O-~v8qe&qt)gFm3mrNnK6o_mAbSAfq@72scci` z^IE@mIAyv{QsM&TTC{u^;Hy2t+)E`8g8y@|ALvFNir{%z%XsE?{82nmQsv)AD6PD6 z5vCdU&mkOPa{J4ab<)e*vKl|j7wy8+bJ6WcgO-~BAGjg$?t4Cts7r)}^vzFNqvOP# z2h9h)MSB84bqoy+WtNlt4J#pKH0;^pzEmjlE005WzzXc?%Omz+blAO|F!m92|$7xVe>@tKQ zM;mvtd#`PEfAwvaO)KE%OdB?9ZI+ry#*B<8Qlor|NSvbLWUeujQ{#{ng3On7mCt6Pn8#lHJd z98c%E(JaH~_Vdfj%MltfGX2@nk=7RujyZN79v(YyZ@zEEv#N!vQ#(7kF&|t?zssno zsDy8aha;q^q&#vUX38lF3j4ssya@aoXGc9f*)Mo`jVf*9DR;W@Ogowx^H}oW*!ysH zI4=C5-*TV8Tlacj^U8z3uJwsr_leD7Mwl z+`K?WjIs+dP>|NSHMkr-S)88Z&T3; zOLKsU11KXZWPYv{lol>HIbmqttfg}RSXQ{&mGBlU5JEFCK*gQ4xuzPoZqK<9IG>NVwyMnC@KipkCAlceL%)f~V7hq=e zW~>J%#?+$nNC+L&&s7(cwB9b=9XD*pye&8mGmK9c-d4az0FVo-d0np)*I2>Vd&av} zw{^I`gS7*i&MY1rPbzg_WsZm1gCfd!@t>Hb*{=BbofBL%Vx^*bOKEiYT6Yps1u=OH z{ZTkD;Fksxkvx>oeHfz-I4_KRaA5(AO^A>yPu$hm^`8B$kL}ISjP^;aUH&&)gm~CB zEKH{sVc^BHZSg;X4C)nRCiOx_%(~la8i01~Y17f`YwMZM*@-6XfA6W7g!%5sZN;H~ zUV(cz;f0mO#oR$$wY$5U?&;Ccm)OeTo2b;OUYG0`Rhb7Zh3hOzR|c;eqZm9ZH&j>0 z1&RDbO|yg%eG&|;gM?$>Jm1WCZQ)+W6^mdJMsYtoHKERS=S0)*uGSX2oG}3xiZ)=o z$^L@-Vuhp_xOOA&^wGwbm`7qRI|Z>Kl@QFs|LZ?nUR*3(SEz*sJE_&bzK_#OaNRJH zL4GzTMQh@=V3c_;!)ug%{@Ld3AWS1jf*R(skwQ2n5jM0)6-M6r$Tc<0tHHx zc9JGIldddFLPBC@F9<9;8SCmTce*)$xUj!D-)ohFekmYews(-npvjGb3Q z9RPP+0i-^xu7>9|ZwPSpGeS2o1qijfrG7T*Z@enu3)E7M8rA;B#v#tOJr`fn+o-1P z(5vRQ=CNPcjmmr6l)QteYKD20Vc&)DL}cFqdK=PgFADbC{iQa=I?neq-RqPa8d4(w z1}G#`xcs*W#R8%=`)XaT<#Ij)!VW07fn#2o^p^hy4pwLoabjA_*R^U>#V=?m>PF}2&Q>o4hnYW4r_U9O0l%Y z#2=6V0klR!2-O!+0UssGIU~#Xy^6q_kT%#qORv5saXVi!6*c>zCyJ7HpJL`TqViz@ELI@Y_s?N z%dQAyygt!zW))#D88tfAl+t~F*_#JWn8kPHcL2F_>D?@MpIhG!Ax_`<1KX$?da8rL z$FlIrYkay`r5pUs{Mf#0qq_&4fkv0DyNy&W6%uUcBWg>RrI*F6@5STY8SC=_>qKPE z*)DyI@!DTs(pE>%%w(?0L{YhS$WgSQJKn}UlbyE0X>46~;w94)@J!nRI4Q(no{ay= zL_kq)aWIxN~3GrRS%wR>K0Ak;7GmgWh{Id{&;iYz$1#B542zbQ74*A z5{o)e+}hRtNyl43q{*WQa~S1LeU4(JnlSgQJu4vvFh)e8y-@EG?c)(-QE$Jbwcbs^g9Csr>$!kO6lD#m;>1=2)XWZDXqhukkXI|kk z|5KA&OKB%$mE-cfdggogciivw3h#3Wt%ee`kkuYNOUpT$-+g!Q-HOwA3`J2e5}v1H z>{I`kdW};V-Dc*=!M4i`Zrg_r?hft|H8ao77mZlDmg^}Q6wro~4QGQ!InVx5{_b!h zr;iy$-mAoLq;GoeUv{@)22&V`&9Mhsbk!EQbE8-m_o=wo-{=dL#ih1`7x@Z1jDcVN zIHynmd3sOdyido~IQP5~NECZCj>SCa3rAmAp|`?Jiy*L}(kwRQ+Jdo-gfNi1XlWJ_ zHm~bR$ytSDiz!zdI^fz{6RpvxsJ9};x=Pynd-{5*|NIJJ^pXIgD<~u{Ez=4a@e9YG z{BSlufQ28uv3ZhLyl z_+;T>EF3mSaX4=$pylR{iQ@z&Hwp1=b2rB{M6nxV-IgI}(@SPev&*J>=aGtdALTZ+E256T&hz6=nW9PrND~ zrpb~U2y>0p8(@eluGXCs>C7s$LXbmyd|ksEvEK1T&+nUgJ-x24A5C-i7MD<-Ig+{g z`u%S@Vq-rF{#{H-^j%?lJX`jMIF+QdHe;-R3tP-&Ojv0>cQ&U+WEec$KYQCm^K;KC zfoy?g%OCW{r3ErfE+%5=Rl`nsMZdD5x6WpFXBHD()|vhZAFwmc`-airT=o4oy~b8o zS^)S$R(Ab|>fFY@^4CPm1`9{12p9-3Ah7#L4=<2|8~4Yx4wzN*El3jl*oi%2+cA;r z`1ww6`S<7VC1JKg-uvB9fu8He{F8Z%bj`qY|Fe_R;o~lopNH?<$7N~5YO;%83ggON z{|fVSZ`SQ)N;3g9u^MVM+5R>h0;nM#rq0Uu&)7;`KZ%z&$KRQpRLG$;1|u%O*x`sa z(leurHgzbDO3@AjV+RBTWD{saFGXAYmd2kRG6B)g@1B@~RF^MQ^d9ACfKU1 zF>jxsr=AZ%Y`k7!RDm)AD9C)#K(~w-GXTYrK?^MMroNtg`xP+|%KdB&#JnS{)#ngF zqAu#Uof)6%BuBzY<7C7L z^tkVXf4NB4p^=DXeQX3nL2;h9&yb>?+_AZtm8@om8+>S>d27W>}QYiqEq<^s>_@t_M zE|0Fqa;wna%fwxaPYnyfzCAmRym*OZfPpy!Z{=^_pGUpsVS^_A!5Cn;JIQGIlVttX z6Yk^<_9H-q*;$7e(-eu*G73HKqa~TLh*xKBtOAn{N?=* z%kULCzJ#~~1Ww9^fa>8dFw3=W6ArGyQGauUTe9+bu?AE-7W*ZJGo?g#(;L0JYb)r_ z7%_R(lD_#ep0aj4(L@X}%CB-X_c^yS{bP1`zF1iT^obILFpGcmTmrD7U(;wo3s2(d zylBgZAMSA&Wj=aF#SvPWuYTw$c4xWZ7#>c9CuJt-X*Nc<=d=XUa}0c-+dYtmZ*$_5 z^QCXv1)qWn?Y&Whw{O%6z)%}{&A4GF+caXbI$#UR+{1ae9`8N+V>i7fzRU4!r|`VQ z#jWh;7mIm10y-AppKE0|sH+5I&s(0#9jCwg>EaWPfa8~J5`OT7-5j*`;d2~fDxbXf zMW9}yfFUwb=f@tHXTm48LAQ{RA@a%uO!WA zs>bQoQh|DJQC!$I(->Tp!mo%7$*yy(^mzkK$LMTf_};@?ueS#l)M`S@lb{Lb5;qBV zTWp3a#!;D1saLct%>>#tR*~hkJ{c@J0}cp@Y;-lnzc=j8Tk%&b;)Skp5@pay{j2jJ z(6Si-qXj1VY6C)rphv7a83U)}a!@rdYNZ2+8lINs)0=oXX6&{&xqho-2tagw?_eb=MAJiMOf9aro)8Pr(i4+Ta`g$k)XtyXbrv7el z`ar)*%sjR-pDrM5=@ozE)0zp89Q|@`Hw91iJYgWlsLo`C$eeO5iUMx-N#;v{g;+lF zF85r$6|u*(c%lTAY4>pH=Q5Yk{4o%PklEcw6C;l!9|}8T(R5$j@@^Gn3h-|#)q`I{ zD)02w*IV=3xY)4zL|9!YR43JRmgnsz?uf|t6IiJ;Dy={VN6%R!-~s8TX2#}USX$YG zr7g4PjJnu8Bt*y+T_ar$BsCA_e`es5IC1=Sc>R+UGO~)bQw?9P5m2i&h_-sPnBj_Q z!Y}fP)=}U;uwjS2l6vOJAe9p>lJ|**#QrE%%4HGot0j4%GZOi1SZ-2fjR+e`oAkuQ=v{g+lp>?;S;LIm%d1m0p>Wq#O%bO_!DUiXO)VC9%r%Cnjqy zxs4Hz(LluDF#>fx0;i)l7R?Y1DbQianak*aQw$lu>e^|q+xHD;>(kZ!1H7Ooa!y*>#q982YgDE*78edN6(e|>J}N5SxlDl>uC;5HQ|^>sgB68RV|wnXEc zMpc@9LOunV?&N-wEj6wcK;fN9IK`%7rG}S^SK~O8)ND~g#ZLHAwhV0NjoPc1>i<*? zs7?$z*1~LOOlQ-AFs{jH zhHGH(@uoO#)XaPlDUV@U$b0y=zuHrXJ^OOEnX?OSHD=nyCyiA8NCU6)=UB|dFn%iN zw;|7SqHCx9z=D^T3tru{KOODD{OH~PYMJTPp|{jZ5nGq!l2G$C7S zq>-4HzbQctfest+>Vm%6ZIm@X`;k0O8UMzL&4_+Xsc@ilpDTvPZAa~F_tl9TctV5N zBZ+D1LEC7-y2R9wX)l7$N77MxF8|?ZlY1YiCXJ_eXDFJ;gHCcU0ESb~2gJOrZFB)^ z!tB#^xXBn2;l*E-(K>n}`?#}N_WPDXf8jbz-zEjaZ7O#qRMeZ4jQkI<0%U$b3rFsE z%tjT@K?=4l;`N_CQhB?=TreKLojnyxHCPbO2n&;ae0`0|bdlLuZy?9i;f=koS3r== z?taQdg$Gzl)uGfTT@Fx>5}EFaN3*#%FsZS<7(`4n1u*$mI}Gf_#QpQ<%riCk6za&x zQjmlWx^k$%jrkb*)zzsCu#Q^8M76Hp(VCQZo?XHkoC$&yeq4;CY7Bh3 z9!W*m;6lG@yb9BhatvLboCp=#A*I8$$2lu*gP}X=`6|%eHBInkEj{g9QAE10u zGxSPC*eF9ATZRcWt8lN85*0V%#fu+;F5J`?J@v&ISAdsne3E^8c#9!}Zy&m5?`Qo( zsE0SAn)JV$hoBjNi}ehMn8C_@oksc|SHB0@j#0|AI~N4j7erXuPzJ`N=spAvgy|qi zy#?Xp0h59uwr)7vE#k~)Yc!@0(%ibf!uC0)8&i4y1H(ux2#1p&iVl`b??|^4l0Bov z?rXTuX}@8odpP>b#ET?(R8H=*^~l`=oxl9=dxhzChX< z%(^L~Mse9gm#-3$*E@{vu%jO3hi3SX752B5TMtRZ%QWY?DpBY}8R*C{ ztWnLt;q+e&iyuC-d=%No`(SO#3UzqfAQjvY2()4Ome)T+y5 zeKs-&yYPBRQ={1V;Zp$+4Bi;eaVNAdRLM?y6+BMkw_RaImsnl;8%fkL@G`y$wF9q} z=a#G=0q31$ZBCzv&wd61Q*(c7ARK#JdF6f)6cBjjHg@D;-#!o!6f6$gb{iaer1=Bb zlk9=^@^bGo+$gl91wrFUz#$$P(kyKUa{^#CM~WcS{L^3T(&+RfjT23{kPiQc4%H3K zW+>(y5Ct6EL8S`-Nu*e|BmgPz6!)}$tQC+AJK-dM&ihxICk#c{(Wpwv91#>R3MPC1u<$3Z z2C#w3QfHLK(r*uEWLel2=B?qs{>~)q$i+fGl+ij*Cs$7#U^Rw{!v>6D=GZe3OMs1? zaivk~2`QhtB+7}%zz`M;+F9SB!0G?al%^r0?zAw?li&#@6ha$zHruG&%%o5S=9aVq zcF!jaj^TTJ1gcpy%G1uoNfd8XrjKjE8o1Wj8$}}}O;rTu8S`_&eX+~siFbE*cuNtH zRDQm%$2M(KSFw%BE+DnP;3Mdf3)vPLJ}5cX1r`J`6f!?IS-3sB8|@_sVTZlvNA3k= ztAc1Ln$briHTZ$1H@`dX3p*+3uPCad9c0O|X<+im08g6C(m8!YI%Zwp0|Dx6sBV1N zV^a6uP-fG8E9$4>9FjqIQXRocz!PZ42i4lRTVL%3ZxAYE5yB5GMXBin1g;z{O=aMK zFYVj>Xg-WF!w0$TWKU#S;+jEaM_g1o#hT5b>D8encxR05rHUqBfsQ=TyCEgXpn?AJ z)`4mf$aI>!AdQcFR<`sgQ4H+S4^HaqA++-fRz+;+)Z0bK?aj@Nyu$?5E%foNI*++4 zb=v`y)YoNdG)eq-FYnFrm`r{C4+$cd|mVG8?{akE);!Kkua{jZ~$zrN^gA z19C%ENY&Nz2|2u!U`DT_J_m&p_@BR>#VpR0pGgfocAaWN`kN23^Pn5khQZf4*@?%|g&!fJlr@ zit%3=)q77z%!qMo8tG0E!*n-#xn`+T*tw^Z@H0S&6X;$+!ED3rHc3v{*rUMtapq8y z9HyII0`z4>@;(+VBdQ}OznzPi%M6GkOg-DPXNI@*^*z5vHLMFS>#L+`HU{`Z0mX{@C4GGh3`4+uomO6-mv?XpmdVL!#^pSoALOflOZZx zN4IG@fxy+Sp^=lO+!EqjA{n?_J+MSSP`6?6Ht)G>uc+d$ba+%_#w^P3=i@WWo1Q2l zlv^8hc2>1$T?g8JjbPKi0;;I7QabZmTU#p)))!qVT5tVkojG`c*XwsgwWH zq9dhvTj7VoBijpt%{^ocR6};M)`f@ycq;8`9bEdNmkzj$+^h0A*>{sO>z7JxSx+Y1 zE`%GCRU$Xc34A8|tl$FHUBC`X*l3bMUQ)Ufm($1L7<=~Xo78j3qy?m27_t$h{=gJ6 z99x17^?)INr$=nxf=I+bS?EtVQCm@(DmufzP<$H5r0C%Tm>Ik_3E;v%b}+ZXsGqI; zm|FR%S2s1no0N#1_E&;AN?u6Bw?j-HG4P+q?g(y?oDp}PuJR?sk_t=Kr~Pe zOZjr&&lZ{8^Vrm7VYh?|*_ZwQ&S{*PPQb+#srimj0r7c@PAzYr;l*%ixP0@h!BhM2 z1t4VQPxxT6U=dsFe)O%sFa%HN zM6=90LsOueDgnmuA<>x}&CntUAvKdRKF9UTC%a5cZc(3v;h{k_QwSeZ2)?2B2#vJ9 zVIW6Ei(9Y?Z=(_Wax9tL2@9Le(iCvhyl&c_@e3$@-E7xm>D%B_Xt9d&PrwWdJ6q$m zELFGai-Q zPV3whwLKQ4+dT8lqe!^DAcgB);H0YQm5{#%^ zZ7TOJI7+7O)@xI~wwnz9hB|2g+B^ zZ-4@jgL3{zHf~d1I!p()AQT?#r9N_i&Zj@%cqRN&(bC#kN9xSZJ({z+xXS- z^t@T*8P%yf6$D?fz4OE)ak-z;K5*L7Z}-QdQB-N!Hd5bo3nxI=gia>i;g)+ze248B zI0+;iQ3*VTC$njJI&JQZ){?nf{ znw%{x<$v$H-W6W5?ePi8$-Iz90waIg3n!{*8fi&4q95$m+gjgA3jfaWnya7g*I__0hff z+{1@>EV2x`LxQ$Zqxser`l1LZAGQLJ7~|em3dORFd@zQonVb>)957ky=1eU-9){yqr2>yg-l^6ZpWRl z9)HJm6$O5A64C^v@|Me?4b}X2)5ElHrt^D+Sq9h(qSji?hfwYK{ICQa6YJ!^Rn#2k z_hc@5|K>0u@uqZnqGDT9J|{Vx6Bo?hM|wag#=eF;@8u%mHQ zyIAvv&)q7`yFZKMLQSLQ$JcL|cR)ax!NZKr^!72Lp{UU~cLf!ZVTP+sE|>S)u`<{b zhSm!eSJoGzC47-3))^lw~Z$2F$e*R3h{khqORKK`{aZNlR1)^ zQOHowFe{z!mes6U5e%5*-Tcy}(G4AP8A({K1eQ|k9(nY3TS$vjl&Wh z_!~{k>pI}J?n7hX{w}IO`I6I-v23DJK%kd`o!AkCt6>(71!m`~usbMgR_DFTh7+Kd zV++$iNQDsw<1%OtwD~;surDa(bl6Jq`wqmBGyg06wZ~zzgx&l^x#$YY9efw`OM^CZ zVg)c0nKk^H8%MNXPD1@dOZ&OGc$Y~^{qSjd*LvS-=5FSxVg;Q&#tLJxpN!^3^o9J5p_jX<;>>OY)zjPG(-q6i=@^(NHpdJ={X4&DgE zpxim%?Eb-o{x&gA3JcHOPxdeTNmts{_xl*OGU+IW0htWwS-9JWS5O5`|2xo9Z#gBf zTyM9oe)#X*z`Z#3?mPGRcFIEdu7^|9VbGt{%7_M{w$GRC+NH4v=5@9&hnW&-pWoMI zWAJQeOw|ZdIkMx>`g;Jg<8;HQtq?K1rI$mw2$^xA$xuSLYH6+YqVns@ot^`X$OE_2 z-aw{sAhnqnQy+Vb|4!Q>s-X=!zrECFBsDMX4=y%{00DkP*ZPX$ zFhc_|R#ezQXlDoCkjg>=FGVKR@AZL;6yF0jz}$p_rJ(ih-|T1EQ$o(Hev=v_4x(Oe zF||KPCm`m~z;m}D7-OnTvx0b)?^W+HqO{YMA#sJ$_}bEaw%R;j>%XD8l}i3*)^?|&I#eKG zCpiDD=cj*_E)b<_22QGy<#V3who}@~$A1JTz;1`wP$`eCy3Fe+=gSi(^LugVxJo$; zULb8E;rEN*=`+fI{PFAU`D(nE9hI18hwLP*jZ-$XBPZF{@Z8lbQ_`3SV7_Ew<%`6J z+9SRO$4N@g9W2*acNPxvP#Ae8{a1~##!F`C z5fd@;LqoPCfqBpN-znh&a?wJ0(ANl~?Xm=U0`pS9j-u@Y}it{rB)AGMu4wu-aTNPbVMrZaJDdyp76! z5}Bdh1s_0DcTTRa?XAk6tjz`Jh%@{nN604;?d^I7y|o@2&LcbXcYN)}%SyB7Ytxm% zfP;$1P4$8SXK7PbnkPQmY|!C!KFz1Ji?e#0W!!Hh#=fA8$-%*n+ZnxIAW^<6t*Zj1 zfdAfNh@G2xa(?~7UxJ?_beID-l#aVQ>|W}AUE9!*oyZgn^g%aBm}=;hS=I|&)N{m7 zrJQo1c#VTHJ02(%>%8*iAJ-)xCOAhh8c&+Ditw1Ck9;-zRfa5CNeFLQ&{H&x&BCvq7|Zj_lwRQ-HV-D}Wldzi5k zE@)vDLI_^&A|v5HsJ-&F8!}0l)^6kc=`MRB5Xeoodw?diukgTh%MU-fKbH1EjE<~p zfKt=~nH`nLU&6%r`e2ePAXQ@YbjZ!{RB#0vuB<2PU6eU~wTOb|qyc{>)o(l1fQv)$ zANeiIFl=vdjLM*c5r8%?L4xMmQL0rB`(Q1izmny2k3TjD=3H~9oD{AI)N|hq#z$<& z+vUFWH16r7TBA*M=p(3vUM6;15l#@bU>VzFKA4`7o$wQy4f+a|!CThfk?wO~c{iR6 zPNM8o-vhBd@;L#ovyeOiEjxOCR>ed=Oy<{W%06MTZq%fm*2%*oRKnsuBWmIP7~=Sa zJ|6|-_*LFI)+v(R-cJA*bpy!jQvxfd;_%3=2bLIJ9@!}A7gVz23D_AsatxtEa7a)K z2ZB9G)bk}sqt7HBL-VWJzC{IW)VY96L-~#aG0ntLbn-r_h=wJ&qetH@*@% zBmjA;Hu#XAAZcsb@29zS!BHj0hSnrb>HO;xw3wnwwURa-s1eUE@+;7)XZ#t>rQh@I z8dsb!g%G%MXI>u_N`~rK{x@d#uTqYpk8wZY`-HaWLz$xeJ-pe8QRed^`Np>iMxhQC z&k`~Rj7IKV-qf(q+CDe4o#Z7xkJnijgC>am5mRwV+Xv3wn=CS*2wJ2uI+*WgB}n+O zB~zCVbkRzB3RIkZ;r2W%eG&)J*9}5Kw+;me0U!EOsZHj>6$>OL+~Rgp!Ar&D_;lYK z7ZB!oPa=fFxr)BXnwZnh684GW=eX#lumy=+VbFAYl{{pilVPl(-p@R=qb_ z85xp1`20e%-bAXFpMiL8tK~*isBhc-80UnMqOt_4_mp}_?c|5#yhJkssgtJwTSf|X z_uJ?DQ+%vQ=bRddbD+ybFP@A==X;?fC(p-=495-fD(fq3U~JB(|628xzk5Q#ue$Ju z=1XUcaq7auqA5ml*^afa*#q|#6Q2G$Iy!cXEPT~L=pFopkudsN_;Mzv!CKlK%opHv zcCQ71jf!H3#|YEogmoYaA9za*-S-Ja1_bS&y@Q0UC%vhXFv6NY3Bkqr+m*Z?0vzOA ztS?W=)CWN^Ve9nJ>74THcP*6PD$-~{1Z!#3w7w|;^R)x5+Stt43 z&KKNTnQ#8_CKS`gkTNl%70RA%BZ1R0ZqWT}Bk%-eDt9|F`5k$~6;|IEzoocFI1VWe zq+Y*J21KFi7lCcyDSCFF_18wkgE{nD&NyKQfsH$r#Apy1Z`^T7s7cBL?!^dx#ppgZ zgtF5~kT@bq?-(9e8V;}?hMy`(n~wF>?fK~m>1`Yv@={+zSvs{?MTQT?*o9F|FQg-0FNp}w+!1xaIy1UC^N znZtFsMv=C2|g#d)Cv2KuUwi=XHD{JeV7PFdx$~$0cP(F&`2d zFBmffR~o)&RjWK1!+e;8eB@VBl+k{^2krX$dN06XF9ciBXT)i5vq)LyfV*LmwOHMt z2wGqV-%7!=#R58yUcaq;)vuyIcliqjo`+%;5Lk!EgI?`7xG?PRzQYdkgJE0xb5$V4 znd@w@R9P>(L`4+ng7+%q_snX~4RcC&}C+QpyFYns-wSCv2#+PLvqYe7E)n4ziuGW-B_@N7RBMf)UlH&-f!o!6~9 z1$|YiCO_cA)TY3LgbD6fA5^BnLn~yz@kti9)xV`%5j}Xs7m^K-qNO$iSGGpzAcdUQ zmHn0MgV1*3f5bk9!FL0SQ!$xs8R%n( zG2o$>rZXrHsz7d8SK})%%tFXLDtZ}r@D8&IKiVMG{0%Y_^91Jc8jmS6`p-UXi}_x( z2r&)zcuY|KV=M+V5g6)J_g}8Au24#apsxy0R3C+Pj z0Wxe)!i&Ym#})IqemO}&$Gm>uxh>#nrh%-#4j=b;lp9S@_|h&CUBJWK@4av9rtIH| z+~N6^dD$1gA5!lEC?R*4?6+)HBlXhKtMCJO&p_KDeCS=+e2~yQ4d6=ii?`w{3s6!G zC%xhT%2omTf5Ed&yN47BjkF_oRAC4q?+^lSR2T}8G=0dw68U<9UVd;-Jzg&Xf>iEj2^6zv-H2sBHc?Iez@&A z@8pWpc8uz$b=}la>0073E2Hapdh+V=YH+Uf&j9PC8ib!8CPhoF!R{-EwYU2UFha4@1}H5=>M z>WvhBZ;lUuf-(Ft^l2|C=^WV|=Q_eeMAsbEM?0?U9 z9rda>v-kX6Nx0I5B)BL}S>SrR(5 z30ydD)t!4ozhpjH`Ga9L*rxO3MBnjRF37{vbL;ixi5bT6_IU9f5+$=;vRln<%oeLD zE~wMOW5fA|#M0p-et2H0wVG^pvzsZ?`QWrS;y?I)#^&~RAO@vGrgo#_7JM6AMDJ#I zsCdmgBYST(>=_@o#AT^s-{*S=T8rtt zU%FFN%*^PfqFl}gaX}cth&VsyZ2|%V7iET==W#xN?=XGGSmvuu>}xF^l&#Gu3-b35fOkQp5^=USo5}5Hysh#TW`Q*+k}pY9UPeza(h}Qqn{8$|(rXHK0n zg(`IJWq%ON$3PVsSGv5E^-%WcSH=ejlgvVTZr&VZHh#wol^TU6UhhS=^_^i&ktUUG zt%0^Yl1=;F^Mlhvj0P$RA3RoHm&fJKW+etom2?^pyC{^VPvsGZGgH(V9r`;<3zn-i z>@|M-Vm21FGx)PR&->x>a=p=Me@+|LV0`eTHuC!ZW7NxfyI(AYc*KUvJN*hjV;y-c z0j^$tP|Gw~I-oD_rF3;M4{AgDd<@XxSU$A%ndVVfOTrRqRF~>^2FS`+RyZ!>XncBU zayiPRrl0_GyjLPvX6IYINJ$fASG=YMv34Sx4IWo{*K6;%C~y=4g0eqeV2OxS_egnn zkm-owv)iNjv!7?vdwPwI!5#pwbl*haSitfaUjQ9Y)#}Rac}arwhlj?cSQ0TP&G-$S8_fEx)|5}F~ zK@V5^cqxT{%_p{@-2Q zM!iHKG3xeW{*7?2Wy8PWrki@aT%g79@?NyUZ#jcsN9ZbBO-7O~ielcbpbHR>2ATDY zw!heDbp;^^){{U!+<5}23I^ryVw~Mx&25KNWH@AD^3Rj*CY0_hwN=YN2463nmqA;dv<1ksK_pz=iZO9$zZ>!6A`XTyVZ~Z-QU0xEIl=8%8TPg(^{l$0oigqu(5=K8-{F-mI zX`X`dxX!Pp$Ne9=Nqf@nt%vqEqMaRYM#TcI`9mf)~=ny!Kr)YT}hoLpyE1CSh>67u8H1_1b6XO1PqLDF&XKHv5mBmshIT*I@dX zEjYpEJ&EO#0k*nBOxS}F%%%A*IvX`jlhao6GJlDy>vrcDS!sRMz-Z3MOSiwbD|69a z{r#n>!3vH%t`NM9Uc^3hKkj& zPvA)CZ65!dv>9_hD%v=z(x)`27XYVeTT96rJf{M#6YPC!PSxCE@CO^z&8N8rd_7oJ z=d=@biruZTtsj4SF(VwaV_P_t>qA`umsfpod07_-TYp6bKN`)peC%k4UeLX{uZ{}z zgWN^JuE^H~@7Z#$O!;_wABUcNKRZ3On|chp7Iz+^ve{r&fs(edIJhjbRUlw&sq5^IQ_-erLn}Si3bRkp}#u$>dX6zk5uMW=RH@J9+(Ka zw?}6>JRw1F`vO-dY#ar!+n#;CXr66Q{^`c$AA-zGwY;YLmy4TOK`*IQdZFklCcygq zC&tvaK`u>(nxX_?l#1?u*OfwxuhRF+L1?QQqd00nDWx>@`jqLV2zS=*Vp?ENfA=z~ zNdejRu!x~44Ha2rU;_1Y@X48|;|PjbSqM_lh@Kv9f}X2?Nd{Ru@CoUkznQy`c(eo+ z+|-e3;Y#U*K>q~A(?!~0e=lW^|Fc17B<1%j6@47yV32B?e*M*wm7LcynPv$MN<;8| zoUe=PbM2@(zo6+De=VNKjXI2_FLT?BnFM0>KiBKzKMD`P+IT?V2nq~r=Oi+$L+(wk zS}q9dE6-f$V08`I-qg?ju#lMmK>n8gQ+{~p{v0l?(Mx6Z;DAQ--(nDBe^02K)~{r~ z>rR)rdgyrvev)z1t@YOM86)xbTPrUqNqtlg5GzU2$MJ*DJz9CvZIGB)@UY|M@&e*yj`7w^pZ0gHw*Z0>pGY2U>V>+kaSeaaJi!l~MqnMr}U ze#rIKtL4;rH7t-YsE&BHRHuw*|7E@;D-I9-;>qJ_#vSxlF7eB`|65{;Mi<{N@~M0p zcE~(5x?@0)1Z{D~N5)st)U=(Vih3bpN|4Fin46YnOwLf8B^SE|={%zcH(_ZMbN~zM z;SZvRCj^&AW!A2}e)d;l3RfMI1jhVg*jF?oki6I^ely=c*iV&iF6ni>GJ_^XeFXCc zf>RmqNp*>f8hWtGa@|R0*iz>AC<=L;dEt`+TUAPl?e|1m>oIz9P-ifa>1j@3uyJtI zU+Pr6b=Ug0pHdAUT$LPq8q=|II8JeU@VO6 z3Gjs&?bhZ>OJy{?*g3_uS=^e@70T&AeXqxWK`2kE&iZY{qy3BNYGXiH# zQ?@^JY6raj)Az2vJLLS?M9ulOLLY}82FOs4!OMbm6DD!zEi=Xl(jGC~h@Y$RJX&EX z#~?jMm+6=+_KR$*@z`R?(fJk3&x1F6A3eeAD6=e@olY3Y_a7Jt@_i-mokrjpGE$~{ zJl44xJ&5OGg6^pgo{}e4hXj7{-;4I`$H6gI0u>bk>nZ~$>sNOzC7!}j+dMq_D%{JL z_n=5~$&><}E(Qz)Bte;<1e`Pj#!so^wxZB%()8apdbHo#r!Q_vZMvUiNn5A9ohh4e z#eMg$T$8HjLgB&{>)$-ypmo4-Iw<34@jrB$+z?J`sr`m;YaZb>1r+GJqRP*fJ||lM z6+OsKpx@t>nQNdQKA-baZt5||L9pCfhrMqX%>;ixcv$n_?`9*A3AsW^EopcytL+ZE zsSksfBU|0z;A?S&Lkb&e$g)9c9J0cLiN2oRE8u{S1@fXC8j_<&pE}EgW?51Sho8o7 z9to0-ozhrN;i3=+*i zqo%C3;4ylj;z~F;qK9JI{7(QS2-^30yWoccQGn8p6|2Gb%4h5A^ZJk<7c`QpTw0rzktR*K zZ?|T?1V5?B+@4F5k_1x%D8VKD8uOb0%25IeD1COW=limIXAm(7oH0az$aZ71Cqp>a zq^egJ0KAGEfB^s|+6t(tx4*0zf)1?ezBTbeEx7q|J(*{Rs`7iUZoL;W>*c^3I6R2E zWFgIAX3p`N9G|B4&bpj4W_3bZ1qALj&=oi)L{;wu_X!x=ori)$lifv6Zl7j0-E4l| z5rxs<;B;RKE#IxV0VX9e$Agg@0tiXX{9j3F@783n7qyD>`p$y?KLEf4lYz_Zu)_}D zmlbdb2SH4Go1WAj6ihSw5j0N=O^sqriI0JZn#XQIw~w8{hAv)3{&*?u=ehSXpL z0cRaLyRSPNk+TYI>eKvpW*rl3NFtiWX7748MrHW3k%UdKTcm*Z)Rqx|B3I2hSi71q zG~iG`yFL6IW4BvI0+*WEz~v|eLBpY8x;ae6Ccsd$$HK~e@fqh?mg)a|Y5$i0|I0}G z&mRC_l0l%Rss5lEd>mFUWf^b)JwMgIn_;HbaN-D)KN!z8k!GHOCNDJ+Ni5ePj4hnu zY)2;0%q5Uy40y1_xmg35;;JoMXs-bPuQwm2+PY}QwG(W|p8fFYl`ES(b%vkw`UwV3 z>@sP~$V)GS(e57KDZzxqWsWF{U1BoNh}%<=zK&&ie?_O_CzhxMRCZ7pK*NQu8;36E?qB}}4u33f2Dt6SNb~n*z&>+c3;pY93 z3}vE}GwVo}IVIYh2zw?n8*psGk2wXt2fjy}^G?(Xco_Ux0LH)BCm}r=;sPZqvDs#u z-B+6bqEX84ucOSr`BTLF4*Wj|7uTP#ubLEj+`HCiHANyGDR4J_jcoX!Pn6dSNhj70Ung*ZCU- zpQduoX}{gqPV+ovxeld{YonElKLRE5B5Yl`D9e*{5Dc8i1TuIt2;DKqm4%Fl zXF$42FTh~!J8set_X6l@J*UU|UNUGcbne<~L9+{Ft?3#Ih8Eoeh9fu}A-xG`EQ z)OY2U_tKVo?X}mwl`M2p^=sP{rgelrK)`x%hv~)^`gpx_&Qoz0D;UEjP{Ya4exDiZGj7-oH*rB96kmYf~*10`+EKcT6KJO0m0N!d{S9{ z*S+}Siw}>&e7}yCv!%wk{&p|D3a9^XGe_(Avw0GM`Z!mtLGA6Wz| zj+V>UKw5s6U3S^KmirYDtY6>N%P(EEmMgbpB58T|nWr#Yz_`7I&H&>qLH_1`fHLRD zPDpA2{VU7F!rJRi;KCuhQ3fLJYhu;s>sc%oZUcy|7+W9w-(XF)e_s9nTWR@UO6Y$o z6aTAP_fTp3r|Q_VM}j_UB=oZuw_7(T41+Kn#ff!p_do6C)a(yDAiz$L=E0Amd`n}? zdNTM&JkP-7%h06Dz)PMO=;6Y!pvs%W!$2>Y_~0G(Rv3KGH7t2ET?BKwWHj}$#e=Y>8uYUavfPoMDsjHXX>MZI4 fAl6k?RX6Gf@e(sXmR4CoPKZ}jNQje zn^1IiA<%sP_$|6g#xS4OVb1Y=h>Uaif{X^COoNZ{uaaFK7v#6QWK&tL!Tbk2p2(G3 z4PRy`I+8~B8#Q{)4U7zYmQ|Jx2b87c35~N+k!{NzX>jx={DjRGeuSKvI>rooBXPXz z+ll?L7g-uRvvYIrp%68(~NrAebrW2ZyfcM z?a9pjMbA{*@*aN=2gWeu7^i(-V)*@8EgA?mn&25^l#C)xSsn)ZF~?$G-~G;g6!rL0 zF;ZC19<{f|t*z{Om$*C(gX%Z&(n@m_q-pxKR8R z+EUIIyWu31s`s${aPY^zWJD8ZOG~xGj6};?R5goWfkH52W{Y4lWRc&_IP9OHok{u4 ziEr-Q8&uaZ-SwVl(9P&w)4Z#ojV<(3RzW7*Bxr6*`knwl%=F(2q^!w!0RRdC%JMSW zek&*0i1C6d!NlY^KR!0(6B1eweEs^hYha-Ghl+Xaa_!mO z-J%2v?otPN3Y^}_DA@sa=8d5SrNC{7pf1rZcXQu~cPtWmHU9W3A{|00q z|C;h&@fX^sI6s2QPz*a9kOi9D92nfYS8lvpyp@+J18o?rXqj3+{5bvNosKuwT@oACho&j`lod*^bsLn4A58eVA(Yi?TLgI@mJxsAOlv@S6W15%Xh8wHzQb^F zhS+_Omnp-~9n9T`{GQN={aR7_uz8`rtg`dp3hbn1C*ftuW1JH@SN^XSH5PzeIxd=g zJoQPGdcNg?t^01E6puEJ73Q+)>F$EYM*MEx^-zPh|zzM94+268 z0y_)Im4mIiM(%^UzQ6qs%KNd!PTJCU(Y0;yuXEk{&>kq=dO$B@2Hig*L-xMu&hkUW zQ*u6@iGm>wk_Z6(n@o|=ZF2dr&jv${-Z$?GUjZ`iSj6E|AM-dI{)`;1v7i$rbk=Mb zHfBy4ZTzbF^qy209UYv7%L(RSAbP3FE??ii`ng^TGs~%-Z*k8y(hDU$$mb5RG;W!e zfsD!q{+-vIyM{{TUa7tRb9y?j*XDaVEr;{u+r$AQt~wGTuS11=h`8f!-Tfk!9e6b= z)mt=f7-*ox+nJDjNDYLsT1unU>kr0Y){{E#$44oE2&AK_y`#1YB4>ESjB$BzgfnMs zs<%jKIFDW8_z|xQx}~Wa>l{y7{_e*|D(;`#Ir9jWw4Ar}K#YzB4YCg|He!CJGn{rk zjRp+#STtDqN#55-#TVM9%qG#|QrUm@+Sq~5lG5)c0Y@~pvAAWE5Xib=)2KdgSUKpvJhzVcZ*-^Xrt-apA&5u{0>$FU!%zI2Ct=t(Ro$D=0%5mhjz({ zhA!>|CUtSM+|8M*V`gqh*%IIXCEY4b9J;ZD-5HOxdT+xvvkbMU3B8V)9K2VambY~( zp#8ZUi(wm-v|a_?LtM#5Z=bT)Cva?4uB+v+MR)1248c7w(13K$?HCpp@7?4#IbTU^ zy26C%<&hq&{;0$vGHjMSTmtWi5S3lV+7-e3FflF7VY^2Dm??suepMftc`qEPDbXRW ztaN(PjmA2D%top2iT(b~*xLy){Nj;aJt3yP>1mmU?_J0SIr~Xf<1bO@(0R>dbgONv zS#g?M7O<2`wj*fLC(#!0YCMNL5WMAJ8mY%V{14Mc=&QlFXH+^1!f*i&48gw~_7RL`F(Iu+A`)YR@Tc z&;!0YOFuq)LM31+?WawvWV2bu#>VtQ0wFtkr(d?y4jgO^Rd{4S?>9Q`lY=Cs0sT{&$qvlx_L*!ZBjWv-s~~`JOlNmJ1yzL5K+CfBkFBwvE$m@ zX=31|Yvx&dZI{td)X3l}?O5pjxPBfCD~5(VUz5CwV%2{SmUi6d;O)4;3Aybbgspi( z6X<_LVvq!QBt8sJ={0y(vu)*_9`1PmBJQH-5$zG3(6tTdebP^4>$sU!!2)f1It#bx z*StPhDcu|YE*xY_U-GOt9v7TbR4nK_&S`_BxHWahimTo6?!q#W*KaoIITd%wp`yxV z^s-camn)nv!z+^>C^3D2iangv6Wxj_V$koicvQT`T{`CD2X9B){D2^TQs#B0Gm=Vd zB}an5J}dK53w*qpc{8t`Za?ee%m&;98sM~A{ur#ybgEMh)XCYX3SC?y|GXlJ?gNS{ z$2* z?&camI&pVSCUmC!C=rDaq%mHy8`kj`#`~zG&U4wIo1m{Zh*Gd|v=S*`;7dh{@;OQx zFVM+h8++p2@GNosA_pCl%XpPht0phj=i-lbSo1!_z)`P-$!rcP}s+uW1>T zhT7^-;|DPk3#99;=`eTdlD0wrHYp`zXz`g+@G(8LOd%oJKOO#K146)aeZCuyzsJPLgySk zIyisX!U^%quY_zPsMg$>LX{ikX#(bTgUiyS zn0pB)TzaO9JYK;}S5sf4 z^|Y7Y)S;Rm|CE;&{oQa=smcR%!_*%Gg^GU_W!3JKwHUjm5Q~F1qlPM{r_$e!WJeK4o>u~&4s*Dn zI<%`kP7md|4n=+vy18VO>Km2Zs+k;rRKEpEez47ju$Bodbbhhl`zeq6nmn{weNvqv7AVVm(#Lg(oiYa{(SjwH#j>6+0z-aqlD|vo z49AC&H<}5_qBB0+A6GAY3-0v#i+oHY^(zH1kP0l6794hfG3LL;1LhLZLFs7(XZCGe zgp|2w)NgkT9zuHszHx@@Pdp)^qc1v^^9f`{aQjy7BLD_hULI2a#Bt}VP=AnsaHX*6 zu7C4Sda22RbfvjbMHk4DlXdOCjty9`r>&^ozY@Z?*a898CyLBPDMQ6vM?hD5n`&Naj`Iy<+2TbS5 z|8w}(O=TW~Qi}Oz!;en!F;h}w#MnA`!>Q&W|VzV;M&Ra=DF*>(vi-Y0&yNhfd z+?;nmWZ2D?1)^cAEx?c6OI(87UgI9gnm5@Vo9;w$kf%0wYXp(Pu{6w+r&xN*~CFR=anT;;jPN0m>&p&RI@mk#TZ@ph43GfKImka46 zhf5SR4kk%g4=7eqYmI}+iATshH7x+`qi+bjHzsXF#OBAt?XkY;nim!Nh}{H&S9o3w ziv4Xb9DY8Xpo}Rhy1ErJyHyfRqh!kI1S+g=H8_3?zUO8qf~ew&s${s+m1xoSe>g-W zk>%cIvx1f$6mrCHR*xudiMUjl1LBj&9qraB1}gk`FNMa(zoXLdP!F_Qq1_5Q06B%` zLEvtGjPZYkNt&g~f4ij~BFfzYu_Jf>>XsoOZko0eN1yJn$g3J7*fSIzEustr?sJ^@ znzo5wK?^)%O1>{|7d$@MFS3!b@dAH*e)fa_n0tdvwuXw-P1lP~()HS8 z=^LW&D!4SE^(7FnYA(QKkB__Ff>tZ#pQuO6Dg0(NqFxO9r#pxVm6e74MWMV~0Bq*j zv}g77NOnJzJt(JJ$kfF$iadTzLTl3M5vG@D6ye^_jGOy~X>-1q4IG?eO`khkyeS+6epdW--^p{fgtl#3!7Oh0E9}iA--YWeD&ym{xjdI1w<9`F=8YT{ zLVHlU_*Z0z!@bz*!1c=y!Gw+gtcYr(0THtzo@$m1b2$jD>+NSMOep4&j28h*|K&fi z?H_rP5?k+^5uT%d!p!tGI!n7m1PF#?`Mv42XLOBM7hR`el(Yw5`I&NmI)V<5AM+;= zMwHvyx!S)G7#56Hb-q-+#WFGLHW@Tb6HBcfrUyTKp6c@6HuapQA4>*HzXI8_@(N75wB!OZH3xV(u=qj zIlw+I&+X%*Bs~YIv$63nQVq=uavYTVXd#9l5{wLuCzGMC*@z&Nsy-NoG1fXemLVec z)*t_!hEasIjQrbS@zN>{e|oUf;p*F{JrO}h#7ZFzjAwF~R+&ED+^AmWn!|O-p&YQC3ON=V!ytg5i;^Kp(-Wo3LU*M=y;D!_?54E z71$}6bknOcOVJLi{~mF=EBJ^mHPw84Gx6b!FnI9!o>TA7FSf1t*B{>2p1A(r@)h^o z2NJ)eJ|jt0k*pLZYPcp2f8WF=pC$>Rck5O4D^f$Ee(`G`WmMxv=FGt$6RiN`vBKKh zUd9AyjkoN{liDnQiHwZi|E)-xw6$BtDB)a{4jfMn)qRL^6I|`L_3|dJ$IrfG*UyrO zGyiz;n27&9T7nV$G8S+_dEf!qJ5k%vPMyscY>-tpj@9xJL z3_T{cy2Kpdfa-F=KKU65S*i-$Ik)dUNevZ%cR@vNW zp~E*8OT2AXM|)Gy|B#1`3h-9xA?nU}A7USW=f0(yED+tqXCnN_DQlA_i7&q1W9}xe z5L&umm!~hT-b72Ai6MR1SICIaT8d{@U!TQ^>|r=g+33@pRId2=Lz`F!r%iMB(L1`u zi!4I@%jHCt{s{ZcL>Ahn2<~<3b+l1KvatrkgpS7dH8E3@EseYTunS$M4n=kuQ4g_rfz;&dP9E|OA9m5?EvM<1SCw9F?fTB?XDi`!2 z#w=WFVJzH4kID^%P?T>9!Q2bd{vtpQI3q|^s) zsP<##A1M0fZ|6g!n8r8b+st(AqHxR+Doo8Oc@Q**dM(ZTuTw__POz$tdY2?OmSp(_ zutuho*eJqBqK(m%ULr_IfElSM7hj^C4h5d`-z+wF%AzL0GH!)7o3kaMBjXsnXh$;? zKMq%`U^Lt6=>ebRZyQ7}SU(Wv_AOBx{4f+q9S7@$DE3>GlF zucbafJolh(vFFjweG-&ivu$KXm2%FGCKXSKnt%9`YoscE5k87-9wDDiI zr$H#SEia;hK!UW=8LoD3iX@SW%9-~p|0*ZhPfvs!XMJg=AHc`eZ*x}y+?vs`W|USRuG*Blc^{D?;C{aGb`HU$F%m* z(KTgyjb>ZKG;||JU%*B{dUuo7APahgI5Huf4GFi*a3Ge>`6qGXTR^U1kdMa- zAU}Y3zGp9}vXv*!+sEAqZ5|;)?s&KMK^H$6viYfQTaalT0Eq)|w^l|`_!2IoFf&DD z2`;WEBTz>S`JJ-NsYI~3UzAFJ)u3K#6Pw@LWh19i2c8F*vmr{RD`7Cq-uoDFIUzt9 zB&}oiw67IP1Y|vm{z%6nFNYF)f&<*Ad6*H3SJeI8UW1QWJZ909l-)AY%af<_k*E9l z*2(;wM9NT!AdUZ0%|yk$NW~D`vtdrE3yue#$?9eI<@Wy!LOC#m(A{3~R|GF(Hb5PZ zWp`GgG^Qq|rrpyzMvQk)Paj>czvYBXOjcsFd_%7!;2Gw>%(}o7m^2$^#1(`Do~L0? zZ2akJ00~02Zd5&L$rdyylGQT)4p-wQdB41zd2Vkcp?)PVlZ43d(TE|*5+nUplQyjC z9nduEc$^M;D~}Szi@79q!GSVW^11b=QJhl(>UlCG^F8nY+7UZj) zMkvCof`1RSxAPGU9dzf_6~aO>#e*7QyIU}w??7yeSP*;p)AO_K0*+}D&ey=4sr@Xu z^lQ*@Fm6AjG95HB3~d~#mxat_`$@3`spS_aPf{izAAb>Mx*5Z{u(!=)MHM-%2c0g~ zn9>lUyt7d6aY-x=x6$2>lZ4!y)zGWm{np16=+s}bkx>LYtV=!(YIWNpSQsEIUeA}J z1J65izoFalR|^XpiC7qTg$Q4>586M`(mn+ekB2TtPoS0AE;n~Bf^I%oed8JOrWS~J znH4q@+~jeRokAXotum&8_-%V`2o8tnq|u|=XaHVf-PKx5{0Ow_urvrI_-zCle;zx+ zjEJMo9*+*9>@N_9Vnm#{ZtzqF^xT|~wzGcifbhH4pKc|nNHLp|aUotA2=f5pA~$DB z`sC-l;y=e^>t`kA@AK$vq&B?hN#>G$A%VA6U}}k{!J{5gM98nrK%r0JG8)&{;ibBp z$^NGnRMG)Y{>=f18GTW-Fh)<KTQ<}*od7AJ z@GquiFZ*z{gC{N3rL`N_F)hMgQ8Z{D$rInv1>UZo?E3#h?3hO#IO=hd7joKk{r2_! zY36H*8M&g|`zQTeIhiK5Et_dHk#2qc!fefssNl*s;d`AUD;y(DoAxh@0iGgd?E`baSvj^S`vpijSkq<+H-SrJ1xnrs#(2%=yrIW zNd@`y-P{hT$BWp^g?PX$iD6KB+YDFoM%<1RFV*_c^AJ37%rPpyc}S2AaU=k>=UO$0 z5&}+2%a4r~FIs86g6zv43<`cM#N@sG+x&f#Wb~8+4$=WXiQeNWwb-RDd7n($PhG2;a~Z-4(0KtM|sDg4<}7I8eiQ zI6rQYp};*HPbZSz`P!G+wG`8)*$J_N|L1$j#ZVB@)AuB$U&1Td%3$j22(O-2c{u9;VYB> z9miVv@HDue*P*-Y&hDrljm`>iWgsg3pDR0!B@~e&3+G2HsBA1VhTh z@Q}(jW{1WPg9yB-iYIig|bYt-Q?CEm7?O66G4xAW9oqs;3 zC>O)++Q*9W4GI2d1$H2-M(+`ZIKGfVM0yoWml3g^LY^O$1s#6n>6IBabDgd>Cw}?z zWd!GJqgx$|La0cu%%t;mwy0;+Y#a`aE){r`P4S!dslH{YjN@WUPYST`P8t zJfN6Jt5mJH^CRqw>F!i^ro;RXg}t$FS{PdMKR%QzCDU7BeA)ZXRs!Zm7UA!qHUjt9 z10NCIjx(>54jevFBTwZ@sk5=M30Mwc#*(Y&N)|Jzq)x-RLZDPpG;WD%dV%80^HaHD zvx|1|+i%nTrW^(+*pwRKGtI81jileWj8~#w)!w}9+|{J72FBw6*`VdrQQ^2( zh}5mw*^eQY+k*};hvmBB4}EX9cSlpwI2`DaA{!kSf6O%4Yc9Y(=NsMj7n{8Tjti9liF>`HfNyTyqtQLc!@vQD3n57`qyz=y+ICzP|p?=mb3lT6m!VtXJamNg##dVf+&O78r3r`%lnp@Sy1 zkxeQzJ*XvI#_QvS*Eeq`!2EW$ScQ!$x;@B$xz74#f0E+uD~%6jTBX?5Q(2;uFI~Dd zCSOrW#e6vY5;TO#mQf%uD={7z(o%RFa2rL7xf4@;VAbaAS40j;=eI4e0BodgZV-z* z-(?CFzYdkzZ?v01!0e`fFB<>!cvnyV^t&ra#rJIO{asg%qH2zKI-m6zA#?-hPK=#A zhe?OSjP(QkH<^)h5N@X4MNccZ7-`zq%jczcvyO%!yzXv-6G+8DPJUHiJ*n1`<+N05 z{sZ$nx2YsLL8)3cU)>XH@##t<22)cSRjMCNp=R;h*d9s|H(DxDj|iO_lXt?dzl(-G zQ)pG_RI!=}*iA!T`<|(m0|BV=8hX+31DiKTb9lWGeJHiRu2e}=JfX^4ll#TdG{d$XL9kI*jrw99$274W7UpT9MZy3_A zdRz6EZr?|kb`ATWD&9#i70y(M*K#>g1!kH?DI%4)U(!OQzMT2?e1)!+EWGzeaip6p zU^N=~vW#pSMan56A7Wxy)N{a{51CWQzlwWFxsYm6BU;TC^#8rO7FfeHf~k~6PUgtT zBYuWJbH@XzzL*dMKi-CFEb9~zf0Yp1o!`Wyt-I1x-@bn2;$c=abzyH4mX>GC6C;gS z4Prvc`|gDQPk{CiQMxG-qFr2fyim0xuxlEw>=p4Gl}Ey^j5Y>CbFe=;9XxZsyn&T#doGiq`=$#Rxo~fyvO&}t6@E;`H0+1e!Vk=k;8tfP; zLlfW*aSrZzR71}5L?C*(3^6vHyd!L&!mkL|MJRr-;cdubv?ySAN!P-QpWgoor_^<$ z3yC;8&o`t$3acxotKIi}cI?}$OV3x6(u>u`M;BZDl)-Pp;PbaI;!pU)Md!8UfGXYH z2ZIREf7Zi>{e6rl0rWx9-5=(pw4i$x@P|`KT-Mu=@Ko|}aD7~pf@A=CT9=n>sSru# zLcyE^xKAI`&3|90=(RUSl;V#Xh%PI}pdo2mhZbF^`T)-32PdC&|Hfi<&Bz}gN2e_c z)g}`3QjeteIaz6R)cS-?#mXw+`a5eXMm1&SiEA>~JPrElVUn@S#9`ofBK5d)$O z%>gu1%a7;po>&WsRb>nFS(upv*7^~Cc}->rYOM#7a2e$}E>@S{6vCIyqnr^9N|%kU z>;eR(sm~O`VP%-?=db`%7fMQFi@xiqohjr7%%gt{AM5@aac*_2^h`4_3ImJzc(wQn zL|jIs-_VO#V&lbq&$9k>IBs-xt{9V*pc-gG#SQ8##G`DHetP&frSw0WJmM;`l{n3A z>dsjmz^WuwJ({!ORUw!{Nh+W6^TOk>`)>XE%_EkFkT&6yrYn#H7s>l%X$E@k^4H2N zDBPTkC)$=n+i#Dxmzoj<5>w-AH#%o{^Fg1%lO822R6`wyQ&yHWu}y;6j#BCLrJmpI zhCnnJUt@!{Xeqwt_>0#f7AewF0ortWNLNfH@h|IaG;nk{tp!@(HfNcf-_qbyr)AXAgMNzQxjk)Q3xtRz{j#u=$iKIdtz1G!G4^RK~BY;CTXLL$f zA=P^K-7cQW1)egI^c(n^`b#Za;-e2bf*aW8BS$uY-cJi8Oo{xQa31Avmp2c>2-*k4j@tcMZFO5AthlTyaHJZqC*Q9K&KU;zYM*#eAnrr9`fo4 z8Dh(3fiVkPev%|BcLEqOxGR>oU}6qZ?<=T93QDaSf=9Z(lq&)ygs^BidN zK=gnHxH)CTx}T~@`SNA@uGH(9fLRqaRyV21aFdkQstzXH2z|^HV4O`z5T@*CBczqp zcjI8=shJ93G)n>BNQ`P~YO+#GOQ4`o`5jgnG``L5Bfy#d7k7ieXbyM$rLW_!N)RCibnH34ztK4h1Au&7P zwr2*Q#k7c9V3YX_G5P@VF#>Ks&zn;9Fc(?Pc0<{Fxejqf>v3;7jx;^@jZx5if09PW zC3f#1e&+)U@D8;ge6Ne_AVrd<%mF&HQ4@-Na?5G zoRQ98z!O>~g)x17?MbCrfl;f_MXSlHZ8^!m96E;=;{LEf1_@okA7Yc-Ighgg430i2 z3>ny3H3hv+MzO-Ey?HozEr3042Y9S1fHW{d_kC-5l(?pN5yl5H_tRUnVy2pAkVcsE zM7hj|3+-w~exkAt|3Yl&iFd>chvt=uv|&#@h^4h{47Z_(oq5nxRmtQ)Nd4 zFOU!vIqOu98of`&eC;zuUXu^KFi}CLU+w0F*sE>{5KKYZ5`24L8RH){UeTTQg7dmj ze5YDS5V=;_*N$qI<+vm-h$J+JoK!dZg`(zR@ADCV}uz1NAFgkIPf`X@={+z#aUf z9@FrPPng=snY|o6++8Ak2>Q+IREDz_DDh0Z7;c0&DwfiCE3Q*?VhESL>Yq00bRPrv z@mul1CsREQMdyRoG-3!dWuY5&uaag>MHSvWdt&E{%D^^$%ssW*=x#Q&bt<$MAP|Z= z(aUwmaH03%YJ6>qMh1@K&l3gDw6S+eK8Go}K734=wr9ryPk!CRZv*5XN1+A7XR@>{ z1WyP0Mt?s(YD_|KQ5b8tJNWob>6 z{?sp{X=IG5Ogz930s|1<{@nQZ_}uF1>fCg-B}yh9(q3cyYk;c(pd!q|@RPhQ7-)n1 z#`5|>1~bFMlJ15`)_2Z_==mj|Pjgu|A<$TC(Q)PByOCY})X$sYMk#bFXKEqEIo!D2 zbf@`fYmaHRfvR!8sz5|N*`(T&Sps?o!ODZ(Er5k@UTfXW_i)=o^nu4Y zz^U&?PG~yE-edo7(H8_f#He3qCE|NVNiOVyX$`N8*6Z-&Rf5+7x!qk^!&w1dH5{=J z$CL3R7r|M_f1;nyW&*WO%NZV)Bz6Togu&x=O53R{8Mug-5eb2w!pkH$m0)Mrf#em=!-55_SF2oM&ld{@*n(@;#L zHXe*+i1!NAU2gScp_T}UfD@rSnQ-KhuWSUw9D0ED-#_zwTC;_3a2i}UsNtk$_zROt z9*02#s{786Lc7oDEv1i_|7TfV3+aUlU3JMFxbR)-3KIL;>UmJ$w$;a~RT2;W11CW4 zSG&qBV||er*6nje%5r!_KhZHU!CL`|uXL;B;q-^>hL9hx0xsCYIzGbGz%_E8u$8{D z;=x68_++N=&RnVbdyoBcizn%_Rgg%Ge%g#mqrFp`2CF;!i*3*T*f*8ej-d!hn*wts z@8sao1S6!$Wjz+Ikb4M6;7uyjueZV9g$L5T53uum9z9l6LKMkQi)^Imh{13yaJb$s z?66qR5c4_diN<5ls5TOH2b+glY;A4P6?|j=!0v9y3?Ex^8XCBXRg|`? zRGg+=M*qy=EQ%0pqImy0G(h}>UVz4v6vhDwLP)BfA5yV+w1i9hfV$u1~ByB^Bw$5e}J=0<^cJvuejL1e)SU$bGyV~2U_dJ-Y zYQ*eIPFZynCcAu>_7HM+g`^Nm*b6JuF2|>_&G;(^+TVe*y7^37aJ#`|7%*|@DDGUjtPGoRs%GuZI=ms7&hBpzgsP*hGR2gn#!f_G?!HRd+ zkQ3$QJ61|MIy$}p-{S==Aw*5l-A$C)U$<)N{o3H6e>p~0Oc0aGK9kr8NHy=y;2G5oH2Sr zv3#vUggDzQG&6w9xa})jIGH>jyKcE=q)^W1hh7NpItEOE+OWwfok|{4@{DF3>)LSv zWVJ7Aj+BEP@z53g6d+(fD<8Joy6hvPo5ptbSv}Y3mHZ+Q|)=4TEbtOts|2nO$MCVUqdUFL|1wXLX2nHANr|bOZ*9BDD>4FiS9blAcwc)}Q1b4Y zqEls8p_Dm;;zH;kg#joOhfyXB9H_p(kI9FQt_6CH%?YVP9bL=X9)|7+DNFPa>GkY9 z5TKKG3wNP3N1i`z4`NKcwc~_@^`9R6EWWtV`x%wbPn`AM>@@V8f=w;O&UCx@H9`T# z+;f%uL#AfP!dFsPGJc!KvES)=3q<`Igsj$>Nly6`69#R))p--<9C9D?QMgc!3DCXK zzHMGVZd^Mlr&=vP58a0#16&hkH|@B(an5iX&-nuwAE~7u*#hY`iqgeDyNZB%fxgo%r+8?rFH`tA4jI(j`__8%8@iVBU|LKpYZyLYAku;{NC29XG}c zw-pj1j=W89SQ>Zki5oq;DE9N81R`4zqBv(%fI?tyOHN3`mJ zlxd&Bh3j77*@n0a6YKy2T00s9^qaqC`Z#K9ZoZfz5=%vmmMCV%xVQxxETvS?v~#Z~ zFQZa-i4}&r{)7Vw!l&xoGsK){uFcMVwc~%P{vzDlu#cv5r@>c#xWS9QMFX2S?z}<= zhOT(bx$U(Tz+=Z&>Ec~|E0be~d%XEHGeYgKu2drUv|-nk=L?y+QNlPmJm}h5byQO+ zKI31s{-Mjy8q5%iv4&FJetpzcg!T0D>p4dM646vqQQa_oZ#8&V5_OBJiXbd4as2H5Z}3Z5)K3bex{oPch|N8(P8Huh z)Z=U)yRzibu-SZobb}8~nr`dk!-F8_m&g-pO)dL2!baPjot=B1&TNid`sSR1W+28>Y)6tRGiZ zTRbs>?V^3EB6;#Ir;mMyPCRyX&mcnt?_r?Dz}s#+<0|{lVIZ3N6aU_hcuC#s8r~c% zNH0_B-4$aH#q%LXj{kQrPUH1(YamEeiTj9Y0@GI$rA~%19^mELkl6WH@zeHQmpK@n zuP4a>cJ?7Q1$6DzcOuYlkmbs)Bqk*&>k}d{n^LW-Aq$vi;mVvxx)$WAAL-tQlI4EZ zvyne-Jo1R%I4w3#EeIr*`fG{#h5MZKVPF#M-Gt@CtWjK%28TcxeV6;Yauh~nBjXKdwh}s)xcSS0$xWMfF+1HxjyC- zjFU6z&C1+RiK3bchY8kUV8-pVJttpEl#mx$kzjADY2sqN;3dnN{7R5Q`;})<^e0Pf z`%oKko_D`zbfOnkQ@m1fuc#<_<}4}Ac)pu4K}t(Yb(11{ofzvgHbb$M1guLky;S4l zX*1>s&P;0vgx~wDI|UX|Q+@KQma=a7+{DZC5s&%JmOdW3S9{}K zf3-YZJUo84!%#miOjPy`T~eww4wZ<_Y9V7XQD}?W_+eiur4S)g0(lt$l&0K{_IHp3 z>-O*Mk-dnSX0wie39EXX%k1qn-$0!8cORYjl=Hm=!V$BLz`YR%HlM+hu81U}1inqD z$!_}mf(wv7M<$*~(g_%t1if+N6WV)8+jcA`g7a1eDymH}+SWIG^JWT=@pFF?(9E*b z<3)cX0SRA&;SIpicvw*tv-$TNQH>+2R}Y zw-bC-UcR88`Ezr6*Ks8wQ{OkfvG9l{+|X7K3eM_5H8W&4-}x9d=a9e9=;~maWkuQ# z3Eujb$mvDKD9$aYFRPhjnb0?OqF_?f8_iF9nbuJ%k7*?I&X*DCz0D3A2WH5XuP1>- z=y}v1(yGl?ROH8m)Of9KhDTAGh*apL{dUJW<@XM3$2YQ%_%~0ghc4E+W7LmyJDwwm zX?lR3UhX;D?!WW9AQPQV+97u;s@n1_Xa7ftLPO!ZUtC+M(Giik#iQ|=1XguHQ?|`? z2pNp2>~0p-FB?aG4~b1%K_!VM_L6OQ#&HG2hOe+an>PBx<;zxIIzqy@Lc^=04n8T3 za;>*V{9DP`u>NQe1m?_TAvPac^gaDHApq!EM709ri*<_c=-u@vrF^}RiBzpwHud6O zSF5B5hlkmzoA1Ez+}c2EIR@>ECSw^H;T(u_5r$uM+CEJ!P%3u4zHD8$H)!(YGn|DZ-TcX)t|8v9%B%g#ST63CCiZB~reRP2q9v47AXr<86yP{gC>-lGKz7CRgR!A~u=i-dElP<;5(x{tO zPfRqFo(9j`k1!vX`5QAW5tY5R;rI66Xf=^yNQuYB82VJYDqp)$ONWV!}5ps!^iP zC$AC0WuMQ(6g9u8IM(yc7O}xj7bd=MvJj=OdOB-bZXy^wIVY=CsHe4UNn5c0({TFK z(w4^}2UOE5&m>6f53c)7Xov7^J!65Ju1gYWn%C1oj_*OO#oc;C3KKkg} znnZH%>g=;Y1mDCE=r5DJ+*ZNpvYBo4%V(F3=KFG&~6Vu$Jp(bk-(*9HgGu#LC|n$m~IYJ zu?aBL?6I(NUwp=SmSy@sU)sOr|Nk=5{__U_m}C&BX{tY{1|NsjOIZdSK+jM0_hy)> zHJmuYGV;>PV6?l(cSOKgZu_`Z@Gu4M?Au|5I2f$TW@PXL?9VA2XsCnCEWus*@Mw~>3-X7#_2o&`pEe7V)Sl&(gQ1=uNO9rOTn3+nV--t zsm|h%A%AyBHo%{cNYeBu$oUr5^yue5_0&_3)TTc-@^6Z?{{R4ODIe_90)dsP_y6uR zk28Syp>~ayF(b^3uV{YmSHBrtoDS=_xRYK?VA)0RHCZUBb*bT<;pj#nCPU*AIU~{K z#y%gph+bsFrO*sdX8RQ0bbAF7ya;#!2MW0}BpDVq=rgz9c?T{R=DGNLC8FK(_~Vb? z(u5zOzbVlE0{}$AmiYA3PhZ()n{BqL=AT<;;GuMsDDPfxPQPUsh$f#2Mc0ZuYTzOq z*P=U0F|JvK$trf#p>{XXXwV?ZNa5!Fkql*`lr!r{mN_NboCtd+G8=Gg!jCxxz6ZWX zoAXZ83V0a&SOCVq*(V`A8sY*aDzVvSo84EM|DsXK@2{iGzxh+d{0{)Y2mjSqUp-!y zzzRS9_~Wki5|>a|F9X4K;ubh+I@X)#d*+xsm)zyM7F1>`Mkum2Vb~LX3Iqph+zQ9w z>CRZ7Da2;s2|m>DCIDR^#e4TNcOS!^o5my{erLjy0LH~&OiO~`fb7CLwB zwV>GrvetBs1w)H&5<(#}v1hV1_e)J|B9EasCo0*UGfVGnb8G{@D>l=pwbnUKn>*sQ z%8NMjrI%g`;rA>3{+{p;<-k*yBHS3Q6>3?z<-N4^UVH7eZzT(zRQ=jEg=rn(4-l{( z++n)0g+Bh>x$)LQ>{#o44G{VGJU%{!++>>n=XK;GOMpP;3P$_L+=llywzj|pQBIt4 zC=MTk3qjU^=Y2hY1FbqfyMSQoC_bqyzw2In@x_NnVZL8S>sixJ_y+(W2rND78?a(6 zD~-Tl#eMeK=fENe52#4NE)}O<5dvf(v~W2d->sK-NKPLf%Y>WfBy^gu8&epB3>c27 z+q$q@;&V{To80VOx3{;(_WlM!6Q@qsC5TXPdo>&sYFjf7H}ZKUn(#-J^}bqR(R-eL z`suryrpNLJ2tNq_007f2O&Hd|@*|62#nF2C8c56Uvdb=e*LuGKg7s@zz5LQuYrS$y zCX$wSpLq(i1&rHU=nOE<66AO82PkuX?1ZEi(7&=wEUdlW1TGx18)YEkz9v?EzMjQm z;WmKSim~;<{|(k;`{&jFzm=B%rG);cGV#Bv-ySM$|5P1&_DIlYEpE3CavTO>Fbd0r zVS4||wxxjkFW$n7p1|>wK1HxZF$qKeWIY-DBc5kq@?~h!W#A$I80g``u%ODD!^1!? znRw$B_Es2t&owN0GhFEMhanx8G+63$sXROnwk%BfV9ACfT_)KxG+aRo3|0OdC)mM( zBW)&k$$nf2R$u-48vp|z_ET3cz13ON1wgE;s;X|(7yJ|^eA<`3E&u=k07*qoM6N<$ Ef(IoLLI3~& literal 0 HcmV?d00001 diff --git a/src/Ryujinx.UI.Common/Resources/Icon_XCI.png b/src/Ryujinx.UI.Common/Resources/Icon_XCI.png new file mode 100644 index 0000000000000000000000000000000000000000..ef33f281620a5998b261a385c1be245c6febc391 GIT binary patch literal 18220 zcmX_{Wl$X7)3#S|x8SnD-CY-V50>B%T!Z`K8a%<>o!|s_cM{xwxI=J%`By#lR?Y03 z`LHw7r+2&ix=&A}nu;ts3NZ=*06>?QlhObHfbT;f014s!p#QDZ;{60>Eh(wyq9H2* zk(ZX_*VLQIT!2@wFk1$eg`FNF2e2Bpsa~ ziWHw85l!M>@*p;Iw(0@m_Q8L})$qg{+zsZ7cibHi6n2OvGadc8_Ssz!Dz_REXGq%P zNA?=ky3arBe|BF|Ts-KP7n8y>$U;J}EVHG+)E)QaGyYp1aAss1Ip7A*tlYaDRlXZq z5^A|{RX)2c z((l~o^&#I#s%(SQltrrb?gnT?}>&qv9z>Q+sufxtcF$7@#IVUQ)jgBB)}H9tPFx~zgQWTU7vX5%*i4- zj_Ry+zXGpE9-96*@>p0-g=gkxyt@S1NlwoN0KljD-wu@5puPYAq&4KF#6Nj1pJak# z>=p6DjyKB_{1>L0@M7#&&=7hS(dc}e>{s45D-z8&`Ocl3AJH=dXVpJG+2r83vn(vr zeV|v%g$9D)QSkn6U;#Hjx<1yHn#cYjfPgaf)rUr>{7Tr22xcW zkj7;>`2`i9+0*ZAwRNhpdEI;8|GI#p|w?kI>NronH?Q3$ zPw=jsaJ#7oVdEjFi9~LUOTCq?IJBKouyx-5=Pm(9LuZbin-~wV1*)_|wBr|<==dWT zIZ{R7;w)TxE3mIiRXHa1b7 z#%lEWMp{a?8-{n7_K# z8K#PB*Gbe5uaqQ6uxu3j$^!ZN!zjF3m{RW(LjogXILrjCN>Dsmu%T!*GUyi%pOS>v zCwni8%3&-k*#N9G&IZEp(paX$j#^9z=Wi=Uz`QM6dYs39w$48`ltGsKl|*~pUNb{P z@LwyV1<)xaZ@q-&|CM-M?G@SKTcHiwPGk{vqmwwcU)F3qOnR(Wn8Ds>srD9l%RAc=KAO|Wx*CWz=?A^-JQrTUJK3ZU7h2C3p=S>xb zWAmE3rR}i={wH$cxtM#)4c9s40Kw~N$-7BJVLC}-Qc|V0wY9?uz6~W25fKGST{1hS zaF}?qRZ`G2b&47l6&0h4RA8=^tUwd3i>H{#lVH2i2}xRf^CeHcjE;SR&95$8`IESv zTW`W3p_6Gq=Kb1gWO-Y+y;F7;GCNxg^jxU=$FJ?>B_v-iB0@Z zzmKb*{>%0`6L1^n7+U4bQVM)@rF@-J5jxH7!w@=g)H_;myKwBd(e^$pN||MBn3mhKDxyeE08TL+%;tSzbpK&f5Y4^`QKm@TO;(wg@Fz+aF=~w=0e% zI!1mxyBYcOD=QVhxIMNH3gTXU{xW0YRa+CI>k)Q;mUz}NaQkyUDSS0fkvqG~BuZ8o zVL0XF>H!E$p2-VYuH2Ce`y9jVA|Y7-LxM`(2}(Mt_gR%?O(N-rS4_xpr8vdJCA-xR zLJ(2rZ}vh$rS?4Lc?n6hRvq;r&`0msPnHy~*@PK98fObRnX<0TGRk2u zGud3`q^HUfk}*)vLay2Nl5FoI%$m-}qYV`T+UZmTjIO1nvA58U+hyCX%6|(x!`MC1 z7e%A+h&e`V(lu>ViMV7q6eD3*lQj>Y5G6SL+kXZ@<`-7VV=uGV4-5JUJF~wIG9hI; zeWZPqa`e7VmaXx=zL=9Sd#qo1p4`zFx)dmNu^-I}s_u{~Q)xYCF>HJk^nLTkZ+8I{ z<2G_RM4D?HdGK;=oOU|91FNbmxFfFPx`6Q$So(hkm zqrtJ=vD7<&L`K0x{U08YNG)sUQu5>-e^jW;hXTXXNzZ$>{XFztt_}D(q>X)3Mpf8% zI@u_~y%#Y4`yPoa9N7fxGU7|q&9GIkCK_PsJ5Y*wMHDwTg$WoOPJPBEuXc_!DQ)+E zI&BVkJ`Z?%3MG15ulzRwE$E#!qI5lb|Gl{$Y{!Xpc2IX=DD!6GAi-0%SgvD3-Da-H zJ5E>wN%iZ19E$_kVmKL8m69j>A-h=iRy5srSY{;ocrpHHG#*xUZ=P}I*=}@@BYmz5 z!MO5}TKzw9r~joF;1k@@K`?*8Cl5eN?tS!>dU+58e(sxRu5f2$TWX~N=NT(o$KT^? zwAk+{(Wgwhk)=O>Xv`)5HD_zFvc`H>f2(eU3oBLcaJMEc%51)}t@~imxFt57#nt5E ziX-@M)Qf@Itm+5ctysXTFZ|z@pTXC0?wwrLIyYH8c~P=eNWBA6E(jDgWS7XTVOz^R zoWVa`-TToG>|A5J9{<#c-W{q2JSse34KSgAbwzCPNgSRg9Q|E(B{4-B+TzQrswRSc zRz=-=^D}>crNo$x<;|A0-f9NU1LyNP1!quKv)yuf-=pG;DWLOjd0@rOs7s~a=XTFa z&aSkGye{>t{47N4!*K(bX!XE^R!s_`pu`m9=OsTha_+(nQ;^U}ZJ)u?+jYXKxtfCJ zAMU`<(nW8XsBG1X5N}(h zz9h&0^C?~$t+kFkLhj3fasUSrf zF$1@eLf9{PwHU$Ln0|a@A}~?*;F2`UHo4yYZ>!ZzezK(67bVzZIlvNPS;?KYVCeHb z1GBj#k_bYma6?Yj{X{EXt3Sue^*9wAMyQH>LuS!U{KMspqhDyfzt&xe5Dq#xz_$Ni z^ryz77houmyN*$S&FIAsNoW?VBz*G25<~kn(8}Yt>&KYC%7}yF`8{fIqPznBh<}SM zosC+g805aWFlav6lB+{n6VQRV=@0AjV&sTEzf5 z#yDmdOyNnv1-MM0+N6oCXtVg5(gl>TnFRha$!J;12>y8aJ-G~27MeDFyVd5|Tr>ag zf)H^MEF@a~T=><`>u_r9`t`{T0wvqG*l_4~{9wvm%0xYVOLs=lHcVkN0cG|Qe@*ZP0=rk2XqL6iYKK4q3yWwwVLueXx|L^IbAThQ=Zc!=I)cUK9nZBa zu4A?P_A9Z3M|hOYkezIb=Pi6WWSQ=XxS(eI<%K|n3KRuWXw$;pQISu(uuh=qx6PbRdPQreTOFx@z&XpSVbB?&;#PLcSCp&&R@1}95O_H3bSYq51|GKWhjxYrs z*K~Cb5%t#yJ4;P{aH#hFj}0w1?0O$MEMG1OhCq?-tSNe>Xg{s;{p?kDKJ)=JUvH5*{zbP?E1IzhUB(e?X``$_taE9SreFt>%IckMqDR za?X0`HclT+??Eem1${%|BPrwM&Vn9JJ@0O`&b(1BC|?v zrYNbPN9eFg0XBnIqjtHnQZa~&r1*1hfta$}lyZh!105vHYpB*3Y2!^SKT1u0fj?Gk zH)KpuGTvs7;+PsDV}(Z#VR07Dh~fOeHOb$PIr6*_M6GKK;?Jr;nke_NQ6G36StqlC z;7N%Sk`HyhQJvGGnZaC*Q!$g_+*`Fg-UVSt=D zeC<-m|CK(j1z(aE(#{V0D_^xS(uMOo5XhqIMWRdtj4Zc`%;bK-LQ;sv0%iR^i(R1wpuLS-XNStcm zpU`P+EhbdWfsP%`{FXP*X5)PMSCsBI%Z|grg=dir(UUcBJW~%o!lqbSW1{W<0PO#7 zx>6Zy6VzUg#kWc~8KZ{t6DsT4yA|m?LGr41hjS%<2<{S_djqj?H&@# zb=L9z4%?v8#34x~Ujz@}lZVJraxkny7w%+1ivhPATWiq#WGax!2&rpZy_InBspsF> zlEP#5M&*pL*PR2E-;|}CPJakfc1DNt?2As3YE<`k)S{Xvw#GuLTH_Fy~h&w4N4HTvl)VMu;ZhmJeqf z!1{01OILNHyT(b6bLKpv>{r;|4)OLVf}~DMuLqnh@L;fK3N!Fc1|3q%R~fg-=V-Fz zkN53}yX^I@Ni`MaXlPhFVR0w6VE4+?(YX3x18*Czbl%fdSez$ zvGPu1VP$+4J{2s2yqoje;KT{WZVS+#WU|Dd)@@e!tq>ZQ8B;172Yt@q0qO{f?SSaw zk_?}*{uad%f_-X{odatJMc3Q)Ofh#H3BAz(Zx5())7`Hp8(2Q5>DYbH%ASwYcLxNv z`>DdGnH|fTkzVfV@u)3az@-UtdIYHYmm=M+H<mibuFWler4e|NUEbsRSr=LYw z%iU_YkYbj@;kIVhHMBqKY1Y@nnp=OVwG)K3gea!tEo&)W@P!!0I4sIR9;3!+BI3IB zG@+nkRX_?ia)iSw++udSEOUHHMuC0r5?T0S3rq;9l$!BPx~z*E&e)~tM}NR8n&0;1 zY+l6-&PI}X@a_XDR*k-Z_&pVY$!aZfe~CG=r<*lOLJnP!3_9W`#xiG)Yl^6r;SyJd zdu58iF6>5swiqGzp!2N=rp zLT3NpL%<(mS;Gzl2_05bQg#i}1>A|+kI;C&=Ru4x%dl(6@5m&RfG09%Ew!BNi_$N^ zfWAc^_Ir=>GB%8fkYCeESx8#fS__XiB-bK{^a8s|BbFzh0gu0(S}cLd)EfpVYon1Y zGdmjVHl74*D5j_w2)ZT~&ia_kzTC<-aN`+K-PKvp@sg=&EmoB+F~6WSyPskFm-yp> zjP|C_3{M{5UIcXb{2a+CSL>}GR6>QG!x!pNJWN3OqB>S!6iV z+`0K_dgKh65UY*Phtt`MvHdwYJzQoo@x#Y_AL(L_LoY>;?N*47x}ivR-Xhm8VG!23 z*L&o(`9ot0iiuI_dy=1IL&mu#IFb>+q^g#76Z^T`aW#*yYgdvOhRNXYMq%oA(Zyz^ zZpAfBp^B+$nVtE4&8F~9m)pUMy}ibnoMf?tkLU@YFy@!chuXD((3VkQf6?AJa`0u9 z8hIx!;+CTnxe%~5BB{jSr>D%1QikbqYWP+X0L`xfsJXe%RGZ;^65ah$sX(O;c`x?U zQ_i$r;Qo#R$_;+%p|Zt) zM%L0mZM_v$9)9vwCY54ZGkKvjk~K)k_qRf1XYVJ;z#3l47aN&URrH-$?je zV;`R)hv2M=gCGA>g=0-cj+-xoVEJ!YaLUg`i%0KEh{rMl)Ppz6&$@hw~Vag2*7Ns8UL{T>;;6%2by^_^qIy#uOE~ zr|Un=>|p;hU#!M1G5?_oP279FQbbeT4*W3qXy|jfR-nflhJrFs0~7X=$s%|>305Os z0gX}B+g&2)uw#${U$^Owgd~K#+Ee$>K0V4wiI~?Rlkf#uc0?H?A7ecqFTh8(zX6b% zq4_t`(@kI1_T^A}f3N_iPL`m|HHJMzz2PdM4<`ce^SXyOtwM}B{az2tkVPRWCNm=#Tyz|2X!(OooWQvJHX|8Z{0PyHE!6T@4W1a8 z6D>CA5rWzH$W4wCy_Pyy=e@6Qon28O8Gpse@Zd~w{Ijte9FU($_@we}_p#q@87}2q zJt^jaKuGZi+fC-*7;8NN4?!C<%;{iOb&0YCN(+P|s*ls?qzFEr@ZfB|Bcu;p;h}=+ zS^*)f!e>&D1#Xa?`_F;_D64H+!QV4{u0Ih*KSuvM61R-X?83lKVGTx-=gdyyiq;E# z{{)gEnu>amCq(nsq1yB>p&TXp?2j>#Ga4CTZADF-i@;lA<3EMmkjXJjV<1-`g%aVJ zt~QeV%o-BE$JH6oMZILu#`D(U???VVoqX||}XyAyW~Wr0c?N&bvl!RcCa zB2smNag>RRk4nZjOfg^?=p?@<8nfd%XIfZC&krWTm3@*gR$;}!B_f8qK?sVe4rn$B z%63Z#7P$7a;EE9V;oGydy8_RW#u3<7;x+f?SAd z1~^VbdMk~NpaqYizl`toOypvpHChr!R!Ky*x7~(JSP?%u_PN5iHGtx0J%NeOHGXUe zn?d9-ZV9OhLpna2WPgGghbVdfyd@fQ^O>9o-b#rhRw+M5o*!b6u2E;=PyR73ddZAN zgcK2`FLiv{rb;1`Y`lx(76`L<+ms*v!@z)1z(S;>@%>nYi9S7qlSTiU3-sq|`kA%+ASqa~a-owHci0=;+YG9-^ROI#!*5yQeTA zH)d5>E3u)i%ecn9h(V6`2iB>52cbzKJ6XIW02+jdv_yp2~V{B{< z9|+`M{ryhuJ7=%rTGQNsmPMdrxbaJ+b~<6OGS?ypvJ^U`OQ;xpHFVV40OWyfT`PU7 zCHkv|nxK+?W1)jI{QE87PT+Ds9x0i)R1_x8Ng)I;fe(M90{v3e)30IN{ygocAcYvl zfwsu!`4en8H>j_BCe14wawpfFrVe-l?uZ$-mg>bW`8(^ z0&Er7=t@GjB!ue~`lf!M#uHr+R3BJ8S=~-^+a9|OOgx`k%5M^!V)(>emex08Z1Ar*#P9*=;EoU#1e+5jnHCim)@Q&-@6U#j@3xV1lb1H3TSDG+XpViNZT9Fo)$I}zf4v;>3SWAirEiyfC`Jk*!UL>L);xV0i2)3+iO@n21941x(MqGB+>_~A0x^GJSZ;u5 z>@CW>A2}`)cl#!IkdAuceY}){`GACMX9dmp-?CI{V8TiG%>Zvf@UJ0l)TMLjpAS@N z`r0~jJ?zoyFtjV|SF`OTY|n#3HWkE4B0wR+ewzR(T2OipNw#5N__!^>F<|z1s`(Ge zG#)RUfGVd4rh|foAK;trIkL@by%76dE~c?4>P7k|S}`BRqi6xZq46GhJ=e}uO8MB5d#X`PsyaR`&Sa&^fAU|1Z=x`YN^Dl+b(p%-M?1PW9sxPnA zQ+be;WGAizxJ;wU${7^~LrJl}1wH@t%d(x-3bhj#9Oa-*=k4}dv6 zSgS5pus8+-0uEwu?aWoKiIncoys11~_Vaq`D}Zdy0i1pY(&7ts!9*UwSbTJ^hUr!; zlxVdek%d4b#L+YlTc1UgYBlp?uXrUwA;A~y4}l@JO=CgQjTDO)@`7(i>4*FEgb0;P ziTq!&& zuekA|vQTv6c#R$`00Z8)ykR`of-&r}{j`hV$D9l!x=qO8P9Ldpg4}GW5$kmI5gRY! zZrfGujz5w1`p0KZ4lR55gUPPNo)5VBvh3M< zL7BO;z&+SrOobK59u0y>vNOXqF415quor%JU$zJc2rzPwE(=&lNl6*Q=z4uT-klPA zYp?Y5e+}=jg*8&4-DTt`Ih?W zq1|U6V=`0TY@|RMq09dz-*%yTW+H>HYXAOX3*6y#HTYPmStutipGck8ABn4(FBM7e zxZaLOr$UPI!E@Ddqf;HyA48OsNTay-TM)~CA3e+?p!2p6@reDI5s(9@e%S`}!x8+c z6FPg`=nBX=TBvbUvd9+oOXswlR7wCA&+=N&hD>G&$qF{u{E4!dNSAg81bbav8tnswu!ki2cJK!qQS>hCImJfc2#UpQu*E4tU8a;?FE97ZihVqQC4 zwL842HpdOosECMM1a#u*$D3n&Hh(~~L6aTPpGv)?I?JipC)@St5<2k6g+?q$lVQIT zakCb?dy-~_b9W@jd3wM-mN@XEWY}~9mF&St5*;3rF6>LQLy`B*Q39E8z-y*V452u! zQW|F{4z--IZ$OTyU%p|7w_cn7C%~3OoBOHNLbU<6#W-OIg?a=18U;qLJX9-2#_-F1 zSKq}LOn2uf7eRnW?gp?5eZIf^1qmiX?q6N2X0@r`{ZrJt! zMbTob3*84MZM3|Q{SLr$wR;Zdi~bSto9+>*FDCTwGmWI8+uhPv7oIDlPG7I1)mE-| zm&bv&PFI?_z5baWvu||=f*YK-WoF-vLgTiYhH!4p7AZz7Ek!&NZ2rpNwO4rGB?xz{ zvm-}ocZ0{q{|UN~zZzp{kqc{lC8Ogo?gosea!?)C8ux$%-Hz}MFJ7KJhy`7()E2}X zR$B`Dq6ygINgMD>abei}?k9v^eo0$c(81?%{@qT%{-zJWS7I~lQbNa!Q;T=?ln{59 zlUc20eNfH#e7yr$f9SG5*;tMuczrMv{O;S(yF!+8x1;&UY!M%so_KC+4Mv0C+`GdG z!Luc*Q@c{hWL0XrJ0nRA78ArwXM8}f2nsR(kjIDZHx(ltn_>Dt0ZvX!abrPs5953Q z3`~TCpL)9%;a`=s>&)eC^CmL|l0aLdKbW+&JKp~sg@Y%!v`P@Ny02q0eowbFxY{*_ z%5*B3xyH~xpfGyFcF#i4O+fCuujji00;t)lR{S6Sg??(VTS`R0BuDFn?N@-t`4S?b zq%dxz$sSB#3tlM>&!-fYlf?ktQr%j?7*c^ma#3G;kDOe+w=&HVjY{2La^Z0lVh9s# z)T-H}5AeeHZ2naF($NU4pqQv#FS&m+dlMNZJGJ*BYQWl?OdBl*kZSb zeR;f@I~y%6D##3538XIsm$t06dGK{&swE^Ca{~|%cRmWM3>Qtl>v8Juj@#Wo`allA zZ)7N7v0{1k{=UV`rMLITRp%B!{~nT~{AVTFN*2Y#fYZ2svW%y2n!3toy>y z3&Am9VHgBjBR;)G@yUk5d$_V|S)UejfAbDBtkh)=sQb+Hihpb0{cyD}i!V}$1)>oV z;`hET{gsAj7=dYj!dAmZ`zA*YkWlfxa{*ukxdEI^Q)2?-VXeRjALk%flp2$-aKBv- zP2K)8X!lGwF`X@xAMRc8e(^Y4{YVyt&oU~2+l;SIxCgi{hbDSYeihe4b|QQ=hrkF# zy4%EJz@0uOlZI46vgvx-Y1 z5`Z4?LZX6p)k&!1ytLVNIr`?>1h@O&6}GEPZhNOWE|yOk1|7f;43lOhXg)zO7gzfe z72bxHN=B<^Zl)X}(Zi2SzW8G;T+kx6PXz;2AC?0LXU@i(L#dOVbhMQlNGpJ|yJ=*+uIRC6$} zCx>rwbK9o0tEn}Mm1L&+!e5u;5r7+C?<3)B+z|wk#1H`yo=U$h`Gpez!v4=KUjdZS z6*@Ju4c2qR7~L}qTk9P@7GF&QqzK<8Z~R&L`ksR=Jo}9uJC7P|;97#jpb+01XMB-5W*eNb~~KFst8E z`jPv$=>Upv^+&e!nF3kKtwnE^4~(BK^l{V>wQf?ZyXAzb(7HWtaY#dQz}>w%LJd3D z_>J6DNdl~(HtKoTn3tQy2O=7UWcI{>Tu6iE6gel5rH^*=mCIrr;0P&{{%tiw`M!q9 z(cyiA5-h=<1kf-xq<)h#gN#@_2mDAp^8o(_{eCIxkHgv!;`2|-!YTMHlG6W#%31J9Z5W_REP zaivq?djf$_d|ZG{N;7*4AX>%)Owmx4c!6xg0e-eyq z$LSY_TP9DbA*RyWQ#_o{8pTRwKFYdMi@SNPB^evxq0yCH?*BO*|E)TF_uyX)M*_>K z?8aCu*fg9A(nYp#7TgYJy`Up#Ukhh8GdKr4jbRUEs(q%QZ~!H)KHZ)3jeg^CSWQJU z9x(7krv1#}Zb2x18j3+R+wSG^xY6wb@z?^wX*|r#hIq}Xw`u%3qlnngs$DSpIhRflu z+2puE4i-*hB>e!`03^j<*CM^=$rzRtG^FrI^9$a~^2qkaD3Ks$|O*T6H`Y_CykENxg z;COO>FzGZc`ZPOk6kqL6Nfuy@ix*_XnNu<1rhZI)uSnEHHODEt_X{+cz6m0i(mu@B z)iWbf0_nNl?zrS z`?cr5u&NQ3pA1ot)Ow`K*Uj-} z(jsADGosY13wiY=8ZkiBi5q|v>@@Qzr$zdf0X;sKsX0@$OA z3#k6`RjJHYL!}sO3$Q|nB0i}GUbI(#;j+Uq+u4c>QiEJ zf+K;OUd7}9Ok+>hGC2KjpJGi*C$a;Kw(T*XdD!tjS+FlsP57kWR0Z*ifP~a>0L|hN z)a6M2mT0`YxQ7;x31s8*b9`fb?zhTk1G-vMKnfQ?x^LR>D@n*Q$Og4hp!BrWO_X#1X9zdl|<0%gR(TCYp-i#l1mqm2R4MqM= zWp1U3(exeMK%4wF1{*4SLXa*_3!O$Rgsk~)AXLO+$oJsD!!oWRMpbl-G7E=MDxVszF2`-1Ca2-gq; zc=KiEm~wKvA((pe>2$NLo#f>~V3*giGU(5)j1IarDAalG<255YO(O21pH<-HhZ{4 zt}y6ic0;fF!C``rH38$92PuF*aMYN~6Xl|xU?>vm;kZlV76*gN|$w~O^ zk=9XSQ9PGpwGV#~%W0LWh6A>7veO*;ID3J#;qukMUD%adMUk0BRBr;|)0w$Xj(-lH z_3Ig(KCijYd;*SsXp%NB<(rWgCj=$VBFq4cvzDIBzCY22I~SJub8VJPhao!w})#8h){!s9|qhAj0eA>N!?x}PKoT}ON%`= zhh_!t$&2t#(SzGQUBP!WrJ_2$r9Hwjr6$bOrwtF~!4^U=Ep24_6#SpDWY|b>%X2kU zR`o#3yen02U^~RHf6FlEm)x&YI(egRJIr;OgHtr_^WtC2ni=0A%5*NIO@a7_ah)QA z$!Q*T5Ft3se5?q9#78?}paz#$q*s8CPg~vxEECa=_V!gnBO}M?bwT%9B6T&xzEbd} z1nRVSj~j4G%uC(W5b6W=n;*Y5#zKd7!OoV}kHnbUhl#w$Qj59?eqCS@s3G&C?f**4 zonb>Pa4Ms2A;dh`xZhk7ZL+jRGhRoEe_W9#Au4;RQj@I280Xmdedk^W3MPhKCL6E% zN0@b22m&||roZlc4-BFD6%!8U=N|q8cm2V}FiS|1$bCVEI?`PwTN9cAboc$`2OOIK zrf#`zUys*^y^J#X zouOeEWKr*Y(5LmT8bp{4#eTG&Erh&-aBrb}Qm*UKJaTq+b_5YuF=@p+8Jb*d_Ncwf z2_Ygrx5FIYKQQ69cXZ4YDH72CFY&&s8PIplBy4*quE2SFP|ad8GjbrBAlQ5)5#Dk# zQ~Vw0-+S1b?0Ql;%%J7kRZ(*#s#Io!F%aQD?r*Oz2P@5Na#wp3&esR()GC>TAy;>+ z>+g02xt_0+k&~0J#znBA+1&9wxf-v#mjJp}iVrbO2*6&P@#_o65#3W}Peku`leMy< zS4d&~UfZ1;LoV_=>Vqud;eB+)3njQNV76_{A-mzlojN5uvO-i~F1O|(@tZ%8w(Kn*n)^EFs zlb_$gm4D)$3)CvKS&(z9zBK>$F3PaOf4epYp+CLac}EHA z?i;Ish(Vg0wHv`ukh&CU)*k`feK(y@#G6~`-q3-n>haz7yh7y+Z5jEbU_{Jbw2=2H zlqXg%Rr~0*x_7bFM@qBny6`UVbX#l>ez#it%_HjnOkdE3<Rnb+*=aadj1pE}iH9 z^4RdU*EAOuJTYQ{CG5|{%eC<3mg;01JM<&E8)M%&r{-mesG1^)gJ5{{nW7c`6`|Isq zROcKkOZAo}z54vB*}}rSlrp^U!i9wmw1UljE$l4xK?I)C_HSh^gLY-=iP7G8T7t0G zx5Z>OBg0OicRc<)1|bz_?Ql=R-Uw-ZOJOroE0n|bje9T5FTINO#PEzojq1!thCh?` zH$4>BvMX~E*0iQskDR0dW&heS>DEG`HSJDT-DQ8ObiLP*R#%soffMh( z?ad%~w}IQETO;{Pv0jbB1S!udXnHiu3GT)68KT0CtYY*t{ zPGsox4^qGLg>Ou3lmz6C3bv#(o?j<$^hGmBWt$b0uJBy&NB>4})&}?$=uKyep$7WTk%4B&Lhs|Bed_WHd)w@5m-HKq6|BVJ87CWYzU*;*p8>)C4B&?FuO z_Ml`qM$gz-w5eG%ZfJ@%h9wrgT7tJAyW`4S;PQAwx4Z@T zia3@)HRK^1MJ=CLTTA=H^V`up!`^{LqyOu(fImEo=#|k+u>9V?o}M1351-u;$b*B& zQ#pcpS4qW*+Fb$Zj?q>O@74a6-ndx-mYP(xVFyYgL>%N#>*9hy1Rq)kAPdKb>V=p) zs9bK}IcF4-Z=x`7US1)?$8?d11+Vl@`CtAAZ%NaD5AE5yy;{`p^sLnFnait5jq(w9BETmh0E;FH!U7%Hr+dlF8mm5Ok3}q-ZFI(1(^j z+dW9ocXq7%4OOoP0>U1NyAun+tiI89U7xV*nF_#VACvSmWjgZDK>T=HYIDlm@2>b- zNbw&9(kq+}j=WWu`pUy5iIHUu0>=%^1=63l>)X zH0}PHzgg$i%^&Nmq0@ti;Z6_1^SlE~zZ*iQ8z115-+BAlR5e4e*Y|OK+ zBazRh7vT;38CTWKNSc#+sagR4^^3XV=cUy|o^`lEc55M!Q@@T;3fyUXvLkyUk%@lX z@p89~TVKgS3x~04gNfXGdu07h4yP3Zl{gx|gSU#;^VAPw7hlH?4w*DMUu4OH zu|)3zxK>}sQva;MfkphE?*+*9kjQV8vh`FjM#(+LgMKvRF|Ql^Fa=Sq^J4A_#u0_n zbRm;Fwf>DwXJ{eEb$Zf#>`Kv8Zl_g)j=Q(bRd?ff^ZlJXo?iVbP~En&@bqFvJm`ow ze5O2zu?nhbAmMqL5{)3p35Kl~rgH%Ov5kF}wx>8TUV-a$#Oy*#IO!Q{PVyeD`v3ae z>pl4HgseNHyr~4gCTcrP%tJMrxljI8ItZ~Hihmm%GHa%!j}y1$t1?Jc*Rb5kaOzwx zJo>gU?%}BY(0Xn!SocG?pPY2$$?E&VM!E)t_`-6p7a;;HdA~v|#4M;i;hzZz!YFja z`RmsqXKUmJrX;(%vjI+V_tfu78Qzq(-#TPETc4M!7nb{@7CmArl*4JO?OPGD+w{-w zw(D7kz5+-jx<-vp0cfnh##5LcRxXEhohIscY)_9!(ZC0N;@~IF+7cN zHsT|QVeAX|j)w)YVGUX6$HCHI_rikNyNdN~uiPwO%6QuXOKz-NG-#u5{W-n?Sy!OVo}lW4 zd2xkjfMvyUzq7taCSgmt_@6y39QP|Huki{|Sj(1t%vw<;J1g)*lVdw;rbX}i3jlDX z{p@Z1T{RD1Io|kJZ5TD;{;Bn8$20KN@w>v|&k9zd`VcQp$wE~+9vBA@ML@h)rc_%j zqvamc**meS_YCAlj7&f0?%zDH>-vdEgGCBH@#c@BA=!5e8Bh;5*JUEJjId_qVAV!dbIZiN7NY~wxej9ejAdq2sIg=KEX=7 z0&JW21-J}bx+^-P&Xcm7v#g}d@eUW6#bYDe7gV7s!XX#_Kg1bla;ik^$#!bHsapcW zR4kS3n{a?X^V_A~dSfGquFNYPt1M{bi*QMX6(v^i4wA}>NrE7|G0J_*Bzcsg0wU6b z9ZDoxVLn@GD}R!f;k1@|`?!#?#Ij!qc!o@Y4E@G`arNov{V8ar9x_jPYn9vgV_jWg z=dc~}kx#)NfUI5n25?dV_wR4yu^LM+RGZww0S)3LgH!3=xjJuDjV5TOvlLS9}Hhx_EcNi|9nuU-UP(} znjC5Dd`F!=`<1z!W-JrX5e>5E8nwKbp&B3{Pbn(@j8EQAEvV)6Nm#ID{|`-0nD(3j z8=Rs%MiC)R}|4R&}De6rA(bSH%l}iVyIwm@2{h_H?J8lcL2I;jAxKHC? ze0KrCKDre<&&gHX%&pjZ_^45*c*mO8(X{j1PPHN~clWBFa{T_Wup^NM9q`kzS0DD{ zsLfrO(%FAP%)=AmgVI;JP2Fh0YQ-eJ;2z%`7EEJL#5;4!%1Hc_cS}NUpifD z9Q)wuQxiiZ7AixgwdzVmtN8t47YE!5eIpm*?8S89g}ZQGb6SkeH^d5Fkaj7NRSH|c zF}sywlB~rW3mk2&9fsy{Mwz>Qy1a-1#hi&e*9aU|8@q$_!23H#;{o)L2)ACpdWw`C zy1LjA0s9gC$WOyORp+dVD)G+SXx}(Q-ZG$vqhjG7hPp?ja%%b6-9g995 zFtsn(=BZJNsFcLh)_r)%zK114Lo5)hpd)VcB)9qYYMc|4Ph|CJzuV9CYjP6gWy0aN z`{wK^buX5&5(=dDtUAiGpQCk>Y`n^mAI)mrC4p z(#8Y3G6C@zm_8TRnn6<1>h!WIceQg*+FKHQt!pI=1p81Px$QrH#%*~y^2TbK2btq` zRmxlu;#Im@4Q0T`lciG%6L>ul1O-dHo(IWj{7|&5=bSBMxI6tjp0Y{zA#KIQS|ia| z0YKAIXk_-6D3_Eb7dC6Tf9`AKevU>-8^{%ggtg?r?ZX*jrd;mr4D6m82ByCE$9w_u z+}u9#NKX4}tuK___?}{Lqz>ry=P7r<|$C&RoX{@&`~x ztU{amH218n5 z-QznYn2@;4alfykwBu?<=r)bB*#8Lt78(%L3;3qyyZ@vLG3K}b)hDnQ^0Gh2-?FZk zt8NWQpP2tsSSZLejpJu}0t(jtk4|yROTcSttCpN~k=^eD*reyi!x&v@X02kxp(zii~+ zBGUd70JLxUU~d-)tXjSQ_o8{60mKirYqX3RVPY*v7-*PyNO1F21!N=H}8*RC=;ceSx2(W zDbeOc*fWvYfTIaN<`noI_#SP}J5ej(Ven%C82@IUg!E{L3zVqDmRoLlOKJYIhm_y1 zhs?iU77_D50RSKT2OoTJZ&?B>fByOBd(=x@T4B8m1lNgM;Hc?XZ=UtcF?TMx%XclP z%v6j}WNE^%C;Su$4%WC8j=|HNu|QLZ&B7CWsNqcjx1i{Pab%>GI7{>p%BAl;%_~D1of$%TN(%%UHSRoiNC`;hUU3cB}gKEz-w>1sX zz;Wgu2_~1IIuzwvc!nKK=-G63jR0AE4o)`E=+!6m!eHzwl>h0ka~%brrgF|{zunhP z^E_p_4yBH@(MrW1fs%O1 z|B6XJ696n8R={srzWeUGZxl)MP=&mo znX^wN3msei+O~jc9pMiUupZoDy3s-(|L)v)Yaw>5^}Ys(e0&}spF(aj&HwW{@{uJ# zAaezyePnL~x2RuR;DRV8PB|2ZkHLi?Yrylqp1*-s9iLr5Fm)7fRhHjnk3atSokN)K z>tXr*;wStQ01yP08~O&UQr}evFj!^Yym|W-LAY;43U;qJ^(qh`3!#O}@%V1NyhC#O z@K`3?JSU;keBGGBAY{OBOx@On-4dUJTHfSl@4CIcHMaLR5Slo3x-LP4g4?U%pitYI zak!DsE764Ss;u|v3X9(K;DZm|*fc%9-+}Oh@J|4+*rf@>8dzzt2v!-EE7d?+Y4_cC zpIgiM1q2(_cdhbE*Qn)cEtyDK-hJjN%oZ?iZ=o~5I7^W0+z(LZ{OE+F7SO-4Of0Ou z-UKcjvKwU};=U$UeZHQ>V&OJ`*ov|B!T$}`Wcz=v|Gz0M|5XY77iHpqP`}+#+Wvt$ z_78)g|Iu#UpfC)=a1izy3^ zbeUw&&~OATFjV<->|g~OjDZvYH@*iT)(^j2q47XY!Ys;atCKihOA U2G4v@egFUf07*qoM6N<$g7fX;jsO4v literal 0 HcmV?d00001 diff --git a/src/Ryujinx.UI.Common/Resources/Logo_Amiibo.png b/src/Ryujinx.UI.Common/Resources/Logo_Amiibo.png new file mode 100644 index 0000000000000000000000000000000000000000..cbee80376ccbaf10126de5f6a2d48c3d4cdef2d7 GIT binary patch literal 10573 zcmZ{KWmr_-6Y$bVcQ;FSOXte6bSTnNA|VJY(jnckbjyk$N=QkA0wN)?3rL5sz=DLr z()I4||9*NuywAPop6AXpGk50PbIzQJBoiZDauOC2002O)2i7se_E*^UmY5LxO4+9sl@HlEJEaP-u)f%r z8j4@h@kwT(mAMZ*D=|DfDG6oEaOH9ZnHJX97?S2Z*W&s zJ$~j{P4{^^F52Oh{*+$fx(P!|7<3|)hZQT69w(KR zQZ_sajP@vcHszbuj?P2iD<;&2+k+;x5{DVV?4C5?uMJeXQJnpt4Y-XKJBl7O z;T7p*qlHl{D>t~0*gI5CSK_ZlorY94!35N!U%m*%$t3ijG475mJg_TPRxJXcLQ{Oo zM57;8M-4w-+FB6i1t}I*f`yqO@@=>}vR-&n-G0W0n=|+$6zOSJB-n0v;BS#6-QVVf zgs*5G0IS)a+hLMu^t@d-7)#CVp>L?E_Ss2Jf4s(-FKNg@od99BWi<9$-2K43=`SEK+^%zjm zmD3YmJtEkV_0JYc;SA5nH}&InuT8Y4SFDRd=SX3Jyzq?28h)ehmBOTOV-v6+Z%jLy zEf>UDn~6e_|Fja+)Q#g)kS<7>FxA6wljwU_zO#l~YNvk$D)E(Lflo1ExmZO&A8WWn z%93HMBU^BPIvQ0=&`27phWm<}P^TKY3HNtlP>sPE5lYBmP<=x^k_P>a1<~}7^1@j4 zOduHHVc#L0ds;md+#krGN`09vg~;H2oI@2Z=nN|!UA1RG_+Rz^IZ)Cz6u(l}!<>;p zyj>EP;Y+-2%=D$lPxGWXK;?__6nn1_b-EY^?x|NUBwz1O@t#^$_}vCR;;UYMw7*3c z&NTolcH%8WS@rz(^I!-f0!0z^^TFa+62&&sUr?*k#I=B&zBijP8Y16_U4Psdh!(4YG5`u5@0B}oAn#iPL?$c zT$T=VJC94TYvOuXN#9Nw?{76mVYv2d_@N8!vFh<#x}>oIP~~0oYD^??@*x9m`a&sS zbwDlLu;I8eL=4qUKU~J$iiTF+IuG$x-v(DgVLJnH;w#^134A`xYEz9)5JU*i>NL7> zb!9m~vS%f@QZHVd4lb$3wqN3BKCkZ`{I-xS>kH+Trw{{ccWqZ{Wu7D#O*Wlo9BJqaXvM3PBroV`fu`uY^fqEm(Z<&D}D>jH#W@u&Y={%En#c$#e2z! zA}lb5q$=%W6Zf?fg(LxH|%mI2ut(foC=wzZrG9Wx0puZd+8AAPB_( z?T`1$4c$FTpvj;I_xMSfTA0Gr>JBI?*s16~`h6FNdAGYpfrVSexjcmWZN{JlNq^3X z8;PaFiUTtFD8Tc;B6c{(8)RTkvm^96AUa7N! zOk^Y}ySiYbIou)h?PtM_H(QMt_8G<9kq#&s^KZLcEc7MGY92p>{Lp_*d3IcD(D4?5 z58~r_&2he*eo922W{#1iL{PhXB%x)Pj88aV^-XrlifL2)YU8gB*k-QkzBS`N# zm#tp*jB{mi$~fid^z+5)rhz_`gdL~Nb6Yb$ok@G5Ou2gay7?*QnWM?X^A}B$vriiN z8daH$PnqfIOUug7I0j&m;x#B{)!)>4hlOHG(P`oq5X26vYlZ6m^-`d;z*QQng5Im3 zKYfrOUtmOmd-8NkWX&7Z-JDFw8Rx?f=<94PXs6l4RP{8n=O(-}Ym%Aaw#RJpW$4gy zhYSs$_b50+{w|Lju>=+lIahm?JyDF^e)3!N?dnW12}9>>Oe~=NQdK?Y1O}I(+78(H z8YHQH8l<5CINK!+0dyBznj)4YfoE+SSwEdK!Gj$$)r+G-_y-Hw-)XJXM0Og7&6@1e zbxqHmBE1vb4+RdKXgujk(Styr7fU4T8S8NVCL4P9%v|W0((80)d>i`SJ-U zulnRt#)Sul`B`S$&;A@OKY6%;oOd18L}h8gb@jxqX!j=*TD*ONwr$TT8CM=&-I?JL zA?uxaI;?GL_x@9R^W7(Lp~h&mh`)$%NRF11x)ue`Sf;_?3jAd`UrwAF7_NK8dzAQR zVyhr>jXSu=Int=_(cZha9T?BWItWy_gI_Ru=A)0UbaQC7ZU#rq-CZS(Dwvj{w+_A@ z$G4DI*3_w8b@{5Z_l~_%orXv4*#PDeO=klnEKI#2>fp{ovjZa==F7_=6M%D#k!$}? zG^OHt?_qPbZn{?#Oh12KXG1W6oAdWgeGmJTzYeMB5AJehEh}W+b7f~S4L>!95y16HMIZt9n0)1I;;*F7pZHRl4k=y>xdJz5p3 z?e+b#OOO=QWi77LeQa19U81g7hv4hVX2?z0W-?^br~xjbfCWwYqfYfR4o=0!>{)2^M({Ww46wM|^>=)Al zmE_BoJ3`Md8QDXjeTm=-ugEbcgo7prSKP+six##(Gea-lvCn2!IYd&dUE22tD{t?= z7(_`~0^(P23xYV-v)@|><;=2!6Er5Cc&_3?8zTF%x5(hsyt~KAB)84g^mZ}g{(5nQ zQ&mQA5mYF_Rz5=!F=~tln_&#SWRH8K3l!$8L&~Ddg;#~ddbBO<{k~NZPxTQzthF^A#drv zw(A2LYzYbiD}_bx+d1~FwkW@A)2?gsldS%8={C zOng8OrSaM+x6zvSFmC#7UfU!)N$fgPb^qQgqdGH^H@KB#7Y{4L?VFmp<6kv_7gq(< zz>5WLTBZTnpCdVg+56OEIPr-bV#jTY21fK=u0MA$xVA93cpdWw8e%2*Pv{A3d#($r zP#f!?9hhJ#uH=~{K2h^WYSy9XNrhMy%J?8x_1@@!zDJ{3oT$^;!)Jb)fFq(5A0;R@ zxf!Qc5)mobXaS*LvAvozB5vw4Ba{4xn|Cok*RbM>XR{g*w?SR{x%@}0@u==Use`3w zk>#mtZm!KjAw~`bG%I?CvO91Y;ZENap}E1={fpqCe8i9WkN%T?4BJIQOe7 z^k+x<^su5llx#=Md_uWcV4RLZe#9@|sKT{pNg1lQA3Xb?t?p&8eCsTge+5nFpcr>2 z$OwfJ(WqGqp6tHmh>N@9n7)y(>LBIsNVvIf^+#CI*%<_AH>ineU)qNkay3`5ITTRJ z&7rjJu9+pN6fsu9bcg!XsHz%6f@&vDBhmb2h7^0)g~inaMtpS6%DVFW)qXu?s6FMI zffKEDdwZpuWo0|Tf6hdXBWt?L_8rxb9>uDTNMiiaz8yuzQd7h|G)ZR+5D)Yi6A#h*sT$pL+cv+nh97iF z6G8QiK=QzXFGr23o(zo(BqunMr2@0%T_=U-{yC5uPcd(P3(_cX%zhxocv~*g`5VpO z$qpr+Dd8U=zJW+}>}s$O+>3^V%ah&qkzH(lD46=O5!Z3pKfy-?{9U`WNRff7Na0f#L5Sv5CK)-c?g;ZLB1fr>`89jO$l9ro)_|x*yVeg5OG4H@reH zGuRbaGMcX-sW19M4pb`UhLFqK4_>|-ByvGm_X11{hil$8E5vEP;AThWThD#f#HF%+l!4%{XQ-Oo+g z-+iUuV%g?`&#~p5=#(*!ARls6jCk%Ky!?(A^<(p1e1uX!n7{2KJU{0`c2^)=0DOO( z0q_a?N3;C%hy?ij?;7P2SQe>fbrP-xkp$R2uXcpJMK@?C~%4B8j4FrWOI{%W5mIP}2Jj57F}EkkFDphnS4{$5N_*{$}{!|`>}BIC}gb+ zzo?`))31Y;M?lb9j0X*b`e(Dzd=9?^6~-ujMbRgGj1iZoTDEB+up?$HP4Uh{2N^#M zcK%4D^EEJ(=J!w|U3;VYqB<#Na2rXYu;e-CN{++6{)PHoIi=pHRx}{1J#M%6ECF>esCkeFBw9v-K6fxM$`Kf zT1jyf@|pO|v9c1tl|xB4<IU7hm!mS7$?wvAFAvu6wa(>_t&_e}iCUg$o}HjIZ0pnef~ zms}OxR8+$rW1xG+-S?EMO-Q0RSye^HY978t3AklYz;W9$!)oz6 zpzcD+_naJ977>|rm+R7_xQaL1sNkT4GkwEfbvc(jTBjM3!i&lW(kzt(SHT6vziPpW^pPCx~NzIDPCAmd2d~DRe|!Zdv-B?mhO~pZ9J0^Uip9$k1lEsP&KD zw~hBMzxuYblgz8KEr*P_wBviUj;|>-3A1ZURNxQD6u9o}ykQVO-AUrVe>0*Iu1&j} zNBr^+0(j$(KDY@@7sjH6javQSt7}S48bN)T^{w>KP!7_X<7@Xz8 zdyt#;&_(L_J&9AXKYoq2&&}1aCZUePF)tGQtLBS2z~304(?UsTJo!SrGj6VJr;pdX zdXmR6ODbNcerY)CVP}xDF}uMHZvUvr@pSteQEmI2y|tj(zGwOhcdHM)!LA06(2IKwqSA^lU~;Ro?L zeQ`l=+6PZ@wBbj2UB(Wtv(M0mWU8Hc=Vq3)0SI1KGp zYd(BG@A;|<8AC<}{r32KPWQE;Of(c>AQG~fz;*8m#?bqv^lr#&tm1~gdY$;#DfvK= z_SRV1Ymn*K3{M$JH+DjUuO3aNW&J@6n&i3oUADSej1t{_X=SxNz9S91ip(+z+0^Ck zlL1}gkQ~$n7oC_|&c19vgpeVtra|G%XqA?4-=4 z;KJ(8iIt{rdaD^TH0cEPgQ$xuh0gVap6eMTOjN$c3q7f+*9Rxd5gYx(WB@Gth}-Xd z-MDsKixrN(WukNJF+;}A1{e!*C7Zd;9~pYKT8yG|2jk{p*)uUNO?d}T>*xGQV#-z) ziD8_%FC7h3oZ&mxR~jj7P!zm5fSgk<&*IbceAq;pY7CFioc;IG~tzMRY zho=n#Z8(WD{D&`Mq2sREv(3?E#gfrO_)5hyyF@&&F2#LS;9c5hjqSu*eLH7n+Hmfk ze{PceAaR?0-m+g)k#+f2}Tq>73!lE{)KP4fv->)!^gU!682C z>*pL*@blIo*|}#$3NIK4f8^@EM&qQOx$EMH&7p(U8qa7X!>w@y@Hc8RllPKXZnU7Jk?iqoDL9ZQ{5d^NvM6=t_&C zn2Vo^`tS@_-*05bP{R7b9Ph!^g=q`S_OH3(HfhZE=5-r^pI)PEH5AK@E@X2@ia&&g z(Qf6>V(XuH*vf#y6m}uXooz!6yClwbD0jmY?YQ18%bTt+(W<7bUaTU@K${zXL0O8uUS}yVCd&9~+VezK&=F zI-WCaf!2Ot7f$s;9%d*|vtAN-1*&9OgFt#bhyPlVbu|yDmXdgzDW#;bmL~ z)$As=$N8D8WJwXszBvDEF%8T#rDc0puv&pHB~-7YU($2pa~y{GgHJx)f2|n&g*9Bz zwAMr;PHpx=PBDY(8vhLl9?B+O=f0>cn_{C;FTo=3TBV6Q2Wx|0!Cw1Z4CU@c{x zbA}{L&eRs#{KHd$!jTse zJqu`LEKkHADP6z!kHSmt-JsCelT;XROi>EQXI$Ce%T@Sl3pMtn?ulrYWLi|VE>N&E zhMt(IC8|A*-lxlueqx3(tkRXPgIRfX3akiK0`zp*9xFzY^EsrttL7 z?ZEQ|4$RSys5eX@W|iR%=7{Vpb&-ekUkI3!@khg-7kwvnJ#_L>qnlp-(qra6fhDl< z+tBBdrdx=%t&KuO@{Cl$uX+3OH1~CfBV5#IGGt9ZqRRAl`b37qlmLPB&qh~2t{+Sj zhRK>jR>N$eak?u+XGyqz9Wt4lz8dKx__qtX|Kuq6h4^~w+Pn6{T-1+0kiqYhFN%{7 zO-A8``ooVJyD6zMte(_Xmub&c@up-Jf^z>(j!l;M2!Df(<#GwFF&8O6D@xFLg(%lX z#Axy06z#qBnanFd*FxjIE+$BsQJj4>uxI~vx{YV1W$HFlgf{JuIdg1}djzH~Iy zjPBYEW$wa&)U25i*E^0IHgkcR%C-pI09xX{Q<>pl1X8%s#ZG^dAb543lwW9D`y$F^ z5H+aNRuaHieLb# z3q=Ut@I8_9H41HcI`m0N+Q>YNz%DzzlA#`upoN>BsJvmVX>BX8t`I4JKdgm zANf;(>Rcyo&+TObZ$Iby=NSc!Fju4bx6>BN3dVG4*s}@%ug?)0>A(*{-PG3g84Npz zp9+)fuecUPT^=+&5uHt7_^$2pJK~j=o+`Ea;2ez+?O#Q0jU{>N7O~}=oYEBWHq;%< zUrLe$Pdn3Nj^e0F$D(S9{BrNHsLEPgnv{OZeqkqbPP$&POwTNhi9y^;rKDU<)qpK= zs?eh?_&RS2&s$4`2HG0asrgF4)GURsnoSdNv_5HlfrIvX*@2#XgC6oLh1f3#P($u~ zwt)Bgum6eUmh#{1H)`Zj;xvg_Qk5kehhAp0-*d>AXa;?+EOX{L_iH>avz?*ir|-}z z@BcRS{uNG8MW#GhW>8o7o1r2o`}V;L=WNY6+|bkYC$>-#dR4&v0UKU4nS;+qzK-C| z>N)_D!!_gUuZl4Umy82JlOI*4aVO5DEcC;2~-GLGq@c!%G8;#XT`pW9}x4UQ2nE9&Fm8CJBhlU5U@c9$6+raQdkuXMU z#t4GW{_FKrq#PsBTsI1GWEK*T9N7h&>I@b%?e|CUtqFgmpGUV)F{bnRZrc=8eOky` z?yjvCDo;iV)Zri>ta%dAsfRwu8o%z;`W!#&p#xT{CvL}HQ z>>@f1V)M3Xlb4@SoiXUe`LKj|GR95>Du7gSM8Avln{E=Tob7!{>p(xrL$}_B-y8Yo zh4>2*VqlHr?p>M%#i^HM;onL?7;OOB3JQ4PbP+BC!gNlV0ADn zr`SW_^b`cfYWC*ukhu4$+JFO;-k$dqoIy>Fg#6F>j$pzRa6@4a3Otf)ErZ_-Q}nBH zIqMV~8Z0L@cE6}E<^qRTK4Jg<;dtP*?Bo~ts_kgYQ9H#n2dz5J*e!>CRB8zM@KuDC zj{}ngFx(x^YFX9e{PqZj;*6X)YtKGyuOQh;v@3k5PrLmWZcc3cSB;Cd72sQw>%lr+Xttb-2m{ zsUcTBO3WG^to>{*UZzYzjCUl4IZxZo)MUZ(8$3TI&pXoK4L5YqGm5vfx>(`pS9nc) zGH>(63AEtN+(o3Yv=;@|A80%DGVcct`-xQ{ly`BI+<<>n)%{FV-D*gN!Z%);5g=k| zhrGFKiUm8e#i?I!Pf6GR3#{pDNb2mq`>or#EaxmH0-w|+J{D;rD16fb+WRDOW9B$2 z{$T8K|GP$0UbG`lO0k7;;^Wl4M2_7AFYSp$r{WV;GxOs)_4E?n_40YV7Vk1!C?RN$ zIj`&L>)&66P74#UD7ma75Mo?0#tU@C9a7mT9Ij%QrF$JjXHVWJ!s`uYFpFZ-;!pv3 z`(HusB9cFcNen@r_xp8RL9xEli)h}rjZaAbD3ReHZ|$Dw9G%mRj0a;0NHRqS$!lsc z*N-9IL_mtd^nH+bzNweLQ4)PrONv{B2Exr7VCT#_6|dumu%cBJ#4W*%UJpAq5Y=9D z)buNwsvZ;URBX%Y8HNHWd_O-&uyWO9Iu1WUK?uH!C)tjy9f7=TzLXKv@l&6{aj$r&`*feZ0~{_LdiohK&8uy&rj%Yn=%V7xp~L-GYrcxLXf6e=m!uhn9ZmP$XM+9a`ukXu)XPoIg45_5t5a})Z2=C&l85TtWj$;g* z@^y+T;hgWnuV|+*%y>$np*5Bxz6h}}U~7FJ=TEOC)@y(M(KBcvP$G#l6K_})Nbmbt z3(5*)DNOB7u(dSt@~7Z^3_O@_aJ=l@CY?gqIpGboqf-+PS?MUo@wC@1XwG(46h4Rr z)1y8|!;AgdgDDGBb$I2?5{*C}F=QI6nnpZ*HG*x5szAe2Qo$>Mwnkk_oI3f~l!-(% zluDexg+0?-#3~5bnve`DK4JBv2_|CZN_Ke91Gm(AEd~6d=$6F-OS&UJnB{yUU!jaH zv1><@B^r&L5n4wwG362hJylmfi_GM76Z&BjX5$NS79U^iu9iC@glK_cYd+Ld1~x=W z%F0uFyd79Nd&5ogu8l`{BdfKqi;SN=^@Bv}hhRFL)PH_Nt8*X1>O$3Z8K-Ut@1*qJ zo}O*OKZz=>Y~>!3o=2&Dg*84h99hXKXUZ!{XPs$F#O@{Kf|UkISDd^#>JYz?^1QST75G7p8&5&%09LI~jPTF+CQ>&Ateci<`?7 z7KsAJY2Aa<1NT(@JDCl!SF`j?^GKk>Ce*Ekb`aWG>dg7!F)lHFZBnyD<5X=0$tbvpXvj0L$cMut^68~Bi%{hq zax8xV8sXFE!qe0g@o}Xfx)p33_57WuSzS2x7#jFenZHAs|0->UU;~@4X>iL=IrXbe z{o_IazZVrB&qZYt-8(inS_ZCTSu3gH20z~ClAB(BydQ4G*z=AJKK)&&t4`z0c|%+- zk9^eb2JhzNhyK6RE8KUIr!2}@lxjDL!HAP93gDM??<#nxh)@V`o%+v;cL!EJpBlkc zy%*0O{DDOR#W%9HD)OKrRJZ)dDIUZmZ)ha`%V+p`hnjh-?fR+xGa$r$^B?DJ(BzTA zuq{-b57aih%f+Wc0Uj#uM_Rgqd=i&ia1qm6*fYGmUlBIhoR~3Y-J7wjJ7R{59!R`YAKCZLstiDuT2Ez|tek->Us%~|L82ly>g`DfU06bfp|T8H`} z%dUxdryTQZ%s9Pxp1@aknW?H~@w$ClNPggKVlTxAk<``mLF_8Q!;6o;p$T8q#fDtR3+dhN5)I`p@%;{IXAJ0Sikg>iT-c8%URTQxW7s z`wG#47N&|(v3Po{oL6?lAIDZm0x>$TP06|ybQM=(yj^IzuX>9vtlj_9tBW(jnX-xd z73<^3Xmmd$!5)i4pDKOj*1|z#(<`ba|L0vPkB}sCOe|&@_}GlUA;(5}I|KGLPUKVE z8su~%ax{<0K*piiq9VkYaXcC9K2g@~H1oS`65rALi^d5ocF1=$UnAPRw!G@9R|j@( z{(Jv5R)`gvD}CVQ^{-c6O^kkPS|dasc(7@d6mLa5(VQE#4Og+9cQ#F$--%~||Enr< zj5TA0d=OLGTBFYlYxxNC$Lffbs=|f&UCRLjY9+m`{BkEH$&S>XL$gJYS*@e^!ul|k6g97u1n-bfmp^ATcc@Xarq zf%oR77;aPM$zpWQV>k*UH7EJr-qJm5h+8>pbU%e7BWVI;X^yF=E7wPe@G58dLVD|w zJdd^aq(v0pCq@}d;f$l>-ugo~kTp^K4{_OWao0C5EZ;!a{&puB)udRMAIsfyGLg6? zMAzxO#1Mb|NjYSpkG?C1OPpdkxsqVv*`Z=zqVa~ literal 0 HcmV?d00001 diff --git a/src/Ryujinx.UI.Common/Resources/Logo_Discord_Dark.png b/src/Ryujinx.UI.Common/Resources/Logo_Discord_Dark.png new file mode 100644 index 0000000000000000000000000000000000000000..baececa904e1590d05dbd1c8aafe8992bbf87ead GIT binary patch literal 9835 zcmbt)bzGBQ^zcJSNh94V4U$7TL_!Iv;TY1AV@QXDq^N`_oq~ig29DV17LX2Mzy_!^ zNGc#w{)XS*`+5I)|9k(~c6aaTbMATWIrn*KY@|y=#X$uC0L?wUyQTm@3y45d;T2E^$>IW+fU@Fx++5%bEFq( z7XyVZ-?d0@QXPf_u1Nl#@Klzs#-b4daZ(ibTDYT=Ck7N`qnycocx@=Zs7Z%* zhrErfYWhy@4YzA?>Q(xEmP!m*bD`Jh}^y-sJQZLQ3lnQaI_7 zJRA<#$CA7xl~@c3mfF{~LPzDWmklMMdbzl&Vr$EJRYpy$Z)hI|4SZ!h_qbgN2(_5V z*(Wlt(2wN*N{OSTyjOz2NPJIppoFCsxWRZ_SVpk(pVs286+Z&wb(j`z75P)mGGafE zJ4ob~NUb6z5wK#udIU~zTlz^xZ`@l>`@~0FXkwhcGI6Sgf8w=C{QYy&y{sVDEA=q_ z*kmR9MDw%>++5ryhZ*8ytGY`7nJ2Wb50#;niJ#xP)AO60nH+A#eU47)3x~%C9 zmwlp$1oZn>kn>cnq4{)X7gvRQ&Lj38y^}GZUW~_;I=ZBb_{;e@!u*`Jm-6&2Cxb2y zM}>q4i<9Muw(=_J(x!>?zg+v3XnaXVR;aU#08%=&8}jCF1F8hnTin!}>0|p4e!s%P zm1%B052f>twC;j{^?U*g&cT7j8nOyBT)H@{DP6lwtsSWm?%fP?>jn@`83;G z1TLt`!6(W>M416QlO7?@Jq3DRjF4AzoSS)x>V;)H$r}wV=H_m4Xa$>tbIh6M+90$s z85A(2RUI)dL|2F%0i!N#8~N+?IAxz6KH+fY)RJzdxolUQl|KKg+1nCJL-dJr(c!yaN)E9pb zYVWu2!<=`sfM52p_L8gpe)rV{vQoyo$IE!k<>w;5K}`X3a(DJ^V$ay{MJ9qU0hv%b zi|svh?w`TVr)(aQjXroPTs>3l&dJ%CVrTo{o;jXM0GX!63|HgHFE{rer?oL$cYoNO z0OMFNJiEFrI)5np*OGUHCQdlt-O4@&ea0Ry|KQzXVRK@)1t09M;%_R5r$wdvqhCOq z&8aVwmHzN+cPpY8v93}pn@k>d+ zBB)a~MNX>=YLm_|mZTc(CsH{p4I(K_MHvRJ>=w&F0o#zOV2UuGLqm%V3?Ml+ zwes2fu0Zi>At-*b0rQdq_C+y4nz*-!D7gttJ#ONb6M>RX)J8tLSw@r`55C4>aEEPH z-T4q-+UNNXG&M7r^>a=uAYvgZjKLC6J z?J5x7l(41o3(|sXyYGQc3Ib3e0qA9T!Wt)usU>IJkq{tovWTR80O8Chj7wyc{YY^I z-4g^1Kj%1F11C7-Llg%PTMOCgFvmVOYj--UpR}a~uxIYnc&Qll_qpClXCRYgdo0p5 zns&*+n8ePxPK*3~4Yu9Me2Vt|X%Ap`RUQAmz_ttex5`me78n{ArDAFhPp3S`coDu*Zn)&6d>k%ewNkPCdKKPo_V_M_~-##xX~35Fq5^3e#@YhMu1(;|7|fXYF)FVff3n6UUm)baO1m#gqs* zSevV!OG07tO72|&#wu^KV)kqQ?lCY|O?LOoNQuX5k z*^=WcUTT2KpB|Q7qr-U+oW2P}xV504tk;`k+g!?Nfy|_SGT8qarn z(*z>gE8iFVR9Mq`Utl{aj z7347;P;(&oTzYtbs~cAiF9GrohUFi8ZmHOtB_2TvOP^!LSU%1~Os?pZDF zxw}ysx`g0qZG}v;4<1JLaLkWdKqJz~al&qYl@V(`1r&(amRAi6Aj=U%8o|yc);U@b zGXgo8;rC7Qai~%R8L*<1*kw?C-?phpE%=$>o+1$O{ZVR5t>%XPb`t{e`yVgJlL|zW zG%M9M$}{~6b5l90&Ze=1RnZ&qUWDNcA|adhH`omNQo^HD!@Rv@g-S# z?4`@>-0z|9#1ahpq&2?hPfD^WL^&av1du{MrqUCM1u3%tKsh}YJ5vn+di|QBkrHVr z=RRV957Y&Xe|ZJaa9?-`M(3W$mJqb|mI44%*_Ju1tMZvUxp0|$@)WT z-IRgkrh8ZQ&EhUO^ZBRTl9YFX)M7Y*m|4uM1isuCfRF4w$SL6n?Dp!}ZwvwvXm-{O0NgE=h|TaJ5(RB1xJVfh zkj2s3B(MD+`HFGhp= ziq8@O%R7dCS9PVGAzz5E0QaS#^REW&_swn@`- z$5@brF;E~^Taj|3sk^0UKYU4 z^Qk0+hT$^*L^KXuN3wtH2O;9C`3A}A4z5?$JAI$SWOR^n2YhTJ`BTXozlWG zc> zq!5+ssO6{bwY$x!q?L==itmIAT9<{ZvR=2z-P~FWTJXcVKq@5j{~lwL3o3%Xc_{CA zi2AR5nO%Wj zl8nbHLl3wo)Ov!dC!?Xv%s*?7Tiz8hQk1X5{S(y0VD-*XZ(rmzM6S_~*?duvLhG_a zZ>7~4X5Ys@aeHRqybR~nqBubyB}hSxs8@&%6CwiTt1TDS;Wee4Bqv-F$8*upDMVds zVupMsRB{U4b{S}9%>!lrwG}^tGB5I{`vkoYV{&+J%oWRgFO?jQ9~p%f8Hkso2;M5K zdByqDJCZIJ)91QdRx=(~nk_%s6jk1kkf8vUFP+er)`#413Ec14aB}w(%ofF`J-HPE z(f+2MGu|1J;4=A6qy%Epv=dn9N(CC);60^vqWUoifh682NX*z!B-A1OUp z-Z@j}^;P=Qz~V5+cBF>JVqvRL)?w2t#GHu_5M!Kq6dno1X7&}ZOCemNa7R2>Y@W9u-sNn?{O}Gm&r`C@;cmYahz~~CPmBQnfzjQ5V^284zI^1!@ zQ;#KN^N+pB;b89dWvs#WY@>8mLCUH?A1yz%(=PGD3_^12HNNFQBi+=PRIQ)7tEsT7 zUHh#LA*V%;74EgJyRqKaSgp`wHEj1}PI?|;ER%e%3%b@?Wj8#qm=`U@^1;KUH{hts zW_15|SGZ7V{qLu+)90YmNEuQls^|MZRA!$t1i!o7=`E%+k z87HODt10wlv{Awy4{Et^xrkoTmVRs(WaG1rFM}H|O|5^)iW_l_S(KTZ5ek%ZN&i)# z`@}(b*64^57LPzIDEb~O_h#mp%yTH@siv*(D=r+kt?ewU5z@xJi8ein_Ej_JEx5ikAj;DAJkFlnKYThH{^L&@QYW&0c(x-S2AIo0Q z-$;lm)tM%%Mtws1EfHJtobY?nIa=K4Jh9v|HiexZu)b8)I!in4)|#Lp9ZQl?I!fnZ zv^h2g)FiVbXx#Df6K2`A0pcrajVW} zVy!cUMN(nIcX({dA{GVj&hEeBw^Ev}wk9kwqI<$KOjS~3(|yS-c&~UgT1Wyh9_;m` zD8BNQJ)sZK;GJ5_&sW^t_&NI5UDnnlOD61k*RVM62$*9>JSK-`n%dd}ozjW1A6_PI z@{%2l^GzdL=O|E3qjDn{j#L<=K>`&vnQzE?x z0p*{Iq2oQh-Y|R&x{|ED^XlBEpG&!h59~(be`&L})@fE&>#%w{{28CfO5cC*XFp1d zm2>TiYqX3U-NjjEtb&B3Lh#efQ=)>Dhqc&J6y`;1g`F*dyoo?&Ix1agE%BBOw-4s> zd^ny)uD;a^N)fV8yrl#tSTa*+skF3+Bkyv;CXW{-g3O}4&&qR*Ql<;bN0c ziC)}2MJd~mSTyUPHvk-L_OKUzj0J^65d#TJJ*szXufnhp61I%*LQ+pgoj*=@;VNL^ z9V~Sd_P3qCb+>F^!?w(ilYcD=WhWz&YIAv)BN+=~W&mdLS-DP7W=_Kyw00ae>Go~r zShhTQ*ER#G?4%!Lt3PJXoQ62GWFShmf^40he#PtEwS$2z1Y+PXtY1sc6kNXi9E%G+ z-QhVih5WKGv{gC&MKJZCRH{lV!5Ia&tztp9-A{^3@_E-hph?fdNj**u=2bNa{%U*h z$9tqy0J+kqSoGj)>@)ks!p4_`KRMXQ`-TAtvc(TXVF@D&NoRzr-Iw;~V&lDc7zDEM z-q`K0<)K_q9|1~HP_TE~`WK5d&`RLRs!lpxrxHz7R;W0#Kx4Em#2!oLM9rh}$E#|> z?pQnEO#%tY4LgbDH z&m5kut0E0VkSmA~##%xWRapnYo%X^XEwBc+?1Xt#Hk7jn@ixRMIoAIxs@u4&@o2g` zJIjuGC2{xR#3pBVb0cpIX>k9&99FL9KiwhG{EgKm`oel&Hs~+T6+grm+;5`3 z$ETOUmnX=);YT?^y^+I8lm_ZO(6Q5>D5TPyh>4PG%#h}&)nv~hfABOnrH(u2EG^fu zR)f9sEBNpbwK~{0wh$tscb}VBZRDKq=8i^=lA?_lTlZHg zNtK#8P79*<_C}L7aR-lN6E&}i7H1i_i`kGbkZmA6NaPrf2j*y9T!54IKm<2r%*fW`8{VOSZ z5~lC>O~A!3>9*PNX$h1w^;&MlwS-=+57fNk^>W#=OUhZ<0~{j8B5ZwGsv7Z&0xm)R z0w?to&+ffFLNnuNVGCTD6Xe@tn!)R-AKF}=Wu0oWWoGD=9v#LSaHgi_R_xI_d@nRZ zzP9lp`=u?AZ7yeTD3W(=a(hhQ<$WNJl9O`1k@Ien;U|6u+pBDb5Z}U-SAkIFR;B7> zmwnm<+aFV=0@*R4OjtgF_8E&0tZ`Ah9zp#Zeb*7ESpJ(LT^!{nT0QkCC-5ztmWzcN zl}BD4r;Ns{Y?QO{ZILFHnWOhgQnrYRQ{~r|8_91!{t%&p6>n+S;b*TWWo>qZp7W!& zx8`PyOiO9*Z<1oRDc$7=--Oz{*a&iioEYyM#qom_Grai<*KYbZc?fqqe$hSpty1K+ zQxu0J!;DSo;XT^6h3Qz3&jhKw80PP2VL>n1vrK`L#MlBI&pOS1L++Ui?j?iO9X(@5 z{q|4P-y0=!@#ckBKsDEqZz`S*hfW7lva>b&4ae#f{YGJ(Lu?zMJr*<178=49e?_gKlLli;-Q@uw19_R*7RQPmUr@9mSKc*{AaI@SJsd0_!Jgoi+)ld_qi zqlRHxW$|~?;hDOxEpx_Hgf@6sLe8OOZ{hYQk~oIeC#~ztO{r$G%1t~uM}m=~B&`fs z;VDhhytzA8OcNJ-9rnbB@gwiDVle@FA@4fYBHJXwgPuM`{2dXBOYf?5n0xre!k}N~ z4(?@yjziV;25Z3}hisURMv>pUAB3{u$xSKN!0PuQ=fKn4^+20XTO*E3%6FdHZM?}j$vi(x#GVFfQ8^aC zxjhj^GFQVqLc7!#{hVJ~2E+_^;wL*>O`73xo2dRru{qN_9Ru!n~2 zyV>~hybIp2L`pC_%(1r^Ku7?+85e!RRm;tPwwG{w* z#>uV#dYPAtQiOV&kMN`gG`a{+kBAsb!c7h(p;!eN1(8JmOXmL%aLRsrfsz=|OZmqR zZv4MYHlAK1aGCHI`+u?Vhcx>~5mwFP(7J160G}bQ001^>{KyGniod!FB+L90+Yr+1 z`<4LqB?csWhX`OSrlBIJ3;Vw+WupSNZxR8;x=sWw4e8P@ML?e3zqFGT6V@|N2#7d< zTx0!T2LJRnb;-N5LpedL;*B4q#+SqlZG|E*-MDC`D?oC30Hit4lkEj7QGxm4IecYH zhMtgbdp;l#>0t0Vp-B^~MSE?r(Epk8PtyU7wyQ9f-jW9u6s~Lgfmqq32MEMdEAZ6r z!re%N`A@Cz*{1fX=nlH2_UHw4ceccb^+D^ohI}i=(r|r+M9%q-3p$Yok3J2qj$i$a z={w#H54^L{|5cj?ZhR8Jpb7uN_W7juNZ<~BbU`nYbV@SiT?OLA#g7cbjt}G5Hkl`B zI%$4ENE5ae-qA|4OS{bpwslMCR*yi}JSjLsedf;PPtqcIiq{A_8 z{zqYw%1Q0K0f!!(lS;6y&FKaPn%O%~oZno0i6Je}eOPIkXG_-hI_%}Eyi?YYKbMzw z=j|arY_nZjF?Hq8HC={pYD$(Jv^>`ptc}bIAa|#~MW^(#sDZYF>=VOU42v0?UyMOU z+b-KqL=%1tbggUYbD4L}6&FscP@NBk0|UZ_vJdTNdf`LK*-%-`wiJ|_1%HJj&?L*X z_k9WdWYXe=NLh8ib#n;X#x*N(2#^~D`1o*Mq4ox~IwLPBA25&agb`OdafGZ6b{Bvh|Y$}Aen6MmV z4{BcL94{<0;t*Km#FDH3@rYKS$mn8dVsdYrO&)>TE&Iw>lG`Qe3?ZMT>pPHYtKNR&bSIcV3J%h(`kFl-x73@^z!oaN%2d8xN z^JW@-VN`kE)&*s*-5deW&Y0~KR(elTJ*m2~x?aUR;?tHNE7vsRelH1r$d2Vec=(yz zcddXr&L=JE4yH1yB4ca01j$=;^olBSTtkj5?(tklf)O4E-)=4F`9OX;S`AuwF)e(a z(h|#D!Qn!al64-oT71Dhr-IRd<(mfF@-WuuZhT<|C*za>RfB|hE-1;tpTyB<p7c(c)2 zBUGW?&qnOUI?CI|>zgT@>MJ8GYMi+?vg|1i?8XS7ng3|IJlvNz>v|kxUDurh^kHzF zTLI3~!!kUG4=m_X@*?vuxL(>z5l_q)2|yqsPxfc-+e3K6mhDmLOf@)bRz@MTNlvqf4)kBS=R$^bl^RDg*>6Nr!(i;ADNLhGPme;DQ~L$+#q?MN+d;7B zrW{F-D;R%2ZwmXNyMc$!s@6=5xv_Kple*XZ$uDm*l8aL>M)kI$gKhpcs}#B$dICsL z(o*Lp%{%@wjQ?~@L#fg^fxO^9&S@gnu{Md%+Q?@%qIcC^j-0~s9m3rIP z{L9;9D5PR>)t0xwmho6TwcN6Hcn0%bj=3=)vjS@W@r@N|*|YKvp4O_q^6!p?Kz-nF z4JQHBt!_mj;Ee+W#-)=stlgjdwnfSXSw2%~MHd$K)a@)o{;|@?jk5Ch(y3r`Sjrl;g z%Brd>id}*|#ij?Kcz;sN18-Bi6i!W=8b4my-*%ks^+)`pP|7qkM*HUa*NH!RH}$|Y zKhZD7oQGs2MLf@k7)4?5*W-*~oK{ymo30Y@j@Xvc?dP>x5_r=vW5I4|Xn$RAlXm?d zj?#to0b&tb5)Ofsr+Q{R^kubgwL&k~r>m0&KIqL2;di4ru6-Ym!p1m1f{ssNC&Xaz z;UJ8eBe$~~Gi%*2x{&KP0#@tKJo?MBB_hMd5(ZoNgdCNBK)5XwPJ)-Mt_k**aF3Ro z$$S{VHI;gt@K?U>0X(p@D>eP6Dk^sc;OX&MqvbW29ksWA9=w?p$*7hvt}IQt&_OH zSncXc`bur`QKe$NrmYXpiUt1R$|!Mz6dlWeM7K6FHdwGMOy6nSKmGJ`Mv0PLxcJJO z5AhVP*v`<%0o)JVU|6Vy+u$okrJ>g)DnBR8Ctl(Dd?`d>%_eSx(FFLmTN3@LEsOF{ z-fR?xw@?Bu$lMV+CF%Z5>F>R!m!4^-zmn=Se;27{SBc#w&P1O!NTwt?Pe?!P zJk%a6LPHUk#(JzSzUG8M*T)6n*EX&9y_ z0Y%c~0K}!Hv%!3V>^hQWG>KwKiZD~35K#7Aq}#H`;inQO$pjgTU%6io;Et+$K^KV>uKl>39A%_go>GALsDA;a=^M|(!_}P;~cr|bWEYety^^Ke8~)62q`~l;FYw3m zZqMh|gO3(#jr*<}OxT~kjOxDDCriEwMZnCoH{qq4{EhJtR|yB0j*DOnIus-^vBciW zk29rOADqJ#-e#DnQgJ46#u7Hy@t^j|abwd#?D%WBG#kUDmH z?5ZO}GO2_)>vE_1xLI*Qx{G}!bS^DAGiG*O@0x&clP!(S=s_G= z9mPq0*5xh9 zSdM6EI2se0cfoEJ8{jh+pd3-(a$0#fYf9gQTH3 ziJ_r6G4vN5cvAeN-WPnFandq$p`no)r2d_F^z_?bG&Dj@@H?s~&y>}1#iTKe z`1KPBehJBa+NnDBqWT>CSdp`|m_I=kG?~94nfY)AuJ7@lHOFx(P|FK&u6U_^5-R zPgdw0WAHx?9CR4-cpEpE$hfigGCg2w#B0prgdlv$P+jov6h?17akZc*dq6|Mh;GxX~V+^;S@RX>!zz#+dvgp-~LBX{jPwusgx z>OSMz!s9rBqlR?jLg%7nTqs`lkpZ9gg&tYy;rbdrq$T8k-qgibsXu4q@P5+2rlJ%DqOReNS~RzF;^*g0-6Hp%r}r08bQ*kG35^g49h zLig~e&qsnFi(|KT02J<>f3>J&b%w-@bPW+n$Z_s5P_+9&l$S`Tol z-r9#WPfU2Ekhi4Uo6;jcS!l{N7stZxit4yTT`|zA#)Jllb5?h-=E4PM>lRK879>?j z^afVPhUQ70$2Bc0Nk@hc`i`Bk(LFTj@LK*3nd;tNbkbeYtAC5ATtoA(@^c3G^@ zjpp^>Vrb|NEpanHUBAAu8P8kiz-LJ?XiPw5$nNQU;*|~fWz~jEI)CfJ#Rp&g` zRpQWiu$L5u+8e(aSy(pWz2TM)J#<>#J`$eZ?0P(&3h`*V+7_ERM?~Fjp}k z^s|;`doec_>h>*d(AIeRk}X+;{QW^JIaAarUmrV|-gf4UcFNUmY=@D*9Wqe5Yw;|c zRH}p}I)J?0itjK|GEyke$Hu>EGsG2)z)rDZWZx=>EcZj)y+OjJ7a4;Lp~}VI=xv}n`hub zFYBJf?*}ukHo9QkMMXoZkm05<=c?8{jGHK&?8>^*)^u!{r`sRy!<| zyHG3g-1rCPbHx6W0g*blvA76S^Rb!7suhr8`N~5$2D%tO8CR^Be_fW*yN;jAqc{(Y zO9I1WNC{p5PSUokXBk4I&^2_^i4OsEnED+GBN$Gc1L(0m)0M-HX4b26EVcw3W(W$G zOCNqz_cFC|S)^1i4!Vd0hKmOP#D-~(UsC~`{KaW&@QgV{^FEOAg>E<>j&&`fN6uQZ zC2PonN1wT&4A014-J6KR_1d(`n1O!ssBLX zd`K|e+8MH?z-dwdCJfh?w{WD;`HlaEcrXQsrR6(B?&1e=P`qxM{l-aojJPp1e2|lC znam&F39>7coIIl4Q<6Ck$9AqwzVIROmu<1+s-{<;oX`{mtG?q|)*`u5m`bKO=Ao)& z{;s&8T;smV=}y)eo8rj8Le=+9taBcm*1fSu{;f9ii@IP8r#xnoGX0zU9m%6RV)&Cp zbvib491xjxZ^-0tAVE@nYu5HsFt=W{ffHAQq42P3s1#D5x5o%c``c*HUpcMXUNmWD zPNj7IQNW5&zZQmXGq7ta;UEy;vfe?0oFj&93{!VO;=mtY^H@r)WU+Zyntmi~I- z>IM-E-ffy*2 z9vE<%+~T!mwSpl_H`gsdp>X87KaG1oe9QdvK!zyQAc&5ea)FHzwdGYU76>d(h~ue; zLt$zqgzA$VD~?nZ-cF-JuqI%OAfMj9de3psj%G-uoe+kTP=54>>kCjKth0x5Q|<&D zd&!Ck)gtR^XAk)6sIo^F*6eOnm8(AVJg*1DIihyYaqIsFJH*h#;a+Mb3l%EzX3`o3 z4Qxgokos+i7)x1BSDw@tAc1=nlgJAzK(B=KRJbFBf@u@ZQ@LFfegg@1+^t68jHCQ4w{!ZMymR(M!rYDwI8KE=9 zm3;~GW8oBlw@@!=YI_yKZ$ixaPb@*Qk55SV!|QSX^iUM2UV#~bsgLw;TRR|#FH(WA zhlT&_rV7`0dnjC<%+&0KRI0n46c>7r2KHJSkS9U*MZTF7O5xaa0mNtF3#ZcJTnJUK zsbPlbOt~jM8;cgy-t)-=;1 zx#Tgw15T*(s$5{nH*{h5@1Tm4;wxund|nr%diu}d05hDVJw|Bgzpr&8&w(->i~AS& zZzH|~#5*a`A8MMS%pl8xVO;Ep1^a?@w9eG!TnmT4sj))J0w$%ey@X+T_ecs2kdioW z3XMD$B3pTOorGiK69G9!mjHOcG8V6|zmSs)FqCg;P_RR8{E-P`Gf1!VEkstkSUW$6YMc6vMNLn^ad20uDdt z?Fi&xe$lLi__n?V8vu1%5+3kf5RAA+o;%Si1jh~r@Av7lOE41K>R5qsp8ra#z+o<^ zd5|F@gFx%(oSM>TUkAl^YM$y!iv0&cP9i#)@z56wg`4_lCy8>k$8;D4>4Od0mTOK; zu|Eb(Zk`9!r2+dqK?uOlxMu=}eh_Im`4wLPh<4Tp5Z9c~7T%i=X%fg->>DUNDamm7{D>ytnw9`ucW*2oocOs^ z2)Fb&X2bm|;0+@c8bUeCB<>Z1uM z?`@8%7`KOy^-RxVAW>s?)hX@}6P>KG?iY2%JS*UDp`UBdrz=`$dzBB*J{IGC!_P)b z{yl4nYTkE00S9qRkN8Ki2QFW%jF8p0g*!CmQkYhWXye%^t*3h`-c61R)|x+(M&n9@ zGA{bk8Q@l}*ftIs@mFrNwIZG_6m~8V(POhwn)?U$?6gZlh5B^xTJq_#I*G!{=Wz~J z>h}V7QW7e<>!T5B-u=8`7^dtNm&RJ>-hdh6HLYvg`a0_F_ z{qgUELjED@1Q$efjYPKUJYw`y;Xx*1kc@&H}IvEPw!&LK3w{M_tAl3Yc?_aWS| z_SUj(gtB|QAYkI_9YkBmDH916MPIAL1YT%V+86Wr8!$4Q;KbNvsXH<#8 zWJ(l|CUKC~RwYfMk-JCV$Lz%6KOQ=LtNvHGY3wZr7RHlYFYF3QGa^O`!|4~7NZj)E zYmp(l?<%gWSPZb6s4rbU;z|xVyQM<0k|x;xgQlSthH+ayjtV!HGRqAt-{(yGU3Q~$ z2egqm_nLqxgj(?aXJ^sjOXIQbNjl+nHmgPF`_u{UE*D9`M;~N#p*4ftwnl@g(T{!v z!VK0e2mY$&XDOV}d!%Of%n}tG(PFpZojAl3gzmZ8p#LzZr*DCa0Om1qD;~chIFm1U>mlE(YoC zXYQZGjSGdWS?vl{wS}SENJ!*mn!6meR#Nrk5mrcp9 z4&z+aY%#^D4*8#@&46CjUYTlYOdAlcb34mrdz)M=O17)#%_CMfr|R(Tj0mw8MAtmY z5NA;MaDSJA@n57^tmX^GJ=*wN_u9fhf-9Upvg(*|k?L zMf}T^cy7xfoL+Xxz@=N<)VR=e`w2-hp=N;NBRE6dW|$`gJ+5ijr@SKmNT`TkxjGEa zn2lMMCc)J2XNJF=vkhm+)wUM&_5>#_On5cB$q7Ml-;C1|2LorVqu&(Pc`5ah>2TP?blbLRynMI#|hM@9VLh4qU$tDtJffmMo2zy{W0b%p*-+B7j)VRhqW zx~wtK5@*nVCeO3GR@oCyaNh~K+l$r|%&;y3euD3MPA`}gWisn+0_Hvogc>niN2S*Y z(BsJcXBG^7RG0U+ADl=`BlDpW_M={Lz9fbC!ULmHvZb;xAC5F?lJ6L={B zLB(X~aZ{!m{p0J&aSP`Eu)YX*MUwH!Fou=@Q2prhLNR83I8pYGVI@M zf$($bdYoqJdh$D#J9tz>Fe7v5f4R&!Q(uTxMe3-qehwZ5rp=W4O*8Uwfh3gy9^!}q zXcV~o()X$YHn%BBW1E8I9Z=D;jcE$ra0cFVee-kS6slq_`&pww;RBq{L-;TS*d^Q{ zi}`I0Rm4Xzxf3i!{)@>aEEt5ftxY}xg>7Iz!f>o=QiQM9PQi0B4b;GmtSu*(=O1^@U;@SE1akiu@{aE>ZC!v~uV zelu65=7QP-r=QlKxuD%)H15ZP#j(Kv>a!2h4`#GGDB;70F+Fk$Mg$zVsFKtLoYU1;cYC4+DmB#$dVrzqvnWGWT73 zpnJUOMld)JV&p61n&;iVsTLz0E0E?Q|K$}S!{QA=CO!a+jW<9vl-O<1X#UDeJDOd# zD7wAWB_emC!46y;8#h?bXSfUlVEy^&zlj_oAXZmHnXOG2m0}2XcvmdqaZ66zS@Iph)xm^ytrYn$4I@DG=kflig?oWzmIF*O&ew`J$Bjqx@^&XEB#=H z>HOK}$P?>Xaed(vQ!qD2R$$eO`E7zaLBg$ISN>%M*n7$VdlP=s-3+4Pw)fSRKxRq{ z$yrj=S^jE4C4XnF!_WpCh6F%xg#@1RcU-lC_*Np1HR-Y6-`=gD|3f%!=WWy+-N<{lHv04>`*ptTo{}7GJ3XzY9A-P+o@Sud2_p zthshBDe`>X`o<*R|FQi&y5MSD3{S%eaz8wxj~fu}gNOfJp`6ogzt3YQ!-_$mA>ba_ zj!zZq-n^Y=>4E-X*pVXvk#f&`6AekBdysbYPxa8^?V#XwlxNI9WZ2;NuG~K#!$7sL z?Xj{qkx3_AGw=?(i!L3v3)ZHKf3HR`&=k0$Xc=^ROiFl#>E0XD#pNO`>uhaVThLPR z@&V7Yg-IAI4>RIX6!>mXQ>D-00zuW5o5?>84og%I9LW?=2|R8?XhR#^#!c{hxn>*$HYb(%}fc>x`^+-lLP7BK^6- zB7-RQT-66jx0(a6K|8GHhaicg6?BfLg-Oz&n9W2N;!y!lDGwbU(;T;da#`?I zmR!v~C0g1R#K{5a za`b4#Ai~x70hb~b+C;V*z- z=mF(;Hx?#<&cl1^1lis5KXoKNYxJoKx~kW}7_jJmNEkzHo zHwZG4wLgkd=4#sdohF@`g5WieE0>e!l0&nPVsu=B7LYuht5W=K6G)_pI7d~F)W)BhU=54!* zu-yIRfy=@M&?rBm8Rt$;xJXd&?s4dae=bPmaS9tlH z#Fu`X2&K($(&e?^orR;uK3=I`33);gNd1;JD_6}uS?pCHo2L5E^1gtglS1FN>X&g1X46u>bDn%2K&+{oZ^ebb5ndP4CfouGFI6?#0her?xX0bH1~cI7)=emnv* z`<)>uK(b`TfX5u=)RxA_x71z=vQy;$k|6O%tReOG{q*y1Y5!kYcI3_2sC_1^&VL{b zfd0dO@eEJ=fp7f(1o4x*l-jI^!gGHyjZoXqSlmDg9k{fAOiWw@J++T-**d7BcYjs} z+BoxlB0!rf={gC1tzQ0pDb3D1VHN?ygaZa zRE6KYoi!i$0!JXy^MHNPhAL!xJz9nF@RaXsggREvrPtXPvzdYVHPVvUK~aJ-Mh?;9 zRg4vnd}u!h^1f)Aj?Kf}^t&4AAl0?$zyOP$jFa1~K#ytgzwh6_2dR-3Y^0@M;B9*50I#(R$!ASI>Li5f)P3Z5D5C z!dlU}TOankgefkq1UgnK`p&@eh~HHp8FLR0*OFZC9MIxNTw>xbEi!j)+h~hTDi>>6 zQs&o?q~C5lmXyOCYPmE-6#{i1sbR~R#n!vvvP zOFf!V)@~F1>oV?|OCz%Wi^7wB+v|_j%Jgj4;OZ3Vg?(e>iCv|Ief~F#X(*YZP+L6| z&M+vQHqH3kK40{}CM$;6dH!pEXpxrs?8!Zen}5R%f2vRx9V`a+y6HSYXo8+_^!jMY zaG{qUd#a_joSHBt4MuYdCqyS+b$xM45-;hD4zCKmH+NqfqAWVNmDF2*(?^PL^;CdV z#6t^Bi0^YrJ{ITxl4x`Gw$7;8ZB7?tT+XAc$Glk)HM94m7Oz3&%(Eg!Vu;4Icp0Ai znoSxZ=3C4@q?XYL@G{@<^}_InYkWHkL*XS9Dq#B&R6UG3LaBV;MDaPFs<9L&19 zZ#)l`dV0xb3i520Sd(SV(kK!6$x=JbI!Y;5fW?t>=COR++uMagxhg?0jl$Of;}K;R zUClpRPrHvse6mzCROjUC6*isqnYcj=Q`}GM*vCS`VY5Uuv+E7yY<#S70iA@{B9uT|Rw&AKio?G2M|JP%jwTI^1` zIfTY>40nZm&e`EyDWiTX3$F@d?G=;C;Wj3n@YNo*6Cp(`Tk5__{JPMF-DCUr+AZmL z(D9Ti{gmVQC2m5~16N67 ziEuZ>@I*VK!ia_CNQx{nq+y>GhnP_>mMIxfw=W?oP)e~RMy<8X< zLk-<|@4+JQD&77QL4~tC422VQ=6LBijJdUnzw$?cAC$Y>p4#I2MDWBo`H;NQSgnk6 zD#r(LsY@q4ncuzEFpqJh=&P||=(ShM@bZ=l9Mecqyi)9Bls(ClLW8fx$jeU$o`*v;5`gIu~6GQMj&(qifRW!J`Le0ZZw zVRb!AA8EVBn3%-Mzyf0A9IA4LvZKMfe`-6+E{eM*m)Eo{F4rgO^k~{F$Gf_>uHYvr zJAy7>(({Z1_=bdj=AC+ejeJ@3z+@Ufur#6A`g`_A=39zooZ`b@zi zYZ5zL;icVu|3=)2G=*(1vmtMZGSsqQV(5&BHn$28wYzwZsA?$7HCVwo1^<98eH%hJ_xb@4wJAu~aciQSP%zmLz`Qc9Bs zb_Wag$`+PMQ3C$tg7#^MzYS@@b)GQzn%4Ti~0 z6Lqp1tWmVm5>`hXzBP~My`J)&3l*$s)VWPiMcNv)M{{euBXkJ$>WPXeNr^?X!n5(A zyz(G>qcL&iH@oonXX{5U<53>%(N{D$oL}ju8Rl!T4MzG0v3qHTSAZ)4x@I1A4He3?H1yR*DeM(m*Z>lcHq36(qjDR(up@d1=DPvW)&sJC;WU)?tJ@ zq>+mDXwlA6LW`Si9C8-^mwqTWw$o1Sklpf}WK?h8lgb)GwBno=q#$HZqH{rfMbd(h~eku;J9N(1O&R)#c&T7a?d3yql}pOys!^UVviv zB)V$+J{QM|p{Bnn-HlY_c?Z`o=ks5Y5Q}DEwOvvPRyVdK9L|+ovt2Qo>Ef9^M4QLv z@SxBYZ!Xm72PnYfN(KDS-Fy_q$Z89rPVVVXJ1U1Ky-3=V_#ng0$(7EM(V8h*jaL3S zjD@ji&vad#W#<$j#Rr}Zui~q0-kbPaSpcDEAR};HjfK@Amf*YJ%V&Grsy!O6 z9|=GRtEdZexwk)q>4%ON1h4>n0{-?%$ih-B{r7X|3dX-z4FeeA`pDUPnv(?jwNkAC ztI=2IwAkqK)jue2e%I!`z#e2BCx& zsTT0lEweyDK9h^v9VKNre7=)!1JppK^R9T(EU{|X!fRanx@{|1iK%QClP)Aij66lG z(kC<=u)Kk}VP?8YzgUDX_v4a>Jgxu9?Ke3_-c71G+eK0=8tS-DQS^7;~<0v#f?r?!DrA0>v``5fxV~gY;98Z^IT-z?+s0Z}`P~vj!-idY1V3 z)P&8n7^mT(a40-_u07hpubs8d2Du~LIpi=lb8r^^3(c(D>Grn|N!4LsT$|7Kn&`I@ z2aKGbI&?8Fb7j*x$P{J^6DH9$@cTQ}LK94zE?~FbaBNzR^Ap-CZAc44I7UCs!;BwD zQ0??;lgBXGM<20z^a@1SrXVKLGSl8?j^Wo~%O&cUKCCuEK-JpgEfqZ3j z&~)yDeavwvRI%j!Qz~|oLczlBwVPCjGw`xobVO6+yGX* zt4J}eL&)~49rBh#kY*0{{h}# B;Q{~v literal 0 HcmV?d00001 diff --git a/src/Ryujinx.UI.Common/Resources/Logo_GitHub_Dark.png b/src/Ryujinx.UI.Common/Resources/Logo_GitHub_Dark.png new file mode 100644 index 0000000000000000000000000000000000000000..50b81752278d084ba9d449fff25f4051df162b0f GIT binary patch literal 4837 zcmVt<80drDELIAGL9O(c600d`2O+f$vv5yPT|5N-v!bF3pQmi>^l zGt!*V`+FY6AAw};-FMG?3m_sQqSIEOaL(NYi~t{q?tg ze#=Tb9R@QZA4CaWfu;(|M+e&~G$H-!uacED9tJZY?F&9fQw?aTqFOgI97$Gnto(Rhhs2%(lAOB z^)(pAp(->Xy<&5>9|rRX9YtNEsg4CG1Q{@T@2}53q~Ae%F_?SkXzE{JQ#B?DrSwNx zMfYGZJG8m_7Oaj_E71hB1l?mW!9XUYLKDy}7H-kO^nqNX38Vw1q{6}jy2xN^h5P^p zGIbRe8qh@rlTB8$Du2CPQXg~?!PKR4QXvbFWm_y{6gTT&>OABte{DcH+4$>y&hwzz z2GfU9)~>z-`;ob-ka7PryI``}x;R^8*t~s&jQCJWv-KMo$|YI*>zjY>Un3(~R7_S$ zQYD(v+X}{+ub4iRvZj?)l0@OJ8(lbJn%Q8=h^xP3aAylHG^Yp7UmxVPp`-F9nQY4H z?vGF4h$|ge`Rkd*rmeY(sRKMWU?}M{2crW+rYfd3U9%c}qsd(R%J~LHmz%&Vl9OB?Q-4t#5KU*}`F zguVvRe6~KEFOh&Gg2_-)LXrsQ?1Mkrd|iVm4QnkFvzj%SI?%&DC8cIP_h{{GO<9h< zk^!>~2+a~qhLQ}KC7hE7Q%@Y&g2;}w59dcrXwqQn2Ip@evPI6Xm4)xOn8;*bcz$;r>dB|vlivRp?NJw7d@Cd0-N;SH=+TaPcg?C zwJEC`oo_&tpJy>|3m7e!JQ9R5C;iN)v5qK-8B7Uffq8w`t91dMh+x(Coy%eVH~rEF z^BE$D63j$a_U!$o=?L)?z5dXT4wMoJp3E73)sMIPDpMj|r8oYu1wU;gcrdjIdx!bG z?0fG-UHGu}*PmcW=OSVJ>@QhibK7@HB9WF^@cw4dU?w(S`FPBHlZI4wyhupd?2WHP z6UNUYpD%f?-eF!90?%)T4rVGxgM9J7q_d`I^i4+o8`3OyppfJR+=j8l8T5Jj7xN2x z(tEIACN?$FyBXVu-qwu)J)Z>fJ(?GBu3@%#2us?&A`Krx-TE&`Fm)8xAq}_D=9U=HF}7&>UoisNDv<_rCg{0BKPo`XccD*bg8b9GEhtCYM3Q+XaP&n*rif+<_M&KhV5 zOz!6N857Yrrj5V;LO2zg`8%mF|KMR#y~59nCcYo5Li&R3Uc%`mU;m~bpCH_eS{~1v zkbV3<{Ld=00jb;#?(BsJX9ZISMN;Zpilhh*|YP z{m=8HZh~;5KjZ8_pMMO`>-20e(x|3vo$k(&Xp4#|ZFPEskV2aDmt>W2Z|}oouf_ zOEr1Fwg+iRjG7@B987&@S|d&WfEHOM4H}{C6-=#`1=7dG(;LsbHqGBfPIaK#Nj08_%tEVUBhY4+c{^s1EiN>}M`c0eg-P0v)TEmIi%x zS!{yScvfGl2VbYhf?2>WHfI;2ez<#^MF-zd_6E~%Ggee+PW`3@&<)ZrVbjH-=Io)0 zX|-ukp}BuV1zHR}!`AAX@!sa_-ov`2R$GhMBrDE#P zvx7ZX4CUgzfV~6R_BLntHDxW1XjXF58qlH{?r#>m-`E#SizAvmOP22GO^n{dmR~aW zQy;TV=kB~iT(MeGm%fhWRDK6L9(Rx6+^v`eY^nTp4WbTxfd{+o`b3KE7uJJ$mGD8o zG$S1dEMZ5{{bDzmmim{~)c0T{b1cnm{*=8R!8EwEiK~0)C>;nYVZ)Q|=8JB{v=mBK zOX|zg8~Be5c7s{K4pvL*MXP278}fO!hl;4jrSGlyKlXkYRc-I6wz2E()ZKg zkA)H05=7^*(BirunSG>3iCFMAh|W{Nh6|~fR^~4&5S>9s^ed$Ai3HQZh6+UItB}46 zOTpy)C57-0(&yNerKPd(25+j5$%;uKSa==%SAzK)4B%2c3dF+e$ep@zEm3aFG-Vx# zC?yxHm_!M(H26cb6sAUHi9&ElpPi;`_smVA+*#^lGMKa&9Q>iBG4Td(DVPpK=VLGf zV^fwwFtO5&!K9@zQ!%ZqL3JQHpF{e-TMDL$CI}_ZLdE=UsVVyyL}xH`zLlw_td+BG zDP3j`1u)geX-Nv$a6c+r!46Be zqo;)U@reR<*lWsi0EkAi)Y`farnOt!u{ld)SZZyVTKUs@4x-@-7_nNdZXX%C(MpT` zOd3S{m!=Ljf7JcL2=+5+C`+xZ`>tghOl$X^T!W~;KVipx7TaK28vwHOi>4WAGuFY5 zO8)Vv`-LHerJVvatG{5&Pfghp_HcBT`Y2$_Lojt@*4nhmD-HtDG5+CStH!iXVfpmMf-k`UDW|vQ{lc*?zKWKhgf$ zzpzKz_YTuvoKdkgKtyi6E-#mB&%9alH+`#rh;IcmUa`&5uZYuN<_Py4jbIMRA zp%mr5ZypNfXXIhSaONkYP>Q`paCPWUXVRQ)v00l5?NiDaf`ff~o3Y~9{V{WB&bFjk z`;DuEZ1c~bY>v;RQi}4>zc?1mT$-~jd8fT$IBn7{iB!s*ros*uzZH%!zLMgYjc-C+ zfs&_hq_W(yKwb_uW5uakz30@N?UF$uR?o!g!hvtdFO=eFVK`MWt*@Q!gVi%JdgP=u zT?^z(_7GQx{^ik%nZerGKBRiy@g#)#Nejkb(rlFho&x#$ax9eMR8v+gp_({~Hkjhi>)?eOnioc z^i5*puUD8)J18dm=;RP3i-(v+qtB5n=xBq;&FhV=f33Xi^9P3nGse`(=&1^=p0aB_ zg_R%`nm+PZ{dl{i<21D*7I+vFU=a7a>^o-BJD9>h0b7JW{rsG8I;6XHQUcl@2`YnI z6$}Sf-xP$rRXz{`Gfw4V=U8q?XPe3h|y1dOww1aU_*uGG(QuS(?3pm6L}9h$9Cwn+n|am zB38}T7ESf62K=3NpPp3Cl;7DUj884jjr!lO?CjvQ(KwewpYuT#Q|SL7=4zldMr_a0 zk&R{%3gs!|G_VsOP2+CPfj?{H`;=g{zPkmftP`J+vAVMPh*>*LrK(x{3lG%&JP&LOVB3lS20 zXCE|Fo-$U=-p*PRJE~#|t(sF*fue4Xzwb@o*;6_iC7T^OteU-@^_-8cm@OZgsrJr2 z8?r`q!is*%sHKM~W7RzA?D2#U!E}f_ebTDXa{+KGkr$9GB-kP|bzaAthBkP5WY_4X zY-@t)la|B4Mf6%>=N@z^k*8eGgF07`DY3IFrkJ?dIH*Z0BJ7OmE4yZFOIK;}=1o5f zwh8*|iYc^tIn}7+;DG7A&p8HQ{zkq^(5_(f)IowNw2Do!rn0CwU<5xj~w;tqGg7@}jt0joXb z1g-4S?~6TnQRW;?hv?fj8{@NmXYwK95CNCW++9}irK2;A4|ciIfI2(%t5n7@HDnyvCJY=eh+3rG-CP1to?41ra5ykLg z%K6I4f+=(*Ow7dxpK9K|ox*!L^(wAOgDG^=aIBG9nRmQlI4Pj3IX1da9!wE=r-wsx zs{0y5=NWvf$Sl-xZiw6Uj@2`sx>?GYs|}W{Zq}K`bXT)_Mp5S*%q?a%OH;PXHx*=> zBjy$?=dTa72DD}crQ<&8&ZAjPvht^odfH95vYblp23^J&0&l}_YCF&fb$%;y->Z#FC6`@U~7xqi5Tt6Z-0QFftpZ{(Wgv6Wq!1v8mYivJ)XG6LqG zZ25G`a5}wyS<9=Bh4Po&=n^jwZ0WG~6gLT?^p!B$blqh>n4)u&AXd+1YOAD~QP)$l2xg1bbCF79QYE{x3Z`K7 zT#W3hWLI{m)!r7ixTo9qw$xyRmrYwgW1wW388OLOY_{oprIP$Uw?gKAZe7kIlcX+9%h4usGC;C5OTvOIi~aibkP3+1_x?|B?wK3 literal 0 HcmV?d00001 diff --git a/src/Ryujinx.UI.Common/Resources/Logo_GitHub_Light.png b/src/Ryujinx.UI.Common/Resources/Logo_GitHub_Light.png new file mode 100644 index 0000000000000000000000000000000000000000..95bc742bb5a4064fd98b7a67dbc3bd4d17013938 GIT binary patch literal 5166 zcmW+)cQ~8h7f)iwC^3_u!>FxduiAUZs9G&zhT3YCQl&xE*4SzlEmGQAEioFi_SdLc zG-$1=qUJCC{c-O-&w0FVpI zRB3cBA~9BgYJdgMkV=8Q#42Wyj&zO}wO6=k%y_S?JbpGjO991SHHwgFEL+IF!7G%M z!Qn9vV$=DX+0jfT)Qcdl0i>q)r|S3BE{?^sPwkqxAlAy4b_UdC)nd1ZIEE=F>AS0+ zg6`)RH>&5?Ij7iD+yx#NpV~pjL`LP<%_eb>fSY1YDaXL6M~W|s_})0BoDN2HjO%kT z$-1j!5z=@0+PjHq8P!fEC#>q#>9XADAz(mMr=pLH7_$VUNjzK~yiD^3DQb#9X{Lnp z5^*5HV|>C8tfE|?LLXag8BuYWbi6ibD3c_YciQ0_~ zVp&$cBINwKiM{c8Cgd6-1yNjrw0O$~D4Hig#xSy#h?~;HdBPz=>Yl8j<{DrJi;%Aq zxy6!R{U-nX7ofITG?eq z!iptsi5t>QwAa(E?wC~bcXbseZ{fKnD(dNdd6HK2y9(YA zg9~>=O&OCK;;2G3Fkf(XQPwQa-rEhj7|T}hQpyGC)(^C@)9jS_S|LqQ8rn-JU&$K8 z0_FO;Czn;0bCokqd%ep8Njs6byw(b#@jj>CDbiaxsDMlBdz;`K3~wPF;S?%L=uN_% zxQe?mtl`%K{hx46SidK|Y$_8<$MsfCQk%FfXs^F=;xcDbmC#h>ki;QE44B>4Ch_NA zM0)a{$&gr&ZS?9Lz57Z;dG#w6(Tc~nP6Qy1Ps3I_G(T1kb|y3LF2oipf3e1R)J6H< z{~gxA#)O_>-;Qprf9;qq99w7MjBVj9o$Fl6#ZW)M3n~hXa`4EEIK7YxH&8#aniG{{ zN*GY$ZhJ-fjx+Am)5+3Ze|_yxwOQFXV%L;yXGAjokWzWOe)m_a;j;9{ga57#;^e_w z{GdIBQ62Z#U=a$u9pX|khHFb$!_#-vjZ7sl_Ul!Rr#C=Sr4mte#^!dj=x)Hrn%4fwQb?X zXnaG6N)n~2$3%_+ly%d}IsYNoS6uB#`%{j@!}o%eRd;q2uh_cuep{m->!)DABAE2O z#kTZ#Be3rg@?Bow<%t+S`_9FV*T6F(sWKwIDefj(Nphldq49ydQxHm(Vz2&GsoS68 z78-CLvk_f#^t%iC8yj3iZ8M%o!DK2`898;nAPfTS74e#SKJaI%Zi&5giidMiT%g+p zD_@x_vaZ*C0etk1+8vaiv8uajp2tbV*#lm@ZHkP^1oc!CA8ELhxnuhLeV3q(Pq%~o zEP+DbbQ}F0R8B_W!q!gG79w*ejdTPLI)$oJ=nhar-eGP@Bc7Gydno5#N{_C^Y}{_b zOraUSq&4V07h<7n(l!PztIc?hun5dGW535*c3-JW^qgJ4773I7mdhu&ewbuDulTpC z7L^Pqgcm2@SxOwgpI#&k%vTkfC?|2?;}~vp%Ix|Whq~Dv%-*Gp4x%>?TQ;p=@}Kp9 z)T=^*Y@D@_79}hHH&mJE$0h0+=@?||Ojpgyx}e`#WkoxL{6+bp{)w5#Ydhe`F@gW< zFI+d6Wj|pzOWnrwOkAaWfpnsZO)-29C%Ps}7~AXTOpJiX|FAYPZc1fzN~2HUmWFWU zRgMyTdJz3yk2l9S64i>p{7rOmNe`empXqC%3d?A7lV@6CMLa9;faW>q8AO)dG5e-@*RPyl;N9y z!88xbYHt4f64r>VanvqTpEvTBkaCK!LJHMZ!AEajh#~hp<>tt|QbfkKk;jpaadou@ z)hX>xlI=4VNK-=XWb&^^)5*%5tA+u#hGwM_hi}Stc+C*Ih9=sVJ{`w;J;4?0Do;vY zw%oBaElSl73QyPCGWH0qX!t#92r~cU*kK7{dUBfo`H;R1EBv;!+Phx_0BC}r$@}u~ z{Ou0%JHgc*oWYT!%m??j?O=jCD$xmVBxcpN1r(b2+#%Kjn^yQ#2AeF|9m>nD+A=4Tn0$kCJC^cp_u{g&xNKPC zD8Z~KHv!C&Ncn%F$dyUdbqI8+p&jH4N#dqlD6YqBaQj0hz#~wqkZ>AKox?i^B|S}*c3#N9Bd&i@IOnIZj+*V zGVI=!8K~ueON*#4Fv&AIkbcjJn%2l|;kK~Orcu8*A&y}NDviqgX4WsR4jD6C3d|2Tq!9Mxtk;{*oBBu{> zFaMu=Wy8Q^6*)Vv*QDB#Gg?>U3mK(j1aLN3+eO2Z^ol|IpWBb zRh{P5W0ggjZuL*TUL*2aNGpw;`0A!2bN7NQk;LavlqO%B)D3=^B{JdqD#vmhKZ^7Z zG%bLBPa^{%?F$?bVn&Rcq*NQHVC`d=Jkk)_;8CkPu!I6KtM~kC+uOxl?LkE9|`_vYA1iraZ%S$U*N?NKlGA{~*luP3$( z-yqPDL-(~&kdy3%JnI!Nq3DPqpHChsMcnYx6U8>m$gNwCC6bLAT{6c~HOGyC@!4kc zAf*P798ql6^}u7898-->@e)2!0&K$C-CI@sQ}ZNmS|BZI2-Gz`L#lqboj5L#oOR&} zsKdz$dHv3|SJ#`;nQ3&qMW<zGu`|8wK+~bQ4R4eBll?YSbxI@|OCVgzq>xcfc_XKu59K_=S zys-T(IzneqRJ-QAC6<}}#Se(VFePe%xS>fM7;|XAJ*mMBquC4RgbLsB1~m#hJh8;s*aO`k?XKov+WnKw zyS^peHcQLz0zG`R_4hFO^s30gZ9ER@&@$d4rK#&;=wz1AI)Y2>bnS7gCUIZmlM=uS z((V|$#g3IXGUiP^r<{KEh@fEHBAr5YMeRov!=_JnHiBfUN7GM)O818G3iy|dM)cuQ z;|7=AsFw}C2(wNLgcHIV6qeQZ=JE$XD|!~(E(Y4u^SoKB&Gef*YvPbV>?bcVe+3uN zs-ef6SPBAuo3)pbJB)a23f!Dr zf5;)?I32;U*HFg3*p@i)J+`4h&HdFh;n>w`F@OHG=TEr_h48FrIXCW0rdS{PrJi?o zT0K11bKdpJ!PCdq+SR+^-NR{$L_Me?HAGS!XR|Jh`P~*sPGcU75S|D4xTjvrhoI|6 zGY_vsvPXeSI$tdSai?2?5ALm7Pi;DiDl&?(a5XUG-S^y#~^ zYS-;;&!*NNE2njv`V1>wEU|n5<-u28$lWMqSuLKFPGQbCkQu)r9AnAT(uAk9_YWa2u zR57y;e2|OT$(3ao?#q$qf$zQyJ>4iAkW&C6EZ%eT6tivdiiM61ipzrSEyapU>2Bb){?1gZW00;+7CeWT(5XHAbGv-T3;~zI;=yKUv=} z3O_`3R9VJQRr1$9rp^*!HxI;~ziZcN<+0FmBc{0!B;dnxeIXgNl;jvDd>;^U z?TPLXM=|8XlUGQ~M2&cTzWGw<1iQWj^Kb!q(-awv6s5xpA5J3VI7D6K8qHO^7Jt%r zrijW38`7#K)DU)f^AJEn%2Tl7@v_FXr8>&xpe>{WG6f896=Zx?GwF_}5Bnawct7{* zbdTiN?N&tWvh?jhz{rWKryHPdp#{#vfdX}Y$?a5aJ=ZOJHO?w$3Br;iNMcZY_M1-b zy(8s(f|P1|P#%ku+)V)U7<-p#=YVtBdNRWOCB#axD^&hBCR>Q_@gt1N<{{*vu0G6l z;JNhUp?xJ9KJLGnPlaQjV!;3+PGBWP=O$7$V*B2QbZ$|Lg+^-)Qb$ zJFVN|2>0hfUzx|op%gu`9y?zjopPEcOGV$Mzuo4vi}LMmE-jQY$&eOE zD?!A*S8U-#-yx2?U^4;pqm2RnMhOnr=V$EPS|{re zqxMd22sghM3iQ+d3MZc3Qb#eK{f#jBjp2F5M?~zOz|7s3IyWlRmYDs2y?F_79!j`_ ztRh*L(yr1&91ZNo%(D=&AxQnx!Xzg4tulJw+>l`10}3*-O(K+7cE<8((1EL`}J4PUCTV9$dBW?5YhfjR6jj!`?67{C`l+R3Q=X(k~1E_G1gwZg3FkoXTB1`e@rxLA`DP+KCpMLIb}1+Yn*qrBzhN9 zReH)(W|tWvVx4zm?@)X)8+bzmJ0(8UrvCz5(lmaV WLB{WW`a*sH1Q_U;YS(ExJ^3FEU3|{~ literal 0 HcmV?d00001 diff --git a/src/Ryujinx.UI.Common/Resources/Logo_Patreon_Dark.png b/src/Ryujinx.UI.Common/Resources/Logo_Patreon_Dark.png new file mode 100644 index 0000000000000000000000000000000000000000..9a521e3fd64d841c1499336e26b8004ad21826ca GIT binary patch literal 52210 zcmeGEgzG z7(%+?yPpAky?=kh?{lup>%h#j_g;Igd);g8wb#Q-4K;;xXU|7I{LUV1N0Xe)bI;1d!rji*)6T`26`J?aV;7{S)U|8S zLVy1|lGD@9`hP1qdmL{IY)}yTM)0P<4Z**2gQ1epr{e1Fc2;0zXnyINl7uJ!e?B{2 zM^X@4{Qn4Zv{S;TV5`#Pl7fHLCQYvLmCKNb2tlNHPgci=cz%Rz7^!c?A+7)B>!fMs@vu~zQ2&?B(ph}xo(kUKZMs=H}y}#ly&VJ zeD5{Pp}T)cha{8d`gz3Y^1(q{Nnf*Xie%gFS(_A(jXxtPZGIb9`b_cFtsWbLhb0?> zbS9Qp5y3>nFcMNK*8lgz|5L*ML&E>_g8zT_0`qoQ!)cQ$HrmDcfs**iJe!CuFBi5# zHB?DMIMb@3N#{^fy)M#hH8nz@@A0oN ziZ8EwMeOk&F24?D*D57KKNnFwu-z>;8k50oT`d-CQg)EwiaxlLEx(j(tQD_4v&i^c zex_R0mB{60DS$v%_ehOJehR-0ykUR?7hYTl+m}-s~o$L#Hk8 zrJA8$M~}Qrt6r(x{~lNp#{luhFV$3u8wK_ibtAub)@08&MJt5)WK9*Pk!zHm+QZX2 zk8xG-mQ7)XKZ|9{yLAiWrOG1w9Xo5I^(u8(f4f8<*l5N2@)C9A9rEGFR57f|QU{}H z^(vT6oj(%=QKRuXZM9sueMK9L;QRGDWt+0kwSH~A`%ev)s)+@m*o9&vD`gsZJ1W^O zQYJ2SeJUp@={Z&dJ{MoCdsrpZUBycW3CXB6epmJlqfP}i#X{#4WygrDKutf6LidsDK$H6s}(6I(B_*PuYuoW zjvNxA|MOCDQ?-g4ch#gy7{4D!Ufw53TUNT?sfV!I{kY0pAW^n|bAzSX{pPHzY2?*5 zA^b+w_u!w;L#_tJZ0h})*o+=c+Sp#mJxo)k*~{uOo7>*x{Ijf2A&ZeLt@TVnxl0wO z_&V`w`)3P5W}Iic@q?*bT6g6R^c%=s%za?h@Y_|VM*SIoPL~PMqeJL~zJ3b1(#I~v znJsTQoa`y574|Dj+kMl>=+7rO-(Z#LSjd&Kd?(ca!`xljxj`7y`XH@Oe`P*ai-L2c zTsdQ@)nZA-&!IB~`6XhkX~AnpSv7p8Z~JU_w%}{@V|NjTk@MD0Be~T`xpwp=$X)x5 z5y2KN3~eWwR5)KDReQhq=Q_jWoj{m+|rwix4rZiXC!P7K) zoF-a&G`6iNg`)@GYV37dibS$?!Yvd44bAvCYv6>$pI$H^wbrSe8~5t#Qdy~@f7_uY zv9&)gsV5tzz}fTgN;SW?H)Su%dur@F7T>^gQO{e{!L&@c-=TzN*5GVlskSAimU$@s z`(`grK%HyPR0UK0l;=vuzBAk9VUkcniQOZ4Gycvj zG3Vxybqdq4cM%qNR*^Gt$X8!_?MMt=m@KD-;%B_zeD(rw=-+D+zmbTt*O3ov&(c=+ z67YxL;t_2AOv3Z0a7Zg9nm(x`gvE(OO=4t<+jo&>gaN-6OTVzpur0ao*XyE;K|E+# zU2fPOu9d-i$c8O?7_RzuTNtM_)MNw*tVTWuD(Rx78h_`ti(xvFr~N6A2kl<%qkyn* zpzu{~dG7|$(e9(8rMhLfYSo?1Sq-paF3$khz>Ble{v`eSZ@fL8{BCJ_7}ieqrI9|S zuP0k^{R(ESDb0xI^D={?OOwrFi{YVZ=b@6Hf^@vl;Kc9~=JiTd`ymW(xlSD|0I7-| zfJd=v&SL^zgR!?@NY{6p6_s;X@D|&tFLTD+KF~Rcin{!H>LU++>GX0gMZ?Dk& zo29c&ZauG)u(>b%8x3byLQ6Xc!O*?2b3#Iuj_ETTW$7^H`h>PgL7V*{MWLn%%9Zzd zFV!5L{p^jJ8Jcn79g}qNRQOiJi7>wagE2NFtW0`70S(+M zUw@WHZmWaQWO%L2B$=#Y|K+WC)}>ztm)v(#VG>W77gqaW%*gtk)e29XuE`IVrYab- z5DI4;Rj%gLXtUGiQ9W8+Hn~ln(LJMswMhKe3`Qa^z>2-Oa$4ufk*AK&!YE|Jl->X6 z%f9p?G9(W3p9u{6EfQ!o(ju@#}1Devz>zqD+^^fF1U@}T8e!ICQ6G8u9Ju1DPdAQ_=1-RS2v9js zz0t3cqJ?omVi+4$;caGFHtuUjxO4SHF=p`}%#El?4K&?oGf85g0V?kdW$$66Z+lR6 zEt_9L!S-Qqav|pp1nPUR0*%Yf@eX+(N$v=J%v3>ipui!8Ogs`UITsT*rdBu+VCcf{ zn-BAi{m@jb`Wq~FiiT?ZvhBC)w11a-p8CT%Gr60aD?$m=rDp$3USgeHq>X=poNFQ8 zEg%DE_l=7A&GDik(g&Y#hiATow|;v? zg!+%P-;gqsKjzudsnOuBc}l9h$v_&QG!7{)oSkAHds+JQSk|d>a@%gfo_H=pQWZ_G zgywoqTR=Mfh-S)u0-{2Te@SI0ea~fIB@Z7M!sOzRS=@d2!CXNH5b0TYV6hvjA6btr z_6WW~W-lqQ-SZ%fK7BbiO?=(bExYp0oO!|dIgGka1wY9&r$)-*hD^JfO}4qhzRbh1 z^y=b3b*+}6<=2LNI6fD()QLPpmzJTc{nTU>#4rXrs_}lGi`S2K!0LJ#6%ax%bk#Pj z_w(&+7z0Z}cic{FmArkaCjE4$?X6yta`*-i{7_<3)jqRWhiEc$wSG&c_~3ck*f4MZ z+Xj3s1hm}yKy3bB!XS!5>gVH)e44$Xyoy^)wZmh%Bl;eMiLXy)Dn! z{aoO}AN($mP@qncmmXI7`R)tqV#ej+XHvS-Y3H{Fh7Y&XlD>rR)aPIo_s4okse$Yy zdYr7-qFY8+E*|-CGr1>PXpWV)LEbpN&`~yw-1dp9mdSRSyd7V7x9zH(j!fpLLz+Al zA4%LLTWBt52Y*~|_G@mAy*nIAX|0m?)+wAwf}m7WTajDL|H)?McIfgbeOG!D(y>-Fpk z%p1%mE34?6)vZicz4qJ>QVu!sdFd2hAG`;qH$FgNJe-FW>!u^N#CCM(@}rrBG4zOt z#h&_@_un7iTs7`&-sIt^wc)e~r7C?betxhxUmq&&Eq(gqtA(a0pd0${num$V*iUp` zIQ5c)L^^Og6#dSIDvjQ;RvU&LHh_}V3+Y!*aF3DYQd)X$!eW26La*=RUspEQiwZp zv5(|qo0^|>eDR=BIgF)JcS)>nKe8Sm-yjthA8Vh@sz71X9UIOQVBL9`xD=A|t4iK$ z&y#NQ4uaS{NIR!C9};L%jHYu9{fR8keHXho#Vz)*bzo{XcPF6Owujywh{DlDVW*p9_PD z8lDG8Dj#E+JMvVFmw034#~I)Gy2f$={0?wKxYcrJ{_D)tZdnjxQ8-ea$P%r@52fRvE#5zZ0M$QaTCa`L zA-)hDb^6QQVC*&sZ!`X%6;7mXXL`b(QAE?7WoE-$s7V|)!!9M5ecp7iSkwr8-Uy)k zmwIZ*N1;cq3_Y4(TJcfRY&apsL;kU=hn2nCrrSNaLz_x)Io|dnqzqsg(q9jO^wTUv zJzNCE2aryVTU2yu*n7%{b^7>qnkb5}_dG8BQT`N|@`GS|odQq4q3mmIMSb=F%lJ8R zKAD6eWfkGVm^MwhcCbk* z(Km?Q{Orx{*#i0jxB4`7bwAzCpogTMdwPrdsc2Z%y7a2&rGK7d-;j`qT6gzjlnC06 zqp?MW31oG^7}}KTSmJf!{hvIn2otWUk@qHb^t`SI2$7>*KB#Odo}NiBvtD;+;EBw> zM(?5Ym^y#^qr08K2Bg0ffqK>Q^709+GyV@u%r)!3-|^GfkMb5De(jSR=^!S)MVTh7 z6{Z=@(e=`oX`ZJel?H`T=8O57^Sjs$%86 z^G8>2JR&9zClkrXK!Bx2$|RJ-suO z$YS7g)fw_Hbv4T1`o^gfR@-$u|4AO8w-0vSrwJ=))E?+8_W1)Ksi*+iAD_dB6J$@N zL@}YlZdV2!P8xs1rX=Zn3ESoD>74G%dU_h-aN(;OsBn5AJ>|sAiHzX`e?^w~rGk{E zDtXU64K0Z>wN$q2v@q?9z<5TlF_Z-3fjAqFl~axqSc8;Qf z9BCT59(mqBup?JnbxtRy7RIoyoJGwLTDYzSBRNgjoL#wbDth`NMoirPUPCtd8fOnF z6)K$7fb@+f(A1l}j9&yznJc0M3N`nG6C4XTDQmr*>p8R4iQTNXAK280FCxX2;DQ*BgP2^?_iJC%z$s6wttZV7#*qF9Y%3qO z9$=Uq!U#0FGaTq#ClMrxcWLy-RTn#>!V~JVGSS_-nsc5pb1{7fb;!D$+mKHap`MZfS;M-smO2xvXAQSo1FU-w1;#2}ehu33 zJ_o^u2%w$#PRO+*w4=$>qD{VK<2_@*IW=kC#zr?@GB*c3ESMeFdvmOA2ISqKtfZG3 zo;VnrG1pm~lCt)8yLEpw_Yhh4bnN|5LsU7esnR7d9BV+D3zY~)ewQCbC_k#KaL?lG zE=`_7w`-d$3F{#HxQ3%vEYmSZ2L_bZ3!a0>BJN`U&ZsKw>x*v#k&7}CV_#$@BWXPrHcs5lF@{?vSQ zNZv_jrcz*Yhg%U~Y@utY;Q7YV7eK!V^Y1X4*{t-CCggn#y{tYeJSb&dgtL7)Zn#b~ zTcFRRpoogK@G&t{D$S|p@u<=SJ(v7pX>Li$d?L}k8k$Wkde^C2VG%%T))=9X<)XQLMCr`%PVRiMI|6E% zTb5Rdy9F(C+R@2In7Wil1&N7@u-D-@McfJ-Dtt^NDNrmsh@PHgNy8I$i#`?R_F z)d0zUwCz{suIPB35V>baPRT=kg>0f9xZxnb-XKBTw&G@XLzGddxCj(3S#J=byats5 z6#DFVg=Dng;(_e=pbtxH^|`DH&V9DwUPg}ylFA{1(UkXnk1z!!-5wc~(Fbf&px8^( z?_=1hI%3T;tI1>unVj^jcB=eXS(hGj^w7H0!LuisL1uMB^=uUmcN(k=lYRxn$M6H% zV+Ik}4-o?=1c+HV)})a<>MOYU-gL8^RpaQ;K-11ud|;ek4U8d#;QYbZLiphI>I84J zb3H3gZe_$Y^YX#!lfFLow0QO&-J&xJ1h0Y*N-9{(082Q(`=8C|X zIEZVLj;NU}Qf;AClc7K@(CnS3LVQ6En(x$nEi}suL_l9(kv@fij93LeCRU;(U%BZ( z$(f-A!~m2P$soIE^W=ZflfcD6_TKqEuhrS)Dj@BG%0vFj!*hx>!&z8)!gu~z}e&y!705hgV^M9qEX>`8~I zBa;Tym6B!RbiTzoXmxnu^mlRQfZthf$fAVF?BK;N$~#r_bUf{D3L6wcV_JZV`oPzs z&w|6dt`^qB9Bq`h9ckduEpKvl>Z;;cg5;rZ=f&tby4tzT2Y92Zpka7pxVU3@L9NhqA(U%3AAu z$!uIWfGSAgUg6W=G}^Y+n#R>@8=i}gq1k;wEu4F;zzi$XwA14B7MjMIvmL1-h?94c zyt7%)PjblDm=*RufL13SeCbbG+QBIFszBaDhib37xIU{=OpQGW7;tw3yLtl!P8%KU z;7AB5o}2_DXT;Q%rd=7JFT8w8u|T!TOiPvBz8gAW0v#1{Xf)U$6$i*YhZA%>Jf0YL zA_pS6v%@%9&jDze0%-X+`g}Oqmzu2vN4j_#vU+-8YdS5HL${$UiZ_h}r1Id28M1lC z7ERe@mY;!>6Bj45$Q}r`I_*ZR{v5tq$Un~VjSEGXW{od0+$JepEBc47>qMjB;Y8Yu zcVc!!%bT%B1`sV^lVMJG6l%w>C`L3&PstS@M9o{cug*^yj+v+f2I;505%PB`_Q+Q2 zby2~E5C*!b4I3`}A#ZjFgVWEVae4L8VkjLvOJaXVfuDvk$h;FD6)3qIPYE2Ow;`Ib z3;z9XD#_mMEyiO9C#gDb9X6a*DV(povuL@i9__4xsT3ZJoER=Wdg2sC_W?R&0%>o- z&l0=lQk;_A+?cF#ix@}yS_b&_lha2??*ze2&m67Fz7u7+l@etd>uII6KQ|rI4y7D` zdAU(uO-gWaXC?di=m1x^c(GvBTf9hLVOLJ)poLwK_ zN_ybl)C9KWaS3F!7CAY$p~v_*BGo2;b8`!-Ss#kw?kg@Z+GVEkam=?sFmfeAk$`o*p_&x(T#+@PEJi^xR!*HUDk{naUCE3PN_W5CRwu2SWF%U@M zKy4stl`gzBm-by-Z!va1uH%`59XZ35kYKfbDkIq|gzRbcpm?uMaRT$k=>d;>m4X;oIWctF=a69}S4y?Nvm=TV+# z9fEw_M87JB`N{-U0j}bdD)8#kV-`u>&-LB|B_TAzJ z8yT-__^K7)R+fOrIEPAk2x(w5c^cbi(Xk)iT=fMi7_;y8bzyU&dc{Ye`$*R9W-4Jd zRav?3Qljo}7{cgnV-g=#c1UmZnp<5ExM%H+CPHN|gI$BEKFy1)AMd^!mOYjemHz;v zt@WXq5juE+0A-XKa@nc$zr4!*mVuN2WZ2|Sr&af}Rk%xwSzs20*i~up6xF*lgm_Sy zi{q-3q)bK7Eu37m5m!guZK#L=DE~Qv!O38^@C;{w)rQB|`*oU;?D*-a%lB{dOv=JN zJJ0h>At5l_@I`s!DIIau4SN*rWR{YoAnr^_y}x1z z<5Kg^C_4ZB*Nd}|170G4WCMol2lz&k)h4)1$dF9lj<(P6U3L0(&_xVcEc$}MS@+@O zgaC&Pf3xI(asQJ1Mg=AoHB9A<{uP$-?XpUGs8EOCiVT04sIsQwEW=HCXwa!pmRVAs zGJgY4(PoXy-^vyf zQJeBo<=l}|^ky$>yk~so1|`VYA)=ck8MF;2x2+Mt`9}>-q8qsMjcF2|1EBIH7;->J zHP=j~aDLbg7pc3}7GTr4u834pLT-_WfC3AY7B^CqvMT7V(J0C!1Wd9O2T8t1CSmNZ z-rxZdTvI6iD|CVc*#WMwr%o`SLR`Yi;Z{db2u0k@}A*At%dAJsT_V*grKZmm{W?um&09-fcqOsX3^`t4;qHO_C)a!4S#r-PKoac zYu$x5WN?=?sYtncTs}#E&nzlAQin;1;2|IuRwLlzJU=xrhSgogN!XhuAXUw$ZWW^n z&uRmof4A^S2hvCTQv~vB=hd8MY@O&pfJ^5$0P)VJd3*$9tibqxYSIArNH1flt=a7u z?`-Tdg)4A_3za5oZChyl&yfi6N9z>Y43(PtireuJ9sdjy-V;7bPQfPOkS>r{27*%d z@nItoXW~u91Cp;5QUj}o6%Jj8^qH%a8p6;h5m1o#mci`(5Y4y`qO*7Vn;|$>05yRF z69q78a$-e*`bHULTj0jMUL^VJ=U8*#Ca1eDkrOBv6r#U`y0^SJ|M%Rvd~U~&8XqDAq>c}n4ow^(?rkv zAjn`WtRY|m3Bv^nFM|xA8h@+Uo8#xQv}`yU?PpUDm}cLviEy44W^IRybT$lDU2=qr zm=4m&xmuiBcoYq3_=X|sJ8J`*Rk(ZAZ)`a2lSdpeF#j?0xdS8$z>vEud4 zwN`cqnH)5BgZi^e@?q0_V*MwE#;ZSzDBor3|DXB|mK@R%Ws^0ybKFW3SN(o`*N|(L zy748RqgFQ3Vt=cAix{Yt(%|!bXi7FJz#25S@#M~X@oBWf28;s6g5WepSPoBZyXsEr zp4%{_w?PH2L{9zqM$nfeigx%xW4V4I{29FW68h9UGCwFUwkA}AUdq}8H~}wMRp2QWwH0*BqE%Hg&4x#Js@&^qPwvW!subYf390M*y7Zz zup*=uI$EmX&xH{-M-u%5WR~wXun@!$HS`;6Y(o;=L@|8{J4~1>Q~sisn}GpmQeYoz zHh{yex#w>gY?oP zL zUzqd>>fKCWc}`z&7cd76W(6urq0poc6VzI>YnF$iNJPS~%}PNwLYQxXRhNS=tutI4 z-V)W*-5X56gF0lkWTW0t(}u8?}7{)&MY(NPwLu5H467P znbe_LTEyWWOP{$zisOX!7~%roZ%^uMD@Utb7mhC1S3SagDE4vWgqUwjUqxT~*tkOE zi$}EuVCA}drUX9P?gzt!%ha{ndcbf(Mak0Tz5r z@5?)Xcyg{{i!{Gh ztKgc6@&YBq7En9}V)t`j-YW4yaRwV98>I!`k9Zr!m{JL*7phlWz=@V94gxRuSO zW`F%x=}Wr|zJ#I(qZVsYxN_&VXzaT3O0S?A#>19SvVpwEiX=HvB*UIg66Lp}uE>tYcqVUq7 z6=a+r(5{5aTO7@7prT#;)BTJ|?|Q4%wLpS7fdZi@93&GEE~o}s#aQ32gsT4n)%YJ- z2%#ux_}nY*_18^7ihe37SDIfFc2D)L7Ln*#8g5a~{w1vH$^34K7tj2>4E+!hj8U2t zQbhY}1X7^0G3$Lx(Df^$z5nw~aQ~#yP>8gOJ;N2CyTcTreu(B2^0tayO|te?54MPF z$?pW?XZ2Z=UVZ1ys3{y9o;oTbgF*&GJ<+OpH7Ul=RdBo{pWv)U|oGk#m^6FFNq^jScaUSZ>$r|-#+S^_ zs5wA*C?PT(o2yAL$ekr+68CD@`;yI~Yg>*Llg!x<*#VL@<_`@X6kr&O6S5JoBM+xI>r8a!Y+$Cl9;}Rmi#p=?=ay~H*w)h&B z1BHNDkW@dN7|G@yd;271;C~)Yy2h^2@L7nZfi0?zb3a}Ixlb1ze%f;GHBm#ivB5rs z!lwxFGeYhYmc1!)^QOIWam7biI7%$92>o8eDZ0!Cre!NC(5YJ(_LNUm%K~3J_j?HH z-uuC-k%Z#w;r=W(wLh3fsf1CiA(WCJxA;h2YW^cf8!Lz-x4YD!VVslcPUgBs!p#6| zuVRpjJZ(Q3b_TibnDl~7-x%C55qyc7LdPr}beQg}uFAETjAZjnPh56GFY0>|hwnd= zR0q>up-8)3D7y4x!baue+>wH?TO@Nnn>t!1`wJdj1(o>}?xN?J&5W6|{-xhQsF>aZ zqqfP?F176rXjJrxYzCSAiQmsOTY3MeI&v>8qpbjPZ%SV{Q`C*^pX2a4gWGY z#RZDw-`rh~f%dg=w<*U*N6;6-d=<8Ec}u%%X8Yfr zM_l*aF-_A$>G2mze=Km^eh-O=&4SN$$0T^-6Qi7&kgAuCZVd=$DyCCF>`m33c@i<^ zq=UA`T!(Iq)g%ZSY%YlJKsaz+HIS|EiuPIz64|aTZSKnE4j6Abx)!0joPbD-Ku^>A zo9KV8^828fBj=DU7pD+B5ovC!28@wSW@!`XcCPJLl>pi9i?m(#O7{6~dZU12XaOgB zJkiy8W@l~fIc8}>C-(kF>QUP9%a739w}8raF3bjI+t%wnF;3zH{V^)PUB~)&z+f}l8)>- z78@M$!hVas_f+VN&y>;0oY$-W>9+E``}E$27Ywcw*XhC9dfXZkOY{HYyJM=DpCkqO zEwN{2T|;?cl)~)y_ejLTz0nVu1S7JjbF%0iEzf0q1`inMu}Um>**Bfx zDiBn1w{I%W8U@Y?jeTR1D(~3+AYA>y&YS#OYPvYqAH^5`Co5IIn|+fAM{}s>fVfcl z88gFUP@`$G>NFTyb_UdRuh>WcB$kZCB!CGOXbdEUrnpTwoooWTC2OH(Y=V4n0AaUl z*=oAmH6&tl7#^Xk)f3ZiOY>jQE6QSLNQ8XiA+!iG>at4A&_DLQg-^JXla%1zJzw)zS}PQ}^#`S@ z+qfmY`&|*@Nr5peFUT#hfQEw^6rjUbdnm%|#{!01=;LfW#4;t%h7`{n)kA?;{h-L( zzx8V^-Ly-}Oy8*JtO3X-v0-RiNMyy-h15gM6@P>H%%#I*SF2;ts%WJKl43L&LC3+r zz}p7HUO4Sz6!rY(V>`bv`BU&fcSHiLkx6b6LQ_+|115@PC0;!-6z_ar9jmyVOve?S zk68&xYg=Ge-fYU|N%}sn2V;EdZ~+byNQ4Y*q_qNmyH2#yG}`iUN1)Ht@rm}ATxwF> z%F0?^a=a!ZGNC||@syK4aX3o&&0`RhQ_r9#MxGfUdtxN2_I_1&oXRDXO8^iDNV{*Y zHl{H|ON+>c?`8xcc^n!#PwkP~#t;j?x(3B_L)5&dU}Nj%z1|AW0sx;tyDT%tlYm1E z3%!^H7R2?>Y1e-8&#}OrfBb+q>{eQ@22`?K<7%%PtCu$H{r3~nUdcbVKJVwt6EFp! zzEc;bB(p?1hw)?@_ZKrdjDGce~Zd>>~)ra)rKCX|O0MmmPQ$kWzc zRM~mfI-#;-{Q3)}3Ipic>32QVFA21}bLpqr#&2*$)LqFVY6yr{;Tz;Ct~q9-O7c=_ z2BB9I7M(K~X+3|(e9ots3ZjP81i44xiNaDuB5v0qs%t@chVv8w@lZ(R)M#h}V+PaI zLClz2)S+qUQMLW%aIWaT(}lj!9yubD91{)J$|tvfrwJEjeT)vc+6=Xo1Dw38MkKnf zaiF%g&VO@TM@ZAqKW07m@$wPq&%NCni|+ddiFgWgY!6}>1#JDB=6aF6FEY@)Dx2YZ z$Ihdnwyo;_Q+?V2PFBWAo7eocH@!Kz+hx)HxE&BM1Qjp_sLQ=yQyiJVy?F0hTGHRI z6l`I!CJb^$R3J%J67Ib~MO)hru_AITo15ts4M9$T5F3>6l400yAFn`LOdY_G6dQ}M z1abJcSH4j7HyMs#QXWgv$%NO}V0w};pD6V% zGZ>MG3X`~$Ks%LVl?|)na|Gr@uJ{{vS}DrAtf6=*j}?r44mYaDm*HO57W-#wGSUef z<4Qo$XDU_TXwY8>a$)tZcXDKG58VB%jL z{w?P|!hj}2yqAYd&2?YtqUOFdy#V4946wSoqUgW7@!$*8dyOpa?+ZZUF;{6Y=NlenFtE&8qR<+u9?s*`kw!M{4+aWEuAvE zSvU{!-9i|OzSfOLqOFf}Nw~;%C_CTWqxh)#25hunAyrLL?5$2fNyNOV!zG0?Mfo6A zu1a%)kSRs#i59UhUoGrLIJ)a5sMb+0wWWHwX>B0c{!+sZgIuu1{&e{R039om za2p%L`qO*+mhKAiZ#x*8|M4R=K_&%VR{Bx&i(hx1On52UiBN06+qeJRFix~IOf1&;$WdT02AY5!g^gYpwnnW~X zq|RO;7+XqW|6SE&p3{r-Pt%rM8GK_b+kjacRh?=>{zbDGJUBPi$2`HRvJ#gRz`~vVjAV!C{6& zcEiyGPvOQJ4F^xAP8OUs2A~t|vf7E-(?Eu4G6q~6yDdW;?w{ZTT{=Cb{@MB`YO#sC zB3*-}mnIj+NVq2=B=wIdx+8P|Z%srekJ0qp^f^=Xie^|ht0@|c+b07Z$SKi$CSvI_ z`eMg*5E4CpNFzH|+yBi4%Dd&pJq)%et8<5ngm2-r@-C9n?|H9(#&ls9iQ}Y@Q8cMpzw{vw)6D5Decn6Z13~ zH`}gPss*9-=432XNDO!GQ7m@~`~a@bCKV zFEsRnd6Xewv1&+sHInAXxfxdo3|3|Md3T*eQiM4>Oz0JQUt|5(VrZ_mp@eSw%;`(x zvqzmO272a?)_(Lf;`7tIn2-`*tlUm&(X546P#Y6HnbxaqxL56J3Zp77IX~-yMo3`{ z4>juzKUDdty-(efarwe^1qx%iJjY?_mqF@|yEj)4rGa2>2g3-B?p|=uMCjenN(B|e zqbT~kgQ1VTfQ|qQ%wJf%xaw;7%1s!w(Js8=32*dwWOyg_xCz>X6sZmZ9Vhsp$_XD= zDI$LQsH9BD^zBQ3yiD78k2pM;6-q;(KFy?|Y=M*+8x#5o5u;^{pj>z;vx;-4#Mu*IkX+h`YS=sT<#j4qq_cZ{BoGEj}dbHqE0*+81 zLGTIt{rM{BkuLji^OXg|8CPAF_99=(b7zar+=J3jkde^WYWB$${P!wiX=RmZ-paU`MWg`StV40( zPArPX?ZfvDUuK(XZg9`gMdM?L@p9^v7fJr-e)u& zJ4Wo9oGcqQZXYlkfx>LNL0`|3a@)*z~AGgWwLhN}GoRC(I>(t$hc@63>EV{=o9J6)eY~NF!{fVxH)7iUZ7O4I3~lQ~nm{iaD6f z-}$t5dzUc_D*ZqRH%<>x z-at-3YB{4Lkt19=8#?7RB2qbfD{J)l9SN#;84w!NvqxJ@Xqc*5QuD4d5j849YJZAI ze7N?!5r5Mpu^0_d-_OCyp&X%<}1m))M@qzmv_ zsj6*m!Qn&wlKL>4!T^%^Q+Xfajag(ILoDs-!~z>fFqaR-h?Xijt#1M{V4n6m3$w^P@E(ho{EJJ+JxP%DT+LCvK~KB_MXQTy&3McVv1Zlp^WF&Ys`- zxh$h2UVQb+U3UlI-=Qx-(P7~q`?VLV9R5WA!&e3jIc%!0T`8Tt@9JfpK}(lgPSgPR zl>}FyC^ia^G_|Rnr&PlvMJ^4Wlq12;!}-svwHiu%JuZdEr*s1Ws9B`b59m;aHwyWcdTJ$`=_8ulF{ZvMzLy_ z_{6y;sP;t?4syy^K5v@OSnIm&c!r*UAh>E3 z^smU@g)vt9_#Pj`0X*zL?rNm!d{`(@F|a8G7mYi3*a)eC&g4VZHjYHxeobCNDt*E8 z&k5xKPcTjmpTeIhOBk+$i|&m6m|X#}-6WbD`1C382IL#A3`bCy-zD848d7{qAXG4) zHg#6rG%o*EMK{DZi)~&U*qcKc`fkAJRI_U8b`sHC>}|@?_BL$Tjx&XlC&NH{nXOE5 zfWb~0Wj-A;psUtYX1f6{$~pEg&x2s>EBFOI8804*=uk@u0m|yfjsqu-Li*vNZNq9? zBOB#BbJBW*pT_ZJXh?Z zua8@7bx6M`U9h2N45Xy99YYI!nU#y4^NigvI5duC1DZV=iSj3xT)We~9+}jOIFph=wY)Zb@LBCuYt92;f?-{rtL?~)-)3U>r`3reJ*2sDrZTz&K^AFVjOmd#5-HTr6@JHB4;*(5f zoi-?0IudW^LYB0xiNHpOBIO)!*Uf44ZRsC++SZ_g#M=G>HX8M{yEQG2cCG*TH4wru z)H-RYcBV^Si&dIiV!VZvG0RUq&?wbO^-4Av>qAoIK|iF125(54hi;4f=z6Af^a33? zUnC%Qmly1CInGHLo$H2~dTkWF;ASF3G8yXI$cK@=Vv&eV`myf2F>PY@pX`Z!iD7tv zrVaH*P8$hI1vvwjYJTX1wg+3NUR}<91=bi#wm()j)#Yl$y1I=Fr2;-JqV{Gi)UbrV zWIL85wE5#()w#qVizI$?gF43&ILzTxH9*4)M7&Gb>7veAiLnJt*IBuIT?;rlIQp zjF9+I&z7lUrkmdACmhhDw;fgy0)uul3oG;Z>3$90tkug9YHB+5wsqo8z>PF$$+Ust+v?jXD-m;Bg;*eSTy9L(vOvJh>o;;|6%VfaHkwoFlBSLc$ z3ze-zXqBH5g==Yp^dFvzzmOw!luzBs$gEcFvqaOi*fF24m-OOyfiAL7k(N5V?xWFR zj?s~z97CT8UcGPv0#q4UDa*JsUM(-VMUqi`+F|$202MLJf_9eE#=hfEs5jDXv*DqT z6-b$n4E8P#A<ZVCgHfEnO%xBSIyZuj#;;Xoh|6HFU^(w*PMn+Dx3P67xM&&_jfSI&7($7G@z;OOLXZH)taVekb-BZN6 zu{!%mq-R0?*OJ+a^7H=sHiYAUzg+6VC3}(vq<_s(>)DZXFIZj6dH)4}1n0q|;RhqY z*3Tl6Z-F}2Y1Br5A#wPtXMIx@gBBLo>u>#KkzWd_>)J~{xqCOW?Y%vkRE<(~{W@cH z#DHXgdKS*AdHHrj$B?Y84XOWSainJzQp2PJ9bb9&xV|bIj3RZ-?bTldn~@v;T@~m9 zV?y#Ti^mtyj-?EpKlr=WS@qO)U6%MyCL=TjaH$vigUL$WJR{m(gf|4(;e)N?)Y&8J zCg+AMqH_zC{_^P?qtx(q8FnQ&4Wc4Pia=*t%@FvmeSkQVF zeetJlokixwS)T{?8}0dCbrM{Czj#jSckezRC|o6P2@O^0%um{XM&A|3Ng#%yg#J9L z+``A4x!!E^p}%8;@(2W_BWAd98TJ1W_Lgx`ZSDWCj38woh@zxYf;7@KD$=1ef`E=l zcekS+5rIKzX+cCl>CW*83?(7bFd##x)X?y(O?mF$|Hbph56;Zqd#!6-@x5{@YlOy_ zuM~Y04jK5kl?&288?ziB4tusdNbo65 zAiSUBSEr_BI;lL7XwmiUV|Jz~fzK3U-;0gS8<@r~MQA<1_zc@5yF*l~hOdnoPjGiF z)z3wMR_-HPAjjN>+OSg#DCVJ-hzVu9*e4K~LPjED3 zNb&LP^9>=&ed2dnXBzNkM4~^-pjhba)wU{TEB`;A=Y_%sug*RiesoMQ2lD1|CC;zQ z((~gQp5W6jI>WvVzDws9Iw>0uny;XR-hs`P?0qQQ;V@QA)powqA`HcAZ;=-))Dx$M zyKFrPJx}+-R^XpwZP#$Me>su z;C3Z60>~UyTrH^Mt%xyNWhXmWYP-roU zRiQ5L2hUQT#%7tjanI$>dHr`Q{%DV=H(xb#G52S`M`%35*&7*w3$_Wflp+>7v_bWZ z?_74>(H=NR3J`m=pYFY{oNnVR?OtYp7Gy}&w}!g|vsVmNu4!2fOFGQRpFl2>7cfrS z*0XFHq@}AYYUF-BDYHASu}@c)HlN_ibpn|{ zG%fLS?MdtByuS5PXiC z=Ir?;=U6~?mh>Uiev!kEoLAnJ)qPg>Mv0qyIQM^$J80_(kmJ2&>iM2)&YR@XGRGv+BNz0l5rRrN@9eV|huM>k z@rO{i_|gkIA|eRj#t@;*yle>N`_F$VB@sS8A9#{3vMakX)$wa4F+OJNl_1qoa{L7~ zh^w>y$K6jS9hdfId%PY%k$_k#;lXeq3UIb{8eRiJiyE$?oBuhVeS z{vlc7luY(|$SYYQrB}WD@BV=d&i*oy4P?Flzynf}e$%d9}(gI8Sq~r0Q(HBQb^#rn#=mFiZ#cC)5gHnAuU#gQ^ znK=&yME@QZxZV5@%JXwq`&He^KUeIG53<$TfZEX`=7@{ zHqJ7%T~YNWCpx#iH4Mbr`0gR!JSjGJH8@xB`OUr?aqvXYBVoVadOCa(#{Jx9Pttke z`a2u0-vy#aVV0$m4xfI58tR>+C_@BaBbL5`$d-6z|4*y62^Sfw#s51U{4HQgF=--0Ju=DVbAW))@IO;w7&zI1G z|L^txe+yoJhVGpomnd5`)i3@(J{tv19H?*}j*&$I%=rxMrTJ>@OcLFj|2z;)0Csqm zWX$yc?i{qkh=O()rK9@mBWb3)2XcJR?iu~x&rj=u9hKOmJo(=nnP|fn#9U{4-fNpy z1$YyQ3V*PT{Xf4DgmHgfq%GXYH)~a6_i|T8J<*~y; z5&a76>1N-3ZXkd1NkoFXlD(CgiCjlDTF_~r#R0c;eXs`6tDw6k{OHB`thTw_++VlE z{<3nj23YVx#rZ6=y>;S*6tHDSyn+Ue%A%*bnBIWQ&(UYlA37}eQo=&mMn-+jvo0mS{_*|eUx7GR(^&hIOfYF8R66vQw%JdnUg}IN){F3(3 z`9L%oAC4YSFeO0wO4B(4WAgyEYC2T)r{D8|kk;R@lNedt6z5L9{$A}bFoOS6SMu~7 zX!@gBzM=PQPj0m;8!&~YOUp)orRUBC02qFJAo5UI@jOA??ueM~?}Ke~j-awJu$P-VW!ul&JZNZqV8 zJb1a3zm#zijE~y1(6Ir@IO@-uT74FrHd>e}yCv9PI-I;{ek)k~?pSi!^WkSro|DO{ z83O)Ul_G%ih7(|O%6r92zYNOLh(zb7M*m{8MKTqEi+l}A!E1t_MghktB&n${(u*aX zx_{y5*2q-CY58(jGW=*(l7@izg0hcv#rqRTQu1x>X?!mP7mxg?&b2+$f}wu2qjOcAxT zka+RGPDvtsreoLUjjyM7?ND_MjP9V{Wtqx+DA=tvd)iXvNR|LNxd=*;rp-dz1BCM+ z(FR3WTT6CfL0#4zbGh-^E{83PkSNm-KJddAYxaCqloPZpl@flnv|vz&zcE(Dy8@j_ zGl=lCcXMzqD1%Pi`?yTbLvb}3fw8Au>@lBXJscAOdY8mS96~49vH2NLnE61iqf6lB z0>iDkQi2Nuqay@inQb*PZ!-Yi@C1Iso~HvwsVy6w%Tunr^~3eQ zegdgh(svTn@4`UJ<5QwjOWG`qV(lbM2?NOAfy6$1zx95CyrBJx&~uzyd0O9R)Uxr^ zaRm7U(t!ljA5^P|_fi+HdSLEvo(u=>-3bLFFc@SOEUEH5UA~nkHIzSuHVR5%SMB_6 z6k6$$#TniOG5+q4ASwd#m_nJ(kxlu&eoL0jqJ3(QdS{O-ZvaWlfbZq%K!_=8%2}Et zwf_>=1I9Ygq{I733&ceGKB_Q(MLj--VxZ<`)nK}t^w~ndL z#P=A9zw&(KQ1_r=loHZr;zScZIc{w{DFB4ldIhm)HZ zbZGYNB#7kq&Vmxi8M_%EP{wFL{o;3&#}TnvkoXhfQO{^twhC>3z_#%{{shJqw+Y1- z;$b#4L;XuN{pIZa2npjE>0>2-(A;61`pjV2?sQ>NQzXco>EBvlhns}L22&r0xP|fU zWM_riOz<^`XI?nE49!CHfDx~n{*XwFtsFRlMHV1V04Cw|l?K?zx6QA;Ej%tg1u>Aj zNkpQ%q6S`j4~j}Y1e4ew42c4R2l_9ACV(x27}6qo!TKDE~ePJ6+yZ zB-=K7_@N(xNPppYd`+`%(q>i^TfBm}l*_5(Y+Dd0joiK^u#v)(g6|R7IIcwm6m%ic z8d?qgIHbHeWBVd(u9eK*ncEr)2R|efc)l=OD<+?-c0XOK>4_Ujf9LXkK4Txe-iDc# zm2Kbd^5g&DIro5?0QFE`5-NRk3ghjpNiM8Fc#R!ZJc3W#NeUPzc5kvKtWaxZ+yz){ zxm4>OXeVN6OU70w&VhPZf{>z=de#59Ti?hEs%95dcE~t8Ay~OX#|i3ZPM!xjHn&fb z)Eim@uhUkBeE&&RHhJ+>FNMCu$`PXPhkZZ}fPEcz-7;+t>9jZA6 zm~S7@1Suw+neoyj+mU$q4Nx}2KkqMPm4V+N^cd$B)<>uDiib#-`cRAd^lJaG^|%xwhfdspFI-saAUW^K-b>=r-VPU z*ni|$)y(p?!RYK-l=m$6qC)nlU3hl_8s8^ZJwsRbfUVN0CoB1H%#j&%G+35D zR(g0QCsLTxV+%_N8f;W@?rR5to(m#CxVeVXtWO%sUe;R)f3G5Z1sV-H%bqtHg!Tc2 zxKK@Ie|qBNXBHs3x=CzTDsSG|#D-nleWcsmu)y@HpYAX81OR{*xozMv$TdE6{TuqCbOwEpl?-vPve^NQ+`_B-piLCD_p|MIAhd)2^tsd&ko)cx$d#HVk zDuAY3z2Bef8!KbGL=~iCP^c7AFnA#rC}b-tz-Gly18vx*@jZQHkM8^hZTRz_8OKrV zl^p5gZ!=9V?toIlxjH-~7nd&7gDlPEJ~#a?zt~GvU%eK&TrXUD2?fv&E=RW!K44tc z)yQ!>^VL3+E93-*(`5{V`?QF#?b=_i;cSQnI?FQl-y^UOeoF}Z(mSgY9=ASy-fnq~ z0OjCwFCy{;@&{Q#{9VC3w%5mG5-SBznnwdNj#a2kKxE6lwIs?unDe#?Y z9mkYbr&Bu1{IvPVH-iNUjJQ9m-Hm%bKb6Zy zx<9U80ycYaNB_%H?Oy$Y&q|a`IhM$;9-?{ghEzPgigPb=7_SzM{q;9Zc#+d#U6Uhw z2`lsSck?%|sy$1wa)Gi*bh1gbBW@K2)hS-;s-yo2733%)@hSbhA1VBb8lGEQ_QU96 zLyJL~P90aFFro7nDL^9C zks3Qjx_S7XB(e;?V~)+I_{`W37?yuZz5Gh+Jh3PNDr*cHaRf%CoL zm)l7TtKxqjcY^}RfUy!bKh7^jg-BHAPeUjxXcnX}XBOTyv)t-d6gns0pW6(B?+<_3 zXRA}pZJQse`MNhmGFXAzwd|oq0S=h0&Gi~JtDloD{^AOazBXXBLxC`G2H6meW)zimVnynAa6Tqf#mi#=8h@L_Xs-B;4Pd-6Yxo%NgZ zj5*Qmg;fySoMi^C%-u$-YiSRBoxBPQ5&UmbzULgXuU;U$TynPciIrbLB==coW*BJu zp{czFH@N__FsHf+Uz$5f&vj!?9&*H#HgJ0Bq>lZ=bfr$fKB`o8^Fgs+}#Hl!IqOt(mLBVxcNMf54H(AtM% z?@})ozADzw@3YgR6XpLkbNfdTZy#S*7fcxB;*bFVKZ^JYuX`9UC5755a@5G?bH(g{e2hB2@--hpwB}Sl^v`c)09K|>QN5A;2g(9tmFI>xRED1wYcwMUY%I4&Ii?=32xmFtQJe;FJz)jS)Y=jpYja1r(Dc7*%$x5FjeDd zrAH+yB;|W|qSEV4ljg3T{Bv9gw>fxdv7;<(FA|WR z&4gl|wl8 ztu^J0wLEGh1?Y;`W8MTWg%rl~rqfZp9Tq(?0+p2uQ`^69{Pg^9zQP`EvO?i&i5JRF z^qp7=KdC@TM+}#QlJ6=o1oJPhtp!!nE7s0zhP6j#7Ow!-qMWw5>pC%?AYefkt!Pho zViB59DM+wCv2h$@eae4obrn^lAu{d*OVl6p^Qo7m{#I-VWR z5OU_13gZWwBin_fzc^WiAilH%PmYo{ia(Mkv3=n5||gqpb^8IWnyB9E=Z}0GklKf}Hr&6HB>+Exa#&1(AAm zB(ADSI6LR*F^+}$hWutVp{YmWfzOVYd6}qAZt60J(}1dJg)k0t{Wu$8f+38lFS)kp z9}uPH;&fW(R-DbL32ROx5P1X?|B<5(PM`Pg?zq;MZ*pc!+DC*m@h6&B81(dLL{Qnq zOsB8a5jqsTC&ct>Re(q?(u1gum7~%%kJ;Gmj*wC8T3GmTd^d+2HmEx*jYyjq{n)$Z z^m8_}dG1bvu-J?`DJnnD9V!scCmVQw=4Wazlayd;l_zI!orTWhe~vs8SEVE=A*-Zs1o;tNSI%}+adin!X20aH$@mzR%^TnZeKk%u>))z8gymatbU zdVcpgA!blY658W^(v>TXYU)bcEz|9l^J7;UN&kgX^84H}90aGjOmA4Ah_c$8{za?z zJcVZBJvhS`I9>34v@+BCHKR|CcgM3;-}5GB8jc-tB*|%2Z^t2EUc%M!@od3& z4?iXQxF`%Skbk~RbbXl&cWRYjdy|&+m#3^cz@yxMp2;E%k+`TYCWdEt5oqM@rDShd z5>XAM4L`x$oKHsF_I%%1sQCzSvdE(Muf*hdBNMj4xr?yX?T(bhDj5mHs{?8D7WW0| zMdW3oP^6szF3%_{bVK=5@)=?Al-%Sal}dhz=0%|N)m8BC^r9CsnDd(h^4>1Ae?iil zrR`I>q%VTAu5TY}q1Wi?Cp|2S192D1e+(yIDmGHEH;jv_2F?>&4mMZGe@3`3X#2*e zf1M=;6gp#%be+W2wr_X)%Zn>T>XKG?%&K00iWn{-;qn6|fk1rfURjtWntgG*mpbAxgwDJn?nUw8XAN~J!z6cABbD@;F7S5KVIFWX|pby zQ6kNjzvqV0sOQyh4)08Mp)stnWGuPu40Ln8JyS8V#9;zG=0Ym_1P2S)xj=&}co^YR z+pR+AX=gZE#&mp{AZgI~JjVE?)579Z8OLUb`~sks@AanL5uWOAch7adp``P7TIPX- zn&oZt_QMX7lm&%bywPMGfe2=8yQ5eZmqrqMm8bRY=NjctS0*d@Zy7cNk$zmT4OIt^ zQ`0;=@5fqO^@!8+G+cavo#SA9I$$UhTB5QA5mZxRLYN6-Or2%Ey8G*{D{uV|YI2^T z*WnET zF49EU(yQd!XJ{S-WPshk={wvVaS0fTv0UiNp{jZG6p-cqG6VR!ndNOw-KI-at3ocl zlmRd5y*Kqy8l>kjlAocU*DTTAFl$z}`_vhriwP2nAq&NZ?C+!JH{J0IaF`qG8UC?y z{#YAR^jt%-3G{j41_P|h40{~oHQLv|j{ea58`O?q@&^n7h4os+j zmEu1`+mfwx<-{|@+FSKd*N)2os)%SodBJfC8rsrCwMW@8WWNFtXZ1~wgtz=IUZ5xgm4^0pH!0$(8WK1~*Fsa;5 z#Gvnzza`WZ?Mv0x@V&do$lZ>|L17O%H1k!7{%(v5VzRpVj0<}S zvent$#)%u6)W_u&_Ag;6YjwIjEz z%ZsHr2j|dX(Yax0UI?^Y6GfQn6T^4r3&9gy7T=KSR$pqHc^Rg!#F;L=v9JB5!7X)=eX!^K6GF!XRqd9195{Me%0j|EC1^)f14vv zw@g;BB}xmyh_)7aAVDGd>JW77MgF*vtK2N)CSLw-K`JG3N zGTcnx9;pIeKnoDc-NyOnmDs5o#?WMQr<{rm1)Kw2mLmgmQ_U3jXzQ$*vg<3l9gEoI(=^k}e2?%Gi8T_guCS^x!p>gTL#w7hulXHNgQ_3|qJ|2L zd27#36MF{8jrGm{8mg1X)$#!;e)d$X)G9V7yNF=AwnF<@v8(Bse7%I3$-l0yuyRFx zXpDV;NBU^W4+XNHGK!tyy}L?s*l3-FKVrioZCx+g2de9MN=*aUQ9%jJ3h-E&jb43m z;TS;K-vSKjY|BC?7ej# zTp!}UaYzh_S^ z|Fqc6k=*?*Tis!!@WSLs)6k{BF|sqilr@c_N9Bl-j8dTO?hnqL5U?_)Hw_XvSEMz( zKQN`+Rr=;X)z+IQNKw71)JOeAqG=E$^GexzTrIiN$=ad#c`CivffSrnR6#3rQtc!` zFY7{5*WFlGMntce9mC@m;BQ%OD}`RkPT*0!oE<;6Q0e*-+Vbm6Xu>RAAzb)lW!>_d zW{K#|PWc^=#^Z>lD0AB_*=z+#=EWNwnYhu8m0z=>K=Gh7`^_LmHYcppe@>jbUd-Y- zm}U@4A6%$$WKoHUe1+I!vHTCkCGkyXcY`M|OaNV+u9toe7+Idgzu?lNH{i0M{q|-Pa-z@yn&My|_lpZL+=jA&M~l=e zgR8R@0`)~zDVZA9|8Ih;=wc#RCzkLc6p70x3u@9}2-g^T$2UCrrGo6RngOquw52~- zJiW3kR=N_if;B()pi)FFHf|lb091A`awuT5LFgIKcyoBNNn;v8Id^-i+*kTB>)|f9 zZnW)WC{u-~Z~v?@82~OE?Ne9J;t$kZCxPSuD0LFG8+bwD6kXwBe7By(A=~F zk@c$Iy{*RSV?SN_h|wyr>m(_P+wycE6zvlu%=<=v5`~Ri^5t}JN*-FnEk#J)JR&TK z;Y&1)-U@$jw`UphF7NGmnmw)OIGS7^C})RKW}?ON4^Q%r3MLBs%$Gb}DY^K4;s`*v z-ZDAOwfS6Y-0TzCWj$wd1D)pxa@yMcIX!I1cGIOT4Xi<$0LDtt=jAcVl;F;h&Y9#L zXZh)J>f#_4A35BEEy{(eVR{spXon^8392e+M=q@7)NqqVWmtcl{&Bd^1k#nzp|f0@ zfdTXCE{^|B7JTp82_Nn$W6OnuUqovsxoJsEEDgO zKEYn@{qYsi3s%Qhji#ZQVN9EJNPlw%zulsAIO!#losUP= z!}#LPBSp)Krc{n5OIcEsX<<|<6y}hF_1+8VkFAs-+A$v)m-TW`a6HxuvxR8U^D9lg zW}b1%nvumk3ie#tKe(XSg)n9Psq&}{!^jP0jW)qg{~BiGy~_|e%(|0bGIzwisUfG5 zk?yQb<)Et3060_`OsbwGUC&_7sU&P_2;ptq~z*?xlUW!TGBI-z~kJWtRGx%CYuG zznPqpF3^IL9P;=)i1G*+qcS9~vWk>BxTQB%G%FqI1@JO0H&u&;U^8~#Cl9ouKB-VB z$12)$eXZp>1k6t2%8ND}_+ZU;9_{ykMcb7usQgm}ci`G^+_p!EvSv~K2Oj5i7EvCp z#vyQ2Cd5Q}SM3B+%s9kzx^}4i-rA=tQ5^bz=oa!I{sv#xt=rX+qjDi3m@!TXfvjIdAIC%QCRIUF^_&`s=16Qjff-0N5)grM17JJ6519?iR@+k8RlZb zWzX|17cG}x54Z>Da5k|rRdaZ5F;0ejIO>xk3Bq*CND z0lXlO9|uA`h1CIYkBng@gh&F`vqC*TYRUD?f<+2Mzybb)9avTq&1!+Su*$~G-Y#iZ}IKSZh z(+OAbeFVfNxDMwTE|WgFBV$XyMa8jdq1k}r!gw=&JZdjMgIfM5IlvN2H6eH$03JUR zMyC)ROTZXL4!wf9?~tWLo=;OcirKRaq{Ii~ojlEtV2fTHoLAMt5%(e>QCS052*eao zZRS9ohhDn?LZ4{?kTB&`>;xTEnj)s8jsX=i$gLMN4mY z@c=0mBMuTbA}U6LRMw4bE0l>hzxDy(fNk ziZRIC7fz%odTT8x>5LwL^_Sg9NCYP)?-PuJQ51>W6_(Ee;ro0Coep>LB7P&=8C&ckoc}`qS$JSa8q|Ap`}i zxM02f^}FBXc^bAWIsP!R1Y!6CzuZ2!6g;Yq_4>J1>84h*q0|>a_@xIO48VDxy~Uh) z4bXm^vL1CC9EdwztMdCjP6y?G-Rb^gBV>~*Z+Zr8y{w^e!!h^TfmhzBHsY~F;09bt z@3^2F6e%)EkRl{eiOr5uHnEXEjw<-QZnTZcd0ABmEN16dFMXK$*j!TMYWE1AK0*oH zxIV~@1HeP4I7HQfJ_JX-Or3em`gh512DFdX^;62c9j&tp8z}k0Zpu@}Vy9OC1U5Og z^d1@o0mht@FM6INRCZZ}@dy zN_{Egg!D9sjs~vZG{x!9^2N{Q`Cb)F28MsqJ~Ro{n;T_ z%Rxf2#>zPq?%>BlZtQo=(_u9{mp7Z6~VmL)D74?gu3Mf+l;H*aINo2@b6X&+} zX0^e$OABG^DJko8riWXE4z(^l9S_h4DpBzBBsPoO>}v@KeCt>2I}QHlqeJN`^Odsw zceVn~(SM>_IEw1W-8DJQuo*pTwcW%b;wkB6wkFfERBY4-1zV)7owcE`=6x_!1F_S> zDHGK?%>;(0A4m&0E3jI#w6|MD-R9$S;+&CN zTZin<6dw8^XE^}xh8Wl>NHT3*nvZr=sP}P?CB#S<ppX8heetj$YH3O)=P5#Sz!o?4|5Uh0F)+6Bkh%2oZuW3hpXf_QEl{*{=Re zaY$up>k+6zI+1)~7XhEaKM6`ZGWFW((-pD&2>*Eh%@@DFhblZ1YN$G%f5xqVFjU^&_~s&en?pIsD?S=A$gj3nPBaT0AbB~JLat!%?ab`ApYRt1Y%tam7iITyF^- z9?gM}h!gs;ZxLt50uw%15r8HFk2K_qkbzJXQj92X*-_rjNMn3RNz$z)FXFHhf=kY1 zWx(Ala(ydpv~KLZvMtvq(qgk)tfEC}++0us5wB*_=P`x)5yi?PhPOR)?-4}t9w+4d zN$2L^SjwTWo#*UhqZsP<;I)oC8GZ_Y+wUAqlp<+n&WRjd{~7K+Cz;B5@S-b<9!KQ6 z0e8E|{=>z`XB{~*D_%d zk&fP6%L{)uU{Agw_3-L=MiC*#FtibpK%^>BS-ulugq$*z?iSyH;iA7l(-7Rk7&=;a z=Tnmgi->DZZb-QjB^`!)ei_nO!(U*%-=1}@CNXAD4}%J>vL;fci-=zQ-ZDq& z@4Rq&i{Y@WSA-bF;%BY<os$b1$tEbMTKfL{2s(YoXqs}1rS-*UHwp~8HU z*)M1$3&uyIJ=U!#kMjUDxhlgQ5ygE@=L_Er>&Xi0L)dXP8?E5@#MkD+Zpcf6`uTs;Ex=u~ zy811*{%L+2#2zxpLKaML`pzXbz%+J32P40GY?1k_BGPRA_9x%ix|Pp0|5UV}Rng3V zB2=!%;KUv7SOQrLE;qJ}zi1*9&^;w%hJSot4zqUbYIh)}~H#^S8khZ2L2y`TdG6=5H@K+ch%hiP3yD;IjtIG8 zpba4=D;RMo9eo(iv|L9XG|fTFHQ@O}tY1Z}CMA;9JYdRUfHf(vaHpgzA1IrKJVz-C zJ!Qv%=xJZq5izfOHizC#Ka;)Y#gXznZr?x7`ftipt?y*cE(#pSZ3$iPny`pzB%XD| zkC7^(9$FZ#?(>xmHE_ZRdQZuSHw1_LFl-IBu}PPDzynN;rL{KX`0jw1Rt>F#89pL!`H_==2!UNIr}niF0=YU`myi2X*qc?w z)8E}Bu<-SfDq)cl{5c{s=KH@8fGwe=f0Ob98nsn^A=mP{9Pm zENf-m{?GpmTTARYjH@~ibk1?m!1}qEj=g=ji7+HYs~jtWRJ!asW+?(uqleKo`~wpo zgY_1L0h#_5;?dCX8_Vl>Nu#|TqdV!_cxVN7{dxlXE6|5{$J*^@)wRVxH6_7c@-@Eg z>qfK@nRB@)&rk(VNVQ;pvph@bBLDj#fIFgQYOdYz>Ae=!xBs@U6UaMcDvGFPwZif_ zndX-MEx|qF0Cklw3&UgS2Z6rXf6xlH71s)Pyn5yEY8<*xeVdGa8L_&_!Ox-MEtIJD zbu&42{aFKizw}SWsBs57<}L1kHMY95*ncjLEkM!;2DOk#GE;AiaFjZDj2F)<)&4^G zfehQf_^>_^T~ zh0jNuM^w5N5ynLrjY;hsqX2v8r>WM_RvfvrEvN>+^ z4IUs^xa1P>rVp@{*%$CIx&R0Nh#8HER%J=H_I$%ZSEZxik^y%P)+(hxZ=tbh4ykDE zToG)9vM?y>{9*R{EHj8&(+KV3W@II#6P}pt*niK}{=Qi^q_T z;BC+tV(-rdJo>02xc24;BB>yB&n<~&ugZfk-)sc{V&=M7qr9IEVpaU@4r1b^PsR#f zBtl1om&lCTTmq}vH^ESR_)j=#v zJ-|4AJ)mZb65%E8%Cj7LASZOO=*hzNa}~l4)z-xf;Wc%b(M5XAWN^>(Jr>B^J4GD_ zlUEL4FePRMb2^wH#l%Q=rg#PEoD-wtCu_(E1~^90a#z`kn6S*p;&DpH4XU9~o1Y~tV_CL|I@OCW&h`BdY$)GERx|-d>^cLOBqB>^&DsPp$Y?v9^kL(;X@Cu zmlPvky&KSCr{-)h!*k>CfaG8^mBQG#&?=GsIHS-dVjjib`p#H41t7luS2VRD86O@c z%nK`>ri_bkZdzU+49NcR)7yAtE+DruVp6n}ZK(m0&I~ve?VxW#w_I8#)ll4Av-*=* zCsT%Op7*o7DunolCbRZ)rys)}SVh$qk9b zxgU#fBzJ$-w6cgd?xY5&xhkk1*5R35(pxBON6IcF+BC}i*zV#8@a^v%8rOzx-2f+F zZIitZ-#~g=Dkaj~4uxuJ7qnh$#q8|VWTu4(otQemc>|hnM}E;9R?&_qJjcoqv7Tow z?<>?FZWQ`c`P%BK(=P+iw}8TUaV_CsmmtMM)o0M)dg3R3eA1p<14n>$p;nW7-xd_|d{Y?yhf!9b<43rCLm5y_NJYtnsISEhJ9d%$xC}Va4^%fK7 zf$Mb+Wqg3_#fZwBV(2^1!uy(Z7~=D)CEK$wb3fX1#mby!)DD=!B!sXItjx8VZE^1@ z?c!>{On#;LLUli7(HXCT+n=X@1ApVJ-9$X3CE&x!U?gf^!WB1skRijGYu;KDFc6a_ z9|ue4iuCPJ`vKY<9E{k@W;^Xyv73VaYG1wUNT_6$59W+k2>J`aJ^7BD2bEsl&dAvu zK(GG1r9l4OyqMzr+aWtGCaPj6=jvle|a#a;n4KlTK+ z*xu6jf{PX!m$zz%6IqOe^_?XKt+Ko#TlZP3=@QM*#E$QR9_?J=qRYl^w-sW;Ci9d5 zNC)N;1)H3Qb_66mPtz+$1f$)){-#Eq4g0Qyt6f>wu!xcns$RLs*;XT0joI?SPK^b$ zTj9hJHeXb>nR0T0R;+t@SST7x{$+K1@|mLuYhM`7H(*G``+EB=h5l5JxSggu`!z6J za1%q+&xOS`7KZohjoz)r-F0JieXm~yfE4#}!N-1mV*DT==-d+tVRd6j(OuwK6T z>f8812`W|Rn+*=SBSW7CvXv!;7m>29FjvRkMhjvY2%)ROW};G7+-Ejxs@1(Ms#+QQ z7w|77K3Z!A>PN-{cIr8N-48{N5I_eGimorclzVmAsyJM>Cjz2WTx^oRGkv6b1vw8z zaFZR!@HI0JhQ#$qZ$BBn#Sd&EOWP>PE-M82m8NU634m_%h3Quy_C%j0b!HcBH)zBM z4W)W)=ci+RC}k2#_;aO6(ig>GkD;h3DG6N#-B_yrD>k93s}8DEodMd}!IEnaePCxs znb+WJ)x63>Y*@Gqv`o;L-zPspft>@;+cq`yd!GuY4&_ z;tnDpyJ2nR&T{Qv*nfdcsgRXH0Z55zGmsjoNt6m#>m}L zAw=Cmqgwfywn!i~4eF%eu9057jb8^n)K>B${6B7It2*=jq@^6}ofpQgANb`aPmyfG zF$I%r)>MtE!fTG<)e-h$N_;AWYDxeyhJ~(`?n|V}9ptRSn%a#t%1Z08PZ!p9JhsD| z_wJ80QTYkCr%MbEo?l?)+IMI~&|1S_7_JI?Jl^x9-Z{uh2KlNl{*PVI^{B9df!#@s1uLsGdvB`>G4w*Rs zI(nDP1xf;jU{X4MotK;qa0oEf8}S=>ahc zq{Sp3*^E3V>}YL#ul@pFMqPb1GEco>IlE!oBsoP&Jch!-suxo8Jz{`8ui)G#3q8DD zM%p)Qy*wvuk_YrtN3$u#_N3c^~!dR8trvz7tENcRzkP2qZ%>RYxl z%2$8wY-UONX)vtiMN~(b=i!#A7^5bC$@+g&(7oUbv>F_VH?aySSd_wVd2CnI;Fbp0 ztlozJj^YXLx&Ar;4Fh%!be3P-fB!W}Ge=we28*!H$;q)rZjP-F9=_V+YpUMbBiXFd z9PNt9xPA8Fx|0+TtX4Ui)nP!{s=jSmwU7|GG5KRWGP{=Vyj}@7=6`A1Mh*Z0NHe7j z_0wnBa>s6rdH7n5%OuKTc%SUL1h9!$UHhH2Z+&w*VLQ?@sn_G>p#2v3_c<-?boGM( zR{PMy(1}y$xWPitLOB{ZbH;%LMJt;qIe+nV{@6Uvz9PPYG2JbpI}MZv_5r?$in+c0 z#cKwYpck+AODKkA2{#}90$ZNHGNh<#Eh)HoV}r*_e_YjiWv$XM_2g?51H(=PX!raF zAS|v50`7wbY7qA4XwRK)fA<23y_P#zj4}DB>g;`I#EB{~bh1F&erQS;9N07TB1uq< zT-X)d`na~oHmJUPj97gb*&Wj}=>^ifv(e~vSWm8H`q_QKXX5imJ5%u0O-_EhMLbL! zfZ}ag=2b3`#~3R)|EXdVue+>O9DU{$SPL^1&5%gc1XZ2G5Ax-bq5HZM< zf7%{LlgyT&*RS;~T4o$}+k++)eMvXxW4n(}Lk1i~D1KvywD zEF@!r+aJ6*IO>{B#DBhr8M!!?Bd@Kay|4X28wf<-vSs*LHjs|~S%L3r_G4-F6JNGd z0m%Sz%EY+IUnj$>(zX~W=^@YG^5PnJ2`$8?-N1((gbJ0aWp#dZ39 z7n@iefwHk6ko+WAO0irR?M}<2n{u_F(OY+m5z>tWi`L-I@P+rgs#LU|)+%gI3Gtxi z1YvAobTG{b-3${enfz8?QadX;Y~H^M(p`!P6`Jww)@<~Qdd^9u0 z#OuaPPKwfJH*LF!UH8x%H;!4akPV|xOrX&$k62T&xs!+Z98J|@gUny;G7pw0Y9$9= zoTQUd5~}H*PQcr#a<*z-1A{`omSIjH__5(JdcXUXEe7T0ZQv`(-Y_0+G`HW@5TNqihSXS@1< z0s`Jp&VxPJ7~&tWbSDy(091K!$2-UVuiGL5d%<)S-j3t+nYetaO7Ag2s=3;B#cWi^ zSzpQ?A|$?x#!Y+Lp?+P)&MtU@y$^xwUK-4~qRc#WhIz_E-e|@R33$7#P~2W z=0rBilxk*!8M`rfu0c5s8@X|ZRxAnRN|Y)0tVXigrGb?qmJ6l7K_)}Or8GRudL6fp z`4-E3i7wF*uL z)nuuXgs8m@?be{})-{QwsE?~&=)rq1U^a(GfClElHcK3wHb_r9@;T+`%9p!T$S^LJ z$o+rqUHMm2cNSNqMeGb(VihVRBAbr2h#(*saA65bjCHG63szAUDVr?W5F^$(QAj{6 z5Ox8DR`x}ZE$CnZMu`C=fI^TEwtx{561I@}27;wd|A09?$xk_XFW=?9yM6Ax-}^N1 zShaX25`QGKiA0SVdU7ns$QirYu=BfrLI`nR4atHaF~~FGh@jUHec|^;*dN#%4cy{o z0?SKvNfWZ}hKuy}(}sI<5?+Gg1GWO#a8U8~XyAWU?9&LKX>(W)v(yl3g8CK%C;IS| zyJl?^uZL)w->f=F^q{XZH4YG|AbredWN$p4X?@TACuZ(egAiiUERzU4>XI}>?OL)5 zE?W*It>o$Gd&4z~I$EoL5p@0$9r+-3v~q7@cM|h5TYNd_TCiO9vfwuJIrg~FIZ8t! zZGYUtG&{+lxH4MYHe_fBPt$Ua<_o7Bh~FDyA9wl>$R0&l&XZ?lxfOCty;_F|CWvjy2}b6QErD~e=xj%S>Dz2alY`UI4PQ7O(Jqh!zKp92I%=mbyb zlJUvSiz+-|y~-Y|k^Su>*3xnhr&3)3gOBp=pR`NGKy+$v@FEhSg~m2B^U&PrG+ks8 z@DVC=>JdI=YK~;>8N1dY5zSt3bz-LBGQ{M$>>bX`9eMoNh1ZOO$KV8v-E zCFv=Vg?6#iL$6t;Hc6vXFi0)qKZ&#_T?q70x2eaAu#;ehA=%L!quq{~g@wECjZfHv zsYbYS)YQkn@b9)YG9*=3;GL|E^7ySvQs?CFB38x1pUH=N>hqFK8mq!K9PbW%OSD+&X_^Fg%*#o2$bE87w0JLfka zX8njpaYjX(t%m~V<`+P$A3C;fSd5v0I`rOenn))zw-j>X(XO`lSRV9Rv&&?XEHp`S zC!UEA`HB;hMu+yl1KVj1B6E@<9gc*8Qhf>ws?BeTDz1^*O{F#n46V~><lqbNH7nNNV`?i&#S~S& zH4uv1rn(zKT-ltoY05S}7WYm5bB5rQ4CZ-tSbhX^gB%LP+$@p5cT;Xr=T!E@@K<(Q zAx0_ulbQO3Aw2{LCmzGK)Raoy8}ro3;vuh+tb`KtdEfKlJ`~-kK+&ickvvHqPpp5o zCJ%d}A9D1o!P}7p~Q8y=FPciFj>oIw2kjt z_&-CkHl#k%PJ9`C!vT?gdYCw{)-T+#RmpCwEwZsmHMFa9Mn3 zvAL+lDvJxYUoGGL_8q@L6}!(<*|S1NEn!Dk_$cY*^(|EYrx!v(@%R9ZEjZ`H5a@+@ zZ_L~_VoyHroC&$6Y=pLj*7|j6 zcZaUshd1uMl0l(PGbiS#hwwgqtq@5XG_0qAZfvM=1YWPJmI;3~=9x!-LY3gPRrF&` z2lVf;4m(t#dLFr@_aqqmA959hDcX`(yac8#HYw zsM+a&0Evcsd%a!)GhZcYLg6u^1RwgK)oxu63vE{9OSjKI5MJOO)Q-Qfm%V3tv}e;J zC{t|>>8P?WdPLmNz6TN3fgdK~Yk~`HG~sAno}@hR=-z?wm%$|}`UKMfHHfz74^>9r z`nd%6GMlUG%78O=5qT*%<5e}%m7JvIn(s*=-=O^*Hmi>U)F|=<8UWYc`@7;@Gdj`r zXP#!kibjTu<3DIQ6W<8K1WkfXg(I-g=NxI%3#?~6V3S_0CNe77O$H(aC;HCL`c({s zr8;J2rHAc2712wlsptoq4(tdpF!kwS9B{M|h4GI=v?3BK^;iauIPaR>pio(tm|SwT z#!^^~Y{j-cf~B`IkX5Ao_R?x;u{_<^ae;82)%}~x)UrJv=;Rhi zCQci|CoFXtSqYoWdqDG~h&R$QvB>K@muYBSK>wAf;nAgvz+E?AIS~l5XDOFQc14-Z za_g(pyS)we&!FcD$dZsV{)OXc1JGBkE!0~;6e5uxHHNJ0XUPyt!=5C$K#gEEdz%YGK>>J)g z=QC%!W;o($3m3IirnrW&$4yW(d75fIgrD>=xow9KY2FbxR@$=M_JAcvVCUWsPgm!J zGFz)r-i%3x%M9c71me=$?j|#U63*rVZTriiX`4mFaIOAJkjckJxvhF=3ZDqnzZY=H zR(CEGe>2c2+U)SJs|M1h{mEn_yLsU%p;$CkW}icvWUZPKt9WZ>ABX7uo8s4$tYHK{ zbA%ySIs2W9wB|c;qU6d2?X@Z$y-QVBL+I6pF~qBxuFk@k*2O1Vo$x_gtNG6dC2q zZG&zY(*y6BB;T$u=!(1V93`&wT1#=n{Zz(w7_(0Fl+~$6yScS+*uwaZw6L1nTKfP_l7$!_&f#(mw<z4jjg;ZoJM=3@xTV$zPsqmZoH5V*5emlLf9-9f zb5$;5v8=<0uF|Z8KJ!=>RgfQ^+%T2fN>Fu_)$uH>wMD|kfRojCKzBfRl6Q-GR9@-W zoG>?($@f6_#mH*qM}FIX3#onae9Skq%5atQ$uIRr`)W$Ne3zRZfEvWM90|^ zEBO&AJYP+Ze|(KK`YD#(4&!W3=LOvfu_DVuxS#j~n}Mr#TqWCq94RTi`r^!%Y${AW zW*jY@M&}DBf2(z9k2By?lP3e?%WHRO98u%52cy58Mb}_a@ul!Rq~CLzM$8sY9}~Fp zYy#9XeWCCM?BH}%Js6FF-{~5#f6^+(;BkgTXMkLir)#4)e|d_%{rr+}0iFF2)YmlS zUEIDH@ds@o*g8oI)^v$&f{p^#C zfDRJ;B?>P6rw9$0@Y!E<%LN2h$nVmv@*KaC!14#6B(Q>8l^nb%VoDXUWb2hGVo}2? zRfJMSEHmj!FGA@>e58Qli&8}>RfJMS$dQ~9XaGL=KSFYDWS-rM6>Bt{?f0Mf16G1H zf+N35*enkLC}FcAK%tCFC*UlRfP)~w)Rn^1rZ;G)jj8+d_J?e2_Gm%94`Bf>`NafA9A7VwlMFoDi?XI?e z8e#drK8=?x8&G*kNvP$Fr=*6G8h=xraL$Y%gK6AtT%{tKU$X*yoE?tX7aTZu{l5Z} Bh%x{G literal 0 HcmV?d00001 diff --git a/src/Ryujinx.UI.Common/Resources/Logo_Patreon_Light.png b/src/Ryujinx.UI.Common/Resources/Logo_Patreon_Light.png new file mode 100644 index 0000000000000000000000000000000000000000..44da0ac45c70dd752afaf84736c841834e3c955c GIT binary patch literal 29395 zcmeFZS6EY78$Y^{QB)8d9mPn~&j2I6C+daV?wmC-*s*>8b_79o zkWQaCk08Gguz$90fxnFH^hki8Z9bw%*~WTly_pq=yIO9DX@*R!vUrY04G}e{qST_+$M|-&0hdY?Apc@$tT$zwsrC zAz^0qjS{w3ZKkj57cK z4}lE-A197*b12`+6Glnq-$PZx1b#Ak-FmY_p%rD^x#k+Ov1r@+q=k-TzgGOb5=o(J zJ>f&rt0a!sw+tut^2gF>^l;k_tt)hihAF=Hi-8@%^q?Pkwg#Jwy3cPGCw;P7xrfh6 zs)J*o?2P?_K%!)@|`kpxYu&+<2OUmum4*ifCM{WXvZC z9e@83KiT?k;QHxk6&{tOXGOMtN!xjuPIuWF%#^!-Vv*11K$7afK2O)OG$httu`co% z;&VgNwP_3PrI6}!%!nY>MoXVuzt6@-kA0MCBg+yHbxQFqxn6%NKF=q`b~xu|&uj0} z(W*|LgSgz_yrVOtl)>4)Ua9^^yqma938lpxy$=bt=)u)}+4_36WT_GIfeO2uEoKom z`@^>CBJ(Y30^*7#Z(RzOCU-m3kMDJMu6XWTdcaAyE@npcpRy~_S8BEK<(Hz(dnpTf z*;#LQX{a|9oGxiK)YgyfoM{Vr5xi!prFXUCqU||tJ58~_(&dDLEu#PBDw6cRScTf6 z3$u9%&hC**4yY-)jTMl++Tk)`@SjD5AbUaQI3Hrva+szTAgHF)wA0Q;Q9s4^IbO4M z=an8k_MDoTs}1+JjrXdR?%LJp_=ih5tCj(>*+;ZeJOB9GWcnJR+wqfU;F7xDL!Jbw z?>=u$cQwekq}#qPjmIU=vB%UFp2b`&+6YrrLS7ymK#&dr_3O+Z@9t5Yw<_hLU+bnB zXwMP-$G)-aqpbusamX_IfRm!^7F_l&yRjJcZ6}yJo!$N|Nc`#+IkXq(j&h%2kZTuxJg_>DdWdUOz74p~X zOFOt2WJ(TqaQm*R{^6=TX?ay7Dp4A4LNI*!x}{?zul#_%gYTSBB2Lr9j|E$4Hh%~y z{LH>_PjG%v`CY`Z&K!Ef+4^(pt?h-q6VqBGNsj#rTc}Mk&x5|^hHAQQ{j-nC>m^_F zb#;wxYMR+sx5rbl_RO!~2hy1>+v9q>jIAgZ%_?HtD1sm7MKqU4!O58)>br{`(Q4z5GN#c!W` zD$40ZxyWL?s5g40T9STGN|?1hZWLc05&Z{u2p6DM?quSjd%8qkotd?48YlueX8^wYyEYtPV3PQ9kf?oN3SAMn$4o>lrrXfgy@QS#?xLc66%oLCvQ;~3GtYSkqHz23t^HZ4aFmOvGRZF)Z}Dhp-f4~D)O`oHUm-hVkEGKF zRrAB)<{M|wKzy(xWn2r_Wz~0%>@9A6aap7Iov?!=b` zjt9h~vQ7P_u;xn4i_SA~Y0^tQLQ>TeIy0WuIK8WJ6&dcj*VQ%GiOJ&q4Ssn2?Dk}5 z?XkrKd~4$!M_}E6^98a%em@|s+IJ&u`za)=$rUMmSf-pDonfj+WLaFfH*-Yz`AHT=Rl3Lfw}=?pjNr3E2JmNt|4*y5U5bc3*?K>jec%%7>f97fFkw6(>fY98DW z7JlLVzt^uEF~4$8J2T}a--=sF;-Ir5D@J{cIh{wcyU#mGp1WhXfha1}>g@#87JMQg z7K3`+Q~aII5L6or@*Bkmn)+qolYjIV7OkClzD2w;!;xiq`Py^*!npmU@$?r?mmmlV z%}%XocjIF42|+=hKI|YpW1nvLL|a>YWEbs7xtcSctirV?XI}BeItd-7as?jR z9b#+F{`;&M$IwO)a{SM!HzKt(k85rZ^z?9e%U`FYYVfU*2 zBDP@ScP?_Nqdk)F=Z=)Z&U~lUjpZ9cif#*}=}f%oNFB-EhMXr+^D#$wzyp)3@V4xu z9Q6w!eO48hqCIVERP+TK#yGh}iocg!yFFOwPxxVuQe0nsUo645UZ(3FtxGD}pwajV zVwnEg7ju{uC28tX=e;r=Ex(IOFBJ;i+)Dj=v&FUeaV@PwE6*v1v%fSvA`pF9%*o75 zy(j^I$8MXTb`6VmHwBK{X!AyS(~TMnMH!LuG$!+h6PDTGvuQ z~6nQLEAsP*b%<@^nYOMmd{PLUkY{%M&sm;PNaR+XWw3XZlK=t znoQ?QZ)u^$H_QtwNS&E(ucC47X(Rl19`j>zGwk4+)=p2Ycoo>9V$bcv1)Ly7i%1)+ zP!hyf&s@KgmeHmvEM4V?dXuLA#c!zWzFH~M=|zpzlF0YNqy;B-Gfwi#y|+wJnczR7 z$0fZx?T=|6Ih)TncDuLBLb9V~{TXBaAAF?x?yLJwZl96uS|LfpcvV^f;4kG}s z|9I*oNfPBPuRV7r+w%5nrg$1kh7HWRML7iEi+^M`rk4C`5yIL7A|T+ z6A&A%y2N$gxbL%pUufLvVN+c*YZ)zqqu|ce@y%N|o|V?ne(5B1th$gf|2N+BHEp=5 zY3s3cXZ;U+jU?;{4hH!JZB`X$qSgLYZrSN&Q&4j?JCZCyw95=&xzn}^v$hH9{wJ62i$(`_8+3-qu~bEoz~(9Z=yO! z^aoG1Wz%aoD~okCwv&)441~)C2JlyBY60!5p5;s{kEK=c$sfu?c?_x2kX2dkfp_G-cdJL?fQT$-VEW z{ZF2$A8Q?bhHKf#b5dWi*t*SwwLOrrYf{P3Z$S%F(bcy1Z8YOO?X3%TX0Y5stTTYMD zCtoYxSdLZq2oBo7TTCd6DL3&EG-C>@K7S!o!vfLJQqKAj_M}CdcGtk!=)1zl1)8qM zN+2nZ_1w7Fc}qCCRGv1fqrl#=(x{=<6MnT?fDoxz*YmXV_}|0~UaU36^cBrOy3?I5 zv7hPhZhigc&0&B-{!Hb$KIxK1ty(+vvagi0nX@S6!z;R&MP0x~2A8ny2+;(nKO}#)^`a z#Tx-r zQO2*6zXR${h>1(i&}p{Gqj?_>^N}SbKMPGzKZ(ktIrq#bWWF)^wI{} z^Lt%RTmf$%(sHVCXT0~EeZMSQYIfW?0r$uDZX0*L+Tanz!`uTO!Pc-- zw>J4yy9(UzQ|Rty+ZDBjzVWGUytxDkz}n!BkLdIaDFT?QZWCo*4EGKU=r)4k-OPAYZ8Sn~4`G$rK-m>M_ zqA!Z=88%0RTRa-ipkcQ0-WSV_D8@$*%GaIThooOmRdit4o(DUa;9Mc?qHA>?NlD01 zluz)Yubn1`A7filnst6R$Art|nTUt==vSsLs&klKR~X)IWVjuf(daJp>&4fA>#o8l zzos>Fx*nA03JXVJA2_=9Fpx_ONr=UtiEowf{-=ym*;ZEzPD{Q%bXG&j3CT zy*bw5rqqB8-~*bv7oXvY55DGhxJC;aC>}->o^~cBR>&?@D_&CyXg#;O!)^J;iLz#N09FUsijjL_|m_yDV{=F95gIEx?Egy#9vEEz5n^! zK5*=RDDBW>28)6E%TjH$^~;{z6ODQ;rXPGA_Zo=CoptA2d9?9w==YvUEobnfdjJt?)z>-<|BT$+V;_g>76AXX^Jtv&EBEyFcLQPNKKu^SbBfa(x9B^1s%(iOU866RJCLMK z%jSYi-U@Zqw{L+SP25$)%zp=jP% zjZ4b!h`5wr&+qKX5>CWZnv#j1<@>k4l|ANn_%Y|>RKy4 zy?Be(d5wIe_Q!b7sd;ZJisp!>NPLs8ZN5DCCBx?;$0=*yMrGAJ_(R(|4sS+U>OJ23 zF5n~K(#OdX;Eh#S*WPZk`OcSrB&4i>6KJ@UfhRtC5@~6@9Z9d~ywA-gW%gLDe4@R9 z%vP#au3$X(5^S%s+smVb11Rkf)h6ZZ4I^xJ7R)YE^Ek9pOIt3!fAb`y-yEi0yUsIx zxChrtZ;63X0cjGGS-N&mAoc`E+}{k26`70*2HX<6;{%ql3y#(+bdYs3^u6y_xG8AE z3yP4Y6{X#1I$d1K3qe+#=C{1NMqNEl8E0jWTuKygqDJvYk-C2MT6IFTTSASI{HM7o zJXtFW@rCW};9Wk5ikA9U=25jl1w@Uh1cZ z5N&}OzhX0<#E6ZOa`%8MQFzvQCVmoP*L<21C-r044}&9Y(N@6e+D+{V>>K4n=G!{Z5HORfyZutwZ-Ze46$|ZmaRM!&x=8Ky z{rd3&bE)D6LsbV}7-F9T7Mk_p<{j!mqR3{rKjWn+^xnrb0Hjc(IA?^hsQi1Pn1MBg zh=cRxywR4%3}hVb$}>iZ-sEOgUEs8^4Q2dnUeDrd5WjId8^nQePv3yb^CCsmFaqLe zUBCJHlmN@bUje$VQJ>jCiXhy%ehJC%0+~`KO&cb!GxOtg(lJ^Hya3=i`MyV&tt?w0 zEsop=9S-xPjQg&2yt#i<9YU{xKZ-&0JQ*B4Z8n!`b7WYW4~ccr-~|PtQm<)o_yF^- z_rSh%1g^ChQs}ma*iy3PUFpfcAPO&3dG4eL2ppZlS1c9j=}B<8oB5f%cX(4%zgVrj z&4{Ik(|cb6I}qsmJ8!@UTgD<>yCV)CnZ(nWu@iyJ5=@xG*YsJ1o)yt5&i28~{=Bh> zCYZK$753V2l_cVD-U_AT+l{9W(HUaowsYMqKQ(ODNgxtf$7AgFpRB;sU0(4P)A8W` zY@bcOA1YFi{a0!;5}L~>u_J?%2LFFuNVgc`c_9lrZurgmDTFNPYhhi!&`9u2!V18Xyw&%}l9flH! zR&FZqr4^-gRKIyV2RwDuOsNy=;mXeood^E}{$|69l3Y}@{SG$xCFZVIF$Z!0D^&4E z3y&a4sX6f&QdbF3y*i_mA;7!~dg#-#eDVZ?pAR)h4`!!iShQ60R0YFF~wU7uuLJ?qu;0_VWKU>;>t-Fx0vg6ZA9thkb1h zvlkYgJTurKVQUNwXt5H})Bx*}#_Pjang5A{ddQUPqZ^PesC@D)n_c6Bv@|rG{TM2< zl#8Fw6hMYN73+-Rl})E(*DTeqUS$Aj2~OOTe^SODktCCOPs^3I0+1DR)&ZGXt4id= zrR;%p&45SF!nVQ42RtjR>Q;Nt_q#h9WEdFcxwkQ{N;)t6@3Qdga8NJW<=S<`^f ze9q7r9Odm^4|v1Wuv5_o?GPmPijaD>5?f?`m_|rShs{3Yl(($^hkbO}KkyGE>ZC>s z*PT>d!I-b@qg|3@?ZQ?$uRY||sjKdk@$Eyt3Q`+td}U@?6$KxyDlE_#x3YaIf&7T&mKe2L;tR&p zpVvAb1&d?3%0E!UVJ|bvxH`Ie9+gk1Ia)*JL&jcDssF19`f5n_bl$3Gxe0bW6|?+- z-f!fW7OcDlagfS!Nn!cchTHG-o=1exmf#|X(~xc)cV~fsJiBKEDmwHN@shy#WW*eh{NqU?LAKS zr?g4A|MO2i2bgpxs7-l8@bHPI2Kk_uYoq=eR^hS@*i1v!hXWLaj7O4J_p z&yW1F@%^Io!w3?iX66@WjCzl=;4El5YNWx*E5* ziMo4Ir~RJqQ`nC9XfR72#(-3xBU^Xo#=E=)@kwg`s<(60 zg4$Cc1z-b7FK6Ic5DxxSwGOwmC55V}RYN|@Kk1A)+OaOH3gc+b!#SawCqXNSBML94*4C2Mg!a;dS_Ke-Xwjbhe!$J1dEY+J3LR|5HYucXy1wdJ z&ORXN{lo>9n?`X~kpuRkxfiAxSGfLNOC$c_{{kvixRJb+wc{zripxwy5~7IlpO(T7A=q*2zgW8}^6c?Y6K3S7(ZZd!P5|a zWZ=NPcH0CGdblU?#a*X(5ZTWGWyJMMsP+7CUpIg$`^Nld4OXZCSVQOTj-~0F0x7Sm z700AO4rR+jiIvyxdh;O;-f`W`2p2SF)=AH;^^TNI?bSHx`@}5XDcrjqlcNYC;5J-R zpvmwYTP@qUCFL=v>xo_{H-a>wBAGe-2tmxhJi&{Rs#Utl;?`T@C86o3_Mb7r#r4xD zrQYEj5*55ZuTBzdoj;iz)~DwaF@Svbf0eAKr`CP*B&3CqnW9Xddo-mebioNkHw7;G z=Q7E!Xk&U5wKnX-^*2k#-{vs^Sv~_MgG*MtT_}@`JZaiPvA75}@|v z5Et|S;u1J&X1)MXE%2nQjG^c+d_ImskUeY@vp79nix)jHw*{&F&;D#ebqQnMWvyH; zo*!x}DbZ6WmexpmMZ^V3SR;s|HulOd5ibJpt;E8&ETG#hq81U%weYq93BQ8I;I($O z!J%Wve41jmSP`M1$R^C4mB8!7-%Unxo9awn}lM3Pz_xJy*{SvyV*p~Gl(J&Xv zcms_vpDd+Nd*jjTzA{_1Vxi9Z<+3DDw!yM~s*N=9F(LI?i7CiWEG4IJ(feE#AEI)y zsE7b{ihx8~msx!QarG?U-n0E$gCOc}NE2Nm-Yx#dUmJE_eE6a;KA065rk?qd6uNR#L{pzhwNvhrGC489Zl9 zs{Wg3Ph?ZH3_us^)iaSAWj1}qf3h7xJ~NVlx1!SJ-W2-lZyljZDSUh1!y1Wbk1{44 zN%ar#iM(Gs`_9$7P3!Vvw&hz2FR&&_e%ZZ^7AUY~0Qy|N(JyUpl+joF&NPk4^hC1UJX5z!QS z1gV|A*b2&MT-5vGouto{P`)K(dE2NSCo!2SJoZJZCYKTLx$i~J(Ip)~BtuYxdWEGN zCiWcGLu%zLTUBDAwyx*hLb?*alq``8V+&jjBAftfRLIF^0q}sBq9j$ z{{rx;&{4GXfxeT<9{^Im6FSa8S3d>yu+=tT-M9{5)dS>Qgt`NbM<+y3iYn<(37!$6kl| zpw@8J1i%^=hJFfcu+e`rO3cY=CHHi-m0s})pCyvws0EV`zJ_;j@(~1#g~%Dd#`PYz z`J#cC%|7|Ub4S)4NnULUspt-*)+$;q9y-BZMi`0BT!h5Bcuv82t~_d^TN1q)88{)L z<}o|AbevE8zMc){AUGs1^~pH|$@dX<3GMhERQ27p0|%EEPcPUrc%0!UqSi3^=;LW>9a#P9i*eGjMh zR7%ZA0$Mkrkm6mpYEJYdcs6@$eg67w|A522#Qh6~errOqhbP}c!>DUz!0I%ot1X)* zB&MrNJP;KQm6FlAliv4v^K1EvH2@D%=Y-m0p}JiJ*lp%Axc>S6C+kod%4*9B-GHk( zW4?*EousEpqFtGJ*neHnBWmqSOl*AHe)|;OjeSahhknG>os#-sRpF~^> z6f`URgO&BqbgMkR+=*C;B|#83w<@BZ3VX-0f7W2&*O*QDMCR^~RZs1AAyNLC6EFOF zJ^BgVkNW-gCdYb4t@&O)>7!nWX6pjKEZ8z26$ssfvT zV^Nz!bw1}NOThpqOv!YG7r!2B*{oSBd5jc#x2gdUmpuiSD?gd&$m|Zf*CN92;N4vE zvgZYH!A=mVUE1Gm1}88fohPUH+kU*^`*BBpX)*YVe0yfvWqWW^R;c@h&Y`Db4hin2 zfDf|bvc3|Db;@$-`frbeO|zWhged!dg_5dT=lljR^=&|0H`}zJM$HcS{F9oncOfZYp8f@?c^P#=t5K>!V>+>o&EJJgK$AnH~XQ z*b6|`5`dT$rlQ8EO(0mj-<7KQ=9&9^dXDN7oll~|NNpdbGxxV@L(2GljYO!^4(QBL z*{3!J$5*(gK--bAgiejb(S>38$nnDi2qBU}W^`IrP)fQla7^cRAc&77N3G{0z5>3%5QPR)<_bXJvWR6@12W!Auok0UN%YcLxrp` ziRX@H%amUvmVDCevbn{do;4Ldn@h^2KC!J2l?TN2!{3S`YEGv6rQMD7iSqwqD#@~< zP{8zvcxQgIMi2+(IDbO*fx70BM5tj90x4uNE;`oETStS1nHVb$D&Bb3I4m*MN&d3S z#y2F?!KjI-%~k}Tnhl2SVTShdbDDX{(~jGO+H$cNLa!eCHc6xK8#Nn*$A@cNs(hJl zt9Y?@{L*G|WcV>=CE)-TMt1C>^GOOZ?2TD6_HeogPR z@x6(OXPAKbEm@57d0juxQ(o>Ht;ceL#3hoykJ-8y=s~wVc#-~dE%h%}6ezVrcS-j+ z!~yxgW8);hxnLz{NswAMudvq2lj!I;0d|}zNpg&-0hk^}d~t(6YWF?am^2sd0n60g zFesQO-8t6kjplHE?wKmKu(!q*r8pCUgCGg#@Zg^2fLFJ5wuP&3jG%v%`UlhPPWvNn zu9rcVAU{%Dc`s{b@*z)d#^(KT(B|}1b1Y*ATI@(HiMZPiGjw2vcQUVGt$e#MqI9&{ zzV8ToZe=Esa1Pq}4`WUy1s9ka;US%C4f5gUs&lyK0GlyXz7s(RaU%((CMeD74(AH6 zCl%MP?0x^>w*J2s&H^t!2W5n?I9-Ei+rA?s-^2%wEVBnJHwKMYoHBTWu(r2T%2X;T zSEcnf)(ThqgHDFZ>bMLp1h;!K3qcfer>y3ZNJ=BEWXSvcU=Ku-ax1v|VBk@r_z^h} zm8vOcpE>CK8~ziFyHK2BTPexh>BB3q5VOpMhBTMor@HSgn8HcAgQ}LH@2yd`k?8(q z^Re2)v(#EJAaTpwPcQocJNNB#g+igraQXe45GN48azT7Xku5hN4pr@jM3m+hacF}r z4_4wmE;}tiU`+kv!@yhg`3k|>Z6VRepOrSxi- z7G;5TtWz)(l+X5K(li6Z_P<@Dh{ zg(2lRz+>)yw})!u$WjF?c9nvYL%mbKKGL}OZS*1#oE3u>hTDIfCUn)GG^53|I4)*>0s~5&UKoJRp!s}Sp-g|+~`XE1VioaoVpOybK>w`&(SrTApS*c}yLSIKf4IPYd{{f{H#GDzV z7&?ki&Kmx69+KPxC%D@NXqX953Fk7#$f$Vg|7~Cd>J*($*!dOfOyt_%&zlz*28PQ6 zRX#4dMy4t(*?mQjoLfnb?&~h7jmj`%4E#;)9cC1FnUq_ENxMiqHUsv94*2Y$H8Z)(d zGp!2;Z&_U0Fu_cpbEljZh@kp%+Um#k#_WWc;bIWT4oWhqP;c_&m;fAM3TkGQ@oAFM z{)A><_&8rwDdX(}ULn|#2)L}c_n3iBlH)vc3`8r1raDmOiVA{Au3`Gqw^$uCgV*fD z^y9^ZWztnrQT}427y-(IRLr8Tgr$@#4H;2vlCSx^E z`&F%ny+%FZ$ebWs?Un&c+C_g*gkFi3fXy*cz;hQByY{|gBpTGv@;3=DD1&%wBwwa0 z+NRwFjs!W;!qP6lbr6%AcTM~acljO?5?;J9eT@56OA&Cue4}ep{`rfr5S>hhy3Qf? zAj@gS229}?{~mE@R)aPki2iWuQPyrN(S4sVFXS$--)UEP>v&k_}rF7Ih7cn%7W zKuGkVP?s|Cua0P;6dYT&Ys4Px0@hhw5;qv!-Z+2%tN+e z%KsZIR_J_u4ii$j?#uJ%pLc<5))PGp^#{t>La2|)-TIM|*3pB|*u zjx4#8Jwa7Mv3^Vh$#xPtYL|+qGoG_ICk-iY%8!xj?yl>S*$an;2FZL+A$7g`yqhyN zU@d(Dusgf*$BX>*;0h`jMY<|QVVQKSR|}XOea4T#c12@{E0c6(vD=sY2FiI(`l9J{ zJU%N^s|0fc-Av7e1-Fzs)lg8Dq?3)iN?Zg9h{^0*4-p*b1-7rx)8|ITe8}VOrd{T8 z*q~WwM4kN`%?M0C%p+G5@KTvza&AWPJ6J=~h&Dp)%zO*?X>`>!v@B2nA*NmF(U6Bf zKEd?CrRZUM8SsW1=RMyi1%Sp}%Ea$#Uu*|OX@D=F^rKXA?o|;C_O%QIT|-Ur$>yrc zn=y;P)Ld+rS#pj}N~BY3K%QjW6b*w8LC!P3Vs1TA;cFTZLFC8v$&9O-Q*g72?)L#{ z4WGb%LjlXx+U%2+HOa+9sHwHlG{LiUU`%{ zDEA7>-H8p1qQc9ngx1hsLCOr|>X5b47n6@s>1?yTm<&Vd%n#94@E7$KXHTK_r%$lrB@5PH>j+Jm3`%Pt}iH*>?4UI%h=M8 zGh!tvcI*QMG4WD2?8@4T?H#x!%nthZl9WU&-_-1^mE70A3T>rvYfI-}^ni5S<4B1# zU)!^C8W`+neGV8_JL@T}g*w)LKkxNx8Y~;2ZceGxV5cq5(5aOmgD)OSFP%o6*4hm% zo&Z-d50dwjp(=L$)iT}Mn}2#MVKN!IhiD_;JS=<^Y@zaB+psUsg@}%CkADu0u^qiZ zyY|Twy4ZFg#DZRxCE?f1pt6$Qsa9=@*An-~F8abyoM#)zf>KAepJJY8E}q{-hDomx@_%0(7vd{IR3bV$;G+z;3Lu>0iZ-)c*wIi zTvK-bT|L2^KIx4zPmL#cvX3yMS@I%jN|!D?48-#KjC_8Bc-$ z_vn~Bl*)ca4t`GF-&g^w`(e#XLWI5y-Z#L~Sc8L!S3u3b3H9a$U{zX!j8~#*%Vve? z;2UAH3%LG8;zQoEpGuqlZ0gbcNqBf@-6igDdE*jDq7JT7mc8=x>?bWq!thAY*}sL- ziJg-wR(G#dI=DUF354&v96JQnlwDc`D~t!?mtUa+UX4*JevLN_1N`am;e|@P)EEY+bILeU#1B&Kg*;g>Rx;i~ z1iK8L6Lxrva{!1d_3GBYj$*hbzk@m229I7`DRjjd&`Qu1qox=A-XpEa%RG>WzvE^! zQAf>M=L)&t%hfdk8TlHhG#}z(lF4{6odwaqayW>&w-6^56W16 zM?3arJO8QOAUCOx6lX#WBdcd+x4~Ur7Oq{4OqVL1{ommJx_g?=6~G92l#rB1*>J2UgXqm6&h1 znY-7z12!Ot72Q1LDGKrbfA;5@+Mfp;Zf$*=?-@oQk9)FPZk__;dcv9%@0?OqaDM#o z&%b-jDVFrz2W6yR2Q!=z~`Nr=1g^nL-dEReT+kXXV*wCoh6o>*Zc-{+wDp6 zqQpdk_hd}-SCH#IW|~*4>GV42njJRkU1B_V0f$Z}o?*fD2;7`9QS&xgYTKiq{A;Nn zyE>xz(>^7&ALiknQ2;U_b+O3q^ozsAye z24J{mZ-H|eau-s!OWBk0-&Nb75c&x()v6NXGr zt05u>^31XDYSAZ?b|o7#Ebtum8rGnK7 zj#*L8YL{ZX#y)oKzR=f|Rlym<#wa>JRt<`=zn5A?hWVd+DW1*tvXVdRHrH&QFBpnTqhZF$fWh&)DZUSHJ2m{!3|KL|t$bxLcfIhh6#$8cU@ zZ|1ZV`C8M&VOb~?j8%2^nqfQnRAa~6urA3{?gY~3vwm*RVs8Ih^O~kPH=L^&h^>#M zK-=e_j|M>q)``wl#K_~979LW;eE%FuZVH7s&4}X~ePzH6AM6ATEgiSv+)`ui7lKXT zDFGTNdk89wOv}xy#k3Gg+{jn**%<->9-xY6b>m< zI-`y}*M(P}i0OV^4W7i+!=)@DEM!;RNmHnt`KYXGu?5sBCyyQGLwsbwc73Le^kS)D zxf`Y!u&7~DKM|cnH4~eELYz}zE%5+yh8LXl>auP~3D@d9x_n{jEE+i(vqKCL>{m6^ zieIh<19o(xX_{aJ*#H3XgVguv>j`O0L7nBgi@Gzy>WQn0OL`5Q^ERTGI?%fy@$cY>LJ3+Q;}ik z(T%azxw9ZG7hLu!g($F#(7!GC1qs+A03U>Q1$uPzw>epSunTdB z*?|N8h^MPwi@bs^IJ;l)E!>1SaDti_fzCBwpSnS}r;iV0JSn&?GrO(?t&32O?P~at zIi2KTuUtZ{O2J-!3%@6L6w@@|m7|n~2e`(M1e@7B^j_Ub@@nQmxWn77v4lv;I&e50 z8Wl5rM2og_bthXf^M*7FiG^Q7MHlKCgkkjk+1?e4PRTRMJp@4@&COE(j_+1xzJN#H zOojaGO&h5SFMCD#r|1A!z92{x>XYY^AGGizD#fby_jyMEK9AS8`B5O@ETB+Bo^c zSgU|N+zLe2t+Q`e6KMzHu;eMDh4RRx^=DE=N5_J(z0XbNiTB5D9ITu5AcHGv^iF5Z zzbuLFg~ypuOWIvl)`rvCofB0(I`xo1PAQ=awTZ!7^qhY<`Jo_@DsF2hz&0wGOgBX= zeprR3-da6vj?{(@te_4n)T?CjA`Y<|m;HY({u11vpKmnXF@e_Cf$gyQET&a};4?_; z&!z0Lu(pd1QI=ZR-i@31jh=+peI5MRG6f%{Ht=k*Wq(<1NOGL(>^nU8gM5tyRg#go zDzGk=JFz;5_Ia|h(ezkEhq)u%#OaWJXu}0mr&P*cpOOI5y|*#w#!3d>o4wPA@B{vr z3H=^(Om6l_Z}@yPrAikJx#a<RXG(vjB$amquY6|Odo5)o zP8Xc4rHb|occKz|MZMBqd)p?&=(A(P z9J~}=hBDSHSoK&>p(B!F*moDy;XCs=<~p{W3icQ3^AK(n=-=Lck3Fmv401VaXP${; zz3wCI!&p`N>@N5D+P@reXxgoP9oHtNe0tgxe18Lokx|_~_f;EseSR?kprgY%&si4n zT$Qg6Y`Oa^mV^fucXm#Eo&DKJ5)V%y*q>1yN2q`uR*L6);Z(W%-&c6=AFX82P6c>4 zJ3PiBw^hJ%9W!rn%x;~4rv9w@sT=rKu)R9%Z~$L0j=P9j(8$np|JVUI-(}qA4g1s6 zLqdEpR2igpwRysC;U8LveO{jrJ>U=QAZi!tryk)W6Q8g?hibtLco0iqJE1z&;JCyO zh}I*Y)my$T_IGC1Kgolhnuro;<5~+sWzE{4uc(fuDGZ&-4wnE9Sonq=U{A^{ortz; zJvJ8zmeQ1&W}MKNA9|)R{KDOz{WmY5?!v&=JmitrAW4G^40yV@8wWd~EQ_{NSyf-V z#xPRLyvkB(U*SHZhT-c%-|RjNi0l|gD(ft{^)j8|E|m#Q@3kk$Z3TVZOQ9q?!vETm znp~qh#X!(sLPO<9+!99q?x5+_8dBd+&I(-4`LYNP>VmzE&sC|)0%^95>ShAiyiCf} z!wU5|_v7#e=&C+S9A)*mKRN`P%*F+7i_lg)sy6^ic;b1z7%ZqA99;( z-nSrIlxXDVTTBDz{?ZT?KQnExCa-FFbpmMH_|-D2W6hsr*O>sgh=S(2@#-+crz;jiFsR#P*h6a>uOjSWKsk+71GqedKhVF;- z=a{jDy^k(gcWlOnmALFVH)^H!miSG`SYm^ZXCl7Fnw*!fICcsAzQTi4PbVm`PT|h{ zUIj=$y~c^ep{}~1#2>7l(^dzRLimzeq5sXGP}k=RTND}S!Lkoe{+t(q1k#pRB}up> znu;TY9AW3lE(HmRZ-(Y(uER%QR==3Sn-sXAp}XtAcPV`E6=ILc92{}%inzk^7`~=6 z0jKaL3L^nUA$4^MKiL7`IO=nngu*h5Ja_8mNm2NJMmdoFOF<{pK(?G7kt zjx(jmZ5LLI|MQjk3m6*n6J$?#q+r(e8C~@!=8)mU-38zrObw?S*E+0fF8*@^EK5$3 zuL>v7t%6*?O$>3UI64S#ylu&oZI9FxK`!^G6;*YvpsQJUsRUaf3OX)!JkpA6`r(lp zykCgg^VymV0fm&zQcQ$pHgrKYXrRz+nvUPdlluHfO%1WAcb#!fo#8$f6$gr~RX~>12wD{xWyz|dVg!K-2M`c~fuOQM z*&`MWtupjPmMn471Cg*nrc#N40FJV^38Ks(dtPq)nhfm^q7cG8f3YTh{ z%Vp#O&VYApi`RK20uW|(Blm_>bone2UeG_*d8}zJe*j8y2q}Fumu7I7v-B87+}0pr z{A{bSO{?LY4~3F(=aM%}%o)F)wtVk1!Wy-)4M!nlk;tLaTV4SVc&#=5j~?6aAPlLj zQFwjqs1mGY4>?f+OH5OZ+Z*%?y2gNT@O9=v9HL!o#+&}z+eBEu>aYuxJ6sbKdgfiw z{uh`{*LML6P>r@3g4Co6UI9i``2I5Bx1V|DeweSqLW+(!C*gdEozqN8d(KEEW7!Wi zc?7LZe)i9%yok!nivM>ZDa?pK-Y`SY4fhx!0+z@EcfAmqx#F*t2>XV1vB^WW;vOG- zO4k?Qzi8_wWcl$Jq==_jhTBBFAFME+^Wmc%;Pq0_NpqnDBfC#u;oCpVJt&s23-$xR98P7n~t@ z$)C#^3AA?~mFjZ8unq@Fba3@>hi)fHIW8C^94>Wm|CXA|E6@*o%f zKzHDIF!YUOlm-*V@<}AT%i+KH%kORp*iG zhdEkG!QGV-B9L?S&HkcrVvJ6$9pvn@ zb3am71w5`Ku#T)@YJl~^4Pt!LtgHl(WHq+$3cr9(0x*iyc2jx`28rwxqhL%=M>2GV zhv0rylrZQ&n#x$F$y#bd`j@cu%qh%cf4LidD`d;nH71gUuZV^hkQNqY%X7Fxl50F& z%YH65_K@ES0EA9i%ZNg36`-!i`%$?7yq8X~EBWf(?1-@HL*Ec~_jr^EYH~)NhYZ#4 zpK|#A4mkX^)!q5wV|A1zTf)Hf>LV-yXo3fRFZtdKAO0I(y4f$l4x`JZ#EI7gf-c;{ z%o}C{Sg8S~gE%uvTScF5n}@OYFEo`&ix51z8+LfE1YC)8e*4m1V*A#WxoJ~O(ZeRG;BbW`+2v`lo!Tfq~2Nhasy`|*ZIK+UMaqNP*) z9VK>xYeUA9#h{kQ&mye`Q*~8UMt-I?~$T;3hUXC*>~Bk>q%+zZC#NE@D~+$^0d3Bpv|aR;}-K^6qSgZLSii z4lG7S<@M%9fPc43dWESO**8dfb)3kU!bklVE3}lj@M)6{fqWr$@bvy*F!EUG(@k31 z-mt%Rzvjx#dB?)lH1RzQcx28<&76;s{gzO?iLZA+<38-Ei)D%p){Cc>zjB27SQa;B z2&`+=x>%h%0UK20X6L4lkdF_@8oR z71$zZSXdmiv^mWuN(3wrIV)Dd7^(V+6aE;IF1yoxqRPQ`u>Qs{J9j3Qu}dA=eQ&c? zG7i9EsO&GO<2U8^G%&7X+km1h0eh zHWRG5(Tru_!71kb4`}N(Q#5QSp?u2`{%#($7WGFJKa{ zuqGVl)T&h;NW_o*qE6w(9{h`ZsP1mz5;52grSQC4Je>(F8EVT@I8UOGnKH+6!Pnp; zTc|MiwwehpkFn~*9Kii>ReCQ%mxrl68#PIe8T>|UB%kWVx{Qx$0*2u+Et^)bvqQwp z80Z(!Zp$D}>ST%JU17?q`bV9@m*m&!fP^NvM~P!UZIcm7Jo2C zmH*b~+rUg>P3E|#OK-wAWTa{P&Tu&CvN1zIU)dr_o3xT|8%+V^ROX4CUy;Gy@q5hT zcsPoP5<~M_^$51Rc;%CCNYyVU@B1A`F6y zII^`lohCv{&@7A{Gf^9335!G9tHZ`~gP2k(i7T7{NpJ2xgC#J7}?8ekjjk1p+B zm?BCTzg<`Bmy)hA^NiSUkE}sZDFrLA+`@p(LX(m3lVM_cBoI(A_zt#QqqAmDPz1V! zv{Dw$4rXTXUXokD)IF3AwEeom29D6TB*)D0ttXC)5-fj`_R>@3^it`+cOAU-6kG?I zRFz&}PPB|U2^fwo<2AjgN0X;7lajNBRQRNcu706-#iIJ37O@-`LT zBFSYsE3@rx!aAw$#A0EjUzDu8a+9*pUJ+sqxB9})A{`R>RexFV?bbes`(5S?t1yq~|U~e z=qaf>3yUZz4sb?+c5S;0I-*@j<~4>ssx$vnFU?rj&tf5{WeN3$oT>SMd;2 zo~Vd=h38UgFN`P{yW`m^rTq)HiIMWqP?@yO#QT9t3!PYq1wP*&_fOgH;o_K-=HuQM zI=@KVgK?tC^cS3eU+@jCB1H-&q26g%NSzoKyKrRNX9z(NhO?q&c-94mY5Y{Q@`t)x z&!Ne)A3R#?-$>b=ueLJTB8K-WgIa>>TOsCVe(^+eGoj$}g>__auC-^S1}A{%ZH2X8+Ide4*LR!^W2pN?(7ryYw^xhy#NMknmtV-6!K37`k!WM_xx;99?R4jh- zGnCDG4(VNY&#F@9vK@WgC9^d$^hn+PP=fYON^89x3V4K>X&SZ&l05gYmnTiOc;Is; z2OFDcU-f|Z23MQWQa3Gkl6TU~n^~u_xe$>Ca&$T}vqDn-s<8MTI=UO~yM)=}{jNhD z9TFE2@B~H~m6bJr63#*!Kla?_SLvKo7R13m^mHgxs=_# zuC<+fy~)}$>=*}_<~8Eq>7!4ih+CfH+eqX?j!s+XJApB2B0Xz*zD|njAHxu?vz3J| zxkeLB2#4>MH*A8jk=Ccu>2UI(k6OWuJ@NF!H`b%16){#l}pXhbj--Q%2U8J2bN)vjM!rd5U=qQM@-LhMk8TTS6X^h)Lf5da{j=hwG5tR?a*uo3B;k?@u9 z)nsUrE;Pwj1)_>ku-uBl*SX6-PFmIDC>7}6w0gIW6-4d+r46k-$T_N5ZiB3p!z+gP zD;9A58Z|lt8B(~e*C+)$Msh;L6gJor3&#(DEKw4vIvo;g9QlV? zj<0-+`UT*{_n#Hgh-*hV_I}Gd&L4;H?wE&ha0DukVR<0P)HHGTJkporoQV(XHz5b7 z+PH}YhIjey=<+!<-mT33vH>0-?~D`H6W6pjD>)-`A==?@w;%LT!NrXUwJBc%S3pIe z4Fyv_yuUoseY~5ooJ7n_QuvZw)v=Kr8M8|o#?Vl4p$QAIM!7|gvpZ-apI+mlR;5e_ zwT~sjY@)cSa4V@VZSYd)kFaRoKh=#tY@XWg_{Ieh~#X8n?*Av32<}Z zv~^hm)fx>|Di4YLN-Zf(-T=sHf6e;zT#$hkkRm|N5U9oXiOBV9W}{I!Zk@Kd`+bRD zHs+Yj2fACHsnC-&k;dP$G9>LgA*yZ918Fa**Rg@bg=k_8f*ds~dR+U+=N{;GJ`~T> z?V8+Mf-N@U+6&9=xV)K{RPz!Kq@sG)h=iA&w_}Y+28sMhblDft*Z4!|%qQrySoCf+yN@2PY$Mc?S1pokcG0f{$5A-MnrN4+0oxtUe~@PanKn6e{P>e!x04gbbFYSy&>B-=G%_y5NBLb@B; z_Xm?vh*fmibZT?}1lY4xJY~4Zc+5t%-F7$$! z6EWBVU!TN;%8XdU);8WBX!qbQxSay z&dx=Z57ET(z>=sqrkK29Tj0_ZVY`KEc8B(TUQ}$Psn89Xc}n+0_hosv{CZr2 zgP{G&~5#2x{LHhuOm7g&__Ye>Or{h3KZo1uTdQO|nDcb}A zxvM3QC{#kf?M38JR^PvE`hQ@0-)k|8P6@T*af>5%9YS!%rtqupm%r0^8akg)h!n2BF$Ub?z6? z+|+fQzl(}>b4x&4Q~SLw6V#&hK~yaimv=JcI3cZD>m}x(tXhS%T;;o-JXvVxC%Q_a2({=_;qGor@FA>)|ME)`njizqW z^qAQqgI{XK?)aJ+iyv!aH8m=Z_xYMj+0tSjX|=dGAf4!x!3P_f*K>^Rt&xL? zB-YJ4-u3VnJP>Q$)55jL7nD+ddv6p=?0iT6CT%mmBFNO?YtwRz*2tu(Fcj=)@oX@u z`X|mXabcM#5!=7VS=97>^X~hCDYh=UY>%$JH7!1UN$dLo9IinVX$Ngo)a4mQfe3Oc zALwX@sL8FqNn#1Ift-M?ja0h0$!KS_h}hw{5hrOt_Ec;b;*lH^`4@0hM@@`Ey<`wI zNFHwt;F3Vjro!s_6AS<$n@Jh(0?7-%iWMF+ILc`%sHz$}T@vX0Z2;sd{WaRumJNfS ztS#;bP2H>wL${EbMS(Px>h2-4?03Tzt`y*{DQUbB#$ULlxU-s^N@kW+6zUWVk zIbORRK79|2;#oS`2#U+YizAL*c9yDxka;w!x1VDPv?Itwh6SMALw~X;``F?Kilw)X zd5LLgs)tb}MQt{COiYuLRPe*}vrP+~QM<7sek`%cC&m*T`|=x{w<7 zTx^#`doF#B?`#m*w_J5~ZXHj1t}1=C^2a*USlIIs^zuDPbcqw&$lrolr;)l{9oZfo z0zT0kbZ#`e5!pcQj({G6I%V<4oz}ZsWpjV+Y-8}}6|w+~5-Z`lNdxBnRH1#@ti`@> zuM)OG+ynw4jGMAd%pk$wVbNNjo7*OB19gs@YjVHu8`;R7d{CWwI^@ zELHzl;-FD_!0u-*Jpz~INM}v1WR(ghQZe`dHLbNhH!EuNU5J#DXx zBX*9-chslv)x<`Qkor)FYd3-r9xCky3&4{;z*E#+eVcxRZTofIRFV2S+eSfmnIcCT zt<*H?=Hx84AEK6ec`{>-f^O}peS(*n@Imyx*RyC@UAqeg8T|5!gCNI$vV&5M(VRbywg^qtga9m0bNUYH}2uu=^DTW2P7ga+w5aDWXFiBW)OyRZZ*2ZCoSqSu2(IzYgb8;>rK(Y~095k-z~Y;#%b=cyzxywrdblokFIc8poetLs<8IYDN)Bdl zZ_sRM6kVo7_O`0slxuH*RZld!&Yn>hQ>!{_=HX4EAAgprQ(f#jyl*P-z#@>Tb`SVBe~j&BMr_#&iBwQq2LYPFG#vQb<)94+u{1~Q3%VP4jpX=ErygBR6HszK;;7RBv zrEx3N#FoMW>6#32mrZJLGuuh(Ly*aiap};}kApIXE>_n!)=GivNszfKi^!4UQitw& zwDe?SU1$2grLafEU9*K{2aMkNriiKQE4Rz%rDP2ajYwgJFOn4`b<$0!87*{KPxe%L z>)tz(F=NX*le43pyoA9?)$Tplu$p#JMwqtIR*o^=ib(oi-s6=8I1KyS*Qzqf#;KgA zqu){qX?RGqR%U#YzA|%f@@9j$r4jjv{?u_<4Yeu#XmzVFhx`1qx^jm5jNTZlaTjeH zEjIYYMvUDap0_E}t|+a|bu~^djJAstKd3gfe9iNR=in@^Z8u1C4oss+br~C^jL(AO zv`M+1ZSIoH@cmFTdhk~HUzbf8`g1;L0X~c3tA1wo-F(ZU1%2R>Ol{{P52S!>V{X*3 z)rw+en%~`?%2<6F+qdP~V&s4TmfF6zJ1!BpnP7Iwq_iKG&nR#E8D&K8BnRec}uq*t_sP<3S+W-Ec&d`*$jx~U{@X#fy*`Ej9DBSf5oUhR_ z6T8VCE`;k4#R2n<8GYR8QbQa0PrHv z^Z;)|AiVuQ_Df~=W6`iIef1XOc$!vE%rI!N417$}@o}i%ysNsj@jc$B=1(yWf2pI_h03gxUQa1tsF!oz8H$E=*#YH210s8`V zR?$@ffSP2YYdak5&+PVEM!EnH%nJbUC;<3}{S|y00Q|)OVAmD^Q&MWtup#t^~ zxX*Mn)v>SGFO*s<3;@O;+UhFCfeU}ygX)FmO_or<_&6mFE~A61aHkct1m7A9ewpa4 zC#Oss<;RWdVrP`0AKCRlyGCw*28qf%Bd-PQf4^AUm~Q_z`dqYiQD_avgJba33|v_? zQ8iMhEX_FbdEiN&Pi);)vM;BkgvWaOy9=ky)^{zPPoKOCvT<_gjq@G5*kAd>^;b>5 zIx1zV=PfJY`jH%tge}e-Fb3oxo?icY9@+z9i-%0TGAHfMI<5AO4XXo~% zO+*vqMIqC#W*om~7NUw!gSf98;IdNuzzL}c(as=_DvRZREGgU{aH1xF8LrGgYUU4Q z!iHCYsbc!!h%&XnBb7FAdTl;8M#eh`UZNZtfe8xc5Ym z5M=6d!an&N=XIvD#B?Df9yW=Szsh*W@U(YN>e%MoP;90o>q~3tX^Ea<;SRNykxJgj+B-Qxn*d?lqfc)h3&9gn>A zxo>hJe2-*51{(aVu<}TL(PBXfNH3Bx^zeM>6f+$|IT7wK9I`MnGCP0KSBq6^_s8uq z&%_v|VLn8|p>gE)af11{%94=fH%?jSZxa6=;{#@#=uZwPI+0Mk(7POZH)VT*Zgf-^ zpehKO%S(VT;ymrmTF;brTu?may2JiEH6Hb`6QK&UylPCF26smMMdUPYzyIZ^Voxd8L81A9|Y*b>-aMuU$ zGxPyMsg849jx|3}Su%63$UE+Q4(Kr~8Ft*}SxblARh}5;Bs|g54H2ycTzWNABMK&y z6$?Neij)vckW8ST`@RG7iKD>oVb8fVJc*w_|;Y1fQFDE}m`uTdf ziq=QCcrFO?Nyo*(c^dagG&x{~`)@wI`pd&wf@x88x_E8+4X((~BlOFSGHu)=wkij4 zgXo!D=qz>*t+9N9s-p!S!u+EOUQImB9&2@x11_qh?Cf!N)(ctB7A)r2eJt-y*oME_ z;KdxiRylTNtJy*9p)rmhpm1`DLwzdCu`SM>05~2TBTWeZ*5Tkg<~NC33HlZF7HA|a zOuD8M$3yI|22fe97Wt}8FrLqbscMm*fayuehn!G&$61WWqGT1_Ls@_EoL*Dm-&E*Rv%Y#6xaWRpPo2Lm+<|W>mUR zk2wCgfAb$(mr1Rb93XN4ckoVQ8lQVi1&ZdnB1pWZdjiNJH4rkC68{Q#n$Z|iFTTww zrMTZS{AV*oj2}mL9gl4g%s}7SDw^^~s+9GiY>g%M+A@6e2gh0TloaJb3}Ng1W#^E56oiJX1!G#6`A$PDMaQql{&>K(x+ zQoV|7X^S1KhO@vb^v4>U|KP8SQt@JfC;(UP%Q{3dXqg4m!GQT(ET@|EGl#g2(ksYu zLFVV9=Ls1Q_7*(t3A?7SUPJk9f@&}L6Pg5l^rLsz`riQlitoCr4D$ut8T*SLJ5Q_^ zsh{=2Bpk!!2qqMy{MS2U;=#$7-*A1sjP6rDNw%$I#Pl8zD3@+h2FR}Ul_-N%M6XIp`3_ZQ9t#3vmi;1Z%mN&Axw_7F4hBLWL9?rDCkF}`QaXvH3iF#; z=1t*EKqp9{bDg64t36%hay++k%sy^&!S$W#qj+X`i)kn)I!I{JFvjdKuao0s- zq~^|OfhKSPyZf@6dA_$B2V5QtS9ySShv#scSUxKb=uvormLO`vfciV~KqS*EPtqX>&JAE>PNKe7xo%Hi0yQqQ2}2-r8UX(A1M0)AD3)vDVaaa^Z{Ei< z0!~HKN!B~5PN@*FCRTvXN69&efET0eS>}9YR%}^zzS>O~4Od97Fb_YR%KU zQrsf2@oVYhQH0e9GvJg7F_>Ku;zM|)31RkFPCD~h;W==O!+PgWW@kA7?}=0<#5Nyb z!XeT@^Koq2WLFt~Sxpgy@RJi`>DfSb2!Nh#CW?nw_=#S$Pef=eQki%z4`zVrdVreJEo&aCV z;NvD{_)dWGFoD>})UnbNameCcqyyyQhUcW5d5WEW6TFG9b_yq9DaS_7Y5U|w)LQuJdJ0B65hV!$RpYj?1UC4) z^rUn+t-}!OcK?g2v`WZer1PJCIxTPqaRGwS{W@4QP}&;ur#<1zylzmv)F4GDtZ{o{ zTsRS8+QUi;PSrKz1O2Q%w8g{~+G(ePhqOrNk93F%1+{62!5F9-ZOAfqdw;}(t_Y0$ zut$BYIOaKB0F^>#C(d;;Vss{z1TWFXc^x+AQp*fbfpq4N$ZlqzEatsVMCr7fNE0X! zU&|mA=Ojec6w}^^+l77lEG~qHP=~MZ-+wiqrXmqp>}6mhd4W+`x4&JyJ2mrV z)sH_9hRIK=q&K~B5IS%+$QYGnTPDg=)}8oQMew$H<86pe%i~l33NErclXTEjhd?gg z67Fvme8}xxRVi2nJ#pOY+(rW%Zr>Wsu(XF0i%PD@6zo?Nbgcp~EQjP>X9WwNJY=D+ z+5q>A{&}()wu%(h>&Bta24Nh}(05}L%79C09eIbabKWB zK%!f2sV7Fvz(mE4F^mnGv`0s>%|LMAKzZtQ6=)pKI-vzklFl?ti_0$)_1}9R9WD_+ zMF;bXi-u#-z7jd00P^7M#oR+&P}O2aUr91g)7Q5H58eyY3gIc!-ofGxA+;arjoAQ> z=@L-0Pi`EEsQ{6*8xn-b;rDHjSi-1R?xfWjFz5c{2R%_1q%a}tmU9bV1Ib^!(L!T` zM=Vpw82}aiJyjqjEv2~O;HH&kuHOovyKBGx%Ue}IBS4G$ z>W#}yCqeUQ1*`|UsVP7Z+;HE7A^$%!F0X2*+_rI&OgC7SIM*1V(%!rdv z<?W)tzZUh+(1vTnJ2B=$xXq;y+P^ROlK1PqrR%AgY8i^-rS|?k&o$}Fi(rS&}%kb%Miw$Zp%Brx!f5p3d=vQ-l0L)eFd zZFHfJQm+}YEwnf8OP*uVgQ*!%2i`zM4g>Eth(EoviI+iIM(8;Uz~Bv*q^SPH zg26fvuJF-kX#aj+;&?bGpRF(<5NS7cQuqWN^9YuZ11aqJEQsY?YDkn()G%qlx8V$| z>%lg2CF4P*H<|N9@SLw|jJ&ZPgAlR&-!_!THrLE>u15}7HJ*M+wew_h;P8us5=#Gv zjW^Z;NPtm9tQ==95pT&ARc!bL9x3v5vL_X@;kTDlhg*C!X%1+dU*KsduF)ykf~ydKR~CQs%Xe zR|ddgr}p`U*`0(eG@*S7X{H@n6hjzB;>T-pb=NV(Tkn|xE39ese)Ay`9Y*o<<6kOe zT2lJgL=98$rzp+&SR^=H!PCp4nkbvW^M{366>gS7Y8m(q|i-=neqbz68SEw*8EJ&r8rE#`nr7m zKoE@Rt9r5U$1QtxM7nI!`7Z^%+!~6TYaQU`OZyxBn#w8Y{?n6R64vD-wFHhcx*tCt zZ)8xS^)ffGsuR#&Xv72a;@5ix$ZE_%03>ZrG|6M;i4D}4y0SlQ^5dYpc((aCa7``J znn6e07a_S9^7av4TJ`ExW*501@F&s?pQ7tBH&V-pU&y=*?3B|M$I=Sc1<4CrXX|*6 z)L!+=rF@R2V_3MDH+IiXowTQajt~kaMTEL`?xjagT)mcJbQ!X(SG{oky6yL&^Hb~J zWuhai@Pp^jZ$Ut-dyS6R|69ekL@c@lm^8ywx|O>j4Dt88l9VSe!(dH=-o1L52G{47dWQy0chV|#Z>9FkBBgDMPaAxuXF_B9tjusq{?PCjJP2Y4^mhw1I}+4#6lxvXXpV1A84A*QBU2jF{Z%@X)K`Jw^>Vhmr)DEuxGE+CmkQynE`8uYw2eQV-wws?rydD=ls_ zG!rY3DA2I!6DdI4>0W&5l0>cs%9lP^vKb%y5!SgO29A4pNWy%wV?C zlPV%_9(1zl$=JF_m5C=cvAO}cd_DWvAm{dR-`f45y8$`0?8P|(w zGDOn2^*l*8*78=_1Dfsq-`VL*oRN!m8)^rc9^j#Ql^=IohRQQOD|sGkO;_4V7F6ZG z6df@>-F)BZRdFZ5Z_+lwV4#uUFGk7iQYXWCb*q3(;SoMdDD9;IHR)Hby#o6u=(qP3 zo{zA7`ICa(HD0heAF4lA1=~K>|YxwePL8qwFa zX&9+$umbR#O%b9G6eKNc0U_yt6^FroqB*2J^^LGQ#SDt z+a-&oiYoDno>j7wPCDi3$PainCsB`j>}>5FUsAck*XL zBNw+-2X8QtkHZ@G3>l8hV~62iiyT&v&REJiy^Ky$L8oIq903;gD;4-Db_+wl-U=}j zfsFhAb#DUPDvPm;=W1t0?RW?=777E_a0ahCADGd@1eut-NDgEOe3|m{g7G;S^Fo4V zGK$Rur7y8*BO_2nMF{$8ULf`;$mfnwPSAzBe$ewYRlHLlVAFJt+8^lkxWH$ursryO z+dQL!B&9?JvDw9~v?H=0nB3~8$cCrwZuWiG{amo*3DYip{!JS`z;qRdiTKAk5UcAR zGp^c>s2b@k|5O06Z3=;=SL;FPon)@dlB1(G-U)O`o~T9y5Ve^h^%z#&l-$j_k4qlJ zqaMehvV<{4sxM$hEMXNbE;j{@SDx8j+%h}Dt#r@N4d=&kPuy?RT9)dL=J6M*JW3SY zn)k|jrX%E4(v=%`p<#;KTPn-KcBs&Ur8nwm&UWbXzhnYy2r7g$7Bze+j*$nYJUfE* z{AmL+A!6?FrGp^bzrMg~OZiT_ng>kfQtn6jsk7{RnL_i-CkmnK2c0+yyoY4m#=N<) z8#cYyVJ%EQ&C$#~$D;ZJ<-7o~qy+Bpy<~=HJ*7-Yb(F*7ksJSpewOF{f7HYXI-^Q)^`gNfkb0(*0`BWQw~8_Y=jqY<4vYmsI9ogq7jmJ<(Q zL5kRJ>d1xI%D!8IN31>)7fQhomOP|G=p@^tww|h=5pfl=!<4GWFwL2;AS3H8L4dJx z94?W;=qT0(93UdBHwWnN9R(_03oeGvZ^K+k22x4}6B!tVdRXQvlG zAgAB7fHun}je|NN*AQD8ZbR)x4Y=<7Uw@H5Ef{pL*PGHH4uf{bsV^&8ZtJ!*l3z5E=?51D`YVayd74* zU&9+4-3^g-tb`%y|k8_4`58kj)(Yr%0Kh$=#MTSbbga0k`KM) zRi#<7p_+q;{Pcgb)*N-3axpVWT&LPV3b%{uD#zzMx%VhU4v6mA+?F@1v8(y|K^=L9 z7>9VW{~4|uKQ3?>VWR?~_n=Kx&P>mByQZ>vqQILne2+GNM~Z*^Re9xC5V!8we2`TA z5XliyKo#hF`Rnu<`sTv!YXkw%Qz6V!t|*>}Blwka3gl49^pMO*F1jeL_o?%I9_Tk- zn^j7u4n7$(Y=ordI>8_Jc0K0+wg4)TjSd%QZe4<9zJEZaOV3;PV!9eftgR`$?j4fh zgmXvZeILta9X&oseuRzg2}jiR;ZZ`~50a0r#VQoG=9O0k@Fm=T zj}c^U0@^U@MN8ph$=vI!gGaY{hdQVK0+AU1B&MTK-}(g+GasB7!EiJ!J_qQB(onR> zew-!wT}wH6lnyM(@C2ZD!n4^a;RTlo862Ovo&xwO-yA%ubnwc6ry2+j-+*_QR@Kn2 z#hHPES}F(78#cJi9)}!gEd^F{rA%Qh1+vX9u5Dn3N8)<(T>Rot`EnH+?p){L_%>(p z%x<;$A()oe!s8R79igvd&M6BTR{FV)&-!3rcL=vfND&xwu}ysNxT|?GUqT6}imr zm(xBzr)qs4EdrAkXwMo7Aq^5$TgtldW8EzH1Eg_ZdXS3LwcdXpUZ()4F~lL%5Uuzv zAkFIQwYh1PW6j=&ASNe|9rIA%s0V~jg?tn-ymHq&8gsw;zNEF{=8Le)o?U*5II(Jz z}z!9V!*m);HdPSw2>Rt*lM#DDQSJ z@1IMm*BMSe#mym!M;`?v=~TjvKinGVr}d~bNE z%sx(;A|gWJ#cC8Y5lyY`!%fw@(flZ-my2N~Os(+xKGRm1Ovm`z6{$9_R@$1xYwj51 z$gfjctM2KDU0$UNf2Cff@Uz*eC!+>5KZ_e#es;0KnTgUF&I3q69dxG$#Cx)@Ehpl` z=^vnNb3hDEF+P?*)Difsz%=^-PRvEkP8(TY#IY*>`q8}=vNAhVVSiR59c}1e?~RMz zixncSj^rFF?VW&zxo_t78RCJwhlDTm(*na+H9S>)E)SH>@w6(DloOQ#@c4H!?xZsG zoQw$3NHobnf$>+Eh5<==oVwbZm8F^0|0Uq8+N=5)T|YY-Q8{qTy|`I6df2%af8qUbCrp2k<`c*DrB3JDg0Rc86m^#D?0rK9qD?CC z#3{ou5QcCiDbf2f2ho%Wd4?*vI8=4_y}VUG!C*d!1$-ooKK1j;(`D`*75U!U1fU!)E( z!tJJIu|nrG=xU0Gq$g=Qpx~KCNY;huFLstkJEwwBQ)i@8Q3auW0mtW}7+f#8K!dF5 zJNX+_AY&-}y_PibJ2Fa@4w^t6W}mmsovhDMIe^#*qbZ)Pwz+$OpCKFcm5x?gZb8tF z%<-_zdGMid?^QNc;jj=GdbGmex7hB2QHE1N5!dL`{Rt4}-pp z=}hKCadQ)IE=5EZgkFxe-AsEz<-NUKoT9BT^U&Ir2@v0Q`zv%5K9djhSO#R<%(^@M z4ls8>FN{02->d2(;1=oPkyAQ3Y&sqj4S`(Ju7|(#1zWzMLqx$>;O~raOejx7jLwv} z%OVMbds+UM3UX}tgC2q7RP2uWKp=_L%8RN?(j|16Oi|^%ZGx2I7nMFqi3E;4D&LJM zOm9FDq6d82NlP!Vy5)pZ4zWc?bnq$DSjMMgmp$KfI2L1rx1bW@5B77-8TcNvGSR^) z8)dBe0#1AhLXyU{U_Mp~+`WyQD)))>CIK*wB=9;+7sb2`N1NEhJe2Xm9Irz&tZp{{ z$s|hV8rhm4iK_|Q2<*ag2dANMJJV`;AbKGeuzaPXsC08Ib5t2)L$+Z3IT!w%1^(<= zKX#odAJ(7Qv}hyi5xRxT&~FmYx5Tl6*WlA24>)}@RsbQx<{lqn*Z4Ag<*g^8)nfv0 zIF+hU%`Yv^p@%<=4`L09q}^ghwy6VLj;BYb6oCjWesOhWg5Ma zSce*miZro4qxar4mPbSCh7h6H|tm&V>fv7c08DOBu-R8^rwN^E=3QaoM8 zyT!P44~c@fU_Ihd>j@EAAN`a)K_1&DZLDPqB+;&2y8u0@W9Rcegu49o>2Swu*!AHo z=Yr}nvnAvGW?4O-uM{BM9>Ww+6&|^xOdz;k&b|6J9id4wMEwS{;$?Cmy8}4)I#l7B zHv9;S)LERWQk|=ue9^JyQYswmg9kTS$ooiPWgTi7S=|95^4tFHx?#=&^sI2gyK(_< z0qwMt$D$!r2f^D7$a}Fi**H9X3z%o2vg}=iV1}goqw*@9c&8i_<)xK6hs3hC*UR-= zV6Xqc;(%EKJ**_nJzT3ov4*elRB43aDFn{Ow!2oIeKNJaM>QC2|4ak<0>O$joZ$`x z<=$orJ`EB}?28JEjCrM(>mL%+Tc0u2-Fe&u=II*s5hiS;+(MSCdE? zP%@am=D{*lGV=D)4IKP@c(a7El$O?(vQY=x0&|}&_|R_qj|!#Yru;>gHO(+?k9P`a znXG>8%2?%JRHb(ExI?=%Im*r(vv~sGf(0x z);%%G619QmaG*SK)_^AcUEH2Wwpa|=hk)v1lM<9EDM1vkt;X?|E~>OoNP}w3;A3YS zw}=*Fxi(Ygd$`S=NJV3y{q+U0WTTfDQNNf^@=o!04U0ZGMvHr)>tUVUk;zJL#w9{I zk*^)l{Tg|=67)$4Rk+d-v=bSJ6~%^*Vk5mnw_zStU^<#4=)eEUPwu#yZP_|DsjR5@ z;gh77mevY=+d`aoTe3KVW&8HS6km25Un@i;ubcV}>AdhMkm z4E@(;B`JY!`;+bf4O?q!8Ioc_`{5mJqS-F&0|U3U{fys?cPTybV!~LJ6fO*r8X>-= zH*|QpZejPh+$`6SD2_)5$mFyO7@n1IwmLw9(gPL zIWe9?eR_+R&r@vnD!k;+>ptJ|p!&AZg;yDHWrY16TSwhNZu=+kuWUDXjbwi#h> zb1zGs7@DW9->rR$6hXIO(fLg=2%|BV#471Vt|vBVh&W968M>MLWcl&^{)M zx;a|;$u1}}*M@ZhExmd^#BI!RS2WTzW1P9tx32S6-V(1bo}e{GVE&?8TnAxz0rzhl zVo`f8;+PN8{=c=9HB|KwgJ;lNnHYh>5xm_3$c(Sx|5Vs^eo(2lnfCWg-L7w%)#X44 zTA<0gLtVx-?ZL1O=UMW!ofNvtn8859g^z)~Vm&XNAE>~loVJ;4j(W~uI~%)>J0s34 z0FE%>aH)2d_EWT;s%~vVd#+OO^1YU|9X{2O0uSs!&?g{O9z>ampKqXlD;Xn2k&8N7 z(BI_dcgf&(GYVoh{(y1URw;L^Wbz;H0hm`_XOCN!gZ|}VNO;~a5i3r8qG6k)Wffym zauCC-iGh23#a#atKpk;Ht@p9wSvgivXbXLf@z|U5Olah-{6dJ9_ZR9zg@%>I-^4!;R-1mZa)HLZux!@KmrniNKY)NH+AwVVe#KYlFJX#zo8gKH zs7D$*;LcEJw@Et|`}lOQ+Dap=UW@12)eUcdlNbEJRgtCsIw2dgYn?^bhOzpMI!*ty zJAj=K>wBnN$J)@35TvAwi6r^Yko{JmN-yuhoi=QGcZBgX|-o*dx0|h>r@ge2o{v+z$zqm8E z<(tVIvz!KzQ}dBdQ44sA=Y*Ut(C$vVH$;xiaQ?CZ8NLt6$6TXrHvX0ra2pcPpnuLf zwT=38X(;irx8!pcUf(k3a~u>bLoDv>4?KLkW%|C<*G^hQXxehp?Uze_4!E@8 z`s#G!sQSl-BJ42dcL0hq=-Vr%Skx@r*1#XWHJI-Nm6z4~v0FsHetS75_FwXANtJ6( zD%>k$yZ}a>S@0tB;*yGQ=jmPWZtV>f_M~~zH2feE^XjVORMwSLcliOm2THPgne_g< zkBhNg)1cXsa+BWLE-{h{?5ylo?{oQ>eK!{VRB308l6a9$x;Vxw&&K4!+X|?v6D6CB zg}h1_iRTr^JFk$KuJ$aEDZTgH_Yc03$|hbX|4#Bb{cCa#9WiIV)!D?)wu{PVQf0Tj zYa>>!3}PVX4^mJ4!IYx4OjXFJjyQiw4H}m7&053|JJyW)Yagw zSTo!Hd?&Pzj&ETDi};j`y@V6dJT zk9}yu?z^IA(k?)pdv>3J9D6)ow@#3fO_g#LF%7CcY`R-*`ZwcV!4;(^dX*2={pA0B|uZs8I=G)h`kM92(pBy=9S(gNT!>6k?X|x)G3Q{vm@!i&Y zM3?Co=^VB$jx6x_4;E=6#Y(Z9B{LuRiPyO^$dcqh6;&MCwO-PBq-{g&ub12RSjQaY zF2aw*mk!stD)=5&hDHD2l0uy|i&6C5TbXxwn=nuK?ep(We~`v!f9u;sw0;r7%^$%w zt-H>*#iwxaGo(ZQtlfl035ROnFm9o~=CC3)*{)ysPk*d!pi1 zZzZj(pLDCGUW7@>?>Cxpd!$-?^!5N|sk<>s)YMJxa-bU?czhYyqGEuw{Fkr-Lx&}u zdGgCYA5$(2tBOnYu8aNh?#vS|a&rVEouA6h-@=FV7CUy8dqAReq^O%793Dtn5GlKK z-KXy(H|pYQu3tso8<80HQvmiso`F#C(71NUcV4iid3(jsCkt89#C!`v>^_Ee2_p94 z%fIztaJP=tdwEbFuJ)=5rK9ig$*BJQ?uIue@}p2T`%TG~6&2S+%-#J1)S>#fu0HpM zCRBRagM%bJ9RMq)k=WMSZO^d_Bz_{w6y#(7cCO*#SC}sUw3VeXuX=+yB#tX9#aQNy zh^4j_`61m_U=t=_)umab+guU8%~>!u6^gYdSY0tEBtu+JgJg^NY~_5w4uu3iyYd6a z8Z8{Gx4B6wi**~V9$2rp=$=%v-R(zoQO(O|)xJgRB|G9K!5)5YlElJM3~SScq-ZNi z5i6(73UZ*b72E=&jmED;v4kzrgc0{#zrTFXVa?#I2v1r@+J_Bt#3Ir?-s1tpwi&v)v)x=?^4r6X1)|-Q_Fnws`+BZo=AaLKu4zx2iG!P*OxDrz`q? z8gjD7F(QeV3HKR#9yjaRh>cr^g@~2HhUKkEyZs;+Axjo`s$W;O9Oz_YjsMl0swShu zKIy$dLRK@OyMNz~&Fe~3I=wNV40NXud-akTJ2CEiOQ6qNZu#(i>Ag@!Y60^g!Ha?a zt5`nKRwpTfLJ_3q3!W>W60-@`Q)XVJIbdEs`;vUpR-PqyV8K1QBO?p{J7a@Q<* zn_z-M0f@;L##eav)mOj*XY0WcLm{C@upHp%cx%J3Qb`o^x(Xr|-`*z5r@Sf2SNO2c znz{b1%~itk+UNc9PkAER$tb)8+=Fi!Gg5YVHZo_T#%>iw5YboFYZpi;dFv;A9P zKBAR|6jtQKR(;p1(@{;i*w=JaSnHt3x)w~7jbpa03mpovH{Yvlo4Dge99yjobDxy3wto3Vp0%M@yefPeWB z8Is1(Chj68rC;~!3(A9#pkN56a@5!QuIUPbVB+_0`v3G_uME0J5@An@VysH_T7O`D z)-4ZJ?Xh8Dc^IDyf0IpR5C|TU1EJTwRXe4UqZ-hw6ZH`RS6u(T2yXpql|ASg>f#4~ z`blo#`n`c0@--Ozz11VtAPA|Wot`jxtaKih_T}>?R%HJpxbNcX>}{ONMFsOOD!Fj% zT8#7%4Z>|oM#5nNaUy0x14p77-O}I2QvX;!@JU|3B6y;qWzynWbwIG40C#Cg@Dg^b z&XdAb0Ll9lwVfIX;MLKPFnp3v zONnDf3C?2DJ+~y>9nN~<&`-Cu#9yhxp~0OHEpKM|{qa1bV6oK=>BO_?{*F7-UrN?* z8;ZZmb6wl)5BnAu!7UbQZfGR+yk+Fq7u(T}et~2yt4)omqa@s^b|bvrmV5{&?!i}` zK7|&w5=65nq}4Kl*hgJg)oHde+BPn$l&<>M$JW|T&nFmU`o98S!0xAQ1 ziVTGC+a}z7hOPC$ZiP%o!Il~Z!K>rg{f~Q-<+rofLKPL{)7I}+(Lrpi!&PbRTJJvk zzh7HrzliR-wINLtf-L6g#C8ypzh2VP$OY+3g;>C3fTsN$PKk^v!O{(9Avxd-&skkg z)4GJc@A6$ei?t zWH0QLp#$~wDD0d{TJJwi>3T72hKG#Jhm(_%|K5BGt7E5=ob=JCvW)yuLT)xpMuIGd zn?2YIze5Pbu|_zB=7Oc+onZ8!Y)M6!bTVw9#XkT~lkzu8Ji0(<{()!^n3^(zX+gNs ziR3u@)coh~aSfMy0ZaET{I!?bDf)0gT-u4ke$Nt>X`{UMyySS4kOG2um1Ob;jstE<;p9JTDsdd4=Up zc<=v-JK?Yub~>9696+0DqCF0xxs~ZMe^2hLidt!S$m+&9n;CcjtkAJZn;=8{>SIy= z2!G_1A+hO)0kuQ^AxWl^CwqeL1TPiV@u>fp#=;{Wk?#m}k9x%Dt-gv>V~*SX9^Gj3 zbCtb+b_H!$VYYiY+1MdC$GY3lC#wll-OL~URv3M=29IO6tH zxLcM(#2(&ZG2m04F;lB4d_|fgu;B3_tMbDus#M=X8SowAN{>647@GN0kq%D~Oe}wY z`C_d+Ch4SY<1yRn%p!~4mB<&|i*VN)8&T21t^Jv)!LwM#+~^{ecNgP2#-21Ty(~C+ zkJXKNpejG(qLDGD8hs^qbpr>BOI)6pn1uz3^?Mw0D0+VHs@Mw|rgnTE&_V9>0R11* zUSaKwv|4X2yx}glHrQJpZ*R}*>uc-gU4yYgAIQxHY+6;>sP>u(aRBHiZG9K8#E4U` zFkKJX+}wY2PP%$x+oz*+QB{gL2vw3Zh?fT)aTxNeeEKQoNF@j6yKOh*D?*6i<~j@mdX95rqRmZlo`6*!|MHKkLzaW zRWMTc2cy$N^};WKqJPp-?#0j%jQQ`BXIoR?o8(?}Q5DMnSsyEG)6$s1Ct}fA7j5^` z6xdfP?bdAfO)k?a1-7H2yLBoRAz(R)^OyG>NoJObluN`#NXxLk?A9nI>bm&@yzJm$ zl@loy9uN;GNPP7rb$g6q-E_wA)n)jyTFjrR5`NY;XVDkE3kt934lCJkCm4_SrwREL z`5hv6M+*ZcY--I(CW%xZk&r8>iu?^|iw!o@0(afxFbq5#`oX>4zw5Q|i^x{++w}iY z{PxU*^7u;`k*XEIVT^9&^VxsFm8O|AK!4=K;-HShK|({IrrzO<3Zd}vUN<7e%Lr^s zy6)L~G?Alm_JBkOqfQa05`%0>;bf(R_45S&#*`F)gT2m0$i3eUI+}K#*e0OQG4tQm zyYqkc^RmMUyUM?`?JsIimNL5vI~o`nbpc!E`LKbj#XHRSm8*DiTpa& zAqK$c67SzLTN7hXs3J`goN8xFtk)#WNR}6%oKDa?`_XrdKzqJk9`0N5YFo!H^72UX zj|1-@{QsfMfVZ0DYY@29qUq&5FP{4{nhFb;#e3WM(KUj_dH6VOg(48`33r~9FuT_} z1&QqmcA$T%2s*onQhdS_)@fbCb^$|E90CIPH>I5_F9O-B*m2Bv*N&~9ba#j^49oDP zsIC3>=bP_LVTF=LojvQz>svMG!tQ@pz6|dIk4mf_BWaNW$UghTy=CTPU&3-la=TS# zt)U&F0yU#PH|vabfxx;8#bqk61Uc_SzLHX?2~xTw_$fQhPi=k~{+`A^ZPA)(?0OnGfGnbY9>e?%YUm z$Ao~3IC6EeJpTu3EjisBy2%yw7BIh__dOBq2ZK^y{1KqxlQ-@6OXR zhv@Ej3MwEj+b_e-DOx@bd2a9rQPXei!fxQ|rPlml-3+|Od?rOc_P;m=l`o|1{@=FC zqRhXFq#;zy-Kt}zVNW;e8#-?rM2~|B{Vgfjf5BqqLZozki8uF>Ah|%XXL`Cpt0R)L z2%jKoD?D*2VZuKtQ^!@C@S$49FX;OzQHT12=U1$_nj+ULKiu9VBv-vSHf|h~oF_D! zk_x1)CkNex67b-Y#T+80omKVV%sw05qHuw}O6;)eHvM1BN7WHpxrHAOHEOPwHISy= z&*6rLba?1+OGlfK2aMc6+{Zlm`<=l_{Xb05yQe`f>xhrrV3|+l?Z3o={@r1CImJ7h zwyC%p##yHO3ZH(#5ZaY~nB}a0v!yvRmbPXPk>qPR`d%SRI(YE4zp#L^&gKsX`KJL>gZzIyV#~cL2|dSm=Y;U{*X=u(fNydKksw(oxMxlh4CHEWpk z1zB_1TeDhDrhKK0)%;n{xb^0JlE@YViRetF$qOV6@Ji<%C@ZVmk9|l>S#DZwCE+XW zM)^F<&A8E2yeq(M{Y$j<`1kau@<4At0-ZA-W;s=n?o7AC?IB(?xjvk#A7Ylz5tqYY za%Nf+ zH!w>?<2Ik!w%3oP2IaqL$h$x351|}z{8o{*noaqo*Oa{t`GCoL1d+X-vcDQvs@L11 z$K?PQv$Z1s^&$mlM96;Fs=TjmhS zy`bcAqra{;j&%(&!6cj!UAkcu{grNq^P8^nY0TRMT7BM_8DDR@E;MOk+{LH&`giaO zLk0g&X1%aRTXO@4LH^}9J-;+=B4B!{uygTv{?T&1MGkh_R=@vnA5)$K78OVxO1ZflnADKhASv zftTTn{Wn?~f*4#Zh&}s3bM6wF%9*R`N9RX!8bL9@7Q6vV-b7HaIlU>k>BW#@Q8S&>G!R2#Qr~2I# z&%czW>OqU?1&$5By88W3RzLEwd)ITXC402JcAF=RJ9wZU!9GF0ft$hXNVQQ}i~R82 zKBaUu?>bde#3l7z?5bCr*8yiKUx|%yH6K_iv~h!y^+iQ`0n%AbTM5*Lns@NKZ`;xJ1L>!uSAcdgX2< zcV;BK#C##zlh!_kQ_eYdgc!r7=ze`n9Dc|YGIi7Sn@Cp>i(r^(+gASK@b86%@2D?4 zTmLr)P`zz>dYU}!CCX3P%h_!VITG!9CprJn2SQ&GMs4@1lp){hjnvK}qx$dJJ#G!% zcr9AHu-{AK_lggpvHC(gk}R+DwMddWjmZ<=3BNK6@CBJMDKn=kbys=#Ba;wsv?{~v zywoVVzgFs#)|@tZql+&E3fwAw_)ihx{n@KuH6LxQ_%|j+1vEHS{I8VLvseXjNCDl6 zI>TI$X9Ah+LOrn?o#f}lvbE=j_<{OO=HoUZhmk1|-LudQUmM%HMerFnWz)XKVBXq8 z`!|@2nP22*pSl?g3y5}c+RcLZm`lG#AXTlFx&U6eOg(L%O9~rP-yMebLYN_x=a>xp(fFnRCvahv?LRD(?6p zVsX(yX*lqM&^x{LL^=uj>j3?6C??)G;y;SyRrXV!!brqUzw)Plt?hLHGwzWwmIx$3VdWk5NjhsmemDA-PHO+tB+zc5G z$Lv@GLo$sLk=I9Xe=A({Pp^daXn{QLF|4u1yBGHZ05nMbzWDSaXrynApaWs7Z!|EwrQ8md4iF0wOqr)%B|0v;7HeUCQ?u| zra()_)GW?z&!;%O9>;u{{m@5Gj>LOErkrTWoY?7_JWLUUVS|uXpbOV%ZcZ~YQ%%LP zjFbu!>ED!y@R|}`&G`{-is+$|aT%qUfv}<^QGw{LvoDH%`SU8SM$hYejrFqMyK8Q< zE;y?~H9M=aFMkvD^ve2Xd|Ugi+%jovoxl;Fd$Mq7UGhh(>|QpF($7vU-gGDo z8zs-UayZ`c9$+x#N4v!nofy$Ac9sN$!Wf4zk+#d>j&6O@2(06@ZCAi;K}i1;hxYYL zdmk5H$xrz3H&U$W`ljowOz}d3plOF;tP-aLwfnUO04DY6?iCo9<<`F9gjPyd_zwI!8$VZsW`DYnCSNK zl9(rSaah)p@!N*M6^v?Ro4&di98ovpFj%O>rG=2EE+s#!YlCHmR++t{w49EQ>+370 zz6h0*mDGJ)o+!JQ>ah2BWQNsnR4zu%=cZh*x^bcCtDa=yY6DYCcHW={lOXeb`;95R zWRs|B(qX;;&lf%Y4|w_f2kjPH!$LY==Pt1#_Y8a^)>fC^k9M$Do}u?Q7h%D^sWgiC zLEJZwBNZxvnggq_UdTE4=c7WuYIbrA>1G36tHjw_hcYktil0`|FT;0LueGg#H`E=e zg@>-&9-CMe87zKgqLR8)W#7Q&P2 zzFpZ%-g_ExUbJhI^x}QAD8gQUIne%mG!QeBPNxg=EwLxu4_-sUh;f112p6Zli#}NH z?5BGah+p{nW>p#!n4b zVPnYs(UjDs)vQAkb)EFgaYa(t(n9C$@b#72U30LOlg2G<`?We|m^Ks8WqI@}$`p2>G#Rh*6z~ zypQUB1o(#}jP)ByC()fNR+@W)ZJ#7s&K&;=NpE4Rd>x3po=HNlANN6za`=XU8o0(A zwtIT>(>en+zN!Pk7@U~GV-gR`J%6tA?*&i)AR6^s`_b(M@PQ-;4P6rXZGOGVFZ2xT z=)RM5Kk2Z&m`mCHZ?3VKabNBX%3fS{@57$pq=pJjlgt{2W9(1}o|7}Zm`W}B%x^c} ziNYjDY>5shRihUC?e+>^%~tV9M3m&x*F8-*-35cg`>YCeTbW zqjXHUCSYHkw|5k&n9j727P+S>^Vtivw~-NFUf)UPubYK;GkMG1zo_|Twurwd*q~*B zsW!e0A`y8yJsM+teG+JF88zR4iYB|a;nxxso@)A)ybIF~*_kEW342rpju(|6bIf?C zO6=ife4f+pY&9}1@{jUYoKTuMPFJjzT%vdX4KH_ZyV39;1?NAx4waKrt$4Xr=^c)= zzYth8_MT<;Hddq~AKelXMcKMu3LNwle?g(st3PvG?$;BG3ZHniEzN@;ne6x|h+&bB zXMTCEl~)&HyhKl(FIhspZ7@TMbw+z=q$mZg-K5WVU|b37qMQ8qsi^u@xaL4kzKbM9 z*|BKKL`SCxV7r5w^;JtY`K~GCOnY)a1(ka z>j`&&I76)93}8$QWs6RJM`x*cK^0zI_pX`DLy+tu<|`z|KB5$PB_0y;Oe1%vU=2wYGw;$i{_SkdWRY>*_S?XqHJ;aD#TS?>o zEsJb85Oi8Ozy$+dsp2-~uE>@o!!N?!F33Muj<~A%!6ZB#c!wi)w)rC%tjGYf!UOZ8 znI8wV_PeAyCRbv(-WZ1Ab8*NqVXd~5EyZ$Ie+syB(tJKzn(Qc5pRe&}){L#*9R~ua z5qKkpY!6Zev0pFLnAH*k-xTnFH3mfs5GNQk9*q2sTm#~;d=I4SU;OS<>vG|&4`8Yk zz`8>!k->rqH1rvvWM)jY1JrNuk28bpTgJlA_mpAdohU_z>(%6DwieYzmqnmDZ9^>_ zV*TmJr8%bf48xi+rRu?%aX$QhDsK?)K2twX_$kx|ssuUcY56R5l}Bm}RqE|6g2d?# zd_|`Hy;4HsgGu_{Tn^1uQ7$#Hckx!Ns=u3204TwZ2;{ccpuU_+vn~NjKJwnLd|}4} z*&h?a{166|H{=_+w26gfAouocUB2Y-8c$b5W8|%MqdUsdb1^G<2n;L70XvJo*~x0S zL7Cu{YF&9@f51a&<{M}`Qi?ozQz|-KN{nvwH{iSAV4IaXdINH8`uawPhe)!XaeM?E923}T7)u4hYgG><~Det^{%rBs{r&8S)XV0~FR{!a* zm0i?>+yyJ80r-{3+inBZ^~b2G;s-0dQ~qrEBYkhv?bEE}Yh*fB{-)`*Z~5tss&nXy z1U6QDAFpXe`p3zA$h}rkHv2juC?B4qrZq2wVYMjtn?2hu9%~Vz4(B-&^ePYO^H|wa z{}NOkdb!j?li{%igW?m|6b;hg*8SWa{%${klMud=M^lbIm}#n@RHx^YHYL&GNcfwm zd)}{+kWLoNKGY+1vPg!3;A>*b3!jNclTq7JA*{*g!iY_3#wr;3z$0?i>;_BNMD{gB zkO$)tz+LZn%Lh`4nSX4ftcD<8DP|`_{ALn6N%9Dyikk zNMj13iaWz%i53j{ib3!|sAD(ty#VXYB4w(i1!3?W6%E8vit^oWmr#Kq{}~d3+9D3@ zI1lG|c~|jL5iAa&jB$9+*c)JFwtGB$EDNFrB`nr=4C&UO3$oLL&4Je0v0ro>J?pTU ze{X}Gx#z1x#J@KIPEEF#ZP$*B@&sdft#6=RS+buV>WFx={OdQ;<}RBM5-yDKuFo9T zXE*~OJeX2=*u?>l0a2Ig7B)kYABFl7uC^#xp8y!@bFw%!xEw>OqoJo7okUKn`S!8A zQWkFFqcl!XT%B>zE-mCIlWZUH0*yBO)m4$Ql&6fl#f$`_>6tp3XFakw0d^P1kLe)s za7`FI8HNHQX~LD6wG~0QgaxcAAWq*J!`gDBME>h1ZooaYA%|y}g_`4gT7=J_FAjmKu=mg} ze)O#g<8dRH6>C$;%IIYGi=J2fl!|4X`h^hkdH)@LjJjeDwmKGmaD0#(EY>aV*_@|8 zDNJtSZH63v7h8XsG`;!|uPrV)Reqw<5^iunoV1UFU%e2P%>q9JgYTdL0R#?+n58q=a7y78BMoqY zs&dc4V$bzFo9{fhJJFd;F;g~o9n^VUr{8~pYs;YjwK!W+)HZaL71xJG`>l+8`sU#3 zFpO>`%)t`g6^do^KU9iA?#(TOtK$ZQxrKgt^bZy7JW1%NpnUq z=&>~{g+CF{M*OopMpP9T8?9GXbdjZJw0mv`mgi&HZ8WC*VuKKpq5hpR&5IN)GPv^$ zJCw8Vok(9shYoh_b(o@RGVE>f5gk9x9GDJe2()V7QAIK1Shn5oTPMIPA3-D^u{6QF zYjzqM8l=brbT=lQqaGlYig$|{4?cdSscAkk09JmEp2?7d64+tXauP!;m z*Bz_C&JsowSPLv>(<&91&}2VOL!Iy_$e}zOu6ih zU~F&Fg>>#9+KFZ7`YrU@Stg!LHi9smDLp^GkB2q)MCaz?!Q+*GGj2Z=);GBG;Gto#h$m!V&vhOS5Z+%hs*)P6VD`^%YA6*`F}-(23b-u6=}G5jW9yUb`1 z>d$e#p`(U@-OQ}@;TC9@?(rrph8>ystg!p6g!u9=su(*`J_GoXQ?7rWQFn+!*~n`m zL0*dcS!0L4+EiJ7x!I#yXE$J=~!_&Fcr>C*a?7miE1%o@DAxZCE<#h0}>B=YFj*_f+P0ZnKoo7Qhkx z(ZyZCMY%MyNpLXe=S~=rR2MOvK9hEl0kDmxpI$p~j?U=XR0B=p3+fWcj-{S+45mL; z-MhB;M@(u3VXhV@lcp_hC3YXSK0bjnuJjvKonq%Cl23!^*o!ms%eDx_I(7v-2Qo5N zTHK34%q-=9NM*0u#FdcIO^7QX8bWuGBL2h#c1T#&@-Ow4e<8ht2G+U!03eF>)bV+) zEBjTUf#p<2vXDdqfwP=2>?w|7-onjYFaNp~{;&kbK?FFEOJ$NEbaKK$`5-F7d;@*= zOWXWtHGu01=s}e+uO6qN+2eM2tX(-t1@*vjd*=~hkl}jsh#*qE{poYv`tfYami|s>H{8=W^2<_ClqC61 zb7Z|B0di~-*9z|dIb8`#;`h6gXTGRMS1RJ$+Tw?o zWH}1TOtv;uH=9I;H!d-rC1l?CRA4Mf+HuO>00(2FdOx}|AyNv->tAzf40E1rFD)o*_C!h0EB)EVJ3JCArRmk$Jmdq_xCZ8PNnVVwz<=}Wkeekm_a z-V^r4*jmbXZ@tt&e=J(BT<{vL5nuic0r!2lJIJXut0U8YV8n1%B15L)EAry=^80dy zSEo1Y8`N_kQ`gr)aYHkBNmcYC#^{#{uuRIGE1VFX5xXC+0?ozu{5cz-@buL8_*IW% z=fNg}Q=KkVQYLR(eF4d+z({#q8=kA^yQn5%>nWUnF@<@uPUj2y)r#X+%dSKFmc6`= zd9<^qQL;APrsXg zs+aE%Qs;NUHzi*YSiaxMgsF8xb9OhpkNww6^BSr~H-%=vkvw=q3KdRFQkg6>s0jetrI6dcW>V`8&>y zXm4Rl+@NKf6Oj`)28?@$=d9~2E8o8R;&4n*MKQnCWFcJysOS7K4tfV+;n7I?DqXrw z`exVCA%3I#I;gdZJJEcrnkB|@`#n$Ql`I>bqRLodu0!X=Ep0AY*8k2vtW@Y~efM)~ zE?j^`*zdC0%X}OLD7%Vu3+Q(ml82M?rk(+%!?~4rURGv0v9kRrSo{X2#?hX36GIkX z76OjiQ~Zdf9%af99>pG9ev$_3UoXQ=l7g@rk8c9_u9y3RfS?gqORajt#jK2GR|>LP z+h{=2t24VmVbZts8H5i;PuusPX3xj@G` zbt7`S2dlHM;G*YH%Lz1A>FV7)(zVDzpO=-oPV7+kG2WR0R9bM7fdHLOq1>&mzAT82 zmWn&<4V<|I11sXx-zYhtrxaOOYnv^^=yZ*gNUos#{u}EvIe%Y33?warAL>4xuXTgJiq{-y0_VdRWPlOon(t; zJmOE~Xr>defuF*fPUi_;x&WPR>(y7^TQ@XpRf68Z+tO-`{!k~4Q4+Oc&08huzS=Hc z_<8ej^R0B%F!DPRmq>{&oY&@Cf?(ueYQFcEIqVYfnU&%iNpl_2ud}eXH@OQm!|(k} zR}h6v#`kTzY;z`BgK`Mxvm?>Bnr{a=0bWcjY3yq%4eN-Rr@C2`qU&cIumu_)d)TcL z;ykPz^}&xIwQ2z1_0=z=*Ary}prlTZd`gyyqxF$Te7VoYrl@vkcW~FtG~el!XG1;L z{;9qeTwgW+@=>HfLI)@e|f32iXwus1*u&kXkcX_zIKXvfZH=Cm4t1f ziH>SXNB~AV`0ViamC_?4w^uxr%M&+&tgG^>+m!}tQbf_HKE}WHqksavrGKOY@~`nU-Ss6sTEt(_rh!T z_)cDA-NNSnw763%dFO=@@V?787Sc`boA@MQ#eF9Kt~NuJK=fI9q}j79Ew3(~UD+cm zEVbLM6=!jNhCzrA>mNK(=u9`!5zgvo$sYal^z1=-Ni8ZH?UhRF)XnOfbKrTB{l63T zkoO{50vr_Wci&cklR)781Jx+m?$*d8kafbUm0m1|-{bDPbte8jAa>_6e*&0vWZhR* zXYz-4jbm)<_wPXubQwJbMjJjuKBZUjZViVCPE%GHrK5Okf*wG1%cC71;^Dnpj)}64 zj4~u*@OP4*);fKUXS_-9ca02^PMEG^(c^FVV8Vp?{f5$Ws#l@4A^Xk`&p#}v)>N9l z!2a+!CM$L9#q?YoP|1dJ+AQdSrAlB9)Yz$2zE19XuLJUEKLgk{9EH0L0ahm z%1(BK7zQo^nCZmnj)bKm_OR?>b^&%-c9t_-TKS{7$xNEmqeaOnbPRx>{qkoKbqxFA04JoKERvaA6f7Cezi}+>lhSKVz;+vXBSDVPA zN`iiL<#GBSGu5&5v$6fYV&7ehOcou3(M*^j8L;S!yn4?UgVsl4&=7jC0Ru@}lwym% z8N89;dYXHb81ii13UT2h95Q9@ym+y5x_n(BuLob`>G&=~2W=FaV|~Oe)5XrfEG|An z6mx-Fia{I8XZ%8OKL#ClO+cQ%o;@4j)l$%B~Kphg=+|CwyNmu!&C_ zyO4unc(2&WxUa&_>Ot1QZ~oXRgN{TpANh%^uGCC_mv>qo zKU9psD)diCiw1+KH{Ke~z{#exKN})C{;-dnh8{V>CwWkkk?D4I=XaG@_2oTZ=m1!D zo+Bp$?q4;!y5VG}3s=wu`*yJyQ$?5tL`Un1uw6@|?ZP#sF65Gl%M>JGIRpvh#GMJH z!S5~BgfuB7T>d!LyT4N6JYNveW3t0|=L!so4Er~OTb-lGZ@$on+P3?E9!r$jgtvYwonPRY0e3E1w>EU6j^ z^i1~-A|sErJwrn)=&UN`W@MjeH(J-Y>__NK$_Fskk)BfD8u_xkSx$jZWd1>*@k$7= z{2_2SLsN-iAe}beljuP44P(*PDl68gdI^@erKebL3ul*oyX((V ztr^Ip&giw)q@hU3_;jrU5r?xU(E;kTyUj|xLG7qGVUm@sp>{3ac=f(&%3z;bzmrCM z$E3qO&zv$VD{?-~N=zM1GU|-Fv*P{oIS=~gGTHz>^E6#7wZXC@t+iG5UPb7NDV`gl z8U9`hdnse(gZ@HIFK@3Bqbo0-LO`FS#zltl6X7PY&DH5F{OA2WBvm$E^EDR#C|E(+ zXSMg>fu0;JTT*o!<01pRQvQP7-zuG1h}|Y;H^tt3ZUE^F=>%~gsHNA7KML2!&>AFb z7?MJW#8Gu{;QewmMbmFWCnqV5wuBJ^;=)8BBzrwjjaF8+eF`r~uH9{ANQGApSc2Ln%W-kNV(fJhJEUxAE`c z8~!xn&ay|+s>deMe%26GBqww&tui1;jU)IBreF$xC1W)e$j+J3BThUOse>2CG>F`1exN*Mnq>PbiJSkKK^z( z|9Qi|_+y53^H}euCFqKQWF|3q^}WY<+#LlL$Fgy0pd?<1yM8JuFRStM&!LM|HzH0* z?Ucmup#C?k7A0W;!EdF9*jx_XfnLFEb$q#q->GoEyv>Cv%c1RsEb{Si_$w@(5#eJ_ zuU}55|6^Aj>_pKD4#5(zq5G$}5W2$39_7wJ(*Q3`x0gJ6bU5LgQ84N~XcdChb2UyD zS#Qn8`K2c!3P+}Ko@I`~W4q(Cq~^+&*F)3$o0#*FK|AKqU6{l7GNMv(N@{VQrOVPU zf6BX|c7age0Fdt$2x%fT@TRxeKp_z$O6XyV;4R16YS5zBUY0RLEU1AqSEDO)<{)J zFzSG(suU>|9;)|2(q7AInXBxb5udns4<9KkM&k9WH;wj;k}++fBriI6|45+C!>>(A z;g%&n75PR|8gi~*uiA^>PP!#r9Ak-(p=C5@+2`*=1Mc8Xh~E*T*qiy7X5J^zNj`kE zO`RK7jPjQ7yOOlBHI?K$oBaFQL1YQM3;h_z6&)_7$1aNO1akR`#kOA|y7Y9W?V0p#v5B+3~oF(sfDdq^lJq z$XQmGEBfE!Sgmh`w#V+sYVk==s85`7_bA2A@txx2 zQo9}PU@c~ND5W0=mwesBf+KK?0^-fDJ9uPaG3~v7PMcC611$dXLfNu_rwxzgaT*xd z+n`OnK=Rq&(2OkWp&JRqUU*cTZ#ZQXFEO0YEYM9}l{)Ku-5SJ+e+Zv3-Tw-P=UagO)pyt$R4z z6_bvdRu|caK4fiyqxE|>^8WBg`$5>bto0kP6DXAT!Ahk^OXU=g?rA|3pELotD z`W413*8x(wK@C|T_z;@lpi{GgBQVCj>9r~785DT8Fd{^ZQd-M(cw#!sl`r}0gzAnM zhTVy6X*_OzbFzKABboi*YB^vNs~f;76gR(7)vrjT;7sUcK9#rw=?>0+js@N>LSuTK zCy1D=_~`j6$CiaCw%Vk2LO`mtM|f6%-Ua<`-)XUv^gV25`C2Pbjat=@z2LhN8})}v zG=u`(3LSX9rzqs5ob-2bt)4w0(4)1U>F@}DEgp6y+ppdMep%3$fqXN5651mO!=DEP z?cHB}WvNoSfbs@;E2s&Y0qnc)ov^bUF|wcFL1u!Z^C&;1^aBcAOiks-GO^Jj@RZ zp?^DkWTa#VICI64qbSvyT#Lj36{GfcyVKnX5IoRS%yVs9>z_;bzXa}ccHND{mJyg6 zyQe+PMe@SS`$6NXQk#x_TPsaWtk9@J9;-XK(v&)sTKQucy{@MRfpK z3IkT^H?+t1Kn)u#Zg5WfHL`)*??O9!kf!FB0Pkf1-JN7&biu741ow2T?rU7=4w~^F zMx#IqfhpVBd+*+<>{sNZ{2Yw4-W=~=wzYMIgHT3o{A8kJI9 z5E(TP!$GKI2AaNM!BQ>T%Zvj4bE%fs;uQ-G08~gNsEEk+NQ?Wh%yQIsWi#Dj^pZR z>9;y>jk>{OIr18QgI=E8KcJOY9;LO9+XMW5hEdP;8IGN5<6hZ~!6;SEH1_g+TRw-R zk@$DQ2iKpJJd*L8zlbP(ScRuz>}*);mls;RHOHa;k$zhC0h7x|MMMP zsFDdmRpFPf?}hXTlHrH5fPCUEHKZ3SNlTs&Q~x@`Sil#y&Nic$8Q``n{7cMdMNkC! zy;DO0pl-e$?i?~(ReN!MTq~_Hc<62Nm7Anbr1BO+&4wk7)#Yq!S%hM3#k8S@tMdId zF_^1Tkh|04rN3Dint48Xk{`w#D;vJ$YaX33vY)?}G3#c8p7vdNkcS{7rnd%`!w1^E z!+3-PhV!1~m6SD9MOF35$*o7E60_zA32gpoDTI)Cs=HE$xVz)WHcM{@_YU>OIgwo(vq{7x3r?VdKOlZW%~<6`Or+NXF)Sr84_%9`OzLb!NSor%XjgZ z!pIrz2d>eqb)jfqSTdd4NkFNRzezyCR`54Syd8>E>gcf(*n znr0&U*re$--lZ&V+qITB^CRDiu%!IWw@6gk0v~4&NZZCaQzHpO#wN8_TFs>if%;E8 znp#!KF(zj4Q*nnOUCu-3TQlO6CC%@493;05?r;hKc+DZ?9j38;v>Q@e~V4vjOBxu!@=KqU^vFj=V4_q~VyVRw-&Y5r4% z8KU=ThO!fU|oytR`X*$Jp_P<R;qL0~?QyW^F(-c*@Jo8^ zupzgwO_@}zC)~rwz#tqmGUqqsjoh{^>33?zF^`*`&|SaAVH(7QT3uSLRX1=VeSEEk<@p|{->gvGRf~aU*l|iU$m}Ho4>jl(g#=I zl>A%{W+`OpqeYhf$`ws{(JK!5*(p*J(-t;!Tw;R=mn#KN^}lL$HESQ;8m5uf;&JNa z7B(=XpkeFaxO_l|PTa)7FYVlu63~PIrU@vMzggo1Jkh{B_(%#C%->QDIedDWz$4>$ zNm-qSb%HYwOMu0qvWF>dCBjSmimau9BWGF@Coqy>m2z>>cjjf+>@?QSgIHW9LMe^E z{jSOJ7@S5kv8M&Jw1RI@5M%#x`%~kMAB_E*Ctz{=C+(4n_&>cp)fdPwJxm_Io}Z^K zJadWBM489vaTGN;!+ao-(fE@GL6NnkMI1HD5u1Q&5#n^3L}VOwBL1+LHP0Qdg=qC6 zjG#Gv?al~cT$oyU_Jfw79_qR!4|zJzBLjtQ4*9$(s8X>{AvlbP>=U1y77rcj`^$EGF8dhZ64u#;kYV;Asy}8Is%| z_UODRR*m(-K^I4T0|dWura5|L{jpoh*f%Q?n@r=((Q}qNoI+rCkp1iBf|#Q#^ootX z`7|A!4bSR9A-|-pD z*lj+dS8NtkFr>VYe2LL{q%mYPMs|})5I>*6l1L@iKah_wKlw6vAOU-XlnV&c3$x8r zf17mkb|nuHO3RIH<|gG?zx%>()@9n^RsM7FWz|NBTQlZ}Qz>$n%vHPnYIoW=+1wX; zC}ubMTHXD1hRS->_xCKGYDfq85BkXyU~+O~&`Tu1{bUV!V3wqT%`K@EV}?{*8c}2` zdr#XZ%q?nGEET|3kThQD%+WM&N>XJp^6+43(u@e@Rou)IZ64bM9m-K+zp>YBTFXC} zY@uB0dr{1f9ZjUYB9v0R{*tfqtANZ{E2)-!7D!>@LIM-X_>Dk!Zaxt}m^g|4@^4MA zZq_80eqVC>$jaVTcPzGcqtVtP z@2|bUC26f`gGPZ5RLxXH=I)3B{yzGtO9WB0s1~5d_Fq))#Gyi`p2aJb;|VeUIZQq- zfAWx(*%mE?fXV!uq{QF{)gB7c-vQ(>sZqCH%Ub!?v&%8DF_JY1?g2IT*VxVgG~u&P0Ip7hdnw!f>v4&v;Ghb{WFbkT z+|L|np-;iQwtjqzXpU-AYMwazu>E|@pUKbU9*?P+{b%Wu5*DA9#qada^)nGd>##tV zdL}p@L{xLbspft{hYCX~O>};ds!n+Nud_!{2j;Fl@r3`>VT^ughjj{`8= zKK&BLH-Bw~V{dCB&4kRB?xoFKa8ECp96d?=M~mq2@mtYt!Jg_MX6oBkugY_ zZqMnu+wApKu(^TCZ_S&UTKP%RBI za2AI&KnMk8kwjO*G`s~1XAeuG-?QK~E$b+Gs8A<;jgo`g-V;)h+~N!(H~{?Y3CIx& zY0+5-Q&)*^s-LachrTiY;th3-(6!!8+@2&O7203}eS=H8 zhCTua?U6c|WM5eWoP$)3^k=S5d&u`(%0rJ1zvT_iU`$2yIZJ4FfKG7%0}INaFboP_ zK#r{sJMg+lqf5S`MUXQ$bIwd=UQqFU>+Vp%2OxSse<7(xT=;W>D@aAaOr(lQc*kML zZ2o$S?ht4$VP0;wY~-Y&P5*^8NZ}i2D^=xf%#j$&mrJu@Uyrw4MzQ%)B^-;iM@h^4 zLc`P{tqB}oKihjkEmV9zApKXdZaS9zym#MTY@K>Oc-BL~k?&K)>!mECpR14*?y9@D z&(@t8Oi{7JB(Z$`7tnc!OQ1OBde83p6At8KXydTeQ=V>A21Np`$MRPaD=puz!<#)l zjA2?|4QASPVzHt?+szT*K#>U}r7#B=bIFi)xNLWxUdDt@%)+zQD$L=dB%f7$PsGoG&=q5 ze)IEA{oEfyjHIqGihX8d46@%ev{{d5*m7@S^27&SBAa&B>kXt&_3K)?B)*H_D{$Qf z1OIoa|5}@lJ^;{8bhh<&L)-}a<`{2V6-2KW?5?9mjApPd2MkCz4bHdggpCh0F&}ZB z46n95_2>YoPj1`S7J+C%S!E*x3y@tuk|xp=g?sTplQ9i?U*IQ>NNzq|9B_XHz(GN2 z4*9^>(f*wcJW_rW@3FwY;PS6tq z1C=yHrO5}bIE1E~DVr3AJn1J8BA2+X1QKse*{%x0Z&$N>1^rS ziwF`buoquP@_p_cgUb2+Y;vSZ3g}Acr-p7hX5wDNP#Lfcha1Se{ zXeCm?B3|l{H2NsA4CzW}**@*r)&#x*635*e-DhdsnN9Tn-N}0Lqu=NQC)Q1lGzY5) z6c0s~dfvTb!OxW8cBDMia78JynAsd*Iw#}j`!I!()o-kiO=mo7O#COC^G#)D+J8Ou z#l{`R>NQV~4-)4s`=_`a4cfqkQkw^4PWh+k0k|qvdx@CSePqibY{ z^dq{f?CoxTzjgRLHR&e4=`Q{oi9n+-QqwLG*+$f__L4e#41XParzq!&2IkY*PO2f` zf9pMqM(&-gfRR6C8z^tz<@qFHDy&_B@;bjIk?{Rco(i(0a9JxBKU3JPy)GWVvkB^; zH^t`0&Qo8gi`YC)d<%cG>F%`wU);0nI3ithwfh{)?@(J9MjPsb@QOSluL5R(!LN_3 z+cJ{BE<;z*PvuY?bIq%i4h$AlFbmN|wfL$fO)gu~;Rk7Yv+xbfLt-3PWnusHOIhRn zeSos!c>hl5V{?u}d_%^cF1%v0yc)AWvL5p--&prf0+@n4IB2L-BukiQtY2JTQQF@5 zhF$0FFvgS`qro>Tdp&}%TV)3Y%V>%TwpKv)1hUQWwYNHL9sPm)3|awE6rE|G(N4&1 zEtBy<+4089xEL9DOpLQAbmftx3UVCbxfQMY4P0^>%$z$`Bi@`(jO#A-qtoh4W- zcOW436{7x?dojMJR)>J~s1f1lk%yG?t*Yf(x_tKZ!c3wNq5j%abU8S+$ ziXf~QLN@hgvnJ@=a~z|M73$4IW;E>H^k>iiYm;)l&nPS#nkB?=K{_V{!LZwz2yNR_ zKy)MbQw-kFKuFxypQm5KP3#cGs|OLM?(5%t_nnOzzMU}E+5Iiy2BBmv8U!sm6l(TH*C-oG*k_S`v|omn@Yiv@V>g|sog+08FWnV9a7w11Xl57(zBRLVO7 z{ST`*2yj3hg+pJxBb{K>zTIVQF&w;kn-|C@6WQHR^Z$tY%77@F@9leo?(PujE)kJf zK)R(vLO@DFNy(+9K|v6Zl$H<_P*4_-kdSVH1(Z&ahTZq(`9077{lu4H_RN{{d?B;%oDfSAX6t!^4R391@JpNPwOmA=tn`v=Ep8A~(qzXFy?~~`& zwaK%EDx@5mLIzS$ynRS<*>kXDGuUY|P!KRQ>C=(Yb_H#_%H#QDG0nTImlL~JIj-zx zk(9KA`Fkb2wr4+xP`(9L1uIzOkWOQPHuSamX44f5rIb;#W_h2y&affwhrx2UUTG3b zP;vUMAxu4=h@$D!!i6dIRlY=bssrDj-JS5~_&0~Q2HQ5tltgE4Ma=@cFDVkrQ-Cx!ZikMT&f}|HUGe{MmhS;)^6^>R6oC z`WFSw>q0AB)>q0QF8Z>2)H(Twjkc=lA^{P?NU4{h%NWjsST2i@rW0Lv4#=DbZ42x# z`2nX~0%7d`1&JO&fIp1l^fbO-PR$wep+I6d@JU%Hb+%ZC;`^?>#S>mnyXhI39z_=e zdkZVVvsb_l)7NFC5LEA!*-@7CapMhq5)IKxof9Oz&)D*wnBDV{3zeDF=>4g;YZfLG zO+^n2`tm-TEC44DJeHH#URY3PgoZaDh#TApMRnyH!jesPv zlYUnbebQJ04T7AI370^l|F!M_ni*ZSKpYMUDgQj_7;FYtp?ngD}Fsc z3<8Fx?{+U3N-aFy5_1zFv3uc+s(x5h_49f7&nR`I@nWeaoIog2hx513LfC&C{B?@h zKb91UBj2)ovc%*}L>dG(O$Dw5% z1@pR0iiKkftdZezPvcX=CFzW3M&rdx_20=fZp!dA%f2l5FFO602n2kNU-RV#xT;RD zVQ+Y|mCG+{&9Y&W3*%o33>L4BOy&0pLbn!pc&{LqUppH6wTh+%=GEu!^)(Il5UECy z81+P0-`+P=o!+i4R#R5eiLiil?lF!hRMNXYlECXdc|sIQI_tRM*+hJm{QgE&-EX%> ztHCbUj>cMD2^c%h)qiLYsht-RN70#w0K;tICKSMC7a8`<2YD~Wx3VEJX% z)2INt0u!0xlkY?u{s1Sr14lT8fgeujS3|tVJsZB99II^;SrxZeiP#;h-lVu3;RXv* zkH2--rxFPXwb~0FU-_kbMB#Io$~35mUwh4Wp@w{*^OD@Fb43}2ZYjZgbM%4b8i(G}7%bsztzHC0TElIqq`RFDFC zkDELk>hOjntOo?Fk7O%V#T1RyIU6D3Q>SG_=k0K z-GQaYCnEN_YR()N5|qrGjoFh=Zm0wlw`g2c!6qzAm=jj<_g&HR0HcgJmgq`ZNbH8& zRhe*U)c#d31unj?fJDK~C>BKp`g-UCRk?~qGO=)V9NE<=fkeY6^hc9%Pkhs9xCb{9 zm@Nnc$9^u{L>oCAxfAtGPdM$!fsNw-afg=P-#gvkXdrPyA9Xp}@K8e-Q?sz9ZH!O5 zj$1f=AAb=S669x;yfUWP=>AIz(N&Qq@0{+6P^2KJ2;x&*#Krsxj*)*YKHdoSjQdSw zXqiZ=+BHyVXh2hCo#dJWmEKNu;3H_pG2`tqH=23plz5~)_V>TEaMmOL??v@TvhFGV zHwkt^d6wyQ@kTEEI}Y_V-tq=}pKN_0Ra5(HBmJ-8H&Tx04vz zzO%E>R$i`NH8nO)50eP9ZPP|?-b;5k2-UvNa)|@`1Me!w7MUKqi7ns>J@k|v?^dFQ zyr8jr#&^E)M1|9VZ-MPL;jriw2`EBk!`CM%qmbT>|LH#mALTv{kxmwQ_V8lL9k&6s;RH0ajy$0Si_HKjmI+mA)KK>WecCf)23zLO8ln1qnQ7q3p^ z+f0{qAw2)(u|gEhkxDsEnoxOi{`Hg(4Ex%$bcVJm$^vbfI72XA22(?tyrI@}!u*g8 zbAGYsCyK(&)lhZ0Fj2;ZBYf3#Z(}Z;&|-a?8-do*Y+|2ftw!Hcy53wgxu^1xx0Q+s zMry4ci;+9c6!uNim6nNM*c=_>5aTK+`#R7G%|M?`H9;@`jTPr)z)dK{!au^9^+&qLof-kc1lun*tmUNRV3)-83e z&xigFF{R)o*k=kptyqBCQ#^tyWItB&L9^Z_*wWwO8KUnBETlB9MSo&6vWP z*ciXyZ9`~w_@^iLYkb2GhtFUDcHrv`FJ(9r9_fkeRY~+S%HOCOe#9JSwBWrj z88scL`07*K0n?5<;}oA4d)%uQg}N61iNnlUanpB9)BulSQk2ebmZ})64c@|P?K^t(w)bWc)+?5R9eJ=_2jZT+H47!sD51o)RV) ze!0DzQoC=5lgDL*A*%DP_a51}V4`tlI0Qe%e9~3UGg~}rW5OiL^KJ9XuL}_o=QBB1}o%(;9n`K zf6hvZX_rr0t8Lnv9JtQ16Mj>J9#cbV(NP>Fpk_M+$+rM*b&?Gs>DE7B{dJpcA_5Ac z@>~8$PUP*eheg?5p8#4GQhr$kMDq&Tz9lT z+4}${?jNl;?p!LPy4oWxU)QdN<_KT5vDxtW(cFl_rbVy??mW|eP`pXUoZ(X)^x~@$ z#Lk^$s!wbx-r&Y2tB%&85?34d94WGzf6+w5P|LiMaWC!l6xuI=@_Aehcbg}E*Yi?G z#UnVVbb6ktc}rcR+AGKPX2y3bm*_X^#`cv70C@A1cbKy-|A1zP@VBRa$?l z4sd?B(YS&9tv18{CXJkgT>9hQoQJRm!CUtz)eIfehToN)A7LJP|Nqd_e3NBSk<*sO zX>0iBKKH&vWXAUw8LjkTuTV5fjOZw9kC!uayr^#0)G}7Q#0Svg*;u))(6R7PDoceCyXbC-3v72+6hhD3k0U%?|ky! zRy`P=<`6wT8qC`CpM5a+?-9i$1yThYN;jiqd#=q1@y@zq=0O+my5?$c=jHk53a47$ z^0HC-f~k8lJYSn0lj{+F=E~Et37b^6NSeI0q4Bs@0;y=-#4o~UU-54KYBahZ54%++ z-zoBCj|cG>2z{@vjrY#t$D%3}|JRaW?qM=K>d5mvyZSsCiv6EmUblu37&(;M1m2x9 z%O886tdXAC1}5&2&tin{?JwTFMPi^LY8%hq_0oCk1b6O36nnsdOZ6LahoG)!gnROL z{dwIl^17)bIQT^b>OF_t#}}~@*wv3tdm$gr^8|*xLY_w6z8w~Y{X3(8{`sz>OEQwU z8?gbZrP~5lD@cA*zCDR2qTH#t@7$*7F z#e)7l3`3zS#)yY$uV7D|b7yk;lzoTB6%}^q(93 zIIk!JfL=7u?2lRlJG2hRisu6~PbM9TdyDqlvTDxhTRlCW@>T_`rb#Mju@`1cvdrtitt=5A%q?Y*-fcNVW|tY5N>X7*sx>`1<1Aw*us>M6GlUYSU)=`q>NhK=HbzCpaqPrsM#Wxsf5FMj@AHNE9S ze$;6ak)I6z)dEYdAV^I_4b;A^Q=fE^2VzQ4l0b~3+Mt6RK7vXIxnLG3;43!=i;2g^ zGUUOff1@1@Bo9YS_gO-ZBinlH$|9MFl?u`+HTd|Kx)(~+&_pUnRmMeuVUn)haz}Pe zz_R?y7tHF}em|^t8!l?Ff@GlwnTOpuUtf)xl1UebG>$9q3tT1)g;2N0-aKnf#yB!mnI*DPF>hWj@~^u%zV#%3>X>GAudLvC zu!+}ZhT`oTJ8xO8W_uKR1ca@ZCCWUOQG9Y!5Z}Kl^z5?1gCxO6B~CNCW9;4im6m6UDN?!iJUyO!!S|S5=ZlYAET7GA zP|Uc1>Bdu}{qRi3-9iykgMh!|E*>*&eSVvqDs{+MEog;lL1DJUp+X7*uzl;n;Wx#FARHOf{(x)v6Xad{S& zJZfooE|kh%Bs)P?4JCV(x!G+tAd&*;?6&U;0ZOf_+KT_?TlD>^hlNSvUX_LOBwIpy ztpMf9yx|#2N|s;0gGWZCnkeNM=oU7n@PAS^5xcf4NjH$;J6-TC5U_Q9XV3VBS_x#w zxSmiuU2*OA>9*e*wpXkimgu`Sn)VL4OvbYeKUDM(-qQ) za-1MUV;Mm(w;aJ|aBwSAGJ#41QA3b=f2b&Qz(l&}Uu+b=SCt2nyu1hYFNjavGf%vn zgpkTg!?MyIXPiWK5oj9g;W_)BvePU(I-MZKN%1_kFn7kti@N*leii?sRx8_|hy89)OJrytFiFnAx*^SlIWL>lm@o=1Wb$ zMa`*FO#u^An6o^9dlQIB>W(=ByjT?H7eyT1U2cqMFHe~g=puVIu})`Cr$RYpz3R`=4gA&jBN+K@=G|Jj60_7E7t|gSPYOuEzi- z(USp7Z&xDjCt_EOi4?P325jNdPVS;Rezfurz!{OeoM=8#Fr;_hp1+xT%37hf;jf!w1dVCC6Pnx2 zQ(#2Ui*1bJkFuP^pI%T@1J3_Z@m_9?CNcRvK(McNZf+ej#Zo=tSJaTJ)FeOCh z?Z75QT!ggboWqplrmVnv!@(vef>IZg@v~OM_y|UiEY*ch4ZoXUFCLjpSow48^5giJ zd{1k+Wx6tTpa!unioi9U*~?>LwWZ!6u73kbQU_9>FZ+&*=@XSi(M$qa|6`V70YHtj z*ey;{p4$TVhlp;}(jf;rD0j9+dv=BZ{LXN~JEfT&XQ?f*HGe=(!RUt}MGCG>Xug8w z@Ox8QQ7;z1rXOBAORTzvZ(EH;EJ)q77hAHI12d)9t*Pc2&J;S^=3%!zejU+sfX2d5Ye4 zp6?jiTvstcwL}Tbl|jubMtIYy9s?)n`rf0J)4k0a|Ihv=pUr}TsG&m_Ks zZdVj)JN=vx#eD|k4v@#g+jCGyrbE$Iaz};@m9ljv9$1n0jRC(o=RUDD_!sJ5Wo~}( zV`mX|w^7yvFQLRJREn+m`{@5T7A8%R_(95(oeg*96f7Y`xsK`{LPY5yqK_q|2Jvj; zMheLtZ}k!4cDy0Gl#0DO_nene_j}=E_%!FK=`-BK-&-=;CJlf914Zi6NT+=U5SM{` zGY0`^LtL5?YO zVaF?qm1_>G18uXS>fIX1ajQlsL3v9(A+c4*?;SVwC-$7S+uwzmANw@=KQXIMdEI)t z@2m-BX58G^ocSYAT`lo1o2!XC!EIknlSeO$UA8x@M&XjRaJT`w&)WLm83S;T8t>cn+1TEaQ;*#X*p2Qvz>nkg zz1tm+ccI>?9Fc8}5+6%tjL^3v=#_-18AHiay+)UO4X9EHSeQ(qgd*yu7hlU+ts<|Z zft}*%M;Cyg&v5+Ae>oO z&MDBG3t7PrDLSG_qWO;bHzpjpUUJqQmBh|9<=oTEq2R<_bSjIE#%3c%hwn5e`lUuB zk$f6lasm!WE5d8;wj%qRWTi&n>y>(Uz?J3FL+YZZ>xi?+P|QWj_yh!tf6vssT;g5H z;mJ{iLedu-2Yr~aZ|tw5;o2nxy_M{Jx4z74>L5z=kTsXPhSxLjLh5j4>JmRa0^^%l zbU;EN+21>Fj@O$xMYi0!4%Oz*oj93wU|(4qDA6xsG5`V|xW-5B0&RFiXF*NpTM$W| zae{rgo#GNK*9&2HlZF)tJWJ!MO}Htl7=kS~+QAq29lZBBq;%!=~-2MT*eTifIv){Bjn_ottzGfgK3xSL4;O*RQ8ObTOia% zyQh>m<_sT zg46x;uf$oKH_n`SjyqEB0o&cNLVYnRL$|7nRyu&pJDqeV)OCGC@@->=i`TPc)9OU&PB3nyu*DbJpewt z&XMrvhL^3fo!R~CDu(1X1dXO``Up0SDq{=K0-7NCjFJv=gxEMeZG8F)a2TuWap zUb(1Z)d6kE>mk)1e<6vjBTr|M1T!cR*KJdV*`H_f%W|`}0 zl%g5jfY+uPskTKVArf58D^1`yyx#;zRFHlZChBHIj*xr4b}oQ-xn8C);MTS|_bm8= z*ySgu^icW}ZUOWi)%LSfxC2q{Djr9*^w2*xK#tTxb*aONV^o}^6XIENGS9RgZBjw! zD=;^(=$|~zc38rQ{kS#R6r(Y-`YEc!Cl##eQvIb^7G*yI7^q_iVZO66L|F`uxn zpL(>t!2DJdpt&-2S8sXPh^p>xG`DTE2I9aQBnQDHU|W@P-<*lG6KW?9G(;hy-}s;^ zLQ}$hHb`-Oaz`_TLue@T>lf!&hkZLB@&Xj%K6y92i1U#jUq8Br@CYHw5$)Y5%0w$W z1zJNT#PD+{szNaZzFuCoCHLTn_%i|C8&Ydq_724|tu`Y8 z9{GSB(9K8=l#!REltoj>E_^~jIqlGtxIfb31ykC$G!nHMG*O7p#R^<*Pi7jn7bmSd zNAXdL?*Lh4Q5^B33OfqWviLZv$~g$*_~XuViJP;=D+yU|(6rxG<0s8$2_2IT?mZAc zp~5!=-5=aHxqq%~vK`Z;UUH58A~;&s>qmW9XM_ZEjtvzaA3Ur_^pt0TOrhr{FMn!$ zDwvewaEOwtZ7xf^{eFgQ$cK>UDQGYi5WP`PYQ*<=^N7TO=UU|kP|@dbG@&BCv#20{ zzZe@5x0u>0tbrDRkC{j6dK>Yxb!4iH=gnb9$^M@nzYvf!d#70J}Y|r zI80oC#tcHq9`y>}O%WQO4@L%dE^su1(is-I56KCL!D$DJAJL--Xs#;TMJ}8-Zip0j zpaRNIN<-%mE_Pa#*mp>Gds=3wqvNMKLidr8s-M6+>FaE-b95vwT!HHYApcPLp&9#( zB)Fz_!789x@RX_^D_fg6%(EXwe0vP)z=1M?^HBCiL@nON25=o=P0t&J?S5`r7uF0p z{OhUkqTG>rh4{m^W=7XE<15gX{0jTvc%PN70lQtOuHSky`bC(@Fm>| z&-LT(AvS-k=G2-G8$3dQcbB`=ibCgiQv@%X(4Jd|9 z_U2(}_)#VZZeoY=H)`S{NDgCOeBf|V(zCmy>TYS}<1+}$(H6DbHTy4vH-t&*$S!5t znZXIxtR&cw!Ch=Zqgthq-bidYyVv-yCNl{ABg{$*;7omaB)#+?N5QTz2JznYX&>42 zpGe(<%$)?2Z?BKDUpsfxfz{6o6KC~zs#6!M7 zdvYqX((W8Cw3)a7y<52QRV37YFo%qAFWX^h18LpdNpk@lq92!F^}$`ijBW|M7yu@) zF(s5peojP{kyth($p2M4SO0Sp9PQ* zpJf?ZryGRX!u@_tp_;%4(SyctA++h^=fvLDwPnP2a+uB%7O=&f$dL!Y{e@|@ym`6_ z%$Y8-qXsEvSWubzZczCs_z2Q7bm=d}b7-@?&OkgtgM2JtOL%z;J@j5EJmyU0%j5+x zzKfb#5+&)!8W54Y@hCT;{uCtVLsAXn-=l?lWXjb_&+?e7rbQ3-9OS^Ra8n^1U}9tE zhz~@4#X82+AorwHU9(m`yUPP5{`q_G=UibvMnp(n;a9luq`Ua^Gkf_0SQWxRq+K+U z;-Tn|?$yDb%~@E_mv!^iwvro%YQCM1BCjp;6@Zu~Jf-#`48R+1`tB^v4Z?`y=c|VE zG)&v3`kuleBz3gftcd7lP1ua*l#Gm2tmsYOrH~&q$cL+KZ%T7GGp>E7CIx`Qn!=V3 zgwEAVbuzD7RD}RF0~$`^R_gb;};wVDrM}Wgdh3pTPmZ&|`?(l;K$WPDklg8PH)l zIEJw#w0Fju1VYun@_3#83dTd+7|#5T_(-51sl(7yaxO#jGAsAY(_|=&a$fv;Y2*O1 zjG!9125BSQ{^8;x`hl$a7?P^`-IsXj*`&=dz1#w4|Fw^&lOxg^ zUP+pXIlin?y#|ti8UV0zKh~PT!}#0PGZHs+qb3L0sz>L0G?Cil5b6*>^``y%DLp$p zB&`5UT;`XG4(hG6T-jvErQ;a3i1zPg%4a&k@8Tf!G!x6>2XonJ6|hh`WZmj_sU|}_ zC~KXb8*0Ej6t)=$(nCvKy&6ytOc@G_2)(R=(65!PUJisw1YwM4+CUYXyZ{iQ@L9Yc z(6-o&a^i;Q@X2$)+0ojRv|WV_X-1Knwu>>-AC$lhK%1M15Sp|oan>~7``;$-ya(L# zCXhjrW`Lo%Qw3N9fo1(_DfI~;c69sD8=YEM_kzez6nSl^sn&&xRBn`^(&|bwSRqqlMe1 zc*&z)qKspzfk->b`iU<`o2&2OdqjiJ7|zVtAu|T;ujvr42Hh^TKc1(uWr(SvM%aZ( zDl}O_q|cpvmC^+o?|`A8-)u@_&rUAowSYRHfIZ+rzTf)~?P2 z@eqWpK*=FcPn@6iTgxsVu>?mHyd}ZM*3xK%W|Vs^%zS+$7k_HqIMJ|z=dSwBT`JANs2vW`gxem=f zBjESsB9;j^v7ESr7Rhp@IJV<_`hA}jUGiLYr4V(&E$HRd$rYc0bI!cx(2oqrPqpd%rrdbGpUG{~3|;|}$5<}1a*J}> zU%!<44NBrpJ*_j4UeBs%uze;n1ywFgP3JnSbxWe=S`*;b0BDZ=5V)A_mc(7<;!ls} za{q*rx1tKQru9C*#gBWm1Ed_vxIQmt4?v~-Jy2t_5coC4{aT@u_Fi!$j2|nKejd4v zSf(X+>>-;T^%=d>KVHg_NY6B+##40M1h|H=q|fT*#n)IgdDiqWqbBd?R(gW+O1LwAZ>G%M@-6R| zEr|h&#s`#s-xD}}91t8VGB1{mG$c5U?ru48mv&32Sh zO9M~|UXELmbRL&VpRG}mOaUJh3Yr#I(3>>J!Vezp^*NG~oxi=3&vtcYb{oJ<#J*Cb z#tSr-l}{z8k7avLdi=f%%>_T+wqKRZ@?7lwnlGndZI%?h{mx+IkWYNyXFN8amIy;7 zj{Ur0EBa0c2S8h+8&E;+0VE|j)ma<#4D<}S??}kSciK%d^>)f+|Gw~iuyx|QKf2e+ z_2%W zH>7LmpJjqzT%+G57NsAJf7~NlKT4K@Ig%$lnZzW~_=iobWj(1*qXzvxY zU~*iH7+Ae*rQ?}-4lwk##^RkZAXe!pbV(&Y&~^Q3+wsjyg~sD=QjZ?lU{&1JEXVq- zIl!t_H#o{ITyHwHiI%Te4QlBpal?()H}xHs!LnldjvdMKC+`Vu85ufNK~T1}?+*aV z7qp-G`wf-va8c{a5>K{iy)eTm(7-`xeVcB?GDy7u-lFKBYg*e=!^zRYbA-Ip&(^2} z0QMXMNS*X#6g}O^CC~K1W()}zxD==jME2YI*?!C*4`7aisyU;eLH_jj-=U*l2_Vrx z+_`89mw3zxK@Q`dvZ8&n#I!BZVt6P%egILAQO<~eu*;`W-(GhBx6Q1h+4e>GQ+*5P z4xR~yqBR|cXj`)hk*HN?V7tcSKBRY-!qG_7P4qD{1PI6~ytQ48x+%3`nYQG>2oMpe znE;cEc&HqbeaWr5H!tm+Ng%)}_5K}OvSy-F8Z<+TAd?M&Xj#Keet2opt^5$ z2_8AcloU2VYnLd%fU%88R?!)+l{!GaG-kg1TjPgehU7}%G0GgUwn`PJ2b)pZfE-_F zOoJqDLA4Ovg|Xr;fF=Mc z?mtCK(DF)oycakJ=w0tCg(nRIZ$A}{czyc~GKO;1bw7Jqc<}RK!qXE}&yKjeHV!*g z2(1K*@|kMyEnv$AkRU5fWQ1K7W(26p)i;FK1=pS0MnZ6*;ORvT(MKw)KD1Z^mh9QVlD0Mx_r+0@a_c^(dXjkK*3GeRU@$ z7ojz?AFT;al_vKR0p=(E*|-HCe-6c<>I#rHx%N=m ziv4l!%xrn6mGbm0uZ2}qoD;SS?U|j#Z2+pp-Ll|%nZ0NmtA{^`tell=H+qmc$gOZ2 zL1J}_=A~dAlS+Y5v9gdazfYQbPb>vpWIDdTbfiOA{&L0r0j?_o)I9AQJ!iZ4XQ^88 zR<19n9q3MPbcl7f5p2`pcEe&(*1>NDm<#&k&wXv!Xij;qE{n{{sC!=(?k3+NK`_xd zUzfJ3!0PHYq1Da@F9a*>t4Jtj2YfE&5dT%D9_2tx4Nyi9qih3gi++x-_VD^0MVnF# zZOTPO2@vH37cwF#!U+u~1>hAB7fDU*3HDnOi z#gmaZjwo+GKb#lINhFMMsEg$(kwgb{PlxpgYmiNN>@<$mme;=p+u?rC60MU)7+bagnnRT89ztqO-#9quRjfv8>$ zOeg!>XM50_^q+r!ibv;#NIC8_`~;D#5GqF{ji0H&#az%(*T zR0wCOl0DyDgE<{qD`G@kDIOwM3MKg-pjDex*+uGY4fXL#_71l zxrft?y&iudD|zsln?g8!^+obRsnMS?VeiuwDmd6_UaKG6Ay~0|6>l6@KNZ$k;5d=i zgCCIXvi=I2pXAJlNW*Ar3 zysol5mIi;!05Q8y`EA&_JpPP_N*u{gVg2A3t!0zhfA)J%qL2|$QI9Cx)TgDKyhG$# zQE%t0DGahjw^heATVy>>A@_q=kulRlq(>qYtvBg5jZgG#78G197vHX#QUHY(L`Wg^ zew}NE`P!d!tvI$QdAKa=if)S};EVYnffav!p>?;f3R~XvR)SdZjY=KP6;*B-xctT6 z-QaH|^hBa+aD}q>KK$`ZNK?@J_>udS>94jWVPk3r^YL$|pY5#q3PEdX=D)5tFi7lB z@e|{J7L;*sZczP78%Ok*SBH>rxsvKUfQg^|z5{Uo@)$}8jWJm)27f~6q~V6BpfsuV zy#w{(N4^$+N>|pz2Nn%UI1xbE(uGp*c0-FnKg4=i9sXfCbg&YJ+xevUn#DiK|6{w; z0<6RHGk@>8au#a>Q;^zqTe9)Utr+z^8Ru6$T4K#X>M#R+5*is5ge1lqhXSs$jHTOq zVC+UlUz*?Y-wvbAc*z?9=wqjo;<=xFVT9O?eAsZvL0k9EgSXg0k!pqPniiN1_T*skh-@jr z8W{S7^JSHIdmEYZdq$B>#J`-lGJ<==Ivg)fiLt-u8B432CoTK_V$Q{IsmV>Q0@nF`6_8#8@K2jSU|$&a}RUHWB% zzPkwZ{}Om_*`5!R9g zZ_9M8Yr&uFC&p$)@e3tC#PuFZU!e)dR#_6D?Msfjwz_D4m|lcO;jBoEuK5%p(&VkS z?y}gC|2(c|oBfgXUy(M3-GHNgmghKO$~rjU`RYG$iVnX+mQ#5v_=sd^2yY=&v|D!S zCHz~Sg86D#kNf`dUDFelMSuTcxU`rKvM|&m2e-gI!3PXz9ot38WC<_tc)@xbp8w^2 zuhrz?-`D$QiSrQ#deTa}fH|KCquL7=_<2@2O{zZzr*}0}0(oim>9tW&Ed5#YmF}C%S?%(%J5B9(cP&iz6^LZL0+lkS-<=~qrzr=~AwBH6^CQsp7-xWTZhUh6 zX-_Q+XvSH|h&&kSvR~T?6i|2pP9=k z{rfQa1?nYZ{gq9E4V$@A-?@%Q#x9a(b!zGa~y)1;8@mqMGJ7|EdvmpSSb!`$Prz&Cp z4L5KgGmX5Vt<6%a+V1@TqiCb5yib+?3{-+@Qw_)8{dpZ*4_EYF?lek`+lk08RQ16a zFR@is9iNrps6dQ2(giyEk`fHog)-+PDzA^$lpo;BaWd~75}-5hIp|T4MQBR1`!iB{ zL9fU6#JE7EFv7R51!xkmI);NH7!AGr+JO`8MD1Z3pl)+SnFlHl!92`_PWzg1t%YL+foLP{5VlU=NXnmVeO(is8wU2 zUJnfIN>!{0`%9W`h*76@KgJeE{3HmjaZP?sI`}u|1(J&#)S?J;DZzBFvn?ZXvS1yC z7QDEf{wfl(&xa-*ZcA6%7s=xixyj&Bz#xugXeXk%ja7 zQN^KqI2d5v$>r89Z383KS%^$HT<{&#Ru20Td@7S*8o$qz5!vV%aQ{>AZ0|=FKE;Eu zwvH>*?`7%=4#SRGUqa1>3hQ@10ufc z<-kKt(5*)Ieg;yKKixafL^;c@3vzCWT>9phdab#e9NTd3w%~mrQ~1NUpGHMXUsx@; zl}5RFZ31Grf!}`X5;m3Ecj>y`%I(cI!{P^gL#uO^}$_31>Z+ z`}NVKDS0V@+2VDw=x(*2zysx$;;inbLF=IOG9tr{Gz`FV>iA#Cca-en3a?z`u*RI= zLe2WGm1%OQ@c7$oBC8kKz6E;5g~Q8G2l3uG>>W*aAjS;@?mI^~0PYVf^$@g|HHP+S z7@8FAi;@^?_*TphkvaB$v+~tq>#P<6ZoXgDb=-vGG!wQpf@h7>%HTuiph_vNL4_EH z6SwUS9P@N8dZ)m|g-ZMo%e4y@^0PZ>e8njg|&UG77u}DYiacvx3pq9eHfacFKcl zL2ltgQSDJaLGhX>X#;v!Xv|CZ%Ga)P*juqK&`BPyp8yG>2Taj~n?$OQ<3~!9oI)5$ zPtQW56^xr1r13_pw37h1Dqy8bM)OLw?Lkv=TySM?2OO{AH zY7c`Y3BoT*9@gGxYo-KQ@j#*jcJgqMozgZNnUQ#%7zhyXvTb-w*AczjH$f+arwCXP z1v5%BbK5?>SDv53&3>U(qp|8Kha~|`+eAVQ05vCani&El%D9u>Cz!j$uy`S)*Vn8r zrSqIc3Mm1#$FL81VOsw;KZ%vj{YMW`M_)o>GmX~yg6a0Z2CxMzYgW+=C_EneA+qs z*Q$y7Mhg6VWGQMn;9B=rJJ#m_H4KGrSefE-T2eKz|@ zpA9XRdBq-GTH=6E1^CE0H74buAb(R>*+o_Q0Dg1T!k(Fl^*59A))lBm7w7M9IJWNc zN|^=)eeH(DLKQziV4@6E8TEoyZc2t7fjmWv^Kq%e)G0@kE)jkPWfcZuxNqGO97R| zhUjJLKuYcqwHtiK&{A_EZd2%{wd>)==yy71g4eNVrHK=grjENFO!<$^(`X325rU9^OK-CmT7$N8_Dug{>XEyssA9kr5RuJr0>pd(8ZPI=n<)Xdsg4hZ`Hi(727c+s4F4*@x$jGW z52^4Az#kJ1?`cp*7~ae|Nzh0Fypl>d5h#0V(x5CM6TUZ(c0h;k|3;eY$LJ+QC5ev343{ z{tB_ICXWLT03TNP3qsldJTO3-QE#pJUn%^XR|Li?0mFttQ%%4kNPrOl!}0Krw?Jnb zUiQjd5U47MZ4sznHAM2#zxv?~+}-Zb?{CAP2M!W{m33DEBpY1;C@-+pUzZERHEpu2 z4}AdncC#(JVQS-UQ*xwnOwR!iDSQZcFYs5ug-Empv(O4@kg-eb%~MecI7$<+3=&`j zz%ZXm!1gx$haZo286m1^Bmu?)3U{e!2>u>$X{bOtfvp|-1BGvS4q4T4umSHlMd*yI zL7P+#7%uTuOMfwNA%87ozfLdp(V}NFU_@xEwS1bD&QW z1bdIg#}mLa3Wp&EOQyBBHj;xwl>WTu#;^rYAOTZo0+vAni~tzsgI{_b9PE?4{8gX{ zzBh<%0jvEEZ@IYEOCSQ$74&is&YYopYKQ(453&)B{{q}t1whyM`c(v_%Cu3AjAbML z6W3}DLG3_7AnV&5Lkyp!2#16o{j+3y^8-y`uZm1go)o{>8 zFA|1*0x3vmO;jdl?c<`rs!Bl7C%`od*4^s7r~=+fo%l`vJE;QFY!yCp?|-E9ERVaX?7_l+4CNYzAy~Pc{q-h z04Y@hx;h8tXNd&p@O;s+cjqt|=82EIpY*T?sm4}-p?q%!WxNNs`QtOx0LY47jq$H_ zBs@!fhIb5eBQ5<=Ek=vc%yk}dXx$1eS(EHQg)1Se4=YMx=+Z(!A1L%x=xG?cUKqnX z=%^0000bbVXQnWMOn=I&E)c wX=Zr$GnSIVSd*oul*rnykTHs~#HWx5gGjcfvQv#j zn-JNTMkYH7QI%^M zF6b8L2KEXuaV_DNN-dL16qd&Aq8D>$R2Gv(Gz@sV>tbTv#|@dk)^*&Ic`-V$^e*uM zM*YwCJ$pvpyjcT{G1|rCz4+iEMUaYtpV{^QKR>sZkC6~^*?-w-Fd^=))ZUFt$tI8c zOijML*!FjzW%y0V+tOocPW9hj^1qwz`Sa&~77kGaeM5e({5x=-&l5k=87>3mQTcd*3v)bS!GU4#B-o+a|+xmDkHZg6tzqEJCfi0a6)}Q{P_`X$_Ng zT&{&NA6t<3B9?6~OFSO+=H%!5bx9oV{%HqBnjk3hpN-M1!higy?QY zp$CFgJH!aOwAR)UHtp1vCeMr&ujzw7oY#LzIjtUo97 z%ekG^SOY(W=%$OkIC2&XA=aLIz0AI8J-&uZ9F~yiw4#}Ipn2@`oW#=y%xm`|&E(16Jrw`yi9yLr z1@9C*?#ia=N0|`>o!WlLWyfO?h$51XT2K~o%O98N`-6__ld716%w z=4qu^c`SN&(BQ`GDOF6;P<$?*rI_ZkzZ+wSxmT=LHzo4gHc@QN$~(Bn-C{?P<3XQt zHKV}heigqf#r{8^y5cxpl@ zCaY?dqT!kJO;!}$W7j8N>a~d@*4`^&bMvLg&TB`+|H5w$7U<|tuFA?174R*RusvXO z&Y3swQNGQ@^SG4c&2z{6`&E+%lD{r@@WtXZOJwejyw1yqCL*tdy_#>`ZPB}K+}WoG zuh`n>{>|rX+pi@Wl;@oBy+=5QOL^Gm=pFiTkG}r~SyH5;p1R=+(=tie5@o)US9;8! z95!GHd(LUh4f~$+{qp!r#%_x4&&)yXU25&!XV}*ZhQqfb2K7rzR^pw#f!N7wt!vsQ z+V9wg&nX8#9pGDEmuOpAc8lyj9LPM99=;C4XgYpd-~mC9Mf5(=l@*YCez7N_c#-1-GySKGStrtG~ zsso35CinFJnKrO=Zhsp?z4z>^eyMW5&bu=Sw=`^+%=2HKY#M*EbNu11931-b>M#oG zj!((WCJ2ZM4I6x`I2RBZp!L({eGzF2!!oXR zDQ)DiS7X>h?I8nIL~#}MajH&189~k*yu~iX5Byw z#Q2)+TWc%ywE1S({o9wrj%nN(WZwV{YF1>b+#-kdj$M}87zAQR?ea%xHpt|?or~m* zQQoLhThO`%cSoBP+w!fBj)*hlg$e8B(;wDWF(P)3Czbyk|M8_I?w)}3 z@?V|1|7z^I+k^XNekSFD2_liJ)(F{<$M> z*iz`}<-GHhm7}dI_F0NP)E5#E-Cu2)5TCR6WFOayY*p@>Gx$rAfen8SM-f~U74j{|g)^fakMjv9 z2Sm`zJIJ-qZv6fbS^kEObdf5Cmg#y(=8GwTzfv-Ba>AqUO^ughO1D_=jD$yO;~{C( zs-li;D{T%oy`z%ZhZg8=X0H__Z?vu@+E3UnA9L)=aYb^T^N)VrYe1yHzdx3nX_P9; zgQBY2jUoEvk6m>SH`ZQy;^O%Li%bU-CU)djU>pdOD1I zx^V%6l-H=E!)of}7dHnjnXf~wsp~9-b2lViE+{owFNU_RQb%;{o*KorN_R(^#lsG_ zMuO9BgqE*at-qau7JEgf|@1t zyP>29Ei|P`zs$lNQA+F6n`)-b(5lmK-Yzbtdx+I<9*8;=sx(<^z!hi29?#`Q9B;9;-}g2cnx^1Qr)Zoeu%hTidGf|oqZ zf4MQ4e_-HJ=kVzD<5)h_`ue*U^R!N&Y4N_HrXB5CnN5h2RX=uq?{GNU`hCZwj=)A8 z7gA*k;g&XH9k?QM%7rf_ZQg>pOdx(%g)EneSXr!YzJq3=&t|T9HBe$rIIl>)L+rzcWgl3qo?p0RP(%vfF+n8PU6n%DV=_+?gpl0OooWh{ z4iI_!_OJ-?r-;p&ROrN=<2um~w?v|?x=9nNs!dF&IP5y)=h>OeBi+mXjpmj7M+BKn zgZoJdBW0#b2pGoGLRVWKg#D$M0@cK3(Q1twA?u zAM5DD%$BL5)O9X0h^^`&isTYf=py?3_;8rw?Jz5W&+nx3p{*+F?pNZmU0&`g2QaMl zOVx_7_QWihKY9!cxa~J(*KU*`F&+%xNUhG+Kzv&g#?EU`%1TBAT zx&vB{78Fe1cyVGf>&fVHkDDX??osoiBk8jGCUS_s=lstoqoaslUESSFmZ;eB{C9iM z?|UHzV?04Ks(Jl$X2;onhLjlE`nEey<`)W_qE zK}#=vBel0B|6rre%4|X!hTaWQ9yiW#^WN<7xOh9Caj-Gz*r==w(w%s`53%=khgn8U zvz7%vCabBtS7*)~{cmC_)0gBW$J&$;un&XwnH#)EoT8oMzdj5&G_BoytyJ0lN94>V zvxWPoKSdS)V7Pv1-MnkkybQ^m6yo@fw61vP_U4pgTcl}q9bG;y;T-PP-FWN@vDh^O ze11b?!&=_xqi1PaTlkz}gKa>Qb9=hH{oWlk)|Vq*{PC#rQ)>|)GI;0QcqeGJdjGWm zo&Kp`Zz5NWH9tDEd&gvSbLY+6~OVe8q9f+zps&ZBJVL)zxh&29#)VqKeTXBq%VyH-Lb;qiV+|BIVv2Smm z9~1o0Lv!pLVVQ5N70u~;9xui4;I$^!H>W&Z#OfZ-5)x<))(mU+0LfcqIyq$G%t>J* z969+g7WMv6&2czdv$4-9#ixhkd}hdO*9%?C=j;{DB4Ruld%^Wz(9`ZsB?%oH??2d% z!!BKWso>taWaepfm_Yowv6f$^M(GX5XgMEoJ>yCQk^8HICduz!AJ$zd{N>Q7J^R9* z50Y)A`?fZn|8b#Z@nwo7Lv5I}_=$+dpC2rx1~qXSM?qj?0YmSP#C!+5j!+^(EnQPj zr&{mH^My!IVvg3j?*f*7(df7H7~wwBgG}sE zy`+_amZF%ium0@$`{gWRGlq&SCWBa`Qa_a=suX-fj{k}HR4%|T-&?Ta@{iSp-`5o$ zA6(pr4p;=pp@e=H>1Z1FNMF|I)&r($b-&~u;>yOba=ZH8{OUDDT=_^hiMB!hljpD< znXSL$lB!Pn zz0Lm?(s3TZb9{Uc&UCm+?EHX@9^hwge?KG;J3E9ns{C~X2=a`p(0@JXm=M}}MDCss zqWdaH{Y)M=Hy7sn`8L;IzCZDvU65f|VlSWVte%t2K{jfhU6NtMt@}`&dP`WYw>3`I zOr6Xkg-m>q32a;=z}jbQCW7TkgeX2I`v$xd$R7D=X_g$d+8DoQW9d*5uG?cLXEOV2E|%bDB zdldx!%gXe#M()|Fu}C*3(1HoQw(yWZ={2%z{VEYX3N9`or=*pt@OTXX?hFiVVO0KvCn(rZ_bVHEP7^>J}tdG zwQC}^>*5kWYYHL$^d5!BH&c~KazbWfTgI*1XWyx~& zxP$0&_fl9F#2WDU%HXy9N;xZz6E^v-!8#-QH_;s>7i3ACT_O8B^YZpk*8YCL$Jp1H zAf}O^N@gq{@4-j4vMhV>lqt|W=ferrCV&44Z8Bp<{y3qMo75nTR{Y3&oT4H~-Tln6dxseD?%uW3xwE^x(f_jBcYj)Y0kY$-DQtgr(p46v@!az) z#|WF0N@b2y{by!^yCy%aB-`&c7A^j`70auf?ti=Ba$*13Rjkv4qKFmMS9K1zorcza z>Lvfpt4HKiaJCmuD0STKY3p2TO0Ip%Rq(s|8OyQ)rsAW+3010-BMNZ#MyZb@co}J` zR7nyg#J2};unHOR=J8Llfx1oaDvR6I`!`z5N7haFMGFJv8OtpV&y@lfr#+36pQ&8~ozF6Iiz0S6qH4FK+i6%b&LPG9#&i{HE^V#FT z-<_J-o`r-&3V89_x9d_=TqQ|zem4H2R-6;>+A(}EAoRk2gSt%`4!;@rKkd@ebqlMo ze7RpSS=BzX>ydWhz(36$Z-`aG^3h<73C00N>cHJRZ4K-n%k#trGC*rG-RE&}P$-929x^n#bj#NUYOn~0>$B6GMY6}ZMyEk z#Ab;VznaneGRqmk_jckh4T*}QobOpFT+b>TUKaWlXfglxZ^wdipO0|aqZv^YHrODi z-L5E&r^Qw%jU`MGI7A@WW^31}8=f$DQ@Wa5`<$ylCseOOFC^DtIWc|H_zLP<4REORey z2Q6hdP~oS>gXMB(oU+e21`!0))yRx3fh}9CzX>r;^UGr?wz+ERy0|DR6I-0{3(qle z-HIxwwF{2}X0x}WHres_p}LHh>uWo6h3%x|u$_0`xC~Vk6|p$4Z5Kv)dbzXMvL*qc z%D3LOpB|6wv9qI6<1x~l`O&RCNzrkgGC(s`77fMDJ4eu3;|~iF+cX(BU-B#O(QLvZ zmW9EJ??7fw4P1(j+pO1*FTPqXfGRC)It$?RI7Xx&@utrn`aMi`h}{xYcN=ZuYCk%X zC35B~&*;PpY`9r3fa9y%sj0MmU!H=oUKiK!r{Q8{g58s-P2Od#XtCSQe%YH2L?EI5 zG;U`9tN~xK9@(aZNcX|<(?nrdujvNK+}9(DmsrUCJO_Q9n2=<2UE8KBNyV^5qk+E8 zqANFD0PqeP+9xOse6Z!_WUlNtLaS>4E#=GWbB=A;n1=<)flq=MuQD7F#lNB;o)cd8 zUymZm&zy1OM1b_Nq#VbuTG15YjS^BgCQRYR`iBp1|C$>)orfFvc=lKB!h5lpTZ@9q zjFR6nx22w0y_Cjpcf}kiEY)3*G{3qOEP^QC>U-kM1qHu4htpU!_^;o#6n;0t##qh^ zAlCNWGPAZ_Q{@82uU3h?n=~+3eZ78L=UN!sJKIv~hr?n-Cv}$wc`t$4Mt_ri*ZZ#u z1!O(`DS@6oJK>UAqW07aif&)RkbJ5==30wz?pZw-(7RT*7$$+p7!G_{?q;%i0m#9y znf%L?9zRtN0JHf;CcPqt7>aKWNV{rS^Ct;V3F8vJvDv%F+ z#GTG@TLPab#ylLK@K|Yp(D$-57G(+Z`ev5T?CQefc{-ru#co?#O65vQ)gE)d9c;)^ zS-&S&x~TA~sIrlM$<*LJvKQ*@%arHu+ zY@@cOHC;SmhQOFt?aq*iVIC+}Wiy~(eXt4nRj{b6<-@HztnJz2u{Fc&;IOLwJ#Qr*qf+L^H5Ab^R_|8 zO9MkUes`d9p$t%_*LUM7JBlO}+a5I5m2H#7MF#&7M{?Qn`12AZlc`M_34E1XLutv| zT~ZJy1NSf2&K1MiRU;uo5PkmcqB7%-Ad$F=9F|jpWz5_%BqvB;M2U7ZnjE$18?>qY z;VU(aVs>uyf0>?QIekHtg_Li2?1B8H$BXlF*H5i<hXs^q4eBZvc#$Py@VlT=NPuhXP^Ejr&!;wPs8M&N zM(W2i!M(7uuegxPrk@L~WBA%lRIfMrdFYb8lkLOBbUYnr?s?h^jSD_U&O17?bEffqj<%mV zF0aI)mWVHHkedVRqg5&Oc*>s^EG&bGP4C}H$EWEc26Y(lxfpA4(yTe5H%p*M?f zyc!ho;E*Oa^fMuYQ*#anEAIO?A-DiUE!}52;qHiAf^Fm0&Wlf<%Lk=T;yZuS%ASJ&lJa5|yT__zmur7`|4a7+R${%8VEu~I;MUg{~St=x&p_9P?fVIO@-QI2zr?D zjNpP5bA}br3Fcv`w}Za(re7Crw(4)!^uCdL($frj$nt>x_?v=P zdHY^Stt#9Z=Iz*AwUf!56BL5FpT@FO#NeI-P6RxaYF2MYnDH>@6;qH6K6nj>KmDm% zCBhGOGD336EEV%@nuYgz#o(YJk7j$iYfz~cb=UqFncwZJ={`RR}!^>Fp6J6jyxA@g0EN;5Of%W*a!;+xWD896VYJQ3Qk zhD>$`huc%L@Xh$kZ~OTBHj;vPoe8wk*+{H*wq3cZ>hNmae{Iy&haP(FI;7j&i{<0> z5#292F-hGK9nEX=>B}@@r;5S8RqX=&`eLTn&*Kltdx5?<-rv^H*n=_5Yg2M(426VG zeyuIr^U`e%u)RpiYWe&B8wEzU1PmG*b-_5N0wTRVk9%8o@&E9#f2|*#8z> z#7!{)&{w#~CVK^x3v^t2skdM}dSGTE{FYF|g(rF2V6bxWELJF!`Q${9VD4J^vtiQr zX7irIV(a(Q=oiZcd)L6ix0E1}>}l-U;Pj>^3q|HSq3CU3qQpf)BqziB5fC_?LWZ z3)j+9_S6D1;VLRDo?pEZ*qC3=mKTo7R^XX5}x-cbN0c(xiz5Z6&uOJf(n(l+^ z%$V(MyTh)$7=wb=zl=uvXza@|%!c@zKliqu=a*eo>7iJddMU^nUZ;P59-Vgk9g7vt z_h(DYq}-#RbNu(XTwlg$?)~C*{V&HscZ@c>0^4hk1c}e}!IlMo_&a*)2X*tgxxRE! z7?At3gRy+WyQ$BpP5x!8-i2S4kn?)!x^lQ&Z<%i1KEwk2#ML}>$2IBEpS?|2L#A50 zebZ!;MOI&L$Mb)aCVY#E>K_c1C?~9W1h9xJI9lME|Bglfh>5D|2b-DhKT?}$elu1P zG?e`G;1A;fEhF-*oncw}1Qoi!{@cFb)n~~I`xZ1{GM_#G`tVIH1CyGs!z`F4ON1qe%DD_ok9##eGG=!FE>-8 zwJU%|w+97re2dA&*C9*-9Q6|`hCUR#ms6XP^e;mCzO!8?!y|@y;}0ZQr%Gy8sQl}Q zqZUzNmggFN*fvn55(u!H*FxC)b-lMg#4~S{@T50iy>wRuu4!)m3ml?|R4Q%C|Hi?6^`LN4*^o&Ev%`lzzO-6D4Q^dZRuvnFaa;ayGcj8&J-Eu|GGfe`5?9F z9mEVp3MHRhQ}}%se;*8hmY?e)U0IL@3uE(-;QwW1?wWQt_V73TcpYv5p_;= zoc?dbU)$FB?c4$Wr0pl|m`Qj|zQ|(f>AWO={BkqZ?0>^4)Xjw~SJDleM?91d8kWp( z=w$UWdHw)s?NOOkAT0ql;J=BcVZR9~yar?T@7;bJUgYpUrxfbmb6ja{)f;mEDjL;s&HQWADeTQzvxCVw0*)&h}*zQO{(EEczP zTyY)Lq{0UZw5QGXB)tZ|m%9>D?TO4~-l!*z>Vf>KjG|Nx(FZ|V#J|V#c=PCYy_ye) z7t3&k=StxIm`nk{zz7IkM+S>4Q3CsfAv4m-I;F6Nys+mgEBpOPV2#jBZigU;kMwoBEE{*T;h0dHUf4U^_SNlGhpCAd z)_b>t$!z(jfx`#QFmH4V=toAxI^O*c=D}r{rY2OOzQ0825uc4+PTKo~jrJ34pvNg~xY)ZUJ*)*L{+61&5#f zR6;CT4P-jF{u9B*JeiwW&Y@mkK?G%PlqSt8Qkxd-D^(zgmyqPG8S{S$Sfq?YyOaBK zV^X_vtKfPLVAgViUGOZp41Yd7KV{bQl@1f66)7n>D@mn36!lrD2_WfG+ZwL5huC6G zuG6B|KhRminqtV_B7fYwS?*nAkQmG=Ij5RWMvdH`IG);dB1sIE3pqVVK-+4v=>uFI z0|@o3GNV9yWb>lb;NcB>^7HA(g7@=)9jCwIJWkt|#@;<*^-4_NoF_%N87!61bw)R_ zLiXbcAu)jDPcK9L8f3Xv^|R!cYhhrqy;VwIj84cD{-Dc^5HJ6cQ3hL+q6BSP({!%~ z^F3;CQwD66l60(wd5}Pi?OA>Lkd7$~kM5G&rol6gV@g|-#i9M8TN-r#SoupnlI4UU z9WywV=(@`Z*ZkSK5DW3~EKSVGwp(VU2?T6iaInC-0l@9}AHdB5Eu{IG%I)2JkLxoT z2c7zEKZIH9=&1W3Sn5&SKfL*|=UNKG6%Zx_@2SDju_{QQZ096(&pIdD+nzkcO4sS` zujGe9DOZDQ$4>mKrp#I&69756+aWfi`Yf-LwrAsY0>JI2=G3#f8ikcTi&SWDM;!n( z!wtQ8KU5q>j(7d}r~?bf{q?OY`@OY@U#n$<2yjUYzT9uQo@eY0()iO2yf6i%{n5jQ zvJ!{?CB=g~35G{(zf&ivMwKudsN>h(HshG8VnDIap)vpl7T85KGC6?_FT>jEob%Ru zh&{g{fQdQtB+3pL&_pcuRN?4Z{)KAcS-ONHVmb+ zc!w$`Qi_y1vu;qF=O}dMIL}{yAV~bID_JPmW}H{jUNWG#gU ztoK{;kTc1!r&jzdHK&_MNBTTSme)45W0IpN1aY) zVtqe&N)*|HX7294pc?b)49Dl^{?4(AZcxK3J0CeCVGCAOc1S_)FT9PYdTtKFaIGx1!mQkh*bP7tlW4z}`pk>|x)sgU_1m`aP8|rb%{QTRem=uY{v>GX4_orTSH)zOgRGQ|B82|g-7oQg6@PUuY zl*ZaVVS#zESCseZI>RZN+7>!HxE-APgo<&?&2mZcSc56GN6fwZ#bEQ!ZtB4$+FN5R zjT4D{eBi!uJ!|y732iORXXIsX)l7?qJvUqKJV6z8A?X&uEeH8Q^{^ngw+2avnZ|E$ zi7;ND`UC^qT4@aO8>vfScomaX)@Xrs^6TL#=~S*FFXJ9DW}R->p&hU`k_@FJQ>3Vv zx-CiR=ho@g9={4_PKIji0qaRjVSjQ@e8`9%te~Uvw@;|9U}A0WNkvB+0ib%#1uy5k z@pgM0KJ|<4gFOui;3j@FFeAj25s1Z2qh5Zj=b#9u#*MiQML}Y*ove)FjnvWFdqB?Z z)W-}+l4zyb1mXJK{6i3xgRVK+QL1tHN_t2|7sZcfB#Y*ndb5qGm(GUn^5eL=$Q*x| z7-g#&dBJHt%y{#~6vp*iWY)oZwlTpWQx!8{Som3)j4yBrRywdVhHp{^gZf$Xa9JX- zvmJ+@y}#W>Uh#462RN`g1$uDc<)igO7ddnp-ckVx zqeC8|z{7_Sy28#@VHz!FCQ|fP*_KNvX!^IxBz%pcuLRYp~Q%8U@ z2>GBq_TDQrR3h?6IX@v>ELMEF7|sDYpNkYtkxCt@zg4nQHtbvtP+zB#fA7xEUdXf( z*S#i$E8B=ebb^5rO`RvT`AO9LEFomVsq@z?cV9~Aow4rW+@+;2Nz2U-Pela>Ex;C{ zOOyx#SHe2Wqa;?DJs*-jYqa=!C1P~l(qOqWnTtv{QvU|>$OJkaBzwhzy?@k4$9DfS z@cak5G1gL~%CW{DaQrebO2&b<8f3p_|4AMm<%4;gYa{zTNy0?K_#C_C+Ppui`o~lM8gqMHuKeay*lQbWY6B0taX!Ozd1w&_ z^)Hq%%m3N9^zWL5$GM%wX)uV9-D$Ap*BTuSe7%#_X}|;F?CbZ6+j#!b;7O^6AEA+} zcCJ#L5EITre{{VyuO11xZfr|wx3>LMbp>9p87WHr32Pnh6`viJg+u1mofL)}n5M0~ z5t&SHDG_7|Y7sLWT|uGA8#yUO1Y*V~E#kUt7=FaKbE8|Vub(M>^sMctds1K{qyESR zwB*S{iGOhN@3%cRlmxJaDLgo7s}MzGS`=q&peAe5Sw@fF5Zo>8KK zsF)7pi+j^SfGIU8PMyT8pOow|zb=JPT0_!LPrn=%!}|EG&(&ApifunH?r?M9`v9kney!Hz^%=6*d?KUV`io`BUfH64pAr`$mqX`e>14m2P00&`q zYxw0Jb+?h}t^%5c=Goxzp8v!zH(l#L3j;0tY_eDnFihPyxJd|yFSF#~(bYx?*E~5J zAz3gvx#c7i>>CGJx~H%hC$jyhAdS3jLMOI1y%64bL+lDL6Gt<_wtd`CSUJ&H4!n`tq;$SX@qKtE)}V#Q zW43-h77>asqfBD5_V0nU{jk8vzU-WTh&%&}P5W#s`z{f>6%56m8|;(G$HyAWo_ z+Q!7j9zV>(w5F3DqU}mr-?vR-d_JE2@8OZJ;CXC+feB>9V_Q4ZV!e$|1B-FvJu-kr z@AJt(byzN#0G>#o5ko|Rq^pqyw#NN@I$ys^xj^D4t>-PUapVOou>FcujL#(hiU-GF zpSX9%72DzstR^vahTAp&d=7@ioH*pS*VPWKxRKBB0tTsl7`)k>eJ?P=6dJ`|)aQ&2 zY^d*hAZ-byu059GR2cIiO6{d|K04AOKDFCrTfNc&wpB{tbLKul;?w`wutBfA*&Hup|yk3-g z_R<>I0bfs$)fpymIKQ$^WslT1Ahdfi#c9i_02K!hvEpk#E&^>>2dYwW)*==Uc)Q5- zUjR+T`IlMXHfWGpCr`av{+VSgf^@eW6J#MK*5k-!r^S{OOMq>So3Enh^sSikfs1R% z5VeG2kIC!gQ82JAGFR}@{0G-zNLy)?gf^HWxpr#fUo24-4)l>>COC#Q4?x7=ZH@ab z5z^yCtd0L=9yNRa2*CIIzhD_X2cGId`XeQ)w?S(GT`g7=^hl9Y;;{?8dt7Au(Fc{oCqbQOJv#wpVHg8A|w8b^pG{T~!UnCs?%0V2y zGr5@{ENqNJ9-&GrMB7#~i2kOGv)2^>Rc_j^u<|l|z~Q7tWJL{1AV%gN#f@1E+goJD zUvF#8(e)6XfznQ|QD-bdKaaH+D2+v?e#D^EFKw}Welfj#;5nWYUY>5mFtFZRThu$j zSPPey>KfW>-aj#oD5`;mj8;EW#=eoqsoa$X5n>P&Y@kSq!QpjzK>##njNEJCO%M|? zE(vaP-Q6*yu$s&;Apq#P9>E?%g9kHI-}$d$#soV*N)N{&;g8yT;!qOA^o(j8i*Bk8 zhp5{O5dj7Z5s%94y*;uCkW8?%ZMX%NLPPHd5{NN1(@LoGioc6we<&glS@c0k#CY&q zT^;%2@U*x;1QAhT@E6m+x5x6)b6Du}hKp=g>4yFjAsB}2?{udvM|ivj=5@fKV3gmB zLtNDW@#t-WM3zYSv2sBR*yZr`1_UTgNhrV;uJC#ApZ*0-0qDH?K0#QtZ4+W>5Cj>r zf<(r(?69#=@u%)(YHRY7>p{I`;QoC+>Vze!3D6s3S zu-#zR(gt-26sxL^+|uG&U~yJ__9zy?{BFD)mgp5&HD z0EjrGS!j6(Uj|%hs-KL;4ZooN)3b6vSDxO<b|Y=n>V&Tn zI*q0A6g!PO>lc$6h!nnaIL=mxitzZ-V!{kMMPl%S&XV9ev)AQs+Vg{`dL6DB(rLWycwRf_GpeZyf1|NFB>CYAf*pZdmb=zJR{hb4> zl4>!D>Aackw=rzW1;gFTo8KpxRB&`Vua3LVK=&ETpz62KvEGC3s>sCN29dT~W7s7X zFS)~P3l#R+7;4snn&Wq8HLbWQfZm*z?Kz38;d?`E%PcTrL2%fEvI^n5%LMjII2Jc2 zQT{wP!W4F&K_-VJgZQHa1rc{UtQ0Ni^UdRY8PI1>djiTip6#^72Am%?7SPPxygvlO z$!uY;id%BP1WM7Q$^|$(H^*_+dU-Lq+|%G_Q_698jyk_Ie20e;j1o@5BA~6NVk!d+ zirJI5A|{uBJpbz1His!rm&bP&6%Yi4wQ!7<`w3bmj1t(hI&l)>fekZ=&GxTU@G{)M z0!ZW6d^Q5BrUK6N@K&49_rnY~2sSz~lsj|)79QjM@kz0HXgC0j#m!#~js_ocAHDV? zj#65wO!5Bd!yg?AZr!{p|1x$HSb-Db;)r4%Xn-=opSvqaK&+JpD-doYk+sto-b7VQ zW^*q(__CCM~lZ~I_{u02@>Uj#++ar1HyO? z`KfZGaCl&@DrJxF^_&(%-=B9{Fk}f3%V+FpI@@cCVIC z#X2E3`8(+nye;Y0n!^Nv*l94us&W!Et{zrl$u^Ea8vF+Rj-0W&YRG-uHQ znR2ZsOy>6(g)rU|!1nu2id4xW4!dm?a*tu5#mz%|LRt6Wh&`l?;belv%WYmbGqn># z5B(s4cwugn16e`@7cs;!tSGoV)B20A%z=hpYzzlPa73o#+5br;WCg9$g}m0yNeu4m z`{?6U7RrH;a1ZuW8B!6dkr?)jB(O0|>_Sd27CpV%6Ngr4K)`C-AaT?XK1%Bf(JYjz z;iumNS7(|&{bCq{o?da|V_*b{e*eMbJkRR^{}NDxsv>-q=ckY4qFIbgL5T0#=^=u) zLfo&$y&(J&%mq0eiv1@@B#%HCJ>3%yxd4UIiw+}}jOCCS(d2I1yD0HNHqMG)EoR>; z@k^XcLRk{sRK8#Ssk{yRqwuToJ>|r6JX1pgJcS>r%(0e!7`d)Xo|ionnD+SQP))}U zGHdIV1-s@gqQ5^W;TWHF-i9|6@FTo9u{e4a9_f0$*?$>GEdoCEaOQaoco(`R4s(%b zKitQY6KE7ojopeFnF?@4b&oC1qFZ1lHjEPZu8zW&F^xY+O;;->gYWmC_mV_IY48UI zt1lGrzvx|wqkKAtUA(+(lOfqT}zzL`OPYCas28T>o^ zfSwARQ_eU5QQl;midAxQ=LL}8lRi+godl{p_=eWm64Kc>?z{YB8Bi0|^D!wp(gleu zjx*N(+V(|zLHrFc+j8kY(&D0?95IcGK7f!w0PpVt?EDWL-?*Kmz%2&`z@<63q-;K< zbKe04g*%Wx?jIOi93FhqyHXuUS2qcQa$__)E}(4M?<=1mYW{dLToiwv)43#n0f$V& zL&!G1dfv18?bQIJDo*#ESiZC!yIBFvJ_=7(4jeZGpW<4Mn&|N9C=_P5GEe1N>*l;2 z4l5D&%e0+1Vki43nfA9H!6C*s&&OuBR{Vz;k>8CRQ&zMbIp;76-r|+8_{p*m(_kd} zYQl;-z0Ku|*c}rA#P}QW;^$Tjhib3y$o`~3toegS=*h-pQ(SV6CJH%G0jfE2u48WE zkud?y`j#$S``ok&$GzV!PrQ6&r)>XTnBR<>hI>K-997X1CkwJok%Vx?^Dra0vTsPv z%}yWHK$mXaprv?ildgFP@Hh8^1i?2)1VtM5lnca#T^J`sJuy^5=;2TC-_p(*Y8Z51 z^GLqRp)pm|Szj5)-7zVKWptBGN$N7nSfsOf7}~k%wu5ZbI`{IckNkSr`m!?8*ieJj z)8zhx@-INKYQRdVUVGAEv>9rfa2m2|(%1!Ou_gOL_Re$?HuPkb9Vsx=T6@7E5X+B> zpJ|=FbPeVpHUB3B9k=|&HUXPSCE~^xfU}HuTzVDLH~jd&b1yc))q5v-mlBrNm>0`@ zr}033Z|dApfC+^ZZiyv|-~64%Hi&;u$;I;_?qeJlm2`tTaFB4}C3>9xC0OgR7}OJ> zM^c}phX#F8MWHyzhB4T>7^Qe9+oU3F4^l^(npD;yG}csxzOg$NMUap>j#$=%G|R-{#eba({Z2xc2=x zRB2B(ROV%PcZu+yg0jDJX`vA^E3uS;VLC#_1pFWQZMwC0ov<+h+!s(KOuLhL3pP;>byK7A)wqii9!`DsF7bbAo|Cy z>1cs&J`{S!f_lGeUCfHj8c0`sUeA<1kI?o1Xu85zv=zK6|LUUR=W1EXp~ox6=w zmI|qIV#|_GE(S%qA2`B!g+;}QKnE@fBg6E{Q*a>K3=zW;iq(;m~l=*lk$*^)=>9&GVBY?pq1yiyfJ(xOHHI)ix5kbSTG8 z#Lx+$#)~wHpvHg%e%E^Jmyk3&;WkB0m_I6+wW&x1?YVkVq-`hgERhmKa`p&6Wm0C~ zF{kWD90q-NAhQg*1K)D=@bn8ZyGfzf;e;{DV`L_&$?ZJP$J$?-G>&@&X2#6|W2vTy z^xYkVO?;1Va)8|V{#SD|8!FLYWDU3Lg(RV}h+`0t9uVoFg@C&u2 zlw7lgi3W_XWfVf_w;BmONwKh~8Goo0ZkT;D>D%95A&cL{meav>&5BuILf4(P`gr$X zV%?|oK7;SuV_VYa{sgW3xH+nMX7gW!6wlL;@U!ZE_U+&9E#Xtg+};QP3XE4LLEWh1 z?<^U97*@RhQH~&-Fo?4;kCzD3_@Bf%&<^5Wqw(@gq(G~PamWG#KeIdFB;?aq4F8VB S90Mi?q3zc8RuvX>=Klk?^^WrZ literal 0 HcmV?d00001 diff --git a/src/Ryujinx.UI.Common/Resources/Logo_Twitter_Light.png b/src/Ryujinx.UI.Common/Resources/Logo_Twitter_Light.png new file mode 100644 index 0000000000000000000000000000000000000000..040ca16995674eb360877c7e9cfb2e5d797e7e8f GIT binary patch literal 19901 zcmX`Tc_7r^_dkATV~l;x9xa+dwn7UrBt>fM5*n!pAxgHG##YfjwroR@?3F!ghF&U5 zq>`dgp;D416#1R${rUd>cxCQ=-sipdoO|v$kFz{q=V*H@g-P-xgplyAo!jjY!ozQz zJ&_N3)-ji};g3Mj&V3;WX|3k{hY2e%2}MW+?b=Rt2+R8R?$60iXc>gm-7^+sK;9>ZDHr+(d9 zIHbBX<8jc60&Af|CJsTu@Yi%Lo&bM1E0|dLGbBjGz@N!@HvEg`&1k$oQ9olYVi806 zk!)^)SZKTX8;Z$>$XcUEEBwOxRt7FCXLl_v6fYiHK`H3U@i+Z`(CVoT7O^9ATi+xu z1ukc_oLMLQj+?0{&ob#Ui(ib1*+a=5oF2sWk$7vpI;;Ck9rJ6n80#vCRrZrv(@`w` zcOk^;%fWlg_t3J06hf6JLylqZnok3Lmf%GKitLhFE18tL8+=_Akp@L0N+$s$U4{TaS8b> z0TD*xzwxd|*FG`%!#tzbsV?%YN*xXImY!tW=~o_z{>9gCro)^IdBx{^VBsFH*Mt zld>63HV*bqH$6PcIa7mmV)aYzv5I77@O8`a#+e>WzL$a1es`+((`f;rJ&NQRDy-IB z-{TtOg2nce`dFDg*Mmy+F>lp?n9mN$$9r8Tn~jgO2m3W2zrAt(+oY$iZmZGGP{V=~ zMk802hN3ZOX0@~EBiTy_lNVavY*ocbhI{11IP124w0ngeQ0=;KPIvTIwy0{{(-wVc zkOd|3pxMaRGtLw1QX&q#m3a0}Wqz;7DeK6-QvGi+_Rb%8+>$SBV(^XVCs`(})N9al z`!%0gUG>A>>W}@(3%xi5j~FVgVFkP~q82}{}-X@7s+#^aq7n-7+o6Zt6xA!Gl&Jea(7M1S#t4WDTJZp!!G z6C0|P5!$Wtus&IL)b53-8P>(BykX&T`~CLXy2_Iy`bN&Ri?dHItCW;m?XZ9omyvTI z`pp@{-0&Mx?XN5(F$0$>YQ`$g1Lf-}_LbT~>T%V-tA&h=PL%q$UDqIvX7|bl--g}v zRZa=NJST(et83Sr>|Vt79yFK6Pe+&kTqw>50gIh-t(_8PT}B2!!ZQv8`Dd@Xbcah~ zhQF>(a8-vhG+_I0*mP$?OfAH|X0=0gZcf9oJSP49xa;fFBzW9g6qOY2@VoHLmnQA+ zMCS>@Da!B!Hx;gVge_l!5g7CmlXj6B+>TKg13^{C$G^eRAO&67R8 z?_cJ@K8CJXNpp8Cm||a!dmXi;x$D%)ex%-3N%xKJxTJ3)>Il3yGNU**Nl2JVda%AS zakYL%n_qU$ZEH6%(JUE+}wUEyqutBqQZzlo5G#BuC=3 z4br(hRlt1{XI#cP;-31IDL6KeGbL~Ar(LA~q-hW3yF-wYsGV&k!|twL?)Ij;9t6~? zYgkF!iIyUAN)m;)_*?%ib$ak}X=~i1<;W`!OKG4%baZ%KWZN~wHgOfEhq;QY+t;j6 zOqGe1uUVn_@ubz4n<+`sQbuqEf0|mIR5I&JF~FLdSoIHw#|CacGkaG$4_gp1#2y~1 zx$VoO@8B-ANlxY2rFt8vB2$yL!R6dIJZHcu?np#?r0o$=#mBPKEAIY~ubA+iSh$ft zq^#RovNpl9FAkv#e~z?^b9iTBy7p%RE45;&^@ym^&3a!ZJAWu(q(APk+L5=saczNn ztncStMs@A&ccndF3zRu0ACCzL;oiS!#_T?*Po>!g)vhfO7}eLmiA6h{-_PVaKClxk zvdr0EnQPY*y+|CXo?| z7|uiO9jlEQ(1IQ2#5T<5yU=LH0InG!Y6_#{YfKT>}z z?Gas1PLTWS{o;1leYZRGL3QMR=`u<;jc!_y^78+k)NGV~x;&Uedh?&+oZc-n3vSFq z{K}e1lUJuH66mGbPEd&%mGtrgx{O%ZceiIJt&ZL*5PWdyJTvB)-${$VhqrbWZ0)^; zIHUe~1!8pZ`r=J5 zUE3l_;}rMTh#^i#Db&G`u< zE4+&{ueUHiD(5;N9fB%`R(~OpFf!fwe&A9lv$Gv1R1pzLyWuRg+xX^3$M8F1GKjO9 zD8SfJs;`;ux3*81d4}={lVEAQtuoJB>eI>i6xLwlmstTml&Q7`$yfDsFzTUsL#HLz||epRn0CLt>nRD79Z| zq6nzBfHj!&Csg<|r{E2?b75`4iQZ}4Vjj~k-K?_(*VUG}5)H`!(>9nfYZ`wTuOZ@` zrC#bin$)Ui9o8;5VLLX0;cSezCDX}f3vr> zm~vy5*zS!v%T{TvlQavnW$q~qh$1HW5LUIAwIVn;H~R>y4B&AW(kq*GWM&$!(vF0p7y>lOu}E!(o8QH&wbw3nh(k6D(>n`Qst zpEo{6L>pHF1Jjo#E@E`9v>Uz9DwugAJiqs~G(opjoZq%c6R`z!Ta8sQNV2U(P&5ud zINmhNetnEzR9ptps`b)LwaLi-?Ouv|9d2;km@FG!9S<~K&#*=umd7oYP*0P2EtQ&l zd|ljRDbqURDm?tks>f>&$RV|>V+{*gCobh^kam=-rLTjv z8Rry<0;wzPt;*b|V-F@T85ecdy}X6vsHLRFnkeZZ$*^O>AAB21$mD4OmyT(%=6Vn> z18O@kM?%8L>{1#3eQ7syYuJQ;+m%!tQd3zIc2hYPD@|z6j3=1Mpx%6k`8UCcQ`00Z zVdQaY1Lu zxX>2t=5;DMw3jc8D@sM(7pSO}BaBLxT9#|hU$&-2gy|svlm|3avukH1WATkvYx+at z-;FCI;-&~cPmND4R}>~4z^$DgpVexW6~bZY;stkq#J6DS*6(k!E-L=|Zj~|!XsNEu zV!6|O`b&hL@n=6hFXKflHmKt6k-27nEUI}#dbE`_w2dFH{bS*hdm(FLF~2GEJQg=^ z@O3V02jvT9NIB6A#%AnB-+M0FfS;+q z8W~B#g|Mhk4U-VkO+$WM)#(SQbiZd|( z$yWGpfg$hH0y&99fwqA&`~0}m`pe@L*6rM8?>E-tXKG@PHnkgXND}%Fu|*56o9;v$ zHO<-Yr%FagMHB;66w_-T`VXxl)7}3*S*y2t(F~Vd-grk`1Xn5G?yS)jkNaa?zR~Sf zrlaeBkpiUB9i=TL`<^CWAR_mzZY`#3QTi$SZI)Scq3V)xxDDabAFRw1BQtF2`Ul-h z_u@Z0gx?EUOGfkyTJ1*6fP|Y}or-QmW37)t<1ahA zo9~v<1I=*)X|qM@KSni3ZH5Xwn-Zmc%O%lZ6`G;KIKN*mdX%q{>VAhP?NT~MxN=i2mJ`HW-X`r~hOKZ&t-!LYE>T1r97+r4*$Rt=QY z`3`CM^>2IKu4Df8@{8IeH^RXus-_{uF<$(p)d6js=6mwml@6WXJL7TH9r7-%nI~(4 zXT+qw0r1k)TJFhyEL(Cq4-NS(=*a9wmH2RjZ=VR&0axBeqd2{VmoO9Q_5hB)E6|*1*&h|S(kn}p5Xw)gGjz&ZPiD&) zuGQ25awSGCyyl<7F$ivH`|y4WMW-_F?mG6@`=w6gjq$O}0Aq3>{j<69)mcjG{FZ_f zA9An`R6kji)VEXm3~gvnTITy5hm2QMhdYnTxU>*-@_Ggr+KkbVvR}!nbib8-;-rD` z!j70eAHPI4Lw?D%r{%&j76q+)g+~M41?tSHZ86>fg`A6d_113&TZ^Ce#$pvjf2T-3 z0v&hgg&l_P)?GnH|Dye`=mXN!eU_U9@kbPhIiU|u(-oroXN1mX^u{u8NxR`Pr#l9) zjPDv6;<=k5(CDYDb01kfwspored}|(s)YPozZ4oM4(u4mtbYW0_QBi9@g}*_NyI%9(RCj^R$nmj2F4?LHNGIFZjJE}IS82>+r6%WYE<%N(aY*Kv)#oe`Odo}@BQT=gU4*X zD4HHSnCx7zN)Yvx*V+fd^u4%m}AoqAtnGQRpOs&r2aZ=rU2zmK2=*>m-~Q`f1+iR!j`%?Zn9*WgfJ zLvgRl`+`|Ehq1+J)-U;Ie&a4a*%aQB95pj0dzQf+$sQi#Q^Mk2oF|}uT|t5C^LO{t zIG=M^)I@LSUwuw@g2j@GS|(0 z(p&jNQ3H}?@aAt+l*pJf#JMu`k}f8!ay!>lk`4UzKLE<597_Vq-9QYkHbT3 zOyjAa)H2N-ZM1?o*?5XMvx#MAxM9Xsl=Muaj<2<#0r^~kcw6tS`Ki@3OekBX)nD#xl;^gjCi2C z@q0LN_}W+SV<0pYmNj9{)GTDF*CoIDPu3(YFgS0=>u?(@tEWRYbUWSFTdHvn-w}|@ z8!=Vt=R-Tr{KEOBCMKLdtE-%5_yyV9+8|V-0I{$Hx#r$Qd9%9nx)WQ2>8p-_SLK^P z5@4Rk_7xATn)Xiq*`aPh$3oPG9^Mj<&%4Ivn3}sz-z~K>Ix)srZ)b~8yPVz{Vm6-O z3|CuI!+FJ`trO*Us%T%4-G7HBwUK5(-(?jdi5}b~ycJ(#3bblYocP4+V$0w2Oymy` zsKfw4E*(d$zR;l`wDCTUetFyS>CpIQ|+ z>74v?=T>Z`TC*jp%e_qBqfI^2$v^XX?yi#O;5&koBlfYkh!~of$-F$|*MujG!T$Ws zqC=Lwk6Lzn36(bc@uPw75VPo8h_bQ3RH@dkj=HOaaXABL7LaDnl=Pl%@?bKPNadIs zZYZ9n?GP`(Qaq`t7H1vzr{P1hfp7JSyJ z&BzlF!pOb}q^x~aqw!gdtWzx!F#A6qJz9kLI^Pr+gl`EIg}2T#X?zZ7Fw${!g=`qn z%hncge5MQ*dC&IMSc%?Zp z(azahKr%k+X(Rb4-Hk#W1ZV2rqg|Bg#k zklU~<=MW9))7=QM4PGBs=4NcYCuht@Xe;zI9>w7_r1OmVu! z^gS5=Yz|}uPQqN#x|is#O-@pWLVcTmbz);5w!8kPG>6o@c7Ur^uQ^SOO3ifX;!&0E zX}(kX^yC~J!HrG|71h< zH`{)B2>B=jI*ZNOc=B`0J-x?6KlC1(t~5ZX^oi#A$=`CFXPpn>0RY5p|U7zGr6vcJpc;lga7}8WWy@Dbympk5i3##p!6%M<58ljbYat%*G;X3eh`q8p05Q}=cSm-^HTlckT=&=RBt)uzLP_A z>DBy%x1x;4>Bns559I*6jO7f-WOL!Y8g)|a(}T}8Y?6|P%uewY4HEMgj*iPob4*H5 z(W?$Q1+nh`x_Qfi1l<8zf6sgu+nxtAyi#b&S4nq7{!guW&GoX|fRt>P6rz7dH{)1c zP#fZ?Cy0_rjs%Xws5vfHY~Q+ZaSS7cNcx+SDF6OtkSeG9fl^$$dF;FAqVXw^ulo0F ze@am?DtfB9abC)+5v({*o>gTW`F>%M=K4Zbmg}~&G1jM-i-+vc#I=&#XKh{K0$YZi zba%u&#nECfQPv&^+qm6=dd04Rq?+Y)To;{+lrRgEHk-fKp})!SH6zk(iz1@$V=^n0 zUY*cIf?>Lbuj#${l|P2a4p|6UOG+GS( zViLonv9ategr{CADJMW?gnpFC3@lPdTV@O1(gSL^S8Pb{?7<3ZcJ{{Z?_(e#wLrT~ zxl1l3uP;g9Ps=VTLXe}3|AM&GfNE=)omvtuc}Gz9xt=3abDzf$=M$d5djz(Cv*L9@ zoO@6=+o&&iW&@%JGnuVK(nMY{8gfz>k-s`-%hJD&bfl?NFFaum?oMUpr4TN+-m4_B zTY*6$lH|R{xyEgLakZ~JR7{Ph40|7ral`9h8n=eo_UbCLx>LTmZH`20g}*Cx)k(@U zKI(|!zP-f{XpDx=x6()MiD=nbg)Rny1w{@+HdbGrT<}Re&*Wzr-hJ$NV2$ZgaNqA> zr<0f0{Y)#4y(at7b3v#52*4+lnx9X{6cnq(2>vSlokZ~IKOElFM0VsKRVwApwR^V( z)$4EI49;bHx%ye%(RIJ>|E`c>AMI-IGHIDRnCz)#RM%5edH0gRO1P8~QkvufR=byI z<8y3Z!@e*vt8p_CvGTFl*hOzY`5z~Te`q7REt5H-C`TmF;=M=4+7k!szs8o-=bdvt z9NYOh3xg_kHjq*et<*QY7W`#ttbT#t+)<@33UMBFkbRQb^j*Dcu_q2ch##JIJKO!v zaFS0RtD_1`ty0!J8c;dB(#Cxgs$D3LSB-ktI$0k_^psKC=yon>v*9=f4ee1UIkDzq zBEfko&9pl`xzHceE58O3oaV%bU|3M{n778Ie&EQ-a9r~(Cv9bhWw@Sm{ABRo0}jRF zW*JFhqs+LHH#F|K_nL1=fP}WwD)SdF*e3rh3-m^HEt)&$1J)0_r3V?;UZKm7l|M>_ zU-Pb3U+xRse`#aEl~1{PeJMa$40>C{rAqg`5&d0L!Vf9lO8N0{g*D{9oy&Fnev=A*o1r4v3D#6Ef5GiCVt?s(~l$Kj;BkN{20I*ksIPes7y5X<0^aVC&u6E=8mD zFt2Tf^W_$1rl;~oc*RCs%70&mbCAxzNrbFd=dX-lMgxDA=f0 z$QrnJ?vdU|XZuSpyL+o(cQxsPIoXTd~xQePdBq&VC1pu2) ze7=$SR*lMi1W`mtB5$YBFqu3C6veaI5{D(5$DeocUTdx2|3-~UwXm}a7i{kT*T?um zB&lDs+ajczbun55)|}pUKmM46lmofogo-9iA%AL^uVxpGR^hfUX!CDX=&5vPGOaqx zY@}qMvA!Yr^;;=>s~fr0R|k{#wpVqJ-N=PabYEoIow)z@W=<-T_To4l@yH;}(#Mlu^+3=*&bG@_F;`Q7p-PRjdu(7P3 zcnF&!HYl!y>h}?oYvLv&iNmW}!)V-D|9Au50UH#g$q~BDh(Ew}y@BV0_#9#%91GZU zKSJTiCI|XIYUuAG-bLu_{$E4=#BgC%$=vMO=`++?bJ4M8<_nS*KSHu@3(00TB1B0? za#HdV?{Vn2F1EYoE+(bhZTYu5$@`(Ww+2=;oelk0d2YfQsEl}gxNNV0;QN`WFaT?# zE*JXhoMM?LjedH>i*5UlN8ca6`8mhc1t+A;!rr~`g1uc=CHo9Kp>jKt;5s#R>Vm{ArKgVVtXEOg7XDURed&?J0Z?mz^qiSV;PYrHJ3q7)$4NNS(iWFEKLGFyQGb? z?CzEIFIro0KN?Y{g{FGry2>2nS+te`AdCLaLGpwtt#aFR9u**DN(P@};n4!cC;y&- zAE)G_Po8(<>KlDJoN@Frzp4l5H^2S$J^>)0l}DINk&T;gs4Vt-501t!Z+$%!^~=6@ z#U3r-@aTG@l3~ym47H`6o<)Ozb>wspeJ*w1QkY#aPb^bsp}1z?c6HeZdnn|w{U4Lm zp`rXZ{{6?Q!7WV?yXbGh8+66)yh!Qk_56f|*qJ0qY$=pm`vloy<4SCPsI4y}f|nky z59S%Y{H9hg^HRw-m(6xJ3>oIW`qnjGzU_&*Ea+m9&>A?PDM*bL%MU&Cbrm7)Y1?A< zVD658LV7%LVQjj1TC}YuK{8eZgBX!nsb#(!CzjV#lU3By_SKeXgDtGatSf*AN!klA{p&= zixoQ1wXMp{R?O<55m5L-?@8tAGJbHfps~Bl``*PWWvQKI&?p?cx6cyK(0%!8lWpF~PoS**A`PQ9Mk$Ze zM5PID9)o)f12=+P%t}aw1%rV3=XM=b;?*#rO7#sNw}9^HG%1$-7(g7hC#1o1on+8F zwSQh%SWxAB!oW|4k;xx=vM21d%lN7PHsN%1>t~0(RBkx<`tSnjKyN57$O0fUif`Ou zMT?L(O=$!2pBSv9f2g@>1#c(5RGCCvCZv55E`|?rFzDPL#H@*}I3T z)Wm^JO%9Sm?AQNX7ifKV2oyE_x1)RqDIt|YR>NtQZtDP(mr{cr`*>JU8j_aA>v^6E z%;%DM>0+=@6F*_y|Mn?br6iO;&Q>X4_ryvm9}}1@Y~w3DPA)HB<$o=yX#qY7d#iUQZ)HF3Ar4oNv_QPr7A7ga z$M|%%)5QJ?3r=189sWP+S#W@lTXn6*siB}KV6v*w8(R%huQ0J8unUyxy^Tvx5FpuE z<(d;A;>+8aHJ%VF2kDvlQ+v1`#9{OQO@yBoXx(5pPK{=ct@+mQKdR3x;BoSBIzsw9 zU6!137ALPu-gJ8iTo&X?-^OJm2$KHyES*g^UF{@=cjVm!(FV-}ReI^Fu}r;}lRQ5S zjBAQ7lX}6`R_x#Kx->SOrdzOaBKDGQ+H<4Z;8P&vJ8e?N?x6S9&-~tL}>5$`w@Ml(|@zIG7u-xC-e>MRPO2m*2QWS=n)o|2-2oOx`>AnPf?uNKngW)gN~7`zE^J(XbBLi!0-K&>XSMn1R;5v%V__c|Xn+AN?7z>7Qn9;H@hg?r zX=}sj5!#StDi@HxUpz;6ZV0w>?Dv6;3_d}7Tb(mgNewH_>f|Q4Ft&dRn5K>=NK#sa z=Ms4UoMYg#mA)2$wv1QD2Eq5jrvY1W)OdaBBsC`k)kdY?zy`+2tQpMh&ML8& z=PmfbmQ#)z7wt^`X%shka0ZH>k@5%+P>A1EmnNKi+9IrNX#)CZ$kst}lB`Zrm-Om* zdZ1i0Y}@aBFjiyXw*S6Zh;FDmG&4)Yny)?Yf>~YKpj7Gcm7lV%qUsKX*Cs~Z=l@^- zxsM<2I7m6b(e8gM=D-8cN%Oh|Cq{0D2}u*AHBMXbAUMOqjU?d0Po<9VU^s_)QAui? zJy`TFf#Tx zc?}(c+ZQyh+3>0^;0j3AW}lRlIziXc3^U=;&M^(>SVxxx{P4t+YIW4)2k0)|d}7`s z*5)o6RI0a~6~AiS;0_ZWQ0Lsq>`?2A=)NuMB_j0x0?&czzTM91DbH+ID<$a}UVAxY!kiH2|bA*0#qHvd4(M@|GFaU>8k?QGbCL zxWfea_8AgI1|Yn2xO5}&BTxTPBHRE)k#)!YubyXe0U%pxH2;r6;ky!7E`VSafqJvs zHyQkIl^vfh9N9W5S$R~Q#HVjqJ5>Ac*yN9^t)VM8NL?-w3~eX<=e-9+u=t-c`R@6` zK}xJGsk2=AJY1AAYgJ7DD)P|E!KzgQo4^bdomWlphm70_hAXT;yu0_K$csgDZh-+$x(Z#T$D=n$wFYl@$gs5#MO#JzlvD8Q4^ zC(rc=W&B1~$+{22MuvvYUBe?rb-jSBj+c3!y9uYp1&F*-_roJwCCsVwN%nsLWeJ3HSSfmFst6DMbuVtA0 zmPq<+bz84bRJHL0+_s{0Uu^MOGTZuaDmOag@MMQOA7bkj3lycY855n=YTpFG^c8Vz zh@R^J;&!rkFtG`%Ho|n+KszhT0DQHClul`Vx5|&Q(-RqRF=dL>*b$H@jVREesbvC1{|UjAW4!aa`CMIiN5P_ z4R^vVjQs&Zsy8?wP9ITN#iiX&^A8V zp5*0_5<1Y`RNsq}2IV<0=FpB=|2zVTrf2Tb@mn7@(E&=5`3AocF7)Gr$>I6F(bNVj zihFXja?-q-RG;H|AY=7yHTGt}&@x^|f~t*Kk#Q@%xm4n?(cG&`@+vG-dNgtPB2NGf zAeY!FE_Fh)70}Ux{X^m&X_G_wLL+~sx>$DC%zwA3F&C$+@R+@0Sj@N(2KDh}iuhaw zsIS|)I6VJD->7-tWwBASxWMJCTg$nDQybe6H{%We@?>Z3v`Mc2)D}=`)M5eND$(y3C2% z|8#fQO*fm$k78dgoM-CZ=DqZIYf!?>P75v@PR{+qy?-=2&FB_XjZJ>Oa-Mno-=J3+ zHm0@U&8WA|Z;ZQKLdr>w26I<=bB|&rc;OS#L1Vt?1m&8iKEZWhUe*byOf}%Eagj0$dxp0{wew#d`@o$oqhN^K& znIf`R&SM|s^OpI09E_#|Ppv2GRzxp+Is|r)b@HqbXr{qk6!$o>J=sEFTJMTn$%9Fp z{z0?@U=kSIPbbg*3D~k4^k7-riPwg_zB25Nn#9mxY z{F_sr$1QP{CD)OGwFSm;Cq*ei zY|lF|VH2M182>YDhVM<*R3me40*<{l?8XSHF(1b`Qv4*R51=kcTcYzkT-d_+4#wKi zit@(eZ#yR&gV>c6!J3^k(~~?n+Obep`iie+`#9!R5A0i~5&WB`=c1Sz8=C1W-hh?{ zKs9~AsQ=b1qqwtLb)XJnGUj(k;%Z=$BSJj6)%OWVCl0p1p;o>yZ%?J)s-SBk)WF=rWbyYHZA8%lQOFU8f zd4%b`sRL^$1@FX~Z{H`P?yA$On3v`>(`kUhyirG*;M&|jO%pBSKe4(sOv-Hjoe}~0 zB-G<+s#J_ST+*-1QxodQbQX%s-vMOVPXIJ;>I^j*s&TpP2Ot{wavaLKPD)61XZL(e zweBl_=Z+ZaO*WM5dVShDn6~}*cIa?V?Q}FPfSs1zJ?P!F17M!-r(fNtYjI!I$ZWsO zn&ffGn2=i-q~@)K*$5IQW@q(SHSU1WW;{yoPzVuV3m=ivVf~8$!LT}{RLTt1IYaCQ~{Wv#(119`Y~D#KZIi=_|1;B-l^f%UHl zHLGB-TFlS8X?6`k(cNm&1f6xu@u_xD*}Z=Z?!#4_DyGVYhHC%GS+v1tF+`^Tit)ZE z^tNyoP1y1&jeaLqWJw}U6YmolMe(Y=G$eRdcOvZ#VQohVhqI-UU;qdM0XTtwuWOJZopD^m{d#p%1Zb7Kc-5j_CL_)i`6|@m^R?*y?Gv zXCmSdrzV?$k{P%$r5T~1XhTfWz=Ga_Wv&;nASWBXp;`OzEgfX4?I9?@j$vYXDYAX= zMU1~?-SKD0^y8jmn;5!2#*Fo6b+-wtDkg|UpL&l>W4EQA-$h3-JYUwUTxSzWwJ~@C znXdNV#Na|P78Z*`Y;nW2YwChp=!#5i`ehD4%`9Pl9hDX#PcuqE39AP%*JC4c zA8f%0LHt!(3^Z&?xL*q<8LoyyQPmnnmW{yPfz<$W_O!2%g%2YMv^#g5j?2gzg;XGh z@V#LqCS|H|Fqwbt#<1Z+3{nV!Z^EP{U|&AFlaJRr$o8?|n3MF+^1cH0Vb$QF6ggT# z0&QSoKbr#c41#Lt1k%Gu*sFu}p#~eE01o zz~C1kF=AL5e35H6_T4kapoS1hvp#VNBq}b2YSlGXc;}t*#UY=5o7=#$m1p3?T?T(j zby8%A2@|>#FC&-pY~=FREvRz;--Tuh*0eeru6(^QCm#w*Tv^<+_a^DiuQ15rpB$C_ z`ykk$uw!#yhM;Ju@Gd-~3>MRX#r8YCl{u2fEsY<(O~ON|AZRLcOHwI(5AEQkwLN`l z14~7=XioCWY~H4``uBr zW!)Oo3$vUksnW~ZH%R0ltq==$t1tI5g^hZbH&fY`1;)c}_b~`3CV~8BV?W{}Amm}6 z17$@}i3#*=l_r=PT9r`WuUP7FC(w}@mcANRSOHx*mXjSy|LcN0I_k{y(}ziPn0~65 z%3bOt#*w4?h;9n73DjccE+&?}3w(qgUpieC-Ke9a;8#p+s@&x&$`}PxVn$pUhetx& zcOw4_=do-#KudNOlLPKJx(cjV0`*-saHHbHw0NU-z^H5(<;y37)zMMK=m@|jtD(wU zP!$M~kl-xWo|>~fwq_igc;66n?aWioA-VxpBlhs>P5ykFa0a+1$TCQksbLYMij$-c z%IBU6T8&HX_Sv|y%f7(%$HGUcoHy#3D+3>Pa)|_v%1D;Bm*=o$Ad!(FF2INlo&lVf zHN8twzcL9vO^|@D4Ysf;x5)X2t1xK8S_u?O&qtd!!52>|5~#%!ev|+2_d)nggx`~~ zJCV@Eb6EDNLLwSUbhP@ieGN)z-Hz#2;5|JgeGNZoEJ63#ErptNED+ke9ZTkjT4wp)W3-muCESY_*-QZeKKblfa&H`yJjft`QR#yj*}UtvifT}cg0>&6#aZVQ{xgrR>M-1 zabfcjgaRXWQ?irroIU{pC$PQ~q3Chlauc@*2TmVU7c{DmMM*st`nwkvc0Pya+)O7U z4^~Kkj1GKJvMtOEO%^*^9ksQppF7LAEkAbts!;s9Xm3?N zQ_7%7lIRJ+&ukRy#E6Kl5tC+DZlp*58i!54QumZCfM3~k3ib7Q4fqF*U%`Z^NO6Q7 z+3-1IG06YB>MCx}4cut6o_TtunwW#3+*v%T%pU#SA_;p?$U|yA>&5DxB2;tg`)_l4 zIcz}S+-klR9}xQXY0$2%-jdjnLPL@&V#w5cz#nzw(9mkN|7(bSBQwPB#sW64K7Sey zY>s@o9dz%r@GEv(R`4VChEuw`=tose#Yl8~@H6k@k2unas5^Vyt}SCsU^0t_a3ChW z(aox2h~wenN}MF+AwD%MH#}-vm9OGC zmOLb)S-#1?kpDs03RG!02t^~QGT24f9>0VjDbsZF=~l#O`t52}KY12ouv*IW(uXi^ z#uB*3NsrT4)%~o)7_5^r75#smnU33;1(qU}N_ptl&4%~gKe6a@+G#x6Dn?>^z4}fN zd@P9Ar+$f}Z#&71i;sevN2lLxATZv)u|(bCc_{R9$hLEvf{sKWBvSasvXWdd5J^Dy z;n=K0p~{i%W*DQ^4?|&k1BZ8R^f`nS-+SSi8b7e?{bra(RT;?|OTldsPDqC+di*8l zRU(RnW0bcPMD%XA)5J<|GfZ4vH5~N;BS|ECGscRN<{`|979e!EJ_{E=&UuePvRanH zC}NG2&Q>_Q7&woyM+;F#;CQqdH)X?x=S3EPxYc?meWdm9MW-@k--Y3(>T;e z63ttLqGr?(ed;=enPk^i;4G};W`=1J<3sdcPRCykMNf!g>7V1UzYcyUbI<7@bnjO+ zD_D0f5$VX7VYVji7wBzaC_xy{e7AVrFERS|IBboRjbPTJ$7OiLX^8P!c_uy{tzIoP zS$GyZJ9cj!A8H&{4cHj;U6r#_fME1=bFDr0@h%8l)|`R@e?tqT6JW*_`j5r=`SvE` za4h0P8n;SuFRUZ#M4NHrn#AxIze|F!|60H53R6QG;QYfGUIfD&w4;t^g^kp}XqL%;y>I`p?4}b%nig=4Wje8?yO!UIggBdNvS}U{V;)Op`pRr|ZsKiLuAE+VfxH-ulHidmR6!}Q2E{&o*Ot7~#Bx0IBXRlX zAQ5qHmK{9L*{05r`|b5&FxdX8HKp?z)8wNlghmPr>Zc}i?b^=mL^=l)_DYrZ2ftAT zPp8){Jj?^MVh$Sp{w_~t$t^T2!|u1<9KQh4u@e<11~i(Yp7Ok*FUxDjUbuvQertjl zKZ8b8(ZkPcq^cAx!R?8^pT4Rx5it&&kSAUJ2sqsQm+YCLw*A;4{p>_$Dz7ItSm_E! z7l+v07!Y`z`KXQzg2JO7aw6Rv?!Q&_S^ ze44T$>hJ5{PA$9r+UCCi-%!miIK-Ii{X!Q(OTWL7c8+;+^O%~vTi*dl3s7q1Q02YY zIOfdsrsn!_DGUnzC3a@0^#-2q%r8hNtK0Ll`7h5WF>X~ZvQ8plKDTkrsT$?>{!H!| zHHr!>2~}z*{b*W;8aAf?<9dE_Kg>K?a-WE=%@Ia_bn1BBxsT({EWv615<}hBxCzWd z!%(yAJ154n(=rl9zjZ#-y6;Ykq4e7oGSb-YTH83L6F&Ax3^&a)3M_D>r>!dDpMIRK ze0n|&ML_(c6&8XIJ zIr-Wg;oIf+Yk+1ZHN?6(k+&VrZsXR$U2Z3doFSVnQa6`dnKzDXrbXca-tPtTbYsH; zty=DgU=8X{Oq}1!y4-~M<@mMsvlN^>vm~Gq(oHAn5u|_ zrdSb;+D*h~+R#>phR7>AP##4?B|OCH!cZwws4!I#N-+a%trjKtpe<TrsU+R*!Dan^y-s@`&ephgDbRgXa|-p9%LX8HVfES-!Ywc_`#ITw zRL{ed_E9B}4R`KL(2TIGXV5|8ifne%EBuSOV>CewR(t8mShnKz--`R z*|PxHKf^C;Fq_0QL1gerpEuzA6OwSUDL81de`|Q?wWp^s+tETDepcn|(h4L0z?v9H z0_V2|FBLd`)ci`NNUulIqaMDsYW zYv*Cs{o-gQNi5VVj}ewTF|tZKI^A9iw>0%xK*BVtJ`j#{1z&*6ZK8&Aua$Py99?p-8Z zsSNW+8;fO30~qL{5Q+?-Ec`*F%2MQM~m|lWqz= zs(q1}&ibV+wSM>UTNSIAF>3;MZ$zL2YVoHAI&v;|)&+7=`PGIRN1rf+#^S^_+xC&F zH5Y7;;^_W0>0s;&?N>#VL|o&B#%@1Z)V6FHa5cS*sT$5j_KB0a{Oz{86W~6Q#me;h z^HhrOyC@__p6nKt0@bi7VrBEVv1|7W{sLNhSg|@q1i4?QU`pcEg-Ja6yP2;|Ld!1j zy_A{m^;ke_)er#-7nDvx5(@>9YZISqv?>gW9(+j3`6>HPmvB|4 z@*(6^Rlux*9~`;nxxgm_WftAptF2Z6BBwhOc5mqcbsAAvRN>VJdX%=h8c?>3BrMhU z4#-a7)9B`91Ly4o65-21+>E}-v`QH_&~VO! z*{&wZ^!pJag^8SMJu9><4TdbxYG9H94@Tp049f}uoOm7$JW@}qsX3mZ!D#R7Q0e^Y zh3ahtwgM!h=dnO!^}b}!p2d8vc~1y_y=RQ3=3nk*MHV5@?Ap_0SO|Y)vzNhVSCvyP z*|u#?0k4&CVz9p3nTd(=#Hf60;J)l4$0J25tj^@>&H+g^`oBowOZT*e{73W2GGj?T zS0umxSE~T4E6!Ve9M)N)NfM*0vWP7k<>`uftBUvP&{xMsD#wR%9SUZDyZ?DKLRkS6 za%%E?EzSCAQ~qq%(DD~aCuc+F8Uf7jUa7Rtomq>}Rz8>R{chzA%6qwdal^ekPfzTd zshs`<3h&kj1HXa5>>s-rZgh!^eR_)zSzknl#(;c)?6GVQsvrS#5F;?N4+StLT&aaH e4P?8Kwqc& + + + net8.0 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx.UI.Common/SystemInfo/LinuxSystemInfo.cs b/src/Ryujinx.UI.Common/SystemInfo/LinuxSystemInfo.cs new file mode 100644 index 00000000..c7fe05a0 --- /dev/null +++ b/src/Ryujinx.UI.Common/SystemInfo/LinuxSystemInfo.cs @@ -0,0 +1,85 @@ +using Ryujinx.Common.Logging; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Runtime.Versioning; + +namespace Ryujinx.UI.Common.SystemInfo +{ + [SupportedOSPlatform("linux")] + class LinuxSystemInfo : SystemInfo + { + internal LinuxSystemInfo() + { + string cpuName = GetCpuidCpuName(); + + if (cpuName == null) + { + var cpuDict = new Dictionary(StringComparer.Ordinal) + { + ["model name"] = null, + ["Processor"] = null, + ["Hardware"] = null, + }; + + ParseKeyValues("/proc/cpuinfo", cpuDict); + + cpuName = cpuDict["model name"] ?? cpuDict["Processor"] ?? cpuDict["Hardware"] ?? "Unknown"; + } + + var memDict = new Dictionary(StringComparer.Ordinal) + { + ["MemTotal"] = null, + ["MemAvailable"] = null, + }; + + ParseKeyValues("/proc/meminfo", memDict); + + // Entries are in KiB + ulong.TryParse(memDict["MemTotal"]?.Split(' ')[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong totalKiB); + ulong.TryParse(memDict["MemAvailable"]?.Split(' ')[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong availableKiB); + + CpuName = $"{cpuName} ; {LogicalCoreCount} logical"; + RamTotal = totalKiB * 1024; + RamAvailable = availableKiB * 1024; + } + + private static void ParseKeyValues(string filePath, Dictionary itemDict) + { + if (!File.Exists(filePath)) + { + Logger.Error?.Print(LogClass.Application, $"File \"{filePath}\" not found"); + + return; + } + + int count = itemDict.Count; + + using StreamReader file = new(filePath); + + string line; + while ((line = file.ReadLine()) != null) + { + string[] kvPair = line.Split(':', 2, StringSplitOptions.TrimEntries); + + if (kvPair.Length < 2) + { + continue; + } + + string key = kvPair[0]; + + if (itemDict.TryGetValue(key, out string value) && value == null) + { + itemDict[key] = kvPair[1]; + + if (--count <= 0) + { + break; + } + } + } + } + } +} diff --git a/src/Ryujinx.UI.Common/SystemInfo/MacOSSystemInfo.cs b/src/Ryujinx.UI.Common/SystemInfo/MacOSSystemInfo.cs new file mode 100644 index 00000000..36deaf35 --- /dev/null +++ b/src/Ryujinx.UI.Common/SystemInfo/MacOSSystemInfo.cs @@ -0,0 +1,164 @@ +using Ryujinx.Common.Logging; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text; + +namespace Ryujinx.UI.Common.SystemInfo +{ + [SupportedOSPlatform("macos")] + partial class MacOSSystemInfo : SystemInfo + { + internal MacOSSystemInfo() + { + if (SysctlByName("kern.osversion", out string buildRevision) != 0) + { + buildRevision = "Unknown Build"; + } + + OsDescription = $"macOS {Environment.OSVersion.Version} ({buildRevision}) ({RuntimeInformation.OSArchitecture})"; + + string cpuName = GetCpuidCpuName(); + + if (cpuName == null && SysctlByName("machdep.cpu.brand_string", out cpuName) != 0) + { + cpuName = "Unknown"; + } + + ulong totalRAM = 0; + + if (SysctlByName("hw.memsize", ref totalRAM) != 0) // Bytes + { + totalRAM = 0; + } + + CpuName = $"{cpuName} ; {LogicalCoreCount} logical"; + RamTotal = totalRAM; + RamAvailable = GetVMInfoAvailableMemory(); + } + + static ulong GetVMInfoAvailableMemory() + { + var port = mach_host_self(); + + uint pageSize = 0; + var result = host_page_size(port, ref pageSize); + + if (result != 0) + { + Logger.Error?.Print(LogClass.Application, $"Failed to query Available RAM. host_page_size() error = {result}"); + return 0; + } + + const int Flavor = 4; // HOST_VM_INFO64 + uint count = (uint)(Marshal.SizeOf() / sizeof(int)); // HOST_VM_INFO64_COUNT + VMStatistics64 stats = new(); + result = host_statistics64(port, Flavor, ref stats, ref count); + + if (result != 0) + { + Logger.Error?.Print(LogClass.Application, $"Failed to query Available RAM. host_statistics64() error = {result}"); + return 0; + } + + return (ulong)(stats.FreeCount + stats.InactiveCount) * pageSize; + } + + private const string SystemLibraryName = "libSystem.dylib"; + + [LibraryImport(SystemLibraryName, SetLastError = true)] + private static partial int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string name, IntPtr oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize); + + private static int SysctlByName(string name, IntPtr oldValue, ref ulong oldSize) + { + if (sysctlbyname(name, oldValue, ref oldSize, IntPtr.Zero, 0) == -1) + { + int err = Marshal.GetLastWin32Error(); + + Logger.Error?.Print(LogClass.Application, $"Cannot retrieve '{name}'. Error Code {err}"); + + return err; + } + + return 0; + } + + private static int SysctlByName(string name, ref T oldValue) + { + unsafe + { + ulong oldValueSize = (ulong)Unsafe.SizeOf(); + + return SysctlByName(name, (IntPtr)Unsafe.AsPointer(ref oldValue), ref oldValueSize); + } + } + + private static int SysctlByName(string name, out string oldValue) + { + oldValue = default; + + ulong strSize = 0; + + int res = SysctlByName(name, IntPtr.Zero, ref strSize); + + if (res == 0) + { + byte[] rawData = new byte[strSize]; + + unsafe + { + fixed (byte* rawDataPtr = rawData) + { + res = SysctlByName(name, (IntPtr)rawDataPtr, ref strSize); + } + + if (res == 0) + { + oldValue = Encoding.ASCII.GetString(rawData); + } + } + } + + return res; + } + + [LibraryImport(SystemLibraryName, SetLastError = true)] + private static partial uint mach_host_self(); + + [LibraryImport(SystemLibraryName, SetLastError = true)] + private static partial int host_page_size(uint host, ref uint out_page_size); + + [StructLayout(LayoutKind.Sequential, Pack = 8)] + struct VMStatistics64 + { + public uint FreeCount; + public uint ActiveCount; + public uint InactiveCount; + public uint WireCount; + public ulong ZeroFillCount; + public ulong Reactivations; + public ulong Pageins; + public ulong Pageouts; + public ulong Faults; + public ulong CowFaults; + public ulong Lookups; + public ulong Hits; + public ulong Purges; + public uint PurgeableCount; + public uint SpeculativeCount; + public ulong Decompressions; + public ulong Compressions; + public ulong Swapins; + public ulong Swapouts; + public uint CompressorPageCount; + public uint ThrottledCount; + public uint ExternalPageCount; + public uint InternalPageCount; + public ulong TotalUncompressedPagesInCompressor; + } + + [LibraryImport(SystemLibraryName, SetLastError = true)] + private static partial int host_statistics64(uint hostPriv, int hostFlavor, ref VMStatistics64 hostInfo64Out, ref uint hostInfo64OutCnt); + } +} diff --git a/src/Ryujinx.UI.Common/SystemInfo/SystemInfo.cs b/src/Ryujinx.UI.Common/SystemInfo/SystemInfo.cs new file mode 100644 index 00000000..38728b9c --- /dev/null +++ b/src/Ryujinx.UI.Common/SystemInfo/SystemInfo.cs @@ -0,0 +1,79 @@ +using Ryujinx.Common.Logging; +using Ryujinx.UI.Common.Helper; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.X86; +using System.Text; + +namespace Ryujinx.UI.Common.SystemInfo +{ + public class SystemInfo + { + public string OsDescription { get; protected set; } + public string CpuName { get; protected set; } + public ulong RamTotal { get; protected set; } + public ulong RamAvailable { get; protected set; } + protected static int LogicalCoreCount => Environment.ProcessorCount; + + protected SystemInfo() + { + OsDescription = $"{RuntimeInformation.OSDescription} ({RuntimeInformation.OSArchitecture})"; + CpuName = "Unknown"; + } + + private static string ToGBString(ulong bytesValue) => (bytesValue == 0) ? "Unknown" : ValueFormatUtils.FormatFileSize((long)bytesValue, ValueFormatUtils.FileSizeUnits.Gibibytes); + + public void Print() + { + Logger.Notice.Print(LogClass.Application, $"Operating System: {OsDescription}"); + Logger.Notice.Print(LogClass.Application, $"CPU: {CpuName}"); + Logger.Notice.Print(LogClass.Application, $"RAM: Total {ToGBString(RamTotal)} ; Available {ToGBString(RamAvailable)}"); + } + + public static SystemInfo Gather() + { + if (OperatingSystem.IsWindows()) + { + return new WindowsSystemInfo(); + } + else if (OperatingSystem.IsLinux()) + { + return new LinuxSystemInfo(); + } + else if (OperatingSystem.IsMacOS()) + { + return new MacOSSystemInfo(); + } + + Logger.Error?.Print(LogClass.Application, "SystemInfo unsupported on this platform"); + + return new SystemInfo(); + } + + // x86 exposes a 48 byte ASCII "CPU brand" string via CPUID leaves 0x80000002-0x80000004. + internal static string GetCpuidCpuName() + { + if (!X86Base.IsSupported) + { + return null; + } + + // Check if CPU supports the query + if ((uint)X86Base.CpuId(unchecked((int)0x80000000), 0).Eax < 0x80000004) + { + return null; + } + + int[] regs = new int[12]; + + for (uint i = 0; i < 3; ++i) + { + (regs[4 * i], regs[4 * i + 1], regs[4 * i + 2], regs[4 * i + 3]) = X86Base.CpuId((int)(0x80000002 + i), 0); + } + + string name = Encoding.ASCII.GetString(MemoryMarshal.Cast(regs)).Replace('\0', ' ').Trim(); + + return string.IsNullOrEmpty(name) ? null : name; + } + } +} diff --git a/src/Ryujinx.UI.Common/SystemInfo/WindowsSystemInfo.cs b/src/Ryujinx.UI.Common/SystemInfo/WindowsSystemInfo.cs new file mode 100644 index 00000000..bf49c2a6 --- /dev/null +++ b/src/Ryujinx.UI.Common/SystemInfo/WindowsSystemInfo.cs @@ -0,0 +1,87 @@ +using Ryujinx.Common.Logging; +using System; +using System.Management; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.UI.Common.SystemInfo +{ + [SupportedOSPlatform("windows")] + partial class WindowsSystemInfo : SystemInfo + { + internal WindowsSystemInfo() + { + CpuName = $"{GetCpuidCpuName() ?? GetCpuNameWMI()} ; {LogicalCoreCount} logical"; // WMI is very slow + (RamTotal, RamAvailable) = GetMemoryStats(); + } + + private static (ulong Total, ulong Available) GetMemoryStats() + { + MemoryStatusEx memStatus = new(); + if (GlobalMemoryStatusEx(ref memStatus)) + { + return (memStatus.TotalPhys, memStatus.AvailPhys); // Bytes + } + + Logger.Error?.Print(LogClass.Application, $"GlobalMemoryStatusEx failed. Error {Marshal.GetLastWin32Error():X}"); + + return (0, 0); + } + + private static string GetCpuNameWMI() + { + ManagementObjectCollection cpuObjs = GetWMIObjects("root\\CIMV2", "SELECT * FROM Win32_Processor"); + + if (cpuObjs != null) + { + foreach (var cpuObj in cpuObjs) + { + return cpuObj["Name"].ToString().Trim(); + } + } + + return Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER").Trim(); + } + + [StructLayout(LayoutKind.Sequential)] + private struct MemoryStatusEx + { + public uint Length; + public uint MemoryLoad; + public ulong TotalPhys; + public ulong AvailPhys; + public ulong TotalPageFile; + public ulong AvailPageFile; + public ulong TotalVirtual; + public ulong AvailVirtual; + public ulong AvailExtendedVirtual; + + public MemoryStatusEx() + { + Length = (uint)Marshal.SizeOf(); + } + } + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool GlobalMemoryStatusEx(ref MemoryStatusEx lpBuffer); + + private static ManagementObjectCollection GetWMIObjects(string scope, string query) + { + try + { + return new ManagementObjectSearcher(scope, query).Get(); + } + catch (PlatformNotSupportedException ex) + { + Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}"); + } + catch (COMException ex) + { + Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}"); + } + + return null; + } + } +} diff --git a/src/Ryujinx.UI.Common/UserError.cs b/src/Ryujinx.UI.Common/UserError.cs new file mode 100644 index 00000000..706971ef --- /dev/null +++ b/src/Ryujinx.UI.Common/UserError.cs @@ -0,0 +1,39 @@ +namespace Ryujinx.UI.Common +{ + /// + /// Represent a common error that could be reported to the user by the emulator. + /// + public enum UserError + { + /// + /// No error to report. + /// + Success = 0x0, + + /// + /// No keys are present. + /// + NoKeys = 0x1, + + /// + /// No firmware is installed. + /// + NoFirmware = 0x2, + + /// + /// Firmware parsing failed. + /// + /// Most likely related to keys. + FirmwareParsingFailed = 0x3, + + /// + /// No application was found at the given path. + /// + ApplicationNotFound = 0x4, + + /// + /// An unknown error. + /// + Unknown = 0xDEAD, + } +} diff --git a/src/Ryujinx.UI.LocaleGenerator/LocaleGenerator.cs b/src/Ryujinx.UI.LocaleGenerator/LocaleGenerator.cs new file mode 100644 index 00000000..bb4918d5 --- /dev/null +++ b/src/Ryujinx.UI.LocaleGenerator/LocaleGenerator.cs @@ -0,0 +1,33 @@ +using Microsoft.CodeAnalysis; +using System.Linq; +using System.Text; + +namespace Ryujinx.UI.LocaleGenerator +{ + [Generator] + public class LocaleGenerator : IIncrementalGenerator + { + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var englishLocaleFile = context.AdditionalTextsProvider.Where(static x => x.Path.EndsWith("en_US.json")); + + IncrementalValuesProvider contents = englishLocaleFile.Select((text, cancellationToken) => text.GetText(cancellationToken)!.ToString()); + + context.RegisterSourceOutput(contents, (spc, content) => + { + var lines = content.Split('\n').Where(x => x.Trim().StartsWith("\"")).Select(x => x.Split(':')[0].Trim().Replace("\"", "")); + StringBuilder enumSourceBuilder = new(); + enumSourceBuilder.AppendLine("namespace Ryujinx.Ava.Common.Locale;"); + enumSourceBuilder.AppendLine("internal enum LocaleKeys"); + enumSourceBuilder.AppendLine("{"); + foreach (var line in lines) + { + enumSourceBuilder.AppendLine($" {line},"); + } + enumSourceBuilder.AppendLine("}"); + + spc.AddSource("LocaleKeys", enumSourceBuilder.ToString()); + }); + } + } +} diff --git a/src/Ryujinx.UI.LocaleGenerator/Ryujinx.UI.LocaleGenerator.csproj b/src/Ryujinx.UI.LocaleGenerator/Ryujinx.UI.LocaleGenerator.csproj new file mode 100644 index 00000000..05cbc764 --- /dev/null +++ b/src/Ryujinx.UI.LocaleGenerator/Ryujinx.UI.LocaleGenerator.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.0 + enable + latest + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/src/Ryujinx/App.axaml b/src/Ryujinx/App.axaml new file mode 100644 index 00000000..eab318b7 --- /dev/null +++ b/src/Ryujinx/App.axaml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ryujinx/App.axaml.cs b/src/Ryujinx/App.axaml.cs new file mode 100644 index 00000000..24d8a70a --- /dev/null +++ b/src/Ryujinx/App.axaml.cs @@ -0,0 +1,145 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Avalonia.Platform; +using Avalonia.Styling; +using Avalonia.Threading; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using System; +using System.Diagnostics; + +namespace Ryujinx.Ava +{ + public class App : Application + { + public override void Initialize() + { + Name = $"Ryujinx {Program.Version}"; + + AvaloniaXamlLoader.Load(this); + + if (OperatingSystem.IsMacOS()) + { + Process.Start("/usr/bin/defaults", "write org.ryujinx.Ryujinx ApplePressAndHoldEnabled -bool false"); + } + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + + if (Program.PreviewerDetached) + { + ApplyConfiguredTheme(); + + ConfigurationState.Instance.UI.BaseStyle.Event += ThemeChanged_Event; + ConfigurationState.Instance.UI.CustomThemePath.Event += ThemeChanged_Event; + ConfigurationState.Instance.UI.EnableCustomTheme.Event += CustomThemeChanged_Event; + } + } + + private void CustomThemeChanged_Event(object sender, ReactiveEventArgs e) + { + ApplyConfiguredTheme(); + } + + private void ShowRestartDialog() + { +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + Dispatcher.UIThread.InvokeAsync(async () => + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogThemeRestartMessage], + LocaleManager.Instance[LocaleKeys.DialogThemeRestartSubMessage], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.DialogRestartRequiredMessage]); + + if (result == UserResult.Yes) + { + var path = Environment.ProcessPath; + var proc = Process.Start(path, CommandLineState.Arguments); + desktop.Shutdown(); + Environment.Exit(0); + } + } + }); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + } + + private void ThemeChanged_Event(object sender, ReactiveEventArgs e) + { + ApplyConfiguredTheme(); + } + + public void ApplyConfiguredTheme() + { + try + { + string baseStyle = ConfigurationState.Instance.UI.BaseStyle; + + if (string.IsNullOrWhiteSpace(baseStyle)) + { + ConfigurationState.Instance.UI.BaseStyle.Value = "Auto"; + + baseStyle = ConfigurationState.Instance.UI.BaseStyle; + } + + ThemeVariant systemTheme = DetectSystemTheme(); + + ThemeManager.OnThemeChanged(); + + RequestedThemeVariant = baseStyle switch + { + "Auto" => systemTheme, + "Light" => ThemeVariant.Light, + "Dark" => ThemeVariant.Dark, + _ => ThemeVariant.Default, + }; + } + catch (Exception) + { + Logger.Warning?.Print(LogClass.Application, "Failed to Apply Theme. A restart is needed to apply the selected theme"); + + ShowRestartDialog(); + } + } + + /// + /// Converts a PlatformThemeVariant value to the corresponding ThemeVariant value. + /// + public static ThemeVariant ConvertThemeVariant(PlatformThemeVariant platformThemeVariant) => + platformThemeVariant switch + { + PlatformThemeVariant.Dark => ThemeVariant.Dark, + PlatformThemeVariant.Light => ThemeVariant.Light, + _ => ThemeVariant.Default, + }; + + public static ThemeVariant DetectSystemTheme() + { + if (Application.Current is App app) + { + var colorValues = app.PlatformSettings.GetColorValues(); + + return ConvertThemeVariant(colorValues.ThemeVariant); + } + + return ThemeVariant.Default; + } + } +} diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs new file mode 100644 index 00000000..0db8ef41 --- /dev/null +++ b/src/Ryujinx/AppHost.cs @@ -0,0 +1,1280 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Input; +using Avalonia.Threading; +using LibHac.Tools.FsSystem; +using Ryujinx.Audio.Backends.Dummy; +using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SoundIo; +using Ryujinx.Audio.Integration; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.Renderer; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Multiplayer; +using Ryujinx.Common.Logging; +using Ryujinx.Common.SystemInterop; +using Ryujinx.Common.Utilities; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.OpenGL; +using Ryujinx.Graphics.Vulkan; +using Ryujinx.HLE; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.Input; +using Ryujinx.Input.HLE; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using Silk.NET.Vulkan; +using SkiaSharp; +using SPB.Graphics.Vulkan; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop; +using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing; +using InputManager = Ryujinx.Input.HLE.InputManager; +using IRenderer = Ryujinx.Graphics.GAL.IRenderer; +using Key = Ryujinx.Input.Key; +using MouseButton = Ryujinx.Input.MouseButton; +using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter; +using Size = Avalonia.Size; +using Switch = Ryujinx.HLE.Switch; + +namespace Ryujinx.Ava +{ + internal class AppHost + { + private const int CursorHideIdleTime = 5; // Hide Cursor seconds. + private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping. + private const int TargetFps = 60; + private const float VolumeDelta = 0.05f; + + private static readonly Cursor _invisibleCursor = new(StandardCursorType.None); + private readonly IntPtr _invisibleCursorWin; + private readonly IntPtr _defaultCursorWin; + + private readonly long _ticksPerFrame; + private readonly Stopwatch _chrono; + private long _ticks; + + private readonly AccountManager _accountManager; + private readonly UserChannelPersistence _userChannelPersistence; + private readonly InputManager _inputManager; + + private readonly MainWindowViewModel _viewModel; + private readonly IKeyboard _keyboardInterface; + private readonly TopLevel _topLevel; + public RendererHost RendererHost; + + private readonly GraphicsDebugLevel _glLogLevel; + private float _newVolume; + private KeyboardHotkeyState _prevHotkeyState; + + private long _lastCursorMoveTime; + private bool _isCursorInRenderer = true; + private bool _ignoreCursorState = false; + + private enum CursorStates + { + CursorIsHidden, + CursorIsVisible, + ForceChangeCursor + }; + + private CursorStates _cursorState = !ConfigurationState.Instance.Hid.EnableMouse.Value ? + CursorStates.CursorIsVisible : CursorStates.CursorIsHidden; + + private bool _isStopped; + private bool _isActive; + private bool _renderingStarted; + + private readonly ManualResetEvent _gpuDoneEvent; + + private IRenderer _renderer; + private readonly Thread _renderingThread; + private readonly CancellationTokenSource _gpuCancellationTokenSource; + private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; + + private bool _dialogShown; + private readonly bool _isFirmwareTitle; + + private readonly object _lockObject = new(); + + public event EventHandler AppExit; + public event EventHandler StatusInitEvent; + public event EventHandler StatusUpdatedEvent; + + public VirtualFileSystem VirtualFileSystem { get; } + public ContentManager ContentManager { get; } + public NpadManager NpadManager { get; } + public TouchScreenManager TouchScreenManager { get; } + public Switch Device { get; set; } + + public int Width { get; private set; } + public int Height { get; private set; } + public string ApplicationPath { get; private set; } + public ulong ApplicationId { get; private set; } + public bool ScreenshotRequested { get; set; } + + public AppHost( + RendererHost renderer, + InputManager inputManager, + string applicationPath, + ulong applicationId, + VirtualFileSystem virtualFileSystem, + ContentManager contentManager, + AccountManager accountManager, + UserChannelPersistence userChannelPersistence, + MainWindowViewModel viewmodel, + TopLevel topLevel) + { + _viewModel = viewmodel; + _inputManager = inputManager; + _accountManager = accountManager; + _userChannelPersistence = userChannelPersistence; + _renderingThread = new Thread(RenderLoop) { Name = "GUI.RenderThread" }; + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + _glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel; + _topLevel = topLevel; + + _inputManager.SetMouseDriver(new AvaloniaMouseDriver(_topLevel, renderer)); + + _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); + + NpadManager = _inputManager.CreateNpadManager(); + TouchScreenManager = _inputManager.CreateTouchScreenManager(); + ApplicationPath = applicationPath; + ApplicationId = applicationId; + VirtualFileSystem = virtualFileSystem; + ContentManager = contentManager; + + RendererHost = renderer; + + _chrono = new Stopwatch(); + _ticksPerFrame = Stopwatch.Frequency / TargetFps; + + if (ApplicationPath.StartsWith("@SystemContent")) + { + ApplicationPath = VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath); + + _isFirmwareTitle = true; + } + + ConfigurationState.Instance.HideCursor.Event += HideCursorState_Changed; + + _topLevel.PointerMoved += TopLevel_PointerEnteredOrMoved; + _topLevel.PointerEntered += TopLevel_PointerEnteredOrMoved; + _topLevel.PointerExited += TopLevel_PointerExited; + + if (OperatingSystem.IsWindows()) + { + _invisibleCursorWin = CreateEmptyCursor(); + _defaultCursorWin = CreateArrowCursor(); + } + + ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState; + ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState; + ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; + ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; + ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAntiAliasing; + ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter; + ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel; + ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough; + + ConfigurationState.Instance.System.EnableInternetAccess.Event += UpdateEnableInternetAccessState; + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState; + ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState; + + _gpuCancellationTokenSource = new CancellationTokenSource(); + _gpuDoneEvent = new ManualResetEvent(false); + } + + private void TopLevel_PointerEnteredOrMoved(object sender, PointerEventArgs e) + { + if (!_viewModel.IsActive) + { + _isCursorInRenderer = false; + _ignoreCursorState = false; + return; + } + + if (sender is MainWindow window) + { + if (ConfigurationState.Instance.HideCursor.Value == HideCursorMode.OnIdle) + { + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + } + + var point = e.GetCurrentPoint(window).Position; + var bounds = RendererHost.EmbeddedWindow.Bounds; + var windowYOffset = bounds.Y + window.MenuBarHeight; + var windowYLimit = (int)window.Bounds.Height - window.StatusBarHeight - 1; + + if (!_viewModel.ShowMenuAndStatusBar) + { + windowYOffset -= window.MenuBarHeight; + windowYLimit += window.StatusBarHeight + 1; + } + + _isCursorInRenderer = point.X >= bounds.X && + Math.Ceiling(point.X) <= (int)window.Bounds.Width && + point.Y >= windowYOffset && + point.Y <= windowYLimit && + !_viewModel.IsSubMenuOpen; + + _ignoreCursorState = false; + } + } + + private void TopLevel_PointerExited(object sender, PointerEventArgs e) + { + _isCursorInRenderer = false; + + if (sender is MainWindow window) + { + var point = e.GetCurrentPoint(window).Position; + var bounds = RendererHost.EmbeddedWindow.Bounds; + var windowYOffset = bounds.Y + window.MenuBarHeight; + var windowYLimit = (int)window.Bounds.Height - window.StatusBarHeight - 1; + + if (!_viewModel.ShowMenuAndStatusBar) + { + windowYOffset -= window.MenuBarHeight; + windowYLimit += window.StatusBarHeight + 1; + } + + _ignoreCursorState = (point.X == bounds.X || + Math.Ceiling(point.X) == (int)window.Bounds.Width) && + point.Y >= windowYOffset && + point.Y <= windowYLimit; + } + + _cursorState = CursorStates.ForceChangeCursor; + } + + private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs e) + { + _renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + _renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + } + + private void UpdateScalingFilter(object sender, ReactiveEventArgs e) + { + _renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + _renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + } + + private void UpdateColorSpacePassthrough(object sender, ReactiveEventArgs e) + { + _renderer.Window?.SetColorSpacePassthrough((bool)ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Value); + } + + private void ShowCursor() + { + Dispatcher.UIThread.Post(() => + { + _viewModel.Cursor = Cursor.Default; + + if (OperatingSystem.IsWindows()) + { + if (_cursorState != CursorStates.CursorIsHidden && !_ignoreCursorState) + { + SetCursor(_defaultCursorWin); + } + } + }); + + _cursorState = CursorStates.CursorIsVisible; + } + + private void HideCursor() + { + Dispatcher.UIThread.Post(() => + { + _viewModel.Cursor = _invisibleCursor; + + if (OperatingSystem.IsWindows()) + { + SetCursor(_invisibleCursorWin); + } + }); + + _cursorState = CursorStates.CursorIsHidden; + } + + private void SetRendererWindowSize(Size size) + { + if (_renderer != null) + { + double scale = _topLevel.RenderScaling; + + _renderer.Window?.SetSize((int)(size.Width * scale), (int)(size.Height * scale)); + } + } + + private void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e) + { + if (e.Data.Length > 0 && e.Height > 0 && e.Width > 0) + { + Task.Run(() => + { + lock (_lockObject) + { + string applicationName = Device.Processes.ActiveApplication.Name; + string sanitizedApplicationName = FileSystemUtils.SanitizeFileName(applicationName); + DateTime currentTime = DateTime.Now; + + string filename = $"{sanitizedApplicationName}_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; + + string directory = AppDataManager.Mode switch + { + AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => Path.Combine(AppDataManager.BaseDirPath, "screenshots"), + _ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx"), + }; + + string path = Path.Combine(directory, filename); + + try + { + Directory.CreateDirectory(directory); + } + catch (Exception ex) + { + Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {directory}. Error : {ex.GetType().Name}", "Screenshot"); + + return; + } + + var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888; + using SKBitmap bitmap = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul)); + + Marshal.Copy(e.Data, 0, bitmap.GetPixels(), e.Data.Length); + + using SKBitmap bitmapToSave = new SKBitmap(bitmap.Width, bitmap.Height); + using SKCanvas canvas = new SKCanvas(bitmapToSave); + + canvas.Clear(SKColors.Black); + + float scaleX = e.FlipX ? -1 : 1; + float scaleY = e.FlipY ? -1 : 1; + + var matrix = SKMatrix.CreateScale(scaleX, scaleY, bitmap.Width / 2f, bitmap.Height / 2f); + + canvas.SetMatrix(matrix); + canvas.DrawBitmap(bitmap, SKPoint.Empty); + + SaveBitmapAsPng(bitmapToSave, path); + + Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot"); + } + }); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Screenshot is empty. Size : {e.Data.Length} bytes. Resolution : {e.Width}x{e.Height}", "Screenshot"); + } + } + + private void SaveBitmapAsPng(SKBitmap bitmap, string path) + { + using var data = bitmap.Encode(SKEncodedImageFormat.Png, 100); + using var stream = File.OpenWrite(path); + + data.SaveTo(stream); + } + + public void Start() + { + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1); + } + + DisplaySleep.Prevent(); + + NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + TouchScreenManager.Initialize(Device); + + _viewModel.IsGameRunning = true; + + Dispatcher.UIThread.InvokeAsync(() => + { + _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device.Processes.ActiveApplication, Program.Version); + }); + + _viewModel.SetUiProgressHandlers(Device); + + RendererHost.BoundsChanged += Window_BoundsChanged; + + _isActive = true; + + _renderingThread.Start(); + + _viewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value; + + MainLoop(); + + Exit(); + } + + private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs args) + { + if (Device != null) + { + Device.Configuration.IgnoreMissingServices = args.NewValue; + } + } + + private void UpdateAspectRatioState(object sender, ReactiveEventArgs args) + { + if (Device != null) + { + Device.Configuration.AspectRatio = args.NewValue; + } + } + + private void UpdateAntiAliasing(object sender, ReactiveEventArgs e) + { + _renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue); + } + + private void UpdateDockedModeState(object sender, ReactiveEventArgs e) + { + Device?.System.ChangeDockedModeState(e.NewValue); + } + + private void UpdateAudioVolumeState(object sender, ReactiveEventArgs e) + { + Device?.SetVolume(e.NewValue); + + Dispatcher.UIThread.Post(() => + { + _viewModel.Volume = e.NewValue; + }); + } + + private void UpdateEnableInternetAccessState(object sender, ReactiveEventArgs e) + { + Device.Configuration.EnableInternetAccess = e.NewValue; + } + + private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs e) + { + Device.Configuration.MultiplayerLanInterfaceId = e.NewValue; + } + + private void UpdateMultiplayerModeState(object sender, ReactiveEventArgs e) + { + Device.Configuration.MultiplayerMode = e.NewValue; + } + + public void ToggleVSync() + { + Device.EnableDeviceVsync = !Device.EnableDeviceVsync; + _renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync); + } + + public void Stop() + { + _isActive = false; + } + + private void Exit() + { + (_keyboardInterface as AvaloniaKeyboard)?.Clear(); + + if (_isStopped) + { + return; + } + + _isStopped = true; + _isActive = false; + } + + public void DisposeContext() + { + Dispose(); + + _isActive = false; + + // NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose. + // We only need to wait for all commands submitted during the main gpu loop to be processed. + _gpuDoneEvent.WaitOne(); + _gpuDoneEvent.Dispose(); + + DisplaySleep.Restore(); + + NpadManager.Dispose(); + TouchScreenManager.Dispose(); + Device.Dispose(); + + DisposeGpu(); + + AppExit?.Invoke(this, EventArgs.Empty); + } + + private void Dispose() + { + if (Device.Processes != null) + { + MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText); + } + + ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; + ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState; + ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState; + ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter; + ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel; + ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAntiAliasing; + ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event -= UpdateColorSpacePassthrough; + + _topLevel.PointerMoved -= TopLevel_PointerEnteredOrMoved; + _topLevel.PointerEntered -= TopLevel_PointerEnteredOrMoved; + _topLevel.PointerExited -= TopLevel_PointerExited; + + _gpuCancellationTokenSource.Cancel(); + _gpuCancellationTokenSource.Dispose(); + + _chrono.Stop(); + } + + public void DisposeGpu() + { + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution?.Dispose(); + _windowsMultimediaTimerResolution = null; + } + + if (RendererHost.EmbeddedWindow is EmbeddedWindowOpenGL openGlWindow) + { + // Try to bind the OpenGL context before calling the shutdown event. + openGlWindow.MakeCurrent(false, false); + + Device.DisposeGpu(); + + // Unbind context and destroy everything. + openGlWindow.MakeCurrent(true, false); + } + else + { + Device.DisposeGpu(); + } + } + + private void HideCursorState_Changed(object sender, ReactiveEventArgs state) + { + if (state.NewValue == HideCursorMode.OnIdle) + { + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + } + + _cursorState = CursorStates.ForceChangeCursor; + } + + public async Task LoadGuestApplication() + { + InitializeSwitchInstance(); + MainWindow.UpdateGraphicsConfig(); + + SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); + + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + if (!SetupValidator.CanStartApplication(ContentManager, ApplicationPath, out UserError userError)) + { + { + if (SetupValidator.CanFixStartApplication(ContentManager, ApplicationPath, userError, out firmwareVersion)) + { + if (userError == UserError.NoFirmware) + { + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedMessage, firmwareVersion.VersionString), + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + ""); + + if (result != UserResult.Yes) + { + await UserErrorDialog.ShowUserErrorDialog(userError); + Device.Dispose(); + + return false; + } + } + + if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _)) + { + await UserErrorDialog.ShowUserErrorDialog(userError); + Device.Dispose(); + + return false; + } + + // Tell the user that we installed a firmware for them. + if (userError == UserError.NoFirmware) + { + firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); + + _viewModel.RefreshFirmwareStatus(); + + await ContentDialogHelper.CreateInfoDialog( + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstalledMessage, firmwareVersion.VersionString), + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage, firmwareVersion.VersionString), + LocaleManager.Instance[LocaleKeys.InputDialogOk], + "", + LocaleManager.Instance[LocaleKeys.RyujinxInfo]); + } + } + else + { + await UserErrorDialog.ShowUserErrorDialog(userError); + Device.Dispose(); + + return false; + } + } + } + } + + Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); + + if (_isFirmwareTitle) + { + Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); + + if (!Device.LoadNca(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + } + else if (Directory.Exists(ApplicationPath)) + { + string[] romFsFiles = Directory.GetFiles(ApplicationPath, "*.istorage"); + + if (romFsFiles.Length == 0) + { + romFsFiles = Directory.GetFiles(ApplicationPath, "*.romfs"); + } + + if (romFsFiles.Length > 0) + { + Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); + + if (!Device.LoadCart(ApplicationPath, romFsFiles[0])) + { + Device.Dispose(); + + return false; + } + } + else + { + Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); + + if (!Device.LoadCart(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + } + } + else if (File.Exists(ApplicationPath)) + { + switch (Path.GetExtension(ApplicationPath).ToLowerInvariant()) + { + case ".xci": + { + Logger.Info?.Print(LogClass.Application, "Loading as XCI."); + + if (!Device.LoadXci(ApplicationPath, ApplicationId)) + { + Device.Dispose(); + + return false; + } + + break; + } + case ".nca": + { + Logger.Info?.Print(LogClass.Application, "Loading as NCA."); + + if (!Device.LoadNca(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + + break; + } + case ".nsp": + case ".pfs0": + { + Logger.Info?.Print(LogClass.Application, "Loading as NSP."); + + if (!Device.LoadNsp(ApplicationPath, ApplicationId)) + { + Device.Dispose(); + + return false; + } + + break; + } + default: + { + Logger.Info?.Print(LogClass.Application, "Loading as homebrew."); + + try + { + if (!Device.LoadProgram(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + } + catch (ArgumentOutOfRangeException) + { + Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); + + Device.Dispose(); + + return false; + } + + break; + } + } + } + else + { + Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); + + Device.Dispose(); + + return false; + } + + DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name); + + ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata => + { + appMetadata.UpdatePreGame(); + }); + + return true; + } + + internal void Resume() + { + Device?.System.TogglePauseEmulation(false); + + _viewModel.IsPaused = false; + _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version); + Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed"); + } + + internal void Pause() + { + Device?.System.TogglePauseEmulation(true); + + _viewModel.IsPaused = true; + _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, LocaleManager.Instance[LocaleKeys.Paused]); + Logger.Info?.Print(LogClass.Emulation, "Emulation was paused"); + } + + private void InitializeSwitchInstance() + { + // Initialize KeySet. + VirtualFileSystem.ReloadKeySet(); + + // Initialize Renderer. + IRenderer renderer; + + if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan) + { + renderer = new VulkanRenderer( + Vk.GetApi(), + (RendererHost.EmbeddedWindow as EmbeddedWindowVulkan).CreateSurface, + VulkanHelper.GetRequiredInstanceExtensions, + ConfigurationState.Instance.Graphics.PreferredGpu.Value); + } + else + { + renderer = new OpenGLRenderer(); + } + + BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; + + var isGALThreaded = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); + if (isGALThreaded) + { + renderer = new ThreadedRenderer(renderer); + } + + Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {isGALThreaded}"); + + // Initialize Configuration. + var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB; + + HLEConfiguration configuration = new(VirtualFileSystem, + _viewModel.LibHacHorizonManager, + ContentManager, + _accountManager, + _userChannelPersistence, + renderer, + InitializeAudio(), + memoryConfiguration, + _viewModel.UiHandler, + (SystemLanguage)ConfigurationState.Instance.System.Language.Value, + (RegionCode)ConfigurationState.Instance.System.Region.Value, + ConfigurationState.Instance.Graphics.EnableVsync, + ConfigurationState.Instance.System.EnableDockedMode, + ConfigurationState.Instance.System.EnablePtc, + ConfigurationState.Instance.System.EnableInternetAccess, + ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, + ConfigurationState.Instance.System.FsGlobalAccessLogMode, + ConfigurationState.Instance.System.SystemTimeOffset, + ConfigurationState.Instance.System.TimeZone, + ConfigurationState.Instance.System.MemoryManagerMode, + ConfigurationState.Instance.System.IgnoreMissingServices, + ConfigurationState.Instance.Graphics.AspectRatio, + ConfigurationState.Instance.System.AudioVolume, + ConfigurationState.Instance.System.UseHypervisor, + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, + ConfigurationState.Instance.Multiplayer.Mode); + + Device = new Switch(configuration); + } + + private static IHardwareDeviceDriver InitializeAudio() + { + var availableBackends = new List + { + AudioBackend.SDL2, + AudioBackend.SoundIo, + AudioBackend.OpenAl, + AudioBackend.Dummy, + }; + + AudioBackend preferredBackend = ConfigurationState.Instance.System.AudioBackend.Value; + + for (int i = 0; i < availableBackends.Count; i++) + { + if (availableBackends[i] == preferredBackend) + { + availableBackends.RemoveAt(i); + availableBackends.Insert(0, preferredBackend); + break; + } + } + + static IHardwareDeviceDriver InitializeAudioBackend(AudioBackend backend, AudioBackend nextBackend) where T : IHardwareDeviceDriver, new() + { + if (T.IsSupported) + { + return new T(); + } + + Logger.Warning?.Print(LogClass.Audio, $"{backend} is not supported, falling back to {nextBackend}."); + + return null; + } + + IHardwareDeviceDriver deviceDriver = null; + + for (int i = 0; i < availableBackends.Count; i++) + { + AudioBackend currentBackend = availableBackends[i]; + AudioBackend nextBackend = i + 1 < availableBackends.Count ? availableBackends[i + 1] : AudioBackend.Dummy; + + deviceDriver = currentBackend switch + { + AudioBackend.SDL2 => InitializeAudioBackend(AudioBackend.SDL2, nextBackend), + AudioBackend.SoundIo => InitializeAudioBackend(AudioBackend.SoundIo, nextBackend), + AudioBackend.OpenAl => InitializeAudioBackend(AudioBackend.OpenAl, nextBackend), + _ => new DummyHardwareDeviceDriver(), + }; + + if (deviceDriver != null) + { + ConfigurationState.Instance.System.AudioBackend.Value = currentBackend; + break; + } + } + + MainWindowViewModel.SaveConfig(); + + return deviceDriver; + } + + private void Window_BoundsChanged(object sender, Size e) + { + Width = (int)e.Width; + Height = (int)e.Height; + + SetRendererWindowSize(e); + } + + private void MainLoop() + { + while (_isActive) + { + UpdateFrame(); + + // Polling becomes expensive if it's not slept. + Thread.Sleep(1); + } + } + + private void RenderLoop() + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (_viewModel.StartGamesInFullscreen) + { + _viewModel.WindowState = WindowState.FullScreen; + } + + if (_viewModel.WindowState == WindowState.FullScreen) + { + _viewModel.ShowMenuAndStatusBar = false; + } + }); + + _renderer = Device.Gpu.Renderer is ThreadedRenderer tr ? tr.BaseRenderer : Device.Gpu.Renderer; + + _renderer.ScreenCaptured += Renderer_ScreenCaptured; + + (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.InitializeBackgroundContext(_renderer); + + Device.Gpu.Renderer.Initialize(_glLogLevel); + + _renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)ConfigurationState.Instance.Graphics.AntiAliasing.Value); + _renderer?.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + _renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + _renderer?.Window?.SetColorSpacePassthrough(ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Value); + + Width = (int)RendererHost.Bounds.Width; + Height = (int)RendererHost.Bounds.Height; + + _renderer.Window.SetSize((int)(Width * _topLevel.RenderScaling), (int)(Height * _topLevel.RenderScaling)); + + _chrono.Start(); + + Device.Gpu.Renderer.RunLoop(() => + { + Device.Gpu.SetGpuThread(); + Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); + + _renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync); + + while (_isActive) + { + _ticks += _chrono.ElapsedTicks; + + _chrono.Restart(); + + if (Device.WaitFifo()) + { + Device.Statistics.RecordFifoStart(); + Device.ProcessFrame(); + Device.Statistics.RecordFifoEnd(); + } + + while (Device.ConsumeFrameAvailable()) + { + if (!_renderingStarted) + { + _renderingStarted = true; + _viewModel.SwitchToRenderer(false); + InitStatus(); + } + + Device.PresentFrame(() => (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers()); + } + + if (_ticks >= _ticksPerFrame) + { + UpdateStatus(); + } + } + + // Make sure all commands in the run loop are fully executed before leaving the loop. + if (Device.Gpu.Renderer is ThreadedRenderer threaded) + { + threaded.FlushThreadedCommands(); + } + + _gpuDoneEvent.Set(); + }); + + (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true); + } + + public void InitStatus() + { + StatusInitEvent?.Invoke(this, new StatusInitEventArgs( + ConfigurationState.Instance.Graphics.GraphicsBackend.Value switch + { + GraphicsBackend.Vulkan => "Vulkan", + GraphicsBackend.OpenGl => "OpenGL", + _ => throw new NotImplementedException() + }, + $"GPU: {_renderer.GetHardwareInfo().GpuDriver}")); + } + + public void UpdateStatus() + { + // Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued. + string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld]; + + if (GraphicsConfig.ResScale != 1) + { + dockedMode += $" ({GraphicsConfig.ResScale}x)"; + } + + StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( + Device.EnableDeviceVsync, + LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%", + dockedMode, + ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), + LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", + $"FIFO: {Device.Statistics.GetFifoPercent():00.00} %")); + } + + public async Task ShowExitPrompt() + { + bool shouldExit = !ConfigurationState.Instance.ShowConfirmExit; + if (!shouldExit) + { + if (_dialogShown) + { + return; + } + + _dialogShown = true; + + shouldExit = await ContentDialogHelper.CreateStopEmulationDialog(); + + _dialogShown = false; + } + + if (shouldExit) + { + Stop(); + } + } + + private bool UpdateFrame() + { + if (!_isActive) + { + return false; + } + + NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + + if (_viewModel.IsActive) + { + bool isCursorVisible = true; + + if (_isCursorInRenderer && !_viewModel.ShowLoadProgress) + { + if (ConfigurationState.Instance.Hid.EnableMouse.Value) + { + isCursorVisible = ConfigurationState.Instance.HideCursor.Value == HideCursorMode.Never; + } + else + { + isCursorVisible = ConfigurationState.Instance.HideCursor.Value == HideCursorMode.Never || + (ConfigurationState.Instance.HideCursor.Value == HideCursorMode.OnIdle && + Stopwatch.GetTimestamp() - _lastCursorMoveTime < CursorHideIdleTime * Stopwatch.Frequency); + } + } + + if (_cursorState != (isCursorVisible ? CursorStates.CursorIsVisible : CursorStates.CursorIsHidden)) + { + if (isCursorVisible) + { + ShowCursor(); + } + else + { + HideCursor(); + } + } + + Dispatcher.UIThread.Post(() => + { + if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen) + { + Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel(); + } + }); + + KeyboardHotkeyState currentHotkeyState = GetHotkeyState(); + + if (currentHotkeyState != _prevHotkeyState) + { + switch (currentHotkeyState) + { + case KeyboardHotkeyState.ToggleVSync: + ToggleVSync(); + break; + case KeyboardHotkeyState.Screenshot: + ScreenshotRequested = true; + break; + case KeyboardHotkeyState.ShowUI: + _viewModel.ShowMenuAndStatusBar = !_viewModel.ShowMenuAndStatusBar; + break; + case KeyboardHotkeyState.Pause: + if (_viewModel.IsPaused) + { + Resume(); + } + else + { + Pause(); + } + break; + case KeyboardHotkeyState.ToggleMute: + if (Device.IsAudioMuted()) + { + Device.SetVolume(_viewModel.VolumeBeforeMute); + } + else + { + _viewModel.VolumeBeforeMute = Device.GetVolume(); + Device.SetVolume(0); + } + + _viewModel.Volume = Device.GetVolume(); + break; + case KeyboardHotkeyState.ResScaleUp: + GraphicsConfig.ResScale = GraphicsConfig.ResScale % MaxResolutionScale + 1; + break; + case KeyboardHotkeyState.ResScaleDown: + GraphicsConfig.ResScale = + (MaxResolutionScale + GraphicsConfig.ResScale - 2) % MaxResolutionScale + 1; + break; + case KeyboardHotkeyState.VolumeUp: + _newVolume = MathF.Round((Device.GetVolume() + VolumeDelta), 2); + Device.SetVolume(_newVolume); + + _viewModel.Volume = Device.GetVolume(); + break; + case KeyboardHotkeyState.VolumeDown: + _newVolume = MathF.Round((Device.GetVolume() - VolumeDelta), 2); + Device.SetVolume(_newVolume); + + _viewModel.Volume = Device.GetVolume(); + break; + case KeyboardHotkeyState.None: + (_keyboardInterface as AvaloniaKeyboard).Clear(); + break; + } + } + + _prevHotkeyState = currentHotkeyState; + + if (ScreenshotRequested) + { + ScreenshotRequested = false; + _renderer.Screenshot(); + } + } + + // Touchscreen. + bool hasTouch = false; + + if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse.Value) + { + hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + } + + if (!hasTouch) + { + Device.Hid.Touchscreen.Update(); + } + + Device.Hid.DebugPad.Update(); + + return true; + } + + private KeyboardHotkeyState GetHotkeyState() + { + KeyboardHotkeyState state = KeyboardHotkeyState.None; + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync)) + { + state = KeyboardHotkeyState.ToggleVSync; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot)) + { + state = KeyboardHotkeyState.Screenshot; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI)) + { + state = KeyboardHotkeyState.ShowUI; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause)) + { + state = KeyboardHotkeyState.Pause; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleMute)) + { + state = KeyboardHotkeyState.ToggleMute; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleUp)) + { + state = KeyboardHotkeyState.ResScaleUp; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleDown)) + { + state = KeyboardHotkeyState.ResScaleDown; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeUp)) + { + state = KeyboardHotkeyState.VolumeUp; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeDown)) + { + state = KeyboardHotkeyState.VolumeDown; + } + + return state; + } + } +} diff --git a/src/Ryujinx/Assets/Fonts/SegoeFluentIcons.ttf b/src/Ryujinx/Assets/Fonts/SegoeFluentIcons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8f05a4bbc13461ba7144efa9c0d3f38634de1283 GIT binary patch literal 408752 zcmeEv2Ygh;_Wzl=Gn-y^lWdaRWK(w&2qYmL2oQQN(gZ{Zy%!N_qOORDh=_oQh=>gp z8#YAcVMG)}EYGgb0*I6lARtXhmj8F|?k1ZM_33-R&#(Mn;M{3v&YU@O=1jRWcZm^E z2sufkJ{|gX?R@*~Bd3zV8b_40s&~JvoX!_!w zC|iycf0--xBGUtIm+z&q+=RS=j5GaD5e(Oh{(S;A)Jh-3k5(LDJzeMD3 zOdFM#tCqo^iuVC0{J#eZx2Xr`1)qGrH0t})J@{cBd_^BP?4N|+?)ljjs%1Et!U~wjCAg0Pl&e6cDR-Y+ zHr{%(5}$NMN8u=N)^ok_f0X8fr+Cu&LYz();VbH*;*efb-h)1V>G1={ONS80TyF5@ zVW_8q8aiH@!iSbBa@u%InPoc?yLO zsNvs}?v+Cw`Fr#E=27^bu&=A~>3KWAo#f%si|cifPt`>)+v=gC!id*tm8pIjYN1ku z5$^`p14`5thVxdK;B&A7OW`K#_#PacUYVOlO}nZz@}UiXO`iIG;HrFHsE=2}pUPJS z0DcXqI4`L9+t9z1tKxj!5}&v-l!Y{%Zyy{^9vtQNar09{+c(X}UxzoJcy~Vjp>lP8 zN__b7#e+_$KI^SJ%SZTCfWj&AxW4$ley1m0xn4igRXy~&!S(VleEc=IPES7Np0CB# z^Qbsap8B*@ew|-0jy__!@qJ}jUpDo_iavvVQlMyZO9}g$XYFUO9W~<#iPeWt;Uf;HQP|JT-MuX(fsVpQQ3|3Rz5~jRYX9AnXuP zL_fV8m7gMJ5g9KLnPw1ST&8gJ8&hAvLLH~fO`mRs>#4h!mm1gU!2vz31b&1SKLW0f z<8=|Ic+>0V8>iwGs5mcp<9^Qt?&Zs#yb8}dE-63KYWPFgD>o%OG2fpg+=g_eCXdSR z1@fV%^hd?3H~{PI2Ho$8^QP70_u?xTpxZx%tNIgMy$&i(#d$%=Is~}huu50p2}`&J zgoTO&9aXQIv0Bkpai#U*$eo|xgZR$@UiUhWUxgJs?h}SzFY{OM($VAh(4j1!a0dL~ z!OP!&yQ_F9`)!^?u!mQLr|dLc2}*}``2)x6dh6(`I||P?zm9v0JFcev>f!j}d;PvN zbv>z{rvx;;;7LXd;nzY}rw@VsTH{9gPze0%A@)p31X9ly+{jtbYCPCoJQ^D=-Z zP3H}M9aqJ9;%i*;$?HuYK=VxSBra#qmw^#wi!^dVRg= z9+$O8JmxDkWhmU=f){{$HPSIAs4+rQeZ%AT%3PJ;B3r@tVG!>`h(-8RL`}l>|kG8Bs^CnN& z02iR@!4f@Xs63CQQ-Ry544Ch=)DaNW}E_b^-b1bPp{h+eHAS*Is&_a;OvHe*$>o^}L%r zcnZgh|IfI<|1G}j(5a7yuwFNmQNy3d@1^G(Uxya(>W6*v)X(Ql)8*{R+fdwZ(e%dG z&~?_UGs23tuGjTkZ`;;)^*-;(=bP3LjT&6Kvff_abQ+#?3A^B(6KSQ$F_8j>Yt(tO>r^a939;mQ_Cp|c~`{1g4psDiK$nmT@Tr)P5)x-DZK|ZPc z3QF8=%{b_dhpW>?y4UrV=asLwT-|N%!E=IMeE_YRIyU6@;`^p;{)0Texc^i936KHm zQXlj&1U!10D96)o zdKhKuuJ0NgarN8XTUJfk-m%YvTNAH%DDla!!T^pq)gP4KJ9ns)N2LL3;yk!sz1GY3 z#Od_N2N!-l-PbLtOV3-TFJ4VqzWIL7@2p#n-p2LfYUK8Nbi8RkdHJnwe(UG2Q-5zh z3PgSJhkb1Twya1*Vdc%YaaFx-hzX&@eZl);_x0U(%f1Es?$~$FzE%4k-M4YyGy9(3 z_v*f_`*!a8V&BjE4(}hh|EB#&c<{h02hRUA;-^tR&HHKFPapjB^}+aq{SO}b`Nczx4rLr_c4*+CA%}(^&OF@g z@W8_p4$nV)>*1w`R~~-g@KcAMJN)wDR}XJJ{KnyJhu=T^!{MI}A30oo#Be0!NZ1kc zk%S{DM;u48j}#nPcjWR>e$;$4?r6f%Mn{_;Ek4@%=%Az59G!S{$PZ zDW6zAxqNQaoV?P`_Qb85GBB;V#5nExcuvH{hq*dfpbgSrFF|=Yt#psGD6*DSkSIn!p zwPJC_l8SpOR#dF2c%tITicJ;IRlHE~R>gZ2A66W!I9hS4;+Ny0$BoCMj$4i=9Je1& zKc0I$|9Fezt&Vp%-uZaXLAl?;Zc{_|L~Loyb2?a-#EzZYQojG3Lay6W5)X zePZ5;RVN-hvFBv$$;6XcC!3utIoa;ypp%nMEI;V_D^3O^gGxM+PnEs0<<^lP z>2UVpyu*VJPda?_;oA-`hxR^k_*q?hU(>aByRN;54woO{M*^U|#v`#ul8)GqG&+)h zr0B?cXsLt z(6!eI?Y*=7{(9Qm7TVkS*nneWb?senZ27VKjy-hjonxOudk-C}g7(S^V~zHvRy3<9 ztmswIU)SEr71veFg!bNCaYqB%`+CLBirvuO!xfd#-r(cm$0MP=HeGugLwlPaFFxKD z+S}uJzvGV{e;L~Q#qoW*_O^lcc0Doh#3+yU&ONb0*WRd;2`8ICds{Z3y-sNF(#n;U z&sDxu`CjFo%1_6|F9y+?bWKC|7Uy;F4UJ%0A|xtMb)(B7--Xm2F6 zH|~7V`Bon7U2^`p^T&1VZ3XSU5!(C2g-y_2?edw+2b2mYPD~6VviqlQO?^y-Q}0&K zq#sjnNF9yPU=^zUv)zb3$+1N!tnAG25rM zk8K}#uDkKT zi2|5nj4?qm7@VRnMPG<%64N*)EhZ)=J0?rLbw+Af^h?n%N52sLQ1p70&;1v@B>K+i zo1$k&--LXH=D_HT=&)!%v&&pKjgHsKTxiZUXPa$it2x>nVfMVXjXFsbbuj8Dg%kB& z)Q+gPBDP1p2LEO~=9#Fcm5cD`sQyu%;ATeR{d44Dz_);1kvk**7P%e1)sfR99g#`! z83BF~l@UKjycY3n#4{0nBl<+NH+^aP%uPu>O&cS}nl_m3H!U~aV_IxlXu8cb-*hA5 zrUmHFtyghc+!^5FVVF$zh9#$5%F63=veAwhL&>!fjBjfz#rd& zcjC(ZK|SGq%TMEZnxFB6D-k-QZP3a^kr*vr5h$HV>=3(k_dT&kd@4Q{-wK>D5I=|$ z?p&f=oRC}!ne7hOK4qRF zz*ZF(ur)x<;yix=uOMb;z;Ci9VPW9nK-3aB%G~j_&p>Ecphp^k zy8}N8Ecd1b9#p@R1?>rsmYmdH|B5=&;|*fq3G4q*4Oq5OL}M25+kw40^K8)OeTOLk;-=3r@Zww%M# z2CJaKE=c6DOM~>2{xU!Y${-mmL*y#C zhIf_AWCm+2Z(rLo-+| z%VYVhfZk<=@($LVwUCQhODW|Fc2KTlAIk^W&vG?8AqUA8^gQUzmCfX6wt_8ZtI+Pn z@@CnR&tfa(z3eX7s6OI-@qt_@Z)Zhfx7fo@%W&GrR@1#~wd^fJ*#zb%+iFe3$KoUL zq4-1`h0QxAD#USdLYx$p;*>Z|Ch>Pz$IrwU;!E+B_*#4eJNXaso%kLW^9S*x*eCXj z1L7xfQ2Z^!>N=;&`6m}N@23CY)A9xM)ZgdvLlX88OcQR=_b0F zEulBbNw?AhilEzQAw^P@>_ldYh7Nb8+f{E;eUKJYEZs><=q^@GOVK0lre$;wdO&yC zgYHGkuB26H;rr-WdXAo_7icrRNH3w^^rBZ}Z+cbsp)Isk^+MTC_NO<{Z=Rv;^cI!T z+jI-H$0K`Ckt7D zx0>#!IEtq=^Z>1;b@ULeCmZWV57P#E1k@j;1WKgG=t)YVZ|NU6SNA>rKtHncw2yw` zNpz5YrbBd?j?hsmr(^h6LC5I?ouo=SMW^W(Izwma9G#~NluRjF4UE} zQFrP=J*gMu*(O+~02auCSTHP6XpJ2TQx=FBnTbWnMJ!U;9=GLjTOFP(rzm>^Yh%-` z3@nVVeUUSDYci`rOEUjTTcGR!%Vy1_Q}uq9%n@mUaU9k!>(eZ)1nVJ$slv$2PD>*rV(*wvj!_o}$~? z)9eMdnZ3wfVlT5-*sE*{dyQ>nud_GUo9u6FJ9~?jvA5X{_72<0IV|pXm|v}Ar`ai1 z$xg!FtzyUZ`rpSY*fCf@SYmdB9cG8*{ji8@*g^IaJHYm{ee6f}1N&Y+zy+7w!2P&C z4`934ZuSA&%id=nvOOHrS02fuxS2=u7#_o`7nMhAIh)cL-=4mh+oYI@&UX*@5lS{ ztN8VN2EUQd<8%2OF-Oc4v&9W!mYBo-Jk1gdJXq3E$Kg`$jhxqgSIsPnvhCbk%_|yC;{v_YXpWu)4$M~c43Hyk@!QbTX z^WAz4rh_*bkw|C)cpzZNfvDd>rl`7vJ3 zkMbk@Fh9f(@}KwtzMrD`DSnav$}jNq{2V{a&+uRPX-#OF#xu?FDVK_M-NZ_OkYh_Nul;drjM_y{^5Xy{T=} z{-$lGMC~o@ORY@Xp}nK+)ZW$J({^e1YrD1gwLRJg+FtE@?K|xk?JQ5=@jQ;t=QbgP zCPG9v5h}WhFwsSXi_XF*Iti2LC?Z4$5h>b>DA7)sMOzUqN<@rkBVt8sVG*r_RTPUj z(Ne^V7Q!Z)iv+An5=Eg%5(Oez`GML{HI2Ob}Oz@uII7C;Ex8qQ4j;24IadP>d2+i;-fG7$F9WQZYmf7uSekVyL(l zc_*TkJ#8%}iRr$&DQ@Ks{=8Ty?iNeMU1Ev2Q!EyDh(+Rdu}Z8IE5yBGqj*9*E*=w) zibuo-@vvAg9un)sgJP|CK&%l@i6_O&VvBec>#TRM#(G2u*G zc>2O7EaygJoramc23IS?TFe4?7L(G}fS0g#asY5@%kNvPg(|SFn2wqBFu-1%-F_Ua z!ueP)0zdQztUsq>eOigrURVJ{F2ITz`OWLGvM<37Kn(U6@`x2^N8{;;Mn>sqQWzHg#q51ZzK$Z0N}JleOjW7V(?eIlc?29qSmYM zDk2y#6z~R7Thy3}0dJv#utChD~U0RH;e zi2AN0>i-JS!2X0mlxR>S(U3kw*DNHuwuERHaE3!(rEp6R5{--lfaYlMHwJXZf|qdv z0iP3%KR`4AFtHcWq->(eNSgu~O&twDn@j_r(?NSW%D!#};0K}^E~4wf=gdcmW(lI% zBZ=mK_MGiRb8jY^2Rid1znf8qo00z($k_>bJ5kQ9tB4kWr`ygE-To!f9Y|lihG+@+ zy=yGdQpji->TnPEyw^muGLdN2M?|Z^%l)qqt(lC2^Wb&uA)*H@0PwaBygsyyXg$&& z-izZ3p!et?qQ}1`dSX4%M$mj}AJNl`i8k%PtIdr>&w|fqQT}s~@e8QK<_!SQd=a=W zqdu>I*H^zKdTjyG>&XC=^9J(2`8d(vGKsdMj@vI1y;Vi@b}`Wo)bSmE0A#n*0vJ#9 z?k7a=wIJH%BHCR^^gejm1A2Q-5PdL&XfOEua0k&xNdIIz(Wj+EpQRIh4*tHFPxK|~ z`6bf6+Di2GbO6fx2D;<0Oyw(0Pt`I^v|N6XI~;Z zw-B%e$5=<=PC^XPuPEnN)b-Ll97RLfF66x&j@_JeVr(H^O_vhWwh$AD6BWed0zefp zzY-khhyx%FD}5F;5U`I}a5=G%r--57v#@W886Ct-ZxD-Ej%y;lh(*mOX6{5RdMdG) z#l&KtC1yc-oCz?4SUk$KjRd?$EMX|I#9(4cvjAs^rGkgl9mMR95_7D;)d1i(T0^Yy zI$}-c5z8D;EV~7-jvftCETJEg{wq_3Q^8 z2gCtDXHYh=!3JVO5FT=x*ii5_bTi-pv0-xnz!~-(;2g2xpfwzHhj$}Z3fiT6h>fU( z3FgGcpw4645gP|O<6OkXe@$${Ou)CqCZfDaJ%~-dNNk#o*mTr+#z11%A0c)FaArex zbKuThK?Y*7`E$HR9!hKhcv`TJ*lj3l5oB-&>bV$Y-3eMtmJ?gLlh`sd zU>*RxE}sSXf!Mv-fMtLRVk=q`TiJ!!D)4vTcmVKLk0o}00kJix%L5$%n~AMO`daY5 zZa%Sxh}im##5O!m?2%9a%6|;*V<__p$YtY50Qh_oxK9ls_Vfl~o1P{149a>Iyg!fh z&5+fLRm5IeMC=vdyqZI7OCSI|Zn;S8wMbk;Iza68fq*h%Z!{tHW)ZQ!q1^4@`z;5+ zN$hO{U@9@#0QOENVmpTtd-nvf_fYhUAmg>2N4h2Ks-2^ct`0uuLJaXl_>)@`Pw7KEHJG@AaCB@vj=St8-UQ_~ zMVXo9#IsRe4$^W)1HfzUQsQ~Q%SV|7sl*GBzvv?I7EJ)(5^uSOcq`Cr4L(XxX4@0Q z+xGy>BHp1Cu!ML=)Uy-PI>X<2AMq}zPgn5LZ5`kU@$RU9kEz6aqV7Fi#Cw5O9|Q5L z!1q1K=3;V@)-j3GtO^@B4~~uXYk&1Kc%>h(90z-2lk5b~Nz^8xvoL z`aA^s>%q&z>xpjw|BnO{e-yZnW#ee~A>tcX6Mw2N@uwFO-?R+Jn-%~nh(G%*@#l6B ze*yB^Jd*f}1Bt&hnfS{u5r5T!*9t?4zxEXItt*JXzK!@BONhUTJlnbeQ0Ded#NR@= zY$EX;F~r{iot^!OzYCt;+dzCb^1lzhKR_9K*Af443-OQMBmOaX`a1z2i_ao)G#%+* zR1p92An~u}5dS6z0Gw|>C;pFe;@>qP{ypmRJ$U({l=zRGi0@lNe1A9M2TXuH#D7BC z&!Bb40YE;C-Tdfe;>TtXuK=$n0*RkQTIB`27+FR9m+y$5h5XMCA%0;a@n269zqpoo z6~GmaWHf1E z?MMsnMw)RcX%XWAZ;%#Q3RplI-d<_u>7+&XCoMJ+fc(~106R&Gf0i`cBGM8Dk(THn zEom`nDcPi@!gb6cEiIX}bnwwQm9!>JNNc)`w9LJvWeo&u1Y963dnRekasXeFmXk+X zF3QY3Lt5TgzyZ?oK|3GiCX@%dCRx}ylBCYvq(pqdIttIMS{1IubK)W?~ zYJ)r_1*EktA+22((%QdATE}^$buy9G8S>}?8r|lT)&uqK37lTw_bSln*O;{ah#vsn z1_EagCv9*!Y1hP&cCCT5VJKtxX3|PK0hW?B0=$pxLE7j{0OH3~kT&jd(k4Jw6Cuk< z=SZ6Z8Bc{Q&`-5#Uy^nm%D6s~w3#=PHoFCBb4QYPV<4c4wE5rxwt&YikjrZYq;0hTmXr26+}BT&_C^QNwt=_ppOW@gGGGRlnkcWVF93LD z$p3Z`;Bf%>-O&XAUf+oWY$t6e%HMgBw0D<~_Fg(*769qHRslXIZTDu<-XBKVo*2M% z(mnvK56+RccRXnyqMQ%ECha50^JCQQlaZu-x{$Qb;C_yBKEFWP7t2Wd3VFX?O4>JO zzyZ>}1z-O_-T$$gwC};!4=&PvJVe_5t)%@FO4`q$`!m|~5Xw8efV87)NGqR7+Oc^6 z@LLf{+KFzYRjwoL7tlEaUd}?l&LjT9Skf*+2A7^EtvZ!7*J=`kBg^a~61Ni;e_B4ZedCZLtMn?!a2iDpAcF>oNOVLQogOFA z88o|WCD9di!}?itUreG$35lL4zb9z+T1TRHe-eGt0T)SJ1)RR%zh65N{bK;&W57}p z1Ht3f*(A`X#h`5@23L}}#!OcmBdt(H61i(;EddiJtVF#BQbLgi5vO=Hj-J zLwU=Nkho_DiRIvR`2`XyMw3{%gv5OdNZj9o#F{D+=(pm*)g&HjP2%A^0Q?&YNTC0U zN0Ik2gday48zIZ5ZYHq_`JaKTo<$v=Urgc!)Z>Lp5}Q9F@!}y8FN42V-ypFCX{*gpzm%`0tDafTx{{NWAMHu`8U!Zq#)T z>bIwu#NO{nd+%^-2OjKmSt zQT-AL*BO#{zarUYl3F-PF^8l)K+>-pN&hV*0~1LG;VS|mL^5>(tZfj-v6GEb1qFD6+qh-4vX7j*%Y zk!%irTEO2D<+Mb6abEz+X$4xX!GB300Cj2m2FZ47Nwx?54k)wZ5t5x#0qaP1i3F@6 z*%fZr_egf@1lUcodnjN7$sXU4?6sO?@6Sp0L0X?}B(G`$K;FJKz($h&(gB-D_U{D% z&jV2I0MHnSyjQm%IS70YLi(U8l7oiIbl2}8_(rf^D9Pud0ktk#2A(Er| z08rNG?Ig#HB{{YLfI5#oO>$goz;cq~caWTb{1dy7oHT^wWaOFBgXA>GZyIDUZ70d= zMv|NX8D0-sGq;euVLi#&NSkwzzH=wLXlL4UfIPfrT%cs&wKD~(KCba#g1tgzAU7npq z^0`z%Ims86kVOBNFHHx4{>zZ*E5LtsH_6wK2Yp{|MH#Pm0D$K=O(eHHMe=W`(_3hh zx7Gnr-rN02?m#`>0k1nV0igX}G09zxN$!TscRx<@ebD~^^IyVbLM)URvB{6HO_XGm zNya3xuqs*WR^5$nk+Uu?k+W7^dI@t5q&w9Y1n|b#iZ`1Sk?hFNE67dCF-JujQrPc= zN-PeC#m}jp7xd?qeuL9tQRx;(bpt-Vbakd0rAJd^ye|c>k)b>#DLIqp8}nLouwmo& zJ4|iB1!d=ezF#qV#>N&-J z5ZkX1ZX~}mE00lL7QZvnP^QpT#=gy~SBEAVQyNv1#Sz5NcAU~vO$niVV)K3$yWJ9R zs$Si6URzi4Cce1syrz7jNy*$vPQOpUxr2t%bjTcSY)m%zg=&7GEG5~|iWQg&QVYRf zZjR0?s$^iE8tPh+gcd;sA^D`7!q#XMs0JDf7pqMaQM$Gl^viNrzC&WeZHbKGW>ayrd@v7nFswU%BqF{!OlK!ktayS#Xe_sYTCWg#m#XHvcUw>xYGb z%$Sf7RlO%D)|gPb$1*zxm$9&0L^|*yQS)~suUml>+uMb z8DUUQhgPOqP5BHF5qL)YdYq3%1jzZ)w!8I6S0|-RHC;1_fwm&|>v2WIVbOGQ)oVen z4CD+E)oW{`Ee;e8tyOKWxQ@oD1!Cx3XvaD|j;S8zZeZ2IMjfxo zxp>a&leeorbxH4K%&B^c=2Xq*=d6bpsB&B;g`vbZmel(y#-aP>P;(af^KENVB@xi{&)q6F{CfF3@-4;`UUcK-F7R> zp$rFyydcS{G>(39z8ZA!Hs5B&eJ`0Hzz7rbGDv%SI<66#}^lmU)o2xm#A^)YjefrPP0;sVe$Df z#-`Dim=${2e#Yu2pYL;%wa_~D^Ie;7>!CR#U`|!9T*3FO(0eVy5tlu2)dlW2gbfrp z{&99WbL!)x-|-o4Twq7Q@Q4PWd)l zey6mm>(aYhT~?!!{cP>lh<)rju5->J;89#N{L^DxCzwf(5tP!w!p%8_I>i}^c3}=T zTRK!frU+_IMLIdYx;!E#IYSE23j4JXJl{6vZ>Tu(H=0 z&b^V}ReqN#6=%Hqfe*bmDWz8opm}o4(Tb^1p5bOOKc_&M@|G;8umGcHS^zX2Eo4T7 z8O>BMKUpu9 zSKDWmF1c;spRZRHMX-M01dX6Li*V_c{L}Sv{etZMSUO4NHpB(~A_a8lS7|$~a6}pqZ*usHd*6OT0s3)hy-HXAJ0IH8UHf#9FDFxR+p& z>#6z?yHyuZ@UP43H|6qgl$X|kaQ?dlMu81#*d&^bbIkYt>w4y^TfY9^l5-uLe@Pde zUWH)|_^KL+tHrC*byWPzn(FBmUPi9eTrYyFR)b~D+)r8P?&$4zDJyMi%~Nx8gpHe` zB6%o>jZV$vyuP*8=YVRN;hiMbn>4zm9&OWda|+ZtQXfmPF;TNvP0N&B7rprtGu(YQ zGyL@CqOL0Bx_I~9u8S(<=PbG5@m)XPIREEekKIsGa>HZ0exC34Uo2CDjko8je#`aV z>>E>E>ac{Hf|_16aAd1N_jNMbj6tSwiz6@~JS#un+~&?sUG6FgNpG6zh!)gf@gCN0 z{CU^obxYSdVZ~n4q`NRo^}^McERKLcM~n2R-24zrD7==?$e@U{)P$f!YkZO++LoM> zqP8Jw$CB360Vm6Q!|D#g%>%-ETpuMd8P`WmW)L_loaEq+)HGwE+tTVIiQ1`979)X0 zq$Sx{v^_cr;>J!zbVPJgS`sEPg-HmhW=Km)PV;m6j-tM!P!&Dk`epAeA%^)Ox9oNO z@<2l~+7pRYd0K$KC{AP-`~$RKcjRuGK6wQDJkd3c&zim|*VVUrdXEi#`)=rwU47c1 z)ecT-p!E*A7=Za3?lK-=At=#xZOx>-X7XPBgfp=u(WxGnY3{C$>~^Qqbz{d}b6sr` z6A}~N#$?7Wwf7D%@cDYvlfkYW( z8c6Awm4D;MC<$lXnqaL`h`B%;+{= zE+gL_$t;P*%~Atc3%`q9qf!jbSz!!&EXH*%hTr&g3@dDINU1vLdT|1~@9`A2I)#1W zdNGCFchQeMl;RrDkJ}A*@qVrWDeNI^VLqDT>YKu9cHgWq!kLg~k76v!o|kaR86%#s zME#x7WlWLX@b@T8o?TAn0T-r5uD=KRS?z6blZs`zoT;fQP07nyvoB+#WUOVHVt*zd zcSuSwgKBV}CJI}L*x1cgo4aaf(U_$6cq0&O1Z8AcLm&trb_}rANHQAqNxoVHT+~-u z?sWrmIyrV9)cl({FX?>h5sK-Hh-M`&r@-YptxT-}{G4_3OPvyy1}eA8QlhY&E>LuX z3lZMhyNRh8eHiL`o!@@kHcG`C9V%>Kos@$8MhCV9^u5I5d`@icHAwm{#3eO{nZvt35Hyni3J2VhIbgCP$_hIc>Qe(=r>&135)SITvk> zGt)Ze+W2Np`Z|NGcKgo}Db}!XODZzOh85{+4wwbcnxp$aKbqO=jb49oWU8}~G3CuA zz5deZvdDK^qVKZpYL~`IH~B5V?q3YnZpCV)s_!45_qq3Zu~DFQUflZ!YPpe;jBNoM zQ)?=-+J09%1hqKEhNGRy@fx$Gq}VhTpPU?DbvZQ2m}0BC9G{{sNQviV)g>yix`e+S znp|~HpjH%@k`kvC1=d0RjcD0AxuucqFqvGfjm63EyGr~6O{~mV?3!g%7juv@+BL>M zV5~LTl^q_=K8dy_7yDoxL3}gBe_S^nvcaFepRameXWZDsZUXl0jWP=R*hV|{U$Hxm z|5&D{xi?W%^_A$PU74>CT&a?tS&V1vOZX`r4y#e+G+m>y|F6rkz@OquQeA|h$wk53 z;E#`-8ywY-gqf`NbnXlZ3JSqJobe&S7W+lLOG)Q7>pq=5=hJmVl-qlyb7J>K7aH4a zQFklFzdOoiYaGQ0j(Yl)RP9MAvGT!tFKB_-Uf5ez;GY<1Es6`Zgyv*NyH*9*tAGu%towA1avOD@=v>(wMDJ!p^sch;?kH8b}B>S3AvVHG8JDI|Y7DqczDGVDwjpm%fQJlQK4KgUBJZ98tWF(X(Wn0kx7fTfsEtSO zF4AA&{Zff{?f0i^p{%NE2;Fkks}Xia{!)$fZ5l&{{tgK9zrWTVm!>CWWhIrW$DglM zNmi08QDtTOlCu6s)v8i)1*ZOI)k=TSq%`oawN;hgR;~^Ij%xi|TkBt~m)=@#P5M`r z@PAUhs`d7&{y*1V)p~nXC;ng4LzmuORsZMO%cZwh)xW2`@E%&t91JBm^)X0|To{48 zV=D&OR@nAwi5G}!UKP)BYdjdN>J4&f`D)#$zjD9|At_(&E$fpFhAqoPQ)oy;VtSFy zXf%a};jHKtj@TdkQARH+!G8n)hH7sYBHhb9uj%0H-wrZLU?IWy3-t38fr3y zmb#yTW>-n=EF`FtQEew-WX4u-iJHu)nFv-AzV%dd%71@N?Rxdy6O(^Ot=-$?YLfCV z*MEkRiF?kX)_uORnDk#Ji&pA&8`h2PNzK1cBH7APl_q8Vd!$k`>-k?Vm(5Bp`~Ek} zMV)n1)<>P-`S(+iEU zW@D#?EpmDpnc7dRkzH4OXA55ms5f=}XXIzkVwo~cO@pJ<4$Yqyr5yrS$GDUKs!T6B z!ne5oyPyACNW=kxMgB=#bpAfYno%z|D2Ho8bSV?Dy$&-2d-n4@2&q8UOd`J<}E6wc$U~ z|MT|$U$;7HQ(hT^|49G8L;76Tee6N~|7P|7M_3-N>wfirbpJ2RJpXmeqv^U|^}j;* zapnpCoS1at`Z3=3>oa!uhBm_~8uwXWR^mQNtj^P8hKjf6>L+5bP*687>*cc7p}}#) z#!aL496l@2Zy#8y6*bQxWe;V%>&61aiG!J*JDqhos!O|WNYrp!TT-o$mAEexxR0Cu zX8A&uZ@@oK`Gvnd!x27fo}j(<{P>V;M;}JLXyKsQV}CUZzb1;7XAD0F_xC z=uy|Nu;})uD^HYE4dF9dE$KPv;kjKh_@(M)HU5$-&W5<2;4`u^y3BoeP|qdoAJxme zeiW~jvQn{uZ%~((>Ywq|N-Y^Jl9F0vSiCML-w8k=oRONa{(jEl6|4YrXtF747~>5U_828SgwE-*YUG0mDa z?kg`7URQG_#2b?x8CGMY!x|FWZgP*tjeAUP7aC$o!^@)#N3tvR24 ztLR{JT1>OKcdY1Ex9;(y=P&K_Pt;vfVyi)g*5dduBThbt4_`aEWy{HHhlk_PGw#b3 zTMGxZN_5qgPs!w-8UKWQV2qT8;!K^o7cziuRBQ6OhlJesg&MjOqBJN!FUxa8*{ts$ zse6R(jU>nyN$Tv~AK~aL_X2%xflU!!bH7o$^0G*E&1I4574gaO0i`m{5^3`qNiO2tO8=5{EQ<+!bl2swui*CiSd)Bjs293ax(H+)#5X78}6o4Ep(|-P#u0VxlWU zf~~1$Q)Sbfrd`_>C&ri&VX>=-Twt_R7{S4D9t@Qwwid(H(mPy04e1nf`h^+FaM}_& zdF73?6n33DQf1=z7+WPKwlea2Oll*HfxRqiicNHg$KR!GwiIatN+e6jz2 zdJtTD1?#=MuU?!d)d^tnuilDz?`EJG@_hriOW-*TuV2l;Cj;ChTk6w|D@YmDE~cV~ zI|cfGePc4sk*W?Ut7FXE=_=E^ObHVq7PC1PNBo?%-V&AODia~GX8pZ84kmlOtmF>Y z!@K?ctd6*-#5Rd*7>%pGN}-owW>i*70_61)s;?S(W`(1>Bg$&G`uV@_dbs*^9E4_0 zgFbHi<2+&#ZnolJVzRGa=}59S1xFTr{8f*x+ndcD*60MkKx0teGdC$*b&;`dei!($ zIOaT=9~v3#mk@1r@azxxZQk?Zq->;9zv~!HBk(&3FVM@saWgw95=Y?Ox2Rj;Vyn7C zio*{zhosF>bpz_F6WFS+&6FQu7SU#$yH}S_vBio1ZYns2aRn)L3R@k;RwUe8^y(BW zau*h)MyNyP3YvTG3d>NhuL?q~mHqED-xbYXwup_|6=<;9EoMh|`@QEz@)ta<=_;vd zbal2|Z%zKf$aDAFyF1JlY&8e&Vxu~#qv$v+iKkOtHm+sq;%UX4bk=Q_-)vS4sdbvL zYMW|iyEQt|FE}zZ|EbyDX7*79sonfI~RPfoozOSn?m-!lKJHKGw!B`COishu8BdD)a55%)3R~qtXSG8w z)U9K*JMJ8-Ce^pL^;=eWJ)`&E*8I0Su)3(FdN8J6dS3<)X`veU_V!`svDWTn;nW+* zrw7(5ggd3SHEVlwRat@EZci*~92*nG#;Tj8Y^*strg2fC!(q=?y~SY}8x-d6+U?ry zA09LY@-1;#3U8b_CfU^w6BX5O)n$V^x~I1gnx?;f^mTKaD{bm>&%ztBn#^8&FYX_! zr2s7CMN$dwRhoO{;j1XVe%0{4jqc;G_P5&$oUvdxzGV~}gB!LFnw#5Hb)5MQyOKzF zP@q3+?%L%a7=+Kgs0Cb3&mn0Ji*E5s>S!D);x>7>F+HS5GgWME-?6#P#`ev3@Nd1A zqxR!EYB^bBLt40R-ZuDc2P^>8K^Zk?!qqRlAX3_3L(}VaB_GYGf8rt;?~c^$)q9m1 znEFLsLrR3l>bMOc8TO6?m-b25QG!9UanNQL3vD`Y2VS6+e4E zX5iAlxnd}B>k5c^KYwrtOzUqzq*FLC+ci^;zJ95l>Z;q0>SnxGq~0R*Q|0dBQ>53F zGv{S}Uf8(PwYln(G?CbLTHrish<+OL;8w0l3M!6PCv*;I9n@VR`j4eE+xwT0zM}R3Z zIy50M!kT1_NVV8I<|kTnI%lL6H)|Rjn`2e_?v~sKC8{k@=L7^iugcwT(DA_~ zKleRY&yh(+@okeWj`&7RjbYXx7RCc?xI?1K^-D3D9G3XlV85V1D@JY3!y@fIC-P8+ z`UobE=O;94TxRy3XA<__vt`3ssTQ$pJ+&r z(c?U~?Xh_Vebs*_Jrb)*3z6reCVD6G^Yq+&Ejm9pst~u()N(+5BPqhAPxqgA!nx7l zVEV^BAS=YGuPz)gFWA1cScw;2u@Ib1>*0t|+TW zqiIA)Y-mE$q}afqg2Ki@!9n4n;VF)^2y0GSjA97&#pH$Xm7NUy_Ea<6;lfUjx2CO_ zdx4DVH_%c{7ekFKVyF>I1E%Iu8DzwV)V5ZQl*W4GR=u&IHsS;kK2h6H5F|L) z?W`I5Q`7)^K8p0)>e8@f^U(-NPS5CP#x7WU*b1C8^?uP#EhE)=Vvmij*%WnK4(|bE z96MGsAZ4XJv#cvWT%?bV?mb=)u`;*ns6NNseoRR`^V2fryF|TjTGuP^sz+|=-TRhD zu2QZ>t*)JsS9smEEn8lDU7>Q-lvW{YItlm3^K`G9M45Y!uv#ZwtrGYUL;t0t3JXTv zGidO#Q3VB~mJOE0!%C8p+m^O!J-lsla@*lmt-T|Jds>RSrtR>9soVX2#xKOR#!WZ; zN*nzRznAtdy-y!PS`NJGEsTa~Pm02HU+M=SJmD04&&{T(H0-#kCk{twg?NTCJ0^wE zI2#d-MPXV&sy!0JsJ;P~lIGcNG$I9g6m={W;qwGq<9EK2B2w~m3zPhEqf!*85(@D3 z0JX+Y;}7@=W)itjR3X-ms^CzD->wzWepo*uqunp9P$u~$rNLceOC7PH%}`Tn1b?AA zDtnT)B|O>LM>q#LkIV_cN`KQ z-;yV|ZVF4yYMI(~=%tG-9&mm5VAfjJl$WxE=2tZ8BJaHA;w>p%Qj(hvY%=0+!?o%*MbG}NfPWJ8+V+rXQ}V9+>G~ziw8Y;O7#m^9 zj47B@(7|=4`Uls^Q|!l6IivDQ<{qEj%F)en!=X;i*RUq{7v1OjwE81s^^Xp=w)r~z zQet(PgAabX;Ne_Do;{`es}qvq3aq_aPI$zW5D{xrzkzFh!~9{E+NR*zU$^7OyS?5yw{fRbp)n!Nf#ych zy+%FR*FV@0Ul`k=O#E45o~$nu)4~YD%cYDSEXCsT1d(r|MrD z^vSI(x5}mWef7~GgB=q*{li3RYOA)xR6kaqHBh&t6$b^m(mM?5X?kIv9>4$Q9U*yd_qf1HY|>~rp=q@!iAx#(!boKFTP+5Lt-8I z8hApI`>m1wZl+ovYH?cv;wr%u%!zdiXQh5FERLJt3jt32@+tJ8-VQoz0>ubs_&w>` zmWM*9ee`;m^p1_ea~ zMPMH}78Bt=rPwHygoLSV(AmXk2b=&pG`v zT+aWh{dDOQBa8vK9IZKB z6OFM}yP3W3Dm2@zu|~E!%@V7=sAhH*viE}nBV77N*qm&nYdssOUGI7*++mH2!0#|F zkBGP0!(9()xoY}G5wW%i*L2tPAai)E3S!^qQ1v+2$j#N`8NOTXo(1?h!DkoD0med? zysIv3XQ~ODQlO;#-24>W7*d~N!}eLSA)ck^%NrkLWg+x0pX%ROP&;927Ytd_+&df| zi&6V=1y`AOr=iU0GTX zY4X{ORkK{TvBa9sYlc1Na%Ci5XQ{0oSV(>gBHvs@YKv00dGI8 zojaS=DOe2sUP4|fZaWMT>JGi1*8saG-KxR#tMvLFWKMx|&;VS>4-9Zv!yTDQm! zNl8sj3Ck>Ooe*p^s!!%bHi_)j*N?`I2#iloHCge+o}k#^?5@qQ1nrs~WC;ogOtXX= zgA-d9HVsR$Uor;1{Hi%}rW23R3j6iP0Ht(t{#kCV`lpY%(P^ zOHd!<0b-*z8D^K$l$;f>#2lZMY*J0)*@ke-UXOJI|q(QF@ z8@6STeel&iV^xtxW3VODGQoA*0a8>m6p|Xwi)}DcQ#cP&wiEKDo++yHg%S3!-+RP0Y3|KpW)Iis4lb>v&rr% z7CHD2{JvNu=z@(2!V37f;e&yXE!b#D7=Lz`0|r}rWxJz|o7V=Tlgvb(sDz2=`f=r{-(Oko^E3enPAs|cnkz?(Y-Y2qX!Oc!Zd`(|@Pn^B^PF*2Z1& zf|L#hjRw8mXg7P>(~D~L27|tKQM%n@wj1?&gE6==cH4R9-4@IK^-1O5-h4Qne)vtX z`bl0-v1^l0c@HbF1`>$?-cYP(Qy29fRdm50i+7e_jpBOP+-qR(901;Xi|}dmARq6j zmrPPrPlkpT%tS0xkBAbPB~_gZUXwUc4~l)C<_e5a+M6!rw2pI?V)8hDMQry|S0%>B95q1x%aVHDKea^2(Ub z9RX7zcf|JmzKQJ1f4A@|phR^HtM9Okau-Y7QE+1Aj@gxz~Vx^3&xFMMym-90?geaEBEpEK~uFMnJ7 zZKxb`-4Z*!YwVGKdF6sFk8eW#ph%s&vL!hBt=}lK*IcK}y#B5aoIY^x zKmX?NIWK(u?nBG1D?hvHCgl&AeV@B(XYJBUR$+xD8kxou(B$31g@`ZV?wnC0pM;pl z7(?YKxWuO-!#dzFtIlv#Bn{Aj%DsTkFvzG-437-ColK)rBtB+OA<2VT$}&Zqm~qW( z)b>l#^`dy1^5XZLA>Q91=l8_g(?rzZ?W?F-Jlu+UB;pUKjC=LyrdFl(gkWzUSz1eE z3*DbtGHH>s+@$=;v8=)D7B^J}m3`K-qVH8ye9sjWZ}um#{Iko_u*@OaOyy4TuY`Nv zWw02eiQl1)seC!AoBmFkz!$Ne8egCxU77tnm40vAu%IgY`DD7@2Si!Ef{yaor9(As z4vQp9*?p#9++uz!E57AT`c2t=@Rdp&Z8g#%yT?`0Kq!=2lb4tcsDq-`ME$#!E=4v= zH8#Qrt6a$~3XVF9tbD32$6c7o(+YGsTEQ%XkMkrnLQ@$=0_(_&82iX6B8*fJE*>Sb zrvsI_u)&@-!~Ic>zu$;B5k82=mt4EOr+?Sc#X2KxO=cgNJUjbiN$A@8;e`gXK}1#i zjaYq(73E*BQTnRXv3k$Bdsekc%ANFGCVwbxWq*7&ogpJ$HF;#)fKHUO1JzRmrM6W# ze07IJr=?ye`D%O(DA?J;KANS};ZEi|dYyd8qL7$V^%WWkZ_pxRPMWJ2)4cR~E-qS6Xuo@5XwGH{v+5aih#t9AmT7YyYnMr9L2 zp+q92ylz7!D{r;+5#`tR(qu=G>5)fFMWy}7ygIp}CcdDzsnlKV4*Ki+Vz!voAP4F? zd%8T32%;1SHZ1o)*yOEl3lH4BX4TUDQ^$^WQG7<9@qNw_rqX#He{3+|@9t@@DXVPn zZ?Ckkv<_58(xJgMt=_7kHI1puZ+m!aS5L#@k^ZpLrGI<%9hYwjg7%Q?tZ3TM)IXY9 zF*Xn>4cUx#lY80bZ7W*EWkofKKaZ@aAL(ul_M{{Aw?6vZwslYc;Q5*Vtd0KJFE2Aj zJg$lL+s|4XtsCrY=~~cGo&0*V##`(e-+W$r^V8q>%8K=0fAHEWL{d8^K44`u=wb%{=Oa8OfJ2C&wzNi^^EZvTK7YJznGeG z_3u7^CcOjN+jGUuix=N~#qMMh0d#+&e);*=A2VD{1^9cmU$Z28&g3;a`ntAVv;4ip z$c8qFE&Gui-q4n1>%J(%N|wfzD3@IH_5ZqtHW%G8G_-qRRn@|?hlg%nOy8`3@V3h~ zQ=kO`DZ?!KIMpm9=mXlO11Xkd6vp8Q%Nh{E9A9YBbg)wThCb+!_gQT=>+H>rp#B?D zWw6u&cD&1Fe=lRn#Dm6S>1MlM{;1@%TAk8IWxrjzx!4$t8)h>wVqosfW(;#HL`H;4 zpfHW757{N8p}}FJo-)Rtf{cTrj)RUTM>_yiio*Tm)kq;skh4W$Qat3Wo( z&)l4>)2C+-iDyeej#Avtg82C}&amSD`V9A>rfAlk5Z=am2kGi+JLD^84@tr7ugSOmta3ZFm?X47`~L`W%MsKR#r?DA!2)0%ajoRO8k}L4 zs%$Xo88js`O~K}xVXg@_@6#7yPwrN_oDr{dzUarZu!l`zgS3tYMK)uH9Xj)$){5-! z#Sb~f_2Mr|fD|}v*+)vq&P0(mX5i+b*o40&?l;NOdE(a{L2q1MFLm1;PHXlB8n(_- z@mGpBtY55L<@6hM*+0w}<9-LO@I9lvgm$2RdA4|gb}XZK8W}7cri${?dR$(!Z$&X@ zSxR~`MI>f6f)qe>)Ae~~YIcUKh#9uGDjVuT1|MUJHE^4cG7g*Y7;MknU5|S^aJ`E_~O( zz2AIcuUftNd{lD2sz;RwbAH>0&N zNQIl2@8j{p7$@@qC7kfeO41@wuDTNjH?8aCwzGXW1WRrug<*klkcWXv0HhyT07u3* zG{(u>)F4>38ZAReNieXoOQJAwZGHW<6C7(zlh~SuVJr%%Ep>s^SS@KnIbF9P6{y>i zq7V*#OEbzCo#KR0*4^1)(HqQeo6T)D=q(MM-DM}(CDOF70Y~Vk<`=4Dm+9~B?$??z zyVjp@m+AJ^E=UCt;Ru4#GIMj8JK@)5FpsB*=rzj@D)vxUM>YrEzAuJGv;@oSNn&q`aUw6?Log!Pw}?#c5@UV z$LAMP+S6wboI6&hV)Ex?(cfI3Aup6@>u=EI$+E&`cFma{y69$N7DQZEhUwq(ZQ+(g4JS4q<$OVi!5Y({}2jNcndqITdRDBBSCU~8U zj)fQt92){ysCe&lHIto@NatjYVY{|9TT)tXmIZG^MTIxpr9AGfsA#a5#f-Up?taJb zpd+MnEVMKB@n$>4aU6xCm5kX^*eI8(;wptsFBId$%xYS>2Jza3Rtg*Gsz5ss6Q3)L*>v=(kUWNov4HKV)geaQ z4z?i))@af#!^s0=2M#io6i1*7Qr3SwKG+nX1qWvYZRtTth>xTq@^SxoA~6mT>JNiZ zG{q*u6ozw}w(vTOz9UsGKk`V1SgQIodTokOEX1QH{Kw@;Y9y}Y&Z=iyfu_Ouaep`r z>MKsq&NKJi${idB%T7J=NLG-5>JtZ;FEUB9NX;RMpqfATWTB7-SfP2?DmfgT$tS|L z2pFIcF=MHx7y7}~odGnpmA3ps2otw?JH6PobPVc{Mjnubm zQC-9Ged*Q_lfIq+ll**MrzIT9P+Gf`PNzGpl(67T6ut=iYqv+uN& zpu=52#vwKyh1JjeM`%d{Q;Es80^?6pym*LAx3W9HHrJUnAM%c*Nx+jho<4U~i^xSf z7w^QEmyE<5$Vq->Y8HK~mK|23lZL7345-N1d5n$|Go)z8$Yn@OAJZ3vC5ecSA%LAl zJbF3}%~RZwDzyiM%~&1i3fv*TBPJ>GmLmlbSs(=Bnbr@{Y)&0mKwR+Tjw8HMwLNk+ z2l3tM0TW!QZK!TF-M?yN?rOjPg}+>W=r`XxcF|D!!ecwOzjhHdR9!+uE#m%BvFOdG zhN<~9j>)dKfoLA98r{@JXeNvLqQJjEWI4hw_!i*`1S8P9G0m^%PaOEhRTJq8?|t^y zhYyl9+t){@omVXWn(rJEm!TQ6Z}6iefvkp1+osVf0zfGwT%$7Vu$W?6gG^L_g!M42 zG+6B+pnPkwIu1e_WO|Z7CSz75IAi4sF0Qe@R4z)S3Cn`T_5Nm#lN!I$G{L-eMdS?E zlR2S)yLJAAZh01KY>}i$IA0K%j8g(!B|?(ur<_0061jfDq$GCbVgSeGPQg!|lRU=6 z{C%~v!0@As%{vg^C@-t6nRluLG}S5EMJFkkk%ajV0HgfCs6Vk0siUZmsp|<<`(Reb z6b(u;AYmR(=`hsD-uS@qKaUx(iO6s+vYc{p3uapuK;k)T&dcNo4*@u%Oe8Wy+^E+U z!s6B$HH9s-Wn>coU^9(+j&2)SLwR%}-(=($HUzS01&!QUA)_IC)Gk#vgn@Fv-hi5> zKod*|zUm4ey0oAllV_r;(u$;Zv1LHSC}$>-M|vf(Advkf=@H$Qz*u5gr&Qh<3e5ag zx{10Fk1_j8(o(a}OBc(H&Y3g(&Yxwk{F$SuNQ!`NB9(L1hGRpW*@43{^=c5BR8-9anHR0X9fv$JGCx=`Xce!aZ$u zrDJ2(`u3iX*@+qe*;&e?;ea~u14sg+$%}Qltp0@8WCWAPV~#XM?RHRv+#!GBmv*z#(1WJRK*&QYQ=Usg5>ebg?d|rQv!|sn8i@d{&md5UWt! z+;X;9ZRXhE@I+nh!r{RfG70<%izT*Z%UKO|+qSNac;gI3JY>AscqP_@sV&47Xt`1q z!?ucW1z3g{HzKhJ3|QVzRZ;)JA@DW1b<+*4sm2+jyGb8$l+GBfz(LUsR^9PZ$06EB zf0UdCizVCbU)S2U)}QURSmY~#pGyCXWXb9Ck?@8sI8J%q9@nZ%3)_^BLlqdjIiVWQ zv-mK^C~TW!^|8-l&YI1W@HktJb-1972adb&r>keGmTzZhH$Ag*X=^&SHUB$UCj2Lmjg?#`hN`$ZETF3KG+n^RF7O_E<=Z+k_3+Ca_!}o>wu^x{ z44iYpbnM(Ql8-|bctdlpnW4avUazk8-a0))TltVYU)r36G0#xuEs|z<7>SPMVI*vg zk-`CDss=8z%$z9z?5V|g`X`JE{I|ul9xC(KB|%UACL`4_K?+Zu{%>#X6D!Ez;U67B zoVoBC4YE|!2{JsOcEO47$+N-1ES}A>Du{Rluc=>WJmGbI?gvV}@{ZWl-f-R}SM>Q8 zE*bZ^ect4XSh-h^fRAKoNW|7Qy(%hV^gH3PB)s}?rqW{%mV8%vSE+t};hx}M#4kMg zfT?8WijrV`w4~Zvvo;)c$P##XWs!s@&=IWJde+AATH|aKan0nm_%L;#2-X^~9%U9U)^W!;)~Gocx4gH(6^p8df}9fH-1+4XFFf<}zq|U# zTMwPZRzTbF_;o7>_TRcD!RP-Jy-kr2$|rU+K>?3kAOS&%R}9JLvsFx+`;_oYGQ)lkh`I>#-GDcmlsPS9a)Wrkta?>*9XUp(iATDk`>si_1yWqZOcVG2~ zFK$2Up|_4Ovf!&|;dgIbIq07*3MkTk0tJtjSjZT7hK{Jlu!Djf3^1_hl{ z`eG)7&RZUjgXW~%7;THYV|~jTtCM2LXrN`;)90VwYA}i6WHs$O!@>@ldZu@Iii|Zx zTHo}w`ctjhx9s-nj%Y}{9W^a}YUr#AH=7@_4%F5TSRXPsl~;8deya00#oI$s{fUaf zjvC8R4> zj8Ric_xf7OXhHbqRp$+swfOdy;^uBCY#Q|C8&0e{X!t=2}UwyV5K(A z$X^G6T1Ddf!K|Q#cjpIMX|VP5_1*vM>j$;9O0RpQkMK+4b+rU_*VJ;$|%ph9HMP5F22Qs%* zHHlCnMzk8B#gA0CS^bUicJ)p!oS1W>Rg)@*d(LgGj&|eQB{j=O-L;;My&K2uadYQ<>B7zXTGyQ2y1suyp}R^Fl@4# zb*A|KD{uYSOq`c8{U7A1YTziDd=(hs^;r)&J?St^8rR^czp^v}&2Tz4~J zVVYbbM&TgV&OHTUFcf()pFmov~nh<^ZQl#Xb8SF%dz*~CV|;gJ|Mpl0lhCaoJatZ&|Dau(35 zkbp9Lq}9Up9Q$LELDO8E;fX{}HqnVe2Frzg35ii?(5PT`0ZhnApX5Ygyy1MD2g+oQ z+vS&FmoTfDh$_03UAvmLe(6)!_6%$ps#tjF&Xu*R7Y}+bsf~B0JLBQ{R*%*1@U`^} zCr`h%_E5OgyZr1cR5OK#G@aRf>J;g<9`c{w7U}Kji>~U84!2jZQ}{2j)}-2fL+31S z=->Z|CtlbeS&&GrpX{*fj${Wh>l)wJr*$KC=YG_rx*;dGhnLyUce&KR@4l2W_hr(zyby*yZ zTZXFgA@T-X8QL|O=V>ro4|14d)&%EJa8WxMI}yi(+TPb;|31q}5;H7M>HDN^6Mnb9 z1UbrJ%km#ll8};^zE2v5`>2|y%D>Tcw3Kt(>3h!2A|Kbl{C`T`qC_tG238xW*A9Es z>Oi)w_6yJofsRCB4Q9#F)^Qf>(y=qpk+W;y>cIe&>*(r*INn@H})}`Hs_CA}*a7^Bq zM*kDG{S>!6FdVT6J0?9Jff$rncdk*CxjRnuPKXH@2Z$VtsY2V?2!TVri+mrLU3tOG zMmc??r^BR86s!NLxEbM51bT8utCXJMJbM~(4U7U0S=4rlX>fumT_|Nt)6i1>P}f}^ zRYK+TfyT7q#dAy*&3zTg(bll;oJ6`QP*?R*qrrPWL%XW=Ec3EmT92|c zfl9Zp%^&S_ejWi5<&FBPmrJPIoHk2sV=a(Ur)^_JBqLV%T?zfQ`U)>XXvsR-!=t=~ zK$yPLTUJ+H<WuzanM>N_4LFsT>iT}cc3cqgh+b#F1YFA-tZJ&F*{Ips!{9WYp+-AyMZ80m zb6RPD+=Sx55Fd3y@gc+qM&$wB+idy zC@7wgPE(8N6?qD<2@uI9o8Ua*GOVO?n{W^O-zS7;|6%(;P5i*)`ujFOAzu^m3Kbx! zK%v?EHi8nT{JwA>hL-SnF9ySZ$FlgK59 z|8H0#;?4iK!J&VgH%Dt$?i~U6+_u_e_E~vZ`x=5=RD_KFUBoUIFsZI_M#0BtiWOcX z2SB)w$e$MTAbE~DM>6#9+5?q|M7dXItZtq!i^5=aRAz(3qJNNU#I(r}e&)=s z3074Fvum8>xUk$q*$ir$^gyJdB9dK$X#VUwbXxkE67A@qjFgMR@))gbMnvzyU@*Jd z>7RXv{1l>Hk)H>6wHa)Le&IA!_dYD#fNvp0xY>oVl_Dd7!7&gn2+ly0VGc633vy z<&VN3^H(5=)>Y&N*c7>R(ddP-Y;Wws(a{SM=F4f_N#CIITcTAIy7z9J@VJV^@c=3z5LGGrT>Zk@ z+Qe9IlWlaxXiv}RsN5q8&bNPVcg<#aal+wsDU&1+h3bURx^Y2uS>td`z&}wFe-oT1 zHI(wczM?4Js}l^HjK3DlcorI~3D%r=Z%B$4IUGe&ytGs&bTrEQOG{^O@->Gr%kC9| zhD|zw9TTN|D@-#CKAh{x($ehD>9Fk2LpmYU?3=x*v{c^TNM5OCF9Z;6S}O=-$wp3nV3{$iDPusv&5^^a}*9{ zG_o(HL}jc(XCQW^t{n3O<~QpdFwzKQCc#Iur2#o(@k}AO*t-7Wt8ZEm8HkLmUOj@h z1vg!N@%mOd;|kl#8od>LM=n3GI(6~$%3F8crM&h0#i`ZjWsdY!cpJ-Xq2H6Nh?k3F z!AK;iJYE`eu(>ae=`w{l==LYRaM#UO4 zH_ADm+{i+u8hB5<#KH$azge~ypWWnok>)p^;F(9)snkxo!2^2+?)dx@n=kpzvnam6 z2OI(7{qGBiQ;N~7i28>#Bf$EnljsC?MlF}=I=Zp@lph}+KX*xW`uuy5w!{b8sSxrv zR$g&>1Cc+K=@g1F;%t-+pqgN?eIcxj-+{|=8e*IWVf}uJS-p)UxQLiRz+sYHR%{K3 zOvI?NshlyMfxkczdFlRD;#@xb#v*Z4iI#zu*z5>vbeJB!Yy+T?k@{>HL;+j-fQ2YmK$RkmCH$gf;&p;T4&PJhT_SYlJ&E-ttE zT{E*tjCXn}{Z5@+QmSn8R)y^Sjh?EogO0kuo5Vuzp(?NXh0AqAQ(Zz_-Mb{x7bq(7 zx~w(jm7c!47OWULSR?)+60J>MQRMeK>cY@(g9c$yCeZF09cv;vLrHuh5s=orOh z4<~tB)Ni0iGHk_}j1Xt})&jV~hK%TaJqda>#TjPN3Gy7p=@Wv&9D0)W zS>{>N7D{-M9yERtwl-niPYS;g-h{=My9KnpgWnMwD~UE}S1!vja0Wy>1}uYEFkWfY zQpFnt4D*z268=sOfg6bhK_k|QdQmV43xF1pVmKJlDsBe-!J|NJy-2{N%OwClG6e(^ zEY$*Aicl9?MzujsqIm?!Fp#A~I|E8Y*s*6Dl4OlvBzs5s6d4~SPLE0XODMIVre-V^ z6!j&_Z-bMG+KIMsDELeyLSIHg4>Bq6Lvg#U+!i5gia?iG?1&^eqKzR@KWgu*tn9Zx zVx9SshNo*573oon%WoA;Nx!c`XAawBq@6h<4dP*Y%rSDXVuh*qw9RWqO5A~TI^eeU z4t0qil^y<|&o0`_%PXA9apz0UM8xVZ7LADj_*8(@(o*J&;+dEGF%ob+zq2|C9$ks@ z32JRHHCEGiai6n9c_Zi#wT{;&7Y3gZ_Z89i|6V;F_#Bfx&#lu*xzf?C>l4}gid;qE z?&f$g>dZtVk*M;ip#JrFR+;ksxZm!z73q}sGJ(o+?}BSaOZ^UWG$tcN)nw3(_LP8h z;*j+*&%dYB2Rt#o&hD_zeqSF?bu~vtcO)EkiYBDnIA3;_M^ME|H2x+KciK(})>5Pe zr^N~dVvp=F_!^Lfl!vsC?6ADWfx)GNg$$jMOF7jM4x@^G|tHR={2 zlM-2{T&hwWw*#4uOyH_yeLgLa9LYQju*Rb-`O8=cQixbZqwqhVx;aEjOtZl5NN>QK z3?lbj%2cT0*I^Ig^>ff0>z#Ua*Ifo|&I| zk|8W_Y!6mAtO4`Txnap~4<^0-q+KeisL1}+p7hfdvTuaX9a=?-QG8u5OG;KvAVtl7 z$fd%6S;0-Gq>~qBxx3i9Ps?FflCO22@VM|V=pQW#L=u2H2z9U^FmtdRRWE744{)bc zsWJcaNoUYAiED84&;%=OE1q-^OYx6ITd_z6sXv3U0oBztNv$B!96D*Q${$(K4g`j$QEsBDI$vDoTTUh^kTJHs6@UviO7(4>kcWyI&sI1R?tYc9~^ z;R{@Ji8(U!DLRnHK$czm5Em9$FNQE;YUj3F^zBpHvlr)DHe zxm~dr>yb5fiQZ<>TN*DN8a=qSB@=2}SeNK-t}7GcWpz>K@ec7S-F}obxXJM7_*HbA zO@E0L$0{S@AMNFITq?Ep;6&{t+*qOThVtnQ%vfOfvtO?}kolA8i zR9&Bnl7@p&D(B_3m?coiR?`9q$m)Yu%NqbEBgu0|Jc|s|*b{w|WgFYYU$t*6n-oJD zXN@IkFeNty+;O?%e(l$`?f=%%Wh;+9v18wp*PJ5u&KYah5`uqQ zQ+IdMZH%{eAzcT<8dZlF8PaKc!scWz^xX`@Z{*@_U%PhsvZLSHzw^VlvLfU!~4xP1U z1g(n)X_OtubO88mBtvSNLFpp$;k3Zw#Nm6^$!bVa&<5!VffrG%CuE!!5#oS5Y4~Yo zs>#pHvITPTK{kGmd2KdI)fN7pKAT%mz*qo27pgVBeUe(!6q2gqSPm6#%bzGHA23(9 zk)E5Q+X}QI)0QO#?%W$%i2#Yzw4Q=2jPpC!OP%{k&I8anXEV)NwuKyEwv8AbS)T%` z@^!E%-uqb3kkFcg!_Ws6Um?lOWI=u=G3b$CI<9Ils%RgEfN}n1)yTT$B9fGgZoG6$ zNFNJ&0p6nS_VtU1Eh00r0~6sWvPRB7W{!?+aV)XWM$+kLS#>s=Byw@{x&<|&0HVs0 znx*HBmC!nF_DEl_qrGSQN0*`d~y8F;iCC6RnC%C5ig_MD{PSnrKgLne<$d$<(oU z)8_SK4U*O2uu2VM>o;#&++i{m{R6BL@l2@~r}Mj%U%Dsj>L%UFFP*rYIDn!FhVq(J zYg5AGux7twb$Aj@t*M%FgTaj9mtnXxF~ljmZ48Xq{iweAG4vTJ@wuK?it^k5#WI7* zULZUePlxu;X?Z?Oa-Zi7pZUXys@WC}F9RDiGzzA3@z6D-6P*#UFU5F2HKGbuc zgY&*u&YHIa5ARjv4gl7p%a9o?XM|G0!O#pfDV1kL%3@SE1r$VPU?!q~*`G_4kWXL0 zT7giNj%8nl6~MHsajlR*WRRji?3u#Tv1UfJN}gvZ2U0{BveDjF56A;b z0*~Azgj+|eY!PerZtv0^7Y(fZ*m)z_-Z6KgHDbv4$|#2gCDaYY$20CgnbVLdLQrJ( zf$YCEEZN>ww_>craQpO2$+6aQz5e?>n@1Dr{r|M;&}pu%1L9D$r@7K3e%(OHFoC$F zj2dEY<(sYwL-s{E;qQBFQP=h*4Ziw8UNts@jH?Wjoz+azZNwTD#1)~Dt2r}_|1npV z{;l5_cvFBa(4d@q^t?~=)F}{J!jCX%2y~jeFQs>rdPI(YPKHnCwhBUwIY=ST!pWzE z=l5glTBzKdsgqA_NauBHp`K05-oaztoYhmX&9Yy3f;t(L3RC&;lc~`s?F{~Eo;sAB zdY@}S^0E<(=d=*G+}X&C(;{CG4>0OTu7-f3C5d+`CXxlxhIuE*!f2$F5_|IDx!c1K9#>4Lvi~gsQ~+9|vEMGOE}x zMaLL<2tGt^!Mv%#UB-OZ&K=ws`kE)_!AgO2&j3BJ;xBEX&H1Drk*%IQx4Rg3ip>T+ z?n*_s{f0E#{)H^ZXk!<;Jlk0?0`EBK=1uugm|&c0y85ZAN-u?9LGt0F!tJANHXv7>%T$K^%#+aQKfW*Dg&Z6xGdV{Inmq3#s7J}Qqb_hc{B7ZSJWt%cCPI}S z?bmo&N7(EEcMLuh9=N(p2^~ilQs&E}T zR(X<63K*`z;kP8FK>|^WA)wEhQ6rAh4hoi!EGuC-3~9(WT7U${&<{^i8;Qq+QW%2n zYF5Pzb=pZzt10}3J=QttE9;9TokzT?Vbek$J%ApCW|1*1rgg@~a2mT7<=6&#P;hmrZa)YoR z6l@j@h7V71ClVR?>Mp#nI@X}#sO{eaJ@o7WV;D?j(pUabdFmj&aEEo%)ZTZfOcVe&$UZ=o3ilH2<%SfZDU5E+zB8}YbTs@3 ztVnMBl_7(As)W)ZI^RKjDk?EV^eP&d97)I}D5;v0tvUa<(C4sJzU|u2J^T74H;U#r zzd2MKDMuN4zrPskzj~K8rSJUGcXnO!#1ZrRnj;-7wWnRDTaNvA&%@OWE%6W8~yxMFSN z!ViCSHx_VeMg6xjdu5yeFFG5Yb^MZJ8!6^T=c03_)}k`#&t0wTru&x!6b%=r94;71rmzDao>UHC z0T^FXL$b87v$L@@+0f)OyR5H!#H+-syk6yE%Lku!Zf3c`vtuzvnhB$*(?D!L)MA8O?Vw!Rxkj2%uS(x|RBymy=(&K~yh7>! z0Hid9Hi^w+mu_s&CyKXixO75n#z3)JvOiFQ*&kS13gQVk zCzHJs{%ZTxhM^rF9@CX*3GOrRjUU-LY`EH9kQY%)oCmh~7jYVMuZ?^{%NfIKCunod|6b9u&nRHwehwRz5E7G$TF< zYZ4k>KdyY+Y!>^bA4?nYE6M}fv%k*1Zi5WuOJcq({=41ju#3OY5MvfL99}pNvGJfd*>+d zI%OS$g#R>aqdn*Z2Gw8AlD~{Y=sWRpksi}@4(Qco#AC|KWGO(HNkX%Z>Rh{kFtm0! zVIER+I1clG>f^u_IEQLf7Zvj&)%L3F52+6IIVK$nO+Q)WxA02SvEw%>KQqP!)Qu3D zvC1AhpJ)gEtCH@tbuyAVp~^1$rSrX52}|kP4Kvzi6*n99$Fl#tUXFV${vu^Gd%eyW zh#N`T3Li3w#xzzpT1c}5l`kPom}>}eLus8PRngNOG1gda{E-BvtXoyC*!5vKF4{Zb zv9#+kf2fo?c4Sh9j|irB4Mr>;MUt51<8(ONO`_|v(x&vHns2+xY8MZ8dRJfBuxibQ zHh+1s&2G9?`H7>PjZ~+c!O~1-ZPtqiyS9u~%~WRB|Hx5(_Vc=b_SJS{-FPpfOU47H z5>uk5d!VLz%<~;Fy00Ww-B6~t=$~#`X1~lLcXhQZ4_1w|RT?XF5IeCW8zadw1QH_w zEI}4ED<1=ET#r%dL=qGd)TD?eca(RBin~wSwM6>N?Bsi1@tTTsDrT6}ReHH=f_fow zoox4%=z?{iS%Hvf);%b!I6F zX{PK6S4qfI>TyaYn<+M!3Wr;UVkWy$boi>P7Wc)&slgb25uM(kTQruMmTrj!{mJ#G zFWVYuA&xG3F*MwQdakqxaDpT*bYW4WCLrkqN9RCqx3#-{ZPYLvfLj)Ft3Two-YLqj0L za7^VQu_#+5Zm{=}pUHGKG)owqnr_G;F3q`!&jou(1SJVN$>V!-KS|drY{4E!j}TIHrP(Mm`X(&&e23Immh(kkl}MG{y@gAQP>i4|<823;Ouo6QUzet2&w)?u}m z#)c#5?c<4_-C#1EfBuV)e|&dOVtji#G8`*KL3~H)-iHql6_S{AQw2Bm#-)FG>8t%% zm(b-i`|3m8*MI84HRr$h#O-@~d-vY{#7_^bdGJ%$cZcfnBl28|`oH?pUoI`6RNlMbe7UNUSr#<}*=CNr4q+XHzBC;{lOZ{>(zTo6S-mcMlt=^sz3D4yPM`t>fdi19#qCX93^rw0)JY+5x3rbLHevAQ9gB^!Qaq>B796QCI9LJBaJ>I|$ zv^OqFg%7@s3f)kjggfI}Rj>@V2zzjOtac3_kkw$Zv(=`Iw9F6#>tJph7#5op-B>%D z5I1gUpa<9r19@+~NOS`QfqkMwiU@Z&q=9vNMc-|kAN%a}XAc@g@xtQL(!F}OKj1BG ztEoSD^iwM?eM`Tc-Onv=NG+=ljEo#u)gmcr3KHt{7bfD$v+M?p8E5vhvpTQKR#qPL z>g6dVBO)WSs%kV9Hax#>*Pg~Thd=(MGp%8((yIL08}L@!_nMpf2D@vG;(so=V`#yi z$>iGOWz;u&#i0vUx3sLjV2C1(B|eK76n}Wyrzgcu_I%z$LD$d7vts+X-AdBfkRBgy zvt+8^z`?0g=PB%k4D7!~VJG?y3Ss_KrJxBcK}Z~XK}?oq)6o1SlY)$J(ir_%YmzTF z>7{BP^uGra1A56L|K46#mOZGgVZzrQ0eU~pII*Oac^!Q5sqqg#x*zdq3J@muUsNfct~5YcDzE1D>Kj9%MaJPDGY zn5RL`Tz~L~pE(?t<-o|sgG&O-)~pC33$+w$YU@pATS-YYUgb8SR*0!?q2J}lqGt#+ zZ+whX{iZtU)F zed5`tl@&MEkT{gDCG3=JDB!<@I%Y_+)300;bNQc-;+<-Xjuzi&-&ymO))8qposZ6Z z{7HHm8O*n|29jm(pq<)=sr;`#{XQ|c0*(!uz~nLUZJ_@keE1w4hhE@pc^We$S-BWs zW{4(WQ3r)&EJ!0M_B|UvP=pY^1}6}Aj{RtdvsyD0)Z;L%dMMmxDl{Wg77}5cWG&^V zwzdwk9A5TM?aQ`xc|6_QmbKr!+*|8))wn2;u*6_Ku+CUsRTuY5<`RR1t6LY-A>uJr~o0>ZVMq_liw`NVvX;zcDqc$=cZC)E4UEOTAH?1Cx ztZlOTt&wzVRD9GauE?^rP^8}|4{?NT3J4oR@P>NV{_OXEIdI@Fzk6wI?~q=vFV>f& z#!qi)-PyX~BOb|6R9sxFb9DCgxV?S-T`mc0U7PC%m({0swl=MuXtiK}v)I(MrSWv1 zwbbEQv-{k-)P;Li+E8TEx2EaLriLL)ku6!d@aY#{+Op-P7oT1jtJcXzTewBM7>M0U zNUnsqk_&oj548&NJTGIZirKk&#s;Q-;RuI>06m`dHi+|iF+yZOlC|_0n3RCR;g6A? z3nmPnFjo**s}g%*e)OQ?9&~O#WltR}>_Owv(~nTNyy$UJStRMDIRKAMW&MEBIz=-; zUD%I?wc)$Ipuriaz{>ElT;eP8y4_xDnZXzjc#tz-2>P;9N!;?=+vvD$gk>gCu*~Tv zbPOuhY>};{c8hE=LkR;dEA>0gCgyR^R{7dfwIwnuGEVjo=V|b)-a=N*@u{VEPTCvm z!tsF7X7E<|(^rm+T$!$_DlRPv#KU!s_Q^ZNX0l%(qrDtP8n) z1r4Ru;W4oCjNqpfb~vxmIFlhNvEj(f4Efg_onwQFl?Bpibsf7jxx#yj`iu&-!w7?4 zC9q1(R7G-xVP%JS)tMYiGbdmRxtUfHx}>2_!sX{Z~IDLiHk_eBVkFd9?B*5AG zPx?iB2)PIdS?w7XgX@4rJOOJ!MQ3=7D4Nd|1TjlBkhNLP@(jr%p{F25G4OI(G0Jh8 zQQ}lS6_Dxz!XrCQ#z0|4;f$a##8IEd*dryR6S8dmGwxVs^0|J{PQ(;pDKldQDTA{| zXy((QG!RL@k@R8+H1WTA!Q4+VH`c;cJ)W6D0UZqTCVo1Pm;XiXQlMK3fV4Hq%OF*U^K{R{%Do`G}C% zKrk`JFfYDAhs$goz3B`p>An8sl@ngf;Cazyip#2OM=kw4yE z0B1VXB*APrRm%-*kq+KuwILE~$t?IAr*3&Z6D-Yh6xrQR@q&L@b`mr?-)^K! zf8g=KDACkO`rKlJMy-GH{VTB^<^4!I^S)&>H($Y-cPIIRQra-3pF-6Tv;*8LELkna ztv^<6V)8N~&b~CgC=CmYGL-hPAQ`9|EQ}hj{ z!ibR8b?dqXEQ}PVs4K+dFCip1O65+|})bj;zzwRVd; zSi~3C!eRwvQ@{a&b*SsirsOjcNUU&30lO<9?vQ>eZv@XG`%8OMSsU3zOkI&bP${$k zYh8lY(!*x>Ih9F2S;DBLpBUYlv@a`2!1e?&rysftHJ_jiAVr9531EXUI7m8hD1&C% z45$yx+Gmyy3R)2b6yN7b$+D0|myjGGSut0YOdlruC~Q7*ybjWX%~e)q;$F6bA!S@`Gr)B z;W>s_Bx%fGXfr4V?-;wuHWq2YP$Y&kmC6f)25Sq(-N(Nz9SPe433qLH_BlBKKq6f0 zjt6bw>?MX?+R$yM*X$+W=Oys9oRY;rbuH~jhu301TPV(-EsLflVaLA>(6Ab<(9jve z;TZp1rK!QK#iSs|j2c1%oX|*GwFXZe5)YX$GOM{XzmgvwGJnuy#$#F_NgUo7BTrF{ z0kRIb1<%e$jv&SogOVAZK*Iqp16)4%14)CCqW-uph9LQi2;#GIkb}n@NO%w^+%BU= zI-_Q4HlUc}#G#TSpOEZ!SUGxzdQrx!$Ea%7={syh1^|_e)6%@9`t2lPl%@3@^<}Yy zwYSQ3Frg;?NtVsR95P$uBmgkgst(YufDxLe?VPL(&qM<^(L%b z5KC4&N&|iENrOuc*zHv{wJvY1)f%s$Lo&W(%3l(3RW7UYRlB2=jv|9CVz)5Sz~j{~|+PAZ65h{iTq(*)m_EIu&>K^>y0fHiymP z@LJYa#O<=|NK~9}^_4nHwSEUl3-o6fd96xv~8yahE7OPQWJ3E~C z&gHOo^r?HtJL*xEtP_1;u{aw$24WKaV*MRW&JuIPn(FgZ)px}6JGql>?Fr2ZWbd)Y*<*#b0H=-_pP2mCAVtM9b5KM#P{aeYHj^3+%aKwGY?k-& zsxUekbDfzh+%LwU1_N!5RD-~qBlTb_NAt3Ywn$oW$CiQJbGmLvo6wqe>5FMQ5PdK<(fZjVN0T zNte+IG-@;;af-3J-QZF)hP85x`dM5DhBa_|t+b*_L;3?JVxLR{C%Xjg%6ZUzwBFu) ztxYXFm3Kh3S_vmpy@NTQB<6;Zu_v8u8RjbzWLTrwt5UE%*P`V1J2T|wuhqfX-6WRs z*a1-#0>YGWFRD~85N-y3_@MAL;c4NA#0$&CGPRQHREwIS`mDSF=_n)RYs%0gF=&W{ zUaLRNfhbZYG&r^Y8EQzS4q%c2Qq<@45W^%1a$>8Ls-e(AETM3l^Q>8TWlnLrfT08> zs+WHKnSa?hdF+8>w_aB7F430;8wMLt;kteA8DlodU;@Uc^A3y-`2vH(1O75o9V>7R zq_28mchYDP?KLZt-jD&6uPuX(!#9kU10D(|n#|S`v#~lDTUcq2+x<~viA85hoO9&X zTW=buSy3~3&)qkrN~1*qP_Y)Q zxhFUok3q#C$#}3k!!mpJtOzKqAY)X8 zB07<%-MW28V<;vU&t8`~{n0PpbLClnd3#3Q?mz3wd%pPS>8Sm?I4K?rM`o6FYZ4@!4BGbZ#@`XhupKGln!) zDmxtvREpJ6^cN-w&Q9H@yNsp>?@7Khd9NYjhU9?(xw@%SP;D{48DDUXI?I&S@8q>BgvnlQl*uia8o_JVF=5QS5hO@4a z3vQaiFviTByQ9X?KNw;U;Dskx^HbgzZ?)PS2UMD4#y-trqTJAN!;@#Yp81qXP212vnca@u7 zDQ%A_#nK;dwDcq;pTq6QUgOk#u6wiRRlVDqeZqx+z%ALA*GqBn7H7a2F}_{o6W?m> z3;VOz1jePCT@J-tk+MFUwTZ7*r|qwq%3T46bRoQIVG7dvQ^tG2T@~VDLBk<6A&&J1 zqb?0L&a!6Lt2V5&*@UCwW_Qq$eY3+cC1>7tHHdG;s^Dv18*g(~M#_w@dU{wtZIm`i zFR>NzID%AQ?mKCm)ANSFCnHR(A3kS!eZ%r|h6eX9tFK?We<+!51iwC=Os4C6zWM>_ zrIyv_4-OtUJq0rFK>EO%lwuCG3?~x9&1?aj_;5>@G|y&Hu=?TUm$Qu33xtbNX9wP} zlZ783I=H@vs?dm4ag0382q2^2q?YmDp{Hbbj2s`T!NwbIO!muqIVR(&r}S+p_msh+ zZ{4?RLj_h3O2qW0Vtvz!RcEFpOIb9cThX&?X+y)(T|Ml5$*x|lNcY(K&s;Lm zFtK*kxYJ}d$X7`wyUjf`KG9uSHGWp_D z*lB0%Xw_Q{hDe3qtRL9A_OvFcNH&DaeZzH&QR#mNme5_i0~P-l*D3YEmZ4miC5Bpp zhL1OI{im%g_Rdake9^-3SmV<1@#PzP2F^%FYL}k3pmy_ZJKF;@=jv`a z{`sHd4`U{S&d#8p+sIx~?GW{m=9vnbZ;XM&U1Q2CmkhcXx9R0HVXXw1&i-8FCHNGS z;FAO%(b^}+cRc;M8`^dCUV@XECFd$Jn`SnXz+SP^v_w}=NSuw&cA*^;g%wu+ zkK4joC&1`GNLyh0>+TX48NVsm;WGjL2LlG=EJ5l z*|v5lc_$7g!&?wD7I5c)64s*jz~#at!ZX6Z3vc6lZS*i3Eyv~IxPdlXnK~uVjWj7B z3an;G>7g8T(MnLNC1o(v`2;2U4{2c>#&+aNnV}X`=7u90?5Zj{!n{Frg$ki?x(OKC zlrY9%O=x!V3Mlgfb#wC4i~3LB2G2;9X0S?mJe?DY^#HK^iMq`_6BjJ2tzCA(M9=2B zaD_kdUZ$+3GZK$Op9`%CP}d`!H9oILxr3Z|Kq)2t~s>0;=N3%c{{h)x0ykQ>l8aphK#>?yuNW98BI;&jr5)sns&6554KgrI!0sd z>+^R0EF?Y+edWKZD#!L;Jv4On{;|rca07}vQR_2p@kGMW;LrGQXXRZU{HJ8GlokHn#v)QCPi`K^tRi`g(YMESLRlRPqrDf5&yT4RY zWC=AVYFh#(AK}=NP%>r62o>On9Zvc1)*h#VdbrY z6b`cLa%*l8L7i+8IABb%Xs}ld1C_#SS``geQHyCgj1&Ao(vXiWeH2%vC3avBAa98Q zs`!~%KAa%|9;h4@nX+VziKu-ZEk4~IkH^vTWj?pKoyLv0-Q%UsPbA_n6H&4qm6TDO zSWIJqF1#;RZeEOU5K~7N!eLFVUsm9C~$7miATwW4$N1&bRuEFbn2;gB+K zpl-Nj^|m&ft!>-tmf^a9w+x3D`G%KoXgvSc)zNp;L{-@i)(=WkJ!`U=5s)`d#hSxb zU%*<2!l&8HwJ1jUZq0&J$ZHL*s6Qbb7udBk^ePMScsrX2p&qxm4a|y9c)G%LN13)n ze9CN=GfuA;bYL^azSqfC^Fl2H8ih5XnsfWRcGo-F2S#HH_lzOtFnHN{TNYF~tNLn7 z?I?d1j)t8h`>q)sy=LDC`w$P zGWE1_*$JVv5>*vjopCH)s6vhi<^hovbWhuk8g|06p(=#L^L-!t22eRSZ}c%|O2j`W z{*`{0D&U3NCTi=(+ptu|c&)R-XH8YPBEFImU-Zu<-}=zChC8lW_KiU$E5kCiij^t=Z3S>DWDanxOVijX& zkIz2LK1SMn>rSf}-952-bjNV?udhvt|E}zpj)*reR4VnE!OuSa^u{xudi2<^Y2d!E zJ-u<$Q(wC$ot@b9^kc_{iu~z{%amW-vFYir-8*0!{On`jR{mADX330AXPUYH|6E^l zF82r2k^jf~+Q3Ku2h-QYTo)KV6lH!?9vz}ummK{8z6l%l|IzjyaBfxCy7(#T8fjEV zBWW}<8nr2&-aK8N@wkoaxOa>VHU=AGuww}27)%L0n9vCj2qBP!6aon(L8Ld5d&wp5 zUBXSsy$Nvhe#uKN34s~u@c-7{=SUjO7)<_oF4EB{`<%1)+H0@+E#~_tk3N%NOX7TZ zU_|}+<_>_EOPD)G-6Nj+C|*~yydoY~SDeU#XW%e9m?z-(X>NU_%ghSGAE`h&^u2;h zi^qnLWif-i%rq>EWys8og96>4eUYqMLt+CHz>ysvWp%4s3`S+2`rs|%&g^b#8L?{Y zKI6Hxs2IybO+$^1!;N7m_lla%rKJo##P_~Or)HJvGY?<1b6{ZSMTd85_DmL0lVDkh z#Xf5-Dk86NGE*_USx}{Ugdc@)8@bu7c$b;!%A^~r%z;?|0u`CkYsl!)%tT(Qz@53= zBu{&g3yo{JW3}J~<}Ozr9LTyhi&L?r(YYd8ZhLvCW7d7Sgq5~JI!Xp0-LSOgO!!;&p^NAEe(qT(u?8Hy#Cs$J<&F11qzR`FeAuWY- zofV54kh7BObU5+0(*MPiN*nK=A`5Xu%|UlGjOg}Ia?35dyIQwgx%Q~~=IxU~gMsm^8mx{SQzVd=?DK)^ zOge|bU8GLqb}jTEWu-uh`2{sB{(|Xwh-mk_Tz(25hrJ*7o@SpU0(jLHBYT{wS>f+g zGL%lHHQSUQH^M#L0-L~X!u{kk;)6$}9-)}#@v;G5IRgcitWyCRS8}AXBTTP=MUrfE za~~m_G9Ok=*h}eA>4cyynr_xQpW|uYe6u?ByJxRnYSzL@^bn~6@bz?U{ls9SNlYP6 zxxCCDtFDgu%Upp9L_L}t2A4_y{XT~WCbCNt$Y-jpt#rmN{^WCKDgW`=^BjhYvH}0j zYaZRY`BPVJM}bO;uVMJxs5E&Ko`?T>7UXv`4_4u!Uqz254Tjss>XD}vPI{~As=VwY z9yS~5$J)ZDIMguK7E+~9ydjd>5_ z=-k-<+!w|4Lu8$6v_;i(q0wp+lCyse(vIkjv6h0>rxdJ|vc-*OpJY~rqLKh73qDxH z=yMyzZN_|^0J>`GUT(zz*uNwUpL631O82KIzqL2+?VlNB*t31SUdqVtTPJ{J$Ef#U zHgFRMD;~hL{p`DxgMN}t4;+>LvpB90RTx(if<+#Kbn#BozzInct1_<)KgO~z&VuiB z2JVu+Hq7O3au@U+W{4(PgS7X&7=lrt-+96ZLKZ)xutz+-h$Wi0e0iJ77zI)hA+@kc zKZQw;?c?+*j)=-srtq5qMHW&DIJ!4mgG&KXWCd5VmdNczdFi4sjeQ!k6lkB$J(#dl z$fGV=xuW1D8^Pa*k;Rh~C&QvD!6pjjusJ8uwLg@T(>h3}O`Y>=>w!@|W_qGt-ob%#+0jmpPR?V38^ z7`Z60oBEgfhqh*xGBx};YbWVjp;P2P<3<$ope11ElnUuyI+}`8diH85C>*|G_iuDoDEZg3Jb zVdP}Rt9Tx;!8oVj>~)J$lNz@f!wHkcA_m$60|Rr4A(#Bg@Fovv<;%RQx-n!hnypsJ zo$xrv*6)}cw7Q+WXPsN0_VPF#!XOplnuPs3_vz!|Y4|57 zG7XSOpU-`stw8uajo;E*i6=1?+$Z!@@4aI_Zhlv&5@ImAbbk0l4`l2}dd@BT{Z z9m93+ijuWwKIwQAt#1|(BaDnetnM_$=pO~8FvW}oG9p%tbUVHW;Vi=x+o%`jC_;}S zx`X?R1q1*z5RONjRxJk#@~k;=&QEt={L`(QpWQ$I`cK?3Mzq+vW8SjcZoFz>`w!2r zTf1pflJ0U`-g9$!iFktDYZ` zUZa$(cLiF9rE3C>skmncLgow)(vqC>De0BDOA0}SMoeiPB85YWvUoW(C{EBjDXz+@ zvdb#N5O+cI>zfLRTqMJ2^d8XgyY`Z0qp+gtIsq>g!J|^hVn&uD-CXuTIu!v!^RqMh zipY`UX_cH}wl7JcBiu90j7g9fW&(F@XyXNW^cLtlmysd&k|-kzY^`Rk&=$AW@w_DZ zozW3JC_zqU)sBlWyoQP!#p&5MGBo7`6S!lVaYwSKFGQ7*J*c576iXa>|6s)lu(V=` zYMNR~Scatl*j|{e$c6SQRC9W{)5q2oX6%Dom3IbTd0<4VgY1=kvbu5J zK()LE8?LG(5Nzc&@)|9eKKmp#d6d0;K28{zmp_~gyQ=wN7EYgik{V*6_RJo{Xg&=e z4rS8wD#jnA&v0Ik<(@>$_aC~MdT{CB6l*hFp*=0l26U_IK&DK|5ES$n2r+aXfjNEf zIauwoSp5xV6kvd410+vGw|F#1XayO?Oa>sDu*C~8G=BFmd`JYyO!P~jlicnpXZAdE zzp}Oc@P6@u`D<47ix2ESeBSeF?hbm6bSU}Jf4gt<&dutX-f)#iJrkSv{o6;AqG(yL z@A^ld+jEy#c|iP3f4{oXi-XWQ0_1Xkg z<@8PB2xN0)i~(yqIF}fO$jGh4CGi6(NdL5E!8s38KIz0_hIarjC?Y-x7@(?{+g0t- zW3s(#VFko|AoG;>HW3bXQcsDisE_8J+G&Y;LkS}cG*OSY79fa-uE4kY(Laf=nkXBA z>Gke#%v}pN6J4t)_$2#yMaYQ&V>}GDy=%4Z_nxGU_n73N3a_~RKrpiz1h>cS$qF`C zIN^88m*if{Yq5}&dr*JtOv?KvOS#_-(YV_T+(%}$prtS(k{As^sTzh{$}PxGQ63bE zsmE7B0~PM=WeEjfFcfP;PYDthPpJrb=5h2m*fG|wcY-kE+H=$ zp)dzRWiqdQq8gEnamPf$``P9ClDk~(4)sRFhqGsreFLc(bZ-MoC!S7I0@{p3H1$i> z^aPYx!14>|vvI6{?}}y)9$(l=pz`FKb0ZSK6Nwl6pnrkh)CyF&Qt4EWagkBnlj>3y zb90dytT@OM8!E`A3%7Q?J55LefyRM^EHrm?G$UeTpfT`{A`jKX<2CZoym(Fs1Va@l zw3Rd|wC3c@L(7&8<;>LI#&1o3TSp==jMXn4ZYvi%Js$PN^0win9V%dK#rJH26CdIUKb=m@T6Wl%f2Bo2(Hf*XCw4-x-F1q6NaqRf5CvP zdv>+Ho%^LN4k5@YFK;`eM|Tjcxbd9fmiFKLroDw66y%sN1XqaPvO~w@!FS60kIrO$ zCbl(8-(X5;MeX;D-;Whb7Py^{H%(^}2d< z<9M5{qp_rZJMhc>2VUKu9IR|<2@ECozj|>0%ll~ziB)X=(ic9zp?0Ju*3b|O_g8KF z;#Z!UdZlE5XCrx&6&<~VeoCRAR$*N}8$G4j&xzRi5)xx;1nHkztV@0oDeNPF10lkB^?*3%!+M!r%Xl-kDVaw!joTFQCQ!=TeS!Nnmz=8y7 zIEzoTLEK|D=YZFauyiMgbIc{DArYOiZq2OVCv98nHh(GMc!}aLVOXj#vT2!c--@M~ z?Vd}jRWIhXUpVp?3f&`)x1eB4vsBCUMo%D=3}ifkfF}bu&wytmz1FEkIXS~|zZ9dA z*1LU{9+`V-2?9TBJP($aKS;Qw@PNAZnA-mMZgD#~(yWj4v3n8|U9BJMNvV>3OR^RH0MS%M#zM%d} zS|u;A^ekJyV%RCydMhftz9!$dZ&QDL+gI*Tf9$PD$#3TVWjdM+vD%}?UwQ)--YFDd zaAF7EokR{Y82%f5P&rq zfwZ+~F8Y~6D-zSis5*0VoZOc~&BF<4Nn*G;wC2R8*RA{Xi8XRn(4G65J2<@SXkXva zUBjoQsZx*c2No2_{WUR+Op|%TiQOkw*VU~)vCrpp`WA0&bGh2c$t$FhC6h6yfhRX( z4(4-ZHKqh8CarAGYt574y3sl;4%(g!l?^hopAGTm&CT>P%YJgX4IB_WFbo>riW@Ri zL@hD}nI^R0A@qc!A-1v%$=Rjd`&tle(ve*sKZW?J~U;u%I=Sf^_Ne-BC7ZLLk^_=ozK=hKq9{TX=%Bu z%4vFQ+osJLw8_2dtIGBRe?88fLrdNz%&R{Q?_5yFy-lpF`@`zbE}^pS7wk7AB}(U- ze0WkkpRL3|KQulnF83q=)=Zijq$YC~l57pQA8g0)LWH#$8`d~i!OpaM)Qm-@upR>ktNZoMAQVmf?I`dPXlFv+zch}Z-kE8IUzGckBB2!8v z7OyhmQ+}_^Aug0I^?2Og+zIs?4pd*M@Q9V_o1Tiec%OK?dR|VU2H<4XnnE-Iwla|z z(g>BRcq{^X%3z+R!PyCOQ_~#tDkNL1fPlxC5whOc5`kvFBtFa*i!^S}9wikz847BNJ%qJ6dE>MDsLCz1-@Nw7 z&5vx}{K(Bm);6;T;Nq~sB=@t2`o_MmU%zJZrf;09{r>*uM{Yg3uC;aD(OW;AeTiD2 z|Hunegd0sO@KjOYJ0XXzWJQBy#2_u0zs10G61JGRi#DZ5Z69LxS_Y$H24dTq?)E+B zB%70G9ldJo8ue|lSD$g}>*Cq1tNWv|fmO}wJvUNZyz!~%U`u$-4bSdhfA{#xSFTuf z=ji;+BT>=NMbnWkRDrV_8(h;mZ_P+TO+)b@I;5G|CtOZ6*wf3!O9-8m1CQkc_}gqH z<=$9^gTb|bI8aETg>VN*ZWad!ry%E*`hr7o*qR}&Ans8{Gy5k?F@LbCw`%e6U45q2 z?wvP$VOMNXopiE(QE$@HXiTQpwuIXPOAl>Z-5BeR_%{v4dqeT&pvACVZm=$B$sGuF zhnJo>XNhCAB*lkT&x_2D!0F|eB~kMD3_zeT+AZ!%v)yTFJUlXZ`_|gAWK~aNNW6aM zH@Asm+s2ER)z*s7?|kUUsLlGcecr&*hL@^>-P>0V`pQDS^og7AS-<4=iJpa_P<+*f zZSq@A=S!`N98X(JotrONqAsxcY%LSr5y=aU4(Y^3yK&8fYh#NR452t|g%e)ri{Ci3 z|Ebkg33pk%wNH@Iy9)EXIlu)8=R}(a#Uh**R96yd#N{z=Mdi)|vqJR7t*hJ`WMULe zG{|bGFlsDpVSq8tEJBJh#+I@Omj{Gz3O~Vfum?c$4@L7sVj>Zo5_=&-XyT>Xm^HN?<^vLwk5;=OW@F1V1M5f^&#j9hW6 zh__VcB`1E}rHO@IHPF=5%jasH`A6yASe*$69cvLq&vh_f$|w_AzKBug1po+v$84e~ z6-}h%l6BGgBu9;S3$IHQ94?nb6ey(2<@m4va%8^<6Cfl@9~tiqair;3u2!Y1HOcZt z@gAaFl$ch`Fe22VIDz?s3(V6C99DxuBIC4Tb%atCUedX_$oLl~PcRRM0;_l$a-bmQ zV-R?hB$P`g7_*dQ{>ZdPtk#OprSt#>WF|$Cgd}}bz5aT(N*YDQ)f5QXtWL|)-UGej z&Ld@&PHUthP-V5dts6yyxo%!-D#+4}zL?weMKgOdT>q-HWV-4)(IqMBV^}J~Wez!0 z@ArBdyiZ@R{`q6_V`6V~yq25|aE;7bdZBdKpjd?nFQ zQ4A&}P{5Q*qFe8$5x)4c9cc$@N zEv#npVTffmGb{}vHBI+ru@GP^WcAVvB-%@Iy9w;5rLz6%R9sG@iiUKEJjbbcHiI9i zi6p9#F4DNd_vP4e`pAnuDe*R%FQ5Z*Lj9Eu) zgG~TTlBp@+k`<>>p9hyS)sOf*WgX1?%~MCohQp7S{+9BAWCh5*ybi>(K^QC3RbB4* zrd>ouoR%`B-At&XGd;Ys_vC~RK4Yse2+Bea;rx%Ooh5o-eei`@;s@wl26!CGtVZ2U~1Ie=~TKaGl5^BeWI2%#h+ zc)o86IGv^Yj3~5W6zKoo8yDM$ja`k?e@D9u%UVs&u115!a7e2Hi=K&zw)BF}6>v3HFvNw=cFO&QSq-SUp!JvTn_-Te$E zm2aN)9r-amm%p{qXs|e3<$!BmIFYt_-Hof)Zfd72|19_-v>ZlX>I17Lw~N+$+BdFU z)9Civ(i01va#^{{VKEr}(Lh;jbmyRR>&bodo7-f!FW@NyWOM7t($Oyaj?B4xzxVlD zRej_J8+@8~5}woB_VsCH+*#CfIu4YD>Azl4$L2 zZ?GAHL7%NM9&+IEzelnG{m-r{zvLQ6Fi~mq1%n1#Lwk2?!eT48)URB7H}6)4m{(d^pmSBKz{G&u?Pfn#)Feam-FDH5@(zE`@w^;3#H$a^KJSO3TNU7 zVA3@ejj4p;;A~f1#yb(KHmFbghk~)envf?}g^VLc7v9QpQ`?rFo_)7(n$~p_w~CV}aJ9U z-R@dkH}vtl?p_>d3xpds$|IdeM#uvgi%W-dZ|XlxGfF^D-u$ioy2Fv~3m;&1?Y?KO zTZP)t`}aS0!)lzM{#PRAYE7@{sNc}ky=&RL!SO(>eJviN z;Ixq=o#MB0Z|Xm|Bmi51iRUwsyttQVWX_J_nbXeA^aZZK>o!BIgiM(RHwAxV`tyw6 zBt7KO4NZBx_k8eap_&0ur)J3zQ;ZpLy>rHAn&b!y2Pi<+^T0kP@j*}5gb)st%jF3G z25Yhw57Q_T!wLc!k6VQuRj0^#hzB@ZxCR_1Pi?4^Y}S3LL`g)AJ-sF~E$iLdX?aLZ zCg?_Cuj0g5ZJ>G|Bm%@itEl%%VH-2BG8v!+<2gOuoGkZ#{id~{n(K~i+EMH7zaNq-MJi=8Vp_)+%mWSPH$>pZv7;6OE;X~knj58*BSOR8qfs=|CwP%< znlExXlzgoO7BG(!c%A+LxePQ#YhGLQAS!nZp3%+lb6tU~-cO^qnIKG6;;E-eT+zLo zT8$H|bC7E3I3-}g+{RW!oXe&?Qru}8P)Zi>W9}M78>H^hm73w4cm$>i;}MKyRL}r1 zy@OpyHV0;k!*!Z{FbZIxXF{n+W1CkYi4XZ$#vUSB^`qn^=lhx_A;?wNO$jDHsfwC8PWjHE(d0b%oh}0 zk-J4d!1B4kPY^a%o%>bwr0CD&8_M9BlQdM~qgh=rr)O4QDY;uxy`SHXt_Wp(b=Tu@ zwYTXmx-A*j1rbEUG`cX$6j%j&4&f;392(=K&mI}*B>%{M3=t*Op&g7cMgvhw8h>Wt z@J!BAP(kt^-#NFM$5py1Hg+W zGR>d4p`Z;miVW0ULwVOcRy_YuHbYAcki!ZYP~1{LTvFeL^UGvFxi+#Oz%@o;O)}2b zENl=iU}hMWErRAjhWnYye9$l8UNC3%HA~;!m%i)qXLerpm;JtE1nc5JN3_Q6O~m6! zUNsxdHis=TQERsc{Z;){edlx@n7Yb>B6PLE@?a>?IXIRKG&`&&x6j?Qa9x8Z84b$n zTj?RA#b{|9T$*wqXTV}BGx!dk(J{N3t&P2!|*ISCVu%6Jm%hw{B!`FvEkgST7vNE0d5BJa>EUy=YTad&1A-hYAHs zu!cxsmtV^OVnH1R(`oiFnbp<1EB*2IDygruVhLihejpwwgZWgKbfFWF|8S+SjVyyq zUcyRDq7st~=n2^`EmIb&JVCNDdgN<7UXFLfRlFUR^FN@j{u|JJZ_9%REQ=m#C>Hmbe3J}t?4iwMd);8ttS`J z8)ApVp|f*;)%J*cX|<;RZ2Y)w@O+XxXX2YMFF!8;tiK_|Nc0aEe-QCYmOpR;iZekO zANIRPbBxf$hoIjA8JNUWrj0^Mg377o5#J)3|LeIEOI-2(b%&OWp1Xe7Kk~w{b3T1& zp!?vX7wmj=fyjN2mWo8*s_yPpeTfQ7aiTGgL|J{u1c!O6-nx5dorZ~G6cQ^hUbpVz zm5Jo4izg>9UX{#)l`OmF8^2fIxw-$!%P-v2m0G-eVBel~H#~jj-f!HxVR#9=&m7D~ zP1m2fd&kmw^Oo+|eP(@ezA+C|iBtj`FA%nsqpsxAs~OMY!Cb9XE1zV$!$2OTS>%2<+{f&dSidFyK zeq8nhvhkceAj~lu3jk!rc?+W@tfzDNp$WcMeFX5&93I%O-?Fw9s#Cr(;R_;GT@a+4R zEtcCzYAYD-#UwOQu~BTa2Av`+UUBLI#?rY6@6^;Co^xrlt6vo->_}U0!2?5OoaF-Va?Vi*qnGl`I&%|X2JHb_CZ2|;YA>^aW6@pmlMsg3N%!0#@t1rz0VE9&GqZ7eN=2iLZ@uRSJ0%$jEvFnTJ_He<0&SYfl~+X<6PA+4bmaSL8a|w=VB@sq5)g zaNPmC_2j{!>9=W%y^2Ag#6#*)-Uc*S&@L(kLGPi8+Z1hblkgC#B>Yr(4I@Q7nd?1V zfo1X*TeRTL;j33(qtuleX1yYiP-wht(aUSS+%Q0wnTs+Cv^B0*bK>xy#EeYl049c! zSrZGao`Fqhu`H18V8$TfkriH`AIvt9O7t2lNYE=@ADBTP$wNb$Yq~>02jC4*lbS7@ zJi|%8n#?VfL=#Gug+krc&9t70X*!SolP4&W6P=ER#oIbjJH0+NwzX?=?8}eGmp6B9 z9ZS`ZZ|m;dwz$Do_A~KyizmA@)Ef8uk?W7CN8bRDfT6?UMJz5MmX%f5t9yc>GHN;} zAX&<#-h5OXjbyJZx(3xj<9=giy!gq2J=OM#GCY&GMSnDESbw?d?vU0fVshCV(B}(@ zP|T}3B7L>RkJ^0E!BAzxqHUdBD?AY%V1bnnnm#2I#6uEk_chJWKNNjkdHjM z7tBezE$C4zq^pd8 zE>UhqR0_FTqIfx#X4(AkN0){ow&CG++19vntgm9;fw6fTHtpyP)ku#1$-M&|yV`VV zOgz@NYjM5r^6D$sf5#{Oy0USgPF)*d#g!6aYni3zg2C=})fus2zXJs_g2poAw>wvQ zE-y2(L@f@$gt+dC2vvC*Klb3(p#wecL(|Vy&g+kjA8f6z4|^f>w9E3Y$1Psc_&W2Om1JRqitV4yT6WJ9p7t~vJIPy6CkZj=tN zj<$*G5hQA-#e&5lhLA@z3i)=6=3TfRnRmAlU*_mR8t2mlT2qYb*m2PS!_ML$=sXq= z;XhS%6dqK@QX$_}uZge6P9YweA z70R2ar@61_3L&FF+%U5n(yenD`pi=Myo1#Ym%6zSp`Oy*sMjmW+aL8E`nP%=fkM!K znia5ao5?g20^Z>onr{du;0iPsF?8Ljke-rHZe$ri;MXIf1_+YRVk<{6b~XYj`ABjx zK{r%a!|H}`Ht8oiNim}71~3@#=|w=Xh!Y%1W$jufvEd6m&O6DmSwtlghC6*pGP#7> z0roI@i1n4i!H}pKNPGAc&z`FRtvTW_UuE%d|Bv5Tpf^S>*2PJAqWF!oU&TWr;f^W= zh!Z?*57~R*$$Ao>mX$|u3^|s)G1_5oJR|XjnxQuqyyEL~HK64La=-kQY`u=4TGkoD zEg0pZ4B!Hoixxxn;h5hH?efF0LX+Y>Nk!hVTE2dgbpWEpG!z*1!*Rmt`>7c&^&1wP zHdizN4(lYXh9?=RXujXVl9CjM zX(noUcHD9gc?JIv9K!-(v=s(R6e&o01R(rdbbxU*n5JN_f~v8r+#Jl~f`N)s9%fBaOA{ z4}jt&(8kGAMAkEUM9wGxF2t^OEB(vv zT=AGVaFxT63^~eeLzgTbzh(N*!BksYlf&l^Cn|b2rF^k)T>eff)VSxYorwcaF4?rP zJP{5>ji?FbT|78)*Un*O9C5dtgK2_Ig*XdzU3Plt-eT|(-rq2 z79-}XY;LG4b9&@W4OQWYZ16<8k`0M)Sy|V*{@UDk6XB{l$q?yU(Pgv2LE)XhV=Q4- zJWf}0l|e>?Ld06#><;*L@3#jKfNL^0F0KrC)lpYl_xz4}lq3jO`Pz36S112BazW}E zyuF_~^O*X_lO}jkDkJ@o-A}LCaC3WYn?sgmV>G+}(_-?S7e?^j$UbQ~Z?!tDKGeCh z!(tAhxP8}0;=9T-0Y#t<-IPH<Pz?gU z9;ds(x9io_D2})M(v5AAo{4Se^i}i^^m@u#+8fLkN4eD*N>!J|HZ%kfbb0vjIZ2Pd z%+SC6qWY50SsjwG>P=5pyOV zxQ4V|=2+JBa|APR=Ou~Qyr??)rsU|;%l36{UYMHB4g5iR-0%0+HU&H5(n_BPbtgV; z@jG&>yrHt;HJetp1Ar5s(^WXt}wN2l3jj)(pC9Qy(>1a8MY%dm=*knnO^fB2}O3e z6grA5GD&rfTU#!7czA z5YlW-Bz=5*?l{ajEd#`ksEP`hQX%I0^kOR*FsoO@U$)+;8Mc0BfGtOZczUJIGhle? zpF|H^FwufnOnYHhgD}V5rVG+FMOK~%n~bg&YrIrfaLDu-$lKdcyUkQbX5x}y%9+dh z`}}HId3IlXak|%QON1(G;$8CyRDy6FjIwMp-a-rW)CDG4Hp>Hhmp7B6T!${0f^3@% znb;?{Mf2@xrLf=lnz8Tfl`Zbtwi=f=l&VSAP}6h^k*VUM*t%+8UZX9f#Hf&^sTH`V z^IMFz^I1Ag=rb*Im^;f0NOv@ux$RwpI6$neMbI0Bn2@89O9JUzFSVpLv+o6 zd>iSta%Us^KEq|3U(oeBDtN|AwOusJ6k+jhh8Fag@D1T5;Wxrt+SqAU6er+A_JF2d6t{UvkQa{LdNZ;>ItY`t7qX5<3bt zJ97tp4Ze|sldbJ*`Qt}wA9n{QMlF4a3wdnMJc z>WBPdr*=^N8Xa}3AN{$rIaG21=|jjyVF(|DFAua@jH!kNty|oSohmT669+`L%}frs z@Uka=`M0Z2{Oy-dUN+9RuYUXGCofxQ76uO7`Sq7Cyzu3(-+5qQ;Jmx|_Fd-epMTL$@7%a{W%jqhcr2KGb=5kvGIr#?E$iuzhKGi-Z^@RS2PmErW36Sl5ef&zQ+@>8ome1y z#k`3%1o(J$=^^AosY4^_etJFxaa z1C2#}NE-pQ%C!0!Qq?@F7=d%piLsVJU?ZrsBn|{X?lX2XcK{FT(8%3v@)4X`53Gi` zrkh{gOINcz5~m!IaI~`ncf{?%IB?T$vQ6XLe2?3`^6r(nM*tDv9$2`jsw$T7nk?m> z!J~uWRM3(8?8HX6#Rdc%8P*pO>qarX+h8z0hl-PLZB-E6Ik2FvZ04gS6V z>#!tkTY2|8zc(1l32@+D(P+1}b=KQu10c|(nl+zV<1!jut6c9s=~`7TH+v?^4cDn} zU01g#Q0}rwvdtYv!id@Ii$}zqp?tz)4ECJYV{}*sZ&+(6U+OZ-h;a$U;#P;lXb-#B z+%k%U*cC?co#9J{+s|xw8Mn7?YXQbznavzFTdV70PS--CE8JVP0VlXz;@RpwF8GH9 zK`|2b*fA!4#Bch6?c$Dh8yQ$La*ptAR+K40QE0{jDv+iwMv=}(&*^_n0s%Zvv-2(3 zo@T#|0?yZbjtG>(&DyOZ(FIWk8Xz+lqaqx!!aJ44U;4zk>?%4+SGnmbeu!O%Nxz4X9v5#%k@6hVoCy0d)aGEpA|DljQ6X1LvM zU4dAb7cZ0?S)qC;(U~$VPjz;tRM=#V#_XPA#{B!snNFWt8#DPQ5SzU~S6 zKp_`ih)}y1{jFB9j4sY~r8?g-TEzvdEQgK`i|^^HlK0TH)W$tYtv6Ut0l%PBcVnEA z+^Yp9$TdCon_j{^Lrt4%k_L5YDhnZwJ7P($GQOPquKwG?9J7Vv(%@8+u8NWLCFfnZ zx$hcZ){h7?5i-YsjsvZx1aX1+gyz!Ibjef~W;vi=QS3))>Z5pi;LLjNm;P;-k+MMf z0f>DpNq1!GT>eUO`FK~LS%zh~qoPzr(`J+D~=&`W}7LN>u0K&03vTq%4OdH-11 z^R_#ZY2brpRh(enBa2(XkcJfTKWB($DyhQcoYP~gTQI-PQ(wQq(}g;Uo~ch%b+7EOdK{sI-If|^ z^bZd>t6lc8c*y7J-7r{VI2?+X*;r+X|w3G#0p_J9qeDtat z#unf9u}fMkW+|vE+}byI7dR_W=>7+2gWd4YZwH0w!C>4b08i$uJu*m!$@BoF53=Zl zqRCRK)0y+|*6r&LFX%bDDXZvQi|sh8$q=iOzgO<5pFf?IPmmm<3rO@0wpX-=fJ@A` zO{FMSju=<^qD@^jQR#Q;7qh>gv9DHsGty}n_nGFS3dj$|)#CiAZN<%^(M>4=EYcdU z=~_%YpL#<9_wS>MK|Gj-tM?Qppvl0@28}Ed@-1U}FgqR(R)93fOwjCHgq)I_mlRxP zAYjaqVsf-|SqosF2z6ir8Hs1~ge0*?0LnnyV?#q@^n_FdlwMDUAUD^3eopcAVp`15 znYz?3{(z+XC=kWyTs6J?+UDkKm#a)B1KwB^Lz3ag#0)>CU(b%E^)t2WS?+g~_ZU`c ztmM3U30_rKg7QjVJ}R*wV}FqH#J!gY=jYb1?4KkC20O)673v_7X-V%1^osOCCACY? zO(i?RGP?|-H!uRj@5eR9Ic*1b^LscD#3ZodbFKUv$N{re3X>q)& zJz0^Q{<5z#66r`)7=CY-)m;b{mA;*uFjO^kbhae|*0;h5>GKYQY*;DZG8Tc{_@)=K1ip}o}v4~YiVfczpl^g zjln2D27*)T^Ws}tB2BKId-PNqLP5sd#d|?T;B}lxqh~UezLfCQ2N48}5KE6D3#DXL z&SGxpp|I1L(xV&=mZImhg*JDu{zkLV0=R^!RSiU}8v2e*bd;vU1NFG6nCK~)y2D;o zP6#!~plNiA6Y{q$HJ)%BP+`_uucyW=E0U2xZ$g_ zI=_d}aDGy<8_W_vXGuP%afV_yDBilNxP4l&RF@N?a%p(y%#v^fz$wlO0iAu$3tpE= zsY2M2`(35Xl`?L&=t*sQil=b~g^?+G7;- z(Jk1JS-YBca8h`J)(+0+n+h8y4PibHw>{TJGR2n*q|BXqqo2 z7Apx5H4BWiGwky}V*~VRysTRB=RoW~>CO3B#h=<099z5ibH)_~luHDMQbs4dQwHUq zc!N0}iMO5;XtI_DrwYJf!Aj3G$~e3rmmtRLHsO=PS6%`9Y= z$rgk~n;VCE5T#XQ+X;A?I8f1mbZ{ucH>@jDek=?tR1+=IaibqKpu$S^=1p*VYb$l@o@KV;F9e~m6K`u}>- zHtpAcmwrh9GarUl~}z1iM)D_D^H}7l!=?hmmKJ|`^!lg z2#zgVk$CWnUwkmWa@lCel?aD@cAtI2dE+-tti5*B=5d7K72mjHcU$Z3Gd9Kph#TVs6UL{cwu44=}daCM*}=@u`X84j+)1Kn)t~u+%Rc>YpvRuTk@bX4s2xRjc*Au9j%0vd9DyL@Xk;QZ(-pj-c786W9U9rYr zW&EAa9z`WlTU8U89|C^-GGxpmxL$#GK1uO?;s|n97er#bi(5mni1d7Q-SlsDdFr(c zo2e*07_1=9sJBV;pfkmOGBbnBfcYb7R0nGbYkqwbbm_sS03NLsig2-h`NrP!4; zj$BmgjgnD9|A~PN6ltFnd{L@_im<+YxCC*UJUo0t%?lX>EAH~B-|)C}@OXj(=dcJk zopo}oRX)2&4rDuXP$fQG!Z;R5EyP03!_w2e;tOhP7sR>Hv%Q<1hf$DzGN%zO3q#wP z4U3HE>Z5Lsj&ExSnK3q9c^-q z2AZ!{z~W^FcaFwLtj&IdE`+{#ka+h97# zV=#B6_&#F$%w|wc;s|v%uD~_~O)c)Wi47<_8%{YMB~3L}vHE~_ktr_*Lwj+GP$%(0 zCfgWcfajq?({EEhFplv&crxdN=s?nyF>%DDYy}d=y zNYsyS?y!Ke97J!zkPn--;V;=d;t0ncL6OzJ0I<(yWEuNc zz1QW;BAr|J3hXG|n|L}U9F%oDIZ7C42BbACH&hJ%XG(Yn2@_f$z6EFOT@rBZXDDt)rvOVopx2EJ&ENH6zxVOPN>$ z=|=f=n)wvS>abimgRBmoVJVJq)eopo5ABpV6gi$b_fIfZg-OB8YRJ)?kxe}_)B1f% z&MQP(dR>U_?WCTTGI69nQ{8x;u#LL$GZUd_=Lz!s!Tj1mjW&#L@7CFdM!qDq*oC_- zcG%CxVSD@pEb>6SoY5O(kDoz*g~U})k%1l}BQTiBEYB?S#KL*M=oWmEDVdSI(1k1j zc(#UL(y4aJxhv$TAJ|70b&8O*S);wJ^}dDtU3q%r_N|_JpUs*Km3!?=4=qm_m)oq4 zpuMA0M?~@^=f~-|({FbM?PH5{-$<}^QL_&OlKboj4~DE1k_0Im@Ch|@ZK$Jyu*jS~ zdt$-ZMC+%&tfyN({Da?L`U&;_9Ib2$*u5}MSZxbG`;8x}Kg9z&!>-Z$C&>>(PdERJ zO_TL@k1Le4S<9-O>wj{d?pGPS>?7wkc$4CnPG>xI%hND2Ru3^uELue&2(lD6X6xxj z*c1V_@E=7|C|_Tpw*whPr)fRgh=iIZhKG*~A}9b@oZ+L!aw>8omL}dJeiBnK4WXFH zfLTG`O6$x(`o3-Tu{$Rx?>x53zL0iiMKX#m{K%%xjtv)$krz?Ef<(HceHA}%wJq0A z)7J!UF$H;2PiQe@RDl{u6Pz*=0SjC76uhPg0de*3y=fAAHk-)^-M;?F_~?<19kAa+ zyK@-y6CfuG)VP=_a0^)SC^iT4V-S)gs@{3YPedfl{7ExHQDX4EbJ2xdSbDt~Jp=_N zKa?bcLcp!@Q7UPSMp$%a4ZQc7NxD@OO^Hq`D(zAW^*$ekt?d87^1!?R{w@E`ehORh zcyH>UV3mz`4Ix-1+1;IlUOIUH1`1WF3dxIIHD>iM-|^P*kPLChAFUCWyT&3qM1!|3 z?ERBo+AQS#f%*q;ZtN-Xzs3LlYVH9Fg)u0d=k#3ole;#pTn3EIKug8a6Hz0bjOe!49P}T_$KGIw_|Lzm_?}R zooIGcI_TkeA9^x{g)qaYVPlE-uUBx zeR0ck;)T!PKy$x3%X>V_h?)r{A5b$7J)~wHfSK~)?6a+tqb)YsDBY#^&s~yHwzZ5- zw#tiI*5cT_rlxuLy|$%o)Q%9VWX$HTsj!c>WuL{4iW5q`in)9E!k z9TyLP>~@MK^kCkC&QAh0dj#+avZ+S>CB_vd5)`oSWFeQ<_3c7t-dtO1hB>U5&q>eW z%w=X@D>1gmsGR(xU2Sn<+{5fm*)BDS$z7pTCPVK=Ki8* zbxMzwV394+EfjS3+`Hsmx+1~230O!Ap)JsoHD_Oo z!jg+0*d)0&J#g`o_N`Y=KK#vHPu+KXTZfT3=zc&opK<3WOb#ftFsqV3=48+q4z@CXMP}_NxuehA%v8a7sI(`MbAE$ zHapjOeloqm>Yj`-%vd*!>X`6Rw~0M2-G1t@-pQuL)P)877cfZTfO$s7gY4)WZcJ6v z$xvFMLI&?aLu9X#Z;J!Ps2P1C7XY^9G__D@iwE|BzoyfOXvcrjtH4p;V>U7kHR>nA ze<61K@92vn?NJx2LI2!*r5RlQu^+nDQ(^(1VGjS$Kaj}_ep{*tgG~^X(b7&%NiXa@ zCWYyw_2XOG3$|-!5i8inPGd`J1X^s~;9M|0pY|4M6H|(=l^WR=BUm2)pEfAdiizp3 z*|*!bjyD#WuenvN*uwT9wi(B;kpf(7CIA1it^QiqU^q-CY0)x>OREf^P|Xp@7Y5?} z%(J`-EAWNFwI9kBtItj|IY*PR5({jeQ5T5d{SB~$$D*d`Ni_p3rUVI@iDWl3KNJkG z%p6L6X0NaZ=2g(Ea+@p0N;5N&D(QNVboT9JJ9qogdhy3tP)i^j=?^{gO; zCseacA?f$TU5NJ@GwQ{}_|-r1WtOk-r&_4f7vFlO!1xF9Qa_KH;cv{XaybAvI<^< zlfqYo=Y?PVLw!Cs?_d$%1#3!=heEtrGBJsKq*5Z8crGtFf-|ZS!~;EvSQ1WVMiw%u zVEg^q&nf*sr_X*cV4@O_l{a?{RNH(gf@8NIJXRBNB^ukpHo&{UWLG`V)m*+<^RCZe zQu6X@Url?|?T)t9`Xhj+vb1+~!)%wx{oj8o_cF9>`t0i1%89W`3&M;L{dk@z+`sJR zg^6L9F>L{4NLwwHV-qW559XH4#pmQ31My8ecel3f-mx)JiI;*Y^V+qVS2CV^>Qr2? zSjL-=sMdY3ryu)Z881*Vz;fiF_}4%N0)H$R&vk{@XO3d5xxLItkn+4>w~1=)K?7!c zfD)Z5&EF;dUJN%3GxVMNEVUj)WY<`Z-c54AdyF?_uhGRh`nF3hJIyKDp1ww(FY=VW zl|6G<{_=RKk-ijdI4i*J{u{T?>-sHqb3tv^Z&eeGG8a4Ds)Xm|etqHux=uuVzF9OL z2ksgmh{^cKELD6xA)f>n6fya8zw)#O0nFp|dcaD%gKcg}aT63#2#&)E9QAquPZVsW zqkeZzakmA*i~LD;4+VMAK={TDSs!b*7_#p2csi=_19N|Ch?-4E$z*q^1=r>jl-KFP zSHgIl7#rGs1qc1HmQXhx^r66Pf?O<0KGP=w4RjnFw^%tq5;@3tl*wE3j;ervvExNui z^)-`7qcVL3jg@f$eZKo1?U5I-%2*KI**DaKtWs_dA6&iZBO$pi7z!bq#Zg^uuWP9) zGe>&cs$!K01M>vyjFF3$tiCwnGZ$7mot%`;jfY1J*N+_D*h#9!G*dNB7U_ty>{CUk-WZC+a8?5%qcvUiGGs&@@k*dVX1r6mD;fnHDu*@;G zaB^A48MkRfrUzIc`va1T@i-h|pfB`rua%?~ZqsALrH4~WBFHL{`&B|G0?-y8eoP^+ zc%0}8bdl)M$biU;lK?ItI$ke5VS2*&1fjxF#WJvXeJD}wi#WNsJ}7Mn#$xo@iLcxn zxi|2$Qx@2+VD9l?ERMoXI1p^-Ev`q48-Rc_7uBLkBHH!NK#j*+<&bb;2;igvCu<1J zKOQs+^xNUcy@6wN9@|UzN58BW*BdvOp5U#s=Vh~jsvc-lPE1Ptk*SpsHR5pOTQTsb zrr&iqmV3idg)l+fowxo3T1S6qy}>$N?-9&I^oI0=_NFjH;u7uc#V7C!f2m&z@6pNR zBTc)G3VnzjC%hPV%)l%1)_{nJYS!eq)XdcgjVV41t?hk8Lq7+3*PUWwo-(;R3!N=T%p9 zFeFWO4<#u}mdkY%49n4H3m5aZB{^69BQGUGNJ>RweUi;k-hdV}!0dN~d@&ywTt{KO zE8(7VjPDGwp2y)RpWYJBQB1F|WE3so049 zrcOKeU$>um{{bY96+8}{y+(MP^$c9hmH_R>>Pf3SyhGybH_81QgIesqiIJ6%lu zG;&Z?>48g?SneV#x=aen8RvK( zmiJewgM{=ZQjDV0- zh^*Bl!~)`rjY5XlMbPdbF+o~u32}4N^uj@s5}6?c851~2Ke=&-I6Qk(=wlU#ilD5z zb4k1BQ=F?z^jb_3f(e)tGLwJc-sD5aymesVMNa!tmkBWk{bH>#HW=IwNE4dkbxe59y3cd2@f^jAsAJ?=%^ zr5zywUi^7Su><`m;5EHEJ%vR^z#s5r5CXp!FIh(OV)ngEo2OT)|H=!g%BZ{Q_5wpa z!v)Cf#kn^P?YP;)RGqclX;xgWa`!t5m+s8Y>7V0X8O?@Q0q>2FhfdZ;2=Xz_OcLLi z6SlXJMu=9RB2z(#_KdwNj8}Z5ups;<6q~wOd7b4AcBe zt~fqyw;}@9H-3EK%1)ihv7pHU zjikcfdge&qv2^FzU3|J`6ji}L47ZYx4PZuXK6%~)Uy;Q$s=_^n=f;lJ;sKqyDRia3v@ z_pPNqh;7(;R);M$uO7_L>*~MgyxooJ(==jYo(S|Vq;BJbpA$BJ;gQ>io6j9)0Zuk+ z_Zdxt+g$4Rs6^6s_21a_3W=@d>MMT(QekXfpy#TSpIk#MH^=&zbu}?t4O5dIHunlm z-X)Lkvio4Xvs?W0{0okBs$XEuUE8{O&5~+UWhKf_daA9_MXR@V^SQ0i17e67l~E|j z^x2_g)ADnMmwc?B1%CPL1LrS1eLNZJy5P{cEdysQZAcp6 z1f@_}85E$H6wJ{8Y(+KNN<_St4=tRpT!Qt2JJ6gV`P+aer%wQa3q06<{~PFjW4rFP$mHWdVtvB_aQ38SUkIl`^$I)ZRMNExp1-^ zGoUZZ!^w(3cWBS}$1b71l; z#MJnMPNR(6@C7Hu*1IIX^GY>$`Lg@(|HPI1V_4CBVH@UG>!nM!zw!3p53)*HI$wZ% z^Tb2yckb=poQ|`%g!}+`ZjErxKk*&u_4%tq9OM&;jgE2FyT2!#*1Hqq*53WyAM&Bp z4OsuAz5uZbj7af_-0LC07bgm!w6xHOb%3R00>r?}$5xZDKx$?r{i1TB%&=pUGUoDq z!{=}4G9;$I3nWQEhg)jgwDDtuC!A{*TQ zh{ddI-b{_uVohR=yhXABNkd6TSAP#Pcn+V}>1gX4Xse-doV^!CaePHrCt^0> z`FUOE3|OlHI$G7-*Vl<}kV^JBij-%oH)z5UOm>s3f`TAS5eE z#0VK@szb~k&ugO5;QUF{!bTB^LUt5Ujoj*l@u>QWmg_N35-5 zlg%v~#@lV;5nI4P=8@^$YvtdnAGeLSv@N%P?oad(>f62I3+!p=!yj@QMAOsrmi1Ie zx+YrQT_zfwA@K=SG2PodzpFj_dwL&kFJX}Y%s(s$RYEN+5pAGkt_DLM2A(8==wvn;W;PUiosOP{>5#83TwZmXVSy8zEo2;lCgy2BW{ug>)=i#3BHr3I@({Ko-3|-SX}v&4v4altG{MsbcWy!+0*^Fm zHt}8_g=P5g`@J80B(%OVI2pcrFh?whl3{XJqGT-ou^y1>$#Ht@pxvGdIX(88fvzSb zA(qeU*4(POY{^J|_%6C!b9~+}3g;1+OTugojcPfUv6ai=9JM4`is|5eyAg|t1RdCz zfrd0B>_Q)MHIn5P78%dTvJ|0tm6{_uO7!Is_FGSd!f)0+@kF`>fIyBt*wPg=W@vsP z*$GpMCvP+3Q`-9DA6q?6r}alavO1lfnZM-Mn9lj2K4}Wp_6Lo@uK&@eH4=feTpsbY zxf^253bL$#2fk-eV2g?qA7PgD8I}*Q6zEt<^39SJy`#rps%(w@W$pN>R zagfKbkhu(eZ!kBKj$1-CAzAtVxO)%yILbSJcxPstwA$77-n+EwtKPhpELpbPyDhol zim|~33^r{kri2CJ(VaCaf^U6Ld1l1nbhKiq+(9sb|n^USP@ zO-Q)A&*yz#gLY@9Ju^@FmG7^`Q>i$Ja)hpevIL)25W*|*`Kv&R&P#*n&=VCQO{F#c znB%)qne`rwQ5o@{qc#Ep|HhtcvGAlaPXjA%M}lFgaOA~QR}119_g*xlWpJgyV&-NldBmM%hDgHVtB!Y> zv`?^kg&C&KuQ|UT!Pmh3{?HjUJBqlBb6X?64K%MDS;FaRL@Mp}A*HFXwej5K_OGByLdZV*L@aGIQ+ zo4wt)xo$LDR?fy7COG==11cpF~$AJ%+|#zIY+s4CPXUNSX` zaA88^=l~lM5?EV1(3>=P+}B1CM;YkGMd1)IP}KIOTkzf{4GpgQkdwQSyphX5aNG$? z5O;AaY+x_K8+cP{n2kXrXe)bSwdXHb_Ut&V^|g;J$ZrT#duBI73ZDj&UxH*E`KuW&o0mQ9z#(qohWH_F$}WNqC5BHy({lsW;Xd# zHTB1Dx%>UsAlLAQXU0q(lg?-hdIK4q-XLD|xhn@t`?Y%QhG$j_25Z>cxN5jpKAFh* ztHi?L@db91)e`TlZ9iw@a`7X+yl>$8uAu{U`5GcYHSO8$y|LcDLUPCbM@DUje);2X zu35gJYs;}h`+04f@40MS^10_EK@_D(&{rdgV)v0QPou{hMI_IX4=&X?s*&sr$scMQ z&X~_(sSsB_xY8FA?Dg4TI-vLX+X>OyTf}eTXVm-EfrJB^JBfBvh3TDfCw4j#0chb&nx`9>bs}VB*an$o$Veo2+o8sJ zye*@|;86P`BKD(}aOXD@>nKVnN01C85|TFUeWI8Yu!kN|he#VIka%`Ev|2 z=tDGz%7!JT88CmEh+wAD0kf^CBMv88${Gy6CoR;u=&TVt{6A=PH z69s6~L-=&?9PSeiy#|}u%Ri?+ta9S%PB}}zl|WHdXo8!kI85>8Lo;v20NHXwrETg1 z=qLEuOd0vS+B8l)IsI7a$>TKZrk~=k3=Oxssds0u__G;zdTmAv^642(6Wxbw#JmSG zh%;M^QRFlo43%{<)*+~|3MHr;KAfdEQoEFLO)&3E>G_J-xlZ}{w95w+G8S9p3b ztW1CUTf{VbcHg*ca>OC5mY)LCE8A^xSYi=Wd!D}qSSyoF-(tP3pWxrpgv3%;Exl=9 zzl?G}!fIhPw|_G~3D|xqvsi&-K0^_f?DzCu$RnrBEknh$Od>E90Wx}`Lup0JK2 zB~~Xz^~q&9L?5Xz>1|nidNDwihNi5q%AoVbQc;g&GV5|1w(RTES`+nkMuS;ARjI4? zSVDdA%1lc~K4&u74LVOa=C@ZH=O5g)wMjDTjG0`GB+5n!LA-x4`% z0BF{nGT#7d6h(E6a}jfTjMc0JZo&M6Nqk}A2x=P<*Mg7&ryg#%F<3;nBXZV-({duj zmL>=_VeVlH-Gqa6IEEan^a;og*LUCs*i5P4ip;4l83*HH)1s&DI-OR`E+0QU+FGl# zX->FvJwH4Y6zVyi_Hv7_An_ai?OQ*iORxb&9omLP{RTaXwM9C;3 z4Yk{7scopMp4yLu*L4e8B6^)9>e5a16-KSmoepci(tfUdP^@;kDk>@rj)41l`PmQQ z<*XUg!Y}>kHSvp)RiD_D82dytw)WYv^ycTDNcPbq@keXKw|onGsMZu7E+3|c1dG#% z$E9<0uOSm)1|!Y%XA&+!h|tRhCQOulH$~~31xN4SwCVn%3lLhnanplGhrWKSfcrn?R`SWV6sX$EG&vHxqOR|YEJ|1e_FpsSTBD( zzke6X1>E01wzrmkE_?XeHTQ=a`v(Stcr~>209yJYZwdK5hhAudH3}9FMuoP$>y!H=^)rMGEs^=i%CnkRwWe@*i<%`b3- zuU>=zlXRL;wHcalZV#$}a*czmkcuM=l^`eCwZl_BS1V!3;|!#bRXO6^sjE|RUZ~zQ zx;aFnqx}rBAt0{Zl`vpJNC`&_f9U)U{5kRx{R#LXF5dU#vH6xz{k&Bz$$DRxp6hNzn3r3^emvC3M?z(&X^6M{H(;ezO@BUo_S9Ey_ca9!<_VU5x z(5`D9XpVK%$5Fqm+GKRsd-``T%9?|9hG$&)^#lED@*Z#dh5{OK$`4iq^0oD?NILIp zt*^}oE2Mq%Ry8|a`E>(*YuY^KSbI$_Tc7vo_5PN++SX8IMXf1-QK5_NZP@*pz+i=%c_N{yLj!QSR zOnp08-w}u}Ub-}CF!pS{uq&9)c!by6FWf$_XVdxlwj*2nIydYuL{^WswvMii#a4|p z+i-nwHsG1aCu5UY8_n@i>?}J$&ap%L{~4)Z_r*kc|vk*{L4o9 zF=34UuqmvS;jJq_AwT;UEOBC(4E(KL! zHFpOjKSiQ{TSIxOPN*q7=*P$D9^Ko1UBez1H#OR(y)WX<5HjHVSWU{nXa2@_%f7 zc-uDaL7lTr>VAN`jza4>pgBPj5l5U<@+o){o5sHtVSu zZi)5;GD}yFwJtoiDIe`#-QrC6s%sYQ>T6v;FPg1322$##(^a$(OPp!|F z>xx9Wa_l?Yg_K^ud@B~Dn{ubxjH_lH=HRr#ef8l&V z(8@!?3tIVY_VaCc7Q7%2VOrpSai#sR{-c-|)ogTWMkMf`3l(o&f-A+o+61F>yD+** zSh`dGo%Gh1OJ4Cer7LY8EnO=9TgfAyknWMHr~V{GRy5JbVslY=anP{m_7hj?@>g{EvN%=MF zAml~~yaSh_R7qP5i6x(CS-qg$y*V_x`GUg2^VTh1HrCU;dZ^R2IlOrM{J}-%tsPl9 zrvHT_<~5tWF~C1wbCbL*+hS3Ev`9ajam>=9|Ln5ly3ITD`#!U5S=;e}$%nODhAx)6 zVx5a?THCjd)Z_}IgA(M9ScXg{0(vE;*KYVzo$=%E5)QOS{Ljt*rWA< zV12|UhG;8e`;7bD(~B{fzb!eWA;`09#|ANAOdYCMm3L$ zS_v2aKqA0l+wdpkksj8QZ|_Js!w4S3P`eUsc;F1Aen^8DyS_dQ|t`E?BNZ~z* zx)8Au$1an9^~xJ>ym9C+fBDM~C;bFIs^8tiA%tHtqqOMt&I(Ni$CeA?KJz7hL3o%NY@j}cs!Yq)f&pkS7F0u z4Q16GlDgsXW~houc3n=+3jdUoYvh`o@MAff6aL{(6Wjm&54(T+huwep!_Ge%|Fg7F z{HX5zjz|rfjymw|cGpB5@*6$1b^X2dwY~CH^Xgp5ptZZ!oeA5__ApMYi6|#V?B8cs z)M7`Ju8cTdS2yf*OB@iM%E?u-xlZ_tY|0652!F}RmM5Ql;q<<#sr_%3_Dy~IZqU04 zu$&2SzyMYh*bfUvQRVdZ4u<|ww@S<}op=e{=D2Xmrl;tbA=P&fD!_mN442y#6acL2 zG~2hXzUBEn1hH)0u2y+q=41j$8%COaWL+SD#n(JyCozGzcd%t)orJo%fD6RjNJ~(< zed!O6t2oc`A1;+TC>EW{Jxtvy9aim$Q@2J(R&VcCA)oH;t4HKhZx) zuy(>I@MW;*P0fRF?1IWqae=HCI>2#{y?fSs9keM`%x2adHZ%DQ)K?=X7l37jGPR>L z2IPEx7Efc(>)i3Uh%<8HmkZPJf^}2Hbk{%^19v&1%!@BjE|^TNXth_MMlVCI%9#h$ zl=*52yfaef�ous_QN7-L$Z$XCS9`qgq;ma=i4OS9g^)7E3y6RcuZEmJ)XhE_s!v z!1^t(S;uVAF-~nes1QI~zmEC?FbMv4*&KWc$GFszYR~~%2Bm}j1!NDLiltVvXbp_} z#ljNvaRph{>W&K9zCE{m?2Eg$f9~XM?fC(nARMhkK&ekINX=`HELd=4ZL_vd>j_4D zwuUvwM(_C8ihJL8aYJo8PPF+VL626_WQX!$5%OR-KP-@)GGO*r2|?le28&_$=N~9; zYOY^@#fs7A#w}rs+$#UV6CfM!;Dr>9dEr+pTL%{x8V$lft$Xl&hiaCm7VaJBzhe|G z=W@o-SXeyRS}DI6ZXL>Ihg!pwwqbBuNg&N~_d%Q8k4HWrJ3u=U!y9AyY)EBnugh^%% zM9nSvGFpAQO0kV61tlDiyp52G3ZIitggdfcZ?-*>(^dN%4CbCtCn3XOg$an$0jmP) zswd%6zNeI^s4^RKJlcyJF(=_Sk4*9xRVJG{BeEva*_5obsyMrq?G^c6@pjrxxkUY~ z?cHH#PJDxzPm71jiA9kaR81wy9tSD}LGcsbY)3fKUgL#r4mKS^xyjDLmnZ!y=E}a; z)`-`lqW5f><}5~|Ik%Qb8!z=l5(!D1MGrUvvw4VY=8y#HiUCRL4S39`nQ zH$YkDxPWBZ+%tU%+PGC4OECqJ0*N|Rw%OpfVK2~!(-zQ8 zD7{XvHd&0sLJ2KXzDVhsoX|4+7Wskj1X$XpSYu;s@}XFx4)-cOWU)KU;9@W>%nv$PeT&?JG3K^?% z#EeK8^m80z(jx&x&u3@!O)=VOce|>qOv%Q!(6qnI>GxZ$zGlBE>C|rHePyYxYmuuh zc2By?pJ`86UD2!~zc^iKbs0DNnscIF(i=qw%2*-5OCPC8tnHO%dC&wZ9q#G=3;T^) ze=clOyYbxg@A+v$u110)_s7 z4d)Mb*nDo=@U`77%WNQIr0r@LjSsVEul<@Ls99NB10cnTJVX1C(uGR}tmhgau#xf! zK1g36BD0iKQeNj~YXw_nAgrq9Pz0=+RzWOmee{x{j@`GeH!LcC_Tq~_TO2X2zjb%V zf=eFVw&0?55C$$n)KG5xjy-$s7|*5sJfGx60|ga&s2SSQdiGSVQ)PKXUB}ZYV~}rq z{Eoe2@7vO*Km5fTMn`Y>;$eN;miLXFd*@h9(cQIU*^(XIp0t0t$iMrqq6CRxodBjkJ^>Yo)c(Soj@MAgEWrW-tsOvdijoB#~ zTm~{9sg5o4P~sX?VjjzCs9=VaP@x~JX=agCr3mZNB`Cmp(RV+%Uah$*!E7MjBP_Gq ze&P%SQnpXY&)N+Y7rJU(N?ObLeM$w^(HZqt5AtfP%a-y=ti1ZF!JV47PcSVHL(1^@}{Aiy4_6;nJRn0YV6;8u{W3GU%iVqW~%m`{y0PFKGqKfjhYM^UB)g@%HoDYa7jrM6t@&JGy4oXwX=x z_cu4S1R@KwRo<%f)}32N>(gmVs;0q{Sh03(<_cMU^Vd7_M~HJWZ8({91i&^!mhdnYgP?8gPmPH^A@CZmAZL1oj6+K_L>&y9JYT< z)S=LKwAbTt>r8^lVwS4>MnPZ?#>}wX3d@icPH9}gpqe!cQP+e4PeFiFc=tA5xl5sA$~EGOX$1e9xjdVmTJ5K~8K zOIL4l!S+76R1vfV98(hxn?-m!wd<1WN5-ET5g(?>F@^ZyfrEFg+V|;xd0hP7*Gr!@ zz@MPBW#p;xk?Swnm6BIjY!0d52-t!Zg4DNtL9(|CSEf%N>i_hEZbSDUdrClYkrk_(3KK@Dc^|jNB4IG6U{FG8|Gp zI6x7UI{|-S+DJqYJEa)ZSK#c*>{b{P4?~glUWfQld(15quc6&r8zo_<*z0Q7K88-}F9G|?O6d`2iD=-Q%!80J}H3k_T4;{#DM$yzXYVJJIQSun2 zLXzbDKp~1q9Lzx(heArpE(7YC6C8|WGKWbdRY(tRYSkHaZRhVDi+8u>T0ZvaY<+Uu z1;_gbu4X~~q-#v}Y#T~N2X+pM6}iRdbo3tUlO#{H_S|-FyDQ#R>n-Yw`SD={q&QN& z^-G@Iw)(yW?Z0?-^rpV%b@Nlf(DO>)YCkL_=5Os?xVbB!o08_O?Fbty^zv6S1HO3J zQE6@H9@2=~BCRf|sByw?0zMV$yi{HWo*3XJltHVJq!}(|S1aEk;vQW1i!gM6xo^)y zpSbB7ZIKe^jz4mI;lksOjMKNMIf5b|fmF5m#0hhCDo|lJ9w91lXzt!av{#rgQo399 zVtfad=ya(-b=bIJ1B%umTuZkU{zPg24Wo1SkKRE08TDX(#k_7DR-&2*fulXMDdg;5 zmA+kcMsO{*Q-L?xr%prL6q&>Q%BSAuTY*$bP`A*X;L}CscQi;jxgZ{bJfFoy zheu_pNku^4RAyTCHBx-8qzO6Z@CV?>^V8B8J(>6zb`TjBe#*M+r<`!d#K-7pX-xR3 z1LdL|a*U2jyKr(EBz^j1P+ocF$#{0aI(MZZuvS23iR%Ec(5e~5RsBqW^5A?{}9P%lk@^~R^ z$fp=vNB)#kY!5;Bv_}Qlcpb_UYJtmtc;i~-jt4&Y{Ht_#g7Rov_~Bo>ozf(i`mtUV z9P)3jf$`o-T)e096*TJ#!2F2!1Tf!$g3{=KI+1UKwxD!4>yeqAuhsBgnsfZs|HSRe?0U^I_p4Tze6#>#UWJEr5ii8pva3)~btedr&bzoWkXj_3dJ5KYae z?ixM1st!(Q7%cdn;fp@mpdP4Ob#(Nur;td(pB^~&_};yb9~+?QyDoRhZ4a;0rWMaM zobHh6Lh@>xd~Mysw_TE3M>*<1(U>+&QEOOB)2j|DuIjr;4-Sll0En`q9A@M64NI?) z1)G|szxG=m{p>m7jFxq9$wTAxyuzJiQRit2h(sVnVzpYN^X)<17sX_- zx(YQ-o!WvUVnoFDlwJTK6JONFz{9 zMzt9wfe^I|iXz1n8P{QuCIvxlDa9W}5Z?T;{=})NXJ{Ox<)^fTsXN3fNomJXV`C2^ zRf$t~OuZp~m&R87E@rp}%m#D(Z}Fofmf#S6_!gTQ`d`WQY!--pdU6jEwF zoFKQxM5!0wOD?}c_^$j4mYnb#G5rUJUw)%ln%rkInVaV?ucL(Gyk2kf=7qUpc4$jm z2|hQ}U+QF{Z|xa-gb!MqdKab_4b2nt1e*KPp@9530Go?8ALxEt)0VP3dxnO3dWKe^ z@T$gX`^oPeQ$#X<8RU^~na9*0?AD#-hUAWg!h@LUY*m!KnBJNoGKBaLe4?~F{HV@yjs`UkE?$0T zao=bSZBZ2j?G1rKf@cuHRg=YAT;Xu7>un9b=(84Iw-9}Sf7TgoQvMBHkt{5T*&`x> z@#Q7crU6rgrwd4~aki5tE`H^{v6|6+#XFbN7AiT2oykIwT4Ptq{##i4g2u%ksMd9h ze3D*p;%v7Q*hSoai`9ii){g37m!NjuOe#bkFw8hWBf(X7B0nJku~r!u_zxTAIlh!y z9Umb;QV}dY1;1wKHbo^Fc&c9jR1(3{P*BP{*w9X=|1DkP$=Jji>a5a6OVlp@n)-~} zOa%MVCCBjoMEIl=>iA5Ln0!OI7q0Buy{rKcj)!u#P<_JV<(@KP^iG|c*>5wmy@d;L znl{n{^5#&7U-<44l}s-AxFa=sH>QHX!gnSM%7|PtJw{;{27=9phGo0E?8%Vb7Jxkt zZn%8T;Ha>hj-z>+L->s!_+;|@^oTH@CUq6X-s^aP=5_FF%WT;|5M&Ff5&BH~|4e^V zh64E{?^vt^{yvz3!Y1U%W+7UWEME1$)2IIQS=Sr&*lb;rFgR6HV#5}98>v!I*_eriErj3Ur4ZFHQjlalI@szd zMG~zYri8%;)^49so)1t31!_QUOJO;+D|l3y0_s|@P85pA77JxiAh?Gu8twa+-8gKq zRp^9UB%9moH0!kOy@iV&{pK$(J^%a9KXB<9`^p|+^tu=RRnXn6)OhRZkzD?e2epa~ zMmf3vSI3r4etde>sOv~9u(?=XLY$BYn=rmNn_ChFVN2Egbz4?->np6r^Ikc|3-;x= zUiZ52(RSSg6y_ZNM9x4ye!gRg!PTKGve#paYCP=P9S3r;m%fIGLVQeIiWX zB55-uk@*fzEF${pHle!J!tQ-q2BsfyR2` z0-Gu-@QR=6I;ZM>Gda&}@q=Xt;P!15DuK@i7AcR0#S(*TNY<3usJ(VVb-^OvDJtwY zNeK*Cci{6X)&8k|zcu6Ni?OcJoVYDF+7+u`w)^nWj*g2D?O9qcY8KtoThncJhpZ)- z|GR5??>Wr?GKo*K34`jYMa`kvYtVsg?lKnwwIzJZd(!R4)P>izHCaP$b7QQjZC$u7gET+LLo6{D z%~ZXQiO)pJWrf`+=NNUy2H4WrCG#52q{wKj*uzB>P%&aTf?D%a1&os7d14|?C4%UGM|l;mYK)Tc!E-4 z`U&*YVaYQeLD-~@&spDL>LCfC=nqZbhMZ^8@=1C;C99CPKIsbOhqF*kKX zGJo*GwauaYVEQt4evy!QYRMW%lwbcfwP~zv#P2a&ErYeqR4u|u$!{9t#V{HgH#S&g zMAvI~yV3z`SUmC8S6wM`=S;cuy`{^-)?ga)kdzMxq^VQkQ~alREgg5ILRRtGaE@Jw zgSnc3KqnDjZC2KQ2=SpEpqOPMUri*DIOnusGtE`Onb}*RN2FpxGbP}|D?08Jenq__ z`|o6b$Aqo`GmCEZCK!p(lzGx-_`qVcG9 zK?wgQzYtDP;*O$r0qM^(pZL8z@8A8ff9GTJ>;e-ERdFC&XdCk--i4JuhosJ?e*c}L z1`TzpDm{~G#6yHRh|f(z@9iiInTjgKzT=HNnG6O?eh)|BWvz0u4Cr7Z(`qT(EybkG z(Q88j=o#c{B|A>6g;l9ANN`Q3C_IHhxl#WeF6xZ#sTM^}%y0y`-xNL9m1+|b2!f#t z=di9bSXr6!^GxiZNK-|c)WWl7WBo|%oMKTdB3mqzxswBtR7jktuFiInv5tIc$ekCv zODqow?GQC$aZ-aziaHF_Y|No_#vY!{YB~x0XznOAPLE)&keu~UeerKn!_wSX?c#T% z&6U5?b!I1jO-4K_+DtCWR=e=+S4$sPc1hoS{?+H@4-;9LImrrUP3Hg39l^-=lXK7hLx&(uxXY+-C-YC>q-c-2ao`Ci~2R>u4M>BX0A zD_B+vI^JTVMoaPm>+@Ei1mi5yhG%AG=m0~^7&3~wqr?`baZEKI@7dsTfuwLH>|<|W4tnH8I3sWhYI;sm|TX~xBfPx5k&v_<_`;}@^F^2OVCCxz<#w#D@bd5*nSft0bGP1cC* z`IeQg-Ybe%UQr#ccCBi@@;0INo_mCv+d|!;!14?4-?#tneG7xS51PZiY(DVI#ivgm zH6|nf$6HljJf?Lxt)OHco5$*ON`05jYcTnQEBy6+33ppZt23~4!}cbB`oN*D+_rMb zjbFGRvtYb+XS^rpb9VK0yYp)udP8e{(Qc0xqV=u9=UUs^T6cYG*XD;VUNCg=N5B3)ENCHP;$!9-mcT+gxXKC(PSDh{YiJm1sgODhO-`t!CDL zm`EgaEBZykie~{CT@Qo2&E4*}9R|P^fJrC%opZ ztI0UF|LKZzUp#sJ*4EZ-*FW~+x$7Ug@!;}YZu!9*A6h?r>A~Gi_DDSAYuwz_xVh1n ziAU^ByANJE`AkPHvpkz!p2`nS|rl31oTG0Q!Tr0GWOsIV>>Q z9=@53Sx#r!OJ{TwFF8Y@Ohx6(FC*n~IOKeZerKI8zwFG0%z+Bwr?g@5Rlf96rO%=s zP){>a=#&n8U9XX@LQWhX+Eo|?r|~YXmN3m_xX1VeeQPLS#yO~t`dsM< zb_uwfAQ6G$n!(~U$S#2wX6QIxxnJtxE`gY8WP4z$Y|KC&EUI7tT|AS5osTY(CWxx3 zel+wgH%Qk-@=3>L%H3L(Ys^()Ba}h!p=6PB!d{A($ zDh#`3x>ZYaPAVsJ(z9ev-NV!&9IvMC`rC_Km)Y5Lmd8nrnxU0}Pkt_86z0x}sRA)O zv7qL6_NOOktkuz{F+Zd9^^fDw9Tf}U-&*_n^a!;)mpf{=sdWV*$4v9d>EcVu)bf#_ zQe=;c$CWqD5#q~;t_E2~`ZOb0UkXT|E?jb(_2>v4Bh3>+212rRHj=YFIu5+HG1nFV zQ88PJR$V4#)TI67Bt&hSS_eiDmgKYvYlNRbH6$MgmUaKF&&uBx>MBhJqwxRy+7U2n z#k^b~JkMb*?a&uXul~m22Xbl`*u!%LM&78!%5a%Yuj5|KQ=vo!1f)my$k*?b#EXCB z5DwjgA|B3lXMKiUQVQOeXsHvV__ApIXY2Yum~otFvu zk0z*(xN+>w!hin^J-X_hoJ}c1`+GTt8KP=xly7alf=$ekKF^kM4N_LdM5bY`7W4>? zemK&(Jr0m+1wPCn8@V8`vNm84kSs=xIa5;Y#aY zxfq0!K{Eb-`ebEnhS*9s;%)&<5XEX%mL6`3)rm5GHt`$sXB-HOfHLuqHm}cB3jdx+ zJf!(Y99AXflFge0Fp)9M1$@rLxHa3O>M9nN;~<0gM2Iwq8C*pb-IA@Q>BGc$XIwm2 z zgamo&_lrDIczOWnDOB(pRZi&fVlIp1fUU00rX=s=7g7xruq<_0;m1H|#LR19DJn-C zYqxctv!vM|%Y;}O+K_xHGqkyb`d=8(w=6xUqhkjS3nF#j&tlzmK3`pT>}Pab{*ocT z^qljA352pFY-XRiecMQF-I6`Mu?0-Wf=!hyo3xm>(FPe=w_&i0v_U({4aMn3ZM>m4 zHPpU&D61Zow^57Kvb>Fsv1b&JOJ0bgh^o}QY*%~Fxyu>_Av(Jqd7Ih}wx*u|3V})^ z%<#BVW9F|U6j2c^&|_RN1t!e|f`(aUgSkE*bAQjpYUUmDYCyPeTPd6EbottRdVL90e)D=!of zACIoAuU{Dz9}k4IH%J7@ee152KnKhM$z~vcHf351=@?3pIdKyxTYn@rPT}>3IM8_NH}tVPRll zTDl2F0)N~Jx0?!Awlf~Re2MA|G;~yOiz)dHV9TEqzPrO1amcSwH0x~S?JV9t#dNw= z&~zWzyvXgW5Jmzao~O8Us(x`UmtNT0WF1;X-abRnD>RPZ|HKBTeU-QHj-D+CGHIMV z&^UE+Y9O4SVOL{{4a0MosSJ@xaFwT|1p8jNX~l}0Uf4(9a*+mdd zq-)aYfy|L5iFrM8(;o}&?ZD7-i6{jV12lMtn^k#LElt2kV47g67vR_mhW@c>N#wVD zElmuP1l*ElE>{wzkgt@MEY;Q8%9zop7ma~@u6sBTdW;zo{y|t=-M8wTe9hA2NolGi z9x>>LySs;rY1C(HCFKe(MYy+giy>Yq7rTdtyM^6? z=42~1Pi>;&v*44>>_PC;k*!HtkF)E?-xtkfqVfaCey2x1CeiRMqlgu=6n570G?ydG zG;BVo(@e^FN7=Pl)z*~-T~^t#ggm6YbITn%9En4)d@x>+w_HvS6{Vcv4n&ez2f33X z?T8|na9-6;`k#78{6x!GME(ztH>orE0-@?i&SGA+&FSuG75ApMHh1r>y>^+?AE|W& zBW6P;o!=A{tc`V<$^Yt=$D|v7=IjXi>Yb%~{2`YvIV`Is@2!Q+-C*5L%cTY_F(ryDYhgEnM06V0>wI;CQX@ z>qxY==IVZHUDy%vxhoBpx~%-Mo~3x?sqZ%-cju{}INi2t+2`tw*Ti*={WF@8gW2#6 zzD$fB#W?f1=9R9fboi@p@MhUD8X1+3QN_=KW|?M>=AyGpymKK{7Fz~HoOB{+x-7o6 z5yPpfv)qKn6ybM2tDw%$>FTZ|)15BT=S13X@q1krpJwywXAZyDu?-0{*)gsxO+lA< zms6NDIDK~x>q3y^dKNXLMP77^!)<(yEuNn_Z0jq0thNlviWug1K1fV~^MxYiuC#GW zB7%CkBEZ;zN-r1;%VQ%P;soi3fBID*l7>DJl}64VsEJxSKyQp!q%$4zAH-kM zQTR%*^Tb_Ke{eVid+B_nyVYnp_5oSYjp*cI*q1b1{vhIo0pP14k!cmvNu*LE>O_u2 zEewgUF)~b{pA`KGVusY{jI~3d0TVf2VZ!2USZTGIAb|)Nn;Jy z_|VRQFOO}!d3(Na(IemdrIzvTH>@N3u7BW}&HKLo_{}>z>Q)@N^`qzjVL58zJ){3u z^a<|`jEo{FV9FAMeu*fTMQpYqUn*?0%?w`@iX36oo^;!ABhnXDb0=j6db=a+Y)@?EhRrz=fwY=i1^w$U zoLIDVY<$sD{#5)42L>!rE7koBxolC(vXzTYd>;11@5AG&$Y>m%E1l+})7`mcg_6b+TEFzE@(9Buasn>{vh_uxih6^@?IZ*aqOb9R z?gdA{2jdzBxcEcTqV!p$#IXDUTvBFgO@l~WU!dHY1c-v$co@^?3zY?EQL0cR{|mF$ijYocFi;mX>jfnV$Wt{;Gi6{bta{=nte|2D9wws(iS zq9PE_y24F%i`VGh-aD`^Q0cbVnnSK^JOC+chcF}p8lBXLUSxJ|oBUAPKW}fk%5BtD zR6{(E8np(k4f!Z*bBlVL9Xg%P7x^Gv0e8o^OxM1aYzIEf$!EoPw`w?lRNkxU`fkw(X5K@iA%7iA{LOBC z!d+b@2O{yoPM19sb0<8ujI(oapv#%DyA$qM#_sAI)Ll9`IrVelQF;6W7R%&v8UF~)X#%g0?02#Df1`9-ny8>5v%jI&%5Ey2cP=F$#|Fjdle?8!0#rI)Ku|3TMzkf6*cN}lb_K$ zKDkcuyx@hV5YG*Upq8Ebn;u`nlI1K=D5_h3)1z-v{46wd^^9$XZh%xmBlT)D!07mr zE62_%UW{lAVnf;_-y`fbK^De*r$XE8ZZH|VUiyU%$+=h%BFUfN&2K~c>xT3W-t2%I zH*;ge-QmX=LA7&cq-K^U4;S&FvKXueDN^fwu9>7YWUHSxZ&vdvdV!y{Z;H6O4E!1F=9I0iY3| za$zn_KaC<{p@v=jRPMu5dO2ui7 z3YaaJFTDOX4IT$?IsP?=oq}V=^kr@_$#@p|7f;Y4LOwRg;c$kc)XBt6O6)joiMNtV zuo6N90|Z43y$u#26f_0>gkgg0>g1;hmj`ZCSHp}6H6e$Hgmh25uQ_2n1d-#w#jTsK zx*sW&KXu>Ln_DkA;L8{f8WYWZ@q#PU9ur$@y%F4OE}RS8MVWG(T=RPPIV6#TCWayZDy6vRa(YYlh6;P&zik3m;3 z8wl(S+d*(2<5r4ihW-_gXMC+K<*MIUh z`^HXZ!ePt$6laarkvicu)Ge|nojd<>E%(+KUf^tkIiEXf-0=E%bU|a&I5}xFE{Kl5 ze&dp*^1q8b?+V&e{Zc0#9l7kXEdD3I4IPGEWWGdskS^LTgWw3HV=_}O(r7POgnUL z-7IXTAB$a02`In77GK%X@^rBy>9q=yw+6+u2$vl~L}E#>m}?e}=i9~s^bd};t{VK* z2$9V`-4ZX%av?IQwdk;FM*Wlk4s;1xM%LgSsIL#MucrguR(0WZ6v8-JI$0b@ih`L_TRa*(6LT{+A-SE>l1kJG<1?kHD-&vpULUE8vogvjWKZ)-jjE=c& z(drc+TEF4$Rqz0-@FrUlt$SN_Ch_#P=Qo6smG*$WA{lk}^me&CwK`lP#Um>|z2o2? zj$QEUfBDWTkIw*ErlKXJO}2D4Mh$kS6WMLGHn*>;W^-NRxLEYmbS3=les`^>dtc4^ z;@aApw)*4C8;%cI0v7B~bk}+WB8kor`vvX6 zW@Mw%>rmdUEl_Q(T^JlWzos#r8a=Yy(-RH2ai369LKOwAlL0r_U5>?-d?|`zr*Ja+VstxYrekg@*h5a zdD@fEYjumS|I%*-%bm%Qgs2rEc2Cen#7@gw*JDo%1#E;+I)3AxfqNeL*lG}m$}7Km zVu7oExVinrlJ>(*AtVi|&0F~HwR-&qB(6>S=ik_!7FkA4wyL!VRZIPK*^3Rg(qDyZjb`ME`QF1vzzt8&MS zGq)!T#OOr`8R{o@)KFVRd%v0?zY66T4!NMnN7Ph#?*wa@KG>7fQ^`dLQ5t%l8TUD3 z8;M=vm&^Zn%?0vd(E!hEV>zuZXS|9c7`0^jL#k&;AS@X!g+Ma1AmKNTcUdhdf)z+| zQW(`7k8M#`*#6voSB zJ*cL}>^%kzZ)9c@e0%aNG2%4ZvznmBPO83eiU*0zv?KyWl^v`n6;vjQ2&~oc1=>D? z>(9(*i%wzkw}gE7c^8MT0o8CYm;pA}2{?^s0HbI&xCp?9j{FCpw!l9a6MEX+uRvoz zjby-kpFch?9KxI&bmc5&0UchCt5`H$rY1y<3@nLx31|q4;TLNQw1<1Ioz@iLze|nG zgfxrP%RCPlIb9ZTTk?e$3Nl)s#4KlcBl$-G$)!94dCmM8{h?ZwA;oy`uIv?|T>uq8 z=intV!+b ztB=>9J|Ka8I*=a=0=k&tDu(oT0A)KDF(mgQlQm(ogm^MJnL#H^6i_UZoVukIizEvk zVNeUIf&aNFxy~D2K^BvaK@m%^?}NY z=*o?ob5&Krq}$dsx?mvVLXh;U4xdGzNE;(ge@18bR2u};09i{3Op`->d51m~a7K*o zTuWENsoQE@@W^IE6|fhFt#0vPA?^zLQ>Cr0NZO{|_}prTCl!FWzJA@tW>ggTl0TcP zrQ4QX^!ucp2u;YgQ~7Ejo^ z?Q0v5K-iBo9IIEZYYn7Jcf-LYvV7fGo!J~nxoq`#m4vg#<5+rmorG{nXQ;wcpYKh& z)AB=JRFn-z0Xzj#T41o+4Af=fbcVkeE`>kqmo%>kqr!c{)52F6>H#`Nas$SN2pS4v zlG6@tc0hO_350;RvLr%SII;l3Yz=u2(>q7WE(i>up{H3x0*EqusAPi&QX(5eOoa}r zBY;Q(YDuYB!_LI`vq@Kt2?Di?-<~q%dHSV{LS$8CL14G1d4VK^M8NU1M95K1gXS7N zEpuR|O`g}39^UOi0zRVqWR8ONK?x!-7x~l9z^McspUQY{JRs{3R#}HBCL|lr05b7_ zycg&>S<}2dHB!Fppv(Mnso!{4K2izeqo^0xgyS}?R2>PdME!!Qcyp&q(0TOXny|a7 z(iN$VSQ{E^;Wmx4tJta9Y6K{HLq4sb$N9EuOHF+}%16-Y?yz#Y%`U9)WuhUg*>3{o z74fwCA$oWF>tc4Ru?h)JgR$C>KNC?-=*xHDa2F0c*kNlZR(n+CHY%@?=Gn5joYRxD zL|c)x%N0#UUF^e~NL#9FJkDHA#%A+LE`6r0FA(f&%jliR)nv=m;-Z>r!JLYFr!O)% zTH}!>dtF@y`N&OL(WK9|^#%fc?R1q-s72gpPJrp%_W-%jUeV^*)i{dGpB-zvPBQ_^?xNO#r{vOc1 zh@V~H_eU~5I2$*OrIS{>)ftMJd*eyEA{D=|+I=X}-P+`gR+_E))^2yUt^?J&>N4(d zhu;!*rQ0Isi1@c7(wcUKEiFCnY&~j*(Fss;%@Oqk;dK3q-bA8rRby^hPa@W{vN7FW zO(PIvBcaiku^KOA@J@JDZ$Ks`jW8Rdf?WX>6a)hoCZ@#*mIg+GdgIQcCI^cLhseyp zOX9QYHi^@7e=CZ5nTd)|w50@iCn4J>v)vR`*@!1i-6TUsElfvk@dR$gX|a@T>P}9s zRdEPzQBBDyTXR2&9YTRt6^yE2$+U%H3Tedsl6zv=ERP1JMP0J@aE4E!yJ~>}@2-ZY zz;ei9YE}czQsLhsf$-5wQ++7~gvsboG>XeyF%JYYrf(|J$Fy|TPO=wyqWc|ha{C^V z+cDel==@#Y+Axy(Bh<}WlwTSPL`Xi=K305ef*yR5z4ru{%38$llhA=Ta^QM!VY-`v zp)Qk?i;l;3iS9(5m)nVpUE}tyC>OOLi4o79A&lv|&wXn%O^sGXPCZb_ltZL%43APd1@Y2t_SQ}Ta(W1&_QhrcP9gjKJI z6^XkKAHF+L`mYz{fBelS3x!YqMu6ilVcNiFfzXDbf9}*=2pcw!X=jS0XQs82v=0<3 z;~^o$R@}2HfZzwg3DEb61#-5TMujRq=ngC@upw2YRqpOix$@krJmdE{v<@~^nao^i zbymuqBvrqyVHF-`8adhEPn@x5_vuZ{rvevoT@jci(j*p7GLcpHbxeuL-%Gnymz}pJ zD_u4dqC(`NZns(k#8|du;w}itnEew;_)ngGjmfo`XB=fm1CH;VBz3Q>tMK}z>uvTrg{%shZ4I`?QGWAl)TiEg*3li@Gf*jG9NV8UztYRw=Ft zVXmUM2Z>kjq}`k@C^@6Fib_M${EJjyM$u){(PS*@jDA)G%!oA;Z$q$k zWug$c#DHYx+P|&}O;P?Z@lqmOOo%(yR3t#9S)-oLX9fkqK77bF<5yUaYQ>i$7X~l=BMVX~_DNwhE<|7IKPVwwf z(7kXxM)yk^MDu83B>2)~Bmxl<5~`Cv38+1SzGx(Gk>0&Gub}c>&9{+jF$0*IkW+77;PZ>uo|?Bl!iXk#$$S%zNn*Af~*3l5orWBwiu0M2;Q?b}-x&okPLW{0gBU-K5{x3wG124vKqf8?PpT7B6;0(p>G z4+?@YkjT1H8`|9ShX&k&#$IKVP#(Y)X*b#oRaL;7+aoT!BuF}~#jxJa3<-& z9##uSJw_-Z9q3T3Tlg)SR{mTt3ds+AKu8M4pRa`d*X}jhLZPT@>2u$B^}zW*`o?oh zz4cC$y|U781D#13+wTA3s|WUcW9;y`>(-CndHp3VzLd-4g(eFRU-$eKn_K25!u5`d z5cX&%9JVXIebeH_H+}mGvLRb6?rbXUT6TPOF1PylGH*TzAl+P@{*~3b?cV*}Elm}* zu4vze&R}<_+Lr#MrK)MoaI1BEf1n@kI-m=p#AiVf(ZH}jE8f+F(=km_hiGyozalb< zzXQ=dThOpjP-!C%$W?!tDu_~^C8YS&?DP09i@$;sHe`3lG*_aQGl&j7<4NH}HftD! z_xj}W6MEPDm0OzUCnB}3${_k&{DAuOv!C%D-{mdV&281=`-6Sd&GMSr7tVb-R3By} zT319wN;5nnNCJPS`8Icm-qVW<8Dsu|FaN`N>ZH5lhGWg%l)K99!s~9FW^*dz`+GY@ znwT^H0=+@ZKl$D>lDz%heMNRq%ml3AI?$~xKvh2QPV1N`+26d5|3~^&G%f$R0J-iG3{&85AObB^7-XxT`2WBt)jwlnf8ECt-r7XCY_@cel~IM_3b3=bSwQtQ6RbkS~* zhsji9xHA{TwAM=702sclB`@V9=69#!eK|v}Vw9O_AZ1sm6o31f8EBy6%@K`AY9VZ% zi$|qn>^($_DjKOmO)SlXnh0G!E6IUgc}O`{mTYuHla;4!(DXt-L`{mhav*<*M=Xgm2ol_~WZGgV;1UfJ22iA)LQ^uM<+REk}mtFuVq zHh&j2|6dwFa%o}Qw}?z=WAG9`06!$egRz-jcK=iC>+m_6PMr}`kS~UeP|6eXiU3j+ zLjYzUXgCa0bUhX1gh2~u6P$z)=h&Rwjni&~wmj2I329+=_%*qu-SK$$(wy{9v_-_A zB|epnx3t8CCuv=4dHyjZLn-1<)&MUp;*aS>sbmMt+YNR*_pjsjN{$5@030?VaEAa% zrWoQxScFSO!rHW&XxC_+@Uz^Ku4uGtN$!u-&>s`^^$9tesHbNjEmpo1xl!N;l>$Or z#Gi12>16p#+G)2p_={z_OlEYtIRTV1hd|LbdGi6M!awHiTLVk*QFuu_gnnm^48TIB zB^~Z-0)7Ywl(3fhIB<1SJ-{t5_{E&ZNYLjro0NPioU3T3OuutUb3MS+-wdlEJd1oE z5NF@j_2K7#c<|6G&wse9i-qX(V#(LO@#@o;51rQd(`tx9+8>Lkp`vMRJ^3wCS>(w- zt4U3TiLR}KDe`)xlFX^WtzBhQK*6oVa>AP_hLjOz#vLaPzPx?=z2|jxop)yAvx9U2cHdKYq7)Z@5 z+%xw;zr0OTqx+n`64K5IjE^o%g{w)KVoaK)>dJuCm06!2NBV$)kp@jhEM@J$O2_SW zlzx^hY!u?pO#F(IKfh;!AZL^(AS3?tVxvXWFG~L|EDAQqofS4uwb5*`TB=cfT|Q~e z1cR9>l+2B0?6$|M%+R0>T3zL1Zl5h&c)r79%S-RSFm&=`p^Izux3Z0+!eQu(@VjH

+2*WmXTC^G?K(*0EbHUj%Fi(!pKr-4aBd}@0KQ%y-MJt!)a zAW)^f1GRF2N5G&{#b+XUtlXRqQs5^teBg)@PS7c~%^^7=txS|;SyubrOWaJ0pouJT zGvckD6D$zF_92vFP9RYxVCxotiOkA|G4(w3BlOJ^CdnydW^&=YJ}IDURT9jK>GubG z^3UEyPCb`cTJG!$*~h=~0V*rY4AlCkkLo=k)egi;QdsCT@FZ~0XN9jKE8}k{vL376 z!Pp*DaMB1KCn4k{(LsBg|Eymm8px|WX;o=5SYTEJ%11fkb@Bpo#Om%k+M6@4P0WNg z5j@`S_xHQsAoO*17y1guXWSfRbNXm(8mTd?84Xa?^ZinVd*E)ik zV@w2Na~(7z5I5L{VY)03dKSZnZ{T67>1CXy>sQD3>PIH(i5%x)#_A}%I>2d;F*O|$ zh6oBXUMH|%q!vg&W?=eN7Mq+HH3*~B3P_s@mnb?UicP~CRpZ<)g9c3G%Mpf#=o7-1 z&TUVFXI4sAKz(KjPe>P!v9a6>#PHHBjPd1S8fa2K@KBEg_@F9UL~{tCac-9=6>+^3 z+{mE@0UR|FLsq=nPo~r+KBLbHA07>P>=`4QjPW>)i%*9(J+~Hlz20JCUlwi#%RWfweT( zARAo4s6|mqqMBQ`B1)Vs6V3S8D1FMK33f$n z%qz%q=0|F(ipm0r&MYnW1l@&IHQ~J6!n{IcpIT~jz`T>^v@gL4UCP6~h3;UvyVUCO zgq(#{wUIn0uDiok1vY0Mzc=457W=7FmAq4qs&*=&D#lsFAK4&#kZWoQ;$bfnjsRXs zR^h@J?P8{RJC|ot=Jw6xS2B+$@h;kS*EtZbY!p*$2RFQsiZf-(iT39@15af;T!W>LeVyiFdETkY5rtA;dGkS<+ z@VcMnjCwJnW{a*<#b)`iXLl+qaNhTP7;E(2EBFC^%{HXHhiO}RH^y($+MbIO)WME@ zzv~mEUWc&CY;GKf_Cvq10#fFl_ofDfPAwUjRI~T9O@ge8Tn7kZnYnhE0-?;e6G@o3 z+r-QgG&6iYQoD)@*z%b8cUU^9Ga9(Rtq(7~gQV1&Fq+4GolMMQaGd3ppho$ojgy&# za#zu28fQimDb90~rrUeho%MEVBW2EA1Mm{f8%gIk!ef1kn5p9&_f5>>F(vGkC3>ee zK-z~Qg{0CV5+kUD)D4UT(Gj5^K6CNOGn}5AB*RlYLQ`{DWM*iZVm{eyiVbXnX|jPC zAx1FDovv(^WUr0up`O&J_^vRYMqlqX>+l7e>a3sZzNyZ-@-EPQo^y^G6(IZioO5+W zOouGpXM-FSCSN;`8Ru@}>35y081pDCdd8owSErPI?+> zwEw@Ja+%6lG7o{3!YSb&fQyOOEYuXO#HVr;3m%q`Fy-*zPDQXN>G{Q&4-i=+fMTJ} zWg@j9%-K?k;$qSaR5SRMge1v9vBxCfz)OiVHoU+x!_Q1aB37B7O_NN5r*G3>sL5AI z^=emr^5}@)?+fUj9!r*2&-IjgQMa;j*`eFcEL(X0sl#=_`aD^d3;{Qc3jSJW`|XcD zvuVrM9=~-ZI(%l0r-t80o+pRDytHaXOK5g9JUg=DW7qGT zR~;VSHqdf;uf=C^1;Vb{^=p?`EIhuVzGeNjpL*f1vu(bPIsfLd1-GjF-jX5rBT@Msa~`uFEXI%_LKHX=La?c?$Z@~pNSZmOO?&}FaPIZ(TN z<@y$vPb%nMd}&Yfrbe19CN&-I+BB=O^eXSw%f4PJ{+qM9C#WuQFk^7oos(~F-_zT+ z)H^CxZ7&E`*Ow{z%GX-vmt2)EO3vEu(9o_1Z>77$YzVrHx!tR$`Ado=zmC1sw`Sya zBiN#|L;cL69YsNd^rcWgA|>V+DjeMX9)vY48_I&gy3jWsF0ClHh+;5jMx(e4aeE}Q za|?epye9TRf%mcC=7SnN8arHWSxD8kUH49`}zaIRwz8^>z2G z$dl^#topptcb$B5C_HlVxnHVdx8ACb{p#71BXlMjZ@uV+HLiO5jd$_VL%hxrKdvut zRyU%0(ay@c<}zicHIXto7Zi2%l$Kudx!;^T`J2yOLf>6`+Pi93)RP6<@L93$$;d4C z>+qf!#kR2i*zHlj7pc?Ng1(fwTxllP0V&OA@Q-9^g-E5*RE2!5 z4wL>=RIelt0|S!l71Q}ZA8{YzUs~2zNW~S=By?#=LK;z5>&{$h_bz67QTK5>&M292 z#8I2!%|sqK$G|s?I2QR$v`kiZ@wiCms;R^))zb~pisYLW>wiiNjrhp8OY`wMobvkG z1A7oPPWGKq8#OH;vxh)79I!4Gs2kjl8JmrWyW@x_ct&_#_$lTtjfZU^7}!G$Kbvf@ zi8$cPX>F;OqoG1sX=(2$6cr$7yF;NA7j{Pr3%ctFAkLbd!b0B~!6D%b=w|c-5R8dl zswk!5fGHn$!jUZPS=s|*w8l+xGay6y%&5a$QRxA4NDH7}j3QvOBC$8^RfoL<>m|x9 z;wzg!b8Kea;{CTiyqe%9ZZ`i#lJm^!V@iQpbkx`Wz)<3@ut_$gT~r_cc1g{$o8&ol zH!Z6vNrue$4>!w_c*N%ULzC zG3-WA;6nAag$M^krOb^@8yZbPEBpf**Eer8sAn#DNp`vqec~HiS3Pq3!DaQd7yU6m z{!PC!CVM){#c4<7YTJTEGjeifELvbYu*0{1{`~#E9S7q4wn?Ar&lsx}j}!ZW!z>@y z)${lHcN|dB^(^01HsT5XtI0RqVTbhxKd>UBQ(=yJLKIF|OG>OK&I%{g9I01DmL&2~ z<+{)sVLIfJZGbg>e=>;!OQG0E>3wQx)n zeM81VCdo1Dclxf5b`#_ExqZyQ6Weo{Z0#sfNLU^WkWC*BB5 zZ@p72vC>Mcxc{oPW$MGUT;xIJqBr7wbj#vbkaoi;2xs46ySfWf^E$|WCwP1iW0e?@ zr}{yRHKX0Za&i(MhkXMMOXme-=E@EHs1YTF5McV$ntjR`g7mKwZkBGSojp9azJFK! zw2qpE0+F!zOsp|8 zT11RzpZ_fTH2Vx5mRS$WgB7(Ez0tNC7A<;U_Q$U6y{vj+dpTSGv z!*dL)@(?P+K|36XP&QPZS)eSb4&rvjDAGAs;}cw0J(vwJS>#9$K<7vmJOwZ^m}c!C zSO&3PAuhvI1toC@Ff-N@4moV2bOgwFs$}sLUPBJ-2padDqv?}@sloI)@TmOODK+`? zXRjUB(xBcf8h-iwb#p}DP$e*?e}*4ys)CHJ#){#vXsfo>)#R1tB_A{Amu93!*x9Z35TpghXDyRW*_Vr+P45w}( zF5|k9k!zp%xtcr`8SoH>6b(j{h%(8Nk}K!Lj!&O{JO-@Ta&cpkx2V`}Xp>A2djqT3Fw@igx~bVV=iQ;o|U0oA=&*ntWw*8m?fNoUf+W3As~ zUSc8KA2LOPB2yh^GZAfJt5a1z$H8u-9iS!Hv#z?JA$MN4ryZ-K7>y#Wj$~BiywvivZfVBy*lX;|PIrlO zu6ynmqH;>(A%|&o6aN|AzW4H#?d>Zs-@84^rnzy?;VZk3{BbiaT(W_;M9US|e?5v! za&cM%Lmd7!Bx78^YUi# zVj5j!7~6#?(+#Lj{%Pt9glVFJ#Mh7)Ho*lBZ48DBd0Z>$XF9rezqsi;yNMWa za!MDY(dcC-?wAj+nBm(7W*_LzwZTua$XQ-cdEoX(*MdDL$(01VT@`XEuXj3CqvmfL zr`BXdZ5Ak!p%8(b%s3enH>e-?J1wD-**Es|U*hjLal_5iJrNUVE{~k0r*?m5(~G;i zIwH^(aq3DJ=ifRwuzgNtB(cO;?((c`%gGJ9yt8Hxx|iL&ZC1&5!Sa~Z7)(W=u4XeR zdW>i_&yGo>iMSHzQHC!K{CkK?kmiXkV#`sNUqM2P;=abV_}q%NP)W#xn6KTa6HqMQ z{(iahl-+vLT;wdHHzBn|Ov{+72DtYR^YPKFiqMG1Ssn=TotX{IBI3tbu61}u1yX4R zv9Qcb1*-|SKjvFSLOUyMYqOvhHiELaqJ}^Vm+6+c*D1#hCU=E&Puw1HIjv{y_On)} zD`Jl)w}+4{JP~(#4RP=v<8cbq5r3wuVkLhZ-XtT+E@#MaqZoE4W4o;$SETr7G^Ibo zz{*Q^Q%x@MMng#cHYFicV~O}d<+lftF->uv+zL%MZh>y%O;M0;fB%^i-<6!4glV6^ASq1$MCy-1W>G~!PHZe7R7RVL zXr{B@b9EJ%De|2)(Y*3R!w`tjKye_XkYoP^RsJ5uj8tj-kJmi{t z@0{R{!&h9?*A9PXcQ_HRn7+0%-<>BuQL}uY`mAu#uq2h$^aLYo!l6Fos(3_Eq-JS3 zG+>WLTm1z{UT7~E-nebebYpLyL4E|eD+(f&(L#sM(Btr$^F7sFk>K>6=91pE(|jhY zuW6{hys5U*%8G=Va>}L+&#TOL=J%}|sCDHA2RF~~%v!RcwP(|uwqiN_R;evN$E@U` zeo*`L(!wIiRFGTbEpvyfk;ivP@_5E_8%BsAGW=5Ysa-?L zDCtS8HkiARbOnj@OmlE0R15EL^J)XqF_2CH%nDV9OC~0hAt?qjB-m_c1x93UQ6C|! zBCdwqpLlTXC$E?_)I*g(WKZb85LehM%tXS}3rG{4GL8IF$~V75`Nj>5xycF~0}Z3* z>63Yj653)~i}4#~9r@%sLu_b&XzW*1b);uVFr}=zVOYtRKqGS{=o>U@TnU36)dQXc zWS3+;9z(<}=K8MSN*bCO6gwj~{~9RXa1q)FjdtZCo?6I(1Er(WH~bkITTM!Qt+izd)9Yv%=7!;4j!L$0mk9 zpyZ5ZC!}YnmE;%7>iWadaJav&Y%#KSEq(Z!#d3L>CHZAb*|d#^y1EW+oJRdn7=_{X z$H7%isM4m#1sa(ROSiX^x@Qa+RRM*f0JDf|3`YiGVjxJW-k;_-m;7^hTCLMrJ1uutNEnU_i;T-) zxC)E|pHVxrwSsZtRL0JJz!R8odX6zv5k*h+4gAPebW8<6D3-wkttaA7^`%tk(X0ko z^I0!pN&pQgn8$Fotb@e*5m#uaJqU;V3Q-EQ4OX}!Rpp@>t$sT__QEfCqFcagVCU1aau!a7-9om`e%(b(zZ z)-~#_B>1Sl*l9h;=!R5TfFV9Yy}NwmXqG$la-)=1E=hxAKsNAWobaRR2)vvq4+*3f zR_6j0W773Qh&?wc0dySypXGbcJY`!QF!r+FY69(0XAcThYwr?9LbUPphzsE2k|lSfHeB|g&Tn8%O~YFbT(+pN zfZp2Jw0v)@XU)7>(XzrvT|*Vofo4y+Yy4UD!Hsif4;F-!IM7Q<3#-T8igM%1!K&!M z+BR3X((SDETT6qrPP^adukERJMQS`H<(@$OG_B-(n^?PcqdiL_Y!C`-?GlERaUl7-dv&HsX3L5Kz=(ix>1!;N* z&)7>a)zZ!LauVJT`3(y68cd7NC#|Ex_|vLIRe82NW1iwdg{+=^J?g5&^XeL?0g?2& zEV8_E@uwC`YYaOiQIslRrIo7dz^jEZ8@;UUK96L@f92Jk9o zlLj&>q*4_0&luzvfuiUF%7jNxnRxo)s+`kbdSs)aLzvw ztGRglp6=q3GQZVa==2r;W8K`a+LGt9I4hj_vQ#>=&f3!5SDsf&5r-aM?(HMuw(6A^ z?;ey*sEnolyfkDUK7RPnHFvMdEd~P+ysWFJuDLl{Ca!(rOD`R;gsqfcPFb2)oa0-! z^T5t^K5J>Q)ru)@?65a1s($j3=-NxRxAh(FEJ6A7^12Rp&9=PkszF_=+!ddi>sv5?rrn&2LRSI5C;!p!h>tX_pWl@) zhFunm8VC$TdTzY^wySqVm9Q%>Z___4bIh2xbivFrgVC_^r57ISGUw*myjVKY#TXpS z8YOVAEN~deMgi3a=|*wHXCF&To5A1+xxGF`gu)nl0wp&Js-Bx`l$05+%3!(4;c}S1 zICTBcZKCJ4+r+Zl-f~w&T`#M|u= zC<#(w?gP+fz$}Gp+Z_>!S8!6k?(r$ID4IN#Z9cKm#^zO{YB%R5(mWY3JYW!qXyq84)wEJ<^&xj*>xFH!7aT^zG|izSjgI(Jp~}OaQUOXwT;C`@>wOmM4F&1mvSJuL98&I z)GV%hLK|9=saP51B(eMBOh#iu!k%1Z_*8#EWnF!QjAEsE^S`{48E?;ui5Qs#Gg&@_ zSxmSB_jDMFNo+8&qy@>FVMR{vu85M5!sRw7hcwSyyp~MAVuL4Oba;cI5IC5e+&PPv zY369Lkx4OW?oF@hqh;r6NQe^4vOre4l<#E&w9;1y_e|Y~P4gS!YD=cDOp9TZYJwqT z9^I!i=jTX?#LT)>v6@Q`D9g%r*CI=4}9mfV>kavpAN(sgrh+}Z~fh23W> zuP_zm*34SlT)%dtF3(v}wvqB-dO{WDXWEu@`TDlrI7j^!cU4Tw0n`cZT1W5aekgJ9 z*GC2Ne?3MXoWOycEYT;>A!iDRLExr&+TE$LOXM(xHGu9f3K9h~(KINXG|L~u7>u)E z<|D!!M8@bJ;XS6}n((X?ciU^{4bQCT-W;*G9i_QN4Gl%5wf@MZUE!It=GEEUZd?7l znM0AzRkJ$nCZE&kGu58jRWUR?ug>l^oczcy=FI-pJ@@=-_MBh5bJ}fdTpI0PP-Y6) z?EzE1ud2l#UD`-Z;QF-14wK*E@SAeoCC#VS+1%27Lpv)gcMh>n!zg}%(|(P&anJPf zwzdvmO-=8%j{bd(zM6o$kUv@E3e@@+~AMU>Q z_sM^zhNAwqE9TqqU*%9=r`sQ84FrsZ5r6Bkx%6d=@Lj;5rgQZA|3g)s*bSGD6Q$uaHGgLohwaj@6;7+)4N0RUCwL zPy-A1!8?WQcqmPUHN^6220aR|$Av1G>Y!^RM7$#dieRx z42ZexHoE1xhi}@_Z#G-0WJa;Y9khhZ)99{CQ~9^Hdo=ClW$Sd(Gr*6a>uhlo;wdm4~=y*n#9=YjK z^xBd`sfcNGL3975H$C!|El4X|efj0C1uNIgZ8eEa^ERwq;Ml!y`->kt6pY>b(8otc zKK8&}(?f?JeD;kk&p&W5IPIc^q72Cu^?jk8GvO1nAD}EajF&}*qiiwu+-4*3p!&bu z;Z0J+srcMsIBfWP#y2gK{;wlyQNP_G2Ibrw^{ECq>_AOX>G)(h^_(UqWK?!52EiG` zS8|I{kuFQby|JK5-X7}H4u9AUzd-S?lLXLn+MzVRH-L6ODiL1>zX9^jFaZ~UHddbE9KyRm7`#x~LDYpSX!u{Zi-Yh88C>R+`YzORelL=oR_zTP$Sople13y>?V z_R3q-U*DE4(c8Jx_+#Brm!8*i`dcagsXjY>x~IiqDlmyNJ$ZS>rVTe;)E6v}TG!63 zaz^TGWr4iu_pK~RSMu#?Z|^BuqrT=GzHXPpnX1$KTPZm($u4{@FD%%BHF{fvd7lR> z@F`SC7dS(wxj@xR3~?YHnh`?A*Z_aXEmkO1$T5J7c-)W>K zpBT|hrYlv2cwFN)jDC8WHh9(2T4z%kyFEsVWnyA@i%+Ed#<5X@N$3_R zwJtrWvWNKN#8e@aPqWa2?+zhaeLc9$`(QsJ8IydC2#>>+kZA(~WE;g?0#&exM4C!n zxy&@C98=JSVV-czn5(N1;I+ZlsDa@VnDmgAoFpCEFq|9{gQ`7P53?2h2$G+jvcN20 z7M{)PS-Q2ebL-NcyhJw*T>hu@6>%nI5FkY$nJ@KkanxCEw7P?K_`}Kt&K!r$<|r&P zn!+G_ZmY4}nH)t$Y3uJdQ*)bt4?m-*7|XM|ip;(eu$X!IGE%+g%lUaZ5m$-NT;#Im zB`~GBe$xoB=vY4(jSjBwATUTDXLbUe8Gm%a19L15wcfDXRBUuqx?)n{i)p`N+Jv z0iC)qJ9`Fa+lOc(5(D+!z$_446@AWlvaDR4VVG92@*OFoEAe8QKA?zYUZE^2p!E$b zPZ6^SrnQ@z*?}dmVef^#l?i^c5zQ=y)KhB5ai@7*Izu0W?TIPT8Qd3D$t~mY?V^MB`)yI|C zEJ-ObPyJj$SWMM$Mwz(-af|RTR>*X-=m(aTnr@xvvUy@#Fmt?4A--fljk^R z*EN#B?*l60rnE2&DBRRFRq6{%pvMh|@-d_$YK1TUDB%gQ6HLl)2nv5=i8|DmXzPx` z<=gEcYhujXIelQqqmT*+07bWMQ)8Ufm6=z3g>#VJFad<*2mRlsNDPs@6mfz)_@BvEsW zg%>qZf3kj(dygHNU$sF;*?254$7K^15T}WEWZ$4Lnv&t>ItR-rKIQf#00v0z8sS0m z2MAwl7&K$Et~j$Y`3}29dY8V32tM!c~lJl8mVnq0zxAC^n>hn)--li9MDlKilf!7I&*jy z{myF1-cP?T(tj3mp@GAcLppE>ULnYG6EwkBs~4~{>m1Mnd4OdoXz4)7Kt~H%zL4dp z#fpptR`6#mQ$ZVY3)$G5hm0v6D-8=QG%^lfn>ct9%AyLmsqw^ycpiZE%bqO<2i-js}a4u2<0)}W> za_NFd)OKf|&+WFA8I9f$5R~3r34gR|r2USZ-VsXkyM{ztJqEcSOc`@sPfP`XW<1 z{Exr@t{S8c)s|Njz|8~aOM)p5rrse0n78M-;F{1R!;a8wXo<&+DMXVlPXWXtjV5Er z)ffk-8zX-Zo;ZVh1Q3qlA;pBTm?wXP;zYQfAYQP(L~Lq@y$nC#5K{ z+8lrLunC?@I?j|c$oLS6W8{fAQ{==MPGvqTJP}Gl(7Ag9JHjO3X4W7fQMS|KuoCt` z&u}sL9=(Q%J~GnyX5t%-0pjGCm?GxQ!$SSj*V43E)TA=5ej{o{0v0if#7m>$P)-TOO<0eFj?aP03m?ytSG{KUlx{b&$Aah9n9^SV9*fDgwbT5^h9#k z5+>;$Q~ynRnbxvA4^#E}3w)@v`&5l@D}uCb+> zI^qzFSRUXFAoen0KBAj;Ak+MX}&Y@oIOa}oA9`SEe1vnUgIl#E1&+E#0!S>T~-Fi z0$>CH<*S8Np07zK=r+D0d>M!W>LAO|d2-g8LbzV!T5P$~Pg@YQ;CilGEOM0~NH| zWX`)uSm#7fI)9WwkBKWMmX#x)G^EjyQDJfg6CW6V&5qJ=$Auw+GM)$zLYE2_fF9*l z4C#nhHayouGUMo>ILB&G^CCL}&n?C^Y)v>JJq9wuc#lhfJbR z6gvW}fE9c9p9oe!_K91%iyL;+Z*OB0TOW$fXmAmgR33qO z!5y}~9k;%$2xG77t#EospsY=xsynUd7ogkE(9lo?%@u&Pbc}&ETQu-<>Gm}PZfYhb z&dkK~DdyXgv9|9$Cli0C$w_C&dapEW9FpH-O3pDyhDmcY6v2N6?qMN%e>ZsLNUhkFs%_Px(8m^5PS zf^+2+JJ&9Fzcrq?=2M%4DJ=P9AUKa{d0#M?1Qr5H5~Z}c4Y0XSc^6Q9bk1lp?kO@w zsJDs&laP?80sO?20bGf>=t;01l;R-*-O+{iJA7gNMh@o->-lJQ$FuQHA9$WA&v>?T zASx`e>1XEhsm?xJWH9TQf9J6RoP`8Uk3`1r%Qz7}z?fK!3|En)l}?(_GewAsW$?aW zJHdid4_#9p*rX{D(iSn|*RUt1iZv$giK$_QY`{8??JiH)6d$1903{Y24><_hmZ|ld zHe-5Eo=3mMQE)mO0Z;=_b7#^fwkM`KF?n!dz1uQMwel-ly{z%zLd9_Kbr6Ay>GC zdf55|)>mB@L*6TFOR%p9Q+QE{Ez$(=-$Xe;%4IJ0{u*hCXjiE4%AsTq?yyOWp=zJi zYZhbhB2q_R6F-kqBMePJ14tf35vyn=g`)a81w%>k;^HDpGF}L=TxwDme2`gxFWfq{ zS^wa0G#R;mkP&@wILg!@aoGOw;3$K?J`b8Pksq1b$`~fFBj+)S2@J`FQ#;0hoHx}l z5J+4Y-7o+iv*<<^4|ra@)O+FBzZ2gvnHPhf*njB}87C8s4>F=`YMy;I#Wm82h5kKd zrVy1k1;P*K;C3%q@|d_^rZ@&h`Z6}ml*d5srkUGC)1fJ#b#>6Ru7X_?euyl(5V<1B z6^axY0rKPRqi`8I?a=ZHVw*Q0{gWkZiz?ibz@uWqQ3dX|6q5uKXAZP(aHI0U(<4fm zMOkRNnQ6^_I+mNVf**`0ZgNFdRZXk5N9XUKr~XveqE)YhLE>A|Y|2Vja@nwwt0bO# z*r3=Pcio_je?%>uclmJ8?|e!6QjU}pA4fDUEI=6IPsu__tpl@sDc@MpF)KRzmIF(Q z#gMK+`}cMEh2qMVns3c7LNt4;YtAm0B#WPWl?uWP!rsdA_PHMIy_iJ75XnW#0Gc+UJ=C(P1$$V~68iJclfoTy8h zczz!AaVol{Y&=a+F=Z+K{a|8>SYZmPq^Tncu8e;V?coQ~C{NTFzu);3XJFzIzu)NZB*mbHZ7;#}65soJwdc#>7&gN%seO_;XcUZ^#PJPTD=&EsMm|IWi| z19*c^51$z_bg&~z@IpnSlJH05M;u>j6`%BZ_&{@zvSgi1g#$3_FR6hQO~C`;h0KIQ;})l z?F>NfVEHr=h{c5Cv`UX#%*p!+e1iyUcc#CB?%u!+*L^C zz}qquDaao;*~@8`GTj$mq|_N-ip*@NSrW`uG;8#0U)+zZg=PNYr;4pfunfBQ09TGM z9?z8=C5IiT)O4=Tbx@qAUJ{*QjTEa_i%;&(kGKL>ZO3#(hFZJ^@ddM|%)sChZD>ws z;Xn{n93!l27s-*tUyuCi2eMA{$^bTKsS&VTH5z4(Rx6d-kzVQ{`uaAieYzm zBa3lrO~|KYz3x*I&UQqjgbkFmgE>?OC_qtH9ZeJa!QW~AX*~L>C4kIQ_2l6X2SO5t zGJO`9nj^J>gn$&Z3(n4=q=S5r{Ex4Q5=%U%Z%yu&DyeZ}Fi}kgQR&3jJXMA2t2C>k zTk(}$ruNaX)bg{_?3(-)+GE`HWRzWrvri%YnH(kKu=GrFw79s;B^Gr*huO7RPESYL zJW8}t$c*&qP4j}WKp8{IOjttz?RVIr2f-|dhsv*@Z|=7C+GF~9Q5{(gOz>uraeZ{( zS)nJn(^^^PZ?*o*_irghS-^r_Z<-4&HfUzRK~MC++VVH@z{*+|wl@&7$d-~^taUvV zzhceWJAVrgEKQHdmWX|*qo5&eGH!8D7M=lE(1IoN?qjzC2oTlRu|AO(%_wANOr|$ zl0%~`{2}oN@rLckm_{)PK~BW~X=hBzEI)QlsS}TIkS=yOz1G{Ma2YPpe{7QAr#PAk z-;YRlCKsuP#NaWmdi>s5nExhvyI#g(XVM-gNV}cF_$lXVjf!zJfk+whkJ8b(HFbb^ zHJ*Py3GS3R|c^!*mSmf~<<36mvv4nl4d_=aOuN=i^ z%D6{s^2krb$7w3num2c12-!?>NK}W5+~qz@U~!YPuuL{5Un}+G+Rf^Kl=o;!h1+di z_~7D9M@>nL8!H_0F%l_xGs<&PzRRg$iDXWBprW{}f+Gq2lbkj=V5-OS9i>Hvr_TzK zm~54x&>a)%>Ymv$1&9fPs!5f5ut(-@rXVOSXjbOLPz*x z3>AID!#p!Wg(||ndxQ%3z$9=2(Pbc=_!yHGSd0}h`Eh=Qc{{Nf8}8|*f@vgP?3^Jg zabj@jkFl)y(!~W7jm2+_rKO*Vt&I~#`Vi&-@%kTN4ulUoO6Pp1AT!EticmmOBC&HU z4vlkn7J`DzI5hH(#Kce>8uNq8kSJ3@)<;>$5M%-fi&$|E2W+K4>jkX88i&I)`8Zo@&+EI zpJIsrVh8EFc??w=O~>h{#Av3RBtN1u(ki}Dh&+of)OX<}UL3qF9#Kn=@hCGfruTxA z8u|t8iL6iQkhGYVNe8rtQJqoNF(6?eY<__VdSSU8WSc6=;3ML4AZiUkL8vK1o~R+n zV%w1i;}PKt!t+>_D9?ze7m@8i)~%|}szQ_N4@Zj9wQK4|);Ir2*W8c) zyOzCP`u^`)Yb!f@s}t_3g|SM1w7?&#C~;4lv#!V2=b1hf$T3^nH!qJ3Hm_bhTYXD= zO5Ca^(D%CH`3;`RPUBl^m#rG;9eL?5>aQP|dFSmnP214A@9>eX8^>OIGS=2p5^8y; z5)1R$+L2aoaglG`6|7cGacxZ{t5w6x+uXQt!NVWBqxVm`8Ne9PUo&>!@2Yz{D{EWj zUpF7S{?v??6~R2GtuT!0*g3ZOn-3kka@Sg~1DW0xv;K%0*E*}N^fcthUEaq{BF&UX|kJ>1Qo& zo@?img1TCa5Xv13B~U)kD@;dB*9lJ8B2z0t;y2IYM;fiKR)Tm?atmjp%`jHq+R`v?vNc{oSCmtf z_#!_)+3i}@*1pn}?9R_;5-q7{fTN3G>l{X|(Bqn=Q;Q{}5sMT(fIXThH*RtS4NQ*QtI%-rlq7ocA)eJ&GAV)P4&?GijH2!}goaX1vXqm^Meh9cg^&T% z7ie`|wq>XE$Z2*7lL--(N#&=JY`N|24;!P)*QYKawH@ls zY#VQt9=__(#)XfsPOiC=wSzWjVMy8pxurMJA>M0@7!2FJr`aic5xtH&bK171_P`Av zS+MiVtB_yFg=$}g6~(K+we>4vUt(nS8^_Yu3w%Xu{$=|;>Th>!YuXg8;%%{)>97E- zLi-E2wZJQiK!Ya$u@Dpyx9rWE2_V@Nw9Xt@j+DXxl4izIAI8B%Im9^!XapjZpTdK526una_lMBnuh5XUtF9nvOYC-|;3 zew@g9X_1v0-vmL74U}5_Q-C4ULm zpQCw|g~Vna=r1MA1haxMj=dN`tm@p1c}z2}9Te2wfn;CA&mi@1hFu{=E2CK|3uRXS z-eoN;cgkM zJr!xSUQD0MiKCi-!J|TPMx>`>+A)^Y`GUs9YO*Isi9xxbafz|cO7=89Cal*78d;XD z=l{vbB#car(Zq=V?Zzf!Y`kog^xtl528=Di)=j3dApwqtLzc zk$^AYJ-oND>ke^y0jh5Oz#5qZhc2-JkSqWQxs53NnhITn?8`wDuUj81l zl|ps@HD+s^&6bKq=&byo+2U-#yYIdPn=MXY|E{xzY>(K9e;e3BtuFCvwEiFZR!qhR zpUB2mhv+i}YYHZv3sLOd&x-Oaa4(iocR^5=G*jmE-!nW%12K^)kqqM)9V? zH_N4_96gRDhZ0?oR0_*0Bax0A7Q}f$ycxpY|4%C5B^Aqp1JFwZwV)_C=;j6SqmU~Q zL-TG;YOkG$<^O9#7NxZMn?V*!*rhX66X)%JsN*DVE9qx=G4?;yaVorNg3T6}vL;~r zzXxpT;`4tIK6RYU)+ht53A45PZ%P}9Dsn{wF#m_hrNWjUPw7=YRFXKxgf5c4K2&{6 zik%|W_XSbDpXqGV1?pQ25Q8|GH^lzCXy|2zFAhK4b*4*_0zx-Nq){hMO(>atB6s+f6Ss*51Ysac-8I|u7XWXOTEv-0YGAy*)9?anviUea+zvr z`(Zz}!hyVpYBiyQLpECRB(BtIKgc5;xjAa9EjmU@Hu~71A zy+(xi^|`mUi$k$Szdzvf)T_@`tZz|cWG!OSRK)d3loEO-Wi^RA9jlM|6w~BX{9|xD zf=*6aE$!{QdG5-GjttjGn<8l8NZB2p@=-?iO}mwoaug)+1{7%I62|3?W+#zvtnh|h`S`xGT0RA_4!HyE~J}1D>SZdpM7Tw1oo0>%V48-R+)z?olba)ZYQRJy|+nnzIkG(g6lcTKj#_Q<2XZk*-@4I`Z z=f2NOI>{V4n4C;*LPA0afdrCp<{%9i2uDCg6e1{yB1;4>kV99;1qBaQR~KE`UBy+| zzq`I3yRR#m=`Q}?-&0lHJ;?-;KzR3kKZ|j?y1MGAr+&}x`Q1l4lnmKynrc5XUZi_` z{=P0MLX4AEJK4x-I+zn~fg9kYb^j>3%}vX{e2>??+{OqicXBIk>PIxK7dL;7m-dOb zY0~ZRTJ(Y#OgAKJN^QE&#UlRsluf$_>ilW5%WrjtgRzE+w%wp=3I}Q6hEVrvoZH78 z=I-VmL476WqlGnd0Q#3vF6Q`BWJCK#%%t%D2F6qFTmB!Ui9=VyVj$Ncm)?7Hi`zw@{8Av8f%`A>0*xuC8|&uwLGJ!I~~O0d#Ih9`gZ|oLO?J(Vxc;s+_uaxift}WDeGLQZX!=FU(uHC zdHT+Ly$kj|vS!UA`xameAHSa@S3Z4mRG9QychXnQ^13v6EP;^A1CE8UQxxt3}^qJ}_=kMg!>r?2

aV29BS)l3^&77_7Es$2nU^gM&1X6XD)ojZPceR58BompQMZ5s%Ntc1@ zM96OmMLAKZFU@ZmF#k;F$agRS#;em4^P@?#y`p7$3iSz~uT8?4zslqWNl-8p~7 zqNKI6Yu$al7LVB!bYYP@{q=pZ-UhAH=02|>)>rR8% zowd3rFc7rb(}lI?0_2<^ZY<&A!n43cGSa*olq-1NJ!Wy!(Fhw~;5X_eq~r0J2_3gjkx%2P8{pH0qjV_bJZ1&l)xl-?da43Lvy4zAS;k4iI@|_D8-udzk zb|-XXOYO4M;=L>C>sRbuT+<%31gop9={HV+W50RFEehF6Abe3^dx-l4_l5Tiy60v; zzsHMlPIv#iTfTEcB6P&CSr41}=wNdp_|*rv4}vnQf%zHM!dEGnAS>CVVyksgNpEjw zC6x#U> zqszeOtE;OlQr_S)e&gwuBjePrvu)K?^Kw_M?hu7TY5tAM$Kz@L`pPT$e7^7+e^0ux z@+YU12}CHIv}}vzj3qtCiB2|aVTIp|u1}A0&r$y2NtMOoPz9OrO!gG!^`y#vBv^-; zl>>`LDpAS&-UWU+t){z^OqB}oPiUHpv^HYUqzbilu>f{eyHvA9Rcka`b*2162}fYk zk1?BJ@0n0;&RAti`;Q{|{+E@d`1IOavYo$W8L@LDxB^)*|bZwcCaQ zRI+pTWAV{78G>**1<~HkwzbH0ZMb2yZRzk}EH*f_q-{Q4q@z`;M!^!BWNPh{o>Q|d z{=w?fJ*K{c9IBkBFcIBnM{n5B<;nEK=>^eNhDNkk zbPp=!#A{jN(Iji~l2=ng`5BHRZiZiTw1AYTqZYTQ=xEMTn~p2lNpe<_xRIQdGyGqA z?1Ym@*Ojpcki&lr8*=a!tp&}#f;$4c_!Gbkmx# zhh5pO%EvIqplIMO{~PI#kMI_M!ZT~sBfTKtKYoS5Xfzb#eCzhk1#`4EhgCFK^wzMY z>ik^Qsym+f$G?04y56t9dT2p$jUn97JG!#9ZG5!1A#AwW9B=GPt~~mI%QCw2)0ZDQ zdj5h{UjASzCEaV(>y6R}X#+~n&ANSzwBFIZkE|yMZ8;Dsd)00V1t^Li$ow z4C9R-{lMiK?s4hcQsEmdTC+CZQD5sFe`%{NYQw+W!8vmd=Cn?Sjo0clJm1hfeDfdw z_OWYo3l4qt*V5#FX14FXVs%&7>MM3{&-CrTZdXfa-8FYFUv~Gk>p~a*>H1=R-=RbM zFJRiEiw+(ZD`x-zy-NTs55YLPWE3cC|c zemRw{2$R5D=*vVN(Bd^@sV;IU)+~;^gmhMJ77xP4>Z4IPJ%1DIFRbX7h-p(*Bb}sw z30;=uTtOyq$I52Qa#ChD&%DXZSs=fmm_PK}C%3GuzxM7&RtleyJ_~XiaPTj>98y0l z9supy;?ix;9A2{Yj<0SP*e>L`El>XT&=Vh7c+H9ieyuYQNa9*o2)6isILHvUx@N_Z z=Pud))jO8b)&iU{D_Q!bUJzs8vL#r)eLf?s)6AO1II3DDyIHltmcUxhnk}U^PI9XK zU&#NMF*!bm4=4T-GXIz+b|&>qKl3AYA8vO_6KuR671N94v}mj$LLau0w_$tr}{K1ym#e{ae@<6(${;|DMZdAN}0( z8~6X_0I!I7yteWX6HGypuqDx*sDm4 zufn;)j6{bP(D=OW2;KT=>4*E~?^;}!yNbr#|C^UEe&^=9*3v^sP`v(+tV^o&#)=zP zH_1k&c1cNOg($)7&3-Z@aj>uhE@@cU7o)c(q$|+9^1I5WnCak1O_yMs1il2T5-~ALO?md-xbyG+`jOd{kkRrd(gp~y486&yORp;auzA6u=M|v; z8dkNFfS!o-X@6dV#_}82G_`c(k`t(?ju1$ff?Rc?5Al`k zqMm59XHizSm5v(vY{9hq_)&K{Xrm)x8x?7jS{tL$#>Qx*iSKA&M^V@;aIS&Au_OFi zK8iFIIQfq9xoD$cbr@5fxn#62+!ylJ!jm4#)P(RQnw-;-GCDjB9{h6{lO1zX(cW-h z7}r3~t@Yya{z%0&g}Lr<*v;Q>vg^d4H`5)7cGY^lbzPANJ_U~6M!a8ruVTw^udrNw zpTZ?{RPC$AKQT}56m4vZ$e-$rPyB3>es{tJQ?D&d^wxRp#)YdqnTGa6qN6_JUA55Y z@Me1x3$%i$S&)Fw(r#L?+EZKK87IcyU6BGoTT<>}CFG65cZ*#{hs9zPUN;(xQKQA;;G_8V9pO84 zET5vk4h$k3!-%iG?TE*!glCEis(cPdmGCOc6x3HaoHcwszC9y6Q{{BjR28#T_z{0m z0D%`aV#IuDL{%FrC2;U*VIxhAO{GitE}JT5qv6<$Y2wB1VmJF8m(hsz7;*eibZA_pO;|4rJu?v77$R&uV$uiO|AqLVX zuP3cfR`?+pLh-NX;n%l7wxmoHCkzJ_Tb(3&0~hOFls$elyQn+HQ0}QW`_HeN-x;x5 zZYJcL{6^FB!ld11^~asgl!gBXTPWpzy*OZV8QXI6XEkcAHNrxTjeo50Wp|1;L&~k& zN=G`bur<4ocICpXh~aypZ}vwz=hub0{dkVF^!aAtxV_2ijQgNzNjJMwL0hrkrV*;` z4y(y+)9Cm*7lyS3Q|Y>cG65(&AfkJP)jUu=vX{XMc{Ac0KFRsOdOpYJphd zRjwKT%E{;Cb>!P=9T8}PHI zrfrfJ6$$Wc*W&vpgZQoY2&^Z8u?#jCf8QmvPKC#(Ixn6%v@|7zEu*P41Dd|_-eBP= z!9^6q0Ge=2Kdv7K2S{=4s0B6#DR&2V0B}@e6w1|3+TEb{C=HLv<@gP}guq8XdUWFyT~<4^u4C#vRi$@CycX?Bi#Ot+qshnVXuLI%ZiRf+ zCU9+uWLq^(rCRy%)?@<5VRm70rKKj~NVMW|d$Nrmr`wXPIA>3^>BlK-Y-(lu1tVF~ z@-C}Um$e1bOsS-yTuP_1%9G+7@3ebmyLXoed7i?3 zRfBbtRyBlE@tG7fF!j3*bp-g_rMI6qcE|QXoqi8R=5(wdse|Co!ksJ1ITdcYpyR69 z6fVfB52fZ_wE5YwwadS~oph#kpSkO*e(XV$vmWhSXeiWx5& z1};4K(Z|=(mpwmTwrurpFYMnxhrX@-Jp6dflA)6;GmRleifS6oV9k&V$WIqeo`}Got^?vhX-0zhhhnEY*;O(E*gKr(sRm z<&08~BI1*PpRj+*mQo;`ypvVm3oHj^Ln%e5h~Xnsdg)}b&>r_9Pi$+e`s|7`mV0^`$tC( ztnU(!na#Uidi17s&CP3X{>-;`nX8hm@nEo}xyirz!5#hmJ0INaZ*FP{1{2uoLnhU5 zMuX9y`r*CnI=e17FrthJbUMb5>sKNm+Y3HQd{9m&C)rl z^b{8woF?JvhTWROyN|7qUXlLv^;q$%sbjoD`uo-T{6x#f7Ky`OsRc(Z8;e)sL%dVC zs(3g5#mW4{gf=gohglth2RUyLxjw{sA3==wv*kz=)>w=*LpcUZLu46*w=ysbTZPG0 zVf-kYQ7NPgXux+gD``P!RNF)&!7zza_>IE5`~n;l@=%EWaI#IssE(|UVgM#cGvyRt zfIk||k9#j|+5DwX+}=NX)7<#Ny$`IYUpY3%yT3lwlj})^8#_D}zoVwJe_m$wz4d#; zR`0UyHy%y~+t`Rk4#`CA^LzHh6jYeWrzl~gLG_Erk4>}W(IS)Pc z;>DN$^yj}lu=dENj;e4+?avmD&%I!{t+6o_T5a6$ZI?B5F6e6NnAh6Y^fPbJs!5JsI()12 zhV*aJUw-%U0(VXA-0?=UO`IVANZKEbQj$`rKKoHQVwA?Msj1dg!(svZxZ-yjMurkN zN=hp)*r0}m05}lZCtHTS=}cNkC&{A(HjPyR@zdy2L^ZHz9&Z!Yi%~`VOcG6#plyTk z9L^@zCBW^9z)rv%&M_AFE!0a%gFcY5c|(4eroUgdLf&;Jul<*=-m*~WUl#b}@1&o8 zMm7NtbZNBJt{RVBwk2a!DiI;E#I(m zPSpnZT-&Ob^$XpCzLWsAgpQCB_CtCY+1xmH79U4w<&V&lRmkd(!^$#;;+a{`N`O^~ zv|vbV1&P5HVT@Sj!3dQAt<-CE-6_7p>>?W$2-5TqHcb!q8yWQhY1%n3aPly1mKl?5 zypfk8O#~PoLaYf3p(0zeCzeSDwVEo;(uO^Qx!o&UTN*x9+n7l{%RCHlOnM7XRNExZ zw4Ohp8S)21ZSyjzA^+4@YmwzpBUrN5^jK!rW!o;F?aI35ede)G>{}FUAE~b&X%F_) zg=R+^ds@b?86Li7yk+*>IAtX%0j=q=^s;Ba`NG=KkKFUYd7d_x4W%dHR-M1~>dTf{ zs%bhctt)ruX76fzq+#VC5+t5wE`w*&osl+I;mK-EUO2i@GtWPi%FJsE1^rXc7hgc0 zhK6@&&2E#`Rjt)Hv%_0%SyeyU77VtH)(>9MXshYGA@vBEJcz7=5B(bPp zgR#CRR&5KUJbJ6y7$P2lg<}zoUyM*A&rbS3H%B~1+j@3ILV9_K-23xJcfdFk6zt3gVdK9l$qy{@2FXS0GIrWT}9qQ>A|xOl#s1~Opk7`#UC zRs}S_flFO)FzK8b=XqB*>$HMS&|1al+yzUU1;HQ~3{_U_L8M~wx|@uSDs!kOWVN+6 z#R3NYUvwsuS#Q$nOqjD#`y;EtS!H(UnxnyiwrIfcT9w~zF(#Kef?d zHVImx<$@MnwZ&Cq`YoG;RwweMk;ofZ!d56_$wLrn zAuJ|(?)5mzQZFI`G>QilIHE;U9WTQ&@#~?OM$3!V-UhKYWDQ&6L-9MM-`{F9`l)!{ z=3RTQzO)t(_Q08t&~ou$Vl?rf^y812jo~$a)reJkBMN?_5e-~B08K&ZZc9+N3F1kHxpSVC$TLe znn3v!sWc?f<27*~&ai*ji6E8WYM`aEq)&shPO0jyVUmY8!YDRXw27E>vMWT-EEEOh}rHkX>3ld$)M3{fHFY0 z>T0`5!wVXn)|hG^92yZCv_?-nTN^WaTS7HyU$ryV(mOX<9dK0(K%pvws5ORrTkAqL zjoD=j4|KJpeBz)Eo893L8auajnqB&)JzIxclHrY)?^xAs3&d=x2(NXA!bYtr)6&_L z&>0;39o|&S*oO5>Yi!PF`@E5%fFrf1BR%W-ofpQ<0cRBmtU>E)j@8T_S(pU*&35VU@*ZSHQ4k{0qddx2WzRqh4qs;JGwX&hmD zL6u)*B3Th_rv=~ax$?0qZ+>EHmKeQv_=c_H*Dp?@D?-}8?YSe%FMQ~SyGYzP|KS_P zW?y>880na2G!uRT-k!~xJnLl7L}x<3w2)yKS;E&Yhm5j>?vMuXPrEtgclylkxPAWK z)(bw?=Zd(DAt>GAz}F9WTLUJK#TRi^>)c(l=Ee0spOc>tf4U_&Cvr1yy=%uGZf@Mr z=&DKk&Bnjxm){@iuQ8gVjqQ%y?YAB3tI9OgLL;bzrx|s*ogC-GMy2+0L}}FjX#iWI zFvrnm1Tf~HPvkXuxhsg2r_@CC|2PC!1+SI3B<*PUltjIT)8bWS5tOn%ySo|$@<(|L zT#d`s+1ulho-FQ`p4;?|i!9ZFl*{h!9~`XtA^&OqG5(3yjAjJ;f4wGPGCA!w{T}Hr zcV$;+bs4{>##|lr`^@W~di;SQF@35v(Q;Ful+@g&5j>H3*At$05Ek$P% z%@MjA5GN2844gZXwugE{()_|b(ij*D5F}n}=U)=<|@HHwhA_HZ3h zK$5=gAb~N;d(*qUfH(;m^cu5dI*9x$8Rv=Jd*nDe!gGrDk!?C;=MkPE6fX{uNrsFw z;yzQdr8iPepyl(C-j<}v7Kta_EEEQ}yOZ(AEtOqC(1|8bk+Il0{`A*q#IMm2lpiHV z_^WhI%&AGNa%df0!t9mI_EZo;EpZm{D`(3(<1L}88h16Bh@|W2&T4lJ|6LkL4y@om zu>ObZB@KIw@@PDejtITwRmgK0#4?SaumWwk5uB~f>BCcwLloGfi!2tI z-f5o>$;2w4b@}P@Q$WLvlT!+4rh`N_$Dui_3b9Fyue_anxALB3d|2Ht(jOzcrZQRx}0pkK*NMuK`GZp8-B5g9}Lq_Q8ND;c=HOO7ljY%1n&uc?nZO~sFx zoYDH4+vTRRt#P4E5J&bta)H2I@W|egLf&cF{Vl!GV6z%b`fu&FI18X8V}CN+?4I15 zy_b#$b-D9Ddd-rm!W8X`JPl^NE(q_#d&xHmMMzU9w9{@mWVy1!4~vBBF?gB&M> zHLpE-?NWPkiDsgrD9F$E|LLb+8Z<&8^qKv&k^Z|r`MK5iNq>0l-aze0gdaWlg`fNv zfBWti7CAz8vmZ$>#BVZQ<8r9+y|6Et4nyx(LRlNtncy{K6}fB?G<5F z(<9L4f8g1Fk&08>7v1``U-6pT34Rp8cJ)Vn%dj9~p6#@}9I-OpI3ng}vR#~HUtn~J zs>vWy)Pw3l01>nKBhsIQT$EXml|*X!qEuQyf+2#NNZ1u+MNgqxd|SM)-WL#F@s8Bh zjd+Du0>1jbxWyp~#WtO zysKI?i<&ul{5SOtjRBUKa%~ynt%^md3%m@omCX}j;2&)*#p-veWNmC^&CDG*tl+Y8 z;t5b!W>Y|1k)sIGY@N>OsAj!3QoU(c_m(>!|JL@II&%nX<=prLemiZ@;M$f(&L3?g&dQdNKD-UB_CzIg1;i@Rsau`cGfzfk^dT62wiXsF(c z)~{|hTUeit`YZPxAjwLBS0}5?bT#*36>7|`S^+jkH#$_EBFkONG}>P&gO2}SK=UN<@3RgT!x%h0t)Tf*?uQ74mRZ^m`ijvc)B<1;~* zU&7#)>`A-%==?fcZ&I)0&1z+vGQKlxVGX%FK?|ce#~tCno!qP>jy!~X&l0pudwG+r$(kQ`> z@GEvCaG&a+ti1O-S6SP8`&+{LFX>Gte!JNbj0PPhyuJn27j+Fzkx&_+~+&#AQ2a5|o^ZCSvB|ktd9(p;JDdvOM zdl9!B`C^;N(yvOEARVkC7-OzV+n0)ZE^_8Z#nnt^1s^U~T!K_Vg!%t8Xux6~{ZS-PX76DcRBurs+P2X!LtpI*ZQl~oF}N^V?nqN2izoU|eL z;H?;u6>va_HpqxMIhS6^lLuh%H2t&sJgRuLp=e!rvEWv(Ac*-WX?!DLg|1yh_qt&88fs%K#|@x^Sq97PD$-!>&C$SM_QG zDX)wFXX#Dye$o8#fo|)`WE5G{G=$UkARXj8(aqDLPU;0NK!YIEKJ}VDa6og&U$Mp-7hD;(6nq;GC6B`BmY4L?edNb zvCT7eH8#%$&P2d!4J4dj5TmVw_DDnEzQU>2$5QJZV!+l=g`= z5O>jYCmK2_8mUET#n$C~30*kri`5pVE z;9o0zb!(7&$Lud5(vxHpZ~{)tgkl6BzCVpcqLdXxE+8EX3xiWdG2SplR9PvjJgA2; zkEPwhciv9!QMrwCD3@SetBS6CZgFuhv$Al4rub*o#=`$*U`KM#{tb?yd59*G9>LrQy?6Vg4we3KJy9Wxw!%M&!*!kXKR*UQF(dQa9(tX4K2bhR99 zIZzWIg%^Zc+D;s|Vr2FVDG#qI4Ns4maJ%^lL{OLOH_7pH%MjeO)O#e3Bh8jn?@T;<^^! zwzYp?+nq}(4lJH}3Z3&P6s+?CGGJZM*|A}`PS0{+%LB@(t}Gilm!rYeAq(sqkk|QC zDn7XVjxu`glY2nPUCEHR zI6@>{a>ksm2it0UPzDf&QpBqPzSK{HvN4@IAQ%mQa{c;G4qHR6+Ma0r_N({x5A3~a zdqWiTxzMBeunQ#GU-)gP&Xw?61JX;VQB9P|p#KG=ml&c_&u7t-C%ze^68Y7bI}1Vv zWR(+NT*vHMIIV6iyfx%@l2|~r$!SLJFu2huL_pLvS#4W(?%ZOtcGtSX);SlCq*5an z&%wOwx_@sr*EcmZn$76H8;-Wd9mBaWS_T(5H3ycGbSW2BnjpNB`#2_NuWIYN>F}LH zLw6p&sjqF-?4-ES8?|kDbj%mw(m$s2vyQ(T&PCt(RZ?$9+AFt8*D1D z`{-W+798a60nUSp0$X7k17^!=ClnGAX34opS_U+GI?px)UIuA!I884CT+w2!Nn@px zbjWlLH7twr;uxxcq0u>(aLi0NH0YoptPGAU91Vs#9My?*I^^(q?gKY;-Phy{r!%Q) zM@J|;JUSAs&l!@2zU-2v+3{?)TOT*fsgKN`KN1dgD)%KjXLlyOuKPV+`9^1JsK#@^ z*Bq>G4VZNfLvME3vh0d%w#yJV3<(EnL8iMXFd9+IWMJk$?vMBb9$VYMoY_6DV3*^7 zt=$)BvEP^;aQDofGtg%9)ZmJ~x)I+?nvV+HZ7p z2HoAtO@=0;E7dz3-IDFmRcQ@|?(7SVox;T|D0}3BE)aE^1C{_-W!B%JpX7pwnUc){ za?4wpoG7eJwtF`ww0FQy34AF0A?T94j<#R`h;fJR4}>4VsaqVKAzE)w@fEihlmqLX zFV#_K#`rU6*p&iEN$Y03iMTe;cr_h~c~vqfpJQxITl!(Lbadi5?4Ht0+{phV8?&+< z)a^6v-I?W)Xo!x{{7TFHBsblZ=xVG%c`_f8$4gAAW5#WAZ8p}^B7{-nw92h(xQ9NC)bYv_x4o19bMfWkM^WW+$(SUR~S*4N;( zR*?-Az(71uUZ2;<_FY<|N@|)Yhb!wi6%t~e7=yaE>61oL)*36#vt?z&l;2II&m@1! zWV@4mtSYAxO0NeEK=G9n**V5zOHUN?>T~H=*&8GKF=$Sh2lK}2I7I1lJmUl8Bmjl! zmSX@ZM1)1F$?k+imJtZq)~HssqX=oeF3$7Gq28>?U^VFUjoH<|_%qL4e;Y3zy!^YX z+IvMoYtrgdopX}-{JTZ#Jocz{>HVu#e{{JmVmH<#+Y%kyI<&ADc+RcK zw<1PcvYrNj5pMv)Ql!{N$in1-@aR+&F z6c8gpKQXPxD}J=_us0gf-A1k-UbC_gfJMEF4rF;@FE@tTxVoPuLV)QfK( z@`RkWU+B6DPhO%8VQ+F|Ck4c7RDZr4^F6?KJH<50myi$!KnqS)X*6&y`X~q2v2r5toqkRrsPN=ZqK%d0i@~HE6`0;!gM*f+<}gZ!dfwUI=rs zlk_~2lTiH!COde;lCak02!~6GE7qEQo2?cTmGc~63}>kivUaOpRQ?7jAHj|s{pBIu zIu9Rav4~%B7>!?X9uLrYEhIa+`Ve5ku2l6=_|d*c2!We0LFdzsKvj+LY3I|jT3XrN z84_*9>=V#IXP-2CIXwfqwiRwmW`Y94Cq-OQ%}<)VTrNYN{P79r(`*vA-pXF!X{Y?F zJo!oX3MWn;SsJ*h@&%lDQF_^FQ?H!)%y2Kw98s>PH+5<j+41QONu>6{%Di zLALMYLw#%dtzEMRP&>?ec?$({n4TF8kL?{Lhlb;>m)0VwqVO45{y_wJc~ zWReRLT9~}TM3ocD-SDv4CJ;G+drkNv3FUqn$ zbVXU}gk(f!1=uZ1Rs9E*kXe>7)-FdKpz1%a1ACI|3!+~ToX7GPIJ7M=Rd~qpO!SI} zXN1`*6vs4Z>~yYu0s>`gYFIE@8He~!`IAY%^ocXZqg!-6wHBkd(X*)RmsUO1WHa_y zzqMjyE#sPNtac-!mXRlTD zDy}ZP#_+VtpG=@o5gA7+a4_tTM*RHGr{f`wbo!_m*VXn^n{@WLtN81a0!buF>G0Va z)-ayFwx+%T{VCFR6fqBmdH=KoO}uK*Vm8mimDXU5$LMeG?2pmZUGYfRCcSK)cD10` z-+dXPvK;3}K4l*EinXA!aC(vJi&SDT9kL&kaW;f3mlC<~vWP?CR9a??k{#wuc~dDh zLK+cmmg;1NTIr{O2HW>kax0V1kv^HM=5~#@VFjBU4%gI3ZqImIbAvS7;xf2&(g2c6 z&fk@5oIk5K9PSw$6(?YVxs|9bEbv5#%gx}w73|=HDs^vM%gpn0+Q#?f26xkN1|9!m z!=Bl>UFWwtlWpN}YZ9JDs;tW-;CjTVujj608M;ba6ym>%lE zMCx6{;ZYG*CJbuwQjWlKYj!~w3Jd+o8HqTB*M>(up5o_3sfDA28k?MX z;NDbD$gx_ge4g16_*}GmL3X59{A5i{;Re4b{Jij>(N`7rcx0c<#97AqJ<9Nk;gnLr zZIhbuoMBluokU7tm5MQ`hk2uv<`6KG6R<&G81fK50)owS9mCC;5~hWH?Q4f9QM~`c zxk(B!(A;Bdoi&`tU{WdsUv*u;zqI4$&_a z9I(XED1r?Cv0d56E#+9BWp!T?`8{c0LWQ4UW1bjJq&-zfmv(CVLiR?`c!W@@TXw=| z5+9kqW6@gWq%B)uadFrE=z+Cmx-7*`Zr#E6~Ml-dHAw8i4!6m3qen%y3I};c-!NRUX-gDWXDwltd(UpRQ z&JkFPq3ZqLdh3JdKfI`?mYSBLS>)>~yE}J)&fWcAPhFBsEuiO7E;{({CIxeEXKaMa}H*n>ve>wdP3oicLm>3Xp%RdEqsY>gK!XP zzh4l3$er+8=G{2ZQ!7T#J&<#@KoVA#5RkcWazn^>ARyE5nEG!XI!idGNRTEdLu^28 zw)Y2@C-c!LqZ|_iapBx!^goM?XaM3x>zSf-C9R&1njRl`w~%H*E$se9{rqq?Cz4r;L3(?x1&Kp1SDH*fh z7p@3F^55oe8%;5YpE+avzZ~8V)%L`0f9~hktbSKITJ?P(UWjNAjh zY}3`tDVZ!jt85@+q(nAwlyksXaH*W$3$>f2G%9&MWd&UNX^y{Wx&pqe`DFe3Aaw^Q z+(=datN980Ksw!T=O?Pm8D6FS043uYvjdft?&5(*&RZ)mvl26R7nXCR2H=R|NmYHj2ffqRa@)A8&I(p3_#Bn2$a4&~<9UyD9nD^N z>m0#vyD#!Ie=`9J{*JSPsy_ z5>TZFzA|so6g)0z3m#;$)KfLolfRq;=rHGI!Q#KSIDhxsh1>@>`U?e2X9Fp#&m?)duz;cOTG zA7>5WF?0^vjZWV0o;Hy8?fnC>2psOIz3KgeG5F?x4;Uw6?<0(X#!&F3|F%G$COf>B znEZZ1S@Fl+@ZT58wi7SemmCVx0|<&p+Mv$NoqtEhYF*>Fo1S&aa3IM=5^k9;-nEd&FCxah{u zT~q-=V4y9SlsxY#50!E#BX!Z|Zd_D`WO2fs^jY|WZ_iMt?cSB>>-QeR6WL|&r65u~ z37)csP<-^8|u*=}|bI32K>AAa002 zrBzxU$Vux+ET@XKEOA|*SNa6VX)$hZzh;-3wmOwZw6?)!z(4v`PO@q0@M$L}yq;g8 znwtJ!nrgDWQExIFvWEH^qP_J#@pV?`{~_{wY5OS!8!dv7>w&1$TQvrq&Y-zfb%{Ns z^>thp>BUGGXHK*?TVrzgtdXQIi{yK7%+gzr!(MWB?~VLZxthuJ7*JF{VTon$6*%t3 z8@khTR<#%$hUS=NW30J3R7Om{+|IGMbnmKxrMOS?mA0pP7Adv%TgW~)WlO_nm8zS zQ_Y665^krs(6tMUc4Ko4xRyI5$Xac&upiRi84A3f;pKuX!+m*X0ZuB~qOr7RR;+oN z47@=00-S~Hj|wDeyf+*H&Qk*ItbjF{_q6(;ACNQP)UY~380|C}d43Ob1Uxb`kf?%3 zgwN}LsoRfQGs{swl4XbiI!gEnDg^ga?Idawq;cD+4wn;|Na!;f#t+nn3#Gi8G>EQ1 zO<kE-BO)byu=o)aof^`t!}qbJ!j)~@w9u#tJx!O&5~(rPY&eLSrP04QTz0x+ubHe!2$uFg4Umj*O+U}w|#KK1K<0{f84O+JNK_^S+(yxK_gH{A%jJK;R8Ddc7E+IZ0=WM z?suRXQ@N5DCeLgKNZFNVPHnjmP%PJ*qs}x+a%2ov0;O#p3K!;geD%%Smwx=VT^W;^ zN_hF}VnIuG*Vw=n8%CW56IJSSsmj0bXTkr$fBWpZ^rD^f zQTiHHz5G?4m}_v3w{v`!x1*-`Yiga@I=*|(EV@}8bvS$b$&xT<>e>W_?#9=K%8 zqt&C56bkB~ssV}=4g1qEpDXE1wMQ(zjupMx`UP8;4lliUenad0JItN( z%mZ!Srs3w+rE@ynHLao6gNvhc;^Q~2YKYF=I${YIF*_NZ@%k_d$_6_)+02C}wR78jIb9M-3j6KV2hUUib`tWmtW}y8{zsAkd4;?~GCg z>dW)_n)?2D?R<}LRDB}$C$Kp*|H*p?|#LfS}O-Kf*Y0K%%HraPyX*+7%7CnCA%$_K6)p+#+3v13+E zlb63nU17v-mk>yE*(Rf5%=1by^>=Z3aa$llxAip$dxgF1z!wP=w+YKtI2WH04o72jYgb+I zVDVsG7Y!JT3Wtl&sMmnwe*iT(soq%{QPqdIPvce9?M`?{TwdP&_yU&A%JaYg$jUE? z-7t&NKSqyP%@v{^v{14gln__BM82xT*;sKy>?Sl-U=*K`9^58&%M~EnW3<Wr2uZ&mT%s!&VO=(M>UbA_9$ z!|3MvJ!5@yy(u!B=(sL_%RB)sB6+QVo)!*Y(5w|T0&2h+#cDxkGMLc(GiptaB=SF9 zXJ~3k74DS@oRmWyeK!?rk*ic3^{0~!na#VdAE4>wmOgUdZQZ%fG%5+J`zC}+=ZhHK zl6Q#OyaA#Waa0-1a_u8!@l;3?ff2Awy6J?QiYfckCfyh4^~3#PN6JAb3KM~p&+TpX zZvXs^i;cymqcI0!;o2H1uaU3K`H-qISWeSAHk$mtO`Y20teKMEd<7 z)^(kSCc$7aN*|UuIs}yE2ij*cf1w>g1fxk{1yhwuXS6RWBnY?5gx0TYYiV;TQBc`Q z>~;0b)JEFqi~2)7!JEEw*GgM)ySnjZuC@Fz>8;NXA6clYuH;fuRK35*?#H(q>CEh9 z7QM-k`p}ENXJ6H!o{>H&egD4}h11n2CyUzqpeLwd$ob(}SwVeR&S1~ex|Vi5y>FQb zGoVm^C`t=bn*hov`i@(d7FON76RM-6TqTy4St%blA(t8~?>=zrbehK!SM0vDf7z$* zKTO+iYAv~NPHe*hG^W~1KrQ?QM8G^)O~+c)B;CmTpe2sV7Pa(}UXP1NE`CK@LomFU znmWbYEv%j!1ai8ZS{yc!UIr}~MW4z24b?xTWd14F{0j&(z|rK&Dd`GFyRm`>%?l2LdXuH+}w?|2%% zlgq0Dl3uPEd>FM*PpT+}ouf4PnGT-QK&BI_bP?RN>`eiKa;dJeh{sQ;JHX;_SfrfA z;jHFaK`gmiI1Mnx0FR1sR4!h9CAn-o&(WSL4I|=0fjahzV}@F>5=y!C3{OD)Q4brE>+}!8F_QDCXQ0{wRgWI1l@;)5?$qv7 zT?Wh>o<}MP4KF0C*Mrxiig8Y7rhWQfsu8DU_%w+6jP5-M-O3Q|Pi<9y+ReXVNQ-WPJx`1%q(MTM+C8m!`T}*=~0nTewREe@kn$d8@fAuAjZ9c(=1B*A}NZ?TLv2(?L#esZIycIvU zp>-GgG3khSt8oK*IZ64fTur z;`G@t))&tPng3%lwbGcx$`DvyVm_?|tT#i`TsH{U2@1&CXqQ;fSj$ zuzdJygF|ySEnAq?*$v)h8@DX$;FlV+=|2rEYZ~fp4-ZgrT&`Z>3|KLR>#JGUL2D@3&+~q#c_MuO?B9XFDC%IIC}j-d%<_Vpp!Pr2l>qG*(eHE zWue=oje-s}@Tg2T{nNh+t5YqGTr7;4crM|N%6rRt4RwjrzuI0n7O$u6S{#!~6WHflq=BD!Mkp6HEgx}|4K0>NF6^dfu-xf#k^VxSq%6&B zf5z`B6beO7l~GCfL`m2a&y1GtaMK+&k~5*9a`cxLS;x@;YP%UaQ|K23aiXFH4I~uO za)drvhZAaPMD3F36HS#mMrw=s+L4Yh9pzk(bO$7vCPz9)=L-4ZYvaWYL_deFI{NsR zmdE2Ozx?DQ!&mI4ULCS@q2o;3o;kckZHz*n2DK%V@~o*pN~K5e>1~$@an60Pt0{#Gyuo%nI}? z{e{n4!mgyRaLm`1Ncw~qWD+F2=wn1kJeHs!Ndk?3Zh?WsfpOZ@CRfvSDeKLrtw{2i@2=Hq8XIp(Z3ePMNM50Un2DwE8uhW1!&p6<6i-kt?5k!OJa&1kB6h2F1(S`=_ow`aM0ojDVkVm z7J$X8I0I*0Gp=NFhFBpiV|Qc96*J(}8IaZ_w+mvZUS578@p@(bX{bqn((b0Cu2kU{ z9-qg>K#oO76T}JjnsNGD{DQs3M+Ye}?x~N3nq6Xs*1+GHE`A>O%WJ`~y{*!2+y%j0 zO&9aXqB&zp5YAOcQkukbox3+2?Lon4w=h}MdAv2*o@lriUxdlVTm(OOJw=@`>8??J z5Hq+%{X$oH=>9s~PnR{~sz|sOw^R(wc8K_jfyg(S;4zJQniO`FO#lzpeV>ke7(;xe zrmiQdo~H+pzJ>qTn|etDDbaIToMW;Mb*`*5-|!cX9Ng5~yXoMFcCLD)r(S=P->1LR zf0+C_UBb7A-P#WIno?C*P)#4}dI+7P39GCM2}(^L#{0`U@r;wvz0$w=rqM#TVfKb= z=LnateeB--jcZ$0T$!8vusS#Kv61{nHeWW&!d9AxcE|ku#-7}jD_WXX?i>5~mx~{n zbM1!N25B=*A9v6jZp@DqR;$w&kJ4>)Gs!v-7W0^W2pp3-XK%uCV0s$xI1O2Pn5!Lr zb3I|TTjon^ZTl#$+Zvsh_#>A=8E~sk%h`{ z{6Ch5oMg|EB=)S5SfhwH#Wg~%d&5vBGqj<*IH45F#g6%R{$9OD=Ya*0^@C7#S9y~v zT=C!Sp^T?K%3mm67;W%mLiT83+?sLMN2T$Ic#NHp9>#C>aK_ybEv&TKqx|C$!WZg6 zHVTNhaU*1%OcCFc$r02o$d-1seVQ!y>6zmXg10Ids-922gEBsuO@&l#*kC9Tl*+=# z+Pd_kBxOuOdgusO@ljK%xhKqD(tF{&lqx%==3Us!UlQ(VPMY~>RpEM7k$BWGe-_%i~fxh=9gcBNPPmZYjqVIE4=vce2<4 z)-#0D=b`ICYLTs4$_mJ`xoYWw1WEF35%M!UI+!11)$Y~9}pLmX4%jV4ISu_UZ zqEg7kbd$<7&}jU^3OTsqiQ*G-aK#E7FhH%qPk5wLlRM>4#V3^8Vd3lncD@Ykyp8); zZi=^JGLVo-2c#}}gBl92p-;uXCVSeLJBJ*`@V&|ZWS?EROCEJ^S6bwOI%33+wAC}(jE1=l?CwbGjCZH1U9F+m@X%1Sp~Y|3X^mP#PkrsOx{CV+ zlg3nK(FgZ03ydxr2{g|S+@Eb~&VJ0m*UZcEU#ray^|s75#pf?b&QBO^W@AHcX;Wh@ z|196M(0|G+C6}susGkB90=e&>`icI3)XUG>HZX5?k2BfJdilk>nj2bsl8&C-yjg7) z)&Q`(q4lCxysciVGZ?zEts7hG#lz1`WC zO)d33(jPo?h6bIB&=?|U>NzlvzbUb~yL(IG1|EIc2X}Q0Sz>{;T`yE=wQU!*HZ1Cj zSciJ|4W05TabKmXMX#$g8e~llsnMVaJceulOH&;>6A$AXY1NDk$1^FW8BUiMLVT)A z%Q!iq31e!^Q0?UHyq?v#A>ph(F18S4rq z7wsBr+`RSDj!XY`{o-8sLK|*R>7)gxi?zY`o^k!_xWv^Yz^q-+Qq&7IMbtgfg?2)gAr)!sdv;*&8!~K&H`t z=$khT4BYTd;`z*rg7vE9I^ac7Y^^1X>+Wg;@oZNXM>aX6VhrNIJ`^Pc)uU)dvc=8f z5ttw!FbY;i%}Sq1+4)6Qg00UVS-~77Q<%!&rFV`kz2njWO`bL5$TQvqE``qWOpybw z%C=Iiws4K=ArWuyTr)SL;dwG{EV=EOOHA;Jd`GrETzaSUMb;(+Oby-c<-ZJdnJ0~o zAe%D?fM=3Da>h=aGua`_E*iPB_B5B+lW8v^q2fjYY>Ney2O>I)SUL zsi~#^S3o>L5+~~no{<%A#=v3L0#Am-=z+olX4lyDaRu;I7+Dn)GhczlUq zcS>B{qV4&6mQ>cd#i^$?w~gmY#V;Nf78}tc6b($ML}wxCRa%N*96Z@jlmi4rhuqIl_nX4+ z*+awwX8c#U*~x$^(z&$gWHT<@Z1z~K9{O?mlitBb>{IcB+ z20Mj(={b@v3Y^Md6mmEkft<*G4O-s$?wau+f&7@M(G%HOjvsFziGp#fQ zX81kNIrq*Ch%N8i`+0x=-{Sk^TW!nTVs{tY$Qo~_A~XQK+CY!}K_0NpJSy4Z!jO1*!tO$0uqwoocrKjv zfl1g?EHn3=xAW}n1q*MV-8nBkecsMkq4|&+dD&EJbQA zDQKQj#roj!nrSIw)52SDPhYs_?CpgM@t$<3>)fpctF1p&iB}htq?A3m?`-vI)~Bj@ z3LH&av+pcgG&sg6v&udIE)Q#%f}xXi6r8DIC1|OUu}bQt;gy*L*eDV%ghuP&L(0h{ ztdwIfSd*0+6<5Qm;a`(r5BIkx_c;<9+snH#9_Vxp4PsWrD7Q%(%1;=r!u`-CZ58& zSZ^(5{g$%Jht{)9eSIraKh#>e)OrQi6UP|%_udl&Jd-ABu1SBfZhy#=A|N~=kUJ`|<3Tts2^a9RoId$RK>kLpT*T~7;0 zEDmBV+-Q^oH83Cu97PEmfQK+WM1!M`0|gH1t8!fBu&)+b*eLbkb%_Z&2lx{h8WQA8 zpuc~AeIp8Q)a}-&hXfFTT}RfYna~I!0<|MP$dpz)vTGBAUD{$+>m}%ph-e}|fzW_A zQ|}$TpA~2&UIW-Syb?@3q(YmktC2jAGh>Y=!4zSF=TUot-2(zXV7>h=*lr5z1R4RK zgyJ%U2Jl+E;CcW{ij4sgzdcwBC`$zD2~)vNS-m|xypi&vCjSSNAN^7$pty}dZ(?8_ z_vNwf#NVM06$y?*5#l6~jskZ9I}miZ3Pcc{HU`9SH%_8e2C*Pz;C6q4eSyeR69piS;c(jbKg4$Efhh z+3b&&Mm!5}@kZLYzr%LbtGT-+k}5{=`3ndTxOqgMQ!* za=DR8=46@&vo`k_v>$9glC}=;wnu0&{F6KIU2<7!6SJC;teTY~()$$n+zgEtAjEe} ze|EJL?oYy^X8*6af-x(p)_q&Se1Y?Zv&Jf)w1>QXubi}EUU*$n&8EEy- z^F9%WX^yL)BYjEffWmOxoAiF=`yn{)dwrYUwrN5{0Qqa^MLUecR_X}k(QNybwUs)& zjtmx6swn(`SFi}@weJf`HCdt(0#6Z4D?~pPO>jVeI3huWvTcuA;Y9my>oH-(g>Q)D zF|mxq3}Z?-Jik+XoA^1-M6J$kjn-m@*qosvn;Dw7V{>Mx5u^sbM%x-kXENCRfwglG z4@Yd&aI@WR&6?n-qZl}TUt}T$N5am?J%9p_f_T&l$YT2t%t?UcbD$W8#c;rrJbQ9s zab3khoF_0O2@#C7V54;tfxA!zEQ;uAiAh?rcdZJ3LZdBz#A>NCot}j&!?i&oI4MD) zS_c=&a8J?!3lChfHa)h~+I;E2!U1^1N*{ZSmEuv;c5aV6s7rGH;a$59@1NWy$QsUO zt40nOwr*jWIC8*96`QS}ws2j`lTI?3lXaCRIB&%#ux!!br}%_*6@If*;g{xexZb)) zH*L{6?zAPlO||T2);+Y^X=0|sme5xI8a}0}VpL+{sEVp7;d8H*sK?G;&YDZE&COXp zdu;Ow)BN7O=bL)XPfMHMi~C%E_|BvvLBcfLS`3q%Hj{anj=jla}xF(k5p{aTB64leN)&Az3^hxinHp20MAxGBopJIXX-im4^MOVG%8gErRHC(y$XKQhQC z*vnZg+)cGX@X*6L2%)xN-c}+ao<1iwG{L5L5lawYVSLzd2t_7X3OcnV;X0i1#32W6 zI45A103}fSD=qU@O0_;9x|dnb=W%*#nbOCTJz?3X42MN3Gb@|dPk2K$J{bgDZiatg z?*!}EiR@WVZ%@l&x!Oe;EN?l?{rs@CiwXD#_n_6+3;6I#%lR>!j$Ntr_X?7~&P`CN z{dLMaW~Ib$0{cvSYM{Smv44g>xxi=V#KZDnFMm&NhTH}42-hqg>|yp}2mL>c_#pE- z_LXZ%006a#vD5@WAW-dX%;>L&=>tM5=Ey|O#M)Q^#i3IPh#0{eae$i|C7|UC#&MPi z|AJ70-zjKy>=wa60y&i0(C8<17A%oXb5I^b0|Aa8YnIr^Io+`rOLEi=(7z-Ad8EX;ichmN3sKwv z;;;#m9AmNdiAaJC=OrJO2q5qY=ae#n-Two`K4EIKPc8bEb!4$j62vQzJ%~7?SE4A< zdKV;$R`3nreg%t|70lDjbMVHn!g(DKKrleR$v@(_<2=zC=mw-6P5$U_!+#=xF!IKK zI7X2hR7&DvBk{T9PMHc!h|n86SXAOrRymY;rG@A$j@NJxM-S|u71OgaI<;FDP3YJ} zV|m*2)w7u z5lW);ur6Ue!V?w`9{fy3_KLI~QzJ97(t2bb`^|cJ!-Rul`!9R2dYHk(J$UTwHDi*> z6Wv2xI(a%vED6c6rk9RG8uuoqfICbx}GOC-3aynRDk> zj15vrw)3*bDE|8n|80{wKIKmkO^G2AayD@3$xJ@*pjT)4@Uy!@R z5HEKHw4m=FX;OZgRIR0$dcHBtPGWNP3-htQ>Ju50s5K1h8EU!A4z#*ED_vca2Mp;K z@1*c|QFtjm#BvX?yb!xTApxLf-Smq~>=6>@%DVZxX;OXvOyAP<4hG!~1bNnk>Xy}C zn`r2wVh6(Z^KPL%hiR>^!egDqVh^R4!o^=9kIxt~Fu9YT2ZI(LSW3Vb5}>aUmM93k z2)h8&Ag%*}0#IAE0I$Q=t-eL4!j1YB6$lIXfo1g67U-;Wm@*3Kp<$$*kMTOB@DLH+K9(3X6{`Z z7MwMs&>}(w4pKtkMa@LP$75QFq|uB>a!WEtY($JRjyG9hDq&~8AOS}XC!<*hTTT3 z7|6}rRwtoBeAFEWd>ulyfJL(;A_|r|wgd+U1pvqBs&*Fzn8K{YdDh0abow#!e3tR1 z4T|)B%(@M0Y`lMUXhR_*$pHS;L~Kg!IuTe~dtMKjWT;^M7srKd7vuoV$N6XvZ(#R% z295kX-OIKMY_Rr$0b;uek`cA7_DN;`Mkg^GEk?!L&U#!p zC_m%(mx-%~S{etfsGk+3K|e{ zI)CtnK@7jbLEp#Fe8CO?xG8iM!xDiw2vpu3#sa&0z@h^esn&k+z@hiypMCf8Tv~2ognTl>x0nIN|K2M2>gQa3cT3wEXl;B}8IqX> zSr5F{0?Z&8(Z zxYb1na1xV%Bz12cQh{bOu3B+!Sysy=!lei^a@3$3qX$i@nwgYPTUC-1qe;%}0xEJ= zH!aJg_n>=83iVzM?cqB`paG`NBU^|OS(MyvfmuYn${X2h*yNtwqOy`T+HP4<#53d& z36@VXYPF+=$cH+%Cc>*C55^S*OAuEePa%l3&$BPv9QdQF*jd61m)O>tNRTBdV1o#G zVFRXT`)2epf>$y?6A@%g>#R_8PBR8YXac}LY3!5Xn>~F%7}xmMu49?DtDCMaY8$j^ zlr1Dgnlq;_E=yNvV?o$b`YMf?c@YtLnMNo=_Mlj;BE4*J`k!6_o*v41%;3u?g6N}% zL3VJk;P(Qegz{MmXkIjmEEvRZzKhD;u>FvwZd4Hb*=el95ht`=PYcy)@4pL?LYpF!ti2$i|YoxNP# zg5w~t2BxzK(mW^XbS(`1c9UGnkcGze)V73$_B4jby+WiKB5nZHLB`BG?(rMt4|McX zSH5tDWw2JxtVuD3I%S~3H6Of3;o;ytn$OC;^+CFL@L6X#g&I>d5TyxA$($6X)9Dh; z{I}T5PSSSk?5pbBEoAiG7I>=NtHe^Ro2QTM=_MS4UmdujS&H6zS8FsF0#>r`vD^^TnB>52YHgS&*qD6$T*dlcLxv6R z6>ilCnx*7nA!?hhC@)TJ3<&GfH#P0uLwzW--CE;O~n_=TDW6|s+AS$`(&JyIP?0mMFoEGb?=W)6U(j#!gL4eN( zJzFOc<^|eI5R_!MhO6}9dX6GRkTwz&GFh9lo?zZaB(jexhHWoeg3&c0J{B}H8XuZO zzNqXE8YLHH4Re!eOjB2~01B?XMmz*d(&%1XXHuxKEcGQ|IxYt~P;Qts&y6sZ;? zE|-NkKes-^#|`b_q;TuEWKj1(byt4qKmav?aQ_&ahhu5oM#XE8^1VUCuBzm8{;t|k zZKu3lc@@9RTZ&A0;ed<<`@XTZJUXcRX4dQ2bjoXcR!o_kB=>NV25Ez&nNMVj6y7Rj zCz&R}bw~==jgumls*NE3p-qiwr(OvC4MwX(CJS;v@pd_6%rbqr&Vf6lxX8~D^g7ts zSUYAdmQh^_Vj+S--=rZCGP;p7cw%HkngV&fE^83$iGmbOuEAaL)VNtj<0RrbE5cQl z2L=8llVCUU@gcmrbf=v@!sgjD6>7(`NAkUf#* ziI?DGnu$~|gtyS13&JsJ*KKg7El<~hEpuqc9zvbNVso%>Dp-IB2?{tS*8>KG9(KWv zWV2M-jjonvJ5Eb#)AOvb9F;3A>u@G5B?7X%O3OM|oH7|Gt9%&D~}UQBi9p=-hwfb(_tQ zK#9=F6&%>Iz&`R6#o<8Kk-z!29K0>m1@G(l(r|`qN*YyptK;vqWFM$Px&}?AVTdH5Z4}VDJKlO zd0Fh^BT(+d*{jnh*evhJ&BF)!hjzxonoQquiR{G0alV;rkagc>%w{Q=0oHU*)T#YL z{jE>?hXkl~iJjMA52DY#m12^Ck0Le@)lM@~=7jvdojdooL8r&%OYIGoD7-)u;6Ef0qpEm=*!$;vfM2WA&J@FR}6V)Kd11>FwuQ z7#u?y{SS^9Te-#7EB0&u&i!I@+h<$N_qK}t2CF8ZU$_~6w3b1ZJ`fsV0)k8v3|82Y6c0{$03=kV zN(mAR5OqNq1MLp78rbI~lx;$rjo!5JnI)hwNs)Dp&`JuHqQL-G#!AwFp(~G*SSj}a z9xEkm7|&Yfa5kCUVm9IJU=4=~Y3VMbXY~ZXB_>EKvt5Xj5LJ#w-;mRknV1=`R`>%x zT`*Ka%taz_$SF(%Se!O9yO=kblgxSM7uK0ovyp6Hb^=?@!p_qQBKRi}eHYV0hIp%p zA-b(UUErHk>`o&2I)DPJcv~*T7^eO z79=n@Pl%_jH(G5a)>fkD;MM7qZMZ+a+e{sw)?3cs=LhS$|M;0!vXU7c5MNLmjL+LU`^a89hL^I&0 z1ne+mRZ|I>T8_gB+=Ji{5onJVY9N`Q5n_qnuP5*kFxrrF-uEiP;CFLi*{A~&WHL$p ziaA$-$T#AIA(I$pEcwPntR&5(C~wrdsx-9c21Z2&qBItrGqtC?5A+3K58)roaNoOY zgs!2cd^}KDjc{Q06^? zlorAu34TsKZ%f-~9J}43l_BN{cz0!o#tUg9Br3y|iJz895Q+oF4D3ht4C$~lgqvF4 zaq&U^X=_mwpoH#e(;qa+N5bRtglu?3-y%^w_;kdHDlLtabDwEmhv&qTtkI+;01^an z2>&amv`{E6oNd>3Knpf@&~&U-_=Qt)O8P<&FaSL)GV>WXLg8)Dx(M@}=t(x8c3aNJ znrMFz4M@;kSVGz;jTKKiLz}=Tm7ow|x0ea?2OfuZDQ9x)-U#g+~r7%IO8KdpQroJ_y zO_wmtAO%8(c^QhgQ_O-lAnpDS>=YPi5yiH%{t#bqEK%A7Yx_TAv1nytb4rt&+E{$h z(fe2v=KU1A`uV7US^Y!C3+uV=fbwf+>LIoaOggFv^;dWXhA}t~dCMV{u!WT_AlV5} z!2x9fD+yI#gtqW@3qe34w3s8zykjv~^pg@~Y7(Ii4=u$D4u?tt+>o>`XJ<(qI|35fyoI zb`Ae#gKX9RF!(=xNT7SE!PVSU8R-Vj`mE!zNfDJ(ScMgg_d4NW=T0K7DM#bTfc zq}&6B)+o})I+>6PmkGBZT>$MJ59zXGC6R+n2G?fefl{X0d8cB>x?Dz62gcc%|7KtMaARBk(*kfN!1%csMi6IR$nXuxu~`QLQIb z`&UK?R3yRNLh~cr28L)zj<7bCY~=|8hn5US0Ki=6LQ z>|+~6gjsKZTsD)TUwKUdWFmB(5D=1s-!Ku=^~2;8C-4{Au@-nEI$wz!2As=a-zBmb zpblwvJ#?+8gG{rNhm91xE-8~1XUC0;PMi?IQ4|slm~IPaX@L;4`~cC}RXOgb|5}D+~v+?A(;H3sdwxd}Cr%sro4w%BP%dx!gI_zk9&QU3*{7L&F@} z3P$gK>BSM6UcsTAEzQ(em!A+n4k%Rr@kYVS&d*=R`PmazJy7E~LKp>n9CRyU8^QgG z^AOzo;k>{POIHSV5OS0NOB_9aB;z^KY|3|n5dTWD;h-TE@FxHU#97b6R=o(Nv@Hbc zd2K}dRiHl?@98O$$m7R1Y}l0#q35>DScUlk29r)1fg9=)IUUs=4~;=P1r^=Q6qyAB7Dt2pJ({%Brcw=GV;Wn!6bm+S?*W1 zdhhs%>L)5S>z z2{u4Ak&!TEofL@1J;*W}7gRgH6nnWxSy(l98Ci7mmT{hmK~@b}bBlZ;BXr`j`+BX~!1{=g zCRu1*2>z-&IDdvm(WEoHgNjPbiH3E+kJirf^~Y&u_V5j8ZV}Qe*r_6MS#{(YLoWPZ z@5%c({2Y5SE$epd%MRRs*pnb>0#`|7W-u=^zarm7 zkUolipu;H&wZzXSq77)Lpu3lp!8HSx{O-vEqaZ`5k6aSq zZE047D7s|#jFfj{S%2RoO($Ph%p@v@R2gEHbMsfb1xn>k(trUG{{4oGi*pT7xmm5a zosm-O5uEJTH7C?7*wY8<%#2dN7HrNgP8=YS`G)kKP}j%L;A^?teUPiOk0>xIhdxLd z9Oc_DAU0Hqj}dX4Yk;3ZE%OQT@rm|}nriZk3vvtaafb%_`*0FPh-g`Sf+8d&M4?X` z73b^4#`Ufk5-pR?a~qKyH8xsrijQ!YE8P-Iy_RQ0j)+R^Y0NI*j_8Jld21ze#FD`H zj412-5*JBia3G9dSggM?EG$e3S)V-(Q9(YP+_fH=EBYD=MrHU&6)OMcqx#2cNGFoR#DQ2}$BDz|zXm43&m1JK$a*16!S)ZhOE5+x!sIdQdJT*5 zH>&zhpOC8$jSE%{?-APFFD%;EN$nMo6wtrEAXz$CG5(R43PSsZSjvW0Dk3Ff?SKWP z8LoplF0AL^sK9Q4FlW4-Bwn4w9vXj%i(KI=b8(YLPB*2j&I|6V3rLJqi#j{?j^S3S z6S$WX^Rs$*ck)*zKlH?goQ%~26Vn4#p*bV-4HNc{V_CzNqml;3rkWU%;v>O8<2?o)oQAi|+ zR=Co!bnnwU$a+tr!jR*)ZHvRhn;afa8$zu~fAVn3Pl!D!9^{Lmkn3B(905j$D$G4V zizdb+eF)*g-U_LJHYQ*s9YGsW11%w)po4%c$fHN@!wmb|;EEiin@VHMZ z>TN707~mkmfQ91T_13)m@{oJEvG_)d3o>;g0mS%=Zb0oQX#-dZxvy2`8N|il1%PaV ztOH-5#LI4LR{tG_%!nYO$4n5}Du@Hb7#Fly2ZKEdmLvf%|{r`n1_$^x+~c ziH_8>A}xCUJX|kcboBrQ!=sh)aCOB`Na-nN8|4}=FUZ!?C=T=Y(#jWFxrJn>D~@@e zT_=NCAe%lNvc^C{8nX;?acG)WxH^f=N*^C(v)Rd29qyxKM_O@fE74WUO_=X2P!ocE zLmOgh$8n1t*$~^#K8{#AaFqVgehBI>OnLf=7u(@56q*LAPK>|Q7FYowA{+%&cgRY? z^}$QPU7)of14B-A(rd9STC|A@2vB@2HM}BNgCzzax-EzVLP)hC?%3^tXd;v|T%%O0 zQY+6`%t31FRy34g9E+08gX}A;ku#rB`uT8;u-Ad!GKVc%)cln7mRfoJJGK5NrL0lr zY%%%x;my_$^cevBQtDv$Bvj4VG5&#gvs$Hw5Nw=BJcD>D8AZ0|Z3H5E>)J z%xyFdc?e`Dn90n6453fLZ)AZH?cHV?X}?KU+ukSddmp+$e-qp?9o3ByH?%(CsrK>{>gfQSq-?nNLBH9c=8CrrF5>0kO9_e91=aAJWT0=^qlR+trFs!rfAUt`IFxOMDZf!A^s7 zyPZ~tra#pEsLrRLdL5Br+i7<9XnKFu&=1CKZ({bMAY>mA;t;JioIKUUf=nUQ7Kq$N zN{oCETm`g4=I^ zgvygZSCA0EOW4joh)7!@^%4aP-ZcxbEoofSRaZBizqg0vP{17+6R=k?_$e~oB}Y;- zP{)H-k#GaWjfoy{3Uv3^xwJ6Kr-k=I@<72}0_}s=>WSB-8bWnxTzgxPwh+Rk4<|@& z9dEhgBbhkFfH*l=6-i< zD+VcTId*vt1S~&+ ze|-6kd5^I&@{C3MFIk%(dlV9+ys>Ctax#Fm$CzrKJ@y@hQ61fa`*c5h^zxkE<)xF8 zJ=|S-Waka-<3b|e7iSHovQT07`EmLa|ojCqZN=&u~eJ~S0_z}PuE zFj~RsAyUKv_XYhO7s}r_&l^yo9tsr_dB-J4%>r|^;YiJh@8_Kl|JF&mg&$2{i)(_W zh?T&GWD=%{@KupnST8N4BxYL7B6ADzt=Ka+P?Q=6I0eYuK<=~MMhXxbZiXt`_FX2~ zYxHmQj%`&r^dz2Z-&=pRr#k4o4o?03J|dP!M(8&?g0F3#2($zIY!szUck*Z(-Whs7 zjJD0CZPKQR1%+%Ee%v(1b+6*_r9 z=EbO01@Sgg>4QQ7@b&F+G6Vx5BLV>q)?G5G9g~XK`;Ev87Bf&C;IB0P1^2DQ#RpSf z*#9g6C;@U?$4tB!B>+LWs3`N^5{R`0TVD*vVz>(l!Nt@Qs5_!bAMd4+gXnA}{(Hf& z2(=ivU2>P0+?u6ZMti3AAD9B}p%=HZ!<1@YU4U07DaM29283A#d&j4w1`RNePZD2n zb_&bTPuc&-U_j5ln9u>b z(OZ_*=Eg`nc?IY}kFvh76|%tTdkpBG>WPt~E^&9Xq`rTclQU%YNeIk}#Dqf{dzMy2_l9QTlWe9 zr8H1g>e9>5#GrPB92a&cgA&`=$_Sf&TZuCnUHGkJ=Q5JXli~-7hJ`!?QG;MZAop4r zOvDM>FXN`+5cI~<`?17C0V1GnP^)DRmoh1=k;byphnq;X&5e!3!%V<%_EICsAWBkw zLOV^+*tJAzFj<*KA~8TN(Z*0kh#EFf2j{=7gc1UeFEK(k2h4AV+7Y-7ji^btY#5I zVXyQO&dhbNlZvH@GX;VB)ZHE2s6iH@5h3@c`4WQWAK9hPN;G$ zpi>;P3qB4OS%F_UKpQ%;p!_#73sOCvhnk}quK;(61W)t z@}FIEpR6XJeNfq%q&K00Ba9`lgz?6jf8(P2>ZJdP)+utJbI79GYOE+KwAD(fE+#SK z8`8|RLN8;#JO0|({?=wfkg2KbA67~os5q4@|7N_Sl9G3z>m9`Z#n`je{kiCARvs#@X)Wj}c{ROdMs?Xz^t2zx~?Z^vF+8-kD6^Pg_1RUx5hG>!u(JI5}qM3tW@)F6x zRFvC)z>vZRnuWw{s67zdAdOD8p?-p3W^F3c2wQ%q=8j-q+*r$>Sz zuzm2d4Y~d|7c(F_9ak|yzas{o*p{gTY9f|KM82c{#9<8}!*5TS+w#Xh;Q(!HRewy= zsk8O3(09ixrHul6Bi|vhb{iwm2+9pDf8iSF2!`LKKr7f<3DkqPyrC3C-C~$Wb;PRU zHPep|_R@JNbzVb`Os_fluPu5`&@U)4}fN z*lm;%RSHE3#I)k#8q6v1-N zEQU=k%a5!p>hul{%R8Nl)*UIw(zwmD($i;c9tY*-mqagHgT{!x-LV^CCr@r1ncJic zA2`qP(-Zvaw9LUYOy|oYhx1~GvIH{0XY$ub84s{%(|cuLhcDsFau_ceUv`1)b{qM! zD|?uEk1xBC^PpIYLGfogaZ7=Ru>i*57!Ph8DND)u=SW#b&c8~^;A4hsj*_wyyg?Cn zjxV!Jn5a8n<`_58IKC`$I4^c6OBi3#H2xYXqY*9O%QB`)w1qFrnMm#uUv^=Ji80EG z6+1DmqKCy(__CYBc_kAe`-Pl$Che#1<$`UNtI7FJ4(HvlERQ7TJ)jJ>c2dUJCr^1g zDWk1R=pSz=d&ozS^F9ve{h;h7pG3~%K4u>B8d45)xF*=497e9E?~56S-Q+9C_0gof z5zBIi_RAff)yd&}C;nY_Xu2A@8a0DT3#%%sD<;)w`cza^R#X+#lvb3-XnIeZrWsmV zGP$N&Gqkw6xN1gmQA}=eNky@y-?ZA|@*2&6!iw_hp~WS&(+a9a6jxQlT^eJI!9Xse z*N{u-O{LYE0!>X-K~Zs8LDf`E#UxG5IZ(m6g`i zKy?%ANPDN178aLdUGPJ-rlLwHOu{F0x%aIqt160W3v0S)umj;Fn#?NOLh<#qh!I3O?_&_9z)Cze7F$1vI88LDE+V27b;P@2fpv991Z zodQuc-@||VZ@GY!E`T#d@L9zaiPnjBiw=qo!T)`t{h}9`9gGHOqXGUKp)`mo1>Z>( z++PjH&lDh0YY8GfrKN5$}64EM}{&my=o z7rsm2u44GLAKXz3rE*gD0C*bKRt81xL9Uo?io>UEsKgT!-}+Kzr~E zoi!S0VHNyF$BT{@-b0TINFP_iG0w$Os11L^*1?Q~PkcVM16y54?h@KX@4)$jvkR|u zJcDCH-vL|l=VOI!tbpg%!c%HU>+v}_1L<>VE%==FZ-8&6fic5=DumCD`)nrk2DTNi zE`+zKg=f*ZeQ)36I8GyP9|6~QhP!ayO(fTMTsv(;`vlHh% zzLl_MJ9-29P93!?1?myz0KN%*7aS*C12}59QcL-FErK&M$(vWed7J_FpL@6_?XwEU zNtgu{&7{Zb88x0;aFSy5vFvioQ#KK?ZOJ7tAoxToC6c# zKb^xdR|Z_ExhlST?&`_!XI=}xqP&uQrR2)|D;KZaxq9&GvG0d}Kj!<%S7X0l=Rk{* z8kgFRzkwr1I$aF(%TA36J#Obf%g4`tXkkK6_^?j;Y`5hAaekKHSuo_3=1-( zY0w2YI6d*N!Pu@svBqoJQbsfJ`{p6;Q>58mt+QRX`|ICpRlHI7{3(CWC95}VY;c)k zXb}HmXb_#>DB@U-Q^tXj@Abp;oLl!kIqf}s2hr{wDjF`9D!Jjg zMx{Z42XdwJ$b#z0ATQKZlp8$^Zg^6rlnpH|Dyt|jG6op}@Qkz4+b(Z8h>XSvgC2hu zDT5tFS+!J*Lz!YF@esQIt7k8i)| zKJ7O&upy2e1p_K-VBHxw@60u@EOSut;H}rvzGK{mW8!W@@hANT4T<vSzRUb?sGd{^A+8KPt_Cbtb@o*9U#Jq;u%!*AIPDS=H3Q8)D48n$n#w4_cu9VPossSmKs$eY%~>+_Yr{ZO*~g-QK65iF5s8`$a4BV`69 zjCG(++{xf9mBXZ$NMs_BArMcxiG9Rg-<%w=ZBq8t%J^q;x5z(RGIccjzKDDd9%FKO=o8^6R zLBN8-qS__fmTXCH+Hn1;*Sb$$<9GV>bidz>bB29$;)%W^w)uYdMa<#IuT_g(Vn2R) z@ywrUySg23F3(81CL0=LdgOtSq&K>zJpc6Y&!)cOny~Sk^|z}0e|zZmvCU2C*XQ|6 zG_ZySX(dp;QIzUA7jzn$g~W~#wd*WH{lotiYSPtUz}YBLcD7I6p%oRNMZ?&ZPAV+~ z{ZiArwq|lgRcTF~jqU;UOEM&OHN+c?Nl5n+=wSjL{?F<1pHaZ?-HHa+mArEJqQ!~H zdiseMejhRUhxAWBI5H|{$JQBh%lhs=lf2^PEkSqh7B^f{onB^1UoXE}yz*3+;R_DU zlV6O9`d9BL-`6&0mJi69>Mi^7qtkCM4x0YN`>)K;e0it*(~}RMo2q)E`^r=LUe~^} z#;+N9HZXs{?LCn(XBQqA-RsWsm!lr6c`s^jO23OY`VDwXHEG!KfP;aDhff??b*E&X zP80uS{)2bzmgt>F&zD_mb^FQenULkV?aiTMZf!gN z^&>7BGtVvTf7WB)(W}p0e`HFuWbA~aFGr499~v;e??ts&P({)Szc}-h#e<%mGO2LZ z%Cm+KRs{J!3CGQ zyBMokWN^2weThK?B%lq2J9~S?iDg$ekDWOAqtqvskNM=(I^V=E|o2eSXP(q0!w?I^TL)rzxu_IP;b4>}OR>m(oQu zQipuLEjiQk%E&5IbaYw^G#Th>iG_FC{jwOi?q zPcFRmz()frpL_Mi16BPCRZZK@ZQi!%+P^%~pDKKHPw8UmyZsi;_U(1`NTBxYoV#3T z^pV~t4h3c%^Z9xA`uQy(nFE%Tdtcl(f5w=Pa_b&nxZ&*S&r_=te(mwnusuWjPicHk zx$5*%#rd@*w7-tI@sp9{RS z@O=NciOaSee>`sd^dQsDHO0q6uBK^qO9G-Ne3rDh_u@z&_fHCXJTZB~(4WusU%Sz4 zy6#;+v*-G+bA8o=1G}wyDri!iGCWx|>!HL`+3$?sb1Suf?yFy4{@kS?HS&`cT}~zC z^)#g!w*-60-yX64P+0yDZo`zi)2cJ)-+E%HY>sYv`t#B$-+g@atI&0;YmXQkyh{uX z-i09i8(`Z0kHzTwiuw*J$;SGZ3_x~x;onJQ?Bt-3h(VyYD=aQXH;_8)$KHlm`;B5_ zh*)##;*n>=-Z-2F+v&wFfL?pMe?t*kN~=H^TS- z)_iGa?0afI>A{e5qgQ7s_qtx1_p#LN@wdXCR_;8o`?F=+8$Ss=v*!!-&1*4xCcnPL z{nuxEGErHNcr|XGH+%QG!2y-($$dgTc~RZCd|5AXx5LgGeynbObxMz<4Oh|+N{qRS zb}LJNnml`U&PMP2-%|Ii?XtbN{Fm-;o($oZ1oi&Bv+K?w6_KAP({BE}tP{{4kr|7OR`-L*fyBF|Lo43>mHj>`;l1_x{(_{-+wIow$;CqB&{IRq^=4{=fbF`J;Y{A3W75 zEc4FZ@{2iVj3aN1efsOm!S5zizWCEA`INXp-&8d0m+jfL@6+-vlg4~6h_(LEQz{1` zw)jeP!oy{+ z3TvZn8~-U;^geaB*S0$_ZOGd#VWNU>kDHpZO{rdc%A+<<_rg!3^L-yQ_RVkjdH#Dl zHP_R;pS*HuRo@vS@?u`w`OKCd%<7rDx?TucKPIn!)bQ}P&L4QEa^<(ofa50d55B&Br0Lq`UY#`V%#v5OzGV&H`QE_b0k>aB zzc$OW&*=BRC>tctdR%rk;K+hfpKFi4pUN!# zJ`(92!?o+I{~J|sus+5mCL{l-r_OS)^WwJ4vw)il5K ziK#WiJkD-)W*QbY|1@`UW|#3t-nm@x_KeqhPKp|H!#g)zYz*%0uU-{+%1OC&Zh>L+ zZbQ2J%?S@L7}lrLCxc$^Cf}R(#mX1XRU1a?8=md!`r!|^`q#gIC~QL4Q4{+8GOGWg zt_y-{3Q8UyUq5X32P?Byymfe6{znDM@`$7AyNk>RzA9+x_f~jdSczwqTDxRtX3^ro z6L;rZW(KF;7+L$m!6j*fOhZ0$UoY)hGXK`3M$zi@2Y&uA(^7tU-Fv(L^~>x?XXB0Y z+0w^feOR7wbymQx@7KQH<4SD${_VeAC_1=>eM{rCa@PATZ!{iH&mZTro|WW;3=$~S zcR;DW{X+%(Y)7Q&|5w1le90^Kpnz{A3OL#r|39RI%f5Sfi0ijUzh2X;e!3`i!?T;T z6?=c&cJh|-%?~8s?yxqEEb}w$ef!9y<5Rx6b>+sagYW+9Y2C%UZ~QcM*hqQQkcM>6 z=6$)3p8Rcs=aLyGO{U&YT^Ors9ea12Dq&67h|HRw(>#vtJT(94{9lsh<@E~qWo_NS z$A>aIySb}w#n#mqf41Y5sh8e8b^E>9Ohu+4JuIZh2S16nu3k2}^Bb>P#NSjt({*6D zW%$lm>#LWS#vPbAe%%od3m%tt zpEkT)^?u-QuP+rR+AF57~f43(3hsVB-i}CDyt1)(1xc2vB zFJzyOczySd&sXo+p0@e;2Y$T=eZA;z2E%uEMMoD6RcHKBQ;r!1| z;Q?p&K9CS3-+w;w^E2a@eOdGH0MAVg+-XAtmqK*#mQDX}sui0lobo;1!11~ zctN7W?Hu|aZ3Ya~WVwyb207j)l5Ns#63XnNjgI08G+9(%&rBWqT-D}BlD6tDbzQLv zQkm_Yoik=c@xqLsqF?{PIATXY*31n#l?9u{yV%!e3^3(7ziPU%V#m^DF>mkxTB*6P zeW~xP_j9)gT-!Z;Nzt-_&lTo>cl*dEt%t2Yp8CG)uHgrJkNah#JnrsIm#}v>aFYf# z9NAUnzcAv3?q7snH%l)lKXdXkHi%q7hjb#ddgcFiM2TIgBNf)suGGK|G^lOX2QVzT zwy`hVV>>!u!HTM8t@h|yu=SA@z2^>44w1+wZya_rsE4fRb96>zb}`X3DS zR}A%+4D|=an&Tl~?}#W=JhV7XeMQ=%^0DyOfB5b2=X2_oe>!Gp(XNGlhWa)C7c7O2 zI}QwIQ;vP!`<0Y07bmYPj4A38Gdl6OyJp|H%&6^`HyFP^Jw)R;A;CU?Mla`^*A&hV zq-mP1A>WTUwDsp<7vino79DEo68VVKv~q&PW!B=SKl!xf?%>6P zmk!-|^7N_oCNAPg+*7~4=oURM@59Zz-}E~^Z^g$^-}{w4^v01rqHVK3y}jb_+|0s7 ztsh@FSvSaAmOuLLgk2B4vi!%z1AQ%Dl&m@ED)L-<^{ua0hK&AhbjBW6-xi4>N(Nm;zP9NS_daChg z&f}uz)_&W4Rkvjy*X?-K+{L-l_tK{8pZkm*^|EYw T-|Wu*C93JG*3SoP9%lXz9{#lZ literal 0 HcmV?d00001 diff --git a/src/Ryujinx/Assets/Icons/Controller_JoyConLeft.svg b/src/Ryujinx/Assets/Icons/Controller_JoyConLeft.svg new file mode 100644 index 00000000..cf78cf12 --- /dev/null +++ b/src/Ryujinx/Assets/Icons/Controller_JoyConLeft.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg b/src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg new file mode 100644 index 00000000..8097762d --- /dev/null +++ b/src/Ryujinx/Assets/Icons/Controller_JoyConPair.svg @@ -0,0 +1,341 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/Assets/Icons/Controller_JoyConRight.svg b/src/Ryujinx/Assets/Icons/Controller_JoyConRight.svg new file mode 100644 index 00000000..adb6e1e1 --- /dev/null +++ b/src/Ryujinx/Assets/Icons/Controller_JoyConRight.svg @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/Assets/Icons/Controller_ProCon.svg b/src/Ryujinx/Assets/Icons/Controller_ProCon.svg new file mode 100644 index 00000000..53eef82c --- /dev/null +++ b/src/Ryujinx/Assets/Icons/Controller_ProCon.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json new file mode 100644 index 00000000..73e55633 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -0,0 +1,780 @@ +{ + "Language": "اَلْعَرَبِيَّةُ", + "MenuBarFileOpenApplet": "فتح التطبيق المصغر", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "‫افتح تطبيق تحرير Mii في الوضع المستقل", + "SettingsTabInputDirectMouseAccess": "الوصول المباشر للفأرة", + "SettingsTabSystemMemoryManagerMode": "وضع إدارة الذاكرة:", + "SettingsTabSystemMemoryManagerModeSoftware": "البرنامج", + "SettingsTabSystemMemoryManagerModeHost": "المُضيف (سريع)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "المضيف (غير مفحوص) (أسرع، غير آمن)", + "SettingsTabSystemUseHypervisor": "استخدم مراقب الأجهزة الافتراضية", + "MenuBarFile": "_ملف", + "MenuBarFileOpenFromFile": "_تحميل تطبيق من ملف", + "MenuBarFileOpenUnpacked": "تحميل لُعْبَة غير محزومة", + "MenuBarFileOpenEmuFolder": "‫فتح مجلد Ryujinx", + "MenuBarFileOpenLogsFolder": "فتح مجلد السجلات", + "MenuBarFileExit": "_خروج", + "MenuBarOptions": "_خيارات", + "MenuBarOptionsToggleFullscreen": "التبديل إلى وضع ملء الشاشة", + "MenuBarOptionsStartGamesInFullscreen": "ابدأ الألعاب في وضع ملء الشاشة", + "MenuBarOptionsStopEmulation": "إيقاف المحاكاة", + "MenuBarOptionsSettings": "_الإعدادات", + "MenuBarOptionsManageUserProfiles": "_إدارة الملفات الشخصية للمستخدم", + "MenuBarActions": "_الإجراءات", + "MenuBarOptionsSimulateWakeUpMessage": "محاكاة رسالة الاستيقاظ", + "MenuBarActionsScanAmiibo": "‫فحص Amiibo", + "MenuBarTools": "_الأدوات", + "MenuBarToolsInstallFirmware": "تثبيت البرنامج الثابت", + "MenuBarFileToolsInstallFirmwareFromFile": "تثبيت برنامج ثابت من XCI أو ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "تثبيت برنامج ثابت من مجلد", + "MenuBarToolsManageFileTypes": "إدارة أنواع الملفات", + "MenuBarToolsInstallFileTypes": "تثبيت أنواع الملفات", + "MenuBarToolsUninstallFileTypes": "إزالة أنواع الملفات", + "MenuBarView": "_عرض", + "MenuBarViewWindow": "حجم النافذة", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "_مساعدة", + "MenuBarHelpCheckForUpdates": "تحقق من التحديثات", + "MenuBarHelpAbout": "حول", + "MenuSearch": "بحث...", + "GameListHeaderFavorite": "مفضلة", + "GameListHeaderIcon": "الأيقونة", + "GameListHeaderApplication": "الاسم", + "GameListHeaderDeveloper": "المطور", + "GameListHeaderVersion": "الإصدار", + "GameListHeaderTimePlayed": "وقت اللعب", + "GameListHeaderLastPlayed": "آخر مرة لُعبت", + "GameListHeaderFileExtension": "صيغة الملف", + "GameListHeaderFileSize": "حجم الملف", + "GameListHeaderPath": "المسار", + "GameListContextMenuOpenUserSaveDirectory": "فتح مجلد حفظ المستخدم", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ المستخدم للتطبيق", + "GameListContextMenuOpenDeviceSaveDirectory": "فتح مجلد حفظ الجهاز", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "يفتح المجلد الذي يحتوي على حفظ الجهاز للتطبيق", + "GameListContextMenuOpenBcatSaveDirectory": "‫فتح مجلد حفظ الـBCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "‫يفتح المجلد الذي يحتوي على حفظ الـBCAT للتطبيق", + "GameListContextMenuManageTitleUpdates": "إدارة تحديثات اللُعبة", + "GameListContextMenuManageTitleUpdatesToolTip": "يفتح نافذة إدارة تحديث اللُعبة", + "GameListContextMenuManageDlc": "إدارة المحتوي الإضافي", + "GameListContextMenuManageDlcToolTip": "يفتح نافذة إدارة المحتوي الإضافي", + "GameListContextMenuCacheManagement": "إدارة ذاكرة التخزين المؤقت", + "GameListContextMenuCacheManagementPurgePptc": "قائمة انتظار إعادة بناء الـ‫PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "تنشيط ‫PPTC لإعادة البناء في وقت الإقلاع عند بدء تشغيل اللعبة التالي", + "GameListContextMenuCacheManagementPurgeShaderCache": "تنظيف ذاكرة مرشحات الفيديو المؤقتة", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "يحذف ذاكرة مرشحات الفيديو المؤقتة الخاصة بالتطبيق", + "GameListContextMenuCacheManagementOpenPptcDirectory": "‫فتح مجلد PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "‫‫يفتح المجلد الذي يحتوي على ذاكرة التخزين المؤقت للترجمة المستمرة (PPTC) للتطبيق", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "فتح مجلد الذاكرة المؤقتة لمرشحات الفيديو ", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "يفتح المجلد الذي يحتوي على ذاكرة المظللات المؤقتة للتطبيق", + "GameListContextMenuExtractData": "استخراج البيانات", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "‫‫ استخراج قسم نظام الملفات القابل للتنفيذ (ExeFS) من الإعدادات الحالية للتطبيقات (يتضمن التحديثات)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "استخراج قسم RomFS من الإعدادات الحالية للتطبيقات (يتضمن التحديثات)", + "GameListContextMenuExtractDataLogo": "شعار", + "GameListContextMenuExtractDataLogoToolTip": "استخراج قسم الشعار من الإعدادات الحالية للتطبيقات (يتضمن التحديثات)", + "GameListContextMenuCreateShortcut": "إنشاء اختصار للتطبيق", + "GameListContextMenuCreateShortcutToolTip": "أنشئ اختصار سطح مكتب لتشغيل التطبيق المحدد", + "GameListContextMenuCreateShortcutToolTipMacOS": "أنشئ اختصار يُشغل التطبيق المحدد في مجلد تطبيقات ‫macOS", + "GameListContextMenuOpenModsDirectory": "‫فتح مجلد التعديلات (Mods)", + "GameListContextMenuOpenModsDirectoryToolTip": "يفتح المجلد الذي يحتوي على تعديلات‫(mods) التطبيق", + "GameListContextMenuOpenSdModsDirectory": "فتح مجلد تعديلات‫(mods) أتموسفير", + "GameListContextMenuOpenSdModsDirectoryToolTip": "يفتح مجلد أتموسفير لبطاقة SD البديلة الذي يحتوي على تعديلات التطبيق. مفيد للتعديلات التي تم تعبئتها للأجهزة الحقيقية.", + "StatusBarGamesLoaded": "{0}/{1} لعبة تم تحميلها", + "StatusBarSystemVersion": "إصدار النظام: {0}", + "LinuxVmMaxMapCountDialogTitle": "الحد الأدنى لتعيينات الذاكرة المكتشفة", + "LinuxVmMaxMapCountDialogTextPrimary": "هل ترغب في زيادة قيمة vm.max_map_count إلى {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيغلق ريوجينكس بمجرد تجاوز هذا الحد.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "نعم، حتى إعادة التشغيل التالية", + "LinuxVmMaxMapCountDialogButtonPersistent": "نعم، دائمًا", + "LinuxVmMaxMapCountWarningTextPrimary": "الحد الأقصى لمقدار تعيينات الذاكرة أقل من الموصى به.", + "LinuxVmMaxMapCountWarningTextSecondary": "القيمة الحالية لـ vm.max_map_count ({0}) أقل من {1}. قد تحاول بعض الألعاب إنشاء المزيد من تعيينات الذاكرة أكثر مما هو مسموح به حاليا. سيغلق ريوجينكس بمجرد تجاوز هذا الحد.\n\nقد ترغب إما في زيادة الحد يدويا أو تثبيت pkexec، مما يسمح لـ ريوجينكس بالمساعدة في ذلك.", + "Settings": "إعدادات", + "SettingsTabGeneral": "واجهة المستخدم", + "SettingsTabGeneralGeneral": "عام", + "SettingsTabGeneralEnableDiscordRichPresence": "تمكين وجود ديسكورد الغني", + "SettingsTabGeneralCheckUpdatesOnLaunch": "التحقق من وجود تحديثات عند التشغيل", + "SettingsTabGeneralShowConfirmExitDialog": "إظهار مربع حوار \"تأكيد الخروج\"", + "SettingsTabGeneralRememberWindowState": "تذكر حجم/موضع النافذة", + "SettingsTabGeneralHideCursor": "إخفاء المؤشر:", + "SettingsTabGeneralHideCursorNever": "مطلقا", + "SettingsTabGeneralHideCursorOnIdle": "عند الخمول", + "SettingsTabGeneralHideCursorAlways": "دائما", + "SettingsTabGeneralGameDirectories": "مجلدات الألعاب", + "SettingsTabGeneralAdd": "إضافة", + "SettingsTabGeneralRemove": "إزالة", + "SettingsTabSystem": "النظام", + "SettingsTabSystemCore": "النواة", + "SettingsTabSystemSystemRegion": "منطقة النظام:", + "SettingsTabSystemSystemRegionJapan": "اليابان", + "SettingsTabSystemSystemRegionUSA": "الولايات المتحدة الأمريكية", + "SettingsTabSystemSystemRegionEurope": "أوروبا", + "SettingsTabSystemSystemRegionAustralia": "أستراليا", + "SettingsTabSystemSystemRegionChina": "الصين", + "SettingsTabSystemSystemRegionKorea": "كوريا", + "SettingsTabSystemSystemRegionTaiwan": "تايوان", + "SettingsTabSystemSystemLanguage": "لغة النظام:", + "SettingsTabSystemSystemLanguageJapanese": "اليابانية", + "SettingsTabSystemSystemLanguageAmericanEnglish": "الإنجليزية الأمريكية", + "SettingsTabSystemSystemLanguageFrench": "الفرنسية", + "SettingsTabSystemSystemLanguageGerman": "الألمانية", + "SettingsTabSystemSystemLanguageItalian": "الإيطالية", + "SettingsTabSystemSystemLanguageSpanish": "الإسبانية", + "SettingsTabSystemSystemLanguageChinese": "الصينية", + "SettingsTabSystemSystemLanguageKorean": "الكورية", + "SettingsTabSystemSystemLanguageDutch": "الهولندية", + "SettingsTabSystemSystemLanguagePortuguese": "البرتغالية", + "SettingsTabSystemSystemLanguageRussian": "الروسية", + "SettingsTabSystemSystemLanguageTaiwanese": "التايوانية", + "SettingsTabSystemSystemLanguageBritishEnglish": "الإنجليزية البريطانية", + "SettingsTabSystemSystemLanguageCanadianFrench": "الفرنسية الكندية", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "إسبانية أمريكا اللاتينية", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "الصينية المبسطة", + "SettingsTabSystemSystemLanguageTraditionalChinese": "الصينية التقليدية", + "SettingsTabSystemSystemTimeZone": "النطاق الزمني للنظام:", + "SettingsTabSystemSystemTime": "توقيت النظام:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (ذاكرة التخزين المؤقت للترجمة المستمرة)", + "SettingsTabSystemEnableFsIntegrityChecks": "التحقق من سلامة نظام الملفات", + "SettingsTabSystemAudioBackend": "خلفية الصوت:", + "SettingsTabSystemAudioBackendDummy": "زائف", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "هاكات", + "SettingsTabSystemHacksNote": "قد يتسبب في عدم الاستقرار", + "SettingsTabSystemExpandDramSize": "استخدام تخطيط الذاكرة البديل (المطورين)", + "SettingsTabSystemIgnoreMissingServices": "تجاهل الخدمات المفقودة", + "SettingsTabGraphics": "الرسومات", + "SettingsTabGraphicsAPI": "API الرسومات ", + "SettingsTabGraphicsEnableShaderCache": "تفعيل ذاكرة المظللات المؤقتة", + "SettingsTabGraphicsAnisotropicFiltering": "تصفية:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "تلقائي", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4×", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "مقياس الدقة", + "SettingsTabGraphicsResolutionScaleCustom": "مخصص (لا ينصح به)", + "SettingsTabGraphicsResolutionScaleNative": "الأصل ‫(720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (لا ينصح به)", + "SettingsTabGraphicsAspectRatio": "نسبة الارتفاع إلى العرض:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "تمديد لتناسب النافذة", + "SettingsTabGraphicsDeveloperOptions": "خيارات المطور", + "SettingsTabGraphicsShaderDumpPath": "مسار تفريغ المظللات:", + "SettingsTabLogging": "تسجيل", + "SettingsTabLoggingLogging": "تسجيل", + "SettingsTabLoggingEnableLoggingToFile": "تفعيل التسجيل إلى ملف", + "SettingsTabLoggingEnableStubLogs": "تفعيل سجلات الـStub", + "SettingsTabLoggingEnableInfoLogs": "تفعيل سجلات المعلومات", + "SettingsTabLoggingEnableWarningLogs": "تفعيل سجلات التحذير", + "SettingsTabLoggingEnableErrorLogs": "تفعيل سجلات الأخطاء", + "SettingsTabLoggingEnableTraceLogs": "تفعيل سجلات التتبع", + "SettingsTabLoggingEnableGuestLogs": "تفعيل سجلات الضيوف", + "SettingsTabLoggingEnableFsAccessLogs": "تمكين سجلات الوصول إلى نظام الملفات", + "SettingsTabLoggingFsGlobalAccessLogMode": "وضع سجل الوصول العالمي لنظام الملفات:", + "SettingsTabLoggingDeveloperOptions": "خيارات المطور", + "SettingsTabLoggingDeveloperOptionsNote": "تحذير: سوف يقلل من الأداء", + "SettingsTabLoggingGraphicsBackendLogLevel": "مستوى سجل خلفية الرسومات:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "لا شيء", + "SettingsTabLoggingGraphicsBackendLogLevelError": "خطأ", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "تباطؤ", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "الكل", + "SettingsTabLoggingEnableDebugLogs": "تمكين سجلات التصحيح", + "SettingsTabInput": "الإدخال", + "SettingsTabInputEnableDockedMode": "تركيب بالمنصة", + "SettingsTabInputDirectKeyboardAccess": "الوصول المباشر للوحة المفاتيح", + "SettingsButtonSave": "حفظ", + "SettingsButtonClose": "إغلاق", + "SettingsButtonOk": "موافق", + "SettingsButtonCancel": "إلغاء", + "SettingsButtonApply": "تطبيق", + "ControllerSettingsPlayer": "اللاعب", + "ControllerSettingsPlayer1": "اللاعب 1", + "ControllerSettingsPlayer2": "اللاعب 2", + "ControllerSettingsPlayer3": "اللاعب 3", + "ControllerSettingsPlayer4": "اللاعب 4", + "ControllerSettingsPlayer5": "اللاعب 5", + "ControllerSettingsPlayer6": "اللاعب 6", + "ControllerSettingsPlayer7": "اللاعب 7", + "ControllerSettingsPlayer8": "اللاعب 8", + "ControllerSettingsHandheld": "محمول", + "ControllerSettingsInputDevice": "جهاز الإدخال", + "ControllerSettingsRefresh": "تحديث", + "ControllerSettingsDeviceDisabled": "معطل", + "ControllerSettingsControllerType": "نوع وحدة التحكم", + "ControllerSettingsControllerTypeHandheld": "محمول", + "ControllerSettingsControllerTypeProController": "وحدة تحكم برو", + "ControllerSettingsControllerTypeJoyConPair": "زوج جوي كون", + "ControllerSettingsControllerTypeJoyConLeft": "جوي كون اليسار ", + "ControllerSettingsControllerTypeJoyConRight": " جوي كون اليمين", + "ControllerSettingsProfile": "الملف الشخصي", + "ControllerSettingsProfileDefault": "افتراضي", + "ControllerSettingsLoad": "تحميل", + "ControllerSettingsAdd": "إضافة", + "ControllerSettingsRemove": "إزالة", + "ControllerSettingsButtons": "الأزرار", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "أسهم الاتجاهات", + "ControllerSettingsDPadUp": "اعلى", + "ControllerSettingsDPadDown": "أسفل", + "ControllerSettingsDPadLeft": "يسار", + "ControllerSettingsDPadRight": "يمين", + "ControllerSettingsStickButton": "زر", + "ControllerSettingsStickUp": "فوق", + "ControllerSettingsStickDown": "أسفل", + "ControllerSettingsStickLeft": "يسار", + "ControllerSettingsStickRight": "يمين", + "ControllerSettingsStickStick": "عصا", + "ControllerSettingsStickInvertXAxis": "عكس عرض العصا", + "ControllerSettingsStickInvertYAxis": "عكس أفق العصا", + "ControllerSettingsStickDeadzone": "المنطقة الميتة:", + "ControllerSettingsLStick": "العصا اليسرى", + "ControllerSettingsRStick": "العصا اليمنى", + "ControllerSettingsTriggersLeft": "الأزندة اليسرى", + "ControllerSettingsTriggersRight": "الأزندة اليمني", + "ControllerSettingsTriggersButtonsLeft": "أزرار الزناد اليسرى", + "ControllerSettingsTriggersButtonsRight": "أزرار الزناد اليمنى", + "ControllerSettingsTriggers": "أزندة", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "الأزرار اليسار", + "ControllerSettingsExtraButtonsRight": "الأزرار اليمين", + "ControllerSettingsMisc": "إعدادات إضافية", + "ControllerSettingsTriggerThreshold": "قوة التحفيز:", + "ControllerSettingsMotion": "الحركة", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "استخدام الحركة المتوافقة مع CemuHook", + "ControllerSettingsMotionControllerSlot": "خانة وحدة التحكم:", + "ControllerSettingsMotionMirrorInput": "إعادة الإدخال", + "ControllerSettingsMotionRightJoyConSlot": "خانة جويكون اليمين :", + "ControllerSettingsMotionServerHost": "مضيف الخادم:", + "ControllerSettingsMotionGyroSensitivity": "حساسية مستشعر الحركة:", + "ControllerSettingsMotionGyroDeadzone": "منطقة مستشعر الحركة الميتة:", + "ControllerSettingsSave": "حفظ", + "ControllerSettingsClose": "إغلاق", + "KeyUnknown": "مجهول", + "KeyShiftLeft": "زر ‫Shift الأيسر", + "KeyShiftRight": "زر ‫Shift الأيمن", + "KeyControlLeft": "زر ‫Ctrl الأيسر", + "KeyMacControlLeft": "زر ⌃ الأيسر", + "KeyControlRight": "زر ‫Ctrl الأيمن", + "KeyMacControlRight": "زر ⌃ الأيمن", + "KeyAltLeft": "زر ‫Alt الأيسر", + "KeyMacAltLeft": "زر ⌥ الأيسر", + "KeyAltRight": "زر ‫Alt الأيمن", + "KeyMacAltRight": "زر ⌥ الأيمن", + "KeyWinLeft": "زر ⊞ الأيسر", + "KeyMacWinLeft": "زر ⌘ الأيسر", + "KeyWinRight": "زر ⊞ الأيمن", + "KeyMacWinRight": "زر ⌘ الأيمن", + "KeyMenu": "زر القائمة", + "KeyUp": "فوق", + "KeyDown": "اسفل", + "KeyLeft": "يسار", + "KeyRight": "يمين", + "KeyEnter": "مفتاح الإدخال", + "KeyEscape": "زر ‫Escape", + "KeySpace": "مسافة", + "KeyTab": "زر ‫Tab", + "KeyBackSpace": "زر المسح للخلف", + "KeyInsert": "زر Insert", + "KeyDelete": "زر الحذف", + "KeyPageUp": "زر ‫Page Up", + "KeyPageDown": "زر ‫Page Down", + "KeyHome": "زر ‫Home", + "KeyEnd": "زر ‫End", + "KeyCapsLock": "زر الحروف الكبيرة", + "KeyScrollLock": "زر ‫Scroll Lock", + "KeyPrintScreen": "زر ‫Print Screen", + "KeyPause": "زر Pause", + "KeyNumLock": "زر Num Lock", + "KeyClear": "زر Clear", + "KeyKeypad0": "لوحة الأرقام 0", + "KeyKeypad1": "لوحة الأرقام 1", + "KeyKeypad2": "لوحة الأرقام 2", + "KeyKeypad3": "لوحة الأرقام 3", + "KeyKeypad4": "لوحة الأرقام 4", + "KeyKeypad5": "لوحة الأرقام 5", + "KeyKeypad6": "لوحة الأرقام 6", + "KeyKeypad7": "لوحة الأرقام 7", + "KeyKeypad8": "لوحة الأرقام 8", + "KeyKeypad9": "لوحة الأرقام 9", + "KeyKeypadDivide": "لوحة الأرقام علامة القسمة", + "KeyKeypadMultiply": "لوحة الأرقام علامة الضرب", + "KeyKeypadSubtract": "لوحة الأرقام علامة الطرح\n", + "KeyKeypadAdd": "لوحة الأرقام علامة الزائد", + "KeyKeypadDecimal": "لوحة الأرقام الفاصلة العشرية", + "KeyKeypadEnter": "لوحة الأرقام زر الإدخال", + "KeyNumber0": "٠", + "KeyNumber1": "١", + "KeyNumber2": "٢", + "KeyNumber3": "٣", + "KeyNumber4": "٤", + "KeyNumber5": "٥", + "KeyNumber6": "٦", + "KeyNumber7": "٧", + "KeyNumber8": "٨", + "KeyNumber9": "٩", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "غير مرتبط", + "GamepadLeftStick": "زر عصا التحكم اليسرى", + "GamepadRightStick": "زر عصا التحكم اليمنى", + "GamepadLeftShoulder": "زر الكتف الأيسر‫ L", + "GamepadRightShoulder": "زر الكتف الأيمن‫ R", + "GamepadLeftTrigger": "زر الزناد الأيسر‫ (ZL)", + "GamepadRightTrigger": "زر الزناد الأيمن‫ (ZR)", + "GamepadDpadUp": "فوق", + "GamepadDpadDown": "اسفل", + "GamepadDpadLeft": "يسار", + "GamepadDpadRight": "يمين", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "دليل", + "GamepadMisc1": "متنوع", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "لوحة اللمس", + "GamepadSingleLeftTrigger0": "زر الزناد الأيسر 0", + "GamepadSingleRightTrigger0": "زر الزناد الأيمن 0", + "GamepadSingleLeftTrigger1": "زر الزناد الأيسر 1", + "GamepadSingleRightTrigger1": "زر الزناد الأيمن 1", + "StickLeft": "عصا التحكم اليسرى", + "StickRight": "عصا التحكم اليمنى", + "UserProfilesSelectedUserProfile": "الملف الشخصي المحدد للمستخدم:", + "UserProfilesSaveProfileName": "حفظ اسم الملف الشخصي", + "UserProfilesChangeProfileImage": "تغيير صورة الملف الشخصي", + "UserProfilesAvailableUserProfiles": "الملفات الشخصية للمستخدم المتاحة:", + "UserProfilesAddNewProfile": "إنشاء ملف الشخصي", + "UserProfilesDelete": "حذف", + "UserProfilesClose": "إغلاق", + "ProfileNameSelectionWatermark": "اختر اسم مستعار", + "ProfileImageSelectionTitle": "تحديد صورة الملف الشخصي", + "ProfileImageSelectionHeader": "اختر صورة الملف الشخصي", + "ProfileImageSelectionNote": "يمكنك استيراد صورة ملف شخصي مخصصة، أو تحديد صورة رمزية من البرامج الثابتة للنظام", + "ProfileImageSelectionImportImage": "استيراد ملف الصورة", + "ProfileImageSelectionSelectAvatar": "حدد الصورة الرمزية من البرنامج الثابتة", + "InputDialogTitle": "حوار الإدخال", + "InputDialogOk": "موافق", + "InputDialogCancel": "إلغاء", + "InputDialogAddNewProfileTitle": "اختر اسم الملف الشخصي", + "InputDialogAddNewProfileHeader": "الرجاء إدخال اسم الملف الشخصي", + "InputDialogAddNewProfileSubtext": "(الطول الأقصى: {0})", + "AvatarChoose": "اختر الصورة الرمزية", + "AvatarSetBackgroundColor": "تعيين لون الخلفية", + "AvatarClose": "إغلاق", + "ControllerSettingsLoadProfileToolTip": "تحميل الملف الشخصي", + "ControllerSettingsAddProfileToolTip": "إضافة ملف شخصي", + "ControllerSettingsRemoveProfileToolTip": "إزالة الملف الشخصي", + "ControllerSettingsSaveProfileToolTip": "حفظ الملف الشخصي", + "MenuBarFileToolsTakeScreenshot": "أخذ لقطة للشاشة", + "MenuBarFileToolsHideUi": "إخفاء واجهة المستخدم", + "GameListContextMenuRunApplication": "تشغيل التطبيق", + "GameListContextMenuToggleFavorite": "تعيين كمفضل", + "GameListContextMenuToggleFavoriteToolTip": "تبديل الحالة المفضلة للعبة", + "SettingsTabGeneralTheme": "السمة:", + "SettingsTabGeneralThemeDark": "داكن", + "SettingsTabGeneralThemeLight": "فاتح", + "ControllerSettingsConfigureGeneral": "ضبط", + "ControllerSettingsRumble": "الاهتزاز", + "ControllerSettingsRumbleStrongMultiplier": "مضاعف اهتزاز قوي", + "ControllerSettingsRumbleWeakMultiplier": "مضاعف اهتزاز ضعيف", + "DialogMessageSaveNotAvailableMessage": "لا توجد بيانات الحفظ لـ {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "هل ترغب في إنشاء بيانات الحفظ لهذه اللعبة؟", + "DialogConfirmationTitle": "ريوجينكس - تأكيد", + "DialogUpdaterTitle": "ريوجينكس - المحدث", + "DialogErrorTitle": "ريوجينكس - خطأ", + "DialogWarningTitle": "ريوجينكس - تحذير", + "DialogExitTitle": "ريوجينكس - الخروج", + "DialogErrorMessage": "واجه ريوجينكس خطأ", + "DialogExitMessage": "هل أنت متأكد من أنك تريد إغلاق ريوجينكس؟", + "DialogExitSubMessage": "سيتم فقدان كافة البيانات غير المحفوظة!", + "DialogMessageCreateSaveErrorMessage": "حدث خطأ أثناء إنشاء بيانات الحفظ المحددة: {0}", + "DialogMessageFindSaveErrorMessage": "حدث خطأ أثناء البحث عن بيانات الحفظ المحددة: {0}", + "FolderDialogExtractTitle": "اختر المجلد الذي تريد الاستخراج إليه", + "DialogNcaExtractionMessage": "استخراج قسم {0} من {1}...", + "DialogNcaExtractionTitle": "ريوجينكس - مستخرج قسم NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "فشل الاستخراج. لم يكن NCA الرئيسي موجودا في الملف المحدد.", + "DialogNcaExtractionCheckLogErrorMessage": "فشل الاستخراج. اقرأ ملف التسجيل لمزيد من المعلومات.", + "DialogNcaExtractionSuccessMessage": "تم الاستخراج بنجاح.", + "DialogUpdaterConvertFailedMessage": "فشل تحويل إصدار ريوجينكس الحالي.", + "DialogUpdaterCancelUpdateMessage": "إلغاء التحديث", + "DialogUpdaterAlreadyOnLatestVersionMessage": "أنت تستخدم بالفعل أحدث إصدار من ريوجينكس!", + "DialogUpdaterFailedToGetVersionMessage": "حدث خطأ أثناء محاولة الحصول على معلومات الإصدار من إصدار غيت هاب. يمكن أن يحدث هذا إذا تم تجميع إصدار جديد بواسطة إجراءات غيت هاب. جرب مجددا بعد دقائق.", + "DialogUpdaterConvertFailedGithubMessage": "فشل تحويل إصدار ريوجينكس المستلم من إصدار غيت هاب.", + "DialogUpdaterDownloadingMessage": "جاري تنزيل التحديث...", + "DialogUpdaterExtractionMessage": "جاري استخراج التحديث...", + "DialogUpdaterRenamingMessage": "إعادة تسمية التحديث...", + "DialogUpdaterAddingFilesMessage": "إضافة تحديث جديد...", + "DialogUpdaterCompleteMessage": "اكتمل التحديث", + "DialogUpdaterRestartMessage": "هل تريد إعادة تشغيل ريوجينكس الآن؟", + "DialogUpdaterNoInternetMessage": "أنت غير متصل بالإنترنت.", + "DialogUpdaterNoInternetSubMessage": "يرجى التحقق من أن لديك اتصال إنترنت فعال!", + "DialogUpdaterDirtyBuildMessage": "لا يمكنك تحديث نسخة القذرة من ريوجينكس!", + "DialogUpdaterDirtyBuildSubMessage": "الرجاء تحميل ريوجينكس من https://ryujinx.org إذا كنت تبحث عن إصدار مدعوم.", + "DialogRestartRequiredMessage": "يتطلب إعادة التشغيل", + "DialogThemeRestartMessage": "تم حفظ السمة. إعادة التشغيل مطلوبة لتطبيق السمة.", + "DialogThemeRestartSubMessage": "هل تريد إعادة التشغيل", + "DialogFirmwareInstallEmbeddedMessage": "هل ترغب في تثبيت البرنامج الثابت المدمج في هذه اللعبة؟ (البرنامج الثابت {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "لم يتم العثور على أي برنامج ثابت مثبت ولكن ريوجينكس كان قادرا على تثبيت البرنامج الثابت {0} من اللعبة المقدمة.\nسيبدأ المحاكي الآن.", + "DialogFirmwareNoFirmwareInstalledMessage": "لا يوجد برنامج ثابت مثبت", + "DialogFirmwareInstalledMessage": "تم تثبيت البرنامج الثابت {0}", + "DialogInstallFileTypesSuccessMessage": "تم تثبيت أنواع الملفات بنجاح!", + "DialogInstallFileTypesErrorMessage": "فشل تثبيت أنواع الملفات.", + "DialogUninstallFileTypesSuccessMessage": "تم إلغاء تثبيت أنواع الملفات بنجاح!", + "DialogUninstallFileTypesErrorMessage": "فشل إلغاء تثبيت أنواع الملفات.", + "DialogOpenSettingsWindowLabel": "فتح نافذة الإعدادات", + "DialogControllerAppletTitle": "تطبيق وحدة التحكم المصغر", + "DialogMessageDialogErrorExceptionMessage": "خطأ في عرض مربع حوار الرسالة: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "خطأ في عرض لوحة مفاتيح البرامج: {0}", + "DialogErrorAppletErrorExceptionMessage": "خطأ في عرض مربع حوار خطأ التطبيق المصغر: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "لمزيد من المعلومات حول كيفية إصلاح هذا الخطأ، اتبع دليل الإعداد الخاص بنا.", + "DialogUserErrorDialogTitle": "خطأ ريوجينكس ({0})", + "DialogAmiiboApiTitle": "أميبو API", + "DialogAmiiboApiFailFetchMessage": "حدث خطأ أثناء جلب المعلومات من API.", + "DialogAmiiboApiConnectErrorMessage": "غير قادر على الاتصال بخادم API أميبو. قد تكون الخدمة معطلة أو قد تحتاج إلى التحقق من اتصالك بالإنترنت.", + "DialogProfileInvalidProfileErrorMessage": "الملف الشخصي {0} غير متوافق مع نظام تكوين الإدخال الحالي.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "لا يمكن الكتابة فوق الملف الشخصي الافتراضي", + "DialogProfileDeleteProfileTitle": "حذف الملف الشخصي", + "DialogProfileDeleteProfileMessage": "هذا الإجراء لا رجعة فيه، هل أنت متأكد من أنك تريد المتابعة؟", + "DialogWarning": "تحذير", + "DialogPPTCDeletionMessage": "أنت على وشك الإنتظار لإعادة بناء ذاكرة التخزين المؤقت للترجمة المستمرة (PPTC) عند الإقلاع التالي لـ:\n\n{0}\n\nأمتأكد من رغبتك في المتابعة؟", + "DialogPPTCDeletionErrorMessage": "خطأ خلال تنظيف ذاكرة التخزين المؤقت للترجمة المستمرة (PPTC) في {0}: {1}", + "DialogShaderDeletionMessage": "أنت على وشك حذف ذاكرة المظللات المؤقتة ل:\n\n{0}\n\nهل انت متأكد انك تريد المتابعة؟", + "DialogShaderDeletionErrorMessage": "حدث خطأ أثناء تنظيف ذاكرة المظللات المؤقتة في {0}: {1}", + "DialogRyujinxErrorMessage": "واجه ريوجينكس خطأ", + "DialogInvalidTitleIdErrorMessage": "خطأ في واجهة المستخدم: اللعبة المحددة لم يكن لديها معرف عنوان صالح", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "لم يتم العثور على برنامج ثابت للنظام صالح في {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "تثبيت البرنامج الثابت {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "سيتم تثبيت إصدار النظام {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nهذا سيحل محل إصدار النظام الحالي {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\nهل تريد المتابعة؟", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "تثبيت البرنامج الثابت...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "تم تثبيت إصدار النظام {0} بنجاح.", + "DialogUserProfileDeletionWarningMessage": "لن تكون هناك ملفات الشخصية أخرى لفتحها إذا تم حذف الملف الشخصي المحدد", + "DialogUserProfileDeletionConfirmMessage": "هل تريد حذف الملف الشخصي المحدد", + "DialogUserProfileUnsavedChangesTitle": "تحذير - التغييرات غير محفوظة", + "DialogUserProfileUnsavedChangesMessage": "لقد قمت بإجراء تغييرات على الملف الشخصي لهذا المستخدم هذا ولم يتم حفظها.", + "DialogUserProfileUnsavedChangesSubMessage": "هل تريد تجاهل التغييرات؟", + "DialogControllerSettingsModifiedConfirmMessage": "تم تحديث إعدادات وحدة التحكم الحالية.", + "DialogControllerSettingsModifiedConfirmSubMessage": "هل تريد الحفظ ؟", + "DialogLoadFileErrorMessage": "{0}. ملف خاطئ: {1}", + "DialogModAlreadyExistsMessage": "التعديل موجود بالفعل", + "DialogModInvalidMessage": "المجلد المحدد لا يحتوي على تعديل!", + "DialogModDeleteNoParentMessage": "فشل الحذف: لم يمكن العثور على المجلد الرئيسي للتعديل\"{0}\"!", + "DialogDlcNoDlcErrorMessage": "الملف المحدد لا يحتوي على محتوى إضافي للعنوان المحدد!", + "DialogPerformanceCheckLoggingEnabledMessage": "لقد تم تمكين تسجيل التتبع، والذي تم تصميمه ليتم استخدامه من قبل المطورين فقط.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "للحصول على الأداء الأمثل، يوصى بتعطيل تسجيل التتبع. هل ترغب في تعطيل تسجيل التتبع الآن؟", + "DialogPerformanceCheckShaderDumpEnabledMessage": "لقد قمت بتمكين تفريغ المظللات، والذي تم تصميمه ليستخدمه المطورون فقط.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "للحصول على الأداء الأمثل، يوصى بتعطيل تفريغ المظللات. هل ترغب في تعطيل تفريغ المظللات الآن؟", + "DialogLoadAppGameAlreadyLoadedMessage": "تم تحميل لعبة بالفعل", + "DialogLoadAppGameAlreadyLoadedSubMessage": "الرجاء إيقاف المحاكاة أو إغلاق المحاكي قبل بدء لعبة أخرى.", + "DialogUpdateAddUpdateErrorMessage": "الملف المحدد لا يحتوي على تحديث للعنوان المحدد!", + "DialogSettingsBackendThreadingWarningTitle": "تحذير - خلفية متعددة المسارات", + "DialogSettingsBackendThreadingWarningMessage": "يجب إعادة تشغيل ريوجينكس بعد تغيير هذا الخيار حتى يتم تطبيقه بالكامل. اعتمادا على النظام الأساسي الخاص بك، قد تحتاج إلى تعطيل تعدد المسارات الخاص ببرنامج الرسومات التشغيل الخاص بك يدويًا عند استخدام الخاص بريوجينكس.", + "DialogModManagerDeletionWarningMessage": "أنت على وشك حذف التعديل: {0}\n\nهل انت متأكد انك تريد المتابعة؟", + "DialogModManagerDeletionAllWarningMessage": "أنت على وشك حذف كافة التعديلات لهذا العنوان.\n\nهل انت متأكد انك تريد المتابعة؟", + "SettingsTabGraphicsFeaturesOptions": "المميزات", + "SettingsTabGraphicsBackendMultithreading": "تعدد المسارات لخلفية الرسومات:", + "CommonAuto": "تلقائي", + "CommonOff": "معطل", + "CommonOn": "تشغيل", + "InputDialogYes": "نعم", + "InputDialogNo": "لا", + "DialogProfileInvalidProfileNameErrorMessage": "يحتوي اسم الملف على أحرف غير صالحة. يرجى المحاولة مرة أخرى.", + "MenuBarOptionsPauseEmulation": "إيقاف مؤقت", + "MenuBarOptionsResumeEmulation": "استئناف", + "AboutUrlTooltipMessage": "انقر لفتح موقع ريوجينكس في متصفحك الافتراضي.", + "AboutDisclaimerMessage": "ريوجينكس لا ينتمي إلى نينتندو™،\nأو أي من شركائها بأي شكل من الأشكال.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) يتم \nاستخدامه في محاكاة أمبيو لدينا.", + "AboutPatreonUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في باتريون في متصفحك الافتراضي.", + "AboutGithubUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في غيت هاب في متصفحك الافتراضي.", + "AboutDiscordUrlTooltipMessage": "انقر لفتح دعوة إلى خادم ريوجينكس في ديكسورد في متصفحك الافتراضي.", + "AboutTwitterUrlTooltipMessage": "انقر لفتح صفحة ريوجينكس في تويتر في متصفحك الافتراضي.", + "AboutRyujinxAboutTitle": "حول:", + "AboutRyujinxAboutContent": "ريوجينكس هو محاكي لجهاز نينتندو سويتش™.\nمن فضلك ادعمنا على باتريون.\nاحصل على آخر الأخبار على تويتر أو ديسكورد.\nيمكن للمطورين المهتمين بالمساهمة معرفة المزيد على غيت هاب أو ديسكورد.", + "AboutRyujinxMaintainersTitle": "تتم صيانته بواسطة:", + "AboutRyujinxMaintainersContentTooltipMessage": "انقر لفتح صفحة المساهمين في متصفحك الافتراضي.", + "AboutRyujinxSupprtersTitle": "مدعوم على باتريون بواسطة:", + "AmiiboSeriesLabel": "مجموعة أميبو", + "AmiiboCharacterLabel": "شخصية", + "AmiiboScanButtonLabel": "فحصه", + "AmiiboOptionsShowAllLabel": "إظهار كل أميبو", + "AmiiboOptionsUsRandomTagLabel": "هاك: استخدم علامة Uuid عشوائية ", + "DlcManagerTableHeadingEnabledLabel": "مفعل", + "DlcManagerTableHeadingTitleIdLabel": "معرف العنوان", + "DlcManagerTableHeadingContainerPathLabel": "مسار الحاوية", + "DlcManagerTableHeadingFullPathLabel": "المسار كاملا", + "DlcManagerRemoveAllButton": "حذف الكل", + "DlcManagerEnableAllButton": "تشغيل الكل", + "DlcManagerDisableAllButton": "تعطيل الكل", + "ModManagerDeleteAllButton": "حذف الكل", + "MenuBarOptionsChangeLanguage": "تغيير اللغة", + "MenuBarShowFileTypes": "إظهار أنواع الملفات", + "CommonSort": "فرز", + "CommonShowNames": "عرض الأسماء", + "CommonFavorite": "المفضلة", + "OrderAscending": "تصاعدي", + "OrderDescending": "تنازلي", + "SettingsTabGraphicsFeatures": "الميزات والتحسينات", + "ErrorWindowTitle": "نافذة الخطأ", + "ToggleDiscordTooltip": "اختر ما إذا كنت تريد عرض ريوجينكس في نشاط ديسكورد \"يتم تشغيله حاليا\" أم لا", + "AddGameDirBoxTooltip": "أدخل مجلد اللعبة لإضافته إلى القائمة", + "AddGameDirTooltip": "إضافة مجلد اللعبة إلى القائمة", + "RemoveGameDirTooltip": "إزالة مجلد اللعبة المحدد", + "CustomThemeCheckTooltip": "استخدم سمة أفالونيا المخصصة لواجهة المستخدم الرسومية لتغيير مظهر قوائم المحاكي", + "CustomThemePathTooltip": "مسار سمة واجهة المستخدم المخصصة", + "CustomThemeBrowseTooltip": "تصفح للحصول على سمة واجهة المستخدم المخصصة", + "DockModeToggleTooltip": "يجعل وضع تركيب بالمنصة النظام الذي تمت محاكاته بمثابة جهاز نينتندو سويتش الذي تم تركيبه بالمنصة. يؤدي هذا إلى تحسين الدقة الرسومية في معظم الألعاب. على العكس من ذلك، سيؤدي تعطيل هذا إلى جعل النظام الذي تمت محاكاته يعمل كجهاز نينتندو سويتش محمول، مما يقلل من جودة الرسومات.\n\nقم بتكوين عناصر تحكم اللاعب 1 إذا كنت تخطط لاستخدام وضع تركيب بالمنصة؛ قم بتكوين عناصر التحكم المحمولة إذا كنت تخطط لاستخدام الوضع المحمول.\n\nاتركه مشغل إذا لم تكن متأكدا.", + "DirectKeyboardTooltip": "دعم الوصول المباشر للوحة المفاتيح (HID). يوفر وصول الألعاب إلى لوحة المفاتيح الخاصة بك كجهاز لإدخال النص.\n\nيعمل فقط مع الألعاب التي تدعم استخدام لوحة المفاتيح في الأصل على أجهزة سويتش.\n\nاتركه معطلا إذا كنت غير متأكد.", + "DirectMouseTooltip": "دعم الوصول المباشر للوحة المفاتيح (HID). يوفر وصول الألعاب إلى لوحة المفاتيح الخاصة بك كجهاز لإدخال النص.\n\nيعمل فقط مع الألعاب التي تدعم استخدام لوحة المفاتيح في الأصل على أجهزة سويتش.\n\nاتركه معطلا إذا كنت غير متأكد.", + "RegionTooltip": "تغيير منطقة النظام", + "LanguageTooltip": "تغيير لغة النظام", + "TimezoneTooltip": "تغيير النطاق الزمني للنظام", + "TimeTooltip": "تغيير وقت النظام", + "VSyncToggleTooltip": "محاكاة المزامنة العمودية للجهاز. في الأساس محدد الإطار لغالبية الألعاب؛ قد يؤدي تعطيله إلى تشغيل الألعاب بسرعة أعلى أو جعل شاشات التحميل تستغرق وقتا أطول أو تتعطل.\n\nيمكن تبديله داخل اللعبة باستخدام مفتاح التشغيل السريع الذي تفضله (F1 افتراضيا). نوصي بالقيام بذلك إذا كنت تخطط لتعطيله.\n\nاتركه ممكنا إذا لم تكن متأكدا.", + "PptcToggleTooltip": "يحفظ وظائف JIT المترجمة بحيث لا تحتاج إلى ترجمتها في كل مرة يتم فيها تحميل اللعبة.\n\nيقلل من التقطيع ويسرع بشكل ملحوظ أوقات التشغيل بعد التشغيل الأول للعبة.\n\nاتركه ممكنا إذا لم تكن متأكدا.", + "FsIntegrityToggleTooltip": "يتحقق من وجود ملفات تالفة عند تشغيل لعبة ما، وإذا تم اكتشاف ملفات تالفة، فسيتم عرض خطأ تجزئة في السجل.\n\nليس له أي تأثير على الأداء ويهدف إلى المساعدة في استكشاف الأخطاء وإصلاحها.\n\nاتركه مفعلا إذا كنت غير متأكد.", + "AudioBackendTooltip": "يغير الواجهة الخلفية المستخدمة لتقديم الصوت.\n\nSDL2 هو الخيار المفضل، بينما يتم استخدام OpenAL وSoundIO كبديلين. زائف لن يكون لها صوت.\n\nاضبط على SDL2 إذا لم تكن متأكدا.", + "MemoryManagerTooltip": "تغيير كيفية تعيين ذاكرة الضيف والوصول إليها. يؤثر بشكل كبير على أداء وحدة المعالجة المركزية التي تمت محاكاتها.\n\nاضبط على المضيف غير محدد إذا لم تكن متأكدا.", + "MemoryManagerSoftwareTooltip": "استخدام جدول الصفحات البرمجي لترجمة العناوين. أعلى دقة ولكن أبطأ أداء.", + "MemoryManagerHostTooltip": "تعيين الذاكرة مباشرة في مساحة عنوان المضيف. تجميع وتنفيذ JIT أسرع بكثير.", + "MemoryManagerUnsafeTooltip": "تعيين الذاكرة مباشرة، ولكن لا تخفي العنوان داخل مساحة عنوان الضيف قبل الوصول. أسرع، ولكن على حساب السلامة. يمكن لتطبيق الضيف الوصول إلى الذاكرة من أي مكان في ريوجينكس، لذا قم بتشغيل البرامج التي تثق بها فقط مع هذا الوضع.", + "UseHypervisorTooltip": "استخدم هايبرڤايزور بدلا من JIT. يعمل على تحسين الأداء بشكل كبير عند توفره، ولكنه قد يكون غير مستقر في حالته الحالية.", + "DRamTooltip": "يستخدم تخطيط وضع الذاكرة البديل لتقليد نموذج سويتش المطورين.\n\nيعد هذا مفيدا فقط لحزم النسيج عالية الدقة أو تعديلات دقة 4K. لا يحسن الأداء.\n\nاتركه معطلا إذا لم تكن متأكدا.", + "IgnoreMissingServicesTooltip": "يتجاهل خدمات نظام هوريزون غير المنفذة. قد يساعد هذا في تجاوز الأعطال عند تشغيل ألعاب معينة.\n\nاتركه معطلا إذا كنت غير متأكد.", + "GraphicsBackendThreadingTooltip": "ينفذ أوامر الواجهة الخلفية للرسومات على مسار ثاني.\n\nيعمل على تسريع عملية تجميع المظللات وتقليل التقطيع وتحسين الأداء على برامج تشغيل وحدة الرسوميات دون دعم المسارات المتعددة الخاصة بهم. أداء أفضل قليلا على برامج التشغيل ذات المسارات المتعددة.\n\nاضبط على تلقائي إذا لم تكن متأكدا.", + "GalThreadingTooltip": "ينفذ أوامر الواجهة الخلفية للرسومات على مسار ثاني.\n\nيعمل على تسريع عملية تجميع المظللات وتقليل التقطيع وتحسين الأداء على برامج تشغيل وحدة الرسوميات دون دعم المسارات المتعددة الخاصة بهم. أداء أفضل قليلا على برامج التشغيل ذات المسارات المتعددة.\n\nاضبط على تلقائي إذا لم تكن متأكدا.", + "ShaderCacheToggleTooltip": "يحفظ ذاكرة المظللات المؤقتة على القرص مما يقلل من التقطيع في عمليات التشغيل اللاحقة.\n\nاتركه مفعلا إذا لم تكن متأكدا.", + "ResolutionScaleTooltip": "يضاعف دقة عرض اللعبة.\n\nقد لا تعمل بعض الألعاب مع هذا وتبدو منقطة حتى عند زيادة الدقة؛ بالنسبة لهذه الألعاب، قد تحتاج إلى العثور على تعديلات تزيل تنعيم الحواف أو تزيد من دقة العرض الداخلي. لاستخدام الأخير، من المحتمل أن ترغب في تحديد أصلي.\n\nيمكن تغيير هذا الخيار أثناء تشغيل اللعبة بالنقر فوق \"تطبيق\" أدناه؛ يمكنك ببساطة تحريك نافذة الإعدادات جانبًا والتجربة حتى تجد المظهر المفضل للعبة.\n\nضع في اعتبارك أن 4x مبالغة في أي إعداد تقريبًا.", + "ResolutionScaleEntryTooltip": "مقياس دقة النقطة العائمة، مثل 1.5. من المرجح أن تتسبب المقاييس غير المتكاملة في حدوث مشكلات أو تعطل.", + "AnisotropyTooltip": "مستوى تصفية. اضبط على تلقائي لاستخدام القيمة التي تطلبها اللعبة.", + "AspectRatioTooltip": "يتم تطبيق نسبة العرض إلى الارتفاع على نافذة العارض.\n\nقم بتغيير هذا فقط إذا كنت تستخدم تعديل نسبة العرض إلى الارتفاع للعبتك، وإلا سيتم تمديد الرسومات.\n\nاتركه16:9 إذا لم تكن متأكدا.", + "ShaderDumpPathTooltip": "مسار تفريغ المظللات", + "FileLogTooltip": "حفظ تسجيل وحدة التحكم إلى ملف سجل على القرص. لا يؤثر على الأداء.", + "StubLogTooltip": "طباعة رسائل سجل stub في وحدة التحكم. لا يؤثر على الأداء.", + "InfoLogTooltip": "طباعة رسائل سجل المعلومات في وحدة التحكم. لا يؤثر على الأداء.", + "WarnLogTooltip": "طباعة رسائل سجل التحذير في وحدة التحكم. لا يؤثر على الأداء.", + "ErrorLogTooltip": "طباعة رسائل سجل الأخطاء في وحدة التحكم. لا يؤثر على الأداء.", + "TraceLogTooltip": "طباعة رسائل سجل التتبع في وحدة التحكم. لا يؤثر على الأداء.", + "GuestLogTooltip": "طباعة رسائل سجل الضيف في وحدة التحكم. لا يؤثر على الأداء.", + "FileAccessLogTooltip": "طباعة رسائل سجل الوصول إلى الملفات في وحدة التحكم.", + "FSAccessLogModeTooltip": "تمكين إخراج سجل الوصول إلى نظام الملفات إلى وحدة التحكم. الأوضاع الممكنة هي 0-3", + "DeveloperOptionTooltip": "استخدمه بعناية", + "OpenGlLogLevel": "يتطلب تمكين مستويات السجل المناسبة", + "DebugLogTooltip": "طباعة رسائل سجل التصحيح في وحدة التحكم.\n\nاستخدم هذا فقط إذا طلب منك أحد الموظفين تحديدًا ذلك، لأنه سيجعل من الصعب قراءة السجلات وسيؤدي إلى تدهور أداء المحاكي.", + "LoadApplicationFileTooltip": "افتح مستكشف الملفات لاختيار ملف متوافق مع سويتش لتحميله", + "LoadApplicationFolderTooltip": "افتح مستكشف الملفات لاختيار تطبيق متوافق مع سويتش للتحميل", + "OpenRyujinxFolderTooltip": "فتح مجلد نظام ملفات ريوجينكس", + "OpenRyujinxLogsTooltip": "يفتح المجلد الذي تتم كتابة السجلات إليه", + "ExitTooltip": "الخروج من ريوجينكس", + "OpenSettingsTooltip": "فتح نافذة الإعدادات", + "OpenProfileManagerTooltip": "فتح نافذة إدارة الملفات الشخصية للمستخدمين", + "StopEmulationTooltip": "إيقاف محاكاة اللعبة الحالية والعودة إلى اختيار اللعبة", + "CheckUpdatesTooltip": "التحقق من وجود تحديثات لريوجينكس", + "OpenAboutTooltip": "فتح حول النافذة", + "GridSize": "حجم الشبكة", + "GridSizeTooltip": "تغيير حجم عناصر الشبكة", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "البرتغالية البرازيلية", + "AboutRyujinxContributorsButtonHeader": "رؤية جميع المساهمين", + "SettingsTabSystemAudioVolume": "مستوى الصوت:", + "AudioVolumeTooltip": "تغيير مستوى الصوت", + "SettingsTabSystemEnableInternetAccess": "الوصول إلى إنترنت كضيف/وضع LAN", + "EnableInternetAccessTooltip": "للسماح للتطبيق الذي تمت محاكاته بالاتصال بالإنترنت.\n\nيمكن للألعاب التي تحتوي على وضع LAN الاتصال ببعضها البعض عند تمكين ذلك وتوصيل الأنظمة بنفس نقطة الوصول. وهذا يشمل الأجهزة الحقيقية أيضا.\n\nلا يسمح بالاتصال بخوادم نينتندو. قد يتسبب في حدوث عطل في بعض الألعاب التي تحاول الاتصال بالإنترنت.\n\nاتركه معطلا إذا لم تكن متأكدا.", + "GameListContextMenuManageCheatToolTip": "إدارة الغش", + "GameListContextMenuManageCheat": "إدارة الغش", + "GameListContextMenuManageModToolTip": "إدارة التعديلات", + "GameListContextMenuManageMod": "إدارة التعديلات", + "ControllerSettingsStickRange": "نطاق:", + "DialogStopEmulationTitle": "ريوجينكس - إيقاف المحاكاة", + "DialogStopEmulationMessage": "هل أنت متأكد أنك تريد إيقاف المحاكاة؟", + "SettingsTabCpu": "المعالج", + "SettingsTabAudio": "الصوت", + "SettingsTabNetwork": "الشبكة", + "SettingsTabNetworkConnection": "اتصال الشبكة", + "SettingsTabCpuCache": "ذاكرة المعالج المؤقت", + "SettingsTabCpuMemory": "وضع المعالج", + "DialogUpdaterFlatpakNotSupportedMessage": "الرجاء تحديث ريوجينكس عبر فلات هاب.", + "UpdaterDisabledWarningTitle": "المحدث معطل!", + "ControllerSettingsRotate90": "تدوير 90 درجة في اتجاه عقارب الساعة", + "IconSize": "حجم الأيقونة", + "IconSizeTooltip": "تغيير حجم أيقونات اللعبة", + "MenuBarOptionsShowConsole": "عرض وحدة التحكم", + "ShaderCachePurgeError": "حدث خطأ أثناء تنظيف ذاكرة المظللات المؤقتة في {0}: {1}", + "UserErrorNoKeys": "المفاتيح غير موجودة", + "UserErrorNoFirmware": "لم يتم العثور على البرنامج الثابت", + "UserErrorFirmwareParsingFailed": "خطأ في تحليل البرنامج الثابت", + "UserErrorApplicationNotFound": "التطبيق غير موجود", + "UserErrorUnknown": "خطأ غير معروف", + "UserErrorUndefined": "خطأ غير محدد", + "UserErrorNoKeysDescription": "لم يتمكن ريوجينكس من العثور على ملف 'prod.keys' الخاص بك", + "UserErrorNoFirmwareDescription": "لم يتمكن ريوجينكس من العثور على أية برامج ثابتة مثبتة", + "UserErrorFirmwareParsingFailedDescription": "لم يتمكن ريوجينكس من تحليل البرامج الثابتة المتوفرة. يحدث هذا عادة بسبب المفاتيح القديمة.", + "UserErrorApplicationNotFoundDescription": "تعذر على ريوجينكس العثور على تطبيق صالح في المسار المحدد.", + "UserErrorUnknownDescription": "حدث خطأ غير معروف!", + "UserErrorUndefinedDescription": "حدث خطأ غير محدد! لا ينبغي أن يحدث هذا، يرجى الاتصال بمطور!", + "OpenSetupGuideMessage": "فتح دليل الإعداد", + "NoUpdate": "لا يوجد تحديث", + "TitleUpdateVersionLabel": "الإصدار: {0}", + "RyujinxInfo": "ريوجينكس - معلومات", + "RyujinxConfirm": "ريوجينكس - تأكيد", + "FileDialogAllTypes": "كل الأنواع", + "Never": "مطلقا", + "SwkbdMinCharacters": "يجب أن يبلغ طوله {0} حرفا على الأقل", + "SwkbdMinRangeCharacters": "يجب أن يتكون من {0}-{1} حرفا", + "SoftwareKeyboard": "لوحة المفاتيح البرمجية", + "SoftwareKeyboardModeNumeric": "يجب أن يكون 0-9 أو '.' فقط", + "SoftwareKeyboardModeAlphabet": "يجب أن تكون الأحرف غير CJK فقط", + "SoftwareKeyboardModeASCII": "يجب أن يكون نص ASCII فقط", + "ControllerAppletControllers": "وحدات التحكم المدعومة:", + "ControllerAppletPlayers": "اللاعبين:", + "ControllerAppletDescription": "الإعدادات الحالية غير صالحة. افتح الإعدادات وأعد تكوين المدخلات الخاصة بك.", + "ControllerAppletDocked": "تم ضبط وضع تركيب بالمنصة. يجب تعطيل التحكم المحمول.", + "UpdaterRenaming": "إعادة تسمية الملفات القديمة...", + "UpdaterRenameFailed": "المحدث غير قادر على إعادة تسمية الملف: {0}", + "UpdaterAddingFiles": "إضافة ملفات جديدة...", + "UpdaterExtracting": "استخراج التحديث...", + "UpdaterDownloading": "تحميل التحديث...", + "Game": "لعبة", + "Docked": "تركيب بالمنصة", + "Handheld": "محمول", + "ConnectionError": "خطأ في الاتصال", + "AboutPageDeveloperListMore": "{0} والمزيد...", + "ApiError": "خطأ في API.", + "LoadingHeading": "جاري تحميل {0}", + "CompilingPPTC": "تجميع الـ‫(PPTC)", + "CompilingShaders": "تجميع المظللات", + "AllKeyboards": "كل لوحات المفاتيح", + "OpenFileDialogTitle": "حدد ملف مدعوم لفتحه", + "OpenFolderDialogTitle": "حدد مجلدا يحتوي على لعبة غير مضغوطة", + "AllSupportedFormats": "كل التنسيقات المدعومة", + "RyujinxUpdater": "محدث ريوجينكس", + "SettingsTabHotkeys": "مفاتيح الاختصار في لوحة المفاتيح", + "SettingsTabHotkeysHotkeys": "مفاتيح الاختصار في لوحة المفاتيح", + "SettingsTabHotkeysToggleVsyncHotkey": "تبديل المزامنة العمودية:", + "SettingsTabHotkeysScreenshotHotkey": "لقطة الشاشة:", + "SettingsTabHotkeysShowUiHotkey": "عرض واجهة المستخدم:", + "SettingsTabHotkeysPauseHotkey": "إيقاف مؤقت:", + "SettingsTabHotkeysToggleMuteHotkey": "كتم:", + "ControllerMotionTitle": "إعدادات التحكم بالحركة", + "ControllerRumbleTitle": "إعدادات الهزاز", + "SettingsSelectThemeFileDialogTitle": "حدد ملف السمة", + "SettingsXamlThemeFile": "ملف سمة Xaml", + "AvatarWindowTitle": "إدارة الحسابات - الصورة الرمزية", + "Amiibo": "أميبو", + "Unknown": "غير معروف", + "Usage": "الاستخدام", + "Writable": "قابل للكتابة", + "SelectDlcDialogTitle": "حدد ملفات المحتوي الإضافي", + "SelectUpdateDialogTitle": "حدد ملفات التحديث", + "SelectModDialogTitle": "حدد مجلد التعديل", + "UserProfileWindowTitle": "مدير الملفات الشخصية للمستخدمين", + "CheatWindowTitle": "مدير الغش", + "DlcWindowTitle": "إدارة المحتوى القابل للتنزيل لـ {0} ({1})", + "ModWindowTitle": "إدارة التعديلات لـ {0} ({1})", + "UpdateWindowTitle": "مدير تحديث العنوان", + "CheatWindowHeading": "الغش متوفر لـ {0} [{1}]", + "BuildId": "معرف البناء:", + "DlcWindowHeading": "المحتويات القابلة للتنزيل {0}", + "ModWindowHeading": "{0} تعديل", + "UserProfilesEditProfile": "تعديل المحدد", + "Cancel": "إلغاء", + "Save": "حفظ", + "Discard": "تجاهل", + "Paused": "متوقف مؤقتا", + "UserProfilesSetProfileImage": "تعيين صورة الملف الشخصي", + "UserProfileEmptyNameError": "الاسم مطلوب", + "UserProfileNoImageError": "يجب تعيين صورة الملف الشخصي", + "GameUpdateWindowHeading": "إدارة التحديثات لـ {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "زيادة الدقة:", + "SettingsTabHotkeysResScaleDownHotkey": "خفض الدقة:", + "UserProfilesName": "الاسم:", + "UserProfilesUserId": "معرف المستخدم:", + "SettingsTabGraphicsBackend": "خلفية الرسومات", + "SettingsTabGraphicsBackendTooltip": "حدد الواجهة الخلفية للرسومات التي سيتم استخدامها في المحاكي.\n\nيعد برنامج فولكان أفضل بشكل عام لجميع بطاقات الرسومات الحديثة، طالما أن برامج التشغيل الخاصة بها محدثة. يتميز فولكان أيضا بتجميع مظللات أسرع (أقل تقطيعا) على جميع بائعي وحدات معالجة الرسومات.\n\nقد يحقق أوبن جي أل نتائج أفضل على وحدات معالجة الرسومات إنفيديا القديمة، أو على وحدات معالجة الرسومات إي إم دي القديمة على لينكس، أو على وحدات معالجة الرسومات ذات ذاكرة الوصول العشوائي للفيديوالأقل، على الرغم من أن تعثرات تجميع المظللات ستكون أكبر.\n\nاضبط على فولكان إذا لم تكن متأكدا. اضبط على أوبن جي أل إذا كانت وحدة معالجة الرسومات الخاصة بك لا تدعم فولكان حتى مع أحدث برامج تشغيل الرسومات.", + "SettingsEnableTextureRecompression": "تمكين إعادة ضغط التكستر", + "SettingsEnableTextureRecompressionTooltip": "يضغط تكستر ASTC من أجل تقليل استخدام ذاكرة الوصول العشوائي للفيديو.\n\nتتضمن الألعاب التي تستخدم تنسيق النسيج هذا Astral Chain وBayonetta 3 وFire Emblem Engage وMetroid Prime Remastered وSuper Mario Bros. Wonder وThe Legend of Zelda: Tears of the Kingdom.\n\nمن المحتمل أن تتعطل بطاقات الرسومات التي تحتوي على 4 جيجا بايت من ذاكرة الوصول العشوائي للفيديو أو أقل في مرحلة ما أثناء تشغيل هذه الألعاب.\n\nقم بالتمكين فقط في حالة نفاد ذاكرة الوصول العشوائي للفيديو في الألعاب المذكورة أعلاه. اتركه معطلا إذا لم تكن متأكدا.", + "SettingsTabGraphicsPreferredGpu": "وحدة معالجة الرسوميات المفضلة", + "SettingsTabGraphicsPreferredGpuTooltip": "حدد بطاقة الرسومات التي سيتم استخدامها مع الواجهة الخلفية لرسومات فولكان.\n\nلا يؤثر على وحدة معالجة الرسومات التي سيستخدمها أوبن جي أل.\n\nاضبط على وحدة معالجة الرسومات التي تم وضع علامة عليها كـ \"dGPU\" إذا لم تكن متأكدًا. إذا لم يكن هناك واحد، اتركه.", + "SettingsAppRequiredRestartMessage": "مطلوب إعادة تشغيل ريوجينكس", + "SettingsGpuBackendRestartMessage": "تم تعديل إعدادات الواجهة الخلفية للرسومات أو وحدة معالجة الرسومات. سيتطلب هذا إعادة التشغيل ليتم تطبيقه", + "SettingsGpuBackendRestartSubMessage": "\n\nهل تريد إعادة التشغيل الآن؟", + "RyujinxUpdaterMessage": "هل تريد تحديث ريوجينكس إلى أحدث إصدار؟", + "SettingsTabHotkeysVolumeUpHotkey": "زيادة مستوى الصوت:", + "SettingsTabHotkeysVolumeDownHotkey": "خفض مستوى الصوت:", + "SettingsEnableMacroHLE": "تمكين Maro HLE", + "SettingsEnableMacroHLETooltip": "محاكاة عالية المستوى لكود مايكرو وحدة معالجة الرسوميات.\n\nيعمل على تحسين الأداء، ولكنه قد يسبب خللا رسوميا في بعض الألعاب.\n\nاتركه مفعلا إذا لم تكن متأكدا.", + "SettingsEnableColorSpacePassthrough": "عبور مساحة اللون", + "SettingsEnableColorSpacePassthroughTooltip": "يوجه واجهة فولكان الخلفية لتمرير معلومات الألوان دون تحديد مساحة اللون. بالنسبة للمستخدمين الذين لديهم شاشات ذات نطاق واسع، قد يؤدي ذلك إلى الحصول على ألوان أكثر حيوية، على حساب صحة الألوان.", + "VolumeShort": "مستوى", + "UserProfilesManageSaves": "إدارة الحفظ", + "DeleteUserSave": "هل تريد حذف حفظ المستخدم لهذه اللعبة؟", + "IrreversibleActionNote": "هذا الإجراء لا يمكن التراجع عنه.", + "SaveManagerHeading": "إدارة الحفظ لـ {0} ({1})", + "SaveManagerTitle": "مدير الحفظ", + "Name": "الاسم", + "Size": "الحجم", + "Search": "بحث", + "UserProfilesRecoverLostAccounts": "استعادة الحسابات المفقودة", + "Recover": "استعادة", + "UserProfilesRecoverHeading": "تم العثور على حفظ للحسابات التالية", + "UserProfilesRecoverEmptyList": "لا توجد ملفات شخصية لاستردادها", + "GraphicsAATooltip": "يتم تطبيق تنعيم الحواف على عرض اللعبة.\n\nسوف يقوم FXAA بتعتيم معظم الصورة، بينما سيحاول SMAA العثور على حواف خشنة وتنعيمها.\n\nلا ينصح باستخدامه مع فلتر FSR لتكبير.\n\nيمكن تغيير هذا الخيار أثناء تشغيل اللعبة بالنقر فوق \"تطبيق\" أدناه؛ يمكنك ببساطة تحريك نافذة الإعدادات جانبا والتجربة حتى تجد المظهر المفضل للعبة.\n\nاتركه على لا شيء إذا لم تكن متأكدا.", + "GraphicsAALabel": "تنعيم الحواف:", + "GraphicsScalingFilterLabel": "فلتر التكبير:", + "GraphicsScalingFilterTooltip": "اختر فلتر التكبير الذي سيتم تطبيقه عند استخدام مقياس الدقة.\n\nيعمل Bilinear بشكل جيد مع الألعاب ثلاثية الأبعاد وهو خيار افتراضي آمن.\n\nيوصى باستخدام Nearest لألعاب البكسل الفنية.\n\nFSR 1.0 هو مجرد مرشح توضيحي، ولا ينصح باستخدامه مع FXAA أو SMAA.\n\nيمكن تغيير هذا الخيار أثناء تشغيل اللعبة بالنقر فوق \"تطبيق\" أدناه؛ يمكنك ببساطة تحريك نافذة الإعدادات جانبا والتجربة حتى تجد المظهر المفضل للعبة.\n\nاتركه على Bilinear إذا لم تكن متأكدا.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "المستوى", + "GraphicsScalingFilterLevelTooltip": "اضبط مستوى وضوح FSR 1.0. الأعلى هو أكثر وضوحا.", + "SmaaLow": "SMAA منخفض", + "SmaaMedium": "SMAA متوسط", + "SmaaHigh": "SMAA عالي", + "SmaaUltra": "SMAA فائق", + "UserEditorTitle": "تعديل المستخدم", + "UserEditorTitleCreate": "إنشاء مستخدم", + "SettingsTabNetworkInterface": "واجهة الشبكة:", + "NetworkInterfaceTooltip": "واجهة الشبكة مستخدمة لميزات LAN/LDN.\n\nبالاشتراك مع VPN أو XLink Kai ولعبة تدعم LAN، يمكن استخدامها لتزييف اتصال الشبكة نفسها عبر الإنترنت.\n\nاتركه على الافتراضي إذا لم تكن متأكدا.", + "NetworkInterfaceDefault": "افتراضي", + "PackagingShaders": "تعبئة المظللات", + "AboutChangelogButton": "عرض سجل التغييرات على غيت هاب", + "AboutChangelogButtonTooltipMessage": "انقر لفتح سجل التغيير لهذا الإصدار في متصفحك الافتراضي.", + "SettingsTabNetworkMultiplayer": "لعب جماعي", + "MultiplayerMode": "الوضع:", + "MultiplayerModeTooltip": "تغيير وضع LDN متعدد اللاعبين.\n\nسوف يقوم LdnMitm بتعديل وظيفة اللعب المحلية/اللاسلكية المحلية في الألعاب لتعمل كما لو كانت شبكة LAN، مما يسمح باتصالات الشبكة المحلية نفسها مع محاكيات ريوجينكس الأخرى وأجهزة نينتندو سويتش المخترقة التي تم تثبيت وحدة ldn_mitm عليها.\n\nيتطلب وضع اللاعبين المتعددين أن يكون جميع اللاعبين على نفس إصدار اللعبة (على سبيل المثال، يتعذر على الإصدار 13.0.1 من سوبر سماش برذرز ألتميت الاتصال بالإصدار 13.0.0).\n\nاتركه معطلا إذا لم تكن متأكدا.", + "MultiplayerModeDisabled": "معطل", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json new file mode 100644 index 00000000..40129319 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -0,0 +1,780 @@ +{ + "Language": "Deutsch", + "MenuBarFileOpenApplet": "Öffne Anwendung", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Öffnet das Mii-Editor-Applet im Standalone-Modus", + "SettingsTabInputDirectMouseAccess": "Direkter Mauszugriff", + "SettingsTabSystemMemoryManagerMode": "Speichermanagermodus:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (schnell)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host ungeprüft (am schnellsten, unsicher)", + "SettingsTabSystemUseHypervisor": "Hypervisor verwenden", + "MenuBarFile": "_Datei", + "MenuBarFileOpenFromFile": "Datei _öffnen", + "MenuBarFileOpenUnpacked": "_Entpacktes Spiel öffnen", + "MenuBarFileOpenEmuFolder": "Ryujinx-Ordner öffnen", + "MenuBarFileOpenLogsFolder": "Logs-Ordner öffnen", + "MenuBarFileExit": "_Beenden", + "MenuBarOptions": "_Optionen", + "MenuBarOptionsToggleFullscreen": "Vollbild", + "MenuBarOptionsStartGamesInFullscreen": "Spiele im Vollbildmodus starten", + "MenuBarOptionsStopEmulation": "Emulation beenden", + "MenuBarOptionsSettings": "_Einstellungen", + "MenuBarOptionsManageUserProfiles": "_Benutzerprofile verwalten", + "MenuBarActions": "_Aktionen", + "MenuBarOptionsSimulateWakeUpMessage": "Aufwachnachricht simulieren", + "MenuBarActionsScanAmiibo": "Amiibo scannen", + "MenuBarTools": "_Tools", + "MenuBarToolsInstallFirmware": "Firmware installieren", + "MenuBarFileToolsInstallFirmwareFromFile": "Firmware von einer XCI- oder einer ZIP-Datei installieren", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Firmware aus einem Verzeichnis installieren", + "MenuBarToolsManageFileTypes": "Dateitypen verwalten", + "MenuBarToolsInstallFileTypes": "Dateitypen installieren", + "MenuBarToolsUninstallFileTypes": "Dateitypen deinstallieren", + "MenuBarView": "_Ansicht", + "MenuBarViewWindow": "Fenstergröße", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "_Hilfe", + "MenuBarHelpCheckForUpdates": "Nach Updates suchen", + "MenuBarHelpAbout": "Über Ryujinx", + "MenuSearch": "Suchen...", + "GameListHeaderFavorite": "Favorit", + "GameListHeaderIcon": "Icon", + "GameListHeaderApplication": "Name", + "GameListHeaderDeveloper": "Entwickler", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Spielzeit", + "GameListHeaderLastPlayed": "Zuletzt gespielt", + "GameListHeaderFileExtension": "Dateiformat", + "GameListHeaderFileSize": "Dateigröße", + "GameListHeaderPath": "Pfad", + "GameListContextMenuOpenUserSaveDirectory": "Spielstand-Verzeichnis öffnen", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den Benutzer-Spielstand beinhaltet", + "GameListContextMenuOpenDeviceSaveDirectory": "Benutzer-Geräte-Verzeichnis öffnen", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den Geräte-Spielstände beinhaltet", + "GameListContextMenuOpenBcatSaveDirectory": "Benutzer-BCAT-Vezeichnis öffnen", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den BCAT-Cache des Spiels beinhaltet", + "GameListContextMenuManageTitleUpdates": "Verwalte Spiel-Updates", + "GameListContextMenuManageTitleUpdatesToolTip": "Öffnet den Spiel-Update-Manager", + "GameListContextMenuManageDlc": "Verwalten von DLC", + "GameListContextMenuManageDlcToolTip": "Öffnet den DLC-Manager", + "GameListContextMenuCacheManagement": "Cache-Verwaltung", + "GameListContextMenuCacheManagementPurgePptc": "PPTC als ungültig markieren", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Markiert den PPTC als ungültig, sodass dieser beim nächsten Spielstart neu erstellt wird", + "GameListContextMenuCacheManagementPurgeShaderCache": "Shader Cache löschen", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Löscht den Shader-Cache der Anwendung", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC-Verzeichnis öffnen", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Öffnet das Verzeichnis, das den PPTC-Cache der Anwendung beinhaltet", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Shader-Cache-Verzeichnis öffnen", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Öffnet das Verzeichnis, das den Shader Cache der Anwendung beinhaltet", + "GameListContextMenuExtractData": "Daten extrahieren", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrahiert das ExeFS aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrahiert das RomFS aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrahiert das Logo aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "GameListContextMenuCreateShortcut": "Erstelle Anwendungsverknüpfung", + "GameListContextMenuCreateShortcutToolTip": "Erstelle eine Desktop-Verknüpfung die die gewählte Anwendung startet", + "GameListContextMenuCreateShortcutToolTipMacOS": "Erstellen Sie eine Verknüpfung im MacOS-Programme-Ordner, die die ausgewählte Anwendung startet", + "GameListContextMenuOpenModsDirectory": "Mod-Verzeichnis öffnen", + "GameListContextMenuOpenModsDirectoryToolTip": "Öffnet das Verzeichnis, welches Mods für die Spiele beinhaltet", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere-Mod-Verzeichnis öffnen", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Öffnet das alternative SD-Karten-Atmosphere-Verzeichnis, das die Mods der Anwendung enthält. Dieser Ordner ist nützlich für Mods, die für echte Hardware erstellt worden sind.", + "StatusBarGamesLoaded": "{0}/{1} Spiele geladen", + "StatusBarSystemVersion": "Systemversion: {0}", + "LinuxVmMaxMapCountDialogTitle": "Niedriges Limit für Speicherzuordnungen erkannt", + "LinuxVmMaxMapCountDialogTextPrimary": "Möchtest Du den Wert von vm.max_map_count auf {0} erhöhen", + "LinuxVmMaxMapCountDialogTextSecondary": "Einige Spiele könnten versuchen, mehr Speicherzuordnungen zu erstellen, als derzeit erlaubt. Ryujinx wird abstürzen, sobald dieses Limit überschritten wird.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Ja, bis zum nächsten Neustart", + "LinuxVmMaxMapCountDialogButtonPersistent": "Ja, permanent", + "LinuxVmMaxMapCountWarningTextPrimary": "Maximale Anzahl an Speicherzuordnungen ist niedriger als empfohlen.", + "LinuxVmMaxMapCountWarningTextSecondary": "Der aktuelle Wert von vm.max_map_count ({0}) ist kleiner als {1}. Einige Spiele könnten versuchen, mehr Speicherzuordnungen zu erstellen, als derzeit erlaubt. Ryujinx wird abstürzen, sobald dieses Limit überschritten wird.\n\nDu kannst das Limit entweder manuell erhöhen oder pkexec installieren, damit Ryujinx Dir dabei hilft.", + "Settings": "Einstellungen", + "SettingsTabGeneral": "Oberfläche", + "SettingsTabGeneralGeneral": "Allgemein", + "SettingsTabGeneralEnableDiscordRichPresence": "Aktiviere die Statusanzeige für Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Beim Start nach Updates suchen", + "SettingsTabGeneralShowConfirmExitDialog": "Zeige den \"Beenden bestätigen\"-Dialog", + "SettingsTabGeneralRememberWindowState": "Fenstergröße/-position merken", + "SettingsTabGeneralHideCursor": "Mauszeiger ausblenden", + "SettingsTabGeneralHideCursorNever": "Niemals", + "SettingsTabGeneralHideCursorOnIdle": "Mauszeiger bei Inaktivität ausblenden", + "SettingsTabGeneralHideCursorAlways": "Immer", + "SettingsTabGeneralGameDirectories": "Spielverzeichnisse", + "SettingsTabGeneralAdd": "Hinzufügen", + "SettingsTabGeneralRemove": "Entfernen", + "SettingsTabSystem": "System", + "SettingsTabSystemCore": "Kern", + "SettingsTabSystemSystemRegion": "Systemregion:", + "SettingsTabSystemSystemRegionJapan": "Japan", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australien", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Korea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "Systemsprache:", + "SettingsTabSystemSystemLanguageJapanese": "Japanisch", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerikanisches Englisch", + "SettingsTabSystemSystemLanguageFrench": "Französisch", + "SettingsTabSystemSystemLanguageGerman": "Deutsch", + "SettingsTabSystemSystemLanguageItalian": "Italienisch", + "SettingsTabSystemSystemLanguageSpanish": "Spanisch", + "SettingsTabSystemSystemLanguageChinese": "Chinesisch", + "SettingsTabSystemSystemLanguageKorean": "Koreanisch", + "SettingsTabSystemSystemLanguageDutch": "Niederländisch", + "SettingsTabSystemSystemLanguagePortuguese": "Portugiesisch", + "SettingsTabSystemSystemLanguageRussian": "Russisch", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanesisch", + "SettingsTabSystemSystemLanguageBritishEnglish": "Britisches Englisch", + "SettingsTabSystemSystemLanguageCanadianFrench": "Kanadisches Französisch", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Lateinamerikanisches Spanisch", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Vereinfachtes Chinesisch", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Traditionelles Chinesisch", + "SettingsTabSystemSystemTimeZone": "System-Zeitzone:", + "SettingsTabSystemSystemTime": "Systemzeit:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Integritätsprüfung", + "SettingsTabSystemAudioBackend": "Audio-Backend:", + "SettingsTabSystemAudioBackendDummy": "Ohne Funktion", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Kann Fehler verursachen)", + "SettingsTabSystemExpandDramSize": "Erweitere DRAM Größe auf 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Ignoriere fehlende Dienste", + "SettingsTabGraphics": "Grafik", + "SettingsTabGraphicsAPI": "Grafik-API", + "SettingsTabGraphicsEnableShaderCache": "Shader-Cache aktivieren", + "SettingsTabGraphicsAnisotropicFiltering": "Anisotrope Filterung:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Auflösungsskalierung:", + "SettingsTabGraphicsResolutionScaleCustom": "Benutzerdefiniert (nicht empfohlen)", + "SettingsTabGraphicsResolutionScaleNative": "Nativ (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Nicht empfohlen)", + "SettingsTabGraphicsAspectRatio": "Bildseitenverhältnis:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "An Fenster anpassen", + "SettingsTabGraphicsDeveloperOptions": "Optionen für Entwickler", + "SettingsTabGraphicsShaderDumpPath": "Grafik-Shader-Dump-Pfad:", + "SettingsTabLogging": "Logs", + "SettingsTabLoggingLogging": "Logs", + "SettingsTabLoggingEnableLoggingToFile": "Protokollierung in Datei aktivieren", + "SettingsTabLoggingEnableStubLogs": "Aktiviere Stub-Logs", + "SettingsTabLoggingEnableInfoLogs": "Aktiviere Info-Logs", + "SettingsTabLoggingEnableWarningLogs": "Aktiviere Warn-Logs", + "SettingsTabLoggingEnableErrorLogs": "Aktiviere Fehler-Logs", + "SettingsTabLoggingEnableTraceLogs": "Aktiviere Trace-Logs", + "SettingsTabLoggingEnableGuestLogs": "Aktiviere Gast-Logs", + "SettingsTabLoggingEnableFsAccessLogs": "Aktiviere Fs Zugriff-Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Globaler Zugriff-Log-Modus:", + "SettingsTabLoggingDeveloperOptions": "Entwickleroptionen", + "SettingsTabLoggingDeveloperOptionsNote": "ACHTUNG: Wird die Leistung reduzieren", + "SettingsTabLoggingGraphicsBackendLogLevel": "Protokollstufe des Grafik-Backends:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Keine", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Fehler", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Verlangsamungen", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Alle", + "SettingsTabLoggingEnableDebugLogs": "Aktiviere Debug-Log", + "SettingsTabInput": "Eingabe", + "SettingsTabInputEnableDockedMode": "Angedockter Modus", + "SettingsTabInputDirectKeyboardAccess": "Direkter Tastaturzugriff", + "SettingsButtonSave": "Speichern", + "SettingsButtonClose": "Schließen", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Abbrechen", + "SettingsButtonApply": "Übernehmen", + "ControllerSettingsPlayer": "Spieler", + "ControllerSettingsPlayer1": "Spieler 1", + "ControllerSettingsPlayer2": "Spieler 2", + "ControllerSettingsPlayer3": "Spieler 3", + "ControllerSettingsPlayer4": "Spieler 4", + "ControllerSettingsPlayer5": "Spieler 5", + "ControllerSettingsPlayer6": "Spieler 6", + "ControllerSettingsPlayer7": "Spieler 7", + "ControllerSettingsPlayer8": "Spieler 8", + "ControllerSettingsHandheld": "Handheld", + "ControllerSettingsInputDevice": "Eingabegerät", + "ControllerSettingsRefresh": "Aktualisieren", + "ControllerSettingsDeviceDisabled": "Deaktiviert", + "ControllerSettingsControllerType": "Controller-Typ", + "ControllerSettingsControllerTypeHandheld": "Handheld", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Joy-Con-Paar", + "ControllerSettingsControllerTypeJoyConLeft": "Linker Joy-Con", + "ControllerSettingsControllerTypeJoyConRight": "Rechter Joy-Con", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Standard", + "ControllerSettingsLoad": "Laden", + "ControllerSettingsAdd": "Hinzufügen", + "ControllerSettingsRemove": "Entfernen", + "ControllerSettingsButtons": "Aktionstasten", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Steuerkreuz", + "ControllerSettingsDPadUp": "Hoch", + "ControllerSettingsDPadDown": "Runter", + "ControllerSettingsDPadLeft": "Links", + "ControllerSettingsDPadRight": "Rechts", + "ControllerSettingsStickButton": "Button", + "ControllerSettingsStickUp": "Hoch", + "ControllerSettingsStickDown": "Runter", + "ControllerSettingsStickLeft": "Links", + "ControllerSettingsStickRight": "Rechts", + "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickInvertXAxis": "X-Achse invertieren", + "ControllerSettingsStickInvertYAxis": "Y-Achse invertieren", + "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsLStick": "Linker Analogstick", + "ControllerSettingsRStick": "Rechter Analogstick", + "ControllerSettingsTriggersLeft": "Linker Trigger", + "ControllerSettingsTriggersRight": "Rechter Trigger", + "ControllerSettingsTriggersButtonsLeft": "Linke Schultertaste", + "ControllerSettingsTriggersButtonsRight": "Rechte Schultertaste", + "ControllerSettingsTriggers": "Trigger", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Linke Aktionstasten", + "ControllerSettingsExtraButtonsRight": "Rechte Aktionstasten", + "ControllerSettingsMisc": "Verschiedenes", + "ControllerSettingsTriggerThreshold": "Empfindlichkeit:", + "ControllerSettingsMotion": "Bewegung", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook kompatible Bewegungssteuerung", + "ControllerSettingsMotionControllerSlot": "Controller-Slot:", + "ControllerSettingsMotionMirrorInput": "Eingabe spiegeln", + "ControllerSettingsMotionRightJoyConSlot": "Rechter Joy-Con-Slot:", + "ControllerSettingsMotionServerHost": "Server Host:", + "ControllerSettingsMotionGyroSensitivity": "Gyro-Empfindlichkeit:", + "ControllerSettingsMotionGyroDeadzone": "Gyro-Deadzone:", + "ControllerSettingsSave": "Speichern", + "ControllerSettingsClose": "Schließen", + "KeyUnknown": "Unbekannt", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", + "UserProfilesSelectedUserProfile": "Ausgewähltes Profil:", + "UserProfilesSaveProfileName": "Profilname speichern", + "UserProfilesChangeProfileImage": "Profilbild ändern", + "UserProfilesAvailableUserProfiles": "Verfügbare Profile:", + "UserProfilesAddNewProfile": "Neues Profil", + "UserProfilesDelete": "Löschen", + "UserProfilesClose": "Schließen", + "ProfileNameSelectionWatermark": "Wähle einen Spitznamen", + "ProfileImageSelectionTitle": "Auswahl des Profilbildes", + "ProfileImageSelectionHeader": "Wähle ein Profilbild aus", + "ProfileImageSelectionNote": "Es kann ein eigenes Profilbild importiert werden oder ein Avatar aus der System-Firmware", + "ProfileImageSelectionImportImage": "Bilddatei importieren", + "ProfileImageSelectionSelectAvatar": "Firmware-Avatar auswählen", + "InputDialogTitle": "Eingabe-Dialog", + "InputDialogOk": "OK", + "InputDialogCancel": "Abbrechen", + "InputDialogAddNewProfileTitle": "Wähle den Profilnamen", + "InputDialogAddNewProfileHeader": "Bitte gebe einen Profilnamen ein", + "InputDialogAddNewProfileSubtext": "(Maximale Länge: {0})", + "AvatarChoose": "Bestätigen", + "AvatarSetBackgroundColor": "Hintergrundfarbe auswählen", + "AvatarClose": "Schließen", + "ControllerSettingsLoadProfileToolTip": "Lädt ein Profil", + "ControllerSettingsAddProfileToolTip": "Fügt ein Profil hinzu", + "ControllerSettingsRemoveProfileToolTip": "Entfernt ein Profil", + "ControllerSettingsSaveProfileToolTip": "Speichert ein Profil", + "MenuBarFileToolsTakeScreenshot": "Screenshot aufnehmen", + "MenuBarFileToolsHideUi": "Oberfläche ausblenden", + "GameListContextMenuRunApplication": "Anwendung ausführen", + "GameListContextMenuToggleFavorite": "Als Favoriten hinzufügen/entfernen", + "GameListContextMenuToggleFavoriteToolTip": "Aktiviert den Favoriten-Status des Spiels", + "SettingsTabGeneralTheme": "Design:", + "SettingsTabGeneralThemeDark": "Dunkel", + "SettingsTabGeneralThemeLight": "Hell", + "ControllerSettingsConfigureGeneral": "Konfigurieren", + "ControllerSettingsRumble": "Vibration", + "ControllerSettingsRumbleStrongMultiplier": "Starker Vibrations-Multiplikator", + "ControllerSettingsRumbleWeakMultiplier": "Schwacher Vibrations-Multiplikator", + "DialogMessageSaveNotAvailableMessage": "Es existieren keine Speicherdaten für {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Sollen Speicherdaten für dieses Spiel erstellt werden?", + "DialogConfirmationTitle": "Ryujinx - Bestätigung", + "DialogUpdaterTitle": "Ryujinx - Updater", + "DialogErrorTitle": "Ryujinx - Fehler", + "DialogWarningTitle": "Ryujinx - Warnung", + "DialogExitTitle": "Ryujinx - Beenden", + "DialogErrorMessage": "Ein Fehler ist aufgetreten", + "DialogExitMessage": "Ryujinx wirklich schließen?", + "DialogExitSubMessage": "Alle nicht gespeicherten Daten gehen verloren!", + "DialogMessageCreateSaveErrorMessage": "Es ist ein Fehler bei der Erstellung der angegebenen Speicherdaten aufgetreten: {0}", + "DialogMessageFindSaveErrorMessage": "Es ist ein Fehler beim Suchen der angegebenen Speicherdaten aufgetreten: {0}", + "FolderDialogExtractTitle": "Wähle den Ordner, in welchen die Dateien entpackt werden sollen", + "DialogNcaExtractionMessage": "Extrahiert {0} abschnitt von {1}...", + "DialogNcaExtractionTitle": "Ryujinx - NCA-Abschnitt-Extraktor", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Extraktion fehlgeschlagen. Der Hauptheader der NCA war in der ausgewählten Datei nicht vorhanden.", + "DialogNcaExtractionCheckLogErrorMessage": "Extraktion fehlgeschlagen. Überprüfe die Logs für weitere Informationen.", + "DialogNcaExtractionSuccessMessage": "Extraktion erfolgreich abgeschlossen.", + "DialogUpdaterConvertFailedMessage": "Die Konvertierung der aktuellen Ryujinx-Version ist fehlgeschlagen.", + "DialogUpdaterCancelUpdateMessage": "Update wird abgebrochen!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Es wird bereits die aktuellste Version von Ryujinx benutzt", + "DialogUpdaterFailedToGetVersionMessage": "Beim Versuch, Veröffentlichungs-Info von GitHub Release zu erhalten, ist ein Fehler aufgetreten. Dies kann aufgrund einer neuen Veröffentlichung, die gerade von GitHub Actions kompiliert wird, verursacht werden.", + "DialogUpdaterConvertFailedGithubMessage": "Fehler beim Konvertieren der erhaltenen Ryujinx-Version von GitHub Release.", + "DialogUpdaterDownloadingMessage": "Update wird heruntergeladen...", + "DialogUpdaterExtractionMessage": "Update wird entpackt...", + "DialogUpdaterRenamingMessage": "Update wird umbenannt...", + "DialogUpdaterAddingFilesMessage": "Update wird hinzugefügt...", + "DialogUpdaterCompleteMessage": "Update abgeschlossen!", + "DialogUpdaterRestartMessage": "Ryujinx jetzt neu starten?", + "DialogUpdaterNoInternetMessage": "Es besteht keine Verbindung mit dem Internet!", + "DialogUpdaterNoInternetSubMessage": "Bitte vergewissern, dass eine funktionierende Internetverbindung existiert!", + "DialogUpdaterDirtyBuildMessage": "Inoffizielle Versionen von Ryujinx können nicht aktualisiert werden", + "DialogUpdaterDirtyBuildSubMessage": "Lade Ryujinx bitte von hier herunter, um eine unterstützte Version zu erhalten: https://ryujinx.org/", + "DialogRestartRequiredMessage": "Neustart erforderlich", + "DialogThemeRestartMessage": "Das Design wurde gespeichert. Ein Neustart ist erforderlich, um das Design anzuwenden.", + "DialogThemeRestartSubMessage": "Jetzt neu starten?", + "DialogFirmwareInstallEmbeddedMessage": "Die in diesem Spiel enthaltene Firmware installieren? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Es wurde keine installierte Firmware gefunden, aber Ryujinx konnte die Firmware {0} aus dem bereitgestellten Spiel installieren.\nRyujinx wird nun gestartet.", + "DialogFirmwareNoFirmwareInstalledMessage": "Keine Firmware installiert", + "DialogFirmwareInstalledMessage": "Firmware {0} wurde installiert", + "DialogInstallFileTypesSuccessMessage": "Dateitypen erfolgreich installiert!", + "DialogInstallFileTypesErrorMessage": "Dateitypen konnten nicht installiert werden.", + "DialogUninstallFileTypesSuccessMessage": "Dateitypen erfolgreich deinstalliert!", + "DialogUninstallFileTypesErrorMessage": "Deinstallation der Dateitypen fehlgeschlagen.", + "DialogOpenSettingsWindowLabel": "Fenster-Einstellungen öffnen", + "DialogControllerAppletTitle": "Controller-Applet", + "DialogMessageDialogErrorExceptionMessage": "Fehler bei der Anzeige des Meldungs-Dialogs: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Fehler bei der Anzeige der Software-Tastatur: {0}", + "DialogErrorAppletErrorExceptionMessage": "Fehler beim Anzeigen des ErrorApplet-Dialogs: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nWeitere Informationen zur Behebung dieses Fehlers können in unserem Setup-Guide gefunden werden.", + "DialogUserErrorDialogTitle": "Ryujinx Fehler ({0})", + "DialogAmiiboApiTitle": "Amiibo-API", + "DialogAmiiboApiFailFetchMessage": "Beim Abrufen von Informationen aus der API ist ein Fehler aufgetreten.", + "DialogAmiiboApiConnectErrorMessage": "Verbindung zum Amiibo API Server kann nicht hergestellt werden. Der Dienst ist möglicherweise nicht verfügbar oder es existiert keine Internetverbindung.", + "DialogProfileInvalidProfileErrorMessage": "Das Profil {0} ist mit dem aktuellen Eingabekonfigurationssystem nicht kompatibel.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Das Standardprofil kann nicht überschrieben werden", + "DialogProfileDeleteProfileTitle": "Profil löschen", + "DialogProfileDeleteProfileMessage": "Diese Aktion kann nicht rückgängig gemacht werden. Wirklich fortfahren?", + "DialogWarning": "Warnung", + "DialogPPTCDeletionMessage": "Du bist dabei den PPTC für das folgende Spiel als ungültig zu markieren:\n\n{0}\n\nWirklich fortfahren?", + "DialogPPTCDeletionErrorMessage": "Fehler bei der Löschung des PPTC Caches bei {0}: {1}", + "DialogShaderDeletionMessage": "Du bist dabei, den Shader Cache zu löschen für :\n\n{0}\n\nWirklich fortfahren?", + "DialogShaderDeletionErrorMessage": "Es ist ein Fehler bei der Löschung des Shader Caches bei {0}: {1} aufgetreten", + "DialogRyujinxErrorMessage": "Ein Fehler ist aufgetreten", + "DialogInvalidTitleIdErrorMessage": "UI Fehler: Das ausgewählte Spiel hat keine gültige Titel-ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Es wurde keine gültige System-Firmware gefunden in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Installiere Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Systemversion {0} wird jetzt installiert.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nDies wird die aktuelle Systemversion {0} ersetzen.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nMöchtest du fortfahren?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Firmware wird installiert...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Systemversion {0} wurde erfolgreich installiert.", + "DialogUserProfileDeletionWarningMessage": "Es können keine anderen Profile geöffnet werden, wenn das ausgewählte Profil gelöscht wird.", + "DialogUserProfileDeletionConfirmMessage": "Möchtest du das ausgewählte Profil löschen?", + "DialogUserProfileUnsavedChangesTitle": "Warnung - Nicht gespeicherte Änderungen", + "DialogUserProfileUnsavedChangesMessage": "Sie haben Änderungen an diesem Nutzerprofil vorgenommen, die nicht gespeichert wurden.", + "DialogUserProfileUnsavedChangesSubMessage": "Möchten Sie Ihre Änderungen wirklich verwerfen?", + "DialogControllerSettingsModifiedConfirmMessage": "Die aktuellen Controller-Einstellungen wurden aktualisiert.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Controller-Einstellungen speichern?", + "DialogLoadFileErrorMessage": "{0}. Fehlerhafte Datei: {1}", + "DialogModAlreadyExistsMessage": "Mod ist bereits vorhanden", + "DialogModInvalidMessage": "Das angegebene Verzeichnis enthält keine Mods!", + "DialogModDeleteNoParentMessage": "Löschen fehlgeschlagen: Das übergeordnete Verzeichnis für den Mod \"{0}\" konnte nicht gefunden werden!", + "DialogDlcNoDlcErrorMessage": "Die angegebene Datei enthält keinen DLC für den ausgewählten Titel!", + "DialogPerformanceCheckLoggingEnabledMessage": "Es wurde die Debug Protokollierung aktiviert", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Um eine optimale Leistung zu erzielen, wird empfohlen, die Debug Protokollierung zu deaktivieren. Debug Protokollierung jetzt deaktivieren?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Es wurde das Shader Dumping aktiviert, das nur von Entwicklern verwendet werden soll.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Für eine optimale Leistung wird empfohlen, das Shader Dumping zu deaktivieren. Shader Dumping jetzt deaktivieren?", + "DialogLoadAppGameAlreadyLoadedMessage": "Es wurde bereits ein Spiel gestartet", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Bitte beende die Emulation oder schließe den Emulator, vor dem Starten eines neuen Spiels", + "DialogUpdateAddUpdateErrorMessage": "Die angegebene Datei enthält keine Updates für den ausgewählten Titel!", + "DialogSettingsBackendThreadingWarningTitle": "Warnung - Render Threading", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx muss muss neu gestartet werden, damit die Änderungen wirksam werden. Abhängig von dem Betriebssystem muss möglicherweise das Multithreading des Treibers manuell deaktiviert werden, wenn Ryujinx verwendet wird.", + "DialogModManagerDeletionWarningMessage": "Du bist dabei, diesen Mod zu lösche. {0}\n\nMöchtest du wirklich fortfahren?", + "DialogModManagerDeletionAllWarningMessage": "Du bist dabei, alle Mods für diesen Titel zu löschen.\n\nMöchtest du wirklich fortfahren?", + "SettingsTabGraphicsFeaturesOptions": "Erweiterungen", + "SettingsTabGraphicsBackendMultithreading": "Grafik-Backend Multithreading:", + "CommonAuto": "Auto", + "CommonOff": "Aus", + "CommonOn": "An", + "InputDialogYes": "Ja", + "InputDialogNo": "Nein", + "DialogProfileInvalidProfileNameErrorMessage": "Der Dateiname enthält ungültige Zeichen. Bitte erneut versuchen.", + "MenuBarOptionsPauseEmulation": "Pause", + "MenuBarOptionsResumeEmulation": "Fortsetzen", + "AboutUrlTooltipMessage": "Klicke hier, um die Ryujinx Website im Standardbrowser zu öffnen.", + "AboutDisclaimerMessage": "Ryujinx ist in keinster Weise weder mit Nintendo™, \nnoch mit deren Partnern verbunden.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) wird in unserer Amiibo \nEmulation benutzt.", + "AboutPatreonUrlTooltipMessage": "Klicke hier, um die Ryujinx Patreon Seite im Standardbrowser zu öffnen.", + "AboutGithubUrlTooltipMessage": "Klicke hier, um die Ryujinx GitHub Seite im Standardbrowser zu öffnen.", + "AboutDiscordUrlTooltipMessage": "Klicke hier, um eine Einladung zum Ryujinx Discord Server im Standardbrowser zu öffnen.", + "AboutTwitterUrlTooltipMessage": "Klicke hier, um die Ryujinx Twitter Seite im Standardbrowser zu öffnen.", + "AboutRyujinxAboutTitle": "Über:", + "AboutRyujinxAboutContent": "Ryujinx ist ein Nintendo Switch™ Emulator.\nBitte unterstütze uns auf Patreon.\nAuf Twitter oder Discord erfährst du alle Neuigkeiten.\nEntwickler, die an einer Mitarbeit interessiert sind, können auf GitHub oder Discord mehr erfahren.", + "AboutRyujinxMaintainersTitle": "Entwickelt von:", + "AboutRyujinxMaintainersContentTooltipMessage": "Klicke hier, um die Liste der Mitwirkenden im Standardbrowser zu öffnen.", + "AboutRyujinxSupprtersTitle": "Unterstützt auf Patreon von:", + "AmiiboSeriesLabel": "Amiibo-Serie", + "AmiiboCharacterLabel": "Charakter", + "AmiiboScanButtonLabel": "Einscannen", + "AmiiboOptionsShowAllLabel": "Zeige alle Amiibos", + "AmiiboOptionsUsRandomTagLabel": "Hack: Benutze zufällige Tag-UUID", + "DlcManagerTableHeadingEnabledLabel": "Aktiviert", + "DlcManagerTableHeadingTitleIdLabel": "Title-ID", + "DlcManagerTableHeadingContainerPathLabel": "Container-Pfad", + "DlcManagerTableHeadingFullPathLabel": "Vollständiger-Pfad", + "DlcManagerRemoveAllButton": "Entferne alle", + "DlcManagerEnableAllButton": "Alle aktivieren", + "DlcManagerDisableAllButton": "Alle deaktivieren", + "ModManagerDeleteAllButton": "Alle löschen", + "MenuBarOptionsChangeLanguage": "Sprache ändern", + "MenuBarShowFileTypes": "Dateitypen anzeigen", + "CommonSort": "Sortieren", + "CommonShowNames": "Spiel-Namen anzeigen", + "CommonFavorite": "Favoriten", + "OrderAscending": "Aufsteigend", + "OrderDescending": "Absteigend", + "SettingsTabGraphicsFeatures": "Erweiterungen", + "ErrorWindowTitle": "Fehler-Fenster", + "ToggleDiscordTooltip": "Zeige momentanes Spiel auf Discord", + "AddGameDirBoxTooltip": "Gibt das Spielverzeichnis an, das der Liste hinzuzufügt wird", + "AddGameDirTooltip": "Fügt ein neues Spielverzeichnis hinzu", + "RemoveGameDirTooltip": "Entfernt das ausgewähltes Spielverzeichnis", + "CustomThemeCheckTooltip": "Verwende ein eigenes Design für die Emulator-Benutzeroberfläche", + "CustomThemePathTooltip": "Gibt den Pfad zum Design für die Emulator-Benutzeroberfläche an", + "CustomThemeBrowseTooltip": "Ermöglicht die Suche nach einem benutzerdefinierten Design für die Emulator-Benutzeroberfläche", + "DockModeToggleTooltip": "Im gedockten Modus verhält sich das emulierte System wie eine Nintendo Switch im TV Modus. Dies verbessert die grafische Qualität der meisten Spiele. Umgekehrt führt die Deaktivierung dazu, dass sich das emulierte System wie eine Nintendo Switch im Handheld Modus verhält, was die Grafikqualität beeinträchtigt.\n\nKonfiguriere das Eingabegerät für Spieler 1, um im Docked Modus zu spielen; konfiguriere das Controllerprofil via der Handheld Option, wenn geplant wird den Handheld Modus zu nutzen.\n\nIm Zweifelsfall AN lassen.", + "DirectKeyboardTooltip": "Direkter Zugriff auf die Tastatur (HID). Bietet Spielen Zugriff auf Ihre Tastatur als Texteingabegerät.\n\nFunktioniert nur mit Spielen, die die Tastaturnutzung auf Switch-Hardware nativ unterstützen.\n\nAus lassen, wenn unsicher.", + "DirectMouseTooltip": "Unterstützt den direkten Mauszugriff (HID). Bietet Spielen Zugriff auf Ihre Maus als Zeigegerät.\n\nFunktioniert nur mit Spielen, die nativ die Steuerung mit der Maus auf Switch-Hardware unterstützen (nur sehr wenige).\n\nTouchscreen-Funktionalität ist möglicherweise eingeschränkt, wenn dies aktiviert ist.\n\n Aus lassen, wenn unsicher.", + "RegionTooltip": "Ändert die Systemregion", + "LanguageTooltip": "Ändert die Systemsprache", + "TimezoneTooltip": "Ändert die Systemzeitzone", + "TimeTooltip": "Ändert die Systemzeit", + "VSyncToggleTooltip": "Vertikale Synchronisierung der emulierten Konsole. Diese Option ist quasi ein Frame-Limiter für die meisten Spiele; die Deaktivierung kann dazu führen, dass Spiele mit höherer Geschwindigkeit laufen oder Ladebildschirme länger benötigen/hängen bleiben.\n\nKann beim Spielen mit einem frei wählbaren Hotkey ein- und ausgeschaltet werden (standardmäßig F1). \n\nIm Zweifelsfall AN lassen.", + "PptcToggleTooltip": "Speichert übersetzte JIT-Funktionen, sodass jene nicht jedes Mal übersetzt werden müssen, wenn das Spiel geladen wird.\n\nVerringert Stottern und die Zeit beim zweiten und den darauffolgenden Startvorgängen eines Spiels erheblich.\n\nIm Zweifelsfall AN lassen.", + "FsIntegrityToggleTooltip": "Prüft beim Startvorgang auf beschädigte Dateien und zeigt bei beschädigten Dateien einen Hash-Fehler (Hash Error) im Log an.\n\nDiese Einstellung hat keinen Einfluss auf die Leistung und hilft bei der Fehlersuche.\n\nIm Zweifelsfall AN lassen.", + "AudioBackendTooltip": "Ändert das Backend, das zum Rendern von Audio verwendet wird.\n\nSDL2 ist das bevorzugte Audio-Backend, OpenAL und SoundIO sind als Alternativen vorhanden. Dummy wird keinen Audio-Output haben.\n\nIm Zweifelsfall SDL2 auswählen.", + "MemoryManagerTooltip": "Ändert wie der Gastspeicher abgebildet wird und wie auf ihn zugegriffen wird. Beinflusst die Leistung der emulierten CPU erheblich.\n\nIm Zweifelsfall Host ungeprüft auswählen.", + "MemoryManagerSoftwareTooltip": "Verwendung einer Software-Seitentabelle für die Adressumsetzung. Höchste Genauigkeit, aber langsamste Leistung.", + "MemoryManagerHostTooltip": "Direkte Zuordnung von Speicher im Host-Adressraum. Viel schnellere JIT-Kompilierung und Ausführung.", + "MemoryManagerUnsafeTooltip": "Direkte Zuordnung des Speichers, aber keine Maskierung der Adresse innerhalb des Gastadressraums vor dem Zugriff. Schneller, aber auf Kosten der Sicherheit. Die Gastanwendung kann von überall in Ryujinx auf den Speicher zugreifen, daher sollte in diesem Modus nur Programme ausgeführt werden denen vertraut wird.", + "UseHypervisorTooltip": "Verwende Hypervisor anstelle von JIT. Verbessert die Leistung stark, falls vorhanden, kann jedoch in seinem aktuellen Zustand instabil sein.", + "DRamTooltip": "Erhöht den Arbeitsspeicher des emulierten Systems von 4 GiB auf 6 GiB.\n\nDies ist nur für Texturenpakete mit höherer Auflösung oder Mods mit 4K-Auflösung nützlich. Diese Option verbessert NICHT die Leistung.\n\nIm Zweifelsfall AUS lassen.", + "IgnoreMissingServicesTooltip": "Durch diese Option werden nicht implementierte Dienste der Switch-Firmware ignoriert. Dies kann dabei helfen, Abstürze beim Starten bestimmter Spiele zu umgehen.\n\nIm Zweifelsfall AUS lassen.", + "GraphicsBackendThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf AUTO stellen.", + "GalThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies Beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf auf AUTO stellen.", + "ShaderCacheToggleTooltip": "Speichert einen persistenten Shader Cache, der das Stottern bei nachfolgenden Durchläufen reduziert.\n\nIm Zweifelsfall AN lassen.", + "ResolutionScaleTooltip": "Multipliziert die Rendering-Auflösung des Spiels.\n\nEinige wenige Spiele funktionieren damit nicht und sehen auch bei höherer Auflösung pixelig aus; für diese Spiele müssen Sie möglicherweise Mods finden, die Anti-Aliasing entfernen oder die interne Rendering-Auflösung erhöhen. Für die Verwendung von Letzterem sollten Sie Native wählen.\n\nSie können diese Option ändern, während ein Spiel läuft, indem Sie unten auf \"Übernehmen\" klicken; Sie können das Einstellungsfenster einfach zur Seite schieben und experimentieren, bis Sie Ihr bevorzugtes Aussehen für ein Spiel gefunden haben.\n\nDenken Sie daran, dass 4x für praktisch jedes Setup Overkill ist.", + "ResolutionScaleEntryTooltip": "Fließkomma Auflösungsskalierung, wie 1,5.\n Bei nicht ganzzahligen Werten ist die Wahrscheinlichkeit größer, dass Probleme entstehen, die auch zum Absturz führen können.", + "AnisotropyTooltip": "Stufe der Anisotropen Filterung. Auf Auto setzen, um den vom Spiel geforderten Wert zu verwenden.", + "AspectRatioTooltip": "Seitenverhältnis, das auf das Renderer-Fenster angewendet wird.\n\nÄndern Sie dies nur, wenn Sie einen Seitenverhältnis-Mod für Ihr Spiel verwenden, da sonst die Grafik gestreckt wird.\n\nLassen Sie es auf 16:9, wenn Sie unsicher sind.", + "ShaderDumpPathTooltip": "Grafik-Shader-Dump-Pfad", + "FileLogTooltip": "Speichert die Konsolenausgabe in einer Log-Datei auf der Festplatte. Hat keinen Einfluss auf die Leistung.", + "StubLogTooltip": "Ausgabe von Stub-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "InfoLogTooltip": "Ausgabe von Info-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "WarnLogTooltip": "Ausgabe von Warn-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "ErrorLogTooltip": "Ausgabe von Fehler-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "TraceLogTooltip": "Ausgabe von Trace-Log in der Konsole. Hat keinen Einfluss auf die Leistung.", + "GuestLogTooltip": "Ausgabe von Gast-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "FileAccessLogTooltip": "Ausgabe von FS-Zugriff-Logs in der Konsole.", + "FSAccessLogModeTooltip": "Aktiviert die Ausgabe des FS-Zugriff-Logs in der Konsole. Mögliche Modi sind 0-3", + "DeveloperOptionTooltip": "Mit Vorsicht verwenden", + "OpenGlLogLevel": "Erfordert die Aktivierung der entsprechenden Log-Level", + "DebugLogTooltip": "Ausgabe von Debug-Logs in der Konsole.\n\nVerwende diese Option nur auf ausdrückliche Anweisung von Ryujinx Entwicklern, da sie das Lesen der Protokolle erschwert und die Leistung des Emulators verschlechtert.", + "LoadApplicationFileTooltip": "Öffnet die Dateiauswahl um Datei zu laden, welche mit der Switch kompatibel ist", + "LoadApplicationFolderTooltip": "Öffnet die Dateiauswahl um ein Spiel zu laden, welches mit der Switch kompatibel ist", + "OpenRyujinxFolderTooltip": "Öffnet den Ordner, der das Ryujinx Dateisystem enthält", + "OpenRyujinxLogsTooltip": "Öffnet den Ordner, in welchem die Logs gespeichert werden", + "ExitTooltip": "Beendet Ryujinx", + "OpenSettingsTooltip": "Öffnet das Einstellungsfenster", + "OpenProfileManagerTooltip": "Öffnet das Profilverwaltungsfenster", + "StopEmulationTooltip": "Beendet die Emulation des derzeitigen Spiels und kehrt zu der Spielauswahl zurück", + "CheckUpdatesTooltip": "Sucht nach Updates für Ryujinx", + "OpenAboutTooltip": "Öffnet das 'Über Ryujinx'-Fenster", + "GridSize": "Rastergröße", + "GridSizeTooltip": "Ändert die Größe der Rasterelemente", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brasilianisches Portugiesisch", + "AboutRyujinxContributorsButtonHeader": "Alle Mitwirkenden anzeigen", + "SettingsTabSystemAudioVolume": "Lautstärke: ", + "AudioVolumeTooltip": "Ändert die Lautstärke", + "SettingsTabSystemEnableInternetAccess": "Gast-Internet-Zugang/LAN Modus", + "EnableInternetAccessTooltip": "Erlaubt es der emulierten Anwendung sich mit dem Internet zu verbinden.\n\nSpiele die den LAN-Modus unterstützen, ermöglichen es Ryujinx sich sowohl mit anderen Ryujinx-Systemen, als auch mit offiziellen Nintendo Switch Konsolen zu verbinden. Allerdings nur, wenn diese Option aktiviert ist und die Systeme mit demselben lokalen Netzwerk verbunden sind.\n\nDies erlaubt KEINE Verbindung zu Nintendo-Servern. Kann bei bestimmten Spielen die versuchen sich mit dem Internet zu verbinden zum Absturz führen.\n\nIm Zweifelsfall AUS lassen", + "GameListContextMenuManageCheatToolTip": "Öffnet den Cheat-Manager", + "GameListContextMenuManageCheat": "Cheats verwalten", + "GameListContextMenuManageModToolTip": "Mods verwalten", + "GameListContextMenuManageMod": "Mods verwalten", + "ControllerSettingsStickRange": "Bereich:", + "DialogStopEmulationTitle": "Ryujinx - Beende Emulation", + "DialogStopEmulationMessage": "Emulation wirklich beenden?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Netzwerk", + "SettingsTabNetworkConnection": "Netwerkverbindung", + "SettingsTabCpuCache": "CPU-Cache", + "SettingsTabCpuMemory": "CPU-Speicher", + "DialogUpdaterFlatpakNotSupportedMessage": "Bitte aktualisiere Ryujinx über FlatHub", + "UpdaterDisabledWarningTitle": "Updater deaktiviert!", + "ControllerSettingsRotate90": "Um 90° rotieren", + "IconSize": "Cover Größe", + "IconSizeTooltip": "Ändert die Größe der Spiel-Cover", + "MenuBarOptionsShowConsole": "Zeige Konsole", + "ShaderCachePurgeError": "Es ist ein Fehler beim löschen des Shader Caches aufgetreten bei {0}: {1}", + "UserErrorNoKeys": "Keys nicht gefunden", + "UserErrorNoFirmware": "Firmware nicht gefunden", + "UserErrorFirmwareParsingFailed": "Firmware-Analysierung-Fehler", + "UserErrorApplicationNotFound": "Anwendung nicht gefunden", + "UserErrorUnknown": "Unbekannter Fehler", + "UserErrorUndefined": "Undefinierter Fehler", + "UserErrorNoKeysDescription": "Ryujinx konnte deine 'prod.keys' Datei nicht finden", + "UserErrorNoFirmwareDescription": "Ryujinx konnte keine installierte Firmware finden!", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx konnte die zu verfügung gestellte Firmware nicht analysieren. Ein möglicher Grund dafür sind veraltete keys.", + "UserErrorApplicationNotFoundDescription": "Ryujinx konnte keine valide Anwendung an dem gegeben Pfad finden.", + "UserErrorUnknownDescription": "Ein unbekannter Fehler ist aufgetreten!", + "UserErrorUndefinedDescription": "Ein undefinierter Fehler ist aufgetreten! Dies sollte nicht passieren. Bitte kontaktiere einen Entwickler!", + "OpenSetupGuideMessage": "Öffne den 'Setup Guide'", + "NoUpdate": "Kein Update", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Bestätigung", + "FileDialogAllTypes": "Alle Typen", + "Never": "Niemals", + "SwkbdMinCharacters": "Muss mindestens {0} Zeichen lang sein", + "SwkbdMinRangeCharacters": "Muss {0}-{1} Zeichen lang sein", + "SoftwareKeyboard": "Software-Tastatur", + "SoftwareKeyboardModeNumeric": "Darf nur 0-9 oder \".\" sein", + "SoftwareKeyboardModeAlphabet": "Keine CJK-Zeichen", + "SoftwareKeyboardModeASCII": "Nur ASCII-Text", + "ControllerAppletControllers": "Unterstützte Controller:", + "ControllerAppletPlayers": "Spieler:", + "ControllerAppletDescription": "Ihre aktuelle Konfiguration ist ungültig. Öffnen Sie die Einstellungen und konfigurieren Sie Ihre Eingaben neu.", + "ControllerAppletDocked": "Andockmodus gesetzt. Handheld-Steuerung sollte deaktiviert worden sein.", + "UpdaterRenaming": "Alte Dateien umbenennen...", + "UpdaterRenameFailed": "Der Updater konnte die folgende Datei nicht umbenennen: {0}", + "UpdaterAddingFiles": "Neue Dateien hinzufügen...", + "UpdaterExtracting": "Update extrahieren...", + "UpdaterDownloading": "Update herunterladen...", + "Game": "Spiel", + "Docked": "Docked", + "Handheld": "Handheld", + "ConnectionError": "Verbindungsfehler.", + "AboutPageDeveloperListMore": "{0} und mehr...", + "ApiError": "API Fehler.", + "LoadingHeading": "{0} wird gestartet", + "CompilingPPTC": "PTC wird kompiliert", + "CompilingShaders": "Shader werden kompiliert", + "AllKeyboards": "Alle Tastaturen", + "OpenFileDialogTitle": "Wähle eine unterstützte Datei", + "OpenFolderDialogTitle": "Wähle einen Ordner mit einem entpackten Spiel", + "AllSupportedFormats": "Alle unterstützten Formate", + "RyujinxUpdater": "Ryujinx - Updater", + "SettingsTabHotkeys": "Tastatur-Hotkeys", + "SettingsTabHotkeysHotkeys": "Tastatur-Hotkeys", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Screenshot:", + "SettingsTabHotkeysShowUiHotkey": "Zeige UI:", + "SettingsTabHotkeysPauseHotkey": "Pausieren:", + "SettingsTabHotkeysToggleMuteHotkey": "Stummschalten:", + "ControllerMotionTitle": "Bewegungssteuerung - Einstellungen", + "ControllerRumbleTitle": "Vibration - Einstellungen", + "SettingsSelectThemeFileDialogTitle": "Wähle ein Design für die Emulator-Benutzeroberfläche", + "SettingsXamlThemeFile": "Xaml Design-Datei", + "AvatarWindowTitle": "Profile verwalten - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Unbekannt", + "Usage": "Nutzung", + "Writable": "Beschreibbar", + "SelectDlcDialogTitle": "DLC-Dateien auswählen", + "SelectUpdateDialogTitle": "Update-Datei auswählen", + "SelectModDialogTitle": "Mod-Ordner auswählen", + "UserProfileWindowTitle": "Benutzerprofile verwalten", + "CheatWindowTitle": "Spiel-Cheats verwalten", + "DlcWindowTitle": "Spiel-DLC verwalten", + "ModWindowTitle": "Manage Mods for {0} ({1})", + "UpdateWindowTitle": "Spiel-Updates verwalten", + "CheatWindowHeading": "Cheats verfügbar für {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "DLC verfügbar für {0} [{1}]", + "ModWindowHeading": "{0} Mod(s)", + "UserProfilesEditProfile": "Profil bearbeiten", + "Cancel": "Abbrechen", + "Save": "Speichern", + "Discard": "Verwerfen", + "Paused": "Pausiert", + "UserProfilesSetProfileImage": "Profilbild einrichten", + "UserProfileEmptyNameError": "Name ist erforderlich", + "UserProfileNoImageError": "Bitte ein Profilbild auswählen", + "GameUpdateWindowHeading": "Update verfügbar für {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Auflösung erhöhen:", + "SettingsTabHotkeysResScaleDownHotkey": "Auflösung verringern:", + "UserProfilesName": "Name:", + "UserProfilesUserId": "Benutzer-ID:", + "SettingsTabGraphicsBackend": "Grafik-Backend:", + "SettingsTabGraphicsBackendTooltip": "Wählen Sie das Grafik-Backend, das im Emulator verwendet werden soll.\n\nVulkan ist insgesamt besser für alle modernen Grafikkarten geeignet, sofern deren Treiber auf dem neuesten Stand sind. Vulkan bietet auch eine schnellere Shader-Kompilierung (weniger Stottern) auf allen GPU-Anbietern.\n\nOpenGL kann auf alten Nvidia-GPUs, alten AMD-GPUs unter Linux oder auf GPUs mit geringerem VRAM bessere Ergebnisse erzielen, obwohl die Shader-Kompilierung stärker stottert.\n\nSetzen Sie auf Vulkan, wenn Sie unsicher sind. Stellen Sie OpenGL ein, wenn Ihr Grafikprozessor selbst mit den neuesten Grafiktreibern Vulkan nicht unterstützt.", + "SettingsEnableTextureRecompression": "Textur-Rekompression", + "SettingsEnableTextureRecompressionTooltip": "Komprimiert ASTC-Texturen, um die VRAM-Nutzung zu reduzieren.\n\nZu den Spielen, die dieses Texturformat verwenden, gehören Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder und The Legend of Zelda: Tears of the Kingdom.\n\nGrafikkarten mit 4GiB VRAM oder weniger werden beim Ausführen dieser Spiele wahrscheinlich irgendwann abstürzen.\n\nAktivieren Sie diese Option nur, wenn Ihnen bei den oben genannten Spielen der VRAM ausgeht. Lassen Sie es aus, wenn Sie unsicher sind.", + "SettingsTabGraphicsPreferredGpu": "Bevorzugte GPU:", + "SettingsTabGraphicsPreferredGpuTooltip": "Wähle die Grafikkarte aus, die mit dem Vulkan Grafik-Backend verwendet werden soll.\n\nDies hat keinen Einfluss auf die GPU die OpenGL verwendet.\n\nIm Zweifelsfall die als \"dGPU\" gekennzeichnete GPU auswählen. Diese Einstellung unberührt lassen, wenn keine zur Auswahl steht.", + "SettingsAppRequiredRestartMessage": "Ein Neustart von Ryujinx ist erforderlich", + "SettingsGpuBackendRestartMessage": "Das Grafik-Backend oder die Grafikkarteneinstellungen wurden geändert. Ein Neustart ist erforderlich um diese Einstellungen anzuwenden.", + "SettingsGpuBackendRestartSubMessage": "Ryujinx jetzt neu starten?", + "RyujinxUpdaterMessage": "Möchtest du Ryujinx auf die neueste Version aktualisieren?", + "SettingsTabHotkeysVolumeUpHotkey": "Lautstärke erhöhen:", + "SettingsTabHotkeysVolumeDownHotkey": "Lautstärke verringern:", + "SettingsEnableMacroHLE": "HLE Makros aktivieren", + "SettingsEnableMacroHLETooltip": "High-Level-Emulation von GPU-Makrocode.\n\nVerbessert die Leistung, kann aber in einigen Spielen zu Grafikfehlern führen.\n\nBei Unsicherheit AKTIVIEREN.", + "SettingsEnableColorSpacePassthrough": "Farbraum Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Weist das Vulkan-Backend an, Farbinformationen ohne Angabe eines Farbraums weiterzuleiten. Für Benutzer mit Wide-Gamut-Displays kann dies zu lebendigeren Farben führen, allerdings auf Kosten der Farbkorrektheit.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Speicherstände verwalten", + "DeleteUserSave": "Möchtest du den Spielerstand für dieses Spiel löschen?", + "IrreversibleActionNote": "Diese Option kann nicht rückgängig gemacht werden.", + "SaveManagerHeading": "Spielstände für {0} verwalten", + "SaveManagerTitle": "Speicherdaten Manager", + "Name": "Name", + "Size": "Größe", + "Search": "Suche", + "UserProfilesRecoverLostAccounts": "Konto wiederherstellen", + "Recover": "Wiederherstellen", + "UserProfilesRecoverHeading": "Speicherstände wurden für die folgenden Konten gefunden", + "UserProfilesRecoverEmptyList": "Keine Profile zum Wiederherstellen", + "GraphicsAATooltip": "Wendet Anti-Aliasing auf das Rendering des Spiels an.\n\nFXAA verwischt den größten Teil des Bildes, während SMAA versucht, gezackte Kanten zu finden und sie zu glätten.\n\nEs wird nicht empfohlen, diese Option in Verbindung mit dem FSR-Skalierungsfilter zu verwenden.\n\nDiese Option kann geändert werden, während ein Spiel läuft, indem Sie unten auf \"Anwenden\" klicken; Sie können das Einstellungsfenster einfach zur Seite schieben und experimentieren, bis Sie Ihr bevorzugtes Aussehen für ein Spiel gefunden haben.\n\nLassen Sie die Option auf NONE, wenn Sie unsicher sind.", + "GraphicsAALabel": "Antialiasing:", + "GraphicsScalingFilterLabel": "Skalierungsfilter:", + "GraphicsScalingFilterTooltip": "Wählen Sie den Skalierungsfilter, der bei der Auflösungsskalierung angewendet werden soll.\n\nBilinear eignet sich gut für 3D-Spiele und ist eine sichere Standardoption.\n\nNearest wird für Pixel-Art-Spiele empfohlen.\n\nFSR 1.0 ist lediglich ein Schärfungsfilter und wird nicht für die Verwendung mit FXAA oder SMAA empfohlen.\n\nDiese Option kann geändert werden, während ein Spiel läuft, indem Sie unten auf \"Anwenden\" klicken; Sie können das Einstellungsfenster einfach zur Seite schieben und experimentieren, bis Sie Ihr bevorzugtes Aussehen für ein Spiel gefunden haben.\n\nBleiben Sie auf BILINEAR, wenn Sie unsicher sind.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nächstes", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "Stufe", + "GraphicsScalingFilterLevelTooltip": "FSR 1.0 Schärfelevel festlegen. Höher ist schärfer.", + "SmaaLow": "SMAA Niedrig", + "SmaaMedium": "SMAA Mittel", + "SmaaHigh": "SMAA Hoch", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Nutzer bearbeiten", + "UserEditorTitleCreate": "Nutzer erstellen", + "SettingsTabNetworkInterface": "Netzwerkschnittstelle:", + "NetworkInterfaceTooltip": "Die für LAN/LDN-Funktionen verwendete Netzwerkschnittstelle.\n\nIn Verbindung mit einem VPN oder XLink Kai und einem Spiel mit LAN-Unterstützung kann eine Verbindung mit demselben Netzwerk über das Internet vorgetäuscht werden.\n\nIm Zweifelsfall auf DEFAULT belassen.", + "NetworkInterfaceDefault": "Standard", + "PackagingShaders": "Verpackt Shader", + "AboutChangelogButton": "Changelog in GitHub öffnen", + "AboutChangelogButtonTooltipMessage": "Klicke hier, um das Changelog für diese Version in Ihrem Standardbrowser zu öffnen.", + "SettingsTabNetworkMultiplayer": "Mehrspieler", + "MultiplayerMode": "Modus:", + "MultiplayerModeTooltip": "Ändert den LDN-Mehrspielermodus.\n\nLdnMitm ändert die lokale drahtlose/lokale Spielfunktionalität in Spielen so, dass sie wie ein LAN funktioniert und lokale, netzwerkgleiche Verbindungen mit anderen Ryujinx-Instanzen und gehackten Nintendo Switch-Konsolen ermöglicht, auf denen das ldn_mitm-Modul installiert ist.\n\nMultiplayer erfordert, dass alle Spieler die gleiche Spielversion verwenden (d.h. Super Smash Bros. Ultimate v13.0.1 kann sich nicht mit v13.0.0 verbinden).\n\nIm Zweifelsfall auf DISABLED lassen.", + "MultiplayerModeDisabled": "Deaktiviert", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json new file mode 100644 index 00000000..ccdf6e0e --- /dev/null +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -0,0 +1,780 @@ +{ + "Language": "Ελληνικά", + "MenuBarFileOpenApplet": "Άνοιγμα Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Άνοιγμα του Mii Editor Applet σε Αυτόνομη λειτουργία", + "SettingsTabInputDirectMouseAccess": "Άμεση Πρόσβαση Ποντικιού", + "SettingsTabSystemMemoryManagerMode": "Λειτουργία Διαχείρισης Μνήμης:", + "SettingsTabSystemMemoryManagerModeSoftware": "Λογισμικό", + "SettingsTabSystemMemoryManagerModeHost": "Υπολογιστής (γρήγορο)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Χωρίς Ελέγχους (γρηγορότερο, μη ασφαλές)", + "SettingsTabSystemUseHypervisor": "Χρήση Hypervisor", + "MenuBarFile": "_Αρχείο", + "MenuBarFileOpenFromFile": "_Φόρτωση Αρχείου Εφαρμογής", + "MenuBarFileOpenUnpacked": "Φόρτωση Απακετάριστου _Παιχνιδιού", + "MenuBarFileOpenEmuFolder": "Άνοιγμα Φακέλου Ryujinx", + "MenuBarFileOpenLogsFolder": "Άνοιγμα Φακέλου Καταγραφής", + "MenuBarFileExit": "_Έξοδος", + "MenuBarOptions": "_Επιλογές", + "MenuBarOptionsToggleFullscreen": "Λειτουργία Πλήρους Οθόνης", + "MenuBarOptionsStartGamesInFullscreen": "Εκκίνηση Παιχνιδιών σε Πλήρη Οθόνη", + "MenuBarOptionsStopEmulation": "Διακοπή Εξομοίωσης", + "MenuBarOptionsSettings": "_Ρυθμίσεις", + "MenuBarOptionsManageUserProfiles": "Διαχείριση Προφίλ _Χρηστών", + "MenuBarActions": "_Δράσεις", + "MenuBarOptionsSimulateWakeUpMessage": "Προσομοίωση Μηνύματος Αφύπνισης", + "MenuBarActionsScanAmiibo": "Σάρωση Amiibo", + "MenuBarTools": "_Εργαλεία", + "MenuBarToolsInstallFirmware": "Εγκατάσταση Firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Εγκατάσταση Firmware από XCI ή ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Εγκατάσταση Firmware από τοποθεσία", + "MenuBarToolsManageFileTypes": "Διαχείριση τύπων αρχείων", + "MenuBarToolsInstallFileTypes": "Εγκαταστήσετε τύπους αρχείων.", + "MenuBarToolsUninstallFileTypes": "Απεγκαταστήσετε τύπους αρχείων", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "_Βοήθεια", + "MenuBarHelpCheckForUpdates": "Έλεγχος για Ενημερώσεις", + "MenuBarHelpAbout": "Σχετικά με", + "MenuSearch": "Αναζήτηση...", + "GameListHeaderFavorite": "Αγαπημένο", + "GameListHeaderIcon": "Εικονίδιο", + "GameListHeaderApplication": "Όνομα", + "GameListHeaderDeveloper": "Προγραμματιστής", + "GameListHeaderVersion": "Έκδοση", + "GameListHeaderTimePlayed": "Χρόνος", + "GameListHeaderLastPlayed": "Παίχτηκε", + "GameListHeaderFileExtension": "Κατάληξη", + "GameListHeaderFileSize": "Μέγεθος Αρχείου", + "GameListHeaderPath": "Τοποθεσία", + "GameListContextMenuOpenUserSaveDirectory": "Άνοιγμα Τοποθεσίας Αποθήκευσης Χρήστη", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Χρήστη της εφαρμογής", + "GameListContextMenuOpenDeviceSaveDirectory": "Άνοιγμα Τοποθεσίας Συσκευής Χρήστη", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Συσκευής της εφαρμογής", + "GameListContextMenuOpenBcatSaveDirectory": "Άνοιγμα Τοποθεσίας BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση BCAT της εφαρμογής", + "GameListContextMenuManageTitleUpdates": "Διαχείριση Ενημερώσεων Παιχνιδιού", + "GameListContextMenuManageTitleUpdatesToolTip": "Ανοίγει το παράθυρο διαχείρισης Ενημερώσεων Παιχνιδιού", + "GameListContextMenuManageDlc": "Διαχείριση DLC", + "GameListContextMenuManageDlcToolTip": "Ανοίγει το παράθυρο διαχείρισης DLC", + "GameListContextMenuCacheManagement": "Διαχείριση Προσωρινής Μνήμης", + "GameListContextMenuCacheManagementPurgePptc": "Εκκαθάριση Προσωρινής Μνήμης PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Διαγράφει την προσωρινή μνήμη PPTC της εφαρμογής", + "GameListContextMenuCacheManagementPurgeShaderCache": "Εκκαθάριση Προσωρινής Μνήμης Shader", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Διαγράφει την προσωρινή μνήμη Shader της εφαρμογής", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Άνοιγμα Τοποθεσίας PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει τη προσωρινή μνήμη PPTC της εφαρμογής", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Άνοιγμα τοποθεσίας προσωρινής μνήμης Shader", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την προσωρινή μνήμη Shader της εφαρμογής", + "GameListContextMenuExtractData": "Εξαγωγή Δεδομένων", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Εξαγωγή της ενότητας ExeFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Εξαγωγή της ενότητας RomFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "GameListContextMenuExtractDataLogo": "Λογότυπο", + "GameListContextMenuExtractDataLogoToolTip": "Εξαγωγή της ενότητας Logo από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "GameListContextMenuCreateShortcut": "Δημιουργία Συντόμευσης Εφαρμογής", + "GameListContextMenuCreateShortcutToolTip": "Δημιουργία συντόμευσης επιφάνειας εργασίας που ανοίγει την επιλεγμένη εφαρμογή", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "StatusBarGamesLoaded": "{0}/{1} Φορτωμένα Παιχνίδια", + "StatusBarSystemVersion": "Έκδοση Συστήματος: {0}", + "LinuxVmMaxMapCountDialogTitle": "Εντοπίστηκε χαμηλό όριο για αντιστοιχίσεις μνήμης", + "LinuxVmMaxMapCountDialogTextPrimary": "Θα θέλατε να αυξήσετε την τιμή του vm.max_map_count σε {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Μερικά παιχνίδια μπορεί να προσπαθήσουν να δημιουργήσουν περισσότερες αντιστοιχίσεις μνήμης από αυτές που επιτρέπονται τώρα. Ο Ryujinx θα καταρρεύσει μόλις ξεπεραστεί αυτό το όριο.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Ναι, μέχρι την επόμενη επανεκκίνηση", + "LinuxVmMaxMapCountDialogButtonPersistent": "Ναι, μόνιμα", + "LinuxVmMaxMapCountWarningTextPrimary": "Ο μέγιστος αριθμός αντιστοιχίσεων μνήμης είναι μικρότερος από τον συνιστώμενο.", + "LinuxVmMaxMapCountWarningTextSecondary": "Η τρέχουσα τιμή του vm.max_map_count ({0}) είναι χαμηλότερη από {1}. Ορισμένα παιχνίδια μπορεί να προσπαθήσουν να δημιουργήσουν περισσότερες αντιστοιχίσεις μνήμης από αυτές που επιτρέπονται τώρα. Ο Ryujinx θα συντριβεί μόλις ξεπεραστεί το όριο.\n\nΜπορεί να θέλετε είτε να αυξήσετε χειροκίνητα το όριο ή να εγκαταστήσετε το pkexec, το οποίο επιτρέπει Ryujinx να βοηθήσει με αυτό.", + "Settings": "Ρυθμίσεις", + "SettingsTabGeneral": "Εμφάνιση", + "SettingsTabGeneralGeneral": "Γενικά", + "SettingsTabGeneralEnableDiscordRichPresence": "Ενεργοποίηση Εμπλουτισμένης Παρουσίας Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Έλεγχος για Ενημερώσεις στην Εκκίνηση", + "SettingsTabGeneralShowConfirmExitDialog": "Εμφάνιση διαλόγου \"Επιβεβαίωση Εξόδου\".", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", + "SettingsTabGeneralHideCursor": "Απόκρυψη Κέρσορα:", + "SettingsTabGeneralHideCursorNever": "Ποτέ", + "SettingsTabGeneralHideCursorOnIdle": "Απόκρυψη Δρομέα στην Αδράνεια", + "SettingsTabGeneralHideCursorAlways": "Πάντα", + "SettingsTabGeneralGameDirectories": "Τοποθεσίες παιχνιδιών", + "SettingsTabGeneralAdd": "Προσθήκη", + "SettingsTabGeneralRemove": "Αφαίρεση", + "SettingsTabSystem": "Σύστημα", + "SettingsTabSystemCore": "Πυρήνας", + "SettingsTabSystemSystemRegion": "Περιοχή Συστήματος:", + "SettingsTabSystemSystemRegionJapan": "Ιαπωνία", + "SettingsTabSystemSystemRegionUSA": "ΗΠΑ", + "SettingsTabSystemSystemRegionEurope": "Ευρώπη", + "SettingsTabSystemSystemRegionAustralia": "Αυστραλία", + "SettingsTabSystemSystemRegionChina": "Κίνα", + "SettingsTabSystemSystemRegionKorea": "Κορέα", + "SettingsTabSystemSystemRegionTaiwan": "Ταϊβάν", + "SettingsTabSystemSystemLanguage": "Γλώσσα Συστήματος:", + "SettingsTabSystemSystemLanguageJapanese": "Ιαπωνικά", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Αμερικάνικα Αγγλικά", + "SettingsTabSystemSystemLanguageFrench": "Γαλλικά", + "SettingsTabSystemSystemLanguageGerman": "Γερμανικά", + "SettingsTabSystemSystemLanguageItalian": "Ιταλικά", + "SettingsTabSystemSystemLanguageSpanish": "Ισπανικά", + "SettingsTabSystemSystemLanguageChinese": "Κινέζικα", + "SettingsTabSystemSystemLanguageKorean": "Κορεάτικα", + "SettingsTabSystemSystemLanguageDutch": "Ολλανδικά", + "SettingsTabSystemSystemLanguagePortuguese": "Πορτογαλικά", + "SettingsTabSystemSystemLanguageRussian": "Ρώσικα", + "SettingsTabSystemSystemLanguageTaiwanese": "Ταϊβανέζικα", + "SettingsTabSystemSystemLanguageBritishEnglish": "Βρετανικά Αγγλικά", + "SettingsTabSystemSystemLanguageCanadianFrench": "Καναδικά Γαλλικά", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Λατινοαμερικάνικα Ισπανικά", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Απλοποιημένα Κινέζικα", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Παραδοσιακά Κινεζικά", + "SettingsTabSystemSystemTimeZone": "Ζώνη Ώρας Συστήματος:", + "SettingsTabSystemSystemTime": "Ώρα Συστήματος:", + "SettingsTabSystemEnableVsync": "Ενεργοποίηση Κατακόρυφου Συγχρονισμού", + "SettingsTabSystemEnablePptc": "Ενεργοποίηση PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Ενεργοποίηση Ελέγχων Ακεραιότητας FS", + "SettingsTabSystemAudioBackend": "Backend Ήχου:", + "SettingsTabSystemAudioBackendDummy": "Απενεργοποιημένο", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Μικροδιορθώσεις", + "SettingsTabSystemHacksNote": " (Μπορεί να προκαλέσουν αστάθεια)", + "SettingsTabSystemExpandDramSize": "Επέκταση μεγέθους DRAM στα 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Αγνόηση υπηρεσιών που λείπουν", + "SettingsTabGraphics": "Γραφικά", + "SettingsTabGraphicsAPI": "API Γραφικά", + "SettingsTabGraphicsEnableShaderCache": "Ενεργοποίηση Προσωρινής Μνήμης Shader", + "SettingsTabGraphicsAnisotropicFiltering": "Ανισότροπο Φιλτράρισμα:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Αυτόματο", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Κλίμακα Ανάλυσης:", + "SettingsTabGraphicsResolutionScaleCustom": "Προσαρμοσμένο (Δεν συνιστάται)", + "SettingsTabGraphicsResolutionScaleNative": "Εγγενής (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", + "SettingsTabGraphicsAspectRatio": "Αναλογία Απεικόνισης:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Έκταση σε όλο το παράθυρο", + "SettingsTabGraphicsDeveloperOptions": "Επιλογές Προγραμματιστή", + "SettingsTabGraphicsShaderDumpPath": "Τοποθεσία Shaders Γραφικών:", + "SettingsTabLogging": "Καταγραφή", + "SettingsTabLoggingLogging": "Καταγραφή", + "SettingsTabLoggingEnableLoggingToFile": "Ενεργοποίηση Καταγραφής Αρχείου", + "SettingsTabLoggingEnableStubLogs": "Ενεργοποίηση Καταγραφής Stub", + "SettingsTabLoggingEnableInfoLogs": "Ενεργοποίηση Καταγραφής Πληροφοριών", + "SettingsTabLoggingEnableWarningLogs": "Ενεργοποίηση Καταγραφής Προειδοποίησης", + "SettingsTabLoggingEnableErrorLogs": "Ενεργοποίηση Καταγραφής Σφαλμάτων", + "SettingsTabLoggingEnableTraceLogs": "Ενεργοποίηση Καταγραφής Ιχνών", + "SettingsTabLoggingEnableGuestLogs": "Ενεργοποίηση Καταγραφής Επισκεπτών", + "SettingsTabLoggingEnableFsAccessLogs": "Ενεργοποίηση Καταγραφής Πρόσβασης FS", + "SettingsTabLoggingFsGlobalAccessLogMode": "Λειτουργία Καταγραφής Καθολικής Πρόσβασης FS:", + "SettingsTabLoggingDeveloperOptions": "Επιλογές Προγραμματιστή (ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Η απόδοση Θα μειωθεί)", + "SettingsTabLoggingDeveloperOptionsNote": "ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Θα μειώσει την απόδοση", + "SettingsTabLoggingGraphicsBackendLogLevel": "Επίπεδο Καταγραφής Διεπαφής Γραφικών:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Κανένα", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Σφάλμα", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Επιβραδύνσεις", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Όλα", + "SettingsTabLoggingEnableDebugLogs": "Ενεργοποίηση Αρχείων Καταγραφής Εντοπισμού Σφαλμάτων", + "SettingsTabInput": "Χειρισμός", + "SettingsTabInputEnableDockedMode": "Ενεργοποίηση Docked Mode", + "SettingsTabInputDirectKeyboardAccess": "Άμεση Πρόσβαση στο Πληκτρολόγιο", + "SettingsButtonSave": "Αποθήκευση", + "SettingsButtonClose": "Κλείσιμο", + "SettingsButtonOk": "ΟΚ", + "SettingsButtonCancel": "Ακύρωση", + "SettingsButtonApply": "Εφαρμογή", + "ControllerSettingsPlayer": "Παίχτης", + "ControllerSettingsPlayer1": "Παίχτης 1", + "ControllerSettingsPlayer2": "Παίχτης 2", + "ControllerSettingsPlayer3": "Παίχτης 3", + "ControllerSettingsPlayer4": "Παίχτης 4", + "ControllerSettingsPlayer5": "Παίχτης 5", + "ControllerSettingsPlayer6": "Παίχτης 6", + "ControllerSettingsPlayer7": "Παίχτης 7", + "ControllerSettingsPlayer8": "Παίχτης 8", + "ControllerSettingsHandheld": "Χειροκίνητο", + "ControllerSettingsInputDevice": "Συσκευή Χειρισμού", + "ControllerSettingsRefresh": "Ανανέωση", + "ControllerSettingsDeviceDisabled": "Απενεργοποιημένο", + "ControllerSettingsControllerType": "Τύπος Χειριστηρίου", + "ControllerSettingsControllerTypeHandheld": "Φορητό", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Ζεύγος JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "Αριστερό JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "Δεξί JoyCon", + "ControllerSettingsProfile": "Προφίλ", + "ControllerSettingsProfileDefault": "Προκαθορισμένο", + "ControllerSettingsLoad": "Φόρτωση", + "ControllerSettingsAdd": "Προσθήκη", + "ControllerSettingsRemove": "Αφαίρεση", + "ControllerSettingsButtons": "Κουμπιά", + "ControllerSettingsButtonA": "Α", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Κατευθυντικό Pad", + "ControllerSettingsDPadUp": "Πάνω", + "ControllerSettingsDPadDown": "Κάτω", + "ControllerSettingsDPadLeft": "Αριστερά", + "ControllerSettingsDPadRight": "Δεξιά", + "ControllerSettingsStickButton": "Κουμπί", + "ControllerSettingsStickUp": "Πάνω", + "ControllerSettingsStickDown": "Κάτω", + "ControllerSettingsStickLeft": "Αριστερά", + "ControllerSettingsStickRight": "Δεξιά", + "ControllerSettingsStickStick": "Μοχλός", + "ControllerSettingsStickInvertXAxis": "Αντιστροφή Μοχλού X", + "ControllerSettingsStickInvertYAxis": "Αντιστροφή Μοχλού Y", + "ControllerSettingsStickDeadzone": "Νεκρή Ζώνη:", + "ControllerSettingsLStick": "Αριστερός Μοχλός", + "ControllerSettingsRStick": "Δεξιός Μοχλός", + "ControllerSettingsTriggersLeft": "Αριστερή Σκανδάλη", + "ControllerSettingsTriggersRight": "Δεξιά Σκανδάλη", + "ControllerSettingsTriggersButtonsLeft": "Αριστερά Κουμπιά Σκανδάλης", + "ControllerSettingsTriggersButtonsRight": "Δεξιά Κουμπιά Σκανδάλης", + "ControllerSettingsTriggers": "Σκανδάλες", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Αριστερά Κουμπιά", + "ControllerSettingsExtraButtonsRight": "Δεξιά Κουμπιά", + "ControllerSettingsMisc": "Διάφορα", + "ControllerSettingsTriggerThreshold": "Κατώφλι Σκανδάλης:", + "ControllerSettingsMotion": "Κίνηση", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Κίνηση συμβατή με CemuHook", + "ControllerSettingsMotionControllerSlot": "Υποδοχή Χειριστηρίου:", + "ControllerSettingsMotionMirrorInput": "Καθρεπτισμός Χειρισμού", + "ControllerSettingsMotionRightJoyConSlot": "Δεξιά Υποδοχή JoyCon:", + "ControllerSettingsMotionServerHost": "Κεντρικός Υπολογιστής Διακομιστή:", + "ControllerSettingsMotionGyroSensitivity": "Ευαισθησία Γυροσκοπίου:", + "ControllerSettingsMotionGyroDeadzone": "Νεκρή Ζώνη Γυροσκοπίου:", + "ControllerSettingsSave": "Αποθήκευση", + "ControllerSettingsClose": "Κλείσιμο", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", + "UserProfilesSelectedUserProfile": "Επιλεγμένο Προφίλ Χρήστη:", + "UserProfilesSaveProfileName": "Αποθήκευση Ονόματος Προφίλ", + "UserProfilesChangeProfileImage": "Αλλαγή Εικόνας Προφίλ", + "UserProfilesAvailableUserProfiles": "Διαθέσιμα Προφίλ Χρηστών:", + "UserProfilesAddNewProfile": "Προσθήκη Νέου Προφίλ", + "UserProfilesDelete": "Διαγράφω", + "UserProfilesClose": "Κλείσιμο", + "ProfileNameSelectionWatermark": "Επιλέξτε ψευδώνυμο", + "ProfileImageSelectionTitle": "Επιλογή Εικόνας Προφίλ", + "ProfileImageSelectionHeader": "Επιλέξτε μία Εικόνα Προφίλ", + "ProfileImageSelectionNote": "Μπορείτε να εισαγάγετε μία προσαρμοσμένη εικόνα προφίλ ή να επιλέξετε ένα avatar από το Firmware", + "ProfileImageSelectionImportImage": "Εισαγωγή Αρχείου Εικόνας", + "ProfileImageSelectionSelectAvatar": "Επιλέξτε Avatar από Firmware", + "InputDialogTitle": "Διάλογος Εισαγωγής", + "InputDialogOk": "ΟΚ", + "InputDialogCancel": "Ακύρωση", + "InputDialogAddNewProfileTitle": "Επιλογή Ονόματος Προφίλ", + "InputDialogAddNewProfileHeader": "Εισαγωγή Ονόματος Προφίλ", + "InputDialogAddNewProfileSubtext": "(Σύνολο Χαρακτήρων: {0})", + "AvatarChoose": "Επιλογή", + "AvatarSetBackgroundColor": "Ορισμός Χρώματος Φόντου", + "AvatarClose": "Κλείσιμο", + "ControllerSettingsLoadProfileToolTip": "Φόρτωση Προφίλ", + "ControllerSettingsAddProfileToolTip": "Προσθήκη Προφίλ", + "ControllerSettingsRemoveProfileToolTip": "Κατάργηση Προφίλ", + "ControllerSettingsSaveProfileToolTip": "Αποθήκευση Προφίλ", + "MenuBarFileToolsTakeScreenshot": "Λήψη Στιγμιότυπου", + "MenuBarFileToolsHideUi": "Απόκρυψη UI", + "GameListContextMenuRunApplication": "Εκτέλεση Εφαρμογής", + "GameListContextMenuToggleFavorite": "Εναλλαγή Αγαπημένου", + "GameListContextMenuToggleFavoriteToolTip": "Εναλλαγή της Κατάστασης Αγαπημένο του Παιχνιδιού", + "SettingsTabGeneralTheme": "Theme:", + "SettingsTabGeneralThemeDark": "Dark", + "SettingsTabGeneralThemeLight": "Light", + "ControllerSettingsConfigureGeneral": "Παραμέτρων", + "ControllerSettingsRumble": "Δόνηση", + "ControllerSettingsRumbleStrongMultiplier": "Ισχυρός Πολλαπλασιαστής Δόνησης", + "ControllerSettingsRumbleWeakMultiplier": "Αδύναμος Πολλαπλασιαστής Δόνησης", + "DialogMessageSaveNotAvailableMessage": "Δεν υπάρχουν αποθηκευμένα δεδομένα για το {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Θέλετε να αποθηκεύσετε δεδομένα για αυτό το παιχνίδι;", + "DialogConfirmationTitle": "Ryujinx - Επιβεβαίωση", + "DialogUpdaterTitle": "Ryujinx - Ενημερωτής", + "DialogErrorTitle": "Ryujinx - Σφάλμα", + "DialogWarningTitle": "Ryujinx - Προειδοποίηση", + "DialogExitTitle": "Ryujinx - Έξοδος", + "DialogErrorMessage": "Το Ryujinx αντιμετώπισε σφάλμα", + "DialogExitMessage": "Είστε βέβαιοι ότι θέλετε να κλείσετε το Ryujinx;", + "DialogExitSubMessage": "Όλα τα μη αποθηκευμένα δεδομένα θα χαθούν!", + "DialogMessageCreateSaveErrorMessage": "Σφάλμα κατά τη δημιουργία των αποθηκευμένων δεδομένων: {0}", + "DialogMessageFindSaveErrorMessage": "Σφάλμα κατά την εύρεση των αποθηκευμένων δεδομένων: {0}", + "FolderDialogExtractTitle": "Επιλέξτε τον φάκελο στον οποίο θέλετε να εξαγάγετε", + "DialogNcaExtractionMessage": "Εξαγωγή ενότητας {0} από {1}...", + "DialogNcaExtractionTitle": "Ryujinx - NCA Εξαγωγέας Τμημάτων", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Αποτυχία εξαγωγής. Η κύρια NCA δεν υπήρχε στο επιλεγμένο αρχείο.", + "DialogNcaExtractionCheckLogErrorMessage": "Αποτυχία εξαγωγής. Διαβάστε το αρχείο καταγραφής για περισσότερες πληροφορίες.", + "DialogNcaExtractionSuccessMessage": "Η εξαγωγή ολοκληρώθηκε με επιτυχία.", + "DialogUpdaterConvertFailedMessage": "Αποτυχία μετατροπής της τρέχουσας έκδοσης Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Ακύρωση Ενημέρωσης!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Χρησιμοποιείτε ήδη την πιο ενημερωμένη έκδοση του Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Προέκυψε ένα σφάλμα στη λήψη πληροφοριών έκδοσης από τα GitHub Releases. Αυτό δύναται να συμβεί αν μία έκδοση χτίζεται αυτή τη στιγμή στα GitHub Actions. Παρακαλούμε προσπαθήστε αργότερα.", + "DialogUpdaterConvertFailedGithubMessage": "Αποτυχία μετατροπής της ληφθείσας έκδοσης Ryujinx από την έκδοση GitHub.", + "DialogUpdaterDownloadingMessage": "Λήψη Ενημέρωσης...", + "DialogUpdaterExtractionMessage": "Εξαγωγή Ενημέρωσης...", + "DialogUpdaterRenamingMessage": "Μετονομασία Ενημέρωσης...", + "DialogUpdaterAddingFilesMessage": "Προσθήκη Νέας Ενημέρωσης...", + "DialogUpdaterCompleteMessage": "Η Ενημέρωση Ολοκληρώθηκε!", + "DialogUpdaterRestartMessage": "Θέλετε να επανεκκινήσετε το Ryujinx τώρα;", + "DialogUpdaterNoInternetMessage": "Δεν είστε συνδεδεμένοι στο Διαδίκτυο!", + "DialogUpdaterNoInternetSubMessage": "Επαληθεύστε ότι έχετε σύνδεση στο Διαδίκτυο που λειτουργεί!", + "DialogUpdaterDirtyBuildMessage": "Δεν μπορείτε να ενημερώσετε μία Πρόχειρη Έκδοση του Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Κάντε λήψη του Ryujinx στη διεύθυνση https://ryujinx.org/ εάν αναζητάτε μία υποστηριζόμενη έκδοση.", + "DialogRestartRequiredMessage": "Απαιτείται Επανεκκίνηση", + "DialogThemeRestartMessage": "Το θέμα έχει αποθηκευτεί. Απαιτείται επανεκκίνηση για την εφαρμογή του θέματος.", + "DialogThemeRestartSubMessage": "Θέλετε να κάνετε επανεκκίνηση", + "DialogFirmwareInstallEmbeddedMessage": "Θα θέλατε να εγκαταστήσετε το Firmware που είναι ενσωματωμένο σε αυτό το παιχνίδι; (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", + "DialogFirmwareNoFirmwareInstalledMessage": "Δεν έχει εγκατασταθεί Firmware", + "DialogFirmwareInstalledMessage": "Το Firmware {0} εγκαταστάθηκε", + "DialogInstallFileTypesSuccessMessage": "Επιτυχής εγκατάσταση τύπων αρχείων!", + "DialogInstallFileTypesErrorMessage": "Απέτυχε η εγκατάσταση τύπων αρχείων.", + "DialogUninstallFileTypesSuccessMessage": "Επιτυχής απεγκατάσταση τύπων αρχείων!", + "DialogUninstallFileTypesErrorMessage": "Αποτυχία απεγκατάστασης τύπων αρχείων.", + "DialogOpenSettingsWindowLabel": "Άνοιγμα Παραθύρου Ρυθμίσεων", + "DialogControllerAppletTitle": "Applet Χειρισμού", + "DialogMessageDialogErrorExceptionMessage": "Σφάλμα εμφάνισης του διαλόγου Μηνυμάτων: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Σφάλμα εμφάνισης Λογισμικού Πληκτρολογίου: {0}", + "DialogErrorAppletErrorExceptionMessage": "Σφάλμα εμφάνισης του διαλόγου ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nΓια πληροφορίες σχετικά με τον τρόπο διόρθωσης του σφάλματος, ακολουθήστε τον Οδηγό Εγκατάστασης.", + "DialogUserErrorDialogTitle": "Σφάλμα Ryujinx ({0})", + "DialogAmiiboApiTitle": "API για Amiibo.", + "DialogAmiiboApiFailFetchMessage": "Παρουσιάστηκε σφάλμα κατά την ανάκτηση πληροφοριών από το API.", + "DialogAmiiboApiConnectErrorMessage": "Δεν είναι δυνατή η σύνδεση με τον διακομιστή Amiibo API. Η υπηρεσία μπορεί να είναι εκτός λειτουργίας ή μπορεί να χρειαστεί να επαληθεύσετε ότι έχετε ενεργή σύνδεσή στο Διαδίκτυο.", + "DialogProfileInvalidProfileErrorMessage": "Το προφίλ {0} δεν είναι συμβατό με το τρέχον σύστημα χειρισμού.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Το προεπιλεγμένο προφίλ δεν μπορεί να αντικατασταθεί", + "DialogProfileDeleteProfileTitle": "Διαγραφή Προφίλ", + "DialogProfileDeleteProfileMessage": "Αυτή η ενέργεια είναι μη αναστρέψιμη, είστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogWarning": "Προειδοποίηση", + "DialogPPTCDeletionMessage": "Πρόκειται να διαγράψετε την προσωρινή μνήμη PPTC για :\n\n{0}\n\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogPPTCDeletionErrorMessage": "Σφάλμα κατά την εκκαθάριση προσωρινής μνήμης PPTC στο {0}: {1}", + "DialogShaderDeletionMessage": "Πρόκειται να διαγράψετε την προσωρινή μνήμη Shader για :\n\n{0}\n\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogShaderDeletionErrorMessage": "Σφάλμα κατά την εκκαθάριση προσωρινής μνήμης Shader στο {0}: {1}", + "DialogRyujinxErrorMessage": "Το Ryujinx αντιμετώπισε σφάλμα", + "DialogInvalidTitleIdErrorMessage": "Σφάλμα UI: Το επιλεγμένο παιχνίδι δεν έχει έγκυρο αναγνωριστικό τίτλου", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Δεν βρέθηκε έγκυρο Firmware συστήματος στο {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Εγκατάσταση Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Θα εγκατασταθεί η έκδοση συστήματος {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nΑυτό θα αντικαταστήσει την τρέχουσα έκδοση συστήματος {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nΘέλετε να συνεχίσετε;", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Εγκατάσταση Firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Η έκδοση συστήματος {0} εγκαταστάθηκε με επιτυχία.", + "DialogUserProfileDeletionWarningMessage": "Δεν θα υπάρχουν άλλα προφίλ εάν διαγραφεί το επιλεγμένο", + "DialogUserProfileDeletionConfirmMessage": "Θέλετε να διαγράψετε το επιλεγμένο προφίλ", + "DialogUserProfileUnsavedChangesTitle": "Προσοχή - Μην Αποθηκευμένες Αλλαγές.", + "DialogUserProfileUnsavedChangesMessage": "Έχετε κάνει αλλαγές σε αυτό το προφίλ χρήστη που δεν έχουν αποθηκευτεί.", + "DialogUserProfileUnsavedChangesSubMessage": "Θέλετε να απορρίψετε τις αλλαγές σας;", + "DialogControllerSettingsModifiedConfirmMessage": "Οι τρέχουσες ρυθμίσεις χειρισμού έχουν ενημερωθεί.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Θέλετε να αποθηκεύσετε;", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", + "DialogDlcNoDlcErrorMessage": "Το αρχείο δεν περιέχει DLC για τον επιλεγμένο τίτλο!", + "DialogPerformanceCheckLoggingEnabledMessage": "Έχετε ενεργοποιημένη την καταγραφή εντοπισμού σφαλμάτων, η οποία έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται η απενεργοποίηση καταγραφής εντοπισμού σφαλμάτων. Θέλετε να απενεργοποιήσετε την καταγραφή τώρα;", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Έχετε ενεργοποιήσει το Shader Dumping, το οποίο έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται να απενεργοποιήσετε το Shader Dumping. Θέλετε να απενεργοποιήσετε τώρα το Shader Dumping;", + "DialogLoadAppGameAlreadyLoadedMessage": "Ένα παιχνίδι έχει ήδη φορτωθεί", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Σταματήστε την εξομοίωση ή κλείστε τον εξομοιωτή πριν ξεκινήσετε ένα άλλο παιχνίδι.", + "DialogUpdateAddUpdateErrorMessage": "Το αρχείο δεν περιέχει ενημέρωση για τον επιλεγμένο τίτλο!", + "DialogSettingsBackendThreadingWarningTitle": "Προειδοποίηση - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Το Ryujinx πρέπει να επανεκκινηθεί αφού αλλάξει αυτή η επιλογή για να εφαρμοστεί πλήρως. Ανάλογα με την πλατφόρμα σας, μπορεί να χρειαστεί να απενεργοποιήσετε με μη αυτόματο τρόπο το multithreading του ίδιου του προγράμματος οδήγησης όταν χρησιμοποιείτε το Ryujinx.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "SettingsTabGraphicsFeaturesOptions": "Χαρακτηριστικά", + "SettingsTabGraphicsBackendMultithreading": "Πολυνηματική Επεξεργασία Γραφικών:", + "CommonAuto": "Αυτόματο", + "CommonOff": "Ανενεργό", + "CommonOn": "Ενεργό", + "InputDialogYes": "Ναι", + "InputDialogNo": "Όχι", + "DialogProfileInvalidProfileNameErrorMessage": "Το όνομα αρχείου περιέχει μη έγκυρους χαρακτήρες. Παρακαλώ προσπαθήστε ξανά.", + "MenuBarOptionsPauseEmulation": "Παύση", + "MenuBarOptionsResumeEmulation": "Συνέχιση", + "AboutUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τον ιστότοπο Ryujinx στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutDisclaimerMessage": "Το Ryujinx δεν είναι συνδεδεμένο με τη Nintendo™,\nούτε με κανέναν από τους συνεργάτες της, με οποιονδήποτε τρόπο.", + "AboutAmiiboDisclaimerMessage": "Το AmiiboAPI (www.amiiboapi.com) χρησιμοποιείται\nστην προσομοίωση Amiibo.", + "AboutPatreonUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx Patreon στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutGithubUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx GitHub στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutDiscordUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε μία πρόσκληση στον διακομιστή Ryujinx Discord στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutTwitterUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx Twitter στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutRyujinxAboutTitle": "Σχετικά με:", + "AboutRyujinxAboutContent": "Το Ryujinx είναι ένας εξομοιωτής για το Nintendo Switch™.\nΥποστηρίξτε μας στο Patreon.\nΛάβετε όλα τα τελευταία νέα στο Twitter ή στο Discord.\nΟι προγραμματιστές που ενδιαφέρονται να συνεισφέρουν μπορούν να μάθουν περισσότερα στο GitHub ή στο Discord μας.", + "AboutRyujinxMaintainersTitle": "Συντηρείται από:", + "AboutRyujinxMaintainersContentTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Συνεισφέροντες στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutRyujinxSupprtersTitle": "Υποστηρίζεται στο Patreon από:", + "AmiiboSeriesLabel": "Σειρά Amiibo", + "AmiiboCharacterLabel": "Χαρακτήρας", + "AmiiboScanButtonLabel": "Σαρώστε το", + "AmiiboOptionsShowAllLabel": "Εμφάνιση όλων των Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Χρησιμοποιήστε τυχαίο αναγνωριστικό UUID", + "DlcManagerTableHeadingEnabledLabel": "Ενεργοποιημένο", + "DlcManagerTableHeadingTitleIdLabel": "Αναγνωριστικό τίτλου", + "DlcManagerTableHeadingContainerPathLabel": "Τοποθεσία DLC", + "DlcManagerTableHeadingFullPathLabel": "Πλήρης τοποθεσία", + "DlcManagerRemoveAllButton": "Αφαίρεση όλων", + "DlcManagerEnableAllButton": "Ενεργοποίηση Όλων", + "DlcManagerDisableAllButton": "Απενεργοποίηση Όλων", + "ModManagerDeleteAllButton": "Delete All", + "MenuBarOptionsChangeLanguage": "Αλλαξε γλώσσα", + "MenuBarShowFileTypes": "Εμφάνιση Τύπων Αρχείων", + "CommonSort": "Κατάταξη", + "CommonShowNames": "Εμφάνιση ονομάτων", + "CommonFavorite": "Αγαπημένα", + "OrderAscending": "Αύξουσα", + "OrderDescending": "Φθίνουσα", + "SettingsTabGraphicsFeatures": "Χαρακτηριστικά & Βελτιώσεις", + "ErrorWindowTitle": "Παράθυρο σφάλματος", + "ToggleDiscordTooltip": "Ενεργοποιεί ή απενεργοποιεί την Εμπλουτισμένη Παρουσία σας στο Discord", + "AddGameDirBoxTooltip": "Εισαγάγετε μία τοποθεσία παιχνιδιών για προσθήκη στη λίστα", + "AddGameDirTooltip": "Προσθέστε μία τοποθεσία παιχνιδιών στη λίστα", + "RemoveGameDirTooltip": "Αφαιρέστε την επιλεγμένη τοποθεσία παιχνιδιών", + "CustomThemeCheckTooltip": "Ενεργοποίηση ή απενεργοποίηση προσαρμοσμένων θεμάτων στο GUI", + "CustomThemePathTooltip": "Διαδρομή προς το προσαρμοσμένο θέμα GUI", + "CustomThemeBrowseTooltip": "Αναζητήστε ένα προσαρμοσμένο θέμα GUI", + "DockModeToggleTooltip": "Ενεργοποιήστε ή απενεργοποιήστε τη λειτουργία σύνδεσης", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "RegionTooltip": "Αλλαγή Περιοχής Συστήματος", + "LanguageTooltip": "Αλλαγή Γλώσσας Συστήματος", + "TimezoneTooltip": "Αλλαγή Ζώνης Ώρας Συστήματος", + "TimeTooltip": "Αλλαγή Ώρας Συστήματος", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "PptcToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί το PPTC", + "FsIntegrityToggleTooltip": "Ενεργοποιεί τους ελέγχους ακεραιότητας σε αρχεία περιεχομένου παιχνιδιού", + "AudioBackendTooltip": "Αλλαγή ήχου υποστήριξης", + "MemoryManagerTooltip": "Αλλάξτε τον τρόπο αντιστοίχισης και πρόσβασης στη μνήμη επισκέπτη. Επηρεάζει σε μεγάλο βαθμό την απόδοση της προσομοίωσης της CPU.", + "MemoryManagerSoftwareTooltip": "Χρησιμοποιήστε έναν πίνακα σελίδων λογισμικού για τη μετάφραση διευθύνσεων. Υψηλότερη ακρίβεια αλλά πιο αργή απόδοση.", + "MemoryManagerHostTooltip": "Απευθείας αντιστοίχιση της μνήμης στον χώρο διευθύνσεων υπολογιστή υποδοχής. Πολύ πιο γρήγορη μεταγλώττιση και εκτέλεση JIT.", + "MemoryManagerUnsafeTooltip": "Απευθείας χαρτογράφηση της μνήμης, αλλά μην καλύπτετε τη διεύθυνση εντός του χώρου διευθύνσεων επισκέπτη πριν από την πρόσβαση. Πιο γρήγορα, αλλά με κόστος ασφάλειας. Η εφαρμογή μπορεί να έχει πρόσβαση στη μνήμη από οπουδήποτε στο Ryujinx, επομένως εκτελείτε μόνο προγράμματα που εμπιστεύεστε με αυτήν τη λειτουργία.", + "UseHypervisorTooltip": "Χρησιμοποιήστε Hypervisor αντί για JIT. Βελτιώνει σημαντικά την απόδοση όταν διατίθεται, αλλά μπορεί να είναι ασταθής στην τρέχουσα κατάστασή του.", + "DRamTooltip": "Επεκτείνει την ποσότητα της μνήμης στο εξομοιούμενο σύστημα από 4 GiB σε 6 GiB", + "IgnoreMissingServicesTooltip": "Ενεργοποίηση ή απενεργοποίηση της αγνοώησης για υπηρεσίες που λείπουν", + "GraphicsBackendThreadingTooltip": "Ενεργοποίηση Πολυνηματικής Επεξεργασίας Γραφικών", + "GalThreadingTooltip": "Εκτελεί εντολές γραφικών σε ένα δεύτερο νήμα. Επιτρέπει την πολυνηματική μεταγλώττιση Shader σε χρόνο εκτέλεσης, μειώνει το τρεμόπαιγμα και βελτιώνει την απόδοση των προγραμμάτων οδήγησης χωρίς τη δική τους υποστήριξη πολλαπλών νημάτων. Ποικίλες κορυφαίες επιδόσεις σε προγράμματα οδήγησης με multithreading. Μπορεί να χρειαστεί επανεκκίνηση του Ryujinx για να απενεργοποιήσετε σωστά την ενσωματωμένη λειτουργία πολλαπλών νημάτων του προγράμματος οδήγησης ή ίσως χρειαστεί να το κάνετε χειροκίνητα για να έχετε την καλύτερη απόδοση.", + "ShaderCacheToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί την Προσωρινή Μνήμη Shader", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleEntryTooltip": "Κλίμακα ανάλυσης κινητής υποδιαστολής, όπως 1,5. Οι μη αναπόσπαστες τιμές είναι πιθανό να προκαλέσουν προβλήματα ή σφάλματα.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "ShaderDumpPathTooltip": "Τοποθεσία Εναπόθεσης Προσωρινής Μνήμης Shaders", + "FileLogTooltip": "Ενεργοποιεί ή απενεργοποιεί την καταγραφή σε ένα αρχείο στο δίσκο", + "StubLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής ατελειών", + "InfoLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής πληροφοριών", + "WarnLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής προειδοποιήσεων", + "ErrorLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής σφαλμάτων", + "TraceLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής ιχνών", + "GuestLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής επισκεπτών", + "FileAccessLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής πρόσβασης", + "FSAccessLogModeTooltip": "Ενεργοποιεί την έξοδο καταγραφής πρόσβασης FS στην κονσόλα. Οι πιθανοί τρόποι λειτουργίας είναι 0-3", + "DeveloperOptionTooltip": "Χρησιμοποιήστε με προσοχή", + "OpenGlLogLevel": "Απαιτεί τα κατάλληλα επίπεδα καταγραφής ενεργοποιημένα", + "DebugLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής εντοπισμού σφαλμάτων", + "LoadApplicationFileTooltip": "Ανοίξτε έναν επιλογέα αρχείων για να επιλέξετε ένα αρχείο συμβατό με το Switch για φόρτωση", + "LoadApplicationFolderTooltip": "Ανοίξτε έναν επιλογέα αρχείων για να επιλέξετε μία μη συσκευασμένη εφαρμογή, συμβατή με το Switch για φόρτωση", + "OpenRyujinxFolderTooltip": "Ανοίξτε το φάκελο συστήματος αρχείων Ryujinx", + "OpenRyujinxLogsTooltip": "Ανοίξτε το φάκελο στον οποίο διατηρούνται τα αρχεία καταγραφής", + "ExitTooltip": "Έξοδος από το Ryujinx", + "OpenSettingsTooltip": "Ανοίξτε το παράθυρο Ρυθμίσεων", + "OpenProfileManagerTooltip": "Ανοίξτε το παράθυρο Διαχείρισης Προφίλ Χρήστη", + "StopEmulationTooltip": "Σταματήστε την εξομοίωση του τρέχοντος παιχνιδιού και επιστρέψτε στην επιλογή παιχνιδιού", + "CheckUpdatesTooltip": "Ελέγξτε για ενημερώσεις του Ryujinx", + "OpenAboutTooltip": "Ανοίξτε το Παράθυρο Σχετικά", + "GridSize": "Μέγεθος Πλέγματος", + "GridSizeTooltip": "Αλλαγή μεγέθους στοιχείων πλέγματος", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Πορτογαλικά Βραζιλίας", + "AboutRyujinxContributorsButtonHeader": "Δείτε Όλους τους Συντελεστές", + "SettingsTabSystemAudioVolume": "Ενταση Ήχου: ", + "AudioVolumeTooltip": "Αλλαγή Έντασης Ήχου", + "SettingsTabSystemEnableInternetAccess": "Ενεργοποίηση πρόσβασης επισκέπτη στο Διαδίκτυο", + "EnableInternetAccessTooltip": "Επιτρέπει την πρόσβαση επισκέπτη στο Διαδίκτυο. Εάν ενεργοποιηθεί, η εξομοιωμένη κονσόλα Switch θα συμπεριφέρεται σαν να είναι συνδεδεμένη στο Διαδίκτυο. Λάβετε υπόψη ότι σε ορισμένες περιπτώσεις, οι εφαρμογές ενδέχεται να εξακολουθούν να έχουν πρόσβαση στο Διαδίκτυο, ακόμη και όταν αυτή η επιλογή είναι απενεργοποιημένη", + "GameListContextMenuManageCheatToolTip": "Διαχείριση Κόλπων", + "GameListContextMenuManageCheat": "Διαχείριση Κόλπων", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", + "ControllerSettingsStickRange": "Εύρος:", + "DialogStopEmulationTitle": "Ryujinx - Διακοπή εξομοίωσης", + "DialogStopEmulationMessage": "Είστε βέβαιοι ότι θέλετε να σταματήσετε την εξομοίωση;", + "SettingsTabCpu": "Επεξεργαστής", + "SettingsTabAudio": "Ήχος", + "SettingsTabNetwork": "Δίκτυο", + "SettingsTabNetworkConnection": "Σύνδεση δικτύου", + "SettingsTabCpuCache": "Προσωρινή Μνήμη CPU", + "SettingsTabCpuMemory": "Μνήμη CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Παρακαλούμε ενημερώστε το Ryujinx μέσω FlatHub.", + "UpdaterDisabledWarningTitle": "Ο Διαχειριστής Ενημερώσεων Είναι Απενεργοποιημένος!", + "ControllerSettingsRotate90": "Περιστροφή 90° Δεξιόστροφα", + "IconSize": "Μέγεθος Εικονιδίου", + "IconSizeTooltip": "Αλλάξτε μέγεθος εικονιδίων των παιχνιδιών", + "MenuBarOptionsShowConsole": "Εμφάνιση Κονσόλας", + "ShaderCachePurgeError": "Σφάλμα κατά την εκκαθάριση του shader cache στο {0}: {1}", + "UserErrorNoKeys": "Τα κλειδιά δεν βρέθηκαν", + "UserErrorNoFirmware": "Το firmware δε βρέθηκε", + "UserErrorFirmwareParsingFailed": "Σφάλμα ανάλυσης firmware", + "UserErrorApplicationNotFound": "Η εφαρμογή δε βρέθηκε", + "UserErrorUnknown": "Άγνωστο σφάλμα", + "UserErrorUndefined": "Αόριστο σφάλμα", + "UserErrorNoKeysDescription": "Το Ryujinx δεν κατάφερε να εντοπίσει το αρχείο 'prod.keys'", + "UserErrorNoFirmwareDescription": "Το Ryujinx δεν κατάφερε να εντοπίσει κανένα εγκατεστημένο firmware", + "UserErrorFirmwareParsingFailedDescription": "Το Ryujinx δεν κατάφερε να αναλύσει το συγκεκριμένο firmware. Αυτό συνήθως οφείλετε σε ξεπερασμένα/παλιά κλειδιά.", + "UserErrorApplicationNotFoundDescription": "Το Ryujinx δεν κατάφερε να εντοπίσει έγκυρη εφαρμογή στη συγκεκριμένη διαδρομή.", + "UserErrorUnknownDescription": "Παρουσιάστηκε άγνωστο σφάλμα.", + "UserErrorUndefinedDescription": "Παρουσιάστηκε ένα άγνωστο σφάλμα! Αυτό δεν πρέπει να συμβεί, παρακαλώ επικοινωνήστε με έναν προγραμματιστή!", + "OpenSetupGuideMessage": "Ανοίξτε τον Οδηγό Εγκατάστασης.", + "NoUpdate": "Καμία Eνημέρωση", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Πληροφορίες", + "RyujinxConfirm": "Ryujinx - Επιβεβαίωση", + "FileDialogAllTypes": "Όλοι οι τύποι", + "Never": "Ποτέ", + "SwkbdMinCharacters": "Πρέπει να έχει μήκος τουλάχιστον {0} χαρακτήρες", + "SwkbdMinRangeCharacters": "Πρέπει να έχει μήκος {0}-{1} χαρακτήρες", + "SoftwareKeyboard": "Εικονικό Πληκτρολόγιο", + "SoftwareKeyboardModeNumeric": "Πρέπει να είναι 0-9 ή '.' μόνο", + "SoftwareKeyboardModeAlphabet": "Πρέπει να μην είναι μόνο χαρακτήρες CJK", + "SoftwareKeyboardModeASCII": "Πρέπει να είναι μόνο κείμενο ASCII", + "ControllerAppletControllers": "Supported Controllers:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "UpdaterRenaming": "Μετονομασία Παλαιών Αρχείων...", + "UpdaterRenameFailed": "Δεν ήταν δυνατή η μετονομασία του αρχείου: {0}", + "UpdaterAddingFiles": "Προσθήκη Νέων Αρχείων...", + "UpdaterExtracting": "Εξαγωγή Ενημέρωσης...", + "UpdaterDownloading": "Λήψη Ενημέρωσης...", + "Game": "Παιχνίδι", + "Docked": "Προσκολλημένο", + "Handheld": "Χειροκίνητο", + "ConnectionError": "Σφάλμα Σύνδεσης.", + "AboutPageDeveloperListMore": "{0} και περισσότερα...", + "ApiError": "Σφάλμα API.", + "LoadingHeading": "Φόρτωση {0}", + "CompilingPPTC": "Μεταγλώττιση του PTC", + "CompilingShaders": "Σύνταξη των Shaders", + "AllKeyboards": "Όλα τα πληκτρολόγια", + "OpenFileDialogTitle": "Επιλέξτε ένα υποστηριζόμενο αρχείο για άνοιγμα", + "OpenFolderDialogTitle": "Επιλέξτε ένα φάκελο με ένα αποσυμπιεσμένο παιχνίδι", + "AllSupportedFormats": "Όλες Οι Υποστηριζόμενες Μορφές", + "RyujinxUpdater": "Ryujinx Ενημερωτής", + "SettingsTabHotkeys": "Συντομεύσεις Πληκτρολογίου", + "SettingsTabHotkeysHotkeys": "Συντομεύσεις Πληκτρολογίου", + "SettingsTabHotkeysToggleVsyncHotkey": "Εναλλαγή VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Στιγμιότυπο Οθόνης:", + "SettingsTabHotkeysShowUiHotkey": "Εμφάνιση Διεπαφής Χρήστη:", + "SettingsTabHotkeysPauseHotkey": "Παύση:", + "SettingsTabHotkeysToggleMuteHotkey": "Σίγαση:", + "ControllerMotionTitle": "Ρυθμίσεις Ελέγχου Κίνησης", + "ControllerRumbleTitle": "Ρυθμίσεις Δόνησης", + "SettingsSelectThemeFileDialogTitle": "Επιλογή Αρχείου Θέματος", + "SettingsXamlThemeFile": "Αρχείο Θέματος Xaml", + "AvatarWindowTitle": "Διαχείριση Λογαριασμών - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Άγνωστο", + "Usage": "Χρήση", + "Writable": "Εγγράψιμο", + "SelectDlcDialogTitle": "Επιλογή αρχείων DLC", + "SelectUpdateDialogTitle": "Επιλογή αρχείων ενημέρωσης", + "SelectModDialogTitle": "Select mod directory", + "UserProfileWindowTitle": "Διαχειριστής Προφίλ Χρήστη", + "CheatWindowTitle": "Διαχειριστής των Cheats", + "DlcWindowTitle": "Downloadable Content Manager", + "ModWindowTitle": "Manage Mods for {0} ({1})", + "UpdateWindowTitle": "Διαχειριστής Ενημερώσεων Τίτλου", + "CheatWindowHeading": "Διαθέσιμα Cheats για {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", + "ModWindowHeading": "{0} Mod(s)", + "UserProfilesEditProfile": "Επεξεργασία Επιλεγμένων", + "Cancel": "Ακύρωση", + "Save": "Αποθήκευση", + "Discard": "Απόρριψη", + "Paused": "Σε παύση", + "UserProfilesSetProfileImage": "Ορισμός Εικόνας Προφίλ", + "UserProfileEmptyNameError": "Απαιτείται όνομα", + "UserProfileNoImageError": "Η εικόνα προφίλ πρέπει να οριστεί", + "GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Αύξηση της ανάλυσης:", + "SettingsTabHotkeysResScaleDownHotkey": "Μείωση της ανάλυσης:", + "UserProfilesName": "Όνομα:", + "UserProfilesUserId": "User Id:", + "SettingsTabGraphicsBackend": "Σύστημα Υποστήριξης Γραφικών", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsEnableTextureRecompression": "Ενεργοποίηση Επανασυμπίεσης Των Texture", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsTabGraphicsPreferredGpu": "Προτιμώμενη GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Επιλέξτε την κάρτα γραφικών η οποία θα χρησιμοποιηθεί από το Vulkan.\n\nΔεν επηρεάζει το OpenGL.\n\nΔιαλέξτε την GPU που διαθέτει την υπόδειξη \"dGPU\" αν δεν είστε βέβαιοι. Αν δεν υπάρχει κάποιαν, το πειράξετε", + "SettingsAppRequiredRestartMessage": "Απαιτείται Επανεκκίνηση Του Ryujinx", + "SettingsGpuBackendRestartMessage": "Οι ρυθμίσεις GPU έχουν αλλαχτεί. Θα χρειαστεί επανεκκίνηση του Ryujinx για να τεθούν σε ισχύ.", + "SettingsGpuBackendRestartSubMessage": "Θέλετε να κάνετε επανεκκίνηση τώρα;", + "RyujinxUpdaterMessage": "Θέλετε να ενημερώσετε το Ryujinx στην πιο πρόσφατη έκδοση:", + "SettingsTabHotkeysVolumeUpHotkey": "Αύξηση Έντασης:", + "SettingsTabHotkeysVolumeDownHotkey": "Μείωση Έντασης:", + "SettingsEnableMacroHLE": "Ενεργοποίηση του Macro HLE", + "SettingsEnableMacroHLETooltip": "Προσομοίωση του κώδικα GPU Macro .\n\nΒελτιώνει την απόδοση, αλλά μπορεί να προκαλέσει γραφικά προβλήματα σε μερικά παιχνίδια.\n\nΑφήστε ΕΝΕΡΓΟ αν δεν είστε σίγουροι.", + "SettingsEnableColorSpacePassthrough": "Διέλευση Χρωματικού Χώρου", + "SettingsEnableColorSpacePassthroughTooltip": "Σκηνοθετεί το σύστημα υποστήριξης του Vulkan για να περάσει από πληροφορίες χρώματος χωρίς να καθορίσει έναν χρωματικό χώρο. Για χρήστες με ευρείες οθόνες γκάμας, αυτό μπορεί να οδηγήσει σε πιο ζωηρά χρώματα, με κόστος την ορθότητα του χρώματος.", + "VolumeShort": "Έντ.", + "UserProfilesManageSaves": "Διαχείριση Των Save", + "DeleteUserSave": "Επιθυμείτε να διαγράψετε το save χρήστη για το συγκεκριμένο παιχνίδι;", + "IrreversibleActionNote": "Αυτή η ενέργεια είναι μη αναστρέψιμη.", + "SaveManagerHeading": "Manage Saves for {0}", + "SaveManagerTitle": "Διαχειριστής Save", + "Name": "Όνομα", + "Size": "Μέγεθος", + "Search": "Αναζήτηση", + "UserProfilesRecoverLostAccounts": "Ανάκτηση Χαμένων Λογαριασμών", + "Recover": "Ανάκτηση", + "UserProfilesRecoverHeading": "Βρέθηκαν save για τους ακόλουθους λογαριασμούς", + "UserProfilesRecoverEmptyList": "Δεν υπάρχουν προφίλ για ανάκτηση", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAALabel": "Anti-Aliasing", + "GraphicsScalingFilterLabel": "Φίλτρο Κλιμάκωσης:", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "Επίπεδο", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "SmaaLow": "Χαμηλό SMAA", + "SmaaMedium": " Μεσαίο SMAA", + "SmaaHigh": "Υψηλό SMAA", + "SmaaUltra": "Oύλτρα SMAA", + "UserEditorTitle": "Επεξεργασία Χρήστη", + "UserEditorTitleCreate": "Δημιουργία Χρήστη", + "SettingsTabNetworkInterface": "Διεπαφή Δικτύου", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceDefault": "Προεπιλογή", + "PackagingShaders": "Shaders Συσκευασίας", + "AboutChangelogButton": "Προβολή αρχείου αλλαγών στο GitHub", + "AboutChangelogButtonTooltipMessage": "Κάντε κλικ για να ανοίξετε το αρχείο αλλαγών για αυτήν την έκδοση στο προεπιλεγμένο πρόγραμμα περιήγησης σας.", + "SettingsTabNetworkMultiplayer": "Πολλαπλοί παίκτες", + "MultiplayerMode": "Λειτουργία:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json new file mode 100644 index 00000000..74e18056 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -0,0 +1,784 @@ +{ + "Language": "English (US)", + "MenuBarFileOpenApplet": "Open Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Open Mii Editor Applet in Standalone mode", + "SettingsTabInputDirectMouseAccess": "Direct Mouse Access", + "SettingsTabSystemMemoryManagerMode": "Memory Manager Mode:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (fast)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (fastest, unsafe)", + "SettingsTabSystemUseHypervisor": "Use Hypervisor", + "MenuBarFile": "_File", + "MenuBarFileOpenFromFile": "_Load Application From File", + "MenuBarFileOpenFromFileError": "No applications found in selected file.", + "MenuBarFileOpenUnpacked": "Load _Unpacked Game", + "MenuBarFileOpenEmuFolder": "Open Ryujinx Folder", + "MenuBarFileOpenLogsFolder": "Open Logs Folder", + "MenuBarFileExit": "_Exit", + "MenuBarOptions": "_Options", + "MenuBarOptionsToggleFullscreen": "Toggle Fullscreen", + "MenuBarOptionsStartGamesInFullscreen": "Start Games in Fullscreen Mode", + "MenuBarOptionsStopEmulation": "Stop Emulation", + "MenuBarOptionsSettings": "_Settings", + "MenuBarOptionsManageUserProfiles": "_Manage User Profiles", + "MenuBarActions": "_Actions", + "MenuBarOptionsSimulateWakeUpMessage": "Simulate Wake-up message", + "MenuBarActionsScanAmiibo": "Scan An Amiibo", + "MenuBarTools": "_Tools", + "MenuBarToolsInstallFirmware": "Install Firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Install a firmware from a directory", + "MenuBarToolsManageFileTypes": "Manage file types", + "MenuBarToolsInstallFileTypes": "Install file types", + "MenuBarToolsUninstallFileTypes": "Uninstall file types", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "_Help", + "MenuBarHelpCheckForUpdates": "Check for Updates", + "MenuBarHelpAbout": "About", + "MenuSearch": "Search...", + "GameListHeaderFavorite": "Fav", + "GameListHeaderIcon": "Icon", + "GameListHeaderApplication": "Name", + "GameListHeaderDeveloper": "Developer", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Play Time", + "GameListHeaderLastPlayed": "Last Played", + "GameListHeaderFileExtension": "File Ext", + "GameListHeaderFileSize": "File Size", + "GameListHeaderPath": "Path", + "GameListContextMenuOpenUserSaveDirectory": "Open User Save Directory", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Opens the directory which contains Application's User Save", + "GameListContextMenuOpenDeviceSaveDirectory": "Open Device Save Directory", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Opens the directory which contains Application's Device Save", + "GameListContextMenuOpenBcatSaveDirectory": "Open BCAT Save Directory", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Opens the directory which contains Application's BCAT Save", + "GameListContextMenuManageTitleUpdates": "Manage Title Updates", + "GameListContextMenuManageTitleUpdatesToolTip": "Opens the Title Update management window", + "GameListContextMenuManageDlc": "Manage DLC", + "GameListContextMenuManageDlcToolTip": "Opens the DLC management window", + "GameListContextMenuCacheManagement": "Cache Management", + "GameListContextMenuCacheManagementPurgePptc": "Queue PPTC Rebuild", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Trigger PPTC to rebuild at boot time on the next game launch", + "GameListContextMenuCacheManagementPurgeShaderCache": "Purge Shader Cache", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Deletes Application's shader cache", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Open PPTC Directory", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Opens the directory which contains Application's PPTC cache", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Open Shader Cache Directory", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Opens the directory which contains Application's shader cache", + "GameListContextMenuExtractData": "Extract Data", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extract the ExeFS section from Application's current config (including updates)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extract the RomFS section from Application's current config (including updates)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)", + "GameListContextMenuCreateShortcut": "Create Application Shortcut", + "GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "StatusBarGamesLoaded": "{0}/{1} Games Loaded", + "StatusBarSystemVersion": "System Version: {0}", + "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", + "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart", + "LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently", + "LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.", + "LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.", + "Settings": "Settings", + "SettingsTabGeneral": "User Interface", + "SettingsTabGeneralGeneral": "General", + "SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch", + "SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", + "SettingsTabGeneralHideCursor": "Hide Cursor:", + "SettingsTabGeneralHideCursorNever": "Never", + "SettingsTabGeneralHideCursorOnIdle": "On Idle", + "SettingsTabGeneralHideCursorAlways": "Always", + "SettingsTabGeneralGameDirectories": "Game Directories", + "SettingsTabGeneralAdd": "Add", + "SettingsTabGeneralRemove": "Remove", + "SettingsTabSystem": "System", + "SettingsTabSystemCore": "Core", + "SettingsTabSystemSystemRegion": "System Region:", + "SettingsTabSystemSystemRegionJapan": "Japan", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europe", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Korea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "System Language:", + "SettingsTabSystemSystemLanguageJapanese": "Japanese", + "SettingsTabSystemSystemLanguageAmericanEnglish": "American English", + "SettingsTabSystemSystemLanguageFrench": "French", + "SettingsTabSystemSystemLanguageGerman": "German", + "SettingsTabSystemSystemLanguageItalian": "Italian", + "SettingsTabSystemSystemLanguageSpanish": "Spanish", + "SettingsTabSystemSystemLanguageChinese": "Chinese", + "SettingsTabSystemSystemLanguageKorean": "Korean", + "SettingsTabSystemSystemLanguageDutch": "Dutch", + "SettingsTabSystemSystemLanguagePortuguese": "Portuguese", + "SettingsTabSystemSystemLanguageRussian": "Russian", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanese", + "SettingsTabSystemSystemLanguageBritishEnglish": "British English", + "SettingsTabSystemSystemLanguageCanadianFrench": "Canadian French", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Latin American Spanish", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Simplified Chinese", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Traditional Chinese", + "SettingsTabSystemSystemTimeZone": "System TimeZone:", + "SettingsTabSystemSystemTime": "System Time:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Integrity Checks", + "SettingsTabSystemAudioBackend": "Audio Backend:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": "May cause instability", + "SettingsTabSystemExpandDramSize": "Use alternative memory layout (Developers)", + "SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services", + "SettingsTabGraphics": "Graphics", + "SettingsTabGraphicsAPI": "Graphics API", + "SettingsTabGraphicsEnableShaderCache": "Enable Shader Cache", + "SettingsTabGraphicsAnisotropicFiltering": "Anisotropic Filtering:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Resolution Scale:", + "SettingsTabGraphicsResolutionScaleCustom": "Custom (Not recommended)", + "SettingsTabGraphicsResolutionScaleNative": "Native (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Not recommended)", + "SettingsTabGraphicsAspectRatio": "Aspect Ratio:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Stretch to Fit Window", + "SettingsTabGraphicsDeveloperOptions": "Developer Options", + "SettingsTabGraphicsShaderDumpPath": "Graphics Shader Dump Path:", + "SettingsTabLogging": "Logging", + "SettingsTabLoggingLogging": "Logging", + "SettingsTabLoggingEnableLoggingToFile": "Enable Logging to File", + "SettingsTabLoggingEnableStubLogs": "Enable Stub Logs", + "SettingsTabLoggingEnableInfoLogs": "Enable Info Logs", + "SettingsTabLoggingEnableWarningLogs": "Enable Warning Logs", + "SettingsTabLoggingEnableErrorLogs": "Enable Error Logs", + "SettingsTabLoggingEnableTraceLogs": "Enable Trace Logs", + "SettingsTabLoggingEnableGuestLogs": "Enable Guest Logs", + "SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:", + "SettingsTabLoggingDeveloperOptions": "Developer Options", + "SettingsTabLoggingDeveloperOptionsNote": "WARNING: Will reduce performance", + "SettingsTabLoggingGraphicsBackendLogLevel": "Graphics Backend Log Level:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "None", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Error", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Slowdowns", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "All", + "SettingsTabLoggingEnableDebugLogs": "Enable Debug Logs", + "SettingsTabInput": "Input", + "SettingsTabInputEnableDockedMode": "Docked Mode", + "SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access", + "SettingsButtonSave": "Save", + "SettingsButtonClose": "Close", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Cancel", + "SettingsButtonApply": "Apply", + "ControllerSettingsPlayer": "Player", + "ControllerSettingsPlayer1": "Player 1", + "ControllerSettingsPlayer2": "Player 2", + "ControllerSettingsPlayer3": "Player 3", + "ControllerSettingsPlayer4": "Player 4", + "ControllerSettingsPlayer5": "Player 5", + "ControllerSettingsPlayer6": "Player 6", + "ControllerSettingsPlayer7": "Player 7", + "ControllerSettingsPlayer8": "Player 8", + "ControllerSettingsHandheld": "Handheld", + "ControllerSettingsInputDevice": "Input Device", + "ControllerSettingsRefresh": "Refresh", + "ControllerSettingsDeviceDisabled": "Disabled", + "ControllerSettingsControllerType": "Controller Type", + "ControllerSettingsControllerTypeHandheld": "Handheld", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Pair", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Left", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Right", + "ControllerSettingsProfile": "Profile", + "ControllerSettingsProfileDefault": "Default", + "ControllerSettingsLoad": "Load", + "ControllerSettingsAdd": "Add", + "ControllerSettingsRemove": "Remove", + "ControllerSettingsButtons": "Buttons", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Directional Pad", + "ControllerSettingsDPadUp": "Up", + "ControllerSettingsDPadDown": "Down", + "ControllerSettingsDPadLeft": "Left", + "ControllerSettingsDPadRight": "Right", + "ControllerSettingsStickButton": "Button", + "ControllerSettingsStickUp": "Up", + "ControllerSettingsStickDown": "Down", + "ControllerSettingsStickLeft": "Left", + "ControllerSettingsStickRight": "Right", + "ControllerSettingsStickStick": "Stick", + "ControllerSettingsStickInvertXAxis": "Invert Stick X", + "ControllerSettingsStickInvertYAxis": "Invert Stick Y", + "ControllerSettingsStickDeadzone": "Deadzone:", + "ControllerSettingsLStick": "Left Stick", + "ControllerSettingsRStick": "Right Stick", + "ControllerSettingsTriggersLeft": "Triggers Left", + "ControllerSettingsTriggersRight": "Triggers Right", + "ControllerSettingsTriggersButtonsLeft": "Trigger Buttons Left", + "ControllerSettingsTriggersButtonsRight": "Trigger Buttons Right", + "ControllerSettingsTriggers": "Triggers", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Buttons Left", + "ControllerSettingsExtraButtonsRight": "Buttons Right", + "ControllerSettingsMisc": "Miscellaneous", + "ControllerSettingsTriggerThreshold": "Trigger Threshold:", + "ControllerSettingsMotion": "Motion", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Use CemuHook compatible motion", + "ControllerSettingsMotionControllerSlot": "Controller Slot:", + "ControllerSettingsMotionMirrorInput": "Mirror Input", + "ControllerSettingsMotionRightJoyConSlot": "Right JoyCon Slot:", + "ControllerSettingsMotionServerHost": "Server Host:", + "ControllerSettingsMotionGyroSensitivity": "Gyro Sensitivity:", + "ControllerSettingsMotionGyroDeadzone": "Gyro Deadzone:", + "ControllerSettingsSave": "Save", + "ControllerSettingsClose": "Close", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", + "UserProfilesSelectedUserProfile": "Selected User Profile:", + "UserProfilesSaveProfileName": "Save Profile Name", + "UserProfilesChangeProfileImage": "Change Profile Image", + "UserProfilesAvailableUserProfiles": "Available User Profiles:", + "UserProfilesAddNewProfile": "Create Profile", + "UserProfilesDelete": "Delete", + "UserProfilesClose": "Close", + "ProfileNameSelectionWatermark": "Choose a nickname", + "ProfileImageSelectionTitle": "Profile Image Selection", + "ProfileImageSelectionHeader": "Choose a profile Image", + "ProfileImageSelectionNote": "You may import a custom profile image, or select an avatar from system firmware", + "ProfileImageSelectionImportImage": "Import Image File", + "ProfileImageSelectionSelectAvatar": "Select Firmware Avatar", + "InputDialogTitle": "Input Dialog", + "InputDialogOk": "OK", + "InputDialogCancel": "Cancel", + "InputDialogAddNewProfileTitle": "Choose the Profile Name", + "InputDialogAddNewProfileHeader": "Please Enter a Profile Name", + "InputDialogAddNewProfileSubtext": "(Max Length: {0})", + "AvatarChoose": "Choose Avatar", + "AvatarSetBackgroundColor": "Set Background Color", + "AvatarClose": "Close", + "ControllerSettingsLoadProfileToolTip": "Load Profile", + "ControllerSettingsAddProfileToolTip": "Add Profile", + "ControllerSettingsRemoveProfileToolTip": "Remove Profile", + "ControllerSettingsSaveProfileToolTip": "Save Profile", + "MenuBarFileToolsTakeScreenshot": "Take Screenshot", + "MenuBarFileToolsHideUi": "Hide UI", + "GameListContextMenuRunApplication": "Run Application", + "GameListContextMenuToggleFavorite": "Toggle Favorite", + "GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game", + "SettingsTabGeneralTheme": "Theme:", + "SettingsTabGeneralThemeAuto": "Auto", + "SettingsTabGeneralThemeDark": "Dark", + "SettingsTabGeneralThemeLight": "Light", + "ControllerSettingsConfigureGeneral": "Configure", + "ControllerSettingsRumble": "Rumble", + "ControllerSettingsRumbleStrongMultiplier": "Strong Rumble Multiplier", + "ControllerSettingsRumbleWeakMultiplier": "Weak Rumble Multiplier", + "DialogMessageSaveNotAvailableMessage": "There is no savedata for {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Would you like to create savedata for this game?", + "DialogConfirmationTitle": "Ryujinx - Confirmation", + "DialogUpdaterTitle": "Ryujinx - Updater", + "DialogErrorTitle": "Ryujinx - Error", + "DialogWarningTitle": "Ryujinx - Warning", + "DialogExitTitle": "Ryujinx - Exit", + "DialogErrorMessage": "Ryujinx has encountered an error", + "DialogExitMessage": "Are you sure you want to close Ryujinx?", + "DialogExitSubMessage": "All unsaved data will be lost!", + "DialogMessageCreateSaveErrorMessage": "There was an error creating the specified savedata: {0}", + "DialogMessageFindSaveErrorMessage": "There was an error finding the specified savedata: {0}", + "FolderDialogExtractTitle": "Choose the folder to extract into", + "DialogNcaExtractionMessage": "Extracting {0} section from {1}...", + "DialogNcaExtractionTitle": "Ryujinx - NCA Section Extractor", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Extraction failure. The main NCA was not present in the selected file.", + "DialogNcaExtractionCheckLogErrorMessage": "Extraction failure. Read the log file for further information.", + "DialogNcaExtractionSuccessMessage": "Extraction completed successfully.", + "DialogUpdaterConvertFailedMessage": "Failed to convert the current Ryujinx version.", + "DialogUpdaterCancelUpdateMessage": "Cancelling Update!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "You are already using the most updated version of Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "An error has occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes.", + "DialogUpdaterConvertFailedGithubMessage": "Failed to convert the received Ryujinx version from Github Release.", + "DialogUpdaterDownloadingMessage": "Downloading Update...", + "DialogUpdaterExtractionMessage": "Extracting Update...", + "DialogUpdaterRenamingMessage": "Renaming Update...", + "DialogUpdaterAddingFilesMessage": "Adding New Update...", + "DialogUpdaterCompleteMessage": "Update Complete!", + "DialogUpdaterRestartMessage": "Do you want to restart Ryujinx now?", + "DialogUpdaterNoInternetMessage": "You are not connected to the Internet!", + "DialogUpdaterNoInternetSubMessage": "Please verify that you have a working Internet connection!", + "DialogUpdaterDirtyBuildMessage": "You Cannot update a Dirty build of Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.", + "DialogRestartRequiredMessage": "Restart Required", + "DialogThemeRestartMessage": "Theme has been saved. A restart is needed to apply the theme.", + "DialogThemeRestartSubMessage": "Do you want to restart", + "DialogFirmwareInstallEmbeddedMessage": "Would you like to install the firmware embedded in this game? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", + "DialogFirmwareNoFirmwareInstalledMessage": "No Firmware Installed", + "DialogFirmwareInstalledMessage": "Firmware {0} was installed", + "DialogInstallFileTypesSuccessMessage": "Successfully installed file types!", + "DialogInstallFileTypesErrorMessage": "Failed to install file types.", + "DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!", + "DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.", + "DialogOpenSettingsWindowLabel": "Open Settings Window", + "DialogControllerAppletTitle": "Controller Applet", + "DialogMessageDialogErrorExceptionMessage": "Error displaying Message Dialog: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Error displaying Software Keyboard: {0}", + "DialogErrorAppletErrorExceptionMessage": "Error displaying ErrorApplet Dialog: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nFor more information on how to fix this error, follow our Setup Guide.", + "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "An error occured while fetching information from the API.", + "DialogAmiiboApiConnectErrorMessage": "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online.", + "DialogProfileInvalidProfileErrorMessage": "Profile {0} is incompatible with the current input configuration system.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Default Profile can not be overwritten", + "DialogProfileDeleteProfileTitle": "Deleting Profile", + "DialogProfileDeleteProfileMessage": "This action is irreversible, are you sure you want to continue?", + "DialogWarning": "Warning", + "DialogPPTCDeletionMessage": "You are about to queue a PPTC rebuild on the next boot of:\n\n{0}\n\nAre you sure you want to proceed?", + "DialogPPTCDeletionErrorMessage": "Error purging PPTC cache at {0}: {1}", + "DialogShaderDeletionMessage": "You are about to delete the Shader cache for :\n\n{0}\n\nAre you sure you want to proceed?", + "DialogShaderDeletionErrorMessage": "Error purging Shader cache at {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx has encountered an error", + "DialogInvalidTitleIdErrorMessage": "UI error: The selected game did not have a valid title ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "A valid system firmware was not found in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Install Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "System version {0} will be installed.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nThis will replace the current system version {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDo you want to continue?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installing firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "System version {0} successfully installed.", + "DialogUserProfileDeletionWarningMessage": "There would be no other profiles to be opened if selected profile is deleted", + "DialogUserProfileDeletionConfirmMessage": "Do you want to delete the selected profile", + "DialogUserProfileUnsavedChangesTitle": "Warning - Unsaved Changes", + "DialogUserProfileUnsavedChangesMessage": "You have made changes to this user profile that have not been saved.", + "DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?", + "DialogControllerSettingsModifiedConfirmMessage": "The current controller settings has been updated.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Do you want to save?", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", + "DialogDlcNoDlcErrorMessage": "The specified file does not contain a DLC for the selected title!", + "DialogPerformanceCheckLoggingEnabledMessage": "You have trace logging enabled, which is designed to be used by developers only.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "You have shader dumping enabled, which is designed to be used by developers only.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?", + "DialogLoadAppGameAlreadyLoadedMessage": "A game has already been loaded", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Please stop emulation or close the emulator before launching another game.", + "DialogUpdateAddUpdateErrorMessage": "The specified file does not contain an update for the selected title!", + "DialogSettingsBackendThreadingWarningTitle": "Warning - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "SettingsTabGraphicsFeaturesOptions": "Features", + "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading:", + "CommonAuto": "Auto", + "CommonOff": "Off", + "CommonOn": "On", + "InputDialogYes": "Yes", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "The file name contains invalid characters. Please try again.", + "MenuBarOptionsPauseEmulation": "Pause", + "MenuBarOptionsResumeEmulation": "Resume", + "AboutUrlTooltipMessage": "Click to open the Ryujinx website in your default browser.", + "AboutDisclaimerMessage": "Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) is used\nin our Amiibo emulation.", + "AboutPatreonUrlTooltipMessage": "Click to open the Ryujinx Patreon page in your default browser.", + "AboutGithubUrlTooltipMessage": "Click to open the Ryujinx GitHub page in your default browser.", + "AboutDiscordUrlTooltipMessage": "Click to open an invite to the Ryujinx Discord server in your default browser.", + "AboutTwitterUrlTooltipMessage": "Click to open the Ryujinx Twitter page in your default browser.", + "AboutRyujinxAboutTitle": "About:", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nPlease support us on Patreon.\nGet all the latest news on our Twitter or Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", + "AboutRyujinxMaintainersTitle": "Maintained By:", + "AboutRyujinxMaintainersContentTooltipMessage": "Click to open the Contributors page in your default browser.", + "AboutRyujinxSupprtersTitle": "Supported on Patreon By:", + "AmiiboSeriesLabel": "Amiibo Series", + "AmiiboCharacterLabel": "Character", + "AmiiboScanButtonLabel": "Scan It", + "AmiiboOptionsShowAllLabel": "Show All Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Use Random tag Uuid", + "DlcManagerTableHeadingEnabledLabel": "Enabled", + "DlcManagerTableHeadingTitleIdLabel": "Title ID", + "DlcManagerTableHeadingContainerPathLabel": "Container Path", + "DlcManagerTableHeadingFullPathLabel": "Full Path", + "DlcManagerRemoveAllButton": "Remove All", + "DlcManagerEnableAllButton": "Enable All", + "DlcManagerDisableAllButton": "Disable All", + "ModManagerDeleteAllButton": "Delete All", + "MenuBarOptionsChangeLanguage": "Change Language", + "MenuBarShowFileTypes": "Show File Types", + "CommonSort": "Sort", + "CommonShowNames": "Show Names", + "CommonFavorite": "Favorite", + "OrderAscending": "Ascending", + "OrderDescending": "Descending", + "SettingsTabGraphicsFeatures": "Features & Enhancements", + "ErrorWindowTitle": "Error Window", + "ToggleDiscordTooltip": "Choose whether or not to display Ryujinx on your \"currently playing\" Discord activity", + "AddGameDirBoxTooltip": "Enter a game directory to add to the list", + "AddGameDirTooltip": "Add a game directory to the list", + "RemoveGameDirTooltip": "Remove selected game directory", + "CustomThemeCheckTooltip": "Use a custom Avalonia theme for the GUI to change the appearance of the emulator menus", + "CustomThemePathTooltip": "Path to custom GUI theme", + "CustomThemeBrowseTooltip": "Browse for a custom GUI theme", + "DockModeToggleTooltip": "Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality.\n\nConfigure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode.\n\nLeave ON if unsure.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "RegionTooltip": "Change System Region", + "LanguageTooltip": "Change System Language", + "TimezoneTooltip": "Change System TimeZone", + "TimeTooltip": "Change System Time", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.", + "FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.", + "AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.", + "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.", + "MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.", + "MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.", + "MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.", + "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", + "DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.", + "IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.", + "GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", + "GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", + "ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "ShaderDumpPathTooltip": "Graphics Shaders Dump Path", + "FileLogTooltip": "Saves console logging to a log file on disk. Does not affect performance.", + "StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.", + "InfoLogTooltip": "Prints info log messages in the console. Does not affect performance.", + "WarnLogTooltip": "Prints warning log messages in the console. Does not affect performance.", + "ErrorLogTooltip": "Prints error log messages in the console. Does not affect performance.", + "TraceLogTooltip": "Prints trace log messages in the console. Does not affect performance.", + "GuestLogTooltip": "Prints guest log messages in the console. Does not affect performance.", + "FileAccessLogTooltip": "Prints file access log messages in the console.", + "FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3", + "DeveloperOptionTooltip": "Use with care", + "OpenGlLogLevel": "Requires appropriate log levels enabled", + "DebugLogTooltip": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.", + "LoadApplicationFileTooltip": "Open a file explorer to choose a Switch compatible file to load", + "LoadApplicationFolderTooltip": "Open a file explorer to choose a Switch compatible, unpacked application to load", + "OpenRyujinxFolderTooltip": "Open Ryujinx filesystem folder", + "OpenRyujinxLogsTooltip": "Opens the folder where logs are written to", + "ExitTooltip": "Exit Ryujinx", + "OpenSettingsTooltip": "Open settings window", + "OpenProfileManagerTooltip": "Open User Profiles Manager window", + "StopEmulationTooltip": "Stop emulation of the current game and return to game selection", + "CheckUpdatesTooltip": "Check for updates to Ryujinx", + "OpenAboutTooltip": "Open About Window", + "GridSize": "Grid Size", + "GridSizeTooltip": "Change the size of grid items", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brazilian Portuguese", + "AboutRyujinxContributorsButtonHeader": "See All Contributors", + "SettingsTabSystemAudioVolume": "Volume: ", + "AudioVolumeTooltip": "Change Audio Volume", + "SettingsTabSystemEnableInternetAccess": "Guest Internet Access/LAN Mode", + "EnableInternetAccessTooltip": "Allows the emulated application to connect to the Internet.\n\nGames with a LAN mode can connect to each other when this is enabled and the systems are connected to the same access point. This includes real consoles as well.\n\nDoes NOT allow connecting to Nintendo servers. May cause crashing in certain games that try to connect to the Internet.\n\nLeave OFF if unsure.", + "GameListContextMenuManageCheatToolTip": "Manage Cheats", + "GameListContextMenuManageCheat": "Manage Cheats", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", + "ControllerSettingsStickRange": "Range:", + "DialogStopEmulationTitle": "Ryujinx - Stop Emulation", + "DialogStopEmulationMessage": "Are you sure you want to stop emulation?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Network", + "SettingsTabNetworkConnection": "Network Connection", + "SettingsTabCpuCache": "CPU Cache", + "SettingsTabCpuMemory": "CPU Mode", + "DialogUpdaterFlatpakNotSupportedMessage": "Please update Ryujinx via FlatHub.", + "UpdaterDisabledWarningTitle": "Updater Disabled!", + "ControllerSettingsRotate90": "Rotate 90° Clockwise", + "IconSize": "Icon Size", + "IconSizeTooltip": "Change the size of game icons", + "MenuBarOptionsShowConsole": "Show Console", + "ShaderCachePurgeError": "Error purging shader cache at {0}: {1}", + "UserErrorNoKeys": "Keys not found", + "UserErrorNoFirmware": "Firmware not found", + "UserErrorFirmwareParsingFailed": "Firmware parsing error", + "UserErrorApplicationNotFound": "Application not found", + "UserErrorUnknown": "Unknown error", + "UserErrorUndefined": "Undefined error", + "UserErrorNoKeysDescription": "Ryujinx was unable to find your 'prod.keys' file", + "UserErrorNoFirmwareDescription": "Ryujinx was unable to find any firmwares installed", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", + "UserErrorApplicationNotFoundDescription": "Ryujinx couldn't find a valid application at the given path.", + "UserErrorUnknownDescription": "An unknown error occured!", + "UserErrorUndefinedDescription": "An undefined error occured! This shouldn't happen, please contact a dev!", + "OpenSetupGuideMessage": "Open the Setup Guide", + "NoUpdate": "No Update", + "TitleUpdateVersionLabel": "Version {0}", + "TitleBundledUpdateVersionLabel": "Bundled: Version {0}", + "TitleBundledDlcLabel": "Bundled:", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmation", + "FileDialogAllTypes": "All types", + "Never": "Never", + "SwkbdMinCharacters": "Must be at least {0} characters long", + "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", + "SoftwareKeyboard": "Software Keyboard", + "SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only", + "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", + "SoftwareKeyboardModeASCII": "Must be ASCII text only", + "ControllerAppletControllers": "Supported Controllers:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "UpdaterRenaming": "Renaming Old Files...", + "UpdaterRenameFailed": "Updater was unable to rename file: {0}", + "UpdaterAddingFiles": "Adding New Files...", + "UpdaterExtracting": "Extracting Update...", + "UpdaterDownloading": "Downloading Update...", + "Game": "Game", + "Docked": "Docked", + "Handheld": "Handheld", + "ConnectionError": "Connection Error.", + "AboutPageDeveloperListMore": "{0} and more...", + "ApiError": "API Error.", + "LoadingHeading": "Loading {0}", + "CompilingPPTC": "Compiling PTC", + "CompilingShaders": "Compiling Shaders", + "AllKeyboards": "All keyboards", + "OpenFileDialogTitle": "Select a supported file to open", + "OpenFolderDialogTitle": "Select a folder with an unpacked game", + "AllSupportedFormats": "All Supported Formats", + "RyujinxUpdater": "Ryujinx Updater", + "SettingsTabHotkeys": "Keyboard Hotkeys", + "SettingsTabHotkeysHotkeys": "Keyboard Hotkeys", + "SettingsTabHotkeysToggleVsyncHotkey": "Toggle VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Screenshot:", + "SettingsTabHotkeysShowUiHotkey": "Show UI:", + "SettingsTabHotkeysPauseHotkey": "Pause:", + "SettingsTabHotkeysToggleMuteHotkey": "Mute:", + "ControllerMotionTitle": "Motion Control Settings", + "ControllerRumbleTitle": "Rumble Settings", + "SettingsSelectThemeFileDialogTitle": "Select Theme File", + "SettingsXamlThemeFile": "Xaml Theme File", + "AvatarWindowTitle": "Manage Accounts - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Unknown", + "Usage": "Usage", + "Writable": "Writable", + "SelectDlcDialogTitle": "Select DLC files", + "SelectUpdateDialogTitle": "Select update files", + "SelectModDialogTitle": "Select mod directory", + "UserProfileWindowTitle": "User Profiles Manager", + "CheatWindowTitle": "Cheats Manager", + "DlcWindowTitle": "Manage Downloadable Content for {0} ({1})", + "ModWindowTitle": "Manage Mods for {0} ({1})", + "UpdateWindowTitle": "Title Update Manager", + "CheatWindowHeading": "Cheats Available for {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "{0} Downloadable Content(s)", + "ModWindowHeading": "{0} Mod(s)", + "UserProfilesEditProfile": "Edit Selected", + "Cancel": "Cancel", + "Save": "Save", + "Discard": "Discard", + "Paused": "Paused", + "UserProfilesSetProfileImage": "Set Profile Image", + "UserProfileEmptyNameError": "Name is required", + "UserProfileNoImageError": "Profile image must be set", + "GameUpdateWindowHeading": "Manage Updates for {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:", + "SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:", + "UserProfilesName": "Name:", + "UserProfilesUserId": "User ID:", + "SettingsTabGraphicsBackend": "Graphics Backend", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsEnableTextureRecompression": "Enable Texture Recompression", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsTabGraphicsPreferredGpu": "Preferred GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.", + "SettingsAppRequiredRestartMessage": "Ryujinx Restart Required", + "SettingsGpuBackendRestartMessage": "Graphics Backend or GPU settings have been modified. This will require a restart to be applied", + "SettingsGpuBackendRestartSubMessage": "Do you want to restart now?", + "RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?", + "SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:", + "SettingsEnableMacroHLE": "Enable Macro HLE", + "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.", + "SettingsEnableColorSpacePassthrough": "Color Space Passthrough", + "SettingsEnableColorSpacePassthroughTooltip": "Directs the Vulkan backend to pass through color information without specifying a color space. For users with wide gamut displays, this may result in more vibrant colors, at the cost of color correctness.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Manage Saves", + "DeleteUserSave": "Do you want to delete user save for this game?", + "IrreversibleActionNote": "This action is not reversible.", + "SaveManagerHeading": "Manage Saves for {0} ({1})", + "SaveManagerTitle": "Save Manager", + "Name": "Name", + "Size": "Size", + "Search": "Search", + "UserProfilesRecoverLostAccounts": "Recover Lost Accounts", + "Recover": "Recover", + "UserProfilesRecoverHeading": "Saves were found for the following accounts", + "UserProfilesRecoverEmptyList": "No profiles to recover", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAALabel": "Anti-Aliasing:", + "GraphicsScalingFilterLabel": "Scaling Filter:", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "Level", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "SmaaLow": "SMAA Low", + "SmaaMedium": "SMAA Medium", + "SmaaHigh": "SMAA High", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Edit User", + "UserEditorTitleCreate": "Create User", + "SettingsTabNetworkInterface": "Network Interface:", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceDefault": "Default", + "PackagingShaders": "Packaging Shaders", + "AboutChangelogButton": "View Changelog on GitHub", + "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser.", + "SettingsTabNetworkMultiplayer": "Multiplayer", + "MultiplayerMode": "Mode:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json new file mode 100644 index 00000000..e58fa5dc --- /dev/null +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -0,0 +1,780 @@ +{ + "Language": "Español (ES)", + "MenuBarFileOpenApplet": "Abrir applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abre el editor de Mii en modo autónomo", + "SettingsTabInputDirectMouseAccess": "Acceso directo al ratón", + "SettingsTabSystemMemoryManagerMode": "Modo del administrador de memoria:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (rápido)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host sin verificación (más rápido, inseguro)", + "SettingsTabSystemUseHypervisor": "Usar hipervisor", + "MenuBarFile": "_Archivo", + "MenuBarFileOpenFromFile": "_Cargar aplicación desde un archivo", + "MenuBarFileOpenUnpacked": "Cargar juego _desempaquetado", + "MenuBarFileOpenEmuFolder": "Abrir carpeta de Ryujinx", + "MenuBarFileOpenLogsFolder": "Abrir carpeta de registros", + "MenuBarFileExit": "_Salir", + "MenuBarOptions": "_Opciones", + "MenuBarOptionsToggleFullscreen": "Cambiar a pantalla completa.", + "MenuBarOptionsStartGamesInFullscreen": "Iniciar juegos en pantalla completa", + "MenuBarOptionsStopEmulation": "Detener emulación", + "MenuBarOptionsSettings": "_Configuración", + "MenuBarOptionsManageUserProfiles": "_Gestionar perfiles de usuario", + "MenuBarActions": "_Acciones", + "MenuBarOptionsSimulateWakeUpMessage": "Simular mensaje de reactivación", + "MenuBarActionsScanAmiibo": "Escanear Amiibo", + "MenuBarTools": "_Herramientas", + "MenuBarToolsInstallFirmware": "Instalar firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware desde un archivo XCI o ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Instalar firmware desde una carpeta", + "MenuBarToolsManageFileTypes": "Administrar tipos de archivo", + "MenuBarToolsInstallFileTypes": "Instalar tipos de archivo", + "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de archivo", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "_Ayuda", + "MenuBarHelpCheckForUpdates": "Buscar actualizaciones", + "MenuBarHelpAbout": "Acerca de", + "MenuSearch": "Buscar...", + "GameListHeaderFavorite": "Favoritos", + "GameListHeaderIcon": "Icono", + "GameListHeaderApplication": "Nombre", + "GameListHeaderDeveloper": "Desarrollador", + "GameListHeaderVersion": "Versión", + "GameListHeaderTimePlayed": "Tiempo jugado", + "GameListHeaderLastPlayed": "Jugado por última vez", + "GameListHeaderFileExtension": "Extensión", + "GameListHeaderFileSize": "Tamaño del archivo", + "GameListHeaderPath": "Directorio", + "GameListContextMenuOpenUserSaveDirectory": "Abrir carpeta de guardado de este usuario", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del usuario para esta aplicación", + "GameListContextMenuOpenDeviceSaveDirectory": "Abrir carpeta de guardado del sistema para el usuario actual", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del sistema para esta aplicación", + "GameListContextMenuOpenBcatSaveDirectory": "Abrir carpeta de guardado BCAT del usuario", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Abrir la carpeta que contiene el guardado BCAT de esta aplicación", + "GameListContextMenuManageTitleUpdates": "Gestionar actualizaciones del juego", + "GameListContextMenuManageTitleUpdatesToolTip": "Abrir la ventana de gestión de actualizaciones de esta aplicación", + "GameListContextMenuManageDlc": "Gestionar DLC", + "GameListContextMenuManageDlcToolTip": "Abrir la ventana de gestión del DLC", + "GameListContextMenuCacheManagement": "Gestión de caché ", + "GameListContextMenuCacheManagementPurgePptc": "Reconstruir PPTC en cola", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la caché de PPTC de esta aplicación", + "GameListContextMenuCacheManagementPurgeShaderCache": "Limpiar caché de sombreadores", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Eliminar la caché de sombreadores de esta aplicación", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Abrir carpeta de PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Abrir la carpeta que contiene la caché de PPTC de esta aplicación", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir carpeta de caché de sombreadores", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abrir la carpeta que contiene la caché de sombreadores de esta aplicación", + "GameListContextMenuExtractData": "Extraer datos", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extraer la sección ExeFS de la configuración actual de la aplicación (incluyendo actualizaciones)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extraer la sección RomFS de la configuración actual de la aplicación (incluyendo actualizaciones)", + "GameListContextMenuExtractDataLogo": "Logotipo", + "GameListContextMenuExtractDataLogoToolTip": "Extraer la sección Logo de la configuración actual de la aplicación (incluyendo actualizaciones)", + "GameListContextMenuCreateShortcut": "Crear acceso directo de aplicación", + "GameListContextMenuCreateShortcutToolTip": "Crear un acceso directo en el escritorio que lance la aplicación seleccionada", + "GameListContextMenuCreateShortcutToolTipMacOS": "Crea un acceso directo en la carpeta de Aplicaciones de macOS que inicie la Aplicación seleccionada", + "GameListContextMenuOpenModsDirectory": "Abrir Directorio de Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Abre el directorio que contiene los Mods de la Aplicación.", + "GameListContextMenuOpenSdModsDirectory": "Abrir Directorio de Mods de Atmosphere\n\n\n\n\n\n", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre el directorio alternativo de la tarjeta SD de Atmosphere que contiene los Mods de la Aplicación. Útil para los mods que están empaquetados para el hardware real.", + "StatusBarGamesLoaded": "{0}/{1} juegos cargados", + "StatusBarSystemVersion": "Versión del sistema: {0}", + "LinuxVmMaxMapCountDialogTitle": "Límite inferior para mapeos de memoria detectado", + "LinuxVmMaxMapCountDialogTextPrimary": "¿Quieres aumentar el valor de vm.max_map_count a {0}?", + "LinuxVmMaxMapCountDialogTextSecondary": "Algunos juegos podrían intentar crear más mapeos de memoria de los permitidos. Ryujinx se bloqueará tan pronto como se supere este límite.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Sí, hasta el próximo reinicio", + "LinuxVmMaxMapCountDialogButtonPersistent": "Si, permanentemente", + "LinuxVmMaxMapCountWarningTextPrimary": "La cantidad máxima de mapeos de memoria es menor de lo recomendado.", + "LinuxVmMaxMapCountWarningTextSecondary": "El valor actual de vm.max_map_count ({0}) es menor que {1}. Algunos juegos podrían intentar crear más mapeos de memoria de los permitidos actualmente. Ryujinx se bloqueará tan pronto como se supere este límite.\n\nPuede que desee aumentar manualmente el límite o instalar pkexec, lo que permite a Ryujinx ayudar con eso.", + "Settings": "Configuración", + "SettingsTabGeneral": "Interfaz de usuario", + "SettingsTabGeneralGeneral": "General", + "SettingsTabGeneralEnableDiscordRichPresence": "Habilitar estado en Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Buscar actualizaciones al iniciar", + "SettingsTabGeneralShowConfirmExitDialog": "Mostrar diálogo de confirmación al cerrar", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", + "SettingsTabGeneralHideCursor": "Esconder el cursor:", + "SettingsTabGeneralHideCursorNever": "Nunca", + "SettingsTabGeneralHideCursorOnIdle": "Ocultar cursor cuando esté inactivo", + "SettingsTabGeneralHideCursorAlways": "Siempre", + "SettingsTabGeneralGameDirectories": "Carpetas de juegos", + "SettingsTabGeneralAdd": "Agregar", + "SettingsTabGeneralRemove": "Quitar", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Núcleo", + "SettingsTabSystemSystemRegion": "Región del sistema:", + "SettingsTabSystemSystemRegionJapan": "Japón", + "SettingsTabSystemSystemRegionUSA": "EEUU", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Corea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwán", + "SettingsTabSystemSystemLanguage": "Idioma del sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Japonés", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglés americano", + "SettingsTabSystemSystemLanguageFrench": "Francés", + "SettingsTabSystemSystemLanguageGerman": "Alemán", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Español", + "SettingsTabSystemSystemLanguageChinese": "Chino", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Neerlandés/Holandés", + "SettingsTabSystemSystemLanguagePortuguese": "Portugués", + "SettingsTabSystemSystemLanguageRussian": "Ruso", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanés", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglés británico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francés canadiense", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Español latinoamericano", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chino simplificado", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chino tradicional", + "SettingsTabSystemSystemTimeZone": "Zona horaria del sistema:", + "SettingsTabSystemSystemTime": "Hora del sistema:", + "SettingsTabSystemEnableVsync": "Sincronización vertical", + "SettingsTabSystemEnablePptc": "PPTC (Cache de Traducción de Perfil Persistente)", + "SettingsTabSystemEnableFsIntegrityChecks": "Comprobar integridad de los archivos", + "SettingsTabSystemAudioBackend": "Motor de audio:", + "SettingsTabSystemAudioBackendDummy": "Vacio", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Pueden causar inestabilidad)", + "SettingsTabSystemExpandDramSize": "Usar diseño alternativo de memoria (Desarrolladores)", + "SettingsTabSystemIgnoreMissingServices": "Ignorar servicios no implementados", + "SettingsTabGraphics": "Gráficos", + "SettingsTabGraphicsAPI": "API de gráficos", + "SettingsTabGraphicsEnableShaderCache": "Habilitar caché de sombreadores", + "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotrópico:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Automático", + "SettingsTabGraphicsAnisotropicFiltering2x": "x2", + "SettingsTabGraphicsAnisotropicFiltering4x": "x4", + "SettingsTabGraphicsAnisotropicFiltering8x": "x8", + "SettingsTabGraphicsAnisotropicFiltering16x": "x16", + "SettingsTabGraphicsResolutionScale": "Escala de resolución:", + "SettingsTabGraphicsResolutionScaleCustom": "Personalizada (no recomendado)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (no recomendado)", + "SettingsTabGraphicsAspectRatio": "Relación de aspecto:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Estirar a la ventana", + "SettingsTabGraphicsDeveloperOptions": "Opciones de desarrollador", + "SettingsTabGraphicsShaderDumpPath": "Directorio de volcado de sombreadores:", + "SettingsTabLogging": "Registros", + "SettingsTabLoggingLogging": "Registros", + "SettingsTabLoggingEnableLoggingToFile": "Habilitar registro a archivo", + "SettingsTabLoggingEnableStubLogs": "Habilitar registros de Stub", + "SettingsTabLoggingEnableInfoLogs": "Habilitar registros de Info", + "SettingsTabLoggingEnableWarningLogs": "Habilitar registros de Advertencia", + "SettingsTabLoggingEnableErrorLogs": "Habilitar registros de Error", + "SettingsTabLoggingEnableTraceLogs": "Habilitar registros de Rastro", + "SettingsTabLoggingEnableGuestLogs": "Habilitar registros de Guest", + "SettingsTabLoggingEnableFsAccessLogs": "Habilitar registros de Fs Access", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modo de registros Fs Global Access:", + "SettingsTabLoggingDeveloperOptions": "Opciones de desarrollador (ADVERTENCIA: empeorarán el rendimiento)", + "SettingsTabLoggingDeveloperOptionsNote": "ADVERTENCIA: Reducirá el rendimiento", + "SettingsTabLoggingGraphicsBackendLogLevel": "Nivel de registro de backend gráficos:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nada", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Errores", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentizaciones", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Todo", + "SettingsTabLoggingEnableDebugLogs": "Habilitar registros de debug", + "SettingsTabInput": "Entrada", + "SettingsTabInputEnableDockedMode": "Modo dock/TV", + "SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado", + "SettingsButtonSave": "Guardar", + "SettingsButtonClose": "Cerrar", + "SettingsButtonOk": "Aceptar", + "SettingsButtonCancel": "Cancelar", + "SettingsButtonApply": "Aplicar", + "ControllerSettingsPlayer": "Jugador", + "ControllerSettingsPlayer1": "Jugador 1", + "ControllerSettingsPlayer2": "Jugador 2", + "ControllerSettingsPlayer3": "Jugador 3", + "ControllerSettingsPlayer4": "Jugador 4", + "ControllerSettingsPlayer5": "Jugador 5", + "ControllerSettingsPlayer6": "Jugador 6", + "ControllerSettingsPlayer7": "Jugador 7", + "ControllerSettingsPlayer8": "Jugador 8", + "ControllerSettingsHandheld": "Portátil", + "ControllerSettingsInputDevice": "Dispositivo de entrada", + "ControllerSettingsRefresh": "Actualizar", + "ControllerSettingsDeviceDisabled": "Deshabilitado", + "ControllerSettingsControllerType": "Tipo de Mando", + "ControllerSettingsControllerTypeHandheld": "Portátil", + "ControllerSettingsControllerTypeProController": "Mando Pro", + "ControllerSettingsControllerTypeJoyConPair": "Doble Joy-Con", + "ControllerSettingsControllerTypeJoyConLeft": "Joy-Con Izquierdo", + "ControllerSettingsControllerTypeJoyConRight": "Joy-Con Derecho", + "ControllerSettingsProfile": "Perfil", + "ControllerSettingsProfileDefault": "Predeterminado", + "ControllerSettingsLoad": "Cargar", + "ControllerSettingsAdd": "Agregar", + "ControllerSettingsRemove": "Quitar", + "ControllerSettingsButtons": "Botones", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Pad direccional", + "ControllerSettingsDPadUp": "Arriba", + "ControllerSettingsDPadDown": "Abajo", + "ControllerSettingsDPadLeft": "Izquierda", + "ControllerSettingsDPadRight": "Derecha", + "ControllerSettingsStickButton": "Botón", + "ControllerSettingsStickUp": "Arriba", + "ControllerSettingsStickDown": "Abajo", + "ControllerSettingsStickLeft": "Izquierda", + "ControllerSettingsStickRight": "Derecha", + "ControllerSettingsStickStick": "Palanca", + "ControllerSettingsStickInvertXAxis": "Invertir eje X", + "ControllerSettingsStickInvertYAxis": "Invertir eje Y", + "ControllerSettingsStickDeadzone": "Zona muerta:", + "ControllerSettingsLStick": "Palanca izquierda", + "ControllerSettingsRStick": "Palanca derecha", + "ControllerSettingsTriggersLeft": "Gatillos izquierdos", + "ControllerSettingsTriggersRight": "Gatillos derechos", + "ControllerSettingsTriggersButtonsLeft": "Botones de gatillo izquierdos", + "ControllerSettingsTriggersButtonsRight": "Botones de gatillo derechos", + "ControllerSettingsTriggers": "Gatillos", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Botones izquierdos", + "ControllerSettingsExtraButtonsRight": "Botones derechos", + "ControllerSettingsMisc": "Misceláneo", + "ControllerSettingsTriggerThreshold": "Límite de gatillos:", + "ControllerSettingsMotion": "Movimiento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usar movimiento compatible con CemuHook", + "ControllerSettingsMotionControllerSlot": "Puerto del mando:", + "ControllerSettingsMotionMirrorInput": "Paralelizar derecho e izquierdo", + "ControllerSettingsMotionRightJoyConSlot": "Puerto del Joy-Con derecho:", + "ControllerSettingsMotionServerHost": "Host del servidor:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilidad de Gyro:", + "ControllerSettingsMotionGyroDeadzone": "Zona muerta de Gyro:", + "ControllerSettingsSave": "Guardar", + "ControllerSettingsClose": "Cerrar", + "KeyUnknown": "Desconocido", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", + "UserProfilesSelectedUserProfile": "Perfil de usuario seleccionado:", + "UserProfilesSaveProfileName": "Guardar nombre de perfil", + "UserProfilesChangeProfileImage": "Cambiar imagen de perfil", + "UserProfilesAvailableUserProfiles": "Perfiles de usuario disponibles:", + "UserProfilesAddNewProfile": "Añadir nuevo perfil", + "UserProfilesDelete": "Eliminar", + "UserProfilesClose": "Cerrar", + "ProfileNameSelectionWatermark": "Escoge un apodo", + "ProfileImageSelectionTitle": "Selección de imagen de perfil", + "ProfileImageSelectionHeader": "Elige una imagen de perfil", + "ProfileImageSelectionNote": "Puedes importar una imagen de perfil personalizada, o seleccionar un avatar del firmware de sistema", + "ProfileImageSelectionImportImage": "Importar imagen", + "ProfileImageSelectionSelectAvatar": "Seleccionar avatar del firmware", + "InputDialogTitle": "Cuadro de diálogo de entrada", + "InputDialogOk": "Aceptar", + "InputDialogCancel": "Cancelar", + "InputDialogAddNewProfileTitle": "Introducir nombre de perfil", + "InputDialogAddNewProfileHeader": "Por favor elige un nombre de usuario", + "InputDialogAddNewProfileSubtext": "(Máximo de caracteres: {0})", + "AvatarChoose": "Escoger", + "AvatarSetBackgroundColor": "Establecer color de fondo", + "AvatarClose": "Cerrar", + "ControllerSettingsLoadProfileToolTip": "Cargar perfil", + "ControllerSettingsAddProfileToolTip": "Agregar perfil", + "ControllerSettingsRemoveProfileToolTip": "Eliminar perfil", + "ControllerSettingsSaveProfileToolTip": "Guardar perfil", + "MenuBarFileToolsTakeScreenshot": "Captura de pantalla", + "MenuBarFileToolsHideUi": "Ocultar interfaz", + "GameListContextMenuRunApplication": "Ejecutar aplicación", + "GameListContextMenuToggleFavorite": "Marcar favorito", + "GameListContextMenuToggleFavoriteToolTip": "Marca o desmarca el juego como favorito", + "SettingsTabGeneralTheme": "Tema:", + "SettingsTabGeneralThemeDark": "Oscuro", + "SettingsTabGeneralThemeLight": "Claro", + "ControllerSettingsConfigureGeneral": "Configurar", + "ControllerSettingsRumble": "Vibración", + "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibraciones fuertes", + "ControllerSettingsRumbleWeakMultiplier": "Multiplicador de vibraciones débiles", + "DialogMessageSaveNotAvailableMessage": "No hay datos de guardado para {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "¿Quieres crear datos de guardado para este juego?", + "DialogConfirmationTitle": "Ryujinx - Confirmación", + "DialogUpdaterTitle": "Ryujinx - Actualizador", + "DialogErrorTitle": "Ryujinx - Error", + "DialogWarningTitle": "Ryujinx - Advertencia", + "DialogExitTitle": "Ryujinx - Salir", + "DialogErrorMessage": "Ryujinx encontró un error", + "DialogExitMessage": "¿Seguro que quieres cerrar Ryujinx?", + "DialogExitSubMessage": "¡Se perderán los datos no guardados!", + "DialogMessageCreateSaveErrorMessage": "Hubo un error al crear los datos de guardado especificados: {0}", + "DialogMessageFindSaveErrorMessage": "Hubo un error encontrando los datos de guardado especificados: {0}", + "FolderDialogExtractTitle": "Elige la carpeta en la que deseas extraer", + "DialogNcaExtractionMessage": "Extrayendo {0} sección de {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Extractor de sección NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Fallo de extracción. El NCA principal no estaba presente en el archivo seleccionado.", + "DialogNcaExtractionCheckLogErrorMessage": "Fallo de extracción. Lee el registro para más información.", + "DialogNcaExtractionSuccessMessage": "Se completó la extracción con éxito.", + "DialogUpdaterConvertFailedMessage": "No se pudo convertir la versión actual de Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "¡Cancelando actualización!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "¡Ya tienes la versión más reciente de Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Se ha producido un error al intentar obtener información de liberación de GitHub Release. Esto puede ser causado si una nueva versión está siendo compilada por GitHub Actions. Inténtalo de nuevo en unos minutos.", + "DialogUpdaterConvertFailedGithubMessage": "No se pudo convertir la versión de Ryujinx recibida de GitHub Release.", + "DialogUpdaterDownloadingMessage": "Descargando actualización...", + "DialogUpdaterExtractionMessage": "Extrayendo actualización...", + "DialogUpdaterRenamingMessage": "Renombrando actualización...", + "DialogUpdaterAddingFilesMessage": "Aplicando actualización...", + "DialogUpdaterCompleteMessage": "¡Actualización completa!", + "DialogUpdaterRestartMessage": "¿Quieres reiniciar Ryujinx?", + "DialogUpdaterNoInternetMessage": "¡No estás conectado a internet!", + "DialogUpdaterNoInternetSubMessage": "¡Por favor, verifica que tu conexión a Internet funciona!", + "DialogUpdaterDirtyBuildMessage": "¡No puedes actualizar una versión \"dirty\" de Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Por favor, descarga Ryujinx en https://ryujinx.org/ si buscas una versión con soporte.", + "DialogRestartRequiredMessage": "Se necesita reiniciar", + "DialogThemeRestartMessage": "Tema guardado. Se necesita reiniciar para aplicar el tema.", + "DialogThemeRestartSubMessage": "¿Quieres reiniciar?", + "DialogFirmwareInstallEmbeddedMessage": "¿Quieres instalar el firmware incluido en este juego? (Firmware versión {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", + "DialogFirmwareNoFirmwareInstalledMessage": "No hay firmware instalado", + "DialogFirmwareInstalledMessage": "Se instaló el firmware {0}", + "DialogInstallFileTypesSuccessMessage": "¡Tipos de archivos instalados con éxito!", + "DialogInstallFileTypesErrorMessage": "No se pudo desinstalar los tipos de archivo.", + "DialogUninstallFileTypesSuccessMessage": "¡Tipos de archivos desinstalados con éxito!", + "DialogUninstallFileTypesErrorMessage": "No se pudo desinstalar los tipos de archivo.", + "DialogOpenSettingsWindowLabel": "Abrir ventana de opciones", + "DialogControllerAppletTitle": "Applet de mandos", + "DialogMessageDialogErrorExceptionMessage": "Error al mostrar cuadro de diálogo: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Error al mostrar teclado de software: {0}", + "DialogErrorAppletErrorExceptionMessage": "Error al mostrar díalogo ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPara más información sobre cómo arreglar este error, sigue nuestra Guía de Instalación.", + "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Ocurrió un error al recibir información de la API.", + "DialogAmiiboApiConnectErrorMessage": "No se pudo conectar al servidor de la API Amiibo. El servicio puede estar caído o tu conexión a internet puede haberse desconectado.", + "DialogProfileInvalidProfileErrorMessage": "El perfil {0} no es compatible con el sistema actual de configuración de entrada.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "El perfil predeterminado no se puede sobreescribir", + "DialogProfileDeleteProfileTitle": "Eliminando perfil", + "DialogProfileDeleteProfileMessage": "Esta acción es irreversible, ¿estás seguro de querer continuar?", + "DialogWarning": "Advertencia", + "DialogPPTCDeletionMessage": "Vas a borrar la caché de PPTC para:\n\n{0}\n\n¿Estás seguro de querer continuar?", + "DialogPPTCDeletionErrorMessage": "Error purgando la caché de PPTC en {0}: {1}", + "DialogShaderDeletionMessage": "Vas a borrar la caché de sombreadores para:\n\n{0}\n\n¿Estás seguro de querer continuar?", + "DialogShaderDeletionErrorMessage": "Error purgando la caché de sombreadores en {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx ha encontrado un error", + "DialogInvalidTitleIdErrorMessage": "Error de interfaz: El juego seleccionado no tiene una ID válida", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "No se pudo encontrar un firmware válido en {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Instalar firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Se instalará la versión de sistema {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nEsto reemplazará la versión de sistema actual, {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n¿Continuar?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalando firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Versión de sistema {0} instalada con éxito.", + "DialogUserProfileDeletionWarningMessage": "Si eliminas el perfil seleccionado no quedará ningún otro perfil", + "DialogUserProfileDeletionConfirmMessage": "¿Quieres eliminar el perfil seleccionado?", + "DialogUserProfileUnsavedChangesTitle": "Advertencia - Cambios sin guardar", + "DialogUserProfileUnsavedChangesMessage": "Ha realizado cambios en este perfil de usuario que no han sido guardados.", + "DialogUserProfileUnsavedChangesSubMessage": "¿Quieres descartar los cambios realizados?", + "DialogControllerSettingsModifiedConfirmMessage": "Se ha actualizado la configuración del mando actual.", + "DialogControllerSettingsModifiedConfirmSubMessage": "¿Guardar cambios?", + "DialogLoadFileErrorMessage": "{0}. Archivo con error: {1}", + "DialogModAlreadyExistsMessage": "El mod ya existe", + "DialogModInvalidMessage": "¡El directorio especificado no contiene un mod!", + "DialogModDeleteNoParentMessage": "Error al eliminar: ¡No se pudo encontrar el directorio principal para el mod \"{0}\"!", + "DialogDlcNoDlcErrorMessage": "¡Ese archivo no contiene contenido descargable para el título seleccionado!", + "DialogPerformanceCheckLoggingEnabledMessage": "Has habilitado los registros debug, diseñados solo para uso de los desarrolladores.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar los registros debug. ¿Quieres deshabilitarlos ahora?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Has habilitado el volcado de sombreadores, diseñado solo para uso de los desarrolladores.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar el volcado de sombreadores. ¿Quieres deshabilitarlo ahora?", + "DialogLoadAppGameAlreadyLoadedMessage": "Ya has cargado un juego", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Por favor, detén la emulación o cierra el emulador antes de iniciar otro juego.", + "DialogUpdateAddUpdateErrorMessage": "¡Ese archivo no contiene una actualización para el título seleccionado!", + "DialogSettingsBackendThreadingWarningTitle": "Advertencia - multihilado de gráficos", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx debe reiniciarse para aplicar este cambio. Dependiendo de tu plataforma, puede que tengas que desactivar manualmente la optimización enlazada de tus controladores gráficos para usar el multihilo de Ryujinx.", + "DialogModManagerDeletionWarningMessage": "Estás a punto de eliminar el mod: {0}\n\n¿Estás seguro de que quieres continuar?", + "DialogModManagerDeletionAllWarningMessage": "Estás a punto de eliminar todos los Mods para este título.\n\n¿Estás seguro de que quieres continuar?", + "SettingsTabGraphicsFeaturesOptions": "Funcionalidades", + "SettingsTabGraphicsBackendMultithreading": "Multihilado del motor gráfico:", + "CommonAuto": "Automático", + "CommonOff": "Desactivado", + "CommonOn": "Activado", + "InputDialogYes": "Sí", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "El nombre de archivo contiene caracteres inválidos. Por favor, inténtalo de nuevo.", + "MenuBarOptionsPauseEmulation": "Pausar", + "MenuBarOptionsResumeEmulation": "Reanudar", + "AboutUrlTooltipMessage": "Haz clic para abrir el sitio web de Ryujinx en tu navegador predeterminado.", + "AboutDisclaimerMessage": "Ryujinx no tiene afiliación alguna con Nintendo™,\nni con ninguno de sus socios.", + "AboutAmiiboDisclaimerMessage": "Utilizamos AmiiboAPI (www.amiiboapi.com)\nen nuestra emulación de Amiibo.", + "AboutPatreonUrlTooltipMessage": "Haz clic para abrir el Patreon de Ryujinx en tu navegador predeterminado.", + "AboutGithubUrlTooltipMessage": "Haz clic para abrir el GitHub de Ryujinx en tu navegador predeterminado.", + "AboutDiscordUrlTooltipMessage": "Haz clic para recibir una invitación al Discord de Ryujinx en tu navegador predeterminado.", + "AboutTwitterUrlTooltipMessage": "Haz clic para abrir el Twitter de Ryujinx en tu navegador predeterminado.", + "AboutRyujinxAboutTitle": "Acerca de:", + "AboutRyujinxAboutContent": "Ryujinx es un emulador para Nintendo Switch™.\nPor favor, apóyanos en Patreon.\nEncuentra las noticias más recientes en nuestro Twitter o Discord.\nDesarrolladores interesados en contribuir pueden encontrar más información en GitHub o Discord.", + "AboutRyujinxMaintainersTitle": "Mantenido por:", + "AboutRyujinxMaintainersContentTooltipMessage": "Haz clic para abrir la página de contribuidores en tu navegador predeterminado.", + "AboutRyujinxSupprtersTitle": "Apoyado en Patreon Por:", + "AmiiboSeriesLabel": "Serie de Amiibo", + "AmiiboCharacterLabel": "Personaje", + "AmiiboScanButtonLabel": "Escanear", + "AmiiboOptionsShowAllLabel": "Mostrar todos los Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: usar etiqueta aleatoria Uuid", + "DlcManagerTableHeadingEnabledLabel": "Habilitado", + "DlcManagerTableHeadingTitleIdLabel": "ID de título", + "DlcManagerTableHeadingContainerPathLabel": "Directorio del contenedor", + "DlcManagerTableHeadingFullPathLabel": "Directorio completo", + "DlcManagerRemoveAllButton": "Quitar todo", + "DlcManagerEnableAllButton": "Activar todas", + "DlcManagerDisableAllButton": "Desactivar todos", + "ModManagerDeleteAllButton": "Eliminar Todo", + "MenuBarOptionsChangeLanguage": "Cambiar idioma", + "MenuBarShowFileTypes": "Mostrar tipos de archivo", + "CommonSort": "Orden", + "CommonShowNames": "Mostrar nombres", + "CommonFavorite": "Favorito", + "OrderAscending": "Ascendente", + "OrderDescending": "Descendente", + "SettingsTabGraphicsFeatures": "Funcionalidades Y Mejoras", + "ErrorWindowTitle": "Ventana de error", + "ToggleDiscordTooltip": "Elige si muestras Ryujinx o no en tu actividad de Discord cuando lo estés usando", + "AddGameDirBoxTooltip": "Elige un directorio de juegos para mostrar en la ventana principal", + "AddGameDirTooltip": "Agrega un directorio de juegos a la lista", + "RemoveGameDirTooltip": "Quita el directorio seleccionado de la lista", + "CustomThemeCheckTooltip": "Activa o desactiva los temas personalizados para la interfaz", + "CustomThemePathTooltip": "Carpeta que contiene los temas personalizados para la interfaz", + "CustomThemeBrowseTooltip": "Busca un tema personalizado para la interfaz", + "DockModeToggleTooltip": "El modo dock o modo TV hace que la consola emulada se comporte como una Nintendo Switch en su dock. Esto mejora la calidad gráfica en la mayoría de los juegos. Del mismo modo, si lo desactivas, el sistema emulado se comportará como una Nintendo Switch en modo portátil, reduciendo la cálidad de los gráficos.\n\nConfigura los controles de \"Jugador\" 1 si planeas jugar en modo dock/TV; configura los controles de \"Portátil\" si planeas jugar en modo portátil.\n\nActívalo si no sabes qué hacer.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "RegionTooltip": "Cambia la región del sistema", + "LanguageTooltip": "Cambia el idioma del sistema", + "TimezoneTooltip": "Cambia la zona horaria del sistema", + "TimeTooltip": "Cambia la hora del sistema", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "PptcToggleTooltip": "Guarda funciones de JIT traducidas para que no sea necesario traducirlas cada vez que el juego carga.\n\nReduce los tirones y acelera significativamente el tiempo de inicio de los juegos después de haberlos ejecutado al menos una vez.\n\nActívalo si no sabes qué hacer.", + "FsIntegrityToggleTooltip": "Comprueba si hay archivos corruptos en los juegos que ejecutes al abrirlos, y si detecta archivos corruptos, muestra un error de Hash en los registros.\n\nEsto no tiene impacto alguno en el rendimiento y está pensado para ayudar a resolver problemas.\n\nActívalo si no sabes qué hacer.", + "AudioBackendTooltip": "Cambia el motor usado para renderizar audio.\n\nSDL2 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL2 si no sabes qué hacer.", + "MemoryManagerTooltip": "Cambia la forma de mapear y acceder a la memoria del guest. Afecta en gran medida al rendimiento de la CPU emulada.\n\nSelecciona \"Host sin verificación\" si no sabes qué hacer.", + "MemoryManagerSoftwareTooltip": "Usa una tabla de paginación de software para traducir direcciones. Ofrece la precisión más exacta pero el rendimiento más lento.", + "MemoryManagerHostTooltip": "Mapea la memoria directamente en la dirección de espacio del host. Compilación y ejecución JIT mucho más rápida.", + "MemoryManagerUnsafeTooltip": "Mapea la memoria directamente, pero no enmascara la dirección dentro del espacio de dirección del guest antes del acceso. El modo más rápido, pero a costa de seguridad. La aplicación guest puede acceder a la memoria desde cualquier parte en Ryujinx, así que ejecuta solo programas en los que confíes cuando uses este modo.", + "UseHypervisorTooltip": "Usar Hypervisor en lugar de JIT. Mejora enormemente el rendimiento cuando está disponible, pero puede ser inestable en su estado actual.", + "DRamTooltip": "Expande la memoria DRAM del sistema emulado de 4GiB a 6GiB.\n\nUtilizar solo con packs de texturas HD o mods de resolución 4K. NO mejora el rendimiento.\n\nDesactívalo si no sabes qué hacer.", + "IgnoreMissingServicesTooltip": "Hack para ignorar servicios no implementados del Horizon OS. Esto puede ayudar a sobrepasar crasheos cuando inicies ciertos juegos.\n\nDesactívalo si no sabes qué hacer.", + "GraphicsBackendThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio procesamiento con múltiples hilos. Rendimiento ligeramente superior en controladores gráficos que soporten múltiples hilos.\n\nSelecciona \"Auto\" si no sabes qué hacer.", + "GalThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio procesamiento con múltiples hilos. Rendimiento ligeramente superior en controladores gráficos que soporten múltiples hilos.\n\nSelecciona \"Auto\" si no sabes qué hacer.", + "ShaderCacheToggleTooltip": "Guarda una caché de sombreadores en disco, la cual reduce los tirones a medida que vas jugando.\n\nActívalo si no sabes qué hacer.", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleEntryTooltip": "Escalado de resolución de coma flotante, como por ejemplo 1,5. Los valores no íntegros pueden causar errores gráficos o crashes.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "ShaderDumpPathTooltip": "Directorio en el cual se volcarán los sombreadores de los gráficos", + "FileLogTooltip": "Guarda los registros de la consola en archivos en disco. No afectan al rendimiento.", + "StubLogTooltip": "Escribe mensajes de Stub en la consola. No afectan al rendimiento.", + "InfoLogTooltip": "Escribe mensajes de Info en la consola. No afectan al rendimiento.", + "WarnLogTooltip": "Escribe mensajes de Advertencia en la consola. No afectan al rendimiento.", + "ErrorLogTooltip": "Escribe mensajes de Error en la consola. No afectan al rendimiento.", + "TraceLogTooltip": "Escribe mensajes de Rastro en la consola. No afectan al rendimiento.", + "GuestLogTooltip": "Escribe mensajes de Guest en la consola. No afectan al rendimiento.", + "FileAccessLogTooltip": "Activa mensajes de acceso a archivo en la consola", + "FSAccessLogModeTooltip": "Activa registros FS Access en la consola. Los modos posibles son entre 0 y 3", + "DeveloperOptionTooltip": "Usar con cuidado", + "OpenGlLogLevel": "Requiere activar los niveles de registro apropiados", + "DebugLogTooltip": "Escribe mensajes de debug en la consola\n\nActiva esto solo si un miembro del equipo te lo pide expresamente, pues hará que el registro sea difícil de leer y empeorará el rendimiento del emulador.", + "LoadApplicationFileTooltip": "Abre el explorador de archivos para elegir un archivo compatible con Switch para cargar", + "LoadApplicationFolderTooltip": "Abre el explorador de archivos para elegir un archivo desempaquetado y compatible con Switch para cargar", + "OpenRyujinxFolderTooltip": "Abre la carpeta de sistema de Ryujinx", + "OpenRyujinxLogsTooltip": "Abre la carpeta en la que se guardan los registros", + "ExitTooltip": "Cierra Ryujinx", + "OpenSettingsTooltip": "Abre la ventana de configuración", + "OpenProfileManagerTooltip": "Abre la ventana para gestionar los perfiles de usuario", + "StopEmulationTooltip": "Detiene la emulación del juego actual y regresa a la selección de juegos", + "CheckUpdatesTooltip": "Busca actualizaciones para Ryujinx", + "OpenAboutTooltip": "Abre la ventana \"Acerca de\"", + "GridSize": "Tamaño de cuadrícula", + "GridSizeTooltip": "Cambia el tamaño de los objetos en la cuadrícula", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugués brasileño", + "AboutRyujinxContributorsButtonHeader": "Ver todos los contribuidores", + "SettingsTabSystemAudioVolume": "Volumen: ", + "AudioVolumeTooltip": "Ajusta el nivel de volumen", + "SettingsTabSystemEnableInternetAccess": "Conectar guest a Internet/Modo LAN", + "EnableInternetAccessTooltip": "Permite a la aplicación emulada conectarse a Internet.\n\nLos juegos que tengan modo LAN podrán conectarse entre sí habilitando esta opción y estando conectados al mismo módem. Asimismo, esto permite conexiones con consolas reales.\n\nNO permite conectar con los servidores de Nintendo Online. Puede causar que ciertos juegos crasheen al intentar conectarse a sus servidores.\n\nDesactívalo si no estás seguro.", + "GameListContextMenuManageCheatToolTip": "Activa o desactiva los cheats", + "GameListContextMenuManageCheat": "Administrar cheats", + "GameListContextMenuManageModToolTip": "Gestionar Mods", + "GameListContextMenuManageMod": "Gestionar Mods", + "ControllerSettingsStickRange": "Alcance:", + "DialogStopEmulationTitle": "Ryujinx - Detener emulación", + "DialogStopEmulationMessage": "¿Seguro que quieres detener la emulación actual?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Sonido", + "SettingsTabNetwork": "Red", + "SettingsTabNetworkConnection": "Conexión de red", + "SettingsTabCpuCache": "Caché de CPU", + "SettingsTabCpuMemory": "Memoria de CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Por favor, actualiza Ryujinx a través de FlatHub.", + "UpdaterDisabledWarningTitle": "¡Actualizador deshabilitado!", + "ControllerSettingsRotate90": "Rotar 90° en el sentido de las agujas del reloj", + "IconSize": "Tamaño de iconos", + "IconSizeTooltip": "Cambia el tamaño de los iconos de juegos", + "MenuBarOptionsShowConsole": "Mostrar consola", + "ShaderCachePurgeError": "Error al eliminar la caché de sombreadores en {0}: {1}", + "UserErrorNoKeys": "No se encontraron keys", + "UserErrorNoFirmware": "No se encontró firmware", + "UserErrorFirmwareParsingFailed": "Error al analizar el firmware", + "UserErrorApplicationNotFound": "No se encontró la aplicación", + "UserErrorUnknown": "Error desconocido", + "UserErrorUndefined": "Error indefinido", + "UserErrorNoKeysDescription": "Ryujinx no pudo encontrar tus 'prod.keys'.", + "UserErrorNoFirmwareDescription": "Ryujinx no pudo encontrar un firmware instalado.", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx no pudo analizar el firmware. Normalmente esto ocurre debido a keys desfasadas.", + "UserErrorApplicationNotFoundDescription": "Ryujinx no pudo encontrar una aplicación válida en ese camino.", + "UserErrorUnknownDescription": "¡Ocurrió un error desconocido!", + "UserErrorUndefinedDescription": "¡Ocurrió un error indefinido! Esto no debería pasar, por favor, ¡contacta con un desarrollador!", + "OpenSetupGuideMessage": "Abrir la guía de instalación", + "NoUpdate": "No actualizado", + "TitleUpdateVersionLabel": "Versión {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmación", + "FileDialogAllTypes": "Todos los tipos", + "Never": "Nunca", + "SwkbdMinCharacters": "Debe tener al menos {0} caracteres", + "SwkbdMinRangeCharacters": "Debe tener {0}-{1} caracteres", + "SoftwareKeyboard": "Teclado de software", + "SoftwareKeyboardModeNumeric": "Debe ser sólo 0-9 o '.'", + "SoftwareKeyboardModeAlphabet": "Solo deben ser caracteres no CJK", + "SoftwareKeyboardModeASCII": "Solo deben ser texto ASCII", + "ControllerAppletControllers": "Controladores Compatibles:", + "ControllerAppletPlayers": "Jugadores:", + "ControllerAppletDescription": "Tu configuración actual no es válida. Abre la configuración y vuelve a configurar tus entradas", + "ControllerAppletDocked": "Modo acoplado activado. El modo portátil debería estar desactivado.", + "UpdaterRenaming": "Renombrando archivos viejos...", + "UpdaterRenameFailed": "El actualizador no pudo renombrar el archivo: {0}", + "UpdaterAddingFiles": "Añadiendo nuevos archivos...", + "UpdaterExtracting": "Extrayendo actualización...", + "UpdaterDownloading": "Descargando actualización...", + "Game": "Juego", + "Docked": "Dock/TV", + "Handheld": "Portátil", + "ConnectionError": "Error de conexión.", + "AboutPageDeveloperListMore": "{0} y más...", + "ApiError": "Error de API.", + "LoadingHeading": "Cargando {0}", + "CompilingPPTC": "Compilando PTC", + "CompilingShaders": "Compilando sombreadores", + "AllKeyboards": "Todos los teclados", + "OpenFileDialogTitle": "Selecciona un archivo soportado para cargar", + "OpenFolderDialogTitle": "Selecciona una carpeta con un juego desempaquetado", + "AllSupportedFormats": "Todos los formatos soportados", + "RyujinxUpdater": "Actualizador de Ryujinx", + "SettingsTabHotkeys": "Atajos de teclado", + "SettingsTabHotkeysHotkeys": "Atajos de teclado", + "SettingsTabHotkeysToggleVsyncHotkey": "Alternar la sincronización vertical:", + "SettingsTabHotkeysScreenshotHotkey": "Captura de pantalla:", + "SettingsTabHotkeysShowUiHotkey": "Mostrar interfaz:", + "SettingsTabHotkeysPauseHotkey": "Pausar:", + "SettingsTabHotkeysToggleMuteHotkey": "Silenciar:", + "ControllerMotionTitle": "Opciones de controles de movimiento", + "ControllerRumbleTitle": "Opciones de vibración", + "SettingsSelectThemeFileDialogTitle": "Selecciona un archivo de tema", + "SettingsXamlThemeFile": "Archivo de tema Xaml", + "AvatarWindowTitle": "Administrar cuentas - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Desconocido", + "Usage": "Uso", + "Writable": "Escribible", + "SelectDlcDialogTitle": "Selecciona archivo(s) de DLC", + "SelectUpdateDialogTitle": "Selecciona archivo(s) de actualización", + "SelectModDialogTitle": "Seleccionar un directorio de Mods", + "UserProfileWindowTitle": "Administrar perfiles de usuario", + "CheatWindowTitle": "Administrar cheats", + "DlcWindowTitle": "Administrar contenido descargable", + "ModWindowTitle": "Manage Mods for {0} ({1})", + "UpdateWindowTitle": "Administrar actualizaciones", + "CheatWindowHeading": "Cheats disponibles para {0} [{1}]", + "BuildId": "Id de compilación:", + "DlcWindowHeading": "Contenido descargable disponible para {0} [{1}]", + "ModWindowHeading": "{0} Mod(s)", + "UserProfilesEditProfile": "Editar selección", + "Cancel": "Cancelar", + "Save": "Guardar", + "Discard": "Descartar", + "Paused": "Pausado", + "UserProfilesSetProfileImage": "Elegir Imagen de Perfil ", + "UserProfileEmptyNameError": "El nombre es obligatorio", + "UserProfileNoImageError": "Debe establecerse la imagen de perfil", + "GameUpdateWindowHeading": "Actualizaciones disponibles para {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Aumentar la resolución:", + "SettingsTabHotkeysResScaleDownHotkey": "Disminuir la resolución:", + "UserProfilesName": "Nombre:", + "UserProfilesUserId": "Id de Usuario:", + "SettingsTabGraphicsBackend": "Fondo de gráficos", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsEnableTextureRecompression": "Activar recompresión de texturas", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsTabGraphicsPreferredGpu": "GPU preferida", + "SettingsTabGraphicsPreferredGpuTooltip": "Selecciona la tarjeta gráfica que se utilizará con los back-end de gráficos Vulkan.\n\nNo afecta la GPU que utilizará OpenGL.\n\nFije a la GPU marcada como \"dGUP\" ante dudas. Si no hay una, no haga modificaciones.", + "SettingsAppRequiredRestartMessage": "Reinicio de Ryujinx requerido.", + "SettingsGpuBackendRestartMessage": "La configuración de la GPU o del back-end de los gráficos fue modificada. Es necesario reiniciar para que se aplique.", + "SettingsGpuBackendRestartSubMessage": "¿Quieres reiniciar ahora?", + "RyujinxUpdaterMessage": "¿Quieres actualizar Ryujinx a la última versión?", + "SettingsTabHotkeysVolumeUpHotkey": "Aumentar volumen:", + "SettingsTabHotkeysVolumeDownHotkey": "Disminuir volumen:", + "SettingsEnableMacroHLE": "Activar Macros HLE", + "SettingsEnableMacroHLETooltip": "Emulación alto-nivel del código de Macros de GPU\n\nIncrementa el rendimiento, pero puede causar errores gráficos en algunos juegos.\n\nDeja esta opción activada si no estás seguro.", + "SettingsEnableColorSpacePassthrough": "Paso de espacio de color", + "SettingsEnableColorSpacePassthroughTooltip": "Dirige el backend de Vulkan a pasar a través de la información del color sin especificar un espacio de color. Para los usuarios con pantallas de gran gama, esto puede resultar en colores más vibrantes, a costa de la corrección del color.", + "VolumeShort": "Volumen", + "UserProfilesManageSaves": "Administrar mis partidas guardadas", + "DeleteUserSave": "¿Quieres borrar los datos de usuario de este juego?", + "IrreversibleActionNote": "Esta acción no es reversible.", + "SaveManagerHeading": "Manage Saves for {0}", + "SaveManagerTitle": "Administrador de datos de guardado.", + "Name": "Nombre", + "Size": "Tamaño", + "Search": "Buscar", + "UserProfilesRecoverLostAccounts": "Recuperar cuentas perdidas", + "Recover": "Recuperar", + "UserProfilesRecoverHeading": "Datos de guardado fueron encontrados para las siguientes cuentas", + "UserProfilesRecoverEmptyList": "No hay perfiles a recuperar", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAALabel": "Suavizado de bordes:", + "GraphicsScalingFilterLabel": "Filtro de escalado:", + "GraphicsScalingFilterTooltip": "Elija el filtro de escala que se aplicará al utilizar la escala de resolución.\n\nBilinear funciona bien para juegos 3D y es una opción predeterminada segura.\n\nSe recomienda el bilinear para juegos de pixel art.\n\nFSR 1.0 es simplemente un filtro de afilado, no se recomienda su uso con FXAA o SMAA.\n\nEsta opción se puede cambiar mientras se ejecuta un juego haciendo clic en \"Aplicar\" a continuación; simplemente puedes mover la ventana de configuración a un lado y experimentar hasta que encuentres tu estilo preferido para un juego.\n\nDéjelo en BILINEAR si no está seguro.", + "GraphicsScalingFilterBilinear": "Bilinear\n", + "GraphicsScalingFilterNearest": "Cercano", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "Nivel", + "GraphicsScalingFilterLevelTooltip": "Ajuste el nivel de nitidez FSR 1.0. Mayor es más nítido.", + "SmaaLow": "SMAA Bajo", + "SmaaMedium": "SMAA Medio", + "SmaaHigh": "SMAA Alto", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Editar usuario", + "UserEditorTitleCreate": "Crear Usuario", + "SettingsTabNetworkInterface": "Interfaz de Red", + "NetworkInterfaceTooltip": "Interfaz de red usada para características LAN/LDN.\n\njunto con una VPN o XLink Kai y un juego con soporte LAN, puede usarse para suplantar una conexión de la misma red a través de Internet.\n\nDeje en DEFAULT si no está seguro.", + "NetworkInterfaceDefault": "Predeterminado", + "PackagingShaders": "Empaquetando sombreadores", + "AboutChangelogButton": "Ver registro de cambios en GitHub", + "AboutChangelogButtonTooltipMessage": "Haga clic para abrir el registro de cambios para esta versión en su navegador predeterminado.", + "SettingsTabNetworkMultiplayer": "Multijugador", + "MultiplayerMode": "Modo:", + "MultiplayerModeTooltip": "Cambiar modo LDN multijugador.\n\nLdnMitm modificará la funcionalidad local de juego inalámbrico para funcionar como si fuera LAN, permitiendo locales conexiones de la misma red con otras instancias de Ryujinx y consolas hackeadas de Nintendo Switch que tienen instalado el módulo ldn_mitm.\n\nMultijugador requiere que todos los jugadores estén en la misma versión del juego (por ejemplo, Super Smash Bros. Ultimate v13.0.1 no se puede conectar a v13.0.0).\n\nDejar DESACTIVADO si no está seguro.", + "MultiplayerModeDisabled": "Deshabilitar", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json new file mode 100644 index 00000000..99a06065 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -0,0 +1,780 @@ +{ + "Language": "Français", + "MenuBarFileOpenApplet": "Ouvrir un applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Ouvrir l'Applet Mii Editor en mode Standalone", + "SettingsTabInputDirectMouseAccess": "Accès direct à la souris", + "SettingsTabSystemMemoryManagerMode": "Mode de gestion de la mémoire :", + "SettingsTabSystemMemoryManagerModeSoftware": "Logiciel", + "SettingsTabSystemMemoryManagerModeHost": "Hôte (rapide)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Hôte non vérifié (plus rapide, non sécurisé)", + "SettingsTabSystemUseHypervisor": "Utiliser l'Hyperviseur", + "MenuBarFile": "_Fichier", + "MenuBarFileOpenFromFile": "_Charger un jeu depuis un fichier", + "MenuBarFileOpenUnpacked": "Charger un jeu extrait", + "MenuBarFileOpenEmuFolder": "Ouvrir le dossier Ryujinx", + "MenuBarFileOpenLogsFolder": "Ouvrir le dossier des journaux", + "MenuBarFileExit": "_Quitter", + "MenuBarOptions": "_Options", + "MenuBarOptionsToggleFullscreen": "Basculer en plein écran", + "MenuBarOptionsStartGamesInFullscreen": "Démarrer le jeu en plein écran", + "MenuBarOptionsStopEmulation": "Arrêter l'émulation", + "MenuBarOptionsSettings": "_Paramètres", + "MenuBarOptionsManageUserProfiles": "_Gérer les profils d'utilisateurs", + "MenuBarActions": "_Actions", + "MenuBarOptionsSimulateWakeUpMessage": "Simuler un message de réveil", + "MenuBarActionsScanAmiibo": "Scanner un Amiibo", + "MenuBarTools": "_Outils", + "MenuBarToolsInstallFirmware": "Installer un firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Installer un firmware depuis un fichier XCI ou ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Installer un firmware depuis un dossier", + "MenuBarToolsManageFileTypes": "Gérer les types de fichiers", + "MenuBarToolsInstallFileTypes": "Installer les types de fichiers", + "MenuBarToolsUninstallFileTypes": "Désinstaller les types de fichiers", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "_Aide", + "MenuBarHelpCheckForUpdates": "Vérifier les mises à jour", + "MenuBarHelpAbout": "À propos", + "MenuSearch": "Rechercher...", + "GameListHeaderFavorite": "Favoris", + "GameListHeaderIcon": "Icône", + "GameListHeaderApplication": "Nom", + "GameListHeaderDeveloper": "Développeur", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Temps de jeu", + "GameListHeaderLastPlayed": "Dernière partie jouée", + "GameListHeaderFileExtension": "Extension du Fichier", + "GameListHeaderFileSize": "Taille du Fichier", + "GameListHeaderPath": "Chemin", + "GameListContextMenuOpenUserSaveDirectory": "Ouvrir le dossier de sauvegarde utilisateur", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde utilisateur du jeu", + "GameListContextMenuOpenDeviceSaveDirectory": "Ouvrir le dossier de sauvegarde console", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde console du jeu", + "GameListContextMenuOpenBcatSaveDirectory": "Ouvrir le dossier de sauvegarde BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde BCAT du jeu", + "GameListContextMenuManageTitleUpdates": "Gérer la mise à jour des titres", + "GameListContextMenuManageTitleUpdatesToolTip": "Ouvre la fenêtre de gestion des mises à jour du jeu", + "GameListContextMenuManageDlc": "Gérer les DLC", + "GameListContextMenuManageDlcToolTip": "Ouvre la fenêtre de gestion des DLC", + "GameListContextMenuCacheManagement": "Gestion des caches", + "GameListContextMenuCacheManagementPurgePptc": "Reconstruction du PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Effectuer une reconstruction du PPTC au prochain démarrage du jeu", + "GameListContextMenuCacheManagementPurgeShaderCache": "Purger le cache des shaders", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Supprime le cache des shaders du jeu", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Ouvrir le dossier du PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ouvre le dossier contenant le PPTC du jeu", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Ouvrir le dossier du cache des shaders", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Ouvre le dossier contenant le cache des shaders du jeu", + "GameListContextMenuExtractData": "Extraire les données", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrait la section ExeFS du jeu (mise à jour incluse)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrait la section RomFS du jeu (mise à jour incluse)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrait la section Logo du jeu (mise à jour incluse)", + "GameListContextMenuCreateShortcut": "Créer un raccourci d'application", + "GameListContextMenuCreateShortcutToolTip": "Créer un raccourci sur le bureau qui lance l'application sélectionnée", + "GameListContextMenuCreateShortcutToolTipMacOS": "Créer un raccourci dans le dossier Applications de macOS qui lance l'application sélectionnée", + "GameListContextMenuOpenModsDirectory": "Ouvrir le dossier des mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Ouvre le dossier contenant les mods de l'application", + "GameListContextMenuOpenSdModsDirectory": "Ouvrir le dossier des mods Atmosphère", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Ouvre le dossier alternatif de la carte SD Atmosphère qui contient les mods de l'application. Utile pour les mods conçus pour du matériel réel.", + "StatusBarGamesLoaded": "{0}/{1} Jeux chargés", + "StatusBarSystemVersion": "Version du Firmware: {0}", + "LinuxVmMaxMapCountDialogTitle": "Limite basse pour les mappings mémoire détectée", + "LinuxVmMaxMapCountDialogTextPrimary": "Voulez-vous augmenter la valeur de vm.max_map_count à {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Certains jeux peuvent essayer de créer plus de mappings mémoire que ce qui est actuellement autorisé. Ryujinx plantera dès que cette limite sera dépassée.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Oui, jusqu'au prochain redémarrage", + "LinuxVmMaxMapCountDialogButtonPersistent": "Oui, en permanence", + "LinuxVmMaxMapCountWarningTextPrimary": "La quantité maximale de mappings mémoire est inférieure à la valeur recommandée.", + "LinuxVmMaxMapCountWarningTextSecondary": "La valeur actuelle de vm.max_map_count ({0}) est inférieure à {1}. Certains jeux peuvent essayer de créer plus de mappings mémoire que ceux actuellement autorisés. Ryujinx plantera dès que cette limite sera dépassée.\n\nVous pouvez soit augmenter manuellement la limite, soit installer pkexec, ce qui permet à Ryujinx de l'aider.", + "Settings": "Paramètres", + "SettingsTabGeneral": "Interface Utilisateur", + "SettingsTabGeneralGeneral": "Général", + "SettingsTabGeneralEnableDiscordRichPresence": "Activer Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Vérifier les mises à jour au démarrage", + "SettingsTabGeneralShowConfirmExitDialog": "Afficher le message de \"Confirmation de sortie\"", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", + "SettingsTabGeneralHideCursor": "Masquer le Curseur :", + "SettingsTabGeneralHideCursorNever": "Jamais", + "SettingsTabGeneralHideCursorOnIdle": "Masquer le curseur si inactif", + "SettingsTabGeneralHideCursorAlways": "Toujours", + "SettingsTabGeneralGameDirectories": "Dossiers des jeux", + "SettingsTabGeneralAdd": "Ajouter", + "SettingsTabGeneralRemove": "Retirer", + "SettingsTabSystem": "Système", + "SettingsTabSystemCore": "Cœur", + "SettingsTabSystemSystemRegion": "Région du système:", + "SettingsTabSystemSystemRegionJapan": "Japon", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europe", + "SettingsTabSystemSystemRegionAustralia": "Australie", + "SettingsTabSystemSystemRegionChina": "Chine", + "SettingsTabSystemSystemRegionKorea": "Corée", + "SettingsTabSystemSystemRegionTaiwan": "Taïwan", + "SettingsTabSystemSystemLanguage": "Langue du système:", + "SettingsTabSystemSystemLanguageJapanese": "Japonais", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Anglais Américain", + "SettingsTabSystemSystemLanguageFrench": "Français", + "SettingsTabSystemSystemLanguageGerman": "Allemand", + "SettingsTabSystemSystemLanguageItalian": "Italien", + "SettingsTabSystemSystemLanguageSpanish": "Espagnol", + "SettingsTabSystemSystemLanguageChinese": "Chinois", + "SettingsTabSystemSystemLanguageKorean": "Coréen", + "SettingsTabSystemSystemLanguageDutch": "Néerlandais", + "SettingsTabSystemSystemLanguagePortuguese": "Portugais", + "SettingsTabSystemSystemLanguageRussian": "Russe", + "SettingsTabSystemSystemLanguageTaiwanese": "Taïwanais", + "SettingsTabSystemSystemLanguageBritishEnglish": "Anglais britannique ", + "SettingsTabSystemSystemLanguageCanadianFrench": "Français Canadien", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Espagnol latino-américain", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chinois simplifié", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chinois traditionnel", + "SettingsTabSystemSystemTimeZone": "Fuseau horaire du système :", + "SettingsTabSystemSystemTime": "Heure du système:", + "SettingsTabSystemEnableVsync": "Synchronisation verticale (VSync)", + "SettingsTabSystemEnablePptc": "Activer le PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Activer la vérification de l'intégrité du système de fichiers", + "SettingsTabSystemAudioBackend": "Bibliothèque Audio :", + "SettingsTabSystemAudioBackendDummy": "Factice", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": "Cela peut causer des instabilités", + "SettingsTabSystemExpandDramSize": "Utiliser disposition alternative de la mémoire (développeur)", + "SettingsTabSystemIgnoreMissingServices": "Ignorer les services manquants", + "SettingsTabGraphics": "Graphismes", + "SettingsTabGraphicsAPI": "API Graphique", + "SettingsTabGraphicsEnableShaderCache": "Activer le cache des shaders", + "SettingsTabGraphicsAnisotropicFiltering": "Filtrage anisotrope:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "x2", + "SettingsTabGraphicsAnisotropicFiltering4x": "x4", + "SettingsTabGraphicsAnisotropicFiltering8x": "x8", + "SettingsTabGraphicsAnisotropicFiltering16x": "x16", + "SettingsTabGraphicsResolutionScale": "Échelle de résolution:", + "SettingsTabGraphicsResolutionScaleCustom": "Personnalisée (Non recommandée)", + "SettingsTabGraphicsResolutionScaleNative": "Natif (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Non recommandé)", + "SettingsTabGraphicsAspectRatio": "Format d'affichage :", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Écran étiré", + "SettingsTabGraphicsDeveloperOptions": "Options développeur", + "SettingsTabGraphicsShaderDumpPath": "Chemin du dossier de copie des shaders:", + "SettingsTabLogging": "Journaux", + "SettingsTabLoggingLogging": "Journaux", + "SettingsTabLoggingEnableLoggingToFile": "Activer la sauvegarde des journaux vers un fichier", + "SettingsTabLoggingEnableStubLogs": "Activer les journaux stub", + "SettingsTabLoggingEnableInfoLogs": "Activer les journaux d'informations", + "SettingsTabLoggingEnableWarningLogs": "Activer les journaux d'avertissements", + "SettingsTabLoggingEnableErrorLogs": "Activer les journaux d'erreurs", + "SettingsTabLoggingEnableTraceLogs": "Activer journaux d'erreurs Trace", + "SettingsTabLoggingEnableGuestLogs": "Activer les journaux du programme simulé", + "SettingsTabLoggingEnableFsAccessLogs": "Activer les journaux d'accès au système de fichiers", + "SettingsTabLoggingFsGlobalAccessLogMode": "Niveau des journaux d'accès au système de fichiers:", + "SettingsTabLoggingDeveloperOptions": "Options développeur", + "SettingsTabLoggingDeveloperOptionsNote": "ATTENTION : Réduira les performances", + "SettingsTabLoggingGraphicsBackendLogLevel": "Niveau du journal du backend graphique :", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Aucun", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Erreur", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentissements", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Tout", + "SettingsTabLoggingEnableDebugLogs": "Activer les journaux de debug", + "SettingsTabInput": "Contrôles", + "SettingsTabInputEnableDockedMode": "Active le mode station d'accueil", + "SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier", + "SettingsButtonSave": "Enregistrer", + "SettingsButtonClose": "Fermer", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Annuler", + "SettingsButtonApply": "Appliquer", + "ControllerSettingsPlayer": "Joueur", + "ControllerSettingsPlayer1": "Joueur 1", + "ControllerSettingsPlayer2": "Joueur 2", + "ControllerSettingsPlayer3": "Joueur 3", + "ControllerSettingsPlayer4": "Joueur 4", + "ControllerSettingsPlayer5": "Joueur 5", + "ControllerSettingsPlayer6": "Joueur 6", + "ControllerSettingsPlayer7": "Joueur 7", + "ControllerSettingsPlayer8": "Joueur 8", + "ControllerSettingsHandheld": "Portable", + "ControllerSettingsInputDevice": "Périphériques", + "ControllerSettingsRefresh": "Actualiser", + "ControllerSettingsDeviceDisabled": "Désactivé", + "ControllerSettingsControllerType": "Type de contrôleur", + "ControllerSettingsControllerTypeHandheld": "Portable", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Joints", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Gauche", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Droite", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Défaut", + "ControllerSettingsLoad": "Charger", + "ControllerSettingsAdd": "Ajouter", + "ControllerSettingsRemove": "Supprimer", + "ControllerSettingsButtons": "Boutons", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Croix directionnelle", + "ControllerSettingsDPadUp": "Haut", + "ControllerSettingsDPadDown": "Bas", + "ControllerSettingsDPadLeft": "Gauche", + "ControllerSettingsDPadRight": "Droite", + "ControllerSettingsStickButton": "Bouton", + "ControllerSettingsStickUp": "Haut", + "ControllerSettingsStickDown": "Bas", + "ControllerSettingsStickLeft": "Gauche", + "ControllerSettingsStickRight": "Droite", + "ControllerSettingsStickStick": "Joystick", + "ControllerSettingsStickInvertXAxis": "Inverser l'axe X", + "ControllerSettingsStickInvertYAxis": "Inverser l'axe Y", + "ControllerSettingsStickDeadzone": "Zone morte :", + "ControllerSettingsLStick": "Joystick Gauche", + "ControllerSettingsRStick": "Joystick Droit", + "ControllerSettingsTriggersLeft": "Gachettes Gauche", + "ControllerSettingsTriggersRight": "Gachettes Droite", + "ControllerSettingsTriggersButtonsLeft": "Boutons Gachettes Gauche", + "ControllerSettingsTriggersButtonsRight": "Boutons Gachettes Droite", + "ControllerSettingsTriggers": "Gachettes", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Boutons Gauche", + "ControllerSettingsExtraButtonsRight": "Boutons Droite", + "ControllerSettingsMisc": "Divers", + "ControllerSettingsTriggerThreshold": "Seuil de gachettes:", + "ControllerSettingsMotion": "Mouvements", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Utiliser un capteur de mouvements CemuHook", + "ControllerSettingsMotionControllerSlot": "Contrôleur ID:", + "ControllerSettingsMotionMirrorInput": "Inverser les contrôles", + "ControllerSettingsMotionRightJoyConSlot": "JoyCon Droit ID:", + "ControllerSettingsMotionServerHost": "Serveur d'hébergement :", + "ControllerSettingsMotionGyroSensitivity": "Sensibilitée du gyroscope:", + "ControllerSettingsMotionGyroDeadzone": "Zone morte du gyroscope:", + "ControllerSettingsSave": "Enregistrer", + "ControllerSettingsClose": "Fermer", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", + "UserProfilesSelectedUserProfile": "Profil utilisateur sélectionné :", + "UserProfilesSaveProfileName": "Enregistrer le nom du profil", + "UserProfilesChangeProfileImage": "Changer l'image du profil", + "UserProfilesAvailableUserProfiles": "Profils utilisateurs disponibles:", + "UserProfilesAddNewProfile": "Créer un profil", + "UserProfilesDelete": "Supprimer", + "UserProfilesClose": "Fermer", + "ProfileNameSelectionWatermark": "Choisir un pseudo", + "ProfileImageSelectionTitle": "Sélection de l'image du profil", + "ProfileImageSelectionHeader": "Choisir l'image du profil", + "ProfileImageSelectionNote": "Vous pouvez importer une image de profil personnalisée ou sélectionner un avatar à partir du firmware", + "ProfileImageSelectionImportImage": "Importer une image", + "ProfileImageSelectionSelectAvatar": "Choisir un avatar du firmware", + "InputDialogTitle": "Fenêtre d'entrée de texte", + "InputDialogOk": "OK", + "InputDialogCancel": "Annuler", + "InputDialogAddNewProfileTitle": "Choisir un nom de profil", + "InputDialogAddNewProfileHeader": "Merci d'entrer un nom de profil", + "InputDialogAddNewProfileSubtext": "(Longueur max.: {0})", + "AvatarChoose": "Choisir un avatar", + "AvatarSetBackgroundColor": "Choisir une couleur de fond", + "AvatarClose": "Fermer", + "ControllerSettingsLoadProfileToolTip": "Charger un profil", + "ControllerSettingsAddProfileToolTip": "Ajouter un profil", + "ControllerSettingsRemoveProfileToolTip": "Supprimer un profil", + "ControllerSettingsSaveProfileToolTip": "Enregistrer un profil", + "MenuBarFileToolsTakeScreenshot": "Prendre une capture d'écran", + "MenuBarFileToolsHideUi": "Masquer l'interface utilisateur", + "GameListContextMenuRunApplication": "Démarrer l'application", + "GameListContextMenuToggleFavorite": "Ajouter/Retirer des favoris", + "GameListContextMenuToggleFavoriteToolTip": "Activer/désactiver le statut favori du jeu", + "SettingsTabGeneralTheme": "Thème :", + "SettingsTabGeneralThemeDark": "Sombre", + "SettingsTabGeneralThemeLight": "Clair", + "ControllerSettingsConfigureGeneral": "Configurer", + "ControllerSettingsRumble": "Vibreur", + "ControllerSettingsRumbleStrongMultiplier": "Multiplicateur de vibrations fortes", + "ControllerSettingsRumbleWeakMultiplier": "Multiplicateur de vibrations faibles", + "DialogMessageSaveNotAvailableMessage": "Il n'y a aucune sauvegarde pour {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Voulez-vous créer une sauvegarde pour ce jeu ?", + "DialogConfirmationTitle": "Ryujinx - Confirmation", + "DialogUpdaterTitle": "Ryujinx - Mise à Jour", + "DialogErrorTitle": "Ryujinx - Erreur", + "DialogWarningTitle": "Ryujinx - Avertissement", + "DialogExitTitle": "Ryujinx - Quitter", + "DialogErrorMessage": "Ryujinx a rencontré une erreur", + "DialogExitMessage": "Êtes-vous sûr de vouloir fermer Ryujinx ?", + "DialogExitSubMessage": "Toutes les données non enregistrées seront perdues !", + "DialogMessageCreateSaveErrorMessage": "Une erreur s'est produite lors de la création de la sauvegarde spécifiée : {0}", + "DialogMessageFindSaveErrorMessage": "Une erreur s'est produite lors de la recherche de la sauvegarde spécifiée : {0}", + "FolderDialogExtractTitle": "Choisissez le dossier dans lequel extraire", + "DialogNcaExtractionMessage": "Extraction de la section {0} depuis {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Extracteur de la section NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Échec de l'extraction. Le NCA principal n'était pas présent dans le fichier sélectionné.", + "DialogNcaExtractionCheckLogErrorMessage": "Échec de l'extraction. Lisez le fichier journal pour plus d'informations.", + "DialogNcaExtractionSuccessMessage": "Extraction terminée avec succès.", + "DialogUpdaterConvertFailedMessage": "Échec de la conversion de la version actuelle de Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Annuler la mise à jour !", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Vous utilisez déjà la version la plus récente de Ryujinx !", + "DialogUpdaterFailedToGetVersionMessage": "Une erreur s'est produite lors de la tentative d'obtention des informations de publication de la version GitHub. Cela peut survenir lorsqu'une nouvelle version est en cours de compilation par GitHub Actions. Réessayez dans quelques minutes.", + "DialogUpdaterConvertFailedGithubMessage": "Impossible de convertir la version reçue de Ryujinx depuis Github Release.", + "DialogUpdaterDownloadingMessage": "Téléchargement de la mise à jour...", + "DialogUpdaterExtractionMessage": "Extraction de la mise à jour…", + "DialogUpdaterRenamingMessage": "Renommage de la mise à jour...", + "DialogUpdaterAddingFilesMessage": "Ajout d'une nouvelle mise à jour...", + "DialogUpdaterCompleteMessage": "Mise à jour terminée !", + "DialogUpdaterRestartMessage": "Voulez-vous redémarrer Ryujinx maintenant ?", + "DialogUpdaterNoInternetMessage": "Vous n'êtes pas connecté à Internet !", + "DialogUpdaterNoInternetSubMessage": "Veuillez vérifier que vous disposez d'une connexion Internet fonctionnelle !", + "DialogUpdaterDirtyBuildMessage": "Vous ne pouvez pas mettre à jour une version Dirty de Ryujinx !", + "DialogUpdaterDirtyBuildSubMessage": "Veuillez télécharger Ryujinx sur https://ryujinx.org/ si vous recherchez une version prise en charge.", + "DialogRestartRequiredMessage": "Redémarrage requis", + "DialogThemeRestartMessage": "Le thème a été enregistré. Un redémarrage est requis pour appliquer le thème.", + "DialogThemeRestartSubMessage": "Voulez-vous redémarrer", + "DialogFirmwareInstallEmbeddedMessage": "Voulez-vous installer le firmware intégré dans ce jeu ? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Aucun firmware installé n'a été trouvé mais Ryujinx a pu installer le firmware {0} à partir du jeu fourni.\nL'émulateur va maintenant démarrer.", + "DialogFirmwareNoFirmwareInstalledMessage": "Aucun Firmware installé", + "DialogFirmwareInstalledMessage": "Le firmware {0} a été installé", + "DialogInstallFileTypesSuccessMessage": "Types de fichiers installés avec succès!", + "DialogInstallFileTypesErrorMessage": "Échec de l'installation des types de fichiers.", + "DialogUninstallFileTypesSuccessMessage": "Types de fichiers désinstallés avec succès!", + "DialogUninstallFileTypesErrorMessage": "Échec de la désinstallation des types de fichiers.", + "DialogOpenSettingsWindowLabel": "Ouvrir la fenêtre de configuration", + "DialogControllerAppletTitle": "Controller Applet", + "DialogMessageDialogErrorExceptionMessage": "Erreur lors de l'affichage de la boîte de dialogue : {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Erreur lors de l'affichage du clavier logiciel: {0}", + "DialogErrorAppletErrorExceptionMessage": "Erreur lors de l'affichage de la boîte de dialogue ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPour plus d'informations sur la manière de corriger cette erreur, suivez notre Guide d'Installation.", + "DialogUserErrorDialogTitle": "Erreur Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Une erreur est survenue lors de la récupération des informations de l'API.", + "DialogAmiiboApiConnectErrorMessage": "Impossible de se connecter au serveur API Amiibo. Le service est peut-être hors service ou vous devriez peut-être vérifier que votre connexion internet est connectée.", + "DialogProfileInvalidProfileErrorMessage": "Le profil {0} est incompatible avec le système de configuration de manette actuel.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Le profil par défaut ne peut pas être écrasé", + "DialogProfileDeleteProfileTitle": "Supprimer le profil", + "DialogProfileDeleteProfileMessage": "Cette action est irréversible, êtes-vous sûr de vouloir continuer ?", + "DialogWarning": "Avertissement", + "DialogPPTCDeletionMessage": "Vous êtes sur le point de mettre en file d'attente une reconstruction PPTC au prochain démarrage de :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?", + "DialogPPTCDeletionErrorMessage": "Erreur lors de la purge du cache PPTC à {0}: {1}", + "DialogShaderDeletionMessage": "Vous êtes sur le point de supprimer le cache du Shader pour :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?", + "DialogShaderDeletionErrorMessage": "Erreur lors de la purge du cache du Shader à {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx a rencontré une erreur", + "DialogInvalidTitleIdErrorMessage": "Erreur d'UI : le jeu sélectionné n'a pas d'ID de titre valide", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware valide n'a pas été trouvé dans {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Installer le Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "La version {0} du système sera installée.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nCela remplacera la version actuelle du système {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nVoulez-vous continuer ?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installation du firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Version du système {0} installée avec succès.", + "DialogUserProfileDeletionWarningMessage": "Il n'y aurait aucun autre profil à ouvrir si le profil sélectionné est supprimé", + "DialogUserProfileDeletionConfirmMessage": "Voulez-vous supprimer le profil sélectionné ?", + "DialogUserProfileUnsavedChangesTitle": "Avertissement - Modifications non enregistrées", + "DialogUserProfileUnsavedChangesMessage": "Vous avez effectué des modifications sur ce profil d'utilisateur qui n'ont pas été enregistrées.", + "DialogUserProfileUnsavedChangesSubMessage": "Voulez-vous annuler les modifications ?", + "DialogControllerSettingsModifiedConfirmMessage": "Les paramètres actuels du contrôleur ont été mis à jour.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Voulez-vous sauvegarder ?", + "DialogLoadFileErrorMessage": "{0}. Fichier erroné : {1}", + "DialogModAlreadyExistsMessage": "Le mod existe déjà", + "DialogModInvalidMessage": "Le répertoire spécifié ne contient pas de mod !", + "DialogModDeleteNoParentMessage": "Impossible de supprimer : impossible de trouver le répertoire parent pour le mod \"{0} \" !", + "DialogDlcNoDlcErrorMessage": "Le fichier spécifié ne contient pas de DLC pour le titre sélectionné !", + "DialogPerformanceCheckLoggingEnabledMessage": "Vous avez activé la journalisation des traces, conçue pour être utilisée uniquement par les développeurs.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Pour des performances optimales, il est recommandé de désactiver la journalisation des traces. Souhaitez-vous désactiver la journalisation des traces maintenant ?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Vous avez activé l'extraction des shaders, qui est conçu pour être utilisé par les développeurs uniquement.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Pour des performances optimales, il est recommandé de désactiver l'extraction des shaders. Souhaitez-vous désactiver l'extraction des shaders maintenant ?", + "DialogLoadAppGameAlreadyLoadedMessage": "Un jeu a déjà été chargé", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Veuillez arrêter l'émulation ou fermer l'émulateur avant de lancer un autre jeu.", + "DialogUpdateAddUpdateErrorMessage": "Le fichier spécifié ne contient pas de mise à jour pour le titre sélectionné !", + "DialogSettingsBackendThreadingWarningTitle": "Avertissement - Backend Threading ", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx doit être redémarré après avoir changé cette option pour qu'elle s'applique complètement. Selon votre plate-forme, vous devrez peut-être désactiver manuellement le multithreading de votre pilote lorsque vous utilisez Ryujinx.", + "DialogModManagerDeletionWarningMessage": "Vous êtes sur le point de supprimer le mod : {0}\n\nÊtes-vous sûr de vouloir continuer ?", + "DialogModManagerDeletionAllWarningMessage": "Vous êtes sur le point de supprimer tous les mods pour ce titre.\n\nÊtes-vous sûr de vouloir continuer ?", + "SettingsTabGraphicsFeaturesOptions": "Fonctionnalités", + "SettingsTabGraphicsBackendMultithreading": "Interface graphique multithread", + "CommonAuto": "Auto", + "CommonOff": "Désactivé", + "CommonOn": "Activé", + "InputDialogYes": "Oui", + "InputDialogNo": "Non", + "DialogProfileInvalidProfileNameErrorMessage": "Le nom du fichier contient des caractères invalides. Veuillez réessayer.", + "MenuBarOptionsPauseEmulation": "Suspendre", + "MenuBarOptionsResumeEmulation": "Reprendre", + "AboutUrlTooltipMessage": "Cliquez pour ouvrir le site de Ryujinx dans votre navigateur par défaut.", + "AboutDisclaimerMessage": "Ryujinx n'est pas affilié à Nintendo™,\nou à aucun de ses partenaires, de quelque manière que ce soit.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) est utilisé\ndans notre émulation Amiibo.", + "AboutPatreonUrlTooltipMessage": "Cliquez pour ouvrir la page Patreon de Ryujinx dans votre navigateur par défaut.", + "AboutGithubUrlTooltipMessage": "Cliquez pour ouvrir la page GitHub de Ryujinx dans votre navigateur par défaut.", + "AboutDiscordUrlTooltipMessage": "Cliquez pour ouvrir une invitation au serveur Discord de Ryujinx dans votre navigateur par défaut.", + "AboutTwitterUrlTooltipMessage": "Cliquez pour ouvrir la page Twitter de Ryujinx dans votre navigateur par défaut.", + "AboutRyujinxAboutTitle": "À propos :", + "AboutRyujinxAboutContent": "Ryujinx est un émulateur pour la Nintendo Switch™.\nMerci de nous soutenir sur Patreon.\nObtenez toutes les dernières actualités sur notre Twitter ou notre Discord.\nLes développeurs intéressés à contribuer peuvent en savoir plus sur notre GitHub ou notre Discord.", + "AboutRyujinxMaintainersTitle": "Maintenu par :", + "AboutRyujinxMaintainersContentTooltipMessage": "Cliquez pour ouvrir la page Contributeurs dans votre navigateur par défaut.", + "AboutRyujinxSupprtersTitle": "Supporté sur Patreon par :", + "AmiiboSeriesLabel": "Séries Amiibo", + "AmiiboCharacterLabel": "Personnage", + "AmiiboScanButtonLabel": "Scanner", + "AmiiboOptionsShowAllLabel": "Afficher tous les Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack : Utiliser un tag Uuid aléatoire", + "DlcManagerTableHeadingEnabledLabel": "Activé", + "DlcManagerTableHeadingTitleIdLabel": "ID du titre", + "DlcManagerTableHeadingContainerPathLabel": "Chemin du conteneur", + "DlcManagerTableHeadingFullPathLabel": "Chemin complet", + "DlcManagerRemoveAllButton": "Tout supprimer", + "DlcManagerEnableAllButton": "Tout activer", + "DlcManagerDisableAllButton": "Tout désactiver", + "ModManagerDeleteAllButton": "Tout supprimer", + "MenuBarOptionsChangeLanguage": "Changer la langue", + "MenuBarShowFileTypes": "Afficher les types de fichiers", + "CommonSort": "Trier", + "CommonShowNames": "Afficher les noms", + "CommonFavorite": "Favoris", + "OrderAscending": "Croissant", + "OrderDescending": "Décroissant", + "SettingsTabGraphicsFeatures": "Fonctionnalités & Améliorations", + "ErrorWindowTitle": "Fenêtre d'erreur", + "ToggleDiscordTooltip": "Choisissez d'afficher ou non Ryujinx sur votre activité « en cours de jeu » Discord", + "AddGameDirBoxTooltip": "Entrez un répertoire de jeux à ajouter à la liste", + "AddGameDirTooltip": "Ajouter un répertoire de jeux à la liste", + "RemoveGameDirTooltip": "Supprimer le répertoire de jeu sélectionné", + "CustomThemeCheckTooltip": "Utilisez un thème personnalisé Avalonia pour modifier l'apparence des menus de l'émulateur", + "CustomThemePathTooltip": "Chemin vers le thème personnalisé de l'interface utilisateur", + "CustomThemeBrowseTooltip": "Parcourir vers un thème personnalisé pour l'interface utilisateur", + "DockModeToggleTooltip": "Le mode station d'accueil permet à la console émulée de se comporter comme une Nintendo Switch en mode station d'accueil, ce qui améliore la fidélité graphique dans la plupart des jeux. Inversement, la désactivation de cette option rendra la console émulée comme une console Nintendo Switch portable, réduisant la qualité graphique.\n\nConfigurer les controles du joueur 1 si vous prévoyez d'utiliser le mode station d'accueil; configurez les commandes portable si vous prévoyez d'utiliser le mode portable.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", + "DirectKeyboardTooltip": "Prise en charge de l'accès direct au clavier (HID). Permet aux jeux d'accéder à votre clavier comme périphérique de saisie de texte.\n\nFonctionne uniquement avec les jeux prenant en charge nativement l'utilisation du clavier sur le matériel Switch.\n\nLaissez OFF si vous n'êtes pas sûr.", + "DirectMouseTooltip": "Prise en charge de l'accès direct à la souris (HID). Permet aux jeux d'accéder à votre souris en tant que dispositif de pointage.\n\nFonctionne uniquement avec les jeux qui prennent en charge nativement les contrôles de souris sur le matériel Switch, ce qui est rare.\n\nLorsqu'il est activé, la fonctionnalité de l'écran tactile peut ne pas fonctionner.\n\nLaissez sur OFF si vous n'êtes pas sûr.", + "RegionTooltip": "Changer la région du système", + "LanguageTooltip": "Changer la langue du système", + "TimezoneTooltip": "Changer le fuseau horaire du système", + "TimeTooltip": "Changer l'heure du système", + "VSyncToggleTooltip": "La synchronisation verticale de la console émulée. Essentiellement un limiteur de trame pour la majorité des jeux ; le désactiver peut entraîner un fonctionnement plus rapide des jeux ou prolonger ou bloquer les écrans de chargement.\n\nPeut être activé ou désactivé en jeu avec un raccourci clavier de votre choix (F1 par défaut). Nous recommandons de le faire si vous envisagez de le désactiver.\n\nLaissez activé si vous n'êtes pas sûr.", + "PptcToggleTooltip": "Sauvegarde les fonctions JIT afin qu'elles n'aient pas besoin d'être à chaque fois recompiler lorsque le jeu se charge.\n\nRéduit les lags et accélère considérablement le temps de chargement après le premier lancement d'un jeu.\n\nLaissez par défaut si vous n'êtes pas sûr.", + "FsIntegrityToggleTooltip": "Vérifie si des fichiers sont corrompus lors du lancement d'un jeu, et si des fichiers corrompus sont détectés, affiche une erreur de hachage dans la console.\n\nN'a aucun impact sur les performances et est destiné à aider le dépannage.\n\nLaissez activer en cas d'incertitude.", + "AudioBackendTooltip": "Modifie le backend utilisé pour donnée un rendu audio.\n\nSDL2 est préféré, tandis que OpenAL et SoundIO sont utilisés comme backend secondaire. Le backend Dummy (Factice) ne rends aucun son.\n\nLaissez sur SDL2 si vous n'êtes pas sûr.", + "MemoryManagerTooltip": "Change la façon dont la mémoire émulée est mappée et utiliser. Cela affecte grandement les performances du processeur.\n\nRéglez sur Host Uncheked en cas d'incertitude.", + "MemoryManagerSoftwareTooltip": "Utilisez une table logicielle pour la traduction d'adresses. La plus grande précision est fournie, mais les performances en seront impacter.", + "MemoryManagerHostTooltip": "Mappez directement la mémoire dans l'espace d'adresses de l'hôte. Compilation et exécution JIT beaucoup plus rapides.", + "MemoryManagerUnsafeTooltip": "Mapper directement la mémoire dans la carte, mais ne pas masquer l'adresse dans l'espace d'adressage du client avant l'accès. Plus rapide, mais la sécurité sera négliger. L'application peut accéder à la mémoire depuis n'importe où dans Ryujinx, donc exécutez uniquement les programmes en qui vous avez confiance avec ce mode.", + "UseHypervisorTooltip": "Utiliser l'Hyperviseur au lieu du JIT. Améliore considérablement les performances lorsqu'il est disponible, mais peut être instable dans son état actuel.", + "DRamTooltip": "Utilise une disposition alternative de la mémoire pour imiter le kit de développeur de la Switch.\n\nActiver cette option uniquement pour les packs de textures 4k ou les mods à résolution 4k.\nN'améliore pas les performances, cause des crashs dans certains jeux si activer.\n\nLaissez Désactiver en cas d'incertitude.", + "IgnoreMissingServicesTooltip": "Ignore les services Horizon OS non-intégré. Cela peut aider à contourner les plantages lors du démarrage de certains jeux.\n\nActivez-le en cas d'incertitude.", + "GraphicsBackendThreadingTooltip": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support de multithreading. Légère augementation des performances sur les pilotes avec multithreading intégrer.\n\nRéglez sur Auto en cas d'incertitude.", + "GalThreadingTooltip": "Exécute des commandes du backend graphiques sur un second thread.\n\nAccélère la compilation des shaders, réduit les crashs et les lags, améliore les performances sur les pilotes GPU sans support de multithreading. Légère augementation des performances sur les pilotes avec multithreading intégrer.\n\nRéglez sur Auto en cas d'incertitude.", + "ShaderCacheToggleTooltip": "Enregistre un cache de shaders sur le disque dur, réduit le lag lors de multiples exécutions.\n\nLaissez Activer si vous n'êtes pas sûr.", + "ResolutionScaleTooltip": "Multiplie la résolution de rendu du jeu.\n\nQuelques jeux peuvent ne pas fonctionner avec cette fonctionnalité et sembler pixelisés même lorsque la résolution est augmentée ; pour ces jeux, vous devrez peut-être trouver des mods qui suppriment l'anti-aliasing ou qui augmentent leur résolution de rendu interne. Pour utiliser cette dernière option, vous voudrez probablement sélectionner \"Native\".\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres de côté et expérimenter jusqu'à ce que vous trouviez l'apparence souhaitée pour un jeu.\n\nGardez à l'esprit que 4x est excessif pour pratiquement n'importe quelle configuration.", + "ResolutionScaleEntryTooltip": "Échelle de résolution à virgule flottante, telle que : 1.5. Les échelles non intégrales sont plus susceptibles de causer des problèmes ou des crashs.", + "AnisotropyTooltip": "Niveau de filtrage anisotrope. Réglez sur Auto pour utiliser la valeur demandée par le jeu.", + "AspectRatioTooltip": "Rapport d'aspect appliqué à la fenêtre du moteur de rendu.\n\nChangez cela uniquement si vous utilisez un mod de rapport d'aspect pour votre jeu, sinon les graphismes seront étirés.\n\nLaissez sur 16:9 si vous n'êtes pas sûr.", + "ShaderDumpPathTooltip": "Chemin de copie des Shaders Graphiques", + "FileLogTooltip": "Sauver le journal de la console dans un fichier journal sur le disque. Cela n'affecte pas les performances.", + "StubLogTooltip": "Affiche les messages de log dans la console. N'affecte pas les performances.", + "InfoLogTooltip": "Affiche les messages de log d'informations dans la console. N'affecte pas les performances.", + "WarnLogTooltip": "Affiche les messages d'avertissement dans la console. N'affecte pas les performances.", + "ErrorLogTooltip": "Affiche les messages de log d'erreur dans la console. N'affecte pas les performances.", + "TraceLogTooltip": "Affiche la trace des messages de log dans la console. N'affecte pas les performances.", + "GuestLogTooltip": "Affiche les messages de log des invités dans la console. N'affecte pas les performances.", + "FileAccessLogTooltip": "Affiche les messages de log d'accès aux fichiers dans la console.", + "FSAccessLogModeTooltip": "Active la sortie du journal d'accès FS de la console. Les modes possibles sont 0-3", + "DeveloperOptionTooltip": "Utiliser avec précaution", + "OpenGlLogLevel": "Nécessite l'activation des niveaux de journalisation appropriés", + "DebugLogTooltip": "Affiche les messages de débogage dans la console.\n\nN'utilisez ceci que si un membre du personnel le demande, car cela rendra les logs difficiles à lire et réduit les performances de l'émulateur.", + "LoadApplicationFileTooltip": "Ouvrir un explorateur de fichiers pour choisir un fichier compatible Switch à charger", + "LoadApplicationFolderTooltip": "Ouvrir un explorateur de fichiers pour choisir une application Switch compatible et décompressée à charger", + "OpenRyujinxFolderTooltip": "Ouvrir le dossier du système de fichiers Ryujinx", + "OpenRyujinxLogsTooltip": "Ouvre le dossier dans lequel les journaux sont écrits", + "ExitTooltip": "Quitter Ryujinx", + "OpenSettingsTooltip": "Ouvrir la fenêtre de configuration", + "OpenProfileManagerTooltip": "Ouvrir la fenêtre du gestionnaire de profils d'utilisateurs", + "StopEmulationTooltip": "Arrêter l'émulation du jeu en cours et revenir à la sélection des jeux", + "CheckUpdatesTooltip": "Vérifier les mises à jour de Ryujinx", + "OpenAboutTooltip": "Ouvrir la fenêtre À Propos", + "GridSize": "Taille de la grille", + "GridSizeTooltip": "Modifier la taille des éléments de la grille", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugais brésilien", + "AboutRyujinxContributorsButtonHeader": "Voir tous les contributeurs", + "SettingsTabSystemAudioVolume": "Volume :", + "AudioVolumeTooltip": "Modifier le volume audio", + "SettingsTabSystemEnableInternetAccess": "Accès Internet Invité/Mode LAN", + "EnableInternetAccessTooltip": "Permet à l'application émulée de se connecter à Internet.\n\nLes jeux avec un mode LAN peuvent se connecter les uns aux autres lorsque cette option est cochée et que les systèmes sont connectés au même point d'accès. Cela inclut également les vrais consoles.\n\nCette option n'autorise PAS la connexion aux serveurs Nintendo. Elle peut faire planter certains jeux qui essaient de se connecter à l'Internet.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", + "GameListContextMenuManageCheatToolTip": "Gérer la triche", + "GameListContextMenuManageCheat": "Gérer la triche", + "GameListContextMenuManageModToolTip": "Gérer les mods", + "GameListContextMenuManageMod": "Gérer les mods", + "ControllerSettingsStickRange": "Intervalle :", + "DialogStopEmulationTitle": "Ryujinx - Arrêt de l'émulation", + "DialogStopEmulationMessage": "Êtes-vous sûr de vouloir arrêter l'émulation ?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Réseau", + "SettingsTabNetworkConnection": "Connexion réseau", + "SettingsTabCpuCache": "Cache CPU", + "SettingsTabCpuMemory": "Mémoire CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Merci de mettre à jour Ryujinx via FlatHub.", + "UpdaterDisabledWarningTitle": "Mise à jour désactivée !", + "ControllerSettingsRotate90": "Faire pivoter de 90° à droite", + "IconSize": "Taille d'icône", + "IconSizeTooltip": "Changer la taille des icônes de jeu", + "MenuBarOptionsShowConsole": "Afficher la console", + "ShaderCachePurgeError": "Erreur lors de la purge du cache du Shader à {0}: {1}", + "UserErrorNoKeys": "Clés introuvables", + "UserErrorNoFirmware": "Firmware introuvable", + "UserErrorFirmwareParsingFailed": "Erreur d'analyse du firmware", + "UserErrorApplicationNotFound": " Application introuvable", + "UserErrorUnknown": "Erreur inconnue", + "UserErrorUndefined": "Erreur non définie", + "UserErrorNoKeysDescription": "Ryujinx n'a pas pu trouver votre fichier 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx n'a pas trouvé de firmwares installés", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx n'a pas pu analyser le firmware fourni. Cela est généralement dû à des clés obsolètes.", + "UserErrorApplicationNotFoundDescription": "Ryujinx n'a pas pu trouver une application valide dans le chemin indiqué.", + "UserErrorUnknownDescription": "Une erreur inconnue est survenue !", + "UserErrorUndefinedDescription": "Une erreur inconnue est survenue ! Cela ne devrait pas se produire, merci de contacter un développeur !", + "OpenSetupGuideMessage": "Ouvrir le guide d'installation", + "NoUpdate": "Aucune mise à jour", + "TitleUpdateVersionLabel": "Version {0}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmation", + "FileDialogAllTypes": "Tous les types", + "Never": "Jamais", + "SwkbdMinCharacters": "Doit comporter au moins {0} caractères", + "SwkbdMinRangeCharacters": "Doit comporter entre {0} et {1} caractères", + "SoftwareKeyboard": "Clavier logiciel", + "SoftwareKeyboardModeNumeric": "Doit être 0-9 ou '.' uniquement", + "SoftwareKeyboardModeAlphabet": "Doit être uniquement des caractères non CJK", + "SoftwareKeyboardModeASCII": "Doit être uniquement du texte ASCII", + "ControllerAppletControllers": "Contrôleurs pris en charge :", + "ControllerAppletPlayers": "Joueurs :", + "ControllerAppletDescription": "Votre configuration actuelle n'est pas valide. Ouvrez les paramètres et reconfigurez vos contrôles.", + "ControllerAppletDocked": "Mode station d'accueil défini. Le mode contrôle portable doit être désactivé.", + "UpdaterRenaming": "Renommage des anciens fichiers...", + "UpdaterRenameFailed": "Impossible de renommer le fichier : {0}", + "UpdaterAddingFiles": "Ajout des nouveaux fichiers...", + "UpdaterExtracting": "Extraction de la mise à jour…", + "UpdaterDownloading": "Téléchargement de la mise à jour...", + "Game": "Jeu", + "Docked": "Mode station d'accueil", + "Handheld": "Mode Portable", + "ConnectionError": "Erreur de connexion.", + "AboutPageDeveloperListMore": "{0} et plus...", + "ApiError": "Erreur API.", + "LoadingHeading": "Chargement {0}", + "CompilingPPTC": "Compilation PTC", + "CompilingShaders": "Compilation des shaders", + "AllKeyboards": "Tous les claviers", + "OpenFileDialogTitle": "Sélectionnez un fichier supporté à ouvrir", + "OpenFolderDialogTitle": "Sélectionnez un dossier avec un jeu décompressé", + "AllSupportedFormats": "Tous les formats supportés", + "RyujinxUpdater": "Mise à jour de Ryujinx", + "SettingsTabHotkeys": "Raccourcis clavier", + "SettingsTabHotkeysHotkeys": "Raccourcis clavier", + "SettingsTabHotkeysToggleVsyncHotkey": "Activer/désactiver la VSync :", + "SettingsTabHotkeysScreenshotHotkey": "Capture d'écran :", + "SettingsTabHotkeysShowUiHotkey": "Afficher UI :", + "SettingsTabHotkeysPauseHotkey": "Suspendre :", + "SettingsTabHotkeysToggleMuteHotkey": "Muet : ", + "ControllerMotionTitle": "Réglages du contrôle par mouvement", + "ControllerRumbleTitle": "Paramètres de vibration", + "SettingsSelectThemeFileDialogTitle": "Sélectionner un fichier de thème", + "SettingsXamlThemeFile": "Fichier thème Xaml", + "AvatarWindowTitle": "Gérer les Comptes - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Inconnu", + "Usage": "Utilisation", + "Writable": "Ecriture possible", + "SelectDlcDialogTitle": "Sélectionner les fichiers DLC", + "SelectUpdateDialogTitle": "Sélectionner les fichiers de mise à jour", + "SelectModDialogTitle": "Sélectionner le répertoire du mod", + "UserProfileWindowTitle": "Gestionnaire de profils utilisateur", + "CheatWindowTitle": "Gestionnaire de triches", + "DlcWindowTitle": "Gérer le contenu téléchargeable pour {0} ({1})", + "ModWindowTitle": "Gérer les mods pour {0} ({1})", + "UpdateWindowTitle": "Gestionnaire de mises à jour", + "CheatWindowHeading": "Cheats disponibles pour {0} [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "{0} Contenu(s) téléchargeable(s)", + "ModWindowHeading": "{0} Mod(s)", + "UserProfilesEditProfile": "Éditer la sélection", + "Cancel": "Annuler", + "Save": "Enregistrer", + "Discard": "Abandonner", + "Paused": "Suspendu", + "UserProfilesSetProfileImage": "Définir l'image de profil", + "UserProfileEmptyNameError": "Le nom est requis", + "UserProfileNoImageError": "L'image du profil doit être définie", + "GameUpdateWindowHeading": "Gérer les mises à jour pour {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "Augmenter la résolution :", + "SettingsTabHotkeysResScaleDownHotkey": "Diminuer la résolution :", + "UserProfilesName": "Nom :", + "UserProfilesUserId": "Identifiant de l'utilisateur :", + "SettingsTabGraphicsBackend": "API de Rendu", + "SettingsTabGraphicsBackendTooltip": "Sélectionnez le moteur graphique qui sera utilisé dans l'émulateur.\n\nVulkan est globalement meilleur pour toutes les cartes graphiques modernes, tant que leurs pilotes sont à jour. Vulkan offre également une compilation de shaders plus rapide (moins de saccades) sur tous les fournisseurs de GPU.\n\nOpenGL peut obtenir de meilleurs résultats sur d'anciennes cartes graphiques Nvidia, sur d'anciennes cartes graphiques AMD sous Linux, ou sur des GPU avec moins de VRAM, bien que les saccades dues à la compilation des shaders soient plus importantes.\n\nRéglez sur Vulkan si vous n'êtes pas sûr. Réglez sur OpenGL si votre GPU ne prend pas en charge Vulkan même avec les derniers pilotes graphiques.", + "SettingsEnableTextureRecompression": "Activer la recompression des textures", + "SettingsEnableTextureRecompressionTooltip": "Les jeux utilisant ce format de texture incluent Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder et The Legend of Zelda: Tears of the Kingdom.\n\nLes cartes graphiques avec 4 Go ou moins de VRAM risquent probablement de planter à un moment donné lors de l'exécution de ces jeux.\n\nActivez uniquement si vous manquez de VRAM sur les jeux mentionnés ci-dessus. Laissez DÉSACTIVÉ si vous n'êtes pas sûr.", + "SettingsTabGraphicsPreferredGpu": "GPU préféré", + "SettingsTabGraphicsPreferredGpuTooltip": "Sélectionnez la carte graphique qui sera utilisée avec l'interface graphique Vulkan.\n\nCela ne change pas le GPU qu'OpenGL utilisera.\n\nChoisissez le GPU noté \"dGPU\" si vous n'êtes pas sûr. S'il n'y en a pas, ne pas modifier.", + "SettingsAppRequiredRestartMessage": "Redémarrage de Ryujinx requis", + "SettingsGpuBackendRestartMessage": "Les paramètres de l'interface graphique ou du GPU ont été modifiés. Cela nécessitera un redémarrage pour être appliqué", + "SettingsGpuBackendRestartSubMessage": "\n\nVoulez-vous redémarrer maintenant ?", + "RyujinxUpdaterMessage": "Voulez-vous mettre à jour Ryujinx vers la dernière version ?", + "SettingsTabHotkeysVolumeUpHotkey": "Augmenter le volume :", + "SettingsTabHotkeysVolumeDownHotkey": "Diminuer le volume :", + "SettingsEnableMacroHLE": "Activer les macros HLE", + "SettingsEnableMacroHLETooltip": "Émulation de haut niveau du code de Macro GPU.\n\nAméliore les performances, mais peut causer des artefacts graphiques dans certains jeux.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", + "SettingsEnableColorSpacePassthrough": "Traversée de l'espace colorimétrique", + "SettingsEnableColorSpacePassthroughTooltip": "Dirige l'interface graphique Vulkan pour qu'il transmette les informations de couleur sans spécifier d'espace colorimétrique. Pour les utilisateurs possédant des écrans Wide Color Gamut, cela peut entraîner des couleurs plus vives, au détriment de l'exactitude des couleurs.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Gérer les sauvegardes", + "DeleteUserSave": "Voulez-vous supprimer la sauvegarde de l'utilisateur pour ce jeu ?", + "IrreversibleActionNote": "Cette action n'est pas réversible.", + "SaveManagerHeading": "Gérer les sauvegardes pour {0} ({1})", + "SaveManagerTitle": "Gestionnaire de sauvegarde", + "Name": "Nom ", + "Size": "Taille", + "Search": "Rechercher", + "UserProfilesRecoverLostAccounts": "Récupérer les comptes perdus", + "Recover": "Récupérer", + "UserProfilesRecoverHeading": "Des sauvegardes ont été trouvées pour les comptes suivants", + "UserProfilesRecoverEmptyList": "Aucun profil à restaurer", + "GraphicsAATooltip": "FXAA floute la plupart de l'image, tandis que SMAA tente de détecter les contours dentelés et de les lisser.\n\nIl n'est pas recommandé de l'utiliser en conjonction avec le filtre de mise à l'échelle FSR.\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres de côté et expérimenter jusqu'à ce que vous trouviez l'apparence souhaitée pour un jeu.\n\nLaissez sur NONE si vous n'êtes pas sûr.", + "GraphicsAALabel": "Anticrénelage :", + "GraphicsScalingFilterLabel": "Filtre de mise à l'échelle :", + "GraphicsScalingFilterTooltip": "Choisissez le filtre de mise à l'échelle qui sera appliqué lors de l'utilisation de la mise à l'échelle de la résolution.\n\nLe filtre bilinéaire fonctionne bien pour les jeux en 3D et constitue une option par défaut sûre.\n\nLe filtre le plus proche est recommandé pour les jeux de pixel art.\n\nFSR 1.0 est simplement un filtre de netteté, non recommandé pour une utilisation avec FXAA ou SMAA.\n\nCette option peut être modifiée pendant qu'un jeu est en cours d'exécution en cliquant sur \"Appliquer\" ci-dessous ; vous pouvez simplement déplacer la fenêtre des paramètres de côté et expérimenter jusqu'à ce que vous trouviez l'aspect souhaité pour un jeu.\n\nLaissez sur BILINEAR si vous n'êtes pas sûr.", + "GraphicsScalingFilterBilinear": "Bilinéaire", + "GraphicsScalingFilterNearest": "Le plus proche", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "Niveau ", + "GraphicsScalingFilterLevelTooltip": "Définissez le niveau de netteté FSR 1.0. Plus élevé signifie plus net.", + "SmaaLow": "SMAA Faible", + "SmaaMedium": "SMAA moyen", + "SmaaHigh": "SMAA Élevé", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Modifier Utilisateur", + "UserEditorTitleCreate": "Créer Utilisateur", + "SettingsTabNetworkInterface": "Interface Réseau :", + "NetworkInterfaceTooltip": "L'interface réseau utilisée pour les fonctionnalités LAN/LDN.\n\nEn conjonction avec un VPN ou XLink Kai et un jeu prenant en charge le LAN, peut être utilisée pour simuler une connexion sur le même réseau via Internet.\n\nLaissez sur DEFAULT si vous n'êtes pas sûr.", + "NetworkInterfaceDefault": "Par défaut", + "PackagingShaders": "Empaquetage des Shaders", + "AboutChangelogButton": "Voir le Changelog sur GitHub", + "AboutChangelogButtonTooltipMessage": "Cliquez pour ouvrir le changelog de cette version dans votre navigateur par défaut.", + "SettingsTabNetworkMultiplayer": "Multijoueur", + "MultiplayerMode": "Mode :", + "MultiplayerModeTooltip": "Changer le mode multijoueur LDN.\n\nLdnMitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", + "MultiplayerModeDisabled": "Désactivé", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json new file mode 100644 index 00000000..848f7808 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -0,0 +1,780 @@ +{ + "Language": "עִברִית", + "MenuBarFileOpenApplet": "פתח יישומון", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "פתח את יישומון עורך ה- Mii במצב עצמאי", + "SettingsTabInputDirectMouseAccess": "גישה ישירה לעכבר", + "SettingsTabSystemMemoryManagerMode": "מצב מנהל זיכרון:", + "SettingsTabSystemMemoryManagerModeSoftware": "תוכנה", + "SettingsTabSystemMemoryManagerModeHost": "מארח (מהיר)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "מארח לא מבוקר (המהיר ביותר, לא בטוח)", + "SettingsTabSystemUseHypervisor": "השתמש ב Hypervisor", + "MenuBarFile": "_קובץ", + "MenuBarFileOpenFromFile": "_טען יישום מקובץ", + "MenuBarFileOpenUnpacked": "טען משחק _שאינו ארוז", + "MenuBarFileOpenEmuFolder": "פתח את תיקיית ריוג'ינקס", + "MenuBarFileOpenLogsFolder": "פתח את תיקיית קבצי הלוג", + "MenuBarFileExit": "_יציאה", + "MenuBarOptions": "_אפשרויות", + "MenuBarOptionsToggleFullscreen": "שנה מצב- מסך מלא", + "MenuBarOptionsStartGamesInFullscreen": "התחל משחקים במסך מלא", + "MenuBarOptionsStopEmulation": "עצור אמולציה", + "MenuBarOptionsSettings": "_הגדרות", + "MenuBarOptionsManageUserProfiles": "_נהל פרופילי משתמש", + "MenuBarActions": "_פעולות", + "MenuBarOptionsSimulateWakeUpMessage": "דמה הודעת השכמה", + "MenuBarActionsScanAmiibo": "סרוק אמיבו", + "MenuBarTools": "_כלים", + "MenuBarToolsInstallFirmware": "התקן קושחה", + "MenuBarFileToolsInstallFirmwareFromFile": "התקן קושחה מקובץ- ZIP/XCI", + "MenuBarFileToolsInstallFirmwareFromDirectory": "התקן קושחה מתוך תקייה", + "MenuBarToolsManageFileTypes": "ניהול סוגי קבצים", + "MenuBarToolsInstallFileTypes": "סוגי קבצי התקנה", + "MenuBarToolsUninstallFileTypes": "סוגי קבצי הסרה", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "_עזרה", + "MenuBarHelpCheckForUpdates": "חפש עדכונים", + "MenuBarHelpAbout": "אודות", + "MenuSearch": "חפש...", + "GameListHeaderFavorite": "אהוב", + "GameListHeaderIcon": "סמל", + "GameListHeaderApplication": "שם", + "GameListHeaderDeveloper": "מפתח", + "GameListHeaderVersion": "גרסה", + "GameListHeaderTimePlayed": "זמן משחק", + "GameListHeaderLastPlayed": "שוחק לאחרונה", + "GameListHeaderFileExtension": "סיומת קובץ", + "GameListHeaderFileSize": "גודל הקובץ", + "GameListHeaderPath": "נתיב", + "GameListContextMenuOpenUserSaveDirectory": "פתח את תקיית השמור של המשתמש", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "פותח את תקיית השמור של המשתמש ביישום הנוכחי", + "GameListContextMenuOpenDeviceSaveDirectory": "פתח את תקיית השמור של המכשיר", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "פותח את הספרייה המכילה את שמור המכשיר של היישום", + "GameListContextMenuOpenBcatSaveDirectory": "פתח את תקיית השמור של ה-BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "פותח את תקיית שמור ה-BCAT של היישום", + "GameListContextMenuManageTitleUpdates": "מנהל עדכוני משחקים", + "GameListContextMenuManageTitleUpdatesToolTip": "פותח את חלון מנהל עדכוני המשחקים", + "GameListContextMenuManageDlc": "מנהל הרחבות", + "GameListContextMenuManageDlcToolTip": "פותח את חלון מנהל הרחבות המשחקים", + "GameListContextMenuCacheManagement": "ניהול מטמון", + "GameListContextMenuCacheManagementPurgePptc": "הוסף PPTC לתור בנייה מחדש", + "GameListContextMenuCacheManagementPurgePptcToolTip": "גרום ל-PPTC להבנות מחדש בפתיחה הבאה של המשחק", + "GameListContextMenuCacheManagementPurgeShaderCache": "ניקוי מטמון הצללות", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "מוחק את מטמון ההצללות של היישום", + "GameListContextMenuCacheManagementOpenPptcDirectory": "פתח את תקיית PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "פותח את התקייה של מטמון ה-PPTC של האפליקציה", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "פתח את תקיית המטמון של ההצללות", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "פותח את תקיית מטמון ההצללות של היישום", + "GameListContextMenuExtractData": "חילוץ נתונים", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "חלץ את קטע ה-ExeFS מתצורת היישום הנוכחית (כולל עדכונים)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "חלץ את קטע ה-RomFS מתצורת היישום הנוכחית (כולל עדכונים)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "חלץ את קטע ה-Logo מתצורת היישום הנוכחית (כולל עדכונים)", + "GameListContextMenuCreateShortcut": "ליצור קיצור דרך לאפליקציה", + "GameListContextMenuCreateShortcutToolTip": "ליצור קיצור דרך בשולחן העבודה שיפתח את אפליקציה זו", + "GameListContextMenuCreateShortcutToolTipMacOS": "ליצור קיצור דרך בתיקיית האפליקציות של macOS שיפתח את אפליקציה זו", + "GameListContextMenuOpenModsDirectory": "פתח תיקיית מודים", + "GameListContextMenuOpenModsDirectoryToolTip": "פותח את התיקייה שמכילה מודים של האפליקציה", + "GameListContextMenuOpenSdModsDirectory": "פתח תיקיית מודים של Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "פותח את תיקיית כרטיס ה-SD החלופית של Atmosphere המכילה את המודים של האפליקציה. שימושי עבור מודים שארוזים עבור חומרה אמיתית.", + "StatusBarGamesLoaded": "{1}/{0} משחקים נטענו", + "StatusBarSystemVersion": "גרסת מערכת: {0}", + "LinuxVmMaxMapCountDialogTitle": "זוהתה מגבלה נמוכה עבור מיפויי זיכרון", + "LinuxVmMaxMapCountDialogTextPrimary": "האם תרצה להגביר את הערך של vm.max_map_count ל{0}", + "LinuxVmMaxMapCountDialogTextSecondary": "משחקים מסוימים עלולים לייצר עוד מיפויי זיכרון ממה שמתאפשר. Ryujinx יקרוס ברגע שהמגבלה תחרוג.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "כן, עד האתחול הבא", + "LinuxVmMaxMapCountDialogButtonPersistent": "כן, לצמיתות", + "LinuxVmMaxMapCountWarningTextPrimary": "הכמות המירבית של מיפויי הזיכרון נמוכה מהמומלץ.", + "LinuxVmMaxMapCountWarningTextSecondary": "הערך הנוכחי של vm.max_map_count {0} נמוך מ{1}. משחקים מסוימים עלולים לייצר עוד מיפוי זיכרון ממה שמתאפשר.Ryujinx יקרוס ברגע שהמגבלה תחרוג.\n\nיתכן ותרצה להעלות את המגבלה הנוכחית או להתקין את pkexec, אשר יאפשר לRyujinx לסייע בכך.", + "Settings": "הגדרות", + "SettingsTabGeneral": "ממשק משתמש", + "SettingsTabGeneralGeneral": "כללי", + "SettingsTabGeneralEnableDiscordRichPresence": "הפעלת תצוגה עשירה בדיסקורד", + "SettingsTabGeneralCheckUpdatesOnLaunch": "בדוק אם קיימים עדכונים בהפעלה", + "SettingsTabGeneralShowConfirmExitDialog": "הראה דיאלוג \"אשר יציאה\"", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", + "SettingsTabGeneralHideCursor": "הסתר את הסמן", + "SettingsTabGeneralHideCursorNever": "אף פעם", + "SettingsTabGeneralHideCursorOnIdle": "במצב סרק", + "SettingsTabGeneralHideCursorAlways": "תמיד", + "SettingsTabGeneralGameDirectories": "תקיות משחקים", + "SettingsTabGeneralAdd": "הוסף", + "SettingsTabGeneralRemove": "הסר", + "SettingsTabSystem": "מערכת", + "SettingsTabSystemCore": "ליבה", + "SettingsTabSystemSystemRegion": "אזור מערכת:", + "SettingsTabSystemSystemRegionJapan": "יפן", + "SettingsTabSystemSystemRegionUSA": "ארה\"ב", + "SettingsTabSystemSystemRegionEurope": "אירופה", + "SettingsTabSystemSystemRegionAustralia": "אוסטרליה", + "SettingsTabSystemSystemRegionChina": "סין", + "SettingsTabSystemSystemRegionKorea": "קוריאה", + "SettingsTabSystemSystemRegionTaiwan": "טייוואן", + "SettingsTabSystemSystemLanguage": "שפת המערכת:", + "SettingsTabSystemSystemLanguageJapanese": "יפנית", + "SettingsTabSystemSystemLanguageAmericanEnglish": "אנגלית אמריקאית", + "SettingsTabSystemSystemLanguageFrench": "צרפתית", + "SettingsTabSystemSystemLanguageGerman": "גרמנית", + "SettingsTabSystemSystemLanguageItalian": "איטלקית", + "SettingsTabSystemSystemLanguageSpanish": "ספרדית", + "SettingsTabSystemSystemLanguageChinese": "סינית", + "SettingsTabSystemSystemLanguageKorean": "קוריאנית", + "SettingsTabSystemSystemLanguageDutch": "הולנדית", + "SettingsTabSystemSystemLanguagePortuguese": "פורטוגזית", + "SettingsTabSystemSystemLanguageRussian": "רוסית", + "SettingsTabSystemSystemLanguageTaiwanese": "טייוואנית", + "SettingsTabSystemSystemLanguageBritishEnglish": "אנגלית בריטית", + "SettingsTabSystemSystemLanguageCanadianFrench": "צרפתית קנדית", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "ספרדית אמריקה הלטינית", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "סינית פשוטה", + "SettingsTabSystemSystemLanguageTraditionalChinese": "סינית מסורתית", + "SettingsTabSystemSystemTimeZone": "אזור זמן מערכת:", + "SettingsTabSystemSystemTime": "זמן מערכת:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS בדיקות תקינות", + "SettingsTabSystemAudioBackend": "אחראי שמע:", + "SettingsTabSystemAudioBackendDummy": "גולם", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "האצות", + "SettingsTabSystemHacksNote": "עלול לגרום לאי יציבות", + "SettingsTabSystemExpandDramSize": "השתמש בפריסת זיכרון חלופית (נועד למפתחים)", + "SettingsTabSystemIgnoreMissingServices": "התעלם משירותים חסרים", + "SettingsTabGraphics": "גרפיקה", + "SettingsTabGraphicsAPI": "ממשק גראפי", + "SettingsTabGraphicsEnableShaderCache": "הפעל מטמון הצללות", + "SettingsTabGraphicsAnisotropicFiltering": "סינון אניסוטרופי:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "אוטומטי", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "קנה מידה של רזולוציה:", + "SettingsTabGraphicsResolutionScaleCustom": "מותאם אישית (לא מומלץ)", + "SettingsTabGraphicsResolutionScaleNative": "מקורי (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (לא מומלץ)", + "SettingsTabGraphicsAspectRatio": "יחס גובה-רוחב:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "מתח לגודל חלון", + "SettingsTabGraphicsDeveloperOptions": "אפשרויות מפתח", + "SettingsTabGraphicsShaderDumpPath": "Graphics Shader Dump Path:", + "SettingsTabLogging": "רישום", + "SettingsTabLoggingLogging": "רישום", + "SettingsTabLoggingEnableLoggingToFile": "אפשר רישום לקובץ", + "SettingsTabLoggingEnableStubLogs": "אפשר רישום בדל", + "SettingsTabLoggingEnableInfoLogs": "אפשר רישום מידע", + "SettingsTabLoggingEnableWarningLogs": "אפשר רישום אזהרות", + "SettingsTabLoggingEnableErrorLogs": "אפשר רישום שגיאות", + "SettingsTabLoggingEnableTraceLogs": "הפעל רישום מעקבי", + "SettingsTabLoggingEnableGuestLogs": "הפעל רישום מארח", + "SettingsTabLoggingEnableFsAccessLogs": "אפשר רישום גישת קבצי מערכת", + "SettingsTabLoggingFsGlobalAccessLogMode": "מצב רישום גלובלי של גישת קבצי מערכת", + "SettingsTabLoggingDeveloperOptions": "אפשרויות מפתח", + "SettingsTabLoggingDeveloperOptionsNote": "אזהרה: יפחית ביצועים", + "SettingsTabLoggingGraphicsBackendLogLevel": "רישום גרפיקת קצה אחורי:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "כלום", + "SettingsTabLoggingGraphicsBackendLogLevelError": "שגיאה", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "האטות", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "הכל", + "SettingsTabLoggingEnableDebugLogs": "אפשר רישום ניפוי באגים", + "SettingsTabInput": "קלט", + "SettingsTabInputEnableDockedMode": "מצב עגינה", + "SettingsTabInputDirectKeyboardAccess": "גישה ישירה למקלדת", + "SettingsButtonSave": "שמירה", + "SettingsButtonClose": "סגירה", + "SettingsButtonOk": "אישור", + "SettingsButtonCancel": "ביטול", + "SettingsButtonApply": "החל", + "ControllerSettingsPlayer": "שחקן/ית", + "ControllerSettingsPlayer1": "שחקן/ית 1", + "ControllerSettingsPlayer2": "שחקן/ית 2", + "ControllerSettingsPlayer3": "שחקן/ית 3", + "ControllerSettingsPlayer4": "שחקן/ית 4", + "ControllerSettingsPlayer5": "שחקן/ית 5", + "ControllerSettingsPlayer6": "שחקן/ית 6", + "ControllerSettingsPlayer7": "שחקן/ית 7", + "ControllerSettingsPlayer8": "שחקן/ית 8", + "ControllerSettingsHandheld": "נייד", + "ControllerSettingsInputDevice": "מכשיר קלט", + "ControllerSettingsRefresh": "רענון", + "ControllerSettingsDeviceDisabled": "מושבת", + "ControllerSettingsControllerType": "סוג שלט", + "ControllerSettingsControllerTypeHandheld": "נייד", + "ControllerSettingsControllerTypeProController": "שלט פרו ", + "ControllerSettingsControllerTypeJoyConPair": "ג'ויקון הותאם", + "ControllerSettingsControllerTypeJoyConLeft": "ג'ויקון שמאלי ", + "ControllerSettingsControllerTypeJoyConRight": "ג'ויקון ימני", + "ControllerSettingsProfile": "פרופיל", + "ControllerSettingsProfileDefault": "ברירת המחדל", + "ControllerSettingsLoad": "טעינה", + "ControllerSettingsAdd": "הוספה", + "ControllerSettingsRemove": "הסר", + "ControllerSettingsButtons": "כפתורים", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "כפתורי כיוונים", + "ControllerSettingsDPadUp": "מעלה", + "ControllerSettingsDPadDown": "מטה", + "ControllerSettingsDPadLeft": "שמאלה", + "ControllerSettingsDPadRight": "ימינה", + "ControllerSettingsStickButton": "כפתור", + "ControllerSettingsStickUp": "למעלה", + "ControllerSettingsStickDown": "למטה", + "ControllerSettingsStickLeft": "שמאלה", + "ControllerSettingsStickRight": "ימינה", + "ControllerSettingsStickStick": "סטיק", + "ControllerSettingsStickInvertXAxis": "הפיכת הX של הסטיק", + "ControllerSettingsStickInvertYAxis": "הפיכת הY של הסטיק", + "ControllerSettingsStickDeadzone": "שטח מת:", + "ControllerSettingsLStick": "מקל שמאלי", + "ControllerSettingsRStick": "מקל ימני", + "ControllerSettingsTriggersLeft": "הדק שמאלי", + "ControllerSettingsTriggersRight": "הדק ימני", + "ControllerSettingsTriggersButtonsLeft": "כפתור הדק שמאלי", + "ControllerSettingsTriggersButtonsRight": "כפתור הדק ימני", + "ControllerSettingsTriggers": "הדקים", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "כפתורים משמאל", + "ControllerSettingsExtraButtonsRight": "כפתורים מימין", + "ControllerSettingsMisc": "שונות", + "ControllerSettingsTriggerThreshold": "סף הדק:", + "ControllerSettingsMotion": "תנועה", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "השתמש בתנועת CemuHook תואמת ", + "ControllerSettingsMotionControllerSlot": "מיקום שלט", + "ControllerSettingsMotionMirrorInput": "קלט מראה", + "ControllerSettingsMotionRightJoyConSlot": "מיקום ג'ויקון ימני", + "ControllerSettingsMotionServerHost": "מארח השרת:", + "ControllerSettingsMotionGyroSensitivity": "רגישות ג'ירוסקופ:", + "ControllerSettingsMotionGyroDeadzone": "שטח מת של הג'ירוסקופ:", + "ControllerSettingsSave": "שמירה", + "ControllerSettingsClose": "סגירה", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", + "UserProfilesSelectedUserProfile": "פרופיל המשתמש הנבחר:", + "UserProfilesSaveProfileName": "שמור שם פרופיל", + "UserProfilesChangeProfileImage": "שנה תמונת פרופיל", + "UserProfilesAvailableUserProfiles": "פרופילי משתמש זמינים:", + "UserProfilesAddNewProfile": "צור פרופיל", + "UserProfilesDelete": "מחיקה", + "UserProfilesClose": "סגור", + "ProfileNameSelectionWatermark": "בחרו כינוי", + "ProfileImageSelectionTitle": "בחירת תמונת פרופיל", + "ProfileImageSelectionHeader": "בחרו תמונת פרופיל", + "ProfileImageSelectionNote": "אתם יכולים לייבא תמונת פרופיל מותאמת אישית, או לבחור אווטאר מקושחת המערכת", + "ProfileImageSelectionImportImage": "ייבוא קובץ תמונה", + "ProfileImageSelectionSelectAvatar": "בחרו אוואטר קושחה", + "InputDialogTitle": "דיאלוג קלט", + "InputDialogOk": "בסדר", + "InputDialogCancel": "ביטול", + "InputDialogAddNewProfileTitle": "בחרו את שם הפרופיל", + "InputDialogAddNewProfileHeader": "אנא הזינו שם לפרופיל", + "InputDialogAddNewProfileSubtext": "(אורך מרבי: {0})", + "AvatarChoose": "בחרו דמות", + "AvatarSetBackgroundColor": "הגדר צבע רקע", + "AvatarClose": "סגור", + "ControllerSettingsLoadProfileToolTip": "טען פרופיל", + "ControllerSettingsAddProfileToolTip": "הוסף פרופיל", + "ControllerSettingsRemoveProfileToolTip": "הסר פרופיל", + "ControllerSettingsSaveProfileToolTip": "שמור פרופיל", + "MenuBarFileToolsTakeScreenshot": "צלם מסך", + "MenuBarFileToolsHideUi": "הסתר ממשק משתמש ", + "GameListContextMenuRunApplication": "הרץ יישום", + "GameListContextMenuToggleFavorite": "למתג העדפה", + "GameListContextMenuToggleFavoriteToolTip": "למתג סטטוס העדפה של משחק", + "SettingsTabGeneralTheme": "ערכת נושא:", + "SettingsTabGeneralThemeDark": "כהה", + "SettingsTabGeneralThemeLight": "בהיר", + "ControllerSettingsConfigureGeneral": "הגדר", + "ControllerSettingsRumble": "רטט", + "ControllerSettingsRumbleStrongMultiplier": "העצמת רטט חזק", + "ControllerSettingsRumbleWeakMultiplier": "מכפיל רטט חלש", + "DialogMessageSaveNotAvailableMessage": "אין שמור משחק עבור [{1:x16}] {0}", + "DialogMessageSaveNotAvailableCreateSaveMessage": "האם תרצה ליצור שמור משחק עבור המשחק הזה?", + "DialogConfirmationTitle": "ריוג'ינקס - אישור", + "DialogUpdaterTitle": "ריוג'ינקס - מעדכן", + "DialogErrorTitle": "ריוג'ינקס - שגיאה", + "DialogWarningTitle": "ריוג'ינקס - אזהרה", + "DialogExitTitle": "ריוג'ינקס - יציאה", + "DialogErrorMessage": "ריוג'ינקס נתקל בשגיאה", + "DialogExitMessage": "האם אתם בטוחים שאתם רוצים לסגור את ריוג'ינקס?", + "DialogExitSubMessage": "כל הנתונים שלא נשמרו יאבדו!", + "DialogMessageCreateSaveErrorMessage": "אירעה שגיאה ביצירת שמור המשחק שצויין: {0}", + "DialogMessageFindSaveErrorMessage": "אירעה שגיאה במציאת שמור המשחק שצויין: {0}", + "FolderDialogExtractTitle": "בחרו את התיקייה לחילוץ", + "DialogNcaExtractionMessage": "מלחץ {0} ממקטע {1}...", + "DialogNcaExtractionTitle": "ריוג'ינקס - מחלץ מקטע NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "כשל בחילוץ. ה-NCA הראשי לא היה קיים בקובץ שנבחר.", + "DialogNcaExtractionCheckLogErrorMessage": "כשל בחילוץ. קרא את קובץ הרישום למידע נוסף.", + "DialogNcaExtractionSuccessMessage": "החילוץ הושלם בהצלחה.", + "DialogUpdaterConvertFailedMessage": "המרת הגרסה הנוכחית של ריוג'ינקס נכשלה.", + "DialogUpdaterCancelUpdateMessage": "מבטל עדכון!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "אתם כבר משתמשים בגרסה המעודכנת ביותר של ריוג'ינקס!", + "DialogUpdaterFailedToGetVersionMessage": "אירעה שגיאה בעת ניסיון לקבל עדכונים מ-גיטהב. זה יכול להיגרם אם הגרסה המעודכנת האחרונה נוצרה על ידי פעולות של גיטהב. נסה שוב בעוד מספר דקות.", + "DialogUpdaterConvertFailedGithubMessage": "המרת גרסת ריוג'ינקס שהתקבלה מ-עדכון הגרסאות של גיטהב נכשלה.", + "DialogUpdaterDownloadingMessage": "מוריד עדכון...", + "DialogUpdaterExtractionMessage": "מחלץ עדכון...", + "DialogUpdaterRenamingMessage": "משנה את שם העדכון...", + "DialogUpdaterAddingFilesMessage": "מוסיף עדכון חדש...", + "DialogUpdaterCompleteMessage": "העדכון הושלם!", + "DialogUpdaterRestartMessage": "האם אתם רוצים להפעיל מחדש את ריוג'ינקס עכשיו?", + "DialogUpdaterNoInternetMessage": "אתם לא מחוברים לאינטרנט!", + "DialogUpdaterNoInternetSubMessage": "אנא ודא שיש לך חיבור אינטרנט תקין!", + "DialogUpdaterDirtyBuildMessage": "אתם לא יכולים לעדכן מבנה מלוכלך של ריוג'ינקס!", + "DialogUpdaterDirtyBuildSubMessage": "אם אתם מחפשים גרסא נתמכת, אנא הורידו את ריוג'ינקס בכתובת https://ryujinx.org", + "DialogRestartRequiredMessage": "אתחול נדרש", + "DialogThemeRestartMessage": "ערכת הנושא נשמרה. יש צורך בהפעלה מחדש כדי להחיל את ערכת הנושא.", + "DialogThemeRestartSubMessage": "האם ברצונך להפעיל מחדש?", + "DialogFirmwareInstallEmbeddedMessage": "האם תרצו להתקין את הקושחה המוטמעת במשחק הזה? (קושחה {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "לא נמצאה קושחה מותקנת אבל ריוג'ינקס הצליח להתקין קושחה {0} מהמשחק שסופק. \nהאמולטור יופעל כעת.", + "DialogFirmwareNoFirmwareInstalledMessage": "לא מותקנת קושחה", + "DialogFirmwareInstalledMessage": "הקושחה {0} הותקנה", + "DialogInstallFileTypesSuccessMessage": "סוגי קבצים הותקנו בהצלחה!", + "DialogInstallFileTypesErrorMessage": "נכשל בהתקנת סוגי קבצים.", + "DialogUninstallFileTypesSuccessMessage": "סוגי קבצים הוסרו בהצלחה!", + "DialogUninstallFileTypesErrorMessage": "נכשל בהסרת סוגי קבצים.", + "DialogOpenSettingsWindowLabel": "פתח את חלון ההגדרות", + "DialogControllerAppletTitle": "יישומון בקר", + "DialogMessageDialogErrorExceptionMessage": "שגיאה בהצגת דיאלוג ההודעה: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "שגיאה בהצגת תוכנת המקלדת: {0}", + "DialogErrorAppletErrorExceptionMessage": "שגיאה בהצגת דיאלוג ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nלמידע נוסף על איך לתקן שגיאה זו, עקוב אחר מדריך ההתקנה שלנו.", + "DialogUserErrorDialogTitle": "שגיאת Ryujinx ({0})", + "DialogAmiiboApiTitle": "ממשק תכנות אמיבו", + "DialogAmiiboApiFailFetchMessage": "אירעה שגיאה בעת שליפת מידע מהממשק.", + "DialogAmiiboApiConnectErrorMessage": "לא ניתן להתחבר לממשק שרת האמיבו. ייתכן שהשירות מושבת או שתצטרך לוודא שהחיבור לאינטרנט שלך מקוון.", + "DialogProfileInvalidProfileErrorMessage": "הפרופיל {0} אינו תואם למערכת תצורת הקלט הנוכחית.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "לא ניתן להחליף את פרופיל ברירת המחדל", + "DialogProfileDeleteProfileTitle": "מוחק פרופיל", + "DialogProfileDeleteProfileMessage": "פעולה זו היא בלתי הפיכה, האם אתם בטוחים שברצונכם להמשיך?", + "DialogWarning": "אזהרה", + "DialogPPTCDeletionMessage": "אם תמשיכו אתם עומדים לגרום לבנייה מחדש של מטמון ה-PPTC עבור:\n\n{0}", + "DialogPPTCDeletionErrorMessage": "שגיאה בטיהור מטמון PPTC ב-{0}: {1}", + "DialogShaderDeletionMessage": "אם תמשיכו אתם עומדים למחוק את מטמון ההצללות עבור:\n\n{0}", + "DialogShaderDeletionErrorMessage": "שגיאה בניקוי מטמון ההצללות ב-{0}: {1}", + "DialogRyujinxErrorMessage": "ריוג'ינקס נתקלה בשגיאה", + "DialogInvalidTitleIdErrorMessage": "שגיאת ממשק משתמש: למשחק שנבחר לא קיים מזהה משחק", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "לא נמצאה קושחת מערכת תקפה ב-{0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "התקן קושחה {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "גירסת המערכת {0} תותקן.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nזה יחליף את גרסת המערכת הנוכחית {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nהאם ברצונך להמשיך?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "מתקין קושחה...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "גרסת המערכת {0} הותקנה בהצלחה.", + "DialogUserProfileDeletionWarningMessage": "לא יהיו פרופילים אחרים שייפתחו אם הפרופיל שנבחר יימחק", + "DialogUserProfileDeletionConfirmMessage": "האם ברצונך למחוק את הפרופיל שנבחר", + "DialogUserProfileUnsavedChangesTitle": "אזהרה - שינויים לא שמורים", + "DialogUserProfileUnsavedChangesMessage": "ביצעת שינויים בפרופיל משתמש זה שלא נשמרו.", + "DialogUserProfileUnsavedChangesSubMessage": "האם ברצונך למחוק את השינויים האחרונים?", + "DialogControllerSettingsModifiedConfirmMessage": "הגדרות השלט הנוכחי עודכנו.", + "DialogControllerSettingsModifiedConfirmSubMessage": "האם ברצונך לשמור?", + "DialogLoadFileErrorMessage": "{0}. קובץ שגוי: {1}", + "DialogModAlreadyExistsMessage": "מוד כבר קיים", + "DialogModInvalidMessage": "התיקייה שצוינה אינה מכילה מוד", + "DialogModDeleteNoParentMessage": "נכשל למחוק: לא היה ניתן למצוא את תיקיית האב למוד \"{0}\"!\n", + "DialogDlcNoDlcErrorMessage": "הקובץ שצוין אינו מכיל DLC עבור המשחק שנבחר!", + "DialogPerformanceCheckLoggingEnabledMessage": "הפעלת רישום מעקב, אשר נועד לשמש מפתחים בלבד.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "לביצועים מיטביים, מומלץ להשבית את רישום המעקב. האם ברצונך להשבית את רישום המעקב כעת?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "הפעלת השלכת הצללה, שנועדה לשמש מפתחים בלבד.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "לביצועים מיטביים, מומלץ להשבית את השלכת הצללה. האם ברצונך להשבית את השלכת הצללה עכשיו?", + "DialogLoadAppGameAlreadyLoadedMessage": "ישנו משחק שכבר רץ כעת", + "DialogLoadAppGameAlreadyLoadedSubMessage": "אנא הפסק את האמולציה או סגור את האמולטור לפני הפעלת משחק אחר.", + "DialogUpdateAddUpdateErrorMessage": "הקובץ שצוין אינו מכיל עדכון עבור המשחק שנבחר!", + "DialogSettingsBackendThreadingWarningTitle": "אזהרה - ריבוי תהליכי רקע", + "DialogSettingsBackendThreadingWarningMessage": "יש להפעיל מחדש את ריוג'ינקס לאחר שינוי אפשרות זו כדי שהיא תחול במלואה. בהתאם לפלטפורמה שלך, ייתכן שיהיה עליך להשבית ידנית את ריבוי ההליכים של ההתקן שלך בעת השימוש ב-ריוג'ינקס.", + "DialogModManagerDeletionWarningMessage": "אתה עומד למחוק את המוד: {0}\nהאם אתה בטוח שאתה רוצה להמשיך?", + "DialogModManagerDeletionAllWarningMessage": "אתה עומד למחוק את כל המודים בשביל משחק זה.\n\nהאם אתה בטוח שאתה רוצה להמשיך?", + "SettingsTabGraphicsFeaturesOptions": "אפשרויות", + "SettingsTabGraphicsBackendMultithreading": "אחראי גרפיקה רב-תהליכי:", + "CommonAuto": "אוטומטי", + "CommonOff": "כבוי", + "CommonOn": "דלוק", + "InputDialogYes": "כן", + "InputDialogNo": "לא", + "DialogProfileInvalidProfileNameErrorMessage": "שם הקובץ מכיל תווים לא חוקיים. אנא נסה שוב.", + "MenuBarOptionsPauseEmulation": "הפסק", + "MenuBarOptionsResumeEmulation": "המשך", + "AboutUrlTooltipMessage": "לחץ כדי לפתוח את אתר ריוג'ינקס בדפדפן ברירת המחדל שלך.", + "AboutDisclaimerMessage": "ריוג'ינקס אינה מזוהת עם נינטנדו,\nאו שוטפייה בכל דרך שהיא.", + "AboutAmiiboDisclaimerMessage": "ממשק אמיבו (www.amiiboapi.com) משומש בהדמיית האמיבו שלנו.", + "AboutPatreonUrlTooltipMessage": "לחץ כדי לפתוח את דף הפטראון של ריוג'ינקס בדפדפן ברירת המחדל שלך.", + "AboutGithubUrlTooltipMessage": "לחץ כדי לפתוח את דף הגיטהב של ריוג'ינקס בדפדפן ברירת המחדל שלך.", + "AboutDiscordUrlTooltipMessage": "לחץ כדי לפתוח הזמנה לשרת הדיסקורד של ריוג'ינקס בדפדפן ברירת המחדל שלך.", + "AboutTwitterUrlTooltipMessage": "לחץ כדי לפתוח את דף הטוויטר של Ryujinx בדפדפן ברירת המחדל שלך.", + "AboutRyujinxAboutTitle": "אודות:", + "AboutRyujinxAboutContent": "ריוג'ינקס הוא אמולטור עבור הנינטנדו סוויץ' (כל הזכויות שמורות).\nבבקשה תתמכו בנו בפטראון.\nקבל את כל החדשות האחרונות בטוויטר או בדיסקורד שלנו.\nמפתחים המעוניינים לתרום יכולים לקבל מידע נוסף ב-גיטהאב או ב-דיסקורד שלנו.", + "AboutRyujinxMaintainersTitle": "מתוחזק על ידי:", + "AboutRyujinxMaintainersContentTooltipMessage": "לחץ כדי לפתוח את דף התורמים בדפדפן ברירת המחדל שלך.", + "AboutRyujinxSupprtersTitle": "תמוך באמצעות Patreon", + "AmiiboSeriesLabel": "סדרת אמיבו", + "AmiiboCharacterLabel": "דמות", + "AmiiboScanButtonLabel": "סרוק את זה", + "AmiiboOptionsShowAllLabel": "הצג את כל האמיבואים", + "AmiiboOptionsUsRandomTagLabel": "האצה: השתמש בתג Uuid אקראי", + "DlcManagerTableHeadingEnabledLabel": "מאופשר", + "DlcManagerTableHeadingTitleIdLabel": "מזהה משחק", + "DlcManagerTableHeadingContainerPathLabel": "נתיב מכיל", + "DlcManagerTableHeadingFullPathLabel": "נתיב מלא", + "DlcManagerRemoveAllButton": "מחק הכל", + "DlcManagerEnableAllButton": "אפשר הכל", + "DlcManagerDisableAllButton": "השבת הכל", + "ModManagerDeleteAllButton": "מחק הכל", + "MenuBarOptionsChangeLanguage": "החלף שפה", + "MenuBarShowFileTypes": "הצג מזהה סוג קובץ", + "CommonSort": "מיין", + "CommonShowNames": "הצג שמות", + "CommonFavorite": "מועדף", + "OrderAscending": "סדר עולה", + "OrderDescending": "סדר יורד", + "SettingsTabGraphicsFeatures": "תכונות ושיפורים", + "ErrorWindowTitle": "חלון שגיאה", + "ToggleDiscordTooltip": "בחרו להציג את ריוג'ינקס או לא בפעילות הדיסקורד שלכם \"משוחק כרגע\".", + "AddGameDirBoxTooltip": "הזן תקיית משחקים כדי להוסיף לרשימה", + "AddGameDirTooltip": "הוסף תקיית משחקים לרשימה", + "RemoveGameDirTooltip": "הסר את תקיית המשחקים שנבחרה", + "CustomThemeCheckTooltip": "השתמש בעיצוב מותאם אישית של אבלוניה עבור ה-ממשק הגראפי כדי לשנות את המראה של תפריטי האמולטור", + "CustomThemePathTooltip": "נתיב לערכת נושא לממשק גראפי מותאם אישית", + "CustomThemeBrowseTooltip": "חפש עיצוב ממשק גראפי מותאם אישית", + "DockModeToggleTooltip": "מצב עגינה גורם למערכת המדומה להתנהג כ-נינטנדו סוויץ' בתחנת עגינתו. זה משפר את הנאמנות הגרפית ברוב המשחקים.\n לעומת זאת, השבתה של תכונה זו תגרום למערכת המדומה להתנהג כ- נינטנדו סוויץ' נייד, ולהפחית את איכות הגרפיקה.\n\nהגדירו את שלט שחקן 1 אם אתם מתכננים להשתמש במצב עגינה; הגדירו את פקדי כף היד אם אתם מתכננים להשתמש במצב נייד.\n\nמוטב להשאיר דלוק אם אתם לא בטוחים.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "RegionTooltip": "שנה אזור מערכת", + "LanguageTooltip": "שנה שפת מערכת", + "TimezoneTooltip": "שנה את אזור הזמן של המערכת", + "TimeTooltip": "שנה זמן מערכת", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "PptcToggleTooltip": "שומר את פונקציות ה-JIT המתורגמות כך שלא יצטרכו לעבור תרגום שוב כאשר משחק עולה.\n\nמפחית תקיעות ומשפר מהירות עלייה של המערכת אחרי הפתיחה הראשונה של המשחק.\n\nמוטב להשאיר דלוק אם לא בטוחים.", + "FsIntegrityToggleTooltip": "בודק לקבצים שגויים כאשר משחק עולה, ואם מתגלים כאלו, מציג את מזהה השגיאה שלהם לקובץ הלוג.\n\nאין לכך השפעה על הביצועים ונועד לעזור לבדיקה וניפוי שגיאות של האמולטור.\n\nמוטב להשאיר דלוק אם לא בטוחים.", + "AudioBackendTooltip": "משנה את אחראי השמע.\n\nSDL2 הוא הנבחר, למראת שOpenAL וגם SoundIO משומשים כאפשרויות חלופיות. אפשרות הDummy לא תשמיע קול כלל.\n\nמוטב להשאיר על SDL2 אם לא בטוחים.", + "MemoryManagerTooltip": "שנה איך שזיכרון מארח מיוחד ומונגד. משפיע מאוד על ביצועי המעבד המדומה.\n\nמוטב להשאיר על מארח לא מבוקר אם לא בטוחים.", + "MemoryManagerSoftwareTooltip": "השתמש בתוכנת ה-page table בכדי להתייחס לתרגומים. דיוק מרבי לקונסולה אך המימוש הכי איטי.", + "MemoryManagerHostTooltip": "ממפה זיכרון ישירות לכתובת המארח. מהיר בהרבה ביכולות קימפול ה-JIT והריצה.", + "MemoryManagerUnsafeTooltip": "ממפה זיכרון ישירות, אך לא ממסך את הכתובת בתוך כתובת המארח לפני הגישה. מהיר, אך במחיר של הגנה. יישום המארח בעל גישה לזיכרון מכל מקום בריוג'ינקס, לכן הריצו איתו רק קבצים שאתם סומכים עליהם.", + "UseHypervisorTooltip": "השתמש ב- Hypervisor במקום JIT. משפר מאוד ביצועים כשניתן, אבל יכול להיות לא יציב במצבו הנוכחי.", + "DRamTooltip": "מנצל תצורת מצב-זיכרון חלופית לחכות את מכשיר הפיתוח של הסוויץ'.\n\nזה שימושי להחלפת חבילות מרקמים באיכותיים יותר או כאלו ברזולוציית 4k. לא משפר ביצועים.\n\nמוטב להשאיר כבוי אם לא בטוחים.", + "IgnoreMissingServicesTooltip": "מתעלם מפעולות שלא קיבלו מימוש במערכת ההפעלה Horizon OS. זה עלול לעזור לעקוף קריסות של היישום במשחקים מסויימים.\n\nמוטב להשאיר כבוי אם לא בטוחים.", + "GraphicsBackendThreadingTooltip": "מריץ פקודות גראפיקה בתהליך שני נפרד.\n\nמאיץ עיבוד הצללות, מפחית תקיעות ומשפר ביצועים של דרייבר כרטיסי מסך אשר לא תומכים בהרצה רב-תהליכית.\n\nמוטב להשאיר על אוטומטי אם לא בטוחים.", + "GalThreadingTooltip": "מריץ פקודות גראפיקה בתהליך שני נפרד.\n\nמאיץ עיבוד הצללות, מפחית תקיעות ומשפר ביצועים של דרייבר כרטיסי מסך אשר לא תומכים בהרצה רב-תהליכית.\n\nמוטב להשאיר על אוטומטי אם לא בטוחים.", + "ShaderCacheToggleTooltip": "שומר זכרון מטמון של הצללות, דבר שמפחית תקיעות בריצות מסוימות.\n\nמוטב להשאיר דלוק אם לא בטוחים.", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleEntryTooltip": "שיפור רזולוציית נקודה צפה, כגון 1.5. הוא שיפור לא אינטגרלי הנוטה לגרום יותר בעיות או להקריס.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "ShaderDumpPathTooltip": "נתיב השלכת הצללות גראפיות", + "FileLogTooltip": "שומר את רישומי שורת הפקודות לזיכרון, לא משפיע על ביצועי היישום.", + "StubLogTooltip": "מדפיס רישומים כושלים לשורת הפקודות. לא משפיע על ביצועי היישום.", + "InfoLogTooltip": "מדפיק רישומי מידע לשורת הפקודות. לא משפיע על ביצועי היישום.", + "WarnLogTooltip": "מדפיק רישומי הערות לשורת הפקודות. לא משפיע על ביצועי היישום.", + "ErrorLogTooltip": "מדפיס רישומי שגיאות לשורת הפקודות. לא משפיע על ביצועי היישום.", + "TraceLogTooltip": "מדפיק רישומי זיכרון לשורת הפקודות. לא משפיע על ביצועי היישום.", + "GuestLogTooltip": "מדפיס רישומי אורח לשורת הפקודות. לא משפיע על ביצועי היישום.", + "FileAccessLogTooltip": "מדפיס גישות לקבצי רישום לשורת הפקודות.", + "FSAccessLogModeTooltip": "מאפשר גישה לרישומי FS ליציאת שורת הפקודות. האפשרויות הינן 0-3.", + "DeveloperOptionTooltip": "השתמש בזהירות", + "OpenGlLogLevel": "דורש הפעלת רמות רישום מתאימות", + "DebugLogTooltip": "מדפיס הודעות יומן ניפוי באגים בשורת הפקודות.", + "LoadApplicationFileTooltip": "פתח סייר קבצים כדי לבחור קובץ תואם סוויץ' לטעינה", + "LoadApplicationFolderTooltip": "פתח סייר קבצים כדי לבחור יישום תואם סוויץ', לא ארוז לטעינה.", + "OpenRyujinxFolderTooltip": "פתח את תיקיית מערכת הקבצים ריוג'ינקס", + "OpenRyujinxLogsTooltip": "פותח את התיקיה שאליה נכתבים רישומים", + "ExitTooltip": "צא מריוג'ינקס", + "OpenSettingsTooltip": "פתח את חלון ההגדרות", + "OpenProfileManagerTooltip": "פתח את חלון מנהל פרופילי המשתמש", + "StopEmulationTooltip": "הפסק את הדמייה של המשחק הנוכחי וחזור למסך בחירת המשחק", + "CheckUpdatesTooltip": "בדוק אם קיימים עדכונים לריוג'ינקס", + "OpenAboutTooltip": "פתח את חלון אודות היישום", + "GridSize": "גודל רשת", + "GridSizeTooltip": "שנה את גודל המוצרים על הרשת.", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "פורטוגלית ברזילאית", + "AboutRyujinxContributorsButtonHeader": "צפה בכל התורמים", + "SettingsTabSystemAudioVolume": "עוצמת קול: ", + "AudioVolumeTooltip": "שנה עוצמת קול", + "SettingsTabSystemEnableInternetAccess": "אפשר גישה לאינטרנט בתור אורח/חיבור לאן", + "EnableInternetAccessTooltip": "מאפשר ליישומים באמולצייה להתחבר לאינטרנט.\n\nמשחקים עם חיבור לאן יכולים להתחבר אחד לשני כשאופצייה זו מופעלת והמערכות מתחברות לאותה נקודת גישה. כמו כן זה כולל שורות פקודות אמיתיות גם.\n\nאופצייה זו לא מאפשרת חיבור לשרתי נינטנדו. כשהאופצייה דלוקה היא עלולה לגרום לקריסת היישום במשחקים מסויימים שמנסים להתחבר לאינטרנט.\n\nמוטב להשאיר כבוי אם לא בטוחים.", + "GameListContextMenuManageCheatToolTip": "נהל צ'יטים", + "GameListContextMenuManageCheat": "נהל צ'יטים", + "GameListContextMenuManageModToolTip": "נהל מודים", + "GameListContextMenuManageMod": "נהל מודים", + "ControllerSettingsStickRange": "טווח:", + "DialogStopEmulationTitle": "ריוג'ינקס - עצור אמולציה", + "DialogStopEmulationMessage": "האם אתם בטוחים שאתם רוצים לסגור את האמולצייה?", + "SettingsTabCpu": "מעבד", + "SettingsTabAudio": "שמע", + "SettingsTabNetwork": "רשת", + "SettingsTabNetworkConnection": "חיבור רשת", + "SettingsTabCpuCache": "מטמון מעבד", + "SettingsTabCpuMemory": "מצב מעבד", + "DialogUpdaterFlatpakNotSupportedMessage": "בבקשה עדכן את ריוג'ינקס דרך פלאטהב.", + "UpdaterDisabledWarningTitle": "מעדכן מושבת!", + "ControllerSettingsRotate90": "סובב 90° עם בכיוון השעון", + "IconSize": "גודל הסמל", + "IconSizeTooltip": "שנה את גודל הסמלים של משחקים", + "MenuBarOptionsShowConsole": "הצג שורת פקודות", + "ShaderCachePurgeError": "שגיאה בניקוי מטמון ההצללות ב-{0}: {1}", + "UserErrorNoKeys": "המפתחות לא נמצאו", + "UserErrorNoFirmware": "קושחה לא נמצאה", + "UserErrorFirmwareParsingFailed": "שגיאת ניתוח קושחה", + "UserErrorApplicationNotFound": "יישום לא נמצא", + "UserErrorUnknown": "שגיאה לא ידועה", + "UserErrorUndefined": "שגיאה לא מוגדרת", + "UserErrorNoKeysDescription": "ריוג'ינקס לא הצליח למצוא את קובץ ה-'prod.keys' שלך", + "UserErrorNoFirmwareDescription": "ריוג'ינקס לא הצליחה למצוא קושחה מותקנת", + "UserErrorFirmwareParsingFailedDescription": "ריוג'ינקס לא הצליחה לנתח את הקושחה שסופקה. זה נגרם בדרך כלל על ידי מפתחות לא עדכניים.", + "UserErrorApplicationNotFoundDescription": "ריוג'ינקס לא מצאה יישום תקין בנתיב הנתון", + "UserErrorUnknownDescription": "קרתה שגיאה לא ידועה!", + "UserErrorUndefinedDescription": "קרתה שגיאה לא מוגדרת! זה לא אמור לקרות, אנא צרו קשר עם מפתח!", + "OpenSetupGuideMessage": "פתח מדריך התקנה", + "NoUpdate": "אין עדכון", + "TitleUpdateVersionLabel": "גרסה {0}", + "RyujinxInfo": "ריוג'ינקס - מידע", + "RyujinxConfirm": "ריוג'ינקס - אישור", + "FileDialogAllTypes": "כל הסוגים", + "Never": "אף פעם", + "SwkbdMinCharacters": "לפחות {0} תווים", + "SwkbdMinRangeCharacters": "באורך {0}-{1} תווים", + "SoftwareKeyboard": "מקלדת וירטואלית", + "SoftwareKeyboardModeNumeric": "חייב להיות בין 0-9 או '.' בלבד", + "SoftwareKeyboardModeAlphabet": "מחויב להיות ללא אותיות CJK", + "SoftwareKeyboardModeASCII": "מחויב להיות טקסט אסקיי", + "ControllerAppletControllers": "בקרים נתמכים:", + "ControllerAppletPlayers": "שחקנים:", + "ControllerAppletDescription": "התצורה הנוכחית אינה תקינה. פתח הגדרות והגדר מחדש את הקלטים שלך.", + "ControllerAppletDocked": "מצב עגינה מוגדר. כדאי ששליטה ניידת תהיה מושבתת.", + "UpdaterRenaming": "משנה שמות של קבצים ישנים...", + "UpdaterRenameFailed": "המעדכן לא הצליח לשנות את שם הקובץ: {0}", + "UpdaterAddingFiles": "מוסיף קבצים חדשים...", + "UpdaterExtracting": "מחלץ עדכון...", + "UpdaterDownloading": "מוריד עדכון...", + "Game": "משחק", + "Docked": "בתחנת עגינה", + "Handheld": "נייד", + "ConnectionError": "שגיאת חיבור", + "AboutPageDeveloperListMore": "{0} ועוד...", + "ApiError": "שגיאת ממשק.", + "LoadingHeading": "טוען {0}", + "CompilingPPTC": "קימפול PTC", + "CompilingShaders": "קימפול הצללות", + "AllKeyboards": "כל המקלדות", + "OpenFileDialogTitle": "בחר קובץ נתמך לפתיחה", + "OpenFolderDialogTitle": "בחר תיקיה עם משחק לא ארוז", + "AllSupportedFormats": "כל הפורמטים הנתמכים", + "RyujinxUpdater": "מעדכן ריוג'ינקס", + "SettingsTabHotkeys": "מקשי קיצור במקלדת", + "SettingsTabHotkeysHotkeys": "מקשי קיצור במקלדת", + "SettingsTabHotkeysToggleVsyncHotkey": "שנה סינכרון אנכי:", + "SettingsTabHotkeysScreenshotHotkey": "צילום מסך:", + "SettingsTabHotkeysShowUiHotkey": "הצג ממשק משתמש:", + "SettingsTabHotkeysPauseHotkey": "הפסק:", + "SettingsTabHotkeysToggleMuteHotkey": "השתק:", + "ControllerMotionTitle": "הגדרות שליטת תנועות ג'ירוסקופ", + "ControllerRumbleTitle": "הגדרות רטט", + "SettingsSelectThemeFileDialogTitle": "בחרו קובץ ערכת נושא", + "SettingsXamlThemeFile": "קובץ ערכת נושא Xaml", + "AvatarWindowTitle": "ניהול חשבונות - אוואטר", + "Amiibo": "אמיבו", + "Unknown": "לא ידוע", + "Usage": "שימוש", + "Writable": "ניתן לכתיבה", + "SelectDlcDialogTitle": "בחרו קבצי הרחבות משחק", + "SelectUpdateDialogTitle": "בחרו קבצי עדכון", + "SelectModDialogTitle": "בחר תיקיית מודים", + "UserProfileWindowTitle": "ניהול פרופילי משתמש", + "CheatWindowTitle": "נהל צ'יטים למשחק", + "DlcWindowTitle": "נהל הרחבות משחק עבור {0} ({1})", + "ModWindowTitle": "Manage Mods for {0} ({1})", + "UpdateWindowTitle": "נהל עדכוני משחקים", + "CheatWindowHeading": "צ'יטים זמינים עבור {0} [{1}]", + "BuildId": "מזהה בניה:", + "DlcWindowHeading": "{0} הרחבות משחק", + "ModWindowHeading": "{0} מוד(ים)", + "UserProfilesEditProfile": "ערוך נבחר/ים", + "Cancel": "בטל", + "Save": "שמור", + "Discard": "השלך", + "Paused": "מושהה", + "UserProfilesSetProfileImage": "הגדר תמונת פרופיל", + "UserProfileEmptyNameError": "נדרש שם", + "UserProfileNoImageError": "נדרשת תמונת פרופיל", + "GameUpdateWindowHeading": "נהל עדכונים עבור {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "שפר רזולוציה:", + "SettingsTabHotkeysResScaleDownHotkey": "הפחת רזולוציה:", + "UserProfilesName": "שם:", + "UserProfilesUserId": "מזהה משתמש:", + "SettingsTabGraphicsBackend": "אחראי גראפיקה", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsEnableTextureRecompression": "אפשר דחיסה מחדש של המרקם", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsTabGraphicsPreferredGpu": "כרטיס גראפי מועדף", + "SettingsTabGraphicsPreferredGpuTooltip": "בחר את הכרטיס הגראפי שישומש עם הגראפיקה של וולקאן.\n\nדבר זה לא משפיע על הכרטיס הגראפי שישומש עם OpenGL.\n\nמוטב לבחור את ה-GPU המסומן כ-\"dGPU\" אם אינכם בטוחים, אם זו לא אופצייה, אל תשנו דבר.", + "SettingsAppRequiredRestartMessage": "ריוג'ינקס דורש אתחול מחדש", + "SettingsGpuBackendRestartMessage": "הגדרות אחראי גרפיקה או כרטיס גראפי שונו. זה ידרוש הפעלה מחדש כדי להחיל שינויים", + "SettingsGpuBackendRestartSubMessage": "האם ברצונך להפעיל מחדש כעט?", + "RyujinxUpdaterMessage": "האם ברצונך לעדכן את ריוג'ינקס לגרסא האחרונה?", + "SettingsTabHotkeysVolumeUpHotkey": "הגבר את עוצמת הקול:", + "SettingsTabHotkeysVolumeDownHotkey": "הנמך את עוצמת הקול:", + "SettingsEnableMacroHLE": "Enable Macro HLE", + "SettingsEnableMacroHLETooltip": "אמולצייה ברמה גבוהה של כרטיס גראפי עם קוד מקרו.\n\nמשפר את ביצועי היישום אך עלול לגרום לגליצ'ים חזותיים במשחקים מסויימים.\n\nמוטב להשאיר דלוק אם אינך בטוח.", + "SettingsEnableColorSpacePassthrough": "שקיפות מרחב צבע", + "SettingsEnableColorSpacePassthroughTooltip": "מנחה את המנוע Vulkan להעביר שקיפות בצבעים מבלי לציין מרחב צבע. עבור משתמשים עם מסכים רחבים, הדבר עשוי לגרום לצבעים מרהיבים יותר, בחוסר דיוק בצבעים האמתיים.", + "VolumeShort": "שמע", + "UserProfilesManageSaves": "נהל שמורים", + "DeleteUserSave": "האם ברצונך למחוק את תקיית השמור למשחק זה?", + "IrreversibleActionNote": "הפעולה הזו בלתי הפיכה.", + "SaveManagerHeading": "נהל שמורי משחק עבור {0} ({1})", + "SaveManagerTitle": "מנהל שמירות", + "Name": "שם", + "Size": "גודל", + "Search": "חפש", + "UserProfilesRecoverLostAccounts": "שחזר חשבון שאבד", + "Recover": "שחזר", + "UserProfilesRecoverHeading": "שמורים נמצאו לחשבונות הבאים", + "UserProfilesRecoverEmptyList": "אין פרופילים לשחזור", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAALabel": "החלקת-עקומות:", + "GraphicsScalingFilterLabel": "מסנן מידת איכות:", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "רמה", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "SmaaLow": "SMAA נמוך", + "SmaaMedium": "SMAA בינוני", + "SmaaHigh": "SMAA גבוהה", + "SmaaUltra": "SMAA אולטרה", + "UserEditorTitle": "ערוך משתמש", + "UserEditorTitleCreate": "צור משתמש", + "SettingsTabNetworkInterface": "ממשק רשת", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceDefault": "ברירת המחדל", + "PackagingShaders": "אורז הצללות", + "AboutChangelogButton": "צפה במידע אודות שינויים בגיטהב", + "AboutChangelogButtonTooltipMessage": "לחץ כדי לפתוח את יומן השינויים עבור גרסה זו בדפדפן ברירת המחדל שלך.", + "SettingsTabNetworkMultiplayer": "רב משתתפים", + "MultiplayerMode": "מצב:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json new file mode 100644 index 00000000..280ebd88 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -0,0 +1,780 @@ +{ + "Language": "Italiano", + "MenuBarFileOpenApplet": "Apri applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Apri l'applet Mii Editor in modalità Standalone", + "SettingsTabInputDirectMouseAccess": "Accesso diretto al mouse", + "SettingsTabSystemMemoryManagerMode": "Modalità di gestione della memoria:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (veloce)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (più veloce, non sicura)", + "SettingsTabSystemUseHypervisor": "Usa Hypervisor", + "MenuBarFile": "_File", + "MenuBarFileOpenFromFile": "_Carica applicazione da un file", + "MenuBarFileOpenUnpacked": "Carica _gioco estratto", + "MenuBarFileOpenEmuFolder": "Apri cartella di Ryujinx", + "MenuBarFileOpenLogsFolder": "Apri cartella dei log", + "MenuBarFileExit": "_Esci", + "MenuBarOptions": "_Opzioni", + "MenuBarOptionsToggleFullscreen": "Schermo intero", + "MenuBarOptionsStartGamesInFullscreen": "Avvia i giochi a schermo intero", + "MenuBarOptionsStopEmulation": "Ferma emulazione", + "MenuBarOptionsSettings": "_Impostazioni", + "MenuBarOptionsManageUserProfiles": "_Gestisci i profili utente", + "MenuBarActions": "_Azioni", + "MenuBarOptionsSimulateWakeUpMessage": "Simula messaggio Wake-up", + "MenuBarActionsScanAmiibo": "Scansiona un Amiibo", + "MenuBarTools": "_Strumenti", + "MenuBarToolsInstallFirmware": "Installa firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Installa un firmware da file XCI o ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Installa un firmare da una cartella", + "MenuBarToolsManageFileTypes": "Gestisci i tipi di file", + "MenuBarToolsInstallFileTypes": "Installa i tipi di file", + "MenuBarToolsUninstallFileTypes": "Disinstalla i tipi di file", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "_Aiuto", + "MenuBarHelpCheckForUpdates": "Controlla aggiornamenti", + "MenuBarHelpAbout": "Informazioni", + "MenuSearch": "Cerca...", + "GameListHeaderFavorite": "Preferito", + "GameListHeaderIcon": "Icona", + "GameListHeaderApplication": "Nome", + "GameListHeaderDeveloper": "Sviluppatore", + "GameListHeaderVersion": "Versione", + "GameListHeaderTimePlayed": "Tempo di gioco", + "GameListHeaderLastPlayed": "Ultima partita", + "GameListHeaderFileExtension": "Estensione", + "GameListHeaderFileSize": "Dimensione file", + "GameListHeaderPath": "Percorso", + "GameListContextMenuOpenUserSaveDirectory": "Apri la cartella dei salvataggi dell'utente", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Apre la cartella che contiene i dati di salvataggio dell'utente", + "GameListContextMenuOpenDeviceSaveDirectory": "Apri la cartella dei salvataggi del dispositivo", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Apre la cartella che contiene i dati di salvataggio del dispositivo", + "GameListContextMenuOpenBcatSaveDirectory": "Apri la cartella del salvataggio BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Apre la cartella che contiene il salvataggio BCAT dell'applicazione", + "GameListContextMenuManageTitleUpdates": "Gestisci aggiornamenti del gioco", + "GameListContextMenuManageTitleUpdatesToolTip": "Apre la finestra di gestione aggiornamenti del gioco", + "GameListContextMenuManageDlc": "Gestisci DLC", + "GameListContextMenuManageDlcToolTip": "Apre la finestra di gestione dei DLC", + "GameListContextMenuCacheManagement": "Gestione della cache", + "GameListContextMenuCacheManagementPurgePptc": "Accoda rigenerazione della cache PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Esegue la rigenerazione della cache PPTC al prossimo avvio del gioco", + "GameListContextMenuCacheManagementPurgeShaderCache": "Elimina la cache degli shader", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Elimina la cache degli shader dell'applicazione", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Apri la cartella della cache PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Apre la cartella che contiene la cache PPTC dell'applicazione", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Apri la cartella della cache degli shader", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Apre la cartella che contiene la cache degli shader dell'applicazione", + "GameListContextMenuExtractData": "Estrai dati", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Estrae la sezione ExeFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Estrae la sezione RomFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Estrae la sezione Logo dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "GameListContextMenuCreateShortcut": "Crea collegamento", + "GameListContextMenuCreateShortcutToolTip": "Crea un collegamento sul desktop che avvia l'applicazione selezionata", + "GameListContextMenuCreateShortcutToolTipMacOS": "Crea un collegamento nella cartella Applicazioni di macOS che avvia l'applicazione selezionata", + "GameListContextMenuOpenModsDirectory": "Apri la cartella delle mod", + "GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella che contiene le mod dell'applicazione", + "GameListContextMenuOpenSdModsDirectory": "Apri la cartella delle mod Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella alternativa di Atmosphere sulla scheda SD che contiene le mod dell'applicazione. Utile per le mod create per funzionare sull'hardware reale.", + "StatusBarGamesLoaded": "{0}/{1} giochi caricati", + "StatusBarSystemVersion": "Versione di sistema: {0}", + "LinuxVmMaxMapCountDialogTitle": "Rilevato limite basso per le mappature di memoria", + "LinuxVmMaxMapCountDialogTextPrimary": "Vuoi aumentare il valore di vm.max_map_count a {0}?", + "LinuxVmMaxMapCountDialogTextSecondary": "Alcuni giochi potrebbero provare a creare più mappature di memoria di quanto sia attualmente consentito. Ryujinx si bloccherà non appena questo limite viene superato.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Sì, fino al prossimo riavvio", + "LinuxVmMaxMapCountDialogButtonPersistent": "Sì, in modo permanente", + "LinuxVmMaxMapCountWarningTextPrimary": "La quantità massima di mappature di memoria è inferiore a quella consigliata.", + "LinuxVmMaxMapCountWarningTextSecondary": "Il valore corrente di vm.max_map_count ({0}) è inferiore a {1}. Alcuni giochi potrebbero provare a creare più mappature di memoria di quanto sia attualmente consentito. Ryujinx si bloccherà non appena questo limite viene superato.\n\nPotresti voler aumentare manualmente il limite o installare pkexec, il che permette a Ryujinx di assisterlo.", + "Settings": "Impostazioni", + "SettingsTabGeneral": "Interfaccia utente", + "SettingsTabGeneralGeneral": "Generali", + "SettingsTabGeneralEnableDiscordRichPresence": "Attiva Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Controlla aggiornamenti all'avvio", + "SettingsTabGeneralShowConfirmExitDialog": "Mostra dialogo \"Conferma Uscita\"", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", + "SettingsTabGeneralHideCursor": "Nascondi il cursore:", + "SettingsTabGeneralHideCursorNever": "Mai", + "SettingsTabGeneralHideCursorOnIdle": "Quando è inattivo", + "SettingsTabGeneralHideCursorAlways": "Sempre", + "SettingsTabGeneralGameDirectories": "Cartelle dei giochi", + "SettingsTabGeneralAdd": "Aggiungi", + "SettingsTabGeneralRemove": "Rimuovi", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Principale", + "SettingsTabSystemSystemRegion": "Regione del sistema:", + "SettingsTabSystemSystemRegionJapan": "Giappone", + "SettingsTabSystemSystemRegionUSA": "Stati Uniti d'America", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "Cina", + "SettingsTabSystemSystemRegionKorea": "Corea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "Lingua del sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Giapponese", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglese americano", + "SettingsTabSystemSystemLanguageFrench": "Francese", + "SettingsTabSystemSystemLanguageGerman": "Tedesco", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Spagnolo", + "SettingsTabSystemSystemLanguageChinese": "Cinese", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Olandese", + "SettingsTabSystemSystemLanguagePortuguese": "Portoghese", + "SettingsTabSystemSystemLanguageRussian": "Russo", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanese", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglese britannico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francese canadese", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Spagnolo latino americano", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Cinese semplificato", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Cinese tradizionale", + "SettingsTabSystemSystemTimeZone": "Fuso orario del sistema:", + "SettingsTabSystemSystemTime": "Data e ora del sistema:", + "SettingsTabSystemEnableVsync": "Attiva VSync", + "SettingsTabSystemEnablePptc": "Attiva PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Attiva controlli d'integrità FS", + "SettingsTabSystemAudioBackend": "Backend audio:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Espedienti", + "SettingsTabSystemHacksNote": "Possono causare instabilità", + "SettingsTabSystemExpandDramSize": "Usa layout di memoria alternativo (per sviluppatori)", + "SettingsTabSystemIgnoreMissingServices": "Ignora servizi mancanti", + "SettingsTabGraphics": "Grafica", + "SettingsTabGraphicsAPI": "API grafica", + "SettingsTabGraphicsEnableShaderCache": "Attiva la cache degli shader", + "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotropico:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Scala della risoluzione:", + "SettingsTabGraphicsResolutionScaleCustom": "Personalizzata (Non raccomandata)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Non consigliato)", + "SettingsTabGraphicsAspectRatio": "Rapporto d'aspetto:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Adatta alla finestra", + "SettingsTabGraphicsDeveloperOptions": "Opzioni per sviluppatori", + "SettingsTabGraphicsShaderDumpPath": "Percorso di dump degli shader:", + "SettingsTabLogging": "Log", + "SettingsTabLoggingLogging": "Log", + "SettingsTabLoggingEnableLoggingToFile": "Salva i log su file", + "SettingsTabLoggingEnableStubLogs": "Attiva log di stub", + "SettingsTabLoggingEnableInfoLogs": "Attiva log di informazioni", + "SettingsTabLoggingEnableWarningLogs": "Attiva log di avviso", + "SettingsTabLoggingEnableErrorLogs": "Attiva log di errore", + "SettingsTabLoggingEnableTraceLogs": "Attiva log di trace", + "SettingsTabLoggingEnableGuestLogs": "Attiva log del guest", + "SettingsTabLoggingEnableFsAccessLogs": "Attiva log di accesso FS", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modalità log di accesso globale FS:", + "SettingsTabLoggingDeveloperOptions": "Opzioni per sviluppatori", + "SettingsTabLoggingDeveloperOptionsNote": "ATTENZIONE: ridurrà le prestazioni", + "SettingsTabLoggingGraphicsBackendLogLevel": "Livello di log del backend grafico:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nessuno", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Errore", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Rallentamenti", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Tutto", + "SettingsTabLoggingEnableDebugLogs": "Attiva log di debug", + "SettingsTabInput": "Input", + "SettingsTabInputEnableDockedMode": "Attiva modalità TV", + "SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera", + "SettingsButtonSave": "Salva", + "SettingsButtonClose": "Chiudi", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Annulla", + "SettingsButtonApply": "Applica", + "ControllerSettingsPlayer": "Giocatore", + "ControllerSettingsPlayer1": "Giocatore 1", + "ControllerSettingsPlayer2": "Giocatore 2", + "ControllerSettingsPlayer3": "Giocatore 3", + "ControllerSettingsPlayer4": "Giocatore 4", + "ControllerSettingsPlayer5": "Giocatore 5", + "ControllerSettingsPlayer6": "Giocatore 6", + "ControllerSettingsPlayer7": "Giocatore 7", + "ControllerSettingsPlayer8": "Giocatore 8", + "ControllerSettingsHandheld": "Portatile", + "ControllerSettingsInputDevice": "Dispositivo di input", + "ControllerSettingsRefresh": "Ricarica", + "ControllerSettingsDeviceDisabled": "Disabilitato", + "ControllerSettingsControllerType": "Tipo di controller", + "ControllerSettingsControllerTypeHandheld": "Portatile", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Coppia di JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon sinistro", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon destro", + "ControllerSettingsProfile": "Profilo", + "ControllerSettingsProfileDefault": "Predefinito", + "ControllerSettingsLoad": "Carica", + "ControllerSettingsAdd": "Aggiungi", + "ControllerSettingsRemove": "Rimuovi", + "ControllerSettingsButtons": "Pulsanti", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Croce direzionale", + "ControllerSettingsDPadUp": "Su", + "ControllerSettingsDPadDown": "Giù", + "ControllerSettingsDPadLeft": "Sinistra", + "ControllerSettingsDPadRight": "Destra", + "ControllerSettingsStickButton": "Pulsante", + "ControllerSettingsStickUp": "Su", + "ControllerSettingsStickDown": "Giù", + "ControllerSettingsStickLeft": "Sinistra", + "ControllerSettingsStickRight": "Destra", + "ControllerSettingsStickStick": "Levetta", + "ControllerSettingsStickInvertXAxis": "Inverti levetta X", + "ControllerSettingsStickInvertYAxis": "Inverti levetta Y", + "ControllerSettingsStickDeadzone": "Zona morta:", + "ControllerSettingsLStick": "Levetta sinistra", + "ControllerSettingsRStick": "Levetta destra", + "ControllerSettingsTriggersLeft": "Grilletto sinistro", + "ControllerSettingsTriggersRight": "Grilletto destro", + "ControllerSettingsTriggersButtonsLeft": "Pulsante dorsale sinistro", + "ControllerSettingsTriggersButtonsRight": "Pulsante dorsale destro", + "ControllerSettingsTriggers": "Grilletti", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Tasto sinistro", + "ControllerSettingsExtraButtonsRight": "Tasto destro", + "ControllerSettingsMisc": "Varie", + "ControllerSettingsTriggerThreshold": "Sensibilità dei grilletti:", + "ControllerSettingsMotion": "Movimento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usa sensore compatibile con CemuHook", + "ControllerSettingsMotionControllerSlot": "Slot del controller:", + "ControllerSettingsMotionMirrorInput": "Input specchiato", + "ControllerSettingsMotionRightJoyConSlot": "Slot JoyCon destro:", + "ControllerSettingsMotionServerHost": "Server:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilità del giroscopio:", + "ControllerSettingsMotionGyroDeadzone": "Zona morta del giroscopio:", + "ControllerSettingsSave": "Salva", + "ControllerSettingsClose": "Chiudi", + "KeyUnknown": "Sconosciuto", + "KeyShiftLeft": "Maiusc sinistro", + "KeyShiftRight": "Maiusc destro", + "KeyControlLeft": "Ctrl sinistro", + "KeyMacControlLeft": "⌃ sinistro", + "KeyControlRight": "Ctrl destro", + "KeyMacControlRight": "⌃ destro", + "KeyAltLeft": "Alt sinistro", + "KeyMacAltLeft": "⌥ sinistro", + "KeyAltRight": "Alt destro", + "KeyMacAltRight": "⌥ destro", + "KeyWinLeft": "⊞ sinistro", + "KeyMacWinLeft": "⌘ sinistro", + "KeyWinRight": "⊞ destro", + "KeyMacWinRight": "⌘ destro", + "KeyMenu": "Menù", + "KeyUp": "Su", + "KeyDown": "Giù", + "KeyLeft": "Sinistra", + "KeyRight": "Destra", + "KeyEnter": "Invio", + "KeyEscape": "Esc", + "KeySpace": "Spazio", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Ins", + "KeyDelete": "Canc", + "KeyPageUp": "Pag. Su", + "KeyPageDown": "Pag. Giù", + "KeyHome": "Inizio", + "KeyEnd": "Fine", + "KeyCapsLock": "Bloc Maiusc", + "KeyScrollLock": "Bloc Scorr", + "KeyPrintScreen": "Stamp", + "KeyPause": "Pausa", + "KeyNumLock": "Bloc Num", + "KeyClear": "Clear", + "KeyKeypad0": "Tast. num. 0", + "KeyKeypad1": "Tast. num. 1", + "KeyKeypad2": "Tast. num. 2", + "KeyKeypad3": "Tast. num. 3", + "KeyKeypad4": "Tast. num. 4", + "KeyKeypad5": "Tast. num. 5", + "KeyKeypad6": "Tast. num. 6", + "KeyKeypad7": "Tast. num. 7", + "KeyKeypad8": "Tast. num. 8", + "KeyKeypad9": "Tast. num. 9", + "KeyKeypadDivide": "Tast. num. /", + "KeyKeypadMultiply": "Tast. num. *", + "KeyKeypadSubtract": "Tast. num. -", + "KeyKeypadAdd": "Tast. num. +", + "KeyKeypadDecimal": "Tast. num. sep. decimale", + "KeyKeypadEnter": "Tast. num. Invio", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "ò", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "'", + "KeyBracketRight": "ì", + "KeySemicolon": "è", + "KeyQuote": "à", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "ù", + "KeyBackSlash": "<", + "KeyUnbound": "Non assegnato", + "GamepadLeftStick": "Pulsante levetta sinistra", + "GamepadRightStick": "Pulsante levetta destra", + "GamepadLeftShoulder": "Pulsante dorsale sinistro", + "GamepadRightShoulder": "Pulsante dorsale destro", + "GamepadLeftTrigger": "Grilletto sinistro", + "GamepadRightTrigger": "Grilletto destro", + "GamepadDpadUp": "Su", + "GamepadDpadDown": "Giù", + "GamepadDpadLeft": "Sinistra", + "GamepadDpadRight": "Destra", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Grilletto sinistro 0", + "GamepadSingleRightTrigger0": "Grilletto destro 0", + "GamepadSingleLeftTrigger1": "Grilletto sinistro 1", + "GamepadSingleRightTrigger1": "Grilletto destro 1", + "StickLeft": "Levetta sinistra", + "StickRight": "Levetta destra", + "UserProfilesSelectedUserProfile": "Profilo utente selezionato:", + "UserProfilesSaveProfileName": "Salva nome del profilo", + "UserProfilesChangeProfileImage": "Cambia immagine profilo", + "UserProfilesAvailableUserProfiles": "Profili utente disponibili:", + "UserProfilesAddNewProfile": "Aggiungi nuovo profilo", + "UserProfilesDelete": "Elimina", + "UserProfilesClose": "Chiudi", + "ProfileNameSelectionWatermark": "Scegli un soprannome", + "ProfileImageSelectionTitle": "Selezione dell'immagine profilo", + "ProfileImageSelectionHeader": "Scegli un'immagine profilo", + "ProfileImageSelectionNote": "Puoi importare un'immagine profilo personalizzata o selezionare un avatar dal firmware del sistema", + "ProfileImageSelectionImportImage": "Importa file immagine", + "ProfileImageSelectionSelectAvatar": "Seleziona avatar dal firmware", + "InputDialogTitle": "Finestra di input", + "InputDialogOk": "OK", + "InputDialogCancel": "Annulla", + "InputDialogAddNewProfileTitle": "Scegli il nome del profilo", + "InputDialogAddNewProfileHeader": "Digita un nome profilo", + "InputDialogAddNewProfileSubtext": "(Lunghezza massima: {0})", + "AvatarChoose": "Scegli", + "AvatarSetBackgroundColor": "Imposta colore di sfondo", + "AvatarClose": "Chiudi", + "ControllerSettingsLoadProfileToolTip": "Carica profilo", + "ControllerSettingsAddProfileToolTip": "Aggiungi profilo", + "ControllerSettingsRemoveProfileToolTip": "Rimuovi profilo", + "ControllerSettingsSaveProfileToolTip": "Salva profilo", + "MenuBarFileToolsTakeScreenshot": "Cattura uno screenshot", + "MenuBarFileToolsHideUi": "Nascondi l'interfaccia", + "GameListContextMenuRunApplication": "Esegui applicazione", + "GameListContextMenuToggleFavorite": "Preferito", + "GameListContextMenuToggleFavoriteToolTip": "Segna il gioco come preferito", + "SettingsTabGeneralTheme": "Tema:", + "SettingsTabGeneralThemeDark": "Scuro", + "SettingsTabGeneralThemeLight": "Chiaro", + "ControllerSettingsConfigureGeneral": "Configura", + "ControllerSettingsRumble": "Vibrazione", + "ControllerSettingsRumbleStrongMultiplier": "Moltiplicatore vibrazione forte", + "ControllerSettingsRumbleWeakMultiplier": "Moltiplicatore vibrazione debole", + "DialogMessageSaveNotAvailableMessage": "Non ci sono dati di salvataggio per {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Vuoi creare dei dati di salvataggio per questo gioco?", + "DialogConfirmationTitle": "Ryujinx - Conferma", + "DialogUpdaterTitle": "Ryujinx - Aggiornamento", + "DialogErrorTitle": "Ryujinx - Errore", + "DialogWarningTitle": "Ryujinx - Avviso", + "DialogExitTitle": "Ryujinx - Esci", + "DialogErrorMessage": "Ryujinx ha riscontrato un problema", + "DialogExitMessage": "Sei sicuro di voler chiudere Ryujinx?", + "DialogExitSubMessage": "Tutti i dati non salvati andranno persi!", + "DialogMessageCreateSaveErrorMessage": "C'è stato un errore durante la creazione dei dati di salvataggio: {0}", + "DialogMessageFindSaveErrorMessage": "C'è stato un errore durante la ricerca dei dati di salvataggio: {0}", + "FolderDialogExtractTitle": "Scegli una cartella in cui estrarre", + "DialogNcaExtractionMessage": "Estrazione della sezione {0} da {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Estrazione sezione NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "L'estrazione è fallita. L'NCA principale non era presente nel file selezionato.", + "DialogNcaExtractionCheckLogErrorMessage": "L'estrazione è fallita. Consulta il file di log per maggiori informazioni.", + "DialogNcaExtractionSuccessMessage": "Estrazione completata con successo.", + "DialogUpdaterConvertFailedMessage": "La conversione dell'attuale versione di Ryujinx è fallita.", + "DialogUpdaterCancelUpdateMessage": "Annullamento dell'aggiornamento in corso!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Stai già usando la versione più recente di Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Si è verificato un errore durante il tentativo di recuperare le informazioni sulla versione da GitHub Release. Ciò può verificarsi se una nuova versione è in fase di compilazione da GitHub Actions. Riprova tra qualche minuto.", + "DialogUpdaterConvertFailedGithubMessage": "La conversione della versione di Ryujinx ricevuta da Github Release è fallita.", + "DialogUpdaterDownloadingMessage": "Download dell'aggiornamento...", + "DialogUpdaterExtractionMessage": "Estrazione dell'aggiornamento...", + "DialogUpdaterRenamingMessage": "Rinominazione dell'aggiornamento...", + "DialogUpdaterAddingFilesMessage": "Aggiunta del nuovo aggiornamento...", + "DialogUpdaterCompleteMessage": "Aggiornamento completato!", + "DialogUpdaterRestartMessage": "Vuoi riavviare Ryujinx adesso?", + "DialogUpdaterNoInternetMessage": "Non sei connesso ad Internet!", + "DialogUpdaterNoInternetSubMessage": "Verifica di avere una connessione ad Internet funzionante!", + "DialogUpdaterDirtyBuildMessage": "Non puoi aggiornare una Dirty build di Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Scarica Ryujinx da https://ryujinx.org/ se stai cercando una versione supportata.", + "DialogRestartRequiredMessage": "Riavvio richiesto", + "DialogThemeRestartMessage": "Il tema è stato salvato. È richiesto un riavvio per applicare il tema.", + "DialogThemeRestartSubMessage": "Vuoi riavviare?", + "DialogFirmwareInstallEmbeddedMessage": "Vuoi installare il firmware incorporato in questo gioco? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Non è stato trovato alcun firmware installato, ma Ryujinx è riuscito ad installare il firmware {0} dal gioco fornito.\nL'emulatore si avvierà adesso.", + "DialogFirmwareNoFirmwareInstalledMessage": "Nessun firmware installato", + "DialogFirmwareInstalledMessage": "Il firmware {0} è stato installato", + "DialogInstallFileTypesSuccessMessage": "Tipi di file installati con successo!", + "DialogInstallFileTypesErrorMessage": "Impossibile installare i tipi di file.", + "DialogUninstallFileTypesSuccessMessage": "Tipi di file disinstallati con successo!", + "DialogUninstallFileTypesErrorMessage": "Disinstallazione dei tipi di file non riuscita.", + "DialogOpenSettingsWindowLabel": "Apri finestra delle impostazioni", + "DialogControllerAppletTitle": "Applet del controller", + "DialogMessageDialogErrorExceptionMessage": "Errore nella visualizzazione del Message Dialog: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Errore nella visualizzazione della tastiera software: {0}", + "DialogErrorAppletErrorExceptionMessage": "Errore nella visualizzazione dell'ErrorApplet Dialog: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPer maggiori informazioni su come risolvere questo errore, segui la nostra guida all'installazione.", + "DialogUserErrorDialogTitle": "Errore di Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Si è verificato un errore durante il recupero delle informazioni dall'API.", + "DialogAmiiboApiConnectErrorMessage": "Impossibile connettersi al server Amiibo API. Il servizio potrebbe essere fuori uso o potresti dover verificare che la tua connessione internet sia online.", + "DialogProfileInvalidProfileErrorMessage": "Il profilo {0} è incompatibile con l'attuale sistema di configurazione input.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Il profilo predefinito non può essere sovrascritto", + "DialogProfileDeleteProfileTitle": "Eliminazione profilo", + "DialogProfileDeleteProfileMessage": "Quest'azione è irreversibile, sei sicuro di voler continuare?", + "DialogWarning": "Avviso", + "DialogPPTCDeletionMessage": "Stai per accodare la rigenerazione della cache PPTC al prossimo avvio per:\n\n{0}\n\nSei sicuro di voler proseguire?", + "DialogPPTCDeletionErrorMessage": "Errore nell'eliminazione della cache PPTC a {0}: {1}", + "DialogShaderDeletionMessage": "Stai per eliminare la cache degli shader per:\n\n{0}\n\nSei sicuro di voler proseguire?", + "DialogShaderDeletionErrorMessage": "Errore nell'eliminazione della cache degli shader a {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx ha incontrato un errore", + "DialogInvalidTitleIdErrorMessage": "Errore UI: Il gioco selezionato non ha un ID titolo valido", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware del sistema valido non è stato trovato in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Installa firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "La versione del sistema {0} sarà installata.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nQuesta sostituirà l'attuale versione di sistema {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nVuoi continuare?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installazione del firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "La versione del sistema {0} è stata installata.", + "DialogUserProfileDeletionWarningMessage": "Non ci sarebbero altri profili da aprire se il profilo selezionato viene cancellato", + "DialogUserProfileDeletionConfirmMessage": "Vuoi eliminare il profilo selezionato?", + "DialogUserProfileUnsavedChangesTitle": "Attenzione - Modifiche Non Salvate", + "DialogUserProfileUnsavedChangesMessage": "Hai apportato modifiche a questo profilo utente che non sono state salvate.", + "DialogUserProfileUnsavedChangesSubMessage": "Vuoi scartare le modifiche?", + "DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Vuoi salvare?", + "DialogLoadFileErrorMessage": "{0}. Errore File: {1}", + "DialogModAlreadyExistsMessage": "La mod risulta già installata", + "DialogModInvalidMessage": "La cartella specificata non contiene nessuna mod!", + "DialogModDeleteNoParentMessage": "Eliminazione non riuscita: impossibile trovare la directory superiore per la mod \"{0}\"!", + "DialogDlcNoDlcErrorMessage": "Il file specificato non contiene un DLC per il titolo selezionato!", + "DialogPerformanceCheckLoggingEnabledMessage": "Hai abilitato il trace logging, che è progettato per essere usato solo dagli sviluppatori.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitarlo adesso?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Hai abilitato il dump degli shader, che è progettato per essere usato solo dagli sviluppatori.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il dump degli shader. Vuoi disabilitarlo adesso?", + "DialogLoadAppGameAlreadyLoadedMessage": "Un gioco è già stato caricato", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Ferma l'emulazione o chiudi l'emulatore prima di avviare un altro gioco.", + "DialogUpdateAddUpdateErrorMessage": "Il file specificato non contiene un aggiornamento per il titolo selezionato!", + "DialogSettingsBackendThreadingWarningTitle": "Avviso - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx deve essere riavviato dopo aver cambiato questa opzione per applicarla completamente. A seconda della tua piattaforma, potrebbe essere necessario disabilitare manualmente il multithreading del driver quando usi quello di Ryujinx.", + "DialogModManagerDeletionWarningMessage": "Stai per eliminare la mod: {0}\n\nConfermi di voler procedere?", + "DialogModManagerDeletionAllWarningMessage": "Stai per eliminare tutte le mod per questo titolo.\n\nVuoi davvero procedere?", + "SettingsTabGraphicsFeaturesOptions": "Funzionalità", + "SettingsTabGraphicsBackendMultithreading": "Multithreading del backend grafico:", + "CommonAuto": "Auto", + "CommonOff": "Disattivato", + "CommonOn": "Attivo", + "InputDialogYes": "Sì", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "Il nome del file contiene dei caratteri non validi. Riprova.", + "MenuBarOptionsPauseEmulation": "Metti in pausa", + "MenuBarOptionsResumeEmulation": "Riprendi", + "AboutUrlTooltipMessage": "Clicca per aprire il sito web di Ryujinx nel tuo browser predefinito.", + "AboutDisclaimerMessage": "Ryujinx non è affiliato con Nintendo™,\no i suoi partner, in alcun modo.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) è usata\nnella nostra emulazione Amiibo.", + "AboutPatreonUrlTooltipMessage": "Clicca per aprire la pagina Patreon di Ryujinx nel tuo browser predefinito.", + "AboutGithubUrlTooltipMessage": "Clicca per aprire la pagina GitHub di Ryujinx nel tuo browser predefinito.", + "AboutDiscordUrlTooltipMessage": "Clicca per aprire un invito al server Discord di Ryujinx nel tuo browser predefinito.", + "AboutTwitterUrlTooltipMessage": "Clicca per aprire la pagina Twitter di Ryujinx nel tuo browser predefinito.", + "AboutRyujinxAboutTitle": "Informazioni:", + "AboutRyujinxAboutContent": "Ryujinx è un emulatore per la console Nintendo Switch™.\nSostienici su Patreon.\nRicevi tutte le ultime notizie sul nostro Twitter o su Discord.\nGli sviluppatori interessati a contribuire possono trovare più informazioni sul nostro GitHub o Discord.", + "AboutRyujinxMaintainersTitle": "Mantenuto da:", + "AboutRyujinxMaintainersContentTooltipMessage": "Clicca per aprire la pagina dei contributori nel tuo browser predefinito.", + "AboutRyujinxSupprtersTitle": "Supportato su Patreon da:", + "AmiiboSeriesLabel": "Serie Amiibo", + "AmiiboCharacterLabel": "Personaggio", + "AmiiboScanButtonLabel": "Scansiona", + "AmiiboOptionsShowAllLabel": "Mostra tutti gli amiibo", + "AmiiboOptionsUsRandomTagLabel": "Espediente: Usa un UUID del tag casuale", + "DlcManagerTableHeadingEnabledLabel": "Abilitato", + "DlcManagerTableHeadingTitleIdLabel": "ID Titolo", + "DlcManagerTableHeadingContainerPathLabel": "Percorso del contenitore", + "DlcManagerTableHeadingFullPathLabel": "Percorso completo", + "DlcManagerRemoveAllButton": "Rimuovi tutti", + "DlcManagerEnableAllButton": "Abilita tutto", + "DlcManagerDisableAllButton": "Disabilita tutto", + "ModManagerDeleteAllButton": "Elimina tutto", + "MenuBarOptionsChangeLanguage": "Cambia lingua", + "MenuBarShowFileTypes": "Mostra tipi di file", + "CommonSort": "Ordina", + "CommonShowNames": "Mostra nomi", + "CommonFavorite": "Preferito", + "OrderAscending": "Crescente", + "OrderDescending": "Decrescente", + "SettingsTabGraphicsFeatures": "Funzionalità e miglioramenti", + "ErrorWindowTitle": "Finestra di errore", + "ToggleDiscordTooltip": "Scegli se mostrare o meno Ryujinx nella tua attività su Discord", + "AddGameDirBoxTooltip": "Inserisci una cartella dei giochi per aggiungerla alla lista", + "AddGameDirTooltip": "Aggiungi una cartella dei giochi alla lista", + "RemoveGameDirTooltip": "Rimuovi la cartella dei giochi selezionata", + "CustomThemeCheckTooltip": "Attiva o disattiva temi personalizzati nella GUI", + "CustomThemePathTooltip": "Percorso al tema GUI personalizzato", + "CustomThemeBrowseTooltip": "Sfoglia per cercare un tema GUI personalizzato", + "DockModeToggleTooltip": "La modalità TV fa sì che il sistema emulato si comporti come una Nintendo Switch posizionata nella sua base. Ciò migliora la qualità grafica nella maggior parte dei giochi. Al contrario, disabilitandola il sistema emulato si comporterà come una Nintendo Switch in modalità portatile, riducendo la qualità grafica.\n\nConfigura i controlli del giocatore 1 se intendi usare la modalità TV; configura i controlli della modalità portatile se intendi usare quest'ultima.\n\nNel dubbio, lascia l'opzione attiva.", + "DirectKeyboardTooltip": "Supporto per l'accesso diretto alla tastiera (HID). Fornisce ai giochi l'accesso alla tastiera come dispositivo di inserimento del testo.\n\nFunziona solo con i giochi che supportano nativamente l'utilizzo della tastiera su hardware Switch.\n\nNel dubbio, lascia l'opzione disattivata.", + "DirectMouseTooltip": "Supporto per l'accesso diretto al mouse (HID). Fornisce ai giochi l'accesso al mouse come dispositivo di puntamento.\n\nFunziona solo con i rari giochi che supportano nativamente l'utilizzo del mouse su hardware Switch.\n\nQuando questa opzione è attivata, il touchscreen potrebbe non funzionare.\n\nNel dubbio, lascia l'opzione disattivata.", + "RegionTooltip": "Cambia regione di sistema", + "LanguageTooltip": "Cambia lingua di sistema", + "TimezoneTooltip": "Cambia fuso orario di sistema", + "TimeTooltip": "Cambia data e ora di sistema", + "VSyncToggleTooltip": "Sincronizzazione verticale della console Emulata. Essenzialmente un limitatore di frame per la maggior parte dei giochi; disabilitarlo può far girare giochi a velocità più alta, allungare le schermate di caricamento o farle bloccare.\n\nPuò essere attivata in gioco con un tasto di scelta rapida (F1 per impostazione predefinita). Ti consigliamo di farlo se hai intenzione di disabilitarlo.\n\nLascia ON se non sei sicuro.", + "PptcToggleTooltip": "Salva le funzioni JIT tradotte in modo che non debbano essere tradotte tutte le volte che si avvia un determinato gioco.\n\nRiduce i fenomeni di stuttering e velocizza sensibilmente gli avvii successivi del gioco.\n\nNel dubbio, lascia l'opzione attiva.", + "FsIntegrityToggleTooltip": "Controlla la presenza di file corrotti quando si avvia un gioco. Se vengono rilevati dei file corrotti, verrà mostrato un errore di hash nel log.\n\nQuesta opzione non influisce sulle prestazioni ed è pensata per facilitare la risoluzione dei problemi.\n\nNel dubbio, lascia l'opzione attiva.", + "AudioBackendTooltip": "Cambia il backend usato per riprodurre l'audio.\n\nSDL2 è quello preferito, mentre OpenAL e SoundIO sono usati come ripiego. Dummy non riprodurrà alcun suono.\n\nNel dubbio, imposta l'opzione su SDL2.", + "MemoryManagerTooltip": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.\n\nNel dubbio, imposta l'opzione su Host Unchecked.", + "MemoryManagerSoftwareTooltip": "Usa una software page table per la traduzione degli indirizzi. Massima precisione ma prestazioni più lente.", + "MemoryManagerHostTooltip": "Mappa direttamente la memoria nello spazio degli indirizzi dell'host. Compilazione ed esecuzione JIT molto più veloce.", + "MemoryManagerUnsafeTooltip": "Mappa direttamente la memoria, ma non maschera l'indirizzo all'interno dello spazio degli indirizzi guest prima dell'accesso. Più veloce, ma a costo della sicurezza. L'applicazione guest può accedere alla memoria da qualsiasi punto di Ryujinx, quindi esegui solo programmi di cui ti fidi con questa modalità.", + "UseHypervisorTooltip": "Usa Hypervisor invece di JIT. Migliora notevolmente le prestazioni quando disponibile, ma può essere instabile nel suo stato attuale.", + "DRamTooltip": "Utilizza un layout di memoria alternativo per imitare un'unità di sviluppo di Switch.\n\nQuesta opzione è utile soltanto per i pacchetti di texture ad alta risoluzione o per le mod che aumentano la risoluzione a 4K. NON migliora le prestazioni.\n\nNel dubbio, lascia l'opzione disattivata.", + "IgnoreMissingServicesTooltip": "Ignora i servizi non implementati del sistema operativo Horizon. Può aiutare ad aggirare gli arresti anomali che si verificano avviando alcuni giochi.\n\nNel dubbio, lascia l'opzione disattivata.", + "GraphicsBackendThreadingTooltip": "Esegue i comandi del backend grafico su un secondo thread.\n\nVelocizza la compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver grafici senza il supporto integrato al multithreading. Migliora leggermente le prestazioni sui driver che supportano il multithreading.\n\nNel dubbio, imposta l'opzione su Auto.", + "GalThreadingTooltip": "Esegue i comandi del backend grafico su un secondo thread.\n\nVelocizza la compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver grafici senza il supporto integrato al multithreading. Migliora leggermente le prestazioni sui driver che supportano il multithreading.\n\nNel dubbio, imposta l'opzione su Auto.", + "ShaderCacheToggleTooltip": "Salva una cache degli shader su disco che riduce i fenomeni di stuttering nelle esecuzioni successive.\n\nNel dubbio, lascia l'opzione attiva.", + "ResolutionScaleTooltip": "Moltiplica la risoluzione di rendering del gioco.\n\nAlcuni giochi potrebbero non funzionare con questa opzione e sembrare pixelati anche quando la risoluzione è aumentata; per quei giochi, potrebbe essere necessario trovare mod che rimuovono l'anti-aliasing o che aumentano la risoluzione di rendering interna. Per quest'ultimo caso, probabilmente dovrai selezionare Nativo (1x).\n\nQuesta opzione può essere modificata mentre un gioco è in esecuzione facendo clic su \"Applica\" qui sotto; puoi semplicemente spostare la finestra delle impostazioni da parte e sperimentare fino a quando non trovi il tuo look preferito per un gioco.\n\nTenete a mente che 4x è troppo per praticamente qualsiasi configurazione.", + "ResolutionScaleEntryTooltip": "Scala della risoluzione in virgola mobile, come 1,5. Le scale non integrali hanno maggiori probabilità di causare problemi o crash.", + "AnisotropyTooltip": "Livello del filtro anisotropico. Imposta su Auto per usare il valore richiesto dal gioco.", + "AspectRatioTooltip": "Proporzioni dello schermo applicate alla finestra di renderizzazione.\n\nCambialo solo se stai usando una mod di proporzioni per il tuo gioco, altrimenti la grafica verrà allungata.\n\nLasciare il 16:9 se incerto.", + "ShaderDumpPathTooltip": "Percorso di dump degli shader", + "FileLogTooltip": "Salva il log della console in un file su disco. Non influisce sulle prestazioni.", + "StubLogTooltip": "Stampa i messaggi di log relativi alle stub nella console. Non influisce sulle prestazioni.", + "InfoLogTooltip": "Stampa i messaggi di log informativi nella console. Non influisce sulle prestazioni.", + "WarnLogTooltip": "Stampa i messaggi di log relativi agli avvisi nella console. Non influisce sulle prestazioni.", + "ErrorLogTooltip": "Stampa i messaggi di log relativi agli errori nella console. Non influisce sulle prestazioni.", + "TraceLogTooltip": "Stampa i messaggi di log relativi al trace nella console. Non influisce sulle prestazioni.", + "GuestLogTooltip": "Stampa i messaggi di log del guest nella console. Non influisce sulle prestazioni.", + "FileAccessLogTooltip": "Stampa i messaggi di log relativi all'accesso ai file nella console.", + "FSAccessLogModeTooltip": "Attiva l'output dei log di accesso FS nella console. Le modalità possibili vanno da 0 a 3", + "DeveloperOptionTooltip": "Usa con attenzione", + "OpenGlLogLevel": "Richiede che i livelli di log appropriati siano abilitati", + "DebugLogTooltip": "Stampa i messaggi di log per il debug nella console.\n\nUsa questa opzione solo se specificatamente richiesto da un membro del team, dal momento che rende i log difficili da leggere e riduce le prestazioni dell'emulatore.", + "LoadApplicationFileTooltip": "Apri un file explorer per scegliere un file compatibile Switch da caricare", + "LoadApplicationFolderTooltip": "Apri un file explorer per scegliere un file compatibile Switch, applicazione sfusa da caricare", + "OpenRyujinxFolderTooltip": "Apri la cartella del filesystem di Ryujinx", + "OpenRyujinxLogsTooltip": "Apre la cartella dove vengono salvati i log", + "ExitTooltip": "Esci da Ryujinx", + "OpenSettingsTooltip": "Apri la finestra delle impostazioni", + "OpenProfileManagerTooltip": "Apri la finestra di gestione dei profili utente", + "StopEmulationTooltip": "Ferma l'emulazione del gioco attuale e torna alla selezione dei giochi", + "CheckUpdatesTooltip": "Controlla la presenza di aggiornamenti di Ryujinx", + "OpenAboutTooltip": "Apri la finestra delle informazioni", + "GridSize": "Dimensione griglia", + "GridSizeTooltip": "Cambia la dimensione dei riquadri della griglia", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portoghese brasiliano", + "AboutRyujinxContributorsButtonHeader": "Mostra tutti i contributori", + "SettingsTabSystemAudioVolume": "Volume: ", + "AudioVolumeTooltip": "Cambia volume audio", + "SettingsTabSystemEnableInternetAccess": "Attiva l'accesso a Internet da parte del guest/Modalità LAN", + "EnableInternetAccessTooltip": "Consente all'applicazione emulata di connettersi a Internet.\n\nI giochi che dispongono di una modalità LAN possono connettersi tra di loro quando questa opzione è abilitata e sono connessi alla stessa rete, comprese le console reali.\n\nQuesta opzione NON consente la connessione ai server di Nintendo. Potrebbe causare arresti anomali in alcuni giochi che provano a connettersi a Internet.\n\nNel dubbio, lascia l'opzione disattivata.", + "GameListContextMenuManageCheatToolTip": "Gestisci trucchi", + "GameListContextMenuManageCheat": "Gestisci trucchi", + "GameListContextMenuManageModToolTip": "Gestisci mod", + "GameListContextMenuManageMod": "Gestisci mod", + "ControllerSettingsStickRange": "Raggio:", + "DialogStopEmulationTitle": "Ryujinx - Ferma emulazione", + "DialogStopEmulationMessage": "Sei sicuro di voler fermare l'emulazione?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Rete", + "SettingsTabNetworkConnection": "Connessione di rete", + "SettingsTabCpuCache": "Cache CPU", + "SettingsTabCpuMemory": "Modalità CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Aggiorna Ryujinx tramite FlatHub.", + "UpdaterDisabledWarningTitle": "Updater disabilitato!", + "ControllerSettingsRotate90": "Ruota in senso orario di 90°", + "IconSize": "Dimensioni icone", + "IconSizeTooltip": "Cambia le dimensioni delle icone dei giochi", + "MenuBarOptionsShowConsole": "Mostra console", + "ShaderCachePurgeError": "Errore nell'eliminazione della cache degli shader a {0}: {1}", + "UserErrorNoKeys": "Chiavi non trovate", + "UserErrorNoFirmware": "Firmware non trovato", + "UserErrorFirmwareParsingFailed": "Errori di analisi del firmware", + "UserErrorApplicationNotFound": "Applicazione non trovata", + "UserErrorUnknown": "Errore sconosciuto", + "UserErrorUndefined": "Errore non definito", + "UserErrorNoKeysDescription": "Ryujinx non è riuscito a trovare il file 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx non è riuscito a trovare alcun firmware installato", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx non è riuscito ad analizzare il firmware. Questo di solito è causato da chiavi non aggiornate.", + "UserErrorApplicationNotFoundDescription": "Ryujinx non è riuscito a trovare un'applicazione valida nel percorso specificato.", + "UserErrorUnknownDescription": "Si è verificato un errore sconosciuto!", + "UserErrorUndefinedDescription": "Si è verificato un errore sconosciuto! Ciò non dovrebbe accadere, contatta uno sviluppatore!", + "OpenSetupGuideMessage": "Apri la guida all'installazione", + "NoUpdate": "Nessun aggiornamento", + "TitleUpdateVersionLabel": "Versione {0}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Conferma", + "FileDialogAllTypes": "Tutti i tipi", + "Never": "Mai", + "SwkbdMinCharacters": "Non può avere meno di {0} caratteri", + "SwkbdMinRangeCharacters": "Può avere da {0} a {1} caratteri", + "SoftwareKeyboard": "Tastiera software", + "SoftwareKeyboardModeNumeric": "Deve essere solo 0-9 o '.'", + "SoftwareKeyboardModeAlphabet": "Deve essere solo caratteri non CJK", + "SoftwareKeyboardModeASCII": "Deve essere solo testo ASCII", + "ControllerAppletControllers": "Controller supportati:", + "ControllerAppletPlayers": "Giocatori:", + "ControllerAppletDescription": "La configurazione corrente non è valida. Aprire le impostazioni e riconfigurare gli input.", + "ControllerAppletDocked": "Modalità TV attivata. Gli input della modalità portatile dovrebbero essere disabilitati.", + "UpdaterRenaming": "Rinominazione dei vecchi files...", + "UpdaterRenameFailed": "Non è stato possibile rinominare il file: {0}", + "UpdaterAddingFiles": "Aggiunta dei nuovi file...", + "UpdaterExtracting": "Estrazione dell'aggiornamento...", + "UpdaterDownloading": "Download dell'aggiornamento...", + "Game": "Gioco", + "Docked": "TV", + "Handheld": "Portatile", + "ConnectionError": "Errore di connessione.", + "AboutPageDeveloperListMore": "{0} e altri ancora...", + "ApiError": "Errore dell'API.", + "LoadingHeading": "Caricamento di {0}", + "CompilingPPTC": "Compilazione PPTC", + "CompilingShaders": "Compilazione degli shader", + "AllKeyboards": "Tutte le tastiere", + "OpenFileDialogTitle": "Seleziona un file supportato da aprire", + "OpenFolderDialogTitle": "Seleziona una cartella con un gioco estratto", + "AllSupportedFormats": "Tutti i formati supportati", + "RyujinxUpdater": "Aggiornamento di Ryujinx", + "SettingsTabHotkeys": "Tasti di scelta rapida", + "SettingsTabHotkeysHotkeys": "Tasti di scelta rapida", + "SettingsTabHotkeysToggleVsyncHotkey": "Attiva/disattiva VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Cattura uno screenshot:", + "SettingsTabHotkeysShowUiHotkey": "Mostra l'interfaccia:", + "SettingsTabHotkeysPauseHotkey": "Metti in pausa:", + "SettingsTabHotkeysToggleMuteHotkey": "Disattiva l'audio:", + "ControllerMotionTitle": "Impostazioni dei sensori di movimento", + "ControllerRumbleTitle": "Impostazioni di vibrazione", + "SettingsSelectThemeFileDialogTitle": "Seleziona file del tema", + "SettingsXamlThemeFile": "File del tema xaml", + "AvatarWindowTitle": "Gestisci account - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Sconosciuto", + "Usage": "Utilizzo", + "Writable": "Scrivibile", + "SelectDlcDialogTitle": "Seleziona file dei DLC", + "SelectUpdateDialogTitle": "Seleziona file di aggiornamento", + "SelectModDialogTitle": "Seleziona cartella delle mod", + "UserProfileWindowTitle": "Gestione profili utente", + "CheatWindowTitle": "Gestione trucchi", + "DlcWindowTitle": "Gestisci DLC per {0} ({1})", + "ModWindowTitle": "Gestisci mod per {0} ({1})", + "UpdateWindowTitle": "Gestione aggiornamenti", + "CheatWindowHeading": "Trucchi disponibili per {0} [{1}]", + "BuildId": "ID Build", + "DlcWindowHeading": "DLC disponibili per {0} [{1}]", + "ModWindowHeading": "{0} mod", + "UserProfilesEditProfile": "Modifica selezionati", + "Cancel": "Annulla", + "Save": "Salva", + "Discard": "Scarta", + "Paused": "In pausa", + "UserProfilesSetProfileImage": "Imposta immagine profilo", + "UserProfileEmptyNameError": "Il nome è obbligatorio", + "UserProfileNoImageError": "Dev'essere impostata un'immagine profilo", + "GameUpdateWindowHeading": "Gestisci aggiornamenti per {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "Aumenta la risoluzione:", + "SettingsTabHotkeysResScaleDownHotkey": "Riduci la risoluzione:", + "UserProfilesName": "Nome:", + "UserProfilesUserId": "ID utente:", + "SettingsTabGraphicsBackend": "Backend grafico", + "SettingsTabGraphicsBackendTooltip": "Seleziona il backend grafico che verrà utilizzato nell'emulatore.\n\nVulkan è nel complesso migliore per tutte le schede grafiche moderne, a condizione che i relativi driver siano aggiornati. Vulkan dispone anche di una compilazione degli shader più veloce (con minore stuttering) su tutte le marche di GPU.\n\nOpenGL può ottenere risultati migliori su vecchie GPU Nvidia, su vecchie GPU AMD su Linux, o su GPU con poca VRAM, anche se lo stuttering dovuto alla compilazione degli shader sarà maggiore.\n\nNel dubbio, scegli Vulkan. Seleziona OpenGL se la GPU non supporta Vulkan nemmeno con i driver grafici più recenti.", + "SettingsEnableTextureRecompression": "Attiva la ricompressione delle texture", + "SettingsEnableTextureRecompressionTooltip": "Comprime le texture ASTC per ridurre l'utilizzo di VRAM.\n\nI giochi che utilizzano questo formato di texture includono Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder e The Legend of Zelda: Tears of the Kingdom.\n\nLe schede grafiche con 4GiB o meno di VRAM probabilmente si bloccheranno ad un certo punto durante l'esecuzione di questi giochi.\n\nAttiva questa opzione solo se sei a corto di VRAM nei giochi sopra menzionati. Nel dubbio, lascia l'opzione disattivata.", + "SettingsTabGraphicsPreferredGpu": "GPU preferita", + "SettingsTabGraphicsPreferredGpuTooltip": "Seleziona la scheda grafica che verrà usata con la backend grafica Vulkan.\n\nNon influenza la GPU che userà OpenGL.\n\nImposta la GPU contrassegnata come \"dGPU\" se non sei sicuro. Se non ce n'è una, lascia intatta quest'impostazione.", + "SettingsAppRequiredRestartMessage": "È richiesto un riavvio di Ryujinx", + "SettingsGpuBackendRestartMessage": "Le impostazioni della backend grafica o della GPU sono state modificate. Questo richiederà un riavvio perché le modifiche siano applicate", + "SettingsGpuBackendRestartSubMessage": "Vuoi riavviare ora?", + "RyujinxUpdaterMessage": "Vuoi aggiornare Ryujinx all'ultima versione?", + "SettingsTabHotkeysVolumeUpHotkey": "Alza il volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Abbassa il volume:", + "SettingsEnableMacroHLE": "Attiva HLE macro", + "SettingsEnableMacroHLETooltip": "Emulazione di alto livello del codice macro della GPU.\n\nMigliora le prestazioni, ma può causare anomalie grafiche in alcuni giochi.\n\nNel dubbio, lascia l'opzione attiva.", + "SettingsEnableColorSpacePassthrough": "Passthrough dello spazio dei colori", + "SettingsEnableColorSpacePassthroughTooltip": "Indica al backend Vulkan di passare le informazioni sul colore senza specificare uno spazio dei colori. Per gli utenti con schermi ad ampia gamma, ciò può rendere i colori più vivaci, sacrificando la correttezza del colore.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Gestisci i salvataggi", + "DeleteUserSave": "Vuoi eliminare il salvataggio utente per questo gioco?", + "IrreversibleActionNote": "Questa azione non è reversibile.", + "SaveManagerHeading": "Gestisci salvataggi per {0} ({1})", + "SaveManagerTitle": "Gestione salvataggi", + "Name": "Nome", + "Size": "Dimensione", + "Search": "Cerca", + "UserProfilesRecoverLostAccounts": "Recupera account persi", + "Recover": "Recupera", + "UserProfilesRecoverHeading": "Sono stati trovati dei salvataggi per i seguenti account", + "UserProfilesRecoverEmptyList": "Nessun profilo da recuperare", + "GraphicsAATooltip": "Applica anti-aliasing al rendering del gioco.\n\nFXAA sfocerà la maggior parte dell'immagine, mentre SMAA tenterà di trovare bordi frastagliati e lisciarli.\n\nNon si consiglia di usarlo in combinazione con il filtro di scala FSR.\n\nQuesta opzione può essere modificata mentre un gioco è in esecuzione facendo clic su \"Applica\" qui sotto; puoi semplicemente spostare la finestra delle impostazioni da parte e sperimentare fino a quando non trovi il tuo look preferito per un gioco.\n\nLasciare su Nessuno se incerto.", + "GraphicsAALabel": "Anti-Aliasing:", + "GraphicsScalingFilterLabel": "Filtro di scala:", + "GraphicsScalingFilterTooltip": "Scegli il filtro di scaling che verrà applicato quando si utilizza o scaling di risoluzione.\n\nBilineare funziona bene per i giochi 3D ed è un'opzione predefinita affidabile.\n\nNearest è consigliato per i giochi in pixel art.\n\nFSR 1.0 è solo un filtro di nitidezza, non raccomandato per l'uso con FXAA o SMAA.\n\nQuesta opzione può essere modificata mentre un gioco è in esecuzione facendo clic su \"Applica\" qui sotto; puoi semplicemente spostare la finestra delle impostazioni da parte e sperimentare fino a quando non trovi il tuo look preferito per un gioco.\n\nLasciare su Bilineare se incerto.", + "GraphicsScalingFilterBilinear": "Bilineare", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "Livello", + "GraphicsScalingFilterLevelTooltip": "Imposta il livello di nitidezza di FSR 1.0. Valori più alti comportano una maggiore nitidezza.", + "SmaaLow": "SMAA Basso", + "SmaaMedium": "SMAA Medio", + "SmaaHigh": "SMAA Alto", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Modificare L'Utente", + "UserEditorTitleCreate": "Crea Un Utente", + "SettingsTabNetworkInterface": "Interfaccia di rete:", + "NetworkInterfaceTooltip": "L'interfaccia di rete utilizzata per le funzionalità LAN/LDN.\n\nIn combinazione con una VPN o XLink Kai e un gioco che supporta la modalità LAN, questa opzione può essere usata per simulare la connessione alla stessa rete attraverso Internet.\n\nNel dubbio, lascia l'opzione su Predefinito.", + "NetworkInterfaceDefault": "Predefinito", + "PackagingShaders": "Salvataggio degli shader", + "AboutChangelogButton": "Visualizza changelog su GitHub", + "AboutChangelogButtonTooltipMessage": "Clicca per aprire il changelog per questa versione nel tuo browser predefinito.", + "SettingsTabNetworkMultiplayer": "Multigiocatore", + "MultiplayerMode": "Modalità:", + "MultiplayerModeTooltip": "Cambia la modalità multigiocatore LDN.\n\nLdnMitm modificherà la funzionalità locale wireless/local play nei giochi per funzionare come se fosse in modalità LAN, consentendo connessioni locali sulla stessa rete con altre istanze di Ryujinx e console Nintendo Switch modificate che hanno il modulo ldn_mitm installato.\n\nLa modalità multigiocatore richiede che tutti i giocatori usino la stessa versione del gioco (es. Super Smash Bros. Ultimate v13.0.1 non può connettersi con la v13.0.0).\n\nNel dubbio, lascia l'opzione su Disabilitato.", + "MultiplayerModeDisabled": "Disabilitato", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json new file mode 100644 index 00000000..61e96325 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -0,0 +1,780 @@ +{ + "Language": "日本語", + "MenuBarFileOpenApplet": "アプレットを開く", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "スタンドアロンモードで Mii エディタアプレットを開きます", + "SettingsTabInputDirectMouseAccess": "マウス直接アクセス", + "SettingsTabSystemMemoryManagerMode": "メモリ管理モード:", + "SettingsTabSystemMemoryManagerModeSoftware": "ソフトウェア", + "SettingsTabSystemMemoryManagerModeHost": "ホスト (高速)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "ホスト, チェックなし (最高速, 安全でない)", + "SettingsTabSystemUseHypervisor": "ハイパーバイザーを使用", + "MenuBarFile": "ファイル(_F)", + "MenuBarFileOpenFromFile": "ファイルからアプリケーションをロード(_L)", + "MenuBarFileOpenUnpacked": "展開されたゲームをロード", + "MenuBarFileOpenEmuFolder": "Ryujinx フォルダを開く", + "MenuBarFileOpenLogsFolder": "ログフォルダを開く", + "MenuBarFileExit": "終了(_E)", + "MenuBarOptions": "オプション(_O)", + "MenuBarOptionsToggleFullscreen": "全画面切り替え", + "MenuBarOptionsStartGamesInFullscreen": "全画面モードでゲームを開始", + "MenuBarOptionsStopEmulation": "エミュレーションを中止", + "MenuBarOptionsSettings": "設定(_S)", + "MenuBarOptionsManageUserProfiles": "ユーザプロファイルを管理(_M)", + "MenuBarActions": "アクション(_A)", + "MenuBarOptionsSimulateWakeUpMessage": "スリープ復帰メッセージをシミュレート", + "MenuBarActionsScanAmiibo": "Amiibo をスキャン", + "MenuBarTools": "ツール(_T)", + "MenuBarToolsInstallFirmware": "ファームウェアをインストール", + "MenuBarFileToolsInstallFirmwareFromFile": "XCI または ZIP からファームウェアをインストール", + "MenuBarFileToolsInstallFirmwareFromDirectory": "ディレクトリからファームウェアをインストール", + "MenuBarToolsManageFileTypes": "ファイル形式を管理", + "MenuBarToolsInstallFileTypes": "ファイル形式をインストール", + "MenuBarToolsUninstallFileTypes": "ファイル形式をアンインストール", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "ヘルプ(_H)", + "MenuBarHelpCheckForUpdates": "アップデートを確認", + "MenuBarHelpAbout": "Ryujinx について", + "MenuSearch": "検索...", + "GameListHeaderFavorite": "お気に入り", + "GameListHeaderIcon": "アイコン", + "GameListHeaderApplication": "名称", + "GameListHeaderDeveloper": "開発元", + "GameListHeaderVersion": "バージョン", + "GameListHeaderTimePlayed": "プレイ時間", + "GameListHeaderLastPlayed": "最終プレイ日時", + "GameListHeaderFileExtension": "ファイル拡張子", + "GameListHeaderFileSize": "ファイルサイズ", + "GameListHeaderPath": "パス", + "GameListContextMenuOpenUserSaveDirectory": "セーブディレクトリを開く", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "アプリケーションのユーザセーブデータを格納するディレクトリを開きます", + "GameListContextMenuOpenDeviceSaveDirectory": "デバイスディレクトリを開く", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "アプリケーションのデバイスセーブデータを格納するディレクトリを開きます", + "GameListContextMenuOpenBcatSaveDirectory": "BCATディレクトリを開く", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "アプリケーションの BCAT セーブデータを格納するディレクトリを開きます", + "GameListContextMenuManageTitleUpdates": "アップデートを管理", + "GameListContextMenuManageTitleUpdatesToolTip": "タイトルのアップデート管理ウインドウを開きます", + "GameListContextMenuManageDlc": "DLCを管理", + "GameListContextMenuManageDlcToolTip": "DLC管理ウインドウを開きます", + "GameListContextMenuCacheManagement": "キャッシュ管理", + "GameListContextMenuCacheManagementPurgePptc": "PPTC を再構築", + "GameListContextMenuCacheManagementPurgePptcToolTip": "次回のゲーム起動時に PPTC を再構築します", + "GameListContextMenuCacheManagementPurgeShaderCache": "シェーダーキャッシュを破棄", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "アプリケーションのシェーダーキャッシュを破棄します", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC ディレクトリを開く", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "アプリケーションの PPTC キャッシュを格納するディレクトリを開きます", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "シェーダーキャッシュディレクトリを開く", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "アプリケーションのシェーダーキャッシュを格納するディレクトリを開きます", + "GameListContextMenuExtractData": "データを展開", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "現在のアプリケーション設定(アップデート含む)から ExeFS セクションを展開します", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "現在のアプリケーション設定(アップデート含む)から RomFS セクションを展開します", + "GameListContextMenuExtractDataLogo": "ロゴ", + "GameListContextMenuExtractDataLogoToolTip": "現在のアプリケーション設定(アップデート含む)からロゴセクションを展開します", + "GameListContextMenuCreateShortcut": "アプリケーションのショートカットを作成", + "GameListContextMenuCreateShortcutToolTip": "選択したアプリケーションを起動するデスクトップショートカットを作成します", + "GameListContextMenuCreateShortcutToolTipMacOS": "選択したアプリケーションを起動する ショートカットを macOS の Applications フォルダに作成します", + "GameListContextMenuOpenModsDirectory": "Modディレクトリを開く", + "GameListContextMenuOpenModsDirectoryToolTip": "アプリケーションの Mod データを格納するディレクトリを開きます", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mods ディレクトリを開く", + "GameListContextMenuOpenSdModsDirectoryToolTip": "アプリケーションの Mod データを格納する SD カードの Atmosphere ディレクトリを開きます. 実際のハードウェア用に作成された Mod データに有用です.", + "StatusBarGamesLoaded": "{0}/{1} ゲーム", + "StatusBarSystemVersion": "システムバージョン: {0}", + "LinuxVmMaxMapCountDialogTitle": "メモリマッピング上限値が小さすぎます", + "LinuxVmMaxMapCountDialogTextPrimary": "vm.max_map_count の値を {0}に増やしますか?", + "LinuxVmMaxMapCountDialogTextSecondary": "ゲームによっては, 現在許可されているサイズより大きなメモリマッピングを作成しようとすることがあります. この制限を超えると, Ryjinx はすぐにクラッシュします.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "はい, 次回再起動まで", + "LinuxVmMaxMapCountDialogButtonPersistent": "はい, 恒久的に", + "LinuxVmMaxMapCountWarningTextPrimary": "メモリマッピングの最大量が推奨値よりも小さいです.", + "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count の現在値 {0} は {1} よりも小さいです. ゲームによっては現在許可されている値よりも大きなメモリマッピングを作成しようとする場合があります. 上限を越えた場合, Ryujinx はクラッシュします.", + "Settings": "設定", + "SettingsTabGeneral": "ユーザインタフェース", + "SettingsTabGeneralGeneral": "一般", + "SettingsTabGeneralEnableDiscordRichPresence": "Discord リッチプレゼンスを有効にする", + "SettingsTabGeneralCheckUpdatesOnLaunch": "起動時にアップデートを確認する", + "SettingsTabGeneralShowConfirmExitDialog": "\"終了を確認\" ダイアログを表示する", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", + "SettingsTabGeneralHideCursor": "マウスカーソルを非表示", + "SettingsTabGeneralHideCursorNever": "決して", + "SettingsTabGeneralHideCursorOnIdle": "アイドル時", + "SettingsTabGeneralHideCursorAlways": "常時", + "SettingsTabGeneralGameDirectories": "ゲームディレクトリ", + "SettingsTabGeneralAdd": "追加", + "SettingsTabGeneralRemove": "削除", + "SettingsTabSystem": "システム", + "SettingsTabSystemCore": "コア", + "SettingsTabSystemSystemRegion": "地域:", + "SettingsTabSystemSystemRegionJapan": "日本", + "SettingsTabSystemSystemRegionUSA": "アメリカ", + "SettingsTabSystemSystemRegionEurope": "ヨーロッパ", + "SettingsTabSystemSystemRegionAustralia": "オーストラリア", + "SettingsTabSystemSystemRegionChina": "中国", + "SettingsTabSystemSystemRegionKorea": "韓国", + "SettingsTabSystemSystemRegionTaiwan": "台湾", + "SettingsTabSystemSystemLanguage": "言語:", + "SettingsTabSystemSystemLanguageJapanese": "日本語", + "SettingsTabSystemSystemLanguageAmericanEnglish": "英語(アメリカ)", + "SettingsTabSystemSystemLanguageFrench": "フランス語", + "SettingsTabSystemSystemLanguageGerman": "ドイツ語", + "SettingsTabSystemSystemLanguageItalian": "イタリア語", + "SettingsTabSystemSystemLanguageSpanish": "スペイン語", + "SettingsTabSystemSystemLanguageChinese": "中国語", + "SettingsTabSystemSystemLanguageKorean": "韓国語", + "SettingsTabSystemSystemLanguageDutch": "オランダ語", + "SettingsTabSystemSystemLanguagePortuguese": "ポルトガル語", + "SettingsTabSystemSystemLanguageRussian": "ロシア語", + "SettingsTabSystemSystemLanguageTaiwanese": "台湾語", + "SettingsTabSystemSystemLanguageBritishEnglish": "英語(イギリス)", + "SettingsTabSystemSystemLanguageCanadianFrench": "フランス語(カナダ)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "スペイン語(ラテンアメリカ)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "中国語", + "SettingsTabSystemSystemLanguageTraditionalChinese": "台湾語", + "SettingsTabSystemSystemTimeZone": "タイムゾーン:", + "SettingsTabSystemSystemTime": "時刻:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "ファイルシステム整合性チェック", + "SettingsTabSystemAudioBackend": "音声バックエンド:", + "SettingsTabSystemAudioBackendDummy": "ダミー", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "ハック", + "SettingsTabSystemHacksNote": " (挙動が不安定になる可能性があります)", + "SettingsTabSystemExpandDramSize": "DRAMサイズを6GiBに拡大する", + "SettingsTabSystemIgnoreMissingServices": "未実装サービスを無視する", + "SettingsTabGraphics": "グラフィックス", + "SettingsTabGraphicsAPI": "グラフィックスAPI", + "SettingsTabGraphicsEnableShaderCache": "シェーダーキャッシュを有効にする", + "SettingsTabGraphicsAnisotropicFiltering": "異方性フィルタリング:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "解像度:", + "SettingsTabGraphicsResolutionScaleCustom": "カスタム (非推奨)", + "SettingsTabGraphicsResolutionScaleNative": "ネイティブ (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (非推奨)", + "SettingsTabGraphicsAspectRatio": "アスペクト比:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "ウインドウサイズに合わせる", + "SettingsTabGraphicsDeveloperOptions": "開発者向けオプション", + "SettingsTabGraphicsShaderDumpPath": "グラフィックス シェーダー ダンプパス:", + "SettingsTabLogging": "ロギング", + "SettingsTabLoggingLogging": "ロギング", + "SettingsTabLoggingEnableLoggingToFile": "ファイルへのロギングを有効にする", + "SettingsTabLoggingEnableStubLogs": "Stub ログを有効にする", + "SettingsTabLoggingEnableInfoLogs": "Info ログを有効にする", + "SettingsTabLoggingEnableWarningLogs": "Warning ログを有効にする", + "SettingsTabLoggingEnableErrorLogs": "Error ログを有効にする", + "SettingsTabLoggingEnableTraceLogs": "Trace ログを有効にする", + "SettingsTabLoggingEnableGuestLogs": "Guest ログを有効にする", + "SettingsTabLoggingEnableFsAccessLogs": "Fs アクセスログを有効にする", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs グローバルアクセスログモード:", + "SettingsTabLoggingDeveloperOptions": "開発者オプション", + "SettingsTabLoggingDeveloperOptionsNote": "警告: パフォーマンスを低下させます", + "SettingsTabLoggingGraphicsBackendLogLevel": "グラフィックスバックエンド ログレベル:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "なし", + "SettingsTabLoggingGraphicsBackendLogLevelError": "エラー", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "パフォーマンス低下", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "すべて", + "SettingsTabLoggingEnableDebugLogs": "デバッグログを有効にする", + "SettingsTabInput": "入力", + "SettingsTabInputEnableDockedMode": "ドッキングモード", + "SettingsTabInputDirectKeyboardAccess": "キーボード直接アクセス", + "SettingsButtonSave": "セーブ", + "SettingsButtonClose": "閉じる", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "キャンセル", + "SettingsButtonApply": "適用", + "ControllerSettingsPlayer": "プレイヤー", + "ControllerSettingsPlayer1": "プレイヤー 1", + "ControllerSettingsPlayer2": "プレイヤー 2", + "ControllerSettingsPlayer3": "プレイヤー 3", + "ControllerSettingsPlayer4": "プレイヤー 4", + "ControllerSettingsPlayer5": "プレイヤー 5", + "ControllerSettingsPlayer6": "プレイヤー 6", + "ControllerSettingsPlayer7": "プレイヤー 7", + "ControllerSettingsPlayer8": "プレイヤー 8", + "ControllerSettingsHandheld": "携帯", + "ControllerSettingsInputDevice": "入力デバイス", + "ControllerSettingsRefresh": "更新", + "ControllerSettingsDeviceDisabled": "無効", + "ControllerSettingsControllerType": "コントローラ種別", + "ControllerSettingsControllerTypeHandheld": "携帯", + "ControllerSettingsControllerTypeProController": "Pro コントローラ", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon ペア", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon 左", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon 右", + "ControllerSettingsProfile": "プロファイル", + "ControllerSettingsProfileDefault": "デフォルト", + "ControllerSettingsLoad": "ロード", + "ControllerSettingsAdd": "追加", + "ControllerSettingsRemove": "削除", + "ControllerSettingsButtons": "ボタン", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "十字キー", + "ControllerSettingsDPadUp": "上", + "ControllerSettingsDPadDown": "下", + "ControllerSettingsDPadLeft": "左", + "ControllerSettingsDPadRight": "右", + "ControllerSettingsStickButton": "ボタン", + "ControllerSettingsStickUp": "上", + "ControllerSettingsStickDown": "下", + "ControllerSettingsStickLeft": "左", + "ControllerSettingsStickRight": "右", + "ControllerSettingsStickStick": "スティック", + "ControllerSettingsStickInvertXAxis": "X軸を反転", + "ControllerSettingsStickInvertYAxis": "Y軸を反転", + "ControllerSettingsStickDeadzone": "遊び:", + "ControllerSettingsLStick": "左スティック", + "ControllerSettingsRStick": "右スティック", + "ControllerSettingsTriggersLeft": "左トリガー", + "ControllerSettingsTriggersRight": "右トリガー", + "ControllerSettingsTriggersButtonsLeft": "左トリガーボタン", + "ControllerSettingsTriggersButtonsRight": "右トリガーボタン", + "ControllerSettingsTriggers": "トリガー", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "左ボタン", + "ControllerSettingsExtraButtonsRight": "右ボタン", + "ControllerSettingsMisc": "その他", + "ControllerSettingsTriggerThreshold": "トリガーしきい値:", + "ControllerSettingsMotion": "モーション", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook 互換モーションを使用", + "ControllerSettingsMotionControllerSlot": "コントローラ スロット:", + "ControllerSettingsMotionMirrorInput": "入力反転", + "ControllerSettingsMotionRightJoyConSlot": "JoyCon 右 スロット:", + "ControllerSettingsMotionServerHost": "サーバ:", + "ControllerSettingsMotionGyroSensitivity": "ジャイロ感度:", + "ControllerSettingsMotionGyroDeadzone": "ジャイロ遊び:", + "ControllerSettingsSave": "セーブ", + "ControllerSettingsClose": "閉じる", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", + "UserProfilesSelectedUserProfile": "選択されたユーザプロファイル:", + "UserProfilesSaveProfileName": "プロファイル名をセーブ", + "UserProfilesChangeProfileImage": "プロファイル画像を変更", + "UserProfilesAvailableUserProfiles": "利用可能なユーザプロファイル:", + "UserProfilesAddNewProfile": "プロファイルを作成", + "UserProfilesDelete": "削除", + "UserProfilesClose": "閉じる", + "ProfileNameSelectionWatermark": "ニックネームを選択", + "ProfileImageSelectionTitle": "プロファイル画像選択", + "ProfileImageSelectionHeader": "プロファイル画像を選択", + "ProfileImageSelectionNote": "カスタム画像をインポート, またはファームウェア内のアバターを選択できます", + "ProfileImageSelectionImportImage": "画像ファイルをインポート", + "ProfileImageSelectionSelectAvatar": "ファームウェア内のアバターを選択", + "InputDialogTitle": "入力ダイアログ", + "InputDialogOk": "OK", + "InputDialogCancel": "キャンセル", + "InputDialogAddNewProfileTitle": "プロファイル名を選択", + "InputDialogAddNewProfileHeader": "プロファイル名を入力してください", + "InputDialogAddNewProfileSubtext": "(最大長: {0})", + "AvatarChoose": "選択", + "AvatarSetBackgroundColor": "背景色を指定", + "AvatarClose": "閉じる", + "ControllerSettingsLoadProfileToolTip": "プロファイルをロード", + "ControllerSettingsAddProfileToolTip": "プロファイルを追加", + "ControllerSettingsRemoveProfileToolTip": "プロファイルを削除", + "ControllerSettingsSaveProfileToolTip": "プロファイルをセーブ", + "MenuBarFileToolsTakeScreenshot": "スクリーンショットを撮影", + "MenuBarFileToolsHideUi": "UIを隠す", + "GameListContextMenuRunApplication": "アプリケーションを実行", + "GameListContextMenuToggleFavorite": "お気に入りを切り替え", + "GameListContextMenuToggleFavoriteToolTip": "ゲームをお気に入りに含めるかどうかを切り替えます", + "SettingsTabGeneralTheme": "テーマ:", + "SettingsTabGeneralThemeDark": "ダーク", + "SettingsTabGeneralThemeLight": "ライト", + "ControllerSettingsConfigureGeneral": "設定", + "ControllerSettingsRumble": "振動", + "ControllerSettingsRumbleStrongMultiplier": "強振動の補正値", + "ControllerSettingsRumbleWeakMultiplier": "弱振動の補正値", + "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}] のセーブデータはありません", + "DialogMessageSaveNotAvailableCreateSaveMessage": "このゲームのセーブデータを作成してよろしいですか?", + "DialogConfirmationTitle": "Ryujinx - 確認", + "DialogUpdaterTitle": "Ryujinx - アップデータ", + "DialogErrorTitle": "Ryujinx - エラー", + "DialogWarningTitle": "Ryujinx - 警告", + "DialogExitTitle": "Ryujinx - 終了", + "DialogErrorMessage": "エラーが発生しました", + "DialogExitMessage": "Ryujinx を閉じてよろしいですか?", + "DialogExitSubMessage": "セーブされていないデータはすべて失われます!", + "DialogMessageCreateSaveErrorMessage": "セーブデータ: {0} の作成中にエラーが発生しました", + "DialogMessageFindSaveErrorMessage": "セーブデータ: {0} の検索中にエラーが発生しました", + "FolderDialogExtractTitle": "展開フォルダを選択", + "DialogNcaExtractionMessage": "{1} から {0} セクションを展開中...", + "DialogNcaExtractionTitle": "Ryujinx - NCA セクション展開", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "展開に失敗しました. 選択されたファイルにはメイン NCA が存在しません.", + "DialogNcaExtractionCheckLogErrorMessage": "展開に失敗しました. 詳細はログを確認してください.", + "DialogNcaExtractionSuccessMessage": "展開が正常終了しました", + "DialogUpdaterConvertFailedMessage": "現在の Ryujinx バージョンの変換に失敗しました.", + "DialogUpdaterCancelUpdateMessage": "アップデータをキャンセル中!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "最新バージョンの Ryujinx を使用中です!", + "DialogUpdaterFailedToGetVersionMessage": "Github からのリリース情報取得時にエラーが発生しました. Github Actions でリリースファイルを作成中かもしれません. 後ほどもう一度試してみてください.", + "DialogUpdaterConvertFailedGithubMessage": "Github から取得した Ryujinx バージョンの変換に失敗しました.", + "DialogUpdaterDownloadingMessage": "アップデートをダウンロード中...", + "DialogUpdaterExtractionMessage": "アップデートを展開中...", + "DialogUpdaterRenamingMessage": "アップデートをリネーム中...", + "DialogUpdaterAddingFilesMessage": "新規アップデートを追加中...", + "DialogUpdaterCompleteMessage": "アップデート完了!", + "DialogUpdaterRestartMessage": "すぐに Ryujinx を再起動しますか?", + "DialogUpdaterNoInternetMessage": "インターネットに接続されていません!", + "DialogUpdaterNoInternetSubMessage": "インターネット接続が正常動作しているか確認してください!", + "DialogUpdaterDirtyBuildMessage": "Dirty ビルドの Ryujinx はアップデートできません!", + "DialogUpdaterDirtyBuildSubMessage": "サポートされているバージョンをお探しなら, https://ryujinx.org/ で Ryujinx をダウンロードしてください.", + "DialogRestartRequiredMessage": "再起動が必要", + "DialogThemeRestartMessage": "テーマがセーブされました. テーマを適用するには再起動が必要です.", + "DialogThemeRestartSubMessage": "再起動しますか", + "DialogFirmwareInstallEmbeddedMessage": "このゲームに含まれるファームウェアをインストールしてよろしいですか? (ファームウェア {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "ファームウェアがインストールされていませんが, ゲームに含まれるファームウェア {0} をインストールできます.\nエミュレータが開始します.", + "DialogFirmwareNoFirmwareInstalledMessage": "ファームウェアがインストールされていません", + "DialogFirmwareInstalledMessage": "ファームウェア {0} がインストールされました", + "DialogInstallFileTypesSuccessMessage": "ファイル形式のインストールに成功しました!", + "DialogInstallFileTypesErrorMessage": "ファイル形式のインストールに失敗しました.", + "DialogUninstallFileTypesSuccessMessage": "ファイル形式のアンインストールに成功しました!", + "DialogUninstallFileTypesErrorMessage": "ファイル形式のアンインストールに失敗しました.", + "DialogOpenSettingsWindowLabel": "設定ウインドウを開く", + "DialogControllerAppletTitle": "コントローラアプレット", + "DialogMessageDialogErrorExceptionMessage": "メッセージダイアログ表示エラー: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "ソフトウェアキーボード表示エラー: {0}", + "DialogErrorAppletErrorExceptionMessage": "エラーアプレットダイアログ表示エラー: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nこのエラーへの対処方法については, セットアップガイドを参照してください.", + "DialogUserErrorDialogTitle": "Ryujinx エラー ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "API からの情報取得中にエラーが発生しました.", + "DialogAmiiboApiConnectErrorMessage": "Amiibo API サーバに接続できませんでした. サーバがダウンしているか, インターネット接続に問題があるかもしれません.", + "DialogProfileInvalidProfileErrorMessage": "プロファイル {0} は現在の入力設定システムと互換性がありません.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "デフォルトのプロファイルは上書きできません", + "DialogProfileDeleteProfileTitle": "プロファイルを削除中", + "DialogProfileDeleteProfileMessage": "このアクションは元に戻せません. 本当に続けてよろしいですか?", + "DialogWarning": "警告", + "DialogPPTCDeletionMessage": "次回起動時に PPTC を再構築します:\n\n{0}\n\n実行してよろしいですか?", + "DialogPPTCDeletionErrorMessage": "PPTC キャッシュ破棄エラー {0}: {1}", + "DialogShaderDeletionMessage": "シェーダーキャッシュを破棄しようとしています:\n\n{0}\n\n実行してよろしいですか?", + "DialogShaderDeletionErrorMessage": "シェーダーキャッシュ破棄エラー {0}: {1}", + "DialogRyujinxErrorMessage": "エラーが発生しました", + "DialogInvalidTitleIdErrorMessage": "UI エラー: 選択されたゲームは有効なタイトル ID を保持していません", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0} には有効なシステムファームウェアがありません.", + "DialogFirmwareInstallerFirmwareInstallTitle": "ファームウェア {0} をインストール", + "DialogFirmwareInstallerFirmwareInstallMessage": "システムバージョン {0} がインストールされます.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n現在のシステムバージョン {0} を置き換えます.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n続けてよろしいですか?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "ファームウェアをインストール中...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "システムバージョン {0} が正常にインストールされました.", + "DialogUserProfileDeletionWarningMessage": "選択されたプロファイルを削除すると,プロファイルがひとつも存在しなくなります", + "DialogUserProfileDeletionConfirmMessage": "選択されたプロファイルを削除しますか", + "DialogUserProfileUnsavedChangesTitle": "警告 - 保存されていない変更", + "DialogUserProfileUnsavedChangesMessage": "保存されていないユーザプロファイルを変更しました.", + "DialogUserProfileUnsavedChangesSubMessage": "変更を破棄しますか?", + "DialogControllerSettingsModifiedConfirmMessage": "現在のコントローラ設定が更新されました.", + "DialogControllerSettingsModifiedConfirmSubMessage": "セーブしますか?", + "DialogLoadFileErrorMessage": "{0}. エラー発生ファイル: {1}", + "DialogModAlreadyExistsMessage": "Modはすでに存在します", + "DialogModInvalidMessage": "指定したディレクトリにはmodが含まれていません!", + "DialogModDeleteNoParentMessage": "削除に失敗しました: Mod \"{0}\" の親ディレクトリが見つかりませんでした!", + "DialogDlcNoDlcErrorMessage": "選択されたファイルはこのタイトル用の DLC ではありません!", + "DialogPerformanceCheckLoggingEnabledMessage": "トレースロギングを有効にします. これは開発者のみに有用な機能です.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "パフォーマンス最適化のためには,トレースロギングを無効にすることを推奨します. トレースロギングを無効にしてよろしいですか?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "シェーダーダンプを有効にします. これは開発者のみに有用な機能です.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "パフォーマンス最適化のためには, シェーダーダンプを無効にすることを推奨します. シェーダーダンプを無効にしてよろしいですか?", + "DialogLoadAppGameAlreadyLoadedMessage": "ゲームはすでにロード済みです", + "DialogLoadAppGameAlreadyLoadedSubMessage": "別のゲームを起動する前に, エミュレーションを中止またはエミュレータを閉じてください.", + "DialogUpdateAddUpdateErrorMessage": "選択されたファイルはこのタイトル用のアップデートではありません!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - バックエンドスレッディング", + "DialogSettingsBackendThreadingWarningMessage": "このオプションの変更を完全に適用するには Ryujinx の再起動が必要です. プラットフォームによっては, Ryujinx のものを使用する前に手動でドライバ自身のマルチスレッディングを無効にする必要があるかもしれません.", + "DialogModManagerDeletionWarningMessage": "以下のModを削除しようとしています: {0}\n\n続行してもよろしいですか?", + "DialogModManagerDeletionAllWarningMessage": "このタイトルの Mod をすべて削除しようとしています.\n\n続行してもよろしいですか?", + "SettingsTabGraphicsFeaturesOptions": "機能", + "SettingsTabGraphicsBackendMultithreading": "グラフィックスバックエンドのマルチスレッド実行:", + "CommonAuto": "自動", + "CommonOff": "オフ", + "CommonOn": "オン", + "InputDialogYes": "はい", + "InputDialogNo": "いいえ", + "DialogProfileInvalidProfileNameErrorMessage": "プロファイル名に無効な文字が含まれています. 再度試してみてください.", + "MenuBarOptionsPauseEmulation": "一時停止", + "MenuBarOptionsResumeEmulation": "再開", + "AboutUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx のウェブサイトを開きます.", + "AboutDisclaimerMessage": "Ryujinx は Nintendo™ および\nそのパートナー企業とは一切関係ありません.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) は\nAmiibo エミュレーションに使用されています.", + "AboutPatreonUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Patreon ページを開きます.", + "AboutGithubUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Github ページを開きます.", + "AboutDiscordUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Discord サーバを開きます.", + "AboutTwitterUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Twitter ページを開きます.", + "AboutRyujinxAboutTitle": "Ryujinx について:", + "AboutRyujinxAboutContent": "Ryujinx は Nintendo Switch™ のエミュレータです.\nPatreon で私達の活動を支援してください.\n最新の情報は Twitter または Discord から取得できます.\n貢献したい開発者の方は GitHub または Discord で詳細をご確認ください.", + "AboutRyujinxMaintainersTitle": "開発者:", + "AboutRyujinxMaintainersContentTooltipMessage": "クリックするとデフォルトのブラウザで 貢献者のページを開きます.", + "AboutRyujinxSupprtersTitle": "Patreon での支援者:", + "AmiiboSeriesLabel": "Amiibo シリーズ", + "AmiiboCharacterLabel": "キャラクタ", + "AmiiboScanButtonLabel": "スキャン", + "AmiiboOptionsShowAllLabel": "すべての Amiibo を表示", + "AmiiboOptionsUsRandomTagLabel": "ハック: ランダムな Uuid を使用", + "DlcManagerTableHeadingEnabledLabel": "有効", + "DlcManagerTableHeadingTitleIdLabel": "タイトルID", + "DlcManagerTableHeadingContainerPathLabel": "コンテナパス", + "DlcManagerTableHeadingFullPathLabel": "フルパス", + "DlcManagerRemoveAllButton": "すべて削除", + "DlcManagerEnableAllButton": "すべて有効", + "DlcManagerDisableAllButton": "すべて無効", + "ModManagerDeleteAllButton": "すべて削除", + "MenuBarOptionsChangeLanguage": "言語を変更", + "MenuBarShowFileTypes": "ファイル形式を表示", + "CommonSort": "並べ替え", + "CommonShowNames": "名称を表示", + "CommonFavorite": "お気に入り", + "OrderAscending": "昇順", + "OrderDescending": "降順", + "SettingsTabGraphicsFeatures": "機能", + "ErrorWindowTitle": "エラーウインドウ", + "ToggleDiscordTooltip": "Discord の \"現在プレイ中\" アクティビティに Ryujinx を表示するかどうかを選択します", + "AddGameDirBoxTooltip": "リストに追加するゲームディレクトリを入力します", + "AddGameDirTooltip": "リストにゲームディレクトリを追加します", + "RemoveGameDirTooltip": "選択したゲームディレクトリを削除します", + "CustomThemeCheckTooltip": "エミュレータのメニュー外観を変更するためカスタム Avalonia テーマを使用します", + "CustomThemePathTooltip": "カスタム GUI テーマのパスです", + "CustomThemeBrowseTooltip": "カスタム GUI テーマを参照します", + "DockModeToggleTooltip": "有効にすると,ドッキングされた Nintendo Switch をエミュレートします.多くのゲームではグラフィックス品質が向上します.\n無効にすると,携帯モードの Nintendo Switch をエミュレートします.グラフィックスの品質は低下します.\n\nドッキングモード有効ならプレイヤー1の,無効なら携帯の入力を設定してください.\n\nよくわからない場合はオンのままにしてください.", + "DirectKeyboardTooltip": "直接キーボード アクセス (HID) のサポートです. テキスト入力デバイスとしてキーボードへのゲームアクセスを提供します.\n\nSwitchハードウェアでキーボードの使用をネイティブにサポートしているゲームでのみ動作します.\n\nわからない場合はオフのままにしてください.", + "DirectMouseTooltip": "直接マウスアクセス (HID) のサポートです. ポインティングデバイスとしてマウスへのゲームアクセスを提供します.\n\nSwitchハードウェアでマウスの使用をネイティブにサポートしているゲームでのみ動作します.\n\n有効にしている場合, タッチスクリーン機能は動作しない場合があります.\n\nわからない場合はオフのままにしてください.", + "RegionTooltip": "システムの地域を変更します", + "LanguageTooltip": "システムの言語を変更します", + "TimezoneTooltip": "システムのタイムゾーンを変更します", + "TimeTooltip": "システムの時刻を変更します", + "VSyncToggleTooltip": "エミュレートされたゲーム機の垂直同期です. 多くのゲームにおいて, フレームリミッタとして機能します. 無効にすると, ゲームが高速で実行されたり, ロード中に時間がかかったり, 止まったりすることがあります.\n\n設定したホットキー(デフォルトではF1)で, ゲーム内で切り替え可能です. 無効にする場合は, この操作を行うことをおすすめします.\n\nよくわからない場合はオンのままにしてください.", + "PptcToggleTooltip": "翻訳されたJIT関数をセーブすることで, ゲームをロードするたびに毎回翻訳する処理を不要とします.\n\n一度ゲームを起動すれば,二度目以降の起動時遅延を大きく軽減できます.\n\nよくわからない場合はオンのままにしてください.", + "FsIntegrityToggleTooltip": "ゲーム起動時にファイル破損をチェックし,破損が検出されたらログにハッシュエラーを表示します..\n\nパフォーマンスには影響なく, トラブルシューティングに役立ちます.\n\nよくわからない場合はオンのままにしてください.", + "AudioBackendTooltip": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL2 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL2 を設定してください.", + "MemoryManagerTooltip": "ゲストメモリのマップ/アクセス方式を変更します. エミュレートされるCPUのパフォーマンスに大きな影響を与えます.\n\nよくわからない場合は「ホスト,チェックなし」を設定してください.", + "MemoryManagerSoftwareTooltip": "アドレス変換にソフトウェアページテーブルを使用します. 非常に正確ですがパフォーマンスが大きく低下します.", + "MemoryManagerHostTooltip": "ホストのアドレス空間にメモリを直接マップします.JITのコンパイルと実行速度が大きく向上します.", + "MemoryManagerUnsafeTooltip": "メモリを直接マップしますが, アクセス前にゲストのアドレス空間内のアドレスをマスクしません. より高速になりますが, 安全性が犠牲になります. ゲストアプリケーションは Ryujinx のどこからでもメモリにアクセスできるので,このモードでは信頼できるプログラムだけを実行するようにしてください.", + "UseHypervisorTooltip": "JIT の代わりにハイパーバイザーを使用します. 利用可能な場合, パフォーマンスが大幅に向上しますが, 現在の状態では不安定になる可能性があります.", + "DRamTooltip": "エミュレートされたシステムのメモリ容量を 4GiB から 6GiB に増加します.\n\n高解像度のテクスチャパックや 4K解像度の mod を使用する場合に有用です. パフォーマンスを改善するものではありません.\n\nよくわからない場合はオフのままにしてください.", + "IgnoreMissingServicesTooltip": "未実装の Horizon OS サービスを無視します. 特定のゲームにおいて起動時のクラッシュを回避できる場合があります.\n\nよくわからない場合はオフのままにしてください.", + "GraphicsBackendThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", + "GalThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", + "ShaderCacheToggleTooltip": "ディスクシェーダーキャッシュをセーブし,次回以降の実行時遅延を軽減します.\n\nよくわからない場合はオンのままにしてください.", + "ResolutionScaleTooltip": "ゲームのレンダリング解像度倍率を設定します.\n\n解像度を上げてもピクセルのように見えるゲームもあります. そのようなゲームでは, アンチエイリアスを削除するか, 内部レンダリング解像度を上げる mod を見つける必要があるかもしれません. その場合, ようなゲームでは、ネイティブを選択してください.\n\nこのオプションはゲーム実行中に下の「適用」をクリックすることで変更できます. 設定ウィンドウを脇に移動して, ゲームが好みの表示になるよう試してみてください.\n\nどのような設定でも, \"4x\" はやり過ぎであることを覚えておいてください.", + "ResolutionScaleEntryTooltip": "1.5 のような整数でない倍率を指定すると,問題が発生したりクラッシュしたりする場合があります.", + "AnisotropyTooltip": "異方性フィルタリングのレベルです. ゲームが要求する値を使用する場合は「自動」を設定してください.", + "AspectRatioTooltip": "レンダリングウインドウに適用するアスペクト比です.\n\nゲームにアスペクト比を変更する mod を使用している場合のみ変更してください.\n\nわからない場合は16:9のままにしておいてください.\n", + "ShaderDumpPathTooltip": "グラフィックス シェーダー ダンプのパスです", + "FileLogTooltip": "コンソール出力されるログをディスク上のログファイルにセーブします. パフォーマンスには影響を与えません.", + "StubLogTooltip": "stub ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "InfoLogTooltip": "info ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "WarnLogTooltip": "warning ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "ErrorLogTooltip": "error ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "TraceLogTooltip": "trace ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "GuestLogTooltip": "guest ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "FileAccessLogTooltip": "ファイルアクセスログメッセージをコンソールに出力します.", + "FSAccessLogModeTooltip": "コンソールへのファイルシステムアクセスログ出力を有効にします.0-3 のモードが有効です", + "DeveloperOptionTooltip": "使用上の注意", + "OpenGlLogLevel": "適切なログレベルを有効にする必要があります", + "DebugLogTooltip": "デバッグログメッセージをコンソールに出力します.\n\nログが読みづらくなり,エミュレータのパフォーマンスが低下するため,開発者から特別な指示がある場合のみ使用してください.", + "LoadApplicationFileTooltip": "ロードする Switch 互換のファイルを選択するためファイルエクスプローラを開きます", + "LoadApplicationFolderTooltip": "ロードする Switch 互換の展開済みアプリケーションを選択するためファイルエクスプローラを開きます", + "OpenRyujinxFolderTooltip": "Ryujinx ファイルシステムフォルダを開きます", + "OpenRyujinxLogsTooltip": "ログが格納されるフォルダを開きます", + "ExitTooltip": "Ryujinx を終了します", + "OpenSettingsTooltip": "設定ウインドウを開きます", + "OpenProfileManagerTooltip": "ユーザプロファイル管理ウインドウを開きます", + "StopEmulationTooltip": "ゲームのエミュレーションを中止してゲーム選択画面に戻ります", + "CheckUpdatesTooltip": "Ryujinx のアップデートを確認します", + "OpenAboutTooltip": "Ryujinx についてのウインドウを開きます", + "GridSize": "グリッドサイズ", + "GridSizeTooltip": "グリッドサイズを変更します", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "ポルトガル語(ブラジル)", + "AboutRyujinxContributorsButtonHeader": "すべての貢献者を確認", + "SettingsTabSystemAudioVolume": "音量: ", + "AudioVolumeTooltip": "音量を変更します", + "SettingsTabSystemEnableInternetAccess": "ゲストインターネットアクセス / LAN モード", + "EnableInternetAccessTooltip": "エミュレートしたアプリケーションをインターネットに接続できるようにします.\n\nLAN モードを持つゲーム同士は,この機能を有効にして同じアクセスポイントに接続すると接続できます. 実機も含まれます.\n\n任天堂のサーバーには接続できません. インターネットに接続しようとすると,特定のゲームでクラッシュすることがあります.\n\nよくわからない場合はオフのままにしてください.", + "GameListContextMenuManageCheatToolTip": "チートを管理します", + "GameListContextMenuManageCheat": "チートを管理", + "GameListContextMenuManageModToolTip": "Modを管理します", + "GameListContextMenuManageMod": "Manage Mods", + "ControllerSettingsStickRange": "範囲:", + "DialogStopEmulationTitle": "Ryujinx - エミュレーションを中止", + "DialogStopEmulationMessage": "エミュレーションを中止してよろしいですか?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "音声", + "SettingsTabNetwork": "ネットワーク", + "SettingsTabNetworkConnection": "ネットワーク接続", + "SettingsTabCpuCache": "CPU キャッシュ", + "SettingsTabCpuMemory": "CPU メモリ", + "DialogUpdaterFlatpakNotSupportedMessage": "FlatHub を使用して Ryujinx をアップデートしてください.", + "UpdaterDisabledWarningTitle": "アップデータは無効です!", + "ControllerSettingsRotate90": "時計回りに 90° 回転", + "IconSize": "アイコンサイズ", + "IconSizeTooltip": "ゲームアイコンのサイズを変更します", + "MenuBarOptionsShowConsole": "コンソールを表示", + "ShaderCachePurgeError": "シェーダーキャッシュの破棄エラー {0}: {1}", + "UserErrorNoKeys": "Keys がありません", + "UserErrorNoFirmware": "ファームウェアがありません", + "UserErrorFirmwareParsingFailed": "ファームウェアのパーズエラー", + "UserErrorApplicationNotFound": "アプリケーションがありません", + "UserErrorUnknown": "不明なエラー", + "UserErrorUndefined": "未定義エラー", + "UserErrorNoKeysDescription": "'prod.keys' が見つかりませんでした", + "UserErrorNoFirmwareDescription": "インストールされたファームウェアが見つかりませんでした", + "UserErrorFirmwareParsingFailedDescription": "ファームウェアをパーズできませんでした.通常,古いキーが原因です.", + "UserErrorApplicationNotFoundDescription": "指定されたパスに有効なアプリケーションがありませんでした.", + "UserErrorUnknownDescription": "不明なエラーが発生しました!", + "UserErrorUndefinedDescription": "未定義のエラーが発生しました! 発生すべきものではないので,開発者にご連絡ください!", + "OpenSetupGuideMessage": "セットアップガイドを開く", + "NoUpdate": "アップデートなし", + "TitleUpdateVersionLabel": "バージョン {0} - {1}", + "RyujinxInfo": "Ryujinx - 情報", + "RyujinxConfirm": "Ryujinx - 確認", + "FileDialogAllTypes": "すべての種別", + "Never": "決して", + "SwkbdMinCharacters": "最低 {0} 文字必要です", + "SwkbdMinRangeCharacters": "{0}-{1} 文字にしてください", + "SoftwareKeyboard": "ソフトウェアキーボード", + "SoftwareKeyboardModeNumeric": "0-9 または '.' のみでなければなりません", + "SoftwareKeyboardModeAlphabet": "CJK文字以外のみ", + "SoftwareKeyboardModeASCII": "ASCII文字列のみ", + "ControllerAppletControllers": "サポートされているコントローラ:", + "ControllerAppletPlayers": "プレイヤー:", + "ControllerAppletDescription": "現在の設定は無効です. 設定を開いて入力を再設定してください.", + "ControllerAppletDocked": "ドッキングモードが設定されています. 携帯コントロールは無効にする必要があります.", + "UpdaterRenaming": "古いファイルをリネーム中...", + "UpdaterRenameFailed": "ファイルをリネームできませんでした: {0}", + "UpdaterAddingFiles": "新規ファイルを追加中...", + "UpdaterExtracting": "アップデートを展開中...", + "UpdaterDownloading": "アップデートをダウンロード中...", + "Game": "ゲーム", + "Docked": "ドッキング", + "Handheld": "携帯", + "ConnectionError": "接続エラー.", + "AboutPageDeveloperListMore": "{0}, その他大勢...", + "ApiError": "API エラー.", + "LoadingHeading": "ロード中: {0}", + "CompilingPPTC": "PTC をコンパイル中", + "CompilingShaders": "シェーダーをコンパイル中", + "AllKeyboards": "すべてのキーボード", + "OpenFileDialogTitle": "開くファイルを選択", + "OpenFolderDialogTitle": "展開されたゲームフォルダを選択", + "AllSupportedFormats": "すべての対応フォーマット", + "RyujinxUpdater": "Ryujinx アップデータ", + "SettingsTabHotkeys": "キーボード ホットキー", + "SettingsTabHotkeysHotkeys": "キーボード ホットキー", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync 切り替え:", + "SettingsTabHotkeysScreenshotHotkey": "スクリーンショット:", + "SettingsTabHotkeysShowUiHotkey": "UI表示:", + "SettingsTabHotkeysPauseHotkey": "一時停止:", + "SettingsTabHotkeysToggleMuteHotkey": "ミュート:", + "ControllerMotionTitle": "モーションコントロール設定", + "ControllerRumbleTitle": "振動設定", + "SettingsSelectThemeFileDialogTitle": "テーマファイルを選択", + "SettingsXamlThemeFile": "Xaml テーマファイル", + "AvatarWindowTitle": "アカウント - アバター管理", + "Amiibo": "Amiibo", + "Unknown": "不明", + "Usage": "使用法", + "Writable": "書き込み可能", + "SelectDlcDialogTitle": "DLC ファイルを選択", + "SelectUpdateDialogTitle": "アップデートファイルを選択", + "SelectModDialogTitle": "modディレクトリを選択", + "UserProfileWindowTitle": "ユーザプロファイルを管理", + "CheatWindowTitle": "チート管理", + "DlcWindowTitle": "DLC 管理", + "ModWindowTitle": "Manage Mods for {0} ({1})", + "UpdateWindowTitle": "アップデート管理", + "CheatWindowHeading": "利用可能なチート {0} [{1}]", + "BuildId": "ビルドID:", + "DlcWindowHeading": "利用可能な DLC {0} [{1}]", + "ModWindowHeading": "{0} Mod(s)", + "UserProfilesEditProfile": "編集", + "Cancel": "キャンセル", + "Save": "セーブ", + "Discard": "破棄", + "Paused": "一時停止中", + "UserProfilesSetProfileImage": "プロファイル画像を設定", + "UserProfileEmptyNameError": "名称が必要です", + "UserProfileNoImageError": "プロファイル画像が必要です", + "GameUpdateWindowHeading": "利用可能なアップデート {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "解像度を上げる:", + "SettingsTabHotkeysResScaleDownHotkey": "解像度を下げる:", + "UserProfilesName": "名称:", + "UserProfilesUserId": "ユーザID:", + "SettingsTabGraphicsBackend": "グラフィックスバックエンド", + "SettingsTabGraphicsBackendTooltip": "エミュレーションに使用するグラフィックスバックエンドを選択します.\n\nVulkanは, 最近のグラフィックカードでドライバが最新であれば, 全体的に優れています. すべてのGPUベンダーで, シェーダーコンパイルがより高速で, スタッタリングが少ないのが特徴です.\n\n古いNvidia GPU, Linuxでの古いAMD GPU, VRAMの少ないGPUなどでは, OpenGLの方が良い結果が得られるかもしれません. ですが, シェーダーコンパイルのスタッターは大きくなります.\n\n不明な場合はVulkanに設定してください。最新のグラフィックドライバでもVulkanをサポートしていないGPUの場合は, OpenGLに設定してください.", + "SettingsEnableTextureRecompression": "テクスチャの再圧縮を有効にする", + "SettingsEnableTextureRecompressionTooltip": "VRAM使用量を減らすためにASTCテクスチャを圧縮します.\n\nこのテクスチャフォーマットを使用するゲームには, Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder, The Legend of Zelda: Tears of the Kingdomが含まれます.\n\nVRAMが4GB以下のグラフィックカードでは, これらのゲームを実行中にクラッシュする可能性があります.\n\n前述のゲームでVRAMが不足している場合のみ有効にしてください. 不明な場合はオフにしてください.", + "SettingsTabGraphicsPreferredGpu": "優先使用するGPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Vulkanグラフィックスバックエンドで使用されるグラフィックスカードを選択します.\n\nOpenGLが使用するGPUには影響しません.\n\n不明な場合は, \"dGPU\" としてフラグが立っているGPUに設定します. ない場合はそのままにします.", + "SettingsAppRequiredRestartMessage": "Ryujinx の再起動が必要です", + "SettingsGpuBackendRestartMessage": "グラフィックスバックエンドまたはGPUの設定が変更されました. 変更を適用するには再起動する必要があります", + "SettingsGpuBackendRestartSubMessage": "今すぐ再起動しますか?", + "RyujinxUpdaterMessage": "Ryujinx を最新版にアップデートしますか?", + "SettingsTabHotkeysVolumeUpHotkey": "音量を上げる:", + "SettingsTabHotkeysVolumeDownHotkey": "音量を下げる:", + "SettingsEnableMacroHLE": "マクロの高レベルエミュレーション (HLE) を有効にする", + "SettingsEnableMacroHLETooltip": "GPU マクロコードの高レベルエミュレーションです.\n\nパフォーマンスを向上させますが, 一部のゲームでグラフィックに不具合が発生する可能性があります.\n\nよくわからない場合はオンのままにしてください.", + "SettingsEnableColorSpacePassthrough": "色空間をパススルー", + "SettingsEnableColorSpacePassthroughTooltip": "Vulkan バックエンドに対して, 色空間を指定せずに色情報を渡します. 高色域ディスプレイを使用する場合, 正確ではないですがより鮮やかな色になる可能性があります.", + "VolumeShort": "音量", + "UserProfilesManageSaves": "セーブデータの管理", + "DeleteUserSave": "このゲームのユーザセーブデータを削除しますか?", + "IrreversibleActionNote": "この操作は元に戻せません.", + "SaveManagerHeading": "{0} のセーブデータを管理", + "SaveManagerTitle": "セーブデータマネージャ", + "Name": "名称", + "Size": "サイズ", + "Search": "検索", + "UserProfilesRecoverLostAccounts": "アカウントの復旧", + "Recover": "復旧", + "UserProfilesRecoverHeading": "以下のアカウントのセーブデータが見つかりました", + "UserProfilesRecoverEmptyList": "復元するプロファイルはありません", + "GraphicsAATooltip": "ゲームレンダリングにアンチエイリアスを適用します.\n\nFXAAは画像の大部分をぼかし, SMAAはギザギザのエッジを見つけて滑らかにします.\n\nFSRスケーリングフィルタとの併用は推奨しません.\n\nこのオプションは, ゲーム実行中に下の「適用」をクリックして変更できます. 設定ウィンドウを脇に移動し, ゲームが好みの表示になるように試してみてください.\n\n不明な場合は「なし」のままにしておいてください.", + "GraphicsAALabel": "アンチエイリアス:", + "GraphicsScalingFilterLabel": "スケーリングフィルタ:", + "GraphicsScalingFilterTooltip": "解像度変更時に適用されるスケーリングフィルタを選択します.\n\nBilinearは3Dゲームに適しており, 安全なデフォルトオプションです.\n\nピクセルアートゲームにはNearestを推奨します.\n\nFSR 1.0は単なるシャープニングフィルタであり, FXAAやSMAAとの併用は推奨されません.\n\nこのオプションは, ゲーム実行中に下の「適用」をクリックすることで変更できます. 設定ウィンドウを脇に移動し, ゲームが好みの表示になるように試してみてください.\n\n不明な場合はBilinearのままにしておいてください.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "レベル", + "GraphicsScalingFilterLevelTooltip": "FSR 1.0のシャープ化レベルを設定します. 高い値ほどシャープになります.", + "SmaaLow": "SMAA Low", + "SmaaMedium": "SMAA Medium", + "SmaaHigh": "SMAA High", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "ユーザを編集", + "UserEditorTitleCreate": "ユーザを作成", + "SettingsTabNetworkInterface": "ネットワークインタフェース:", + "NetworkInterfaceTooltip": "LAN/LDN機能に使用されるネットワークインタフェースです.\n\nVPNやXLink Kai、LAN対応のゲームと併用することで, インターネット上の同一ネットワーク接続になりすますことができます.\n\n不明な場合はデフォルトのままにしてください.", + "NetworkInterfaceDefault": "デフォルト", + "PackagingShaders": "シェーダーを構築中", + "AboutChangelogButton": "GitHub で更新履歴を表示", + "AboutChangelogButtonTooltipMessage": "クリックして, このバージョンの更新履歴をデフォルトのブラウザで開きます.", + "SettingsTabNetworkMultiplayer": "マルチプレイヤー", + "MultiplayerMode": "モード:", + "MultiplayerModeTooltip": "LDNマルチプレイヤーモードを変更します.\n\nldn_mitmモジュールがインストールされた, 他のRyujinxインスタンスや,ハックされたNintendo Switchコンソールとのローカル/同一ネットワーク接続を可能にします.\n\nマルチプレイでは, すべてのプレイヤーが同じゲームバージョンである必要があります(例:Super Smash Bros. Ultimate v13.0.1はv13.0.0に接続できません).\n\n不明な場合は「無効」のままにしてください.", + "MultiplayerModeDisabled": "無効", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json new file mode 100644 index 00000000..a92d084e --- /dev/null +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -0,0 +1,780 @@ +{ + "Language": "한국어", + "MenuBarFileOpenApplet": "애플릿 열기", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "독립 실행형 모드로 Mii 편집기 애플릿 열기", + "SettingsTabInputDirectMouseAccess": "다이렉트 마우스 접근", + "SettingsTabSystemMemoryManagerMode": "메모리 관리자 모드:", + "SettingsTabSystemMemoryManagerModeSoftware": "소프트웨어", + "SettingsTabSystemMemoryManagerModeHost": "호스트 (빠름)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "호스트 확인 안함 (가장 빠르나 안전하지 않음)", + "SettingsTabSystemUseHypervisor": "하이퍼바이저 사용하기", + "MenuBarFile": "_파일", + "MenuBarFileOpenFromFile": "_파일에서 응용 프로그램 불러오기", + "MenuBarFileOpenUnpacked": "_압축을 푼 게임 불러오기", + "MenuBarFileOpenEmuFolder": "Ryujinx 폴더 열기", + "MenuBarFileOpenLogsFolder": "로그 폴더 열기", + "MenuBarFileExit": "_종료", + "MenuBarOptions": "옵션(_O)", + "MenuBarOptionsToggleFullscreen": "전체화면 전환", + "MenuBarOptionsStartGamesInFullscreen": "전체 화면 모드에서 게임 시작", + "MenuBarOptionsStopEmulation": "에뮬레이션 중지", + "MenuBarOptionsSettings": "_설정", + "MenuBarOptionsManageUserProfiles": "_사용자 프로파일 관리", + "MenuBarActions": "_동작", + "MenuBarOptionsSimulateWakeUpMessage": "깨우기 메시지 시뮬레이션", + "MenuBarActionsScanAmiibo": "Amiibo 스캔", + "MenuBarTools": "_도구", + "MenuBarToolsInstallFirmware": "펌웨어 설치", + "MenuBarFileToolsInstallFirmwareFromFile": "XCI 또는 ZIP에서 펌웨어 설치", + "MenuBarFileToolsInstallFirmwareFromDirectory": "디렉터리에서 펌웨어 설치", + "MenuBarToolsManageFileTypes": "파일 형식 관리", + "MenuBarToolsInstallFileTypes": "파일 형식 설치", + "MenuBarToolsUninstallFileTypes": "파일 형식 설치 제거", + "MenuBarView": "_보기", + "MenuBarViewWindow": "창 크기", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "도움말(_H)", + "MenuBarHelpCheckForUpdates": "업데이트 확인", + "MenuBarHelpAbout": "정보", + "MenuSearch": "검색...", + "GameListHeaderFavorite": "즐겨찾기", + "GameListHeaderIcon": "아이콘", + "GameListHeaderApplication": "이름", + "GameListHeaderDeveloper": "개발자", + "GameListHeaderVersion": "버전", + "GameListHeaderTimePlayed": "플레이 시간", + "GameListHeaderLastPlayed": "마지막 플레이", + "GameListHeaderFileExtension": "파일 확장자", + "GameListHeaderFileSize": "파일 크기", + "GameListHeaderPath": "경로", + "GameListContextMenuOpenUserSaveDirectory": "사용자 저장 디렉터리 열기", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "응용프로그램의 사용자 저장이 포함된 디렉터리 열기", + "GameListContextMenuOpenDeviceSaveDirectory": "사용자 장치 디렉토리 열기", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "응용프로그램의 장치 저장이 포함된 디렉터리 열기", + "GameListContextMenuOpenBcatSaveDirectory": "BCAT 저장 디렉터리 열기", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "응용프로그램의 BCAT 저장이 포함된 디렉터리 열기", + "GameListContextMenuManageTitleUpdates": "타이틀 업데이트 관리", + "GameListContextMenuManageTitleUpdatesToolTip": "타이틀 업데이트 관리 창 열기", + "GameListContextMenuManageDlc": "DLC 관리", + "GameListContextMenuManageDlcToolTip": "DLC 관리 창 열기", + "GameListContextMenuCacheManagement": "캐시 관리", + "GameListContextMenuCacheManagementPurgePptc": "대기열 PPTC 재구성", + "GameListContextMenuCacheManagementPurgePptcToolTip": "다음 게임 시작에서 부팅 시 PPTC가 다시 빌드하도록 트리거", + "GameListContextMenuCacheManagementPurgeShaderCache": "셰이더 캐시 제거", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "응용프로그램 셰이더 캐시 삭제\n", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC 디렉터리 열기", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "응용프로그램 PPTC 캐시가 포함된 디렉터리 열기", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "셰이더 캐시 디렉터리 열기", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "응용프로그램 셰이더 캐시가 포함된 디렉터리 열기", + "GameListContextMenuExtractData": "데이터 추출", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "응용프로그램의 현재 구성에서 ExeFS 추출 (업데이트 포함)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "응용 프로그램의 현재 구성에서 RomFS 추출 (업데이트 포함)", + "GameListContextMenuExtractDataLogo": "로고", + "GameListContextMenuExtractDataLogoToolTip": "응용프로그램의 현재 구성에서 로고 섹션 추출 (업데이트 포함)", + "GameListContextMenuCreateShortcut": "애플리케이션 바로 가기 만들기", + "GameListContextMenuCreateShortcutToolTip": "선택한 애플리케이션을 실행하는 바탕 화면 바로 가기를 만듭니다.", + "GameListContextMenuCreateShortcutToolTipMacOS": "해당 게임을 실행할 수 있는 바로가기를 macOS의 응용 프로그램 폴더에 추가합니다.", + "GameListContextMenuOpenModsDirectory": "Mod 디렉터리 열기", + "GameListContextMenuOpenModsDirectoryToolTip": "해당 게임의 Mod가 저장된 디렉터리 열기", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mod 디렉터리 열기", + "GameListContextMenuOpenSdModsDirectoryToolTip": "해당 게임의 Mod가 포함된 대체 SD 카드 Atmosphere 디렉터리를 엽니다. 실제 하드웨어용으로 패키징된 Mod에 유용합니다.", + "StatusBarGamesLoaded": "{0}/{1}개의 게임 불러옴", + "StatusBarSystemVersion": "시스템 버전 : {0}", + "LinuxVmMaxMapCountDialogTitle": "감지된 메모리 매핑의 하한선", + "LinuxVmMaxMapCountDialogTextPrimary": "vm.max_map_count의 값을 {0}으로 늘리시겠습니까?", + "LinuxVmMaxMapCountDialogTextSecondary": "일부 게임은 현재 허용된 것보다 더 많은 메모리 매핑을 생성하려고 시도할 수 있습니다. 이 제한을 초과하는 즉시 Ryujinx에 문제가 발생합니다.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "예, 다음에 다시 시작할 때까지", + "LinuxVmMaxMapCountDialogButtonPersistent": "예, 영구적으로", + "LinuxVmMaxMapCountWarningTextPrimary": "메모리 매핑의 최대 용량이 권장 용량보다 적습니다.", + "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count({0})의 현재 값이 {1}보다 낮습니다. 일부 게임은 현재 허용된 것보다 더 많은 메모리 매핑을 생성하려고 시도할 수 있습니다. 이 제한을 초과하는 즉시 Ryujinx에 문제가 발생합니다.\n\n수동으로 제한을 늘리거나 Ryujinx의 도움을 받을 수 있는 pkexec을 설치하는 것이 좋습니다.", + "Settings": "설정", + "SettingsTabGeneral": "사용자 인터페이스", + "SettingsTabGeneralGeneral": "일반", + "SettingsTabGeneralEnableDiscordRichPresence": "디스코드 활동 상태 활성화", + "SettingsTabGeneralCheckUpdatesOnLaunch": "시작 시, 업데이트 확인", + "SettingsTabGeneralShowConfirmExitDialog": "\"종료 확인\" 대화 상자 표시", + "SettingsTabGeneralRememberWindowState": "창 크기/위치 기억", + "SettingsTabGeneralHideCursor": "마우스 커서 숨기기", + "SettingsTabGeneralHideCursorNever": "절대 안 함", + "SettingsTabGeneralHideCursorOnIdle": "유휴 상태", + "SettingsTabGeneralHideCursorAlways": "언제나", + "SettingsTabGeneralGameDirectories": "게임 디렉터리", + "SettingsTabGeneralAdd": "추가", + "SettingsTabGeneralRemove": "제거", + "SettingsTabSystem": "시스템", + "SettingsTabSystemCore": "코어", + "SettingsTabSystemSystemRegion": "시스템 지역:", + "SettingsTabSystemSystemRegionJapan": "일본", + "SettingsTabSystemSystemRegionUSA": "미국", + "SettingsTabSystemSystemRegionEurope": "유럽", + "SettingsTabSystemSystemRegionAustralia": "호주", + "SettingsTabSystemSystemRegionChina": "중국", + "SettingsTabSystemSystemRegionKorea": "한국", + "SettingsTabSystemSystemRegionTaiwan": "대만", + "SettingsTabSystemSystemLanguage": "시스템 언어 :", + "SettingsTabSystemSystemLanguageJapanese": "일본어", + "SettingsTabSystemSystemLanguageAmericanEnglish": "영어(미국)", + "SettingsTabSystemSystemLanguageFrench": "프랑스어", + "SettingsTabSystemSystemLanguageGerman": "독일어", + "SettingsTabSystemSystemLanguageItalian": "이탈리아어", + "SettingsTabSystemSystemLanguageSpanish": "스페인어", + "SettingsTabSystemSystemLanguageChinese": "중국어", + "SettingsTabSystemSystemLanguageKorean": "한국어", + "SettingsTabSystemSystemLanguageDutch": "네덜란드어", + "SettingsTabSystemSystemLanguagePortuguese": "포르투갈어", + "SettingsTabSystemSystemLanguageRussian": "러시아어", + "SettingsTabSystemSystemLanguageTaiwanese": "대만어", + "SettingsTabSystemSystemLanguageBritishEnglish": "영어(영국)", + "SettingsTabSystemSystemLanguageCanadianFrench": "프랑스어(캐나다)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "스페인어(라틴 아메리카)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "중국어 간체", + "SettingsTabSystemSystemLanguageTraditionalChinese": "중국어 번체", + "SettingsTabSystemSystemTimeZone": "시스템 시간대:", + "SettingsTabSystemSystemTime": "시스템 시간:", + "SettingsTabSystemEnableVsync": "수직 동기화", + "SettingsTabSystemEnablePptc": "PPTC(프로파일된 영구 번역 캐시)", + "SettingsTabSystemEnableFsIntegrityChecks": "파일 시스템 무결성 검사", + "SettingsTabSystemAudioBackend": "음향 후단부 :", + "SettingsTabSystemAudioBackendDummy": "더미", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "사운드IO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "해킹", + "SettingsTabSystemHacksNote": "불안정성을 유발할 수 있음", + "SettingsTabSystemExpandDramSize": "대체 메모리 레이아웃 사용(개발자)", + "SettingsTabSystemIgnoreMissingServices": "누락된 서비스 무시", + "SettingsTabGraphics": "그래픽", + "SettingsTabGraphicsAPI": "그래픽 API", + "SettingsTabGraphicsEnableShaderCache": "셰이더 캐시 활성화", + "SettingsTabGraphicsAnisotropicFiltering": "이방성 필터링 :", + "SettingsTabGraphicsAnisotropicFilteringAuto": "자동", + "SettingsTabGraphicsAnisotropicFiltering2x": "2배", + "SettingsTabGraphicsAnisotropicFiltering4x": "4배", + "SettingsTabGraphicsAnisotropicFiltering8x": "8배", + "SettingsTabGraphicsAnisotropicFiltering16x": "16배", + "SettingsTabGraphicsResolutionScale": "해상도 배율 :", + "SettingsTabGraphicsResolutionScaleCustom": "사용자 정의(권장하지 않음)", + "SettingsTabGraphicsResolutionScaleNative": "원본(720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2배(1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3배(2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (권장하지 않음)", + "SettingsTabGraphicsAspectRatio": "종횡비 :", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "창에 맞게 늘리기", + "SettingsTabGraphicsDeveloperOptions": "개발자 옵션", + "SettingsTabGraphicsShaderDumpPath": "그래픽 셰이더 덤프 경로 :", + "SettingsTabLogging": "로그 기록", + "SettingsTabLoggingLogging": "로그 기록", + "SettingsTabLoggingEnableLoggingToFile": "파일에 로그 기록 활성화", + "SettingsTabLoggingEnableStubLogs": "스텁 로그 활성화", + "SettingsTabLoggingEnableInfoLogs": "정보 로그 활성화", + "SettingsTabLoggingEnableWarningLogs": "경고 로그 활성화", + "SettingsTabLoggingEnableErrorLogs": "오류 로그 활성화", + "SettingsTabLoggingEnableTraceLogs": "추적 로그 활성화", + "SettingsTabLoggingEnableGuestLogs": "게스트 로그 활성화", + "SettingsTabLoggingEnableFsAccessLogs": "Fs 접속 로그 활성화", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs 전역 접속 로그 모드 :", + "SettingsTabLoggingDeveloperOptions": "개발자 옵션", + "SettingsTabLoggingDeveloperOptionsNote": "경고: 성능이 저하됨", + "SettingsTabLoggingGraphicsBackendLogLevel": "그래픽 후단부 로그 수준 :", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "없음", + "SettingsTabLoggingGraphicsBackendLogLevelError": "오류", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "느려짐", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "모두", + "SettingsTabLoggingEnableDebugLogs": "디버그 로그 활성화", + "SettingsTabInput": "입력", + "SettingsTabInputEnableDockedMode": "도킹 모드", + "SettingsTabInputDirectKeyboardAccess": "직접 키보드 접속", + "SettingsButtonSave": "저장", + "SettingsButtonClose": "닫기", + "SettingsButtonOk": "확인", + "SettingsButtonCancel": "취소", + "SettingsButtonApply": "적용", + "ControllerSettingsPlayer": "플레이어", + "ControllerSettingsPlayer1": "플레이어 1", + "ControllerSettingsPlayer2": "플레이어 2", + "ControllerSettingsPlayer3": "플레이어 3", + "ControllerSettingsPlayer4": "플레이어 4", + "ControllerSettingsPlayer5": "플레이어 5", + "ControllerSettingsPlayer6": "플레이어 6", + "ControllerSettingsPlayer7": "플레이어 7", + "ControllerSettingsPlayer8": "플레이어 8", + "ControllerSettingsHandheld": "휴대 모드", + "ControllerSettingsInputDevice": "입력 장치", + "ControllerSettingsRefresh": "새로 고침", + "ControllerSettingsDeviceDisabled": "비활성화됨", + "ControllerSettingsControllerType": "컨트롤러 유형", + "ControllerSettingsControllerTypeHandheld": "휴대 모드", + "ControllerSettingsControllerTypeProController": "프로 컨트롤러", + "ControllerSettingsControllerTypeJoyConPair": "조이콘 페어링", + "ControllerSettingsControllerTypeJoyConLeft": "좌측 조이콘", + "ControllerSettingsControllerTypeJoyConRight": "우측 조이콘", + "ControllerSettingsProfile": "프로필", + "ControllerSettingsProfileDefault": "기본", + "ControllerSettingsLoad": "불러오기", + "ControllerSettingsAdd": "추가", + "ControllerSettingsRemove": "제거", + "ControllerSettingsButtons": "버튼", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "방향 패드", + "ControllerSettingsDPadUp": "↑", + "ControllerSettingsDPadDown": "↓", + "ControllerSettingsDPadLeft": "←", + "ControllerSettingsDPadRight": "→", + "ControllerSettingsStickButton": "버튼", + "ControllerSettingsStickUp": "↑", + "ControllerSettingsStickDown": "↓", + "ControllerSettingsStickLeft": "←", + "ControllerSettingsStickRight": "→", + "ControllerSettingsStickStick": "스틱", + "ControllerSettingsStickInvertXAxis": "스틱 X 축 반전", + "ControllerSettingsStickInvertYAxis": "스틱 Y 축 반전", + "ControllerSettingsStickDeadzone": "사각지대 :", + "ControllerSettingsLStick": "좌측 스틱", + "ControllerSettingsRStick": "우측 스틱", + "ControllerSettingsTriggersLeft": "좌측 트리거", + "ControllerSettingsTriggersRight": "우측 트리거", + "ControllerSettingsTriggersButtonsLeft": "좌측 트리거 버튼", + "ControllerSettingsTriggersButtonsRight": "우측 트리거 버튼", + "ControllerSettingsTriggers": "트리거 버튼", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "좌측 버튼", + "ControllerSettingsExtraButtonsRight": "우측 버튼", + "ControllerSettingsMisc": "기타", + "ControllerSettingsTriggerThreshold": "트리거 임계값 :", + "ControllerSettingsMotion": "동작", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook 호환 모션 사용", + "ControllerSettingsMotionControllerSlot": "컨트롤러 슬롯 :", + "ControllerSettingsMotionMirrorInput": "미러 입력", + "ControllerSettingsMotionRightJoyConSlot": "우측 조이콘 슬롯 :", + "ControllerSettingsMotionServerHost": "서버 호스트 :", + "ControllerSettingsMotionGyroSensitivity": "자이로 감도 :", + "ControllerSettingsMotionGyroDeadzone": "자이로 사각지대 :", + "ControllerSettingsSave": "저장", + "ControllerSettingsClose": "닫기", + "KeyUnknown": "알 수 없음", + "KeyShiftLeft": "왼쪽 Shift", + "KeyShiftRight": "오른쪽 Shift", + "KeyControlLeft": "왼쪽 Ctrl", + "KeyMacControlLeft": "왼쪽 ^", + "KeyControlRight": "오른쪽 Ctrl", + "KeyMacControlRight": "오른쪽 ^", + "KeyAltLeft": "왼쪽 Alt", + "KeyMacAltLeft": "왼쪽 ⌥", + "KeyAltRight": "오른쪽 Alt", + "KeyMacAltRight": "오른쪽 ⌥", + "KeyWinLeft": "왼쪽 ⊞", + "KeyMacWinLeft": "왼쪽 ⌘", + "KeyWinRight": "오른쪽 ⊞", + "KeyMacWinRight": "오른쪽 ⌘", + "KeyMenu": "메뉴", + "KeyUp": "↑", + "KeyDown": "↓", + "KeyLeft": "←", + "KeyRight": "→", + "KeyEnter": "엔터", + "KeyEscape": "이스케이프", + "KeySpace": "스페이스", + "KeyTab": "탭", + "KeyBackSpace": "백스페이스", + "KeyInsert": "Ins", + "KeyDelete": "Del", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "프린트 스크린", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "지우기", + "KeyKeypad0": "키패드 0", + "KeyKeypad1": "키패드 1", + "KeyKeypad2": "키패드 2", + "KeyKeypad3": "키패드 3", + "KeyKeypad4": "키패드 4", + "KeyKeypad5": "키패드 5", + "KeyKeypad6": "키패드 6", + "KeyKeypad7": "키패드 7", + "KeyKeypad8": "키패드 8", + "KeyKeypad9": "키패드 9", + "KeyKeypadDivide": "키패드 분할", + "KeyKeypadMultiply": "키패드 멀티플", + "KeyKeypadSubtract": "키패드 빼기", + "KeyKeypadAdd": "키패드 추가", + "KeyKeypadDecimal": "숫자 키패드", + "KeyKeypadEnter": "키패드 엔터", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "바인딩 해제", + "GamepadLeftStick": "L 스틱 버튼", + "GamepadRightStick": "R 스틱 버튼", + "GamepadLeftShoulder": "좌측 숄더", + "GamepadRightShoulder": "우측 숄더", + "GamepadLeftTrigger": "좌측 트리거", + "GamepadRightTrigger": "우측 트리거", + "GamepadDpadUp": "↑", + "GamepadDpadDown": "↓", + "GamepadDpadLeft": "←", + "GamepadDpadRight": "→", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "안내", + "GamepadMisc1": "기타", + "GamepadPaddle1": "패들 1", + "GamepadPaddle2": "패들 2", + "GamepadPaddle3": "패들 3", + "GamepadPaddle4": "패들 4", + "GamepadTouchpad": "터치패드", + "GamepadSingleLeftTrigger0": "왼쪽 트리거 0", + "GamepadSingleRightTrigger0": "오른쪽 트리거 0", + "GamepadSingleLeftTrigger1": "왼쪽 트리거 1", + "GamepadSingleRightTrigger1": "오른쪽 트리거 1", + "StickLeft": "좌측 스틱", + "StickRight": "우측 스틱", + "UserProfilesSelectedUserProfile": "선택한 사용자 프로필 :", + "UserProfilesSaveProfileName": "프로필 이름 저장", + "UserProfilesChangeProfileImage": "프로필 이미지 변경", + "UserProfilesAvailableUserProfiles": "사용 가능한 사용자 프로필 :", + "UserProfilesAddNewProfile": "프로필 생성", + "UserProfilesDelete": "삭제", + "UserProfilesClose": "닫기", + "ProfileNameSelectionWatermark": "닉네임을 입력하세요", + "ProfileImageSelectionTitle": "프로필 이미지 선택", + "ProfileImageSelectionHeader": "프로필 이미지 선택", + "ProfileImageSelectionNote": "사용자 지정 프로필 이미지를 가져오거나 시스템 펌웨어에서 아바타 선택 가능", + "ProfileImageSelectionImportImage": "이미지 파일 가져오기", + "ProfileImageSelectionSelectAvatar": "펌웨어 아바타 선택", + "InputDialogTitle": "입력 대화상자", + "InputDialogOk": "확인", + "InputDialogCancel": "취소", + "InputDialogAddNewProfileTitle": "프로필 이름 선택", + "InputDialogAddNewProfileHeader": "프로필 이름 입력", + "InputDialogAddNewProfileSubtext": "(최대 길이 : {0})", + "AvatarChoose": "선택", + "AvatarSetBackgroundColor": "배경색 설정", + "AvatarClose": "닫기", + "ControllerSettingsLoadProfileToolTip": "프로필 불러오기", + "ControllerSettingsAddProfileToolTip": "프로필 추가", + "ControllerSettingsRemoveProfileToolTip": "프로필 제거", + "ControllerSettingsSaveProfileToolTip": "프로필 저장", + "MenuBarFileToolsTakeScreenshot": "스크린 샷 찍기", + "MenuBarFileToolsHideUi": "UI 숨기기", + "GameListContextMenuRunApplication": "응용프로그램 실행", + "GameListContextMenuToggleFavorite": "즐겨찾기 전환", + "GameListContextMenuToggleFavoriteToolTip": "게임 즐겨찾기 상태 전환", + "SettingsTabGeneralTheme": "테마:", + "SettingsTabGeneralThemeDark": "어두운 테마", + "SettingsTabGeneralThemeLight": "밝은 테마", + "ControllerSettingsConfigureGeneral": "구성", + "ControllerSettingsRumble": "진동", + "ControllerSettingsRumbleStrongMultiplier": "강력한 진동 증폭기", + "ControllerSettingsRumbleWeakMultiplier": "약한 진동 증폭기", + "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}]에 대한 저장 데이터가 없음", + "DialogMessageSaveNotAvailableCreateSaveMessage": "이 게임에 대한 저장 데이터를 생성하겠습니까?", + "DialogConfirmationTitle": "Ryujinx - 확인", + "DialogUpdaterTitle": "Ryujinx - 업데이터", + "DialogErrorTitle": "Ryujinx - 오류", + "DialogWarningTitle": "Ryujinx - 경고", + "DialogExitTitle": "Ryujinx - 종료", + "DialogErrorMessage": "Ryujinx 오류 발생", + "DialogExitMessage": "Ryujinx를 종료하겠습니까?", + "DialogExitSubMessage": "저장하지 않은 모든 데이터는 손실됩니다!", + "DialogMessageCreateSaveErrorMessage": "지정된 저장 데이터를 작성하는 중에 오류 발생: {0}", + "DialogMessageFindSaveErrorMessage": "지정된 저장 데이터를 찾는 중에 오류 발생: {0}", + "FolderDialogExtractTitle": "추출할 폴더 선택", + "DialogNcaExtractionMessage": "{1}에서 {0} 섹션을 추출하는 중...", + "DialogNcaExtractionTitle": "Ryujinx - NCA 섹션 추출기", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "추출 실패하였습니다. 선택한 파일에 기본 NCA가 없습니다.", + "DialogNcaExtractionCheckLogErrorMessage": "추출 실패하였습니다. 자세한 내용은 로그 파일을 읽으세요.", + "DialogNcaExtractionSuccessMessage": "추출이 성공적으로 완료되었습니다.", + "DialogUpdaterConvertFailedMessage": "현재 Ryujinx 버전을 변환하지 못했습니다.", + "DialogUpdaterCancelUpdateMessage": "업데이트 취소 중 입니다!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "이미 최신 버전의 Ryujinx를 사용하고 있습니다!", + "DialogUpdaterFailedToGetVersionMessage": "GitHub 릴리스에서 릴리스 정보를 가져오는 중에 오류가 발생했습니다. 이는 GitHub Actions에서 새 릴리스를 컴파일하는 경우 발생할 수 있습니다. 몇 분 후에 다시 시도하세요.", + "DialogUpdaterConvertFailedGithubMessage": "Github 개정에서 받은 Ryujinx 버전을 변환하지 못했습니다.", + "DialogUpdaterDownloadingMessage": "업데이트 다운로드 중...", + "DialogUpdaterExtractionMessage": "업데이트 추출 중...", + "DialogUpdaterRenamingMessage": "업데이트 이름 바꾸는 중...", + "DialogUpdaterAddingFilesMessage": "새 업데이트 추가 중...", + "DialogUpdaterCompleteMessage": "업데이트를 완료했습니다!", + "DialogUpdaterRestartMessage": "지금 Ryujinx를 다시 시작하겠습니까?", + "DialogUpdaterNoInternetMessage": "인터넷에 연결되어 있지 않습니다!", + "DialogUpdaterNoInternetSubMessage": "인터넷 연결이 작동하는지 확인하세요!", + "DialogUpdaterDirtyBuildMessage": "Ryujinx의 나쁜 빌드는 업데이트할 수 없습니다!\n", + "DialogUpdaterDirtyBuildSubMessage": "지원되는 버전을 찾고 있다면 https://ryujinx.org/에서 Ryujinx를 다운로드하세요.", + "DialogRestartRequiredMessage": "재시작 필요", + "DialogThemeRestartMessage": "테마가 저장되었습니다. 테마를 적용하려면 다시 시작해야 합니다.", + "DialogThemeRestartSubMessage": "다시 시작하겠습니까?", + "DialogFirmwareInstallEmbeddedMessage": "이 게임에 내장된 펌웨어를 설치하겠습니까? (펌웨어 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "설치된 펌웨어가 없지만 Ryujinx가 제공된 게임에서 펌웨어 {0}을(를) 설치할 수 있었습니다.\n이제 에뮬레이터가 시작됩니다.", + "DialogFirmwareNoFirmwareInstalledMessage": "설치된 펌웨어 없음", + "DialogFirmwareInstalledMessage": "펌웨어 {0}이(가) 설치됨", + "DialogInstallFileTypesSuccessMessage": "파일 형식을 성공적으로 설치했습니다!", + "DialogInstallFileTypesErrorMessage": "파일 형식을 설치하지 못했습니다.", + "DialogUninstallFileTypesSuccessMessage": "파일 형식을 성공적으로 제거했습니다!", + "DialogUninstallFileTypesErrorMessage": "파일 형식을 제거하지 못했습니다.", + "DialogOpenSettingsWindowLabel": "설정 창 열기", + "DialogControllerAppletTitle": "컨트롤러 애플릿", + "DialogMessageDialogErrorExceptionMessage": "메시지 대화상자를 표시하는 동안 오류 발생 : {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "소프트웨어 키보드를 표시하는 동안 오류 발생 : {0}", + "DialogErrorAppletErrorExceptionMessage": "오류에플릿 대화상자를 표시하는 동안 오류 발생 : {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n이 오류를 수정하는 방법에 대한 자세한 내용은 설정 가이드를 따르세요.", + "DialogUserErrorDialogTitle": "Ryuijnx 오류 ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "API에서 정보를 가져오는 동안 오류가 발생했습니다.", + "DialogAmiiboApiConnectErrorMessage": "Amiibo API 서버에 연결할 수 없습니다. 서비스가 다운되었거나 인터넷 연결이 온라인 상태인지 확인해야 할 수 있습니다.", + "DialogProfileInvalidProfileErrorMessage": "{0} 프로필은 현재 입력 구성 시스템과 호환되지 않습니다.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "기본 프로필을 덮어쓸 수 없음", + "DialogProfileDeleteProfileTitle": "프로필 삭제", + "DialogProfileDeleteProfileMessage": "이 작업은 되돌릴 수 없습니다. 계속하겠습니까?", + "DialogWarning": "경고", + "DialogPPTCDeletionMessage": "다음 부팅 시, PPTC 재구축을 대기열에 추가 :\n\n{0}\n\n계속하겠습니까?", + "DialogPPTCDeletionErrorMessage": "{0}에서 PPTC 캐시 삭제 오류 : {1}", + "DialogShaderDeletionMessage": "다음에 대한 셰이더 캐시 삭제 :\n\n{0}\n\n계속하겠습니까?", + "DialogShaderDeletionErrorMessage": "{0}에서 셰이더 캐시 제거 오류 : {1}", + "DialogRyujinxErrorMessage": "Ryujinx에 오류 발생", + "DialogInvalidTitleIdErrorMessage": "UI 오류 : 선택한 게임에 유효한 타이틀 ID가 없음", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0}에서 유효한 시스템 펌웨어를 찾을 수 없습니다.", + "DialogFirmwareInstallerFirmwareInstallTitle": "펌웨어 {0} 설치", + "DialogFirmwareInstallerFirmwareInstallMessage": "시스템 버전 {0}이(가) 설치됩니다.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n이것은 현재 시스템 버전 {0}을(를) 대체합니다.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n계속하겠습니까?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "펌웨어 설치 중...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "시스템 버전 {0}이(가) 성공적으로 설치되었습니다.", + "DialogUserProfileDeletionWarningMessage": "선택한 프로파일이 삭제되면 사용 가능한 다른 프로파일이 없음", + "DialogUserProfileDeletionConfirmMessage": "선택한 프로파일을 삭제하겠습니까?", + "DialogUserProfileUnsavedChangesTitle": "경고 - 변경사항 저장되지 않음", + "DialogUserProfileUnsavedChangesMessage": "저장되지 않은 사용자 프로파일을 수정했습니다.", + "DialogUserProfileUnsavedChangesSubMessage": "변경사항을 저장하지 않으시겠습니까?", + "DialogControllerSettingsModifiedConfirmMessage": "현재 컨트롤러 설정이 업데이트되었습니다.", + "DialogControllerSettingsModifiedConfirmSubMessage": "저장하겠습니까?", + "DialogLoadFileErrorMessage": "{0}. 오류 발생 파일 : {1}", + "DialogModAlreadyExistsMessage": "Mod가 이미 존재합니다.", + "DialogModInvalidMessage": "지정된 디렉터리에 Mod가 없습니다!", + "DialogModDeleteNoParentMessage": "삭제 실패: \"{0}\" Mod의 상위 디렉터리를 찾을 수 없습니다!", + "DialogDlcNoDlcErrorMessage": "지정된 파일에 선택한 타이틀에 대한 DLC가 포함되어 있지 않습니다!", + "DialogPerformanceCheckLoggingEnabledMessage": "개발자만 사용하도록 설계된 추적 로그 기록이 활성화되어 있습니다.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "최적의 성능을 위해 추적 로그 생성을 비활성화하는 것이 좋습니다. 지금 추적 로그 기록을 비활성화하겠습니까?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "개발자만 사용하도록 설계된 셰이더 덤프를 활성화했습니다.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "최적의 성능을 위해 세이더 덤핑을 비활성화하는 것이 좋습니다. 지금 세이더 덤핑을 비활성화하겠습니까?", + "DialogLoadAppGameAlreadyLoadedMessage": "이미 게임 불러옴", + "DialogLoadAppGameAlreadyLoadedSubMessage": "다른 게임을 시작하기 전에 에뮬레이션을 중지하거나 에뮬레이터를 닫으세요.", + "DialogUpdateAddUpdateErrorMessage": "지정된 파일에 선택한 제목에 대한 업데이트가 포함되어 있지 않습니다!", + "DialogSettingsBackendThreadingWarningTitle": "경고 - 후단부 스레딩", + "DialogSettingsBackendThreadingWarningMessage": "변경 사항을 완전히 적용하려면 이 옵션을 변경한 후, Ryujinx를 다시 시작해야 합니다. 플랫폼에 따라 Ryujinx를 사용할 때 드라이버 자체의 멀티스레딩을 수동으로 비활성화해야 할 수도 있습니다.", + "DialogModManagerDeletionWarningMessage": "해당 Mod를 삭제하려고 합니다: {0}\n\n정말로 삭제하시겠습니까?", + "DialogModManagerDeletionAllWarningMessage": "해당 타이틀에 대한 모든 Mod들을 삭제하려고 합니다.\n\n정말로 삭제하시겠습니까?", + "SettingsTabGraphicsFeaturesOptions": "기능", + "SettingsTabGraphicsBackendMultithreading": "그래픽 후단부 멀티스레딩 :", + "CommonAuto": "자동", + "CommonOff": "끔", + "CommonOn": "켬", + "InputDialogYes": "예", + "InputDialogNo": "아니오", + "DialogProfileInvalidProfileNameErrorMessage": "파일 이름에 잘못된 문자가 포함되어 있습니다. 다시 시도하세요.", + "MenuBarOptionsPauseEmulation": "일시 정지", + "MenuBarOptionsResumeEmulation": "다시 시작", + "AboutUrlTooltipMessage": "기본 브라우저에서 Ryujinx 웹사이트를 열려면 클릭하세요.", + "AboutDisclaimerMessage": "Ryujinx는 닌텐도™,\n또는 그 파트너와 제휴한 바가 없습니다.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com)는\nAmiibo 에뮬레이션에 사용됩니다.", + "AboutPatreonUrlTooltipMessage": "기본 브라우저에서 Ryujinx Patreon 페이지를 열려면 클릭하세요.", + "AboutGithubUrlTooltipMessage": "기본 브라우저에서 Ryujinx GitHub 페이지를 열려면 클릭하세요.", + "AboutDiscordUrlTooltipMessage": "기본 브라우저에서 Ryujinx 디스코드 서버에 대한 초대를 열려면 클릭하세요.", + "AboutTwitterUrlTooltipMessage": "기본 브라우저에서 Ryujinx 트위터 페이지를 열려면 클릭하세요.", + "AboutRyujinxAboutTitle": "정보 :", + "AboutRyujinxAboutContent": "Ryujinx는 닌텐도 스위치™용 에뮬레이터입니다.\nPatreon에서 지원해 주세요.\n트위터나 디스코드에서 최신 소식을 받아보세요.\n기여에 참여하고자 하는 개발자는 GitHub 또는 디스코드에서 자세한 내용을 확인할 수 있습니다.", + "AboutRyujinxMaintainersTitle": "유지 관리 :", + "AboutRyujinxMaintainersContentTooltipMessage": "기본 브라우저에서 기여자 페이지를 열려면 클릭하세요.", + "AboutRyujinxSupprtersTitle": "Patreon에서 후원:", + "AmiiboSeriesLabel": "Amiibo 시리즈", + "AmiiboCharacterLabel": "캐릭터", + "AmiiboScanButtonLabel": "스캔", + "AmiiboOptionsShowAllLabel": "모든 Amiibo 표시", + "AmiiboOptionsUsRandomTagLabel": "해킹: 임의의 태그 UUID 사용", + "DlcManagerTableHeadingEnabledLabel": "활성화됨", + "DlcManagerTableHeadingTitleIdLabel": "타이틀 ID", + "DlcManagerTableHeadingContainerPathLabel": "컨테이너 경로", + "DlcManagerTableHeadingFullPathLabel": "전체 경로", + "DlcManagerRemoveAllButton": "모두 제거", + "DlcManagerEnableAllButton": "모두 활성화", + "DlcManagerDisableAllButton": "모두 비활성화", + "ModManagerDeleteAllButton": "모두 삭제", + "MenuBarOptionsChangeLanguage": "언어 변경", + "MenuBarShowFileTypes": "파일 유형 표시", + "CommonSort": "정렬", + "CommonShowNames": "이름 표시", + "CommonFavorite": "즐겨찾기", + "OrderAscending": "오름차순", + "OrderDescending": "내림차순", + "SettingsTabGraphicsFeatures": "기능ㆍ개선 사항", + "ErrorWindowTitle": "오류 창", + "ToggleDiscordTooltip": "\"현재 재생 중인\" 디스코드 활동에 Ryujinx를 표시할지 여부 선택", + "AddGameDirBoxTooltip": "목록에 추가할 게임 디렉터리 입력", + "AddGameDirTooltip": "목록에 게임 디렉터리 추가", + "RemoveGameDirTooltip": "선택한 게임 디렉터리 제거", + "CustomThemeCheckTooltip": "GUI에 사용자 지정 Avalonia 테마를 사용하여 에뮬레이터 메뉴의 모양 변경", + "CustomThemePathTooltip": "사용자 정의 GUI 테마 경로", + "CustomThemeBrowseTooltip": "사용자 정의 GUI 테마 찾아보기", + "DockModeToggleTooltip": "독 모드에서는 에뮬레이트된 시스템이 도킹된 닌텐도 스위치처럼 작동합니다. 이것은 대부분의 게임에서 그래픽 품질을 향상시킵니다. 반대로 이 기능을 비활성화하면 에뮬레이트된 시스템이 휴대용 닌텐도 스위치처럼 작동하여 그래픽 품질이 저하됩니다.\n\n독 모드를 사용하려는 경우 플레이어 1의 컨트롤을 구성하세요. 휴대 모드를 사용하려는 경우 휴대용 컨트롤을 구성하세요.\n\n확실하지 않으면 켜 두세요.", + "DirectKeyboardTooltip": "다이렉트 키보드 접근(HID)은 게임에서 사용자의 키보드를 텍스트 입력 장치로 사용할 수 있게끔 제공합니다.\n\n스위치 하드웨어에서 키보드 사용을 네이티브로 지원하는 게임에서만 작동합니다.\n\n이 옵션에 대해 잘 모른다면 끄기를 권장합니다.", + "DirectMouseTooltip": "다이렉트 마우스 접근(HID)은 게임에서 사용자의 마우스를 포인터 장치로 사용할 수 있게끔 제공합니다.\n\n스위치 하드웨어에서 마우스 사용을 네이티브로 지원하는 극히 일부 게임에서만 작동합니다.\n\n이 옵션이 활성화된 경우, 터치 스크린 기능이 작동하지 않을 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 끄기를 권장합니다.", + "RegionTooltip": "시스템 지역 변경", + "LanguageTooltip": "시스템 언어 변경", + "TimezoneTooltip": "시스템 시간대 변경", + "TimeTooltip": "시스템 시간 변경", + "VSyncToggleTooltip": "에뮬레이트된 콘솔의 수직 동기화. 기본적으로 대부분의 게임에 대한 프레임 제한 장치로, 비활성화시 게임이 더 빠른 속도로 실행되거나 로딩 화면이 더 오래 걸리거나 멈출 수 있습니다.\n\n게임 내에서 선호하는 핫키로 전환할 수 있습니다(기본값 F1). 핫키를 비활성화할 계획이라면 이 작업을 수행하는 것이 좋습니다.\n\n이 옵션에 대해 잘 모른다면 켜기를 권장드립니다.", + "PptcToggleTooltip": "게임이 불러올 때마다 번역할 필요가 없도록 번역된 JIT 기능을 저장합니다.\n\n게임을 처음 부팅한 후 끊김 현상을 줄이고 부팅 시간을 크게 단축합니다.\n\n확실하지 않으면 켜 두세요.", + "FsIntegrityToggleTooltip": "게임을 부팅할 때 손상된 파일을 확인하고 손상된 파일이 감지되면 로그에 해시 오류를 표시합니다.\n\n성능에 영향을 미치지 않으며 문제 해결에 도움이 됩니다.\n\n확실하지 않으면 켜 두세요.", + "AudioBackendTooltip": "오디오를 렌더링하는 데 사용되는 백엔드를 변경합니다.\n\nSDL2가 선호되는 반면 OpenAL 및 사운드IO는 폴백으로 사용됩니다. 더미는 소리가 나지 않습니다.\n\n확실하지 않으면 SDL2로 설정하세요.", + "MemoryManagerTooltip": "게스트 메모리가 매핑되고 접속되는 방식을 변경합니다. 에뮬레이트된 CPU 성능에 크게 영향을 미칩니다.\n\n확실하지 않은 경우 호스트 확인 안함으로 설정하세요.", + "MemoryManagerSoftwareTooltip": "주소 변환을 위해 소프트웨어 페이지 테이블을 사용하세요. 정확도는 가장 높지만 성능은 가장 느립니다.", + "MemoryManagerHostTooltip": "호스트 주소 공간의 메모리를 직접 매핑합니다. 훨씬 빠른 JIT 컴파일 및 실행합니다.", + "MemoryManagerUnsafeTooltip": "메모리를 직접 매핑하지만 접속하기 전에 게스트 주소 공간 내의 주소를 마스킹하지 마십시오. 더 빠르지만 안전을 희생해야 합니다. 게스트 응용 프로그램은 Ryujinx의 어디에서나 메모리에 접속할 수 있으므로 이 모드에서는 신뢰할 수 있는 프로그램만 실행하세요.", + "UseHypervisorTooltip": "JIT 대신 하이퍼바이저를 사용합니다. 하이퍼바이저를 사용할 수 있을 때 성능을 향상시키지만, 현재 상태에서는 불안정할 수 있습니다.", + "DRamTooltip": "대체 메모리모드 레이아웃을 활용하여 스위치 개발 모델을 모방합니다.\n\n고해상도 텍스처 팩 또는 4k 해상도 모드에만 유용합니다. 성능을 향상시키지 않습니다.\n\n확실하지 않으면 꺼 두세요.", + "IgnoreMissingServicesTooltip": "구현되지 않은 호라이즌 OS 서비스를 무시합니다. 이것은 특정 게임을 부팅할 때 충돌을 우회하는 데 도움이 될 수 있습니다.\n\n확실하지 않으면 꺼 두세요.", + "GraphicsBackendThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", + "GalThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", + "ShaderCacheToggleTooltip": "후속 실행에서 끊김 현상을 줄이는 디스크 세이더 캐시를 저장합니다.\n\n확실하지 않으면 켜 두세요.", + "ResolutionScaleTooltip": "게임의 렌더링 해상도를 늘립니다.\n\n일부 게임에서는 해당 기능을 지원하지 않거나 해상도가 늘어났음에도 픽셀이 자글자글해 보일 수 있습니다; 이러한 게임들의 경우 사용자가 직접 안티 앨리어싱 기능을 끄는 Mod나 내부 렌더링 해상도를 증가시키는 Mod 등을 찾아보아야 합니다. 후자의 Mod를 사용 시에는 해당 옵션을 네이티브로 두시는 것이 좋습니다.\n\n이 옵션은 게임이 구동중일 때에도 아래 Apply 버튼을 눌러서 변경할 수 있습니다; 설정 창을 게임 창 옆에 두고 사용자가 선호하는 해상도를 실험하여 고를 수 있습니다.\n\n4x 설정은 어떤 셋업에서도 무리인 점을 유의하세요.", + "ResolutionScaleEntryTooltip": "1.5와 같은 부동 소수점 분해능 스케일입니다. 비통합 척도는 문제나 충돌을 일으킬 가능성이 더 큽니다.", + "AnisotropyTooltip": "비등방성 필터링 레벨. 게임에서 요청한 값을 사용하려면 자동으로 설정하세요.", + "AspectRatioTooltip": "렌더러 창에 적용될 화면비.\n\n화면비를 변경하는 Mod를 사용할 때에만 이 옵션을 바꾸세요, 그렇지 않을 경우 그래픽이 늘어나 보일 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 16:9로 설정하세요.", + "ShaderDumpPathTooltip": "그래픽 셰이더 덤프 경로", + "FileLogTooltip": "디스크의 로그 파일에 콘솔 로깅을 저장합니다. 성능에 영향을 미치지 않습니다.", + "StubLogTooltip": "콘솔에 스텁 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "InfoLogTooltip": "콘솔에 정보 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "WarnLogTooltip": "콘솔에 경고 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "ErrorLogTooltip": "콘솔에 오류 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "TraceLogTooltip": "콘솔에 추적 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "GuestLogTooltip": "콘솔에 게스트 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "FileAccessLogTooltip": "콘솔에 파일 액세스 로그 메시지를 인쇄합니다.", + "FSAccessLogModeTooltip": "콘솔에 대한 FS 접속 로그 출력을 활성화합니다. 가능한 모드는 0-3\t\t\t\t", + "DeveloperOptionTooltip": "주의해서 사용", + "OpenGlLogLevel": "적절한 로그 수준을 활성화해야 함", + "DebugLogTooltip": "콘솔에 디버그 로그 메시지를 인쇄합니다.\n\n로그를 읽기 어렵게 만들고 에뮬레이터 성능을 악화시키므로 직원이 구체적으로 지시한 경우에만 사용하세요.", + "LoadApplicationFileTooltip": "파일 탐색기를 열어 불러올 스위치 호환 파일 선택", + "LoadApplicationFolderTooltip": "파일 탐색기를 열어 불러올 스위치 호환 압축 해제 응용 프로그램 선택", + "OpenRyujinxFolderTooltip": "Ryujinx 파일 시스템 폴더 열기", + "OpenRyujinxLogsTooltip": "로그가 기록된 폴더 열기", + "ExitTooltip": "Ryujinx 종료", + "OpenSettingsTooltip": "설정 창 열기", + "OpenProfileManagerTooltip": "사용자 프로파일 관리자 창 열기", + "StopEmulationTooltip": "현재 게임의 에뮬레이션을 중지하고 게임 선택으로 돌아감", + "CheckUpdatesTooltip": "Ryujinx 업데이트 확인", + "OpenAboutTooltip": "정보 창 열기", + "GridSize": "격자 크기", + "GridSizeTooltip": "격자 항목의 크기 변경", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "포르투갈어(브라질)", + "AboutRyujinxContributorsButtonHeader": "모든 기여자 보기", + "SettingsTabSystemAudioVolume": "음량 : ", + "AudioVolumeTooltip": "음향 음량 변경", + "SettingsTabSystemEnableInternetAccess": "게스트 인터넷 접속/LAN 모드", + "EnableInternetAccessTooltip": "에뮬레이션된 응용프로그램이 인터넷에 연결되도록 허용합니다.\n\nLAN 모드가 있는 게임은 이 모드가 활성화되고 시스템이 동일한 접속 포인트에 연결된 경우 서로 연결할 수 있습니다. 여기에는 실제 콘솔도 포함됩니다.\n\n닌텐도 서버에 연결할 수 없습니다. 인터넷에 연결을 시도하는 특정 게임에서 충돌이 발생할 수 있습니다.\n\n확실하지 않으면 꺼두세요.", + "GameListContextMenuManageCheatToolTip": "치트 관리", + "GameListContextMenuManageCheat": "치트 관리", + "GameListContextMenuManageModToolTip": "Mod 관리", + "GameListContextMenuManageMod": "Mod 관리", + "ControllerSettingsStickRange": "범위 :", + "DialogStopEmulationTitle": "Ryujinx - 에뮬레이션 중지", + "DialogStopEmulationMessage": "에뮬레이션을 중지하겠습니까?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "오디오", + "SettingsTabNetwork": "네트워크", + "SettingsTabNetworkConnection": "네트워크 연결", + "SettingsTabCpuCache": "CPU 캐시", + "SettingsTabCpuMemory": "CPU 모드", + "DialogUpdaterFlatpakNotSupportedMessage": "FlatHub를 통해 Ryujinx를 업데이트하세요.", + "UpdaterDisabledWarningTitle": "업데이터 비활성화입니다!", + "ControllerSettingsRotate90": "시계 방향으로 90° 회전", + "IconSize": "아이콘 크기", + "IconSizeTooltip": "게임 아이콘 크기 변경", + "MenuBarOptionsShowConsole": "콘솔 표시", + "ShaderCachePurgeError": "{0}에서 셰이더 캐시를 제거하는 중 오류 발생: {1}", + "UserErrorNoKeys": "키를 찾을 수 없음", + "UserErrorNoFirmware": "펌웨어를 찾을 수 없음", + "UserErrorFirmwareParsingFailed": "펌웨어 구문 분석 오류", + "UserErrorApplicationNotFound": "응용 프로그램을 찾을 수 없음", + "UserErrorUnknown": "알 수 없는 오류", + "UserErrorUndefined": "정의되지 않은 오류", + "UserErrorNoKeysDescription": "Ryujinx가 'prod.keys' 파일을 찾을 수 없음", + "UserErrorNoFirmwareDescription": "Ryujinx가 설치된 펌웨어를 찾을 수 없음", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx가 제공된 펌웨어를 구문 분석할 수 없습니다. 일반적으로 오래된 키가 원인입니다.", + "UserErrorApplicationNotFoundDescription": "Ryujinx가 지정된 경로에서 유효한 응용 프로그램을 찾을 수 없습니다.", + "UserErrorUnknownDescription": "알 수 없는 오류가 발생했습니다!", + "UserErrorUndefinedDescription": "정의되지 않은 오류가 발생했습니다! 이런 일이 발생하면 안 되므로, 개발자에게 문의하세요!", + "OpenSetupGuideMessage": "설정 가이드 열기", + "NoUpdate": "업데이트 없음", + "TitleUpdateVersionLabel": "버전 {0}", + "RyujinxInfo": "Ryujinx - 정보", + "RyujinxConfirm": "Ryujinx - 확인", + "FileDialogAllTypes": "모든 유형", + "Never": "절대 안 함", + "SwkbdMinCharacters": "{0}자 이상이어야 함", + "SwkbdMinRangeCharacters": "{0}-{1}자여야 함", + "SoftwareKeyboard": "소프트웨어 키보드", + "SoftwareKeyboardModeNumeric": "'0~9' 또는 '.'만 가능", + "SoftwareKeyboardModeAlphabet": "한중일 문자가 아닌 문자만 가능", + "SoftwareKeyboardModeASCII": "ASCII 텍스트만 가능", + "ControllerAppletControllers": "지원하는 컨트롤러:", + "ControllerAppletPlayers": "플레이어:", + "ControllerAppletDescription": "현재 설정은 유효하지 않습니다. 설정을 열어 입력 장치를 다시 설정하세요.", + "ControllerAppletDocked": "독 모드가 설정되었습니다. 핸드헬드 컨트롤은 비활성화됩니다.", + "UpdaterRenaming": "이전 파일 이름 바꾸는 중...", + "UpdaterRenameFailed": "업데이터가 파일 이름을 바꿀 수 없음: {0}", + "UpdaterAddingFiles": "새로운 파일을 추가하는 중...", + "UpdaterExtracting": "업데이트를 추출하는 중...", + "UpdaterDownloading": "업데이트 다운로드 중...", + "Game": "게임", + "Docked": "도킹됨", + "Handheld": "휴대용", + "ConnectionError": "연결 오류입니다.", + "AboutPageDeveloperListMore": "{0} 등...", + "ApiError": "API 오류입니다.", + "LoadingHeading": "{0} 로딩 중", + "CompilingPPTC": "PTC 컴파일 중", + "CompilingShaders": "셰이더 컴파일 중", + "AllKeyboards": "모든 키보드", + "OpenFileDialogTitle": "지원되는 파일을 선택", + "OpenFolderDialogTitle": "압축을 푼 게임이 있는 폴더 선택", + "AllSupportedFormats": "지원되는 모든 형식", + "RyujinxUpdater": "Ryujinx 업데이터", + "SettingsTabHotkeys": "키보드 단축키", + "SettingsTabHotkeysHotkeys": "키보드 단축키", + "SettingsTabHotkeysToggleVsyncHotkey": "수직 동기화 전환 :", + "SettingsTabHotkeysScreenshotHotkey": "스크린샷 :", + "SettingsTabHotkeysShowUiHotkey": "UI 표시 :", + "SettingsTabHotkeysPauseHotkey": "일시 중지 :", + "SettingsTabHotkeysToggleMuteHotkey": "음 소거 :", + "ControllerMotionTitle": "동작 제어 설정", + "ControllerRumbleTitle": "진동 설정", + "SettingsSelectThemeFileDialogTitle": "테마 파일 선택", + "SettingsXamlThemeFile": "Xaml 테마 파일", + "AvatarWindowTitle": "계정 관리 - 아바타", + "Amiibo": "Amiibo", + "Unknown": "알 수 없음", + "Usage": "사용법", + "Writable": "쓰기 가능", + "SelectDlcDialogTitle": "DLC 파일 선택", + "SelectUpdateDialogTitle": "업데이트 파일 선택", + "SelectModDialogTitle": "Mod 디렉터리 선택", + "UserProfileWindowTitle": "사용자 프로파일 관리자", + "CheatWindowTitle": "치트 관리자", + "DlcWindowTitle": "{0} ({1})의 다운로드 가능한 콘텐츠 관리", + "ModWindowTitle": "{0} ({1})의 Mod 관리", + "UpdateWindowTitle": "타이틀 업데이트 관리자", + "CheatWindowHeading": "{0} [{1}]에 사용할 수 있는 치트", + "BuildId": "빌드ID :", + "DlcWindowHeading": "{0} 내려받기 가능한 콘텐츠", + "ModWindowHeading": "{0} Mod(s)", + "UserProfilesEditProfile": "선택된 항목 편집", + "Cancel": "취소", + "Save": "저장", + "Discard": "삭제", + "Paused": "일시 중지", + "UserProfilesSetProfileImage": "프로파일 이미지 설정", + "UserProfileEmptyNameError": "이름 필요", + "UserProfileNoImageError": "프로파일 이미지를 설정해야 함", + "GameUpdateWindowHeading": "{0} ({1})에 대한 업데이트 관리", + "SettingsTabHotkeysResScaleUpHotkey": "해상도 증가 :", + "SettingsTabHotkeysResScaleDownHotkey": "해상도 감소 :", + "UserProfilesName": "이름 :", + "UserProfilesUserId": "사용자 ID :", + "SettingsTabGraphicsBackend": "그래픽 후단부", + "SettingsTabGraphicsBackendTooltip": "에뮬레이터에 사용될 그래픽 백엔드를 선택합니다.\n\nVulkan이 드라이버가 최신이기 때문에 모든 현대 그래픽 카드들에서 더 좋은 성능을 발휘합니다. 또한 Vulkan은 모든 벤더사의 GPU에서 더 빠른 쉐이더 컴파일을 지원하여 스터터링이 적습니다.\n\nOpenGL의 경우 오래된 Nvidia GPU나 오래된 AMD GPU(리눅스 한정), 혹은 VRAM이 적은 GPU에서 더 나은 성능을 발휘할 수는 있으나 쉐이더 컴파일로 인한 스터터링이 Vulkan보다 심할 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 Vulkan으로 설정하세요. 사용하는 GPU가 최신 그래픽 드라이버에서도 Vulkan을 지원하지 않는다면 그 땐 OpenGL로 설정하세요.", + "SettingsEnableTextureRecompression": "텍스처 재압축 활성화", + "SettingsEnableTextureRecompressionTooltip": "ASTC 텍스처를 압축하여 VRAM 사용량을 줄입니다.\n\n애스트럴 체인, 바요네타 3, 파이어 엠블렘 인게이지, 메트로이드 프라임 리마스터, 슈퍼 마리오브라더스 원더, 젤다의 전설: 티어스 오브 더 킹덤 등이 이러한 텍스처 포맷을 사용합니다.\n\nVRAM이 4GiB 이하인 그래픽 카드로 위와 같은 게임들을 구동할시 특정 지점에서 크래시가 발생할 수 있습니다.\n\n위에 서술된 게임들에서 VRAM이 부족한 경우에만 해당 옵션을 켜고, 그 외의 경우에는 끄기를 권장드립니다.", + "SettingsTabGraphicsPreferredGpu": "선호하는 GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan 그래픽 후단부와 함께 사용할 그래픽 카드를 선택하세요.\n\nOpenGL이 사용할 GPU에는 영향을 미치지 않습니다.\n\n확실하지 않은 경우 \"dGPU\" 플래그가 지정된 GPU로 설정하세요. 없는 경우, 그대로 두세요.", + "SettingsAppRequiredRestartMessage": "Ryujinx 다시 시작 필요", + "SettingsGpuBackendRestartMessage": "그래픽 후단부 또는 GPU 설정이 수정되었습니다. 적용하려면 다시 시작해야 합니다.", + "SettingsGpuBackendRestartSubMessage": "지금 다시 시작하겠습니까?", + "RyujinxUpdaterMessage": "Ryujinx를 최신 버전으로 업데이트하겠습니까?", + "SettingsTabHotkeysVolumeUpHotkey": "음량 증가 :", + "SettingsTabHotkeysVolumeDownHotkey": "음량 감소 :", + "SettingsEnableMacroHLE": "매크로 HLE 활성화", + "SettingsEnableMacroHLETooltip": "GPU 매크로 코드의 높은 수준 에뮬레이션입니다.\n\n성능이 향상되지만 일부 게임에서 그래픽 결함이 발생할 수 있습니다.\n\n확실하지 않으면 켜 두세요.", + "SettingsEnableColorSpacePassthrough": "색 공간 통과", + "SettingsEnableColorSpacePassthroughTooltip": "색 공간을 지정하지 않고 색상 정보를 전달하도록 Vulkan 후단에 지시합니다. 와이드 가멋 디스플레이를 사용하는 사용자의 경우 색 정확도가 저하되지만 더 생생한 색상을 얻을 수 있습니다.", + "VolumeShort": "음량", + "UserProfilesManageSaves": "저장 관리", + "DeleteUserSave": "이 게임에 대한 사용자 저장을 삭제하겠습니까?", + "IrreversibleActionNote": "이 작업은 되돌릴 수 없습니다.", + "SaveManagerHeading": "{0} ({1})의 저장 관리", + "SaveManagerTitle": "저장 관리자", + "Name": "이름", + "Size": "크기", + "Search": "검색", + "UserProfilesRecoverLostAccounts": "잃어버린 계정 복구", + "Recover": "복구", + "UserProfilesRecoverHeading": "다음 계정에 대한 저장 발견", + "UserProfilesRecoverEmptyList": "복구할 프로파일이 없습니다", + "GraphicsAATooltip": "게임 렌더에 안티 앨리어싱을 적용합니다.\n\nFXAA는 대부분의 이미지를 뿌옇게 만들지만, SMAA는 들쭉날쭉한 모서리 부분들을 찾아 부드럽게 만듭니다.\n\nFSR 스케일링 필터와 같이 사용하는 것은 권장하지 않습니다.\n\n이 옵션은 게임이 구동중일 때에도 아래 Apply 버튼을 눌러서 변경할 수 있습니다; 설정 창을 게임 창 옆에 두고 사용자가 선호하는 옵션을 실험하여 고를 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 끄기를 권장드립니다.", + "GraphicsAALabel": "안티 앨리어싱:", + "GraphicsScalingFilterLabel": "스케일링 필터:", + "GraphicsScalingFilterTooltip": "해상도 스케일에 사용될 스케일링 필터를 선택하세요.\n\nBilinear는 3D 게임에서 잘 작동하며 안전한 기본값입니다.\n\nNearest는 픽셀 아트 게임에 추천합니다.\n\nFSR 1.0은 그저 샤프닝 필터임으로, FXAA나 SMAA와 같이 사용하는 것은 권장하지 않습니다.\n\n이 옵션은 게임이 구동중일 때에도 아래 Apply 버튼을 눌러서 변경할 수 있습니다; 설정 창을 게임 창 옆에 두고 사용자가 선호하는 옵션을 실험하여 고를 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 BILINEAR로 두세요.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "수준", + "GraphicsScalingFilterLevelTooltip": "FSR 1.0의 샤프닝 레벨을 설정하세요. 높을수록 더 또렷해집니다.", + "SmaaLow": "SMAA 낮음", + "SmaaMedium": "SMAA 중간", + "SmaaHigh": "SMAA 높음", + "SmaaUltra": "SMAA 울트라", + "UserEditorTitle": "사용자 수정", + "UserEditorTitleCreate": "사용자 생성", + "SettingsTabNetworkInterface": "네트워크 인터페이스:", + "NetworkInterfaceTooltip": "LAN/LDN 기능에 사용될 네트워크 인터페이스입니다.\n\nLAN 기능을 지원하는 게임에서 VPN이나 XLink Kai 등을 동시에 사용하면, 인터넷을 통해 동일 네트워크 연결인 것을 속일 수 있습니다.\n\n이 옵션에 대해 잘 모른다면 기본값으로 설정하세요.", + "NetworkInterfaceDefault": "기본", + "PackagingShaders": "셰이더 패키징 중", + "AboutChangelogButton": "GitHub에서 변경 로그 보기", + "AboutChangelogButtonTooltipMessage": "기본 브라우저에서 이 버전의 변경 로그를 열려면 클릭합니다.", + "SettingsTabNetworkMultiplayer": "멀티 플레이어", + "MultiplayerMode": "모드 :", + "MultiplayerModeTooltip": "LDN 멀티플레이어 모드를 변경합니다.\n\nLdnMitm은 로컬 무선/로컬 플레이 기능을 수정하여 LAN 모드에 있는 것처럼 만들어 로컬이나 동일한 네트워크 상에 있는 다른 Ryujinx 인스턴스나 커펌된 닌텐도 스위치 콘솔(ldn_mitm 모듈 설치 필요)과 연결할 수 있습니다.\n\n멀티플레이어 모드는 모든 플레이어들이 동일한 게임 버전을 요구합니다. 예를 들어 슈퍼 스매시브라더스 얼티밋 v13.0.1 사용자는 v13.0.0 사용자와 연결할 수 없습니다.\n\n해당 옵션에 대해 잘 모른다면 비활성화해두세요.", + "MultiplayerModeDisabled": "비활성화됨", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json new file mode 100644 index 00000000..9d1bd7b4 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -0,0 +1,780 @@ +{ + "Language": "Polski", + "MenuBarFileOpenApplet": "Otwórz Aplet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Otwórz aplet Mii Editor w trybie indywidualnym", + "SettingsTabInputDirectMouseAccess": "Bezpośredni dostęp do myszy", + "SettingsTabSystemMemoryManagerMode": "Tryb menedżera pamięci:", + "SettingsTabSystemMemoryManagerModeSoftware": "Oprogramowanie", + "SettingsTabSystemMemoryManagerModeHost": "Gospodarz (szybki)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Gospodarza (NIESPRAWDZONY, najszybszy, niebezpieczne)", + "SettingsTabSystemUseHypervisor": "Użyj Hipernadzorcy", + "MenuBarFile": "_Plik", + "MenuBarFileOpenFromFile": "_Załaduj aplikację z pliku", + "MenuBarFileOpenUnpacked": "Załaduj _rozpakowaną grę", + "MenuBarFileOpenEmuFolder": "Otwórz folder Ryujinx", + "MenuBarFileOpenLogsFolder": "Otwórz folder plików dziennika zdarzeń", + "MenuBarFileExit": "_Wyjdź", + "MenuBarOptions": "_Opcje", + "MenuBarOptionsToggleFullscreen": "Przełącz na tryb pełnoekranowy", + "MenuBarOptionsStartGamesInFullscreen": "Uruchamiaj gry w trybie pełnoekranowym", + "MenuBarOptionsStopEmulation": "Zatrzymaj emulację", + "MenuBarOptionsSettings": "_Ustawienia", + "MenuBarOptionsManageUserProfiles": "_Zarządzaj profilami użytkowników", + "MenuBarActions": "_Akcje", + "MenuBarOptionsSimulateWakeUpMessage": "Symuluj wiadomość wybudzania", + "MenuBarActionsScanAmiibo": "Skanuj Amiibo", + "MenuBarTools": "_Narzędzia", + "MenuBarToolsInstallFirmware": "Zainstaluj oprogramowanie", + "MenuBarFileToolsInstallFirmwareFromFile": "Zainstaluj oprogramowanie z XCI lub ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Zainstaluj oprogramowanie z katalogu", + "MenuBarToolsManageFileTypes": "Zarządzaj rodzajami plików", + "MenuBarToolsInstallFileTypes": "Typy plików instalacyjnych", + "MenuBarToolsUninstallFileTypes": "Typy plików dezinstalacyjnych", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "_Pomoc", + "MenuBarHelpCheckForUpdates": "Sprawdź aktualizacje", + "MenuBarHelpAbout": "O programie", + "MenuSearch": "Wyszukaj...", + "GameListHeaderFavorite": "Ulubione", + "GameListHeaderIcon": "Ikona", + "GameListHeaderApplication": "Nazwa", + "GameListHeaderDeveloper": "Twórca", + "GameListHeaderVersion": "Wersja", + "GameListHeaderTimePlayed": "Czas w grze:", + "GameListHeaderLastPlayed": "Ostatnio grane", + "GameListHeaderFileExtension": "Rozszerzenie pliku", + "GameListHeaderFileSize": "Rozmiar pliku", + "GameListHeaderPath": "Ścieżka", + "GameListContextMenuOpenUserSaveDirectory": "Otwórz katalog zapisów użytkownika", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Otwiera katalog, który zawiera zapis użytkownika dla tej aplikacji", + "GameListContextMenuOpenDeviceSaveDirectory": "Otwórz katalog zapisów urządzenia", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Otwiera katalog, który zawiera zapis urządzenia dla tej aplikacji", + "GameListContextMenuOpenBcatSaveDirectory": "Otwórz katalog zapisu BCAT obecnego użytkownika", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Otwiera katalog, który zawiera zapis BCAT dla tej aplikacji", + "GameListContextMenuManageTitleUpdates": "Zarządzaj aktualizacjami", + "GameListContextMenuManageTitleUpdatesToolTip": "Otwiera okno zarządzania aktualizacjami danej aplikacji", + "GameListContextMenuManageDlc": "Zarządzaj dodatkową zawartością (DLC)", + "GameListContextMenuManageDlcToolTip": "Otwiera okno zarządzania dodatkową zawartością", + "GameListContextMenuCacheManagement": "Zarządzanie Cache", + "GameListContextMenuCacheManagementPurgePptc": "Zakolejkuj rekompilację PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Zainicjuj Rekompilację PPTC przy następnym uruchomieniu gry", + "GameListContextMenuCacheManagementPurgeShaderCache": "Wyczyść pamięć podręczną cieni", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Usuwa pamięć podręczną cieni danej aplikacji", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Otwórz katalog PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Otwiera katalog, który zawiera pamięć podręczną PPTC aplikacji", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Otwórz katalog pamięci podręcznej cieni", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Otwiera katalog, który zawiera pamięć podręczną cieni aplikacji", + "GameListContextMenuExtractData": "Wypakuj dane", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Wyodrębnij sekcję ExeFS z bieżącej konfiguracji aplikacji (w tym aktualizacje)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Wyodrębnij sekcję RomFS z bieżącej konfiguracji aplikacji (w tym aktualizacje)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Wyodrębnij sekcję z logiem z bieżącej konfiguracji aplikacji (w tym aktualizacje)", + "GameListContextMenuCreateShortcut": "Utwórz skrót aplikacji", + "GameListContextMenuCreateShortcutToolTip": "Utwórz skrót na pulpicie, który uruchamia wybraną aplikację", + "GameListContextMenuCreateShortcutToolTipMacOS": "Utwórz skrót w folderze 'Aplikacje' w systemie macOS, który uruchamia wybraną aplikację", + "GameListContextMenuOpenModsDirectory": "Otwórz katalog modów", + "GameListContextMenuOpenModsDirectoryToolTip": "Otwiera katalog zawierający mody dla danej aplikacji", + "GameListContextMenuOpenSdModsDirectory": "Otwórz katalog modów Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Otwiera alternatywny katalog Atmosphere na karcie SD, który zawiera mody danej aplikacji. Przydatne dla modów przygotowanych pod prawdziwy sprzęt.", + "StatusBarGamesLoaded": "{0}/{1} Załadowane gry", + "StatusBarSystemVersion": "Wersja systemu: {0}", + "LinuxVmMaxMapCountDialogTitle": "Wykryto niski limit dla przypisań pamięci", + "LinuxVmMaxMapCountDialogTextPrimary": "Czy chcesz zwiększyć wartość vm.max_map_count do {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Niektóre gry mogą próbować przypisać sobie więcej pamięci niż obecnie, jest to dozwolone. Ryujinx ulegnie awarii, gdy limit zostanie przekroczony.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Tak, do następnego ponownego uruchomienia", + "LinuxVmMaxMapCountDialogButtonPersistent": "Tak, permanentnie ", + "LinuxVmMaxMapCountWarningTextPrimary": "Maksymalna ilość przypisanej pamięci jest mniejsza niż zalecana.", + "LinuxVmMaxMapCountWarningTextSecondary": "Obecna wartość vm.max_map_count ({0}) jest mniejsza niż {1}. Niektóre gry mogą próbować stworzyć więcej mapowań pamięci niż obecnie jest to dozwolone. Ryujinx napotka crash, gdy dojdzie do takiej sytuacji.\n\nMożesz chcieć ręcznie zwiększyć limit lub zainstalować pkexec, co pozwala Ryujinx na pomoc w tym zakresie.", + "Settings": "Ustawienia", + "SettingsTabGeneral": "Interfejs użytkownika", + "SettingsTabGeneralGeneral": "Ogólne", + "SettingsTabGeneralEnableDiscordRichPresence": "Włącz Bogatą Obecność Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Sprawdzaj aktualizacje przy uruchomieniu", + "SettingsTabGeneralShowConfirmExitDialog": "Pokazuj okno dialogowe \"Potwierdź wyjście\"", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", + "SettingsTabGeneralHideCursor": "Ukryj kursor:", + "SettingsTabGeneralHideCursorNever": "Nigdy", + "SettingsTabGeneralHideCursorOnIdle": "Gdy bezczynny", + "SettingsTabGeneralHideCursorAlways": "Zawsze", + "SettingsTabGeneralGameDirectories": "Katalogi gier", + "SettingsTabGeneralAdd": "Dodaj", + "SettingsTabGeneralRemove": "Usuń", + "SettingsTabSystem": "System", + "SettingsTabSystemCore": "Główne", + "SettingsTabSystemSystemRegion": "Region systemu:", + "SettingsTabSystemSystemRegionJapan": "Japonia", + "SettingsTabSystemSystemRegionUSA": "Stany Zjednoczone", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "Chiny", + "SettingsTabSystemSystemRegionKorea": "Korea", + "SettingsTabSystemSystemRegionTaiwan": "Tajwan", + "SettingsTabSystemSystemLanguage": "Język systemu:", + "SettingsTabSystemSystemLanguageJapanese": "Japoński", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Angielski (Stany Zjednoczone)", + "SettingsTabSystemSystemLanguageFrench": "Francuski", + "SettingsTabSystemSystemLanguageGerman": "Niemiecki", + "SettingsTabSystemSystemLanguageItalian": "Włoski", + "SettingsTabSystemSystemLanguageSpanish": "Hiszpański", + "SettingsTabSystemSystemLanguageChinese": "Chiński", + "SettingsTabSystemSystemLanguageKorean": "Koreański", + "SettingsTabSystemSystemLanguageDutch": "Holenderski", + "SettingsTabSystemSystemLanguagePortuguese": "Portugalski", + "SettingsTabSystemSystemLanguageRussian": "Rosyjski", + "SettingsTabSystemSystemLanguageTaiwanese": "Tajwański", + "SettingsTabSystemSystemLanguageBritishEnglish": "Angielski (Wielka Brytania)", + "SettingsTabSystemSystemLanguageCanadianFrench": "Kanadyjski Francuski", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Hiszpański (Ameryka Łacińska)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chiński (Uproszczony)", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chiński (Tradycyjny)", + "SettingsTabSystemSystemTimeZone": "Strefa czasowa systemu:", + "SettingsTabSystemSystemTime": "Czas systemu:", + "SettingsTabSystemEnableVsync": "Synchronizacja pionowa", + "SettingsTabSystemEnablePptc": "PPTC (Profilowana pamięć podręczna trwałych łłumaczeń)", + "SettingsTabSystemEnableFsIntegrityChecks": "Sprawdzanie integralności systemu plików", + "SettingsTabSystemAudioBackend": "Backend Dżwięku:", + "SettingsTabSystemAudioBackendDummy": "Atrapa", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacki", + "SettingsTabSystemHacksNote": " (mogą powodować niestabilność)", + "SettingsTabSystemExpandDramSize": "Użyj alternatywnego układu pamięci (Deweloperzy)", + "SettingsTabSystemIgnoreMissingServices": "Ignoruj Brakujące Usługi", + "SettingsTabGraphics": "Grafika", + "SettingsTabGraphicsAPI": "Graficzne API", + "SettingsTabGraphicsEnableShaderCache": "Włącz pamięć podręczną cieni", + "SettingsTabGraphicsAnisotropicFiltering": "Filtrowanie anizotropowe:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Automatyczne", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Skalowanie rozdzielczości:", + "SettingsTabGraphicsResolutionScaleCustom": "Niestandardowa (Niezalecane)", + "SettingsTabGraphicsResolutionScaleNative": "Natywna (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (niezalecane)", + "SettingsTabGraphicsAspectRatio": "Format obrazu:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Rozciągnij do Okna", + "SettingsTabGraphicsDeveloperOptions": "Opcje programisty", + "SettingsTabGraphicsShaderDumpPath": "Ścieżka do zgranych cieni graficznych:", + "SettingsTabLogging": "Dziennik zdarzeń", + "SettingsTabLoggingLogging": "Dziennik zdarzeń", + "SettingsTabLoggingEnableLoggingToFile": "Włącz rejestrowanie zdarzeń do pliku", + "SettingsTabLoggingEnableStubLogs": "Wlącz Skróty Logów", + "SettingsTabLoggingEnableInfoLogs": "Włącz Logi Informacyjne", + "SettingsTabLoggingEnableWarningLogs": "Włącz Logi Ostrzeżeń", + "SettingsTabLoggingEnableErrorLogs": "Włącz Logi Błędów", + "SettingsTabLoggingEnableTraceLogs": "Włącz Logi Śledzenia", + "SettingsTabLoggingEnableGuestLogs": "Włącz Logi Gości", + "SettingsTabLoggingEnableFsAccessLogs": "Włącz Logi Dostępu do Systemu Plików", + "SettingsTabLoggingFsGlobalAccessLogMode": "Tryb globalnego dziennika zdarzeń systemu plików:", + "SettingsTabLoggingDeveloperOptions": "Opcje programisty (UWAGA: wpływa na wydajność)", + "SettingsTabLoggingDeveloperOptionsNote": "UWAGA: Pogrorszy wydajność", + "SettingsTabLoggingGraphicsBackendLogLevel": "Poziom rejestrowania do dziennika zdarzeń Backendu Graficznego:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nic", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Błędy", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Spowolnienia", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Wszystko", + "SettingsTabLoggingEnableDebugLogs": "Włącz dzienniki zdarzeń do debugowania", + "SettingsTabInput": "Sterowanie", + "SettingsTabInputEnableDockedMode": "Tryb zadokowany", + "SettingsTabInputDirectKeyboardAccess": "Bezpośredni dostęp do klawiatury", + "SettingsButtonSave": "Zapisz", + "SettingsButtonClose": "Zamknij", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Anuluj", + "SettingsButtonApply": "Zastosuj", + "ControllerSettingsPlayer": "Gracz", + "ControllerSettingsPlayer1": "Gracz 1", + "ControllerSettingsPlayer2": "Gracz 2", + "ControllerSettingsPlayer3": "Gracz 3", + "ControllerSettingsPlayer4": "Gracz 4", + "ControllerSettingsPlayer5": "Gracz 5", + "ControllerSettingsPlayer6": "Gracz 6", + "ControllerSettingsPlayer7": "Gracz 7", + "ControllerSettingsPlayer8": "Gracz 8", + "ControllerSettingsHandheld": "Przenośny", + "ControllerSettingsInputDevice": "Urządzenie wejściowe", + "ControllerSettingsRefresh": "Odśwież", + "ControllerSettingsDeviceDisabled": "Wyłączone", + "ControllerSettingsControllerType": "Typ kontrolera", + "ControllerSettingsControllerTypeHandheld": "Przenośny", + "ControllerSettingsControllerTypeProController": "Pro Kontroler", + "ControllerSettingsControllerTypeJoyConPair": "Para JoyCon-ów", + "ControllerSettingsControllerTypeJoyConLeft": "Lewy JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "Prawy JoyCon", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Domyślny", + "ControllerSettingsLoad": "Wczytaj", + "ControllerSettingsAdd": "Dodaj", + "ControllerSettingsRemove": "Usuń", + "ControllerSettingsButtons": "Przyciski", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Krzyżak (D-Pad)", + "ControllerSettingsDPadUp": "Góra", + "ControllerSettingsDPadDown": "Dół", + "ControllerSettingsDPadLeft": "Lewo", + "ControllerSettingsDPadRight": "Prawo", + "ControllerSettingsStickButton": "Przycisk", + "ControllerSettingsStickUp": "Góra ", + "ControllerSettingsStickDown": "Dół ", + "ControllerSettingsStickLeft": "Lewo", + "ControllerSettingsStickRight": "Prawo", + "ControllerSettingsStickStick": "Gałka", + "ControllerSettingsStickInvertXAxis": "Odwróć gałkę X", + "ControllerSettingsStickInvertYAxis": "Odwróć gałkę Y", + "ControllerSettingsStickDeadzone": "Martwa strefa:", + "ControllerSettingsLStick": "Lewa Gałka", + "ControllerSettingsRStick": "Prawa Gałka", + "ControllerSettingsTriggersLeft": "Lewe Triggery", + "ControllerSettingsTriggersRight": "Prawe Triggery", + "ControllerSettingsTriggersButtonsLeft": "Lewe Przyciski Triggerów", + "ControllerSettingsTriggersButtonsRight": "Prawe Przyciski Triggerów", + "ControllerSettingsTriggers": "Triggery", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Lewe Przyciski", + "ControllerSettingsExtraButtonsRight": "Prawe Przyciski", + "ControllerSettingsMisc": "Różne", + "ControllerSettingsTriggerThreshold": "Próg Triggerów:", + "ControllerSettingsMotion": "Ruch", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Użyj ruchu zgodnego z CemuHook", + "ControllerSettingsMotionControllerSlot": "Slot Kontrolera:", + "ControllerSettingsMotionMirrorInput": "Odzwierciedlaj Sterowanie", + "ControllerSettingsMotionRightJoyConSlot": "Prawy Slot JoyCon:", + "ControllerSettingsMotionServerHost": "Host Serwera:", + "ControllerSettingsMotionGyroSensitivity": "Czułość Żyroskopu:", + "ControllerSettingsMotionGyroDeadzone": "Deadzone Żyroskopu:", + "ControllerSettingsSave": "Zapisz", + "ControllerSettingsClose": "Zamknij", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", + "UserProfilesSelectedUserProfile": "Wybrany profil użytkownika:", + "UserProfilesSaveProfileName": "Zapisz nazwę profilu", + "UserProfilesChangeProfileImage": "Zmień obrazek profilu", + "UserProfilesAvailableUserProfiles": "Dostępne profile użytkownika:", + "UserProfilesAddNewProfile": "Utwórz profil", + "UserProfilesDelete": "Usuń", + "UserProfilesClose": "Zamknij", + "ProfileNameSelectionWatermark": "Wybierz pseudonim", + "ProfileImageSelectionTitle": "Wybór Obrazu Profilu", + "ProfileImageSelectionHeader": "Wybierz zdjęcie profilowe", + "ProfileImageSelectionNote": "Możesz zaimportować niestandardowy obraz profilu lub wybrać awatar z firmware'u systemowego", + "ProfileImageSelectionImportImage": "Importuj Plik Obrazu", + "ProfileImageSelectionSelectAvatar": "Wybierz domyślny awatar z oprogramowania konsoli", + "InputDialogTitle": "Okno Dialogowe Wprowadzania", + "InputDialogOk": "OK", + "InputDialogCancel": "Anuluj", + "InputDialogAddNewProfileTitle": "Wybierz nazwę profilu", + "InputDialogAddNewProfileHeader": "Wprowadź nazwę profilu", + "InputDialogAddNewProfileSubtext": "(Maksymalna długość: {0})", + "AvatarChoose": "Wybierz awatar", + "AvatarSetBackgroundColor": "Ustaw kolor tła", + "AvatarClose": "Zamknij", + "ControllerSettingsLoadProfileToolTip": "Wczytaj profil", + "ControllerSettingsAddProfileToolTip": "Dodaj profil", + "ControllerSettingsRemoveProfileToolTip": "Usuń profil", + "ControllerSettingsSaveProfileToolTip": "Zapisz profil", + "MenuBarFileToolsTakeScreenshot": "Zrób zrzut ekranu", + "MenuBarFileToolsHideUi": "Ukryj interfejs użytkownika", + "GameListContextMenuRunApplication": "Uruchom aplikację ", + "GameListContextMenuToggleFavorite": "Przełącz na ulubione", + "GameListContextMenuToggleFavoriteToolTip": "Przełącz status Ulubionej Gry", + "SettingsTabGeneralTheme": "Motyw:", + "SettingsTabGeneralThemeDark": "Ciemny", + "SettingsTabGeneralThemeLight": "Jasny", + "ControllerSettingsConfigureGeneral": "Konfiguruj", + "ControllerSettingsRumble": "Wibracje", + "ControllerSettingsRumbleStrongMultiplier": "Mnożnik mocnych wibracji", + "ControllerSettingsRumbleWeakMultiplier": "Mnożnik słabych wibracji", + "DialogMessageSaveNotAvailableMessage": "Nie ma zapisanych danych dla {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Czy chcesz utworzyć zapis danych dla tej gry?", + "DialogConfirmationTitle": "Ryujinx - Potwierdzenie", + "DialogUpdaterTitle": "Ryujinx - Asystent aktualizacji", + "DialogErrorTitle": "Ryujinx - Błąd", + "DialogWarningTitle": "Ryujinx - Ostrzeżenie", + "DialogExitTitle": "Ryujinx - Wyjdź", + "DialogErrorMessage": "Ryujinx napotkał błąd", + "DialogExitMessage": "Czy na pewno chcesz zamknąć Ryujinx?", + "DialogExitSubMessage": "Wszystkie niezapisane dane zostaną utracone!", + "DialogMessageCreateSaveErrorMessage": "Wystąpił błąd podczas tworzenia określonych zapisanych danych: {0}", + "DialogMessageFindSaveErrorMessage": "Wystąpił błąd podczas próby znalezienia określonych zapisanych danych: {0}", + "FolderDialogExtractTitle": "Wybierz folder, do którego chcesz rozpakować", + "DialogNcaExtractionMessage": "Wypakowywanie sekcji {0} z {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Asystent wypakowania sekcji NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Niepowodzenie podczas wypakowywania. W wybranym pliku nie było głównego NCA.", + "DialogNcaExtractionCheckLogErrorMessage": "Niepowodzenie podczas wypakowywania. Przeczytaj plik dziennika, aby uzyskać więcej informacji.", + "DialogNcaExtractionSuccessMessage": "Wypakowywanie zakończone pomyślnie.", + "DialogUpdaterConvertFailedMessage": "Nie udało się przekonwertować obecnej wersji Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Anulowanie aktualizacji!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Używasz już najnowszej wersji Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Wystąpił błąd podczas próby uzyskania informacji o obecnej wersji z GitHub Release. Może to być spowodowane nową wersją kompilowaną przez GitHub Actions. Spróbuj ponownie za kilka minut.", + "DialogUpdaterConvertFailedGithubMessage": "Nie udało się przekonwertować otrzymanej wersji Ryujinx z Github Release.", + "DialogUpdaterDownloadingMessage": "Pobieranie aktualizacji...", + "DialogUpdaterExtractionMessage": "Wypakowywanie Aktualizacji...", + "DialogUpdaterRenamingMessage": "Zmiana Nazwy Aktualizacji...", + "DialogUpdaterAddingFilesMessage": "Dodawanie Nowej Aktualizacji...", + "DialogUpdaterCompleteMessage": "Aktualizacja Zakończona!", + "DialogUpdaterRestartMessage": "Czy chcesz teraz zrestartować Ryujinx?", + "DialogUpdaterNoInternetMessage": "Nie masz połączenia z Internetem!", + "DialogUpdaterNoInternetSubMessage": "Sprawdź, czy masz działające połączenie internetowe!", + "DialogUpdaterDirtyBuildMessage": "Nie możesz zaktualizować Dirty wersji Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Pobierz Ryujinx ze strony https://ryujinx.org/, jeśli szukasz obsługiwanej wersji.", + "DialogRestartRequiredMessage": "Wymagane Ponowne Uruchomienie", + "DialogThemeRestartMessage": "Motyw został zapisany. Aby zastosować motyw, konieczne jest ponowne uruchomienie.", + "DialogThemeRestartSubMessage": "Czy chcesz uruchomić ponownie?", + "DialogFirmwareInstallEmbeddedMessage": "Czy chcesz zainstalować firmware wbudowany w tę grę? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Nie znaleziono zainstalowanego oprogramowania, ale Ryujinx był w stanie zainstalować oprogramowanie {0} z dostarczonej gry.\n\nEmulator uruchomi się teraz.", + "DialogFirmwareNoFirmwareInstalledMessage": "Brak Zainstalowanego Firmware'u", + "DialogFirmwareInstalledMessage": "Firmware {0} został zainstalowany", + "DialogInstallFileTypesSuccessMessage": "Pomyślnie zainstalowano typy plików!", + "DialogInstallFileTypesErrorMessage": "Nie udało się zainstalować typów plików.", + "DialogUninstallFileTypesSuccessMessage": "Pomyślnie odinstalowano typy plików!", + "DialogUninstallFileTypesErrorMessage": "Nie udało się odinstalować typów plików.", + "DialogOpenSettingsWindowLabel": "Otwórz Okno Ustawień", + "DialogControllerAppletTitle": "Aplet Kontrolera", + "DialogMessageDialogErrorExceptionMessage": "Błąd wyświetlania okna Dialogowego Wiadomości: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Błąd wyświetlania Klawiatury Oprogramowania: {0}", + "DialogErrorAppletErrorExceptionMessage": "Błąd wyświetlania okna Dialogowego ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nAby uzyskać więcej informacji o tym, jak naprawić ten błąd, zapoznaj się z naszym Przewodnikiem instalacji.", + "DialogUserErrorDialogTitle": "Błąd Ryujinxa ({0})", + "DialogAmiiboApiTitle": "API Amiibo", + "DialogAmiiboApiFailFetchMessage": "Wystąpił błąd podczas pobierania informacji z API.", + "DialogAmiiboApiConnectErrorMessage": "Nie można połączyć się z serwerem API Amiibo. Usługa może nie działać lub może być konieczne sprawdzenie, czy połączenie internetowe jest online.", + "DialogProfileInvalidProfileErrorMessage": "Profil {0} jest niezgodny z bieżącym systemem konfiguracji sterowania.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Profil Domyślny nie może zostać nadpisany", + "DialogProfileDeleteProfileTitle": "Usuwanie Profilu", + "DialogProfileDeleteProfileMessage": "Ta czynność jest nieodwracalna, czy na pewno chcesz kontynuować?", + "DialogWarning": "Uwaga", + "DialogPPTCDeletionMessage": "Masz zamiar umieścić w kolejce rekompilację PPTC przy następnym uruchomieniu:\n\n{0}\n\nCzy na pewno chcesz kontynuować?", + "DialogPPTCDeletionErrorMessage": "Błąd czyszczenia cache PPTC w {0}: {1}", + "DialogShaderDeletionMessage": "Zamierzasz usunąć cache Shaderów dla :\n\n{0}\n\nNa pewno chcesz kontynuować?", + "DialogShaderDeletionErrorMessage": "Błąd czyszczenia cache Shaderów w {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx napotkał błąd", + "DialogInvalidTitleIdErrorMessage": "Błąd UI: Wybrana gra nie miała prawidłowego ID tytułu", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Nie znaleziono prawidłowego firmware'u systemowego w {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Zainstaluj Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Wersja systemu {0} zostanie zainstalowana.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nZastąpi to obecną wersję systemu {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nCzy chcesz kontynuować?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalowanie firmware'u...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Wersja systemu {0} została pomyślnie zainstalowana.", + "DialogUserProfileDeletionWarningMessage": "Nie będzie innych profili do otwarcia, jeśli wybrany profil zostanie usunięty", + "DialogUserProfileDeletionConfirmMessage": "Czy chcesz usunąć wybrany profil", + "DialogUserProfileUnsavedChangesTitle": "Uwaga - Niezapisane zmiany", + "DialogUserProfileUnsavedChangesMessage": "Wprowadziłeś zmiany dla tego profilu użytkownika, które nie zostały zapisane.", + "DialogUserProfileUnsavedChangesSubMessage": "Czy chcesz odrzucić zmiany?", + "DialogControllerSettingsModifiedConfirmMessage": "Aktualne ustawienia kontrolera zostały zaktualizowane.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Czy chcesz zapisać?", + "DialogLoadFileErrorMessage": "{0}. Błędny plik: {1}", + "DialogModAlreadyExistsMessage": "Modyfikacja już istnieje", + "DialogModInvalidMessage": "Podany katalog nie zawiera modyfikacji!", + "DialogModDeleteNoParentMessage": "Nie udało się usunąć: Nie można odnaleźć katalogu nadrzędnego dla modyfikacji \"{0}\"!", + "DialogDlcNoDlcErrorMessage": "Określony plik nie zawiera DLC dla wybranego tytułu!", + "DialogPerformanceCheckLoggingEnabledMessage": "Masz włączone rejestrowanie śledzenia, które jest przeznaczone tylko dla programistów.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Aby uzyskać optymalną wydajność, zaleca się wyłączenie rejestrowania śledzenia. Czy chcesz teraz wyłączyć rejestrowanie śledzenia?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Masz włączone zrzucanie shaderów, które jest przeznaczone tylko dla programistów.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Aby uzyskać optymalną wydajność, zaleca się wyłączenie zrzucania shaderów. Czy chcesz teraz wyłączyć zrzucanie shaderów?", + "DialogLoadAppGameAlreadyLoadedMessage": "Gra została już załadowana", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Zatrzymaj emulację lub zamknij emulator przed uruchomieniem innej gry.", + "DialogUpdateAddUpdateErrorMessage": "Określony plik nie zawiera aktualizacji dla wybranego tytułu!", + "DialogSettingsBackendThreadingWarningTitle": "Ostrzeżenie — Wątki Backend", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx musi zostać ponownie uruchomiony po zmianie tej opcji, aby działał w pełni. W zależności od platformy może być konieczne ręczne wyłączenie sterownika wielowątkowości podczas korzystania z Ryujinx.", + "DialogModManagerDeletionWarningMessage": "Zamierzasz usunąć modyfikacje: {0}\n\nCzy na pewno chcesz kontynuować?", + "DialogModManagerDeletionAllWarningMessage": "Zamierzasz usunąć wszystkie modyfikacje dla wybranego tytułu: {0}\n\nCzy na pewno chcesz kontynuować?", + "SettingsTabGraphicsFeaturesOptions": "Funkcje", + "SettingsTabGraphicsBackendMultithreading": "Wielowątkowość Backendu Graficznego:", + "CommonAuto": "Auto", + "CommonOff": "Wyłączone", + "CommonOn": "Włączone", + "InputDialogYes": "Tak", + "InputDialogNo": "Nie", + "DialogProfileInvalidProfileNameErrorMessage": "Nazwa pliku zawiera nieprawidłowe znaki. Proszę spróbuj ponownie.", + "MenuBarOptionsPauseEmulation": "Pauza", + "MenuBarOptionsResumeEmulation": "Wznów", + "AboutUrlTooltipMessage": "Kliknij, aby otworzyć stronę Ryujinx w domyślnej przeglądarce.", + "AboutDisclaimerMessage": "Ryujinx nie jest w żaden sposób powiązany z Nintendo™,\nani z żadnym z jej partnerów.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) jest używane\nw naszej emulacji Amiibo.", + "AboutPatreonUrlTooltipMessage": "Kliknij, aby otworzyć stronę Patreon Ryujinx w domyślnej przeglądarce.", + "AboutGithubUrlTooltipMessage": "Kliknij, aby otworzyć stronę GitHub Ryujinx w domyślnej przeglądarce.", + "AboutDiscordUrlTooltipMessage": "Kliknij, aby otworzyć zaproszenie na serwer Discord Ryujinx w domyślnej przeglądarce.", + "AboutTwitterUrlTooltipMessage": "Kliknij, aby otworzyć stronę Twitter Ryujinx w domyślnej przeglądarce.", + "AboutRyujinxAboutTitle": "O Aplikacji:", + "AboutRyujinxAboutContent": "Ryujinx to emulator Nintendo Switch™.\nWspieraj nas na Patreonie.\nOtrzymuj najnowsze wiadomości na naszym Twitterze lub Discordzie.\nDeweloperzy zainteresowani współpracą mogą dowiedzieć się więcej na naszym GitHubie lub Discordzie.", + "AboutRyujinxMaintainersTitle": "Utrzymywany Przez:", + "AboutRyujinxMaintainersContentTooltipMessage": "Kliknij, aby otworzyć stronę Współtwórcy w domyślnej przeglądarce.", + "AboutRyujinxSupprtersTitle": "Wspierani na Patreonie Przez:", + "AmiiboSeriesLabel": "Seria Amiibo", + "AmiiboCharacterLabel": "Postać", + "AmiiboScanButtonLabel": "Zeskanuj", + "AmiiboOptionsShowAllLabel": "Pokaż Wszystkie Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Użyj losowego UUID tagu", + "DlcManagerTableHeadingEnabledLabel": "Włączone", + "DlcManagerTableHeadingTitleIdLabel": "ID Tytułu", + "DlcManagerTableHeadingContainerPathLabel": "Ścieżka Kontenera", + "DlcManagerTableHeadingFullPathLabel": "Pełna Ścieżka", + "DlcManagerRemoveAllButton": "Usuń Wszystkie", + "DlcManagerEnableAllButton": "Włącz Wszystkie", + "DlcManagerDisableAllButton": "Wyłącz Wszystkie", + "ModManagerDeleteAllButton": "Usuń wszystko", + "MenuBarOptionsChangeLanguage": "Zmień język", + "MenuBarShowFileTypes": "Pokaż typy plików", + "CommonSort": "Sortuj", + "CommonShowNames": "Pokaż Nazwy", + "CommonFavorite": "Ulubione", + "OrderAscending": "Rosnąco", + "OrderDescending": "Malejąco", + "SettingsTabGraphicsFeatures": "Funkcje i Ulepszenia", + "ErrorWindowTitle": "Okno Błędu", + "ToggleDiscordTooltip": "Wybierz, czy chcesz wyświetlać Ryujinx w swojej \"aktualnie grane\" aktywności Discord", + "AddGameDirBoxTooltip": "Wprowadź katalog gier aby dodać go do listy", + "AddGameDirTooltip": "Dodaj katalog gier do listy", + "RemoveGameDirTooltip": "Usuń wybrany katalog gier", + "CustomThemeCheckTooltip": "Użyj niestandardowego motywu Avalonia dla GUI, aby zmienić wygląd menu emulatora", + "CustomThemePathTooltip": "Ścieżka do niestandardowego motywu GUI", + "CustomThemeBrowseTooltip": "Wyszukaj niestandardowy motyw GUI", + "DockModeToggleTooltip": "Tryb Zadokowany sprawia, że emulowany system zachowuje się jak zadokowany Nintendo Switch. Poprawia to jakość grafiki w większości gier. I odwrotnie, wyłączenie tej opcji sprawi, że emulowany system będzie zachowywał się jak przenośny Nintendo Switch, zmniejszając jakość grafiki.\n\nSkonfiguruj sterowanie gracza 1, jeśli planujesz używać trybu Zadokowanego; Skonfiguruj sterowanie przenośne, jeśli planujesz używać trybu przenośnego.\n\nPozostaw WŁĄCZONY, jeśli nie masz pewności.", + "DirectKeyboardTooltip": "Obsługa bezpośredniego dostępu do klawiatury (HID). Zapewnia dostęp gier do klawiatury jako urządzenia do wprowadzania tekstu.\n\nDziała tylko z grami, które natywnie wspierają użycie klawiatury w urządzeniu Switch hardware.\n\nPozostaw wyłączone w razie braku pewności.", + "DirectMouseTooltip": "Obsługa bezpośredniego dostępu do myszy (HID). Zapewnia dostęp gier do myszy jako urządzenia wskazującego.\n\nDziała tylko z grami, które natywnie obsługują przyciski myszy w urządzeniu Switch, które są nieliczne i daleko między nimi.\n\nPo włączeniu funkcja ekranu dotykowego może nie działać.\n\nPozostaw wyłączone w razie wątpliwości.", + "RegionTooltip": "Zmień Region Systemu", + "LanguageTooltip": "Zmień język systemu", + "TimezoneTooltip": "Zmień Strefę Czasową Systemu", + "TimeTooltip": "Zmień czas systemowy", + "VSyncToggleTooltip": "Synchronizacja pionowa emulowanej konsoli. Zasadniczo ogranicznik klatek dla większości gier; wyłączenie jej może spowodować, że gry będą działać z większą szybkością, ekrany wczytywania wydłużą się lub nawet utkną.\n\nMoże być przełączana w grze za pomocą preferowanego skrótu klawiszowego. Zalecamy to zrobić, jeśli planujesz ją wyłączyć.\n\nW razie wątpliwości pozostaw WŁĄCZONĄ.", + "PptcToggleTooltip": "Zapisuje przetłumaczone funkcje JIT, dzięki czemu nie muszą być tłumaczone za każdym razem, gdy gra się ładuje.\n\nZmniejsza zacinanie się i znacznie przyspiesza uruchamianie po pierwszym uruchomieniu gry.\n\nJeśli nie masz pewności, pozostaw WŁĄCZONE", + "FsIntegrityToggleTooltip": "Sprawdza pliki podczas uruchamiania gry i jeśli zostaną wykryte uszkodzone pliki, wyświetla w dzienniku błąd hash.\n\nNie ma wpływu na wydajność i ma pomóc w rozwiązywaniu problemów.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", + "AudioBackendTooltip": "Zmienia backend używany do renderowania dźwięku.\n\nSDL2 jest preferowany, podczas gdy OpenAL i SoundIO są używane jako rezerwy. Dummy nie będzie odtwarzać dźwięku.\n\nW razie wątpliwości ustaw SDL2.", + "MemoryManagerTooltip": "Zmień sposób mapowania i uzyskiwania dostępu do pamięci gości. Znacznie wpływa na wydajność emulowanego procesora.\n\nUstaw na HOST UNCHECKED, jeśli nie masz pewności.", + "MemoryManagerSoftwareTooltip": "Użyj tabeli stron oprogramowania do translacji adresów. Najwyższa celność, ale najwolniejsza wydajność.", + "MemoryManagerHostTooltip": "Bezpośrednio mapuj pamięć w przestrzeni adresowej hosta. Znacznie szybsza kompilacja i wykonanie JIT.", + "MemoryManagerUnsafeTooltip": "Bezpośrednio mapuj pamięć, ale nie maskuj adresu w przestrzeni adresowej gościa przed uzyskaniem dostępu. Szybciej, ale kosztem bezpieczeństwa. Aplikacja gościa może uzyskać dostęp do pamięci z dowolnego miejsca w Ryujinx, więc w tym trybie uruchamiaj tylko programy, którym ufasz.", + "UseHypervisorTooltip": "Użyj Hiperwizora zamiast JIT. Znacznie poprawia wydajność, gdy jest dostępny, ale może być niestabilny w swoim obecnym stanie ", + "DRamTooltip": "Wykorzystuje alternatywny układ MemoryMode, aby naśladować model rozwojowy Switcha.\n\nJest to przydatne tylko w przypadku pakietów tekstur o wyższej rozdzielczości lub modów w rozdzielczości 4k. NIE poprawia wydajności.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", + "IgnoreMissingServicesTooltip": "Ignoruje niezaimplementowane usługi Horizon OS. Może to pomóc w ominięciu awarii podczas uruchamiania niektórych gier.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", + "GraphicsBackendThreadingTooltip": "Wykonuje polecenia backend'u graficznego w drugim wątku.\n\nPrzyspiesza kompilację shaderów, zmniejsza zacinanie się i poprawia wydajność sterowników GPU bez własnej obsługi wielowątkowości. Nieco lepsza wydajność w sterownikach z wielowątkowością.\n\nUstaw na AUTO, jeśli nie masz pewności.", + "GalThreadingTooltip": "Wykonuje polecenia backend'u graficznego w drugim wątku.\n\nPrzyspiesza kompilację shaderów, zmniejsza zacinanie się i poprawia wydajność sterowników GPU bez własnej obsługi wielowątkowości. Nieco lepsza wydajność w sterownikach z wielowątkowością.\n\nUstaw na AUTO, jeśli nie masz pewności.", + "ShaderCacheToggleTooltip": "Zapisuje pamięć podręczną shaderów na dysku, co zmniejsza zacinanie się w kolejnych uruchomieniach.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleEntryTooltip": "Skala rozdzielczości zmiennoprzecinkowej, np. 1,5. Skale niecałkowite częściej powodują problemy lub awarie.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "ShaderDumpPathTooltip": "Ścieżka Zrzutu Shaderów Grafiki", + "FileLogTooltip": "Zapisuje logowanie konsoli w pliku dziennika na dysku. Nie wpływa na wydajność.", + "StubLogTooltip": "Wyświetla w konsoli skrótowe komunikaty dziennika. Nie wpływa na wydajność.", + "InfoLogTooltip": "Wyświetla komunikaty dziennika informacyjnego w konsoli. Nie wpływa na wydajność.", + "WarnLogTooltip": "Wyświetla komunikaty dziennika ostrzeżeń w konsoli. Nie wpływa na wydajność.", + "ErrorLogTooltip": "Wyświetla w konsoli komunikaty dziennika błędów. Nie wpływa na wydajność.", + "TraceLogTooltip": "Wyświetla komunikaty dziennika śledzenia w konsoli. Nie wpływa na wydajność.", + "GuestLogTooltip": "Wyświetla komunikaty dziennika gości w konsoli. Nie wpływa na wydajność.", + "FileAccessLogTooltip": "Wyświetla w konsoli komunikaty dziennika dostępu do plików.", + "FSAccessLogModeTooltip": "Włącza wyjście dziennika dostępu FS do konsoli. Możliwe tryby to 0-3", + "DeveloperOptionTooltip": "Używaj ostrożnie", + "OpenGlLogLevel": "Wymaga włączonych odpowiednich poziomów logów", + "DebugLogTooltip": "Wyświetla komunikaty dziennika debugowania w konsoli.\n\nUżywaj tego tylko na wyraźne polecenie członka załogi, ponieważ utrudni to odczytanie dzienników i pogorszy wydajność emulatora.", + "LoadApplicationFileTooltip": "Otwórz eksplorator plików, aby wybrać plik kompatybilny z Switch do wczytania", + "LoadApplicationFolderTooltip": "Otwórz eksplorator plików, aby wybrać zgodną z Switch, rozpakowaną aplikację do załadowania", + "OpenRyujinxFolderTooltip": "Otwórz folder systemu plików Ryujinx", + "OpenRyujinxLogsTooltip": "Otwiera folder, w którym zapisywane są logi", + "ExitTooltip": "Wyjdź z Ryujinx", + "OpenSettingsTooltip": "Otwórz okno ustawień", + "OpenProfileManagerTooltip": "Otwórz okno Menedżera Profili Użytkownika", + "StopEmulationTooltip": "Zatrzymaj emulację bieżącej gry i wróć do wyboru gier", + "CheckUpdatesTooltip": "Sprawdź aktualizacje Ryujinx", + "OpenAboutTooltip": "Otwórz Okno Informacje", + "GridSize": "Wielkość siatki", + "GridSizeTooltip": "Zmień rozmiar elementów siatki", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brazylijski Portugalski", + "AboutRyujinxContributorsButtonHeader": "Zobacz Wszystkich Współtwórców", + "SettingsTabSystemAudioVolume": "Głośność: ", + "AudioVolumeTooltip": "Zmień Głośność Dźwięku", + "SettingsTabSystemEnableInternetAccess": "Dostęp do Internetu Gościa/Tryb LAN", + "EnableInternetAccessTooltip": "Pozwala emulowanej aplikacji na łączenie się z Internetem.\n\nGry w trybie LAN mogą łączyć się ze sobą, gdy ta opcja jest włączona, a systemy są połączone z tym samym punktem dostępu. Dotyczy to również prawdziwych konsol.\n\nNie pozwala na łączenie się z serwerami Nintendo. Może powodować awarie niektórych gier, które próbują połączyć się z Internetem.\n\nPozostaw WYŁĄCZONE, jeśli nie masz pewności.", + "GameListContextMenuManageCheatToolTip": "Zarządzaj Kodami", + "GameListContextMenuManageCheat": "Zarządzaj Kodami", + "GameListContextMenuManageModToolTip": "Zarządzaj modyfikacjami", + "GameListContextMenuManageMod": "Zarządzaj modyfikacjami", + "ControllerSettingsStickRange": "Zasięg:", + "DialogStopEmulationTitle": "Ryujinx - Zatrzymaj Emulację", + "DialogStopEmulationMessage": "Czy na pewno chcesz zatrzymać emulację?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Dżwięk", + "SettingsTabNetwork": "Sieć", + "SettingsTabNetworkConnection": "Połączenie Sieciowe", + "SettingsTabCpuCache": "Cache CPU", + "SettingsTabCpuMemory": "Pamięć CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Zaktualizuj Ryujinx przez FlatHub.", + "UpdaterDisabledWarningTitle": "Aktualizator Wyłączony!", + "ControllerSettingsRotate90": "Obróć o 90° w Prawo", + "IconSize": "Rozmiar ikon", + "IconSizeTooltip": "Zmień rozmiar ikon gry", + "MenuBarOptionsShowConsole": "Pokaż Konsolę", + "ShaderCachePurgeError": "Błąd podczas czyszczenia cache shaderów w {0}: {1}", + "UserErrorNoKeys": "Nie znaleziono kluczy", + "UserErrorNoFirmware": "Nie znaleziono firmware'u", + "UserErrorFirmwareParsingFailed": "Błąd parsowania firmware'u", + "UserErrorApplicationNotFound": "Aplikacja nie znaleziona", + "UserErrorUnknown": "Nieznany błąd", + "UserErrorUndefined": "Niezdefiniowany błąd", + "UserErrorNoKeysDescription": "Ryujinx nie mógł znaleźć twojego pliku 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx nie mógł znaleźć żadnego zainstalowanego firmware'u", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx nie był w stanie zparsować dostarczonego firmware'u. Jest to zwykle spowodowane nieaktualnymi kluczami.", + "UserErrorApplicationNotFoundDescription": "Ryujinx nie mógł znaleźć prawidłowej aplikacji na podanej ścieżce.", + "UserErrorUnknownDescription": "Wystąpił nieznany błąd!", + "UserErrorUndefinedDescription": "Wystąpił niezdefiniowany błąd! To nie powinno się zdarzyć, skontaktuj się z deweloperem!", + "OpenSetupGuideMessage": "Otwórz Podręcznik Konfiguracji", + "NoUpdate": "Brak Aktualizacji", + "TitleUpdateVersionLabel": "Wersja {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Potwierdzenie", + "FileDialogAllTypes": "Wszystkie typy", + "Never": "Nigdy", + "SwkbdMinCharacters": "Musi mieć co najmniej {0} znaków", + "SwkbdMinRangeCharacters": "Musi mieć długość od {0}-{1} znaków", + "SoftwareKeyboard": "Klawiatura Oprogramowania", + "SoftwareKeyboardModeNumeric": "Może składać się jedynie z 0-9 lub '.'", + "SoftwareKeyboardModeAlphabet": "Nie może zawierać znaków CJK", + "SoftwareKeyboardModeASCII": "Musi zawierać tylko tekst ASCII", + "ControllerAppletControllers": "Obsługiwane Kontrolery:", + "ControllerAppletPlayers": "Gracze:", + "ControllerAppletDescription": "Twoja aktualna konfiguracja jest nieprawidłowa. Otwórz ustawienia i skonfiguruj swoje wejścia.", + "ControllerAppletDocked": "Ustawiony tryb zadokowany. Sterowanie przenośne powinno być wyłączone.", + "UpdaterRenaming": "Zmienianie Nazw Starych Plików...", + "UpdaterRenameFailed": "Aktualizator nie mógł zmienić nazwy pliku: {0}", + "UpdaterAddingFiles": "Dodawanie Nowych Plików...", + "UpdaterExtracting": "Wypakowywanie Aktualizacji...", + "UpdaterDownloading": "Pobieranie Aktualizacji...", + "Game": "Gra", + "Docked": "Zadokowany", + "Handheld": "Przenośny", + "ConnectionError": "Błąd Połączenia.", + "AboutPageDeveloperListMore": "{0} i więcej...", + "ApiError": "Błąd API.", + "LoadingHeading": "Wczytywanie {0}", + "CompilingPPTC": "Kompilowanie PTC", + "CompilingShaders": "Kompilowanie Shaderów", + "AllKeyboards": "Wszystkie klawiatury", + "OpenFileDialogTitle": "Wybierz obsługiwany plik do otwarcia", + "OpenFolderDialogTitle": "Wybierz folder z rozpakowaną grą", + "AllSupportedFormats": "Wszystkie Obsługiwane Formaty", + "RyujinxUpdater": "Aktualizator Ryujinx", + "SettingsTabHotkeys": "Skróty Klawiszowe Klawiatury", + "SettingsTabHotkeysHotkeys": "Skróty Klawiszowe Klawiatury", + "SettingsTabHotkeysToggleVsyncHotkey": "Przełącz VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Zrzut Ekranu:", + "SettingsTabHotkeysShowUiHotkey": "Pokaż UI:", + "SettingsTabHotkeysPauseHotkey": "Pauza:", + "SettingsTabHotkeysToggleMuteHotkey": "Wycisz:", + "ControllerMotionTitle": "Ustawienia Sterowania Ruchowego", + "ControllerRumbleTitle": "Ustawienia Wibracji", + "SettingsSelectThemeFileDialogTitle": "Wybierz Plik Motywu", + "SettingsXamlThemeFile": "Plik Motywu Xaml", + "AvatarWindowTitle": "Zarządzaj Kontami — Avatar", + "Amiibo": "Amiibo", + "Unknown": "Nieznane", + "Usage": "Użycie", + "Writable": "Zapisywalne", + "SelectDlcDialogTitle": "Wybierz pliki DLC", + "SelectUpdateDialogTitle": "Wybierz pliki aktualizacji", + "SelectModDialogTitle": "Wybierz katalog modów", + "UserProfileWindowTitle": "Menedżer Profili Użytkowników", + "CheatWindowTitle": "Menedżer Kodów", + "DlcWindowTitle": "Menedżer Zawartości do Pobrania", + "ModWindowTitle": "Zarządzaj modami dla {0} ({1})", + "UpdateWindowTitle": "Menedżer Aktualizacji Tytułu", + "CheatWindowHeading": "Kody Dostępne dla {0} [{1}]", + "BuildId": "Identyfikator wersji:", + "DlcWindowHeading": "{0} Zawartości do Pobrania dostępna dla {1} ({2})", + "ModWindowHeading": "{0} Mod(y/ów)", + "UserProfilesEditProfile": "Edytuj Zaznaczone", + "Cancel": "Anuluj", + "Save": "Zapisz", + "Discard": "Odrzuć", + "Paused": "Wstrzymano", + "UserProfilesSetProfileImage": "Ustaw Obraz Profilu", + "UserProfileEmptyNameError": "Nazwa jest wymagana", + "UserProfileNoImageError": "Należy ustawić obraz profilowy", + "GameUpdateWindowHeading": "{0} Aktualizacje dostępne dla {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Zwiększ Rozdzielczość:", + "SettingsTabHotkeysResScaleDownHotkey": "Zmniejsz Rozdzielczość:", + "UserProfilesName": "Nazwa:", + "UserProfilesUserId": "ID Użytkownika:", + "SettingsTabGraphicsBackend": "Backend Graficzny", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsEnableTextureRecompression": "Włącz Rekompresję Tekstur", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsTabGraphicsPreferredGpu": "Preferowane GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Wybierz kartę graficzną, która będzie używana z backendem graficznym Vulkan.\n\nNie wpływa na GPU używane przez OpenGL.\n\nW razie wątpliwości ustaw flagę GPU jako \"dGPU\". Jeśli żadnej nie ma, pozostaw nietknięte.", + "SettingsAppRequiredRestartMessage": "Wymagane Zrestartowanie Ryujinx", + "SettingsGpuBackendRestartMessage": "Zmieniono ustawienia Backendu Graficznego lub GPU. Będzie to wymagało ponownego uruchomienia", + "SettingsGpuBackendRestartSubMessage": "Czy chcesz zrestartować teraz?", + "RyujinxUpdaterMessage": "Czy chcesz zaktualizować Ryujinx do najnowszej wersji?", + "SettingsTabHotkeysVolumeUpHotkey": "Zwiększ Głośność:", + "SettingsTabHotkeysVolumeDownHotkey": "Zmniejsz Głośność:", + "SettingsEnableMacroHLE": "Włącz Macro HLE", + "SettingsEnableMacroHLETooltip": "Wysokopoziomowa emulacja kodu GPU Macro.\n\nPoprawia wydajność, ale może powodować błędy graficzne w niektórych grach.\n\nW razie wątpliwości pozostaw WŁĄCZONE.", + "SettingsEnableColorSpacePassthrough": "Przekazywanie przestrzeni kolorów", + "SettingsEnableColorSpacePassthroughTooltip": "Nakazuje API Vulkan przekazywać informacje o kolorze bez określania przestrzeni kolorów. Dla użytkowników z wyświetlaczami o szerokim zakresie kolorów może to skutkować bardziej żywymi kolorami, kosztem ich poprawności.", + "VolumeShort": "Głoś", + "UserProfilesManageSaves": "Zarządzaj Zapisami", + "DeleteUserSave": "Czy chcesz usunąć zapis użytkownika dla tej gry?", + "IrreversibleActionNote": "Ta czynność nie jest odwracalna.", + "SaveManagerHeading": "Zarządzaj Zapisami dla {0}", + "SaveManagerTitle": "Menedżer Zapisów", + "Name": "Nazwa", + "Size": "Rozmiar", + "Search": "Wyszukaj", + "UserProfilesRecoverLostAccounts": "Odzyskaj Utracone Konta", + "Recover": "Odzyskaj", + "UserProfilesRecoverHeading": "Znaleziono zapisy dla następujących kont", + "UserProfilesRecoverEmptyList": "Brak profili do odzyskania", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAALabel": "Antyaliasing:", + "GraphicsScalingFilterLabel": "Filtr skalowania:", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Dwuliniowe", + "GraphicsScalingFilterNearest": "Najbliższe", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "Poziom", + "GraphicsScalingFilterLevelTooltip": "Ustaw poziom ostrzeżenia FSR 1.0. Wyższy jest ostrzejszy.", + "SmaaLow": "SMAA Niskie", + "SmaaMedium": "SMAA Średnie", + "SmaaHigh": "SMAA Wysokie", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Edytuj użytkownika", + "UserEditorTitleCreate": "Utwórz użytkownika", + "SettingsTabNetworkInterface": "Interfejs sieci:", + "NetworkInterfaceTooltip": "Interfejs sieciowy używany dla funkcji LAN/LDN.\n\nw połączeniu z VPN lub XLink Kai i grą z obsługą sieci LAN, może być użyty do spoofowania połączenia z tą samą siecią przez Internet.\n\nZostaw DOMYŚLNE, jeśli nie ma pewności.", + "NetworkInterfaceDefault": "Domyślny", + "PackagingShaders": "Pakuje Shadery ", + "AboutChangelogButton": "Zobacz listę zmian na GitHubie", + "AboutChangelogButtonTooltipMessage": "Kliknij, aby otworzyć listę zmian dla tej wersji w domyślnej przeglądarce.", + "SettingsTabNetworkMultiplayer": "Gra Wieloosobowa", + "MultiplayerMode": "Tryb:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Wyłączone", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json new file mode 100644 index 00000000..a8c244b6 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -0,0 +1,780 @@ +{ + "Language": "Português (BR)", + "MenuBarFileOpenApplet": "Abrir Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abrir editor Mii em modo avulso", + "SettingsTabInputDirectMouseAccess": "Acesso direto ao mouse", + "SettingsTabSystemMemoryManagerMode": "Modo de gerenciamento de memória:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Hóspede (rápido)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Hóspede sem verificação (mais rápido, inseguro)", + "SettingsTabSystemUseHypervisor": "Usar Hipervisor", + "MenuBarFile": "_Arquivo", + "MenuBarFileOpenFromFile": "_Abrir ROM do jogo...", + "MenuBarFileOpenUnpacked": "Abrir jogo _extraído...", + "MenuBarFileOpenEmuFolder": "Abrir diretório do e_mulador...", + "MenuBarFileOpenLogsFolder": "Abrir diretório de _logs...", + "MenuBarFileExit": "_Sair", + "MenuBarOptions": "_Opções", + "MenuBarOptionsToggleFullscreen": "_Mudar para tela cheia", + "MenuBarOptionsStartGamesInFullscreen": "Iniciar jogos em tela cheia", + "MenuBarOptionsStopEmulation": "_Encerrar emulação", + "MenuBarOptionsSettings": "_Configurações", + "MenuBarOptionsManageUserProfiles": "_Gerenciar perfis de usuário", + "MenuBarActions": "_Ações", + "MenuBarOptionsSimulateWakeUpMessage": "_Simular mensagem de acordar console", + "MenuBarActionsScanAmiibo": "Escanear um Amiibo", + "MenuBarTools": "_Ferramentas", + "MenuBarToolsInstallFirmware": "_Instalar firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware a partir de um arquivo ZIP/XCI", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Instalar firmware a partir de um diretório", + "MenuBarToolsManageFileTypes": "Gerenciar tipos de arquivo", + "MenuBarToolsInstallFileTypes": "Instalar tipos de arquivo", + "MenuBarToolsUninstallFileTypes": "Desinstalar tipos de arquivos", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "_Ajuda", + "MenuBarHelpCheckForUpdates": "_Verificar se há atualizações", + "MenuBarHelpAbout": "_Sobre", + "MenuSearch": "Buscar...", + "GameListHeaderFavorite": "Favorito", + "GameListHeaderIcon": "Ícone", + "GameListHeaderApplication": "Nome", + "GameListHeaderDeveloper": "Desenvolvedor", + "GameListHeaderVersion": "Versão", + "GameListHeaderTimePlayed": "Tempo de jogo", + "GameListHeaderLastPlayed": "Último jogo", + "GameListHeaderFileExtension": "Extensão", + "GameListHeaderFileSize": "Tamanho", + "GameListHeaderPath": "Caminho", + "GameListContextMenuOpenUserSaveDirectory": "Abrir diretório de saves do usuário", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Abre o diretório que contém jogos salvos para o usuário atual", + "GameListContextMenuOpenDeviceSaveDirectory": "Abrir diretório de saves de dispositivo do usuário", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Abre o diretório que contém saves do dispositivo para o usuário atual", + "GameListContextMenuOpenBcatSaveDirectory": "Abrir diretório de saves BCAT do usuário", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Abre o diretório que contém saves BCAT para o usuário atual", + "GameListContextMenuManageTitleUpdates": "Gerenciar atualizações do jogo", + "GameListContextMenuManageTitleUpdatesToolTip": "Abre a janela de gerenciamento de atualizações", + "GameListContextMenuManageDlc": "Gerenciar DLCs", + "GameListContextMenuManageDlcToolTip": "Abre a janela de gerenciamento de DLCs", + "GameListContextMenuCacheManagement": "Gerenciamento de cache", + "GameListContextMenuCacheManagementPurgePptc": "Limpar cache PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Deleta o cache PPTC armazenado em disco do jogo", + "GameListContextMenuCacheManagementPurgeShaderCache": "Limpar cache de Shader", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Deleta o cache de Shader armazenado em disco do jogo", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Abrir diretório do cache PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Abre o diretório contendo os arquivos do cache PPTC", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir diretório do cache de Shader", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abre o diretório contendo os arquivos do cache de Shader", + "GameListContextMenuExtractData": "Extrair dados", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrai a seção ExeFS do jogo (incluindo atualizações)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrai a seção RomFS do jogo (incluindo atualizações)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrai a seção Logo do jogo (incluindo atualizações)", + "GameListContextMenuCreateShortcut": "Criar atalho da aplicação", + "GameListContextMenuCreateShortcutToolTip": "Criar um atalho de área de trabalho que inicia o aplicativo selecionado", + "GameListContextMenuCreateShortcutToolTipMacOS": "Crie um atalho na pasta Aplicativos do macOS que abre o Aplicativo selecionado", + "GameListContextMenuOpenModsDirectory": "Abrir pasta de Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Abre a pasta que contém os mods da aplicação ", + "GameListContextMenuOpenSdModsDirectory": "Abrir diretório de mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "StatusBarGamesLoaded": "{0}/{1} jogos carregados", + "StatusBarSystemVersion": "Versão do firmware: {0}", + "LinuxVmMaxMapCountDialogTitle": "Limite baixo para mapeamentos de memória detectado", + "LinuxVmMaxMapCountDialogTextPrimary": "Você gostaria de aumentar o valor de vm.max_map_count para {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Alguns jogos podem tentar criar mais mapeamentos de memória do que o atualmente permitido. Ryujinx irá falhar assim que este limite for excedido.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Sim, até a próxima reinicialização", + "LinuxVmMaxMapCountDialogButtonPersistent": "Sim, permanentemente", + "LinuxVmMaxMapCountWarningTextPrimary": "A quantidade máxima de mapeamentos de memória é menor que a recomendada.", + "LinuxVmMaxMapCountWarningTextSecondary": "O valor atual de vm.max_map_count ({0}) é menor que {1}. Alguns jogos podem tentar criar mais mapeamentos de memória do que o permitido no momento. Ryujinx vai falhar assim que este limite for excedido.\n\nTalvez você queira aumentar o limite manualmente ou instalar pkexec, o que permite que Ryujinx ajude com isso.", + "Settings": "Configurações", + "SettingsTabGeneral": "Geral", + "SettingsTabGeneralGeneral": "Geral", + "SettingsTabGeneralEnableDiscordRichPresence": "Habilitar Rich Presence do Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Verificar se há atualizações ao iniciar", + "SettingsTabGeneralShowConfirmExitDialog": "Exibir diálogo de confirmação ao sair", + "SettingsTabGeneralRememberWindowState": "Lembrar tamanho/posição da Janela", + "SettingsTabGeneralHideCursor": "Esconder o cursor do mouse:", + "SettingsTabGeneralHideCursorNever": "Nunca", + "SettingsTabGeneralHideCursorOnIdle": "Esconder o cursor quando ocioso", + "SettingsTabGeneralHideCursorAlways": "Sempre", + "SettingsTabGeneralGameDirectories": "Diretórios de jogo", + "SettingsTabGeneralAdd": "Adicionar", + "SettingsTabGeneralRemove": "Remover", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Principal", + "SettingsTabSystemSystemRegion": "Região do sistema:", + "SettingsTabSystemSystemRegionJapan": "Japão", + "SettingsTabSystemSystemRegionUSA": "EUA", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Austrália", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Coreia", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "Idioma do sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Japonês", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglês americano", + "SettingsTabSystemSystemLanguageFrench": "Francês", + "SettingsTabSystemSystemLanguageGerman": "Alemão", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Espanhol", + "SettingsTabSystemSystemLanguageChinese": "Chinês", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Holandês", + "SettingsTabSystemSystemLanguagePortuguese": "Português", + "SettingsTabSystemSystemLanguageRussian": "Russo", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanês", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglês britânico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francês canadense", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Espanhol latino", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chinês simplificado", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chinês tradicional", + "SettingsTabSystemSystemTimeZone": "Fuso horário do sistema:", + "SettingsTabSystemSystemTime": "Hora do sistema:", + "SettingsTabSystemEnableVsync": "Habilitar sincronia vertical", + "SettingsTabSystemEnablePptc": "Habilitar PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Habilitar verificação de integridade do sistema de arquivos", + "SettingsTabSystemAudioBackend": "Biblioteca de saída de áudio:", + "SettingsTabSystemAudioBackendDummy": "Nenhuma", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Pode causar instabilidade)", + "SettingsTabSystemExpandDramSize": "Expandir memória para 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Ignorar serviços não implementados", + "SettingsTabGraphics": "Gráficos", + "SettingsTabGraphicsAPI": "API gráfica", + "SettingsTabGraphicsEnableShaderCache": "Habilitar cache de shader", + "SettingsTabGraphicsAnisotropicFiltering": "Filtragem anisotrópica:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Automático", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Escala de resolução:", + "SettingsTabGraphicsResolutionScaleCustom": "Customizada (não recomendado)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (não recomendado)", + "SettingsTabGraphicsAspectRatio": "Proporção:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Esticar até caber", + "SettingsTabGraphicsDeveloperOptions": "Opções do desenvolvedor", + "SettingsTabGraphicsShaderDumpPath": "Diretório para despejo de shaders:", + "SettingsTabLogging": "Log", + "SettingsTabLoggingLogging": "Log", + "SettingsTabLoggingEnableLoggingToFile": "Salvar logs em arquivo", + "SettingsTabLoggingEnableStubLogs": "Habilitar logs de stub", + "SettingsTabLoggingEnableInfoLogs": "Habilitar logs de informação", + "SettingsTabLoggingEnableWarningLogs": "Habilitar logs de alerta", + "SettingsTabLoggingEnableErrorLogs": "Habilitar logs de erro", + "SettingsTabLoggingEnableTraceLogs": "Habilitar logs de rastreamento", + "SettingsTabLoggingEnableGuestLogs": "Habilitar logs do programa convidado", + "SettingsTabLoggingEnableFsAccessLogs": "Habilitar logs de acesso ao sistema de arquivos", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modo global de logs do sistema de arquivos:", + "SettingsTabLoggingDeveloperOptions": "Opções do desenvolvedor (AVISO: Vai reduzir a performance)", + "SettingsTabLoggingDeveloperOptionsNote": "AVISO: Reduzirá o desempenho", + "SettingsTabLoggingGraphicsBackendLogLevel": "Nível de log do backend gráfico:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nenhum", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Erro", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Lentidão", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Todos", + "SettingsTabLoggingEnableDebugLogs": "Habilitar logs de depuração", + "SettingsTabInput": "Controle", + "SettingsTabInputEnableDockedMode": "Habilitar modo TV", + "SettingsTabInputDirectKeyboardAccess": "Acesso direto ao teclado", + "SettingsButtonSave": "Salvar", + "SettingsButtonClose": "Fechar", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Cancelar", + "SettingsButtonApply": "Aplicar", + "ControllerSettingsPlayer": "Jogador", + "ControllerSettingsPlayer1": "Jogador 1", + "ControllerSettingsPlayer2": "Jogador 2", + "ControllerSettingsPlayer3": "Jogador 3", + "ControllerSettingsPlayer4": "Jogador 4", + "ControllerSettingsPlayer5": "Jogador 5", + "ControllerSettingsPlayer6": "Jogador 6", + "ControllerSettingsPlayer7": "Jogador 7", + "ControllerSettingsPlayer8": "Jogador 8", + "ControllerSettingsHandheld": "Portátil", + "ControllerSettingsInputDevice": "Dispositivo de entrada", + "ControllerSettingsRefresh": "Atualizar", + "ControllerSettingsDeviceDisabled": "Desabilitado", + "ControllerSettingsControllerType": "Tipo do controle", + "ControllerSettingsControllerTypeHandheld": "Portátil", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Par de JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon esquerdo", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon direito", + "ControllerSettingsProfile": "Perfil", + "ControllerSettingsProfileDefault": "Padrão", + "ControllerSettingsLoad": "Carregar", + "ControllerSettingsAdd": "Adicionar", + "ControllerSettingsRemove": "Remover", + "ControllerSettingsButtons": "Botões", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Direcional", + "ControllerSettingsDPadUp": "Cima", + "ControllerSettingsDPadDown": "Baixo", + "ControllerSettingsDPadLeft": "Esquerda", + "ControllerSettingsDPadRight": "Direita", + "ControllerSettingsStickButton": "Botão", + "ControllerSettingsStickUp": "Cima", + "ControllerSettingsStickDown": "Baixo", + "ControllerSettingsStickLeft": "Esquerda", + "ControllerSettingsStickRight": "Direita", + "ControllerSettingsStickStick": "Analógico", + "ControllerSettingsStickInvertXAxis": "Inverter eixo X", + "ControllerSettingsStickInvertYAxis": "Inverter eixo Y", + "ControllerSettingsStickDeadzone": "Zona morta:", + "ControllerSettingsLStick": "Analógico esquerdo", + "ControllerSettingsRStick": "Analógico direito", + "ControllerSettingsTriggersLeft": "Gatilhos esquerda", + "ControllerSettingsTriggersRight": "Gatilhos direita", + "ControllerSettingsTriggersButtonsLeft": "Botões de gatilho esquerda", + "ControllerSettingsTriggersButtonsRight": "Botões de gatilho direita", + "ControllerSettingsTriggers": "Gatilhos", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Botões esquerda", + "ControllerSettingsExtraButtonsRight": "Botões direita", + "ControllerSettingsMisc": "Miscelâneas", + "ControllerSettingsTriggerThreshold": "Sensibilidade do gatilho:", + "ControllerSettingsMotion": "Sensor de movimento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usar sensor compatível com CemuHook", + "ControllerSettingsMotionControllerSlot": "Slot do controle:", + "ControllerSettingsMotionMirrorInput": "Espelhar movimento", + "ControllerSettingsMotionRightJoyConSlot": "Slot do JoyCon direito:", + "ControllerSettingsMotionServerHost": "Endereço do servidor:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilidade do giroscópio:", + "ControllerSettingsMotionGyroDeadzone": "Zona morta do giroscópio:", + "ControllerSettingsSave": "Salvar", + "ControllerSettingsClose": "Fechar", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", + "UserProfilesSelectedUserProfile": "Perfil de usuário selecionado:", + "UserProfilesSaveProfileName": "Salvar nome de perfil", + "UserProfilesChangeProfileImage": "Mudar imagem de perfil", + "UserProfilesAvailableUserProfiles": "Perfis de usuário disponíveis:", + "UserProfilesAddNewProfile": "Adicionar novo perfil", + "UserProfilesDelete": "Apagar", + "UserProfilesClose": "Fechar", + "ProfileNameSelectionWatermark": "Escolha um apelido", + "ProfileImageSelectionTitle": "Seleção da imagem de perfil", + "ProfileImageSelectionHeader": "Escolha uma imagem de perfil", + "ProfileImageSelectionNote": "Você pode importar uma imagem customizada, ou selecionar um avatar do firmware", + "ProfileImageSelectionImportImage": "Importar arquivo de imagem", + "ProfileImageSelectionSelectAvatar": "Selecionar avatar do firmware", + "InputDialogTitle": "Diálogo de texto", + "InputDialogOk": "OK", + "InputDialogCancel": "Cancelar", + "InputDialogAddNewProfileTitle": "Escolha o nome de perfil", + "InputDialogAddNewProfileHeader": "Escreva o nome do perfil", + "InputDialogAddNewProfileSubtext": "(Máximo de caracteres: {0})", + "AvatarChoose": "Escolher", + "AvatarSetBackgroundColor": "Definir cor de fundo", + "AvatarClose": "Fechar", + "ControllerSettingsLoadProfileToolTip": "Carregar perfil", + "ControllerSettingsAddProfileToolTip": "Adicionar perfil", + "ControllerSettingsRemoveProfileToolTip": "Remover perfil", + "ControllerSettingsSaveProfileToolTip": "Salvar perfil", + "MenuBarFileToolsTakeScreenshot": "Salvar captura de tela", + "MenuBarFileToolsHideUi": "Esconder Interface", + "GameListContextMenuRunApplication": "Executar Aplicativo", + "GameListContextMenuToggleFavorite": "Alternar favorito", + "GameListContextMenuToggleFavoriteToolTip": "Marca ou desmarca jogo como favorito", + "SettingsTabGeneralTheme": "Tema:", + "SettingsTabGeneralThemeDark": "Escuro", + "SettingsTabGeneralThemeLight": "Claro", + "ControllerSettingsConfigureGeneral": "Configurar", + "ControllerSettingsRumble": "Vibração", + "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibração forte", + "ControllerSettingsRumbleWeakMultiplier": "Multiplicador de vibração fraca", + "DialogMessageSaveNotAvailableMessage": "Não há jogos salvos para {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Gostaria de criar o diretório de salvamento para esse jogo?", + "DialogConfirmationTitle": "Ryujinx - Confirmação", + "DialogUpdaterTitle": "Ryujinx - Atualizador", + "DialogErrorTitle": "Ryujinx - Erro", + "DialogWarningTitle": "Ryujinx - Alerta", + "DialogExitTitle": "Ryujinx - Sair", + "DialogErrorMessage": "Ryujinx encontrou um erro", + "DialogExitMessage": "Tem certeza que deseja fechar o Ryujinx?", + "DialogExitSubMessage": "Todos os dados que não foram salvos serão perdidos!", + "DialogMessageCreateSaveErrorMessage": "Ocorreu um erro ao criar o diretório de salvamento: {0}", + "DialogMessageFindSaveErrorMessage": "Ocorreu um erro ao tentar encontrar o diretório de salvamento: {0}", + "FolderDialogExtractTitle": "Escolha o diretório onde os arquivos serão extraídos", + "DialogNcaExtractionMessage": "Extraindo seção {0} de {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Extrator de seções NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Falha na extração. O NCA principal não foi encontrado no arquivo selecionado.", + "DialogNcaExtractionCheckLogErrorMessage": "Falha na extração. Leia o arquivo de log para mais informações.", + "DialogNcaExtractionSuccessMessage": "Extração concluída com êxito.", + "DialogUpdaterConvertFailedMessage": "Falha ao converter a versão atual do Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Cancelando atualização!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Você já está usando a versão mais recente do Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Ocorreu um erro ao tentar obter as informações de atualização do GitHub Release. Isso pode ser causado se uma nova versão estiver sendo compilado pelas Ações do GitHub. Tente novamente em alguns minutos.", + "DialogUpdaterConvertFailedGithubMessage": "Falha ao converter a versão do Ryujinx recebida do AppVeyor.", + "DialogUpdaterDownloadingMessage": "Baixando atualização...", + "DialogUpdaterExtractionMessage": "Extraindo atualização...", + "DialogUpdaterRenamingMessage": "Renomeando atualização...", + "DialogUpdaterAddingFilesMessage": "Adicionando nova atualização...", + "DialogUpdaterCompleteMessage": "Atualização concluída!", + "DialogUpdaterRestartMessage": "Deseja reiniciar o Ryujinx agora?", + "DialogUpdaterNoInternetMessage": "Você não está conectado à Internet!", + "DialogUpdaterNoInternetSubMessage": "Por favor, certifique-se de que você tem uma conexão funcional à Internet!", + "DialogUpdaterDirtyBuildMessage": "Você não pode atualizar uma compilação Dirty do Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Por favor, baixe o Ryujinx em https://ryujinx.org/ se está procurando por uma versão suportada.", + "DialogRestartRequiredMessage": "Reinicialização necessária", + "DialogThemeRestartMessage": "O tema foi salvo. Uma reinicialização é necessária para aplicar o tema.", + "DialogThemeRestartSubMessage": "Deseja reiniciar?", + "DialogFirmwareInstallEmbeddedMessage": "Gostaria de instalar o firmware incluso neste jogo? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", + "DialogFirmwareNoFirmwareInstalledMessage": "Firmware não foi instalado", + "DialogFirmwareInstalledMessage": "Firmware {0} foi instalado", + "DialogInstallFileTypesSuccessMessage": "Tipos de arquivo instalados com sucesso!", + "DialogInstallFileTypesErrorMessage": "Falha ao instalar tipos de arquivo.", + "DialogUninstallFileTypesSuccessMessage": "Tipos de arquivo desinstalados com sucesso!", + "DialogUninstallFileTypesErrorMessage": "Falha ao desinstalar tipos de arquivo.", + "DialogOpenSettingsWindowLabel": "Abrir janela de configurações", + "DialogControllerAppletTitle": "Applet de controle", + "DialogMessageDialogErrorExceptionMessage": "Erro ao exibir diálogo de mensagem: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Erro ao exibir teclado virtual: {0}", + "DialogErrorAppletErrorExceptionMessage": "Erro ao exibir applet ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPara mais informações sobre como corrigir esse erro, siga nosso Guia de Configuração.", + "DialogUserErrorDialogTitle": "Erro do Ryujinx ({0})", + "DialogAmiiboApiTitle": "API Amiibo", + "DialogAmiiboApiFailFetchMessage": "Um erro ocorreu ao tentar obter informações da API.", + "DialogAmiiboApiConnectErrorMessage": "Não foi possível conectar ao servidor da API Amiibo. O serviço pode estar fora do ar ou você precisa verificar sua conexão com a Internet.", + "DialogProfileInvalidProfileErrorMessage": "Perfil {0} é incompatível com o sistema de configuração de controle atual.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "O perfil Padrão não pode ser substituído", + "DialogProfileDeleteProfileTitle": "Apagando perfil", + "DialogProfileDeleteProfileMessage": "Essa ação é irreversível, tem certeza que deseja continuar?", + "DialogWarning": "Alerta", + "DialogPPTCDeletionMessage": "Você está prestes a apagar o cache PPTC para :\n\n{0}\n\nTem certeza que deseja continuar?", + "DialogPPTCDeletionErrorMessage": "Erro apagando cache PPTC em {0}: {1}", + "DialogShaderDeletionMessage": "Você está prestes a apagar o cache de Shader para :\n\n{0}\n\nTem certeza que deseja continuar?", + "DialogShaderDeletionErrorMessage": "Erro apagando o cache de Shader em {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx encontrou um erro", + "DialogInvalidTitleIdErrorMessage": "Erro de interface: O jogo selecionado não tem um ID de título válido", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Um firmware de sistema válido não foi encontrado em {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Instalar firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "A versão do sistema {0} será instalada.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nIsso substituirá a versão do sistema atual {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDeseja continuar?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalando firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Versão do sistema {0} instalada com sucesso.", + "DialogUserProfileDeletionWarningMessage": "Não haveria nenhum perfil selecionado se o perfil atual fosse deletado", + "DialogUserProfileDeletionConfirmMessage": "Deseja deletar o perfil selecionado", + "DialogUserProfileUnsavedChangesTitle": "Alerta - Alterações não salvas", + "DialogUserProfileUnsavedChangesMessage": "Você fez alterações para este perfil de usuário que não foram salvas.", + "DialogUserProfileUnsavedChangesSubMessage": "Deseja descartar as alterações?", + "DialogControllerSettingsModifiedConfirmMessage": "As configurações de controle atuais foram atualizadas.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Deseja salvar?", + "DialogLoadFileErrorMessage": "{0}. Errored File: {1}", + "DialogModAlreadyExistsMessage": "Mod already exists", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!", + "DialogDlcNoDlcErrorMessage": "O arquivo especificado não contém DLCs para o título selecionado!", + "DialogPerformanceCheckLoggingEnabledMessage": "Os logs de depuração estão ativos, esse recurso é feito para ser usado apenas por desenvolvedores.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar os logs de depuração. Gostaria de desabilitar os logs de depuração agora?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "O despejo de shaders está ativo, esse recurso é feito para ser usado apenas por desenvolvedores.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar o despejo de shaders. Gostaria de desabilitar o despejo de shaders agora?", + "DialogLoadAppGameAlreadyLoadedMessage": "Um jogo já foi carregado", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Por favor, pare a emulação ou feche o emulador antes de abrir outro jogo.", + "DialogUpdateAddUpdateErrorMessage": "O arquivo especificado não contém atualizações para o título selecionado!", + "DialogSettingsBackendThreadingWarningTitle": "Alerta - Threading da API gráfica", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx precisa ser reiniciado após mudar essa opção para que ela tenha efeito. Dependendo da sua plataforma, pode ser preciso desabilitar o multithreading do driver de vídeo quando usar o Ryujinx.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "SettingsTabGraphicsFeaturesOptions": "Recursos", + "SettingsTabGraphicsBackendMultithreading": "Multithreading da API gráfica:", + "CommonAuto": "Automático", + "CommonOff": "Desligado", + "CommonOn": "Ligado", + "InputDialogYes": "Sim", + "InputDialogNo": "Não", + "DialogProfileInvalidProfileNameErrorMessage": "O nome do arquivo contém caracteres inválidos. Por favor, tente novamente.", + "MenuBarOptionsPauseEmulation": "Pausar", + "MenuBarOptionsResumeEmulation": "Resumir", + "AboutUrlTooltipMessage": "Clique para abrir o site do Ryujinx no seu navegador padrão.", + "AboutDisclaimerMessage": "Ryujinx não é afiliado com a Nintendo™,\nou qualquer um de seus parceiros, de nenhum modo.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) é usado\nem nossa emulação de Amiibo.", + "AboutPatreonUrlTooltipMessage": "Clique para abrir a página do Patreon do Ryujinx no seu navegador padrão.", + "AboutGithubUrlTooltipMessage": "Clique para abrir a página do GitHub do Ryujinx no seu navegador padrão.", + "AboutDiscordUrlTooltipMessage": "Clique para abrir um convite ao servidor do Discord do Ryujinx no seu navegador padrão.", + "AboutTwitterUrlTooltipMessage": "Clique para abrir a página do Twitter do Ryujinx no seu navegador padrão.", + "AboutRyujinxAboutTitle": "Sobre:", + "AboutRyujinxAboutContent": "Ryujinx é um emulador de Nintendo Switch™.\nPor favor, nos dê apoio no Patreon.\nFique por dentro de todas as novidades no Twitter ou Discord.\nDesenvolvedores com interesse em contribuir podem conseguir mais informações no GitHub ou Discord.", + "AboutRyujinxMaintainersTitle": "Mantido por:", + "AboutRyujinxMaintainersContentTooltipMessage": "Clique para abrir a página de contribuidores no seu navegador padrão.", + "AboutRyujinxSupprtersTitle": "Apoiado no Patreon por:", + "AmiiboSeriesLabel": "Franquia Amiibo", + "AmiiboCharacterLabel": "Personagem", + "AmiiboScanButtonLabel": "Escanear", + "AmiiboOptionsShowAllLabel": "Exibir todos os Amiibos", + "AmiiboOptionsUsRandomTagLabel": "Hack: Usar Uuid de tag aleatório", + "DlcManagerTableHeadingEnabledLabel": "Habilitado", + "DlcManagerTableHeadingTitleIdLabel": "ID do título", + "DlcManagerTableHeadingContainerPathLabel": "Caminho do container", + "DlcManagerTableHeadingFullPathLabel": "Caminho completo", + "DlcManagerRemoveAllButton": "Remover todos", + "DlcManagerEnableAllButton": "Habilitar todos", + "DlcManagerDisableAllButton": "Desabilitar todos", + "ModManagerDeleteAllButton": "Delete All", + "MenuBarOptionsChangeLanguage": "Mudar idioma", + "MenuBarShowFileTypes": "Mostrar tipos de arquivo", + "CommonSort": "Ordenar", + "CommonShowNames": "Exibir nomes", + "CommonFavorite": "Favorito", + "OrderAscending": "Ascendente", + "OrderDescending": "Descendente", + "SettingsTabGraphicsFeatures": "Recursos & Melhorias", + "ErrorWindowTitle": "Janela de erro", + "ToggleDiscordTooltip": "Habilita ou desabilita Discord Rich Presence", + "AddGameDirBoxTooltip": "Escreva um diretório de jogo para adicionar à lista", + "AddGameDirTooltip": "Adicionar um diretório de jogo à lista", + "RemoveGameDirTooltip": "Remover diretório de jogo selecionado", + "CustomThemeCheckTooltip": "Habilita ou desabilita temas customizados na interface gráfica", + "CustomThemePathTooltip": "Diretório do tema customizado", + "CustomThemeBrowseTooltip": "Navegar até um tema customizado", + "DockModeToggleTooltip": "Habilita ou desabilita modo TV", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "RegionTooltip": "Mudar a região do sistema", + "LanguageTooltip": "Mudar o idioma do sistema", + "TimezoneTooltip": "Mudar o fuso-horário do sistema", + "TimeTooltip": "Mudar a hora do sistema", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "PptcToggleTooltip": "Habilita ou desabilita PPTC", + "FsIntegrityToggleTooltip": "Habilita ou desabilita verificação de integridade dos arquivos do jogo", + "AudioBackendTooltip": "Mudar biblioteca de áudio", + "MemoryManagerTooltip": "Muda como a memória do sistema convidado é acessada. Tem um grande impacto na performance da CPU emulada.", + "MemoryManagerSoftwareTooltip": "Usar uma tabela de página via software para tradução de endereços. Maior precisão, porém performance mais baixa.", + "MemoryManagerHostTooltip": "Mapeia memória no espaço de endereço hóspede diretamente. Compilação e execução do JIT muito mais rápida.", + "MemoryManagerUnsafeTooltip": "Mapeia memória diretamente, mas sem limitar o acesso ao espaço de endereçamento do sistema convidado. Mais rápido, porém menos seguro. O aplicativo convidado pode acessar memória de qualquer parte do Ryujinx, então apenas rode programas em que você confia nesse modo.", + "UseHypervisorTooltip": "Usa o Hypervisor em vez de JIT (recompilador dinâmico). Melhora significativamente o desempenho quando disponível, mas pode ser instável no seu estado atual.", + "DRamTooltip": "Expande a memória do sistema emulado de 4GiB para 6GiB", + "IgnoreMissingServicesTooltip": "Habilita ou desabilita a opção de ignorar serviços não implementados", + "GraphicsBackendThreadingTooltip": "Habilita multithreading do backend gráfico", + "GalThreadingTooltip": "Executa comandos do backend gráfico em uma segunda thread. Permite multithreading em tempo de execução da compilação de shader, diminui os travamentos, e melhora performance em drivers sem suporte embutido a multithreading. Pequena variação na performance máxima em drivers com suporte a multithreading. Ryujinx pode precisar ser reiniciado para desabilitar adequadamente o multithreading embutido do driver, ou você pode precisar fazer isso manualmente para ter a melhor performance.", + "ShaderCacheToggleTooltip": "Habilita ou desabilita o cache de shader", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleEntryTooltip": "Escala de resolução de ponto flutuante, como 1.5. Valores não inteiros tem probabilidade maior de causar problemas ou quebras.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "ShaderDumpPathTooltip": "Diretòrio de despejo de shaders", + "FileLogTooltip": "Habilita ou desabilita log para um arquivo no disco", + "StubLogTooltip": "Habilita ou desabilita exibição de mensagens de stub", + "InfoLogTooltip": "Habilita ou desabilita exibição de mensagens informativas", + "WarnLogTooltip": "Habilita ou desabilita exibição de mensagens de alerta", + "ErrorLogTooltip": "Habilita ou desabilita exibição de mensagens de erro", + "TraceLogTooltip": "Habilita ou desabilita exibição de mensagens de rastreamento", + "GuestLogTooltip": "Habilita ou desabilita exibição de mensagens do programa convidado", + "FileAccessLogTooltip": "Habilita ou desabilita exibição de mensagens do acesso de arquivos", + "FSAccessLogModeTooltip": "Habilita exibição de mensagens de acesso ao sistema de arquivos no console. Modos permitidos são 0-3", + "DeveloperOptionTooltip": "Use com cuidado", + "OpenGlLogLevel": "Requer que os níveis de log apropriados estejaam habilitados", + "DebugLogTooltip": "Habilita exibição de mensagens de depuração", + "LoadApplicationFileTooltip": "Abre o navegador de arquivos para seleção de um arquivo do Switch compatível a ser carregado", + "LoadApplicationFolderTooltip": "Abre o navegador de pastas para seleção de pasta extraída do Switch compatível a ser carregada", + "OpenRyujinxFolderTooltip": "Abre o diretório do sistema de arquivos do Ryujinx", + "OpenRyujinxLogsTooltip": "Abre o diretório onde os logs são salvos", + "ExitTooltip": "Sair do Ryujinx", + "OpenSettingsTooltip": "Abrir janela de configurações", + "OpenProfileManagerTooltip": "Abrir janela de gerenciamento de perfis", + "StopEmulationTooltip": "Parar emulação do jogo atual e voltar a seleção de jogos", + "CheckUpdatesTooltip": "Verificar por atualizações para o Ryujinx", + "OpenAboutTooltip": "Abrir janela sobre", + "GridSize": "Tamanho da grade", + "GridSizeTooltip": "Mudar tamanho dos items da grade", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Português do Brasil", + "AboutRyujinxContributorsButtonHeader": "Ver todos os contribuidores", + "SettingsTabSystemAudioVolume": "Volume:", + "AudioVolumeTooltip": "Mudar volume do áudio", + "SettingsTabSystemEnableInternetAccess": "Habilitar acesso à internet do programa convidado", + "EnableInternetAccessTooltip": "Habilita acesso à internet do programa convidado. Se habilitado, o aplicativo vai se comportar como se o sistema Switch emulado estivesse conectado a Internet. Note que em alguns casos, aplicativos podem acessar a Internet mesmo com essa opção desabilitada", + "GameListContextMenuManageCheatToolTip": "Gerenciar Cheats", + "GameListContextMenuManageCheat": "Gerenciar Cheats", + "GameListContextMenuManageModToolTip": "Manage Mods", + "GameListContextMenuManageMod": "Manage Mods", + "ControllerSettingsStickRange": "Intervalo:", + "DialogStopEmulationTitle": "Ryujinx - Parar emulação", + "DialogStopEmulationMessage": "Tem certeza que deseja parar a emulação?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Áudio", + "SettingsTabNetwork": "Rede", + "SettingsTabNetworkConnection": "Conexão de rede", + "SettingsTabCpuCache": "Cache da CPU", + "SettingsTabCpuMemory": "Memória da CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Por favor, atualize o Ryujinx pelo FlatHub.", + "UpdaterDisabledWarningTitle": "Atualizador desabilitado!", + "ControllerSettingsRotate90": "Rodar 90° sentido horário", + "IconSize": "Tamanho do ícone", + "IconSizeTooltip": "Muda o tamanho do ícone do jogo", + "MenuBarOptionsShowConsole": "Exibir console", + "ShaderCachePurgeError": "Erro ao deletar o shader em {0}: {1}", + "UserErrorNoKeys": "Chaves não encontradas", + "UserErrorNoFirmware": "Firmware não encontrado", + "UserErrorFirmwareParsingFailed": "Erro na leitura do Firmware", + "UserErrorApplicationNotFound": "Aplicativo não encontrado", + "UserErrorUnknown": "Erro desconhecido", + "UserErrorUndefined": "Erro indefinido", + "UserErrorNoKeysDescription": "Ryujinx não conseguiu encontrar o seu arquivo 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx não conseguiu encontrar nenhum Firmware instalado", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx não conseguiu ler o Firmware fornecido. Geralmente isso é causado por chaves desatualizadas.", + "UserErrorApplicationNotFoundDescription": "Ryujinx não conseguiu encontrar um aplicativo válido no caminho fornecido.", + "UserErrorUnknownDescription": "Um erro desconhecido foi encontrado!", + "UserErrorUndefinedDescription": "Um erro indefinido occoreu! Isso não deveria acontecer, por favor contate um desenvolvedor!", + "OpenSetupGuideMessage": "Abrir o guia de configuração", + "NoUpdate": "Sem atualizações", + "TitleUpdateVersionLabel": "Versão {0} - {1}", + "RyujinxInfo": "Ryujinx - Informação", + "RyujinxConfirm": "Ryujinx - Confirmação", + "FileDialogAllTypes": "Todos os tipos", + "Never": "Nunca", + "SwkbdMinCharacters": "Deve ter pelo menos {0} caracteres", + "SwkbdMinRangeCharacters": "Deve ter entre {0}-{1} caracteres", + "SoftwareKeyboard": "Teclado por Software", + "SoftwareKeyboardModeNumeric": "Deve ser somente 0-9 ou '.'", + "SoftwareKeyboardModeAlphabet": "Apenas devem ser caracteres não CJK.", + "SoftwareKeyboardModeASCII": "Deve ser apenas texto ASCII", + "ControllerAppletControllers": "Supported Controllers:", + "ControllerAppletPlayers": "Players:", + "ControllerAppletDescription": "Your current configuration is invalid. Open settings and reconfigure your inputs.", + "ControllerAppletDocked": "Docked mode set. Handheld control should be disabled.", + "UpdaterRenaming": "Renomeando arquivos antigos...", + "UpdaterRenameFailed": "O atualizador não conseguiu renomear o arquivo: {0}", + "UpdaterAddingFiles": "Adicionando novos arquivos...", + "UpdaterExtracting": "Extraíndo atualização...", + "UpdaterDownloading": "Baixando atualização...", + "Game": "Jogo", + "Docked": "TV", + "Handheld": "Portátil", + "ConnectionError": "Erro de conexão.", + "AboutPageDeveloperListMore": "{0} e mais...", + "ApiError": "Erro de API.", + "LoadingHeading": "Carregando {0}", + "CompilingPPTC": "Compilando PTC", + "CompilingShaders": "Compilando Shaders", + "AllKeyboards": "Todos os teclados", + "OpenFileDialogTitle": "Selecione um arquivo suportado para abrir", + "OpenFolderDialogTitle": "Selecione um diretório com um jogo extraído", + "AllSupportedFormats": "Todos os formatos suportados", + "RyujinxUpdater": "Atualizador do Ryujinx", + "SettingsTabHotkeys": "Atalhos do teclado", + "SettingsTabHotkeysHotkeys": "Atalhos do teclado", + "SettingsTabHotkeysToggleVsyncHotkey": "Mudar VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Captura de tela:", + "SettingsTabHotkeysShowUiHotkey": "Exibir UI:", + "SettingsTabHotkeysPauseHotkey": "Pausar:", + "SettingsTabHotkeysToggleMuteHotkey": "Mudo:", + "ControllerMotionTitle": "Configurações do controle de movimento", + "ControllerRumbleTitle": "Configurações de vibração", + "SettingsSelectThemeFileDialogTitle": "Selecionar arquivo do tema", + "SettingsXamlThemeFile": "Arquivo de tema Xaml", + "AvatarWindowTitle": "Gerenciar contas - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Desconhecido", + "Usage": "Uso", + "Writable": "Gravável", + "SelectDlcDialogTitle": "Selecionar arquivos de DLC", + "SelectUpdateDialogTitle": "Selecionar arquivos de atualização", + "SelectModDialogTitle": "Select mod directory", + "UserProfileWindowTitle": "Gerenciador de perfis de usuário", + "CheatWindowTitle": "Gerenciador de Cheats", + "DlcWindowTitle": "Gerenciador de DLC", + "ModWindowTitle": "Manage Mods for {0} ({1})", + "UpdateWindowTitle": "Gerenciador de atualizações", + "CheatWindowHeading": "Cheats disponíveis para {0} [{1}]", + "BuildId": "ID da Build", + "DlcWindowHeading": "{0} DLCs disponíveis para {1} ({2})", + "ModWindowHeading": "{0} Mod(s)", + "UserProfilesEditProfile": "Editar selecionado", + "Cancel": "Cancelar", + "Save": "Salvar", + "Discard": "Descartar", + "Paused": "Paused", + "UserProfilesSetProfileImage": "Definir imagem de perfil", + "UserProfileEmptyNameError": "É necessário um nome", + "UserProfileNoImageError": "A imagem de perfil deve ser definida", + "GameUpdateWindowHeading": "{0} atualizações disponíveis para {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Aumentar a resolução:", + "SettingsTabHotkeysResScaleDownHotkey": "Diminuir a resolução:", + "UserProfilesName": "Nome:", + "UserProfilesUserId": "ID de usuário:", + "SettingsTabGraphicsBackend": "Backend gráfico", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsEnableTextureRecompression": "Habilitar recompressão de texturas", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsTabGraphicsPreferredGpu": "GPU preferencial", + "SettingsTabGraphicsPreferredGpuTooltip": "Selecione a placa de vídeo que será usada com o backend gráfico Vulkan.\n\nNão afeta a GPU que OpenGL usará.\n\nSelecione \"dGPU\" em caso de dúvida. Se não houver nenhuma, não mexa.", + "SettingsAppRequiredRestartMessage": "Reinicialização do Ryujinx necessária", + "SettingsGpuBackendRestartMessage": "Configurações do backend gráfico ou da GPU foram alteradas. Uma reinicialização é necessária para que as mudanças tenham efeito.", + "SettingsGpuBackendRestartSubMessage": "Deseja reiniciar agora?", + "RyujinxUpdaterMessage": "Você quer atualizar o Ryujinx para a última versão?", + "SettingsTabHotkeysVolumeUpHotkey": "Aumentar volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Diminuir volume:", + "SettingsEnableMacroHLE": "Habilitar emulação de alto nível para Macros", + "SettingsEnableMacroHLETooltip": "Habilita emulação de alto nível de códigos Macro da GPU.\n\nMelhora a performance, mas pode causar problemas gráficos em alguns jogos.\n\nEm caso de dúvida, deixe ATIVADO.", + "SettingsEnableColorSpacePassthrough": "Passagem de Espaço Cor", + "SettingsEnableColorSpacePassthroughTooltip": "Direciona o backend Vulkan para passar informações de cores sem especificar um espaço de cores. Para usuários com telas de ampla gama, isso pode resultar em cores mais vibrantes, ao custo da correção de cores.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Gerenciar jogos salvos", + "DeleteUserSave": "Deseja apagar o jogo salvo do usuário para este jogo?", + "IrreversibleActionNote": "Esta ação não é reversível.", + "SaveManagerHeading": "Gerenciar jogos salvos para {0}", + "SaveManagerTitle": "Gerenciador de jogos salvos", + "Name": "Nome", + "Size": "Tamanho", + "Search": "Buscar", + "UserProfilesRecoverLostAccounts": "Recuperar contas perdidas", + "Recover": "Recuperar", + "UserProfilesRecoverHeading": "Jogos salvos foram encontrados para as seguintes contas", + "UserProfilesRecoverEmptyList": "Nenhum perfil para recuperar", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAALabel": "Anti-serrilhado:", + "GraphicsScalingFilterLabel": "Filtro de escala:", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "Nível", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "SmaaLow": "SMAA Baixo", + "SmaaMedium": "SMAA Médio", + "SmaaHigh": "SMAA Alto", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle": "Editar usuário", + "UserEditorTitleCreate": "Criar usuário", + "SettingsTabNetworkInterface": "Interface de rede:", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceDefault": "Padrão", + "PackagingShaders": "Empacotamento de Shaders", + "AboutChangelogButton": "Ver mudanças no GitHub", + "AboutChangelogButtonTooltipMessage": "Clique para abrir o relatório de alterações para esta versão no seu navegador padrão.", + "SettingsTabNetworkMultiplayer": "Multiplayer", + "MultiplayerMode": "Modo:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json new file mode 100644 index 00000000..75fd4fe1 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -0,0 +1,780 @@ +{ + "Language": "Русский (RU)", + "MenuBarFileOpenApplet": "Открыть апплет", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Открывает апплет Mii Editor в автономном режиме", + "SettingsTabInputDirectMouseAccess": "Прямой ввод мыши", + "SettingsTabSystemMemoryManagerMode": "Режим менеджера памяти:", + "SettingsTabSystemMemoryManagerModeSoftware": "Программное обеспечение", + "SettingsTabSystemMemoryManagerModeHost": "Хост (быстро)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Хост не установлен (самый быстрый, небезопасный)", + "SettingsTabSystemUseHypervisor": "Использовать Hypervisor", + "MenuBarFile": "_Файл", + "MenuBarFileOpenFromFile": "_Добавить приложение из файла", + "MenuBarFileOpenUnpacked": "Добавить _распакованную игру", + "MenuBarFileOpenEmuFolder": "Открыть папку Ryujinx", + "MenuBarFileOpenLogsFolder": "Открыть папку с логами", + "MenuBarFileExit": "_Выход", + "MenuBarOptions": "_Настройки", + "MenuBarOptionsToggleFullscreen": "Включить полноэкранный режим", + "MenuBarOptionsStartGamesInFullscreen": "Запускать игры в полноэкранном режиме", + "MenuBarOptionsStopEmulation": "Остановить эмуляцию", + "MenuBarOptionsSettings": "_Параметры", + "MenuBarOptionsManageUserProfiles": "_Менеджер учетных записей", + "MenuBarActions": "_Действия", + "MenuBarOptionsSimulateWakeUpMessage": "Имитировать сообщение пробуждения", + "MenuBarActionsScanAmiibo": "Сканировать Amiibo", + "MenuBarTools": "_Инструменты", + "MenuBarToolsInstallFirmware": "Установка прошивки", + "MenuBarFileToolsInstallFirmwareFromFile": "Установить прошивку из XCI или ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Установить прошивку из папки", + "MenuBarToolsManageFileTypes": "Управление типами файлов", + "MenuBarToolsInstallFileTypes": "Установить типы файлов", + "MenuBarToolsUninstallFileTypes": "Удалить типы файлов", + "MenuBarView": "_Вид", + "MenuBarViewWindow": "Размер окна", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "_Помощь", + "MenuBarHelpCheckForUpdates": "Проверить наличие обновлений", + "MenuBarHelpAbout": "О программе", + "MenuSearch": "Поиск...", + "GameListHeaderFavorite": "Избранное", + "GameListHeaderIcon": "Значок", + "GameListHeaderApplication": "Название", + "GameListHeaderDeveloper": "Разработчик", + "GameListHeaderVersion": "Версия", + "GameListHeaderTimePlayed": "Время в игре", + "GameListHeaderLastPlayed": "Последний запуск", + "GameListHeaderFileExtension": "Расширение файла", + "GameListHeaderFileSize": "Размер файла", + "GameListHeaderPath": "Путь", + "GameListContextMenuOpenUserSaveDirectory": "Открыть папку с сохранениями", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Открывает папку с пользовательскими сохранениями", + "GameListContextMenuOpenDeviceSaveDirectory": "Открыть папку сохраненных устройств", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные устройства", + "GameListContextMenuOpenBcatSaveDirectory": "Открыть папку сохраненных BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Открывает папку, содержащую сохраненные BCAT", + "GameListContextMenuManageTitleUpdates": "Управление обновлениями", + "GameListContextMenuManageTitleUpdatesToolTip": "Открывает окно управления обновлениями приложения", + "GameListContextMenuManageDlc": "Управление DLC", + "GameListContextMenuManageDlcToolTip": "Открывает окно управления DLC", + "GameListContextMenuCacheManagement": "Управление кэшем", + "GameListContextMenuCacheManagementPurgePptc": "Перестроить очередь PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Запускает перестройку PPTC во время следующего запуска игры.", + "GameListContextMenuCacheManagementPurgeShaderCache": "Очистить кэш шейдеров", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Удаляет кеш шейдеров приложения", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Открыть папку PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Открывает папку, содержащую PPTC кэш приложений и игр", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Открыть папку с кэшем шейдеров", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Открывает папку, содержащую кэш шейдеров приложений и игр", + "GameListContextMenuExtractData": "Извлечь данные", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Извлечение раздела ExeFS из текущих настроек приложения (включая обновления)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Извлечение раздела RomFS из текущих настроек приложения (включая обновления)", + "GameListContextMenuExtractDataLogo": "Логотип", + "GameListContextMenuExtractDataLogoToolTip": "Извлечение раздела с логотипом из текущих настроек приложения (включая обновления)", + "GameListContextMenuCreateShortcut": "Создать ярлык приложения", + "GameListContextMenuCreateShortcutToolTip": "Создает ярлык на рабочем столе, с помощью которого можно запустить игру или приложение", + "GameListContextMenuCreateShortcutToolTipMacOS": "Создает ярлык игры или приложения в папке Программы macOS", + "GameListContextMenuOpenModsDirectory": "Открыть папку с модами", + "GameListContextMenuOpenModsDirectoryToolTip": "Открывает папку, содержащую моды для приложений и игр", + "GameListContextMenuOpenSdModsDirectory": "Открыть папку с модами Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Открывает папку Atmosphere на альтернативной SD-карте, которая содержит моды для приложений и игр. Полезно для модов, сделанных для реальной консоли.", + "StatusBarGamesLoaded": "{0}/{1} игр загружено", + "StatusBarSystemVersion": "Версия прошивки: {0}", + "LinuxVmMaxMapCountDialogTitle": "Обнаружен низкий лимит разметки памяти", + "LinuxVmMaxMapCountDialogTextPrimary": "Хотите увеличить значение vm.max_map_count до {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Некоторые игры могут создавать большую разметку памяти, чем разрешено на данный момент по умолчанию. Ryujinx вылетит при превышении этого лимита.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Да, до следующего перезапуска", + "LinuxVmMaxMapCountDialogButtonPersistent": "Да, постоянно", + "LinuxVmMaxMapCountWarningTextPrimary": "Максимальная разметка памяти меньше, чем рекомендуется.", + "LinuxVmMaxMapCountWarningTextSecondary": "Текущее значение vm.max_map_count ({0}) меньше, чем {1}. Некоторые игры могут попытаться создать большую разметку памяти, чем разрешено в данный момент. Ryujinx вылетит как только этот лимит будет превышен.\n\nВозможно, вам потребуется вручную увеличить лимит или установить pkexec, что позволит Ryujinx помочь справиться с превышением лимита.", + "Settings": "Параметры", + "SettingsTabGeneral": "Интерфейс", + "SettingsTabGeneralGeneral": "Общее", + "SettingsTabGeneralEnableDiscordRichPresence": "Статус активности в Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Проверять наличие обновлений при запуске", + "SettingsTabGeneralShowConfirmExitDialog": "Подтверждать выход из приложения", + "SettingsTabGeneralRememberWindowState": "Запомнить размер/положение окна", + "SettingsTabGeneralHideCursor": "Скрывать курсор", + "SettingsTabGeneralHideCursorNever": "Никогда", + "SettingsTabGeneralHideCursorOnIdle": "В простое", + "SettingsTabGeneralHideCursorAlways": "Всегда", + "SettingsTabGeneralGameDirectories": "Папки с играми", + "SettingsTabGeneralAdd": "Добавить", + "SettingsTabGeneralRemove": "Удалить", + "SettingsTabSystem": "Система", + "SettingsTabSystemCore": "Основные настройки", + "SettingsTabSystemSystemRegion": "Регион прошивки:", + "SettingsTabSystemSystemRegionJapan": "Япония", + "SettingsTabSystemSystemRegionUSA": "США", + "SettingsTabSystemSystemRegionEurope": "Европа", + "SettingsTabSystemSystemRegionAustralia": "Австралия", + "SettingsTabSystemSystemRegionChina": "Китай", + "SettingsTabSystemSystemRegionKorea": "Корея", + "SettingsTabSystemSystemRegionTaiwan": "Тайвань", + "SettingsTabSystemSystemLanguage": "Язык прошивки:", + "SettingsTabSystemSystemLanguageJapanese": "Японский", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Английский (США)", + "SettingsTabSystemSystemLanguageFrench": "Французский", + "SettingsTabSystemSystemLanguageGerman": "Германский", + "SettingsTabSystemSystemLanguageItalian": "Итальянский", + "SettingsTabSystemSystemLanguageSpanish": "Испанский", + "SettingsTabSystemSystemLanguageChinese": "Китайский", + "SettingsTabSystemSystemLanguageKorean": "Корейский", + "SettingsTabSystemSystemLanguageDutch": "Нидерландский", + "SettingsTabSystemSystemLanguagePortuguese": "Португальский", + "SettingsTabSystemSystemLanguageRussian": "Русский", + "SettingsTabSystemSystemLanguageTaiwanese": "Тайванский", + "SettingsTabSystemSystemLanguageBritishEnglish": "Английский (Британия)", + "SettingsTabSystemSystemLanguageCanadianFrench": "Французский (Канада)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Испанский (Латинская Америка)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Китайский (упрощённый)", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Китайский (традиционный)", + "SettingsTabSystemSystemTimeZone": "Часовой пояс прошивки:", + "SettingsTabSystemSystemTime": "Системное время в прошивке:", + "SettingsTabSystemEnableVsync": "Вертикальная синхронизация", + "SettingsTabSystemEnablePptc": "Использовать PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Проверка целостности файловой системы", + "SettingsTabSystemAudioBackend": "Аудио бэкенд:", + "SettingsTabSystemAudioBackendDummy": "Без звука", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Хаки", + "SettingsTabSystemHacksNote": "Возможна нестабильная работа", + "SettingsTabSystemExpandDramSize": "Использовать альтернативный макет памяти (для разработчиков)", + "SettingsTabSystemIgnoreMissingServices": "Игнорировать отсутствующие службы", + "SettingsTabGraphics": "Графика", + "SettingsTabGraphicsAPI": "Графические API", + "SettingsTabGraphicsEnableShaderCache": "Кэшировать шейдеры", + "SettingsTabGraphicsAnisotropicFiltering": "Анизотропная фильтрация:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Автоматически", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Масштабирование:", + "SettingsTabGraphicsResolutionScaleCustom": "Пользовательское (не рекомендуется)", + "SettingsTabGraphicsResolutionScaleNative": "Нативное (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (не рекомендуется)", + "SettingsTabGraphicsAspectRatio": "Соотношение сторон:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Растянуть до размеров окна", + "SettingsTabGraphicsDeveloperOptions": "Параметры разработчика", + "SettingsTabGraphicsShaderDumpPath": "Путь дампа графических шейдеров", + "SettingsTabLogging": "Журналирование", + "SettingsTabLoggingLogging": "Журналирование", + "SettingsTabLoggingEnableLoggingToFile": "Включить запись в файл", + "SettingsTabLoggingEnableStubLogs": "Включить журнал-заглушку", + "SettingsTabLoggingEnableInfoLogs": "Включить информационный журнал", + "SettingsTabLoggingEnableWarningLogs": "Включить журнал предупреждений", + "SettingsTabLoggingEnableErrorLogs": "Включить журнал ошибок", + "SettingsTabLoggingEnableTraceLogs": "Включить журнал трассировки", + "SettingsTabLoggingEnableGuestLogs": "Включить гостевые журналы", + "SettingsTabLoggingEnableFsAccessLogs": "Включить журналы доступа файловой системы", + "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журнала глобального доступа файловой системы:", + "SettingsTabLoggingDeveloperOptions": "Параметры разработчика", + "SettingsTabLoggingDeveloperOptionsNote": "ВНИМАНИЕ: эти настройки снижают производительность", + "SettingsTabLoggingGraphicsBackendLogLevel": "Уровень журнала бэкенда графики:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Нет", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Ошибка", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Замедления", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Всё", + "SettingsTabLoggingEnableDebugLogs": "Включить журнал отладки", + "SettingsTabInput": "Управление", + "SettingsTabInputEnableDockedMode": "Стационарный режим", + "SettingsTabInputDirectKeyboardAccess": "Прямой ввод клавиатуры", + "SettingsButtonSave": "Сохранить", + "SettingsButtonClose": "Закрыть", + "SettingsButtonOk": "Ок", + "SettingsButtonCancel": "Отмена", + "SettingsButtonApply": "Применить", + "ControllerSettingsPlayer": "Игрок", + "ControllerSettingsPlayer1": "Игрок 1", + "ControllerSettingsPlayer2": "Игрок 2", + "ControllerSettingsPlayer3": "Игрок 3", + "ControllerSettingsPlayer4": "Игрок 4", + "ControllerSettingsPlayer5": "Игрок 5", + "ControllerSettingsPlayer6": "Игрок 6", + "ControllerSettingsPlayer7": "Игрок 7", + "ControllerSettingsPlayer8": "Игрок 8", + "ControllerSettingsHandheld": "Портативный", + "ControllerSettingsInputDevice": "Устройство ввода", + "ControllerSettingsRefresh": "Обновить", + "ControllerSettingsDeviceDisabled": "Отключить", + "ControllerSettingsControllerType": "Тип контроллера", + "ControllerSettingsControllerTypeHandheld": "Портативный", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon (пара)", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon (левый)", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon (правый)", + "ControllerSettingsProfile": "Профиль", + "ControllerSettingsProfileDefault": "По умолчанию", + "ControllerSettingsLoad": "Загрузить", + "ControllerSettingsAdd": "Добавить", + "ControllerSettingsRemove": "Удалить", + "ControllerSettingsButtons": "Кнопки", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Кнопки направления", + "ControllerSettingsDPadUp": "Вверх", + "ControllerSettingsDPadDown": "Вниз", + "ControllerSettingsDPadLeft": "Влево", + "ControllerSettingsDPadRight": "Вправо", + "ControllerSettingsStickButton": "Нажатие на стик", + "ControllerSettingsStickUp": "Вверх", + "ControllerSettingsStickDown": "Вниз", + "ControllerSettingsStickLeft": "Влево", + "ControllerSettingsStickRight": "Вправо", + "ControllerSettingsStickStick": "Стик", + "ControllerSettingsStickInvertXAxis": "Инвертировать ось X", + "ControllerSettingsStickInvertYAxis": "Инвертировать ось Y", + "ControllerSettingsStickDeadzone": "Мёртвая зона:", + "ControllerSettingsLStick": "Левый стик", + "ControllerSettingsRStick": "Правый стик", + "ControllerSettingsTriggersLeft": "Триггеры слева", + "ControllerSettingsTriggersRight": "Триггеры справа", + "ControllerSettingsTriggersButtonsLeft": "Триггерные кнопки слева", + "ControllerSettingsTriggersButtonsRight": "Триггерные кнопки справа", + "ControllerSettingsTriggers": "Триггеры", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Левые кнопки", + "ControllerSettingsExtraButtonsRight": "Правые кнопки", + "ControllerSettingsMisc": "Разное", + "ControllerSettingsTriggerThreshold": "Порог срабатывания:", + "ControllerSettingsMotion": "Движение", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Включить совместимость с CemuHook", + "ControllerSettingsMotionControllerSlot": "Слот контроллера:", + "ControllerSettingsMotionMirrorInput": "Зеркальный ввод", + "ControllerSettingsMotionRightJoyConSlot": "Слот правого JoyCon:", + "ControllerSettingsMotionServerHost": "Хост сервера:", + "ControllerSettingsMotionGyroSensitivity": "Чувствительность гироскопа:", + "ControllerSettingsMotionGyroDeadzone": "Мертвая зона гироскопа:", + "ControllerSettingsSave": "Сохранить", + "ControllerSettingsClose": "Закрыть", + "KeyUnknown": "Неизвестно", + "KeyShiftLeft": "Левый Shift", + "KeyShiftRight": "Правый Shift", + "KeyControlLeft": "Левый Ctrl", + "KeyMacControlLeft": "Левый ⌃", + "KeyControlRight": "Правый Ctrl", + "KeyMacControlRight": "Правый ⌃", + "KeyAltLeft": "Левый Alt", + "KeyMacAltLeft": "Левый ⌥", + "KeyAltRight": "Правый Alt", + "KeyMacAltRight": "Правый ⌥", + "KeyWinLeft": "Левый ⊞", + "KeyMacWinLeft": "Левый ⌘", + "KeyWinRight": "Правый ⊞", + "KeyMacWinRight": "Правый ⌘", + "KeyMenu": "Меню", + "KeyUp": "Вверх", + "KeyDown": "Вниз", + "KeyLeft": "Влево", + "KeyRight": "Вправо", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Пробел", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Очистить", + "KeyKeypad0": "Блок цифр 0", + "KeyKeypad1": "Блок цифр 1", + "KeyKeypad2": "Блок цифр 2", + "KeyKeypad3": "Блок цифр 3", + "KeyKeypad4": "Блок цифр 4", + "KeyKeypad5": "Блок цифр 5", + "KeyKeypad6": "Блок цифр 6", + "KeyKeypad7": "Блок цифр 7", + "KeyKeypad8": "Блок цифр 8", + "KeyKeypad9": "Блок цифр 9", + "KeyKeypadDivide": "/ (блок цифр)", + "KeyKeypadMultiply": "* (блок цифр)", + "KeyKeypadSubtract": "- (блок цифр)", + "KeyKeypadAdd": "+ (блок цифр)", + "KeyKeypadDecimal": ". (блок цифр)", + "KeyKeypadEnter": "Enter (блок цифр)", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Не привязано", + "GamepadLeftStick": "Кнопка лев. стика", + "GamepadRightStick": "Кнопка пр. стика", + "GamepadLeftShoulder": "Левый бампер", + "GamepadRightShoulder": "Правый бампер", + "GamepadLeftTrigger": "Левый триггер", + "GamepadRightTrigger": "Правый триггер", + "GamepadDpadUp": "Вверх", + "GamepadDpadDown": "Вниз", + "GamepadDpadLeft": "Влево", + "GamepadDpadRight": "Вправо", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Кнопка Xbox", + "GamepadMisc1": "Прочее", + "GamepadPaddle1": "Доп.кнопка 1", + "GamepadPaddle2": "Доп.кнопка 2", + "GamepadPaddle3": "Доп.кнопка 3", + "GamepadPaddle4": "Доп.кнопка 4", + "GamepadTouchpad": "Тачпад", + "GamepadSingleLeftTrigger0": "Левый триггер 0", + "GamepadSingleRightTrigger0": "Правый триггер 0", + "GamepadSingleLeftTrigger1": "Левый триггер 1", + "GamepadSingleRightTrigger1": "Правый триггер 1", + "StickLeft": "Левый стик", + "StickRight": "Правый стик", + "UserProfilesSelectedUserProfile": "Выбранный пользовательский профиль:", + "UserProfilesSaveProfileName": "Сохранить пользовательский профиль", + "UserProfilesChangeProfileImage": "Изменить аватар", + "UserProfilesAvailableUserProfiles": "Доступные профили пользователей:", + "UserProfilesAddNewProfile": "Добавить новый профиль", + "UserProfilesDelete": "Удалить", + "UserProfilesClose": "Закрыть", + "ProfileNameSelectionWatermark": "Укажите никнейм", + "ProfileImageSelectionTitle": "Выбор изображения профиля", + "ProfileImageSelectionHeader": "Выбор аватара", + "ProfileImageSelectionNote": "Вы можете импортировать собственное изображение или выбрать аватар из системной прошивки.", + "ProfileImageSelectionImportImage": "Импорт изображения", + "ProfileImageSelectionSelectAvatar": "Встроенные аватары", + "InputDialogTitle": "Диалоговое окно ввода", + "InputDialogOk": "ОК", + "InputDialogCancel": "Отмена", + "InputDialogAddNewProfileTitle": "Выберите никнейм", + "InputDialogAddNewProfileHeader": "Пожалуйста, введите никнейм", + "InputDialogAddNewProfileSubtext": "(Максимальная длина: {0})", + "AvatarChoose": "Выбрать аватар", + "AvatarSetBackgroundColor": "Установить цвет фона", + "AvatarClose": "Закрыть", + "ControllerSettingsLoadProfileToolTip": "Загрузить профиль", + "ControllerSettingsAddProfileToolTip": "Добавить профиль", + "ControllerSettingsRemoveProfileToolTip": "Удалить профиль", + "ControllerSettingsSaveProfileToolTip": "Сохранить профиль", + "MenuBarFileToolsTakeScreenshot": "Сделать снимок экрана", + "MenuBarFileToolsHideUi": "Скрыть интерфейс", + "GameListContextMenuRunApplication": "Запуск приложения", + "GameListContextMenuToggleFavorite": "Добавить в избранное", + "GameListContextMenuToggleFavoriteToolTip": "Добавляет игру в избранное и помечает звездочкой", + "SettingsTabGeneralTheme": "Тема:", + "SettingsTabGeneralThemeDark": "Темная", + "SettingsTabGeneralThemeLight": "Светлая", + "ControllerSettingsConfigureGeneral": "Настройка", + "ControllerSettingsRumble": "Вибрация", + "ControllerSettingsRumbleStrongMultiplier": "Множитель сильной вибрации", + "ControllerSettingsRumbleWeakMultiplier": "Множитель слабой вибрации", + "DialogMessageSaveNotAvailableMessage": "Нет сохранений для {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Создать сохранение для этой игры?", + "DialogConfirmationTitle": "Ryujinx - Подтверждение", + "DialogUpdaterTitle": "Ryujinx - Обновление", + "DialogErrorTitle": "Ryujinx - Ошибка", + "DialogWarningTitle": "Ryujinx - Предупреждение", + "DialogExitTitle": "Ryujinx - Выход", + "DialogErrorMessage": "Ryujinx обнаружил ошибку", + "DialogExitMessage": "Вы уверены, что хотите выйти из Ryujinx?", + "DialogExitSubMessage": "Все несохраненные данные будут потеряны", + "DialogMessageCreateSaveErrorMessage": "Произошла ошибка при создании указанных данных сохранения: {0}", + "DialogMessageFindSaveErrorMessage": "Произошла ошибка при поиске указанных данных сохранения: {0}", + "FolderDialogExtractTitle": "Выберите папку для извлечения", + "DialogNcaExtractionMessage": "Извлечение {0} раздела из {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Извлечение разделов NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Ошибка извлечения. Основной NCA не присутствовал в выбранном файле.", + "DialogNcaExtractionCheckLogErrorMessage": "Ошибка извлечения. Прочтите файл журнала для получения дополнительной информации.", + "DialogNcaExtractionSuccessMessage": "Извлечение завершено успешно.", + "DialogUpdaterConvertFailedMessage": "Не удалось преобразовать текущую версию Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Отмена обновления...", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Вы используете самую последнюю версию Ryujinx", + "DialogUpdaterFailedToGetVersionMessage": "Произошла ошибка при попытке получить информацию о выпуске от GitHub Release. Это может быть вызвано тем, что в данный момент в GitHub Actions компилируется новый релиз. Повторите попытку позже.", + "DialogUpdaterConvertFailedGithubMessage": "Не удалось преобразовать полученную версию Ryujinx из Github Release.", + "DialogUpdaterDownloadingMessage": "Загрузка обновления...", + "DialogUpdaterExtractionMessage": "Извлечение обновления...", + "DialogUpdaterRenamingMessage": "Переименование обновления...", + "DialogUpdaterAddingFilesMessage": "Добавление нового обновления...", + "DialogUpdaterCompleteMessage": "Обновление завершено", + "DialogUpdaterRestartMessage": "Перезапустить Ryujinx?", + "DialogUpdaterNoInternetMessage": "Вы не подключены к интернету", + "DialogUpdaterNoInternetSubMessage": "Убедитесь, что у вас работает подключение к интернету", + "DialogUpdaterDirtyBuildMessage": "Вы не можете обновлять Dirty Build", + "DialogUpdaterDirtyBuildSubMessage": "Загрузите Ryujinx по адресу https://ryujinx.org/ если вам нужна поддерживаемая версия.", + "DialogRestartRequiredMessage": "Требуется перезагрузка", + "DialogThemeRestartMessage": "Тема сохранена. Для применения темы требуется перезапуск.", + "DialogThemeRestartSubMessage": "Хотите перезапустить", + "DialogFirmwareInstallEmbeddedMessage": "Хотите установить прошивку, встроенную в эту игру? (Прошивка {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Установленная прошивка не была найдена, но Ryujinx удалось установить прошивку {0} из предоставленной игры.\nТеперь эмулятор запустится.", + "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не установлена", + "DialogFirmwareInstalledMessage": "Прошивка {0} была установлена", + "DialogInstallFileTypesSuccessMessage": "Типы файлов успешно установлены", + "DialogInstallFileTypesErrorMessage": "Не удалось установить типы файлов.", + "DialogUninstallFileTypesSuccessMessage": "Типы файлов успешно удалены", + "DialogUninstallFileTypesErrorMessage": "Не удалось удалить типы файлов.", + "DialogOpenSettingsWindowLabel": "Открывает окно параметров", + "DialogControllerAppletTitle": "Апплет контроллера", + "DialogMessageDialogErrorExceptionMessage": "Ошибка отображения сообщения: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Ошибка отображения программной клавиатуры: {0}", + "DialogErrorAppletErrorExceptionMessage": "Ошибка отображения диалогового окна ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nДля получения дополнительной информации о том, как исправить эту ошибку, следуйте нашему Руководству по установке.", + "DialogUserErrorDialogTitle": "Ошибка Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Произошла ошибка при получении информации из API.", + "DialogAmiiboApiConnectErrorMessage": "Не удалось подключиться к серверу Amiibo API. Служба может быть недоступна или вам может потребоваться проверить ваше интернет-соединение.", + "DialogProfileInvalidProfileErrorMessage": "Профиль {0} несовместим с текущей системой конфигурации ввода.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Профиль по умолчанию не может быть перезаписан", + "DialogProfileDeleteProfileTitle": "Удаление профиля", + "DialogProfileDeleteProfileMessage": "Это действие необратимо. Вы уверены, что хотите продолжить?", + "DialogWarning": "Внимание", + "DialogPPTCDeletionMessage": "Вы собираетесь перестроить кэш PPTC при следующем запуске для:\n\n{0}\n\nВы уверены, что хотите продолжить?", + "DialogPPTCDeletionErrorMessage": "Ошибка очистки кэша PPTC в {0}: {1}", + "DialogShaderDeletionMessage": "Вы собираетесь удалить кэш шейдеров для:\n\n{0}\n\nВы уверены, что хотите продолжить?", + "DialogShaderDeletionErrorMessage": "Ошибка очистки кэша шейдеров в {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx обнаружил ошибку", + "DialogInvalidTitleIdErrorMessage": "Ошибка пользовательского интерфейса: выбранная игра не имеет действительного ID.", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Валидная системная прошивка не найдена в {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Установить прошивку {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Будет установлена версия прошивки {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЭто заменит текущую версию прошивки {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nПродолжить?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Установка прошивки...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Прошивка версии {0} успешно установлена.", + "DialogUserProfileDeletionWarningMessage": "Если выбранный профиль будет удален, другие профили не будут открываться.", + "DialogUserProfileDeletionConfirmMessage": "Удалить выбранный профиль?", + "DialogUserProfileUnsavedChangesTitle": "Внимание - Несохраненные изменения", + "DialogUserProfileUnsavedChangesMessage": "В эту учетную запись внесены изменения, которые не были сохранены.", + "DialogUserProfileUnsavedChangesSubMessage": "Отменить изменения?", + "DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки управления обновлены.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Сохранить?", + "DialogLoadFileErrorMessage": "{0}. Файл с ошибкой: {1}", + "DialogModAlreadyExistsMessage": "Мод уже существует", + "DialogModInvalidMessage": "Выбранная папка не содержит модов", + "DialogModDeleteNoParentMessage": "Невозможно удалить: не удалось найти папку мода \"{0}\"", + "DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры", + "DialogPerformanceCheckLoggingEnabledMessage": "У вас включено ведение журнала отладки, предназначенное только для разработчиков.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Хотите отключить ведение журнала отладки?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "У вас включен дамп шейдеров, который предназначен только для разработчиков.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить дамп шейдеров. Хотите отключить дамп шейдеров?", + "DialogLoadAppGameAlreadyLoadedMessage": "Игра уже загружена", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Пожалуйста, остановите эмуляцию или закройте эмулятор перед запуском другой игры.", + "DialogUpdateAddUpdateErrorMessage": "Указанный файл не содержит обновлений для выбранного приложения", + "DialogSettingsBackendThreadingWarningTitle": "Предупреждение: многопоточность в бэкенде", + "DialogSettingsBackendThreadingWarningMessage": "Для применения этой настройки необходимо перезапустить Ryujinx. В зависимости от используемой вами операционной системы вам может потребоваться вручную отключить многопоточность драйвера при использовании Ryujinx.", + "DialogModManagerDeletionWarningMessage": "Вы сейчас удалите мод: {0}\n\nВы уверены, что хотите продолжить?", + "DialogModManagerDeletionAllWarningMessage": "Вы сейчас удалите все выбранные моды для этой игры.\n\nВы уверены, что хотите продолжить?", + "SettingsTabGraphicsFeaturesOptions": "Функции & Улучшения", + "SettingsTabGraphicsBackendMultithreading": "Многопоточность графического бэкенда:", + "CommonAuto": "Автоматически", + "CommonOff": "Выключено", + "CommonOn": "Включено", + "InputDialogYes": "Да", + "InputDialogNo": "Нет", + "DialogProfileInvalidProfileNameErrorMessage": "Имя файла содержит недопустимые символы. Пожалуйста, попробуйте еще раз.", + "MenuBarOptionsPauseEmulation": "Пауза эмуляции", + "MenuBarOptionsResumeEmulation": "Продолжить", + "AboutUrlTooltipMessage": "Нажмите, чтобы открыть веб-сайт Ryujinx", + "AboutDisclaimerMessage": "Ryujinx никоим образом не связан ни с Nintendo™, ни с кем-либо из ее партнеров.", + "AboutAmiiboDisclaimerMessage": "Amiibo API (www.amiiboapi.com) используется для эмуляции Amiibo.", + "AboutPatreonUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx на Patreon", + "AboutGithubUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx на GitHub", + "AboutDiscordUrlTooltipMessage": "Нажмите, чтобы открыть приглашение на сервер Ryujinx в Discord", + "AboutTwitterUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx в X (бывший Twitter)", + "AboutRyujinxAboutTitle": "О программе:", + "AboutRyujinxAboutContent": "Ryujinx — это эмулятор Nintendo Switch™.\nПожалуйста, поддержите нас на Patreon.\nЧитайте последние новости в наших X (Twitter) или Discord.\nРазработчики, заинтересованные в участии, могут ознакомиться с проектом на GitHub или в Discord.", + "AboutRyujinxMaintainersTitle": "Разработка:", + "AboutRyujinxMaintainersContentTooltipMessage": "Нажмите, чтобы открыть страницу с участниками", + "AboutRyujinxSupprtersTitle": "Поддержка на Patreon:", + "AmiiboSeriesLabel": "Серия Amiibo", + "AmiiboCharacterLabel": "Персонаж", + "AmiiboScanButtonLabel": "Сканировать", + "AmiiboOptionsShowAllLabel": "Показать все Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Хак: Использовать случайный тег Uuid", + "DlcManagerTableHeadingEnabledLabel": "Включено", + "DlcManagerTableHeadingTitleIdLabel": "ID приложения", + "DlcManagerTableHeadingContainerPathLabel": "Путь к контейнеру", + "DlcManagerTableHeadingFullPathLabel": "Полный путь", + "DlcManagerRemoveAllButton": "Удалить все", + "DlcManagerEnableAllButton": "Включить все", + "DlcManagerDisableAllButton": "Отключить все", + "ModManagerDeleteAllButton": "Удалить все", + "MenuBarOptionsChangeLanguage": "Сменить язык", + "MenuBarShowFileTypes": "Показывать форматы файлов", + "CommonSort": "Сортировка", + "CommonShowNames": "Показывать названия", + "CommonFavorite": "Избранное", + "OrderAscending": "По возрастанию", + "OrderDescending": "По убыванию", + "SettingsTabGraphicsFeatures": "Функции", + "ErrorWindowTitle": "Окно ошибки", + "ToggleDiscordTooltip": "Включает или отключает отображение статуса \"Играет в игру\" в Discord", + "AddGameDirBoxTooltip": "Введите путь к папке с играми для добавления ее в список выше", + "AddGameDirTooltip": "Добавить папку с играми в список", + "RemoveGameDirTooltip": "Удалить выбранную папку игры", + "CustomThemeCheckTooltip": "Включить или отключить пользовательские темы", + "CustomThemePathTooltip": "Путь к пользовательской теме для интерфейса", + "CustomThemeBrowseTooltip": "Просмотр пользовательской темы интерфейса", + "DockModeToggleTooltip": "\"Стационарный\" режим запускает эмулятор, как если бы Nintendo Switch находилась в доке, что улучшает графику и разрешение в большинстве игр. И наоборот, при отключении этого режима эмулятор будет запускать игры в \"Портативном\" режиме, снижая качество графики.\n\nНастройте управление для Игрока 1 если планируете использовать в \"Стационарном\" режиме; настройте портативное управление если планируете использовать эмулятор в \"Портативном\" режиме.\n\nРекомендуется оставить включенным.", + "DirectKeyboardTooltip": "Поддержка прямого ввода с клавиатуры (HID). Предоставляет игре прямой доступ к клавиатуре в качестве устройства ввода текста.\nРаботает только с играми, которые изначально поддерживают использование клавиатуры с Switch.\nРекомендуется оставить выключенным.", + "DirectMouseTooltip": "Поддержка прямого ввода мыши (HID). Предоставляет игре прямой доступ к мыши в качестве указывающего устройства.\nРаботает только с играми, которые изначально поддерживают использование мыши совместно с железом Switch.\nРекомендуется оставить выключенным.", + "RegionTooltip": "Сменяет регион прошивки", + "LanguageTooltip": "Меняет язык прошивки", + "TimezoneTooltip": "Меняет часовой пояс прошивки", + "TimeTooltip": "Меняет системное время прошивки", + "VSyncToggleTooltip": "Эмуляция вертикальной синхронизации консоли, которая ограничивает количество кадров в секунду в большинстве игр; отключение может привести к тому, что игры будут запущены с более высокой частотой кадров, но загрузка игры может занять больше времени, либо игра не запустится вообще.\n\nМожно включать и выключать эту настройку непосредственно в игре с помощью горячих клавиш (F1 по умолчанию). Если планируете отключить вертикальную синхронизацию, рекомендуем настроить горячие клавиши.\n\nРекомендуется оставить включенным.", + "PptcToggleTooltip": "Сохраняет скомпилированные JIT-функции для того, чтобы не преобразовывать их по новой каждый раз при запуске игры.\n\nУменьшает статтеры и значительно ускоряет последующую загрузку игр.\n\nРекомендуется оставить включенным.", + "FsIntegrityToggleTooltip": "Проверяет файлы при загрузке игры и если обнаружены поврежденные файлы, выводит сообщение о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для помощи в устранении неполадок.\n\nРекомендуется оставить включенным.", + "AudioBackendTooltip": "Изменяет используемый аудио бэкенд для рендера звука.\n\nSDL2 является предпочтительным вариантом, в то время как OpenAL и SoundIO используются в качестве резервных.\n\nРекомендуется использование SDL2.", + "MemoryManagerTooltip": "Меняет разметку и доступ к гостевой памяти. Значительно влияет на производительность процессора.\n\nРекомендуется оставить \"Хост не установлен\"", + "MemoryManagerSoftwareTooltip": "Использует таблицу страниц для преобразования адресов. \nСамая высокая точность, но самая низкая производительность.", + "MemoryManagerHostTooltip": "Прямая разметка памяти в адресном пространстве хоста. \nЗначительно более быстрые запуск и компиляция JIT.", + "MemoryManagerUnsafeTooltip": "Производит прямую разметку памяти, но не маскирует адрес в гостевом адресном пространстве перед получением доступа. \nБыстро, но небезопасно. Гостевое приложение может получить доступ к памяти из Ryujinx, поэтому в этом режиме рекомендуется запускать только те программы, которым вы доверяете.", + "UseHypervisorTooltip": "Использует Hypervisor вместо JIT. Значительно увеличивает производительность, но может работать нестабильно.", + "DRamTooltip": "Использует альтернативный макет MemoryMode для имитации использования Nintendo Switch в режиме разработчика.\n\nПолезно только для пакетов текстур с высоким разрешением или модов добавляющих разрешение 4К. Не улучшает производительность.\n\nРекомендуется оставить выключенным.", + "IgnoreMissingServicesTooltip": "Игнорирует нереализованные сервисы Horizon в новых прошивках. Эта настройка поможет избежать вылеты при запуске определенных игр.\n\nРекомендуется оставить выключенным.", + "GraphicsBackendThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах видеоадаптера без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить Автоматически.", + "GalThreadingTooltip": "Выполняет команды графического бэкенда на втором потоке.\n\nУскоряет компиляцию шейдеров, уменьшает статтеры и повышает производительность на драйверах видеоадаптера без поддержки многопоточности. Производительность на драйверах с многопоточностью немного выше.\n\nРекомендуется оставить Автоматически.", + "ShaderCacheToggleTooltip": "Сохраняет кэш шейдеров на диске, для уменьшения статтеров при последующих запусках.\n\nРекомендуется оставить включенным.", + "ResolutionScaleTooltip": "Увеличивает разрешение рендера игры.\n\nНекоторые игры могут не работать с этой настройкой и выглядеть смазано даже когда разрешение увеличено. Для таких игр может потребоваться установка модов, которые убирают сглаживание или увеличивают разрешение рендеринга. \nДля использования последнего, вам нужно будет выбрать опцию \"Нативное\".\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже. Вы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nИмейте в виду, что \"4x\" является излишеством.", + "ResolutionScaleEntryTooltip": "Масштабирование разрешения с плавающей запятой, например 1,5. Неинтегральное масштабирование с большой вероятностью вызовет сбои в работе.", + "AnisotropyTooltip": "Уровень анизотропной фильтрации. \n\nУстановите значение Автоматически, чтобы использовать значение по умолчанию игры.", + "AspectRatioTooltip": "Соотношение сторон окна рендерера.\n\nИзмените эту настройку только если вы используете мод для соотношения сторон, иначе изображение будет растянуто.\n\nРекомендуется настройка 16:9.", + "ShaderDumpPathTooltip": "Путь с дампами графических шейдеров", + "FileLogTooltip": "Включает ведение журнала в файл на диске. Не влияет на производительность.", + "StubLogTooltip": "Включает ведение журнала-заглушки. Не влияет на производительность.", + "InfoLogTooltip": "Включает вывод сообщений информационного журнала в консоль. Не влияет на производительность.", + "WarnLogTooltip": "Включает вывод сообщений журнала предупреждений в консоль. Не влияет на производительность.", + "ErrorLogTooltip": "Включает вывод сообщений журнала ошибок. Не влияет на производительность.", + "TraceLogTooltip": "Выводит сообщения журнала трассировки в консоли. Не влияет на производительность.", + "GuestLogTooltip": "Включает вывод сообщений гостевого журнала. Не влияет на производительность.", + "FileAccessLogTooltip": "Включает вывод сообщений журнала доступа к файлам.", + "FSAccessLogModeTooltip": "Включает вывод журнала доступа к файловой системе. Возможные режимы: 0-3", + "DeveloperOptionTooltip": "Используйте с осторожностью", + "OpenGlLogLevel": "Требует включения соответствующих уровней ведения журнала", + "DebugLogTooltip": "Выводит журнал сообщений отладки в консоли.\n\nИспользуйте только в случае просьбы разработчика, так как включение этой функции затруднит чтение журналов и ухудшит работу эмулятора.", + "LoadApplicationFileTooltip": "Открывает файловый менеджер для выбора файла, совместимого с Nintendo Switch.", + "LoadApplicationFolderTooltip": "Открывает файловый менеджер для выбора распакованного приложения, совместимого с Nintendo Switch.", + "OpenRyujinxFolderTooltip": "Открывает папку с файлами Ryujinx. ", + "OpenRyujinxLogsTooltip": "Открывает папку в которую записываются логи", + "ExitTooltip": "Выйти из Ryujinx", + "OpenSettingsTooltip": "Открывает окно параметров", + "OpenProfileManagerTooltip": "Открыть менеджер учетных записей", + "StopEmulationTooltip": "Остановка эмуляции текущей игры и возврат к списку игр", + "CheckUpdatesTooltip": "Проверяет наличие обновлений для Ryujinx", + "OpenAboutTooltip": "Открывает окно «О программе»", + "GridSize": "Размер сетки", + "GridSizeTooltip": "Меняет размер сетки элементов", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Португальский язык (Бразилия)", + "AboutRyujinxContributorsButtonHeader": "Посмотреть всех участников", + "SettingsTabSystemAudioVolume": "Громкость: ", + "AudioVolumeTooltip": "Изменяет громкость звука", + "SettingsTabSystemEnableInternetAccess": "Гостевой доступ в интернет/сетевой режим", + "EnableInternetAccessTooltip": "Позволяет эмулированному приложению подключаться к Интернету.\n\nПри включении этой функции игры с возможностью сетевой игры могут подключаться друг к другу, если все эмуляторы (или реальные консоли) подключены к одной и той же точке доступа.\n\nНЕ разрешает подключение к серверам Nintendo. Может вызвать сбой в некоторых играх, которые пытаются подключиться к Интернету.\n\nРекомендутеся оставить выключенным.", + "GameListContextMenuManageCheatToolTip": "Открывает окно управления читами", + "GameListContextMenuManageCheat": "Управление читами", + "GameListContextMenuManageModToolTip": "Открывает окно управления модами", + "GameListContextMenuManageMod": "Управление модами", + "ControllerSettingsStickRange": "Диапазон:", + "DialogStopEmulationTitle": "Ryujinx - Остановка эмуляции", + "DialogStopEmulationMessage": "Вы уверены, что хотите остановить эмуляцию?", + "SettingsTabCpu": "Процессор", + "SettingsTabAudio": "Аудио", + "SettingsTabNetwork": "Сеть", + "SettingsTabNetworkConnection": "Подключение к сети", + "SettingsTabCpuCache": "Кэш процессора", + "SettingsTabCpuMemory": "Режим процессора", + "DialogUpdaterFlatpakNotSupportedMessage": "Пожалуйста, обновите Ryujinx через FlatHub.", + "UpdaterDisabledWarningTitle": "Средство обновления отключено", + "ControllerSettingsRotate90": "Повернуть на 90° по часовой стрелке", + "IconSize": "Размер обложек", + "IconSizeTooltip": "Меняет размер обложек", + "MenuBarOptionsShowConsole": "Показать консоль", + "ShaderCachePurgeError": "Ошибка очистки кэша шейдеров в {0}: {1}", + "UserErrorNoKeys": "Ключи не найдены", + "UserErrorNoFirmware": "Прошивка не найдена", + "UserErrorFirmwareParsingFailed": "Ошибка извлечения прошивки", + "UserErrorApplicationNotFound": "Приложение не найдено", + "UserErrorUnknown": "Неизвестная ошибка", + "UserErrorUndefined": "Неопределенная ошибка", + "UserErrorNoKeysDescription": "Ryujinx не удалось найти ваш 'prod.keys' файл", + "UserErrorNoFirmwareDescription": "Ryujinx не удалось найти ни одной установленной прошивки", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx не удалось распаковать выбранную прошивку. Обычно это вызвано устаревшими ключами.", + "UserErrorApplicationNotFoundDescription": "Ryujinx не удалось найти валидное приложение по указанному пути.", + "UserErrorUnknownDescription": "Произошла неизвестная ошибка", + "UserErrorUndefinedDescription": "Произошла неизвестная ошибка. Этого не должно происходить, пожалуйста, свяжитесь с разработчиками.", + "OpenSetupGuideMessage": "Открыть руководство по установке", + "NoUpdate": "Без обновлений", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Информация", + "RyujinxConfirm": "Ryujinx - Подтверждение", + "FileDialogAllTypes": "Все типы", + "Never": "Никогда", + "SwkbdMinCharacters": "Должно быть не менее {0} символов.", + "SwkbdMinRangeCharacters": "Должно быть {0}-{1} символов", + "SoftwareKeyboard": "Программная клавиатура", + "SoftwareKeyboardModeNumeric": "Должно быть в диапазоне 0-9 или '.'", + "SoftwareKeyboardModeAlphabet": "Не должно быть CJK-символов", + "SoftwareKeyboardModeASCII": "Текст должен быть только в ASCII кодировке", + "ControllerAppletControllers": "Поддерживаемые геймпады:", + "ControllerAppletPlayers": "Игроки:", + "ControllerAppletDescription": "Текущая конфигурация некорректна. Откройте параметры и перенастройте управление.", + "ControllerAppletDocked": "Используется стационарный режим. Управление в портативном режиме должно быть отключено.", + "UpdaterRenaming": "Переименование старых файлов...", + "UpdaterRenameFailed": "Программе обновления не удалось переименовать файл: {0}", + "UpdaterAddingFiles": "Добавление новых файлов...", + "UpdaterExtracting": "Извлечение обновления...", + "UpdaterDownloading": "Загрузка обновления...", + "Game": "Игра", + "Docked": "Стационарный режим", + "Handheld": "Портативный режим", + "ConnectionError": "Ошибка соединения", + "AboutPageDeveloperListMore": "{0} и другие...", + "ApiError": "Ошибка API.", + "LoadingHeading": "Загрузка {0}", + "CompilingPPTC": "Компиляция PTC", + "CompilingShaders": "Компиляция шейдеров", + "AllKeyboards": "Все клавиатуры", + "OpenFileDialogTitle": "Выберите совместимый файл для открытия", + "OpenFolderDialogTitle": "Выберите папку с распакованной игрой", + "AllSupportedFormats": "Все поддерживаемые форматы", + "RyujinxUpdater": "Ryujinx - Обновление", + "SettingsTabHotkeys": "Горячие клавиши", + "SettingsTabHotkeysHotkeys": "Горячие клавиши", + "SettingsTabHotkeysToggleVsyncHotkey": "Вертикальная синхронизация:", + "SettingsTabHotkeysScreenshotHotkey": "Сделать скриншот:", + "SettingsTabHotkeysShowUiHotkey": "Показать интерфейс:", + "SettingsTabHotkeysPauseHotkey": "Пауза эмуляции:", + "SettingsTabHotkeysToggleMuteHotkey": "Выключить звук:", + "ControllerMotionTitle": "Настройки управления движением", + "ControllerRumbleTitle": "Настройки вибрации", + "SettingsSelectThemeFileDialogTitle": "Выбрать файл темы", + "SettingsXamlThemeFile": "Файл темы Xaml", + "AvatarWindowTitle": "Управление аккаунтами - Аватар", + "Amiibo": "Amiibo", + "Unknown": "Неизвестно", + "Usage": "Применение", + "Writable": "Доступно для записи", + "SelectDlcDialogTitle": "Выберите файлы DLC", + "SelectUpdateDialogTitle": "Выберите файлы обновлений", + "SelectModDialogTitle": "Выбрать папку с модами", + "UserProfileWindowTitle": "Менеджер учетных записей", + "CheatWindowTitle": "Менеджер читов", + "DlcWindowTitle": "Управление DLC для {0} ({1})", + "ModWindowTitle": "Управление модами для {0} ({1})", + "UpdateWindowTitle": "Менеджер обновлений игр", + "CheatWindowHeading": "Доступные читы для {0} [{1}]", + "BuildId": "ID версии:", + "DlcWindowHeading": "{0} DLC", + "ModWindowHeading": "Моды для {0} ", + "UserProfilesEditProfile": "Изменить выбранные", + "Cancel": "Отмена", + "Save": "Сохранить", + "Discard": "Отменить", + "Paused": "Приостановлено", + "UserProfilesSetProfileImage": "Установить аватар", + "UserProfileEmptyNameError": "Необходимо ввести никнейм", + "UserProfileNoImageError": "Необходимо установить аватар", + "GameUpdateWindowHeading": "Доступные обновления для {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "Увеличить разрешение:", + "SettingsTabHotkeysResScaleDownHotkey": "Уменьшить разрешение:", + "UserProfilesName": "Никнейм:", + "UserProfilesUserId": "ID пользователя:", + "SettingsTabGraphicsBackend": "Графический бэкенд", + "SettingsTabGraphicsBackendTooltip": "Выберает бэкенд, который будет использован в эмуляторе.\n\nVulkan является лучшим выбором для всех современных графических карт с актуальными драйверами. В Vulkan также включена более быстрая компиляция шейдеров (меньше статтеров) для всех видеоадаптеров.\n\nПри использовании OpenGL можно достичь лучших результатов на старых видеоадаптерах Nvidia и AMD в Linux или на видеоадаптерах с небольшим количеством видеопамяти, хотя статтеров при компиляции шейдеров будет больше.\n\nРекомендуется использовать Vulkan. Используйте OpenGL, если ваш видеоадаптер не поддерживает Vulkan даже с актуальными драйверами.", + "SettingsEnableTextureRecompression": "Пережимать текстуры", + "SettingsEnableTextureRecompressionTooltip": "Сжатие ASTC текстур для уменьшения использования VRAM. \n\nИгры, использующие этот формат текстур: Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder и The Legend of Zelda: Tears of the Kingdom. \nНа видеоадаптерах с 4GiB видеопамяти или менее возможны вылеты при запуске этих игр. \n\nВключите, только если у вас заканчивается видеопамять в вышеупомянутых играх. \n\nРекомендуется оставить выключенным.", + "SettingsTabGraphicsPreferredGpu": "Предпочтительный видеоадаптер", + "SettingsTabGraphicsPreferredGpuTooltip": "Выберает видеоадаптер, который будет использоваться графическим бэкендом Vulkan.\n\nЭта настройка не влияет на видеоадаптер, который будет использоваться с OpenGL.\n\nЕсли вы не уверены что нужно выбрать, используйте графический процессор, помеченный как \"dGPU\". Если его нет, оставьте выбор по умолчанию.", + "SettingsAppRequiredRestartMessage": "Требуется перезапуск Ryujinx", + "SettingsGpuBackendRestartMessage": "Графический бэкенд или настройки графического процессора были изменены. Требуется перезапуск для вступления в силу изменений.", + "SettingsGpuBackendRestartSubMessage": "Перезапустить сейчас?", + "RyujinxUpdaterMessage": "Обновить Ryujinx до последней версии?", + "SettingsTabHotkeysVolumeUpHotkey": "Увеличить громкость:", + "SettingsTabHotkeysVolumeDownHotkey": "Уменьшить громкость:", + "SettingsEnableMacroHLE": "Использовать макрос высокоуровневой эмуляции видеоадаптера", + "SettingsEnableMacroHLETooltip": "Высокоуровневая эмуляции макрокода видеоадаптера.\n\nПовышает производительность, но может вызывать графические артефакты в некоторых играх.\n\nРекомендуется оставить включенным.", + "SettingsEnableColorSpacePassthrough": "Пропускать цветовое пространство", + "SettingsEnableColorSpacePassthroughTooltip": "Направляет бэкенд Vulkan на передачу информации о цвете без указания цветового пространства. Для пользователей с экранами с расширенной гаммой данная настройка приводит к получению более ярких цветов за счет снижения корректности цветопередачи.", + "VolumeShort": "Громкость", + "UserProfilesManageSaves": "Управление сохранениями", + "DeleteUserSave": "Удалить сохранения для этой игры?", + "IrreversibleActionNote": "Данное действие является необратимым.", + "SaveManagerHeading": "Редактирование сохранений для {0} ({1})", + "SaveManagerTitle": "Менеджер сохранений", + "Name": "Название", + "Size": "Размер", + "Search": "Поиск", + "UserProfilesRecoverLostAccounts": "Восстановить учетные записи", + "Recover": "Восстановление", + "UserProfilesRecoverHeading": "Были найдены сохранения для следующих аккаунтов", + "UserProfilesRecoverEmptyList": "Нет учетных записей для восстановления", + "GraphicsAATooltip": "Применимое сглаживание для рендера.\n\nFXAA размывает большую часть изображения, SMAA попытается найти \"зазубренные\" края и сгладить их.\n\nНе рекомендуется использовать вместе с масштабирующим фильтром FSR.\n\nЭта опция может быть изменена во время игры по нажатию \"Применить\" ниже; \nВы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не найдёте подходящую настройку игры.\n\nРекомендуется использовать \"Нет\".", + "GraphicsAALabel": "Сглаживание:", + "GraphicsScalingFilterLabel": "Интерполяция:", + "GraphicsScalingFilterTooltip": "Фильтрация текстур, которая будет применяться при масштабировании.\n\nБилинейная хорошо работает для 3D-игр и является настройкой по умолчанию.\n\nСтупенчатая рекомендуется для пиксельных игр.\n\nFSR это фильтр резкости, который не рекомендуется использовать с FXAA или SMAA.\n\nЭта опция может быть изменена во время игры по нажатию кнопки \"Применить\" ниже; \nВы можете просто переместить окно настроек в сторону и поэкспериментировать, пока не подберете подходящие настройки для конкретной игры.\n\nРекомендуется использовать \"Билинейная\".", + "GraphicsScalingFilterBilinear": "Билинейная", + "GraphicsScalingFilterNearest": "Ступенчатая", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "Уровень", + "GraphicsScalingFilterLevelTooltip": "Выбор режима работы FSR 1.0. Выше - четче.", + "SmaaLow": "SMAA Низкое", + "SmaaMedium": "SMAA Среднее", + "SmaaHigh": "SMAA Высокое", + "SmaaUltra": "SMAA Ультра", + "UserEditorTitle": "Редактирование пользователя", + "UserEditorTitleCreate": "Создание пользователя", + "SettingsTabNetworkInterface": "Сетевой интерфейс:", + "NetworkInterfaceTooltip": "Сетевой интерфейс, используемый для функций LAN/LDN.\n\nМожет использоваться для игры через интернет в сочетании с VPN или XLink Kai и игрой с поддержкой LAN.\n\nРекомендуется использовать \"По умолчанию\".", + "NetworkInterfaceDefault": "По умолчанию", + "PackagingShaders": "Упаковка шейдеров", + "AboutChangelogButton": "Список изменений на GitHub", + "AboutChangelogButtonTooltipMessage": "Нажмите, чтобы открыть список изменений для этой версии", + "SettingsTabNetworkMultiplayer": "Мультиплеер", + "MultiplayerMode": "Режим:", + "MultiplayerModeTooltip": "Меняет многопользовательский режим LDN.\n\nLdnMitm модифицирует функциональность локальной беспроводной/игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить отключенным.", + "MultiplayerModeDisabled": "Отключено", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json new file mode 100644 index 00000000..62944226 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -0,0 +1,780 @@ +{ + "Language": "ภาษาไทย", + "MenuBarFileOpenApplet": "เปิด Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "เปิด Mii Editor Applet ในโหมดสแตนด์อโลน", + "SettingsTabInputDirectMouseAccess": "เข้าถึงเมาส์ได้โดยตรง", + "SettingsTabSystemMemoryManagerMode": "โหมดจัดการหน่วยความจำ:", + "SettingsTabSystemMemoryManagerModeSoftware": "ซอฟต์แวร์", + "SettingsTabSystemMemoryManagerModeHost": "โฮสต์ (เร็ว)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "ไม่ได้ตรวจสอบโฮสต์ (เร็วที่สุด, แต่ไม่ปลอดภัย)", + "SettingsTabSystemUseHypervisor": "ใช้งาน Hypervisor", + "MenuBarFile": "ไฟล์", + "MenuBarFileOpenFromFile": "โหลดแอปพลิเคชั่นจากไฟล์", + "MenuBarFileOpenUnpacked": "โหลดเกมที่คลายแพ็กแล้ว", + "MenuBarFileOpenEmuFolder": "เปิดโฟลเดอร์ Ryujinx", + "MenuBarFileOpenLogsFolder": "เปิดโฟลเดอร์ Logs", + "MenuBarFileExit": "_ออก", + "MenuBarOptions": "_ตัวเลือก", + "MenuBarOptionsToggleFullscreen": "สลับการแสดงผลแบบเต็มหน้าจอ", + "MenuBarOptionsStartGamesInFullscreen": "เริ่มเกมในโหมดเต็มหน้าจอ", + "MenuBarOptionsStopEmulation": "หยุดการจำลอง", + "MenuBarOptionsSettings": "_ตั้งค่า", + "MenuBarOptionsManageUserProfiles": "_จัดการโปรไฟล์ผู้ใช้งาน", + "MenuBarActions": "การดำเนินการ", + "MenuBarOptionsSimulateWakeUpMessage": "จำลองข้อความปลุก", + "MenuBarActionsScanAmiibo": "สแกนหา Amiibo", + "MenuBarTools": "_เครื่องมือ", + "MenuBarToolsInstallFirmware": "ติดตั้งเฟิร์มแวร์", + "MenuBarFileToolsInstallFirmwareFromFile": "ติดตั้งเฟิร์มแวร์จาก ไฟล์ XCI หรือ ไฟล์ ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "ติดตั้งเฟิร์มแวร์จากไดเร็กทอรี", + "MenuBarToolsManageFileTypes": "จัดการประเภทไฟล์", + "MenuBarToolsInstallFileTypes": "ติดตั้งตามประเภทของไฟล์", + "MenuBarToolsUninstallFileTypes": "ถอนการติดตั้งตามประเภทของไฟล์", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "_ช่วยเหลือ", + "MenuBarHelpCheckForUpdates": "ตรวจสอบอัปเดต", + "MenuBarHelpAbout": "เกี่ยวกับ", + "MenuSearch": "กำลังค้นหา...", + "GameListHeaderFavorite": "ชื่นชอบ", + "GameListHeaderIcon": "ไอคอน", + "GameListHeaderApplication": "ชื่อ", + "GameListHeaderDeveloper": "ผู้พัฒนา", + "GameListHeaderVersion": "เวอร์ชั่น", + "GameListHeaderTimePlayed": "เล่นไปแล้ว", + "GameListHeaderLastPlayed": "เล่นล่าสุด", + "GameListHeaderFileExtension": "นามสกุลไฟล์", + "GameListHeaderFileSize": "ขนาดไฟล์", + "GameListHeaderPath": "ที่อยู่ไฟล์", + "GameListContextMenuOpenUserSaveDirectory": "เปิดไดเร็กทอรี่บันทึกของผู้ใช้", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "เปิดไดเร็กทอรี่ซึ่งมีการบันทึกผู้ใช้ของแอปพลิเคชัน", + "GameListContextMenuOpenDeviceSaveDirectory": "เปิดไดเร็กทอรี่บันทึกของอุปกรณ์", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "เปิดไดเรกทอรี่ซึ่งมีบันทึกอุปกรณ์ของแอปพลิเคชัน", + "GameListContextMenuOpenBcatSaveDirectory": "เปิดไดเรกทอรี่บันทึก BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "เปิดไดเรกทอรี่ซึ่งมีการบันทึก BCAT ของแอปพลิเคชัน", + "GameListContextMenuManageTitleUpdates": "จัดการอัปเดตตามหัวข้อ", + "GameListContextMenuManageTitleUpdatesToolTip": "เปิดหน้าต่างการจัดการการอัพเดตหัวข้อ", + "GameListContextMenuManageDlc": "จัดการ DLC", + "GameListContextMenuManageDlcToolTip": "เปิดหน้าต่างจัดการ DLC", + "GameListContextMenuCacheManagement": "จัดการ แคช", + "GameListContextMenuCacheManagementPurgePptc": "เพิ่มเข้าคิวงาน PPTC ที่สร้างใหม่", + "GameListContextMenuCacheManagementPurgePptcToolTip": "ทริกเกอร์ PPTC ให้สร้างใหม่ในเวลาบูตเมื่อเปิดตัวเกมครั้งถัดไป", + "GameListContextMenuCacheManagementPurgeShaderCache": "ล้างแคช พื้นผิวและแสงเงา", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "ลบแคช พื้นผิวและแสงเงา ของแอปพลิเคชัน", + "GameListContextMenuCacheManagementOpenPptcDirectory": "เปิดไดเรกทอรี่ PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "เปิดไดเร็กทอรี่ PPTC แคช ของแอปพลิเคชัน", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "เปิดไดเรกทอรี่ แคช พื้นผิวและแสงเงา", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "เปิดไดเรกทอรี่ แคช พื้นผิวและแสงเงา ของแอปพลิเคชัน", + "GameListContextMenuExtractData": "แยกส่วนข้อมูล", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "แยกส่วน ExeFS ออกจากการกำหนดค่าปัจจุบันของแอปพลิเคชัน (รวมถึงการอัปเดต)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "แยกส่วน RomFS ออกจากการกำหนดค่าปัจจุบันของแอปพลิเคชัน (รวมถึงการอัพเดต)", + "GameListContextMenuExtractDataLogo": "โลโก้", + "GameListContextMenuExtractDataLogoToolTip": "แยกส่วน โลโก้ ออกจากการกำหนดค่าปัจจุบันของแอปพลิเคชัน (รวมถึงการอัปเดต)", + "GameListContextMenuCreateShortcut": "สร้างทางลัดของแอปพลิเคชัน", + "GameListContextMenuCreateShortcutToolTip": "สร้างทางลัดบนเดสก์ท็อปที่เรียกใช้แอปพลิเคชันที่เลือก", + "GameListContextMenuCreateShortcutToolTipMacOS": "สร้างทางลัดในโฟลเดอร์ Applications ของ macOS ที่เรียกใช้ Application ที่เลือก", + "GameListContextMenuOpenModsDirectory": "เปิดไดเร็กทอรี่ Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "เปิดไดเร็กทอรี่ Mods ของแอปพลิเคชัน", + "GameListContextMenuOpenSdModsDirectory": "เปิดไดเร็กทอรี่ Mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "เปิดไดเร็กทอรี่ Atmosphere ของการ์ด SD สำรองซึ่งมี Mods ของแอปพลิเคชัน มีประโยชน์สำหรับ Mods ที่บรรจุมากับฮาร์ดแวร์จริง", + "StatusBarGamesLoaded": "เกมส์โหลดแล้ว {0}/{1}", + "StatusBarSystemVersion": "เวอร์ชั่นของระบบ: {0}", + "LinuxVmMaxMapCountDialogTitle": "ตรวจพบขีดจำกัดต่ำสุด สำหรับการแมปหน่วยความจำ", + "LinuxVmMaxMapCountDialogTextPrimary": "คุณต้องการที่จะเพิ่มค่า vm.max_map_count ไปยัง {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "บางเกมอาจพยายามสร้างการแมปหน่วยความจำมากกว่าที่ได้รับอนุญาตในปัจจุบัน รียูจินซ์ จะปิดตัวลงเมื่อเกินขีดจำกัดนี้", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "ใช่, จนกว่าจะรีสตาร์ทครั้งถัดไป", + "LinuxVmMaxMapCountDialogButtonPersistent": "ใช่, อย่างถาวร", + "LinuxVmMaxMapCountWarningTextPrimary": "จำนวนสูงสุดของการแม็ปหน่วยความจำ ต่ำกว่าที่แนะนำ", + "LinuxVmMaxMapCountWarningTextSecondary": "ค่าปัจจุบันของ vm.max_map_count ({0}) มีค่าต่ำกว่า {1} บางเกมอาจพยายามสร้างการแมปหน่วยความจำมากกว่าที่ได้รับอนุญาตในปัจจุบัน รียูจินซ์ จะปิดตัวลงเมื่อเกินขีดจำกัดนี้\n\nคุณอาจต้องการเพิ่มขีดจำกัดด้วยตนเองหรือติดตั้ง pkexec ซึ่งอนุญาตให้ ริวจินซ์ เพื่อช่วยเหลือคุณได้", + "Settings": "ตั้งค่า", + "SettingsTabGeneral": "หน้าจอผู้ใช้", + "SettingsTabGeneralGeneral": "ทั่วไป", + "SettingsTabGeneralEnableDiscordRichPresence": "เปิดใช้งาน Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "ตรวจหาการอัปเดตเมื่อเปิดโปรแกรม", + "SettingsTabGeneralShowConfirmExitDialog": "แสดง \"ยืนยันการออก\" กล่องข้อความโต้ตอบ", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", + "SettingsTabGeneralHideCursor": "ซ่อน เคอร์เซอร์:", + "SettingsTabGeneralHideCursorNever": "ไม่มี", + "SettingsTabGeneralHideCursorOnIdle": "เมื่อไม่ได้ใช้", + "SettingsTabGeneralHideCursorAlways": "ตลอดเวลา", + "SettingsTabGeneralGameDirectories": "ไดเรกทอรี่ของเกม", + "SettingsTabGeneralAdd": "เพิ่ม", + "SettingsTabGeneralRemove": "เอาออก", + "SettingsTabSystem": "ระบบ", + "SettingsTabSystemCore": "แกนกลาง", + "SettingsTabSystemSystemRegion": "ภูมิภาคของระบบ:", + "SettingsTabSystemSystemRegionJapan": "ญี่ปุ่น", + "SettingsTabSystemSystemRegionUSA": "สหรัฐอเมริกา", + "SettingsTabSystemSystemRegionEurope": "ยุโรป", + "SettingsTabSystemSystemRegionAustralia": "ออสเตรเลีย", + "SettingsTabSystemSystemRegionChina": "จีน", + "SettingsTabSystemSystemRegionKorea": "เกาหลี", + "SettingsTabSystemSystemRegionTaiwan": "ไต้หวัน", + "SettingsTabSystemSystemLanguage": "ภาษาของระบบ:", + "SettingsTabSystemSystemLanguageJapanese": "ญี่ปุ่น", + "SettingsTabSystemSystemLanguageAmericanEnglish": "อังกฤษ (อเมริกัน)", + "SettingsTabSystemSystemLanguageFrench": "ฝรั่งเศส", + "SettingsTabSystemSystemLanguageGerman": "เยอรมัน", + "SettingsTabSystemSystemLanguageItalian": "อิตาลี", + "SettingsTabSystemSystemLanguageSpanish": "สเปน", + "SettingsTabSystemSystemLanguageChinese": "จีน", + "SettingsTabSystemSystemLanguageKorean": "เกาหลี", + "SettingsTabSystemSystemLanguageDutch": "ดัตช์", + "SettingsTabSystemSystemLanguagePortuguese": "โปรตุเกส", + "SettingsTabSystemSystemLanguageRussian": "รัสเซีย", + "SettingsTabSystemSystemLanguageTaiwanese": "จีนตัวเต็ม (ไต้หวัน)", + "SettingsTabSystemSystemLanguageBritishEnglish": "อังกฤษ (บริติช)", + "SettingsTabSystemSystemLanguageCanadianFrench": "ฝรั่งเศส (แคนาดา)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "สเปน (ลาตินอเมริกา)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "จีน (ตัวย่อ)", + "SettingsTabSystemSystemLanguageTraditionalChinese": "จีน (ดั้งเดิม)", + "SettingsTabSystemSystemTimeZone": "เขตเวลาของระบบ:", + "SettingsTabSystemSystemTime": "เวลาของระบบ:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (แคชโปรไฟล์การแปลแบบถาวร)", + "SettingsTabSystemEnableFsIntegrityChecks": "ตรวจสอบความถูกต้องของ FS", + "SettingsTabSystemAudioBackend": "ระบบเสียงเบื้องหลัง:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "แฮ็ก", + "SettingsTabSystemHacksNote": "อาจทำให้เกิดข้อผิดพลาดได้", + "SettingsTabSystemExpandDramSize": "ใช้รูปแบบหน่วยความจำสำรอง (โหมดนักพัฒนา)", + "SettingsTabSystemIgnoreMissingServices": "ไม่สนใจบริการที่ขาดหายไป", + "SettingsTabGraphics": "กราฟิก", + "SettingsTabGraphicsAPI": "กราฟฟิก API", + "SettingsTabGraphicsEnableShaderCache": "เปิดใช้งาน แคชพื้นผิวและแสงเงา", + "SettingsTabGraphicsAnisotropicFiltering": "ตัวกรองแบบ Anisotropic:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "อัตโนมัติ", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "อัตราส่วนความละเอียด:", + "SettingsTabGraphicsResolutionScaleCustom": "กำหนดเอง (ไม่แนะนำ)", + "SettingsTabGraphicsResolutionScaleNative": "พื้นฐานของระบบ (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (ไม่แนะนำ)", + "SettingsTabGraphicsAspectRatio": "อัตราส่วนภาพ:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "ยืดภาพเพื่อให้พอดีกับหน้าต่าง", + "SettingsTabGraphicsDeveloperOptions": "ตัวเลือกนักพัฒนา", + "SettingsTabGraphicsShaderDumpPath": "ที่เก็บ ดัมพ์ไฟล์ พื้นผิวและแสงเงา:", + "SettingsTabLogging": "ประวัติ", + "SettingsTabLoggingLogging": "ประวัติ", + "SettingsTabLoggingEnableLoggingToFile": "เปิดใช้งาน ประวัติ ไปยังไฟล์", + "SettingsTabLoggingEnableStubLogs": "เปิดใช้งาน ประวัติ", + "SettingsTabLoggingEnableInfoLogs": "เปิดใช้งาน ประวัติการใช้งาน", + "SettingsTabLoggingEnableWarningLogs": "เปิดใช้งาน ประวัติคำเตือน", + "SettingsTabLoggingEnableErrorLogs": "เปิดใช้งาน ประวัติข้อผิดพลาด", + "SettingsTabLoggingEnableTraceLogs": "เปิดใช้งาน ประวัติการติดตาม", + "SettingsTabLoggingEnableGuestLogs": "เปิดใช้งาน บันทึกของผู้เยี่ยมชม", + "SettingsTabLoggingEnableFsAccessLogs": "เปิดใช้งาน ประวัติการเข้าถึง Fs", + "SettingsTabLoggingFsGlobalAccessLogMode": "โหมด ประวัติการเข้าถึงส่วนกลาง:", + "SettingsTabLoggingDeveloperOptions": "ตัวเลือกนักพัฒนา", + "SettingsTabLoggingDeveloperOptionsNote": "คำเตือน: จะทำให้ประสิทธิภาพลดลง", + "SettingsTabLoggingGraphicsBackendLogLevel": "ระดับการบันทึกประวัติ กราฟิกเบื้องหลัง:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "ไม่มี", + "SettingsTabLoggingGraphicsBackendLogLevelError": "ผิดพลาด", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "ช้าลง", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "ทั้งหมด", + "SettingsTabLoggingEnableDebugLogs": "เปิดใช้งาน ประวัติแก้ไขข้อบกพร่อง", + "SettingsTabInput": "ป้อนข้อมูล", + "SettingsTabInputEnableDockedMode": "ด็อกโหมด", + "SettingsTabInputDirectKeyboardAccess": "เข้าถึงคีย์บอร์ดโดยตรง", + "SettingsButtonSave": "บันทึก", + "SettingsButtonClose": "ปิด", + "SettingsButtonOk": "ตกลง", + "SettingsButtonCancel": "ยกเลิก", + "SettingsButtonApply": "นำไปใช้", + "ControllerSettingsPlayer": "ผู้เล่น", + "ControllerSettingsPlayer1": "ผู้เล่นคนที่ 1", + "ControllerSettingsPlayer2": "ผู้เล่นคนที่ 2", + "ControllerSettingsPlayer3": "ผู้เล่นคนที่ 3", + "ControllerSettingsPlayer4": "ผู้เล่นคนที่ 4", + "ControllerSettingsPlayer5": "ผู้เล่นคนที่ 5", + "ControllerSettingsPlayer6": "ผู้เล่นคนที่ 6", + "ControllerSettingsPlayer7": "ผู้เล่นคนที่ 7", + "ControllerSettingsPlayer8": "ผู้เล่นคนที่ 8", + "ControllerSettingsHandheld": "แฮนด์เฮลด์โหมด", + "ControllerSettingsInputDevice": "อุปกรณ์ป้อนข้อมูล", + "ControllerSettingsRefresh": "รีเฟรช", + "ControllerSettingsDeviceDisabled": "ปิดการใช้งาน", + "ControllerSettingsControllerType": "ประเภทของคอนโทรลเลอร์", + "ControllerSettingsControllerTypeHandheld": "แฮนด์เฮลด์", + "ControllerSettingsControllerTypeProController": "โปรคอนโทรลเลอร์", + "ControllerSettingsControllerTypeJoyConPair": "จับคู่ จอยคอน", + "ControllerSettingsControllerTypeJoyConLeft": "จอยคอน ด้านซ้าย", + "ControllerSettingsControllerTypeJoyConRight": "จอยคอน ด้านขวา", + "ControllerSettingsProfile": "โปรไฟล์", + "ControllerSettingsProfileDefault": "ค่าเริ่มต้น", + "ControllerSettingsLoad": "โหลด", + "ControllerSettingsAdd": "เพิ่ม", + "ControllerSettingsRemove": "เอาออก", + "ControllerSettingsButtons": "ปุ่มกด", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "ปุ่มลูกศร", + "ControllerSettingsDPadUp": "ขึ้น", + "ControllerSettingsDPadDown": "ลง", + "ControllerSettingsDPadLeft": "ซ้าย", + "ControllerSettingsDPadRight": "ขวา", + "ControllerSettingsStickButton": "ปุ่ม", + "ControllerSettingsStickUp": "ขึ้น", + "ControllerSettingsStickDown": "ลง", + "ControllerSettingsStickLeft": "ซ้าย", + "ControllerSettingsStickRight": "ขวา", + "ControllerSettingsStickStick": "จอยสติ๊ก", + "ControllerSettingsStickInvertXAxis": "กลับทิศทางของแกน X", + "ControllerSettingsStickInvertYAxis": "กลับทิศทางของแกน Y", + "ControllerSettingsStickDeadzone": "โซนที่ไม่ทำงานของ จอยสติ๊ก:", + "ControllerSettingsLStick": "จอยสติ๊ก ด้านซ้าย", + "ControllerSettingsRStick": "จอยสติ๊ก ด้านขวา", + "ControllerSettingsTriggersLeft": "ทริกเกอร์ ด้านซ้าย", + "ControllerSettingsTriggersRight": "ทริกเกอร์ ด้านขวา", + "ControllerSettingsTriggersButtonsLeft": "ปุ่มทริกเกอร์ ด้านซ้าย", + "ControllerSettingsTriggersButtonsRight": "ปุ่มทริกเกอร์ ด้านขวา", + "ControllerSettingsTriggers": "ทริกเกอร์", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "ปุ่มกดเสริม ด้านซ้าย", + "ControllerSettingsExtraButtonsRight": "ปุ่มกดเสริม ด้านขวา", + "ControllerSettingsMisc": "การควบคุมเพิ่มเติม", + "ControllerSettingsTriggerThreshold": "ตั้งค่าขีดจำกัดของ ทริกเกอร์:", + "ControllerSettingsMotion": "การเคลื่อนไหว", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "ใช้การเคลื่อนไหวที่เข้ากันได้กับ CemuHook", + "ControllerSettingsMotionControllerSlot": "ช่องเสียบ คอนโทรลเลอร์:", + "ControllerSettingsMotionMirrorInput": "นำเข้าการสะท้อน การควบคุม", + "ControllerSettingsMotionRightJoyConSlot": "ช่องเสียบ จอยคอน ด้านขวา:", + "ControllerSettingsMotionServerHost": "เจ้าของเซิร์ฟเวอร์:", + "ControllerSettingsMotionGyroSensitivity": "ความไวของไจโร:", + "ControllerSettingsMotionGyroDeadzone": "ส่วนไม่ทำงานของไจโร:", + "ControllerSettingsSave": "บันทึก", + "ControllerSettingsClose": "ปิด", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", + "UserProfilesSelectedUserProfile": "โปรไฟล์ผู้ใช้งานที่เลือก:", + "UserProfilesSaveProfileName": "บันทึกชื่อโปรไฟล์", + "UserProfilesChangeProfileImage": "เปลี่ยนรูปโปรไฟล์", + "UserProfilesAvailableUserProfiles": "โปรไฟล์ผู้ใช้ที่ใช้งานได้:", + "UserProfilesAddNewProfile": "สร้างโปรไฟล์ใหม่", + "UserProfilesDelete": "ลบ", + "UserProfilesClose": "ปิด", + "ProfileNameSelectionWatermark": "เลือก ชื่อเล่น", + "ProfileImageSelectionTitle": "เลือก รูปโปรไฟล์ ของคุณ", + "ProfileImageSelectionHeader": "เลือก รูปโปรไฟล์", + "ProfileImageSelectionNote": "คุณสามารถนำเข้ารูปโปรไฟล์ที่กำหนดเอง หรือ เลือกอวาต้าจากเฟิร์มแวร์ระบบได้", + "ProfileImageSelectionImportImage": "นำเข้า ไฟล์รูปภาพ", + "ProfileImageSelectionSelectAvatar": "เลือก รูปอวาต้า เฟิร์มแวร์", + "InputDialogTitle": "กล่องโต้ตอบการป้อนข้อมูล", + "InputDialogOk": "ตกลง", + "InputDialogCancel": "ยกเลิก", + "InputDialogAddNewProfileTitle": "เลือก ชื่อโปรไฟล์", + "InputDialogAddNewProfileHeader": "กรุณาใส่ชื่อโปรไฟล์", + "InputDialogAddNewProfileSubtext": "(ความยาวสูงสุด: {0})", + "AvatarChoose": "เลือก รูปอวาต้า ของคุณ", + "AvatarSetBackgroundColor": "ตั้งค่าสีพื้นหลัง", + "AvatarClose": "ปิด", + "ControllerSettingsLoadProfileToolTip": "โหลด โปรไฟล์", + "ControllerSettingsAddProfileToolTip": "เพิ่ม โปรไฟล์", + "ControllerSettingsRemoveProfileToolTip": "ลบ โปรไฟล์", + "ControllerSettingsSaveProfileToolTip": "บันทึก โปรไฟล์", + "MenuBarFileToolsTakeScreenshot": "ถ่ายภาพหน้าจอ", + "MenuBarFileToolsHideUi": "ซ่อน UI", + "GameListContextMenuRunApplication": "เรียกใช้แอปพลิเคชัน", + "GameListContextMenuToggleFavorite": "สลับรายการโปรด", + "GameListContextMenuToggleFavoriteToolTip": "สลับสถานะเกมที่ชื่นชอบ", + "SettingsTabGeneralTheme": "ธีม:", + "SettingsTabGeneralThemeDark": "มืด", + "SettingsTabGeneralThemeLight": "สว่าง", + "ControllerSettingsConfigureGeneral": "กำหนดค่า", + "ControllerSettingsRumble": "การสั่นไหว", + "ControllerSettingsRumbleStrongMultiplier": "เพิ่มความแรงการสั่นไหว", + "ControllerSettingsRumbleWeakMultiplier": "ลดความแรงการสั่นไหว", + "DialogMessageSaveNotAvailableMessage": "ไม่มีข้อมูลบันทึกไว้สำหรับ {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "คุณต้องการสร้างข้อมูลบันทึกสำหรับเกมนี้หรือไม่?", + "DialogConfirmationTitle": "ริวจินซ์ - ยืนยัน", + "DialogUpdaterTitle": "รียูจินซ์ - อัพเดต", + "DialogErrorTitle": "รียูจินซ์ - ผิดพลาด", + "DialogWarningTitle": "รียูจินซ์ - คำเตือน", + "DialogExitTitle": "รียูจินซ์ - ออก", + "DialogErrorMessage": "รียูจินซ์ พบข้อผิดพลาด", + "DialogExitMessage": "คุณแน่ใจหรือไม่ว่าต้องการปิด ริวจินซ์ หรือไม่?", + "DialogExitSubMessage": "ข้อมูลที่ไม่ได้บันทึกทั้งหมดจะสูญหาย!", + "DialogMessageCreateSaveErrorMessage": "มีข้อผิดพลาดในการสร้างข้อมูลการบันทึกที่ระบุ: {0}", + "DialogMessageFindSaveErrorMessage": "มีข้อผิดพลาดในการค้นหาข้อมูลที่บันทึกไว้ที่ระบุ: {0}", + "FolderDialogExtractTitle": "เลือกโฟลเดอร์ที่จะแตกไฟล์เข้าไป", + "DialogNcaExtractionMessage": "กำลังแตกไฟล์ {0} จากส่วน {1}...", + "DialogNcaExtractionTitle": "รียูจินซ์ - เครื่องมือแตกไฟล์ของ NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "เกิดความล้มเหลวในการแตกไฟล์เนื่องจากไม่พบ NCA หลักในไฟล์ที่เลือก", + "DialogNcaExtractionCheckLogErrorMessage": "เกิดความล้มเหลวในการแตกไฟล์ โปรดอ่านไฟล์บันทึกเพื่อดูข้อมูลเพิ่มเติม", + "DialogNcaExtractionSuccessMessage": "การแตกไฟล์เสร็จสมบูรณ์แล้ว", + "DialogUpdaterConvertFailedMessage": "ไม่สามารถแปลงเวอร์ชั่น รียูจินซ์ ปัจจุบันได้", + "DialogUpdaterCancelUpdateMessage": "ยกเลิกการอัพเดต!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "คุณกำลังใช้ รียูจินซ์ เวอร์ชั่นที่อัปเดตล่าสุด!", + "DialogUpdaterFailedToGetVersionMessage": "เกิดข้อผิดพลาดขณะพยายามรับข้อมูลเวอร์ชั่นจาก GitHub Release ปัญหานี้อาจเกิดขึ้นได้หากมีการรวบรวมเวอร์ชั่นใหม่โดย GitHub Actions โปรดลองอีกครั้งในอีกไม่กี่นาทีข้างหน้า", + "DialogUpdaterConvertFailedGithubMessage": "ไม่สามารถแปลงเวอร์ชั่น รียูจินซ์ ที่ได้รับจาก Github Release", + "DialogUpdaterDownloadingMessage": "กำลังดาวน์โหลดอัปเดต...", + "DialogUpdaterExtractionMessage": "กำลังแตกไฟล์อัปเดต...", + "DialogUpdaterRenamingMessage": "กำลังลบไฟล์เก่า...", + "DialogUpdaterAddingFilesMessage": "กำลังเพิ่มไฟล์อัปเดตใหม่...", + "DialogUpdaterCompleteMessage": "อัปเดตเสร็จสมบูรณ์แล้ว!", + "DialogUpdaterRestartMessage": "คุณต้องการรีสตาร์ท รียูจินซ์ ตอนนี้หรือไม่?", + "DialogUpdaterNoInternetMessage": "คุณไม่ได้เชื่อมต่อกับอินเทอร์เน็ต!", + "DialogUpdaterNoInternetSubMessage": "โปรดตรวจสอบว่าคุณมีการเชื่อมต่ออินเทอร์เน็ตว่ามีการใช้งานได้หรือไม่!", + "DialogUpdaterDirtyBuildMessage": "คุณไม่สามารถอัปเดต Dirty build ของ รียูจินซ์ ได้!", + "DialogUpdaterDirtyBuildSubMessage": "โปรดดาวน์โหลด รียูจินซ์ ได้ที่ https://ryujinx.org/ หากคุณกำลังมองหาเวอร์ชั่นที่รองรับ", + "DialogRestartRequiredMessage": "จำเป็นต้องรีสตาร์ทเพื่อให้การอัพเดตสามารถให้งานได้", + "DialogThemeRestartMessage": "บันทึกธีมแล้ว จำเป็นต้องรีสตาร์ทเพื่อใช้ธีม", + "DialogThemeRestartSubMessage": "คุณต้องการรีสตาร์ทหรือไม่?", + "DialogFirmwareInstallEmbeddedMessage": "คุณต้องการติดตั้งเฟิร์มแวร์ที่ฝังอยู่ในเกมนี้หรือไม่? (เฟิร์มแวร์ {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "ไม่พบเฟิร์มแวร์ที่ติดตั้งไว้ แต่ รียูจินซ์ สามารถติดตั้งเฟิร์มแวร์ได้ {0} จากเกมที่ให้มา\nตอนนี้โปรแกรมจำลองจะเริ่มทำงาน", + "DialogFirmwareNoFirmwareInstalledMessage": "ไม่มีการติดตั้งเฟิร์มแวร์", + "DialogFirmwareInstalledMessage": "เฟิร์มแวร์ติดตั้งแล้ว {0}", + "DialogInstallFileTypesSuccessMessage": "ติดตั้งตามประเภทของไฟล์สำเร็จแล้ว!", + "DialogInstallFileTypesErrorMessage": "ติดตั้งตามประเภทของไฟล์ไม่สำเร็จ", + "DialogUninstallFileTypesSuccessMessage": "ถอนการติดตั้งตามประเภทของไฟล์สำเร็จแล้ว!", + "DialogUninstallFileTypesErrorMessage": "ไม่สามารถถอนการติดตั้งตามประเภทของไฟล์ได้", + "DialogOpenSettingsWindowLabel": "เปิดหน้าต่างการตั้งค่า", + "DialogControllerAppletTitle": "แอพเพล็ตคอนโทรลเลอร์", + "DialogMessageDialogErrorExceptionMessage": "เกิดข้อผิดพลาดในการแสดงกล่องโต้ตอบข้อความ: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "เกิดข้อผิดพลาดในการแสดงซอฟต์แวร์แป้นพิมพ์: {0}", + "DialogErrorAppletErrorExceptionMessage": "เกิดข้อผิดพลาดในการแสดงกล่องโต้ตอบ ข้อผิดพลาด แอปเพล็ต: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nสำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีแก้ไขข้อผิดพลาดนี้ โปรดทำตามคำแนะนำในการตั้งค่าของเรา", + "DialogUserErrorDialogTitle": "ข้อผิดพลาด รียูจินซ์ ({0})", + "DialogAmiiboApiTitle": "อะมิโบ API", + "DialogAmiiboApiFailFetchMessage": "เกิดข้อผิดพลาดขณะเรียกข้อมูลจาก API", + "DialogAmiiboApiConnectErrorMessage": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ อะมิโบ API บ้างบริการอาจหยุดทำงาน หรือไม่คุณต้องทำการตรวจสอบว่าการเชื่อมต่ออินเทอร์เน็ตของคุณอยู่ในสถานะเชื่อมต่ออยู่หรือไม่", + "DialogProfileInvalidProfileErrorMessage": "โปรไฟล์ {0} เข้ากันไม่ได้กับระบบการกำหนดค่าอินพุตปัจจุบัน", + "DialogProfileDefaultProfileOverwriteErrorMessage": "ไม่สามารถเขียนทับโปรไฟล์เริ่มต้นได้", + "DialogProfileDeleteProfileTitle": "กำลังลบโปรไฟล์", + "DialogProfileDeleteProfileMessage": "การดำเนินการนี้ไม่สามารถย้อนกลับได้ คุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อหรือไม่?", + "DialogWarning": "คำเตือน", + "DialogPPTCDeletionMessage": "คุณกำลังจะจัดคิวการสร้าง PPTC ใหม่ในการบูตครั้งถัดไป:\n\n{0}\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อหรือไม่?", + "DialogPPTCDeletionErrorMessage": "มีข้อผิดพลาดในการล้างแคช PPTC {0}: {1}", + "DialogShaderDeletionMessage": "คุณกำลังจะลบ เชเดอร์แคช:\n\n{0}\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อหรือไม่?", + "DialogShaderDeletionErrorMessage": "เกิดข้อผิดพลาดในการล้าง เชเดอร์แคช {0}: {1}", + "DialogRyujinxErrorMessage": "รียูจินซ์ พบข้อผิดพลาด", + "DialogInvalidTitleIdErrorMessage": "ข้อผิดพลาดของ UI: เกมที่เลือกไม่มีชื่อ ID ที่ถูกต้อง", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "ไม่พบเฟิร์มแวร์ของระบบที่ถูกต้อง {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "ติดตั้งเฟิร์มแวร์ {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "นี่คื่อเวอร์ชั่นของระบบ {0} ที่ได้รับการติดตั้งเมื่อเร็วๆ นี้", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nสิ่งนี้จะแทนที่เวอร์ชั่นของระบบปัจจุบัน {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nคุณต้องการดำเนินการต่อหรือไม่?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "กำลังติดตั้งเฟิร์มแวร์...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "การติดตั้งเวอร์ชั่นระบบ {0} เรียบร้อยแล้ว", + "DialogUserProfileDeletionWarningMessage": "จะไม่มีโปรไฟล์อื่นให้เปิดหากโปรไฟล์ที่เลือกถูกลบ", + "DialogUserProfileDeletionConfirmMessage": "คุณต้องการลบโปรไฟล์ที่เลือกหรือไม่?", + "DialogUserProfileUnsavedChangesTitle": "คำเตือน - มีการเปลี่ยนแปลงที่ไม่ได้บันทึก", + "DialogUserProfileUnsavedChangesMessage": "คุณได้ทำการเปลี่ยนแปลงโปรไฟล์ผู้ใช้นี้โดยไม่ได้รับการบันทึก", + "DialogUserProfileUnsavedChangesSubMessage": "คุณต้องการยกเลิกการเปลี่ยนแปลงของคุณหรือไม่?", + "DialogControllerSettingsModifiedConfirmMessage": "การตั้งค่าคอนโทรลเลอร์ปัจจุบันได้รับการอัปเดตแล้ว", + "DialogControllerSettingsModifiedConfirmSubMessage": "คุณต้องการบันทึกหรือไม่?", + "DialogLoadFileErrorMessage": "{0} ไฟล์เกิดข้อผิดพลาด: {1}", + "DialogModAlreadyExistsMessage": "มีม็อดอยู่แล้ว", + "DialogModInvalidMessage": "ไดเร็กทอรีที่ระบุไม่มี ม็อดอยู่!", + "DialogModDeleteNoParentMessage": "ไม่สามารถลบ: ไม่พบไดเร็กทอรีหลักสำหรับ ม็อด \"{0}\"!", + "DialogDlcNoDlcErrorMessage": "ไฟล์ที่ระบุไม่มี DLC สำหรับชื่อที่เลือก!", + "DialogPerformanceCheckLoggingEnabledMessage": "คุณได้เปิดใช้งานการบันทึกการติดตาม ซึ่งออกแบบมาเพื่อให้นักพัฒนาใช้เท่านั้น", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "เพื่อประสิทธิภาพสูงสุด ขอแนะนำให้ปิดใช้งานการบันทึกการติดตาม คุณต้องการปิดใช้การบันทึกการติดตามตอนนี้หรือไม่?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "คุณได้เปิดใช้งาน การดัมพ์เชเดอร์ ซึ่งออกแบบมาเพื่อให้นักพัฒนาใช้งานเท่านั้น", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "เพื่อประสิทธิภาพสูงสุด ขอแนะนำให้ปิดใช้การดัมพ์เชเดอร์ คุณต้องการปิดการใช้งานการ ดัมพ์เชเดอร์ ตอนนี้หรือไม่?", + "DialogLoadAppGameAlreadyLoadedMessage": "ทำการโหลดเกมเรียบร้อยแล้ว", + "DialogLoadAppGameAlreadyLoadedSubMessage": "โปรดหยุดการจำลอง หรือปิดโปรแกรมจำลองก่อนที่จะเปิดเกมอื่น", + "DialogUpdateAddUpdateErrorMessage": "ไฟล์ที่ระบุไม่มีการอัพเดตสำหรับชื่อเรื่องที่เลือก!", + "DialogSettingsBackendThreadingWarningTitle": "คำเตือน - การทำเธรดแบ็กเอนด์", + "DialogSettingsBackendThreadingWarningMessage": "รียูจินซ์ ต้องรีสตาร์ทหลังจากเปลี่ยนตัวเลือกนี้จึงจะใช้งานได้อย่างสมบูรณ์ คุณอาจต้องปิดการใช้งาน มัลติเธรด ของไดรเวอร์ของคุณด้วยตนเองเมื่อใช้ รียูจินซ์ ทั้งนี้ขึ้นอยู่กับแพลตฟอร์มของคุณ", + "DialogModManagerDeletionWarningMessage": "คุณกำลังจะลบ ม็อด: {0}\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อ?", + "DialogModManagerDeletionAllWarningMessage": "คุณกำลังจะลบม็อดทั้งหมดสำหรับชื่อนี้\n\nคุณแน่ใจหรือไม่ว่าต้องการดำเนินการต่อ?", + "SettingsTabGraphicsFeaturesOptions": "คุณสมบัติ", + "SettingsTabGraphicsBackendMultithreading": "มัลติเธรด กราฟิกเบื้องหลัง:", + "CommonAuto": "อัตโนมัติ", + "CommonOff": "ปิดการใช้งาน", + "CommonOn": "เปิดใช้งาน", + "InputDialogYes": "ใช่", + "InputDialogNo": "ไม่ใช่", + "DialogProfileInvalidProfileNameErrorMessage": "ชื่อไฟล์ประกอบด้วยอักขระที่ไม่ถูกต้อง กรุณาลองอีกครั้ง", + "MenuBarOptionsPauseEmulation": "หยุดชั่วคราว", + "MenuBarOptionsResumeEmulation": "ดำเนินการต่อ", + "AboutUrlTooltipMessage": "คลิกเพื่อเปิดเว็บไซต์ รียูจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutDisclaimerMessage": "ทางผู้พัฒนาโปรแกรม รียูจินซ์ ไม่มีส่วนเกี่ยวข้องกับทางบริษัท Nintendo™\nหรือพันธมิตรใดๆ ทั้งสิ้น!", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) ถูกใช้\nในการจำลอง อะมิโบ ของเรา", + "AboutPatreonUrlTooltipMessage": "คลิกเพื่อเปิดหน้า เพทรีออน ของ รียูจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutGithubUrlTooltipMessage": "คลิกเพื่อเปิดหน้า กิตฮับ ของ ริวจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutDiscordUrlTooltipMessage": "คลิกเพื่อเปิดคำเชิญเข้าสู่เซิร์ฟเวอร์ ดิสคอร์ด ของ รียูจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutTwitterUrlTooltipMessage": "คลิกเพื่อเปิดหน้าเพจ ทวิตเตอร์ ของ รียูจินซ์ บนเบราว์เซอร์เริ่มต้นของคุณ", + "AboutRyujinxAboutTitle": "เกี่ยวกับ:", + "AboutRyujinxAboutContent": "รียูจินซ์ เป็นอีมูเลเตอร์สำหรับ Nintendo Switch™\nโปรดสนับสนุนเราบน เพทรีออน\nรับข่าวสารล่าสุดทั้งหมดบน ทวิตเตอร์ หรือ ดิสคอร์ด ของเรา\nนักพัฒนาที่สนใจจะมีส่วนร่วมสามารถดูข้อมูลเพิ่มเติมได้ที่ กิตฮับ หรือ ดิสคอร์ด ของเรา", + "AboutRyujinxMaintainersTitle": "ได้รับการดูแลรักษาโดย:", + "AboutRyujinxMaintainersContentTooltipMessage": "คลิกเพื่อเปิดหน้าผู้ร่วมให้ข้อมูลในเบราว์เซอร์เริ่มต้นของคุณ", + "AboutRyujinxSupprtersTitle": "ลายนามผู้สนับสนุนบน เพทรีออน:", + "AmiiboSeriesLabel": "อะมิโบซีรีส์", + "AmiiboCharacterLabel": "ตัวละคร", + "AmiiboScanButtonLabel": "สแกนเลย", + "AmiiboOptionsShowAllLabel": "แสดง อะมิโบ ทั้งหมด", + "AmiiboOptionsUsRandomTagLabel": "แฮ็ค: ใช้แท็กสุ่ม Uuid", + "DlcManagerTableHeadingEnabledLabel": "เปิดใช้งานแล้ว", + "DlcManagerTableHeadingTitleIdLabel": "ชื่อไอดี", + "DlcManagerTableHeadingContainerPathLabel": "ที่เก็บไฟล์ คอนเทนเนอร์", + "DlcManagerTableHeadingFullPathLabel": "ที่เก็บไฟล์แบบเต็ม", + "DlcManagerRemoveAllButton": "ลบทั้งหมด", + "DlcManagerEnableAllButton": "เปิดใช้งานทั้งหมด", + "DlcManagerDisableAllButton": "ปิดใช้งานทั้งหมด", + "ModManagerDeleteAllButton": "ลบทั้งหมด", + "MenuBarOptionsChangeLanguage": "เปลี่ยนภาษา", + "MenuBarShowFileTypes": "แสดงประเภทของไฟล์", + "CommonSort": "เรียงลำดับ", + "CommonShowNames": "แสดงชื่อ", + "CommonFavorite": "สิ่งที่ชื่นชอบ", + "OrderAscending": "จากน้อยไปมาก", + "OrderDescending": "จากมากไปน้อย", + "SettingsTabGraphicsFeatures": "คุณสมบัติ และ การเพิ่มประสิทธิภาพ", + "ErrorWindowTitle": "หน้าต่างแสดงข้อผิดพลาด", + "ToggleDiscordTooltip": "เลือกว่าจะแสดง รียูจินซ์ ในกิจกรรม ดิสคอร์ด \"ที่กำลังเล่นอยู่\" ของคุณหรือไม่?", + "AddGameDirBoxTooltip": "ป้อนไดเรกทอรี่เกมที่จะทำการเพิ่มลงในรายการ", + "AddGameDirTooltip": "เพิ่มไดเรกทอรี่เกมลงในรายการ", + "RemoveGameDirTooltip": "ลบไดเรกทอรี่เกมที่เลือก", + "CustomThemeCheckTooltip": "ใช้ธีม Avalonia แบบกำหนดเองสำหรับ GUI เพื่อเปลี่ยนรูปลักษณ์ของเมนูโปรแกรมจำลอง", + "CustomThemePathTooltip": "ไปยังที่เก็บไฟล์ธีม GUI แบบกำหนดเอง", + "CustomThemeBrowseTooltip": "เรียกดูธีม GUI ที่กำหนดเอง", + "DockModeToggleTooltip": "ด็อกโหมด ทำให้ระบบจำลองการทำงานเสมือน Nintendo ที่กำลังเชื่อมต่ออยู่ด็อก สิ่งนี้จะปรับปรุงความเสถียรภาพของกราฟิกในเกมส่วนใหญ่ ในทางกลับกัน การปิดใช้จะทำให้ระบบจำลองทำงานเหมือนกับ Nintendo Switch แบบพกพา ส่งผลให้คุณภาพกราฟิกลดลง\n\nกำหนดค่าส่วนควบคุมของผู้เล่น 1 หากวางแผนที่จะใช้ด็อกโหมด กำหนดค่าการควบคุมแบบ แฮนด์เฮลด์ หากวางแผนที่จะใช้โหมดแฮนด์เฮลด์\n\nเปิดทิ้งไว้หากคุณไม่แน่ใจ", + "DirectKeyboardTooltip": "รองรับการเข้าถึงแป้นพิมพ์โดยตรง (HID) ให้เกมเข้าถึงคีย์บอร์ดของคุณเป็นอุปกรณ์ป้อนข้อความ\n\nใช้งานได้กับเกมที่รองรับการใช้งานคีย์บอร์ดบนฮาร์ดแวร์ของ Switch เท่านั้น\n\nหากคุณไม่แน่ใจปล่อยให้ปิดอย่างนั้น", + "DirectMouseTooltip": "รองรับการเข้าถึงเมาส์โดยตรง (HID) ให้เกมเข้าถึงเมาส์ของคุณเป็นอุปกรณ์ชี้ตำแหน่ง\n\nใช้งานได้เฉพาะกับเกมที่รองรับการควบคุมเมาส์บนฮาร์ดแวร์ของ Switch เท่านั้น ซึ่งมีอยู่ไม่มากนัก\n\nเมื่อเปิดใช้งาน ฟังก์ชั่นหน้าจอสัมผัสอาจไม่ทำงาน\n\nหากคุณไม่แน่ใจปล่อยให้ปิดอย่างนั้น", + "RegionTooltip": "เปลี่ยนภูมิภาคของระบบ", + "LanguageTooltip": "เปลี่ยนภาษาของระบบ", + "TimezoneTooltip": "เปลี่ยนโซนเวลาของระบบ", + "TimeTooltip": "เปลี่ยนเวลาของระบบ", + "VSyncToggleTooltip": "Vertical Sync ของคอนโซลจำลอง โดยพื้นฐานแล้วเป็นตัวจำกัดเฟรมสำหรับเกมส่วนใหญ่ การปิดใช้งานอาจทำให้เกมทำงานด้วยความเร็วสูงขึ้น หรือทำให้หน้าจอการโหลดใช้เวลานานขึ้นหรือค้าง\n\nสามารถสลับได้ในเกมด้วยปุ่มลัดตามที่คุณต้องการ (F1 เป็นค่าเริ่มต้น) เราขอแนะนำให้ทำเช่นนี้หากคุณวางแผนที่จะปิดการใช้งาน\n\nหากคุณไม่แน่ใจให้ปล่อยไว้อย่างนั้น", + "PptcToggleTooltip": "บันทึกฟังก์ชั่น JIT ที่แปลแล้ว ดังนั้นจึงไม่จำเป็นต้องแปลทุกครั้งที่โหลดเกม\n\nลดอาการกระตุกและเร่งความเร็วการบูตได้อย่างมากหลังจากการบูตครั้งแรกของเกม\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "FsIntegrityToggleTooltip": "ตรวจสอบไฟล์ที่เสียหายเมื่อบูตเกม และหากตรวจพบไฟล์ที่เสียหาย จะแสดงข้อผิดพลาดของแฮชในบันทึก\n\nไม่มีผลกระทบต่อประสิทธิภาพการทำงานและมีไว้เพื่อช่วยในการแก้ไขปัญหา\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "AudioBackendTooltip": "เปลี่ยนแบ็กเอนด์ที่ใช้ในการเรนเดอร์เสียง\n\nSDL2 เป็นที่ต้องการ ในขณะที่ OpenAL และ SoundIO ถูกใช้เป็นทางเลือกสำรอง ดัมมี่จะไม่มีเสียง\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "MemoryManagerTooltip": "เปลี่ยนวิธีการแมปและเข้าถึงหน่วยความจำของผู้เยี่ยมชม ส่งผลอย่างมากต่อประสิทธิภาพการทำงานของ CPU ที่จำลอง\n\nตั้งค่าเป็น ไม่ทำการตรวจสอบ โฮสต์ หากคุณไม่แน่ใจ", + "MemoryManagerSoftwareTooltip": "ใช้ตารางหน้าซอฟต์แวร์สำหรับการแปลที่อยู่ ความแม่นยำสูงสุดแต่ประสิทธิภาพช้าที่สุด", + "MemoryManagerHostTooltip": "แมปหน่วยความจำในพื้นที่ที่อยู่โฮสต์โดยตรง การคอมไพล์และดำเนินการ JIT เร็วขึ้นมาก", + "MemoryManagerUnsafeTooltip": "แมปหน่วยความจำโดยตรง แต่อย่าปิดบังที่อยู่ภายในพื้นที่ที่อยู่ของผู้เยี่ยมชมก่อนที่จะเข้าถึง เร็วกว่า แต่ต้องแลกกับความปลอดภัย แอปพลิเคชั่นผู้เยี่ยมชมสามารถเข้าถึงหน่วยความจำได้จากทุกที่ใน รียูจินซ์ ดังนั้นให้รันเฉพาะโปรแกรมที่คุณเชื่อถือในโหมดนี้", + "UseHypervisorTooltip": "ใช้ Hypervisor แทน JIT ปรับปรุงประสิทธิภาพอย่างมากเมื่อพร้อมใช้งาน แต่อาจไม่เสถียรในสถานะปัจจุบัน", + "DRamTooltip": "ใช้เค้าโครง MemoryMode ทางเลือกเพื่อเลียนแบบโมเดลการพัฒนาสวิตช์\n\nสิ่งนี้มีประโยชน์สำหรับแพ็กพื้นผิวที่มีความละเอียดสูงกว่าหรือม็อดที่มีความละเอียด 4k เท่านั้น ไม่ปรับปรุงประสิทธิภาพ\n\nปล่อยให้ปิดหากคุณไม่แน่ใจ", + "IgnoreMissingServicesTooltip": "ละเว้นบริการ Horizon OS ที่ยังไม่ได้ใช้งาน วิธีนี้อาจช่วยในการหลีกเลี่ยงข้อผิดพลาดเมื่อบู๊ตเกมบางเกม\n\nปล่อยให้ปิดหากคุณไม่แน่ใจ", + "GraphicsBackendThreadingTooltip": "ดำเนินการคำสั่งแบ็กเอนด์กราฟิกบนเธรดที่สอง\n\nเร่งความเร็วการคอมไพล์เชเดอร์ ลดการกระตุก และปรับปรุงประสิทธิภาพการทำงานของไดรเวอร์ GPU โดยไม่ต้องรองรับมัลติเธรดในตัว ประสิทธิภาพที่ดีขึ้นเล็กน้อยสำหรับไดรเวอร์ที่มีมัลติเธรด\n\nตั้งเป็น อัตโนมัติ หากคุณไม่แน่ใจ", + "GalThreadingTooltip": "ดำเนินการคำสั่งแบ็กเอนด์กราฟิกบนเธรดที่สอง\n\nเร่งความเร็วการคอมไพล์เชเดอร์ ลดการกระตุก และปรับปรุงประสิทธิภาพการทำงานของไดรเวอร์ GPU โดยไม่ต้องรองรับมัลติเธรดในตัว ประสิทธิภาพที่ดีขึ้นเล็กน้อยสำหรับไดรเวอร์ที่มีมัลติเธรด\n\nตั้งเป็น อัตโนมัติ หากคุณไม่แน่ใจ", + "ShaderCacheToggleTooltip": "บันทึกแคชเชเดอร์ของดิสก์ซึ่งช่วยลดการกระตุกในการรันครั้งต่อๆ ไป\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "ResolutionScaleTooltip": "คูณความละเอียดการเรนเดอร์ของเกม\n\nเกมบางเกมอาจไม่สามารถใช้งานได้และดูเป็นพิกเซลแม้ว่าความละเอียดจะเพิ่มขึ้นก็ตาม สำหรับเกมเหล่านั้น คุณอาจต้องค้นหาม็อดที่ลบรอยหยักของภาพหรือเพิ่มความละเอียดในการเรนเดอร์ภายใน หากต้องการใช้อย่างหลัง คุณอาจต้องเลือก Native\n\nตัวเลือกนี้สามารถเปลี่ยนแปลงได้ในขณะที่เกมกำลังทำงานอยู่โดยคลิก \"นำมาใช้\" ด้านล่าง คุณสามารถย้ายหน้าต่างการตั้งค่าไปด้านข้างและทดลองจนกว่าคุณจะพบรูปลักษณ์ที่คุณต้องการสำหรับเกม\n\nโปรดทราบว่า 4x นั้นเกินความจำเป็นสำหรับการตั้งค่าแทบทุกประเภท", + "ResolutionScaleEntryTooltip": "สเกลความละเอียดจุดทศนิยม เช่น 1.5 ไม่ใช่จำนวนเต็มของสเกล มีแนวโน้มที่จะก่อให้เกิดปัญหาหรือความผิดพลาดได้", + "AnisotropyTooltip": "ระดับของการกรองแบบ Anisotropic ตั้งค่าเป็นอัตโนมัติเพื่อใช้ค่าที่เกมร้องขอ", + "AspectRatioTooltip": "อัตราส่วนภาพที่ใช้กับหน้าต่างตัวแสดงภาพ\n\nเปลี่ยนสิ่งนี้หากคุณใช้ตัวดัดแปลงอัตราส่วนกว้างยาวสำหรับเกมของคุณ ไม่เช่นนั้นกราฟิกจะถูกยืดออก\n\nทิ้งไว้ที่ 16:9 หากไม่แน่ใจ", + "ShaderDumpPathTooltip": "ที่เก็บ ดัมพ์ไฟล์ พื้นผิวและแสงเงา", + "FileLogTooltip": "บันทึก ประวัติคอนโซลลงในไฟล์บันทึกบนดิสก์ จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "StubLogTooltip": "พิมพ์ข้อความประวัติในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "InfoLogTooltip": "พิมพ์ข้อความบันทึกข้อมูลในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "WarnLogTooltip": "พิมพ์ข้อความประวัติแจ้งตือนในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "ErrorLogTooltip": "พิมพ์ข้อความบันทึกข้อผิดพลาดในคอนโซล จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "TraceLogTooltip": "พิมพ์ข้อความประวัติการติดตามในคอนโซล ไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "GuestLogTooltip": "พิมพ์ข้อความประวัติของผู้เยี่ยมชมในคอนโซล ไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน", + "FileAccessLogTooltip": "พิมพ์ข้อความบันทึกการเข้าถึงไฟล์ในคอนโซล", + "FSAccessLogModeTooltip": "เปิดใช้งาน เอาต์พุตประวัติการเข้าถึง FS ไปยังคอนโซล โหมดที่เป็นไปได้คือ 0-3", + "DeveloperOptionTooltip": "โปรดใช้ด้วยความระมัดระวัง", + "OpenGlLogLevel": "จำเป็นต้องเปิดใช้งานระดับบันทึกที่เหมาะสม", + "DebugLogTooltip": "พิมพ์ข้อความประวัติการแก้ไขข้อบกพร่องในคอนโซล\n\nใช้สิ่งนี้เฉพาะเมื่อได้รับคำแนะนำจากเจ้าหน้าที่โดยเฉพาะเท่านั้น เนื่องจากจะทำให้บันทึกอ่านยากและทำให้ประสิทธิภาพของโปรแกรมจำลองแย่ลง", + "LoadApplicationFileTooltip": "เปิด File Explorer เพื่อเลือกไฟล์ที่เข้ากันได้กับ Switch ที่จะโหลด", + "LoadApplicationFolderTooltip": "เปิดตัวสำรวจไฟล์เพื่อเลือกไฟล์ที่เข้ากันได้กับ Switch ที่จะโหลด", + "OpenRyujinxFolderTooltip": "เปิดโฟลเดอร์ระบบไฟล์ Ryujinx", + "OpenRyujinxLogsTooltip": "เปิดโฟลเดอร์ ที่เก็บไฟล์ประวัติ", + "ExitTooltip": "ออกจากโปรแกรม รียูจินซ์", + "OpenSettingsTooltip": "เปิดหน้าต่างการตั้งค่า", + "OpenProfileManagerTooltip": "เปิดหน้าต่างตัวจัดการโปรไฟล์ผู้ใช้", + "StopEmulationTooltip": "หยุดการจำลองของเกมที่เปิดอยู่ในปัจจุบันและกลับไปยังการเลือกเกม", + "CheckUpdatesTooltip": "ตรวจสอบการอัปเดตของ รียูจินซ์", + "OpenAboutTooltip": "เปิดหน้าต่าง เกี่ยวกับ", + "GridSize": "ขนาดตาราง", + "GridSizeTooltip": "เปลี่ยนขนาด ของตาราง", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "บราซิล โปรตุเกส", + "AboutRyujinxContributorsButtonHeader": "ดูผู้มีส่วนร่วมทั้งหมด", + "SettingsTabSystemAudioVolume": "ระดับเสียง: ", + "AudioVolumeTooltip": "ปรับระดับเสียง", + "SettingsTabSystemEnableInternetAccess": "การเข้าถึงอินเทอร์เน็ตของผู้เยี่ยมชม/โหมด LAN", + "EnableInternetAccessTooltip": "อนุญาตให้แอปพลิเคชันจำลองเชื่อมต่ออินเทอร์เน็ต\n\nเกมที่มีโหมด LAN สามารถเชื่อมต่อระหว่างกันได้เมื่อเปิดใช้งานและระบบเชื่อมต่อกับจุดเชื่อมต่อเดียวกัน รวมถึงคอนโซลจริงด้วย\n\nไม่อนุญาตให้มีการเชื่อมต่อกับเซิร์ฟเวอร์ Nintendo อาจทำให้เกิดการหยุดทำงานในบางเกมที่พยายามเชื่อมต่ออินเทอร์เน็ต\n\nปล่อยให้ปิดหากคุณไม่แน่ใจ", + "GameListContextMenuManageCheatToolTip": "ฟังชั่นจัดการสูตรโกง", + "GameListContextMenuManageCheat": "จัดการสูตรโกง", + "GameListContextMenuManageModToolTip": "จัดการ ม็อด", + "GameListContextMenuManageMod": "จัดการ ม็อด", + "ControllerSettingsStickRange": "ขอบเขต:", + "DialogStopEmulationTitle": "รียูจินซ์ - หยุดการจำลอง", + "DialogStopEmulationMessage": "คุณแน่ใจหรือไม่ว่าต้องการหยุดการจำลองหรือไม่?", + "SettingsTabCpu": "หน่วยประมวลผลกลาง", + "SettingsTabAudio": "เสียง", + "SettingsTabNetwork": "เครือข่าย", + "SettingsTabNetworkConnection": "การเชื่อมต่อเครือข่าย", + "SettingsTabCpuCache": "ซีพียู แคช", + "SettingsTabCpuMemory": "โหมดซีพียู", + "DialogUpdaterFlatpakNotSupportedMessage": "โปรดอัปเดต รียูจินซ์ ผ่านช่องทาง FlatHub", + "UpdaterDisabledWarningTitle": "ปิดใช้งานการอัปเดตแล้ว!", + "ControllerSettingsRotate90": "หมุน 90 องศา ตามเข็มนาฬิกา", + "IconSize": "ขนาดไอคอน", + "IconSizeTooltip": "เปลี่ยนขนาดของไอคอนเกม", + "MenuBarOptionsShowConsole": "แสดง คอนโซล", + "ShaderCachePurgeError": "เกิดข้อผิดพลาดในการล้างแคชเชเดอร์ {0}: {1}", + "UserErrorNoKeys": "ไม่พบ คีย์", + "UserErrorNoFirmware": "ไม่พบ เฟิร์มแวร์", + "UserErrorFirmwareParsingFailed": "เกิดข้อผิดพลาดในการวิเคราะห์เฟิร์มแวร์", + "UserErrorApplicationNotFound": "ไม่พบ แอปพลิเคชัน", + "UserErrorUnknown": "ข้อผิดพลาดที่ไม่รู้จัก", + "UserErrorUndefined": "ข้อผิดพลาดที่ไม่ได้ระบุ", + "UserErrorNoKeysDescription": "รียูจินซ์ ไม่พบไฟล์ 'prod.keys' ในเครื่องของคุณ", + "UserErrorNoFirmwareDescription": "รียูจินซ์ ไม่พบ เฟิร์มแวร์ที่ติดตั้งไว้ในเครื่องของคุณ", + "UserErrorFirmwareParsingFailedDescription": "รียูจินซ์ ไม่สามารถวิเคราะห์เฟิร์มแวร์ที่ให้มาได้ ซึ่งมักมีสาเหตุมาจากคีย์ที่ล้าสมัย", + "UserErrorApplicationNotFoundDescription": "รียูจินซ์ ไม่พบแอปพลิเคชันที่ถูกต้องในที่เก็บไฟล์ที่กำหนด", + "UserErrorUnknownDescription": "เกิดข้อผิดพลาดที่ไม่รู้จัก!", + "UserErrorUndefinedDescription": "เกิดข้อผิดพลาดที่ไม่สามารถระบุได้! สิ่งนี้ไม่ควรเกิดขึ้น โปรดติดต่อผู้พัฒนา!", + "OpenSetupGuideMessage": "เปิดคู่มือการตั้งค่า", + "NoUpdate": "ไม่มีการอัปเดต", + "TitleUpdateVersionLabel": "เวอร์ชั่น {0}", + "RyujinxInfo": "รียูจินซ์ – ข้อมูล", + "RyujinxConfirm": "รียูจินซ์ - ยืนยัน", + "FileDialogAllTypes": "ทุกประเภท", + "Never": "ไม่มี", + "SwkbdMinCharacters": "ต้องมีความยาวของตัวอักษรอย่างน้อย {0} ตัว", + "SwkbdMinRangeCharacters": "ต้องมีความยาวของตัวอักษร {0}-{1} ตัว", + "SoftwareKeyboard": "ซอฟต์แวร์ ของคีย์บอร์ด", + "SoftwareKeyboardModeNumeric": "ต้องเป็น 0-9 หรือ '.' เท่านั้น", + "SoftwareKeyboardModeAlphabet": "ต้องเป็นตัวอักษรที่ไม่ใช่ CJK เท่านั้น", + "SoftwareKeyboardModeASCII": "ต้องเป็นตัวอักษร ASCII เท่านั้น", + "ControllerAppletControllers": "คอนโทรลเลอร์ที่รองรับ:", + "ControllerAppletPlayers": "ผู้เล่น:", + "ControllerAppletDescription": "การกำหนดค่าปัจจุบันของคุณไม่ถูกต้อง เปิดการตั้งค่าและกำหนดค่าอินพุตของคุณใหม่", + "ControllerAppletDocked": "ตั้งค่าด็อกโหมด ควรปิดใช้งานการควบคุมแบบแฮนด์เฮลด์", + "UpdaterRenaming": "กำลังเปลี่ยนชื่อไฟล์เก่า...", + "UpdaterRenameFailed": "โปรแกรมอัปเดตไม่สามารถเปลี่ยนชื่อไฟล์ได้: {0}", + "UpdaterAddingFiles": "กำลังเพิ่มไฟล์ใหม่...", + "UpdaterExtracting": "กำลังแยกการอัปเดต...", + "UpdaterDownloading": "กำลังดาวน์โหลดอัปเดต...", + "Game": "เกมส์", + "Docked": "ด็อก", + "Handheld": "แฮนด์เฮลด์", + "ConnectionError": "การเชื่อมต่อล้มเหลว", + "AboutPageDeveloperListMore": "{0} และอื่นๆ ...", + "ApiError": "ข้อผิดพลาดของ API", + "LoadingHeading": "กำลังโหลด {0}", + "CompilingPPTC": "กำลังคอมไพล์ PTC", + "CompilingShaders": "กำลังคอมไพล์ พื้นผิวและแสงเงา", + "AllKeyboards": "คีย์บอร์ดทั้งหมด", + "OpenFileDialogTitle": "เลือกไฟล์ที่สนับสนุนเพื่อเปิด", + "OpenFolderDialogTitle": "เลือกโฟลเดอร์ที่มีเกมที่แตกไฟล์แล้ว", + "AllSupportedFormats": "รูปแบบที่รองรับทั้งหมด", + "RyujinxUpdater": "อัปเดต รียูจินซ์", + "SettingsTabHotkeys": "ปุ่มลัดของคีย์บอร์ด", + "SettingsTabHotkeysHotkeys": "ปุ่มลัดของคีย์บอร์ด", + "SettingsTabHotkeysToggleVsyncHotkey": "สลับเป็น VSync:", + "SettingsTabHotkeysScreenshotHotkey": "ภาพหน้าจอ:", + "SettingsTabHotkeysShowUiHotkey": "แสดง UI:", + "SettingsTabHotkeysPauseHotkey": "หยุดชั่วคราว:", + "SettingsTabHotkeysToggleMuteHotkey": "ปิดเสียง:", + "ControllerMotionTitle": "ตั้งค่าควบคุมการเคลื่อนไหว", + "ControllerRumbleTitle": "ตั้งค่าการสั่นไหว", + "SettingsSelectThemeFileDialogTitle": "เลือกไฟล์ธีม", + "SettingsXamlThemeFile": "ไฟล์ธีมรูปแบบ XAML", + "AvatarWindowTitle": "จัดการบัญชี - อวาต้า", + "Amiibo": "อะมิโบ", + "Unknown": "ไม่รู้จัก", + "Usage": "การใช้งาน", + "Writable": "สามารถเขียนได้", + "SelectDlcDialogTitle": "เลือกไฟล์ DLC", + "SelectUpdateDialogTitle": "เลือกไฟล์อัพเดต", + "SelectModDialogTitle": "เลือกไดเรกทอรี Mods", + "UserProfileWindowTitle": "จัดการโปรไฟล์ผู้ใช้", + "CheatWindowTitle": "จัดการสูตรโกง", + "DlcWindowTitle": "จัดการเนื้อหาที่ดาวน์โหลดได้สำหรับ {0} ({1})", + "ModWindowTitle": "Manage Mods for {0} ({1})", + "UpdateWindowTitle": "จัดการอัปเดตหัวข้อ", + "CheatWindowHeading": "สูตรโกงมีให้สำหรับ {0} [{1}]", + "BuildId": "รหัสบิวด์:", + "DlcWindowHeading": "{0} เนื้อหาที่สามารถดาวน์โหลดได้", + "ModWindowHeading": "{0} ม็อด", + "UserProfilesEditProfile": "แก้ไขที่เลือกแล้ว", + "Cancel": "ยกเลิก", + "Save": "บันทึก", + "Discard": "ละทิ้ง", + "Paused": "หยุดชั่วคราว", + "UserProfilesSetProfileImage": "ตั้งค่ารูปโปรไฟล์", + "UserProfileEmptyNameError": "จำเป็นต้องระบุชื่อ", + "UserProfileNoImageError": "จำเป็นต้องตั้งค่ารูปโปรไฟล์", + "GameUpdateWindowHeading": "จัดการอัพเดตสำหรับ {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "เพิ่มความละเอียด:", + "SettingsTabHotkeysResScaleDownHotkey": "ลดความละเอียด:", + "UserProfilesName": "ชื่อ:", + "UserProfilesUserId": "รหัสผู้ใช้:", + "SettingsTabGraphicsBackend": "กราฟิกเบื้องหลัง", + "SettingsTabGraphicsBackendTooltip": "เลือกกราฟิกเบื้องหลังที่จะใช้ในโปรแกรมจำลอง\n\nโดยรวมแล้ว Vulkan นั้นดีกว่าสำหรับกราฟิกการ์ดรุ่นใหม่ทั้งหมด ตราบใดที่ไดรเวอร์ยังอัพเดทอยู่เสมอ Vulkan ยังมีคุณสมบัติการคอมไพล์เชเดอร์ที่เร็วขึ้น (ลดอาการกระตุก) ของผู้จำหน่าย GPU ทุกราย\n\nOpenGL อาจได้รับผลลัพธ์ที่ดีกว่าบน Nvidia GPU รุ่นเก่า, AMD GPU รุ่นเก่าบน Linux หรือบน GPU ที่มี VRAM ต่ำกว่า แม้ว่าการคอมไพล์เชเดอร์ จะทำให้อาการกระตุกมากขึ้นก็ตาม\n\nตั้งค่าเป็น Vulkan หากไม่แน่ใจ ตั้งค่าเป็น OpenGL หาก GPU ของคุณไม่รองรับ Vulkan แม้จะมีไดรเวอร์กราฟิกล่าสุดก็ตาม", + "SettingsEnableTextureRecompression": "เปิดใช้งาน การบีบอัดพื้นผิวอีกครั้ง", + "SettingsEnableTextureRecompressionTooltip": "บีบอัดพื้นผิว ASTC เพื่อลดการใช้งาน VRAM\n\nเกมที่ใช้รูปแบบพื้นผิวนี้ ได้แก่ Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder และ The Legend of Zelda: Tears of the Kingdom\n\nกราฟิกการ์ดที่มี 4 กิกะไบต์ VRAM หรือน้อยกว่ามีแนวโน้มที่จะให้แคชในบางจุดขณะเล่นเกมเหล่านี้\n\nเปิดใช้งานเฉพาะในกรณีที่ VRAM ของคุณใกล้หมดในเกมที่กล่าวมาข้างต้น ปล่อยให้ปิดหากไม่แน่ใจ", + "SettingsTabGraphicsPreferredGpu": "GPU ที่ต้องการ", + "SettingsTabGraphicsPreferredGpuTooltip": "เลือกกราฟิกการ์ดที่จะใช้กับแบ็กเอนด์กราฟิก Vulkan\n\nไม่ส่งผลต่อ GPU ที่ OpenGL จะใช้\n\nตั้งค่าเป็น GPU ที่ถูกตั้งค่าสถานะเป็น \"dGPU\" หากคุณไม่แน่ใจ หากไม่มีก็ปล่อยทิ้งไว้โดยไม่มีใครแตะต้องมัน", + "SettingsAppRequiredRestartMessage": "จำเป็นต้องรีสตาร์ท รียูจินซ์", + "SettingsGpuBackendRestartMessage": "การตั้งค่ากราฟิกเบื้องหลังหรือ GPU ได้รับการแก้ไขแล้ว สิ่งนี้จะต้องมีการรีสตาร์ทจึงจะสามารถใช้งานได้", + "SettingsGpuBackendRestartSubMessage": "คุณต้องการรีสตาร์ทตอนนี้หรือไม่?", + "RyujinxUpdaterMessage": "คุณต้องการอัพเดต รียูจินซ์ เป็นเวอร์ชั่นล่าสุดหรือไม่?", + "SettingsTabHotkeysVolumeUpHotkey": "เพิ่มระดับเสียง:", + "SettingsTabHotkeysVolumeDownHotkey": "ลดระดับเสียง:", + "SettingsEnableMacroHLE": "เปิดใช้งาน มาโคร HLE", + "SettingsEnableMacroHLETooltip": "การจำลองระดับสูงของโค้ดมาโคร GPU\n\nปรับปรุงประสิทธิภาพ แต่อาจทำให้เกิดข้อผิดพลาดด้านกราฟิกในบางเกม\n\nปล่อยไว้หากคุณไม่แน่ใจ", + "SettingsEnableColorSpacePassthrough": "ทะลุผ่านพื้นที่สี", + "SettingsEnableColorSpacePassthroughTooltip": "สั่งให้แบ็กเอนด์ Vulkan ส่งผ่านข้อมูลสีโดยไม่ต้องระบุค่าของสี สำหรับผู้ใช้ที่มีการแสดงกระจายตัวของสี อาจส่งผลให้สีสดใสมากขึ้น โดยต้องแลกกับความถูกต้องของสี", + "VolumeShort": "ระดับเสียง", + "UserProfilesManageSaves": "จัดการบันทึก", + "DeleteUserSave": "คุณต้องการลบบันทึกผู้ใช้สำหรับเกมนี้หรือไม่?", + "IrreversibleActionNote": "การดำเนินการนี้ไม่สามารถย้อนกลับได้", + "SaveManagerHeading": "จัดการบันทึกสำหรับ {0} ({1})", + "SaveManagerTitle": "จัดการบันทึก", + "Name": "ชื่อ", + "Size": "ขนาด", + "Search": "ค้นหา", + "UserProfilesRecoverLostAccounts": "กู้คืนบัญชีที่สูญหาย", + "Recover": "กู้คืน", + "UserProfilesRecoverHeading": "พบบันทึกสำหรับบัญชีดังต่อไปนี้", + "UserProfilesRecoverEmptyList": "ไม่มีโปรไฟล์ที่สามารถกู้คืนได้", + "GraphicsAATooltip": "ใช้การลดรอยหยักกับการเรนเดอร์เกม\n\nFXAA จะเบลอภาพส่วนใหญ่ ในขณะที่ SMAA จะพยายามค้นหาขอบหยักและปรับให้เรียบ\n\nไม่แนะนำให้ใช้ร่วมกับตัวกรองสเกล FSR\n\nตัวเลือกนี้สามารถเปลี่ยนแปลงได้ในขณะที่เกมกำลังทำงานอยู่โดยคลิก \"นำไปใช้\" ด้านล่าง คุณสามารถย้ายหน้าต่างการตั้งค่าไปด้านข้างและทดลองจนกว่าคุณจะพบรูปลักษณ์ที่คุณต้องการสำหรับเกม\n\nปล่อยไว้ที่ NONE หากไม่แน่ใจ", + "GraphicsAALabel": "ลดการฉีกขาดของภาพ:", + "GraphicsScalingFilterLabel": "ปรับขนาดตัวกรอง:", + "GraphicsScalingFilterTooltip": "เลือกตัวกรองสเกลที่จะใช้เมื่อใช้สเกลความละเอียด\n\nBilinear ทำงานได้ดีกับเกม 3D และเป็นตัวเลือกเริ่มต้นที่ปลอดภัย\n\nแนะนำให้ใช้เกมภาพพิกเซลที่ใกล้เคียงที่สุด\n\nFSR 1.0 เป็นเพียงตัวกรองความคมชัด ไม่แนะนำให้ใช้กับ FXAA หรือ SMAA\n\nตัวเลือกนี้สามารถเปลี่ยนแปลงได้ในขณะที่เกมกำลังทำงานอยู่โดยคลิก \"นำไปใช้\" ด้านล่าง คุณสามารถย้ายหน้าต่างการตั้งค่าไปด้านข้างและทดลองจนกว่าคุณจะพบรูปลักษณ์ที่คุณต้องการสำหรับเกม", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "ระดับ", + "GraphicsScalingFilterLevelTooltip": "ตั้งค่าระดับความคมชัด FSR 1.0 สูงกว่าจะคมชัดกว่า", + "SmaaLow": "SMAA ต่ำ", + "SmaaMedium": "SMAA ปานกลาง", + "SmaaHigh": "SMAA สูง", + "SmaaUltra": "SMAA สูงมาก", + "UserEditorTitle": "แก้ไขผู้ใช้", + "UserEditorTitleCreate": "สร้างผู้ใช้", + "SettingsTabNetworkInterface": "เชื่อมต่อเครือข่าย:", + "NetworkInterfaceTooltip": "อินเทอร์เฟซเครือข่ายที่ใช้สำหรับคุณสมบัติ LAN/LDN\n\nเมื่อใช้ร่วมกับ VPN หรือ XLink Kai และเกมที่รองรับ LAN สามารถใช้เพื่อปลอมการเชื่อมต่อเครือข่ายเดียวกันผ่านทางอินเทอร์เน็ต\n\nปล่อยให้เป็น ค่าเริ่มต้น หากคุณไม่แน่ใจ", + "NetworkInterfaceDefault": "ค่าเริ่มต้น", + "PackagingShaders": "รวม Shaders เข้าด้วยกัน", + "AboutChangelogButton": "ดูประวัติการเปลี่ยนแปลงบน GitHub", + "AboutChangelogButtonTooltipMessage": "คลิกเพื่อเปิดประวัติการเปลี่ยนแปลงสำหรับเวอร์ชั่นนี้ บนเบราว์เซอร์เริ่มต้นของคุณ", + "SettingsTabNetworkMultiplayer": "ผู้เล่นหลายคน", + "MultiplayerMode": "โหมด:", + "MultiplayerModeTooltip": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nLdnMitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ", + "MultiplayerModeDisabled": "Disabled", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json new file mode 100644 index 00000000..f74baaa1 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -0,0 +1,780 @@ +{ + "Language": "Türkçe", + "MenuBarFileOpenApplet": "Applet'i Aç", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Mii Editör Applet'ini Bağımsız Mod'da Aç", + "SettingsTabInputDirectMouseAccess": "Doğrudan Mouse Erişimi", + "SettingsTabSystemMemoryManagerMode": "Hafıza Yönetim Modu:", + "SettingsTabSystemMemoryManagerModeSoftware": "Yazılım", + "SettingsTabSystemMemoryManagerModeHost": "Host (hızlı)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (en hızlısı, tehlikeli)", + "SettingsTabSystemUseHypervisor": "Hypervisor Kullan", + "MenuBarFile": "_Dosya", + "MenuBarFileOpenFromFile": "_Dosyadan Uygulama Aç", + "MenuBarFileOpenUnpacked": "_Sıkıştırılmamış Oyun Aç", + "MenuBarFileOpenEmuFolder": "Ryujinx Klasörünü aç", + "MenuBarFileOpenLogsFolder": "Logs Klasörünü aç", + "MenuBarFileExit": "_Çıkış", + "MenuBarOptions": "_Seçenekler", + "MenuBarOptionsToggleFullscreen": "Tam Ekran Modunu Aç", + "MenuBarOptionsStartGamesInFullscreen": "Oyunları Tam Ekran Modunda Başlat", + "MenuBarOptionsStopEmulation": "Emülasyonu Durdur", + "MenuBarOptionsSettings": "_Seçenekler", + "MenuBarOptionsManageUserProfiles": "_Kullanıcı Profillerini Yönet", + "MenuBarActions": "_Eylemler", + "MenuBarOptionsSimulateWakeUpMessage": "Uyandırma Mesajı Simüle Et", + "MenuBarActionsScanAmiibo": "Bir Amiibo Tara", + "MenuBarTools": "_Araçlar", + "MenuBarToolsInstallFirmware": "Yazılım Yükle", + "MenuBarFileToolsInstallFirmwareFromFile": "XCI veya ZIP'ten Yazılım Yükle", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Bir Dizin Üzerinden Yazılım Yükle", + "MenuBarToolsManageFileTypes": "Dosya uzantılarını yönet", + "MenuBarToolsInstallFileTypes": "Dosya uzantılarını yükle", + "MenuBarToolsUninstallFileTypes": "Dosya uzantılarını kaldır", + "MenuBarView": "_Görüntüle", + "MenuBarViewWindow": "Pencere Boyutu", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "_Yardım", + "MenuBarHelpCheckForUpdates": "Güncellemeleri Denetle", + "MenuBarHelpAbout": "Hakkında", + "MenuSearch": "Ara...", + "GameListHeaderFavorite": "Favori", + "GameListHeaderIcon": "Simge", + "GameListHeaderApplication": "Oyun Adı", + "GameListHeaderDeveloper": "Geliştirici", + "GameListHeaderVersion": "Sürüm", + "GameListHeaderTimePlayed": "Oynama Süresi", + "GameListHeaderLastPlayed": "Son Oynama Tarihi", + "GameListHeaderFileExtension": "Dosya Uzantısı", + "GameListHeaderFileSize": "Dosya Boyutu", + "GameListHeaderPath": "Yol", + "GameListContextMenuOpenUserSaveDirectory": "Kullanıcı Kayıt Dosyası Dizinini Aç", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Uygulamanın Kullanıcı Kaydı'nın bulunduğu dizini açar", + "GameListContextMenuOpenDeviceSaveDirectory": "Kullanıcı Cihaz Dizinini Aç", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Uygulamanın Kullanıcı Cihaz Kaydı'nın bulunduğu dizini açar", + "GameListContextMenuOpenBcatSaveDirectory": "Kullanıcı BCAT Dizinini Aç", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Uygulamanın Kullanıcı BCAT Kaydı'nın bulunduğu dizini açar", + "GameListContextMenuManageTitleUpdates": "Oyun Güncellemelerini Yönet", + "GameListContextMenuManageTitleUpdatesToolTip": "Oyun Güncelleme Yönetim Penceresini Açar", + "GameListContextMenuManageDlc": "DLC'leri Yönet", + "GameListContextMenuManageDlcToolTip": "DLC yönetim penceresini açar", + "GameListContextMenuCacheManagement": "Önbellek Yönetimi", + "GameListContextMenuCacheManagementPurgePptc": "PPTC Yeniden Yapılandırmasını Başlat", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Oyunun bir sonraki açılışında PPTC'yi yeniden yapılandır", + "GameListContextMenuCacheManagementPurgeShaderCache": "Shader Önbelleğini Temizle", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Uygulamanın shader önbelleğini temizler", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC Dizinini Aç", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Uygulamanın PPTC Önbelleğinin bulunduğu dizini açar", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Shader Önbelleği Dizinini Aç", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Uygulamanın shader önbelleğinin bulunduğu dizini açar", + "GameListContextMenuExtractData": "Veriyi Ayıkla", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Uygulamanın geçerli yapılandırmasından ExeFS kısmını ayıkla (Güncellemeler dahil)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Uygulamanın geçerli yapılandırmasından RomFS kısmını ayıkla (Güncellemeler dahil)", + "GameListContextMenuExtractDataLogo": "Simge", + "GameListContextMenuExtractDataLogoToolTip": "Uygulamanın geçerli yapılandırmasından Logo kısmını ayıkla (Güncellemeler dahil)", + "GameListContextMenuCreateShortcut": "Uygulama Kısayolu Oluştur", + "GameListContextMenuCreateShortcutToolTip": "Seçilmiş uygulamayı çalıştıracak bir masaüstü kısayolu oluştur", + "GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application", + "GameListContextMenuOpenModsDirectory": "Mod Dizinini Aç", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "StatusBarGamesLoaded": "{0}/{1} Oyun Yüklendi", + "StatusBarSystemVersion": "Sistem Sürümü: {0}", + "LinuxVmMaxMapCountDialogTitle": "Bellek Haritaları İçin Düşük Limit Tespit Edildi ", + "LinuxVmMaxMapCountDialogTextPrimary": "vm.max_map_count değerini {0} sayısına yükseltmek ister misiniz", + "LinuxVmMaxMapCountDialogTextSecondary": "Bazı oyunlar şu an izin verilen bellek haritası limitinden daha fazlasını yaratmaya çalışabilir. Ryujinx bu limitin geçildiği takdirde kendini kapatıcaktır.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Evet, bir sonraki yeniden başlatmaya kadar", + "LinuxVmMaxMapCountDialogButtonPersistent": "Evet, kalıcı olarak", + "LinuxVmMaxMapCountWarningTextPrimary": "İzin verilen maksimum bellek haritası değeri tavsiye edildiğinden daha düşük. ", + "LinuxVmMaxMapCountWarningTextSecondary": "Şu anki vm.max_map_count değeri {0}, bu {1} değerinden daha az. Bazı oyunlar şu an izin verilen bellek haritası limitinden daha fazlasını yaratmaya çalışabilir. Ryujinx bu limitin geçildiği takdirde kendini kapatıcaktır.\n\nManuel olarak bu limiti arttırmayı deneyebilir ya da pkexec'i yükleyebilirsiniz, bu da Ryujinx'in yardımcı olmasına izin verir.", + "Settings": "Ayarlar", + "SettingsTabGeneral": "Kullancı Arayüzü", + "SettingsTabGeneralGeneral": "Genel", + "SettingsTabGeneralEnableDiscordRichPresence": "Discord Zengin İçerik'i Etkinleştir", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Her Açılışta Güncellemeleri Denetle", + "SettingsTabGeneralShowConfirmExitDialog": "\"Çıkışı Onayla\" Diyaloğunu Göster", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", + "SettingsTabGeneralHideCursor": "İşaretçiyi Gizle:", + "SettingsTabGeneralHideCursorNever": "Hiçbir Zaman", + "SettingsTabGeneralHideCursorOnIdle": "Hareketsiz Durumda", + "SettingsTabGeneralHideCursorAlways": "Her Zaman", + "SettingsTabGeneralGameDirectories": "Oyun Dizinleri", + "SettingsTabGeneralAdd": "Ekle", + "SettingsTabGeneralRemove": "Kaldır", + "SettingsTabSystem": "Sistem", + "SettingsTabSystemCore": "Çekirdek", + "SettingsTabSystemSystemRegion": "Sistem Bölgesi:", + "SettingsTabSystemSystemRegionJapan": "Japonya", + "SettingsTabSystemSystemRegionUSA": "ABD", + "SettingsTabSystemSystemRegionEurope": "Avrupa", + "SettingsTabSystemSystemRegionAustralia": "Avustralya", + "SettingsTabSystemSystemRegionChina": "Çin", + "SettingsTabSystemSystemRegionKorea": "Kore", + "SettingsTabSystemSystemRegionTaiwan": "Tayvan", + "SettingsTabSystemSystemLanguage": "Sistem Dili:", + "SettingsTabSystemSystemLanguageJapanese": "Japonca", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerikan İngilizcesi", + "SettingsTabSystemSystemLanguageFrench": "Fransızca", + "SettingsTabSystemSystemLanguageGerman": "Almanca", + "SettingsTabSystemSystemLanguageItalian": "İtalyanca", + "SettingsTabSystemSystemLanguageSpanish": "İspanyolca", + "SettingsTabSystemSystemLanguageChinese": "Çince", + "SettingsTabSystemSystemLanguageKorean": "Korece", + "SettingsTabSystemSystemLanguageDutch": "Flemenkçe", + "SettingsTabSystemSystemLanguagePortuguese": "Portekizce", + "SettingsTabSystemSystemLanguageRussian": "Rusça", + "SettingsTabSystemSystemLanguageTaiwanese": "Tayvanca", + "SettingsTabSystemSystemLanguageBritishEnglish": "İngiliz İngilizcesi", + "SettingsTabSystemSystemLanguageCanadianFrench": "Kanada Fransızcası", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Latin Amerika İspanyolcası", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Basitleştirilmiş Çince", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Geleneksel Çince", + "SettingsTabSystemSystemTimeZone": "Sistem Saat Dilimi:", + "SettingsTabSystemSystemTime": "Sistem Saati:", + "SettingsTabSystemEnableVsync": "Dikey Eşitleme", + "SettingsTabSystemEnablePptc": "PPTC (Profilli Sürekli Çeviri Önbelleği)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Bütünlük Kontrolleri", + "SettingsTabSystemAudioBackend": "Ses Motoru:", + "SettingsTabSystemAudioBackendDummy": "Yapay", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hack'ler", + "SettingsTabSystemHacksNote": " (dengesizlik oluşturabilir)", + "SettingsTabSystemExpandDramSize": "Alternatif bellek düzeni kullan (Geliştirici)", + "SettingsTabSystemIgnoreMissingServices": "Eksik Servisleri Görmezden Gel", + "SettingsTabGraphics": "Grafikler", + "SettingsTabGraphicsAPI": "Grafikler API", + "SettingsTabGraphicsEnableShaderCache": "Shader Önbelleğini Etkinleştir", + "SettingsTabGraphicsAnisotropicFiltering": "Eşyönsüz Doku Süzmesi:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Otomatik", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Çözünürlük Ölçeği:", + "SettingsTabGraphicsResolutionScaleCustom": "Özel (Tavsiye Edilmez)", + "SettingsTabGraphicsResolutionScaleNative": "Yerel (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Tavsiye Edilmez)", + "SettingsTabGraphicsAspectRatio": "En-Boy Oranı:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Pencereye Sığdırmak İçin Genişlet", + "SettingsTabGraphicsDeveloperOptions": "Geliştirici Seçenekleri", + "SettingsTabGraphicsShaderDumpPath": "Grafik Shader Döküm Yolu:", + "SettingsTabLogging": "Loglama", + "SettingsTabLoggingLogging": "Loglama", + "SettingsTabLoggingEnableLoggingToFile": "Logları Dosyaya Kaydetmeyi Etkinleştir", + "SettingsTabLoggingEnableStubLogs": "Stub Loglarını Etkinleştir", + "SettingsTabLoggingEnableInfoLogs": "Bilgi Loglarını Etkinleştir", + "SettingsTabLoggingEnableWarningLogs": "Uyarı Loglarını Etkinleştir", + "SettingsTabLoggingEnableErrorLogs": "Hata Loglarını Etkinleştir", + "SettingsTabLoggingEnableTraceLogs": "Trace Loglarını Etkinleştir", + "SettingsTabLoggingEnableGuestLogs": "Guest Loglarını Etkinleştir", + "SettingsTabLoggingEnableFsAccessLogs": "Fs Erişim Loglarını Etkinleştir", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Evrensel Erişim Log Modu:", + "SettingsTabLoggingDeveloperOptions": "Geliştirici Seçenekleri (UYARI: Performansı düşürecektir)", + "SettingsTabLoggingDeveloperOptionsNote": "UYARI: Oyun performansı azalacak", + "SettingsTabLoggingGraphicsBackendLogLevel": "Grafik Arka Uç Günlük Düzeyi", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Hiçbiri", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Hata", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Yavaşlamalar", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Hepsi", + "SettingsTabLoggingEnableDebugLogs": "Hata Ayıklama Loglarını Etkinleştir", + "SettingsTabInput": "Giriş Yöntemi", + "SettingsTabInputEnableDockedMode": "Docked Modu Etkinleştir", + "SettingsTabInputDirectKeyboardAccess": "Doğrudan Klavye Erişimi", + "SettingsButtonSave": "Kaydet", + "SettingsButtonClose": "Kapat", + "SettingsButtonOk": "Tamam", + "SettingsButtonCancel": "İptal", + "SettingsButtonApply": "Uygula", + "ControllerSettingsPlayer": "Oyuncu", + "ControllerSettingsPlayer1": "Oyuncu 1", + "ControllerSettingsPlayer2": "Oyuncu 2", + "ControllerSettingsPlayer3": "Oyuncu 3", + "ControllerSettingsPlayer4": "Oyuncu 4", + "ControllerSettingsPlayer5": "Oyuncu 5", + "ControllerSettingsPlayer6": "Oyuncu 6", + "ControllerSettingsPlayer7": "Oyuncu 7", + "ControllerSettingsPlayer8": "Oyuncu 8", + "ControllerSettingsHandheld": "Portatif Mod", + "ControllerSettingsInputDevice": "Giriş Cihazı", + "ControllerSettingsRefresh": "Yenile", + "ControllerSettingsDeviceDisabled": "Devre Dışı", + "ControllerSettingsControllerType": "Kumanda Tipi", + "ControllerSettingsControllerTypeHandheld": "Portatif Mod", + "ControllerSettingsControllerTypeProController": "Profesyonel Kumanda", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Çifti", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Sol", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Sağ", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Varsayılan", + "ControllerSettingsLoad": "Yükle", + "ControllerSettingsAdd": "Ekle", + "ControllerSettingsRemove": "Kaldır", + "ControllerSettingsButtons": "Tuşlar", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Yön Tuşları", + "ControllerSettingsDPadUp": "Yukarı", + "ControllerSettingsDPadDown": "Aşağı", + "ControllerSettingsDPadLeft": "Sol", + "ControllerSettingsDPadRight": "Sağ", + "ControllerSettingsStickButton": "Tuş", + "ControllerSettingsStickUp": "Yukarı", + "ControllerSettingsStickDown": "Aşağı", + "ControllerSettingsStickLeft": "Sol", + "ControllerSettingsStickRight": "Sağ", + "ControllerSettingsStickStick": "Analog", + "ControllerSettingsStickInvertXAxis": "X Eksenini Tersine Çevir", + "ControllerSettingsStickInvertYAxis": "Y Eksenini Tersine Çevir", + "ControllerSettingsStickDeadzone": "Ölü Bölge", + "ControllerSettingsLStick": "Sol Analog", + "ControllerSettingsRStick": "Sağ Analog", + "ControllerSettingsTriggersLeft": "Tetikler Sol", + "ControllerSettingsTriggersRight": "Tetikler Sağ", + "ControllerSettingsTriggersButtonsLeft": "Tetik Tuşları Sol", + "ControllerSettingsTriggersButtonsRight": "Tetik Tuşları Sağ", + "ControllerSettingsTriggers": "Tetikler", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Tuşlar Sol", + "ControllerSettingsExtraButtonsRight": "Tuşlar Sağ", + "ControllerSettingsMisc": "Diğer", + "ControllerSettingsTriggerThreshold": "Tetik Eşiği:", + "ControllerSettingsMotion": "Hareket", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook uyumlu hareket kullan", + "ControllerSettingsMotionControllerSlot": "Kumanda Yuvası:", + "ControllerSettingsMotionMirrorInput": "Girişi Aynala", + "ControllerSettingsMotionRightJoyConSlot": "Sağ JoyCon Yuvası:", + "ControllerSettingsMotionServerHost": "Sunucu Sahibi:", + "ControllerSettingsMotionGyroSensitivity": "Gyro Hassasiyeti:", + "ControllerSettingsMotionGyroDeadzone": "Gyro Ölü Bölgesi:", + "ControllerSettingsSave": "Kaydet", + "ControllerSettingsClose": "Kapat", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Sol Shift", + "KeyShiftRight": "Sağ Shift", + "KeyControlLeft": "Sol Ctrl", + "KeyMacControlLeft": "⌃ Sol", + "KeyControlRight": "Sağ Control", + "KeyMacControlRight": "⌃ Sağ", + "KeyAltLeft": "Sol Alt", + "KeyMacAltLeft": "⌥ Sol", + "KeyAltRight": "Sağ Alt", + "KeyMacAltRight": "⌥ Sağ", + "KeyWinLeft": "⊞ Sol", + "KeyMacWinLeft": "⌘ Sol", + "KeyWinRight": "⊞ Sağ", + "KeyMacWinRight": "⌘ Sağ", + "KeyMenu": "Menü", + "KeyUp": "Yukarı", + "KeyDown": "Aşağı", + "KeyLeft": "Sol", + "KeyRight": "Sağ", + "KeyEnter": "Enter", + "KeyEscape": "Esc", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Geri tuşu", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Sağ", + "GamepadMinus": "-", + "GamepadPlus": "4", + "GamepadGuide": "Rehber", + "GamepadMisc1": "Diğer", + "GamepadPaddle1": "Pedal 1", + "GamepadPaddle2": "Pedal 2", + "GamepadPaddle3": "Pedal 3", + "GamepadPaddle4": "Pedal 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Sol Tetik 0", + "GamepadSingleRightTrigger0": "Sağ Tetik 0", + "GamepadSingleLeftTrigger1": "Sol Tetik 1", + "GamepadSingleRightTrigger1": "Sağ Tetik 1", + "StickLeft": "Sol Çubuk", + "StickRight": "Sağ çubuk", + "UserProfilesSelectedUserProfile": "Seçili Kullanıcı Profili:", + "UserProfilesSaveProfileName": "Profil İsmini Kaydet", + "UserProfilesChangeProfileImage": "Profil Resmini Değiştir", + "UserProfilesAvailableUserProfiles": "Mevcut Kullanıcı Profilleri:", + "UserProfilesAddNewProfile": "Yeni Profil Ekle", + "UserProfilesDelete": "Sil", + "UserProfilesClose": "Kapat", + "ProfileNameSelectionWatermark": "Kullanıcı Adı Seç", + "ProfileImageSelectionTitle": "Profil Resmi Seçimi", + "ProfileImageSelectionHeader": "Profil Resmi Seç", + "ProfileImageSelectionNote": "Özel bir profil resmi içeri aktarabilir veya sistem avatarlarından birini seçebilirsiniz", + "ProfileImageSelectionImportImage": "Resim İçeri Aktar", + "ProfileImageSelectionSelectAvatar": "Yazılım Avatarı Seç", + "InputDialogTitle": "Giriş Yöntemi Diyaloğu", + "InputDialogOk": "Tamam", + "InputDialogCancel": "İptal", + "InputDialogAddNewProfileTitle": "Profil İsmini Seç", + "InputDialogAddNewProfileHeader": "Lütfen Bir Profil İsmi Girin", + "InputDialogAddNewProfileSubtext": "(Maksimum Uzunluk: {0})", + "AvatarChoose": "Seç", + "AvatarSetBackgroundColor": "Arka Plan Rengi Ayarla", + "AvatarClose": "Kapat", + "ControllerSettingsLoadProfileToolTip": "Profil Yükle", + "ControllerSettingsAddProfileToolTip": "Profil Ekle", + "ControllerSettingsRemoveProfileToolTip": "Profili Kaldır", + "ControllerSettingsSaveProfileToolTip": "Profili Kaydet", + "MenuBarFileToolsTakeScreenshot": "Ekran Görüntüsü Al", + "MenuBarFileToolsHideUi": "Arayüzü Gizle", + "GameListContextMenuRunApplication": "Uygulamayı Çalıştır", + "GameListContextMenuToggleFavorite": "Favori Ayarla", + "GameListContextMenuToggleFavoriteToolTip": "Oyunu Favorilere Ekle/Çıkar", + "SettingsTabGeneralTheme": "Tema:", + "SettingsTabGeneralThemeDark": "Karanlık", + "SettingsTabGeneralThemeLight": "Aydınlık", + "ControllerSettingsConfigureGeneral": "Ayarla", + "ControllerSettingsRumble": "Titreşim", + "ControllerSettingsRumbleStrongMultiplier": "Güçlü Titreşim Çoklayıcı", + "ControllerSettingsRumbleWeakMultiplier": "Zayıf Titreşim Seviyesi", + "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}] için kayıt verisi bulunamadı", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Bu oyun için kayıt verisi oluşturmak ister misiniz?", + "DialogConfirmationTitle": "Ryujinx - Onay", + "DialogUpdaterTitle": "Ryujinx - Güncelleyici", + "DialogErrorTitle": "Ryujinx - Hata", + "DialogWarningTitle": "Ryujinx - Uyarı", + "DialogExitTitle": "Ryujinx - Çıkış", + "DialogErrorMessage": "Ryujinx bir hata ile karşılaştı", + "DialogExitMessage": "Ryujinx'i kapatmak istediğinizden emin misiniz?", + "DialogExitSubMessage": "Kaydedilmeyen bütün veriler kaybedilecek!", + "DialogMessageCreateSaveErrorMessage": "Belirtilen kayıt verisi oluşturulurken bir hata oluştu: {0}", + "DialogMessageFindSaveErrorMessage": "Belirtilen kayıt verisi bulunmaya çalışırken hata: {0}", + "FolderDialogExtractTitle": "İçine ayıklanacak klasörü seç", + "DialogNcaExtractionMessage": "{1} den {0} kısmı ayıklanıyor...", + "DialogNcaExtractionTitle": "Ryujinx - NCA Kısmı Ayıklayıcısı", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Ayıklama hatası. Ana NCA seçilen dosyada bulunamadı.", + "DialogNcaExtractionCheckLogErrorMessage": "Ayıklama hatası. Ek bilgi için kayıt dosyasını okuyun.", + "DialogNcaExtractionSuccessMessage": "Ayıklama başarıyla tamamlandı.", + "DialogUpdaterConvertFailedMessage": "Güncel Ryujinx sürümü dönüştürülemedi.", + "DialogUpdaterCancelUpdateMessage": "Güncelleme iptal ediliyor!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Zaten Ryujinx'in en güncel sürümünü kullanıyorsunuz!", + "DialogUpdaterFailedToGetVersionMessage": "GitHub tarafından sürüm bilgileri alınırken bir hata oluştu. Eğer yeni sürüm için hazırlıklar yapılıyorsa bu hatayı almanız olasıdır. Lütfen birkaç dakika sonra tekrar deneyiniz.", + "DialogUpdaterConvertFailedGithubMessage": "Github Release'den alınan Ryujinx sürümü dönüştürülemedi.", + "DialogUpdaterDownloadingMessage": "Güncelleme İndiriliyor...", + "DialogUpdaterExtractionMessage": "Güncelleme Ayıklanıyor...", + "DialogUpdaterRenamingMessage": "Güncelleme Yeniden Adlandırılıyor...", + "DialogUpdaterAddingFilesMessage": "Yeni Güncelleme Ekleniyor...", + "DialogUpdaterCompleteMessage": "Güncelleme Tamamlandı!", + "DialogUpdaterRestartMessage": "Ryujinx'i şimdi yeniden başlatmak istiyor musunuz?", + "DialogUpdaterNoInternetMessage": "İnternete bağlı değilsiniz!", + "DialogUpdaterNoInternetSubMessage": "Lütfen aktif bir internet bağlantınız olduğunu kontrol edin!", + "DialogUpdaterDirtyBuildMessage": "Ryujinx'in Dirty build'lerini güncelleyemezsiniz!", + "DialogUpdaterDirtyBuildSubMessage": "Desteklenen bir sürüm için lütfen Ryujinx'i https://ryujinx.org/ sitesinden indirin.", + "DialogRestartRequiredMessage": "Yeniden Başlatma Gerekli", + "DialogThemeRestartMessage": "Tema kaydedildi. Temayı uygulamak için yeniden başlatma gerekiyor.", + "DialogThemeRestartSubMessage": "Yeniden başlatmak ister misiniz", + "DialogFirmwareInstallEmbeddedMessage": "Bu oyunun içine gömülü olan yazılımı yüklemek ister misiniz? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.", + "DialogFirmwareNoFirmwareInstalledMessage": "Yazılım Yüklü Değil", + "DialogFirmwareInstalledMessage": "Yazılım {0} yüklendi", + "DialogInstallFileTypesSuccessMessage": "Dosya uzantıları başarıyla yüklendi!", + "DialogInstallFileTypesErrorMessage": "Dosya uzantıları yükleme işlemi başarısız oldu.", + "DialogUninstallFileTypesSuccessMessage": "Dosya uzantıları başarıyla kaldırıldı!", + "DialogUninstallFileTypesErrorMessage": "Dosya uzantıları kaldırma işlemi başarısız oldu.", + "DialogOpenSettingsWindowLabel": "Seçenekler Penceresini Aç", + "DialogControllerAppletTitle": "Kumanda Applet'i", + "DialogMessageDialogErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", + "DialogErrorAppletErrorExceptionMessage": "Applet diyaloğu gösterilirken hata: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nBu hatayı düzeltmek adına daha fazla bilgi için kurulum kılavuzumuzu takip edin.", + "DialogUserErrorDialogTitle": "Ryujinx Hatası ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "API'dan bilgi alırken bir hata oluştu.", + "DialogAmiiboApiConnectErrorMessage": "Amiibo API sunucusuna bağlanılamadı. Sunucu çevrimdışı olabilir veya uygun bir internet bağlantınızın olduğunu kontrol etmeniz gerekebilir.", + "DialogProfileInvalidProfileErrorMessage": "Profil {0} güncel giriş konfigürasyon sistemi ile uyumlu değil.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Varsayılan Profil'in üstüne yazılamaz", + "DialogProfileDeleteProfileTitle": "Profil Siliniyor", + "DialogProfileDeleteProfileMessage": "Bu eylem geri döndürülemez, devam etmek istediğinizden emin misiniz?", + "DialogWarning": "Uyarı", + "DialogPPTCDeletionMessage": "Belirtilen PPTC cache silinecek :\n\n{0}\n\nDevam etmek istediğinizden emin misiniz?", + "DialogPPTCDeletionErrorMessage": "Belirtilen PPTC cache temizlenirken hata {0}: {1}", + "DialogShaderDeletionMessage": "Belirtilen Shader cache silinecek :\n\n{0}\n\nDevam etmek istediğinizden emin misiniz?", + "DialogShaderDeletionErrorMessage": "Belirtilen Shader cache temizlenirken hata {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx bir hata ile karşılaştı", + "DialogInvalidTitleIdErrorMessage": "Arayüz hatası: Seçilen oyun geçerli bir title ID'ye sahip değil", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0} da geçerli bir sistem firmware'i bulunamadı.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Firmware {0} Yükle", + "DialogFirmwareInstallerFirmwareInstallMessage": "Sistem sürümü {0} yüklenecek.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nBu şimdiki sistem sürümünün yerini alacak {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDevam etmek istiyor musunuz?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Firmware yükleniyor...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Sistem sürümü {0} başarıyla yüklendi.", + "DialogUserProfileDeletionWarningMessage": "Seçilen profil silinirse kullanılabilen başka profil kalmayacak", + "DialogUserProfileDeletionConfirmMessage": "Seçilen profili silmek istiyor musunuz", + "DialogUserProfileUnsavedChangesTitle": "Uyarı - Kaydedilmemiş Değişiklikler", + "DialogUserProfileUnsavedChangesMessage": "Kullanıcı profilinizde kaydedilmemiş değişiklikler var.", + "DialogUserProfileUnsavedChangesSubMessage": "Yaptığınız değişiklikleri iptal etmek istediğinize emin misiniz?", + "DialogControllerSettingsModifiedConfirmMessage": "Geçerli kumanda seçenekleri güncellendi.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Kaydetmek istiyor musunuz?", + "DialogLoadFileErrorMessage": "{0}. Hatalı Dosya: {1}", + "DialogModAlreadyExistsMessage": "Mod zaten var", + "DialogModInvalidMessage": "The specified directory does not contain a mod!", + "DialogModDeleteNoParentMessage": "Silme Başarısız: \"{0}\" Modu için üst dizin bulunamadı! ", + "DialogDlcNoDlcErrorMessage": "Belirtilen dosya seçilen oyun için DLC içermiyor!", + "DialogPerformanceCheckLoggingEnabledMessage": "Sadece geliştiriler için dizayn edilen Trace Loglama seçeneği etkin.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "En iyi performans için trace loglama'nın devre dışı bırakılması tavsiye edilir. Trace loglama seçeneğini şimdi devre dışı bırakmak ister misiniz?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Sadece geliştiriler için dizayn edilen Shader Dumping seçeneği etkin.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "En iyi performans için Shader Dumping'in devre dışı bırakılması tavsiye edilir. Shader Dumping seçeneğini şimdi devre dışı bırakmak ister misiniz?", + "DialogLoadAppGameAlreadyLoadedMessage": "Bir oyun zaten yüklendi", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Lütfen yeni bir oyun açmadan önce emülasyonu durdurun veya emülatörü kapatın.", + "DialogUpdateAddUpdateErrorMessage": "Belirtilen dosya seçilen oyun için güncelleme içermiyor!", + "DialogSettingsBackendThreadingWarningTitle": "Uyarı - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Bu seçeneğin tamamen uygulanması için Ryujinx'in kapatıp açılması gerekir. Kullandığınız işletim sistemine bağlı olarak, Ryujinx'in multithreading'ini kullanırken driver'ınızın multithreading seçeneğini kapatmanız gerekebilir.", + "DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?", + "DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?", + "SettingsTabGraphicsFeaturesOptions": "Özellikler", + "SettingsTabGraphicsBackendMultithreading": "Grafik Backend Multithreading:", + "CommonAuto": "Otomatik", + "CommonOff": "Kapalı", + "CommonOn": "Açık", + "InputDialogYes": "Evet", + "InputDialogNo": "Hayır", + "DialogProfileInvalidProfileNameErrorMessage": "Dosya adı geçersiz karakter içeriyor. Lütfen tekrar deneyin.", + "MenuBarOptionsPauseEmulation": "Durdur", + "MenuBarOptionsResumeEmulation": "Devam Et", + "AboutUrlTooltipMessage": "Ryujinx'in websitesini varsayılan tarayıcınızda açmak için tıklayın.", + "AboutDisclaimerMessage": "Ryujinx, Nintendo™ veya ortaklarıyla herhangi bir şekilde bağlantılı değildir.", + "AboutAmiiboDisclaimerMessage": "Amiibo emülasyonumuzda \nAmiiboAPI (www.amiiboapi.com) kullanılmaktadır.", + "AboutPatreonUrlTooltipMessage": "Ryujinx'in Patreon sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutGithubUrlTooltipMessage": "Ryujinx'in GitHub sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutDiscordUrlTooltipMessage": "Varsayılan tarayıcınızda Ryujinx'in Discord'una bir davet açmak için tıklayın.", + "AboutTwitterUrlTooltipMessage": "Ryujinx'in Twitter sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutRyujinxAboutTitle": "Hakkında:", + "AboutRyujinxAboutContent": "Ryujinx bir Nintendo Switch™ emülatörüdür.\nLütfen bizi Patreon'da destekleyin.\nEn son haberleri Twitter veya Discord'umuzdan alın.\nKatkıda bulunmak isteyen geliştiriciler GitHub veya Discord üzerinden daha fazla bilgi edinebilir.", + "AboutRyujinxMaintainersTitle": "Geliştiriciler:", + "AboutRyujinxMaintainersContentTooltipMessage": "Katkıda bulunanlar sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutRyujinxSupprtersTitle": "Patreon Destekleyicileri:", + "AmiiboSeriesLabel": "Amiibo Serisi", + "AmiiboCharacterLabel": "Karakter", + "AmiiboScanButtonLabel": "Tarat", + "AmiiboOptionsShowAllLabel": "Tüm Amiibo'ları Göster", + "AmiiboOptionsUsRandomTagLabel": "Hack: Rastgele bir Uuid kullan", + "DlcManagerTableHeadingEnabledLabel": "Etkin", + "DlcManagerTableHeadingTitleIdLabel": "Başlık ID", + "DlcManagerTableHeadingContainerPathLabel": "Container Yol", + "DlcManagerTableHeadingFullPathLabel": "Tam Yol", + "DlcManagerRemoveAllButton": "Tümünü kaldır", + "DlcManagerEnableAllButton": "Tümünü Aktif Et", + "DlcManagerDisableAllButton": "Tümünü Devre Dışı Bırak", + "ModManagerDeleteAllButton": "Hepsini Sil", + "MenuBarOptionsChangeLanguage": "Dili Değiştir", + "MenuBarShowFileTypes": "Dosya Uzantılarını Göster", + "CommonSort": "Sırala", + "CommonShowNames": "İsimleri Göster", + "CommonFavorite": "Favori", + "OrderAscending": "Artan", + "OrderDescending": "Azalan", + "SettingsTabGraphicsFeatures": "Özellikler & İyileştirmeler", + "ErrorWindowTitle": "Hata Penceresi", + "ToggleDiscordTooltip": "Ryujinx'i \"şimdi oynanıyor\" Discord aktivitesinde göstermeyi veya göstermemeyi seçin", + "AddGameDirBoxTooltip": "Listeye eklemek için oyun dizini seçin", + "AddGameDirTooltip": "Listeye oyun dizini ekle", + "RemoveGameDirTooltip": "Seçili oyun dizinini kaldır", + "CustomThemeCheckTooltip": "Emülatör pencerelerinin görünümünü değiştirmek için özel bir Avalonia teması kullan", + "CustomThemePathTooltip": "Özel arayüz temasının yolu", + "CustomThemeBrowseTooltip": "Özel arayüz teması için göz at", + "DockModeToggleTooltip": "Docked modu emüle edilen sistemin yerleşik Nintendo Switch gibi davranmasını sağlar. Bu çoğu oyunda grafik kalitesini arttırır. Diğer yandan, bu seçeneği devre dışı bırakmak emüle edilen sistemin portatif Ninendo Switch gibi davranmasını sağlayıp grafik kalitesini düşürür.\n\nDocked modu kullanmayı düşünüyorsanız 1. Oyuncu kontrollerini; Handheld modunu kullanmak istiyorsanız portatif kontrollerini konfigüre edin.\n\nEmin değilseniz aktif halde bırakın.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.\n\nOnly works with games that natively support keyboard usage on Switch hardware.\n\nLeave OFF if unsure.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.\n\nOnly works with games that natively support mouse controls on Switch hardware, which are few and far between.\n\nWhen enabled, touch screen functionality may not work.\n\nLeave OFF if unsure.", + "RegionTooltip": "Sistem Bölgesini Değiştir", + "LanguageTooltip": "Sistem Dilini Değiştir", + "TimezoneTooltip": "Sistem Saat Dilimini Değiştir", + "TimeTooltip": "Sistem Saatini Değiştir", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference (F1 by default). We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "PptcToggleTooltip": "Çevrilen JIT fonksiyonlarını oyun her açıldığında çevrilmek zorunda kalmaması için kaydeder.\n\nTeklemeyi azaltır ve ilk açılıştan sonra oyunların ilk açılış süresini ciddi biçimde hızlandırır.\n\nEmin değilseniz aktif halde bırakın.", + "FsIntegrityToggleTooltip": "Oyun açarken hatalı dosyaların olup olmadığını kontrol eder, ve hatalı dosya bulursa log dosyasında hash hatası görüntüler.\n\nPerformansa herhangi bir etkisi yoktur ve sorun gidermeye yardımcı olur.\n\nEmin değilseniz aktif halde bırakın.", + "AudioBackendTooltip": "Ses çıkış motorunu değiştirir.\n\nSDL2 tercih edilen seçenektir, OpenAL ve SoundIO ise alternatif olarak kullanılabilir. Dummy seçeneğinde ses çıkışı olmayacaktır.\n\nEmin değilseniz SDL2 seçeneğine ayarlayın.", + "MemoryManagerTooltip": "Guest hafızasının nasıl tahsis edilip erişildiğini değiştirir. Emüle edilen CPU performansını ciddi biçimde etkiler.\n\nEmin değilseniz HOST UNCHECKED seçeneğine ayarlayın.", + "MemoryManagerSoftwareTooltip": "Adres çevirisi için bir işlemci sayfası kullanır. En yüksek doğruluğu ve en yavaş performansı sunar.", + "MemoryManagerHostTooltip": "Hafızayı doğrudan host adres aralığında tahsis eder. Çok daha hızlı JIT derleme ve işletimi sunar.", + "MemoryManagerUnsafeTooltip": "Hafızayı doğrudan tahsis eder, ancak host aralığına erişimden önce adresi maskelemez. Daha iyi performansa karşılık emniyetten ödün verir. Misafir uygulama Ryujinx içerisinden istediği hafızaya erişebilir, bu sebeple bu seçenek ile sadece güvendiğiniz uygulamaları çalıştırın.", + "UseHypervisorTooltip": "JIT yerine Hypervisor kullan. Uygun durumlarda performansı büyük oranda arttırır. Ancak şu anki halinde stabil durumda çalışmayabilir.", + "DRamTooltip": "Emüle edilen sistem hafızasını 4GiB'dan 6GiB'a yükseltir.\n\nBu seçenek yalnızca yüksek çözünürlük doku paketleri veya 4k çözünürlük modları için kullanılır. Performansı artırMAZ!\n\nEmin değilseniz devre dışı bırakın.", + "IgnoreMissingServicesTooltip": "Henüz programlanmamış Horizon işletim sistemi servislerini görmezden gelir. Bu seçenek belirli oyunların açılırken çökmesinin önüne geçmeye yardımcı olabilir.\n\nEmin değilseniz devre dışı bırakın.", + "GraphicsBackendThreadingTooltip": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.", + "GalThreadingTooltip": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.", + "ShaderCacheToggleTooltip": "Sonraki çalışmalarda takılmaları engelleyen bir gölgelendirici disk önbelleğine kaydeder.", + "ResolutionScaleTooltip": "Multiplies the game's rendering resolution.\n\nA few games may not work with this and look pixelated even when the resolution is increased; for those games, you may need to find mods that remove anti-aliasing or that increase their internal rendering resolution. For using the latter, you'll likely want to select Native.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nKeep in mind 4x is overkill for virtually any setup.", + "ResolutionScaleEntryTooltip": "Küsüratlı çözünürlük ölçeği, 1.5 gibi. Küsüratlı ölçekler hata oluşturmaya ve çökmeye daha yatkındır.", + "AnisotropyTooltip": "Level of Anisotropic Filtering. Set to Auto to use the value requested by the game.", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.\n\nOnly change this if you're using an aspect ratio mod for your game, otherwise the graphics will be stretched.\n\nLeave on 16:9 if unsure.", + "ShaderDumpPathTooltip": "Grafik Shader Döküm Yolu", + "FileLogTooltip": "Konsol loglarını diskte bir log dosyasına kaydeder. Performansı etkilemez.", + "StubLogTooltip": "Stub log mesajlarını konsola yazdırır. Performansı etkilemez.", + "InfoLogTooltip": "Bilgi log mesajlarını konsola yazdırır. Performansı etkilemez.", + "WarnLogTooltip": "Uyarı log mesajlarını konsola yazdırır. Performansı etkilemez.", + "ErrorLogTooltip": "Hata log mesajlarını konsola yazdırır. Performansı etkilemez.", + "TraceLogTooltip": "Trace log mesajlarını konsola yazdırır. Performansı etkilemez.", + "GuestLogTooltip": "Guest log mesajlarını konsola yazdırır. Performansı etkilemez.", + "FileAccessLogTooltip": "Dosya sistemi erişim log mesajlarını konsola yazdırır.", + "FSAccessLogModeTooltip": "Konsola FS erişim loglarının yazılmasını etkinleştirir. Kullanılabilir modlar 0-3'tür", + "DeveloperOptionTooltip": "Dikkatli kullanın", + "OpenGlLogLevel": "Uygun log seviyesinin aktif olmasını gerektirir", + "DebugLogTooltip": "Debug log mesajlarını konsola yazdırır.\n\nBu seçeneği yalnızca geliştirici üyemiz belirtirse aktifleştirin, çünkü bu seçenek log dosyasını okumayı zorlaştırır ve emülatörün performansını düşürür.", + "LoadApplicationFileTooltip": "Switch ile uyumlu bir dosya yüklemek için dosya tarayıcısını açar", + "LoadApplicationFolderTooltip": "Switch ile uyumlu ayrıştırılmamış bir uygulama yüklemek için dosya tarayıcısını açar", + "OpenRyujinxFolderTooltip": "Ryujinx dosya sistem klasörünü açar", + "OpenRyujinxLogsTooltip": "Log dosyalarının bulunduğu klasörü açar", + "ExitTooltip": "Ryujinx'ten çıkış yapmayı sağlar", + "OpenSettingsTooltip": "Seçenekler penceresini açar", + "OpenProfileManagerTooltip": "Kullanıcı profil yöneticisi penceresini açar", + "StopEmulationTooltip": "Oynanmakta olan oyunun emülasyonunu durdurup oyun seçimine geri döndürür", + "CheckUpdatesTooltip": "Ryujinx güncellemelerini denetlemeyi sağlar", + "OpenAboutTooltip": "Hakkında penceresini açar", + "GridSize": "Öge Boyutu", + "GridSizeTooltip": "Grid ögelerinin boyutunu değiştirmeyi sağlar", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brezilya Portekizcesi", + "AboutRyujinxContributorsButtonHeader": "Tüm katkıda bulunanları gör", + "SettingsTabSystemAudioVolume": "Ses Seviyesi: ", + "AudioVolumeTooltip": "Ses seviyesini değiştirir", + "SettingsTabSystemEnableInternetAccess": "Guest Internet Erişimi/LAN Modu", + "EnableInternetAccessTooltip": "Emüle edilen uygulamanın internete bağlanmasını sağlar.\n\nLAN modu bulunan oyunlar bu seçenek ile birbirine bağlanabilir ve sistemler aynı access point'e bağlanır. Bu gerçek konsolları da kapsar.\n\nNintendo sunucularına bağlanmayı sağlaMAZ. Internete bağlanmaya çalışan baz oyunların çökmesine sebep olabilr.\n\nEmin değilseniz devre dışı bırakın.", + "GameListContextMenuManageCheatToolTip": "Hileleri yönetmeyi sağlar", + "GameListContextMenuManageCheat": "Hileleri Yönet", + "GameListContextMenuManageModToolTip": "Modları Yönet", + "GameListContextMenuManageMod": "Modları Yönet", + "ControllerSettingsStickRange": "Menzil:", + "DialogStopEmulationTitle": "Ryujinx - Emülasyonu Durdur", + "DialogStopEmulationMessage": "Emülasyonu durdurmak istediğinizden emin misiniz?", + "SettingsTabCpu": "İşlemci", + "SettingsTabAudio": "Ses", + "SettingsTabNetwork": "Ağ", + "SettingsTabNetworkConnection": "Ağ Bağlantısı", + "SettingsTabCpuCache": "İşlemci Belleği", + "SettingsTabCpuMemory": "CPU Hafızası", + "DialogUpdaterFlatpakNotSupportedMessage": "Lütfen Ryujinx'i FlatHub aracılığıyla güncelleyin.", + "UpdaterDisabledWarningTitle": "Güncelleyici Devre Dışı!", + "ControllerSettingsRotate90": "Saat yönünde 90° Döndür", + "IconSize": "Ikon Boyutu", + "IconSizeTooltip": "Oyun ikonlarının boyutunu değiştirmeyi sağlar", + "MenuBarOptionsShowConsole": "Konsol'u Göster", + "ShaderCachePurgeError": "Belirtilen shader cache temizlenirken hata {0}: {1}", + "UserErrorNoKeys": "Keys bulunamadı", + "UserErrorNoFirmware": "Firmware bulunamadı", + "UserErrorFirmwareParsingFailed": "Firmware çözümleme hatası", + "UserErrorApplicationNotFound": "Uygulama bulunamadı", + "UserErrorUnknown": "Bilinmeyen hata", + "UserErrorUndefined": "Tanımlanmayan hata", + "UserErrorNoKeysDescription": "Ryujinx 'prod.keys' dosyasını bulamadı", + "UserErrorNoFirmwareDescription": "Ryujinx yüklü herhangi firmware bulamadı", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx temin edilen firmware'i çözümleyemedi. Bu durum genellikle güncel olmayan keys'den kaynaklanır.", + "UserErrorApplicationNotFoundDescription": "Ryujinx belirtilen yolda geçerli bir uygulama bulamadı.", + "UserErrorUnknownDescription": "Bilinmeyen bir hata oluştu!", + "UserErrorUndefinedDescription": "Tanımlanmayan bir hata oluştu! Bu durum ile karşılaşılmamalıydı, lütfen bir geliştirici ile iletişime geçin!", + "OpenSetupGuideMessage": "Kurulum Kılavuzunu Aç", + "NoUpdate": "Güncelleme Yok", + "TitleUpdateVersionLabel": "Sürüm {0} - {1}", + "RyujinxInfo": "Ryujinx - Bilgi", + "RyujinxConfirm": "Ryujinx - Doğrulama", + "FileDialogAllTypes": "Tüm türler", + "Never": "Hiçbir Zaman", + "SwkbdMinCharacters": "En az {0} karakter uzunluğunda olmalı", + "SwkbdMinRangeCharacters": "{0}-{1} karakter uzunluğunda olmalı", + "SoftwareKeyboard": "Yazılım Klavyesi", + "SoftwareKeyboardModeNumeric": "Sadece 0-9 veya '.' olabilir", + "SoftwareKeyboardModeAlphabet": "Sadece CJK-characters olmayan karakterler olabilir", + "SoftwareKeyboardModeASCII": "Sadece ASCII karakterler olabilir", + "ControllerAppletControllers": "Desteklenen Kumandalar:", + "ControllerAppletPlayers": "Oyuncular:", + "ControllerAppletDescription": "Halihazırdaki konfigürasyonunuz geçersiz. Ayarları açın ve girişlerinizi yeniden konfigüre edin.", + "ControllerAppletDocked": "Docked mod ayarlandı. Portatif denetim devre dışı bırakılmalı.", + "UpdaterRenaming": "Eski dosyalar yeniden adlandırılıyor...", + "UpdaterRenameFailed": "Güncelleyici belirtilen dosyayı yeniden adlandıramadı: {0}", + "UpdaterAddingFiles": "Yeni Dosyalar Ekleniyor...", + "UpdaterExtracting": "Güncelleme Ayrıştırılıyor...", + "UpdaterDownloading": "Güncelleme İndiriliyor...", + "Game": "Oyun", + "Docked": "Docked", + "Handheld": "El tipi", + "ConnectionError": "Bağlantı Hatası.", + "AboutPageDeveloperListMore": "{0} ve daha fazla...", + "ApiError": "API Hatası.", + "LoadingHeading": "{0} Yükleniyor", + "CompilingPPTC": "PTC Derleniyor", + "CompilingShaders": "Shaderlar Derleniyor", + "AllKeyboards": "Tüm Klavyeler", + "OpenFileDialogTitle": "Açmak için desteklenen bir dosya seçin", + "OpenFolderDialogTitle": "Ayrıştırılmamış oyun içeren bir klasör seçin", + "AllSupportedFormats": "Tüm Desteklenen Formatlar", + "RyujinxUpdater": "Ryujinx Güncelleyicisi", + "SettingsTabHotkeys": "Klavye Kısayolları", + "SettingsTabHotkeysHotkeys": "Klavye Kısayolları", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync'i Etkinleştir/Devre Dışı Bırak:", + "SettingsTabHotkeysScreenshotHotkey": "Ekran Görüntüsü Al:", + "SettingsTabHotkeysShowUiHotkey": "Arayüzü Göster:", + "SettingsTabHotkeysPauseHotkey": "Durdur:", + "SettingsTabHotkeysToggleMuteHotkey": "Sustur:", + "ControllerMotionTitle": "Hareket Kontrol Seçenekleri", + "ControllerRumbleTitle": "Titreşim Seçenekleri", + "SettingsSelectThemeFileDialogTitle": "Tema Dosyası Seç", + "SettingsXamlThemeFile": "Xaml Tema Dosyası", + "AvatarWindowTitle": "Hesapları Yönet - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Bilinmeyen", + "Usage": "Kullanım", + "Writable": "Yazılabilir", + "SelectDlcDialogTitle": "DLC dosyalarını seç", + "SelectUpdateDialogTitle": "Güncelleme dosyalarını seç", + "SelectModDialogTitle": "Mod Dizinini Seç", + "UserProfileWindowTitle": "Kullanıcı Profillerini Yönet", + "CheatWindowTitle": "Oyun Hilelerini Yönet", + "DlcWindowTitle": "Oyun DLC'lerini Yönet", + "ModWindowTitle": "Manage Mods for {0} ({1})", + "UpdateWindowTitle": "Oyun Güncellemelerini Yönet", + "CheatWindowHeading": "{0} için Hile mevcut [{1}]", + "BuildId": "BuildId:", + "DlcWindowHeading": "{0} için DLC mevcut [{1}]", + "ModWindowHeading": "{0} Mod(lar)", + "UserProfilesEditProfile": "Seçiliyi Düzenle", + "Cancel": "İptal", + "Save": "Kaydet", + "Discard": "Iskarta", + "Paused": "Durduruldu", + "UserProfilesSetProfileImage": "Profil Resmi Ayarla", + "UserProfileEmptyNameError": "İsim gerekli", + "UserProfileNoImageError": "Profil resmi ayarlanmalıdır", + "GameUpdateWindowHeading": "{0} için güncellemeler mevcut [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Çözünürlüğü artır:", + "SettingsTabHotkeysResScaleDownHotkey": "Çözünürlüğü azalt:", + "UserProfilesName": "İsim:", + "UserProfilesUserId": "Kullanıcı Adı:", + "SettingsTabGraphicsBackend": "Grafik Arka Ucu", + "SettingsTabGraphicsBackendTooltip": "Select the graphics backend that will be used in the emulator.\n\nVulkan is overall better for all modern graphics cards, as long as their drivers are up to date. Vulkan also features faster shader compilation (less stuttering) on all GPU vendors.\n\nOpenGL may achieve better results on old Nvidia GPUs, on old AMD GPUs on Linux, or on GPUs with lower VRAM, though shader compilation stutters will be greater.\n\nSet to Vulkan if unsure. Set to OpenGL if your GPU does not support Vulkan even with the latest graphics drivers.", + "SettingsEnableTextureRecompression": "Yeniden Doku Sıkıştırılmasını Aktif Et", + "SettingsEnableTextureRecompressionTooltip": "Compresses ASTC textures in order to reduce VRAM usage.\n\nGames using this texture format include Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder and The Legend of Zelda: Tears of the Kingdom.\n\nGraphics cards with 4GiB VRAM or less will likely crash at some point while running these games.\n\nEnable only if you're running out of VRAM on the aforementioned games. Leave OFF if unsure.", + "SettingsTabGraphicsPreferredGpu": "Kullanılan GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan Grafik Arka Ucu ile kullanılacak Ekran Kartını Seçin.\n\nOpenGL'nin kullanacağı GPU'yu etkilemez.\n\n Emin değilseniz \"dGPU\" olarak işaretlenmiş GPU'ya ayarlayın. Eğer yoksa, dokunmadan bırakın.\n", + "SettingsAppRequiredRestartMessage": "Ryujinx'i Yeniden Başlatma Gerekli", + "SettingsGpuBackendRestartMessage": "Grafik Motoru ya da GPU ayarları değiştirildi. Bu işlemin uygulanması için yeniden başlatma gerekli.", + "SettingsGpuBackendRestartSubMessage": "Şimdi yeniden başlatmak istiyor musunuz?", + "RyujinxUpdaterMessage": "Ryujinx'i en son sürüme güncellemek ister misiniz?", + "SettingsTabHotkeysVolumeUpHotkey": "Sesi Arttır:", + "SettingsTabHotkeysVolumeDownHotkey": "Sesi Azalt:", + "SettingsEnableMacroHLE": "Macro HLE'yi Aktifleştir", + "SettingsEnableMacroHLETooltip": "GPU Macro kodunun yüksek seviye emülasyonu.\n\nPerformansı arttırır, ama bazı oyunlarda grafik hatalarına yol açabilir.\n\nEmin değilseniz AÇIK bırakın.", + "SettingsEnableColorSpacePassthrough": "Renk Alanı Geçişi", + "SettingsEnableColorSpacePassthroughTooltip": "Vulkan Backend'ini renk alanı belirtmeden renk bilgisinden geçmeye yönlendirir. Geniş gam ekranlı kullanıcılar için bu, renk doğruluğu pahasına daha canlı renklerle sonuçlanabilir.", + "VolumeShort": "Ses", + "UserProfilesManageSaves": "Kayıtları Yönet", + "DeleteUserSave": "Bu oyun için kullanıcı kaydını silmek istiyor musunuz?", + "IrreversibleActionNote": "Bu eylem geri alınamaz.", + "SaveManagerHeading": "{0} için Kayıt Dosyalarını Yönet", + "SaveManagerTitle": "Kayıt Yöneticisi", + "Name": "İsim", + "Size": "Boyut", + "Search": "Ara", + "UserProfilesRecoverLostAccounts": "Kayıp Hesapları Kurtar", + "Recover": "Kurtar", + "UserProfilesRecoverHeading": "Aşağıdaki hesaplar için kayıtlar bulundu", + "UserProfilesRecoverEmptyList": "Kurtarılacak profil bulunamadı", + "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", + "GraphicsAALabel": "Kenar Yumuşatma:", + "GraphicsScalingFilterLabel": "Ölçekleme Filtresi:", + "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", + "GraphicsScalingFilterBilinear": "Bilinear", + "GraphicsScalingFilterNearest": "Nearest", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "Seviye", + "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", + "SmaaLow": "Düşük SMAA", + "SmaaMedium": "Orta SMAA", + "SmaaHigh": "Yüksek SMAA", + "SmaaUltra": "En Yüksek SMAA", + "UserEditorTitle": "Kullanıcıyı Düzenle", + "UserEditorTitleCreate": "Kullanıcı Oluştur", + "SettingsTabNetworkInterface": "Ağ Bağlantısı:", + "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features.\n\nIn conjunction with a VPN or XLink Kai and a game with LAN support, can be used to spoof a same-network connection over the Internet.\n\nLeave on DEFAULT if unsure.", + "NetworkInterfaceDefault": "Varsayılan", + "PackagingShaders": "Gölgeler Paketleniyor", + "AboutChangelogButton": "GitHub'da Değişiklikleri Görüntüle", + "AboutChangelogButtonTooltipMessage": "Kullandığınız versiyon için olan değişiklikleri varsayılan tarayıcınızda görmek için tıklayın", + "SettingsTabNetworkMultiplayer": "Çok Oyunculu", + "MultiplayerMode": "Mod:", + "MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "MultiplayerModeDisabled": "Devre Dışı", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json new file mode 100644 index 00000000..976edfb1 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -0,0 +1,780 @@ +{ + "Language": "Українська", + "MenuBarFileOpenApplet": "Відкрити аплет", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Відкрити аплет Mii Editor в автономному режимі", + "SettingsTabInputDirectMouseAccess": "Прямий доступ мишею", + "SettingsTabSystemMemoryManagerMode": "Режим диспетчера пам’яті:", + "SettingsTabSystemMemoryManagerModeSoftware": "Програмне забезпечення", + "SettingsTabSystemMemoryManagerModeHost": "Хост (швидко)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Неперевірений хост (найшвидший, небезпечний)", + "SettingsTabSystemUseHypervisor": "Використовувати гіпервізор", + "MenuBarFile": "_Файл", + "MenuBarFileOpenFromFile": "_Завантажити програму з файлу", + "MenuBarFileOpenUnpacked": "Завантажити _розпаковану гру", + "MenuBarFileOpenEmuFolder": "Відкрити теку Ryujinx", + "MenuBarFileOpenLogsFolder": "Відкрити теку журналів змін", + "MenuBarFileExit": "_Вихід", + "MenuBarOptions": "_Параметри", + "MenuBarOptionsToggleFullscreen": "На весь екран", + "MenuBarOptionsStartGamesInFullscreen": "Запускати ігри на весь екран", + "MenuBarOptionsStopEmulation": "Зупинити емуляцію", + "MenuBarOptionsSettings": "_Налаштування", + "MenuBarOptionsManageUserProfiles": "_Керувати профілями користувачів", + "MenuBarActions": "_Дії", + "MenuBarOptionsSimulateWakeUpMessage": "Симулювати повідомлення про пробудження", + "MenuBarActionsScanAmiibo": "Сканувати Amiibo", + "MenuBarTools": "_Інструменти", + "MenuBarToolsInstallFirmware": "Установити прошивку", + "MenuBarFileToolsInstallFirmwareFromFile": "Установити прошивку з XCI або ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Установити прошивку з теки", + "MenuBarToolsManageFileTypes": "Керувати типами файлів", + "MenuBarToolsInstallFileTypes": "Установити типи файлів", + "MenuBarToolsUninstallFileTypes": "Видалити типи файлів", + "MenuBarView": "_View", + "MenuBarViewWindow": "Window Size", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "_Допомога", + "MenuBarHelpCheckForUpdates": "Перевірити оновлення", + "MenuBarHelpAbout": "Про застосунок", + "MenuSearch": "Пошук...", + "GameListHeaderFavorite": "Обране", + "GameListHeaderIcon": "Значок", + "GameListHeaderApplication": "Назва", + "GameListHeaderDeveloper": "Розробник", + "GameListHeaderVersion": "Версія", + "GameListHeaderTimePlayed": "Зіграно часу", + "GameListHeaderLastPlayed": "Востаннє зіграно", + "GameListHeaderFileExtension": "Розширення файлу", + "GameListHeaderFileSize": "Розмір файлу", + "GameListHeaderPath": "Шлях", + "GameListContextMenuOpenUserSaveDirectory": "Відкрити теку збереження користувача", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Відкриває каталог, який містить збереження користувача програми", + "GameListContextMenuOpenDeviceSaveDirectory": "Відкрити каталог пристроїв користувача", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Відкриває каталог, який містить збереження пристрою програми", + "GameListContextMenuOpenBcatSaveDirectory": "Відкрити каталог користувача BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Відкриває каталог, який містить BCAT-збереження програми", + "GameListContextMenuManageTitleUpdates": "Керування оновленнями заголовків", + "GameListContextMenuManageTitleUpdatesToolTip": "Відкриває вікно керування оновленням заголовка", + "GameListContextMenuManageDlc": "Керування DLC", + "GameListContextMenuManageDlcToolTip": "Відкриває вікно керування DLC", + "GameListContextMenuCacheManagement": "Керування кешем", + "GameListContextMenuCacheManagementPurgePptc": "Очистити кеш PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Видаляє кеш PPTC програми", + "GameListContextMenuCacheManagementPurgeShaderCache": "Очистити кеш шейдерів", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Видаляє кеш шейдерів програми", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Відкрити каталог PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Відкриває каталог, який містить кеш PPTC програми", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Відкрити каталог кешу шейдерів", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Відкриває каталог, який містить кеш шейдерів програми", + "GameListContextMenuExtractData": "Видобути дані", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Видобуває розділ ExeFS із поточної конфігурації програми (включаючи оновлення)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Видобуває розділ RomFS із поточної конфігурації програми (включаючи оновлення)", + "GameListContextMenuExtractDataLogo": "Логотип", + "GameListContextMenuExtractDataLogoToolTip": "Видобуває розділ логотипу з поточної конфігурації програми (включаючи оновлення)", + "GameListContextMenuCreateShortcut": "Створити ярлик застосунку", + "GameListContextMenuCreateShortcutToolTip": "Створити ярлик на робочому столі, який запускає вибраний застосунок", + "GameListContextMenuCreateShortcutToolTipMacOS": "Створити ярлик у каталозі macOS програм, що запускає обраний Додаток", + "GameListContextMenuOpenModsDirectory": "Відкрити теку з модами", + "GameListContextMenuOpenModsDirectoryToolTip": "Відкриває каталог, який містить модифікації Додатків", + "GameListContextMenuOpenSdModsDirectory": "Відкрити каталог модифікацій Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Відкриває альтернативний каталог SD-карти Atmosphere, що містить модифікації Додатків. Корисно для модифікацій, зроблених для реального обладнання.", + "StatusBarGamesLoaded": "{0}/{1} ігор завантажено", + "StatusBarSystemVersion": "Версія системи: {0}", + "LinuxVmMaxMapCountDialogTitle": "Виявлено низьку межу для відображення памʼяті", + "LinuxVmMaxMapCountDialogTextPrimary": "Бажаєте збільшити значення vm.max_map_count на {0}", + "LinuxVmMaxMapCountDialogTextSecondary": "Деякі ігри можуть спробувати створити більше відображень памʼяті, ніж дозволено наразі. Ryujinx завершить роботу, щойно цей ліміт буде перевищено.", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "Так, до наст. перезапуску", + "LinuxVmMaxMapCountDialogButtonPersistent": "Так, назавжди", + "LinuxVmMaxMapCountWarningTextPrimary": "Максимальна кількість відображення памʼяті менша, ніж рекомендовано.", + "LinuxVmMaxMapCountWarningTextSecondary": "Поточне значення vm.max_map_count ({0}) менше за {1}. Деякі ігри можуть спробувати створити більше відображень пам’яті, ніж дозволено наразі. Ryujinx завершить роботу, щойно цей ліміт буде перевищено.\n\nВи можете збільшити ліміт вручну або встановити pkexec, який дозволяє Ryujinx допомогти з цим.", + "Settings": "Налаштування", + "SettingsTabGeneral": "Інтерфейс користувача", + "SettingsTabGeneralGeneral": "Загальні", + "SettingsTabGeneralEnableDiscordRichPresence": "Увімкнути розширену присутність Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Перевіряти наявність оновлень під час запуску", + "SettingsTabGeneralShowConfirmExitDialog": "Показати діалогове вікно «Підтвердити вихід».", + "SettingsTabGeneralRememberWindowState": "Remember Window Size/Position", + "SettingsTabGeneralHideCursor": "Сховати вказівник:", + "SettingsTabGeneralHideCursorNever": "Ніколи", + "SettingsTabGeneralHideCursorOnIdle": "Сховати у режимі очікування", + "SettingsTabGeneralHideCursorAlways": "Завжди", + "SettingsTabGeneralGameDirectories": "Тека ігор", + "SettingsTabGeneralAdd": "Додати", + "SettingsTabGeneralRemove": "Видалити", + "SettingsTabSystem": "Система", + "SettingsTabSystemCore": "Ядро", + "SettingsTabSystemSystemRegion": "Регіон системи:", + "SettingsTabSystemSystemRegionJapan": "Японія", + "SettingsTabSystemSystemRegionUSA": "США", + "SettingsTabSystemSystemRegionEurope": "Європа", + "SettingsTabSystemSystemRegionAustralia": "Австралія", + "SettingsTabSystemSystemRegionChina": "Китай", + "SettingsTabSystemSystemRegionKorea": "Корея", + "SettingsTabSystemSystemRegionTaiwan": "Тайвань", + "SettingsTabSystemSystemLanguage": "Мова системи:", + "SettingsTabSystemSystemLanguageJapanese": "Японська", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Англійська (США)", + "SettingsTabSystemSystemLanguageFrench": "Французька", + "SettingsTabSystemSystemLanguageGerman": "Німецька", + "SettingsTabSystemSystemLanguageItalian": "Італійська", + "SettingsTabSystemSystemLanguageSpanish": "Іспанська", + "SettingsTabSystemSystemLanguageChinese": "Китайська", + "SettingsTabSystemSystemLanguageKorean": "Корейська", + "SettingsTabSystemSystemLanguageDutch": "Нідерландська", + "SettingsTabSystemSystemLanguagePortuguese": "Португальська", + "SettingsTabSystemSystemLanguageRussian": "Російська", + "SettingsTabSystemSystemLanguageTaiwanese": "Тайванська", + "SettingsTabSystemSystemLanguageBritishEnglish": "Англійська (Великобританія)", + "SettingsTabSystemSystemLanguageCanadianFrench": "Французька (Канада)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Іспанська (Латиноамериканська)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Спрощена китайська", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Традиційна китайська", + "SettingsTabSystemSystemTimeZone": "Часовий пояс системи:", + "SettingsTabSystemSystemTime": "Час системи:", + "SettingsTabSystemEnableVsync": "Вертикальна синхронізація", + "SettingsTabSystemEnablePptc": "PPTC (профільований постійний кеш перекладу)", + "SettingsTabSystemEnableFsIntegrityChecks": "Перевірка цілісності FS", + "SettingsTabSystemAudioBackend": "Аудіосистема:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Хитрощі", + "SettingsTabSystemHacksNote": " (може викликати нестабільність)", + "SettingsTabSystemExpandDramSize": "Використовувати альтернативне розташування пам'яті (розробники)", + "SettingsTabSystemIgnoreMissingServices": "Ігнорувати відсутні служби", + "SettingsTabGraphics": "Графіка", + "SettingsTabGraphicsAPI": "Графічний API", + "SettingsTabGraphicsEnableShaderCache": "Увімкнути кеш шейдерів", + "SettingsTabGraphicsAnisotropicFiltering": "Анізотропна фільтрація:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Авто", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Роздільна здатність:", + "SettingsTabGraphicsResolutionScaleCustom": "Користувацька (не рекомендовано)", + "SettingsTabGraphicsResolutionScaleNative": "Стандартний (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p) (Не рекомендується)", + "SettingsTabGraphicsAspectRatio": "Співвідношення сторін:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Розтягнути до розміру вікна", + "SettingsTabGraphicsDeveloperOptions": "Параметри розробника", + "SettingsTabGraphicsShaderDumpPath": "Шлях скидання графічного шейдера:", + "SettingsTabLogging": "Налагодження", + "SettingsTabLoggingLogging": "Налагодження", + "SettingsTabLoggingEnableLoggingToFile": "Увімкнути налагодження у файл", + "SettingsTabLoggingEnableStubLogs": "Увімкнути журнали заглушки", + "SettingsTabLoggingEnableInfoLogs": "Увімкнути інформаційні журнали", + "SettingsTabLoggingEnableWarningLogs": "Увімкнути журнали попереджень", + "SettingsTabLoggingEnableErrorLogs": "Увімкнути журнали помилок", + "SettingsTabLoggingEnableTraceLogs": "Увімкнути журнали трасування", + "SettingsTabLoggingEnableGuestLogs": "Увімкнути журнали гостей", + "SettingsTabLoggingEnableFsAccessLogs": "Увімкнути журнали доступу Fs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журналу глобального доступу Fs:", + "SettingsTabLoggingDeveloperOptions": "Параметри розробника (УВАГА: знизиться продуктивність)", + "SettingsTabLoggingDeveloperOptionsNote": "УВАГА: Знижує продуктивність", + "SettingsTabLoggingGraphicsBackendLogLevel": "Рівень журналу графічного сервера:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Немає", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Помилка", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Уповільнення", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Все", + "SettingsTabLoggingEnableDebugLogs": "Увімкнути журнали налагодження", + "SettingsTabInput": "Введення", + "SettingsTabInputEnableDockedMode": "Режим док-станції", + "SettingsTabInputDirectKeyboardAccess": "Прямий доступ з клавіатури", + "SettingsButtonSave": "Зберегти", + "SettingsButtonClose": "Закрити", + "SettingsButtonOk": "Гаразд", + "SettingsButtonCancel": "Скасувати", + "SettingsButtonApply": "Застосувати", + "ControllerSettingsPlayer": "Гравець", + "ControllerSettingsPlayer1": "Гравець 1", + "ControllerSettingsPlayer2": "Гравець 2", + "ControllerSettingsPlayer3": "Гравець 3", + "ControllerSettingsPlayer4": "Гравець 4", + "ControllerSettingsPlayer5": "Гравець 5", + "ControllerSettingsPlayer6": "Гравець 6", + "ControllerSettingsPlayer7": "Гравець 7", + "ControllerSettingsPlayer8": "Гравець 8", + "ControllerSettingsHandheld": "Портативний", + "ControllerSettingsInputDevice": "Пристрій введення", + "ControllerSettingsRefresh": "Оновити", + "ControllerSettingsDeviceDisabled": "Вимкнено", + "ControllerSettingsControllerType": "Тип контролера", + "ControllerSettingsControllerTypeHandheld": "Портативний", + "ControllerSettingsControllerTypeProController": "Контролер Pro", + "ControllerSettingsControllerTypeJoyConPair": "Обидва JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "Лівий JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "Правий JoyCon", + "ControllerSettingsProfile": "Профіль", + "ControllerSettingsProfileDefault": "Типовий", + "ControllerSettingsLoad": "Завантажити", + "ControllerSettingsAdd": "Додати", + "ControllerSettingsRemove": "Видалити", + "ControllerSettingsButtons": "Кнопки", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Панель направлення", + "ControllerSettingsDPadUp": "Вгору", + "ControllerSettingsDPadDown": "Вниз", + "ControllerSettingsDPadLeft": "Вліво", + "ControllerSettingsDPadRight": "Вправо", + "ControllerSettingsStickButton": "Кнопка", + "ControllerSettingsStickUp": "Уверх", + "ControllerSettingsStickDown": "Униз", + "ControllerSettingsStickLeft": "Ліворуч", + "ControllerSettingsStickRight": "Праворуч", + "ControllerSettingsStickStick": "Стик", + "ControllerSettingsStickInvertXAxis": "Обернути вісь стику X", + "ControllerSettingsStickInvertYAxis": "Обернути вісь стику Y", + "ControllerSettingsStickDeadzone": "Мертва зона:", + "ControllerSettingsLStick": "Лівий джойстик", + "ControllerSettingsRStick": "Правий джойстик", + "ControllerSettingsTriggersLeft": "Тригери ліворуч", + "ControllerSettingsTriggersRight": "Тригери праворуч", + "ControllerSettingsTriggersButtonsLeft": "Кнопки тригерів ліворуч", + "ControllerSettingsTriggersButtonsRight": "Кнопки тригерів праворуч", + "ControllerSettingsTriggers": "Тригери", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Кнопки ліворуч", + "ControllerSettingsExtraButtonsRight": "Кнопки праворуч", + "ControllerSettingsMisc": "Різне", + "ControllerSettingsTriggerThreshold": "Поріг спрацьовування:", + "ControllerSettingsMotion": "Рух", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Використовувати рух, сумісний з CemuHook", + "ControllerSettingsMotionControllerSlot": "Слот контролера:", + "ControllerSettingsMotionMirrorInput": "Дзеркальний вхід", + "ControllerSettingsMotionRightJoyConSlot": "Правий слот JoyCon:", + "ControllerSettingsMotionServerHost": "Хост сервера:", + "ControllerSettingsMotionGyroSensitivity": "Чутливість гіроскопа:", + "ControllerSettingsMotionGyroDeadzone": "Мертва зона гіроскопа:", + "ControllerSettingsSave": "Зберегти", + "ControllerSettingsClose": "Закрити", + "KeyUnknown": "Unknown", + "KeyShiftLeft": "Shift Left", + "KeyShiftRight": "Shift Right", + "KeyControlLeft": "Ctrl Left", + "KeyMacControlLeft": "⌃ Left", + "KeyControlRight": "Ctrl Right", + "KeyMacControlRight": "⌃ Right", + "KeyAltLeft": "Alt Left", + "KeyMacAltLeft": "⌥ Left", + "KeyAltRight": "Alt Right", + "KeyMacAltRight": "⌥ Right", + "KeyWinLeft": "⊞ Left", + "KeyMacWinLeft": "⌘ Left", + "KeyWinRight": "⊞ Right", + "KeyMacWinRight": "⌘ Right", + "KeyMenu": "Menu", + "KeyUp": "Up", + "KeyDown": "Down", + "KeyLeft": "Left", + "KeyRight": "Right", + "KeyEnter": "Enter", + "KeyEscape": "Escape", + "KeySpace": "Space", + "KeyTab": "Tab", + "KeyBackSpace": "Backspace", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "Clear", + "KeyKeypad0": "Keypad 0", + "KeyKeypad1": "Keypad 1", + "KeyKeypad2": "Keypad 2", + "KeyKeypad3": "Keypad 3", + "KeyKeypad4": "Keypad 4", + "KeyKeypad5": "Keypad 5", + "KeyKeypad6": "Keypad 6", + "KeyKeypad7": "Keypad 7", + "KeyKeypad8": "Keypad 8", + "KeyKeypad9": "Keypad 9", + "KeyKeypadDivide": "Keypad Divide", + "KeyKeypadMultiply": "Keypad Multiply", + "KeyKeypadSubtract": "Keypad Subtract", + "KeyKeypadAdd": "Keypad Add", + "KeyKeypadDecimal": "Keypad Decimal", + "KeyKeypadEnter": "Keypad Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "Unbound", + "GamepadLeftStick": "L Stick Button", + "GamepadRightStick": "R Stick Button", + "GamepadLeftShoulder": "Left Shoulder", + "GamepadRightShoulder": "Right Shoulder", + "GamepadLeftTrigger": "Left Trigger", + "GamepadRightTrigger": "Right Trigger", + "GamepadDpadUp": "Up", + "GamepadDpadDown": "Down", + "GamepadDpadLeft": "Left", + "GamepadDpadRight": "Right", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "Guide", + "GamepadMisc1": "Misc", + "GamepadPaddle1": "Paddle 1", + "GamepadPaddle2": "Paddle 2", + "GamepadPaddle3": "Paddle 3", + "GamepadPaddle4": "Paddle 4", + "GamepadTouchpad": "Touchpad", + "GamepadSingleLeftTrigger0": "Left Trigger 0", + "GamepadSingleRightTrigger0": "Right Trigger 0", + "GamepadSingleLeftTrigger1": "Left Trigger 1", + "GamepadSingleRightTrigger1": "Right Trigger 1", + "StickLeft": "Left Stick", + "StickRight": "Right Stick", + "UserProfilesSelectedUserProfile": "Вибраний профіль користувача:", + "UserProfilesSaveProfileName": "Зберегти ім'я профілю", + "UserProfilesChangeProfileImage": "Змінити зображення профілю", + "UserProfilesAvailableUserProfiles": "Доступні профілі користувачів:", + "UserProfilesAddNewProfile": "Створити профіль", + "UserProfilesDelete": "Видалити", + "UserProfilesClose": "Закрити", + "ProfileNameSelectionWatermark": "Оберіть псевдонім", + "ProfileImageSelectionTitle": "Вибір зображення профілю", + "ProfileImageSelectionHeader": "Виберіть зображення профілю", + "ProfileImageSelectionNote": "Ви можете імпортувати власне зображення профілю або вибрати аватар із мікропрограми системи", + "ProfileImageSelectionImportImage": "Імпорт файлу зображення", + "ProfileImageSelectionSelectAvatar": "Виберіть аватар прошивки ", + "InputDialogTitle": "Діалог введення", + "InputDialogOk": "Гаразд", + "InputDialogCancel": "Скасувати", + "InputDialogAddNewProfileTitle": "Виберіть ім'я профілю", + "InputDialogAddNewProfileHeader": "Будь ласка, введіть ім'я профілю", + "InputDialogAddNewProfileSubtext": "(Макс. довжина: {0})", + "AvatarChoose": "Вибрати", + "AvatarSetBackgroundColor": "Встановити колір фону", + "AvatarClose": "Закрити", + "ControllerSettingsLoadProfileToolTip": "Завантажити профіль", + "ControllerSettingsAddProfileToolTip": "Додати профіль", + "ControllerSettingsRemoveProfileToolTip": "Видалити профіль", + "ControllerSettingsSaveProfileToolTip": "Зберегти профіль", + "MenuBarFileToolsTakeScreenshot": "Зробити знімок екрана", + "MenuBarFileToolsHideUi": "Сховати інтерфейс", + "GameListContextMenuRunApplication": "Запустити додаток", + "GameListContextMenuToggleFavorite": "Перемкнути вибране", + "GameListContextMenuToggleFavoriteToolTip": "Перемкнути улюблений статус гри", + "SettingsTabGeneralTheme": "Тема:", + "SettingsTabGeneralThemeDark": "Темна", + "SettingsTabGeneralThemeLight": "Світла", + "ControllerSettingsConfigureGeneral": "Налаштування", + "ControllerSettingsRumble": "Вібрація", + "ControllerSettingsRumbleStrongMultiplier": "Множник сильної вібрації", + "ControllerSettingsRumbleWeakMultiplier": "Множник слабкої вібрації", + "DialogMessageSaveNotAvailableMessage": "Немає збережених даних для {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Хочете створити дані збереження для цієї гри?", + "DialogConfirmationTitle": "Ryujinx - Підтвердження", + "DialogUpdaterTitle": "Ryujinx - Програма оновлення", + "DialogErrorTitle": "Ryujinx - Помилка", + "DialogWarningTitle": "Ryujinx - Попередження", + "DialogExitTitle": "Ryujinx - Вихід", + "DialogErrorMessage": "У Ryujinx сталася помилка", + "DialogExitMessage": "Ви впевнені, що бажаєте закрити Ryujinx?", + "DialogExitSubMessage": "Усі незбережені дані буде втрачено!", + "DialogMessageCreateSaveErrorMessage": "Під час створення вказаних даних збереження сталася помилка: {0}", + "DialogMessageFindSaveErrorMessage": "Під час пошуку вказаних даних збереження сталася помилка: {0}", + "FolderDialogExtractTitle": "Виберіть папку для видобування", + "DialogNcaExtractionMessage": "Видобування розділу {0} з {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Екстрактор розділів NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Помилка видобування. Основний NCA не був присутній у вибраному файлі.", + "DialogNcaExtractionCheckLogErrorMessage": "Помилка видобування. Прочитайте файл журналу для отримання додаткової інформації.", + "DialogNcaExtractionSuccessMessage": "Видобування успішно завершено.", + "DialogUpdaterConvertFailedMessage": "Не вдалося конвертувати поточну версію Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Скасування оновлення!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Ви вже використовуєте останню версію Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Під час спроби отримати інформацію про випуск із GitHub Release сталася помилка. Це може бути спричинено, якщо новий випуск компілюється GitHub Actions. Повторіть спробу через кілька хвилин.", + "DialogUpdaterConvertFailedGithubMessage": "Не вдалося конвертувати отриману версію Ryujinx із випуску Github.", + "DialogUpdaterDownloadingMessage": "Завантаження оновлення...", + "DialogUpdaterExtractionMessage": "Видобування оновлення...", + "DialogUpdaterRenamingMessage": "Перейменування оновлення...", + "DialogUpdaterAddingFilesMessage": "Додавання нового оновлення...", + "DialogUpdaterCompleteMessage": "Оновлення завершено!", + "DialogUpdaterRestartMessage": "Перезапустити Ryujinx зараз?", + "DialogUpdaterNoInternetMessage": "Ви не підключені до Інтернету!", + "DialogUpdaterNoInternetSubMessage": "Будь ласка, переконайтеся, що у вас є робоче підключення до Інтернету!", + "DialogUpdaterDirtyBuildMessage": "Ви не можете оновити брудну збірку Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Будь ласка, завантажте Ryujinx на https://ryujinx.org/, якщо ви шукаєте підтримувану версію.", + "DialogRestartRequiredMessage": "Потрібен перезапуск", + "DialogThemeRestartMessage": "Тему збережено. Щоб застосувати тему, потрібен перезапуск.", + "DialogThemeRestartSubMessage": "Ви хочете перезапустити", + "DialogFirmwareInstallEmbeddedMessage": "Бажаєте встановити прошивку, вбудовану в цю гру? (Прошивка {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Встановлену прошивку не знайдено, але Ryujinx вдалося встановити прошивку {0} з наданої гри.\nТепер запуститься емулятор.", + "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не встановлена", + "DialogFirmwareInstalledMessage": "Встановлено прошивку {0}", + "DialogInstallFileTypesSuccessMessage": "Успішно встановлено типи файлів!", + "DialogInstallFileTypesErrorMessage": "Не вдалося встановити типи файлів.", + "DialogUninstallFileTypesSuccessMessage": "Успішно видалено типи файлів!", + "DialogUninstallFileTypesErrorMessage": "Не вдалося видалити типи файлів.", + "DialogOpenSettingsWindowLabel": "Відкрити вікно налаштувань", + "DialogControllerAppletTitle": "Аплет контролера", + "DialogMessageDialogErrorExceptionMessage": "Помилка показу діалогового вікна повідомлення: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Помилка показу програмної клавіатури: {0}", + "DialogErrorAppletErrorExceptionMessage": "Помилка показу діалогового вікна ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nДля отримання додаткової інформації про те, як виправити цю помилку, дотримуйтесь нашого посібника з налаштування.", + "DialogUserErrorDialogTitle": "Помилка Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Під час отримання інформації з API сталася помилка.", + "DialogAmiiboApiConnectErrorMessage": "Неможливо підключитися до сервера Amiibo API. Можливо, служба не працює або вам потрібно перевірити, чи є підключення до Інтернету.", + "DialogProfileInvalidProfileErrorMessage": "Профіль {0} несумісний із поточною системою конфігурації вводу.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Стандартний профіль не можна перезаписати", + "DialogProfileDeleteProfileTitle": "Видалення профілю", + "DialogProfileDeleteProfileMessage": "Цю дію неможливо скасувати. Ви впевнені, що бажаєте продовжити?", + "DialogWarning": "Увага", + "DialogPPTCDeletionMessage": "Ви збираєтеся видалити кеш PPTC для:\n\n{0}\n\nВи впевнені, що бажаєте продовжити?", + "DialogPPTCDeletionErrorMessage": "Помилка очищення кешу PPTC на {0}: {1}", + "DialogShaderDeletionMessage": "Ви збираєтеся видалити кеш шейдерів для:\n\n{0}\n\nВи впевнені, що бажаєте продовжити?", + "DialogShaderDeletionErrorMessage": "Помилка очищення кешу шейдерів на {0}: {1}", + "DialogRyujinxErrorMessage": "У Ryujinx сталася помилка", + "DialogInvalidTitleIdErrorMessage": "Помилка інтерфейсу: вибрана гра не мала дійсного ідентифікатора назви", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Дійсна прошивка системи не знайдена в {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Встановити прошивку {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Буде встановлено версію системи {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЦе замінить поточну версію системи {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nВи хочете продовжити?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Встановлення прошивки...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версію системи {0} успішно встановлено.", + "DialogUserProfileDeletionWarningMessage": "Якщо вибраний профіль буде видалено, інші профілі не відкриватимуться", + "DialogUserProfileDeletionConfirmMessage": "Ви хочете видалити вибраний профіль", + "DialogUserProfileUnsavedChangesTitle": "Увага — Незбережені зміни", + "DialogUserProfileUnsavedChangesMessage": "Ви зробили зміни у цьому профілю користувача які не було збережено.", + "DialogUserProfileUnsavedChangesSubMessage": "Бажаєте скасувати зміни?", + "DialogControllerSettingsModifiedConfirmMessage": "Поточні налаштування контролера оновлено.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Ви хочете зберегти?", + "DialogLoadFileErrorMessage": "{0}. Файл з помилкою: {1}", + "DialogModAlreadyExistsMessage": "Модифікація вже існує", + "DialogModInvalidMessage": "Вказаний каталог не містить модифікації!", + "DialogModDeleteNoParentMessage": "Не видалено: Не знайдено батьківський каталог для модифікації \"{0}\"!", + "DialogDlcNoDlcErrorMessage": "Зазначений файл не містить DLC для вибраного заголовку!", + "DialogPerformanceCheckLoggingEnabledMessage": "Ви увімкнули журнал налагодження, призначений лише для розробників.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути ведення журналу налагодження. Ви хочете вимкнути ведення журналу налагодження зараз?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Ви увімкнули скидання шейдерів, призначений лише для розробників.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути скидання шейдерів. Ви хочете вимкнути скидання шейдерів зараз?", + "DialogLoadAppGameAlreadyLoadedMessage": "Гру вже завантажено", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Зупиніть емуляцію або закрийте емулятор перед запуском іншої гри.", + "DialogUpdateAddUpdateErrorMessage": "Зазначений файл не містить оновлення для вибраного заголовка!", + "DialogSettingsBackendThreadingWarningTitle": "Попередження - потокове керування сервером", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx потрібно перезапустити після зміни цього параметра, щоб він застосовувався повністю. Залежно від вашої платформи вам може знадобитися вручну вимкнути власну багатопотоковість драйвера під час використання Ryujinx.", + "DialogModManagerDeletionWarningMessage": "Ви збираєтесь видалити модифікацію: {0}\n\nВи дійсно бажаєте продовжити?", + "DialogModManagerDeletionAllWarningMessage": "Ви збираєтесь видалити всі модифікації для цього Додатка.\n\nВи дійсно бажаєте продовжити?", + "SettingsTabGraphicsFeaturesOptions": "Особливості", + "SettingsTabGraphicsBackendMultithreading": "Багатопотоковість графічного сервера:", + "CommonAuto": "Авто", + "CommonOff": "Вимкнути", + "CommonOn": "Увімкнути", + "InputDialogYes": "Так", + "InputDialogNo": "Ні", + "DialogProfileInvalidProfileNameErrorMessage": "Ім'я файлу містить неприпустимі символи. Будь ласка, спробуйте ще раз.", + "MenuBarOptionsPauseEmulation": "Пауза", + "MenuBarOptionsResumeEmulation": "Продовжити", + "AboutUrlTooltipMessage": "Натисніть, щоб відкрити сайт Ryujinx у браузері за замовчування.", + "AboutDisclaimerMessage": "Ryujinx жодним чином не пов’язано з Nintendo™,\nчи будь-яким із їхніх партнерів.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) використовується в нашій емуляції Amiibo.", + "AboutPatreonUrlTooltipMessage": "Натисніть, щоб відкрити сторінку Patreon Ryujinx у вашому браузері за замовчування.", + "AboutGithubUrlTooltipMessage": "Натисніть, щоб відкрити сторінку GitHub Ryujinx у браузері за замовчуванням.", + "AboutDiscordUrlTooltipMessage": "Натисніть, щоб відкрити запрошення на сервер Discord Ryujinx у браузері за замовчуванням.", + "AboutTwitterUrlTooltipMessage": "Натисніть, щоб відкрити сторінку Twitter Ryujinx у браузері за замовчуванням.", + "AboutRyujinxAboutTitle": "Про програму:", + "AboutRyujinxAboutContent": "Ryujinx — це емулятор для Nintendo Switch™.\nБудь ласка, підтримайте нас на Patreon.\nОтримуйте всі останні новини в нашому Twitter або Discord.\nРозробники, які хочуть зробити внесок, можуть дізнатися більше на нашому GitHub або в Discord.", + "AboutRyujinxMaintainersTitle": "Підтримується:", + "AboutRyujinxMaintainersContentTooltipMessage": "Натисніть, щоб відкрити сторінку співавторів у вашому браузері за замовчування.", + "AboutRyujinxSupprtersTitle": "Підтримується на Patreon:", + "AmiiboSeriesLabel": "Серія Amiibo", + "AmiiboCharacterLabel": "Персонаж", + "AmiiboScanButtonLabel": "Сканувати", + "AmiiboOptionsShowAllLabel": "Показати всі Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Хитрість: Використовувати випадковий тег Uuid", + "DlcManagerTableHeadingEnabledLabel": "Увімкнено", + "DlcManagerTableHeadingTitleIdLabel": "ID заголовка", + "DlcManagerTableHeadingContainerPathLabel": "Шлях до контейнеру", + "DlcManagerTableHeadingFullPathLabel": "Повний шлях", + "DlcManagerRemoveAllButton": "Видалити все", + "DlcManagerEnableAllButton": "Увімкнути всі", + "DlcManagerDisableAllButton": "Вимкнути всі", + "ModManagerDeleteAllButton": "Видалити все", + "MenuBarOptionsChangeLanguage": "Змінити мову", + "MenuBarShowFileTypes": "Показати типи файлів", + "CommonSort": "Сортувати", + "CommonShowNames": "Показати назви", + "CommonFavorite": "Вибрані", + "OrderAscending": "За зростанням", + "OrderDescending": "За спаданням", + "SettingsTabGraphicsFeatures": "Функції та вдосконалення", + "ErrorWindowTitle": "Вікно помилок", + "ToggleDiscordTooltip": "Виберіть, чи відображати Ryujinx у вашій «поточній грі» в Discord", + "AddGameDirBoxTooltip": "Введіть каталог ігор, щоб додати до списку", + "AddGameDirTooltip": "Додати каталог гри до списку", + "RemoveGameDirTooltip": "Видалити вибраний каталог гри", + "CustomThemeCheckTooltip": "Використовуйте користувацьку тему Avalonia для графічного інтерфейсу, щоб змінити вигляд меню емулятора", + "CustomThemePathTooltip": "Шлях до користувацької теми графічного інтерфейсу", + "CustomThemeBrowseTooltip": "Огляд користувацької теми графічного інтерфейсу", + "DockModeToggleTooltip": "У режимі док-станції емульована система веде себе як приєднаний Nintendo Switch. Це покращує точність графіки в більшості ігор. І навпаки, вимкнення цього призведе до того, що емульована система поводитиметься як портативний комутатор Nintendo, погіршуючи якість графіки.\n\nНалаштуйте елементи керування для гравця 1, якщо плануєте використовувати режим док-станції; налаштуйте ручні елементи керування, якщо плануєте використовувати портативний режим.\n\nЗалиште увімкненим, якщо не впевнені.", + "DirectKeyboardTooltip": "Підтримка прямого доступу до клавіатури (HID). Надає іграм доступ до клавіатури для вводу тексту.\n\nПрацює тільки з іграми, які підтримують клавіатуру на обладнанні Switch.\n\nЗалиште вимкненим, якщо не впевнені.", + "DirectMouseTooltip": "Підтримка прямого доступу до миші (HID). Надає іграм доступ до миші, як пристрій вказування.\n\nПрацює тільки з іграми, які підтримують мишу на обладнанні Switch, їх небагато.\n\nФункціонал сенсорного екрана може не працювати, якщо функція ввімкнена.\n\nЗалиште вимкненим, якщо не впевнені.", + "RegionTooltip": "Змінити регіон системи", + "LanguageTooltip": "Змінити мову системи", + "TimezoneTooltip": "Змінити часовий пояс системи", + "TimeTooltip": "Змінити час системи", + "VSyncToggleTooltip": "Емульована вертикальна синхронізація консолі. По суті, обмежувач кадрів для більшості ігор; його вимкнення може призвести до того, що ігри працюватимуть на вищій швидкості, екрани завантаження триватимуть довше чи зупинятимуться.\n\nМожна перемикати в грі гарячою клавішею (За умовчанням F1). Якщо ви плануєте вимкнути функцію, рекомендуємо зробити це через гарячу клавішу.\n\nЗалиште увімкненим, якщо не впевнені.", + "PptcToggleTooltip": "Зберігає перекладені функції JIT, щоб їх не потрібно було перекладати кожного разу, коли гра завантажується.\n\nЗменшує заїкання та значно прискорює час завантаження після першого завантаження гри.\n\nЗалиште увімкненим, якщо не впевнені.", + "FsIntegrityToggleTooltip": "Перевіряє наявність пошкоджених файлів під час завантаження гри, і якщо виявлено пошкоджені файли, показує помилку хешу в журналі.\n\nНе впливає на продуктивність і призначений для усунення несправностей.\n\nЗалиште увімкненим, якщо не впевнені.", + "AudioBackendTooltip": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL2 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL2, якщо не впевнені.", + "MemoryManagerTooltip": "Змінює спосіб відображення та доступу до гостьової пам’яті. Значно впливає на продуктивність емульованого ЦП.\n\nВстановіть «Неперевірений хост», якщо не впевнені.", + "MemoryManagerSoftwareTooltip": "Використовує програмну таблицю сторінок для перекладу адрес. Найвища точність, але найповільніша продуктивність.", + "MemoryManagerHostTooltip": "Пряме відображення пам'яті в адресному просторі хосту. Набагато швидша компіляція та виконання JIT.", + "MemoryManagerUnsafeTooltip": "Пряме відображення пам’яті, але не маскує адресу в гостьовому адресному просторі перед доступом. Швидше, але ціною безпеки. Гостьова програма може отримати доступ до пам’яті з будь-якого місця в Ryujinx, тому запускайте в цьому режимі лише програми, яким ви довіряєте.", + "UseHypervisorTooltip": "Використання гіпервізор замість JIT. Значно покращує продуктивність, коли доступний, але може бути нестабільним у поточному стані.", + "DRamTooltip": "Використовує альтернативний макет MemoryMode для імітації моделі розробки Switch.\n\nЦе корисно лише для пакетів текстур з вищою роздільною здатністю або модифікацій із роздільною здатністю 4K. НЕ покращує продуктивність.\n\nЗалиште вимкненим, якщо не впевнені.", + "IgnoreMissingServicesTooltip": "Ігнорує нереалізовані служби Horizon OS. Це може допомогти в обході збоїв під час завантаження певних ігор.\n\nЗалиште вимкненим, якщо не впевнені.", + "GraphicsBackendThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\nВстановіть значення «Авто», якщо не впевнені", + "GalThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\n\nВстановіть значення «Авто», якщо не впевнені.", + "ShaderCacheToggleTooltip": "Зберігає кеш дискового шейдера, що зменшує затримки під час наступних запусків.\n\nЗалиште увімкненим, якщо не впевнені.", + "ResolutionScaleTooltip": "Множить роздільну здатність гри.\n\nДеякі ігри можуть не працювати з цією функцією, і виглядатимуть піксельними; для цих ігор треба знайти модифікації, що зупиняють згладжування або підвищують роздільну здатність. Для останніх модифікацій, вибирайте \"Native\".\n\nЦей параметр можна міняти коли гра запущена кліком на \"Застосувати\"; ви можете перемістити вікно налаштувань і поекспериментувати з видом гри.\n\nМайте на увазі, що 4x це занадто для будь-якого комп'ютера.", + "ResolutionScaleEntryTooltip": "Масштаб роздільної здатності з плаваючою комою, наприклад 1,5. Не інтегральні масштаби, швидше за все, спричинять проблеми або збій.", + "AnisotropyTooltip": "Рівень анізотропної фільтрації. Встановіть на «Авто», щоб використовувати значення, яке вимагає гра.", + "AspectRatioTooltip": "Співвідношення сторін застосовано до вікна рендера.\n\nМіняйте тільки, якщо використовуєте модифікацію співвідношення сторін для гри, інакше графіка буде розтягнута.\n\nЗалиште на \"16:9\", якщо не впевнені.", + "ShaderDumpPathTooltip": "Шлях скидання графічних шейдерів", + "FileLogTooltip": "Зберігає журнал консолі у файл журналу на диску. Не впливає на продуктивність.", + "StubLogTooltip": "Друкує повідомлення журналу-заглушки на консолі. Не впливає на продуктивність.", + "InfoLogTooltip": "Друкує повідомлення інформаційного журналу на консолі. Не впливає на продуктивність.", + "WarnLogTooltip": "Друкує повідомлення журналу попереджень у консолі. Не впливає на продуктивність.", + "ErrorLogTooltip": "Друкує повідомлення журналу помилок у консолі. Не впливає на продуктивність.", + "TraceLogTooltip": "Друкує повідомлення журналу трасування на консолі. Не впливає на продуктивність.", + "GuestLogTooltip": "Друкує повідомлення журналу гостей у консолі. Не впливає на продуктивність.", + "FileAccessLogTooltip": "Друкує повідомлення журналу доступу до файлів у консолі.", + "FSAccessLogModeTooltip": "Вмикає виведення журналу доступу до FS на консоль. Можливі режими 0-3", + "DeveloperOptionTooltip": "Використовуйте з обережністю", + "OpenGlLogLevel": "Потрібно увімкнути відповідні рівні журналу", + "DebugLogTooltip": "Друкує повідомлення журналу налагодження на консолі.\n\nВикористовуйте це лише за спеціальною вказівкою співробітника, оскільки це ускладнить читання журналів і погіршить роботу емулятора.", + "LoadApplicationFileTooltip": "Відкриває файловий провідник, щоб вибрати для завантаження сумісний файл Switch", + "LoadApplicationFolderTooltip": "Відкриває файловий провідник, щоб вибрати сумісну з комутатором розпаковану програму для завантаження", + "OpenRyujinxFolderTooltip": "Відкриває папку файлової системи Ryujinx", + "OpenRyujinxLogsTooltip": "Відкриває папку, куди записуються журнали", + "ExitTooltip": "Виходить з Ryujinx", + "OpenSettingsTooltip": "Відкриває вікно налаштувань", + "OpenProfileManagerTooltip": "Відкриває вікно диспетчера профілів користувачів", + "StopEmulationTooltip": "Зупиняє емуляцію поточної гри та повертається до вибору гри", + "CheckUpdatesTooltip": "Перевіряє наявність оновлень для Ryujinx", + "OpenAboutTooltip": "Відкриває вікно «Про програму».", + "GridSize": "Розмір сітки", + "GridSizeTooltip": "Змінити розмір елементів сітки", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Португальська (Бразилія)", + "AboutRyujinxContributorsButtonHeader": "Переглянути всіх співавторів", + "SettingsTabSystemAudioVolume": "Гучність: ", + "AudioVolumeTooltip": "Змінити гучність звуку", + "SettingsTabSystemEnableInternetAccess": "Гостьовий доступ до Інтернету/режим LAN", + "EnableInternetAccessTooltip": "Дозволяє емульованій програмі підключатися до Інтернету.\n\nІгри з режимом локальної мережі можуть підключатися одна до одної, якщо це увімкнено, і системи підключені до однієї точки доступу. Сюди входять і справжні консолі.\n\nНЕ дозволяє підключатися до серверів Nintendo. Може призвести до збою в деяких іграх, які намагаються підключитися до Інтернету.\n\nЗалиште вимкненим, якщо не впевнені.", + "GameListContextMenuManageCheatToolTip": "Керування читами", + "GameListContextMenuManageCheat": "Керування читами", + "GameListContextMenuManageModToolTip": "Керування модами", + "GameListContextMenuManageMod": "Керування модами", + "ControllerSettingsStickRange": "Діапазон:", + "DialogStopEmulationTitle": "Ryujinx - Зупинити емуляцію", + "DialogStopEmulationMessage": "Ви впевнені, що хочете зупинити емуляцію?", + "SettingsTabCpu": "ЦП", + "SettingsTabAudio": "Аудіо", + "SettingsTabNetwork": "Мережа", + "SettingsTabNetworkConnection": "Підключення до мережі", + "SettingsTabCpuCache": "Кеш ЦП", + "SettingsTabCpuMemory": "Пам'ять ЦП", + "DialogUpdaterFlatpakNotSupportedMessage": "Будь ласка, оновіть Ryujinx через FlatHub.", + "UpdaterDisabledWarningTitle": "Програму оновлення вимкнено!", + "ControllerSettingsRotate90": "Повернути на 90° за годинниковою стрілкою", + "IconSize": "Розмір значка", + "IconSizeTooltip": "Змінити розмір значків гри", + "MenuBarOptionsShowConsole": "Показати консоль", + "ShaderCachePurgeError": "Помилка очищення кешу шейдера {0}: {1}", + "UserErrorNoKeys": "Ключі не знайдено", + "UserErrorNoFirmware": "Прошивка не знайдена", + "UserErrorFirmwareParsingFailed": "Помилка аналізу прошивки", + "UserErrorApplicationNotFound": "Додаток не знайдено", + "UserErrorUnknown": "Невідома помилка", + "UserErrorUndefined": "Невизначена помилка", + "UserErrorNoKeysDescription": "Ryujinx не вдалося знайти ваш файл «prod.keys».", + "UserErrorNoFirmwareDescription": "Ryujinx не вдалося знайти встановлену прошивку", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx не вдалося проаналізувати прошивку. Зазвичай це спричинено застарілими ключами.", + "UserErrorApplicationNotFoundDescription": "Ryujinx не вдалося знайти дійсний додаток за вказаним шляхом", + "UserErrorUnknownDescription": "Сталася невідома помилка!", + "UserErrorUndefinedDescription": "Сталася невизначена помилка! Цього не повинно статися, зверніться до розробника!", + "OpenSetupGuideMessage": "Відкрити посібник із налаштування", + "NoUpdate": "Немає оновлень", + "TitleUpdateVersionLabel": "Версія {0} - {1}", + "RyujinxInfo": "Ryujin x - Інформація", + "RyujinxConfirm": "Ryujinx - Підтвердження", + "FileDialogAllTypes": "Всі типи", + "Never": "Ніколи", + "SwkbdMinCharacters": "Мінімальна кількість символів: {0}", + "SwkbdMinRangeCharacters": "Має бути {0}-{1} символів", + "SoftwareKeyboard": "Програмна клавіатура", + "SoftwareKeyboardModeNumeric": "Повинно бути лише 0-9 або “.”", + "SoftwareKeyboardModeAlphabet": "Повинно бути лише не CJK-символи", + "SoftwareKeyboardModeASCII": "Повинно бути лише ASCII текст", + "ControllerAppletControllers": "Підтримувані контролери:", + "ControllerAppletPlayers": "Гравці:", + "ControllerAppletDescription": "Поточна конфігурація невірна. Відкрийте налаштування та переналаштуйте Ваші дані.", + "ControllerAppletDocked": "Встановлений режим в док-станції. Вимкніть портативні контролери.", + "UpdaterRenaming": "Перейменування старих файлів...", + "UpdaterRenameFailed": "Програмі оновлення не вдалося перейменувати файл: {0}", + "UpdaterAddingFiles": "Додавання нових файлів...", + "UpdaterExtracting": "Видобування оновлення...", + "UpdaterDownloading": "Завантаження оновлення...", + "Game": "Гра", + "Docked": "Док-станція", + "Handheld": "Портативний", + "ConnectionError": "Помилка з'єднання.", + "AboutPageDeveloperListMore": "{0} та інші...", + "ApiError": "Помилка API.", + "LoadingHeading": "Завантаження {0}", + "CompilingPPTC": "Компіляція PTC", + "CompilingShaders": "Компіляція шейдерів", + "AllKeyboards": "Всі клавіатури", + "OpenFileDialogTitle": "Виберіть підтримуваний файл для відкриття", + "OpenFolderDialogTitle": "Виберіть теку з розпакованою грою", + "AllSupportedFormats": "Усі підтримувані формати", + "RyujinxUpdater": "Програма оновлення Ryujinx", + "SettingsTabHotkeys": "Гарячі клавіші клавіатури", + "SettingsTabHotkeysHotkeys": "Гарячі клавіші клавіатури", + "SettingsTabHotkeysToggleVsyncHotkey": "Увімк/вимк вертикальну синхронізацію:", + "SettingsTabHotkeysScreenshotHotkey": "Знімок екрана:", + "SettingsTabHotkeysShowUiHotkey": "Показати інтерфейс:", + "SettingsTabHotkeysPauseHotkey": "Пауза:", + "SettingsTabHotkeysToggleMuteHotkey": "Вимкнути звук:", + "ControllerMotionTitle": "Налаштування керування рухом", + "ControllerRumbleTitle": "Налаштування вібрації", + "SettingsSelectThemeFileDialogTitle": "Виберіть файл теми", + "SettingsXamlThemeFile": "Файл теми Xaml", + "AvatarWindowTitle": "Керування обліковими записами - Аватар", + "Amiibo": "Amiibo", + "Unknown": "Невідомо", + "Usage": "Використання", + "Writable": "Можливість запису", + "SelectDlcDialogTitle": "Виберіть файли DLC", + "SelectUpdateDialogTitle": "Виберіть файли оновлення", + "SelectModDialogTitle": "Виберіть теку з модами", + "UserProfileWindowTitle": "Менеджер профілів користувачів", + "CheatWindowTitle": "Менеджер читів", + "DlcWindowTitle": "Менеджер вмісту для завантаження", + "ModWindowTitle": "Керувати модами для {0} ({1})", + "UpdateWindowTitle": "Менеджер оновлення назв", + "CheatWindowHeading": "Коди доступні для {0} [{1}]", + "BuildId": "ID збірки:", + "DlcWindowHeading": "Вміст для завантаження, доступний для {1} ({2}): {0}", + "ModWindowHeading": "{0} мод(ів)", + "UserProfilesEditProfile": "Редагувати вибране", + "Cancel": "Скасувати", + "Save": "Зберегти", + "Discard": "Скасувати", + "Paused": "Призупинено", + "UserProfilesSetProfileImage": "Встановити зображення профілю", + "UserProfileEmptyNameError": "Імʼя обовʼязкове", + "UserProfileNoImageError": "Зображення профілю обовʼязкове", + "GameUpdateWindowHeading": "{0} Доступні оновлення для {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Збільшити роздільність:", + "SettingsTabHotkeysResScaleDownHotkey": "Зменшити роздільність:", + "UserProfilesName": "Імʼя", + "UserProfilesUserId": "ID користувача:", + "SettingsTabGraphicsBackend": "Графічний сервер", + "SettingsTabGraphicsBackendTooltip": "Виберіть backend графіки, що буде використовуватись в емуляторі.\n\n\"Vulkan\" краще для всіх сучасних відеокарт, якщо драйвери вчасно оновлюються. У Vulkan також швидше компілюються шейдери (менше \"заїкання\" зображення) на відеокартах всіх компаній.\n\n\"OpenGL\" може дати кращі результати на старих відеокартах Nvidia, старих відеокартах AMD на Linux, або на відеокартах з маленькою кількістю VRAM, але \"заїкання\" через компіляцію шейдерів будуть частіші.\n\nЯкщо не впевнені, встановіть на \"Vulkan\". Встановіть на \"OpenGL\", якщо Ваша відеокарта не підтримує Vulkan навіть на останніх драйверах.", + "SettingsEnableTextureRecompression": "Увімкнути рекомпресію текстури", + "SettingsEnableTextureRecompressionTooltip": "Стискає текстури ASTC, щоб зменшити використання VRAM.\n\nЦим форматом текстур користуються такі ігри, як Astral Chain, Bayonetta 3, Fire Emblem Engage, Metroid Prime Remastered, Super Mario Bros. Wonder і The Legend of Zelda: Tears of the Kingdom.\n\nЦі ігри, скоріше всього крашнуться на відеокартах з розміром VRAM в 4 Гб і менше.\n\nВмикайте тільки якщо у Вас закінчується VRAM на цих іграх. Залиште на \"Вимкнути\", якщо не впевнені.", + "SettingsTabGraphicsPreferredGpu": "Бажаний GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Виберіть відеокарту, яка використовуватиметься з графічним сервером Vulkan.\n\nНе впливає на графічний процесор, який використовуватиме OpenGL.\n\nВстановіть графічний процесор, позначений як «dGPU», якщо не впевнені. Якщо такого немає, не чіпайте.", + "SettingsAppRequiredRestartMessage": "Необхідно перезапустити Ryujinx", + "SettingsGpuBackendRestartMessage": "Налаштування графічного сервера або GPU було змінено. Для цього знадобиться перезапуск", + "SettingsGpuBackendRestartSubMessage": "Бажаєте перезапустити зараз?", + "RyujinxUpdaterMessage": "Бажаєте оновити Ryujinx до останньої версії?", + "SettingsTabHotkeysVolumeUpHotkey": "Збільшити гучність:", + "SettingsTabHotkeysVolumeDownHotkey": "Зменшити гучність:", + "SettingsEnableMacroHLE": "Увімкнути макрос HLE", + "SettingsEnableMacroHLETooltip": "Високорівнева емуляція коду макросу GPU.\n\nПокращує продуктивність, але може викликати графічні збої в деяких іграх.\n\nЗалиште увімкненим, якщо не впевнені.", + "SettingsEnableColorSpacePassthrough": "Наскрізний колірний простір", + "SettingsEnableColorSpacePassthroughTooltip": "Дозволяє серверу Vulkan передавати інформацію про колір без вказівки колірного простору. Для користувачів з екранами з широкою гамою це може призвести до більш яскравих кольорів, але шляхом втрати коректності передачі кольору.", + "VolumeShort": "Гуч.", + "UserProfilesManageSaves": "Керувати збереженнями", + "DeleteUserSave": "Ви хочете видалити збереження користувача для цієї гри?", + "IrreversibleActionNote": "Цю дію не можна скасувати.", + "SaveManagerHeading": "Керувати збереженнями для {0}", + "SaveManagerTitle": "Менеджер збереження", + "Name": "Назва", + "Size": "Розмір", + "Search": "Пошук", + "UserProfilesRecoverLostAccounts": "Відновлення втрачених облікових записів", + "Recover": "Відновити", + "UserProfilesRecoverHeading": "Знайдено збереження для наступних облікових записів", + "UserProfilesRecoverEmptyList": "Немає профілів для відновлення", + "GraphicsAATooltip": "Застосовує згладження до рендера гри.\n\nFXAA розмиє більшість зображення, а SMAA спробує знайти нерівні краї та згладити їх.\n\nНе рекомендується використовувати разом з фільтром масштабування FSR.\n\nЦю опцію можна міняти коли гра запущена кліком на \"Застосувати; ви можете відсунути вікно налаштувань і поекспериментувати з видом гри.\n\nЗалиште на \"Немає\", якщо не впевнені.", + "GraphicsAALabel": "Згладжування:", + "GraphicsScalingFilterLabel": "Фільтр масштабування:", + "GraphicsScalingFilterTooltip": "Виберіть фільтр масштабування, що використається при збільшенні роздільної здатності.\n\n\"Білінійний\" добре виглядає в 3D іграх, і хороше налаштування за умовчуванням.\n\n\"Найближчий\" рекомендується для ігор з піксель-артом.\n\n\"FSR 1.0\" - це просто фільтр різкості, не рекомендується використовувати разом з FXAA або SMAA.\n\nЦю опцію можна міняти коли гра запущена кліком на \"Застосувати; ви можете відсунути вікно налаштувань і поекспериментувати з видом гри.\n\nЗалиште на \"Білінійний\", якщо не впевнені.", + "GraphicsScalingFilterBilinear": "Білінійний", + "GraphicsScalingFilterNearest": "Найближчий", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "Рівень", + "GraphicsScalingFilterLevelTooltip": "Встановити рівень різкості в FSR 1.0. Чим вище - тим різкіше.", + "SmaaLow": "SMAA Низький", + "SmaaMedium": "SMAA Середній", + "SmaaHigh": "SMAA Високий", + "SmaaUltra": "SMAA Ультра", + "UserEditorTitle": "Редагувати користувача", + "UserEditorTitleCreate": "Створити користувача", + "SettingsTabNetworkInterface": "Мережевий інтерфейс:", + "NetworkInterfaceTooltip": "Мережевий інтерфейс, що використовується для LAN/LDN.\n\nРазом з VPN або XLink Kai, і грою що підтримує LAN, може імітувати з'єднання в однаковій мережі через Інтернет.", + "NetworkInterfaceDefault": "Стандартний", + "PackagingShaders": "Пакування шейдерів", + "AboutChangelogButton": "Переглянути журнал змін на GitHub", + "AboutChangelogButtonTooltipMessage": "Клацніть, щоб відкрити журнал змін для цієї версії у стандартному браузері.", + "SettingsTabNetworkMultiplayer": "Мережева гра", + "MultiplayerMode": "Режим:", + "MultiplayerModeTooltip": "Змінити LDN мультиплеєру.\n\nLdnMitm змінить функціонал бездротової/локальної гри в іграх, щоб вони працювали так, ніби це LAN, що дозволяє локальні підключення в тій самій мережі з іншими екземплярами Ryujinx та хакнутими консолями Nintendo Switch, які мають встановлений модуль ldn_mitm.\n\nМультиплеєр вимагає, щоб усі гравці були на одній і тій же версії гри (наприклад Super Smash Bros. Ultimate v13.0.1 не зможе під'єднатися до v13.0.0).\n\nЗалиште на \"Вимкнено\", якщо не впевнені, ", + "MultiplayerModeDisabled": "Вимкнено", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json new file mode 100644 index 00000000..66f59ecd --- /dev/null +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -0,0 +1,780 @@ +{ + "Language": "简体中文", + "MenuBarFileOpenApplet": "打开小程序", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "打开独立的 Mii 小程序", + "SettingsTabInputDirectMouseAccess": "直通鼠标操作", + "SettingsTabSystemMemoryManagerMode": "内存管理模式:", + "SettingsTabSystemMemoryManagerModeSoftware": "软件管理", + "SettingsTabSystemMemoryManagerModeHost": "本机映射 (较快)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "跳过检查的本机映射 (最快,但不安全)", + "SettingsTabSystemUseHypervisor": "使用 Hypervisor 虚拟化", + "MenuBarFile": "文件(_F)", + "MenuBarFileOpenFromFile": "加载游戏文件(_L)", + "MenuBarFileOpenUnpacked": "加载解包后的游戏(_U)", + "MenuBarFileOpenEmuFolder": "打开 Ryujinx 系统目录", + "MenuBarFileOpenLogsFolder": "打开日志目录", + "MenuBarFileExit": "退出(_E)", + "MenuBarOptions": "选项(_O)", + "MenuBarOptionsToggleFullscreen": "切换全屏", + "MenuBarOptionsStartGamesInFullscreen": "全屏模式启动游戏", + "MenuBarOptionsStopEmulation": "停止模拟", + "MenuBarOptionsSettings": "设置(_S)", + "MenuBarOptionsManageUserProfiles": "管理用户账户(_M)", + "MenuBarActions": "操作(_A)", + "MenuBarOptionsSimulateWakeUpMessage": "模拟唤醒消息", + "MenuBarActionsScanAmiibo": "扫描 Amiibo", + "MenuBarTools": "工具(_T)", + "MenuBarToolsInstallFirmware": "安装系统固件", + "MenuBarFileToolsInstallFirmwareFromFile": "从 XCI 或 ZIP 文件中安装系统固件", + "MenuBarFileToolsInstallFirmwareFromDirectory": "从文件夹中安装系统固件", + "MenuBarToolsManageFileTypes": "管理文件扩展名", + "MenuBarToolsInstallFileTypes": "关联文件扩展名", + "MenuBarToolsUninstallFileTypes": "取消关联扩展名", + "MenuBarView": "视图(_V)", + "MenuBarViewWindow": "窗口大小", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "帮助(_H)", + "MenuBarHelpCheckForUpdates": "检查更新", + "MenuBarHelpAbout": "关于", + "MenuSearch": "搜索…", + "GameListHeaderFavorite": "收藏", + "GameListHeaderIcon": "图标", + "GameListHeaderApplication": "名称", + "GameListHeaderDeveloper": "制作商", + "GameListHeaderVersion": "版本", + "GameListHeaderTimePlayed": "游玩时长", + "GameListHeaderLastPlayed": "最近游玩", + "GameListHeaderFileExtension": "扩展名", + "GameListHeaderFileSize": "大小", + "GameListHeaderPath": "路径", + "GameListContextMenuOpenUserSaveDirectory": "打开用户存档目录", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "打开储存游戏用户存档的目录", + "GameListContextMenuOpenDeviceSaveDirectory": "打开系统数据目录", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "打开储存游戏系统数据的目录", + "GameListContextMenuOpenBcatSaveDirectory": "打开 BCAT 数据目录", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "打开储存游戏 BCAT 数据的目录", + "GameListContextMenuManageTitleUpdates": "管理游戏更新", + "GameListContextMenuManageTitleUpdatesToolTip": "打开游戏更新管理窗口", + "GameListContextMenuManageDlc": "管理 DLC", + "GameListContextMenuManageDlcToolTip": "打开 DLC 管理窗口", + "GameListContextMenuCacheManagement": "缓存管理", + "GameListContextMenuCacheManagementPurgePptc": "清除 PPTC 缓存文件", + "GameListContextMenuCacheManagementPurgePptcToolTip": "删除游戏的 PPTC 缓存文件,下次启动游戏时重新编译生成 PPTC 缓存文件", + "GameListContextMenuCacheManagementPurgeShaderCache": "清除着色器缓存文件", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "删除游戏的着色器缓存文件,下次启动游戏时重新生成着色器缓存文件", + "GameListContextMenuCacheManagementOpenPptcDirectory": "打开 PPTC 缓存目录", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "打开储存游戏 PPTC 缓存文件的目录", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "打开着色器缓存目录", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "打开储存游戏着色器缓存文件的目录", + "GameListContextMenuExtractData": "提取数据", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "从游戏的当前状态中提取 ExeFS 分区 (包括更新)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "从游戏的当前状态中提取 RomFS 分区 (包括更新)", + "GameListContextMenuExtractDataLogo": "图标", + "GameListContextMenuExtractDataLogoToolTip": "从游戏的当前状态中提取图标 (包括更新)", + "GameListContextMenuCreateShortcut": "创建游戏快捷方式", + "GameListContextMenuCreateShortcutToolTip": "创建一个直接启动此游戏的桌面快捷方式", + "GameListContextMenuCreateShortcutToolTipMacOS": "在 macOS 的应用程序目录中创建一个直接启动此游戏的快捷方式", + "GameListContextMenuOpenModsDirectory": "打开 MOD 目录", + "GameListContextMenuOpenModsDirectoryToolTip": "打开存放游戏 MOD 的目录", + "GameListContextMenuOpenSdModsDirectory": "打开大气层系统 MOD 目录", + "GameListContextMenuOpenSdModsDirectoryToolTip": "打开存放适用于大气层系统的游戏 MOD 的目录,对于为真实硬件打包的 MOD 非常有用", + "StatusBarGamesLoaded": "{0}/{1} 游戏加载完成", + "StatusBarSystemVersion": "系统固件版本:{0}", + "LinuxVmMaxMapCountDialogTitle": "检测到操作系统内存映射最大数量被设置的过低", + "LinuxVmMaxMapCountDialogTextPrimary": "你想要将操作系统 vm.max_map_count 的值增加到 {0} 吗", + "LinuxVmMaxMapCountDialogTextSecondary": "有些游戏可能会尝试创建超过当前系统允许的内存映射最大数量,若超过当前最大数量,Ryujinx 模拟器将会闪退。", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "确定,临时保存(重启后失效)", + "LinuxVmMaxMapCountDialogButtonPersistent": "确定,永久保存", + "LinuxVmMaxMapCountWarningTextPrimary": "内存映射的最大数量低于推荐值。", + "LinuxVmMaxMapCountWarningTextSecondary": "vm.max_map_count ({0}) 的当前值小于 {1}。 有些游戏可能会尝试创建超过当前系统允许的内存映射最大数量,若超过当前最大数量,Ryujinx 模拟器将会闪退。\n\n你可以手动增加内存映射最大数量,或者安装 pkexec,它可以辅助 Ryujinx 完成内存映射最大数量的修改操作。", + "Settings": "设置", + "SettingsTabGeneral": "用户界面", + "SettingsTabGeneralGeneral": "常规", + "SettingsTabGeneralEnableDiscordRichPresence": "启用 Discord 在线状态展示", + "SettingsTabGeneralCheckUpdatesOnLaunch": "启动时检查更新", + "SettingsTabGeneralShowConfirmExitDialog": "退出游戏时需要确认", + "SettingsTabGeneralRememberWindowState": "记住窗口大小和位置", + "SettingsTabGeneralHideCursor": "隐藏鼠标指针:", + "SettingsTabGeneralHideCursorNever": "从不隐藏", + "SettingsTabGeneralHideCursorOnIdle": "自动隐藏", + "SettingsTabGeneralHideCursorAlways": "始终隐藏", + "SettingsTabGeneralGameDirectories": "游戏目录", + "SettingsTabGeneralAdd": "添加", + "SettingsTabGeneralRemove": "删除", + "SettingsTabSystem": "系统", + "SettingsTabSystemCore": "核心", + "SettingsTabSystemSystemRegion": "系统区域:", + "SettingsTabSystemSystemRegionJapan": "日本", + "SettingsTabSystemSystemRegionUSA": "美国", + "SettingsTabSystemSystemRegionEurope": "欧洲", + "SettingsTabSystemSystemRegionAustralia": "澳大利亚", + "SettingsTabSystemSystemRegionChina": "中国", + "SettingsTabSystemSystemRegionKorea": "韩国", + "SettingsTabSystemSystemRegionTaiwan": "台湾地区", + "SettingsTabSystemSystemLanguage": "系统语言:", + "SettingsTabSystemSystemLanguageJapanese": "日语", + "SettingsTabSystemSystemLanguageAmericanEnglish": "英语(美国)", + "SettingsTabSystemSystemLanguageFrench": "法语", + "SettingsTabSystemSystemLanguageGerman": "德语", + "SettingsTabSystemSystemLanguageItalian": "意大利语", + "SettingsTabSystemSystemLanguageSpanish": "西班牙语", + "SettingsTabSystemSystemLanguageChinese": "中文(简体)——无效", + "SettingsTabSystemSystemLanguageKorean": "韩语", + "SettingsTabSystemSystemLanguageDutch": "荷兰语", + "SettingsTabSystemSystemLanguagePortuguese": "葡萄牙语", + "SettingsTabSystemSystemLanguageRussian": "俄语", + "SettingsTabSystemSystemLanguageTaiwanese": "中文(繁体)——无效", + "SettingsTabSystemSystemLanguageBritishEnglish": "英语(英国)", + "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法语", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉美西班牙语", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "简体中文(推荐)", + "SettingsTabSystemSystemLanguageTraditionalChinese": "繁体中文(推荐)", + "SettingsTabSystemSystemTimeZone": "系统时区:", + "SettingsTabSystemSystemTime": "系统时钟:", + "SettingsTabSystemEnableVsync": "启用垂直同步", + "SettingsTabSystemEnablePptc": "开启 PPTC 缓存", + "SettingsTabSystemEnableFsIntegrityChecks": "启用文件系统完整性检查", + "SettingsTabSystemAudioBackend": "音频处理引擎:", + "SettingsTabSystemAudioBackendDummy": "无", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "修改", + "SettingsTabSystemHacksNote": "会导致模拟器不稳定", + "SettingsTabSystemExpandDramSize": "使用开发机的内存布局(开发人员使用)", + "SettingsTabSystemIgnoreMissingServices": "忽略缺失的服务", + "SettingsTabGraphics": "图形", + "SettingsTabGraphicsAPI": "图形 API", + "SettingsTabGraphicsEnableShaderCache": "启用着色器缓存", + "SettingsTabGraphicsAnisotropicFiltering": "各向异性过滤:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "自动", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "分辨率缩放:", + "SettingsTabGraphicsResolutionScaleCustom": "自定义(不推荐)", + "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2 倍 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3 倍 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4 倍 (2880p/4320p) (不推荐)", + "SettingsTabGraphicsAspectRatio": "宽高比:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "拉伸以适应窗口", + "SettingsTabGraphicsDeveloperOptions": "开发者选项", + "SettingsTabGraphicsShaderDumpPath": "图形着色器转储路径:", + "SettingsTabLogging": "日志", + "SettingsTabLoggingLogging": "日志", + "SettingsTabLoggingEnableLoggingToFile": "将日志写入文件", + "SettingsTabLoggingEnableStubLogs": "启用存根日志", + "SettingsTabLoggingEnableInfoLogs": "启用信息日志", + "SettingsTabLoggingEnableWarningLogs": "启用警告日志", + "SettingsTabLoggingEnableErrorLogs": "启用错误日志", + "SettingsTabLoggingEnableTraceLogs": "启用跟踪日志", + "SettingsTabLoggingEnableGuestLogs": "启用访客日志", + "SettingsTabLoggingEnableFsAccessLogs": "启用文件访问日志", + "SettingsTabLoggingFsGlobalAccessLogMode": "文件系统全局访问日志模式:", + "SettingsTabLoggingDeveloperOptions": "开发者选项", + "SettingsTabLoggingDeveloperOptionsNote": "警告:会降低模拟器性能", + "SettingsTabLoggingGraphicsBackendLogLevel": "图形引擎日志级别:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "无", + "SettingsTabLoggingGraphicsBackendLogLevelError": "错误", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "减速", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "全部", + "SettingsTabLoggingEnableDebugLogs": "启用调试日志", + "SettingsTabInput": "输入", + "SettingsTabInputEnableDockedMode": "主机模式", + "SettingsTabInputDirectKeyboardAccess": "直通键盘控制", + "SettingsButtonSave": "保存", + "SettingsButtonClose": "关闭", + "SettingsButtonOk": "确定", + "SettingsButtonCancel": "取消", + "SettingsButtonApply": "应用", + "ControllerSettingsPlayer": "玩家", + "ControllerSettingsPlayer1": "玩家 1", + "ControllerSettingsPlayer2": "玩家 2", + "ControllerSettingsPlayer3": "玩家 3", + "ControllerSettingsPlayer4": "玩家 4", + "ControllerSettingsPlayer5": "玩家 5", + "ControllerSettingsPlayer6": "玩家 6", + "ControllerSettingsPlayer7": "玩家 7", + "ControllerSettingsPlayer8": "玩家 8", + "ControllerSettingsHandheld": "掌机模式", + "ControllerSettingsInputDevice": "输入设备", + "ControllerSettingsRefresh": "刷新", + "ControllerSettingsDeviceDisabled": "禁用", + "ControllerSettingsControllerType": "手柄类型", + "ControllerSettingsControllerTypeHandheld": "掌机", + "ControllerSettingsControllerTypeProController": "Pro 手柄", + "ControllerSettingsControllerTypeJoyConPair": "双 JoyCon 手柄", + "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon 手柄", + "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon 手柄", + "ControllerSettingsProfile": "配置文件", + "ControllerSettingsProfileDefault": "默认设置", + "ControllerSettingsLoad": "加载", + "ControllerSettingsAdd": "新建", + "ControllerSettingsRemove": "删除", + "ControllerSettingsButtons": "基础按键", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "方向键", + "ControllerSettingsDPadUp": "上", + "ControllerSettingsDPadDown": "下", + "ControllerSettingsDPadLeft": "左", + "ControllerSettingsDPadRight": "右", + "ControllerSettingsStickButton": "按下摇杆", + "ControllerSettingsStickUp": "上", + "ControllerSettingsStickDown": "下", + "ControllerSettingsStickLeft": "左", + "ControllerSettingsStickRight": "右", + "ControllerSettingsStickStick": "摇杆", + "ControllerSettingsStickInvertXAxis": "摇杆左右反转", + "ControllerSettingsStickInvertYAxis": "摇杆上下反转", + "ControllerSettingsStickDeadzone": "死区:", + "ControllerSettingsLStick": "左摇杆", + "ControllerSettingsRStick": "右摇杆", + "ControllerSettingsTriggersLeft": "左扳机", + "ControllerSettingsTriggersRight": "右扳机", + "ControllerSettingsTriggersButtonsLeft": "左扳机键", + "ControllerSettingsTriggersButtonsRight": "右扳机键", + "ControllerSettingsTriggers": "扳机", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "左背键", + "ControllerSettingsExtraButtonsRight": "右背键", + "ControllerSettingsMisc": "其他", + "ControllerSettingsTriggerThreshold": "扳机阈值:", + "ControllerSettingsMotion": "体感", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 兼容的体感协议", + "ControllerSettingsMotionControllerSlot": "手柄槽位:", + "ControllerSettingsMotionMirrorInput": "镜像操作", + "ControllerSettingsMotionRightJoyConSlot": "右 JoyCon 槽位:", + "ControllerSettingsMotionServerHost": "服务器地址:", + "ControllerSettingsMotionGyroSensitivity": "陀螺仪敏感度:", + "ControllerSettingsMotionGyroDeadzone": "陀螺仪死区:", + "ControllerSettingsSave": "保存", + "ControllerSettingsClose": "关闭", + "KeyUnknown": "未知", + "KeyShiftLeft": "左侧Shift", + "KeyShiftRight": "右侧Shift", + "KeyControlLeft": "左侧Ctrl", + "KeyMacControlLeft": "左侧⌃", + "KeyControlRight": "右侧Ctrl", + "KeyMacControlRight": "右侧⌃", + "KeyAltLeft": "左侧Alt", + "KeyMacAltLeft": "左侧⌥", + "KeyAltRight": "右侧Alt", + "KeyMacAltRight": "右侧⌥", + "KeyWinLeft": "左侧⊞", + "KeyMacWinLeft": "左侧⌘", + "KeyWinRight": "右侧⊞", + "KeyMacWinRight": "右侧⌘", + "KeyMenu": "菜单键", + "KeyUp": "上", + "KeyDown": "下", + "KeyLeft": "左", + "KeyRight": "右", + "KeyEnter": "回车键", + "KeyEscape": "Esc", + "KeySpace": "空格键", + "KeyTab": "Tab", + "KeyBackSpace": "退格键", + "KeyInsert": "Insert", + "KeyDelete": "Delete", + "KeyPageUp": "Page Up", + "KeyPageDown": "Page Down", + "KeyHome": "Home", + "KeyEnd": "End", + "KeyCapsLock": "Caps Lock", + "KeyScrollLock": "Scroll Lock", + "KeyPrintScreen": "Print Screen", + "KeyPause": "Pause", + "KeyNumLock": "Num Lock", + "KeyClear": "清除键", + "KeyKeypad0": "小键盘0", + "KeyKeypad1": "小键盘1", + "KeyKeypad2": "小键盘2", + "KeyKeypad3": "小键盘3", + "KeyKeypad4": "小键盘4", + "KeyKeypad5": "小键盘5", + "KeyKeypad6": "小键盘6", + "KeyKeypad7": "小键盘7", + "KeyKeypad8": "小键盘8", + "KeyKeypad9": "小键盘9", + "KeyKeypadDivide": "小键盘/", + "KeyKeypadMultiply": "小键盘*", + "KeyKeypadSubtract": "小键盘-", + "KeyKeypadAdd": "小键盘+", + "KeyKeypadDecimal": "小键盘.", + "KeyKeypadEnter": "小键盘回车键", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "未分配", + "GamepadLeftStick": "左摇杆按键", + "GamepadRightStick": "右摇杆按键", + "GamepadLeftShoulder": "左肩键L", + "GamepadRightShoulder": "右肩键R", + "GamepadLeftTrigger": "左扳机键ZL", + "GamepadRightTrigger": "右扳机键ZR", + "GamepadDpadUp": "上键", + "GamepadDpadDown": "下键", + "GamepadDpadLeft": "左键", + "GamepadDpadRight": "右键", + "GamepadMinus": "-键", + "GamepadPlus": "+键", + "GamepadGuide": "主页键", + "GamepadMisc1": "截图键", + "GamepadPaddle1": "其他按键1", + "GamepadPaddle2": "其他按键2", + "GamepadPaddle3": "其他按键3", + "GamepadPaddle4": "其他按键4", + "GamepadTouchpad": "触摸板", + "GamepadSingleLeftTrigger0": "左扳机0", + "GamepadSingleRightTrigger0": "右扳机0", + "GamepadSingleLeftTrigger1": "左扳机1", + "GamepadSingleRightTrigger1": "右扳机1", + "StickLeft": "左摇杆", + "StickRight": "右摇杆", + "UserProfilesSelectedUserProfile": "选定的用户账户:", + "UserProfilesSaveProfileName": "保存名称", + "UserProfilesChangeProfileImage": "更换头像", + "UserProfilesAvailableUserProfiles": "现有用户账户:", + "UserProfilesAddNewProfile": "新建账户", + "UserProfilesDelete": "删除", + "UserProfilesClose": "关闭", + "ProfileNameSelectionWatermark": "输入昵称", + "ProfileImageSelectionTitle": "选择头像", + "ProfileImageSelectionHeader": "选择合适的头像图片", + "ProfileImageSelectionNote": "您可以导入自定义头像,或从模拟器系统固件中选择预设头像", + "ProfileImageSelectionImportImage": "导入图像文件", + "ProfileImageSelectionSelectAvatar": "选择预设头像", + "InputDialogTitle": "输入对话框", + "InputDialogOk": "完成", + "InputDialogCancel": "取消", + "InputDialogAddNewProfileTitle": "选择用户名称", + "InputDialogAddNewProfileHeader": "请输入账户名称", + "InputDialogAddNewProfileSubtext": "(最大长度:{0})", + "AvatarChoose": "保存选定头像", + "AvatarSetBackgroundColor": "设置背景色", + "AvatarClose": "关闭", + "ControllerSettingsLoadProfileToolTip": "加载配置文件", + "ControllerSettingsAddProfileToolTip": "新增配置文件", + "ControllerSettingsRemoveProfileToolTip": "删除配置文件", + "ControllerSettingsSaveProfileToolTip": "保存配置文件", + "MenuBarFileToolsTakeScreenshot": "保存截屏", + "MenuBarFileToolsHideUi": "隐藏菜单栏和状态栏", + "GameListContextMenuRunApplication": "启动游戏", + "GameListContextMenuToggleFavorite": "收藏", + "GameListContextMenuToggleFavoriteToolTip": "切换游戏的收藏状态", + "SettingsTabGeneralTheme": "主题:", + "SettingsTabGeneralThemeDark": "深色(暗黑)", + "SettingsTabGeneralThemeLight": "浅色(亮色)", + "ControllerSettingsConfigureGeneral": "配置", + "ControllerSettingsRumble": "震动", + "ControllerSettingsRumbleStrongMultiplier": "强震动幅度", + "ControllerSettingsRumbleWeakMultiplier": "弱震动幅度", + "DialogMessageSaveNotAvailableMessage": "没有{0} [{1:x16}]的游戏存档", + "DialogMessageSaveNotAvailableCreateSaveMessage": "是否创建该游戏的存档?", + "DialogConfirmationTitle": "Ryujinx - 确认", + "DialogUpdaterTitle": "Ryujinx - 更新", + "DialogErrorTitle": "Ryujinx - 错误", + "DialogWarningTitle": "Ryujinx - 警告", + "DialogExitTitle": "Ryujinx - 退出", + "DialogErrorMessage": "Ryujinx 模拟器发生错误", + "DialogExitMessage": "是否关闭 Ryujinx 模拟器?", + "DialogExitSubMessage": "未保存的进度将会丢失!", + "DialogMessageCreateSaveErrorMessage": "创建指定存档时出错:{0}", + "DialogMessageFindSaveErrorMessage": "查找指定存档时出错:{0}", + "FolderDialogExtractTitle": "选择要提取到的文件夹", + "DialogNcaExtractionMessage": "提取 {1} 的 {0} 分区...", + "DialogNcaExtractionTitle": "Ryujinx - NCA 分区提取", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失败,所选文件中没有 NCA 文件", + "DialogNcaExtractionCheckLogErrorMessage": "提取失败,请查看日志文件获取详情", + "DialogNcaExtractionSuccessMessage": "提取成功!", + "DialogUpdaterConvertFailedMessage": "无法切换当前 Ryujinx 版本。", + "DialogUpdaterCancelUpdateMessage": "取消更新!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "您使用的 Ryujinx 模拟器是最新版本。", + "DialogUpdaterFailedToGetVersionMessage": "尝试从 Github 获取版本信息时无效,可能由于 GitHub Actions 正在编译新版本。\n请过一会再试。", + "DialogUpdaterConvertFailedGithubMessage": "无法切换至从 Github 接收到的新版 Ryujinx 模拟器。", + "DialogUpdaterDownloadingMessage": "下载更新中...", + "DialogUpdaterExtractionMessage": "正在提取更新...", + "DialogUpdaterRenamingMessage": "正在重命名更新...", + "DialogUpdaterAddingFilesMessage": "安装更新中...", + "DialogUpdaterCompleteMessage": "更新成功!", + "DialogUpdaterRestartMessage": "是否立即重启 Ryujinx 模拟器?", + "DialogUpdaterNoInternetMessage": "没有连接到网络", + "DialogUpdaterNoInternetSubMessage": "请确保互联网连接正常。", + "DialogUpdaterDirtyBuildMessage": "无法更新非官方版本的 Ryujinx 模拟器!", + "DialogUpdaterDirtyBuildSubMessage": "如果想使用受支持的版本,请您在 https://ryujinx.org/ 下载官方版本。", + "DialogRestartRequiredMessage": "需要重启模拟器", + "DialogThemeRestartMessage": "主题设置已保存,需要重启模拟器才能生效。", + "DialogThemeRestartSubMessage": "是否要重启模拟器?", + "DialogFirmwareInstallEmbeddedMessage": "要安装游戏文件中内嵌的系统固件吗?(固件版本 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Ryujinx 模拟器已经从当前游戏文件中安装了系统固件 {0} 。\n模拟器现在可以正常运行了。", + "DialogFirmwareNoFirmwareInstalledMessage": "未安装系统固件", + "DialogFirmwareInstalledMessage": "已安装系统固件 {0}", + "DialogInstallFileTypesSuccessMessage": "关联文件类型成功!", + "DialogInstallFileTypesErrorMessage": "关联文件类型失败!", + "DialogUninstallFileTypesSuccessMessage": "成功解除文件类型关联!", + "DialogUninstallFileTypesErrorMessage": "解除文件类型关联失败!", + "DialogOpenSettingsWindowLabel": "打开设置窗口", + "DialogControllerAppletTitle": "控制器小窗口", + "DialogMessageDialogErrorExceptionMessage": "显示消息对话框时出错:{0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "显示软件键盘时出错:{0}", + "DialogErrorAppletErrorExceptionMessage": "显示错误对话框时出错:{0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息,可以查看我们的安装指南。", + "DialogUserErrorDialogTitle": "Ryujinx 错误 ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "从 API 获取信息时出错。", + "DialogAmiiboApiConnectErrorMessage": "无法连接到 Amiibo API 服务器,服务可能已关闭,或者没有连接互联网。", + "DialogProfileInvalidProfileErrorMessage": "配置文件 {0} 与当前输入配置系统不兼容。", + "DialogProfileDefaultProfileOverwriteErrorMessage": "不允许覆盖默认配置文件", + "DialogProfileDeleteProfileTitle": "删除配置文件", + "DialogProfileDeleteProfileMessage": "删除后不可恢复,确认删除吗?", + "DialogWarning": "警告", + "DialogPPTCDeletionMessage": "您即将删除:\n\n{0} 的 PPTC 缓存文件\n\n确定吗?", + "DialogPPTCDeletionErrorMessage": "清除 {0} 的 PPTC 缓存文件时出错:{1}", + "DialogShaderDeletionMessage": "您即将删除:\n\n{0} 的着色器缓存文件\n\n确定吗?", + "DialogShaderDeletionErrorMessage": "清除 {0} 的着色器缓存文件时出错:{1}", + "DialogRyujinxErrorMessage": "Ryujinx 模拟器发生错误", + "DialogInvalidTitleIdErrorMessage": "用户界面错误:所选游戏没有有效的游戏 ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "在路径 {0} 中找不到有效的 Switch 系统固件。", + "DialogFirmwareInstallerFirmwareInstallTitle": "安装系统固件 {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "即将安装系统固件版本 {0} 。", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n替换当前系统固件版本 {0} 。", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n是否继续?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安装系统固件中...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安装系统固件版本 {0} 。", + "DialogUserProfileDeletionWarningMessage": "删除后将没有可用的账户", + "DialogUserProfileDeletionConfirmMessage": "是否删除所选账户", + "DialogUserProfileUnsavedChangesTitle": "警告 - 有未保存的更改", + "DialogUserProfileUnsavedChangesMessage": "您对该账户的更改尚未保存。", + "DialogUserProfileUnsavedChangesSubMessage": "确定要放弃更改吗?", + "DialogControllerSettingsModifiedConfirmMessage": "当前的输入设置已更新", + "DialogControllerSettingsModifiedConfirmSubMessage": "是否保存?", + "DialogLoadFileErrorMessage": "{0}. 错误的文件:{1}", + "DialogModAlreadyExistsMessage": "MOD 已存在", + "DialogModInvalidMessage": "指定的目录找不到 MOD 文件!", + "DialogModDeleteNoParentMessage": "删除失败:找不到 MOD 的父目录“{0}”!", + "DialogDlcNoDlcErrorMessage": "选择的文件不是当前游戏的 DLC!", + "DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,该功能仅供开发人员使用。", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "为了获得最佳性能,建议禁用跟踪日志记录。您是否要立即禁用?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "您启用了着色器转储,该功能仅供开发人员使用。", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "为了获得最佳性能,建议禁用着色器转储。您是否要立即禁用?", + "DialogLoadAppGameAlreadyLoadedMessage": "游戏已经启动", + "DialogLoadAppGameAlreadyLoadedSubMessage": "请停止模拟或关闭模拟器,再启动另一个游戏。", + "DialogUpdateAddUpdateErrorMessage": "选择的文件不是当前游戏的更新!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - 图形引擎多线程", + "DialogSettingsBackendThreadingWarningMessage": "更改此选项后,必须重启 Ryujinx 模拟器才能生效。\n\n当启用图形引擎多线程时,根据显卡不同,您可能需要手动禁用显卡驱动程序自身的多线程(线程优化)。", + "DialogModManagerDeletionWarningMessage": "您即将删除 MOD:{0} \n\n确定吗?", + "DialogModManagerDeletionAllWarningMessage": "您即将删除该游戏的所有 MOD,\n\n确定吗?", + "SettingsTabGraphicsFeaturesOptions": "功能", + "SettingsTabGraphicsBackendMultithreading": "图形引擎多线程:", + "CommonAuto": "自动(推荐)", + "CommonOff": "关闭", + "CommonOn": "打开", + "InputDialogYes": "是", + "InputDialogNo": "否", + "DialogProfileInvalidProfileNameErrorMessage": "文件名包含无效字符,请重试。", + "MenuBarOptionsPauseEmulation": "暂停", + "MenuBarOptionsResumeEmulation": "继续", + "AboutUrlTooltipMessage": "在浏览器中打开 Ryujinx 模拟器官网。", + "AboutDisclaimerMessage": "Ryujinx 与 Nintendo™ 以及其合作伙伴没有任何关联。", + "AboutAmiiboDisclaimerMessage": "我们的 Amiibo 模拟使用了\nAmiiboAPI (www.amiiboapi.com)。", + "AboutPatreonUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Patreon 赞助页。", + "AboutGithubUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 GitHub 代码库。", + "AboutDiscordUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Discord 邀请链接。", + "AboutTwitterUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Twitter 主页。", + "AboutRyujinxAboutTitle": "关于:", + "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模拟器。\n您可以在 Patreon 上赞助 Ryujinx。\n关注 Twitter 或 Discord 可以获取模拟器最新动态。\n如果您对开发感兴趣,欢迎来 GitHub 或 Discord 加入我们!", + "AboutRyujinxMaintainersTitle": "开发维护人员名单:", + "AboutRyujinxMaintainersContentTooltipMessage": "在浏览器中打开贡献者页面", + "AboutRyujinxSupprtersTitle": "感谢 Patreon 上的赞助者:", + "AmiiboSeriesLabel": "Amiibo 系列", + "AmiiboCharacterLabel": "角色", + "AmiiboScanButtonLabel": "扫描", + "AmiiboOptionsShowAllLabel": "显示所有 Amiibo", + "AmiiboOptionsUsRandomTagLabel": "修改:使用随机生成的Amiibo ID", + "DlcManagerTableHeadingEnabledLabel": "已启用", + "DlcManagerTableHeadingTitleIdLabel": "游戏 ID", + "DlcManagerTableHeadingContainerPathLabel": "容器路径", + "DlcManagerTableHeadingFullPathLabel": "完整路径", + "DlcManagerRemoveAllButton": "全部删除", + "DlcManagerEnableAllButton": "全部启用", + "DlcManagerDisableAllButton": "全部停用", + "ModManagerDeleteAllButton": "全部刪除", + "MenuBarOptionsChangeLanguage": "更改界面语言", + "MenuBarShowFileTypes": "主页显示的文件类型", + "CommonSort": "排序", + "CommonShowNames": "显示名称", + "CommonFavorite": "收藏", + "OrderAscending": "升序", + "OrderDescending": "降序", + "SettingsTabGraphicsFeatures": "功能与优化", + "ErrorWindowTitle": "错误窗口", + "ToggleDiscordTooltip": "选择是否在 Discord 中显示您的游玩状态", + "AddGameDirBoxTooltip": "输入要添加的游戏目录", + "AddGameDirTooltip": "添加游戏目录到列表中", + "RemoveGameDirTooltip": "移除选中的目录", + "CustomThemeCheckTooltip": "使用自定义的 Avalonia 主题作为模拟器菜单的外观", + "CustomThemePathTooltip": "自定义主题的目录", + "CustomThemeBrowseTooltip": "查找自定义主题", + "DockModeToggleTooltip": "启用 Switch 的主机模式(电视模式、底座模式),就是模拟 Switch 连接底座的情况;若禁用主机模式,则使用 Switch 的掌机模式,就是模拟手持 Switch 运行游戏的情况。\n对于绝大多数游戏而言,主机模式会比掌机模式,画质更高,同时性能消耗也更高。\n\n简而言之,想要更好画质则启用主机模式;电脑硬件性能不足则禁用主机模式。\n\n如果使用主机模式,请选择“玩家 1”的手柄设置;如果使用掌机模式,请选择“掌机模式”的手柄设置。\n\n如果不确定,请保持开启状态。", + "DirectKeyboardTooltip": "直接键盘访问(HID)支持,游戏可以直接访问键盘作为文本输入设备。\n\n仅适用于在 Switch 硬件上原生支持键盘的游戏。\n\n如果不确定,请保持关闭状态。", + "DirectMouseTooltip": "直接鼠标访问(HID)支持,游戏可以直接访问鼠标作为指针输入设备。\n\n只适用于在 Switch 硬件上原生支持鼠标控制的游戏,这种游戏很少。\n\n启用后,触屏功能可能无法正常工作。\n\n如果不确定,请保持关闭状态。", + "RegionTooltip": "更改系统区域", + "LanguageTooltip": "更改系统语言", + "TimezoneTooltip": "更改系统时区", + "TimeTooltip": "更改系统时间", + "VSyncToggleTooltip": "模拟控制台的垂直同步,开启后会降低大部分游戏的帧率。关闭后,可以获得更高的帧率,但也可能导致游戏画面加载耗时更长或卡住。\n\n在游戏中可以使用热键进行切换(默认为 F1 键)。\n\n如果不确定,请保持开启状态。", + "PptcToggleTooltip": "缓存已编译的游戏指令,这样每次游戏加载时就无需重新编译。\n\n可以减少卡顿和启动时间,提高游戏响应速度。\n\n如果不确定,请保持开启状态。", + "FsIntegrityToggleTooltip": "启动游戏时检查游戏文件的完整性,并在日志中记录损坏的文件。\n\n对性能没有影响,用于排查故障。\n\n如果不确定,请保持开启状态。", + "AudioBackendTooltip": "更改音频处理引擎。\n\n推荐选择“SDL2”,另外“OpenAL”和“SoundIO”可以作为备选,选择“无”将没有声音。\n\n如果不确定,请设置为“SDL2”。", + "MemoryManagerTooltip": "更改模拟器内存映射和访问的方式,对模拟器 CPU 的性能影响很大。\n\n如果不确定,请设置为“跳过检查的本机映射”。", + "MemoryManagerSoftwareTooltip": "使用软件内存页进行内存地址映射,最准确但是速度最慢。", + "MemoryManagerHostTooltip": "直接映射内存页到电脑内存,使得即时编译和执行的效率更高。", + "MemoryManagerUnsafeTooltip": "直接映射内存页到电脑内存,并且不检查内存溢出,使得效率更高,但牺牲了安全。\n游戏程序可以访问模拟器内存的任意地址,所以不安全。\n建议此模式下只运行您信任的游戏程序。", + "UseHypervisorTooltip": "使用 Hypervisor 虚拟机代替即时编译,在可用的情况下能大幅提高性能,但目前可能还不稳定。", + "DRamTooltip": "模拟 Switch 开发机的内存布局。\n\n不会提高性能,某些高清纹理包或 4k 分辨率 MOD 可能需要使用此选项。\n\n如果不确定,请保持关闭状态。", + "IgnoreMissingServicesTooltip": "开启后,游戏会忽略未实现的系统服务,从而继续运行。\n少部分新发布的游戏由于使用了新的未知系统服务,可能需要此选项来避免闪退。\n模拟器更新完善系统服务之后,则无需开启此选项。\n\n如果不确定,请保持关闭状态。", + "GraphicsBackendThreadingTooltip": "在第二个线程上执行图形引擎指令。\n\n可以加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。", + "GalThreadingTooltip": "在第二个线程上执行图形引擎指令。\n\n可以加速着色器编译,减少卡顿,提高 GPU 的性能。\n\n如果不确定,请设置为“自动”。", + "ShaderCacheToggleTooltip": "模拟器将已编译的着色器保存到硬盘,可以减少游戏再次渲染相同图形导致的卡顿。\n\n如果不确定,请保持开启状态。", + "ResolutionScaleTooltip": "将游戏的渲染分辨率乘以一个倍数。\n\n有些游戏可能不适用这项设置,而且即使提高了分辨率仍然看起来像素化;对于这些游戏,您可能需要找到移除抗锯齿或提高内部渲染分辨率的 MOD。当使用这些 MOD 时,建议设置为“原生”。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n请记住,对于几乎所有人而言,4倍分辨率都是过度的。", + "ResolutionScaleEntryTooltip": "建议设置为整数倍,带小数的分辨率缩放倍数(例如1.5),非整数倍的缩放容易导致问题或闪退。", + "AnisotropyTooltip": "各向异性过滤等级,可以提高倾斜视角纹理的清晰度。\n当设置为“自动”时,使用游戏自身设定的等级。", + "AspectRatioTooltip": "游戏渲染窗口的宽高比。\n\n只有当游戏使用了修改宽高比的 MOD 时才需要修改这个设置,否则图像会被拉伸。\n\n如果不确定,请保持为“16:9”。", + "ShaderDumpPathTooltip": "转储图形着色器的路径", + "FileLogTooltip": "将控制台日志保存到硬盘文件,不影响性能。", + "StubLogTooltip": "在控制台中显示存根日志,不影响性能。", + "InfoLogTooltip": "在控制台中显示信息日志,不影响性能。", + "WarnLogTooltip": "在控制台中显示警告日志,不影响性能。", + "ErrorLogTooltip": "在控制台中显示错误日志,不影响性能。", + "TraceLogTooltip": "在控制台中显示跟踪日志。", + "GuestLogTooltip": "在控制台中显示访客日志,不影响性能。", + "FileAccessLogTooltip": "在控制台中显示文件访问日志。", + "FSAccessLogModeTooltip": "在控制台中显示文件系统访问日志,可选模式为 0-3。", + "DeveloperOptionTooltip": "请谨慎使用", + "OpenGlLogLevel": "需要启用适当的日志级别", + "DebugLogTooltip": "在控制台中显示调试日志。\n\n仅在特别需要时使用此功能,因为它会导致日志信息难以阅读,并降低模拟器性能。", + "LoadApplicationFileTooltip": "选择 Switch 游戏文件并加载", + "LoadApplicationFolderTooltip": "选择解包后的 Switch 游戏目录并加载", + "OpenRyujinxFolderTooltip": "打开 Ryujinx 模拟器系统目录", + "OpenRyujinxLogsTooltip": "打开日志存放的目录", + "ExitTooltip": "退出 Ryujinx 模拟器", + "OpenSettingsTooltip": "打开设置窗口", + "OpenProfileManagerTooltip": "打开用户账户管理窗口", + "StopEmulationTooltip": "停止运行当前游戏,并回到主界面", + "CheckUpdatesTooltip": "检查 Ryujinx 新版本", + "OpenAboutTooltip": "打开关于窗口", + "GridSize": "网格尺寸", + "GridSizeTooltip": "调整网格项目的大小", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙语", + "AboutRyujinxContributorsButtonHeader": "查看所有贡献者", + "SettingsTabSystemAudioVolume": "音量:", + "AudioVolumeTooltip": "调节音量", + "SettingsTabSystemEnableInternetAccess": "启用网络连接(局域网)", + "EnableInternetAccessTooltip": "允许模拟的游戏程序访问网络。\n\n当多个模拟器或实体 Switch 连接到同一个网络时,带有局域网模式的游戏便可以相互通信。\n\n即使开启此选项也无法访问 Nintendo 服务器,有可能导致某些尝试联网的游戏闪退。\n\n如果不确定,请保持关闭状态。", + "GameListContextMenuManageCheatToolTip": "管理当前游戏的金手指", + "GameListContextMenuManageCheat": "管理金手指", + "GameListContextMenuManageModToolTip": "管理当前游戏的 MOD", + "GameListContextMenuManageMod": "管理 MOD", + "ControllerSettingsStickRange": "范围:", + "DialogStopEmulationTitle": "Ryujinx - 停止模拟", + "DialogStopEmulationMessage": "确定要停止模拟?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "音频", + "SettingsTabNetwork": "网络", + "SettingsTabNetworkConnection": "网络连接", + "SettingsTabCpuCache": "CPU 缓存", + "SettingsTabCpuMemory": "CPU 模式", + "DialogUpdaterFlatpakNotSupportedMessage": "请通过 FlatHub 更新 Ryujinx 模拟器。", + "UpdaterDisabledWarningTitle": "已禁用更新!", + "ControllerSettingsRotate90": "顺时针旋转 90°", + "IconSize": "图标尺寸", + "IconSizeTooltip": "更改游戏图标的显示尺寸", + "MenuBarOptionsShowConsole": "显示控制台", + "ShaderCachePurgeError": "清除 {0} 的着色器缓存文件时出错:{1}", + "UserErrorNoKeys": "找不到密钥Keys", + "UserErrorNoFirmware": "未安装系统固件", + "UserErrorFirmwareParsingFailed": "固件文件解析出错", + "UserErrorApplicationNotFound": "找不到游戏程序", + "UserErrorUnknown": "未知错误", + "UserErrorUndefined": "未定义错误", + "UserErrorNoKeysDescription": "Ryujinx 模拟器找不到“prod.keys”密钥文件", + "UserErrorNoFirmwareDescription": "Ryujinx 模拟器未安装 Switch 系统固件", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx 模拟器无法解密当前固件,一般是由于使用了旧版的密钥导致的。", + "UserErrorApplicationNotFoundDescription": "Ryujinx 模拟器在所选路径中找不到有效的游戏程序。", + "UserErrorUnknownDescription": "出现未知错误!", + "UserErrorUndefinedDescription": "出现未定义错误!此类错误不应出现,请联系开发者!", + "OpenSetupGuideMessage": "打开安装指南", + "NoUpdate": "无更新(或不加载游戏更新)", + "TitleUpdateVersionLabel": "游戏更新的版本 {0}", + "RyujinxInfo": "Ryujinx - 信息", + "RyujinxConfirm": "Ryujinx - 确认", + "FileDialogAllTypes": "全部类型", + "Never": "从不", + "SwkbdMinCharacters": "不少于 {0} 个字符", + "SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字符", + "SoftwareKeyboard": "软键盘", + "SoftwareKeyboardModeNumeric": "只能输入 0-9 或 \".\"", + "SoftwareKeyboardModeAlphabet": "仅支持非中文字符", + "SoftwareKeyboardModeASCII": "仅支持 ASCII 字符", + "ControllerAppletControllers": "支持的手柄:", + "ControllerAppletPlayers": "玩家:", + "ControllerAppletDescription": "您当前的输入配置无效。打开设置并重新设置您的输入选项。", + "ControllerAppletDocked": "已经设置为主机模式,应禁用掌机手柄操控。", + "UpdaterRenaming": "正在重命名旧文件...", + "UpdaterRenameFailed": "更新过程中无法重命名文件:{0}", + "UpdaterAddingFiles": "安装更新中...", + "UpdaterExtracting": "正在提取更新...", + "UpdaterDownloading": "下载更新中...", + "Game": "游戏", + "Docked": "主机模式", + "Handheld": "掌机模式", + "ConnectionError": "连接错误。", + "AboutPageDeveloperListMore": "{0} 等开发者...", + "ApiError": "API 错误。", + "LoadingHeading": "正在启动 {0}", + "CompilingPPTC": "编译 PPTC 缓存中", + "CompilingShaders": "编译着色器中", + "AllKeyboards": "所有键盘", + "OpenFileDialogTitle": "选择支持的游戏文件并加载", + "OpenFolderDialogTitle": "选择包含解包游戏的目录并加载", + "AllSupportedFormats": "所有支持的格式", + "RyujinxUpdater": "Ryujinx 更新", + "SettingsTabHotkeys": "快捷键", + "SettingsTabHotkeysHotkeys": "键盘快捷键", + "SettingsTabHotkeysToggleVsyncHotkey": "开启或关闭垂直同步:", + "SettingsTabHotkeysScreenshotHotkey": "保存截屏:", + "SettingsTabHotkeysShowUiHotkey": "隐藏菜单栏和状态栏:", + "SettingsTabHotkeysPauseHotkey": "暂停:", + "SettingsTabHotkeysToggleMuteHotkey": "静音:", + "ControllerMotionTitle": "体感设置", + "ControllerRumbleTitle": "震动设置", + "SettingsSelectThemeFileDialogTitle": "选择主题文件", + "SettingsXamlThemeFile": "Xaml 主题文件", + "AvatarWindowTitle": "管理账户 - 头像", + "Amiibo": "Amiibo", + "Unknown": "未知", + "Usage": "用法", + "Writable": "可写入", + "SelectDlcDialogTitle": "选择 DLC 文件", + "SelectUpdateDialogTitle": "选择更新文件", + "SelectModDialogTitle": "选择 MOD 目录", + "UserProfileWindowTitle": "管理用户账户", + "CheatWindowTitle": "金手指管理器", + "DlcWindowTitle": "管理 {0} ({1}) 的 DLC", + "ModWindowTitle": "管理 {0} ({1}) 的 MOD", + "UpdateWindowTitle": "游戏更新管理器", + "CheatWindowHeading": "适用于 {0} [{1}] 的金手指", + "BuildId": "游戏版本 ID:", + "DlcWindowHeading": "{0} 个 DLC", + "ModWindowHeading": "{0} 个 MOD", + "UserProfilesEditProfile": "编辑所选", + "Cancel": "取消", + "Save": "保存", + "Discard": "放弃", + "Paused": "已暂停", + "UserProfilesSetProfileImage": "选择头像", + "UserProfileEmptyNameError": "必须输入名称", + "UserProfileNoImageError": "必须设置头像", + "GameUpdateWindowHeading": "管理 {0} ({1}) 的更新", + "SettingsTabHotkeysResScaleUpHotkey": "提高分辨率:", + "SettingsTabHotkeysResScaleDownHotkey": "降低分辨率:", + "UserProfilesName": "名称:", + "UserProfilesUserId": "用户 ID:", + "SettingsTabGraphicsBackend": "图形渲染引擎:", + "SettingsTabGraphicsBackendTooltip": "选择模拟器中使用的图像渲染引擎。\n\n安装了最新显卡驱动程序的所有现代显卡基本都支持 Vulkan,Vulkan 能够提供更快的着色器编译(较少的卡顿)。\n\n在旧版 Nvidia 显卡上、Linux 上的旧版 AMD 显卡,或者显存较低的显卡上,OpenGL 可能会取得更好的效果,但着色器编译更慢(更多的卡顿)。\n\n如果不确定,请设置为“Vulkan”。如果您的 GPU 已安装了最新显卡驱动程序也不支持 Vulkan,那请设置为“OpenGL”。", + "SettingsEnableTextureRecompression": "启用纹理压缩", + "SettingsEnableTextureRecompressionTooltip": "压缩 ASTC 纹理以减少 VRAM (显存)的占用。\n\n使用此纹理格式的游戏包括:异界锁链(Astral Chain),蓓优妮塔3(Bayonetta 3),火焰纹章Engage(Fire Emblem Engage),密特罗德 究极(Metroid Prime Remased),超级马力欧兄弟 惊奇(Super Mario Bros. Wonder)以及塞尔达传说 王国之泪(The Legend of Zelda: Tears of the Kingdom)。\n\n显存小于4GB的显卡在运行这些游戏时可能会偶发闪退。\n\n只有当您在上述游戏中的显存不足时才需要启用此选项。\n\n如果不确定,请保持关闭状态。", + "SettingsTabGraphicsPreferredGpu": "首选 GPU:", + "SettingsTabGraphicsPreferredGpuTooltip": "选择 Vulkan 图形引擎使用的 GPU。\n\n此选项不会影响 OpenGL 使用的 GPU。\n\n如果不确定,建议选择\"独立显卡(dGPU)\"。如果没有独立显卡,则无需改动此选项。", + "SettingsAppRequiredRestartMessage": "Ryujinx 模拟器需要重启", + "SettingsGpuBackendRestartMessage": "您修改了图形引擎或 GPU 设置,需要重启模拟器才能生效", + "SettingsGpuBackendRestartSubMessage": "是否要立即重启模拟器?", + "RyujinxUpdaterMessage": "是否更新 Ryujinx 到最新的版本?", + "SettingsTabHotkeysVolumeUpHotkey": "音量加:", + "SettingsTabHotkeysVolumeDownHotkey": "音量减:", + "SettingsEnableMacroHLE": "启用 HLE 宏加速", + "SettingsEnableMacroHLETooltip": "GPU 宏指令的高级模拟。\n\n提高性能表现,但一些游戏可能会出现图形错误。\n\n如果不确定,请保持开启状态。", + "SettingsEnableColorSpacePassthrough": "色彩空间直通", + "SettingsEnableColorSpacePassthroughTooltip": "使 Vulkan 图形引擎直接传输原始色彩信息。对于宽色域 (例如 DCI-P3) 显示器的用户来说,可以产生更鲜艳的颜色,代价是会损失部分色彩准确度。", + "VolumeShort": "音量", + "UserProfilesManageSaves": "管理存档", + "DeleteUserSave": "确定删除此游戏的用户存档吗?", + "IrreversibleActionNote": "删除后不可恢复。", + "SaveManagerHeading": "管理 {0} ({1}) 的存档", + "SaveManagerTitle": "存档管理器", + "Name": "名称", + "Size": "大小", + "Search": "搜索", + "UserProfilesRecoverLostAccounts": "恢复丢失的账户", + "Recover": "恢复", + "UserProfilesRecoverHeading": "找到了这些用户的存档数据", + "UserProfilesRecoverEmptyList": "没有可以恢复的用户数据", + "GraphicsAATooltip": "抗锯齿是一种图形处理技术,用于减少图像边缘的锯齿状现象,使图像更加平滑。\n\nFXAA(快速近似抗锯齿)是一种性能开销相对较小的抗锯齿方法,但可能会使得整体图像看起来有些模糊。\n\nSMAA(增强型子像素抗锯齿)则更加精细,它会尝试找到锯齿边缘并平滑它们,相比 FXAA 有更好的图像质量,但性能开销可能会稍大一些。\n\n如果开启了 FSR(FidelityFX Super Resolution,超级分辨率锐画技术)来提高性能或图像质量,不建议再启用抗锯齿,因为它们会产生不必要的图形处理开销,或者相互之间效果不协调。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n如果不确定,请保持为“无”。", + "GraphicsAALabel": "抗锯齿:", + "GraphicsScalingFilterLabel": "缩放过滤:", + "GraphicsScalingFilterTooltip": "选择在分辨率缩放时将使用的缩放过滤器。\n\nBilinear(双线性过滤)对于3D游戏效果较好,是一个安全的默认选项。\n\nNearest(最近邻过滤)推荐用于像素艺术游戏。\n\nFSR(超级分辨率锐画)只是一个锐化过滤器,不推荐与 FXAA 或 SMAA 抗锯齿一起使用。\n\n在游戏运行时,通过点击下面的“应用”按钮可以使设置生效;你可以将设置窗口移开,并试验找到您喜欢的游戏画面效果。\n\n如果不确定,请保持为“Bilinear(双线性过滤)”。", + "GraphicsScalingFilterBilinear": "Bilinear(双线性过滤)", + "GraphicsScalingFilterNearest": "Nearest(最近邻过滤)", + "GraphicsScalingFilterFsr": "FSR(超级分辨率锐画技术)", + "GraphicsScalingFilterLevelLabel": "等级", + "GraphicsScalingFilterLevelTooltip": "设置 FSR 1.0 的锐化等级,数值越高,图像越锐利。", + "SmaaLow": "SMAA 低质量", + "SmaaMedium": "SMAA 中质量", + "SmaaHigh": "SMAA 高质量", + "SmaaUltra": "SMAA 超高质量", + "UserEditorTitle": "编辑用户", + "UserEditorTitleCreate": "创建用户", + "SettingsTabNetworkInterface": "网络接口:", + "NetworkInterfaceTooltip": "用于局域网(LAN)/本地网络发现(LDN)功能的网络接口。\n\n结合 VPN 或 XLink Kai 以及支持局域网功能的游戏,可以在互联网上伪造为同一网络连接。\n\n如果不确定,请保持为“默认”。", + "NetworkInterfaceDefault": "默认", + "PackagingShaders": "整合着色器中", + "AboutChangelogButton": "在 Github 上查看更新日志", + "AboutChangelogButtonTooltipMessage": "点击这里在浏览器中打开此版本的更新日志。", + "SettingsTabNetworkMultiplayer": "多人联机游玩", + "MultiplayerMode": "联机模式:", + "MultiplayerModeTooltip": "修改 LDN 多人联机游玩模式。\n\nldn_mitm 联机插件将修改游戏中的本地无线和本地游玩功能,使其表现得像局域网一样,允许和其他安装了 ldn_mitm 插件的 Ryujinx 模拟器和破解的任天堂 Switch 主机在同一网络下进行本地连接,实现多人联机游玩。\n\n多人联机游玩要求所有玩家必须运行相同的游戏版本(例如,任天堂明星大乱斗特别版 v13.0.1 无法与 v13.0.0 版本联机)。\n\n如果不确定,请保持为“禁用”。", + "MultiplayerModeDisabled": "禁用", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json new file mode 100644 index 00000000..fc838d25 --- /dev/null +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -0,0 +1,780 @@ +{ + "Language": "繁體中文 (台灣)", + "MenuBarFileOpenApplet": "開啟小程式", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "在獨立模式下開啟 Mii 編輯器小程式", + "SettingsTabInputDirectMouseAccess": "滑鼠直接存取", + "SettingsTabSystemMemoryManagerMode": "記憶體管理員模式:", + "SettingsTabSystemMemoryManagerModeSoftware": "軟體模式", + "SettingsTabSystemMemoryManagerModeHost": "主體模式 (快速)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "主體略過檢查模式 (最快,不安全)", + "SettingsTabSystemUseHypervisor": "使用 Hypervisor", + "MenuBarFile": "檔案(_F)", + "MenuBarFileOpenFromFile": "從檔案載入應用程式(_L)", + "MenuBarFileOpenUnpacked": "載入未封裝的遊戲(_U)", + "MenuBarFileOpenEmuFolder": "開啟 Ryujinx 資料夾", + "MenuBarFileOpenLogsFolder": "開啟日誌資料夾", + "MenuBarFileExit": "結束(_E)", + "MenuBarOptions": "選項(_O)", + "MenuBarOptionsToggleFullscreen": "切換全螢幕模式", + "MenuBarOptionsStartGamesInFullscreen": "使用全螢幕模式啟動遊戲", + "MenuBarOptionsStopEmulation": "停止模擬", + "MenuBarOptionsSettings": "設定(_S)", + "MenuBarOptionsManageUserProfiles": "管理使用者設定檔(_M)", + "MenuBarActions": "動作(_A)", + "MenuBarOptionsSimulateWakeUpMessage": "模擬喚醒訊息", + "MenuBarActionsScanAmiibo": "掃描 Amiibo", + "MenuBarTools": "工具(_T)", + "MenuBarToolsInstallFirmware": "安裝韌體", + "MenuBarFileToolsInstallFirmwareFromFile": "從 XCI 或 ZIP 安裝韌體", + "MenuBarFileToolsInstallFirmwareFromDirectory": "從資料夾安裝韌體", + "MenuBarToolsManageFileTypes": "管理檔案類型", + "MenuBarToolsInstallFileTypes": "安裝檔案類型", + "MenuBarToolsUninstallFileTypes": "移除檔案類型", + "MenuBarView": "檢視(_V)", + "MenuBarViewWindow": "視窗大小", + "MenuBarViewWindow720": "720p", + "MenuBarViewWindow1080": "1080p", + "MenuBarHelp": "說明(_H)", + "MenuBarHelpCheckForUpdates": "檢查更新", + "MenuBarHelpAbout": "關於", + "MenuSearch": "搜尋...", + "GameListHeaderFavorite": "我的最愛", + "GameListHeaderIcon": "圖示", + "GameListHeaderApplication": "名稱", + "GameListHeaderDeveloper": "開發者", + "GameListHeaderVersion": "版本", + "GameListHeaderTimePlayed": "遊玩時數", + "GameListHeaderLastPlayed": "最近遊玩", + "GameListHeaderFileExtension": "副檔名", + "GameListHeaderFileSize": "檔案大小", + "GameListHeaderPath": "路徑", + "GameListContextMenuOpenUserSaveDirectory": "開啟使用者存檔資料夾", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "開啟此應用程式的使用者存檔資料夾", + "GameListContextMenuOpenDeviceSaveDirectory": "開啟裝置存檔資料夾", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "開啟此應用程式的裝置存檔資料夾", + "GameListContextMenuOpenBcatSaveDirectory": "開啟 BCAT 存檔資料夾", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "開啟此應用程式的 BCAT 存檔資料夾", + "GameListContextMenuManageTitleUpdates": "管理遊戲更新", + "GameListContextMenuManageTitleUpdatesToolTip": "開啟遊戲更新管理視窗", + "GameListContextMenuManageDlc": "管理 DLC", + "GameListContextMenuManageDlcToolTip": "開啟 DLC 管理視窗", + "GameListContextMenuCacheManagement": "快取管理", + "GameListContextMenuCacheManagementPurgePptc": "佇列 PPTC 重建", + "GameListContextMenuCacheManagementPurgePptcToolTip": "下一次啟動遊戲時,觸發 PPTC 進行重建", + "GameListContextMenuCacheManagementPurgeShaderCache": "清除著色器快取", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "刪除應用程式的著色器快取", + "GameListContextMenuCacheManagementOpenPptcDirectory": "開啟 PPTC 資料夾", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "開啟此應用程式的 PPTC 快取資料夾", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "開啟著色器快取資料夾", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "開啟此應用程式的著色器快取資料夾", + "GameListContextMenuExtractData": "提取資料", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "從應用程式的目前配置中提取 ExeFS 分區 (包含更新)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "從應用程式的目前配置中提取 RomFS 分區 (包含更新)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "從應用程式的目前配置中提取 Logo 分區 (包含更新)", + "GameListContextMenuCreateShortcut": "建立應用程式捷徑", + "GameListContextMenuCreateShortcutToolTip": "建立桌面捷徑,啟動選取的應用程式", + "GameListContextMenuCreateShortcutToolTipMacOS": "在 macOS 的應用程式資料夾中建立捷徑,啟動選取的應用程式", + "GameListContextMenuOpenModsDirectory": "開啟模組資料夾", + "GameListContextMenuOpenModsDirectoryToolTip": "開啟此應用程式模組的資料夾", + "GameListContextMenuOpenSdModsDirectory": "開啟 Atmosphere 模組資料夾", + "GameListContextMenuOpenSdModsDirectoryToolTip": "開啟此應用程式模組的另一個 SD 卡 Atmosphere 資料夾。適用於為真實硬體封裝的模組。", + "StatusBarGamesLoaded": "{0}/{1} 遊戲已載入", + "StatusBarSystemVersion": "系統版本: {0}", + "LinuxVmMaxMapCountDialogTitle": "檢測到記憶體映射的低限值", + "LinuxVmMaxMapCountDialogTextPrimary": "您是否要將 vm.max_map_count 的數值增至 {0}?", + "LinuxVmMaxMapCountDialogTextSecondary": "某些遊戲可能會嘗試建立超過目前允許的記憶體映射。一旦超過此限制,Ryujinx 就會崩潰。", + "LinuxVmMaxMapCountDialogButtonUntilRestart": "是的,直到下次重新啟動", + "LinuxVmMaxMapCountDialogButtonPersistent": "是的,永久設定", + "LinuxVmMaxMapCountWarningTextPrimary": "記憶體映射的最大值低於建議值。", + "LinuxVmMaxMapCountWarningTextSecondary": "目前 vm.max_map_count ({0}) 的數值小於 {1}。某些遊戲可能會嘗試建立比目前允許值更多的記憶體映射。一旦超過此限制,Ryujinx 就會崩潰。\n\n您可能需要手動提高上限,或者安裝 pkexec,讓 Ryujinx 協助提高上限。", + "Settings": "設定", + "SettingsTabGeneral": "使用者介面", + "SettingsTabGeneralGeneral": "一般", + "SettingsTabGeneralEnableDiscordRichPresence": "啟用 Discord 動態狀態展示", + "SettingsTabGeneralCheckUpdatesOnLaunch": "啟動時檢查更新", + "SettingsTabGeneralShowConfirmExitDialog": "顯示「確認結束」對話方塊", + "SettingsTabGeneralRememberWindowState": "記住視窗大小/位置", + "SettingsTabGeneralHideCursor": "隱藏滑鼠游標:", + "SettingsTabGeneralHideCursorNever": "從不", + "SettingsTabGeneralHideCursorOnIdle": "閒置時", + "SettingsTabGeneralHideCursorAlways": "總是", + "SettingsTabGeneralGameDirectories": "遊戲資料夾", + "SettingsTabGeneralAdd": "新增", + "SettingsTabGeneralRemove": "刪除", + "SettingsTabSystem": "系統", + "SettingsTabSystemCore": "核心", + "SettingsTabSystemSystemRegion": "系統區域:", + "SettingsTabSystemSystemRegionJapan": "日本", + "SettingsTabSystemSystemRegionUSA": "美國", + "SettingsTabSystemSystemRegionEurope": "歐洲", + "SettingsTabSystemSystemRegionAustralia": "澳洲", + "SettingsTabSystemSystemRegionChina": "中國", + "SettingsTabSystemSystemRegionKorea": "韓國", + "SettingsTabSystemSystemRegionTaiwan": "台灣 (中華民國)", + "SettingsTabSystemSystemLanguage": "系統語言:", + "SettingsTabSystemSystemLanguageJapanese": "日文", + "SettingsTabSystemSystemLanguageAmericanEnglish": "英文 (美國)", + "SettingsTabSystemSystemLanguageFrench": "法文", + "SettingsTabSystemSystemLanguageGerman": "德文", + "SettingsTabSystemSystemLanguageItalian": "義大利文", + "SettingsTabSystemSystemLanguageSpanish": "西班牙文", + "SettingsTabSystemSystemLanguageChinese": "中文 (中國)", + "SettingsTabSystemSystemLanguageKorean": "韓文", + "SettingsTabSystemSystemLanguageDutch": "荷蘭文", + "SettingsTabSystemSystemLanguagePortuguese": "葡萄牙文", + "SettingsTabSystemSystemLanguageRussian": "俄文", + "SettingsTabSystemSystemLanguageTaiwanese": "中文 (台灣)", + "SettingsTabSystemSystemLanguageBritishEnglish": "英文 (英國)", + "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法文", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "美洲西班牙文", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "簡體中文", + "SettingsTabSystemSystemLanguageTraditionalChinese": "正體中文 (建議)", + "SettingsTabSystemSystemTimeZone": "系統時區:", + "SettingsTabSystemSystemTime": "系統時鐘:", + "SettingsTabSystemEnableVsync": "垂直同步", + "SettingsTabSystemEnablePptc": "PPTC (剖析式持久轉譯快取, Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "檔案系統完整性檢查", + "SettingsTabSystemAudioBackend": "音效後端:", + "SettingsTabSystemAudioBackendDummy": "虛設 (Dummy)", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "補釘修正", + "SettingsTabSystemHacksNote": "可能導致模擬器不穩定", + "SettingsTabSystemExpandDramSize": "使用替代的記憶體配置 (開發者專用)", + "SettingsTabSystemIgnoreMissingServices": "忽略缺少的模擬器功能", + "SettingsTabGraphics": "圖形", + "SettingsTabGraphicsAPI": "圖形 API", + "SettingsTabGraphicsEnableShaderCache": "啟用著色器快取", + "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", + "SettingsTabGraphicsAnisotropicFiltering2x": "2 倍", + "SettingsTabGraphicsAnisotropicFiltering4x": "4 倍", + "SettingsTabGraphicsAnisotropicFiltering8x": "8 倍", + "SettingsTabGraphicsAnisotropicFiltering16x": "16 倍", + "SettingsTabGraphicsResolutionScale": "解析度比例:", + "SettingsTabGraphicsResolutionScaleCustom": "自訂 (不建議使用)", + "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2 倍 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3 倍 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4 倍 (2880p/4320p) (不建議使用)", + "SettingsTabGraphicsAspectRatio": "顯示長寬比例:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "拉伸以適應視窗", + "SettingsTabGraphicsDeveloperOptions": "開發者選項", + "SettingsTabGraphicsShaderDumpPath": "圖形著色器傾印路徑:", + "SettingsTabLogging": "日誌", + "SettingsTabLoggingLogging": "日誌", + "SettingsTabLoggingEnableLoggingToFile": "啟用日誌到檔案", + "SettingsTabLoggingEnableStubLogs": "啟用 Stub 日誌", + "SettingsTabLoggingEnableInfoLogs": "啟用資訊日誌", + "SettingsTabLoggingEnableWarningLogs": "啟用警告日誌", + "SettingsTabLoggingEnableErrorLogs": "啟用錯誤日誌", + "SettingsTabLoggingEnableTraceLogs": "啟用追蹤日誌", + "SettingsTabLoggingEnableGuestLogs": "啟用客體日誌", + "SettingsTabLoggingEnableFsAccessLogs": "啟用檔案系統存取日誌", + "SettingsTabLoggingFsGlobalAccessLogMode": "檔案系統全域存取日誌模式:", + "SettingsTabLoggingDeveloperOptions": "開發者選項", + "SettingsTabLoggingDeveloperOptionsNote": "警告: 會降低效能", + "SettingsTabLoggingGraphicsBackendLogLevel": "圖形後端日誌等級:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "無", + "SettingsTabLoggingGraphicsBackendLogLevelError": "錯誤", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "減速", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "全部", + "SettingsTabLoggingEnableDebugLogs": "啟用偵錯日誌", + "SettingsTabInput": "輸入", + "SettingsTabInputEnableDockedMode": "底座模式", + "SettingsTabInputDirectKeyboardAccess": "鍵盤直接存取", + "SettingsButtonSave": "儲存", + "SettingsButtonClose": "關閉", + "SettingsButtonOk": "確定", + "SettingsButtonCancel": "取消", + "SettingsButtonApply": "套用", + "ControllerSettingsPlayer": "玩家", + "ControllerSettingsPlayer1": "玩家 1", + "ControllerSettingsPlayer2": "玩家 2", + "ControllerSettingsPlayer3": "玩家 3", + "ControllerSettingsPlayer4": "玩家 4", + "ControllerSettingsPlayer5": "玩家 5", + "ControllerSettingsPlayer6": "玩家 6", + "ControllerSettingsPlayer7": "玩家 7", + "ControllerSettingsPlayer8": "玩家 8", + "ControllerSettingsHandheld": "手提模式", + "ControllerSettingsInputDevice": "輸入裝置", + "ControllerSettingsRefresh": "重新整理", + "ControllerSettingsDeviceDisabled": "已停用", + "ControllerSettingsControllerType": "控制器類型", + "ControllerSettingsControllerTypeHandheld": "手提模式", + "ControllerSettingsControllerTypeProController": "Pro 控制器", + "ControllerSettingsControllerTypeJoyConPair": "雙 JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon", + "ControllerSettingsProfile": "設定檔", + "ControllerSettingsProfileDefault": "預設", + "ControllerSettingsLoad": "載入", + "ControllerSettingsAdd": "新增", + "ControllerSettingsRemove": "刪除", + "ControllerSettingsButtons": "按鍵", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "方向鍵", + "ControllerSettingsDPadUp": "上", + "ControllerSettingsDPadDown": "下", + "ControllerSettingsDPadLeft": "左", + "ControllerSettingsDPadRight": "右", + "ControllerSettingsStickButton": "按鍵", + "ControllerSettingsStickUp": "上", + "ControllerSettingsStickDown": "下", + "ControllerSettingsStickLeft": "左", + "ControllerSettingsStickRight": "右", + "ControllerSettingsStickStick": "搖桿", + "ControllerSettingsStickInvertXAxis": "搖桿左右反向", + "ControllerSettingsStickInvertYAxis": "搖桿上下反向", + "ControllerSettingsStickDeadzone": "無感帶:", + "ControllerSettingsLStick": "左搖桿", + "ControllerSettingsRStick": "右搖桿", + "ControllerSettingsTriggersLeft": "左扳機", + "ControllerSettingsTriggersRight": "右扳機", + "ControllerSettingsTriggersButtonsLeft": "左扳機鍵", + "ControllerSettingsTriggersButtonsRight": "右扳機鍵", + "ControllerSettingsTriggers": "板機", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "左背鍵", + "ControllerSettingsExtraButtonsRight": "右背鍵", + "ControllerSettingsMisc": "其他", + "ControllerSettingsTriggerThreshold": "扳機閾值:", + "ControllerSettingsMotion": "體感", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用與 CemuHook 相容的體感", + "ControllerSettingsMotionControllerSlot": "控制器插槽:", + "ControllerSettingsMotionMirrorInput": "鏡像輸入", + "ControllerSettingsMotionRightJoyConSlot": "右 JoyCon 插槽:", + "ControllerSettingsMotionServerHost": "伺服器主機位址:", + "ControllerSettingsMotionGyroSensitivity": "陀螺儀靈敏度:", + "ControllerSettingsMotionGyroDeadzone": "陀螺儀無感帶:", + "ControllerSettingsSave": "儲存", + "ControllerSettingsClose": "關閉", + "KeyUnknown": "未知", + "KeyShiftLeft": "左 Shift", + "KeyShiftRight": "右 Shift", + "KeyControlLeft": "左 Ctrl", + "KeyMacControlLeft": "左 ⌃", + "KeyControlRight": "右 Ctrl", + "KeyMacControlRight": "右 ⌃", + "KeyAltLeft": "左 Alt", + "KeyMacAltLeft": "左 ⌥", + "KeyAltRight": "右 Alt", + "KeyMacAltRight": "右 ⌥", + "KeyWinLeft": "左 ⊞", + "KeyMacWinLeft": "左 ⌘", + "KeyWinRight": "右 ⊞", + "KeyMacWinRight": "右 ⌘", + "KeyMenu": "功能表", + "KeyUp": "上", + "KeyDown": "下", + "KeyLeft": "左", + "KeyRight": "右", + "KeyEnter": "Enter 鍵", + "KeyEscape": "Esc 鍵", + "KeySpace": "空白鍵", + "KeyTab": "Tab 鍵", + "KeyBackSpace": "Backspace 鍵", + "KeyInsert": "Insert 鍵", + "KeyDelete": "Delete 鍵", + "KeyPageUp": "向上捲頁鍵", + "KeyPageDown": "向下捲頁鍵", + "KeyHome": "Home 鍵", + "KeyEnd": "End 鍵", + "KeyCapsLock": "Caps Lock 鍵", + "KeyScrollLock": "Scroll Lock 鍵", + "KeyPrintScreen": "Print Screen 鍵", + "KeyPause": "Pause 鍵", + "KeyNumLock": "Num Lock 鍵", + "KeyClear": "清除", + "KeyKeypad0": "數字鍵 0", + "KeyKeypad1": "數字鍵 1", + "KeyKeypad2": "數字鍵 2", + "KeyKeypad3": "數字鍵 3", + "KeyKeypad4": "數字鍵 4", + "KeyKeypad5": "數字鍵 5", + "KeyKeypad6": "數字鍵 6", + "KeyKeypad7": "數字鍵 7", + "KeyKeypad8": "數字鍵 8", + "KeyKeypad9": "數字鍵 9", + "KeyKeypadDivide": "數字鍵除號", + "KeyKeypadMultiply": "數字鍵乘號", + "KeyKeypadSubtract": "數字鍵減號", + "KeyKeypadAdd": "數字鍵加號", + "KeyKeypadDecimal": "數字鍵小數點", + "KeyKeypadEnter": "數字鍵 Enter", + "KeyNumber0": "0", + "KeyNumber1": "1", + "KeyNumber2": "2", + "KeyNumber3": "3", + "KeyNumber4": "4", + "KeyNumber5": "5", + "KeyNumber6": "6", + "KeyNumber7": "7", + "KeyNumber8": "8", + "KeyNumber9": "9", + "KeyTilde": "~", + "KeyGrave": "`", + "KeyMinus": "-", + "KeyPlus": "+", + "KeyBracketLeft": "[", + "KeyBracketRight": "]", + "KeySemicolon": ";", + "KeyQuote": "\"", + "KeyComma": ",", + "KeyPeriod": ".", + "KeySlash": "/", + "KeyBackSlash": "\\", + "KeyUnbound": "未分配", + "GamepadLeftStick": "左搖桿按鍵", + "GamepadRightStick": "右搖桿按鍵", + "GamepadLeftShoulder": "左肩鍵", + "GamepadRightShoulder": "右肩鍵", + "GamepadLeftTrigger": "左扳機", + "GamepadRightTrigger": "右扳機", + "GamepadDpadUp": "上", + "GamepadDpadDown": "下", + "GamepadDpadLeft": "左", + "GamepadDpadRight": "右", + "GamepadMinus": "-", + "GamepadPlus": "+", + "GamepadGuide": "快顯功能表鍵", + "GamepadMisc1": "其他按鍵", + "GamepadPaddle1": "其他按鍵 1", + "GamepadPaddle2": "其他按鍵 2", + "GamepadPaddle3": "其他按鍵 3", + "GamepadPaddle4": "其他按鍵 4", + "GamepadTouchpad": "觸控板", + "GamepadSingleLeftTrigger0": "左扳機 0", + "GamepadSingleRightTrigger0": "右扳機 0", + "GamepadSingleLeftTrigger1": "左扳機 1", + "GamepadSingleRightTrigger1": "右扳機 1", + "StickLeft": "左搖桿", + "StickRight": "右搖桿", + "UserProfilesSelectedUserProfile": "選取的使用者設定檔:", + "UserProfilesSaveProfileName": "儲存設定檔名稱", + "UserProfilesChangeProfileImage": "變更設定檔圖像", + "UserProfilesAvailableUserProfiles": "可用的使用者設定檔:", + "UserProfilesAddNewProfile": "建立設定檔", + "UserProfilesDelete": "刪除", + "UserProfilesClose": "關閉", + "ProfileNameSelectionWatermark": "選擇暱稱", + "ProfileImageSelectionTitle": "設定檔圖像選取", + "ProfileImageSelectionHeader": "選擇設定檔圖像", + "ProfileImageSelectionNote": "您可以匯入自訂的設定檔圖像,或從系統韌體中選取大頭貼。", + "ProfileImageSelectionImportImage": "匯入圖像檔案", + "ProfileImageSelectionSelectAvatar": "選取韌體大頭貼", + "InputDialogTitle": "輸入對話方塊", + "InputDialogOk": "確定", + "InputDialogCancel": "取消", + "InputDialogAddNewProfileTitle": "選擇設定檔名稱", + "InputDialogAddNewProfileHeader": "請輸入設定檔名稱", + "InputDialogAddNewProfileSubtext": "(最大長度: {0})", + "AvatarChoose": "選擇大頭貼", + "AvatarSetBackgroundColor": "設定背景顏色", + "AvatarClose": "關閉", + "ControllerSettingsLoadProfileToolTip": "載入設定檔", + "ControllerSettingsAddProfileToolTip": "新增設定檔", + "ControllerSettingsRemoveProfileToolTip": "刪除設定檔", + "ControllerSettingsSaveProfileToolTip": "儲存設定檔", + "MenuBarFileToolsTakeScreenshot": "儲存擷取畫面", + "MenuBarFileToolsHideUi": "隱藏 UI", + "GameListContextMenuRunApplication": "執行應用程式", + "GameListContextMenuToggleFavorite": "加入/移除為我的最愛", + "GameListContextMenuToggleFavoriteToolTip": "切換遊戲的我的最愛狀態", + "SettingsTabGeneralTheme": "佈景主題:", + "SettingsTabGeneralThemeDark": "深色", + "SettingsTabGeneralThemeLight": "淺色", + "ControllerSettingsConfigureGeneral": "配置", + "ControllerSettingsRumble": "震動", + "ControllerSettingsRumbleStrongMultiplier": "強震動調節", + "ControllerSettingsRumbleWeakMultiplier": "弱震動調節", + "DialogMessageSaveNotAvailableMessage": "沒有 {0} [{1:x16}] 的存檔", + "DialogMessageSaveNotAvailableCreateSaveMessage": "您想為這款遊戲建立存檔嗎?", + "DialogConfirmationTitle": "Ryujinx - 確認", + "DialogUpdaterTitle": "Ryujinx - 更新程式", + "DialogErrorTitle": "Ryujinx - 錯誤", + "DialogWarningTitle": "Ryujinx - 警告", + "DialogExitTitle": "Ryujinx - 結束", + "DialogErrorMessage": "Ryujinx 遇到了錯誤", + "DialogExitMessage": "您確定要關閉 Ryujinx 嗎?", + "DialogExitSubMessage": "所有未儲存的資料將會遺失!", + "DialogMessageCreateSaveErrorMessage": "建立指定的存檔時出現錯誤: {0}", + "DialogMessageFindSaveErrorMessage": "尋找指定的存檔時出現錯誤: {0}", + "FolderDialogExtractTitle": "選擇要解壓到的資料夾", + "DialogNcaExtractionMessage": "從 {1} 提取 {0} 分區...", + "DialogNcaExtractionTitle": "Ryujinx - NCA 分區提取器", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失敗。所選檔案中不存在主 NCA 檔案。", + "DialogNcaExtractionCheckLogErrorMessage": "提取失敗。請閱讀日誌檔案了解更多資訊。", + "DialogNcaExtractionSuccessMessage": "提取成功。", + "DialogUpdaterConvertFailedMessage": "無法轉換目前的 Ryujinx 版本。", + "DialogUpdaterCancelUpdateMessage": "取消更新!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "您已經在使用最新版本的 Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "嘗試從 GitHub Release 取得發布資訊時發生錯誤。如果 GitHub Actions 正在編譯新版本,則可能會出現這種情況。請幾分鐘後再試一次。", + "DialogUpdaterConvertFailedGithubMessage": "無法轉換從 Github Release 接收到的 Ryujinx 版本。", + "DialogUpdaterDownloadingMessage": "正在下載更新...", + "DialogUpdaterExtractionMessage": "正在提取更新...", + "DialogUpdaterRenamingMessage": "重新命名更新...", + "DialogUpdaterAddingFilesMessage": "加入新更新...", + "DialogUpdaterCompleteMessage": "更新成功!", + "DialogUpdaterRestartMessage": "您現在要重新啟動 Ryujinx 嗎?", + "DialogUpdaterNoInternetMessage": "您沒有連線到網際網路!", + "DialogUpdaterNoInternetSubMessage": "請確認您的網際網路連線正常!", + "DialogUpdaterDirtyBuildMessage": "您無法更新非官方版本的 Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "如果您正在尋找受官方支援的版本,請從 https://ryujinx.org/ 下載 Ryujinx。", + "DialogRestartRequiredMessage": "需要重新啟動", + "DialogThemeRestartMessage": "佈景主題設定已儲存。需要重新啟動才能套用主題。", + "DialogThemeRestartSubMessage": "您要重新啟動嗎", + "DialogFirmwareInstallEmbeddedMessage": "您想安裝遊戲內建的韌體嗎? (韌體 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安裝的韌體,但 Ryujinx 可以從現有的遊戲安裝韌體{0}。\n模擬器現在可以執行。", + "DialogFirmwareNoFirmwareInstalledMessage": "未安裝韌體", + "DialogFirmwareInstalledMessage": "已安裝韌體{0}", + "DialogInstallFileTypesSuccessMessage": "成功安裝檔案類型!", + "DialogInstallFileTypesErrorMessage": "無法安裝檔案類型。", + "DialogUninstallFileTypesSuccessMessage": "成功移除檔案類型!", + "DialogUninstallFileTypesErrorMessage": "無法移除檔案類型。", + "DialogOpenSettingsWindowLabel": "開啟設定視窗", + "DialogControllerAppletTitle": "控制器小程式", + "DialogMessageDialogErrorExceptionMessage": "顯示訊息對話方塊時出現錯誤: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "顯示軟體鍵盤時出現錯誤: {0}", + "DialogErrorAppletErrorExceptionMessage": "顯示錯誤對話方塊時出現錯誤: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n有關如何修復此錯誤的更多資訊,請參閱我們的設定指南。", + "DialogUserErrorDialogTitle": "Ryujinx 錯誤 ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "從 API 取得資訊時出現錯誤。", + "DialogAmiiboApiConnectErrorMessage": "無法連接 Amiibo API 伺服器。服務可能已停機,或者您可能需要確認網際網路連線是否在線上。", + "DialogProfileInvalidProfileErrorMessage": "設定檔 {0} 與目前輸入配置系統不相容。", + "DialogProfileDefaultProfileOverwriteErrorMessage": "無法覆蓋預設設定檔", + "DialogProfileDeleteProfileTitle": "刪除設定檔", + "DialogProfileDeleteProfileMessage": "此動作不可復原,您確定要繼續嗎?", + "DialogWarning": "警告", + "DialogPPTCDeletionMessage": "您將在下一次啟動時佇列重建以下遊戲的 PPTC:\n\n{0}\n\n您確定要繼續嗎?", + "DialogPPTCDeletionErrorMessage": "在 {0} 清除 PPTC 快取時出錯: {1}", + "DialogShaderDeletionMessage": "您將刪除以下遊戲的著色器快取:\n\n{0}\n\n您確定要繼續嗎?", + "DialogShaderDeletionErrorMessage": "在 {0} 清除著色器快取時出錯: {1}", + "DialogRyujinxErrorMessage": "Ryujinx 遇到錯誤", + "DialogInvalidTitleIdErrorMessage": "UI 錯誤: 所選遊戲沒有有效的遊戲 ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "在 {0} 中未發現有效的系統韌體。", + "DialogFirmwareInstallerFirmwareInstallTitle": "安裝韌體 {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "將安裝系統版本 {0}。", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n這將取代目前的系統版本 {0}。", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n您確定要繼續嗎?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "正在安裝韌體...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安裝系統版本 {0}。", + "DialogUserProfileDeletionWarningMessage": "如果刪除選取的設定檔,將無法開啟其他設定檔", + "DialogUserProfileDeletionConfirmMessage": "您是否要刪除所選設定檔", + "DialogUserProfileUnsavedChangesTitle": "警告 - 未儲存的變更", + "DialogUserProfileUnsavedChangesMessage": "您對該使用者設定檔所做的變更尚未儲存。", + "DialogUserProfileUnsavedChangesSubMessage": "您確定要放棄變更嗎?", + "DialogControllerSettingsModifiedConfirmMessage": "目前控制器設定已更新。", + "DialogControllerSettingsModifiedConfirmSubMessage": "您想要儲存嗎?", + "DialogLoadFileErrorMessage": "{0}。出錯檔案: {1}", + "DialogModAlreadyExistsMessage": "模組已經存在", + "DialogModInvalidMessage": "指定資料夾不包含模組!", + "DialogModDeleteNoParentMessage": "刪除失敗: 無法找到模組「{0}」的父資料夾!", + "DialogDlcNoDlcErrorMessage": "指定檔案不包含所選遊戲的 DLC!", + "DialogPerformanceCheckLoggingEnabledMessage": "您已啟用追蹤日誌,該功能僅供開發者使用。", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "為獲得最佳效能,建議停用追蹤日誌。您是否要立即停用追蹤日誌嗎?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "您已啟用著色器傾印,該功能僅供開發者使用。", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "為獲得最佳效能,建議停用著色器傾印。您是否要立即停用著色器傾印嗎?", + "DialogLoadAppGameAlreadyLoadedMessage": "已載入此遊戲", + "DialogLoadAppGameAlreadyLoadedSubMessage": "請停止模擬或關閉模擬器,然後再啟動另一款遊戲。", + "DialogUpdateAddUpdateErrorMessage": "指定檔案不包含所選遊戲的更新!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - 後端執行緒處理中", + "DialogSettingsBackendThreadingWarningMessage": "變更此選項後,必須重新啟動 Ryujinx 才能完全生效。使用 Ryujinx 的多執行緒功能時,可能需要手動停用驅動程式本身的多執行緒功能,這取決於您的平台。", + "DialogModManagerDeletionWarningMessage": "您將刪除模組: {0}\n\n您確定要繼續嗎?", + "DialogModManagerDeletionAllWarningMessage": "您即將刪除此遊戲的所有模組。\n\n您確定要繼續嗎?", + "SettingsTabGraphicsFeaturesOptions": "功能", + "SettingsTabGraphicsBackendMultithreading": "圖形後端多執行緒:", + "CommonAuto": "自動", + "CommonOff": "關閉", + "CommonOn": "開啟", + "InputDialogYes": "是", + "InputDialogNo": "否", + "DialogProfileInvalidProfileNameErrorMessage": "檔案名稱包含無效字元。請重試。", + "MenuBarOptionsPauseEmulation": "暫停", + "MenuBarOptionsResumeEmulation": "繼續", + "AboutUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 網站。", + "AboutDisclaimerMessage": "Ryujinx 和 Nintendo™\n或其任何合作夥伴完全沒有關聯。", + "AboutAmiiboDisclaimerMessage": "我們在 Amiibo 模擬中\n使用了 AmiiboAPI (www.amiiboapi.com)。", + "AboutPatreonUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Patreon 網頁。", + "AboutGithubUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 GitHub 網頁。", + "AboutDiscordUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Discord 邀請連結。", + "AboutTwitterUrlTooltipMessage": "在預設瀏覽器中開啟 Ryujinx 的 Twitter 網頁。", + "AboutRyujinxAboutTitle": "關於:", + "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模擬器。\n請在 Patreon 上支持我們。\n關注我們的 Twitter 或 Discord 取得所有最新消息。\n對於有興趣貢獻的開發者,可以在我們的 GitHub 或 Discord 上了解更多資訊。", + "AboutRyujinxMaintainersTitle": "維護者:", + "AboutRyujinxMaintainersContentTooltipMessage": "在預設瀏覽器中開啟貢獻者的網頁", + "AboutRyujinxSupprtersTitle": "Patreon 支持者:", + "AmiiboSeriesLabel": "Amiibo 系列", + "AmiiboCharacterLabel": "角色", + "AmiiboScanButtonLabel": "掃描", + "AmiiboOptionsShowAllLabel": "顯示所有 Amiibo", + "AmiiboOptionsUsRandomTagLabel": "補釘修正:使用隨機標記的 Uuid", + "DlcManagerTableHeadingEnabledLabel": "已啟用", + "DlcManagerTableHeadingTitleIdLabel": "遊戲 ID", + "DlcManagerTableHeadingContainerPathLabel": "容器路徑", + "DlcManagerTableHeadingFullPathLabel": "完整路徑", + "DlcManagerRemoveAllButton": "全部刪除", + "DlcManagerEnableAllButton": "全部啟用", + "DlcManagerDisableAllButton": "全部停用", + "ModManagerDeleteAllButton": "全部刪除", + "MenuBarOptionsChangeLanguage": "變更語言", + "MenuBarShowFileTypes": "顯示檔案類型", + "CommonSort": "排序", + "CommonShowNames": "顯示名稱", + "CommonFavorite": "我的最愛", + "OrderAscending": "從小到大", + "OrderDescending": "從大到小", + "SettingsTabGraphicsFeatures": "功能與改進", + "ErrorWindowTitle": "錯誤視窗", + "ToggleDiscordTooltip": "啟用或關閉 Discord 動態狀態展示", + "AddGameDirBoxTooltip": "輸入要新增到清單中的遊戲資料夾", + "AddGameDirTooltip": "新增遊戲資料夾到清單中", + "RemoveGameDirTooltip": "移除選取的遊戲資料夾", + "CustomThemeCheckTooltip": "為圖形使用者介面使用自訂 Avalonia 佈景主題,變更模擬器功能表的外觀", + "CustomThemePathTooltip": "自訂 GUI 佈景主題的路徑", + "CustomThemeBrowseTooltip": "瀏覽自訂 GUI 佈景主題", + "DockModeToggleTooltip": "底座模式可使模擬系統表現為底座的 Nintendo Switch。這可以提高大多數遊戲的圖形保真度。反之,停用該模式將使模擬系統表現為手提模式的 Nintendo Switch,從而降低圖形品質。\n\n如果計劃使用底座模式,請配置玩家 1 控制;如果計劃使用手提模式,請配置手提控制。\n\n如果不確定,請保持開啟狀態。", + "DirectKeyboardTooltip": "支援直接鍵盤存取 (HID)。遊戲可將鍵盤作為文字輸入裝置。\n\n僅適用於在 Switch 硬體上原生支援使用鍵盤的遊戲。\n\n如果不確定,請保持關閉狀態。", + "DirectMouseTooltip": "支援滑鼠直接存取 (HID)。遊戲可將滑鼠作為指向裝置使用。\n\n僅適用於在 Switch 硬體上原生支援滑鼠控制的遊戲,這類遊戲很少。\n\n啟用後,觸控螢幕功能可能無法使用。\n\n如果不確定,請保持關閉狀態。", + "RegionTooltip": "變更系統區域", + "LanguageTooltip": "變更系統語言", + "TimezoneTooltip": "變更系統時區", + "TimeTooltip": "變更系統時鐘", + "VSyncToggleTooltip": "模擬遊戲機的垂直同步。對大多數遊戲來說,它本質上是一個幀率限制器;停用它可能會導致遊戲以更高的速度執行,或使載入畫面耗時更長或卡住。\n\n可以在遊戲中使用快速鍵進行切換 (預設為 F1)。如果您打算停用,我們建議您這樣做。\n\n如果不確定,請保持開啟狀態。", + "PptcToggleTooltip": "儲存已轉譯的 JIT 函數,這樣每次載入遊戲時就無需再轉譯這些函數。\n\n減少遊戲首次啟動後的卡頓現象,並大大加快啟動時間。\n\n如果不確定,請保持開啟狀態。", + "FsIntegrityToggleTooltip": "在啟動遊戲時檢查損壞的檔案,如果檢測到損壞的檔案,則在日誌中顯示雜湊值錯誤。\n\n對效能沒有影響,旨在幫助排除故障。\n\n如果不確定,請保持開啟狀態。", + "AudioBackendTooltip": "變更用於繪製音訊的後端。\n\nSDL2 是首選,而 OpenAL 和 SoundIO 則作為備用。虛設 (Dummy) 將沒有聲音。\n\n如果不確定,請設定為 SDL2。", + "MemoryManagerTooltip": "變更客體記憶體的映射和存取方式。這會極大地影響模擬 CPU 效能。\n\n如果不確定,請設定為主體略過檢查模式。", + "MemoryManagerSoftwareTooltip": "使用軟體分頁表進行位址轉換。精度最高,但效能最差。", + "MemoryManagerHostTooltip": "直接映射主體位址空間中的記憶體。更快的 JIT 編譯和執行速度。", + "MemoryManagerUnsafeTooltip": "直接映射記憶體,但在存取前不封鎖客體位址空間內的位址。速度更快,但相對不安全。訪客應用程式可以從 Ryujinx 中的任何地方存取記憶體,因此只能使用該模式執行您信任的程式。", + "UseHypervisorTooltip": "使用 Hypervisor 取代 JIT。使用時可大幅提高效能,但在目前狀態下可能不穩定。", + "DRamTooltip": "利用另一種 MemoryMode 配置來模仿 Switch 開發模式。\n\n這僅對高解析度紋理套件或 4K 解析度模組有用。不會提高效能。\n\n如果不確定,請保持關閉狀態。", + "IgnoreMissingServicesTooltip": "忽略未實現的 Horizon OS 服務。這可能有助於在啟動某些遊戲時避免崩潰。\n\n如果不確定,請保持關閉狀態。", + "GraphicsBackendThreadingTooltip": "在第二個執行緒上執行圖形後端指令。\n\n在本身不支援多執行緒的 GPU 驅動程式上,可加快著色器編譯、減少卡頓並提高效能。在支援多執行緒的驅動程式上效能略有提升。\n\n如果不確定,請設定為自動。", + "GalThreadingTooltip": "在第二個執行緒上執行圖形後端指令。\n\n在本身不支援多執行緒的 GPU 驅動程式上,可加快著色器編譯、減少卡頓並提高效能。在支援多執行緒的驅動程式上效能略有提升。\n\n如果不確定,請設定為自動。", + "ShaderCacheToggleTooltip": "儲存磁碟著色器快取,減少後續執行時的卡頓。\n\n如果不確定,請保持開啟狀態。", + "ResolutionScaleTooltip": "使用倍數提升遊戲的繪製解析度。\n\n少數遊戲可能無法使用此功能,即使提高解析度也會顯得像素化;對於這些遊戲,您可能需要找到去除反鋸齒或提高內部繪製解析度的模組。對於後者,您可能需要選擇原生。\n\n此選項可在遊戲執行時透過點選下方的「套用」進行變更;您只需將設定視窗移到一旁,然後進行試驗,直到找到您喜歡的遊戲效果。\n\n請記住,4 倍幾乎對任何設定都是過度的。", + "ResolutionScaleEntryTooltip": "浮點解析度刻度,如 1.5。非整數刻度更容易出現問題或崩潰。", + "AnisotropyTooltip": "各向異性過濾等級。設定為自動可使用遊戲要求的值。", + "AspectRatioTooltip": "套用於繪製器視窗的長寬比。\n\n只有在遊戲中使用長寬比模組時才可變更,否則圖形會被拉伸。\n\n如果不確定,請保持 16:9 狀態。", + "ShaderDumpPathTooltip": "圖形著色器傾印路徑", + "FileLogTooltip": "將控制台日誌儲存到磁碟上的日誌檔案中。不會影響效能。", + "StubLogTooltip": "在控制台中輸出日誌訊息。不會影響效能。", + "InfoLogTooltip": "在控制台中輸出資訊日誌訊息。不會影響效能。", + "WarnLogTooltip": "在控制台中輸出警告日誌訊息。不會影響效能。", + "ErrorLogTooltip": "在控制台中輸出錯誤日誌訊息。不會影響效能。", + "TraceLogTooltip": "在控制台中輸出追蹤日誌訊息。不會影響效能。", + "GuestLogTooltip": "在控制台中輸出客體日誌訊息。不會影響效能。", + "FileAccessLogTooltip": "在控制台中輸出檔案存取日誌訊息。", + "FSAccessLogModeTooltip": "啟用檔案系統存取日誌輸出到控制台中。可能的模式為 0 到 3", + "DeveloperOptionTooltip": "謹慎使用", + "OpenGlLogLevel": "需要啟用適當的日誌等級", + "DebugLogTooltip": "在控制台中輸出偵錯日誌訊息。\n\n只有在人員特別指示的情況下才能使用,因為這會導致日誌難以閱讀,並降低模擬器效能。", + "LoadApplicationFileTooltip": "開啟檔案總管,選擇與 Switch 相容的檔案來載入", + "LoadApplicationFolderTooltip": "開啟檔案總管,選擇與 Switch 相容且未封裝的應用程式來載入", + "OpenRyujinxFolderTooltip": "開啟 Ryujinx 檔案系統資料夾", + "OpenRyujinxLogsTooltip": "開啟日誌被寫入的資料夾", + "ExitTooltip": "結束 Ryujinx", + "OpenSettingsTooltip": "開啟設定視窗", + "OpenProfileManagerTooltip": "開啟使用者設定檔管理員視窗", + "StopEmulationTooltip": "停止模擬目前遊戲,返回遊戲選擇介面", + "CheckUpdatesTooltip": "檢查 Ryujinx 的更新", + "OpenAboutTooltip": "開啟關於視窗", + "GridSize": "網格尺寸", + "GridSizeTooltip": "調整網格的大小", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙文", + "AboutRyujinxContributorsButtonHeader": "查看所有貢獻者", + "SettingsTabSystemAudioVolume": "音量:", + "AudioVolumeTooltip": "調節音量", + "SettingsTabSystemEnableInternetAccess": "訪客網際網路存取/區域網路模式", + "EnableInternetAccessTooltip": "允許模擬應用程式連線網際網路。\n\n當啟用此功能且系統連線到同一接入點時,具有區域網路模式的遊戲可相互連線。這也包括真正的遊戲機。\n\n不允許連接 Nintendo 伺服器。可能會導致某些嘗試連線網際網路的遊戲崩潰。\n\n如果不確定,請保持關閉狀態。", + "GameListContextMenuManageCheatToolTip": "管理密技", + "GameListContextMenuManageCheat": "管理密技", + "GameListContextMenuManageModToolTip": "管理模組", + "GameListContextMenuManageMod": "管理模組", + "ControllerSettingsStickRange": "範圍:", + "DialogStopEmulationTitle": "Ryujinx - 停止模擬", + "DialogStopEmulationMessage": "您確定要停止模擬嗎?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "音訊", + "SettingsTabNetwork": "網路", + "SettingsTabNetworkConnection": "網路連線", + "SettingsTabCpuCache": "CPU 快取", + "SettingsTabCpuMemory": "CPU 模式", + "DialogUpdaterFlatpakNotSupportedMessage": "請透過 Flathub 更新 Ryujinx。", + "UpdaterDisabledWarningTitle": "更新已停用!", + "ControllerSettingsRotate90": "順時針旋轉 90°", + "IconSize": "圖示大小", + "IconSizeTooltip": "變更遊戲圖示的大小", + "MenuBarOptionsShowConsole": "顯示控制台", + "ShaderCachePurgeError": "在 {0} 清除著色器快取時出錯: {1}", + "UserErrorNoKeys": "找不到金鑰", + "UserErrorNoFirmware": "找不到韌體", + "UserErrorFirmwareParsingFailed": "韌體解析錯誤", + "UserErrorApplicationNotFound": "找不到應用程式", + "UserErrorUnknown": "未知錯誤", + "UserErrorUndefined": "未定義錯誤", + "UserErrorNoKeysDescription": "Ryujinx 無法找到您的「prod.keys」檔案", + "UserErrorNoFirmwareDescription": "Ryujinx 無法找到已安裝的任何韌體", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx 無法解析所提供的韌體。這通常是由於金鑰過時造成的。", + "UserErrorApplicationNotFoundDescription": "Ryujinx 無法在指定路徑下找到有效的應用程式。", + "UserErrorUnknownDescription": "發生未知錯誤!", + "UserErrorUndefinedDescription": "發生未定義錯誤! 這種情況不應該發生,請聯絡開發人員!", + "OpenSetupGuideMessage": "開啟設定指南", + "NoUpdate": "沒有更新", + "TitleUpdateVersionLabel": "版本 {0}", + "RyujinxInfo": "Ryujinx - 資訊", + "RyujinxConfirm": "Ryujinx - 確認", + "FileDialogAllTypes": "全部類型", + "Never": "從不", + "SwkbdMinCharacters": "長度必須至少為 {0} 個字元", + "SwkbdMinRangeCharacters": "長度必須為 {0} 到 {1} 個字元", + "SoftwareKeyboard": "軟體鍵盤", + "SoftwareKeyboardModeNumeric": "必須是 0 到 9 或「.」", + "SoftwareKeyboardModeAlphabet": "必須是「非中日韓字元」 (non CJK)", + "SoftwareKeyboardModeASCII": "必須是 ASCII 文字", + "ControllerAppletControllers": "支援的控制器:", + "ControllerAppletPlayers": "玩家:", + "ControllerAppletDescription": "您目前的配置無效。開啟設定並重新配置輸入。", + "ControllerAppletDocked": "已設定底座模式。手提控制應該停用。", + "UpdaterRenaming": "正在重新命名舊檔案...", + "UpdaterRenameFailed": "更新程式無法重新命名檔案: {0}", + "UpdaterAddingFiles": "正在加入新檔案...", + "UpdaterExtracting": "正在提取更新...", + "UpdaterDownloading": "正在下載更新...", + "Game": "遊戲", + "Docked": "底座模式", + "Handheld": "手提模式", + "ConnectionError": "連線錯誤。", + "AboutPageDeveloperListMore": "{0} 等人...", + "ApiError": "API 錯誤。", + "LoadingHeading": "正在載入 {0}", + "CompilingPPTC": "正在編譯 PTC", + "CompilingShaders": "正在編譯著色器", + "AllKeyboards": "所有鍵盤", + "OpenFileDialogTitle": "選取支援的檔案格式", + "OpenFolderDialogTitle": "選取未封裝遊戲的資料夾", + "AllSupportedFormats": "所有支援的格式", + "RyujinxUpdater": "Ryujinx 更新程式", + "SettingsTabHotkeys": "鍵盤快速鍵", + "SettingsTabHotkeysHotkeys": "鍵盤快捷鍵", + "SettingsTabHotkeysToggleVsyncHotkey": "切換垂直同步:", + "SettingsTabHotkeysScreenshotHotkey": "擷取畫面:", + "SettingsTabHotkeysShowUiHotkey": "顯示 UI:", + "SettingsTabHotkeysPauseHotkey": "暫停:", + "SettingsTabHotkeysToggleMuteHotkey": "靜音:", + "ControllerMotionTitle": "體感控制設定", + "ControllerRumbleTitle": "震動設定", + "SettingsSelectThemeFileDialogTitle": "選取佈景主題檔案", + "SettingsXamlThemeFile": "Xaml 佈景主題檔案", + "AvatarWindowTitle": "管理帳戶 - 大頭貼", + "Amiibo": "Amiibo", + "Unknown": "未知", + "Usage": "用途", + "Writable": "可寫入", + "SelectDlcDialogTitle": "選取 DLC 檔案", + "SelectUpdateDialogTitle": "選取更新檔", + "SelectModDialogTitle": "選取模組資料夾", + "UserProfileWindowTitle": "使用者設定檔管理員", + "CheatWindowTitle": "密技管理員", + "DlcWindowTitle": "管理 {0} 的可下載內容 ({1})", + "ModWindowTitle": "管理 {0} 的模組 ({1})", + "UpdateWindowTitle": "遊戲更新管理員", + "CheatWindowHeading": "可用於 {0} [{1}] 的密技", + "BuildId": "組建識別碼:", + "DlcWindowHeading": "{0} 個可下載內容", + "ModWindowHeading": "{0} 模組", + "UserProfilesEditProfile": "編輯所選", + "Cancel": "取消", + "Save": "儲存", + "Discard": "放棄變更", + "Paused": "暫停", + "UserProfilesSetProfileImage": "設定設定檔圖像", + "UserProfileEmptyNameError": "名稱為必填", + "UserProfileNoImageError": "必須設定設定檔圖像", + "GameUpdateWindowHeading": "管理 {0} 的更新 ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "提高解析度:", + "SettingsTabHotkeysResScaleDownHotkey": "降低解析度:", + "UserProfilesName": "名稱:", + "UserProfilesUserId": "使用者 ID:", + "SettingsTabGraphicsBackend": "圖形後端", + "SettingsTabGraphicsBackendTooltip": "選擇模擬器將使用的圖形後端。\n\n只要驅動程式是最新的,Vulkan 對所有現代顯示卡來說都更好用。Vulkan 還能在所有 GPU 廠商上實現更快的著色器編譯 (減少卡頓)。\n\nOpenGL 在舊式 Nvidia GPU、Linux 上的舊式 AMD GPU 或 VRAM 較低的 GPU 上可能會取得更好的效果,不過著色器編譯的卡頓會更嚴重。\n\n如果不確定,請設定為 Vulkan。如果您的 GPU 使用最新的圖形驅動程式也不支援 Vulkan,請設定為 OpenGL。", + "SettingsEnableTextureRecompression": "開啟材質重新壓縮", + "SettingsEnableTextureRecompressionTooltip": "壓縮 ASTC 紋理,以減少 VRAM 占用。\n\n使用這種紋理格式的遊戲包括 Astral Chain、Bayonetta 3、Fire Emblem Engage、Metroid Prime Remastered、Super Mario Bros. Wonder 和 The Legend of Zelda: Tears of the Kingdom。\n\n使用 4GB 或更低 VRAM 的顯示卡在執行這些遊戲時可能會崩潰。\n\n只有在上述遊戲的 VRAM 即將耗盡時才啟用。如果不確定,請保持關閉狀態。", + "SettingsTabGraphicsPreferredGpu": "優先選取的 GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "選擇將與 Vulkan 圖形後端一起使用的顯示卡。\n\n不會影響 OpenGL 將使用的 GPU。\n\n如果不確定,請設定為標記為「dGPU」的 GPU。如果沒有,則保持原狀。", + "SettingsAppRequiredRestartMessage": "需要重新啟動 Ryujinx", + "SettingsGpuBackendRestartMessage": "圖形後端或 GPU 設定已修改。這需要重新啟動才能套用。", + "SettingsGpuBackendRestartSubMessage": "您現在要重新啟動嗎?", + "RyujinxUpdaterMessage": "您想將 Ryujinx 升級到最新版本嗎?", + "SettingsTabHotkeysVolumeUpHotkey": "提高音量:", + "SettingsTabHotkeysVolumeDownHotkey": "降低音量:", + "SettingsEnableMacroHLE": "啟用 Macro HLE", + "SettingsEnableMacroHLETooltip": "GPU 巨集程式碼的進階模擬。\n\n可提高效能,但在某些遊戲中可能會導致圖形閃爍。\n\n如果不確定,請保持開啟狀態。", + "SettingsEnableColorSpacePassthrough": "色彩空間直通", + "SettingsEnableColorSpacePassthroughTooltip": "指示 Vulkan 後端在不指定色彩空間的情況下傳遞色彩資訊。對於使用廣色域顯示器的使用者來說,這可能會帶來更鮮艷的色彩,但代價是犧牲色彩的正確性。", + "VolumeShort": "音量", + "UserProfilesManageSaves": "管理存檔", + "DeleteUserSave": "您想刪除此遊戲的使用者存檔嗎?", + "IrreversibleActionNote": "此動作將無法復原。", + "SaveManagerHeading": "管理 {0} 的存檔 ({1})", + "SaveManagerTitle": "存檔管理員", + "Name": "名稱", + "Size": "大小", + "Search": "搜尋", + "UserProfilesRecoverLostAccounts": "復原遺失的帳戶", + "Recover": "復原", + "UserProfilesRecoverHeading": "發現下列帳戶有一些存檔", + "UserProfilesRecoverEmptyList": "無設定檔可復原", + "GraphicsAATooltip": "對遊戲繪製進行反鋸齒處理。\n\nFXAA 會模糊大部分圖像,而 SMAA 則會嘗試找出鋸齒邊緣並將其平滑化。\n\n不建議與 FSR 縮放濾鏡一起使用。\n\n此選項可在遊戲執行時透過點選下方的「套用」進行變更;您只需將設定視窗移到一旁,然後進行試驗,直到找到您喜歡的遊戲效果。\n\n如果不確定,請選擇無狀態。", + "GraphicsAALabel": "反鋸齒:", + "GraphicsScalingFilterLabel": "縮放過濾器:", + "GraphicsScalingFilterTooltip": "選擇使用解析度縮放時套用的縮放過濾器。\n\n雙線性 (Bilinear) 濾鏡適用於 3D 遊戲,是一個安全的預設選項。\n\n建議像素美術遊戲使用近鄰性 (Nearest) 濾鏡。\n\nFSR 1.0 只是一個銳化濾鏡,不建議與 FXAA 或 SMAA 一起使用。\n\n此選項可在遊戲執行時透過點選下方的「套用」進行變更;您只需將設定視窗移到一旁,然後進行試驗,直到找到您喜歡的遊戲效果。\n\n如果不確定,請保持雙線性 (Bilinear) 狀態。", + "GraphicsScalingFilterBilinear": "雙線性 (Bilinear)", + "GraphicsScalingFilterNearest": "近鄰性 (Nearest)", + "GraphicsScalingFilterFsr": "FSR", + "GraphicsScalingFilterLevelLabel": "日誌等級", + "GraphicsScalingFilterLevelTooltip": "設定 FSR 1.0 銳化等級。越高越清晰。", + "SmaaLow": "低階 SMAA", + "SmaaMedium": "中階 SMAA", + "SmaaHigh": "高階 SMAA", + "SmaaUltra": "超高階 SMAA", + "UserEditorTitle": "編輯使用者", + "UserEditorTitleCreate": "建立使用者", + "SettingsTabNetworkInterface": "網路介面:", + "NetworkInterfaceTooltip": "用於 LAN/LDN 功能的網路介面。\n\n與 VPN 或 XLink Kai 以及支援區域網路的遊戲配合使用,可用於在網路上偽造同網際網路連線。\n\n如果不確定,請保持預設狀態。", + "NetworkInterfaceDefault": "預設", + "PackagingShaders": "封裝著色器", + "AboutChangelogButton": "在 GitHub 上檢視更新日誌", + "AboutChangelogButtonTooltipMessage": "在預設瀏覽器中開啟此版本的更新日誌。", + "SettingsTabNetworkMultiplayer": "多人遊戲", + "MultiplayerMode": "模式:", + "MultiplayerModeTooltip": "變更 LDN 多人遊戲模式。\n\nLdnMitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。", + "MultiplayerModeDisabled": "已停用", + "MultiplayerModeLdnMitm": "ldn_mitm" +} diff --git a/src/Ryujinx/Assets/Styles/Styles.xaml b/src/Ryujinx/Assets/Styles/Styles.xaml new file mode 100644 index 00000000..b3a6f59c --- /dev/null +++ b/src/Ryujinx/Assets/Styles/Styles.xaml @@ -0,0 +1,395 @@ + + + + + + +

+ + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs b/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs new file mode 100644 index 00000000..6b999b1f --- /dev/null +++ b/src/Ryujinx/UI/Applet/ControllerAppletDialog.axaml.cs @@ -0,0 +1,137 @@ +using Avalonia.Controls; +using Avalonia.Styling; +using Avalonia.Svg.Skia; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Hid; +using System.Linq; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Applet +{ + internal partial class ControllerAppletDialog : UserControl + { + private const string ProControllerResource = "Ryujinx/Assets/Icons/Controller_ProCon.svg"; + private const string JoyConPairResource = "Ryujinx/Assets/Icons/Controller_JoyConPair.svg"; + private const string JoyConLeftResource = "Ryujinx/Assets/Icons/Controller_JoyConLeft.svg"; + private const string JoyConRightResource = "Ryujinx/Assets/Icons/Controller_JoyConRight.svg"; + + public static SvgImage ProControllerImage => GetResource(ProControllerResource); + public static SvgImage JoyconPairImage => GetResource(JoyConPairResource); + public static SvgImage JoyconLeftImage => GetResource(JoyConLeftResource); + public static SvgImage JoyconRightImage => GetResource(JoyConRightResource); + + public string PlayerCount { get; set; } = ""; + public bool SupportsProController { get; set; } + public bool SupportsLeftJoycon { get; set; } + public bool SupportsRightJoycon { get; set; } + public bool SupportsJoyconPair { get; set; } + public bool IsDocked { get; set; } + + private readonly MainWindow _mainWindow; + + public ControllerAppletDialog(MainWindow mainWindow, ControllerAppletUIArgs args) + { + if (args.PlayerCountMin == args.PlayerCountMax) + { + PlayerCount = args.PlayerCountMin.ToString(); + } + else + { + PlayerCount = $"{args.PlayerCountMin} - {args.PlayerCountMax}"; + } + + SupportsProController = (args.SupportedStyles & ControllerType.ProController) != 0; + SupportsLeftJoycon = (args.SupportedStyles & ControllerType.JoyconLeft) != 0; + SupportsRightJoycon = (args.SupportedStyles & ControllerType.JoyconRight) != 0; + SupportsJoyconPair = (args.SupportedStyles & ControllerType.JoyconPair) != 0; + + IsDocked = args.IsDocked; + + _mainWindow = mainWindow; + + DataContext = this; + + InitializeComponent(); + } + + public ControllerAppletDialog(MainWindow mainWindow) + { + _mainWindow = mainWindow; + DataContext = this; + + InitializeComponent(); + } + + public static async Task ShowControllerAppletDialog(MainWindow window, ControllerAppletUIArgs args) + { + ContentDialog contentDialog = new(); + UserResult result = UserResult.Cancel; + ControllerAppletDialog content = new(window, args); + + contentDialog.Title = LocaleManager.Instance[LocaleKeys.DialogControllerAppletTitle]; + contentDialog.Content = content; + + void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs) + { + if (eventArgs.Result == ContentDialogResult.Primary) + { + result = UserResult.Ok; + } + } + + contentDialog.Closed += Handler; + + Style bottomBorder = new(x => x.OfType().Name("DialogSpace").Child().OfType()); + bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false)); + + contentDialog.Styles.Add(bottomBorder); + + await ContentDialogHelper.ShowAsync(contentDialog); + + return result; + } + + private static SvgImage GetResource(string path) + { + SvgImage image = new(); + + if (!string.IsNullOrWhiteSpace(path)) + { + SvgSource source = SvgSource.LoadFromStream(EmbeddedResources.GetStream(path)); + + image.Source = source; + } + + return image; + } + + public void OpenSettingsWindow() + { + if (_mainWindow.SettingsWindow == null) + { + Dispatcher.UIThread.InvokeAsync(async () => + { + _mainWindow.SettingsWindow = new SettingsWindow(_mainWindow.VirtualFileSystem, _mainWindow.ContentManager); + _mainWindow.SettingsWindow.NavPanel.Content = _mainWindow.SettingsWindow.InputPage; + _mainWindow.SettingsWindow.NavPanel.SelectedItem = _mainWindow.SettingsWindow.NavPanel.MenuItems.ElementAt(1); + + await ContentDialogHelper.ShowWindowAsync(_mainWindow.SettingsWindow, _mainWindow); + _mainWindow.SettingsWindow = null; + this.Close(); + }); + } + } + + public void Close() + { + ((ContentDialog)Parent)?.Hide(); + } + } +} + diff --git a/src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml b/src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml new file mode 100644 index 00000000..51f37051 --- /dev/null +++ b/src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml.cs b/src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml.cs new file mode 100644 index 00000000..ec6f7682 --- /dev/null +++ b/src/Ryujinx/UI/Applet/ErrorAppletWindow.axaml.cs @@ -0,0 +1,74 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Windows; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Applet +{ + internal partial class ErrorAppletWindow : StyleableWindow + { + private readonly Window _owner; + private object _buttonResponse; + + public ErrorAppletWindow(Window owner, string[] buttons, string message) + { + _owner = owner; + Message = message; + DataContext = this; + InitializeComponent(); + + int responseId = 0; + + if (buttons != null) + { + foreach (string buttonText in buttons) + { + AddButton(buttonText, responseId); + responseId++; + } + } + else + { + AddButton(LocaleManager.Instance[LocaleKeys.InputDialogOk], 0); + } + } + + public ErrorAppletWindow() + { + DataContext = this; + InitializeComponent(); + } + + public string Message { get; set; } + + private void AddButton(string label, object tag) + { + Dispatcher.UIThread.InvokeAsync(() => + { + Button button = new() { Content = label, Tag = tag }; + + button.Click += Button_Click; + ButtonStack.Children.Add(button); + }); + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + _buttonResponse = button.Tag; + } + + Close(); + } + + public async Task Run() + { + await ShowDialog(_owner); + + return _buttonResponse; + } + } +} diff --git a/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml new file mode 100644 index 00000000..b1c84734 --- /dev/null +++ b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs new file mode 100644 index 00000000..af3837e4 --- /dev/null +++ b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs @@ -0,0 +1,183 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Media; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Controls +{ + internal partial class SwkbdAppletDialog : UserControl + { + private Predicate _checkLength = _ => true; + private Predicate _checkInput = _ => true; + private int _inputMax; + private int _inputMin; + private readonly string _placeholder; + + private ContentDialog _host; + + public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder, string message) + { + MainText = mainText; + SecondaryText = secondaryText; + Message = message ?? ""; + DataContext = this; + _placeholder = placeholder; + InitializeComponent(); + + Input.Watermark = _placeholder; + + Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true); + } + + public SwkbdAppletDialog() + { + DataContext = this; + 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; } = ""; + + public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, SoftwareKeyboardUIArgs args) + { + ContentDialog contentDialog = new(); + + UserResult result = UserResult.Cancel; + + SwkbdAppletDialog content = new(args.HeaderText, args.SubtitleText, args.GuideText, args.InitialText); + + string input = string.Empty; + + content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); + content.SetInputValidation(args.KeyboardMode); + + content._host = contentDialog; + contentDialog.Title = title; + contentDialog.PrimaryButtonText = args.SubmitText; + contentDialog.IsPrimaryButtonEnabled = content._checkLength(content.Message.Length); + contentDialog.SecondaryButtonText = ""; + contentDialog.CloseButtonText = LocaleManager.Instance[LocaleKeys.InputDialogCancel]; + contentDialog.Content = content; + + void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs) + { + if (eventArgs.Result == ContentDialogResult.Primary) + { + result = UserResult.Ok; + input = content.Input.Text; + } + } + + contentDialog.Closed += Handler; + + await ContentDialogHelper.ShowAsync(contentDialog); + + return (result, input); + } + + private void ApplyValidationInfo(string text) + { + Error.IsVisible = !string.IsNullOrEmpty(text); + Error.Text = text; + } + + public void SetInputLengthValidation(int min, int max) + { + _inputMin = Math.Min(min, max); + _inputMax = Math.Max(min, max); + + Error.IsVisible = false; + Error.FontStyle = FontStyle.Italic; + + string validationInfoText = ""; + + if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable. + { + Error.IsVisible = false; + + _checkLength = length => true; + } + else if (_inputMin > 0 && _inputMax == int.MaxValue) + { + validationInfoText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinCharacters, _inputMin); + + _checkLength = length => _inputMin <= length; + } + else + { + validationInfoText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinRangeCharacters, _inputMin, _inputMax); + + _checkLength = length => _inputMin <= length && length <= _inputMax; + } + + ApplyValidationInfo(validationInfoText); + Message_TextInput(this, new TextInputEventArgs()); + } + + private void SetInputValidation(KeyboardMode mode) + { + string validationInfoText = Error.Text; + string localeText; + switch (mode) + { + case KeyboardMode.Numeric: + localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumeric); + validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText); + _checkInput = text => text.All(NumericCharacterValidation.IsNumeric); + break; + case KeyboardMode.Alphabet: + localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeAlphabet); + validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText); + _checkInput = text => text.All(value => !CJKCharacterValidation.IsCJK(value)); + break; + case KeyboardMode.ASCII: + localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeASCII); + validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText); + _checkInput = text => text.All(char.IsAscii); + break; + default: + _checkInput = _ => true; + break; + } + + ApplyValidationInfo(validationInfoText); + Message_TextInput(this, new TextInputEventArgs()); + } + + private void Message_TextInput(object sender, TextInputEventArgs e) + { + if (_host != null) + { + _host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message); + } + } + + private void Message_KeyUp(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter && _host.IsPrimaryButtonEnabled) + { + _host.Hide(ContentDialogResult.Primary); + } + else + { + _host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message); + } + } + } +} diff --git a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml new file mode 100644 index 00000000..dd0926fc --- /dev/null +++ b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs new file mode 100644 index 00000000..5edd0230 --- /dev/null +++ b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs @@ -0,0 +1,359 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using LibHac.Fs; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common.Configuration; +using Ryujinx.HLE.HOS; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common.Helper; +using System; +using System.Collections.Generic; +using System.IO; +using Path = System.IO.Path; + +namespace Ryujinx.Ava.UI.Controls +{ + public class ApplicationContextMenu : MenuFlyout + { + public ApplicationContextMenu() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + public void ToggleFavorite_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite; + + ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata => + { + appMetadata.Favorite = viewModel.SelectedApplication.Favorite; + }); + + viewModel.RefreshView(); + } + } + + public void OpenUserSaveDirectory_Click(object sender, RoutedEventArgs args) + { + if (sender is MenuItem { DataContext: MainWindowViewModel viewModel }) + { + OpenSaveDirectory(viewModel, SaveDataType.Account, new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low)); + } + } + + public void OpenDeviceSaveDirectory_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + OpenSaveDirectory(viewModel, SaveDataType.Device, default); + } + + public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + OpenSaveDirectory(viewModel, SaveDataType.Bcat, default); + } + + private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId) + { + if (viewModel?.SelectedApplication != null) + { + var saveDataFilter = SaveDataFilter.Make(viewModel.SelectedApplication.Id, saveDataType, userId, saveDataId: default, index: default); + + ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name); + } + } + + public async void OpenTitleUpdateManager_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await TitleUpdateWindow.Show(viewModel.VirtualFileSystem, viewModel.SelectedApplication); + } + } + + public async void OpenDownloadableContentManager_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await DownloadableContentManagerWindow.Show(viewModel.VirtualFileSystem, viewModel.SelectedApplication); + } + } + + public async void OpenCheatManager_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await new CheatWindow( + viewModel.VirtualFileSystem, + viewModel.SelectedApplication.IdString, + viewModel.SelectedApplication.Name, + viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window); + } + } + + public void OpenModsDirectory_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + string modsBasePath = ModLoader.GetModsBasePath(); + string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString); + + OpenHelper.OpenFolder(titleModsPath); + } + } + + public void OpenSdModsDirectory_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + string sdModsBasePath = ModLoader.GetSdModsBasePath(); + string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString); + + OpenHelper.OpenFolder(titleModsPath); + } + } + + public async void OpenModManager_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await ModManagerWindow.Show(viewModel.SelectedApplication.Id, viewModel.SelectedApplication.Name); + } + } + + public async void PurgePtcCache_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogWarning], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name), + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0")); + DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1")); + + List cacheFiles = new(); + + if (mainDir.Exists) + { + cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); + } + + if (backupDir.Exists) + { + cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); + } + + if (cacheFiles.Count > 0) + { + foreach (FileInfo file in cacheFiles) + { + try + { + file.Delete(); + } + catch (Exception ex) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex)); + } + } + } + } + } + } + + public async void PurgeShaderCache_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogWarning], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.Name), + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader")); + + List oldCacheDirectories = new(); + List newCacheFiles = new(); + + if (shaderCacheDir.Exists) + { + oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*")); + newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc")); + newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data")); + } + + if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0)) + { + foreach (DirectoryInfo directory in oldCacheDirectories) + { + try + { + directory.Delete(true); + } + catch (Exception ex) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, ex)); + } + } + + foreach (FileInfo file in newCacheFiles) + { + try + { + file.Delete(); + } + catch (Exception ex) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, ex)); + } + } + } + } + } + } + + public void OpenPtcDirectory_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu"); + string mainDir = Path.Combine(ptcDir, "0"); + string backupDir = Path.Combine(ptcDir, "1"); + + if (!Directory.Exists(ptcDir)) + { + Directory.CreateDirectory(ptcDir); + Directory.CreateDirectory(mainDir); + Directory.CreateDirectory(backupDir); + } + + OpenHelper.OpenFolder(ptcDir); + } + } + + public void OpenShaderCacheDirectory_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader"); + + if (!Directory.Exists(shaderCacheDir)) + { + Directory.CreateDirectory(shaderCacheDir); + } + + OpenHelper.OpenFolder(shaderCacheDir); + } + } + + public async void ExtractApplicationExeFs_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await ApplicationHelper.ExtractSection( + viewModel.StorageProvider, + NcaSectionType.Code, + viewModel.SelectedApplication.Path, + viewModel.SelectedApplication.Name); + } + } + + public async void ExtractApplicationRomFs_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await ApplicationHelper.ExtractSection( + viewModel.StorageProvider, + NcaSectionType.Data, + viewModel.SelectedApplication.Path, + viewModel.SelectedApplication.Name); + } + } + + public async void ExtractApplicationLogo_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await ApplicationHelper.ExtractSection( + viewModel.StorageProvider, + NcaSectionType.Logo, + viewModel.SelectedApplication.Path, + viewModel.SelectedApplication.Name); + } + } + + public void CreateApplicationShortcut_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + ApplicationData selectedApplication = viewModel.SelectedApplication; + ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.Name, selectedApplication.IdString, selectedApplication.Icon); + } + } + + public async void RunApplication_Click(object sender, RoutedEventArgs args) + { + var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; + + if (viewModel?.SelectedApplication != null) + { + await viewModel.LoadApplication(viewModel.SelectedApplication); + } + } + } +} diff --git a/src/Ryujinx/UI/Controls/ApplicationGridView.axaml b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml new file mode 100644 index 00000000..98a1c004 --- /dev/null +++ b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Controls/ApplicationGridView.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml.cs new file mode 100644 index 00000000..ee15bc8d --- /dev/null +++ b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml.cs @@ -0,0 +1,51 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.UI.App.Common; +using System; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class ApplicationGridView : UserControl + { + public static readonly RoutedEvent ApplicationOpenedEvent = + RoutedEvent.Register(nameof(ApplicationOpened), RoutingStrategies.Bubble); + + public event EventHandler ApplicationOpened + { + add { AddHandler(ApplicationOpenedEvent, value); } + remove { RemoveHandler(ApplicationOpenedEvent, value); } + } + + public ApplicationGridView() + { + InitializeComponent(); + } + + public void GameList_DoubleTapped(object sender, TappedEventArgs args) + { + if (sender is ListBox listBox) + { + if (listBox.SelectedItem is ApplicationData selected) + { + RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent)); + } + } + } + + public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args) + { + if (sender is ListBox listBox) + { + (DataContext as MainWindowViewModel).GridSelectedApplication = listBox.SelectedItem as ApplicationData; + } + } + + private void SearchBox_OnKeyUp(object sender, KeyEventArgs args) + { + (DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text; + } + } +} diff --git a/src/Ryujinx/UI/Controls/ApplicationListView.axaml b/src/Ryujinx/UI/Controls/ApplicationListView.axaml new file mode 100644 index 00000000..f99cf316 --- /dev/null +++ b/src/Ryujinx/UI/Controls/ApplicationListView.axaml @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs b/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs new file mode 100644 index 00000000..8681158f --- /dev/null +++ b/src/Ryujinx/UI/Controls/ApplicationListView.axaml.cs @@ -0,0 +1,51 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.UI.App.Common; +using System; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class ApplicationListView : UserControl + { + public static readonly RoutedEvent ApplicationOpenedEvent = + RoutedEvent.Register(nameof(ApplicationOpened), RoutingStrategies.Bubble); + + public event EventHandler ApplicationOpened + { + add { AddHandler(ApplicationOpenedEvent, value); } + remove { RemoveHandler(ApplicationOpenedEvent, value); } + } + + public ApplicationListView() + { + InitializeComponent(); + } + + public void GameList_DoubleTapped(object sender, TappedEventArgs args) + { + if (sender is ListBox listBox) + { + if (listBox.SelectedItem is ApplicationData selected) + { + RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent)); + } + } + } + + public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args) + { + if (sender is ListBox listBox) + { + (DataContext as MainWindowViewModel).ListSelectedApplication = listBox.SelectedItem as ApplicationData; + } + } + + private void SearchBox_OnKeyUp(object sender, KeyEventArgs args) + { + (DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text; + } + } +} diff --git a/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml b/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml new file mode 100644 index 00000000..bf34b303 --- /dev/null +++ b/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs b/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs new file mode 100644 index 00000000..a32c052b --- /dev/null +++ b/src/Ryujinx/UI/Controls/NavigationDialogHost.axaml.cs @@ -0,0 +1,217 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Styling; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Views.User; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using UserId = Ryujinx.HLE.HOS.Services.Account.Acc.UserId; +using UserProfile = Ryujinx.Ava.UI.Models.UserProfile; + +namespace Ryujinx.Ava.UI.Controls +{ + public partial class NavigationDialogHost : UserControl + { + public AccountManager AccountManager { get; } + public ContentManager ContentManager { get; } + public VirtualFileSystem VirtualFileSystem { get; } + public HorizonClient HorizonClient { get; } + public UserProfileViewModel ViewModel { get; set; } + + public NavigationDialogHost() + { + InitializeComponent(); + } + + public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager, + VirtualFileSystem virtualFileSystem, HorizonClient horizonClient) + { + AccountManager = accountManager; + ContentManager = contentManager; + VirtualFileSystem = virtualFileSystem; + HorizonClient = horizonClient; + ViewModel = new UserProfileViewModel(); + LoadProfiles(); + + if (contentManager.GetCurrentFirmwareVersion() != null) + { + Task.Run(() => + { + UserFirmwareAvatarSelectorViewModel.PreloadAvatars(contentManager, virtualFileSystem); + }); + } + InitializeComponent(); + } + + public void GoBack() + { + if (ContentFrame.BackStack.Count > 0) + { + ContentFrame.GoBack(); + } + + LoadProfiles(); + } + + public void Navigate(Type sourcePageType, object parameter) + { + ContentFrame.Navigate(sourcePageType, parameter); + } + + public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, + VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient) + { + var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient); + ContentDialog contentDialog = new() + { + Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle], + PrimaryButtonText = "", + SecondaryButtonText = "", + CloseButtonText = "", + Content = content, + Padding = new Thickness(0), + }; + + contentDialog.Closed += (sender, args) => + { + content.ViewModel.Dispose(); + }; + + Style footer = new(x => x.Name("DialogSpace").Child().OfType()); + footer.Setters.Add(new Setter(IsVisibleProperty, false)); + + contentDialog.Styles.Add(footer); + + await contentDialog.ShowAsync(); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + Navigate(typeof(UserSelectorViews), this); + } + + public void LoadProfiles() + { + ViewModel.Profiles.Clear(); + ViewModel.LostProfiles.Clear(); + + var profiles = AccountManager.GetAllUsers().OrderBy(x => x.Name); + + foreach (var profile in profiles) + { + ViewModel.Profiles.Add(new UserProfile(profile, this)); + } + + var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account, default, saveDataId: default, index: default); + + using var saveDataIterator = new UniqueRef(); + + HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure(); + + Span saveDataInfo = stackalloc SaveDataInfo[10]; + + HashSet lostAccounts = new(); + + while (true) + { + saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure(); + + if (readCount == 0) + { + break; + } + + for (int i = 0; i < readCount; i++) + { + var save = saveDataInfo[i]; + var id = new UserId((long)save.UserId.Id.Low, (long)save.UserId.Id.High); + if (ViewModel.Profiles.Cast().FirstOrDefault(x => x.UserId == id) == null) + { + lostAccounts.Add(id); + } + } + } + + foreach (var account in lostAccounts) + { + ViewModel.LostProfiles.Add(new UserProfile(new HLE.HOS.Services.Account.Acc.UserProfile(account, "", null), this)); + } + + ViewModel.Profiles.Add(new BaseModel()); + } + + public async void DeleteUser(UserProfile userProfile) + { + var lastUserId = AccountManager.LastOpenedUser.UserId; + + if (userProfile.UserId == lastUserId) + { + // If we are deleting the currently open profile, then we must open something else before deleting. + var profile = ViewModel.Profiles.Cast().FirstOrDefault(x => x.UserId != lastUserId); + + if (profile == null) + { + static async void Action() + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionWarningMessage]); + } + + Dispatcher.UIThread.Post(Action); + + return; + } + + AccountManager.OpenUser(profile.UserId); + } + + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionConfirmMessage], + "", + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + ""); + + if (result == UserResult.Yes) + { + GoBack(); + AccountManager.DeleteUser(userProfile.UserId); + } + + LoadProfiles(); + } + + public void AddUser() + { + Navigate(typeof(UserEditorView), (this, (UserProfile)null, true)); + } + + public void EditUser(UserProfile userProfile) + { + Navigate(typeof(UserEditorView), (this, userProfile, false)); + } + + public void RecoverLostAccounts() + { + Navigate(typeof(UserRecovererView), this); + } + + public void ManageSaves() + { + Navigate(typeof(UserSaveManagerView), (this, AccountManager, HorizonClient, VirtualFileSystem)); + } + } +} diff --git a/src/Ryujinx/UI/Controls/SliderScroll.axaml.cs b/src/Ryujinx/UI/Controls/SliderScroll.axaml.cs new file mode 100644 index 00000000..b57c6f0a --- /dev/null +++ b/src/Ryujinx/UI/Controls/SliderScroll.axaml.cs @@ -0,0 +1,31 @@ +using Avalonia.Controls; +using Avalonia.Input; +using System; + +namespace Ryujinx.Ava.UI.Controls +{ + public class SliderScroll : Slider + { + protected override Type StyleKeyOverride => typeof(Slider); + + protected override void OnPointerWheelChanged(PointerWheelEventArgs e) + { + var newValue = Value + e.Delta.Y * TickFrequency; + + if (newValue < Minimum) + { + Value = Minimum; + } + else if (newValue > Maximum) + { + Value = Maximum; + } + else + { + Value = newValue; + } + + e.Handled = true; + } + } +} diff --git a/src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml b/src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml new file mode 100644 index 00000000..09fa0404 --- /dev/null +++ b/src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml.cs b/src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml.cs new file mode 100644 index 00000000..7ad1ee33 --- /dev/null +++ b/src/Ryujinx/UI/Controls/UpdateWaitWindow.axaml.cs @@ -0,0 +1,31 @@ +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() + { + InitializeComponent(); + } + } +} diff --git a/src/Ryujinx/UI/Helpers/ApplicationOpenedEventArgs.cs b/src/Ryujinx/UI/Helpers/ApplicationOpenedEventArgs.cs new file mode 100644 index 00000000..bc5622b5 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/ApplicationOpenedEventArgs.cs @@ -0,0 +1,16 @@ +using Avalonia.Interactivity; +using Ryujinx.UI.App.Common; + +namespace Ryujinx.Ava.UI.Helpers +{ + public class ApplicationOpenedEventArgs : RoutedEventArgs + { + public ApplicationData Application { get; } + + public ApplicationOpenedEventArgs(ApplicationData application, RoutedEvent routedEvent) + { + Application = application; + RoutedEvent = routedEvent; + } + } +} diff --git a/src/Ryujinx/UI/Helpers/BitmapArrayValueConverter.cs b/src/Ryujinx/UI/Helpers/BitmapArrayValueConverter.cs new file mode 100644 index 00000000..42bd8d5a --- /dev/null +++ b/src/Ryujinx/UI/Helpers/BitmapArrayValueConverter.cs @@ -0,0 +1,36 @@ +using Avalonia.Data.Converters; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using System; +using System.Globalization; +using System.IO; + +namespace Ryujinx.Ava.UI.Helpers +{ + internal class BitmapArrayValueConverter : IValueConverter + { + public static BitmapArrayValueConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + { + return null; + } + + if (value is byte[] buffer && targetType == typeof(IImage)) + { + MemoryStream mem = new(buffer); + + return new Bitmap(mem); + } + + throw new NotSupportedException(); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs b/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs new file mode 100644 index 00000000..4f2b8daa --- /dev/null +++ b/src/Ryujinx/UI/Helpers/ButtonKeyAssigner.cs @@ -0,0 +1,102 @@ +using Avalonia.Controls.Primitives; +using Avalonia.Threading; +using Ryujinx.Input; +using Ryujinx.Input.Assigner; +using System; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Helpers +{ + internal class ButtonKeyAssigner + { + internal class ButtonAssignedEventArgs : EventArgs + { + public ToggleButton Button { get; } + public Button? ButtonValue { get; } + + public ButtonAssignedEventArgs(ToggleButton button, Button? buttonValue) + { + Button = button; + ButtonValue = buttonValue; + } + } + + public ToggleButton ToggledButton { get; set; } + + private bool _isWaitingForInput; + private bool _shouldUnbind; + public event EventHandler ButtonAssigned; + + public ButtonKeyAssigner(ToggleButton toggleButton) + { + ToggledButton = toggleButton; + } + + public async void GetInputAndAssign(IButtonAssigner assigner, IKeyboard keyboard = null) + { + Dispatcher.UIThread.Post(() => + { + ToggledButton.IsChecked = true; + }); + + if (_isWaitingForInput) + { + Dispatcher.UIThread.Post(() => + { + Cancel(); + }); + + return; + } + + _isWaitingForInput = true; + + assigner.Initialize(); + + await Task.Run(async () => + { + while (true) + { + if (!_isWaitingForInput) + { + return; + } + + await Task.Delay(10); + + assigner.ReadInput(); + + if (assigner.IsAnyButtonPressed() || assigner.ShouldCancel() || (keyboard != null && keyboard.IsPressed(Key.Escape))) + { + break; + } + } + }); + + await Dispatcher.UIThread.InvokeAsync(() => + { + Button? pressedButton = assigner.GetPressedButton(); + + if (_shouldUnbind) + { + pressedButton = null; + } + + _shouldUnbind = false; + _isWaitingForInput = false; + + ToggledButton.IsChecked = false; + + ButtonAssigned?.Invoke(this, new ButtonAssignedEventArgs(ToggledButton, pressedButton)); + + }); + } + + public void Cancel(bool shouldUnbind = false) + { + _isWaitingForInput = false; + ToggledButton.IsChecked = false; + _shouldUnbind = shouldUnbind; + } + } +} diff --git a/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs b/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs new file mode 100644 index 00000000..15b7ddd1 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs @@ -0,0 +1,425 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.Threading; +using FluentAvalonia.Core; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Helpers +{ + public static class ContentDialogHelper + { + private static bool _isChoiceDialogOpen; + private static ContentDialogOverlayWindow _contentDialogOverlayWindow; + + private async static Task ShowContentDialog( + string title, + object content, + string primaryButton, + string secondaryButton, + string closeButton, + UserResult primaryButtonResult = UserResult.Ok, + ManualResetEvent deferResetEvent = null, + TypedEventHandler deferCloseAction = null) + { + UserResult result = UserResult.None; + + ContentDialog contentDialog = new() + { + Title = title, + PrimaryButtonText = primaryButton, + SecondaryButtonText = secondaryButton, + CloseButtonText = closeButton, + Content = content, + PrimaryButtonCommand = MiniCommand.Create(() => + { + result = primaryButtonResult; + }), + }; + + contentDialog.SecondaryButtonCommand = MiniCommand.Create(() => + { + result = UserResult.No; + contentDialog.PrimaryButtonClick -= deferCloseAction; + }); + + contentDialog.CloseButtonCommand = MiniCommand.Create(() => + { + result = UserResult.Cancel; + contentDialog.PrimaryButtonClick -= deferCloseAction; + }); + + if (deferResetEvent != null) + { + contentDialog.PrimaryButtonClick += deferCloseAction; + } + + await ShowAsync(contentDialog); + + return result; + } + + public async static Task ShowTextDialog( + string title, + string primaryText, + string secondaryText, + string primaryButton, + string secondaryButton, + string closeButton, + int iconSymbol, + UserResult primaryButtonResult = UserResult.Ok, + ManualResetEvent deferResetEvent = null, + TypedEventHandler deferCloseAction = null) + { + Grid content = CreateTextDialogContent(primaryText, secondaryText, iconSymbol); + + return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction); + } + + public async static Task ShowDeferredContentDialog( + StyleableWindow window, + string title, + string primaryText, + string secondaryText, + string primaryButton, + string secondaryButton, + string closeButton, + int iconSymbol, + ManualResetEvent deferResetEvent, + Func doWhileDeferred = null) + { + bool startedDeferring = false; + + return await ShowTextDialog( + title, + primaryText, + secondaryText, + primaryButton, + secondaryButton, + closeButton, + iconSymbol, + primaryButton == LocaleManager.Instance[LocaleKeys.InputDialogYes] ? UserResult.Yes : UserResult.Ok, + deferResetEvent, + DeferClose); + + async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args) + { + if (startedDeferring) + { + return; + } + + sender.PrimaryButtonClick -= DeferClose; + + startedDeferring = true; + + var deferral = args.GetDeferral(); + + sender.PrimaryButtonClick -= DeferClose; + + _ = Task.Run(() => + { + deferResetEvent.WaitOne(); + + Dispatcher.UIThread.Post(() => + { + deferral.Complete(); + }); + }); + + if (doWhileDeferred != null) + { + await doWhileDeferred(window); + + deferResetEvent.Set(); + } + } + } + + private static Grid CreateTextDialogContent(string primaryText, string secondaryText, int symbol) + { + Grid content = new() + { + RowDefinitions = new RowDefinitions { new(), new() }, + ColumnDefinitions = new ColumnDefinitions { new(GridLength.Auto), new() }, + + MinHeight = 80, + }; + + SymbolIcon icon = new() + { + Symbol = (Symbol)symbol, + Margin = new Thickness(10), + FontSize = 40, + VerticalAlignment = VerticalAlignment.Center, + }; + + Grid.SetColumn(icon, 0); + Grid.SetRowSpan(icon, 2); + Grid.SetRow(icon, 0); + + TextBlock primaryLabel = new() + { + Text = primaryText, + Margin = new Thickness(5), + TextWrapping = TextWrapping.Wrap, + MaxWidth = 450, + }; + + TextBlock secondaryLabel = new() + { + Text = secondaryText, + Margin = new Thickness(5), + TextWrapping = TextWrapping.Wrap, + MaxWidth = 450, + }; + + Grid.SetColumn(primaryLabel, 1); + Grid.SetColumn(secondaryLabel, 1); + Grid.SetRow(primaryLabel, 0); + Grid.SetRow(secondaryLabel, 1); + + content.Children.Add(icon); + content.Children.Add(primaryLabel); + content.Children.Add(secondaryLabel); + + return content; + } + + public static async Task CreateInfoDialog( + string primary, + string secondaryText, + string acceptButton, + string closeButton, + string title) + { + return await ShowTextDialog( + title, + primary, + secondaryText, + acceptButton, + "", + closeButton, + (int)Symbol.Important); + } + + internal static async Task CreateConfirmationDialog( + string primaryText, + string secondaryText, + string acceptButtonText, + string cancelButtonText, + string title, + UserResult primaryButtonResult = UserResult.Yes) + { + return await ShowTextDialog( + string.IsNullOrWhiteSpace(title) ? LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle] : title, + primaryText, + secondaryText, + acceptButtonText, + "", + cancelButtonText, + (int)Symbol.Help, + primaryButtonResult); + } + + internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText) + { + await ShowTextDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterTitle], + primary, + secondaryText, + "", + "", + LocaleManager.Instance[LocaleKeys.InputDialogOk], + (int)Symbol.Important); + } + + internal static async Task CreateWarningDialog(string primary, string secondaryText) + { + await ShowTextDialog( + LocaleManager.Instance[LocaleKeys.DialogWarningTitle], + primary, + secondaryText, + "", + "", + LocaleManager.Instance[LocaleKeys.InputDialogOk], + (int)Symbol.Important); + } + + internal static async Task CreateErrorDialog(string errorMessage, string secondaryErrorMessage = "") + { + Logger.Error?.Print(LogClass.Application, errorMessage); + + await ShowTextDialog( + LocaleManager.Instance[LocaleKeys.DialogErrorTitle], + LocaleManager.Instance[LocaleKeys.DialogErrorMessage], + errorMessage, + secondaryErrorMessage, + "", + LocaleManager.Instance[LocaleKeys.InputDialogOk], + (int)Symbol.Dismiss); + } + + internal static async Task CreateChoiceDialog(string title, string primary, string secondaryText) + { + if (_isChoiceDialogOpen) + { + return false; + } + + _isChoiceDialogOpen = true; + + UserResult response = await ShowTextDialog( + title, + primary, + secondaryText, + LocaleManager.Instance[LocaleKeys.InputDialogYes], + "", + LocaleManager.Instance[LocaleKeys.InputDialogNo], + (int)Symbol.Help, + UserResult.Yes); + + _isChoiceDialogOpen = false; + + return response == UserResult.Yes; + } + + internal static async Task CreateExitDialog() + { + return await CreateChoiceDialog( + LocaleManager.Instance[LocaleKeys.DialogExitTitle], + LocaleManager.Instance[LocaleKeys.DialogExitMessage], + LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]); + } + + internal static async Task CreateStopEmulationDialog() + { + return await CreateChoiceDialog( + LocaleManager.Instance[LocaleKeys.DialogStopEmulationTitle], + LocaleManager.Instance[LocaleKeys.DialogStopEmulationMessage], + LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]); + } + + public static async Task ShowAsync(ContentDialog contentDialog) + { + ContentDialogResult result; + bool isTopDialog = true; + + Window parent = GetMainWindow(); + + if (_contentDialogOverlayWindow != null) + { + isTopDialog = false; + } + + if (parent is MainWindow window) + { + parent.Activate(); + + _contentDialogOverlayWindow = new ContentDialogOverlayWindow + { + Height = parent.Bounds.Height, + Width = parent.Bounds.Width, + Position = parent.PointToScreen(new Point()), + ShowInTaskbar = false, + }; + + parent.PositionChanged += OverlayOnPositionChanged; + + void OverlayOnPositionChanged(object sender, PixelPointEventArgs e) + { + if (_contentDialogOverlayWindow is null) + { + return; + } + + _contentDialogOverlayWindow.Position = parent.PointToScreen(new Point()); + } + + _contentDialogOverlayWindow.ContentDialog = contentDialog; + + bool opened = false; + + _contentDialogOverlayWindow.Opened += OverlayOnActivated; + + async void OverlayOnActivated(object sender, EventArgs e) + { + if (opened) + { + return; + } + + opened = true; + + _contentDialogOverlayWindow.Position = parent.PointToScreen(new Point()); + + result = await ShowDialog(); + } + + result = await _contentDialogOverlayWindow.ShowDialog(parent); + } + else + { + result = await ShowDialog(); + } + + async Task ShowDialog() + { + if (_contentDialogOverlayWindow is not null) + { + result = await contentDialog.ShowAsync(_contentDialogOverlayWindow); + + _contentDialogOverlayWindow!.Close(); + } + else + { + result = ContentDialogResult.None; + + Logger.Warning?.Print(LogClass.UI, "Content dialog overlay failed to populate. Default value has been returned."); + } + + return result; + } + + if (isTopDialog && _contentDialogOverlayWindow is not null) + { + _contentDialogOverlayWindow.Content = null; + _contentDialogOverlayWindow.Close(); + _contentDialogOverlayWindow = null; + } + + return result; + } + + public static Task ShowWindowAsync(Window dialogWindow, Window mainWindow = null) + { + mainWindow ??= GetMainWindow(); + + return dialogWindow.ShowDialog(_contentDialogOverlayWindow ?? mainWindow); + } + + private static Window GetMainWindow() + { + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al) + { + foreach (Window item in al.Windows) + { + if (item is MainWindow window) + { + return window; + } + } + } + + return null; + } + } +} diff --git a/src/Ryujinx/UI/Helpers/Glyph.cs b/src/Ryujinx/UI/Helpers/Glyph.cs new file mode 100644 index 00000000..f257dc02 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/Glyph.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Ava.UI.Helpers +{ + public enum Glyph + { + List, + Grid, + Chip, + } +} diff --git a/src/Ryujinx/UI/Helpers/GlyphValueConverter.cs b/src/Ryujinx/UI/Helpers/GlyphValueConverter.cs new file mode 100644 index 00000000..7da23648 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/GlyphValueConverter.cs @@ -0,0 +1,42 @@ +using Avalonia.Markup.Xaml; +using FluentAvalonia.UI.Controls; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Ava.UI.Helpers +{ + public class GlyphValueConverter : MarkupExtension + { + private readonly string _key; + + private static readonly Dictionary _glyphs = new() + { + { Glyph.List, char.ConvertFromUtf32((int)Symbol.List) }, + { Glyph.Grid, char.ConvertFromUtf32((int)Symbol.ViewAll) }, + { Glyph.Chip, char.ConvertFromUtf32(59748) }, + }; + + public GlyphValueConverter(string key) + { + _key = key; + } + + public string this[string key] + { + get + { + if (_glyphs.TryGetValue(Enum.Parse(key), out var val)) + { + return val; + } + + return string.Empty; + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this[_key]; + } + } +} diff --git a/src/Ryujinx/UI/Helpers/KeyValueConverter.cs b/src/Ryujinx/UI/Helpers/KeyValueConverter.cs new file mode 100644 index 00000000..5b0d6ee1 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/KeyValueConverter.cs @@ -0,0 +1,184 @@ +using Avalonia.Data.Converters; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace Ryujinx.Ava.UI.Helpers +{ + internal class KeyValueConverter : IValueConverter + { + public static KeyValueConverter Instance = new(); + + private static readonly Dictionary _keysMap = new() + { + { Key.Unknown, LocaleKeys.KeyUnknown }, + { Key.ShiftLeft, LocaleKeys.KeyShiftLeft }, + { Key.ShiftRight, LocaleKeys.KeyShiftRight }, + { Key.ControlLeft, LocaleKeys.KeyControlLeft }, + { Key.ControlRight, LocaleKeys.KeyControlRight }, + { Key.AltLeft, LocaleKeys.KeyAltLeft }, + { Key.AltRight, LocaleKeys.KeyAltRight }, + { Key.WinLeft, LocaleKeys.KeyWinLeft }, + { Key.WinRight, LocaleKeys.KeyWinRight }, + { Key.Up, LocaleKeys.KeyUp }, + { Key.Down, LocaleKeys.KeyDown }, + { Key.Left, LocaleKeys.KeyLeft }, + { Key.Right, LocaleKeys.KeyRight }, + { Key.Enter, LocaleKeys.KeyEnter }, + { Key.Escape, LocaleKeys.KeyEscape }, + { Key.Space, LocaleKeys.KeySpace }, + { Key.Tab, LocaleKeys.KeyTab }, + { Key.BackSpace, LocaleKeys.KeyBackSpace }, + { Key.Insert, LocaleKeys.KeyInsert }, + { Key.Delete, LocaleKeys.KeyDelete }, + { Key.PageUp, LocaleKeys.KeyPageUp }, + { Key.PageDown, LocaleKeys.KeyPageDown }, + { Key.Home, LocaleKeys.KeyHome }, + { Key.End, LocaleKeys.KeyEnd }, + { Key.CapsLock, LocaleKeys.KeyCapsLock }, + { Key.ScrollLock, LocaleKeys.KeyScrollLock }, + { Key.PrintScreen, LocaleKeys.KeyPrintScreen }, + { Key.Pause, LocaleKeys.KeyPause }, + { Key.NumLock, LocaleKeys.KeyNumLock }, + { Key.Clear, LocaleKeys.KeyClear }, + { Key.Keypad0, LocaleKeys.KeyKeypad0 }, + { Key.Keypad1, LocaleKeys.KeyKeypad1 }, + { Key.Keypad2, LocaleKeys.KeyKeypad2 }, + { Key.Keypad3, LocaleKeys.KeyKeypad3 }, + { Key.Keypad4, LocaleKeys.KeyKeypad4 }, + { Key.Keypad5, LocaleKeys.KeyKeypad5 }, + { Key.Keypad6, LocaleKeys.KeyKeypad6 }, + { Key.Keypad7, LocaleKeys.KeyKeypad7 }, + { Key.Keypad8, LocaleKeys.KeyKeypad8 }, + { Key.Keypad9, LocaleKeys.KeyKeypad9 }, + { Key.KeypadDivide, LocaleKeys.KeyKeypadDivide }, + { Key.KeypadMultiply, LocaleKeys.KeyKeypadMultiply }, + { Key.KeypadSubtract, LocaleKeys.KeyKeypadSubtract }, + { Key.KeypadAdd, LocaleKeys.KeyKeypadAdd }, + { Key.KeypadDecimal, LocaleKeys.KeyKeypadDecimal }, + { Key.KeypadEnter, LocaleKeys.KeyKeypadEnter }, + { Key.Number0, LocaleKeys.KeyNumber0 }, + { Key.Number1, LocaleKeys.KeyNumber1 }, + { Key.Number2, LocaleKeys.KeyNumber2 }, + { Key.Number3, LocaleKeys.KeyNumber3 }, + { Key.Number4, LocaleKeys.KeyNumber4 }, + { Key.Number5, LocaleKeys.KeyNumber5 }, + { Key.Number6, LocaleKeys.KeyNumber6 }, + { Key.Number7, LocaleKeys.KeyNumber7 }, + { Key.Number8, LocaleKeys.KeyNumber8 }, + { Key.Number9, LocaleKeys.KeyNumber9 }, + { Key.Tilde, LocaleKeys.KeyTilde }, + { Key.Grave, LocaleKeys.KeyGrave }, + { Key.Minus, LocaleKeys.KeyMinus }, + { Key.Plus, LocaleKeys.KeyPlus }, + { Key.BracketLeft, LocaleKeys.KeyBracketLeft }, + { Key.BracketRight, LocaleKeys.KeyBracketRight }, + { Key.Semicolon, LocaleKeys.KeySemicolon }, + { Key.Quote, LocaleKeys.KeyQuote }, + { Key.Comma, LocaleKeys.KeyComma }, + { Key.Period, LocaleKeys.KeyPeriod }, + { Key.Slash, LocaleKeys.KeySlash }, + { Key.BackSlash, LocaleKeys.KeyBackSlash }, + { Key.Unbound, LocaleKeys.KeyUnbound }, + }; + + private static readonly Dictionary _gamepadInputIdMap = new() + { + { GamepadInputId.LeftStick, LocaleKeys.GamepadLeftStick }, + { GamepadInputId.RightStick, LocaleKeys.GamepadRightStick }, + { GamepadInputId.LeftShoulder, LocaleKeys.GamepadLeftShoulder }, + { GamepadInputId.RightShoulder, LocaleKeys.GamepadRightShoulder }, + { GamepadInputId.LeftTrigger, LocaleKeys.GamepadLeftTrigger }, + { GamepadInputId.RightTrigger, LocaleKeys.GamepadRightTrigger }, + { GamepadInputId.DpadUp, LocaleKeys.GamepadDpadUp}, + { GamepadInputId.DpadDown, LocaleKeys.GamepadDpadDown}, + { GamepadInputId.DpadLeft, LocaleKeys.GamepadDpadLeft}, + { GamepadInputId.DpadRight, LocaleKeys.GamepadDpadRight}, + { GamepadInputId.Minus, LocaleKeys.GamepadMinus}, + { GamepadInputId.Plus, LocaleKeys.GamepadPlus}, + { GamepadInputId.Guide, LocaleKeys.GamepadGuide}, + { GamepadInputId.Misc1, LocaleKeys.GamepadMisc1}, + { GamepadInputId.Paddle1, LocaleKeys.GamepadPaddle1}, + { GamepadInputId.Paddle2, LocaleKeys.GamepadPaddle2}, + { GamepadInputId.Paddle3, LocaleKeys.GamepadPaddle3}, + { GamepadInputId.Paddle4, LocaleKeys.GamepadPaddle4}, + { GamepadInputId.Touchpad, LocaleKeys.GamepadTouchpad}, + { GamepadInputId.SingleLeftTrigger0, LocaleKeys.GamepadSingleLeftTrigger0}, + { GamepadInputId.SingleRightTrigger0, LocaleKeys.GamepadSingleRightTrigger0}, + { GamepadInputId.SingleLeftTrigger1, LocaleKeys.GamepadSingleLeftTrigger1}, + { GamepadInputId.SingleRightTrigger1, LocaleKeys.GamepadSingleRightTrigger1}, + { GamepadInputId.Unbound, LocaleKeys.KeyUnbound}, + }; + + private static readonly Dictionary _stickInputIdMap = new() + { + { StickInputId.Left, LocaleKeys.StickLeft}, + { StickInputId.Right, LocaleKeys.StickRight}, + { StickInputId.Unbound, LocaleKeys.KeyUnbound}, + }; + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + string keyString = ""; + LocaleKeys localeKey; + + switch (value) + { + case Key key: + if (_keysMap.TryGetValue(key, out localeKey)) + { + if (OperatingSystem.IsMacOS()) + { + localeKey = localeKey switch + { + LocaleKeys.KeyControlLeft => LocaleKeys.KeyMacControlLeft, + LocaleKeys.KeyControlRight => LocaleKeys.KeyMacControlRight, + LocaleKeys.KeyAltLeft => LocaleKeys.KeyMacAltLeft, + LocaleKeys.KeyAltRight => LocaleKeys.KeyMacAltRight, + LocaleKeys.KeyWinLeft => LocaleKeys.KeyMacWinLeft, + LocaleKeys.KeyWinRight => LocaleKeys.KeyMacWinRight, + _ => localeKey + }; + } + + keyString = LocaleManager.Instance[localeKey]; + } + else + { + keyString = key.ToString(); + } + break; + case GamepadInputId gamepadInputId: + if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out localeKey)) + { + keyString = LocaleManager.Instance[localeKey]; + } + else + { + keyString = gamepadInputId.ToString(); + } + break; + case StickInputId stickInputId: + if (_stickInputIdMap.TryGetValue(stickInputId, out localeKey)) + { + keyString = LocaleManager.Instance[localeKey]; + } + else + { + keyString = stickInputId.ToString(); + } + break; + } + + return keyString; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Ryujinx/UI/Helpers/LocalizedNeverConverter.cs b/src/Ryujinx/UI/Helpers/LocalizedNeverConverter.cs new file mode 100644 index 00000000..26fe36c4 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/LocalizedNeverConverter.cs @@ -0,0 +1,43 @@ +using Avalonia.Data.Converters; +using Avalonia.Markup.Xaml; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.UI.Common.Helper; +using System; +using System.Globalization; + +namespace Ryujinx.Ava.UI.Helpers +{ + /// + /// This makes sure that the string "Never" that's returned by is properly localized in the Avalonia UI. + /// After the Avalonia UI has been made the default and the GTK UI is removed, should be updated to directly return a localized string. + /// + internal class LocalizedNeverConverter : MarkupExtension, IValueConverter + { + private static readonly LocalizedNeverConverter _instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is not string valStr) + { + return ""; + } + + if (valStr == "Never") + { + return LocaleManager.Instance[LocaleKeys.Never]; + } + + return valStr; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return _instance; + } + } +} diff --git a/src/Ryujinx/UI/Helpers/LoggerAdapter.cs b/src/Ryujinx/UI/Helpers/LoggerAdapter.cs new file mode 100644 index 00000000..fc714541 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/LoggerAdapter.cs @@ -0,0 +1,102 @@ +using Avalonia.Logging; +using Avalonia.Utilities; +using Ryujinx.Common.Logging; +using System; +using System.Text; + +namespace Ryujinx.Ava.UI.Helpers +{ + using AvaLogger = Avalonia.Logging.Logger; + using AvaLogLevel = LogEventLevel; + using RyuLogClass = LogClass; + using RyuLogger = Ryujinx.Common.Logging.Logger; + + internal class LoggerAdapter : ILogSink + { + public static void Register() + { + AvaLogger.Sink = new LoggerAdapter(); + } + + private static RyuLogger.Log? GetLog(AvaLogLevel level) + { + return level switch + { + AvaLogLevel.Verbose => RyuLogger.Debug, + AvaLogLevel.Debug => RyuLogger.Debug, + AvaLogLevel.Information => RyuLogger.Debug, + AvaLogLevel.Warning => RyuLogger.Debug, + AvaLogLevel.Error => RyuLogger.Error, + AvaLogLevel.Fatal => RyuLogger.Error, + _ => throw new ArgumentOutOfRangeException(nameof(level), level, null), + }; + } + + public bool IsEnabled(AvaLogLevel level, string area) + { + return GetLog(level) != null; + } + + public void Log(AvaLogLevel level, string area, object source, string messageTemplate) + { + GetLog(level)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, null)); + } + + public void Log(AvaLogLevel level, string area, object source, string messageTemplate, params object[] propertyValues) + { + GetLog(level)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, propertyValues)); + } + + private static string Format(AvaLogLevel level, string area, string template, object source, object[] v) + { + var result = new StringBuilder(); + var r = new CharacterReader(template.AsSpan()); + int i = 0; + + result.Append('['); + result.Append(level); + result.Append("] "); + + result.Append('['); + result.Append(area); + result.Append("] "); + + while (!r.End) + { + var c = r.Take(); + + if (c != '{') + { + result.Append(c); + } + else + { + if (r.Peek != '{') + { + result.Append('\''); + result.Append(i < v.Length ? v[i++] : null); + result.Append('\''); + r.TakeUntil('}'); + r.Take(); + } + else + { + result.Append('{'); + r.Take(); + } + } + } + + if (source != null) + { + result.Append(" ("); + result.Append(source.GetType().Name); + result.Append(" #"); + result.Append(source.GetHashCode()); + result.Append(')'); + } + + return result.ToString(); + } + } +} diff --git a/src/Ryujinx/UI/Helpers/MiniCommand.cs b/src/Ryujinx/UI/Helpers/MiniCommand.cs new file mode 100644 index 00000000..7e1bb9a6 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/MiniCommand.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Ryujinx.Ava.UI.Helpers +{ + public sealed class MiniCommand : MiniCommand, ICommand + { + private readonly Action _callback; + private bool _busy; + private readonly Func _asyncCallback; + + public MiniCommand(Action callback) + { + _callback = callback; + } + + public MiniCommand(Func callback) + { + _asyncCallback = callback; + } + + private bool Busy + { + get => _busy; + set + { + _busy = value; + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } + + public override event EventHandler CanExecuteChanged; + public override bool CanExecute(object parameter) => !_busy; + + public override async void Execute(object parameter) + { + if (Busy) + { + return; + } + try + { + Busy = true; + if (_callback != null) + { + _callback((T)parameter); + } + else + { + await _asyncCallback((T)parameter); + } + } + finally + { + Busy = false; + } + } + } + + public abstract class MiniCommand : ICommand + { + public static MiniCommand Create(Action callback) => new MiniCommand(_ => callback()); + public static MiniCommand Create(Action callback) => new MiniCommand(callback); + public static MiniCommand CreateFromTask(Func callback) => new MiniCommand(_ => callback()); + + public abstract bool CanExecute(object parameter); + public abstract void Execute(object parameter); + public abstract event EventHandler CanExecuteChanged; + } +} diff --git a/src/Ryujinx/UI/Helpers/NotificationHelper.cs b/src/Ryujinx/UI/Helpers/NotificationHelper.cs new file mode 100644 index 00000000..656a8b52 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/NotificationHelper.cs @@ -0,0 +1,70 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Notifications; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Common; +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.Ava.UI.Helpers +{ + public static class NotificationHelper + { + private const int MaxNotifications = 4; + private const int NotificationDelayInMs = 5000; + + private static WindowNotificationManager _notificationManager; + + private static readonly BlockingCollection _notifications = new(); + + public static void SetNotificationManager(Window host) + { + _notificationManager = new WindowNotificationManager(host) + { + Position = NotificationPosition.BottomRight, + MaxItems = MaxNotifications, + Margin = new Thickness(0, 0, 15, 40), + }; + + var maybeAsyncWorkQueue = new Lazy>( + () => new AsyncWorkQueue(notification => + { + Dispatcher.UIThread.Post(() => + { + _notificationManager.Show(notification); + }); + }, + "UI.NotificationThread", + _notifications), + LazyThreadSafetyMode.ExecutionAndPublication); + + _notificationManager.TemplateApplied += (sender, args) => + { + // NOTE: Force creation of the AsyncWorkQueue. + _ = maybeAsyncWorkQueue.Value; + }; + + host.Closing += (sender, args) => + { + if (maybeAsyncWorkQueue.IsValueCreated) + { + maybeAsyncWorkQueue.Value.Dispose(); + } + }; + } + + public static void Show(string title, string text, NotificationType type, bool waitingExit = false, Action onClick = null, Action onClose = null) + { + var delay = waitingExit ? TimeSpan.FromMilliseconds(0) : TimeSpan.FromMilliseconds(NotificationDelayInMs); + + _notifications.Add(new Notification(title, text, type, delay, onClick, onClose)); + } + + public static void ShowError(string message) + { + Show(LocaleManager.Instance[LocaleKeys.DialogErrorTitle], $"{LocaleManager.Instance[LocaleKeys.DialogErrorMessage]}\n\n{message}", NotificationType.Error); + } + } +} diff --git a/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs b/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs new file mode 100644 index 00000000..dd736037 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/OffscreenTextBox.cs @@ -0,0 +1,42 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using System; + +namespace Ryujinx.Ava.UI.Helpers +{ + public class OffscreenTextBox : TextBox + { + protected override Type StyleKeyOverride => typeof(TextBox); + + public static RoutedEvent GetKeyDownRoutedEvent() + { + return KeyDownEvent; + } + + public static RoutedEvent GetKeyUpRoutedEvent() + { + return KeyUpEvent; + } + + public void SendKeyDownEvent(KeyEventArgs keyEvent) + { + OnKeyDown(keyEvent); + } + + public void SendKeyUpEvent(KeyEventArgs keyEvent) + { + OnKeyUp(keyEvent); + } + + public void SendText(string text) + { + OnTextInput(new TextInputEventArgs + { + Text = text, + Source = this, + RoutedEvent = TextInputEvent, + }); + } + } +} diff --git a/src/Ryujinx/UI/Helpers/TimeZoneConverter.cs b/src/Ryujinx/UI/Helpers/TimeZoneConverter.cs new file mode 100644 index 00000000..876d51f7 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/TimeZoneConverter.cs @@ -0,0 +1,28 @@ +using Avalonia.Data.Converters; +using System; +using System.Globalization; +using TimeZone = Ryujinx.Ava.UI.Models.TimeZone; + +namespace Ryujinx.Ava.UI.Helpers +{ + internal class TimeZoneConverter : IValueConverter + { + public static TimeZoneConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + { + return null; + } + + var timeZone = (TimeZone)value; + return string.Format("{0} {1} {2}", timeZone.UtcDifference, timeZone.Location, timeZone.Abbreviation); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Ryujinx/UI/Helpers/UserErrorDialog.cs b/src/Ryujinx/UI/Helpers/UserErrorDialog.cs new file mode 100644 index 00000000..9a44b862 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/UserErrorDialog.cs @@ -0,0 +1,90 @@ +using Ryujinx.Ava.Common.Locale; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Helper; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Helpers +{ + internal class UserErrorDialog + { + private const string SetupGuideUrl = "https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide"; + + private static string GetErrorCode(UserError error) + { + return $"RYU-{(uint)error:X4}"; + } + + private static string GetErrorTitle(UserError error) + { + return error switch + { + UserError.NoKeys => LocaleManager.Instance[LocaleKeys.UserErrorNoKeys], + UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.UserErrorNoFirmware], + UserError.FirmwareParsingFailed => LocaleManager.Instance[LocaleKeys.UserErrorFirmwareParsingFailed], + UserError.ApplicationNotFound => LocaleManager.Instance[LocaleKeys.UserErrorApplicationNotFound], + UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknown], + _ => LocaleManager.Instance[LocaleKeys.UserErrorUndefined], + }; + } + + private static string GetErrorDescription(UserError error) + { + return error switch + { + UserError.NoKeys => LocaleManager.Instance[LocaleKeys.UserErrorNoKeysDescription], + UserError.NoFirmware => LocaleManager.Instance[LocaleKeys.UserErrorNoFirmwareDescription], + UserError.FirmwareParsingFailed => LocaleManager.Instance[LocaleKeys.UserErrorFirmwareParsingFailedDescription], + UserError.ApplicationNotFound => LocaleManager.Instance[LocaleKeys.UserErrorApplicationNotFoundDescription], + UserError.Unknown => LocaleManager.Instance[LocaleKeys.UserErrorUnknownDescription], + _ => LocaleManager.Instance[LocaleKeys.UserErrorUndefinedDescription], + }; + } + + private static bool IsCoveredBySetupGuide(UserError error) + { + return error switch + { + UserError.NoKeys or + UserError.NoFirmware or + UserError.FirmwareParsingFailed => true, + _ => false, + }; + } + + private static string GetSetupGuideUrl(UserError error) + { + if (!IsCoveredBySetupGuide(error)) + { + return null; + } + + return error switch + { + UserError.NoKeys => SetupGuideUrl + "#initial-setup---placement-of-prodkeys", + UserError.NoFirmware => SetupGuideUrl + "#initial-setup-continued---installation-of-firmware", + _ => SetupGuideUrl, + }; + } + + public static async Task ShowUserErrorDialog(UserError error) + { + string errorCode = GetErrorCode(error); + + bool isInSetupGuide = IsCoveredBySetupGuide(error); + + string setupButtonLabel = isInSetupGuide ? LocaleManager.Instance[LocaleKeys.OpenSetupGuideMessage] : ""; + + var result = await ContentDialogHelper.CreateInfoDialog( + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogMessage, errorCode, GetErrorTitle(error)), + GetErrorDescription(error) + (isInSetupGuide + ? LocaleManager.Instance[LocaleKeys.DialogUserErrorDialogInfoMessage] + : ""), setupButtonLabel, LocaleManager.Instance[LocaleKeys.InputDialogOk], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogTitle, errorCode)); + + if (result == UserResult.Ok) + { + OpenHelper.OpenUrl(GetSetupGuideUrl(error)); + } + } + } +} diff --git a/src/Ryujinx/UI/Helpers/UserResult.cs b/src/Ryujinx/UI/Helpers/UserResult.cs new file mode 100644 index 00000000..2fcd35ae --- /dev/null +++ b/src/Ryujinx/UI/Helpers/UserResult.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Ava.UI.Helpers +{ + public enum UserResult + { + Ok, + Yes, + No, + Abort, + Cancel, + None, + } +} diff --git a/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs new file mode 100644 index 00000000..01478cb3 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/Win32NativeInterop.cs @@ -0,0 +1,115 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Ava.UI.Helpers +{ + [SupportedOSPlatform("windows")] + internal partial class Win32NativeInterop + { + internal const int GWLP_WNDPROC = -4; + + [Flags] + public enum ClassStyles : uint + { + CsClassdc = 0x40, + CsOwndc = 0x20, + } + + [Flags] + public enum WindowStyles : uint + { + WsChild = 0x40000000, + } + + public enum Cursors : uint + { + IdcArrow = 32512, + } + + [SuppressMessage("Design", "CA1069: Enums values should not be duplicated")] + public enum WindowsMessages : uint + { + NcHitTest = 0x0084, + } + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + internal delegate IntPtr WindowProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam); + + [StructLayout(LayoutKind.Sequential)] + public struct WndClassEx + { + public int cbSize; + public ClassStyles style; + public IntPtr lpfnWndProc; // not WndProc + public int cbClsExtra; + public int cbWndExtra; + public IntPtr hInstance; + public IntPtr hIcon; + public IntPtr hCursor; + public IntPtr hbrBackground; + public IntPtr lpszMenuName; + public IntPtr lpszClassName; + public IntPtr hIconSm; + + public WndClassEx() + { + cbSize = Marshal.SizeOf(); + } + } + + public static IntPtr CreateEmptyCursor() + { + return CreateCursor(IntPtr.Zero, 0, 0, 1, 1, new byte[] { 0xFF }, new byte[] { 0x00 }); + } + + public static IntPtr CreateArrowCursor() + { + return LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IdcArrow); + } + + [LibraryImport("user32.dll")] + public static partial IntPtr SetCursor(IntPtr handle); + + [LibraryImport("user32.dll")] + public static partial IntPtr CreateCursor(IntPtr hInst, int xHotSpot, int yHotSpot, int nWidth, int nHeight, [In] byte[] pvAndPlane, [In] byte[] pvXorPlane); + + [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")] + public static partial ushort RegisterClassEx(ref WndClassEx param); + + [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "UnregisterClassW")] + public static partial short UnregisterClass([MarshalAs(UnmanagedType.LPWStr)] string lpClassName, IntPtr instance); + + [LibraryImport("user32.dll", EntryPoint = "DefWindowProcW")] + public static partial IntPtr DefWindowProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam); + + [LibraryImport("kernel32.dll", EntryPoint = "GetModuleHandleA")] + public static partial IntPtr GetModuleHandle([MarshalAs(UnmanagedType.LPStr)] string lpModuleName); + + [LibraryImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool DestroyWindow(IntPtr hwnd); + + [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "LoadCursorA")] + public static partial IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName); + + [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "CreateWindowExW")] + public static partial IntPtr CreateWindowEx( + uint dwExStyle, + [MarshalAs(UnmanagedType.LPWStr)] string lpClassName, + [MarshalAs(UnmanagedType.LPWStr)] string lpWindowName, + WindowStyles dwStyle, + int x, + int y, + int nWidth, + int nHeight, + IntPtr hWndParent, + IntPtr hMenu, + IntPtr hInstance, + IntPtr lpParam); + + [LibraryImport("user32.dll", SetLastError = true)] + public static partial IntPtr SetWindowLongPtrW(IntPtr hWnd, int nIndex, IntPtr value); + } +} diff --git a/src/Ryujinx/UI/Models/CheatNode.cs b/src/Ryujinx/UI/Models/CheatNode.cs new file mode 100644 index 00000000..8e9aee25 --- /dev/null +++ b/src/Ryujinx/UI/Models/CheatNode.cs @@ -0,0 +1,57 @@ +using Ryujinx.Ava.UI.ViewModels; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; + +namespace Ryujinx.Ava.UI.Models +{ + public class CheatNode : BaseModel + { + private bool _isEnabled = false; + public ObservableCollection SubNodes { get; } = new(); + public string CleanName => Name[1..^7]; + public string BuildIdKey => $"{BuildId}-{Name}"; + public bool IsRootNode { get; } + public string Name { get; } + public string BuildId { get; } + public string Path { get; } + public bool IsEnabled + { + get + { + if (SubNodes.Count > 0) + { + return SubNodes.ToList().TrueForAll(x => x.IsEnabled); + } + + return _isEnabled; + } + set + { + foreach (var cheat in SubNodes) + { + cheat.IsEnabled = value; + cheat.OnPropertyChanged(); + } + + _isEnabled = value; + } + } + + public CheatNode(string name, string buildId, string path, bool isRootNode, bool isEnabled = false) + { + Name = name; + BuildId = buildId; + Path = path; + IsEnabled = isEnabled; + IsRootNode = isRootNode; + + SubNodes.CollectionChanged += CheatsList_CollectionChanged; + } + + private void CheatsList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(nameof(IsEnabled)); + } + } +} diff --git a/src/Ryujinx/UI/Models/ControllerModel.cs b/src/Ryujinx/UI/Models/ControllerModel.cs new file mode 100644 index 00000000..98de7757 --- /dev/null +++ b/src/Ryujinx/UI/Models/ControllerModel.cs @@ -0,0 +1,6 @@ +using Ryujinx.Common.Configuration.Hid; + +namespace Ryujinx.Ava.UI.Models +{ + internal record ControllerModel(ControllerType Type, string Name); +} diff --git a/src/Ryujinx/UI/Models/DeviceType.cs b/src/Ryujinx/UI/Models/DeviceType.cs new file mode 100644 index 00000000..bb4fc3b3 --- /dev/null +++ b/src/Ryujinx/UI/Models/DeviceType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Ava.UI.Models +{ + public enum DeviceType + { + None, + Keyboard, + Controller, + } +} diff --git a/src/Ryujinx/UI/Models/DownloadableContentModel.cs b/src/Ryujinx/UI/Models/DownloadableContentModel.cs new file mode 100644 index 00000000..1409d971 --- /dev/null +++ b/src/Ryujinx/UI/Models/DownloadableContentModel.cs @@ -0,0 +1,39 @@ +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.ViewModels; +using System.IO; + +namespace Ryujinx.Ava.UI.Models +{ + public class DownloadableContentModel : BaseModel + { + private bool _enabled; + + public bool Enabled + { + get => _enabled; + set + { + _enabled = value; + + OnPropertyChanged(); + } + } + + public string TitleId { get; } + public string ContainerPath { get; } + public string FullPath { get; } + + public string FileName => Path.GetFileName(ContainerPath); + + public string Label => + Path.GetExtension(FileName)?.ToLower() == ".xci" ? $"{LocaleManager.Instance[LocaleKeys.TitleBundledDlcLabel]} {FileName}" : FileName; + + public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled) + { + TitleId = titleId; + ContainerPath = containerPath; + FullPath = fullPath; + Enabled = enabled; + } + } +} diff --git a/src/Ryujinx/UI/Models/Generic/LastPlayedSortComparer.cs b/src/Ryujinx/UI/Models/Generic/LastPlayedSortComparer.cs new file mode 100644 index 00000000..224f78f4 --- /dev/null +++ b/src/Ryujinx/UI/Models/Generic/LastPlayedSortComparer.cs @@ -0,0 +1,31 @@ +using Ryujinx.UI.App.Common; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Ava.UI.Models.Generic +{ + internal class LastPlayedSortComparer : IComparer + { + public LastPlayedSortComparer() { } + public LastPlayedSortComparer(bool isAscending) { IsAscending = isAscending; } + + public bool IsAscending { get; } + + public int Compare(ApplicationData x, ApplicationData y) + { + DateTime aValue = DateTime.UnixEpoch, bValue = DateTime.UnixEpoch; + + if (x?.LastPlayed != null) + { + aValue = x.LastPlayed.Value; + } + + if (y?.LastPlayed != null) + { + bValue = y.LastPlayed.Value; + } + + return (IsAscending ? 1 : -1) * DateTime.Compare(aValue, bValue); + } + } +} diff --git a/src/Ryujinx/UI/Models/Generic/TimePlayedSortComparer.cs b/src/Ryujinx/UI/Models/Generic/TimePlayedSortComparer.cs new file mode 100644 index 00000000..f0fb035d --- /dev/null +++ b/src/Ryujinx/UI/Models/Generic/TimePlayedSortComparer.cs @@ -0,0 +1,31 @@ +using Ryujinx.UI.App.Common; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Ava.UI.Models.Generic +{ + internal class TimePlayedSortComparer : IComparer + { + public TimePlayedSortComparer() { } + public TimePlayedSortComparer(bool isAscending) { IsAscending = isAscending; } + + public bool IsAscending { get; } + + public int Compare(ApplicationData x, ApplicationData y) + { + TimeSpan aValue = TimeSpan.Zero, bValue = TimeSpan.Zero; + + if (x?.TimePlayed != null) + { + aValue = x.TimePlayed; + } + + if (y?.TimePlayed != null) + { + bValue = y.TimePlayed; + } + + return (IsAscending ? 1 : -1) * TimeSpan.Compare(aValue, bValue); + } + } +} diff --git a/src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs b/src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs new file mode 100644 index 00000000..833670bd --- /dev/null +++ b/src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs @@ -0,0 +1,580 @@ +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using System; + +namespace Ryujinx.Ava.UI.Models.Input +{ + public class GamepadInputConfig : BaseModel + { + public bool EnableCemuHookMotion { get; set; } + public string DsuServerHost { get; set; } + public int DsuServerPort { get; set; } + public int Slot { get; set; } + public int AltSlot { get; set; } + public bool MirrorInput { get; set; } + public int Sensitivity { get; set; } + public double GyroDeadzone { get; set; } + + public float WeakRumble { get; set; } + public float StrongRumble { get; set; } + + public string Id { get; set; } + public ControllerType ControllerType { get; set; } + public PlayerIndex PlayerIndex { get; set; } + + private StickInputId _leftJoystick; + public StickInputId LeftJoystick + { + get => _leftJoystick; + set + { + _leftJoystick = value; + OnPropertyChanged(); + } + } + + private bool _leftInvertStickX; + public bool LeftInvertStickX + { + get => _leftInvertStickX; + set + { + _leftInvertStickX = value; + OnPropertyChanged(); + } + } + + private bool _leftInvertStickY; + public bool LeftInvertStickY + { + get => _leftInvertStickY; + set + { + _leftInvertStickY = value; + OnPropertyChanged(); + } + } + + private bool _leftRotate90; + public bool LeftRotate90 + { + get => _leftRotate90; + set + { + _leftRotate90 = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _leftStickButton; + public GamepadInputId LeftStickButton + { + get => _leftStickButton; + set + { + _leftStickButton = value; + OnPropertyChanged(); + } + } + + private StickInputId _rightJoystick; + public StickInputId RightJoystick + { + get => _rightJoystick; + set + { + _rightJoystick = value; + OnPropertyChanged(); + } + } + + private bool _rightInvertStickX; + public bool RightInvertStickX + { + get => _rightInvertStickX; + set + { + _rightInvertStickX = value; + OnPropertyChanged(); + } + } + + private bool _rightInvertStickY; + public bool RightInvertStickY + { + get => _rightInvertStickY; + set + { + _rightInvertStickY = value; + OnPropertyChanged(); + } + } + + private bool _rightRotate90; + public bool RightRotate90 + { + get => _rightRotate90; + set + { + _rightRotate90 = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _rightStickButton; + public GamepadInputId RightStickButton + { + get => _rightStickButton; + set + { + _rightStickButton = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _dpadUp; + public GamepadInputId DpadUp + { + get => _dpadUp; + set + { + _dpadUp = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _dpadDown; + public GamepadInputId DpadDown + { + get => _dpadDown; + set + { + _dpadDown = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _dpadLeft; + public GamepadInputId DpadLeft + { + get => _dpadLeft; + set + { + _dpadLeft = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _dpadRight; + public GamepadInputId DpadRight + { + get => _dpadRight; + set + { + _dpadRight = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonL; + public GamepadInputId ButtonL + { + get => _buttonL; + set + { + _buttonL = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonMinus; + public GamepadInputId ButtonMinus + { + get => _buttonMinus; + set + { + _buttonMinus = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _leftButtonSl; + public GamepadInputId LeftButtonSl + { + get => _leftButtonSl; + set + { + _leftButtonSl = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _leftButtonSr; + public GamepadInputId LeftButtonSr + { + get => _leftButtonSr; + set + { + _leftButtonSr = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonZl; + public GamepadInputId ButtonZl + { + get => _buttonZl; + set + { + _buttonZl = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonA; + public GamepadInputId ButtonA + { + get => _buttonA; + set + { + _buttonA = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonB; + public GamepadInputId ButtonB + { + get => _buttonB; + set + { + _buttonB = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonX; + public GamepadInputId ButtonX + { + get => _buttonX; + set + { + _buttonX = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonY; + public GamepadInputId ButtonY + { + get => _buttonY; + set + { + _buttonY = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonR; + public GamepadInputId ButtonR + { + get => _buttonR; + set + { + _buttonR = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonPlus; + public GamepadInputId ButtonPlus + { + get => _buttonPlus; + set + { + _buttonPlus = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _rightButtonSl; + public GamepadInputId RightButtonSl + { + get => _rightButtonSl; + set + { + _rightButtonSl = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _rightButtonSr; + public GamepadInputId RightButtonSr + { + get => _rightButtonSr; + set + { + _rightButtonSr = value; + OnPropertyChanged(); + } + } + + private GamepadInputId _buttonZr; + public GamepadInputId ButtonZr + { + get => _buttonZr; + set + { + _buttonZr = value; + OnPropertyChanged(); + } + } + + private float _deadzoneLeft; + public float DeadzoneLeft + { + get => _deadzoneLeft; + set + { + _deadzoneLeft = MathF.Round(value, 3); + OnPropertyChanged(); + } + } + + private float _deadzoneRight; + public float DeadzoneRight + { + get => _deadzoneRight; + set + { + _deadzoneRight = MathF.Round(value, 3); + OnPropertyChanged(); + } + } + + private float _rangeLeft; + public float RangeLeft + { + get => _rangeLeft; + set + { + _rangeLeft = MathF.Round(value, 3); + OnPropertyChanged(); + } + } + + private float _rangeRight; + public float RangeRight + { + get => _rangeRight; + set + { + _rangeRight = MathF.Round(value, 3); + OnPropertyChanged(); + } + } + + private float _triggerThreshold; + public float TriggerThreshold + { + get => _triggerThreshold; + set + { + _triggerThreshold = MathF.Round(value, 3); + OnPropertyChanged(); + } + } + + private bool _enableMotion; + public bool EnableMotion + { + get => _enableMotion; + set + { + _enableMotion = value; + OnPropertyChanged(); + } + } + + private bool _enableRumble; + public bool EnableRumble + { + get => _enableRumble; + set + { + _enableRumble = value; + OnPropertyChanged(); + } + } + + public GamepadInputConfig(InputConfig config) + { + if (config != null) + { + Id = config.Id; + ControllerType = config.ControllerType; + PlayerIndex = config.PlayerIndex; + + if (config is not StandardControllerInputConfig controllerInput) + { + return; + } + + LeftJoystick = controllerInput.LeftJoyconStick.Joystick; + LeftInvertStickX = controllerInput.LeftJoyconStick.InvertStickX; + LeftInvertStickY = controllerInput.LeftJoyconStick.InvertStickY; + LeftRotate90 = controllerInput.LeftJoyconStick.Rotate90CW; + LeftStickButton = controllerInput.LeftJoyconStick.StickButton; + + RightJoystick = controllerInput.RightJoyconStick.Joystick; + RightInvertStickX = controllerInput.RightJoyconStick.InvertStickX; + RightInvertStickY = controllerInput.RightJoyconStick.InvertStickY; + RightRotate90 = controllerInput.RightJoyconStick.Rotate90CW; + RightStickButton = controllerInput.RightJoyconStick.StickButton; + + DpadUp = controllerInput.LeftJoycon.DpadUp; + DpadDown = controllerInput.LeftJoycon.DpadDown; + DpadLeft = controllerInput.LeftJoycon.DpadLeft; + DpadRight = controllerInput.LeftJoycon.DpadRight; + ButtonL = controllerInput.LeftJoycon.ButtonL; + ButtonMinus = controllerInput.LeftJoycon.ButtonMinus; + LeftButtonSl = controllerInput.LeftJoycon.ButtonSl; + LeftButtonSr = controllerInput.LeftJoycon.ButtonSr; + ButtonZl = controllerInput.LeftJoycon.ButtonZl; + + ButtonA = controllerInput.RightJoycon.ButtonA; + ButtonB = controllerInput.RightJoycon.ButtonB; + ButtonX = controllerInput.RightJoycon.ButtonX; + ButtonY = controllerInput.RightJoycon.ButtonY; + ButtonR = controllerInput.RightJoycon.ButtonR; + ButtonPlus = controllerInput.RightJoycon.ButtonPlus; + RightButtonSl = controllerInput.RightJoycon.ButtonSl; + RightButtonSr = controllerInput.RightJoycon.ButtonSr; + ButtonZr = controllerInput.RightJoycon.ButtonZr; + + DeadzoneLeft = controllerInput.DeadzoneLeft; + DeadzoneRight = controllerInput.DeadzoneRight; + RangeLeft = controllerInput.RangeLeft; + RangeRight = controllerInput.RangeRight; + TriggerThreshold = controllerInput.TriggerThreshold; + + if (controllerInput.Motion != null) + { + EnableMotion = controllerInput.Motion.EnableMotion; + GyroDeadzone = controllerInput.Motion.GyroDeadzone; + Sensitivity = controllerInput.Motion.Sensitivity; + + if (controllerInput.Motion is CemuHookMotionConfigController cemuHook) + { + EnableCemuHookMotion = true; + DsuServerHost = cemuHook.DsuServerHost; + DsuServerPort = cemuHook.DsuServerPort; + Slot = cemuHook.Slot; + AltSlot = cemuHook.AltSlot; + MirrorInput = cemuHook.MirrorInput; + } + } + + if (controllerInput.Rumble != null) + { + EnableRumble = controllerInput.Rumble.EnableRumble; + WeakRumble = controllerInput.Rumble.WeakRumble; + StrongRumble = controllerInput.Rumble.StrongRumble; + } + } + } + + public InputConfig GetConfig() + { + var config = new StandardControllerInputConfig + { + Id = Id, + Backend = InputBackendType.GamepadSDL2, + PlayerIndex = PlayerIndex, + ControllerType = ControllerType, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = DpadUp, + DpadDown = DpadDown, + DpadLeft = DpadLeft, + DpadRight = DpadRight, + ButtonL = ButtonL, + ButtonMinus = ButtonMinus, + ButtonSl = LeftButtonSl, + ButtonSr = LeftButtonSr, + ButtonZl = ButtonZl, + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = ButtonA, + ButtonB = ButtonB, + ButtonX = ButtonX, + ButtonY = ButtonY, + ButtonPlus = ButtonPlus, + ButtonSl = RightButtonSl, + ButtonSr = RightButtonSr, + ButtonR = ButtonR, + ButtonZr = ButtonZr, + }, + LeftJoyconStick = new JoyconConfigControllerStick + { + Joystick = LeftJoystick, + InvertStickX = LeftInvertStickX, + InvertStickY = LeftInvertStickY, + Rotate90CW = LeftRotate90, + StickButton = LeftStickButton, + }, + RightJoyconStick = new JoyconConfigControllerStick + { + Joystick = RightJoystick, + InvertStickX = RightInvertStickX, + InvertStickY = RightInvertStickY, + Rotate90CW = RightRotate90, + StickButton = RightStickButton, + }, + Rumble = new RumbleConfigController + { + EnableRumble = EnableRumble, + WeakRumble = WeakRumble, + StrongRumble = StrongRumble, + }, + Version = InputConfig.CurrentVersion, + DeadzoneLeft = DeadzoneLeft, + DeadzoneRight = DeadzoneRight, + RangeLeft = RangeLeft, + RangeRight = RangeRight, + TriggerThreshold = TriggerThreshold, + }; + + if (EnableCemuHookMotion) + { + config.Motion = new CemuHookMotionConfigController + { + EnableMotion = EnableMotion, + MotionBackend = MotionInputBackendType.CemuHook, + GyroDeadzone = GyroDeadzone, + Sensitivity = Sensitivity, + DsuServerHost = DsuServerHost, + DsuServerPort = DsuServerPort, + Slot = Slot, + AltSlot = AltSlot, + MirrorInput = MirrorInput, + }; + } + else + { + config.Motion = new StandardMotionConfigController + { + EnableMotion = EnableMotion, + MotionBackend = MotionInputBackendType.GamepadDriver, + GyroDeadzone = GyroDeadzone, + Sensitivity = Sensitivity, + }; + } + + return config; + } + } +} diff --git a/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs b/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs new file mode 100644 index 00000000..b5f53508 --- /dev/null +++ b/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs @@ -0,0 +1,141 @@ +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common.Configuration.Hid; + +namespace Ryujinx.Ava.UI.Models.Input +{ + public class HotkeyConfig : BaseModel + { + private Key _toggleVsync; + public Key ToggleVsync + { + get => _toggleVsync; + set + { + _toggleVsync = value; + OnPropertyChanged(); + } + } + + private Key _screenshot; + public Key Screenshot + { + get => _screenshot; + set + { + _screenshot = value; + OnPropertyChanged(); + } + } + + private Key _showUI; + public Key ShowUI + { + get => _showUI; + set + { + _showUI = value; + OnPropertyChanged(); + } + } + + private Key _pause; + public Key Pause + { + get => _pause; + set + { + _pause = value; + OnPropertyChanged(); + } + } + + private Key _toggleMute; + public Key ToggleMute + { + get => _toggleMute; + set + { + _toggleMute = value; + OnPropertyChanged(); + } + } + + private Key _resScaleUp; + public Key ResScaleUp + { + get => _resScaleUp; + set + { + _resScaleUp = value; + OnPropertyChanged(); + } + } + + private Key _resScaleDown; + public Key ResScaleDown + { + get => _resScaleDown; + set + { + _resScaleDown = value; + OnPropertyChanged(); + } + } + + private Key _volumeUp; + public Key VolumeUp + { + get => _volumeUp; + set + { + _volumeUp = value; + OnPropertyChanged(); + } + } + + private Key _volumeDown; + public Key VolumeDown + { + get => _volumeDown; + set + { + _volumeDown = value; + OnPropertyChanged(); + } + } + + public HotkeyConfig(KeyboardHotkeys config) + { + if (config != null) + { + ToggleVsync = config.ToggleVsync; + Screenshot = config.Screenshot; + ShowUI = config.ShowUI; + Pause = config.Pause; + ToggleMute = config.ToggleMute; + ResScaleUp = config.ResScaleUp; + ResScaleDown = config.ResScaleDown; + VolumeUp = config.VolumeUp; + VolumeDown = config.VolumeDown; + } + } + + public KeyboardHotkeys GetConfig() + { + var config = new KeyboardHotkeys + { + ToggleVsync = ToggleVsync, + Screenshot = Screenshot, + ShowUI = ShowUI, + Pause = Pause, + ToggleMute = ToggleMute, + ResScaleUp = ResScaleUp, + ResScaleDown = ResScaleDown, + VolumeUp = VolumeUp, + VolumeDown = VolumeDown, + }; + + return config; + } + } +} diff --git a/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs b/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs new file mode 100644 index 00000000..66f1f62a --- /dev/null +++ b/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs @@ -0,0 +1,422 @@ +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Keyboard; + +namespace Ryujinx.Ava.UI.Models.Input +{ + public class KeyboardInputConfig : BaseModel + { + public string Id { get; set; } + public ControllerType ControllerType { get; set; } + public PlayerIndex PlayerIndex { get; set; } + + private Key _leftStickUp; + public Key LeftStickUp + { + get => _leftStickUp; + set + { + _leftStickUp = value; + OnPropertyChanged(); + } + } + + private Key _leftStickDown; + public Key LeftStickDown + { + get => _leftStickDown; + set + { + _leftStickDown = value; + OnPropertyChanged(); + } + } + + private Key _leftStickLeft; + public Key LeftStickLeft + { + get => _leftStickLeft; + set + { + _leftStickLeft = value; + OnPropertyChanged(); + } + } + + private Key _leftStickRight; + public Key LeftStickRight + { + get => _leftStickRight; + set + { + _leftStickRight = value; + OnPropertyChanged(); + } + } + + private Key _leftStickButton; + public Key LeftStickButton + { + get => _leftStickButton; + set + { + _leftStickButton = value; + OnPropertyChanged(); + } + } + + private Key _rightStickUp; + public Key RightStickUp + { + get => _rightStickUp; + set + { + _rightStickUp = value; + OnPropertyChanged(); + } + } + + private Key _rightStickDown; + public Key RightStickDown + { + get => _rightStickDown; + set + { + _rightStickDown = value; + OnPropertyChanged(); + } + } + + private Key _rightStickLeft; + public Key RightStickLeft + { + get => _rightStickLeft; + set + { + _rightStickLeft = value; + OnPropertyChanged(); + } + } + + private Key _rightStickRight; + public Key RightStickRight + { + get => _rightStickRight; + set + { + _rightStickRight = value; + OnPropertyChanged(); + } + } + + private Key _rightStickButton; + public Key RightStickButton + { + get => _rightStickButton; + set + { + _rightStickButton = value; + OnPropertyChanged(); + } + } + + private Key _dpadUp; + public Key DpadUp + { + get => _dpadUp; + set + { + _dpadUp = value; + OnPropertyChanged(); + } + } + + private Key _dpadDown; + public Key DpadDown + { + get => _dpadDown; + set + { + _dpadDown = value; + OnPropertyChanged(); + } + } + + private Key _dpadLeft; + public Key DpadLeft + { + get => _dpadLeft; + set + { + _dpadLeft = value; + OnPropertyChanged(); + } + } + + private Key _dpadRight; + public Key DpadRight + { + get => _dpadRight; + set + { + _dpadRight = value; + OnPropertyChanged(); + } + } + + private Key _buttonL; + public Key ButtonL + { + get => _buttonL; + set + { + _buttonL = value; + OnPropertyChanged(); + } + } + + private Key _buttonMinus; + public Key ButtonMinus + { + get => _buttonMinus; + set + { + _buttonMinus = value; + OnPropertyChanged(); + } + } + + private Key _leftButtonSl; + public Key LeftButtonSl + { + get => _leftButtonSl; + set + { + _leftButtonSl = value; + OnPropertyChanged(); + } + } + + private Key _leftButtonSr; + public Key LeftButtonSr + { + get => _leftButtonSr; + set + { + _leftButtonSr = value; + OnPropertyChanged(); + } + } + + private Key _buttonZl; + public Key ButtonZl + { + get => _buttonZl; + set + { + _buttonZl = value; + OnPropertyChanged(); + } + } + + private Key _buttonA; + public Key ButtonA + { + get => _buttonA; + set + { + _buttonA = value; + OnPropertyChanged(); + } + } + + private Key _buttonB; + public Key ButtonB + { + get => _buttonB; + set + { + _buttonB = value; + OnPropertyChanged(); + } + } + + private Key _buttonX; + public Key ButtonX + { + get => _buttonX; + set + { + _buttonX = value; + OnPropertyChanged(); + } + } + + private Key _buttonY; + public Key ButtonY + { + get => _buttonY; + set + { + _buttonY = value; + OnPropertyChanged(); + } + } + + private Key _buttonR; + public Key ButtonR + { + get => _buttonR; + set + { + _buttonR = value; + OnPropertyChanged(); + } + } + + private Key _buttonPlus; + public Key ButtonPlus + { + get => _buttonPlus; + set + { + _buttonPlus = value; + OnPropertyChanged(); + } + } + + private Key _rightButtonSl; + public Key RightButtonSl + { + get => _rightButtonSl; + set + { + _rightButtonSl = value; + OnPropertyChanged(); + } + } + + private Key _rightButtonSr; + public Key RightButtonSr + { + get => _rightButtonSr; + set + { + _rightButtonSr = value; + OnPropertyChanged(); + } + } + + private Key _buttonZr; + public Key ButtonZr + { + get => _buttonZr; + set + { + _buttonZr = value; + OnPropertyChanged(); + } + } + + public KeyboardInputConfig(InputConfig config) + { + if (config != null) + { + Id = config.Id; + ControllerType = config.ControllerType; + PlayerIndex = config.PlayerIndex; + + if (config is not StandardKeyboardInputConfig keyboardConfig) + { + return; + } + + LeftStickUp = keyboardConfig.LeftJoyconStick.StickUp; + LeftStickDown = keyboardConfig.LeftJoyconStick.StickDown; + LeftStickLeft = keyboardConfig.LeftJoyconStick.StickLeft; + LeftStickRight = keyboardConfig.LeftJoyconStick.StickRight; + LeftStickButton = keyboardConfig.LeftJoyconStick.StickButton; + + RightStickUp = keyboardConfig.RightJoyconStick.StickUp; + RightStickDown = keyboardConfig.RightJoyconStick.StickDown; + RightStickLeft = keyboardConfig.RightJoyconStick.StickLeft; + RightStickRight = keyboardConfig.RightJoyconStick.StickRight; + RightStickButton = keyboardConfig.RightJoyconStick.StickButton; + + DpadUp = keyboardConfig.LeftJoycon.DpadUp; + DpadDown = keyboardConfig.LeftJoycon.DpadDown; + DpadLeft = keyboardConfig.LeftJoycon.DpadLeft; + DpadRight = keyboardConfig.LeftJoycon.DpadRight; + ButtonL = keyboardConfig.LeftJoycon.ButtonL; + ButtonMinus = keyboardConfig.LeftJoycon.ButtonMinus; + LeftButtonSl = keyboardConfig.LeftJoycon.ButtonSl; + LeftButtonSr = keyboardConfig.LeftJoycon.ButtonSr; + ButtonZl = keyboardConfig.LeftJoycon.ButtonZl; + + ButtonA = keyboardConfig.RightJoycon.ButtonA; + ButtonB = keyboardConfig.RightJoycon.ButtonB; + ButtonX = keyboardConfig.RightJoycon.ButtonX; + ButtonY = keyboardConfig.RightJoycon.ButtonY; + ButtonR = keyboardConfig.RightJoycon.ButtonR; + ButtonPlus = keyboardConfig.RightJoycon.ButtonPlus; + RightButtonSl = keyboardConfig.RightJoycon.ButtonSl; + RightButtonSr = keyboardConfig.RightJoycon.ButtonSr; + ButtonZr = keyboardConfig.RightJoycon.ButtonZr; + } + } + + public InputConfig GetConfig() + { + var config = new StandardKeyboardInputConfig + { + Id = Id, + Backend = InputBackendType.WindowKeyboard, + PlayerIndex = PlayerIndex, + ControllerType = ControllerType, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = DpadUp, + DpadDown = DpadDown, + DpadLeft = DpadLeft, + DpadRight = DpadRight, + ButtonL = ButtonL, + ButtonMinus = ButtonMinus, + ButtonZl = ButtonZl, + ButtonSl = LeftButtonSl, + ButtonSr = LeftButtonSr, + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = ButtonA, + ButtonB = ButtonB, + ButtonX = ButtonX, + ButtonY = ButtonY, + ButtonPlus = ButtonPlus, + ButtonSl = RightButtonSl, + ButtonSr = RightButtonSr, + ButtonR = ButtonR, + ButtonZr = ButtonZr, + }, + LeftJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = LeftStickUp, + StickDown = LeftStickDown, + StickRight = LeftStickRight, + StickLeft = LeftStickLeft, + StickButton = LeftStickButton, + }, + RightJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = RightStickUp, + StickDown = RightStickDown, + StickLeft = RightStickLeft, + StickRight = RightStickRight, + StickButton = RightStickButton, + }, + Version = InputConfig.CurrentVersion, + }; + + return config; + } + } +} diff --git a/src/Ryujinx/UI/Models/ModModel.cs b/src/Ryujinx/UI/Models/ModModel.cs new file mode 100644 index 00000000..ee28ca5f --- /dev/null +++ b/src/Ryujinx/UI/Models/ModModel.cs @@ -0,0 +1,32 @@ +using Ryujinx.Ava.UI.ViewModels; +using System.IO; + +namespace Ryujinx.Ava.UI.Models +{ + public class ModModel : BaseModel + { + private bool _enabled; + + public bool Enabled + { + get => _enabled; + set + { + _enabled = value; + OnPropertyChanged(); + } + } + + public bool InSd { get; } + public string Path { get; } + public string Name { get; } + + public ModModel(string path, string name, bool enabled, bool inSd) + { + Path = path; + Name = name; + Enabled = enabled; + InSd = inSd; + } + } +} diff --git a/src/Ryujinx/UI/Models/PlayerModel.cs b/src/Ryujinx/UI/Models/PlayerModel.cs new file mode 100644 index 00000000..a19852b9 --- /dev/null +++ b/src/Ryujinx/UI/Models/PlayerModel.cs @@ -0,0 +1,6 @@ +using Ryujinx.Common.Configuration.Hid; + +namespace Ryujinx.Ava.UI.Models +{ + public record PlayerModel(PlayerIndex Id, string Name); +} diff --git a/src/Ryujinx/UI/Models/ProfileImageModel.cs b/src/Ryujinx/UI/Models/ProfileImageModel.cs new file mode 100644 index 00000000..99365dfc --- /dev/null +++ b/src/Ryujinx/UI/Models/ProfileImageModel.cs @@ -0,0 +1,32 @@ +using Avalonia.Media; +using Ryujinx.Ava.UI.ViewModels; + +namespace Ryujinx.Ava.UI.Models +{ + public class ProfileImageModel : BaseModel + { + public ProfileImageModel(string name, byte[] data) + { + Name = name; + Data = data; + } + + public string Name { get; set; } + public byte[] Data { get; set; } + + private SolidColorBrush _backgroundColor = new(Colors.White); + + public SolidColorBrush BackgroundColor + { + get + { + return _backgroundColor; + } + set + { + _backgroundColor = value; + OnPropertyChanged(); + } + } + } +} diff --git a/src/Ryujinx/UI/Models/SaveModel.cs b/src/Ryujinx/UI/Models/SaveModel.cs new file mode 100644 index 00000000..181295b0 --- /dev/null +++ b/src/Ryujinx/UI/Models/SaveModel.cs @@ -0,0 +1,96 @@ +using LibHac.Fs; +using LibHac.Ncm; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.HLE.FileSystem; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common.Helper; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Path = System.IO.Path; + +namespace Ryujinx.Ava.UI.Models +{ + public class SaveModel : BaseModel + { + private long _size; + + public ulong SaveId { get; } + public ProgramId TitleId { get; } + public string TitleIdString => $"{TitleId.Value:X16}"; + public UserId UserId { get; } + public bool InGameList { get; } + public string Title { get; } + public byte[] Icon { get; } + + public long Size + { + get => _size; set + { + _size = value; + SizeAvailable = true; + OnPropertyChanged(); + OnPropertyChanged(nameof(SizeString)); + OnPropertyChanged(nameof(SizeAvailable)); + } + } + + public bool SizeAvailable { get; set; } + + public string SizeString => ValueFormatUtils.FormatFileSize(Size); + + public SaveModel(SaveDataInfo info) + { + SaveId = info.SaveDataId; + TitleId = info.ProgramId; + UserId = info.UserId; + + var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.IdString.ToUpper() == TitleIdString); + + InGameList = appData != null; + + if (InGameList) + { + Icon = appData.Icon; + Title = appData.Name; + } + else + { + var appMetadata = ApplicationLibrary.LoadAndSaveMetaData(TitleIdString); + Title = appMetadata.Title ?? TitleIdString; + } + + Task.Run(() => + { + var saveRoot = Path.Combine(VirtualFileSystem.GetNandPath(), $"user/save/{info.SaveDataId:x16}"); + + long totalSize = GetDirectorySize(saveRoot); + + static long GetDirectorySize(string path) + { + long size = 0; + if (Directory.Exists(path)) + { + var directories = Directory.GetDirectories(path); + foreach (var directory in directories) + { + size += GetDirectorySize(directory); + } + + var files = Directory.GetFiles(path); + foreach (var file in files) + { + size += new FileInfo(file).Length; + } + } + + return size; + } + + Size = totalSize; + }); + + } + } +} diff --git a/src/Ryujinx/UI/Models/StatusInitEventArgs.cs b/src/Ryujinx/UI/Models/StatusInitEventArgs.cs new file mode 100644 index 00000000..4b08737e --- /dev/null +++ b/src/Ryujinx/UI/Models/StatusInitEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.Ava.UI.Models +{ + internal class StatusInitEventArgs : EventArgs + { + public string GpuBackend { get; } + public string GpuName { get; } + + public StatusInitEventArgs(string gpuBackend, string gpuName) + { + GpuBackend = gpuBackend; + GpuName = gpuName; + } + } +} diff --git a/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs b/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs new file mode 100644 index 00000000..ee5648fa --- /dev/null +++ b/src/Ryujinx/UI/Models/StatusUpdatedEventArgs.cs @@ -0,0 +1,24 @@ +using System; + +namespace Ryujinx.Ava.UI.Models +{ + internal class StatusUpdatedEventArgs : EventArgs + { + public bool VSyncEnabled { get; } + public string VolumeStatus { get; } + public string AspectRatio { get; } + public string DockedMode { get; } + public string FifoStatus { get; } + public string GameStatus { get; } + + public StatusUpdatedEventArgs(bool vSyncEnabled, string volumeStatus, string dockedMode, string aspectRatio, string gameStatus, string fifoStatus) + { + VSyncEnabled = vSyncEnabled; + VolumeStatus = volumeStatus; + DockedMode = dockedMode; + AspectRatio = aspectRatio; + GameStatus = gameStatus; + FifoStatus = fifoStatus; + } + } +} diff --git a/src/Ryujinx/UI/Models/TempProfile.cs b/src/Ryujinx/UI/Models/TempProfile.cs new file mode 100644 index 00000000..659092e6 --- /dev/null +++ b/src/Ryujinx/UI/Models/TempProfile.cs @@ -0,0 +1,61 @@ +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System; + +namespace Ryujinx.Ava.UI.Models +{ + public class TempProfile : BaseModel + { + private readonly UserProfile _profile; + private byte[] _image; + private string _name = String.Empty; + private UserId _userId; + + public static uint MaxProfileNameLength => 0x20; + + public byte[] Image + { + get => _image; + set + { + _image = value; + OnPropertyChanged(); + } + } + + public UserId UserId + { + get => _userId; + set + { + _userId = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(UserIdString)); + } + } + + public string UserIdString => _userId.ToString(); + + public string Name + { + get => _name; + set + { + _name = value; + OnPropertyChanged(); + } + } + + public TempProfile(UserProfile profile) + { + _profile = profile; + + if (_profile != null) + { + Image = profile.Image; + Name = profile.Name; + UserId = profile.UserId; + } + } + } +} diff --git a/src/Ryujinx/UI/Models/TimeZone.cs b/src/Ryujinx/UI/Models/TimeZone.cs new file mode 100644 index 00000000..950fbce4 --- /dev/null +++ b/src/Ryujinx/UI/Models/TimeZone.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Ava.UI.Models +{ + internal class TimeZone + { + public TimeZone(string utcDifference, string location, string abbreviation) + { + UtcDifference = utcDifference; + Location = location; + Abbreviation = abbreviation; + } + + public string UtcDifference { get; set; } + public string Location { get; set; } + public string Abbreviation { get; set; } + } +} diff --git a/src/Ryujinx/UI/Models/TitleUpdateModel.cs b/src/Ryujinx/UI/Models/TitleUpdateModel.cs new file mode 100644 index 00000000..46f6f46d --- /dev/null +++ b/src/Ryujinx/UI/Models/TitleUpdateModel.cs @@ -0,0 +1,21 @@ +using Ryujinx.Ava.Common.Locale; + +namespace Ryujinx.Ava.UI.Models +{ + public class TitleUpdateModel + { + public uint Version { get; } + public string Path { get; } + public string Label { get; } + + public TitleUpdateModel(uint version, string displayVersion, string path) + { + Version = version; + Label = LocaleManager.Instance.UpdateAndGetDynamicValue( + System.IO.Path.GetExtension(path)?.ToLower() == ".xci" ? LocaleKeys.TitleBundledUpdateVersionLabel : LocaleKeys.TitleUpdateVersionLabel, + displayVersion + ); + Path = path; + } + } +} diff --git a/src/Ryujinx/UI/Models/UserProfile.cs b/src/Ryujinx/UI/Models/UserProfile.cs new file mode 100644 index 00000000..7a9237fe --- /dev/null +++ b/src/Ryujinx/UI/Models/UserProfile.cs @@ -0,0 +1,104 @@ +using Avalonia.Media; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Views.User; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile; + +namespace Ryujinx.Ava.UI.Models +{ + public class UserProfile : BaseModel + { + private readonly Profile _profile; + private readonly NavigationDialogHost _owner; + private byte[] _image; + private string _name; + private UserId _userId; + private bool _isPointerOver; + private IBrush _backgroundColor; + + public byte[] Image + { + get => _image; + set + { + _image = value; + OnPropertyChanged(); + } + } + + public UserId UserId + { + get => _userId; + set + { + _userId = value; + OnPropertyChanged(); + } + } + + public string Name + { + get => _name; + set + { + _name = value; + OnPropertyChanged(); + } + } + + public bool IsPointerOver + { + get => _isPointerOver; + set + { + _isPointerOver = value; + OnPropertyChanged(); + } + } + + public IBrush BackgroundColor + { + get => _backgroundColor; + set + { + _backgroundColor = value; + OnPropertyChanged(); + } + } + + public UserProfile(Profile profile, NavigationDialogHost owner) + { + _profile = profile; + _owner = owner; + + UpdateBackground(); + + Image = profile.Image; + Name = profile.Name; + UserId = profile.UserId; + } + + public void UpdateState() + { + UpdateBackground(); + OnPropertyChanged(nameof(Name)); + } + + private void UpdateBackground() + { + var currentApplication = Avalonia.Application.Current; + currentApplication.Styles.TryGetResource("ControlFillColorSecondary", currentApplication.ActualThemeVariant, out object color); + + if (color is not null) + { + BackgroundColor = _profile.AccountState == AccountState.Open ? new SolidColorBrush((Color)color) : Brushes.Transparent; + } + } + + public void Recover(UserProfile userProfile) + { + _owner.Navigate(typeof(UserEditorView), (_owner, userProfile, true)); + } + } +} diff --git a/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs new file mode 100644 index 00000000..0930e779 --- /dev/null +++ b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs @@ -0,0 +1,226 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Platform; +using Ryujinx.Common.Configuration; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using SPB.Graphics; +using SPB.Platform; +using SPB.Platform.GLX; +using SPB.Platform.X11; +using SPB.Windowing; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Threading.Tasks; +using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop; + +namespace Ryujinx.Ava.UI.Renderer +{ + public class EmbeddedWindow : NativeControlHost + { + private WindowProc _wndProcDelegate; + private string _className; + + protected GLXWindow X11Window { get; set; } + + protected IntPtr WindowHandle { get; set; } + protected IntPtr X11Display { get; set; } + protected IntPtr NsView { get; set; } + protected IntPtr MetalLayer { get; set; } + + public delegate void UpdateBoundsCallbackDelegate(Rect rect); + private UpdateBoundsCallbackDelegate _updateBoundsCallback; + + public event EventHandler WindowCreated; + public event EventHandler BoundsChanged; + + public EmbeddedWindow() + { + this.GetObservable(BoundsProperty).Subscribe(StateChanged); + + Initialized += OnNativeEmbeddedWindowCreated; + } + + public virtual void OnWindowCreated() { } + + protected virtual void OnWindowDestroyed() { } + + protected virtual void OnWindowDestroying() + { + WindowHandle = IntPtr.Zero; + X11Display = IntPtr.Zero; + NsView = IntPtr.Zero; + MetalLayer = IntPtr.Zero; + } + + private void OnNativeEmbeddedWindowCreated(object sender, EventArgs e) + { + OnWindowCreated(); + + Task.Run(() => + { + WindowCreated?.Invoke(this, WindowHandle); + }); + } + + private void StateChanged(Rect rect) + { + BoundsChanged?.Invoke(this, rect.Size); + _updateBoundsCallback?.Invoke(rect); + } + + protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle control) + { + if (OperatingSystem.IsLinux()) + { + return CreateLinux(control); + } + + if (OperatingSystem.IsWindows()) + { + return CreateWin32(control); + } + + if (OperatingSystem.IsMacOS()) + { + return CreateMacOS(); + } + + return base.CreateNativeControlCore(control); + } + + protected override void DestroyNativeControlCore(IPlatformHandle control) + { + OnWindowDestroying(); + + if (OperatingSystem.IsLinux()) + { + DestroyLinux(); + } + else if (OperatingSystem.IsWindows()) + { + DestroyWin32(control); + } + else if (OperatingSystem.IsMacOS()) + { + DestroyMacOS(); + } + else + { + base.DestroyNativeControlCore(control); + } + + OnWindowDestroyed(); + } + + [SupportedOSPlatform("linux")] + private IPlatformHandle CreateLinux(IPlatformHandle control) + { + if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan) + { + X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(control.Handle)); + X11Window.Hide(); + } + else + { + X11Window = PlatformHelper.CreateOpenGLWindow(new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false), 0, 0, 100, 100) as GLXWindow; + } + + WindowHandle = X11Window.WindowHandle.RawHandle; + X11Display = X11Window.DisplayHandle.RawHandle; + + return new PlatformHandle(WindowHandle, "X11"); + } + + [SupportedOSPlatform("windows")] + IPlatformHandle CreateWin32(IPlatformHandle control) + { + _className = "NativeWindow-" + Guid.NewGuid(); + + _wndProcDelegate = delegate (IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam) + { + switch (msg) + { + case WindowsMessages.NcHitTest: + return -1; + } + + return DefWindowProc(hWnd, msg, wParam, lParam); + }; + + WndClassEx wndClassEx = new() + { + cbSize = Marshal.SizeOf(), + hInstance = GetModuleHandle(null), + lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate), + style = ClassStyles.CsOwndc, + lpszClassName = Marshal.StringToHGlobalUni(_className), + hCursor = CreateArrowCursor() + }; + + RegisterClassEx(ref wndClassEx); + + WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WsChild, 0, 0, 640, 480, control.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + + SetWindowLongPtrW(control.Handle, GWLP_WNDPROC, wndClassEx.lpfnWndProc); + + Marshal.FreeHGlobal(wndClassEx.lpszClassName); + + return new PlatformHandle(WindowHandle, "HWND"); + } + + [SupportedOSPlatform("macos")] + IPlatformHandle CreateMacOS() + { + // Create a new CAMetalLayer. + ObjectiveC.Object layerObject = new("CAMetalLayer"); + ObjectiveC.Object metalLayer = layerObject.GetFromMessage("alloc"); + metalLayer.SendMessage("init"); + + // Create a child NSView to render into. + ObjectiveC.Object nsViewObject = new("NSView"); + ObjectiveC.Object child = nsViewObject.GetFromMessage("alloc"); + child.SendMessage("init", new ObjectiveC.NSRect(0, 0, 0, 0)); + + // Make its renderer our metal layer. + child.SendMessage("setWantsLayer:", 1); + child.SendMessage("setLayer:", metalLayer); + metalLayer.SendMessage("setContentsScale:", Program.DesktopScaleFactor); + + // Ensure the scale factor is up to date. + _updateBoundsCallback = rect => + { + metalLayer.SendMessage("setContentsScale:", Program.DesktopScaleFactor); + }; + + IntPtr nsView = child.ObjPtr; + MetalLayer = metalLayer.ObjPtr; + NsView = nsView; + + return new PlatformHandle(nsView, "NSView"); + } + + [SupportedOSPlatform("Linux")] + void DestroyLinux() + { + X11Window?.Dispose(); + } + + [SupportedOSPlatform("windows")] + void DestroyWin32(IPlatformHandle handle) + { + DestroyWindow(handle.Handle); + UnregisterClass(_className, GetModuleHandle(null)); + } + + [SupportedOSPlatform("macos")] +#pragma warning disable CA1822 // Mark member as static + void DestroyMacOS() + { + // TODO + } +#pragma warning restore CA1822 + } +} diff --git a/src/Ryujinx/UI/Renderer/EmbeddedWindowOpenGL.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindowOpenGL.cs new file mode 100644 index 00000000..3842301d --- /dev/null +++ b/src/Ryujinx/UI/Renderer/EmbeddedWindowOpenGL.cs @@ -0,0 +1,94 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.OpenGL; +using Ryujinx.UI.Common.Configuration; +using SPB.Graphics; +using SPB.Graphics.Exceptions; +using SPB.Graphics.OpenGL; +using SPB.Platform; +using SPB.Platform.WGL; +using SPB.Windowing; +using System; + +namespace Ryujinx.Ava.UI.Renderer +{ + public class EmbeddedWindowOpenGL : EmbeddedWindow + { + private SwappableNativeWindowBase _window; + + public OpenGLContextBase Context { get; set; } + + protected override void OnWindowDestroying() + { + Context.Dispose(); + + base.OnWindowDestroying(); + } + + public override void OnWindowCreated() + { + base.OnWindowCreated(); + + if (OperatingSystem.IsWindows()) + { + _window = new WGLWindow(new NativeHandle(WindowHandle)); + } + else if (OperatingSystem.IsLinux()) + { + _window = X11Window; + } + else + { + throw new PlatformNotSupportedException(); + } + + var flags = OpenGLContextFlags.Compat; + if (ConfigurationState.Instance.Logger.GraphicsDebugLevel != GraphicsDebugLevel.None) + { + flags |= OpenGLContextFlags.Debug; + } + + var graphicsMode = Environment.OSVersion.Platform == PlatformID.Unix ? new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false) : FramebufferFormat.Default; + + Context = PlatformHelper.CreateOpenGLContext(graphicsMode, 3, 3, flags); + + Context.Initialize(_window); + Context.MakeCurrent(_window); + + GL.LoadBindings(new OpenTKBindingsContext(Context.GetProcAddress)); + + Context.MakeCurrent(null); + } + + public void MakeCurrent(bool unbind = false, bool shouldThrow = true) + { + try + { + Context?.MakeCurrent(!unbind ? _window : null); + } + catch (ContextException e) + { + if (shouldThrow) + { + throw; + } + + Logger.Warning?.Print(LogClass.UI, $"Failed to {(!unbind ? "bind" : "unbind")} OpenGL context: {e}"); + } + } + + public void SwapBuffers() + { + _window?.SwapBuffers(); + } + + public void InitializeBackgroundContext(IRenderer renderer) + { + (renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Context)); + + MakeCurrent(); + } + } +} diff --git a/src/Ryujinx/UI/Renderer/EmbeddedWindowVulkan.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindowVulkan.cs new file mode 100644 index 00000000..fafbec20 --- /dev/null +++ b/src/Ryujinx/UI/Renderer/EmbeddedWindowVulkan.cs @@ -0,0 +1,42 @@ +using Silk.NET.Vulkan; +using SPB.Graphics.Vulkan; +using SPB.Platform.Metal; +using SPB.Platform.Win32; +using SPB.Platform.X11; +using SPB.Windowing; +using System; + +namespace Ryujinx.Ava.UI.Renderer +{ + public class EmbeddedWindowVulkan : EmbeddedWindow + { + public SurfaceKHR CreateSurface(Instance instance) + { + NativeWindowBase nativeWindowBase; + + if (OperatingSystem.IsWindows()) + { + nativeWindowBase = new SimpleWin32Window(new NativeHandle(WindowHandle)); + } + else if (OperatingSystem.IsLinux()) + { + nativeWindowBase = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle)); + } + else if (OperatingSystem.IsMacOS()) + { + nativeWindowBase = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer)); + } + else + { + throw new PlatformNotSupportedException(); + } + + return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, nativeWindowBase)); + } + + public SurfaceKHR CreateSurface(Instance instance, Vk _) + { + return CreateSurface(instance); + } + } +} diff --git a/src/Ryujinx/UI/Renderer/OpenTKBindingsContext.cs b/src/Ryujinx/UI/Renderer/OpenTKBindingsContext.cs new file mode 100644 index 00000000..85e8585f --- /dev/null +++ b/src/Ryujinx/UI/Renderer/OpenTKBindingsContext.cs @@ -0,0 +1,20 @@ +using OpenTK; +using System; + +namespace Ryujinx.Ava.UI.Renderer +{ + internal class OpenTKBindingsContext : IBindingsContext + { + private readonly Func _getProcAddress; + + public OpenTKBindingsContext(Func getProcAddress) + { + _getProcAddress = getProcAddress; + } + + public IntPtr GetProcAddress(string procName) + { + return _getProcAddress(procName); + } + } +} diff --git a/src/Ryujinx/UI/Renderer/RendererHost.axaml b/src/Ryujinx/UI/Renderer/RendererHost.axaml new file mode 100644 index 00000000..e0b586b4 --- /dev/null +++ b/src/Ryujinx/UI/Renderer/RendererHost.axaml @@ -0,0 +1,12 @@ + + diff --git a/src/Ryujinx/UI/Renderer/RendererHost.axaml.cs b/src/Ryujinx/UI/Renderer/RendererHost.axaml.cs new file mode 100644 index 00000000..d055d9ea --- /dev/null +++ b/src/Ryujinx/UI/Renderer/RendererHost.axaml.cs @@ -0,0 +1,68 @@ +using Avalonia; +using Avalonia.Controls; +using Ryujinx.Common.Configuration; +using Ryujinx.UI.Common.Configuration; +using System; + +namespace Ryujinx.Ava.UI.Renderer +{ + public partial class RendererHost : UserControl, IDisposable + { + public readonly EmbeddedWindow EmbeddedWindow; + + public event EventHandler WindowCreated; + public event Action BoundsChanged; + + public RendererHost() + { + InitializeComponent(); + + if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl) + { + EmbeddedWindow = new EmbeddedWindowOpenGL(); + } + else + { + EmbeddedWindow = new EmbeddedWindowVulkan(); + } + + Initialize(); + } + + private void Initialize() + { + EmbeddedWindow.WindowCreated += CurrentWindow_WindowCreated; + EmbeddedWindow.BoundsChanged += CurrentWindow_BoundsChanged; + + Content = EmbeddedWindow; + } + + public void Dispose() + { + if (EmbeddedWindow != null) + { + EmbeddedWindow.WindowCreated -= CurrentWindow_WindowCreated; + EmbeddedWindow.BoundsChanged -= CurrentWindow_BoundsChanged; + } + + GC.SuppressFinalize(this); + } + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + + Dispose(); + } + + private void CurrentWindow_BoundsChanged(object sender, Size e) + { + BoundsChanged?.Invoke(sender, e); + } + + private void CurrentWindow_WindowCreated(object sender, IntPtr e) + { + WindowCreated?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/src/Ryujinx/UI/Renderer/SPBOpenGLContext.cs b/src/Ryujinx/UI/Renderer/SPBOpenGLContext.cs new file mode 100644 index 00000000..63bf6cf7 --- /dev/null +++ b/src/Ryujinx/UI/Renderer/SPBOpenGLContext.cs @@ -0,0 +1,49 @@ +using OpenTK.Graphics.OpenGL; +using Ryujinx.Graphics.OpenGL; +using SPB.Graphics; +using SPB.Graphics.OpenGL; +using SPB.Platform; +using SPB.Windowing; + +namespace Ryujinx.Ava.UI.Renderer +{ + class SPBOpenGLContext : IOpenGLContext + { + private readonly OpenGLContextBase _context; + private readonly NativeWindowBase _window; + + private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window) + { + _context = context; + _window = window; + } + + public void Dispose() + { + _context.Dispose(); + _window.Dispose(); + } + + public void MakeCurrent() + { + _context.MakeCurrent(_window); + } + + public bool HasContext() => _context.IsCurrent; + + public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext) + { + OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext); + NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100); + + context.Initialize(window); + context.MakeCurrent(window); + + GL.LoadBindings(new OpenTKBindingsContext(context.GetProcAddress)); + + context.MakeCurrent(null); + + return new SPBOpenGLContext(context, window); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs new file mode 100644 index 00000000..f8fd5b7d --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs @@ -0,0 +1,149 @@ +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using Avalonia.Styling; +using Avalonia.Threading; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Common.Utilities; +using Ryujinx.UI.Common.Configuration; +using System; +using System.Net.Http; +using System.Net.NetworkInformation; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class AboutWindowViewModel : BaseModel, IDisposable + { + private Bitmap _githubLogo; + private Bitmap _discordLogo; + private Bitmap _patreonLogo; + private Bitmap _twitterLogo; + + private string _version; + private string _supporters; + + public Bitmap GithubLogo + { + get => _githubLogo; + set + { + _githubLogo = value; + OnPropertyChanged(); + } + } + + public Bitmap DiscordLogo + { + get => _discordLogo; + set + { + _discordLogo = value; + OnPropertyChanged(); + } + } + + public Bitmap PatreonLogo + { + get => _patreonLogo; + set + { + _patreonLogo = value; + OnPropertyChanged(); + } + } + + public Bitmap TwitterLogo + { + get => _twitterLogo; + set + { + _twitterLogo = value; + OnPropertyChanged(); + } + } + + public string Supporters + { + get => _supporters; + set + { + _supporters = value; + OnPropertyChanged(); + } + } + + public string Version + { + get => _version; + set + { + _version = value; + OnPropertyChanged(); + } + } + + public string Developers => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.AboutPageDeveloperListMore, "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz"); + + public AboutWindowViewModel() + { + Version = Program.Version; + UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value); + Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson); + + ThemeManager.ThemeChanged += ThemeManager_ThemeChanged; + } + + private void ThemeManager_ThemeChanged(object sender, EventArgs e) + { + Dispatcher.UIThread.Post(() => UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value)); + } + + private void UpdateLogoTheme(string theme) + { + bool isDarkTheme = theme == "Dark" || (theme == "Auto" && App.DetectSystemTheme() == ThemeVariant.Dark); + + string basePath = "resm:Ryujinx.UI.Common.Resources."; + string themeSuffix = isDarkTheme ? "Dark.png" : "Light.png"; + + GithubLogo = LoadBitmap($"{basePath}Logo_GitHub_{themeSuffix}?assembly=Ryujinx.UI.Common"); + DiscordLogo = LoadBitmap($"{basePath}Logo_Discord_{themeSuffix}?assembly=Ryujinx.UI.Common"); + PatreonLogo = LoadBitmap($"{basePath}Logo_Patreon_{themeSuffix}?assembly=Ryujinx.UI.Common"); + TwitterLogo = LoadBitmap($"{basePath}Logo_Twitter_{themeSuffix}?assembly=Ryujinx.UI.Common"); + } + + private Bitmap LoadBitmap(string uri) + { + return new Bitmap(Avalonia.Platform.AssetLoader.Open(new Uri(uri))); + } + + public void Dispose() + { + ThemeManager.ThemeChanged -= ThemeManager_ThemeChanged; + GC.SuppressFinalize(this); + } + + private async Task DownloadPatronsJson() + { + if (!NetworkInterface.GetIsNetworkAvailable()) + { + Supporters = LocaleManager.Instance[LocaleKeys.ConnectionError]; + + return; + } + + HttpClient httpClient = new(); + + try + { + string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/"); + + Supporters = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray)) + "\n\n"; + } + catch + { + Supporters = LocaleManager.Instance[LocaleKeys.ApiError]; + } + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs new file mode 100644 index 00000000..8f09568a --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/AmiiboWindowViewModel.cs @@ -0,0 +1,518 @@ +using Avalonia; +using Avalonia.Collections; +using Avalonia.Media.Imaging; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +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; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class AmiiboWindowViewModel : BaseModel, IDisposable + { + private const string DefaultJson = "{ \"amiibo\": [] }"; + private const float AmiiboImageSize = 350f; + + private readonly string _amiiboJsonPath; + private readonly byte[] _amiiboLogoBytes; + private readonly HttpClient _httpClient; + private readonly StyleableWindow _owner; + + private Bitmap _amiiboImage; + private List _amiiboList; + private AvaloniaList _amiibos; + private ObservableCollection _amiiboSeries; + + private int _amiiboSelectedIndex; + private int _seriesSelectedIndex; + private bool _enableScanning; + 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.FromSeconds(30), + }; + + LastScannedAmiiboId = lastScannedAmiiboId; + TitleId = titleId; + + Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo")); + + _amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json"); + _amiiboList = new List(); + _amiiboSeries = new ObservableCollection(); + _amiibos = new AvaloniaList(); + + _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.UI.Common/Resources/Logo_Amiibo.png"); + + _ = LoadContentAsync(); + } + + public AmiiboWindowViewModel() { } + + public string TitleId { get; set; } + public string LastScannedAmiiboId { get; set; } + + public UserResult Response { get; private set; } + + public bool UseRandomUuid + { + get => _useRandomUuid; + set + { + _useRandomUuid = value; + + OnPropertyChanged(); + } + } + + public bool ShowAllAmiibo + { + get => _showAllAmiibo; + set + { + _showAllAmiibo = value; + + ParseAmiiboData(); + + OnPropertyChanged(); + } + } + + public AvaloniaList AmiiboList + { + get => _amiibos; + set + { + _amiibos = value; + + OnPropertyChanged(); + } + } + + public ObservableCollection AmiiboSeries + { + get => _amiiboSeries; + set + { + _amiiboSeries = value; + OnPropertyChanged(); + } + } + + public int SeriesSelectedIndex + { + get => _seriesSelectedIndex; + set + { + _seriesSelectedIndex = value; + + FilterAmiibo(); + + OnPropertyChanged(); + } + } + + public int AmiiboSelectedIndex + { + get => _amiiboSelectedIndex; + set + { + _amiiboSelectedIndex = value; + + EnableScanning = _amiiboSelectedIndex >= 0 && _amiiboSelectedIndex < _amiibos.Count; + + SetAmiiboDetails(); + + OnPropertyChanged(); + } + } + + public Bitmap AmiiboImage + { + get => _amiiboImage; + set + { + _amiiboImage = value; + + OnPropertyChanged(); + } + } + + public string Usage + { + get => _usage; + set + { + _usage = value; + + OnPropertyChanged(); + } + } + + public bool EnableScanning + { + get => _enableScanning; + set + { + _enableScanning = value; + + OnPropertyChanged(); + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _httpClient.Dispose(); + } + + private static bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson) + { + if (string.IsNullOrEmpty(json)) + { + amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); + + return false; + } + + try + { + amiiboJson = JsonHelper.Deserialize(json, _serializerContext.AmiiboJson); + + return true; + } + catch (JsonException exception) + { + Logger.Error?.Print(LogClass.Application, $"Unable to deserialize amiibo data: {exception}"); + amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson); + + return false; + } + } + + private async Task GetMostRecentAmiiboListOrDefaultJson() + { + bool localIsValid = false; + bool remoteIsValid = false; + AmiiboJson amiiboJson = new(); + + try + { + try + { + if (File.Exists(_amiiboJsonPath)) + { + localIsValid = TryGetAmiiboJson(await File.ReadAllTextAsync(_amiiboJsonPath), out amiiboJson); + } + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"Unable to read data from '{_amiiboJsonPath}': {exception}"); + } + + if (!localIsValid || await NeedsUpdate(amiiboJson.LastUpdated)) + { + remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson); + } + } + catch (Exception exception) + { + if (!(localIsValid || remoteIsValid)) + { + Logger.Error?.Print(LogClass.Application, $"Couldn't get valid amiibo data: {exception}"); + + // Neither local or remote files are valid JSON, close window. + ShowInfoDialog(); + Close(); + } + else if (!remoteIsValid) + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't update amiibo data: {exception}"); + + // Only the local file is valid, the local one should be used + // but the user should be warned. + ShowInfoDialog(); + } + } + + return amiiboJson; + } + + private async Task LoadContentAsync() + { + AmiiboJson amiiboJson = await GetMostRecentAmiiboListOrDefaultJson(); + + _amiiboList = amiiboJson.Amiibo.OrderBy(amiibo => amiibo.AmiiboSeries).ToList(); + + ParseAmiiboData(); + } + + private void ParseAmiiboData() + { + _amiiboSeries.Clear(); + _amiibos.Clear(); + + for (int i = 0; i < _amiiboList.Count; i++) + { + if (!_amiiboSeries.Contains(_amiiboList[i].AmiiboSeries)) + { + if (!ShowAllAmiibo) + { + foreach (AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch) + { + if (game != null) + { + if (game.GameId.Contains(TitleId)) + { + AmiiboSeries.Add(_amiiboList[i].AmiiboSeries); + + break; + } + } + } + } + else + { + AmiiboSeries.Add(_amiiboList[i].AmiiboSeries); + } + } + } + + if (LastScannedAmiiboId != "") + { + SelectLastScannedAmiibo(); + } + else + { + SeriesSelectedIndex = 0; + } + } + + private void SelectLastScannedAmiibo() + { + AmiiboApi scanned = _amiiboList.Find(amiibo => amiibo.GetId() == LastScannedAmiiboId); + + SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries); + AmiiboSelectedIndex = AmiiboList.IndexOf(scanned); + } + + private void FilterAmiibo() + { + _amiibos.Clear(); + + if (_seriesSelectedIndex < 0) + { + return; + } + + List amiiboSortedList = _amiiboList + .Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex]) + .OrderBy(amiibo => amiibo.Name).ToList(); + + for (int i = 0; i < amiiboSortedList.Count; i++) + { + if (!_amiibos.Contains(amiiboSortedList[i])) + { + if (!_showAllAmiibo) + { + foreach (AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch) + { + if (game != null) + { + if (game.GameId.Contains(TitleId)) + { + _amiibos.Add(amiiboSortedList[i]); + + break; + } + } + } + } + else + { + _amiibos.Add(amiiboSortedList[i]); + } + } + } + + AmiiboSelectedIndex = 0; + } + + private void SetAmiiboDetails() + { + ResetAmiiboPreview(); + + Usage = string.Empty; + + if (_amiiboSelectedIndex < 0) + { + return; + } + + AmiiboApi selected = _amiibos[_amiiboSelectedIndex]; + + string imageUrl = _amiiboList.Find(amiibo => amiibo.Equals(selected)).Image; + + StringBuilder usageStringBuilder = new(); + + for (int i = 0; i < _amiiboList.Count; i++) + { + if (_amiiboList[i].Equals(selected)) + { + bool writable = false; + + foreach (AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch) + { + if (item.GameId.Contains(TitleId)) + { + foreach (AmiiboApiUsage usageItem in item.AmiiboUsage) + { + usageStringBuilder.Append($"{Environment.NewLine}- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}"); + + writable = usageItem.Write; + } + } + } + + if (usageStringBuilder.Length == 0) + { + usageStringBuilder.Append($"{LocaleManager.Instance[LocaleKeys.Unknown]}."); + } + + Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : "")} : {usageStringBuilder}"; + } + } + + _ = UpdateAmiiboPreview(imageUrl); + } + + private async Task NeedsUpdate(DateTime oldLastModified) + { + try + { + HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/")); + + if (response.IsSuccessStatusCode) + { + return response.Content.Headers.LastModified != oldLastModified; + } + } + catch (HttpRequestException exception) + { + Logger.Error?.Print(LogClass.Application, $"Unable to check for amiibo data updates: {exception}"); + } + + return false; + } + + private async Task DownloadAmiiboJson() + { + try + { + HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/"); + + if (response.IsSuccessStatusCode) + { + string amiiboJsonString = await response.Content.ReadAsStringAsync(); + + try + { + using FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough); + dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString)); + } + catch (Exception exception) + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't write amiibo data to file '{_amiiboJsonPath}: {exception}'"); + } + + return amiiboJsonString; + } + + Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}"); + } + catch (HttpRequestException exception) + { + Logger.Error?.Print(LogClass.Application, $"Failed to request amiibo data: {exception}"); + } + + await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle], + LocaleManager.Instance[LocaleKeys.DialogAmiiboApiFailFetchMessage], + LocaleManager.Instance[LocaleKeys.InputDialogOk], + "", + LocaleManager.Instance[LocaleKeys.RyujinxInfo]); + + return null; + } + + private void Close() + { + Dispatcher.UIThread.Post(_owner.Close); + } + + private async Task UpdateAmiiboPreview(string imageUrl) + { + HttpResponseMessage response = await _httpClient.GetAsync(imageUrl); + + if (response.IsSuccessStatusCode) + { + byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync(); + using MemoryStream memoryStream = new(amiiboPreviewBytes); + + Bitmap bitmap = new(memoryStream); + + double ratio = Math.Min(AmiiboImageSize / bitmap.Size.Width, + AmiiboImageSize / bitmap.Size.Height); + + int resizeHeight = (int)(bitmap.Size.Height * ratio); + int resizeWidth = (int)(bitmap.Size.Width * ratio); + + 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() + { + using MemoryStream memoryStream = new(_amiiboLogoBytes); + + Bitmap bitmap = new(memoryStream); + + AmiiboImage = bitmap; + } + + private static async void ShowInfoDialog() + { + await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle], + LocaleManager.Instance[LocaleKeys.DialogAmiiboApiConnectErrorMessage], + LocaleManager.Instance[LocaleKeys.InputDialogOk], + "", + LocaleManager.Instance[LocaleKeys.RyujinxInfo]); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/AppListFavoriteComparable.cs b/src/Ryujinx/UI/ViewModels/AppListFavoriteComparable.cs new file mode 100644 index 00000000..e8098450 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/AppListFavoriteComparable.cs @@ -0,0 +1,43 @@ +using Ryujinx.UI.App.Common; +using System; + +namespace Ryujinx.Ava.UI.ViewModels +{ + /// + /// Implements a custom comparer which is used for sorting titles by favorite on a UI. + /// Returns a sorted list of favorites in alphabetical order, followed by all non-favorites sorted alphabetical. + /// + public readonly struct AppListFavoriteComparable : IComparable + { + /// + /// The application data being compared. + /// + private readonly ApplicationData app; + + /// + /// Constructs a new with the specified application data. + /// + /// The app data being compared. + public AppListFavoriteComparable(ApplicationData app) + { + ArgumentNullException.ThrowIfNull(app, nameof(app)); + this.app = app; + } + + /// + public readonly int CompareTo(object o) + { + if (o is AppListFavoriteComparable other) + { + if (app.Favorite == other.app.Favorite) + { + return string.Compare(app.Name, other.app.Name, StringComparison.OrdinalIgnoreCase); + } + + return app.Favorite ? -1 : 1; + } + + throw new InvalidCastException($"Cannot cast {o.GetType()} to {nameof(AppListFavoriteComparable)}"); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/BaseModel.cs b/src/Ryujinx/UI/ViewModels/BaseModel.cs new file mode 100644 index 00000000..4db9cf81 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/BaseModel.cs @@ -0,0 +1,15 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class BaseModel : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs new file mode 100644 index 00000000..c919a7ad --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs @@ -0,0 +1,344 @@ +using Avalonia.Collections; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform.Storage; +using Avalonia.Threading; +using DynamicData; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.Loaders.Processes.Extensions; +using Ryujinx.HLE.Utilities; +using Ryujinx.UI.App.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Application = Avalonia.Application; +using Path = System.IO.Path; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class DownloadableContentManagerViewModel : BaseModel + { + private readonly List _downloadableContentContainerList; + private readonly string _downloadableContentJsonPath; + + private readonly VirtualFileSystem _virtualFileSystem; + private AvaloniaList _downloadableContents = new(); + private AvaloniaList _views = new(); + private AvaloniaList _selectedDownloadableContents = new(); + + private string _search; + private readonly ApplicationData _applicationData; + private readonly IStorageProvider _storageProvider; + + private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public AvaloniaList DownloadableContents + { + get => _downloadableContents; + set + { + _downloadableContents = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(UpdateCount)); + Sort(); + } + } + + public AvaloniaList Views + { + get => _views; + set + { + _views = value; + OnPropertyChanged(); + } + } + + public AvaloniaList SelectedDownloadableContents + { + get => _selectedDownloadableContents; + set + { + _selectedDownloadableContents = value; + OnPropertyChanged(); + } + } + + public string Search + { + get => _search; + set + { + _search = value; + OnPropertyChanged(); + Sort(); + } + } + + public string UpdateCount + { + get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count); + } + + public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData) + { + _virtualFileSystem = virtualFileSystem; + + _applicationData = applicationData; + + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + _storageProvider = desktop.MainWindow.StorageProvider; + } + + _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationData.IdBaseString, "dlc.json"); + + if (!File.Exists(_downloadableContentJsonPath)) + { + _downloadableContentContainerList = new List(); + + Save(); + } + + try + { + _downloadableContentContainerList = JsonHelper.DeserializeFromFile(_downloadableContentJsonPath, _serializerContext.ListDownloadableContentContainer); + } + catch + { + Logger.Error?.Print(LogClass.Configuration, "Downloadable Content JSON failed to deserialize."); + _downloadableContentContainerList = new List(); + } + + LoadDownloadableContents(); + } + + private void LoadDownloadableContents() + { + foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList) + { + if (File.Exists(downloadableContentContainer.ContainerPath)) + { + using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(downloadableContentContainer.ContainerPath, _virtualFileSystem); + + foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) + { + using UniqueRef ncaFile = new(); + + partitionFileSystem.OpenFile(ref ncaFile.Ref, downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath); + if (nca != null) + { + var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), + downloadableContentContainer.ContainerPath, + downloadableContentNca.FullPath, + downloadableContentNca.Enabled); + + DownloadableContents.Add(content); + + if (content.Enabled) + { + SelectedDownloadableContents.Add(content); + } + + OnPropertyChanged(nameof(UpdateCount)); + } + } + } + } + + // NOTE: Try to load downloadable contents from PFS last to preserve enabled state. + AddDownloadableContent(_applicationData.Path); + + // NOTE: Save the list again to remove leftovers. + Save(); + Sort(); + } + + public void Sort() + { + DownloadableContents.AsObservableChangeSet() + .Filter(Filter) + .Bind(out var view).AsObservableList(); + + _views.Clear(); + _views.AddRange(view); + OnPropertyChanged(nameof(Views)); + } + + private bool Filter(object arg) + { + if (arg is DownloadableContentModel content) + { + return string.IsNullOrWhiteSpace(_search) || content.FileName.ToLower().Contains(_search.ToLower()) || content.TitleId.ToLower().Contains(_search.ToLower()); + } + + return false; + } + + private Nca TryOpenNca(IStorage ncaStorage, string containerPath) + { + try + { + return new Nca(_virtualFileSystem.KeySet, ncaStorage); + } + catch (Exception ex) + { + Dispatcher.UIThread.InvokeAsync(async () => + { + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogLoadFileErrorMessage], ex.Message, containerPath)); + }); + } + + return null; + } + + public async void Add() + { + var result = await _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle], + AllowMultiple = true, + FileTypeFilter = new List + { + new("NSP") + { + Patterns = new[] { "*.nsp" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, + MimeTypes = new[] { "application/x-nx-nsp" }, + }, + }, + }); + + foreach (var file in result) + { + if (!AddDownloadableContent(file.Path.LocalPath)) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]); + } + } + } + + private bool AddDownloadableContent(string path) + { + if (!File.Exists(path) || _downloadableContentContainerList.Any(x => x.ContainerPath == path)) + { + return true; + } + + using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem); + + bool success = false; + foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca")) + { + using var ncaFile = new UniqueRef(); + + partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path); + if (nca == null) + { + continue; + } + + if (nca.Header.ContentType == NcaContentType.PublicData) + { + if (nca.GetProgramIdBase() != _applicationData.IdBase) + { + continue; + } + + var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true); + DownloadableContents.Add(content); + Dispatcher.UIThread.InvokeAsync(() => SelectedDownloadableContents.Add(content)); + + success = true; + } + } + + if (success) + { + OnPropertyChanged(nameof(UpdateCount)); + Sort(); + } + + return success; + } + + public void Remove(DownloadableContentModel model) + { + DownloadableContents.Remove(model); + OnPropertyChanged(nameof(UpdateCount)); + Sort(); + } + + public void RemoveAll() + { + DownloadableContents.Clear(); + OnPropertyChanged(nameof(UpdateCount)); + Sort(); + } + + public void EnableAll() + { + SelectedDownloadableContents = new(DownloadableContents); + } + + public void DisableAll() + { + SelectedDownloadableContents.Clear(); + } + + public void Save() + { + _downloadableContentContainerList.Clear(); + + DownloadableContentContainer container = default; + + foreach (DownloadableContentModel downloadableContent in DownloadableContents) + { + if (container.ContainerPath != downloadableContent.ContainerPath) + { + if (!string.IsNullOrWhiteSpace(container.ContainerPath)) + { + _downloadableContentContainerList.Add(container); + } + + container = new DownloadableContentContainer + { + ContainerPath = downloadableContent.ContainerPath, + DownloadableContentNcaList = new List(), + }; + } + + container.DownloadableContentNcaList.Add(new DownloadableContentNca + { + Enabled = downloadableContent.Enabled, + TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16), + FullPath = downloadableContent.FullPath, + }); + } + + if (!string.IsNullOrWhiteSpace(container.ContainerPath)) + { + _downloadableContentContainerList.Add(container); + } + + JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, _serializerContext.ListDownloadableContentContainer); + } + + } +} diff --git a/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs new file mode 100644 index 00000000..6ee79a37 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs @@ -0,0 +1,84 @@ +using Avalonia.Svg.Skia; +using Ryujinx.Ava.UI.Models.Input; +using Ryujinx.Ava.UI.Views.Input; + +namespace Ryujinx.Ava.UI.ViewModels.Input +{ + public class ControllerInputViewModel : BaseModel + { + private GamepadInputConfig _config; + public GamepadInputConfig Config + { + get => _config; + set + { + _config = value; + OnPropertyChanged(); + } + } + + private bool _isLeft; + public bool IsLeft + { + get => _isLeft; + set + { + _isLeft = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(HasSides)); + } + } + + private bool _isRight; + public bool IsRight + { + get => _isRight; + set + { + _isRight = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(HasSides)); + } + } + + public bool HasSides => IsLeft ^ IsRight; + + private SvgImage _image; + public SvgImage Image + { + get => _image; + set + { + _image = value; + OnPropertyChanged(); + } + } + + public readonly InputViewModel ParentModel; + + public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config) + { + ParentModel = model; + model.NotifyChangesEvent += OnParentModelChanged; + OnParentModelChanged(); + Config = config; + } + + public async void ShowMotionConfig() + { + await MotionInputView.Show(this); + } + + public async void ShowRumbleConfig() + { + await RumbleInputView.Show(this); + } + + public void OnParentModelChanged() + { + IsLeft = ParentModel.IsLeft; + IsRight = ParentModel.IsRight; + Image = ParentModel.Image; + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs new file mode 100644 index 00000000..89cc6496 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs @@ -0,0 +1,889 @@ +using Avalonia; +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Svg.Skia; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.Models.Input; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using Ryujinx.Common.Configuration.Hid.Keyboard; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.Input; +using Ryujinx.UI.Common.Configuration; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text.Json; +using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; +using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; +using Key = Ryujinx.Common.Configuration.Hid.Key; + +namespace Ryujinx.Ava.UI.ViewModels.Input +{ + public class InputViewModel : BaseModel, IDisposable + { + private const string Disabled = "disabled"; + private const string ProControllerResource = "Ryujinx.UI.Common/Resources/Controller_ProCon.svg"; + private const string JoyConPairResource = "Ryujinx.UI.Common/Resources/Controller_JoyConPair.svg"; + private const string JoyConLeftResource = "Ryujinx.UI.Common/Resources/Controller_JoyConLeft.svg"; + private const string JoyConRightResource = "Ryujinx.UI.Common/Resources/Controller_JoyConRight.svg"; + private const string KeyboardString = "keyboard"; + private const string ControllerString = "controller"; + private readonly MainWindow _mainWindow; + + private PlayerIndex _playerId; + private int _controller; + private int _controllerNumber; + private string _controllerImage; + private int _device; + private object _configViewModel; + private string _profileName; + private bool _isLoaded; + + private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public IGamepadDriver AvaloniaKeyboardDriver { get; } + public IGamepad SelectedGamepad { get; private set; } + + public ObservableCollection PlayerIndexes { get; set; } + public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; } + internal ObservableCollection Controllers { get; set; } + public AvaloniaList ProfilesList { get; set; } + public AvaloniaList DeviceList { get; set; } + + // XAML Flags + public bool ShowSettings => _device > 0; + public bool IsController => _device > 1; + public bool IsKeyboard => !IsController; + public bool IsRight { get; set; } + public bool IsLeft { get; set; } + + public bool IsModified { get; set; } + public event Action NotifyChangesEvent; + + public object ConfigViewModel + { + get => _configViewModel; + set + { + _configViewModel = value; + + OnPropertyChanged(); + } + } + + public PlayerIndex PlayerId + { + get => _playerId; + set + { + if (IsModified) + { + return; + } + + IsModified = false; + _playerId = value; + + if (!Enum.IsDefined(typeof(PlayerIndex), _playerId)) + { + _playerId = PlayerIndex.Player1; + } + + LoadConfiguration(); + LoadDevice(); + LoadProfiles(); + + _isLoaded = true; + + OnPropertyChanged(); + } + } + + public int Controller + { + get => _controller; + set + { + _controller = value; + + if (_controller == -1) + { + _controller = 0; + } + + if (Controllers.Count > 0 && value < Controllers.Count && _controller > -1) + { + ControllerType controller = Controllers[_controller].Type; + + IsLeft = true; + IsRight = true; + + switch (controller) + { + case ControllerType.Handheld: + ControllerImage = JoyConPairResource; + break; + case ControllerType.ProController: + ControllerImage = ProControllerResource; + break; + case ControllerType.JoyconPair: + ControllerImage = JoyConPairResource; + break; + case ControllerType.JoyconLeft: + ControllerImage = JoyConLeftResource; + IsRight = false; + break; + case ControllerType.JoyconRight: + ControllerImage = JoyConRightResource; + IsLeft = false; + break; + } + + LoadInputDriver(); + LoadProfiles(); + } + + OnPropertyChanged(); + NotifyChanges(); + } + } + + public string ControllerImage + { + get => _controllerImage; + set + { + _controllerImage = value; + + OnPropertyChanged(); + OnPropertyChanged(nameof(Image)); + } + } + + public SvgImage Image + { + get + { + SvgImage image = new(); + + if (!string.IsNullOrWhiteSpace(_controllerImage)) + { + SvgSource source = SvgSource.LoadFromStream(EmbeddedResources.GetStream(_controllerImage)); + + image.Source = source; + } + + return image; + } + } + + public string ProfileName + { + get => _profileName; set + { + _profileName = value; + + OnPropertyChanged(); + } + } + + public int Device + { + get => _device; + set + { + _device = value < 0 ? 0 : value; + + if (_device >= Devices.Count) + { + return; + } + + var selected = Devices[_device].Type; + + if (selected != DeviceType.None) + { + LoadControllers(); + + if (_isLoaded) + { + LoadConfiguration(LoadDefaultConfiguration()); + } + } + + OnPropertyChanged(); + NotifyChanges(); + } + } + + public InputConfig Config { get; set; } + + public InputViewModel(UserControl owner) : this() + { + if (Program.PreviewerDetached) + { + _mainWindow = + (MainWindow)((IClassicDesktopStyleApplicationLifetime)Application.Current + .ApplicationLifetime).MainWindow; + + AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner); + + _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; + _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; + + _mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates(); + + _isLoaded = false; + + LoadDevices(); + + PlayerId = PlayerIndex.Player1; + } + } + + public InputViewModel() + { + PlayerIndexes = new ObservableCollection(); + Controllers = new ObservableCollection(); + Devices = new ObservableCollection<(DeviceType Type, string Id, string Name)>(); + ProfilesList = new AvaloniaList(); + DeviceList = new AvaloniaList(); + + ControllerImage = ProControllerResource; + + PlayerIndexes.Add(new(PlayerIndex.Player1, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer1])); + PlayerIndexes.Add(new(PlayerIndex.Player2, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer2])); + PlayerIndexes.Add(new(PlayerIndex.Player3, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer3])); + PlayerIndexes.Add(new(PlayerIndex.Player4, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer4])); + PlayerIndexes.Add(new(PlayerIndex.Player5, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer5])); + PlayerIndexes.Add(new(PlayerIndex.Player6, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer6])); + PlayerIndexes.Add(new(PlayerIndex.Player7, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer7])); + PlayerIndexes.Add(new(PlayerIndex.Player8, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer8])); + PlayerIndexes.Add(new(PlayerIndex.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsHandheld])); + } + + private void LoadConfiguration(InputConfig inputConfig = null) + { + Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerId); + + if (Config is StandardKeyboardInputConfig keyboardInputConfig) + { + ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig)); + } + + if (Config is StandardControllerInputConfig controllerInputConfig) + { + ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig)); + } + } + + public void LoadDevice() + { + if (Config == null || Config.Backend == InputBackendType.Invalid) + { + Device = 0; + } + else + { + var type = DeviceType.None; + + if (Config is StandardKeyboardInputConfig) + { + type = DeviceType.Keyboard; + } + + if (Config is StandardControllerInputConfig) + { + type = DeviceType.Controller; + } + + var item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id); + if (item != default) + { + Device = Devices.ToList().FindIndex(x => x.Id == item.Id); + } + else + { + Device = 0; + } + } + } + + private void LoadInputDriver() + { + if (_device < 0) + { + return; + } + + string id = GetCurrentGamepadId(); + var type = Devices[Device].Type; + + if (type == DeviceType.None) + { + return; + } + + if (type == DeviceType.Keyboard) + { + if (_mainWindow.InputManager.KeyboardDriver is AvaloniaKeyboardDriver) + { + // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused... + SelectedGamepad = AvaloniaKeyboardDriver.GetGamepad(id); + } + else + { + SelectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); + } + } + else + { + SelectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); + } + } + + private void HandleOnGamepadDisconnected(string id) + { + Dispatcher.UIThread.Post(() => + { + LoadDevices(); + }); + } + + private void HandleOnGamepadConnected(string id) + { + Dispatcher.UIThread.Post(() => + { + LoadDevices(); + }); + } + + private string GetCurrentGamepadId() + { + if (_device < 0) + { + return string.Empty; + } + + var device = Devices[Device]; + + if (device.Type == DeviceType.None) + { + return null; + } + + return device.Id.Split(" ")[0]; + } + + public void LoadControllers() + { + Controllers.Clear(); + + if (_playerId == PlayerIndex.Handheld) + { + Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeHandheld])); + + Controller = 0; + } + else + { + Controllers.Add(new(ControllerType.ProController, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeProController])); + Controllers.Add(new(ControllerType.JoyconPair, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConPair])); + Controllers.Add(new(ControllerType.JoyconLeft, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConLeft])); + Controllers.Add(new(ControllerType.JoyconRight, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConRight])); + + if (Config != null && Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType) != -1) + { + Controller = Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType); + } + else + { + Controller = 0; + } + } + } + + private static string GetShortGamepadName(string str) + { + const string Ellipsis = "..."; + const int MaxSize = 50; + + if (str.Length > MaxSize) + { + return $"{str.AsSpan(0, MaxSize - Ellipsis.Length)}{Ellipsis}"; + } + + return str; + } + + private static string GetShortGamepadId(string str) + { + const string Hyphen = "-"; + const int Offset = 1; + + return str[(str.IndexOf(Hyphen) + Offset)..]; + } + + public void LoadDevices() + { + lock (Devices) + { + Devices.Clear(); + DeviceList.Clear(); + Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled])); + + foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds) + { + using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); + + if (gamepad != null) + { + Devices.Add((DeviceType.Keyboard, id, $"{GetShortGamepadName(gamepad.Name)}")); + } + } + + foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds) + { + using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); + + if (gamepad != null) + { + if (Devices.Any(controller => GetShortGamepadId(controller.Id) == GetShortGamepadId(gamepad.Id))) + { + _controllerNumber++; + } + + Devices.Add((DeviceType.Controller, id, $"{GetShortGamepadName(gamepad.Name)} ({_controllerNumber})")); + } + } + + _controllerNumber = 0; + + DeviceList.AddRange(Devices.Select(x => x.Name)); + Device = Math.Min(Device, DeviceList.Count); + } + } + + private string GetProfileBasePath() + { + string path = AppDataManager.ProfilesDirPath; + var type = Devices[Device == -1 ? 0 : Device].Type; + + if (type == DeviceType.Keyboard) + { + path = Path.Combine(path, KeyboardString); + } + else if (type == DeviceType.Controller) + { + path = Path.Combine(path, ControllerString); + } + + return path; + } + + private void LoadProfiles() + { + ProfilesList.Clear(); + + string basePath = GetProfileBasePath(); + + if (!Directory.Exists(basePath)) + { + Directory.CreateDirectory(basePath); + } + + ProfilesList.Add((LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault])); + + foreach (string profile in Directory.GetFiles(basePath, "*.json", SearchOption.AllDirectories)) + { + ProfilesList.Add(Path.GetFileNameWithoutExtension(profile)); + } + + if (string.IsNullOrWhiteSpace(ProfileName)) + { + ProfileName = LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]; + } + } + + public InputConfig LoadDefaultConfiguration() + { + var activeDevice = Devices.FirstOrDefault(); + + if (Devices.Count > 0 && Device < Devices.Count && Device >= 0) + { + activeDevice = Devices[Device]; + } + + InputConfig config; + if (activeDevice.Type == DeviceType.Keyboard) + { + string id = activeDevice.Id; + + config = new StandardKeyboardInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.WindowKeyboard, + Id = id, + ControllerType = ControllerType.ProController, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = Key.Up, + DpadDown = Key.Down, + DpadLeft = Key.Left, + DpadRight = Key.Right, + ButtonMinus = Key.Minus, + ButtonL = Key.E, + ButtonZl = Key.Q, + ButtonSl = Key.Unbound, + ButtonSr = Key.Unbound, + }, + LeftJoyconStick = + new JoyconConfigKeyboardStick + { + StickUp = Key.W, + StickDown = Key.S, + StickLeft = Key.A, + StickRight = Key.D, + StickButton = Key.F, + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = Key.Z, + ButtonB = Key.X, + ButtonX = Key.C, + ButtonY = Key.V, + ButtonPlus = Key.Plus, + ButtonR = Key.U, + ButtonZr = Key.O, + ButtonSl = Key.Unbound, + ButtonSr = Key.Unbound, + }, + RightJoyconStick = new JoyconConfigKeyboardStick + { + StickUp = Key.I, + StickDown = Key.K, + StickLeft = Key.J, + StickRight = Key.L, + StickButton = Key.H, + }, + }; + } + else if (activeDevice.Type == DeviceType.Controller) + { + bool isNintendoStyle = Devices.ToList().Find(x => x.Id == activeDevice.Id).Name.Contains("Nintendo"); + + string id = activeDevice.Id.Split(" ")[0]; + + config = new StandardControllerInputConfig + { + Version = InputConfig.CurrentVersion, + Backend = InputBackendType.GamepadSDL2, + Id = id, + ControllerType = ControllerType.ProController, + DeadzoneLeft = 0.1f, + DeadzoneRight = 0.1f, + RangeLeft = 1.0f, + RangeRight = 1.0f, + TriggerThreshold = 0.5f, + LeftJoycon = new LeftJoyconCommonConfig + { + DpadUp = ConfigGamepadInputId.DpadUp, + DpadDown = ConfigGamepadInputId.DpadDown, + DpadLeft = ConfigGamepadInputId.DpadLeft, + DpadRight = ConfigGamepadInputId.DpadRight, + ButtonMinus = ConfigGamepadInputId.Minus, + ButtonL = ConfigGamepadInputId.LeftShoulder, + ButtonZl = ConfigGamepadInputId.LeftTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + LeftJoyconStick = new JoyconConfigControllerStick + { + Joystick = ConfigStickInputId.Left, + StickButton = ConfigGamepadInputId.LeftStick, + InvertStickX = false, + InvertStickY = false, + }, + RightJoycon = new RightJoyconCommonConfig + { + ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B, + ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A, + ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y, + ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X, + ButtonPlus = ConfigGamepadInputId.Plus, + ButtonR = ConfigGamepadInputId.RightShoulder, + ButtonZr = ConfigGamepadInputId.RightTrigger, + ButtonSl = ConfigGamepadInputId.Unbound, + ButtonSr = ConfigGamepadInputId.Unbound, + }, + RightJoyconStick = new JoyconConfigControllerStick + { + Joystick = ConfigStickInputId.Right, + StickButton = ConfigGamepadInputId.RightStick, + InvertStickX = false, + InvertStickY = false, + }, + Motion = new StandardMotionConfigController + { + MotionBackend = MotionInputBackendType.GamepadDriver, + EnableMotion = true, + Sensitivity = 100, + GyroDeadzone = 1, + }, + Rumble = new RumbleConfigController + { + StrongRumble = 1f, + WeakRumble = 1f, + EnableRumble = false, + }, + }; + } + else + { + config = new InputConfig(); + } + + config.PlayerIndex = _playerId; + + return config; + } + + public async void LoadProfile() + { + if (Device == 0) + { + return; + } + + InputConfig config = null; + + if (string.IsNullOrWhiteSpace(ProfileName)) + { + return; + } + + if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]) + { + config = LoadDefaultConfiguration(); + } + else + { + string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); + + if (!File.Exists(path)) + { + var index = ProfilesList.IndexOf(ProfileName); + if (index != -1) + { + ProfilesList.RemoveAt(index); + } + return; + } + + try + { + config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig); + } + catch (JsonException) { } + catch (InvalidOperationException) + { + Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system."); + + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogProfileInvalidProfileErrorMessage, ProfileName)); + + return; + } + } + + if (config != null) + { + _isLoaded = false; + + LoadConfiguration(config); + + LoadDevice(); + + _isLoaded = true; + + NotifyChanges(); + } + } + + public async void SaveProfile() + { + if (Device == 0) + { + return; + } + + if (ConfigViewModel == null) + { + return; + } + + if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileDefaultProfileOverwriteErrorMessage]); + + return; + } + else + { + bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1; + + if (validFileName) + { + string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); + + InputConfig config = null; + + if (IsKeyboard) + { + config = (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig(); + } + else if (IsController) + { + config = (ConfigViewModel as ControllerInputViewModel).Config.GetConfig(); + } + + config.ControllerType = Controllers[_controller].Type; + + string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig); + + await File.WriteAllTextAsync(path, jsonString); + + LoadProfiles(); + } + else + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]); + } + } + } + + public async void RemoveProfile() + { + if (Device == 0 || ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault] || ProfilesList.IndexOf(ProfileName) == -1) + { + return; + } + + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogProfileDeleteProfileTitle], + LocaleManager.Instance[LocaleKeys.DialogProfileDeleteProfileMessage], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); + + if (File.Exists(path)) + { + File.Delete(path); + } + + LoadProfiles(); + } + } + + public void Save() + { + IsModified = false; + + List newConfig = new(); + + newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value); + + newConfig.Remove(newConfig.Find(x => x == null)); + + if (Device == 0) + { + newConfig.Remove(newConfig.Find(x => x.PlayerIndex == this.PlayerId)); + } + else + { + var device = Devices[Device]; + + if (device.Type == DeviceType.Keyboard) + { + var inputConfig = (ConfigViewModel as KeyboardInputViewModel).Config; + inputConfig.Id = device.Id; + } + else + { + var inputConfig = (ConfigViewModel as ControllerInputViewModel).Config; + inputConfig.Id = device.Id.Split(" ")[0]; + } + + var config = !IsController + ? (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig() + : (ConfigViewModel as ControllerInputViewModel).Config.GetConfig(); + config.ControllerType = Controllers[_controller].Type; + config.PlayerIndex = _playerId; + + int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId); + if (i == -1) + { + newConfig.Add(config); + } + else + { + newConfig[i] = config; + } + } + + _mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + + // Atomically replace and signal input change. + // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. + ConfigurationState.Instance.Hid.InputConfig.Value = newConfig; + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + + public void NotifyChange(string property) + { + OnPropertyChanged(property); + } + + public void NotifyChanges() + { + OnPropertyChanged(nameof(ConfigViewModel)); + OnPropertyChanged(nameof(IsController)); + OnPropertyChanged(nameof(ShowSettings)); + OnPropertyChanged(nameof(IsKeyboard)); + OnPropertyChanged(nameof(IsRight)); + OnPropertyChanged(nameof(IsLeft)); + NotifyChangesEvent?.Invoke(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; + _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; + + _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); + + SelectedGamepad?.Dispose(); + + AvaloniaKeyboardDriver.Dispose(); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs new file mode 100644 index 00000000..0b530eb0 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/Input/KeyboardInputViewModel.cs @@ -0,0 +1,73 @@ +using Avalonia.Svg.Skia; +using Ryujinx.Ava.UI.Models.Input; + +namespace Ryujinx.Ava.UI.ViewModels.Input +{ + public class KeyboardInputViewModel : BaseModel + { + private KeyboardInputConfig _config; + public KeyboardInputConfig Config + { + get => _config; + set + { + _config = value; + OnPropertyChanged(); + } + } + + private bool _isLeft; + public bool IsLeft + { + get => _isLeft; + set + { + _isLeft = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(HasSides)); + } + } + + private bool _isRight; + public bool IsRight + { + get => _isRight; + set + { + _isRight = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(HasSides)); + } + } + + public bool HasSides => IsLeft ^ IsRight; + + private SvgImage _image; + public SvgImage Image + { + get => _image; + set + { + _image = value; + OnPropertyChanged(); + } + } + + public readonly InputViewModel ParentModel; + + public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config) + { + ParentModel = model; + model.NotifyChangesEvent += OnParentModelChanged; + OnParentModelChanged(); + Config = config; + } + + public void OnParentModelChanged() + { + IsLeft = ParentModel.IsLeft; + IsRight = ParentModel.IsRight; + Image = ParentModel.Image; + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/Input/MotionInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/MotionInputViewModel.cs new file mode 100644 index 00000000..c9ed8f2d --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/Input/MotionInputViewModel.cs @@ -0,0 +1,93 @@ +namespace Ryujinx.Ava.UI.ViewModels.Input +{ + public class MotionInputViewModel : BaseModel + { + private int _slot; + public int Slot + { + get => _slot; + set + { + _slot = value; + OnPropertyChanged(); + } + } + + private int _altSlot; + public int AltSlot + { + get => _altSlot; + set + { + _altSlot = value; + OnPropertyChanged(); + } + } + + private string _dsuServerHost; + public string DsuServerHost + { + get => _dsuServerHost; + set + { + _dsuServerHost = value; + OnPropertyChanged(); + } + } + + private int _dsuServerPort; + public int DsuServerPort + { + get => _dsuServerPort; + set + { + _dsuServerPort = value; + OnPropertyChanged(); + } + } + + private bool _mirrorInput; + public bool MirrorInput + { + get => _mirrorInput; + set + { + _mirrorInput = value; + OnPropertyChanged(); + } + } + + private int _sensitivity; + public int Sensitivity + { + get => _sensitivity; + set + { + _sensitivity = value; + OnPropertyChanged(); + } + } + + private double _gryoDeadzone; + public double GyroDeadzone + { + get => _gryoDeadzone; + set + { + _gryoDeadzone = value; + OnPropertyChanged(); + } + } + + private bool _enableCemuHookMotion; + public bool EnableCemuHookMotion + { + get => _enableCemuHookMotion; + set + { + _enableCemuHookMotion = value; + OnPropertyChanged(); + } + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/Input/RumbleInputViewModel.cs b/src/Ryujinx/UI/ViewModels/Input/RumbleInputViewModel.cs new file mode 100644 index 00000000..8ad33cf4 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/Input/RumbleInputViewModel.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.Ava.UI.ViewModels.Input +{ + public class RumbleInputViewModel : BaseModel + { + private float _strongRumble; + public float StrongRumble + { + get => _strongRumble; + set + { + _strongRumble = value; + OnPropertyChanged(); + } + } + + private float _weakRumble; + public float WeakRumble + { + get => _weakRumble; + set + { + _weakRumble = value; + OnPropertyChanged(); + } + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs new file mode 100644 index 00000000..bd9f165b --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,1752 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Input; +using Avalonia.Media; +using Avalonia.Platform.Storage; +using Avalonia.Threading; +using DynamicData; +using DynamicData.Binding; +using LibHac.Common; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.Models.Generic; +using Ryujinx.Ava.UI.Renderer; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.UI; +using Ryujinx.Input.HLE; +using Ryujinx.Modules; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using SkiaSharp; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Key = Ryujinx.Input.Key; +using MissingKeyException = LibHac.Common.Keys.MissingKeyException; +using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class MainWindowViewModel : BaseModel + { + private const int HotKeyPressDelayMs = 500; + + private ObservableCollection _applications; + private string _aspectStatusText; + + private string _loadHeading; + private string _cacheLoadStatus; + private string _searchText; + private Timer _searchTimer; + private string _dockedStatusText; + private string _fifoStatusText; + private string _gameStatusText; + private string _volumeStatusText; + private string _gpuStatusText; + private bool _isAmiiboRequested; + private bool _isGameRunning; + private bool _isFullScreen; + private int _progressMaximum; + private int _progressValue; + private long _lastFullscreenToggle = Environment.TickCount64; + private bool _showLoadProgress; + private bool _showMenuAndStatusBar = true; + private bool _showStatusSeparator; + private Brush _progressBarForegroundColor; + private Brush _progressBarBackgroundColor; + private Brush _vsyncColor; + private byte[] _selectedIcon; + private bool _isAppletMenuActive; + private int _statusBarProgressMaximum; + private int _statusBarProgressValue; + private bool _isPaused; + private bool _showContent = true; + private bool _isLoadingIndeterminate = true; + private bool _showAll; + private string _lastScannedAmiiboId; + private bool _statusBarVisible; + private ReadOnlyObservableCollection _appsObservableList; + + private string _showUiKey = "F4"; + private string _pauseKey = "F5"; + private string _screenshotKey = "F8"; + private float _volume; + private float _volumeBeforeMute; + private string _backendText; + + private bool _canUpdate = true; + private Cursor _cursor; + private string _title; + private ApplicationData _currentApplicationData; + private readonly AutoResetEvent _rendererWaitEvent; + private WindowState _windowState; + private double _windowWidth; + private double _windowHeight; + + private bool _isActive; + private bool _isSubMenuOpen; + + public ApplicationData ListSelectedApplication; + public ApplicationData GridSelectedApplication; + + internal AppHost AppHost { get; set; } + + public MainWindowViewModel() + { + Applications = new ObservableCollection(); + + Applications.ToObservableChangeSet() + .Filter(Filter) + .Sort(GetComparer()) + .Bind(out _appsObservableList).AsObservableList(); + + _rendererWaitEvent = new AutoResetEvent(false); + + if (Program.PreviewerDetached) + { + LoadConfigurableHotKeys(); + + Volume = ConfigurationState.Instance.System.AudioVolume; + } + } + + public void Initialize( + ContentManager contentManager, + IStorageProvider storageProvider, + ApplicationLibrary applicationLibrary, + VirtualFileSystem virtualFileSystem, + AccountManager accountManager, + InputManager inputManager, + UserChannelPersistence userChannelPersistence, + LibHacHorizonManager libHacHorizonManager, + IHostUIHandler uiHandler, + Action showLoading, + Action switchToGameControl, + Action setMainContent, + TopLevel topLevel) + { + ContentManager = contentManager; + StorageProvider = storageProvider; + ApplicationLibrary = applicationLibrary; + VirtualFileSystem = virtualFileSystem; + AccountManager = accountManager; + InputManager = inputManager; + UserChannelPersistence = userChannelPersistence; + LibHacHorizonManager = libHacHorizonManager; + UiHandler = uiHandler; + + ShowLoading = showLoading; + SwitchToGameControl = switchToGameControl; + SetMainContent = setMainContent; + TopLevel = topLevel; + } + + #region Properties + + public string SearchText + { + get => _searchText; + set + { + _searchText = value; + + _searchTimer?.Dispose(); + + _searchTimer = new Timer(TimerCallback, null, 1000, 0); + } + } + + private void TimerCallback(object obj) + { + RefreshView(); + + _searchTimer.Dispose(); + _searchTimer = null; + } + + public bool CanUpdate + { + get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate(false); + set + { + _canUpdate = value; + OnPropertyChanged(); + } + } + + public Cursor Cursor + { + get => _cursor; + set + { + _cursor = value; + OnPropertyChanged(); + } + } + + public ReadOnlyObservableCollection AppsObservableList + { + get => _appsObservableList; + set + { + _appsObservableList = value; + + OnPropertyChanged(); + } + } + + public bool IsPaused + { + get => _isPaused; + set + { + _isPaused = value; + + OnPropertyChanged(); + } + } + + public long LastFullscreenToggle + { + get => _lastFullscreenToggle; + set + { + _lastFullscreenToggle = value; + + OnPropertyChanged(); + } + } + + public bool StatusBarVisible + { + get => _statusBarVisible && EnableNonGameRunningControls; + set + { + _statusBarVisible = value; + + OnPropertyChanged(); + } + } + + public bool EnableNonGameRunningControls => !IsGameRunning; + + public bool ShowFirmwareStatus => !ShowLoadProgress; + + public bool IsGameRunning + { + get => _isGameRunning; + set + { + _isGameRunning = value; + + if (!value) + { + ShowMenuAndStatusBar = false; + } + + OnPropertyChanged(); + OnPropertyChanged(nameof(EnableNonGameRunningControls)); + OnPropertyChanged(nameof(IsAppletMenuActive)); + OnPropertyChanged(nameof(StatusBarVisible)); + OnPropertyChanged(nameof(ShowFirmwareStatus)); + } + } + + public bool IsAmiiboRequested + { + get => _isAmiiboRequested && _isGameRunning; + set + { + _isAmiiboRequested = value; + + OnPropertyChanged(); + } + } + + public bool ShowLoadProgress + { + get => _showLoadProgress; + set + { + _showLoadProgress = value; + + OnPropertyChanged(); + OnPropertyChanged(nameof(ShowFirmwareStatus)); + } + } + + public string GameStatusText + { + get => _gameStatusText; + set + { + _gameStatusText = value; + + OnPropertyChanged(); + } + } + + public bool IsFullScreen + { + get => _isFullScreen; + set + { + _isFullScreen = value; + + OnPropertyChanged(); + } + } + + public bool IsSubMenuOpen + { + get => _isSubMenuOpen; + set + { + _isSubMenuOpen = value; + + OnPropertyChanged(); + } + } + + public bool ShowAll + { + get => _showAll; + set + { + _showAll = value; + + OnPropertyChanged(); + } + } + + public string LastScannedAmiiboId + { + get => _lastScannedAmiiboId; + set + { + _lastScannedAmiiboId = value; + + OnPropertyChanged(); + } + } + + public ApplicationData SelectedApplication + { + get + { + return Glyph switch + { + Glyph.List => ListSelectedApplication, + Glyph.Grid => GridSelectedApplication, + _ => null, + }; + } + } + + public bool OpenUserSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0; + + public bool OpenDeviceSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0; + + public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0; + + public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild; + + public string LoadHeading + { + get => _loadHeading; + set + { + _loadHeading = value; + + OnPropertyChanged(); + } + } + + public string CacheLoadStatus + { + get => _cacheLoadStatus; + set + { + _cacheLoadStatus = value; + + OnPropertyChanged(); + } + } + + public Brush ProgressBarBackgroundColor + { + get => _progressBarBackgroundColor; + set + { + _progressBarBackgroundColor = value; + + OnPropertyChanged(); + } + } + + public Brush ProgressBarForegroundColor + { + get => _progressBarForegroundColor; + set + { + _progressBarForegroundColor = value; + + OnPropertyChanged(); + } + } + + public Brush VsyncColor + { + get => _vsyncColor; + set + { + _vsyncColor = value; + + OnPropertyChanged(); + } + } + + public byte[] SelectedIcon + { + get => _selectedIcon; + set + { + _selectedIcon = value; + + OnPropertyChanged(); + } + } + + public int ProgressMaximum + { + get => _progressMaximum; + set + { + _progressMaximum = value; + + OnPropertyChanged(); + } + } + + public int ProgressValue + { + get => _progressValue; + set + { + _progressValue = value; + + OnPropertyChanged(); + } + } + + public int StatusBarProgressMaximum + { + get => _statusBarProgressMaximum; + set + { + _statusBarProgressMaximum = value; + + OnPropertyChanged(); + } + } + + public int StatusBarProgressValue + { + get => _statusBarProgressValue; + set + { + _statusBarProgressValue = value; + + OnPropertyChanged(); + } + } + + public string FifoStatusText + { + get => _fifoStatusText; + set + { + _fifoStatusText = value; + + OnPropertyChanged(); + } + } + + public string GpuNameText + { + get => _gpuStatusText; + set + { + _gpuStatusText = value; + + OnPropertyChanged(); + } + } + + public string BackendText + { + get => _backendText; + set + { + _backendText = value; + + OnPropertyChanged(); + } + } + + public string DockedStatusText + { + get => _dockedStatusText; + set + { + _dockedStatusText = value; + + OnPropertyChanged(); + } + } + + public string AspectRatioStatusText + { + get => _aspectStatusText; + set + { + _aspectStatusText = value; + + OnPropertyChanged(); + } + } + + public string VolumeStatusText + { + get => _volumeStatusText; + set + { + _volumeStatusText = value; + + OnPropertyChanged(); + } + } + + public bool VolumeMuted => _volume == 0; + + public float Volume + { + get => _volume; + set + { + _volume = value; + + if (_isGameRunning) + { + AppHost.Device.SetVolume(_volume); + } + + OnPropertyChanged(nameof(VolumeStatusText)); + OnPropertyChanged(nameof(VolumeMuted)); + OnPropertyChanged(); + } + } + + public float VolumeBeforeMute + { + get => _volumeBeforeMute; + set + { + _volumeBeforeMute = value; + + OnPropertyChanged(); + } + } + + public bool ShowStatusSeparator + { + get => _showStatusSeparator; + set + { + _showStatusSeparator = value; + + OnPropertyChanged(); + } + } + + public bool ShowMenuAndStatusBar + { + get => _showMenuAndStatusBar; + set + { + _showMenuAndStatusBar = value; + + OnPropertyChanged(); + } + } + + public bool IsLoadingIndeterminate + { + get => _isLoadingIndeterminate; + set + { + _isLoadingIndeterminate = value; + + OnPropertyChanged(); + } + } + + public bool IsActive + { + get => _isActive; + set + { + _isActive = value; + + OnPropertyChanged(); + } + } + + + public bool ShowContent + { + get => _showContent; + set + { + _showContent = value; + + OnPropertyChanged(); + } + } + + public bool IsAppletMenuActive + { + get => _isAppletMenuActive && EnableNonGameRunningControls; + set + { + _isAppletMenuActive = value; + + OnPropertyChanged(); + } + } + + public WindowState WindowState + { + get => _windowState; + internal set + { + _windowState = value; + + OnPropertyChanged(); + } + } + + public double WindowWidth + { + get => _windowWidth; + set + { + _windowWidth = value; + + OnPropertyChanged(); + } + } + + public double WindowHeight + { + get => _windowHeight; + set + { + _windowHeight = value; + + OnPropertyChanged(); + } + } + + public bool IsGrid => Glyph == Glyph.Grid; + public bool IsList => Glyph == Glyph.List; + + internal void Sort(bool isAscending) + { + IsAscending = isAscending; + + RefreshView(); + } + + internal void Sort(ApplicationSort sort) + { + SortMode = sort; + + RefreshView(); + } + + public bool StartGamesInFullscreen + { + get => ConfigurationState.Instance.UI.StartFullscreen; + set + { + ConfigurationState.Instance.UI.StartFullscreen.Value = value; + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + + OnPropertyChanged(); + } + } + + public bool ShowConsole + { + get => ConfigurationState.Instance.UI.ShowConsole; + set + { + ConfigurationState.Instance.UI.ShowConsole.Value = value; + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + + OnPropertyChanged(); + } + } + + public string Title + { + get => _title; + set + { + _title = value; + + OnPropertyChanged(); + } + } + + public bool ShowConsoleVisible + { + get => ConsoleHelper.SetConsoleWindowStateSupported; + } + + public bool ManageFileTypesVisible + { + get => FileAssociationHelper.IsTypeAssociationSupported; + } + + public ObservableCollection Applications + { + get => _applications; + set + { + _applications = value; + + OnPropertyChanged(); + } + } + + public Glyph Glyph + { + get => (Glyph)ConfigurationState.Instance.UI.GameListViewMode.Value; + set + { + ConfigurationState.Instance.UI.GameListViewMode.Value = (int)value; + + OnPropertyChanged(); + OnPropertyChanged(nameof(IsGrid)); + OnPropertyChanged(nameof(IsList)); + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + + public bool ShowNames + { + get => ConfigurationState.Instance.UI.ShowNames && ConfigurationState.Instance.UI.GridSize > 1; set + { + ConfigurationState.Instance.UI.ShowNames.Value = value; + + OnPropertyChanged(); + OnPropertyChanged(nameof(GridSizeScale)); + OnPropertyChanged(nameof(GridItemSelectorSize)); + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + + internal ApplicationSort SortMode + { + get => (ApplicationSort)ConfigurationState.Instance.UI.ApplicationSort.Value; + private set + { + ConfigurationState.Instance.UI.ApplicationSort.Value = (int)value; + + OnPropertyChanged(); + OnPropertyChanged(nameof(SortName)); + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + + public int ListItemSelectorSize + { + get + { + return ConfigurationState.Instance.UI.GridSize.Value switch + { + 1 => 78, + 2 => 100, + 3 => 120, + 4 => 140, + _ => 16, + }; + } + } + + public int GridItemSelectorSize + { + get + { + return ConfigurationState.Instance.UI.GridSize.Value switch + { + 1 => 120, + 2 => ShowNames ? 210 : 150, + 3 => ShowNames ? 240 : 180, + 4 => ShowNames ? 280 : 220, + _ => 16, + }; + } + } + + public int GridSizeScale + { + get => ConfigurationState.Instance.UI.GridSize; + set + { + ConfigurationState.Instance.UI.GridSize.Value = value; + + if (value < 2) + { + ShowNames = false; + } + + OnPropertyChanged(); + OnPropertyChanged(nameof(IsGridSmall)); + OnPropertyChanged(nameof(IsGridMedium)); + OnPropertyChanged(nameof(IsGridLarge)); + OnPropertyChanged(nameof(IsGridHuge)); + OnPropertyChanged(nameof(ListItemSelectorSize)); + OnPropertyChanged(nameof(GridItemSelectorSize)); + OnPropertyChanged(nameof(ShowNames)); + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + + public string SortName + { + get + { + return SortMode switch + { + ApplicationSort.Title => LocaleManager.Instance[LocaleKeys.GameListHeaderApplication], + ApplicationSort.Developer => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper], + ApplicationSort.LastPlayed => LocaleManager.Instance[LocaleKeys.GameListHeaderLastPlayed], + ApplicationSort.TotalTimePlayed => LocaleManager.Instance[LocaleKeys.GameListHeaderTimePlayed], + ApplicationSort.FileType => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension], + ApplicationSort.FileSize => LocaleManager.Instance[LocaleKeys.GameListHeaderFileSize], + ApplicationSort.Path => LocaleManager.Instance[LocaleKeys.GameListHeaderPath], + ApplicationSort.Favorite => LocaleManager.Instance[LocaleKeys.CommonFavorite], + _ => string.Empty, + }; + } + } + + public bool IsAscending + { + get => ConfigurationState.Instance.UI.IsAscendingOrder; + private set + { + ConfigurationState.Instance.UI.IsAscendingOrder.Value = value; + + OnPropertyChanged(); + OnPropertyChanged(nameof(SortMode)); + OnPropertyChanged(nameof(SortName)); + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + + public KeyGesture ShowUiKey + { + get => KeyGesture.Parse(_showUiKey); + set + { + _showUiKey = value.ToString(); + + OnPropertyChanged(); + } + } + + public KeyGesture ScreenshotKey + { + get => KeyGesture.Parse(_screenshotKey); + set + { + _screenshotKey = value.ToString(); + + OnPropertyChanged(); + } + } + + public KeyGesture PauseKey + { + get => KeyGesture.Parse(_pauseKey); set + { + _pauseKey = value.ToString(); + + OnPropertyChanged(); + } + } + + public ContentManager ContentManager { get; private set; } + public IStorageProvider StorageProvider { get; private set; } + public ApplicationLibrary ApplicationLibrary { get; private set; } + public VirtualFileSystem VirtualFileSystem { get; private set; } + public AccountManager AccountManager { get; private set; } + public InputManager InputManager { get; private set; } + public UserChannelPersistence UserChannelPersistence { get; private set; } + public Action ShowLoading { get; private set; } + public Action SwitchToGameControl { get; private set; } + public Action SetMainContent { get; private set; } + public TopLevel TopLevel { get; private set; } + public RendererHost RendererHostControl { get; private set; } + public bool IsClosing { get; set; } + public LibHacHorizonManager LibHacHorizonManager { get; internal set; } + public IHostUIHandler UiHandler { get; internal set; } + public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite; + public bool IsSortedByTitle => SortMode == ApplicationSort.Title; + public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer; + public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed; + public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed; + public bool IsSortedByType => SortMode == ApplicationSort.FileType; + public bool IsSortedBySize => SortMode == ApplicationSort.FileSize; + public bool IsSortedByPath => SortMode == ApplicationSort.Path; + public bool IsGridSmall => ConfigurationState.Instance.UI.GridSize == 1; + public bool IsGridMedium => ConfigurationState.Instance.UI.GridSize == 2; + public bool IsGridLarge => ConfigurationState.Instance.UI.GridSize == 3; + public bool IsGridHuge => ConfigurationState.Instance.UI.GridSize == 4; + + #endregion + + #region PrivateMethods + + private IComparer GetComparer() + { + return SortMode switch + { +#pragma warning disable IDE0055 // Disable formatting + ApplicationSort.Title => IsAscending ? SortExpressionComparer.Ascending(app => app.Name) + : SortExpressionComparer.Descending(app => app.Name), + ApplicationSort.Developer => IsAscending ? SortExpressionComparer.Ascending(app => app.Developer) + : SortExpressionComparer.Descending(app => app.Developer), + ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending), + ApplicationSort.TotalTimePlayed => new TimePlayedSortComparer(IsAscending), + ApplicationSort.FileType => IsAscending ? SortExpressionComparer.Ascending(app => app.FileExtension) + : SortExpressionComparer.Descending(app => app.FileExtension), + ApplicationSort.FileSize => IsAscending ? SortExpressionComparer.Ascending(app => app.FileSize) + : SortExpressionComparer.Descending(app => app.FileSize), + ApplicationSort.Path => IsAscending ? SortExpressionComparer.Ascending(app => app.Path) + : SortExpressionComparer.Descending(app => app.Path), + ApplicationSort.Favorite => IsAscending ? SortExpressionComparer.Ascending(app => new AppListFavoriteComparable(app)) + : SortExpressionComparer.Descending(app => new AppListFavoriteComparable(app)), + _ => null, +#pragma warning restore IDE0055 + }; + } + + public void RefreshView() + { + RefreshGrid(); + } + + private void RefreshGrid() + { + Applications.ToObservableChangeSet() + .Filter(Filter) + .Sort(GetComparer()) + .Bind(out _appsObservableList).AsObservableList(); + + OnPropertyChanged(nameof(AppsObservableList)); + } + + private bool Filter(object arg) + { + if (arg is ApplicationData app) + { + if (string.IsNullOrWhiteSpace(_searchText)) + { + return true; + } + + CompareInfo compareInfo = CultureInfo.CurrentCulture.CompareInfo; + + return compareInfo.IndexOf(app.Name, _searchText, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0; + } + + return false; + } + + private async Task HandleFirmwareInstallation(string filename) + { + try + { + SystemVersion firmwareVersion = ContentManager.VerifyFirmwarePackage(filename); + + if (firmwareVersion == null) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage, filename)); + + return; + } + + string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallTitle, firmwareVersion.VersionString); + string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage, firmwareVersion.VersionString); + + SystemVersion currentVersion = ContentManager.GetCurrentFirmwareVersion(); + if (currentVersion != null) + { + dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSubMessage, currentVersion.VersionString); + } + + dialogMessage += LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallConfirmMessage]; + + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + dialogTitle, + dialogMessage, + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + UpdateWaitWindow waitingDialog = new(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]); + + if (result == UserResult.Yes) + { + Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}"); + + Thread thread = new(() => + { + Dispatcher.UIThread.InvokeAsync(delegate + { + waitingDialog.Show(); + }); + + try + { + ContentManager.InstallFirmware(filename); + + Dispatcher.UIThread.InvokeAsync(async delegate + { + waitingDialog.Close(); + + string message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage, firmwareVersion.VersionString); + + await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance[LocaleKeys.InputDialogOk], "", LocaleManager.Instance[LocaleKeys.RyujinxInfo]); + + Logger.Info?.Print(LogClass.Application, message); + + // Purge Applet Cache. + + DirectoryInfo miiEditorCacheFolder = new(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache")); + + if (miiEditorCacheFolder.Exists) + { + miiEditorCacheFolder.Delete(true); + } + }); + } + catch (Exception ex) + { + Dispatcher.UIThread.InvokeAsync(async () => + { + waitingDialog.Close(); + + await ContentDialogHelper.CreateErrorDialog(ex.Message); + }); + } + finally + { + RefreshFirmwareStatus(); + } + }) + { + Name = "GUI.FirmwareInstallerThread", + }; + + thread.Start(); + } + } + catch (MissingKeyException ex) + { + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + Logger.Error?.Print(LogClass.Application, ex.ToString()); + + await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys); + } + } + catch (Exception ex) + { + await ContentDialogHelper.CreateErrorDialog(ex.Message); + } + } + + private void ProgressHandler(T state, int current, int total) where T : Enum + { + Dispatcher.UIThread.Post((() => + { + ProgressMaximum = total; + ProgressValue = current; + + switch (state) + { + case LoadState ptcState: + CacheLoadStatus = $"{current} / {total}"; + switch (ptcState) + { + case LoadState.Unloaded: + case LoadState.Loading: + LoadHeading = LocaleManager.Instance[LocaleKeys.CompilingPPTC]; + IsLoadingIndeterminate = false; + break; + case LoadState.Loaded: + LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name); + IsLoadingIndeterminate = true; + CacheLoadStatus = ""; + break; + } + break; + case ShaderCacheLoadingState shaderCacheState: + CacheLoadStatus = $"{current} / {total}"; + switch (shaderCacheState) + { + case ShaderCacheLoadingState.Start: + case ShaderCacheLoadingState.Loading: + LoadHeading = LocaleManager.Instance[LocaleKeys.CompilingShaders]; + IsLoadingIndeterminate = false; + break; + case ShaderCacheLoadingState.Packaging: + LoadHeading = LocaleManager.Instance[LocaleKeys.PackagingShaders]; + IsLoadingIndeterminate = false; + break; + case ShaderCacheLoadingState.Loaded: + LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name); + IsLoadingIndeterminate = true; + CacheLoadStatus = ""; + break; + } + break; + default: + throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}"); + } + })); + } + + private void PrepareLoadScreen() + { + using MemoryStream stream = new(SelectedIcon); + using var gameIconBmp = SKBitmap.Decode(stream); + + var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp); + + const float ColorMultiple = 0.5f; + + Color progressFgColor = Color.FromRgb(dominantColor.Red, dominantColor.Green, dominantColor.Blue); + Color progressBgColor = Color.FromRgb( + (byte)(dominantColor.Red * ColorMultiple), + (byte)(dominantColor.Green * ColorMultiple), + (byte)(dominantColor.Blue * ColorMultiple)); + + ProgressBarForegroundColor = new SolidColorBrush(progressFgColor); + ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor); + } + + private void InitializeGame() + { + RendererHostControl.WindowCreated += RendererHost_Created; + + AppHost.StatusInitEvent += Init_StatusBar; + AppHost.StatusUpdatedEvent += Update_StatusBar; + AppHost.AppExit += AppHost_AppExit; + + _rendererWaitEvent.WaitOne(); + + AppHost?.Start(); + + AppHost?.DisposeContext(); + } + + private async Task HandleRelaunch() + { + if (UserChannelPersistence.PreviousIndex != -1 && UserChannelPersistence.ShouldRestart) + { + UserChannelPersistence.ShouldRestart = false; + + await LoadApplication(_currentApplicationData); + } + else + { + // Otherwise, clear state. + UserChannelPersistence = new UserChannelPersistence(); + _currentApplicationData = null; + } + } + + private void Init_StatusBar(object sender, StatusInitEventArgs args) + { + if (ShowMenuAndStatusBar && !ShowLoadProgress) + { + Dispatcher.UIThread.InvokeAsync(() => + { + GpuNameText = args.GpuName; + BackendText = args.GpuBackend; + }); + } + } + + private void Update_StatusBar(object sender, StatusUpdatedEventArgs args) + { + if (ShowMenuAndStatusBar && !ShowLoadProgress) + { + Dispatcher.UIThread.InvokeAsync(() => + { + Application.Current.Styles.TryGetResource(args.VSyncEnabled + ? "VsyncEnabled" + : "VsyncDisabled", + Application.Current.ActualThemeVariant, + out object color); + + if (color is not null) + { + VsyncColor = new SolidColorBrush((Color)color); + } + + DockedStatusText = args.DockedMode; + AspectRatioStatusText = args.AspectRatio; + GameStatusText = args.GameStatus; + VolumeStatusText = args.VolumeStatus; + FifoStatusText = args.FifoStatus; + + ShowStatusSeparator = true; + }); + } + } + + private void RendererHost_Created(object sender, EventArgs e) + { + ShowLoading(false); + + _rendererWaitEvent.Set(); + } + + #endregion + + #region PublicMethods + + public void SetUiProgressHandlers(Switch emulationContext) + { + if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null) + { + emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler; + emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler; + } + + emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; + emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler; + } + + public void LoadConfigurableHotKeys() + { + if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI, out var showUiKey)) + { + ShowUiKey = new KeyGesture(showUiKey); + } + + if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey)) + { + ScreenshotKey = new KeyGesture(screenshotKey); + } + + if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey)) + { + PauseKey = new KeyGesture(pauseKey); + } + } + + public void TakeScreenshot() + { + AppHost.ScreenshotRequested = true; + } + + public void HideUi() + { + ShowMenuAndStatusBar = false; + } + + public void ToggleStartGamesInFullscreen() + { + StartGamesInFullscreen = !StartGamesInFullscreen; + } + + public void ToggleShowConsole() + { + ShowConsole = !ShowConsole; + } + + public void SetListMode() + { + Glyph = Glyph.List; + } + + public void SetGridMode() + { + Glyph = Glyph.Grid; + } + + public void SetAspectRatio(AspectRatio aspectRatio) + { + ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio; + } + + public async Task InstallFirmwareFromFile() + { + var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + AllowMultiple = false, + FileTypeFilter = new List + { + new(LocaleManager.Instance[LocaleKeys.FileDialogAllTypes]) + { + Patterns = new[] { "*.xci", "*.zip" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci", "public.zip-archive" }, + MimeTypes = new[] { "application/x-nx-xci", "application/zip" }, + }, + new("XCI") + { + Patterns = new[] { "*.xci" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" }, + MimeTypes = new[] { "application/x-nx-xci" }, + }, + new("ZIP") + { + Patterns = new[] { "*.zip" }, + AppleUniformTypeIdentifiers = new[] { "public.zip-archive" }, + MimeTypes = new[] { "application/zip" }, + }, + }, + }); + + if (result.Count > 0) + { + await HandleFirmwareInstallation(result[0].Path.LocalPath); + } + } + + public async Task InstallFirmwareFromFolder() + { + var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + AllowMultiple = false, + }); + + if (result.Count > 0) + { + await HandleFirmwareInstallation(result[0].Path.LocalPath); + } + } + + public void OpenRyujinxFolder() + { + OpenHelper.OpenFolder(AppDataManager.BaseDirPath); + } + + public void OpenLogsFolder() + { + string logPath = AppDataManager.GetOrCreateLogsDir(); + if (!string.IsNullOrEmpty(logPath)) + { + OpenHelper.OpenFolder(logPath); + } + } + + public void ToggleDockMode() + { + if (IsGameRunning) + { + ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value; + } + } + + public async Task ExitCurrentState() + { + if (WindowState == WindowState.FullScreen) + { + ToggleFullscreen(); + } + else if (IsGameRunning) + { + await Task.Delay(100); + + AppHost?.ShowExitPrompt(); + } + } + + public static void ChangeLanguage(object languageCode) + { + LocaleManager.Instance.LoadLanguage((string)languageCode); + + if (Program.PreviewerDetached) + { + ConfigurationState.Instance.UI.LanguageCode.Value = (string)languageCode; + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + } + + public async Task ManageProfiles() + { + await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient); + } + + public void SimulateWakeUpMessage() + { + AppHost.Device.System.SimulateWakeUpMessage(); + } + + public async Task OpenFile() + { + var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle], + AllowMultiple = false, + FileTypeFilter = new List + { + new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) + { + Patterns = new[] { "*.nsp", "*.xci", "*.nca", "*.nro", "*.nso" }, + AppleUniformTypeIdentifiers = new[] + { + "com.ryujinx.nsp", + "com.ryujinx.xci", + "com.ryujinx.nca", + "com.ryujinx.nro", + "com.ryujinx.nso", + }, + MimeTypes = new[] + { + "application/x-nx-nsp", + "application/x-nx-xci", + "application/x-nx-nca", + "application/x-nx-nro", + "application/x-nx-nso", + }, + }, + new("NSP") + { + Patterns = new[] { "*.nsp" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, + MimeTypes = new[] { "application/x-nx-nsp" }, + }, + new("XCI") + { + Patterns = new[] { "*.xci" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" }, + MimeTypes = new[] { "application/x-nx-xci" }, + }, + new("NCA") + { + Patterns = new[] { "*.nca" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nca" }, + MimeTypes = new[] { "application/x-nx-nca" }, + }, + new("NRO") + { + Patterns = new[] { "*.nro" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nro" }, + MimeTypes = new[] { "application/x-nx-nro" }, + }, + new("NSO") + { + Patterns = new[] { "*.nso" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nso" }, + MimeTypes = new[] { "application/x-nx-nso" }, + }, + }, + }); + + if (result.Count > 0) + { + if (ApplicationLibrary.TryGetApplicationsFromFile(result[0].Path.LocalPath, + out List applications)) + { + await LoadApplication(applications[0]); + } + else + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.MenuBarFileOpenFromFileError]); + } + } + } + + public async Task OpenFolder() + { + var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle], + AllowMultiple = false, + }); + + if (result.Count > 0) + { + ApplicationData applicationData = new() + { + Name = Path.GetFileNameWithoutExtension(result[0].Path.LocalPath), + Path = result[0].Path.LocalPath, + }; + + await LoadApplication(applicationData); + } + } + + public async Task LoadApplication(ApplicationData application, bool startFullscreen = false) + { + if (AppHost != null) + { + await ContentDialogHelper.CreateInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedMessage], + LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedSubMessage], + LocaleManager.Instance[LocaleKeys.InputDialogOk], + "", + LocaleManager.Instance[LocaleKeys.RyujinxInfo]); + + return; + } + +#if RELEASE + await PerformanceCheck(); +#endif + + Logger.RestartTime(); + + SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id); + + PrepareLoadScreen(); + + RendererHostControl = new RendererHost(); + + AppHost = new AppHost( + RendererHostControl, + InputManager, + application.Path, + application.Id, + VirtualFileSystem, + ContentManager, + AccountManager, + UserChannelPersistence, + this, + TopLevel); + + if (!await AppHost.LoadGuestApplication()) + { + AppHost.DisposeContext(); + AppHost = null; + + return; + } + + CanUpdate = false; + + LoadHeading = application.Name; + + if (string.IsNullOrWhiteSpace(application.Name)) + { + LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name); + application.Name = AppHost.Device.Processes.ActiveApplication.Name; + } + + SwitchToRenderer(startFullscreen); + + _currentApplicationData = application; + + Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" }; + gameThread.Start(); + } + + public void SwitchToRenderer(bool startFullscreen) + { + Dispatcher.UIThread.Post(() => + { + SwitchToGameControl(startFullscreen); + + SetMainContent(RendererHostControl); + + RendererHostControl.Focus(); + }); + } + + public static void UpdateGameMetadata(string titleId) + { + ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => + { + appMetadata.UpdatePostGame(); + }); + } + + public void RefreshFirmwareStatus() + { + SystemVersion version = null; + try + { + version = ContentManager.GetCurrentFirmwareVersion(); + } + catch (Exception) { } + + bool hasApplet = false; + + if (version != null) + { + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, version.VersionString); + + hasApplet = version.Major > 3; + } + else + { + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, "0.0"); + } + + IsAppletMenuActive = hasApplet; + } + + public void AppHost_AppExit(object sender, EventArgs e) + { + if (IsClosing) + { + return; + } + + IsGameRunning = false; + + Dispatcher.UIThread.InvokeAsync(async () => + { + ShowMenuAndStatusBar = true; + ShowContent = true; + ShowLoadProgress = false; + IsLoadingIndeterminate = false; + CanUpdate = true; + Cursor = Cursor.Default; + + SetMainContent(null); + + AppHost = null; + + await HandleRelaunch(); + }); + + RendererHostControl.WindowCreated -= RendererHost_Created; + RendererHostControl = null; + + SelectedIcon = null; + + Dispatcher.UIThread.InvokeAsync(() => + { + Title = $"Ryujinx {Program.Version}"; + }); + } + + public void ToggleFullscreen() + { + if (Environment.TickCount64 - LastFullscreenToggle < HotKeyPressDelayMs) + { + return; + } + + LastFullscreenToggle = Environment.TickCount64; + + if (WindowState == WindowState.FullScreen) + { + WindowState = WindowState.Normal; + + if (IsGameRunning) + { + ShowMenuAndStatusBar = true; + } + } + else + { + WindowState = WindowState.FullScreen; + + if (IsGameRunning) + { + ShowMenuAndStatusBar = false; + } + } + + IsFullScreen = WindowState == WindowState.FullScreen; + } + + public static void SaveConfig() + { + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + } + + public static async Task PerformanceCheck() + { + if (ConfigurationState.Instance.Logger.EnableTrace.Value) + { + string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledMessage]; + string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledConfirmMessage]; + + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + mainMessage, + secondaryMessage, + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + ConfigurationState.Instance.Logger.EnableTrace.Value = false; + + SaveConfig(); + } + } + + if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value)) + { + string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledMessage]; + string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledConfirmMessage]; + + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + mainMessage, + secondaryMessage, + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = ""; + + SaveConfig(); + } + } + } + #endregion + } +} diff --git a/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs new file mode 100644 index 00000000..8321bf89 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs @@ -0,0 +1,336 @@ +using Avalonia; +using Avalonia.Collections; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform.Storage; +using Avalonia.Threading; +using DynamicData; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.HOS; +using System; +using System.IO; +using System.Linq; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class ModManagerViewModel : BaseModel + { + private readonly string _modJsonPath; + + private AvaloniaList _mods = new(); + private AvaloniaList _views = new(); + private AvaloniaList _selectedMods = new(); + + private string _search; + private readonly ulong _applicationId; + private readonly IStorageProvider _storageProvider; + + private static readonly ModMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public AvaloniaList Mods + { + get => _mods; + set + { + _mods = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(ModCount)); + Sort(); + } + } + + public AvaloniaList Views + { + get => _views; + set + { + _views = value; + OnPropertyChanged(); + } + } + + public AvaloniaList SelectedMods + { + get => _selectedMods; + set + { + _selectedMods = value; + OnPropertyChanged(); + } + } + + public string Search + { + get => _search; + set + { + _search = value; + OnPropertyChanged(); + Sort(); + } + } + + public string ModCount + { + get => string.Format(LocaleManager.Instance[LocaleKeys.ModWindowHeading], Mods.Count); + } + + public ModManagerViewModel(ulong applicationId) + { + _applicationId = applicationId; + + _modJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationId.ToString("x16"), "mods.json"); + + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + _storageProvider = desktop.MainWindow.StorageProvider; + } + + LoadMods(applicationId); + } + + private void LoadMods(ulong applicationId) + { + Mods.Clear(); + SelectedMods.Clear(); + + string[] modsBasePaths = [ModLoader.GetSdModsBasePath(), ModLoader.GetModsBasePath()]; + + foreach (var path in modsBasePaths) + { + var inSd = path == ModLoader.GetSdModsBasePath(); + var modCache = new ModLoader.ModCache(); + + ModLoader.QueryContentsDir(modCache, new DirectoryInfo(Path.Combine(path, "contents")), applicationId); + + foreach (var mod in modCache.RomfsDirs) + { + var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd); + if (Mods.All(x => x.Path != mod.Path.Parent.FullName)) + { + Mods.Add(modModel); + } + } + + foreach (var mod in modCache.RomfsContainers) + { + Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd)); + } + + foreach (var mod in modCache.ExefsDirs) + { + var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd); + if (Mods.All(x => x.Path != mod.Path.Parent.FullName)) + { + Mods.Add(modModel); + } + } + + foreach (var mod in modCache.ExefsContainers) + { + Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd)); + } + } + + Sort(); + } + + public void Sort() + { + Mods.AsObservableChangeSet() + .Filter(Filter) + .Bind(out var view).AsObservableList(); + + _views.Clear(); + _views.AddRange(view); + + SelectedMods = new(Views.Where(x => x.Enabled)); + + OnPropertyChanged(nameof(ModCount)); + OnPropertyChanged(nameof(Views)); + OnPropertyChanged(nameof(SelectedMods)); + } + + private bool Filter(object arg) + { + if (arg is ModModel content) + { + return string.IsNullOrWhiteSpace(_search) || content.Name.ToLower().Contains(_search.ToLower()); + } + + return false; + } + + public void Save() + { + ModMetadata modData = new(); + + foreach (ModModel mod in Mods) + { + modData.Mods.Add(new Mod + { + Name = mod.Name, + Path = mod.Path, + Enabled = SelectedMods.Contains(mod), + }); + } + + JsonHelper.SerializeToFile(_modJsonPath, modData, _serializerContext.ModMetadata); + } + + public void Delete(ModModel model) + { + var isSubdir = true; + var pathToDelete = model.Path; + var basePath = model.InSd ? ModLoader.GetSdModsBasePath() : ModLoader.GetModsBasePath(); + var modsDir = ModLoader.GetApplicationDir(basePath, _applicationId.ToString("x16")); + + if (new DirectoryInfo(model.Path).Parent?.FullName == modsDir) + { + isSubdir = false; + } + + if (isSubdir) + { + var parentDir = String.Empty; + + foreach (var dir in Directory.GetDirectories(modsDir, "*", SearchOption.TopDirectoryOnly)) + { + if (Directory.GetDirectories(dir, "*", SearchOption.AllDirectories).Contains(model.Path)) + { + parentDir = dir; + break; + } + } + + if (parentDir == String.Empty) + { + Dispatcher.UIThread.Post(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogModDeleteNoParentMessage, + model.Path)); + }); + return; + } + } + + Logger.Info?.Print(LogClass.Application, $"Deleting mod at \"{pathToDelete}\""); + Directory.Delete(pathToDelete, true); + + Mods.Remove(model); + OnPropertyChanged(nameof(ModCount)); + Sort(); + } + + private void AddMod(DirectoryInfo directory) + { + string[] directories; + + try + { + directories = Directory.GetDirectories(directory.ToString(), "*", SearchOption.AllDirectories); + } + catch (Exception exception) + { + Dispatcher.UIThread.Post(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogLoadFileErrorMessage, + exception.ToString(), + directory)); + }); + return; + } + + var destinationDir = ModLoader.GetApplicationDir(ModLoader.GetSdModsBasePath(), _applicationId.ToString("x16")); + + // TODO: More robust checking for valid mod folders + var isDirectoryValid = true; + + if (directories.Length == 0) + { + isDirectoryValid = false; + } + + if (!isDirectoryValid) + { + Dispatcher.UIThread.Post(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogModInvalidMessage]); + }); + return; + } + + foreach (var dir in directories) + { + string dirToCreate = dir.Replace(directory.Parent.ToString(), destinationDir); + + // Mod already exists + if (Directory.Exists(dirToCreate)) + { + Dispatcher.UIThread.Post(async () => + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue( + LocaleKeys.DialogLoadFileErrorMessage, + LocaleManager.Instance[LocaleKeys.DialogModAlreadyExistsMessage], + dirToCreate)); + }); + + return; + } + + Directory.CreateDirectory(dirToCreate); + } + + var files = Directory.GetFiles(directory.ToString(), "*", SearchOption.AllDirectories); + + foreach (var file in files) + { + File.Copy(file, file.Replace(directory.Parent.ToString(), destinationDir), true); + } + + LoadMods(_applicationId); + } + + public async void Add() + { + var result = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + Title = LocaleManager.Instance[LocaleKeys.SelectModDialogTitle], + AllowMultiple = true, + }); + + foreach (var folder in result) + { + AddMod(new DirectoryInfo(folder.Path.LocalPath)); + } + } + + public void DeleteAll() + { + foreach (var mod in Mods) + { + Delete(mod); + } + + Mods.Clear(); + OnPropertyChanged(nameof(ModCount)); + Sort(); + } + + public void EnableAll() + { + SelectedMods = new(Mods); + } + + public void DisableAll() + { + SelectedMods.Clear(); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs new file mode 100644 index 00000000..70e5fa5c --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -0,0 +1,615 @@ +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Threading; +using LibHac.Tools.FsSystem; +using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SoundIo; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models.Input; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Multiplayer; +using Ryujinx.Common.GraphicsDriver; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Vulkan; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Time.TimeZone; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Configuration.System; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Net.NetworkInformation; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using TimeZone = Ryujinx.Ava.UI.Models.TimeZone; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class SettingsViewModel : BaseModel + { + private readonly VirtualFileSystem _virtualFileSystem; + private readonly ContentManager _contentManager; + private TimeZoneContentManager _timeZoneContentManager; + + private readonly List _validTzRegions; + + private readonly Dictionary _networkInterfaces; + + private float _customResolutionScale; + private int _resolutionScale; + private int _graphicsBackendMultithreadingIndex; + private float _volume; + private bool _isVulkanAvailable = true; + private bool _directoryChanged; + private readonly List _gpuIds = new(); + private int _graphicsBackendIndex; + private int _scalingFilter; + private int _scalingFilterLevel; + + public event Action CloseWindow; + public event Action SaveSettingsEvent; + private int _networkInterfaceIndex; + private int _multiplayerModeIndex; + + public int ResolutionScale + { + get => _resolutionScale; + set + { + _resolutionScale = value; + + OnPropertyChanged(nameof(CustomResolutionScale)); + OnPropertyChanged(nameof(IsCustomResolutionScaleActive)); + } + } + + public int GraphicsBackendMultithreadingIndex + { + get => _graphicsBackendMultithreadingIndex; + set + { + _graphicsBackendMultithreadingIndex = value; + + if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value) + { + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage], + "", + "", + LocaleManager.Instance[LocaleKeys.InputDialogOk], + LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle]) + ); + } + + OnPropertyChanged(); + } + } + + public float CustomResolutionScale + { + get => _customResolutionScale; + set + { + _customResolutionScale = MathF.Round(value, 1); + + OnPropertyChanged(); + } + } + + public bool IsVulkanAvailable + { + get => _isVulkanAvailable; + set + { + _isVulkanAvailable = value; + + OnPropertyChanged(); + } + } + + public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS(); + + public bool IsHypervisorAvailable => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64; + + public bool DirectoryChanged + { + get => _directoryChanged; + set + { + _directoryChanged = value; + + OnPropertyChanged(); + } + } + + public bool IsMacOS => OperatingSystem.IsMacOS(); + + public bool EnableDiscordIntegration { get; set; } + public bool CheckUpdatesOnStart { get; set; } + public bool ShowConfirmExit { get; set; } + public bool RememberWindowState { get; set; } + public int HideCursor { get; set; } + public bool EnableDockedMode { get; set; } + public bool EnableKeyboard { get; set; } + public bool EnableMouse { get; set; } + public bool EnableVsync { get; set; } + public bool EnablePptc { get; set; } + public bool EnableInternetAccess { get; set; } + public bool EnableFsIntegrityChecks { get; set; } + public bool IgnoreMissingServices { get; set; } + public bool ExpandDramSize { get; set; } + public bool EnableShaderCache { get; set; } + public bool EnableTextureRecompression { get; set; } + public bool EnableMacroHLE { get; set; } + public bool EnableColorSpacePassthrough { get; set; } + public bool ColorSpacePassthroughAvailable => IsMacOS; + public bool EnableFileLog { get; set; } + public bool EnableStub { get; set; } + public bool EnableInfo { get; set; } + public bool EnableWarn { get; set; } + public bool EnableError { get; set; } + public bool EnableTrace { get; set; } + public bool EnableGuest { get; set; } + public bool EnableFsAccessLog { get; set; } + public bool EnableDebug { get; set; } + public bool IsOpenAlEnabled { get; set; } + public bool IsSoundIoEnabled { get; set; } + public bool IsSDL2Enabled { get; set; } + public bool IsCustomResolutionScaleActive => _resolutionScale == 4; + public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr; + + public bool IsVulkanSelected => GraphicsBackendIndex == 0; + public bool UseHypervisor { get; set; } + + public string TimeZone { get; set; } + public string ShaderDumpPath { get; set; } + + public int Language { get; set; } + public int Region { get; set; } + public int FsGlobalAccessLogMode { get; set; } + public int AudioBackend { get; set; } + public int MaxAnisotropy { get; set; } + public int AspectRatio { get; set; } + public int AntiAliasingEffect { get; set; } + public string ScalingFilterLevelText => ScalingFilterLevel.ToString("0"); + public int ScalingFilterLevel + { + get => _scalingFilterLevel; + set + { + _scalingFilterLevel = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(ScalingFilterLevelText)); + } + } + public int OpenglDebugLevel { get; set; } + public int MemoryMode { get; set; } + public int BaseStyleIndex { get; set; } + public int GraphicsBackendIndex + { + get => _graphicsBackendIndex; + set + { + _graphicsBackendIndex = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(IsVulkanSelected)); + } + } + public int ScalingFilter + { + get => _scalingFilter; + set + { + _scalingFilter = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(IsScalingFilterActive)); + } + } + + public int PreferredGpuIndex { get; set; } + + public float Volume + { + get => _volume; + set + { + _volume = value; + + ConfigurationState.Instance.System.AudioVolume.Value = _volume / 100; + + OnPropertyChanged(); + } + } + + public DateTimeOffset CurrentDate { get; set; } + public TimeSpan CurrentTime { get; set; } + + internal AvaloniaList TimeZones { get; set; } + public AvaloniaList GameDirectories { get; set; } + public ObservableCollection AvailableGpus { get; set; } + + public AvaloniaList NetworkInterfaceList + { + get => new(_networkInterfaces.Keys); + } + + public HotkeyConfig KeyboardHotkey { get; set; } + + public int NetworkInterfaceIndex + { + get => _networkInterfaceIndex; + set + { + _networkInterfaceIndex = value != -1 ? value : 0; + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[_networkInterfaceIndex]]; + } + } + + public int MultiplayerModeIndex + { + get => _multiplayerModeIndex; + set + { + _multiplayerModeIndex = value; + ConfigurationState.Instance.Multiplayer.Mode.Value = (MultiplayerMode)_multiplayerModeIndex; + } + } + + public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() + { + _virtualFileSystem = virtualFileSystem; + _contentManager = contentManager; + if (Program.PreviewerDetached) + { + Task.Run(LoadTimeZones); + } + } + + public SettingsViewModel() + { + GameDirectories = new AvaloniaList(); + TimeZones = new AvaloniaList(); + AvailableGpus = new ObservableCollection(); + _validTzRegions = new List(); + _networkInterfaces = new Dictionary(); + + Task.Run(CheckSoundBackends); + Task.Run(PopulateNetworkInterfaces); + + if (Program.PreviewerDetached) + { + Task.Run(LoadAvailableGpus); + LoadCurrentConfiguration(); + } + } + + public async Task CheckSoundBackends() + { + IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported; + IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported; + IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported; + + await Dispatcher.UIThread.InvokeAsync(() => + { + OnPropertyChanged(nameof(IsOpenAlEnabled)); + OnPropertyChanged(nameof(IsSoundIoEnabled)); + OnPropertyChanged(nameof(IsSDL2Enabled)); + }); + } + + private async Task LoadAvailableGpus() + { + AvailableGpus.Clear(); + + var devices = VulkanRenderer.GetPhysicalDevices(); + + if (devices.Length == 0) + { + IsVulkanAvailable = false; + GraphicsBackendIndex = 1; + } + else + { + foreach (var device in devices) + { + await Dispatcher.UIThread.InvokeAsync(() => + { + _gpuIds.Add(device.Id); + + AvailableGpus.Add(new ComboBoxItem { Content = $"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}" }); + }); + } + } + + // GPU configuration needs to be loaded during the async method or it will always return 0. + PreferredGpuIndex = _gpuIds.Contains(ConfigurationState.Instance.Graphics.PreferredGpu) ? + _gpuIds.IndexOf(ConfigurationState.Instance.Graphics.PreferredGpu) : 0; + + Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(PreferredGpuIndex))); + } + + public async Task LoadTimeZones() + { + _timeZoneContentManager = new TimeZoneContentManager(); + + _timeZoneContentManager.InitializeInstance(_virtualFileSystem, _contentManager, IntegrityCheckLevel.None); + + foreach ((int offset, string location, string abbr) in _timeZoneContentManager.ParseTzOffsets()) + { + int hours = Math.DivRem(offset, 3600, out int seconds); + int minutes = Math.Abs(seconds) / 60; + + string abbr2 = abbr.StartsWith('+') || abbr.StartsWith('-') ? string.Empty : abbr; + + await Dispatcher.UIThread.InvokeAsync(() => + { + TimeZones.Add(new TimeZone($"UTC{hours:+0#;-0#;+00}:{minutes:D2}", location, abbr2)); + + _validTzRegions.Add(location); + }); + } + + Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(TimeZone))); + } + + private async Task PopulateNetworkInterfaces() + { + _networkInterfaces.Clear(); + _networkInterfaces.Add(LocaleManager.Instance[LocaleKeys.NetworkInterfaceDefault], "0"); + + foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces()) + { + await Dispatcher.UIThread.InvokeAsync(() => + { + _networkInterfaces.Add(networkInterface.Name, networkInterface.Id); + }); + } + + // Network interface index needs to be loaded during the async method or it will always return 0. + NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value); + + Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(NetworkInterfaceIndex))); + } + + public void ValidateAndSetTimeZone(string location) + { + if (_validTzRegions.Contains(location)) + { + TimeZone = location; + } + } + + public void LoadCurrentConfiguration() + { + ConfigurationState config = ConfigurationState.Instance; + + // User Interface + EnableDiscordIntegration = config.EnableDiscordIntegration; + CheckUpdatesOnStart = config.CheckUpdatesOnStart; + ShowConfirmExit = config.ShowConfirmExit; + RememberWindowState = config.RememberWindowState; + HideCursor = (int)config.HideCursor.Value; + + GameDirectories.Clear(); + GameDirectories.AddRange(config.UI.GameDirs.Value); + + BaseStyleIndex = config.UI.BaseStyle.Value switch + { + "Auto" => 0, + "Light" => 1, + "Dark" => 2, + _ => 0 + }; + + // Input + EnableDockedMode = config.System.EnableDockedMode; + EnableKeyboard = config.Hid.EnableKeyboard; + EnableMouse = config.Hid.EnableMouse; + + // Keyboard Hotkeys + KeyboardHotkey = new HotkeyConfig(config.Hid.Hotkeys.Value); + + // System + Region = (int)config.System.Region.Value; + Language = (int)config.System.Language.Value; + TimeZone = config.System.TimeZone; + + DateTime currentHostDateTime = DateTime.Now; + TimeSpan systemDateTimeOffset = TimeSpan.FromSeconds(config.System.SystemTimeOffset); + DateTime currentDateTime = currentHostDateTime.Add(systemDateTimeOffset); + CurrentDate = currentDateTime.Date; + CurrentTime = currentDateTime.TimeOfDay; + + EnableVsync = config.Graphics.EnableVsync; + EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks; + ExpandDramSize = config.System.ExpandRam; + IgnoreMissingServices = config.System.IgnoreMissingServices; + + // CPU + EnablePptc = config.System.EnablePtc; + MemoryMode = (int)config.System.MemoryManagerMode.Value; + UseHypervisor = config.System.UseHypervisor; + + // Graphics + GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value; + // Physical devices are queried asynchronously hence the prefered index config value is loaded in LoadAvailableGpus(). + EnableShaderCache = config.Graphics.EnableShaderCache; + EnableTextureRecompression = config.Graphics.EnableTextureRecompression; + EnableMacroHLE = config.Graphics.EnableMacroHLE; + EnableColorSpacePassthrough = config.Graphics.EnableColorSpacePassthrough; + ResolutionScale = config.Graphics.ResScale == -1 ? 4 : config.Graphics.ResScale - 1; + CustomResolutionScale = config.Graphics.ResScaleCustom; + MaxAnisotropy = config.Graphics.MaxAnisotropy == -1 ? 0 : (int)(MathF.Log2(config.Graphics.MaxAnisotropy)); + AspectRatio = (int)config.Graphics.AspectRatio.Value; + GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value; + ShaderDumpPath = config.Graphics.ShadersDumpPath; + AntiAliasingEffect = (int)config.Graphics.AntiAliasing.Value; + ScalingFilter = (int)config.Graphics.ScalingFilter.Value; + ScalingFilterLevel = config.Graphics.ScalingFilterLevel.Value; + + // Audio + AudioBackend = (int)config.System.AudioBackend.Value; + Volume = config.System.AudioVolume * 100; + + // Network + EnableInternetAccess = config.System.EnableInternetAccess; + // LAN interface index is loaded asynchronously in PopulateNetworkInterfaces() + + // Logging + EnableFileLog = config.Logger.EnableFileLog; + EnableStub = config.Logger.EnableStub; + EnableInfo = config.Logger.EnableInfo; + EnableWarn = config.Logger.EnableWarn; + EnableError = config.Logger.EnableError; + EnableTrace = config.Logger.EnableTrace; + EnableGuest = config.Logger.EnableGuest; + EnableDebug = config.Logger.EnableDebug; + EnableFsAccessLog = config.Logger.EnableFsAccessLog; + FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode; + OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value; + + MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value; + } + + public void SaveSettings() + { + ConfigurationState config = ConfigurationState.Instance; + + // User Interface + config.EnableDiscordIntegration.Value = EnableDiscordIntegration; + config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart; + config.ShowConfirmExit.Value = ShowConfirmExit; + config.RememberWindowState.Value = RememberWindowState; + config.HideCursor.Value = (HideCursorMode)HideCursor; + + if (_directoryChanged) + { + List gameDirs = new(GameDirectories); + config.UI.GameDirs.Value = gameDirs; + } + + config.UI.BaseStyle.Value = BaseStyleIndex switch + { + 0 => "Auto", + 1 => "Light", + 2 => "Dark", + _ => "Auto" + }; + + // Input + config.System.EnableDockedMode.Value = EnableDockedMode; + config.Hid.EnableKeyboard.Value = EnableKeyboard; + config.Hid.EnableMouse.Value = EnableMouse; + + // Keyboard Hotkeys + config.Hid.Hotkeys.Value = KeyboardHotkey.GetConfig(); + + // System + config.System.Region.Value = (Region)Region; + config.System.Language.Value = (Language)Language; + + if (_validTzRegions.Contains(TimeZone)) + { + config.System.TimeZone.Value = TimeZone; + } + + config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds()); + config.Graphics.EnableVsync.Value = EnableVsync; + config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks; + config.System.ExpandRam.Value = ExpandDramSize; + config.System.IgnoreMissingServices.Value = IgnoreMissingServices; + + // CPU + config.System.EnablePtc.Value = EnablePptc; + config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode; + config.System.UseHypervisor.Value = UseHypervisor; + + // Graphics + config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex; + config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex); + config.Graphics.EnableShaderCache.Value = EnableShaderCache; + config.Graphics.EnableTextureRecompression.Value = EnableTextureRecompression; + config.Graphics.EnableMacroHLE.Value = EnableMacroHLE; + config.Graphics.EnableColorSpacePassthrough.Value = EnableColorSpacePassthrough; + config.Graphics.ResScale.Value = ResolutionScale == 4 ? -1 : ResolutionScale + 1; + config.Graphics.ResScaleCustom.Value = CustomResolutionScale; + config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy); + config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio; + config.Graphics.AntiAliasing.Value = (AntiAliasing)AntiAliasingEffect; + config.Graphics.ScalingFilter.Value = (ScalingFilter)ScalingFilter; + config.Graphics.ScalingFilterLevel.Value = ScalingFilterLevel; + + if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex) + { + DriverUtilities.ToggleOGLThreading(GraphicsBackendMultithreadingIndex == (int)BackendThreading.Off); + } + + config.Graphics.BackendThreading.Value = (BackendThreading)GraphicsBackendMultithreadingIndex; + config.Graphics.ShadersDumpPath.Value = ShaderDumpPath; + + // Audio + AudioBackend audioBackend = (AudioBackend)AudioBackend; + if (audioBackend != config.System.AudioBackend.Value) + { + config.System.AudioBackend.Value = audioBackend; + + Logger.Info?.Print(LogClass.Application, $"AudioBackend toggled to: {audioBackend}"); + } + + config.System.AudioVolume.Value = Volume / 100; + + // Network + config.System.EnableInternetAccess.Value = EnableInternetAccess; + + // Logging + config.Logger.EnableFileLog.Value = EnableFileLog; + config.Logger.EnableStub.Value = EnableStub; + config.Logger.EnableInfo.Value = EnableInfo; + config.Logger.EnableWarn.Value = EnableWarn; + config.Logger.EnableError.Value = EnableError; + config.Logger.EnableTrace.Value = EnableTrace; + config.Logger.EnableGuest.Value = EnableGuest; + config.Logger.EnableDebug.Value = EnableDebug; + config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog; + config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode; + config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel; + + config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]]; + config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex; + + config.ToFileFormat().SaveConfig(Program.ConfigurationPath); + + MainWindow.UpdateGraphicsConfig(); + + SaveSettingsEvent?.Invoke(); + + _directoryChanged = false; + } + + private static void RevertIfNotSaved() + { + Program.ReloadConfig(); + } + + public void ApplyButton() + { + SaveSettings(); + } + + public void OkButton() + { + SaveSettings(); + CloseWindow?.Invoke(); + } + + public void CancelButton() + { + RevertIfNotSaved(); + CloseWindow?.Invoke(); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs b/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs new file mode 100644 index 00000000..e9b39dfe --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs @@ -0,0 +1,265 @@ +using Avalonia.Collections; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform.Storage; +using Avalonia.Threading; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Ncm; +using LibHac.Ns; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.Loaders.Processes.Extensions; +using Ryujinx.HLE.Utilities; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common.Configuration; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Application = Avalonia.Application; +using ContentType = LibHac.Ncm.ContentType; +using Path = System.IO.Path; +using SpanHelpers = LibHac.Common.SpanHelpers; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class TitleUpdateViewModel : BaseModel + { + public TitleUpdateMetadata TitleUpdateWindowData; + public readonly string TitleUpdateJsonPath; + private VirtualFileSystem VirtualFileSystem { get; } + private ApplicationData ApplicationData { get; } + + private AvaloniaList _titleUpdates = new(); + private AvaloniaList _views = new(); + private object _selectedUpdate; + + private static readonly TitleUpdateMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + + public AvaloniaList TitleUpdates + { + get => _titleUpdates; + set + { + _titleUpdates = value; + OnPropertyChanged(); + } + } + + public AvaloniaList Views + { + get => _views; + set + { + _views = value; + OnPropertyChanged(); + } + } + + public object SelectedUpdate + { + get => _selectedUpdate; + set + { + _selectedUpdate = value; + OnPropertyChanged(); + } + } + + public IStorageProvider StorageProvider; + + public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ApplicationData applicationData) + { + VirtualFileSystem = virtualFileSystem; + + ApplicationData = applicationData; + + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + StorageProvider = desktop.MainWindow.StorageProvider; + } + + TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, ApplicationData.IdBaseString, "updates.json"); + + try + { + TitleUpdateWindowData = JsonHelper.DeserializeFromFile(TitleUpdateJsonPath, _serializerContext.TitleUpdateMetadata); + } + catch + { + Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {ApplicationData.IdBaseString} at {TitleUpdateJsonPath}"); + + TitleUpdateWindowData = new TitleUpdateMetadata + { + Selected = "", + Paths = new List(), + }; + + Save(); + } + + LoadUpdates(); + } + + private void LoadUpdates() + { + // Try to load updates from PFS first + AddUpdate(ApplicationData.Path, true); + + foreach (string path in TitleUpdateWindowData.Paths) + { + AddUpdate(path); + } + + TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == TitleUpdateWindowData.Selected, null); + + SelectedUpdate = selected; + + // NOTE: Save the list again to remove leftovers. + Save(); + SortUpdates(); + } + + public void SortUpdates() + { + var sortedUpdates = TitleUpdates.OrderByDescending(update => update.Version); + + Views.Clear(); + Views.Add(new BaseModel()); + Views.AddRange(sortedUpdates); + + if (SelectedUpdate == null) + { + SelectedUpdate = Views[0]; + } + else if (!TitleUpdates.Contains(SelectedUpdate)) + { + if (Views.Count > 1) + { + SelectedUpdate = Views[1]; + } + else + { + SelectedUpdate = Views[0]; + } + } + } + + private void AddUpdate(string path, bool ignoreNotFound = false, bool selected = false) + { + if (!File.Exists(path) || TitleUpdates.Any(x => x.Path == path)) + { + return; + } + + IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks + ? IntegrityCheckLevel.ErrorOnInvalid + : IntegrityCheckLevel.None; + + try + { + using IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(path, VirtualFileSystem); + + Dictionary updates = pfs.GetContentData(ContentMetaType.Patch, VirtualFileSystem, checkLevel); + + Nca patchNca = null; + Nca controlNca = null; + + if (updates.TryGetValue(ApplicationData.Id, out ContentMetaData content)) + { + patchNca = content.GetNcaByType(VirtualFileSystem.KeySet, ContentType.Program); + controlNca = content.GetNcaByType(VirtualFileSystem.KeySet, ContentType.Control); + } + + if (controlNca != null && patchNca != null) + { + ApplicationControlProperty controlData = new(); + + using UniqueRef nacpFile = new(); + + controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); + + var displayVersion = controlData.DisplayVersionString.ToString(); + var update = new TitleUpdateModel(content.Version.Version, displayVersion, path); + + TitleUpdates.Add(update); + + if (selected) + { + Dispatcher.UIThread.InvokeAsync(() => SelectedUpdate = update); + } + } + else + { + if (!ignoreNotFound) + { + Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage])); + } + } + } + catch (Exception ex) + { + Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadFileErrorMessage, ex.Message, path))); + } + } + + public void RemoveUpdate(TitleUpdateModel update) + { + TitleUpdates.Remove(update); + + SortUpdates(); + } + + public async Task Add() + { + var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + AllowMultiple = true, + FileTypeFilter = new List + { + new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) + { + Patterns = new[] { "*.nsp" }, + AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, + MimeTypes = new[] { "application/x-nx-nsp" }, + }, + }, + }); + + foreach (var file in result) + { + AddUpdate(file.Path.LocalPath, selected: true); + } + + SortUpdates(); + } + + public void Save() + { + TitleUpdateWindowData.Paths.Clear(); + TitleUpdateWindowData.Selected = ""; + + foreach (TitleUpdateModel update in TitleUpdates) + { + TitleUpdateWindowData.Paths.Add(update.Path); + + if (update == SelectedUpdate) + { + TitleUpdateWindowData.Selected = update.Path; + } + } + + JsonHelper.SerializeToFile(TitleUpdateJsonPath, TitleUpdateWindowData, _serializerContext.TitleUpdateMetadata); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs new file mode 100644 index 00000000..b07bf78b --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs @@ -0,0 +1,225 @@ +using Avalonia.Media; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Ava.UI.Models; +using Ryujinx.HLE.FileSystem; +using SkiaSharp; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using Color = Avalonia.Media.Color; +using Image = SkiaSharp.SKImage; + +namespace Ryujinx.Ava.UI.ViewModels +{ + internal class UserFirmwareAvatarSelectorViewModel : BaseModel + { + private static readonly Dictionary _avatarStore = new(); + + private ObservableCollection _images; + private Color _backgroundColor = Colors.White; + + private int _selectedIndex; + + public UserFirmwareAvatarSelectorViewModel() + { + _images = new ObservableCollection(); + + LoadImagesFromStore(); + } + + public Color BackgroundColor + { + get => _backgroundColor; + set + { + _backgroundColor = value; + OnPropertyChanged(); + ChangeImageBackground(); + } + } + + public ObservableCollection Images + { + get => _images; + set + { + _images = value; + OnPropertyChanged(); + } + } + + public int SelectedIndex + { + get => _selectedIndex; + set + { + _selectedIndex = value; + + if (_selectedIndex == -1) + { + SelectedImage = null; + } + else + { + SelectedImage = _images[_selectedIndex].Data; + } + + OnPropertyChanged(); + } + } + + public byte[] SelectedImage { get; private set; } + + private void LoadImagesFromStore() + { + Images.Clear(); + + foreach (var image in _avatarStore) + { + Images.Add(new ProfileImageModel(image.Key, image.Value)); + } + } + + private void ChangeImageBackground() + { + foreach (var image in Images) + { + image.BackgroundColor = new SolidColorBrush(BackgroundColor); + } + } + + public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem) + { + if (_avatarStore.Count > 0) + { + return; + } + + string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem, NcaContentType.Data); + string avatarPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath); + + if (!string.IsNullOrWhiteSpace(avatarPath)) + { + using IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open); + + Nca nca = new(virtualFileSystem.KeySet, ncaFileStream); + IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); + + foreach (DirectoryEntryEx item in romfs.EnumerateEntries()) + { + // TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy. + if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") && item.FullPath.Contains("szs")) + { + using var file = new UniqueRef(); + + romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + using MemoryStream stream = new(); + using MemoryStream streamPng = new(); + + file.Get.AsStream().CopyTo(stream); + + stream.Position = 0; + + Image avatarImage = Image.FromPixelCopy(new SKImageInfo(256, 256, SKColorType.Rgba8888, SKAlphaType.Premul), DecompressYaz0(stream)); + + using (SKData data = avatarImage.Encode(SKEncodedImageFormat.Png, 100)) + { + data.SaveTo(streamPng); + } + + _avatarStore.Add(item.FullPath, streamPng.ToArray()); + } + } + } + } + + private static byte[] DecompressYaz0(Stream stream) + { + using BinaryReader reader = new(stream); + + reader.ReadInt32(); // Magic + + uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32()); + + reader.ReadInt64(); // Padding + + byte[] input = new byte[stream.Length - stream.Position]; + stream.ReadExactly(input, 0, input.Length); + + uint inputOffset = 0; + + byte[] output = new byte[decodedLength]; + uint outputOffset = 0; + + ushort mask = 0; + byte header = 0; + + while (outputOffset < decodedLength) + { + if ((mask >>= 1) == 0) + { + header = input[inputOffset++]; + mask = 0x80; + } + + if ((header & mask) != 0) + { + if (outputOffset == output.Length) + { + break; + } + + output[outputOffset++] = input[inputOffset++]; + } + else + { + byte byte1 = input[inputOffset++]; + byte byte2 = input[inputOffset++]; + + uint dist = (uint)((byte1 & 0xF) << 8) | byte2; + uint position = outputOffset - (dist + 1); + + uint length = (uint)byte1 >> 4; + if (length == 0) + { + length = (uint)input[inputOffset++] + 0x12; + } + else + { + length += 2; + } + + uint gap = outputOffset - position; + uint nonOverlappingLength = length; + + if (nonOverlappingLength > gap) + { + nonOverlappingLength = gap; + } + + Buffer.BlockCopy(output, (int)position, output, (int)outputOffset, (int)nonOverlappingLength); + outputOffset += nonOverlappingLength; + position += nonOverlappingLength; + length -= nonOverlappingLength; + + while (length-- > 0) + { + output[outputOffset++] = output[position++]; + } + } + } + + return output; + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs new file mode 100644 index 00000000..8e7d41a5 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Ava.UI.ViewModels +{ + internal class UserProfileImageSelectorViewModel : BaseModel + { + private bool _firmwareFound; + + public bool FirmwareFound + { + get => _firmwareFound; + + set + { + _firmwareFound = value; + OnPropertyChanged(); + } + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs b/src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs new file mode 100644 index 00000000..0b65e6d1 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/UserProfileViewModel.cs @@ -0,0 +1,28 @@ +using Ryujinx.Ava.UI.Models; +using System; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class UserProfileViewModel : BaseModel, IDisposable + { + public UserProfileViewModel() + { + Profiles = new ObservableCollection(); + LostProfiles = new ObservableCollection(); + IsEmpty = !LostProfiles.Any(); + } + + public ObservableCollection Profiles { get; set; } + + public ObservableCollection LostProfiles { get; set; } + + public bool IsEmpty { get; set; } + + public void Dispose() + { + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs new file mode 100644 index 00000000..85adef00 --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs @@ -0,0 +1,117 @@ +using DynamicData; +using DynamicData.Binding; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Models; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public class UserSaveManagerViewModel : BaseModel + { + private int _sortIndex; + private int _orderIndex; + private string _search; + private ObservableCollection _saves = new(); + private ObservableCollection _views = new(); + private readonly AccountManager _accountManager; + + public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId); + + public int SortIndex + { + get => _sortIndex; + set + { + _sortIndex = value; + OnPropertyChanged(); + Sort(); + } + } + + public int OrderIndex + { + get => _orderIndex; + set + { + _orderIndex = value; + OnPropertyChanged(); + Sort(); + } + } + + public string Search + { + get => _search; + set + { + _search = value; + OnPropertyChanged(); + Sort(); + } + } + + public ObservableCollection Saves + { + get => _saves; + set + { + _saves = value; + OnPropertyChanged(); + Sort(); + } + } + + public ObservableCollection Views + { + get => _views; + set + { + _views = value; + OnPropertyChanged(); + } + } + + public UserSaveManagerViewModel(AccountManager accountManager) + { + _accountManager = accountManager; + } + + public void Sort() + { + Saves.AsObservableChangeSet() + .Filter(Filter) + .Sort(GetComparer()) + .Bind(out var view).AsObservableList(); + + _views.Clear(); + _views.AddRange(view); + OnPropertyChanged(nameof(Views)); + } + + private bool Filter(object arg) + { + if (arg is SaveModel save) + { + return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower()); + } + + return false; + } + + private IComparer GetComparer() + { + return SortIndex switch + { + 0 => OrderIndex == 0 + ? SortExpressionComparer.Ascending(save => save.Title) + : SortExpressionComparer.Descending(save => save.Title), + 1 => OrderIndex == 0 + ? SortExpressionComparer.Ascending(save => save.Size) + : SortExpressionComparer.Descending(save => save.Size), + _ => null, + }; + } + } +} diff --git a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml new file mode 100644 index 00000000..08bdf90f --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml @@ -0,0 +1,765 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs new file mode 100644 index 00000000..b76648da --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs @@ -0,0 +1,198 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.LogicalTree; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels.Input; +using Ryujinx.Common.Configuration.Hid.Controller; +using Ryujinx.Input; +using Ryujinx.Input.Assigner; +using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; + +namespace Ryujinx.Ava.UI.Views.Input +{ + public partial class ControllerInputView : UserControl + { + private ButtonKeyAssigner _currentAssigner; + + public ControllerInputView() + { + InitializeComponent(); + + foreach (ILogical visual in SettingButtons.GetLogicalDescendants()) + { + if (visual is ToggleButton button and not CheckBox) + { + button.IsCheckedChanged += Button_IsCheckedChanged; + } + } + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + base.OnPointerReleased(e); + + if (_currentAssigner != null && _currentAssigner.ToggledButton != null && !_currentAssigner.ToggledButton.IsPointerOver) + { + _currentAssigner.Cancel(); + } + } + + private void Button_IsCheckedChanged(object sender, RoutedEventArgs e) + { + if (sender is ToggleButton button) + { + if ((bool)button.IsChecked) + { + if (_currentAssigner != null && button == _currentAssigner.ToggledButton) + { + return; + } + + bool isStick = button.Tag != null && button.Tag.ToString() == "stick"; + + if (_currentAssigner == null) + { + _currentAssigner = new ButtonKeyAssigner(button); + + this.Focus(NavigationMethod.Pointer); + + PointerPressed += MouseClick; + + var viewModel = (DataContext as ControllerInputViewModel); + + IKeyboard keyboard = (IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations. + IButtonAssigner assigner = CreateButtonAssigner(isStick); + + _currentAssigner.ButtonAssigned += (sender, e) => + { + if (e.ButtonValue.HasValue) + { + var buttonValue = e.ButtonValue.Value; + viewModel.ParentModel.IsModified = true; + + switch (button.Name) + { + case "ButtonZl": + viewModel.Config.ButtonZl = buttonValue.AsHidType(); + break; + case "ButtonL": + viewModel.Config.ButtonL = buttonValue.AsHidType(); + break; + case "ButtonMinus": + viewModel.Config.ButtonMinus = buttonValue.AsHidType(); + break; + case "LeftStickButton": + viewModel.Config.LeftStickButton = buttonValue.AsHidType(); + break; + case "LeftJoystick": + viewModel.Config.LeftJoystick = buttonValue.AsHidType(); + break; + case "DpadUp": + viewModel.Config.DpadUp = buttonValue.AsHidType(); + break; + case "DpadDown": + viewModel.Config.DpadDown = buttonValue.AsHidType(); + break; + case "DpadLeft": + viewModel.Config.DpadLeft = buttonValue.AsHidType(); + break; + case "DpadRight": + viewModel.Config.DpadRight = buttonValue.AsHidType(); + break; + case "LeftButtonSr": + viewModel.Config.LeftButtonSr = buttonValue.AsHidType(); + break; + case "LeftButtonSl": + viewModel.Config.LeftButtonSl = buttonValue.AsHidType(); + break; + case "RightButtonSr": + viewModel.Config.RightButtonSr = buttonValue.AsHidType(); + break; + case "RightButtonSl": + viewModel.Config.RightButtonSl = buttonValue.AsHidType(); + break; + case "ButtonZr": + viewModel.Config.ButtonZr = buttonValue.AsHidType(); + break; + case "ButtonR": + viewModel.Config.ButtonR = buttonValue.AsHidType(); + break; + case "ButtonPlus": + viewModel.Config.ButtonPlus = buttonValue.AsHidType(); + break; + case "ButtonA": + viewModel.Config.ButtonA = buttonValue.AsHidType(); + break; + case "ButtonB": + viewModel.Config.ButtonB = buttonValue.AsHidType(); + break; + case "ButtonX": + viewModel.Config.ButtonX = buttonValue.AsHidType(); + break; + case "ButtonY": + viewModel.Config.ButtonY = buttonValue.AsHidType(); + break; + case "RightStickButton": + viewModel.Config.RightStickButton = buttonValue.AsHidType(); + break; + case "RightJoystick": + viewModel.Config.RightJoystick = buttonValue.AsHidType(); + break; + } + } + }; + + _currentAssigner.GetInputAndAssign(assigner, keyboard); + } + else + { + if (_currentAssigner != null) + { + _currentAssigner.Cancel(); + _currentAssigner = null; + button.IsChecked = false; + } + } + } + else + { + _currentAssigner?.Cancel(); + _currentAssigner = null; + } + } + } + + private void MouseClick(object sender, PointerPressedEventArgs e) + { + bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed; + + _currentAssigner?.Cancel(shouldUnbind); + + PointerPressed -= MouseClick; + } + + private IButtonAssigner CreateButtonAssigner(bool forStick) + { + IButtonAssigner assigner; + + var controllerInputViewModel = DataContext as ControllerInputViewModel; + + assigner = new GamepadButtonAssigner( + controllerInputViewModel.ParentModel.SelectedGamepad, + (controllerInputViewModel.ParentModel.Config as StandardControllerInputConfig).TriggerThreshold, + forStick); + + return assigner; + } + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + _currentAssigner?.Cancel(); + _currentAssigner = null; + } + } +} diff --git a/src/Ryujinx/UI/Views/Input/InputView.axaml b/src/Ryujinx/UI/Views/Input/InputView.axaml new file mode 100644 index 00000000..b4940941 --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/InputView.axaml @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Input/InputView.axaml.cs b/src/Ryujinx/UI/Views/Input/InputView.axaml.cs new file mode 100644 index 00000000..356381a8 --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/InputView.axaml.cs @@ -0,0 +1,61 @@ +using Avalonia.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels.Input; + +namespace Ryujinx.Ava.UI.Views.Input +{ + public partial class InputView : UserControl + { + private bool _dialogOpen; + private InputViewModel ViewModel { get; set; } + + public InputView() + { + DataContext = ViewModel = new InputViewModel(this); + + InitializeComponent(); + } + + public void SaveCurrentProfile() + { + ViewModel.Save(); + } + + private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (ViewModel.IsModified && !_dialogOpen) + { + _dialogOpen = true; + + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmMessage], + LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmSubMessage], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + ViewModel.Save(); + } + + _dialogOpen = false; + + ViewModel.IsModified = false; + + if (e.AddedItems.Count > 0) + { + var player = (PlayerModel)e.AddedItems[0]; + ViewModel.PlayerId = player.Id; + } + } + } + + public void Dispose() + { + ViewModel.Dispose(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml new file mode 100644 index 00000000..e4566f46 --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml @@ -0,0 +1,675 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml.cs new file mode 100644 index 00000000..f17c7496 --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/KeyboardInputView.axaml.cs @@ -0,0 +1,208 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.LogicalTree; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels.Input; +using Ryujinx.Input; +using Ryujinx.Input.Assigner; +using Key = Ryujinx.Common.Configuration.Hid.Key; + +namespace Ryujinx.Ava.UI.Views.Input +{ + public partial class KeyboardInputView : UserControl + { + private ButtonKeyAssigner _currentAssigner; + + public KeyboardInputView() + { + InitializeComponent(); + + foreach (ILogical visual in SettingButtons.GetLogicalDescendants()) + { + if (visual is ToggleButton button and not CheckBox) + { + button.IsCheckedChanged += Button_IsCheckedChanged; + } + } + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + base.OnPointerReleased(e); + + if (_currentAssigner != null && _currentAssigner.ToggledButton != null && !_currentAssigner.ToggledButton.IsPointerOver) + { + _currentAssigner.Cancel(); + } + } + + private void Button_IsCheckedChanged(object sender, RoutedEventArgs e) + { + if (sender is ToggleButton button) + { + if ((bool)button.IsChecked) + { + if (_currentAssigner != null && button == _currentAssigner.ToggledButton) + { + return; + } + + if (_currentAssigner == null) + { + _currentAssigner = new ButtonKeyAssigner(button); + + Focus(NavigationMethod.Pointer); + + PointerPressed += MouseClick; + + var viewModel = (DataContext as KeyboardInputViewModel); + + IKeyboard keyboard = (IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations. + IButtonAssigner assigner = CreateButtonAssigner(); + + _currentAssigner.ButtonAssigned += (sender, e) => + { + if (e.ButtonValue.HasValue) + { + var buttonValue = e.ButtonValue.Value; + viewModel.ParentModel.IsModified = true; + + switch (button.Name) + { + case "ButtonZl": + viewModel.Config.ButtonZl = buttonValue.AsHidType(); + break; + case "ButtonL": + viewModel.Config.ButtonL = buttonValue.AsHidType(); + break; + case "ButtonMinus": + viewModel.Config.ButtonMinus = buttonValue.AsHidType(); + break; + case "LeftStickButton": + viewModel.Config.LeftStickButton = buttonValue.AsHidType(); + break; + case "LeftStickUp": + viewModel.Config.LeftStickUp = buttonValue.AsHidType(); + break; + case "LeftStickDown": + viewModel.Config.LeftStickDown = buttonValue.AsHidType(); + break; + case "LeftStickRight": + viewModel.Config.LeftStickRight = buttonValue.AsHidType(); + break; + case "LeftStickLeft": + viewModel.Config.LeftStickLeft = buttonValue.AsHidType(); + break; + case "DpadUp": + viewModel.Config.DpadUp = buttonValue.AsHidType(); + break; + case "DpadDown": + viewModel.Config.DpadDown = buttonValue.AsHidType(); + break; + case "DpadLeft": + viewModel.Config.DpadLeft = buttonValue.AsHidType(); + break; + case "DpadRight": + viewModel.Config.DpadRight = buttonValue.AsHidType(); + break; + case "LeftButtonSr": + viewModel.Config.LeftButtonSr = buttonValue.AsHidType(); + break; + case "LeftButtonSl": + viewModel.Config.LeftButtonSl = buttonValue.AsHidType(); + break; + case "RightButtonSr": + viewModel.Config.RightButtonSr = buttonValue.AsHidType(); + break; + case "RightButtonSl": + viewModel.Config.RightButtonSl = buttonValue.AsHidType(); + break; + case "ButtonZr": + viewModel.Config.ButtonZr = buttonValue.AsHidType(); + break; + case "ButtonR": + viewModel.Config.ButtonR = buttonValue.AsHidType(); + break; + case "ButtonPlus": + viewModel.Config.ButtonPlus = buttonValue.AsHidType(); + break; + case "ButtonA": + viewModel.Config.ButtonA = buttonValue.AsHidType(); + break; + case "ButtonB": + viewModel.Config.ButtonB = buttonValue.AsHidType(); + break; + case "ButtonX": + viewModel.Config.ButtonX = buttonValue.AsHidType(); + break; + case "ButtonY": + viewModel.Config.ButtonY = buttonValue.AsHidType(); + break; + case "RightStickButton": + viewModel.Config.RightStickButton = buttonValue.AsHidType(); + break; + case "RightStickUp": + viewModel.Config.RightStickUp = buttonValue.AsHidType(); + break; + case "RightStickDown": + viewModel.Config.RightStickDown = buttonValue.AsHidType(); + break; + case "RightStickRight": + viewModel.Config.RightStickRight = buttonValue.AsHidType(); + break; + case "RightStickLeft": + viewModel.Config.RightStickLeft = buttonValue.AsHidType(); + break; + } + } + }; + + _currentAssigner.GetInputAndAssign(assigner, keyboard); + } + else + { + if (_currentAssigner != null) + { + _currentAssigner.Cancel(); + _currentAssigner = null; + button.IsChecked = false; + } + } + } + else + { + _currentAssigner?.Cancel(); + _currentAssigner = null; + } + } + } + + private void MouseClick(object sender, PointerPressedEventArgs e) + { + bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed; + + _currentAssigner?.Cancel(shouldUnbind); + + PointerPressed -= MouseClick; + } + + private IButtonAssigner CreateButtonAssigner() + { + IButtonAssigner assigner; + + assigner = new KeyboardKeyAssigner((IKeyboard)(DataContext as KeyboardInputViewModel).ParentModel.SelectedGamepad); + + return assigner; + } + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + _currentAssigner?.Cancel(); + _currentAssigner = null; + } + } +} diff --git a/src/Ryujinx/UI/Views/Input/MotionInputView.axaml b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml new file mode 100644 index 00000000..0d018e29 --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Input/MotionInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml.cs new file mode 100644 index 00000000..2304364b --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/MotionInputView.axaml.cs @@ -0,0 +1,66 @@ +using Avalonia.Controls; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.ViewModels.Input; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Views.Input +{ + public partial class MotionInputView : UserControl + { + private readonly MotionInputViewModel _viewModel; + + public MotionInputView() + { + InitializeComponent(); + } + + public MotionInputView(ControllerInputViewModel viewModel) + { + var config = viewModel.Config; + + _viewModel = new MotionInputViewModel + { + Slot = config.Slot, + AltSlot = config.AltSlot, + DsuServerHost = config.DsuServerHost, + DsuServerPort = config.DsuServerPort, + MirrorInput = config.MirrorInput, + Sensitivity = config.Sensitivity, + GyroDeadzone = config.GyroDeadzone, + EnableCemuHookMotion = config.EnableCemuHookMotion, + }; + + InitializeComponent(); + DataContext = _viewModel; + } + + public static async Task Show(ControllerInputViewModel viewModel) + { + MotionInputView content = new(viewModel); + + ContentDialog contentDialog = new() + { + Title = LocaleManager.Instance[LocaleKeys.ControllerMotionTitle], + PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave], + SecondaryButtonText = "", + CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose], + Content = content, + }; + contentDialog.PrimaryButtonClick += (sender, args) => + { + var config = viewModel.Config; + config.Slot = content._viewModel.Slot; + config.Sensitivity = content._viewModel.Sensitivity; + config.GyroDeadzone = content._viewModel.GyroDeadzone; + config.AltSlot = content._viewModel.AltSlot; + config.DsuServerHost = content._viewModel.DsuServerHost; + config.DsuServerPort = content._viewModel.DsuServerPort; + config.EnableCemuHookMotion = content._viewModel.EnableCemuHookMotion; + config.MirrorInput = content._viewModel.MirrorInput; + }; + + await contentDialog.ShowAsync(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml new file mode 100644 index 00000000..1beb1f06 --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml.cs new file mode 100644 index 00000000..58a4b416 --- /dev/null +++ b/src/Ryujinx/UI/Views/Input/RumbleInputView.axaml.cs @@ -0,0 +1,56 @@ +using Avalonia.Controls; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.ViewModels.Input; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Views.Input +{ + public partial class RumbleInputView : UserControl + { + private readonly RumbleInputViewModel _viewModel; + + public RumbleInputView() + { + InitializeComponent(); + } + + public RumbleInputView(ControllerInputViewModel viewModel) + { + var config = viewModel.Config; + + _viewModel = new RumbleInputViewModel + { + StrongRumble = config.StrongRumble, + WeakRumble = config.WeakRumble, + }; + + InitializeComponent(); + + DataContext = _viewModel; + } + + public static async Task Show(ControllerInputViewModel viewModel) + { + RumbleInputView content = new(viewModel); + + ContentDialog contentDialog = new() + { + Title = LocaleManager.Instance[LocaleKeys.ControllerRumbleTitle], + PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave], + SecondaryButtonText = "", + CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose], + Content = content, + }; + + contentDialog.PrimaryButtonClick += (sender, args) => + { + var config = viewModel.Config; + config.StrongRumble = content._viewModel.StrongRumble; + config.WeakRumble = content._viewModel.WeakRumble; + }; + + await contentDialog.ShowAsync(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml new file mode 100644 index 00000000..ac373611 --- /dev/null +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs new file mode 100644 index 00000000..73ae0df1 --- /dev/null +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs @@ -0,0 +1,275 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Threading; +using LibHac.Ncm; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Utilities; +using Ryujinx.Modules; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Ryujinx.Ava.UI.Views.Main +{ + public partial class MainMenuBarView : UserControl + { + public MainWindow Window { get; private set; } + public MainWindowViewModel ViewModel { get; private set; } + + public MainMenuBarView() + { + InitializeComponent(); + + ToggleFileTypesMenuItem.ItemsSource = GenerateToggleFileTypeItems(); + ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems(); + } + + private CheckBox[] GenerateToggleFileTypeItems() + { + List 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(() => Window.ToggleFileType(fileName)), + }); + } + + return checkBoxes.ToArray(); + } + + private static MenuItem[] GenerateLanguageMenuItems() + { + List menuItems = new(); + + string localePath = "Ryujinx/Assets/Locales"; + string localeExt = ".json"; + + string[] localesPath = EmbeddedResources.GetAllAvailableResources(localePath, localeExt); + + Array.Sort(localesPath); + + foreach (string locale in localesPath) + { + string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last(); + string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}"); + var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary); + + if (!strings.TryGetValue("Language", out string languageName)) + { + languageName = languageCode; + } + + MenuItem menuItem = new() + { + Header = languageName, + Command = MiniCommand.Create(() => + { + MainWindowViewModel.ChangeLanguage(languageCode); + }), + }; + + menuItems.Add(menuItem); + } + + return menuItems.ToArray(); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + if (VisualRoot is MainWindow window) + { + Window = window; + } + + ViewModel = Window.ViewModel; + DataContext = ViewModel; + } + + private async void StopEmulation_Click(object sender, RoutedEventArgs e) + { + await Window.ViewModel.AppHost?.ShowExitPrompt(); + } + + private void PauseEmulation_Click(object sender, RoutedEventArgs e) + { + Window.ViewModel.AppHost?.Pause(); + } + + private void ResumeEmulation_Click(object sender, RoutedEventArgs e) + { + Window.ViewModel.AppHost?.Resume(); + } + + public async void OpenSettings(object sender, RoutedEventArgs e) + { + Window.SettingsWindow = new(Window.VirtualFileSystem, Window.ContentManager); + + await Window.SettingsWindow.ShowDialog(Window); + + Window.SettingsWindow = null; + + ViewModel.LoadConfigurableHotKeys(); + } + + public async void OpenMiiApplet(object sender, RoutedEventArgs e) + { + string contentPath = ViewModel.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); + + if (!string.IsNullOrEmpty(contentPath)) + { + ApplicationData applicationData = new() + { + Name = "miiEdit", + Id = 0x0100000000001009, + Path = contentPath, + }; + + await ViewModel.LoadApplication(applicationData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen); + } + } + + public async void OpenAmiiboWindow(object sender, RoutedEventArgs e) + { + if (!ViewModel.IsAmiiboRequested) + { + return; + } + + if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId)) + { + string titleId = ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(); + AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId); + + await window.ShowDialog(Window); + + if (window.IsScanned) + { + ViewModel.ShowAll = window.ViewModel.ShowAllAmiibo; + ViewModel.LastScannedAmiiboId = window.ScannedAmiibo.GetId(); + + ViewModel.AppHost.Device.System.ScanAmiibo(deviceId, ViewModel.LastScannedAmiiboId, window.ViewModel.UseRandomUuid); + } + } + } + + public async void OpenCheatManagerForCurrentApp(object sender, RoutedEventArgs e) + { + if (!ViewModel.IsGameRunning) + { + return; + } + + string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString(); + + await new CheatWindow( + Window.VirtualFileSystem, + ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, + name, + Window.ViewModel.SelectedApplication.Path).ShowDialog(Window); + + ViewModel.AppHost.Device.EnableCheats(); + } + + private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) + { + if (sender is MenuItem) + { + ViewModel.IsAmiiboRequested = Window.ViewModel.AppHost.Device.System.SearchingForAmiibo(out _); + } + } + + private async void InstallFileTypes_Click(object sender, RoutedEventArgs e) + { + if (FileAssociationHelper.Install()) + { + await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); + } + else + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesErrorMessage]); + } + } + + private async void UninstallFileTypes_Click(object sender, RoutedEventArgs e) + { + if (FileAssociationHelper.Uninstall()) + { + await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); + } + else + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesErrorMessage]); + } + } + + private async void ChangeWindowSize_Click(object sender, RoutedEventArgs e) + { + if (sender is MenuItem item) + { + int height; + int width; + + switch (item.Tag) + { + case "720": + height = 720; + width = 1280; + break; + + case "1080": + height = 1080; + width = 1920; + break; + + default: + throw new ArgumentNullException($"Invalid Tag for {item}"); + } + + await Dispatcher.UIThread.InvokeAsync(() => + { + ViewModel.WindowState = WindowState.Normal; + + height += (int)Window.StatusBarHeight + (int)Window.MenuBarHeight; + + Window.Arrange(new Rect(Window.Position.X, Window.Position.Y, width, height)); + }); + } + } + + public async void CheckForUpdates(object sender, RoutedEventArgs e) + { + if (Updater.CanUpdate(true)) + { + await Updater.BeginParse(Window, true); + } + } + + public async void OpenAboutWindow(object sender, RoutedEventArgs e) + { + await AboutWindow.Show(); + } + + public void CloseWindow(object sender, RoutedEventArgs e) + { + Window.Close(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml new file mode 100644 index 00000000..f9e192e6 --- /dev/null +++ b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs new file mode 100644 index 00000000..52849571 --- /dev/null +++ b/src/Ryujinx/UI/Views/Main/MainStatusBarView.axaml.cs @@ -0,0 +1,72 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.UI.Common.Configuration; +using System; + +namespace Ryujinx.Ava.UI.Views.Main +{ + public partial class MainStatusBarView : UserControl + { + public MainWindow Window; + + public MainStatusBarView() + { + InitializeComponent(); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + if (VisualRoot is MainWindow window) + { + Window = window; + } + + DataContext = Window.ViewModel; + } + + private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e) + { + Window.ViewModel.AppHost.ToggleVSync(); + + Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {Window.ViewModel.AppHost.Device.EnableDeviceVsync}"); + } + + private void DockedStatus_PointerReleased(object sender, PointerReleasedEventArgs e) + { + ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value; + } + + private void AspectRatioStatus_OnClick(object sender, RoutedEventArgs e) + { + AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value; + ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1; + } + + private void Refresh_OnClick(object sender, RoutedEventArgs e) + { + Window.LoadApplications(); + } + + private void VolumeStatus_OnPointerWheelChanged(object sender, PointerWheelEventArgs e) + { + // Change the volume by 5% at a time + float newValue = Window.ViewModel.Volume + (float)e.Delta.Y * 0.05f; + + Window.ViewModel.Volume = newValue switch + { + < 0 => 0, + > 1 => 1, + _ => newValue, + }; + + e.Handled = true; + } + } +} diff --git a/src/Ryujinx/UI/Views/Main/MainViewControls.axaml b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml new file mode 100644 index 00000000..cc21b5c6 --- /dev/null +++ b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Main/MainViewControls.axaml.cs b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml.cs new file mode 100644 index 00000000..02fd1bf5 --- /dev/null +++ b/src/Ryujinx/UI/Views/Main/MainViewControls.axaml.cs @@ -0,0 +1,54 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Windows; +using System; + +namespace Ryujinx.Ava.UI.Views.Main +{ + public partial class MainViewControls : UserControl + { + public MainWindowViewModel ViewModel; + + public MainViewControls() + { + InitializeComponent(); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + if (VisualRoot is MainWindow window) + { + ViewModel = window.ViewModel; + } + + DataContext = ViewModel; + } + + public void Sort_Checked(object sender, RoutedEventArgs args) + { + if (sender is RadioButton button) + { + ViewModel.Sort(Enum.Parse(button.Tag.ToString())); + } + } + + public void Order_Checked(object sender, RoutedEventArgs args) + { + if (sender is RadioButton button) + { + ViewModel.Sort(button.Tag.ToString() != "Descending"); + } + } + + private void SearchBox_OnKeyUp(object sender, KeyEventArgs e) + { + ViewModel.SearchText = SearchBox.Text; + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml new file mode 100644 index 00000000..657e07ee --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml.cs new file mode 100644 index 00000000..b672a0f2 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsAudioView : UserControl + { + public SettingsAudioView() + { + InitializeComponent(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml new file mode 100644 index 00000000..c74d3dd5 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml.cs new file mode 100644 index 00000000..a475971a --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsCPUView : UserControl + { + public SettingsCPUView() + { + InitializeComponent(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml new file mode 100644 index 00000000..5cffc684 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml @@ -0,0 +1,301 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml.cs new file mode 100644 index 00000000..67341330 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsGraphicsView : UserControl + { + public SettingsGraphicsView() + { + InitializeComponent(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml new file mode 100644 index 00000000..bffcada0 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs new file mode 100644 index 00000000..fb0fe2bb --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsHotkeysView.axaml.cs @@ -0,0 +1,144 @@ +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.LogicalTree; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Input; +using Ryujinx.Input.Assigner; +using Key = Ryujinx.Common.Configuration.Hid.Key; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsHotkeysView : UserControl + { + private ButtonKeyAssigner _currentAssigner; + private readonly IGamepadDriver _avaloniaKeyboardDriver; + + public SettingsHotkeysView() + { + InitializeComponent(); + + foreach (ILogical visual in SettingButtons.GetLogicalDescendants()) + { + if (visual is ToggleButton button and not CheckBox) + { + button.IsCheckedChanged += Button_IsCheckedChanged; + } + } + + _avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this); + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + base.OnPointerReleased(e); + + if (!_currentAssigner?.ToggledButton?.IsPointerOver ?? false) + { + _currentAssigner.Cancel(); + } + } + + private void MouseClick(object sender, PointerPressedEventArgs e) + { + bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed; + + _currentAssigner?.Cancel(shouldUnbind); + + PointerPressed -= MouseClick; + } + + private void Button_IsCheckedChanged(object sender, RoutedEventArgs e) + { + if (sender is ToggleButton button) + { + if ((bool)button.IsChecked) + { + if (_currentAssigner != null && button == _currentAssigner.ToggledButton) + { + return; + } + + if (_currentAssigner == null) + { + _currentAssigner = new ButtonKeyAssigner(button); + + this.Focus(NavigationMethod.Pointer); + + PointerPressed += MouseClick; + + var keyboard = (IKeyboard)_avaloniaKeyboardDriver.GetGamepad("0"); + IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard); + + _currentAssigner.ButtonAssigned += (sender, e) => + { + if (e.ButtonValue.HasValue) + { + var viewModel = (DataContext) as SettingsViewModel; + var buttonValue = e.ButtonValue.Value; + + switch (button.Name) + { + case "ToggleVsync": + viewModel.KeyboardHotkey.ToggleVsync = buttonValue.AsHidType(); + break; + case "Screenshot": + viewModel.KeyboardHotkey.Screenshot = buttonValue.AsHidType(); + break; + case "ShowUI": + viewModel.KeyboardHotkey.ShowUI = buttonValue.AsHidType(); + break; + case "Pause": + viewModel.KeyboardHotkey.Pause = buttonValue.AsHidType(); + break; + case "ToggleMute": + viewModel.KeyboardHotkey.ToggleMute = buttonValue.AsHidType(); + break; + case "ResScaleUp": + viewModel.KeyboardHotkey.ResScaleUp = buttonValue.AsHidType(); + break; + case "ResScaleDown": + viewModel.KeyboardHotkey.ResScaleDown = buttonValue.AsHidType(); + break; + case "VolumeUp": + viewModel.KeyboardHotkey.VolumeUp = buttonValue.AsHidType(); + break; + case "VolumeDown": + viewModel.KeyboardHotkey.VolumeDown = buttonValue.AsHidType(); + break; + } + } + }; + + _currentAssigner.GetInputAndAssign(assigner, keyboard); + } + else + { + if (_currentAssigner != null) + { + _currentAssigner.Cancel(); + _currentAssigner = null; + button.IsChecked = false; + } + } + } + else + { + _currentAssigner?.Cancel(); + _currentAssigner = null; + } + } + } + + public void Dispose() + { + _currentAssigner?.Cancel(); + _currentAssigner = null; + + _avaloniaKeyboardDriver.Dispose(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml new file mode 100644 index 00000000..55c2ed6e --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml.cs new file mode 100644 index 00000000..55b69af0 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsInputView.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsInputView : UserControl + { + public SettingsInputView() + { + InitializeComponent(); + } + + public void Dispose() + { + InputView.Dispose(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml new file mode 100644 index 00000000..0fc9ea1b --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml.cs new file mode 100644 index 00000000..c8df46b3 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsLoggingView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsLoggingView : UserControl + { + public SettingsLoggingView() + { + InitializeComponent(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml new file mode 100644 index 00000000..67fac192 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml.cs new file mode 100644 index 00000000..b771933e --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsNetworkView.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsNetworkView : UserControl + { + public SettingsNetworkView() + { + InitializeComponent(); + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml new file mode 100644 index 00000000..e6f7c6e4 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs new file mode 100644 index 00000000..2c9eac28 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml.cs @@ -0,0 +1,37 @@ +using Avalonia.Controls; +using Ryujinx.Ava.UI.ViewModels; +using TimeZone = Ryujinx.Ava.UI.Models.TimeZone; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsSystemView : UserControl + { + public SettingsViewModel ViewModel; + + public SettingsSystemView() + { + InitializeComponent(); + } + + private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (e.AddedItems != null && e.AddedItems.Count > 0) + { + if (e.AddedItems[0] is TimeZone timeZone) + { + e.Handled = true; + + ViewModel.ValidateAndSetTimeZone(timeZone.Location); + } + } + } + + private void TimeZoneBox_OnTextChanged(object sender, TextChangedEventArgs e) + { + if (sender is AutoCompleteBox box && box.SelectedItem is TimeZone timeZone) + { + ViewModel.ValidateAndSetTimeZone(timeZone.Location); + } + } + } +} diff --git a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml new file mode 100644 index 00000000..f9b9be44 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml.cs new file mode 100644 index 00000000..996d15cd --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml.cs @@ -0,0 +1,65 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Platform.Storage; +using Avalonia.VisualTree; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.ViewModels; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsUiView : UserControl + { + public SettingsViewModel ViewModel; + + public SettingsUiView() + { + InitializeComponent(); + } + + private async void AddButton_OnClick(object sender, RoutedEventArgs e) + { + string path = PathBox.Text; + + if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path)) + { + ViewModel.GameDirectories.Add(path); + ViewModel.DirectoryChanged = true; + } + else + { + if (this.GetVisualRoot() is Window window) + { + var result = await window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + AllowMultiple = false, + }); + + if (result.Count > 0) + { + ViewModel.GameDirectories.Add(result[0].Path.LocalPath); + ViewModel.DirectoryChanged = true; + } + } + } + } + + private void RemoveButton_OnClick(object sender, RoutedEventArgs e) + { + int oldIndex = GameList.SelectedIndex; + + foreach (string path in new List(GameList.SelectedItems.Cast())) + { + ViewModel.GameDirectories.Remove(path); + ViewModel.DirectoryChanged = true; + } + + if (GameList.ItemCount > 0) + { + GameList.SelectedIndex = oldIndex < GameList.ItemCount ? oldIndex : 0; + } + } + } +} diff --git a/src/Ryujinx/UI/Views/User/UserEditorView.axaml b/src/Ryujinx/UI/Views/User/UserEditorView.axaml new file mode 100644 index 00000000..ab83c2cd --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserEditorView.axaml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml.cs b/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml.cs new file mode 100644 index 00000000..b4f23b5b --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserProfileImageSelectorView.axaml.cs @@ -0,0 +1,120 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Platform.Storage; +using Avalonia.VisualTree; +using FluentAvalonia.UI.Controls; +using FluentAvalonia.UI.Navigation; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.HLE.FileSystem; +using SkiaSharp; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.Ava.UI.Views.User +{ + public partial class UserProfileImageSelectorView : UserControl + { + private ContentManager _contentManager; + private NavigationDialogHost _parent; + private TempProfile _profile; + + internal UserProfileImageSelectorViewModel ViewModel { get; private set; } + + public UserProfileImageSelectorView() + { + InitializeComponent(); + AddHandler(Frame.NavigatedToEvent, (s, e) => + { + NavigatedTo(e); + }, RoutingStrategies.Direct); + } + + private void NavigatedTo(NavigationEventArgs arg) + { + if (Program.PreviewerDetached) + { + switch (arg.NavigationMode) + { + case NavigationMode.New: + (_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter; + _contentManager = _parent.ContentManager; + + ((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.ProfileImageSelectionHeader]}"; + + if (Program.PreviewerDetached) + { + DataContext = ViewModel = new UserProfileImageSelectorViewModel(); + ViewModel.FirmwareFound = _contentManager.GetCurrentFirmwareVersion() != null; + } + + break; + case NavigationMode.Back: + if (_profile.Image != null) + { + _parent.GoBack(); + } + break; + } + } + } + + private async void Import_OnClick(object sender, RoutedEventArgs e) + { + var window = this.GetVisualRoot() as Window; + var result = await window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + AllowMultiple = false, + FileTypeFilter = new List + { + new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) + { + Patterns = new[] { "*.jpg", "*.jpeg", "*.png", "*.bmp" }, + AppleUniformTypeIdentifiers = new[] { "public.jpeg", "public.png", "com.microsoft.bmp" }, + MimeTypes = new[] { "image/jpeg", "image/png", "image/bmp" }, + }, + }, + }); + + if (result.Count > 0) + { + _profile.Image = ProcessProfileImage(File.ReadAllBytes(result[0].Path.LocalPath)); + _parent.GoBack(); + } + } + + private void GoBack(object sender, RoutedEventArgs e) + { + _parent.GoBack(); + } + + private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e) + { + if (ViewModel.FirmwareFound) + { + _parent.Navigate(typeof(UserFirmwareAvatarSelectorView), (_parent, _profile)); + } + } + + private static byte[] ProcessProfileImage(byte[] buffer) + { + using var bitmap = SKBitmap.Decode(buffer); + + var resizedBitmap = bitmap.Resize(new SKImageInfo(256, 256), SKFilterQuality.High); + + using var streamJpg = new MemoryStream(); + + if (resizedBitmap != null) + { + using var image = SKImage.FromBitmap(resizedBitmap); + using var dataJpeg = image.Encode(SKEncodedImageFormat.Jpeg, 100); + + dataJpeg.SaveTo(streamJpg); + } + + return streamJpg.ToArray(); + } + } +} diff --git a/src/Ryujinx/UI/Views/User/UserRecovererView.axaml b/src/Ryujinx/UI/Views/User/UserRecovererView.axaml new file mode 100644 index 00000000..3fdb4ab9 --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserRecovererView.axaml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/User/UserRecovererView.axaml.cs b/src/Ryujinx/UI/Views/User/UserRecovererView.axaml.cs new file mode 100644 index 00000000..31934349 --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserRecovererView.axaml.cs @@ -0,0 +1,51 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using FluentAvalonia.UI.Controls; +using FluentAvalonia.UI.Navigation; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Controls; + +namespace Ryujinx.Ava.UI.Views.User +{ + public partial class UserRecovererView : UserControl + { + private NavigationDialogHost _parent; + + public UserRecovererView() + { + InitializeComponent(); + AddHandler(Frame.NavigatedToEvent, (s, e) => + { + NavigatedTo(e); + }, RoutingStrategies.Direct); + } + + private void NavigatedTo(NavigationEventArgs arg) + { + if (Program.PreviewerDetached) + { + switch (arg.NavigationMode) + { + case NavigationMode.New: + var parent = (NavigationDialogHost)arg.Parameter; + + _parent = parent; + + ((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {LocaleManager.Instance[LocaleKeys.UserProfilesRecoverHeading]}"; + + break; + } + } + } + + private void GoBack(object sender, RoutedEventArgs e) + { + _parent?.GoBack(); + } + + private void Recover(object sender, RoutedEventArgs e) + { + _parent?.RecoverLostAccounts(); + } + } +} diff --git a/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml b/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml new file mode 100644 index 00000000..8bc5125a --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml.cs b/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml.cs new file mode 100644 index 00000000..00a229fa --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserSaveManagerView.axaml.cs @@ -0,0 +1,148 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using FluentAvalonia.UI.Navigation; +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using Button = Avalonia.Controls.Button; +using UserId = LibHac.Fs.UserId; + +namespace Ryujinx.Ava.UI.Views.User +{ + public partial class UserSaveManagerView : UserControl + { + internal UserSaveManagerViewModel ViewModel { get; private set; } + + private AccountManager _accountManager; + private HorizonClient _horizonClient; + private VirtualFileSystem _virtualFileSystem; + private NavigationDialogHost _parent; + + public UserSaveManagerView() + { + InitializeComponent(); + AddHandler(Frame.NavigatedToEvent, (s, e) => + { + NavigatedTo(e); + }, RoutingStrategies.Direct); + } + + private void NavigatedTo(NavigationEventArgs arg) + { + if (Program.PreviewerDetached) + { + switch (arg.NavigationMode) + { + case NavigationMode.New: + var (parent, accountManager, client, virtualFileSystem) = ((NavigationDialogHost parent, AccountManager accountManager, HorizonClient client, VirtualFileSystem virtualFileSystem))arg.Parameter; + _accountManager = accountManager; + _horizonClient = client; + _virtualFileSystem = virtualFileSystem; + + _parent = parent; + break; + } + + DataContext = ViewModel = new UserSaveManagerViewModel(_accountManager); + ((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - {ViewModel.SaveManagerHeading}"; + + Task.Run(LoadSaves); + } + } + + public void LoadSaves() + { + ViewModel.Saves.Clear(); + var saves = new ObservableCollection(); + var saveDataFilter = SaveDataFilter.Make( + programId: default, + saveType: SaveDataType.Account, + new UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low), + saveDataId: default, + index: default); + + using var saveDataIterator = new UniqueRef(); + + _horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure(); + + Span saveDataInfo = stackalloc SaveDataInfo[10]; + + while (true) + { + saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure(); + + if (readCount == 0) + { + break; + } + + for (int i = 0; i < readCount; i++) + { + var save = saveDataInfo[i]; + if (save.ProgramId.Value != 0) + { + var saveModel = new SaveModel(save); + saves.Add(saveModel); + } + } + } + + Dispatcher.UIThread.Post(() => + { + ViewModel.Saves = saves; + ViewModel.Sort(); + }); + } + + private void GoBack(object sender, RoutedEventArgs e) + { + _parent?.GoBack(); + } + + private void OpenLocation(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is SaveModel saveModel) + { + ApplicationHelper.OpenSaveDir(saveModel.SaveId); + } + } + } + + private async void Delete(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is SaveModel saveModel) + { + var result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DeleteUserSave], + LocaleManager.Instance[LocaleKeys.IrreversibleActionNote], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], ""); + + if (result == UserResult.Yes) + { + _horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, saveModel.SaveId); + ViewModel.Saves.Remove(saveModel); + ViewModel.Sort(); + } + } + } + } + } +} diff --git a/src/Ryujinx/UI/Views/User/UserSelectorView.axaml b/src/Ryujinx/UI/Views/User/UserSelectorView.axaml new file mode 100644 index 00000000..3a9de303 --- /dev/null +++ b/src/Ryujinx/UI/Views/User/UserSelectorView.axaml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Windows/AboutWindow.axaml.cs b/src/Ryujinx/UI/Windows/AboutWindow.axaml.cs new file mode 100644 index 00000000..c32661b0 --- /dev/null +++ b/src/Ryujinx/UI/Windows/AboutWindow.axaml.cs @@ -0,0 +1,63 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Styling; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.UI.Common.Helper; +using System.Threading.Tasks; +using Button = Avalonia.Controls.Button; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class AboutWindow : UserControl + { + public AboutWindow() + { + DataContext = new AboutWindowViewModel(); + + InitializeComponent(); + } + + public static async Task Show() + { + ContentDialog contentDialog = new() + { + PrimaryButtonText = "", + SecondaryButtonText = "", + CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose], + Content = new AboutWindow(), + }; + + Style closeButton = new(x => x.Name("CloseButton")); + closeButton.Setters.Add(new Setter(WidthProperty, 80d)); + + Style closeButtonParent = new(x => x.Name("CommandSpace")); + closeButtonParent.Setters.Add(new Setter(HorizontalAlignmentProperty, HorizontalAlignment.Right)); + + contentDialog.Styles.Add(closeButton); + contentDialog.Styles.Add(closeButtonParent); + + await ContentDialogHelper.ShowAsync(contentDialog); + } + + private void Button_OnClick(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + OpenHelper.OpenUrl(button.Tag.ToString()); + } + } + + private void AmiiboLabel_OnPointerPressed(object sender, PointerPressedEventArgs e) + { + if (sender is TextBlock) + { + OpenHelper.OpenUrl("https://amiiboapi.com"); + } + } + } +} diff --git a/src/Ryujinx/UI/Windows/AmiiboWindow.axaml b/src/Ryujinx/UI/Windows/AmiiboWindow.axaml new file mode 100644 index 00000000..c587aa87 --- /dev/null +++ b/src/Ryujinx/UI/Windows/AmiiboWindow.axaml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Windows/CheatWindow.axaml.cs b/src/Ryujinx/UI/Windows/CheatWindow.axaml.cs new file mode 100644 index 00000000..8f4c3ceb --- /dev/null +++ b/src/Ryujinx/UI/Windows/CheatWindow.axaml.cs @@ -0,0 +1,128 @@ +using Avalonia.Collections; +using LibHac.Tools.FsSystem; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Models; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common.Configuration; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class CheatWindow : StyleableWindow + { + private readonly string _enabledCheatsPath; + public bool NoCheatsFound { get; } + + public AvaloniaList LoadedCheats { get; } + + public string Heading { get; } + public string BuildId { get; } + + public CheatWindow() + { + DataContext = this; + + InitializeComponent(); + + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.CheatWindowTitle]; + } + + public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath) + { + LoadedCheats = new AvaloniaList(); + IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks + ? IntegrityCheckLevel.ErrorOnInvalid + : IntegrityCheckLevel.None; + + Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper()); + BuildId = ApplicationData.GetBuildId(virtualFileSystem, checkLevel, titlePath); + + InitializeComponent(); + + string modsBasePath = ModLoader.GetModsBasePath(); + string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId); + ulong titleIdValue = ulong.Parse(titleId, NumberStyles.HexNumber); + + _enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt"); + + string[] enabled = Array.Empty(); + + if (File.Exists(_enabledCheatsPath)) + { + enabled = File.ReadAllLines(_enabledCheatsPath); + } + + int cheatAdded = 0; + + var mods = new ModLoader.ModCache(); + + ModLoader.QueryContentsDir(mods, new DirectoryInfo(Path.Combine(modsBasePath, "contents")), titleIdValue); + + string currentCheatFile = string.Empty; + string buildId = string.Empty; + + CheatNode currentGroup = null; + + foreach (var cheat in mods.Cheats) + { + if (cheat.Path.FullName != currentCheatFile) + { + currentCheatFile = cheat.Path.FullName; + string parentPath = currentCheatFile.Replace(titleModsPath, ""); + + buildId = Path.GetFileNameWithoutExtension(currentCheatFile).ToUpper(); + currentGroup = new CheatNode("", buildId, parentPath, true); + + LoadedCheats.Add(currentGroup); + } + + var model = new CheatNode(cheat.Name, buildId, "", false, enabled.Contains($"{buildId}-{cheat.Name}")); + currentGroup?.SubNodes.Add(model); + + cheatAdded++; + } + + if (cheatAdded == 0) + { + NoCheatsFound = true; + } + + DataContext = this; + + Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.CheatWindowTitle]; + } + + public void Save() + { + if (NoCheatsFound) + { + return; + } + + List enabledCheats = new(); + + foreach (var cheats in LoadedCheats) + { + foreach (var cheat in cheats.SubNodes) + { + if (cheat.IsEnabled) + { + enabledCheats.Add(cheat.BuildIdKey); + } + } + } + + Directory.CreateDirectory(Path.GetDirectoryName(_enabledCheatsPath)); + + File.WriteAllLines(_enabledCheatsPath, enabledCheats); + + Close(); + } + } +} diff --git a/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml b/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml new file mode 100644 index 00000000..8b52bade --- /dev/null +++ b/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml @@ -0,0 +1,25 @@ + + + + + + diff --git a/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs b/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs new file mode 100644 index 00000000..0e241024 --- /dev/null +++ b/src/Ryujinx/UI/Windows/ContentDialogOverlayWindow.axaml.cs @@ -0,0 +1,20 @@ +using Avalonia.Controls; +using Avalonia.Media; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class ContentDialogOverlayWindow : StyleableWindow + { + public ContentDialogOverlayWindow() + { + InitializeComponent(); + + TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent }; + WindowStartupLocation = WindowStartupLocation.Manual; + SystemDecorations = SystemDecorations.None; + ExtendClientAreaTitleBarHeightHint = 0; + Background = Brushes.Transparent; + CanResize = false; + } + } +} diff --git a/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml new file mode 100644 index 00000000..98aac09c --- /dev/null +++ b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs new file mode 100644 index 00000000..ab04fd68 --- /dev/null +++ b/src/Ryujinx/UI/Windows/DownloadableContentManagerWindow.axaml.cs @@ -0,0 +1,114 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Styling; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.HLE.FileSystem; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common.Helper; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class DownloadableContentManagerWindow : UserControl + { + public DownloadableContentManagerViewModel ViewModel; + + public DownloadableContentManagerWindow() + { + DataContext = this; + + InitializeComponent(); + } + + public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ApplicationData applicationData) + { + DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, applicationData); + + InitializeComponent(); + } + + public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationData applicationData) + { + ContentDialog contentDialog = new() + { + PrimaryButtonText = "", + SecondaryButtonText = "", + CloseButtonText = "", + Content = new DownloadableContentManagerWindow(virtualFileSystem, applicationData), + Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], applicationData.Name, applicationData.IdBaseString), + }; + + Style bottomBorder = new(x => x.OfType().Name("DialogSpace").Child().OfType()); + bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false)); + + contentDialog.Styles.Add(bottomBorder); + + await contentDialog.ShowAsync(); + } + + private void SaveAndClose(object sender, RoutedEventArgs routedEventArgs) + { + ViewModel.Save(); + ((ContentDialog)Parent).Hide(); + } + + private void Close(object sender, RoutedEventArgs e) + { + ((ContentDialog)Parent).Hide(); + } + + private void RemoveDLC(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is DownloadableContentModel model) + { + ViewModel.Remove(model); + } + } + } + + private void OpenLocation(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is DownloadableContentModel model) + { + OpenHelper.LocateFile(model.ContainerPath); + } + } + } + + private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + foreach (var content in e.AddedItems) + { + if (content is DownloadableContentModel model) + { + var index = ViewModel.DownloadableContents.IndexOf(model); + + if (index != -1) + { + ViewModel.DownloadableContents[index].Enabled = true; + } + } + } + + foreach (var content in e.RemovedItems) + { + if (content is DownloadableContentModel model) + { + var index = ViewModel.DownloadableContents.IndexOf(model); + + if (index != -1) + { + ViewModel.DownloadableContents[index].Enabled = false; + } + } + } + } + } +} diff --git a/src/Ryujinx/UI/Windows/IconColorPicker.cs b/src/Ryujinx/UI/Windows/IconColorPicker.cs new file mode 100644 index 00000000..dd6a55d4 --- /dev/null +++ b/src/Ryujinx/UI/Windows/IconColorPicker.cs @@ -0,0 +1,203 @@ +using SkiaSharp; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Ava.UI.Windows +{ + static class IconColorPicker + { + private const int ColorsPerLine = 64; + private const int TotalColors = ColorsPerLine * ColorsPerLine; + + private const int UvQuantBits = 3; + private const int UvQuantShift = BitsPerComponent - UvQuantBits; + + private const int SatQuantBits = 5; + private const int SatQuantShift = BitsPerComponent - SatQuantBits; + + private const int BitsPerComponent = 8; + + private const int CutOffLuminosity = 64; + + private readonly struct PaletteColor + { + public int Qck { get; } + public byte R { get; } + public byte G { get; } + public byte B { get; } + + public PaletteColor(int qck, byte r, byte g, byte b) + { + Qck = qck; + R = r; + G = g; + B = b; + } + } + + public static SKColor GetFilteredColor(SKBitmap image) + { + var color = GetColor(image); + + + // We don't want colors that are too dark. + // If the color is too dark, make it brighter by reducing the range + // and adding a constant color. + int luminosity = GetColorApproximateLuminosity(color.Red, color.Green, color.Blue); + if (luminosity < CutOffLuminosity) + { + color = new SKColor( + (byte)Math.Min(CutOffLuminosity + color.Red, byte.MaxValue), + (byte)Math.Min(CutOffLuminosity + color.Green, byte.MaxValue), + (byte)Math.Min(CutOffLuminosity + color.Blue, byte.MaxValue)); + } + + return color; + } + + public static SKColor GetColor(SKBitmap image) + { + var colors = new PaletteColor[TotalColors]; + var dominantColorBin = new Dictionary(); + + var buffer = GetBuffer(image); + + int w = image.Width; + int w8 = w << 8; + int h8 = image.Height << 8; + +#pragma warning disable IDE0059 // Unnecessary assignment + int xStep = w8 / ColorsPerLine; + int yStep = h8 / ColorsPerLine; +#pragma warning restore IDE0059 + + int i = 0; + int maxHitCount = 0; + + for (int y = 0; y < image.Height; y++) + { + int yOffset = y * image.Width; + + for (int x = 0; x < image.Width && i < TotalColors; x++) + { + int offset = x + yOffset; + + SKColor pixel = buffer[offset]; + byte cr = pixel.Red; + byte cg = pixel.Green; + byte cb = pixel.Blue; + + var qck = GetQuantizedColorKey(cr, cg, cb); + + if (dominantColorBin.TryGetValue(qck, out int hitCount)) + { + dominantColorBin[qck] = hitCount + 1; + + if (maxHitCount < hitCount) + { + maxHitCount = hitCount; + } + } + else + { + dominantColorBin.Add(qck, 1); + } + + colors[i++] = new PaletteColor(qck, cr, cg, cb); + } + } + + int highScore = -1; + PaletteColor bestCandidate = default; + + for (i = 0; i < TotalColors; i++) + { + var score = GetColorScore(dominantColorBin, maxHitCount, colors[i]); + + if (highScore < score) + { + highScore = score; + bestCandidate = colors[i]; + } + } + + return new SKColor(bestCandidate.R, bestCandidate.G, bestCandidate.B); + } + + public static SKColor[] GetBuffer(SKBitmap image) + { + var pixels = new SKColor[image.Width * image.Height]; + + for (int y = 0; y < image.Height; y++) + { + for (int x = 0; x < image.Width; x++) + { + pixels[x + y * image.Width] = image.GetPixel(x, y); + } + } + + return pixels; + } + + private static int GetColorScore(Dictionary dominantColorBin, int maxHitCount, PaletteColor color) + { + var hitCount = dominantColorBin[color.Qck]; + var balancedHitCount = BalanceHitCount(hitCount, maxHitCount); + var quantSat = (GetColorSaturation(color) >> SatQuantShift) << SatQuantShift; + var value = GetColorValue(color); + + // If the color is rarely used on the image, + // then chances are that theres a better candidate, even if the saturation value + // is high. By multiplying the saturation value with a weight, we can lower + // it if the color is almost never used (hit count is low). + var satWeighted = quantSat; + var satWeight = balancedHitCount << 5; + if (satWeight < 0x100) + { + satWeighted = (satWeighted * satWeight) >> 8; + } + + // Compute score from saturation and dominance of the color. + // We prefer more vivid colors over dominant ones, so give more weight to the saturation. + var score = ((satWeighted << 1) + balancedHitCount) * value; + + return score; + } + + private static int BalanceHitCount(int hitCount, int maxHitCount) + { + return (hitCount << 8) / maxHitCount; + } + + private static int GetColorApproximateLuminosity(byte r, byte g, byte b) + { + return (r + g + b) / 3; + } + + private static int GetColorSaturation(PaletteColor color) + { + int cMax = Math.Max(Math.Max(color.R, color.G), color.B); + + if (cMax == 0) + { + return 0; + } + + int cMin = Math.Min(Math.Min(color.R, color.G), color.B); + int delta = cMax - cMin; + return (delta << 8) / cMax; + } + + private static int GetColorValue(PaletteColor color) + { + return Math.Max(Math.Max(color.R, color.G), color.B); + } + + private static int GetQuantizedColorKey(byte r, byte g, byte b) + { + int u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128; + int v = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128; + return (v >> UvQuantShift) | ((u >> UvQuantShift) << UvQuantBits); + } + } +} diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml b/src/Ryujinx/UI/Windows/MainWindow.axaml new file mode 100644 index 00000000..3a2e02c2 --- /dev/null +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs new file mode 100644 index 00000000..348412e7 --- /dev/null +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -0,0 +1,652 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; +using Avalonia.Platform; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using LibHac.Tools.FsSystem; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Applet; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.Gpu; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.Input.HLE; +using Ryujinx.Input.SDL2; +using Ryujinx.Modules; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common; +using Ryujinx.UI.Common.Configuration; +using Ryujinx.UI.Common.Helper; +using System; +using System.Collections.Generic; +using System.Runtime.Versioning; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class MainWindow : StyleableWindow + { + internal static MainWindowViewModel MainWindowViewModel { get; private set; } + + private bool _isLoading; + private bool _applicationsLoadedOnce; + + private UserChannelPersistence _userChannelPersistence; + private static bool _deferLoad; + private static string _launchPath; + private static string _launchApplicationId; + private static bool _startFullscreen; + internal readonly AvaHostUIHandler UiHandler; + + public VirtualFileSystem VirtualFileSystem { get; private set; } + public ContentManager ContentManager { get; private set; } + public AccountManager AccountManager { get; private set; } + + public LibHacHorizonManager LibHacHorizonManager { get; private set; } + + public InputManager InputManager { get; private set; } + + internal MainWindowViewModel ViewModel { get; private set; } + public SettingsWindow SettingsWindow { get; set; } + + public static bool ShowKeyErrorOnLoad { get; set; } + public ApplicationLibrary ApplicationLibrary { get; set; } + + public readonly double StatusBarHeight; + public readonly double MenuBarHeight; + + public MainWindow() + { + ViewModel = new MainWindowViewModel(); + + MainWindowViewModel = ViewModel; + + DataContext = ViewModel; + + InitializeComponent(); + Load(); + + UiHandler = new AvaHostUIHandler(this); + + ViewModel.Title = $"Ryujinx {Program.Version}"; + + // NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point. + StatusBarHeight = StatusBarView.StatusBar.MinHeight; + MenuBarHeight = MenuBar.MinHeight; + double barHeight = MenuBarHeight + StatusBarHeight; + Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight; + Width /= Program.WindowScaleFactor; + + SetWindowSizePosition(); + + if (Program.PreviewerDetached) + { + InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver()); + + this.GetObservable(IsActiveProperty).Subscribe(IsActiveChanged); + this.ScalingChanged += OnScalingChanged; + } + } + + /// + /// Event handler for detecting OS theme change when using "Follow OS theme" option + /// + private void OnPlatformColorValuesChanged(object sender, PlatformColorValues e) + { + if (Application.Current is App app) + { + app.ApplyConfiguredTheme(); + } + } + + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + if (PlatformSettings != null) + { + /// + /// Unsubscribe to the ColorValuesChanged event + /// + PlatformSettings.ColorValuesChanged -= OnPlatformColorValuesChanged; + } + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + NotificationHelper.SetNotificationManager(this); + } + + private void IsActiveChanged(bool obj) + { + ViewModel.IsActive = obj; + } + + private void OnScalingChanged(object sender, EventArgs e) + { + Program.DesktopScaleFactor = this.RenderScaling; + } + + private void ApplicationLibrary_ApplicationAdded(object sender, ApplicationAddedEventArgs e) + { + Dispatcher.UIThread.Post(() => + { + ViewModel.Applications.Add(e.AppData); + }); + } + + private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e) + { + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, e.NumAppsLoaded, e.NumAppsFound); + + Dispatcher.UIThread.Post(() => + { + ViewModel.StatusBarProgressValue = e.NumAppsLoaded; + ViewModel.StatusBarProgressMaximum = e.NumAppsFound; + + if (e.NumAppsFound == 0) + { + StatusBarView.LoadProgressBar.IsVisible = false; + } + + if (e.NumAppsLoaded == e.NumAppsFound) + { + StatusBarView.LoadProgressBar.IsVisible = false; + } + }); + } + + public void Application_Opened(object sender, ApplicationOpenedEventArgs args) + { + if (args.Application != null) + { + ViewModel.SelectedIcon = args.Application.Icon; + + ViewModel.LoadApplication(args.Application).Wait(); + } + + args.Handled = true; + } + + internal static void DeferLoadApplication(string launchPathArg, string launchApplicationId, bool startFullscreenArg) + { + _deferLoad = true; + _launchPath = launchPathArg; + _launchApplicationId = launchApplicationId; + _startFullscreen = startFullscreenArg; + } + + public void SwitchToGameControl(bool startFullscreen = false) + { + ViewModel.ShowLoadProgress = false; + ViewModel.ShowContent = true; + ViewModel.IsLoadingIndeterminate = false; + + if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen) + { + ViewModel.ToggleFullscreen(); + } + } + + public void ShowLoading(bool startFullscreen = false) + { + ViewModel.ShowContent = false; + ViewModel.ShowLoadProgress = true; + ViewModel.IsLoadingIndeterminate = true; + + if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen) + { + ViewModel.ToggleFullscreen(); + } + } + + private void Initialize() + { + _userChannelPersistence = new UserChannelPersistence(); + VirtualFileSystem = VirtualFileSystem.CreateInstance(); + LibHacHorizonManager = new LibHacHorizonManager(); + ContentManager = new ContentManager(VirtualFileSystem); + + LibHacHorizonManager.InitializeFsServer(VirtualFileSystem); + LibHacHorizonManager.InitializeArpServer(); + LibHacHorizonManager.InitializeBcatServer(); + LibHacHorizonManager.InitializeSystemClients(); + + IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks + ? IntegrityCheckLevel.ErrorOnInvalid + : IntegrityCheckLevel.None; + + ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel) + { + DesiredLanguage = ConfigurationState.Instance.System.Language, + }; + + // Save data created before we supported extra data in directory save data will not work properly if + // given empty extra data. Luckily some of that extra data can be created using the data from the + // save data indexer, which should be enough to check access permissions for user saves. + // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened. + // Consider removing this at some point in the future when we don't need to worry about old saves. + VirtualFileSystem.FixExtraData(LibHacHorizonManager.RyujinxClient); + + AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient, CommandLineState.Profile); + + VirtualFileSystem.ReloadKeySet(); + + ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient); + } + + [SupportedOSPlatform("linux")] + private static async Task ShowVmMaxMapCountWarning() + { + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary, + LinuxHelper.VmMaxMapCount, LinuxHelper.RecommendedVmMaxMapCount); + + await ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextPrimary], + LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary] + ); + } + + [SupportedOSPlatform("linux")] + private static async Task ShowVmMaxMapCountDialog() + { + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary, + LinuxHelper.RecommendedVmMaxMapCount); + + UserResult response = await ContentDialogHelper.ShowTextDialog( + $"Ryujinx - {LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTitle]}", + LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary], + LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextSecondary], + LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonUntilRestart], + LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonPersistent], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + (int)Symbol.Help + ); + + int rc; + + switch (response) + { + case UserResult.Ok: + rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}"); + if (rc == 0) + { + Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart."); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}"); + } + break; + case UserResult.No: + rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}"); + if (rc == 0) + { + Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}"); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}"); + } + break; + } + } + + private async Task CheckLaunchState() + { + if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount) + { + Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})"); + + if (LinuxHelper.PkExecPath is not null) + { + await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountDialog); + } + else + { + await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountWarning); + } + } + + if (!ShowKeyErrorOnLoad) + { + if (_deferLoad) + { + _deferLoad = false; + + if (ApplicationLibrary.TryGetApplicationsFromFile(_launchPath, out List applications)) + { + ApplicationData applicationData; + + if (_launchApplicationId != null) + { + applicationData = applications.Find(application => application.IdString == _launchApplicationId); + + if (applicationData != null) + { + await ViewModel.LoadApplication(applicationData, _startFullscreen); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Couldn't find requested application id '{_launchApplicationId}' in '{_launchPath}'."); + await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.ApplicationNotFound)); + } + } + else + { + applicationData = applications[0]; + await ViewModel.LoadApplication(applicationData, _startFullscreen); + } + } + else + { + Logger.Error?.Print(LogClass.Application, $"Couldn't find any application in '{_launchPath}'."); + await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.ApplicationNotFound)); + } + } + } + else + { + ShowKeyErrorOnLoad = false; + + await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys)); + } + + if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false)) + { + await Updater.BeginParse(this, false).ContinueWith(task => + { + Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"); + }, TaskContinuationOptions.OnlyOnFaulted); + } + } + + private void Load() + { + StatusBarView.VolumeStatus.Click += VolumeStatus_CheckedChanged; + + ApplicationGrid.ApplicationOpened += Application_Opened; + + ApplicationGrid.DataContext = ViewModel; + + ApplicationList.ApplicationOpened += Application_Opened; + + ApplicationList.DataContext = ViewModel; + } + + private void SetWindowSizePosition() + { + if (!ConfigurationState.Instance.RememberWindowState) + { + ViewModel.WindowHeight = (720 + StatusBarHeight + MenuBarHeight) * Program.WindowScaleFactor; + ViewModel.WindowWidth = 1280 * Program.WindowScaleFactor; + + WindowState = WindowState.Normal; + WindowStartupLocation = WindowStartupLocation.CenterScreen; + + return; + } + + PixelPoint savedPoint = new(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX, + ConfigurationState.Instance.UI.WindowStartup.WindowPositionY); + + ViewModel.WindowHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor; + ViewModel.WindowWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor; + + ViewModel.WindowState = ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value ? WindowState.Maximized : WindowState.Normal; + + if (CheckScreenBounds(savedPoint)) + { + Position = savedPoint; + } + else + { + WindowStartupLocation = WindowStartupLocation.CenterScreen; + } + } + + private bool CheckScreenBounds(PixelPoint configPoint) + { + for (int i = 0; i < Screens.ScreenCount; i++) + { + if (Screens.All[i].Bounds.Contains(configPoint)) + { + return true; + } + } + + Logger.Warning?.Print(LogClass.Application, "Failed to find valid start-up coordinates. Defaulting to primary monitor center."); + return false; + } + + private void SaveWindowSizePosition() + { + ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized; + + // Only save rectangle properties if the window is not in a maximized state. + if (WindowState != WindowState.Maximized) + { + ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)Height; + ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)Width; + + ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = Position.X; + ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y; + } + + MainWindowViewModel.SaveConfig(); + } + + protected override void OnOpened(EventArgs e) + { + base.OnOpened(e); + + Initialize(); + + /// + /// Subscribe to the ColorValuesChanged event + /// + PlatformSettings.ColorValuesChanged += OnPlatformColorValuesChanged; + + ViewModel.Initialize( + ContentManager, + StorageProvider, + ApplicationLibrary, + VirtualFileSystem, + AccountManager, + InputManager, + _userChannelPersistence, + LibHacHorizonManager, + UiHandler, + ShowLoading, + SwitchToGameControl, + SetMainContent, + this); + + ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated; + ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded; + + ViewModel.RefreshFirmwareStatus(); + + // Load applications if no application was requested by the command line + if (!_deferLoad) + { + LoadApplications(); + } + +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + CheckLaunchState(); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + } + + private void SetMainContent(Control content = null) + { + content ??= GameLibrary; + + if (MainContent.Content != content) + { + // Load applications while switching to the GameLibrary if we haven't done that yet + if (!_applicationsLoadedOnce && content == GameLibrary) + { + LoadApplications(); + } + + MainContent.Content = content; + } + } + + public static void UpdateGraphicsConfig() + { +#pragma warning disable IDE0055 // Disable formatting + GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale; + GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy; + GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; + GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache; + GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression; + GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE; +#pragma warning restore IDE0055 + } + + private void VolumeStatus_CheckedChanged(object sender, RoutedEventArgs e) + { + var volumeSplitButton = sender as ToggleSplitButton; + if (ViewModel.IsGameRunning) + { + if (!volumeSplitButton.IsChecked) + { + ViewModel.AppHost.Device.SetVolume(ViewModel.VolumeBeforeMute); + } + else + { + ViewModel.VolumeBeforeMute = ViewModel.AppHost.Device.GetVolume(); + ViewModel.AppHost.Device.SetVolume(0); + } + + ViewModel.Volume = ViewModel.AppHost.Device.GetVolume(); + } + } + + protected override void OnClosing(WindowClosingEventArgs e) + { + if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit) + { + e.Cancel = true; + + ConfirmExit(); + + return; + } + + ViewModel.IsClosing = true; + + if (ViewModel.AppHost != null) + { + ViewModel.AppHost.AppExit -= ViewModel.AppHost_AppExit; + ViewModel.AppHost.AppExit += (sender, e) => + { + ViewModel.AppHost = null; + + Dispatcher.UIThread.Post(() => + { + MainContent = null; + + Close(); + }); + }; + ViewModel.AppHost?.Stop(); + + e.Cancel = true; + + return; + } + + if (ConfigurationState.Instance.RememberWindowState) + { + SaveWindowSizePosition(); + } + + ApplicationLibrary.CancelLoading(); + InputManager.Dispose(); + Program.Exit(); + + base.OnClosing(e); + } + + private void ConfirmExit() + { + Dispatcher.UIThread.InvokeAsync(async () => + { + ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog(); + + if (ViewModel.IsClosing) + { + Close(); + } + }); + } + + public void LoadApplications() + { + _applicationsLoadedOnce = true; + ViewModel.Applications.Clear(); + + StatusBarView.LoadProgressBar.IsVisible = true; + ViewModel.StatusBarProgressMaximum = 0; + ViewModel.StatusBarProgressValue = 0; + + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0); + + ReloadGameList(); + } + + public void ToggleFileType(string fileType) + { + _ = fileType switch + { +#pragma warning disable IDE0055 // Disable formatting + "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), +#pragma warning restore IDE0055 + }; + + ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); + LoadApplications(); + } + + private void ReloadGameList() + { + if (_isLoading) + { + return; + } + + _isLoading = true; + + Thread applicationLibraryThread = new(() => + { + ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language; + ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs); + + _isLoading = false; + }) + { + Name = "GUI.ApplicationLibraryThread", + IsBackground = true, + }; + applicationLibraryThread.Start(); + } + } +} diff --git a/src/Ryujinx/UI/Windows/ModManagerWindow.axaml b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml new file mode 100644 index 00000000..0ed05ce3 --- /dev/null +++ b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs new file mode 100644 index 00000000..98f694db --- /dev/null +++ b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml.cs @@ -0,0 +1,139 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Styling; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.UI.Common.Helper; +using System.Threading.Tasks; +using Button = Avalonia.Controls.Button; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class ModManagerWindow : UserControl + { + public ModManagerViewModel ViewModel; + + public ModManagerWindow() + { + DataContext = this; + + InitializeComponent(); + } + + public ModManagerWindow(ulong titleId) + { + DataContext = ViewModel = new ModManagerViewModel(titleId); + + InitializeComponent(); + } + + public static async Task Show(ulong titleId, string titleName) + { + ContentDialog contentDialog = new() + { + PrimaryButtonText = "", + SecondaryButtonText = "", + CloseButtonText = "", + Content = new ModManagerWindow(titleId), + Title = string.Format(LocaleManager.Instance[LocaleKeys.ModWindowTitle], titleName, titleId.ToString("X16")), + }; + + Style bottomBorder = new(x => x.OfType().Name("DialogSpace").Child().OfType()); + bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false)); + + contentDialog.Styles.Add(bottomBorder); + + await contentDialog.ShowAsync(); + } + + private void SaveAndClose(object sender, RoutedEventArgs e) + { + ViewModel.Save(); + ((ContentDialog)Parent).Hide(); + } + + private void Close(object sender, RoutedEventArgs e) + { + ((ContentDialog)Parent).Hide(); + } + + private async void DeleteMod(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is ModModel model) + { + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogWarning], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogModManagerDeletionWarningMessage, model.Name), + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + ViewModel.Delete(model); + } + } + } + } + + private async void DeleteAll(object sender, RoutedEventArgs e) + { + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogWarning], + LocaleManager.Instance[LocaleKeys.DialogModManagerDeletionAllWarningMessage], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + + if (result == UserResult.Yes) + { + ViewModel.DeleteAll(); + } + } + + private void OpenLocation(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is ModModel model) + { + OpenHelper.OpenFolder(model.Path); + } + } + } + + private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + foreach (var content in e.AddedItems) + { + if (content is ModModel model) + { + var index = ViewModel.Mods.IndexOf(model); + + if (index != -1) + { + ViewModel.Mods[index].Enabled = true; + } + } + } + + foreach (var content in e.RemovedItems) + { + if (content is ModModel model) + { + var index = ViewModel.Mods.IndexOf(model); + + if (index != -1) + { + ViewModel.Mods[index].Enabled = false; + } + } + } + } + } +} diff --git a/src/Ryujinx/UI/Windows/SettingsWindow.axaml b/src/Ryujinx/UI/Windows/SettingsWindow.axaml new file mode 100644 index 00000000..de3c2291 --- /dev/null +++ b/src/Ryujinx/UI/Windows/SettingsWindow.axaml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs new file mode 100644 index 00000000..af917e7f --- /dev/null +++ b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml.cs @@ -0,0 +1,103 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Interactivity; +using Avalonia.Styling; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.HLE.FileSystem; +using Ryujinx.UI.App.Common; +using Ryujinx.UI.Common.Helper; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Windows +{ + public partial class TitleUpdateWindow : UserControl + { + public readonly TitleUpdateViewModel ViewModel; + + public TitleUpdateWindow() + { + DataContext = this; + + InitializeComponent(); + } + + public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ApplicationData applicationData) + { + DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, applicationData); + + InitializeComponent(); + } + + public static async Task Show(VirtualFileSystem virtualFileSystem, ApplicationData applicationData) + { + ContentDialog contentDialog = new() + { + PrimaryButtonText = "", + SecondaryButtonText = "", + CloseButtonText = "", + Content = new TitleUpdateWindow(virtualFileSystem, applicationData), + Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, applicationData.Name, applicationData.IdBaseString), + }; + + Style bottomBorder = new(x => x.OfType().Name("DialogSpace").Child().OfType()); + bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false)); + + contentDialog.Styles.Add(bottomBorder); + + await contentDialog.ShowAsync(); + } + + private void Close(object sender, RoutedEventArgs e) + { + ((ContentDialog)Parent).Hide(); + } + + public void Save(object sender, RoutedEventArgs e) + { + ViewModel.Save(); + + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al) + { + foreach (Window window in al.Windows) + { + if (window is MainWindow mainWindow) + { + mainWindow.LoadApplications(); + } + } + } + + ((ContentDialog)Parent).Hide(); + } + + private void OpenLocation(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + if (button.DataContext is TitleUpdateModel model) + { + OpenHelper.LocateFile(model.Path); + } + } + } + + private void RemoveUpdate(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + ViewModel.RemoveUpdate((TitleUpdateModel)button.DataContext); + } + } + + private void RemoveAll(object sender, RoutedEventArgs e) + { + ViewModel.TitleUpdates.Clear(); + + ViewModel.SortUpdates(); + } + } +} diff --git a/src/Ryujinx/app.manifest b/src/Ryujinx/app.manifest new file mode 100644 index 00000000..920136fb --- /dev/null +++ b/src/Ryujinx/app.manifest @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/Spv.Generator/Autogenerated/CoreGrammar.cs b/src/Spv.Generator/Autogenerated/CoreGrammar.cs new file mode 100644 index 00000000..37936b8e --- /dev/null +++ b/src/Spv.Generator/Autogenerated/CoreGrammar.cs @@ -0,0 +1,5315 @@ +// AUTOGENERATED: DO NOT EDIT +// Last update date: 2021-01-06 23:02:26.837899 +#region Grammar License +// Copyright (c) 2014-2020 The Khronos Group Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and/or associated documentation files (the "Materials"), +// to deal in the Materials without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Materials, and to permit persons to whom the +// Materials are furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Materials. +// +// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +// STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +// HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +// +// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +// IN THE MATERIALS. +#endregion + +using static Spv.Specification; + +namespace Spv.Generator +{ + public partial class Module + { + // Miscellaneous + + public Instruction Nop() + { + Instruction result = NewInstruction(Op.OpNop); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Undef(Instruction resultType) + { + Instruction result = NewInstruction(Op.OpUndef, GetNewId(), resultType); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SizeOf(Instruction resultType, Instruction pointer) + { + Instruction result = NewInstruction(Op.OpSizeOf, GetNewId(), resultType); + + result.AddOperand(pointer); + AddToFunctionDefinitions(result); + + return result; + } + + // Debug + + public Instruction SourceContinued(string continuedSource) + { + Instruction result = NewInstruction(Op.OpSourceContinued); + + result.AddOperand(continuedSource); + AddDebug(result); + + return result; + } + + public Instruction Source(SourceLanguage sourceLanguage, LiteralInteger version, Instruction file = null, string source = null) + { + Instruction result = NewInstruction(Op.OpSource); + + result.AddOperand(sourceLanguage); + result.AddOperand(version); + if (file != null) + { + result.AddOperand(file); + } + if (source != null) + { + result.AddOperand(source); + } + AddDebug(result); + + return result; + } + + public Instruction SourceExtension(string extension) + { + Instruction result = NewInstruction(Op.OpSourceExtension); + + result.AddOperand(extension); + AddDebug(result); + + return result; + } + + public Instruction Name(Instruction target, string name) + { + Instruction result = NewInstruction(Op.OpName); + + result.AddOperand(target); + result.AddOperand(name); + AddDebug(result); + + return result; + } + + public Instruction MemberName(Instruction type, LiteralInteger member, string name) + { + Instruction result = NewInstruction(Op.OpMemberName); + + result.AddOperand(type); + result.AddOperand(member); + result.AddOperand(name); + AddDebug(result); + + return result; + } + + public Instruction String(string str) + { + Instruction result = NewInstruction(Op.OpString, GetNewId()); + + result.AddOperand(str); + AddDebug(result); + + return result; + } + + public Instruction Line(Instruction file, LiteralInteger line, LiteralInteger column) + { + Instruction result = NewInstruction(Op.OpLine); + + result.AddOperand(file); + result.AddOperand(line); + result.AddOperand(column); + AddDebug(result); + + return result; + } + + public Instruction NoLine() + { + Instruction result = NewInstruction(Op.OpNoLine); + + AddDebug(result); + + return result; + } + + public Instruction ModuleProcessed(string process) + { + Instruction result = NewInstruction(Op.OpModuleProcessed); + + result.AddOperand(process); + AddDebug(result); + + return result; + } + + // Annotation + + public Instruction Decorate(Instruction target, Decoration decoration) + { + Instruction result = NewInstruction(Op.OpDecorate); + + result.AddOperand(target); + result.AddOperand(decoration); + AddAnnotation(result); + + return result; + } + + public Instruction Decorate(Instruction target, Decoration decoration, IOperand parameter) + { + Instruction result = NewInstruction(Op.OpDecorate); + + result.AddOperand(target); + result.AddOperand(decoration); + result.AddOperand(parameter); + AddAnnotation(result); + + return result; + } + + public Instruction Decorate(Instruction target, Decoration decoration, params IOperand[] parameters) + { + Instruction result = NewInstruction(Op.OpDecorate); + + result.AddOperand(target); + result.AddOperand(decoration); + result.AddOperand(parameters); + AddAnnotation(result); + + return result; + } + + public Instruction MemberDecorate(Instruction structureType, LiteralInteger member, Decoration decoration) + { + Instruction result = NewInstruction(Op.OpMemberDecorate); + + result.AddOperand(structureType); + result.AddOperand(member); + result.AddOperand(decoration); + AddAnnotation(result); + + return result; + } + + public Instruction MemberDecorate(Instruction structureType, LiteralInteger member, Decoration decoration, IOperand parameter) + { + Instruction result = NewInstruction(Op.OpMemberDecorate); + + result.AddOperand(structureType); + result.AddOperand(member); + result.AddOperand(decoration); + result.AddOperand(parameter); + AddAnnotation(result); + + return result; + } + + public Instruction MemberDecorate(Instruction structureType, LiteralInteger member, Decoration decoration, params IOperand[] parameters) + { + Instruction result = NewInstruction(Op.OpMemberDecorate); + + result.AddOperand(structureType); + result.AddOperand(member); + result.AddOperand(decoration); + result.AddOperand(parameters); + AddAnnotation(result); + + return result; + } + + public Instruction DecorationGroup() + { + Instruction result = NewInstruction(Op.OpDecorationGroup, GetNewId()); + + AddAnnotation(result); + + return result; + } + + public Instruction GroupDecorate(Instruction decorationGroup, params Instruction[] targets) + { + Instruction result = NewInstruction(Op.OpGroupDecorate); + + result.AddOperand(decorationGroup); + result.AddOperand(targets); + AddAnnotation(result); + + return result; + } + + public Instruction GroupMemberDecorate(Instruction decorationGroup, params IOperand[] targets) + { + Instruction result = NewInstruction(Op.OpGroupMemberDecorate); + + result.AddOperand(decorationGroup); + result.AddOperand(targets); + AddAnnotation(result); + + return result; + } + + public Instruction DecorateId(Instruction target, Decoration decoration, params IOperand[] parameters) + { + Instruction result = NewInstruction(Op.OpDecorateId); + + result.AddOperand(target); + result.AddOperand(decoration); + result.AddOperand(parameters); + AddAnnotation(result); + + return result; + } + + public Instruction DecorateString(Instruction target, Decoration decoration, params IOperand[] parameters) + { + Instruction result = NewInstruction(Op.OpDecorateString); + + result.AddOperand(target); + result.AddOperand(decoration); + result.AddOperand(parameters); + AddAnnotation(result); + + return result; + } + + public Instruction DecorateStringGOOGLE(Instruction target, Decoration decoration, params IOperand[] parameters) + { + Instruction result = NewInstruction(Op.OpDecorateStringGOOGLE); + + result.AddOperand(target); + result.AddOperand(decoration); + result.AddOperand(parameters); + AddAnnotation(result); + + return result; + } + + public Instruction MemberDecorateString(Instruction structType, LiteralInteger member, Decoration decoration, params IOperand[] parameters) + { + Instruction result = NewInstruction(Op.OpMemberDecorateString); + + result.AddOperand(structType); + result.AddOperand(member); + result.AddOperand(decoration); + result.AddOperand(parameters); + AddAnnotation(result); + + return result; + } + + public Instruction MemberDecorateStringGOOGLE(Instruction structType, LiteralInteger member, Decoration decoration, params IOperand[] parameters) + { + Instruction result = NewInstruction(Op.OpMemberDecorateStringGOOGLE); + + result.AddOperand(structType); + result.AddOperand(member); + result.AddOperand(decoration); + result.AddOperand(parameters); + AddAnnotation(result); + + return result; + } + + // Type-Declaration + + public Instruction TypeVoid(bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeVoid); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeBool(bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeBool); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeInt(LiteralInteger width, LiteralInteger signedness, bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeInt); + + result.AddOperand(width); + result.AddOperand(signedness); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeFloat(LiteralInteger width, bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeFloat); + + result.AddOperand(width); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeVector(Instruction componentType, LiteralInteger componentCount, bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeVector); + + result.AddOperand(componentType); + result.AddOperand(componentCount); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeMatrix(Instruction columnType, LiteralInteger columnCount, bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeMatrix); + + result.AddOperand(columnType); + result.AddOperand(columnCount); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeImage(Instruction sampledType, Dim dim, LiteralInteger depth, LiteralInteger arrayed, LiteralInteger mS, LiteralInteger sampled, ImageFormat imageFormat, AccessQualifier accessQualifier = (AccessQualifier)int.MaxValue, bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeImage); + + result.AddOperand(sampledType); + result.AddOperand(dim); + result.AddOperand(depth); + result.AddOperand(arrayed); + result.AddOperand(mS); + result.AddOperand(sampled); + result.AddOperand(imageFormat); + if (accessQualifier != (AccessQualifier)int.MaxValue) + { + result.AddOperand(accessQualifier); + } + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeSampler(bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeSampler); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeSampledImage(Instruction imageType, bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeSampledImage); + + result.AddOperand(imageType); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeArray(Instruction elementType, Instruction length, bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeArray); + + result.AddOperand(elementType); + result.AddOperand(length); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeRuntimeArray(Instruction elementType, bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeRuntimeArray); + + result.AddOperand(elementType); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeStruct(bool forceIdAllocation, params Instruction[] parameters) + { + Instruction result = NewInstruction(Op.OpTypeStruct); + + result.AddOperand(parameters); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeOpaque(string thenameoftheopaquetype, bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeOpaque); + + result.AddOperand(thenameoftheopaquetype); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypePointer(StorageClass storageClass, Instruction type, bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypePointer); + + result.AddOperand(storageClass); + result.AddOperand(type); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeFunction(Instruction returnType, bool forceIdAllocation, params Instruction[] parameters) + { + Instruction result = NewInstruction(Op.OpTypeFunction); + + result.AddOperand(returnType); + result.AddOperand(parameters); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeEvent(bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeEvent); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeDeviceEvent(bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeDeviceEvent); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeReserveId(bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeReserveId); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeQueue(bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeQueue); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypePipe(AccessQualifier qualifier, bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypePipe); + + result.AddOperand(qualifier); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeForwardPointer(Instruction pointerType, StorageClass storageClass, bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeForwardPointer); + + result.AddOperand(pointerType); + result.AddOperand(storageClass); + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypePipeStorage(bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypePipeStorage); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + public Instruction TypeNamedBarrier(bool forceIdAllocation = false) + { + Instruction result = NewInstruction(Op.OpTypeNamedBarrier); + + AddTypeDeclaration(result, forceIdAllocation); + + return result; + } + + // Constant-Creation + + public Instruction ConstantTrue(Instruction resultType) + { + Instruction result = NewInstruction(Op.OpConstantTrue, Instruction.InvalidId, resultType); + + AddConstant(result); + + return result; + } + + public Instruction ConstantFalse(Instruction resultType) + { + Instruction result = NewInstruction(Op.OpConstantFalse, Instruction.InvalidId, resultType); + + AddConstant(result); + + return result; + } + + public Instruction Constant(Instruction resultType, LiteralInteger value) + { + Instruction result = NewInstruction(Op.OpConstant, Instruction.InvalidId, resultType); + + result.AddOperand(value); + AddConstant(result); + + return result; + } + + public Instruction ConstantComposite(Instruction resultType, params Instruction[] constituents) + { + Instruction result = NewInstruction(Op.OpConstantComposite, Instruction.InvalidId, resultType); + + result.AddOperand(constituents); + AddConstant(result); + + return result; + } + + public Instruction ConstantSampler(Instruction resultType, SamplerAddressingMode samplerAddressingMode, LiteralInteger param, SamplerFilterMode samplerFilterMode) + { + Instruction result = NewInstruction(Op.OpConstantSampler, Instruction.InvalidId, resultType); + + result.AddOperand(samplerAddressingMode); + result.AddOperand(param); + result.AddOperand(samplerFilterMode); + AddConstant(result); + + return result; + } + + public Instruction ConstantNull(Instruction resultType) + { + Instruction result = NewInstruction(Op.OpConstantNull, Instruction.InvalidId, resultType); + + AddConstant(result); + + return result; + } + + public Instruction SpecConstantTrue(Instruction resultType) + { + Instruction result = NewInstruction(Op.OpSpecConstantTrue, GetNewId(), resultType); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SpecConstantFalse(Instruction resultType) + { + Instruction result = NewInstruction(Op.OpSpecConstantFalse, GetNewId(), resultType); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SpecConstant(Instruction resultType, LiteralInteger value) + { + Instruction result = NewInstruction(Op.OpSpecConstant, GetNewId(), resultType); + + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SpecConstantComposite(Instruction resultType, params Instruction[] constituents) + { + Instruction result = NewInstruction(Op.OpSpecConstantComposite, GetNewId(), resultType); + + result.AddOperand(constituents); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SpecConstantOp(Instruction resultType, LiteralInteger opcode) + { + Instruction result = NewInstruction(Op.OpSpecConstantOp, GetNewId(), resultType); + + result.AddOperand(opcode); + AddToFunctionDefinitions(result); + + return result; + } + + // Memory + + public Instruction Variable(Instruction resultType, StorageClass storageClass, Instruction initializer = null) + { + Instruction result = NewInstruction(Op.OpVariable, GetNewId(), resultType); + + result.AddOperand(storageClass); + if (initializer != null) + { + result.AddOperand(initializer); + } + return result; + } + + public Instruction ImageTexelPointer(Instruction resultType, Instruction image, Instruction coordinate, Instruction sample) + { + Instruction result = NewInstruction(Op.OpImageTexelPointer, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + result.AddOperand(sample); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Load(Instruction resultType, Instruction pointer, MemoryAccessMask memoryAccess = (MemoryAccessMask)int.MaxValue) + { + Instruction result = NewInstruction(Op.OpLoad, GetNewId(), resultType); + + result.AddOperand(pointer); + if (memoryAccess != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Store(Instruction pointer, Instruction obj, MemoryAccessMask memoryAccess = (MemoryAccessMask)int.MaxValue) + { + Instruction result = NewInstruction(Op.OpStore); + + result.AddOperand(pointer); + result.AddOperand(obj); + if (memoryAccess != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CopyMemory(Instruction target, Instruction source, MemoryAccessMask memoryAccess0 = (MemoryAccessMask)int.MaxValue, MemoryAccessMask memoryAccess1 = (MemoryAccessMask)int.MaxValue) + { + Instruction result = NewInstruction(Op.OpCopyMemory); + + result.AddOperand(target); + result.AddOperand(source); + if (memoryAccess0 != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess0); + } + if (memoryAccess1 != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess1); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CopyMemorySized(Instruction target, Instruction source, Instruction size, MemoryAccessMask memoryAccess0 = (MemoryAccessMask)int.MaxValue, MemoryAccessMask memoryAccess1 = (MemoryAccessMask)int.MaxValue) + { + Instruction result = NewInstruction(Op.OpCopyMemorySized); + + result.AddOperand(target); + result.AddOperand(source); + result.AddOperand(size); + if (memoryAccess0 != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess0); + } + if (memoryAccess1 != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess1); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AccessChain(Instruction resultType, Instruction baseObj, Instruction index) + { + Instruction result = NewInstruction(Op.OpAccessChain, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(index); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AccessChain(Instruction resultType, Instruction baseObj, Instruction index0, Instruction index1) + { + Instruction result = NewInstruction(Op.OpAccessChain, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(index0); + result.AddOperand(index1); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AccessChain(Instruction resultType, Instruction baseObj, Instruction index0, Instruction index1, Instruction index2) + { + Instruction result = NewInstruction(Op.OpAccessChain, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(index0); + result.AddOperand(index1); + result.AddOperand(index2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AccessChain(Instruction resultType, Instruction baseObj, params Instruction[] indexes) + { + Instruction result = NewInstruction(Op.OpAccessChain, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(indexes); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction InBoundsAccessChain(Instruction resultType, Instruction baseObj, params Instruction[] indexes) + { + Instruction result = NewInstruction(Op.OpInBoundsAccessChain, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(indexes); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction PtrAccessChain(Instruction resultType, Instruction baseObj, Instruction element, params Instruction[] indexes) + { + Instruction result = NewInstruction(Op.OpPtrAccessChain, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(element); + result.AddOperand(indexes); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ArrayLength(Instruction resultType, Instruction structure, LiteralInteger arraymember) + { + Instruction result = NewInstruction(Op.OpArrayLength, GetNewId(), resultType); + + result.AddOperand(structure); + result.AddOperand(arraymember); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GenericPtrMemSemantics(Instruction resultType, Instruction pointer) + { + Instruction result = NewInstruction(Op.OpGenericPtrMemSemantics, GetNewId(), resultType); + + result.AddOperand(pointer); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction InBoundsPtrAccessChain(Instruction resultType, Instruction baseObj, Instruction element, params Instruction[] indexes) + { + Instruction result = NewInstruction(Op.OpInBoundsPtrAccessChain, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(element); + result.AddOperand(indexes); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction PtrEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpPtrEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction PtrNotEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpPtrNotEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction PtrDiff(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpPtrDiff, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + // Function + + public Instruction Function(Instruction resultType, FunctionControlMask functionControl, Instruction functionType) + { + Instruction result = NewInstruction(Op.OpFunction, GetNewId(), resultType); + + result.AddOperand(functionControl); + result.AddOperand(functionType); + + return result; + } + + public void AddFunction(Instruction function) + { + AddToFunctionDefinitions(function); + } + + public Instruction FunctionParameter(Instruction resultType) + { + Instruction result = NewInstruction(Op.OpFunctionParameter, GetNewId(), resultType); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FunctionEnd() + { + Instruction result = NewInstruction(Op.OpFunctionEnd); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FunctionCall(Instruction resultType, Instruction function, params Instruction[] parameters) + { + Instruction result = NewInstruction(Op.OpFunctionCall, GetNewId(), resultType); + + result.AddOperand(function); + result.AddOperand(parameters); + AddToFunctionDefinitions(result); + + return result; + } + + // Image + + public Instruction SampledImage(Instruction resultType, Instruction image, Instruction sampler) + { + Instruction result = NewInstruction(Op.OpSampledImage, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(sampler); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSampleImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSampleExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(imageOperands); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleDrefImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSampleDrefImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleDrefExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSampleDrefExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + result.AddOperand(imageOperands); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleProjImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSampleProjImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleProjExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSampleProjExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(imageOperands); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleProjDrefImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSampleProjDrefImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleProjDrefExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSampleProjDrefExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + result.AddOperand(imageOperands); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageFetch(Instruction resultType, Instruction image, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageFetch, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageGather(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction component, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageGather, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(component); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageDrefGather(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageDrefGather, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageRead(Instruction resultType, Instruction image, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageRead, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageWrite(Instruction image, Instruction coordinate, Instruction texel, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageWrite); + + result.AddOperand(image); + result.AddOperand(coordinate); + result.AddOperand(texel); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Image(Instruction resultType, Instruction sampledImage) + { + Instruction result = NewInstruction(Op.OpImage, GetNewId(), resultType); + + result.AddOperand(sampledImage); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageQueryFormat(Instruction resultType, Instruction image) + { + Instruction result = NewInstruction(Op.OpImageQueryFormat, GetNewId(), resultType); + + result.AddOperand(image); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageQueryOrder(Instruction resultType, Instruction image) + { + Instruction result = NewInstruction(Op.OpImageQueryOrder, GetNewId(), resultType); + + result.AddOperand(image); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageQuerySizeLod(Instruction resultType, Instruction image, Instruction levelofDetail) + { + Instruction result = NewInstruction(Op.OpImageQuerySizeLod, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(levelofDetail); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageQuerySize(Instruction resultType, Instruction image) + { + Instruction result = NewInstruction(Op.OpImageQuerySize, GetNewId(), resultType); + + result.AddOperand(image); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageQueryLod(Instruction resultType, Instruction sampledImage, Instruction coordinate) + { + Instruction result = NewInstruction(Op.OpImageQueryLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageQueryLevels(Instruction resultType, Instruction image) + { + Instruction result = NewInstruction(Op.OpImageQueryLevels, GetNewId(), resultType); + + result.AddOperand(image); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageQuerySamples(Instruction resultType, Instruction image) + { + Instruction result = NewInstruction(Op.OpImageQuerySamples, GetNewId(), resultType); + + result.AddOperand(image); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSparseSampleImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSparseSampleExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(imageOperands); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleDrefImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSparseSampleDrefImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleDrefExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSparseSampleDrefExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + result.AddOperand(imageOperands); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleProjImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSparseSampleProjImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleProjExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSparseSampleProjExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(imageOperands); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleProjDrefImplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSparseSampleProjDrefImplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseSampleProjDrefExplicitLod(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSparseSampleProjDrefExplicitLod, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + result.AddOperand(imageOperands); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseFetch(Instruction resultType, Instruction image, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSparseFetch, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseGather(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction component, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSparseGather, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(component); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseDrefGather(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction dRef, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSparseDrefGather, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(dRef); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseTexelsResident(Instruction resultType, Instruction residentCode) + { + Instruction result = NewInstruction(Op.OpImageSparseTexelsResident, GetNewId(), resultType); + + result.AddOperand(residentCode); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSparseRead(Instruction resultType, Instruction image, Instruction coordinate, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSparseRead, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ImageSampleFootprintNV(Instruction resultType, Instruction sampledImage, Instruction coordinate, Instruction granularity, Instruction coarse, ImageOperandsMask imageOperands, params Instruction[] imageOperandIds) + { + Instruction result = NewInstruction(Op.OpImageSampleFootprintNV, GetNewId(), resultType); + + result.AddOperand(sampledImage); + result.AddOperand(coordinate); + result.AddOperand(granularity); + result.AddOperand(coarse); + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperands); + } + if (imageOperands != (ImageOperandsMask)int.MaxValue) + { + result.AddOperand(imageOperandIds); + } + AddToFunctionDefinitions(result); + + return result; + } + + // Conversion + + public Instruction ConvertFToU(Instruction resultType, Instruction floatValue) + { + Instruction result = NewInstruction(Op.OpConvertFToU, GetNewId(), resultType); + + result.AddOperand(floatValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ConvertFToS(Instruction resultType, Instruction floatValue) + { + Instruction result = NewInstruction(Op.OpConvertFToS, GetNewId(), resultType); + + result.AddOperand(floatValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ConvertSToF(Instruction resultType, Instruction signedValue) + { + Instruction result = NewInstruction(Op.OpConvertSToF, GetNewId(), resultType); + + result.AddOperand(signedValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ConvertUToF(Instruction resultType, Instruction unsignedValue) + { + Instruction result = NewInstruction(Op.OpConvertUToF, GetNewId(), resultType); + + result.AddOperand(unsignedValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UConvert(Instruction resultType, Instruction unsignedValue) + { + Instruction result = NewInstruction(Op.OpUConvert, GetNewId(), resultType); + + result.AddOperand(unsignedValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SConvert(Instruction resultType, Instruction signedValue) + { + Instruction result = NewInstruction(Op.OpSConvert, GetNewId(), resultType); + + result.AddOperand(signedValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FConvert(Instruction resultType, Instruction floatValue) + { + Instruction result = NewInstruction(Op.OpFConvert, GetNewId(), resultType); + + result.AddOperand(floatValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction QuantizeToF16(Instruction resultType, Instruction value) + { + Instruction result = NewInstruction(Op.OpQuantizeToF16, GetNewId(), resultType); + + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ConvertPtrToU(Instruction resultType, Instruction pointer) + { + Instruction result = NewInstruction(Op.OpConvertPtrToU, GetNewId(), resultType); + + result.AddOperand(pointer); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SatConvertSToU(Instruction resultType, Instruction signedValue) + { + Instruction result = NewInstruction(Op.OpSatConvertSToU, GetNewId(), resultType); + + result.AddOperand(signedValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SatConvertUToS(Instruction resultType, Instruction unsignedValue) + { + Instruction result = NewInstruction(Op.OpSatConvertUToS, GetNewId(), resultType); + + result.AddOperand(unsignedValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ConvertUToPtr(Instruction resultType, Instruction integerValue) + { + Instruction result = NewInstruction(Op.OpConvertUToPtr, GetNewId(), resultType); + + result.AddOperand(integerValue); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction PtrCastToGeneric(Instruction resultType, Instruction pointer) + { + Instruction result = NewInstruction(Op.OpPtrCastToGeneric, GetNewId(), resultType); + + result.AddOperand(pointer); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GenericCastToPtr(Instruction resultType, Instruction pointer) + { + Instruction result = NewInstruction(Op.OpGenericCastToPtr, GetNewId(), resultType); + + result.AddOperand(pointer); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GenericCastToPtrExplicit(Instruction resultType, Instruction pointer, StorageClass storage) + { + Instruction result = NewInstruction(Op.OpGenericCastToPtrExplicit, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(storage); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Bitcast(Instruction resultType, Instruction operand) + { + Instruction result = NewInstruction(Op.OpBitcast, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + // Composite + + public Instruction VectorExtractDynamic(Instruction resultType, Instruction vector, Instruction index) + { + Instruction result = NewInstruction(Op.OpVectorExtractDynamic, GetNewId(), resultType); + + result.AddOperand(vector); + result.AddOperand(index); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction VectorInsertDynamic(Instruction resultType, Instruction vector, Instruction component, Instruction index) + { + Instruction result = NewInstruction(Op.OpVectorInsertDynamic, GetNewId(), resultType); + + result.AddOperand(vector); + result.AddOperand(component); + result.AddOperand(index); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction VectorShuffle(Instruction resultType, Instruction vector1, Instruction vector2, params LiteralInteger[] components) + { + Instruction result = NewInstruction(Op.OpVectorShuffle, GetNewId(), resultType); + + result.AddOperand(vector1); + result.AddOperand(vector2); + result.AddOperand(components); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CompositeConstruct(Instruction resultType, params Instruction[] constituents) + { + Instruction result = NewInstruction(Op.OpCompositeConstruct, GetNewId(), resultType); + + result.AddOperand(constituents); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CompositeExtract(Instruction resultType, Instruction composite, params LiteralInteger[] indexes) + { + Instruction result = NewInstruction(Op.OpCompositeExtract, GetNewId(), resultType); + + result.AddOperand(composite); + result.AddOperand(indexes); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CompositeInsert(Instruction resultType, Instruction obj, Instruction composite, params LiteralInteger[] indexes) + { + Instruction result = NewInstruction(Op.OpCompositeInsert, GetNewId(), resultType); + + result.AddOperand(obj); + result.AddOperand(composite); + result.AddOperand(indexes); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CopyObject(Instruction resultType, Instruction operand) + { + Instruction result = NewInstruction(Op.OpCopyObject, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Transpose(Instruction resultType, Instruction matrix) + { + Instruction result = NewInstruction(Op.OpTranspose, GetNewId(), resultType); + + result.AddOperand(matrix); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CopyLogical(Instruction resultType, Instruction operand) + { + Instruction result = NewInstruction(Op.OpCopyLogical, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + // Arithmetic + + public Instruction SNegate(Instruction resultType, Instruction operand) + { + Instruction result = NewInstruction(Op.OpSNegate, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FNegate(Instruction resultType, Instruction operand) + { + Instruction result = NewInstruction(Op.OpFNegate, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IAdd(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpIAdd, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FAdd(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFAdd, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ISub(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpISub, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FSub(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFSub, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IMul(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpIMul, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FMul(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFMul, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UDiv(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpUDiv, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SDiv(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpSDiv, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FDiv(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFDiv, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UMod(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpUMod, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SRem(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpSRem, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SMod(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpSMod, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FRem(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFRem, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FMod(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFMod, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction VectorTimesScalar(Instruction resultType, Instruction vector, Instruction scalar) + { + Instruction result = NewInstruction(Op.OpVectorTimesScalar, GetNewId(), resultType); + + result.AddOperand(vector); + result.AddOperand(scalar); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction MatrixTimesScalar(Instruction resultType, Instruction matrix, Instruction scalar) + { + Instruction result = NewInstruction(Op.OpMatrixTimesScalar, GetNewId(), resultType); + + result.AddOperand(matrix); + result.AddOperand(scalar); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction VectorTimesMatrix(Instruction resultType, Instruction vector, Instruction matrix) + { + Instruction result = NewInstruction(Op.OpVectorTimesMatrix, GetNewId(), resultType); + + result.AddOperand(vector); + result.AddOperand(matrix); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction MatrixTimesVector(Instruction resultType, Instruction matrix, Instruction vector) + { + Instruction result = NewInstruction(Op.OpMatrixTimesVector, GetNewId(), resultType); + + result.AddOperand(matrix); + result.AddOperand(vector); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction MatrixTimesMatrix(Instruction resultType, Instruction leftMatrix, Instruction rightMatrix) + { + Instruction result = NewInstruction(Op.OpMatrixTimesMatrix, GetNewId(), resultType); + + result.AddOperand(leftMatrix); + result.AddOperand(rightMatrix); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction OuterProduct(Instruction resultType, Instruction vector1, Instruction vector2) + { + Instruction result = NewInstruction(Op.OpOuterProduct, GetNewId(), resultType); + + result.AddOperand(vector1); + result.AddOperand(vector2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Dot(Instruction resultType, Instruction vector1, Instruction vector2) + { + Instruction result = NewInstruction(Op.OpDot, GetNewId(), resultType); + + result.AddOperand(vector1); + result.AddOperand(vector2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IAddCarry(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpIAddCarry, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ISubBorrow(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpISubBorrow, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UMulExtended(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpUMulExtended, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SMulExtended(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpSMulExtended, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + // Bit + + public Instruction ShiftRightLogical(Instruction resultType, Instruction baseObj, Instruction shift) + { + Instruction result = NewInstruction(Op.OpShiftRightLogical, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(shift); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ShiftRightArithmetic(Instruction resultType, Instruction baseObj, Instruction shift) + { + Instruction result = NewInstruction(Op.OpShiftRightArithmetic, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(shift); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ShiftLeftLogical(Instruction resultType, Instruction baseObj, Instruction shift) + { + Instruction result = NewInstruction(Op.OpShiftLeftLogical, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(shift); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitwiseOr(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpBitwiseOr, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitwiseXor(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpBitwiseXor, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitwiseAnd(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpBitwiseAnd, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Not(Instruction resultType, Instruction operand) + { + Instruction result = NewInstruction(Op.OpNot, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitFieldInsert(Instruction resultType, Instruction baseObj, Instruction insert, Instruction offset, Instruction count) + { + Instruction result = NewInstruction(Op.OpBitFieldInsert, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(insert); + result.AddOperand(offset); + result.AddOperand(count); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitFieldSExtract(Instruction resultType, Instruction baseObj, Instruction offset, Instruction count) + { + Instruction result = NewInstruction(Op.OpBitFieldSExtract, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(offset); + result.AddOperand(count); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitFieldUExtract(Instruction resultType, Instruction baseObj, Instruction offset, Instruction count) + { + Instruction result = NewInstruction(Op.OpBitFieldUExtract, GetNewId(), resultType); + + result.AddOperand(baseObj); + result.AddOperand(offset); + result.AddOperand(count); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitReverse(Instruction resultType, Instruction baseObj) + { + Instruction result = NewInstruction(Op.OpBitReverse, GetNewId(), resultType); + + result.AddOperand(baseObj); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BitCount(Instruction resultType, Instruction baseObj) + { + Instruction result = NewInstruction(Op.OpBitCount, GetNewId(), resultType); + + result.AddOperand(baseObj); + AddToFunctionDefinitions(result); + + return result; + } + + // Relational_and_Logical + + public Instruction Any(Instruction resultType, Instruction vector) + { + Instruction result = NewInstruction(Op.OpAny, GetNewId(), resultType); + + result.AddOperand(vector); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction All(Instruction resultType, Instruction vector) + { + Instruction result = NewInstruction(Op.OpAll, GetNewId(), resultType); + + result.AddOperand(vector); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IsNan(Instruction resultType, Instruction x) + { + Instruction result = NewInstruction(Op.OpIsNan, GetNewId(), resultType); + + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IsInf(Instruction resultType, Instruction x) + { + Instruction result = NewInstruction(Op.OpIsInf, GetNewId(), resultType); + + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IsFinite(Instruction resultType, Instruction x) + { + Instruction result = NewInstruction(Op.OpIsFinite, GetNewId(), resultType); + + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IsNormal(Instruction resultType, Instruction x) + { + Instruction result = NewInstruction(Op.OpIsNormal, GetNewId(), resultType); + + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SignBitSet(Instruction resultType, Instruction x) + { + Instruction result = NewInstruction(Op.OpSignBitSet, GetNewId(), resultType); + + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LessOrGreater(Instruction resultType, Instruction x, Instruction y) + { + Instruction result = NewInstruction(Op.OpLessOrGreater, GetNewId(), resultType); + + result.AddOperand(x); + result.AddOperand(y); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Ordered(Instruction resultType, Instruction x, Instruction y) + { + Instruction result = NewInstruction(Op.OpOrdered, GetNewId(), resultType); + + result.AddOperand(x); + result.AddOperand(y); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Unordered(Instruction resultType, Instruction x, Instruction y) + { + Instruction result = NewInstruction(Op.OpUnordered, GetNewId(), resultType); + + result.AddOperand(x); + result.AddOperand(y); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LogicalEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpLogicalEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LogicalNotEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpLogicalNotEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LogicalOr(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpLogicalOr, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LogicalAnd(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpLogicalAnd, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LogicalNot(Instruction resultType, Instruction operand) + { + Instruction result = NewInstruction(Op.OpLogicalNot, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Select(Instruction resultType, Instruction condition, Instruction object1, Instruction object2) + { + Instruction result = NewInstruction(Op.OpSelect, GetNewId(), resultType); + + result.AddOperand(condition); + result.AddOperand(object1); + result.AddOperand(object2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpIEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction INotEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpINotEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UGreaterThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpUGreaterThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SGreaterThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpSGreaterThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UGreaterThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpUGreaterThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SGreaterThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpSGreaterThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ULessThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpULessThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SLessThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpSLessThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ULessThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpULessThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SLessThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpSLessThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FOrdEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFOrdEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FUnordEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFUnordEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FOrdNotEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFOrdNotEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FUnordNotEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFUnordNotEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FOrdLessThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFOrdLessThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FUnordLessThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFUnordLessThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FOrdGreaterThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFOrdGreaterThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FUnordGreaterThan(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFUnordGreaterThan, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FOrdLessThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFOrdLessThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FUnordLessThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFUnordLessThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FOrdGreaterThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFOrdGreaterThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FUnordGreaterThanEqual(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpFUnordGreaterThanEqual, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + // Derivative + + public Instruction DPdx(Instruction resultType, Instruction p) + { + Instruction result = NewInstruction(Op.OpDPdx, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction DPdy(Instruction resultType, Instruction p) + { + Instruction result = NewInstruction(Op.OpDPdy, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Fwidth(Instruction resultType, Instruction p) + { + Instruction result = NewInstruction(Op.OpFwidth, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction DPdxFine(Instruction resultType, Instruction p) + { + Instruction result = NewInstruction(Op.OpDPdxFine, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction DPdyFine(Instruction resultType, Instruction p) + { + Instruction result = NewInstruction(Op.OpDPdyFine, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FwidthFine(Instruction resultType, Instruction p) + { + Instruction result = NewInstruction(Op.OpFwidthFine, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction DPdxCoarse(Instruction resultType, Instruction p) + { + Instruction result = NewInstruction(Op.OpDPdxCoarse, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction DPdyCoarse(Instruction resultType, Instruction p) + { + Instruction result = NewInstruction(Op.OpDPdyCoarse, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FwidthCoarse(Instruction resultType, Instruction p) + { + Instruction result = NewInstruction(Op.OpFwidthCoarse, GetNewId(), resultType); + + result.AddOperand(p); + AddToFunctionDefinitions(result); + + return result; + } + + // Control-Flow + + public Instruction Phi(Instruction resultType, params Instruction[] parameters) + { + Instruction result = NewInstruction(Op.OpPhi, GetNewId(), resultType); + + result.AddOperand(parameters); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LoopMerge(Instruction mergeBlock, Instruction continueTarget, LoopControlMask loopControl) + { + Instruction result = NewInstruction(Op.OpLoopMerge); + + result.AddOperand(mergeBlock); + result.AddOperand(continueTarget); + result.AddOperand(loopControl); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SelectionMerge(Instruction mergeBlock, SelectionControlMask selectionControl) + { + Instruction result = NewInstruction(Op.OpSelectionMerge); + + result.AddOperand(mergeBlock); + result.AddOperand(selectionControl); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Label() + { + Instruction result = NewInstruction(Op.OpLabel); + + return result; + } + + public Instruction Branch(Instruction targetLabel) + { + Instruction result = NewInstruction(Op.OpBranch); + + result.AddOperand(targetLabel); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BranchConditional(Instruction condition, Instruction trueLabel, Instruction falseLabel, params LiteralInteger[] branchweights) + { + Instruction result = NewInstruction(Op.OpBranchConditional); + + result.AddOperand(condition); + result.AddOperand(trueLabel); + result.AddOperand(falseLabel); + result.AddOperand(branchweights); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Switch(Instruction selector, Instruction defaultObj, params IOperand[] target) + { + Instruction result = NewInstruction(Op.OpSwitch); + + result.AddOperand(selector); + result.AddOperand(defaultObj); + result.AddOperand(target); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Kill() + { + Instruction result = NewInstruction(Op.OpKill); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Return() + { + Instruction result = NewInstruction(Op.OpReturn); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReturnValue(Instruction value) + { + Instruction result = NewInstruction(Op.OpReturnValue); + + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction Unreachable() + { + Instruction result = NewInstruction(Op.OpUnreachable); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LifetimeStart(Instruction pointer, LiteralInteger size) + { + Instruction result = NewInstruction(Op.OpLifetimeStart); + + result.AddOperand(pointer); + result.AddOperand(size); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LifetimeStop(Instruction pointer, LiteralInteger size) + { + Instruction result = NewInstruction(Op.OpLifetimeStop); + + result.AddOperand(pointer); + result.AddOperand(size); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TerminateInvocation() + { + Instruction result = NewInstruction(Op.OpTerminateInvocation); + + AddToFunctionDefinitions(result); + + return result; + } + + // Atomic + + public Instruction AtomicLoad(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics) + { + Instruction result = NewInstruction(Op.OpAtomicLoad, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicStore(Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = NewInstruction(Op.OpAtomicStore); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicExchange(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = NewInstruction(Op.OpAtomicExchange, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicCompareExchange(Instruction resultType, Instruction pointer, Instruction memory, Instruction equal, Instruction unequal, Instruction value, Instruction comparator) + { + Instruction result = NewInstruction(Op.OpAtomicCompareExchange, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(equal); + result.AddOperand(unequal); + result.AddOperand(value); + result.AddOperand(comparator); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicCompareExchangeWeak(Instruction resultType, Instruction pointer, Instruction memory, Instruction equal, Instruction unequal, Instruction value, Instruction comparator) + { + Instruction result = NewInstruction(Op.OpAtomicCompareExchangeWeak, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(equal); + result.AddOperand(unequal); + result.AddOperand(value); + result.AddOperand(comparator); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicIIncrement(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics) + { + Instruction result = NewInstruction(Op.OpAtomicIIncrement, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicIDecrement(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics) + { + Instruction result = NewInstruction(Op.OpAtomicIDecrement, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicIAdd(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = NewInstruction(Op.OpAtomicIAdd, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicISub(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = NewInstruction(Op.OpAtomicISub, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicSMin(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = NewInstruction(Op.OpAtomicSMin, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicUMin(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = NewInstruction(Op.OpAtomicUMin, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicSMax(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = NewInstruction(Op.OpAtomicSMax, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicUMax(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = NewInstruction(Op.OpAtomicUMax, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicAnd(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = NewInstruction(Op.OpAtomicAnd, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicOr(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = NewInstruction(Op.OpAtomicOr, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicXor(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = NewInstruction(Op.OpAtomicXor, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicFlagTestAndSet(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics) + { + Instruction result = NewInstruction(Op.OpAtomicFlagTestAndSet, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicFlagClear(Instruction pointer, Instruction memory, Instruction semantics) + { + Instruction result = NewInstruction(Op.OpAtomicFlagClear); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AtomicFAddEXT(Instruction resultType, Instruction pointer, Instruction memory, Instruction semantics, Instruction value) + { + Instruction result = NewInstruction(Op.OpAtomicFAddEXT, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(memory); + result.AddOperand(semantics); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + // Primitive + + public Instruction EmitVertex() + { + Instruction result = NewInstruction(Op.OpEmitVertex); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction EndPrimitive() + { + Instruction result = NewInstruction(Op.OpEndPrimitive); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction EmitStreamVertex(Instruction stream) + { + Instruction result = NewInstruction(Op.OpEmitStreamVertex); + + result.AddOperand(stream); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction EndStreamPrimitive(Instruction stream) + { + Instruction result = NewInstruction(Op.OpEndStreamPrimitive); + + result.AddOperand(stream); + AddToFunctionDefinitions(result); + + return result; + } + + // Barrier + + public Instruction ControlBarrier(Instruction execution, Instruction memory, Instruction semantics) + { + Instruction result = NewInstruction(Op.OpControlBarrier); + + result.AddOperand(execution); + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction MemoryBarrier(Instruction memory, Instruction semantics) + { + Instruction result = NewInstruction(Op.OpMemoryBarrier); + + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction NamedBarrierInitialize(Instruction resultType, Instruction subgroupCount) + { + Instruction result = NewInstruction(Op.OpNamedBarrierInitialize, GetNewId(), resultType); + + result.AddOperand(subgroupCount); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction MemoryNamedBarrier(Instruction namedBarrier, Instruction memory, Instruction semantics) + { + Instruction result = NewInstruction(Op.OpMemoryNamedBarrier); + + result.AddOperand(namedBarrier); + result.AddOperand(memory); + result.AddOperand(semantics); + AddToFunctionDefinitions(result); + + return result; + } + + // Group + + public Instruction GroupAsyncCopy(Instruction resultType, Instruction execution, Instruction destination, Instruction source, Instruction numElements, Instruction stride, Instruction eventObj) + { + Instruction result = NewInstruction(Op.OpGroupAsyncCopy, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(destination); + result.AddOperand(source); + result.AddOperand(numElements); + result.AddOperand(stride); + result.AddOperand(eventObj); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupWaitEvents(Instruction execution, Instruction numEvents, Instruction eventsList) + { + Instruction result = NewInstruction(Op.OpGroupWaitEvents); + + result.AddOperand(execution); + result.AddOperand(numEvents); + result.AddOperand(eventsList); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupAll(Instruction resultType, Instruction execution, Instruction predicate) + { + Instruction result = NewInstruction(Op.OpGroupAll, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupAny(Instruction resultType, Instruction execution, Instruction predicate) + { + Instruction result = NewInstruction(Op.OpGroupAny, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupBroadcast(Instruction resultType, Instruction execution, Instruction value, Instruction localId) + { + Instruction result = NewInstruction(Op.OpGroupBroadcast, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(localId); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupIAdd(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupIAdd, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupFAdd(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupFAdd, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupFMin(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupFMin, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupUMin(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupUMin, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupSMin(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupSMin, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupFMax(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupFMax, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupUMax(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupUMax, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupSMax(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupSMax, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupBallotKHR(Instruction resultType, Instruction predicate) + { + Instruction result = NewInstruction(Op.OpSubgroupBallotKHR, GetNewId(), resultType); + + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupFirstInvocationKHR(Instruction resultType, Instruction value) + { + Instruction result = NewInstruction(Op.OpSubgroupFirstInvocationKHR, GetNewId(), resultType); + + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupAllKHR(Instruction resultType, Instruction predicate) + { + Instruction result = NewInstruction(Op.OpSubgroupAllKHR, GetNewId(), resultType); + + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupAnyKHR(Instruction resultType, Instruction predicate) + { + Instruction result = NewInstruction(Op.OpSubgroupAnyKHR, GetNewId(), resultType); + + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupAllEqualKHR(Instruction resultType, Instruction predicate) + { + Instruction result = NewInstruction(Op.OpSubgroupAllEqualKHR, GetNewId(), resultType); + + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupReadInvocationKHR(Instruction resultType, Instruction value, Instruction index) + { + Instruction result = NewInstruction(Op.OpSubgroupReadInvocationKHR, GetNewId(), resultType); + + result.AddOperand(value); + result.AddOperand(index); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupIAddNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupIAddNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupFAddNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupFAddNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupFMinNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupFMinNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupUMinNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupUMinNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupSMinNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupSMinNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupFMaxNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupFMaxNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupUMaxNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupUMaxNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupSMaxNonUniformAMD(Instruction resultType, Instruction execution, GroupOperation operation, Instruction x) + { + Instruction result = NewInstruction(Op.OpGroupSMaxNonUniformAMD, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(x); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupShuffleINTEL(Instruction resultType, Instruction data, Instruction invocationId) + { + Instruction result = NewInstruction(Op.OpSubgroupShuffleINTEL, GetNewId(), resultType); + + result.AddOperand(data); + result.AddOperand(invocationId); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupShuffleDownINTEL(Instruction resultType, Instruction current, Instruction next, Instruction delta) + { + Instruction result = NewInstruction(Op.OpSubgroupShuffleDownINTEL, GetNewId(), resultType); + + result.AddOperand(current); + result.AddOperand(next); + result.AddOperand(delta); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupShuffleUpINTEL(Instruction resultType, Instruction previous, Instruction current, Instruction delta) + { + Instruction result = NewInstruction(Op.OpSubgroupShuffleUpINTEL, GetNewId(), resultType); + + result.AddOperand(previous); + result.AddOperand(current); + result.AddOperand(delta); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupShuffleXorINTEL(Instruction resultType, Instruction data, Instruction value) + { + Instruction result = NewInstruction(Op.OpSubgroupShuffleXorINTEL, GetNewId(), resultType); + + result.AddOperand(data); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupBlockReadINTEL(Instruction resultType, Instruction ptr) + { + Instruction result = NewInstruction(Op.OpSubgroupBlockReadINTEL, GetNewId(), resultType); + + result.AddOperand(ptr); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupBlockWriteINTEL(Instruction ptr, Instruction data) + { + Instruction result = NewInstruction(Op.OpSubgroupBlockWriteINTEL); + + result.AddOperand(ptr); + result.AddOperand(data); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupImageBlockReadINTEL(Instruction resultType, Instruction image, Instruction coordinate) + { + Instruction result = NewInstruction(Op.OpSubgroupImageBlockReadINTEL, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupImageBlockWriteINTEL(Instruction image, Instruction coordinate, Instruction data) + { + Instruction result = NewInstruction(Op.OpSubgroupImageBlockWriteINTEL); + + result.AddOperand(image); + result.AddOperand(coordinate); + result.AddOperand(data); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupImageMediaBlockReadINTEL(Instruction resultType, Instruction image, Instruction coordinate, Instruction width, Instruction height) + { + Instruction result = NewInstruction(Op.OpSubgroupImageMediaBlockReadINTEL, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + result.AddOperand(width); + result.AddOperand(height); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SubgroupImageMediaBlockWriteINTEL(Instruction image, Instruction coordinate, Instruction width, Instruction height, Instruction data) + { + Instruction result = NewInstruction(Op.OpSubgroupImageMediaBlockWriteINTEL); + + result.AddOperand(image); + result.AddOperand(coordinate); + result.AddOperand(width); + result.AddOperand(height); + result.AddOperand(data); + AddToFunctionDefinitions(result); + + return result; + } + + // Device-Side_Enqueue + + public Instruction EnqueueMarker(Instruction resultType, Instruction queue, Instruction numEvents, Instruction waitEvents, Instruction retEvent) + { + Instruction result = NewInstruction(Op.OpEnqueueMarker, GetNewId(), resultType); + + result.AddOperand(queue); + result.AddOperand(numEvents); + result.AddOperand(waitEvents); + result.AddOperand(retEvent); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction EnqueueKernel(Instruction resultType, Instruction queue, Instruction flags, Instruction nDRange, Instruction numEvents, Instruction waitEvents, Instruction retEvent, Instruction invoke, Instruction param, Instruction paramSize, Instruction paramAlign, params Instruction[] localSize) + { + Instruction result = NewInstruction(Op.OpEnqueueKernel, GetNewId(), resultType); + + result.AddOperand(queue); + result.AddOperand(flags); + result.AddOperand(nDRange); + result.AddOperand(numEvents); + result.AddOperand(waitEvents); + result.AddOperand(retEvent); + result.AddOperand(invoke); + result.AddOperand(param); + result.AddOperand(paramSize); + result.AddOperand(paramAlign); + result.AddOperand(localSize); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetKernelNDrangeSubGroupCount(Instruction resultType, Instruction nDRange, Instruction invoke, Instruction param, Instruction paramSize, Instruction paramAlign) + { + Instruction result = NewInstruction(Op.OpGetKernelNDrangeSubGroupCount, GetNewId(), resultType); + + result.AddOperand(nDRange); + result.AddOperand(invoke); + result.AddOperand(param); + result.AddOperand(paramSize); + result.AddOperand(paramAlign); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetKernelNDrangeMaxSubGroupSize(Instruction resultType, Instruction nDRange, Instruction invoke, Instruction param, Instruction paramSize, Instruction paramAlign) + { + Instruction result = NewInstruction(Op.OpGetKernelNDrangeMaxSubGroupSize, GetNewId(), resultType); + + result.AddOperand(nDRange); + result.AddOperand(invoke); + result.AddOperand(param); + result.AddOperand(paramSize); + result.AddOperand(paramAlign); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetKernelWorkGroupSize(Instruction resultType, Instruction invoke, Instruction param, Instruction paramSize, Instruction paramAlign) + { + Instruction result = NewInstruction(Op.OpGetKernelWorkGroupSize, GetNewId(), resultType); + + result.AddOperand(invoke); + result.AddOperand(param); + result.AddOperand(paramSize); + result.AddOperand(paramAlign); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetKernelPreferredWorkGroupSizeMultiple(Instruction resultType, Instruction invoke, Instruction param, Instruction paramSize, Instruction paramAlign) + { + Instruction result = NewInstruction(Op.OpGetKernelPreferredWorkGroupSizeMultiple, GetNewId(), resultType); + + result.AddOperand(invoke); + result.AddOperand(param); + result.AddOperand(paramSize); + result.AddOperand(paramAlign); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RetainEvent(Instruction eventObj) + { + Instruction result = NewInstruction(Op.OpRetainEvent); + + result.AddOperand(eventObj); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReleaseEvent(Instruction eventObj) + { + Instruction result = NewInstruction(Op.OpReleaseEvent); + + result.AddOperand(eventObj); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CreateUserEvent(Instruction resultType) + { + Instruction result = NewInstruction(Op.OpCreateUserEvent, GetNewId(), resultType); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IsValidEvent(Instruction resultType, Instruction eventObj) + { + Instruction result = NewInstruction(Op.OpIsValidEvent, GetNewId(), resultType); + + result.AddOperand(eventObj); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction SetUserEventStatus(Instruction eventObj, Instruction status) + { + Instruction result = NewInstruction(Op.OpSetUserEventStatus); + + result.AddOperand(eventObj); + result.AddOperand(status); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CaptureEventProfilingInfo(Instruction eventObj, Instruction profilingInfo, Instruction value) + { + Instruction result = NewInstruction(Op.OpCaptureEventProfilingInfo); + + result.AddOperand(eventObj); + result.AddOperand(profilingInfo); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetDefaultQueue(Instruction resultType) + { + Instruction result = NewInstruction(Op.OpGetDefaultQueue, GetNewId(), resultType); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BuildNDRange(Instruction resultType, Instruction globalWorkSize, Instruction localWorkSize, Instruction globalWorkOffset) + { + Instruction result = NewInstruction(Op.OpBuildNDRange, GetNewId(), resultType); + + result.AddOperand(globalWorkSize); + result.AddOperand(localWorkSize); + result.AddOperand(globalWorkOffset); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetKernelLocalSizeForSubgroupCount(Instruction resultType, Instruction subgroupCount, Instruction invoke, Instruction param, Instruction paramSize, Instruction paramAlign) + { + Instruction result = NewInstruction(Op.OpGetKernelLocalSizeForSubgroupCount, GetNewId(), resultType); + + result.AddOperand(subgroupCount); + result.AddOperand(invoke); + result.AddOperand(param); + result.AddOperand(paramSize); + result.AddOperand(paramAlign); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetKernelMaxNumSubgroups(Instruction resultType, Instruction invoke, Instruction param, Instruction paramSize, Instruction paramAlign) + { + Instruction result = NewInstruction(Op.OpGetKernelMaxNumSubgroups, GetNewId(), resultType); + + result.AddOperand(invoke); + result.AddOperand(param); + result.AddOperand(paramSize); + result.AddOperand(paramAlign); + AddToFunctionDefinitions(result); + + return result; + } + + // Pipe + + public Instruction ReadPipe(Instruction resultType, Instruction pipe, Instruction pointer, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpReadPipe, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(pointer); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction WritePipe(Instruction resultType, Instruction pipe, Instruction pointer, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpWritePipe, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(pointer); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReservedReadPipe(Instruction resultType, Instruction pipe, Instruction reserveId, Instruction index, Instruction pointer, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpReservedReadPipe, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(reserveId); + result.AddOperand(index); + result.AddOperand(pointer); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReservedWritePipe(Instruction resultType, Instruction pipe, Instruction reserveId, Instruction index, Instruction pointer, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpReservedWritePipe, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(reserveId); + result.AddOperand(index); + result.AddOperand(pointer); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReserveReadPipePackets(Instruction resultType, Instruction pipe, Instruction numPackets, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpReserveReadPipePackets, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(numPackets); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReserveWritePipePackets(Instruction resultType, Instruction pipe, Instruction numPackets, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpReserveWritePipePackets, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(numPackets); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CommitReadPipe(Instruction pipe, Instruction reserveId, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpCommitReadPipe); + + result.AddOperand(pipe); + result.AddOperand(reserveId); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CommitWritePipe(Instruction pipe, Instruction reserveId, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpCommitWritePipe); + + result.AddOperand(pipe); + result.AddOperand(reserveId); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IsValidReserveId(Instruction resultType, Instruction reserveId) + { + Instruction result = NewInstruction(Op.OpIsValidReserveId, GetNewId(), resultType); + + result.AddOperand(reserveId); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetNumPipePackets(Instruction resultType, Instruction pipe, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpGetNumPipePackets, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GetMaxPipePackets(Instruction resultType, Instruction pipe, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpGetMaxPipePackets, GetNewId(), resultType); + + result.AddOperand(pipe); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupReserveReadPipePackets(Instruction resultType, Instruction execution, Instruction pipe, Instruction numPackets, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpGroupReserveReadPipePackets, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(pipe); + result.AddOperand(numPackets); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupReserveWritePipePackets(Instruction resultType, Instruction execution, Instruction pipe, Instruction numPackets, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpGroupReserveWritePipePackets, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(pipe); + result.AddOperand(numPackets); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupCommitReadPipe(Instruction execution, Instruction pipe, Instruction reserveId, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpGroupCommitReadPipe); + + result.AddOperand(execution); + result.AddOperand(pipe); + result.AddOperand(reserveId); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupCommitWritePipe(Instruction execution, Instruction pipe, Instruction reserveId, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpGroupCommitWritePipe); + + result.AddOperand(execution); + result.AddOperand(pipe); + result.AddOperand(reserveId); + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ConstantPipeStorage(Instruction resultType, LiteralInteger packetSize, LiteralInteger packetAlignment, LiteralInteger capacity) + { + Instruction result = NewInstruction(Op.OpConstantPipeStorage, GetNewId(), resultType); + + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + result.AddOperand(capacity); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CreatePipeFromPipeStorage(Instruction resultType, Instruction pipeStorage) + { + Instruction result = NewInstruction(Op.OpCreatePipeFromPipeStorage, GetNewId(), resultType); + + result.AddOperand(pipeStorage); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReadPipeBlockingINTEL(Instruction resultType, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpReadPipeBlockingINTEL, GetNewId(), resultType); + + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction WritePipeBlockingINTEL(Instruction resultType, Instruction packetSize, Instruction packetAlignment) + { + Instruction result = NewInstruction(Op.OpWritePipeBlockingINTEL, GetNewId(), resultType); + + result.AddOperand(packetSize); + result.AddOperand(packetAlignment); + AddToFunctionDefinitions(result); + + return result; + } + + // Non-Uniform + + public Instruction GroupNonUniformElect(Instruction resultType, Instruction execution) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformElect, GetNewId(), resultType); + + result.AddOperand(execution); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformAll(Instruction resultType, Instruction execution, Instruction predicate) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformAll, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformAny(Instruction resultType, Instruction execution, Instruction predicate) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformAny, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformAllEqual(Instruction resultType, Instruction execution, Instruction value) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformAllEqual, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBroadcast(Instruction resultType, Instruction execution, Instruction value, Instruction id) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformBroadcast, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(id); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBroadcastFirst(Instruction resultType, Instruction execution, Instruction value) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformBroadcastFirst, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBallot(Instruction resultType, Instruction execution, Instruction predicate) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformBallot, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(predicate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformInverseBallot(Instruction resultType, Instruction execution, Instruction value) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformInverseBallot, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBallotBitExtract(Instruction resultType, Instruction execution, Instruction value, Instruction index) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformBallotBitExtract, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(index); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBallotBitCount(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformBallotBitCount, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBallotFindLSB(Instruction resultType, Instruction execution, Instruction value) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformBallotFindLSB, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBallotFindMSB(Instruction resultType, Instruction execution, Instruction value) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformBallotFindMSB, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformShuffle(Instruction resultType, Instruction execution, Instruction value, Instruction id) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformShuffle, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(id); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformShuffleXor(Instruction resultType, Instruction execution, Instruction value, Instruction mask) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformShuffleXor, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(mask); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformShuffleUp(Instruction resultType, Instruction execution, Instruction value, Instruction delta) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformShuffleUp, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(delta); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformShuffleDown(Instruction resultType, Instruction execution, Instruction value, Instruction delta) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformShuffleDown, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(delta); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformIAdd(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformIAdd, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformFAdd(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformFAdd, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformIMul(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformIMul, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformFMul(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformFMul, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformSMin(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformSMin, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformUMin(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformUMin, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformFMin(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformFMin, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformSMax(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformSMax, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformUMax(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformUMax, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformFMax(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformFMax, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBitwiseAnd(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformBitwiseAnd, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBitwiseOr(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformBitwiseOr, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformBitwiseXor(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformBitwiseXor, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformLogicalAnd(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformLogicalAnd, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformLogicalOr(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformLogicalOr, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformLogicalXor(Instruction resultType, Instruction execution, GroupOperation operation, Instruction value, Instruction clusterSize = null) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformLogicalXor, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(operation); + result.AddOperand(value); + if (clusterSize != null) + { + result.AddOperand(clusterSize); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformQuadBroadcast(Instruction resultType, Instruction execution, Instruction value, Instruction index) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformQuadBroadcast, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(index); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformQuadSwap(Instruction resultType, Instruction execution, Instruction value, Instruction direction) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformQuadSwap, GetNewId(), resultType); + + result.AddOperand(execution); + result.AddOperand(value); + result.AddOperand(direction); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction GroupNonUniformPartitionNV(Instruction resultType, Instruction value) + { + Instruction result = NewInstruction(Op.OpGroupNonUniformPartitionNV, GetNewId(), resultType); + + result.AddOperand(value); + AddToFunctionDefinitions(result); + + return result; + } + + // Reserved + + public Instruction TraceRayKHR(Instruction accel, Instruction rayFlags, Instruction cullMask, Instruction sBTOffset, Instruction sBTStride, Instruction missIndex, Instruction rayOrigin, Instruction rayTmin, Instruction rayDirection, Instruction rayTmax, Instruction payload) + { + Instruction result = NewInstruction(Op.OpTraceRayKHR); + + result.AddOperand(accel); + result.AddOperand(rayFlags); + result.AddOperand(cullMask); + result.AddOperand(sBTOffset); + result.AddOperand(sBTStride); + result.AddOperand(missIndex); + result.AddOperand(rayOrigin); + result.AddOperand(rayTmin); + result.AddOperand(rayDirection); + result.AddOperand(rayTmax); + result.AddOperand(payload); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ExecuteCallableKHR(Instruction sBTIndex, Instruction callableData) + { + Instruction result = NewInstruction(Op.OpExecuteCallableKHR); + + result.AddOperand(sBTIndex); + result.AddOperand(callableData); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ConvertUToAccelerationStructureKHR(Instruction resultType, Instruction accel) + { + Instruction result = NewInstruction(Op.OpConvertUToAccelerationStructureKHR, GetNewId(), resultType); + + result.AddOperand(accel); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IgnoreIntersectionKHR() + { + Instruction result = NewInstruction(Op.OpIgnoreIntersectionKHR); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TerminateRayKHR() + { + Instruction result = NewInstruction(Op.OpTerminateRayKHR); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TypeRayQueryKHR() + { + Instruction result = NewInstruction(Op.OpTypeRayQueryKHR, GetNewId()); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryInitializeKHR(Instruction rayQuery, Instruction accel, Instruction rayFlags, Instruction cullMask, Instruction rayOrigin, Instruction rayTMin, Instruction rayDirection, Instruction rayTMax) + { + Instruction result = NewInstruction(Op.OpRayQueryInitializeKHR); + + result.AddOperand(rayQuery); + result.AddOperand(accel); + result.AddOperand(rayFlags); + result.AddOperand(cullMask); + result.AddOperand(rayOrigin); + result.AddOperand(rayTMin); + result.AddOperand(rayDirection); + result.AddOperand(rayTMax); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryTerminateKHR(Instruction rayQuery) + { + Instruction result = NewInstruction(Op.OpRayQueryTerminateKHR); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGenerateIntersectionKHR(Instruction rayQuery, Instruction hitT) + { + Instruction result = NewInstruction(Op.OpRayQueryGenerateIntersectionKHR); + + result.AddOperand(rayQuery); + result.AddOperand(hitT); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryConfirmIntersectionKHR(Instruction rayQuery) + { + Instruction result = NewInstruction(Op.OpRayQueryConfirmIntersectionKHR); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryProceedKHR(Instruction resultType, Instruction rayQuery) + { + Instruction result = NewInstruction(Op.OpRayQueryProceedKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionTypeKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = NewInstruction(Op.OpRayQueryGetIntersectionTypeKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FragmentMaskFetchAMD(Instruction resultType, Instruction image, Instruction coordinate) + { + Instruction result = NewInstruction(Op.OpFragmentMaskFetchAMD, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FragmentFetchAMD(Instruction resultType, Instruction image, Instruction coordinate, Instruction fragmentIndex) + { + Instruction result = NewInstruction(Op.OpFragmentFetchAMD, GetNewId(), resultType); + + result.AddOperand(image); + result.AddOperand(coordinate); + result.AddOperand(fragmentIndex); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReadClockKHR(Instruction resultType, Instruction execution) + { + Instruction result = NewInstruction(Op.OpReadClockKHR, GetNewId(), resultType); + + result.AddOperand(execution); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction WritePackedPrimitiveIndices4x8NV(Instruction indexOffset, Instruction packedIndices) + { + Instruction result = NewInstruction(Op.OpWritePackedPrimitiveIndices4x8NV); + + result.AddOperand(indexOffset); + result.AddOperand(packedIndices); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReportIntersectionNV(Instruction resultType, Instruction hit, Instruction hitKind) + { + Instruction result = NewInstruction(Op.OpReportIntersectionNV, GetNewId(), resultType); + + result.AddOperand(hit); + result.AddOperand(hitKind); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ReportIntersectionKHR(Instruction resultType, Instruction hit, Instruction hitKind) + { + Instruction result = NewInstruction(Op.OpReportIntersectionKHR, GetNewId(), resultType); + + result.AddOperand(hit); + result.AddOperand(hitKind); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IgnoreIntersectionNV() + { + Instruction result = NewInstruction(Op.OpIgnoreIntersectionNV); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TerminateRayNV() + { + Instruction result = NewInstruction(Op.OpTerminateRayNV); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TraceNV(Instruction accel, Instruction rayFlags, Instruction cullMask, Instruction sBTOffset, Instruction sBTStride, Instruction missIndex, Instruction rayOrigin, Instruction rayTmin, Instruction rayDirection, Instruction rayTmax, Instruction payloadId) + { + Instruction result = NewInstruction(Op.OpTraceNV); + + result.AddOperand(accel); + result.AddOperand(rayFlags); + result.AddOperand(cullMask); + result.AddOperand(sBTOffset); + result.AddOperand(sBTStride); + result.AddOperand(missIndex); + result.AddOperand(rayOrigin); + result.AddOperand(rayTmin); + result.AddOperand(rayDirection); + result.AddOperand(rayTmax); + result.AddOperand(payloadId); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TypeAccelerationStructureNV() + { + Instruction result = NewInstruction(Op.OpTypeAccelerationStructureNV, GetNewId()); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TypeAccelerationStructureKHR() + { + Instruction result = NewInstruction(Op.OpTypeAccelerationStructureKHR, GetNewId()); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ExecuteCallableNV(Instruction sBTIndex, Instruction callableDataId) + { + Instruction result = NewInstruction(Op.OpExecuteCallableNV); + + result.AddOperand(sBTIndex); + result.AddOperand(callableDataId); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction TypeCooperativeMatrixNV(Instruction componentType, Instruction execution, Instruction rows, Instruction columns) + { + Instruction result = NewInstruction(Op.OpTypeCooperativeMatrixNV, GetNewId()); + + result.AddOperand(componentType); + result.AddOperand(execution); + result.AddOperand(rows); + result.AddOperand(columns); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CooperativeMatrixLoadNV(Instruction resultType, Instruction pointer, Instruction stride, Instruction columnMajor, MemoryAccessMask memoryAccess = (MemoryAccessMask)int.MaxValue) + { + Instruction result = NewInstruction(Op.OpCooperativeMatrixLoadNV, GetNewId(), resultType); + + result.AddOperand(pointer); + result.AddOperand(stride); + result.AddOperand(columnMajor); + if (memoryAccess != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CooperativeMatrixStoreNV(Instruction pointer, Instruction obj, Instruction stride, Instruction columnMajor, MemoryAccessMask memoryAccess = (MemoryAccessMask)int.MaxValue) + { + Instruction result = NewInstruction(Op.OpCooperativeMatrixStoreNV); + + result.AddOperand(pointer); + result.AddOperand(obj); + result.AddOperand(stride); + result.AddOperand(columnMajor); + if (memoryAccess != (MemoryAccessMask)int.MaxValue) + { + result.AddOperand(memoryAccess); + } + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CooperativeMatrixMulAddNV(Instruction resultType, Instruction a, Instruction b, Instruction c) + { + Instruction result = NewInstruction(Op.OpCooperativeMatrixMulAddNV, GetNewId(), resultType); + + result.AddOperand(a); + result.AddOperand(b); + result.AddOperand(c); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction CooperativeMatrixLengthNV(Instruction resultType, Instruction type) + { + Instruction result = NewInstruction(Op.OpCooperativeMatrixLengthNV, GetNewId(), resultType); + + result.AddOperand(type); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction BeginInvocationInterlockEXT() + { + Instruction result = NewInstruction(Op.OpBeginInvocationInterlockEXT); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction EndInvocationInterlockEXT() + { + Instruction result = NewInstruction(Op.OpEndInvocationInterlockEXT); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction DemoteToHelperInvocationEXT() + { + Instruction result = NewInstruction(Op.OpDemoteToHelperInvocationEXT); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IsHelperInvocationEXT(Instruction resultType) + { + Instruction result = NewInstruction(Op.OpIsHelperInvocationEXT, GetNewId(), resultType); + + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UCountLeadingZerosINTEL(Instruction resultType, Instruction operand) + { + Instruction result = NewInstruction(Op.OpUCountLeadingZerosINTEL, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UCountTrailingZerosINTEL(Instruction resultType, Instruction operand) + { + Instruction result = NewInstruction(Op.OpUCountTrailingZerosINTEL, GetNewId(), resultType); + + result.AddOperand(operand); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AbsISubINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpAbsISubINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction AbsUSubINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpAbsUSubINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IAddSatINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpIAddSatINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UAddSatINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpUAddSatINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IAverageINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpIAverageINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UAverageINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpUAverageINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IAverageRoundedINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpIAverageRoundedINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UAverageRoundedINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpUAverageRoundedINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction ISubSatINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpISubSatINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction USubSatINTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpUSubSatINTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction IMul32x16INTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpIMul32x16INTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction UMul32x16INTEL(Instruction resultType, Instruction operand1, Instruction operand2) + { + Instruction result = NewInstruction(Op.OpUMul32x16INTEL, GetNewId(), resultType); + + result.AddOperand(operand1); + result.AddOperand(operand2); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction LoopControlINTEL(params LiteralInteger[] loopControlParameters) + { + Instruction result = NewInstruction(Op.OpLoopControlINTEL); + + result.AddOperand(loopControlParameters); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction FPGARegINTEL(Instruction resultType, Instruction resultObj, Instruction input) + { + Instruction result = NewInstruction(Op.OpFPGARegINTEL, GetNewId(), resultType); + + result.AddOperand(resultObj); + result.AddOperand(input); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetRayTMinKHR(Instruction resultType, Instruction rayQuery) + { + Instruction result = NewInstruction(Op.OpRayQueryGetRayTMinKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetRayFlagsKHR(Instruction resultType, Instruction rayQuery) + { + Instruction result = NewInstruction(Op.OpRayQueryGetRayFlagsKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionTKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = NewInstruction(Op.OpRayQueryGetIntersectionTKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionInstanceCustomIndexKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = NewInstruction(Op.OpRayQueryGetIntersectionInstanceCustomIndexKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionInstanceIdKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = NewInstruction(Op.OpRayQueryGetIntersectionInstanceIdKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = NewInstruction(Op.OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionGeometryIndexKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = NewInstruction(Op.OpRayQueryGetIntersectionGeometryIndexKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionPrimitiveIndexKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = NewInstruction(Op.OpRayQueryGetIntersectionPrimitiveIndexKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionBarycentricsKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = NewInstruction(Op.OpRayQueryGetIntersectionBarycentricsKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionFrontFaceKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = NewInstruction(Op.OpRayQueryGetIntersectionFrontFaceKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionCandidateAABBOpaqueKHR(Instruction resultType, Instruction rayQuery) + { + Instruction result = NewInstruction(Op.OpRayQueryGetIntersectionCandidateAABBOpaqueKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionObjectRayDirectionKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = NewInstruction(Op.OpRayQueryGetIntersectionObjectRayDirectionKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionObjectRayOriginKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = NewInstruction(Op.OpRayQueryGetIntersectionObjectRayOriginKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetWorldRayDirectionKHR(Instruction resultType, Instruction rayQuery) + { + Instruction result = NewInstruction(Op.OpRayQueryGetWorldRayDirectionKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetWorldRayOriginKHR(Instruction resultType, Instruction rayQuery) + { + Instruction result = NewInstruction(Op.OpRayQueryGetWorldRayOriginKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionObjectToWorldKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = NewInstruction(Op.OpRayQueryGetIntersectionObjectToWorldKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + public Instruction RayQueryGetIntersectionWorldToObjectKHR(Instruction resultType, Instruction rayQuery, Instruction intersection) + { + Instruction result = NewInstruction(Op.OpRayQueryGetIntersectionWorldToObjectKHR, GetNewId(), resultType); + + result.AddOperand(rayQuery); + result.AddOperand(intersection); + AddToFunctionDefinitions(result); + + return result; + } + + } +} diff --git a/src/Spv.Generator/Autogenerated/GlslStd450Grammar.cs b/src/Spv.Generator/Autogenerated/GlslStd450Grammar.cs new file mode 100644 index 00000000..fa01e94c --- /dev/null +++ b/src/Spv.Generator/Autogenerated/GlslStd450Grammar.cs @@ -0,0 +1,439 @@ +// AUTOGENERATED: DO NOT EDIT +// Last update date: 2021-01-06 23:02:26.955269 +#region Grammar License +// Copyright (c) 2014-2016 The Khronos Group Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and/or associated documentation files (the "Materials"), +// to deal in the Materials without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Materials, and to permit persons to whom the +// Materials are furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Materials. +// +// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +// STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +// HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +// +// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +// IN THE MATERIALS. +#endregion + +namespace Spv.Generator +{ + public partial class Module + { + public Instruction GlslRound(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 1, x); + } + + public Instruction GlslRoundEven(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 2, x); + } + + public Instruction GlslTrunc(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 3, x); + } + + public Instruction GlslFAbs(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 4, x); + } + + public Instruction GlslSAbs(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 5, x); + } + + public Instruction GlslFSign(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 6, x); + } + + public Instruction GlslSSign(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 7, x); + } + + public Instruction GlslFloor(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 8, x); + } + + public Instruction GlslCeil(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 9, x); + } + + public Instruction GlslFract(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 10, x); + } + + public Instruction GlslRadians(Instruction resultType, Instruction degrees) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 11, degrees); + } + + public Instruction GlslDegrees(Instruction resultType, Instruction radians) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 12, radians); + } + + public Instruction GlslSin(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 13, x); + } + + public Instruction GlslCos(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 14, x); + } + + public Instruction GlslTan(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 15, x); + } + + public Instruction GlslAsin(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 16, x); + } + + public Instruction GlslAcos(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 17, x); + } + + public Instruction GlslAtan(Instruction resultType, Instruction y_over_x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 18, y_over_x); + } + + public Instruction GlslSinh(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 19, x); + } + + public Instruction GlslCosh(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 20, x); + } + + public Instruction GlslTanh(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 21, x); + } + + public Instruction GlslAsinh(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 22, x); + } + + public Instruction GlslAcosh(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 23, x); + } + + public Instruction GlslAtanh(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 24, x); + } + + public Instruction GlslAtan2(Instruction resultType, Instruction y, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 25, y, x); + } + + public Instruction GlslPow(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 26, x, y); + } + + public Instruction GlslExp(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 27, x); + } + + public Instruction GlslLog(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 28, x); + } + + public Instruction GlslExp2(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 29, x); + } + + public Instruction GlslLog2(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 30, x); + } + + public Instruction GlslSqrt(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 31, x); + } + + public Instruction GlslInverseSqrt(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 32, x); + } + + public Instruction GlslDeterminant(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 33, x); + } + + public Instruction GlslMatrixInverse(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 34, x); + } + + public Instruction GlslModf(Instruction resultType, Instruction x, Instruction i) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 35, x, i); + } + + public Instruction GlslModfStruct(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 36, x); + } + + public Instruction GlslFMin(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 37, x, y); + } + + public Instruction GlslUMin(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 38, x, y); + } + + public Instruction GlslSMin(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 39, x, y); + } + + public Instruction GlslFMax(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 40, x, y); + } + + public Instruction GlslUMax(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 41, x, y); + } + + public Instruction GlslSMax(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 42, x, y); + } + + public Instruction GlslFClamp(Instruction resultType, Instruction x, Instruction minVal, Instruction maxVal) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 43, x, minVal, maxVal); + } + + public Instruction GlslUClamp(Instruction resultType, Instruction x, Instruction minVal, Instruction maxVal) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 44, x, minVal, maxVal); + } + + public Instruction GlslSClamp(Instruction resultType, Instruction x, Instruction minVal, Instruction maxVal) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 45, x, minVal, maxVal); + } + + public Instruction GlslFMix(Instruction resultType, Instruction x, Instruction y, Instruction a) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 46, x, y, a); + } + + public Instruction GlslIMix(Instruction resultType, Instruction x, Instruction y, Instruction a) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 47, x, y, a); + } + + public Instruction GlslStep(Instruction resultType, Instruction edge, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 48, edge, x); + } + + public Instruction GlslSmoothStep(Instruction resultType, Instruction edge0, Instruction edge1, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 49, edge0, edge1, x); + } + + public Instruction GlslFma(Instruction resultType, Instruction a, Instruction b, Instruction c) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 50, a, b, c); + } + + public Instruction GlslFrexp(Instruction resultType, Instruction x, Instruction exp) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 51, x, exp); + } + + public Instruction GlslFrexpStruct(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 52, x); + } + + public Instruction GlslLdexp(Instruction resultType, Instruction x, Instruction exp) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 53, x, exp); + } + + public Instruction GlslPackSnorm4x8(Instruction resultType, Instruction v) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 54, v); + } + + public Instruction GlslPackUnorm4x8(Instruction resultType, Instruction v) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 55, v); + } + + public Instruction GlslPackSnorm2x16(Instruction resultType, Instruction v) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 56, v); + } + + public Instruction GlslPackUnorm2x16(Instruction resultType, Instruction v) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 57, v); + } + + public Instruction GlslPackHalf2x16(Instruction resultType, Instruction v) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 58, v); + } + + public Instruction GlslPackDouble2x32(Instruction resultType, Instruction v) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 59, v); + } + + public Instruction GlslUnpackSnorm2x16(Instruction resultType, Instruction p) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 60, p); + } + + public Instruction GlslUnpackUnorm2x16(Instruction resultType, Instruction p) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 61, p); + } + + public Instruction GlslUnpackHalf2x16(Instruction resultType, Instruction v) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 62, v); + } + + public Instruction GlslUnpackSnorm4x8(Instruction resultType, Instruction p) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 63, p); + } + + public Instruction GlslUnpackUnorm4x8(Instruction resultType, Instruction p) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 64, p); + } + + public Instruction GlslUnpackDouble2x32(Instruction resultType, Instruction v) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 65, v); + } + + public Instruction GlslLength(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 66, x); + } + + public Instruction GlslDistance(Instruction resultType, Instruction p0, Instruction p1) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 67, p0, p1); + } + + public Instruction GlslCross(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 68, x, y); + } + + public Instruction GlslNormalize(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 69, x); + } + + public Instruction GlslFaceForward(Instruction resultType, Instruction n, Instruction i, Instruction nref) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 70, n, i, nref); + } + + public Instruction GlslReflect(Instruction resultType, Instruction i, Instruction n) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 71, i, n); + } + + public Instruction GlslRefract(Instruction resultType, Instruction i, Instruction n, Instruction eta) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 72, i, n, eta); + } + + public Instruction GlslFindILsb(Instruction resultType, Instruction value) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 73, value); + } + + public Instruction GlslFindSMsb(Instruction resultType, Instruction value) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 74, value); + } + + public Instruction GlslFindUMsb(Instruction resultType, Instruction value) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 75, value); + } + + public Instruction GlslInterpolateAtCentroid(Instruction resultType, Instruction interpolant) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 76, interpolant); + } + + public Instruction GlslInterpolateAtSample(Instruction resultType, Instruction interpolant, Instruction sample) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 77, interpolant, sample); + } + + public Instruction GlslInterpolateAtOffset(Instruction resultType, Instruction interpolant, Instruction offset) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 78, interpolant, offset); + } + + public Instruction GlslNMin(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 79, x, y); + } + + public Instruction GlslNMax(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 80, x, y); + } + + public Instruction GlslNClamp(Instruction resultType, Instruction x, Instruction minVal, Instruction maxVal) + { + return ExtInst(resultType, AddExtInstImport("GLSL.std.450"), 81, x, minVal, maxVal); + } + + } +} diff --git a/src/Spv.Generator/Autogenerated/OpenClGrammar.cs b/src/Spv.Generator/Autogenerated/OpenClGrammar.cs new file mode 100644 index 00000000..03eb5ccb --- /dev/null +++ b/src/Spv.Generator/Autogenerated/OpenClGrammar.cs @@ -0,0 +1,841 @@ +// AUTOGENERATED: DO NOT EDIT +// Last update date: 2021-01-06 23:02:27.020534 +#region Grammar License +// Copyright (c) 2014-2016 The Khronos Group Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and/or associated documentation files (the "Materials"), +// to deal in the Materials without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Materials, and to permit persons to whom the +// Materials are furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Materials. +// +// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +// STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +// HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +// +// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +// IN THE MATERIALS. +#endregion + +using static Spv.Specification; + +namespace Spv.Generator +{ + public partial class Module + { + public Instruction OpenClAcos(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 0, x); + } + + public Instruction OpenClAcosh(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 1, x); + } + + public Instruction OpenClAcospi(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 2, x); + } + + public Instruction OpenClAsin(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 3, x); + } + + public Instruction OpenClAsinh(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 4, x); + } + + public Instruction OpenClAsinpi(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 5, x); + } + + public Instruction OpenClAtan(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 6, x); + } + + public Instruction OpenClAtan2(Instruction resultType, Instruction y, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 7, y, x); + } + + public Instruction OpenClAtanh(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 8, x); + } + + public Instruction OpenClAtanpi(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 9, x); + } + + public Instruction OpenClAtan2pi(Instruction resultType, Instruction y, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 10, y, x); + } + + public Instruction OpenClCbrt(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 11, x); + } + + public Instruction OpenClCeil(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 12, x); + } + + public Instruction OpenClCopysign(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 13, x, y); + } + + public Instruction OpenClCos(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 14, x); + } + + public Instruction OpenClCosh(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 15, x); + } + + public Instruction OpenClCospi(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 16, x); + } + + public Instruction OpenClErfc(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 17, x); + } + + public Instruction OpenClErf(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 18, x); + } + + public Instruction OpenClExp(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 19, x); + } + + public Instruction OpenClExp2(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 20, x); + } + + public Instruction OpenClExp10(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 21, x); + } + + public Instruction OpenClExpm1(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 22, x); + } + + public Instruction OpenClFabs(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 23, x); + } + + public Instruction OpenClFdim(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 24, x, y); + } + + public Instruction OpenClFloor(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 25, x); + } + + public Instruction OpenClFma(Instruction resultType, Instruction a, Instruction b, Instruction c) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 26, a, b, c); + } + + public Instruction OpenClFmax(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 27, x, y); + } + + public Instruction OpenClFmin(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 28, x, y); + } + + public Instruction OpenClFmod(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 29, x, y); + } + + public Instruction OpenClFract(Instruction resultType, Instruction x, Instruction ptr) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 30, x, ptr); + } + + public Instruction OpenClFrexp(Instruction resultType, Instruction x, Instruction exp) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 31, x, exp); + } + + public Instruction OpenClHypot(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 32, x, y); + } + + public Instruction OpenClIlogb(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 33, x); + } + + public Instruction OpenClLdexp(Instruction resultType, Instruction x, Instruction k) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 34, x, k); + } + + public Instruction OpenClLgamma(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 35, x); + } + + public Instruction OpenClLgamma_r(Instruction resultType, Instruction x, Instruction signp) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 36, x, signp); + } + + public Instruction OpenClLog(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 37, x); + } + + public Instruction OpenClLog2(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 38, x); + } + + public Instruction OpenClLog10(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 39, x); + } + + public Instruction OpenClLog1p(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 40, x); + } + + public Instruction OpenClLogb(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 41, x); + } + + public Instruction OpenClMad(Instruction resultType, Instruction a, Instruction b, Instruction c) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 42, a, b, c); + } + + public Instruction OpenClMaxmag(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 43, x, y); + } + + public Instruction OpenClMinmag(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 44, x, y); + } + + public Instruction OpenClModf(Instruction resultType, Instruction x, Instruction iptr) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 45, x, iptr); + } + + public Instruction OpenClNan(Instruction resultType, Instruction nancode) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 46, nancode); + } + + public Instruction OpenClNextafter(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 47, x, y); + } + + public Instruction OpenClPow(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 48, x, y); + } + + public Instruction OpenClPown(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 49, x, y); + } + + public Instruction OpenClPowr(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 50, x, y); + } + + public Instruction OpenClRemainder(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 51, x, y); + } + + public Instruction OpenClRemquo(Instruction resultType, Instruction x, Instruction y, Instruction quo) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 52, x, y, quo); + } + + public Instruction OpenClRint(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 53, x); + } + + public Instruction OpenClRootn(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 54, x, y); + } + + public Instruction OpenClRound(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 55, x); + } + + public Instruction OpenClRsqrt(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 56, x); + } + + public Instruction OpenClSin(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 57, x); + } + + public Instruction OpenClSincos(Instruction resultType, Instruction x, Instruction cosval) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 58, x, cosval); + } + + public Instruction OpenClSinh(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 59, x); + } + + public Instruction OpenClSinpi(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 60, x); + } + + public Instruction OpenClSqrt(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 61, x); + } + + public Instruction OpenClTan(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 62, x); + } + + public Instruction OpenClTanh(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 63, x); + } + + public Instruction OpenClTanpi(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 64, x); + } + + public Instruction OpenClTgamma(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 65, x); + } + + public Instruction OpenClTrunc(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 66, x); + } + + public Instruction OpenClHalf_cos(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 67, x); + } + + public Instruction OpenClHalf_divide(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 68, x, y); + } + + public Instruction OpenClHalf_exp(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 69, x); + } + + public Instruction OpenClHalf_exp2(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 70, x); + } + + public Instruction OpenClHalf_exp10(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 71, x); + } + + public Instruction OpenClHalf_log(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 72, x); + } + + public Instruction OpenClHalf_log2(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 73, x); + } + + public Instruction OpenClHalf_log10(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 74, x); + } + + public Instruction OpenClHalf_powr(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 75, x, y); + } + + public Instruction OpenClHalf_recip(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 76, x); + } + + public Instruction OpenClHalf_rsqrt(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 77, x); + } + + public Instruction OpenClHalf_sin(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 78, x); + } + + public Instruction OpenClHalf_sqrt(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 79, x); + } + + public Instruction OpenClHalf_tan(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 80, x); + } + + public Instruction OpenClNative_cos(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 81, x); + } + + public Instruction OpenClNative_divide(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 82, x, y); + } + + public Instruction OpenClNative_exp(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 83, x); + } + + public Instruction OpenClNative_exp2(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 84, x); + } + + public Instruction OpenClNative_exp10(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 85, x); + } + + public Instruction OpenClNative_log(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 86, x); + } + + public Instruction OpenClNative_log2(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 87, x); + } + + public Instruction OpenClNative_log10(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 88, x); + } + + public Instruction OpenClNative_powr(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 89, x, y); + } + + public Instruction OpenClNative_recip(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 90, x); + } + + public Instruction OpenClNative_rsqrt(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 91, x); + } + + public Instruction OpenClNative_sin(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 92, x); + } + + public Instruction OpenClNative_sqrt(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 93, x); + } + + public Instruction OpenClNative_tan(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 94, x); + } + + public Instruction OpenClS_abs(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 141, x); + } + + public Instruction OpenClS_abs_diff(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 142, x, y); + } + + public Instruction OpenClS_add_sat(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 143, x, y); + } + + public Instruction OpenClU_add_sat(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 144, x, y); + } + + public Instruction OpenClS_hadd(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 145, x, y); + } + + public Instruction OpenClU_hadd(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 146, x, y); + } + + public Instruction OpenClS_rhadd(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 147, x, y); + } + + public Instruction OpenClU_rhadd(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 148, x, y); + } + + public Instruction OpenClS_clamp(Instruction resultType, Instruction x, Instruction minval, Instruction maxval) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 149, x, minval, maxval); + } + + public Instruction OpenClU_clamp(Instruction resultType, Instruction x, Instruction minval, Instruction maxval) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 150, x, minval, maxval); + } + + public Instruction OpenClClz(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 151, x); + } + + public Instruction OpenClCtz(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 152, x); + } + + public Instruction OpenClS_mad_hi(Instruction resultType, Instruction a, Instruction b, Instruction c) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 153, a, b, c); + } + + public Instruction OpenClU_mad_sat(Instruction resultType, Instruction x, Instruction y, Instruction z) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 154, x, y, z); + } + + public Instruction OpenClS_mad_sat(Instruction resultType, Instruction x, Instruction y, Instruction z) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 155, x, y, z); + } + + public Instruction OpenClS_max(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 156, x, y); + } + + public Instruction OpenClU_max(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 157, x, y); + } + + public Instruction OpenClS_min(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 158, x, y); + } + + public Instruction OpenClU_min(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 159, x, y); + } + + public Instruction OpenClS_mul_hi(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 160, x, y); + } + + public Instruction OpenClRotate(Instruction resultType, Instruction v, Instruction i) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 161, v, i); + } + + public Instruction OpenClS_sub_sat(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 162, x, y); + } + + public Instruction OpenClU_sub_sat(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 163, x, y); + } + + public Instruction OpenClU_upsample(Instruction resultType, Instruction hi, Instruction lo) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 164, hi, lo); + } + + public Instruction OpenClS_upsample(Instruction resultType, Instruction hi, Instruction lo) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 165, hi, lo); + } + + public Instruction OpenClPopcount(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 166, x); + } + + public Instruction OpenClS_mad24(Instruction resultType, Instruction x, Instruction y, Instruction z) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 167, x, y, z); + } + + public Instruction OpenClU_mad24(Instruction resultType, Instruction x, Instruction y, Instruction z) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 168, x, y, z); + } + + public Instruction OpenClS_mul24(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 169, x, y); + } + + public Instruction OpenClU_mul24(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 170, x, y); + } + + public Instruction OpenClU_abs(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 201, x); + } + + public Instruction OpenClU_abs_diff(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 202, x, y); + } + + public Instruction OpenClU_mul_hi(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 203, x, y); + } + + public Instruction OpenClU_mad_hi(Instruction resultType, Instruction a, Instruction b, Instruction c) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 204, a, b, c); + } + + public Instruction OpenClFclamp(Instruction resultType, Instruction x, Instruction minval, Instruction maxval) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 95, x, minval, maxval); + } + + public Instruction OpenClDegrees(Instruction resultType, Instruction radians) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 96, radians); + } + + public Instruction OpenClFmax_common(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 97, x, y); + } + + public Instruction OpenClFmin_common(Instruction resultType, Instruction x, Instruction y) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 98, x, y); + } + + public Instruction OpenClMix(Instruction resultType, Instruction x, Instruction y, Instruction a) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 99, x, y, a); + } + + public Instruction OpenClRadians(Instruction resultType, Instruction degrees) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 100, degrees); + } + + public Instruction OpenClStep(Instruction resultType, Instruction edge, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 101, edge, x); + } + + public Instruction OpenClSmoothstep(Instruction resultType, Instruction edge0, Instruction edge1, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 102, edge0, edge1, x); + } + + public Instruction OpenClSign(Instruction resultType, Instruction x) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 103, x); + } + + public Instruction OpenClCross(Instruction resultType, Instruction p0, Instruction p1) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 104, p0, p1); + } + + public Instruction OpenClDistance(Instruction resultType, Instruction p0, Instruction p1) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 105, p0, p1); + } + + public Instruction OpenClLength(Instruction resultType, Instruction p) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 106, p); + } + + public Instruction OpenClNormalize(Instruction resultType, Instruction p) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 107, p); + } + + public Instruction OpenClFast_distance(Instruction resultType, Instruction p0, Instruction p1) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 108, p0, p1); + } + + public Instruction OpenClFast_length(Instruction resultType, Instruction p) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 109, p); + } + + public Instruction OpenClFast_normalize(Instruction resultType, Instruction p) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 110, p); + } + + public Instruction OpenClBitselect(Instruction resultType, Instruction a, Instruction b, Instruction c) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 186, a, b, c); + } + + public Instruction OpenClSelect(Instruction resultType, Instruction a, Instruction b, Instruction c) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 187, a, b, c); + } + + public Instruction OpenClVloadn(Instruction resultType, Instruction offset, Instruction p, LiteralInteger n) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 171, offset, p, n); + } + + public Instruction OpenClVstoren(Instruction resultType, Instruction data, Instruction offset, Instruction p) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 172, data, offset, p); + } + + public Instruction OpenClVload_half(Instruction resultType, Instruction offset, Instruction p) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 173, offset, p); + } + + public Instruction OpenClVload_halfn(Instruction resultType, Instruction offset, Instruction p, LiteralInteger n) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 174, offset, p, n); + } + + public Instruction OpenClVstore_half(Instruction resultType, Instruction data, Instruction offset, Instruction p) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 175, data, offset, p); + } + + public Instruction OpenClVstore_half_r(Instruction resultType, Instruction data, Instruction offset, Instruction p, FPRoundingMode mode) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 176, data, offset, p, LiteralInteger.CreateForEnum(mode)); + } + + public Instruction OpenClVstore_halfn(Instruction resultType, Instruction data, Instruction offset, Instruction p) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 177, data, offset, p); + } + + public Instruction OpenClVstore_halfn_r(Instruction resultType, Instruction data, Instruction offset, Instruction p, FPRoundingMode mode) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 178, data, offset, p, LiteralInteger.CreateForEnum(mode)); + } + + public Instruction OpenClVloada_halfn(Instruction resultType, Instruction offset, Instruction p, LiteralInteger n) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 179, offset, p, n); + } + + public Instruction OpenClVstorea_halfn(Instruction resultType, Instruction data, Instruction offset, Instruction p) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 180, data, offset, p); + } + + public Instruction OpenClVstorea_halfn_r(Instruction resultType, Instruction data, Instruction offset, Instruction p, FPRoundingMode mode) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 181, data, offset, p, LiteralInteger.CreateForEnum(mode)); + } + + public Instruction OpenClShuffle(Instruction resultType, Instruction x, Instruction shufflemask) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 182, x, shufflemask); + } + + public Instruction OpenClShuffle2(Instruction resultType, Instruction x, Instruction y, Instruction shufflemask) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 183, x, y, shufflemask); + } + + public Instruction OpenClPrefetch(Instruction resultType, Instruction ptr, Instruction numelements) + { + return ExtInst(resultType, AddExtInstImport("OpenCL.std"), 185, ptr, numelements); + } + + } +} diff --git a/src/Spv.Generator/ConstantKey.cs b/src/Spv.Generator/ConstantKey.cs new file mode 100644 index 00000000..5359c24d --- /dev/null +++ b/src/Spv.Generator/ConstantKey.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Spv.Generator +{ + internal readonly struct ConstantKey : IEquatable + { + private readonly Instruction _constant; + + public ConstantKey(Instruction constant) + { + _constant = constant; + } + + public override int GetHashCode() + { + return HashCode.Combine(_constant.Opcode, _constant.GetHashCodeContent(), _constant.GetHashCodeResultType()); + } + + public bool Equals(ConstantKey other) + { + return _constant.Opcode == other._constant.Opcode && _constant.EqualsContent(other._constant) && _constant.EqualsResultType(other._constant); + } + + public override bool Equals([NotNullWhen(true)] object obj) + { + return obj is ConstantKey key && Equals(key); + } + } +} diff --git a/src/Spv.Generator/DeterministicHashCode.cs b/src/Spv.Generator/DeterministicHashCode.cs new file mode 100644 index 00000000..ee7ad1de --- /dev/null +++ b/src/Spv.Generator/DeterministicHashCode.cs @@ -0,0 +1,109 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Spv.Generator +{ + /// + /// Similar to System.HashCode, but without introducing random values. + /// The same primes and shifts are used. + /// + internal static class DeterministicHashCode + { + private const uint Prime1 = 2654435761U; + private const uint Prime2 = 2246822519U; + private const uint Prime3 = 3266489917U; + private const uint Prime4 = 668265263U; + + public static int GetHashCode(string value) + { + uint hash = (uint)value.Length + Prime1; + + for (int i = 0; i < value.Length; i++) + { + hash += (hash << 7) ^ value[i]; + } + + return (int)MixFinal(hash); + } + + public static int Combine(ReadOnlySpan values) + { + uint hashCode = Prime2; + hashCode += 4 * (uint)values.Length; + + foreach (T value in values) + { + uint hc = (uint)(value?.GetHashCode() ?? 0); + hashCode = MixStep(hashCode, hc); + } + + return (int)MixFinal(hashCode); + } + + public static int Combine(T1 value1, T2 value2) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + + uint hash = Prime2; + hash += 8; + + hash = MixStep(hash, hc1); + hash = MixStep(hash, hc2); + + return (int)MixFinal(hash); + } + + public static int Combine(T1 value1, T2 value2, T3 value3) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + + uint hash = Prime2; + hash += 12; + + hash = MixStep(hash, hc1); + hash = MixStep(hash, hc2); + hash = MixStep(hash, hc3); + + return (int)MixFinal(hash); + } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + + uint hash = Prime2; + hash += 16; + + hash = MixStep(hash, hc1); + hash = MixStep(hash, hc2); + hash = MixStep(hash, hc3); + hash = MixStep(hash, hc4); + + return (int)MixFinal(hash); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixStep(uint hashCode, uint mixValue) + { + return BitOperations.RotateLeft(hashCode + mixValue * Prime3, 17) * Prime4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixFinal(uint hash) + { + hash ^= hash >> 15; + hash *= Prime2; + hash ^= hash >> 13; + hash *= Prime3; + hash ^= hash >> 16; + return hash; + } + } +} diff --git a/src/Spv.Generator/DeterministicStringKey.cs b/src/Spv.Generator/DeterministicStringKey.cs new file mode 100644 index 00000000..7ed14c33 --- /dev/null +++ b/src/Spv.Generator/DeterministicStringKey.cs @@ -0,0 +1,29 @@ +using System; + +namespace Spv.Generator +{ + internal class DeterministicStringKey : IEquatable + { + private readonly string _value; + + public DeterministicStringKey(string value) + { + _value = value; + } + + public override int GetHashCode() + { + return DeterministicHashCode.GetHashCode(_value); + } + + public bool Equals(DeterministicStringKey other) + { + return _value == other?._value; + } + + public override bool Equals(object obj) + { + return obj is DeterministicStringKey key && Equals(key); + } + } +} diff --git a/src/Spv.Generator/GeneratorPool.cs b/src/Spv.Generator/GeneratorPool.cs new file mode 100644 index 00000000..3c16586c --- /dev/null +++ b/src/Spv.Generator/GeneratorPool.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; + +namespace Spv.Generator +{ + public class GeneratorPool where T : class, new() + { + private readonly List _pool; + private int _chunkIndex = -1; + private int _poolIndex = -1; + private readonly int _poolSizeIncrement; + + public GeneratorPool() : this(1000, 200) { } + + public GeneratorPool(int chunkSizeLimit, int poolSizeIncrement) + { + _poolSizeIncrement = poolSizeIncrement; + + _pool = new(chunkSizeLimit * 2); + + AddChunkIfNeeded(); + } + + public T Allocate() + { + if (++_poolIndex >= _poolSizeIncrement) + { + AddChunkIfNeeded(); + + _poolIndex = 0; + } + + return _pool[_chunkIndex][_poolIndex]; + } + + private void AddChunkIfNeeded() + { + if (++_chunkIndex >= _pool.Count) + { + T[] pool = new T[_poolSizeIncrement]; + + for (int i = 0; i < _poolSizeIncrement; i++) + { + pool[i] = new T(); + } + + _pool.Add(pool); + } + } + + public void Clear() + { + _chunkIndex = 0; + _poolIndex = -1; + } + } +} diff --git a/src/Spv.Generator/IOperand.cs b/src/Spv.Generator/IOperand.cs new file mode 100644 index 00000000..8809b606 --- /dev/null +++ b/src/Spv.Generator/IOperand.cs @@ -0,0 +1,14 @@ +using System; +using System.IO; + +namespace Spv.Generator +{ + public interface IOperand : IEquatable + { + OperandType Type { get; } + + ushort WordCount { get; } + + void WriteOperand(BinaryWriter writer); + } +} diff --git a/src/Spv.Generator/Instruction.cs b/src/Spv.Generator/Instruction.cs new file mode 100644 index 00000000..45492033 --- /dev/null +++ b/src/Spv.Generator/Instruction.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +namespace Spv.Generator +{ + public sealed class Instruction : IOperand, IEquatable + { + public const uint InvalidId = uint.MaxValue; + + public Specification.Op Opcode { get; private set; } + private Instruction _resultType; + private InstructionOperands _operands; + + public uint Id { get; set; } + + public Instruction() { } + + public void Set(Specification.Op opcode, uint id = InvalidId, Instruction resultType = null) + { + Opcode = opcode; + Id = id; + _resultType = resultType; + + _operands = new InstructionOperands(); + } + + public void SetId(uint id) + { + Id = id; + } + + public OperandType Type => OperandType.Instruction; + + public ushort GetTotalWordCount() + { + ushort result = WordCount; + + if (Id != InvalidId) + { + result++; + } + + if (_resultType != null) + { + result += _resultType.WordCount; + } + + Span operands = _operands.AsSpan(); + for (int i = 0; i < operands.Length; i++) + { + result += operands[i].WordCount; + } + + return result; + } + + public ushort WordCount => 1; + + public void AddOperand(IOperand value) + { + Debug.Assert(value != null); + _operands.Add(value); + } + + public void AddOperand(IOperand[] value) + { + foreach (IOperand instruction in value) + { + AddOperand(instruction); + } + } + + public void AddOperand(LiteralInteger[] value) + { + foreach (LiteralInteger instruction in value) + { + AddOperand(instruction); + } + } + + public void AddOperand(LiteralInteger value) + { + AddOperand((IOperand)value); + } + + public void AddOperand(Instruction[] value) + { + foreach (Instruction instruction in value) + { + AddOperand(instruction); + } + } + + public void AddOperand(Instruction value) + { + AddOperand((IOperand)value); + } + + public void AddOperand(string value) + { + AddOperand(new LiteralString(value)); + } + + public void AddOperand(T value) where T : Enum + { + AddOperand(LiteralInteger.CreateForEnum(value)); + } + + public void Write(BinaryWriter writer) + { + // Word 0 + writer.Write((ushort)Opcode); + writer.Write(GetTotalWordCount()); + + _resultType?.WriteOperand(writer); + + if (Id != InvalidId) + { + writer.Write(Id); + } + + Span operands = _operands.AsSpan(); + for (int i = 0; i < operands.Length; i++) + { + operands[i].WriteOperand(writer); + } + } + + public void WriteOperand(BinaryWriter writer) + { + Debug.Assert(Id != InvalidId); + + if (Id == InvalidId) + { + string methodToCall; + + if (Opcode == Specification.Op.OpVariable) + { + methodToCall = "AddLocalVariable or AddGlobalVariable"; + } + else if (Opcode == Specification.Op.OpLabel) + { + methodToCall = "AddLabel"; + } + else + { + throw new InvalidOperationException("Internal error"); + } + + throw new InvalidOperationException($"Id wasn't bound to the module, please make sure to call {methodToCall}"); + } + + writer.Write(Id); + } + + public override bool Equals(object obj) + { + return obj is Instruction instruction && Equals(instruction); + } + + public bool Equals(Instruction cmpObj) + { + bool result = Type == cmpObj.Type && Id == cmpObj.Id; + + if (result) + { + if (_resultType != null && cmpObj._resultType != null) + { + result &= _resultType.Equals(cmpObj._resultType); + } + else if (_resultType != null || cmpObj._resultType != null) + { + return false; + } + } + + if (result) + { + result &= EqualsContent(cmpObj); + } + + return result; + } + + public bool EqualsContent(Instruction cmpObj) + { + Span thisOperands = _operands.AsSpan(); + Span cmpOperands = cmpObj._operands.AsSpan(); + + if (thisOperands.Length != cmpOperands.Length) + { + return false; + } + + for (int i = 0; i < thisOperands.Length; i++) + { + if (!thisOperands[i].Equals(cmpOperands[i])) + { + return false; + } + } + + return true; + } + + public bool EqualsResultType(Instruction cmpObj) + { + return _resultType.Opcode == cmpObj._resultType.Opcode && _resultType.EqualsContent(cmpObj._resultType); + } + + public int GetHashCodeContent() + { + return DeterministicHashCode.Combine(_operands.AsSpan()); + } + + public int GetHashCodeResultType() + { + return DeterministicHashCode.Combine(_resultType.Opcode, _resultType.GetHashCodeContent()); + } + + public override int GetHashCode() + { + return DeterministicHashCode.Combine(Opcode, Id, _resultType, DeterministicHashCode.Combine(_operands.AsSpan())); + } + + public bool Equals(IOperand obj) + { + return obj is Instruction instruction && Equals(instruction); + } + + private static readonly Dictionary _operandLabels = new() + { + { Specification.Op.OpConstant, new [] { "Value" } }, + { Specification.Op.OpTypeInt, new [] { "Width", "Signed" } }, + { Specification.Op.OpTypeFloat, new [] { "Width" } }, + }; + + public override string ToString() + { + var labels = _operandLabels.TryGetValue(Opcode, out var opLabels) ? opLabels : Array.Empty(); + var result = _resultType == null ? string.Empty : $"{_resultType} "; + return $"{result}{Opcode}{_operands.ToString(labels)}"; + } + } +} diff --git a/src/Spv.Generator/InstructionOperands.cs b/src/Spv.Generator/InstructionOperands.cs new file mode 100644 index 00000000..de74f112 --- /dev/null +++ b/src/Spv.Generator/InstructionOperands.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Spv.Generator +{ + public struct InstructionOperands + { + private const int InternalCount = 5; + + public int Count; + public IOperand Operand1; + public IOperand Operand2; + public IOperand Operand3; + public IOperand Operand4; + public IOperand Operand5; + public IOperand[] Overflow; + + public Span AsSpan() + { + if (Count > InternalCount) + { + return MemoryMarshal.CreateSpan(ref this.Overflow[0], Count); + } + else + { + return MemoryMarshal.CreateSpan(ref this.Operand1, Count); + } + } + + public void Add(IOperand operand) + { + if (Count < InternalCount) + { + MemoryMarshal.CreateSpan(ref this.Operand1, Count + 1)[Count] = operand; + Count++; + } + else + { + if (Overflow == null) + { + Overflow = new IOperand[InternalCount * 2]; + MemoryMarshal.CreateSpan(ref this.Operand1, InternalCount).CopyTo(Overflow.AsSpan()); + } + else if (Count == Overflow.Length) + { + Array.Resize(ref Overflow, Overflow.Length * 2); + } + + Overflow[Count++] = operand; + } + } + + private readonly IEnumerable AllOperands => new[] { Operand1, Operand2, Operand3, Operand4, Operand5 } + .Concat(Overflow ?? Array.Empty()) + .Take(Count); + + public readonly override string ToString() + { + return $"({string.Join(", ", AllOperands)})"; + } + + public readonly string ToString(string[] labels) + { + var labeledParams = AllOperands.Zip(labels, (op, label) => $"{label}: {op}"); + var unlabeledParams = AllOperands.Skip(labels.Length).Select(op => op.ToString()); + var paramsToPrint = labeledParams.Concat(unlabeledParams); + return $"({string.Join(", ", paramsToPrint)})"; + } + } +} diff --git a/src/Spv.Generator/LICENSE b/src/Spv.Generator/LICENSE new file mode 100644 index 00000000..31aa7938 --- /dev/null +++ b/src/Spv.Generator/LICENSE @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/Spv.Generator/LiteralInteger.cs b/src/Spv.Generator/LiteralInteger.cs new file mode 100644 index 00000000..013ca3b9 --- /dev/null +++ b/src/Spv.Generator/LiteralInteger.cs @@ -0,0 +1,105 @@ +using System; +using System.IO; + +namespace Spv.Generator +{ + public class LiteralInteger : IOperand, IEquatable + { + [ThreadStatic] + private static GeneratorPool _pool; + + internal static void RegisterPool(GeneratorPool pool) + { + _pool = pool; + } + + internal static void UnregisterPool() + { + _pool = null; + } + + public OperandType Type => OperandType.Number; + + private enum IntegerType + { + UInt32, + Int32, + UInt64, + Int64, + Float32, + Float64, + } + + private IntegerType _integerType; + private ulong _data; + + public ushort WordCount { get; private set; } + + public LiteralInteger() { } + + private static LiteralInteger New() + { + return _pool.Allocate(); + } + + private LiteralInteger Set(ulong data, IntegerType integerType, ushort wordCount) + { + _data = data; + _integerType = integerType; + + WordCount = wordCount; + + return this; + } + + public static implicit operator LiteralInteger(int value) => New().Set((ulong)value, IntegerType.Int32, 1); + public static implicit operator LiteralInteger(uint value) => New().Set(value, IntegerType.UInt32, 1); + public static implicit operator LiteralInteger(long value) => New().Set((ulong)value, IntegerType.Int64, 2); + public static implicit operator LiteralInteger(ulong value) => New().Set(value, IntegerType.UInt64, 2); + public static implicit operator LiteralInteger(float value) => New().Set(BitConverter.SingleToUInt32Bits(value), IntegerType.Float32, 1); + public static implicit operator LiteralInteger(double value) => New().Set(BitConverter.DoubleToUInt64Bits(value), IntegerType.Float64, 2); + public static implicit operator LiteralInteger(Enum value) => New().Set((ulong)(int)(object)value, IntegerType.Int32, 1); + + // NOTE: this is not in the standard, but this is some syntax sugar useful in some instructions (TypeInt ect) + public static implicit operator LiteralInteger(bool value) => New().Set(Convert.ToUInt64(value), IntegerType.Int32, 1); + + public static LiteralInteger CreateForEnum(T value) where T : Enum + { + return value; + } + + public void WriteOperand(BinaryWriter writer) + { + if (WordCount == 1) + { + writer.Write((uint)_data); + } + else + { + writer.Write(_data); + } + } + + public override bool Equals(object obj) + { + return obj is LiteralInteger literalInteger && Equals(literalInteger); + } + + public bool Equals(LiteralInteger cmpObj) + { + return Type == cmpObj.Type && _integerType == cmpObj._integerType && _data == cmpObj._data; + } + + public override int GetHashCode() + { + return DeterministicHashCode.Combine(Type, _data); + } + + public bool Equals(IOperand obj) + { + return obj is LiteralInteger literalInteger && Equals(literalInteger); + } + + public override string ToString() => $"{_integerType} {_data}"; + } +} diff --git a/src/Spv.Generator/LiteralString.cs b/src/Spv.Generator/LiteralString.cs new file mode 100644 index 00000000..ed20d0e8 --- /dev/null +++ b/src/Spv.Generator/LiteralString.cs @@ -0,0 +1,54 @@ +using System; +using System.IO; +using System.Text; + +namespace Spv.Generator +{ + public class LiteralString : IOperand, IEquatable + { + public OperandType Type => OperandType.String; + + private readonly string _value; + + public LiteralString(string value) + { + _value = value; + } + + public ushort WordCount => (ushort)(_value.Length / 4 + 1); + + public void WriteOperand(BinaryWriter writer) + { + writer.Write(_value.AsSpan()); + + // String must be padded to the word size (which is 4 bytes). + int paddingSize = 4 - (Encoding.ASCII.GetByteCount(_value) % 4); + + Span padding = stackalloc byte[paddingSize]; + + writer.Write(padding); + } + + public override bool Equals(object obj) + { + return obj is LiteralString literalString && Equals(literalString); + } + + public bool Equals(LiteralString cmpObj) + { + return Type == cmpObj.Type && _value.Equals(cmpObj._value); + } + + public override int GetHashCode() + { + return DeterministicHashCode.Combine(Type, DeterministicHashCode.GetHashCode(_value)); + } + + public bool Equals(IOperand obj) + { + return obj is LiteralString literalString && Equals(literalString); + } + + public override string ToString() => _value; + } +} diff --git a/src/Spv.Generator/Module.cs b/src/Spv.Generator/Module.cs new file mode 100644 index 00000000..fb02cc96 --- /dev/null +++ b/src/Spv.Generator/Module.cs @@ -0,0 +1,367 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using static Spv.Specification; + +namespace Spv.Generator +{ + public partial class Module + { + // TODO: Register on SPIR-V registry. + private const int GeneratorId = 0; + + private readonly uint _version; + + private uint _bound; + + // Follow spec order here while keeping it as simple as possible. + private readonly List _capabilities; + private readonly List _extensions; + private readonly Dictionary _extInstImports; + private AddressingModel _addressingModel; + private MemoryModel _memoryModel; + + private readonly List _entrypoints; + private readonly List _executionModes; + private readonly List _debug; + private readonly List _annotations; + + // In the declaration block. + private readonly Dictionary _typeDeclarations; + private readonly List _typeDeclarationsList; + // In the declaration block. + private readonly List _globals; + // In the declaration block. + private readonly Dictionary _constants; + // In the declaration block, for function that aren't defined in the module. + private readonly List _functionsDeclarations; + + private readonly List _functionsDefinitions; + + private readonly GeneratorPool _instPool; + private readonly GeneratorPool _integerPool; + + public Module(uint version, GeneratorPool instPool = null, GeneratorPool integerPool = null) + { + _version = version; + _bound = 1; + _capabilities = new List(); + _extensions = new List(); + _extInstImports = new Dictionary(); + _addressingModel = AddressingModel.Logical; + _memoryModel = MemoryModel.Simple; + _entrypoints = new List(); + _executionModes = new List(); + _debug = new List(); + _annotations = new List(); + _typeDeclarations = new Dictionary(); + _typeDeclarationsList = new List(); + _constants = new Dictionary(); + _globals = new List(); + _functionsDeclarations = new List(); + _functionsDefinitions = new List(); + + _instPool = instPool ?? new GeneratorPool(); + _integerPool = integerPool ?? new GeneratorPool(); + + LiteralInteger.RegisterPool(_integerPool); + } + + private uint GetNewId() + { + return _bound++; + } + + public void AddCapability(Capability capability) + { + _capabilities.Add(capability); + } + + public void AddExtension(string extension) + { + _extensions.Add(extension); + } + + public Instruction NewInstruction(Op opcode, uint id = Instruction.InvalidId, Instruction resultType = null) + { + var result = _instPool.Allocate(); + result.Set(opcode, id, resultType); + + return result; + } + + public Instruction AddExtInstImport(string import) + { + var key = new DeterministicStringKey(import); + + if (_extInstImports.TryGetValue(key, out Instruction extInstImport)) + { + // Update the duplicate instance to use the good id so it ends up being encoded correctly. + return extInstImport; + } + + Instruction instruction = NewInstruction(Op.OpExtInstImport); + instruction.AddOperand(import); + + instruction.SetId(GetNewId()); + + _extInstImports.Add(key, instruction); + + return instruction; + } + + private void AddTypeDeclaration(Instruction instruction, bool forceIdAllocation) + { + var key = new TypeDeclarationKey(instruction); + + if (!forceIdAllocation) + { + if (_typeDeclarations.TryGetValue(key, out Instruction typeDeclaration)) + { + // Update the duplicate instance to use the good id so it ends up being encoded correctly. + + instruction.SetId(typeDeclaration.Id); + + return; + } + } + + instruction.SetId(GetNewId()); + + _typeDeclarations[key] = instruction; + _typeDeclarationsList.Add(instruction); + } + + public void AddEntryPoint(ExecutionModel executionModel, Instruction function, string name, params Instruction[] interfaces) + { + Debug.Assert(function.Opcode == Op.OpFunction); + + Instruction entryPoint = NewInstruction(Op.OpEntryPoint); + + entryPoint.AddOperand(executionModel); + entryPoint.AddOperand(function); + entryPoint.AddOperand(name); + entryPoint.AddOperand(interfaces); + + _entrypoints.Add(entryPoint); + } + + public void AddExecutionMode(Instruction function, ExecutionMode mode, params IOperand[] parameters) + { + Debug.Assert(function.Opcode == Op.OpFunction); + + Instruction executionModeInstruction = NewInstruction(Op.OpExecutionMode); + + executionModeInstruction.AddOperand(function); + executionModeInstruction.AddOperand(mode); + executionModeInstruction.AddOperand(parameters); + + _executionModes.Add(executionModeInstruction); + } + + private void AddToFunctionDefinitions(Instruction instruction) + { + Debug.Assert(instruction.Opcode != Op.OpTypeInt); + _functionsDefinitions.Add(instruction); + } + + private void AddAnnotation(Instruction annotation) + { + _annotations.Add(annotation); + } + + private void AddDebug(Instruction debug) + { + _debug.Add(debug); + } + + public void AddLabel(Instruction label) + { + Debug.Assert(label.Opcode == Op.OpLabel); + + label.SetId(GetNewId()); + + AddToFunctionDefinitions(label); + } + + public void AddLocalVariable(Instruction variable) + { + // TODO: Ensure it has the local modifier. + Debug.Assert(variable.Opcode == Op.OpVariable); + + variable.SetId(GetNewId()); + + AddToFunctionDefinitions(variable); + } + + public void AddGlobalVariable(Instruction variable) + { + // TODO: Ensure it has the global modifier. + // TODO: All constants opcodes (OpSpecXXX and the rest of the OpConstantXXX). + Debug.Assert(variable.Opcode == Op.OpVariable); + + variable.SetId(GetNewId()); + + _globals.Add(variable); + } + + private void AddConstant(Instruction constant) + { + Debug.Assert(constant.Opcode == Op.OpConstant || + constant.Opcode == Op.OpConstantFalse || + constant.Opcode == Op.OpConstantTrue || + constant.Opcode == Op.OpConstantNull || + constant.Opcode == Op.OpConstantComposite); + + var key = new ConstantKey(constant); + + if (_constants.TryGetValue(key, out Instruction global)) + { + // Update the duplicate instance to use the good id so it ends up being encoded correctly. + constant.SetId(global.Id); + + return; + } + + constant.SetId(GetNewId()); + + _constants.Add(key, constant); + } + + public Instruction ExtInst(Instruction resultType, Instruction set, LiteralInteger instruction, params IOperand[] parameters) + { + Instruction result = NewInstruction(Op.OpExtInst, GetNewId(), resultType); + + result.AddOperand(set); + result.AddOperand(instruction); + result.AddOperand(parameters); + AddToFunctionDefinitions(result); + + return result; + } + + public void SetMemoryModel(AddressingModel addressingModel, MemoryModel memoryModel) + { + _addressingModel = addressingModel; + _memoryModel = memoryModel; + } + + // TODO: Find a way to make the auto generate one used. + public Instruction OpenClPrintf(Instruction resultType, Instruction format, params Instruction[] additionalarguments) + { + Instruction result = NewInstruction(Op.OpExtInst, GetNewId(), resultType); + + result.AddOperand(AddExtInstImport("OpenCL.std")); + result.AddOperand((LiteralInteger)184); + result.AddOperand(format); + result.AddOperand(additionalarguments); + AddToFunctionDefinitions(result); + + return result; + } + + public byte[] Generate() + { + // Estimate the size needed for the generated code, to avoid expanding the MemoryStream. + int sizeEstimate = 1024 + _functionsDefinitions.Count * 32; + + using MemoryStream stream = new(sizeEstimate); + + BinaryWriter writer = new(stream, System.Text.Encoding.ASCII); + + // Header + writer.Write(MagicNumber); + writer.Write(_version); + writer.Write(GeneratorId); + writer.Write(_bound); + writer.Write(0u); + + // 1. + foreach (Capability capability in _capabilities) + { + Instruction capabilityInstruction = NewInstruction(Op.OpCapability); + + capabilityInstruction.AddOperand(capability); + capabilityInstruction.Write(writer); + } + + // 2. + foreach (string extension in _extensions) + { + Instruction extensionInstruction = NewInstruction(Op.OpExtension); + + extensionInstruction.AddOperand(extension); + extensionInstruction.Write(writer); + } + + // 3. + foreach (Instruction extInstImport in _extInstImports.Values) + { + extInstImport.Write(writer); + } + + // 4. + Instruction memoryModelInstruction = NewInstruction(Op.OpMemoryModel); + memoryModelInstruction.AddOperand(_addressingModel); + memoryModelInstruction.AddOperand(_memoryModel); + memoryModelInstruction.Write(writer); + + // 5. + foreach (Instruction entrypoint in _entrypoints) + { + entrypoint.Write(writer); + } + + // 6. + foreach (Instruction executionMode in _executionModes) + { + executionMode.Write(writer); + } + + // 7. + // TODO: Order debug information correctly. + foreach (Instruction debug in _debug) + { + debug.Write(writer); + } + + // 8. + foreach (Instruction annotation in _annotations) + { + annotation.Write(writer); + } + + // Ensure that everything is in the right order in the declarations section. + List declarations = new(); + declarations.AddRange(_typeDeclarationsList); + declarations.AddRange(_globals); + declarations.AddRange(_constants.Values); + declarations.Sort((Instruction x, Instruction y) => x.Id.CompareTo(y.Id)); + + // 9. + foreach (Instruction declaration in declarations) + { + declaration.Write(writer); + } + + // 10. + foreach (Instruction functionDeclaration in _functionsDeclarations) + { + functionDeclaration.Write(writer); + } + + // 11. + foreach (Instruction functionDefinition in _functionsDefinitions) + { + functionDefinition.Write(writer); + } + + _instPool.Clear(); + _integerPool.Clear(); + + LiteralInteger.UnregisterPool(); + + return stream.ToArray(); + } + } +} diff --git a/src/Spv.Generator/OperandType.cs b/src/Spv.Generator/OperandType.cs new file mode 100644 index 00000000..ddbdc89d --- /dev/null +++ b/src/Spv.Generator/OperandType.cs @@ -0,0 +1,10 @@ +namespace Spv.Generator +{ + public enum OperandType + { + Invalid, + Number, + String, + Instruction, + } +} diff --git a/src/Spv.Generator/Spv.Generator.csproj b/src/Spv.Generator/Spv.Generator.csproj new file mode 100644 index 00000000..ae2821ed --- /dev/null +++ b/src/Spv.Generator/Spv.Generator.csproj @@ -0,0 +1,7 @@ + + + + net8.0 + + + diff --git a/src/Spv.Generator/TypeDeclarationKey.cs b/src/Spv.Generator/TypeDeclarationKey.cs new file mode 100644 index 00000000..aaaf4fd3 --- /dev/null +++ b/src/Spv.Generator/TypeDeclarationKey.cs @@ -0,0 +1,30 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Spv.Generator +{ + internal readonly struct TypeDeclarationKey : IEquatable + { + private readonly Instruction _typeDeclaration; + + public TypeDeclarationKey(Instruction typeDeclaration) + { + _typeDeclaration = typeDeclaration; + } + + public override int GetHashCode() + { + return DeterministicHashCode.Combine(_typeDeclaration.Opcode, _typeDeclaration.GetHashCodeContent()); + } + + public bool Equals(TypeDeclarationKey other) + { + return _typeDeclaration.Opcode == other._typeDeclaration.Opcode && _typeDeclaration.EqualsContent(other._typeDeclaration); + } + + public override bool Equals([NotNullWhen(true)] object obj) + { + return obj is TypeDeclarationKey key && Equals(key); + } + } +} diff --git a/src/Spv.Generator/spirv.cs b/src/Spv.Generator/spirv.cs new file mode 100644 index 00000000..7844c970 --- /dev/null +++ b/src/Spv.Generator/spirv.cs @@ -0,0 +1,1624 @@ +// Copyright (c) 2014-2020 The Khronos Group Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and/or associated documentation files (the "Materials"), +// to deal in the Materials without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Materials, and to permit persons to whom the +// Materials are furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Materials. +// +// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +// STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +// HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +// +// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +// IN THE MATERIALS. + +// This header is automatically generated by the same tool that creates +// the Binary Section of the SPIR-V specification. + +// Enumeration tokens for SPIR-V, in various styles: +// C, C++, C++11, JSON, Lua, Python, C#, D +// +// - C will have tokens with a "Spv" prefix, e.g.: SpvSourceLanguageGLSL +// - C++ will have tokens in the "spv" name space, e.g.: spv::SourceLanguageGLSL +// - C++11 will use enum classes in the spv namespace, e.g.: spv::SourceLanguage::GLSL +// - Lua will use tables, e.g.: spv.SourceLanguage.GLSL +// - Python will use dictionaries, e.g.: spv['SourceLanguage']['GLSL'] +// - C# will use enum classes in the Specification class located in the "Spv" namespace, +// e.g.: Spv.Specification.SourceLanguage.GLSL +// - D will have tokens under the "spv" module, e.g: spv.SourceLanguage.GLSL +// +// Some tokens act like mask values, which can be OR'd together, +// while others are mutually exclusive. The mask-like ones have +// "Mask" in their name, and a parallel enum that has the shift +// amount (1 << x) for each corresponding enumerant. + +namespace Spv +{ + + public static class Specification + { + public const uint MagicNumber = 0x07230203; + public const uint Version = 0x00010500; + public const uint Revision = 4; + public const uint OpCodeMask = 0xffff; + public const uint WordCountShift = 16; + + public enum SourceLanguage + { + Unknown = 0, + ESSL = 1, + GLSL = 2, + OpenCL_C = 3, + OpenCL_CPP = 4, + HLSL = 5, + } + + public enum ExecutionModel + { + Vertex = 0, + TessellationControl = 1, + TessellationEvaluation = 2, + Geometry = 3, + Fragment = 4, + GLCompute = 5, + Kernel = 6, + TaskNV = 5267, + MeshNV = 5268, + RayGenerationKHR = 5313, + RayGenerationNV = RayGenerationKHR, + IntersectionKHR = 5314, + IntersectionNV = IntersectionKHR, + AnyHitKHR = 5315, + AnyHitNV = AnyHitKHR, + ClosestHitKHR = 5316, + ClosestHitNV = ClosestHitKHR, + MissKHR = 5317, + MissNV = MissKHR, + CallableKHR = 5318, + CallableNV = CallableKHR, + } + + public enum AddressingModel + { + Logical = 0, + Physical32 = 1, + Physical64 = 2, + PhysicalStorageBuffer64 = 5348, + PhysicalStorageBuffer64EXT = PhysicalStorageBuffer64, + } + + public enum MemoryModel + { + Simple = 0, + GLSL450 = 1, + OpenCL = 2, + Vulkan = 3, + VulkanKHR = Vulkan, + } + + public enum ExecutionMode + { + Invocations = 0, + SpacingEqual = 1, + SpacingFractionalEven = 2, + SpacingFractionalOdd = 3, + VertexOrderCw = 4, + VertexOrderCcw = 5, + PixelCenterInteger = 6, + OriginUpperLeft = 7, + OriginLowerLeft = 8, + EarlyFragmentTests = 9, + PointMode = 10, + Xfb = 11, + DepthReplacing = 12, + DepthGreater = 14, + DepthLess = 15, + DepthUnchanged = 16, + LocalSize = 17, + LocalSizeHint = 18, + InputPoints = 19, + InputLines = 20, + InputLinesAdjacency = 21, + Triangles = 22, + InputTrianglesAdjacency = 23, + Quads = 24, + Isolines = 25, + OutputVertices = 26, + OutputPoints = 27, + OutputLineStrip = 28, + OutputTriangleStrip = 29, + VecTypeHint = 30, + ContractionOff = 31, + Initializer = 33, + Finalizer = 34, + SubgroupSize = 35, + SubgroupsPerWorkgroup = 36, + SubgroupsPerWorkgroupId = 37, + LocalSizeId = 38, + LocalSizeHintId = 39, + PostDepthCoverage = 4446, + DenormPreserve = 4459, + DenormFlushToZero = 4460, + SignedZeroInfNanPreserve = 4461, + RoundingModeRTE = 4462, + RoundingModeRTZ = 4463, + StencilRefReplacingEXT = 5027, + OutputLinesNV = 5269, + OutputPrimitivesNV = 5270, + DerivativeGroupQuadsNV = 5289, + DerivativeGroupLinearNV = 5290, + OutputTrianglesNV = 5298, + PixelInterlockOrderedEXT = 5366, + PixelInterlockUnorderedEXT = 5367, + SampleInterlockOrderedEXT = 5368, + SampleInterlockUnorderedEXT = 5369, + ShadingRateInterlockOrderedEXT = 5370, + ShadingRateInterlockUnorderedEXT = 5371, + MaxWorkgroupSizeINTEL = 5893, + MaxWorkDimINTEL = 5894, + NoGlobalOffsetINTEL = 5895, + NumSIMDWorkitemsINTEL = 5896, + } + + public enum StorageClass + { + UniformConstant = 0, + Input = 1, + Uniform = 2, + Output = 3, + Workgroup = 4, + CrossWorkgroup = 5, + Private = 6, + Function = 7, + Generic = 8, + PushConstant = 9, + AtomicCounter = 10, + Image = 11, + StorageBuffer = 12, + CallableDataKHR = 5328, + CallableDataNV = CallableDataKHR, + IncomingCallableDataKHR = 5329, + IncomingCallableDataNV = IncomingCallableDataKHR, + RayPayloadKHR = 5338, + RayPayloadNV = RayPayloadKHR, + HitAttributeKHR = 5339, + HitAttributeNV = HitAttributeKHR, + IncomingRayPayloadKHR = 5342, + IncomingRayPayloadNV = IncomingRayPayloadKHR, + ShaderRecordBufferKHR = 5343, + ShaderRecordBufferNV = ShaderRecordBufferKHR, + PhysicalStorageBuffer = 5349, + PhysicalStorageBufferEXT = PhysicalStorageBuffer, + CodeSectionINTEL = 5605, + } + + public enum Dim + { + Dim1D = 0, + Dim2D = 1, + Dim3D = 2, + Cube = 3, + Rect = 4, + Buffer = 5, + SubpassData = 6, + } + + public enum SamplerAddressingMode + { + None = 0, + ClampToEdge = 1, + Clamp = 2, + Repeat = 3, + RepeatMirrored = 4, + } + + public enum SamplerFilterMode + { + Nearest = 0, + Linear = 1, + } + + public enum ImageFormat + { + Unknown = 0, + Rgba32f = 1, + Rgba16f = 2, + R32f = 3, + Rgba8 = 4, + Rgba8Snorm = 5, + Rg32f = 6, + Rg16f = 7, + R11fG11fB10f = 8, + R16f = 9, + Rgba16 = 10, + Rgb10A2 = 11, + Rg16 = 12, + Rg8 = 13, + R16 = 14, + R8 = 15, + Rgba16Snorm = 16, + Rg16Snorm = 17, + Rg8Snorm = 18, + R16Snorm = 19, + R8Snorm = 20, + Rgba32i = 21, + Rgba16i = 22, + Rgba8i = 23, + R32i = 24, + Rg32i = 25, + Rg16i = 26, + Rg8i = 27, + R16i = 28, + R8i = 29, + Rgba32ui = 30, + Rgba16ui = 31, + Rgba8ui = 32, + R32ui = 33, + Rgb10a2ui = 34, + Rg32ui = 35, + Rg16ui = 36, + Rg8ui = 37, + R16ui = 38, + R8ui = 39, + R64ui = 40, + R64i = 41, + } + + public enum ImageChannelOrder + { + R = 0, + A = 1, + RG = 2, + RA = 3, + RGB = 4, + RGBA = 5, + BGRA = 6, + ARGB = 7, + Intensity = 8, + Luminance = 9, + Rx = 10, + RGx = 11, + RGBx = 12, + Depth = 13, + DepthStencil = 14, + sRGB = 15, + sRGBx = 16, + sRGBA = 17, + sBGRA = 18, + ABGR = 19, + } + + public enum ImageChannelDataType + { + SnormInt8 = 0, + SnormInt16 = 1, + UnormInt8 = 2, + UnormInt16 = 3, + UnormShort565 = 4, + UnormShort555 = 5, + UnormInt101010 = 6, + SignedInt8 = 7, + SignedInt16 = 8, + SignedInt32 = 9, + UnsignedInt8 = 10, + UnsignedInt16 = 11, + UnsignedInt32 = 12, + HalfFloat = 13, + Float = 14, + UnormInt24 = 15, + UnormInt101010_2 = 16, + } + + public enum ImageOperandsShift + { + Bias = 0, + Lod = 1, + Grad = 2, + ConstOffset = 3, + Offset = 4, + ConstOffsets = 5, + Sample = 6, + MinLod = 7, + MakeTexelAvailable = 8, + MakeTexelAvailableKHR = MakeTexelAvailable, + MakeTexelVisible = 9, + MakeTexelVisibleKHR = MakeTexelVisible, + NonPrivateTexel = 10, + NonPrivateTexelKHR = NonPrivateTexel, + VolatileTexel = 11, + VolatileTexelKHR = VolatileTexel, + SignExtend = 12, + ZeroExtend = 13, + } + + public enum ImageOperandsMask + { + MaskNone = 0, + Bias = 0x00000001, + Lod = 0x00000002, + Grad = 0x00000004, + ConstOffset = 0x00000008, + Offset = 0x00000010, + ConstOffsets = 0x00000020, + Sample = 0x00000040, + MinLod = 0x00000080, + MakeTexelAvailable = 0x00000100, + MakeTexelAvailableKHR = MakeTexelAvailable, + MakeTexelVisible = 0x00000200, + MakeTexelVisibleKHR = MakeTexelVisible, + NonPrivateTexel = 0x00000400, + NonPrivateTexelKHR = NonPrivateTexel, + VolatileTexel = 0x00000800, + VolatileTexelKHR = VolatileTexel, + SignExtend = 0x00001000, + ZeroExtend = 0x00002000, + Offsets = 0x00010000, + } + + public enum FPFastMathModeShift + { + NotNaN = 0, + NotInf = 1, + NSZ = 2, + AllowRecip = 3, + Fast = 4, + } + + public enum FPFastMathModeMask + { + MaskNone = 0, + NotNaN = 0x00000001, + NotInf = 0x00000002, + NSZ = 0x00000004, + AllowRecip = 0x00000008, + Fast = 0x00000010, + } + + public enum FPRoundingMode + { + RTE = 0, + RTZ = 1, + RTP = 2, + RTN = 3, + } + + public enum LinkageType + { + Export = 0, + Import = 1, + } + + public enum AccessQualifier + { + ReadOnly = 0, + WriteOnly = 1, + ReadWrite = 2, + } + + public enum FunctionParameterAttribute + { + Zext = 0, + Sext = 1, + ByVal = 2, + Sret = 3, + NoAlias = 4, + NoCapture = 5, + NoWrite = 6, + NoReadWrite = 7, + } + + public enum Decoration + { + RelaxedPrecision = 0, + SpecId = 1, + Block = 2, + BufferBlock = 3, + RowMajor = 4, + ColMajor = 5, + ArrayStride = 6, + MatrixStride = 7, + GLSLShared = 8, + GLSLPacked = 9, + CPacked = 10, + BuiltIn = 11, + NoPerspective = 13, + Flat = 14, + Patch = 15, + Centroid = 16, + Sample = 17, + Invariant = 18, + Restrict = 19, + Aliased = 20, + Volatile = 21, + Constant = 22, + Coherent = 23, + NonWritable = 24, + NonReadable = 25, + Uniform = 26, + UniformId = 27, + SaturatedConversion = 28, + Stream = 29, + Location = 30, + Component = 31, + Index = 32, + Binding = 33, + DescriptorSet = 34, + Offset = 35, + XfbBuffer = 36, + XfbStride = 37, + FuncParamAttr = 38, + FPRoundingMode = 39, + FPFastMathMode = 40, + LinkageAttributes = 41, + NoContraction = 42, + InputAttachmentIndex = 43, + Alignment = 44, + MaxByteOffset = 45, + AlignmentId = 46, + MaxByteOffsetId = 47, + NoSignedWrap = 4469, + NoUnsignedWrap = 4470, + ExplicitInterpAMD = 4999, + OverrideCoverageNV = 5248, + PassthroughNV = 5250, + ViewportRelativeNV = 5252, + SecondaryViewportRelativeNV = 5256, + PerPrimitiveNV = 5271, + PerViewNV = 5272, + PerTaskNV = 5273, + PerVertexNV = 5285, + NonUniform = 5300, + NonUniformEXT = NonUniform, + RestrictPointer = 5355, + RestrictPointerEXT = RestrictPointer, + AliasedPointer = 5356, + AliasedPointerEXT = AliasedPointer, + ReferencedIndirectlyINTEL = 5602, + CounterBuffer = 5634, + HlslCounterBufferGOOGLE = CounterBuffer, + HlslSemanticGOOGLE = 5635, + UserSemantic = HlslSemanticGOOGLE, + UserTypeGOOGLE = 5636, + RegisterINTEL = 5825, + MemoryINTEL = 5826, + NumbanksINTEL = 5827, + BankwidthINTEL = 5828, + MaxPrivateCopiesINTEL = 5829, + SinglepumpINTEL = 5830, + DoublepumpINTEL = 5831, + MaxReplicatesINTEL = 5832, + SimpleDualPortINTEL = 5833, + MergeINTEL = 5834, + BankBitsINTEL = 5835, + ForcePow2DepthINTEL = 5836, + } + + public enum BuiltIn + { + Position = 0, + PointSize = 1, + ClipDistance = 3, + CullDistance = 4, + VertexId = 5, + InstanceId = 6, + PrimitiveId = 7, + InvocationId = 8, + Layer = 9, + ViewportIndex = 10, + TessLevelOuter = 11, + TessLevelInner = 12, + TessCoord = 13, + PatchVertices = 14, + FragCoord = 15, + PointCoord = 16, + FrontFacing = 17, + SampleId = 18, + SamplePosition = 19, + SampleMask = 20, + FragDepth = 22, + HelperInvocation = 23, + NumWorkgroups = 24, + WorkgroupSize = 25, + WorkgroupId = 26, + LocalInvocationId = 27, + GlobalInvocationId = 28, + LocalInvocationIndex = 29, + WorkDim = 30, + GlobalSize = 31, + EnqueuedWorkgroupSize = 32, + GlobalOffset = 33, + GlobalLinearId = 34, + SubgroupSize = 36, + SubgroupMaxSize = 37, + NumSubgroups = 38, + NumEnqueuedSubgroups = 39, + SubgroupId = 40, + SubgroupLocalInvocationId = 41, + VertexIndex = 42, + InstanceIndex = 43, + SubgroupEqMask = 4416, + SubgroupEqMaskKHR = SubgroupEqMask, + SubgroupGeMask = 4417, + SubgroupGeMaskKHR = SubgroupGeMask, + SubgroupGtMask = 4418, + SubgroupGtMaskKHR = SubgroupGtMask, + SubgroupLeMask = 4419, + SubgroupLeMaskKHR = SubgroupLeMask, + SubgroupLtMask = 4420, + SubgroupLtMaskKHR = SubgroupLtMask, + BaseVertex = 4424, + BaseInstance = 4425, + DrawIndex = 4426, + PrimitiveShadingRateKHR = 4432, + DeviceIndex = 4438, + ViewIndex = 4440, + ShadingRateKHR = 4444, + BaryCoordNoPerspAMD = 4992, + BaryCoordNoPerspCentroidAMD = 4993, + BaryCoordNoPerspSampleAMD = 4994, + BaryCoordSmoothAMD = 4995, + BaryCoordSmoothCentroidAMD = 4996, + BaryCoordSmoothSampleAMD = 4997, + BaryCoordPullModelAMD = 4998, + FragStencilRefEXT = 5014, + ViewportMaskNV = 5253, + SecondaryPositionNV = 5257, + SecondaryViewportMaskNV = 5258, + PositionPerViewNV = 5261, + ViewportMaskPerViewNV = 5262, + FullyCoveredEXT = 5264, + TaskCountNV = 5274, + PrimitiveCountNV = 5275, + PrimitiveIndicesNV = 5276, + ClipDistancePerViewNV = 5277, + CullDistancePerViewNV = 5278, + LayerPerViewNV = 5279, + MeshViewCountNV = 5280, + MeshViewIndicesNV = 5281, + BaryCoordNV = 5286, + BaryCoordNoPerspNV = 5287, + FragSizeEXT = 5292, + FragmentSizeNV = FragSizeEXT, + FragInvocationCountEXT = 5293, + InvocationsPerPixelNV = FragInvocationCountEXT, + LaunchIdKHR = 5319, + LaunchIdNV = LaunchIdKHR, + LaunchSizeKHR = 5320, + LaunchSizeNV = LaunchSizeKHR, + WorldRayOriginKHR = 5321, + WorldRayOriginNV = WorldRayOriginKHR, + WorldRayDirectionKHR = 5322, + WorldRayDirectionNV = WorldRayDirectionKHR, + ObjectRayOriginKHR = 5323, + ObjectRayOriginNV = ObjectRayOriginKHR, + ObjectRayDirectionKHR = 5324, + ObjectRayDirectionNV = ObjectRayDirectionKHR, + RayTminKHR = 5325, + RayTminNV = RayTminKHR, + RayTmaxKHR = 5326, + RayTmaxNV = RayTmaxKHR, + InstanceCustomIndexKHR = 5327, + InstanceCustomIndexNV = InstanceCustomIndexKHR, + ObjectToWorldKHR = 5330, + ObjectToWorldNV = ObjectToWorldKHR, + WorldToObjectKHR = 5331, + WorldToObjectNV = WorldToObjectKHR, + HitTNV = 5332, + HitKindKHR = 5333, + HitKindNV = HitKindKHR, + IncomingRayFlagsKHR = 5351, + IncomingRayFlagsNV = IncomingRayFlagsKHR, + RayGeometryIndexKHR = 5352, + WarpsPerSMNV = 5374, + SMCountNV = 5375, + WarpIDNV = 5376, + SMIDNV = 5377, + } + + public enum SelectionControlShift + { + Flatten = 0, + DontFlatten = 1, + } + + public enum SelectionControlMask + { + MaskNone = 0, + Flatten = 0x00000001, + DontFlatten = 0x00000002, + } + + public enum LoopControlShift + { + Unroll = 0, + DontUnroll = 1, + DependencyInfinite = 2, + DependencyLength = 3, + MinIterations = 4, + MaxIterations = 5, + IterationMultiple = 6, + PeelCount = 7, + PartialCount = 8, + InitiationIntervalINTEL = 16, + MaxConcurrencyINTEL = 17, + DependencyArrayINTEL = 18, + PipelineEnableINTEL = 19, + LoopCoalesceINTEL = 20, + MaxInterleavingINTEL = 21, + SpeculatedIterationsINTEL = 22, + } + + public enum LoopControlMask + { + MaskNone = 0, + Unroll = 0x00000001, + DontUnroll = 0x00000002, + DependencyInfinite = 0x00000004, + DependencyLength = 0x00000008, + MinIterations = 0x00000010, + MaxIterations = 0x00000020, + IterationMultiple = 0x00000040, + PeelCount = 0x00000080, + PartialCount = 0x00000100, + InitiationIntervalINTEL = 0x00010000, + MaxConcurrencyINTEL = 0x00020000, + DependencyArrayINTEL = 0x00040000, + PipelineEnableINTEL = 0x00080000, + LoopCoalesceINTEL = 0x00100000, + MaxInterleavingINTEL = 0x00200000, + SpeculatedIterationsINTEL = 0x00400000, + } + + public enum FunctionControlShift + { + Inline = 0, + DontInline = 1, + Pure = 2, + Const = 3, + } + + public enum FunctionControlMask + { + MaskNone = 0, + Inline = 0x00000001, + DontInline = 0x00000002, + Pure = 0x00000004, + Const = 0x00000008, + } + + public enum MemorySemanticsShift + { + Acquire = 1, + Release = 2, + AcquireRelease = 3, + SequentiallyConsistent = 4, + UniformMemory = 6, + SubgroupMemory = 7, + WorkgroupMemory = 8, + CrossWorkgroupMemory = 9, + AtomicCounterMemory = 10, + ImageMemory = 11, + OutputMemory = 12, + OutputMemoryKHR = OutputMemory, + MakeAvailable = 13, + MakeAvailableKHR = MakeAvailable, + MakeVisible = 14, + MakeVisibleKHR = MakeVisible, + Volatile = 15, + } + + public enum MemorySemanticsMask + { + MaskNone = 0, + Acquire = 0x00000002, + Release = 0x00000004, + AcquireRelease = 0x00000008, + SequentiallyConsistent = 0x00000010, + UniformMemory = 0x00000040, + SubgroupMemory = 0x00000080, + WorkgroupMemory = 0x00000100, + CrossWorkgroupMemory = 0x00000200, + AtomicCounterMemory = 0x00000400, + ImageMemory = 0x00000800, + OutputMemory = 0x00001000, + OutputMemoryKHR = OutputMemory, + MakeAvailable = 0x00002000, + MakeAvailableKHR = MakeAvailable, + MakeVisible = 0x00004000, + MakeVisibleKHR = MakeVisible, + Volatile = 0x00008000, + } + + public enum MemoryAccessShift + { + Volatile = 0, + Aligned = 1, + Nontemporal = 2, + MakePointerAvailable = 3, + MakePointerAvailableKHR = MakePointerAvailable, + MakePointerVisible = 4, + MakePointerVisibleKHR = MakePointerVisible, + NonPrivatePointer = 5, + NonPrivatePointerKHR = NonPrivatePointer, + } + + public enum MemoryAccessMask + { + MaskNone = 0, + Volatile = 0x00000001, + Aligned = 0x00000002, + Nontemporal = 0x00000004, + MakePointerAvailable = 0x00000008, + MakePointerAvailableKHR = MakePointerAvailable, + MakePointerVisible = 0x00000010, + MakePointerVisibleKHR = MakePointerVisible, + NonPrivatePointer = 0x00000020, + NonPrivatePointerKHR = NonPrivatePointer, + } + + public enum Scope + { + CrossDevice = 0, + Device = 1, + Workgroup = 2, + Subgroup = 3, + Invocation = 4, + QueueFamily = 5, + QueueFamilyKHR = QueueFamily, + ShaderCallKHR = 6, + } + + public enum GroupOperation + { + Reduce = 0, + InclusiveScan = 1, + ExclusiveScan = 2, + ClusteredReduce = 3, + PartitionedReduceNV = 6, + PartitionedInclusiveScanNV = 7, + PartitionedExclusiveScanNV = 8, + } + + public enum KernelEnqueueFlags + { + NoWait = 0, + WaitKernel = 1, + WaitWorkGroup = 2, + } + + public enum KernelProfilingInfoShift + { + CmdExecTime = 0, + } + + public enum KernelProfilingInfoMask + { + MaskNone = 0, + CmdExecTime = 0x00000001, + } + + public enum Capability + { + Matrix = 0, + Shader = 1, + Geometry = 2, + Tessellation = 3, + Addresses = 4, + Linkage = 5, + Kernel = 6, + Vector16 = 7, + Float16Buffer = 8, + Float16 = 9, + Float64 = 10, + Int64 = 11, + Int64Atomics = 12, + ImageBasic = 13, + ImageReadWrite = 14, + ImageMipmap = 15, + Pipes = 17, + Groups = 18, + DeviceEnqueue = 19, + LiteralSampler = 20, + AtomicStorage = 21, + Int16 = 22, + TessellationPointSize = 23, + GeometryPointSize = 24, + ImageGatherExtended = 25, + StorageImageMultisample = 27, + UniformBufferArrayDynamicIndexing = 28, + SampledImageArrayDynamicIndexing = 29, + StorageBufferArrayDynamicIndexing = 30, + StorageImageArrayDynamicIndexing = 31, + ClipDistance = 32, + CullDistance = 33, + ImageCubeArray = 34, + SampleRateShading = 35, + ImageRect = 36, + SampledRect = 37, + GenericPointer = 38, + Int8 = 39, + InputAttachment = 40, + SparseResidency = 41, + MinLod = 42, + Sampled1D = 43, + Image1D = 44, + SampledCubeArray = 45, + SampledBuffer = 46, + ImageBuffer = 47, + ImageMSArray = 48, + StorageImageExtendedFormats = 49, + ImageQuery = 50, + DerivativeControl = 51, + InterpolationFunction = 52, + TransformFeedback = 53, + GeometryStreams = 54, + StorageImageReadWithoutFormat = 55, + StorageImageWriteWithoutFormat = 56, + MultiViewport = 57, + SubgroupDispatch = 58, + NamedBarrier = 59, + PipeStorage = 60, + GroupNonUniform = 61, + GroupNonUniformVote = 62, + GroupNonUniformArithmetic = 63, + GroupNonUniformBallot = 64, + GroupNonUniformShuffle = 65, + GroupNonUniformShuffleRelative = 66, + GroupNonUniformClustered = 67, + GroupNonUniformQuad = 68, + ShaderLayer = 69, + ShaderViewportIndex = 70, + FragmentShadingRateKHR = 4422, + SubgroupBallotKHR = 4423, + DrawParameters = 4427, + SubgroupVoteKHR = 4431, + StorageBuffer16BitAccess = 4433, + StorageUniformBufferBlock16 = StorageBuffer16BitAccess, + StorageUniform16 = 4434, + UniformAndStorageBuffer16BitAccess = StorageUniform16, + StoragePushConstant16 = 4435, + StorageInputOutput16 = 4436, + DeviceGroup = 4437, + MultiView = 4439, + VariablePointersStorageBuffer = 4441, + VariablePointers = 4442, + AtomicStorageOps = 4445, + SampleMaskPostDepthCoverage = 4447, + StorageBuffer8BitAccess = 4448, + UniformAndStorageBuffer8BitAccess = 4449, + StoragePushConstant8 = 4450, + DenormPreserve = 4464, + DenormFlushToZero = 4465, + SignedZeroInfNanPreserve = 4466, + RoundingModeRTE = 4467, + RoundingModeRTZ = 4468, + RayQueryProvisionalKHR = 4471, + RayQueryKHR = 4472, + RayTraversalPrimitiveCullingKHR = 4478, + RayTracingKHR = 4479, + Float16ImageAMD = 5008, + ImageGatherBiasLodAMD = 5009, + FragmentMaskAMD = 5010, + StencilExportEXT = 5013, + ImageReadWriteLodAMD = 5015, + Int64ImageEXT = 5016, + ShaderClockKHR = 5055, + SampleMaskOverrideCoverageNV = 5249, + GeometryShaderPassthroughNV = 5251, + ShaderViewportIndexLayerEXT = 5254, + ShaderViewportIndexLayerNV = ShaderViewportIndexLayerEXT, + ShaderViewportMaskNV = 5255, + ShaderStereoViewNV = 5259, + PerViewAttributesNV = 5260, + FragmentFullyCoveredEXT = 5265, + MeshShadingNV = 5266, + ImageFootprintNV = 5282, + FragmentBarycentricNV = 5284, + ComputeDerivativeGroupQuadsNV = 5288, + FragmentDensityEXT = 5291, + ShadingRateNV = FragmentDensityEXT, + GroupNonUniformPartitionedNV = 5297, + ShaderNonUniform = 5301, + ShaderNonUniformEXT = ShaderNonUniform, + RuntimeDescriptorArray = 5302, + RuntimeDescriptorArrayEXT = RuntimeDescriptorArray, + InputAttachmentArrayDynamicIndexing = 5303, + InputAttachmentArrayDynamicIndexingEXT = InputAttachmentArrayDynamicIndexing, + UniformTexelBufferArrayDynamicIndexing = 5304, + UniformTexelBufferArrayDynamicIndexingEXT = UniformTexelBufferArrayDynamicIndexing, + StorageTexelBufferArrayDynamicIndexing = 5305, + StorageTexelBufferArrayDynamicIndexingEXT = StorageTexelBufferArrayDynamicIndexing, + UniformBufferArrayNonUniformIndexing = 5306, + UniformBufferArrayNonUniformIndexingEXT = UniformBufferArrayNonUniformIndexing, + SampledImageArrayNonUniformIndexing = 5307, + SampledImageArrayNonUniformIndexingEXT = SampledImageArrayNonUniformIndexing, + StorageBufferArrayNonUniformIndexing = 5308, + StorageBufferArrayNonUniformIndexingEXT = StorageBufferArrayNonUniformIndexing, + StorageImageArrayNonUniformIndexing = 5309, + StorageImageArrayNonUniformIndexingEXT = StorageImageArrayNonUniformIndexing, + InputAttachmentArrayNonUniformIndexing = 5310, + InputAttachmentArrayNonUniformIndexingEXT = InputAttachmentArrayNonUniformIndexing, + UniformTexelBufferArrayNonUniformIndexing = 5311, + UniformTexelBufferArrayNonUniformIndexingEXT = UniformTexelBufferArrayNonUniformIndexing, + StorageTexelBufferArrayNonUniformIndexing = 5312, + StorageTexelBufferArrayNonUniformIndexingEXT = StorageTexelBufferArrayNonUniformIndexing, + RayTracingNV = 5340, + VulkanMemoryModel = 5345, + VulkanMemoryModelKHR = VulkanMemoryModel, + VulkanMemoryModelDeviceScope = 5346, + VulkanMemoryModelDeviceScopeKHR = VulkanMemoryModelDeviceScope, + PhysicalStorageBufferAddresses = 5347, + PhysicalStorageBufferAddressesEXT = PhysicalStorageBufferAddresses, + ComputeDerivativeGroupLinearNV = 5350, + RayTracingProvisionalKHR = 5353, + CooperativeMatrixNV = 5357, + FragmentShaderSampleInterlockEXT = 5363, + FragmentShaderShadingRateInterlockEXT = 5372, + ShaderSMBuiltinsNV = 5373, + FragmentShaderPixelInterlockEXT = 5378, + DemoteToHelperInvocationEXT = 5379, + SubgroupShuffleINTEL = 5568, + SubgroupBufferBlockIOINTEL = 5569, + SubgroupImageBlockIOINTEL = 5570, + SubgroupImageMediaBlockIOINTEL = 5579, + IntegerFunctions2INTEL = 5584, + FunctionPointersINTEL = 5603, + IndirectReferencesINTEL = 5604, + SubgroupAvcMotionEstimationINTEL = 5696, + SubgroupAvcMotionEstimationIntraINTEL = 5697, + SubgroupAvcMotionEstimationChromaINTEL = 5698, + FPGAMemoryAttributesINTEL = 5824, + UnstructuredLoopControlsINTEL = 5886, + FPGALoopControlsINTEL = 5888, + KernelAttributesINTEL = 5892, + FPGAKernelAttributesINTEL = 5897, + BlockingPipesINTEL = 5945, + FPGARegINTEL = 5948, + AtomicFloat32AddEXT = 6033, + AtomicFloat64AddEXT = 6034, + } + + public enum RayFlagsShift + { + OpaqueKHR = 0, + NoOpaqueKHR = 1, + TerminateOnFirstHitKHR = 2, + SkipClosestHitShaderKHR = 3, + CullBackFacingTrianglesKHR = 4, + CullFrontFacingTrianglesKHR = 5, + CullOpaqueKHR = 6, + CullNoOpaqueKHR = 7, + SkipTrianglesKHR = 8, + SkipAABBsKHR = 9, + } + + public enum RayFlagsMask + { + MaskNone = 0, + OpaqueKHR = 0x00000001, + NoOpaqueKHR = 0x00000002, + TerminateOnFirstHitKHR = 0x00000004, + SkipClosestHitShaderKHR = 0x00000008, + CullBackFacingTrianglesKHR = 0x00000010, + CullFrontFacingTrianglesKHR = 0x00000020, + CullOpaqueKHR = 0x00000040, + CullNoOpaqueKHR = 0x00000080, + SkipTrianglesKHR = 0x00000100, + SkipAABBsKHR = 0x00000200, + } + + public enum RayQueryIntersection + { + RayQueryCandidateIntersectionKHR = 0, + RayQueryCommittedIntersectionKHR = 1, + } + + public enum RayQueryCommittedIntersectionType + { + RayQueryCommittedIntersectionNoneKHR = 0, + RayQueryCommittedIntersectionTriangleKHR = 1, + RayQueryCommittedIntersectionGeneratedKHR = 2, + } + + public enum RayQueryCandidateIntersectionType + { + RayQueryCandidateIntersectionTriangleKHR = 0, + RayQueryCandidateIntersectionAABBKHR = 1, + } + + public enum FragmentShadingRateShift + { + Vertical2Pixels = 0, + Vertical4Pixels = 1, + Horizontal2Pixels = 2, + Horizontal4Pixels = 3, + } + + public enum FragmentShadingRateMask + { + MaskNone = 0, + Vertical2Pixels = 0x00000001, + Vertical4Pixels = 0x00000002, + Horizontal2Pixels = 0x00000004, + Horizontal4Pixels = 0x00000008, + } + + public enum Op + { + OpNop = 0, + OpUndef = 1, + OpSourceContinued = 2, + OpSource = 3, + OpSourceExtension = 4, + OpName = 5, + OpMemberName = 6, + OpString = 7, + OpLine = 8, + OpExtension = 10, + OpExtInstImport = 11, + OpExtInst = 12, + OpMemoryModel = 14, + OpEntryPoint = 15, + OpExecutionMode = 16, + OpCapability = 17, + OpTypeVoid = 19, + OpTypeBool = 20, + OpTypeInt = 21, + OpTypeFloat = 22, + OpTypeVector = 23, + OpTypeMatrix = 24, + OpTypeImage = 25, + OpTypeSampler = 26, + OpTypeSampledImage = 27, + OpTypeArray = 28, + OpTypeRuntimeArray = 29, + OpTypeStruct = 30, + OpTypeOpaque = 31, + OpTypePointer = 32, + OpTypeFunction = 33, + OpTypeEvent = 34, + OpTypeDeviceEvent = 35, + OpTypeReserveId = 36, + OpTypeQueue = 37, + OpTypePipe = 38, + OpTypeForwardPointer = 39, + OpConstantTrue = 41, + OpConstantFalse = 42, + OpConstant = 43, + OpConstantComposite = 44, + OpConstantSampler = 45, + OpConstantNull = 46, + OpSpecConstantTrue = 48, + OpSpecConstantFalse = 49, + OpSpecConstant = 50, + OpSpecConstantComposite = 51, + OpSpecConstantOp = 52, + OpFunction = 54, + OpFunctionParameter = 55, + OpFunctionEnd = 56, + OpFunctionCall = 57, + OpVariable = 59, + OpImageTexelPointer = 60, + OpLoad = 61, + OpStore = 62, + OpCopyMemory = 63, + OpCopyMemorySized = 64, + OpAccessChain = 65, + OpInBoundsAccessChain = 66, + OpPtrAccessChain = 67, + OpArrayLength = 68, + OpGenericPtrMemSemantics = 69, + OpInBoundsPtrAccessChain = 70, + OpDecorate = 71, + OpMemberDecorate = 72, + OpDecorationGroup = 73, + OpGroupDecorate = 74, + OpGroupMemberDecorate = 75, + OpVectorExtractDynamic = 77, + OpVectorInsertDynamic = 78, + OpVectorShuffle = 79, + OpCompositeConstruct = 80, + OpCompositeExtract = 81, + OpCompositeInsert = 82, + OpCopyObject = 83, + OpTranspose = 84, + OpSampledImage = 86, + OpImageSampleImplicitLod = 87, + OpImageSampleExplicitLod = 88, + OpImageSampleDrefImplicitLod = 89, + OpImageSampleDrefExplicitLod = 90, + OpImageSampleProjImplicitLod = 91, + OpImageSampleProjExplicitLod = 92, + OpImageSampleProjDrefImplicitLod = 93, + OpImageSampleProjDrefExplicitLod = 94, + OpImageFetch = 95, + OpImageGather = 96, + OpImageDrefGather = 97, + OpImageRead = 98, + OpImageWrite = 99, + OpImage = 100, + OpImageQueryFormat = 101, + OpImageQueryOrder = 102, + OpImageQuerySizeLod = 103, + OpImageQuerySize = 104, + OpImageQueryLod = 105, + OpImageQueryLevels = 106, + OpImageQuerySamples = 107, + OpConvertFToU = 109, + OpConvertFToS = 110, + OpConvertSToF = 111, + OpConvertUToF = 112, + OpUConvert = 113, + OpSConvert = 114, + OpFConvert = 115, + OpQuantizeToF16 = 116, + OpConvertPtrToU = 117, + OpSatConvertSToU = 118, + OpSatConvertUToS = 119, + OpConvertUToPtr = 120, + OpPtrCastToGeneric = 121, + OpGenericCastToPtr = 122, + OpGenericCastToPtrExplicit = 123, + OpBitcast = 124, + OpSNegate = 126, + OpFNegate = 127, + OpIAdd = 128, + OpFAdd = 129, + OpISub = 130, + OpFSub = 131, + OpIMul = 132, + OpFMul = 133, + OpUDiv = 134, + OpSDiv = 135, + OpFDiv = 136, + OpUMod = 137, + OpSRem = 138, + OpSMod = 139, + OpFRem = 140, + OpFMod = 141, + OpVectorTimesScalar = 142, + OpMatrixTimesScalar = 143, + OpVectorTimesMatrix = 144, + OpMatrixTimesVector = 145, + OpMatrixTimesMatrix = 146, + OpOuterProduct = 147, + OpDot = 148, + OpIAddCarry = 149, + OpISubBorrow = 150, + OpUMulExtended = 151, + OpSMulExtended = 152, + OpAny = 154, + OpAll = 155, + OpIsNan = 156, + OpIsInf = 157, + OpIsFinite = 158, + OpIsNormal = 159, + OpSignBitSet = 160, + OpLessOrGreater = 161, + OpOrdered = 162, + OpUnordered = 163, + OpLogicalEqual = 164, + OpLogicalNotEqual = 165, + OpLogicalOr = 166, + OpLogicalAnd = 167, + OpLogicalNot = 168, + OpSelect = 169, + OpIEqual = 170, + OpINotEqual = 171, + OpUGreaterThan = 172, + OpSGreaterThan = 173, + OpUGreaterThanEqual = 174, + OpSGreaterThanEqual = 175, + OpULessThan = 176, + OpSLessThan = 177, + OpULessThanEqual = 178, + OpSLessThanEqual = 179, + OpFOrdEqual = 180, + OpFUnordEqual = 181, + OpFOrdNotEqual = 182, + OpFUnordNotEqual = 183, + OpFOrdLessThan = 184, + OpFUnordLessThan = 185, + OpFOrdGreaterThan = 186, + OpFUnordGreaterThan = 187, + OpFOrdLessThanEqual = 188, + OpFUnordLessThanEqual = 189, + OpFOrdGreaterThanEqual = 190, + OpFUnordGreaterThanEqual = 191, + OpShiftRightLogical = 194, + OpShiftRightArithmetic = 195, + OpShiftLeftLogical = 196, + OpBitwiseOr = 197, + OpBitwiseXor = 198, + OpBitwiseAnd = 199, + OpNot = 200, + OpBitFieldInsert = 201, + OpBitFieldSExtract = 202, + OpBitFieldUExtract = 203, + OpBitReverse = 204, + OpBitCount = 205, + OpDPdx = 207, + OpDPdy = 208, + OpFwidth = 209, + OpDPdxFine = 210, + OpDPdyFine = 211, + OpFwidthFine = 212, + OpDPdxCoarse = 213, + OpDPdyCoarse = 214, + OpFwidthCoarse = 215, + OpEmitVertex = 218, + OpEndPrimitive = 219, + OpEmitStreamVertex = 220, + OpEndStreamPrimitive = 221, + OpControlBarrier = 224, + OpMemoryBarrier = 225, + OpAtomicLoad = 227, + OpAtomicStore = 228, + OpAtomicExchange = 229, + OpAtomicCompareExchange = 230, + OpAtomicCompareExchangeWeak = 231, + OpAtomicIIncrement = 232, + OpAtomicIDecrement = 233, + OpAtomicIAdd = 234, + OpAtomicISub = 235, + OpAtomicSMin = 236, + OpAtomicUMin = 237, + OpAtomicSMax = 238, + OpAtomicUMax = 239, + OpAtomicAnd = 240, + OpAtomicOr = 241, + OpAtomicXor = 242, + OpPhi = 245, + OpLoopMerge = 246, + OpSelectionMerge = 247, + OpLabel = 248, + OpBranch = 249, + OpBranchConditional = 250, + OpSwitch = 251, + OpKill = 252, + OpReturn = 253, + OpReturnValue = 254, + OpUnreachable = 255, + OpLifetimeStart = 256, + OpLifetimeStop = 257, + OpGroupAsyncCopy = 259, + OpGroupWaitEvents = 260, + OpGroupAll = 261, + OpGroupAny = 262, + OpGroupBroadcast = 263, + OpGroupIAdd = 264, + OpGroupFAdd = 265, + OpGroupFMin = 266, + OpGroupUMin = 267, + OpGroupSMin = 268, + OpGroupFMax = 269, + OpGroupUMax = 270, + OpGroupSMax = 271, + OpReadPipe = 274, + OpWritePipe = 275, + OpReservedReadPipe = 276, + OpReservedWritePipe = 277, + OpReserveReadPipePackets = 278, + OpReserveWritePipePackets = 279, + OpCommitReadPipe = 280, + OpCommitWritePipe = 281, + OpIsValidReserveId = 282, + OpGetNumPipePackets = 283, + OpGetMaxPipePackets = 284, + OpGroupReserveReadPipePackets = 285, + OpGroupReserveWritePipePackets = 286, + OpGroupCommitReadPipe = 287, + OpGroupCommitWritePipe = 288, + OpEnqueueMarker = 291, + OpEnqueueKernel = 292, + OpGetKernelNDrangeSubGroupCount = 293, + OpGetKernelNDrangeMaxSubGroupSize = 294, + OpGetKernelWorkGroupSize = 295, + OpGetKernelPreferredWorkGroupSizeMultiple = 296, + OpRetainEvent = 297, + OpReleaseEvent = 298, + OpCreateUserEvent = 299, + OpIsValidEvent = 300, + OpSetUserEventStatus = 301, + OpCaptureEventProfilingInfo = 302, + OpGetDefaultQueue = 303, + OpBuildNDRange = 304, + OpImageSparseSampleImplicitLod = 305, + OpImageSparseSampleExplicitLod = 306, + OpImageSparseSampleDrefImplicitLod = 307, + OpImageSparseSampleDrefExplicitLod = 308, + OpImageSparseSampleProjImplicitLod = 309, + OpImageSparseSampleProjExplicitLod = 310, + OpImageSparseSampleProjDrefImplicitLod = 311, + OpImageSparseSampleProjDrefExplicitLod = 312, + OpImageSparseFetch = 313, + OpImageSparseGather = 314, + OpImageSparseDrefGather = 315, + OpImageSparseTexelsResident = 316, + OpNoLine = 317, + OpAtomicFlagTestAndSet = 318, + OpAtomicFlagClear = 319, + OpImageSparseRead = 320, + OpSizeOf = 321, + OpTypePipeStorage = 322, + OpConstantPipeStorage = 323, + OpCreatePipeFromPipeStorage = 324, + OpGetKernelLocalSizeForSubgroupCount = 325, + OpGetKernelMaxNumSubgroups = 326, + OpTypeNamedBarrier = 327, + OpNamedBarrierInitialize = 328, + OpMemoryNamedBarrier = 329, + OpModuleProcessed = 330, + OpExecutionModeId = 331, + OpDecorateId = 332, + OpGroupNonUniformElect = 333, + OpGroupNonUniformAll = 334, + OpGroupNonUniformAny = 335, + OpGroupNonUniformAllEqual = 336, + OpGroupNonUniformBroadcast = 337, + OpGroupNonUniformBroadcastFirst = 338, + OpGroupNonUniformBallot = 339, + OpGroupNonUniformInverseBallot = 340, + OpGroupNonUniformBallotBitExtract = 341, + OpGroupNonUniformBallotBitCount = 342, + OpGroupNonUniformBallotFindLSB = 343, + OpGroupNonUniformBallotFindMSB = 344, + OpGroupNonUniformShuffle = 345, + OpGroupNonUniformShuffleXor = 346, + OpGroupNonUniformShuffleUp = 347, + OpGroupNonUniformShuffleDown = 348, + OpGroupNonUniformIAdd = 349, + OpGroupNonUniformFAdd = 350, + OpGroupNonUniformIMul = 351, + OpGroupNonUniformFMul = 352, + OpGroupNonUniformSMin = 353, + OpGroupNonUniformUMin = 354, + OpGroupNonUniformFMin = 355, + OpGroupNonUniformSMax = 356, + OpGroupNonUniformUMax = 357, + OpGroupNonUniformFMax = 358, + OpGroupNonUniformBitwiseAnd = 359, + OpGroupNonUniformBitwiseOr = 360, + OpGroupNonUniformBitwiseXor = 361, + OpGroupNonUniformLogicalAnd = 362, + OpGroupNonUniformLogicalOr = 363, + OpGroupNonUniformLogicalXor = 364, + OpGroupNonUniformQuadBroadcast = 365, + OpGroupNonUniformQuadSwap = 366, + OpCopyLogical = 400, + OpPtrEqual = 401, + OpPtrNotEqual = 402, + OpPtrDiff = 403, + OpTerminateInvocation = 4416, + OpSubgroupBallotKHR = 4421, + OpSubgroupFirstInvocationKHR = 4422, + OpSubgroupAllKHR = 4428, + OpSubgroupAnyKHR = 4429, + OpSubgroupAllEqualKHR = 4430, + OpSubgroupReadInvocationKHR = 4432, + OpTraceRayKHR = 4445, + OpExecuteCallableKHR = 4446, + OpConvertUToAccelerationStructureKHR = 4447, + OpIgnoreIntersectionKHR = 4448, + OpTerminateRayKHR = 4449, + OpTypeRayQueryKHR = 4472, + OpRayQueryInitializeKHR = 4473, + OpRayQueryTerminateKHR = 4474, + OpRayQueryGenerateIntersectionKHR = 4475, + OpRayQueryConfirmIntersectionKHR = 4476, + OpRayQueryProceedKHR = 4477, + OpRayQueryGetIntersectionTypeKHR = 4479, + OpGroupIAddNonUniformAMD = 5000, + OpGroupFAddNonUniformAMD = 5001, + OpGroupFMinNonUniformAMD = 5002, + OpGroupUMinNonUniformAMD = 5003, + OpGroupSMinNonUniformAMD = 5004, + OpGroupFMaxNonUniformAMD = 5005, + OpGroupUMaxNonUniformAMD = 5006, + OpGroupSMaxNonUniformAMD = 5007, + OpFragmentMaskFetchAMD = 5011, + OpFragmentFetchAMD = 5012, + OpReadClockKHR = 5056, + OpImageSampleFootprintNV = 5283, + OpGroupNonUniformPartitionNV = 5296, + OpWritePackedPrimitiveIndices4x8NV = 5299, + OpReportIntersectionKHR = 5334, + OpReportIntersectionNV = OpReportIntersectionKHR, + OpIgnoreIntersectionNV = 5335, + OpTerminateRayNV = 5336, + OpTraceNV = 5337, + OpTypeAccelerationStructureKHR = 5341, + OpTypeAccelerationStructureNV = OpTypeAccelerationStructureKHR, + OpExecuteCallableNV = 5344, + OpTypeCooperativeMatrixNV = 5358, + OpCooperativeMatrixLoadNV = 5359, + OpCooperativeMatrixStoreNV = 5360, + OpCooperativeMatrixMulAddNV = 5361, + OpCooperativeMatrixLengthNV = 5362, + OpBeginInvocationInterlockEXT = 5364, + OpEndInvocationInterlockEXT = 5365, + OpDemoteToHelperInvocationEXT = 5380, + OpIsHelperInvocationEXT = 5381, + OpSubgroupShuffleINTEL = 5571, + OpSubgroupShuffleDownINTEL = 5572, + OpSubgroupShuffleUpINTEL = 5573, + OpSubgroupShuffleXorINTEL = 5574, + OpSubgroupBlockReadINTEL = 5575, + OpSubgroupBlockWriteINTEL = 5576, + OpSubgroupImageBlockReadINTEL = 5577, + OpSubgroupImageBlockWriteINTEL = 5578, + OpSubgroupImageMediaBlockReadINTEL = 5580, + OpSubgroupImageMediaBlockWriteINTEL = 5581, + OpUCountLeadingZerosINTEL = 5585, + OpUCountTrailingZerosINTEL = 5586, + OpAbsISubINTEL = 5587, + OpAbsUSubINTEL = 5588, + OpIAddSatINTEL = 5589, + OpUAddSatINTEL = 5590, + OpIAverageINTEL = 5591, + OpUAverageINTEL = 5592, + OpIAverageRoundedINTEL = 5593, + OpUAverageRoundedINTEL = 5594, + OpISubSatINTEL = 5595, + OpUSubSatINTEL = 5596, + OpIMul32x16INTEL = 5597, + OpUMul32x16INTEL = 5598, + OpFunctionPointerINTEL = 5600, + OpFunctionPointerCallINTEL = 5601, + OpDecorateString = 5632, + OpDecorateStringGOOGLE = OpDecorateString, + OpMemberDecorateString = 5633, + OpMemberDecorateStringGOOGLE = OpMemberDecorateString, + OpVmeImageINTEL = 5699, + OpTypeVmeImageINTEL = 5700, + OpTypeAvcImePayloadINTEL = 5701, + OpTypeAvcRefPayloadINTEL = 5702, + OpTypeAvcSicPayloadINTEL = 5703, + OpTypeAvcMcePayloadINTEL = 5704, + OpTypeAvcMceResultINTEL = 5705, + OpTypeAvcImeResultINTEL = 5706, + OpTypeAvcImeResultSingleReferenceStreamoutINTEL = 5707, + OpTypeAvcImeResultDualReferenceStreamoutINTEL = 5708, + OpTypeAvcImeSingleReferenceStreaminINTEL = 5709, + OpTypeAvcImeDualReferenceStreaminINTEL = 5710, + OpTypeAvcRefResultINTEL = 5711, + OpTypeAvcSicResultINTEL = 5712, + OpSubgroupAvcMceGetDefaultInterBaseMultiReferencePenaltyINTEL = 5713, + OpSubgroupAvcMceSetInterBaseMultiReferencePenaltyINTEL = 5714, + OpSubgroupAvcMceGetDefaultInterShapePenaltyINTEL = 5715, + OpSubgroupAvcMceSetInterShapePenaltyINTEL = 5716, + OpSubgroupAvcMceGetDefaultInterDirectionPenaltyINTEL = 5717, + OpSubgroupAvcMceSetInterDirectionPenaltyINTEL = 5718, + OpSubgroupAvcMceGetDefaultIntraLumaShapePenaltyINTEL = 5719, + OpSubgroupAvcMceGetDefaultInterMotionVectorCostTableINTEL = 5720, + OpSubgroupAvcMceGetDefaultHighPenaltyCostTableINTEL = 5721, + OpSubgroupAvcMceGetDefaultMediumPenaltyCostTableINTEL = 5722, + OpSubgroupAvcMceGetDefaultLowPenaltyCostTableINTEL = 5723, + OpSubgroupAvcMceSetMotionVectorCostFunctionINTEL = 5724, + OpSubgroupAvcMceGetDefaultIntraLumaModePenaltyINTEL = 5725, + OpSubgroupAvcMceGetDefaultNonDcLumaIntraPenaltyINTEL = 5726, + OpSubgroupAvcMceGetDefaultIntraChromaModeBasePenaltyINTEL = 5727, + OpSubgroupAvcMceSetAcOnlyHaarINTEL = 5728, + OpSubgroupAvcMceSetSourceInterlacedFieldPolarityINTEL = 5729, + OpSubgroupAvcMceSetSingleReferenceInterlacedFieldPolarityINTEL = 5730, + OpSubgroupAvcMceSetDualReferenceInterlacedFieldPolaritiesINTEL = 5731, + OpSubgroupAvcMceConvertToImePayloadINTEL = 5732, + OpSubgroupAvcMceConvertToImeResultINTEL = 5733, + OpSubgroupAvcMceConvertToRefPayloadINTEL = 5734, + OpSubgroupAvcMceConvertToRefResultINTEL = 5735, + OpSubgroupAvcMceConvertToSicPayloadINTEL = 5736, + OpSubgroupAvcMceConvertToSicResultINTEL = 5737, + OpSubgroupAvcMceGetMotionVectorsINTEL = 5738, + OpSubgroupAvcMceGetInterDistortionsINTEL = 5739, + OpSubgroupAvcMceGetBestInterDistortionsINTEL = 5740, + OpSubgroupAvcMceGetInterMajorShapeINTEL = 5741, + OpSubgroupAvcMceGetInterMinorShapeINTEL = 5742, + OpSubgroupAvcMceGetInterDirectionsINTEL = 5743, + OpSubgroupAvcMceGetInterMotionVectorCountINTEL = 5744, + OpSubgroupAvcMceGetInterReferenceIdsINTEL = 5745, + OpSubgroupAvcMceGetInterReferenceInterlacedFieldPolaritiesINTEL = 5746, + OpSubgroupAvcImeInitializeINTEL = 5747, + OpSubgroupAvcImeSetSingleReferenceINTEL = 5748, + OpSubgroupAvcImeSetDualReferenceINTEL = 5749, + OpSubgroupAvcImeRefWindowSizeINTEL = 5750, + OpSubgroupAvcImeAdjustRefOffsetINTEL = 5751, + OpSubgroupAvcImeConvertToMcePayloadINTEL = 5752, + OpSubgroupAvcImeSetMaxMotionVectorCountINTEL = 5753, + OpSubgroupAvcImeSetUnidirectionalMixDisableINTEL = 5754, + OpSubgroupAvcImeSetEarlySearchTerminationThresholdINTEL = 5755, + OpSubgroupAvcImeSetWeightedSadINTEL = 5756, + OpSubgroupAvcImeEvaluateWithSingleReferenceINTEL = 5757, + OpSubgroupAvcImeEvaluateWithDualReferenceINTEL = 5758, + OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminINTEL = 5759, + OpSubgroupAvcImeEvaluateWithDualReferenceStreaminINTEL = 5760, + OpSubgroupAvcImeEvaluateWithSingleReferenceStreamoutINTEL = 5761, + OpSubgroupAvcImeEvaluateWithDualReferenceStreamoutINTEL = 5762, + OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminoutINTEL = 5763, + OpSubgroupAvcImeEvaluateWithDualReferenceStreaminoutINTEL = 5764, + OpSubgroupAvcImeConvertToMceResultINTEL = 5765, + OpSubgroupAvcImeGetSingleReferenceStreaminINTEL = 5766, + OpSubgroupAvcImeGetDualReferenceStreaminINTEL = 5767, + OpSubgroupAvcImeStripSingleReferenceStreamoutINTEL = 5768, + OpSubgroupAvcImeStripDualReferenceStreamoutINTEL = 5769, + OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeMotionVectorsINTEL = 5770, + OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeDistortionsINTEL = 5771, + OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeReferenceIdsINTEL = 5772, + OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeMotionVectorsINTEL = 5773, + OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeDistortionsINTEL = 5774, + OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeReferenceIdsINTEL = 5775, + OpSubgroupAvcImeGetBorderReachedINTEL = 5776, + OpSubgroupAvcImeGetTruncatedSearchIndicationINTEL = 5777, + OpSubgroupAvcImeGetUnidirectionalEarlySearchTerminationINTEL = 5778, + OpSubgroupAvcImeGetWeightingPatternMinimumMotionVectorINTEL = 5779, + OpSubgroupAvcImeGetWeightingPatternMinimumDistortionINTEL = 5780, + OpSubgroupAvcFmeInitializeINTEL = 5781, + OpSubgroupAvcBmeInitializeINTEL = 5782, + OpSubgroupAvcRefConvertToMcePayloadINTEL = 5783, + OpSubgroupAvcRefSetBidirectionalMixDisableINTEL = 5784, + OpSubgroupAvcRefSetBilinearFilterEnableINTEL = 5785, + OpSubgroupAvcRefEvaluateWithSingleReferenceINTEL = 5786, + OpSubgroupAvcRefEvaluateWithDualReferenceINTEL = 5787, + OpSubgroupAvcRefEvaluateWithMultiReferenceINTEL = 5788, + OpSubgroupAvcRefEvaluateWithMultiReferenceInterlacedINTEL = 5789, + OpSubgroupAvcRefConvertToMceResultINTEL = 5790, + OpSubgroupAvcSicInitializeINTEL = 5791, + OpSubgroupAvcSicConfigureSkcINTEL = 5792, + OpSubgroupAvcSicConfigureIpeLumaINTEL = 5793, + OpSubgroupAvcSicConfigureIpeLumaChromaINTEL = 5794, + OpSubgroupAvcSicGetMotionVectorMaskINTEL = 5795, + OpSubgroupAvcSicConvertToMcePayloadINTEL = 5796, + OpSubgroupAvcSicSetIntraLumaShapePenaltyINTEL = 5797, + OpSubgroupAvcSicSetIntraLumaModeCostFunctionINTEL = 5798, + OpSubgroupAvcSicSetIntraChromaModeCostFunctionINTEL = 5799, + OpSubgroupAvcSicSetBilinearFilterEnableINTEL = 5800, + OpSubgroupAvcSicSetSkcForwardTransformEnableINTEL = 5801, + OpSubgroupAvcSicSetBlockBasedRawSkipSadINTEL = 5802, + OpSubgroupAvcSicEvaluateIpeINTEL = 5803, + OpSubgroupAvcSicEvaluateWithSingleReferenceINTEL = 5804, + OpSubgroupAvcSicEvaluateWithDualReferenceINTEL = 5805, + OpSubgroupAvcSicEvaluateWithMultiReferenceINTEL = 5806, + OpSubgroupAvcSicEvaluateWithMultiReferenceInterlacedINTEL = 5807, + OpSubgroupAvcSicConvertToMceResultINTEL = 5808, + OpSubgroupAvcSicGetIpeLumaShapeINTEL = 5809, + OpSubgroupAvcSicGetBestIpeLumaDistortionINTEL = 5810, + OpSubgroupAvcSicGetBestIpeChromaDistortionINTEL = 5811, + OpSubgroupAvcSicGetPackedIpeLumaModesINTEL = 5812, + OpSubgroupAvcSicGetIpeChromaModeINTEL = 5813, + OpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL = 5814, + OpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL = 5815, + OpSubgroupAvcSicGetInterRawSadsINTEL = 5816, + OpLoopControlINTEL = 5887, + OpReadPipeBlockingINTEL = 5946, + OpWritePipeBlockingINTEL = 5947, + OpFPGARegINTEL = 5949, + OpRayQueryGetRayTMinKHR = 6016, + OpRayQueryGetRayFlagsKHR = 6017, + OpRayQueryGetIntersectionTKHR = 6018, + OpRayQueryGetIntersectionInstanceCustomIndexKHR = 6019, + OpRayQueryGetIntersectionInstanceIdKHR = 6020, + OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR = 6021, + OpRayQueryGetIntersectionGeometryIndexKHR = 6022, + OpRayQueryGetIntersectionPrimitiveIndexKHR = 6023, + OpRayQueryGetIntersectionBarycentricsKHR = 6024, + OpRayQueryGetIntersectionFrontFaceKHR = 6025, + OpRayQueryGetIntersectionCandidateAABBOpaqueKHR = 6026, + OpRayQueryGetIntersectionObjectRayDirectionKHR = 6027, + OpRayQueryGetIntersectionObjectRayOriginKHR = 6028, + OpRayQueryGetWorldRayDirectionKHR = 6029, + OpRayQueryGetWorldRayOriginKHR = 6030, + OpRayQueryGetIntersectionObjectToWorldKHR = 6031, + OpRayQueryGetIntersectionWorldToObjectKHR = 6032, + OpAtomicFAddEXT = 6035, + } + } +}

m`f zt8g}1{0VaR^rXy;|*48Yp8SSt>`_pHMm%LVSh?^SjGz~FFUz=Kp)Pv2iMny z+UmlFVRYs|Fq00J?z%+hiDq08UtQd*6~0Zkv4zOn7JVYcv~gYK+51Gg;G)pUkA^Q6 zK8(3}2y@ekxw)TJ{pEl?O+cDFjf$7r^wgX&Ye@iCXDBF#N=jzUQJ6E%Hc9`=4AoXX z*3^cnSd~lOskc;A)rLa3N(bsn!uDz%srEZe4%;Ve6+)HKQXy(9KH>2>A~mj&YCsuC zNyrDl5l-3|->g^p&&- zyuK>&6y1!Do#Jyvp)8*O10F~Ha}7p|#wPr&71c&UfLCz#0md+nPSo8;o3H~Rn z9CM#N!dc0Q3pIm?ExZFs(S3q4^O|k8p(Zni%^YfWnB25gQRPpBo$^uOcr=K#%W6vm zl`*pVV>U~`BpgpRmcEK0QQOMtv1-APO8UQ0wqITAroeZIX1rWo>CVQSZJvLnU;m*^ z`v2leYL^Kn{i;V-g>to_si)|Gc%6j$Ch9_R!K5tgTWshOn=$;JM`n%;s7HvX$8FjO zMn(hqT^X7G%ify+wo#sW<8$b=Y}t}5%a$zLl6Cl$5BWZ2J9eBnmvbNFCJ7`YA>?R6 zI3_>{1QJ>(rOUBU($a&r^k24gfi6`~*mk!EwB2pH+tMxVcH8aOZfRL!Ta)kiyzh)8 zA0fb&?*8{{z|zcU=AD`Mc<$%-Knj_AWWwHYePA$77#M*D#v7hBENJzEMDdNgAP>E5 zdQV2-X^V-&zYw)~(`4L|V-RI5DNe|BTJv@ZH8_TeU|q6FwMKS8#j<8OtlF?+&*DIC zeV%Ld*3i6RLuS-RM$!b-P+grpRMSW!?2~`yr|&2~{h*=OaGCKn%4XuUimeDD6xo!@ zSzO}S5D$go8yqFYPUCA{hx(Ai`&=j<4?X8~h}#`rj1=-{7t}`_gbHDdC?Vm)%HSZ1vc*Qeeew^eHaIM8$uGB|+)UI~o*#^P)PEGK zJh5QVqkdogz9$%r<=Vt5^&gRlYe-~?e7ES^Pki?^^*j1GbR9KC*Xb>xb!rN&2O97$ z=f$MiYT5xCOoDm_K}lL{9)&+;gl%TXpv4R0vx`y*4-Fn#-5g(YXmIG@DpXxRXxfoD^4P9i zpS`&^P+VB$>fJI_S-oWcNRB(F#P0+ah0lA%hY$Csz7J6O!NJ3;k+XexaPZ(70Pe`* zu=st`-e zu@?Bl?y_o!E9~CzgPT^KxpsGxBjS(sS}Y}lp@Zs=)c1Mox`V;)I&TU4uJ?)`^w#yz zkrMqE@%t@XPp`3;IEd(bJtd8vO=}~gy9SDU9Sx2md;M7D-UIS0fu4FVyMn&!(PmE_ z|L$h{)0?9@9Ob!_ za=bmh?&$E~bq&Vc+ydiu()kiGTwLGO6{%a;S_ZU)gNA+Lscn;YuknVZ>x>1t1~q=$ z=LOjuv1=yZvrRl@*w3C4Yr?D`R<#v+8=Fhu z!RT$oqtYjr>Ph)x0wy^}Gw|UkU`3;X3~0^TM-pqsXPQcq>ccUJX-i@OwsMj~Z*#E4 zl#pb)3Uh@K`l4B(Q&UWHQq!uH%=SfgE;}^^oee?CbS~QCdt3`OGV6O4DsL=gD4jE~ z5B$I%W=u6JP6%Fo$RAwzhWv3c98GQ`pmQCJQXumH!V01d$<=R~0hTZeNK;8A=#hCy zePY4!N7w7sY&U=K=&<%9dn;793y+>#wd&kx*ynM4%Hg4p>^Njn+CSD`+pX*3TAYkQ z&`pf$3+o>_I-(c59lh?cU+TZ4P6C?jNPR3;ub%dW?Pu)xpFEEZ0-pz7*baeA2n-fx z7z_~A&}IU|)L}vKnw0|>Igqt!62~uAlNCmkbh(=bZ?SZF{dI$~N6$B(`nSRB>QfIE zdrj}r)zqoqR*#mZ9xN!3F83|05|^ojSb`FX04?D;ZrEf;fJw;<;TVL zzo#CL$K_Ryh?^=?NKaPQP#h1QaFqfSTET?edYbTF$)s-K8%nglAN9U-?aUyxMKXC2 zUXkxJq-nH@nzSSUk?jU!2TCUwJ8p|&C`U4&2cYfM9i&>U}R-r3GW37EDb zqS6&b{+gl8m>F{2B*U%Cg3wb8()PiP^~)=yH3sTj{thxDa{8R7Z)T>V*&!;<{M1M7 zm4#KMsfY4i^wv5$9!KRv;}c(Z<)hikiqs={E+gv|YMaL<(gf05CW3>%$;S-RHhVrA zL_7sJlUARk7n=4BSnG`Vk#@$-Yg|vGU;}_kB|d?x+qAlS ztm~pgIXtAOF;gkBH6c-vaH0v^l^}*Cg=2@}i}wy!R1GYPZhK$>ypGNa%QMB^?W>J8 zQ*MD=)7)4QtdmTx%C@S(Cl6Z9PesD53R~xY>uvC?9+#{2H%1}Q9gdf%`2KO-!SG)JiH;#?slk~+mH4fOfEE-3T_wk`W8BJ~{zw zd)rod-+2n$eq!OHAiU7?v$)%zgtxuJ{rAqYE@m0|bC10RX8-3Nd#mEnpL^`jJ?1R@ zGaY-YI?OxWW4hF#Ah&KKDh9&7Kxw-(TyQ+ecn%w`-d#U2jy>#ihB>s6W z^}mT;`rkE|bPe;5Oqbr-sq`nN-S1Ezy|WW(dLQk6r}-Q4%|GHCqJ+yoGGAl(F{zic z?US6V?Re+Lx3yT3V_S zZ+p>oMpMaX=bXHb5C`5KA5UBRv;d~J#l<< z7EZK$y$!6Yftp516LVTN%&R(#I+C^iY^a!fcnqIl zbLZQN>3*0lu6uYLq6IhgUtkeTeS?F26vu_Sni>WgqXk$lgsO^AGZu~4%qq;lRvj+# zWCf?Pjz)A*CT`0SU0r!ck?#Jw`x-st;4IAX-n$i9{Br_UJ5NO{ibIvm@2mwR2G&Dg(FWj9hW^ z$jHrCjN}k_yE4C%)bUJZt*L)1D7Hwy(c79(Jn<5X-ApZ-kl5MamS| zY?K8{g$r1tvS1AR@^Zy)^=sJ3%{41drvpH3xw$wA`L&9MM%Ku%_^v{Z^qshs8!%!V z)R%b+rVk84^(mTOj}e}0QOpc0mS@C!n|X2&3%zsA_)S)Tqyz&$b0cIz;JAJPMQf5# zmrQ{^HLXZUiEKcy*bL!F0 z-1R^w>52{ghCi*eJO-MrJ3{Pd*|lu`SFzgJHw*V+1sE}cSop}lq}V+K*f}|Pf0^WT z7fN;snGF#T4tz0qQ@vClh19cJ^sG>lA%+W%wtMdC1(-{GZr3d(A{2BX<(l^kKGeQ4FD$QSqc!*cji8H8|M&-*o<>z@F zAzo9cZ~yKq+ARfE;QUsaM(><2JKP>uuGw2%n$J(5;xqa!-^90&Bj6t!okA_IVTW*w z@O~gCzaV@ctAl4ZWc9F~)TQ%)nduBO>x~(Syk$*$W5C4G=%{`MC>AJPOlDM#g=Z75 zKt?3}B(@OPBdxwR=9QyX%zYvoWAK)AtAs>L3shrDqM)-2Li-|IFDVW8RXx11wPn+A zHFuNI7FEXW*gS$DOCnP`v!L(PNn!|9!<$-J1>KR&Q?H8OD^e2-Ffg`gd#lYncE|1n z7J+89h6_LmHB-bt9Fj+W1v^Fe*8uJy=|)yBY=GL9DZN({Ppk za!SuUqA*s6rDxCDA$LW8uz3Bk_pTVBC%YhcLNSltLU}{Lfy{+SZtD^h@rv}!qcy(% z?)?iK_7XciHod^U4Q|JfmfCXYZAZ>*|*5=@(a7 zbL59+V>2P?!z~K#nzAzY86t0I++}4obVO1{4*$i9RSzB;Gl<7uxNGr}vo9PUIeh;L zk1^M{q^n_3tAC(5G_P{>ss*9Y=vAYY^TOR7@m2e}d-ttusz_8Y%s~#9PpC~mAN&@z zN{ycp*HgEukDXz+y#VkqzD8O8*9zc5oJ4hvhlFnkKM;N{{0=w?f`(fSf*Cbxb>YG! zZSj<&b_b7)CTTMqY00c-K>TWv?X<2mlsU$$z-Mul*xDnuvxd+vNvt>vxg#ES6{7aD zV#$r!+a&yRy(G`?t0#g({L_5otf_PZ2aq@PcN+SV911!wmrZue}CkCr;n+1PE z&`FcBerc(?RxEEBjz))D%0+c;>C$?d&(2_jUq2^2>=QpH(%F%b)(U_^f6g}?)=!$k z!sA>EH6BJf`vydH2tVT*b)HDAbdHGPN6;$nG*zXaYZCM=;@hr*AB*fbW{Y=zN$8;2JIiZGjzumsyQUP==^ZpWlsdv8_!Y_Wr zU+VrDtpNU){Q&6~!W_$L3Nf24kO8xVC`{M1yv3mv;%RjVP^CoVQF9Rr(R93%wpJIL zWz5P6<&UclMDFJSt_aU_ABW${a#Db%Y45 zKACuN1y-qy3;iTnF%gOip~ZY+g0APlCa43h^(QndL$2@v^uT7|&#hqyxEY{dJ+Xii zAF)lE6DuEp)FW0~080nx&+To_))p#rbn!Fv6a{t$X#uqMXyuuN+}!-~{KT$EV=UGf z*_Ftzu;=DNI(FtXtXVuzvS&}pz>+mFt1A~3mTk6uN008a*`i*%(^lTyQd=;8etunR zcUewho;Uis+0xkB+DNrc%L*$R0}e-^sl2ev8~KT&$OiV%b7txGZ|&Xtt?f(C_F&gm ze=HS}xSGN54_1AwK-@Scp&}>FgiUPOoDp7doAyK%><}UDt%~tQ=!o}f7KJ}?b zDwix8Dz;d{C}QQz89cRO#i>D7*tIykX4Ce@hV7fzhCCtW`5|s&R?w%EiOD94Mrmdm z{D~Z8W*LD5zhTuNK9^|tXwGCR18fmaALOoKXbp->a!s`I zrFSmwIWM_%OlrSluluyEwBCw(XbQ8VKJQ3)00rNC= zr<5RE+^99@Ti?pDM#`oGH|F`Q;*SZp*IttI&2Q$E*n!lW2JXdWRW>W#75@y1fmMo* zGyXIK!3!=U6rL78ufxu=!qbG0)<(8{i2-yK`~!S@4L==AqSn ziwWC>yD4%#ZRub$YPwPm`LdgmUWo_dL`n#X$!t|J?VGBHAlH;#2y7aRl57`|TS)|s zs)T0HKWOl99hjOK*xjVbP;m#b-PJZD53_KvX8I&tTo@}JKQk;PYL*Oj7S-(?s$0Ew zQ`}oF<#jFJ)!n?ck+=Y(Z$~?~&aW!Gq3otrUn&%TR9w>?R9ARd3SZb~&9$^0=xJS9 zmK3XZ=LKtGB@kP_7+>PNA$Mk`W@=m8WfAcw`b6f*ntAzhPHml9T-Q}Oysx3G%ICBg zf&kd)THjaUERZS~W5R=|uMOsOt-P|Uqh0;j_*MBqgY;Bof4tH(W(avWwM-#fR5p|Z zgY}hP{!n44)FFyN>tLg}s+_NJg=r>#DSU=i1ckB~Z`IdBD-&=S)Hfxv(AY3_GDfgI z+yq5(1wpJr#pK7BAq534D76brC4DhyWu==!{D@M(E=O!9LrsWW@OedOS`bqCZ)rxw z?#>!yc~$c{?!A~~#U^P1(x4>jmtECgnoGN4UP-vEHSEO>Y2j7~)J{m2@n( z$X3=~0tk+_vU<%1G1hM%*?ufxqDLldemuqb8SSa(=`gn<<`B};(%U|JMGjrS!~w&P zbqYE4s$QUbhKFn$NWt5*_-3W#rp-bCI^b-Nm$97NcA6D?Ik)8$KKoZtS7RZ=e0BA&*>%E|uT zlP3h<1`ezI|$m2i;)+*OiqJ3LC4IB|)BDzFbiF#@@6l62Zz!!jE1uYI%8b2w9 zGQ()24Ez~TVMwxO)U6Jw#O@NJDV&ZG?MmuwWh%+>;u2ii?jBYmzQO`v`tf4&II3`( zE2SD%`o!FP>lfJdv}%&nrbbn*$p|x{X*gPzR9Ny3sHSkebp6A$#jX#$lz2` z5WpQ74hxv1P!Gv-F>p$EAol!PU~S&b>MEkT1q-`CpJ=rx!BtoyF?(l>e2HT5i|EW4f_>5)4#%>8H)fryQ4q$*!c>SJvv5IQ z#Cwd0w>NSMrMnV|rS~Yi$EwNDtURv@r%sufCc}Ih%pit3YtWMrPfnh{stz%c-bzZs z=&^^_j;7KcmCTP)`ZWFY6ofwoIFX6@$AUldMi_$p7b4!DS00?l?I5H#nZ*+))EiL` zPq?73q6@sF3O&^p{s?-D9aFc7FRXp|*r@vQj30`%ef1aG#{3Hx1S_}x0}~;HZ2vfF zYU&Gva?Ig!=gYC8;@Ygw$$I{*Zb;w74dhfT@>{9UJC$C zI3_cHQ$pI!qpxOYPe2tuBD_!d0&yD>a5PPvW}Bx^W(oE5{=^StH|%yAZb(|lCF@a< z=w{gcq_6@mEqfc+l9v#@N!Tnj>A?lR(p(1T~(MXRc*PT<*Ivj}%w^pR&0*|w}duXi6>(A+_7fl7Fp!gQCtT|jL z8&dA7>bjE1*l@4M-8Hl@99=lrRaD$Hw5Z0{7^^QL(r;i!1w-2utAb3bp4<%^ktMcz zw5=c~H{Vm_i^%x{jh2p8SM`|l%+{hjlk`+#|B8mjmDdcW-fMA&rFR{-7CLfJRKXFP zUp|}zwgOh~2#%Jw<(G%bttRQo=ung2-!zm8S@SAuD=iIUPG>aWj(YRFdDc)>giks$D1U%vO5?)1I3#`wzk!13i<*gc}W+8bc(>L#Q%L7ABx6 zK&X%`;=-u4YZ4dP@i0$_t}`LHTkJw%B1Y&TCPN_5e8E%~n}%LgU82)l=X8Z#1Q+Uy z^;U}iuu86d8^Ol|4#T?eaD?1))iY;jN6+6`O9Z#JePfEE( zWu#ATdwMbMfx&R8dwK_OgdCHKX7g?YMmJUZ*dpA9syvSf9|ZJ@I769angeHoFltdJ zQ&lgh*N#Fi5pfO*6iL1^XmMq2PdlEyUa1{|AY|<^K7ujJK0A9`bi^HoM^l?EG6oj!?*zNMzOGRI`bO;tr_&k50x=Pgo5wPCDvGR+J!*QiM* zE1A+fotx08lq;xaLS$60&>3mDAdDD)K7+oiYiAyKokK2VUdRGp2kKKYwEA#Lgp_Y0YEJJ!_tjP`Bc!dfn=qwyw#k3j-wHej|Cp}r zBWsFtmM=CGlcYI)rEpWe^32Hv24PO`<${yXydt81bt|v}G`tWH#S+AG;?rj_)j@KI zmDEwKcn6U_+_WoW%RpNOlLDcHRhq+ur7Z*OSVn%3fMzZTyPv&dnbLFRolAFr;d;}zx>XReP#FB51u`| zB1Yb8EyJ9iycTl9z(t>Z+7nCeK;~Vr-Vw2ZPlLeTV2V8hy#XOzOA2}V7zPhLnj|RW zcRaJ3DC8szfhqhxfhk6HsP%V{_&X4fSI9%iU!FC+`lT(T%f$m|?o;4UU|GP{Rn2Z=<%A?caD$WxvR&> zDk@dZ3jn^@>rNO;F!IQLBUIxFw@E7UMyX=QUGpo>XPWX|LGjQqQtA#Xtmrv8R3MO|m$nr}2_gr5ta zBx^G68_{@hlviPeL6c&7s1hqoj5k{I7S#D`%bNNF1%=M3LN5S^N~&vHBQ?GCfznc^ zv$V9LzPDD}^SUm$T#i70b6K6gZULaBkKF%}kB!;u3JU7%V;}p-{YOTO69$8&GS*n- z-L&|=j$=!f9P7Ym^>BZe|Gkj}DseToHI{d8I?}!V*-e|CUEh6VQ@7mP+qdkVp}OsD zwF9lyE^Dc)q);>$7CK78CAqHBV5uwX3tXyRlF<}n-)Z{;k zpE5$#&$mlTK|#JFr2x4*A~mT?1tVV}7~LvZOV$ay1l)$%AgDasbhEGtRrNrn*cxFm zh-8!G44oJ;uMe$r;&4E>V7MtZ9~@n?U>d0nT4tJ|0}%*V2N2qi>jpGl{d%;66afyB zZ-Nd*Hq!!l^D8AwUipnnY3Wxuf~5`s+%x7)D~fxem^}{I{B*s(&PcNddj?(=r1D-M?tt^~m5!~S>U;FLv zKlg$A=gqtS1Lxj~@EmN|*oWcfZ@Y^LM}d(el!!ko$F|C|K=U_?4GlT)XDQ z?|fyUt2$UjN1BhFI(z4>U29)_>C0nu%q(=Ae*d{g9~{2o)!+SiC7sc9?X7p=jILYn zyzAC$o9G;4BBg{Aoz3pr!KOf=#Znk(8mx6UcP7FTj(0Q_C04}?3*)O2MNJ)va7kUH zBDZNET2K%jXv(dK)RmOfg)4F^;;=lK3!;NK7OqR}5yh4D2*>SlIPpgB@f zU^c@)7RULKI_geAdfXxySFyeYH?at>V=uK@`WSIoje|$QqlT%5stDZ`6HXVtTjNBo zS&#tad{$?gG+b83fN8qyT$6_z8(6?_OPk0wVjLs%eEAD`IdWM}{^TEg%j+o`L?L+8 zC3n=T|A=~{KG&~Z29x}NReF&qHIK}^Ah>pN%@v}Zx}M}a{F#NEsej4Ib6HbYhH5CJ zWbp8s=F!A+CIi~=p>V13;rt}Y+FHCNTA^Kbv6vtdjZJ(o$%1h<1t^0(uuW0b7!b_t zB@6I>ZeU^jH~_O|fa#S`B4Kp_>?iYkq53H6A5D6L@WJ8LL9NjjF#`)xip$s7%3P_% z`U(|)*0VQsQ2H6|gZpBc7AlD)scUsZpmZx_nl+o8ZDoeddI1C=%MJ}4TGL!mJJuPf zTeSV^Lv2MZ2lww-Tp#Eh10Z2AQAW(nm_v=WHQvv5XmPEoDymJa+||=Tw<0l7pCc`(v2!`F&d&&sqQBDbtyc%fa6?DW z7-X>snGpn^DCUP>X_cnQS|N+@0guWYf%rDFH_bE2UG~}3o<&%2{M@>A=Z-Hhm{z~% z%C2<}ym+m%(rJ|R3KD_d)vYb?|7_W=Rxao<8m&e|b(E9PPpXIdRN2a%hx!%__)GM5 zYR3<%j+x!gJodV-Lf=MKqC)FDHZtO0Z+PVB@bJ+`Hh7y$O0T->@%6{P^Ou)N64X|` zZbkc!6}|ZjKDvJXZT*8^F+|Fm#q!pXh~8}Pl(TJ3Z=k5{svf{AF8yo(emX+T6} zI7XDzfS=@c#1#Z$auT#eRpW}{U`TCO)DZ-oxFclxT`*pTWB={a&=EQJoO>YO{hRiB~{Jfre4PO zz|~mDa)a0OQT0jI&?+=!5RUxh>z}@Bm#?w)KL&0aQ4%)}4BVI)>_fN}{?B*Z{M&DQ zf-rzwF4n$H(t_*9L#n)klwaYgZpXdGh!L z#y-IfaZ^^Xmq-tbo~8AUs7@SLm2@Mp3SeXu$l3y8m#blAT#T5l1H#RyrFU+&hnbch zi+jq*0W9?C&F;RXYh6G%)(j8`*qY&MCNeW=OEl`m5$Ib~-rD}uTRgv!`f^gCPmzfu zG7F8MN0~Z8Z2i5(;n zf(I4pCK73;_yBIAabQ_^iohp87Qt27@5Fj>A)rRdq5~Zvs(IA<$=HU%b+V_3ym=1C zFC3+Ii{I^y7FE{PL~Qe)*mli-Dyu)be0jr`MtZnCU-|SygFh`3Uyd#Btt|FP4_ke( zVj9)!?SVkI{8H3gSZ2vBLoV7fqJ0K$jnnSyI5`q zayoj{kPP*X#~Q|aE9L*sQ0ehj%C|`dxNPW+^Zfc7U!nf;gN56k`N+M!*78uu@0^(4 ztIpD3u*}+h*Q1}_T=amN+WylrX>$xsR`!fH6c!qmdHfaL$(9EFOm>2;`)1ezDJp4>ahKB*2Co-w z3(+(p1G|wCV=kX*##O@Y_h`023*3-yzr|jvzwzKhpZ@A@ni`L)FCRPdfOz9SocR60 zZBLBaA~m&@MN{|0yJ;vpmPMv6xWZnG(=5LkO$Mh{GFlLp~ec85U5*1S17# zoe$N31XzDL6ggiP08S!}2UcT9t0Mg=a00Ofk8m@L64J@ZhGP(tCV}Q6m9v6mm&B*> z2e6C?DTO}yJR=K+0ch_XkZ*+&X6z!t4gv`*MWCd$B3CXn6EwF;lV}mv#1-H}6<4*F z8@^K-uksph*X?Zg6pZdZ^_QR8#BD}KCC@rpXU)^AYKbiTnnI~hqU`3r^7g9Y!cb$W zZh^b(!)FgJtK&8!X&=?$NZqa1!qR=j2-1b@rD;PIK&u~OzQe6J2GX+n{EiV%@@g%SKqG_{(RzN|`PL z$1B&fvz?*Se*xD-P!FFrtnaC#A?Hw-vF?%PSpE}fvo`Y_^Sl>kwVuR+=iQ{}FUkXE zXzn6*C*swrZ1WjS2PtPKsTrAw>oM~*JKcyqIhjh|jNeCM9ZCW&t46R(PEad2mEDkk zqL0x63k69ZpvdIuq6&Y|h!kW*}e~&z&vNudhx9Lk$yjHxmdco+z>cM!zT+uR87aE9{rJk%kV+@pw z!Ni^=wN0xIB*d|(x4>cZw3Jq_uBZOu{a0TZ+O+@r!ONc>o%&Rvyg(l>%5T$G@A-2^ zn^Dg3cl1>Aqam+9&C(ejQ?1+p{9Rym0T%`K z!TuzRm9Bss!x3}91C_G~t_8$>@x`kxWs5+Md!7xv(J0BAfv(hZDC3bCz{Ue z|L&KcSim2ITd>4O`p-T0;y#{b#S@&4{q*_A`Vhl_+Jesdh#IS`33k<%l+<~BwXVi_kFSw{haVpSV>n*(C z9wY9q*;lAnfO7nf^ozL#74r1u)!ZI#dj~s8DNyUp7Q54Fx0uD33f`H1t8-aZgVU}W z>`sGf1)oo(tyI0JwssTxG3~Pgz0gAim^V3&127Y2%oEJDU|<$kGPvS5E!>4ji6|FW{kAFg8&w4a8FI>-dt-d@2L7UQ~OkL`Tdc>3-ca>ra zPI;ojkl1x{RGqIS0v%1qDXafMbQ}v?&OQ3M-``G~2VWXrvf3!ik;f<$TZ^zp==C@C zU8{{Ag{)$FKfB?xT1Nh!Oh4=9o_5Xf3`4ADi6;F8)nchvnGF`fdTfW$egtq}DN|qS zg*0c30OO~buGL8_OTME>IlZIh7$0AiAhGX@+cfscKQp#+Q zWGnS?rmx3eI*6=0In$S^t91q;>-{M)(T2pd^T$K;w)Rwu zlWb}TeA7GbzdkobuXIiC>BST!RVz)V&g$Elj{IXz%AL|R4Yd>#w)nL8!_--Qo7-r~ zBBBTdO!kFtq^SwzP>}QSR~aA|4#bIen5>93LMDc=p7ydhSXm=3{sqKoW(;6TET~;l zey_o7xY9R2lBz+RnDo7Y&QMRhG2RiBSya>`v1NODrxx|@Sr$`#^*urPuYx`GK8SE^ zg>iA#Tv62#D7#xW8Fu^p69{xLJW`V15RX>_0DdBg*tZ|h<LM7ZLndQ zMr4hKw^*x1R0@0aGU&rsVAf=8YiTZscPtmfGYrt|b-8)kQ#3>5#oRFJBCkUea5cs| z6F7mya&DNOdYRllSw`qbvTW2;Fqwo8p4`op0IFh7`A}TYNC%HrKWjQhoXnGXZI9oju8JLht>-%nnXoXf&mILslmD#p0X-Hj>;%c z!c5`AVFz;#k!MKvoS9KU(!Bg#w;$h>Piu#q>D@bTUov*G0|L_6?(42zX|En?^eN5N z>V{dVz@`K0ZxEgvtcjEwCqJn1z8CoRho=s z86#qPL15FDXRsyJw3e5h?#ANM1eyjEf_XuZF~k!XVu!cF?=hGIZiC_umP(BHX`m{~6nhp+*Yb@k2lEPxiwpAxS8iP1 zCGt=kCRIYu!+85J-m_~M64N%PnKrA-h0sBjqhe93Ok?IDijJRFNi{elHj>$ zl|u0rs!7=K0*V{rOyGy)=F<7xwvaHtZc5_GI-nH^Bq=ceML(Uh1-7JE?vjQh5L3GSrH-&{n#?_Tc{EP4lGHN$;Glfq*7O9Jf|!$->qO?*H$3W>*cXh_?J&;_ zV`gp^u0}6jFPvaAfE#9rEfLkp&fj8YIvRhT4+L>Y^QCZG7epoyss?a(<}+k=1!{#G z#$;H*xM2t6!7qvqshEoNRsK-W=JGr3PP@Y% za7X;kaI!F0bmbN0NwOvB^TQhDADN`O;8JI4bOFpP7FV#j+y}q6`kE5^#y4Y%`jlu< zUzc3<_404a&E>VNrRwV>`HPlNyLC`@+Qbe1s>VQSiM_Et0ENKi4HO+Q6qJ>`+8X}6 zyTO}Rl9%TyFSNNVz`*&uZzS?+X>-KW6D^frqx(*MRqmSl>KDpdsyy($`~F^3fKpWd zjc?Q|>V;HNX|oLok_dUqlteGP5qF#=N$J#nZn2)n18AxwmZU*Si$}e?ZXa86_9`r+ zY+*4PVKU#6_ng=hT|z(9j~_bwSVrJUT|IXDuHMeePmia@)D##0^4_J%*Ng5M9NN9G zs%qixp_32NZyP@Lz8khMI{=>EOiy0`J3xytg6fP{QWOv;18gKI-H*lPlY+};0?it3 zc!DO!IRI2)lA<;AlQw>wS-{y8BUVS3hsmM<@L7!$hOuGRJq^><9tyA~*B`Z?>6>4? zzTmFZQ3`ZHwk?8I^X(V#ZAwtax+qZ4i>u&*V4rMaUu>5jrqpzPvVQSI@@3I7a>smK zDm5gwef*cVWq0}7@#Wt>bm-g5$Jf$P)4+UPHgEZm*j7C*x)Is0@Vs$+J;CzJ6{O@K z&0I07N%o~5ynouc))VSe@$GT-M|z8v+kgMLqq+LHrS&4*eEFI+%kdZ(G$wD7)rQTr z1pK9!&(09#nVzt`XClnGv>9Q=-#F={5_w#mOmikgbBWUoI2i>|R~Z=_E=`1p^K#HB zqDHFOStiAZa}#%+cox7CNrv#C9^|MJNw@fyY&wEDBatPU)&rMnn&ISMGr?}MGT~Ei z<3TnQ07+DJsF!}?GMappS|%t7YMG!bl5W*PBSU}C4Dk;bBeV_&5SIfvMT`7PXU~!r zNL4~-^i3^!tr){&^YZO=j&z*501jE|9Ug#5z4N*+Q7N*Y=tZWw=jt7#jquEsIeTY< z_l^n;UiIs}1D^tVCzL3qR?jmL)-Xr+q%z&Z`-gdPYFYnm!y2E#Rmtl&LuiAg;^K%U z*^@`N{-Jj>@5@E%vP6)&f@geC_lU>lMkNuGb9B!n^6+6{J}2i`r7U5MUR=G|1NKs_ zvtKT?VF>0#A(=UK*hyFnzI+ST%cfWkEeCOycD3u>`OVrY|^YlnkMBtTU;D zFD5c9WWw8cVzhXCe%pC{cJZ|bEnO98kxg566aOM&nUM{FX$3LZ3~v(7B1`OH%qK8_ zG-)TC6-W}yqf8JL`8m=#gs z*|&7q6hHj!1ylFx;Y;%63%-4L=;p&)D=bzfHNPt6(bHHh6l9G~dzvB4uEA06xK~W$!CDO7h^OI;}$1AU{rO2|h_|FA@Ha=Xwo=y*D ziqvT-fhiRvW6unJ5*gQ^iN^u^Xv?Tet{k?HQ_PuSbBZM-P}n;~u+z89BqSX>ioU6_ zOrR&HgKUgBeWWIVa{;M5!>-t^QIFQgvIX>}>D!{K$l4t!-bJ(N=7F0WM5N0dh)#M! z_>%DVL_3)UkxV-9QF3o5IcgCb0c}5ubUWxE`excm(2oE`x~`kTo>^a)!{|d3-NT?W zc`!z$5#AL_KEf)X3a+3e@LzEco){nt3M}a4C4^vRW}!U?+q2T@vgVneS05$vZMZFU^?k0& z{|ve$zy_(0K5x#$&nrd5Q%4mwjaF!NQ+#Nx;iYq&2(Qhw_9pMyK*dpQnEi9bb5TAMx>)4kT0xW4n9!eW7Cy@O1G zr+AP(HRt+jg|$yJe6d7Y%p*)6Z9U9ZjY!#yzm;X3ES7z@J%@^=y<0OB+vA2llQ(z|Z#WK^82J{~i z@55O&yfycm`2vS5O#9IQU?`f-?$iBfEQ3OGr9Iqt?r&b&n|7tW@U!c-JoEAU`>#4k zZ!jf_dwFsj@L{m8d4mWN@fq^W!Hf0^{B-c5Ef?`KvWS0sj54g~-1TR-w`Lq^hZ5CG z4vZcb8$XEC5~>EDs<4&p1d(`1zPOev7w zR0VR9`dLo}!sd;V_@A>Rc$g#gX_ww2xk>@2QIN~vM{dqink}$)!-vZ8Y$%2dZyyyS z5)-*G8M06@$>Gc_Q6zb3v}_i0Q4)s$W_jIpKY#w*p+x2Iee!~;z-kVStR7N6| z;%60cZ0f0y_|#sp>;W`<$M&|i%kMyH)#Cf^ys=GP=yb{#W^$8RScjkykfgX2)k)e4 z=cw~0rGk|5_kkc9ZGYEo{d#sSP8UscsIkhBY9Ai@GhCLpS2WJ*{R*OPYGf0}P z*i}$wIcY6-2BT(Ub<3)IU$_QQXim@0_3Z~XcDHUj{rK~H_kH#8)7x5&O82^5b?f)p z$4_l^S3UjYh6=X7SLSbid`>ku$Sf8^9$_X&v!Ie*gd&x zAK!k%bGNN@i}{8A_o=@*GJ4NLk8j!YwNKx(r@MR4J)i#CHH+^%dvo`@#Qb{;M|R!5 zcG=zYy>0%Q#ht;X`tsiL)Az0a=)KpkYu^9tYcz92^bGteWh{QC6TQP^5{P(UYty9_ z5~r~^0>lvEXlZLj2n`kqZJ7lM!AFbFR5)<6bLhsa zHw}g7_eruo!^vY1843BUzV=eeiG(oZdgDUsOjyhHbY z?9UBYVD*!qJU6`m8Y6O!M$fHUb#655^Ef``g!kf84wI0>!KvxqHx5}!B94$l zm!ingV6zV|G#QXkszxmoFRtGZ->*=5&rmPPe!CvHW>LM%ntPMYQcyF}w)z*UBkU88 z)W>4=>Sw@%ma7mSeok+Ah905L~&AD1k|LUGxYSC znTs=9lk94C8NZl&wG7OTET%8bj+)Wi<;O6Ad1wuxiezt3{@0rjWRN!u8G-}J+CWvl zw3Ul+VHQoJFJg$d6f8x#FJv1C^nUABtc8mwyqz{KL|Q0HCrv_I5lhE*0mA zb=RFye|Fb7HTg(!cTcCM%rw6KTR;2#gnE4MRi>uJ+uB4i)LLKLKEF9Wzpb{uB`lgc zf#+RTw%qVb^~vS;UNf($-dPp&l*ig*CF@yFcWD)bE$z%@|j)@@bd|Q=An2cObDslOl*gH8zdGU!l6@i zs~<;+VKfgDlpvupo(pvV^2SG}5;U=3E->;yNossbcmpAUf-$qWA4PJ2`>|9lme@rK z6bHd#(jdZxNlK)sbNWSuW7;J>>-PKI(z8soOYKeVC5!{mWtVi1#3i%TFrASab{Pd6 zea`Jp4Kf@A>E6^HS473>uGAih10SRY-EQeQ7w~A4LK5ig31C(E;lCYZc0|(r?h!tK zXXN6l7A3->FnJOz%Q`c{9KoLx^caH8EXqDheBm}T5F4gLav@pY6KAx>z;LqfHtkvD zB=IE1muSnCVEvMC84}o}S7o153Cit%W0jg)of6a-Co61oLdkB&kjTCVV7Rn{tYod)4=+yhJT*E9nl-eQYtXP@-dr8UH}s?YY5Fo zOeQ`i*@+6R)Whx5IzMh!?IpoN^*8QNw4t1MzlsGnX)7xKlDE9Ds$eDS%awV#dE4@f zjsHZCIVI4ixv#t-8ghfk1)&_bjKtM{n|Pr(e;dwR$%bNOK~-V7_eY)BwTxu|1 zz%z$|+tZK8X=XuVks~zG$$9|2iIOU`XN4vKX2x*$@phg**&JnOdq{9JW>2HI`GUW6 z;wW;!zvwNm31&UGy~Ow(|3DR9u3QQG@_in6&PJ=pZGT|f#51x*+S>m`gH1O4IQ!kI z2mBNDxh~7Lypm~eQ|2+=?kgqX$`{Tn^yF-^78QFxv27wH+YDdq-zr(;&!t~Bi_Q`M zUdkdcb$-h%A)L3)X?4=|M@>0|l*d<$<~c&DBcezk7V#!QQS#Q!6W~r16%+mwTtow>JFG>5J2G_bJhEw{?5UJ-+B7| zSM_9XPcn+fS@K20L2j9S9cv?=UW`O7Gta%|O+5D{R^VHIlPTq5OYiNy*X-3M7}GO| zB#Z%MVC>1Rxk4Is+VI4s50Wvg>0>C<(Vm9jvf5o zBTxLAwm@I9AUTt8U%NygX);EEpIvH_PDxeFEOYTP1`ub89QK%`b7Gk z8Hmn=OZ~xI4MfECGpcK(J}~1E>9H~2rKBhr6R`c2gJTVVVsY~o1r3qtlV!)`i97Pl zzM3}wWz9HA^O6zBByDY(`Py5EL~|n>!9--A%dS!rGoPN7^Q1HJ#0p#<3iIt%gU4po z+?hW9(<$#;8B=xf%m;o;<23WxFVgeVR_t4*H1n>1;(LEMdCMQZ_r&$XeEZ~U-+kix zc|h~qd*9RFJ$UfDPv5tu$>`)M| z9=~ja0r8xsk>?IhNjQxPCN5V+ZSmB<)0VTMwgjnq18bV4M_ShPhx7=}Q2)A?)RyKo z13(Ak5&{1~CKOy(EWNL|wwzihPVFwKEq9VHO@F%pWVl|StDL|al8mNHBD3Dr z3>(!9fMq7V@BkZb*`cKb+dp>(LDMB)j=rO%2Eg3MgZ|SdL!-jNC&?u?#R3AVhSxR2 z|4&_`P^zH_w~7w8!d-4822EkjRk$Ko^!M+Iy5VZCC}_W9$&%CUjtY6R+h3lu;TI=v z`q#^H%1hmdWbx*}kvjS}J9d6<6kgOEUjYgZv7pNAYGVwlh;<_RE)!D;j3=0unE!LY z8!l;TX+e^-*Hcx{-qqFa2>)w-I10lYFp!?m z*GwL0M4S@UV##<_W=v<$cR`1EpmkrkpC2Yg> z9IJ*pkK#IBgZMay!aQdb19yOCx(0zZi!a2+en{KmXWKc#WIV1K3 zIuCk;1R=a~MM9X*@&J_TWTeq4SzEfy|?q^qFMhJM9$T**nB76}nkW>~xm>8$B+U zue8^E3Smi>H|szV1!f>&&C{0pZeR^`>Nl8Tmc5}~L%Lgd2O*|PogK3>r&ERNxx~+! zg#Cm)L_^*y#@&U$gV$_hFf<2(X7U*^2TFEi6Ua&#f-oo_gU%w40%@8|!=#{OMtE3( z!?e={oL%p8F15-mAVq<*H52BP5>TB{p&S*SHcQieqI>T8hN^0vR^O0p(&T?91o(3K zCo}+}K%dmeNd-=6S|TPE@f3;16@^l6=r=9yDKTeRL(y82n^o3OH+&GJyT6`7clCK` zpJrE{k6795%(8s5a0<5NbHay($KmszHB+bAn6va(mXSSk63@ZE!o&yYZ^jCscG+?I z?TxO}uTKY;i8=ZrmeM&5nPZcaOz8y&03p`YUmmYbTp@ETg;07C$~t{cq+>TS?MRdK zi^P7{BD$tsPoenw^%vMSKi9xww=(Tya}52t`7x$aV+J4+fCfIpH)bAF_`N0=2b$S3 zhZPjz(xFB!aw#E;(c@A?6aF#oQUU6;2zd_I!nYuZh)HE}OPNJPFiQZL7hC%YVJXY* zPYbIp4#>OdqU0>AALBx-J&naP#|}C3uxy#nhpkWp%X23Rm9hD&_diuy<#d&nD<2Xc zpb1Pif2{!8l%P9&F7DEtQc7DE)X7Kc7POR}zHQ~_Hm&^VU))$z5ilrLhu1N68Xs0x zzU}EVD~+g(gt|>LJqe#Jwb&Z{OFoow7_hQ&$|e1ssa4hE{n2Ruc=ZFnUKpz=9oVz9 z@77^^(C>2jgZ674-dvF*phW~H2-g79I}k&o1NKs_ zv?!U!3?vv)LO0I9D6#)>wiR|VPW##{A~M)rsqYUZ4^aNtrpvyj{7s8Fx3B^vK`I*G zf9Gd3-H zMC#Yxm4MD9MK9Y#nPLZ$Ltk6|>RDGuYZy@#Ia@#bp|hRh7l8a5hA+ZpyknQwyZqj7 zUhf=RKIZa;eMR05-|(r<@e}Ktrrsw$(|FffVo;0Nywviv$!gXM`wk)a%}I^svYa`l z@lIWd=X88JRiE)&r=9>|oSq6n^DzS*KvPf)Bw`es({0Ma(WeJL!Wk-Oj&i!escYya z%-O_KwHP~Qo-0Dr-~?L#&IBy#NAxudjE)%7?GFj}3m=)?H!(nyq!$?J0EI~E0fE>mf7ajD7W3l;&gVzbxv>mAfP zJAMs2K{Pt~uWTF|6{)~#%a!fOsZJzjpw6MYkf)GfP<^q$a%A2@#-n?5ARaH6pC{0^d*Weg?aOu(q#~clHW#O>v z*PLsU5rYILS)gHpgWc-$`z)d5k*XyTYiX&^f;Cc5x?u65@`|y=3rZ2fFRo7yC)yO&*#HP#YfNYR79aQFT<_z|Mj z4$Scr(1V&0zwof|94HpqI&=a|?s2VX4cGaBO3&>1=o;?-py^7;jU1hqs1h>;XhJ=w z__~PCnqtfn)DAgE6ij6T+96B4;qg~Qy0kcL3eF}tL*kl|Od={Ll(P8*kBh`gN66iC zFYR6X@$qZ_cFgH_a4C-R)=8pB(bmms9T(!_`Md#$ZY;{+4 z`-i)u@T!zjSNZ34MWWq9rJYyT*b0IBm1FfRSg|tDb!cOETdc8Nnlig16%Bg_2lq8p zMBFA5?d=;J+)I0AMK;@Qxgl>=?ZDzFD*G)SaMu)iL%BA)S%%dHG>FE6Lihr~__@8_ zc3*)jZGGYs(7!y_<)+F0YKNyRK&ImNn(|&CT`YYqo5wuHKf}+gel7+1chPZm~J~H(%Y{ zeD&sji^bMj>}l)l#0;R7^Z=UyqsTWng&8nSUIEX6!AEy;asES^nS(}<1ROxsunSrp zX&)g?1LpI5tAqML7+}UZ4!?8kZ?7Hy_}YChK6kE9e1v*W@52R8eC0d)&x(bweyP_q zxr6%UGKb3>@h9;amX)_VNXA+SLj}He@7{CqV4!vTS&b2JORxH~6(`okmfic}O?LGZ zSH-2kJKtvCLs*btGcl2eznu9YH^M7`4AZm)n{_NlSkl$w7)nAROob`O z0E)nJ(kw}|nAqZi?9N1Y+IvH<{xo@vQkFH6@iJsm7&@`86it~bIzrxYFd zv!bFrpl3F~ZdXB(6o9y)4}>}Bo9C0XX=VqE8K3-!-k!_iaJpSyG>-exryA7JJpHvb zem!r&8>wBTKcN1dZPC&C5??%8VllY&l?;(X_tUc8nH9M(BeLPreW`N@okY$hzh?Di zf_gE@`ZDPMZGWiG1lwr^)}J+W10KC&Av2X2EwX? zzVyJ79D@#;on@c45y&hE2=R{469Y`Bbp&Kh@|g=Q;W$YYOela^tOSwh!LD2EM zBrl1#>C0U`O5(nJizl>ZGgYD8vIbRXec2UieW4TqAdsDep{!x7Z$rGoQOweHpqmQk zUjJ?5Y5Eh6(SKjBgE~kspumFcB#YK2^6almnnxXWoPL`q_oTxwQJ zB~&7L+I&X3KFxDf9zpSu+q8B1w=H))!rAAM~5=iMRf; zYRTJsB~i&dHV8Y}7tQ|)+P(ycOYaswD0~gI?z!X#^8q(NkVT8ESepCKY=VS=N=n?@ z#}p(I(KQnVN|8k2$6?YsBEyNh- zTw2X$&V5KfkViet!;h>?l#UP|2U@hVa=5?MSz8mWayE7*DsxbzER>VusViK$CKT@6 zn(NKA*$kf#`RvY|;6O*Cv#Kf@i94O4H7g72JUKa2K~!*bwy*9fFKu1YY^8#bww!2h zjk~YgURGesm8MWk)@6+^Y4sT`g*Al@_M+T!&|CplCQix-3LFM?7ssxo- z0<%W7rvwY*Q<&9y*cZw#UA|#c^|o*9+4a@UHS5c4) zskc~x)ac2Lt>3!4Z_8)KoE47T68TPV)M{xseB$(Qa4;ZC1N}XFwp~_dD@i>*Gh`t3 zvYLK)k!`V3RrDGPUPvrfFzsc0SOkND4EJu#96n^}lp$kt1_-VJ)+%toIpE^txjdE$ zLV4q`ZZm3I)2=kzH}&_Zfz2V=(gQhsR}zbMf>3!ND2u= z1Sf^kIcy#J8}SjNiO2IqszXn*Sx6ryL7$XrMg4N$ttqz8dk7|GSh_1QnoE!ymF;NI zS~de~6e$S&4;M!V^SDpJs6(* z9U;(BB?`P^l9_b`5=qd5bdwIR*KW4DT;{2bp`Q8@JlZkcKB@I)f<#%Tx5Dc@VGP6v zvMuHTVf$Py&6l8qc~@tWLtv5dY1R>&Se^YDOV^@78z0MIKZ7&(qdkl>Vc0fy_bw0eqkS6WgQo zpXh0)|ID^+n>KBuzproJx^0vG_w}utH*eMdzP@SO*3J6g*Kw}sNwR+`u)FA={tY>X z{gV^fXS3EXeFu3nzj$V2dZStMf#R2YDWV}8k(a7`xEWHfoWbBVKyoq2fRq5rn{Za0 zeC1in=Y19x`FK7r-%}^f=+VZ=5EX^f3?PASRGh!Q6hIm%&S85ibxoi9J4PFJe^%6peC+bm?#dR z-NQU$Q8%iVxGT#Lg{rMcNQFEtfy7FXawf4N8dkd&c(ssCX{4|`saBvFz|2ar{DdC87pJ;%~18lrFc#6owM93J#I=@VEkzEAgA++m=V^moj zN!AI>IMNyoupgPvvkk2T;>b!jeOYwd3(V5;;qn_64IimY^;8}i#`mK%2`R83EGqI3 z^}vtx4EB+HB`PTJKjnQdkk6R@CQB zmehAk8)J>7o<%p54=e)l#GRrzE@yNHkz#00uMft3V679~FGMf##xPVOub znSus31Qyhbg%FKNp-RdqrClvWpViw2hiZvN>M%4c?vA^cp^Gs*LoUB#z1SrWbIfF| z!x{UM@SM0;Tq1^#6cwRcf=#7He@%@{0?skT?w%favAAa|^vy z&dYyvUj5aBPJ2;~oFL#m=O>}G>{frP{;m3nPY_4G>GfZHZ)@|a2Ag~8J=uMWe*D=b z_ASC?ANkEazV6EYtwoRfTWb79e`ByPR-O8IX=7z!NnKaK`w=9^bush4(s_y>*0rn;BPDm&o=WYM|5HmVAZ@g_){niFsZer$;&YB1C zpU#*WkghSpSX3BY%8B{E%zX)b8`YivywPdPmMzJWEXlep$)|ju@u9?VoRHgzbH_O# z2}j7mnQ&DB!d-4CrBGTZv?VQcOQ94BtG1Mu?NY9WcIidw(stWzyKO0du&v?${k=CM z$&M2!Y(Jk3SQ^c|dGqGI-}~LiHmoc6{va7v&0&YgDr_03l!2eHhGYcGr6!>Ai0%QL z5}S(KDd~`BvEWN%)R9;Qke_>YGRaHX&`(ADJoo9t3q|pF5lXr6@Kf1q2G6~J*RK1| z9UM7G=;h#u433EIp;aIR6PG`+mS(CsnLK+goy`4M+x6fpZygd{iEadN5-SpTi`>8+ld$+iW_$|wPq8bJWp4n1@so4)||EimGFG` zT?F)D04O<^bcK<+NwY?&F3zNq=h}YtH_~f}!dSDW!7mK>DLg*oiECHrC2|7n-#@;{ znQd?`8sE?Erpz;rh_Afd8_hNr{5VeTc=gHeAXe!<$_nx3_wGA4Rd}3PaqSal(6bOM zzon;V%TP>431=!VU*?H8lt~2dmCgK3m|3N$rI^Dw__lFi<}VYb(DH@TRtIFMaiR2! z^8z$)Cd?aG=V*HyRwTmEeENZ+MO{?>iNYUt7z8jokq>VD{D zV$HqPJO#?(MM`GntzF}? zg=Z7qBd0K7ik$vS{zZI&FZ(NRAmG(+L+17s-Ey$o@08Y2pS5nUh96ymS_=(+DQdyLJ-SIJMGTUEa{t|7bjyp@hb7b_^op6>kgY9u= zLJz$G%mgQ#i3_QTc=P{-m*x`NrF^f4kva7M{JNn*C9U>!zZuXml83OOI#MDKZzpu~ zT6Fkpx%#*$_XV)} zAfU0!(P@zI)?BMaIy!H>wn{4mp*-o0pI5hRsK?cC=1{|$b>nT`pj_HJx@%7Bwq{bh zkp71q=-D<>Q*lY)(y{MUO#YlSfZwHw%pgA+X-2gUjjXb6PDEMd4iHGhd_^VZ&i#EI zs{wqIu11EGV%YcXtxUOnkwJJr|B<@Cj7+Wj<2;m@}Aj zzV-rZnRwua@JJO^FexJtd>J!hFiK{|?;mjQXrV=;moa7DJ*fH28n0AKV9^EPF;VzQ z^~-3vjI^PFr8F8e2JeaUAcx${VH41mv%`P`;B{&>CW7-6?k-z|7!u21K&niYt)&v16au8B!r`vgg2i>IqCZLV1jqxjNz#U`Na2CDsHwEE zxxUC+qB)(!Uhu?0;sFa<5H>O7?3 zPiT!nVknzB_oM-5nIut?R-7G&im7m3K*dzLjAbUIlcQ-MX(WXbw+cjsC(t@`?v(I5 zUk`#$Z`$caa&kkd7ul{!X3=mmGG!dpPkEN8pDxU)AL|A6 z%O)7d`7G^Fo+a|9*x0S?&yIJ|jhIy(5kh76PaF=`>&RZ1?>bU}t{qWd89eDzvpq8Y zn$|~3I5=nnFEz+|tM{Fwejn>4qB%lC@^>rW<3ux?pxY2}L=VJxl-(GQDX+4QPyL7E zC+yZA!)u?$e273)>p3?ik}k(fiMn&#(uGVlCb6P4!-C(?cqW?Y0?Y$$34A+3NxD$8 zLfvd|Q#mQ6>LeO2;c$`7lzu7yF|nNOK?-ttJF<)T3+D^X>$RV7^Pt+%bzx~k3kx4&82 zs>F5nmUdk!nRcDD0`sLK#wSf<(Af5AZ$&v<1!5J31NhqtQydBUHirwzSc&1Korj%D zY%s4w37lwr($_Q)`DWMnMf2xfG~V;g=pcyXy;`W*XT6i{`pCnjba~{W%^jt3sq!Fh zh(-sHdE~+9K+ zlsi2Y$^hMuehMIODPqHHz z2{skjSUz{~jjf$K){VHcN9BWo%Ql@iOLQfwmpMPqHV|e|9uV2y-;nG+C|+GIv5hW?E)sDgI8syQ&r1JKw}0={PbxpU z+hz*{Y_`v`w!6>x_Z_dYM}JpQUKOyt(-imK%X&V&`-eMUVGqAaGx=Ag` zFmyxqV2z8JnDWTSPL%NAX_V=PDj*Rj&zfa;~PpUtCjP8 z&?oLZrFYC}Fv(2TIl_&;s-~zT)VZi`Xq8=Wka1S72sdgw0kn1u7DZ^yNm@;^rouv7 z1n8VCf(qRv*d8Eamz$=RHa@ZA*yh~qNH~nl#N>1skYnJO1gAu%O0Z0V-_q>BSVOZ^ z;_V6ONS!wUrF1)JVuq-Nd}Yth24y}byDDNcxcZ2uWgxEk{G^{tQF|S4pCXgO1Y{Ip zhBZo!OLul>n|pUIX-Lc;?<`($>SYTSTz2Y$;&$lS4XJE88mm z1^SjH+uAW6Xf(L?upTyv%i!x-P@ULG`l;!WRjBj>tdP{@T~myvgvM@?ClloAIO)b{ z3wK_oCNYoL@z$@m}5sQh`-_S0VS0&sALs84vV6`meF%4svXkMCLfAPRTi>~+| z60O)TKCt|xll1x24BI<5p|YaJZB9wfeZ%m2_LqFxJd4xf@@tNo{bzRC z_Dw$dkIP%~V;@|sqJ1QThc%?(g-lh8kMWuy_@6D#P%x3iI7Z^Rpy7OG_FU>Mu_W9n zIYU{w=}%O3m>Ivzb^74NAaj!JDWZT`NL6Zb$dtG=@0JqWdIQYNB|vrS3gIc1&+^Gd z9pa-&Eh{#$P}{VH+q=MK)1K%41}FtG0QM#)e>sT{03wHH2$*XFYAMtH@9cmhAy4?8 z&-SU8Xa~}!o6I2RxkNY@hL5;0jV+m?*AOKtRA2Kl9BTasO z>!VzL&Av+#5C+i%KfHK*d_!7|Cgx=iAizLB2q`8F%z6qWm`k~pjK}1@-z*YJCC4rm zp{o9a0VYr?u~G2k=EN^NeS*1fp@x8+DDse`)gsOd{E3H3Sx=vS)A1%Z-oYnVKn|r5 z;3+*~@eX7}N!sldLJtwsTFsbkvcYF1S~E1Kg`hc_;*RqSo1}0tFztm?jfh`9)!Yva zSrXV`O8b$<=?c3&>E#|;%7swJ@XqpcZ$!8xKSfW1+pG%tltYC{Q|Xy2W1*D+Z$kMr zI^YMp6qf!S(mvRYZ$Om-_~g&@csd4Kw}qXB`?k3GM-wg<{L#bcyLMUm)PcA(&r z+CX&uC<&(0QSOjWw8BN!4E1{k-tecQc_dDAN$Hw3oWas3I+~hxHPm5l9u0fh6~(QA zV2efhuC__@+eh`kdKLv*37XQhk6uvTv6M80QBV&rh#e&JU6PB!_0jvU&uE|ge;9w|FAz=T_tkv=0_wpw1}d0);sH!6gg6Jk?&m; z1PnKoSeW?*oRNUDRr}OGR|?=oY`AJ1oPUt1xnqzG0jv1WlLCn2CtjS&K7CwyfTnm{Ll4wCUV(Eg^D2dRpt48cYW>O&bW^0_m#SX z_UcwEK9x0ejgFXS{CAg(-FwqT&Aw`n-E3@-e{zlT?p|t|Yx1Ca({H!^;n0qM{aiXx z;V>JLQG38$%DaE;IlWX`8MGJu=%IDjY;9e$`a!z2wL1S4{4QO+wsy(h1vLv7ElY;$ zr;e-}<*oP_)b`YBY|X&lJ2y9NXpPiaD}%P8qYte`m(U`tWn;!K8IJ%@<+HExyn3Lx zYT`W76Nt=vn3YIE&ym~7xb5Fl^ZtnQ0*YIXzc(WsTGqN7$-3NGoY^-JdMRz7p-q8 zJl0Fcp5xa6X}WqTtv`wpI%&3mFKLQM4METaxH~4F-k7FP(7sVLerZF)(%wjm~UJ=^A{=foOJ2)g5FHG%Vvy)TT&1tCNl}%njbg4SnSxo}PPfIEU9o#68ut0@~(` zy@ zYT12sTj}N_1kO?A&SHas?WiU&o0901yjOWHR94Pr#Urf9zPxjUwEM>Egqk4%ACr9UW! zCF!rGQd1dx^E|oLrHZhmB%>)z$}oq@5L*VKEe*QIW3{!hsfD#|q*pqk`#QTz|9u)p zRrYQ-t+6|Mx6hj#(Es;jw|t7=h`|pi$c{FINKC}$BPtJiNJ^njj93?(T*%&}BPN}c zTmt1#?qGs3iu4#!FtT-ZS7&*44NVG~c#v;l-Of4~2V|HFj?EPd&}XbgON4 zRyQx{A&RO@jve8Q6v=K!$GD0;F@Hmwndg=v1B3DY9cw4vLeO)hp$U#NNDApF*Zm4{ zBW9mh`57493LC3#>n^r?%O6`f+PV3X1r5vRMA{oC%an6&)@Qu~+5fgS&s|;8;f&$kxaYnilesqVSRf;uuV)DDlg zFCUufTDs04O?7&!svrrB-`^qM>2}u)Z|Zn=s$qdt4Gts@xhuoXPf zg~((nCiebx=zzrrn+yY?WW<|0mJjKkTef!XvZ=12K()c!F8)7QI$lnvzNccZ-xCGi^VZ16q zhC*Gs-SVB@1uHi+Hf~tCz{_81{^s^~Cyj>ecJU@+Y;_AFy657(iHeFuZ+xl?b3-F! z5{<6Pt34aeHI>kP``p$(NiBfYBNksz&;WegOR-yWC!cZWJh z5?g|ESYkwsaQveHmnUDOtGSc5GiTl&_X z(_pCdQ1m%x*DrW><)KqXl1_I#a?T>`a?B665+Y_EKI0B8j{~0yE-L}x)d`@9gh4Kk z0w9}+PANd$!Csz#H6I~2Zz@S1>@N3P@GtuvhcPpW25L%xiQnKZ2YZF?Wv}p1`0RHq zuCg*0H5FK(U__wdoIa?|JfttlB}68VZ)`}635xD2@Co2F=HU#xzc)F%Iz8;HiaXi1R=$Bv@8iWQ`Zs= zP&gdY`=$b_aIZ;P*gGAF!aWr$$q^P%WH`}P;76hXI=8k&2UKiI(@NR;bCgrVb}lXh zI<-nIL$T)h4h=dE;vQlW}d zKl#l53tzfxs|7Ph^Y@MZYnE!IcGll_;iCE`HsZ*pOj=S03I|8Py8c%q2ksslyX(Nn z@|L#Md*-C}u4=FL~FH5v9ja^bvX*MI*)I4A+wMw;qLU%Cn9f`$)# zX5-Rp`ju}uSX@g+K{4>uwW=}7d7KVornY_X?CqMiKv+^v^o#sUMYzYWQNC@ux2YDs zajgH|M27Z-j*HS${PWo36Z_(6-i0$90NF*_Mo==x9$}ZUG@Nm<&%m9H0dm^A92^jG zMqwA3fWf8l%|!rR&m4T0sW2WlXz><;ND>zS-*R18M*_H;u|vTpeWckCPZzHxtl_>V zgAK~CdsQ;I%B>7H$b)`pR)jlG)XzRwT5A7M_NAlt(o*(0-5(bWv1>>{SYQUvMR8I0 z%6UTz)}53sscCAek>5H=Z<`aDv z;bb98v=dfVdFH%*h?IJQWfO z_eoS**4^w0&kHVGLm?AsXp-wxg=5ku)Xf@lgyxMHCC-W9D}ZdX^I!>7wdeMVv{BeB#PVbrRf8%d=7`Yi2FV3#7OwogBv;0IAo~Y3%GV z=@%xKDFUf3FSF-~Hf?jxx5vTyVAdczlPCcRGT9$(tkLhTO(tt6Z>?#J_9uOY73J2+ z+e*vb;jp{BRDXuGoL#N|Q)Dn%<;q^|Uf$HS+?~DJRn;^Ae%E7`&o@QflXiE+B%g2g zfIew-lXCNK)}UJ`rc|B^Yl4Y~0!s}K;#*B9rBq`nm*KgcgoC`mM};&$pAAo$L)esM zXf?8vB=rjgHtkAUGXq~Ug-2wQ0~{GC>6$DG7-av*egnt3G&L2O?dT$bpv)yQnFBw~ zIUe#|0*oy9Ct4?kr?;EpSD>E9b;XNLX~$>(h^UAE&6xlx(pVy{<$&Z2M8&7`L(T*r zHY_@ny%8Bh9aM`7>6$+DJP_+8rk4E%ozg7G^qJEQo8CpPxw)|2K%T(B2lUWhYTOh> z<#PpuD3HVp4G|*_#eu=tMXCV7nLsh1>k1I7g5U(fv%`Uba2#-9L2fJxl*+OCf^oEI#O8Y^ zpS`{S{dK8`=YCFU$OVq^@IjY3wNG-#!71`4WPMY%~`3z{f+lEtQlw<#}H`39*=hf zTPvm>D%GduJGbhGyz^s;p;mvDck+qst@Zw3@7f(}dyC6j`WMD+p;A++FJ4nx5h+D_ zPG`g@JLV21BYsb3$ll%@>0PsPP4A*1;Ln_77=ta9IL3>lo1}Xs`03CF1YfIpBmkf0 z>|!KwYbqCxNlB*$K^Qlnup77m0AE5wC0HC)L#QeUXS#cGv!)QN(J`LO>qY|+sh(zx zI}H#v%+NMu#myTR)aA}8DUG(niiSYAsK_9$$ zo?LMR@l{XbU`$>Z8*KE9UUvWL)%RaEs;lzYvX9$516$AU={bMv0F6Tg#S1zxe|<|4 zofD+fNc=tbLe|LC1eVr-{ZFxiB8pxw&GgLd&=9-o?v{NkHqcO6*)R}0`S9{cWclG! zE3DRvk+sd`<;`p1A*S(ZWLJigB)3-FAw*EUnZf;C0v)KXmvTy@T=^J#nk2z*Vzio# zmfq~RHJIN;L!eA*jg2hE#kuNQc(0?5&CGGluzT=*^AAAN?%N_-1n((Y~S|8r}kAc zBv^I_&BgYzz`E1U?N=sd0ta=RcMh1k+*~Z{PS%!bLV~Gw?x36w_l>oC+pApOn9o?e z^`5f^2hO@@t6ZY<#i}Z*aAmA7tfXhIHgPSGvAK>Xgs6{UMAz>GUA(n`8s>|Ps4O_P z5Oy)oB#Y|4`8mr^8*6}99p8ju?kz1C)%&2`G@9oTt3o$YE$b#RdE&08w1-fyzX~16U~?<0y{+_7@f=zc*mDOi0h1;#ecG34gzL*OA3C=3izplHg*I$Mz+2!S1?%X{`Nk<}| zZf*?(LQC3g@aQiqnRn)rI(Ulr)_V&^AUx{QJnM-Q+~i*1L!pH#DC02G?f^GA%()ws zs0);e#3X34Srk8UlF%Y}?=ZlhxJm@y%L;YI2MZd2jw5=YYT zF5K6d_Y9(=125mTAy3@}bArN|a5y_WFWr~@vDt1mNM@Tk`$bEnysFLZbIWV>v_+J) zD5^QN?|3eS`m1O%(G4rNYgoe5GJe@CT|lAtYF|iz;X7q!cKC2=*NtWzbAP4q(o{sL=$G)VkPm$T!Dd7A)Ih zb1hBmK6LZa*l=~R+0&r&pK-&38xH>H)0?X2ZySJ)h4_E*)^maV&zbGLb@iOJjf0&c z1qvKgpp*tjD-88QN9}UI&kQ(Jr)bf|vGd^_Ub;MDvAc5ha}Fj_jUJQPF#oJitZLif zFDfzV-fwDnMH-O+_t;sgXsE6&Y%E|oah(oZxr-0 zy-z|hc?5!fNtH%Y4euL|5q?l)?H)jq0Ief>1K_fxJUu4VK-UQ)iM#oGq6Pd3`H*u+ zi~k}s&BoTaE#di_imNQ8Eo@#Bl5d$WLX^Kc$&1M_eW@1@WThTYSHR*bp&n)QRpPP)Iz8SJ z>Xim>c7TZZY@}*YRU#}?>#v#Lih$lgqvV(mvY7(aF{<0u>k6-&@9#Eo*9S`R>r*eA>D!xB6)CaF(AkD;*# zL0$~nmpwlE+%W@Gs>!q{Y}XU6S`gU^+-o< zp1*bPDNC&7B~$nIc8*^>vhMcg1MZsIq<>&f^^4dlWmQEl+IxbwDo>?cGWE27TW904 z-mu-STa`_ZDctALlblz#R1>caS83r|W-i;*48Z|n%s784Sng0HOiWEv14Im)p=AAz z5qMLqT=ZzU*Iv}!ru@tXI$$kj)n1<&>GxK#>-2wVY>&)Y-Vo~dPU_h6oW|BKpWAFb zM-!m6_%g0k7og7~rWsDRvKF8zLiN@Od8k#7D z@xa*)`W3|neN}Z$1T@uHtVs3}_V4FSKGC^EXlwLCS(7K~DKi%NUEWlpW6Q?%NqvQ$ zRX~WeMoV3ta|U`^9hNdvNx7k!WhmTAR}6H!{k?B5MFu4fn6g8;On%Z#Kcb4FX+^CR!^N1ZfW4ccOh23oAeT&WcF*Uw+dbw;zs;S)6>2Ml}* zogL;IlWkHpK}9lV8(uyXjK%fJ6sv`3Bte-pY6&6FhAJ@#$&`aUBvZeQLNb5lTM(ZF zZq|KUJ~;IfUY0B@4*m7eABKvG?39(j!M;HoTcA!L0KG>fA3#6;?#X*Z9ObWk{y4jb zXERVE`X{fYE-FiWqLWGqb9@4(J7H;Q?Pj;TwQ%6CjhN(P@I1Yh)yo$)aVc^4t&1*HL0>e~Rl`n8Cve*$yI zm7KsN1k)rq-~+gl;!KF*cZAWU_qlSnW5#{%mn+Tw|5j_L2F6aA4j*uW~WuQ-Ln9u>Wv_ znYwa%<6OL3zF;utur&A3*)1f>|56(oL0-Kss-$w$i7{v7hNBZ)pinZJjJBH1hFnTD zjf-k5QG6f01++05&G$(?(E7sr1&a_KfYKa@NsWPqhFfT+^n|qZWN6VxMQIb>n$}FX zm+ulrm!bd6+t*Ltu;a07R>p=l@4IA(Jv4O5zRg21 z@vGPwt12(AiaC{?+L8X~s+-RoTzlKz!S2)V*fMnR;GxCqKY!(TdG>~t*F3gk`t8c7 z=#1S-eXu*VK16R4SsYZZ#$RYNdcRj;Z|BFWub6ebQ>^vGQM-m9wpRZfo9SU_Xt~2h`j&yDGTl+=dychM|+DNwI3_9sJx{5fz z1|kDyKIfbVz5*FbRV`XPp_j7b{>8P_Q_wHpiyuLHLhWMwknhc1$&TYk?#eGdP_yiK zLDvLhYMsmxXsveP7eGr`>xddsG$K6Tt0d!FlaP+@lD>kuAvmDHx=d;>e|-adrGZ*%464#>Sb$j)4Y`p;r>vp($@e% zI^5u^jD`Bc4J+q06^c-O19a*13ekgDe z<$e(IEsUIV96I_1wnP|11x1lRiA~qP*B}Uu9C8-5BMj2wAgd$ro z#j1SA8RWv_X1`V0-Jc5Aty#UIy4>fWbVxk?E ziBV@+RJZ7pV~%8FO_}jqsq-3NG6Vs2{jmP`uR#RJ_zxU;IcydjKfvO6 zbfUh|x%$%!>qkm;<`PF)0Y|wM`-N}~uMnVRGUk#SS#mIKxqQYaD5xphffLIZnVdFsAF_8HpZq_L$| ziynUF=w3BH@ucLbpA)W&vX|qHjd3LzMaX8oCvCTyP3)EOa;4E^w%VaXS0*&icKEqd zH|PI)*QpPj(=U?G3P1Grlds<>P?XG{B&ueLQ7Y%Msqmb7Z^e7g#SIOMo$op5x#(I! zL6ss!Z?{TCWGCfXrY1yuirV7mLdH6b90G+L6eYtTMqe#-I4a5&Bb4soyE}=i~!Agog58DQ+WT3{+G?JYF20OM75>)mDKF z(gc&FKj*NTcUW>n6BNK2c7slm^%AM2+FGm0E0Y+zxNzldQeBdoR3BD>dSL12$R|je z625FXj)A!ZMO`v@%Ik0b=c%Xu=bNveGPs0Z7!!lpO~umpzbonQPfz?53At88A7Wb` zd4z3wIII+ywD2H)6>up8)-cCZec&8(yVyS#=`Kl#7U18j7dNG&WMC?%SYj zAR46mi^9uz=SgXzI{O!x8;T7?>e$B^@qohlfsW)fdq8=OXc*bOkgGWjSUF+6P;SEl zaF7ViO@_3wps?-74l|Bu`AR4LsvVh#VKQ=FXCg(5EvK@Qf$zU80Fz3_iyW!>;_JKDSO*;aU#4=)BlIb=Ess#8(DLYW%R^pzVvFaGxFp6QE)CC$^~m*l5c9xgQ|1(M zC+TSgXGai+N)q591nlMDk;|v~cC0{3rQ|BApD^wc(kseV<)l`+E}RdE`{Jle()eSQ zvHe5k=1y@aE_ZIO)H!Fiq!$wk$cZ!^3nWV1w07RWdk-pK|B~|c;PgjOXKUDKb9cVi z?q)W+W_l~)yG6J3AVMbMfpiW(@|U!v}Gsl;QVja=) z@@Pjaw0!)`9>K6Tc^j6@>vDyXeyi1=47s}IEfF3RLdL8@eu_<4>&s@vBS=kyMT;R< z7cy$2iao7_Bb<{j$#!PbgiY>&kEQM4mBYe>&Kar{k#a&6T2W?!^dv$Yg&_sDF0(|@ zQxjSt!4CF1)yR`yr?9Pe(()B3y|Wgj1CapeUPAF_*hIMeqS{qR5}8(@luMLi z#~El}i8OyWAcmZFA92^v5ygmsB+*CRjVj-w+PGBhkhUl3M%uLEwgR;k?a7a#pmGpV zB#KT%91DA*8m0xMPEBW<4Qlz)!n&p*Pv^T2&tAn;t8r?ODr);{i!HgDri=v#p+Bkb z4U?a=*_2p_Doi+%BPUj2eIO;*J+zn_+dHSKc+VbU58{&)Oxl*U)g_TebrTv`` z9k=8u{MC6;)H#EDPTAaK@sxSB3aBOS5(~6-szbOvVzVQbqrP{t`lIN|QksNMn+cm> z8tg7XUckfBCtw@={yzr0$C3>WxC8GGxRC2LxDDvt$Phw=8?aA1h2RzLhjcKpSc~$ypSs1@@r(BbDz+=s>EkfvPxf>-TP?-P>onF#p#92uI=sO> zGii--S-5*iee=L+WAhqZuKLvKU47m4OL{`kSZ0v3B4bJ;ThOV%ao1BM0-~}ZH6hzG zM_57D*`>;++=Ej#skK3yz?bntlLpQ|&^<|AM}bU>C}xOZ{C|!TiN8odbHmvRN*?VV z(6KPkCmE#?PRLQA@ay3^BLsCzWy1$rAF(97Wq$MJeMQQ*Id=sftGDX`c%4NY7dP&m zTj8&&W*K`~dANM&oR-GL9pDon+DiUtxOaI|-L|e$Z)rJ1E7W#ae7HMs(yFy}R%}qY zU-^!+rYjohsfTc(+_cmjgnyErrA*<9im-{L^l`5fs;#MOScUG8vtsR$QyBW8Oc&oh z@|fUkuUGzj*#vwr;{GSb?jLc*Vh&%i#cpXlGd1+-FMMewHL9sv`{Xyiys%{~Y%Vbu zSD~#=Uwzs@_0o}kr#Y^BZT_`aAL<2*a_z)3M=RVhpT(j}BST`v6iAvk?yiu3tXu*{ zmD3_vFQ}dlY=_~3YE^9oRji;3omRO@=g_s$60{AlLDr^&kc>4zHQh2s6(gZq;WZ+E z&5YM;1*;A}`Dg2> z9Q@m9ANy%8PySt+`%5u$|X?s zztf+*cE!}g?t1sSdk!wt>6o5|pvwzpsi#s;|o7SWDou#+SJ+bhW z?#zu53q=0|@4-fhBf?yi&P)iDLf5K62v4XKK(=*KB~5h5ahuZ#4!#{q>A2ITlj0qe ztq4_Z4L{(59GBs;KeaV`@Y5Nm2A*bW=R^{u4zvUR&z}#04q9dLd;!+{!GbYW4Ic!d z0#cz}&q{XfxflRCm18s}da4{3!_XbV6C`^De=J>a+Gt>0+`v6Fd>Tjo`3$kR;{C}e z#x<>6?&jM}SjM0-QMbRxr&W%{ammB4a2O?1bTKj#507D;iG3@O8DF$ zLkPwpW&@{uo+dP<$J2o1c&t;M9U25_s|Xs5Dd?ZW@P%50EFFmD7E(f9D46cN4Y@#M z3n0fRRX;d00{z52m^e()K(zUMH$~e+=O{WXNbs zuFii<+a8l#11zG7N!KSLj?=`RQs)Xj^Ht7f-YjM+%{>a7QWP^aqRkY?Y#dZ51sSj& z@ZF60BIFZfAGr<1jCvib*eYQ%P`za*GBkWWe+fot<|DQ+rht$}*(k?c3irK5|MUzI z>h)o+Nz8CSddGB?d=;m*Vm{Oy9l1F>KrzZopXZsCMah%2lVy-*8RzH{&Nx6fZA%(uhHZdQco8((9Qxq0zL3< z#C7Q7W@u?*s!vixwW)K~wR@^AuUX&RyuRl0Dvu`XQj|&C`i9HB zKA-pUhV^Y)*0dA0U)ONh>+3jNFPcws<+rBKrf;EaFFcc>9Z`59d(0U<-#>+IScXft zVu$n{{VpgQJ|OPEC6IdJVDeI-GtF}1hC?@tsJ@7q^g z6c6|-_R{Z~ihcVkYW$_XBDRGNte)b%dy73*?Sj_KQiKQT)1+h7rKJk|n7#v&vOYai z+0QaF_3C6#Vv=l9Oz*)Heh{AU6yFU+shH0uUeel*g2Kdq!c`Ahx1e|s+YJU{#5R*} zpHPgZY-}72zA$gsaP{JAzV!!Yy?OlGkAHFg(NN@_ z%fj=v4nDeJym{RPw;$Pj>7T|QP2K*vhu5e7_`(+t&NF7KPMP@mwvvRcrb_?mD#f+@ z+h^?l_AP4{E;ghaS5n83Na*PB7asrimYbRN51DHgS1;H(?}gx~(mM9lFWfQb(eXcB zy7|cM7p!X@-|*;ed*52~%-Mcf(k4ssFVDM2W zE0+m~?h>#9cBj*tnY2*`HrMFtyCpz#Q0$!vRSu(80UX)_(T@K-g)D^RF z1kU!cGnAg9;*gtt&t8KN;C*Jjd=`7M$Xnu$>E=yL6j#P11}3J(q^NzM3Mwc8fKCgm zG)nAy?oe@&(sKs<>$!#hNy@jdE`^ntj$YF~LfepO`R8$cPxFcml8j=duxy zIDNG6PgbNaYhBCdMd}Vz$Ls_b)5_88 zx7zj`Xx;F!JIN_HCu!Cb9+@VofMpkSoU1BnGhoGu8#PPh&OLb*do}02J@vdl81(Df za?agKo&Eqhfa@jtRO)jVcpJEa<6HCnR_f8;jIjvz9FlYapRO?t+(Uzp#>zzhK|gzS z@+x`~jZA+)smpav&k6c5zQw8!Iy*`S)RrC1jf$?NNfPOU+~u#|c>pBjj_%-*RrX7kaDHhuXQl5;-&$~V!A z*5H`=orAZ^Nflhs;3MVOj=xgD+vHx+)TTY1zNg06u=aZ@7gRuJbVrmYwJQQr^4Hn$ z)R^{~^sUSFsXVDWqFn)rOsm`{<@FzgckwQGTHYYtgK_iWa=9({2Kgc9Wu;g_)SKo% zOui7BkHr64KRNa(@1vhD=#oDNLo8^7@D&phm^hxw(TjZBf8>6_G3M{hM-j|C2f;>U zh)^BY(%OK8&^EI&|~A8$ic<7Kd}+)sWPx%HVt(fG7R?!0=8oLAI! zf#;>_%7T=4loA|hlRPkK{Z34gPb>+)&((~^&Wh^JO!)czKszg@OMHrGL~7R)fVWe3 zbFBCWG>ym5b(W#iLh7CR2v=zpj2uL$LYP*^Yl5F4hdg?UH~uab24P-te^c1u2sa^S zbppTispE<1%5eqw<6oKXv;I$6;}fsbi6P*`SMv!EKl~c&j#E^Er!06#+`j`KkR0lTP}51>vChB z#`~IB4c;y;KckKOWVSv|Lm!60udPsRMJ*w=xUis~`@j@5O_ z8fT8`gF-7@k7r-**`V}()-n4k&jy8Kp`Gaa$mUU_k%3n@5t?@0!W z$z9zl!6#d_{n4DAFJ1G`nKg1!$xE2rr_RV-OCZJnQ|D86TS-%Ma}!)wgU&g9eRG^a zsHo5QXU_2*4*P=Z$JSgoVu$yLZ`m{3wmq}V7auxi<_$FSCP{_XOQbtc=}lA`AlOnv zvPf}9>a7z&ELm)4^#~UFk@tb;f9A>a?D#L zn-rYXG3>~Tc}DGeX1R%YWGn#)s*pkOOz>73r`rJ)@l0kIwvfp@Q%j2C=~4#UdXw_^ z0tWU)Xp3jc6WoeGRvCCpc;Rh$4x+Fhl%D1|M@w^RMPPtx2_U_h$hQdX1Y*-k9YxwG z=Bm()1;Vc5a>JU$IikD)w2FZTE>I5|MH;PS&Lds*k%+@@ln7TN_b1hqaWjpabNAk~ za*w8I+Zpa!T-Ug`D`4ES@}|9WHC0+pAvm>zwXWVfFt~43OVj9{!T5rR`CI=M8IBL^ z8EtM}y>D<}@9I{@I%J!p-W}?$Lvn>Fk5T!7Gv-GImAF&QM`C<~I&K{G8+|pcRqlF+ z@^x16OM_XjJ?~xQO3AgxUHTD@}g^h7DxVR&%)p*3TOtQd-%P&`>T-;iw`yyI&>JeTqpW5aQ8xinMlt?oyob*vghT)^3$3J3aZ3_jZVw)TX-kv%kiEF%)CX^Ldn5Mzo>76?^^D8{$&FKod9&qr!RI!ywx>!2y58C7r&=)9G(OvKTrC&Bq8}U zx60+>k$|PS?%8E`Ruof-T#jSOJclNvZWf84;V&t8Lmr@n@FmJv1y%sT)v}!o!81S! zG=Q^~JX`=ylPb_%>~YsEnGYxXZM^V=8R_}a}opMCD7ee1qH zwqnerEBBXlE!)~Yxi<)B`9yz>b1=L*X?5rg#-fsPOJw<~(U_}oed8yX@5)QRb7<}) z-+3wog67i>6q2zy!kq2zAQRF>Z);&)!r(5lWouLsSq(GUwg?;i^U_Z|Cfbvr000=v_ zfTOBx*}kh5>2I%F*byiyD)Cs_TO++o>fIi!&)3)Od+E7nci#NkjSCHDyJDlGDL}`O(Loo;v#k+e6OGT(@k7c1)vR z+cEm|%`AT7N$0Gn3wNDV>rS}*J%Ov0Ki$0Lp$G0AtzX?TdWNIIWUQLgvE(SUuXR(O zVTe-D>CGh;tXr_#xABo}k7l+%{>Z}{ib{+9jh?n;omVUWdF_qL+rN2gsJWxkAbGS zigv*5xXVHI9%zHOSYAT=keRUO0&?3dc$z|Su;0|2aU4_#V63vp62}+N=}_I7k>-mT zDIk;8>pit|5__2*8Z+mig&$woxjEjyXLU<(yRC2e<~A5^s*AU~dphn;_77h@e7=%Z z-uvsW)^s}oT?n&*y%zwt)}?E?(?qQv~jiax8|kYfwVo`RK=`=^A~@-^PZ&1 ztar4mo*O!?SE(-{>WdAmdP;&y(1Yohq15B>&#m_NRQi`Rb}g*8v$UbCvce722%cS6 zQt9;7rV#^|Uy((~nRqp__kW&8MPp486yC)55O!5tYY90hASbq9S!q@J83hw0k>j5- zfAK^%yf6aVwROGk9#gU6EUPqan;E#*IqkV=OZG_H=H-31?ZKASd-~&>JF!}au#nKe z?oG_8^&mu!B5!GS>J1&OJOB2cq)eSRe9drw^6rivx2M@t9Bb+hEIr{~n{5rVjnns= zoSAJ=1KmrTS?TJHo31@?_dvYKU^2QoHx5^(7LPS3^~MqtXnswapr(W!?L94&THWf< zn@!1kI__)PO#rga0}kA+aCH*I_!{?b&jo1=E@ zczQp)5_5!gD1mU=vc|?`r}e4d@}*y7EB|bKz2FqTG!dedQ{b_{Bcz}!M7Rg^0At7Y z>QG@*iw!oY1ckIjB9l|W4(iH>lFK}y5~VeocP`b~Js0VX+5>_11vToo?)#m)R}CzDq&Decf1;^Q}@)h&DHMytDj%AR@S4ff1Wu@@Y3_ue|H{J*}_5@o^aT5q7SKH0mhgKcXY>8x*T zk1=D<&ZUh3cTeA(%Fig7`|eXRpQ)VF*W(T}F5TI~jMW{D4V@!NElF7RGqkZY^dWq+ zGd{SY%hJ$Ux21*bFiVquRJn(+gbBl8tpX^cQDzN46@N+Q)g4KY1ytG zwlr8|ww4qd0*J9T6qi`dMM33}o?Xju8BN%-nr{BY0~$r7?}XPIBfj&qlHGK*>5#Ml zd3(Mfy)5F1h}BVxTL~p2riC-3N%(|;fw6LeCwLzu?4!(xsL#kX1A3uG5YR@e0s?8^ z&e34qsi-J&R&tIL=sd>x9^MV7uY~E7Is;E@$*l!ZW5i?-4-;!-5`}{Uy3fY@m(^@~ zgyOwJ0cRzmHH+GI ztZZf7{o4;7JR`dJ6H7ev7majB9Dy2CL9&#U87%F6U8M_eSm0W`etdMW#O04xIqRZM zc3752)?a>k-TXBT?8}~@udBVW-0LiQz!YsAh}i4v>+R9rhDtW#iW(l!=?D6l&ZJXT zy4vdf7Jp0PVPA}`Wr0d-qBd@*Yu~bJ#6NGx{76ZlZQ1UHHA@!^c#5DPM=A?PqM^l~ z9Bv6ZOu>OoefHKlb0dlFI)8~d==N3X$~#Zmy0c;F-1damWVP!o6_K{;ism|hwa;Er zR%$U@jB>eE@9>7>wa#Ln&lh)BH%>lSghcdbUvNd+XsV&~LQ`pF)D>M_SCg1}C)`~N z5?`bah;==^4Ao=nB>Q60KF1n9rx1t*Dj!GjZb@+boFuBq zif4hqtk4!!6$c2V<@=B06!uG^U%_7mLa38=(GvC!#Ul9^Iyt4aq;n{=~L!$2Kg(( zYYfB9dx#Mo*7ytZ!jlAlk>@Gssk3F@{(z~mMOiCRX_B-@Q*Y;VM)0yyISpje1}DNj z{v~4u5~UOQ7m*ew=P8tP%XEOFNeVrxCV@johvny_ifi?O7Rkyd&R8+0S{)SaJ1tA3 z#vyeM5Aum$_4_Yvr}bI4a`dHh&UtBc*}|@2}O@=_#bJX@kS_sO35c0!Ym)0&>)x!$ZS5< zdwEkh+(dsY1BMJe@w7<45$BqS?(=L@RPFq9P^6os2SAaCa}rFvnkJ6Z4MF^2lCLHj zZ|oAA!$xY({3=6y zTs9{XnX|0XZvPfbf9E?a{Vlt_;GBYU3eLF%|H(^{LNeokfgj?a*ijs7Z;urlo>k6& z?m6ZBXAR=FL%xP?(}0LT2<50}oY5b1hDlMB&CxGwSUxAJ^M^uy9sQpCs`woa`*nvW zX?H7MmGv@PK!?Q7Do@bQWpp?x>)K=bWwCY^42OfPJvRB(Si4TnYEAifq-n>D-#luN z?pLo9+eTEv^grO<)j0k2ix!hPal`F{^AV`uH%pK=j05@XcfF-%04j~?*}QS=1BN$(1fwrL2qgEUKxf%Ac$7igOPCWV))N+-E66 z%29VkRhgx%I#`iEN_M+{|-^sO4 zlmk|hHeWIOSzd)ssq~DQ+1@dK&hHR&;jK9K!P*(W!|Hci{SN+?gM(8?oB?xqZ12@$ zE&H|(mPNYj?1`Y$<#WfqmWraLmYTVv^~0wvtgcRtZ9lDj^tR#7E!F05s4^ydv>uOr zUG*6guTa38yyIp~f4{ORo1-#vCNMXwDfDm7-b%Rzfr{{tM-loo8$Oc!Q;vW53C+Lw z;aC5D`0(FfefZ+}>har0ajeTsJEvVg`Zl39qV*)jp+9(hf}6?rm+sqr$2Wd-_SrxB z#vQx+3XgS}W6$yH;GgpSfq$CW<4nHkxKA)jt(y+r^|fu=zINB4O|6B;$9?wM$F}A7 z;T_8`XZuatD3?GRVeVNDOxbE9Ad;Hg$Dv^2mRw2(~Xs1looZ$vVRut=-+N z6M?#9Vz@1UzHWeT(qD|flFC)ysL(w0LS>Cf{tE@z$bT^>5}blJ|f2Iq*s|qnT%( z@_T;0pwoIRA7z1Kw0?3VUsN9b9y4lv?|TfA@rABiUwMU{$y?5KRlA2Gj2I_IoCx9L zi803rvS$i$>-R+7s>%#!Fm%b73F|8&e+jOVF^$hh4D%6(-H|we44QAv;s)f6y3p&V ziKahAPg|Y5A!B05o|J~_Dv}E(!ZpHrxHy178U!sd%0A8TuF$PW8~*o|99)+yH2ieQ z!bBi-;a%Zdx_#jiz7ekc-y6iQ;Uf{yu3(r_OpYrt1MxkAsPpWME0^LlIR2T2D{#Q0 zfSXa}1=tk2X`ZC*&O4y|u%TR&7;Uf=C2 zM8WG!_>EV^T54(=ykH)zR$?8{tM;`+(df|H_J*OJ4qssz6<}4zbkLRn83N( zYW5=yloI0&BD3|Nzh`lCsWa@#$s5|Yq;6<>PH7%XouB+wZrwj-L z_|9jPn*)9AwMED3?#2@?(XV{dv4~-^6`+^%T>ZBOewNEaZ|BsvIpV~ex99y|%Pv=$p7u~&Y zsAj?5;S~?gT=e+b<@fI&eCU3|+MK?NuUoSJ^9OsP^$&E!Di6_ORG^Q{R?Of>a4V` zHFK~SyU}FDvdBnL|LHw1-aLQt9WNbR_|Wk1nPtoF*wz!3FG?HM@s#l07u-U)Mvt9R3>=a2Ls>*+ccTX)BfIrXJV_X`^rUb(b3 zy6DPfv#wm*TD|ltMVx=#lb7uH>K)5sJ8u8@V+$^R_2s8;Tx4~g@_1+LKK;o@=N#OZ zI6i&H&9kvw{aqW6&zyblk$E&bMS#rTvAGrinZ{hn6rza^NJa8bFMa;OLxFp|2R{1L zhQxPGOM0JE?^bVCulT`>;!!wiU-T$&v1EhK&u=l(DC(*&K!Z9oan_o0$YqE|yud4i zFvgO@d_sDQ6nk!XDYj#7&Frh6+P?jZ*UYY+zjJ!a^8V1!fd^NveBi)PsBc;FKWpbg zDZX`HZSCBxebctitr5%TUbV8RW%aR<*~eBlH?BNpI9#^mviQthpFI+re&n+|XP(%x z*jYBRdVBYpd$#xVTz2=Go~^4!#J=9`^XuyuZ13sazM#Ht!S?Y(Eo+X?9l2_4YwMb; zX3smmrWvbf!z%uQtzti0MUA)9VaL#h)N3RS(h#*(1nbAtE|S5p>GbxQRTf9*`dhww ze8%y<-V=kD+_Ph5b+MuS`Kz}-wtw2tiKj0eJiNNiQqeJcL-&F!m(^7*y~?x;>v!g{ z`CEVV@>AE(cQ|kJhGRSK_{8H25AT|H-9K*LHG13P#L2eRM+Td>Zd%q~5!-&#$bwt8 z0mx)wEo_Y+VOXhB7z9u!}h9ME7onfRB76N-}dRD z{Nn22y=Qi8xp&84Wl>Sp@V-y3Td;S2Lz&rVubsPR;nIVP8d31OtYQ8>twCZ@-25;h)xC zS4db_!i@M$yI8wJ+4J!(D45hJOzJG;c$hqB({m$#nfQ0H_OEQKU9>NSl#xO| zGB>nyTiv{^hfnmEwE{8c7bpfLyMyCT$E!Mbwa;dFdf!dUhxaY4(eSiUHkDY~zjkQt zT}QWWSkq*!ZEi1jwYe=mU=xf(F)d`{n6R#T;kclw?*EeJ;DW%o$^KRRVl3Y?|)*S0pVziHLQH!jPh4YW@c(;3#gcGC4_>{D)( zV;Uw><-QN0Fr@pctUXVX(PoWw&RLh;`~R3C1(|p1GDbm~Hm_gO2a!b9q+nHW!Veiy zNC+rkYX6L)z5q~gCOO-#OdL07VxRW<&BYgQ!C>ykees;CGh*1CG(1}Q&WR+d;}wV}3E4T@L2YF$`S zj`XHah~KA)`o8!C(w!ph=|fV8&XYe?P*yr%(=~2TvekkI?GvV>5(i@cnf$G3Lzz1v z+`GuZ(#{Kt!}poAF+o9kK z!u%psZK ze0fTYp_XYOX-BZXIcTz$LvFE@ctV!Mu-qO|LXPBJj*xPvQqw|B@bY2JwTdO=DWOE{ z9;+$X%%5%v8B6r1rMP|gJy%mL)l`glO?{XlI zJ09w)c4j>^s!66>J&u>rKvF-~i(GAG>I2o!zW68M1wb2%sUn*X@vAA@0j)e=;B#4h zL@w*Xr$pQR+#M2rC49Zk<#U`Wn3{Gt)}f!{wdKM?KSRH%(s+a5aLX)PIj~gcor|3`uMEeDIyKy5t#}1*bv;=;T%5I=NR87 z3>TA)sCSU^OC}Bv9WqcSIa)Z65uRm+D#QBVp`pZK%9c4OKTl((zX@wd^3@dA(0U;w z8oi(a@Fim1MSlfob>qOjZb(UOlQqSeSON5rp$N(e(cC^nI^=7w{mZMLKQ_dVPyPiu zRc`6au@;>`Q8ed-;T?)28M=c|1_Tk>gp9-f{s7GUNnk0l#3Pi+W4_SV#RrJvB} z`SiQSOHX)f&|Cb+Vk(GQqPLmc@O$wB*>q%F4>3wtCr=SZ9$v%pJQDPC>_l`veeZiz zGEbNAgC_;!TckKF3a|}kds2jTo`kU?EW<0Kl;pq4H=P*csFG{JGma^mLFXAFu*Kgj zoDrTw`?}Zz7z^l%dInh6|;&%vKp-<(9bnXfG zDXaVh_+pNVz(-LOBEbPH^o%01#-WfA#AynT4df9~_#|;zLv`H8bjpWY$a4+&kmyZ`^H;FaJnA_=WZrL(PF= z*_>xKdmP?UXPw*L<|%Kig$rUZNBokw`e3QkS=Etzn-9LAqQZ~BBx>4P{2(20*JJS7 zR5R&6atjI$-LF3UwUYj7aZtR@9IS4v4vhb#yrb5IK%qvDx8LokDnW@xtEajX(18C; zXThs9plE0lS8!O_{5qi(m>t3&6n?M)k?esy5`iN^2?mAKs?-Ep1mV~ZTX|lZ;!?O z(|yEpEAcXWuvVrQr@{y+npjV<+q`1`z`*_$%?ZKWw`FOpCRLUNZ=iQ+S`t+E_L*DEQJ`qS&R^IaF??qw-hlT__iyDeI7MzZ6pHoFghhKSMwG2=O^l2N2RY853H%0 zvps%#0UfP=;FV5l06@(JcR|P}rwCU0*$sYwzzG_*hp4L?Duv*9CItwD6<0zvp^m+ek7TAI=YrUv? zS?}Or@3QKPT9wCXZO#gO5yx%5o-%JwU3qz3x38?nC*Q2`U%D`?e^09T{JuZ1csIz* zKLSpmtO8WY`w-TU0269-WF`*4#f~Dy;gG$ig*a9Ul8Cxo19g$70wC>IE~}4a%fev8F8o*qE|~w z74BhKQc{ls?!+z1NQ4Tr`l`ZL%zjJaxQk`{7o*MLwaDTMWU-2h4#IGs3fi zP{yZLBa2-0B9#yUx5k|^4UvW{a2*?j0v?cdkc@BiBYfppT!WSZWKw~y$yFaYdS_cq zb7Es+KkY8A8Q|MbQ-DPXM~K(cuIH|`gal}Fh}RPeqB8cUf7Vct8nkiI1reN0iuRR}$6T|ZQn zV7N|io;&kOF{0D78|XSiUPsWG*yOH6r2Eo0q#jD#m*;jn%};)=cI7oI%}$paXD$xP)t*uYruwcOrPj5Ap>PM39sXP9re)Kl^`_jZ!4Xtmb zzDfdBCb{&PTbAhGle~n*_{7m&Sf4?ofFkrUO})>u`NQcn^;+~r{Y0W>yqtLc6DVo-=#9!-{e!ghg zvPII*-L?J_I#2rOJn5rzgV5h8{k+6(FG>2k`}(?*K020u-YF*Eu-L+`fI-I5d2CG`IYj{24w7u`2FhZd5HZhPtwdo&$&EC0sGqWnQM12Pex=lJ z79oAAKf+m}9%t&0?DE@hn!Rw|G5-0_Mr<{&`OV~KXs2do0--3ey@;8577m+>LsE9TqOIG1Luf0rK5zE-Y}bW^+^|dbPSaD2xK7Ddv>2E7RPj zvVzozCgeZm=5lcJQioR#Lvo1Xev?;ra8ZDFMv4a;eMLpS#-R2s>j(O`6pVLlKK1xF zwhdq1-hTD)_OCyFYI6thPsHVKAE}Oxl#4^+%i>V^NVIyS!%RwRPB+z*!%2#6S`>mJ0tG(iVUaz_ZMK~hv zV()L?`8s7VF$?tDJ732=?g;L|?{r@^{pS6xv5&ru0)74#iUzt!EwKA21535{w|G9m zlA-*TTE-3ZIC~kjT*Ge27+W2y&`o5H=p_?p7}C`pRG2A{_BM8Ym8hew08vz?1f7&D zyiihDzG`cr*2!~oQ;HhHVP|c?O8H)@W_!fD{6U{b-Qt;@c$=pSr(4EvaMlD$wOiC3 z{vcfG?_rs`JN%`A8o7mjs_3k*ak8{AvpfCH>W0n=%I$*2cKC&0^m9#p zN0|SNHxAd=&ZY)^^e$=!8{E)|$|=M0@Vj_*LTzN|r1B`Yh_|rPDQXN~wo#iTU~cIm z{fLXWgw}*F3vbIW@z$)w7fh;4^W~{F2LDJcAKD0HqKlDL`6%?BH#0fmN#RA*IQ=$W z$aMq6+VFMbxlkD9;6MmA5*(<8lv9O}DXbpq3Ia$Ii4_R+Xm7(MAa-hrMc%h4oUvhS zQ>H8tW>P0 zIBkDTJ-}*%aY+Q1-x)Q6ok-x1o~BP}lx}ADLP#C42IveWK#=B28l^k9zK5Hf=r=We zKl`0)g_8fG9w0pm^0po@%gSg{Af5_0WT@OT6!8!pxxU1RKyK(>=tZN%?(ov))H|M2 zn%JKh@2K9*)VKNwG4%SWewT`(X6zdOq#noP`HnnWbbH($wbiHWym4?)9gs=5#(a